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

C 将赋值表达式编译为字节码,c,parsing,compiler-construction,bytecode,interpreter,C,Parsing,Compiler Construction,Bytecode,Interpreter,我正试图将赋值表达式编译成字节码。我正在用自己的语言写作,这一部分真的让我难堪。我的语言接受源代码并将其转换为令牌,这些令牌直接转换为字节码。例如,类似于: 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

我正试图将赋值表达式编译成字节码。我正在用自己的语言写作,这一部分真的让我难堪。我的语言接受源代码并将其转换为令牌,这些令牌直接转换为字节码。例如,类似于:

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
我需要加载一个,在该对象上做一个下标(0下标),然后将2存储到1下标中。我应该补充一点,解析器实际上是LL(1),这使得这一点特别困难

我想不出一种方法来确保左侧表达式的最后一部分(我分配给的部分)没有加载,而是将值(2)存储在其中

如果有任何不清楚的地方,请留下评论,我很乐意澄清我的计划。(为一种编程语言的整个解释器制作MCVE是相当困难的!)


提前感谢。

您可以使用简单的回溯方法构造引用:

  • 编译表达式,就像您正在读取表达式一样
  • 如果下一个操作需要左值,请回顾上一个字节码操作
  • 根据下表转换最后一个操作:
    • LOAD\u VALUE
      转换为
      GET\u VALUE\u REF
    • LOAD\u属性
      转换为
      GET\u属性
      LOAD\u属性
      a.b
      生成)
    • LOAD\u ELEMENT
      转换为
      GET\u ELEMENT\u REF
      LOAD\u ELEMENT
      a[b]
      生成)
    • 任何其他操作码都会生成无效的左值错误
对于最常见的语义,此方法已足够。对于C,您将添加对解引用操作符的支持
*
GET\u POINTER\u VALUE
转换为
GET\u POINTER\u REF
,这本质上是一个no操作

要实现这一点,您需要跟踪编译器生成的最后一个操作码,并可能将其修补到另一个字节码中

表达式
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
LOAD_VARIABLE a
LOAD_CONSTANT 0
GET_ELEMENT
LOAD_CONSTANT 2
LOAD_CONSTANT 3
STORE_ELEMENT (uses 3 stack slots)
如果不需要引用,也可以直接生成特定的存储(例如,
a[b]+=c
需要引用)

a[0][2]=3
然后转换为

LOAD_VARIABLE a
LOAD_CONSTANT 0
GET_ELEMENT
LOAD_CONSTANT 2
GET_ELEMENT_REF
LOAD_CONSTANT 3
STORE_REF
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

难道你不能得到分配令牌,然后返回到名字或括号吗?它是一个LL(1)解析器,因此我不能回溯。在实际代码中,这些表达式在下标之间可能非常大,因此返回将非常低效。您不能回溯,但可以将标记存储在辅助列表中。这可能适用于下标,但我不确定是否类似于
x=2
如何确保在x中存储2,而且没有加载x firstWell,这听起来像是递归下降解析器,@DanielGee,而不是LL(1)。递归下降解析器也构建AST,但它通过一系列堆栈帧体现在调用堆栈上。