#!/usr/local/bin/python # # f o r t h . p y # Author: Chris Meyers @ # http://openbookproject.net/py4fun/forth/forth.html # import re try: raw_input # Python 2 except NameError: raw_input = input # Python 3 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 " 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()