выбираются для получения значения или присваивания с помощью функции
getarg
, которая следит за сбалансированностью стека:
double *getarg /* return pointer to argument */
{
int nargs = (int)*pc++;
if (nargs > fp->nargs)
execerror(fp->sp->name, "not enough arguments");
return &fp->argn[nargs - fp->nargs].val;
}
arg /* push argument onto stack */
{
Datum d;
d.val = *getarg;
push(d);
}
argassign /* store top of stack in argument */
{
Datum d;
d = pop;
push(d); /* leave value on stack */
*getarg = d.val;
}
Функции
prstr
и
prexpr
печатают строки и числа:
prstr /* print string value */
{
printf("%s", (char*)*pc++);
}
prexpr /* print numeric value */
{
Datum d;
d = pop;
printf("%.8g d.val);
}
Функция
varread
читает переменные. Она возвращает 0 при обнаружении конца файла и 1 — в противном случае, а также устанавливает значение указанной переменной:
varread /* read into variable */
{
Datum d;
extern FILE *fin;
Symbol *var = (Symbol*)*pc++;
Again:
switch (fscanf(fin, "%lf", &var->u.val)) {
case EOF:
if (moreinput)
goto Again;
d.val = var->u.val = 0.0;
break;
case 0:
execerror("non-number read into", var->name);
break;
default:
d.val = 1.0;
break;
}
var->type = VAR;
push(d);
}
Обнаружив
конец файла для текущего входного потока, функция
varread
обратится к
moreinput
, которая откроет следующий файл, заданный в качестве аргумента (если он есть). В функции
moreinput
обработка входной информации имеет некоторые нюансы, здесь не рассматриваемые; речь о них идет в приложении 3.
Итак, мы завершили разработку программы
hoc
. Для сравнения приведем число непустых строк в каждой версии:
hoc1
59
hoc2
94
hoc3
248 (для версии с
lex
229)
hoc4
396
hoc5
574
hoc6
809
Конечно, эти значения были вычислены программным способом: $
sed '/$/d' `pick *.[chyl]` | wc -l
Безусловно, развитие языка может быть продолжено, и вам предоставляется такая возможность в приведенных ниже упражнениях.
Упражнение 8.18
Измените
hoc6
так, чтобы можно было использовать поименованные формальные параметры в подпрограммах вместо
$1
и т.д.
Упражнение 8.19
Сейчас все переменные глобальны, за исключением параметров. Уже есть большая часть механизма для введения локальных переменных, хранимых в стеке. Одно из решений заключается во введении описания
auto
, которое резервирует место в стеке для перечисленных переменных; не перечисленные переменные считаются глобальными. Кроме того, придется расширить таблицу имен так, чтобы поиск в ней осуществлялся вначале для локальных, а затем для глобальных переменных. Как это связано с поименованными аргументами?
Упражнение 8.20
Как бы вы ввели массивы в язык
hoc
? Как следует передавать их функциям и процедурам? Как возвращать их?
Упражнение 8.21
Обобщите работу со строками так, чтобы переменные могли хранить строки, а не только числа. Какие операции потребуются для этого? Самая трудная часть управление памятью добейтесь динамичного хранения строк: память должна освобождаться, когда строки перестают быть нужными. В качестве промежуточного шага добавьте более развитые форматы печати, например, обеспечьте возможность использования некоторых форм стандартной Си функции
printf
.
8.7 Оценка времени выполнения
Мы сравнивали
hoc
с другими программами-калькуляторами UNIX, чтобы приблизительно оценить, насколько хорошо он работает. К таблице, представленной ниже (табл. 8.1), можно, конечно, отнестись скептически, но она показывает "разумность" нашей реализации. Все приведенные в ней величины даны в секундах. Работа велась на PDP-11/70. Было выполнено два теста. Первый, вычисление функции Аккерманна
ack(3,3)
, — хороший тест для отработки механизма вызова функций. Здесь происходят 2432 вызова, причем некоторые из них достаточно глубоко вложены.