我正在构建一个同类AST的树步行者(所有节点都有相同的类),评估if语句的正确方法是什么?
我的AST if if是这样的:
我会在解析IF
块时,依次评估他的CONDBLOCK
个孩子,如果其中一个是真的,那么树步行者不会评估其余的。
更清楚的是,我的树步行者是这样的:
ifStat : ^(IF { jump=false; } condition* defcond?)
condition : { if (jump) return retval; } ^(CONDBLOCK exp block) { jump=$exp.value; }
defcond : ^(DEFAULT block)
我的问题是,如果在示例中$op=+
所以必须执行第一个CONDBLOCK
,我不想评估任何其他内容,我想要执行第一个CODEBLOCK
并继续在我的AST树中评估if
之后的块。
现在我已经用condition
规则实现了一个标志,如果标志为真则返回(这意味着另一个块已被执行)。
但return retval;
完全停止了执行,我想在不评估剩余条件的情况下上升。我怎么能这样做?
答案 0 :(得分:2)
来自AST的涉及分支或跳转的任何类型的运行时评估可能会变得丑陋。您可能需要考虑将AST转换为一系列更常规的操作并按顺序执行它们。这是一个额外的步骤,但它会让你摆脱像这样的堵塞,我认为它比AST评估者更容易验证和调试。
在此过程中,这是一种跳过评估后续condition
和defcond
规则的方法。我坚持你所拥有的结构,这意味着评估有两个不同的阶段:匹配阶段(exp
)和执行阶段(block
)。这是值得注意的,因为阶段在子图的不同部分处理,并且没有自然的跳转方式,因此需要在整个if
语句中跟踪它们。
这是一个简单的类来管理跟踪单个if
评估:
class Evaluation {
boolean matched = false;
boolean done = false;
}
如果matched
为真且done
为false,则会执行下一个block
。执行后,done
设置为true。当matched
和done
都为真时,block
语句的其余部分不再执行if
。
以下是处理此问题的树解析器规则:
ifStat
@init { Evaluation eval = new Evaluation(); }
: ^(IF condition[eval]* defcond[eval]?)
;
condition [Evaluation eval]
: ^(CONDBLOCK exp {if ($exp.value) eval.matched = true;} evalblock[eval])
;
defcond [Evaluation eval]
: ^(DEFAULT {eval.matched = true;} evalblock[eval]) //force a match
;
evalblock [Evaluation eval]
: {eval.matched && !eval.done}? //Only do this when a condition is matched but not yet executed
block //call the execution code
{eval.done = true;} //evaluation is complete.
| ^(CODEBLOCK .*) //read the code subgraph (nothing gets executed)
;
以下是我用来测试它的语法和代码:
grammar TreeEvaluator;
options {
output = AST;
}
tokens {
CONDBLOCK;
CODEBLOCK;
DEFAULT;
}
compilationUnit : condition+ EOF;
condition : cif elif* celse? -> ^(IF cif elif* celse?);
cif : IF expr block -> ^(CONDBLOCK expr block);
elif : ELIF expr block -> ^(CONDBLOCK expr block);
celse : ELSE block -> ^(DEFAULT block);
expr : ID EQ^ ID;
block : LCUR ID RCUR -> ^(CODEBLOCK ID);
IF : 'if';
ELIF: 'elif';
ELSE: 'else';
LCUR: '{';
RCUR: '}';
EQ : '==';
ID : ('a'..'z'|'A'..'Z')+;
WS : (' '|'\t'|'\f'|'\r'|'\n')+ {skip();};
tree grammar AstTreeEvaluatorParser;
options {
output = AST;
tokenVocab = TreeEvaluator;
ASTLabelType = CommonTree;
}
@members {
private static final class Evaluation {
boolean matched = false;
boolean done = false;
}
private java.util.HashMap<String, Integer> vars = new java.util.HashMap<String, Integer>();
public void addVar(String name, int value){
vars.put(name, value);
}
}
compilationUnit : ifStat+;
ifStat
@init { Evaluation eval = new Evaluation(); }
: ^(IF condition[eval]* defcond[eval]?)
;
condition [Evaluation eval]
: ^(CONDBLOCK exp {if ($exp.value) eval.matched = true;} evalblock[eval])
;
defcond [Evaluation eval]
: ^(DEFAULT {eval.matched = true;} evalblock[eval]) //force a match
;
evalblock [Evaluation eval]
: {eval.matched && !eval.done}? //Only do this when a condition is matched but not finished
block //call the execution code
{eval.done = true;} //evaluation is complete.
| ^(CODEBLOCK .*) //read the code node and continue without executing
;
block : ^(CODEBLOCK ID) {System.out.println("Executed " + $ID.getText());};
exp returns [boolean value]
: ^(EQ lhs=ID rhs=ID)
{$value = vars.get($lhs.getText()) == vars.get($rhs.getText());}
;
public class TreeEvaluatorTest {
public static void main(String[] args) throws Exception {
CharStream input = new ANTLRStringStream("if a == b {b} elif a == c {c} elif a == d {d} else {e}");
TreeEvaluatorLexer lexer = new TreeEvaluatorLexer(input);
CommonTokenStream tokens = new CommonTokenStream(lexer);
TreeEvaluatorParser parser = new TreeEvaluatorParser(tokens);
TreeEvaluatorParser.compilationUnit_return result = parser.compilationUnit();
if (lexer.getNumberOfSyntaxErrors() > 0 || parser.getNumberOfSyntaxErrors() > 0){
throw new Exception("Syntax Errors encountered!");
}
AstTreeEvaluatorParser tparser = new AstTreeEvaluatorParser(new CommonTreeNodeStream(result.getTree()));
tparser.addVar("a", 0);
tparser.addVar("b", 2);
tparser.addVar("c", 3);
tparser.addVar("d", 4);
AstTreeEvaluatorParser.compilationUnit_return tresult = tparser.compilationUnit();
}
}
测试代码评估if a == b {b} elif a == c {c} elif a == d {d} else {e}
。如果评估,则打印{}
之间的ID。因此,如果a == b
为真,则会打印"Executed b"
。
通过调用tparser.addVar(...)
来分配变量值。在这种情况下,a
不等于任何其他变量,因此会对{e}
块进行评估。