从零写一个编译器(二):语法分析之前置知识

前言

在之前完成了词法分析之后,得到了Token流,那么接下来就是实现语法分析器来输入Token流得到抽象语法树 (Abstract Syntax Tree,AST)。但是在完成这个语法分析器不像词法分析器,直接手撸就好了,还是需要一些前置的知识。

这些前置知识在之前的博文都有提起过

之前的博文目录

项目的完整代码在 C2j-Compiler

什么是语法分析?

如果我们把词法分析看成是组合单词,输出单词流,那么语法分析就可以看作是检查这些单词是不是符合语法的过程。在词法分析的时候用正则或者手工比对来验证单词,语法分析则是用上下文无关文法 (context-free grammar,CFG)

若一个形式文法 G = (N, Σ, P, S) 的产生式规则都取如下的形式:V -> w,则谓之。其中 V∈N ,w∈(N∪Σ) 。上下文无关文法取名为“上下文无关”的原因就是因为字符 V 总可以被字符串 w 自由替换,而无需考虑字符 V 出现的上下文。一个形式语言是上下文无关的,如果它是由上下文无关文法生成的*

BNF范式

巴科斯范式(英语:Backus Normal Form,BNF)是一种用于表示上下文无关文法的语言。

看一个例子:

1
2
3
S –> AB
A –> aA | ε
B –> b | bB

其中S A B叫作非终结符,代表可以通过推导产生新的符号,之前在Token类里定义的也有这些非终结符;a b ε叫作终结符,表示其无法再通过推导产生新的符号了,ε则表示空;

上面的每一行就是一个产生式规则,也叫推导式,代表了一种非终结符的转移方式;

S就是开始符号。

只有终结符的符号串称为句子 (sentence)

比如通过这三个产生式,就可以断定bbb符合语法规则。

语法分析的几种方法

和之前讲的一样,主要分为自顶向上和自底向下两种

之前在学习的时候稍微记录了一下这几种方法,在这里就不说了

递归下降和LL(1)语法分析
自底向上语法分析

在这里稍微的再说一下这次语法分析使用的方法,LALR(1),它也属于自底向上的分析算法。

自底向上的语法分析

一个自底向上的语法分析过程对应为一个输入串构造语法分析书的过程,它从叶子节点开始,通过shift和reduce操作逐渐向上到达根节点

自底向上的语法分析需要一个堆栈来存放解析的符号,例如对于如下语法:

1
2
3
4
5
0.	statement -> expr
1. expr -> expr + factor
2. | factor
3. factor -> ( expr )
4. | NUM

来解析1+2

stack input
null 1 + 2
NUM + 2 开始读入一个字符,并把对应的token放入解析堆栈,称为shift操作
factor + 2 根据语法推导式,factor -> NUM,将NUM出栈,factor入栈,这个操作称为reduce
expr + 2 这里继续做reduce操作,但是由于语法推导式有两个产生式,所以需要向前看一个符合才能判断是进行shift还是reduce,也就是语法解析的LA
expr + 2 shift操作
expr + NUM null shift操作
expr + factor null 根据fator的产生式进行reduce
expr null reduce操作
statement null reduce操作
此时规约到开始符号,并且输入串也为空,代表语法解析成功

所以实现自底向上的语法解析关键就是识别堆栈上是应该进行shift还是reduce操作。

  • 进行暴力匹配,搜索堆栈上的符号和所有的语法推导式进行匹配 x

  • 构造一个状态机来根据堆栈压入或弹出后的状态来决定是否进行reduce操作

    所以接下来的任务自然就是构建一个有限状态自动机来能够指导语法分析器来进行操作。

小结

所谓的前置知识其实也就是了解语法分析在干什么,和大概要怎么干。

语法分析就是检查输入的Token流是不是符合语法的过程,而完成这一步骤的语法分析算法,拿自底向上来说,也就是从叶子节点向上推导成树顶端的过程。