最新消息:20210816 当前crifan.com域名已被污染,为防止失联,请关注(页面右下角的)公众号

【记录】用antlr预处理异常MismatchedTokenException时能输出更详细的信息

ANTLR crifan 4020浏览 0评论

【背景】

折腾:

【记录】用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

_removedUpsuppoted.ddl line 2100:45 [mTokens, mIDENTIFIER]  mismatch token; token=null (expected=41

如图:

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 2100:45 [mTokens, mIDENTIFIER]  mismatch token; token=null (expected=41)

对应的,之前错误信息是:

_removedUpsuppoted.ddl line 2096:54 mismatched character ‘ ‘ expecting ‘)’

_removedUpsuppoted.ddl line 2100:45 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   
preprocessLexer(Lexer).match(int) line: 208   
preprocessLexer.mIDENTIFIER() line: 1135   
preprocessLexer.mTokens() line: 2410   
preprocessLexer(Lexer).nextToken() line: 85   
preprocessLexer.nextToken() line: 79   
antlrPreprecessTest.preprcessTest() line: 61   
antlrPreprecessTest.main(String[]) line: 33   

所以去对应的代码中看看具体是怎么出错的。

找到是:

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多重选择:

cause id check is multiple choice

不过后来发现,ID多重匹配,是之前就有的问题。

暂时可忽略。

但是对于新代码,导致了EXPR本身的多重匹配:

expr multiple desision

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
        )?
    ;

然后对应的语法含义表达图示为:

expr simple not contain comma

然后去看看效果:

结果错误依旧。

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时能输出更详细的信息

发表我的评论
取消评论

表情

Hi,您需要填写昵称和邮箱!

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址
82 queries in 0.228 seconds, using 22.12MB memory