Управление словарными статьями
Страница 1 из 1
Управление словарными статьями
Насколько просты и понятны слова, компилирующие шитый код, настолько неестественными кажутся слова, создающие заголовки словарных статей.
(Мур даже ввел для них специальное сложное кодовое слово ENTRY).
По Баранову и Ноздрунову - ТЕМА #120, АБЗАЦ #2029
***
Первая сложность: заголовок слова и шитый код слова могут лежать в разных областях памяти. Это может быть вызвано оптимизацией хранения или желанием сэкономить место при целевой компиляции (имена слов в исполняемом файле могут и не понадобится), или, даже, какими-нибудь свойствами ЭВМ.
Более того, даже разные части заголовков могут лежать в разных областях памяти. Из тех же соображений. (Например, Мур рассматривал возможность хранить отдельно части заголовков фиксированной и переменной длины).
Следствие таких разночтений - нечитабельность кода ENTRY. Начинающему может показаться, что там присутствует некий сокровенный смысл, а не просто код, создающий типовой набор полей наиболее простым способом.
***
Вторая сложность: количество и содержимое полей заголовка могут сильно варьироваться. Типовыми считаются:
- Поле признаков/флагов - содержит самый главный признак слова - признак немедленного исполнения (IMMEDIATE), часто добавляется признак SMUDGE - того, что слово еще недокомпилировалось. Обычно совмещают с байтом длины имени (входит в поле имени - NF), длину обычно ограничивают 32 байтами, т.о. остается место под три битовых флага/признака. Такое совмещение позволяет отдельно не проверять флаг SMUDGE - его установка портит длину слова и FIND его не найдет.
- Поле имени (NF) - в классическом случае - строка со счетчиком. Возможны самые разные "оптимизации" - хранение завершающего пробела/нулевого символа, отдельное/исключительное хранение части имени фиксированной длины (для удобства сравнения по-словно, а не по-байтово), упаковка символов, хеширование...
Так как главная функция СЛОВАРЯ - обеспечить главное свойство слов - связь "имя-код", имя обязано присутствовать, даже если оно совсем не похоже на имя в его классической, текстовой форме.
(Конечно, в FORTH, как в PostScript или SmallTalk можно создавать неименованные куски шитого кода, есть даже специальное слово - NONAME, и такие куски, скорее всего, будут храниться в СЛОВАРЕ, но называть их словарными статьями нельзя. Лучше называть их, по предложению Мура, псевдо-статьями).
- Поле связи (LF) - поле нужное FIND для перебора всего списка слов. Классически - содержит адрес предыдущей словарной статьи. Если FIND ищет другим способом, это поле может выглядеть совершенно иначе. Например, если словарные статьи имеют фиксированный размер, то можно перейти к предыдущему слову без всякой ссылки (предложение Мура). А, если имеет место хеширование списка слов, то надо будет привязать слово не просто к предыдущему, но к предыдущему в нужном списке (тоже Мур).
- Поле кода (CF) - самое главное в слове, оно всегда есть и оно всегда одно (чтобы гарантировать однозначность связи "имя-код"). Существует всего два варианта хранения кода: сам код (подпрограммный или прямой шитый код, внутреннее управление) и ссылка на код (косвенный шитый код, внешнее управление).
В общем смысле это вещи эквивалентные, поэтому оба варианта считаются FORTH-овскими, но поклонники разных шитых кодов готовы до последнего отстаивать именно свой вариант (иногда ЭВМ более приспособлена для одного вида кода, чем для других).
- Поле параметров (PF) - это универсальная затычка, позволяющая хранить все, что угодно. Самое главное назначение - хранить сам шитый код (в CF только его запускатель или ссылка на его интерпретатор). Но возможны и другие варианты: дополнительные точки входа в слово, данные слова, даже, код (например в косвенном шитом коде, его и хранить-то больше негде). В пределах одной FORTH-системы обычно принимается пяток вариантов, который считается правильным/достаточным (кодовые слова, шитые слова, CREATE-слова, DOES>-слова, QUAN-слова...), а остальные объявляются избыточными.
***
Третья сложность: в одной FORTH-системе могут одновременно существовать по-разному устроенные СЛОВАРИ.
Иногда это необходимо: для реализации специфического проблемно-ориентированного языка, работы с нестандартными ПОТОКАМИ, целевой компиляции, использовании СЛОВАРЯ в качестве структуры данных (т.е. одним словом, для создания вторичной FORTH-машины).
Иногда же авторам просто нравится рассматривать лексиконы, как некие объектно-ориентированные сущности, в которых можно инкапсулировать не только нестандартные словарные статьи (притягивая, например, к стандартно-словарному виду внешние источники кода/данных), но и позволять каждому лексикону СЛОВАРЯ иметь свою собственную копию процедуры СИМВОЛ.
(Мур даже ввел для них специальное сложное кодовое слово ENTRY).
По Баранову и Ноздрунову - ТЕМА #120, АБЗАЦ #2029
***
Первая сложность: заголовок слова и шитый код слова могут лежать в разных областях памяти. Это может быть вызвано оптимизацией хранения или желанием сэкономить место при целевой компиляции (имена слов в исполняемом файле могут и не понадобится), или, даже, какими-нибудь свойствами ЭВМ.
Более того, даже разные части заголовков могут лежать в разных областях памяти. Из тех же соображений. (Например, Мур рассматривал возможность хранить отдельно части заголовков фиксированной и переменной длины).
Следствие таких разночтений - нечитабельность кода ENTRY. Начинающему может показаться, что там присутствует некий сокровенный смысл, а не просто код, создающий типовой набор полей наиболее простым способом.
***
Вторая сложность: количество и содержимое полей заголовка могут сильно варьироваться. Типовыми считаются:
- Поле признаков/флагов - содержит самый главный признак слова - признак немедленного исполнения (IMMEDIATE), часто добавляется признак SMUDGE - того, что слово еще недокомпилировалось. Обычно совмещают с байтом длины имени (входит в поле имени - NF), длину обычно ограничивают 32 байтами, т.о. остается место под три битовых флага/признака. Такое совмещение позволяет отдельно не проверять флаг SMUDGE - его установка портит длину слова и FIND его не найдет.
- Поле имени (NF) - в классическом случае - строка со счетчиком. Возможны самые разные "оптимизации" - хранение завершающего пробела/нулевого символа, отдельное/исключительное хранение части имени фиксированной длины (для удобства сравнения по-словно, а не по-байтово), упаковка символов, хеширование...
Так как главная функция СЛОВАРЯ - обеспечить главное свойство слов - связь "имя-код", имя обязано присутствовать, даже если оно совсем не похоже на имя в его классической, текстовой форме.
(Конечно, в FORTH, как в PostScript или SmallTalk можно создавать неименованные куски шитого кода, есть даже специальное слово - NONAME, и такие куски, скорее всего, будут храниться в СЛОВАРЕ, но называть их словарными статьями нельзя. Лучше называть их, по предложению Мура, псевдо-статьями).
- Поле связи (LF) - поле нужное FIND для перебора всего списка слов. Классически - содержит адрес предыдущей словарной статьи. Если FIND ищет другим способом, это поле может выглядеть совершенно иначе. Например, если словарные статьи имеют фиксированный размер, то можно перейти к предыдущему слову без всякой ссылки (предложение Мура). А, если имеет место хеширование списка слов, то надо будет привязать слово не просто к предыдущему, но к предыдущему в нужном списке (тоже Мур).
- Поле кода (CF) - самое главное в слове, оно всегда есть и оно всегда одно (чтобы гарантировать однозначность связи "имя-код"). Существует всего два варианта хранения кода: сам код (подпрограммный или прямой шитый код, внутреннее управление) и ссылка на код (косвенный шитый код, внешнее управление).
В общем смысле это вещи эквивалентные, поэтому оба варианта считаются FORTH-овскими, но поклонники разных шитых кодов готовы до последнего отстаивать именно свой вариант (иногда ЭВМ более приспособлена для одного вида кода, чем для других).
- Поле параметров (PF) - это универсальная затычка, позволяющая хранить все, что угодно. Самое главное назначение - хранить сам шитый код (в CF только его запускатель или ссылка на его интерпретатор). Но возможны и другие варианты: дополнительные точки входа в слово, данные слова, даже, код (например в косвенном шитом коде, его и хранить-то больше негде). В пределах одной FORTH-системы обычно принимается пяток вариантов, который считается правильным/достаточным (кодовые слова, шитые слова, CREATE-слова, DOES>-слова, QUAN-слова...), а остальные объявляются избыточными.
***
Третья сложность: в одной FORTH-системе могут одновременно существовать по-разному устроенные СЛОВАРИ.
Иногда это необходимо: для реализации специфического проблемно-ориентированного языка, работы с нестандартными ПОТОКАМИ, целевой компиляции, использовании СЛОВАРЯ в качестве структуры данных (т.е. одним словом, для создания вторичной FORTH-машины).
Иногда же авторам просто нравится рассматривать лексиконы, как некие объектно-ориентированные сущности, в которых можно инкапсулировать не только нестандартные словарные статьи (притягивая, например, к стандартно-словарному виду внешние источники кода/данных), но и позволять каждому лексикону СЛОВАРЯ иметь свою собственную копию процедуры СИМВОЛ.
Последний раз редактировалось: Gudleifr (Сб Мар 20, 2021 1:22 pm), всего редактировалось 1 раз(а)
Gudleifr- Admin
- Сообщения : 3251
Дата регистрации : 2017-03-29
Re: Управление словарными статьями
КАК ЭТО СДЕЛАНО В FOBOS
Самым простым из вожможных способов. На этапе 0 было принята такое начало статьи (NF-LF):
Некоторые предлагают размещать первым полем LF (туда пишется Link), якобы так быстрее перебирать слова, но т.к. в FIND-перебор входит проверка соответствия имен слов, то ставить впереди NF (строка name) или LF было бы делом исключительно вкуса, но... Ведь, проще всего разместить буфер WORD именно на вершине словаря, и т.о. при чтении имени создаваемой статьи мы автоматически получаем там готовое поле NF. Остается только дописать к нему остальные поля.
Признаки стандартно запихиваю в байт длины (length).
Поле кода (CF) начинается сразу за эаголовком - и метка WordLabel автоматически на него указывает.
Очевидно, можно было бы что-то выиграть, добавив выравнивание слов и/или введением завершающего имя символа, но в моем проекте это без надобности, т.к. основной источник торможения здесь - Windows, я в принципе не смогу написать что-то столь же тормозное.
Слово, создающее этот заголовок у меня получилось таким:
Т.е., если перевести с языка ассемблера на FORTH:
Т.е. сначала находим Link, затем регистрируем в текущем лексиконе адрес начала статьи (Link = Link2), потом, очевидно, WORD, перевод имени в верхний регистр и перенос указателя вершины словаря, наконец, запись в LF Link.
***
Как устроено CF. Я исторически использую прямой шитый код, поэтому, здесь просто код (а не ссылка на него). В кодовых словах т.о. можно лепить все, что угодно, надо лишь сохранять FORTH-регистры и в конце ставить макрос Next (СЛЕДУЮЩИЙ).
В случае шитого слова код стандартен:
Кто читал Баранова и Ноздрунова без труда узнает, обычную процедуру CALL.
Шитый код (PF) начинается с метки Start и, очевидно, должен заканчиваться прошивкой адреса кода слова EXIT:
***
Неприятности начинаются только тогда, когда PF начинает "плавать" в зависимости от длины CF.
Например, слова, созданные CREATE, и слова, созданные CREATE ... DOES> должны иметь разный код, но место-то под CF резервируется в CREATE. Ранее даже использовалось дополнительное слово: вместо CREATE ... DOES> писали <BUILDS ... DOES> . Пришлось резервировать сразу максималную длину:
Т.е. компилируем код "push ebx; mov ebx, OFFSET Data; lodsd; jmp eax" и резервируем 4 байта под будующий DOES> (и даже заранее для него ставим второй NEXT,).
Т.к. DOES> должен, как всякий уважающий себя IMMEDIATE работать и на этапе компиляции слова и при его исполнении, то естественно разделить его на две части - что исполнять, а что компилировать.
Исполняющаяся часть завершает компиляцию слова и переходит к компиляции того, что будет компилироваться в создаваемые словом слова (т.е. того, что после DOES>).
Компилирующаяся же часть как раз заменяет код, созданный CREATE начиная с 6-го байта (т.е. затирая первый NEXT,) на "mov [ebp], esi; add ebp, 4; mov esi, Start; lodsd; jmp eax", где метка Start обозначает то, что будет после DOES> - "R@ 4 +".
***
Также плавающий размер CF приводит к тому, что для доступа к PF нужно использовать разные слова:
Может показаться, что в косвенном шитом коде, где вместо кода имеем дело с ссылками на него, все намного проще, но запутаться в косвенных адресациях тоже можно запросто.
***
Можно ли это как-то записать формально? Как бы подразумевается, что когда вы дорастете до применения подобных конструкций, вам не составит труда это понять. Мол, "создать слово, создающие слова, которые будут отличаться набором данных, но будут исполнять одинаковый код" означает
а потом, всего лишь, найти способ разрешения всех связанных с этим процессом ссылок.
Самым простым из вожможных способов. На этапе 0 было принята такое начало статьи (NF-LF):
- Код:
Link = 0
Header MACRO length, name, WordLabel
Link2 = $
DB length
DB name
DD Link
Link = Link2
WordLabel:
ENDM
Некоторые предлагают размещать первым полем LF (туда пишется Link), якобы так быстрее перебирать слова, но т.к. в FIND-перебор входит проверка соответствия имен слов, то ставить впереди NF (строка name) или LF было бы делом исключительно вкуса, но... Ведь, проще всего разместить буфер WORD именно на вершине словаря, и т.о. при чтении имени создаваемой статьи мы автоматически получаем там готовое поле NF. Остается только дописать к нему остальные поля.
Признаки стандартно запихиваю в байт длины (length).
Поле кода (CF) начинается сразу за эаголовком - и метка WordLabel автоматически на него указывает.
Очевидно, можно было бы что-то выиграть, добавив выравнивание слов и/или введением завершающего имя символа, но в моем проекте это без надобности, т.к. основной источник торможения здесь - Windows, я в принципе не смогу написать что-то столь же тормозное.
Слово, создающее этот заголовок у меня получилось таким:
- Код:
WordCode 8, '(CREATE)', @28CRE
DD @LATES, @3ER, @HERE
DD @CURRE, @40, @21
DD @BL, @WORD, @DUP, @COUNT, @TOUPP
DD @C40, @312B, @ALLOT
DD @R3E, @2C, @EXIT
Т.е., если перевести с языка ассемблера на FORTH:
- Код:
: (CREATE) LATEST >R
HERE CURRENT @ !
BL WORD DUP COUNT TOUPPER C@ 1+ ALLOT
R> , ;
Т.е. сначала находим Link, затем регистрируем в текущем лексиконе адрес начала статьи (Link = Link2), потом, очевидно, WORD, перевод имени в верхний регистр и перенос указателя вершины словаря, наконец, запись в LF Link.
***
Как устроено CF. Я исторически использую прямой шитый код, поэтому, здесь просто код (а не ссылка на него). В кодовых словах т.о. можно лепить все, что угодно, надо лишь сохранять FORTH-регистры и в конце ставить макрос Next (СЛЕДУЮЩИЙ).
В случае шитого слова код стандартен:
- Код:
Called MACRO
LOCAL Start
mov [ebp], esi
add ebp, 4
mov esi, Start
Next
Start:
ENDM
Кто читал Баранова и Ноздрунова без труда узнает, обычную процедуру CALL.
Шитый код (PF) начинается с метки Start и, очевидно, должен заканчиваться прошивкой адреса кода слова EXIT:
- Код:
Header 4, 'EXIT', @EXIT
sub ebp, 4
mov esi, [ebp]
Next
***
Неприятности начинаются только тогда, когда PF начинает "плавать" в зависимости от длины CF.
Например, слова, созданные CREATE, и слова, созданные CREATE ... DOES> должны иметь разный код, но место-то под CF резервируется в CREATE. Ранее даже использовалось дополнительное слово: вместо CREATE ... DOES> писали <BUILDS ... DOES> . Пришлось резервировать сразу максималную длину:
- Код:
: CREATE (CREATE) 053 C, 0BB C, >MARK NEXT,
4 ALLOT 0 , NEXT, >RESOLVE ;
Т.е. компилируем код "push ebx; mov ebx, OFFSET Data; lodsd; jmp eax" и резервируем 4 байта под будующий DOES> (и даже заранее для него ставим второй NEXT,).
- Код:
: (DOES) LATEST >CFA 6 + 83007589 OVER ! 4 + 0BE04C5 OVER ! 3 +
R@ 4 + SWAP ! ;
: DOES> COMPILE (DOES) COMPILE EXIT ; IMMEDIATE
Т.к. DOES> должен, как всякий уважающий себя IMMEDIATE работать и на этапе компиляции слова и при его исполнении, то естественно разделить его на две части - что исполнять, а что компилировать.
Исполняющаяся часть завершает компиляцию слова и переходит к компиляции того, что будет компилироваться в создаваемые словом слова (т.е. того, что после DOES>).
Компилирующаяся же часть как раз заменяет код, созданный CREATE начиная с 6-го байта (т.е. затирая первый NEXT,) на "mov [ebp], esi; add ebp, 4; mov esi, Start; lodsd; jmp eax", где метка Start обозначает то, что будет после DOES> - "R@ 4 +".
***
Также плавающий размер CF приводит к тому, что для доступа к PF нужно использовать разные слова:
- Код:
: >PFA ( cfa -- pfa) 14 + ; ( ТОЛЬКО ДЛЯ СЛОВ, СОЗДАННЫХ CREATE)
: >BODY ( cfa -- wa) 0E + ; ( ТОЛЬКО ДЛЯ СЛОВ, СОЗДАННЫХ ":" )
Может показаться, что в косвенном шитом коде, где вместо кода имеем дело с ссылками на него, все намного проще, но запутаться в косвенных адресациях тоже можно запросто.
***
Можно ли это как-то записать формально? Как бы подразумевается, что когда вы дорастете до применения подобных конструкций, вам не составит труда это понять. Мол, "создать слово, создающие слова, которые будут отличаться набором данных, но будут исполнять одинаковый код" означает
- Код:
"создать слово,
создающее заголовок слова и зафигачивающее туда кусок кода,
обеспечивающий выкладывание на стек адреса данных;
затем размещающее данные, снимая их со стека;
затем изменяющее первоначально зафигаченный кусок кода так,
чтобы он после после выкладывания на стек адреса данных, перепрыгнул к нужному коду;
дописать в конец слова нужный код."
а потом, всего лишь, найти способ разрешения всех связанных с этим процессом ссылок.
Gudleifr- Admin
- Сообщения : 3251
Дата регистрации : 2017-03-29
Re: Управление словарными статьями
КРАМОЛА
По сути, СЛОВАРЬ - это тоже СТЕК. Причем, его "шитая" часть практически повторяет то, что копится в СТЕКЕ (особенно в модели Дейкстры). Так, может, их совместить?
Правда, возникнут некоторые сложности с прошивкой оборотов, но ничего непреодолимого, вроде не наблюдается. Наоборот, некоторые вещи станут проще - не придется, например, дважды проверять тип литералов (при формировании ЗНАЧЕНИЯ и при прошивке нужного для его извлечения оборота).
ТЕМА #45, АБЗАЦ #450
По сути, СЛОВАРЬ - это тоже СТЕК. Причем, его "шитая" часть практически повторяет то, что копится в СТЕКЕ (особенно в модели Дейкстры). Так, может, их совместить?
Правда, возникнут некоторые сложности с прошивкой оборотов, но ничего непреодолимого, вроде не наблюдается. Наоборот, некоторые вещи станут проще - не придется, например, дважды проверять тип литералов (при формировании ЗНАЧЕНИЯ и при прошивке нужного для его извлечения оборота).
ТЕМА #45, АБЗАЦ #450
Gudleifr- Admin
- Сообщения : 3251
Дата регистрации : 2017-03-29
Страница 1 из 1
Права доступа к этому форуму:
Вы не можете отвечать на сообщения
|
|