在类似方案的编译器中创建一个闭包

时间:2019-05-10 08:34:56

标签: compiler-construction elixir

我正在实现类似方案的Lisp,它将在某个时候编译为某种形式的字节码,可以在here中看到。不幸的是,我已经将自己编码成一个小坑,并且不确定如何摆脱它。本质上,给定一个看起来像这样的lambda(故意不使用外部b):

(lambda (a b) (lambda (c) (+ a c)))

我的代码产生以下语法树:

[
  type: :lambda,
  args: [[type: :word, val: 'a'], [type: :word, val: 'b']],
  body: [
    [
      type: :lambda,
      args: [[type: :word, val: 'c']],
      body: [
        [
          type: :expr,
          val: [
            [type: :word, val: '+'],
            [type: :word, val: 'a'],
            [type: :word, val: 'c']
          ]
        ]
      ]
    ]
  ]
]

不幸的是,当我真正开始生成字节码时,要为这些lambda创建必要的闭包并不容易(据我所知)。理想情况下,我想生成一棵看起来像这样的树:

[
  type: :lambda,
  args: [[type: :word, val: 'a'], [type: :word, val: 'b']],
  closure: [],
  body: [
    [
      type: :lambda,
      args: [[type: :word, val: 'c']],
      closure: [[type: :word, val: 'a']],
      body: [
        [
          type: :expr,
          val: [
            [type: :word, val: '+'],
            [type: :word, val: 'a'],
            [type: :word, val: 'c']
          ]
        ]
      ]
    ]
  ]
]

通过查看一个给定参数是否应该包含在闭包中来判断它是否应该很容易,但是,因为我只是在body上调用Enum.map不知道如何将这些信息返回给我的lambda对象。我不需要特定的代码来解决此问题,但是朝正确的方向进行一般指导/提示/推送会很棒(我知道这有点含糊,但是我不确定如何进行更具体的测试这种情况)。

1 个答案:

答案 0 :(得分:2)

您可以走下AST,在每个节点上构建绑定标识符列表。

例如,一个lambda节点绑定其参数(如果这些名称已在绑定列表中,则可以重写它们),以及letlet*。您还可以在返回该树时为每个AST节点建立一个引用的空闲标识符的列表。

lambdaletlet*从这些自由变量列表中删除标识符。

其余的操作很容易:在每个lambda节点上,您都计算引用列表和绑定列表之间的交集,结果将是此闭包必须捕获的环境。如果为空,这是一个没有环境的简单函数。

在您的示例中,它将是:

[b:() f:()](lambda (a b) [b:(a b) f:(a)] (lambda (c) [b: (a b c) f: (a c)] (+ a c)))

如您所见,内部lambda在其ab:列表之间有f:相同,因此您必须在此处发出闭包分配指令,从而构造一个元素的环境,a

您可以将此过程与变量重命名结合起来(例如,将lambda参数名称转换为参数编号)。