为什么你不应该使用(f)lex,yacc和野牛

2021-06-17 04:24:59

Lex和Yacc是第一个流行和高效的Lexers和Parsers发电机,Flex和Bison是与原始软件兼容的第一个广泛的开放源版本。这些软件中的每一个都有30多年的历史,这本身就是一项成就。对于一些人来说,这些仍然是他们在谈论解析时考虑的第一个软件。那么,为什么你应该避开它们?嗯,我们在为客户开发解析器的经验中找到了一些原因。

例如,我们必须使用Flex中的现有Lexers,发现难以添加现代功能,如Unicode支持或制作Lexer重新参与者(即,在许多线程中使用)。通过北美野牛,我们的客户无法组织大型码级,我们发现难以提高解析器的效率而不重写语法的大部分。简短的版本是有些工具,更灵活和富有成效,如Antlr。

本文的第一部分解释了这两个软件的历史,而第二个分析了缺陷。如果您不关心他们的历史,您可以使用此方便的目录前往第二部分。

当然,我们从一开始就开始。我们简要介绍了这些软件的历史。

虽然YACC的故事看起来像肥皂剧,但这两个软件都有一个有趣的历史。

Lex是一个Lexer生成器,也就是说一个生成词汇分析仪的工具。如果您不知道Lexer是什么,这些是基础知识。 Lexer接受为输入普通文本,它将其组织在相应的令牌中。您需要的只是一个语法,描述了要遵循的规则。因此,例如,您可以告诉LEX,即任何分组数字(即[0-9] +字符)应被分类为int。独立的Lexer很少使用,因为它基本上只是将一组字符组织成类别。它可以用于识别编程语言的元素,以执行语法突出显示的内容。您可以在文章中阅读有关解析的基础知识:解析的指南:算法和术语。

Mike Lesk和Eric Sc​​hmidt(后来成为谷歌首席执行官)在1975年开发了它作为专有软件在C中。它是一个重要的软件,既有质量,也是当时的发展需求。实际上它成为POSIX标准的一部分,基本上有任何可爱的操作系统需要这样的工具。

YACC是一个解析器生成器,专门用于生成LALR解析器的工具。基本上是一个解析器组(就像Lex生成的那样)进入逻辑结构。同样,您需要的只是一个描述要遵循的规则的语法。例如,您可以生成一个解析器,该解析器可以采用标识符(例如,变量),等于令牌和int令牌,并理解它们在一起形成分配。解析器涉及比Lexer更复杂的问题。

斯蒂芬约翰逊在20世纪70年代初期开发了YACC,在1973年至1978年作为专有软件之间编写(和重写)许多次。他在C中写了最新版本,尽管他在B中写道。它开始作为帮助发展B语言的实用工具。它不断改进,有效地帮助发展了解析的理论。约翰逊是一位计算机科学家,确保每个提高速度的技巧都是理论上的声音,并将在计算机科学中。这是您在当代软件中没有看到的奉献程度。 YACC也成为POSIX标准的一部分。

由于Lex用于生成Lexers和YACC来生成解析器,因此它们是互补的并且通常一起使用。它们只是他们各自的利基中提供的最好的软件。

如上所述,初始版本是专有软件。这意味着他们的使用受到限制,这让一些用户不满意。这就是为什么创建了两个软件的几个开源版本。这些软件通常与原始软件的语法兼容,但也增加了自己的改进。 LEX兼容的软件,获得了突出的是Flex,今天仍然使用。它使用BSD许可证并支持C ++。还有不同语言的Lex无数实施。

YACC有两个主要的替代品,伯克利YACC(重叠)和GNU野牛,第一是在公共领域,而另一个明显使用GPL许可证。这两个软件的原作者是罗伯特·科特贝特,令人困惑的是,特征最初是被称为野牛。此外,野牛并不是最初与YACC兼容,但它是由Ri​​chard Stallman兼容的,它在GNU世界中带来了它。与此同时,Corbett通过划痕重写CACC以与YACC兼容。该关系是最初是最受欢迎的YACC兼容软件版本,但现在是GNU北美野牛(仍然发展)。还有其他语言的北美野牛。

在许多方面,YACC是一种创新的软件,因为它对解析器的发展影响。 Lex不那么创新,但它也有很长的影响力。您会发现许多独立的Lexer生成器由Lex启发。这两者都是因为它的质量,因为没有对独立的失败者的需求。因此,Lex-Inspired Lexer Generators仍然足够好以满足常见用途。

甚至存在兼容的软件,并将LEX和YACC替换在一起,如Python。

现在介绍结束了,如果您有机会,我们可以看到为什么要避免这些工具。我们希望避免谈论最有可能和理想的事情,专注于今天的真正可行的东西。这就是为什么我们将使用现代解析器生成器:ANTLR比较Flex和Bison。

现在我们知道这些软件的历史,我们已经可以想象一些问题。这些都是非常古老的软件,以与较旧的软件兼容。更糟糕的是,他们的设计是基于真正的旧原则,这些原则不是今天不是最佳实践。你能想象30年写入和修复的代码质量吗?

他们的要点是,仍然是与旧码比的兼容性。简而言之,主要有三种情况需要考虑:

如果您使用Flex和Bison在维护模式下有现有解析器,则继续使用它们是有意义的;

如果您有积极开发的现有解析器,您应该考虑将它们移植到更高效的工具,将其移植的努力偿还更高的生产力;

如果您需要从头开始开发新的解析器,则使用它们的缺点太多,您应该选择更加富有成效的工具,如antlr

新功能的稳定性和开发Flex和Bison是稳定和维护的软件,但没有积极的发展。 C ++支持可以具有有限的质量。

语法和Code Antlr之间的分离是一款现代解析发电机工具,设计了一种设计,可用于便携式语法,可用于多种目标语言

Unicode支持Antlr确实支持所有unicode的样本,甚至可以易于选择Unicode属性(例如,表情符号)

它们使用不同的许可证Flex使用BSD许可证,而Bison使用GPL。北美野牛放弃了最生成的解析器的许可证,但这可能是一个问题

Lexing algIthms Flex的功能支持定期表达式来定义规则,它适用于大多数元素,但增加了复杂性

解析算法博尔的功能支持两个解析算法,涵盖所有性能和语言的范围。它给出了隐秘的错误消息

Flex和Bison是非常稳定的软件。他们被维持,但新功能的发展是有限的,如果不是缺席。此外,他们的代码是稳定的,但并不总是质量很大。如果您认为C和C ++是不同的语言,则尤其如此,但由于其兼容性,通常会被考虑并一起使用它们。让我给你一个例子,这是一个从Flex源代码直接评论:

*我们将在未来的Flex版本中解决此问题,或省略C ++扫描仪

来自https://github.com/westes/flex/blob/master/src/flex.skl#l127.

所以,是的,稳定的代码并不意味着良好的质量代码。如果你改变了一些你打破的东西,这对旧项目没有任何罕见。所以你离开它,但这正是旧软件并不总是有恒星声誉的原因。

反而更加积极开发。多年来,它已从划痕重新编写了几次,所以代码质量很好。此外,通常会定期添加新功能,从而提高解析和支持功能。这些特征可以很小改进,例如更好地支持根据属性选择Unicode字符。然而,它们也是显着提高开发人员的使用寿命的功能,例如对语义谓词的强大支持。其他添加对于项目的支持非常有用,例如用于文档目的的解析树的图。

Flex和Bison的设计是停滞不前的并且是原则上的。这部分是因为他们的杀手功能是兼容性,因此无法更改。它也是由于它们是两种不同的软件,必须共同努力,所以既不能单独发展。而是作为一个整体软件开发的Antlr,它们都作为Lexer和解析器工作,因此它有更改的自由。

Flex和Bison都仅支持包含动作的语法(即,用于操纵解析结果的代码在语法内)。另一方面的Antlr允许将语言的定义与处理结果的代码分开。

这种语法和正常代码之间这种分离的极大优点是允许重复使用语法。您可以使用其他项目重用语法或其他语言。这是可能的,因为只要语法不包含动作,Antlr支持以不同语法生成不同语言的解析器。这并不总是可能的,因为一些复杂的语言需要解析高级操作,但通常是可行的。这意味着要解析像Java这样的真实编程语言,您可能需要使用操作,但您不需要它们来解析JSON或平均DSL。

Antlr支持编写一个只包含语言描述的语法。可以在Flex和Bison中写入操作,但建议在可能的时候避免它。这允许编写一个清洁和更可理解的语法。存在关注的关注:语法描述了解析它的语言,而代码处理解析结果。

此外,嵌入在语法内的代码缺少上下文。虽然开发人员正在编辑语法,但它不明显,可以在每个规则中访问哪个参数。在语法手段中编写代码也没有来自IDE的支持:没有语法突出显示或自动完成功能。相反,在传统的源代码文件中,将代码与语法写入,允许利用通常的IDE功能。

我们将显示一项简单的例子,在北美和安特尔写的规则中,以了解如何与他们合作的感觉。

这是一个为北美野牛编写的典型简单规则。令牌数量在Flex(未示出)中定义。

抛开代码是一个密码,规则定义的组合和传统代码更难阅读和理解。想象一下,许多规则像这个漂浮在一起,穿插着长期的代码段。当您认为,在实际情况下,语法还必须包括管理通常的代码,名称空间,设置等。

Bison语法必须显然还包括代码来集成北美野牛内部的Flex令牌,因为Flex是一个单独的软件。

/ * Bison设置并链接到Flex令牌* /%令牌Num //我们表示该和使用左联想%左' +'

另一方面,与antlr,您可以在语法中编写像这样的简单规则。

然后,Antlr提供了两种简单的方法来操纵解析的结果:访客或侦听器。这些都是允许在遍历解析器创建的解析树时执行代码的模式。两者之间的区别在于,使用侦听器,树将自动遍历:您无法更改其路径。而是使用访问者您可以根据需要更改遍历路径。

代码在传统的源代码文件中,以及程序代码的其余部分。因此,更容易理解它,IDE可以像往常一样支持开发人员。

Flex并未自然支持Unicode。这是因为它只能读取8位的输入。虽然这并不是不可能识别Unicode字符,但这意味着Flex不会简单。开发人员必须创建自己的解决方案来识别Unicode字符。这需要大量工作,开发人员必须维护它。

Antlr全面支持Unicode,既有广泛的UTF8编码和其他与UTF16相同的。它还支持属性,可简化引用字符范围。例如,以下是有效的antlr代码,它创建一个符合所有表情符号字符的列出规则。

Flex和Bison是两个单独的软件。即使它们旨在一起工作,也有兼容性问题和功能不兼容。例如,Bison支持C,C ++和Java中的解析器的生成,而Flex仅支持C井。 Flex具有许可证许可证(BSD),而北美野牛则具有限制性许可证(GPL)。

在最简单的解析项目中,GPL许可证的限制不相关,因为不需要将Bison本身与生成的解析器一起包含。除此之外,北美野牛还为GPL提供了一个特殊的例外,以排除其在北美野牛生成的解析器中的适用性。但是,有一些高级用户需要包含解析器生成器。例如,提供了一种创建自定义语法突出显示规则的IDE可以使用解析器生成器。在这种情况下,许可证可能是一个问题。

北美野牛仅支持BNF等效格式来定义规则,而不是更灵活的EBNF。您可以在EBNF上查看我们的文章:如何描述语言的语法,以查看主要差异。简短的版本是语法在北极野牛中将更加详细和复杂化。例如,使用元素列表的规则必须以相当尴尬的方式定义。

这条规则正试图说一条线是一系列或多行。但是,由于BNF格式的局限性,它实际上说了更复杂的东西。它说规则线是一组线,后跟一行,这是一种递归方式,直到找到最后一行。您可以清楚地看到解析树的图形表示,以为一个简单的例子。

该规则实际上只是说规则线是一系列或多行。 Antlr在Lexer和Parser中支持此格式。

Flex支持正则表达式。 Antlr支持在Lexer规则的定义中提供无系统表达式。不同之处在于无背景表达式更强大,可以匹配更多规则。

Lexing通常比正确解析的第二阶段更简单,因此它需要更强大的算法。这意味着Flex是一种专用于LEXING的工具,比ANTLR严格更强大。这是因为Antlr是一个结合Lexing和解析的工具,因此它可以在两个阶段采用更强大的算法。

实际上,这意味着它更容易定义ANTLR中的复杂词汇结构。例如,您可以在Antlr中轻松定义递归词汇规则。

例如,这就是如何定义Flex中双引号之间的字符串的常用编程元素语言。

这条规则与A&#34匹配; (双引号),任何[^"]的任何情况(任何角色不是双引号),最后a"再次。

鉴于Lexer是棘手的情况下,这种柔性的这种限制产生了生产率的影响,并且更容易犯错误。这是因为你是字符的工作角色,没有任何上下文,所以更难以概念化正在发生的事情。

Flex和Antlr都支持一个功能,仅在某些条件下激活一些令牌的识别。此功能称为Antlr中的Flex和词汇模式中的状态。此功能允许创建一个解释多种语言的Lexer。例如,它允许构建一个Lexer来解析标记语言(例如,XML,HTML),其中包含免费文本和标记代码。

解析器生成器的定义特征是它用于创建解析器的算法。 Parsing的理论被计算机科学家广泛研究,开发了不同的解析算法,具有不同的优点和缺点。

Antlr使用其主要作者Terence Parr开发的算法。该算法称为所有(*)。这是对传统LL算法的改进,这不太强大。 LL算法更容易手动和强大的实现,以使其有用。因此,所以历史上,许多计算机语言被故障设计用于用LL算法解析。全(*)变体比基本LL算法更强大,可以处理您将遇到的大多数语言。

北美野牛支持使用两种算法的解析器:LALR和GLR。一个比Antlr使用的更快,更强大,另一个可能慢,但稍微强大(即它可以解析更多语言)。

实际上,所有(*)和GLR都可以解析开发人员遇到的大多数语言。唯一的区别是GLR可能在某些情况下允许在某些情况下编写更简单的规则。我们稍后会看到哪一个。

Antlr 4和Flex / Bison之间没有全面的性能比较,但是有一个科学纸,可以基于相同的算法比较一些解析器发生器。在以下段落中,我们展示了这些调查结果。

根据理论分析,ANTLR(即,所有(*))使用的新算法具有比GLR算法(O(n 3))更高的复杂性(O(n 4))。但是,在实际使用中,所有(*)都显示出比GLR更快。在Java 6源文件的语料库上进行的测试已发现:

在实践中,我们发现GLL和GLR在12,920个Java 6库源文件(123M)的语料库上的所有(*)慢,分别在单个3.2M Java文件中的6个级别的所有(*)上的所有(*)和6个幅度较慢。

[..]对于这个实验,所有(*)都优于其他解析器发生器,并且比Java编译器本身中的手工构建解析器慢的速度慢约20%

在任何情况下,两种算法都在生产系统中广泛使用,因此它们足够快,以便每天使用。所有(*)和LALR解析器的性能大致相同。

在继续讨论算法的力量之前,我们必须了解一个重要的区别。即,语法格式和解析算法的不同方式影响你写作语法的方式。您可以编写语法的格式限制了您可以用来描述您的语言的正式规则。它基本上是一种生产力问题:格式更强大,更容易写规则(例如,某些格式允许轻松使用重复)。我们已经看到了北美和禁区之间语法格式的差异,所以这部分应该清楚。相反,解析算法确定如何组合不同的规则或其部件。特定解析算法可能会产生不可能的规则组合。即,每个规则可能正式正确,但整个语法可能无效。

简单来说:语法格式确定规则的语法(例如

......