JavaScript 中的 Lisp 宏引用实现

Lisp macros quotation implementation in JavaScript

我有 basic scheme like lisp in JavaScript 并且反引号和引号宏有问题,如果它们是数组的第一个元素,它们会评估符号,例如

> `(foo 10)

报错foo not found

它适用于这样的代码

> (define x '(1 2 3))
> (print `(1 2 ,@x 4 5))

我的评估函数如下所示:

function evaluate(code, env) {
  env = env || global_env;
  var value;
  if (typeof code === 'undefined') {
    return;
  }
  var first = code.car;
  var rest = code.cdr;
  if (first instanceof Pair) {
    value = evaluate(first, env);
  }
  if (typeof first === 'function') {
    value = first;
  }
  if (first instanceof Symbol) {
    value = env.get(first);
    if (value instanceof Macro) {
      return evaluate(value.invoke(rest, env), env);
    } else if (typeof value !== 'function') {
      throw new Error('Unknown function `' + first.name + '\'');
    }
  }
  if (typeof value === 'function') {
    var args = [];
    var node = rest;
    while (true) {
      if (node instanceof Pair) {
        args.push(evaluate(node.car, env));
        node = node.cdr;
      } else {
        break;
      }
    }
    var promises = args.filter((arg) => arg instanceof Promise);
    if (promises.length) {
      return Promise.all(args).then((args) => {
        return value.apply(env, args);
      });
    }
    return value.apply(env, args);
  } else if (code instanceof Symbol) {
    value = env.get(code);
    if (value === 'undefined') {
      throw new Error('Unbound variable `' + code.name + '\'');
    }
    return value;
  } else {
    return code;
  }
}

my Macro i 只是一个函数,return Pair 的实例与要评估的输入和代码参数相同,env 是具有函数 get return 函数的 Environment 实例或变量。

quasiquote 的宏如下所示:

  quasiquote: new Macro(function(arg) {
    var env = this;
    function recur(pair) {
      if (pair instanceof Pair) {
        var eval_pair;
        if (Symbol.is(pair.car.car, 'unquote-splicing')) {
          eval_pair = evaluate(pair.car.cdr.car, env);
          if (!eval_pair instanceof Pair) {
            throw new Error('Value of unquote-splicing need to be pair')
          }
          if (pair.cdr instanceof Pair) {
            if (eval_pair instanceof Pair) {
              eval_pair.cdr.append(recur(pair.cdr));
            } else {
              eval_pair = new Pair(eval_pair, recur(pair.cdr));
            }
          }
          return eval_pair;
        }
        if (Symbol.is(pair.car, 'unquote-splicing')) {
          eval_pair = evaluate(pair.cdr.car, env);
          if (!eval_pair instanceof Pair) {
            throw new Error('Value of unquote-splicing need to be pair')
          }
          return eval_pair;
        }
        if (Symbol.is(pair.car, 'unquote')) {
          return evaluate(pair.cdr.car, env);
        }
        var car = pair.car;
        if (car instanceof Pair) {
          car = recur(car);
        }
        var cdr = pair.cdr;
        if (cdr instanceof Pair) {
          cdr = recur(cdr);
        }
        return new Pair(car, cdr);
      }
      return pair;
    }
    return recur(arg.car);
  }),

是否应该将引用硬编码到评估函数中,而不应按原样处理参数和 return 它们?那么每个符号的反引号会 return (引号)?

在@WillNess 的评论帮助下,我解决了这个问题,我需要在解析器中添加特殊情况。

  if (first instanceof Symbol) {
    value = env.get(first);
    if (value instanceof Macro) {
      value = value.invoke(rest, env);
      if (value instanceof Constant) {
        return value.value;
      }
      return evaluate(value, env);
    }
  ...

and quasiquote 宏用常量包装输出。这只是:

function Constant(value) {
   this.value = value;
}

准引用的目的是 return 可以评估为 return 新形式的代码。 例如:

`(a b ,@list (,c ,d))

... 可能展开为:

(append (list 'a 'b) list (list (list c d)))

... 这样,在评估期间,它会生成预期的列表。 您可以找到一些方法来避免分配实际上是常量的列表,例如 '(a b) 而不是 (list 'a 'b),但这意味着不能保证从 quasi-quotation 构建的值始终可以修改。

但是,您似乎在运行时扩展宏,在必要时评估其嵌套形式,因此在您的情况下,returned 列表每次都会不同。在那种情况下,我相信 Constant 所采用的方法应该有效。