Готовый перевод Inferno Programmer's Guide / Руководство программиста по ОС Inferno: Chapter 5 - The Limbo Language Definition

Описание языка Limbo

Введение

Limbo - язык общего назначения, предназначенный для приложений запущенных на распределенных системах малых компьютеров. Он поддерживает модульное программирование, строгую проверку типа во время компиляции и во время исполнения, межпроцессное взаимодействие через типизированные каналы, автоматическая сборка мусора, и простые абстрактные типы данных.
Начальной реализацией являлся режим интерпретации, который был спроектирован для предоставления безопасного исполнения программ даже на машинах без аппаратной защиты памяти.

Приложение Limbo состоит из одного или более модулей, каждый из которых поддерживает объявление интерфейса и часть с реализацией. Модуль который использует другой модуль, включает часть объявления другого модуля. Во время выполнения, модуль динамически связывается с другим модулем при помощи заявления идентификатора типа модуля и указания местоположения из которого загружается объектный код для этой реализации.
Объявление модуля определяет функции которые она желает сделать видимыми, а так же их тип данных, константы и объекты данных. В части реализации определяются функции видимые через ее интерфейс и любые функции связанные с ее типом данных, она так же содержит определения функций используемых только внутри модуля и локальные данные.

Пример Модуля
Здесь представлен простой модуль иллюстрирующий особенности языка.

1 implement command;
2 include "sys.m";
3 include "draw.m";
4 sys : sys;
5 command : module {
6 init: fn(ctxt: ref Draw->context, argv: list of string);
7 };
8 # The canonical "Hello world" program, enhanced
9 init(ctxt : ref Draw->context, argv: list of string)
10 {
7 7 sys = load sys Sys->PATH;
12 sys->print("he~l~lo world\n");
13 for (; argv != nil; argv = tl argv)
14 sys->print("%s ", hd argv);
75 sys->print("\n");
16 }

Замечание: Номера строк в левой части показаны только для облегчения обсуждения программы. Они не являются частью исходного кода.

На первый взгляд программа показывает слияние на Limbo языка C, его выражений, операторов и некоторых соглашений (например, взгляните на строки 13-14), а так же языка Pascal и его потомков (объявления в строках 4, 6). Но он отличается от них обоих. Когда программа выполняется в среде Inferno/Limbo , она выводит "hello world" и затем отображает аргументы.

Давайте посмотрим на программу строка за строкой. Она начинается (Строка 1) высказываением, что это реализация модуля Command.
Строка 2 включает файл (который ищется аналогично директиве #include языка C) названный sys m. Этот файл определяет интерфейс модуля Sys. Это говорится в части,

sys : module {
PATH : con "$sys";
print : fn (s : string, *): int;
};

Эти строки объявляют Sys типом модуль, содержащих различные вещи, среди них функцию print; первый аргумент print является строкой. Звездочка (*) в списке аргументов указывает на то, что все последующие аргументы могут быть переданы неустановленного типа.

Строка 3 включает файл draw.m . Только одна часть информации из него используется, что будет показано ниже. Строка 4 объявляет переменную sys типом Sys; это имя будет видимым посредством файла описывающего этот модуль. Она будет использована позднее для ссылки на экземпляр модуля Sys. Это объявление инициализируется в nil; это требуется для установки пригодного значения.

Строки 5-7 вводят в силу объявление реализации модуля Command. Оно состоит только из функции с именем init с двумя аргументами ref Draw->Context и список строк, и не возвращает никакого значения.
Аргумент ref Draw->Context используется если программа работает с графикой, этот тип данных определен в draw.m и ссылается на display. Поскольку программа просто выводит текст, оно не используется (и таким образом аргумент мог быть назван nil).

В модуле разработтаном для использования с другими модулями в приложении, было бы мудрым вынести объявление Command, поместив в отдельный файл названный command.m и пользоваться затем incluse "command.m" давая возможность самому модулю и другим ссылаться на него.
Функция init не является особенной в языке Limbo, однако оговорена в окружении. Например, она вызывается из программы-загрузчика системы Inferno для начала выполнения приложения.

Строка 8 является комментарием. Все что появляется после знака # до конца строки - игнорируется.

Строка 9 начинает определение функции init которая была обещана в объявлении модуля (Строка 6). Аргументом является список строк названный argv.

Строка 11 соединяет написанную программу и модуль Sys. Первый токен после load имя целевого модуляопределенного его интерфейсом (смотри Строку 2 c директивой include). Следующий токен это место где код модуля может быть найден, эта строка обычно имя файла. По соглашению, в системе Inferno каждый модуль содержит объявление константы PATH как строку с именем файла где объектный модуль может быть найден. Загрузка файла производится динамечески во время выполнения за исключением нескольких модулей встроенных в окружение выполнения (Они включают и модуль Sys; он хранит особый файл названный "$Sys").

Значением load является ссылка на имя модуля. Строка 11 присваивает ее переменной sys для последующего использования. Оператор load загружает динамически код модуля если он еще не загружен и создает его новый экземпляр.

12-ая строка начинает свою работу печатая схожее сообщение используя, конструкции предоставленные модулем Sys через указатель sys. Запись sys->print(..) означает вызов функции print модуля, на который ссылается sys. Интерфейс модуля Sys напоминает связывание с некоторым механизмом в Unix или в библиотеке ISO/ANSI C.

Цикл в строках 13-14 доставляет список аргументов строки в оператор init и запускает в них итерацию используя операторы hd(head) или tl(tail). Когда он выполнется, модуль сочетает строку "hello world" и эхо.

Лексические соглашения

Есть несколько видов лексем: ключевые слова, идентификаторы, константы, строки, операторы выражения, и другие разделители. Незаполненное пространство (пробелы, позиции табуляции, новые строки) игнорируется за исключением того случая, когда они служат для разделения лексем. Если ввод был обработан от лексем до какого-то определенного символа, то следующая лексема берется для использования строки символов, которая может сложить определенную лексему.

Родная кодировка для Limbo - Unicode, которая идентична первому 16-битному плоскому стандарту ISO 10646. Любой символ Unicode может быть использован для комментариев или в строковых или символьных константах. Реализации предполагает, что файлы исходники использующие представление UTF-8, в которых 16-битные Unicode символы представлены как последовательности из одного, двух или трех байт.

Комментарии

Комментарии начинаются со знака # и продолжаются до конца строки. Комментарии игнорируются компилятором.

Идентификаторы

Идентификатор - это последовательность букв и цифр, вначале которой стоит буква.
Буквы - это символы Unicode от a до I и от A до z вместе с символом подчеркивания (_), и все символы Unicode вместе с закодированными значениями больше 160(A0 в шестнадцатеричном формате, начало диапазона соответствует Латинскому-1).
Только первые 256 символов важны в идентификаторах.

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

Следующие идентификаторы зарезервированы в качестве ключевых слов, и не могут быть переопределены:

{|
|-
adt alt
byte case
cyclic do
for hd
include int
module nil
real ref
string tagof
while
// Table 3 col
array big break
chan con continue
else exit fn
if implement import
Ten list load
of or pick
return self spawn
tl to type
|}

Константы

Существует несколько типов констант для обозначения базовых типов данных.

Целые константы

Целочисленные константы имеют тип int или big. Они могут быть представлены несколькими способами. Десятичная целая константа состоит из последовательности дисятичных цифр. Константа с неявным основанием системы счисление имеет десятичную основания сопровождающееся буквами R или r сопрождающееся тремя цифрами числа.Основание системы счисления находится между 2 и 36 включительно; цифры выше 10 в числе выражены с помощью символов от A до Z или от a до z. Например значение 16r20 - 32.

Тип десятичного числа с неявной системой счисления - big, если его значение превышает 2^31-1, в другом случае - int.
У символьных констант тип int и они насчитывают один Unicode символ, заключенный в одинарные кавычки ('). Внутри кавычек следующие escape-последовательности представляются специальными символами:

// Table 2 col
V single quote
\" double quote
w backslash
\t tab
\n newline
\r carriage return
\b backspace
\a alert character (bell)
\v vertical tab
\udddd Unicode character named by 4 hexadecimal digits
\0 NULL

Вещественные константы

Вещественные константы содержат последовательность десятичных чисел содержащих одну точку (.) и не обязательный символ e или E, и далее возможно знаковое целое. Если это явно заданная экспонента, знак точки не обязателен. Вещественные константы имеют тип real.

Строки

Строки содержат последовательность символов Unicode заключенных в двойные кавычки. Они не могут выходить за границы одной строки файла с исходным кодом. Те же самые Esc-последовательности перечисленные выше можно использовать в строковых константах. Строковые константы имеют тип string.

Константа nil

Операторы и другие разделители

// Table 7 col
The operators are :
+ * / V 7a & 1 A
< > <= >= ! = « »
&& 11 <-
+= - = * = /= 7o= &= 1 =
A= «= »= ! ~ ++ : =
The other separators are:
j ( ) { } [ ]
j -> =>

Типы и Объекты

В Limbo существуют три типа объектов:
* Данные
* Функции
* Модули

Объекты данных существуют в хранилище связанном с модулем. Над ними можно производить арифметические операции, операции присваивания, выбирать сущности компонент, и другие конкретные операции. Каждый объект данных имеет тип, который определяет, что может быть храниться внутри него и операции которые к нему могут быть прикреплены.

Другой тип объекта - это функция. Функция характеризуется теми типами аргументов, которые она принимает и значениями, которые она возвращает и ассоциируется с модулями, в которых определена. Ее имена могут стать видимыми в модулях, где она определена. Их имена могут стать видимыми в объявлении модуля, или они могут быть инкапсулированы с помощью adt(абстрактных типов данных) их модулей, или могут быть закрытыми внутри модуля.

В конечном счете, Limbo-программы организованы в модули: именованная коллекция объектов данных, констант, абстрактных типов данных и функций может быть доступна через этот модуль. Объявление модуля выводить модули-члены в видимую для модулей область; определение модуля определяет открытую область видимости членов и его закрытые части, включая те объекты данных, которые она использует. Модуль, в котором хотят использовать структуры других модулей включает его объявление по порядку, чтобы понять, что он экспортирует, но перед их использованием он явно загружает новый модуль.

Типы

В Limbo есть несколько основных типов, некоторые встроенные высокоуровневые абстракции и другие типы - смещение нескольких типов. В объявлении и некоторых других местах, конструкции именуют тип который используется. Синтаксис такой:

type:
data-type function-type

Обсуждение функции будет в главе Определение Функций. Во-первых будут рассмотрены типы данных.

Типы данных

Синтаксис типов данных таков:

data-type: byte int big real string tuple-type
array of data-type list of data-type chan of data-type adt-type ref adt-type module-type module-qualified-type type-name
data-type-list: data-type data-type-list, data-type

Объектов большинства типов данных имеют семантику значений; когда они присваиваются или передаются функциям, то место куда они назначаются принимает копию объекта. Последний изменяет себя на присвоенный объект и не воздейтсвует на исходный объект. Типы данных - byte, int, big, real, strng, tuple и абстрактные типы данных (adt). Остальные имеют семантику ссылок. Когда они присваиваются, число уже присвоеное - ссылка(указатель) на объект, на который указывается не копируется; эти изменения и операции над присвоенным объектом изменяют оригинальный объект. Ссылочные типы включают типы списков, массивов, каналов, модулей и ref adt.

Основные типы

Пять основных типов данных - byte, int, big, real и string.
Таблица A-1 Основные типы данных в Limbo

byte unsigned - беззнаковое целое, 8-bit
int signed - знаков целое, 32-bit
big signed - знаковое целое, 64-bit
real - число с плавающей точкой двойной точности стандарта IEEE , 64-bit

byte, int, big и real все вместе называются арифметическими типами. Строки - векторы символов Unicode. Они могут соединяться и расширяться символ-за-символом. Когда строка проиндексирована с помощью одного нижнего индекса, она содержит целое с Unicode расшифровкой символа; когда она индексируется по диапазону, она содержит другую строку. Строки индексируются начиная с 0.

Кортеж(фиксированный список, тьюпл)

Тип кортежа, обозначенный
(список-типов-данных)

- это тип состоящий из упорядоченной коллекции двух или более объектов, у каждого из которых свой собственный тип данных. Для каждого типа кортежа, тип его члена фиксирован, но они не должны быть идентичными; Например, функций может вернуть кортеж содержащий целое и строку. Каждый тип кортежа характеризуется исключительно порядком и идентичностью типов, которые он содержит. Объекты типа кортежа могут быть объявлены списку идентификаторов (чтобы подобрать остальные компоненты), и заключенный в скобки, разделенный запятыми список выражений обозначает сам кортеж.

Тип массива

Тип массива описывает вектор объектов с динамической размерностью, которые все имеют один тип; они индексируются начиная с 0. Тип массива обозначается с помощью массива определенного типа данных(array of data-type)

Размер массива - это та часть, которая не является частью типа его данных; в отличие от этого - это часть значения. Тип данных сам по себе может быть массивом, для того, чтобы представлять многомерные массивы.

Тип списка

Список это последовательность однотипных объектов; задаваемый как
list of data-type
Список это объект похожий на стек который оптимизирован для следующих операций: получение головы списка (первый объект), получение хвоста списка (остатка списка), и генерировании нового списка на основе существующего путем помещения указанного объекта в начало.

Тип канала

Канал, чей тип записывается
chan of data-type
это механизм синхронного сообщения способный посылать и принимать объекты указанного типа другому объекту в системе. Каналы могут использоваться для свзяи между локальными процессами, используя библиотечные процедуры, они могут соединяться с именованными адресатами (местом назначения). В любом случае операции отправки и получения могут ориентироваться на них. Например:
chan of (int, string)
это тип канала передающего тьюплы (кортежи) содержащие челое число и строку. Один экземпляр такого канала (к примеру - c) будет объявлен и инициализирован следующим выражением:
c <-= (123, "Hello");
отправляющим тьюпл через него.

Именованные типы

Типы данных могут быть названы при помощи объявления типа; это будет рассмотрено в разделе "Объявление Типов".
type-name: identifier

Тип модуля

Тип модуля это имя идентификатора:
module-type: identifier
Идентификатор объявлянный идентификатором модуля как module-definition, как описано в "Объявлении Модуля".
Объект типа модуль служит для манипулирования модулем, и используется для доступа к его фугкциям и данным.

Тип квалифицированный модулем

Когда тип объявили внутри модули объявления модуля, его название в основном не видно для большинства программ, до тех пор пока не дано специальное слово import(смотрите разделы Объявления с импортированием и Ссылки на модуля ).
Без такого запроса, когда объекты типа объявлены в модуле клиента, название типа квалифировано.
Модуль - имя типа квалифицировано
квалифицированный-модулем-тип:
идентификатор -> идентификатор
Здесь должен быть идентификатор - либо название модуля, либо переменной типа модуля. Второй - это название типа в объявлении модуля.

Абстрактный тип данных

Абстрактный тип данных или adt - это объект, который может содержать объекты данных нескольких разных типов и объявление функции, которые работают ими. Абстрактный тип данных обозначает:
тип-adt:
модулем-квалифицированый-тип идентификатор

Здесь модуль ref adt предостовляется как ссылка на объект(указатель) типа adt. Он обозначает:
ref тип-adt
Где идентификатор - это имя типа adt.
Тогда, когда объявлен, идентификатор ассоциированный с ним становится именем типа-данных.

Тип функции

Тип функции характерезует передаваемыми ей аргументами и возвращаемым значением. Синтаксис таков:
тип-функции:
fn возвращаемые-аргументы-функции
возвращаемые-аргументы-функции:
(опциисписка-формал-арг^)(список-формал-арг^):тип-данных
список-формал-арг: формал-арг список-формал-арг,формал-арг
формал-арг:
nil-или-D-list : тип
nil-или-D-список : self ref t идентификатор
nil-или-D-список: nil-или-D nil-или-D, nil-или-D
nil-или-D:
идентификатор nil

У обозначения типа функции есть ключевое слово fn, следующее за разделенным запятыми списком его аргументом, заключенных в скобки и, возможно, сопровождающееся типом значения, которое функция возвращает. Отсутствие возвращаемого значения означает, что функция его не возвращает - она является процедурой. Имена и типы аргументов определяются. Однако, имя аргумента заменяется на nil, делая его безымянным. Например:
fn (nil : int, nil : int) : int
fn (radius : int, angle : int) : int
fn (radius, angle : int) : int
все они обозначают в точности один и тот же тип, функция с двумя целыми значениями, которая возвращает целое.

В следующий пример иллюстрирует пример функции, который берет аргумент строки и не возвращает никакого значения:
fn (nil : string)
Ключевое слово self имеет специализированную область использования внутри объявления adt. Она может быть использована толко для первого аргумента функции объявленной внутри adt, ее значение обсуждается в секции "Объявления ADT".

Символ звездочка * может передаваться как последний аргумент в типе функции.
Он объявляет, что функция с переменным числом аргументов, во время вызова, текущий аргумент на его позиции и последующий передаются неопределенным для языка образом. Например, тип функции print модуля Sys такой:
fn (s : string, *) : int

Это означает, что первый аргумент функции print - строка и остальные могут быть переданы, когда функция вызывается. Сам по себе язык Limbo не имеет доступа к этим аргументам, используется искусственное обозначение для описания конструкций встроенных в runtime-систему поддержки исполнения программ, такую как модуль Sys.

Программы Limbo

Исходный код программ на Limbo, в которым подключаемые модули хранятся в файлах, по соглашению заканчивается суффиксом .b. Каждый исходный файл, начинающийся директивы подключения имен модуля типа, продолжается последовательностью объявлений.

Другие файлы, имя которых по соглашению заканчивается суффиксом .m, содержат объявления для тех вещей, которые доступны из других модулей. Эти файлы объедены директивой include в подключаемых модулях, которые нужны ему.

Верхний уровень

На верхнем уровне, в программе есть последовательность объявлений. Синтаксис таков:
программа:
implement identifier ; top-declaration-sequence
top-declaration-sequence: top-declaration top-declaration-sequence top-declaration
top-declaration: declaration
identifier-list : = expression; identifier-list = expression; ( identifier-list ) : = expression; module-declaration function-definition adt-declaration

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

Оба объявления используются на верхнем уровне(вне функций) и внутри функций и объявлений модулей. Некоторый стили объявлений разрешены только точно на каком-то из этих месте, но обсуждены они будут вместе.

Объявления

Объявления принимают несколько форм:

Объявления
список-идентификаторов: тип, список-идентификаторов: тип = оператор, идентификатор = оператор; ( id-or-nil-list ) : = оператор; список-идентификаторов : con expression; список-идентификаторов : import идентификатор; список-идентификаторов : тип тип; include строковая-константа;
список-идентификаторов: идентификатор список-идентификаторов, идентификатор список-выражений: выражение список-выражений, выражение

Объявления типов данных

Эти формы составляют основной путь для объявления и инициализации данных:
список-идентификаторов : тип; список идентификаторов : тип = выражение;

Последовательность идентификаторов разделенная запятыми сопровождается двоеточием и затем именем типа. Каждый идентификатор объявлен имеющим этот тип и обозначает определенный объект для оставшейся части его области видимости (смотрите Видимость). Если объявление содержит = и выражение, все объекты инициализированы из значения выражения. В объявлении на верхнем уровне (вне функции), выражение должно быть константой или массивом инициализированным константными выражениями; граница массива должна быть константным выражением.

Определение модуля

Обозначается в виде:
элемент -> элемент
означает принадлежность модулю. Первый элемент определяет модуль: это может быть и имя типа модуль или выражение типа модуль. Второй элемент является именем константы, типом, объектом данных или функцией указанной в объявлении модуля. В любом случае, будь то имя типа модуль или объект типа модуль, этого достаточно для определения констант или типов; функции и объекты непосредственно экспортируемые модулем и содержащие в себе adt должны быть уточнены объектом типа модуль, инициализированные оператором load.

Пример использующий урезанную версию вышесказанного:

Linear : module {
setflags : fn (flag : int); TRUNCATE : con 1; vector : adt {
v : array of real;
add : fn (vl : self vector, v2: vector) : vector;
make : fn(v : array of real) : vector;
}; };
one might say:
Tin := load Linear "/dis/linear.dis"; a : array of real;
vl : Tin -> vector;
v2 : Linear -> vector;
1i n->setf1ags (Li near->TRUNCATE);
vl = 1in->(Linear->vector).make(a);
vl = 1in->vl.make(a);
vl = 1in->vl.add(vl);
vl.v = nil;

Здесь, объявления vl и v2 одинаковы; оба имени типа модуль ( Linear) или хэндл (Tin) достаточны для идентификации модуля. В вызове setflags, необходим хэндл для для обращения самому себе; имя типа достаточно для константы.

vl = 1in->(Linear->vector) .tnake(a) ;

vl = 1in->vl.make(a);

vl = 1in->vector.make(a);#wrong
vl = 1in->Linear->vector.make(a);#wrong

Tin := load Linear "/dis/linear.dis"; vector, TRUNCATE, setflags : import Tin; a : array of real;
vl : vector; v2 : vector; set-flags (TRUNCATE); vl = vector.make(a); vl = vl.make(a); vl = vl.add(vl); vl.v = nil;

Вызовы функций

Слайс

Вычленение подстроки происходит следующим образом:
term [.expression : expression]

Инкремент и декремент

Монадические выражения

Монадические операторы сложения

Логическое инвертирование/отрицание

Оператор дополнения

Point: adt {... } ;
p : Point;
pp : ref Point;
p = Point(l, 2);
pp = ref p; # Присваивает те же значения *pp что и у p
p = Point(3, 4);# Изменяет *pp от p
*pp= Point(4, 5);# Не влияет на p

Head и tail / Получение головы и хвоста списка

Логические операторы

Логический оператор && (AND) сначала вычисляет свой левый операнд. Если результатом является ноль, тогда значение всего выражения будет int 0. В противном случае, вычисляется выражение в правой части, если результатом будет ноль, значение всего выражения будет снова 0, в противном случае это 1. Операнды должны быть типа int.

Оператор for

Оператор for имеет следующий вид:

labelopt for {expression-1 opt; expression-2opt; expression-3opt~) statement
It is equivalent to:
expression-1;
while {expression-2") {
statement
expression-3;
}

Оператор case

Оператор case передает управление одной из частей кода в зависимости от значения выражения:

label t case expression { qual-statement-sequence }

Выражение должно иметь тип int или string. За оператором case следует последовательность операторов, которые помечены выражениями или диапазоном выражений:

qual-statement-sequence: qual-list =>
qual-statement-sequence qual-list => qual-statement-sequence statement qual-statement-sequence declaration
qual-list:
qualifier
qual-list O r qualifier
qualifier:
expression
expression to expression

это эквивалентно следующему:

Видимость

Видимость идентификатора эта лексическая область программы в которой этот идентификатор имеет определенный тип, или экземпляр объекта. Один и тот же идентификатор может быть связан с несколькими разными объектами в различных частях одной программы.

Имена членов ADT занимают отдельное, не конфликтующее с другими идентификаторами пространство; они объявляются в синтаксически отдельном месте, и всегда используются различным образом, а именно после (.) оператора выбора. не смотря на то, что одни и те же правила видимости применяются к членам ADT как к другим идентификаторам, их имена могут совпадать с другими записями в той же самой области видимости.
Аналогично, имена констант, функций, объекты данных, и типов появляющихся в объявлениях модуля обычно указываются с именем модуля или переменной модуля используя запись ->. Как обсуждалось выше, объявление import вводит эти имена в текущую область видимости.

Например:

Tree : adt {
1 : ref Tree;
r : ref Tree;
t : ref Ntree;
};
Ntree : adt {
t : ref Tree;
};
tl : = Tree(ni1, nil, nil)
t2 : = Tree(ref tl, ref tl
tl = Tree(ref tl, ref t2,
tl.l = . . . ; # not OK
nt : = ref Ntree(nil);# OK
nt.t = . . . # not OK

Примеры

Простой модуль командного интерпретатора

27 exec(ctx : ref Draw->context, args : list of string)
28 {
29 c : command;
30 cmd, file : string;
31 cmd = hd args;
32 file = cmd + ".dis";
33 c = load command file;
34 if (c == nil)
35 c = load command "/dis/"+fi1e;
36 if (c == nil) {
37 sys->print("%s: not found\n", cmd);
38 return;
39 }
40 c->init(ctx, args);
41 }

Инфракрасное дистанционное управление

Переведено на Нотабеноиде
http://notabenoid.org/book/2198/9408

Переводчики: rapidfx, wuffur

Настройки

Готово:

29.55% КП = 1.0

Непереведённые фрагменты будут пропущены.