将赋值表达式编译为字节码

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_REFLOAD_PROPERTYa.b生成)
    • LOAD_ELEMENT转换为GET_ELEMENT_REFLOAD_ELEMENTa[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