y
… для переменной у
eval
Вычислить: заменить указатель значением
mul
Перемножить два верхних элемента; результат заменяет их
varpush
Записать указатель на таблицу имен в стек
x
… для переменной x
assign
Записать значение в переменную, убрать указатель
pop
Убрать верхний элемент из стека
STOP
Конец последовательности команд
Когда выполняются команды, выражение вычисляется и результат записывается в x
, как и указано в примечаниях. Последняя команда pop
удаляет из стека верхний элемент, поскольку он больше не нужен.
Стековые машины обычно реализуются с помощью простых интерпретаторов, и наш интерпретатор тоже не является исключением: это просто массив, содержащий операции и операнды. Операции представляют собой машинные команды: каждая из них суть обращение к функции с параметрами, которые следуют за командой. Некоторые операнды могут уже находиться в стеке, как было показано в приведенном выше примере.
Структура таблицы имен для hoc4
совпадает с таковой для hoc3
: инициация проводится в init.c
, и математические функции, находящиеся в math.c
, одни и те же. Грамматика hoc4
идентична грамматике hoc3
, но действия совершенно иные. Вообще, каждое действие порождает машинные команды и все необходимые для них аргументы. Например, в случае появления VAR
в выражении создаются три команды: команда varpush
, указатель на таблицу имен для переменной и команда eval
, которая заменяет при вычислении указатель на таблицу имен соответствующим значением. Код для '*'
содержит одну команду mul
, поскольку операнды для нее уже находятся в стеке.
$ cat hoc.y
%{
#include "hoc.h"
#define code2(c1,c2) code(c1); code(c2)
#define code3(c1,c2,c3) code(c1); code(c2); code(c3)
%}
%union {
Symbol *sym; /* symbol table pointer */
Inst *inst; /* machine instruction */
}
%token
%right '='
%left '+'
%left '*' '/'
%left UNARYMINUS
%right '^' /* exponentiation */
%%
list: /* nothing */ | list '\n'
| list asgn '\n' { code2(pop, STOP); return 1; }
| list expr '\n' { code2(print, STOP); return 1; }
| list error '\n' { yyerrok; }
;
asgn: VAR '=' expr { code3(varpush, (Inst)$1, assign); }
;
expr: NUMBER { code2(constpush, (Inst)$1); }
| VAR { code3(varpush, (Inst)$1, eval); }
| asgn
| BLTIN '(' expr ')' { code2(bltin, (Inst)$1->u.ptr); }
| '(' expr ')'
| expr '+' expr { code(add); }
| expr '-' expr { code(sub); }
| expr '*' expr { code(mul); }
| expr '/' expr { code(div); }
| expr '^' expr { code(power); }
| '-' expr %prec UNARYMINUS { code (negate); }
;
%%
/* end of grammar */
...
Inst
является типом данных машинной команды (указатель на функцию, возвращающую int
), к обсуждению которого мы вскоре вернемся. Обратите внимание на то, что аргументами для программы code
служат имена функций, т.е. указатели на функции или другие совместимые с ними величины.
Мы несколько изменили процедуру main
. Теперь происходит возврат из анализатора после выполнения каждого оператора или выражения, и порожденный код выполняется. При обнаружении файла yyparse
возвращает нуль.
main(argc, argv) /* hoc4 */
char *argv[];
{
int fpecatch();
progname = argv[0];
init();
setjmp(begin);
signal(SIGFPE, fpecatch);
for (initcode(); yyparse(); initcode())
execute(prog);
return 0;
}
Лексический анализатор отличается мало в основном тем, что числа следует сохранять, а не использовать немедленно. Для этого достаточно занести их в таблицу имен вместе с переменными. Ниже приведена измененная часть yylex
:
yylex() /* hoc4 */
...
if (с == '.' || isdigit(c)) {
/* number */
double d;
ungetc(c, stdin);
scanf("%lf", &d);
yylval.sym = install("", NUMBER, d);
return NUMBER;
}
...
Вильям Л Саймон , Вильям Саймон , Наталья Владимировна Макеева , Нора Робертс , Юрий Викторович Щербатых
Зарубежная компьютерная, околокомпьютерная литература / ОС и Сети, интернет / Короткие любовные романы / Психология / Прочая справочная литература / Образование и наука / Книги по IT / Словари и Энциклопедии