解析在PLY

时间:2018-05-25 19:30:59

标签: python parsing yacc lex ply

我是lexing和解析世界的新手,所以我希望这是一个容易解决的问题。我正在尝试使用Python的PLY解析具有不同类型的令牌组的文件:

STRING STRING QUANTITY STRING STRING       # TypeA
STRING STRING STRING STRING STRING STRING  # TypeB
STRING STRING QUANTITY QUANTITY QUANTITY   # TypeC

每一行都应该是我的程序理解的一种命令。例如,让我们调用顶行TypeA,第二行TypeB中定义的类型,依此类推。由于每行应该有一个命令,每行末尾的NEWLINE标记表示命令的结束。我成功设法使用以下词法分析器对文件进行标记:

# top level tokens
tokens = [
    'QUANTITY',
    'STRING',
    'NEWLINE'
]

# number, possibly in exponential notion, e.g. -1.5e-3.0, or SI suffix, e.g. 'k'
t_QUANTITY = r'[+-]?(\d+\.\d*|\d*\.\d+|\d+)([eE][+-]?\d*\.?\d*|[GMkmunpf])?'

# any group of 2 or more alphanumeric characters, with the first being a letter
t_STRING = r'[a-zA-Z_][a-zA-Z_0-9]*'

# ignore spaces and tabs
t_ignore = ' \t'

# ignore comments
t_ignore_COMMENT = r'\#.*'

# detect new lines
def t_newline(t):
    r'\n+'
    # generate newline token
    t.type = "NEWLINE"

    return t

我想编写一个解析器,它将每个匹配的命令解析为不同的对象。我最终应该得到一个已解析对象的列表。

我尝试构建以下规则:

def p_command(self, p):
    '''command : tokens NEWLINE
               | NEWLINE'''
    print("found command:", list(p))

def p_tokens(self, p):
    '''tokens : type_a_tokens
              | type_b_tokens
              | type_c_tokens'''
    p[0] = p[1]

def p_type_a_tokens(self, p):
    '''type_a_tokens : STRING STRING QUANTITY STRING STRING'''
    p[0] = "TypeA"

def p_type_b_tokens(self, p):
    '''type_b_tokens : STRING STRING STRING STRING STRING STRING'''
    p[0] = "TypeB"

def p_type_c_tokens(self, p):
    '''type_c_tokens : STRING STRING QUANTITY QUANTITY QUANTITY'''
    p[0] = "TypeC"

我在第一个SyntaxError之后立即获得了NEWLINE代币。不知何故,解析器在看到与p_type_a_tokens的模式匹配的模式后才开始解析新命令。

任何人都可以了解一下非常简单的解析规则集吗?尽管PLY的文档通常非常好,但到目前为止我发现的所有示例都是针对计算器或编程语言,其中不适用换行符。

完整来源:

from ply import lex, yacc

class InputParser(object):
    # top level tokens
    tokens = [
        'QUANTITY',
        'STRING',
        'NEWLINE'
    ]

    t_QUANTITY = r'[+-]?(\d+\.\d*|\d*\.\d+|\d+)([eE][+-]?\d*\.?\d*|[GMkmunpf])?'
    t_STRING = r'[a-zA-Z_][a-zA-Z_0-9]*'

    # ignored characters
    t_ignore = ' \t'

    # ignore comments
    t_ignore_COMMENT = r'\#.*'

    def __init__(self, **kwargs):
        self.lexer = lex.lex(module=self, **kwargs)
        self.parser = yacc.yacc(module=self, **kwargs)

    # detect new lines
    def t_newline(self, t):
        r'\n+'
        # generate newline token
        t.type = "NEWLINE"

    # error handling
    def t_error(self, t):
        # anything that gets past the other filters
        print("Illegal character '%s' on line %i at position %i" %
              (t.value[0], self.lexer.lineno))

        # skip forward a character
        t.lexer.skip(1)

    # match commands on their own lines
    def p_command(self, p):
        '''command : tokens NEWLINE
                   | NEWLINE'''
        print("found command:", list(p))
        p[0] = p[1]

    def p_tokens(self, p):
        '''tokens : type_a_tokens
                  | type_b_tokens
                  | type_c_tokens'''
        p[0] = p[1]

    def p_type_a_tokens(self, p):
        '''type_a_tokens : STRING STRING QUANTITY STRING STRING'''
        print("found type a")
        p[0] = "TypeA"

    def p_type_b_tokens(self, p):
        '''type_b_tokens : STRING STRING STRING STRING STRING STRING'''
        print("found type b")
        p[0] = "TypeB"

    def p_type_c_tokens(self, p):
        '''type_c_tokens : STRING STRING QUANTITY QUANTITY QUANTITY'''
        print("found type c")
        p[0] = "TypeC"

    def p_error(self, p):
        if p:
            error_msg = "syntax error '%s'" % p.value
        else:
            error_msg = "syntax error at end of file"

        print(error_msg)

    def parse(self, text):
        self.parser.parse(text, lexer=self.lexer)

if __name__ == "__main__":
    parser = InputParser()
    parser.parse("""
a b 5.5 c d     # TypeA
e f 1.6 g h     # TypeA
i j k l m n     # TypeB
# empty line
o p -1 2.0 3e4  # TypeC
""")

1 个答案:

答案 0 :(得分:0)

问题是由第一条规则特殊引起的:这是解析器启动的地方。由于上面的第一个规则不能组合两个命令(在两个相邻的行上找到),因此失败。

我通过在p_command之上添加一个新的根规则来修复它,它可以采用单个command(当文件只包含一个命令时)或命令列表({{1 }}):

command_list

(我还在类中添加了一个def p_command_list(self, p): '''command_list : command | command_list command''' if len(p) == 3: self.commands.append(p[2]) else: self.commands.append(p[1]) 字段来保存已解析的命令)

这可以处理多个命令"合并"在我的输入文件中找到。