Parsing 具有歧义替代项的ANTLR语法

Parsing 具有歧义替代项的ANTLR语法,parsing,antlr,grammar,Parsing,Antlr,Grammar,我的语法产生了意想不到的结果。我不确定这是我的错误还是ANTLR处理逻辑不明确的问题 这是我的语法: grammar PPMacro; options { language=Java; backtrack=true; } file: (inputLines)+ EOF; inputLines : ( preprocessorLineSet | oneNormalInputLine ) ; oneNormalInputLine @after{System.ou

我的语法产生了意想不到的结果。我不确定这是我的错误还是ANTLR处理逻辑不明确的问题

这是我的语法:

    grammar PPMacro;
options {
  language=Java;
  backtrack=true;

}

file: (inputLines)+ EOF;

inputLines 
:  ( preprocessorLineSet  |  oneNormalInputLine )  ; 

oneNormalInputLine  @after{System.out.print("["+$text+"]");}  
: (any_token_except_crlf)* CRLF ;

preprocessorLineSet 
: ifPart endifLine;

ifPart: ifLine  inputLines*   ;
ifLine  @after{System.out.print("{"+$text+"}" );} 
:  '#' IF (any_token_except_crlf)* CRLF ;

endifLine @after{System.out.print("{"+$text+"}" );} 
:  '#' ENDIF (any_token_except_crlf)* CRLF ;

any_token_except_crlf: (ANY_ID | WS | '#'|IF|ENDIF);
// just matches everything

CRLF: '\r'?  '\n'  ;
WS: (' '|'\t'|'\f' )+;
Hash: '#'  ;
IF     : 'if'    ;
ENDIF  : 'endif' ;
ANY_ID: ( 'a'..'z'|'A'..'Z'|'0'..'9'| '_')+ ;
说明

< P> >解析C++,如果…endif块

我试图识别嵌套的#if#endif块。这是由我的预处理器行集完成的。它包含支持嵌套块的递归定义。oneNormalInputLine用于处理任何不属于#if形式的内容。此规则是“匹配任何内容”规则,实际上与#if行匹配。但我故意把它放在输入行中的预处理行之后。我希望这种排序可以阻止它匹配#if或#endif行。使用catch规则的原因是,我希望一个规则接受任何其他C++语法,并简单地将它们返回到输出。 我检查了一下,我把所有的东西都打印出来。由preprocessorLineSet匹配的行应该被{}包围,而由oneNormalInputLine匹配的行应该被[]包围

样本输入

#if s
s
#if a
s 
s
#endif
#endif
还有这个

#if
abc
#endif
[#if
][abc
][#endif
]
相应的产出:

[#if s
][s
][#if a
][s
][s
][#endif
][#endif
]
还有这个

#if
abc
#endif
[#if
][abc
][#endif
]
问题

包括#if#endif在内的所有输出行都被[]包围,这意味着它们仅由一个normalInputLine匹配!但我没想到会这样。预处理器线性集应该能够匹配#if行。为什么我会得到这个结果

该行包含不明确的备选方案:

inputLines  :  ( preprocessorLineSet  |  oneNormalInputLine );
因为两者都可以匹配#if和#endif。但我希望第一种选择应该使用,而不是后一种。还请注意,回溯处于启用状态

编辑 我的oneNormalInputLine规则接受一切的原因是,很难将没有特定模式的东西表示为#如果模式可能相当复杂:

/***

comments

*/   # /***
comments
*/ if  

这是一个有效的模式。编写一个没有这种模式的规则似乎很困难。

你的方法不是很稳健-我建议你保持简单,使用实际的语言规则,它说以
开头的每一行都是预处理器指令,而不是以
开头的每一行都不是。使用这个规则,语法中不会有歧义,理解起来也会简单得多

为什么你的语法不好用?问题是您的
预处理线性集
规则无法匹配任何内容

preprocessorLineSet 
: ifPart endifLine;

ifPart: ifLine  inputLines*   ;
它以
#if…
开头,然后应该匹配其他行,当第一个匹配的
#endif
出现时,它应该匹配并结束。然而,它实际上并没有做到这一点<代码>输入行可以匹配几乎任何一行(几乎-它不会匹配,例如C++的运算符和其他非标识符),包括所有预处理器指令。这意味着
ifPart
规则将与输入的末尾匹配,并且将不会剩下
endifLine
。请注意,回溯对此没有影响,因为一旦ANTLR与规则匹配(在本例中为
ifPart
,它将在整个剩余输入上成功,因为
*
是贪婪的),它将永远不会回溯到该规则中。ANTLR的回溯规则很复杂


请注意,如果您使
oneNormalLine
与预处理器指令不匹配(例如,它类似于
(nonHash any*|)CRLF
,它将开始工作。

您的
任何除\u CRLF
之外的\u标记都会导致歧义。您需要修复它(并删除
backtrack=true;
)通过让该规则与以下内容匹配:

  • 太空船
  • a
    “#”
    后跟除
    'if'
    'endif'
    和换行符以外的任何字符
  • “#”和换行符外的任何字符,后跟
    'if'
    'endif'
  • 标识符
一个小的工作示例(我对规则的命名有点不同…):

语法;
选择权{
输出=AST;
}
代币{
文件
}
文件
:行+EOF->^(文件行+)
;
线
:if_stat
|法线
;
如果统计
:HASH IF normal\u line*HASH ENDIF->^(IF normal\u line*)
;
法线
:非特殊*CRLF->非特殊*
;
非特殊
:WS
|HASH~(IF | ENDIF | CRLF)
|~(HASH | CRLF)(IF | ENDIF)
|身份证
;
CRLF:'\r'?'\n';
WS:(“|”\t“|”\f')+;
散列:“#”;
如果:‘如果’;
ENDIF:‘ENDIF’;
ID:(‘a’、‘z’|‘a’、‘z’|‘0’、‘9’|’)+;
这可以通过以下类进行测试:

import org.antlr.runtime.*;
导入org.antlr.runtime.tree.*;
导入org.antlr.stringtemplate.*;
公共班机{
公共静态void main(字符串[]args)引发异常{
PPMacroLexer lexer=新的PPMacroLexer(新的ANTLRFileStream(“test.cpp”);
PPMacroParser parser=newPPMacroparser(newCommonTokenStream(lexer));
CommonTree=(CommonTree)parser.file().getTree();
DOTTreeGenerator gen=新的DOTTreeGenerator();
StringTemplate st=gen.toDOT(树);
系统输出打印LN(st);
}
}
以及
test.cpp
文件,该文件可能如下所示:

ab
#如果是
T
#如果
U
v
#恩迪夫
#恩迪夫
C
D
将生成以下AST:

编辑 我刚才看到,您需要考虑
#
if
(和
endif
)之间的多行注释和空格。您可以在lexer中最好地处理这种情况,如下所示:

语法;
选择权{
输出=AST;
}
代币{
文件
ENDIF;
}
文件
:行+EOF->^(文件行+)
;
线
:if_stat
|法线
;
如果统计
:IF normal_line*ENDIF->^(IF normal_line*)
;
法线
:非特殊*CRLF->非特殊*
;
非特殊
:WS
|身份证
;
IF:“#”噪声*(“IF”|“endif”{$type=endif;});
CRLF:'\r'?'\n';
WS:(“|”\t“|”\f')+;
ID:('a'..'z'|'a'..'z'|'0'..'9'|'|')+;
注释:'/*'.'*/'{skip();};
碎片噪声
:  '/*' .* '*/'
|WS
;
片段ENDIF:;
wh