Forth Interpreter/Compiler

Version Française



A complete user's manual available FORTH Manual
Editable LibreOffice version I want to translate it!

It's a FORTH Editor, Interpteter and Compiler for the ATARI computers.
  • Version 68030 (Falcon, TT) and 68000 (STe)
  • Version in french or english
  • Support for ST and TT Ram
  • Support for FPU, but can work without.
  • Functions Gemdos, Bios, Xbios, VDI, AES
  • Support for the Supercharger as a coprocessor
  • Support for an external assembler
  • Support for the M&E Parx modules (image management)
  • Dialog with M_PLAYER/MP_STE for videos
  • Cooperative multitasking
  • Easy creation of menus or dialogs directly in FORTH
  • compact et fast, fully written in assembler
  • In-line help (in process...)


Version 0.4.2 (07/26/2022):
  • Dialog with M_PLAYER/MP_STE for videos
  • In-line help with help/guide and ST-Guide or HypView
  • Allows multitasking with separate pages and stacks
  • M&E modules management (load/save images + effects)
  • AUTOEXEC.FOR file
Some help with guide, here HypView is used The in-line help with help

An interactive language A coding program Download Including assembly Modules M&E Parx Multitasking M_Player/MP_STE
Create a menu Other FORTH programs All in one source file Image viewer Local variables & VDI stations Creating animations
TGA saving

An interactive language:

This language allows a direct mode, as on a calculator, but in witch you can create new words to enhance its capabilities.

On the right, let's imagine that you want to reduce a set of prices by 20%. Each time, you have to compute 80% of it. The first example gives the new price of an item at $59, it's now only $47.
Note that values are first pushed on a stack and then the operator works on those numbers, the period "." is the instruction to display the top of the stack. If you don't want to type again and again the same calculations, you create a new word reduc using the pair : ; and it is used to compute the new price for $70, the result is $56.
You can even type a little program using here the structure list..dolist..lloop that is a loop whose indexes are an unsorted list of numbers.
Inside the loop, the index i is displayed (the original price) then a space and then the reduced price (cr is a carriage return to jump to next line)

system is the instruction to quit to the desktop.

Usage in direct mode



This word uses one signle variable!
A program to code a message

Let's write, step by step, a GEM program with a menu to code or decode a text message. First, the heart of the program...

Let's code a word that will move each uppercase letter of the alphabet several positions ahead or back. To do this, only one variable is required: p a pointer that will go through the string and test every character.

  • p ! : store the string address into p
  • .b size : we will work on bytes
  • begin/while/repeat : to parse the string
  • p )@ : returns the character pointed by p
  • while : as long as it is not zero
  • dup 65 90 <seg> if : is it from A to Z?
  • over + : if so, shift it by n (relative value)
  • dup 90 > if : if above Z, return down into the alphabet
  • dup 65 < if : if below A, return up into the alphabet
  • p )+! : store the character, modified or not, and increment the pointer p
  • drop : removes n from the stack


Let's use our new word

We will use a predefined string named pad that the Forth uses for some string results.

  • We store the string "THIS IS SECRET !" into pad
  • Then we perform a shift of 17 positions in the alphabet using 17 pad code
  • pad type display the new string
To decode the message, we could use -17 pad code, but...

To decode a message without the key, we can just try the 25 differents shifts, that's done with the following structure 25 ndo..nloop.
  • 1 pad code performs one new shift
  • pad type cr display the text and jump one line
You can see the message appearing on one line (it's a partial copy of the screen).
>



Version usable within a program.
Codage avec prompt

Let's enhance a bit the system. I want the program to clearly ask for the key and the text. That is realised with the word code-decode.
  • 15 >ifflag : uses a conditional compilation, flag 15 is the one of the current language. It is true in the french version and false in the english one.
  • ." Key : " input cr display Key, wait for a number pushed on the stakc and jump one line
  • ." Text : " input$ cr idem but with a string stored in pad
  • code performs the desired coding
  • pad type cr display the new text


Raw search

Similarly, let's program the raw search with the 25 shifts when you don't know the key into the word multi-decode.
  • ." Text : " input$ cr drop , this time, the pad address is dropped from the stack as it is not used directly.
  • 26 1 do..loop will loop from i=1 to i=25 (it stops before 26!)
  • i . space display the index and a space
  • 1 pad code shift the message by one
  • pad type cr display the message and jump one line




Let's create the menu !

No need to use a RSC file to create the menu, the FORTH do it for you using only an array of strings.
  • 30 10 array$ MENU create an array of 10 strings with 30 characters named MENU
  • 16 allot constant BUFFER allocates the little AES buffer (for evnt_mesag)
  • The array MENU is filled
  • first the title strings and an empty one
  • then for each drop-down menu, the entries followed by an empty one
  • 0 MENU menu, the menu instruction creates the object tree into RAM and returns its address stored into TREE
  • 7 gemindex 0 TREE menu_ienable used to disable string 7, the separator.


One last effort

This little word to react to the Infos... entry of the menu.

It's a simple alert box.




The main loop!

It's a classical loop to manage the AES events.
  • fastopen drop cls open the window, drop the handle and clear screen.
  • 1 TREE menu_bar display the menu bar
  • begin...until loop until a non zero value is returned (only by Exit entry)
  • begin...until wait for the good event
  • BUFFER evnt_mesag call the AES and wait for an event
  • BUFFER w@ 10 = Is the first word from the buffer equal to 10? (menu selected)
  • BUFFER 8 + w@ if so, this returns the GEM index of the selected entry
  • strindex convert it to a string index, easier for the programmer
  • case...endcase manage the four possible cases, only Exit returns 1 to exit
  • BUFFER 6 + w@ return the GEM index of the menu title that is still in reverse video
  • 1 TREE menu_tnormal and turns it back to a normal state
  • 0 TREE menu_bar when quitting, remove the menu bar.


Auto-run or not

I want that, when under the interpreter, the compilation doesn't run the program to be able to test some parts individually in interactive mode.

On the other hand, when creating a standelone program with the compiler, I want the program to be run!

That's again a conditional compilation using flag 13: this one is true under the interpreter and false under the compiler.
Note: a stadonlone program removes the mouse at start, so v_show_c was added to get the pointer back.




A message is coded and then a raw search is run.
Our program in action !

When the program is run, it displays its menu and window.

You can select the action and test it..

Partial view of the raw search.


Downloads

FORTH Interpreter and Compiler in french and english.
Versions 68030/68000, binaries and sources.

PARX M&E modules for image management.
With the kind permission of Pierre-Louis Lamballais
and Eric Da Cunha.


Forth-Code: source and binaries for the coding program presented here.




Other examples in FORTH

Link with the
HP-41 using its serial interface.
Menus, serial communication

How to use the Forth instruction to drive the Supercharger as a coprocessor.
Include PC assembly, Supercharger instructions

Save and retore data on the BOSS-DR5 via MIDI.
Menu, file, MIDI communication

RTC Clock manager for the Apollo Vampire V4.
Include assembly, dialog creation, dialog in a window




Mixing Forth and Assembler

You may want to replace code by an assembly routine. This is really simple:
  • Prepare a routine starting with a 16 bits word 437. This value tells the Forth that what follows is machine code.
  • The DATA section must start with "ADD_DEFS" on 8 bytes
  • Then every routine address is listed, ending with -1
  • Then every word name is listed with a null byte as separator.
  • You assemble this source to F:\_TEMP.PRG" for example
  • Replace the definition of code by
  • You just have to preserve A4 and A5 and use A6 as stack pointer


Even better: all in one source!

The assembly text code and the PRG generation can be driven directly from Forth. Here is how:
  • Inside your Forth listing, the directive >export (1) saves the text until >comp (2) under the specified name: here F:\_TEMP.S.
  • Then, the directive >exec (3) runs the assembler using the next line as a command line.
  • Last, the PRG being available, it is included using directive >include (4).

During compilation, you'll see the activity of your assembler:


Note: the null argument before every directive can be used in conjunction with a flag to decide wether to execute or ignore those orders. For example, if you're working on the Forth part, you don't have to save an generate the same PRG at every compilation.




Parx Systems' M&E modules: reading a GIF image

This program loads a GIF image with the module GIF00.RIM and converts it to the current screen with the module PARX252.TRM whatever the resolution.

Variables definition
variable p       \ RIM module
variable t       \ TRM module
variable fichier \ address of file in memory
variable total   \ total size of file
256 string filename		  \ path+name
1536 allot constant PALs  \ palette source
1536 allot constant PALd  \ palette destination
variable fhd		\ file handle
Every module is pointed at by a variable.

The palettes are at their maximal size to receive 256 colors in VDI format:
256 colors × 3 components × 2-bytes word (0 to 1000) = 1536.
Loading modules
: main
   " d:\parx.sys\" 1 modset

   0 t !
   " parx252.trm" graphic_card negate t modload drop

   0 p !
   " rim\gif00.rim" -1 p modload drop
  • modset: set the PARX folder path
  • modload: load the dithering module PARX252.TRM
    graphic_card negate allows FORTH to adapt the TRM to every graphic mode
  • modload: load GIF00.RIM, to read a GIF
GIF loading and decoding
   " F:\FORTH\GIPHY.GIF" filename $!

   filename 0 fopensize dup fhd !
   0> if
      total !
      fhd @ total @ dup allot dup fichier ! fread drop
      fichier @ total @ filename fhd @ PALs 1 p dorim drop
  • fopensize : opens the file GIPHY.GIF
  • fread : if open is ok, then loads the entire file in memory
  • dorim : runs the RIM for decoding

The image in 16 bits
Dithering for current screen mode
      mfdbs mfdbd fillmfdb
      work_out 2- w@ mfdbd 12 + w!
      %b1100110 1 PALs PALd 3 t dotrm drop
  • mfdbs/d : are images descriptors used by the modules
  • fillmfdb : duplicates the source descriptor to the destination
  • work_out 2- w@ : and force destination to the current number of planes found at this address
  • dotrm : runs the TRM to adapt the bloc to the screen
Blitting to screen
      mfdbd mfdbs fillmfdb
      PALs savevdipal
      PALd setvdipal
      0 mfdbd ! 0 0 0 0
      mfdbs 4 + w@ mfdbs 6 + w@ 3 vro_cpyfm
      key drop
      fhd @ fclose drop
      PALs setvdipal
   then
   p modunload
   t modunload
;
  • fillmfdb : the destination becomes the new source
  • savevdipal : save current palette in PALs
  • setvdipal : set the palette to PALd, the one of the new image
  • 0 mdfdb ! : screen is the destination
  • vro_cpyfm : blits my image to the screen
  • key : waits for a key
  • fclose : close the file
  • setvdipal : back to current palette
  • modunload : frees the modules

With the same program, the image in 4 bits
If the program is compiled
>comp

-13 >ifflag
   fastopen drop
   main
>endf
  • >ifflag : conditional compilation, skipped by the interpretor (flag 13 makes the difference between interpretor and compiler)
  • fastopen : opens the FORTH window
  • main : and calls my program

With the same program, the image in monochrome



Parx Systems' M&E modules: universal image viewer

This program will load the whole set of RIM modules in an array and will be able to load any image and display it in any resolution.
It is an extension of the previous one, so we'll only detail the differences with the array management.

Variables definitions
variable t       \ TRM module
variable fichier \ address of file in memory
variable total   \ total size of file
256 string filename
256 string chemin
32 string nom
1536 allot constant PALs
1536 allot constant PALd
20 allot constant MFDB
variable fhd
100 array MODS	  \ the array of pointers to the modules
variable nMODS	  \ the actual number of modules
  • chemin/nom : two additional strings for the fileselector
  • MODS : array of 100 pointers to the modules
  • nMODS : will contain the actual number of modules laoded
Selecting the image
: main
   cls
   " \*.*" chemin $!
   " " nom $!
   chemin nom fsel_input
   chemin nom path filename $!
   cls
  • $! : stores a default path in my strings
  • fsel_input : opens the fileselector
  • path : builds a full pathname from the two strings
  • cls : clears the window

The selected file
Loading the modules
   " d:\parx.sys\" 1 modset

   ." Charment des modules..." cr

   0 t !
   ( " parx252.trm" graphic_card negate t )
      modload drop

   0 0 MODS !
   ( " rim\*.rim" -1 0 MODS )
      modmload .. nMODS !

   ."  modules chargés" cr
  • modmload : mod multi load load every *.rim in the array
  • nMODS : upon return, this variable receives the number of modules
Loading the image
   ." Chargement du fichier..." cr

   filename 0 fopensize dup fhd !
   0> if
      total !
      ( fhd @ total @ dup allot dup fichier ! )
         fread drop
Same as before, the whole file is loaded into memory.
Finding the image type
      nMODS @ 0 do
         -1 mfdbs fillmfdb
         ( fichier @ total @ filename fhd @ PALs 1 i MODS )
            dorim
         0<
      ifloop
  • do : starts a loop from 0 to nMODS-1 to parse every module
  • dorim : tests module i
  • ifloop : loop while there is an error (negative value)
  • ifloop : else put on the stack the correct index
Displaying the type
      dup nMODS @ =
      if
         drop
         ." Image non reconnue" cr
      else
         MODS modlname type cr
         key drop
  • dup nMODS @ = : compare the index to nMODS
  • if equal : then no module matched the file, it is unknown
  • else : modlname displays the long name of the good module
  • key : waits for a key

Number of modules and the one that matched
Displaying the image
         mfdbs mfdbd fillmfdb

         work_out 2- w@ mfdbd 12 + w!

         ( %b1100110 1 PALs PALd 3 t )
            dotrm drop

         mfdbd mfdbs fillmfdb

         PALs savevdipal
         PALd setvdipal

         ( 0 mfdbd ! 0 0 0 0
         mfdbs 4 + w@ mfdbs 6 + w@ 3 )
            vro_cpyfm
            key drop
         PALs setvdipal
      then
      fhd @ fclose drop
   then
   0 MODS nMODS @ modmunload
   t modunload
;

>comp

-13 >ifflag
   fastopen drop
   main
>endf
The end of the program is similar to the previous one.

The TRM is called to adapt the image to the current screen mode.

The bloc is displayed with vro_cpyfm.

modmunload : mod multi unload frees all the modules from the array.

The TIF image in 16 bits



Parx Systems' M&E modules: saving to a TGA fila

This programs copies a 320x200 screen block in the current screen mode and save it to a TGA 16 bits image.

Variables definition
variable p       \ WIM module
variable t       \ TRM module
1536 allot constant PALs  \ palette source if needed
1536 allot constant PALd  \ palette destination (unused in 16 bits)
The same as before, each module requires its own variable.

Every palette has the maximum size for 256 colors.
Loading modules
: main
   absolute
   " d:\parx.sys\" 1 modset

   ." Loading modules..." cr

   0 t !
   " parx252.trm" graphic_card negate t modload
   if exit else ."    TRM Ok" cr then

   0 p !
   " wim\tga_tc16.wim" 1 p modload
   if exit else ."    WIM Ok" cr then
   
   remember
  • absolute : turns VDI coordinates in absolute mode (relative to the top of the screen)
  • exit : modload returns a non zero value if it fails, then exit stops the execution.
  • graphic_card : this same tips to allow the TRM to turn on graphic cards correctly.
  • tga_tc16.wim : the module for saving TGA 16 bits images
  • remember : keeps in memory the current FORTH RAM pointer to free every block at the end
Copying a screen block


   ." Block 320x200 with palette" cr
   0 mfdbs !
   0 320 200 work_out 2- w@ 0 mfdbd fillmfdb
   mfdbd imagesize allot mfdbd !
   0 0 0 0 320 200 3 vro_cpyfm
   PALs savevdipal
   mfdbd mfdbs fillmfdb
  • 0 mfdbs ! : defines the screen as the source
  • fillmdfb : prepares destination in 320x200
  • imagesize : computes the size of the image and reseve the memory with allot
  • vro_cpyfm : copy the block from upper left corner of the screen to memory
  • savevdipal : save current palette
  • fillmfdb : the dest becomes the new source for the following instructions

What the program displays
In case of a graphic card
   ( graphic_card
   work_out 2- w@ 4 8 <seg> ) and
   if
      ." Converting to VDI mode..." cr
      1 mfdbd 10 + w!
      mfdbd imagesize allot mfdbd !
      vr_trnfm
      mfdbd mfdbs fillmfdb
   then
If the computer has a graphic card and the currunt mode has 4 or 8 planes, then we must turn the bloc to the VDI independant format, else the TRM won't work.

With the TC modes (15, 16, 24 and 32 bits) it perfectly handles the blocks.
  • 1 mfdbd 10 + w! : forces the destination to VDI format (1)
  • imagesize : recomputes the size for a new bloc
  • vr_trnfm : transforms the block
  • fillmfdb : the dest becomes the new source for the following instructions
Turning the block to 16 bits
   ." Converting to 16 bits..." cr
   16 mfdbd 12 + w!
   %b0100011 1 PALs PALd 3 t dotrm
   if exit else ."    Dotrm Ok" cr then

   mfdbd mfdbs fillmfdb
  • 16 mfdbd 12 + w! : forces to 16 the number of planes in the destination
  • dotrm : calls the TRM to transform the block
  • fillmfdb : the dest becomes the new source for the following instructions

The image obtained from a TC screen (displayed by GemView)
Saving to TGA 16 bits
   ." Saving TGA 16 bits..." cr
   " I:\AAA.TGA" PALd 1 p dowim
   if exit else ."    Dowim Ok" cr then
Now that the block has the correct number of planes, a last call to dowin will run the module to save in TGA 16 bits.
  • dowim : runs the WIM to save the image

The image obtained from a TT Med screen (displayed by GemView)
Cleaning memory
   restore
   t modunload
   p modunload
   relative
;
  • restore : restores the FORTH RAM pointer to what it was when remember was called
  • modunload : frees the modules
  • relative : turns back VDI coordinates to relative mode (relative to the FORTH window)



Cooperative multitasking : a calculus example

The idea is to create several threads with the word thread. Each one is defined by a page, a personal stack, a unique identifier and the word to be executed. Once the list is created, you use thrun to execute the threads, each one hand over to the next using thnext. They also can be synchronized with thsync to exchange data for example, each one having an access to the stack of the others.

In this first example, we'll compute the sum of integers from 1 to n sharing the work into three processes: The last thread will be in charge of collecting data and displaying the total sum. Note that the very first value of each sum is equal to the ID number of the thread, we'll use this trick in the word somme.
Variables definition:

n will contain the limit of the sum, and the three stacks for the threads. 10 integers each, this is more than enough.
Computing a sum:

This same word will be used by the three threads.
do opens the loop from thid (the identifier 1,2 or 3) to n+1
3 +loop sets a step of 3
after each addition, the thread hands over to another with thnext
Code for threads #1 and #2:

Here is the code executed by the first two threads, the slaves. While the value n is not zero, they compute their sum and wait for synchronization on channel zero with 0 thsync to send their result before starting again.
Code for thread #3:

Here is the code executed by the last thread, the master.
  • 3 0 thnsync specifies that 3 threads are to be synchronized on channel 0.
  • input gets the limit, stores it into n, and if non zero...
  • somme calculates the sum for this thread
  • 0 thsyncmain waits for synchronization on channel 0 and takes control
  • PILE1 st> + gets the result from the stack of thread #1
  • PILE2 st> + the same with thread 2, and then displays the global sum.
The main program:

Each thread is defined, they all share page0 as only one actually writes to the screen. They have a personal stack, an ID that looks pretty to simplify our program and the name of the word that they will execute.

The last defined thread is the first to gain control when thrun is executed. That ensures that our master program will be the first to pilot and synchronize the two slaves.
Executing the program:

Main runs the three threads.

The user asks for the sum from 1 to 100 that evaluates to 5050, then the sum from 1 to 2022 evaluated to 2.045.253.

To stop, a zero tells the program to end, the three threads exit and are removed from the list.



Multitasking: local VDI stations and variables

In this next program, each thread will own a personal VDI workstation with its own graphic settings. In the same way, we'll use local variables that allow to have a different value for each thread under the same name. A same piece of code can be used by different threads.

We'll divide the FORTH window into 3 columns. The first one reserved for the direct mode, the remaining two for the threads. One of them will display lines in color with REPLACE mode and the other black lines with a XOR writting mode.

Look at the direct mode, the program was run with main, then multitasking was stopped with Ctrl+Alt+Shift and the tasks list was displayed with thstat. You can resume execution where it stopped with thrun.
Variables definition:

Here is how FORTH does cope with the problem of local variables, for example: 3 4 thvariable X1 creates an internal array of three values of 4 bytes each that is automatically indexed with the current thread identifier.
In direct mode, ID=0, so X1(0) is accessed, using thread 1, it's X1(1) that is used, and so on.
You have a total liberty to give an identifer to your threads, but when using local variables, consecutive values starting from 1 are far better.
Stacks and pages:

  • Two stacks, PILE2 and PILE3 are defined for the threads.
  • Three pages are defined
  • setsubpage tells that page0 will be shared in 3 columns and one row
  • getsubpage gets the coordinates of every zone to define pages
  • setpage sets PAGE1 for the direct mode
Words common for both threads

Init a drawing:

To start, we need two points X1,Y1 and X2,Y2 as well as a displacement dX1,dY1 and dX2,dY2 to go to the next line.
Coordinates are picked at random from 0 to Xmax/Ymax (size of the thread's page) and the movement from 1 to 8 pixels.

Bouncing:

If a point exits the page rectangle, the word bounce changes the sign of the displacement and recomputes the position.
One step ahead:

This word moves both pixels X1,Y1 and X2,Y2 and verifies that the pixels are inside the page.

Note that in FORTH, by default coordinates are relatives to the current page. This way, no offset has to be managed by the user, the upper left corner of every page is always 0,0 from the programmer's point of view.

Note that the with the case stucture you can check for other things than equality. 0 <of checks if the value is negative and Xmax @ >of checks if the value is greater than Xmax.

Calling v_pline draws the line.
Code for thread #2:

This is the one that draws black lines.
  • vsl_color set color to black
  • vswr_mode set writing mode to XOR
  • begin/again enters an infinite loop
  • init initialize the drawing
  • 100 ndo/nloop for 100 lines
  • onestep one step and one line drawn
  • thnext hands over to the next thread
  • wait before a new drawing, waits for 2 seconds regularly handing over to the second thread.
Code for thread #1:

This one draws lines in color.
  • vswr_mode set writing mode to REPLACE
  • begin/again enters an infinite loop
  • init initialize the drawing
  • 10 ndo/nloop loop for 10 times 16 lines, this is 160 lines
  • 16 0 do/loop allows to set the color index from 0 to 15
  • vsl_color set color to i
  • The rest that uses onestep, thnext and wait is similar to the previous code.
The main program:

Every thread is defined not with thread but with vthread. This alternative opens a new VDI workstation for the thread allowing it to set its own graphics needs.
thrun initiates the multitasking mode and the drawings start in the two rightmost columns.



Dialog with M_PLAYER/MP_STE (and ANIPLAY)

FORTH provides an intruction set to dialog with video players.

The player must be installed as an accessory.

Then you can get informations about a video file, replay it or even create a video using a FORTH word that generates the images !
  • vd_set tells FORTH to use the specified player for the following instructions. (can be used with ANIPLAY)
  • vd_info returns informations about a video without replaying it. You can get:
    • The size of the image and the compression used
    • The sound characteristics (bits, frequency, channels)
    • If the player supports or not those codecs
    • The detected type of video
    • The version of M_PLAYER


Here, only the video type is displayed


The options -d (don't display dialog boxes) and +e (show errors) are used.
fastclose and fastopen close the FORTH window during replay to avoid conflicts.
vd_play runs the animation replay with eventual options. (Can be used with ANIPLAY)



Creating animations

vd_create enters a parallel mode with the player. It's M_PLAYER that builds the animation and, for each frame, it runs a FORTH word that generates the pixels. This feature is not available with ANIPLAY.
Here is a very simple example. The animation will be in 120×96 and 16 bits. At start, this is a black image (filled with nul pixels) and each time a new frame is required, the FORTH word fills 4 lines with white pixels from top to bottom (480 pixels equal to FFFF). At the end of 24 images, 4×24=96 lines will be filled, the last image is totally white.
The word TEST fills 480 white pixels and returns, using vd_frame, the image address and the format (Intel or Motorola).

vd_create parameters are:
  • 'test %h8101 the FORTH word name and zoom code
  • " I:\TOTO.MOV" 16 : the output file name and MOV 16 bits
  • 120 96 24 size and number of frames
  • 1 5 100 informations on key-frames, quality and timing
Upon return, you get your animation on disk !


While the video is created, you can display a progression dialog



The created animation can be viewed !
Using MP_STE, you get a simpler interface when creating a movie.

Note that, for now, the options of vd_create with MP_STE are reduced comprared to M_PLAYER.


15 décembre 2007