代码之家  ›  专栏  ›  技术社区  ›  DainDwarf

在antlr4中精确分析n个参数

  •  2
  • DainDwarf  · 技术社区  · 9 年前

    我将Antlr4与python3运行时一起使用。在我试图解析的语言中,有许多操作(大约50个)接受表单中固定数量的参数 OPNAME [ parameter1, parameter2, parameter3 ]

    我以前有一个语法,有这样的规则:

    statement: OP1 '[' NUM ']'
        | OP2 '[' NUM ',' NUM ']'
        | OP3 '[' NUM ',' NUM ',' NUM ']'
        | OP2or3 (('[' NUM ',' NUM ']')|('[' NUM ',' NUM ',' NUM ']'))
        ;
    

    然而,为了更清楚,我决定制定一个子规则 parameter[n] 完全接受 n 参数。因此,我的(完整示例)语法如下:

    grammar test;
    
    program: (statement? NEWLINE)* EOF;
    
    statement: OP1 parameter[1]
        | OP2 parameter[2]
        | OP3 parameter[3]
        | OP2or3 (parameter[2]|parameter[3])
        ;
    
    parameter[n]
    locals[i = 1]
        : '[' NUM 
            ( ',' NUM {$i += 1} )*
          ']' 
          {$i == $n}?
        ;
    
    
    
    OP1     : 'OP1'     ;
    OP2     : 'OP2'     ;
    OP3     : 'OP3'     ;
    OP2or3  : 'OP2or3'  ;
    
    NUM     : ('0'..'9')+;
    NEWLINE : '\n'      ;
    WS      : [ \t\r] -> channel(1);
    

    在以下对象上运行此语法 testfile.txt 几乎可以。我在OP1、OP2和OP3中测试了或多或少的参数,如果我没有确切对应的参数数量,那就失败了。 然而,这对OP2或OP3不起作用,OP2或3在3个参数下总是失败 。我猜antlr解析器首先尝试使用2个参数进行检查,失败谓词,然后无法正确回溯(错误消息为 Error at [5:16] : rule parameter failed predicate: {$i == $n}? ). 的内容 测试文件.txt :

    OP1 [1] 
    OP2 [32, 52]
    OP3 [1, 2, 3]
    OP2or3 [1, 2]
    OP2or3 [1, 2, 3]
    

    我试图用一个更明确的规则替换入口处的谓词,但这仍然不起作用(错误消息是 Error at [5:7] : no viable alternative at input '[' )

    parameter[n]
        : {$n == 1}? '[' NUM ']'
        | {$n == 2}? '[' NUM ',' NUM ']'
        | {$n == 3}? '[' NUM ',' NUM ',' NUM ']'
        ;
    

    作为参考,下面是我用来测试语法的python代码:

    import codecs
    from antlr4 import *
    from antlr4.error.ErrorListener import ErrorListener
    from testParser import testParser as Parser
    from testLexer import testLexer as Lexer
    
    class SimpleErrorThrower(ErrorListener):
        def syntaxError(self, recognizer, offendingSymbol, line, column, msg, e):
            msg = msg.replace('\n', '\\n')
            raise RuntimeError("Error at [%s:%s] : %s" % (line, column, msg))
    
    def load_code(filename):
        return codecs.decode(open(filename, 'rb').read(), 'utf-8')
    
    def ParseFromRule(input_string, rule_to_call='program'):
        '''Try to parse a given string (case insensitive) from a given rule.
            Raises 'AttrivuteError' if rule does not exist.
            Raises 'ParsingException' if parsing failed.
            Returns the parse tree if parsing was successfull.'''
        source = InputStream(input_string)
        lexer = Lexer(source)
        stream = CommonTokenStream(lexer)
        parser = Parser(stream)
        parser.removeErrorListeners()
        parser.addErrorListener(SimpleErrorThrower())
        parseTree = getattr(parser, rule_to_call)()
        return parseTree
    
    
    if __name__ == '__main__':
        from argparse import ArgumentParser
    
        args = ArgumentParser()
        args.add_argument("-p", "--print", help="Print resulting tree.", action='store_true')
        args.add_argument("filename", metavar="Source filename", help="file containing the code to test.", type=str)
        options = args.parse_args()
    
        input_string = load_code(options.filename)
        try:
            tree = ParseFromRule(input_string, 'program')
        except RuntimeError as e:
            print(str(e))
            exit(1)
    
        if options.print:
            print(tree.toStringTree(recog=tree.parser))
    

    这是我的 Makefile :

    ANTLR_CP=/usr/local/bin/antlr-4.5.1-complete.jar
    ANTLR=java -Xmx500M -cp "$(ANTLR_CP):$$CLASSPATH" org.antlr.v4.Tool
    
    all: testParser.py
    
    clean:
        rm -f *Lexer.py *Listener.py *Parser.py *.tokens *.pyc
    
    testParser.py: *.g4
        $(ANTLR) -Dlanguage=Python3 test.g4
    

    你知道我能不能制定一个规则吗 参数[n] 这也适用于 OP2or3 ? 有了这个子规则确实有助于提高清晰度,因为规则往往会经常更改(每隔几个月就会添加或删除一些运算符)

    1 回复  |  直到 9 年前
        1
  •  1
  •   DainDwarf    9 年前

    很抱歉打扰了任何人看我的问题,但我自己找到了答案,使用了python魔法。也许这有一天能帮助到某人。我返工了 parameter[n] int tuple :

    parameter[n]
    locals[i = 1]
        : '[' NUM
            ( ',' NUM {$i += 1} )*
          ']'
          {($i == $n or $i in $n)}?
        ;
    

    注意语义谓词中的括号。 你需要把这些括号 因为它将被翻译为 not (i == n or i in n) 在python解析器中,没有无法正确否定的括号(我想这可能被视为antlr4错误)

    因此,现在我的陈述规则是:

    statement: OP1 parameter[1]
        | OP2 parameter[2]
        | OP3 parameter[3]
        | OP2or3 parameter[(2, 3)]
        ;
    

    这在我的测试文件中起作用:

    $ python3 test_grammar.py testfile.txt -p
    (program (statement OP1 (parameter [ 1 ]))
    (statement OP2 (parameter [ 32 , 52 ]))
    (statement OP3 (parameter [ 1 , 2 , 3 ]))
    (statement OP2or3 (parameter [ 1 , 2 ]))
    (statement OP2or3 (parameter [ 1 , 2 , 3 ]))
    <EOF>)