| 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). Это более надежный и переносимый вариант, чем введение своих проверок, так как, вероятно, конкретные ограничения функций полнее учитываются в "официальной" программе. Файл макроопределений содержит описания типов для стандартных математических функций, а файл — определения фатальных ошибок:
$ cat math.с
#include
#include
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");
}
double Sqrt(x)
double x;
{
return errcheck(sqrt(x), "sqrt");
}
double Exp(x)
double x;
{
return errcheck(exp(x), "exp");
}
double Pow(x, y)
double x, y;
{
return errcheck(pow(x,y), "exponentiation");
}
double integer(x)
double x;
{
return (double)(long)x;
}
double errcheck(d, s) /* check result of library call */
double d;
char *s;
{
if (errno == EDOM) {
errno = 0;
execerror(s, "argument out of domain");
} else if (errno == ERANGE) {
errno = 0;
execerror(s, "result out of range");
}
return d;
}
Любопытная, хотя грамматически неясная, диагностики появится при запуске yacc с новой грамматикой:
$ yacc hoc.y
conflicts: 1 shift/reduce
$
Сообщение shift/reduce
означает, что грамматика hoc3
неоднозначна: единственная входная строка
x=1
Читать дальше
Конец ознакомительного отрывка
Купить книгу