Разработка программ в среде операционной платформы Inferno

       

Limbo: коммуникация каналов и основные графические возможности


Будучи предложенным новичком "комнаты Unix" (the Unix room) Шоном Дорвардом (Sean Dorward), язык Limbo суть безусловно интегральная составляющая часть Inferno. Перед обсуждением языка не помешает ознакомится с рядом фундаментальных императивов, который был выдвинут при его проектировании:

  • Малый размер - язык не должен требовать большого компилятора/runtime-системы.
  • Портабельность - скомпилированный код должен выполняться на различных машинных архитектурах.
  • Скорость - время исполнения должно приближаться к таковому в C.
  • Динамичность - чтобы минимизировать использование памяти и увеличить гибкость, части кода и данных должны быть загружаемыми и выгружаемыми во время исполнения.
  • Безопасность - проверки типов во время компиляции и исполнения должны использоваться с тем, чтобы предотвратить запуск некорректных программ.
  • Многозадачность - необходима легкость в написании программ, содержащих несколько процессов (в терминологии Inferno - нитей), которые коммуникатируют между собой и/или совместно используют данные.
  • Высокоуровневая сборка мусора - программисты должны сбросить с плеч бремя явного освобождения памяти.

Что же получилось в результате? Прежде всего удачный инструмент, язык простой, но мощный, и незаменимый при разработки параллельных, распределенных систем. Чтобы бегло ознакомиться с особенностями языка, рассмотрим пару примеров (замечание: номера строк введены для удобства ссылок). Полагаем, что можно безболезненно обойтись без "Hello World!" и ей подобных, поэтому приводим примеры более серьезных программ.

01 implement Targ1; 02 03 include "sys.m"; 04 sys: Sys; 05 include "draw.m"; 06 07 Targ1: module { 08 init: fn (nil: ref Draw->Context, argv: list of string); 09}; 10 11 init (nil: ref Draw->Context, argv: list of string) 12 { 13 sys = load Sys Sys->PATH; 14 n:= len argv; 15 sync:= chan of int; 16 spawn targ (sync, n); 17 # Вывод параметров командной строки с секундной задержкой 18 for (i:= 0; i < n; i++) { 19 sys->print ("%d: %s\n", i, hd argv); 20 argv = tl argv; 21 <-sync; 22 } 23} 24 25 targ (sync: chan of int, n: int) 26 { 27 for (;;) { 28 sys->sleep (1000); 29 sync <-= 1; 30 } 31}


Первая программа записывает свои параметры, разделенные пробелами, по одному на каждую строку файла вывода. Взглянув на синтаксис языка Limbo, можно констатировать факт его попадания под отнюдь не дурное влияние C (что неудивительно) и Pascal - это выражения, операторы и соглашения первого и объявления второго. Структурная программная организация (по всей видимости, уходит корнями в Modula-2) - модули. Так, первая строка в листинге указывает на создание модуля Targ1. Имя файла не обязательно должно совпадать с именем модуля. В 3-й и 5-й строках объявляются стандартные встроенные модули sys.m (обеспечивает основные системные и В/В примитивы) и draw.m (графические возможности). В Limbo отсутствует препроцессор, как у C, следовательно, include является ключевым словом, а не директивой препроцессора, тем не менее по функциональности они эквивалентны. Имя файла, предваренное include, всегда заключается в кавычки ("модуль.m"). 4-я строка объявляет переменную sys типа Sys (Limbo регистро-зависимый язык), определенную в модуле sys.m. По соглашению, внутреннее значение sys на данном этапе равняется nil (ссылка на "ничего").

Каждый из модулей Limbo состоит из разделов интерфейса и реализации. В интерфейсе объявляются данные и методы, типы данных и константы модуля, в примере это строки с 7-й по 9-ю - модуль Targ1 и его единственная функция init с двумя аргументами: ref Draw->Context и list of string. Подобная init необходима во всех программах, вызывающихся из командной строки Inferno; она во многом аналогична функции main языка C, поскольку обозначает начало выполнения инструкций. Аргумент ref Draw->Context определен в draw.m, он используется для захвата контекста показа, потребность в нем есть даже в том случае, если программа совсем не работает с графикой, так, в нашем примере, этот аргумент может быть назван nil. list of string есть список (группа последовательных, упорядоченных элементов) параметров командной строки.

В реализации размещаются тела функций модуля, в примере это строки с 11-й по 31-ю.


В 13- й посредством ключевого слова load и константы PATH, содержащей путь к модулю, создается ссылка на реализацию библиотечного модуля Sys, которая присваивается переменной sys. Происходит связывание программы с модулем, при этом первая получает доступ ко всем данным и методам последнего. В следующей строке под номером 14 переменной n присваивается значение количества элементов в списке параметров.

Limbo присуща легкость в организации упреждающей многозадачности (другими словами, созданию асинхронных, независимых нитей, совместно использующих одно общее пространство памяти) и управлении взаимодействием между ними с помощью коммуникационного канала. В 15-й строке программы объявляется канал-коммуникатор sync целого типа. Далее, строка 16, с использованием оператора spawn инициируется отдельная нить targ, служащая темпо-регулятором показа параметров командной строки; с этого момента функции init и targ будут запускаться параллельно в независимых нитях. 17-я служит комментарием - в Limbo комментарии начинаются с шарпа и распространяются на всю оставшуюся строку. В строках с 18-й по 22-ю задается цикл секундного ожидания и вывода параметров командной строки. Инкремент (фигурирующий в заголовке цикла) допускается только в постфиксной форме. Перемещение по элементам argv выполняется посредством операторов hd (первый элемент списка) и tl (часть списка без первого элемента). Последнее выражение цикла - ожидание значения, получаемого из канала. В targ же совершается секундная задержка, - это строка 27, где функция sys->sleep получает время в миллисекундах как аргумент, и отправка единицы. После n-передач выполняется выход из цикла.

01 implement Targ2; 02 03 include "sys.m"; 04 sys: Sys; 05 include "draw.m"; 06 draw: Draw; 07 Point, Rect, Display, Image, Font, Screen, Context: import draw; 08 09 Targ2: module { 10 init: fn (ctxt: ref Draw->Context, argv: list of string); 11}; 12 13 init (ctxt: ref Draw->Context, argv: list of string) 14 { 15 sys = load Sys Sys->PATH; 16 draw = load Draw Draw->PATH; 17 18 display:= ctxt.display; 19 screen:= ctxt.screen; 20 21 black:= display.color (Draw->Black); 22 grey:= display.rgb (192,192,192); 23 font:= Font.open (display, "*default*"); 24 wr:= Rect ((100,100), (325,150)); 25 r:= Rect ((125,110), (300,140)); 26 27 win:= screen.newwindow (wr, Draw->White); 28 29 n:= len argv; 30 for (i:= 0; i < n; i++) { 31 win.draw (r, grey, nil, r.min); 32 win.text (r.min.add ((5,8)), black, (0,0), font, string i+": "+hd argv); 33 argv = tl argv; 34 sys->sleep (1000); 35 } 36}



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

В 6-й строке объявляется переменная draw типа Draw, в следующей за ней импортируются основные типы библиотеки. Строки с 9-й по 11-ю (интерфейс), ref Draw->Context уже не nil, а ctxt, поскольку необходима "настоящая" графика. Переходим к реализации. После строк 15 и 16, в которых загружаются модули Sys и Draw, для обращения к типам Display и Screen происходит создание локальных переменных display и screen. Первая, типа ref Draw->Display, представляет связь с физическим устройством отображения, CRT или LCD дисплей, на котором программа будет рисовать. Вторая, типа ref Draw->Screen, является структурой данных, используемой системой для управления окнами.

Следующий этап, строки 21-25, - распределение графических единиц: цветов, прямоугольников и шрифтов. Цвет black создается посредством имени, известному Draw; grey же определяется с использованием тройки красный/зеленый/синий (red/green/blue, RGB). Копия стандартного шрифта *default* загружается в переменную, удачно названную font. Модуль Draw содержит два геометрических типа: Point, точка с целочисленными координатами x и y, описывающая верхний левый угол соответствующего пикселя, и Rect, прямоугольная область, определяемая двумя точками - верхней левой (Rect.min) и нижней правой (Rect.max). Прямоугольник wr описан размером и размещением окна: верхний левый угол (100,100), нижний правый угол (325, 150). Прямоугольник r служит блоком серого цвета. В 27-й строке создается окно win; первый подразумевающийся аргумент, Screen, описывает то, где и как окно размещается и его цвет. Наконец, в теле цикла, в 31-й и 32-й строках, окно прорисовывается: вызов win.draw создает серый блок, определенный r, а win.text выводит сообщение. Выражение r.min.add ((5,8)) идентифицирует верхний левый угол текста, добавляя точку (5,8) к координатам верхнего левого угла прямоугольника r.min.

После того как программа завершит свою работу и последняя ссылка на все частные графические ресурсы исчерпается, в дело вступит сборщик мусора, т.е. заботится об удалении окна с экрана не нужно.


Содержание раздела