词法分析器和解析器在理论上真的有很大不同吗?
但是,流行的基于词法分析的工具:pygments,geshi或prettify都使用正则表达式。他们似乎什么都没法...
什么时候足够词汇,何时需要EBNF?
有没有人将这些词法分析器生成的令牌与bison或antlr解析器生成器一起使用?
词法分析器和解析器在理论上真的有很大不同吗?
但是,流行的基于词法分析的工具:pygments,geshi或prettify都使用正则表达式。他们似乎什么都没法...
什么时候足够词汇,何时需要EBNF?
有没有人将这些词法分析器生成的令牌与bison或antlr解析器生成器一起使用?
Answers:
解析器和词法分析器的共同点是:
*
,==
,<=
,^
将被划分为“运营商”,由C / C ++词法记号。[number][operator][number]
,[id][operator][id]
,[id][operator][number][operator][number]
将被分类为“表达”非终结由C / C ++解析器。[TXT][TAG][TAG][TXT][TAG][TXT]...
如您所见,解析器和令牌生成器有很多共同点。一个解析器可以是其他解析器的标记器,该解析器从其自己的字母表中将输入标记作为符号读取(标记只是某些字母的符号),就像一种语言的句子可以是其他更高级别的字母符号一样语言。例如,如果*
和-
是字母符号M
(如“摩尔斯电码符号”),则可以构建一个解析器,将这些点和线的字符串识别为摩尔斯电码编码的字母。在语言的“莫尔斯电码”的句子可能是令牌其他一些解析器,对于这些令牌是其语言(例如“英语单词”语言)的原子符号。这些“英语单词”可能是一些高级解析器的标记(字母的符号),这些解析器可以理解“英语句子”的语言。而所有这些语言的语法的复杂性唯一有所不同。而已。
那么,这些“乔姆斯基语法水平”到底是什么呢?好吧,Noam Chomsky根据语法的复杂程度将其分为四个级别:
a
,b
),它们的级联(ab
,aba
,bbb
ETD),或者替代品(如a|b
)。(()()(()()))
,嵌套HTML / BBcode标签,嵌套块等。这是因为要处理它的状态自动机必须具有无限多个状态才能处理无限多个嵌套级别。x+3
并且在一个上下文中,它x
可能是变量的名称,而在另一个上下文中,它可能是函数的名称,等等。STMT_END
在语法中(对于解析器)使用终端符号来表示指令的末尾。现在,您可以拥有由词法分析器生成的具有相同名称的令牌。但是您可以更改其代表的实际词素。例如。您可以定义STMT_END
为;
具有类似C / C ++的源代码。或者,您可以将其定义为end
某种程度上类似于Pascal样式。或者,您可以将其定义为仅以'\n'
行尾结束指令,例如在Python中。但是指令(和解析器)的语法保持不变:-)仅需更改词法分析器。
是的,它们在理论和实现上都大不相同。
词法分析器通常用于识别构成语言元素的“单词”,因为此类单词的结构通常很简单。正则表达式非常擅长处理这种简单的结构,并且有非常高性能的正则表达式匹配引擎用于实现词法分析器。
解析器用于识别语言短语的“结构”。这种结构通常远远超出“正则表达式”所能识别的范围,因此需要“上下文敏感”解析器来提取这种结构。上下文敏感的解析器很难构建,因此工程上的折衷方案是使用“无上下文”语法,并向解析器(“符号表”等)添加hack,以处理上下文敏感的部分。
词汇化和解析技术都不会很快消失。
通过决定使用“解析”技术来识别“单词”,可以将它们统一起来,正如所谓的无扫描器GLR解析器目前正在探索的那样。这具有运行时成本,因为您将更多的通用机器应用于通常不需要的问题上,通常您需要为开销付费。如果您有很多空闲周期,那么开销可能并不重要。如果您处理大量文本,那么开销确实很重要,经典的正则表达式解析器将继续使用。
什么时候足够词汇,何时需要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.
知道它只会生成一个空字符串(最终),然后是零个或多个A
s,因此可以使用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
将无限期替换,这将始终在两边都添加a
s和b
s。词法分析器(更具体地说:词法分析器使用的有限状态自动机)不能计数为任意数(它们是有限的,还记得吗?),所以他们不知道a
有多少s来与它们均匀匹配b
。像这样的语法被称为上下文无关语法(至少是),它们需要解析器。
上下文无关的语法是众所周知的解析方法,因此它们被广泛用于描述编程语言的语法。但是还有更多。有时需要更通用的语法-当您需要独立地同时计算更多内容时。例如,当您要描述一种语言时,可以使用圆括号和交错的方括号,但必须将它们正确地配对(大括号的括号,圆的括号)。这种语法称为上下文相关。您可以通过在左侧(箭头之前)有多个符号来识别它。例如:
A R B --> A S B
您可以将左侧的这些其他符号视为应用规则的“上下文”。可能有一些先决条件,后置条件等。例如,上述规则将替换R
为S
,但仅当它在A
和之间时B
,才使它们A
和B
它们自身保持不变。这种语法确实很难解析,因为它需要一台成熟的图灵机。这完全是另一个故事,所以我在这里结束。
回答所要求的问题(无需过度重复其他答案中出现的内容)
正如公认的答案所建议的,词法分析器和语法分析器并没有太大的区别。两者都基于简单的语言形式主义:用于词法分析器的常规语言,以及几乎总是用于语法分析器的上下文无关(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解析器(在此其他答案中使用的意思)难以构建且效率较低。它们也不足以明显地表达可能需要的上下文相关性。而且,它们自然不会产生便于导出程序语义(即生成已编译代码)的语法结构(例如,语法分析树)。
将编译器的分析部分通常分为词法分析和语法分析(语法分析)阶段的原因有很多。
resource___ 编译器(第2版),作者:Alfred V. Abo哥伦比亚大学Monica S. Lam斯坦福大学Ravi Sethi Avaya Jeffrey D. Ullman斯坦福大学