Mercurial > repo
comparison interps/lazyk/lazy.cpp @ 996:859f9b4339e6
<Gregor> tar xf egobot.tar.xz
author | HackBot |
---|---|
date | Sun, 09 Dec 2012 19:30:08 +0000 |
parents | |
children |
comparison
equal
deleted
inserted
replaced
995:6883f5911eb7 | 996:859f9b4339e6 |
---|---|
1 // Lazy K interpreter in C++. | |
2 // For usage see usage() function below. | |
3 // Copyright 2002 Ben Rudiak-Gould. Distributed under the GPL. | |
4 // | |
5 // Implementation notes: | |
6 // - When Sxyz is reduced to (xz)(yz), both "copies" of z | |
7 // point to the same expression tree. When z (or any of | |
8 // its subexpressions) is reduced, the old tree nodes are | |
9 // overwritten with their newly reduced versions, so that | |
10 // any other pointers to the node get the benefit of the | |
11 // change. This is critical to the performance of any | |
12 // lazy evaluator. Despite this destructive update, the | |
13 // meaning (i.e. behavior) of the function described by | |
14 // any subtree never changes (until the nodes are | |
15 // garbage-collected and reassigned, that is). | |
16 // - I actually got stack overflows in the evaluator when | |
17 // running complicated programs (e.g. prime_numbers.unl | |
18 // inside the Unlambda interpreter), so I rewrote it to | |
19 // eliminate recursion from partial_eval() and free(). | |
20 // These functions now use relatively abstruse iterative | |
21 // algorithms which borrow expression tree pointers for | |
22 // temporary storage, and restore the original values | |
23 // where necessary before returning. Other than that, the | |
24 // interpreter is pretty simple to understand. The only | |
25 // recursion left (I think) is in the parser and in the | |
26 // Inc case of partial_eval_primitive_application; the | |
27 // former will only bite you if you have really deep | |
28 // nesting in your source code, and the latter only if | |
29 // you return a ridiculously large number in the output | |
30 // stream. | |
31 // | |
32 | |
33 | |
34 #include <stdio.h> | |
35 #include <fcntl.h> | |
36 #include <stdlib.h> | |
37 #include <ctype.h> | |
38 #include <sys/stat.h> | |
39 #include <sys/types.h> | |
40 #include <unistd.h> | |
41 | |
42 #ifdef __WIN32 | |
43 #include <io.h> | |
44 #else | |
45 #define O_BINARY 0 | |
46 #endif | |
47 | |
48 class Expr { | |
49 private: | |
50 int refcnt; | |
51 union { | |
52 Expr* arg1; | |
53 int numeric_arg1; | |
54 }; | |
55 Expr* arg2; | |
56 | |
57 static Expr* free_list; | |
58 static Expr* alloc(); | |
59 void free(); | |
60 | |
61 void partial_eval_primitive_application(); | |
62 | |
63 public: | |
64 enum Type { A, K, K1, S, S1, S2, I1, LazyRead, Inc, Num, Free } type; | |
65 | |
66 static void* operator new(size_t) { | |
67 Expr* result = free_list; | |
68 if (result) { | |
69 free_list = result->arg1; | |
70 return result; | |
71 } else { | |
72 return alloc(); | |
73 } | |
74 } | |
75 | |
76 // caller keeps original ref plus returned ref | |
77 Expr* dup() { | |
78 ++refcnt; | |
79 return this; | |
80 } | |
81 // caller loses original ref | |
82 void deref() { | |
83 if (--refcnt == 0) { | |
84 free(); | |
85 } | |
86 } | |
87 | |
88 // caller keeps original ref | |
89 Type gettype() { return type; } | |
90 | |
91 // caller loses refs to a1 and a2, gets ref to new object | |
92 Expr(Type t, Expr* a1 =0, Expr* a2 =0) { | |
93 refcnt = 1; | |
94 type = t; | |
95 arg1 = a1; arg2 = a2; | |
96 } | |
97 | |
98 // caller loses original ref, gets returned ref | |
99 Expr* partial_eval(); | |
100 | |
101 // caller loses original ref, gets returned ref | |
102 static Expr* partial_apply(Expr* lhs, Expr* rhs) { | |
103 // You could do something more complicated here, | |
104 // but I tried it and it didn't seem to improve | |
105 // execution speed. | |
106 return new Expr(A, lhs, rhs); | |
107 } | |
108 | |
109 // caller loses original ref | |
110 int to_number() { | |
111 int result = (type == Num) ? numeric_arg1 : -1; | |
112 deref(); | |
113 return result; | |
114 } | |
115 #if 0 | |
116 void print(Expr*); | |
117 #endif | |
118 // caller loses original ref, gets returned ref | |
119 Expr* drop_i1() { | |
120 Expr* cur = this; | |
121 if (type == I1) { | |
122 do { | |
123 cur = cur->arg1; | |
124 } while (cur->type == I1); | |
125 cur = cur->dup(); | |
126 this->deref(); | |
127 } | |
128 return cur; | |
129 } | |
130 }; | |
131 | |
132 | |
133 Expr* Expr::free_list = 0; | |
134 | |
135 | |
136 Expr K(Expr::K); | |
137 Expr S(Expr::S); | |
138 Expr I(Expr::S2, &K, &K); | |
139 Expr KI(Expr::K1, &I); | |
140 | |
141 Expr SI(Expr::S1, &I); | |
142 Expr KS(Expr::K1, &S); | |
143 Expr KK(Expr::K1, &K); | |
144 Expr SKSK(Expr::S2, &KS, &K); | |
145 Expr SIKS(Expr::S2, &I, &KS); | |
146 Expr Iota(Expr::S2, &SIKS, &KK); | |
147 | |
148 Expr Inc(Expr::Inc); | |
149 Expr Zero(Expr::Num); | |
150 | |
151 | |
152 Expr* Expr::alloc() { | |
153 enum { blocksize = 10000 }; | |
154 static Expr* p = 0; | |
155 static Expr* end = 0; | |
156 if (p >= end) { | |
157 p = (Expr*)malloc(blocksize*sizeof(Expr)); | |
158 if (p == 0) { | |
159 fputs("Out of memory!\n", stderr); | |
160 exit(2); | |
161 } | |
162 end = p + blocksize; | |
163 } | |
164 return p++; | |
165 } | |
166 | |
167 #if 0 | |
168 void Expr::print(Expr* highlight) { | |
169 if (this == highlight) { | |
170 fputs("###", stdout); | |
171 } | |
172 switch (type) { | |
173 case A: | |
174 putchar('('); | |
175 arg1->print(highlight); | |
176 putchar(' '); | |
177 arg2->print(highlight); | |
178 putchar(')'); | |
179 break; | |
180 case K: | |
181 putchar('K'); | |
182 break; | |
183 case K1: | |
184 fputs("[K ", stdout); | |
185 arg1->print(highlight); | |
186 putchar(']'); | |
187 break; | |
188 case S: | |
189 putchar('S'); | |
190 break; | |
191 case S1: | |
192 fputs("[s ", stdout); | |
193 arg1->print(highlight); | |
194 putchar(']'); | |
195 break; | |
196 case S2: | |
197 fputs("[S ", stdout); | |
198 arg1->print(highlight); | |
199 putchar(' '); | |
200 arg2->print(highlight); | |
201 putchar(']'); | |
202 break; | |
203 case I1: | |
204 putchar('.'); | |
205 arg1->print(highlight); | |
206 break; | |
207 case LazyRead: | |
208 fputs("LazyRead", stdout); | |
209 break; | |
210 case Inc: | |
211 fputs("Inc", stdout); | |
212 break; | |
213 case Num: | |
214 printf("%d", numeric_arg1); | |
215 break; | |
216 default: | |
217 putchar('?'); | |
218 } | |
219 if (this == highlight) { | |
220 fputs("###", stdout); | |
221 } | |
222 } | |
223 #endif | |
224 | |
225 Expr* make_church_char(int ch) { | |
226 if (ch < 0 || ch > 256) { | |
227 ch = 256; | |
228 } | |
229 | |
230 static Expr* cached_church_chars[257] = { KI.dup(), I.dup() }; | |
231 | |
232 if (cached_church_chars[ch] == 0) { | |
233 cached_church_chars[ch] = new Expr(Expr::S2, SKSK.dup(), make_church_char(ch-1)); | |
234 } | |
235 return cached_church_chars[ch]->dup(); | |
236 } | |
237 | |
238 | |
239 Expr* g_expr; | |
240 | |
241 | |
242 // This function modifies the object in-place so that | |
243 // all references to it see the new version. | |
244 | |
245 void Expr::partial_eval_primitive_application() { | |
246 Expr* lhs = arg1; | |
247 Expr* rhs = arg2->drop_i1(); | |
248 | |
249 // arg1 and arg2 are now uninitialized space | |
250 | |
251 switch (lhs->type) { | |
252 case K: | |
253 type = K1; | |
254 arg1 = rhs; | |
255 arg2 = 0; | |
256 break; | |
257 case K1: | |
258 type = I1; | |
259 arg1 = lhs->arg1->dup(); | |
260 arg2 = 0; | |
261 rhs->deref(); | |
262 break; | |
263 case S: | |
264 type = S1; | |
265 arg1 = rhs; | |
266 arg2 = 0; | |
267 break; | |
268 case S1: | |
269 type = S2; | |
270 arg1 = lhs->arg1->dup(); | |
271 arg2 = rhs; | |
272 break; | |
273 case LazyRead: | |
274 lhs->type = S2; | |
275 lhs->arg1 = new Expr(S2, I.dup(), new Expr(K1, make_church_char(getchar()))); | |
276 lhs->arg2 = new Expr(K1, new Expr(LazyRead)); | |
277 // fall thru | |
278 case S2: | |
279 //type = A; | |
280 arg1 = partial_apply(lhs->arg1->dup(), rhs->dup()); | |
281 arg2 = partial_apply(lhs->arg2->dup(), rhs); | |
282 break; | |
283 case Inc: | |
284 rhs = rhs->partial_eval(); | |
285 type = Num; | |
286 numeric_arg1 = rhs->to_number() + 1; | |
287 if (numeric_arg1 == 0) { | |
288 fputs("Runtime error: invalid output format (attempted to apply inc to a non-number)\n", stderr); | |
289 exit(3); | |
290 } | |
291 arg2 = 0; | |
292 break; | |
293 case Num: | |
294 fputs("Runtime error: invalid output format (attempted to apply a number)\n", stderr); | |
295 exit(3); | |
296 default: | |
297 fprintf(stderr, "INTERNAL ERROR: invalid type in partial_eval_primitive_application (%d)\n", lhs->type); | |
298 exit(4); | |
299 } | |
300 lhs->deref(); | |
301 } | |
302 | |
303 | |
304 Expr* Expr::partial_eval() { | |
305 Expr* prev = 0; | |
306 Expr* cur = this; | |
307 for (;;) { | |
308 cur = cur->drop_i1(); | |
309 while (cur->type == A) { | |
310 Expr* next = cur->arg1->drop_i1(); | |
311 cur->arg1 = prev; | |
312 prev = cur; cur = next; | |
313 } | |
314 if (!prev) { | |
315 return cur; | |
316 } | |
317 Expr* next = cur; cur = prev; | |
318 prev = cur->arg1; | |
319 cur->arg1 = next; | |
320 cur->partial_eval_primitive_application(); | |
321 } | |
322 } | |
323 | |
324 | |
325 /* | |
326 void Expr::free() { | |
327 if (type != Num) { | |
328 if (arg1) arg1->deref(); | |
329 if (arg2) arg2->deref(); | |
330 } | |
331 type = Free; | |
332 arg1 = free_list; | |
333 free_list = this; | |
334 } | |
335 */ | |
336 | |
337 void Expr::free() { | |
338 Expr* cur = this; | |
339 Expr* partially_free_list = 0; | |
340 for (;;) { | |
341 while (--cur->refcnt <= 0 && cur->arg1 != 0 && cur->type != Num) { | |
342 Expr* next = cur->arg1; | |
343 if (cur->arg2 != 0) { | |
344 cur->arg1 = partially_free_list; | |
345 partially_free_list = cur; | |
346 } else { | |
347 cur->arg1 = free_list; | |
348 free_list = cur; | |
349 } | |
350 cur = next; | |
351 } | |
352 if (partially_free_list == 0) { | |
353 break; | |
354 } | |
355 cur = partially_free_list; | |
356 partially_free_list = partially_free_list->arg1; | |
357 cur->arg1 = free_list; | |
358 free_list = cur; | |
359 cur = cur->arg2; | |
360 } | |
361 } | |
362 | |
363 | |
364 class Stream { | |
365 public: | |
366 virtual int getch() = 0; | |
367 virtual void ungetch(int ch) = 0; | |
368 virtual void error(const char* msg) = 0; | |
369 }; | |
370 | |
371 class File : public Stream { | |
372 FILE* f; | |
373 const char* filename; | |
374 enum { circular_buf_size = 256 }; | |
375 char circular_buf[circular_buf_size]; | |
376 int last_newline, cur_pos; | |
377 public: | |
378 File(FILE* _f, const char* _filename) { | |
379 f = _f; filename = _filename; | |
380 last_newline = cur_pos = 0; | |
381 } | |
382 int getch(); | |
383 void ungetch(int ch); | |
384 void error(const char* msg); | |
385 }; | |
386 | |
387 int File::getch() { | |
388 int ch; | |
389 do { | |
390 ch = getc(f); | |
391 circular_buf[(cur_pos++)%circular_buf_size] = ch; | |
392 if (ch == '#') { | |
393 do { | |
394 ch = getc(f); | |
395 } while (ch != '\n' && ch != EOF); | |
396 } | |
397 if (ch == '\n') { | |
398 last_newline = cur_pos; | |
399 } | |
400 } while (isspace(ch)); | |
401 return ch; | |
402 } | |
403 | |
404 void File::ungetch(int ch) { | |
405 ungetc(ch, f); | |
406 --cur_pos; | |
407 } | |
408 | |
409 void File::error(const char* msg) { | |
410 fprintf(stderr, "While parsing \"%s\": %s\n", filename, msg); | |
411 int from; | |
412 if (cur_pos-last_newline < circular_buf_size) { | |
413 from = last_newline; | |
414 } else { | |
415 from = cur_pos-circular_buf_size+1; | |
416 fputs("...", stdout); | |
417 } | |
418 for (int i=from; i < cur_pos; ++i) { | |
419 putc(circular_buf[i%circular_buf_size], stderr); | |
420 } | |
421 fputs(" <--\n", stderr); | |
422 exit(1); | |
423 } | |
424 | |
425 class StringStream : public Stream { | |
426 const char* str; | |
427 const char* p; | |
428 public: | |
429 StringStream(const char* s) { | |
430 str = s; p = s; | |
431 } | |
432 int getch() { | |
433 return *p ? *p++ : EOF; | |
434 } | |
435 void ungetch(int ch) { | |
436 if (ch != EOF) --p; | |
437 } | |
438 void error(const char* msg) { | |
439 fprintf(stderr, "While parsing command line: %s\n%s\n", msg, str); | |
440 for (const char* q = str+1; q < p; ++q) { | |
441 putc(' ', stderr); | |
442 } | |
443 fputs("^\n", stderr); | |
444 exit(1); | |
445 } | |
446 }; | |
447 | |
448 | |
449 Expr* parse_expr(Stream* f, int ch, bool i_is_iota); | |
450 | |
451 Expr* parse_manual_close(Stream* f, int expected_terminator); | |
452 | |
453 | |
454 Expr* parse_expr(Stream* f, int ch, bool i_is_iota) { | |
455 switch (ch) { | |
456 case '`': case '*': | |
457 { | |
458 Expr* p = parse_expr(f, f->getch(), ch=='*'); | |
459 Expr* q = parse_expr(f, f->getch(), ch=='*'); | |
460 return Expr::partial_apply(p, q); | |
461 } | |
462 case '(': | |
463 return parse_manual_close(f, ')'); | |
464 case ')': | |
465 f->error("Mismatched close-parenthesis!"); | |
466 case 'k': case 'K': | |
467 return K.dup(); | |
468 case 's': case 'S': | |
469 return S.dup(); | |
470 case 'i': | |
471 if (i_is_iota) | |
472 return Iota.dup(); | |
473 // else fall thru | |
474 case 'I': | |
475 return I.dup(); | |
476 case '0': case '1': | |
477 { | |
478 Expr* e = I.dup(); | |
479 do { | |
480 if (ch == '0') { | |
481 e = Expr::partial_apply(Expr::partial_apply(e, S.dup()), K.dup()); | |
482 } else { | |
483 e = Expr::partial_apply(S.dup(), Expr::partial_apply(K.dup(), e)); | |
484 } | |
485 ch = f->getch(); | |
486 } while (ch == '0' || ch == '1'); | |
487 f->ungetch(ch); | |
488 return e; | |
489 } | |
490 default: | |
491 f->error("Invalid character!"); | |
492 } | |
493 return 0; | |
494 } | |
495 | |
496 | |
497 Expr* parse_manual_close(Stream* f, int expected_terminator) { | |
498 Expr* e = 0; | |
499 int peek; | |
500 while (peek = f->getch(), peek != ')' && peek != EOF) { | |
501 Expr* e2 = parse_expr(f, peek, false); | |
502 e = e ? Expr::partial_apply(e, e2) : e2; | |
503 } | |
504 if (peek != expected_terminator) { | |
505 f->error(peek == EOF ? "Premature end of program!" : "Unmatched trailing close-parenthesis!"); | |
506 } | |
507 if (e == 0) { | |
508 e = I.dup(); | |
509 } | |
510 return e; | |
511 } | |
512 | |
513 | |
514 static Expr* car(Expr* list) { | |
515 return Expr::partial_apply(list, K.dup()); | |
516 } | |
517 | |
518 static Expr* cdr(Expr* list) { | |
519 return Expr::partial_apply(list, KI.dup()); | |
520 } | |
521 | |
522 static int church2int(Expr* church) { | |
523 Expr* e = Expr::partial_apply(Expr::partial_apply(church, Inc.dup()), Zero.dup()); | |
524 g_expr = e; | |
525 int result = e->partial_eval()->to_number(); | |
526 if (result == -1) { | |
527 fputs("Runtime error: invalid output format (result was not a number)\n", stderr); | |
528 exit(3); | |
529 } | |
530 return result; | |
531 } | |
532 | |
533 | |
534 Expr* compose(Expr* f, Expr* g) { | |
535 return new Expr(Expr::S2, new Expr(Expr::K1, f), g); | |
536 } | |
537 | |
538 | |
539 Expr* append_program(Expr* old, Stream* stream) { | |
540 return compose(parse_manual_close(stream, EOF), old); | |
541 } | |
542 | |
543 | |
544 void usage() { | |
545 fputs( | |
546 "usage: lazy [-b] { -e program | program-file.lazy } *\n" | |
547 "\n" | |
548 " -b puts stdin and stdout into binary mode on systems that care\n" | |
549 " (i.e. Windows)\n" | |
550 "\n" | |
551 " -e program takes program code from the command line (like Perl's -e\n" | |
552 " switch)\n" | |
553 "\n" | |
554 " program-file.lazy name of file containing program code\n" | |
555 "\n" | |
556 " If more than one -e or filename argument is given, the programs will be\n" | |
557 " combined by functional composition (but in Unix pipe order, not mathematical-\n" | |
558 " notation order). If no -e or filename argument is given, the result is a\n" | |
559 " degenerate composition, i.e. the identity function.\n", stdout); | |
560 exit(0); | |
561 } | |
562 | |
563 | |
564 int main(int argc, char** argv) { | |
565 Expr* e = I.dup(); | |
566 for (int i=1; i<argc; ++i) { | |
567 if (argv[i][0] == '-') { | |
568 switch (argv[i][1]) { | |
569 case 0: | |
570 { | |
571 File f = File(stdin, "(standard input)"); | |
572 e = append_program(e, &f); | |
573 break; | |
574 } | |
575 case 'b': | |
576 #ifdef __WIN32 | |
577 setmode(fileno(stdin), O_BINARY); | |
578 setmode(fileno(stdout), O_BINARY); | |
579 #endif | |
580 break; | |
581 case 'e': | |
582 { | |
583 ++i; | |
584 if (i == argc) { | |
585 usage(); | |
586 } | |
587 StringStream s = StringStream(argv[i]); | |
588 e = append_program(e, &s); | |
589 break; | |
590 } | |
591 default: | |
592 usage(); | |
593 } | |
594 } else { | |
595 FILE* f = fopen(argv[i], "r"); | |
596 if (!f) { | |
597 fprintf(stderr, "Unable to open the file \"%s\".\n", argv[i]); | |
598 exit(1); | |
599 } | |
600 File ff = File(f, argv[i]); | |
601 e = append_program(e, &ff); | |
602 } | |
603 } | |
604 e = Expr::partial_apply(e, new Expr(Expr::LazyRead)); | |
605 for (;;) { | |
606 int ch = church2int(car(e->dup())); | |
607 if (ch >= 256) | |
608 return ch-256; | |
609 putchar(ch); | |
610 e = cdr(e); | |
611 } | |
612 } |