User:Marinus/Nhohnhehr interpreter

This is a Nhohnhehr interpreter.

There's not much to test it with but I think it works.


 * 1) !/usr/bin/env python


 * 1) Nhohnhehr interpreter ###


 * 1) usage: nhohnhehr.py bits filename    (binary I/O)
 * 2)        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)

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
 * 1) room

# 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)