Skip to content

Latest commit

 

History

History
126 lines (73 loc) · 6.29 KB

0X01语法架构.md

File metadata and controls

126 lines (73 loc) · 6.29 KB

0X01 语法架构

从这节开始我将用自然语言描述性地介绍 EBNF 语法.学过这一部分之后,应该可以看懂各种用 EBNF 定义的编程语言语法规则.当对 EBNF 有一个比较全面的了解之后我会再用比较严格的形式语法重述这一部分的内容.

"语法架构"这个词是我自己造出来的.我想在本节概览 EBNF 的语法而不纠结于具体规则,想来想去借用一下"架构"这个词来表达我的意思.

规范

ISO 对 EBNF 的定义分为四个部分--字符,语法,空白,注释.

ISO 标准中的字符部分就是描述所有可以在 EBNF 中出现的字符. EBNF 语言中用到的所有字符都是 ASCII 字符, ISO 在描述的时候采用了这些字符在标准中的命名.但其实我们只要知道 EBNF 中使用了 ASCII 的 95 个可打印字符(含空格),换行 \n (或者 \r, \r\n)以及水平制表 \t 就可以了.

空白符在最初的 BNF 有时是必不可少的.但是在 EBNF 中,由于采用了显示的表示字符串连接的符号,所有的空格都是可选的.它们存在的意义仅仅是辅助排版,增强可读性.

注释和空白符的地位相近.它对于定义语法本身没有任何贡献.写注释是为了解释语法规则,方便读者理解.

标准中的语法规则部分是整个 EBNF 的核心.它定义了 EBNF 中的各种组件和语句.如果去掉空白符和注释或者不使用部分普通字符, EBNF 或许仍然能完成定义编程语言的任务.但是如果把语法部分的规则随便去掉一条,可能 EBNF 就无法使用了.

注释

注释的写法很简单,使用一对括号和星号括起来的就是注释,例如:

(*This is a comment*)

字符串字面值

任何编程语言的源码,本质上都是字符串.但是我们又经常需要在代码中处理字符串.为了区分源码和被处理的字符串,就需要字面值语法.用一些特殊的符号标记表示一段文字是字符串字面值,不加标记的是需要执行的源码.(或者标记了的是有特殊语法意义的,不标记的是字面值.)

对于 EBNF 这样定义语言的语言来说,字符串字面值更是不可缺少.一般来说, EBNF 中定义的任何语句都应该归结为一些字符串字面值的组合链接.

在 EBNF 中有两种字面值的写法,类似于 Lua 中的字符串字面值:

'This is a terminal string.'
"This is another terminal string."

需要注意的是 ISO 标准中没有定义空字符串.因为在这个版本的 EBNF 中不需要空字符串来辅助描述语法.

定义

一个语法系统由若干条语法组成.具体到 EBNF 的源码中,我们就是写若干条定义语句,这些语句合称一套语法规则.

在 EBNF 中,一个定义语句由三部分组成--被定义的名词,等号,定义内容.

经过定义之后,在其他的语句(其他的定义中)中如果出现这个名词,就等价于把它的内容写在那里.

例如我想给字符串 '123456789' 起个别名叫 seq,就用:

seq='123456789';

在别的定义中, seq 出现在等号右边的时候就表示这一串 '123456789' .例如:

s2=seq;

等价于

s2='123456789';

另外注意,在 EBNF 中使用分号作为一个定义语句的终止.不要忘记最后的分号.

在定义内容的时候,不仅仅可以使用单个的已经定义过的名词或者字符串.也可以使用多个名词或者字符串,它们之间用逗号隔开.表示把它们按照顺序连接生成的字符串赋予等号左边的名词.例如:

s3='number',seq;

内容上等价于

s3='number','123456789';

也等价于

s3='number123456789';

关于 EBNF 的定义语句有 3 点需要注意:

1

EBNF 中的定义语句是没有严格的先后顺序的.只要一个名词在这个源码文件中被定义过,就可以在这个文件中任何地方使用.

具体书写的时候,可以按照构造性的顺序,先写底层的词法,然后再写句法等上层.

也可以按照自顶向下,逐步求精的顺序,先给出总的 syntax 定义,然后再逐个解释其部件.然后递归地解释其部件的部件直到所有用到的名词都有解释.

2

按照 ISO 的标准,用 EBNF 定义语言的时候,必须定义一个名叫 syntax 的名词.它表示被定义的语言的最上层的语法结构.相当于是对被定义语言的源码文件看起来应该是什么样子的定义.

例如我用 sentence 表示 C 语言的所有语句都应当遵循的语法.那么 C 语言的 syntax 就可以定义为:

syntax=sentence,{sentence};

其中用到的新语法我们后边会解释.这里这个定义就表示"一份 C 语言的源码是由若干条 C 语句组成的".所以我们就知道任何C源码都是很多语句顺序排列起来的.但是具体一个 C 语言语句长什么样子,就还需要别的定义语句来定义 sentence.

3

ISO 标准中没有明确说如果一个名词被重复定义时应当如何处理(也可能是我没看到,如果有看到的欢迎指出).

我就这个问题给 ISO 写邮件问过.但是 ISO 秘书处让我问 SAC.我写给 SAC 的邮件至今还没有收到回复.

所以我这里就按照我的理解处理.一般的编程语言中,如果是面向过程的,允许副作用改变变量值的语言.多次给一个变量赋值,运行到最后就是,变量等于最后一次赋值的结果.如果是静态的,描述性的,则不允许多次给一个变量赋值,以防止歧义.

当然还有很多别的处理方法(比如多次赋值之间是'或'逻辑关系,建立一个列表来存储等等),但是这里我采用静态的,描述性的语言中的处理方法.在对一种语言的定义中不允许多次定义一个名词.

但是,其实 ISO 对 EBNF 本身的定义中,对 syntax 定义了三次.这个我后边会给出一种我认为起码看上去还算合理的解释.

语义和语法

需要注意的一点是,不论 BNF 家族还是别的什么用于定义语法的元语言,它们都只能定义语法而非语义.漏写了 C 语言中的标点或者写错了 Lua 中的关键字这些问题是语法检测所能检测到的.但是重复声明 C 中的同一个变量或者在 C 语言中赋值类型错误,指针越界这类问题,是语法检测很难处理的.这需要语义层面的分析.

一般来说我们只会用 EBNF 定义词法句法,语义层面就不归它管了.