[Edu-sig] A little Forth core in Python

Dirk-Ulrich Heise hei@adtranzsig.de
Thu, 12 Oct 2000 09:40:25 +0200


Yesterday i started hacking together a little script
that acts like a Forth engine. Maybe this can be useful
for those of you who want to show some postfix
syntax to pupils without introducing extra compilers
or the likes. I did it after reading about Joy ; it was an
interesting reading - that somebody creates a FORTHish
syntax and the outcome is a functional language.
The engine i wrote places Python objects on its stack -
at the moment, only numbers are possible, but with a
little more work, it might be possible to define combinators
like the ones of Joy as primitives, because this engine
cares about data types the same way Python does -
functions are just objects. So an "apply" or "map"
primitive is possible.

By now, my script only does some simple arithmetic,
and it has dup and swap and "." (print), all
definition words and control structures of FORTH
are missing. Import the script and it will explain
its usage. Should be working with any Python 1.4
to 2.0.

Feedback appreciated!

Dipl.Inform. Dirk-Ulrich Heise
hei@adtranzsig.de
dheise@debitel.net

# FORTH.PY
# 11.10.00
# dirk heise
#
# This is public domain software. It consists of exactly this file.
# And i would really, really appreciate if people who *publish* stuff
# derived from this script would put it in the PD also.
#
# VERSION # : see the variable "version"
#
# a simple Forth like language interpreted in Python.
#
# It's a Forth that uses Python objects.
# Historically, Forth began with having only one data type, a 16bit word.
# Later, several deviating schemes were introduced.
#
# Here, we use Python objects as what lands on the Forth stack and is
# manipulated by the Forth primitive words. So, Python semantics hold:
# Objects are always referenced, strings and ints are immutable etc,
# there is the same kind of runtime type checking that Python applies.
# Forth operators are implemented through the according Python operators,
# Python exceptions are thrown when types don't fit an operation.
#
# If you want to extend the core set of words:
#  - You must create a class (derive it from CWord or another suitable
#    CWord-derived class)
#  - You must create an instance of this class in the init() method at
#    the end. (in practice, extend a list)
#
# TODOs:
#   - this is by far not a complete FORTH. I can't list all things
#     missing.
#   - seek the word "TODO" in the file to find particular things
#     i noticed.
#   - definition words are still missing
#   - conditionals are missing
#   - error handling is missing (Python exceptions are thrown to
#     the user - which isn't that good, as the FORTH user would be
#     interested in FORTH stack info, not Python stack stuff.)
#
# Why did i write this?
# - People on the python edu-sig mailing list
#   found it useful to show prefix or postfix based
#   syntaxes to kids to open their mind. Okay, you're right, here you
#   have a toy for your educational purposes.
#
# - I thought it would be interesting to see how a FORTH with Python
#   objects would feel like.
#
# - I wanted to know how fast it goes. Now i know. I assumed it would be
#   very fast using Python. This assumption turned out to be right.
#
# For my own and your information, here's how much time i spent
# writing this so far.
#
# 100 Minutes: +,.,integer numerals work.
# 24  Minutes: commented. Simplified classes. Added "-".
# 8   Minutes: added "*", simplified init function
# 36  Minutes: added divide, simplified classes
#              added swap
#              added help() function.
# 10 Min : Reworking this program header
# Total so far: 178 min or 2h58
# That's it for version 0.001.
#---------------------- IMPORTS ----------------------
import string
#---------------------- CONSTs -----------------------
FALSE = 0
TRUE  = 1


version = "0.001"

vocabulary = {}
stack = []

def push(x):
  """
    push it onto the forth stack
  """
  global stack
  stack.append(x)

def pop():
  """
    push it onto the forth stack
  """
  global stack
  x = stack[-1]
  stack = stack[:-1]
  return x

class CWord:
  """
    represents a FORTH word
    an abstract base class! does nothing when executed.
    member variables:
     name -keeps name of word
  """
  def __init__(self,name):
    """
      constructor - create an empty word definition
      for later introspection, the word knows its name
    """
    self.name = name

# ----------------------- arithmetics ---------------------------------
#   use like "3 4 +" (-->7)
class CPlus(CWord):
  """
    add two numbers
  """
  def execute(self):
    push(pop()+pop())

class CMinus(CWord):
  """
    subtract
  """
  def execute(self):
    a = pop()
    b = pop()
    push(b-a)

class CMul(CWord):
  """
    multiply
  """
  def execute(self):
    a = pop()
    b = pop()
    push(b*a)

class CDiv(CWord):
  """
    divide
  """
  def execute(self):
    a = pop()
    b = pop()
    push(b/a)

# ----------------------- stack manipulation ---------------------


class CDup(CWord):
  """
     a --> a a  (duplicates top value on stack)
  """
  def execute(self):
    a = pop()
    push(a)
    push(a)

class CSwap(CWord):
  """
     a b --> b a (exchanges top two values on stack)
  """
  def execute(self):
    a = pop()
    b = pop()
    push(a)
    push(b)

# ---------------------------- I/O -------------------------------
class CPrint(CWord):
  """
    print top number from stack - use like '3 .' --> prints 3"
  """
  def execute(self):
    print pop()

# --------------------- definition words -------------------------

# TODO

# ----------------------------------------------------------------

def find(w):
  """
    w is a string - try to find that word in the forth dictionary
    return the word definition object
  """
  global vocabulary
  try:
    x = vocabulary[w]
  except :
    x = None
  return x

def execute(s):
  """
    interpret a forth string
  """
  l = string.split(s)
    # split it up at the spaces, so now l ist a list of forth words.
    # TODO:
    # Please note: This is an oversimplistic approach for real world
    # application - it prohibits having string literals in the FORTH source
    # code that contain spaces
  for w in l:
    word = find(w)
      # try to find it in the forth dictionary
    if word :
      # execute it
      word.execute()
    else:
      # suppose it's a number literal
      # TODO:
      # here,  we should watch out for exceptions.
      push(eval(w))

def init():
  """
    initialize forth engine
  """
  global stack
  global vocabulary
  stack = []
  vocabulary = {}
  # vocabulary["."] = CPrint(".")
  # vocabulary["+"] = CPlus("+")
  # vocabulary["-"] = CMul("-")
  # vocabulary["*"] = CMinus("*")
  # vocabulary["dup"] = CDup("dup")
  # ahem - this is getting old... better: (and avoids errors)
  for name,classname in [(".",CPrint),
            ("+",CPlus),
            ("-",CMinus),
            ("*",CMul),
            ("dup",CDup),
            ("/",CDiv),
            ("swap",CSwap)
            ]:
    vocabulary[name] =classname(name)
    print "Added "+name

def help():
  print ("# example for '.' :             ")
  print ("forth.execute('5 .')            ")
  print ("# example for '+' :             ")
  print ("forth.execute('3 4 + .')        ")
  print ("# example for '-' :             ")
  print ("forth.execute('32 4 - .')       ")
  print ("# example for '*' :             ")
  print ("forth.execute('3.14 2 * .')     ")
  print ("# example for 'dup' :           ")
  print ("forth.execute('3.14 dup . .')   ")
  print ("# example for '/' :             ")
  print ("forth.execute('7 2.0 / .')      ")
  print ("# example for 'swap' :          ")
  print ("forth.execute('7 6 swap - .')   ")



init()

print "FORTH has been loaded."
print " use forth.init() to reset the engine."
print " use forth.execute('1 1 + .') tetc. to run forth words."
print " use forth.help() to print out some examples."

##########################################################
# end of file