Mercurial > repo
comparison emmental.hs @ 2135:463776b34329
<oerjan> fetch http://oerjan.nvg.org/esoteric/emmental.hs
author | HackBot |
---|---|
date | Fri, 15 Feb 2013 07:16:34 +0000 |
parents | |
children |
comparison
equal
deleted
inserted
replaced
2134:9115bf1f2da4 | 2135:463776b34329 |
---|---|
1 -- | |
2 -- emmental.hs | |
3 -- Interpreter for the Emmental Programming Language | |
4 -- Chris Pressey, Cat's Eye Technologies | |
5 -- This work is in the public domain. See UNLICENSE for more information. | |
6 -- | |
7 -- Ørjan Johansen added main | |
8 -- | |
9 | |
10 | |
11 import qualified Data.Map as Map | |
12 import qualified Data.Char as Char | |
13 import System.Environment (getArgs) | |
14 | |
15 main = do | |
16 args <- getArgs | |
17 case args of | |
18 "-d" : as -> debug =<< parse return as | |
19 _ -> emmental =<< parse return args | |
20 where | |
21 parse :: (String -> IO String) -> [String] -> IO String | |
22 parse _ ("-f":as) = parse readFile as | |
23 parse _ ("-e":as) = parse return as | |
24 parse op (a:as) = op a >>= (`fmap` parse op as) . (++) | |
25 parse _ [] = return "" | |
26 | |
27 | |
28 ----------------------------------------------------------------------- | |
29 -- ============================ Symbols ============================ -- | |
30 ----------------------------------------------------------------------- | |
31 | |
32 type Symbol = Char | |
33 | |
34 | |
35 ----------------------------------------------------------------------- | |
36 -- ======================== Program States ========================= -- | |
37 ----------------------------------------------------------------------- | |
38 | |
39 data State = State [Symbol] [Symbol] | |
40 deriving (Ord, Eq, Show) | |
41 | |
42 pop (State (head:tail) queue) = (head, State tail queue) | |
43 push (State list queue) sym = (State (sym:list) queue) | |
44 | |
45 popString (State (';':tail) queue) = ([], State tail queue) | |
46 popString (State (head:tail) queue) = | |
47 let | |
48 (string, state') = popString (State tail queue) | |
49 in | |
50 (string ++ [head], state') | |
51 | |
52 enqueue (State stack queue) symbol = State stack (symbol:queue) | |
53 dequeue (State stack queue) = | |
54 let | |
55 symbol = last queue | |
56 queue' = init queue | |
57 in | |
58 (symbol, State stack queue') | |
59 | |
60 | |
61 ----------------------------------------------------------------------- | |
62 -- ========================= Interpreters ========================== -- | |
63 ----------------------------------------------------------------------- | |
64 | |
65 data Interpreter = Interp (Map.Map Symbol Operation) | |
66 | |
67 fetch (Interp map) sym = Map.findWithDefault (fitRegOp opNop) sym map | |
68 supplant (Interp map) sym op = (Interp (Map.insert sym op map)) | |
69 | |
70 | |
71 ----------------------------------------------------------------------- | |
72 -- ========================== Operations =========================== -- | |
73 ----------------------------------------------------------------------- | |
74 | |
75 type Operation = State -> Interpreter -> IO (State, Interpreter) | |
76 | |
77 composeOps :: Operation -> Operation -> Operation | |
78 | |
79 composeOps op1 op2 = f where | |
80 f state interpreter = do | |
81 (state', interpreter') <- op1 state interpreter | |
82 op2 state' interpreter' | |
83 | |
84 createOp :: Interpreter -> [Symbol] -> Operation | |
85 | |
86 createOp interpreter [] = | |
87 (fitRegOp opNop) | |
88 createOp interpreter (head:tail) = | |
89 composeOps (fetch interpreter head) (createOp interpreter tail) | |
90 | |
91 -- | |
92 -- It's useful for us to express a lot of our operators as non-monadic | |
93 -- functions that don't affect the interpreter. This is a little "adapter" | |
94 -- function that lets us create monadic functions with the right signature | |
95 -- from them. | |
96 -- | |
97 | |
98 fitRegOp :: (State -> State) -> Operation | |
99 | |
100 fitRegOp regop = f where | |
101 f state interpreter = | |
102 let | |
103 state' = regop state | |
104 in | |
105 do return (state', interpreter) | |
106 | |
107 | |
108 ------------------------------------------------------------ | |
109 --------------- The operations themselves. ----------------- | |
110 ------------------------------------------------------------ | |
111 | |
112 -- | |
113 -- Redefine the meaning of the symbol on the stack with | |
114 -- a mini-program also popped off the stack. | |
115 -- | |
116 | |
117 opSupplant state interpreter = | |
118 let | |
119 (opSym, state') = pop state | |
120 (newOpDefn, state'') = popString state' | |
121 newOp = createOp interpreter newOpDefn | |
122 in | |
123 do return (state'', supplant interpreter opSym newOp) | |
124 | |
125 -- | |
126 -- Execute the symbol on the stack with the current interpreter. | |
127 -- | |
128 | |
129 opEval state interpreter = | |
130 let | |
131 (opSym, state') = pop state | |
132 newOp = createOp interpreter [opSym] | |
133 in | |
134 newOp state' interpreter | |
135 | |
136 -- | |
137 -- I/O. | |
138 -- | |
139 | |
140 opInput state interpreter = do | |
141 symbol <- getChar | |
142 do return (push state symbol, interpreter) | |
143 | |
144 opOutput state interpreter = | |
145 let | |
146 (symbol, state') = pop state | |
147 in do | |
148 putChar symbol | |
149 return (state', interpreter) | |
150 | |
151 -- | |
152 -- Primitive arithmetic. | |
153 -- | |
154 | |
155 opAdd state = | |
156 let | |
157 (symA, state') = pop state | |
158 (symB, state'') = pop state' | |
159 in | |
160 push state'' (Char.chr (((Char.ord symB) + (Char.ord symA)) `mod` 256)) | |
161 | |
162 opSubtract state = | |
163 let | |
164 (symA, state') = pop state | |
165 (symB, state'') = pop state' | |
166 in | |
167 push state'' (Char.chr (((Char.ord symB) - (Char.ord symA)) `mod` 256)) | |
168 | |
169 discreteLog 0 = 8 | |
170 discreteLog 1 = 0 | |
171 discreteLog 2 = 1 | |
172 discreteLog n = (discreteLog (n `div` 2)) + 1 | |
173 | |
174 opDiscreteLog state = | |
175 let | |
176 (symbol, state') = pop state | |
177 in | |
178 push state' (Char.chr (discreteLog (Char.ord symbol))) | |
179 | |
180 -- | |
181 -- Stack manipulation. | |
182 -- | |
183 | |
184 -- | |
185 -- Pop the top symbol of the stack, make a copy of it, push it back onto the | |
186 -- stack, and enqueue the copy onto the queue. | |
187 -- | |
188 | |
189 opEnqueueCopy state = | |
190 let | |
191 (sym, _) = pop state | |
192 in | |
193 enqueue state sym | |
194 | |
195 -- | |
196 -- Dequeue a symbol from the queue and push it onto the stack. | |
197 -- | |
198 | |
199 opDequeue state = | |
200 let | |
201 (sym, state') = dequeue state | |
202 in | |
203 push state' sym | |
204 | |
205 -- | |
206 -- Duplicate the top symbol of the stack. | |
207 -- | |
208 | |
209 opDuplicate state = | |
210 let | |
211 (symbol, _) = pop state | |
212 in | |
213 push state symbol | |
214 | |
215 -- | |
216 -- Miscellaneous operations. | |
217 -- | |
218 | |
219 opNop state = | |
220 state | |
221 | |
222 -- | |
223 -- Parameterizable operations. | |
224 -- | |
225 | |
226 opPushValue value state = | |
227 push state (Char.chr value) | |
228 | |
229 opAccumValue value state = | |
230 let | |
231 (sym, state') = pop state | |
232 value' = ((Char.ord sym) * 10) + value | |
233 in | |
234 push state' (Char.chr (value' `mod` 256)) | |
235 | |
236 | |
237 ----------------------------------------------------------------------- | |
238 -- ===================== Debugging Functions ======================= -- | |
239 ----------------------------------------------------------------------- | |
240 | |
241 type Debugger = State -> Interpreter -> IO () | |
242 | |
243 debugNop s i = do | |
244 return () | |
245 | |
246 debugPrintState s i = do | |
247 putStr ((show s) ++ "\n") | |
248 return () | |
249 | |
250 | |
251 ----------------------------------------------------------------------- | |
252 -- ============================ Executor =========================== -- | |
253 ----------------------------------------------------------------------- | |
254 | |
255 execute :: [Symbol] -> State -> Interpreter -> Debugger -> IO (State, Interpreter) | |
256 | |
257 execute [] state interpreter debugger = | |
258 return (state, interpreter) | |
259 execute (opSym:program') state interpreter debugger = | |
260 let | |
261 operation = fetch interpreter opSym | |
262 in do | |
263 (state', interpreter') <- operation state interpreter | |
264 debugger state' interpreter' | |
265 execute program' state' interpreter' debugger | |
266 | |
267 | |
268 ----------------------------------------------------------------------- | |
269 -- ====================== Top-Level Function ======================= -- | |
270 ----------------------------------------------------------------------- | |
271 | |
272 initialInterpreter = Interp | |
273 (Map.fromList | |
274 [ | |
275 ('.', opOutput), | |
276 (',', opInput), | |
277 | |
278 ('#', fitRegOp (opPushValue 0)), | |
279 ('0', fitRegOp (opAccumValue 0)), | |
280 ('1', fitRegOp (opAccumValue 1)), | |
281 ('2', fitRegOp (opAccumValue 2)), | |
282 ('3', fitRegOp (opAccumValue 3)), | |
283 ('4', fitRegOp (opAccumValue 4)), | |
284 ('5', fitRegOp (opAccumValue 5)), | |
285 ('6', fitRegOp (opAccumValue 6)), | |
286 ('7', fitRegOp (opAccumValue 7)), | |
287 ('8', fitRegOp (opAccumValue 8)), | |
288 ('9', fitRegOp (opAccumValue 9)), | |
289 | |
290 ('+', fitRegOp opAdd), | |
291 ('-', fitRegOp opSubtract), | |
292 ('~', fitRegOp opDiscreteLog), | |
293 | |
294 ('^', fitRegOp opEnqueueCopy), | |
295 ('v', fitRegOp opDequeue), | |
296 (':', fitRegOp opDuplicate), | |
297 | |
298 ('!', opSupplant), | |
299 ('?', opEval), | |
300 | |
301 (';', fitRegOp (opPushValue (Char.ord ';'))) | |
302 ] | |
303 ) | |
304 | |
305 initialState = State [] [] | |
306 | |
307 emmental string = do | |
308 (state, interpreter) <- execute string initialState initialInterpreter debugNop | |
309 return state | |
310 | |
311 debug string = do | |
312 (state, interpreter) <- execute string initialState initialInterpreter debugPrintState | |
313 return state | |
314 | |
315 | |
316 ----------------------------------------------------------------------- | |
317 -- ========================== Test Cases =========================== -- | |
318 ----------------------------------------------------------------------- | |
319 | |
320 -- | |
321 -- Drivers for test cases. 'demo' runs them straight, whereas 'test' | |
322 -- uses the debugger. | |
323 -- | |
324 | |
325 demo n = emmental (testProg n) | |
326 | |
327 test n = debug (testProg n) | |
328 | |
329 -- | |
330 -- Here we introduce a bit of a cheat, in order to make writing | |
331 -- complex Emmental programs tolerable. You can still see the | |
332 -- programs in their fully glory by executing "show (testProg n)". | |
333 -- | |
334 | |
335 quote [] = [] | |
336 quote (symbol:rest) = "#" ++ (show (Char.ord symbol)) ++ (quote rest) | |
337 | |
338 -- | |
339 -- Add one and one. | |
340 -- | |
341 | |
342 testProg 1 = "#1#1+" | |
343 | |
344 -- | |
345 -- Redefine & as "+". | |
346 -- | |
347 | |
348 testProg 2 = ";#43#38!#1#1&" -- 59,43,38 ==> ";+&" | |
349 | |
350 -- | |
351 -- Redefine 0 as "9". | |
352 -- | |
353 | |
354 testProg 3 = ";#57#48!#0" -- 59,57,48 ==> ";90" | |
355 | |
356 -- | |
357 -- Redefine 0 as "#48?". This results in an infinite loop when 0 is executed. | |
358 -- | |
359 | |
360 testProg 4 = ";#35#52#56#63#48!0" -- 59,35,52,56,63,48 ==> ";#48?0" | |
361 | |
362 -- | |
363 -- Redefine $ as ".#36?". This results in a loop that pops symbols and | |
364 -- and prints them, until the stack underflows, when $ is executed. | |
365 -- | |
366 | |
367 testProg 5 = ";#46#35#51#54#63#36! #65#66#67#68#69$" | |
368 | |
369 -- | |
370 -- Duplicate the top stack element (assuming an empty queue.) | |
371 -- This shows that the : operation is not strictly necessary | |
372 -- (when you know the size of the queue.) | |
373 -- | |
374 | |
375 testProg 6 = "#65^v" | |
376 | |
377 -- | |
378 -- Discard the top stack element (assuming more than one element | |
379 -- on the stack, and an empty queue.) | |
380 -- | |
381 | |
382 testProg 7 = "#33#123^v-+" | |
383 | |
384 -- | |
385 -- Swap the top two elements of the stack (assuming an empty queue.) | |
386 -- | |
387 | |
388 testProg 8 = "#67#66#65^v^-+^^v^v^v-+^v-+^v-+vv" | |
389 | |
390 -- | |
391 -- Input a symbol. Report whether its ASCII value is even or odd. | |
392 -- | |
393 | |
394 testProg 9 = (quote ";^v:") ++ "!" ++ -- : = dup | |
395 (quote ";#69.") ++ "#!" ++ -- NUL = print "E" | |
396 (quote ";#79.") ++ "#128!" ++ -- \128 = print "O" | |
397 (quote (";" ++ (take 127 [':',':'..]) ++ -- m = mul by 128 | |
398 (take 127 ['+','+'..]) ++ "m")) ++ "!" ++ | |
399 ",m?" | |
400 | |
401 -- | |
402 -- Input a symbol. Report whether it is M or not. | |
403 -- | |
404 | |
405 testProg 10 = (quote ";#78.") ++ "#!" ++ -- NUL = print "N" | |
406 ";##1!" ++ -- SOH = same as NUL | |
407 ";##2!" ++ -- STX = same as NUL | |
408 ";##3!" ++ -- ETX = same as NUL | |
409 ";##4!" ++ -- EOT = same as NUL | |
410 ";##5!" ++ -- ENQ = same as NUL | |
411 ";##6!" ++ -- ACK = same as NUL | |
412 ";##7!" ++ -- BEL = same as NUL | |
413 (quote ";#89.") ++ "#8!" ++ -- BS = print "Y" | |
414 ",#77-~?" | |
415 | |
416 -- | |
417 -- Same as testProg 5, except stop printing when a NUL is | |
418 -- encountered, instead of just underflowing the stack. | |
419 -- | |
420 | |
421 testProg 11 = ";" ++ (quote ":~?$") ++ "!" ++ -- $ = dup & test | |
422 ";" ++ (quote ".$") ++ "#!" ++ -- NUL = print & repeat | |
423 ";#0#1!" ++ -- SOH = same as NUL | |
424 ";#0#2!" ++ -- STX = same as NUL | |
425 ";#0#3!" ++ -- ETX = same as NUL | |
426 ";#0#4!" ++ -- EOT = same as NUL | |
427 ";#0#5!" ++ -- ENQ = same as NUL | |
428 ";#0#6!" ++ -- ACK = same as NUL | |
429 ";#0#7!" ++ -- BEL = same as NUL | |
430 -- BS = stop (nop) | |
431 "#0" ++ (quote (reverse "Hello!")) ++ "$" |