%right '='
%left '+'
%left '*' '/'
%left UNARYMINUS
%%
list: /* nothing */
| list '\n'
| list expr '\n' { printf ("\t%.8g\n", $2); }
| list error '\n' { yyerrok; }
;
expr: NUMBER
| VAR { $$ = mem[$1]; }
| VAR '=' expr { $$ = mem[$1] = $3; }
| expr '+' expr { $$ = $1 + $3; }
| expr '-' expr { $$ = $1 - $3; }
| expr '*' expr { $$ = $1 * $3; }
| expr '/' expr {
if ($3 == 0.0)
execerror("division by zero", "");
$$ = $1 / $3;
}
| '(' expr ')' { $$ = $2; }
| '-' expr %prec UNARYMINUS { $$ = -$2; }
;
%%
/* end of grammar */
...
Из описания %union
следует, что элементы стека содержат или число с двойной точностью (обычный случай), или целое, являющееся индексом в массиве mem
. В описании %token
дополнительно указывается тип значения. В описании %type
есть сведения о том, что выраж является элементом объединения , т.е. double
. Информация о типе позволяет yacc
обращаться к нужному элементу объединения. Обратите внимание: "="
представляет собой правоассоциативную операцию, тогда как другие операции — левоассоциативные.
Обработка ошибок происходит в несколько этапов. Прежде всего производится проверка на нулевой делитель: если делитель равен нулю, вызывается процедура обработки ошибок 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 не определено, а значит, равно 0
hoc2: division by zero near line 4
Восстановление после ошибки
x/y
3.1415929
1е30 * 1е30
Переполнение
Читать дальше
Конец ознакомительного отрывка
Купить книгу