📜 ⬆️ ⬇️

FORTH: Self-Defined Words

Suppose there is some project in the Fort language in which a sufficiently large number of variables of the same type are used.
For example: x, y, z, x ', y', z ', x' ', y' ', z' ', s, m and so on ...
For their definition it is necessary to write out the word VARIABLE each time, and this is cumbersome, dull and ugly. Is it possible to somehow make more fun?
Indicate that further variables will be defined and write out their names.
Sort of:
 VARIABLES:
   xyzx 'y' z '
   x '' y '' z ''
   sm
 ; VARIABLES



Recall that the Fort interpreter implemented in SP-Forth, if it does not find the word in the context dictionary, searches for the word NOTFOUND in the same context dictionary that it executes. NOTFOUND input parameters are the address and the substring counter from the input stream. Therefore, it is necessary to redefine NOTFOUND so that it does what we need.

What is needed?
Take this not found word and compile it to the top of the current dictionary as a variable. I will remind the definition
  : VARIABLE CREATE 0,;

But the word CREATE chooses the next word from the input stream itself, but we need to create a dictionary entry from the line with the address and counter on the stack. The blessing for such a case is the word CREATED, which is precisely the one that receives the address and the row counter from the stack and creates a dictionary entry. Unfortunately, it, like NOTFOUND, is not included in the standard ANSI-94 word set.
In this way
 : NOTFOUND (addr u -) CREATED 0,; 

But, if we put such a definition in the base FORTH list, we will be unable to enter numbers. So you need to hide this new NOTFOUND in some other context. Let's get the variables dictionary.
 VOCABULARY variables 

and make it current.
 ALSO variables DEFINITIONS

put the NOTFOUND definition there
')
return current context
 PREVIOUS DEFINITIONS


Thus, the word VARIABLES: switches the context to variables and makes available the required NOTFOUND
 : VARIABLES: ALSO variables;

The closing word; VARIABLES will return the context. It should be located, of course, in the context of variables.

That is, total:
 VOCABULARY variables 
 ALSO variables DEFINITIONS

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

 PREVIOUS DEFINITIONS

 : VARIABLES: ALSO variables;

So, literally in four lines, we expanded the SP-Forth interpreter and simplified the description of variables.
But after all, a similar approach can be used for VALUE-variables, and constants, and in general any words with the general semantics of execution. Those words that are defined using the defining word. In principle, it is useful to have a pair of defining words. One for a single definition, and a pair for a group definition. Actually, the defining word is created for the sake of the ability to create groups of words with common semantics. And it is convenient if these definitions are not smeared in the text, but are collected in one block.

Let's try to implement similar for VALUE-variables.
 VOCABULARY values
 ALSO values ​​DEFINITIONS
 : NOTFOUND ...

And here we come across some trouble. VALUE is not defined by CREATE. It is defined as:
 : VALUE  
        HEADER
        ['] _CONSTANT-CODE COMPILE,,
        ['] _TOVALUE-CODE COMPILE,
 ;

Fortunately, the word HEADER, which takes a string from the input stream, has a pair in the form of the word SHEADER, synonymous with the word CREATED.
Just replace one with the other and get the desired version of the word.
 : VALUED (n addr u ---)
       SHEADER
       ['] _CONSTANT-CODE COMPILE,,
       ['] _TOVALUE-CODE COMPILE,
 ;


So:
 VOCABULARY values
 ALSO values ​​DEFINITIONS

 :; VALUES PREVIOUS DROP;
 : NOTFOUND VALUED 0;

 PREVIOUS DEFINITIONS 
  
 : VALUES: ALSO values ​​0;

But there is one drawback. All VALUEs are initialized to zero. It would be nice to eliminate this.
Implementation options may be several.
You can write just
 VALUES:
   11 aa   
   22 bb 
   33 cc
 ; VALUES

This is not readable.

Let's try to write like this:
 VALUES:
    aa = 11
    bb = 22
    cc = 33 
 ; VALUES

looks beautiful.

Obviously, the word "equal" should be present in the context of values. One must choose the next word and interpret it as a number. That is to be almost a synonym for LITERAL. Another "equal" should assign this value to the last defined VALUE variable.

We write
 VOCABULARY values
 ALSO values ​​DEFINITIONS

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

 PREVIOUS DEFINITIONS 
  
 : VALUES: ALSO values ​​0;


This option
 VALUES:
   11 TO aa   
   22 TO bb 
   33 TO cc
 ; VALUES
It is valuable because it does not fall out of the paradigm of the language, besides it allows initializing VALUE variables with calculated values.
 VALUES:
        11 TO aa   
   22 1980 * TO bb 
   aa bb + TO cc
 ; VALUES

To implement it, you do not need to redefine NOTFOUND. Only the meaning of the word TO will be changed. Between the terminating words VALUES:; VALUES TO should act like normal VALUE.
 VOCABULARY values
 ALSO values ​​DEFINITIONS

 :; VALUES PREVIOUS;
 : TO VALUE;

 PREVIOUS DEFINITIONS 
  
 : VALUES: ALSO values;


You can do the same way of writing for constants.
 CONSTANTS:
        11 IS aa   
   22 1980 * IS bb 
   aa bb + is cc
 ; CONSTANTS

The implementation of this method, I think, is obvious.

In general, this approach forms a new type of defining words - group defining words. A simple defining word allows you to create words combined by common semantics. Group possessing the same property, they require to concentrate the definitions of the same type words in one part of the source text. What positively affects its readability and maintenance.
The group implementation of the word WINAPI: can become a much more pleasant addition to SP-SP-Forth. In particular, in the Winctl library the definitions of WINAPI: are scattered throughout the text, which looks like a mess.
As an option:
 WINAPIS:
     LIB: USER32.DLL
              Postquitmessage
              PostMessageA
              SetActiveWindow
     LIB: GDI32.DLL
              CreateFontA
              GetDeviceCaps
              DeleteDC
     LIB: COMCTL32.DLL
              InitCommonControlsEx
 ; WINAPIS

To do this, see how the word WINAPI is implemented:
spf_win_defwords.f

 : __WIN: (params "ProcessName" "LibraryName" -)
   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!  (communication)
   THEN
   HERE DUP R @ CELL + CELL +!
   PARSE-NAME CHARS HERE SWAP DUP ALLOT MOVE 0 C, \ function name
   HERE DUP R> CELL +!
   PARSE-NAME CHARS HERE SWAP DUP ALLOT MOVE 0 C, \ library name
   LoadLibraryA DUP 0 = IF -2009 THROW THEN \ ABORT "Library not found"
   GetProcAddress 0 = IF -2010 THROW THEN \ ABORT "Procedure not found"
 ;

 : WINAPI: ("ProcessName" "LibraryName" -)
   (Used to import WIN32 procedures.
     The resulting definition will have the name "ProcedureName".
     The address of winproc field will be filled at the time of the first
     fulfillment of the received dictionary entry.
     To call the received "import" procedure parameters
     pushed onto the data stack in the reverse order of
     in C call this procedure.  The result of the function
     will be put on the stack.
   )
   NEW-WINAPI?
   IF HEADER
   ELSE
     -one
     > IN @ HEADER> IN!
   THEN
   ['] _WINAPI-CODE COMPILE,
   __Win:
 ;


Apparently implemented a delayed loading DLL. The dictionary entry with the name of the imported function compiles a link to the WinAPI call code, then some parameters and then the name of the library file and the procedure in it. Next comes the validation of the presence of such a file and such a procedure.
In order to remake this code according to our wishes, we will decide what each word will do.
; WINAPIS - just restores the context.
LIB: - enters the next word from the input stream and stores it in a temporary buffer. Can be combined with validation.
The remaining words are perceived as procedure names.

So:
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>;
 : ss (- addr u) NextWord 2> RR @ 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 ss 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!  (communication)
                      THEN 
               HERE DUP R @ CELL + CELL +!  > R 
                CHARS HERE SWAP DUP ALLOT MOVE 0 C, R> \ function name
               HERE R> CELL +!  2> R  
                 CHARS HERE SWAP DUP ALLOT MOVE 0 C, 2R> \ library name 
               SWAP GetProcAddress 0 = IF -2010 THROW THEN \ ABORT "Procedure not found"
 ;

  PREVIOUS DEFINITIONS

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




Source: https://habr.com/ru/post/220365/


All Articles