我正在使用rply
和Python3.6为一个小的privat项目创建一个词法分析器和解析器。
但是我注意到的是,解析器似乎在翻转lexerstream的顺序。
这是我正在解析的文件:
let test:string = "test";
print(test);
Lexer输出:
Token('LET', 'let')
Token('NAME', 'test')
Token('COLON', ':')
Token('NAME', 'string')
Token('EQUALS', '=')
Token('STRING', '"test"')
Token('SEMI_COLON', ';')
Token('PRINT', 'print')
Token('OPEN_PARENS', '(')
Token('STRING', '"test"')
Token('CLOSE_PARENS', ')')
Token('SEMI_COLON', ';')
如您所见,它是按脚本顺序排列的。
我使用解析器创建一个名称为test
,类型为string
和值为test
的变量。然后我要打印变量。
它确实创建了变量,但是当我想将其打印出来时,什么也没有。
但是当我像这样翻转脚本
print(test);
let test:string = "test";
它能够正确打印该值。
两个解析器“规则”如下所示: 打印:
@self.pg.production('expression : PRINT OPEN_PARENS expression CLOSE_PARENS SEMI_COLON expression')
def print_s(p):
...
创建变量:
@self.pg.production('expression : LET expression COLON expression EQUALS expression SEMI_COLON expression')
def create_var(p):
...
所以我的问题是:如何翻转内容解析的顺序?
编辑:我在文档中寻找了类似的问题或问题,但没有找到任何东西。
答案 0 :(得分:2)
这是一个更简单的例子;希望您可以看到该模式。
关键见解是在完全解析了生产的匹配项之后执行归约动作(即解析器功能)。这意味着,如果产品包含非终端,则在整个产品的操作之前执行那些非终端的操作。
应该清楚为什么如此。每个生产动作都取决于所有组件的语义值,在非终端的情况下,这些值是通过运行相应的动作来产生的。
现在,考虑这两种非常相似的方法来解析list
中的thing
。在这两种情况下,我们都假设有一个基本产品可以识别空的list
(list :
)而什么也不做。
右递归:
list : thing list
左递归:
list : list thing
在两种情况下,该操作均输出thing
,在右递归的情况下为p[0]
,在左递归的情况下为p[1]
。
右递归产生将导致thing
的打印顺序相反,因为直到解析内部thing
之后,list
的打印才会发生(并且组件已打印)。
但是出于相同的原因,左递归生产将按从左到右的顺序打印thing
。区别在于左递归情况下的tgat,内部(递归)list
包含初始的thing
,而在右递归情况下,list
包含最终的{{1 }}。
如果您只是构建thing
的Python列表,那么这可能无关紧要,因为执行顺序并不重要。仅在此示例中可见,因为该动作具有副作用(打印值),这使执行顺序可见。
还有其他一些方法可以对动作进行排序,在极少数情况下,确实需要这样做。但是最佳实践是在语法上可行时始终使用左递归。左递归解析器效率更高,因为该解析器不需要累积一堆不完整的产品。左递归通常也更适合您的操作。
例如,在这里,左递归操作可以附加新值(thing
),而右递归操作则需要创建新列表(p[0].append(p[1]); return p[0]
)。由于重复附加是在平均线性时间上进行的,而重复串联是二次进行的,因此左递归解析器对于大型列表更具可伸缩性。