如何在 python lark 解析器中平衡规则和终端?
How to balance rules and terminals in python lark parser?
我用的是lark,非常棒python parsing library。
它提供了 Earley 和 LALR(1) 解析器并通过 custom EBNF
format. (EBNF stands for Extended Backus–Naur form).
定义
小写定义是规则,大写定义是终结符。 Lark 还为大写定义提供了一个权重来优先匹配。
我正在尝试定义一个语法,但我受困于一种我似乎无法平衡的行为。
我有一些使用未命名文字(双引号之间的字符串或字符)的规则:
directives: directive+
directive: "@" NAME arguments ?
directive_definition: description? "directive" "@" NAME arguments? "on" directive_locations
directive_locations: "SCALAR" | "OBJECT" | "ENUM"
arguments: "(" argument+ ")"
argument: NAME ":" value
union_type_definition: description? "union" NAME directives? union_member_types?
union_member_types: "=" NAME ("|" NAME)*
description: STRING | LONG_STRING
STRING: /("(?!"").*?(?<!\)(\\)*?"|'(?!'').*?(?<!\)(\\)*?')/i
LONG_STRING: /(""".*?(?<!\)(\\)*?"""|'''.*?(?<!\)(\\)*?''')/is
NAME.2: /[_A-Za-z][_0-9A-Za-z]*/
它适用于 99% 的用例。但是,如果在我解析的语言中,我使用 称为 directive
的 directive
,一切都会中断:
union Foo @something(test: 42) = Bar | Baz # This works
union Foo @directive(test: 42) = Bar | Baz # This fails
此处,directive
字符串在 directive_definition
规则中应与 NAME.2
终端匹配时在未命名文字上匹配。
我怎样才能平衡/调整它以使 LALR(1) 解析器没有歧义?
这里是 Lark 的作者。
发生这种误解是因为 "directive" 可以是两个不同的标记:"directive" 字符串或名称。默认情况下,Lark 的 LALR 词法分析器总是选择更具体的一个,即字符串。
那么我们怎样才能让词法分析器知道 @directive
是一个名字,而不仅仅是两个常量字符串呢?
解决方案 1 - 使用上下文词法分析器
在这种情况下(如果没有完整的语法很难确定)可能会有帮助的是使用上下文词法分析器,而不是标准的 LALR(1) 词法分析器。
上下文词法分析器可以在某种程度上与解析器通信,以确定哪个终端在每个点上更有意义。这是Lark独有的算法,你可以这样使用:
parser = Lark(grammar, parser="lalr", lexer="contextual")
(这个词法分析器可以做任何标准词法分析器可以做的事情,甚至更多,所以在未来的版本中它可能会成为默认的词法分析器。)
解决方案 2 - 为终端添加前缀
如果上下文词法分析器不能解决您的冲突,那么 "classic" 解决这种情况的方法是定义指令标记,例如:
DIRECTIVE: "@" NAME
与您的指令规则不同,这不会给词法分析器留下歧义。指令和 "directive" 字符串(或 NAME 终端)之间有明显的区别。
如果其他所有方法都失败了,您始终可以使用 Earley 解析器,它以牺牲性能为代价,可以处理您提供的任何语法,无论可能存在多少冲突。
希望对您有所帮助!
编辑: 我只想指出,上下文词法分析器现在是 LALR 的默认设置,因此调用就足够了:
parser = Lark(grammar, parser="lalr")
我用的是lark,非常棒python parsing library。
它提供了 Earley 和 LALR(1) 解析器并通过 custom EBNF
format. (EBNF stands for Extended Backus–Naur form).
小写定义是规则,大写定义是终结符。 Lark 还为大写定义提供了一个权重来优先匹配。
我正在尝试定义一个语法,但我受困于一种我似乎无法平衡的行为。
我有一些使用未命名文字(双引号之间的字符串或字符)的规则:
directives: directive+
directive: "@" NAME arguments ?
directive_definition: description? "directive" "@" NAME arguments? "on" directive_locations
directive_locations: "SCALAR" | "OBJECT" | "ENUM"
arguments: "(" argument+ ")"
argument: NAME ":" value
union_type_definition: description? "union" NAME directives? union_member_types?
union_member_types: "=" NAME ("|" NAME)*
description: STRING | LONG_STRING
STRING: /("(?!"").*?(?<!\)(\\)*?"|'(?!'').*?(?<!\)(\\)*?')/i
LONG_STRING: /(""".*?(?<!\)(\\)*?"""|'''.*?(?<!\)(\\)*?''')/is
NAME.2: /[_A-Za-z][_0-9A-Za-z]*/
它适用于 99% 的用例。但是,如果在我解析的语言中,我使用 称为 directive
的 directive
,一切都会中断:
union Foo @something(test: 42) = Bar | Baz # This works
union Foo @directive(test: 42) = Bar | Baz # This fails
此处,directive
字符串在 directive_definition
规则中应与 NAME.2
终端匹配时在未命名文字上匹配。
我怎样才能平衡/调整它以使 LALR(1) 解析器没有歧义?
这里是 Lark 的作者。
发生这种误解是因为 "directive" 可以是两个不同的标记:"directive" 字符串或名称。默认情况下,Lark 的 LALR 词法分析器总是选择更具体的一个,即字符串。
那么我们怎样才能让词法分析器知道 @directive
是一个名字,而不仅仅是两个常量字符串呢?
解决方案 1 - 使用上下文词法分析器
在这种情况下(如果没有完整的语法很难确定)可能会有帮助的是使用上下文词法分析器,而不是标准的 LALR(1) 词法分析器。
上下文词法分析器可以在某种程度上与解析器通信,以确定哪个终端在每个点上更有意义。这是Lark独有的算法,你可以这样使用:
parser = Lark(grammar, parser="lalr", lexer="contextual")
(这个词法分析器可以做任何标准词法分析器可以做的事情,甚至更多,所以在未来的版本中它可能会成为默认的词法分析器。)
解决方案 2 - 为终端添加前缀
如果上下文词法分析器不能解决您的冲突,那么 "classic" 解决这种情况的方法是定义指令标记,例如:
DIRECTIVE: "@" NAME
与您的指令规则不同,这不会给词法分析器留下歧义。指令和 "directive" 字符串(或 NAME 终端)之间有明显的区别。
如果其他所有方法都失败了,您始终可以使用 Earley 解析器,它以牺牲性能为代价,可以处理您提供的任何语法,无论可能存在多少冲突。
希望对您有所帮助!
编辑: 我只想指出,上下文词法分析器现在是 LALR 的默认设置,因此调用就足够了:
parser = Lark(grammar, parser="lalr")