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

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

ANTLR crifan 4182浏览 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 {

中添加了:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
//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中代码去改为:

1
2
3
4
5
6
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

中的:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
@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中,太多的:

1
2
3
4
5
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()

中的:

1
2
3
4
5
6
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.然后所要处理的代码,最后改为:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
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从:

1
2
3
4
5
6
7
8
9
10
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
        )?
    ;

改为:

1
2
3
4
5
6
7
8
9
10
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.然后用:

1
2
3
4
5
6
7
8
9
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后面的:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
/*
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之前:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
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的写法:

1
2
3
4
5
6
7
8
9
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
        )?
    ;

很明显是递归的写法,而且是包含了逗号的表达式,所以对于:

IDENTIFIER中函数调用的参数时

用EXPR去匹配,得到内容给callArg0和callArg1

结果:

按理来说,都无需考虑对应的逗号COMMA的:

因为本身callArg0和callArg1之前,就已经写出了逗号去匹配的:

1
( COMMA WS* callArg1=EXPR

所以:

EXPR中,本身就不应该包含逗号COMMA的

所以去改EXPR为:

1
2
3
4
5
6
7
8
9
10
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为:

1
2
3
4
5
6
7
8
9
10
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.改为:

1
2
3
4
5
6
7
8
9
10
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.改为:

1
2
3
4
5
6
fragment EXPR // allow just about anything without being ambiguous
    :
    (NUMBER|IDENTIFIER|STRING) (OPERATOR ( NUMBER|IDENTIFIER |STRING))?
    |
    (LEFT EXPR RIGHT)
    ;

结果:

错误依旧。

17.有点注意到了,对于:

1
new_lrv + (pv_urv - pv_lrv)

其EXPR匹配时,

感觉是:

优先匹配到:

new_lrv

就不往下匹配了。

所以此处无论怎么改,都会出错。

18.所以去改为:

1
2
3
4
5
6
fragment EXPR // allow just about anything without being ambiguous
    :
    (LEFT EXPR RIGHT)
    |
    (NUMBER|IDENTIFIER|STRING) (OPERATOR ( NUMBER|IDENTIFIER |STRING))?  
    ;

结果:

错误依旧。

19.改为:

1
2
3
4
5
6
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.200 seconds, using 22.20MB memory