git @ Cat's Eye Technologies Nhohnhehr / 65c41a0
Initial import of Nhohnhehr version 1.0 revision 2011.0510 sources. catseye 9 years ago
4 changed file(s) with 505 addition(s) and 0 deletion(s). Raw diff Collapse all Expand all
0 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/2002/REC-xhtml1-20020801/DTD/xhtml1-strict.dtd">
1 <html xmlns="http://www.w3.org/1999/xhtml" lang="en">
2 <head>
3 <title>The Nhohnhehr Programming Language</title>
4 <!-- begin html doc dynamic markup -->
5 <script type="text/javascript" src="/contrib/jquery-1.6.4.min.js"></script>
6 <script type="text/javascript" src="/scripts/documentation.js"></script>
7 <!-- end html doc dynamic markup -->
8 </head>
9 <body>
10
11 <h1>The Nhohnhehr Programming Language</h1>
12
13 <p><dfn>Nhohnhehr</dfn> is a remotely fungeoid esoteric programming language
14 designed by Chris Pressey between December 4 and December 8, 2010.</p>
15
16 <h2>Overview</h2>
17
18 <p>A Nhohnhehr program consists of a single object called a <i>room</i>, which is a 2-dimensional, square grid
19 of cells of finite, and usually small, extent. To emphasize its bounds, the single room in a Nhohnhehr program text
20 must be delimited with an ASCII box, in the manner of the following:
21 </p>
22 <pre>+----+
23 | |
24 | |
25 | |
26 | |
27 +----+
28 </pre>
29 <p>Arbitrary text, including comments, may occur outside this bounding box; it will not be considered part of the Nhohnhehr program.
30 </p><p>Once defined, the contents of a room are immutable. Although only a single room may appear in a program text, new rooms
31 may be created dynamically at runtime and adjoined to the edges of existing rooms (see below for details on how this works.)
32 </p>
33
34 <h2>Execution of Instructions</h2>
35
36 <p>In a running Nhohnhehr program there is an instruction pointer. At any given time it has a definite position inside one of the
37 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
38 <i>edge mode</i>. As the instruction pointer passes over non-blank cells, it executes them, heeding the following meanings:</p>
39
40 <pre> / causes the pointer to travel north if it was traveling east, south if travelling west.
41 \ causes the pointer to travel north if it was traveling west, south if travelling east.
42 = sets wrap edge mode.
43 &amp; sets copy-room-verbatim edge mode.
44 } sets copy-room-rotate-cw-90 edge mode.
45 { sets copy-room-rotate-ccw-90 edge mode.
46 ! sets copy-room-rotate-180 edge mode.
47 # causes the instruction pointer to skip over the next cell (like # in Befunge-93.)
48 ? inputs a bit. If it is 0, rotate direction of travel 90 degrees counterclockwise;
49 if it is 1, rotate direction of travel 90 degress clockwise; if no more input is
50 available, the direction of travel does not change.
51 0 outputs a 0 bit.
52 1 outputs a 1 bit.
53 @ halts the program.
54 $ only indicates where initial instruction pointer is located; otherwise it has no effect.
55 The initial direction of travel is east.
56 blank cells are NOPs.
57 </pre>
58
59 <h2>Edge Crossing</h2>
60
61 <p>If the instruction pointer reaches an edge of the room and tries to cross it, what happens depends on the current edge mode:
62 </p>
63 <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.
64 </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:
65 <ul><li> In copy-room-verbatim edge mode, no translation is done.
66 </li><li> In copy-room-rotate-cw-90 edge mode, the copy of the current room is rotated clockwise 90 degrees before being adjoined.
67 </li><li> In copy-room-rotate-ccw-90 edge mode, the copy of the current room is rotated counterclockwise 90 degrees before being adjoined.
68 </li><li> In copy-room-rotate-180 edge mode, the copy of the current room is rotated 180 degrees before being adjoined.
69 </li></ul>
70 </li></ul>
71
72 <h2>Examples</h2>
73
74 <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).
75 </p>
76 <pre>+------+
77 | /}|
78 |&amp;#/$?@|
79 | / \&amp;|
80 | |
81 | { |
82 |\\ |
83 +------+
84 </pre>
85 <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:
86 </p>
87 <pre>+------+------+
88 | /}|\ &amp; |
89 |&amp;#/$?@|\{ # |
90 | / \&amp;| // |
91 | | $ |
92 | { | \?/|
93 |\\ | &amp;@}|
94 +------+------+
95 </pre>
96 <p>After leaving the right edge again, the current room is copied, this time rotated 90 degrees counterclockwise, and adjoined, and we get:
97 </p>
98
99 <pre>+------+------+------+
100 | /}|\ &amp; | /}|
101 |&amp;#/$?@|\{ # |&amp;#/$?@|
102 | / \&amp;| // | / \&amp;|
103 | | $ | |
104 | { | \?/| { |
105 |\\ | &amp;@}|\\ |
106 +------+------+------+
107 </pre>
108 <p>Say we were to now read in a 1 bit; we would thus have:
109 </p>
110 <pre>+------+------+------+------+
111 | /}|\ &amp; | /}| /}|
112 |&amp;#/$?@|\{ # |&amp;#/$?@|&amp;#/$?@|
113 | / \&amp;| // | / \&amp;| / \&amp;|
114 | | $ | | |
115 | { | \?/| { | { |
116 |\\ | &amp;@}|\\ |\\ |
117 +------+------+------+------+
118
119 </pre>
120 <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.
121 </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:
122 </p>
123 <ul><li> for every "1" in the input, a "1" comes out
124 </li><li> for every "0" in the input, "10" comes out
125 </li><li> there's an extra "1" at the end of the output
126 </li></ul>
127 <pre>+------------+
128 | /} |
129 |&amp;#/$? \ |
130 | / \&amp; |
131 | |
132 | |
133 | 0 |
134 | ! |
135 | |
136 | |
137 | {1 /# |
138 | { |
139 |\\@ |
140 +------------+
141 </pre>
142
143 <h2>Computational Class</h2>
144
145 <p>The last example in the previous section was written to demonstrate that Nhohnhehr is at least as powerful as a
146 push-down automaton.</p>
147
148 <p>The author suspects Nhohnhehr to be more powerful still; at least a linear bounded automaton,
149 but possibly even Turing-complete. A strategy for simulating a Turing machine could be developed from the above examples:
150 create new rooms to represent new tape cells, with each possible orientation of the room representing a different tape symbol.
151 The finite control is encoded and embedded in the possible pathways that the instruction pointer can traverse inside each room.
152 Because rooms cannot be changed once created, one might have to resort to creative measures to "change" a tape cell; for
153 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>
154
155 <h2>Source</h2>
156
157 <p>This document was adapted from
158 <a class="external" href="http://www.esolangs.org/wiki/Nhohnhehr">the esolangs.org wiki page for Nhohnhehr</a>,
159 which, like all esowiki articles, has been placed under public domain dedication.</p>
160
161 </body>
162 </html>
0 +------+
1 | /}|
2 |&#/$?@|
3 | / \&|
4 | |
5 | { |
6 |\\ |
7 +------+
0 +------------+
1 | /} |
2 |&#/$? \ |
3 | / \& |
4 | |
5 | |
6 | 0 |
7 | ! |
8 | |
9 | |
10 | {1 /# |
11 | { |
12 |\\@ |
13 +------------+
0 #!/usr/bin/env python
1
2 ### Nhohnhehr interpreter ###
3
4 # Written by Marinus. The contents of this file are in the public domain.
5 # Adapted from this esowiki page on May 10 2011:
6 # http://www.esolangs.org/wiki/User:Marinus/Nhohnhehr_interpreter
7
8 # usage: nhohnhehr.py bits filename (binary I/O)
9 # nhohnhehr.py [bytes] filename (ASCII I/O)
10
11 import sys
12
13 def addvec( (x1,y1), (x2,y2) ): return (x1+x2, y1+y2)
14 def mulvec( (x,y), m ): return (x*m, y*m)
15
16
17 # room
18 class Room:
19 data = None
20 size = 0
21
22 def __getitem__(self, (x, y)):
23 if x>=0 and y>=0 and x<self.size and y<self.size:
24 return self.data[y][x]
25 else:
26 raise IndexError("value out of range")
27
28 def __str__(self):
29 return "\n".join(''.join(str(item) for item in line) for line in self.data)
30
31 # transformations
32 NONE, CW, CCW, ROT = range(4)
33
34 def transform(self, transformation):
35 if transformation==Room.NONE: return
36 elif transformation==Room.ROT:
37 # rotate 180 degrees: flip lines, and reverse each line
38 self.data.reverse()
39 for line in self.data: line.reverse()
40 elif transformation==Room.CCW:
41 # clockwise 90 degrees
42 data = self.data
43 self.data = [[0]*self.size for x in range(self.size)]
44 for y in range(self.size):
45 for x in range(self.size):
46 self.data[self.size-x-1][y] = data[y][x]
47 elif transformation==Room.CW:
48 # counterclockwise 90 degrees
49 data = self.data
50 self.data = [[0]*self.size for x in range(self.size)]
51 for y in range(self.size):
52 for x in range(self.size):
53 self.data[y][x] = data[self.size-x-1][y]
54 else:
55 raise ValueError("invalid transformation (%d)"%transformation)
56
57 # init room from file or from room+transformation
58 def __init__(self, file=None, room=None, transform=None):
59 if (file and room) or (not (file or room)):
60 raise TypeError("Room needs to be initialized with either a file or a room.")
61
62 # init from file
63 if file:
64 self.data = []
65
66 # read file
67 lines = file.readlines()
68
69 # find possible top-left coordinates for the box
70 possibleStartCoords = []
71 for y in range(len(lines)):
72 for x in range(len(lines[y])):
73 if lines[y][x] == '+':
74 try:
75 if lines[y][x+1] == '-' and lines[y+1][x]=='|':
76 possibleStartCoords.append((x, y))
77 except IndexError:
78 # we hit a boundary looking for | or -, so this
79 # isn't a valid one.
80 pass
81
82 # check if a box can be found
83 startCoords = None
84 roomSize = 0
85 for (x, y) in possibleStartCoords:
86
87 line = lines[y]
88 # find next '+'
89 x2 = x+1
90 while x2<len(line) and line[x2]!='+': x2+=1
91
92 if x2==len(line):
93 # no '+' here.
94 continue
95
96 # found
97 size = x2 - x
98 ok = False
99 # see if it's square
100 try:
101 # check horizontal lines
102 if lines[y+size][x:x+size+1] == '+'+'-'*(size-1)+'+':
103 ok = True
104 # check vertical lines
105 for y2 in range(y+1, y+size):
106 ok = ok and (lines[y2][x] + lines[y2][x+size] == '||')
107 if not ok: break
108
109 except IndexError:
110 # we went outside of the file, so this one isn't valid
111 ok = False
112
113 if not ok:
114 # try next pair
115 continue
116 else:
117 # found one!
118 if startCoords:
119 # but we already had one...
120 raise ValueError("Multiple valid rooms in one file, first room" +
121 " found at: (%d,%d); second one at: (%d,%d)." \
122 % (startCoords[0], startCoords[1], x, y))
123 else:
124 # + 1 because that's the start of the data, we don't need the boundary
125 startCoords = (x + 1, y + 1)
126 roomSize = size - 1
127 # and we have to continue looking in case we find another one,
128 # in which case the file is invalid.
129
130 # no room in the file
131 if not startCoords:
132 raise ValueError("Cannot find a valid room in this file.")
133
134 # we have a room, load it
135 x, y = startCoords
136 for lineno in range(roomSize):
137 self.data.append([m for m in lines[lineno+y][x:roomSize+x]])
138
139 self.size = roomSize
140
141 # init from other room
142 elif room:
143 # this one's easier
144 self.size = room.size
145 self.data = [line[:] for line in room.data]
146
147 # transformation needed?
148 if transform:
149 self.transform(transform)
150
151
152
153 class Environment:
154 rooms = {}
155 ip = ()
156 direction = ()
157 edgemode = None
158 roomsize = 0
159 halt = False
160
161 # states
162 WRAP,COPY,CW,CCW,ROT = range(5)
163
164 # directions
165 LEFT,RIGHT,UP,DOWN = (-1,0), (1,0), (0,-1), (0,1)
166
167 def __getitem__(self, (x, y)):
168 # get whatever's in that room at that space
169 room = self.rooms[self.roomCoords((x, y))]
170 roomX = x%self.roomsize
171 roomY = y%self.roomsize
172 return room[roomX, roomY]
173
174 def __init__(self, room, (infunc, outfunc)):
175 self.rooms = { (0,0): room }
176 self.roomsize = room.size
177 self.dir = Environment.RIGHT
178 self.edgemode = Environment.WRAP
179 self.infunc, self.outfunc = infunc, outfunc
180 self.halt = False
181 # find initial instruction pointer
182
183 self.ip = (-1, -1)
184 for x in range(self.roomsize):
185 for y in range(self.roomsize):
186 if room[x, y] == '$':
187 self.ip = (x, y)
188 break
189
190 if self.ip == (-1, -1):
191 raise ValueError("no $ in room")
192
193 def roomCoords(self, (x,y)):
194 return (int(x/self.roomsize), int(y/self.roomsize))
195
196 def advanceIP(self):
197 newIP = addvec(self.ip, self.dir)
198
199 if self.roomCoords(self.ip) != self.roomCoords(newIP):
200 if self.edgemode == Environment.WRAP:
201 # wrap to edge of last room
202 newIP = addvec(newIP, mulvec(self.dir, -self.roomsize))
203 else:
204 # make a new room if none exists yet
205 if not self.roomCoords(newIP) in self.rooms:
206 # transformations
207 transform = { Environment.COPY: Room.NONE,
208 Environment.CW: Room.CW,
209 Environment.CCW: Room.CCW,
210 Environment.ROT: Room.ROT } [self.edgemode]
211
212 self.rooms.update( { self.roomCoords(newIP):
213 Room(room=self.rooms[self.roomCoords(self.ip)],
214 transform=transform) } )
215 self.ip = newIP
216
217 def step(self):
218 command = self[self.ip]
219 ccwrot = {self.LEFT: self.DOWN, self.RIGHT: self.UP,
220 self.UP: self.RIGHT, self.DOWN: self.LEFT}
221 cwrot = {self.LEFT: self.UP, self.RIGHT: self.DOWN,
222 self.UP: self.LEFT, self.DOWN: self.RIGHT}
223
224 if command == '/':
225 self.dir = ccwrot[self.dir]
226 elif command == '\\':
227 self.dir = cwrot[self.dir]
228 elif command in '=&{}!':
229 self.edgemode = { '=': self.WRAP,
230 '&': self.COPY,
231 '{': self.CCW,
232 '}': self.CW,
233 '!': self.ROT } [command]
234 elif command == '#':
235 self.advanceIP()
236 elif command == '?':
237 try:
238 self.dir = ( self.infunc() and cwrot or ccwrot ) [self.dir]
239 except IOError:
240 # no more input available = do nothing
241 pass
242 elif command in '01':
243 self.outfunc(int(command))
244 elif command == '@':
245 self.halt = True
246
247 self.advanceIP()
248
249 def run(self):
250 while not self.halt: self.step()
251
252
253 def main(argv):
254 if not len(argv) in (2,3) or (len(argv)==3 and not argv[1] in ('bits','bytes')):
255 print "Usage: [python] %s [bits|bytes] filename" % argv[0]
256 print " bits/bytes: specify i/o mode"
257 print ""
258 print " In bits mode, i/o uses the characters '0' and '1'"
259 print " (and when reading input, everything that's not '0'"
260 print " or 1 is ignored)."
261 print " In bytes mode, i/o is done 8 bits at a time as ASCII."
262 print ""
263 print " If no mode is given, bytes mode is used."
264 print ""
265
266 sys.exit()
267
268 if len(argv)==2:
269 mode = 'bytes'
270 fname = argv[1]
271 else:
272 mode = argv[1]
273 fname = argv[2]
274
275 # i/o functions
276 def bits_in():
277 i = None
278 while not i in ('0','1'):
279 i = sys.stdin.read(1)
280 if i == '':
281 raise IOError() # eof
282 return int(i)
283
284 def bits_out(bit):
285 sys.stdout.write(('0', '1')[bit])
286 sys.stdout.flush()
287
288 def bytes_in(bits=[[]]):
289 # get data if necessary
290 if bits[0]==[]:
291 i = sys.stdin.read(1)
292 if (i==''): raise IOError() # eof
293 else:
294 bits[0] = [ int(bool(ord(i) & (1 << b))) for b in range(7,-1,-1) ]
295
296 # return data
297 bit = bits[0][0]
298 bits[0] = bits[0][1:]
299 return bit
300
301 def bytes_out(bit, bits=[[]]):
302 bits[0].append(bit)
303
304 # if we have 8 bits, output
305 if len(bits[0]) == 8:
306 sys.stdout.write(chr(sum(bits[0][7-b]<<b for b in range(7,-1,-1))))
307 sys.stdout.flush()
308 bits[0] = []
309
310 modes = { 'bits': (bits_in, bits_out),
311 'bytes': (bytes_in, bytes_out) }
312 try:
313 Environment( Room(file=file(fname)), modes[mode] ).run()
314 if mode=='bits': print # newline
315
316 except Exception, e:
317 print "Error: ", e
318
319 if __name__=='__main__': main(sys.argv)