环境
evaluate所接受的作用域是一个对象,它的名称对应绑定名称,它的值对应这些绑定所绑定的值。 我们定义一个对象来表示全局作用域。
我们需要先定义布尔绑定才能使用之前定义的if语句。由于只有两个布尔值,因此我们不需要为其定义特殊语法。我们简单地将true、false两个名称与其值绑定即可。
const topEnv = Object.create(null);topScope.true = true;topScope.false = false;
我们现在可以求解一个简单的表达式来对布尔值求反。
let prog = parse(`if(true, false, true)`);console.log(evaluate(prog, topScope));// → false
为了提供基本的算术和比较运算符,我们也添加一些函数值到作用域中。为了确保代码短小,我们在循环中使用Function来合成一批运算符,而不是分别定义所有运算符。
for (let op of ["+", "-", "*", "/", "==", "<", ">"]) {topScope[op] = Function("a, b", `return a ${op} b;`);}
输出也是一个实用的功能,因此我们将console.log包装在一个函数中,并称之为print。
topScope.print = value => {console.log(value);return value;};
这样一来我们就有足够的基本工具来编写简单的程序了。下面的函数提供了一个便利的方式来编写并运行程序。它创建一个新的环境对象,并解析执行我们赋予它的单个程序。
function run(program) {return evaluate(parse(program), Object.create(topScope));}
我们将使用对象原型链来表示嵌套的作用域,以便程序可以在不改变顶级作用域的情况下,向其局部作用域添加绑定。
run(`do(define(total, 0),define(count, 1),while(<(count, 11),do(define(total, +(total, count)),define(count, +(count, 1)))),print(total))`);// → 55
我们之前已经多次看到过这个程序,该程序计算数字 1 到 10 的和,只不过这里使用 Egg 语言表达。很明显,相较于实现同样功能的 JavaScript 代码,这个程序并不优雅,但对于一个不足 150 行代码的程序来说已经很不错了。
