您会惊讶于在我们的评估器中添加对条件的支持是多么容易。 他们实施的唯一困难是决定何时评估什么。 因为这就是条件语句的全部意义所在:只根据条件来评估某事。 考虑一下:
if (x > 10) {
puts("everything okay!");
} else {
puts("x is too low!");
shutdownSystem();
}
当评估这里的if-else最重要的事情就是只评估正确的分支。如果情况正确,我们就必须禁止评估else-分支,只评估if-分支。并且如果不正确,我们就只评估else-分支。
换句话说:如果条件 x > 10 不是……那么,我们只能评估这个条件的 else 分支……好吧,当它不是什么时? 我们是否应该评估后果,“everything okay!” 分支,仅当条件表达式生成真或生成“真”、不假或不为空的东西时?
这就是这个问题的难点,因为这是一个设计决策,准确地说是一个语言设计决策,会产生广泛的后果。
在 Monkey 的情况下,条件的结果部分将在条件为“真”时进行评估。 “真实”的意思是:它不是null,也不是false。 它不一定是真的。
let x = 10;
if (x) {
puts("everything okay!");
} else {
puts("x is too high!");
shutdownSystem();
}
在这个例子中“everything okay!”应该被打印,为什么呢?因为x被绑定为10,评估10并且10不是null或者不是false。
既然我们已经讨论过这个,我们可以把这个规范变成一组测试用例:
// evaluator/evaluator_test.go
func TestIfElseExpressions(t *testing.T) {
tests := []struct {
input string
expected interface{}
}{
{"if (true) { 10 }", 10},
{"if (false) { 10 }", nil},
{"if (1) { 10 }", 10},
{"if (1 < 2) { 10 }", 10},
{"if (1 > 2) { 10 }", nil},
{"if (1 > 2) { 10 } else { 20 }", 20},
{"if (1 < 2) { 10 } else { 20 }", 10},
}
for _, tt := range tests {
evaluated := testEval(tt.input)
integer, ok := tt.expected.(int)
if ok {
testIntegerObject(t, evaluated, int64(integer))
} else {
testNullObject(t, evaluated)
}
}
}
func testNullObject(t *testing.T, obj object.Object) bool {
if obj != NULL {
t.Errorf("object is not NULL. got=%T (%+v)", obj, obj)
return false
}
return true
}
这个测试函数也明确了我们已经讨论过的情况。当一个情况没有评估为一个值时,它返回NULL,等等:
if(false){ 10 }
else 缺失,因此条件应该产生 NULL。
我们必须做一些类型断言和转换思考,以在我们预期的字段中允许 nil,当然,但测试是可读的,并且清楚地显示了所需的和特此指定的行为。 它们也会失败,因为我们不返回任何 *object.Integers 或 NULL:
$ go test ./evaluator
--- FAIL: TestIfElseExpressions (0.00s)
evaluator_test.go:125: object is not Integer. got=<nil> (<nil>)
evaluator_test.go:153: object is not NULL. got=<nil> (<nil>)
evaluator_test.go:125: object is not Integer. got=<nil> (<nil>)
evaluator_test.go:125: object is not Integer. got=<nil> (<nil>)
evaluator_test.go:153: object is not NULL. got=<nil> (<nil>)
evaluator_test.go:125: object is not Integer. got=<nil> (<nil>)
evaluator_test.go:125: object is not Integer. got=<nil> (<nil>)
FAIL
FAIL monkey/evaluator 0.007s
早些时候我告诉过你,你会惊讶于实现对条件的支持是多么容易。不相信我吗? 好吧,看看让测试通过所需的少量代码:
// evaluator/evaluator.go
func Eval(node ast.Node) object.Object {
// [...]
case *ast.BlockStatement:
return evalStatements(node.Statements)
case *ast.IfExpression:
return evalIfExpression(node)
// [...]
}
func evalIfExpression(ie *ast.IfExpression) object.Object {
condition := Eval(ie.Condition)
if isTruthy(condition) {
return Eval(ie.Consequence)
} else if ie.Alternative != nil {
return Eval(ie.Alternative)
} else {
return NULL
}
}
func isTruthy(obj object.Object) bool {
switch obj {
case NULL:
return false
case TRUE:
return true
case FALSE:
return false
default:
return true
}
}
正如我所说:唯一困难的是决定要评估什么。 该决定封装在 evalIfExpression 中,其中行为的逻辑非常清晰。 isTruthy 同样具有表现力。 除了这两个函数之外,我们还在 Eval switch 语句中添加了 *ast.BlockStatement 的 case 分支,因为 *ast.IfExpression 的 .Consequence 和 .Alternative 都是块语句。
我们添加了两个新的、简洁的函数,以清晰的方式显示 Monkey 编程语言的语义,重用了我们已有的另一个函数,并通过这样做增加了对条件的支持并使测试通过。 我们的解释器现在支持 if-else 表达式! 我们现在离开计算器领域,直奔编程语言领域:
$ go run main.go
Hello mrnugget! This is the Monkey programming language!
Feel free to type in commands
>> if (5 * 5 + 10 > 34) { 99 } else { 100 }
99
>> if ((1000 / 2) + 250 * 2 == 1000) { 9999 }
9999
>>
< 3.5评估表达式 | > 3.7return语句 |
---|