解析的部分评估

时间:2009-01-23 19:59:02

标签: python parsing macros language-design

我正在研究Python的宏系统(as discussed here),我一直在考虑的事情之一是度量单位。虽然可以在没有宏或静态宏的情况下实现度量单位(例如,提前定义所有单元),但我正在考虑允许在运行时动态扩展语法。

为此,我正在考虑在编译时对代码进行一种部分评估。如果给定表达式的解析失败,由于其语法不可用的宏,编译器将停止对函数/块的评估,并生成已经具有未知表达式的存根的代码。在运行时命中此存根时,将针对当前宏集重新编译该函数。如果此编译失败,将抛出解析错误,因为执行无法继续。如果编译成功,则新函数将替换旧函数并继续执行。

我看到的最大问题是,在运行受影响的代码之前,您无法找到解析错误。但是,这不会影响很多情况,例如:像[],{},()和``这样的组操作符仍然需要配对(我的标记器/列表解析器的要求),并且像类和函数这样的顶级语法不会受到影响,因为它们的“运行时”是实际加载时间,评估语法并生成对象。

除了上面描述的实施难度和问题,这个想法有什么问题?

6 个答案:

答案 0 :(得分:3)

以下是一些可能的问题:

  • 如果出现问题,您可能会发现很难向用户提供有用的错误消息。这似乎很可能,因为任何编译时语法错误都只是语法扩展。
  • 表演热播。

我试图在Perl 6中找到关于动态解析的优缺点和/或实现的一些讨论,但我找不到合适的东西。但是,您可能会发现Nicklaus Wirth(Pascal和其他语言的设计者)的这句话很有趣:

  

计算机科学家的幻想   在20世纪60年代,没有任何限制。唾弃   通过自动语法的成功   一些分析和解析器生成   提出灵活的想法,或   至少是可扩展的语言。该   这个概念就是一个程序   之前是句法规则   然后会引导一般的解析器   在解析后续程序时。   更进一步:语法规则会   不仅仅是在程序之前,而是它们   可以穿插在任何地方   整篇文章。例如,如果   有人希望特别使用   花哨的私人形式的声明,   他甚至可以优雅地做到这一点   指定不同的变体   同一概念在不同的部分   同一个程序。这个概念   语言用于交流   人类已完全融合   显然,现在每个人都可以   动态定义自己的语言。   然而,很快就寄予厚望   受到遇到的困难   在试图指明时,这些是什么   私人建筑应该意味着。如   结果,这是一种无可非议的想法   可扩展语言逐渐消失   快。

编辑:这是Perl 6的Synopsis 6: Subroutines,遗憾的是标记格式,因为我找不到更新的格式化版本;在“宏观”中搜索。不幸的是,它不是有趣,但你可能会发现一些相关的东西,比如Perl 6的一遍解析规则,或者它的抽象语法树的语法。 Perl 6采用的方法是宏是一个在解析参数后立即执行并返回AST或字符串的函数; Perl 6继续解析,就好像源实际包含返回值一样。提到了错误消息的生成,但它们看起来好像宏返回AST,你可以做得好。

答案 1 :(得分:2)

进一步推进,你可以做“懒惰”解析,并且总是只解析足以评估下一个语句。像某种即时解析器。然后语法错误可能会成为正常的运行时错误,只会引发可由周围代码处理的正常异常:

def fun():
   not implemented yet

try:
  fun()
except:
  pass

这将是一个有趣的效果,但如果它有用或可取是一个不同的问题。通常,即使您此刻不调用代码,也很了解错误。

在控制到达它们之前不会评估宏,并且解析器自然会知道所有先前的定义。此外,宏定义甚至可以使用程序到目前为止计算的变量和数据(例如为先前计算的列表中的所有元素添加一些语法)。但是,开始为通常可以直接在语言中完成的事情编写自修改程序可能是一个坏主意。这可能会让人感到困惑......

在任何情况下,您都应确保仅解析一次代码,如果第二次执行该代码,则使用已解析的表达式,这样就不会导致性能问题。

答案 2 :(得分:1)

我考虑的另一件事是将其作为全面的默认行为,但允许语言(意味着一组宏来解析给定的语言)在编译时抛出一个解析错误。例如,我的系统中的Python 2.5就是这样做的。

而不是简单的想法,只需重新编译在编译时执行时无法完全处理的函数。这也将使自修改代码更容易,因为您可以修改代码并在运行时重新编译它。

答案 3 :(得分:1)

以下是我的硕士论文中的一些想法,这些想法可能有用也可能没有帮助。 论文是关于自然语言的强大解析。 主要思想:给定语言的无上下文语法,尝试解析给定的语法 文本(或者,在您的情况下,是一个python程序)。如果解析失败,您将获得部分生成的解析树。使用树结构来建议新的语法规则,以更好地覆盖已解析的文本。 我可以把你的论文发给你,但除非你读希伯来语,否则这可能没用。

简而言之: 我使用了bottom-up chart parser。这种类型的解析器根据语法生成边缘。每条边都标有所消耗的树的一部分。每个边缘根据与完全覆盖的接近程度得分,例如:

S -> NP . VP

得分为一半(我们成功覆盖了NP而不是VP)。 得分最高的边缘表示新规则(例如X-> NP)。 通常,图表解析器的效率低于普通的LALR或LL解析器(通常用于编程语言的类型) - O(n ^ 3)而不是O(n)复杂度,但是你再次尝试的东西比只是解析现有的语言。 如果您能对这个想法做些什么,我可以向您发送更多详细信息。 我相信看自然语言解析器可能会给你一些其他想法。

答案 4 :(得分:0)

您可能需要使用未知语法分隔输入文本的位,以便除了稍后将扩展的某些字符序列节点之外,还可以解析语法树的其余部分。根据您的顶级语法,这可能没问题。

您可能会发现解析算法和词法分析器以及它们之间的接口都需要更新,这可能会排除大多数编译器创建工具。

(更常用的方法是为此目的使用字符串常量,可以在运行时解析为一个小解释器。)

答案 5 :(得分:0)

我认为你的方法不会很好。我们来看一个用伪代码编写的简单例子:

define some syntax M1 with definition D1
if _whatever_:
    define M1 to do D2
else:
    define M1 to do D3
code that uses M1

所以有一个例子,如果你在运行时允许语法重新定义,你就会遇到问题(因为根据你的方法,使用M1的代码将由定义D1编译)。请注意,验证语法重新定义是否发生是不可判定的。可以通过某种类型的打字系统或其他类型的静态分析来计算过度近似,但Python并不为此所熟知:D。

困扰我的另一件事是你的解决方案“感觉”不正确。我觉得存储你无法解析的源代码是邪恶的,因为你可以在运行时解析它。

另一个想到的例子是:

...function definition fun1 that calls fun2...
define M1 (at runtime)
use M1
...function definition for fun2

从技术上讲,当你使用M1时,你无法解析它,所以你需要在源代码中保留程序的其余部分(包括fun2的函数定义)。当你运行整个程序时,你会看到一个你无法调用的fun2调用,即使它被定义了。