使用PyParsing解析Python代码?

3
我想编写 PyParsing 代码,能够解析任何 Python 代码(我知道 AST 模块存在,但那只是一个起点 - 我最终想解析的不仅仅是 Python 代码。)
无论如何,我想先从编写能够解析经典的开始。
print("Hello World!")

所以这就是我写的内容:

from pyparsing import (alphanums, alphas, delimitedList, Forward,
                       quotedString, removeQuotes, Suppress, Word)

expr = Forward()
string = quotedString.setParseAction(removeQuotes)
call = expr + Suppress('(') + Optional(delimitedList(expr)) + Suppress(')')
name = World(alphas + '_', alphanums + '_')
expr <<= string | name | call

test = 'print("Hello World!")'
print(expr.parseString(test))

当我这样做时,它只是输出:
['print']

这在技术上是一个有效的expr - 你可以在REPL中输入它,而且没有问题解析它,即使它是无用的。

所以我想也许我想要的是在我的expr定义中翻转namecall,这样它会更喜欢返回call而不是name,就像这样:

expr <<= string | call | name

现在我收到了一个"最大递归深度超过限制"的错误信息。这也很合理:
- 检查它是否为 "expr"。
- 检查它是否为 "string",它不是。 - 检查它是否为 "call"。
- 它必须以 "expr" 开头,返回到外部列表的起点。
那么我的问题是... 我该如何定义 "call" 和 "expr",以便不会出现无限递归,但同时也不会在检测到名称时停止并忽略参数?
Python 代码对 PyParsing 来说太复杂了吗?如果不是,那么 PyParsing 可以处理的内容是否有限制?
(注意 - 我包含了一般标签, ,因为我怀疑这是一个普遍的递归语法定义问题,不一定是特定于的。)

解析任意(不仅限于Python)源代码实际上是很困难的;你必须处理许多问题,其中最小的问题是你的语言由哪些原子组成。一旦你“解析”了某个东西,那么在实践中你将没有足够的能力做很多有用的事情。请参阅我的个人简介中关于“解析之后的生活”的内容(其中也谈到了解析问题)。 - Ira Baxter
即使你只使用“AST和字符串”,对于现代语言来说,这个问题仍然非常困难,而且并不会变得更容易。例如,请参见https://dev59.com/6nVC5IYBdhLWcg3wnCSA#1004737 我会感到惊讶,如果你的意图仅限于那些“易于”解析的语言。 - Ira Baxter
@IraBaxter - 我想用Python作为基础构建一种新的语言。我的语言是Python的超集(也就是说,所有有效的Python代码都是我语言中有效的代码)。我将其解析为我的语言的AST。然后我的AST知道如何将自己转换为等效的Python AST,然后编译成.py Python字节码文件。我已经处理了我的AST-> Python AST问题,以及Python AST-> .pyc字节码文件问题 - 唯一剩下的桥梁是将我的代码解析为我的AST。 - ArtOfWarfare
所以你正在尝试从你的自定义方言翻译成纯Python。除非这种翻译在任何地方都是非常简单的,否则你需要更多的东西而不仅仅是一个AST。这是我的经验,祝你好运。 - Ira Baxter
1
我借用了你的问题(并注明来源),作为下一个Pyparsing版本中左递归解析器的示例,该版本支持左递归。您可以在此处找到它:https://github.com/pyparsing/pyparsing/blob/master/examples/left_recursion.py - PaulMcG
显示剩余3条评论
1个回答

2
你的语法是左递归的: expr 期望一个 call,而这个 call 又期望一个 expr,最后这个 expr 又期望一个 call... 如果 PyParsing 无法处理左递归,你需要将语法改成 PyParsing 可以处理的形式。
消除直接左递归的一种方法是更改语法规则,例如:
A = A b | c

转换为

A = c b*

在您的情况下,左递归是间接的:它不会在expr中发生,而是在一个子规则(call)中发生:
E = C | s | n
C = E x y z

为了消除间接左递归,通常需要将子规则的定义提升到主规则中。不幸的是,这会从语法中移除有问题的子规则,也就是说,这样做会失去一些结构表达能力。
消除间接递归后的前一个示例如下:
E = E x y z | s | n

此时,您有直接的左递归,这更容易转换。处理完后,结果将类似于以下内容 - 伪EBNF:

E = (s | n) (x y z)*

在您的情况下,Expr 的定义将变为:
Expr = (string | name) Args*
Args = "(" ExprList? ")"
ExprList = Expr ("," Expr)*

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接