User:Marinus/Nhohnhehr interpreter
Jump to navigation
Jump to search
This is a Nhohnhehr interpreter.
There's not much to test it with but I think it works.
#!/usr/bin/env python ### Nhohnhehr interpreter ### # usage: nhohnhehr.py bits filename (binary I/O) # nhohnhehr.py [bytes] filename (ASCII I/O) import sys def addvec( (x1,y1), (x2,y2) ): return (x1+x2, y1+y2) def mulvec( (x,y), m ): return (x*m, y*m) # room class Room: data = None size = 0 def __getitem__(self, (x, y)): if x>=0 and y>=0 and x<self.size and y<self.size: return self.data[y][x] else: raise IndexError("value out of range") def __str__(self): return "\n".join(''.join(str(item) for item in line) for line in self.data) # transformations NONE, CW, CCW, ROT = range(4) def transform(self, transformation): if transformation==Room.NONE: return elif transformation==Room.ROT: # rotate 180 degrees: flip lines, and reverse each line self.data.reverse() for line in self.data: line.reverse() elif transformation==Room.CCW: # clockwise 90 degrees data = self.data self.data = [[0]*self.size for x in range(self.size)] for y in range(self.size): for x in range(self.size): self.data[self.size-x-1][y] = data[y][x] elif transformation==Room.CW: # counterclockwise 90 degrees data = self.data self.data = [[0]*self.size for x in range(self.size)] for y in range(self.size): for x in range(self.size): self.data[y][x] = data[self.size-x-1][y] else: raise ValueError("invalid transformation (%d)"%transformation) # init room from file or from room+transformation def __init__(self, file=None, room=None, transform=None): if (file and room) or (not (file or room)): raise TypeError("Room needs to be initialized with either a file or a room.") # init from file if file: self.data = [] # read file lines = file.readlines() # find possible top-left coordinates for the box possibleStartCoords = [] for y in range(len(lines)): for x in range(len(lines[y])): if lines[y][x] == '+': try: if lines[y][x+1] == '-' and lines[y+1][x]=='|': possibleStartCoords.append((x, y)) except IndexError: # we hit a boundary looking for | or -, so this # isn't a valid one. pass # check if a box can be found startCoords = None roomSize = 0 for (x, y) in possibleStartCoords: line = lines[y] # find next '+' x2 = x+1 while x2<len(line) and line[x2]!='+': x2+=1 if x2==len(line): # no '+' here. continue # found size = x2 - x ok = False # see if it's square try: # check horizontal lines if lines[y+size][x:x+size+1] == '+'+'-'*(size-1)+'+': ok = True # check vertical lines for y2 in range(y+1, y+size): ok = ok and (lines[y2][x] + lines[y2][x+size] == '||') if not ok: break except IndexError: # we went outside of the file, so this one isn't valid ok = False if not ok: # try next pair continue else: # found one! if startCoords: # but we already had one... raise ValueError("Multiple valid rooms in one file, first room" + " found at: (%d,%d); second one at: (%d,%d)." \ % (startCoords[0], startCoords[1], x, y)) else: # + 1 because that's the start of the data, we don't need the boundary startCoords = (x + 1, y + 1) roomSize = size - 1 # and we have to continue looking in case we find another one, # in which case the file is invalid. # no room in the file if not startCoords: raise ValueError("Cannot find a valid room in this file.") # we have a room, load it x, y = startCoords for lineno in range(roomSize): self.data.append([m for m in lines[lineno+y][x:roomSize+x]]) self.size = roomSize # init from other room elif room: # this one's easier self.size = room.size self.data = [line[:] for line in room.data] # transformation needed? if transform: self.transform(transform) class Environment: rooms = {} ip = () direction = () edgemode = None roomsize = 0 halt = False # states WRAP,COPY,CW,CCW,ROT = range(5) # directions LEFT,RIGHT,UP,DOWN = (-1,0), (1,0), (0,-1), (0,1) def __getitem__(self, (x, y)): # get whatever's in that room at that space room = self.rooms[self.roomCoords((x, y))] roomX = x%self.roomsize roomY = y%self.roomsize return room[roomX, roomY] def __init__(self, room, (infunc, outfunc)): self.rooms = { (0,0): room } self.roomsize = room.size self.dir = Environment.RIGHT self.edgemode = Environment.WRAP self.infunc, self.outfunc = infunc, outfunc self.halt = False # find initial instruction pointer self.ip = (-1, -1) for x in range(self.roomsize): for y in range(self.roomsize): if room[x, y] == '$': self.ip = (x, y) break if self.ip == (-1, -1): raise ValueError("no $ in room") def roomCoords(self, (x,y)): return (int(x/self.roomsize), int(y/self.roomsize)) def advanceIP(self): newIP = addvec(self.ip, self.dir) if self.roomCoords(self.ip) != self.roomCoords(newIP): if self.edgemode == Environment.WRAP: # wrap to edge of last room newIP = addvec(newIP, mulvec(self.dir, -self.roomsize)) else: # make a new room if none exists yet if not self.roomCoords(newIP) in self.rooms: # transformations transform = { Environment.COPY: Room.NONE, Environment.CW: Room.CW, Environment.CCW: Room.CCW, Environment.ROT: Room.ROT } [self.edgemode] self.rooms.update( { self.roomCoords(newIP): Room(room=self.rooms[self.roomCoords(self.ip)], transform=transform) } ) self.ip = newIP def step(self): command = self[self.ip] ccwrot = {self.LEFT: self.DOWN, self.RIGHT: self.UP, self.UP: self.RIGHT, self.DOWN: self.LEFT} cwrot = {self.LEFT: self.UP, self.RIGHT: self.DOWN, self.UP: self.LEFT, self.DOWN: self.RIGHT} if command == '/': self.dir = ccwrot[self.dir] elif command == '\\': self.dir = cwrot[self.dir] elif command in '=&{}!': self.edgemode = { '=': self.WRAP, '&': self.COPY, '{': self.CCW, '}': self.CW, '!': self.ROT } [command] elif command == '#': self.advanceIP() elif command == '?': try: self.dir = ( self.infunc() and cwrot or ccwrot ) [self.dir] except IOError: # no more input available = do nothing pass elif command in '01': self.outfunc(int(command)) elif command == '@': self.halt = True self.advanceIP() def run(self): while not self.halt: self.step() def main(argv): if not len(argv) in (2,3) or (len(argv)==3 and not argv[1] in ('bits','bytes')): print "Usage: [python] %s [bits|bytes] filename" % argv[0] print " bits/bytes: specify i/o mode" print "" print " In bits mode, i/o uses the characters '0' and '1'" print " (and when reading input, everything that's not '0'" print " or 1 is ignored)." print " In bytes mode, i/o is done 8 bits at a time as ASCII." print "" print " If no mode is given, bytes mode is used." print "" sys.exit() if len(argv)==2: mode = 'bytes' fname = argv[1] else: mode = argv[1] fname = argv[2] # i/o functions def bits_in(): i = None while not i in ('0','1'): i = sys.stdin.read(1) if i == '': raise IOError() # eof return int(i) def bits_out(bit): sys.stdout.write(('0', '1')[bit]) sys.stdout.flush() def bytes_in(bits=[[]]): # get data if necessary if bits[0]==[]: i = sys.stdin.read(1) if (i==''): raise IOError() # eof else: bits[0] = [ int(bool(ord(i) & (1 << b))) for b in range(7,-1,-1) ] # return data bit = bits[0][0] bits[0] = bits[0][1:] return bit def bytes_out(bit, bits=[[]]): bits[0].append(bit) # if we have 8 bits, output if len(bits[0]) == 8: sys.stdout.write(chr(sum(bits[0][7-b]<<b for b in range(7,-1,-1)))) sys.stdout.flush() bits[0] = [] modes = { 'bits': (bits_in, bits_out), 'bytes': (bytes_in, bytes_out) } try: Environment( Room(file=file(fname)), modes[mode] ).run() if mode=='bits': print # newline except Exception, e: print "Error: ", e if __name__=='__main__': main(sys.argv)