在匹配特定类型时解决 ANTLR 歧义
Resolving ANTLR ambiguity while matching specific Types
我开始探索 ANTLR 并尝试匹配此格式:(test123 A0020)
其中:
- test123 是最多 10 个字符(字母和数字)的标识符
- A : 时间指示符(表示 Am 或 Pm ),一个字母可以是 "A" 或 "P"
- 0020 : 表示时间的 4 位数字格式。
我试过这个语法:
IDENTIFIER
:
( LETTER | DIGIT ) +
;
INT
:
DIGIT+
;
fragment
DIGIT
:
[0-9]
;
fragment
LETTER
:
[A-Z]
;
WS : [ \t\r\n(\s)+]+ -> channel(HIDDEN) ;
formatter: '(' information ')';
information :
information '/' 'A' INT
|IDENTIFIER ;
如何解决歧义并使时间格式匹配为 'A' INT 而不是 IDENTIFIER?
另外,如何将令牌长度等检查添加到标识符中?
我知道这在 ANTLR 中不起作用: IDENTIFIER : (DIGIT | LETTER ) {2,10}
更新:
我更改了规则以进行语义检查,但标识符和时间格式之间仍然存在同样的歧义。这是修改后的规则:
formatter
: information
| information '-' time
;
time :
timeMode timeCode;
timeMode:
{ getCurrentToken().getText().matches("[A,C]")}? MOD
;
timeCode: {getCurrentToken().getText().matches("[0-9]{4}")}? INT;
information: {getCurrentToken().getText().length() <= 10 }? IDENTIFIER;
MOD: 'A' | 'C';
所以问题在生产树中得到了说明,A0023 与 timeMode 匹配,解析器抱怨缺少 timeCode
使用语义谓词(检查此 amazing QA),您可以为您的特定模型定义解析器规则,并进行逻辑检查以确保信息可以被解析。请注意,这只是解析器规则的一个选项,而不是词法分析器规则。
information
: information '/' meridien time
| text
;
meridien
: am
| pm
;
am: {input.LT(1).getText() == "A"}? IDENTIFIER;
pm: {input.LT(1).getText() == "P"}? IDENTIFIER;
time: {input.LT(1).getText().length == 4}? INT;
text: {input.LT(1).getText().length <= 10}? IDENTIFIER;
compileUnit
: alfaNum time
;
alfaNum : (ALFA | MOD | NUM)+;
time : MOD NUM+;
MOD: 'A' | 'P';
ALFA: [a-zA-Z];
NUM: [0-9];
WS
: ' ' -> channel(HIDDEN)
;
您需要将 MOD 包含在 alfaNum 规则中以避免歧义。
这里有一个处理方法:
grammar Test;
@lexer::members {
private boolean isAhead(int maxAmountOfCharacters, String pattern) {
final Interval ahead = new Interval(this._tokenStartCharIndex, this._tokenStartCharIndex + maxAmountOfCharacters - 1);
return this._input.getText(ahead).matches(pattern);
}
}
parse
: formatter EOF
;
formatter
: information ( '-' time )?
;
time
: timeMode timeCode
;
timeMode
: TIME_MODE
;
timeCode
: {getCurrentToken().getType() == IDENTIFIER_OR_INTEGER && getCurrentToken().getText().matches("\d{4}")}?
IDENTIFIER_OR_INTEGER
;
information
: {getCurrentToken().getType() == IDENTIFIER_OR_INTEGER && getCurrentToken().getText().matches("\w*[a-zA-Z]\w*")}?
IDENTIFIER_OR_INTEGER
;
IDENTIFIER_OR_INTEGER
: {!isAhead(6, "[AP]\d{4}(\D|$)")}? [a-zA-Z0-9]+
;
TIME_MODE
: [AP]
;
SPACES
: [ \t\r\n] -> skip
;
小测试class:
public class Main {
private static void indent(String lispTree) {
int indentation = -1;
for (final char c : lispTree.toCharArray()) {
if (c == '(') {
indentation++;
for (int i = 0; i < indentation; i++) {
System.out.print(i == 0 ? "\n " : " ");
}
}
else if (c == ')') {
indentation--;
}
System.out.print(c);
}
}
public static void main(String[] args) throws Exception {
TestLexer lexer = new TestLexer(new ANTLRInputStream("1P23 - A0023"));
TestParser parser = new TestParser(new CommonTokenStream(lexer));
indent(parser.parse().toStringTree(parser));
}
}
将打印:
(parse
(formatter
(information 1P23) -
(time
(timeMode A)
(timeCode 0023))) <EOF>)
输入"1P23 - A0023"
.
编辑
ANTLR 也可以在 UI 组件上输出解析树。如果您这样做:
public class Main {
public static void main(String[] args) throws Exception {
TestLexer lexer = new TestLexer(new ANTLRInputStream("1P23 - A0023"));
TestParser parser = new TestParser(new CommonTokenStream(lexer));
new TreeViewer(Arrays.asList(TestParser.ruleNames), parser.parse()).open();
}
}
将出现以下对话框:
使用 ANTLR 版本 4.5.2-1 测试
我开始探索 ANTLR 并尝试匹配此格式:(test123 A0020)
其中:
- test123 是最多 10 个字符(字母和数字)的标识符
- A : 时间指示符(表示 Am 或 Pm ),一个字母可以是 "A" 或 "P"
- 0020 : 表示时间的 4 位数字格式。
我试过这个语法:
IDENTIFIER
:
( LETTER | DIGIT ) +
;
INT
:
DIGIT+
;
fragment
DIGIT
:
[0-9]
;
fragment
LETTER
:
[A-Z]
;
WS : [ \t\r\n(\s)+]+ -> channel(HIDDEN) ;
formatter: '(' information ')';
information :
information '/' 'A' INT
|IDENTIFIER ;
如何解决歧义并使时间格式匹配为 'A' INT 而不是 IDENTIFIER? 另外,如何将令牌长度等检查添加到标识符中? 我知道这在 ANTLR 中不起作用: IDENTIFIER : (DIGIT | LETTER ) {2,10}
更新:
我更改了规则以进行语义检查,但标识符和时间格式之间仍然存在同样的歧义。这是修改后的规则:
formatter
: information
| information '-' time
;
time :
timeMode timeCode;
timeMode:
{ getCurrentToken().getText().matches("[A,C]")}? MOD
;
timeCode: {getCurrentToken().getText().matches("[0-9]{4}")}? INT;
information: {getCurrentToken().getText().length() <= 10 }? IDENTIFIER;
MOD: 'A' | 'C';
所以问题在生产树中得到了说明,A0023 与 timeMode 匹配,解析器抱怨缺少 timeCode
使用语义谓词(检查此 amazing QA),您可以为您的特定模型定义解析器规则,并进行逻辑检查以确保信息可以被解析。请注意,这只是解析器规则的一个选项,而不是词法分析器规则。
information
: information '/' meridien time
| text
;
meridien
: am
| pm
;
am: {input.LT(1).getText() == "A"}? IDENTIFIER;
pm: {input.LT(1).getText() == "P"}? IDENTIFIER;
time: {input.LT(1).getText().length == 4}? INT;
text: {input.LT(1).getText().length <= 10}? IDENTIFIER;
compileUnit
: alfaNum time
;
alfaNum : (ALFA | MOD | NUM)+;
time : MOD NUM+;
MOD: 'A' | 'P';
ALFA: [a-zA-Z];
NUM: [0-9];
WS
: ' ' -> channel(HIDDEN)
;
您需要将 MOD 包含在 alfaNum 规则中以避免歧义。
这里有一个处理方法:
grammar Test;
@lexer::members {
private boolean isAhead(int maxAmountOfCharacters, String pattern) {
final Interval ahead = new Interval(this._tokenStartCharIndex, this._tokenStartCharIndex + maxAmountOfCharacters - 1);
return this._input.getText(ahead).matches(pattern);
}
}
parse
: formatter EOF
;
formatter
: information ( '-' time )?
;
time
: timeMode timeCode
;
timeMode
: TIME_MODE
;
timeCode
: {getCurrentToken().getType() == IDENTIFIER_OR_INTEGER && getCurrentToken().getText().matches("\d{4}")}?
IDENTIFIER_OR_INTEGER
;
information
: {getCurrentToken().getType() == IDENTIFIER_OR_INTEGER && getCurrentToken().getText().matches("\w*[a-zA-Z]\w*")}?
IDENTIFIER_OR_INTEGER
;
IDENTIFIER_OR_INTEGER
: {!isAhead(6, "[AP]\d{4}(\D|$)")}? [a-zA-Z0-9]+
;
TIME_MODE
: [AP]
;
SPACES
: [ \t\r\n] -> skip
;
小测试class:
public class Main {
private static void indent(String lispTree) {
int indentation = -1;
for (final char c : lispTree.toCharArray()) {
if (c == '(') {
indentation++;
for (int i = 0; i < indentation; i++) {
System.out.print(i == 0 ? "\n " : " ");
}
}
else if (c == ')') {
indentation--;
}
System.out.print(c);
}
}
public static void main(String[] args) throws Exception {
TestLexer lexer = new TestLexer(new ANTLRInputStream("1P23 - A0023"));
TestParser parser = new TestParser(new CommonTokenStream(lexer));
indent(parser.parse().toStringTree(parser));
}
}
将打印:
(parse
(formatter
(information 1P23) -
(time
(timeMode A)
(timeCode 0023))) <EOF>)
输入"1P23 - A0023"
.
编辑
ANTLR 也可以在 UI 组件上输出解析树。如果您这样做:
public class Main {
public static void main(String[] args) throws Exception {
TestLexer lexer = new TestLexer(new ANTLRInputStream("1P23 - A0023"));
TestParser parser = new TestParser(new CommonTokenStream(lexer));
new TreeViewer(Arrays.asList(TestParser.ruleNames), parser.parse()).open();
}
}
将出现以下对话框:
使用 ANTLR 版本 4.5.2-1 测试