#!/usr/local/bin/python
#
#   f o r t h . p y
#
import re, sys, traceback, copy
import queryIsectd as qI

ds       = []          # The data stack stores anything
cStack   = []          # The control struct stack
heap     = ['']*999    # The data heap stores anything but IS LIMITED to a fixed number of slots. If the same variable
		       # is CREATED twice, the data is written to a later slot but the earlier data is still present.
heapNext =  0          # Next avail slot in heap
words    = []          # The input stream of tokens
doForget = False       # Don't forget soft core words

def main() :
    global sock2
    if len(sys.argv) >= 2:
	softcore = sys.argv[1]
    	tokenizeWords(open(softcore).read())
	if len(sys.argv) < 5:
		fatal("Host, port ,service missing!")
	else: 
		sock2 = qI.attach(sys.argv[2], int(sys.argv[3]), sys.argv[4], '3', '0')# Isectd client login
    while 1 :
	try :
	    pcode = compile()          			# compile/run from user
	    # print "pcode = ", pcode
	    if pcode == None : 
		    print 
		    qI.close(sock2)
		    sys.stdout.flush()
		    return					
	    execute(pcode)
	    sys.stdout.flush()
	except Exception, err :
	    print traceback.print_exc()			# we never want to exit the Forth interpreter, but instead
							# we want to see the Python traceback for debugging.

def ForgetWords() :
	global doForget, lds, lcStack, lheap, lheapNext, lrDict, ds, cStack, heap, heapNext, rDict
	if doForget :
		del ds[:]
		ds = copy.deepcopy(lds)
		del cStack[:]
		cStack = copy.deepcopy(lcStack)
		del heap[:]
		heap = copy.deepcopy(lheap)
		heapNext = lheapNext
		rDict.clear()
		rDict = copy.deepcopy(lrDict)
	else :  
		lds = copy.deepcopy(ds)				# system state stored when program starts 
		lcStack = copy.deepcopy(cStack)			# after loading softcore library
		lheap = copy.deepcopy(heap)
		lheapNext = heapNext
		lrDict = copy.deepcopy(rDict)
		doForget = True

#============================== 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()
	    	ForgetWords()			 	  # restore system state to that after softcore words were loaded
        tokenizeWords(lin)
    word = words[0]
    words = words[1:]
    return word

def tokenizeWords(s) :
    global words                                          # clip comments, split to list of case sensitive words
    words += re.sub("#\n|#\s.*\n","\n",s+"\n").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)		# call a word implicitly from it's address in memory
        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 rWords(cod,p) : print "words = ", words
def rDot (cod,p) : sys.stdout.write(str(ds.pop()) + ' ')# print without \n using a space instead
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

#================================= Additions to the runtime words for scripting and typeless variables

def rNewLine(cod,p) : print

def rInt(cod,p) : a = ds.pop(); ds.append(int(a)) # convert string to int

def rStr(cod,p) : a = ds.pop(); ds.append(str(a)) # convert int to string

def rStrip(cod,p) : a = ds.pop(); ds.append(eval("a.strip()")) # strip string

def rClean(cod,p) : a = ds.pop(); ds.append(eval("a.replace(' ', '')")) # remove all white space in a string

def rComment(cod,p) :			# words surrounded by """ are discarded for multiline commenting
    while 1: 				
        label = getWord()		
        if label == '"""' : break

def rQuote(cod,p) :				# words surrounded by ' are printed verbatim
    while 1: 					# to mix Forth and other scripting languages together
        label = getWord()		
        if label == "'" : break
	if label == "/n" :
		print 
	else :
        	sys.stdout.write(label + ' ')	# print without \n using a space instead

def rSpeech(cod,p) :			# words surrounded by " are pushed onto the stack as a string
    labels = ''				# to allow typeless variables to be created
    while 1: 				
        label = getWord()		
        if label == '"' : break
        labels += label + ' '
    ds.append(labels)

def rEval(cod,p) : 			# Safe Python eval which only works on a string containing a list
    rs_raw = ds.pop()			# returning a native Python list
    if rs_raw[0] == "[" :		
	ds.append(eval(rs_raw))	
    else : fatal(rs_raw)

def rSlice(cod,p) : 			# Safe Python eval list slice
    a = ds.pop()			# Python list
    b = ds.pop()			# slice parameters e.g. [1,1,2]
    if b[0] == "[" :
	g = b.find('=')
	if g > -1:
		h = b[g+1:].strip()	# Update list position with new data
		b = b[:g]
	else : h = ''
	c = eval(b)
	if len(c) == 1 :
		d = c[0]
		if h : a[d]=h; ds.append(a)	# Because we've updated the recordset all of it must be returned
		else : ds.append(a[d])
	elif len(c) == 2 :
		d = c[0]
		e = c[1]
		if h : a[d][e]=h; ds.append(a)
		else : ds.append(a[d][e])
	elif len(c) == 3 :
		d = c[0]
		e = c[1]
		f = c[2]
		if h : a[d][e][f]=h; ds.append(a)
		else : ds.append(a[d][e][f])
    else : fatal("Not a valid slice. Should be [1] or [1,1] or [1,1,2] or [1,1,2] = some new data")

def rLen(cod,p) : 			# Python list size
    a = ds.pop()			
    ds.append(len(a))

#================================= Isectd query function

def rQuery (cod,p) :
   global sock2
   sql = ds.pop()
   if sql.strip().split(' ')[0].lower() == "select" :
	rs_raw = qI.query(sock2, sql)
    	if rs_raw[0] == "[" : qI.commit(sock2)
	else :	qI.rollback(sock2)
	ds.append(rs_raw)
   else : fatal("Not a valid Select query")
   
#=================================

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, 'words': rWords,
 
  '/n' : rNewLine, 'int' : rInt, 'str' : rStr, 'strip' : rStrip, 'clean' : rClean,
  '"""' : rComment, '\'' : rQuote, '"' : rSpeech, 'eval' : rEval, 'slice' : rSlice, 'len' : rLen,

  'query' : rQuery,
}
#================================= 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 Exception(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()
