在现代的前端开发中,当需求涉及到代码、表达式的展示和编辑时便牵扯到了程序员的三大浪漫之一的编译原理。它是一个看似高深复杂的领域。在本文中,我们将展示如何利用 TypeScript 构建一个基于编译原理的项目,通过解析四则运算表达式来建立语法树。这不仅可以加深你对编译原理的理解,还能为你的前端技能栈增添一份亮点。
项目概述
在这个项目中,我们将创建一个工具,用于验证和计算数学表达式。该工具将接受一个数学表达式作为输入,然后对其进行验证,最后计算其值。为了实现这一功能,我们将使用状态机、词法分析和语法分析等编译原理的概念。
关键概念与技术
概念的完整性至关重要
在这个项目中,我们使用了以下关键概念和技术:
- 状态机: 我们使用状态机来进行词法分析,将输入的数学表达式转换为 Token 序列。
- 词法分析: 通过状态机,我们将输入的字符串解析为一系列 Token,每个 Token 包含类型和值。eg:
1+11=>[1, +, 11] - 语法分析: 我们使用从左到右消耗Token的方式一步一步构建语法树,与Array.reduce如出一辙。另外值得注意的是: 由于乘除法优先级高于加减法, 遍历到乘除法时应把乘除法的表达式向右贪婪扩张直到碰壁, 再并入语法树。这个思想与数学归纳法相合, 可以如此表达:
- EOF: end of file, 表达式的结束
- AdditiveExpression: 加减表达式
- MultiplicativeExpression: 乘除表达式
- ps: 这里的 | 与 typescript 中的 | 是一个意思
- 语法树: 语法树是表达式的一种分层表示形式,用于描述表达式的结构和运算顺序。
- 验证与计算: 我们不仅验证表达式的合法性,还可以通过计算语法树上的节点来求得表达式的值。
词法分析
如上所述, 是将表达式的字符串按顺序拆成数字、运算符序列的过程。 用一个指针遍历每一个字符。 定义状态机 inNumber, 含义为指针在一个数字范围内, 当指针从 inNumber 状态中离开时需要将此数字提交到 Token 序列中(记得标记Token的type)。
以 12 + 34 为例:
括号的处理
先偷个懒,感兴趣的朋友请移步 github 代码在这里
语法分析
有了Token队列, 就可以基于它以类似 Array.reduce 的方式构建语法树。 这里以 [11, +, 22, *, 33] 为例:
直接看图 开始从左到右遍历 Token 队列
检测到加号,进入 additiveExpression 过程(如果加号-数字的组合不停止, 相应的会不断 fork 出 additiveExpression 过程)
检测到乘号, 由于乘法优先于加法, 先给乘法表达式留个坑位, 进入 multiplicativeExpression 过程
遍历结束, 乘法表达式归位
验证表达式语法
验证的逻辑可以渗透在词法和语法分析的过程中, 直接抛出错误即可, 提示信息可参照常见的 expect xx after xx, but got xx 格式
结语
通过本项目,我们初步了解了编译原理中的一些概念,例如状态机、词法分析和语法分析。我们展示了如何使用 TypeScript 构建一个简单的表达式验证和计算工具,以此加深对编译原理的理解,并在前端开发中掌握更多强大的技能。无论你是想提升技术深度还是丰富技能栈,编译原理都是一个值得探索的领域。