Представление связи исходного и выходного текстов при автоматическом порождении текстов программ [Антон Евгеньевич Москаль kouzdra] (pdf) читать постранично

Книга в формате pdf! Изображения и текст могут не отображаться!


 [Настройки текста]  [Cбросить фильтры]

А. Е. Москаль

ПРЕДСТАВЛЕНИЕ СВЯЗИ ИСХОДНОГО
И ВЫХОДНОГО ТЕКСТОВ ПРИ
АВТОМАТИЧЕСКОМ ПОРОЖДЕНИИ
ТЕКСТОВ ПРОГРАММ
Введение
При использовании макропроцессоров существует проблема представления связи
порожденного текста с исходным. Такая привязка необходима для корректной выдачи
диагностики об ошибках, работы отладчика, browser'a и т. п.
Следует отметить, что автоматическая генерация текста программы является очень
распространенным механизмом, используемым не только в макропроцессорах в собственном
смысле этого слова. Аналогичные проблемы возникают во многих других системах, например в
широко известной системе построения трансляторов YACC [1]. Кроме того, близкие проблемы
возникают в системах с расширяемым синтаксисом [2, 3], в системах для автоматического
построения кодогенераторов ([4] и другие).
Хотя проблема является общей для всех макропроцессоров, рассмотрение будет
производиться на примере макропроцессора языка C [5], который является наиболее известным.
Другие подходы к проблеме
Обычным решением проблемы является привязка текста с точностью до строки. Например, в
препроцессорах языка C она осуществляется при помощи вставки в порожденный текст
специальных операторов, задающих имя файла и номер строки в этом файле для каждой строки
порожденного текста (так называемый оператор #line1).
Это является удовлетворительным решением проблемы в случае языка C (имеющего крайне
слабые макровозможности, которые к тому же ориентированы на построчную обработку), но
гораздо менее приемлемо для более развитых макроязыков (например, для языка широко
используемого макропроцессора M4).
Даже в случае, если построчная привязка считается удовлетворительным решением
проблемы, при таком подходе трансляторы с языка С, как правило, неспособны точно указать
позицию в строке, приведшую к возникновению ошибки.
Однако кроме этих недостатков, существуют более серьезные. Рассмотрим следующий
фрагмент программы на языке C++:
#define for_all(ptr, list) \
for (List * ptr = list; \
ptr != NUL; ptr = ptr -> next)
int f (List * list)
{
int s = 0;
for_all (plist, lis)
s += plist -> info;
return s;
}

В данном примере содержится две ошибки: использование NUL вместо NULL и неверно
написанное имя переменной list в параметре макроса.
Обычно применяемый подход даст нам оба сообщения о неописанных именах, которые
будут привязаны к строке, содержащей вызов макроса. Если применительно к имени
переменной list это вполне разумная привязка, то для имени NUL это не слишком удачный
вариант.

1
Вставка этой информации непосредственно в порожденный текст не является необходимой. Того же результата
можно достичь, задав отдельно таблицу соответствия для строк выходного файла.
© А. Е. Москаль, 2000

В случае более сложного макроса поиск причины такой ошибки может быть источником
серьезных трудностей (особенно если ошибка имеет не столь очевидный характер, либо если в
одной строке вызывается несколько макросов).
Первый вариант, который приходит в голову, — это для каждого символа выходного текста
хранить указатель на байт в одном из исходных файлов, из которого он получен. Этот способ
даст удовлетворительные результаты в данном примере, но будет вести себя совершенно
неудовлетворительно, например, в таком случае:
#define isIdLetter(c)\
(isalpha (c) || (c) == '_')

Для корректной работы этого макроса должно быть доступно описание функции isaplha.
Если это не так, то данный подход приведет к привязке сообщения об ошибке в строке с
описанием макроса, тогда как причину ошибки следует искать в месте его использования. Более
того, при таком способе привязки место вызова макроса, приведшее к ошибке, установить почти
невозможно.
В случае работы browser'a или отладчика, а также в случае использования более развитых
макросредств (формирования новых идентификаторов, определения макросов внутри других
макросов и т. п.) проблемы только усугубляются.
Становится ясным, что попытка однозначной привязки позиции выходного текста к
исходному тексту является принципиально недостаточной. Разумным выходом представляется
хранение полной информации о происхождении каждого байта выходного файла, позволяющей
проследить историю его формирования.
Предлагаемая структура
Предлагаемое решение основывается на наблюдении, что порождаемый текст может быть
представлен как результат последовательности замен подстрок в тексте: #include заменяется на
содержимое включаемого файла, оператор условной трансляции — на ветвь then или else, вызов
макроса — на его тело, вхождения формальных параметров в тело макроса — на значения
фактических параметров и т. п.
Таким образом, мы получаем следующее представление порождаемого текста, состоящее из
четырех типов записей, каждый из которых описывает некоторый текст, реально (или
«виртуально») существовавший во время генерации текста. Все эти записи содержат в качестве
первого поля длину описываемого ими