Обработка ошибок происходит в несколько этапов. Прежде всего производится проверка на нулевой делитель: если делитель равен нулю, вызывается процедура обработки ошибок
execerror
. Второй этап заключается в перехвате сигнала "переполнение вещественного" ("floating point exception"), который возникает при переполнении вещественного числа. Сигнал устанавливается в функции main
. Последний шаг восстановления после ошибки заключается в добавлении к грамматике правила вывода для ошибки. В грамматике yacc
слово error
зарезервировано; оно дает возможность анализатору осознать синтаксическую ошибку и восстановиться после нее. Если произойдет ошибка, yacc
в конце концов использует это правило, распознает ошибку как грамматически "правильную" конструкцию и, таким образом, восстановится. Действие yyerrok
заключается в установке признака в анализаторе, который позволяет вернуться ему назад в состояние осмысленного разбора. Восстановление после ошибки сложная проблема для всех анализаторов. Мы показали вам здесь лишь самые элементарные приемы и только обозначили возможности yacc
.В грамматике
hoс2
произошли незначительные изменения. Ниже приведена функция main
, дополненная обращением к setjmp
. Оно позволяет запомнить то нормальное состояние, которое будет использовано при восстановлении после ошибки. В функции execerror
происходит соответствующее обращение к longjmp
. (Описание setjmp
и longjmp
см. в разд. 7.5.)...
#include
#include
char *progname;
int lineno = 1;
#include
#include
jmp_buf begin;
main(argc, argv) /* hoc2 */
char *argv[];
{
int fpecatch();
progname = argv[0];
setjmp(begin);
signal(SIGFPE, fpecatch);
yyparse();
}
execerror(s, t) /* recover from run-time error */
char *s, *t;
{
warning(s, t);
longjmp(begin, 0);
}
fpecatch() /* catch floating point exceptions */
{
execerror("floating point exception", (char*)0);
}
В целях отладки мы сочли удобным, чтобы функция
execerror
вызывала abort
(см. справочное руководство по abort(3)
), что приведет к распечатке содержимого памяти, которую затем смогут использовать программы adb
и sdb
. Когда разработка программы полностью завершится, обращение к abort
будет заменено на longjmp
.В программе
hoc2
лексический анализатор несколько иной. В нем учтено различие строчных и прописных букв, а поскольку теперь yyval
является объединением, нужно выбрать подходящий элемент перед выходом из yylex
. Ниже показаны измененные фрагменты:yylex() /* hoc2 */
{
...
if (с == '.' || isdigit(c)) { /* number */
ungetc(c, stdin);
scanf("%lf", &yylval.val);
return NUMBER;
}
if (islower(c)) {
yylval.index = с - 'a'; /* ASCII only */
return VAR;
}
...
Еще раз отметим, что тип лексемы (т.е.
NUMBER
) не совпадает с ее значением (например, 3.1416).Продемонстрируем новые возможности
hoc2
переменные и способность восстановления после ошибки:$ hoc2
x = 355
355
y = 113
113
p = x/z
z не определено, а значит, равно 0hoc2: division by zero near line 4
Восстановление после ошибкиx/y
3.1415929
1е30 * 1е30
Переполнениеhoc2: floating point exception near line 5
...
В самом деле, для PDP-11 требуются вполне конкретные меры, чтобы обнаружить переполнение вещественного, но на большинстве других машин
hoc2
действует так, как показано выше.Упражнение 8.3Обеспечьте возможность запоминания последнего вычисленного значения, чтобы его не приходилось вводить снова для последовательности связанных вычислений. Одним из решений может быть использование какой-либо переменной, например
'p'
, в качестве "предыдущего" (previous) значения.Упражнение 8.4Измените программу
hoc
так, чтобы можно было использовать символ ';'
как разделитель выражений наравне с символом перевода строки.8.3 Этап 3: переменные с произвольными именами; встроенные функции
В версию
hoc3
добавлено несколько новых средств, из-за чего увеличился текст программы. Основное нововведение возможность обращения к встроенным функциям:sin cos atan exp log log10 sqrt int abs
Введена также дополнительно операция возведения в степень
'^'
(право ассоциативная с наивысшим приоритетом).Поскольку лексический анализатор должен справляться с встроенными именами длиной более чем в один символ, не так уж много усилий придется приложить, чтобы допустить переменные с именами произвольной длины. Для хранения информации об этих переменных нужна довольно сложная таблица имен, но если мы ее создаем, то можно заранее задать в ней вместе с именами значения некоторых полезных констант:
PI
3.14159265358979323846 Число π E
2.71828182845904523536 Основание натурального логарифма GAMMA
0.57721566490153286060 Константа Эйлера-Маскерони DEG
57.2957795130823208768 Отношение градуса к радиану PHI
1.61803398874989484820 Золотое сечение
В результате получим полезный калькулятор:
$ hoc3
1.5^2.3
2.5410306
exp(2*3*log(1.5))
2.5410306
sin(PI/2)
1
atan(1)*DEG
45
Несколько улучшилась и работа распознавателя. В
hoc2
присваивание x = expr
не только вызывало присваивание, но и приводило к печати значения, поскольку все выражения печатаются:$ hoc2
x=2*3.14159
6.28318
В случае присваивания переменной значение печатаетсяВ программе
hoc3
проводится различие между присваиваниями и выражениями; значения печатаются только для выражений: