Начало[]
Эта глава познакомит вас с языком программирования Limbo. В следующем разделе разбирается простой пример, иллюстрирующий основы любой Limbo-программы. Далее следует раздел посвященный языковой базе, который углубляется в язык Limbo. Он освещает основные моменты языка и включает множество примеров программ.
Программа на Limbo[]
Для иллюстрации языка Limbo, здесь приведен простой пример программы. Это вариация древнейшей “Hello, World!”, которая просто печатает приветствие.
Построчно пронумерованный исходный текст можно скомпилировать компилятором Limbo и затем запустить из командной строки Inferno.
1 implement Command; 2 3 include "sys.m"; 4 include "draw.m"; 5 6 sys: Sys; 7 8 Command: module { 9 init: fn(nil: ref Draw->Context, argv: list of string) 10 }; 11 12 init(nil: ref Draw->Context, argv: list of string) { 13 sys = load sys Sys->PATH; 14 # проверяем наличие аргументов командной строки 15 if ((tl argv) != nil) { 16 sys->print("Hello, %s! ", hd (tl argv)); 17 } else 18 sys->print("Hello, world!"); 19 }
Строка 1 говорит о том, что этот файл - реализация модуля Command. В этом простом примере, как и в любой несложной программе на Limbo, интерфейс и реализация находятся в одном и том же файле. Имя файла не обязательно должно совпадать с именем модуля или именем реализации, но обычно они одинаковы.
Строки 3 и 4 включают определение интерфейса двух модулей, которые будет использвать наш модуль. Строка 3 включает интерфейс к модулю System (sys.m), а строка 4 - интерфейс к модулю Draw (draw.m). В Limbo нет препроцессора в отличие от C. Поэтому слово include - ключевое слово, а не директива препроцессора. В Limbo имя файла за словом include всегда заключается в двойные кавычки ("mod.m").
Строка 6 объявляет переменную sys типа Sys (в Limbo переменные чувствительны к регистру). Sys - это тип модуля, определенный в файле sys.m:
sys: module { ... };
Переменная sys объявлена для обращения к функциям и данным модуля Sys. Пока она имеет значение nil.
Строки с 8 по 10 содержат объявление модуля Command, который мы начали реализовавать в нашем файле на Строке 1. Единственная функция, объявленная в этом модуле - init. Функции передается два аргумента, имеющие типы ref Draw->Context и list of string.
Функция init и два ее аргумента необходимы в программах, предназначенных для запуска интерпретатором командной строки Inferno. Во многом она аналогична функции main() языка C и указывает с какой точки начинать исполнение в модуле. Она принимает "графический контекст" и "аргументы командной" в качестве аргументов.
ref Draw->Context используется для захвата контекста дисплея и определена в draw.m, который был включен в Строке 4. Этот аргумент нужен только в том случае, если программа работает с графикой. В нашем случае аргумент может быть назван ni1.
list of string - это список аргументов командной строки, переданный модулю командным интерпретатором. Его общепринятое название argv. Если программа не ожидает аргументов командной строки, аргумент может быть назван nil.
Строки с 12 по 19 содержат определение функции init. Ранее мы упоминали, что значение переменной sys было nil. В строке 13, через использование ключевого слова load, мы загрузили модуль Sys в память и присвоили ссылку на него переменной sys. Теперь наша программа имеет доступ к функциям и данным, определенным в этом модуле.
Строка 14 - комментарий. Комментарии в Limbo начинаются с решетки (#) и заканчиваются в конце строки. Для многострочных комментариев нужно ставить решетку в начале каждой строчки.
В строках с 15 по 18 содержится код, печатающий сообщение. В нем используется аргумент командной строки, переданный в init через argv. Второй аргумент функции init - список строк. Списки в Limbo - группа последовательных, упорядоченных элементов. Вы проходите по элементам списка используя операторы hd (head - "голова") и tl (tail - "хвост"). Первый элементы списка argv, его голова - это имя программы. Так как нам интересен только остаток списка, его хвост, мы проверяем, не nil ли это (хвост имеет какое-то значение), затем мы печатаем сообщение и голову оставшегося хвоста (второй элемент списка). Если там нет никаких аргументов, печатаем сообщение “Hello, World!”.
Компиляция[]
Запустите компилятор Limbo с помощью следующей строки в окне командного интерпритатора:
% limbo greet.b
Если компилятор не найдет ошибок, то в текущем каталоге появится исполняемый файл, называемый Dis-файлом, в данном случае greet.dis. Обратитесь к главе 4 чтобы узнать больше о компиляции Limbo-программ и соглашениях об именовании файлов. Опции комплятора описаны в man-странице limbo(1).
Запуск[]
Чтобы запустить программу просто наберите ее имя в окне командного интерпритатора, опустив расширение .dis:
% greet Reader Hello, Reader!
Любые аргументы командной строки Inferno передает функции init() как элементы списка строк. Имя программы - первый элемент этого списка.
Ключевые слова[]
Limbo включает небольшой, но полный набор зарезервированных слов. Следующие идентификаторы зарезервированы для использования в качестве ключевых слов и не могут быть использованы не для чего больше:
adt alt byte case cyclic do for hd include int module nil real ref string tagof while array big break chan con continue else exit fn if implement import Ten list load of or pick return self spawn tl to type
Операторы[]
Limbo включает три класса операторов: унарные, бинарные и операторы присваивания.
Унарные операторы[]
++ Инкремент -- Декремент + Унарный плюс - Унарный минус ! Логическое отрицание (NOT) * Косвенная ссылка ADT <- Оператор канала (отправка и получение) & Побитовое отрицание (NOT) ref Ссылка на объект ADT hd Голова списка tl Хвост списка tagof Индекс элемента pick в ADT len Возвращает размер чего-либо
Бинарные операторы[]
* Умножение / Деление % Деление по модулю + Сложение - Вычитание << Сдвиг влево >> Сдвиг вправо == Равенство < Меньше чем > Больше чем <= Меньше или равно >= Больше или равно != Неравенство & Побитовое И (AND) | Побитовое ИЛИ (OR) < Побитовое исключающее или (XOR) :: Конструктор списков && Логическое И (AND) || Логическое ИЛИ (OR)
Операторы объявления и присваивания[]
= простое присваивание : объявление (создание нового) := произвести объявление и присвоить - инициализация (сокращение) += произвести инкремент и присвоить -= произвести декремент и присвоить *= Умножить и присвоить \= разделить и присвоить %= взять остаток от деления и присвоить &= Произвести побитовое И (AND) и присвоить |= Произвести побитовое ИЛИ (OR) и присвоить ^= Произвести побитовое исключающее или (XOR) и присвоить <<= произвести побитовый сдвиг влево и присвоить >>= произвести побитовый сдвиг вправо и присвоить
Типы данных[]
В Limbo есть две категории типов данных:
- Примитивные: byte, integer, big, real, string
- Ссылочные: array, list, turple
Кроме того, вы можете определить собственный тип данных с помощью абстрактного типа данных (ADT).
Примитивные типы[]
Примитивные типы в Limbo имеют фиксированное определение, то есть их размер одинаков на всех платформах. Это нужно для обеспечения портабельности кода. В следующей таблице перечислены эти типы данных, диапазан их значений.
тип | диапазон значений |
---|---|
byte | Беззнаковвое 8-битное целое, длиной 1 байт |
int | 32-битное целое со знаком |
big | 64-битное целое со знаком |
real | 64-битное число с плавающей точкой двойной точности стандарта IEEE |
Строки[]
В Limbo строковые константы заключаются в двойные ковычки. Например:
s := "inferno";
объявляет строку s и присваивает ей значение inferno.
Inferno использует кодировку Unicode вместо ASCII, так что строковый тип в Limbo - это вектор (массив) Unicode-символов, имеющих длину 16 бит.
В Limbo нет специального типа данных для хранения одиночных символов, поэтому символ, заключенный в одинарные ковычки, представляет собой целое число. Например:
c := 'I';
Объявляет переменную c, и присваивает ей зачение 73 (код символа I).
Из строки можно извлеч подстроку, называемую срезом (slice). Смотрите раздел Срезы массивов чтобы узнать как это сделать.
Константы[]
Идентификатор, имеющий константное значение примитивного типа, объявляется с использованием ключевого слова con:
data: con "/data/user.dat"; # константа типа string block: con 512; # константа типа int pi: con 3.1415926535897932; # константа типа real
Перечисление констант
Вы можете использовать специальное значение iota вместе с ключевым словом con чтобы присвоить константе значение, на один большее предыдущего в группе идентификаторов. Это похоже на перечисления в языке Си.
Пример:
Mon, Tue, wed, Thu, Fri, sat, sun: con iota;
Эта строка объявляет константу Mon со значением 0, Tue со значением 1, Web - 2 и т.д. Вы можете использовать другие операторы совместно с iota чтобы изменять значения. Наиболее используемые операторы это сложение, умножение и побитовый сдвиг.
Пример:
Ten, Eleven, Twelve: con iota+10
Приведенный код устанавливает стартовое значение значение в 10 и увеличивает последующие значения на единицу.
Вы можете использовать оператор умножения чтобы изменить диапазон увеличения значения:
zero, Five, Ten, Fifteen: con iota*5;
Так же вы можете использовать операторы побитового сдвига:
Two, Four, Eight, sixteen, ThirtyTwo: con 2«iota;
Используя различные комбинации операторов вы сможете получить множество различных вариантов изменения значения.
Ссылочные типы (ref)[]
Ссылки в Limbo подобны указателям в языках Си и Си++. Осовное различие между ними заключается в том, что Limbo не позволяет математически манипулировать ссылками. Это обеспечивает безопасную работу с памятью, так как ссылка никогда не укажет на незаконный адрес. Однако она может иметь значение nil.
Значение nil[]
Ссылочным типам можно присвоить специальное значение, представленное ключевым словом nil. В результате ссылка будет удалена. Если это последняя сслыка к данным, то они будут автоматически и немедленно утилизированы сборщиком мусора. Сразу после объявления ссылочные типы ни на что не указывают. Значение должно быть присвоено им перед использованием.
Массивы[]
Массивы состоят из последовательно пронумерованных однотипных данных. Нумерация начинается с 0. Простейшее объявление массива выглядит так:
buf : array of byte;
В примере объявляется переменная buf, являющаяся массивом элементов типа byte. Если вы хотите заранее выделить место для массива, используйте следующий вид объявления:
buf: array[80] of byte
Это выражение создает новый массив из 80 элементов типа byte. Перед тем как использвоать массив присвойте его переменной:
buf = array[80] of byte;
Пример: следующий блок печатает число и квадрат этого числа.
count := array[5] of int; i := 0; do { count[i] = i ; sys->print("%d\t%d\n" , i, count[i]*i++); } while (i < 5);
Вывод выглядит примерно так:
0 0 1 1 2 4 3 9 4 16
Срезы массивов
Срез - это последовательный диапазон элементов массива или строки (в Limbo строки представляют собой массив символов Unicode). Диапазон задается индексом в квадратных скобках, которой указывает начальное и конечное значения, разделенные дветочием. Результат среза можно присвоить переменной, получив в результате новый массив.
Срезы удобно использовать для манипулирования строками и словами:
s := "inferno";
За перменной s теперь закреплен такой массив:
Начало строки Конец строки | | _I___n___f___e___r___n___o [0] [1] [2] [3] [4] [5] [6]
Вырежим из строки s небольшой кусочек со второго по шестой элемент:
s2:=s[2:6];
Значение s2 - строка "fern".
Списки (list)[]
Списки состоят из упорядоченных, неиндексированных, одинаковых элементов. Первый элемент списка - его голова, остальные составляют хвост.
Начало списка___________________________________Конец списка [элемент]-►[элемент]-►[элемент]-►[элемент]-►...-►[элемент] \Голова/ \_________________хвост______________________/
Элементы списка
line : list of string;
Эта строка объявляет переменную line типа list of string (дословно: список строк).
Элементы списка доступны, изменяемы и управляемы с помощью операторов управления списками, которые перечислены в следующей таблице
Оператор | Имя | Описание | :: | конструктор | конструктор значения | tl | хвост | список без первого элемента | hd | голова | первый элемент |
Оператор создания списка (::)
Для создания списка используйте ::, оператор создания списка, например так:
line = "First" :: "Second";
В любой момент вы можете добавить новый элемент в конец списка:
line = line :: "Third";
Или в его начало:
line = "Zero" :: line;
Это похоже на то, как кладутся элементы в стек. Конструктор списка правоассоциативный. Правый операнд - это один элемент и он должен иметь тот же тип, что и элементы списка. Идентификатор списка левый операнд.
Оператор взятия головного элемента списка (hd)
Голова списка - это один элемент. Он и имеет тот же тип, что и остальные элементы списка. Для вычленения головного элемент списка используйте оператор hd. Он не будет создавать новый список или менять исходный, а просто вернет первый элемент.
Например, опишем использование созданного выше списка:
sys->print("The list head is: %s", hd line);
Печатает:
The list head is: First
Оператор взятия хвоста списка (tl)
Хвост списка - это сам список минус первый элемент. Присвоение результата выполнения оператора hd исходному списку просто удалит его головной элемент:
line = tl line;
Новый список можно создать присвоив хвост списока другой переменной:
args := tl line;
Кортежи (tuple)[]
Кортеж - это последовательный, заключенный в скобки, набор данных любых типов, которые обрабатываются как один объект.
Например:
retcode := (0, success);
Так cоздается кортеж retcode, в котором хранится целое и строка. Note tuple
Кортеж распаковывается через присваивание. Например:
err : int; tnsg : string; (err, tnsg) = retcode;
Переменной err присваивается значение 0, а пермененной msg - строка “Success”.
Используя оператор := распаковать кортеж можно и одним выражением:
(err, msg) := retcode;
Этот код объявляет переменные err и msg и присваивает им значения, взятые из кортежа retcode. Используя ключевое слово nil можно игнорировать части кортежа во время присваивания.
(err, nil) = retcode;
Этот код присваивает целое значение из кортежа retcode, но игнорирует его строковую часть.
Абстрактные типы данных (ADT)[]
В языке Limbo абстрактный тип данных (ADT) - это определенный пользователем тип данных, который инкапсулирует данные и функции внутри проименованного типа. Он похож на структуры языка Си, но может включать так же и функции, на манер классов Си++. Абстактный тип данных не поддерживает наследование и полиморфизм, и потому не имеет никакого отношения к объектно-ориентированному программированию. Однако мы будем использовать термины, позаимствованные из словаря ООП, такие как поле и метод. Полем называется член данных (переменная), инкапсулированный в ADT, а методом - одна из функций ADT.
Объявление ADT[]
У объявления нового абстрактного типа данных есть основная форма:
identifier: adt { поле; поле; ... метод; метод; ... };
Объявление полей и методов ADT очень похоже на объявление данных или функций в других местах программы на Limbo.
Например, следующий код - это объявление простого ADT Inventory:
Inventory: adt { id: string; onhand: int; cost: real; value: fn(item: inventory): real; };
Это объявление содержит три поля и один метод.
После объявления, Inventory становится полноправным типом данных, таким как int или real. Обычно объявление типа ADT находятся в отдельном файле модуля (.m).
Определение методов ADT, которое обычно находится в файле реализации (.b), происходит также как и определение обычных функций, но с добавлением спецификатора к имени, используя оператор точка (.):
Inventory.value(item: inventory): real { return (real(item.onhand) * item.cost); }
Создание экземпляра ADT[]
Создание экземпляра ADT - это объявление переменной, имеющей заранее объявленный тип ADT. Например:
part1: Inventory;
Объявляет переменную part1 типа Inventory (иными словами part1 теперь экземпляр ADT Inventory).
Доступ к членами ADT[]
К полям и методам переменной типа ADT обращаются через точку (.), то есть используя уточняющую запись. С ними можно работать как с любой другой переменной или функцией.
Например:
part1.id = "widget"; part1.onhand = 250; part1.cost = 4.23;
Присваивает значения полям id, onhand и cost переменной part1.
Методы вызываются похожим образом. Например:
sys->print("Количество денег на руках: $%5.2f\n", part1.value(part1));
Используя присвоенные ранее значения, выражение печатает:
Количество денег на руках: $1057.50
Чтобы метод value() в этом примере мог работать с полями, экземпляр ADT Inventory явно передается как аргумент функции. Но передачу экземпляра ADT можно поизвести и неявно, испольуя ключевое слово self.
Неявный аргумент: self[]
Изменив описание метода value() так, чтобы она использовала ключевое слово self, мы сможем передать экземпляр ADT Inventory как неявный аргумент:
inventory: adt { id: string; onhand: int; cost: real; value: fn(item: self inventory): real; }; inventory.value(item: self inventory): real { return (real(item.onhand) * item.cost); }
Добавление аргумента self к объявлению и определению метода позволяет вызвать метод без явной передачи экземпляра ADT.
Следующий код отражает изменения в выражении, которое вызывает метод:
sys->print("Количество денег на руках: $%5.2f\n", part1.value());
Используя значения, определенные ранее, выражение печатает:
Количество денег на руках: $1057.50
ref ADT (ссылки на абстрактные типы данных)[]
Язык Limbo допускает объявление переменных, имеющих тип ссылки на экземпляр ADT.
part2:= ref Inventory;
Так создается новый экземпляр ADT Inventory, а ссылка на него присваивается переменной part2, которой можно пользоваться как обычной перменной абстрактного типа. Например, присвоение значений полям может быть в точности таким же:
part2.id = "whatsit"; part2.onhand = 1000; part2.cost = 0.17;
Важно понимать, что тип переменной part2 отличается от типа part1. Это значит, что part2 не может быть присвоена переменной абстрактного типа Inventory или передана непосредственно методам экземпляра ADT Inventory. Например, следующие выражения вызовут ошибки на этапе компиляции:
part2 = part1; # конфликт типов аргументов, не будет компилироваться part2.value(); # не соответствует тип аргумента, не будет компилироваться
Чтобы получить доступ к экземпляру ADT, на который ссылается переменная, используйте префиксный оператор звездочка (*):
part1=*part2; #правильно (*part2).value(); #правильно
ref ADT против ADT
Основное преимущество ссылок на экземпляры ADT в том, что они могут передавать между функциями без копирования. Это более эффективно, особенно для больших ADT. Методы, которые принимают сслыки на экземпляры ADT могут переписывать содержимое своих аргументов. Это нельзя сделать, используя методы, принимающие экземпляры ADT.
Однако интерфейс доступа к экземплярам ADT через ссылки может стать менее ясным, в основном из-за необходимости переписывать значения переменных. Интерфейс прямого доступа к экземпляру ADT более прям и прост, более естественнен для большинства ситуаций.
pick (подборка) ADT[]
// TODO
В Limbo pick ADT подобна объединению в Си. Она позволяет использовать экземпляры одного ADT для хранения данных различных типов. Таким образом, из одного объявления ADT можно породить экземпляры ADT различных типов. Pick ADT может иметь поля, специфичные для конкретного экземпляра ADT.
В pick ADT две части: 1) его объявление 2) и выражение pick ADT, где происходит доступ к элементам в pick ADT.
Объявление подборки(pick)
Основная форма pick ADT выглядит так:
adtidentifier: adt { pick { element => variable: type; } };
element это идентификатор подборки(pick), который определяет специфическую переменную, чтобы она имела некоторый тип. Например:
Apick: adt { str: string; pick { # string element string => val: string; # int element int => val: int; # real element Real => val: real; } };
Обычный элемент этого ADT - это переменная str типа string. pick-элементы это String, Int, and Real. Они определяют три различных Apick ADT. Если у pick ADT обычные данные члены, pick-блок будет заключительным сегментом данных. Однако, функции члены следуют за pick-блоком. pick ADT должен быть объявлен как ref ADT. Этот специфичный pick-элемент должен быть определен. Например:
r := ref Apick.Real;
Когда вы объявляете значение членов данных ADT используйте переменные, определенные через pick-структуру:
r. str = "Pi"; r.val = 3.141593;
Вы также можете объявлять и инициализировать экземпляр ADT. Например:
s := ref Apick.string("Greeting", "Hello, world!");
Оператор tagof[]
Оператор tagof возвращает индекс pick-элемента ADT в конкретный экземпляр ADT. Индексация элементов начинается с 0. Например:
tagof r
Возвращает индекс экземпляра r подборки Apick ADT сделанной ранее, который равен 2(третий элемент).
Оператор pick[]
Оператор pick позволяет выбирать операторы, на том основании, что типы pick ADT различны. Основная форма оператора pick такая:
pick localinst : = refadtinst { element => statements ... }
Оператор pick использует локальное копирование localinst, ссылки на ADT, redadtinst. element(s) - в pick ADT можно объявлять один или несколько элементов. Например:
t : ref Apick = s; pick u := t { String => sys->print("%s is %s\n", u.str, u.val); Int => sys->print("%s is %d\n", u.str, u.val); Real => sys->print("%s is %f\n", u.str, u.val); }
Во-первых, что важнее для оператора pick,экземпляр s типа ref ADT инициализирован. Потом, оператор pick выбирает оператор для выполнение основываясь на различных вариантах типа переменной t типа ADT.
Другой путь обычно используемый для завершения этого - это поставить оператор pick в функции, который примет аргумент ref ADT. Например:
printval(a: ref Apick) { pick t := a { String => sys->print("%d: %s\n", tagof t, t.val); Int => sys->print("%d: %d\n", tagof t, t.val); Real => sys->print("%d: %f\n", tagof t, t.val); } }
Для того, чтобы вызвать эту функцию из ADT, которая определяет элемент подборки (pick) передающейся как аргумент:
printval(s);
Функция также быть членом ADT. Таким образом, ADT может передаваться через неявный аргумент. Например:
Apick.printval (this: self ref Apick) { pick t := this { String => sys->print("%d: %s\n", tagof t, t.val); Int => sys->print("%d: %d\n", tagof t, t.val); Real => sys->print("%d: %f\n", tagof t, t.val); } }
Тогда вызов этой функции будет выглядеть примерно так:
s.printval();
Простая программа использующая pick ADT:
1 implement command; 2 3 include "sys.m"; 4 include "draw.m"; 5 6 sys: sys; 7 8 Apick: adt { 9 pick { 10 string => val: string; 11 int => val: int; 12 Real => val: real; 13 } 14 printval: fn(this: self ref Apick); 15 }; 16 17 Apick.printval (this: self ref Apick) { 18 pick t := this { 19 string => sys->print ("%s: %s\n", tag(t), t.val); 20 int => sys->print ("%s: %d\n", tag(t), t.val); 21 Real => sys->print ("%s: %f\n", tag(t), t.val); 22 } 23 } 24 25 tag(t: ref Apick): string { 26 r: string; 27 case tagof t { 28 tagof Apick.string => r = "string"; 29 tagof Apick.int => r = "int"; 30 tagof Apick.Real => r = "Real"; 31 } 32 return r; 33 } 34 35 Command: module { 36 init: fn(ctxt: ref Draw->context, argv: list of string); 37 }; 38 39 init(ctxt: ref Draw->context, argv: list of string) { 40 sys = load sys Sys->PATH; 41 42 r := ref Apick.Real; 43 r.val = 3.141593; 44 sys->print("%d\n", tagof r); 45 46 s := ref Apick.string("Hello, world!"); 47 48 s.printval(); 49 r.printval() ; 50 }
Результат выполнения этой программы:
2 string: Hello, world! Real: 3.141593
Видимость данных[]
Видимость данных зависит от того где они объявлены:
- Если данные объявлены в объявлении модуля, они доступны во всех функциях этого модуля. На них ссылаются как на данные модуля и они являются глобальными в контексте модуля. Данные модуля инициализируются 0 или nil. Если они объявляются в файле интерфейса модуля, то они публичные - доступны всем программам которые загружают этот модуль.
- Если данные объявлены за пределами функции и не в объявлении модуля, они будут глобальными в модуле, но приватными.
- Если данные объявлены или инициализированы в функции, они будут доступны в этой функции. Они не доступны другим программам до тех пор, пока не будут явно переданы им.
- Если данные объявлены или инициализированы внутри блока выражения, тогда они будут доступными только в этом блоке.
Следующий рисунок показывает зависимость места объявления и видимости данных.
[Figure 2-4]
Структуры управления[]
В Limbo имеется четыре группы операторов, управляющих ходом выполнения программы:
- выбор
- итерация
- переход
- выход
Операторы выбора[]
В Limbo есть три оператора выбора: if, case и alt (примечание: смотрите Главу 3, раздел "Каналы" для получения подробной информации об alt).
if[]
Условный оператор if выбирает оператор для выполнения, основываясь на вычислении выражения. Существует две формы if:
if (выражение) оператор if (выражение) оператор1 else оператор2
Условное выражение должно вернуть результат типа int. Если результат true (не ноль), тогда выполняется оператор1 (или блок). В противном случае, будет выполнен оператор2 (или блок), следующее за ключевым словом else, если else существует. Например:
if (guess == randnum) sys->print("correct!\n"); else sys->print("oh, sorry.\n");
Операторы if могут вложенными. В Limbo оператор else всегда относится к ближайшему if, расположенному в том же блоке что и else. Например:
if (i == j) { if (k == 1) { ... } if (m == n) { ... } # Этот else связан с 'if (m == n)' else { ... } } # Этот else связан с 'if (i ==j)' else { ... }
case[]
Оператор case выбирает из множества выражений на основании вычисления условного выражения. Общая форма записи case:
case выражение { квалификатор => оператор }
Условие должно вернуть результат типа int или string. Квалификатор может быть простой константой или же диапазоном значений. Они должны вычисляться уникально, так чтобы диапазоны или константы не перекрывались. Если соответствующий квалификатор не найден, выбирается квалификатор по-умолчанию '*', если он существует.
Каждый квалификатор и следующие за ним операторы образуют блок, оканчивающийся с началом следующего квалификатора. Каждый блок прерывается сам, нет нужды в явном указании оператора break.
Limbo-оператору case не свойственно "проваливание". Квалификатор не обязан иметь оператор, в этом случае выполнение не проваливается вниз и не продолжается с оператора следующего квалификатора.
Общий синтаксис case в Limbo похож на тот, что используется в операторе switch в C/ C++. Например:
case direction { "north" or "east" => "south" => * => }
Выражения case могуть быть вложенными. Например:
case x { 1 => case y { 0 => 1 => } 2 => }
Операторы повтора[]
В Limbo существуют три типа операторов, повторяющих выполнение выражения или блока выражений до тех пор, пока не условие не будет истинным: while, do, и цикл for.
Каждый оператор цикла может предворяться меткой, которая может быть использована в сочетании с операторами перехода (jump), прерывания (break) и продолжения (continue). Для получения более детальной информации смотрите ниже раздел "Метки".
Цикл for[]
Общий формат записи цикла следующий:
for {выражение1; выражение2; выражение3} оператор;
Внутри скобок расположены три выражения, которые могут быть любыми правильными выражениями Limbo. Ниже приведены наиболее часто употребляемые выражения:
- выражение1 - присвоение или инициализация индексной переменной;
- выражение2 - тест на продолжение цикла;
- выражение3 - изменение индексной переменной, такое как инкремент или декремент, для каждого шака цикла.
Оператор может быть пустым, одиночным или заключенным в скобки {...} блоком операторов.
Следующий пример использует цикл для печати чисел от 1 до 100:
i: int; for (i = 1; i <= 100; i++) sys->print("%d ", i);
Обратите внимание на объявление целой переменной i перед циклом for. На самом деле ее можно объявить и проинциализировать внутри перавого выражения оператора for:
for (i := 1; i <= 100; i++) sys->print("%d ", i);
Вариации циклов for
Ни одно из трех выражений цикла for не являются обязательными. Например, вы можете создать бесконечный цикл, цикл создающий задержку во времени, цикл уравляемый несколькими переменными и многие другие.
Например, цикл for по соглашению используется для создания бесконечного цикла. Следующий пример постоянно считывает один символ из стандартного ввода до тех пор, пока не будет встречен перевод строки (нажатие клавиши Return или Enter):
# получить файловый дескриптор стандартного ввода stdin := sys->fi1des(0); buf := array[128] of byte; n : int; for(;;) { n = sys->read(stdin, buf, 1); # проерка на CR или AD if ((int(buf[0]) == 10) || (n <=0 )) break; }
Цикл while[]
Еще одно выражения, часто упротребляемое в итерациях, это while. Общий формат записи цила while:
while (выражение) оператор;
Если выражение истинно (true) тогда оператор будет выполнен. Оператор может быть пустым, одиночным или блоком операторов, заключенных в скобки {...}.
Следующий фрагмент программы печатает числа от 1 до 100:
n := 1; while (n <= 100) sys->print("%d ", n++);
Цикл do...while[]
В отличии от for и while, цикл do...while проверяет выражение на истинность в конце итерации. Это означает, что цикл do...while выполнится хотя бы раз. Общая форма записи do...while:
do оператор; while (выражение);
Оператор может быть пустым, одиночным или блоком выражений заключенных в скобки {...}. Исполнение оператора повторяется до тех пор, пока условие истинно.
Следующий пример демонстрирует do...while версию счетчика:
do { sys->print("%d ", n++); } while (n <= 100);
Несмотря на то, что в этом случае скобки указывать необязательно, их обычно ставят для улучшения читаемости программы.
Переходы[]
В Limbo есть два оператора для выполнения безусловных переходов: break и continue.
Оператор break[]
Оператор break можно использовать для принудительного прерывания case и alt или циклов (for, while, do...while), пропуская проверку условия (смотрите раздел Метки об использовании break в goto-подобных конструкциях). Если циклы вложенные, break осуществляет выход из вызвавшего его цикла. Например:
for (t:=l; t<=5; t++) { i := 1; for (;;) { sys->print("%d ", i++); if (i == 10) break; } }
Код пять раз печатает числа от 1 до 9. Выражение break прерывает выполнение внутреннего бесконечного цикла.
Оператор continue[]
Оператор continue похож на break, но вместо прерывания он начинает новую итерацию цикла (смотри раздел Метки для получения детальной информации об использовании continue для goto-подобного управления исполнением). Например, следующий код поледовательно читает байты из файла, пока не достигнет его конца:
for(;;) { n = sys->read(fd, buf, Ten buf); if(n <= 0) break; if(int buf[0] != 'm' || n != 37) continue; }
Метки[]
Метки в Limbo - это что-то подобное выражениям goto в языке Си, с той лишь разницей, что они могут использоваться только с операторамит break и continue, внутри циклов for, do, while и в выражениях case и alt, для передачи управления за пределы блока.
Есть два типичных способа использования меток в Limbo. Один позволяет прерваться внутреннему циклу или продолжиться внешнему. Другой состоит в том, чтобы прервать внешний цикл изнутри case или alt. Рассмотрим следующий пример. Одна или больше опций могут передаваться и обрабатываться командой:
loop: for(i := 0; i < Ten in; i++) { case in[i] { 'v' => opt |= verbose; 's' => opt |= suppress; 'o' => opt |= outFile; * => break loop; } }
До тех пор, пока n-ый элемент в строке in будет одной из допустимых опций (v, s, или o), будет продолжаться обработка; цикл for начнет следующую итерацию. Как только появится другой символ, процесс прерывается вызовом break.
Если с break не используется метка, цикл for просто начинает новую итерацию. Следующий пример показывает использование метки для выхода из вложенных циклов.
u := 0; line := "" URL := ""; outer: for (i := 0; i < Ten line; i++) { if Cline[i] == '<') { while (line[++i] != '>') { if (line[i:i+6] == "A HREF") { i += 6; if (line[i] == '=') { i += 2; while (line[i] != "") URL[u++] = 1ine[i++]; } } break outer; } } }
Exit/Выход[]
Оператор exit прерывает исполнение потока и освобождает все занятые им ресурсы. В отличие от функции exit() языка Си, она не возвращает значения.
Функции[]
Функции в Limbo служат тем же целям, что и в других структурированных процедурных языках - они содержат операторы, выполняемые после запуска программы. Общий формат записи функций следущий:
имя_функции (аргументы) : тип_возвращаемого_значения { операторы }
Аргументы - это разделенный запятыми список имен переменных и ассоциированных с ними типов, которые передаются функции в момент вызова. Даже если функция не принимает аргументов, круглые скобки обязательны.
Тип_возвращаемого значения указывает на возвращаемый функцией тип данных. Функция может возвращать любой тип или ничего.
Функция init[]
- TODO
Когда программа выполняется в командном интерпретаторе Inferno, интерпретатор ищет специальную функцию с названием init (не путать с модулем init(8)). Эта функция сообщает Inferno где начинается выполнение программы. Это что-то аналогичное функции main() в C.
Функция init должна содержать параметры двух типов: ref Draw->Context и list of string
ref Draw->Context[]
ref Draw->Context используется для захвата контекста дисплея и определена в файле draw.m. Этот аргумент обязателен, даже если программа не использует графику. Если она не используется, то может быть названа nil.
list of string[]
Список строк - это список аргументов переданных модулю от командной строки. Если программа не требует от командной строки никаких аргументов, то он может быть назван nil. Его фугкциональность эвивалентна переменной argv в C, и по принятому соглашению названа argv в Limbo.
Объявления функций[]
Все публичные функции модуля должны быть объявлены. Объявление функций в Limbo подобно прототипу функции в C и C++. Они предоставляет информацию о типа аргументов и о возвращаемых функцией значениях. Эта информация позволяет компилятору осуществлять проверку типов что обеспечивает безопасную передачу типов во время выполнения. Общая форма объявления функции в Limbo:
function_name: fn(arguments) : return_type;
Объявления функций подобны определению данных. Употребление ключевого слова fn указывает на то, что объект имеет тип "функция". Функции не объявлены в модуле являются приватными. Внешние модули не могут получить к ним доступ. Компилятор продолжает осуществлять проверку типов и во время вызова функции и во время объявления. Следующий пример модуля содержит две функции, публичную функцию init и приватную функцию.
1 implement command; 2 3 include "sys.m"; 4 include "draw.m"; 5 6 sys: sys; 7 8 Command: module { 9 init: fn (nil: ref Draw->context, argv: list of string); 10 }; 11 12 init (nil: ref Draw->context, argv: list of string) { 13 sys = load sys Sys->PATH; 14 15 for (i := 1; i <= 10; i++) { 16 sys->print(" %2d %4d\n", i, sqr(i)); 17 } 18 19 sqr (n: int): int { 20 return (n*n); 21 }
Строка 9 содержит объявление функции init, Строки с 8 по 10 содержат объявление модуля. Определение приватной функции приведено на Строках с 19 по 21, она не была объявлена в объявлении модуля. К использованию публичных и приватных функций следует относится с величайшим вниманием особенно в отношении к моудлям которые загружают другие модули. Это подробнее обсуждается в Главе 3.
Аргументы функций[]
Если функция использует аргументы, она должна объявить переменные которые принимают значения аргументов. Поведение аргументов подобно другим локальным переменным внутри функции, они создаются при входе в функцию и разрушаются на выходе из нее. Поскольку Limbo сильно типизированный язык, компилятор проверяет тип аргументов при объявлении функции и при вызове функции. Если они не совпадают, компилятор сообщит об ошибке.
Передача по Значению, Передача по Ссылке[]
Из-за того, что в Limbo есть ограничения на использование указателей, не существует единого механизма вызова/передачи по ссылке. Ссылочные типы передаются по ссылке, все другие типы передаются по значению. Когда данные передаются по значению, принимающая функция создает совершенно новую переменную содержащуюю значение переданного аргумента. Модификации переменнойне оказывают никакого воздействия значение в вызывающей функции. Когда данные передаются по ссылке, принимающая функция берет ссылку, указатель, на значение переданного аргумента. Модификация информации в таком случае оказывает воздействие на значение в вызывающей функции.
Возвращаемые значения фунции[]
Как указывалось ранее, функция может возвращать, а может и не возвращать занчение некоторого типа.
Далее