Lark 匹配自定义分隔符多行字符串
Lark matching custom delimiter multiline strings
我正在尝试使用 lark 从 perl 文件中提取一些信息。为此,我需要对语句是什么有一个基本的了解。我遇到的问题是“Here Document”字符串。我会将它们描述为带有自定义分隔符的多行字符串,例如:
$my_var .= << 'anydelim';
some things
other things
anydelim
在写下这个问题时,我想出了一个使用带有反向引用/命名引用的正则表达式的解决方案。由于我找不到任何类似的问题,所以我决定post这个问题并自己回答。
如果有人知道任何其他方法(例如跨多个百灵鸟规则使用反向引用的方法),请告诉我!
使用正则表达式的解决方案。主要成分:
- 反向引用,在本例中命名为引用
- /s 修饰符(导致 . 也匹配换行符
- .*?匹配非贪婪(否则它也会消耗分隔符)
from lark import Lark
block_grammar = r"""
%import common.WS
%ignore WS
delimited_string: "<<" /(?P<quote>['"])(?P<delimiter>[A-Za-z_]+)(?P=quote)\;.*?(?P=delimiter)/s
"""
minimal_parser = Lark(block_grammar, start="delimited_string")
ast = minimal_parser.parse(r"""
<< 'SomeDelim'; fasdfasdf
fddfsdg SomeDelim
""")
print(ast.pretty())
如果您想跨多个终端进行复杂的反向引用,例如您不能使用单个正则表达式,您需要使用 PostLexer(或者最坏的情况是自定义词法分析器)。一个 XML 结构的小例子:
<html>
<body>
Hello World
</body>
</html>
可以被这个语法 + Postlexer 解析(验证):
from typing import Iterator
from lark import Lark, Token
TEXT = r"""
<html>
<body>
Hello World
</body>
</html>
"""
GRAMMAR = r"""
start: node
node: OPEN_TAG content* CLOSE_TAG
content: node
| TEXT
TEXT: /[^\s<>]+/
RAW_OPEN: "<" /\w+/ ">"
RAW_CLOSE: "</" /\w+/ ">"
%ignore WS
%import common.WS
%declare OPEN_TAG CLOSE_TAG
"""
class MatchTag:
always_accept = "RAW_OPEN", "RAW_CLOSE"
def process(self, stream: Iterator[Token]) -> Iterator[Token]:
stack = []
for t in stream:
if t.type == "RAW_OPEN":
stack.append(t)
t.type = "OPEN_TAG"
elif t.type == "RAW_CLOSE":
open_tag = stack.pop()
if open_tag.value[1:-1] != t.value[2:-1]:
raise ValueError(f"Non matching closing tag (expected {open_tag.value!r}, got {t.value!r})")
t.type = "CLOSE_TAG"
yield t
parser = Lark(GRAMMAR, parser='lalr', postlex=MatchTag())
print(parser.parse(TEXT).pretty())
(注意:如果你真的想解析,请不要使用 Lark XML。有很多难以处理甚至无法处理的陷阱)
我正在尝试使用 lark 从 perl 文件中提取一些信息。为此,我需要对语句是什么有一个基本的了解。我遇到的问题是“Here Document”字符串。我会将它们描述为带有自定义分隔符的多行字符串,例如:
$my_var .= << 'anydelim';
some things
other things
anydelim
在写下这个问题时,我想出了一个使用带有反向引用/命名引用的正则表达式的解决方案。由于我找不到任何类似的问题,所以我决定post这个问题并自己回答。
如果有人知道任何其他方法(例如跨多个百灵鸟规则使用反向引用的方法),请告诉我!
使用正则表达式的解决方案。主要成分:
- 反向引用,在本例中命名为引用
- /s 修饰符(导致 . 也匹配换行符
- .*?匹配非贪婪(否则它也会消耗分隔符)
from lark import Lark
block_grammar = r"""
%import common.WS
%ignore WS
delimited_string: "<<" /(?P<quote>['"])(?P<delimiter>[A-Za-z_]+)(?P=quote)\;.*?(?P=delimiter)/s
"""
minimal_parser = Lark(block_grammar, start="delimited_string")
ast = minimal_parser.parse(r"""
<< 'SomeDelim'; fasdfasdf
fddfsdg SomeDelim
""")
print(ast.pretty())
如果您想跨多个终端进行复杂的反向引用,例如您不能使用单个正则表达式,您需要使用 PostLexer(或者最坏的情况是自定义词法分析器)。一个 XML 结构的小例子:
<html>
<body>
Hello World
</body>
</html>
可以被这个语法 + Postlexer 解析(验证):
from typing import Iterator
from lark import Lark, Token
TEXT = r"""
<html>
<body>
Hello World
</body>
</html>
"""
GRAMMAR = r"""
start: node
node: OPEN_TAG content* CLOSE_TAG
content: node
| TEXT
TEXT: /[^\s<>]+/
RAW_OPEN: "<" /\w+/ ">"
RAW_CLOSE: "</" /\w+/ ">"
%ignore WS
%import common.WS
%declare OPEN_TAG CLOSE_TAG
"""
class MatchTag:
always_accept = "RAW_OPEN", "RAW_CLOSE"
def process(self, stream: Iterator[Token]) -> Iterator[Token]:
stack = []
for t in stream:
if t.type == "RAW_OPEN":
stack.append(t)
t.type = "OPEN_TAG"
elif t.type == "RAW_CLOSE":
open_tag = stack.pop()
if open_tag.value[1:-1] != t.value[2:-1]:
raise ValueError(f"Non matching closing tag (expected {open_tag.value!r}, got {t.value!r})")
t.type = "CLOSE_TAG"
yield t
parser = Lark(GRAMMAR, parser='lalr', postlex=MatchTag())
print(parser.parse(TEXT).pretty())
(注意:如果你真的想解析,请不要使用 Lark XML。有很多难以处理甚至无法处理的陷阱)