home *** CD-ROM | disk | FTP | other *** search
-
- A4TH.DOC Notes on..
- -------------------
-
- A4TH. A Public Domain Forth system for Amiga's.
- (based on Laxen & Perry's F83)
-
- This Forth system is Public Domain. You may freely distribute,
- copy and use it, for any legal purposes. I cannot be held
- responsible for any errors and/or omissions, I do not warrant
- this system. I bear no responsibility for any use or abuse,
- with or without intend.
-
-
- The system is composed of three 'arced' files:
- ( Arc program: AmigArc version 0.23, compatible with ARC v 5.0 )
-
- A4THSYS.ARC The system and system source files.
-
- A4th The executable kernel.
- Akernel.blk The source for the kernel.
- Util.blk The Utilities to extend the kernel.
- Cpu68k.blk Assembler and debugger support
- Meta.blk The Meta compiler source.
- Include.blk The Amiga structures support source
- Tasks.blk Task support source.
- README Small warning that the file Util.blk has to be
- renamed to Utilities.blk before using the system.
-
- A4THINC.ARC The compressed and include files to be used with Include.blk
-
- D1.ARC The Dx.ARC files are 'arced' directories.
- D2.ARC
- D3.ARC
- D4.ARC
- D5.ARC
- D6.ARC
- D7.ARC
- D8.ARC
- names A note on naming the romcalls and structures.
- readme Instructions on how to create the include files.
- unpack.bat Execute this batch file to recreate the include files.
-
-
- A4THDOC.ARC Documentation and examples.
-
- A4th.doc This file.
- metavocs.txt Multiple vocabularies using Meta compiler.
- infix.txt How the infix parser works.
- hello.world example from intuition manual.
- dotty.blk dotty window as in demo
- dotty.c the example for dotty.blk
- gad.blk gadget example
- gad.c the example for gad.blk
- Small.blk A real small hello
-
-
- How to 'un-arc' the system.
- ---------------------------
-
- I'll describe the way I have my system set up. It could work for you also.
-
- Start off with a Workbench disk, with some room on it. Discard what you
- think will not be usefull. You will need minimally 25% space on it. That is
- what it will take for the include files. Please leave most of the files in
- the c directory, enough to execute a batch file.
-
- Create a directory on it, I call mine Includes, but you can use any name.
- Move the file A4THINC.ARC to that directory.
-
- Copy your favorite arc utility to your C: directory and name it 'arc'.
-
- Unarc A4THINC.ARC. One of the files will be unpack.bat, which will restore
- all the directories and files in the directories for you. Execute it and
- let it go.
-
- In your startup-sequence routine enter the following:
-
- assign i: df0:includes
-
- or the directory name you have chosen. That will make access to the include
- files simple from anywhere. ( example: tload i:exec/ports )
-
-
- Format a new diskette and move the files A4THSYS.ARC and A4THDOC.ARC to it.
- Unarc the files, they are simple arced files, not directories.
-
- The file Util.blk in the A4THSYS.ARC file must be renamed to Utilities.blk
- before you run A4th.
-
-
- In my setup (Amiga1000, 2drives, 512k) I use an interlaced screen
- (640x400). That way I can have two A4th systems going at the same time, or
- run A4th with a text editor, behind which Dos resides of course. Several
- advantages, the biggest one is that I have twice the information in front
- of me. I have been using an interlaced screen for some time now, and when I
- switch back to a 640x200 display I feel hemmed in. If I enlarge the A4th
- window to cover 640x400, I can dump more at the same time, see more words
- at the same time etc. I did not change the editor to take advantage of the
- large screen, it still expects 640x200, but will work in 640x400.
- I also copy some commonly used routines to a ram: disk in order to speed up
- some operations. My startup-sequence is as follows:
-
- echo "A4th disk. Release 1.2 version 33.47"
- echo " "
- echo "current date"
- date
- echo "enter correct date"
- date ?
- date
- date to df0:s/last-date
- stack 8000
- addbuffers df0: 25
- addbuffers df1: 25
- makedir ram:c
- copy c:dir ram:c
- copy c:type ram:c
- copy c:cd ram:c
- path ram:c
- BindDrivers
- assign i: df0:includes
- cd df1:forth
-
-
-
- Starting the kernel.
- --------------------
-
- FROM THE CLI, of course, you can start it by entering:
-
- A4th
-
- by itself, and the kernel will execute. After it is loaded, you can load
- the Utilities by entering, in the forth window:
-
- open Utilities.blk ok
-
- which will load the extension as listed on screen 1 in the file
- Utilities.blk.
-
- Doing it this way will give you 64k bytes of space to use for anything
- defined. The utilities will be located in that area, and take about 32k if
- the distributed version is loaded as is.
- Another problem is that you will loose the services of Amiga Dos. This
- system does not have a directory utility for instance. It relies on the
- Amiga multitasking capabilities for that.
-
- Better to start A4th as a seperate task:
-
- run A4th 18000 open Utilities.blk ok
-
- That will open a 96k byte usable space and load the Utilities in. Make a
- batch file out of it if you like. The number is a hex number.
-
-
-
-
-
- About the system.
- -----------------
-
- GENERAL.
-
- Although the excellent Public Domain system from Laxen & Perry was a
- starting point, this system is NOT the same NOR is it F-83 standard. A
- lot of the utilities present in F83 are also present in A4th.
-
- If you expect lightning speed or super compactness, you will be
- disappointed. It does not crawl, on the contrary, I find it responsive.
- But then I might be a little prejudiced. :)
- Try it out.
-
- If you want support, or more speed, or compactness, or all of these, I
- would suggest you part with (about) $100 and buy one (or both) of the
- commercially available systems:
-
- JFORTH Multi-Forth for Amiga
- Box 1051 Creative Solutions Inc.
- San Rafael, CA 94915 4701 Randolph Road, Suite 12
- 415-485-6867 Rockville, MD 20852
- 301-984-0262
-
- Both of these are excellent systems and well worth the (just under)
- $100.
-
- Why did I write this, if the above commercially available systems are
- so good? Well, first of all, I like to be in control of the entire
- system. Secondly I cut my Forth teeth on L&P F83, like so many others,
- and was looking for something similar.
-
- The above mentioned systems use different threading schemes then the
- L&P.
-
- ( If you are not entirely in agreement with the way this turned out,
- you can always change it! And if you think the idea is good, share
- it with the rest of the Forth community. )
-
-
- The system features:
- Indirect threaded
- All numbers default to the address size of 32 bits.
- Stack works on 32 bits at a time.
- Uses two section, a precompiled kernel loaded by Dos, and a
- usable user dictionary, size of which can be specified at
- startup.
- Word dictionary is inline, same as L&P, with 32 bit links.
-
- Since the Amiga is multitasking and has no hardware memory management,
- each program that is to be run must be in a special loader format. It
- will not be run in the same location, and therefore needs relocation
- information with the file.
- That makes it impossible (well not really, nothing in Forth is
- impossible) to save the current version from within the system. In
- order to create a new kernel, you will have to Meta compile the kernel.
- The Meta compiler saves the system in the proper format.
-
- There are ways around this of course, but I chose not to use any of
- them. For instance, use your own loader, or use a different threading
- scheme.
-
- Finally I tried to make a system that doesn't use all of the cpu's
- registers. Naturally I set aside the Forth register:
- SP(a7) RP(a6) IP(a5) W(a4)
- The only additional register I used is >next(a3) which is the address
- of the next routine. All other registers are available.
- To add to that, another 'goal' of mine was to make it possible to read
- some program in one of the Amiga Manuals and convert it for Forth.
- Since most of the published stuff for Amiga is in C, I made some moves
- towards that area, instead of staying more on the Forth track. (see the
- infix parser in the include.blk file.)
-
-
- KERNEL
-
- - This kernel supports multitasking, using Amiga Exec, see also TASKS.
- Some variables need to be different for each task. This kernel puts
- these task or as they were called in L&P, user variables ahead of the
- next routine.
- The advantage is that no additional register needs to be used. In L&P
- UP was used as the base address for the tasks. Here I use >next which
- is cpu register a3 as a base register. The variables are located ahead
- of the next routine. The disadvantage is that each task needs its own
- next routine, as it turns out that is exactly what I wanted. One has to
- metacompile in order to increase the number of 'user' variables.
-
- The 'rcall' routine is now (as opposed to version 1.0) right after the
- next routine. It is the bases for calling Amiga Rom routines.
- It is a bit of self modifying code (heavens!). Because of that it
- cannot be used by any other tasks. Each task will need its own copy.
- Since it is now directly after next, it can be copied to any new task,
- together with 'next' and task, or 'user', variables. All that the new
- task will have to do is change the >next register to point to its own
- 'next' routine. (Of course the other registers must also be set).
-
- The 'callrom' routine on screen 81 is the routine that sets the code in
- rcall. It uses relative addressing (relative to >next), each task will
- thus use their own rcall, but can use the common callrom.
-
- Calculating the address of a task variable is a matter of adding the
- >next register to the offset. The same is true for task deferred words.
-
- - You realize that having Exec provide the task switching releaves the
- kernel of the 'pause' word, that was used in L&P.
-
- - The cmove and cmove> and also fill routines are limited to 64k bytes.
-
- - In the previous version, I used a stdbuffer for output of single
- characters and multiple characters, such as spaces.
- In this version I use pad for this purpose. Stdbuffer is no longer.
-
- - Note that the common character output routine is 'type', not 'emit' as
- in L&P. In other words, emit calls type, in L&P type called emit.
-
- - In the kernel you will find, on screen 67, 68 and 75 some references to
- words having brackets around them. For example <find>. These words are
- a way to Meta compile in different vocabularies, see Meta compiler
- explanation for more info. Normally these have no effect on finding and
- defining words.
-
- - Doubles are really 64 bits wide. */mod is internally 64 bits. Large
- enough to not need floating point? But you can use Amiga Rom math if
- you want to.
-
- - Defined is now deferred, and defaults to (defined). It is used in
- structure support.
-
- - Of course I have added support for the task variables. Screen 79.
-
- - In the previous version I had a linked list, linking all the declared
- Amiga Library bases together. When the system was exited the list was
- traversed and each library was closed. That caused some complications
- when defining a library base.
- In this version I have made an array of opened library bases, or
- devices for that matter. When the system is exited the array is checked
- and each open library is closed.
- Access to each base is via an index, which are constants with names
- referring to the library, such as >Exec, >Dos and >Intuition.
- These three are opened by the kernel,as they are needed to run the
- kernel. One more is defined, >Graphics, but is opened in the
- Utilities.blk file: it is not required by the kernel, or the Utilities
- for that matter. It is opened as a convenience. (screen 81)
-
- - These library base indices are used to define an Amiga Rom routine. On
- screen 83 are some examples. The registers used in each call are
- defined between (r and r), which simply sets the base to hex and treats
- the registers as hex numbers. It then calculates the mask. No specific
- order of registers is required, I put them in the order they will be
- filled from the stack. In the first call defined, OpenLibrary, the name
- will be stuck in the a1 register, the version in d0.
- The offset and the flag indicating a return value (^) are assembled
- into three words, as tight as I could make it.
- In the Utilities.blk is support in the decompiler to 'see' these
- defined rom call entry points.
-
- - The change in (type), screen 88, was to accomodate the task variable
- #out. Before it was not a task variable. Now it is, so it needs to be
- calculated at run time. It could be used by any task. This is easily
- done in high level. At the end of (type) you can see I switch from code
- to high level using 'c:' but I don't return to code, I simply end the
- word as if it was a colon definition. Works ok.
- Similarily, here and pad are now also in high level, to let the system
- calculate the proper address at runtime.
-
- - To see an example of opening a library look at screen 91, or screen 98.
- That is the only time you need to be concerned with the library base.
- Most of the routines are already declared in the include files, later.
-
- - In the Intuition vocabulary I have added the romcalls, AllocRemember
- and FreeRemember. They support allocating memory and freeing it again.
- A linked list pointer, mem-link, helps to find them. When forgetting
- the list is traversed and any allocated memory, about to be forgotten
- will be released before access to the RememberKey is lost. Tasks use
- them. This method is available to any.
- When exiting A4th close-mem will release all.
-
- - Some new stuff in screens 99-101. The first bunch is some bitfiddling,
- used in Meta, but available for any bitarray. The words >> and << are
- here to make it a little easier to translate programs from 'C' to
- forth.
- Next is a random number generator. I am no statistician, works ok for
- me, if you have a better one, let me 'ave it.(pleese). Randomseed is
- there to be used, if you need variety.
- Tinit is a small code routine used by tasks. I put it here, in order to
- be able to not include Cpu68k.blk. That would leave out the assembler
- and the debugger. Still enough of the system to be usefull, but
- significantly smaller.
- The last ones are some C like string support, that is strings without
- the length byte and with an ending null byte.
-
- - I have added a 'fence' variable. You can set it to prevent forgetting
- by mistake.
-
- Forgetting now traverses the file linked list, the mem linked list and
- the vocabulary linked list.
-
- - The window declared in the Intuition screen is now called 4thw,
- previous version was Window, which collided with the Window structure
- declaration in the Intuition include file.
-
- - There are now dp0 a variable holding the address of the start of the
- usable dictionary, and dpsize a variable holding the size of the usable
- dictionary area. Before they were simple stored in some 'unnamed'
- cells.
-
- - The cold start routine now sets the task variable dp, which has to be
- the first task variable.
-
- - User or task variables are set by temporarily setting the (meta) dp to
- the user area, then filling the locations. This requires that all the
- task variables are set. Screen 111
-
- - All variables are now set in screen 113, again when meta compiling.
-
-
-
- META
-
- - Gone is the dump utility, use the kernel's by not forgetting it. Forget
- 'out' should be sufficient.
-
- - This Meta compiler supports vocabularies in the target. An additional
- file, METAVOCS.TXT, explains it more.
- What the end result is, is that you are now able to declare
- vocabularies in the target, and declare the same word in different
- vocabularies. You will get the one you want if you control the target
- search order correctly. This is particularily important if you intend
- to Meta compile more then just the kernel.
- For instance, you could compile the assembler in the kernel and make it
- part of the kernel. The system will load faster.
- Enlarging the target buffer (now 32k in Akernel.blk screen 1) you could
- compile a lot, possibly an application.
- The only hitch is that defining words ( create..does> ) must be handled
- when compiling. The assembler has a few of them. To handle that I made:
-
- h: ado (s n1 n2 -- n1 ) \ used after does> word is defined
- target-create over ,-tr ,-t ;
-
- Then to use the above, eg with sz screen 3 Cpu68k.blk
-
- : sz (s n -- ) does> @ size @ and or ;
- 00300 ado sz3 00400 ado sz4
- 30000 ado sz300 drop
-
- Since does> in Meta leaves the address for patching, it works.
- There are other problems, all solvable.
-
- Don't forget the width, setting it to 0 will create a headerless
- system, or manipulate it while compiling, and make words disappear.
-
-
- - A user vocabulary is added to Meta.
-
- - Removed the bitfiddlers, they exist in the kernel.
-
- - Removed .relocations, wasn't very usefull.
-
- - Made saving the target system a little more readable. Created some
- words to take the noise out of it.
- But more importantly, block storage section or bss is now optional.
- Before you had to declare some, even if you didn't need it. The target
- is saved with the proper header, and relocation information.
-
- - Vocabulary creates a vocabulary in the target (no kidding). It also
- creates a vocabulary with the same name in the symbol table. Symbols
- are linked to the last one, depending on the vocabulary in <current>
-
- - The Meta compiler determines the size of the task variable area, see
- screen 23.
-
- - Screen 24 allows target symbol table vocabulary manipulation.
- Gone is .symbols, it's not needed, use <words> to see the symbols.
-
-
- UTILITIES
-
- - Screen 1 determines the utilities to be loaded, change as required.
-
- - Open the Graphics library, screen 13.
-
- - The editor is unchanged from the previous version. See the alternate or
- help screens for more information.
-
- - The decompiler has been extended to show a" strings, or amiga
- compatible strings. It also shows each library rom call if you want.
- Typing 'see OpenLibrary' gives some detailed information about the
- call.
- User or task variables and deferred words are also decompiled.
-
- - I did not implement any printing by pressing control-P, not in the
- previous version or this one. I hate noisy printers, I love laser
- printers, but they to expensive. So I very rarely print. If you want
- to, take a look at how the printer is intialized. 'init-pr' is deferred
- and is right now set to some control characters that initialize my
- printer to 132 columns. You will have to set this.
- Also the deferred p-name determines where the printer is connected to.
- Normally it is PRT: for my setup it is SER:. Change it accordingly.
-
-
- ASSEMBLER
-
- - The assembler is not changed. A small correction in one of the words.
- Take a look at the Akernel.blk for example usage of the assembler.
-
- - The debugger is changed. Since >next must not change in each task,
- the debugger had to be changed. (Before I changed >next). Now a jump to
- debnext is put in the place of the next routine. Unbug will fix the
- next routine. You will invite problems if you declare a new task when
- debug is in effect.
-
-
- INCLUDE
-
- - The first part of the utility to load the predefined structures and
- definitions for the Amiga, is a parser.
- This one is infix, and is extensively used in the Include files, which
- are text files. It also has some use in filling structures, see {fill.
- The text file INFIX.TXT explains the parser in more detail.
- The parser is in screens 4 to 8.
-
- - The next part, screens 12-18, are textfile loading.
- I prefer regular Forth blocks. The textfile loading does not support
- nesting. It was not intended to be the principal method of loading
- programs. I wrote it to load the Include files, outside of the Forth
- blocks. These are left alone, the text file loading opens a buffer for
- its own use, and when done, releases it.
- In screen 14 some additional Dos words are defined. I load these prior
- to any Include files, as a result these definitions are commented out
- in the include file libraries/dos. The same goes for all the words
- already defined in the kernel. Intuition calls, Exec calls etc
- defined in the kernel are all commented out.
- The text file loading obtains a Lock before it opens the file. Once the
- files has been opened it maintains some pointers into the buffer. It
- scans for LineFeeds as line terminators.
- Getline gets the next line. The line start will be returned by the word
- 'line' and the length is returned by the word 'llen'.
- The above three words are in the 'text' vocabulary, hidden normally.
- The words visible are: more?, ttype, tload.
- More? is a small keyboard pause word while listing a file via ttype.
- Ttype, or text-type, takes a file name and lists it. Any key will
- pause, and a return key will stop listing.
- Tload takes the next word in the input stream as a text file and loads
- it. It must be loadable, if it is not the system will not be forgiving;
- it will hang.
- See the examples and Tasks.blk for some example text file loading.
-
- - I tried to keep Include files small. They do not rely on other include
- files, with the exception of libraries/dos and libraries/dosextens.
- The intuition file that comes with the assembler or a C compiler is
- large. I made it a collection of files, each with ROM calls defined at
- the end.
- My model for these include files was Draco. Present on FredFish #76/77.
- It is a compiled language in C and Pascal spirit written by Chris Gray.
-
- - The last part of the Include.blk file is the structures.
-
- - Structures are defined with a {s..s} pair. The field names for the
- structure are hidden in a single linked list of words. The start of
- that word list is put in a variable, scontext, when a structure defined
- with the template, is used. An example is probably a better
- explanation..
- In the file i:exec/lists is the List structure defined:
- {s List
- APTR lh_Head
- APTR lh_Tail
- APTR lh_TailPred
- BYTE lh_Type
- BYTE l_pad
- s}
- The opening {s takes the next word and makes it a defining word. This
- word will have the pointer to the linked list of field words and the
- total size of the structure in the parameter field. (The size of a
- structure is <=64k ). To make a definition using the List word:
- List MemEntry
- In this case List creates an actual structure, List is just a template.
- The structure has the name MemEntry, and when it is executed it will
- put the address of the structure on the stack and stick the pointer to
- the linked list of field words in scontext.
- The search order, which was made deferred in the kernel, is now altered
- so that the field words are checked FIRST. Then the rest of the search
- order as set in 'context' is searched.
- For instance, if you want to get the value from the lh_Type field:
- MemEntry lh_Type s@
- That would return the type byte on the stack. What happens, MemEntry
- puts the address of the structure on the stack. Next lh_Type is found
- and executed. It alters the address by the offset of lh_Type to the
- start of the structure and sets the deferred word s@ and s! to that of
- a character fetch and store. Executing s@ will get the byte from that
- address.
-
- - Another example is in i:exec/ports, there the structure for MsgPort is
- defined as follows:
- {s MsgPort
- struct 14 mp_Node ( { Node=14;exec/nodes )
- BYTE mp_Flags
- BYTE mp_SigBit
- APTR mp_SigTask -4 soffset +!
- APTR mp_SoftInt ( union )
- struct 14 mp_MsgList ( { List=14;exec/lists )
- s}
- The field defining word 'struct' is actually a synonym for BYTES, but
- in this case it indicates that the next 14 bytes are a nested structure
- with the name mp_Node. At first I concidered nesting of substructures,
- but rejected it. As a compromise I added the reference after the field
- definition in the Include file. Here the nested structure is a Node, is
- 14 bytes large and the defininition is in the file exec/nodes. If you
- require access to the nested structure in mp_Node you must load the
- include file i:exec/nodes also. For example if you require the list
- type of mp_MsgList which is a List in the occurance of a MsgPort called
- e.g. myport:
- myport mp_MsgList { List lh_Type s@
- The sequence { List forces the field names for a List structure to
- become available. The '{' is immediate, and must be used in colon
- definitions, because if the occurance of a structure is compiled, it
- does not set the scontext variable to point to its field words.
- : ........... myport { MsgPort mp_Flags s@ ......... ;
- The MsgPort field words will be available until the next '{'. Examples
- of its use are in the examples provided.
- ( The major objection to this way of accessing structures )
-
- - Another peculiarity is the -4 soffset +! sequence. That sequence
- resets 'soffset' to what it was before APRT mp_SigTask was defined.
- The next line APTR mp_SoftInt will point to the same offset as
- mp_SigTask. This is the way I handled unions as they are called in C.
- Not many are present in the include files.
-
- - The field defining words are:
- BYTE BYTES WORD WORDS LONG LONGS STRUCT
- APTR is a synonym for LONG. The plurals expect a value before the
- actual field name. The infix parser is used to get the number.
- STRUCT is used if the structure is defined and known. The next word
- will be the structure template that provides the number of bytes to
- reserve as the fieldsize for the word following.
- These field definitions, when used, set the structured fetch/store
- words, s@/s!, to the appropriate size. If you do not know what the
- field size is you can use s@/s!, or if you do know use the regular
- Forth fetch/store.
-
- - After using a structure word or after setting scontext to a specific
- structure you can see the names of the fields using 'swords'. It will
- print a list of field names, backwards. For instance when the system is
- loaded, as distributed, enter:
- Exec { Task swords
- This will print all the fieldnames, use any keypress to stop/start the
- listing.
- Another way of showing what a structure consists of is:
- .{ Task
- This will show the fieldname and what type it is. And this time in the
- declared order, again use key to stop/start.
- The last aid is {?}. It takes an address of the stack and uses the
- structure which is currently in scontext to show each field, the type,
- the offset and the contents of it in hex. The use of that? Try:
- Exec definitions
- tload i:exec/execbase
- { ExecBase 4 @ {?}
- And the execbase is visible.
-
- - An additional help word is {fill. It is used in one of the example
- files. It uses the infix parser to fill the structure, and all of it,
- from the input. See the example file Gad.blk for its usage.
-
- - As a final note on structures;
- I had to redefine 'forget' since 'defined' would get messed up. The new
- forget will work properly and can even forget itself.
-
-
- TASKS
-
- - Tasks use Exec as scheduler. They are simple tasks and require their
- own stack and return stack and task area.
- Task area is taken from the dictionary, in which the task local
- variables, the next routine and the rcall routine are copied.
- The stacks are obtained from Exec and kept in an Intuition Remember
- key, which is linked into the mem-link so the task can be forgotten.
-
- - Tasks can be given parameters, they will be copied to the task stack
- before the task is started. No parameters are expected in return.
-
- - Tasks should not use the A4th console input and output, they should
- define their own input and output file or device. A tasks output file
- could be a window of course.
-
- - Tasks should also have one entrance and one means of exit. If you use
- an abort, things will not work properly.
-
- - Priority of tasks are set at -25 that puts them below any system task.
- This makes sure it behaves as a background task. A4th will continue
- running at full speed, and when time is available the background task
- will run. As it should.
- If for some reason the task must execute at a different priority, you
- can alter it at any time, using 'priority'.
-
- - Tasks cannot compile or do any interpreting, the dp for the task is
- mainly used for number conversion and to find pad which is needed for
- emit.
-
- - The example on screen 17 shows how tasks can be used.
-
- - Each task needs a 'stop', and 'activate' requires the task on the stack
- under which should be the parameter count (0 for none) and the
- parameters under that.
- Tasks can be reassigned on the fly. But be carefull the resources
- allocated by the task will not be released; if a window was opened it
- will stay open. That could be exactly what you want of course.
-
- - The tasks are a minimal approach and it's up to you to make it better
- and/or more fool proof.
-
-
-
-
- I appologize for any mistakes I have made that might haunt you, I do hope
- you will be able to use this system as a tool and have some fun with Forth.
-
-
- Peter Appelman
- 05Oct88
-
-
-