UNIX — универсальная среда программирования
Шрифт:
| expr GE expr { code(ge); }
| expr LT expr { code(lt); }
| expr LE expr { code(le); }
| expr EQ expr { code(eq); }
| expr NE expr { code(ne); }
| expr AND expr { code(and); }
| expr OR expr { code(or); }
| NOT expr { $$ = $2; code(not); }
;
prlist: expr { code(prexpr); }
| STRING { $$ = code2(prstr, (Inst)$1); }
| prlist ',' expr { code(prexpr); }
| prlist ',' STRING { code2(prstr, (Inst)$3); }
;
defn: FUNC procname { $2->type=FUNCTION; indef=1; }
'(' ')' stmt { code(procret); define($2); indef=0; }
| PROC procname { $2->type=PROCEDURE; indef=1; }
'(' ')' stmt { code(procret); define($2); indef=0; }
;
procname: VAR
| FUNCTION
| PROCEDURE
;
arglist: /* nothing */ { $$ = 0; }
| expr { $$ = 1; }
| arglist expr { $$ = $1 + 1; }
;
%%
/* end of grammar */
...
С
Правило для опред вводит новое свойство языка
yacc
: встроенное действие. Оказывается, можно поместить действие посредине правила, так, чтобы оно выполнялось в процессе распознавания последнего. Мы воспользовались этой возможностью, чтобы запомнить, что сейчас распознается: определение функции или процедуры. (В качестве альтернативного решения можно было бы ввести новый символ типа begin
, который распознавался бы в соответствующее время.) Функция defnonly
печатает предупреждающее сообщение, если вопреки синтаксису какая-либо конструкция окажется вне определения функции или процедуры. Обычно вам предоставляется выбор: обнаруживать ошибку синтаксически или семантически. Перед нами уже стояла такая задача ранее, при диагностике неопределенных переменных. Функция defnonly
хорошо иллюстрирует ситуацию, когда семантическая проверка легче синтаксической.
defnonly(s) /* warn if illegal definition */
char *s;
{
if (!indef)
execerror(s, "used outside definition");
}
Переменная
indef
определена в hoc.y
и принимает значения в действиях для опред. К лексическому анализатору добавлены средства проверки аргументов: символ
$
, за которым следует чисто для строки в кавычках. Последовательности в строках, начинающиеся с обратной дробной черты, например \n
, обрабатываются функцией backslash
:
yylex /* hoc6 */
...
if (c == '$') { /* argument? */
int n = 0;
while (isdigit(c=getc(fin)))
n = 10 * n + c — '0';
ungetc(с, fin);
if (n == 0)
execerror("strange $...", (char*)0);
yylval.narg = n;
return ARG;
}
if (c == '"') { /* quoted string */
char sbuf [100], *p, *emalloc;
for (p = sbuf; (c=getc(fin)) != '"'; p++) {
if (с == '\n' || c == EOF)
execerror("missing quote", "");
if (p >= sbuf + sizeof (sbuf) - 1) {
*p = '\0';
execerror("string too long", sbuf);
}
*p = backslash(c);
}
*p = 0;
yylval.sym = (Symbol*)emalloc(strlen(sbuf)+1);
strcpy(yylval.sym, sbuf);
return STRING;
}
...
backslash(c) /* get next char with \'s interpreted */
int c;
{
char *index; /* 'strchr' in some systems */
static char transtab[] = "b\bf\fn\nr\rt\t";
if (c != '\\')
return c;
c = getc(fin);
if (islower(c) && index(transtab, c))
return index(transtab,
с)[1];
return c;
}
Лексический анализатор является примером конечного автомата независимо от того, написан ли он на Си или получен с помощью порождающей программы типа
lex
. Наша первоначальная версия Си программы стала весьма сложной, и поэтому для всех программ, больших ее по объему, лучше использовать lex
, чтобы максимально упростить внесение изменений. Остальные изменения сосредоточены главным образом в файле
code.c
, хотя несколько имен функций добавляется к файлу hoc.h
. Машина остается той же, но с дополнительным стеком для хранения последовательности вложенных вызовов функций и процедур (проще ввести второй стек, чем загружать больший объем информации в существующий). Начало файла code.c
выглядит так:
$ cat code.c
#include "hoc.h"
#include "y.tab.h"
#include <stdio.h>
#define NSTACK 256
static Datum stack[NSTACK]; /* the stack */
static Datum *stackp; /* next free spot on stack */
#define NPROG 2000
Inst prog[NPROG]; /* the machine */
Inst *progp; /* next free spot for code generation */
Inst *pc; /* program counter during execution */
Поделиться с друзьями: