javascript - “范围”或“上下文”如何在编译程序中存储和引用?
问题描述
抱歉,如果我混淆了“范围”和“上下文”这两个术语,但基本上我指的是我认为的词法范围,但是当函数(或类主体)正在评估时它的实例。
我正在努力实现一种奇怪的基于树的编程语言,并且有不同的结构需要一种“范围”。类似的情况出现在我熟悉的 2 种语言中,JavaScript 和不太常见的 Ruby。在 Ruby 中,您有可执行的类主体,因此它们有自己的范围,但是您也有具有自己范围的可执行函数。然后在块内部,它们每个都有自己的范围。“词法”范围树基本上是您可以在父树的可见代码中引用的变量/东西树(可见范围是词法范围)。
同样,我想为模块、函数和其他一些东西实现这个,比如我的视图/组件(我不想成为像 React 这样的函数,类似地,我的语言中的模块不是评估函数)。在 JavaScript 中,您只有被调用的函数,并且范围以某种方式存储在每个函数中。
但我想知道的是,什么是数据模型/数据结构/或者通常是范围如何存储和引用的“结构”?从字面上看,在 AST 或任何地方,它是如何工作的?
我想象的是将模块包装在实际上是一个“树”对象中,例如:
{
type: 'tree',
scope: {
foo: 'bar',
},
object: module
}
这里的tree
对象是某种 AST 包装器对象,它具有对 上使用的范围的引用object
,以及对象本身。这是为了防止对象被它没有的额外scope
属性污染:模块没有一般意义上的范围属性。模块的解析是有作用域的,所以也许树对象实际上被称为解析对象。
{
type: 'resolution',
scope: {
foo: 'bar',
},
object: module
}
但随后它开始变得多毛。模块中有类,它们有一个类作用域和一个实例作用域。或具有实例范围的函数。或我正在使用的不同自定义对象中的其他范围树。
所以我想象做的是把这个解析对象称为“叉子”,本质上是构建一个数据树。每个 fork 对象都有一个 scope 属性,每个对象实际上都包含在一个 fork中。
const moduleFork = {
type: 'fork',
scope: {
foo: 'bar',
},
children: {
// this is a module now.
functions: {
type: 'fork',
children: {}
}
}
}
然后在functions
对象内部,我们有一个函数示例:
// the functions scope is the module itself.
moduleFork.children.functions.scope = moduleFork.children
// then a function func1
moduleFork.children.functions.func1 = {
// this is inside the function now.
type: 'fork',
scope: moduleFork.children,
children: {
params: {
type: 'fork',
list: true,
children: {
param1: {
type: 'fork',
children: {
name: {
type: 'string',
value: 'param1'
},
default: {
type: 'string',
value: 'hello world'
}
}
}
}
}
}
}
我不知道,类似的东西,我还在努力。但正因如此,AST 中的每个对象都有一个对可用于获取变量值的范围的引用。我省略了一些重复,但实际上它或多或少看起来像这样:
// then a function func1
{
// this is inside the function now.
type: 'fork',
scope: moduleFork.children,
children: {
params: {
type: 'fork',
list: true,
scope: moduleFork.children,
children: {
param1: {
type: 'fork',
scope: moduleFork.children,
children: {
name: {
type: 'string',
value: 'param1'
},
default: {
type: 'string',
value: 'hello world'
}
}
}
}
}
}
}
这样一来,您就可以做到getFromTree(astNode.scope, 'foo')
,而且很简单。同样,由于我们在模块的上下文中,我们可以这样做getFromTree(paramNode.scope, 'functions')
,它会从模块中获取函数数组(从fork
包装器类的东西反序列化)。
在我的语言中,一些 AST 节点会像{ type: 'reference', path: ['foo'] }
,在这种情况下,这个对象知道范围很重要,这样它才能解析foo
。因此,拥有“包装分叉”概念似乎可以很容易地传递范围,尤其是当您拥有事件和数据绑定之类的东西时。而不是像 JavaScript 那样具有实际的嵌套绑定函数范围,您只是在处理 AST 树对象。
关键问题是,这在其他编程语言实现中是如何完成的?例如,当 v8 编译 JavaScript 时,每个 AST 对象是否都有对其作用域对象的引用?因此,每个 AST 对象是否都包装在“scopeResolver”对象之类的东西中,就像我在这里尝试做的那样?
如果可以的话,请画出范围如何工作的数据结构的基本图片,以及它是否在每个对象中都被引用,就像我在这里尝试做的那样。
解决方案
V8 通过一个在AST 节点以及所有其他引入自己的范围的节点中Scope
引用的名为的类来表示范围,例如,或.Block
With
TryCatch
FunctionExpression
例如,当 v8 编译 JavaScript 时,每个 AST 对象是否都有对其作用域对象的引用?因此,每个 AST 对象是否都包装在“scopeResolver”对象之类的东西中,就像我在这里尝试做的那样?
没有也没有。在解释和编译期间,AST 树总是深度优先遍历,因此当访问读取或写入变量的节点时,父块已经被遍历。因此,在遍历期间将父块存储在某种堆栈中似乎更容易,而不是为 AST 中的每个节点添加另一个引用。尤其是手动内存管理,这似乎是一个很大的开销,没有任何好处。
据我所见,V8 解释器将作用域保留在ContextScope
链表内。
推荐阅读
- react-native - react-native Avatar 元素为 uri 属性添加授权标头
- vue.js - 使用类绑定时未显示 Vue 错误页面
- python - QTreeView 拖放:QAbstractItemView.InternalMove 允许拖放外部视图?
- ios - 用户选择后应用跟踪透明度重置
- python - 序数编码:如何对满足特定条件的值进行编码?
- java - Mkdir() 在 Android Realme 设备中不起作用?
- c++ - C ++设置允许重复?
- python - 如何在 spyder anaconda 中安装 arcpy
- java - 注释处理不适用于 lombok 和 java
- arrays - 如何从文本文件创建多个数组并循环遍历每个数组的值