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:
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
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
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.
and make it current.
put the definition of NOTFOUND there;
return the current context
Thus, the word VARIABLES: switches the context to variables and makes available the required NOTFOUND
Closing word; VARIABLES will return a context. It should naturally be in the context of variables.
That is, total:
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.
And here we come across some trouble. The qualifier word VALUE is not defined via CREATE. It is defined as follows:
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.
So:
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
This is unreadable.
Let's try to write like this:
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
Such an option
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.
You can make a similar way of writing for 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:
To do this, take a look at how the word WINAPI is implemented:
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:
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
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 to bb 33 to cc ; 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;