设计模式精读:Interpreter 解释器模式——把语言文法翻译成可执行的结构

发布时间:2026/10/3 12:51:24
设计模式精读:Interpreter 解释器模式——把语言文法翻译成可执行的结构 文档技术博客教程【免费下载链接】weekly前端精读周刊。帮你理解最前沿、实用的技术。项目地址https://gitcode.com/GitHub_Trending/we/weekly点击查看免费下载Interpreter解释器模式是行为型设计模式之一它的核心思想是给定一门语言用文法描述它的语法再定义一个解释器把字符串形式的句子解析成结构化数据或可执行逻辑。本篇以 SQL、编译器和自然语言处理三个场景切入完整讲解解释器模式的意图、类结构、终结符/非终结符的递归机制并给出一个可运行的 TypeScript 加法文法实现同时结合本仓库《手写 SQL 编译器》系列文章文法介绍、语法分析从编译原理层面印证解释器模式的落地方式与演进方向。读完你将掌握如何把一个语言翻译成一棵可解释的树这一通用能力。模式定位行为型家族中的语言翻译官解释器模式Interpreter属于行为型模式。它的正式意图是给定一个语言定义它的文法的一种表示并定义一个解释器。这个解释器使用该表示来解释语言中的句子。任何一门语言无论是日常语言还是编程语言都有明确的语法只要有语法就可以用文法Grammar描述并通过语法解释器将字符串的语言结构化。这正是解释器模式存在的前提语言本质上是字符串而字符串必须经过解析才能变成程序可以操作的结构。什么时候需要解释器三个典型场景如果只看意图介绍会觉得抽象设计模式必须在日常工作里用起来。原文档给出了三个能加深理解的现实场景SQL 解释器SQL 是一种描述语言天然适用于解释器模式。不同的 SQL 方言有不同的语法我们可以根据某种特定的 SQL 方言定制一套适配它的文法表达式再利用 antlr 解析为一棵语法树。在这个例子中antlr 就是解释器。值得注意的是SQL 的文法往往并非上下文无关。正如《手写 SQL 编译器 - 文法介绍》 中所分析等号在CASE WHEN语句和WHERE语句中的含义取决于上下文属于上下文相关文法但把CASE WHEN与WHERE区块分别交由两块文法解决、将等号抽离为通用表达式后就转换成了程序处理起来相对轻松的上下文无关文法。这解释了为什么 SQL 可以被 antlr 这类工具稳定解析。代码编译器程序语言同样因为其天然是字符串的原因和 SQL、日常语言类似需要一种模式解析后才能工作。不同的语言有不同的文法表示我们只需要一个类似 antlr 的通用解释器通过传入不同的文法表示返回不同的对象结构——文法与解释器分离一套解析引擎可以服务多种语言。自然语言处理自然语言处理也是解释器的一种。自然语言处理一般只能处理日常语言的子集因此需要先定义好支持的范围再定义一套分词系统与文法表达式并将分词后的结果传入灌入了该文法表达式的解释器。这样解释器可以返回结构化数据后续根据结构化数据再进行分析与加工。可以看到分词 → 文法 → 结构化输出的链条与代码编译器完全同构。逐句拆解意图文法、解释器与抽象语法树再回到那句意图定义把它拆开看对于给定的语言这个语言可以是 SQL、代码或自然语言。定义它的文法的一种表示文法可以有多种表示只需定义一种。要注意的是不同文法执行效率会有差异——比如同一语法左递归文法会导致解析死循环右递归文法可以通过消耗 Token 跳出循环这正是《手写 SQL 编译器 - 文法介绍》中消除左递归一节的核心结论左递归完全不消耗 Token而右递归通过消耗 Token 的方式跳出死循环。并定义一个解释器这个解释器就是类似 antlr 的东西传给它一个文法表达式就可以解析句子了。用公式表达就是解释器(语言, 文法) 抽象语法树我们可以直接把文法定义耦合到解释器里但这样做会导致语法复杂时解释器难以维护。比较好的方式是定义一套与解释器解耦的文法表达式通过预处理器最终生成解释器。这与 antlr 的文法文件.g4→ 生成 parser 代码的工作模式完全一致也是解释器模式最具工程价值的设计点。结构解剖终结符与非终结符的递归树原文档给出的结构图中包含四个关键角色Context、AbstractExpression、TerminalExpression、NonterminalExpression。由于当前仓库不包含该图的本地副本这里用文字与代码块还原其结构关系AbstractExpression抽象语法表达式 ▲ ▲ │ │ TerminalExpression NonterminalExpression 终结符表达式 非终结符表达式 │ │ 持有 ▼ AbstractExpression子表达式递归Context承载解释执行过程中的上下文变量比如变量表、环境状态等外部信息。AbstractExpression抽象语法表达式是所有表达式的统一接口。TerminalExpression终结符指的是没有后续展开的符号是语法树的叶子节点。NonterminalExpression非终结符与终结符相反它还有后续展开所以又指向 AbstractExpression如此递归。终结符与非终结符的概念并非解释器模式独有它直接来自文法理论。在《手写 SQL 编译器 - 文法介绍》中使用 BNF 范式可以这样描述 SQL 文法selectStatement :: SELECT selectList FROM tableName selectList :: selectField [ , selectList ] tableName :: tableName [ , tableList ]所有::号左边的都是非终结符解析selectStatement时遇到selectList会进入selectList产生式继续展开而解析到普通的SELECT单词终结符就不再继续解析。这正是解释器模式结构图中递归关系的文法本质。TypeScript 实战实现一个加法文法解释器下面例子使用 TypeScript 编写原文档代码非终结符为简化说明写作TerminalExpression与NonterminalExpression。假设我们要实现以下文法sum :: number number number :: 1 | 2这是一个最简单的加法文法其中加法表达式sum和number都是非终结符而、1、2是终结符。这个例子只能做到 1 与 2 的加法但足以借此了解解释器模式的精髓——遇到非终结符则继续调用只有终结符才能直接判断// 抽象表达式 class AbstractExpression { interpret (text: string) {} } // 终结符表达式 class TerminalExpression extends AbstractExpression { constructor(values: string[]) { this.values values } interpret(value: string) { // 值必须是其中之一 return this.values.includes(value) } } // 非终结符表达式 class NonterminalExpression extends AbstractExpression { constructor(left: TerminalExpression, right: TerminalExpression) { this.left left this.right right } interpret(value: string) { if (value.indexOf() -1) { // 必须包含 号 return false } const splitValue value.split() return this.left.interpret(splitValue[0]) this.right.interpret(splitValue[1]) } } // 调用 const context new Context() const terminal new TerminalExpression([1, 2]) const add new AddExpression(terminal, terminal) add.interpreter(1 1) // true add.interpreter(1 2) // true add.interpreter(1 3) // false add.interpreter(2 - 1) // false为了让上述代码真正可运行需要补全原文档示例中引用但未定义的两个类——Context与AddExpression。Context即结构图中的上下文对象AddExpression是语义更明确的非终结符表达式继承NonterminalExpression补全版本如下// Context承载解释执行过程中的上下文变量此处示例中为空 class Context {} // AddExpression加法非终结符复用左右两个终结符表达式 class AddExpression extends NonterminalExpression {} const context new Context() const terminal new TerminalExpression([1, 2]) const add new AddExpression(terminal, terminal) console.log(add.interpreter(1 1)) // true console.log(add.interpreter(1 2)) // true console.log(add.interpreter(1 3)) // false3 不在终结符取值集合内 console.log(add.interpreter(2 - 1)) // false不包含 号逐行追踪执行过程当输入1 3时AddExpression.interpret发现字符串包含将其按拆分为[1, 3]然后调用left.interpret(1)返回true、right.interpret(3)返回false两者做运算得到false。可以看到文法树的每一层都只负责自己的局部判断判断结果由父节点按文法组合规则汇总这就是解释器模式的核心工作方式。解释器如何被驱动递归下降与 Match 思想解释器模式在工程落地时解释器内部通常由递归下降Recursive Descent驱动。这一点在《手写 SQL 编译器 - 语法分析》中有非常清晰的对应递归下降的 SQL 语法解析就是一个走迷宫的过程——Token 从左到右逐个匹配最终找到一条路线完全贴合 Token 序列。其中几个关键组件与解释器模式一一对应Match 函数迷宫中索取令牌的关卡匹配上当前 Token 便将其下标下移一位没有匹配上则不消耗 Token。这正是TerminalExpression.interpret中values.includes(value)的等价物——都是对单个终结符做直接判断。连接运算match(x) match(y)对应非终结符对多个子表达式按顺序组合判断即left.interpret(...) right.interpret(...)。分支函数 tree对应文法中的|并运算按顺序尝试各分支失败则还原 Token 位置——这与解释器模式中非终结符选择某条产生式展开的机制同源。可选函数 optional即文法中的?可选符号可以看作分支函数的一个特例func? func | ε。原文如此评价这种函数式写法只要每个文法自己的判断做好了剩下的工作就是组装文法。把单个逻辑判断与文法组装解耦可以使二者独立变换把复杂的语法解析转化为一个个具体的简单问题。弊端语法复杂后的类爆炸与通用解析器上面的例子是比较低效的场景因为当语法复杂后类的数目会明显增多难以维护——每个文法规则都需要对应一个表达式类规则一多类层次就会迅速膨胀。此时需要换用一个通用语法解析器让文法与执行逻辑进一步解耦。这正是本仓库《手写 SQL 编译器》系列做的事情。从《手写 SQL 编译器 - 语法分析》可以看到语法复杂后主要面临这些工程问题全部在解释器模式文法复杂化时会暴露出来回溯能力若 A 分支执行成功则函数栈退出后续失败时无法再回到分支 B因此需要模拟函数执行机制实现回溯达到 LL(∞) 的匹配能力详见《手写 SQL 编译器 - 回溯》。左递归消除左递归产生式会被无限展开导致死循环需要通过文法转换或右递归方式处理。生成语法树仅匹配语句的正确性不够还要根据语义生成语法树见《手写 SQL 编译器 - 语法树》。错误检查与错误恢复在错误的地方给出建议甚至对某些错误做自动修复见《手写 SQL 编译器 - 错误提示》。性能优化解析过程中的缓存策略见《手写 SQL 编译器 - 性能优化之缓存》。智能提示面向用户的交互式解析见《手写 SQL 编译器 - 智能提示》。系列入口可参考《手写 SQL 编译器 - 词法分析》从词法、文法、语法分析、回溯、语法树到性能优化完整覆盖了解释器的各个工程侧面。此外社区中也有根据文法自动生成 parser 的成熟方案如 antlr4、pegjs它们把文法 → 解释器的转换过程自动化正是原文档所说通过预处理器最终生成解释器的工程化体现。总结解释器是一种思维解释器模式的本质是一种思维将复杂语法解析抽象为一个个独立的终结符与非终结符各自判断只要每个文法自己的判断做好了剩下的工作就是组装文法。这种将单个逻辑判断与文法组装解耦的做法可以使逻辑判断与文法组装独立变换使复杂语法解析转化为一个个具体的简单问题。当你面对把一段字符串翻译成结构化数据的需求时——无论是解析配置文件、公式、表达式、查询语句还是实现一门小型 DSL——都可以先问自己三个问题语言的文法是什么哪些是终结符、哪些是非终结符解释器如何递归地组合它们回答清楚这三个问题你就已经站在了解释器模式的门内。赞分享文档技术博客教程【免费下载链接】weekly前端精读周刊。帮你理解最前沿、实用的技术。项目地址https://gitcode.com/GitHub_Trending/we/weekly点击查看免费下载相关推荐CLIProxyAPI核心架构解析深入理解翻译器与执行器设计模式CLIProxyAPI核心架构解析深入理解翻译器与执行器设计模式 CLIProxyAPI是一个功能强大的AI API代理网关项目其核心架构采用翻译器与执行器后端API网关大模型AI 应用Interpreter 解释器模式实战解读基于 java-design-patterns 的语法树构建与表达式求值Interpreter 解释器模式实战解读基于 java design patterns 的语法树构建与表达式求值 导读 本文以开源仓库 java desig示例工程教程Actual-Server解释器模式语法解析与执行引擎Actual Server解释器模式语法解析与执行引擎 痛点分布式同步的复杂性挑战 在现代个人财务管理工具中多设备数据同步是一个核心但极其复杂的挑战。传统后端金融科技数据同步创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

关于本文作者

来自尧图内容编辑团队

尧图内容编辑团队 内容团队

尧图内容编辑团队

本文由尧图网络内容编辑团队执笔。团队由资深项目经理、前端工程师与设计师组成,所有内容均来自亲手交付的真实项目,先讲清问题、再给出可落地的解法。尧图深耕北京网站建设十年,服务过京华建材集团、智造科技等各行业客户,把一线经验沉淀为可复用的行业观察。

  • 十年建站经验,覆盖建材、制造、服务、文创等
  • 项目经理把关选题与事实准确性
  • 工程师与设计师联合撰写专业细节
  • 统一编辑规范,保证文风与排版一致
  • 每月复盘转化数据,迭代选题方向

延伸阅读

相关资讯与近期热门内容

深度阅读推荐

建站决策前值得细读的三篇

网站改版的5个关键决策
2024-08-12

网站改版的5个关键决策

什么时候该改版、改到什么程度、如何避免流量掉光,京华建材集团改版复盘给出答案。

获取专属建站方案

看完文章,把您的行业与预算告诉我们,免费获取一份量身定制的官网建设方案与报价。

立即免费咨询