- •Введение
- •2.1.2. Основная программа анализатора
- •2.1.3. Вспомогательные процедуры
- •2.1.4. Распознающие процедуры
- •2.2. Умножение многочленов
- •2.2.1. Постановка задачи
- •2.2.2. Умножение и вывод
- •2.2.3. Транслятор многочленов
- •2.2.4. Обработка ошибок при трансляции
- •2.3. Табличный ll(1) – анализатор
- •2.4. Табличный транслятор многочленов
- •2.5. Реализация стека
- •2.6. Ll(1) – драйвер
- •3. Порядок выполнения работы
- •Варианты заданий
- •4. Контрольные вопросы
- •Список литературы
- •450000, Уфа - центр, ул. К. Маркса, 12
2.2.4. Обработка ошибок при трансляции
В рассмотренном примере обработка ошибок выполнялась с помощью процедуры Error, которая после выдачи сообщения прекращала работу всей программы. Такой способ реакции на ошибку упрощает синтаксический анализатор, но далеко не идеален. Рассмотрим способ его усовершенствования: завершение работы распознавателя (транслятора) после обнаружения первой ошибки без прерывания работы вызвавшей его программы.
Проблема при обработке ошибок состоит в том, чтобы после обнаружения ошибки завершить все процедуры, цепочка вызова которых привела к процедуре, обнаружившей ошибку. Это можно сделать, если процедура Error установит признак ошибки и прочитает текст до конца. Текущим символом станет «конец текста». Поскольку этот символ не может встретиться в тексте, он будет отвергнут всеми частями анализатора, который завершит свою работу без прерывания программы в целом. В качестве признака ошибки будем использовать номер ошибочного символа (обозначим ErrPos), ненулевое значение которого соответствует наличию ошибки и несет информацию о ее местоположении. Исправленные процедуры выглядят следующим образом.
procedure Error(Message: string);
var
e: integer;
begin
if ErrPos = 0 then begin
WriteLn(‘^’: Pos);
WriteLn (‘Синтаксическая ошибка: ‘, Message);
e:=Pos;
while Ch <> EOT do NextCh;
ErrPos:=e;
end;
end;
Процедура Error «самоблокируется», проверяя значение ErrPos и обходя выдачу сообщений при повторных вызовах.
Процедура NextCh после обнаружения ошибки всегда возвращает «конец текста».
procedure NextCh;
begin
if ErrPos <> 0 then
Ch := EOT
else
…
end;
Необходимо беспокоиться о том, чтобы при обнаружении ошибки не оставались неопределенными или недопустимыми данные, связанные с семантической обработкой. Они могут использоваться в вычислениях, которые будут выполняться после выдачи сообщения об ошибке при возврате из распознающих процедур. При этом не должно возникнуть недопустимых ситуаций: деление на ноль, разыменование неопределенного или равного nil указателя, выход индекса за границы массива и т.д. Например, распознаватель степени, в задачу которого входит определение величины p, при использовании в нашем примере следует записать следующим образом:
procedure Power (var p: integer);
begin
if Ch = ‘^’ then begin
NextCh;
Number(p);
if p>nmax then begin
Error (‘Слишком большая степень’);
p:=0; {Степень не должна остаться слишком большой}
end
end
else
p:=1;
end;
Требуется еще одно уточнение. Поскольку при завершении работы распознавателя, обнаружившего ошибку, текущим символом станет EOT, его проверка уже не будет достаточным условием успешного окончания анализа. Использовавшееся завершение:
if Ch <> EOT then
Error (‘Ожидается конец текста’)
else
WriteLn (‘Правильно’);
должно быть заменено следующим:
if (ErrPos = 0) and (Ch = EOT) then
WriteLn (‘Правильно’)
else
Error (‘Ожидается конец текста’);
2.3. Табличный ll(1) – анализатор
Рассматривая автоматные языки, мы использовали детерминированный конечный автомат в роли эффективного универсального распознавателя. Один из вариантов его реализации – программная интерпретация таблицы переходов автомата. На похожих принципах может быть построен и распознаватель для LL(1) – грамматик, который будет представлять собой реализацию определенного подкласса МП – автоматов.
Для примера возьмем конечный автомат, распознающий целые числа без знака. Табл. 1, имеющая традиционный вид, задает его переходы.
Таблица 1.
Состояние |
Символ |
|
Цифра |
Не цифра |
|
1 |
2 |
E |
2 |
2 |
K |
E |
E |
E |
K |
|
|
Здесь E означает состояние ошибки, а K – конечное состояние автомата. Теперь будем модифицировать таблицу. Символы запишем во втором столбце, а состояние, в которое переходит автомат при совпадении входного символа и символа в таблице, - в третьем. В четвертом столбце будем отмечать, возникает ли ошибка, если входной символ не совпадает с символом, записанным в таблице для данного состояния. Если в данном столбце записано «Нет», то при совпадении символов автомат переходит в следующее по порядку состояние.
Таблица 2.
Состояние |
Символ |
Переход |
Ошибка |
10 |
Цифра |
11 |
Да |
11 |
Цифра |
11 |
Нет |
12 |
Любой |
0 |
Нет |
Распознаватель целого для данного примера начинается с состояния 10 (нумерация не с единицы, чтобы подчеркнуть, что данный распознаватель составляет не законченный автомат, а только его часть). Переход в состояние 0, который переходит при получении автоматом, находящимся в состоянии 12, любого символа (например, символа «конец текста») означает завершение его работы с принятием (части) входной цепочки.
Данный автомат можно применить при построении автомата, распознающего многочлен в целом. Начнем с автомата, распознающего степень. Построим таблицу переходов (табл. 3). Пусть состояния этого автомата начинаются с 20.
Таблица 3.
Состояние |
Символ |
Переход |
Ошибка |
Вызов |
Читать |
20 |
^ |
22 |
Нет |
Нет |
Да |
21 |
Любой |
0 |
Нет |
Нет |
Нет |
22 |
Любой |
10 |
Нет |
Да |
Нет |
23 |
Любой |
0 |
Нет |
Нет |
Нет |
В таблице добавилось два столбца. Столбец «Читать» управляет чтением следующего символа: если в этом столбце стоит «Да», то при совпадении входного символа и символа, записанного во втором столбце для данного состояния, читается следующий символ входной цепочки. Распознавание начинается, когда автомат находится в состоянии 20. Если текущим символом является «^», автомат переходит в состояние 22, считывая следующий символ. Если первый символ не равен «^», автомат переходит в состояние 21 и принимает часть входной цепочки, за которую он отвечает – запись степени в этом случае пуста.
Попав в состояние 22, автомат ожидает целое число, поэтому в данном состоянии запрограммирован переход в состояние 10. Но это не обычный переход, а переход с возвратом. По окончании распознавания целого должен произойти возврат из состояния 12 в состояние 23. Чтобы отметить особенность таких переходов, в таблицу введена графа «Вызов», в которой записывается «Да», если происходит переход с возвратом.
Значение 0 в графе «Переход» следует воспринимать, как возврат в состояние, следующее за тем, из которого произошел вызов.