Mithril - переносимая Оберон-2 система

                    Недоря А.Е., Никитин А.Г.

                                   "Mithril! All folk desired it."
                                                   J.R.R. Tolkien
                                            The Lord of the Rings

Введение

Существенной  частью  реализации  любого  языка  программирования
является  языковое  окружение (или система программирования). Для
объектно-ориентированного  (ОО)  языка такое окружение может быть
описано   в  виде  множества  абстрактных  классов  и  конкретных
под-классов.  Система  Mithril  -  это  попытка построить удобное
окружение  для  языка  Оберон-2  [1]. Система является развитием
системы  Oberon [2]. Как и исходная система Mithril поддерживает
многозадачность в рамках одного процесса.

Мы  предполагаем,  что  система  Mithril будет использоваться как
база  для  разработки переносимого прикладного ПО. Для того чтобы
упростить  разработку  прикладного ПО необходимо решить несколько
важных задач:
    1) Необходимо создать комфортную обстановку для программиста
(языки, отладка, поддержка проекта);
    2) Система должна включать набор средств для создания
пользовательского интерфейса (окна, мышь, конструктор интерфейсов);
    3) Система должна создавать абсолютно одинаковую обстановку
на разных платформах (т.е. на разных машинах и под разными ОС).

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

Возможность  (почти)  неограниченной  расширяемости системы - это
основное  свойство отличающее системы Oberon и Mithril от обычных
систем  программирования.  Расширяемая  системы  состоит из ядра,
определяющего  набор  базовых  типов (базы для расширения). Далее
определяется  набор  расширений  этих  типов.  Вообще  говоря, на
каждом   таком   ядре   может  быть  построены  различные  наборы
расширению. С точки зрения программиста расширенные типы ничем не
отличаются от базовых, то есть в расширяемых системах нет барьера
отделяющего системную часть от пользовательской.

Построение надежных расширяемых систем невозможно без подходящей
языковой  поддержки.  В  первую  очередь язык должен поддерживать
понятие  расширения  типа.  Система  Mithril реализована на языке
Оберон-2  (система  Oberon  на  языке Оберон). Этот язык обладает
рядом   преимуществ,   позволяющих   улучшить   эффективность   и
расширяемость системы (по сравнению с [2]):
    - методы (type-bound procedure) позволяют аккуратно разделить
операции  над  каждым объектом и переопределять одни операции, не
изменяя   другие;   повышают   эффективность,  ликвидируя  лишние
динамические  проверки  типов; повышают надежность, ввиду неявной
инициализации методов;
    -  экспорт  только  для  чтения  (read only export) позволяет
повысить  эффективность  доступа к атрибутам объектов (без ущерба
для надежности);
    -  динамические  массивы  позволяют  повысить универсальность
операции и избавиться от ограничений в системе.

В   качестве   вспомогательного   языка  при  разработке  системы
использовалась "оберонизированная" версия языка Модула-2.

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

1. Пользовательский интерфейс

С точки зрения пользователя системы она практически не отличается
от  системы  Oberon.  При  запуске  системы пользователь видит на
экране  окно стандартного вывода и окно (system tool), содержащее
набор основных команд (в том числе команду создания нового окна).
Каждая  команда  M.P  состоит из имени модуля M и имени процедуры
без параметров P. Пользователь запускает команду, нажимая среднюю
кнопку  мыши  на  тексте  команды.  При активации команды система
вызывает  процедуру  P  из модуля M (загружая модуль M, если этот
модуль не был загружен в систему).

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

В  каждый  момент  на экране расположено множество рамок (Frame),
каждая  рамка  содержит  органы  управления  (для перемещения или
изменения  размеров)  и  несколько  под-окон.  Основное  под-окно
называется  рабочим.  Так  как  понятие  текста  и текстовых окон
является  очень  важным  (все  есть  текст), то основные операции
текстового  редактирования выполняются с помощью кнопок мыши (см.
[2]).

2. Структура системы

Система состоит из трех частей: поддержки времени исполнения (run
time  support, RTS), ядра системы и оболочки. Кроме этого система
включает в себя набор драйверов для взаимодействия с операционной
системой и/или аппаратурой.

Поддержка  времени  исполнения  (RTS)  -  это  неотъемлимая часть
реализации  языка Оберон-2, содержашая операции выделения памяти,
сборки мусора и финализации объектов. В нашей системе RTS написан
на  языке  Модула-2.  При  переносе  системы  RTS  (как  правило)
переписывается  для  повышения  эффективности  сборки мусора. Для
сборки мусора используется mark-and-sweep алгоритм [3].

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

Ядро  системы  определяет  набор базовых понятий (типов): Object,
Persistent  Object,  Module,  Rider,  File,  Font, Window, Text и
реализует  несколько полезных библиотек. При запуске системы ядро
выполняет  действия,  необходимые для конфигурации системы, в том
числе действия по установке драйверов.

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

3. Пример расширения системы

В   этой   части   мы   попытаемся  продемонстировать  технологию
программирования  в  системе, описывая снизу-вверх построение так
называемых   "элементов-команд".   Элементом  мы  будем  называть
абстрактную  литеру  в  тексте,  элемент-команда - это расширения
элемента  до  активного  элемента, исполняющего ассоциированную с
ним команду при нажатии кнопки мыши на нем. Естественно, мы будем
опускать массу деталей, более того изрядная часть системы (файлы,
модули,  загрузка)  вообще  не  будут  рассматриваться  в  данной
статье.   Все   примеры   мы   будем   приводить   на   некотором
обероно-подобном    языке,   так   например,   описания   методов
(type-bound    procedures)    будут    вставлены    в    описания
соответствующих  типов. Мы будем полагать, что читатели знакомы с
работами [2,4], иначе статью пришлось бы разбивать на несколько
томов.

Для начала определим понятие Текста и рассмотрим, как он устроен,
далее перейдем к окнам, а затем к текстовым окнам.

Текст  в  системе  есть  последовательность  символов.  С  каждым
символом ассоциирован набор атрибутов: шрифт, цвет и вертикальное
смещение, относительно базовой линии. Атрибуты символа не зависят
от  атрибутов  соседей.  Все операции над текстом выполняются над
последовательностью   литер.   Каждый   текст  может  быть  виден
различным  образом разными "зрителями". Любая операция над тексте
вызывает  оповещение  всех  зрителей  о  проишедшем  изменении  в
диапазоне текста.

Так  же, как и в [2] модуль Texts определяет базовые типы Text и
Buffer,  а  также  типы  Reader  и  Writer,  реализующие операции
доступа  к  текстам и буферам соответственно. Кроме того, введено
понятие  абстрактной  литеры  -  элемента  (Elem) [4]. Различные
расширения  элемента позволяют включать в текст картинки, чертежи
и   другие   объекты.   Основное   отличие   данного   модуля  от
соответствующего  модуля  системы  Oberon, связано с переходом на
Оберон-2   и   использованием   методов   вместо   процедур,  что
существенно увеличивает возможности расширения.

Из описаний модуля Texts:

TYPE
  Elem = POINTER TO ElemDesc;
  ElemDesc = RECORD (Storage.ObjectDesc)
    temp*: BOOLEAN;     (* suppress storage of element *)
    ....
    PROCEDURE (e: Elem) copy(VAR x: Elem);
    PROCEDURE (e: Elem) handle(VAR M: Objects.Message);
  END;

PROCEDURE broadcast(T: Text; pos,end: LONGINT; VAR M: Objects.Message);
(* send a message to all elements within stretch [beg..end[ *)

В  модуле  Texts  определены  только  те  операции над элементом,
которые  в нем используются, а именно копирование элемента (copy)
и  посылка  сообщения  элементу  (handle). Метод handle позволяет
неограниченным   образом   расширять   множество   операций   над
элементами.  Операции  отображения  объекта определяются в модуле
Elements (см. ниже).

Элемент   есть   расширение   постоянного  (persistent)  объекта,
определенного в модуле Storage.

Из описаний модуля Storage:

TYPE
  Object = POINTER TO ObjectDesc;
  ObjectDesc = RECORD (Objects.ObjectDesc)

    PROCEDURE (o: Object) constructor(): Str.String;
    (* возвращает строку, содержащую команду создания объекта;
       Str.String = POINTER TO ARRAY OF CHAR.
    *)

    PROCEDURE (o: Object) externalize(r: Riders.Rider);
    (* записывает состояние объекта *)

    PROCEDURE (o: Object) internalize(r: Riders.Rider);
    (* восстанавливает состояние объекта *)
  END;

Тип Riders.Rider определяет абстрактные операции ввода/вывода.

Для    реализации   постоянных   объектах   используется   схема,
объединяющая  методы,  описанные  в  [4,5].  При записи объекта
сначала записывается строка-конструктор, а затем вызывается метод
externalize  для  записи  состояния  объекта.  При чтении объекта
сначала  читается  строка-конструктор,  затем  она выполняется и,
если  объект  удалось  создать,  то  восстанавливается  состояние
объекта вызовом метода internalize.

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

В  свою  очередь  Storage.Object  есть расширение Objects.Object,
который определяет корневой тип в системе типов.

Теперь  перейдем  к  окнам.  Реализация  окон  в системе подробно
рассматривается  в  [6],  в  данной  статье мы кратко перечислим
основные характеристики оконной под-системы.

Любое новое окно создается на некотором окне-родителе. Изначально
в  модуле Windows определено единственное окно - meta-desktop, на
котором  создаются  окна  (desktop),  соответствующие  физическим
экранам.  Система  поддерживает  полиэкранность,  т.е.  она может
одновременно  работать  на  нескольких  физических  экранах.  Для
каждого  физического экрана должен быть определен драйвер экрана.
Модуль  Screens  определяет  тип  Screen  -  абстрактный  драйвер
экрана.   Конкретный  драйвер  экрана  расширяет  Screens.Screen.
Драйвер   экрана  определяет  характеристики  экрана:  размеры  в
пикселах,   число   слоев,  палитру  и  т.д.  и  реализует  набор
графических  примитивов.  Система  располагает все окна-экраны на
одной  прямой  (ордината  у  левого  угла любого экрана равна 0).
Размеры  экранов  могут быть различны. Очередной экран может быть
добавлен слева или справа от уже установленных экранов.

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

Модуль  Windows  определяет  понятие  абстрактного  окна, которое
может   быть   расширено   для   отображения  объектов  различной
структуры.  Текстовое  окно  расширяет абстрактное окно, добавляя
операции   отображения  и  редактирования  текста.  Все  операции
редактировании  содержимого  окна  реализованы  как  в  [2]. Эти
операции  включают  копирование/удаление  части текста, изменение
атрибутов литер, установку курсора, вертикальное и горизонтальное
позиционирование   в   тексте.  Остальные  операции  над  текстом
реализованы в виде набора команд.

Реализованные  тектовые  окна  занимают  промежуточное  положение
между  текстовыми  окнами,  описанными  в  [2]  и  [4]. С одной
стороны  текстовые окна могут выполнять операции над абстрактными
литерами   (элементами),   с   другой  стороны  мы  считаем,  что
реализация операций форматирования (см. [4]) должна быть сделана
над  уровнем текстовых окон.

Для  отображения элементов в текстовых окнах необходимо расширить
тип  Texts.Elem операциями отображения элемента. Такое расширение
описано в модуле Elements.

Из описаний модуля Elements:

TYPE
  Elem = POINTER TO ElemDesc;
  ElemDesc = RECORD (Texts.ElemDesc)
    w*,h*: INTEGER;   (* линейные размеры элемента *)

    PROCEDURE (e: Elem) prepare(v: Windows.Window;
                      font: Fonts.Font; col: SET; voff: INTEGER);
    (* подготовка элемента к изображению;
       элемент может изменить свои размер.
    *)

    PROCEDURE (e: Elem) draw(v: Windows.Window; x,y: INTEGER;
                      font: Fonts.Font; col: SET; voff: INTEGER);
    (* отображение элемента *)

    PROCEDURE (e: Elem) handle_mouse(v: Windows.Window;
                                   x,y: INTEGER; keys: SET);
    (* управление курсором для элемента *)
  END;

И,  наконец,  расмотрим  реализацию  элементов-команд. Для начала
определим    расширим    абстрактный    элемент   (Elements.Elem)
изображением  некоторой  картинки  (иконки).  Будем полагать, что
изображения картинок хранятся в некотором фонте.

TYPE
  IconElem = POINTER TO IconElemDesc;
  IconElemDesc = RECORD (Elements.Elem)
    icons: Fonts.Font;  (* фонт с иконками *)
    no   : SHORTINT;    (* номер иконки в фонте *)
  END;

Для  реализации  таких  элементов  нам  надо  определить операции
рисования  и копирования, а также записи/восстановления элемента.
Приведем схемы реализации некоторых методов.

PROCEDURE (e: Icon) draw(v: Windows.Window; x,y: INTEGER;
                         f: Fonts.Font; color: SET;
BEGIN
  ...
  Windows.writech(v,..,f,x,y,e.no);   (* рисуем иконку *)
  ...
END draw;

PROCEDURE (e: Icon) copy(VAR x: Texts.Elem);
  VAR i: Icon;
BEGIN
  IF x=NIL THEN NEW(i); x:=i END;
  e.copy^(x);      (* вызов метода из супер класса *)
  WITH x: Icon DO  (* проверили динамический тип x *)
    x.icons:=e.icons;
    x.no:=e.no;
  END;
END copy;

Расширим тип Icon, добавив команду и возможность ее запуска.

TYPE
  CmdElem = POINTER TO CmdElemDesc;
  CmdElemDesc = RECORD (IconElemDesc)
    icon: BOOLEAN;      (* состояние иконки *)
    cmd : Str.String;   (* команда *)
  END;

Мы   должны   переопределить   операции  рисования,  копирования,
записи/восстановления и реакции на кнопку мыши.

PROCEDURE (c: CmdIcon) handle_mouse(v: wnd.Window; x,y: INTEGER;
                                    keys: SET);
BEGIN
  IF middle_button IN keys THEN
    (* суммируем кнопки мыши, пока все они не будут отжаты *)
    IF keys={middle_button} THEN
      (* была нажата только средняя кнопка *)
      Kernel.call(c.cmd^,....);    (* вызов команды *)
    ELSIF right_button IN keys THEN
      (* была нажата средняя и правая кнопка *)
      flip(c);  (* изменить состояние элемента *)
    ....
    END;
  END;
END handle_mouse;

Процедура  flip переключает состояние элемента. В одном состоянии
элемент   рисуется  как  иконка,  в  другом  как  текст  команды,
заключенный в рамку.

PROCEDURE flip(c: CmdIcon);
BEGIN
  c.icon:=NOT c.icon;
  Texts.notify_elem(c);
END flip;

Процедура Texts.notify_elem оповещает всех "зрителей" (как правило,
это текстовые окна) о изменении состояния элемента.

PROCEDURE (c: CmdIcon) draw(v: Windows.Window; x,y: INTEGER;
                         f: Fonts.Font; color: SET;
BEGIN
  IF c.icon THEN
    c.draw^(v,x,y,f,color);  (* рисуем иконку *)
  ELSE
    .....
    Windows.frame( .... );       (* рисуем рамку *)
    Windows.print(....,c.cmd^);  (* рисуем текст команды  *)
  END;
END draw;

Нам остается только реализовать команду, которая будет вставлять
элемент-команду в произвольный текст.

PROCEDURE InsCmd*;
  VAR v: TextWindows.Window;  c: CmdIcon;
      w: Texts.Writer;        f: Fonts.Font;    no: SHORTINT;
    cmd: Str.String;
BEGIN
  ....
  v:=focus_window();  (* окно, в котором стоит текстовый курсор *)
  get_parameters(f,no,cmd);
  c:=command(f,no,cmd); (* создали элемент *)
  w.element(c);         (* добавили элемент к буферу w.buf *)
  v.text.insert(v.carloc.pos,w.buf);
  (* вставили элемент в позицию курсора *)
END InsCmd;

Теперь достаточно набрать команду IconElems.InsCmd, для того чтобы
вставить элемент-команду в любой текст.

При  реализации  примера  мы получили достаточно длинную иерархию
типов:

Objects.Object       корневой тип в системе типов
Storage.Object       постоянный объект
Texts.Elem           постоянный элемент
Elements.Elem        отображаемый постоянный элемент
IconElem             элемент-иконка
CmdElem              элемент-иконка с командой

Каждый уровень в этой иерархии осмысленен, т.е., во-первых, имеет
четко  определенную  семантику  и, во-вторых, может быть расширен
различными способами.

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

4. Заключение

Разработка  ОО  систем  является,  пожалуй,  самым  модным  видом
деятельности  в  мире в течении последних нескольких лет. Система
Mithril отличается от большинства аналогичных систем в нескольких
очень важных аспектах:
    1)  минимальность  и ортогональность системы (базовый уровень
системы реализует только необходимы набор понятий);
    2)  строгая  иерархия  базовых  типов  (большинство  подобных
систем  проектируется  с использованием multiple inheritance, что
приводит  к  слабо  структурированной  системе типов, и ослабляет
возможности расширения);
    3)  независимость системы от аппаратуры и ОС.

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

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

При  разработке  такой  системы  очень  важным  фактором являются
свойства  языка  программирования: расширение типов, динамическая
типизация,  сборка  мусора.  Отсутствии  в  таком  языке, как C++
динамической типизации и сборки мусора, делает проблематичным его
использование  для  разработки  мощных  OO  систем. При акуратном
проектировании   системы  не  возникает  потребности  в  multiple
inheritance.

Переход   с   языка   Оберон   на   Оберон-2  позволяет  повысить
расширяемость системы и, одновременно, эффективность ее.

Для   повышения   переносимости  системы  очень  важным  является
введение  понятие  драйвера. Все машинно/системно зависимые части
выносятся в драйвер, после чего систему можно свободно носить.

Мощная  оконная  под-система  снимает все ограничения на работу с
окнами  и  позволяет  произвольным  образом формировать интерфейс
системы.

И, наконец, мы хотим сказать спасибо всем, кто нам помогал в этой
работе, и ОГРОМНОЕ спасибо всем тем, кто нам не мешал.

Литература

1. H.Mossenboeck, N.Wirth
   The Programming Language Oberon-2
   Structured Programming (1991) 12: 179-195

2. J.Gutknecht
   The Oberon Guide: System Release 1.2
   Report 138, ETH Zurich, 1990.

3. B.Heeb, C.Pfister
   An Integrated Heap Allocator/Garbage Collector
   In report 157, ETH Zurich, 1991.

4. C.Szyperski,
   Write: An Extensible Text Editor for the Oberon System
   Report 151, ETH Zurich, 1991.

5. J.Templ,
   A Symmetric Solution to the Load/Store problem
   In report 157, ETH Zurich, 1991.

6. Никитин А.Г., Недоря А.Е.
    Оконная поддержка в системе Mithril
    В данном сборнике