求值器(evaluator)
在有了一个程序的语法树之后,我们该做什么呢?当然是执行程序了!而这就是求值器的功能。我们将语法树和作用域对象传递给求值器,执行器就会求解语法树中的表达式,然后返回整个过程的结果。
const specialForms = Object.create(null);function evaluate(expr, scope) {if (expr.type == "value") {return expr.value;} else if (expr.type == "word") {if (expr.name in scope) {return scope[expr.name];} else {throw new ReferenceError(`Undefined binding: ${expr.name}`);}} else if (expr.type == "apply") {let {operator, args} = expr;if (operator.type == "word" &&operator.name in specialForms) {return specialForms[operator.name](expr.args, scope);} else {let op = evaluate(operator, scope);if (typeof op == "function") {return op(...args.map(arg => evaluate(arg, scope)));} else {throw new TypeError("Applying a non-function.");}}}}
求值器为每一种表达式类型都提供了相应的处理逻辑。字面值表达式产生自身的值(例如,表达式100的求值为数值100)。对于绑定而言,我们必须检查程序中是否实际定义了该绑定,如果已经定义,则获取绑定的值。
应用则更为复杂。若应用有特殊形式(比如if),我们不会求解任何表达式,而是将表达式参数和环境传递给处理这种形式的函数。如果是普通调用,我们求解运算符,验证其是否是函数,并使用求值后的参数调用函数。
我们使用一般的 JavaScript 函数来表示 Egg 的函数。在定义特殊格式fun时,我们再回过头来看这个问题。
evaluate的递归结构类似于解析器的结构。两者都反映了语言自身的结构。我们也可以将解析器和求值器集成到一起,在解析的同时求解表达式,但将其分离为两个阶段使得程序更易于理解。
这就是解释 Egg 所需的全部代码。这段代码非常简单,但如果不定义一些特殊的格式,或向环境中添加一些有用的值,你无法使用该语言完成很多工作。
