如何自己设计一个DSL

📅 2026/8/3 19:06:08 👁️ 阅读次数 📝 编程学习
如何自己设计一个DSL

设计DSL并没有想象中那么高深,它分为三个层级,难度从易到难。

从最简单的开始,逐步深入到原理层。


第一层:用通用语言“伪装”DSL(内部DSL)

这是最简单、最安全的方式,不需要自己写解析器,直接利用现有语言的特性。

  • 方法:使用方法链(Fluent API)Builder模式,让代码读起来像英语句子。

  • Java示例(模拟一个条件查询)

    List<User> result = Query.select("name", "age") .from("users") .where("age").greaterThan(18) .and("city").equals("Beijing") .execute();

    虽然它是Java代码,但读起来已经像一门专门查数据的DSL了。这招在构建测试框架(如Mockito)和配置工具时极其常用。

  • 优点:不用学新语法,IDE自动补全,安全可靠。

  • 缺点:受限于宿主语言的语法规则(比如括号、分号去不掉),无法做到极致的简洁。


第二层:解析结构化配置(外部DSL的轻量版)

如果你希望DSL不是代码,而是存成纯文本的配置文件(比如.txt.yaml),方便非开发人员修改,那就需要写一个解释器(Interpreter)

  • 场景:设计一个“简单规则引擎”,比如电商促销规则:IF 订单金额 > 500 THEN 打9折

  • 做法:不用复杂工具,直接用字符串匹配词法分析库(如ANTLR)

  • 极简手写示例(Python)

    rule = "price > 100 AND brand == 'Apple'" # 你写代码把这条字符串拆成: # {"left": "price", "op": ">", "right": 100, "logic": "AND", ...} # 然后判断当前商品是否满足条件。

    这就是DSL解析的最朴素形态。

  • 进阶工具:当规则变复杂时,建议用ANTLR(Java)、Peg.js(JavaScript)或Parsec(Haskell)这类解析器生成器,你只需定义语法规则(类似正则的增强版),它们能自动生成解析代码。


第三层:完整的外部DSL(设计一门“小语言”)

这是最高阶玩法,你需要定义完整的语法(Syntax)语义(Semantics),甚至自己写词法分析器(Lexer)语法分析器(Parser)

  • 经典案例Elasticsearch的查询DSL(基于JSON)PromQL(Prometheus的监控查询语言)

  • 核心流程(这就是编译原理的简化版):

    1. 词法分析:把(age > 18)拆成一堆Token(左括号、标识符age、操作符>、数字18、右括号)。

    2. 语法分析:根据你定义的语法规则(比如比较表达式 -> 标识符 操作符 数字),把这堆Token组装成一棵树——抽象语法树(AST,Abstract Syntax Tree)

    3. 执行/编译:遍历这棵树,执行具体的逻辑(比如调用数据库查询)。

    这里的核心产出就是AST。你看下面这段DSL:

    filter: { range: { age: { gte: 18 } } }

    解析后生成的AST大致是:

    有了这棵树,计算机就知道“需要比较年龄字段,条件是大于等于18”。


进阶实战:在Spring Boot里实现一个极简DSL(思路)

假设你要实现一个权限校验DSL:@Permission("user:delete OR admin:all")

  1. 定义语法:支持ANDOR、括号。

  2. 写解析器:用Java的Pattern先分词,再通过递归下降解析(一种手写解析方式)构建AST。

  3. 执行:校验当前用户权限列表是否满足这棵树的逻辑。

你会发现,整个过程中最难的不是执行,而是“优雅地处理语法错误”(比如少个括号时给出友好的报错),这是工业级DSL最考验功底的地方。


给你一个“该不该造DSL”的判断标准

虽然造DSL很酷,但别为了炫技而造。记住这条铁律:

如果某类配置或规则,需要非程序员(如运营、测试、运维)频繁修改,且修改频率远高于代码发布频率,那么DSL就是最佳方案。否则,直接用代码写死或使用现有工具即可。