【问题标题】:Parse Python Code using PyParsing?使用 PyParsing 解析 Python 代码?
【发布时间】:2015-09-28 20:06:12
【问题描述】:

我正在尝试编写能够解析任何 Python 代码的 PyParsing 代码(我知道存在 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,所以它更愿意将calls 返回到names,如下所示:

expr <<= string | call | name

现在我得到一个最大递归深度超出错误。这也是有道理的:

  1. 检查是否为expr
    1. 检查是不是string,不是。
    2. 检查是否为call
      1. 它必须以expr 开头,返回到外部列表的开头。

所以我的问题是...我如何定义 callexpr 以便我不会以无限递归结束,而且它不会在看到名称时停止忽略论点?

Python 代码是否过于复杂,PyParsing 无法处理?如果没有,PyParsing 可以处理的内容是否有任何限制?

(注意 - 我已经包含了通用标签 ,因为我怀疑这是一个通用的递归语法定义问题,不一定特定于 。)

【问题讨论】:

  • 我的猜测是您的代码无法使用终端,因此会一遍又一遍地解析相同的标记,从而导致无限递归。我不知道 pyparsing,但是 PyParsing 主页上的这个 sample of a Python parser 确实会消耗已解析的终端。
  • 解析任意(“不仅仅是 Python”)源代码实际上很困难;您必须处理很多很多问题,其中最少的是原子构成了您的语言。一旦你“解析”了一些东西,那么你将没有足够的东西在实践中做很多有用的事情。请参阅我关于“解析后的生活”的简历(其中也谈到了解析问题)。
  • @IraBaxter - 解析后的生活假设我想做比我更复杂的事情。我正在寻找在 AST 和字符串之间来回切换的工具——仅此而已。我不想对它做任何类型的分析。此外,我需要以源代码形式免费分发所有内容 - 无论您的产品多么完美地满足我的需求,我都不会购买。
  • 我不是要你购买我的产品。我的重点是使用代码执行复杂的任务需要更多的“AST 和字符串”。 (仅在 AST 和字符串之间来回切换没有什么意义,对于多种语言,您到底在做什么,仅此而已?)。欢迎您以任何您喜欢的方式实现“更复杂”,包括从头开始构建它,或为其他一些开源解决方案做出贡献。我会建议这比你想象的要困难得多。
  • 即使你只坚持“AST 和字符串”,对于现代语言来说,这个问题仍然非常困难,而且不会变得更容易。例如,请参阅stackoverflow.com/questions/243383/… 如果您的意图仅限于那些“易于”解析的语言,我会感到惊讶。

标签: parsing abstract-syntax-tree bnf pyparsing python parsing abstract-syntax-tree pyparsing bnf


【解决方案1】:

您的语法是左递归的:expr 需要一个 call,它需要一个 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

此时,你有direct左递归,更容易转换。当你处理它时,结果将是这样的——在伪 EBNF 中:

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

在您的情况下,Expr 的定义将变为:

Expr = (string | name) Args*
Args = "(" ExprList? ")"
ExprList = Expr ("," Expr)*

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2013-10-19
    • 2015-05-29
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多