FORTH: Самоопределяющиеся слова

Пусть имеется некоторый проект на языке Форт, в котором используется достаточно большое число однотипных переменных.Например: x, y, z, x', y', z', x'', y'', z'', s, m и так далее…Для их определения придется каждый раз выписывать слово VARIABLE, а это громоздко, уныло и некрасиво. Можно ли как-то сделать повеселее? Указать, что дальше будут определены переменные и выписать их имена.Что-то вроде: VARIABLES: x y z x' y' z' x'' y'' z'' s m ; VARIABLES Вспомним, что интерпретатор языка Форт реализованный в SP-Forth, если не находит слово в контекстном словаре ищет в том же контекстном словаре слово NOTFOUND, которое и исполняет. Входными параметрами NOTFOUND являются адрес и счетчик подстроки из входного потока. Значит, необходимо переопределить NOTFOUND так, чтобы оно делало то, что нам нужно.

Что же нужно? Взять это не найденное слово и скомпилировать его на вершину текущего словаря как переменную. Напомню определение

: VARIABLE CREATE 0, ;

Но слово CREATE само выбирает из входного потока следующее слово, а нам надо создать словарную статью из строки с адресом и счетчиком на стеке. Благо на такой случай есть слово CREATED, как раз и принимающее адрес и счетчик строки со стека и создающее словарную статью. К сожалению, оно, как и NOTFOUND, не входит в стандартный ANSI-94 набор слов.Таким образом : NOTFOUND (addr u —) CREATED 0, ; Но, если мы поместим такое определение в базовый список FORTH, то лишимся возможности вводить числа. Значит надо спрятать этот новый NOTFOUND в какой-то иной контекст. Заведем словарь variables. VOCABULARY variables и сделаем его текущим. ALSO variables DEFINITIONS поместим туда определение NOTFOUNDвернем текущий контекст

PREVIOUS DEFINITIONS

Таким образом слово VARIABLES: переключает контекст на variables и делает доступным нужный нам NOTFOUND

: VARIABLES: ALSO variables;

Закрывающее слово ; VARIABLES будет возвращать контекст. Находится оно должно, естественно, в контексте variables.То-есть, итого:

VOCABULARY variables ALSO variables DEFINITIONS

: NOTFOUND (addr u —) CREATED 0, ; : ; VARIABLES PREVIOUS;

PREVIOUS DEFINITIONS

: VARIABLES: ALSO variables;

Вот так, буквально в четыре строки мы расширили интерпретатор SP-Forth и упростили себе описание переменных.Но ведь аналогичный подход можно использовать и для VALUE-переменных, и констант, и вообще любых слов с общей семантикой исполнения. Тех слов, которые определяются с помощью определяющего слова. В принципе полезно иметь пары определяющих слов. Одно для единственного определения, и парное для группового определения. Собственно, определяющее слово создается ради возможности создавать группы слов с общей семантикой. И удобно если эти определения будут не размазаны по тексту, а собраны в одном блоке.Попробуем реализовать подобное для VALUE-переменных.

VOCABULARY values ALSO values DEFINITIONS : NOTFOUND …

А вот тут мы натыкаемся на некоторую неприятность. Определяющее слово VALUE не определено через CREATE. Оно определено так: : VALUE HEADER ['] _CONSTANT-CODE COMPILE, , ['] _TOVALUE-CODE COMPILE, ; На счастье слову HEADER, берущему строку из входного потока есть пара в виде слова SHEADER, синонимичного слову CREATED.Просто заменим одно на другое и получим необходимый вариант слова. : VALUED (n addr u ---) SHEADER ['] _CONSTANT-CODE COMPILE, , ['] _TOVALUE-CODE COMPILE, ; Итак:

VOCABULARY values ALSO values DEFINITIONS

: ; VALUES PREVIOUS DROP; : NOTFOUND VALUED 0;

PREVIOUS DEFINITIONS : VALUES: ALSO values 0;

Но здесь присутствует один недостаток. Все VALUE инициализируются нулем. Было бы неплохо устранить это.Вариантов реализации может быть несколько.Можно записывать просто VALUES: 11 aa 22 bb 33 cc ; VALUES Это неудобочитаемо.Попробуем писать так:

VALUES: aa = 11 bb = 22 cc = 33 ; VALUES

выглядит красиво.Очевидно, что слово «равно» должно присутствовать в контексте values. Одно должно выбирать следующее слово и интерпретировать его как число. То-есть быть почти синонимом LITERAL. Еще «равно» должно присваивать это значение последней определенной VALUE-переменной.

Пишем

VOCABULARY values ALSO values DEFINITIONS

: ; VALUES PREVIOUS DROP; : = BL WORD? LITERAL LATEST NAME> 9 + EXECUTE; : NOTFOUND VALUED 0;

PREVIOUS DEFINITIONS : VALUES: ALSO values 0;

Такой вариант

VALUES: 11 TO aa 22 TO bb 33 TO cc ; VALUES

Ценен тем, что не выпадает из парадигмы языка, кроме того позволяет инициализировать VALUE-переменные вычисляемыми значениями. VALUES: 11 TO aa 22 1980 * TO bb aa bb + TO cc ; VALUES Для его реализации не потребуется переопределять NOTFOUND. Будет изменен только смысл слова TO. Между словами-ограничителями VALUES: ; VALUES TO должно действовать как обычное VALUE. VOCABULARY values ALSO values DEFINITIONS

: ; VALUES PREVIOUS; : TO VALUE;

PREVIOUS DEFINITIONS : VALUES: ALSO values; Можно сделать аналогичный способ записи и для констант.

CONSTANTS: 11 IS aa 22 1980 * IS bb aa bb + IS cc ; CONSTANTS

Реализация этого способа, думаю, очевидна.Вообще этот поход формирует новый тип определяющих слов — групповые определяющие слова. Простое определяющее слово позволяет создавать слова, объединенные общей семантикой. Групповые обладая тем же свойством, требуют концентрировать определения однотипных слов в одной части исходного текста. Что позитивно влияет на его читаемость и сопровождение.Существенно более приятным дополнением SP- SP-Forth может стать групповая реализация слова WINAPI:. В частности, в библиотеке Winctl определения WINAPI: разбросаны по всему тексту, что выглядит бардачно.Как вариант:

WINAPIS: LIB: USER32.DLL PostQuitMessage PostMessageA SetActiveWindow LIB: GDI32.DLL CreateFontA GetDeviceCaps DeleteDC LIB: COMCTL32.DLL InitCommonControlsEx ; WINAPIS

Чтобы это сделать, подглядим как реализовано слово WINAPI: spf_win_defwords.f : __WIN: (params «ИмяПроцедуры» «ИмяБиблиотеки» —) HERE >R 0, \ address of winproc 0, \ address of library name 0, \ address of function name , \ # of parameters IS-TEMP-WL 0= IF HERE WINAPLINK @ , WINAPLINK! (связь) THEN HERE DUP R@ CELL+ CELL+ ! PARSE-NAME CHARS HERE SWAP DUP ALLOT MOVE 0 C, \ имя функции HERE DUP R> CELL+ ! PARSE-NAME CHARS HERE SWAP DUP ALLOT MOVE 0 C, \ имя библиотеки LoadLibraryA DUP 0= IF -2009 THROW THEN \ ABORT» Library not found» GetProcAddress 0= IF -2010 THROW THEN \ ABORT» Procedure not found» ;

: WINAPI: («ИмяПроцедуры» «ИмяБиблиотеки» —) (Используется для импорта WIN32-процедур. Полученное определение будет иметь имя «ИмяПроцедуры». Поле address of winproc будет заполнено в момент первого выполнения полученной словарной статьи. Для вызова полученной «импортной» процедуры параметры помещаются на стек данных в порядке, обратном описанному в Си-вызове этой процедуры. Результат выполнения функции будет положен на стек. ) NEW-WINAPI? IF HEADER ELSE -1 >IN @ HEADER >IN! THEN ['] _WINAPI-CODE COMPILE, __WIN: ; Судя по всему реализуется отложенная загрузка DLL. В словарную статью с названием импортируемой функции компилируется ссылка на код вызова WinAPI, далее некоторые параметры и затем название файла библиотеки и процедуры в ней. Далее происходит валидация наличия такого файла и такой процедуры.Для того, чтобы переделать этот код под наши пожелания, определимся с тем, что будет делать каждое слово.; WINAPIS — просто восстанавливает контекст.LIB: — вводит из входного потока следующее слово и запоминает его во временном буфере. Можно совместить с валидацией.Остальные слова воспринимаются как имена процедур.Итак:

string to stack.f SP@ VALUE spstore : sp-save SP@ TO spstore; : sp-restore spstore SP! ; : s-allot (n bytes — addr) sp-save spstore SWAP — ALIGNED DUP >R CELL- CELL- SP! R>; : s-s ( — addr u) NextWord 2>R R@ s-allot DUP DUP R@ + 0! 2R> >R SWAP R@ CMOVE R>; : s-free spstore CELL+ SP! ; : 3DUP 2 PICK 2 PICK 2 PICK; winapis.f VOCABULARY winlibs ALSO winlibs DEFINITIONS

: ; WINAPIS s-free PREVIOUS;

: LIB: ( — addr u id) s-free s-s CR OVER LoadLibraryA DUP 0= IF -2009 THROW THEN;

: NOTFOUND (addr u id addr u — addr u id) 2>R 3DUP 2R> 2DUP SHEADER ['] _WINAPI-CODE COMPILE, HERE >R 0, \ address of winproc 0, \ address of library name 0, \ address of function name -1, \ # of parameters IS-TEMP-WL 0= IF HERE WINAPLINK @ , WINAPLINK! (связь) THEN HERE DUP R@ CELL+ CELL+ ! >R CHARS HERE SWAP DUP ALLOT MOVE 0 C, R> \ имя функции HERE R> CELL+ ! 2>R CHARS HERE SWAP DUP ALLOT MOVE 0 C, 2R> \ имя библиотеки SWAP GetProcAddress 0= IF -2010 THROW THEN \ ABORT» Procedure not found» ;

PREVIOUS DEFINITIONS

: WINAPIS: sp-save 1 2 3 ALSO winlibs;

© Habrahabr.ru