词法分析者与解析者

时间:2010-05-16 06:47:39

标签: parsing antlr lexer pygments

词法分析器和解析器在理论上真的不同吗?

讨厌正则表达式似乎很时髦:coding horroranother blog post

但是,基于lexing的常用工具:pygmentsgeshiprettify都使用正则表达式。他们好像有什么东西......

什么时候足够兴奋,什么时候需要EBNF?

有没有人使用这些词法分析器生成的令牌与bison或antlr解析器生成器?

5 个答案:

答案 0 :(得分:449)

答案 1 :(得分:97)

是的,它们在理论和实施方面都有很大不同。

Lexers习惯于识别构成语言元素的“单词”,因为这些单词的结构通常很简单。正则表达式非常擅长处理这个更简单的结构,并且有非常高性能的正则表达式匹配引擎用于实现词法分析器。

解析器用于识别语言短语的“结构”。这种结构通常远远超出“正则表达式”可以识别的结构,因此需要 “上下文敏感”解析器提取这样的结构。上下文相关的解析器 难以构建,因此工程方面的妥协是使用“无上下文”语法 并向解析器添加黑客(“符号表”等)以处理上下文相关的部分。

lexing和解析技术都不会很快消失。

通过决定使用“解析”技术来识别“单词”,可以 统一,正如目前所谓的无扫描GLR解析器所探索的那样。这有一个运行时成本,因为您将更多通用机器应用于通常不需要它的问题,通常您需要在开销中支付。如果你有很多自由周期,那么开销可能无关紧要。如果您处理大量文本,那么开销就很重要,并且将继续使用经典的正则表达式解析器。

答案 2 :(得分:30)

  

什么时候足够兴奋,什么时候需要EBNF?

EBNF确实没有为语法的 power 增加太多。它只是标准的乔姆斯基普通形式(CNF)语法规则的便利/快捷符号/ “语法糖”。例如,EBNF替代方案:

S --> A | B

您可以通过单独列出每个替代产品来实现CNF:

S --> A      // `S` can be `A`,
S --> B      // or it can be `B`.

EBNF的可选元素:

S --> X?

你可以通过使用 nullable 生产在CNF中实现,也就是说,可以用空字符串替换它(在这里只用空生产表示;其他使用epsilon或lambda或交叉圆圈):

S --> B       // `S` can be `B`,
B --> X       // and `B` can be just `X`,
B -->         // or it can be empty.

上面最后一个B形式的制作被称为“擦除”,因为它可以删除其他制作中的任何形式(产品是空字符串而不是其他产品)。

来自EBNF的零次或多次重复:

S --> A*

你可以通过使用递归生产,也就是将其自身嵌入其中的生产。它可以通过两种方式完成。第一个是左递归(通常应该避免,因为自上而下的递归下降解析器无法解析它):

S --> S A    // `S` is just itself ended with `A` (which can be done many times),
S -->        // or it can begin with empty-string, which stops the recursion.

知道它只生成一个空字符串(最终)后跟零个或多个A s,可以用但不是同一种语言!) >右递归:

S --> A S    // `S` can be `A` followed by itself (which can be done many times),
S -->        // or it can be just empty-string end, which stops the recursion.

当涉及到来自EBNF的一次或多次重复+时:

S --> A+

可以通过将A分解出来并使用*来完成:

S --> A A*

你可以在CNF中表达(我在这里使用正确的递归;试着找出另一个你自己作为练习):

S --> A S   // `S` can be one `A` followed by `S` (which stands for more `A`s),
S --> A     // or it could be just one single `A`.

知道这一点,你现在可以识别正则表达式的语法(即常规语法),它可以在仅由终端符号组成的单个EBNF生成中表示。更一般地说,当您看到与以下类似的作品时,您可以识别常规语法:

A -->        // Empty (nullable) production (AKA erasure).
B --> x      // Single terminal symbol.
C --> y D    // Simple state change from `C` to `D` when seeing input `y`.
E --> F z    // Simple state change from `E` to `F` when seeing input `z`.
G --> G u    // Left recursion.
H --> v H    // Right recursion.

也就是说,只使用空字符串,终端符号,简单的非终端进行替换和状态更改,并且仅使用递归来实现重复(迭代,这只是线性递归 - 一个不分枝树状)。没有比这更高级的东西了,那么你肯定这是一个常规的语法,你可以选择lexer。

但是当你的语法以非平凡的方式使用递归时,要产生类似树的,自相似的嵌套结构,如下所示:

S --> a S b    // `S` can be itself "parenthesized" by `a` and `b` on both sides.
S -->          // or it could be (ultimately) empty, which ends recursion.

然后您可以很容易地看到这不能通过正则表达式完成,因为您无法以任何方式将其解析为单个EBNF生成;你最终将无限期地替换S,这将始终在双方添加另一个ab。词汇表(更具体地说:词法分析器使用的有限状态自动机)不能算作任意数字(它们是有限的,记得吗?),所以他们不知道有多少a可以将它们与这么多b均匀匹配{1}}秒。像这样的语法被称为无上下文语法(至少),它们需要一个解析器。

无上下文语法是众所周知的解析,因此它们被广泛用于描述编程语言的语法。但还有更多。有时候需要一个更通用的语法 - 当你有更多的东西可以同时计算,独立时。例如,当您想要描述一种语言,其中可以使用圆括号和方括号交错,但它们必须彼此正确配对(带括号的大括号,圆形的圆形)。这种语法称为上下文敏感。您可以通过左侧(箭头前)有多个符号来识别它。例如:

A R B --> A S B

您可以将左侧的这些附加符号视为应用规则的“上下文”。可能存在一些先决条件,后置条件等。例如,上述规则将R替换为S,但仅当它位于AB之间时,保留A {1}}和B本身没有变化。这种语法很难解析,因为它需要一台成熟的图灵机。这是另一个故事,所以我会在这里结束。

答案 3 :(得分:11)

按要求回答问题(不要过度重复出现的问题 其他答案)

词汇表和解析器并没有太大不同,正如所建议的那样 接受了答案。两者都基于简单的语言形式:常规 词法分析器的语言,几乎总是无上下文(CF)语言 对于解析器。它们都与相当简单的计算相关联 模型,有限状态自动机和下推堆栈自动机。 常规语言是无上下文语言的特例,因此 词法分析器可以用更复杂的CF生成 技术。但至少有两个原因,这不是一个好主意

编程的一个基本点是系统组件应该 用最合适的技术来装,这样很容易 生产,理解和维护。技术不应该 矫枉过正(使用比所需更复杂,更昂贵的技术), 也不应该达到其权力的极限,因此需要技术 扭曲以达到预期目标。

这就是为什么"讨厌正则表达式似乎很时髦"。 虽然他们可以做很多事情,但他们有时需要非常难以理解 编码实现它,更不用说各种扩展的事实 实施中的限制在一定程度上减少了他们 简单。 Lexers通常不这样做,通常很简单, 高效且适当的技术来解析令牌。使用CF解析器 虽然有可能,令牌会有点矫枉过正。

不鼓励使用CF形式主义的另一个原因是它可能 然后很想使用完整的CF电源。但这可能会提高 有关阅读课程的重大问题。

从根本上说,程序文本的大部分结构都来自哪个 意思是提取,是树结构。它表达了如何解析 句子(程序)是从语法规则生成的。语义是 通过组合技术得到的(同态性) 从语法规则的编写方式到数学 构建解析树。因此树结构是必不可少的。 事件是使用基于常规集的词法分析器识别令牌 不会改变这种情况,因为CF还是经常组成的 给CF(我说的是常规换能器非常松散,那 将字符流转换为令牌流。)

然而,CF由CF组成(通过CF传感器......对不起 数学),不一定给CF,并可能使事情更多 一般而言,但在实践中不易处理。所以CF不合适 词法分析器的工具,即使它可以使用。

常规和CF之间的主要区别之一是常规 语言(和传感器)与几乎任何语言组合得非常好 形式主义以各种方式,而CF语言(和传感器) 不是,甚至没有自己(除了少数例外)。

(注意常规换能器可能有其他用途,例如 一些语法错误处理技术的形式化。)

BNF只是用于呈现CF语法的特定语法。

EBNF是BNF 的一种语法糖,使用常规设施 给出更快版本的BNF语法的符号。它总是可以的 转化为等效的纯BNF。

然而,EBNF中经常使用常规符号来强调这些 与词法结构相对应的语法部分 元素,应该与词法分析器一起识别,而其余的用 宁可以直接的BNF呈现。但这不是一个绝对的规则。

总结一下,可以更好地分析令牌的简单结构 常规语言的简单技术,而树导向 语言(程序语法)的结构由CF更好地处理 语法。

我建议也看一下AHR's answer

但这留下了一个问题:为什么是树?

树是指定语法的良好基础,因为

  • 他们为文本提供了一个简单的结构

  • 将语义与文本相关联非常方便 在这个结构的基础上,数学上很好 理解技术(通过同态的组合性),as 如上所述。它是定义数据的基本代数工具 数学形式主义的语义学。

因此它是一个很好的中间表示,如图所示 摘要语法树(AST)的成功。注意AST通常是 与解析树不同,因为许多人使用的解析技术 专业人士(如LL或LR)仅适用于CF的子集 语法,因此迫使语法扭曲,这是后来的 用AST校正。通过更一般的解析可以避免这种情况 接受任何CF语法的技术(基于动态编程)。

关于编程语言的事实的陈述 上下文敏感(CS)而不是CF是任意和有争议的。

问题在于语法和语义的分离 随心所欲。检查声明或类型协议可能被视为 语法的一部分或语义的一部分。同样的情况也是如此 自然语言中的性别和号码协议。但是有自然的 复数一致性取决于实际语义的语言 单词的含义,因此它不适合语法。

指称语义中的编程语言的许多定义 在语义中放置声明和类型检查。如此陈述 由Ira Baxter完成CF解析器被黑客攻击以获取上下文 语法所需的灵敏度充其量只是一个任意的视图 情况。它可能被组织为一些编译器中的黑客,但它 不必是。

此外,不仅CS解析器(在此处的其他答案中使用的意义上)难以构建,而且更少 高效。他们也不足以表达出明显的表达 可能需要的上下文敏感性的kinf。他们没有 自然地产生一个句法结构(如解析树) 方便地导出程序的语义,即生成 编译后的代码。

答案 4 :(得分:6)

编译器的分析部分通常有很多原因 分为词法分析和解析(语法分析)阶段。

  1. 简单的设计是最重要的考虑因素。词法和句法分析的分离通常允许我们简化这些任务中的至少一个。例如,一个必须处理注释和空格作为语法单元的解析器。词法分析器已经删除了可以假设注释和空格的复杂程度。如果我们正在设计一种新语言,将词汇和句法问题分开可以使整体语言设计更清晰。
  2. 编译器效率得到提高。一个单独的词法分析器允许我们应用专门的技术,只用于词法任务,而不是解析工作。此外,用于读取输入字符的专用缓冲技术可以显着加快编译器的速度。
  3. 增强了编译器的可移植性。特定于输入设备的特性可以限制在词法分析器中。
  4. 资源___ 编制者(第2版) 写的 - Alfred V. Abo 哥伦比亚大学 Monica S. Lam 斯坦福大学 拉维塞西 AVAYA 杰弗里D.乌尔曼 斯坦福大学