PLY:错过了“if”语句 注意事项

问题描述

这是我第一次尝试使用 PLY 或任何词法分析器/解析器工具,所以我不确定出了什么问题。

我正在尝试基于 Python 语法松散地实现一种小型汇编语言,尤其是对于带有缩进块的 if 语句。这是一个例子:

if d0 == 0x42:
    a1 = d2
d0 = 0

我编写了一个解析器来处理 INDENTDEDENT,从而生成标记列表:

LexToken(IF,'if',1,0)
LexToken(REGISTER,'d0',3)
LexToken(IS_EQUAL,'==',6)
LexToken(NUMBER,'0x42',9)
LexToken(COLON,':',13)
LexToken(INDENT,'\t',2,15)
LexToken(REGISTER,'a1',16)
LexToken(EQUAL,'=',19)
LexToken(REGISTER,'d2',21)
LexToken(DEDENT,3,24)
LexToken(REGISTER,24)
LexToken(EQUAL,27)
LexToken(NUMBER,'0',29)

似乎没问题(INDENTDEDENT valuelinenolexpos错误的,但我不使用它们)

我的解析器是:

import ply.yacc as yacc

# Get the token map from the lexer.  This is required.
from asmlexer import tokens,MyLexer
from instr import *


def p_statement_assignment(p):
    'statement : assignment'
    print('statmt1 :',[x for x in p])
    p[0] = p[1]
    
def p_statement_ifstatmt(p):
    'statement : ifstatmt'
    print('statmt2 :',[x for x in p])
    p[0] = p[1]
    
def p_assignment(p):
    'assignment : value EQUAL value'
    print("assignment :",[x for x in p])
    p[0] = Move(p[3],p[1])

def p_ifstatmt(p):
    'ifstatmt : IF condition COLON INDENT statement DEDENT'
    print("IF :",[x for x in p])
    p[0] = If(p[2],body = p[5])

def p_condition_equal(p):
    'condition : value IS_EQUAL value'
    print("condition ==",[x for x in p])
    p[0] = "%s == %s" % (p[1],p[3])
    
def p_value_register(p):
    'value : REGISTER'
    print('register :',[x for x in p])
    p[0] = Register(p[1])

def p_value_number(p):
    'value : NUMBER'
    print('value:',[x for x in p])
    p[0] = Value(p[1])

# Error rule for Syntax errors
def p_error(p):
    print(p)
    print("Syntax error in input!")

# Build the parser
class MyParser(object):
    def __init__(self):
        lexer = MyLexer()
        self.lexer = lexer
        self.parser = yacc.yacc()
    
    def parse(self,code):
        self.lexer.input(code)
        result = self.parser.parse(lexer = self.lexer)
        return result

if True:
    with open("test.psm") as f:
        data = f.read()

    parser = MyParser()
    result = parser.parse(data)
    print(result)
    print(result.get_code())

似乎缺少 IF 令牌:

register : [None,'d0']
value: [None,'0x42']
condition == [None,<instr.Register object at 0x000002429DA5E340>,<instr.Value object at 0x000002429DA5E250>]
register : [None,'a1']
register : [None,'d2']
assignment : [None,<instr.Register object at 0x000002429DA2C970>,<instr.Register object at 0x000002429DA5E5E0>]
statmt1 : [None,<instr.Move object at 0x000002429DA5E370>]
LexToken(REGISTER,24)
Syntax error in input!
value: [None,'0']
None
Traceback (most recent call last):
  File ".\asmparser.py",line 137,in <module>
    print(result.get_code())
AttributeError: 'nonetype' object has no attribute 'get_code'

我不明白为什么...

解决方法

语法的起始符号是 statement,所以你的语法描述了一个由一个语句组成的输入,无论是赋值语句还是条件语句。因此,跟在单一语句之后的标记必须是输入的结尾。但事实并非如此。它是 REGISTER d0,因为您的输入包含两个语句。

LALR(1) 解析器,这是 Ply 生成的,可以使用下一个标记来验证可能的归约操作。 [注 1] 如果下一个标记不能跟在减少的非终结符之后,那么将发出错误信号。这个错误是在减少之前还是之后发出信号取决于解析器生成器的特定性质。一些解析器生成器,如 Bison,积极优化前瞻,如果不是绝对必要,它们的解析器甚至不会读取前瞻标记。但是大多数解析器生成器,包括 Ply,生成的解析器总是在决定下一个动作之前读取前瞻标记。 [注2]

如果你想让你的解析器处理一系列语句,你将需要一个扩展为一系列语句的开始符号,例如 program: | program statement。您可能还希望让 if 语句的主体包含一系列语句,而不仅仅是当前语法允许的单个语句。


注意事项

  1. “归约动作”是解析器在到达非终结符的末尾时所做的,并将对应于该非终结符的序列“归约”为单个非终结符。作为归约动作的一部分,解析器执行归约动作;在 Ply 的情况下,这意味着调用与减少的产生式相关联的 p_ 函数。

  2. 仅仅因为解析器已经读取了前瞻标记,并不意味着它在进行归约之前一定会参考它。许多解析器使用压缩表,这些表可能会将错误操作与默认减少操作结合起来。