lexers vs解析器


308

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

讨厌正则表达式似乎很时髦:编码恐怖另一篇博客文章

但是,流行的基于词法分析的工具:pygmentsgeshiprettify都使用正则表达式。他们似乎什么都没法...

什么时候足够词汇,何时需要EBNF?

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


2
是。我正在尝试解析autohotkey。我真的可以使用pygments来构建语法突出显示工具。但是antlr花费的时间更长……在这两种工具之间我还没有看到很多异花授粉。
纳文

67
讨厌正则表达式被滥用时,这是唯一的时髦。当需要上下文无关的解析时,许多人尝试使用正则表达式。他们总是失败。他们指责正则表达式技术。这就像抱怨您的锤子是一把破锯一样。是的,但是您不会得到太多同情。
艾拉·巴克斯特

2
幸运的是,我开始使用antlr加快速度。很多词法是与上下文无关的,有时甚至取决于上下文。
纳文

1
词法分析器与解析器问题的一个基本方面是,词法分析器基于有限自动机(FSA),或更精确地说是基于有限换能器(FST)。在与FSA交叉或FST应用时,大多数解析形式主义(不仅是上下文无关)都是封闭的。因此,将更简单的基于正则表达式的形式主义用于词法分析器不会增加更复杂的解析器形式主义的句法结构的复杂性。当定义语言的结构和语义时,这绝对是一个主要的模块化问题,被高投票答案高兴地忽略了。
babou 2014年

应当指出的是,词法分析器和解析器不具有有所不同,如LLLPG和早期版本的ANTLR使用相同的LL(k)的解析系统既词法分析器和解析器。主要区别在于,正则表达式通常对于词法分析器就足够了,但对于语法分析器而言却不足。
Qwertie

Answers:


475

解析器和词法分析器的共同点是:

  1. 他们从输入中读取某些字母的符号

    • 提示:字母不一定必须是字母。但是它必须是解析器/词法分析器理解的语言的原子符号。
    • 词法分析器的符号:ASCII字符。
    • 解析器的符号:特定标记,是其语法的终端符号。
  2. 他们分析这些符号,并尝试将其与他们所理解的语言的语法相匹配。

    • 真正的区别通常就在这里。有关更多信息,请参见下文。
    • lexers理解的语法:常规语法(Chomsky的3级)。
    • 解析器理解的语法:上下文无关的语法(Chomsky的2级)。
  3. 他们将语义(含义)附加到找到的语言片段上。

    • 词法分析器通过将词素(输入中的符号字符串)分类为特定标记来附加含义。例如,所有的这些语意:*==<=^将被划分为“运营商”,由C / C ++词法记号。
    • 解析器通过将来自输入(语句)的令牌字符串分类为特定的非终结符并构建解析树来附加含义。例如,所有这些令牌字符串:[number][operator][number][id][operator][id][id][operator][number][operator][number]将被分类为“表达”非终结由C / C ++解析器。
  4. 它们可以将一些附加含义(数据)附加到已识别的元素上。

    • 当词法分析器识别出组成适当数字的字符序列时,可以将其转换为其二进制值并与“数字”标记一起存储。
    • 类似地,当解析器识别出一个表达式时,它可以计算其值并将其存储在语法树的“表达式”节点中。
  5. 他们都在输出中产生他们认可的语言的正确句子

    • 词法分析器产生令牌,这是他们认可的普通语言的句子。每个标记都可以具有内部语法(尽管是3级,而不是2级),但这对于输出数据和读取它们的无关紧要。
    • 解析器产生语法树语法树是他们识别的上下文无关语言句子的表示。通常,对于整个文档/源文件来说,这只是一棵大树,因为整个文档/源文件对他们来说都是恰当的句子。但是,没有任何理由使解析器无法在其输出中生成一系列语法树。例如,它可能是一个解析器,可以识别粘贴到纯文本中的SGML标签。因此,它将SGML文档标记为一系列标记:。[TXT][TAG][TAG][TXT][TAG][TXT]...

如您所见,解析器和令牌生成器有很多共同点。一个解析器可以是其他解析器的标记器,该解析器从其自己的字母表中将输入标记作为符号读取(标记只是某些字母的符号),就像一种语言的句子可以是其他更高级别的字母符号一样语言。例如,如果*-是字母符号M(如“摩尔斯电码符号”),则可以构建一个解析器,将这些点和线的字符串识别为摩尔斯电码编码的字母。在语言的“莫尔斯电码”的句子可能是令牌其他一些解析器,对于这些令牌是其语言(例如“英语单词”语言)的原子符号。这些“英语单词”可能是一些高级解析器的标记(字母的符号),这些解析器可以理解“英语句子”的语言。而所有这些语言的语法的复杂性唯一有所不同。而已。

那么,这些“乔姆斯基语法水平”到底是什么呢?好吧,Noam Chomsky根据语法的复杂程度将其分为四个级别:

  • 第3级:常规语法

    他们使用正则表达式,也就是说,它们只能包含字母的符号(ab),它们的级联(abababbbETD),或者替代品(如a|b)。
    它们可以实现为有限状态自动机(FSA),例如NFA(非确定有限自动机)或更好的DFA(确定性有限自动机)。
    常规语法无法使用嵌套语法处理,例如正确嵌套/匹配的括号(()()(()())),嵌套HTML / BBcode标签,嵌套块等。这是因为要处理它的状态自动机必须具有无限多个状态才能处理无限多个嵌套级别。
  • 第2级:无上下文语法

    它们可以在语法树中具有嵌套的,递归的,自相似的分支,因此它们可以很好地处理嵌套结构。
    它们可以实现为带有堆栈的状态自动机。该堆栈用于表示语法的嵌套级别。实际上,它们通常被实现为自上而下的递归下降解析器,该解析器使用机器的过程调用堆栈来跟踪嵌套级别,并对语法中的每个非终端符号使用递归调用的过程/函数。
    但是它们无法使用上下文相关的语法进行处理。例如,当您有一个表达式,x+3并且在一个上下文中,它x可能是变量的名称,而在另一个上下文中,它可能是函数的名称,等等。
  • 级别1:上下文相关的语法

  • 级别0:无限制语法
    也称为递归可枚举语法。


70
哦耶?那么,这些“单词或标记”是什么?它们只是普通语言的句子,由字母组成。解析器中的那些“构造”或“树”是什么?它们也是句子,但使用不同的高级语言,为此特定标记是字母符号。区别不在于您所说的,而在于所用语言复杂性。使用有关解析理论的任何手册来面对-1。
SasQ

3
@SasQ可以说Lexers和Parsers都接受一些语法和一系列标记作为输入吗?
Parag 2012年

4
这么。他们俩都从他们认识的字母中提取了一系列符号。对于词法分析器,此字母仅由普通字符组成。对于解析器,字母由终端符号组成,无论它们定义了什么。如果您不使用lexer并使用单字符标识符和一位数字等(在开发的第一阶段非常有用),它们也可以是字符。但是它们通常是标记(词汇类),因为标记是一个很好的抽象:您可以更改它们所代表的实际词素(字符串),而解析器看不到更改。
2012年

6
例如,您可以STMT_END在语法中(对于解析器)使用终端符号来表示指令的末尾。现在,您可以拥有由词法分析器生成的具有相同名称的令牌。但是您可以更改其代表的实际词素。例如。您可以定义STMT_END;具有类似C / C ++的源代码。或者,您可以将其定义为end某种程度上类似于Pascal样式。或者,您可以将其定义为仅以'\n'行尾结束指令,例如在Python中。但是指令(和解析器)的语法保持不变:-)仅需更改词法分析器。
2012年

24
维基百科和Google的工作时间没有帮助,但是您在3分钟内解释了乔姆斯基的语法。谢谢。
enrey 2013年

107

是的,它们在理论和实现上都大不相同。

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

解析器用于识别语言短语的“结构”。这种结构通常远远超出“正则表达式”所能识别的范围,因此需要“上下文敏感”解析器来提取这种结构。上下文敏感的解析器很难构建,因此工程上的折衷方案是使用“无上下文”语法,并向解析器(“符号表”等)添加hack,以处理上下文敏感的部分。

词汇化和解析技术都不会很快消失。

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


40
很好的解释,艾拉。增加您的类比:虽然词法分析器是要使单词正确,但解析器是要使句子正确。就词法分析器而言,“ See spot run”和“ spot run See”都是有效的。它需要一个解析器来确定短语结构是错误的(英语语法)。
艾伦(Alan)2010年

我想解析器是词法分析器,就像树助行器是解析器。我不相信这种理论是不同的:antlr.org/wiki/display/~admin/ANTLR+v4+lexers,但我开始理解它们之间的约定差异……
Naveen 2010年

4
理论是非常不同的。大多数解析器技术都在某种程度上尝试处理上下文无关的语言(某些仅作用于一部分,例如LALR,有些则全部起作用,例如GLR)。大多数词法分析器技术仅尝试做正则表达式。
伊拉·巴克斯特

3
该理论是不同的,因为它是由许多不同的人提出并使用不同的术语和算法的。但是,如果仔细观察它们,您会发现相似之处。例如,左递归问题与NFA中的非确定性问题非常相似,而删除左递归与删除非确定性并将NFA转换为DFA相似。令牌是令牌生成器(输出)的句子,但解析器(输入)是字母符号。我不否认差异(Chomsky级别),但是相似性在设计上有很大帮助。
SasQ

1
我的上班族涉足范畴理论。他展示了滑轮的分类理论概念如何涵盖各种模式匹配,并能够从抽象的分类规范中得出LR解析。因此,实际上,如果足够抽象,便可以找到这样的共性。范畴论的要点是,您通常可以将“一路向上”抽象化;我相信您可以构建一个类别理论解析器来消除差异。但是它的任何实际用途都必须实例化到特定的问题域,然后差异才是真实的。
Ira Baxter

32

什么时候足够词汇,何时需要EBNF?

EBNF真的没有多地增加电力语法。它只是标准Chomsky的Normal Form(CNF)语法规则的便利/快捷方式符号/ “语法糖”。例如,EBNF替代方案:

S --> A | B

您可以通过仅列出每个替代产品来在CNF中实现:

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

EBNF中的可选元素:

S --> X?

您可以在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.

知道它只会生成一个空字符串(最终),然后是零个或多个As,因此可以使用right-recursion表示相同的字符串(但不是相同的语言!):

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将无限期替换,这将始终在两边都添加as和bs。词法分析器(更具体地说:词法分析器使用的有限状态自动机)不能计数为任意数(它们是有限的,还记得吗?),所以他们不知道a有多少s来与它们均匀匹配b。像这样的语法被称为上下文无关语法(至少是),它们需要解析器。

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

A R B --> A S B

您可以将左侧的这些其他符号视为应用规则的“上下文”。可能有一些先决条件,后置条件等。例如,上述规则将替换RS,但仅当它在A和之间时B,才使它们AB它们自身保持不变。这种语法确实很难解析,因为它需要一台成熟的图灵机。这完全是另一个故事,所以我在这里结束。


1
您声明EBNF只是“在标准Chomsky的范式(CNF)语法规则之上的一种便利/快捷方式表示法/“语法糖”。但是CNF与手头的话题几乎没有任何关系。EBNF可以轻松转换为标准BNF。期。它是标准BNF的语法糖。
babou 2014年

11

回答所要求的问题(无需过度重复其他答案中出现的内容)

正如公认的答案所建议的,词法分析器和语法分析器并没有太大的区别。两者都基于简单的语言形式主义:用于词法分析器的常规语言,以及几乎总是用于语法分析器的上下文无关(CF)语言。它们都与相当简单的计算模型(有限状态自动机和下推堆栈自动机)相关联。常规语言是无上下文语言的一种特殊情况,因此可以使用稍微更复杂的CF技术来生成词法分析器。但这不是一个好主意,至少有两个原因。

编程的基本要点是,系统组件应采用最合适的技术,以便易于生产,理解和维护。该技术不应该过分杀人(使用比所需技术更复杂,成本更高的技术),也不应处于其功能极限,因此需要技术上的扭曲才能实现所需的目标。

这就是为什么“讨厌正则表达式似乎很流行”的原因。尽管它们可以做很多事情,但有时它们需要非常不易读的代码才能实现,更不用说各种扩展和实现限制在某种程度上降低了它们的理论简单性。词法分析器通常不这样做,通常是一种简单,有效且合适的技术来解析令牌。尽管有可能,但使用CF解析器获取令牌可能会过大。

对词法分析器不使用CF形式主义的另一个原因是,然后可能会很想使用全部CF功能。但这可能会引起程序阅读方面的结构性问题。

从根本上讲,从中提取含义的程序文本的大多数结构都是树结构。它表示如何从语法规则生成解析语句(程序)。语义是通过合成技术(针对数学的同态)从构成语法规则以构建解析树的方式得出的。因此,树结构是必不可少的。使用基于常规集的词法分析器识别令牌的事实不会改变这种情况,因为用常规组成的CF仍然可以提供CF(我在讲常规换能器时讲得很松散,将字符流转换为令牌流)。

但是,由CF与CF组成的CF(通过CF换能器……数学上的遗憾),不一定提供CF,并且可能使事情变得更笼统,但在实践中却难以处理。因此,即使可以使用CF,它也不是适合词法分析器的工具。

常规语言和CF语言之间的主要区别之一是,常规语言(和转换器)几乎可以以各种方式与任何形式主义完美地结合在一起,而CF语言(和转换器)则不然,甚至与它们自身也不一样(少数例外)。

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

BNF只是表示CF语法的一种特定语法。

EBNF是BNF的语法糖,它使用常规表示法的功能来提供BNF语法的更简短版本。它始终可以转换为等效的纯BNF。

但是,在EBNF中经常使用常规符号来强调语法的这些部分,这些部分与词法元素的结构相对应,并应由词法分析器识别,而其余部分应以纯BNF形式呈现。但这不是绝对的规则。

总之,使用常规语言的简单技术可以更好地分析令牌的简单结构,而CF语法则可以更好地分析(程序语法)语言的树形结构。

我建议也查看AHR的答案

但这留下了一个悬而未决的问题:为什么要树木?

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

  • 他们给文本一个简单的结构

  • 如上所述,使用数学上很好理解的技术(通过同构的可组合性)将语义与文本关联起来非常方便。它是定义数学形式主义语义的基本代数工具。

因此,它是一个很好的中间表示形式,如抽象语法树(AST)的成功展示。请注意,AST通常与语法分析树不同,因为许多专业人员(例如LL或LR)使用的语法分析技术仅适用于CF语法的子集,因此会导致语法失真,以后会在AST中进行纠正。可以通过接受任何CF语法的更通用的解析技术(基于动态编程)来避免这种情况。

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

问题在于语法和语义的分离是任意的。检查声明或类型协议可能被视为语法的一部分或语义的一部分。自然语言中的性别和数量协议也是如此。但是,在自然语言中,复数约定取决于单词的实际语义,因此它与语法不太吻合。

指称语义中的许多编程语言定义都在语义中声明和类型检查。因此,正如Ira Baxter所做的那样,说CF解析器被黑以获取语法所需的上下文敏感度,充其量只是对情况的任意看法。在某些编译器中,它可能被组织为黑客,但并非必须如此。

同样,不仅仅是CS解析器(在此其他答案中使用的意思)难以构建且效率较低。它们也不足以明显地表达可能需要的上下文相关性。而且,它们自然不会产生便于导出程序语义(即生成已编译代码)的语法结构(例如,语法分析树)。


是的,解析树和AST有所不同,但是几乎不是一种真正有用的方式。请参阅我对此的讨论:stackoverflow.com/a/1916687/120163
Ira Baxter,

@IraBaxter我不同意您的意见,但是我现在真的没有时间为您的帖子起草一个清晰的答案。基本上,您采用务实的观点(我认为还捍卫自己的系统)。这甚至更容易,因为您正在使用通用CF解析器(但是GLR可能不是最有效的),而不是像某些系统中那样使用确定性解析器。我认为AST是参考表示形式,它适合于正式定义的处理方式,可证明的正确变换,数学证明,对多种具体表示形式的
解析

“实用”的观点是我声称它们在有用的方式上并没有很大不同的原因。而且我简直不相信使用(临时AST)会给您“证明正确的变换”;您的临时AST与正在处理的语言的实际语法没有明显的关系(在这里,是的,我的系统是可以辩护的,因为我们的“ AST”同等地等同于BNF)。特设AST的不给你任何额外的能力unparse“多重具体表现)你反对GLR(不是最有效的)似乎很没有意义它们也不是不确定的。
艾拉巴克斯特

因此,事实上,我不理解您反对我的评论的任何部分。您必须编写“干净答案”。
艾拉·巴克斯特

@IraBaxter注释过于局限,无法提供正确的答案(建议?)。我主张“ ad hoc”不是AST的合适限定词,它应该(有时是)参考语法。从计算机科学中AST概念的历史以及分类代数中作为术语(树)的形式系统的历史以及解释的角度来看,这在历史上都是真实的。AST是参考形式,而不是派生形式。另请参见现代证明系统和自动程序生成。您必须从别人设计的具体语法中进行工作,这可能会使您产生偏见。
babou

7

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

  1. 设计的简单性是最重要的考虑因素。词汇和句法分析的分离通常使我们能够简化至少一项任务。例如,必须将注释和空白作为语法单位处理的解析器。比可以假定注释和空格的词法分析器要复杂得多。如果我们正在设计一种新的语言,那么将词汇和句法关注点分开可以使整体语言设计更加简洁。
  2. 编译器效率得到提高。单独的词法分析器使我们可以应用仅用于词法任务而不是语法分析工作的专门技术。此外,用于读取输入字符的专用缓冲技术可以大大提高编译器的速度。
  3. 编译器的可移植性得到增强。可以将特定于输入设备的特性限制为词法分析器。

resource___ 编译器(第2版),作者:Alfred V. Abo哥伦比亚大学Monica S. Lam斯坦福大学Ravi Sethi Avaya Jeffrey D. Ullman斯坦福大学

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.