知识全景图/ 软件工程与系统/ 编译器与运行时底座/ 如何设计一门编程语言

如何设计一门编程语言

从一个小型记账规则语言出发,把目的、语法、语义、类型、错误、运行时和工具链逐步设计出来

阅读定位: 这页不追求立刻设计 Java、Go、Rust 这种通用语言,而是用一个小型领域语言做训练。先设计一门能表达“记账分类和报销规则”的语言,再回收出通用语言设计的方法。

一句话原则: 设计语言不是先发明漂亮语法,而是先确定谁要用它、它要表达什么、哪些错误必须提前阻止、最后由什么运行时执行。
一、先决定:为什么需要一门语言
问题如果不用新语言会怎样语言设计里的含义
财务人员想配置“餐饮超过 200 元需审批”每次都要找程序员改 Java 代码需要一种业务人员可读、工程师可验证的规则表达
规则越来越多,散落在代码和配置里没人知道哪条规则生效,改一处可能影响很多场景语言需要统一语法、版本和解释规则
错误规则可能导致错报销、错分类上线后才发现,靠人工追账语言必须能在运行前做校验,而不是只靠运行时报错
同一规则要被后端、报表、审计系统理解每个系统各写一套逻辑,结果不一致语言需要清晰语义和可共享的执行结果
二、贯穿案例:设计一门 LedgerRule 语言
规则对象 语法草案 语义解释 类型检查 错误提示 执行模型 工具链
rule meal_limit: when category == "meal" and amount > 200 then require_approval("manager") rule taxi_auto_tag: when merchant contains "Taxi" or category == "transport" then tag("transport")
设计选择为什么这样选暂时不做什么
规则以 rule name 开头每条规则要可命名、可追踪、可禁用先不做复杂模块系统
when 写条件,then 写动作业务人员能看懂触发条件和后果先不支持任意函数和循环
字段只允许访问账目对象防止规则随意读写外部系统先不开放数据库查询
动作只有少数白名单避免规则语言变成不可控脚本先不允许执行任意代码
三、语法:让人写得出来,也让机器读得懂
语法问题设计判断LedgerRule 的选择
关键字要不要像自然语言越自然越易读,但歧义也更容易出现使用 rule / when / then,保持短而固定
表达式要不要支持括号括号能消除歧义,但会增加学习成本支持括号,并明确 and 优先级高于 or
字符串、数字、日期怎么写字面量格式决定解析和错误提示复杂度字符串用双引号,金额用数字,日期先不纳入第一版
缩进是否有意义有意义的缩进更整洁,但复制粘贴容易出错第一版不让缩进承担语义,只用换行分隔
四、语义:同一句话必须只有一个意思
语义问题必须说清什么如果不说清会怎样
多条规则同时命中是全部执行,还是按优先级只执行一条同一账目可能被重复标记或重复审批
动作执行顺序先打标签还是先审批,是否影响后续规则不同系统执行出不同结果
条件里的空值字段不存在时是 false、报错,还是 unknown规则在缺字段数据上表现不稳定
规则失败一条规则失败是否中断全部规则审计和补偿无法判断哪些规则生效过
LedgerRule 第一版语义: 所有命中的规则都会执行;动作按规则文件顺序执行;缺失字段在条件中视为 false;动作失败会记录规则错误,但不允许静默吞掉。
五、类型系统:提前阻止一部分错误
错误写法类型系统应该提示什么背后的设计点
amount contains "200"amount 是 Money / Number,不能使用字符串包含操作每个字段要有类型,操作符也要有适用类型
category > 100category 是 String,不能和数字比较大小比较操作不能只按符号解析,还要看类型
require_approval(300)审批人参数必须是角色名字符串动作也需要签名和参数类型
tag()tag 需要一个标签参数函数调用要检查参数数量
六、错误提示:语言好不好用,常常败在这里
差的错误提示好的错误提示为什么重要
Parse error at line 2第 2 行 amount > 后面缺少数字,例如 amount > 200用户能立即修正
Invalid operatorcontains 只能用于字符串字段,amount 是数字提示要解释规则,不只指出错误
Unknown field未找到字段 merchent,你是否想写 merchant拼写建议能显著降低挫败感
Runtime failed规则 meal_limit 执行动作 require_approval 失败:角色 manager 不存在规则名、动作名、原因都要可追踪
七、运行时:语言最终在哪里执行
执行方式适合什么代价LedgerRule 选择
解释执行规则经常改,立即生效性能上限较低,运行时检查多第一版采用解释执行
编译成 Java / 字节码规则稳定,性能要求高实现复杂,调试和错误映射更难第二版再考虑
编译成 SQL大量数据批量筛选动作语义难映射,数据库差异大只允许条件部分下推
远程规则服务多个系统共享规则网络延迟、版本和可用性治理生产化后再做
八、工具链:一门语言不只是语法文件
格式化器
统一规则排版,减少团队因为空格、换行和风格争论。
静态检查
在规则上线前检查字段、类型、动作参数和重复规则。
测试器
给一条样例账目,展示哪些规则命中、执行了哪些动作。
解释追踪
当一笔账被要求审批时,能回答是哪条规则触发的。
版本管理
规则变化要能回滚,也要能审计谁改了什么。
编辑器提示
字段补全、动作补全、错误高亮,让非编译器专家也能使用。
九、从小语言回收到通用语言设计
通用问题在 LedgerRule 里的对应物扩展到通用语言时会变成什么
这门语言服务谁财务规则配置者和后端工程师语言的用户画像、学习曲线和生态定位
程序是什么结构规则列表,每条规则有条件和动作模块、函数、类、包、命名空间
值有哪些类型数字、字符串、布尔、金额、动作参数类型系统、泛型、空值、所有权、代数数据类型
代码如何执行解释器逐条匹配规则解释器、编译器、VM、JIT、AOT、运行时
错误如何暴露解析错误、类型错误、动作执行错误编译期错误、运行时异常、诊断系统
语言如何演进规则语法版本、动作白名单版本标准库、兼容性、版本语义、弃用策略
十、常见误区
误区为什么会走偏更好的做法
先设计漂亮语法语法好看但不知道解决谁的问题先写 5 个真实程序样例,再抽语法
第一版就做成通用语言特性爆炸,语义和工具链很快失控先做小领域语言,把边界收窄
只写 parser,不定义语义能读懂文本,但不知道执行是什么意思每个语法结构都写清执行规则
忽视错误提示用户实际学习语言时,看到最多的是错误错误提示要有位置、原因、修正建议和规则名
语言能执行任意代码灵活性变成安全、权限和审计风险从白名单动作开始,逐步扩展能力
十一、设计练习
练习要输出什么验收问题
写 5 条 LedgerRule 样例餐饮、交通、住宿、超额、缺发票五类规则业务人员能不能读懂,工程师能不能执行
定义字段类型表amount、category、merchant、invoice、date 等字段及类型每个操作符能不能知道自己适用哪些字段
写一页语义说明规则顺序、缺失字段、多规则命中、动作失败处理两个实现者是否会写出相同执行结果
设计 5 条错误提示语法、类型、字段拼写、动作参数、角色不存在用户看提示后能不能自己修正
决定执行模型解释执行、编译执行、SQL 下推或规则服务选择是否匹配规则变化频率、性能和治理成本
十二、回到主线
如何设计编译器
如果你已经确定 LedgerRule 的语法和语义,下一步可以继续看 如何设计编译器,把源码推进到 Token、AST、类型检查、IR 和执行后端。
编译器与运行时底座
如果要继续理解 AST、IR、优化、JIT、GC 和运行时差异,回到 编译器与运行时底座
Java 学习阶段三:多语言对比
如果你正在用多语言理解 Java 的类型、运行时和生态边界,可以回到 Java 多语言对比实验
数据结构与算法底座
如果要理解语言里的集合、树、图、查找和复杂度基础,可以继续读 数据结构与算法底座
总结: 设计一门编程语言的吸收顺序是:先找真实问题,写出样例程序,再定义语法和语义,然后加入类型检查、错误提示、执行模型和工具链。语法只是语言的脸,语义、错误和工具链才决定它能不能被长期使用。