antlr4总结
算符优先级问题
同一条规则内,运算符分支在前的,优先级越高,比如:
expr : expr op=('+'|'-') expr #AddSub | expr op=('*'|'/') expr #MulDiv会认为加减运算符优先于乘除运算符,这样在计算1+2*3时,就会得到错误的结果9。正确的写法是乘除分支在前:
expr : expr op=('*'|'/') expr #MulDiv | expr op=('+'|'-') expr #AddSub算符右结合问题
antlr默认是算符左结合,像幂运算这样的算符一般要求右结合,可以这么写:
expr : <assoc=right> expr '**' expr #power | expr op=('*'|'/'|'%') expr #MulDiv | expr op=('+'|'-') expr #AddSub | '(' expr ')' #Parens注意:<assoc=right>要写在分支的最左边,写在其它位置会有warning。
多分支下的标签不可省略
多分支规则里,要么所有分支给标签(否则无法为每个分支自动生成visit函数),要么所有分支都不给标签。
上例中,#AddSub就是一个分支标签,这条规则里的每个分支都要有一个标签,这样antlr会自动为每个分支生成一个visit函数,方便使用者来处理。比如:
@OverridepublicTvisitMulDiv(LumenParser.MulDivContextctx)...@OverridepublicTvisitAddSub(LumenParser.AddSubContextctx)单分支规则自然无需手写标签,antlr会自动生成visit函数。
字面量
字面量可以是一个字符,也可以是字符串。必须用单引号括起来。下面是例子:
STRING : '"' (~["\r\n])* '"' ; BOOL : 'true' | 'false' ;上例中,true和false都是字符串字面量。
错误处理
两种方式,一个是定制自己的BaseErrorListener:
publicclassSyntaxErrorListenerextendsBaseErrorListener{privateSourcesource;publicSyntaxErrorListener(Sourcesource){this.source=source;}@OverridepublicvoidsyntaxError(Recognizer<?,?>recognizer,ObjectoffendingSymbol,intline,intcharPositionInLine,Stringmsg,RecognitionExceptione){// line 和 charPositionInLine 就是错误的行列号StringsourceName=source.getName();if(!sourceName.isEmpty()){sourceName=String.format("%s:%d:%d: ",sourceName,line,charPositionInLine);}System.err.println(sourceName+msg);}}然后设置这个Listener:
parser.addErrorListener(newSyntaxErrorListener(request.getSource()));parser就是antlr自动为我们生成的语法解析器。
antlr框架会在出现语法错误时调用BaseErrorListener,然后恢复错误,继续解析。
若有运行时错误,可在每个grammar node里塞入行列号信息,从antlr的ParserRuleContext里即可获得,比如:
publicNodesupplySourceSection(Sourcesource,ParserRuleContextctx){Tokenstart=ctx.getStart();Tokenstop=ctx.getStop();// 可能为 null(EOF)intstartLine=start.getLine();intstartCol=start.getCharPositionInLine()+1;intendLine=(stop!=null?stop.getLine():startLine);intendCol=(stop!=null?stop.getCharPositionInLine()+1:startCol);this.sourceSection=source.createSection(startLine,startCol,endLine,endCol);returnthis;}我这里使用的是graal vm的truffle框架,所以把行列号信息设置为每个语法节点的SourceSection。这样,当执行到特定节点出错时,就可以报出详细的行列号信息。