По мере увеличения исходного текста программы
hoc
возрастает необходимость механически отслеживать изменения и взаимозависимости. Неоценимую помощь здесь может оказать команда
make
: она автоматизирует процесс, который иначе пришлось бы выполнять вручную (и иногда с ошибками) или создавать для этого специальный командный файл.
Мы сделаем еще две модификации в файле
makefile
. Первая связана с тем, что хотя несколько файлов и зависят от констант, определенных в yacc программе файла
y.tab.h
, нет нужды их перетранслировать, если не изменились сами константы, а изменение в тексте Си программы из файла
hoc.y
не влияет на другие файлы. В новой версии
makefile
файлы
.o
зависят от нового файла
x.tab.h
, который изменяется только при замене содержимого файла
y.tab.h
. Вторая модификация основана на том, что правило для
pr
(печать исходных файлов) зависит лишь от самих исходных файлов, а именно, печатаются только измененные файлы.
Первая модификация позволяет существенно экономить время в случае больших программ, когда грамматика постоянна, а семантические действия меняются (обычная ситуация). Второе изменение обеспечивает экономию бумаги.
Приведем
makefile
для
hoc4
:
YFLAGS = -d
OBJS = hoc.o code.o init.o math.o symbol.o
hoc4: $(OBJS)
cc $(OBJS) -lm -o hoc4
hoc.o code.o init.o symbol.o: hoc.h
code.o init.o symbol.o: x.tab.h
x.tab.h: y.tab.h
-cmp -s x.tab.h y.tab.h || cp y.tab.h x.tab.h
pr: hoc.y hoc.h code.c init.c math.c symbol.c
@pr $?
@touch pr
clean:
rm -f $(OBJS) [xy].tab.[ch]
Символ
'-'
перед командой
cmp
дает указание
make
продолжать выполнение даже в случае неудачи
cmp
; это позволяет не останавливать работу и при несуществующем файле
x.tab.h
(флаг
-s
предписывает команде
cmp
не производить вывод, но установить код завершения). Комбинация
$?
раскрывается как список элементов из правила с устаревшей версией. К сожалению, форма записи в
makefile
слабо связана с обозначениями в интерпретаторе.
Проиллюстрируем изложенное выше на примере (в предположении, что все файлы последней версии):
$ touch hoc.y
Изменим время для файла hoc.y
$ make
yacc -d hoc.y
conflicts: 1 shift/reduce
сс -с y.tab.c
rm y.tab.c
mv y.tab.o hoc.o
cmp -s x.tab.h y.tab.h || cp y.tab.h x.tab.h
cc hoc.o code.o init.o math.o symbol.o -lm -o hoc4
$ make -n p
r Печать измененных файлов
pr hoc.y
touch pr
$
Отметим, что, кроме
hoc.y
, файлы не перетранслировались, поскольку файл
y.tab.h
остался тем же.
Упражнение 8.10
Сделайте размеры стека и массива
prog
динамическими, чтобы для
hoc4
всегда хватало объема памяти, если только ее можно получить, обращаясь к функции
malloc
.
Упражнение 8.11
Измените
hoc4
так, чтобы использовать в функции
execute
вместо вызова функций переключатель по виду операции
+
. Каково соотношение версий по размеру исходного текста и по времени выполнения? Как приблизительно их сопоставить по сложности развития и поддержания?
8.5 Этап 5: структуры управления и операции отношений
Версия
hoc5
оправдывает все затраты, связанные с созданием интерпретатора. В нее допустимо включать операторы
if-else
и
while
, аналогичные операторам языка Си, группировать операторы с помощью
{
и
}
и использовать оператор
print
. Она содержит полный набор операций отношений (
>
,
>=
, и т.д.), а также операций
AND
,
OR
,
&&
и
||
. (Две последние операции не гарантируют вычисления слева направо, хотя такой подход принят в Си; вычисляются оба условия, даже если в этом нет необходимости.)
Грамматику
hoc5
дополняют лексемы, нетерминальные символы и правила для
if
,
while
, фигурных скобок и операций отношений. Поэтому она получилась несколько больше, но не намного сложнее предыдущих версий (возможно, за исключением правил для
if
и
while
):
$ 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 */
}