繁体   English   中英

来自非终结符的 CTF 语法的 Python 解析器

[英]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()方法必须返回一个具有typevalue属性的对象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]

这是一个完整的可运行示例,使用问题中的语法:

文件:inject.py

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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM