[Содержание]   [Назад]   [Пред]   [Вверх]   [След]   [Вперед]  


7. Восстановление после ошибок

Обычно программа, заканчивающая работу при ошибке разбора, неприемлема. Например, компилятору следует восстановиться настолько, чтобы разобрать остаток входного текста и проверить наличие в ней ошибок, калькулятор должен обработать следующее выражение.

В простом интерактивном анализаторе команд, где каждый вход -- одна строка, может быть достаточно разрешить yyparse вернуть 1 при ошибке, а вызывающей программе -- проигнорировать остаток входной строки, в которой она произошла (и затем вызвать yyparse снова). Но это не годится для компилятора, потому что в этом случае он забудет весь синтаксический контекст, приведший к ошибке. Синтаксическая ошибка глубоко внутри функции во входном тексте компилятора не должна заставлять компилятор рассматривать следующую строку как начало исходного файла.

Вы можете определять способ восстановления после синтаксической ошибки, составляя правила, распознающие специальную лексему error. Это терминальный символ, который всегда определён (вам не нужно объявлять его) и зарезервирован для обработки ошибок. Анализатор Bison генерирует лексему error каждый раз, когда обнаружена синтаксическая ошибка. Если вы предусмотрели правило для распознавания этой лексемы в текущем контексте, разбор может быть продолжен.

Например:

stmnts:  /* пустая строка */
        | stmnts '\n'
        | stmnts exp '\n'
        | stmnts error '\n'

Четвёртое правило в этом примере говорит, что ошибка, за которой следует переход на новую строку, является допустимым дополнением для любого stmnts.

Что случится, если синтаксическая ошибка будет обнаружена внутри exp? Правило восстановления после ошибки, если его интерпретировать строго, применимо к последовательности, состоящей в точности из stmnts, error и перехода на новую строку. Если ошибка обнаружена внутри exp, вероятно, в стеке после последнего stmnts будут находиться некоторые дополнительные лексемы и подвыражения, а до литеры новой строки нужно будет прочитать ещё несколько лексем. Поэтому это правило обычным способом неприменимо.

Но Bison может принудительно привести ситуацию к правилу, отбрасывая часть семантического контекта и часть входного текста. Во-первых, он отбрасывает состояния и объекты в стеке до тех пор, пока не вернётся к правилу, в котором приемлема лексема error (это означает, что уже разобранные подвыражения будут отброшены, вплоть до последнего завершённого stmnts). В этот момент может быть выполнен сдвиг лексемы error. Потом, если нельзя выполнить сдвиг старой предпросмотренной лексемы, анализатор читает лексемы и отбрасывает их до тех пор, пока не найдёт подходящую лексему. В данном примере, Bison читает и отбрасывает входной текст, пока не обнаружит литеру новой строки, так что можно применить четвёртое правило.

Выбор правил грамматики для ошибок -- это выбор стратегии восстановления после ошибки. Простая и полезная стратегия -- при обнаружении ошибки просто пропустить остаток текущей входной строки или текущего оператора.

stmnt: error ';'  /* при ошибке пропускать, пока не будет считана ';' */

Также полезно восстанавливать до закрывающего ограничителя, соответствующего уже разобранному открывающему ограничителю. В противном случае закрывающий ограничитель, вероятно, оказался бы без пары, и вызвал новое, ложное сообщение об ошибке.

primary:  '(' expr ')'
        | '(' error ')'
        ...
        ;

Стратегии восстановления после ошибки неизбежно связаны с догадками. Когда догадка неверна, одна синтаксическая ошибка часто приводит к появлению других. В вышеприведённом примере, правило восстановления после ошибки предполагает, что ошибка вызвана неправильным входным текстом внутри одного stmnt. Предположим, что вместо этого внутрь правильного stmnt вставлена точка с запятой. После того, как правило восстановления после ошибки произведёт восстановление от первой ошибки, сразу же будет обнаружена другая синтаксическая ошибка, поскольку текст, следующий за лишней точкой с запятой также не является верным stmnt.

Чтобы предотвратить поток сообщений об ошибках, анализатор не будет выводить сообщения об ошибках, произошедших вскоре после первой. Возобновит он их вывод только после того, как будет успешно произведён сдвиг трёх лексем подряд.

Имейте в виду, что правила, принимающие лексему error, могут содержать действия, так же как и любые другие правила.

Вы можете возобновить вывод сообщений об ошибках немедленно, используя в действиях макрос yyerrok. Если вы сделаете это в правиле действии правила обработки ошибки, сообщения об ошибках не будут подавляться. Этот макрос не требует аргументов, `yyerrok;' -- это правильный оператор C.

Сразу после обнаружения ошибки предыдущая предпросмотренная лексема анализируется заново. Если это невозможно, можно использовать макрос yyclearin для очистки этой лексемы. Напишите оператор `yyclearin;' в действии правила обработки ошибки.

Например, предположим, что при ошибке разбора вызывается подпрограмма обработки ошибки, продвигающаяся по входному потоку до некоторой точки, где разбор снова может быть начат. Предыдующая предпросмотренная лексема должна быть отброшена с помощью `yyclearin;'.

Макрос YYRECOVERING обозначает выражение, значение которого равно 1, если анализатор производит восстановление после синтаксической ошибки, и 0 всё остальное время. Значение 1 обозначает, что сообщеия о новых синтаксических ошибках в данный момент подавляются.


[Содержание]   [Назад]   [Пред]   [Вверх]   [След]   [Вперед]