Merge pull request #2 from catseye/support-python-3
Develop 1.0 revision 2021.0624
Chris Pressey authored 2 years ago
GitHub committed 2 years ago
0 | c70c2046cb929b3775c0457dfb8c3109631390ff rel_1_0_2011_0510 | |
1 | 49142375b8a2de1b65fbc3094b87009a4c46c45f rel_1_0_2014_0819 |
0 | The Nhohnhehr Programming Language | |
1 | ================================== | |
2 | ||
3 | Nhohnhehr is a remotely fungeoid esoteric programming language designed | |
4 | by Chris Pressey between December 4 and December 8, 2010. | |
5 | ||
6 | Overview | |
7 | -------- | |
8 | ||
9 | A Nhohnhehr program consists of a single object called a *room*, which | |
10 | is a 2-dimensional, square grid of cells of finite, and usually small, | |
11 | extent. To emphasize its bounds, the single room in a Nhohnhehr program | |
12 | text must be delimited with an ASCII box, in the manner of the | |
13 | following: | |
14 | ||
15 | +----+ | |
16 | | | | |
17 | | | | |
18 | | | | |
19 | | | | |
20 | +----+ | |
21 | ||
22 | Arbitrary text, including comments, may occur outside this bounding box; | |
23 | it will not be considered part of the Nhohnhehr program. | |
24 | ||
25 | Once defined, the contents of a room are immutable. Although only a | |
26 | single room may appear in a program text, new rooms may be created | |
27 | dynamically at runtime and adjoined to the edges of existing rooms (see | |
28 | below for details on how this works.) | |
29 | ||
30 | Execution of Instructions | |
31 | ------------------------- | |
32 | ||
33 | In a running Nhohnhehr program there is an instruction pointer. At any | |
34 | given time it has a definite position inside one of the rooms of the | |
35 | program, and is traveling in one of the four cardinal directions. It is | |
36 | also associated with a five-state variable called the *edge mode*. As | |
37 | the instruction pointer passes over non-blank cells, it executes them, | |
38 | heeding the following meanings: | |
39 | ||
40 | / causes the pointer to travel north if it was traveling east, | |
41 | south if travelling west. | |
42 | \ causes the pointer to travel north if it was traveling west, | |
43 | south if travelling east. | |
44 | = sets wrap edge mode. | |
45 | & sets copy-room-verbatim edge mode. | |
46 | } sets copy-room-rotate-cw-90 edge mode. | |
47 | { sets copy-room-rotate-ccw-90 edge mode. | |
48 | ! sets copy-room-rotate-180 edge mode. | |
49 | # causes the instruction pointer to skip over the next cell | |
50 | (like # in Befunge-93.) | |
51 | ? inputs a bit. If it is 0, rotate direction of travel 90 degrees | |
52 | counterclockwise; if it is 1, rotate direction of travel 90 degress | |
53 | clockwise; if no more input is available, the direction of travel | |
54 | does not change. | |
55 | 0 outputs a 0 bit. | |
56 | 1 outputs a 1 bit. | |
57 | @ halts the program. | |
58 | $ only indicates where initial instruction pointer is located; | |
59 | otherwise it has no effect. The initial direction of travel is east. | |
60 | ||
61 | Blank cells are NOPs. | |
62 | ||
63 | Edge Crossing | |
64 | ------------- | |
65 | ||
66 | If the instruction pointer reaches an edge of the room and tries to | |
67 | cross it, what happens depends on the current edge mode: | |
68 | ||
69 | - In wrap edge mode (this is the initial edge mode), the pointer wraps | |
70 | to the corresponding other edge of the room, as if the room were | |
71 | mapped onto a torus. | |
72 | - In all other modes, if there already exists a room adjoining the | |
73 | current room on that edge, the instruction pointer leaves the | |
74 | current room and enters the adjoining room in the corresponding | |
75 | position. However, if no such adjoining room exists yet, one will be | |
76 | created by making a copy of the current room, transforming it | |
77 | somehow, and adjoining it. The instruction pointer then enters the | |
78 | new room, just as if it had already existed. The details of the | |
79 | transformation depend on the edge mode: | |
80 | - In copy-room-verbatim edge mode, no translation is done. | |
81 | - In copy-room-rotate-cw-90 edge mode, the copy of the current | |
82 | room is rotated clockwise 90 degrees before being adjoined. | |
83 | - In copy-room-rotate-ccw-90 edge mode, the copy of the current | |
84 | room is rotated counterclockwise 90 degrees before being | |
85 | adjoined. | |
86 | - In copy-room-rotate-180 edge mode, the copy of the current room | |
87 | is rotated 180 degrees before being adjoined. | |
88 | ||
89 | Examples | |
90 | -------- | |
91 | ||
92 | The following example reads in a sequence of bits and creates a series | |
93 | of rooms, where 1 bits correspond to unrotated rooms and 0 bits | |
94 | correspond to rooms rotated 90 degrees clockwise (though not precisely | |
95 | one-to-one). | |
96 | ||
97 | +------+ | |
98 | | /}| | |
99 | |&#/$?@| | |
100 | | / \&| | |
101 | | | | |
102 | | { | | |
103 | |\\ | | |
104 | +------+ | |
105 | ||
106 | After reading a 0 bit and leaving the right edge, the room is copied, | |
107 | rotated 90 degrees clockwise, and adjoined, so that the rooms of the | |
108 | program are: | |
109 | ||
110 | +------+------+ | |
111 | | /}|\ & | | |
112 | |&#/$?@|\{ # | | |
113 | | / \&| // | | |
114 | | | $ | | |
115 | | { | \?/| | |
116 | |\\ | &@}| | |
117 | +------+------+ | |
118 | ||
119 | After leaving the right edge again, the current room is copied, this | |
120 | time rotated 90 degrees counterclockwise, and adjoined, and we get: | |
121 | ||
122 | +------+------+------+ | |
123 | | /}|\ & | /}| | |
124 | |&#/$?@|\{ # |&#/$?@| | |
125 | | / \&| // | / \&| | |
126 | | | $ | | | |
127 | | { | \?/| { | | |
128 | |\\ | &@}|\\ | | |
129 | +------+------+------+ | |
130 | ||
131 | Say we were to now read in a 1 bit; we would thus have: | |
132 | ||
133 | +------+------+------+------+ | |
134 | | /}|\ & | /}| /}| | |
135 | |&#/$?@|\{ # |&#/$?@|&#/$?@| | |
136 | | / \&| // | / \&| / \&| | |
137 | | | $ | | | | |
138 | | { | \?/| { | { | | |
139 | |\\ | &@}|\\ |\\ | | |
140 | +------+------+------+------+ | |
141 | ||
142 | It should be fairly clear at this point that this program will read all | |
143 | input bits, creating rooms thusly, terminating when there are no more | |
144 | input bits. | |
145 | ||
146 | We can write a program that is a variation of the above which, when it | |
147 | encounters the end of input, writes out the bits in the reverse order | |
148 | they were read in, with the following changes: | |
149 | ||
150 | * for every `1` in the input, a `1` comes out | |
151 | * for every `0` in the input, `10` comes out | |
152 | * there's an extra `1` at the end of the output | |
153 | ||
154 | Here is the program: | |
155 | ||
156 | +------------+ | |
157 | | /} | | |
158 | |&#/$? \ | | |
159 | | / \& | | |
160 | | | | |
161 | | | | |
162 | | 0 | | |
163 | | ! | | |
164 | | | | |
165 | | | | |
166 | | {1 /# | | |
167 | | { | | |
168 | |\\@ | | |
169 | +------------+ | |
170 | ||
171 | Computational Class | |
172 | ------------------- | |
173 | ||
174 | The last example in the previous section was written to demonstrate that | |
175 | Nhohnhehr is at least as powerful as a push-down automaton. | |
176 | ||
177 | The author suspects Nhohnhehr to be more powerful still; at least a | |
178 | linear bounded automaton, but possibly even Turing-complete. A strategy | |
179 | for simulating a Turing machine could be developed from the above | |
180 | examples: create new rooms to represent new tape cells, with each | |
181 | possible orientation of the room representing a different tape symbol. | |
182 | The finite control is encoded and embedded in the possible pathways that | |
183 | the instruction pointer can traverse inside each room. Because rooms | |
184 | cannot be changed once created, one might have to resort to creative | |
185 | measures to "change" a tape cell; for instance, each tape cell might | |
186 | have a "stack" of rooms, with a new room appended to the stack each time | |
187 | the cell is to be "changed". | |
188 | ||
189 | Source | |
190 | ------ | |
191 | ||
192 | This document was adapted from [the esolangs.org wiki page for | |
193 | Nhohnhehr](http://www.esolangs.org/wiki/Nhohnhehr), which, like all | |
194 | esowiki articles, has been placed under public domain dedication. | |
195 | ||
196 | Implementation | |
197 | -------------- | |
198 | ||
199 | The Nhohnhehr distribution contains a Nhohnhehr interpreter, written | |
200 | in Python, based on [this implementation of | |
201 | Nhohnhehr](http://esolangs.org/wiki/User:Marinus/Nhohnhehr_interpreter) | |
202 | by [Marinus](http://www.esolangs.org/wiki/User:Marinus). It | |
203 | is effectively the reference interpreter, since it seems to correctly | |
204 | implement the language described here, and there are, to the best of | |
205 | my knowledge, no other implementations of Nhohnhehr in existence. | |
206 | Like all content from the esowiki, it too is in the public domain. |
0 | The Nhohnhehr Programming Language | |
1 | ================================== | |
2 | ||
3 | Version 1.0 | |
4 | | _Wiki entry_ [@ esolangs.org](https://esolangs.org/wiki/Nhohnhehr) | |
5 | | _See also:_ [Flobnar](https://github.com/catseye/Flobnar#readme) | |
6 | ∘ [Wunnel](https://github.com/catseye/Wunnel#readme) | |
7 | ∘ [Gemooy](https://github.com/catseye/Gemooy#readme) | |
8 | ∘ [Jolverine](https://github.com/catseye/Jolverine#readme) | |
9 | ||
10 | - - - - | |
11 | ||
12 | Nhohnhehr is a remotely fungeoid esoteric programming language designed | |
13 | by Chris Pressey between December 4 and December 8, 2010. | |
14 | ||
15 | Overview | |
16 | -------- | |
17 | ||
18 | A Nhohnhehr program consists of a single object called a *room*, which | |
19 | is a 2-dimensional, square grid of cells of finite, and usually small, | |
20 | extent. To emphasize its bounds, the single room in a Nhohnhehr program | |
21 | text must be delimited with an ASCII box, in the manner of the | |
22 | following: | |
23 | ||
24 | +----+ | |
25 | | | | |
26 | | | | |
27 | | | | |
28 | | | | |
29 | +----+ | |
30 | ||
31 | Arbitrary text, including comments, may occur outside this bounding box; | |
32 | it will not be considered part of the Nhohnhehr program. | |
33 | ||
34 | Once defined, the contents of a room are immutable. Although only a | |
35 | single room may appear in a program text, new rooms may be created | |
36 | dynamically at runtime and adjoined to the edges of existing rooms (see | |
37 | below for details on how this works.) | |
38 | ||
39 | Execution of Instructions | |
40 | ------------------------- | |
41 | ||
42 | In a running Nhohnhehr program there is an instruction pointer. At any | |
43 | given time it has a definite position inside one of the rooms of the | |
44 | program, and is traveling in one of the four cardinal directions. It is | |
45 | also associated with a five-state variable called the *edge mode*. As | |
46 | the instruction pointer passes over non-blank cells, it executes them, | |
47 | heeding the following meanings: | |
48 | ||
49 | / causes the pointer to travel north if it was traveling east, | |
50 | south if travelling west. | |
51 | \ causes the pointer to travel north if it was traveling west, | |
52 | south if travelling east. | |
53 | = sets wrap edge mode. | |
54 | & sets copy-room-verbatim edge mode. | |
55 | } sets copy-room-rotate-cw-90 edge mode. | |
56 | { sets copy-room-rotate-ccw-90 edge mode. | |
57 | ! sets copy-room-rotate-180 edge mode. | |
58 | # causes the instruction pointer to skip over the next cell | |
59 | (like # in Befunge-93.) | |
60 | ? inputs a bit. If it is 0, rotate direction of travel 90 degrees | |
61 | counterclockwise; if it is 1, rotate direction of travel 90 degress | |
62 | clockwise; if no more input is available, the direction of travel | |
63 | does not change. | |
64 | 0 outputs a 0 bit. | |
65 | 1 outputs a 1 bit. | |
66 | @ halts the program. | |
67 | $ only indicates where initial instruction pointer is located; | |
68 | otherwise it has no effect. The initial direction of travel is east. | |
69 | ||
70 | Blank cells are NOPs. | |
71 | ||
72 | Edge Crossing | |
73 | ------------- | |
74 | ||
75 | If the instruction pointer reaches an edge of the room and tries to | |
76 | cross it, what happens depends on the current edge mode: | |
77 | ||
78 | - In wrap edge mode (this is the initial edge mode), the pointer wraps | |
79 | to the corresponding other edge of the room, as if the room were | |
80 | mapped onto a torus. | |
81 | - In all other modes, if there already exists a room adjoining the | |
82 | current room on that edge, the instruction pointer leaves the | |
83 | current room and enters the adjoining room in the corresponding | |
84 | position. However, if no such adjoining room exists yet, one will be | |
85 | created by making a copy of the current room, transforming it | |
86 | somehow, and adjoining it. The instruction pointer then enters the | |
87 | new room, just as if it had already existed. The details of the | |
88 | transformation depend on the edge mode: | |
89 | - In copy-room-verbatim edge mode, no translation is done. | |
90 | - In copy-room-rotate-cw-90 edge mode, the copy of the current | |
91 | room is rotated clockwise 90 degrees before being adjoined. | |
92 | - In copy-room-rotate-ccw-90 edge mode, the copy of the current | |
93 | room is rotated counterclockwise 90 degrees before being | |
94 | adjoined. | |
95 | - In copy-room-rotate-180 edge mode, the copy of the current room | |
96 | is rotated 180 degrees before being adjoined. | |
97 | ||
98 | Examples | |
99 | -------- | |
100 | ||
101 | The following example reads in a sequence of bits and creates a series | |
102 | of rooms, where 1 bits correspond to unrotated rooms and 0 bits | |
103 | correspond to rooms rotated 90 degrees clockwise (though not precisely | |
104 | one-to-one). | |
105 | ||
106 | +------+ | |
107 | | /}| | |
108 | |&#/$?@| | |
109 | | / \&| | |
110 | | | | |
111 | | { | | |
112 | |\\ | | |
113 | +------+ | |
114 | ||
115 | After reading a 0 bit and leaving the right edge, the room is copied, | |
116 | rotated 90 degrees clockwise, and adjoined, so that the rooms of the | |
117 | program are: | |
118 | ||
119 | +------+------+ | |
120 | | /}|\ & | | |
121 | |&#/$?@|\{ # | | |
122 | | / \&| // | | |
123 | | | $ | | |
124 | | { | \?/| | |
125 | |\\ | &@}| | |
126 | +------+------+ | |
127 | ||
128 | After leaving the right edge again, the current room is copied, this | |
129 | time rotated 90 degrees counterclockwise, and adjoined, and we get: | |
130 | ||
131 | +------+------+------+ | |
132 | | /}|\ & | /}| | |
133 | |&#/$?@|\{ # |&#/$?@| | |
134 | | / \&| // | / \&| | |
135 | | | $ | | | |
136 | | { | \?/| { | | |
137 | |\\ | &@}|\\ | | |
138 | +------+------+------+ | |
139 | ||
140 | Say we were to now read in a 1 bit; we would thus have: | |
141 | ||
142 | +------+------+------+------+ | |
143 | | /}|\ & | /}| /}| | |
144 | |&#/$?@|\{ # |&#/$?@|&#/$?@| | |
145 | | / \&| // | / \&| / \&| | |
146 | | | $ | | | | |
147 | | { | \?/| { | { | | |
148 | |\\ | &@}|\\ |\\ | | |
149 | +------+------+------+------+ | |
150 | ||
151 | It should be fairly clear at this point that this program will read all | |
152 | input bits, creating rooms thusly, terminating when there are no more | |
153 | input bits. | |
154 | ||
155 | We can write a program that is a variation of the above which, when it | |
156 | encounters the end of input, writes out the bits in the reverse order | |
157 | they were read in, with the following changes: | |
158 | ||
159 | * for every `1` in the input, a `1` comes out | |
160 | * for every `0` in the input, `10` comes out | |
161 | * there's an extra `1` at the end of the output | |
162 | ||
163 | Here is the program: | |
164 | ||
165 | +------------+ | |
166 | | /} | | |
167 | |&#/$? \ | | |
168 | | / \& | | |
169 | | | | |
170 | | | | |
171 | | 0 | | |
172 | | ! | | |
173 | | | | |
174 | | | | |
175 | | {1 /# | | |
176 | | { | | |
177 | |\\@ | | |
178 | +------------+ | |
179 | ||
180 | Computational Class | |
181 | ------------------- | |
182 | ||
183 | The last example in the previous section was written to demonstrate that | |
184 | Nhohnhehr is at least as powerful as a push-down automaton. | |
185 | ||
186 | The author suspects Nhohnhehr to be more powerful still; at least a | |
187 | linear bounded automaton, but possibly even Turing-complete. A strategy | |
188 | for simulating a Turing machine could be developed from the above | |
189 | examples: create new rooms to represent new tape cells, with each | |
190 | possible orientation of the room representing a different tape symbol. | |
191 | The finite control is encoded and embedded in the possible pathways that | |
192 | the instruction pointer can traverse inside each room. Because rooms | |
193 | cannot be changed once created, one might have to resort to creative | |
194 | measures to "change" a tape cell; for instance, each tape cell might | |
195 | have a "stack" of rooms, with a new room appended to the stack each time | |
196 | the cell is to be "changed". | |
197 | ||
198 | Source | |
199 | ------ | |
200 | ||
201 | This document was adapted from [the esolangs.org wiki page for | |
202 | Nhohnhehr](http://www.esolangs.org/wiki/Nhohnhehr), which, like all | |
203 | esowiki articles, has been placed under public domain dedication. | |
204 | ||
205 | Implementation | |
206 | -------------- | |
207 | ||
208 | The Nhohnhehr distribution contains a Nhohnhehr interpreter, written | |
209 | in Python, based on [this implementation of | |
210 | Nhohnhehr](http://esolangs.org/wiki/User:Marinus/Nhohnhehr_interpreter) | |
211 | by [Marinus](http://www.esolangs.org/wiki/User:Marinus). It | |
212 | is effectively the reference interpreter, since it seems to correctly | |
213 | implement the language described here, and there are, to the best of | |
214 | my knowledge, no other implementations of Nhohnhehr in existence. | |
215 | Like all content from the esowiki, it too is in the public domain. |
0 | 0 | #!/usr/bin/env python |
1 | 1 | |
2 | ### Nhohnhehr interpreter ### | |
2 | # ### Nhohnhehr interpreter ### | |
3 | 3 | |
4 | 4 | # Written by Marinus. The contents of this file are in the public domain. |
5 | 5 | # Adapted from this esowiki page on May 10 2011: |
6 | 6 | # http://www.esolangs.org/wiki/User:Marinus/Nhohnhehr_interpreter |
7 | # Adapted to run under Python 3, and to conform to PEP8 style, | |
8 | # by Chris Pressey, summer 2021. | |
7 | 9 | |
8 | 10 | # usage: nhohnhehr.py bits filename (binary I/O) |
9 | 11 | # nhohnhehr.py [bytes] filename (ASCII I/O) |
10 | 12 | |
11 | 13 | import sys |
12 | 14 | |
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 | DEBUG = False | |
17 | ||
18 | ||
19 | def addvec(v1, v2): | |
20 | (x1, y1) = v1 | |
21 | (x2, y2) = v2 | |
22 | return (x1 + x2, y1 + y2) | |
23 | ||
24 | ||
25 | def mulvec(v, m): | |
26 | (x, y) = v | |
27 | return (x * m, y * m) | |
15 | 28 | |
16 | 29 | |
17 | 30 | # room |
18 | class Room: | |
31 | class Room(object): | |
19 | 32 | data = None |
20 | 33 | size = 0 |
21 | ||
22 | def __getitem__(self, (x, y)): | |
23 | if x>=0 and y>=0 and x<self.size and y<self.size: | |
34 | ||
35 | def __getitem__(self, v): | |
36 | (x, y) = v | |
37 | if x >= 0 and y >= 0 and x < self.size and y < self.size: | |
24 | 38 | return self.data[y][x] |
25 | 39 | else: |
26 | 40 | raise IndexError("value out of range") |
27 | ||
41 | ||
28 | 42 | def __str__(self): |
29 | 43 | return "\n".join(''.join(str(item) for item in line) for line in self.data) |
30 | ||
44 | ||
31 | 45 | # transformations |
32 | 46 | NONE, CW, CCW, ROT = range(4) |
33 | ||
47 | ||
34 | 48 | def transform(self, transformation): |
35 | if transformation==Room.NONE: return | |
36 | elif transformation==Room.ROT: | |
49 | if transformation == Room.NONE: | |
50 | return | |
51 | elif transformation == Room.ROT: | |
37 | 52 | # rotate 180 degrees: flip lines, and reverse each line |
38 | 53 | self.data.reverse() |
39 | for line in self.data: line.reverse() | |
40 | elif transformation==Room.CCW: | |
54 | for line in self.data: | |
55 | line.reverse() | |
56 | elif transformation == Room.CCW: | |
41 | 57 | # clockwise 90 degrees |
42 | 58 | data = self.data |
43 | self.data = [[0]*self.size for x in range(self.size)] | |
59 | self.data = [[0] * self.size for x in range(self.size)] | |
44 | 60 | for y in range(self.size): |
45 | 61 | for x in range(self.size): |
46 | self.data[self.size-x-1][y] = data[y][x] | |
47 | elif transformation==Room.CW: | |
62 | self.data[self.size - x - 1][y] = data[y][x] | |
63 | elif transformation == Room.CW: | |
48 | 64 | # counterclockwise 90 degrees |
49 | 65 | data = self.data |
50 | self.data = [[0]*self.size for x in range(self.size)] | |
66 | self.data = [[0] * self.size for x in range(self.size)] | |
51 | 67 | for y in range(self.size): |
52 | 68 | for x in range(self.size): |
53 | self.data[y][x] = data[self.size-x-1][y] | |
69 | self.data[y][x] = data[self.size - x - 1][y] | |
54 | 70 | else: |
55 | raise ValueError("invalid transformation (%d)"%transformation) | |
56 | ||
71 | raise ValueError("invalid transformation (%d)" % transformation) | |
72 | ||
57 | 73 | # init room from file or from room+transformation |
58 | 74 | def __init__(self, file=None, room=None, transform=None): |
59 | 75 | if (file and room) or (not (file or room)): |
60 | 76 | raise TypeError("Room needs to be initialized with either a file or a room.") |
61 | ||
77 | ||
62 | 78 | # init from file |
63 | 79 | if file: |
64 | 80 | self.data = [] |
65 | ||
81 | ||
66 | 82 | # read file |
67 | 83 | lines = file.readlines() |
68 | ||
84 | ||
69 | 85 | # find possible top-left coordinates for the box |
70 | 86 | possibleStartCoords = [] |
71 | 87 | for y in range(len(lines)): |
72 | 88 | for x in range(len(lines[y])): |
73 | 89 | if lines[y][x] == '+': |
74 | 90 | try: |
75 | if lines[y][x+1] == '-' and lines[y+1][x]=='|': | |
91 | if lines[y][x + 1] == '-' and lines[y + 1][x] == '|': | |
76 | 92 | possibleStartCoords.append((x, y)) |
77 | 93 | except IndexError: |
78 | 94 | # we hit a boundary looking for | or -, so this |
79 | 95 | # isn't a valid one. |
80 | 96 | pass |
81 | ||
97 | ||
82 | 98 | # check if a box can be found |
83 | 99 | startCoords = None |
84 | 100 | roomSize = 0 |
85 | 101 | for (x, y) in possibleStartCoords: |
86 | ||
102 | ||
87 | 103 | line = lines[y] |
88 | 104 | # find next '+' |
89 | x2 = x+1 | |
90 | while x2<len(line) and line[x2]!='+': x2+=1 | |
91 | ||
92 | if x2==len(line): | |
105 | x2 = x + 1 | |
106 | while x2 < len(line) and line[x2] != '+': | |
107 | x2 += 1 | |
108 | ||
109 | if x2 == len(line): | |
93 | 110 | # no '+' here. |
94 | 111 | continue |
95 | ||
112 | ||
96 | 113 | # found |
97 | size = x2 - x | |
114 | size = x2 - x | |
98 | 115 | ok = False |
99 | 116 | # see if it's square |
100 | 117 | try: |
101 | 118 | # check horizontal lines |
102 | if lines[y+size][x:x+size+1] == '+'+'-'*(size-1)+'+': | |
119 | if lines[y + size][x:x + size + 1] == '+' + '-' * (size - 1) + '+': | |
103 | 120 | ok = True |
104 | 121 | # 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 | ||
122 | for y2 in range(y + 1, y + size): | |
123 | ok = ok and (lines[y2][x] + lines[y2][x + size] == '||') | |
124 | if not ok: | |
125 | break | |
126 | ||
109 | 127 | except IndexError: |
110 | 128 | # we went outside of the file, so this one isn't valid |
111 | 129 | ok = False |
112 | ||
130 | ||
113 | 131 | if not ok: |
114 | 132 | # try next pair |
115 | 133 | continue |
117 | 135 | # found one! |
118 | 136 | if startCoords: |
119 | 137 | # 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)." \ | |
138 | raise ValueError("Multiple valid rooms in one file, first room" | |
139 | " found at: (%d,%d); second one at: (%d,%d)." | |
122 | 140 | % (startCoords[0], startCoords[1], x, y)) |
123 | 141 | else: |
124 | 142 | # + 1 because that's the start of the data, we don't need the boundary |
126 | 144 | roomSize = size - 1 |
127 | 145 | # and we have to continue looking in case we find another one, |
128 | 146 | # in which case the file is invalid. |
129 | ||
147 | ||
130 | 148 | # no room in the file |
131 | 149 | if not startCoords: |
132 | 150 | raise ValueError("Cannot find a valid room in this file.") |
133 | ||
151 | ||
134 | 152 | # we have a room, load it |
135 | 153 | x, y = startCoords |
136 | 154 | for lineno in range(roomSize): |
137 | self.data.append([m for m in lines[lineno+y][x:roomSize+x]]) | |
138 | ||
155 | self.data.append([m for m in lines[lineno + y][x:roomSize + x]]) | |
156 | ||
139 | 157 | self.size = roomSize |
140 | 158 | |
141 | 159 | # init from other room |
143 | 161 | # this one's easier |
144 | 162 | self.size = room.size |
145 | 163 | self.data = [line[:] for line in room.data] |
146 | ||
164 | ||
147 | 165 | # transformation needed? |
148 | 166 | if transform: |
149 | 167 | self.transform(transform) |
150 | ||
151 | ||
152 | ||
153 | class Environment: | |
168 | ||
169 | ||
170 | class Environment(object): | |
154 | 171 | rooms = {} |
155 | 172 | ip = () |
156 | 173 | direction = () |
157 | 174 | edgemode = None |
158 | 175 | roomsize = 0 |
159 | 176 | halt = False |
160 | ||
177 | ||
161 | 178 | # states |
162 | WRAP,COPY,CW,CCW,ROT = range(5) | |
163 | ||
179 | WRAP, COPY, CW, CCW, ROT = range(5) | |
180 | ||
164 | 181 | # directions |
165 | LEFT,RIGHT,UP,DOWN = (-1,0), (1,0), (0,-1), (0,1) | |
166 | ||
167 | def __getitem__(self, (x, y)): | |
182 | LEFT, RIGHT, UP, DOWN = (-1, 0), (1, 0), (0, -1), (0, 1) | |
183 | ||
184 | def __getitem__(self, v): | |
168 | 185 | # get whatever's in that room at that space |
186 | (x, y) = v | |
169 | 187 | room = self.rooms[self.roomCoords((x, y))] |
170 | roomX = x%self.roomsize | |
171 | roomY = y%self.roomsize | |
188 | roomX = x % self.roomsize | |
189 | roomY = y % self.roomsize | |
190 | if DEBUG: | |
191 | sys.stdout.write('XY {}: room {}, roomXY {}\n'.format( | |
192 | (x, y), self.roomCoords((x, y)), (roomX, roomY) | |
193 | )) | |
172 | 194 | return room[roomX, roomY] |
173 | ||
174 | def __init__(self, room, (infunc, outfunc)): | |
175 | self.rooms = { (0,0): room } | |
195 | ||
196 | def __init__(self, room, io_system): | |
197 | self.rooms = { | |
198 | (0, 0): room | |
199 | } | |
176 | 200 | self.roomsize = room.size |
177 | 201 | self.dir = Environment.RIGHT |
178 | 202 | self.edgemode = Environment.WRAP |
179 | self.infunc, self.outfunc = infunc, outfunc | |
203 | self.io_system = io_system | |
180 | 204 | self.halt = False |
181 | 205 | # find initial instruction pointer |
182 | ||
206 | ||
183 | 207 | self.ip = (-1, -1) |
184 | 208 | for x in range(self.roomsize): |
185 | 209 | for y in range(self.roomsize): |
186 | 210 | if room[x, y] == '$': |
187 | 211 | self.ip = (x, y) |
188 | 212 | break |
189 | ||
213 | ||
190 | 214 | if self.ip == (-1, -1): |
191 | 215 | raise ValueError("no $ in room") |
192 | ||
193 | def roomCoords(self, (x,y)): | |
194 | return (int(x/self.roomsize), int(y/self.roomsize)) | |
195 | ||
216 | ||
217 | def infunc(self): | |
218 | return self.io_system.units_in() | |
219 | ||
220 | def outfunc(self, unit): | |
221 | return self.io_system.units_out(unit) | |
222 | ||
223 | def roomCoords(self, v): | |
224 | (x, y) = v | |
225 | return (x // self.roomsize, y // self.roomsize) | |
226 | ||
196 | 227 | def advanceIP(self): |
197 | 228 | newIP = addvec(self.ip, self.dir) |
198 | ||
229 | ||
199 | 230 | if self.roomCoords(self.ip) != self.roomCoords(newIP): |
200 | 231 | if self.edgemode == Environment.WRAP: |
201 | 232 | # wrap to edge of last room |
204 | 235 | # make a new room if none exists yet |
205 | 236 | if not self.roomCoords(newIP) in self.rooms: |
206 | 237 | # 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) } ) | |
238 | transform = { | |
239 | Environment.COPY: Room.NONE, | |
240 | Environment.CW: Room.CW, | |
241 | Environment.CCW: Room.CCW, | |
242 | Environment.ROT: Room.ROT | |
243 | }[self.edgemode] | |
244 | self.rooms.update({ | |
245 | self.roomCoords(newIP): Room( | |
246 | room=self.rooms[self.roomCoords(self.ip)], | |
247 | transform=transform | |
248 | ) | |
249 | }) | |
215 | 250 | self.ip = newIP |
216 | ||
251 | ||
217 | 252 | def step(self): |
218 | 253 | 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} | |
254 | if DEBUG: | |
255 | sys.stdout.write('({}) @ {}\n'.format(command, self.ip)) | |
256 | ccwrot = { | |
257 | self.LEFT: self.DOWN, | |
258 | self.RIGHT: self.UP, | |
259 | self.UP: self.RIGHT, | |
260 | self.DOWN: self.LEFT | |
261 | } | |
262 | cwrot = { | |
263 | self.LEFT: self.UP, | |
264 | self.RIGHT: self.DOWN, | |
265 | self.UP: self.LEFT, | |
266 | self.DOWN: self.RIGHT | |
267 | } | |
223 | 268 | |
224 | 269 | if command == '/': |
225 | 270 | self.dir = ccwrot[self.dir] |
226 | 271 | elif command == '\\': |
227 | 272 | self.dir = cwrot[self.dir] |
228 | 273 | elif command in '=&{}!': |
229 | self.edgemode = { '=': self.WRAP, | |
230 | '&': self.COPY, | |
231 | '{': self.CCW, | |
232 | '}': self.CW, | |
233 | '!': self.ROT } [command] | |
274 | self.edgemode = { | |
275 | '=': self.WRAP, | |
276 | '&': self.COPY, | |
277 | '{': self.CCW, | |
278 | '}': self.CW, | |
279 | '!': self.ROT | |
280 | }[command] | |
234 | 281 | elif command == '#': |
235 | 282 | self.advanceIP() |
236 | 283 | elif command == '?': |
237 | 284 | try: |
238 | self.dir = ( self.infunc() and cwrot or ccwrot ) [self.dir] | |
285 | self.dir = (self.infunc() and cwrot or ccwrot)[self.dir] | |
239 | 286 | except IOError: |
240 | 287 | # no more input available = do nothing |
241 | 288 | pass |
243 | 290 | self.outfunc(int(command)) |
244 | 291 | elif command == '@': |
245 | 292 | self.halt = True |
246 | ||
293 | ||
247 | 294 | self.advanceIP() |
248 | ||
295 | ||
249 | 296 | def run(self): |
250 | while not self.halt: self.step() | |
251 | ||
297 | while not self.halt: | |
298 | self.step() | |
299 | ||
300 | ||
301 | class NhohnhehrIO(object): | |
302 | def units_in(self): | |
303 | raise NotImplementedError('implement units_in please') | |
304 | ||
305 | def units_out(self, unit): | |
306 | raise NotImplementedError('implement units_out please') | |
307 | ||
308 | def close(self): | |
309 | pass | |
310 | ||
311 | ||
312 | class BitsIO(NhohnhehrIO): | |
313 | def units_in(self): | |
314 | i = None | |
315 | while i not in ('0', '1'): | |
316 | i = sys.stdin.read(1) | |
317 | if i == '': | |
318 | raise IOError() # eof | |
319 | return int(i) | |
320 | ||
321 | def units_out(self, bit): | |
322 | sys.stdout.write(('0', '1')[bit]) | |
323 | sys.stdout.flush() | |
324 | ||
325 | def close(self): | |
326 | sys.stdout.write("\n") | |
327 | sys.stdout.flush() | |
328 | ||
329 | ||
330 | class BytesIO(NhohnhehrIO): | |
331 | def __init__(self): | |
332 | self.bits = [[]] | |
333 | ||
334 | def units_in(self): | |
335 | # get data if necessary | |
336 | if self.bits[0] == []: | |
337 | i = sys.stdin.read(1) | |
338 | if (i == ''): | |
339 | raise IOError() # eof | |
340 | else: | |
341 | self.bits[0] = [int(bool(ord(i) & (1 << b))) for b in range(7, -1, -1)] | |
342 | ||
343 | # return data | |
344 | bit = self.bits[0][0] | |
345 | self.bits[0] = self.bits[0][1:] | |
346 | return bit | |
347 | ||
348 | def units_out(self, bit): | |
349 | self.bits[0].append(bit) | |
350 | ||
351 | # if we have 8 bits, output | |
352 | if len(self.bits[0]) == 8: | |
353 | sys.stdout.write(chr(sum(self.bits[0][7 - b] << b for b in range(7, -1, -1)))) | |
354 | sys.stdout.flush() | |
355 | self.bits[0] = [] | |
356 | ||
252 | 357 | |
253 | 358 | 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 | ||
359 | if len(argv) not in (2, 3) or (len(argv) == 3 and not argv[1] in ('bits', 'bytes')): | |
360 | print("""\ | |
361 | Usage: [python] %s [bits|bytes] filename | |
362 | bits/bytes: specify i/o mode | |
363 | ||
364 | In bits mode, i/o uses the characters '0' and '1' | |
365 | (and when reading input, everything that's not '0' | |
366 | or 1 is ignored). | |
367 | In bytes mode, i/o is done 8 bits at a time as ASCII. | |
368 | ||
369 | If no mode is given, bytes mode is used. | |
370 | """ % argv[0]) | |
266 | 371 | sys.exit() |
267 | ||
268 | if len(argv)==2: | |
372 | ||
373 | if len(argv) == 2: | |
269 | 374 | mode = 'bytes' |
270 | 375 | fname = argv[1] |
271 | 376 | else: |
272 | 377 | mode = argv[1] |
273 | 378 | 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) } | |
379 | ||
380 | io_system = { | |
381 | 'bits': BitsIO(), | |
382 | 'bytes': BytesIO(), | |
383 | }[mode] | |
312 | 384 | 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) | |
385 | with open(fname, 'r') as f: | |
386 | Environment(Room(file=f), io_system).run() | |
387 | io_system.close() | |
388 | except Exception as e: | |
389 | print("Error: {}".format(e)) | |
390 | ||
391 | ||
392 | if __name__ == '__main__': | |
393 | main(sys.argv) |
0 | 0 | #!/bin/sh |
1 | 1 | |
2 | # Just a rudimentary sanity test, better than nothing. | |
3 | ||
4 | EXPECTED='110101010101011111111' | |
5 | R=`echo '11111110000001' | src/nhohnhehr.py bits eg/reverse-esque.nho` | |
6 | if [ $R = $EXPECTED ]; then | |
7 | echo 'Sanity test passed.' | |
8 | else | |
9 | echo "Expected $EXPECTED but got $R." | |
10 | exit 1 | |
11 | fi | |
2 | APPLIANCES="tests/appliances/nhohnhehr.py.md" | |
3 | falderal $APPLIANCES tests/Nhohnhehr.md || exit 1 |
0 | Test Suite for Nhohnhehr | |
1 | ======================== | |
2 | ||
3 | This test suite is written in [Falderal][] format. | |
4 | ||
5 | [Falderal]: https://catseye.tc/node/Falderal | |
6 | ||
7 | -> Tests for functionality "Run Nhohnhehr program, outputting at most 80 bits" | |
8 | ||
9 | "Reverse-esque" example. | |
10 | ||
11 | | +------------+ | |
12 | | | /} | | |
13 | | |&#/$? \ | | |
14 | | | / \& | | |
15 | | | | | |
16 | | | | | |
17 | | | 0 | | |
18 | | | ! | | |
19 | | | | | |
20 | | | | | |
21 | | | {1 /# | | |
22 | | | { | | |
23 | | |\\@ | | |
24 | | +------------+ | |
25 | + 11111110000001 | |
26 | = 110101010101011111111 | |
27 | ||
28 | "Read and store" example. | |
29 | ||
30 | | +------+ | |
31 | | | /}| | |
32 | | |&#/$?@| | |
33 | | | / \&| | |
34 | | | | | |
35 | | | { | | |
36 | | |\\ | | |
37 | | +------+ | |
38 | + 11111110000001 | |
39 | = | |
40 | ||
41 | Truth-machine example. | |
42 | ||
43 | | +---+ | |
44 | | |@/0| | |
45 | | |$? | | |
46 | | |#\1| | |
47 | | +---+ | |
48 | + 0 | |
49 | = 0 | |
50 | ||
51 | | +---+ | |
52 | | |@/0| | |
53 | | |$? | | |
54 | | |#\1| | |
55 | | +---+ | |
56 | + 1 | |
57 | = 11111111111111111111111111111111111111111111111111111111111111111111111111111111 |
0 | -> Functionality "Run Nhohnhehr program, outputting at most 80 bits" is implemented by | |
1 | -> shell command | |
2 | -> "python2 src/nhohnhehr.py bits %(test-body-file) <%(test-input-file) 2>&1 | head -c 80" | |
3 | ||
4 | -> Functionality "Run Nhohnhehr program, outputting at most 80 bits" is implemented by | |
5 | -> shell command | |
6 | -> "python3 src/nhohnhehr.py bits %(test-body-file) <%(test-input-file) 2>&1 | head -c 80" |