Skip to content

Latest commit

 

History

History
138 lines (125 loc) · 5.24 KB

File metadata and controls

138 lines (125 loc) · 5.24 KB

3.6条件句

您会惊讶于在我们的评估器中添加对条件的支持是多么容易。 他们实施的唯一困难是决定何时评估什么。 因为这就是条件语句的全部意义所在:只根据条件来评估某事。 考虑一下:

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语句