feel free to email me at

fo.py, mighty Forth Lisp SmallTalk mix in Python

Blending Forth, Lisp and SmallTalk produced first results.

I mixed late binding, reflection, concatenation and dynamics in a small python file. 17 lines of VM. 18 lines of compiler.

Simple ping-pong program below.

Grab fo.py and run

python fo.py

Cut&paste code to it.

You need pygame installed! Tested under python 2.3.5/Linux. Probably will not work under windows because interactive interpreter uses weird non-blocking polling IO.

First of all let’s make a window.

from pygame init 0 ginit
from pygame.locals DOUBLEBUF 0 doublebuf
from pygame.display set_mode 2 mode
| # 800 , # 600 , ! display
: dinit ginit display doublebuf mode ! sc

Oops, window opened!

Importing functions and values from python looks like from <package> <func> <arity> <word>.. Word from imports function or value and binds it to name word. On execution of word arity values will be popped from stack and passed as arguments to imported function.

Another strange thing is a | # 800 , # 600 , ! display. You could write just a py display (800, 600) (see below) but i write it in this way to show how numbers and tuples constructed.

Word | puts empty tuple () on top of stack.
Word # reads next word and parses it as number and puts result on top of stack.
Word , appends value on top of stack to next value on stack (i.e. () + (800,)).
Word ! pops top of stack and binds this value to variable with a name in a next word (i.e. display = (800,600)).

Next, let’s draw something. Say, a square ball.

py white (255, 255, 255)
py shape (10, 10)
py ball-x 200
py ball-y 200
method fill 2 fill
: ball sc white | | ball-x , ball-y , , shape , fill drop
from pygame.display update 0 update
: loop input ball update drop loop

Word py binds result of pythonic evaluation of rest of string to a name in a next word (i.e. white = eval(’(255, 255, 255)’)).

After evaluation of method x a y, y will execute stack.pop().x(*args). Parameter a is an arity just like in word from.

Look at word loop. Yes, we have tail call optimization.

Word drop just throws out top of stack.

Word input is an non-blocking interactive interpreter you are typing into.

And as you could see changes are instant! See the power of reflection!

Now, move the ball!

from operator add 2 +
: move ball-x # 5 + ! ball-x ball-y # 5 + ! ball-y
: loop input ball update drop move loop

Word w will show you current bindings.

Run word w several times and look at ball-x and ball-y. They are changing!

Let’s trap the ball into a screen box!

py ball-dx 5
py ball-dy 5
: move ball-x ball-dx + ! ball-x ball-y ball-dy + ! ball-y
py ball-ddy 5
py ball-ddx 5
from operator gt 2 >
from operator neg 1 neg
: top # 0 ball-y > ? ball-ddy ! ball-dy
: left # 0 ball-x > ? ball-ddx ! ball-dx
: right ball-x # 800 > ? ball-ddx neg ! ball-dx
: bottom ball-y # 600 > ? ball-ddy neg ! ball-dy
: box top left right bottom
: loop input ball update drop move box loop

Word ? acts as if not stack.pop(): return.

I really like definition of box. And we have changed definition of loop on the fly again.

But, ohh, the ball is far away from the screen box. Put it back with bare hands:

py ball-x 100
py ball-y 100

What a weird trail! Let’s wipe it.

py black (0, 0, 0)
method fill 1 erase
: clear sc black erase drop
: loop input clear ball update drop move box loop

Nothing new just an old magic. Adding a bat!

py bat-x 50
py bat-length 50
: bat sc white | bat-x , bat-y , # 10 , bat-length , fill drop
py bat-y 100
: loop input clear ball bat update drop move box loop

Mouse control…

from pygame.event pump 0 pump
from operator getitem 2 []
from pygame.mouse get_pos 0 mouse
: bat-y mouse # 1 []
: loop input clear move ball box bat update drop pump drop loop

Ugly ghost bat! Make it real!

: ping ball-ddx ! ball-dx
: pong bat-x ball-x > ? ball-y bat-y > ? bat-y bat-length + ball-y > ? ping
: loop input clear move ball box pong bat update drop pump drop loop

Now type:

: init dinit loop

And run fo.py again. Whooa, it starts from where it was before! Even a ball position is preserved.

As a home work you could implement a second bat, AI and scores.