目录戳这里
经过词法分析,终于得到了略显奇葩的“词法树”(一般来说都是词法串)。下一个步骤就是针对它进行语法分析,生成AST。
而我当时首先做的是四则运算。
7+8*9-10/2
它应当被看做
((7+(8*9))-(10/2))
这便是运算符优先级作用的结果。乘法和除法比加减法优先级高,所以更优先结合。加法减法优先级相同,所以越早出现越先结合。
编译最基本的算法是 递归 。所以先尝试一下,纯粹毫无技巧的使用递归的思路来解析这个串(我尽量少写代码,因为java代码挺冗长的。如果喜欢代码演示可以看github上的成品)
首先规定两个数据结构,一是数字 Number(n)
,二是 二元运算 TwoVarOp(op,a,b)
。其次,对于一个表达式可能会有许多值等待被解析,所以需要有一个地方用来存储临时的解析结果。为了和递归相匹配,我使用了栈结构(Stack<Expression> parsedExps)。不过实现完了才发现似乎并不需要栈,一个变量(空间O(1))就够了。哎,不过也懒得改了。而且用O(n)的空间对于错误恢复也有帮助。
osc的字体不是等宽的,所以“^”符号位置可能错位,暂时好像没有什么解决办法。。有什么好方法的麻烦评论一下哦~
7+8*9-10/2
^
首先读入一个token,发现是数字7,立即生成Number(7)并存储在Expression栈内
[(7)]
7+8*9-10/2
^
接着继续,读入符号+,由于栈内非空,所以应该理解为二元运算符(而不是一元的。吐槽下,话说完全不知道为啥各种语言都要弄个毫无用处的一元+,解析还费劲)。从栈内取出栈顶表达式(7),并递归解析,然后取栈顶值并组合成二元表达式,放入栈内。
伪代码大概这样
parse_twoVarOp()
a=stack.pop()
op=currentNode.value
next() // 向前移动一格
parse_expression()
b=stack.pop()
我们来看看这个算法将是什么结果
[]
7+8*9-10/2
^
local (7 +)
[(8)]
7+8*9-10/2
^
local (7 +)
[]
7+8*9-10/2
^
local (7 +) (8 *)
[(9)]
7+8*9-10/2
^
local (7 +) (8 *)
[]
7+8*9-10/2
^
local (7 +) (8 *) (9 -)
[(10)]
7+8*9-10/2
^
local (7 +) (8 *) (9 -)
[]
7+8*9-10/2
^
local (7 +) (8 *) (9 -) (10 /)
[2]
7+8*9-10/2 (end)
^
local (7 +) (8 *) (9 -) (10 /)
local (7 +) (8 *) (9 -) (10/2)
local (7 +) (8 *) (9-(10/2))
local (7 +) (8*(9-(10/2)))
local (7+(8*(9-(10/2))))
result (7+(8*(9-(10/2))))
这是一个与预期不符的结果,而且错的离谱。如果毫无技巧的使用递归,对于中缀式将产生上述的结果。这也从侧面体现出了前缀式的好处,前缀式可以先找到+,再去找两个表达式。
那么如何解决呢?这就要加入运算符优先级
上述算法也不是一无是处。至少可以发现,若任由它递归下去,得到的结果一定是(left op (all of right))的形式。也就是说,若进行递归,那么一开始从栈中取得的值最终在二元表达式中,一定还是原来这个值。
上述例子中,7还是7,而右侧变为一堆运算的合体
那么什么情况需要进行递归呢?
3+2*5
对于这个式子,在处理*
时就必须继续递归,让2保留为2,而不是3+2
。最后得到的将会是(2*5)
,再返回给处理+的调用,变为3+(2*5)
所以说,看是否要继续递归下去,需要检查的是上一个运算符。 这里要加一个栈,用于存储上一次使用的运算符
若当前优先级高于上一个运算符,就继续递归,否则应当直接返回,并弹出运算符栈顶元素。
[]
[]
7+8*9-10/2
^
local
[]
[(7)]
7+8*9-10/2
^
local
[+]
[]
7+8*9-10/2
^
local (7 +)
[+]
[(8)]
7+8*9-10/2
^
local (7 +)
[+, *]
[]
7+8*9-10/2
^
local (7 +) (8 *)
[+, *]
[(9)]
7+8*9-10/2
^ 减优先级不大于乘,将直接返回,并弹出栈顶的*
local (7 +) (8 *)
[+]
[(8*9)]
7+8*9-10/2
^ 减优先级不大于加,将直接返回,并弹出栈顶的+
local (7 +)
[]
[(7+(8*9))]
7+8*9-10/2
^
local
[-]
[]
7+8*9-10/2
^
local ((7+(8*9)) -)
[-]
[(10)]
7+8*9-10/2
^
local ((7+(8*9)) -)
[-, /]
[]
7+8*9-10/2
^
local ((7+(8*9)) -) (10 /)
[-, /]
[(2)]
7+8*9-10/2 (end)
^
local ((7+(8*9)) -) (10 /)
local ((7+(8*9)) -) (10/2)
local ((7+(8*9)) - (10/2))
result ((7+(8*9)) - (10/2))
和预期完全一致。
这就是运算符的解析过程。不光针对四则运算,而是对于任何二元运算符都适用
希望看官能够关注下我的编程语言哦~Latte