Python反向解析器

时间:2019-04-05 10:00:44

标签: python python-3.x parsing ply

我正在使用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):
    ...

所以我的问题是:如何翻转内容解析的顺序?

编辑:我在文档中寻找了类似的问题或问题,但没有找到任何东西。

1 个答案:

答案 0 :(得分:2)

这是一个更简单的例子;希望您可以看到该模式。

关键见解是在完全解析了生产的匹配项之后执行归约动作(即解析器功能)。这意味着,如果产品包含非终端,则在整个产品的操作之前执行那些非终端的操作。

应该清楚为什么如此。每个生产动作都取决于所有组件的语义值,在非终端的情况下,这些值是通过运行相应的动作来产生的。

现在,考虑这两种非常相似的方法来解析list中的thing。在这两种情况下,我们都假设有一个基本产品可以识别空的listlist :)而什么也不做。

  1. 右递归:

    list : thing list
    
  2. 左递归:

    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])。由于重复附加是在平均线性时间上进行的,而重复串联是二次进行的,因此左递归解析器对于大型列表更具可伸缩性。