This document is in Windows 1251 cyrillic coding
Институт компьютерных систем, ETH, Цюрих
Июль 1996
Перевод с английского С.Свердлова
Язык программирования Оберон создан автором Паскаля и Модулы-2 Никлаусом
Виртом в 1987 году в ходе разработки одноименной операционной системы
для однопользовательской рабочей станции Ceres. Язык и операционная система
названы именем одного из спутников планеты Уран - Оберона, открытого английским
астрономом Уильямом Гершелем ровно за двести лет до описываемых событий.
"Сделай так просто, как возможно, но не проще того" - это
высказывание А.Эйнштейна Вирт выбрал эпиграфом к описанию языка. Удивительно
простой и даже аскетичный Оберон является, вероятно, минимальным универсальным
языком высокого уровня. Он проще Паскаля и Модулы-2 и в то же время обогащает
их рядом новых возможностей. Важно то, что автором языка руководили не
сиюминутные коммерческие и конъюнктурные соображения, а глубокий анализ
реальных программистских потребностей и стремление удовлетворить их простым,
понятным, эффективным и безопасным образом, не вводя по возможности новых
понятий.
Являясь объектно-ориентированным языком, Оберон даже не содержит слова
object. Оберон представляется идеальным языком для изучения программирования.
Сочетание простоты, строгости и неизбыточности предоставляет начинающему
программисту великолепную возможность, не заблудившись в дебрях, выработать
хороший стиль, освоив при этом и структурное и объектно-ориентированное
и модульно-компонентное программирование.
В 1992 году сотрудничество Н.Вирта с Ханспетером
Мёссенбёком привело к добавлению в язык ряда новых средств. Новая версия
получила название Оберон-2. Описание именно этого языка по состоянию на
1 октября 1996 года (последние изменения внесены авторами в июле 1996 года)
и дается в настоящем переводе. Оберон-2 представляет собой почти правильное
расширение Оберона и является фактическим стандартом языка, который поддерживается
большинством современных Оберон-систем. В Оберон-2 добавлены:
Отдельного внимания заслуживает само описание, с которым вам предстоит
познакомиться. Вирт и его соавтор достигли совершенства не только в искусстве
разработки, но, несомненно, и в деле описания языков программирования.
Поражают изумительная точность и краткость этого документа. Почти каждая
его фраза превращается при написании компилятора в конкретные строки программного
кода.
Возникшие при переводе описания Оберона-2 на русский язык терминологические
вопросы решались исходя из следующих соображений: предпочтительным является
буквальный перевод; недопустимо добавление терминов, отсутствующих в оригинале;
должны быть учтены отечественные традиции в терминологии алголоподобных
языков; предпочтительно использование терминов, привычных широкому кругу
программистов, вместо узкоспециальных. Ниже приведен список терминов, перевод
которых не представляется очевидным.
(direct) base type | (непосредственный) базовый тип |
array compatible | совместимый массив |
array type | тип массив |
assignment compatible | совместимый по присваиванию |
basic type | основной тип |
browser | смотритель |
case statement | оператор case |
character | символ, знак |
declaration | объявление |
designator | обозначение |
direct extension | непосредственное расширение |
equal types | равные типы |
exit statement | оператор выхода |
expression compatible | совместимое выражение |
for statement | оператор for |
function procedure | процедура-функция |
if statement | оператор if |
loop statement | оператор loop |
matching | совпадение |
operator | операция |
pointer type | тип указатель |
predeclared | стандартный |
private field | скрытое поле |
proper procedure | собственно процедура |
public field | доступное поле |
qualified | уточненный |
real | вещественный |
record type | тип запись |
repeat statement | оператор repeat |
return statement | оператор возврата |
same type | одинаковый тип |
scale factor | порядок |
scope | область действия |
statement | оператор |
string | строка |
symbol | слово |
type extension | расширение типа |
type guard | охрана типа |
type inclusion | поглощение типа |
type tag | тег |
type test | проверка типа |
type-bound procedures | связанные с типом процедуры |
while statement | оператор while |
with statement | оператор with |
С. Свердлов
с3с@uni-vologda.ac.ru
2 октября 1996 г. - 12 июня 1998 г.
Вологда
Оберон-2 - язык программирования общего назначения, продолжающий традицию
языков Паскаль и Modula-2. Его основные черты - блочная структура, модульность,
раздельная компиляция, статическая типизация со строгим контролем соответствия
типов (в том числе межмодульным), а также расширение типов и связанные
с типами процедуры.
Расширение типов делает Оберон-2 объектно-ориентированным языком.
Объект - это переменная абстрактного типа, содержащая данные (состояние
объекта) и процедуры, которые оперируют этими данными. Абстрактные типы
данных определены как расширяемые записи. Оберон-2 перекрывает большинство
терминов объектно-ориентированных языков привычным словарем языков императивных,
обходясь минимумом понятий в рамках тех же концепций.
Этот документ не является учебником программирования. Он преднамеренно
краток. Его назначение - служить справочником для программистов, разработчиков
компиляторов и авторов руководств. Если о чем-то не сказано, то обычно
сознательно: или потому, что это следует из других правил языка, или потому,
что потребовалось бы определять то, что фиксировать для общего случая представляется
неразумным.
В приложении A определены
некоторые термины, которые используются при описании правил соответствия
типов Оберона-2. В тексте эти термины выделены курсивом, чтобы подчеркнуть
их специальное значение (например, одинаковый
тип).
Для описания синтаксиса Оберона-2 используются Расширенные Бэкуса-Наура Формы (РБНФ). Варианты разделяются знаком |. Квадратные скобки [ и ] означают необязательность записанного внутри них выражения, а фигурные скобки { и } означают его повторение (возможно 0 раз). Нетерминальные символы начинаются с заглавной буквы (например, Оператор). Терминальные символы или начинаются малой буквой (например, идент), или записываются целиком заглавными буквами (например, BEGIN), или заключаются в кавычки (например, ":=").
Для представления терминальных символов предусматривается использование набора знаков ASCII. Слова языка - это идентификаторы, числа, строки, операции и разделители. Должны соблюдаться следующие лексические правила. Пробелы и концы строк не должны встречаться внутри слов (исключая комментарии и пробелы в символьных строках). Пробелы и концы строк игнорируются, если они не существенны для отделения двух последовательных слов. Заглавные и строчные буквы считаются различными.
1. Идентификаторы - последовательности букв и цифр. Первый символ должен быть буквой.
идент = буква {буква | цифра}.
Примеры: x Scan Oberon2 GetSymbol firstLetter
2. Числа - целые или вещественные (без знака) константы. Типом целочисленной константы считается минимальный тип, которому принадлежит ее значение (см. 6.1). Если константа заканчивается буквой H, она является шестнадцатеричной, иначе - десятичной.
Вещественное число всегда содержит десятичную точку. Оно может также содержать десятичный порядок. Буква E (или D) означает "умножить на десять в степени". Вещественное число относится к типу REAL кроме случая, когда у него есть порядок, содержащий букву D. В этом случае оно относится к типу LONGREAL.
число | = | целое | вещественное. |
целое | = | цифра {цифра} | цифра {шестнЦифра} "H". |
вещественное | = | цифра {цифра} "." {цифра} [Порядок]. |
Порядок | = | ("E" | "D") ["+" | "-"] цифра {цифра}. |
шестнЦифра | = | цифра | "A" | "B" | "C" | "D" | "E" | "F". |
цифра | = | "0" | "1" | "2" | "3" | "4" | "5" | "6" | "7" | "8" | "9". |
Примеры:
1991 | INTEGER | 1991 |
0DH | SHORTINT | 13 |
12.3 | REAL | 12.3 |
4.567E8 | REAL | 456700000 |
0.57712566D-6 | LONGREAL | 0.00000057712566 |
3. Символьные константы обозначаются порядковым номером символа в шестнадцатеричной записи, оканчивающейся буквой X.
символ = цифра {шестнЦифра} "X".
4. Строки - последовательности символов, заключенные в одиночные (') или двойные (") кавычки. Открывающая кавычка должна быть такой же, что и закрывающая и не должна встречаться внутри строки. Число символов в строке называется ее длиной. Строка длины 1 может использоваться везде, где допустима символьная константа и наоборот.
строка = ' " ' {символ} ' " ' | " ' " {символ} " ' ".
Примеры: "Oberon-2" "Don't worry!" "x"
5. Операции и разделители - это специальные символы, пары символов или зарезервированные слова, перечисленные ниже. Зарезервированные слова состоят исключительно из заглавных букв и не могут использоваться как идентификаторы.
+ | := | IMPORT | ARRAY | RETURN |
- | ^ | BEGIN | IN | THEN |
* | = | BY | IS | TO |
/ | # | CASE | LOOP | TYPE |
~ | < | CONST | MOD | UNTIL |
& | > | DIV | MODULE | VAR |
. | <= | DO | NIL | WHILE |
, | >= | ELSE | OF | WITH |
; | .. | ELSIF | OR | |
| | : | END | POINTER | |
( | ) | EXIT | PROCEDURE | |
[ | ] | FOR | RECORD | |
{ | } | IF | REPEAT |
6. Комментарии могут быть вставлены между любыми двумя словами программы. Это произвольные последовательности символов, начинающиеся скобкой (* и оканчивающиеся *). Комментарии могут быть вложенными. Они не влияют на смысл программы.
Каждый идентификатор, встречающийся в программе, должен быть объявлен, если это не стандартный идентификатор. Объявления задают некоторые постоянные свойства объекта, например, является ли он константой, типом, переменной или процедурой. После объявления идентификатор используется для ссылки на соответствующий объект.
Область действия объекта x распространяется текстуально от точки его объявления до конца блока (модуля, процедуры или записи), в котором находится объявление. Для этого блока объект является локальным. Это разделяет области действия одинаково именованных объектов, которые объявлены во вложенных блоках. Правила для областей действия таковы:
Идентификатор, объявленный в блоке модуля, может сопровождаться при своем объявлении экспортной меткой ("*" или "-"), чтобы указать, что он экспортируется. Идентификатор x, экспортируемый модулем M, может использоваться в других модулях, если они импортируют M (см. гл. 11). Тогда идентификатор обозначается в этих модулях М.x и называется уточненным идентификатором. Переменные и поля записей, помеченные знаком "-" в их объявлении, предназначены только для чтения в модулях-импортерах.
УточнИдент | = | [идент "."] идент. |
ИдентОпр | = | идент ["*" | "-"]. |
Следующие идентификаторы являются стандартными; их значение определено в указанных разделах:
ABS | (10.3) | LEN | (10.3) |
ASH | (10.3) | LONG | (10.3) |
BOOLEAN | (6.1) | LONGINT | (6.1) |
CAP | (10.3) | LONGREAL | (6.1) |
CHAR | (6.1) | MAX | (10.3) |
CHR | (10.3) | MIN | (10.3) |
COPY | (10.3) | NEW | (10.3) |
DEC | (10.3) | ODD | (10.3) |
ENTIER | (10.3) | ORD | (10.3) |
EXCL | (10.3) | REAL | (6.1) |
FALSE | (6.1) | SET | (6.1) |
HALT | (10.3) | SHORT | (10.3) |
INC | (10.3) | SHORTINT | (6.1) |
INCL | (10.3) | SIZE | (10.3) |
INTEGER | (6.1) | TRUE | (6.1) |
Объявление константы связывает ее идентификатор с ее значением.
ОбъявлениеКонстанты | = | ИдентОпр "=" КонстантноеВыражение. |
КонстантноеВыражение | = | Выражение. |
Константное выражение - это выражение, которое может быть вычислено
по его тексту без фактического выполнения программы. Его операнды - константы
(Гл. 8) или стандартные функции (Гл.
10.3), которые могут быть вычислены во время компиляции.
Примеры объявлений констант:
N = 100
limit = 2*N - 1
fullSet = {MIN(SET) .. MAX(SET)}
Тип данных определяет набор значений, которые могут принимать переменные этого типа, и набор применимых операций. Объявление типа связывает идентификатор с типом. В случае структурированных типов (массивы и записи) объявление также определяет структуру переменных этого типа. Структурированный тип не может содержать сам себя.
ОбъявлениеТипа | = | ИдентОпр "=" Тип. |
Тип | = | УточнИдент | ТипМассив | ТипЗапись | ТипУказатель | ПроцедурныйТип. |
Table = ARRAY N OF REAL
Tree = POINTER TO Node
Node = RECORD
key : INTEGER;
left, right: Tree
END
CenterTree = POINTER TO CenterNode
CenterNode = RECORD (Node)
width: INTEGER;
subnode: Tree
END
Function = PROCEDURE(x: INTEGER): INTEGER
Основные типы обозначаются стандартными идентификаторами. Соответствующие операции определены в 8.2, а стандартные функции в 10.3. Предусмотрены следующие основные типы:
Типы от 3 до 5 - целые типы, типы 6 и 7 - вещественные типы, а вместе они называются числовыми типами. Эти типы образуют иерархию; больший тип поглощает меньший тип:
LONGREAL >= REAL >= LONGINT >= INTEGER >= SHORTINT
Массив - структура, состоящая из определенного количества элементов одинакового типа, называемого типом элементов. Число элементов массива называется его длиной. Элементы массива обозначаются индексами, которые являются целыми числами от 0 до длины массива минус 1.
ТипМассив | = | ARRAY [Длина {"," Длина}] OF Тип. |
Длина | = | КонстантноеВыражение. |
Тип вида
ARRAY L0, L1, ..., Ln OF T
понимается как сокращение
ARRAY L0 OF
ARRAY L1 OF
...
ARRAY Ln OF T
Массивы, объявленные без указания длины, называются открытыми массивами. Они могут использоваться только в качестве базового типа указателя (см. 6.4), типа элементов открытых массивов и типа формального параметра (см. 10.1). Примеры:
ARRAY 10, N OF INTEGER
ARRAY OF CHAR
Тип запись - структура, состоящая из фиксированного числа элементов, которые могут быть различных типов и называются полями. Объявление типа запись определяет имя и тип каждого поля. Область действия идентификаторов полей простирается от точки их объявления до конца объявления типа запись, но они также видимы внутри обозначений, ссылающихся на элементы переменных-записей (см. 8.1). Если тип запись экспортируется, то идентификаторы полей, которые должны быть видимы вне модуля, в котором объявлены, должны быть помечены. Они называются доступными полями; непомеченные элементы называются скрытыми полями.
ТипЗапись | = | RECORD ["(" БазовыйТип ")"] СписокПолей {";" СписокПолей} END. |
БазовыйТип | = | УточнИдент. |
СписокПолей | = | [СписокИдент ":" Тип]. |
Тип запись может быть объявлен как расширение другого типа запись. В примере
T0 = RECORD x: INTEGER END
T1 = RECORD (T0) y: REAL END
T1 - (непосредственное) расширение T0, а T0 - (непосредственный) базовый тип T1 (см. Прил. A). Расширенный тип T1 состоит из полей своего базового типа и полей, которые объявлены в T1. Все идентификаторы, объявленные в расширенной записи, должны быть отличны от идентификаторов, объявленных в записи(записях) ее базового типа.
Примеры объявлений типа запись:
RECORD
day, month, year: INTEGER
END
RECORD
name, firstname: ARRAY 32 OF CHAR;
age: INTEGER;
salary: REAL
END
Переменные-указатели типа P принимают в качестве значений указатели на переменные некоторого типа T. T называется базовым типом указателя типа P и должен быть типом массив или запись. Типы указатель заимствуют отношение расширения своих базовых типов: если тип T1 - расширение T и P1 - это тип POINTER TO T1, то P1 также является расширением P.
ТипУказатель = POINTER TO Тип.
Если p - переменная типа P = POINTER TO T, вызов
стандартной процедуры NEW(p) (см.
10.3) размещает переменную типа T в свободной памяти. Если T
- тип запись или тип массив с фиксированной длиной, размещение должно быть
выполнено вызовом NEW(p); если тип T - n-мерный открытый
массив, размещение должно быть выполнено вызовом NEW(p, e0, ..., en-1),
чтобы T был размещен с длинами, заданными выражениями e0, ...,
en-1. В любом случае указатель на размещенную переменную присваивается
p. Переменная p имеет тип P. Переменная p^ (динамическая
переменная), на которую ссылается p, имеет тип T.
Любая переменная-указатель может принимать значение NIL, которое не
указывает ни на какую переменную вообще.
Переменные процедурного типа T, имеют значением процедуру (или NIL). Если процедура P присваивается переменной типа T, списки формальных параметров (см. Гл. 10.1) P и T должны совпадать (см. Прил. A). P не должна быть стандартной или связанной с типом процедурой, и не может быть локальной в другой процедуре.
ПроцедурныйТип = PROCEDURE [ФормальныеПараметры].
Объявления переменных дают описание переменных, определяя идентификатор и тип данных для них.
ОбъявлениеПеременных = СписокИдент ":" Тип.
Переменные типа запись и указатель имеют как статический тип (тип, с которым они объявлены - называемый просто их типом), так и динамический тип (тип их значения во время выполнения). Для указателей и параметров-переменных типа запись динамический тип может быть расширением их статического типа. Статический тип определяет какие поля записи доступны. Динамический тип используется для вызова связанных с типом процедур (см. 10.2).
Примеры объявлений переменных (со ссылками на примеры из Гл. 6):
i, j, k: INTEGER
x, y: REAL
p, q: BOOLEAN
s: SET
F: Function
a: ARRAY 100 OF REAL
w: ARRAY 16 OF RECORD
name: ARRAY 32 OF CHAR;
count: INTEGER
END
t, c: Tree
Выражения - конструкции, задающие правила вычисления по значениям констант и текущим значениям переменных других значений путем применения операций и процедур-функций. Выражения состоят из операндов и операций. Круглые скобки могут использоваться для группировки операций и операндов.
За исключением конструкторов множества и литералов (чисел, символьных констант или строк), операнды представлены обозначениями. Обозначение содержит идентификатор константы, переменной или процедуры. Этот идентификатор может быть уточнен именем модуля (см. Гл. 4 и 11) и может сопровождаться селекторами, если обозначенный объект - элемент структуры.
Обозначение | = | УточнИдент { "." идент | "[" СписокВыражений "]" | "^" | "(" УточнИдент ")" }. |
СписокВыражений | = | Выражение {"," Выражение}. |
Если а - обозначение массива, a[e] означает элемент а,
чей индекс - текущее значение выражения e. Тип e должен быть
целым типом. Обозначение вида a[e0, e1, ..., en] применимо вместо
a[e0] [e1] ... [en]. Если r обозначает запись, то r.f
означает поле f записи r или процедуру f, связанную
с динамическим типом r (Гл. 10.2).
Если p обозначает указатель, p^ означает переменную, на которую
ссылается p. Обозначения p^.f и p^[e] могут быть сокращены
до p.f и p[e], то есть запись и индекс массива подразумевают
разыменование. Если a или r доступны только для чтения, то
a[e] и r.f также предназначены только для чтения.
Охрана типа v(T) требует, чтобы динамическим типом v был
T (или расширение T), то есть выполнение программы прерывается,
если динамический тип v - не T (или расширение T).
В пределах такого обозначения v воспринимается как имеющая статический
тип T. Охрана применима, если
Если обозначенный объект - константа или переменная, то обозначение ссылается на их текущее значение. Если он - процедура, то обозначение ссылается на эту процедуру, если только обозначение не сопровождается (возможно пустым) списком параметров. В последнем случае подразумевается активация процедуры и подстановка значения результата, полученного при ее исполнении. Фактические параметры должны соответствовать формальным параметрам как и при вызовах собственно процедуры (см. 10.1).
Примеры обозначений (со ссылками на примеры из Гл. 7):
i | (INTEGER) |
a[i] | (REAL) |
w[3].name[i] | (CHAR) |
t.left.right | (Tree) |
t(CenterTree).subnode | (Tree) |
В выражениях синтаксически различаются четыре класса операций с разными приоритетами (порядком выполнения). Операция ~ имеет самый высокий приоритет, далее следуют операции типа умножения, операции типа сложения и отношения. Операции одного приоритета выполняются слева направо. Например, x-y-z означает (x- y) -z.
Выражение | = | ПростоеВыражение [Отношение ПростоеВыражение]. |
ПростоеВыражение | = | ["+" | "-"] Слагаемое {ОперацияСложения Слагаемое}. |
Слагаемое | = | Множитель {ОперацияУмножения Множитель}. |
Множитель | = | Обозначение [ФактическиеПараметры] | число | символ | строка | NIL | Множество | "(" Выражение ")" | "~" Множитель. |
Множество | = | "{" [Элемент {"," Элемент}] "}". |
Элемент | = | Выражение [".." Выражение]. |
ФактическиеПараметры | = | "(" [СписокВыражений] ")". |
Отношение | = | "=" | "#" | "<" | "<=" | ">" | ">=" | IN | IS. |
ОперацияСложения | = | "+" | "-" | OR. |
ОперацияУмножения | = | "*" | "/" | DIV | MOD | "&". |
Предусмотренные операции перечислены в следующих таблицах. Некоторые операции применимы к операндам различных типов, обозначая разные действия. В этих случаях фактическая операция определяется типом операндов. Операнды должны быть совместимыми выражениями для данной операции (см. Прил. A).
OR | логическая дизъюнкция | p OR q | "если p, то TRUE, иначе q" |
& | логическая конъюнкция | p & q | "если p то q, иначе FALSE" |
~ | отрицание | ~p | "не p" |
Эти операции применимы к операндам типа BOOLEAN и дают результат типа BOOLEAN.
+ | сумма - разность |
* | произведение |
/ | вещественное деление |
DIV | деление нацело |
MOD | остаток |
Операции +, -, *, и / применимы к операндам числовых типов. Тип их результата - тип того операнда, который поглощает тип другого операнда, кроме деления (/), чей результат - наименьший вещественный тип, который поглощает типы обоих операндов. При использовании в качестве одноместной операции "-" обозначает перемену знака, а "+" - тождественную операцию. Операции DIV и MOD применимы только к целочисленным операндам. Они связаны следующими формулами, определенными для любого x и положительного делителя y:
x = (x DIV y) * y + (x MOD y)
0 < = (x MOD y) < y
Примеры:
x |
y |
x DIV y |
x MOD y |
5 |
3 |
1 |
2 |
-5 |
3 |
-2 |
1 |
+ объединение
- разность (x - y = x * (-y))
* пересечение
/ симметрическая разность множеств (x / y = (x-y) + (y-x))
Эти операции применимы к операндам типа SET и дают результат типа SET. Одноместный "минус" обозначает дополнение x, то есть -x это множество целых между 0 и MAX(SET), которые не являются элементами x. Операции с множествами не ассоциативны ((a+b)-c # a+(b-c)). Конструктор множества задает значение множества списком элементов, заключенным в фигурные скобки. Элементы должны быть целыми в диапазоне 0..MAX(SET). Диапазон a..b обозначает все целые числа в интервале [a, b].
= | равно |
# | не равно |
< | меньше |
<= | меньшее или равно |
> | больше |
>= | больше или равно |
IN | принадлежность множеству |
IS | проверка типа |
Отношения дают результат типа BOOLEAN. Отношения =, #, <, <=, >, и >= применимы к числовым типам, типу CHAR, строкам и символьным массивам, содержащим в конце 0X. Отношения = и # кроме того применимы к типам BOOLEAN и SET, а также к указателям и процедурным типам (включая значение NIL). x IN s означает "x является элементом s". x должен быть целого типа, а s - типа SET. v IS T означает "динамический тип v есть T (или расширение T)" и называется проверкой типа. Проверка типа применима, если
Примеры выражений (со ссылками на примеры из Гл. 7):
1991 | INTEGER |
i DIV 3 | INTEGER |
~p OR q | BOOLEAN |
(i+j) * (i-j) | INTEGER |
s - {8, 9, 13} | SET |
i + x | REAL |
a[i+j] * a[i-j] | REAL |
(0<=i) & (i<100) | BOOLEAN |
t.key = 0 | BOOLEAN |
k IN {i..j-1} | BOOLEAN |
w[i].name <= "John" | BOOLEAN |
t IS CenterTree | BOOLEAN |
Операторы обозначают действия. Есть простые и структурные операторы. Простые операторы не содержат в себе никаких частей, которые являются самостоятельными операторами. Простые операторы - присваивание, вызов процедуры, операторы возврата и выхода. Структурные операторы состоят из частей, которые являются самостоятельными операторами. Они используются, чтобы выразить последовательное и условное, выборочное и повторное исполнение. Оператор также может быть пустым, в этом случае он не обозначает никакого действия. Пустой оператор добавлен, чтобы упростить правила пунктуации в последовательности операторов.
Оператор =
[Присваивание | ВызовПроцедуры
| ОператорIf | ОператорCase | ОператорWhile | ОператорRepeat
| ОператорFor | ОператорLoop | ОператорWith
| EXIT | RETURN [Выражение]].
Присваивание заменяет текущее значение переменной новым значением, определяемым выражением. Выражение должно быть совместимо по присваиванию с переменной (см. Приложение. A). Знаком операции присваивания является ":=", который читается "присвоить".
Присваивание = Обозначение ":=" Выражение.
Если выражение e типа Te присваивается переменной v типа Tv, имеет место следующее:
Примеры присваиваний (со ссылками на примеры из Гл. 7):
i := 0
p := i = j
x := i + 1
k := log2(i+j)
F := log2 (* см. 10.1
*)
s := {2, 3, 5, 7, 11, 13}
a[i] := (x+y) * (x-y)
t.key := i
w[i+1].name := "John"
t := c
Вызов процедуры активирует процедуру. Он может содержать список фактических
параметров, которые заменяют соответствующие формальные параметры, определенные
в объявлении процедуры (см. Гл. 10).
Соответствие устанавливается в порядке следования параметров в списках
фактических и формальных параметров. Имеются два вида параметров: параметры-переменные
и параметры-значения.
Если формальный параметр - параметр-переменная, соответствующий фактический
параметр должен быть обозначением переменной. Если фактический параметр
обозначает элемент структурной переменной, селекторы компонент вычисляются,
когда происходит замена формальных параметров фактическими, то есть перед
выполнением процедуры. Если формальный параметр - параметр-значение, соответствующий
фактический параметр должен быть выражением. Это выражение вычисляется
перед вызовом процедуры, а полученное в результате значение присваивается
формальному параметру (см. также 10.1).
ВызовПроцедуры = Обозначение [ФактическиеПараметры].
Примеры:
Последовательность операторов, разделенных точкой с запятой, означает поочередное выполнение действий, заданных составляющими операторами.
ПоследовательностьОператоров = Оператор {";" Оператор}.
ОператорIf =
IF Выражение THEN ПоследовательностьОператоров
{ELSIF Выражение THEN ПоследовательностьОператоров}
[ELSE ПоследовательностьОператоров]
END.
Операторы if задают условное выполнение входящих в них последовательностей операторов. Логическое выражение, предшествующие последовательности операторов, будем называть условием (в оригинале - guard. Прим. перев.) Условия проверяются последовательно одно за другим, пока очередное не окажется равным TRUE, после чего выполняется связанная с этим условием последовательность операторов. Если ни одно условие не удовлетворено, выполняется последовательность операторов, записанная после слова ELSE, если оно имеется.
Пример:
IF (ch >= "A") & (ch <= "Z") THEN ReadIdentifier
ELSIF (ch >= "0") & (ch <= "9") THEN ReadNumber
ELSIF (ch = " ' ") OR (ch = ' " ') THEN ReadString
ELSE SpecialCharacter
END
Операторы case определяют выбор и выполнение последовательности операторов по значению выражения. Сначала вычисляется выбирающее выражение, а затем выполняется та последовательность операторов, чей список меток варианта содержит полученное значение. Выбирающее выражение должно быть такого целого типа, который поглощает типы всех меток вариантов, или и выбирающее выражение и метки вариантов должны иметь тип CHAR. Метки варианта - константы, и ни одно из их значений не должно употребляться больше одного раза. Если значение выражения не совпадает с меткой ни одного из вариантов, выбирается последовательность операторов после слова ELSE, если оно есть, иначе программа прерывается.
ОператорCase | = | CASE Выражение OF Вариант {" | " Вариант} [ELSE ПоследовательностьОператоров ] END. |
Вариант | = | [СписокМетокВарианта ":" ПоследовательностьОператоров]. |
СписокМетокВарианта | = | МеткиВарианта {"," МеткиВарианта }. |
МеткиВарианта | = | КонстантноеВыражение [".." КонстантноеВыражение]. |
Пример:
CASE ch OF
"A" .. "Z": ReadIdentifier
| "0" .. "9": ReadNumber
| "'", '"' : ReadString
ELSE SpecialCharacter
END
Операторы while задают повторное выполнение последовательности операторов, пока логическое выражение (условие) остается равным TRUE. Условие проверяется перед каждым выполнением последовательности операторов.
ОператорWhile = WHILE Выражение DO ПоследовательностьОператоров END.
Примеры:
WHILE i > 0 DO i := i DIV 2; k := k + 1 END
WHILE (t # NIL) & (t.key # i) DO t := t.left END
Оператор repeat определяет повторное выполнение последовательности операторов пока условие, заданное логическим выражением, не удовлетворено. Последовательность операторов выполняется по крайней мере один раз.
ОператорRepeat = REPEAT ПоследовательностьОператоров UNTIL Выражение.
Оператор for определяет повторное выполнение последовательности операторов фиксированное число раз для прогрессии значений целочисленной переменной, называемой управляющей переменной оператора for.
ОператорFor | = | FOR идент ":=" Выражение TO Выражение [BY КонстантноеВыражение]
DO ПоследовательностьОператоров END. |
Оператор
FOR v := beg TO end BY step DO statements END
эквивалентен
temp := end; v := beg;
IF step > 0 THEN
WHILE v <= temp DO statements; v := v + step END
ELSE
WHILE v >= temp DO statements; v := v + step END
END
temp и v имеют одинаковый тип. Шаг (step) должен быть отличным от нуля константным выражением. Если шаг не указан, он предполагается равным 1.
Примеры:
FOR i := 0 TO 79 DO k := k + a[i] END
FOR i := 79 TO 1 BY -1 DO a[i] := a[i-1] END
Оператор loop определяет повторное выполнение последовательности операторов. Он завершается после выполнения оператора выхода внутри этой последовательности (см. 9.10).
ОператорLoop = LOOP ПоследовательностьОператоров END.
Пример:
LOOP
ReadInt(i);
IF i < 0 THEN EXIT END;
WriteInt(i)
END
Операторы loop полезны, чтобы выразить повторения с несколькими точками выхода, или в случаях, когда условие выхода находится в середине повторяемой последовательности операторов.
Оператор возврата выполняет завершение процедуры. Он обозначается словом
RETURN, за которым следует выражение, если процедура является процедурой-функцией.
Тип выражения должен быть совместим
по присваиванию (см. Приложение
A) с типом результата, определенным в заголовке процедуры (см. Гл.
10).
Процедуры-функции должны быть завершены оператором возврата, задающим
значение результата. В собственно процедурах оператор возврата подразумевается
в конце тела процедуры. Любой явный оператор появляется, следовательно,
как дополнительная (вероятно, для исключительной ситуации) точка завершения.
Оператор выхода обозначается словом EXIT. Он определяет завершение
охватывающего оператора loop и продолжение выполнения программы с оператора,
следующего за оператором loop. Оператор выхода связан с содержащим его
оператором loop контекстуально, а не синтаксически.
Операторы with выполняют последовательность операторов в зависимости от результата проверки типа и применяют охрану типа к каждому вхождению проверяемой переменной внутри этой последовательности операторов.
ОператорWith | = | WITH Охрана DO ПоследовательностьОператоров {"|" Охрана DO ПоследовательностьОператоров} [ELSE ПоследовательностьОператоров] END. |
Охрана | = | УточнИдент ":" УточнИдент. |
Если v - параметр-переменная типа запись или переменная-указатель, и если ее статический тип T0, оператор
WITH v: T1 DO S1 | v: T2 DO S2 ELSE S3 END
имеет следующий смысл: если динамический тип v - T1, то выполняется последовательность операторов S1 в которой v воспринимается так, будто она имеет статический тип T1; иначе, если динамический тип v - T2, выполняется S2, где v воспринимается как имеющая статический тип T2; иначе выполняется S3. T1 и T2 должны быть расширениями T0. Если ни одна проверка типа не удовлетворена, а ELSE отсутствует, программа прерывается.
Пример:
WITH t: CenterTree DO i := t.width; c := t.subnode END
Объявление процедуры состоит из заголовка процедуры и тела
процедуры. Заголовок определяет имя процедуры и формальные
параметры. Для связанных с типом процедур в объявлении также определяется
параметр-приемник. Тело содержит объявления и операторы. Имя процедуры
повторяется в конце объявления процедуры.
Имеются два вида процедур: собственно процедуры и процедуры-
функции. Последние активизируются обозначением функции как часть выражения
и возвращают результат, который является операндом выражения. Собственно
процедуры активизируются вызовом процедуры. Процедура является процедурой-функцией,
если ее формальные параметры задают тип результата. Тело процедуры-функции
должно содержать оператор возврата, который определяет результат.
Все константы, переменные, типы и процедуры, объявленные внутри тела
процедуры, локальны в процедуре. Поскольку процедуры тоже могут
быть объявлены как локальные объекты, объявления процедур могут быть вложенными.
Вызов процедуры изнутри ее объявления подразумевает рекурсивную активацию.
Объекты, объявленные в окружении процедуры, также видимы в тех частях
процедуры, в которых они не перекрыты локально объявленным объектом с тем
же самым именем.
ОбъявлениеПроцедуры | = | ЗаголовокПроцедуры ";" ТелоПроцедуры идент. |
ЗаголовокПроцедуры | = | PROCEDURE [Приемник] ИдентОпр [ФормальныеПараметры]. |
ТелоПроцедуры | = | ПоследовательностьОбъявлений [BEGIN ПоследовательностьОператоров] END. |
ПослОбъявлений | = | {CONST {ОбъявлениеКонстант ";"} | TYPE{ОбъявлениеТипов ";"} | VAR {ОбъявлениеПеременных ";"}} {ОбъявлениеПроцедуры ";" | ОпережающееОбъявление";"}. |
ОпережающееОбъявление | = | PROCEDURE"^" [Приемник] ИдентОпр [ФормальныеПараметры]. |
Если объявление процедуры содержит параметр-приемник, процедура рассматривается как связанная с типом (см. 10.2). Опережающее объявление служит чтобы разрешить ссылки на процедуру, чье фактическое объявление появляется в тексте позже. Списки формальных параметров опережающего объявления и фактического объявления должны быть идентичны.
Формальные параметры - идентификаторы, объявленные в списке формальных параметров процедуры. Им соответствуют фактические параметры, которые задаются при вызове процедуры. Подстановка фактических параметров вместо формальных происходит при вызове процедуры. Имеются два вида параметров: параметры-значения и параметры-переменные, обозначаемые в списке формальных параметров отсутствием или наличием ключевого слова VAR. Параметры-значения это локальные переменные, которым в качестве начального присваивается значение соответствующего фактического параметра. Параметры-переменные соответствуют фактическим параметрам, которые являются переменными, и означают эти переменные. Область действия формального параметра простирается от его объявления до конца блока процедуры, в котором он объявлен. Процедура-функция без параметров должна иметь пустой список параметров. Она должна вызываться обозначением функции, чей список фактических параметров также пуст. Тип результата процедуры не может быть ни записью, ни массивом.
ФормальныеПараметры | = | "(" [СекцияФП {";" СекцияФП }] ")" [":" УточненныйИдент]. |
СекцияФП | = | [VAR] идент {"," идент} ":" Тип. |
Пусть Tf - тип формального параметра f (не открытого массива)
и Ta - тип соответствующего фактического параметра a. Для
параметров-переменных Ta и Tf должны быть одинаковыми типами
или Tf должен быть типом запись, а Ta - расширением Tf.
Для параметров-значений а должен быть совместим
по присваиванию с f (см. Прил.
A).
Если Tf - открытый массив, то a должен быть совместимым
массивом для f (см. Прил.
A). Длина f становится равной длине a.
Примеры объявлений процедур:
PROCEDURE ReadInt (VAR x: INTEGER);
VAR i: INTEGER; ch: CHAR;
BEGIN i := 0; Read(ch);
WHILE ("0" <= ch) & (ch <= "9") DO
i := 10*i + (ORD(ch)-ORD("0")); Read(ch)
END;
x := i
END ReadInt
PROCEDURE WriteInt (x: INTEGER); (*0 <= x <100000*)
VAR i: INTEGER; buf: ARRAY 5 OF INTEGER;
BEGIN i := 0;
REPEAT buf[i] := x MOD 10; x := x DIV 10; INC(i) UNTIL x = 0;
REPEAT DEC(i); Write(CHR(buf[i] + ORD("0"))) UNTIL i = 0
END WriteInt
PROCEDURE WriteString (s: ARRAY OF CHAR);
VAR i: INTEGER;
BEGIN i := 0;
WHILE (i < LEN(s)) & (s[i] # 0X) DO Write(s[i]); INC(i) END
END WriteString;
PROCEDURE log2 (x: INTEGER): INTEGER;
VAR y: INTEGER; (*предполагается x>0*)
BEGIN
y := 0; WHILE x > 1 DO x := x DIV 2; INC(y) END;
RETURN y
END log2
Глобально объявленные процедуры могут быть ассоциированы с типом запись, объявленным в том же самом модуле. В этом случае говорится, что процедуры связаны с типом запись. Связь выражается типом приемника в заголовке объявления процедуры. Приемник может быть или параметром-переменной типа Т, если Т - тип запись, или параметром-значением типа POINTER TO T (где T - тип запись). Процедура, связанная с типом T, рассматривается как локальная для него.
ЗаголовокПроцедуры | = | PROCEDURE [Приемник] ИдентОпр [ФормальныеПараметры]. |
Приемник | = | "(" [VAR] имя ":" имя ")". |
Если процедура P связана с типом T0, она неявно также
связана с любым типом T1, который является расширением T0.
Однако процедура P' (с тем же самым именем что и P) может
быть явно связана с T1, перекрывая в этом случае связывание c P.
P' рассматривается как переопределение P для T1.
Формальные параметры P и P' должны совпадать
(см. Прил. A). Если P
и T1 экспортируются (см. Главу
4), P' также должна экспортироваться.
Если v - обозначение, а P - связанная процедура, то v.P
обозначает процедуру P, связанную с динамическим типом v.
Заметим, что это может быть процедура, отличная от той, что связана со
статическим типом v. v передается приемнику процедуры P
согласно правилам передачи параметров, определенным в Главе
10.1.
Если r - параметр-приемник, объявленный с типом T, r.P^ обозначает
(переопределенную) процедуру P, связанную с базовым для T
типом. В опережающем объявлении связанной процедуры и в фактическом объявлении
процедуры параметр-приемник должен иметь одинаковый
тип. Списки формальных параметров в обоих объявлениях должны быть идентичны.
Примеры:
PROCEDURE (t: Tree) Insert (node: Tree);
VAR p, father: Tree;
BEGIN p := t;
REPEAT father := p;
IF node.key = p.key THEN RETURN END;
IF node.key < p.key THEN p := p.left ELSE p := p.right END
UNTIL p = NIL;
IF node.key < father.key THEN father.left := node ELSE father.right
:= node END;
node.left := NIL; node.right := NIL
END Insert;
PROCEDURE (t: CenterTree) Insert (node: Tree); (*переопределение*)
BEGIN
WriteInt(node(CenterTree).width);
t.Insert^ (node) (* вызывает процедуру Insert, связанную с Tree *)
END Insert;
Следующая таблица содержит список стандартных процедур. Некоторые процедуры - обобщенные, то есть они применимы к операндам нескольких типов. Буква v обозначает переменную, x и n - выражения, T - тип.
Процедуры-функции
Собственно процедуры
COPY разрешает присваивание строки или символьного массива, содержащего ограничитель 0X, другому символьному массиву. В случае необходимости, присвоенное значение усекается до длины получателя минус один. Получатель всегда будет содержать 0X как ограничитель. В ASSERT(x, n) и HALT(n), интерпретация n зависит от реализации основной системы.
Модуль - совокупность объявлений констант, типов, переменных и процедур вместе с последовательностью операторов, предназначенных для присваивания начальных значений переменным. Модуль представляет собой текст, который является единицей компиляции.
Модуль | = | MODULE идент ";" [СписокИмпорта] ПоследовательностьОбъявлений
[BEGIN ПоследовательностьОператоров] END идент ".". |
СписокИмпорта | = | IMPORT Импорт {"," Импорт} ";". |
Импорт | = | [идент ":="] идент. |
Список импорта определяет имена импортируемых модулей. Если модуль A
импортируется модулем M, и A экспортирует идентификатор x,
то x упоминается внутри M как A.x. Если A импортируется
как B:=A, объект x должен вызываться как B.x. Это
позволяет использовать короткие имена-псевдонимы в уточненных идентификаторах.
Модуль не должен импортировать себя. Идентификаторы, которые экспортируются
(то есть должны быть видимы в модулях-импортерах) нужно отметить экспортной
меткой в их объявлении (см. Главу 4).
Последовательность операторов после символа BEGIN выполняется, когда
модуль добавляется к системе (загружается). Это происходит после загрузки
импортируемых модулей. Отсюда следует, тот циклический импорт модулей запрещен.
Отдельные (не имеющие параметров и экспортированные) процедуры могут быть
активированы из системы. Эти процедуры служат командами (см. Приложение
D1).
MODULE Trees; (* экспорт: Tree, Node, Insert,
Search, Write, Init *)
IMPORT Texts, Oberon; (* экспорт только для чтения: Node.name *)
TYPE
Tree* = POINTER TO Node;
Node* = RECORD
name-: POINTER TO ARRAY OF CHAR;
left, right: Tree
END;
VAR w: Texts.Writer;
PROCEDURE (t: Tree) Insert* (name: ARRAY OF CHAR);
VAR p, father: Tree;
BEGIN p := t;
REPEAT father := p;
IF name = p.name^ THEN RETURN END;
IF name < p.name^ THEN p := p.left ELSE p := p.right
END
UNTIL p = NIL;
NEW(p); p.left := NIL; p.right := NIL; NEW(p.name, LEN(name)+1);
COPY(name, p.name^);
IF name < father.name^ THEN father.left := p ELSE father.right
:= p END
END Insert;
PROCEDURE (t: Tree) Search* (name: ARRAY OF CHAR): Tree;
VAR p: Tree;
BEGIN p := t;
WHILE (p # NIL) & (name # p.name^) DO
IF name < p.name^ THEN p := p.left ELSE p := p.right
END
END;
RETURN p
END Search;
PROCEDURE (t: Tree) Write*;
BEGIN
IF t.left # NIL THEN t.left.Write END;
Texts.WriteString(w, t.name^); Texts.WriteLn(w); Texts.Append(Oberon.Log,
w.buf);
IF t.right # NIL THEN t.right.Write END
END Write;
PROCEDURE Init* (t: Tree);
BEGIN NEW(t.name, 1); t.name[0] := 0X; t.left := NIL; t.right := NIL
END Init;
BEGIN Texts.OpenWriter(w)
END Trees.
Целые типы | SHORTINT, INTEGER, LONGINT |
Вещественные типы | REAL, LONGREAL |
Числовые типы | Целые типы, вещественные типы |
Две переменные a и b с типами Ta и Tb имеют одинаковый тип, если
Два типа Ta, и Tb равны, если
Числовые типы поглощают (значения) меньших числовых типов согласно
следующей иерархии:
LONGREAL >= REAL >= LONGINT >= INTEGER >= SHORTINT
В объявлении типа Tb = RECORD (Ta) ... END, Tb - непосредственное расширение Ta, а Ta - непосредственный базовый тип Tb. Тип Tb есть расширение типа Ta (Ta есть базовый тип Tb) если
Если Pa = POINTER TO Ta и Pb = POINTER TO Tb, то Pb есть расширение Pa (Pa есть базовый тип Pb), если Tb есть расширение Ta.
Выражение e типа Te совместимо по присваиванию с переменной v типа Tv, если выполнено одно из следующих условий:
Фактический параметр a типа Ta является совместимым массивом для формального параметра f типа Tf если
Для данной операции операнды являются совместимыми выражениями, если их типы соответствуют следующей таблице (в который указан также тип результата выражения). Символьные массивы, которые сравниваются, должны содержать в качестве ограничителя 0X. Тип T1 должен быть расширением типа T0:
операция | первый операнд | второй операнд | тип результата |
+ - * | числовой | числовой | наименьший числовой тип, поглощающий оба операнда |
/ | числовой | числовой | наименьший вещественный тип, поглощающий оба операнда |
+ - * / | SET | SET | SET |
DIV MOD | целый | целый | наименьший целый тип, поглощающий оба операнда |
OR & ~ | BOOLEAN | BOOLEAN | BOOLEAN |
= # < <= > >= | числовой CHAR символьный массив, строка |
числовой CHAR символьный массив, строка |
BOOLEAN BOOLEAN BOOLEAN |
= # | BOOLEAN SET NIL, тип указатель T0 или T1 процедурный тип T, NIL |
BOOLEAN SET NIL, тип указатель T0 или T1 процедурный тип T, NIL |
BOOLEAN BOOLEAN BOOLEAN BOOLEAN |
IN | целый | SET | BOOLEAN |
IS | тип T0 | тип T1 | BOOLEAN |
Два списка формальных параметров совпадают если
Модуль | = | MODULE идент ";" [СписокИмпорта] ПослОбъявл [BEGIN ПослОператоров] END идент ".". |
СписокИмпорта | = | IMPORT [идент ":="] идент {"," [идент ":="] идент} ";". |
ПослОбъявл | = | { CONST {ОбъявлКонст ";" } | TYPE {ОбъявлТипа ";" } | VAR {ОбъявлПерем ";" }} {ОбъявлПроц ";" | ОпережающееОбъяв";"}. |
ОбъявлКонст | = | ИдентОпр "=" КонстВыраж. |
ОбъявлТипа | = | ИдентОпр "=" Тип. |
ОбъявлПерем | = | СписокИдент ":" Тип. |
ОбъявлПроц | = | PROCEDURE [Приемник] ИдентОпр [ФормальныеПарам]";" ПослОбъявл [BEGIN ПослОператоров] END идент. |
ОпережающееОбъяв | = | PROCEDURE "^" [Приемник] ИдентОпр [ФормальныеПарам]. |
ФормальныеПарам | = | "(" [СекцияФП {";" СекцияФП}] ")" [":" УточнИдент]. |
СекцияФП | = | [VAR] идент {"," идент} ":" Тип. |
Приемник | = | "(" [VAR] идент ":" идент ")". |
Тип | = | УточнИдент | ARRAY [КонстВыраж {"," КонстВыраж}] OF Тип | RECORD ["("УточнИдент")"] СписокПолей {";" СписокПолей} END | POINTER TO Тип | PROCEDURE [ФормальныеПарам]. |
СписокПолей | = | [СписокИдент ":" Тип]. |
ПослОператоров | = | Оператор {";" Оператор}. |
Оператор | = | [ Обозначение ":=" Выраж | Обозначение ["(" [СписокВыраж] ")"] | IF Выраж THEN ПослОператоров {ELSIF Выраж THEN ПослОператоров} [ELSE ПослОператоров] END | CASE Выраж OF Вариант {"|" Вариант} [ELSE ПослОператоров] END | WHILE Выраж DO ПослОператоров END | REPEAT ПослОператоров UNTIL Выраж | FOR идент ":=" Выраж TO Выраж [BY КонстВыраж] DO ПослОператоров END | LOOP ПослОператоров END | WITH Охрана DO ПослОператоров {"|" Охрана DO ПослОператоров} [ELSE ПослОператоров] END | EXIT | RETURN [Выраж] ]. |
Вариант | = | [МеткиВарианта {"," МеткиВарианта} ":" ПослОператоров]. |
МеткиВарианта | = | КонстВыраж [".." КонстВыраж]. |
Охрана | = | УточнИдент ":" УточнИдент. |
КонстВыраж | = | Выраж. |
Выраж | = | ПростоеВыраж [Отношение ПростоеВыраж]. |
ПростоеВыраж | = | ["+" | "-"] Слагаемое {ОперСлож Слагаемое}. |
Слагаемое | = | Множитель {ОперУмн Множитель}. |
Множитель | = | Обозначение ["(" [СписокВыраж] ")"] | число | символ | строка | NIL | Множество | "(" Выраж ")" | " ~ " Множитель. |
Множество | = | "{" [Элемент {"," Элемент}] "}". |
Элемент | = | Выраж [".." Выраж]. |
Отношение | = | "=" | "#" | "<" | "<=" | ">" | ">=" | IN | IS. |
ОперСлож | = | "+" | "-" | OR. |
ОперУмн | = | "*" | "/" | DIV | MOD | "&". |
Обозначение | = | УточнИдент {"." идент | "[" СписокВыраж "]" | "^" | "(" УточнИдент ")"}. |
СписокВыраж | = | Выраж {"," Выраж}. СписокИдент = ИдентОпр {"," ИдентОпр}. |
УточнИдент | = | [идент "."] идент. ИдентОпр = идент [ "*" | "-" ]. |
Модуль SYSTEM содержит некоторые типы и процедуры, которые необходимы
для реализации операций низкого уровня, специфичных для данного
компьютера и/или реализации. Они включают, например, средства для доступа
к устройствам, которые управляются компьютером, и средства, позволяющие
обойти правила совместимости типов, наложенные определением языка. Настоятельно
рекомендуется ограничить использование этих средств специфическими модулями
(модулями низкого уровня). Такие модули непременно являются непереносимыми,
но легко распознаются по идентификатору SYSTEM, появляющемуся в их списке
импорта. Следующие спецификации действительны для реализации Оберон-2 на
компьютере Ceres.
Модуль SYSTEM экспортирует тип BYTE со следующими характеристиками:
переменным типа BYTE можно присваивать значения переменных типа CHAR или
SHORTINT. Если формальный параметр-переменная имеет тип ARRAY OF BYTE,
то соответствующий фактический параметр может иметь любой тип.
Другой тип, экспортируемый модулем SYSTEM, - тип PTR. Переменным типа PTR
могут быть присвоены значения переменных-указателей любого типа. Если формальный
параметр-переменная имеет тип PTR, фактический параметр может быть указателем
любого типа.
Процедуры, содержащиеся в модуле SYSTEM, перечислены в таблицах. Большинство
их соответствует одиночным командам и компилируются непосредственно в машинный
код. О деталях читатель может справиться в описании процессора. В таблице
v обозначает переменную, x, y, a, и n - выражения,
а T - тип.
Процедуры-функции
Название | Типы аргументов | Тип результата | Функция |
ADR(v) | любой | LONGINT | адрес переменной v |
BIT(a,n) | a: LONGINT; n:целый | BOOLEAN | n-й бит Память[a] |
CC(n) | целая константа | BOOLEAN | условие n (0 <= n <= 15) |
LSH(x,n) | x: целый, CHAR, BYTE; n: целый | совпадает с типом x | логический сдвиг |
ROT(x,n) | x: целый, CHAR, BYTE; n: целый | совпадает с типом x | циклический сдвиг |
VAL(T,x) | T, x: любого типа | T | x интерпретируется как значение типа T |
Собственно процедуры
Название | Типы аргументов | Функция |
GET(a, v) | a: LONGINT; v: любой основной тип, указатель, процедурный тип | v := Память[a] |
PUT(a, x) | a: LONGINT; x: любой основной тип, указатель, процедурный тип | Память[a] := x |
GETREG(n, v) | n: целая константа; v: любой основной тип, указатель, процедурный тип | v := Регистр n |
PUTREG(n, x) | n: целая константа; x: любой основной тип, указатель, процедурный тип | Регистр n := x |
MOVE(a0,a1,n) | a0, a1: LONGINT; n: целый | Память[a1..a1+n-1] := Память[a0..a0+n-1] |
NEW(v, n) | v: любой указатель; n: целый | размещает блок памяти размером n байт; присваивает его адрес переменной v |
Программы на Обероне-2 обычно выполняются в среде, которая обеспечивает активацию команд, сбор мусора, динамическую загрузку модулей и определенные структуры данных времени выполнения. Не являясь частью языка, эта среда способствует увеличению мощности Оберона-2 и до некоторой степени подразумевается при определении языка. В приложении D описаны существенные особенности типичной Оберон-среды и даны советы по peализации. Подробности можно найти в [1], [2], и [3].
Команда - это любая процедура P, которая экспортируется модулем
M и не имеет параметров. Она обозначается M.P и может быть
активирована под таким именем из оболочки операционной системы. В Обероне
пользователь вызывает команды вместо программ или модулей. Это дает лучшую
структуру управления и предоставляет модули с несколькими точками входа.
Когда вызывается команда M.P, модуль M динамически загружается,
если он уже не был в памяти (см. D2)
и выполняется процедура P. Когда P завершается, M
остается загруженным. Все глобальные переменные и структуры данных, которые
могут быть достигнуты через глобальные переменные-указатели в M,
сохраняют значения. Когда P (или другая команда M) вызывается
снова, она может продолжать использовать эти значения.
Следующий модуль демонстрирует использование команд. Он реализует
абстрактную структуру данных Counter, которая содержит переменную-счетчик
и обеспечивает команды для увеличения и печати его значения.
MODULE Counter;
IMPORT Texts, Oberon;
VAR
counter: LONGINT;
w: Texts.Writer;
PROCEDURE Add*; (* получает числовой аргумент из командной строки *)
VAR s: Texts.Scanner;
BEGIN
Texts.OpenScanner(s, Oberon.Par.text, Oberon.Par.pos);
Texts.Scan(s);
IF s.class = Texts.Int THEN INC(counter, s.i) END
END Add;
PROCEDURE Write*;
BEGIN
Texts.WriteInt(w, counter, 5); Texts.WriteLn(w);
Texts.Append(Oberon.Log, w.buf)
END Write;
BEGIN counter := 0; Texts.OpenWriter(w)
END Counter.
Пользователь может выполнить следующие две команды:
Counter.Add n Добавляет значение n к переменной counter
Counter.Write Выводит текущее значение counter на экран
Так как команды не содержат параметров, они должны получать свои аргументы
из операционной системы. Вообще команды вольны брать параметры отовсюду
(например из текста после команды, из текущего выбранного фрагмента или
из отмеченного окна просмотра). Команда Add использует сканер (тип
данных, обеспечиваемый Оберон-системой) чтобы читать значение, которое
следует за нею в командной строке.
Когда Counter.Add вызывается впервые, модуль Counter загружается
и выполняется его тело. Каждое обращение Counter.Add n увеличивает
переменную counter на n. Каждое обращение Counter.Write
выводит текущее значение counter на экран.
Поскольку модуль остается загруженным после выполнения его команд,
должен существовать явный способ выгрузить его (например, когда пользователь
хочет заменить загруженную версию перекомпилированной версией). Оберон-система
содержит команду, позволяющую это сделать.
Загруженный модуль может вызывать команду незагруженного модуля, задавая
ее имя как строку. Специфицированный модуль при этом динамически загружается
и выполняется заданная команда. Динамическая загрузка позволяет пользователю
запустить программу как небольшой набор базисных модулей и расширять ее,
добавляя последующие модули во время выполнения по мере необходимости.
Модуль M0 может вызвать динамическую загрузку модуля M1
без того, чтобы импортировать его. M1 может, конечно, импортировать
и использовать M0, но M0 не должен знать о существовании
M1. M1 может быть модулем, который спроектирован и реализован
намного позже M0.
В Обероне-2 стандартная процедура NEW используется, чтобы распределить
блоки данных в свободной памяти. Нет, однако, никакого способа явно освободить
распределенный блок. Взамен Оберон-среда использует сборщик мусора
чтобы найти блоки, которые больше не используются и сделать их снова доступными
для распределения. Блок считается используемым только если он может быть
достигнут через глобальную переменную-указатель по цепочке указателей.
Разрыв этой цепочки (например, установкой указателя в NIL) делает блок
утилизируемым.
Сборщик мусора освобождает программиста от нетривиальной задачи правильного
освобождения структур данных и таким образом помогает избегать ошибок.
Возникает, однако, необходимость иметь информацию о динамических данных
во время выполнения (см. D5).
Интерфейс модуля (объявления экспортируемых объектов) извлекается из модуля так называемым смотрителем, который является отдельным инструментом среды Оберон. Например, смотритель производит следующий интерфейс модуля Trees из Гл. 11.
DEFINITION Trees;
TYPE
Tree = POINTER TO Node;
Node = RECORD
name: POINTER TO ARRAY OF CHAR;
PROCEDURE (t: Tree) Insert (name: ARRAY OF CHAR);
PROCEDURE (t: Tree) Search (name: ARRAY OF CHAR): Tree;
PROCEDURE (t: Tree) Write;
END;
PROCEDURE Init (VAR t: Tree);
END Trees.
Для типа запись смотритель также собирает все процедуры, связанные с этим типом, и показывает их заголовки в объявлении типа запись.
Некоторая информация о записях должна быть доступна во время выполнения:
Динамический тип записей необходим для проверки и охраны типа. Таблица
с адресами процедур, связанных с записью, необходима для их вызова. Наконец,
сборщик мусора нуждается в информации о расположении указателей в динамически
распределенных записях. Вся эта информация сохраняется в так называемых
дескрипторах типа. Один дескриптор необходим во время выполнения
для каждого типа записи. Ниже показана возможная реализация дескрипторов
типа.
Динамический тип записи соответствует адресу дескриптора типа. Для динамически
распределенных записей этот адрес сохраняется в так называемом теге
типа, который предшествует фактическим данным записи и является невидимым
для программиста. Если t - переменная типа CenterTree (см.
пример в Гл. 6), рисунок D5.1 показывает одну
из возможных реализаций структур данных времени выполнения.
Рис. D5.1 переменная t типа CenterTree, запись t^, на которую она указывает, и дескриптор типа
Поскольку и таблица адресов процедур и таблица смещений указателей должны
иметь фиксированное смещение относительно адреса дескриптора типа, и поскольку
они могут расти, когда тип расширяется и добавляются новые процедуры и
указатели, то таблицы размещены в противоположных концах дескриптора типа
и растут в разных направлениях.
Связанная с типом процедура t.P вызывается как t^.tag^.ProcTab[IndexP].
Индекс таблицы процедур для каждой связанной с типом процедуры известен
во время компиляции. Проверка типа v IS T транслируется в v^.tag^.BaseTypes
[ExtensionLevelT] = TypeDescrAdrT. И уровень расширения типа запись
(ExtensionLevelT), и адрес описателя типа (TypeDescrAdrT)
известны во время компиляции. Например, уровень расширения Node
- 0 (этот тип не имеет базового типа), а уровень расширения CenterNode
равен 1.
[1] N.Wirth, J.Gutknecht: The Oberon System. Software Practice and Experience
19, 9, Sept. 1989
[2] M.Reiser: The Oberon System. User Guide and Programming Manual. Addison-Wesley,
1991
[3] C.Pfister, B.Heeb, J.Templ: Oberon Technical Notes. Report 156, ETH
Zurich, March 1991