使用Lark在BASIC语法中的“ END”和“ END IF”之间发生冲突

时间:2019-05-10 14:44:11

标签: python parsing grammar lark-parser

我正在尝试使用Lark为BASIC创建一个LALR解析器,并且我很难解决“ END”语句和诸如“ END IF”之类的语句之间的冲突。这是语法的简化版本:

%ignore /[ \t\f]+/

program: _nlopt _part_list

_part_list: (stmt | block) _nl _part_list
          |

_nlopt: _nl
      |

_nl: _NEWLINE _nl
   | _NEWLINE

block: if_block

stmt: print_stmt
    | end_stmt

end_stmt: END_KW

if_block: IF_KW expr THEN_KW _nl block_body endif_stmt

endif_stmt: END_KW IF_KW

block_body: _block_body_item block_body
          |

_block_body_item: stmt _nl

print_stmt: PRINT_KW expr

?expr: NUMERIC_LITERAL

_NEWLINE: "\n"
NUMERIC_LITERAL: /[\-+]?\d+(\.\d*)?[!#%&]?/

END_KW: "end"i
IF_KW: "if"i
PRINT_KW: "print"i
THEN_KW: "then"i

如果我使用以下代码尝试此语法:

parser = Lark(grammar, start='program', parser='lalr')

prog = r"""
if 1 then
   print 200
end if
"""
t = parser.parse(prog)
print(t.pretty())

这是我从云雀身上得到的东西:

Traceback (most recent call last):
  File "test.py", line 230, in <module>
    t = parser.parse(prog)
  File "/home/user/.pyenv/versions/venv/lib/python3.6/site-packages/lark/lark.py", line 250, in parse
    return self.parser.parse(text)
  File "/home/user/.pyenv/versions/venv/lib/python3.6/site-packages/lark/parser_frontends.py", line 37, in parse
    return self.parser.parse(token_stream, *[sps] if sps is not NotImplemented else [])
  File "/home/user/.pyenv/versions/venv/lib/python3.6/site-packages/lark/parsers/lalr_parser.py", line 68, in parse
    for token in stream:
  File "/home/user/.pyenv/versions/venv/lib/python3.6/site-packages/lark/lexer.py", line 341, in lex
    for x in l.lex(stream, self.root_lexer.newline_types, self.root_lexer.ignore_types):
  File "/home/user/.pyenv/versions/venv/lib/python3.6/site-packages/lark/lexer.py", line 175, in lex
    raise UnexpectedCharacters(stream, line_ctr.char_pos, line_ctr.line, line_ctr.column, allowed=allowed, state=self.state)
lark.exceptions.UnexpectedCharacters: No terminal defined for 'i' at line 4 col 5

end if
    ^

Expecting: ['__IGNORE_0', '_NEWLINE']

如果我删除“ end_stmt”规则,则不会发生。有没有一种方法可以解决这种语法问题,从而避免这种情况发生?

2 个答案:

答案 0 :(得分:3)

默认情况下,Lark不会警告您有关减少语法中的移位冲突,而是默默地解决它们以利于移位。通常,这会导致解析器无法解析您想要的内容-如此处的情况。您可以通过将debug = True标志传递到Lark()来使百灵鸟对此类冲突发出警告。这样一来,您甚至在通过测试发现问题之前,都会发现问题出在哪里,甚至可能获得有关问题所在的有用信息。

在启用debug选项的情况下,您会收到一条警告,指出存在移位减少冲突,其中END_KW可能意味着当前的block_body已经结束,或者可能是end_stmt。这是一个问题,因为LALR(1)解析器只能向前看一个令牌,但是我们必须向前看第二个令牌才能查看在if之后是否有end才能正确决定哪个选择。

您可以通过将end if转换为单个令牌(如下所示)来稍微修改一下:

ENDIF_KW: /end[ \t\f]+if/i

然后使用ENDIF_KW代替END_KW IF_KW

PS:请注意,如果您使用Earley解析而不是LALR(1),则无需进行这些更改,语法就可以正常工作。

答案 1 :(得分:0)

我和我的基本语法有同样的冲突。由于END WHILE,END IF等原因,基本语言是LALR(2)或LR(2)。如果您具有LR(2)解析器生成器,则可以解析基本语言。 LRSTAR解析器生成器可以创建LR(2)解析器。