Commits

Anonymous committed c70c204

Initial import of Nhohnhehr version 1.0 revision 2011.0510 sources.

Comments (0)

Files changed (4)

doc/nhohnhehr.html

+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/2002/REC-xhtml1-20020801/DTD/xhtml1-strict.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml" lang="en">
+<head>
+<title>The Nhohnhehr Programming Language</title>
+  <!-- begin html doc dynamic markup -->
+  <script type="text/javascript" src="/contrib/jquery-1.6.4.min.js"></script>
+  <script type="text/javascript" src="/scripts/documentation.js"></script>
+  <!-- end html doc dynamic markup -->
+</head>
+<body>
+
+<h1>The Nhohnhehr Programming Language</h1>
+
+<p><dfn>Nhohnhehr</dfn> is a remotely fungeoid esoteric programming language
+designed by Chris Pressey between December 4 and December 8, 2010.</p>
+
+<h2>Overview</h2>
+
+<p>A Nhohnhehr program consists of a single object called a <i>room</i>, which is a 2-dimensional, square grid
+of cells of finite, and usually small, extent.  To emphasize its bounds, the single room in a Nhohnhehr program text
+must be delimited with an ASCII box, in the manner of the following:
+</p>
+<pre>+----+
+|    |
+|    |
+|    |
+|    |
++----+
+</pre>
+<p>Arbitrary text, including comments, may occur outside this bounding box; it will not be considered part of the Nhohnhehr program.
+</p><p>Once defined, the contents of a room are immutable.  Although only a single room may appear in a program text, new rooms
+may be created dynamically at runtime and adjoined to the edges of existing rooms (see below for details on how this works.)
+</p>
+
+<h2>Execution of Instructions</h2>
+
+<p>In a running Nhohnhehr program there is an instruction pointer.  At any given time it has a definite position inside one of the
+rooms of the program, and is traveling in one of the four cardinal directions.  It is also associated with a five-state variable called the
+<i>edge mode</i>.  As the instruction pointer passes over non-blank cells, it executes them, heeding the following meanings:</p>
+
+<pre> / causes the pointer to travel north if it was traveling east, south if travelling west.
+ \ causes the pointer to travel north if it was traveling west, south if travelling east.
+ = sets wrap edge mode.
+ &amp; sets copy-room-verbatim edge mode.
+ } sets copy-room-rotate-cw-90 edge mode.
+ { sets copy-room-rotate-ccw-90 edge mode.
+ ! sets copy-room-rotate-180 edge mode.
+ # causes the instruction pointer to skip over the next cell (like # in Befunge-93.)
+ ? inputs a bit.  If it is 0, rotate direction of travel 90 degrees counterclockwise;
+   if it is 1, rotate direction of travel 90 degress clockwise; if no more input is
+   available, the direction of travel does not change.
+ 0 outputs a 0 bit.
+ 1 outputs a 1 bit.
+ @ halts the program.
+ $ only indicates where initial instruction pointer is located; otherwise it has no effect.
+   The initial direction of travel is east.
+ blank cells are NOPs.
+</pre>
+
+<h2>Edge Crossing</h2>
+
+<p>If the instruction pointer reaches an edge of the room and tries to cross it, what happens depends on the current edge mode:
+</p>
+<ul><li> In wrap edge mode (this is the initial edge mode), the pointer wraps to the corresponding other edge of the room, as if the room were mapped onto a torus.
+</li><li> In all other modes, if there already exists a room adjoining the current room on that edge, the instruction pointer leaves the current room and enters the adjoining room in the corresponding position.  However, if no such adjoining room exists yet, one will be created by making a copy of the current room, transforming it somehow, and adjoining it.  The instruction pointer then enters the new room, just as if it had already existed.  The details of the transformation depend on the edge mode:
+<ul><li> In copy-room-verbatim edge mode, no translation is done.
+</li><li> In copy-room-rotate-cw-90 edge mode, the copy of the current room is rotated clockwise 90 degrees before being adjoined.
+</li><li> In copy-room-rotate-ccw-90 edge mode, the copy of the current room is rotated counterclockwise 90 degrees before being adjoined.
+</li><li> In copy-room-rotate-180 edge mode, the copy of the current room is rotated 180 degrees before being adjoined.
+</li></ul>
+</li></ul>
+
+<h2>Examples</h2>
+
+<p>The following example reads in a sequence of bits and creates a series of rooms, where 1 bits correspond to unrotated rooms and 0 bits correspond to rooms rotated 90 degrees clockwise (though not precisely one-to-one).
+</p>
+<pre>+------+
+|    /}|
+|&amp;#/$?@|
+|  / \&amp;|
+|      |
+| {    |
+|\\    |
++------+
+</pre>
+<p>After reading a 0 bit and leaving the right edge, the room is copied, rotated 90 degrees clockwise, and adjoined, so that the rooms of the program are:
+</p>
+<pre>+------+------+
+|    /}|\   &amp; |
+|&amp;#/$?@|\{  # |
+|  / \&amp;|   // |
+|      |    $ |
+| {    |   \?/|
+|\\    |   &amp;@}|
++------+------+
+</pre>
+<p>After leaving the right edge again, the current room is copied, this time rotated 90 degrees counterclockwise, and adjoined, and we get:
+</p>
+
+<pre>+------+------+------+
+|    /}|\   &amp; |    /}|
+|&amp;#/$?@|\{  # |&amp;#/$?@|
+|  / \&amp;|   // |  / \&amp;|
+|      |    $ |      |
+| {    |   \?/| {    |
+|\\    |   &amp;@}|\\    |
++------+------+------+
+</pre>
+<p>Say we were to now read in a 1 bit; we would thus have:
+</p>
+<pre>+------+------+------+------+
+|    /}|\   &amp; |    /}|    /}|
+|&amp;#/$?@|\{  # |&amp;#/$?@|&amp;#/$?@|
+|  / \&amp;|   // |  / \&amp;|  / \&amp;|
+|      |    $ |      |      |
+| {    |   \?/| {    | {    |
+|\\    |   &amp;@}|\\    |\\    |
++------+------+------+------+
+
+</pre>
+<p>It should be fairly clear at this point that this program will read all input bits, creating rooms thusly, terminating when there are no more input bits.
+</p><p>The following program is a variation of the above which, when it encounters the end of input, writes out the bits in the reverse order they were read in, with the following changes:
+</p>
+<ul><li> for every "1" in the input, a "1" comes out
+</li><li> for every "0" in the input, "10" comes out
+</li><li> there's an extra "1" at the end of the output
+</li></ul>
+<pre>+------------+
+|    /}      |
+|&amp;#/$?   \   |
+|  / \&amp;      |
+|            |
+|            |
+|         0  |
+|         !  |
+|            |
+|            |
+|    {1  /#  |
+| {          |
+|\\@         |
++------------+
+</pre>
+
+<h2>Computational Class</h2>
+
+<p>The last example in the previous section was written to demonstrate that Nhohnhehr is at least as powerful as a
+push-down automaton.</p>
+
+<p>The author suspects Nhohnhehr to be more powerful still; at least a linear bounded automaton,
+but possibly even Turing-complete.  A strategy for simulating a Turing machine could be developed from the above examples:
+create new rooms to represent new tape cells, with each possible orientation of the room representing a different tape symbol.
+The finite control is encoded and embedded in the possible pathways that the instruction pointer can traverse inside each room.
+Because rooms cannot be changed once created, one might have to resort to creative measures to "change" a tape cell; for
+instance, each tape cell might have a "stack" of rooms, with a new room appended to the stack each time the cell is to be "changed".</p>
+
+<h2>Source</h2>
+
+<p>This document was adapted from
+<a class="external" href="http://www.esolangs.org/wiki/Nhohnhehr">the esolangs.org wiki page for Nhohnhehr</a>,
+which, like all esowiki articles, has been placed under public domain dedication.</p>
+
+</body>
+</html>

eg/read-and-store.nho

++------+
+|    /}|
+|&#/$?@|
+|  / \&|
+|      |
+| {    |
+|\\    |
++------+

eg/reverse-esque.nho

++------------+
+|    /}      |
+|&#/$?   \   |
+|  / \&      |
+|            |
+|            |
+|         0  |
+|         !  |
+|            |
+|            |
+|    {1  /#  |
+| {          |
+|\\@         |
++------------+
+#!/usr/bin/env python
+
+### Nhohnhehr interpreter ###
+
+# Written by Marinus.  The contents of this file are in the public domain.
+# Adapted from this esowiki page on May 10 2011:
+#   http://www.esolangs.org/wiki/User:Marinus/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)