在匹配特定类型时解决ANTLR歧义

时间:2016-03-09 12:04:19

标签: parsing antlr antlr4 lexer ambiguous

我正在开始探索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 enter image description here

3 个答案:

答案 0 :(得分:1)

以下是处理它的方法:

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
 ;

小型测试课程:

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();
    }
}

将出现以下对话框:

enter image description here

使用ANTLR版本4.5.2-1进行测试

答案 1 :(得分:0)

使用语义谓词(检查此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;

答案 2 :(得分:0)

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规则中来避免歧义。