我正在使用 ANTLR4 将一种词汇量有限的简单人工编程语言翻译(“编译”)为 Java。由于此练习不需要任何评估,因此即使是条件表达式也只能完全转换为等效的 Java 代码,因此我正在努力实现基于监听器的解决方案。由于语言词汇量有限,我已经能够围绕大部分翻译任务和相关策略,在很大程度上依赖于一个简单的单作用域符号表来保存和比较编译时和运行时变量(记住,没有表达式的评估是正在执行)。
简单的算术和比较表达式很容易解析并转换为 Java;但是,我遇到了嵌套和复合表达式的麻烦。它们解析得很好,但是将它们翻译成 Java 是一个问题。我尝试了多种处理它们的策略,其中大部分包括获取表达式的 lhs 和 rhs 并使用各种机制检查一个表达式是否是嵌入式表达式(例如,检查字符串中是否出现括号或其他运算符表示),检查变量并在符号表中查找它们,如果确定 lhs 或 rhs 是数字或有效变量,则将其与运算符一起压入堆栈。然而,弹出这些堆栈元素并尝试以正确的顺序重新组合表达式是徒劳的,因为表达式的嵌套位置会影响它们何时被压入以及相关运算符的放置位置。
我觉得我在正确的道路上:我的存储和重新生成表达式的策略,但需要插入。但是,我担心如果我不走在正确的道路上,或者如果有更好的方法(可能是通过经过良好测试的设计模式),我可能会浪费时间。
完整的语法如下所示。我认为这是不言自明的……除了用于在字符串中转义的嵌入式引号的三重引号 (""")。请记住,这是一种非常有限的语言,我不评估任何表达式。
grammar Test;
prog
: (stat ';')+
| COMMENT ;
stat
: assign
| if_stat
| loop_stat
| expr
| get
| put
;
assign
: VARIABLE '=' expr
;
if_stat
: 'if' expr 'then' (stat ';')+ (('elsif' expr 'then' (stat ';')+)* 'else' (stat ';')+)? 'end if'
;
loop_stat
: 'loop' ('exit when' expr ';')* (stat ';')+ 'end loop'
;
expr
: number #Num
| variable #Var
| '!' expr #LogNeg
| expr '&' expr #LogAnd
| expr '|' expr #LogOr
| expr ('='|'<>'|'<'|'>'|'<='|'>=') expr #Comp
| '-' expr #Neg
| expr ('*'|'/'|'%') expr #MultDivRem
| expr ('+'|'-') expr #AddSub
| '(' expr ')' #Parens
;
get
: 'get' variable (',' variable)*
;
put
: 'put' (expr|str) (',' (expr|str))*
;
number
: NUMBER
;
variable
: VARIABLE
;
str
: STRING
;
COMMENT : '#' .*? '\n' -> skip ;
WS : [ \t\n\r]+ -> skip ;
VARIABLE : LETTER (LETTER|DIGIT|'_')* ;
NUMBER : DIGIT (DIGIT|'_' DIGIT)* ;
STRING : ('"""'|'"') .*? ('"""'|'"') ;
fragment LETTER : [a-z] | [A-Z] ;
fragment DIGIT : [0-9] ;
表达式处理方法示例如下:
public void enterAddSub(SimpleParser.AddSubContext ctx) {
// Simplified example does not account for variables.
boolean opSeen = false;
// Get operator and left and right hand expressions.
String op = ctx.getChild(1).getText();
String lhs = ctx.getChild(0).getText();
String rhs = ctx.getChild(2).getText();
// lhs is not a nested expression, print it. If nested, skip for now.
if (isInteger(lhs) == true) {
//System.out.print(lhs + " " + op + " ");
cts.push(lhs);
cts.push(op);
opSeen = true;
}
// rhs is not a nested expression, print it. If nested, skip for now.
if (isInteger(rhs) == true) {
//System.out.print(rhs);
cts.push(rhs);
}
else {
if (opSeen == false) {
//System.out.print(op);
cts.push(op);
}
}
//System.out.println();
}
相应的 expr exit 方法只是将堆栈中的所有内容弹出到一个字符串中,这就是一个不按顺序排列的谜题,我无法想出一个算法来始终如一地将元素放在需要的位置是。
此外,我没有重写 Number 或 Variable 方法,而是使用自上而下的方法从它们的封闭表达式中访问这些元素。也许这给我带来了麻烦;不幸的是,如果是的话,我看不出如何。
任何有关如何继续以相同方式解决此问题或如何更改策略的建议都将不胜感激。
我查看了许多关于 SO 的问题和示例,但找不到等效项,并且有 Parr 的 ANTLR4 引用书,它非常有用,但在任何一个地方都找不到针对这个特定问题的策略。
最佳答案
处理此问题的一种方法是继续使用范围符号表 - 或者更具体地说是范围“op”表。在每个“enterExpr”上推送范围并在每个“exitExpr”上弹出。在每个子表达式的输入中,例如“enterAddSub”,添加一个“op object”来表征该子表达式的运算符到当前范围。
现在,在每个“expr”的进入和退出时,评估父作用域中的 op 对象以查看是否有您需要打印的 op 的某些部分。在“enterAddSub”的特殊情况下,并选择在从第二个 expr 打印任何内容之前打印运算符的策略,在 op 对象中包含一个计数器,以便在 op 对象的第三次评估时打印运算符(否则递增计数器).对于 parens 子规则,策略是根据 enterExpr 打印“(”和 exitExpr 打印“)”进行评估。
对于简单的情况,op 对象通常有'onEnter' 和'onExit' 方法来调用自评估并有条件地打印结果就足够了。
在更有趣的情况下,特别是当翻译可以从延迟求值中获益时,op 对象变成了一个智能累加器。在每个“onExit”评估中,它决定是打印、累积还是将其值添加到其父范围内的 op 对象。
enterExpr:
pushScope()
parentScope().onEntry()
enterAddSub:
currentScope().add(new OpObject(ADDSUB)) // enum
enterExpr
visit ...
exitExpr
enterExpr
visit ...
exitExpr
exitAddSub:
currentScope().finalize()
exitExpr:
call parentScope().onExit()
popScope()
关于java - 使用 ANTLR4 从 DSL 转换为 Java,需要重建(而不是评估)表达式的策略,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/26986278/