编程语言,正则表达式和形式语言之间的关系是什么


25

我在网上四处寻找这个问题的答案,似乎每个人都隐含地知道答案,除了我。据推测,这是因为唯一关心的人是受过该学科高等教育的人。另一方面,我却被高中作业深深吸引。

我的问题是,编程语言与形式语言到底有什么关系?在我读过的任何地方,都说着类似“形式语言用于定义编程语言语法”的内容。

现在,根据我的能力,正式语言是一系列适用于一组特定符号(该语言的字母)的生产规则。这些生产规则定义了一组转换,例如:

b -> a

aaa->c

可以这样应用:

abab->aaaa aaaa-> ca

顺便提一句,如果我们将形式语言的字母定义为{a,b,c},则a和b是非终结符,而c是终结符,因为它不能转换(如果我错了,请纠正我那)。

那么,考虑到所有这些,这到底在编程语言中有什么用呢?通常还会指出,正则表达式用于解析其文本形式的语言,以确保语法正确。这很有道理。然后说明正则表达式是由正式语言定义的。正则表达式返回true或false(至少以我的经验),这取决于代表正则表达式的有限状态自动机是否达到目标点。据我所知,这与转换无关。

对于程序本身的编译,我认为一种形式语言可以将代码转换为连续的较低级别的代码,并最终通过一组复杂的规则到达汇编,然后硬件可以理解这些规则。

从我的困惑来看,这就是事实。我所说的内容可能有很多根本上的错误,这就是为什么我要寻求帮助。


*除非您认为某条(a|b)*b*c->true规则类似于生产规则,否则在这种情况下该规则需要一个有限状态自动机(即regex)。就像我们刚才说的那样,这没有任何意义


2
您正在将形式语法与形式语言混淆。一个语法是一组描述语言重写规则。语言是语法描述的一组字符串。因此,语法可以替代正则表达式:它是描述语言的一种方式。
reinierpost 2014年

@reinierpost完全正确,在浏览大学讲义后,我从中获得了一些信息,我发现了自己的错误。
2014年

一开始我就分享了您的困惑。当然,语法也构成语言,正则表达式也是如此。但是形式语言理论致力于研究如何描述语言的语法(形式),因此它通常将术语“语言”用于描述的内容,而不是描述的内容。
reinierpost 2014年

Answers:


24

谁告诉您使用正则表达式解析代码的人散布了虚假信息。传统上(我不知道现代编译器在多大程度上适用),代码的解析(即从文本到语法树的代码转换)由两个阶段组成:

  1. 词法分析:将原始文本处理成例如关键字数字常量字符串标识符等。传统上,这是使用某种有限状态机实现的,其本质上与确定性有限自动机(DFA)相似。

  2. 解析器:在词法分析之后运行,并将原始文本转换为带注释的语法树。编程语言的语法是(无近似的)上下文无关的(实际上,它需要一个甚至更严格的子集),并且这允许某些有效的算法将词汇化的代码解析为语法树。这类似于识别给定字符串是否属于某种与上下文无关的语法的问题,不同之处在于我们还希望采用语法树形式的证明

编程语言的语法是作为无上下文语法编写的,解析器生成器使用此表示形式为其构建快速解析器。一个简单的示例将具有一些非终结符STATEMENT,然后是STATEMENT IF-STATEMENT 形式的规则,其中IF-STATEMENT 如果CONDITION则为BLOCK endif,等等(例如,其中BLOCK STATEMENT | BLOCK; STATEMENT)。通常,这些语法以Backus-Naur形式(BNF)指定。

编程语言的实际规范不是上下文无关的。例如,如果没有用多种语言声明变量,则该变量将不会出现,并且严格键入的语言可能不允许您将整数分配给字符串变量。解析器的工作只是将原始代码转换成易于处理的形式。

我应该提到,还有其他一些方法,例如递归下降解析,它实际上并不生成解析树,而是在解析代码时对其进行处理。尽管它不麻烦生成树,但在所有其他方面,它都在如上所述的相同级别上运行。


感谢您的答复,它肯定清除了一些问题。它还带来了更多的问题。我应该将它们附加到我的问题中,还是在这里问他们?
2014年

5
@Zwander-实际上,两者都不是。我们希望您在此站点上为每个问题写一个问题。这不是讨论论坛:这是一个问答网站,我们希望每个问题都在一个单独的主题中。如果此答案提出了一个新问题,请花一些时间研究该后续问题,如果您在任何标准来源中都找不到答案,请发布一个新问题。(但请务必确保首先查看标准资源。)
DW

1
@DW Gotcha,加油。
Zwander 2014年

3
您提到的两个阶段中的第一阶段通常是使用正则表达式完成的。每个令牌的格式通常由正则表达式给出。这些正则表达式被编译成单个DFA,然后将DFA应用于实际代码。
kasperd 2014年

2
@Zwander递归下降解析只是一种解析技术。它可能会生成解析树,也可能不会生成。通常,解析算法相当于开发一种计算策略来探索程序文本中隐含的语法树。根据编译策略(阶段数),此语法/分析树可能在过程中或不在过程中被明确显示。但是,有必要进行的是,至少对解析树进行至少一个自下而上的探索,无论是在计算结构中是显式的还是隐式的。
宝贝

12

对于高中作业,这是沉重的工作。

Yuval Filmus的回答确实很好,所以这更多是一个补充性的回答,以阐明他的观点。

形式语言是一种数学构造。它们在编程语言中的用途只是众多可能用途中的一种。实际上,语言学家Noam Chomsky对形式语言的早期理论做出了重大贡献。他发明了乔姆斯基体系,将形式语言分为常规语言,无上下文语言等。形式语言也用于语言学中,以描述自然语言(如英语)的语法。将其视为实数:我们可以使用实数来描述具体事物,例如从洛杉矶到纽约的距离,以及抽象事物,例如圆的周长与其直径之比。即使这两个事物都与实数无关地存在,但实数是用于描述它们的有用系统。形式语言是用于描述英语和Python的有用系统,因为两者都有相似的结构化格式。

a+b+c=da+b=dcabc 例如以美元为单位,则等式具有意义。

传统上,一种编程语言将具有两种语法:词汇语法和句法语法。词汇语法处理诸如字母,分号,花括号和括号之类的字符。它通常是常规语法,因此可以使用正则表达式或DFA或NFA表示。(形式语言理论中的证据表明,这三种语言在功能上是等效的,这意味着它们接受相同的语言集。)编译器或解释器的词法翻译阶段类似于常规语言语法的迷你解释器。它读取语法规则,并遵循这些规则,将单个字符集中为记号。例如,如果语言中的某条if语句看起来或多或少都像C一样,则词法分析器可能会把字符if成单个标记IF,然后寻找开括号并输出令牌。当词法分析器制作完标记后,它将它们交给解析器,该解析器确定标记是否实际上形成了编程语言的有效语句。因此,如果您使用Python 编写,那么词法分析器会尽力猜测是哪种令牌(大多数词法分析器可能会将其用作标识符),然后将其传递给解析器,该解析器会抱怨,因为您无法拥有该位置的标识符。OPEN_PAREN,然后处理括号之间的所有内容,然后找到右括号并输出 CLOSE_PARENip a == bip

解析器实现了语法语法,该语法通常是无上下文的,尽管正如Yuval的答案所提到的那样,当今大多数编程语言实际上并未充分利用无上下文语法的全部功能来使解析更简单,更高效。这是Python的语法语法,以Backus-Naur形式的变体编写,这是的略微变体ab OP中格式。的 Java语言规范还拥有Java的词法和句法语法的例子。

我们来看一下Python if语句的语法规则。这是规则:

if_stmt: 'if' test ':' suite ('elif' test ':' suite)* ['else' ':' suite]

该规则告诉我们解析器将如何确定从词法分析器发送来的令牌字符串是否是if-statement。就像这样,单引号中的任何单词都需要出现在源代码中,因此解析器将查找纯单词if。然后,解析器将尝试将一些令牌与以下规则匹配test

test: or_test ['if' or_test 'else' test] | lambdef

test是根据语法中的其他规则定义的。注意如何test自身包括在其定义中;这就是所谓的递归定义。这是常规语言所不具备的上下文无关语言的强大功能,并且它允许为编程语言语法定义诸如嵌套循环之类的东西。

如果解析器设法将某些令牌与匹配test,它将尝试匹配冒号。如果成功,它将使用的规则尝试匹配更多令牌suite。该部分的('elif' test ':' suite)*意思是我们可以对文字文本进行任意数量的重复elif,其次是匹配的内容test,然后是冒号,然后是匹配的内容suite。我们也可以有零次重复;最后的星号表示“零或任意多”。

最后是['else' ':' suite]。该部分用方括号括起来;这意味着我们可以有零或一,但不能再有其他。为此,解析器需要匹配文字文本else,冒号,然后匹配suite。这是a的规则suite

suite: simple_stmt | NEWLINE INDENT stmt+ DEDENT

从本质上讲,这是类似C的语言中的一个块。由于Python使用换行和缩进来平均的东西,词法分析器输出NEWLINEINDENT以及DEDENT标记来告诉解析器新行在哪里开始,在哪里开始缩进代码以及在哪里返回到外部缩进级别。

如果这些匹配尝试均失败,则解析器会标记错误并停止。如果整个程序的解析成功,则解析器通常会建立一个解析树,如Yuval覆盖在他的答案中,并可能建立符号表和其他存储语义信息的数据结构。如果该语言是静态类型的,则编译器将遍历解析树并查找类型错误。它还会遍历解析树以生成实际运行的低级代码(汇编语言,Java字节码,.Net中间语言或类似内容)。

作为练习,我建议您采用一些您熟悉的编程语言(再次是PythonJavaC#JavascriptC)的语法,并尝试手工解析一些简单的东西,例如may x = a + b;if (True): print("Yay!")。如果您正在寻找更简单的方法,那么JSON还有一个不错的语法,它基本上只涵盖Javascript中的对象文字的语法(例如{'a': 1, 'b': 2})。祝您好运,这真是令人难以置信的事情,但是当您不在一个疯狂的截止日期时,它真的很有趣。


我知道我不应该在这里张贴“谢谢”,但是为花些时间解释所有这些而欢呼。“这对于高中作业来说是沉重的负担。” 作业的目的是浏览顶部并讨论正则表达式,但是作为一个狂热的计算机科学专业的学生,​​我想了解整个情况。整个主题令人着迷。
Zwander 2014年

1
@Zwander我刚刚大学毕业,我的大部分选修课都是这样的。我记得自己完全困惑,却完全专注。您可能还会喜欢本博客中提到的有关编译器设计的论文,或者是Michael Sipser和John C. Martin所著的《计算理论入门》一书,《语言和计算理论入门》。您可以在亚马逊上找到便宜的二手书。两者都使形式语言理论变得尽可能简单。
tsleyson 2014年

10

简而言之

编程语言由将程序表示为字符串的语法和作为程序预期含义的语义组成。

形式语言是没有意义的语法。它旨在研究正式定义的字符串集的结构,而通常无需在这些字符串上附加含义。

正则表达式和其他形式主义(例如,上下文无关文法)用于定义形式语言,用作编程语言和自然语言的语法组成部分,即以结构化方式表示句子。其他机制用于将该结构与编程语言的语义相关联。

此处已大大简化,尤其是在自然语言方面。

还有更多细节

要回答您的问题,我们应该从头开始。通常,一种语言非正式地是一种传达信息或思想的手段。在一种语言中,通常可以区分语法和语义。语义就是您要谈论/编写的内容。您要传达的信息。语法是用于传达语法的手段,即可以在人与人之间交换的常规表示形式,现在也可以在人与设备之间或在设备(计算机)之间交换。

通常,您会用这个词dog来表达狗的概念。这个单词dog是由三个字母或等效的声音组成的,意在表示某种动物。关键思想是通过表示要传达的内容来进行交流。表示结构通常称为语法,而表示的结构称为语义。对于自然语言以及编程语言,这或多或少都适用。

单词是代表或多或少基本语义概念的句法实体。但是必须以多种方式将这些基本概念组合在一起,以赋予更复杂的含义。我们写 the dog来传达我们的意思是指特定的狗,并the dog bites the cat传达更复杂的想法。但是单词的组织方式必须由规则确定,以便我们可以分辨出狗和猫中的哪一个实际上在咬对方。

因此,我们有这样的规则sentence -> subject verb complement,应该匹配句子并告诉我们如何表达与每个部分相关的想法。这些规则是语法规则,因为它们告诉我们如何组织消息的表示形式。该subject可以通过自身的规则来定义subject -> article noun,等等。

2x+1=23x123

equation -> expression "=" expression  
expression -> expression "+" expression 
expression -> number

编程语言的结构是相同的。编程语言在语义上专用于表达要执行的计算,而不是表达要解决的问题,定理或动物之间的友好关系的证明。但这是主要区别。

语法中使用的表示形式通常是字符串或口语的声音。语义通常属于抽象领域,或者可能属于现实,但是仍然在我们的思维过程中抽象,或者属于设备的行为领域。通信需要将信息/想法编码为语法,然后由接收器发送和解码。然后,接收器以任何方式解释结果。

因此,我们看到的语言主要是语法及其结构。上面的示例只是定义语法字符串及其结构组织的最常用方法之一。还有其他 对于给定的语言,可以为某些字符串分配结构,并说它们属于该语言,而其他则不属于。

单词也是如此。某些字母(或声音)序列是合法单词,而其他则不是。

形式语言只是没有语义的语法。它们使用一组规则定义使用字母的基本元素可以构造哪些序列。规则是可变的,有时很复杂。但是,形式语言除了用于语言交流之外,还用于许多数学目的,无论是编程语言的自然目的。定义语言中的字符串的规则集称为语法。但是还有许多其他定义语言的方法。

实际上,一种语言分为两个层次。词汇级别定义了由字符字母构成的单词。句法级别定义了由单词字母(或更确切地说单词系列,以使其保持为有限字母)构成的句子或程序。这一定程度上简化了。

在大多数语言(编程语言或自然语言)中,单词的结构都非常简单,因此通常使用通常被认为是最简单的形式语言:常规语言来定义它们。可以使用正则表达式(regexp)定义它们,并且可以通过称为有限状态自动机的编程设备轻松识别它们。在使用编程语言的情况下,单词的示例是标识符,整数,字符串,实数,保留单词,例如ifrepeat,标点符号或空心括号。单词族的示例是标识符,字符串,整数。

语法级别通常由形式语言的一种稍微复杂一些来定义:上下文无关的语言,使用单词作为字母。我们上面看到的规则是自然语言的无上下文规则。对于编程语言,规则可以是:

statement -> assignment
statement -> loop
loop ->  "while" expression "do" statement
assignment -> "identifier" "=" expression
expression -> "identifier"
expression -> "integer"
expression -> expression "operator" expression

有了这样的规则,您可以编写:

while aaa /= bbb do aaa = aaa + bbb / 6 这是一个声明。

它的产生方式可以由称为解析树或语法树的树结构表示(此处不完整):

                          statement
                              |
            _______________  loop _______________
           /      /                 \            \
      "while" expression           "do"       statement
       __________|_________                       |
      /          |         \                  assignment
 expression "operator" expression          _______|_______
     |           |          |             /       |       \
"identifier"   "/="   "identifier" "identifier"  "="   expression
     |                      |            |                 |
    aaa                    bbb          aaa             ... ...

规则左侧出现的名称称为非终结符,而单词也称为终结符,因为它们在语言的字母中(在词汇级别之上)。非终结表示不同的语法结构,可用于组成程序。

这样的规则被称为上下文无关的,因为非终结符可以使用任何相应规则任意替换,而与出现的上下文无关。定义语言的规则集称为上下文无关文法。

实际上,在必须先声明标识符或表达式必须满足类型限制的情况下,这是有限制的。但是这种限制可以被认为是语义上的,而不是语法上的。实际上,有些专业人员将它们置于所谓的 静态语义中

给定任何句子,任何程序,通过分析该句子的分析树所给出的结构来提取该句子的含义。因此,开发称为解析器的算法非常重要,当给定程序时,该算法可以恢复与该程序相对应的树结构。

语法分析器之前是词法分析器,后者可识别单词并确定单词所属的族。然后,将单词或词汇元素的序列提供给解析器,该解析器检索基础树结构。然后,通过这种结构,编译器可以确定如何生成代码,这是他在编译器端处理程序的语义部分。

编译器的解析器实际上可以构建与解析树相对应的数据结构,并将其传递到编译过程的后续阶段,但不必这样做。运行解析算法相当于开发一种计算策略,以探索隐含在程序文本中的语法树。根据编译策略(阶段数),此语法/分析树可能在过程中或未在过程中被明确显示。但是,必需的是,最终至少对解析树进行了一种自下而上的探索,无论是在计算结构中是显式的还是隐式的。

直观地讲,其原因是定义与句法树结构相关的语义的标准形式方法是借助所谓的同态性。不要害怕这个大词。这个想法只是在连接它们的运算符的基础上考虑整体含义的含义,这些含义是由各个部分的含义构成的

例如,the dog bites the cat可以使用规则分析句子sentence -> subject verb complement。知道3个子树subjectverb和的含义complement,组成它们的规则告诉我们对象正在做动作,而猫是被咬的那只。

这只是一个直观的解释,但是可以形式化。语义是从构成要素向上构造的。但这隐藏了很多复杂性。

编译器的内部工作可以分解为几个阶段。实际的编译器可能会使用中间表示逐步地工作。它还可能合并一些阶段。这取决于所使用的技术以及所用语言的复杂性。


太棒了,非常有帮助。我知道正则表达式用于标记化过程中(例如,可以以"[^"]*"最简单的形式定义字符串文字,而忽略转义字符等),但是它是否也用于创建语法树(就编程语言而言)?正如有限状态自动机所定义的那样,我认为不是有限的。if由于嵌套,即使对于单个语句,语法树在理论上也可以是无限的。因此,作为有限状态自动机的正则表达式不能用于生成语法树。
2014年

@Zwander thx 4编辑-您的正则表达式示例正确(我应该提供一些示例)。顺便说一句,Regex还是一种语言,在字符串集的世界中具有其自己的语义,并具有上下文无关(CF)语法。它仅用于语言字符串的标记化,至少用于编程语言,通常不用于定义用于语法树的较大语法,除非是扩展BNF(EBNF)的简称。在大多数情况下,以某种形式将Regex添加到更复杂的形式主义中并不会改变其表达能力。您关于无穷大的说法不太正确。请参阅下一条评论。
2014年

@Zwander有限地描述了所有形式主义(形式语言)。这是一个基本假设。例如,即使您对具有无限数量的规则的CF语法感兴趣,也必须对规则的无限性进行有限的描述。无限也会在您身上发挥作用(没有空间)。一种if说法是无限的(任意大),但总是有限的。有限定义的无限ifwhile。CF和常规之间的区别是CF控制/允许嵌套(即,括号),而常规则不允许。但是两者都是有限描述的,并且允许无限制的字符串。
2014年

1
@Zwander形式主义必须能够表示任何格式正确的句子(程序),但只能表示格式良好的句子。简而言之,FSA不能无限计数。因此,他们无法知道应该打开多少个括号,或者正确嵌套两种不同的括号。许多语言结构都有“隐藏”括号。这不仅是语法检查的问题,而且还主要意味着无法表达和构建适当的树结构,并无法从中导出语义。恢复一些适当的树结构需要进行计数。
2014年

1
@Zwander稍后可能对您有用的一句话是,可以使用括号将树简单地线性化为字符串。通常在完全括上算术表达式(例如)时执行的操作一种-+3×C确保每个运算符都获得正确的子树(子表达式)。pashdown堆栈(请参见下推自动机),上下文无关的语言和树之间有着密切的关系。
2014年

2

有显着差异。我想说,其中最主要的是解析真实的编程语言就是处理语法错误。使用正式语言,您只会说“好吧,它不是用该语言编写的”,但是说那不是很有用的编译器-它应该告诉您出了什么问题,并且如果这是一个小错误,最好继续进行分析,以便可以报告更多错误。对此进行了大量研究(和实施工作)。因此,实际上,您甚至不关心真实/错误结果,您只想分析输入的结构。那里使用形式语言作为工具,并且有很多重叠之处,但是您实际上正在解决另一个问题。

另外,在大多数语言中,都选择不强制语法中的某些内容,例如您提到的示例“如果未声明变量就不会出现”。这通常是解析器将完全忽略的事情,然后陷入单独的分析(语义分析)中,该分析着眼于这种事情,并且不受上下文无关性等因素的影响。但并非总是如此-例如解析C,经常使用lexer hack,C ++是一种语言的著名示例,如果不同时进行一些认真的语义分析就无法解析该语言(实际上,解析C ++是不确定的,因为模板已经完成了图灵化)。在更简单的语言中,它往往会被拆分,这样更容易。


1

正式语言是一组单词-其中一个单词是来自某个字母的符号字符串。

这意味着您对生产规则和形式语言的耦合太强了。正式语言是生产规则是不正确的。相反,生产规则定义了形式语言。形式语言是可以由生产规则生产的词。(这要求形式语言可以由生产规则定义,例如常规语言可以由上下文无关的语法定义)

因此,与表达式相对应的常规语言(a|b)*c*d由生产规则定义;

S->ACd
A->
A->aA
A->bA
C->
C->cC

这些生产规则从起始符号S生成的单词恰好是正则表达式接受的那些字符串。


0

正则表达式与编程语言之间还有另一种关系,这与语义有关。命令式语言的基本控制结构是顺序组合(先执行A,然后执行B),选择(执行A或B)和重复(一次又一次地执行A)。

在正则表达式中可以找到相同的三种组合行为的方式。抛出子例程调用,您就可以与EBNF类似。

因此,正则表达式的代数与命令的代数之间有很多相似之处。Dijkstra在“三个结石的统一”中对此进行了详细探讨。这也是米尔纳CCS的基础,它为以下问题提供了答案:如果增加并行性会怎样?

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.