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

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");

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