Добавил:
ivanov666
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз:
Предмет:
Файл:Стили и методы программирования. Учебное пособие для СПО
.pdf
symbol <= 'z';}, {printf ("%c", symbol);
cnt = 1;}, 4},
{{return symbol != '\n';},
/* Так как нужно печатать только слова,
действия не заполняются */,
1},
{{return true},
/* Переход не требует чтения,
symbol == '\n' не нужно читать */, 0},
/*St2*/{{return 'a'<=symbol &&
symbol <= 'z';}, {printf ("%c", symbol);
cnt++;}, 4},
{{return symbol!='\n';},
{printf ("-%i\n",cnt);},
1},
{{return true },
{printf ("- %i\n",cnt);}, 0}
};
Листинг 10.3.2.
Но для С/С++/C#, а также для других языков, не ориентированных на
работу с таблицами, говорить о естественности представления не
приходится. По-видимому, наиболее разумно строить автоматический
преобразователь (типа препроцессора, но не путать этот термин с С!),
который составляет текст программы по таблице. В этом случае
снимается много проблем:
для программиста сохраняется наглядность;
при исполнении достигается алгоритмичность;
легко разносить фрагменты таблицы по разным участкам
программы;
можно жертвовать наглядностью результирующего текста,
поскольку с ним никто, кроме транслятора, не работает (пример:
вставка новых строк в таблицу - проблема для человека, но не для
препроцессора);
по той же причине можно жертвовать даже структурностью
результирующего текста (в рассматриваемом примере, в
частности, можно отказаться от представления условий и
Стили и методы программированияНепейвода Н.Н.
181

действий процедурами и функциями).
Это решение часто является приемлемым, однако и у него есть свои
недостатки. Прежде всего, поиск ошибок, допущенных в текстах
условий и действий, затруднен, возникает проблема двойной
интерпретации (приходится понимать как исходное представление, так
и внутреннее). Так что ручной перевод остается важным методом
преобразования таблиц.
Проанализировав исходную таблицу, легко заметить, что используется
фактически два варианта функций-условий и три - функций-действий.
Из-за нестандартности работы с инициализацией (нулевая строка
таблицы) удобно пополнить этот набор еще двумя функциями:
<условием> и <действием>, выполняемыми при переходе к этой строке,
которая интерпретируется как завершение работы автомата (таким
образом, предикат OUTSIDE (iSt) можно представить как равенство
аргумента нулю).
Теперь почти все готово для составления программы 10.3.3
,
реализующей интерпретатор таблицы переходов. Осталось решить
вопрос о неявном для таблицы, но с необходимостью явном для
программы способе чтения потока символов. Этот вопрос имеет три
аспекта:
Когда читать? Поскольку прочитанный символ используется после
его распознавания, естественно "не терять" его, пока выполняется
действие. Следовательно, читать очередной символ
целесообразно после отработки действия.
Как поступать в начале процесса: должен ли быть прочитан
символ перед первым вызовом функции handler? Понятно, что
чтение символов является частью функциональности этой
функции. Если в ней реализовать чтение до вызовов условия и
действия, то это противоречит предыдущему соглашению, а
противоположное решение делает нестандартным начало
процесса. В предлагаемой программе принято решение об особой
обработке нулевой строки. Поскольку, являясь инициализацией,
она действительно особая, это соответствует сделанному
соглашению.
Как поступать в конце процесса? Чтение заключительного
Стили и методы программированияНепейвода Н.Н.
182

символа ('\n') должно прекращать возможные попытки
последующего обращения к чтению. Следовательно, необходимо
позаботиться о завершающих переходах. В предлагаемой
программе принято решение о завершающем переходе к нулевой
строке, которая, согласно предыдущему, является нестандартной.
Отметим, что данные соглашения не являются абсолютными, и
поставленные вопросы нуждаются в решении всякий раз, когда
реализуется переход от табличного представления к программе. В
предыдущем параграфе Вы уже видели, как практически в каждой
реализации метод чтения слегка изменялся.
С учетом сделанных замечаний программа, решающая задачу подсчета
длин строк методом интерпретации таблицы, может быть записана
следующим образом.
#include <stdio.h>
char symbol; // переменная для чтения потока символов
int cnt; // переменная для счета длин слов
int c0(), c1(), c2(); // функции-условия
void a0(), a1(), a2(), a3(); // функции-действия
int handler ( int i ); // обработчик строк таблицы
struct T_StructTableLine
{ // поле для имени состояния избыточно
int (*condition)(); // поле условия
void (*action)(); // поле действия
int ref; // поле перехода: индекс строки таблицы,
// которая будет обрабатываться следующей,
// или признак завершения процесса
}
T_StructTableLine
Table[]={ {c0,a0,1}, // таблица инициализируется статически,
{c1,a1,2}, // альтернативное решение - специальная
{c2,a0,1}, // функция задания начальных значений.
{c0,a0,3}, // Оно более гибкое, но менее
{c1,a2,2}, // эффективно.
{c2,a3,1}, // О наглядности см. комментарий
{c0,a3,3}; // в тексте.
Стили и методы программированияНепейвода Н.Н.
183

{c1,a2,2},
{c2,a3,1},
{c0,a3,0}};
void main()
{
int iSt=0;
do {
iSt = handler ( iSt );
}
while ( iSt);
}
int handler ( int i )
{
if (Table[i].condition()) {
Table[i].action();
if (Table[i].ref) { symbol = getchar ();
return Table[i].ref; }
} else return ++i;
}
// Описания используемых функций:
int c0(){return symbol == '\n';}
int c1(){return 'a'<=symbol && symbol <= 'z';}
int c2(){return 'a'>symbol ||symbol > 'z';}
void a0(){}
void a1(){printf ("%c", symbol);cnt = 1;}
void a2(){printf ("%c", symbol);cnt++;}
void a3(){printf ("- %i\n", cnt);}
Листинг 10.3.3. Длины слов: интерпретатор конечного
автомата.
Обсуждение решения
Как уже отмечалось, фрагменты, описывающие условия и действия в
таблице переходов и реализованные как процедурные вставки, с точки
зрения препроцессора (не препроцессора системы программирования, а
Стили и методы программированияНепейвода Н.Н.
184

специального преобразователя, генерирующего представление таблицы
для интерпретации), являются нераспознаваемыми данными, которые
он просто пропускает без изменений для обработки компилятором.
Интерпретатор же, наоборот, не в силах сам исполнять процедуры, а
потому трактует ссылки на них (в соответствующих полях) как данные.
Таким образом, условия и действия в таблице двойственны: они
являются одновременно и данными, и программными фрагментами.
Автоматический преобразователь таблицы, не понимая языка таких
двойственных данных, пытается, тем не менее, их объединить с
обычными данными (в рассматриваемом случае это индексы строк
переходов) в одной структуре.
На первый взгляд кажется, что ситуация существенно упростилась бы в
языке, в котором есть возможность воспринимать данные как
выполнимые фрагменты. Пример такого рода - команда <Вычислить
строку>. Эта команда давала бы возможность интерпретатору понимать
программы-данные, не оставляя их нераспознанными. Подобная
команда порою встречается в языках скриптов, ориентированных на
интерпретацию. А в языке LISP, в котором структуры данных и
программы просто совпадают, указанная двойственность не возникает.
Но по опыту известно, что столь общее решение чревато столь же
общими и вездесущими неприятностями, и нужно искать частный его
вариант, адекватный данной задаче.
Метод решения задач с помощью построения таблиц и графов
состояний и переходов часто удобен для обработки потоков данных. В
книгах [32]
, [33], а особенно в задачнике [2] и на сайте ссылка:
http://is.ifmo.ru - http://is.ifmo.ru, имеется ряд задач такого рода. Решения
стоит оценить количественно и качественно: сколько требуется
состояний и действий, как лучше получается в данном случае (в
состояниях или на переходах). Может оказаться, что понадобятся
средства, выходящие за рамки описанных методов. Это допустимо,
следует только осознавать грань между использованием конечного
автомата и применением другого подходящего метода, а также четко
разделять внутри программы возможности, относящиеся к разным
стилям программирования.
Подведем итоги
Стили и методы программированияНепейвода Н.Н.
185

Таблица и граф - два теоретически эквивалентных (но практически
разных) представления. Каждое из них имеет свои достоинства и
недостатки (на графе информация беднее, но обозримость может быть
лучше). Одна из этих спецификаций чаще всего делает ненужной
другую.
Таблицы и графы гораздо лучше самой программы с точки зрения
модифицируемости и преобразований.
Таким образом, табличное и графовое представления автомата являются
спецификациями №1, о которых нужно думать в первую очередь, когда
составляется автоматная программа.
Главный недостаток табличных и графовых методов представления
автоматов в том, что их приходится дополнительно преобразовывать в
программу. А в дальнейшем возникает соблазн модифицировать
непосредственно программу, и спецификация перестает быть
адекватной программе.
В связи с этим возникает вопрос об улучшении поддержки методов
табличного и графового представления автоматных программ.
1)
В этом пункте заложено недетерминированное поведение
получаемой программы. Как правило, при применении метода таблиц
переходов возможность недетерминированного поведения
игнорируется. Вместо этого задается какое-либо произвольное
детерминированное правило, например, выбирается текстуально
первое из действий, для которого условие истинно. Это похоже на то,
как определяются условные выражения и оператор переключения в
традиционных языках.
2)
Памятью можно считать номер текущего состояния (замечание проф.
А. А.Шалыто).
3)
Это верно и для унификационного варианта.
4)
Этот вариант сознательно запрограммирован на Object Pascal,
поскольку разница между языками традиционного типа несущественна,
а изменение языка заодно выделяет и тот факт, что данный вариант
ориентирован на другую модель автоматного программирования.
Стили и методы программированияНепейвода Н.Н.
186

Автоматное преобразование структурированных
текстов
Понятие о языках разметки. Представление автоматной программы с
помощью XML.
Внимание!
Данная лекция предназначена для тех, кто уже имеет начальные навыки
работы на языке разметки XML
1)
.
Требования к автоматической трансляции таблиц
Серьезный недостаток предложенного в 10.3 решения задачи
автоматического преобразования таблиц переходов в программы связан
с идеей препроцессорного построения, удобного для обработки
представления таблиц переходов. Игнорирование обратной связи
между исходным представлением автомата и его интерпретируемым
представлением порождает проблемы. Если не рассматривать развитие
программы, то отслеживать эту связь не нужно. Но как только встает
вопрос хотя бы о минимальных переделках, возникают проблемы.
Внесение изменений в таблицу не влечет за собой
автоматического изменения внутреннего представления, а
возможные нарушения соглашений, связанных с исходным
решением, могут сделать бессмысленным переиспользование. Это
особенно заметно, если обратить внимание на то, что в
результирующей таблице отсутствуют имена состояний.
Поиск и диагностика ошибок (речь идет не только о синтаксисе)
возможны лишь на уровне интерпретируемого представления, что
противоречит осмыслению программы в прежних терминах.
Наличие программы, не зависящей от исходного представления,
провоцирует на ее модификации, которые не будут перенесены в
исходное представление. В результате концептуальная и
реализационная модели расходятся между собой.
Таким образом, предложенное решение не является
технологичным. Оно может быть распространено на другие
Стили и методы программированияНепейвода Н.Н.
187

ситуации лишь для ограниченного класса задач.
Разработка технологии включает в себя создание и использование
инструментов поддержки всех этапов, из которых состоит
технологическая цепочка оперирования с таблицами. Ключевым
моментом является выбор формата представления таблиц,
приспособленный как к ручной, так и к автоматической обработке. Есть
смысл попытаться отыскать универсальное решение. Если, вдобавок,
оно будет стандартизовано, то появится возможность создать
технологию. Сегодня предпосылки для такого решения существуют, в
частности, использование так называемых языков разметки текстов и, в
первую очередь, XML.
Постановка задачи
При обсуждении программы 10.3.1
было показано, что предложенный
текст размечен символами _i, i=1,...,5 а также то, как от
размеченного текста переходить к программе на языке С++ или к
интерпретации таблицы. У такой разметки только один недостаток: она
не стандартизована, а потому о распространении за пределы среды
разработчиков системы, предназначенной для поддержки применения
таблиц, говорить не приходится.
В настоящее время используются следующие языки разметки, которые
можно считать стандартными:
TEX и, в первую очередь, надстройка над ним LATEX — для
типографской подготовки текстов.
HTML — стандартный язык разметки текстов в Интернет.
XML — язык разметки текстов в Интернет, ориентированный на
активную работу с ними.
Их основное назначение — структурированное представление
визуализации информации. С точки зрения автоматической трансляции
таблиц целесообразно обсудить алгоритмический аспект этих языков, т.
е. ответить, в какой мере разметка текста позволяет описывать
требуемые преобразования. Но предварительно зададимся вопросами о
том, правомерно ли языки разметки считать языками
Стили и методы программированияНепейвода Н.Н.
188

программирования, и если да, то какие стили программирования они в
принципе могут поддерживать.
Построение разного рода визуализаций текстов и сопутствующей им
информации — определяющая функция языков разметки, которая
реализуется путем размещения разметочных команд вместе с
предъявляемой информацией. Эти команды являются управляющими
сигналами для вычислителя, который отвечает за визуализацию.
Внутреннее совместное представление команд и данных для их
выполнения естественно рассматривать в качестве операционной и
одновременно информационной среды вычислителя-визуализатора.
Язык разметки от языка программирования отличает в первую очередь
совмещение данных и задания на их обработку.
Пример 11.2.1. Текст данного примера в языке разметки LATEX (точнее,
в том пакете определений над ним, который разработан для верстки
данного текста и других работ автора) для автоматического размещения
заголовка, создания идентификатора, по которому в дальнейшем можно
ссылаться на Пример 11.2.1, форматирования тела примера и отработки
его конца, окружен командами
\begin{example}\label{markupeample0}
и \end{example}.
Для того, чтобы напечатать первую из этих команд, пришлось
применить дополнительную разметку (чтобы команда была
проинтерпретирована как часть текста)
\verb|\begin{example}\label{markupeample0}|\\.
И, наконец, предыдущая строка также потребовала дополнительной
разметки, но, во избежание бесконечного регресса, на этом закончим.
Для употребляющихся в Интернет языков разметки визуализатором
служит браузер. Но визуализация — далеко не единственное
вычисление, которое можно связывать с браузером или другой
программой, обрабатывающей размеченный текст. Над информацией
можно задавать разные обобщенные вычисления. Эти вычисления
могут быть либо совмещены в одном процессе, либо разнесены во
времени, что непринципиально. Применительно к числовой разметке,
Стили и методы программированияНепейвода Н.Н.
189

использованной в 10.3 для задания таблицы конечного автомата,
правомерно трактовать разметку как команды трех видов вычислений:
визуализация таблицы — трактовка разметочных символов как
команд, предписывающих взаимное расположение фрагментов
текста для его показа и печати;
трансляция таблицы — автоматическое построение программы,
выполняющей действия автомата;
интерпретация таблицы — трактовка разметочных символов как
команд, выполняющих действия с операндами, в качестве которых
используются текстовые фрагменты.
Подобные вычисления можно определять для любого языка разметки,
рассматривая подходящие абстрактные вычислители. Иными словами,
существует программирование на языке разметки. Наиболее полно эта
концепция прослеживается в технологии XML/XSL.
Если обратиться к стилю программирования, который в принципе
может поддерживать язык разметки, то это сентенциальное
программирование. Характерным видом вычислений этого стиля
являются не просто порождение новых данных с сохранением
операндов, а глобальные трансформации данных, существенно
зависящие от контекста. Заметим, что в настоящий момент верно и
обратное: любой сентенциальный язык можно рассматривать в качестве
языка разметки перерабатываемых данных. Иногда такая разметка явно
расставляется по структуре данных (пример — язык Рефал), иногда она
выносится в специальную структуру (Prolog), но в обоих случаях
контекст, определяющий выполнение команд, и замена данных путем
порождения результатов команд остаются. В развитии языков разметки
прослеживается тенденция отделять распознавание структуры текста —
сентенциальная часть обработки — от использования этой структуры
для задания действий, которые определяют обобщенные вычисления
2)
.
Ниже описывается решение задачи автоматической трансформации
таблиц конечного автомата с использованием наиболее подходящего для
этих целей языка XML (см. книгу [19]
).
Если описывать тексты в современных языках разметки, таких, как
Стили и методы программированияНепейвода Н.Н.
190
Соседние файлы в предмете [НЕСОРТИРОВАННОЕ]
