【背景】
折腾:
【记录】用antlr的preprocess去预处理一个新的eddl文件去除eddl中不支持的元素对应的文件内容
期间,已经添加了,当出错就退出:
【已解决】在用antlr预处理一个新的hart的eddl文件时希望第一次出错就退出
但是退出时,错误信息很少。
所以希望可以加上,当出错时,对于此处的MismatchedTokenException时,可以输出更详细的信息。
【折腾过程】
1. 去参考:
【记录】折腾antlr的异常处理:使得当初错时,输出更详细的错误信息,包含堆栈信息
去添加grammar代码。
期间,参考:
http://antlr3.org/api/Java/org/antlr/runtime/MismatchedTokenException.html
和:
http://antlr3.org/api/Java/org/antlr/runtime/NoViableAltException.html
然后在:
@lexer::members { |
中添加了:
//override default error handling with more rich error messages public String getErrorMessage(RecognitionException e, String[] tokenNames) { List stack = getRuleInvocationStack(e, this.getClass().getName()); String msg = null; if ( e instanceof NoViableAltException ) { NoViableAltException nvae = (NoViableAltException)e; msg = " no viable alt; token="+e.token+ " (decision="+nvae.decisionNumber+ " state "+nvae.stateNumber+")"+ " decision=<<"+nvae.grammarDecisionDescription+">>"; } else if ( e instanceof MismatchedTokenException ) { MismatchedTokenException mte = (MismatchedTokenException)e; msg = " mismatch token; token="+e.token+ " (expected="+mte.expecting; } else { msg = super.getErrorMessage(e, tokenNames); } return stack+" "+msg; } public String getTokenErrorDisplay(Token t) { return t.toString(); }
然后效果是:
然后运行期间,出现MismatchedTokenException时,是可以显示出更多详细的信息的:
_removedUpsuppoted.ddl line 2096:54 [mTokens, mIDENTIFIER] mismatch token; token=null (expected=41 |
如图:
2.对应的,最后少了个括号,所以lexer中代码去改为:
else if ( e instanceof MismatchedTokenException ) { MismatchedTokenException mte = (MismatchedTokenException)e; msg = " mismatch token; token="+e.token+ " (expected="+mte.expecting + ")"; }
最后输出是:
_removedUpsuppoted.ddl line 2096:54 [mTokens, mIDENTIFIER] mismatch token; token=null (expected=41) |
对应的,之前错误信息是:
_removedUpsuppoted.ddl line 2096:54 mismatched character ‘ ‘ expecting ‘)’ |
3.对应上述详细错误信息中的:
[mTokens, mIDENTIFIER] |
算是错误的stack了。
然后去找到:
preprocessLexer.java
中的:
@Override public void mTokens() throws RecognitionException { // D:\\DevRoot\\IndustrialMobileAutomation\\HandheldDataSetter\\ANTLR\\projects\\v1.5\\HartEddlParser_local_TFS\\preprocess\\remove_comment\\preprocess.g:1:8: ( COMMENT | INCLUDE | DIRECTIVE | IDENTIFIER | NUMBER | LEFT | RIGHT | COMMA | POINT | OPERATOR | FLOAT | WS | STRING | CHAR ) int alt45=14; alt45 = dfa45.predict(input); switch (alt45) { case 1 : // D:\\DevRoot\\IndustrialMobileAutomation\\HandheldDataSetter\\ANTLR\\projects\\v1.5\\HartEddlParser_local_TFS\\preprocess\\remove_comment\\preprocess.g:1:10: COMMENT { mCOMMENT(); } break; case 2 : // D:\\DevRoot\\IndustrialMobileAutomation\\HandheldDataSetter\\ANTLR\\projects\\v1.5\\HartEddlParser_local_TFS\\preprocess\\remove_comment\\preprocess.g:1:18: INCLUDE { mINCLUDE(); } break; case 3 : // D:\\DevRoot\\IndustrialMobileAutomation\\HandheldDataSetter\\ANTLR\\projects\\v1.5\\HartEddlParser_local_TFS\\preprocess\\remove_comment\\preprocess.g:1:26: DIRECTIVE { mDIRECTIVE(); } break; case 4 : // D:\\DevRoot\\IndustrialMobileAutomation\\HandheldDataSetter\\ANTLR\\projects\\v1.5\\HartEddlParser_local_TFS\\preprocess\\remove_comment\\preprocess.g:1:36: IDENTIFIER { mIDENTIFIER(); } break; ...... }
4.再去找mIDENTIFIER :
其代码很长。
去看看到底是哪部分出错的,是哪部分导致和抛出此处的MismatchedTokenException
结果代码中,没有MismatchedTokenException相关的内容。
那估计是代码某处,调用别的token,导致此处的mismatch的。
5.经过查看代码发现,Lexer.java中,太多的:
else { MismatchedSetException mse = new MismatchedSetException(null,input); recover(mse); throw mse; }
所以即使是这些token中的某个出现mismatch,也无法快速找到哪个出错。
6.突然想到了:
都调用了:
recover(mse);
所以去在recover中打断点,结果就一下子,找到了出错时候的stack调用:
preprocessLexer(Lexer).recover(RecognitionException) line: 343 |
所以去对应的代码中看看具体是怎么出错的。
找到是:
mIDENTIFIER()
中的:
if ( !(( foundArgs.size()==define.size()-1 )) ) { throw new FailedPredicateException(input, "IDENTIFIER", " foundArgs.size()==define.size()-1 "); } match(')'); } break;
中的:
match(‘)’);
出现的异常。
7.然后就可以去分析,和修改源码,找找错误背后的原因,和如何修改了。
经过一番调试和分析,暂时找到错误的原因了:
之前的,借用别人写的EXPR的语法,有点问题,导致:
对于:
assign_float(PV.UPPER_RANGE_VALUE, new_lrv + pv_urv); |
无法识别第二个参数:
new_lrv + pv_urv |
而错误的识别为:
new_lrv |
因为:
参数中的是表达式:A+B的形式,且A和加号+之间,有空格,导致无法识别到A+B为一个整体,作为函数调用的第二个参数。
然后改为:
assign_float(PV.UPPER_RANGE_VALUE, new_lrv+pv_urv); |
即:
new_lrv+pv_urv |
然后EXPR就可以识别了。。。
8.然后所要处理的代码,最后改为:
if ((pv_range < -199.96) || (pv_range > 199.96)) { new_lrv = pv_val - ((pv_urv - pv_lrv) * set_val / 100.0); assign_float(PV.LOWER_RANGE_VALUE, new_lrv); //assign_float(PV.UPPER_RANGE_VALUE, new_lrv + (pv_urv - pv_lrv)); //assign_float(PV.UPPER_RANGE_VALUE, new_lrv + pv_urv - pv_lrv); //assign_float(PV.UPPER_RANGE_VALUE, new_lrv + pv_urv); assign_float(PV.UPPER_RANGE_VALUE, new_lrv+pv_urv); } else { new_lrv = (pv_range - set_val) * (pv_urv - pv_lrv) / 100.0 + pv_lrv; assign_float(PV.LOWER_RANGE_VALUE, new_lrv); //assign_float(PV.UPPER_RANGE_VALUE, new_lrv + (pv_urv - pv_lrv)); //assign_float(PV.UPPER_RANGE_VALUE, new_lrv+(pv_urv - pv_lrv)); assign_float(PV.UPPER_RANGE_VALUE, new_lrv+(pv_urv-pv_lrv)); }
然后就没有报错了,EXPR就可以识别:
new_lrv+pv_urv |
和:
new_lrv+(pv_urv-pv_lrv) |
了。
剩下的,就是去如何修改那个EXPR,去支持:
A,可能的多个空格(甚至TAB),加号’+’(或其他操作符),可能的多个空格(甚至TAB),B
之类的形式了。
9.把EXPR从:
fragment EXPR // allow just about anything without being ambiguous : (WS)? (NUMBER|IDENTIFIER)? ( ( LEFT EXPR ( COMMA EXPR )* RIGHT | STRING | OPERATOR // quotes, COMMA, LEFT, and RIGHT not in here ) EXPR )? ;
改为:
fragment EXPR // allow just about anything without being ambiguous : (WS)? (NUMBER|IDENTIFIER)? ( ( LEFT EXPR ( COMMA EXPR )* RIGHT | STRING | (WS* OPERATOR WS*) // quotes, COMMA, LEFT, and RIGHT not in here; but also match adjacent whitespace ) EXPR )? ;
看看效果如何:
结果导致ID多重选择:
不过后来发现,ID多重匹配,是之前就有的问题。
暂时可忽略。
但是对于新代码,导致了EXPR本身的多重匹配:
10.然后用:
fragment EXPR // allow just about anything without being ambiguous : WS* (NUMBER|IDENTIFIER)? //also match adjacent whitespace ( ( LEFT EXPR ( COMMA EXPR )* RIGHT | STRING | OPERATOR // quotes, COMMA, LEFT, and RIGHT not in here; ) EXPR )? ;
倒是可以编译通过,EXPR无多重匹配。
然后去试试效果。
结果和上面一样,导致生成的preprocessLexer.java,无法编译,出现define变量找不到等异常问题。
11.去试试,把WS本来在EXPR后面的:
/* fragment EXPR // allow just about anything without being ambiguous : (WS)? (NUMBER|IDENTIFIER)? ( ( LEFT EXPR ( COMMA EXPR )* RIGHT | STRING | OPERATOR // quotes, COMMA, LEFT, and RIGHT not in here ) EXPR )? ; */ fragment EXPR // allow just about anything without being ambiguous : (WS)? (NUMBER|IDENTIFIER)? //also match adjacent whitespace ( ( LEFT EXPR ( COMMA EXPR )* RIGHT | STRING | OPERATOR // quotes, COMMA, LEFT, and RIGHT not in here; ) EXPR )? ; /* eXPR // allow just about anything without being ambiguous : (WS)? (NUMBER|iDENTIFIER)? ( ( LEFT eXPR ( COMMA eXPR )* RIGHT | STRING | OPERATOR // quotes, COMMA, LEFT, and RIGHT not in here ) eXPR )? ; */ //INT : '0'..'9'+ ; FLOAT : ('0'..'9')+ '.' ('0'..'9')* EXPONENT? | '.' ('0'..'9')+ EXPONENT? | ('0'..'9')+ EXPONENT ; WS : ( ' ' | '\t' | '\r' | '\n' ) {$channel=HIDDEN;} ;
移到此处的EXPR之前:
WS : ( ' ' | '\t' | '\r' | '\n' ) {$channel=HIDDEN;} ; /* fragment EXPR // allow just about anything without being ambiguous : (WS)? (NUMBER|IDENTIFIER)? ( ( LEFT EXPR ( COMMA EXPR )* RIGHT | STRING | OPERATOR // quotes, COMMA, LEFT, and RIGHT not in here ) EXPR )? ; */ fragment EXPR // allow just about anything without being ambiguous : (WS)? (NUMBER|IDENTIFIER)? //also match adjacent whitespace ( ( LEFT EXPR ( COMMA EXPR )* RIGHT | STRING | OPERATOR // quotes, COMMA, LEFT, and RIGHT not in here; ) EXPR )? ; /* eXPR // allow just about anything without being ambiguous : (WS)? (NUMBER|iDENTIFIER)? ( ( LEFT eXPR ( COMMA eXPR )* RIGHT | STRING | OPERATOR // quotes, COMMA, LEFT, and RIGHT not in here ) eXPR )? ; */ //INT : '0'..'9'+ ; FLOAT : ('0'..'9')+ '.' ('0'..'9')* EXPONENT? | '.' ('0'..'9')+ EXPONENT? | ('0'..'9')+ EXPONENT ;
看看能否实现:
WS自动被忽略且被忽略掉。
-> 就不用自己再加WS去匹配WS了。
结果去调试,错误依旧,还是会出错。
12.把WS移动到IDENTIFIER之前,看看是否可以达到上面的效果:
WS被匹配到,然后自动忽略
后续(IDENTIFIER中的EXPR中)就无需判断和考虑WS了。
结果错误依旧。
13.然后看了看当前的preprocess.g中,用到EXPR的地方,也就只有IDENTIFIER中去判断表达式的地方,其他暂时没人调用
然后对于EXPR的写法:
fragment EXPR // allow just about anything without being ambiguous : (WS)? (NUMBER|IDENTIFIER)? //also match adjacent whitespace ( ( LEFT EXPR ( COMMA EXPR )* RIGHT | STRING | OPERATOR // quotes, COMMA, LEFT, and RIGHT not in here; ) EXPR )? ;
很明显是递归的写法,而且是包含了逗号的表达式,所以对于:
IDENTIFIER中函数调用的参数时
用EXPR去匹配,得到内容给callArg0和callArg1
结果:
按理来说,都无需考虑对应的逗号COMMA的:
因为本身callArg0和callArg1之前,就已经写出了逗号去匹配的:
( COMMA WS* callArg1=EXPR
所以:
EXPR中,本身就不应该包含逗号COMMA的
所以去改EXPR为:
fragment EXPR // allow just about anything without being ambiguous : (WS)? (NUMBER|IDENTIFIER)? ( ( LEFT EXPR RIGHT | STRING | OPERATOR // quotes, COMMA, LEFT, and RIGHT not in here; ) EXPR )? ;
然后对应的语法含义表达图示为:
然后去看看效果:
结果错误依旧。
14.再去改EXPR为:
fragment EXPR // allow just about anything without being ambiguous : WS* (NUMBER|IDENTIFIER)? ( ( LEFT EXPR RIGHT | STRING | OPERATOR // quotes, COMMA, LEFT, and RIGHT not in here; ) EXPR )? ;
结果:
lexer无法编译。
15.改为:
fragment EXPR // allow just about anything without being ambiguous : (NUMBER|IDENTIFIER)? ( ( LEFT EXPR RIGHT | STRING | OPERATOR // quotes, COMMA, LEFT, and RIGHT not in here; ) EXPR )? ;
结果:
错误依旧。
15.把WS放到DIRECTIVE之前,看看效果:
错误依旧。
16.改为:
fragment EXPR // allow just about anything without being ambiguous : (NUMBER|IDENTIFIER|STRING) (OPERATOR ( NUMBER|IDENTIFIER |STRING))? | (LEFT EXPR RIGHT) ;
结果:
错误依旧。
17.有点注意到了,对于:
new_lrv + (pv_urv - pv_lrv)
其EXPR匹配时,
感觉是:
优先匹配到:
new_lrv
就不往下匹配了。
所以此处无论怎么改,都会出错。
18.所以去改为:
fragment EXPR // allow just about anything without being ambiguous : (LEFT EXPR RIGHT) | (NUMBER|IDENTIFIER|STRING) (OPERATOR ( NUMBER|IDENTIFIER |STRING))? ;
结果:
错误依旧。
19.改为:
fragment EXPR // allow just about anything without being ambiguous : ( (NUMBER|IDENTIFIER|STRING) (OPERATOR (NUMBER|IDENTIFIER|STRING))? ) | (LEFT EXPR RIGHT) ;
结果:
问题依旧。
20.后来测试了其他几十次改动,还是没最终解决问题。。。
待续。。。
【总结】
当ANTRL语法解析,比如Lexer期间,出错时:
可以通过添加代码,使得可以打印出错误期间的详细信息(此处是:mTokens -> mIDENTIFIER)
其中包含了堆栈调用
而得知是那个token(此处是mIDENTIFIER())出错的
然后再去找到错误的背后所需要执行的代码
然后打断点,再去调试,即可找到出错的具体的代码的位置。
剩下的,就是根据错误现象和代码,找原因,并解决具体问题了。
转载请注明:在路上 » 【记录】用antlr预处理异常MismatchedTokenException时能输出更详细的信息