Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
Синтаксический анализитор.doc
Скачиваний:
8
Добавлен:
19.11.2019
Размер:
385.54 Кб
Скачать

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 в графе «Переход» следует воспринимать, как возврат в состояние, следующее за тем, из которого произошел вызов.