解析器组合器是否将单独的解析和词法传递为良好实践?


18

当我开始使用解析器组合器时,我的第一个反应是从感觉上像是在解析和词法化之间的人为区别中解放出来。突然所有的一切都只是解析!

但是,我最近在codereview.stackexchange上发现了帖子,说明有人在恢复这种区别。起初我以为这对他们很愚蠢,但是后来Parsec中存在支持这种行为的功能这一事实使我开始质疑自己。

在解析器组合器中对已经词法化的流进行解析有什么优点/缺点?


有人可以添加[parser-combinator]标签吗?
伊莱·弗雷

Answers:


15

在解析下,我们最常理解上下文无关语言的分析。上下文无关的语言比常规语言更强大,因此解析器可以(通常)立即执行词法分析器的工作。

但是,这是a)非常不自然的b)通常效率低下。

对于a),如果我考虑一个if表达式的外观,我认为IF expr THEN expr ELSE expr而不是'i'f',也许是空格,那么表达式可以以任何字符开头,依此类推。理念。

对于b),有强大的工具可以出色地识别词汇实体,例如标识符,文字,各种方括号等。它们几乎可以立即完成工作,并为您提供一个不错的界面:令牌列表。不再担心在解析器中跳过空格,当解析器处理令牌而不是字符时,解析器将更加抽象。

毕竟,如果您认为解析器应该忙于处理低级内容,那么为什么还要处理字符呢?一个人也可以写在位的水平!您会发现,这种在位级别上运行的解析器几乎是难以理解的。字符和标记相同。

只是我的2美分。


3
仅出于精度考虑:解析器始终可以完成词法分析器的工作。
Giorgio 2012年

另外,关于效率:我不确定解析器的效率是否会较低(较慢)。我希望生成的语法将包含描述常规语言的子语法,并且该子语法的代码将与相应的词法分析器一样快。IMO的真正意义是(a):使用更简单,更抽象的解析器是多么自然,直观。
Giorgio 2012年

@ Giorgio-关于您的第一条评论:对。我在这里想到的是词法分析器务实地进行一些使语法更容易的工作,以便人们可以使用LALR(1)而不是LALR(2)。
Ingo 2012年

2
经过进一步的试验和思考,我已不再接受您的回答。似乎你们两个来自安特尔(Antlr)等世界。考虑到解析器组合器的一流性质,我常常经常最终为令牌解析器定义一个包装解析器,而将每个令牌作为单个名称留在解析器的解析层中。例如你的if例子看起来像if = string "if" >> expr >> string "then" >> expr >> string "else" >> expr
伊莱·弗雷

1
性能仍然是一个悬而未决的问题,我将做一些基准测试。
Eli Frey 2012年

8

每个人都建议将词法分析与语法分析分开是一种“良好做法”,我不得不不同意-在许多情况下,单遍执行词法分析与语法分析可以提供更多的功能,并且性能影响不如本文中介绍的那么糟糕。其他答案(请参阅Packrat)。

当必须在一个输入流中混合使用多种不同的语言时,这种方法将大放异彩。这不仅是由像怪异的导向元编程语言需要卡塔丁一致好评,但对于更多的主流应用,以及像有文化的编程在注释中使用HTML,馅的Javascript成HTML(混合乳液,并说,C ++),以及以此类推。


在我的回答中,我建议这是“在某些情况下的良好实践”,而不是“在所有情况下的更好实践”。
Giorgio 2012年

5

词法分析器可识别常规语言,而解析器可识别上下文无关的语言。由于每种常规语言也是上下文无关的(可以通过所谓的右线性语法定义),因此解析器还可以识别常规语言,并且解析器和词法分析器之间的区别似乎增加了一些不必要的复杂性:单个上下文语法(解析器)可以完成解析器和词法分析器的工作。

另一方面,通过常规语言(因此是词法分析器)捕获上下文无关语言的某些元素可能很有用,因为

  1. 这些元素经常出现,以至于可以用一种标准的方式来处理:识别数字和字符串文字,关键字,标识符,跳过空白等等。
  2. 定义标记的常规语言可以简化所得的上下文无关语法,例如,可以根据标识符而不是单个字符进行推理,或者可以完全忽略空白(如果与该特定语言无关)。

因此,将分析与词法分析分开具有优势,您可以使用更简单的上下文无关文法,并在词法分析器(divide et impera)中封装一些基本(通常是常规)任务。

编辑

我对解析器组合器不熟悉,因此我不确定上述注意事项在这种情况下如何应用。我的印象是,即使使用解析器组合器只有一个上下文无关的语法,在两个级别之间进行区分(词法分析/解析)也可以帮助使这种语法更加模块化。如前所述,较低的词法分析层可以包含用于标识符,文字等的基本可重用解析器。


2
由于所有词法分析器都是基于正则表达式引擎构建的,因此Lexemes并非自然而然地属于常规语法。它限制了您可以设计的语言的表达能力。
SK-logic

1
您能否举一个适合定义无法被描述为常规语言的词素的语言示例?
乔治

1
例如,在我构建的几种特定于域的语言中,标识符可能是TeX表达式,它简化了代码的漂亮打印,例如,诸如\alpha'_1 (K_0, \vec{T})\ alpha'_1,K_0和\ vec {T}之类的表达式。是标识符。
SK-logic

1
给定与上下文无关的语法,您始终可以采用非结尾的N并将其派生的单词视为本身具有有用含义的单位(例如,表达式,术语,数字,语句)。无论您如何解析该单元(解析器,解析器+词法分析器等),都可以完成此操作。IMO选择解析器+词法分析器比技术上(语义上的意思是什么)更多地是技术性的(如何实现解析)。也许我正在忽略某些东西,但是这两个方面看起来与我正交。
乔治

3
因此,我同意你的看法:如果您定义了一些任意的基本构建基块(lexemes),并希望使用词法分析器来识别它们,则并非总是可能的。我只是想知道这是否是词法分析器的目标。据我了解,词法分析器的目标更多是技术性的:从解析器中删除一些底层的,繁琐的实现细节。
乔治

3

简而言之,应将词法分析和语法分析分开,因为它们是不同的复杂性。Lexing是DFA(确定性有限自动机),解析器是PDA(下推自动机)。这意味着与lexing相比,解析在本质上消耗更多的资源,并且只有DFA可以使用特定的优化技术。此外,编写有限状态机要简单得多,并且更容易实现自动化。

通过使用语法分析算法来浪费您的时间。


如果使用解析器进行词法分析,则PDA永远不会使用堆栈,它基本上可以用作DFA:仅消耗输入并在状态之间跳转。我不确定100%,但是我认为可以应用于DFA的优化技术(减少状态数)也可以应用于PDA。但是,是的:这样编写词法分析器而不使用更强大的工具会更容易,然后在它上面编写一个更简单的解析器。
乔治

另外,它使整个事情更加灵活和可维护。例如,假设我们有一个Haskell语言的解析器,但没有布局规则(即,带有分号和花括号)。如果我们有一个单独的词法分析器,我们现在可以通过对标记进行另一遍传递来添加布局规则,并根据需要添加花括号和分号。或者,举一个更简单的例子:假设我们以一种仅在标识符中支持ASCII字符的语言开始,现在我们希望在标识符中支持unicode字母。
Ingo 2012年

1
@Ingo,为什么还要在单独的词法分析器中执行此操作?只需考虑那些终端。
SK-logic

1
@ SK-logic:我不确定我是否理解你的问题。为什么我尝试在自己的文章中证实单独的词法分析器是一个不错的选择。
Ingo 2012年

乔治,不。堆栈是普通LALR样式解析器的重要组成部分。用解析器进行词法分析会浪费内存(静态存储和动态分配),并且速度会慢得多。Lexer / Parser模型是有效的-使用它:)
riwalk 2012年

1

单独的parse / lex的主要优点之一是中间表示-令牌流。可以以多种方式处理此问题,否则无法使用合并的lex / parse进行处理。

就是说,我发现,相对于学习一些解析器生成器,并且必须弄清楚如何在解析器生成器的规则内表达语法的弱点,良好的'ol递归体面操作可以更简单,更轻松地工作。


您能否解释一下有关语法的更多信息,这些语法在预制流中更容易表达,然后在解析时执行?我只有实现玩具语言的经验和很少的数据格式,所以也许我错过了一些东西。您是否注意到手动RD解析器/ lex组合和BNF馈入(假设)生成器之间的任何性能特征?
Eli Frey 2012年
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.