这是一个比较深刻的广泛的 bug。 :-)
如下的代码能编译,也符合之前对语言的定义。 但运行起来,会有一个运行时错误。可以看到,是一个栈的状态错误:到程序结束的时候,栈上有一个多余的值。
let a
a = 2
a + 1
let b = a + 3
b
Function #0 reading/validation error: At instruction End(@10): Unexpected stack height 1, expected 0
这个值是由第三行的a+1
遗留下来的。凡是“求值”表达式,单独一行的,都会引起这个问题。原因可以形象地理解为:有值产生,却没人用这个值,那么这个值就遗留在栈上了。
解决办法看来有两个方向:
- 禁止一个求值表达式单独成为一行。
- 如果求值表达式单独成行,编译的时候附加一个
Drop
指令,把多余的值丢弃掉。
这两个方向其实是同一种思路。都需要完成一件事,定义“一行”。当“一行”里仅有一个求值表达式的时候,采取某种措施。
在我们之前的语言定义里没有“一行”的概念。现在需要增加一个——Statement
。之前的概念表达式Expr
,与“行”是有区别的,一个行可以包含一个或多个表达式,也可以包含表达式和其他一些部件,比如if
语句,就包含条件、分支一、分支二等多个表达式。
“行”对于表达能力来说,没有新的增强。从某种角度来说,它只是一个编译单位,只是为了方便编译规则的定义而出现的。
回归到现在这个bug,我们选择解决方法 2,即单独成行的求值表达式,一律添加一个Drop
指令。对于最后一行,虽然形式上也是一个单独成行表达式,但比较特殊,除了表达式本身的语义以外,还有一个返回值的语义,这里改下语言定义,最后一个返回值,必须是return xxx
另外,还从表达式Expr
中特化出了求值表达式EvalExpr
,作为简单计算的表达式的统称,比如字面量、变量取值、算数计算等。因为这类表达式,与广泛意义的表达式,在用途上是不一样的。上面的例子是其中一个,还有一个典型的例子:if语句的条件表达式,只能是求值表达式。