Aztec Forth – Native Code ANS Forth for Windows

Pre-introduction

This is an almost verbatim copy (including the appalling HTML generated by Netscape) of the last documentation for Aztec Forth that I produced. Shortly after writing this I stopped using Windows for good and so no further development of Aztec took place. “Project Maya” which is mentioned in here has been started as a basic Forth for Linux. Development of that has also stopped for the moment due to work pressures and the fact that I can’t decide whether it should be based on ANS or ColorForth. Fred Warren distributed a version of Aztec for a while but I have not heard from him in some time.

Currently, the best Forth for Linux seems to be Bernd Paysan’s Bigforth. There are other Linux Forths but none of them are in the same league as Bigforth.

Current Download

The most recent version I have is here.

Introduction & Getting Started

This forth system is intended to be a minimum system for using Forth on IBM compatible computers using the Microsoft 32bit operating system, it has not been tested under NT earlier than version 4.0. The code is targeted at Intel 386 or higher processors and so should run on any computer that has a Win32 OS; code has been written with an eye to Pentium pairing rules and is very tight for a non-optimised compiler.

A block editor which works under windows and allows cut/paste from and to other Windows applications and simultaneous viewing/editing of a source block and its shadow block (if required) is included in the blockfile BEDIT in the zip file with the compiler. The words required for linear source code are included in this blockfile at block 90.

Introduction to Version 2

This is a much faster implementation than version 1 and runs on NT. The pickup in speed is particularly noticeable in recursion or words which are heavily factored. The default inlining behaviour is such that loops and branches were already quite fast and so the improvement is not so obvious in these areas.

I would like to thank Fred Warren for a great deal of what we gamers call `playtesting’ and his general enthusiasm. Harry at MPE is also to be thanked for providing the suggestion that switching the stacks around would improve performance. I was dubious and he was right.

I would also like to thank Graham Telfor.

I do not intend to add any more words to the kernel unless there is a big demand for them; I’m entering the final year of my degree and as part of that I will be adding some level of optimisation to Aztec but I have very little hope of getting anything else of note done for the compiler. Also, I intend to start really using Aztec rather than writing it. Next year I may start project Maya, a metacompiler version of Aztec which would allow DLL creation.

Getting Started

Double click on the program icon to start it. This will open a console window with a copyright notice. To load the block editor type

BLOCKFILE” BEDIT”

1 LOAD

This will set BEDIT as the current blockfile and load the editor. To use the editor with shadow blocks active use +SHADOWS. To use the editor without shadow blocks (the default) use -SHADOWS.

EDIT starts the editor. Notice that the editor runs at the same time as the console so it is possible to switch between them using the mouse or Alt-Tab. At the moment the editor does not automatically save the current blocks out to disc when it loses the keyboard focus, although using “End Edit” does. The current editor display is in the block buffers, however and so changes are immediately available from the console.

The display in the upper right is the current code block, which is the one shown in the uppermost editbox (the only edit box if shadow blocks are off). When shadow blocks are on the lower display is the shadow block associated with the current code block.

The controls along the top are associated with viewing the blocks and saving changes made to them. Using “back” and “next” automatically save changes made to the blocks as do “Go to” and “End Edit”. “Save” forces a FLUSH and “Undo” will cause an EMPTY-BUFFERS and the display to to updated. Typing a value into the block display and pressing return or clicking on “Go to” will cause that block to become the current code block, if possible.

Blockfiles are locked by default to prevent new blocks being added by simply moving past the end and this may be switched off using the “Lock” button when shadows are off. The only way to extend a blockfile when shadows are on is with the “Insert Block” control which will automatically keep the relationship between code and shadow correct.

“Wipe blocks” are all self-explanatory while “Clip File” makes the current blockfile end at the current code/shadow blocks. Again, this automatically updates the position of the shadow blocks to keep them aligned with code.

Block 90 is a load block for some of the file words including INCLUDE-FILE for those who do not like blocks and integrated development.

To start your own block file use BLOCKFILE” and then EXPAND to set it up to the size you require. If shadows are to be used then uses SEXPAND which automatically adds shadow blocks. See Source Code Format.

For longer term use you will want to set up a development environment with the editor and so-on in memory at start up, but with the option of throwing them away when your application is ready. To do this start up the basic Aztec file and type

system BLOCKFILE” BEDIT” 1 LOAD 90 LOAD (and any other loads you want for development)

application FSAVE” DEVELOPMENT.EXE”

This will give you a new file called DEVELOPMENT.EXE which will have the editor and so on already loaded when it is started but which will remove these before producing a TURNKEYed application. There are more details on TURNKEY” and FSAVE” later.

Philosophy/Aims

The main reason for writing this Forth system was to try to integrate the Windows DLL calls into normal Forth syntax as much as possible, thus simplifying the process of using system calls. It was apparent to me that the structure of the Win32 kernel was well suited to Forth, particularly if the top of the data stack is kept in register EAX. When this is the case the operating system takes the Forth data stack as its inputs and leaves its result at the top of the stack, consuming its input parameters in the process; this is exactly what normal Forth words do. Additionally, the virtual address space assigned to processes in this operating system means that Forth can treat the addresses returned by GetProcAddress as if they were normal Forth execution tokens and that the OS can treat Forth’s data areas as normal parts of its own address space. Therefore no conversion from Forth address space to OS address space is required and the join between Forth and Windows is almost invisible.

What does “almost invisible” mean?

There are a few areas of trouble in the Forth/Win32 interface. The first is that Void functions effectively return garbage on the top of stack which must be DROPed by a Forth application. There are very few useful OS calls that have a Void return type, so this is not much of a problem.

Secondly, it is necessary for the compiler to know when a search for a word is to be done in the normal dictionary or in a DLL. Previous methods which were invisible to the user have proved impossible to rely on to identify a wordlist as being a DLL so it is now required that a DLL handle (such as is returned by LoadLibrary) must have its low bit set. Since all DLLs and wordlist handles are even this may be accomplished by adding one to the DLL handle. This method should be totally reliable (so expect some idiot to bring out a DLL that returns an odd handle any day now). LIB” performs this modification automatically.

Some DLLs are automatically available when the system is loaded; these are KERN32.DLL, USER32.DLL, and GDI32.DLL. These are represented as forth wordlists and the return value from LoadLibrary may be treated in much the same way as the value returned by WORDLIST. Wids (wordlist identifiers) which correspond to a DLL may not, however, be used for the CURRENT compilation wordlist and doing so causes an exception to be thrown. The forth word WORDS will not return anything while the first wordlist in the search order is a DLL but no error is caused.

Since the location of Windows functions can change from run to run if the particular release of the OS being used has changed in between, it is not normally possible to use ' or ['] to call Windows functions. Both words will return a valid xt, but that xt is only usable until the system has changed – do not expect to be able to take compiled code with a Windows xt in it to another machine and have it run. It might run, and it might not.

Windows functions which are used inside normal Forth definitions will work as they will be automatically linked in and updated at boot time.

The final hitch in using OS calls from Forth is that many of the calls that take or return character based parameters exist in their DLL in two forms: a standard ASCII form and a UNICODE, or wide form. The compiler assumes that you want the ‘A’ version if there is one. In order to tell the system that you want the unicode version it is necessary to append a U to the end of the function name that you want, e.g., LoadLibraryU.

Callbacks

The situation when Windows calls a Forth word is the most complicated situation of all since Windows is written in and for C and uses only one stack, expecting the role of the second stack to be taken by a stack frame. A Forth word uses two stacks and, on top of that, it is possible in many Window situations that the Forth word may be called recursively before returning. This means that providing it with an area of global memory to use is of no use, and therefore the single hardware stack must be split into two. This is done by the non-standard word c: and is undone by the word ;c (meaning from-C-word and end-C-word).

c: expects (at compilation time) to have a number on the top of the stack which represents the size of data stack required by the word. There is at this time no method known to me of determining how much this should be, assume it to be at least 500 cells and increase it until the minimum required for the code to work is found. This is not very helpful but I’m looking into it!

A simple colon definition WILL NOT WORK as a callback word.

Threads

The block editor example runs as a separate thread. This too requires its own stack in addition to any space needed for the data stack of the word which is to be run as a thread (which must be a callback word). This is achieved by passing the required total size of both stacks as the fifth parameter of the OS call CreateThread. For example:

VARAIBLE ID

ID fdwCreate 0 ‘ THREADED-WORD 2000 cells lpsa CreateThread

will start the word THREADED-WORD with a total stack size of 2000 cells. fdwCreate and lpsa are normally zero, their meaning can be looked up in the MS SDK if you have access to it (university students: check your department’s computer language section – that’s how I got my information). After this code has been executed the ID of the started thread will be in the variable ID and the handle of the thread will be on the top of the stack, where all Win32 functions leave their result.

Other System Characteristics

The system starts by opening a console for the user to work at. The console appears to use an old 16-bit part of Windows ’95 but do not worry about that – the code generated is 32-bit. The text interpreter is case sensitive but will make multiple passes before declaring that a word is not found. An exact match is first searched for, then the word is transformed to lowercase and the search repeated, and finally a search is made for an uppercase match. If all three fail then an attempt to transform the word to a number in the current base will be attempted before giving up. This slows down compilation of numbers by a factor of three but is the result of the author’s frustration caused by the frequent uses of mixed-case words when calling the operation system and leaving the Caps Lock key off, or pressing said key by mistake.

Code generation is native 386 compatible code with the level of subroutine threading controlled by the system variable INLINE. When the compiler is active each word going into a new definition is checked to see if its length in bytes is greater than the value of INLINE. If it is then the compilation is in the form of a subroutine call, otherwise the code is compiler inline without a call. Certain words may not be inlined, the most important class being those words which have an EXIT in them, as this would require a much more sophisticated compiler to support and I can see no reason to write such just to support unstructured programming! Some of the kernel words do support such unstructured programming (I forgive myself since its assembly language) and these words may not be inlined either. The programmer need not worry about which words are and are not eligible for inlining as the compiler checks a flag in the header for this purpose. When using WORDS it will be noticed that many of the precompiled words dealing with the return stack appear twice – one version is for inlining and the other is for threading.

The inner loop parameters of a DO…LOOP are contained in registers for speed although this too can be inlined for further gains.

The header structure (17th September 1998) is:

Back-link, Size, Code Address, Flags, Name length, Name,[Padding]

Name length, Name and Padding are byte based (padding ensures that the next header starts on a 4-byte boundary) and the rest are 32-bit cells. In addition, the cell immediately before a word’s xt points back to its header, this is in order to allow the construction of a debugger sometime

The words in the dictionary are split into two groups – those needed by the application to run and those needed by the system to compile the application. Which set of words are being compiled into is set by the words ‘system’ and ‘application’ (not including the quotes). the word ‘application?’ returns the current status of the compiler as regards this feature.

TURNKEY” FSAVE” and BOOT

Once you have compiled your application it is possible to save it out to the disc as a stand-alone executable file using either TURNKEY” or FSAVE” . The variable BOOT stores the execution token of the word which you want to be executed when the program is started. At the moment BOOT is set to OPEN-CONSOLE and so if your boot word requires a console it must explicitly use OPEN-CONSOLE itself.

TURNKEY” ( xt <name>” — ) takes the required execution token for BOOT, throws away anything in the system section of the dictionary and produces an executable file with the name inside its double quotes. e.g. ‘ MYWORD TURNKEY” My new program.exe” Note the free use of spaces and the explicit use of .exe to indicate to windows that this is an executable file.

The file that TURNKEY” produces will not compile or interpret normally as all the header information is thrown away, as are most of the compilation words themselves.

FSAVE” ( <name>” — ) does not take an xt so BOOT will have to be set up before use if a special start word is required. FSAVE” simply puts an image of the entire system (with a few small exceptions) onto disc with the name in quotes. The only things which are changed is that the search order and the current compilation wordlist are reset. Other wordlists and DLLs are still available, of course, but will have to be put back into the search order before use. Everything else will be as it was before FSAVE” was used, including values in variables and so on.

Neither TURNKEY” or FSAVE” will save over a file with the same name as the one being used as Windows automatically locks an executable while it is in use. There is some way around this but I don’t know what it is.

TURNKEY” and FSAVE” are part of the file words which are stored on the blockfile from block 90.

While FSAVE” and TURNKEY” save any resources currently in the compiler file, further resources may be added with an editor.

Standard Words at Start-up

All the Core words are available at start-up.

All the Block words are available at start-up.

All the Exception words are available at start-up

All the Search-Order words are available at start-up

All the Memory-Allocation words are available at start-up.

All the String words except BLANK are available at start-up, BLANK is defined on disc.

All the Block extensions are available either at start up or on disc.

All the Search-Order extensions are available either at start-up or on disc

All the Floating-Point and Floating-Point extension words are available on disc.

Non-Standard Words at Startup

In the order they are listed by WORDS:

OPEN-CONSOLE ( — hndl) Returns a handle to a new console if a console has not already been opened. If used from a system which has a console this causes a crash. It is called as part of the start-up routine.

STARTUP ( — ) Loads the Kern32, User32, and GDI32 Dll’s and initialises the error trapping. This word is executed on loading Aztec and cannot be avoided. No longer visible to the user.

: CON-START ( i*x --) STARTUP OPEN-CONSOLE ;

header ( <blanks>cccc<blank>) Skip leading spaces, parse characters delimited by space (ASCII 32). Make a header for a word using the parsed string for the name. HERE returns the address at which the new word’s code begins, the cell before HERE holds the starting address of the header.

(accept) ( c-addr +n1 — +n2) This is the dpANS ACCEPT. At start up the word ACCEPT is vectored to this word but may be revectored using IS (supplied in blocks).

(type) (c-addr n –) As with (accept), this is the default vector for TYPE.

(number) ( caddr — value flag) This is the default number conversion routine.

do-number ( ?? — ?? ) This is the vector for number conversion. ‘ do-number @ will initially return the xt of (number).

interpret ( <word> — ?? ) Parse the next blank-delimited word from the input stream and execute, compile, or convert to a number or literal based on the current value of STATE.

c: (n — colon-sys) Interpretation: Start the definition of a word to be called from C prefixing the runtime action below to the new definition, Enter compilation state

Run-time ( r: nest-sys — nest-sys ) Assign n bytes of space (from above) for the data stack.

;c ( colon-sys– ) Compilation: End C-style definition, appending the runtime action below to the current definition.

Run-time (R: nest-sys — ) Restore the return stack pointer to the value it had when the current word was called and return to the calling word.

Interpretation: Interpretation behaviour for this word is undefined.

.WINERR ( n — ) Attempt to display a text error message corresponding to the Windows error code n. Uses pictured numeric area.

WINERR ( — ) Attempt to display a text error message reporting the most recent error condition returned by Windows. Also display the numerical error code of that error. Uses pictured numeric area.

INLINE ( — addr) Returns the address of the cell which hold the current inline threshold for the compiler (see above).

ZCOUNT ( caddr — caddr u) Converts a null terminated string address into an address and count. The count does not include the null and the second address is the same as the first.

SET-ENVIRONMENT ( — ) Sets the definitions wordlist to the environmental queries wordlist.

CELL-FILL ( addr u1 u2 –) If u1 is greater than zero, stores u2 in u1 consecutive cells beginning at addr.

>HEAD (xt — addr) Addr is the address of the word whose code starts at xt. Addr is undefined if xt refers to an OS function or a headless definition.

KERN32 ( — wid) Returns the wid for the KERNEL32 DLL.

GDI32 ( — wid) Returns the wid for the GDI32 DLL.

USER32 (–wid) Returns the wid for the USER32 DLL.

WINBUFFER ( — caddr) Returns the address of a buffer big enough to hold a 300 byte string. Useful for receiving strings from Windows.

WINCOUNT ( — addr) Returns the address of a cell which is intended for temporary storage of character counts in and out of Windows.

>UPPER ( caddr u — ) Transforms the character string defined by caddr u to upper case.

>LOWER ( caddr u — ) Transforms the character string defined by caddr u to lower case.

zmove ( caddr1 caddr2 u — ) Moves u bytes from caddr1 to caddr2, appending an ASCII zero after the last byte transferred.

hinst ( — u ) u is the instance value given by Windows to the current incarnation of Aztec Forth.

BLOCKFILE” , CLOSE-BLOCKS, and BLKFILE are described under Source Code Format.

save-buffer ( u — ) Saves out the contents of mass storage buffer u. There are four of these ( 0 to 3).

LOCKED? ( u — flag) Indicates if it is permissible to write to block u.

LASTWORD ( — addr) Returns the header address of the most recently defined word.

STDIN ( — addr) Returns the address of a cell containing the handle of the current standard input device or file.

STDOUT ( — addr) Returns the address of a cell containing the handle of the current standard output device or file.

STDERR ( — addr) Returns the address of a cell containing the handle of the current standard output device or file for error messages NOT YET IMPLIMENTED – changing the handle does nothing.

BOOT ( — addr) A variable which holds the xt of the word to be executed at startup.

BLOCK-LOCK ( — addr) A variable holding a flag. If the flag is true then blockfiles may not be created nor may they be extended beyond their current length.

W, (n — ) Stores the lower 16 bits of n at the address pointed to by HERE, then increases HERE by 2.

W@ ( addr — u) n is equal to 0 + the unsigned value of the 16 bit half cell at addr.

W! ( n addr — ) Stores the value n in the WORD at addr. Only the lower 16bits of n are used.

CURRENT-WORD ( — addr) The header address of the word currently being compiled. Returns zero if there is no compilation ongoing.

BREAKER ( — ) causes an INT 3.

system ( — ) words following this will be marked as system words which may be discarded by TURNKEY”.

application ( — ) words following this will be marked as part of the final application and will not be thrown away by TURNKEY”

application? ( — flag) returns true if currently compiling application words.

(source) ( — addr) returns a pointer to the address of the current input stream.

WINCON returns the address of the handle of the WINCON.DLL file if it was found at startup. If this is set to zero then the system will not search the constants database; it will not crash.

MEMTOP ( — addr) returns the address of the first cell BEYOND the usable memory area for whichever dictionary space (application or system) the system is in.

LIB” ( <name>” <name> — ) Links in the DLL given by <name>” and assigns its wid to a constant called <name>. The DLL will be automatically linked in if the current system is saved with FSAVE” or TURNKEY”.

USES” ( <name>” — ??) Roughly equivalent to SAVE-INPUT BLOCKFILE"<name>" 1 LOAD CLOSE-BLOCKS RESTORE-INPUT .

-DEFINED ( <name> — flag) If the word <name> is in the current search order then the flag returned is false, otherwise it is true.

E.g., -DEFINED D>S [IF] : D>S DROP ; [THEN]

LOCALISED LOCAL >LOCALS LOCALS> LOCAL# -LOCALS see Local Data Objects.

Source Code Format

Source code is organised by default into blocks (hoo-rah!) although old fashioned text files can be INCLUDEd using the words defined in the blockfile BEDIT at block 90. A Windows based block editor is provided which allows simultaneous editing/viewing of both a block of code and its shadow block of comments. Text may be selected, cut, and pasted using the standard keyboard shortcuts and the mouse. Navigation is quite easy, although more work in that area is planned. I know that many out there regard blocks as a pain and they can be without an editor but the interactive nature of the block editor and the ability to pull in code segments (as opposed to entire files) makes an editor-based block system far superior to linear source file, in my opinion. For those who still doubt, the word INCLUDE-FILE is provided (in the block file).

The words concerned with using blocks, other than those in the standard, are:

BLOCKFILE” – parses up to a closing quote and opens the resulting string as a file, creating it if necessary, and storing the handle in BLKFILE , a variable which holds the handle of the currently active block file.

BLKFILE – see above.

CLOSE-BLOCKS – which closes the file whose handle is in BLKFILE.

BLOCK-LOCK – this variable contains a flag which, when non-zero, prevents the current blockfile from being extended. This was introduced after a nasty experience with a runaway DO loop. From version 2 it also prevents a new blockfile from being started.

LOCKED? – takes a block number and returns a flag to indicate if it is possible to write to it.

The current active block file may be changed at any time by changing the handle in BLKFILE but don’t forget to save the old handle somewhere first.

Once the block editor is loaded the word +SHADOWS directs the editor to treat the current blockfile as consisting of half code blocks and half shadow blocks. Shadow blocks are displayed below the code block to which they correspond. -SHADOWS switches this feature off. When shadows are active the editor automatically locks the block file to prevent its length from changing, Otherwise the lock may be turned on and off with the relevant button on the editor.

EXPAND ( U –) forces the current blockfile to end at block n unless already bigger than that. Overrides the status of BLOCK-LOCK while operating. SEXPAND ( U — ) (shadow expand) adds the requested number of blocks and the same number of shadow blocks to the end of the current blockfile, if shadow blocks are active otherwise works as EXPAND.

Local Data Objects

The locals wordset in ANS is a hopeless compromise and I do not intend to ever support it. Windows programming, on the other hand, really needs some kind of local data mechanism and I have provided a set of words intended mainly to help Windows programmers, but which are also useful for general programming tasks. None of the words in this set conflict with words in the ANS locals wordset.

The primary words for local data are LOCALISED , LOCAL and -LOCALS (no locals).

LOCALISED ( u — ) This causes the system to allocate u bytes of aligned local memory.

LOCAL ( — addr) Returns the address of the current local data area. The intention is that this should be a structure of some kind, the fields of which are accessed using whatever structure package you are using (there is one included with Aztec).

-LOCALS ( — ) Deallocates the current local data area, restoring any previous one.  

Note that -LOCALS should be called before exiting the word which contained the corresponding LOCALISED either at ; or at EXIT . The final point to note is that factors of a word with LOCALISED data may access that data with LOCAL. The ANS wordset for locals discourages factorisation and that reason alone should have been enough to dismiss it from the standard.

 The following words are also provided:

>LOCALS ( x1 x2 … xn u — ) Allocates a local area big enough for u cells and places the top u cells into that area such that the address returned by LOCAL is the address of the uth item, LOCAL +CELL is the return of the u-1th item etc. In other words LOCAL x CELLS + returns the address of xth item from the top of the stack (ignoring the count u).

4 3 2 1 4 >LOCALS

LOCAL 2 CELLS + @

Returns 3 just as 4 3 2 1 3 PICK would have.

LOCALS> ( u — u*x) Returns the first u cells of local data without deallocating the local data area. The order of the items so returned is the same as the order they would have been in if they had been placed there by >LOCALS.

LOCAL# ( u — addr) Returns the address of the uth item of local data as if the phrase LOCAL u CELLS + had been executed.

Mad Zombie File Killer

Also included in the blockfile is an example application called the Mad Zombie File Killer which removes files from the bloody annoying Document menu on the Start tab. It works by sitting in a loop and every 30 seconds coming to life and deleting any files which have found their way into the menu. To set it up open the blockfile BEDIT and LOAD block 90 and then load blocks 117 THRU to 120 and type ‘ EMPTY TURNKEY” MAD ZOMBIE FILE KILLER.EXE” or whatever you want to call it.

Double clicking on the resulting .exe file will launch what is called a zombie app in Windows. This is an application which has no windows and so from the point of view of the user does not have a true existence and can not be turned off except by using special utilities or rebooting (you can not kill that which does not live!). I keep mine in the start-up folder. Since it comes out at only 36K and sleeps (or shambles) for several billion clock cycles before doing a very trivial amount of work, it can’t really be said to drain the system resources and it illustrates some important points about Windows program implementation. Not least of which is that useful work can be done without a window or a message loop.

Major Changes from Version 1.x

The register set has been changed to make the hardware stack the return stack. This has allowed much faster threading but at the cost of tightening the rules on callbacks, which must now be defined with c: and ;c .

I have admitted defeat on CASE and added it to the kernel along with -DEFINED and [IF] [ELSE] [THEN] etc.

The intermittent bug which struck only one person seriously and one other person occasionally when using LOAD was tracked down to a bug which caused me to wonder how anyone ever got anything to LOAD. The problem never arose on my own machine in an entire year of use!

Aztec now works under NT. The CPU’s direction flag is set by Windows ’95/98 if it needs to use it, whereas NT expects the compiler to have set it. Since this is not documented anywhere it took me a looooong time to find it. Additionally, NT is a lot fussier about unused areas of files being set to zero than ’95.

A set of words for working with local data has been added, documentation for which is in another section. The ANS words are not really up to the job so I have ignored them. The words provided do not share any names with the ANS words.

Initial Wordlist

These are the words, in alphabetical order, which are available at start up of the basic Aztec Forth executable. Further system documentation is also provided at this link.

‘ – ! # #> #S ( (accept) (number) (source) (type) * */ */MOD , . .” .WINERR / /MOD /STRING : :NONAME ;c ; ?DO ?DUP @ [ [‘] [CHAR] [COMPILE] [ELSE] [IF] [THEN] \ ] + +! +LOOP < <# = > >BODY >HEAD >IN >LOCALS >LOWER >NUMBER >R >R >UPPER 0< 0= 1- 1+ 2! 2* 2/ 2@ 2>R 2>R 2DROP 2DUP 2LITERAL 2OVER 2R@ 2R@ 2R> 2R> 2SWAP ABORT ABORT” ABS ACCEPT AGAIN AHEAD ALIGN ALIGNED ALLOCATE ALLOT AND application application? BASE BEGIN BL BLK BLKFILE BLOCK BLOCKFILE” BLOCK-LOCK BOOT BREAKER BUFFER BYE C! C, c: C@ CASE CATCH CELL+ CELL-FILL CELLS CHAR CHAR+ CHARS CLOSE-BLOCKS CLOSE-FILE CMOVE CMOVE> COMPARE COMPILE, CONSTANT CON-START COUNT CR CREATE CURRENT-WORD D+ D>F D0= DECIMAL -DEFINED DEFINITIONS DEPTH DNEGATE DO DOES> do-number DROP DUP ELSE EMIT EMPTY-BUFFERS ENDCASE ENDOF ENVIRONMENT? EVALUATE EXECUTE EXIT FILE-SIZE FILL FIND FLUSH FM/MOD FORGET FORTH-WORDLIST FREE GDI32 GET-CURRENT GET-ORDER header HERE HEX hinst HOLD I IF IMMEDIATE INLINE interpret INVERT J KERN32 KEY LASTWORD LEAVE LIB” LITERAL LOAD LOCAL LOCAL# LOCALISED -LOCALS LOCALS> LOCKED? LOOP LSHIFT M* M*/ M/ M+ MARKER MAX MEMTOP MIN MOD MOVE NEGATE NIP OF OPEN-CONSOLE OR OVER PARSE PICK POSTPONE QUIT R@ R@ R> R> RECURSE REFILL REPEAT REPOSITION-FILE RESIZE RESTORE-INPUT ROLL ROT RSHIFT RSP! S” S” S>D save-buffer SAVE-BUFFERS SAVE-INPUT SEARCH SEARCH-WORDLIST SET-CURRENT SET-ENVIRONMENT SET-ORDER SIGN SLITERAL SM/REM SOURCE SOURCE-ID SPACE SPACES STATE STDERR STDIN STDOUT SWAP system THEN THROW THRU -TRAILING TUCK TYPE U. U< UD. UM* UM/MOD UNLOOP UNTIL UPDATE USER32 USES” VARIABLE W! W, W@ WHILE WINBUFFER WINCON WINCOUNT WINERR WORD WORDLIST WORDS WRITE-FILE XOR ZCOUNT zmove

The Assembler

A 386/486 assembler is provided as a separate blockfile in the zip file and can be loaded as whole by opening it and typing 1 LOAD. There is very little reason not to load the assembler into the system space of the dictionary.

Once loaded the assembler may be used at any time during compilation of a word by using [ASSEMBLER] which switches to interpret mode and allows the input of assembly language mnemonics. COMPILE swaps back. The words CODE: and ;CODE are also provided to start a definition in assembler mode and to end such a definition (this is NOT the ANS definition).

If required a raw block of code may be started with CODE and ended with END-CODE. The address of this code is stored in CODE-START which is part of the assembler wordlist.

ASSEMBLER returns the wid of the assembler wordlist and RESET is a MARKER which will reset the system to what it was before loading the assembler.

Note that assembly language words must have only one exit point unless EXIT is used.

Format of Assembly Language Instructions

The assembler is a typical reverse order Forth assembler which has the happy trait of completely reversing the horrible Intel syntax to SOURCE DESTINATION CODE. So, for instance the normal Intel line

mov eax edx

meaning to put the contents of edx into eax, is reversed to

edx eax mov

which I find much more logical.

The only instruction to which this does not apply is the CMP instruction which have their operands in the same order as Intel in order to make conditional jumps etc read correctly. So Intel’s

cmp eax ecx

ja label1 \ jump if eax is above ecx

becomes

eax ecx cmp

1 ja \ still reads as “if eax is above ecx”

Addressing modes

Registers are simply referred to by name e.g. EAX. Several aliases have been defined: TOS for top-of-stack is the same as EAX. DSP (data stack pointer) is the same as EBP and RSP (return stack pointer) is the same as ESP.

Base registers are indicated by [] as normal, e.g [EDX]. Modes which use more a base register and some other offset require braces to indicate the address components. e.g. { EAX [EDX] } indicates that edx is the base and eax is the index. If no square brackets are included then the first named register is used as the base so { EAX EDX } means that EAX is the base and EDX is the index. Scaling is indicated by x1 x2 x4 or x8 and is applied to the index register regardless of the order in which the registers have been encountered so

{ EAX [EDX] x2 } ECX MOV

moves the memory pointed to by EDX +(2*EAX) into ecx.

Immediate data is indicated by the numerical value (which may be a Forth word which returns a value) followed by # if the data is simply a number or [#] if the data is to be used as a base address or an offset to an address. So

{ ECX 42000 [#] } EAX ADD

will add the contents of the memory address at 42000 plus the current value of ECX, to the top of the stack in EAX while

42000 # EAX ADD

adds 42000 to the contents of EAX and

{ ECX x4 [EDX] 50000 [#] } TOS mov will replace the top of the stack with the cell at the address (EDX + 50000)+(4*ECX).

The size of the data is normally based on the register, i.e. ax [ecx] mov moves 16 bits. When immediate data can be fitted into smaller units, it is.

It may still be necessary to force a particular size and the words LONG and 16DAT are available for this. If LONG is set to false (0) then the data mode for the next instruction is byte. Otherwise, if 16DAT is set to true then the data is 16 bits, and if LONG is true and 16DAT is false the data mode is 32bits. The words DATA8 and DATA16 may be used to manipulate these flags.

The pseudo-instructions DPOP and DPUSH are provided to pop or push an item on the data stack, POP and PUSH work on the hardware stack which is the return stack.

Labels & Jumps

The assembler can currently handle ten labels in one definition with up to ten outstanding forward references to each. This may be changed in block 36 of the assembler if required. Labels consist of numbers and should be numbered between 0 and one less than the maximum number of labels, although they do not need to be used in order. A jump instruction may be preceded by a SHORT directive which forces the next jump only to use the smaller encoding. Note that there is no error checking to see if the required jump is actually in the requested range!

The syntax for labels is the number of the label followed by either the jump instruction for references or the word @@: for definitions.

Normal Forth words may be called using FCALL <name> which will compile a call to the named word at that point. e.g.

10 # tos add

tos push \ dup

FCALL U. \ compiles a call to U.

But remember that calling other words may scramble your register set (particularly ECX and EDX) so protect them with pushes or some other form of storage

Register Usage

The top of the data stack is in EAX. ECX and EDX are free for any use you like. ESI and EDI are required by Windows to remain the same over a call from Windows to your code so any words which you may want to use in a callback function must preserve these. EBP and ESP are the data and return stack pointers. The segment registers are not used by Aztec.

A Few Examples of Assembly Language Definitions

All of these may be found in the assembler blockfile starting at block 45. They are not automatically LOADed from block 1 because you will want them to go into the application area if you do use them.

CODE: -ROT ( N1 N2 N3 -- N3 N1 N2)
EDX DPOP \ N2
ECX DPOP \ N1
TOS DPUSH
ECX DPUSH
EDX TOS MOV
;CODE
 
CODE: BIT@ ( BIT ADDR -- FLAG) \ RETURNS A BIT FROM A BIT ARRAY AT ADDR
EDX DPOP \ BIT#
EDX [TOS] BT \ SET/CLEAR CARRY
1 SHORT JC \ IF TRUE JUMP TO TRUE BRANCH
\ FALSE BRANCH
TOS TOS XOR \ CLEAR TOS
2 SHORT JMP \ JUMP TO END
\ TRUE BRANCH
1 @@:
-1 # TOS MOV
2 @@:
;CODE
 
CODE: BIT! ( FLAG BIT ADDR -- ) \ SETS A FLAG IN A BIT ARRAY
EDX DPOP \ BIT#
ECX DPOP \ FLAG
-1 # ECX TEST \ AND WITH FFFFFFFF
1 SHORT JNZ \ FLAG WAS NOT ZERO 
\ FLAG WAS ZERO
EDX [TOS] BTR \ CLEAR BIT
2 SHORT JMP \ TO END
1 @@: 
EDX [TOS] BTS \ SET BIT
2 @@:
TOS DPOP \ DROP ADDR
;CODE
 
\ A REPLACEMENT FOR "DUP @ SWAP CELL+"
CODE: @+ ( ADDR -- X1 ADDR+CELL) \ X1 IS CONTENTS OF ADDR1
[TOS] EDX MOV
EDX DPUSH
4 # TOS ADD
;CODE
 
\ THIS WORD REPLACES "SWAP OVER ! CELL+" AND IS MUCH FASTER
CODE: !+ ( X1 ADDR -- ADDR+CELL) \ STORE X1 IN ADDR1 & MOVE ON
ECX DPOP
ECX [TOS] MOV
4 # TOS ADD
;CODE
 
\ THIS WORD LOOKS THROUGH A STRING FOR A CHARACTER
\ IF THE CHARACTER IS FOUND ITS LOCATION (1 BASED)
\ IS RETURNED. IF NOT FOUND THEN A ZERO IS RETURNED.
CODE: VALID? ( CHAR ADDR U1-- U1 )
EDI PUSH
EDI DPOP \ STRING ADDRESS
TOS ECX MOV \ COUNT
TOS EDX MOV \ KEEP COUNT FOR LATER
TOS DPOP \ CHARACTER TO LOOK FOR
CLD \ WORK FORWARDS
REPNE DATA8 SCAS \ SCAN FOR MATCH (IE SCAN WHILE NO MATCH)
1 SHORT JNE \ NOT EQUAL SO NO MATCH
\ WAS A MATCH ECX HOLDS HOW FAR FROM RIGHT IT WAS
ECX EDX SUB \ USE ECX & EDX TO
EDX DEC \ CALCULATE LOCATION
EDX TOS MOV \ RETURN LOCATION
2 SHORT JMP \ AND RETURN
1 @@: \ NO MATCH
TOS TOS XOR \ JUST RETURN ZERO
2 @@:
EDI POP
;CODE

Floating Point Support

Floating point words are defined in the ASSEMBLER blockfile. The assembler words must be imported (with 1 LOAD) before loading the FP words. The core Floating Point wordset is imported with 66 LOAD and the extension wordset is imported with 101 LOAD. The FP words have an environmental dependancy on an 80387 or later floating point unit.

The default floatiing point format is 80 bit Intel extended real but the standard 32 and 64 bit IEEE formats may be read or stored. The floating point stack is only 6 deep as 2 of the registers are needed for internal calculations. This is the bare minimum for ANS but the alternative was to move things in and out of memory every time an FP word was used. If you need greater depth then this is what you will have to do anyway but at least for smaller calculations you have the faster option of using the FP hardware.

There is at least one non-standard FP word defined and exposed: F= . I don’t know why this is not in the standard, it is possible to define it with F- F0= but the hardware supports compares so I’ve used that.

Modules & Structures

The main zip file also includes a couple of blockfiles which are intended for use as sealed wordsets: modules and structures. It is intended that these are simply included with the USES” word.

Modules

The modules wordset defines the following words for manipulating the compilation wordlist:

The variables PRIVATEWID , PUBLICWID and GLOBALWID

PUBLIC: ( — ) Sets the compilation wordlist to the wid held in PUBLICWID.

PRIVATE: ( — ) Sets the compilation wordlist to the wid held in PRIVATEWID.

GLOBAL: ( — ) Sets the compilation wordlist to the wid held in GLOBALWID.

PRIVATE[ ( — ) Same as PRIVATE:

]PUBLIC ( — ) Same as PUBLIC:

(PRIVATE) ( — ) Sets PRIVATEWID to the first wordlist in the search order.

(PUBLIC) ( — ) Sets PUBLICWID to the first wordlist in the search order.

(GLOBAL) ( — ) Sets GLOBALWID to the first wordlist in the search order.

The search-order itself is accessed with the following words, some of which replace the obsolete F83 words which have managed to struggle into the ANS extension wordsets:

FORTH ( — wid ) Returns the FORTH-WORDLIST wid.

<ALSO ( wid — ) Adds wid to the end of the search-order (i.e., wid will be the last list searched).

ALSO> ( wid — ) Adds wid to the start of the search-order.

PREVIOUS ( — ) Removes the first item in the search-order.

ONLY ( wid — ) Makes the search order consist only of the item wid and the FORTH wordlist. The item wid is the first item in the new search order and is also set to the current compilation wordlist. PUBLIC etc are not affected by this word.

WINDOWS ( — ) Adds the DLLs KERN32, USER32, and GDI32 to the end of the search order (as per <ALSO).

The final group of words are intended to make working with sets of words easier:

START-MODULE ( — n*wid n publicwid privatewid globalwid 85216) Saves the current search-order and other context information, marks the top of stack with a magic number.

END-MODULE ( n*wid n publicwid privatewid globalwid 85216 — ) Restores a context. If the top of stack is not 85216 causes an abort” MODULE ERROR”.

VOCAB ( <NAME> — ) Creates a wordlist <name> which places itself at the start of the search-order if it is not already there. Does not change PUBLICWID etc.

Structures

The blockfile” structures” defines words which implement a restricted object-orientated structure package which supports overloading but not inheritance. The structures package uses (and automatically loads) the modules package.

The public words defined are:

STRUCTURES ( — ) Places the structures wordlist at the start of the search-order.

STRUCTURE: ( <name> — ) Starts the definition of a new structure type. Places the structure wordlist at the start of the search-order and then creates a new wordlist for the structure, places that at the start of the search-order and makes it the current compilation wordlist.

;STRUCTURE ( — ) Ends the defition of a new structure type and restores the search-order and the compilation wordlist.

>DATA ( xt — addr) Transforms the value returned by tick into the address of a structure’s data field.

EARLY ( — ) Manually sets the system to early binding.

LATE ( — ) Manually sets the system to late binding.

NEW ( <name> — ADDR) Creates an instance of the structure <name> on the heap and returns the address of the structure. Such a structure consists only of the data area, there is no context or size information.

KILL ( ADDR — ) Deletes the structure at ADDR if it was created with NEW.

CONTEXT[ ( <name> — ) Immediate word which sets the current context up to allow use of words private to the structure <name>. Mainly used to allow work on heap structures created by NEW.

]CONTEXT ( — ) Closes the context set up with CONTEXT[ .

Once a structure is started the following words are also available:

:: ( n <name> — ) Adds a field to the current structure definition. The field is n bytes long and is called <name>

CELL:: ( <name> — ) Equivalent to 1 CELLS :: <name>.

CELLS:: ( n <name> — ) Equivalent to n CELLS :: <name>.

Similar words are defined for doubles, words, bytes and characters.

ALIGNED:: Forces the next field to be aligned in instances of the structure.

+ALIGNED:: Forces the next field to start on the next aligned cell beyond the current point, even if the current point is itself aligned.

HAS ( <structure> — pfa) Places the structure <structure> into the current structure as a field.

CALLED ( pfa <name> — ) Names a structure sub-field introduced by HAS.

M: ( <name> — caddr len xt colon-sys) Starts the definition of a word which will be private to and act on the data of the current structure.

;M ( caddr len xt colon-sys — ) Ends the definition of a local word.

Note that factors of local words which do not act on the structure’s fields can be defined with : ; as normal.

Some Examples of Structures and their Use

STRUCTURE: POINT
  CELL:: X
  CELL:: Y
  64 CHARS:: LABEL
 M: NAME ( CADDR LEN ADDR -- ) LABEL 2DUP C! CHAR+ SWAP MOVE ;M
 M: . ( addr -- ) DUP LABEL COUNT TYPE SPACE
    DUP X @ . [CHAR] , EMIT SPACE Y @ . ;M
 M: ! ( x y addr -- ) dup >r y ! r> x ! ;M
;STRUCTURE
 
STRUCTURE: LINE
  HAS POINT CALLED START
  HAS POINT CALLED END
;STRUCTURE
 
LINE AB
40 30 AB START !
S" From" AB START NAME
20 13 AB END !
S" To" AB END NAME
 
AB START .
From 40 , 30 < 0 > OK 
AB END .
To 20 , 13 < 0 > OK

Leave a Reply

Your email address will not be published. Required fields are marked *