环境

evaluate所接受的作用域是一个对象,它的名称对应绑定名称,它的值对应这些绑定所绑定的值。 我们定义一个对象来表示全局作用域。

我们需要先定义布尔绑定才能使用之前定义的if语句。由于只有两个布尔值,因此我们不需要为其定义特殊语法。我们简单地将truefalse两个名称与其值绑定即可。

  1. const topEnv = Object.create(null);
  2. topScope.true = true;
  3. topScope.false = false;

我们现在可以求解一个简单的表达式来对布尔值求反。

  1. let prog = parse(`if(true, false, true)`);
  2. console.log(evaluate(prog, topScope));
  3. // → false

为了提供基本的算术和比较运算符,我们也添加一些函数值到作用域中。为了确保代码短小,我们在循环中使用Function来合成一批运算符,而不是分别定义所有运算符。

  1. for (let op of ["+", "-", "*", "/", "==", "<", ">"]) {
  2. topScope[op] = Function("a, b", `return a ${op} b;`);
  3. }

输出也是一个实用的功能,因此我们将console.log包装在一个函数中,并称之为print

  1. topScope.print = value => {
  2. console.log(value);
  3. return value;
  4. };

这样一来我们就有足够的基本工具来编写简单的程序了。下面的函数提供了一个便利的方式来编写并运行程序。它创建一个新的环境对象,并解析执行我们赋予它的单个程序。

  1. function run(program) {
  2. return evaluate(parse(program), Object.create(topScope));
  3. }

我们将使用对象原型链来表示嵌套的作用域,以便程序可以在不改变顶级作用域的情况下,向其局部作用域添加绑定。

  1. run(`
  2. do(define(total, 0),
  3. define(count, 1),
  4. while(<(count, 11),
  5. do(define(total, +(total, count)),
  6. define(count, +(count, 1)))),
  7. print(total))
  8. `);
  9. // → 55

我们之前已经多次看到过这个程序,该程序计算数字 1 到 10 的和,只不过这里使用 Egg 语言表达。很明显,相较于实现同样功能的 JavaScript 代码,这个程序并不优雅,但对于一个不足 150 行代码的程序来说已经很不错了。