从零开始开发JVM语言(四)四则运算

目录戳这里

​经过词法分析,终于得到了略显奇葩的“词法树”(一般来说都是词法串)。下一个步骤就是针对它进行语法分析,生成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

转载于:https://my.oschina.net/wkgcass/blog/686362

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值