A rewritten of: https://github.com/hcchengithub/project-k/wiki/Play-with-the-forth-kernel-on-python
You can play with this article online directly through the jupyter notebook binder: https://mybinder.org/v2/gh/hcchengithub/project-k/master

Play with the FORTH kernel on jupyter notebook

The small file projectk.py from the project-k on GitHub at the same directory where you launched this jupyter notebook is the only thing we need. Project-k's purpose is to put the FORTH programming language fundamental components into a small kernel file that bridges FORTH into the host system which is Python here. Obviously projectk.js is for JavaScript. Project-k supports these two host systems so far (2018.3.15).

With the project-k kernel, it takes only 15 minutes to build your own FORTH system. So when you need an user interface to communicate with your machine, FORTH is a remarkable choice.

1. Import the project-k kernel

Now let's create a project-k vm object:


In [1]:
import projectk as vm  # vm means 'Virtual Machine'.

The above python statement created an instance of project-k object.

2. Try the Virtual Machine's basic features

Let's try some project-k vm methods:


In [2]:
vm.dictate("123").stack


Out[2]:
[123]

vm.dictate() method is the way project-k VM receives your commands (in a multiple lines string). It actually is also the way we feed it an entire Forth source code file. "123" means: "please push this number into the FORTH data stack". vm.stack is the Forth VM data stack which was empty at first and now has one item, 123, that we've just put in it. Some methods as vm.dictate() returns the vm object itself so we can cascade multiple function calls in one line. Therefore the above statement vm.dictate("123").stack is cascated from the two lines:

vm.dictate("123")
vm.stack

3. Use code and end-code to define a high-level FORTH word

In FORTH we call an identifier a word that is a command or a variable name. Now we can start to define a new word:


In [3]:
vm.dictate("code hi print('Hello World!!') end-code hi");


Hello World!!

A code word is defined between code and end-code. The first token after the leading code, which is 'hi' in this example, is the name of the new FORTH word. All the rests down to the ending end-code are python statements. Note after end-code above we execute hi the new word immediately and it works!

4. Define the 'words' command

'words' is a basic FORTH word that lists all words. This example shows you how to define it.


In [4]:
vm.dictate("code words print([w.name for w in words['forth'][1:]]) end-code words");


['code', 'end-code', 'hi', 'words']

Where the vm.words that appears in above definition is project-k vm's word-list that is a common component of a FORTH system. We can see it this way:


In [5]:
vm.words


Out[5]:
{'forth': [0, <Word 'code'>, <Word 'end-code'>, <Word 'hi'>, <Word 'words'>]}

5. Define commands + , .s , and s".

These examples demonstrate project-k built-in methods push(), pop(), and nexttoken().


In [6]:
vm.dictate("code + push(pop(1)+pop()) end-code");

The vm knows how to do the '+' now, let's try:


In [7]:
vm.stack=[]  # clear the data stack
vm.dictate("123 456 +").stack


Out[7]:
[579]

FORTH is a programming language of postfix-expression. "123 456 +" means: "Please push 123 into the data stack, please push 456 too. Now please get the top two cells out of the data stack, add them and push the result back to the data stack". The result is 579 left in the data stack.

The common FORTH word to view its data stack is .s, we can have it by this definition:


In [8]:
vm.dictate("code .s print(stack) end-code");
vm.execute('.s');


[579]

Another FORTH word that quotes a text string is s" that can be defined like this:


In [9]:
vm.dictate('code s" push(nexttoken(\'"\'));nexttoken() end-code');

The FORTH term TIB (terminal input buffer) is simply the string given from vm.dictate('I am the TIB'). nexttoken() is a project-k built-in function that gets a quote out of the TIB from the current position to the given delimiter which is " in the example above. So FORTH strings can now be expressed by s" this is a string" and that will be pushed to the FORTH data stack. Let's use it:


In [10]:
# Put a string onto the TOS (Top of the data stack)
vm.dictate('s" The wise build bridges, " .s');


[579, 'The wise build bridges, ']

In [11]:
vm.dictate('s" while the foolish build barriers." .s');


[579, 'The wise build bridges, ', 'while the foolish build barriers.']

and according to the + word we defined above, it can concatenate two strings too. So let's execute + and check the result on the data stack:


In [12]:
vm.execute('+').execute('.s');


[579, 'The wise build bridges, while the foolish build barriers.']

The two strings are correctly concatenated into one.

Look into the project-k module object

6. See them all at a glance

First, let's see what are in the vm. When in a code word definition these properties are global variables and global functions being seen in that new word.


In [13]:
# list all global functions and global variables seen in a code word definition. 

print([propertie for propertie in dir(vm) if not propertie.startswith('__')])


['Comment', 'EXIT', 'RET', 'Word', 'code', 'comma', 'compiling', 'context', 'context_word_list', 'current', 'current_word_list', 'debug', 'dictate', 'dictionary', 'dis', 'docode', 'doendcode', 'endcode', 'execute', 'genfunc', 'genxt', 'here', 'inner', 'inspect', 'ip', 'isReDef', 'json', 'last', 'local', 'major_version', 'name', 'newhelp', 'newname', 'newxt', 'nextstring', 'nexttoken', 'ntib', 'order', 'os', 'outer', 'pdb', 'phaseA', 'phaseB', 'pop', 'push', 're', 'reset', 'rpop', 'rstack', 'rtos', 'stack', 'stop', 'sys', 'tib', 'tick', 'tos', 'vm', 'vocs', 'wordhash', 'words']

In [14]:
# List only functions out of the aboves

print([method for method in dir(vm) if callable(getattr(vm,method))])


['Comment', 'Word', 'comma', 'context_word_list', 'current_word_list', 'dictate', 'docode', 'doendcode', 'execute', 'genfunc', 'genxt', 'inner', 'isReDef', 'last', 'newxt', 'nextstring', 'nexttoken', 'outer', 'phaseA', 'phaseB', 'pop', 'push', 'reset', 'rpop', 'rtos', 'tick', 'tos']

7. Get help of project-k global functions

Project-k built-in functions are explained with comments in the projectk.py source code. View them by using the python help() function.


In [15]:
help(vm.tos)
help(vm.pop)
help(vm.push)
help(vm.nexttoken)


Help on function tos in module projectk:

tos(index=None, value=None)
    # Top of Stack access easier. ( tos(2) tos(1) tos(void|0) -- ditto )
    # tos(i,new) returns tos(i) and by the way change tos(i) to new value this is good
    # for counting up or down in a loop.

Help on function pop in module projectk:

pop(index=None)
    # Stack access easier. e.g. pop(1) gets tos(1) ( tos(2) tos(1) tos(0) -- tos(2) tos(0) )
    # push(formula(pop(i)),i-1) manipulate the tos(i) directly, when i is the index of a loop.

Help on function push in module projectk:

push(data=None, index=None)
    # Stack access easier. e.g. push(data,1) inserts data to tos(1), 
    # ( tos2 tos1 tos -- tos2 tos1 data tos )
    # push(formula(pop(i)),i-1) manipulate the tos(i) directly, usually when i 
    # is the index of a loop.

Help on function nexttoken in module projectk:

nexttoken(deli='\\s')
    # Get next token which is found after the recent ntib of TIB.
    # If delimiter is RegEx white-space ('\\s') or absent then skip all leading white spaces first.
    # Usual case, skip the next character which should be a white space for Forth.
    # But if delimiter is CRLF, which is to read the entire line, for blank lines the ending CRLF won't be skipped.
    # o  Return "" if TIB has nothing left. 
    # o  Return the remaining TIB if delimiter is not found.
    # o  The ending delimiter is remained. 
    # o  The delimiter is a regular expression.

8. Self-reference of a code word


In [16]:
vm.dictate("code see-locals print(locals()) end-code see-locals");


{'_me': <Word 'see-locals'>}

The _me object points to the new word itself. For example, this word prints its own name:


In [17]:
vm.dictate("code who-am-I? print('My name is: ' + _me.name) end-code").execute('who-am-I?');


My name is: who-am-I?

9. Let's see the globals

You will want to know about the globals when at the view point in a code word definition. Many of them have been seen above yet this is from a different view point.


In [18]:
vm.dictate("code see-globals print(globals().keys()) end-code see-globals");


dict_keys(['__name__', '__doc__', '__package__', '__loader__', '__spec__', '__file__', '__cached__', '__builtins__', 're', 'pdb', 'os', 'sys', 'inspect', 'dis', 'json', 'name', 'vm', 'major_version', 'ip', 'stack', 'rstack', 'vocs', 'words', 'current', 'context', 'order', 'wordhash', 'dictionary', 'here', 'tib', 'ntib', 'RET', 'EXIT', 'compiling', 'stop', 'newname', 'newxt', 'newhelp', 'debug', 'local', 'reset', 'Word', 'Comment', 'last', 'current_word_list', 'context_word_list', 'nextstring', 'nexttoken', 'tick', 'isReDef', 'comma', 'phaseA', 'phaseB', 'execute', 'inner', 'outer', 'genxt', 'genfunc', 'docode', 'code', 'doendcode', 'endcode', 'dictate', 'tos', 'rtos', 'rpop', 'pop', 'push'])

The __name__ is 'projectk', as shown below, that indicates that the namespace of this small world is within the FORTH virtual machine. We can't see anything in the outside world unless vm.push() passes them into the data stack.


In [19]:
vm.dictate("code see__name__ print(globals()['__name__']) end-code see__name__");


projectk

For example, this jupyter notebook itself is the main program we are running and through vm.push() we can pass this information into the FORTH vm and get it back by vm.pop():


In [20]:
vm.push(__name__).pop()
vm.push(__IPYTHON__).pop()  # see another property from the main program


Out[20]:
'__main__'
Out[20]:
True

List of all project-k global variables and built-in functions

List of all project-k global variables and built-in functions can be found at the end of this page on project-k project wiki on GitHub.


You can start building your own FORTH system now. Don't hesitate to let me know anything that is unclear above.

May the FORTH be with you!

Have fun!

H.C. Chen @ FitTaiwan
hcchen5600@gmail.com
2018.3.15