2.5410306
exp(2*3*log(1.5))
2.5410306
sin(PI/2)
1
atan(1)*DEG
45
Несколько улучшилась и работа распознавателя. В
hoc2
присваивание
x = expr
не только вызывало присваивание, но и приводило к печати значения, поскольку все выражения печатаются:
$ hoc2
x=2*3.14159
6.28318
В случае присваивания переменной значение печатается
В программе
hoc3
проводится различие между присваиваниями и выражениями; значения печатаются только для выражений:
$ hoc3
x=2*3.14159
Присваивание: значение не печатается
x
Выражение:
6.28318
Значение печатается
Получившаяся в результате всех этих изменений программа настолько велика (около 250 строк текста), что для простоты редактирования и ускорения компиляции лучше разбить ее на отдельные файлы. Итак, теперь мы имеем пять файлов вместо одного:
hoc.y
| грамматика, main , yylex (как и прежде); |
hoc.h
| глобальные структуры данных для включения в другие файлы; |
symbol.c
| функции, работающие с таблицей имен: lookup , install ; |
unit.c
| встроенные функции и константы; init ; |
math.c
| функции для вызова стандартных математических функций: Sqrt , Log и т.д. |
Необходимо более детально познакомиться с работой Си программы, состоящей из нескольких файлов, и программы
make
, чтобы переложить на нее часть своих обязанностей.
Вернемся снова к программе
make
и рассмотрим вначале структуру таблицы имен. Поименованный объект имеет имя, тип (
VAR
или
BLTIN
) и значение. Так, объект типа
VAR
имеет значение
double
; если объект является встроенным, то его значением служит указатель на функцию, возвращающую
double
. Данная информация используется в
hoc.y
,
symbol.c
и
init.c
. Ее можно размножить в трех экземплярах, но тогда легко ошибиться или забыть исправить один из экземпляров при внесении изменений. Вместо этого мы поместили общую информацию в файл макроопределений
hoc.h
, который можно включить при необходимости в любой файл. (Окончание
.h
традиционно, но не контролируется никакими программами.) В файл
makefile
также добавлены сведения о зависимости исходных файлов от
hoc.h
, чтобы при изменении
hoc.h
была проведена требуемая перекомпиляция.
$ cat hoc.h
typedef struct Symbol { /* symbol table entry */
char *name;
short type; /* VAR, BLTIN, UNDEF */
union {
double val; /* if VAR */
double (*ptr)(); /* if BLTIN */
} u;
struct Symbol *next; /* to link to another */
} Symbol;
Symbol *install(), *lookup();
$
Тип
UNDEF
обозначает
VAR
, которой пока не присвоили значения. Объекты связаны в список с помощью элемента
next
в записи
Symbol
. Сам список является локальным для
symbol.c
, доступ к нему возможен только посредством функций
lookup
и
install
. Это позволяет в случае необходимости легко менять структуру таблицы имен (что мы уже сделали однажды). Функция
lookup
отыскивает в списке заданное имя и возвращает указатель на
Symbol
, если имя найдено, и 0 в противном случае. Таблица имен рассчитана на линейный поиск, что вполне допустимо для диалогового калькулятора, поскольку поиск имен выполняется не во время его работы, а в процессе разбора. Функция
install
вносит переменную и связанные с ней тип и значение в начало списка. Функция
emalloc
обращается к стандартной функции размещения
malloc
(см. справочное руководство по
malloc(3)
) и проверяет результат. Указанные три функции составляют содержимое файла
symbol.c
. Файл
y.tab.h
создается при выполнении команды
yacc -d
; он содержит операторы
#define
, порождаемые
yacc
для лексем
NUMBER
,
VAR
,
BLTIN
и т.д.
$ cat symbol.c
#include "hoc.h"
#include "y.tab.h"
static Symbol *symlist = 0; /* symbol table: linked list */
Symbol *lookup(s) /* find s in symbol table */
char *s;
{
Symbol *sp;
for (sp = symlist; sp != (Symbol*)0; sp = sp->next)
if (strcmp(sp->name, s) == 0)