project-k
is a very small FORTH programming language kernel supporting Javascript and Python open-sourced on GitHub https://github.com/hcchengithub/project-k. We are going to use this FORTH kernel to build our own tiny FORTH programming language system.
Read this tutorial on GitHub https://github.com/hcchengithub/project-k/blob/master/notebooks/tutor.ipynb
Use an online Jupyter Notebook, I recommend notebooks.ai while , Microsoft Azure Notebooks, and more are available out there, you don't need to install anything. Click [Download Zip]
form GitHub project-k. We only need this notebook, notebooks\tutor.ipynb
, and project-k source code file for Python, projectk.py
that has only 20k bytes includes a lot of comments. Choose an online Jupyter notebook you like, create an acount, upload the minimum two files, double click or run this notebook tutor.ipynb
and start playing.
In [1]:
# In case you are not familiar with Jupyter Notebook, click here and press Ctrl+Enter to run this cell.
import projectk as vm
vm
Out[1]:
Python standard function dir(obj)
gets all member names of an object. Lest's see what are in the FORTH kernel vm
:
In [2]:
print(dir(vm))
I only want you to see that there are very few properties and methods in this FORTH kernel object and many of them are conventional FORTH tokens like code
, endcode
, comma
, compiling
, dictionary
, here
, last
, stack
, pop
, push
, tos
, rpop
, rstack
, rtos
, tib
, ntib
, tick
, and words
.
In [3]:
vm.stack
Out[3]:
vm.dictate()
method is the way project-k VM receives your commands (a string). It actually is also the way we feed it an entire FORTH source code file. Everything given to vm.dictate()
is like a command line you type to the FORTH system as simple as only a number:
In [4]:
vm.dictate("123")
vm.stack
Out[4]:
The first line above dictates project-k VM to push 123 onto the data stack and the second line views the data stack. We can even cascade these two lines into one:
In [5]:
vm.dictate("456").stack
Out[5]:
In [6]:
vm.dictate("code hi! print('Hello World!!') end-code"); # define the "hi!" comamnd where print() is a standard python function
vm.dictate("hi!");
Did you know what have we done? We defined a new FORTH code word! By the way, we can use any character in a word name except white spaces. This is a FORTH convention.
In [7]:
vm.dictate("code words print([w.name for w in vm.words['forth'][1:]]) end-code")
vm.dictate("words");
In the above definition the vm.words
is a python dictionary (not FORTH dictionary) defined in the project-k VM object as a property which is something like an array of all recent words in the recent vocabulary named forth
which is the only one vocabulary comes with the FORTH kernel. Where a FORTH 'vocabulary' is simply a key in python dictionary key:value pair.
We have only 4 words so far as the words
new command show above. Where 'code' and 'end-code' are built-in in the FORTH kernel; 'hi!' and 'words' were defined above.
In [8]:
vm.dictate("code + push(pop(1)+pop()) end-code"); # pop two operands from FORTH data stack and push back the result
vm.dictate("code .s print(stack) end-code"); # print the FORTH data stack
vm.dictate('code s" push(nexttoken(\'"\'));nexttoken() end-code'); # get a string
vm.dictate('words'); # list all recent words
This example demonstrates how to use built-in methods push()
, pop()
, nexttoken()
and the stack
property (or global variable). As shown in above definitions, we can omit vm.
so vm.push
, vm.stack
are simplified to push
, stack
because code ... end-code
definitions are right in the VM name space. Now let's try these new words:
In [9]:
vm.stack = [] # clear the data stack
vm.dictate(' s" Forth "') # get the string 'Forth '
vm.dictate(' s" is the easist "') # get the string 'is the easist '
vm.dictate(' s" programming langage."') # get the string 'programing language.'
vm.dictate('.s'); # view the data stack
In [10]:
print(vm.dictate('+').stack) # concatenate top two strings
print(vm.dictate('+').stack) # concatenate the reset
The +
command can certainly concatenate strings together and also can add numbers because Python's +
operator works that way. Please try it with integers and floating point numbers:
In [11]:
print(vm.dictate('123 456 + ').pop()); # Push 123, push 456, add them
print(vm.dictate('1.23 45.6 + ').pop());
If you want to see more examples like how to define if
,else
,then
,for
,next
,begin
,again
and more please refer to https://github.com/hcchengithub/peforth and find peforth.f
source code file.
Below tables list and explain all project-k FORTH kernel properties and methods.
Global variables (properties of the project-k object) and built-in functions (methods of the project-k object) can be seen and used in FORTH code ... end-code
word definitions. You are encouraged to read the projectk.py
source code directly. It's short, interesting and with a lot of comments.
No. | Global variable initial definition | Description |
---|---|---|
1 | vm | The project-k module object. |
2 | wordhash = {} | Forth words of recent active vocabularies. Find and get the word object through its name at the highest speed. |
3 | RET=None | The 'ret' instruction code. It marks the end of a colon word. |
4 | EXIT="" | The 'exit' instruction code. Same effect as 'ret' but used in colon definitions (instead of at the end of them). |
5 | stop = False | The flag to stop the outer loop. |
6 | newname = "" | The last new word's name. |
7 | newxt = function() | The last new word's executable. |
Above variables may not found in a traditional Forth system. Following ones are common Forth global variables you probably are very familiar with already .
No. | Global variable initial definition | Description |
---|---|---|
8 | compiling=False | The conventional flag of the Forth system state which is either compiling or interpreting. |
9 | ip=0 | The instruction pointer. Always points to the next word when in the inner loop. |
10 | stack = [] | The data stack. |
11 | rstack = [] | The return stack. |
12 | vocs = [] | The vocabulary list. e.g. ['forth','assembler', ...] |
13 | words = {} | The word-list. e.g. words['forth'][], words['assembler'][], ... etc. |
14 | current = "forth" | The current definition of word-list or vocabulary. |
15 | context = "forth" | The recent top priority word-list or vocabulary. |
16 | order = [context] | Active vocabularies and their priority order. |
17 | dictionary=[]; dictionary[0]=0; | The Forth VM memory. |
18 | here=1 | Index of the Form VM memory. Next free address. |
19 | tib="" | The conventional Forth system's Terminal Input Buffer string. |
20 | ntib=0 | The index of TIB string. |
No. | Built-in function | Description |
---|---|---|
1 | dictate("commands") | An exported method of the project-k VM. This is where the Forth VM receives commands from the outside world. |
2 | Word(a[]) | The common constructor of all Forth words. |
3 | nextstring("delimitor") | Low level tool to get next string from TIB. |
4 | nexttoken("delimitor") | High level tool to get next string from TIB. |
5 | panic("msg", bool:severe) | Prints the error message. |
6 | reset(void) | Reset the Forth VM to avoid hanging up the computer. |
7 | isReDef("name") | Check if the new word is a re-defined. |
8 | mytypeof(x) | N/A, only needed in projectk.js |
9 | inner(entry, bool:resuming) | The loop that runs through a colon definition as fast as possible. |
10 | outer(entry) | The loop that walks through the command string from dictate(). |
Above functions may not be seen in a traditional Forth system and that's all of them. Following ones are from common Forth words you probably are very familiar with already.
No. | Built-in function | Description |
---|---|---|
11 | current_word_list(void) | Gets words[current]. |
12 | context_word_list(void) | Gets words[context]. |
13 | tick("name") | Find the word object through the given word name. |
14 | comma(x) | Comile x into the dictionary. |
15 | execute(x) | Execte one word. x can be a word name or a word object or simply a function. |
16 | tos(void or index) | Get a value from the data stack w/o removing it. tos() or tos(0) is the Top of the data stack. |
17 | rtos(void or index) | Similar to tos() but works on the return stack. |
18 | pop(void or index) | Get and consume a value from the data stack. pop() or pop(0) is the Top of the data stack. |
19 | push(data,volid or index) | Push a value into the data stack. push(data) or push(data,0) adds the value to the Top of the data stack. |
20 | type("s") | N/A, for projectk.js only |
21 | last(void) | Gets the last defined Forth word. |
H.C. Chen @ FigTaiwan
hcchen5600@gmail.com