Inferno OS Wiki
Регистрация
Advertisement

Inferno Programmer's Guide / Руководство программиста по ОС Inferno Chapter 3 - Limbo Programming Перевод с английского на русский

Программирование на Limbo

Обзор[]

В этой главе даётся обзор возможностей, которые важны как для программирования на Limbo, так и в программном окружении Inferno.

В этой части описываются:

  • Модули
  • I/O
  • Нити
  • Каналы
  • Графическое программирование

Модули[]

В Limbo модули - основной строительный материал для программ. Про каждую программу можно сказать - это "модуль". Limbo-модули не только делят программу на меньшие части, а еще:

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

Интерфейс и реализация[]

Limbo-модули могут сочетать интерфейс и реализацию. Программа Greet (приветствие!) показанная в программном листинге 2-1 на странице 2-2 - пример этого. В данном случае в объявлении модуля присутствует только функция init, поэтому извне можно вызывать только эту функцию. Так же как и модульная модель программирования на Limbo, разделение интерфейса и реализации является важной концепцией. Это означает, что программа на Limbo может динамически загружать и выгружать модули или библиотеки.

Inferno Reference API иллюстрирует мощь этого подхода в Limbo. API группирует функциональность в небольшие загружаемые модули, с хорошо определенными интерфейсами содержащимися в файлах-интерфейсах модулей. Исполняемый код содержится в файле реализации

Файл интерфейса модуля[]

Интерфейс public доступа к модулю содержится в файле его интерфейса, они по соглашению имеют расширение ".m". Это аналогично заголовочным файлам на C/C++ (.h). The Inferno Reference API по соглашению хранится в директории /module. Файл интерфейса модуля определяет public данные и функции члены. Там определены типы и интерфейсы функций, которые могут использоваться другими программами. Это определение интерфейса модуля.

Программа 3-1 - пример простого файла интерфейса модуля:

1 Greet: module {
2 init: fn(ctxt: ref Draw->context, argv: list of string);
3 };

Это объявление одной функции - init. Листинг программы 3-2 показывает другой пример небольшой программы, модуль Random генерации случайных числе из Inferno Reference API(/module/rand.m)

1 Rand: module
2 {
3 PATH: con "/dis/lib/rand.dis";
4 # init sets a seed
5 init: fn(seed: int);
6 # rand returns something in 0 .. modulus-1
7 # (if 0 < modulus < 2^31)
8 rand: fn(modulus: int): int;
9 # (if 0 < modulus < 2^53)
10 bigrand: fn(modulus: big): big;
11 };

Здесь определена строковая константа PATH, в которой содержится путь к скомпилированному файлу реализации и трем функция init, rand и big rand. Строковая константа PATH - соглашение в Limbo-модулях.

Файл реализации[]

Реализация модуля содержится в его файле реализации, который по соглашению находится в файле с расширением .b. Это исполняемый оператор программы. Также он может называться файлом с исходниками. Примеры файлов реализации находятся в ветке /appl. Листинг программы 3-2 - файл реализации для файла интерфейса модуля показанного в листинге программы 3-1: Program Listing 3-3 greet.b

1 implement Greet;
2
3 include "sys.m";
4 include "draw.m";
5 include "greet.m";
6
7 sys: Sys;
8
9 init(ctxt: ref Draw->context, argv: list of string) {
10 sys = load Sys Sys->PATH;
11
12 if ((tl argv) != nil)
13 sys->print("Hello, %s!\n", hd (tl argv));
14 else
15 sys->print("Hello, wor1d!\n");
16 }

Файл реализации содержит определение функции, которое объявлено в файле интерфейса модуля. Файл реализации может также содержать private данные и объявление функций членов и определение. Например, файл реализации модуля Random (генерации случайных чисел) (/appl/lib/rand.b )

1 implement Rand;
2
3 include "rand.m";
4
5 rsalt: big;
6
7 init(seed: int)
8 {
9 rsalt = big seed;
10 }
11
12 MASK: con (big l«63)-(big 1);
13
14 rand(modulus: int): int
15 {
16 rsalt = rsalt * big 1103515245 + big 12345;
17 if(modulus <= 0)
18 return 0;
19 return int (((rsalt&MASK)»10) % big modulus);
20 }
21
22 bigrand(modulus: big): big
23 {
24 rsalt = rsalt * big 1103515245 + big 12345;
25 if(modulus <= big 0)
26 return big 0;
27 return ((rsalt&MASK)»10) % modulus;
28 }

В этом файле находятся определяние трех public членов, init, rand и big rand, также как и private члены, такие как rsalt или MASK.

Исполняемый файл[]

Скомпилированный файл реализации - это исполняемый файл с расширением (.dis) по соглашению находится в директории /dis. Исполняемый файл - это объект который загружается во время исполнения. Это происходит с помощью оператора load. Например для загрузки rand, dis-файл, оператор загрузки выглядит так: rand := load Rand Rand->PATH; Это подробнее обсуждается на странице 3-9.

Структура модуля[]

Основная структура Limbo-модулей включает следующее:

  • Секция реализации
  • Объявление интерфейса модуля( необходимо всем файлам)
  • Определение функций ( нужно только для файлов реализации)

Секция Implementation[]

Секция реализации внутри Limbo-модуля определяет, какой модуль реализуется. Вначале находится оператор implement, как здесь: implement Greet; По соглашению оператор include также расположен внутри секции реализации. Например: include "sys.m"; include "draw.m";

Объявление интерфейса модуля[]

Объявление интерфейса модуля определяет public доступную часть модули. Она включает все public типы и функции. Если у модуля есть отдельный файл модуль интерфейса - .m файл, то он содержит объяление этого интерфейса. Например:

Greet: module {
init: fn(ctxt: ref Display->Draw, args: list of string);
};

Здесь говориться, что Greet - модуль. Интерфейс public доступа состоит из единственной функции init, которая принимает два аргумента.

Определение функций[]

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

init(ctxt: ref Draw->context, argv: list of string) {
    sys = load Sys Sys->PATH;
    if ((tl argv) != nil)
        sys->print("He"l"lo, %s!\n", hd (tl argv));
    else
        sys->print("He"l"lo, wor1d!\n");
}

Использование модулей[]

Данные модуля и члены функции становятся доступны путем включения файла модуля-интерфейса и загрузки файла-реализации в код программы Limbo.

Оператор include[]

Как упоминалось ранее, в Limbo, слово include является зарегистрированным, а не директивой препроцессора. Выражение include указывает файл модуля-интерфейса (.m) который вы хотите включить в свою программу. Это необходимо сделать перед тем как вы загрузите или воспользуетесь данными этого модуля или функциями-членами . Имя файла следующее за ключевым словом include всегда заключено в двойные кавычки (например, " sys.m"). Если не указан явный путь к файлу, Limbo ищет его текущей рабочей директории. Если файл не найден, он ищется в директориях указанных в опции -l компилятора Limbo. Если и это не удаётся, он ищется в каталоге /module.

Оператор load[]

Перед вызовом функций модуля приложение должно загрузить модуль. Приложение использует результирующий дескриптор для описания модуля. Например:

sys = load Sys Sys -> PATH;
mymod= load MyMod "/mymodules/mymod.dis";

Основная форма оператора load такова:

handle = load имя_модуля путь_к_модулю_реализации;

Как показано в модулях из Inferno Reference API, в соответствии с соглашением, константа PATH объявляется в модуле, и содержит явный путь к файлу реализации. (Более подробно об Inferno Reference API смотрите Inferno Reference Manual) Используйте оператор взятия члена модуля (->) для доступа к членам данным и членам функциям модуля. Например:

sys := load Sys Sys->PATH;
sys->print("Hello, world!\n");

В первой строке, переменная sys объявлена и ей присвоен тип дескриптора модуля Sys. Во второй сточке дескриптор sys используется для доступа к функции print с помощью оператора ->. Модуль Sys использует специальную константу PATH:

sys: module {
PATH: con"$Sys" ;
}

Константа "$Sys" показывает, что это встроенный модуль. Функции встроенных модулей - это код на C написанный для системы Inferno, но не реализованный на Limbo и откомпилированный в файл .dis. Встроенными модулями также являются:

Draw, Keyring, Loader, Math, Prefab и Tk.

Оператор import[]

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

sys := load Sys Sys->PATH;
print: import sys;
print("Hello, world!\n");

По соглашению оператор import следует за оператором include или оператором объявления дескриптора. Так импортируемые данные или функции попадают в глобальную область видимости внутри текущего модуля (доступны всем функциям внутри модуля напрямую). Например, мы можем изменить программу Greet для импорта функции print. Листинг программы 3-5 greetimport.b

1 implement command;
2
3 include "sys.m";
4 include "draw.m";
5
6 sys: Sys;
7 print: import sys;
8
9 Command: module {
10 init: fn(ctxt: ref Draw->context, argv: list of string); 
11 };
12 init(ctxt: ref Draw->context, argv: list of string) {
13 sys = load Sys Sys->PATH;
14
15 if ((tl argv) != nil)
16 print("Hello, %s!\n", hd (tl argv));
17 else
18 print("Hello, world!\n");
19 }

На 7ой строке, оператор import следует за объявлением дескриптора модуля Sys (строка 6). Далее функция может быть доступна как локальная, без квалификатора дескриптора модуля (строки 16 и 18).

Ввод-вывод (I/O)[]

Функциональность ввода и вывода (I/O) - важный аспект виртуальности всех Limbo-программ. Функции ввода-вывода на Limbo предоставляются Inferno Reference API.The Inferno Reference API описано подробнее в его справочном руководстве(Inferno Reference Manual). Базовый ввод-вывод на Limbo содержится в модуле System (sys.m). Эти функции похожи на основные небуферизироованные функции UNIX C. Большинство примеров до настоящего времени использовало функции ввода-вывода из модуля Sys.

Другой модуль, буферизированный модуль ввода-вывода (bufio.m), предоставляет фнкции буферизированного ввода-вывода которые похожи на функции стандарта ANSI C. Другие модули в Inferno Reference API предоставляют более продвинутые и специализированные функции ввода и вывода, такие как модуль указывающего устройства (Device Pointer)((devpointer.m)), модуль инфракрасного порта (ir.m) и модуль синхронизации ввода-вывода (timedio.m). Limbo-программа может использовать любую комбинацию функций предоставляемых этими модулями.

Базовый ввод-вывод[]

Базовый ввод-вывод - это стандартное средство ввода-вывода на Limbo. Существует восемь основных функций из модуля Sys показанных в таблице 3-1. Таблица 3-1 базовые функции ввода-вывода в модуле Sys:

create Создание файла
open Открытие файла
print Форматированная печать в стандартный поток вывода
fprint Форматированная печать в открытый файл
read Чтение открытого файла
write Запись в открытый файл
stream Копирование байтов из источника в место назначения

Файловые дескрипторы[]

Файлы в Inferno идентифицируются файловым дескриптором (fd). Ссылка на файловый дескриптор необходима как параметр для функций read, write, fprint и stream. Файловый дескриптор может быть получен из функций open, create и fildes. Эти функции ввода-вывода возвращают ссылку, названную FD, на абстрактный тип данных (ADT) из модуля Sys, которая содержит целочисленное значение fd. При открытии файла, создаётся и возвращается Sys-> FD. Когда Sys->FD освобождается, файл автоматически закрывается. В Limbo нет явной функции закрытия. Сборщик мусора на Inferno немедленно и автоматически освобождает Sys->FD, когда все ссылки на него уходят из его области видимости. Если на Sys->FD нет других ссылок, файл можно закрыть явно, установив ссылку на Sys->FD в nil.

Стандартный ввод-вывод[]

Каждая Limbo-программа имеет доступ к трем стандартным файловым дескрипторам:

Стандартные дескрипторы ввода-вывода[]

0 Стандартный ввод (stdin)
1 Стандартный вывод (stdout)
2 Стандартный вывод ошибок (stderr)

Получение Sys->FD для стандартного потока ввода-вывода. Функция fildes в модуле Sys принимает в качестве целочисленного аргумента файловый дескриптор и создает ссылку на Sys->FD. Эта функция используется для получения ссылки на Sys->FD для файловых дескрипторов стандартного потока ввода-вывода. Например:

stderr := sys->fildes(2);

Это выражение присваивает ссылку на Sys->FD в идентификатора потока stderr. Затем для вывода в стандартный поток ошибки, ссылка передает функцию printf модуля Sys:

sys->fprint(stderr, "error: %s\n" , tnsg) ;

Функция open()[]

Функция open() возвращает ссылку на Sys->FD, которая необходима для вполнения других функций ввода-вывода модуля Sys. Основная форма вызова функции open() выглядит так:

fd := open {"имя_файла", режим);

Параметр имя_файла - это строка содержащая путь к файлу. Параметр режим задает режим открытия файла.

Режимы открытия файла

OREAD Открыть только для чтения
OWRITE Открыть только для записи
ORDWR Открыть для чтения и записи

Над параметрами режима можно производить логическую операцию или (OR) для задания двух дополнительных опций для специальной обработки файла. Таблица 3-4 показывает эти значения определенные в модуле Sys.

Таблица 3-4 Cпециальные опции для обработки файлов

OTRUNC усечь файл перед открытием (этой операции необходимы права на запись в файл)
ORCLOSE удалить файл во время закрытия

Например, следующее выражение открывает для чтения файл по имени program.cfg в текущей директории:

cfgfd := sys->open("program.cfg", sys->OREAD);

Функция create()[]

Функция create() как и функция открытия возвращает ссылку на Sys->FD, которая необходима для других функций ввода-вывода из модуля Sys. Основной формат для создания вызова функций включает те же первые два аргумента как и функция открытия и еще один добавочный:

fd := created("имя_файла", режим, права_доступа) ;

Дополнительный аргумент для создания функции определяет разрешение на доступ, с которыми этот файл создан. В Inferno права доступа такие же как и в Unix. Существует девять бит разделенных на три секции: первая секция - права владельца файла, вторая - групповые права, а третья - для всех остальных. Каждая секция содержит три бита по одному для чтения записи и исполнения(или поиска для директорий), где 0 - нет прав, 1 - есть.

r w X____ r w X __r w X
владелец группа все

Рисунок 3-6 права доступа к файлу

Трехзначное восьмеричное число удобно для определения прав доступа. В Limbo восьмеричным числам добавляется префикс 8r("radix(по основанию) 8").Например 8r666 -дает права на чтение и запись владельцу,группе и всем. Конечно, права доступа к директории где файл создан отражаются на всех правах доступа к файлу. Права доступа определённые созданием, затем логически складываются с правами доступа директории. Следующее выражение создает файл с именем file.tmp для чтения и записи в директорию /tmp. Он будет удалён при закрытии, владелец имеет права доступа на чтения и запись.

tmp := sys->create("/tmp/file.tmp",
sys->ORDWR | sys->ORCLOSE, 8r600);

Чтение и запись[]

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

ttnpbuf := array[] of byte;
n = sys->read(cfgfd, ttnpbuf, 80);

Для записи этих байт в file.tmp созданный ранее, можете использовать функцию записи:

write(tmp, ttnpbuf, 80);

Форматированный вывод[]

Функция для записи в стандартный поток вывода - это функция print из модуля System. Как и printf функция из С, она предоставляет форматированный вывод через через использование команд формата. Например:

sys->print("Hello, %s!\n", hd (tl argv));

Здесь используется команда форматирования %s чтобы напечатать данные содержащиеся в заголовке списка argv(типа строка), после печати последовательности строковых литералов. Например:

Hello, inferno!

Для вывода на устройство отличное от стандартного потока вывода, используйте функцию fprint. Для этого нужен дополнительный параметр - открытый дескриптор FD. Например, для в стандартный поток ошибок, используйте функцию fildes, чтобы получить:

# Получим файловый дескриптор для стандартного вывода ошибок
stderr := sys->fildes(2);
sys->fprint(stderr, "error: %s\n" , tnsg) ;

Команды форматирования[]

Основная форма команды форматирования такая: %[флаги]команда Таблица 3-1 список команд форматирования доступный для функции print. Таблица 3-1 команды форматирования для вывода на консоль

% prefix for big

например

%D десятичное целое формата big (аналог long)
%c символ
%d десятичное целое
%e Тоже что и %f с префиксом e
%E Тоже что и %f с префиксом E
%f действительное число вида [-]цифры[. цифры]
%g действительное число. Выбирает что больше подходит - %e или %f
%G действительное число. Выбирает что больше подходит- %E или %f
%r самая последняя строка системной ошибки; очищает ошибки
%s строка
%x шестнадцатеричное целое типа ref ADT

Флаги[]

Флаги команды формата определяют дополнительный контроль за форматированием. Флаги предоставляющие контроль для минимальной ширины поля, точности, выравнивания и десятичных и шестнадцатеричных чисел.

Минимальная ширина поля[]

При установленном флаге минимальной ширины поля, поле заполняется пробелами для того чтобы соответствовать указанной длинне. Если строка или число длинне чем указанная длина, то они будут напечатаны полностью. Например:

num := 123;
for (i := 1; i <= 5; i++) {
sys->print("%8d %8d %8d %8d %8d\n", i, i*i, i*i*i, i*i*i*i, i*i*i*i*i);
}
sys->print("%d\n", num);
sys->print("%10d\n\n", num);

Печатает:

1 1
2 4
3 9
4 16
5 25
123

1 1 1
8 16 32
27 81 243
64 256 1024
125 625 3125

Точность[]

Флаг точности следует за флагом минимальной ширины поля, если он есть. Флаг точности задаётся точкой и следующим за ней целым числом. Его точное значение зависит от типа данных. Если вы используете флаг точности вместе с действительным(с плавающей точкой) числом используя %f, %e или %E команды, он определяет количество выведенных позиций занятых десятичными числами. Если вы используете флаг точности с действительным числом используя команды %g или %G, он определяет число значащих цифр.

Например:

decnum := 2345.678901; sys->print("%. 3f\n", decnum);

Это выражение печатает:

2345.679

Если вы используете флаг точности со строкой используя команду %s, он определяет максимальную длину поля. Например, %3.6 печатает строку которая по крайней мере трех, но не более шести символов длиной. Если строка длиннее чем максимальное поле, строка обрезается. Например:

str := "Hello, inferno!"; sys->print("%5.10s\n\n", str);

Это выражение печатает:

Hello, inf

Если вы используете флаг точности используя команду %d, оно определяет максимальное число цифр на печать. Если число цифр меньше чем опрелено, спереди добавляются нули. Например:

num := 123; sys->print("%2.5d\n", num);

Это выражение печатает:

00123

По умолчание вывод выравнен по правому краю. Вы можете заставить выводиться по левому краю используя флаг выравнивания - знак минус (-). Поставив флаг выравнивания прямо после %, печать данных выравнивается по левому краю.

Например:

num := 123;
decnum := 2345.678901;
sys->print("Выравнен по правому краю: %5d\n", num);
sys->print("Выравнен по левому краю: %-5d\n", num);
sys->print("$%9.2f\n\n", decnum);

Печатает:

Выравнен по правому краю:____123
Выравнен по левому краю:123
$ 2345.68

Десятичные и шестнадцатеричные числа[]

Если вы используете знак решетки (#) с командами e, E, f, g, или G, то печатается десятичная точка даже если нет десятичных цифр. Если вы используете знак решетки (#) с командами x или X, шестнадцатеричные числа печатаются с приставкой Ox или OX соответственно.

Пример:

nutn := 123;
sys->print("%#d\n", num);
sys->print("%#e\n", real num);
sys->print("%x\n", num);
sys->print("%#x\n", num);

Печатает:

123
123.000000
7b
0x7B

Примеры[]

Листинг программы 3-7 - это законченная программа показывающая команды формата и флаги функции print: Листинг программы 3-7 iox.b

Эта программа выводит следующий результат:

123
1 1 1 1 1
2 4 8 16 32
3 9 27 81 243
4 16 64 256 1024
5 25 125 625 3125
123

2345.679 00123 Hello, inf
9.870000e-05 2.345679E+03 2345.678901 9.87e-05
Right-justified: 123 Left-justified: 123 $ 2345.68
123.0
7b
0X7B

Нити[]

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

init(ctx: Draw->Context, argv: list of string) {

Figure 97

Планирование[]

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

Использование потоков[]

Создавать нити в Limbo-программах легко. Каждая Limbo-программа запускается как одиночная нить. Первая нить каждой программы создается когда программа запускается, это начинается с исполнения функции init. Зарезервированное слово spawn позволяет вам создать новую, независимо планируемую нить выполнения из функции Limbo.

Оператор spawn[]

Ключевое слово spawn создает нить которая начинает выполнение функции переданной в качестве операнда. Например: spawn func(1, 2); Это выражение запускает нить из функции названной func и передает ей два аргумента. Текущая нить продолжает исполнение с выражения следующего за словом spawn.

Пример[]

Программа из Листинга 3-8 иллюстрирует нити Limbo: Program Listing 3-8 threadx.b

1 implement command;
2
3 include "sys.m";
4 include "draw.m"; 5
6 sys: sys;
7
8 Command: module {
9 init: fn(ctxt: ref Draw->Context, argv: list of string);
10 };
11
12 init(ctxt: ref Draw->Context, argv: list of string) {
13 sys = load sys Sys->PATH;
14
15 for (i:=l; i<=5; i++) {
16 spawn func(i);
17 sys->print("%2d\n", i);
18 }
19 }
20
21 func(n: int) {
22 sys->print("%2.2d\n", n) ;
23 }

Функция init в Строках с 11 по 18 создает цикл который повторяется пять раз, каждый раз порождая функцию func и печатая текущий номер итерации. Функция func также печатает номер итерации переданный ей init. (Флаг формата со значениями %d, 2 и 2.2, добавлен только для того, чтобы было легче отличить какая нить печатает какое число.) Вывод этой программы мог бы быть следующим:

// уточнить
divineS threadx 1
1 2
2 3 4 5
3
4
divine$ 5

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

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

Каналы[]

Каналы Limbo это языковой механизм, для передачи типизированных данных между нитями. Канал в чём-то напоминает массив: он сам по себе тип, и он содержит другой тип. Каналы это общая управляющая модель программ Limbo: выполняющийся процесс (например, init) запускает другие нити и общается с ними при помощи каналов.

Figure 102

Использование каналов[]

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

Объявление канала[]

Следующее выражение объявляет канал:

c : chan of int;

Это объявление канала, передающего целые значения. Каналы имеют ссылочный тип, поэтому они не имеют связанной области памяти, выделенной при определении. При определении его значение равно nil. Для инициализации канала и подготовки его к отправке и принятию данных, вы должны использовать выражение присваивания:

c = chan of int;

Оператор канала[]

После того, как канал объявлен и инициализирован он может посылать и принимать данные того типа, который был указан при объявлении. Используя оператор канала, <-, канал посылает или принимает данные, в зависимости от того с какой строны оператора расположен идентификатор канала.

Оператор отправки[]

В качестве постфиксного оператора (идентификатор канала расположен в левой части), оператор канала действует как оператор отправки. Он передает результаты выражения содержащиеся в в его правом операнде в канал указанный левым опреандом. Например:

c <-= 4; # send int on 'c'

Оператор приема[]

В качестве префиксного оператора (идентификатор канала расположен в правой части), оператор канала действует как оператор приема. Канал принимает данные поставляемые операндом. В дальнейшем, принятыми значениями могут манипулировать другие операторы. Например, они могут быть присвоены переменной или использоваться как операнд в выражении:

i := <-c; # присваивается значение принятое из c в переменную i
func(<-c); # в функцию func передается значение принятое из c

Выбор канала[]

Это можно сравнить с приложением в котором есть множество нитей, каждая из которых может посылать и принимать данные из множества каналов.

Оператор alt[]

Оператор alt выбирает операторы для выполнения опираясь на готовность каналов. Оператор alt в отношении каналов сравним с выражением case. Спецификаторы в операторе alt должны быть выражениями содержащими оператор взаимодействия с каналом, <-.

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

outchan := chan of string;
inchan := chan of int;
i := int;
alt {
i = <- inchan =>
outchan <- = "sent" =>
}

В этом примере, выборка осуществляется из двух каналов, inchan и outchan. Если inchan готов принимать, он будет выбран. Если outchan готов отправлять, выбирается он. Если оба они готовы одновременно, не определенно какой из них будет выбран. Последовательность спецификаторов не влияет на порядок выбора.

Примеры[]

Для демонстрации использования каналов, приведем три примера которые используют каналы для сообщения между нитями.

Простая синхронизация[]

В первом примере из Листинга 3-9, каналы используются для простой синхронизации (смотри Листинг программы 3-8 на странице 3-30).

Program Listing 3-9 chanx.b

1 implement command;
2
3 include "sys.m";
4 include "draw.m";
5
6 sys: Sys;
7 print: import sys;
8 sc: chan of int;
9 re: chan of int; 
10
11 Command: module {
12 init: fn(ctxt: ref Draw->context, argv: list of string);
13 };
14
15 init(ctxt: ref Draw->context, argv: list of string) {
16 sys = load Sys Sys->PATH;
17 sc = chan of int;
18 re = chan of int;
19
20 for (i:=1; i<=5; i++) {
21 spawn func();
22 print("%2d\n", i);
23 sc<- = i;
24 <-re;
25 }
26 }
27 
28 func() {
29 i := <-sc;
30 print(" %2d\n", i)
31 re<- = i;
32 }

Нить init инициализирует два канала которые передают целые числа. Затем порождается новая нить с функцией func. Новая нить начинает выполнение в func, где она блокируется в канале sc, до тех пор пока не придёт сообщение (Строка 29). Тем временем, нить init продожает выполняться, печатая целое (Строка 22) и отправляя целое число в качестве сообщения в канал sc, перед тем как он будет заблокирован для ожидания ответного сообщения (Строка 24).

Когда func примает сообщение из канала sc, она продолжает выполнение, напечатав целое (Строка 30) и отправляет целое в качестве сообщения в канал re, перед тем как завершить свое выполнение. После того как init примет сообщение из канала re (он блокируется в Строке 24), она начинает новый цикл итерации (Строка 20) и последовательность повторяется.

Взаимодействие с внешними функциями[]

Следующий пример состоит из трех файлов. Главная программа порождает новый поток с внешней функцией (из загруженного модуля) и испольует механизм каналов для взаимодействия в потоками.

chanx2a.b:
1 implement command;
2
3 include "sys.m";
4 include "draw.m";
5 include "chanx.m";
6
7 sys: sys;
8 print: import sys;
9 ex: chanx;
10 sc: chan of int;
11 re: chan of int;
12
13 Command: module {
14 init: fn(ctxt: ref Draw->context, argv: list of string);
15 };
16
17 init(ctxt: ref Draw->context, argv: list of string) {
18 sys = load sys Sys->PATH;
19 cx = load chanx "./chanx2b.dis";
20 sc = chan of int;
21 re = chan of int;
22
23 for (i:=l; i<=5; i++) {
24 spawn cx->func(sc, re);
25 print("%2d\n", i);
26 sc<- = i;
27 func(<-rc);
28 }
29 }
30
31 func(i: int) {
32 print("%6d\n", i);
33 }

chanx2b.m (интерфейс модуля для chanx2b.b):

1 chanx: module {
2 func: fn(sc: chan of int, re: chan of int);
3 };

chanx2b.b

1 implement chanx;
2
3 include "sys.m";
4 include "draw.m";
5 include "chanx.m";
6
7 sys: sys;
8 print: import sys;
9
10 func(sc: chan of int, re: cha
11 sys = load sys Sys->PATH;
12 i: int;
13 i = <-sc;
14 print("%4d\n", i);
15 rc<- = i;
16 }

Пример событийного пользовательского интерфейса[]

Этот пример демонстрирует использование оператора alt. В событийной модели графического окружения Limbo/Tk, каналы используются для отправки команд от графических элементов (виджетов). Использование оператора alt даёт возможность программам откликаться на команды от различных событий. (Подробную информацию о графическом программировании в Limbo смотри в разделе Графическое Программирование на странице 3-43.) Хотя Листинг 3-13 длиннее большинства других примеров, использование оператоа alt начинается со строки 50.

Листинг 3-13 wmaltx.b

1 implement wmAltx;
2
3 include "sys.m";
4 include "draw.m";
5 include "tk.m";
6 include"wmlib.m";
7
8 sys: sys;
9 draw: Draw;
10 Display, image: import draw;
11 tk: Tk;
12 wmlib: wmlib;
13
14 WmAltx: module {
15 init: fn(ctxt: ref Draw->context, argv: list of string);
16 };
17
18 win_cfg := array[] of {
19 "frame .ft",
20 "text .ft.t -yscrollcommand {.ft.s set} -width 40w -height 15h",
21 "scrollbar .ft.s -command {.ft.t yview}",
22 "focus .ft.t",
23 "pack .ft.s -side right -fill y",
24 "frame .fb.a -bd 2 -relief sunken",
25 "pack .ft.t -side left -fill y",
26 "button .fb.a.b -height 2h -text {Button 1} -command {send cmd 1}",
27 "pack .fb.a.b -padx 1 -pady 1",
28 "button .fb.c -height 2h -text {Button 2} -command {send cmd 2}",
29 "button .fb.q -height 2h -text {Quit} -command {send cmd Q}",
30 "frame .fb",
31 "pack .fb.a .fb.c .fb.q -padx 5 -pady 5 -side left",
32 "pack .ft .fb",
33 "pack propagate . 0",
34 "update",
35 };
36 init(ctxt: ref Draw->context, argv: list of string) {
37 sys = load sys Sys->PATH;
38 draw = load Draw Draw->PATH;
39 tk = load Tk Tk->PATH;
40 wmlib = load wmlib Wm"lib->PATH;
41
42 wm~lib->init();
43
44 (win, menubut) := wm~lib->titlebar(ctxt.screen, "-x 5 -y 5", "wmAltx", 0);
45
46 cmd := chan of string;
47 tk->namechan(win, cmd, "cmd");
48 wm~lib->tkcmds(win, win_cfg);
49
50 for(;;) alt {
51 menu := <-menubut =>
52 if(menu[0] == 'e') {
53 tk->cmd(win, ".ft.t insert end '"+"close button pressed.\n");
54 tk->cmd(win, "update");
55 wm~lib->dia~log(win, "warning -fg red",
"close Button", "close Button pressed!", 0, "close"::nil);
56 exit;
57 }
58 wm~lib->tit1ect1 (win, menu);
59 s := <-cmd =>
60 case s[0] {
61 '1' =>
62 tk->cmd(win, ".ft.t insert end '"+"Button 1 pressed.\n");
63 tk->cmd(win, "update");
64 break;
65 '2' =>
66 tk->cmd(win, ".ft.t insert end '"+ "Button 2 pressed.\n");
67 tk->cmd(win, "update");
68 break;
69 'Q' =>
70 tk->ctnd(win, ".ft.t insert end '" + "Quit button pressed.\n");
71 tk->cmd(win, "update");
72 wm~lib->dia~log(win, "info", "Quit Button", "Quit button pressed!", 0, "Quit": :nil);
73 exit;
74 }
75 }
76 }

Выполнение оператора alt начинается со строки 50. Там происходит выбор между двумя каналами: menubut и cmd. Канал menubut ипользуется для уведомления приложения о событиях связанных с заголовком окна (titlebar), таких как перемещение и закрытие. Канал cmd уведомляет приложение о событиях от других виджетов, таких как кнопки расположенные внизу, Кнопка 1, Кнопка 2, и Выход.

Графика[]

Средства работы с графикой не встроены в язык Limbo, вместо этого они вынесены в специальные модули: Draw, Prefab и Limbo/Tk.

  • Модуль Draw (draw.m) обеспечивает базовую функциональность для работы с растровой графикой и окнами. Модули Prefab и Limbo/Tk пользуются его услугами для создания более высокоуровневых интерфейсов.
  • Модуль Prefab (prefab.m) предоставляет набор функций, предназначенных для использования приложениями интерактивного телевидения. Он включает поддержку инфракрасных устройств ввода.
  • Модули Limbo/Tk (tk.m и wmlib.m) предоставляют функции для создания интерактивных настольных приложений.

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

Модуль Draw[]

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

Концепция и Терминология[]

Мдуль Draw предоставляет низкоуровневые средства работы с графикой, поэтому его функции и данные находятся на более низком уровне, чем в большинтсве графических API. Как следствие, он имеет несколько концепций и терминов, с которыми лучше познакомиться сразу.

Графический контекст (Context)[]

Как мы уже говорили в этой главе, графический контекст (Context), который захватывает программа через аргумент функции init(), определен в модуле Draw. Аргумент Context содержит ссылку на графические ресурсы. Два важных элемента графического контекста - display и screen.

Display[]

Объект display типа Draw->Display представляет физический экран, соответствующий одиночному подключению к устройству draw (/dev/draw). Кроме изображения самого экрана, тип Display также хранит ссылки на закадровые изображения, шрифты и т.п. Содержимое этих изображений хранится в устройстве display (???), а не в клиенте, что влияет на то, как они выделяются и используются. Ресурсы для объекта типа Display должны быть выделены с помощью его же функций (методов). Если объект Display будет создан стандартными средствами языка Limbo, его поведение будет неправильным и может привести к ошибкам исполнения.

Screen[]

Объект screen типа Draw->Screen используется для управления набором окон в изображении, и обычно нет необходимости в его отображении. Screen'ы и следовательно и окна, могут создаваться рекурсивно в окнах для дочерних окон/подокон, или даже для закадровых изображений. Ресурсы для объекта типа Screen должны быть выделены с помощью его же функций (методов).

Пиксели[]

Изображения представляют собой прямоугольный регион в целочисленной плоскости с элементом изображения (пиксель) на каждой точке сетки. Значение пикселя - это целое число, длиной 1, 2, 4 или 8 бит на пиксель. Все пиксели одного изображения имеют одинаковый размер (или глубину цвета). Некторые операции позволяют объединить изображения с разной глубиной, например для выполнения маскирования (masking). Когда изображение будет показано, значение каждого пикселя определит цвет экрана. Для цветных дисплеев Inferno использует фиксированную палитру цветов для каждой глубины экрана, и программа должна сама позаботиться чтобы использовать необходимые ей цвета из палитры. Предусмотрены функции для конвертирования троек красный, зеленый, синий (RGB) в значения пикселей.

Point[]

Тип Point определяет координаты. Графическая плоскость определена на целочисленной решетке, каждая (x, y) координата которой идентифицирует верхний левый угол соответствующего пикселя. Начало плоскости, (0,0) находится в верхнем левом углу экрана; координаты x и y возрастают вправо и вниз.

Rect[]

Тип Rect определяет прямоугольный регион плоскости. Он содержит два поля типа Point (min и max) и указывает на регион, определенный пикселями с координатами большими или равными min и меньшими чем max, как x, так и y. Это полу-открытое свойство позволяет прямоугольникам, совместно использующим край, иметь одинаковые координаты на краю.

Image[]

Тип Image предоставляет базовые действия над группами пикселей. Через несколько простых действий тип Image предоставляет строительные блоки для Display, Screen и Font. Объекты типа Image должны быть выделены с помощью их собственных методов. Изображение занимает прямоугольник Image.r графической плоскости. Второй прямоугольник Image.clipr определяет "clipping region" изображения. Обычно "clipping rectangle" равен прямоугольнику базового изображения, но они могут различаться. Можно, например, сделать "clipping region" меньше и поместить в центр базового изображения чтобы обозначить защищенную рамку.

Глубина пикселей в Image записана как логарифм, записанный в Image. 1 depth (???). Пиксели, глубиной 1, 2, 4 или 8 бит соответствуют значениям 1 depth 0, 1, 2 или 3. Изображение может быть маркировано для репликации. // FIXME

Font[]

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

Pointer[]

Тип Pointer сообщает информацию о состоянии устройств-указателей, таких как мыши и трекболы.

Возвращаемые значения[]

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

Освобождение графических объектов[]

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

Пример[]

Далее приведен исходный код версии программы Greet, которая иллюстрирует использование модуля Draw:

drawgreet.b:
1 implement DrawGreet;
2
3 include "sys.m";
4 include "draw.m";
5
6 sys: sys;
7 draw: Draw;
8 Display, Font, Rect, Point, image, screen: import draw;
9 display: ref Display;
10 disp, img: ref image;
11 font: Font;
12
13 DrawGreet : module {
14 init: fn(ctxt: ref Draw->context, argv: list of string);
15 };
16
17 init(ctxt: ref Draw->context, argv: list of string) {
18 sys = load sys Sys->PATH;
19 draw = load Draw Draw->PATH;
20
21 display = Display.allocate(nil);
22 disp = display.image;
23 spawn refresh(display);
24 white := display.color(Draw->white); # Предопределенный цвет
25 yellow := display.color(Draw->Yellow); # Предопределенный цвет
26 black := display.color(Draw->Black) ; # Предопределенный цвет
27 ones := display.ones; # Пиксельная маска
28 font = Font.open(display, "/fonts/lucidasans/uni code.9x24.font");
29
30 if ((tl argv) != nil)
31 str := "Hello, " + (hd(tl argv)) + "!";
32 else
33 str = "Hello, world!";
34
35 strctr := (font.width(str) / 2);
36
37 img := display.open("/icons/logon.bit") ;
38 imgxctr := img.r.max.x / 2;
39
40 dispctr := disp.r.max.x / 2;
41
42 disp.draw(disp.r, white, ones, (0,0));
43 disp.draw(disp.r, img, ones, (-(dispctr-imgxctr),-10));
44 disp.text(((dispctr - strctr), img.r.max.y+20), black, (0,0), font, str);
45 sys->sleep(10000);
46 disp.draw(disp.r, white, ones, (0,0));
47 }
48
49 refresh(disp1ay: ref Display) {
50 display.startrefresh();
51 }

Седьмая строка объявляет хэндл к модулю Draw, а строка 8 импортирует структуры данных Draw в текущее пространство имен. В частности это типы Display, Image и Font. Тип Display олицетворяет физический экран, представленный устройством draw, смонтированным в /dev/draw. Строка 21 выделяет ресурсы для объекта display используя функцию Display.allocate().

Тип Image предоставляет базовые операции над группами пикселей и строительные блоки для более высокоуровневых объектов вроде изображний (графические файлы), окон и шрифтов. Строка 22 присваивает видимое содержимое (холст Draw) переменной disp. Строка 37 открывает файл изображения logon.bit, расположенный в каталоге /icons и присваивает его переменной img.

Тип Font определяет внешний вид символов, отображаемых с помощью функции Image.font(). Шрифты считываются из файлов, которые описывают их размер, стиль и начертание тех Unicode-символов, которые они содержат. Строка 27 открывает файл шрифта unicode.9x24.font, расположенный в каталоге /fonts/lucidasans, и присваивает его переменной font.

Строка 23 обновляет экран используя рекомендованную технику порождения отдельного потока для локальной функции refresh() (строки 49-51), которая вызывает функцию Display.startrefresh() модуля Draw.

Строка 42 заполняет область экрана (прямоугольник, определенный в disp.r) белым цветом. Строка 43 отрисовывает изображение img на экране. Строка 44 рисует текст str используя шрифт font. Строка 45 делает паузу перед тем как экран будет очищен в строке 46.

После компиляции этой программы, вы можете запустить ее из консоли Inferno (не из графического окна командного инерпретатора):

% drawgreet inferno

Figure 121

Модуль Prefab[]

Модуль Prefab (prefab.m) содержит компоненты для создания высокоуровневых графических объектов. Он расчитан на применение в устройствах, которые не похожи на обычные компьютеры с клавиатурами и мышами. Он специально предназначен для создания приложений интерактивного телевидения (ITV), управляемых с помощью инфракрасных пультов. Используя возможности модуля Draw, модуль Prefab может группировать единичные элементы, обращаясь к ним как к группам элементов и активизировать элементы по команде.

Концепция и Терминология[]

Модуль prefab, как графический "тулкит" предоставляет высокоуровневые графические возможности, сравнимые с модулем Draw.

Compound[]

Тип Compound определяет боксы, рисуемые на экране. Каждый из них появляется в новом окне Compound.image и содержит (возможно nil) заголовок и содержимое. Он занимает место на экране, определенное в Compound.r. Процесс размещения ресурсов для Compound создает окно, но не отрисовывает его.

После того, как Compound будет создан, необходимо вызвать функцию Compound.draw(), чтобы сделать его видимым. Compound имеет обрамление вокруг себя, определенное в Style.edgecolor и содержащее (сверху вниз):

  • заголовок (если есть)
  • горизонтальную линию (если есть заголовок)
  • содержимое

приложения должны выдеять ресурсы для Compound с помощью его же методов.

Элементы[]

Модуль Prefab определяет элементы как графические компоненты. Существует шесть типов элементов Prefab:

  • EText. Единичная текстовая строка. Текст этого элемента отрисовывается с использованием style.textfont и style.textcolor.
  • ETitle. Текстовая строка, обычно используется для заголовка compound. Текст этого элемента отрисовывается с использованием style.textfont и style.textcolor.
  • Eicon. Изображение.
  • ESeparator Изображение, предназначенное для заполнения свободного пространства в списке.
  • EHorizontal. Горизонтальный список элементов.
  • EVertical. Вертикальный список элементов.

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

Приложения должны выделять ресурсы для объектов типа Element только через его же методы.

Environ[]

Тип Environ указывает тип и стиль элемента. Screen типа ref Draw->Screen, определяет как будут отображены элементы. Стиль типа Style, определяет как отрисовываются элементы. Тип Style это набор информации о шрифте и цвете для приложения или набора пунктов в приложении. За исключением использования Layout, единственный способ управлять видом элементов Prefab, это пользоваться членами Style.

Цвет членов elemcolor, edgecolor, titlecolor, textcolor и highlightcolor обычно ссылаются на константу/значения цвета (единственную копию цвета пикселя). Все они имеют тип ref Draw->lmage, и все они могут быть любым изображением.

Стили выделяются обычными Limbo-выражениями. Не существует специальных функций. Все члены Style должны быть определены. Кроме того, хотя модификация членов Style после того, как как они были созданы и переданы в функцию Prefab не вызовет ошибок, результаты могут быть непредсказуемыми. Тип Layout определяет более общую форму текста и вида дисплея. Он обеспечивает более точное управление шрифтом и цветом которым показывается текст и включаемыми изображениями в виде элементов текста. Он позволяет установить тег для каждого компонента результирующего элемента или списка элементов.

Пример[]

Листинг 3-15 prefabgreet.b

1 implement PrefabGreet;
2 include "sys.m"
3 include "draw.m";
4 include "prefab.m";
5 sys: Sys;
6 draw: Draw;
7 Display, Font, Rect, Point, Image, Screen: import draw;
8 prefab: ref Prefab;
9 Style, Element, Compound, Environ: import prefab;
10 PrefabGreet : module {
11 init: fn(ctxt: ref Draw->context, argv: list of string);
12 };
13 init(ctxt: ref Draw->context, argv: list of string) {
14 sys = load sys Sys->PATH;
15 draw = load Draw Draw->PATH;
16 prefab = load Prefab Prefab->PATH;
17 display = Display.allocate(nil);
18 disp = display.image;
19 spawn refresh(display);
20 white := display.color(Draw->white); # Предопределенный цвет
21 yellow := display.color(Draw->Yellow); # Предопределенный цвет
22 black := display.color(Draw->Black) ; # Предопределенный цвет
23 grey := display.rgb(160,160,160);
24 ones := display.ones; # Пиксельная маска
25 screen := Screen.allocate(disp, white, 1);
26 textfont := Font.open(display, "/fonts/lucidasans/unicode.13.font");
27 titlefont := Font.open(display, "/fonts/lucidasans/italiclatin1.10.font");
28 winstyle := ref Style(
29       titlefont,
30       textfont,
31       grey,           # element color
32       black,          # edge color
33       yellow,         # title color
34       black,          # text color
35       white);         # hightlight color
36 win := ref Environ(screen, win_style);
37 if ((tl argv) != nil)
38    msg := "Hello, " + (hd(tl argv)) + "!";
39 else
40    msg = "Hello, world!";
41 icon := display.open("/icons/lucent.bit");
42 dy := (icon.r.dy()-textfont.height) / 2;
43 wintitle := Element.text(win, "Inferno Prefab Example",
44                         ((0,0),(0,0)), Prefab->ETitle);
45 ie := Element.icon(win, icon.r, icon, ones);
46 te := Element.text(win, msg, ((0,dy),(0,dy)),
47                               Prefab->EText);
48 se := Element.separator(win, ((0,0),(10,0)),
49                        display.zeros, display.zeros);
50 le := Element.elist(win, ie, Prefab->EHorizontal);
51 le.append(se);                  # add space
52 le.append(te);
53 le.append(se);
54 le.adjust(Prefab->Adjpack, Prefab->Adjleft);
55 c := Compound.box(win, (20,20), wintitle, le);
56 c.draw;
57 sys->sleep(10000);
58 }
59 refresh(display: ref Display) {
60 display.startrefresh();
61 }


Сравнив этот пример с примером Draw, вы увидите что код инициализации остался практически тем же. В добавок к типам Display, Image, and Font, в этом примере используется объект Screen. В Строке 25 создается новый объект Screen и сохраняется в screen. Это становится основой на которой рисуются такие элементы как окна.

Строки с 20 по 35 определяют цвет и шрифт информации которая будет отображена в окне.

строка 36 создает оконное окружение (??), базирующееся на ранее определенном screen и стиле.

Строка 43 задает заголовок окна. Строки 45 и 46 задают иконку и текст для отображения. Строка 55 определяет пространство которое будет использовано для размещения элементов.

Строка 50 устанавливает размещение элементов по горизонтальной линии. Строки с 51 по 53 добавляют элементы списка в соответствующем порядке. Строка 54 форматирует элементы.

Строка 55 задает прямоугольник или окно, которое будет отображено. Оно содержит заголовок и содержимое. Строка 60 фактически отрисовывает окно. После компиляции этой программы, вы должны запустить ее из консоли Inferno: inferno$ prefabgreet inferno

Figure 128

Модули Limbo/Tk[]

Модули Limbo/Tk содержат общие компоненты GUI, подобные таковым в тулките Tk (меню, кнопки и другие виджеты). Они используются для конструирования графического интерфейса пользователя без прямого вызова примитивов модуля Draw.

Концепция и Терминология[]

Те, кто знаком со связкой Tcl/Tk, найдут Limbo/Tk очень похожим. Однако, Limbo/Tk не поддерживает всех возможностей Tk 4.0. Для получения большей информации о различиях между Limbo/Tk и Tk 4.0 смотрите Inferno Reference Manual.

Важно отметить, тем не менее, что для приложений Limbo/Tk, для создания и управления компонентами Tk (виджетов), используется язык Limbo, а не Tcl.

Функция toplevel()[]

Функция toplevel() создает окно. Она возвращает объект типа Toplevel, который можно передавать другим функциям Limbo/Tk.

Функция cmd()[]

Функция cmd принимает команды Limbo/Tk как строки для создания и управления графическими объектами Tk. Управляющие строки содержат команды и их аргументы. Они похожи на упрощённую версию Tcl. Смотрите Inferno Reference Manual для получения более подробных сведений о командах Limbo/Tk.

Функция namechan()[]

Функция namechan модуля Limbo/Tk используется для установки соединения между виджетами Limbo/Tk и программами Limbo. Отправка команды выражается в пересылке строки по Limbo-каналу. Функция namechan используется для связывания канала строк Limbo (Limbo chan of string) с именем которое может использоваться внутри Limbo/Tk.

Wmlib[]

В стандартную поставку Inferno входит менеджер окон wm и модуль Wmlib (wmlib.m), который упрощает обработку команд Limbo/Tk и создание грфических приложений.

Смотри Inferno User s Guide для подробной информации о wm. Смотри Inferno Reference Manual для дополнительной информации о модуле Wmlib.

Пример[]

Слудющая версия программы Greet иллюстрирует использование модулей Limbo/Tk:

// FIXME Код устарел и не будет работать, требуется модификация.
1 implement TkGreet;
2
3 include "sys.m";
4 include "draw.m";
5 include "tk.m";
6 include "wmlib.m";
7
8 sys: sys;
9 tk: Tk;
10 wmlib: Wmlib;
11 msg: string;
12
13 TkGreet : module {
14 init: fn(ctxt: ref Draw->context, argv: list of string);
15 };
16
17 init(ctxt: ref Draw->Context, argv: list of string) {
18 sys = load sys Sys->PATH;
19 tk = load Tk Tk->PATH;
20 wmlib = load Wmlib Wmlib->PATH;
21 wmlib->init();
22
23 if ((tl argv) != nil)
24 msg = "Hello, " + (hd(t1 argv)) + "!";
25 else
26 msg = "Hello, world!";
27
28 win_cfg := array [] of {
29 "label .i -bitmap @/icons/logon.bit",
30 "label .t -text {" + msg + "\n" + "}",
31 "focus .t",
32 "button .b -text {OK} -width lOh -command {send cmd o}",
33 "pack .i .t .b -padx 5 -pady 5",
34 "pack propagate . 0",
35 "update",
36 };
37
38 (title, menubut) := wmlib->titlebar(ctxt. screen, "-x 50 -y 25", "Limbo/Tk Example", wmlib->Hide); 39
40 cmd := chan of string;
41 tk->namechan(tit1e, cmd, "cmd"); 42
43 wmlib->tkcmds(tit1e, win_cfg);
44
45 for(;;) alt {
46 click := <- menubut =>
47 if (click[0] == 'e')
48 return;
49 wmlib->titlectl (title, click);
50 if (click[0] == 't')
51 tk->cmd(title, "focus .t");

: // FIXME: Что это? =)
s := <- ctnd =>
case s[0] {
'0' => exit
}
}

Строка 21 инициализирует дисплей для подготовки к рисованию.

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

Строка 38 определяет текст и кнопки отображаемые в заголовке, а также начальные координаты (как командную строку).

Функция Wmlib. titlebar возвращает кортеж (тьюпл) содержащий ссылку на виджет верхнего уровня и канал используемый для уведомления программы о событиях от мыши на заголовке окна.

Строки 40 и 41 устанавливают канал, используемый для сообщений между виджетами и программой. Он позволяет программам Limbo принимать уведомления о событиях.

Строка 43 создает окно при помощи массива строк которые задают окно как команды.

Строки с 45 по 56 это бесконечный цикл который обрабатывает события от виджетов. Оператор alt выбирает между двумя каналами: menubut - события от заголовка окна, и cmd - команды от других виджетов (в нашем случае, кнопка OK).

После компиляции этой программы, вы можете запустить ее в интерпретаторе Inferno в Window Manager (wm):

$ tkgreet inferno

Figure 133


Далее

Inferno-Programmers_Guide_Chapter_04

Advertisement