UNIX — страница 82 из 115

{

 int i;

 Symbol *s;


 for (i = 0; consts[i].name; i++)

  install(consts[i].name, VAR, consts[i].cval);

 for (i = 0; builtins[i].name; i++) {

  s = install(builtins[i].name, BLTIN, 0.0);

  s->u.ptr = builtins[i].func;

 }

}

Данные хранятся в таблицах, а не вводятся в текст программы, чтобы легче было их читать и изменять. Таблицы определены как статические, что обеспечивает их доступность только в данном файле. Мы вскоре вернемся к обсуждению стандартных математических функций типа

Log
и
Sqrt
.

Построив такой базис, можно перейти к изменениям в грамматике, которые осуществляются на его основе.

$ cat hoc.y

%{

#include "hoc.h"

extern double Pow();

%}

%union {

 double val;  /* actual value */

 Symbol *sym; /* symbol table pointer */

}

%token  NUMBER

%token  VAR BLTIN UNDEF

%type   expr asgn

%right '='

%left  '+'

%left  '*' '/'

%left  UNARYMINUS

%right '^' /* exponentiation */

%%

list: /* nothing */

 | list '\n'

 | list asgn '\n'

 | list expr '\n' { printf("\t%.8g\n", $2); }

 | list error '\n' { yyerrok; }

 ;

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). Это более надежный и переносимый вариант, чем введение своих проверок, так как, вероятно, конкретные ограничения функций полнее учитываются в "официальной" программе. Файл макроопределений
содержит описания типов для стандартных математических функций, а файл
— определения фатальных ошибок:

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