asgn: VAR '=' expr { $$=$1->u.val=$3; $1->type = VAR; }
;
expr: NUMBER
| VAR {
if ($1->type == UNDEF)
execerror("undefined variable", $1->name);
$$ = $1->u.val;
}
| asgn
| BLTIN '(' expr ')' { $$ = (*($1->u.ptr))($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 '^' expr { $$ = Pow($1, $3); }
| '(' expr ')' { $$ = $2; }
| '-' expr %prec UNARYMINUS { $$ = -$2; }
;
%%
/* end of grammar */
...
Теперь в грамматике присутствует
asgn
для присваивания, подобно
expr
для выражения. Входная строка, состоящая только из
VAR = expr
является присваиванием, и, следовательно, ни одно из значений не печатается. Заметьте, кстати, как мы легко добавили к грамматике операцию возведения в степень, являющуюся правоассоциативной.
Для стека
yacc
используется другое определение
%union
: вместо представления переменной как индекса в массиве из 26 элементов введен указатель на объект типа
Symbol
. Файл макроопределений
hoc.h
содержит определение этого типа.
Лексический анализатор распознает имена переменных, находит их в таблице имен и определяет, относятся ли они к переменным (
VAR
) или к встроенным функциям (
BLTIN
). Функция
yylex
возвращает один из указанных типов. Заметим, что определенные пользователем переменные и предопределенные переменные типа
PI
относятся к
VAR
.
Одно из свойств переменной состоит в том, что ей может быть присвоено либо не присвоено значение, поэтому обращение к не определенной переменной должно диагностироваться программой
yyparse
как ошибка. Возможность проверки переменной (определена она или нет) должна быть предусмотрена в грамматике, а не в лексическом анализаторе. Когда
VAR
распознается на лексическом уровне, контекст пока еще не известен, но нам не нужны сообщения о том, что
x
не определен, хотя контекст и вполне допустимый, как, например,
x
в присваивании типа
x = 1
.
Ниже приводится измененная часть функции
yylex
:
yylex() /* hoc3 */
{
...
if (isalpha(c)) {
Symbol *s;
char sbuf[100], *p = sbuf;
do {
*p++ = c;
} while ((c=getchar()) != EOF && isalnum(c));
ungetc(c, stdin);
*p = '\0';
if ((s=lookup(sbuf)) == 0)
s = install(sbuf, UNDEF, 0.0);
yylval.sym = s;
return s->type == UNDEF ? VAR : s->type;
}
...
В функции
main
добавлена еще одна строка, в которой вызывается процедура инициации
init
для занесения в таблицу имен встроенных и предопределенных имен типа
PI
:
main(argc, argv) /* hoc3 */
char *argv[];
{
int fpecatch();
progname = argv[0];
init();
setjmp(begin);
signal(SIGFPE, fpecatch);
yyparse();
}
Теперь остался только файл
math.с
. Для некоторых стандартных математических функций требуется обработка ошибок для диагностики и восстановления, например, стандартная функция по умолчанию возвращает 0, если аргумент отрицателен. Функции из файла
math.с
используют контроль ошибок, описанный в разд. 2 справочного руководства по UNIX (см. гл. 7). Это более надежный и переносимый вариант, чем введение своих проверок, так как, вероятно, конкретные ограничения функций полнее учитываются в "официальной" программе. Файл макроопределений
<math.h>
содержит описания типов для стандартных математических функций, а файл
<errno.h>
— определения фатальных ошибок:
$ cat math.с
#include <math.h>
#include <errno.h>
extern int errno;
double errcheck();
double Log(x)
double x;
{
return errcheck(log(x), "log");
}
double Log10(x)
double x;
{
return errcheck(log10(x), "log10");