目录戳这里
上一篇描述了“四则运算”乃至任何二元运算都适用的解析。这一篇继续讲述语法分析的内容。
语法分析从Token根节点开始,跑完整个序列,最终生成AST
。
那么,可以从最简单的表达式,例如四则运算入手,也可以从最顶级的语句Statement
入手。
parse_statement()
这个方法将开始将输入串看作一个statement
进行解析。
这时候可以了解一下“产生式”。(至于BNF,如果用语法分析器生成器,例如yacc,可以仔细研究一下,否则大致知道,不会写错就好了。BNF的规则很简单的。。)
什么叫产生式呢
例如
val n = 2 * 3
这是一个表达式Expression
,它定义了一个变量,所以它也是VariableDef
。那么它是语句Statement
吗?是的,它也是语句
可以有
Statement -> Expression
Expression -> VariableDef
VariableDef -> modifier VariableDef
-> validName : TypeSpec
-> validName : TypeSpec = Expression
-> validName = Expression
产生式大致可以这么写。
使用递归下降
,可以写出这样的方法(伪代码)
parse_statement()
if current is Modifier
parse_modifier()
if current is VariableDef
parse_variableDef()
...
parse_modifier()
...
if current is VariableDef
parse_variableDef()
...
parse_variableDef()
...
写出来的形式与“产生式”几乎一模一样。
这里也许会需要向前多读几个字符以确定匹配哪种生成式。
比如
val n = 2 * 3
^
是Modifier,也许是VariableDef
val n = 2 * 3
^
是合法的变量名,也许是VariableDef。也有可能是方法定义,例如val n() 所以再读一位
val n = 2 * 3
^
是等于号,那跑不了,就是VariableDef了
当然,还有另一种思路,遇到任何一个Token就立即解析。解析依据不但是后续的串,还有已经解析完毕存放在栈中的“中间结果”
val n = 2 * 3
^
这是个Modifier,先记录一下
[val]
val n = 2 * 3
^
是合法的变量名,先把它解析出来。由于附带了个Modifier,把它也加上去。由于有Modifier,那(val n)这一小块一定是VariableDef
[]
[(val n)]
val n = 2 * 3
^
等于号,那就是给(val n)赋值,取出值然后解析`Expression`,最后存入(val n=2*3)
这种方式叫做移进-归约
,从结束符开始逆推产生式
实际上,parser的写法并不难,代码量也不多(相比Spring这样的库,动不动一个“非核心”文件就得6000多行比起来代码量简直忽略不计啊!)。它不是一个技术活。编译原理中的文法限制摆在手写parser面前都不是事。
即使你设计的文法是一个上下文有关文法,你也可以将上下文传入并解析。理论只是为了更好的实践,而不是对实践进行限制。
希望看官能够关注我的编译器哦~Latte
PS:说实话,我对语法分析研究并不深入,只是拿着两种最基本的理论一路走到黑。这也是手写Parser的一个好处:管他什么理论呢,怎么着都能写出来(其实用生成器比手写还要简单,只是生成器本身的理论并不简单)。许多人都说,看龙/虎书,一个星期就能撸个Parser,确实如此。仅仅是_做出_一个Parser需要的理论,即使这个人对自动机、形式语言一窍不通,一个星期的时间也确实能学懂。 就像大学里“编译原理”或者“形式语言”考试,牛人只要一天的"预习"就能80+。 由于手写的“繁杂”,造成错误报告和错误恢复特别容易写,提示也更加人性化。