编译原理工程实践—01编译器前端技术核心三步
文本主要介绍编译器前端技术的实现和应用。
1. 编译技术无处不在
什么是编译器的前端技术?我们在大学课堂里学习的《编译原理》大多侧重讲解编译器的 "前端(Front End)" 技术,即编译器对程序代码的分析和理解过程。而与之对应的 "后端(Back End)" 则是生成目标代码和优化的过程,跟目标机器有关。
总体来看,编译器前端技术的核心三步分别为:
- 词法分析:通过构造有限自动机把程序源码分割为一个个 Token
- 语法分析:用递归下降算法识别程序结构,并形成一棵便于由计算机处理的抽象语法树 AST
- 语义分析:消除语义模糊,生成一些属性信息,用于生成目标代码
在现实中的大部分场景,依靠编译器前端技术就能解决,而前端编译过程也无处不在,例如,正则表达式的使用就是词法分析的过程;而解析文本、配置文件或是编写自定义表达式就是语法分析的过程。
在实际工作中,无论是前端还是后端开发者都常会碰到需要编译技术的场景:
【前端】代码转换、性能优化和工具链开发
-
现代前端框架与编译器:如利用 词法分析 & 语法分析 实现 Vue/React/Svelte 等框架的模板编译(template=>render函数),还可以开发改造出符合特殊需求的模板引擎,对异型循环、条件分支等语法的支持都不在话下;利用编译技术还能实现如 TypeScript 般对不同 语言翻译转换,利用Babel的 AST 转换 将 ES6+ 代码降级为 ES5;Svelte 直接编译生成高效原生 JS 代码,减少运行时开销。
-
打包工具与 Tree Shaking:如 Webpack 通过 AST 分析模块依赖、
import()
实现动态加载模块,再如 Rollup 的 Tree Shakcsxiaoyao.com ing 移除未使用的代码。 -
CSS 预处理与优化:如 Sass/Less/Stylus 编译成 CSS,PostCSS 优化样式,利用 词法分析 解析 Sass 的嵌套规则,PostCSS 的
autoprefixer
功能自动添加浏览器前缀(代码生成)。 -
WebAssembly(Wasm):利用编译后端技术,如 字节码生成 技术可以将 Rust/C++ 编译成 Wasm,前端通过 JS 调用。
【后端】语言运行时优化、代码生成和数据库查询优化
- 代码生成与 ORM 框架:如 ORM 工具的模板引擎解析
#{}
和${}
,利用 AST 操作实现 HQL 转 SQL,还能实现 SQL 查询优化。 - RPC 框架与协议编解码:如根据协议生成不同客户端和服务端执行代码,还可以实现如注解的支持、自定义 DSL,对静态语言代码的解释性执行,为特殊场景下的开发提效。
【运维】优化构建、部署和监控流程
- 配置管理与模板渲染:解析
YAML/JSON
配置文件、生成 k8s 配置,优化 CI/CD 流水线,优化增量编译和缓存等等。 - 日志与监控数据处理:利用词法和语法分析更高效灵活地解析日志文件。
编译原理不仅是"实现编程语言"的理论,更是提升代码质量、开发效率和解决复杂问题的关键工具。
2. Step1: 词法分析(Lexical Analysis): 识别单词Token
计算机执行代码的第一步需要先"阅读"代码,和人类阅读文章时需要识别一个个单词一样,程序也需要首先将源码识别为一个个词法记号: Token。词法分析在日常开发中可能用的比想象多,因为 正则表达式 的应用就是词法分析的过程,如校验用户是否输入了合法的邮箱。
词法分析程序如何识别 Token?词法分析依赖一套规则和一套算法:正则文法(Regular Grammar) 和 有限自动机(Finite-state Automaton),就是常说的 FSA。
我们在下一篇将实现一个简单的词法分析器程序,这里先举个简单的例子,如下图所示,词法分析器读取到代码字符串 age>=18
后逐字符解析,在遇到不同的字符后会迁移至不同状态。词法分析过程,本质是一个个状态迁移的过程。
实际的生产环境中很少纯手写词法分析器,开发者只需要编写符合 "正则文法" 规则的 "正则表达式",结合市面上的词法分析器工具生成 "有限自动机" 算法即可实现。
3. Step2: 语法分析(Syntactic Analysis, or Parsing): 识别语法结构
前面的词法分析识别出了源码的一个个单词 Token,而语法分析是为了继续识别出程序结构。为了方便计算机理解和执行,语法分析过程会将程序语法结构构造成树形结构。一个程序对应一棵 抽象语法树 (Abstract Syntax Tree),也就是我们熟知的 AS
通过网站 https://www.jointjs.com/demos/abstract-syntax-tree 可以可视化地观察 AST 的结构化表示。如下图展示了将表达式 1+2*3
赋值给变量 a
的 AST:
上面 AST 的结构计算机是方便处理的,除了上面的语法,再增加循环、条件分支等语法节点,就可以实现一门脚本语言了。而执行脚本语言的过程,其实就是遍历 AST 的过程。
语法分析的实现需要借助 递归下降算法,这是一种自顶向下的算法,我们在后面的篇章中将会详细介绍语法分析器的实现。
其实在日常工作中,我们或多或少也会接触到语法分析,如要实现用户自定义公式的计算,对公式的解析就是语法分析过程。另外分析日志文件时对每行日志的解析,本质上也是语法分析过程。
同样的,实际的生产环境中也很少从零手写语法分析器,改一改网上找到的开源语法规则文件,就能用 Antlr 等工具生成语法分析器。
4. Step3: 语义分析(Semantic Analysis): 让计算机理解意图
识别出程序结构后,此时距离编译器后端生成目标代码还缺少一些上下文信息。在语义分析阶段,编译器会根据语义规则,基于上下文对 AST 节点的属性做各种解析、标注和合法性检查,完成这些属性标注后,编译器后端就可以依据这些信息生成目标代码了。
语义分析阶段的成果,大多会作为属性标注在 AST 抽象语法树上。如标记节点数据类型,如前面提到的表达式 age>=18
,会在 AST 的标识符节点 age 和字面量节点 18 上标注数据类型为数值类型;再如标记节点的源代码行列号,以方便报错信息的定位。