Added forth.py, an interpreter of Forth in Python written by Chris Meyers and available from http://openbookproject.net/py4fun/forth/forth.html. Great for exercises about this style.
This commit is contained in:
172
02-go-forth/forth.py
Normal file
172
02-go-forth/forth.py
Normal file
@@ -0,0 +1,172 @@
|
|||||||
|
#!/usr/local/bin/python
|
||||||
|
#
|
||||||
|
# f o r t h . p y
|
||||||
|
# Author: Chris Meyers @
|
||||||
|
# http://openbookproject.net/py4fun/forth/forth.html
|
||||||
|
#
|
||||||
|
import sys, re
|
||||||
|
|
||||||
|
ds = [] # The data stack
|
||||||
|
cStack = [] # The control struct stack
|
||||||
|
heap = [0]*2000 # The data heap
|
||||||
|
heapNext = 0 # Next avail slot in heap
|
||||||
|
words = [] # The input stream of tokens
|
||||||
|
|
||||||
|
def main() :
|
||||||
|
while 1 :
|
||||||
|
pcode = compile() # compile/run from user
|
||||||
|
if pcode == None : print; return
|
||||||
|
execute(pcode)
|
||||||
|
|
||||||
|
#============================== Lexical Parsing
|
||||||
|
|
||||||
|
def getWord (prompt="... ") :
|
||||||
|
global words
|
||||||
|
while not words :
|
||||||
|
try : lin = raw_input(prompt)+"\n"
|
||||||
|
except : return None
|
||||||
|
if lin[0:1] == "@" : lin = open(lin[1:-1]).read()
|
||||||
|
tokenizeWords(lin)
|
||||||
|
word = words[0]
|
||||||
|
words = words[1:]
|
||||||
|
return word
|
||||||
|
|
||||||
|
def tokenizeWords(s) :
|
||||||
|
global words # clip comments, split to list of words
|
||||||
|
words += re.sub("#.*\n","\n",s+"\n").lower().split() # Use "#" for comment to end of line
|
||||||
|
|
||||||
|
#================================= Runtime operation
|
||||||
|
|
||||||
|
def execute (code) :
|
||||||
|
p = 0
|
||||||
|
while p < len(code) :
|
||||||
|
func = code[p]
|
||||||
|
p += 1
|
||||||
|
newP = func(code,p)
|
||||||
|
if newP != None : p = newP
|
||||||
|
|
||||||
|
def rAdd (cod,p) : b=ds.pop(); a=ds.pop(); ds.append(a+b)
|
||||||
|
def rMul (cod,p) : b=ds.pop(); a=ds.pop(); ds.append(a*b)
|
||||||
|
def rSub (cod,p) : b=ds.pop(); a=ds.pop(); ds.append(a-b)
|
||||||
|
def rDiv (cod,p) : b=ds.pop(); a=ds.pop(); ds.append(a/b)
|
||||||
|
def rEq (cod,p) : b=ds.pop(); a=ds.pop(); ds.append(int(a==b))
|
||||||
|
def rGt (cod,p) : b=ds.pop(); a=ds.pop(); ds.append(int(a>b))
|
||||||
|
def rLt (cod,p) : b=ds.pop(); a=ds.pop(); ds.append(int(a<b))
|
||||||
|
def rSwap(cod,p) : a=ds.pop(); b=ds.pop(); ds.append(a); ds.append(b)
|
||||||
|
def rDup (cod,p) : ds.append(ds[-1])
|
||||||
|
def rDrop(cod,p) : ds.pop()
|
||||||
|
def rOver(cod,p) : ds.append(ds[-2])
|
||||||
|
def rDump(cod,p) : print "ds = ", ds
|
||||||
|
def rDot (cod,p) : print ds.pop()
|
||||||
|
def rJmp (cod,p) : return cod[p]
|
||||||
|
def rJnz (cod,p) : return (cod[p],p+1)[ds.pop()]
|
||||||
|
def rJz (cod,p) : return (p+1,cod[p])[ds.pop()==0]
|
||||||
|
def rRun (cod,p) : execute(rDict[cod[p]]); return p+1
|
||||||
|
def rPush(cod,p) : ds.append(cod[p]) ; return p+1
|
||||||
|
|
||||||
|
def rCreate (pcode,p) :
|
||||||
|
global heapNext, lastCreate
|
||||||
|
lastCreate = label = getWord() # match next word (input) to next heap address
|
||||||
|
rDict[label] = [rPush, heapNext] # when created word is run, pushes its address
|
||||||
|
|
||||||
|
def rDoes (cod,p) :
|
||||||
|
rDict[lastCreate] += cod[p:] # rest of words belong to created words runtime
|
||||||
|
return len(cod) # jump p over these
|
||||||
|
|
||||||
|
def rAllot (cod,p) :
|
||||||
|
global heapNext
|
||||||
|
heapNext += ds.pop() # reserve n words for last create
|
||||||
|
|
||||||
|
def rAt (cod,p) : ds.append(heap[ds.pop()]) # get heap @ address
|
||||||
|
def rBang(cod,p) : a=ds.pop(); heap[a] = ds.pop() # set heap @ address
|
||||||
|
def rComa(cod,p) : # push tos into heap
|
||||||
|
global heapNext
|
||||||
|
heap[heapNext]=ds.pop()
|
||||||
|
heapNext += 1
|
||||||
|
|
||||||
|
rDict = {
|
||||||
|
'+' : rAdd, '-' : rSub, '/' : rDiv, '*' : rMul, 'over': rOver,
|
||||||
|
'dup': rDup, 'swap': rSwap, '.': rDot, 'dump' : rDump, 'drop': rDrop,
|
||||||
|
'=' : rEq, '>' : rGt, '<': rLt,
|
||||||
|
',' : rComa,'@' : rAt, '!' : rBang,'allot': rAllot,
|
||||||
|
|
||||||
|
'create': rCreate, 'does>': rDoes,
|
||||||
|
}
|
||||||
|
#================================= Compile time
|
||||||
|
|
||||||
|
def compile() :
|
||||||
|
pcode = []; prompt = "Forth> "
|
||||||
|
while 1 :
|
||||||
|
word = getWord(prompt) # get next word
|
||||||
|
if word == None : return None
|
||||||
|
cAct = cDict.get(word) # Is there a compile time action ?
|
||||||
|
rAct = rDict.get(word) # Is there a runtime action ?
|
||||||
|
|
||||||
|
if cAct : cAct(pcode) # run at compile time
|
||||||
|
elif rAct :
|
||||||
|
if type(rAct) == type([]) :
|
||||||
|
pcode.append(rRun) # Compiled word.
|
||||||
|
pcode.append(word) # for now do dynamic lookup
|
||||||
|
else : pcode.append(rAct) # push builtin for runtime
|
||||||
|
else :
|
||||||
|
# Number to be pushed onto ds at runtime
|
||||||
|
pcode.append(rPush)
|
||||||
|
try : pcode.append(int(word))
|
||||||
|
except :
|
||||||
|
try: pcode.append(float(word))
|
||||||
|
except :
|
||||||
|
pcode[-1] = rRun # Change rPush to rRun
|
||||||
|
pcode.append(word) # Assume word will be defined
|
||||||
|
if not cStack : return pcode
|
||||||
|
prompt = "... "
|
||||||
|
|
||||||
|
def fatal (mesg) : raise mesg
|
||||||
|
|
||||||
|
def cColon (pcode) :
|
||||||
|
if cStack : fatal(": inside Control stack: %s" % cStack)
|
||||||
|
label = getWord()
|
||||||
|
cStack.append(("COLON",label)) # flag for following ";"
|
||||||
|
|
||||||
|
def cSemi (pcode) :
|
||||||
|
if not cStack : fatal("No : for ; to match")
|
||||||
|
code,label = cStack.pop()
|
||||||
|
if code != "COLON" : fatal(": not balanced with ;")
|
||||||
|
rDict[label] = pcode[:] # Save word definition in rDict
|
||||||
|
while pcode : pcode.pop()
|
||||||
|
|
||||||
|
def cBegin (pcode) :
|
||||||
|
cStack.append(("BEGIN",len(pcode))) # flag for following UNTIL
|
||||||
|
|
||||||
|
def cUntil (pcode) :
|
||||||
|
if not cStack : fatal("No BEGIN for UNTIL to match")
|
||||||
|
code,slot = cStack.pop()
|
||||||
|
if code != "BEGIN" : fatal("UNTIL preceded by %s (not BEGIN)" % code)
|
||||||
|
pcode.append(rJz)
|
||||||
|
pcode.append(slot)
|
||||||
|
|
||||||
|
def cIf (pcode) :
|
||||||
|
pcode.append(rJz)
|
||||||
|
cStack.append(("IF",len(pcode))) # flag for following Then or Else
|
||||||
|
pcode.append(0) # slot to be filled in
|
||||||
|
|
||||||
|
def cElse (pcode) :
|
||||||
|
if not cStack : fatal("No IF for ELSE to match")
|
||||||
|
code,slot = cStack.pop()
|
||||||
|
if code != "IF" : fatal("ELSE preceded by %s (not IF)" % code)
|
||||||
|
pcode.append(rJmp)
|
||||||
|
cStack.append(("ELSE",len(pcode))) # flag for following THEN
|
||||||
|
pcode.append(0) # slot to be filled in
|
||||||
|
pcode[slot] = len(pcode) # close JZ for IF
|
||||||
|
|
||||||
|
def cThen (pcode) :
|
||||||
|
if not cStack : fatal("No IF or ELSE for THEN to match")
|
||||||
|
code,slot = cStack.pop()
|
||||||
|
if code not in ("IF","ELSE") : fatal("THEN preceded by %s (not IF or ELSE)" % code)
|
||||||
|
pcode[slot] = len(pcode) # close JZ for IF or JMP for ELSE
|
||||||
|
|
||||||
|
cDict = {
|
||||||
|
':' : cColon, ';' : cSemi, 'if': cIf, 'else': cElse, 'then': cThen,
|
||||||
|
'begin': cBegin, 'until': cUntil,
|
||||||
|
}
|
||||||
|
|
||||||
|
if __name__ == "__main__" : main()
|
||||||
Reference in New Issue
Block a user