解析任意上下文无关的语法,主要是简短的摘要


20

我想解析用户定义的域特定语言。这些语言通常接近数学符号(我不是在解析自然语言)。用户以BNF表示法定义其DSL,如下所示:

expr ::= LiteralInteger
       | ( expr )
       | expr + expr
       | expr * expr

像输入1 + ( 2 * 3 )必须接受,而像输入1 +必须予以拒绝为不正确,并输入像1 + 2 * 3必须被拒绝暧昧。

这里的中心难题是以一种用户友好的方式处理模棱两可的语法。限制语法的唯一性不是一种选择:这就是语言的方式-想法是作者宁愿在不必要时避免使用括号来避免歧义。只要表达式不是模棱两可的,我就需要解析它,如果不是,我就必须拒绝它。

我的解析器必须能够处理任何与上下文无关的语法,即使是模棱两可的语法,也必须接受所有明确的输入。我需要所有接受的输入的分析树。对于无效或模棱两可的输入,理想情况下,我希望得到良好的错误消息,但首先,我将尽我所能。

通常,我将在相对较短的输入上调用解析器,而偶尔会有较长的输入。因此,渐近更快的算法可能不是最佳选择。我想针对少于80个符号长的输入,大约20%和50个符号之间的19%以及很少的较长输入的1%的分布进行优化。无效输入的速度不是主要问题。此外,我希望大约每1000至100000个输入都可以修改DSL。我可以花几秒钟来预处理我的语法,而不是几分钟。

给定我的典型输入大小,我应该研究哪种解析算法?错误报告应该成为我选择的一个因素,还是应该专注于解析明确的输入并可能运行一个完全独立的,较慢的解析器以提供错误反馈?

(在需要时(前一段时间)的项目中,我使用了CYK,实现起来并不难,并且可以很好地适应我的输入大小,但不会产生非常好的错误。)


特别好的错误报告似乎很难实现。在语法不明确的情况下,您可能会进行多个本地更改,从而导致可接受的输入。
拉斐尔

我在下面回答。回答已经有很好的答案的旧问题的修改有点尴尬。显然,我不应该以类似的方式回答,但是用户将阅读两个答案,就像他们在回答相同的问题一样。
2015年

如果用户输入,您是否真的期望输入错误的错误消息x+y+z
babou 2015年

@babou我没有更改问题,我只添加了注释中要求的澄清(现已删除)。对于此处给出的微小语法,我尚未指定的关联性+,因此x+y+z确实是模棱两可的,因此是错误的。
吉尔(Gilles)'所以别再邪恶了'

好吧,这是您的最后一句话,即使加上括号也是如此。您似乎在说:我终于用CYK做到了,但由于某些原因,它已不再足够。我想知道确切的原因可能是……您现在是在解决您的问题和使用的解决方案方面经验最丰富的人,因此,如果要给出进一步的答案,您可能会希望从您那里获得更多信息。
2015年

Answers:


19

可能最理想的算法是通用LL解析或GLL。这是一个非常新的算法(该论文于2010年发表)。从某种意义上讲,它是用图形结构堆栈(GSS)增强并使用LL(1)超前的Earley算法。

该算法与普通旧版LL(1)非常相似,不同之处在于,它不会拒绝语法不是LL(1)的语法:它只是尝试所有可能的LL(1)解析。它对解析中的每个点都使用一个有向图,这意味着,如果遇到之前已处理过的解析状态,则只需合并这两个顶点即可。与LL不同,这使得它甚至适用于左递归语法。有关其内部工作原理的确切详细信息,请阅读该论文(虽然汤标签需要一定的毅力,但它还是可读性很强的论文)。

与其他一般解析算法(据我所知)相比,该算法具有许多与您的需求相关的明显优势。首先,实施非常容易:我认为只有Earley易于实施。其次,性能相当不错:实际上,在LL(1)的语法上,它的速度与LL(1)一样快。第三,恢复解析非常容易,并且检查是否存在多个可能的解析也是很容易的。

GLL的主要优点是它基于LL(1),因此在实现,设计语法以及解析输入时非常易于理解和调试。此外,它还使错误处理更加容易:您确切地知道可能的解析滞留在何处以及它们可能如何继续。您可以轻松地在错误点给出可能的解析,例如解析滞留的最后3个点。相反,您可能会选择尝试从错误中恢复,并将最靠前的解析正在进行的生产标记为该解析为“完成”,然后查看解析是否可以在此之后继续(例如有人忘记了括号)。您甚至可以这样做,例如获得最远的5个解析。

该算法的唯一缺点是它是新算法,这意味着尚无成熟的实现可用。对于您来说,这可能不是问题-我自己实现了该算法,而且操作起来非常容易。


很高兴学习新东西。当我需要此功能时(几年前,在一个我想复兴的项目中),我使用了CYK,主要是因为它是我发现的第一个算法。GLL如何处理歧义输入?这篇文章似乎没有讨论这一点,但是我只是略过了。
吉尔斯(Gillles)“所以-别再邪恶了”

@Gilles:它建立了一个图结构化的堆栈,并且所有(可能成倍增长的)解析都在该图中紧凑地表示,类似于GLR的工作方式。如果我没记错的话,在cstheory.stackexchange.com/questions/7374/…中提到的论文可以解决这个问题。
亚历克斯十布林克

@Gilles这个2010解析器似乎必须通过语法手动编程,如果您使用多种语言,或者您经常修改该语言,则该语法不足。遵循任何选择的策略(LL,LR或其他策略)从通用解析器的语法自动生成并生成所有解析器的林的技术已有40年了。但是,存在关于表示解析的图的复杂性和组织性的隐藏问题。解析次数可能比指数更糟:无限。错误恢复可以使用更系统的,与解析器无关的技术。
2014年

GLL与ANTLR中的LL(*)有何关系?
拉斐尔

6

我的公司(语义设计)非常成功地使用了GLR解析器,通过我们的DMS软件再造工具包,可以准确地执行OP在解析特定领域语言和解析“经典”编程语言中所提出的建议。这支持用于大规模程序重组/逆向工程/正向代码生成的源到源程序转换。这包括以相当实用的方式自动修复语法错误。使用GLR作为基础,以及其他一些更改(语义谓词,令牌集输入,而不仅仅是令牌输入,...),我们设法为40种语言构建了解析器。

与解析完整语言实例的能力一样重要,GLR在解析源到源重写规则中也被证明非常有用。这些程序片段的上下文要比完整程序少得多,因此通常具有更大的歧义。我们使用特殊的注释(例如,坚持一个短语对应于特定的语法非终结符)来帮助解析规则期间/之后解决这些歧义。通过组织GLR解析机制及其周围的工具,一旦有了针对其语言的解析器,我们就可以获取用于“免费”重写规则的解析器。DMS引擎具有内置的重写规则应用程序,然后可以将其用于应用这些规则来执行所需的代码更改。

尽管有很多歧义,但使用无上下文语法作为基础,解析最完整的C ++ 14可能是我们最壮观的结果。我注意到所有经典的C ++编译器(GCC,Clang)都放弃了执行此操作并使用手写解析器的功能(恕我直言,这使它们难以维护,但这不是我的问题)。我们已经使用这种机器对大型C ++系统的体系结构进行了大规模更改。

在性能方面,我们的GLR解析器相当快:每秒数万条线。这远远低于现有技术,但是我们并未认真尝试对其进行优化,并且某些瓶颈在于字符流处理(完整Unicode)。为了构建这样的解析器,我们使用与LR(1)解析器生成器非常接近的东西对上下文无关文法进行预处理。通常,它可以在十分钟之内在现代工作站上运行,且语法只有C ++。令人惊讶的是,对于非常复杂的语言(例如现代COBOL和C ++),生成词法器的过程大约需要一分钟。某些通过Unicode定义的DFA显得有些毛茸茸。我只是将Ruby(带有令人难以置信的正则表达式的完整子语法)用作手指练习。DMS可以在大约8秒内一起处理其词法分析器和语法。


@Raphael:“大量更改”链接指向一组学术风格的技术论文,其中包括一些有关C ++架构重新设计的文章,关于DMS引擎本身的文章(虽然年代久远,但描述得很好),以及设计捕获和重用的奇异主题,这是DMS的最初动机(不幸的是,仍然没有实现,但是事实证明DMS仍然非常有用)。
Ira Baxter

1

有许多通用的无上下文解析器可以解析歧义句子(根据歧义语法)。它们以各种名称命名,尤其是动态编程或图表解析器。最著名的,也是最简单的,可能是您一直在使用的CYK解析器。这种通用性是必需的,因为您必须处理多个解析,并且可能直到最后才知道您是否在处理歧义。

从您的意见来看,我认为CYK并不是一个糟糕的选择。通过添加预测性(LL或LR),您可能没有太多收获,并且通过区分应该合并而不是区分的计算(尤其是在LR情况下),实际上可能会付出一定的代价。在生成的解析林的大小中,它们也可能具有相应的成本(这可能会导致歧义错误)。实际上,虽然我不确定如何正式比较更复杂算法的适用性,但我确实知道CYK确实提供了良好的计算共享。

现在,我不相信有太多关于通用CF解析器的文献,这些语法分析器只应接受明确的输入。我不记得看到任何内容,可能是因为即使对于技术文档,甚至编程语言,只要能够通过其他方法(例如ADA表达式中的歧义)解决它,语法歧义也是可以接受的。

我实际上在想为什么您要更改算法,而不是坚持使用现有算法。这可能有助于我了解哪种变化最能为您提供帮助。是速度问题,还是解析的表示形式,还是错误检测和恢复?

表示多个解析的最好方法是使用共享林,这是一个无上下文的语法,仅生成您的输入,但解析树与DSL语法完全相同。这使得它很容易理解和处理。有关更多详细信息,建议您查看我在语言站点上给出的答案。我确实了解您对获取解析森林不感兴趣,但是正确表示解析森林可以帮助您更好地传达歧义是什么。如果您想这样做,它还可以帮助您确定歧义在某些情况下(关联性)并不重要。

您提到了DSL语法的处理时间限制,但没有提示它的大小(这并不意味着我可以用您所做的数字来回答)。

可以通过简单的方式将一些错误处理集成到这些常规CF算法中。但是我需要了解您希望哪种错误处理更加肯定。请问一些例子。

我不敢多说,因为我不了解您的动机和制约因素到底是什么。根据您所说的内容,我会坚持使用CYK(我确实知道其他算法及其某些属性)。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.