将赋值表达式编译为字节码
compiling assignment expression to bytecode
我正在努力将赋值表达式编译为字节码。我在写我自己的语言,这部分真的让我难住了。我的语言获取源代码并将其转换为标记,然后直接转换为字节码。例如,类似于:
a + 2
变成
TOKEN_NAME
TOKEN_ADD
TOKEN_INT
这将被解析并转换为字节码,看起来像
LOAD_VARIABLE (this is the a)
LOAD_CONSTANT (this is the 2)
ADD
这很简单。但是对于赋值表达式,例如:
a[0][1] = 2
会变成
TOKEN_NAME
TOKEN_L_BRACKET
TOKEN_INT
TOKEN_R_BRACKET
TOKEN_L_BRACKET
TOKEN_INT
TOKEN_R_BRACKET
TOKEN_ASSIGN
TOKEN_INT
我需要加载 a,在该对象上做一个下标(0 下标),然后将 2 存储到 1 下标中。我应该补充一点,解析器实际上是 LL(1),这使得这特别困难。
我想不出一种方法来确保左侧表达式的最后部分(我分配给的部分)没有被加载,但是值 (2) 存储在其中。
如果有任何不清楚的地方,请发表评论,我很乐意澄清我的程序。 (很难为编程语言的整个解释器制作 MCVE!)
提前致谢。
可以使用简单的回溯法构造引用:
- 像阅读表达式一样编译表达式
- 如果下一个操作需要左值,回头看上一个字节码操作
- 根据这个 table 转换最后一个操作:
LOAD_VALUE
转换为 GET_VALUE_REF
LOAD_PROPERTY
转换为GET_PROPERTY_REF
(LOAD_PROPERTY
由a.b
生成)
LOAD_ELEMENT
转换为GET_ELEMENT_REF
(LOAD_ELEMENT
由a[b]
生成)
- 任何其他操作码都会生成无效的左值错误。
这种方法对于最常见的语义来说已经足够了。对于 C,您将添加对取消引用运算符 *
的支持:GET_POINTER_VALUE
转换为 GET_POINTER_REF
,这实际上是一个无操作。
要实现这一点,您需要跟踪编译器生成的最后一个操作码,并有可能将其修补为另一个字节码。
表达式 a[0][2]
将编译为
LOAD_VARIABLE a (this is the a)
LOAD_CONSTANT 0 (this is the 0)
GET_ELEMENT
LOAD_CONSTANT 2 (this is the 2)
GET_ELEMENT
a[0][2] = 3
转换为
LOAD_VARIABLE a
LOAD_CONSTANT 0
GET_ELEMENT
LOAD_CONSTANT 2
GET_ELEMENT_REF
LOAD_CONSTANT 3
STORE_REF
如果您不需要参考,您也可以直接生成特定商店(例如您需要 a[b] += c
的参考)。
a[0][2] = 3
然后转换为
LOAD_VARIABLE a
LOAD_CONSTANT 0
GET_ELEMENT
LOAD_CONSTANT 2
LOAD_CONSTANT 3
STORE_ELEMENT (uses 3 stack slots)
而 a[0][2] += 3
产生:
LOAD_VARIABLE a
LOAD_CONSTANT 0
GET_ELEMENT
LOAD_CONSTANT 2
GET_ELEMENT_REF
LOAD_REF
LOAD_CONSTANT 3
ADD
STORE_REF
我正在努力将赋值表达式编译为字节码。我在写我自己的语言,这部分真的让我难住了。我的语言获取源代码并将其转换为标记,然后直接转换为字节码。例如,类似于:
a + 2
变成
TOKEN_NAME
TOKEN_ADD
TOKEN_INT
这将被解析并转换为字节码,看起来像
LOAD_VARIABLE (this is the a)
LOAD_CONSTANT (this is the 2)
ADD
这很简单。但是对于赋值表达式,例如:
a[0][1] = 2
会变成
TOKEN_NAME
TOKEN_L_BRACKET
TOKEN_INT
TOKEN_R_BRACKET
TOKEN_L_BRACKET
TOKEN_INT
TOKEN_R_BRACKET
TOKEN_ASSIGN
TOKEN_INT
我需要加载 a,在该对象上做一个下标(0 下标),然后将 2 存储到 1 下标中。我应该补充一点,解析器实际上是 LL(1),这使得这特别困难。
我想不出一种方法来确保左侧表达式的最后部分(我分配给的部分)没有被加载,但是值 (2) 存储在其中。
如果有任何不清楚的地方,请发表评论,我很乐意澄清我的程序。 (很难为编程语言的整个解释器制作 MCVE!)
提前致谢。
可以使用简单的回溯法构造引用:
- 像阅读表达式一样编译表达式
- 如果下一个操作需要左值,回头看上一个字节码操作
- 根据这个 table 转换最后一个操作:
LOAD_VALUE
转换为GET_VALUE_REF
LOAD_PROPERTY
转换为GET_PROPERTY_REF
(LOAD_PROPERTY
由a.b
生成)LOAD_ELEMENT
转换为GET_ELEMENT_REF
(LOAD_ELEMENT
由a[b]
生成)- 任何其他操作码都会生成无效的左值错误。
这种方法对于最常见的语义来说已经足够了。对于 C,您将添加对取消引用运算符 *
的支持:GET_POINTER_VALUE
转换为 GET_POINTER_REF
,这实际上是一个无操作。
要实现这一点,您需要跟踪编译器生成的最后一个操作码,并有可能将其修补为另一个字节码。
表达式 a[0][2]
将编译为
LOAD_VARIABLE a (this is the a)
LOAD_CONSTANT 0 (this is the 0)
GET_ELEMENT
LOAD_CONSTANT 2 (this is the 2)
GET_ELEMENT
a[0][2] = 3
转换为
LOAD_VARIABLE a
LOAD_CONSTANT 0
GET_ELEMENT
LOAD_CONSTANT 2
GET_ELEMENT_REF
LOAD_CONSTANT 3
STORE_REF
如果您不需要参考,您也可以直接生成特定商店(例如您需要 a[b] += c
的参考)。
a[0][2] = 3
然后转换为
LOAD_VARIABLE a
LOAD_CONSTANT 0
GET_ELEMENT
LOAD_CONSTANT 2
LOAD_CONSTANT 3
STORE_ELEMENT (uses 3 stack slots)
而 a[0][2] += 3
产生:
LOAD_VARIABLE a
LOAD_CONSTANT 0
GET_ELEMENT
LOAD_CONSTANT 2
GET_ELEMENT_REF
LOAD_REF
LOAD_CONSTANT 3
ADD
STORE_REF