基于TypeScript的DSL:一晚创建自己的编程语言

面向初学者的在线Python训练器

轻松学习Python,无需理论负担。在浏览器中直接解决带自动检查的实践任务,无需安装任何东西。

开始课程

为什么你的项目值得拥有自己的语言?

\n\n

想象一下:你正在编写计算保险费的代码,不再需要几十个嵌套的if-else和魔法数字,而是看到简洁的结构:

\n\n
const rule = new InsuranceRule()\n  .when(age > 25)\n  .and(experience > 2)\n  .then(premium = base * 0.8);
\n\n

这不是魔法。这是DSL(领域特定语言)——一种嵌入在TypeScript中的领域特定语言。在本文中,我们将探讨如何创建这样的语言,让你的代码用业务语言说话,而不是机器语言。

\n\n

什么是DSL,为什么需要它?

\n\n

DSL是为解决特定领域问题而创建的语言。与通用语言(Java、C#、TypeScript)不同,DSL不追求通用性。它牺牲灵活性以换取表现力。

\n\n

何时应该使用DSL:

\n
    \n
  • 业务逻辑经常变化,需要让分析师能够修改
  • \n
  • 项目中有大量重复的配置模式
  • \n
  • 需要将复杂算法与其余代码隔离
  • \n
  • 需要在语法层面进行数据验证
  • \n
\n\n

TypeScript是创建DSL的理想选择。静态类型、强大的类型系统以及编译为JavaScript的能力,使其成为构建内部和外部语言的灵活工具。

\n\n

内部DSL:流畅API与链式调用

\n\n

在TypeScript中创建DSL最简单的方法是使用Fluent Interface(流畅接口)。即每个方法都返回对象自身,从而允许构建调用链。

\n\n

示例:SQL查询构造器

\n\n
class QueryBuilder {\n  private table: string = '';\n  private conditions: string[] = [];\n  private selectedFields: string[] = ['*'];\n\n  select(...fields: string[]): this {\n    this.selectedFields = fields.length ? fields : ['*'];\n    return this;\n  }\n\n  from(table: string): this {\n    this.table = table;\n    return this;\n  }\n\n  where(condition: string): this {\n    this.conditions.push(condition);\n    return this;\n  }\n\n  and(condition: string): this {\n    return this.where(condition);\n  }\n\n  build(): string {\n    let sql = `SELECT ${this.selectedFields.join(', ')} FROM ${this.table}`;\n    if (this.conditions.length) {\n      sql += ` WHERE ${this.conditions.join(' AND ')}`;\n    }\n    return sql;\n  }\n}\n\n// 使用\nconst query = new QueryBuilder()\n  .select('name', 'age')\n  .from('users')\n  .where('age > 18')\n  .and('status = "active"')\n  .build();\n\nconsole.log(query);\n// SELECT name, age FROM users WHERE age > 18 AND status = "active"
\n\n

这是经典的TypeScript内部DSL。它不需要单独的解析器,直接在TypeScript代码中工作。主要优点是完全支持自动补全和类型化。

\n\n

进阶:类型化的业务规则DSL

\n\n

使用泛型和联合类型添加严格类型化:

\n\n
type ComparisonOperator = '>' | '<' | '=' | '!=';\n\nclass RuleBuilder<T extends Record<string, any>> {\n  private conditions: ((data: T) => boolean)[] = [];\n\n  when<K extends keyof T>(field: K, op: ComparisonOperator, value: T[K]): this {\n    this.conditions.push((data: T) => {\n      switch (op) {\n        case '>': return data[field] > value;\n        case '<': return data[field] < value;\n        case '=': return data[field] === value;\n        case '!=': return data[field] !== value;\n      }\n    });\n    return this;\n  }\n\n  and<K extends keyof T>(field: K, op: ComparisonOperator, value: T[K]): this {\n    return this.when(field, op, value);\n  }\n\n  evaluate(data: T): boolean {\n    return this.conditions.every(c => c(data));\n  }\n}\n\n// 用于检查的数据类型\ninterface UserData                

博客

书籍推荐