[英]Python parser for CTF grammar from nonterminals
在 Python 3 中,最常见的解析器生成器(例如 ANTLR 或 Lark)通过从字符串的终结符派生非终结符来定义语法,并构造一个词法分析器和一个解析器来评估字符串。
相反,我正在寻找一个解析器生成器,它可以处理仅由非终结符和终结符组成的“中间输入”,这意味着我会事先进行词法分析和部分解析。
例如,如果输入语法是
S -> AB
A -> a | aA
B -> b | aB
其中大写字母是非终结符,小写字母是终结符,可能的输入可能是aaaB
,然后可以从中构建具有根S
的解析树。
输入实际上不能只是像aaaB
这样的一串 ASCII 字符,因为非终结符必须存储有关它们自己的子树的信息。 所以至少那些必须是任意对象,而输入更可能是一个对象列表。
是否有提供此功能的库或包?
注意:这不是背书。 可能还有许多其他 Python 解析包提供类似的功能。 它只是作为实现(假定)目标的一种可能机制来呈现的。
Ply 解析包确实包含一个词法分析器生成器,但您没有义务使用它; 你可以随意使用任何你喜欢的函数来提供词法标记。 令牌是简单的对象类型,其中包括一个value
属性。 但是,除了要求它存在之外,Ply 不会对value
对象做任何假设。
当 lex 返回令牌时,它们有一个存储在
value
属性中的value
。 通常,该值是匹配的文本。 但是,该值可以分配给任何 Python 对象。
…
如果您要创建一个手写词法分析器并打算将它与 yacc.py 一起使用,则只需符合以下要求:
token()
方法,该方法返回下一个令牌或None
如果没有更多令牌可用。token()
方法必须返回一个具有type
和value
属性的对象tok
。 如果使用行号跟踪,则令牌还应定义lineno
属性。 为了将非终端传递给解析器,您需要使它们看起来像终端。 最简单的方法是为每个非终端制作一个伪终端,并用单位产品转换伪终端。 例如,假设伪终端的名称以_
结尾(这将使它们很容易以编程方式生成,您可以修改规则
B : b | a B
进入
B : b
| a B
| B_
如果我们假设您将正确的 AST 值注入到B_
终端返回的令牌对象中,那么您只需要在语法中添加一个动作函数:
def p_pseudo_terminals(p):
'''A : A_
B : B_
'''
p[0] = p[1]
这是一个完整的可运行示例,使用问题中的语法:
from ply import yacc
from collections import namedtuple
Token = namedtuple('Token', ['type', 'value'])
Node = namedtuple('Node', ['type', 'children'])
tokens = ['a', 'b', 'A_', 'B_']
def p_S(p):
'''S : A B'''
p[0] = Node('S', [ p[1], p[2] ])
def p_A(p):
'''A : a'''
p[0] = Node('A', [ p[1] ])
def p_Al(p):
'''A : a A'''
p[0] = Node('A', [ p[1], p[2] ])
def p_B(p):
'''B : b'''
p[0] = Node('B', [ p[1] ])
def p_Bl(p):
'''B : a B'''
p[0] = Node('B', [ p[1], p[2] ])
def p_pseudo(p):
'''A : A_
B : B_
'''
p[0] = p[1]
class Lexer(object):
def __init__(self, iter):
self.iter = iter.__iter__()
def token(self):
try:
retval = next(self.iter)
if type(retval) == Token:
# Input is a token, just pass it through
return retval
else:
# Input is an AST node; fabricate a pseudo-token
return Token(retval.type + '_', retval)
except StopIteration:
return None
parser = yacc.yacc()
和一个示例运行:
$ python3
Python 3.6.9 (default, Nov 7 2019, 10:44:02)
[GCC 8.3.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> from inject import *
>>> token_stream = [Token('a', 'a')] * 3 + [Node('B', ['a', Node('B', children=['a', Node('B', children=['b'])])])]
>>> lexer = Lexer(token_stream)
>>> print(parser.parse(None, lexer=lexer))
Node(type='S', children=[Node(type='A', children=['a', Node(type='A', children=['a', Node(type='A', children=['a'])])]), Node(type='B', children=['a', Node(type='B', children=['a', Node(type='B', children=['b'])])])])
在真正的语法中,键入所有这些伪标记名称和单元产生式可能会很乏味。 您可以利用这样一个事实,即您可以自己生成文档字符串,只要它在您通过调用yacc.yacc
构建解析器之前yacc.yacc
。 您还需要将伪令牌名称添加到令牌名称列表中,以便 Ply 知道B_
是一个令牌。
Lark 支持使用“自定义词法分析器”,您可以使用它来提供您选择的任何终端流。 源不必是字符串。
您可以在此处找到此功能的简单示例: https : //github.com/lark-parser/lark/blob/master/examples/custom_lexer.py
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.