编译器-3:文法

问候,

这周我们讨论小语言的句法方面的设计。

它有助于识别此类语法的解析器的设计。 上个星期

我们看到了令牌化器:它只是将字节序列分组为令牌; 它没有

完全不了解任何语言的语法。 那是工作

解析器 另一方面,解析器不在乎这些令牌的来源,

也不知道它们是如何形成的。 解析器和分词器紧密协作,顺序良好

了解并检查特定输入流的正确性。 但首先,

让我们设计我们的小语言。

一种小型编程语言

我希望这种语言尽可能小,因为我不想写

整本关于这个主题的小说。 我们的小语言将是一小段表达

语言。 它将翻译将被解释的表达式序列

由产生这些表达式的值的解释器。

语言也必须了解变量,函数,特殊函数

作为“原子”部分,即浮点数和简单列表。 必有

是内置函数以及用户定义的函数。 内置列表

如果要扩展此语言,则函数必须易于扩展。

用我们的小语言从语法上讲一个程序看起来像这样:


program: ((definition | expression) ';')* 
内容为:程序是以下零个或多个的列表:a

定义或表达式,后跟“;” 字符。 拖尾星

表示括号中的前一个序列出现0次或多次。

竖线表示一个选择:左侧的零件或零件

在垂直栏的右侧。

“程序”一词被称为语法/语法的“规则”。 “定义”和

“表达”也是规则; 我们只是还没有定义它们; 任何东西

规则定义中任何地方的引号是令牌生成器提供的令牌。

';' 是一个简单的令牌。 括号用于对规则的各个部分进行分组

一起| 和*具有特殊含义(请参见上文)。 +加号

符号也有特殊含义,就像*星:星代表零

或上一个规则部分中的一个或多个,加号表示一个或多个上一个规则部分

规则部分。 如果我是这样写的:


program: ((definition | expression) ';')+ 
如果一个程序至少包含一个,则在语法上是正确的

定义或表达。 当程序可以为空时,我喜欢它,所以我使用了

星号(零个或多个定义或表达式)。 还有另一个特别之处

符号:问号; 一个 ? 指示应该发生前一部分

最多一次 所以如果我写了这个:


program: ((definition | expression) ';')? 
语法上有效的程序可以为空,也可以为一个定义,或者

允许表达。 就我们的目的而言,这太过严格了。

EBNF表示法

上面的语法描述称为“ EBNF” :(扩展Backus-Naur格式)。

John Backus发明了这种表示法,Peter Naur稍后对其进行了简化。

两者都受到Noam Chomsky的工作的启发,他提出了一个全新的概念。

语言的思考方式。 在1950年代后期的某个地方,他发表了他的

工作,并立即被古典语言学家所讨厌,他认为

语言的“感觉”和“直觉”将不再存在。

诺姆·乔姆斯基(Noam Chomsky)是一位固执的科学家,根本不在乎愤怒

他因工作而受到批评,并认为语言学从未如此

在他发表作品之前先学习一门适当的科学。

乔姆斯基更进一步指出,每个人天生都是自然的

通用语法,所有语言都是该通用语言的派生词

语法。 蹒跚学步的孩子要做的就是弄清楚它们的特殊性

他母亲的语言是为了学习如何说和形成句子

在语法上是正确的。 这种烙印的通用语法逐渐消失

当人类长大。 那个现象可以解释为什么小孩子学习

第二语言的速度如此之快(只需将其与通用语法相匹配),

大人要掌握另一种语言时就必须努力学习。

扩展Backus-Naur格式将语法的这一概念形式化。

定义

再次回到我们的编程语言:该语法中提到的定义

规则实际上可以是函数定义或函数声明。

我们需要一个声明的概念,因为我们的解析器是“一次通过”解析器,

即,它一次读取令牌流,然后必须完成所有工作。

当解析器“看到”由令牌生成器传递的名称时,它不知道

但是该名称代表一个用户函数,它将引发错误:它

无法将该名称识别为用户功能。 如果两个函数分别调用

其他必须递归声明两个函数。 但是使用了一种功能

在其他功能的主体中,反之亦然。 一个功能体定义

必须先行,这就是为什么我们需要声明; 考虑一下:


function foo(x) = bar(x-1);
function bar(x) = foo(x/2); 
当解析函数foo的函数体时,关于bar的信息一无所知

然而。 但是我们不能先定义功能栏,因为它指向功能foo

再次。 使用声明,我们可以解决这个小问题22:


function bar(x);
function foo(x) = bar(x-1);
function bar = foo(x/2); 
第一行声明功能栏的存在,但尚未定义。

第二行声明并定义了一个使用函数bar和

第三行最后通过提供功能栏来定义功能栏。 注意

当已经声明一个函数时(第一行),不允许重复

再次使用正式参数列表,即正式参数列表已经

在函数的声明中定义。

这也允许使用更简单的解析器,因为它不必检查是否

两个形式参数列表是否相等,它们当然必须相等。

还要注意,我引入了“功能”一词; 这是保留字; 另一个

保留字将在稍后偷偷引入。 没有用户定义的功能

变量也不能命名为“函数”。 唯一的目的是介绍

函数声明或定义的开始。

请注意,在函数go中声明并定义了函数foo的第二行是

有时称为“暂定”定义。 该函数都被声明并且

它的形式参数列表以及函数本身都已定义。

还要注意,其他编程语言使用不同的语法,但是我们

定义自己的小语言,我们就可以自由地做自己想做的事。

上面所有这些东西在EBNF表示法中是什么样的? 看看这个:


definition: ( 'function' | 'listfunc' ) 'name' defordecl
defordecl: ('(' paramlist ')' ( '=' expression )?) | ( '=' expression ) 
paramlist ( 'name' ( ',' 'name' )* )? 
第一行很简单:它只是读取关键字'function'或'listfunc'

需要后面跟一个“名称”,再跟另一个规则defordecl。 德福

规则定义函数声明或函数的(暂定)定义。

我们要么解析一个参数列表,然后可选地后面跟一个'='和一个表达式

或者我们跳过参数列表,并期望立即有一个'='和一个表达式。

参数列表可以是“名称”,后跟其余的参数列表

或列表完全为空。 参数列表的其余部分是一个序列

一个“,”后跟另一个“名称”的次数为零或多次。

现在,我们略微忽略“ listfunc”保留字; 它表明

用户定义的函数将整个列表作为其参数。 我们将回到它。

我们已经为大多数我们的小语言设计了语法。 有一部分

仍然缺少:表达式的语法:

表达

我们想解析普通的,易读的表达式。 表达式包括

二进制和一元运算符以及变量名,函数

用户定义(请参见上文)或内置函数。 我们也希望能够

解析列表。

让我们从二进制运算符表达式开始。 一些二进制运算符绑定更多

它们的操作数比其他操作数更紧密,例如,我们期望4 + 2 * 19等于42

不是114。我们希望首先进行乘法运算,然后再进行加法运算。

乘法运算符的优先级高于加法运算符。

我们的小语言知道六个不同的优先级。 从最低到

运算符的最高优先级是:


:
== !=
<= < > >=
+ -
* /
^ 
从鸟瞰来看,一个表达只是一个或多个其他表达

中间带有':'标记。 当只有另一种表达时

表达式中没有':'标记。 “其他表达方式”

不包含令牌“:”。 这是此规则的EBNF形式:


expression: comparison ( operator0 comparison )*
operator0: ':' 
':'运算符只是表达一系列表达式的一种方式

只是一个 表达式的值是右边的表达式

它; 忘了':'标记左侧表达式的值。

相等表达式测试(in)相等的另外两个表达式:


expression: comparison ( operator1 comparison )*
operator1: '==' | '!=' 
比较看起来与顶级表达式没有什么不同:

comparison: addition ( operator2 addition )*
operator2: '<=' | '<' | '>' | '>=' 
而且加法表达式也看起来类似:

addition: multiplication ( operator3 multiplication )*
operator3: '+' | '-' 
已经有点无聊了。 这是乘法的EBNF:

multiplication: power ( operator4 power )*
operator4: '*' | '/' 
最后,这里是处理具有以下内容的二进制表达式的表达式:

最高优先级:


power: unary ( operator5 unary )*
operator5: '^' 
如果我已命名表达式,比较,加法,乘法和幂

规则不同,我们将得到以下信息:


expression0: expression1 ( operator0 expression1 )*
expression1: expression2 ( operator1 expression2 )*
expression2: expression3 ( operator2 expression3 )*
expression3: expression4 ( operator3 expression4 )*
expression4: expression5 ( operator4 expression4 )*
expression5: unary       ( operator5 unary       )*
operator0: ':'
operator1: '==' | '!='
operator2: '<=' | '<' | '>' | '>='
operator3: '+' | '-'
operator4: '*' | '/'
operator5: '^' 
当我们要为我们的开发实际代码时,请记住这个重命名技巧

二进制表达式解析器。 接下来是“一元”表达式。 一元表达式

不像二进制表达式那么规则。 一元表达式是一元

运算符后跟一元表达式,或者它是不带任何表达式的表达式

领先的一元运算符:


unary: ( unaryop unary ) | atomic
unaryop: '+' | '-' | '!'  
原子表达式是以下之一:

-括号中的表达式(嵌套)或

-函数调用(function)或

-名称(可选)后跟一个赋值(nameexpr)或

-常数(常数)或

-表达式列表(列表)

括号中的表达式就是这样,用EBNF表示法:


nested: '(' expression ')' 
这是函数调用的EBNF表示法:

function: 'name' '(' params ')'
params: ( expression ( ',' expression )* )? 
请注意参数规则与形式参数列表的规则的相似性

(有关该规则,请参见上面的定义部分)。

赋值(或只是变量名)如下所示:


nameexpr: 'name' ( ( assignop expression ) | '++' | '--' )?
assignop: '=' | '+=' | '-=' | '*=' | '/=' | '^=' 
如果一个赋值运算符之一后没有名称,我们只想

使用以“名称”表示的变量的值; 否则我们想

使用中提到的表达式为变量分配另一个值

第一法则。 最后,名称后可以带有一元++或-后缀表达式。

常量就是它的含义:它是一个浮点常量,可被

分词器; 没有有趣的EBNF表示法; 仅此一个:


constant: 'constant' 
最后,列表由零个或多个表达式组成,并用逗号和

括在大括号中:


list: '{' ( expression ( ',' expression )? )* '}' 
这是我们小的编程语言的整个语法。 原子的

表达的东西是最大的混乱,解析器必须检查几个

它解析一个“名称”时的事情,即它可以是一个简单的变量名,但是

它也可以是内置函数的名称,也可以是

用户定义的功能。 它也可能是作业的第一部分。

为了弄清楚解析器全都跟踪内置列表

功能以及用户定义功能的映射。 但是,正如他们所说,

只是一个“实施细节”,我们将其留给我们

开始实现我们的解析器。

解析器生成器

我们正在手工制作解析器; 如今不再需要:

很少有工具可以为您做到这一点。 你喂它简单

包含语法的EBNF表示法的文本文件,该工具会生成

整个解析器在片刻之内。 词法分析器生成器也是如此:

当您输入一堆常规的词时,它们会生成整个词法分析器

表达式。 我们不会走那条路; 我们没有走那条路

我们的标记器(词法分析器),我们也不会走这条路

为我们的解析器。 我们将以艰难的方式做到这一点,只是为了展示

这些野兽的复杂性就在这里。

结束语

在本部分中所有这些理论知识之后

在编译器文章中,看看我们的语言可以做什么:


function fac(n) =
   if ((n < 2)
      , 1
      , n*fac(n)
   ); 
这定义了一个函数“ fac”,用于计算参数“ n”的能力。

我们甚至可以用数字列表来做到这一点:


fac( { 1, 2, 3, 4, 5, } ); 
结果将是{1,2,6,24,120}。 列表以及一些特殊的

内置函数将使我们的小程序设计语言相当丰富

好玩

实话实说,我已经实现了大部分功能:令牌生成器,

解析器,代码生成器和解释器以及所有用具

系统需要它,并且确实很有趣。 也许

这种小语言在某处找到了用处; 谁知道? 也许我会想出

一个很好的应用程序。

下周,我们必须进行一些簿记:解析器的表

然后将说明令牌生成器。 它显示了一些有趣的用法

资源类:它处理原始文件资源并为

要填充的表。原始文件资源只是Properties对象,

但是加上一点String摆弄,他们可以做相当高级的事情,

由该Resource类进行。

请继续关注,下周见

亲切的问候,

乔斯

From: https://bytes.com/topic/java/insights/655999-compilers-3-grammars

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值