FORTH: Self-Defining Words

    Let there be 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 ...
    To determine them, you will have to write out the word VARIABLE each time, and this is cumbersome, sad and ugly. Is there any way to make it more fun?
    Indicate that further variables will be defined and write down their names.
    Sort of:
    VARIABLES:
      xyz  
      x 'y' z '
      x '' y '' z ''
      sm
    ; VARIABLES
    



    Recall that the Fort language interpreter implemented in SP-Forth, if it does not find a word in the context dictionary, searches for the word NOTFOUND in the same context dictionary, which it executes. The NOTFOUND input parameters are the address and counter of the substring from the input stream. So, you need 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 recall the definition
     : VARIABLE CREATE 0,;
    

    But the word CREATE itself selects the next word from the input stream, and we need to create a dictionary entry from a line with an address and a counter on the stack. Fortunately for this case, there is the word CREATED, which just takes the address and line counter from the stack and creates a dictionary entry. Unfortunately, it, like NOTFOUND, is not part of the standard ANSI-94 word set.
    Thus
    : NOTFOUND (addr u -) CREATED 0,; 
    

    But, if we put such a definition in the base FORTH list, then we lose the ability to enter numbers. So you need to hide this new NOTFOUND in some other context. We will start the dictionary of variables.
    VOCABULARY variables 
    

    and make it current.
    ALSO variables DEFINITIONS
    

    put the definition of NOTFOUND there;

    return the current context
    PREVIOUS DEFINITIONS
    


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

    Closing word; VARIABLES will return a context. It should naturally be 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, in just four lines, we expanded the SP-Forth interpreter and simplified the description of variables.
    But a similar approach can be used for VALUE variables, constants, and generally any words with common execution semantics. Those words that are defined using the defining word. In principle, it is useful to have pairs of defining words. One for a single definition, and paired for a group definition. Actually, the defining word is created for the sake of being able to create groups of words with common semantics. And it is convenient if these definitions are not spread over 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. The qualifier word VALUE is not defined via CREATE. It is defined as follows:
    : 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 another and get the necessary 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.
    There may be several options for implementation.
    You can record simply
    VALUES:
      11 aa   
      22 bb 
      33 cc
    ; VALUES
    

    This is unreadable.

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

    looks beautiful.

    Obviously, the word "equal" must be present in the context of values. One must choose the next word and interpret it as a number. That is, to be almost synonymous with 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;
    


    Such an option
    VALUES:
      11 to aa   
      22 to bb 
      33 to cc
    ; VALUES
    
    It is valuable in that it does not fall out of the language paradigm, in addition, it allows you to initialize 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 limit words VALUES:; VALUES TO should act like a normal VALUE.
    VOCABULARY values
    ALSO values ​​DEFINITIONS
    :; VALUES PREVIOUS;
    : TO VALUE;
    PREVIOUS DEFINITIONS 
    : VALUES: ALSO values;
    


    You can make a similar 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 campaign forms a new type of defining words - group defining words. A simple defining word allows you to create words that are united by common semantics. Group possessing the same property, require concentrating the definitions of the same type of words in one part of the source text. Which positively affects its readability and accompaniment.
    A much more enjoyable addition to SP-SP-Forth is the group implementation of the word WINAPI :. In particular, in the Winctl library, WINAPI definitions are scattered throughout the text, which looks gloomy.
    As an option:
    WINAPIS:
        LIB: USER32.DLL
                 PostQuitMessage
                 PostMessageA
                 SetActiveWindow
        LIB: GDI32.DLL
                 CreateFontA
                 GetDeviceCaps
                 DeleteDC
        LIB: COMCTL32.DLL
                 InitCommonControlsEx
    ; WINAPIS
    

    To do this, take a look at how the word WINAPI is implemented:
    spf_win_defwords.f

    : __WIN: (params "ProcedureName" "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: ("ProcedureName" "LibraryName" -)
      (Used to import WIN32 procedures.
        The resulting definition will be named ProcedureName.
        The address of winproc field will be filled at the time of the first
        execution of the resulting dictionary entry.
        To call the received "import" procedure parameters
        pushed onto the data stack in the reverse order
        in the C call of this procedure. Function Result
        will be put on the stack.
      )
      NEW-WINAPI?
      IF HEADER
      ELSE
        -1
        > IN @ HEADER> IN!
      THEN
      ['] _WINAPI-CODE COMPILE,
      __WIN:
    ;
    


    Apparently the delayed loading of DLL is implemented. A link to the WinAPI call code is compiled into a dictionary entry with the name of the imported function, then some parameters and then the name of the library file and procedure in it. Then, the presence of such a file and such a procedure takes place.
    In order to remake this code to our wishes, we will determine 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. It can be combined with validation.
    The remaining words are perceived as the names of the procedures.

    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; 
    





    Also popular now: