6.2. Оформление рассредоточенного набора
Для того чтобы организовать работу с рассредоточенным набором, необходимы две основные конструкции: объявление элемента рассредоточенного набора и наборное гнездо, в котором собираются воедино такие элементы. Что касается гнезда рассредоточенного набора, то оно ничем не отличается от наборного гнезда, рассмотренного в предыдущей главе. Однако для оформления объявления элемента понадобится новая конструкция.
6.2.1. Объявление элемента. Объявление элемента рассредоточенного набора можно оформить следующим образом:
#INSTALL_IN [ LOCAL ] имя_набора [ SUBSET ]
{ имя_компонента : значение }
[ #APPLY
применение ]
#END_OF_INSTALL
|
В первой строке указывается имя_набора, в который включается объявляемый элемент. Служебное слово LOCAL говорит о том, что область действия набора текущий модуль трансляции; если LOCAL отсутствует, то область действия вся программа. Служебное слово SUBSET указывает на то, что рассредоточенный набор высекает подмножество существующего в программном фонде регулярного однородного набора; в этом случае в объявлении присутствует только один компонент с именем NAME, значение которого задает имя включаемого в подмножество элемента регулярного набора.
Фигурные скобки, окаймляющие вторую строку, означают, что записанная в них конструкция должна быть воспроизведена в одном или в нескольких экземплярах. Тем самым имеется возможность объявления составного элемента: каждый экземпляр конструкции вводит один из компонентов. Имя_компонента задается в левой части конструкции, а справа записывается значение объявляемого компонента.
Далее следует необязательная часть #APPLY. Там располагается то, что должно остаться в исходном тексте после обработки конструкции #INSTALL_IN препроцессором. Часть #APPLY представляет собой обычный текст, среди которого могут размещаться значения компонентов объявленного элемента рассредоточенного набора, задаваемые конструкцией вида #имя_набора.имя_компонента. Кроме того, в текст можно включать порядковый номер, полученный объявленным элементом в наборе; он задается в виде #имя_набора . NUMBER.
Рассмотрим примеры применения конструкции #INSTALL_IN.
Пусть объявляемые тексты диагностических сообщений (см. разд. 6.1.2) снабжаются кодом серьезности, управляющим блокированием тех или иных последующих действий. С появлением кода серьезности элемент рассредоточенного набора становится составным: «ТЕКСТ + КОД». Конструкция объявления элемента рассредоточенного набора ДИАГНОСТИКА с текстом Ошибка и кодом серьезности 1 могла бы иметь вид
#INSTALL_IN ДИАГНОСТИКА
ТЕКСТ : Ошибка
КОД : 1
#APPLY
#END_OF_INSTALL
|
Еще два примера разберем более подробно.
6.2.2. Список литературы. Пусть требуется подготовить и опубликовать на WWW в форме HTML-файла небольшую статью, в конце которой размещается список литературы. (Надо надеяться, большинство читателей этой книги знакомы с HTML.) Пусть ссылки надо представить в виде номера позиции в списке литературы, например «[9]». Как оформить в тексте библиографическую ссылку, скажем, на книгу Э.Дейкстры «Дисциплина программирования», чтобы разрешить противоречия, обсуждавшиеся в разд. 6.1.3?
Сделать это можно следующим образом. В квадратных скобках, там где должен появиться порядковый номер ссылки в списке литературы, записывается объявление элемента рассредоточенного набора БИБЛИОГРАФИЯ:
#INSTALL_IN LOCAL БИБЛИОГРАФИЯ
ТЕКСТ : Дейкстра Э. Дисциплина ...
#APPLY
<a href="#b#БИБЛИОГРАФИЯ.NUMBER">
#БИБЛИОГРАФИЯ . NUMBER</a>
#END_OF_INSTALL
|
Не надо придавать значения некоторому многословию получившейся записи: в реальных текстах тут будет применен компактный макрос.
На месте объявления элемента после обработки его препроцессором появится текст
<a href="#b5">5</a>
где 5 порядковый номер ссылки в наборе БИБЛИОГРАФИЯ (он же порядковый номер в списке литературы), а #b5 метка этой ссылки в списке. Броузер представит всю конструкцию на экране в виде
. . . [ 5 ] . . .
т. е. как гиперссылку на библиографическую ссылку. (Квадратные скобки взяты из обрамляющего текста.)
В конце статьи все объявленные в ней библиографические ссылки собираются в список литературы посредством наборного гнезда, знакомого нам по гл. 5:
<ol>
#HORIZON БИБЛИОГРАФИЯ
<li><a name="b#БИБЛИОГРАФИЯ.NUMBER">
#БИБЛИОГРАФИЯ.ТЕКСТ</a>
#DELIMITER
<br>
#END_OF_HORIZON
</ol>
|
Каждая ссылка снабжается HTML-меткой, представляющей собой префикс «b», за которым следует порядковый номер ссылки в гнезде. Именно на эту метку указывает гиперссылка, формируемая на месте объявления элемента рассредоточенного набора БИБЛИОГРАФИЯ.
После обработки препроцессором и броузером на месте гнезда на экране появится искомый список:
4. . . .
5. Дейкстра Э. Дисциплина . . .
|
Если требуется, например, выделять в списке фамилию автора курсивом, то указания о курсиве можно добавить в объявление поля ТЕКСТ. Но лучше сделать элемент набора составным, выделив самостоятельный компонент АВТОР тогда объявление курсива перекочует в наборное гнездо, что несколько экономичнее и точнее.
Если текст статьи вырастет и распадется на несколько HTML-файлов, то надо будет расширить область действия набора БИБЛИОГРАФИЯ, т. е. удалить из заголовка объявления служебное слово LOCAL. Кроме того, остающаяся в тексте гиперссылка теперь должна будет указывать на соседний файл с гнездом-списком литературы.
Нетрудно видеть, что в приведенном решении удалось преодолеть противоречия традиционной техники оформления списка литературы, разбиравшиеся в разд. 6.1.3. В частности, библиографическая ссылка автоматически исчезнет из списка, если из текста статьи будет удален содержащий ссылку фрагмент.
6.2.3. Архив веществ. Пусть в программный фонд включен (в форме регулярного однородного набора) архив, содержащий значения разнообразных атрибутов многочисленных химических веществ (см. разд. 6.1.6). И пусть по модулям расчетной программы разбросаны обращения к этому архиву, в которых указываются вещество и его атрибут. Требуется построить таблицу атрибутов веществ, состав которой определяется запросами программы: в таблицу включаются только запрошенные вещества и только запрошенные атрибуты этих веществ.
Чтобы не загромождать изложение излишними подробностями, несколько упростим постановку задачи. Будем считать, что все необходимые программе атрибуты непосредственно представимы в форме полей структуры, т. е. пока исключим из рассмотрения атрибуты, описывающие то или иное поведение вещества в форме фрагментов алгоритма.
Нам предстоит спроектировать два объекта: схему модуля, содержащего обращение к архиву веществ, и модуль, представляющий собой собственно таблицу запрошенных атрибутов веществ. В качестве языка реализации, как и ранее, выберем Си.
Решение задачи строится следующим образом. Образуются два рассредоточенных набора: ВЕЩЕСТВО и АТРИБУТ. Каждое обращение из расчетной программы к архиву веществ включает в себя объявление запрошенного элемента-вещества первого набора и элемента-атрибута второго. Тем самым формируется состав таблицы атрибутов веществ matter, реализуемой с помощью наборного гнезда ВЕЩЕСТВО, в которое вложено наборное гнездо АТРИБУТ. Таблица matter представляет собой массив структур, где структуры соответствуют веществам, а поля структур атрибутам. Обращение к архиву веществ, помимо объявления элементов рассредоточенных наборов, разумеется, включает в себя и выражение, обслуживающее данный запрос в выполняемой программе, т. е. обеспечивающее доступ к нужному полю нужного элемента массива matter.
Реализацию начнем с того, что создадим вставляемый во все автономно транслируемые модули интерфейсный модуль matter.h. В этом модуле разместим сначала структуру unit элемента массива matter. Состав полей unit определяется составом рассредоточенного набора АТРИБУТ:
struct unit {
#HORIZON АТРИБУТ
#АТРИБУТ.ТИП #АТРИБУТ.NAME;
#END_OF_HORIZON
};
|
Поскольку в рассредоточенный набор АТРИБУТ попадут те и только те атрибуты, которые требуются программе, постольку и в структуру unit, строящуюся с помощью этого набора, войдут, соответственно, только востребованные поля. Здесь предполагается, что в программный фонд ранее (при формировании архива веществ) была записана вся необходимая нам информация о допустимых атрибутах, включающая, в частности, тип (ТИП) каждого атрибута. Конкатенируя тип с именем (NAME) атрибута, мы формируем искомое описание поля структуры.
Далее опишем таблицу matter, которая, как уже говорилось, представляет собой массив структур unit:
extern struct unit matter[];
Чтобы на выходе препроцессора обращения к элементам массива matter выглядели более мнемонично, присвоим нашим веществам соответствующие номера элементов этого массива. Сделать это можно с помощью перечисления, также поместив его в модуль matter.h:
enum {
#HORIZON ВЕЩЕСТВО
#ВЕЩЕСТВО.NAME,
#END_OF_HORIZON
};
|
Теперь формирование модуля matter.h завершено. Перейдем к автономно транслируемому модулю, запрашивающему информацию из архива веществ.
Прежде всего немного усилим введенную в разд. 6.2.1 конструкцию объявления элемента рассредоточенного набора, разрешив объявлять сразу несколько элементов нескольких наборов:
{ #INSTALL_IN [ LOCAL ] имя_набора [ SUBSET ]
{ имя_компонента : значение } }
[ #APPLY
применение ]
#END_OF_INSTALL
|
(По сравнению с разд. 6.2.1 добавились только внешние фигурные скобки, разрешающие многократное повторение первой, описательной части объявления.)
Примем для определенности, что нужно узнать плотность (DENSITY) воды (H2O). Тогда запрашивающий характеристику вещества модуль будет иметь вид:
#include "matter.h"
#INSTALL_IN ВЕЩЕСТВО SUBSET
NAME : H2O
#INSTALL_IN АТРИБУТ SUBSET
NAME : DENSITY
#APPLY
matter[#ВЕЩЕСТВО.NAME].#АТРИБУТ.NAME
#END_OF_INSTALL
|
Первая строка обеспечивает подключение только что заготовленного интерфейсного модуля matter.h. Далее записана полная форма обращения к архиву веществ.
Прежде чем комментировать отдельные компоненты этого обращения, поспешим, как обычно, развеять тревоги читателя, которого, вероятно, насторожила бросающаяся в глаза громоздкость приведенной конструкции. Разумеется, в тексте реальной программы обращение скорее всего будет выглядеть существенно компактнее тут вполне уместно применение макроса (т. е. процедуры периода препроцессирования), сжимающее обращение до
. . . #MATTER(H2O, DENSITY) . . .
Однако сейчас нас в первую очередь интересуют именно нюансы данного обращения, анализировать которые существенно легче, имея перед глазами полную, развернутую форму.
Из каких компонентов складывается обращение к архиву веществ? Легко видеть, что согласно семантике конструкции #INSTALL_IN на выходе из конфигурационного препроцессора обращение превратится в
. . . matter[H2O].DENSITY . . .
Данное выражение извлекает из таблицы атрибутов веществ требуемое значение. H2O номер вещества, который был определен при перечислении всех веществ в модуле matter.h; здесь H2O используется как номер элемента массива matter (еще раз напомним, что во всех наборных гнездах вещества располагаются в одной и той же последовательности). Из этого элемента извлекается требуемый атрибут поле DENSITY структуры unit.
Две записанные в обращении описательные части #INSTALL_IN объявляют элементы рассредоточенных наборов: H2O набора ВЕЩЕСТВО и DENSITY набора АТРИБУТ. Таким образом, предложенная форма обращения к архиву веществ успешно справляется с обеими своими задачами: пополняет рассредоточенные наборы и извлекает значение требуемого атрибута вещества.
Придирчивый читатель, вероятно, обратил внимание на оставшуюся небольшую технологическую шероховатость: с появлением в составе модуля трансляции обращения к архиву веществ в заголовке модуля должно появиться предложение #include "matter.h", подключающее описание интерфейсов. При исключении из модуля последнего обращения к архиву предложение #include желательно также исключить. Если эти манипуляции с исходным текстом выполняются вручную, то закономерно встает вопрос: все ли сделано для обеспечения расширяемости нашей программы?
И вновь на помощь приходит рассредоточенный набор. На этот раз сформируем набор ЗАГОЛОВОК, локальный в модуле трансляции. В нем будут собираться имена всех требующихся модулю интерфейсов (h-файлов). Для этого в обращении к архиву веществ дописывается еще одна, третья описательная часть:
#INSTALL_IN LOCAL ЗАГОЛОВОК
NAME : matter
|
А в начале модуля трансляции вместо явного упоминания h-файлов записывается наборное гнездо:
#HORIZON ЗАГОЛОВОК
#include "#ЗАГОЛОВОК.NAME.h"
#END_OF_HORIZON
|
Теперь с расширяемостью все в порядке.
Последний модуль, который нам предстоит рассмотреть, таблица веществ. Размер его текста сравнительно невелик, поскольку основная масса его компонентов не присутствует в тексте явно, а извлекается частично из объявлений элементов, содержащихся в обращениях к архиву веществ, и частично из программного фонда.
#include "matter.h"
struct unit matter[] = {
#HORIZON ВЕЩЕСТВО
{
#HORIZON АТРИБУТ
#ВЕЩЕСТВО.#АТРИБУТ.NAME,
#END_OF_HORIZON
},
#END_OF_HORIZON
};
|
Интерфейсный модуль matter.h снабжает нас описанием структуры unit. Далее описывается составленный из таких структур массив matter, т. е. таблица атрибутов веществ. Для нас наиболее интересна замыкающая часть этого описания, обеспечивающая инициализацию таблицы.
Инициализацию осуществляют два вложенных одно в другое наборных гнезда, или, иначе говоря, два вложенных один в другой цикла периода сборки программы. Внешнее гнездо обеспечивает перебор всех веществ, вошедших в рассредоточенный набор ВЕЩЕСТВО, а вложенное гнездо для каждого из веществ задает значения его атрибутов, т. е. инициализирует соответствующие поля структуры unit. Предполагается, что значения атрибутов вещества хранятся в программном фонде в форме компонентов элемента регулярного однородного набора, причем имена компонентов совпадают с именами атрибутов.
6.2.4. Неизбежность рассредоточенного набора. Попытаемся определить роль, отведенную рассредоточенному набору в построении расширяемых программ.
Напомним, что одной из наиболее привлекательных черт описанных в гл. 4 и 5 схем реализации многовариантности и регулярного однородного набора была безболезненность развития программы. Утверждалось, что при наличии необходимой системной поддержки подключение к программному фонду новых сменных или однородных модулей не требует каких-либо ручных переделок текстов их соседей. Однако, как теперь стало видно, это утверждение справедливо только с определенными оговорками.
В самом деле, если обратиться к рассмотренным примерам, придется согласиться, что любой подключаемый к программному фонду модуль вправе так или иначе объявить элементы, воздействующие на другие области программы. Тогда безболезненное подключение модуля возможно только при условии безболезненного обслуживания его объявлений. К сожалению, существующие операционные среды обычно не в состоянии выполнить данное условие. Здесь-то и потребуется рассредоточенный набор с его помощью удается обеспечить недостающую безболезненность. Это означает, что механизм рассредоточенного набора является необходимым компонентом инструментального базиса расширяемого программирования.
Рассмотренные в данном разделе примеры достаточно полно иллюстрируют технику работы с рассредоточенным набором. Были опущены лишь малозначительные детали, не добавляющие ничего принципиально нового. Большинство задач, приведенных в разд. 6.1, успешно решаются по аналогичной схеме.
Реализация подобного рассредоточенного набора не вызывает сложностей. Достаточно оснастить алгоритм сборки выполняемой программы средствами извлечения (из составляющих программу модулей) и накопления объявляемых элементов, после чего в работу включается уже известная нам техника заполнения наборного гнезда. Однако иногда, в частности в некоторых из упомянутых в разд. 6.1 задач, требуется подключение более трудоемких механизмов реализации. Об этом пойдет речь в следующем разделе.
|