由于您的问题不是很具体,因此只能使用通用算法来回答。我也没有声称这是“最明智”的方式,只是一种有效的方式。
首先,我们需要定义问题空间。
您的脚本语言有局部变量(使用 Lua 术语):非全局变量。这些是理论上可以被 lambda 捕获的变量。
现在,让我们假设您的脚本语言没有动态选择局部变量的方法。这意味着,只需检查语法树,就可以看到以下内容:
- 函数捕获了哪些局部变量。
- 函数未捕获哪些局部变量。
- 哪些函数捕获其范围之外的局部变量。
- 哪些函数不会捕获其范围之外的局部变量。
鉴于此信息,局部变量现在分为两组:纯局部变量和捕获的局部变量。我将这些称为“纯当地人”和“被俘虏的当地人”。
纯粹的本地人,为了更好的术语,是注册人。当你编译成你的字节码时,纯本地是最容易处理的。它们是特定的堆栈索引,或者它们是特定的寄存器名称。无论您如何进行堆栈管理,都会为纯本地人分配特定范围内的固定位置。如果您使用 JIT 的力量,那么这些将成为寄存器,或者最接近可能的东西。
关于捕获的本地变量,您需要了解的第一件事是:它们必须由您的内存管理器管理。它们独立于当前的调用堆栈和范围而存在,因此,它们需要是独立的对象,由捕获它们的函数引用。这允许多个函数捕获相同的本地数据,从而相互引用彼此的私有数据。
因此,当您进入一个包含捕获的 lambda 的作用域时,您将分配一块内存,其中包含属于该特定作用域的所有捕获的局部变量。例如:
comp(threshold)
{
local data;
return lambda(x)
{
return x < (threshold + data);
};
}
函数的根作用域comp
有两个局部变量。两人都被俘虏了。因此,捕获的局部数为 2,纯局部数为零。
因此,您的编译器(字节码)将为纯本地变量分配 0 个寄存器/堆栈变量,并将分配一个包含两个变量的独立对象。假设您正在使用垃圾收集,您将需要一些东西来引用它以使其继续存在。这很容易:您在寄存器/堆栈位置引用它,脚本不能直接访问它。所以真的,你确实分配了一个寄存器/堆栈变量,但脚本不能直接接触它。
现在,让我们看看有什么lambda
作用。它创建了一个函数。同样,我们知道这个函数捕获了一些超出其范围的变量。我们知道它捕获了哪些变量。我们看到它捕获了两个变量,但我们也看到这两个变量来自同一个独立的内存块。
所以要做lambda
的是创建一个函数对象,该对象具有对某些字节码的引用和对其关联的变量的引用。字节码将使用该引用来获取其捕获的变量。您的编译器知道哪些变量是函数的纯局部变量(如参数x
),哪些是外部捕获的局部变量(如阈值)。所以它可以弄清楚如何访问每个变量。
现在,当lambda
完成时,它返回一个函数对象。此时,捕获的变量被两件事引用:lambda 函数和堆栈:函数的当前范围。然而,当return
完成时,当前作用域被销毁,并且之前引用的所有内容都不再被引用。因此,当它返回函数对象时,只有 lambda 函数具有对捕获变量的引用。
不过,这一切都相当复杂。一个更简单的实现是只有效地捕获所有局部变量;所有局部变量都是捕获的局部变量。如果你这样做,那么你的编译器会简单得多(而且可能更快)。当进入一个新的作用域时,该作用域的所有局部变量都被分配在一块内存中。创建函数时,它会引用它使用的所有外部作用域(如果有)。并且当一个范围退出时,它会删除对它分配的局部变量的引用。如果没有其他人引用它,则可以释放内存。
这非常简单明了。