Литмир - Электронная Библиотека
Содержание  
A
A

'hoc1' is up to date make понимает, что это не нужно

$

8.2 Этап 2: переменные и восстановление после ошибки

Следующий шаг переход от

hoc1
к
hoc2
, который сводится к расширению памяти (в памяти хранится 26 переменных с именами от
а
до
z
). Это довольно несложный и весьма полезный промежуточный этап. Мы также введем здесь процесс обработки ошибок. Если вы проверите
hoc1
, то убедитесь, что реакцией на синтаксические ошибки являются вывод сообщения и прекращение работы. Поведение же
hoc1
в случае арифметических ошибок типа деления на нуль достойно всяческого порицания:

$ hoc1

1/0

Floating exception - core dump

$

Для реализации новых возможностей требуются лишь небольшие изменения: приблизительно 35 строк текста. Лексический анализатор

yylex
должен распознавать буквы как переменные, а грамматика содержать правила вывода вида

expr: VAR

 | VAR '=' expr

Выражение может содержать операцию присваивания; разрешены также многократные присваивания типа

x = y = z = 0

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

yacc
, что элемент стека является объединением
double
и
int
, а не просто элементом типа
double
. Это делается с помощью описания
%union
. Описания
#define
или
typedef
подходят для определения стека из базовых типов как
double
, но для типов объединения требуется описание
%union
, поскольку
yacc
осуществляет контроль типов в выражениях вида
$$ = $2
.

Ниже приведена часть определения грамматики

hoc.y
для программы
hoc2
:

$ cat hoc.y

%{

double mem[26]; /* memory for variables 'a'..'z' */

%}

%union {     /* stack type */

 double val; /* actual value */

 int index;  /* index into mem[] */

}

%token <val> NUMBER

%token <index> VAR

%type <val> expr

%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
есть сведения о том, что выраж является элементом объединения
<val>
, т.е.
double
. Информация о типе позволяет
yacc
обращаться к нужному элементу объединения. Обратите внимание:
"="
представляет собой правоассоциативную операцию, тогда как другие операции — левоассоциативные.

Обработка ошибок происходит в несколько этапов. Прежде всего производится проверка на нулевой делитель: если делитель равен нулю, вызывается процедура обработки ошибок

execerror
. Второй этап заключается в перехвате сигнала "переполнение вещественного" ("floating point exception"), который возникает при переполнении вещественного числа. Сигнал устанавливается в функции
main
. Последний шаг восстановления после ошибки заключается в добавлении к грамматике правила вывода для ошибки. В грамматике
yacc
слово
error
зарезервировано; оно дает возможность анализатору осознать синтаксическую ошибку и восстановиться после нее. Если произойдет ошибка,
yacc
в конце концов использует это правило, распознает ошибку как грамматически "правильную" конструкцию и, таким образом, восстановится. Действие
yyerrok
заключается в установке признака в анализаторе, который позволяет вернуться ему назад в состояние осмысленного разбора. Восстановление после ошибки сложная проблема для всех анализаторов. Мы показали вам здесь лишь самые элементарные приемы и только обозначили возможности
yacc
.

В грамматике

hoс2
произошли незначительные изменения. Ниже приведена функция
main
, дополненная обращением к
setjmp
. Оно позволяет запомнить то нормальное состояние, которое будет использовано при восстановлении после ошибки. В функции
execerror
происходит соответствующее обращение к
longjmp
. (Описание
setjmp
и
longjmp
см. в разд. 7.5.)

104
{"b":"248117","o":1}