代码之家  ›  专栏  ›  技术社区  ›  Ken Williams Dirk is no longer here

ANTLR语义谓词-不阻止规则

  •  1
  • Ken Williams Dirk is no longer here  · 技术社区  · 6 年前

    我在ANTLR解析器规则的语义谓词方面遇到了一些问题。以下是我的语法,旨在识别两种不同的日期格式:

    grammar sample ;
    
    options { language=Python3; }
    
    @parser::header {
    from datetime import datetime
    }
    
    month_number returns [val] : INTEGER { 1    <= int($INTEGER.text) <= 12   }?  {$val = int($INTEGER.text)} ;
    day_number   returns [val] : INTEGER { 1    <= int($INTEGER.text) <= 31   }?  {$val = int($INTEGER.text)} ;
    year_4digit  returns [val] : INTEGER { 1900 <= int($INTEGER.text) <= 2100 }?  {$val = int($INTEGER.text)} ;
    
    year_2digit  returns [val] : '\''? INTEGER {(int($INTEGER.text) >= 65 or int($INTEGER.text) < 40)}?
                                         {$val = (1900 + int($INTEGER.text)) if (int($INTEGER.text) >= 65) else (2000 + int($INTEGER.text))} ;
    
    year_digits  returns [val]
      : year_4digit {$val = $year_4digit.val}
      | year_2digit {$val = $year_2digit.val}
      ;
    
    
    mdy returns [val]
      : month_number '-' day_number '-' year_digits  {$val = datetime($year_digits.val, $month_number.val, $day_number.val)}
      | month_number '/' day_number '/' year_digits  {$val = datetime($year_digits.val, $month_number.val, $day_number.val)}
      ;
    
    ymd returns [val]
      : year_4digit '-' month_number '-' day_number  {$val = datetime($year_4digit.val, $month_number.val, $day_number.val)}
      | year_4digit '/' month_number '/' day_number  {$val = datetime($year_4digit.val, $month_number.val, $day_number.val)}
      ;
    
    date_as_numbers returns [val]
      : ymd {$val = $ymd.val}
      | mdy {$val = $mdy.val}
      ;
    
    INTEGER: '0'..'9'+ ;
    

    我用以下程序进行测试:

    from myPackage.sampleParser import sampleParser
    from myPackage.sampleLexer import sampleLexer
    
    from antlr4 import CommonTokenStream
    from antlr4 import InputStream
    
    date_input = InputStream("2/12/2017".lower())
    lexer = sampleLexer(date_input)
    stream = CommonTokenStream(lexer)
    parser = sampleParser(stream)
    result = parser.date_as_numbers()
    print(result.val)
    

    这将导致以下错误:

    line 1:1 rule year_4digit failed predicate: { 1900 <= int($INTEGER.text) <= 2100 }?
    line 1:9 rule day_number failed predicate: { 1    <= int($INTEGER.text) <= 31   }?
    Traceback (most recent call last):
      File "/Users/kwilliams/Library/Preferences/IntelliJIdea2017.3/scratches/scratch_1.py", line 11, in <module>
        result = parser.date_as_numbers()
      File "/Users/kwilliams/git/myPackage/sampleParser.py", line 482, in date_as_numbers
        localctx._ymd = self.ymd()
      File "/Users/kwilliams/git/myPackage/sampleParser.py", line 436, in ymd
        localctx.val = datetime(localctx._year_4digit.val, localctx._month_number.val, localctx._day_number.val)
    TypeError: an integer is required (got type NoneType)
    

    所以我认为现在发生的是 year_4digit 引发异常,因为数字 2 不在其范围内,但它返回 第4年数字 不管怎样,这场比赛 val 属性已填充,导致下游错误 NoneType . 对吗?

    如果是,什么是好的解决方案?我需要把语义谓词放在规则的前面吗?我如何对 INTEGER token这是正确的解决方案吗?

    (同时,我希望能够 $INTEGER.int 而不是 int($INTEGER.text) ,但这可能在Python目标中不可用?切向和次要问题。)

    顺便说一句,上面的语法是我真实语法的一个小摘录,我希望有一个解决方案,不需要对这部分进行重大更改,可能会造成连锁反应,可能需要一段时间才能解决。

    谢谢

    1 回复  |  直到 6 年前
        1
  •  1
  •   Bart Kiers    6 年前

    显然,谓词嵌套太深,导致解析器无法回溯并尝试第二种选择:

    date_as_numbers returns [val]
      : ymd {$val = $ymd.val} // alternaitve 1
      | mdy {$val = $mdy.val} // alternaitve 2
      ;
    

    当我交换备选方案时:

    date_as_numbers returns [val]
      : mdy {$val = $mdy.val}
      | ymd {$val = $ymd.val}
      ;
    

    输入 "2/12/2017" 正确分析,但 "2017/12/2" 失败。

    我不知道这是预期的行为,还是一个bug(我还没有对新的v4谓词做过太多)。你可以 raise an issue 关于这个。

    在玩了一会儿之后,我把规则合并成了一个大的 any_date 规则,并让这些规则从谓词开始,而不是让谓词在中间的某个位置(正如您自己已经暗示的):

    grammar sample;
    
    @parser::members {
      boolean lte(Token token, int value) {
        return Integer.parseInt(token.getText()) <= value;
      }
      boolean gte(Token token, int value) {
        return Integer.parseInt(token.getText()) >= value;
      }
    }
    
    date_as_numbers returns [String val]
      : any_date EOF {$val = $any_date.val;}
      ;
    
    any_date returns [String val]
     : {gte(_input.LT(1), 1) && lte(_input.LT(1), 12)}?
       INTEGER '-' day_number '-' year_digits {$val = "y=" + $year_digits.val + ", m=" + $INTEGER.text + ", d=" + $day_number.val;}
     | {gte(_input.LT(1), 1) && lte(_input.LT(1), 12)}?
       INTEGER '/' day_number '/' year_digits {$val = "y=" + $year_digits.val + ", m=" + $INTEGER.text + ", d=" + $day_number.val;}
     | {gte(_input.LT(1), 1900) && lte(_input.LT(1), 2100)}?
       INTEGER '-' month_number '-' day_number {$val = "y=" + $INTEGER.text + ", m=" + $month_number.val + ", d=" + $day_number.val;}
     | {gte(_input.LT(1), 1900) && lte(_input.LT(1), 2100)}?
       INTEGER '/' month_number '/' day_number {$val = "y=" + $INTEGER.text + ", m=" + $month_number.val + ", d=" + $day_number.val;}
     ;
    
    month_number returns [int val]
     : INTEGER {gte($INTEGER, 1) && lte($INTEGER, 12)}?
       {$val = Integer.parseInt($INTEGER.text);}
     ;
    
    day_number returns [int val]
     : INTEGER {gte($INTEGER, 1) && lte($INTEGER, 31)}?
       {$val = Integer.parseInt($INTEGER.text);}
     ;
    
    year_4digit returns [int val]
     : INTEGER {gte($INTEGER, 1900) && lte($INTEGER, 2100)}?
       {$val = Integer.parseInt($INTEGER.text);}
     ;
    
    year_2digit returns [int val]
     : '\''? INTEGER {gte($INTEGER, 65) || lte($INTEGER, 39)}?
       {$val = Integer.parseInt($INTEGER.text) >= 65 ? 1900 + Integer.parseInt($INTEGER.text) : 2000 + Integer.parseInt($INTEGER.text);}
     ;
    
    year_digits  returns [int val]
      : year_4digit {$val = $year_4digit.val;}
      | year_2digit {$val = $year_2digit.val;}
      ;
    
    INTEGER: '0'..'9'+ ;
    

    (抱歉,没有python)

    运行此类时:

    import org.antlr.v4.runtime.*;
    
    public class Main {
    
      public static void main(String[] args) {
    
        String[] tests = { "2/12/2017", "2017/12/31", "1-2-'03" };
    
        for (String test : tests) {
          sampleLexer lexer = new sampleLexer(CharStreams.fromString(test));
          sampleParser parser = new sampleParser(new CommonTokenStream(lexer));
          System.out.println(test + " -> " + parser.date_as_numbers().val);
        }
      }
    }
    

    打印以下内容:

    2/12/2017 -> y=2017, m=2, d=12
    2017/12/31 -> y=2017, m=12, d=31
    1-2-'03 -> y=2003, m=1, d=2
    

    我知道,这并不完美,但也许你可以调整一下你目前的语法,让它发挥作用。

    编辑

    当然,您也可以放弃谓词,改为执行以下操作:

    grammar sample;
    
    date_as_numbers
     : ymd
     | mdy
     | failure
     ;
    
    ymd
     : year '/' month '/' day
     | year '-' month '-' day
     ;
    
    mdy
     : month '/' day '/' year
     | month '-' day '-' year
     ;
    
    year
     : '\''? year_2digits
     | NUM_4DIGITS
     ;
    
    year_2digits
     : NUM_1_12
     | NUM_13_31
     | NUM_2DIGITS
     ;
    
    month
     : NUM_1_12
     ;
    
    day
     : NUM_1_12
     | NUM_13_31
     ;
    
    failure
     : NUM_OTHER
     ;
    
    NUM_1_12
     : [1-9]     // 1..9
     | '1' [0-2] // 10..12
     ;
    
    NUM_13_31
     : '1' [3-9] // 13..19
     | '2' D     // 20..29
     | '3' [01]  // 30..31
     ;
    
    NUM_2DIGITS
     : D D
     ;
    
    NUM_4DIGITS
     : '19' D D // 1900..1999
     | '20' D D // 2000..2099
     | '2100'   // 2100
     ;
    
    NUM_OTHER
     : D+
     ;
    
    fragment D : [0-9];