使用ANTLR4从DSL转换为Java,需要重建(而不是评估)表达式的策略



我正在使用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              : [ tnr]+    -> 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 方法,而是使用自上而下的方法从这些元素的封闭 expr 中访问这些元素。也许这给我带来了一个问题;不幸的是,如果是,我看不出如何。

任何关于如何继续以同样方式解决这个问题或如何改变战略的建议将不胜感激。

我看过许多关于SO的问题和例子,但找不到等价物,并且有Parr的ANTLR4参考书,这非常有用,但在任何一个地方都找不到这个特定问题的策略。

处理此问题的一种方法是继续使用作用域符号表 - 或者更具体地说是作用域"op"表。 在每个"进入Expr"上推送范围,在每个"exitExpr"上弹出。 在每个子 expr 的输入(例如 'enterAddSub')上,添加一个描述该子 expr 运算符的"op 对象"到当前范围。

现在,在每个"expr"的输入和退出时,评估父作用域中的 op 对象,以查看是否有需要打印的 op 的某些部分。在"enterAddSub"的特殊情况下,选择在打印第二个 expr 中的任何内容之前打印运算符的策略,请在 op 对象中包含一个计数器,以便在 op 对象的第三次评估时打印运算符(否则递增计数器)。 对于括号子规则,该策略是从 enterExpr 打印 "(" 和 exitExpr 打印 ")"进行评估。

对于简单情况,操作对象通常具有"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()

最新更新