12.1 Пример |
Предыдущая Содержание Следующая |
Наш калькулятор содержит таблицу символов с переменными, константами и функциями. В то время как константы и математические функции предопределены, значения всех переменных и определяемых пользователем функциональных определений теряются каждый раз, когда завершается выполнение приложения. В качестве реалистичного примера использования постоянных объектов мы добавляем в калькулятор два оператора: save сохранит некоторых или все переменные и определения функций в файлах; load вернёт их оттуда.
$ value def sqr = $ * $ def one = sqr(sin($)) + sqr(cos($)) let n = one(10) 1 save
В этом сеансе с помощью value мы определяем функции sqr() и one(), чтобы протестировать, что sin2x + cos2x ≡ 1. Кроме того, мы создаём переменную n со значением 1. save без параметров записывает эти три определения в файл value.stb.
$ value load n + one(20) 2
При новом запуске value мы можем использовать load, чтобы вернуть эти определения. Это выражение демонстрирует восстановление значения переменной и определений обоих функций. save реализована в функции синтаксического анализатора stmt(), точно так же, как let или def. Аргумента нет, необходимо сохранить все переменные и функции; поэтому мы просто передаём эту задачу в Symtab:
#define SYMTABFILE "value.stb" #define SYMBOLFILE "%s.sym"
static void * stmt (void) { void * sym, * node;
switch (token) { ... case SAVE: if (! scan(0)) { /* вся таблица символов целиком */ if (save(table, 0, SYMTABFILE)) error("cannot save symbol table"); } else /* список символов */ do { char fnm [BUFSIZ];
sprintf(fnm, SYMBOLFILE, name(symbol)); if (save(table, symbol, fnm)) error("cannot save %s", name(symbol)); } while (scan(0)); return 0;
Более сложный синтаксис мог бы позволить указать имя файла как часть save. А пока мы предопределяем SYMTABFILE и SYMBOLFILE: вся таблица символов сохраняется в value.stb, а единственный символ, подобный one, был бы сохранён в файл one.sym. Приложение управляет именем файла, то есть оно создано в parse.c и передаётся в save().
Symtab.d
% Class Symtab: Object { ... %
... int save (const _self, const Var @ entry, const char * fnm); int load (_self, Symbol @ entry, const char * fnm); %}
Symtab.dc
% Symtab save { const struct Symtab * self = cast(Symtab(), _self); FILE * fp;
if (entry) { // один символ if (! respondsTo(entry, "move")) return EOF; if (! (fp = fopen(fnm, "w"))) return EOF; puto(entry, fp); }
Единственный символ передан как entry. Нет никакого смысла в сохранении не определённых символов, констант или математических функций. Поэтому сохраняем только символы, которые поддерживают метод move(); как мы увидим позже, этот метод используется при загрузке символа из файла. save() открывает выходной файл и позволяет puto() выполнить фактическую работу. Сохранение всех символов почти какое же простое:
else { // вся таблица целиком int i; if (! (fp = fopen(fnm, "w"))) return EOF; for (i = 0; i < self -> count; ++ i) if (respondsTo(self -> buf[i], "move")) puto(self -> buf[i], fp); } return fclose(fp); // 0 или EOF }
save() определена как метод Symtab, поскольку необходим цикл по элементам в .buf[]. Тест, должен ли символ быть сохранён в файле, не повторяется за пределами save(). Однако, Symtab не должен знать, какие виды символов мы имеем. Поэтому решение о сохранении основывается на наличии move(), а не на членстве в определённых подклассах Symbol. Похоже, что загрузка должна быть полностью симметричной к сохранению. Снова, parse.c принимает решение об имени файла и позволяет load() выполнить фактическую работу:
case LOAD: if (! scan(0)) { /* вся таблица символов целиком */ if (load(table, 0, SYMTABFILE)) error("cannot load symbol table"); } else /* список символов */ do { char fnm [BUFSIZ];
sprintf(fnm, SYMBOLFILE, name(symbol)); if (load(table, symbol, fnm)) error("cannot load %s", name(symbol)); } while (scan(0)); reclaim(Node(), sunder); return 0;
К сожалению, load() сильно отличается от save(). Есть две причины: необходимо по крайней мере попытаться защитить наш калькулятор от кого-то, кто переделает имена файлов или содержание; и хотя save() может просто вывести на экран что-то из таблицы символов с помощью puto(), довольно вероятно, что потребуется добавить или изменить символы в этой таблице в ходе работы save(). Возвращение постоянных объектов очень похоже на первоначальное выделение им памяти и конструирование. Давайте посмотрим на работу load(). Если должен быть загружен единственный символ, его имя уже находится в таблице символов. Поэтому мы ищем или не определённый Symbol или символ, знающий как ответить на move():
% Symtab load { struct Symtab * self = cast(Symtab(), _self); const char * target = NULL; FILE * fp; int result = EOF; void * in;
if (entry) if (isOf(entry, Symbol()) || respondsTo(entry, "move")) target = name(entry); else return EOF;
Если entry существует, начальная проверка предохраняет нас от напрасной работы. Затем обращаемся к файлу и пытаемся прочитать из него столько символов, сколько сможем:
if (! (fp = fopen(fnm, "r"))) return EOF;
while (in = retrieve(fp)) ... if (! target && feof(fp)) result = 0;
fclose(fp); return result; }
Если мы не ищем определенную entry, то будем рады достичь конца файла. retrieve() возвращает объект из потока; это будет обсуждаться в разделе 12.4. Тело цикла while обрабатывает по одному символу за раз. У нас большая беда, если полученный объект не знает о move(), потому что поток, вероятно, был записан не save(). Если это произошло, пора выйти из цикла и позволить load() вернуть EOF. В противном случае, если мы ищем определенную entry, пропускаем все символы с другими именами.
{ const char * nm; void ** pp;
if (! respondsTo(in, "move")) break;
if (target && strcmp(name(in), target)) continue;
Технически parse.c настраивает всё так, что файл должен либо содержать одну желаемую запись, либо всю таблицу символов, а strcmp() защищает нас от переименованных или изменённых файлов. Мы готовы занести полученный символ в таблицу символов. Вот почему load() является методом Symtab. Этот процесс очень похож на то, что делает screen(): мы предполагаем, что retrieve() позаботился динамически сохранить имя, и используем search(), чтобы определить местоположение имени в таблице. Если возвращено то же имя, мы только что прочитали новый символ, и можем просто вставить его в таблицу.
nm = name(in);
pp = search(self, & nm);
if (* pp == nm) // ещё не в таблице * pp = in;
Однако, скорее всего load получила имя нового символа для загрузки. В этом случае имя уже находится в таблице символов как не определённый Symbol с динамически сохранённым именем. Мы удаляем его полностью и вставляем на его место полученный символ.
else if (isA(* pp, Symbol())) // ещё не определён { nm = name(* pp), delete(* pp), free((void *) nm); * pp = in; } // можно освободить значение, но затем ниже мы выходим
Если мы достигаем этой точки, значит встретились с существующим символом, то есть переменная получает новое значение или переопределяется функция. Однако, мы перезаписываем только такой символ, который знает о move(), чтобы защититься от изменения содержимого нашего входного файла.
else if (! respondsTo(* pp, "move")) { nm = name(in); delete(in); free((void *) nm); continue; // не должно произойти } else { move(* pp, in); delete(in), free((void *) nm); }
if (target) { result = 0; break; } }
Если желаемая entry найдена, можно покинуть цикл. Мы должны быть очень осторожны, чтобы не заменить существующий символ, потому что какое-нибудь выражение могло бы уже указывать на него. Вот поэтому для передачи значения от одного символа другому добавлен динамически компонуемый метод move() .
Symbol.d
% VarClass: Class Var: Symbol { ... %- void move (_self, _from); %}
Symbol.dc
% Var move { %casts setvalue(self, from -> value); }
% : Const move { // don’t respondTo move }
% Fun move { %casts setfun(self, from -> fun), from -> fun = 0; }
Var и Fun подготовлены для move(), но Const — нет. move() выполняет работу, подобную операции поверхностного копирования (shallow copy): для Fun, который указывает на выражение, она передаёт указатель, но не копирует всё выражение. move() фактически очищает исходный указатель так, чтобы исходный объект мог быть удалён без уничтожения переданного выражения.
|
Предыдущая Содержание Следующая |