python - ANTLR 语义谓词 - 不阻止规则

标签 python antlr grammar predicate

我在 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 不在其范围内,但它返回一个 无论如何,year_4digit 都匹配,但尚未填充其 val 属性,从而导致有关 NoneType 的下游错误。这是正确的吗?

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

(另外 - 我希望能够执行 $INTEGER.int 而不是 int($INTEGER.text),但也许这在 Python 目标中不可用?无关紧要的小问题。)

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

谢谢。

最佳答案

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

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”失败。

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

经过一番尝试后,我通过将规则合并到 1 个大 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];

关于python - ANTLR 语义谓词 - 不阻止规则,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/49075258/

相关文章:

c# - 有没有用 C# 编写的 ANTLR 的有效替代方案?

c - 使用此语法在 flex/bison 中获取语法错误

c++ - 当我将 {} 分配给一个已经存在的对象时,这意味着什么?

python - 如何选择二级隐藏菜单

python - 执行 Numba 生成的程序集

thread-safety - Java 线程的 ANTLR 解析器是否安全?

java - DFA 预测和范围

go - 'sconn, ok:= conn.(*tls.Conn)' 是什么意思?

python - nltk.concordance 给出最多 25 行,无论我如何更改该参数

python - 用新实例替换类的实例