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

时间:2018-07-16 21:57:26

标签: search antlr antlr4

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

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

但是,实际上这并不是很好,因为S中的前缀可能恰好具有与我的语法中的标记匹配的字符。取决于发生这种情况的方式,ss中的有效字符串S可能会被识别几次,或者不规律地被跳过,或者可能会打印很多有关“令牌识别错误”的错误。

是否有我没有想到的方法,或者我不知道的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上找不到任何内容组。)

2 个答案:

答案 0 :(得分:2)

  

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

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

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

IGNORE : . -> skip; 

这样,将忽略词法分析器中未明确识别的所有内容。

下一个问题是“普通”文本和关键字之间可能存在语义重叠,例如 Jan(名称)-Jan(简称abrev)。通常,可以通过在解析器中添加BaseErrorListener来区分真实错误和无意义错误来解决此问题。构成真实与无意义的内容可能会涉及复杂的极端情况,具体取决于应用程序。

最后,规则

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

返回一个int值而不是一个INT令牌,因此正在报告错误。规则应该是

day_number : INT ;

答案 1 :(得分:0)

基于@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)