【问题标题】:ANTLR - find first match for grammar within stringANTLR - 在字符串中找到语法的第一个匹配项
【发布时间】:2018-07-16 21:57:26
【问题描述】:

有没有办法使用 ANTLR 解析器作为搜索器,即找到与给定规则 my_rule 匹配的较长字符串 S 的子字符串 ss 的第一个实例?

从概念上讲,我可以通过在位置 S[i] 查找匹配来完成此操作,递增 i 直到我成功检索到匹配或 S 用尽。

但是,在实践中,这并不能很好地工作,因为S 中的前缀可能恰好包含与我的语法中的标记匹配的字符。根据这种情况的发生,S 中的有效字符串 ss 可能会被多次识别,或者不规律地跳过,或者可能会打印很多关于“令牌识别错误”的错误。

是否有我没有想到的方法,或者我不知道的 ANTLR 功能?

我正在使用 ANTLR 的 Python 绑定,如果这很重要的话。

示例

给定以下语法:

grammar test ;

options { language=Python3; }


month returns [val]
  : JAN {$val = 1}
  | FEB {$val = 2}
  | MAR {$val = 3}
  | APR {$val = 4}
  | MAY {$val = 5}
  ;

day_number returns [val]
  : a=INT {$val = int($a.text)} ;

day returns [val]
  : day_number WS?     {$val = int($day_number.start.text)}
  ;

month_and_day returns [val]
  : month WS day             {$val = ($month.val, $day.val)}
  | day WS ('of' WS)? month  {$val = ($month.val, $day.val)}
  ;


WS : [ \n\t]+ ;  // whitespace is not ignored

JAN : 'jan' ('.' | 'uary')? ;
FEB : 'feb' ('.' | 'ruary')? ;
MAR : 'mar' ('.' | 'ch')? ;
APR : 'apr' ('.' | 'il')? ;
MAY : 'may' ;


INT
  : [1-9]
  | '0' [1-9]
  | '1' [0-9]
  | '2' [0-3]
  ;

和下面的脚本来测试它:

import sys

sys.path.append('gen')


from testParser import testParser
from testLexer import testLexer


from antlr4 import InputStream
from antlr4 import CommonTokenStream, TokenStream


def parse(text: str):
    date_input = InputStream(text.lower())
    lexer = testLexer(date_input)
    stream = CommonTokenStream(lexer)
    parser = testParser(stream)
    return parser.month_and_day()


for t in ['Jan 6',
          'hello Jan 6, 1984',
          'hello maybe Jan 6, 1984']:
    value = parse(t)
    print(value.val)

我得到以下结果:

# First input - good
(1, 6)

# Second input - errors printed to STDERR
line 1:0 token recognition error at: 'h'
line 1:1 token recognition error at: 'e'
line 1:2 token recognition error at: 'l'
line 1:3 token recognition error at: 'l'
line 1:4 token recognition error at: 'o '
line 1:11 token recognition error at: ','
(1, 6)

# Third input - prints errors and throws exception
line 1:0 token recognition error at: 'h'
line 1:1 token recognition error at: 'e'
line 1:2 token recognition error at: 'l'
line 1:3 token recognition error at: 'l'
line 1:4 token recognition error at: 'o '
line 1:9 token recognition error at: 'b'
line 1:10 token recognition error at: 'e'
line 1:12 mismatched input 'jan' expecting INT
Traceback (most recent call last):
  File "test_grammar.py", line 25, in <module>
    value = parse(t)
  File "test_grammar.py", line 19, in parse
    return parser.month_and_day()
  File "gen/testParser.py", line 305, in month_and_day
    localctx._day = self.day()
  File "gen/testParser.py", line 243, in day
    localctx.val = int((None if localctx._day_number is None else localctx._day_number.start).text)
ValueError: invalid literal for int() with base 10: 'jan'
Process finished with exit code 1

要使用我上面概述的增量方法,我需要一种方法来抑制 token recognition error 输出并将异常包装在 try 或类似名称中。感觉我会非常反常,并且很难将这些解析异常与其他出错的事情区分开来。

(META - 我可以发誓我已经在大约 4 个月前的某个地方问过这个问题,但我在 SO、ANTLR GitHub 跟踪器或 ANTLR Google 上找不到任何东西组。)

【问题讨论】:

  • 字符串S 是结构化的,还是一些任意文本?规则my_rule的完整表达是什么?没有更多细节很难理解第 3 段。
  • S 可能是完全任意的——在我的例子中,它是用户可以输入的文本。我明天回去工作时添加一个示例规则,谢谢。
  • 添加到问题的示例。

标签: search antlr antlr4


【解决方案1】:

有没有办法使用 ANTLR 解析器作为搜索器,即找到 匹配的较长字符串 S 的子字符串 ss 的第一个实例 给定规则my_rule?

简短的回答是否定的。 ANTLR 不能替代/等效于任何基于正则表达式的标准工具,例如 sedawk

更长的答案是肯定的,但有一些混乱的警告。 ANTLR 期望解析结构化的、基本明确的输入文本。可以通过添加词法规则忽略没有语义意义的文本(在最低优先级/底部位置)

IGNORE : . -> skip; 

这样,词法分析器中未明确识别的任何内容都会被忽略。

下一个问题是“普通”文本和关键字之间潜在的语义重叠,例如 Jan(姓名)- Jan(月份缩写)。一般来说,这可以通过在解析器中添加BaseErrorListener 来区分真实错误和无意义错误来处理。什么构成真实与无意义可能涉及复杂的极端情况,具体取决于应用程序。

最后,规则

day_number returns [val]
  : a=INT {$val = int($a.text)} ;

返回的是 int 值而不是 INT 令牌,因此报告了错误。规则应该是

day_number : INT ;

【讨论】:

  • 感谢您的回答,非常有帮助。几点/问题:1)我并没有真正使用 ANTLR 作为 sed/awk/regex 的 替代,事实上,如果它使用基于文本的正则表达式来做到这一点会容易得多具有 ANTLR 解析返回值的能力。非常希望拥有能够理解模式的语法,也可以将各个部分组合在一起以获得有意义的返回值。
  • 2) 是否忽略所有未知字符也允许它们出现在我编写的规则中间?例如。如果我有一个匹配 {month} {year} 的规则并且用户输入 Jan HI THERE 2017 它会匹配(错误地与我的用例匹配),对吧?
  • 3) 我认为day_number 规则没有问题,我认为问题在于它与"maybe" 中的"may" 匹配为month,然后期望在那里成为day_number 下一个;它会丢弃"b", "e",因为它们不匹配任何已知的令牌类型;丢弃" ",因为它是空格;然后尝试将下一个有效令牌"jan" 转换为整数,但失败了。
  • Re (2):规则需要收紧,可能需要谓词来处理这个问题。充其量是混乱的,最坏的情况可能是不可能的,导致回到上面给出的“简短”答案。回复 (3):尚未测试,但从错误消息来看,该规则似乎与 JAN 令牌匹配,并且正在寻找,期待下一个 INT 令牌。
  • 其实,回复 (2):您的 IGNORE : . -&gt; skip; 想法提出了类似的解决方案 - 我可以添加 OTHER : . ; 并且 not 跳过它,以便 Lexer 始终成功,然后执行parser.removeErrorListener(ConsoleErrorListener.INSTANCE) 以减少喋喋不休。我认为这可能会为我解决问题。
【解决方案2】:

基于@grosenberg 回答的一个想法的变体,我确定的解决方案如下。

1) 添加备用词法分析器规则以匹配现有规则尚未匹配的任何文本。 不要忽略/跳过这些标记。

OTHER : . ;

2) 添加一个解析器替代项以匹配感兴趣的规则,或(具有较低优先级)其他任何规则:

month_and_day_or_null returns [val]
  : month_and_day  {$val = $month_and_day.val}
  | .              {$val = None}
  ;

3) 在应用程序代码中,查找 None 或填充值:

def parse(text: str):
    date_input = InputStream(text.lower())
    lexer = testLexer(date_input)
    stream = CommonTokenStream(lexer)
    parser = testParser(stream)
    return parser.month_and_day_or_null()

for t in ['Jan 6',
          'hello Jan 6, 1984',
          'hello maybe Jan 6, 1984']:
    for i in range(len(t)):
        value = parse(t[i:])
        if value.val:
            print(f"Position {i}: {value.val}")
            break

这在比赛时具有预期的效果:

Position 0: (1, 6)
Position 6: (1, 6)
Position 12: (1, 6)

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-01-26
    • 2021-04-11
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多