Язык: Оберон за одну главу

Оберон — последний язык Вирта, наследник Паскаля и Модулы-2. Его редакция 2007 года, на которой работает эта система, задумана как язык, который целиком помещается в голову: тридцать три ключевых слова и сорок с небольшим встроенных имён. Для сравнения, в C++ ключевых слов около сотни.

Ниже не полное описание, а рабочий минимум: столько, чтобы читать исходники системы и писать свои модули.

Модуль целиком

Вот настоящий модуль с диска этой системы — он мигает светодиодом на плате:

MODULE Blink;
  IMPORT SYSTEM, Oberon;
  VAR z: INTEGER;
    T: Oberon.Task;

  PROCEDURE Run*;
  BEGIN Oberon.Install(T)
  END Run;

  PROCEDURE Stop*;
  BEGIN Oberon.Remove(T)
  END Stop;

  PROCEDURE Tick;
  BEGIN z := 1 - z; LED(z)
  END Tick;

BEGIN z := 0; T := Oberon.NewTask(Tick, 500)
END Blink.

Здесь видно почти всё устройство языка.

Модуль — единица компиляции и единица загрузки. Его тело (между последним BEGIN и END) выполняется один раз при загрузке.

Звёздочка после имени означает экспорт. Run* виден снаружи, Tick — нет. Это весь механизм видимости: никаких public, private, заголовочных файлов и списков экспорта.

Имя модуля повторяется после последнего END и заканчивается точкой. Компилятор это проверяет.

Ключевые слова

Все тридцать три:

ARRAY BEGIN BY CASE CONST DIV DO ELSE ELSIF END FALSE FOR IF IMPORT
IN IS MOD MODULE NIL OF OR POINTER PROCEDURE RECORD REPEAT RETURN
THEN TO TRUE TYPE UNTIL VAR WHILE

Обратите внимание, чего здесь нет: class, try, catch, throw, template, interface, async, private. Ничего этого в языке нет — и это осознанный выбор, а не бедность.

Типы

Базовые: INTEGER, REAL, BOOLEAN, CHAR, BYTE, SET.

SET — множество из 32 элементов, по биту на элемент, с операциями + (объединение), * (пересечение), - (разность) и проверкой IN. Дёшево и очень выразительно; система пользуется этим постоянно, например для состояния кнопок мыши.

Составные:

TYPE
  Name = ARRAY 32 OF CHAR;
  Point = RECORD x, y: INTEGER END;
  Node = POINTER TO NodeDesc;
  NodeDesc = RECORD key: INTEGER; next: Node END;

  Shape = RECORD x, y: INTEGER END;
  Circle = RECORD (Shape) r: INTEGER END;     (* расширение записи *)

Записи расширяются — это всё наследование, которое есть в языке. Проверка типа во время исполнения делается через IS, приведение — через v(Circle). Виртуальных методов нет: вместо них в запись кладут переменную процедурного типа. Именно так устроены все «объекты» в этой системе.

Массивы бывают с заданной длиной (ARRAY 32 OF CHAR) и открытые — только как параметры процедур (ARRAY OF CHAR); их длину даёт LEN.

Операторы

Присваивание — :=, сравнение — = и #. Деление целых — DIV и MOD, деление вещественных — /.

IF x > 0 THEN y := 1 ELSIF x < 0 THEN y := -1 ELSE y := 0 END;

WHILE i < n DO s := s + a[i]; INC(i) END;

REPEAT ch := Read() UNTIL ch = 0X;

FOR i := 0 TO n-1 DO a[i] := 0 END;

CASE ch OF
  "a".."z": Small(ch)
| "0".."9": Digit(ch)
ELSE Other(ch)
END

Каждая конструкция закрывается своим END. Фигурных скобок нет, висящего else не бывает, а точка с запятой — разделитель, а не терминатор: перед END она не нужна.

Процедуры

PROCEDURE Add(a, b: INTEGER): INTEGER;
BEGIN RETURN a + b
END Add;

PROCEDURE Swap(VAR a, b: INTEGER);
  VAR t: INTEGER;
BEGIN t := a; a := b; b := t
END Swap;

Параметр с VAR передаётся по ссылке, без — по значению. Объявления локальных переменных идут между заголовком и BEGIN. Вложенные процедуры разрешены и видят переменные охватывающей.

RETURN в функции стоит последним оператором — выйти из середины нельзя. Это неудобно ровно до того момента, когда приходится читать чужой код.

Встроенные процедуры

Их сорок с небольшим, и почти все — на одну строку:

INC DEC увеличить, уменьшить
INCL EXCL добавить, убрать элемент множества
LEN длина открытого массива
ORD CHR символ ↔ код
ABS ODD модуль, нечётность
FLOOR FLT вещественное ↔ целое
PACK UNPK разобрать и собрать вещественное по мантиссе и порядку
NEW выделить память под указатель
ASSERT проверка; при ложности — ловушка
LED зажечь светодиоды на плате

Отдельно стоит псевдомодуль SYSTEM: ADR, VAL, GET, PUT, COPY, BIT, H, SIZE. Это дверь наружу из типобезопасного языка — через неё написаны драйверы и сборщик мусора. Модуль, импортирующий SYSTEM, компилятор помечает особо, и это видно в его исходнике первой строкой.

Чего в языке нет и почему

Нет исключений. Ошибка времени исполнения — это ловушка, она печатает позицию и останавливает команду. Программа не пишет try вокруг всего подряд.

Нет перегрузки — ни операторов, ни имён процедур. Имя означает ровно одно.

Нет обобщённых типов. Контейнер пишется либо под конкретный тип, либо через расширение записей.

Нет предпроцессора, макросов и условной компиляции. Текст, который вы видите, — это текст, который компилируется.

Нет заголовочных файлов. Интерфейс модуля компилятор извлекает из него самого; как именно — в главе про модули.

Язык проектировался под одно ограничение: компилятор для него должен писаться одним человеком за обозримое время. Проверка простая — весь компилятор Оберона это четыре модуля и сто девять килобайт исходного текста. Что это за модули, разбираем в главе про компилятор.