Недоря А.Е., Никитин А.Г. "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 В данном сборнике