UNIX — универсальная среда программирования - Керниган Брайан Уилсон
Шрифт:
Интервал:
Закладка:
По мере увеличения исходного текста программы 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 pr Печать измененных файлов
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 */
}