[toc]

# 小结

现代的语言通常会划分成前后端
这里主要小结下前端:
即前端负责词法的解析与语义树的构建等,前端最终的目标是构建出一个抽象语法树,实际上在基本的前端框架确定后,添加新的语法就比较简单了

# 前端模块的划分

假设代码输入了一个 string 字符串
这堆字符串会先输入 Lexer (词法分析器) 其中 Lexer 主要负责从代码中读取出一个又一个 Token 或者其他针对字符串检索的任务
Token: 我个人觉得用 “标记” 翻译这个单次比较合适
即一个标记可以是 变量 / 常量 / 运算符 / 语句终结符 等
比如:

1
int ptr = 321;

其中 i、=、321、; 分别就是一个又一个 Token
为什么 int 不是 Token 呢?
因为实际上对于 int/ptr 等这一系列标记的解析并不是简单的 Read 即可完成的,所以对于这种标记的解析一般是放到了 Parser 中去进行的 Parser 是实际上负责解析语法的 Parser 本身是一个巨大的状态机,它是和 Lexer 进行配合进行语法的解析的 即 Lexer 用于匹配单词,而 Parser 在不断使用 Lexer 进行单次匹配的过程中解析出抽象语法树 ast Ast 抽象语法树存放的是一个语言实际的语法树
即 AST 是 Parser 和 Lexer 进行配合后的阶段产物,这个抽象语法树的目标是将当前代码转换成一种更加接近机器实现的表达 比如下圖
实际上在这个阶段,编译器前端的工作基本就已经完成了,可以发现,最终经过 Parser 生成的 AST 是很容易使用代码去进行遍历的,而所谓最终转换的字节码,或者对于代码的执行都是基于这颗抽象语法树进行的。 比如我们实际上就可以定义一个最简单的由 if 构成的代码执行器
在遍历上面那颗抽象语法树的时候,如果遍历到 + 那么代表我们需要处理的是一个 中缀表达式(即 A + B)
那么执行器就需要在左子树上找到一个 A, 在右子树上找到一个 B
这其实就是一个最简单的 虚拟机 ,只不过没有中间的字节码阶段,不好进行优化

# 整理

代码字符串 -- 输入–> Lexer -- 输入–> Parser -- 输出–> AST (抽象语法树) -- 输入–> 后端 (虚拟机 / 栈模拟 / 代码执行器)

# 名词解释

前缀运算符: ~A,!A
中缀运算符: A+B,A*B
后缀运算符: A++,A-- 其实三元运算符也是中缀表达式: A ? (左:右)
即?是中缀符

# if 的实现方式

if 主要分成三部分

1
2
3
if 条件 {

}

即 if 关键字
条件块
代码块 条件块的实现就和 "()“一致,读到”(“时就继续往下读,直到匹配到和自己相关的”)"