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   ; 
    




    Share post
    AdBlock has stolen the banner, but banners are not teeth — they will be back

    More
    Ads

    Comments 10

      0
      К сожалению, все это завязано на SP-Forth
        0
        В Gforth для подобного можно использовать обработку исключений CATCH… THROW. Собственно, NOTFOUND является некоторой надстройкой над базовым механизмом исключений.
          +1
          Я конечно давно не брал в руки шашку, лет 15ть уже.

          Но простите — а нечто вроде такого — не комильфо?

          : variables 
              parse-name dup s" END" strcmp not if nextname create 0 , then ; immediate
          
          

          и пишем стандартно

          variables a b c d e f END
          

            0
            Да, не комильфо :) Вообще не будет работать. Во-первых, strcmp — функция сишная, во-вторых nextname как такового в Форте нет. В-третьих, будет скомпилирована лишь первая переменная.
              0
              Ай, цикл забыл. Посыпаю лысину пеплом.

              SP-Forth я вообще в глаза не видел, в первой половине 1990х там другие игрушки были. С тех пор кстати у меня идиосинкразия на «кричащие» фортовсеие идентификаторы. В своих собственноручно налепленных интертрепаторах из принципа все писал в lowercase ;-)

              Но nextname ранее реализовывался на раз — подсовыванием токена в pad, то бишь входной поток. Или подсовыванием своего эрзаца входного потока. Вот мне стало интересно — а что, такая техника уже не работает?

              : variables( 
                  begin 
                      parse-name dup s" )" =s not dup 
                  while
                      if nextname create 0 ,  then 
                  repeat
                  drop 
              ; immediate
              
              variables( a b c d e f )
              


              Кстати, в gforth nextname есть. Не поленился глянуть — и на самом деле входной поток подменяет.
                0
                Форт прекрасен тем, что позволяет обходиться без циклов. Ваше решение, безусловно, имеет право на жизнь. Но оно совершенно в лоб, в духе традиционных языков. Кроме того, я предлагаю не просто реализацию определения кучки переменных, а некий универсальный механизм, базовый принцип, с помощью которого можно решать разнообразные задачи. Заметил, что очень мало людей должно оценивают красоту и мощь определяющих слов, в которых скрыта суть Форта.
                Сможете использовать аналогичный подход для, например, констант? А чтобы можно было константе присвоить вычисляемое значение?
            0
            Судя по всем автор даже не запускал вариант:
            VOCABULARY values
            ALSO values DEFINITIONS
            
            : ;VALUES   PREVIOUS DROP ;
            : NOTFOUND   VALUED 0 ;
            
            PREVIOUS DEFINITIONS 
              
            : VALUES:  ALSO values 0 ;
            

            И если потом попробовать сделать вот так:
            VALUES:
              11 aa   
              22 bb 
              33 cc
            ;VALUES
            

            То все будет вроде бы ок. Но вот только в переменных будут нули. А еще будут переменные 11 22 и 33. А все потому, что в СПФ обработка чисел тоже идет через NOTFOUND. Т.е. NOTFOUND будет вызван 6 раз, а не три — т.е. и для каждого числа тоже.
              0
              И вообще, обычно обработка неизвестных лексем через NOTFOUND делается несколько по-другому: лексеме делается проверка типа «а это точно наша лексема?» и если это не так, то она передается дальше по цепочке предыдущему NOTFOUND.
                0
                Есть такое пожелание. Вот только зачем это делать? Только потому, что разработчики системы так сказали? Какой предыдущий нотфаунд надо вызывать, если никакого другого нотфаунда в данном контексте не предусмотрено? Поведение «нашей лексемы» в предлагаемом мной варианте использования нотфаунда заключается в создании этой лексемы. То-есть, она всегда «наша». Все очень красиво получается, все оттестировано и работает.
                0
                Вы невнимательно читали. Вариант
                VALUES:
                11 aa
                22 bb
                33 cc
                ;VALUES
                Я не реализовывал вообще. Ввиду его бесперспективности. А так, да, конечно, если с нулевым вариантом использовать отброшенный вариант записи, эффект будет такой.

              Only users with full accounts can post comments. Log in, please.