在JavaScript中实现简化借用检查器的困难
出于所有意图和目的,我有一堆函数和函数调用,它们具有这种AST结构。这是一个函数数组在JavaScript中实现简化借用检查器的困难,javascript,algorithm,types,rust,borrow-checker,Javascript,Algorithm,Types,Rust,Borrow Checker,出于所有意图和目的,我有一堆函数和函数调用,它们具有这种AST结构。这是一个函数数组 const ast = [ { type: 'function', name: 'doX', inputs: [ { name: 'x', type: 'String' } ], calls: [ { type: 'call', name: 'do123',
const ast = [
{
type: 'function',
name: 'doX',
inputs: [
{
name: 'x',
type: 'String'
}
],
calls: [
{
type: 'call',
name: 'do123',
args: [
{
type: 'literal',
value: 123
},
{
type: 'reference',
value: 'x'
}
]
},
{
type: 'call',
name: 'test',
args: [
{
type: 'borrow',
value: 'x'
}
],
block: [
{
type: 'call',
name: 'set',
args: [
{
type: 'reference',
value: 'a'
},
{
type: 'move',
value: {
type: 'call',
name: 'multiply',
args: [
{
type: 'borrow',
value: 'x'
},
{
type: 'literal',
value: 2
}
]
}
}
]
},
{
type: 'call',
name: 'doFoo',
args: [
{
name: 'a',
value: {
type: 'literal',
value: 'foo'
}
},
{
name: 'b',
value: {
type: 'reference',
value: 'x'
}
}
]
}
]
}
]
}
]
其结果是:
function doX(x) {
do123(123, x)
test(x) {
set(a, multiply(x, 2))
doFoo('foo', a)
}
}
现在忘记我也在尝试处理词法作用域(即嵌套函数)的事实,因为这可能会使这个问题变得不必要的复杂
还要注意,一切都是一个函数,因此您需要设置(foo,'bar')来实例化一个变量。尽管它以命令式方式执行,但它不是一种函数式语言。这只是为了简化问题,所以没有各种复杂类型的问题。对于这个例子,只有函数和调用
还请注意,我们有借用(几种类型的“引用”之一)。您可以拥有借用(股份所有权)或移动(转让所有权),借用可以标记为可变或不可变(默认为不可变)。我们的目标是重现Rust的功能,让这个迷你/演示“编译器”完全像Rust使用借阅检查器一样
此外,我们还在该函数中创建了一个新的局部作用域,并定义了变量a
我们的目标是要弄清楚这一点:
每个变量的生存期(当它可以从内存中释放d时,就像在Rust中一样)。并在AST中插入一个free(name)
调用
通过Rust borrow checker,它可以检查变量的所有者和生存期,以便判断变量是否正确使用以及何时超出范围
因此,首先我们必须收集变量声明(并且只需提升它们,这样我们就知道有多少局部变量,这样我们就可以创建大小合适的激活记录)。要做到这一点,我想我们只需要沿着AST向下移动一次
第二,我开始迷茫到底要做什么才能完成(1)。首先,创建一个地图
。然后从头开始学习AST。对于每个变量,检查它是否在映射中(如果它已被标记/遍历/遇到)。如果它不在地图上,把它添加到地图上,我还不知道我们为什么要这样做。为每个作用域创建一个新映射。在每个作用域的末尾,释放变量。这就是我所处的位置
process(ast)
function process(ast) {
ast.forEach(fxn => {
let stack = []
let locals = { count: 0, vars: {} }
fxn.inputs.forEach(input => {
locals.vars[input.name] = locals.count++
})
fxn.calls.forEach(call => {
handleCall(call, locals)
})
function handleCall(call, locals) {
if (call.name == 'set') {
let name = call.args[0].value
locals.vars[name] = locals.count++
}
if (call.block) {
call.block.forEach(nestedCall => {
handleCall(nestedCall, locals)
})
}
}
})
}
现在的问题是,如何添加借阅检查,以便知道在何处插入免费(名称)
我开始迷失在野草中,看不见森林,看不见树木。到目前为止,我已经读了很多关于Rust中的借用检查的书,但不知道它是如何实现的。我已经阅读了普罗尼尔斯的源代码,阅读了大部分,阅读了关于生命周期、借用、易变性和所有权的文档,以及一些编译器组会议记录和文档。但似乎没有人能简单地解释在实践中进行借阅检查的算法
正在寻找有关此特定示例的帮助,以帮助我开始了解JavaScript中借用检查器的算法。想知道是否可以用这个或稍微复杂一点的例子来概括算法应该做什么,以确定变量是否被正确借用,以及何时可以释放
在我真正编写算法之前,我需要更好地了解算法应该做什么,它应该如何工作。这就是这个问题。如果你知道如何编写它的演示,那就太好了!但是,对步骤进行更深入的解释(而不是对关键步骤进行修饰)也会有所帮助。我可以看到一些问题:
我在代码中没有看到任何引用语法,因为我没有看到任何“&”。你唯一拥有的就是移动。如果你开始脱离这种语法,它就开始毁掉一切
您还在javascript中同时使用了“reference”
和“borrow”
,这让人困惑,因为它们在Rust中的意思是相同的
您没有doX
参数的类型,这意味着您无法正确处理该变量,因为它可能是一个移动,这可能会导致调用函数的范围问题
b
如何成为x
的参考
锈蚀/了解所有权:
以下是上述链接的概要:
对于所有这些,当我说“variable”时,我指的是“使用堆的变量”。另外,请随意将“reference”替换/解释为“借用”
如果变量仍然有效,则该变量将在其作用域的末尾被删除。删除意味着释放内存。作用域是从引入变量到最后一次使用变量的时间。如果对该变量的任何引用仍在范围内,则为错误
默认情况下,变量是移动的,而不是复制的
复制变量时,将创建一个新的唯一变量并复制数据。这创造了一个全新的独立变量
当一个变量被移动到另一个变量时,初始变量被标记为无效,不能再使用。(这种内存管理方式的一大折衷)新变量指向旧变量所指向的堆数据。如果在标记为无效后使用初始变量,则为错误
通过以下三种方式之一将变量分配给另一个变量,可以移动变量:
直接的。e、 g.x=y
通过设置函数参数的值。e、 g.f(x)
通过从函数返回它,例如x=f()
如果您将一个变量传递给另一个函数,并希望在调用之后继续使用它,那么该函数必须通过返回它来“返回”(与预期的另一个主要偏差)。这只是(2)后接(3),例如x=f(x)
是pos
process(ast)
function process(ast) {
ast.forEach(fxn => {
let stack = []
let locals = { count: 0, vars: {} }
fxn.inputs.forEach(input => {
locals.vars[input.name] = {
id: locals.count++,
status: '?'
}
})
fxn.calls.forEach(call => {
handleCall(call, locals)
})
function handleCall(call, locals) {
if (call.name == 'set') {
let name = call.args[0].value
let local = locals.vars[name] = {
id: locals.count++
}
let value = call.args[1]
if (value.type == 'move') {
local.status = 'owner'
} else if (value.type == 'borrow') {
local.status = 'borrow'
} else {
// literal
}
if (value.value.type == 'call') {
handleCall(value.value, locals)
}
} else {
}
if (call.block) {
let newLocals = {}
call.block.forEach(nestedCall => {
handleCall(nestedCall, newLocals)
})
}
}
})
}