copyright © 2017 Frank Sergeant
all rights reserved
Editor in Stew Pot comic, by artist Frank Heitzman, used with permission
formatted October 29, 2017 at 04:15 PM
eBooks, PDF, and HTML versions formatted with the Nepo Press web-based service at http://nepotism.net.
Pygmy Forth is the new, 32-bit or 64-bit Pygmy Forth for PCs/desktop computers. It was first released in October, 2017.
This manual corresponds to Pygmy Forth version 17.10, released October, 2017. The first two digits of the version number represent the year (17). The next two digits represent the month (10). If more than one release occurs in a month, the version will be adjusted slightly.
I now refer to the older 16-bit Pygmy Forth as Pygmy for DOS or Pygmy Forth for DOS.
I miss my Pygmy for DOS. This new Pygmy Forth is my attempt to replace it for the modern desktop environment (primarily Linux in my case).
For programming for PCs, I used to love my lovely Pygmy for DOS. However, the days of DOS are gone, and good riddance to Microsoft as far as I’m concerned. I am delighted with the speed and conveniences and openness of the Linux world, but I have been missing Forth in that environment. I know there are many alternative Forths, and I am sure they are great for those who like them, but none of them suits me the way Pygmy did.
In summary, I WANT MY PYGMY BACK for Linux/Mac/Windows.
Pygmy Forth is aimed at desktop systems (Linux, Unix, Mac OS, Windows). It is written in Python (version 3), so should run anywhere that Python runs. I run it mainly on Linux, so if you find it needs any minor adjustments to run elsewhere, please let me know and I’ll add the details to this manual.
Pygmy Forth is distributed as a zip file. See the Quick Start section for how to run it with or without installing it.
Pygmy Forth is written in Python and it can be extended by writing
CODE
words (primitives)
in Python, or by writing high-level Forth words.
Pygmy Forth gives you the benefits of Forth factoring, incremental and interactive testing, and simplicity, while providing easy access to the world of Python facilities and libraries.
This project is in its early stages as of fall, 2017.
If you find anything that does not work as expected or as described in the manual, please let me know.
Pygmy Forth inherits heavily from my various prior Forths, which inherited heavily from Chuck Moore’s Forths, especially cmFORTH.
Pygmy Forth is licensed under the MIT license except
as otherwise noted in specific files. See the file LICENSE.txt
for the full text of the MIT license. Thus you are free to use it
for just about any purpose. The only exception at the moment is
the file pygmy-mode.el
(an Emacs
mode for editing pseudo block files) which is licensed under the
GPLv2 (see the file gpl-2.0.txt
).
This manual does not teach Forth, not even Pygmy Forth, from the ground up. Perhaps I can add some of that to the manual later.
Meanwhile, it will be helpful if you already have some familiarity with Forth, especially Pygmy for DOS and/or Riscy Pygness. If not, you might consider downloading Pygmy for DOS (http://pygmy.utoh.org/pygmyforth.html). It has some supplementary material which might help.
My name is Frank Sergeant. My email address is frank@pygmy.utoh.org. I will be pleased to receive comments, corrections, suggestions about the manual and Pygmy Forth. If you find a bug, please send the smallest example that illustrates the problem, as that will greatly increase the chance of a fix.
I wrote Pygmy for DOS, starting in the late 1980s, as a DOS Forth heavily based on (but not as lean as) Chuck Moore’s cmFORTH. I produced several variants of Pygmy for microprocessors, including the 68HC11, 68000, Super 8, 8051, etc.
Later, I wrote Riscy Pygness, a version of Pygmy for the ARM and ARM Cortex. When developing on Riscy Pygness, the work is split between the host PC and the target ARM chip, thus requiring only very limited RAM/Flash on the target.
I have done programming work in a number of languages in addition to Forth, including assembly, Smalltalk, Lisp, Python, and Tcl.
A major project these days is doing both custom and automated eBook and printed book formatting at http://nepotism.net.
Note: When command line examples
are given, a leading dollar sign ($
)
represents the terminal’s shell or command prompt for an ordinary
user, and a leading pound sign (#
)
represents the terminal’s shell or command prompt for root
(also called the superuser). Do not type the leading pound
or dollar sign. Your exact shell prompt will likely look
slightly different, e.g., frank@oak:~$
or C:\>
.
To get to a command line (shell prompt), open a terminal in Linux or MacOS. For Windows, see https://www.lifewire.com/how-to-open-command-prompt-2618089 or do a web search for something like “getting a command prompt (DOS box) on Windows 10”.
The examples below assume Linux, Unix, or MacOS. You’ll
need to adjust the commands and directories slightly for Microsoft
Windows. For example, the Linux cp
command would be the Windows copy
command (or just use the Windows Explorer file browser).
To run it without “installing” it:
$ python pygmykernel.py
or
$ python3 pygmykernel.py
To “install” it:
pygmy
or pygmyforth
. This directory
can be a system wide directory, such as a subdirectory under /usr/local/
, or a directory under your
home directory ~/
pygmykernel.py
and pygmy.fth
and pygmy.sh
to a directory in your path such as /usr/local/bin
(perhaps C:\Program Files
on Windows)
pygmy.sh
, if necessary. On
Windows, create a similar batch file named pygmy.bat
Pygmy Forth requires Python version 3 (or later). You can test for this at a command line by typing the following in a terminal (a command prompt):
$ python --version
If the response is something like this,
Python 3.6.2
all is good because the first digit is 3. But if you get a response like this
Python 2.7.14
you are running version 2 and that won’t work. Even so, you still might have version 3 available under a slightly different name. Try this:
$ python3 --version
If you do not have version 3 available, use your system’s package manager to install it or go to https://www.python.org/ to download and install it. It is free, and easy to install. My video at https://www.youtube.com/watch?v=CmUhs6-Aa9k might help. Also, you can do a web or YouTube search for “How to Install Python on <my operating system>”.
If you get a message like the following when you try running Pygmy Forth,
File "pygmykernel.py", line 425 print ("%s " % i, end=END) ^ SyntaxError: invalid syntax
that is a clue that you are not running Python version 3.
Here is how I might install it on a Linux system.
Suppose I have downloaded pygmyforth1710.zip
into my ~/Downloads
directory
and that my home directory is /home/frank
(also known as ~/
). Remember,
the dollar sign represents my ordinary user account (frank
)
prompt and the pound sign represents the superuser account (root
) prompt. Here is what I would
type (with comments below each line):
$ python --version (uh-oh, on my system, python is just version 2) $ python3 --version (oh, good, I get version 3 with the python3 command) $ echo $PATH (make sure /usr/local/bin is in my path) $ sudo -i (temporarily become root, alternatively, use the su command) # cd /usr/local (change to the /usr/local directory) # mkdir pygmy (create a pygmy directory under /usr/local) # cp /home/frank/Downloads/pygmyforth1710.zip . (copy the zip file to the pygmy directory) # unzip pygmyforth1710.zip (unzip the file) # cd .. (move up a level to /usr/local) # cd bin (move down a level to /usr/local/bin) # ln -s /usr/local/python/pygmykernel.py . (link the Pygmy kernel program into /usr/local/bin) # ln -s /usr/local/python/pygmy.fth . (link the Pygmy Forth code into /usr/local/bin) # ln -s /usr/local/python/pygmy.sh . (link the shell script into /usr/local/bin) # cat pygmy.sh (display the contents of the shell script to verify it is correct. For example, you may need to change 'python3' to 'python') # exit (exit the root account) $ (now I am once again running as my regular user account)
After that, any user, from any directory, should be able to start Pygmy by running the following in a terminal:
$ pygmy.sh
Note, if you want the start-up command to be even
shorter, you can rename or copy pygmy.sh
to just pygmy
(or even pyg
).
You can start Pygmy Forth like this:
$ pygmy.sh
or, since the $*
in the shell script passes all command line arguments to pygmykernel.py
,
you can run it like this to load additional files:
$ pygmy stars.fth
or
$ pygmy myutils.fth myapp.fth
The shell script starts pygmykernel.py
,
which loads the rest of the Forth system from pygmy.fth
.
The file pygmy.fth
is written
in Forth. You may load additional Forth files as well.
Since pygmykernel.py
is marked
executable (on systems that respect that), if the default Python
is version 3 or later, another way to start Pygmy Forth is just by
running
$ pygmykernel.py
Assuming it is in your path, the above runs pygmykernel.py
.
$ pygmykernel.py stars.fth
pygmykernel.py
looks for pygmy.fth
in the same
directory pygmykernel.py
is in.
If it doesn’t find it there, it looks for it in the current
directory.
After that, pygmykernel.py
loads
any additional files listed on the command line, such as the file
stars.fth
in the above example.
It looks for the additional files in the current directory unless
a different path is specified.
Once you have started Pygmy Forth in the terminal, you will get an interactive prompt where you can type Forth commands or load Forth files. It looks like this:
Pygmy Forth version 17.10 Welcome to Pygmy Forth >
To exit from Pygmy Forth, type
BYE
Pygmy Forth is written in Python (Python version 3), so it will run everywhere: Linux, Mac, Windows, 32 bits or 64 bits. (I don’t know about cell phones and tablets. Please let me know if you try it.)
High-level Forth words are compiled as Python procedures, rather
than as lists of tokens or addresses. CODE
words are supported and are written in Python, not assembly language.
That is, you can think of Python as being Pygmy Forth’s assembly
language.
There are two:
I hope it will be supported, but I have not begun to think about it yet.
This needs some more thought.
New Forth words are defined conventionally. High-level definitions begin with a colon and end with a semicolon.
When typing interactively, a new word must be defined entirely on one line. So, don’t press the Enter key until after typing the ending semicolon. However, there is no limit to line length, so just let it wrap on the terminal as necessary.
Here are some examples:
: STAR ( -) '* EMIT ; : STARS ( n -) FOR STAR NEXT ; : 6STARS ( - ) 6 STARS ;
Definitions may have multiple exits, with either
the word EXIT
or the shorthand
double semicolon (;;
). The single
semicolon is used only to mark the end of a definition.
High-level Forth words are maintained in the dictionary and automatically map the Forth word name to the name of the corresponding Python procedure.
Recursion is allowed, but be careful. The word
must already be defined, at least with a dummy definition. In the
following example, FAC
will first
be defined with a dummy definition, but that will be replaced by
the second definition of FAC
.
Also, “tail call optimization” is not performed, so you
could run out of stack eventually.
: FAC DUP ; ( just a dummy definition) : FAC ( n - n!) DUP 1 > IF ( n) DUP 1 - ( n n-1) FAC * THEN ;
As with a traditional Forth, there is a kernel,
which is just enough Forth to know how to LOAD
the rest of the Forth system from a Forth text file.
The kernel is a Python (Python 3) program named pygmykernel.py
.
Running this in a terminal gives you an interactive loop (a REPL)
with a prompt where you can type Forth words and load Forth source
code files.
In effect, Python is the assembly language for Pygmy Forth. Thus,
“primitives” (CODE
words) are defined as Python procedures.
Although there is no need to define small constants (such as 0,
1, 2, 3) as Forth words, here is how they could be defined as CODE
words. The new word 0
would push the integer zero to the Forth data stack:
CODE 0 dpush(0) END-CODE
See any of the CODE words in pygmy.fth
for further examples. Note that there must be at least two spaces
between the name and the start of the Python code, or the Python
code must start on a new line and be indented.
When pygmykernel.py
begins,
it tries to load pygmy.fth
from
the directory that pygmykernel.py
is in. Failing that, it tries to load pygmy.fth
from the current directory.
After loading pygmy.fth
, it
attempts to load any additional Forth files passed to it on the command
line. For example,
$ pygmykernel.py stars.fth
The above would load pygmy.fth
and then load stars.fth
.
After loading the source files, it runs the interactive loop (the
REPL) known in Forth as QUIT
.
The word QUIT
is defined in pygmy.fth
but you could redefine it
either by editing pygmy.fth
directly
(not recommended) or by defining it again in a file that is loaded
later. Later definitions replace earlier definitions.
By redefining QUIT
, you can
run an application (in which case, depending upon your code, perhaps
no interactive prompt will appear).
See the previous section about the kernel and how
to run Pygmy Forth. Since you can load additional files just by placing
them on the command line, you can load your application (let’s
call it myapp.fth
) wherein you
have redefined QUIT
to run your
application instead of running a REPL.
$ pygmy myapp.fth
or you can create a shell script named myapp
or myapp.sh
(or, on Windows, perhaps
an equivalent batch file) that looks something like this:
#!/bin/sh /usr/bin/python3 \ /usr/local/bin/pygmykernel.py myapp.fth
The backslash at the end of the second line indicates the command is continued on the third line. You would ordinarily write lines two and three on a single line (without the backslash).
Of course, you will need to make the shell script executable, e.g.,
$ chmod +x myapp
If you are running something other than Linux/Unix (or even if you aren’t), you might Google for “how to make a turnkey Python app for Windows”. One thing that turned up that looks interesting is http://www.pyinstaller.org/index.html.
Let’s not do that.
By default, input and output are in decimal but hexadecimal is also recognized.
Traditionally, Forth allowed nearly any arbitrary base, at least up to base 36 (10 digits plus 26 letters of the alphabet).
In microprocessor work, binary, octal, decimal, and hexadecimal might all be useful at times, but for PC/desktop applications, I think decimal and hexadecimal are all that will be needed.
An initial dollar sign indicates a hexadecimal number on input.
To display a number in hexadecimal, print it with the word .H
.
$45 EMIT --> E 65 .H --> 41
Pygmy Forth accepts integers and floats and characters as numbers, e.g.,
13 . --> 13 13.75 . --> 13.75 'E . --> 69 'E EMIT --> E
" Hello" . --> Hello " Hello" EMIT --> H " Hello" COUNT TYPE --> Hello ." Hello" --> Hello
Unlike traditional Forths, in Pygmy Forth, a character is just a string with a length of one. Consider the Forth BL. It is defined like this:
CODE BL dpush(' ') END-CODE
It returns a string containing a space rather than
the ASCII code for a space. However, 'A
,
etc., put a number on the stack (the ASCII code for the character),
so there may be some inconsistency here.
There are more or less three types of files for Forth source code:
I can see Pygmy Forth supporting all three types, but to begin with, it supports loading just from pseudo block files and from text files.
Pygmy for DOS has a simple, pleasant, block editor. I used it happily for years.
However, these days, most of my life is spent in Emacs. Anything other than Emacs is fairly painful to me. I’m sure others feel the same about Vi or their favorite text editor.
Some people like to keep Forth source code in plain text files and others like to keep it in blocks.
The new Pygmy Forth lets you use either or both, except, the block files supported at this time are really pseudo block files.
The new Pygmy Forth can load directly from text files like this:
" mysource.fth" LOAD
Note that the file doesn’t need to be opened or closed explicitly.
Pygmy Forth can also load from pseudo block files like this
" myapp.blk" OPEN 1 LOAD 13 LOAD " anotherapp.blk" OPEN 300 LOAD
A pseudo block file needs to be opened before accessing it. It does not need to be closed explicitly. It remains open until another pseudo block file is opened (or until the program exits).
Pseudo block files are really plain text files with a special comment line that marks the beginning of each block.
This lets you edit text files as if they were block files, but with no requirements on the size of the blocks. They are “logical” blocks rather than physical 1024-byte blocks.
A special comment is used to indicate the start of a block. Here is an example:
( block 7)
The comment must start at the left margin with an opening parenthesis followed by one or more spaces, followed by either the word “block” or the word “shadow”, followed by one or more spaces and then the block number. Eventually, the comment line must end with a closing parenthesis.
Here are some examples:
( block 3 miscellaneous) ( block 7) ( shadow 7) ( block 375 some description)
Shadow blocks, used for documentation, are optional
and are ignored by LOAD
.
Since a pseudo block file is a plain text file, you can use any text editor you wish. However, an editor with outlining capabilities comes in handy so you can see all the block comment lines with the contents hidden, or display just one block at a time and page down and page up to move from block to block.
Pygmy Forth comes with an Emacs mode (pygmy-mode) to help edit pseudo block files.
I first tried something like this with Riscy Pygness (for ARM chips)
(forthblocks.el
), but I wasn’t
completely happy with that first attempt.
I tried again for Pygmy Forth. I am much happier with pygmy-mode:
(see the included file pygmy-mode.el
).
The mode also has a command to renumber blocks which goes through the text file and renumbers all the blocks consecutively. Other keystrokes collapse or expand the blocks (to and from outline mode) or move to the next or previous block or shadow block.
Because the file is a text file, and the block boundaries are marked by special comment lines, each block can be as short or long as you like.
The word LOAD
distinguishes
between block files and text files by whether you give it a number
or a file name:
1492 LOAD
loads block number 1492 (assuming the file had already been opened)
" utilities.fth" LOAD
loads the text file named utilities.fth
from the current directory
" /home/frank/app1/utilities.fth" LOAD
loads a text file from an absolute path
Pseudo block files need to be opened before accessing them. At most a single pseudo block file can be active at any one time. So, the 1492 example above should really be
" columbus-navigation-aids.scr" OPEN 1492 LOAD
To use pygmy-mode, just copy pygmy-mode.el
to somewhere in your Emacs load path, or, as shown below, give the
full path to pygmy-mode.el
, which
assumes you put the file into your home directory.
Then add this to your .emacs
file
(autoload 'pygmy-mode "~/pygmy-mode.el" "Major mode to edit Forth pseudo block files." t) (add-to-list 'auto-mode-alist '("\\.scr\\'" . pygmy-mode)) (add-to-list 'auto-mode-alist '("\\.blk\\'" . pygmy-mode))
(Warning, you should be able to
copy and paste the above to your .emacs
file from the HTML version of this manual. However, you if you copy
and paste from the PDF version of this manual, the single and double
quotation marks must be edited to change them from curly quotes to
straight quotes.)
The add-to-list
forms associate
the file extensions .scr
and .blk
with pygmy-mode so that it will
be started automatically when you open a file with one of those extensions.
Edit the add-to-list
forms to
use suit your file naming conventions. (Currently, I use .fth
to indicate a text file and .blk
and .scr
to indicate pseudo block
files.)
To see the documentation for pygmy-mode, open a file in pygmy-mode
then press C-h m
. This runs the
Emacs describe-mode
command.
Pygmy-mode recognizes the special block marker comment lines and
treats them as headings. See the pygmy-mode documentation mentioned
above or see the file stars.scr
for examples.
The keystrokes C-c C-n
and C-c C-p
move to the next or previous
block. S-TAB
cycles through the
three visibility settings. C-v
and M-v
(and PgDn
and PgUp
) move to the next or
previous block and also “narrow” the buffer to show just
the single block. To “unnarrow” the buffer, press S-TAB
.
Because words, both CODE
and high-level, are compiled down to Python procedures, there is
not much need for the return stack except for its use as a “third
hand” and for nested LOAD
s.
PUSH
and POP
move an item to and from the return stack. R@
copies the top return stack item to the data stack. PUSH
ing
and POP
ing addresses to affect
flow control will not work.
Furthermore, stack items, unlike with previous Forths, no longer have a “width” and are not necessarily numbers. For example, a stack item could be a string itself rather than the address of a string. In practice, though, this makes little difference. COUNT, TYPE, etc., still work.
" This is a string" COUNT TYPE
As with my previous Forths, FOR
… NEXT
is the main looping
mechanism.
: DOSOMETHING ( -) 10 FOR SOMETHING NEXT ; DOSOMETHING
The above will do SOMETHING
10 times. The index counts down, so
: CD ( -) 3 FOR I . NEXT ; CD
would print
2 1 0
Other looping commands are BEGIN
… UNTIL
, BEGIN
… AGAIN
, and BEGIN
… WHILE
… REPEAT
.
In addition to Python (version 3 or later), two files are required to run Pygmy Forth.
pygmykernel.py
pygmy.fth
.
pygmy.fth
pygmykernel.py
to flesh out the Forth system.
The shell script pygmy.sh
is also provided as an example of a shorter way to start Pygmy Forth.
It may need to be edited to adjust it to your system. On a Microsoft
Windows system, you could write a similar batch file.
The Pygmy Forth kernel (pygmykernel.py
)
is written in Python. This has the advantage of running anywhere
that Python can run, which is everywhere (at least Linux, Unix, other
Unix-like systems, Microsoft Windows, Apple Mac OS—if you get
Pygmy running on something else, including tablets and phones, I’d
love to hear about it).
The Forth primitives (CODE
words),
instead of being written in assembly language, are written in Python.
In effect, Python is the assembly language for Pygmy Forth.
Some utility Python functions (defined in pygmykernel.py
)
are useful for accessing the stacks and printing a list:
dpush()
dpop()
tos()
rpush()
rpop()
dotList()
.S
, .RS
, and WORDS
)
Here is an example of a CODE
word (from pygmy.fth
):
CODE PUSH rpush(dpop()) END-CODE
Note, there must be at least two spaces (or a line
feed followed by a space) between the name (PUSH
)
and the start of the Python code. Otherwise, you will get the error:
The body of code word XXXX must be indented
The above code for PUSH
pops an item from the data stack and pushes that item to the return
stack.
Everything between the new word’s name and END-CODE
is written in Python.
The above definition can be written on a single line, but more complicated definitions may need to be written on multiple lines like this:
CODE PUSH rpush(dpop()) END-CODE
or
CODE EMIT x = dpop() if isinstance (x, str): print(x[:1], end='') else: print(chr(x), end='') END-CODE
EMIT
sets x
to the item popped from the data stack
and then checks to see whether it is a string to decide whether to
print its first character as a string or a character.
Note that Python indentation rules must be observed.
If the Python code consists of multiple expressions (not statements), they can be written on a single line if separated by semicolons, like this:
CODE + a,b = dpop(2); dpush(a+b) END-CODE
or
CODE + a,b = dpop(2); dpush(a+b) END-CODE
Unlike traditional Forth, you cannot use Forth-style
comments between CODE
and END-CODE
. Forth-style comments may
be placed outside, though, like this:
( a b - a+b) CODE + a,b = dpop(2); dpush(a+b) END-CODE
or, with proper indentation, Python comments may be used, like this:
CODE + # ( a b - a+b) a,b = dpop(2); dpush(a+b) END-CODE
If the Python code consists of multiple statements (rather than expressions), then multiple lines must be used, like this:
CODE IF global _tab assemble ("if dpop():") _tab += 1 END-CODE
Note that Python procedures defined as CODE
words are not allowed to have parameters and, if they return values,
those values are ignored. Instead, they must take their arguments
from the data stack and return any values by placing them on the
data stack. The functions dpush()
,
dpop()
, tos()
,
etc., help with that.
dpop()
called with no argument
(or an argument of 1) removes one item from the data stack and returns
it.
a = dpop()
otherwise, dpop(n) (with n > 1) removes n items from the data stack and returns a tuple. If the data stack consists of (4, 5, 6, 7, 8) (8 is on top), then the top 3 items can be popped off and assigned to three Python variables with
a,b,c = dpop(3)
which sets a to 6, b to 7, c to 8, and leaves the data stack as (4, 5) with 5 on top.
dpush()
works similarly. It
must have at least one argument. All its arguments are pushed to
the data stack, leaving the right-most argument on top.
The following would push 3 items to the data stack, leaving 9 on top.
dpush (7, 8, 9)
The functions rpop()
and rpush()
work the same way
except on the return stack.
These are colon definitions and VARIABLE
s.
Here are some examples:
: 0= ( n - f) 0 = ;
: STAR ( -) '* EMIT ;
: STARS ( n -) FOR STAR NEXT ;
VARIABLE F1 F1 @ . --> 0 13 F1 ! F1 @ . --> 13 " hello" F1 ! F1 @ . --> hello F1 @ COUNT TYPE --> hello
To define a constant, use a colon definition, e.g.,
: X 35 ; : Y 72 ;
Each Forth word is compiled as a Python procedure.
As always, a Forth word name may contain any non-white-space character.
When possible, the Forth word name is used as the Python procedure
name. For example, the Forth word QUIT
becomes the Python procedure QUIT
.
However, when the Forth word is not a valid Python procedure name,
the name is adjusted. For example, the Forth word +
becomes the Python procedure Plus
and the Forth word ;
becomes the
Python procedure Semi
.
The Forth dictionary maps the Forth word name to the name of the Python procedure. (You do not need to keep track of the Python procedure names.)
One caution: it is possible to step on
a Python function. Generally, you are safe if you use upper case
for the Forth word names. That is, OPEN
would not step on the Python open
function.
To help avoid such problems, a warning is displayed whenever a Python name is redefined.
Traditionally in Forth, false is represented by
the integer zero and true is represented by any other integer. This
is still true in Pygmy Forth but with an extension. The Python values
True
and False
are also recognized by Pygmy Forth. (The Forth words TRUE
or FALSE
put the Python values
True
or False
on the stack.)
So, we can define 0=
like this
( n - f) CODE 0= dpush (dpop() == 0) END-CODE
rather than
( n - f) CODE 0= if dpop() == 0: dpush (1) else: dpush (0) END-CODE
In practice, we define =
as a CODE
word then define 0=
as a high-level word, like this:
( a b - f) CODE = a = dpop() b = dpop() dpush(a == b) END-CODE : 0= ( n - f) 0 = ;
or
( a b - f) CODE = a,b = dpop(2) dpush(a == b) END-CODE : 0= ( n - f) 0 = ;
The Forth data stack can hold more than just integers.
It can also hold True
, False
, strings, and floats.
Forth IF
, UNTIL
,
and WHILE
recognize 0
,
False
, and the empty string as
false, and anything else as true.
A Forth variable is defined by the word VARIABLE, like this
VARIABLE STATUS
When the variable, e.g., STATUS
,
is executed, it puts its name (as a string) on the stack. The words
@
and !
use this string to access the variable in the variables dictionary.
Since we are not concerned the width of a stack item, there is no
need for C@
, C!
,
etc.
I’m not sure you would need to do it, but here is an example
of how you could access the value of a variable within a CODE
word:
VARIABLE STAT# CODE CHECK-STATUS if variables['STAT#']: print ("Uh-oh, we've got trouble") else print ("Fortunately, status is OK") END-CODE
Unlike with traditional Forths, a Pygmy Forth variable does not have a fixed size and does not represent a memory address.
Constants are defined as colon definitions, e.g.,
: WIDTH 30 ;
Alternatively, constants could be defined as CODE
definitions like the following,
but there is no reason not to use the simpler colon definition.
: CODE WIDTH dpush(30) END-CODE
Unlike traditional Forths, there is no need to define small integers as constants. The only reason to define a constant is for naming clarity.
In Pygmy Forth, WORDS
will display the words in whichever vocabulary is current. COMPILER
switches to the COMPILER vocabulary.
FORTH
switches back to the FORTH
vocabulary.
The following sections list the words in the two vocabularies, along with their stack effects.
The FORTH words have a single stack effect.
The COMPILER words have two stack effects:
The first is the stack effect at compile time and the second is the stack effect at run time.
For example, consider the word "
.
It marks the beginning of a string. It is defined both in FORTH
and in COMPILER.
The version in FORTH collects the string up to the ending quotation mark and pushes the string to the data stack, so its stack effect is ( - s).
The version in COMPILER collects the string up to the ending quotation mark and lays down some code, so its stack effect at compile time is ( -). At run-time, the laid-down code pushes the string to the data stack, so its stack effect at run-time is ( - s).
In the stack effect comments,
f
stands for flag (something treated
as a true or false value)
n
, n1
,
n2
, etc., stand for numbers
s
stands for a string
#
stands for a count or length
x
, a
,
b
, etc., stand for anything, or
something appropriate for the context
n|s
stands for either a number
or a string
c|s
stands for either a character
code or a string
The hyphen separates the stack picture when the word begins to execute from the stack picture after the word finishes executing.
END-CODE
, )
,
and ;
are not really Forth words.
They are just markers used by the words CODE
,
(
, and :
,
respectively.
To see the definitions, look in the files pygmykernel.py
and pygmy.fth
.
!
( value v -)
“store”
the value into the variable v
"
( - s)
put a string on the stack,
e.g., " This is a string"
(
( -)
ignore the input stream
up through the closing parenthesis, i.e., start a comment
*
( a b - a*b)
+
( a b - a+b)
-
( a b - a-b)
.
( x -)
print and remove top
of stack
."
( -)
print the string, e.g., ." Hello"
.H
( n -)
print n in hexadecimal
.RS
( -)
display the return stack
.S
( -)
display the data stack
/
( a b - a/b)
divide a by b
0=
( n - f)
f is True if n is zero,
else False
1+
( n - n+1)
increment n by 1
2DROP
(a b -)
2DUP
(a b - a b a b)
;
( -)
marks the end of a colon
definition
<
( n1 n2 - f)
f is True if n1
is less than n2, else False
<=
( n1 n2 - f)
f is True if n1 is
less than or equal to n2, else False
=
( a b - f)
f is True if a equals
b, else False
>
( n1 n2 - f)
f is True if n1
is greater than n2, else False
?
( v -)
fetch and print the value
of variable v
@
( v - value)
“fetch”
the value of the variable v
ABORT
( s -)
print the string and return
to the interactive QUIT loop
AND
( f f - f)
f is True if both inputs
are true, else False
BL
( - blank)
put a space on the
stack
BLOCK
( n - s)
put the contents of pseudo
block n on the stack as a string
BYE
( -)
exit Pygmy Forth
CODE
( -)
define a primitive in Python
up to the END-CODE marker
COMPILER
( -)
change context to COMPILER
vocabulary
COUNT
( s - s #)
put the length of the
string on the stack
CR
( -)
print a carriage return
DROP
( x -)
DUP
( a - a a)
EMIT
( c|s -)
print a character
END-CODE
( -)
marks the end of a CODE
definition
FALSE
( - False)
push the Python False
to the stack
FORTH
( -)
change context to FORTH vocabulary
LOAD
( n|s -)
load the block or file
NOT
( f - f)
f is True if input is
false and vice versa
OPEN
( s -)
open a pseudo block file
named s
OR
( f f - f)
f is True if either
input is true, else False
OVER
( a b - a b a)
POP
( - x)
move top of return stack
to data stack
PUSH
( x -)
move top of data stack
to return stack
QUIT
( -)
the main interactive loop
(REPL)
R@
( - x)
copy top of return stack
to data stack
SWAP
( a b - b a)
THRU
( first last -)
load a range of
pseudo blocks
TRUE
( - True)
push the Python True
to the stack
TYPE
( s # -)
print the first #
characters of the string
VARIABLE
( -)
define a variable, taking
the name from the input stream, e.g., VARIABLE STATUS
WORD
( s1 - s2)
collect input up to
s1 and put it on the stack as s2
WORDS
( -)
display words of the current
vocabulary
XOR
( f f -)
f is True if exactly
one input is true, else False
:
( -)
start a high-level Forth
definition
"
( -)
(
- s)
compile a string that will be pushed to stack at run-time
(
( -)
(
-)
ignore the input string up through the next closing parenthesis,
i.e., a comment
."
( -)
(
-)
compile a string that will be printed at run-time
;;
( -)
(
-)
exit the Forth word at run-time, synonym for EXIT
AGAIN
( -)
(
-)
end a BEGIN … AGAIN structure
BEGIN
( -)
(
-)
begin a BEGIN … UNTIL or BEGIN … AGAIN structure
ELSE
( -)
(
-)
begin the false part of an IF … ELSE … THEN
structure
EXIT
( -)
(
-)
exit the Forth word at run-time
FOR
( -)
(
n -)
begin a FOR … NEXT structure
I
( -)
(
- n)
at run-time, push the loop index to the data stack
IF
( -)
(
f -)
begin an IF … THEN or IF … ELSE … THEN
structure
NEXT
( -)
(
-)
end a FOR … NEXT structure
REPEAT
( -)
(
-)
end a BEGIN … WHILE … REPEAT structure
THEN
( -)
(
-)
end the IF … THEN or IF … ELSE … THEN
structure
UNTIL
( -)
(
f -)
end a BEGIN … UNTIL structure
WHILE
( -)
(
f -)
begin the conditional part of a BEGIN … WHILE
… REPEAT structure
My mind is not yet settled on whether the current implementation is the best approach, or even how useful it might be. Certainly, it has some quirks and lacks some features. Also, it has not been tested very thoroughly yet. I need to get some experience with it, and, hopefully, some feedback from you about your experiences with it.
I hope to put together a mailing list of Pygmy correspondents, etc., so I can mail out the occasional notice. If you would like to be on the list, just drop me a note at frank@pygmy.utoh.org.
– Frank
frank@pygmy.utoh.org