User:Marinus/Gibberish interpreter

This is my implementation of Gibberish. It should be a full implementation, though the reference interpreter isn't finished so I can't really be sure. Unless I missed a bug, it is compatible with the finished part of the official interpreter.

(I first wanted to finish the official interpreter, but this new one was a lot easier to do.)


 * 1) gibberish.py - python gibberish interpreter
 * 2) possible inconsistencies:
 * 3)   - q(3) stops the entire program, not just the currently running
 * 4)     piece of code, even when called from an exec'ed string
 * 5)     (I took "currently-running program" to mean the entire program)
 * 6)   - not in specification, but unfinished official interpreter seems to
 * 7)     do it this way and/or given examples seem to rely on this:
 * 8)     - trying to copy/move/pop past the stack results in a run-time error
 * 9)     - numbers are floating point
 * 10)     - numbers used for stack indexes are rounded down
 * 11)     - type mismatches are run-time errors
 * 12)     - the stack and selected instruction set are global across "subroutines"
 * 13)     - pushing a string constant [like this] counts as one instruction
 * 14)     - using x(0) to set the instruction set to one that doesn't exist
 * 15)       is a run-time error
 * 16)   - neither in specification or in unfinished official interpreter
 * 17)     - but seems logical:
 * 18)       - exec'ing a string of malformed code is a run-time error
 * 19)       - numbers used for string indexes are rounded down
 * 20)         (like the stack)
 * 21)       - reading past EOF returns -1 (char) or "" ([]?) (str)
 * 22)         (most other esolangs return -1 on EOF)
 * 23)       - a line read from stdin still has its newline attached
 * 24)         (to distinguish [\n] (=empty line) from [] (=EOF),
 * 25)          also this is how almost everyone else does it)
 * 26)       - logical operators consider 0.0000... to be false and everything
 * 27)         else to be true
 * 28)     - and I have no idea how else to do it:
 * 29)       - lshift and rshift convert their arguments to ints first
 * 30)         (can't shift something, say, 2.3 bits to the left,
 * 31)          and bitshifting IEEE-754 floating point values seems monumentally
 * 32)          useless in comparison with bitshifting ints)
 * 33)       - arguments to binary and/or are converted to ints first
 * 34)         (fits with lshift/rshift)
 * 1)         else to be true
 * 2)     - and I have no idea how else to do it:
 * 3)       - lshift and rshift convert their arguments to ints first
 * 4)         (can't shift something, say, 2.3 bits to the left,
 * 5)          and bitshifting IEEE-754 floating point values seems monumentally
 * 6)          useless in comparison with bitshifting ints)
 * 7)       - arguments to binary and/or are converted to ints first
 * 8)         (fits with lshift/rshift)
 * 1)       - arguments to binary and/or are converted to ints first
 * 2)         (fits with lshift/rshift)

from operator import * import sys

STRT = str NUMT = float
 * 1) types used for strings and numbers

def typedesc(type): if type==STRT: return "string" if type==NUMT: return "number" return "invalid (%s)"%str(type)
 * 1) helper functions ###

def errstr(code, error, position): amt = max(0, position - 5) prev = code[amt : position] if amt!=0: prev = "..." + prev amt = min(position + 6, len(code)) next = code[position+1 : amt] if amt!=len(code): next += "..." return "%s at position %d (%s ->%s<- %s)" % \ (error, position, prev, code[position], next)
 * 1) make error string

def items_errstr(code, error, position): prev = ",".join([str(c) for c in code[max(0, position - 5) : position]]) next = ",".join([str(c) for c in code[position+1:position+6]])
 * 1) make error string with list of items

return "%s at ip %s (%s ->%s<- %s)" % \ (error, position, prev, str(code[position]), next)

def ordsuffix(num): if (int(num) % 100 in (11,12,13)): return "th" if (int(num) % 10 == 1): return "st" if (int(num) % 10 == 2): return "nd" if (int(num) % 10 == 3): return "rd" return "th"
 * 1) return ordinal suffix (i.e. 1 => st and 4 => th)



class Stack:
 * 1) stack class

stack = None def __init__(self, copystack=None): if copystack: self.stack = copystack[:] else: self.stack = []

def push(self, value): # make sure all numbers use the same type if type(value) in [bool, int]: value = NUMT(value) self.stack.append(value) def pop(self): return self.stack.pop

def swapn(self, n): n=int(n) foo = self.stack[-1-n] self.stack[-1-n] = self.stack[-1] self.stack[-1] = foo

swap = lambda s: s.swapn(1) swap2 = lambda s: s.swapn(2) swap3 = lambda s: s.swapn(3) def copy(self, n): n=int(n) self.stack.append(self.stack[-1-n])

dup = lambda s: s.copy(0) def move(self, n): n=int(n) self.stack.append(self.stack[-1-n]) del self.stack[-1-n]

def insert(self, n, value): n=int(n) self.stack.insert(-n-1, value)

# 'inverted' copy/move = copy/move counting from the bottom of the stack def invcopy(self, n): n=int(n) self.stack.append(self.stack[n]) def invmove(self, n): n=int(n) self.stack.append(self.stack[n]) del self.stack[n]

# getitem/setitem/len def __getitem__(self, n): return self.stack[-1-n] def __setitem__(self, n, val): self.insert(n, val) def __len__(self): return len(self.stack)

class Parser: CONSTANT, COMMAND = range(2)
 * 1) parser

# program will be made up of a list of these class Item: def __init__(self, type, value): if type in (Parser.CONSTANT, Parser.COMMAND): self.type=type self.value=value else: raise TypeError, "item type invalid: %d" % type def __str__(self): if self.type==Parser.COMMAND: return self.value elif self.type==Parser.CONSTANT: if isinstance(self.value, str): return "[%s]" % self.value else: return str(self.value)

# takes string, returns list of items @staticmethod def parse(string): items = [] i = 0 while i0: i += 1 if (i>=len(string) and lvl>0): raise ValueError(errstr(string, "unterminated [", i-1)) if string[i] == ']': lvl -= 1 elif string[i] == '[': lvl += 1 if (lvl!=0): curstr += string[i] items.append(Parser.Item(Parser.CONSTANT, curstr)) # whitespace is ignored elif string[i] in ' \n\t': pass # ] without [ == error elif string[i] == ']': raise ValueError(errstr(string, "] without [", i)) # not one of these == command else: items.append(Parser.Item(Parser.COMMAND, string[i])) i += 1 return items

class Interpreter:

# exception to be caught if something goes wrong with the code class CodeError(Exception): pass;

world = None code = None stack = None ip = 0 activeset = 0 sets = None # command sets, see init parent = None

def __init__(self,world,code,stack=None,ip=0,activeset=0,parent = None): self.world = world self.code = code if stack==None: self.stack = Stack else: self.stack = stack self.ip = ip      self.activeset = activeset self.parent = None

s=self # makes the huge list less long s.sets = [ # instruction set 0 # { 'e' : s.activateSet1, 'f' : s.activateSet2, 'g' : s.activateSet3, 'x' : s.cActivateSet, 'j' : s.cGetSet, 'z' : s.cNop },         # instruction set 1 # { 'u' : s.cDuplicate, 'a' : s.cAdd, 's' : s.cSub, 'm' : s.cMul, 'd' : s.cDiv, 't' : s.cToStr, 'i' : s.cToNum, 'c' : s.cConcatenate, 'o' : s.cOutput, 'q' : s.cInlineOutput, 'n' : s.cReadChar, 'l' : s.cReadLine, 'h' : s.cSubstring, 'y' : s.cStrLen, 'v' : s.cDiscard, 'p' : s.cCopy, 'k' : s.cMove, 'r' : s.cStackSize },         # instruction set 2 # { 'u' : s.cGT, 'd' : s.cLT, 's' : s.cSkip, 't' : s.cSkipTwo, 'p' : s.cInsert, 'a' : s.cAnd, 'o' : s.cOr, 'n' : s.cNot, 'c' : s.cExec, 'w' : s.cWhile, 'q' : s.cEqual, 'l' : s.cLshift, 'r' : s.cRshift },         # instruction set 3 # { 'q' : s.cQuit, 'w' : s.cRecallWhile, 'n' : s.cIsNumber, 's' : s.cIsString, 'a' : s.cBinAnd, 'o' : s.cBinOr, 'i' : s.cInteger, 'm' : s.cMod, 't' : s.cToChar, 'c' : s.cCharAt, 'r' : s.cReplaceChar, 'p' : s.cInvertedCopy, 'k' : s.cInvertedMove, 'b' : s.cSwap, 'd' : s.cSwap2, 'h' : s.cSwap3, }     ]   # step function # returns True if there are more steps, False if not. def step(self): if self.ip >= len(self.code): return False # at the end of the code curitem = self.code[self.ip] # if there's a trace variable defined and true, give a trace try: global trace if trace: print "\x1b[7m", print "trace: cur=", self.ip, str(curitem), "set=", self.activeset, "stack=", self.stack.stack, print "\x1b[0m" except: pass

if curitem.type == Parser.CONSTANT: # this is a constant value, push it        self.sf(self.stack.push, [curitem.value]) else: # it's a command if curitem.value in self.sets[0]: # command set 0 has priority above everything self.sets[0][curitem.value] else: # get it from selected set if self.activeset<0 or self.activeset>=len(self.sets): # selected set is invalid raise Interpreter.CodeError(self.err("no such set: %d", self.activeset)) else: if not curitem.value in self.sets[self.activeset]: #invalid command for selected set raise Interpreter.CodeError(self.err( "set %d has no command '%s'"%( self.activeset, \                                                  curitem.value))) else: #yay it's good, run it                 try: self.sets[self.activeset][curitem.value] except ZeroDivisionError, complaint: raise Interpreter.CodeError(self.err( "division by zero (%s)"%complaint)) self.ip += 1 return True

# make an error string with error & current state def err(self, error): return items_errstr(self.code, error, self.ip)

# call stack function and catch its errors def sf(self, func, args): rv=None try: rv = func(*args) except IndexError, complaint: raise Interpreter.CodeError(self.err("stack error (%s)"%complaint)) return rv

# raise a type error def raiseTypeErr(self, expected, got): raise Interpreter.CodeError(         self.err("wrong type: expected %s instead of %s" % \ (typedesc(expected), typedesc(got)))) ### commands ### # convert a function f(a) into push(f(pop)) + type checking def unarystackf(self, f, ntype=None, pushresult=True): def stackfunc: a=self.sf(self.stack.pop,[]) if ntype and not isinstance(a,ntype): self.raiseTypeErr(ntype, type(a))

res = f(a) if pushresult: self.sf(self.stack.push,[res]) return stackfunc

# convert a function f(a,b) into b=pop,a=pop,push(f(a,b)) + type checking def binstackf(self, f, atype=None, btype=None, pushresult=True): def stackfunc: b = self.sf(self.stack.pop,[]) a = self.sf(self.stack.pop,[]) if btype and not isinstance(b, btype): self.raiseTypeErr(btype, type(b)) if atype and not isinstance(a, atype): self.raiseTypeErr(atype, type(a)) res=f(a,b) if pushresult: self.sf(self.stack.push,[res]) return stackfunc # and now one with three arguments f(a,b,c) def tristackf(self, f, atype=None, btype=None, ctype=None, pushresult=True): x = self.sf     y = self.stack.pop def stackfunc: c = x(y,[]) b = x(y,[]) a = x(y,[]) if ctype and not isinstance(c,ctype): self.raiseTypeErr(ctype, type(c)) if btype and not isinstance(b,btype): self.raiseTypeErr(btype, type(b)) if atype and not isinstance(a,atype): self.raiseTypeErr(atype, type(a)) res = f(a,b,c) if pushresult: self.sf(self.stack.push,[res]) return stackfunc ## set 0 ## def cActivateSet(self): set = self.sf(self.stack.pop,[]) if not isinstance(set, NUMT): raise Interpreter.CodeError(                              self.err("wrong type: expected number")) if len(self.sets)<=int(set) or int(set)<0: raise Interpreter.CodeError(                              self.err("no instruction set %d exists" % set)) self.activeset = int(set) def activateSet1(self): self.activeset = 1 def activateSet2(self): self.activeset = 2 def activateSet3(self): self.activeset = 3

def cGetSet(self): self.sf(self.stack.push,[self.activeset]) def cNop(self): pass

## set 1 ## def cDuplicate(self): self.sf(self.stack.dup,[]) def cAdd(self): self.binstackf(add, NUMT, NUMT) def cSub(self): self.binstackf(sub, NUMT, NUMT) def cMul(self): self.binstackf(mul, NUMT, NUMT) def cDiv(self): self.binstackf(div, NUMT, NUMT)

# shortest representation of value in string def v2str(self, n): if n=="": return n     if isinstance(n,str) and (n[0]==' ' or n[-1]==' '): return n      try: v=str(int(n)) except: try: v=str(float(n)) except: v=str(n) return v

def cToStr(self): self.unarystackf(self.v2str, NUMT) def cToNum(self): def tonum(s): # return a number from the string if possible, # if not a valid number then push the string back try: n = NUMT(s) except ValueError: n = s        return n      self.unarystackf(tonum, STRT) def cConcatenate(self): self.binstackf(add, STRT, STRT) def cOutput(self): self.unarystackf(            (lambda s:self.world.out(self.v2str(s)+"\n")),pushresult=False) def cInlineOutput(self): self.unarystackf(            (lambda s:self.world.out(self.v2str(s))),pushresult=False) def cReadChar(self): self.sf(self.stack.push, [NUMT(self.world.readchar)]) def cReadLine(self): self.sf(self.stack.push, [self.world.readline]) def cSubstring(self): self.tristackf((lambda strn,start,end:strn[int(start):int(end)]),                            STRT,NUMT,NUMT) def cStrLen(self): self.unarystackf(len, STRT) def cDiscard(self): self.sf(self.stack.pop, []) def cCopy(self): self.unarystackf(self.stack.copy, NUMT, False) def cMove(self): self.unarystackf(self.stack.move, NUMT, False) def cStackSize(self): self.sf(self.stack.push, NUMT(len(self.stack))) ## set 2 ## def cGT(self): self.binstackf(gt) def cLT(self): self.binstackf(lt) def cSkip(self): def skip(n): self.ip += int(n) self.unarystackf(skip,NUMT,False) def cSkipTwo(self): def skip2(n): self.ip += int(n*2) self.unarystackf(skip2,NUMT,False) def cInsert(self): def insert(thing, where): self.stack.insert(where, thing) self.binstackf(insert, btype=NUMT, pushresult=False) def cAnd(self): self.binstackf(lambda a,b:NUMT(a and b and 1 or 0)) def cOr(self): self.binstackf(lambda a,b:NUMT((a or b) and 1 or 0)) def cNot(self): self.unarystackf(lambda a:NUMT(not a and 1 or 0)) def execstr(self, codestr): self.world.recurse(self, codestr)

def cExec(self): self.unarystackf(self.execstr,STRT,False) def cWhile(self): while True: # pop a value val = self.sf(self.stack.pop,[]) if val: # pop code, run it           code = self.sf(self.stack.pop,[]) if not isinstance(code, STRT): self.raiseTypeErr(STRT, type(code)) else: self.execstr(code) else: break def cEqual(self): self.binstackf(eq) def cLshift(self): self.binstackf((lambda a,b:int(a)<>int(b)),NUMT,NUMT) ## set 3 ## def cQuit(self): self.world.quit def cRecallWhile(self): # pop code code = self.sf(self.stack.pop,[]) if not isinstance(code, STRT): self.raiseTypeErr(STRT, type(code)) # while while self.sf(self.stack.pop,[]): self.execstr(code) def cIsNumber(self): self.unarystackf(lambda v: isinstance(v,NUMT) and 1 or 0) def cIsString(self): self.unarystackf(lambda v: isinstance(v,STRT) and 1 or 0) # binary and/or use integers again def binop(self,op): return lambda a,b: op(int(a), int(b)) def cBinAnd(self): self.binstackf(self.binop(and_), NUMT, NUMT) def cBinOr(self): self.binstackf(self.binop(or_), NUMT, NUMT) def cInteger(self): self.unarystackf(int, NUMT) def cMod(self): self.binstackf(mod, NUMT, NUMT) def cToChar(self): self.unarystackf((lambda k:chr(int(k)%256)), NUMT) def cCharAt(self): def charat(strn, idx): idx=int(idx) if (idx =len(strn)): # nowhere in the spec it says python-style negative indexes # are allowed... raise Interpreter.CodeError(self.err( ("index out of bounds: tried to get %d-char string's " +\                   "%d%s character") % (len(strn), idx, ordsuffix(idx)) )) return NUMT(ord(strn[idx])) self.binstackf(charat, STRT, NUMT) def cReplaceChar(self): def replacechar(strn, idx, repl): idx=int(idx) if (idx =len(strn)): raise Interpreter.CodeError(self.err( ("index out of bounds: tried to change %d-char string's "+\                   "%d%s character") % (len(strn), idx, ordsuffix(idx)) )) return strn[:idx] + repl[0] + strn[idx+1:] self.tristackf(replacechar, STRT, NUMT, STRT) def cInvertedCopy(self): self.unarystackf(self.stack.invcopy, NUMT, False) def cInvertedMove(self): self.unarystackf(self.stack.invmove, NUMT, False) def cSwap(self): self.sf(self.stack.swap, []) def cSwap2(self): self.sf(self.stack.swap2, []) def cSwap3(self): self.sf(self.stack.swap3, [])


 * 1) class World
 * 2) handles stepping of interpreter and input and output
 * 3)   World.quit -> ends program
 * 4)   World.out("blah") -> stdout.write("blah")
 * 5)   World.readchar -> read 1 char from stdin
 * 6)   World.readline -> read 1 line from stdin

class World: interpreter = None

def __init__(self, codestr, prevint=None): # parse the code self.code = Parser.parse(codestr) if prevint: self.interpreter = Interpreter( self, self.code,                                         parent=prevint,                                         stack=prevint.stack,                                         activeset=prevint.activeset ) else: self.interpreter = Interpreter( self, self.code ) # run the interpreter until it fails or quits def run(self): while self.interpreter.step: pass

# the interpreter calls these for program control def quit(self): sys.exit(0) def out(self, string): sys.stdout.write(string)

def readchar(self): v = sys.stdin.read(1) if not v: return -1 else: return ord(v)

def readline(self): return sys.stdin.readline

def recurse(self, prevint, codestr): try: # run the sub-interpreter in its own world w = World(codestr, prevint=prevint) w.run except ValueError, complaint: # parsing failed raise Interpreter.CodeError(prevint.err( "exec: parsing of string failed: \n\t%s\n" % complaint)) except Interpreter.CodeError, complaint: # run-time error in code raise Interpreter.CodeError(prevint.err( "exec: sub-interpreter runtime error: \n\t%s\n" % complaint)) except Exception, complaint: # something else went wrong raise Interpreter.CodeError(prevint.err( "exec: sub-interpreter failed: %s" % str(complaint)))



def main(argv): global trace if (not len(argv) in (2,3)) or (len(argv)==3 and \                     (not argv[1] == '-trace')): print "usage: %s [-trace] filename | -" % argv[0] sys.exit(2) else: if argv[1]=='-trace': trace=True fname = argv[2] else: trace = False fname = argv[1] try: if fname=='-': f = sys.stdin else: f = file(fname, 'r') except: print "Can't open file %s" % argv[1] sys.exit(3) code = f.read f.close try: w = World(code) w.run except Interpreter.CodeError, complaint: print "Run-time error: %s" % complaint except ValueError, complaint: print "Parse error: %s" % complaint except Exception, complaint: print "An exception occured: %s" % complaint
 * 1) start the program

if __name__=="__main__": main(sys.argv)