
Корректировка грамматики
Предполагаемая до сих пор схема пытается возобновить грамматический разбор, вернуться на правильный путь, пропустив один или более символов входного текста. Но это неудачная стратегия во всех случаях, когда ошибка вызвана пропуском символа. Опыт говорит о том, что такие ошибки фактически связаны с символами, выполняющими чисто синтаксические функции и не представляющими какого-либо действия. Примером служит точка с запятой. Однако, поскольку множества внешних символов дополнены определенными служебными словами, на самом деле грамматический разбор вовремя прекращает пропуск символов, т. е. ведет себя так, как если бы забытый символ был вставлен.
Рассмотрим анализ составного оператора begin . . . end
┌----------┐
>--- begin ---│ operator │-┬------------------------┬--- end >
└----------┘ ^ │
│ │
│┌----------┐ │
└│ operator │< semicolon ┘
└----------┘
if sym = begin
then
begin
getsym;
operator;
while sym = semicolon do
begin
getsym;
operator
end;
if sym = end
then getsym
else error(17)
end
else обработка другого оператора
Если будет пропущена точка с запятой между операторами, то анализатор будет проверять наличие терминального символа end и в его отсутствии выдавать ошибку с кодом 17 - "Требуется точка с запятой или end"
Рассмотрим вариант программы, позволяющий продолжить процесс синтаксического контроля при отсутствии между операторами точки с запятой (semicolon).
Сначала проверяется может ли входная последовательность быть оператором, путем обращения к процедуре operator. Для нормального окончания процедуры, ей передается множество внешних символов – [semicolon, end] и множество символов возобновления fsys = [point] + declbegsys + ststbegsys , передаваемых и дополняемых транзитом от самой внешней процедуры. Здесь для групп символов даны следующие названия:
declbegsys = [const, var]
statbegsys = [begin, if, while]
facbegsys = [id, number, lparent]
Далее необходимо проверить нет ли символа "точки с запятой". Но в данном случае список символов, указывающий, что составной блок еще не закончен, расширен. В этот список включены ключевые слова – begin, if, while. Таким образом программа как бы «вставляет» пропущенные точки с запятой перед ключевыми словами. Множество, называемое statbegsys, есть множество начальных символов конструкции «оператор».
if sym = begin
then
begin
getsym;
operator([semicolon, end] + fsys);
while sym in [semicolon]+ statbegsys do
begin
if sym = semicolon
then getsym
else error(10);
operator([semicolon, end] + fsys)
end;
if sym = end
then getsym
else error(17)
end
else обработка другого оператора
Хотя анализ входной цепочки будет продолжен, сообщение об отсутствии "точки с запятой" выводится. Подправленный синтаксический граф составного оператора показан ниже:
┌----------┐
>--- begin ---│ operator │-┬------------------------┬--> end -
└----------┘ ^ │
│ │
│┌----------┐ │
└│ operator │< semicolon ┤
└----------┘<-----------┘
О том, насколько успешно эта программа обнаруживает синтаксические ошибки и справляется с необычными ситуациями, можно судить по программе ПЛ/0 (5.20). Ее распечатка является результатом работы программы 5.5, а в табл. 5.3 перечислены возможные сообщения об ошибках, соответствующие номерам ошибок в программе 5.5.
Таблица 5.3. Сообщения об ошибках, выдаваемые транслятором с ПЛ/0
1. = вместо :=
2. Нет числа после =
3. Нет = после идентификатора
4. Нет идентификатора после const. var, procedure
5. Пропущена запятая или точка с запятой
6. Неверный символ после описания процедуры
7. Нет оператора
8. Неверный символ после операторной части блока
9. Нет многоточия
10. Пропущена точка с запятой между операторами
11. Неописанный идентификатор
12. Недопустимое присваивание константе или процедуре.
13. Требуется :=
14. Нет идентификатора после call
15. Вызов константы или переменной вместо процедуры
16. Требуется then
17. Требуется точка с запятой или end
18. Требуется do
19. Неверный символ после оператора
20. Требуется сравнение
21. Выражение содержит идентификатор процедуры
22. Отсутствует правая скобка
23. Неверный символ после множителя
24. Неверный символ в начале выражения
30. Слишком большое число
Все же ясно, что никакая схема, которая достаточно эффективно транслирует правильные предложения, не сможет так же эффективно справляться со всеми возможными неправильными конструкциями. Да и как может быть иначе!
Любая схема восстановления, реализованная с разумными затратами, потерпит неудачу, т. е. не сможет адекватно обработать некоторые ошибочные конструкции. Однако хороший транслятор должен обладать такими важными свойствами:
1. Никакая входная последовательность не должна приводить к катастрофе.
2. Все конструкции, которые по определению языка являются незаконными, должны обнаруживаться и отмечаться.
3. Ошибки, встречающиеся довольно часто и действительно являющиеся ошибками программиста (вызванными недосмотром или недопониманием), должны правильно диагностироваться и не вызывать каких-либо дальнейших отклонений в работе транслятора — сообщений о так называемых наведенных ошибках.
Предлагаемая схема восстановления работает удовлетворительно, хотя, как всегда, возможно ее дальнейшее усовершенствование. Ее преимущество в том, что она построена систематическим образом по нескольким основным правилам. Эти основные правила просто разработаны с помощью выбора параметров, основанного на эвристических соображениях и опыте практического использования языка.
Синтаксис языка
<program> ::= <block> point
<block> ::= <declare> <operator>
<block> ::= <operator>
<declare> ::= <declare> semicolon <dcl_const>
<declare> ::= <declare> semicolon <dcl_var>
<declare> ::= <dcl_const> semicolon
<declare> ::= <dcl_var> semicolon
<dcl_const> ::= const <list_const>
<list_const> ::= <list_const> comma id equal number
<list_const> ::= id equal number
<dcl_var> ::= var <list_var>
<list_var> ::= <list_var> comma id
<list_var> ::= id
<operator> ::= id become <expr>
<operator> ::= if log_expr then <operator>
<operator> ::= while log_expr do <operator>
<operator> ::= begin <list_oper> end
<list_oper> ::= <list_oper> semicolon <operator>
<list_oper> ::= <operator>
<log_expr> ::= <expr> rel <expr>
<expr> ::= <expr> plus <add>
<expr> ::= <expr> minus <add>
<expr> ::= <add>
<add> ::= <add> mult <factor>
<add> ::= <add> div < factor>
<add> ::= <factor>
<factor> ::= id
<factor> ::= lparent <expr> rparent
Таблица 5.2. 1 Начальные и внешние символы в ПЛ/0
.
Нетерминальный символ S |
Начальные символы L(S) |
Внешние символы F(S) |
blok Блок |
const var id if begin while |
point |
Оператор |
id call begin if while |
point semicolon end |
log_expr Условие |
lparent id number |
then do |
expr Выражение |
lparent id number |
point. semicolon rparent rel end then do |
add Слагаемое |
lparent id number |
point. semicolon rparent rel end then do |
factor Множитель |
id number lparent |
semicolon rparent rel plus minus mult div end then do |