首页 文章

ANTLR - 在字符串中找到语法的第一个匹配项

提问于
浏览
2

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

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

但是,实际上这并不能很好地工作,因为 S 中的前缀可能巧合地在我的语法中包含与令牌匹配的字符 . 根据这种情况的发生, S 中的有效字符串 ss 可能会被多次识别,或者被错误地跳过,或者可能会出现很多关于"token recognition error"的错误 .

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

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

EXAMPLE

鉴于以下语法:

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 - 我可以 swear 我已经在4个月前的某个地方问了这个问题,但是我找不到SO,或者ANTLR GitHub跟踪器或者ANTLR Google Group . )

2 回答

  • 0

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

    最简洁的答案是不 . ANTLR不能作为任何标准的基于正则表达式的工具的替代/等效工具,如 sedawk .

    更长的答案是肯定的,但有一个混乱的警告 . ANTLR希望解析一个结构化的,基本上无歧义的输入文本 . 通过添加词法分析器规则(在最低优先级/最低位置)可以忽略没有语义意义的文本

    IGNORE : . -> skip;
    

    这样,忽略了词法分析器中未明确识别的任何内容 .

    下一个问题是'normal'文本和关键字之间潜在的语义重叠,例如Jan(姓名) - Jan(月abrev) . 通常,这可以通过向解析器添加_1574249来处理,以区分真实和无意义的错误 . 什么构成真实与无意义可能涉及复杂的角落案例取决于应用程序 .

    最后,规则

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

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

    day_number : INT ;
    
  • 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)
    

相关问题