Одно из свойств переменной состоит в том, что ей может быть присвоено либо не присвоено значение, поэтому обращение к не определенной переменной должно диагностироваться программой 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
может быть разобрана двумя способами.
Анализатор может решить, что \n
сразу (shift — перенос) и преобразовать все в список, не используя промежуточных выводов, как в правом дереве разбора. Встретив неоднозначность, yacc
выбирает перенос, так как это почти всегда правильное решение для реальных грамматик. Вы должны понимать такие сообщения, чтобы быть уверенным, что yacc
сделал правильный выбор[16]. Запуск yacc
с флагом -v
порождает обширный файл с именем y.output
, который поможет вам найти причины конфликтов.
В данной версии hoc3
допустимо присваивание:
PI=3
Хорошо ли это? Как бы вы изменили hoc3
, чтобы запретить присваивание "констант"?
Вильям Л Саймон , Вильям Саймон , Наталья Владимировна Макеева , Нора Робертс , Юрий Викторович Щербатых
Зарубежная компьютерная, околокомпьютерная литература / ОС и Сети, интернет / Короткие любовные романы / Психология / Прочая справочная литература / Образование и наука / Книги по IT / Словари и Энциклопедии