本文件最初托管在 ETHZ。它仍然在 ETH 许可证 下,并且在 WayBack 存档 中。
Oberon 语言报告
修订版 1. 10. 90, N. Wirth
尽可能简单,但不要更简单。
A. 爱因斯坦
Oberon 是一种通用编程语言,它从 Modula-2 发展而来。它的主要新特性是类型扩展的概念。它允许基于现有类型构建新的数据类型,并在它们之间建立关系。
本报告并非旨在作为程序员教程。它有意保持简洁。它的作用是作为程序员、实现者和手册作者的参考。未言明的事物大多是出于有意,要么是因为它可以从语言的既定规则中推断出来,要么是因为它需要在普遍承诺显得不明智时做出具体承诺。
语言是句子的无限集合,即根据其语法形成的句子。在 Oberon 中,这些句子被称为编译单元。每个单元都是来自有限词汇的有限符号序列。Oberon 的词汇表包含标识符、数字、字符串、运算符、分隔符和注释。它们被称为词法符号,由字符序列组成。(注意符号和字符之间的区别。)
为了描述语法,使用了称为 EBNF 的扩展巴克斯-诺尔范式。方括号 [ 和 ] 表示所包含的句法形式的可选性,而花括号 { 和 } 表示其重复(可能为 0 次)。句法实体(非终结符)由表达其直观含义的英文单词表示。语言词汇表中的符号(终结符)由包含在引号中的字符串或以大写字母书写的单词表示,即所谓的保留字。句法规则(产生式)在行左侧标记为 $ 符号。
使用 ASCII 集定义符号的字符表示。符号是标识符、数字、字符串、运算符、分隔符和注释。必须遵守以下词法规则。空格和换行符不能出现在符号内(除了注释中,以及字符串中空格的情况)。它们被忽略,除非它们是分隔两个连续符号所必需的。大写和小写字母被认为是不同的。
1. 标识符是字母和数字的序列。第一个字符必须是字母。
示例
2. 数字是(无符号)整数或实数。整数是数字序列,后面可以跟一个后缀字母。类型是最小的包含该数字的类型(参见第 6.1 节)。如果没有指定后缀,则表示为十进制。后缀H表示十六进制表示。 实数始终包含小数点。可选地,它也可以包含小数比例因子。字母E(或D)读作“乘以十的次方”。实数的类型为 REAL,除非其比例因子包含字母D,在这种情况下其类型为 LONGREAL。
示例
3. 字符常量要么由包含在引号中的单个字符表示,要么由字符在十六进制表示中的序数后跟字母X表示。 $ CharConstant = """ character """ | digit {hexDigit} "X".
4. 字符串是包含在引号(")中的字符序列。字符串不能包含引号。字符串中的字符数称为字符串的长度。字符串可以分配给字符数组并与之进行比较(参见第 9.1 节和第 8.2.4 节)。 $ string = """ {character} """ .
示例
"OBERON" "Don't worry!"
5. 运算符和分隔符是下面列出的特殊字符、字符对或保留字。这些保留字仅由大写字母组成,不能用作标识符。 6. 注释可以插入程序中任何两个符号之间。它们是通过括号 (* 开头并以 *) 结尾的任意字符序列。注释不会影响程序的含义。
程序中出现的每个标识符都必须通过声明引入,除非它是预定义的标识符。声明还用于指定对象的某些永久属性,例如它是否是常量、类型、变量或过程。
然后使用标识符引用关联的对象。这只能在程序中处于声明作用域内的部分进行。在给定作用域内,任何标识符都不能表示多个对象。作用域在文本上从声明点延伸到其所属的块(过程或模块)的末尾,因此对象是局部的。作用域规则有以下修正
1. 如果类型T被定义为 POINTER TO T1(参见第 6.4 节),则标识符T1可以在文本上声明为T的声明之后,但必须位于相同的范围之内。 2. 记录声明的字段标识符(参见第 6.3 节)仅在字段指示符中有效。
在声明中,全局范围内的标识符后面可以跟一个导出标记 (*),以指示它要从其声明模块导出。在这种情况下,标识符可以在其他模块中使用,如果它们导入声明模块。然后,标识符将以指定其模块的标识符为前缀(参见第 11 节)。前缀和标识符由句点分隔,合在一起称为限定标识符。
以下标识符是预定义的;它们在指示的节中定义了含义
常量声明将标识符与常数值关联起来。
常量表达式可以通过简单的文本扫描进行计算,而无需实际执行程序。它的操作数是常量(参见第 8 节)。
示例
数据类型确定该类型变量可以假设的值集,以及适用的运算符。类型声明用于将标识符与类型关联起来。这种关联可以是与非结构化(基本)类型,也可以是与结构化类型,在这种情况下,它定义了此类型变量的结构,并隐含地定义了适用于组件的运算符。有两种不同的结构,即数组和记录,它们具有不同的组件选择器。
示例
以下基本类型由预定义的标识符表示。关联的运算符在第 8.2 节中定义,预定义的函数过程在第 10.2 节中定义。给定基本类型的值如下
- BOOLEAN 真值 TRUE 和 FALSE。
- CHAR 扩展 ASCII 集中的字符(0X ... 0FFX)。
- SHORTINT MIN(SHORTINT) 和 MAX(SHORTINT) 之间的整数。
- INTEGER MIN(INTEGER) 和 MAX(INTEGER) 之间的整数。
- LONGINT MIN(LONGINT) 和 MAX(LONGINT) 之间的整数。
- REAL MIN(REAL) 和 MAX(REAL) 之间的实数。
- LONGREAL MIN(LONGREAL) 和 MAX(LONGREAL) 之间的实数。
- SET 0 和 MAX(SET) 之间的整数集合。
类型 3 到 5 是整数类型,6 和 7 是实数类型,它们合称为数值类型。它们形成一个层次结构;较大的类型包含(较小的类型的)值
- LONGREAL >= REAL >= LONGINT >= INTEGER >= SHORTINT
注意:>= 在这里表示“是超集或等于”。
数组是由固定数量的元素组成的结构,这些元素都是相同类型,称为元素类型。数组的元素数量称为其长度。数组的元素由索引指定,索引是 0 到长度减 1 之间的整数。
以下形式的声明:ARRAY N0, N1, ... , Nk OF T
被理解为以下声明的缩写
示例
记录类型是由固定数量的元素组成的结构,这些元素可能是不同类型。记录类型声明为每个元素(称为字段)指定其类型和标识符,该标识符表示该字段。这些字段标识符的作用域是记录定义本身,但它们也在引用记录变量的元素的字段指示符中可见(参见第 8.1 节)。
如果导出记录类型,则在声明模块之外可见的字段标识符必须被标记。它们被称为公共字段;未标记的字段被称为私有字段。
记录类型是可扩展的,也就是说,可以将记录类型定义为另一个记录类型的扩展。在上面的示例中,CenterNode(直接)扩展Node,它是 CenterNode 的(直接)基类型。更具体地说,CenterNode通过name和subnode字段扩展了Node。
定义:类型T0扩展类型T,如果它等于T,或者如果它直接扩展了T的扩展。相反,类型T是T0的基类型,如果它等于T0,或者如果它是T0的基类型的直接基类型。
示例
指针类型 P 的变量假设指向某种类型 T 的变量的指针作为值。指针类型 P 被称为绑定到 T,而 T 是 P 的指针基类型。T 必须是记录类型或数组类型。指针类型继承了其基类型的扩展关系。如果类型 T0 是 T 的扩展,而 P0 是绑定到 T0 的指针类型,则 P0 也是 P 的扩展。
如果 p 是类型 P = POINTER TO T 的变量,则调用预定义过程 NEW(p) 会产生以下效果(参见 10.2):在空闲存储中分配一个类型为 T 的变量,并将其指针分配给 p。这个指针 p 是类型为 P;被引用的变量 p^ 是类型为 T。分配失败会导致 p 获得值 NIL。任何指针变量都可以分配值 NIL,它不指向任何变量。
过程类型 T 的变量具有过程(或 NIL)作为值。如果过程 P 被分配给类型为 T 的过程变量,则 P 的形式参数(类型)必须与 T 的形式参数中指示的相同。在函数过程的情况下,结果类型也是如此(参见 10.1)。P 不能被声明为另一个过程的局部过程,也不能是预定义的过程。
变量声明用于引入变量并将它们与在给定作用域内必须唯一的标识符关联起来。它们还用于将固定数据类型与变量关联起来。
标识符出现在同一列表中的变量都具有相同的类型。
变量声明示例(参见第 6 节中的示例)
指针类型T0的变量和记录类型T0的 VAR 参数可以假设其类型T1是其声明类型T0的扩展的值。
表达式是表示计算规则的结构,通过该规则将常量和变量的当前值结合起来,通过应用运算符和函数过程来推导出其他值。表达式由操作数和运算符组成。可以使用括号来表达运算符和操作数的特定关联。
除了集合和字面常量(例如数字和字符字符串)之外,操作数由标识符表示。标识符由一个标识符组成,该标识符引用要指定的常量、变量或过程。此标识符可能由模块标识符限定(参见第 4 节和第 11 节),如果指定的对象是结构的元素,则后面可以跟选择器。
如果 A 表示一个数组,则 A[E] 表示 A 中索引为表达式 E 的当前值的元素。E 的类型必须是整数类型。形式为 A[E1, E2, ... , En] 的标识符代表 A[E1][E2] ... [En]。如果 p 表示一个指针变量,则 p^ 表示 p 所引用的变量。如果 r 表示一个记录,则 r.f 表示 r 的字段 f。如果 p 表示一个指针,则 p.f 表示记录 p^ 的字段 f,即点表示解引用,p.f 代表 p^.f,而 p[E] 表示 p^ 中索引为 E 的元素。
类型保护 v(T0) 断言v 的类型为T0,即如果v 的类型不为T0,则它会中止程序执行。如果满足以下条件,则该保护可应用:
1. T0 是v 的声明类型T 的扩展,并且如果 2. v 是记录类型形式变量参数或v 是指针。
如果指定的对象是变量,则标识符引用变量的当前值。如果该对象是过程,则不带参数列表的标识符引用该过程。如果后面跟着(可能为空)的参数列表,则标识符表示过程的激活,并表示其执行结果的值。(实际参数的)类型必须与过程声明中指定的形式参数相对应(参见第 10 节)。
标识符示例(参见第 7 节中的示例)
表达式的语法区分了四类具有不同优先级(绑定强度)的运算符。运算符 ~ 具有最高优先级,其次是乘法运算符、加法运算符和关系运算符。具有相同优先级的运算符从左到右结合。例如,x-y-z 代表 (x-y)-z。
可用的运算符列在下面的表格中。在某些情况下,同一个运算符符号可能表示几种不同的运算。在这些情况下,实际运算由操作数的类型确定。
8.2.1 逻辑运算符
这些运算符适用于 BOOLEAN 操作数并产生 BOOLEAN 结果。
8.2.2. 算术运算符
运算符 +、-、* 和 / 适用于数字类型操作数。结果的类型是包含另一个操作数类型的操作数类型,除了除法 (/),其中结果是包含两个操作数类型的实数类型。当用作具有单个操作数的运算符时,- 表示符号反转,+ 表示恒等运算。
运算符 DIV 和 MOD 仅适用于整数操作数。它们由以下公式定义,适用于任何被除数 x 和正除数 y
8.2.3. 集合运算符
一元减号表示 x 的补集,即 -x 表示 0 到 MAX(SET) 之间的整数集,这些整数不是 x 的元素。
8.2.4. 关系
关系是布尔值。排序关系 <、<=、> 和 >= 适用于数字类型、CHAR 和字符数组(字符串)。关系 = 和 # 也适用于类型 BOOLEAN 以及集合、指针和过程类型。x IN s 表示“x 是 s 的元素”。x 必须是整数类型,而 s 必须是 SET 类型。v IS T 表示“v 的类型为 T”,称为类型测试。如果满足以下条件,则该测试适用:
- T 是 v 的声明类型 T0 的扩展,并且如果
- v 是记录类型变量参数或 v 是指针。
例如,假设 T 是 T0 的扩展,并且 v 是声明为类型 T0 的标识符,则测试“v IS T”确定实际指定的变量是否为(不仅是 T0,而且也是)T。NIL IS T 的值是未定义的。
表达式示例(参见第 7 节中的示例)
语句表示动作。有基本语句和结构化语句。基本语句不包含任何本身是语句的部分。它们是赋值、过程调用以及返回和退出语句。结构化语句由本身是语句的部分组成。它们用于表达顺序执行以及条件执行、选择执行和重复执行。语句也可能是空的,在这种情况下,它表示不执行任何操作。空语句的引入是为了放松语句序列中的标点规则。
赋值用于用表达式指定的新的值替换变量的当前值。赋值运算符写为 ":=",读作变成。
表达式的类型必须被变量的类型包含,或者它必须扩展变量的类型。以下例外情况成立:
- 常量 NIL 可以分配给任何指针或过程类型的变量。
- 字符串可以分配给任何类型为字符数组的变量,前提是字符串的长度小于数组的长度。如果长度为 n 的字符串 s 分配给数组 a,则结果为 a[i] = si,其中 i = 0 ... n-1,而 a[n] = 0X。
赋值示例(参见第 7 节中的示例)
过程调用用于激活过程。过程调用可能包含实际参数列表,这些参数将替换过程声明中定义的相应形式参数(参见第 10 节)。对应关系由参数在实际参数列表和形式参数列表中的位置分别建立。参数有两种类型:变量参数和值参数。
在变量参数的情况下,实际参数必须是表示变量的标识符。如果它指定了结构化变量的元素,则选择器在形式参数/实际参数替换发生时(即在过程执行之前)进行计算。如果参数是值参数,则相应的实际参数必须是表达式。该表达式在过程激活之前进行计算,所得的值被分配给形式参数,该形式参数现在构成了局部变量(另见 10.1)。
过程调用的示例
语句序列表示由组件语句指定的动作序列,这些组件语句由分号分隔。
if 语句指定受保护语句的条件执行。语句之前的布尔表达式称为其保护条件。保护条件按出现顺序进行计算,直到其中一个计算结果为 TRUE,然后执行其关联的语句序列。如果没有任何保护条件满足,则执行 ELSE 符号后的语句序列(如果有)。
示例
case 语句指定根据表达式的值选择和执行语句序列。首先计算 case 表达式,然后执行其 case 标签列表包含所得值的语句序列。case 表达式和所有标签必须具有相同的类型,该类型必须是整数类型或 CHAR。case 标签是常量,任何值不得出现多次。如果表达式的值在任何 case 的标签中都没有出现,则选择 ELSE 符号后的语句序列(如果有)。否则,它被视为错误。
示例
while 语句指定重复执行。如果布尔表达式(保护条件)结果为 TRUE,则执行语句序列。表达式计算和语句执行重复进行,直到布尔表达式结果为 TRUE。
示例
repeat 语句指定语句序列的重复执行,直到满足条件为止。语句序列至少执行一次。
loop 语句指定语句序列的重复执行。它由该序列中任何退出语句的执行终止(参见 9.9)。
示例
虽然 while 和 repeat 语句可以通过包含单个退出语句的 loop 语句来表达,但在大多数情况下,建议使用 while 和 repeat 语句,在这种情况下,终止依赖于在重复语句序列的开头或结尾确定的单个条件。loop 语句用于表达具有多个终止条件和点的案例。
返回语句由符号 RETURN 组成,后面可能跟着一个表达式。它表示过程的终止,表达式指定函数过程的结果。它的类型必须与过程标题中指定的返回类型相同(参见第 10 节)。
函数过程需要一个返回语句,表示结果值。可能有多个,但只有一个会被执行。在正确过程中,返回语句由过程体末尾隐式提供。因此,显式返回语句作为附加的(可能是例外情况的)终止点出现。
退出语句由符号 EXIT 组成。它指定退出封闭的 loop 语句,并继续执行该 loop 语句后的语句。退出语句与包含它们的 loop 语句在语义上相关联,尽管在语法上没有关联。
如果指针变量或带有记录结构的变量参数的类型为 T0,则它可以在 with 子句的标题中与类型 T 一起指定,该类型 T 是 T0 的扩展。然后,该变量在 with 语句中被保护,就好像它已声明为类型 T 一样。with 语句承担类似于类型保护的角色,将保护条件扩展到整个语句序列。它可以被视为区域类型保护。
示例
过程声明由过程标题和过程体组成。标题指定过程标识符、形式参数和返回类型(如果有)。主体包含声明和语句。过程标识符在过程声明末尾重复。
过程有两种类型:正确过程和函数过程。后者由表达式中的函数标识符激活,并产生一个表达式中的操作数结果。正确过程由过程调用激活。函数过程在声明中通过在参数列表之后指示其结果的类型来区分。它的主体必须包含一个定义函数过程结果的 RETURN 语句。
在过程体内声明的所有常量、变量、类型和过程都对该过程是局部的。在进入过程时,局部变量的值未定义。由于过程也可以声明为局部对象,因此过程声明可以嵌套。
除了其形式参数和局部声明的对象外,在过程环境中声明的对象在过程中也是可见的(那些与局部声明的对象同名的对象除外)。
在过程声明中调用时使用过程标识符表示过程的递归激活。
前向声明用于允许对文本中后面出现的过程进行前向引用。实际声明 - 指定主体 - 必须指示与前向声明相同的参数和返回类型(如果有),并且必须在同一个范围内。
在符号 PROCEDURE 后面的星号是编译器的提示,指定该过程可用作参数并可分配给变量。(根据实现方式,提示可能是可选的或必需的。)
形式参数是标识符,表示在过程调用中指定的实际参数。当调用过程时,建立形式参数和实际参数之间的对应关系。参数有两种,即*值*参数和*变量参数*。参数的类型在形式参数列表中指示。值参数代表局部变量,将对应实际参数的求值结果作为初始值赋予该局部变量。变量参数对应于实际参数,实际参数是变量,并且变量参数代表这些变量。变量参数用符号 VAR 表示,值参数用 VAR 符号的缺失表示。没有参数的函数过程必须有一个空参数列表。它必须通过一个实际参数列表也是空的函数设计器来调用。
形式参数对过程是局部的,即它们的范围是构成过程声明的程序文本。
每个形式参数的类型在参数列表中指定。对于变量参数,它必须与相应实际参数的类型相同,除了记录的情况,它必须是相应实际参数类型的基本类型。对于值参数,赋值规则适用(参见 9.1)。如果形式参数的类型指定为 ARRAY OF T,则该参数被称为*开放数组参数*,并且相应的实际参数可以是任何元素类型为 *T* 的数组。
如果形式参数指定了过程类型,则相应的实际参数必须是全局声明的过程,或者该过程类型的变量(或参数)。它不能是预定义的过程。过程的结果类型既不能是记录也不能是数组。
过程声明的示例
下表列出了预定义的过程。一些是*泛型*过程,即它们适用于几种类型的操作数。v 代表变量,x 和 n 代表表达式,T 代表类型。
函数过程
名称 | 参数类型 | 结果类型 | 函数 |
ABS(x) | 数字类型 | x 的类型 | 绝对值 |
ODD(x) | 整数类型 | BOOLEAN | x MOD 2 = 1 |
CAP(x) | CHAR | CHAR | 相应的大写字母 |
ASH(x, n) | x, n: 整数类型 | LONGINT | x * 2n,算术移位 |
LEN(v, n) | v: 数组 n: 整数类型 |
LONGINT | v 在维度 n 中的长度 |
LEN(v) | 数组类型 | LONGINT | LEN(v, 0) |
MAX(T) | T = 基本类型 T = SET |
T INTEGER |
类型 T 的最大值 集合的最大元素 |
MIN(T) | T = 基本类型 T = SET |
T INTEGER |
类型 T 的最小值 0 |
SIZE(T) | T = 任何类型 | 整数类型 | T 需要的字节数 |
类型转换过程
名称 | 参数类型 | 结果类型 | 函数 |
ORD(x) | CHAR | INTEGER | x 的序数 |
CHR(x) | 整数类型 | CHAR | 序数为 x 的字符 |
SHORT(x) | LONGINT INTEGER LONGREAL |
INTEGER SHORTINT REAL |
身份(可能截断) |
LONG(x) | SHORTINT INTEGER REAL |
INTEGER LONGINT LONGREAL |
身份 |
ENTIER(x) | 实数类型 | LONGINT | 不超过 x 的最大整数。 注意 ENTIER(i/j) = i DIV j |
适当的过程
名称 | 参数类型 | 函数 |
INC(v) | 整数类型 | v := v+1 |
INC(v, x) | 整数类型 | v := v+x |
DEC(v) | 整数类型 | v := v-1 |
DEC(v, x) | 整数类型 | v := v-x |
INCL(v, x) | v: SET x: 整数类型 |
v := v + {x} |
EXCL(v, x) | v: SET x: 整数类型 |
v := v - {x} |
COPY(x, v) | x: 字符数组,字符串 v: 字符数组 |
v := x |
NEW(v) | 指针类型 | 分配 v^ |
HALT(x) | 整数常量 | 终止程序执行 |
在 HALT(x) 中,x 是一个参数,其解释留给底层系统实现。
模块是常量、类型、变量和过程声明的集合,以及用于为变量分配初始值的语句序列。模块通常构成一个可作为单元编译的文本。
导入列表指定了模块是其客户端的模块。如果标识符 x 从模块 M 导出,并且 M 列在模块的导入列表中,则 x 被称为 M.x。如果在导入列表中使用形式“M := M1”,则在 M1 中声明的对象将被引用为 M.x 。
在客户端模块中可见的标识符,即在声明模块之外可见的标识符,必须在其声明中用导出标记标记。
在符号 BEGIN 之后的语句序列是在将模块添加到系统(加载)时执行的。此后,可以从系统中激活各个(无参数)过程,这些过程用作*命令*。
示例
模块 SYSTEM 包含定义,这些定义对于直接引用特定计算机和/或实现的资源的*低级*操作的编程是必需的。这些包括例如访问由计算机控制的设备的工具,以及打破语言定义中其他施加的数据类型兼容性规则的工具。建议将它们的使用限制在特定的*低级*模块中。此类模块本质上不可移植,但由于它们的导入列表中出现了标识符 SYSTEM 而易于识别。随后的定义适用于大多数现代计算机;但是,各个实现可能在此模块中包含特定于特定底层计算机的定义。
模块 SYSTEM 导出数据类型 BYTE。没有指定值的表示。相反,给出了与其他类型的某些兼容性规则
- 类型 BYTE 与 CHAR 和 SHORTINT 兼容。
- 如果形式参数的类型为 ARRAY OF BYTE,则相应的实际参数可以是任何类型。
模块 SYSTEM 中包含的过程列在下表中。它们对应于作为内联代码编译的单个指令。有关详细信息,请参阅处理器手册。v 代表变量,x、y、a 和 n 代表表达式,T 代表类型。
函数过程
名称 | 参数类型 | 结果类型 | 函数 |
ADR(v) | 任何 | LONGINT | 变量 v 的地址 |
BIT(a, n) | a: LONGINT n: 整数类型 |
BOOLEAN | Mem[a] 的位 n |
CC(n) | 整数常量 | BOOLEAN | 条件 n (0 <= n < 16) |
LSH(x, n) | x: 整数类型或 SET n: 整数类型 |
x 的类型 | 逻辑移位 |
ROT(x, n) | x: 整数类型或 SET n: 整数类型 |
x 的类型 | 旋转 |
VAL(T, x) | T, x: 任何类型 | T | x 被解释为类型 T |
适当的过程
名称 | 参数类型 | 函数 |
GET(a, v) | a: LONGINT v: 任何基本类型 |
v := Mem[a] |
PUT(a, x) | a: LONGINT x: 任何基本类型 |
Mem[a] := x |
MOVE(s, d, n) | s, d: LONGINT n: 整数类型 |
Mem[d] ... Mem[d+n-1] := Mem[s] ... Mem[s+n-1] |
NEW(v, n) | v: 任何指针类型 n: 整数类型 |
分配大小为 n 字节的存储块 将它的地址分配给 v |
HTML 翻译:A. Fischer 和 J. Gutknecht/ 1999 年 10 月
2002 年 7 月 13 日 - 版权所有 © 2002 ETH Zürich。保留所有权利。
电子邮件:oberon at lists.inf.ethz.ch
主页:http://www.ethoberon.ethz.ch/