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

в 8:34, , рубрики: forth, open source, Программирование, метки:

Пусть имеется некоторый проект на языке Форт, в котором используется достаточно большое число однотипных переменных.
Например: 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   ; 

Автор: V2008n

Источник

Поделиться

* - обязательные к заполнению поля