12.5 Подключение объектов — Возвращаемся к value |
Предыдущая Содержание Следующая |
Запись и чтение автономного набора со строгой древовидной структурой объектов может быть выполнено с помощью puto(), retrieve() и соответствующих методов geto. Наш калькулятор демонстрирует, что появляется проблема, как только записан набор объектов, который ссылается на другие объекты, записанные в различном контексте или не записанные вообще. Рассмотрим:
$ value def sqr = $ * $ def one = sqr(sin($)) + sqr(cos($)) save one
Выходной файл one.sym содержит ссылки на sqr, но не определение:
$ cat one.sym Fun at 0x50ec9f8 name one lex 102 value 10 = Add at 0x50ed168 User at 0x50ed074 Ref name sqr putsymbol lex 102 Builtin at 0x50ecfd0 Ref name sin putsymbol lex 109 Parm at 0x50ecea8 Val name one putsymbol lex 102 User at 0x50ed14c name sqr lex 102 Builtin at 0x50ed130 name cos lex 109 Parm at 0x50ed118 name one lex 102
User представляет собой Ref, и Ref_puto() использовал putsymbol() в parse.c, чтобы записать только имя символа и значение маркера. Таким образом, определение для sqr() не сохранено в one.sym умышленно. Как только ссылка таблицы символов считана, она должна быть присоединена к таблице символов. Наш калькулятор содержит единственную таблицу символов table, которая создаётся и управляется в parse.c, то есть ссылка из дерева выражения на таблицу символов должна использовать getsymbol() из parse.c, чтобы присоединить ссылку к текущей таблицу символов. Чтобы надлежащий подкласс Symbol мог быть найден или создан getsymbol(), разные виды ссылок используют разные подклассы Node. Поэтому необходимо различать Global как ссылку на Var, а Parm как ссылку на Fun там, где извлекается значение параметра.
% Global geto { struct Global * self = super_geto(Global(), _self, fp);
down(self) = getsymbol(Var(), fp); return self; }
% Parm geto { struct Parm * self = super_geto(Parm(), _self, fp);
down(self) = getsymbol(Fun(), fp); return self; }
Точно так же Assign ищет Var; Builtin ищет Math; и User ищет Fun. Все они используют getsymbol(), чтобы найти подходящий символ в table, создать новый или пожаловаться, если существует символ с правильным именем, но неправильным классом:
void * getsymbol (const void * class, FILE * fp) { char buf [BUFSIZ]; int token; void * result;
if (fscanf(fp, "\tname %s\n\tlex %d\n", buf, & token) != 2) assert(0); result = screen(table, buf, UNDEF); if (lex(result) == UNDEF) install(table, result = new(class, name(result), token)); else if (lex(result) != token) { fclose(fp); error("%s: need a %s, got a %s", buf, nameOf(class), nameOf(classOf(result))); } return result; }
Помогает то, что когда символ Fun создан, ещё нет необходимости определять выражение:
$ value load one one(10) undefined function
one() пробует вызвать sqr(), но он не определён.
let sqr = 9 bad assignment
Не определённый Symbol может быть перезаписан и присвоен, то есть sqr() действительно не определённая функция.
def sqr = $ * $ one(10) 1 def sqr = 1 one(10) 2
Вот иерархия классов калькулятора с большинством определений методов. Метаклассы были опущены; жирный шрифт указывает, где метод определён изначально.
Остаётся незначительный пробел, к которому обратимся в следующей главе: по-видимому, getsymbol() знает достаточно, чтобы закрыть поток fp до использования error(), чтобы вернуться к основному циклу.
| ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
Предыдущая Содержание Следующая |