1 ## experimental compiler to translate programs written in a generic
2 ## expression-oriented language called 'lambda' into Mu
4 # incomplete; code generator not done
5 # potential enhancements:
8 # substitute one instruction with multiple, parameterized by inputs and products
10 scenario convert-lambda [
13 1:text/raw <- lambda-to-mu [(add a (multiply b c))]
14 2:@:char/raw <- copy *1:text/raw
16 memory-should-contain [
17 2:array:character <- [t1 <- multiply b c
22 def lambda-to-mu in:text -> out:text [
26 cells:&:cell <- parse in
30 # 'parse' will turn lambda expressions into trees made of cells
31 exclusive-container cell [
36 # printed below as < first | rest >
42 def new-atom name:text -> result:&:cell [
45 result <- new cell:type
46 *result <- merge 0/tag:atom, name
49 def new-pair a:&:cell, b:&:cell -> result:&:cell [
52 result <- new cell:type
53 *result <- merge 1/tag:pair, a/first, b/rest
56 def is-atom? x:&:cell -> result:bool [
59 return-unless x, false
60 _, result <- maybe-convert *x, atom:variant
63 def is-pair? x:&:cell -> result:bool [
66 return-unless x, false
67 _, result <- maybe-convert *x, pair:variant
70 scenario atom-is-not-pair [
73 x:&:cell <- new-atom s
74 10:bool/raw <- is-atom? x
75 11:bool/raw <- is-pair? x
76 memory-should-contain [
82 scenario pair-is-not-atom [
86 x:&:cell <- new-atom s
87 y:&:cell <- new-pair x, null
88 10:bool/raw <- is-atom? y
89 11:bool/raw <- is-pair? y
90 memory-should-contain [
96 def atom-match? x:&:cell, pat:text -> result:bool [
99 s:text, is-atom?:bool <- maybe-convert *x, atom:variant
100 return-unless is-atom?, false
101 result <- equal pat, s
104 scenario atom-match [
106 x:&:cell <- new-atom [abc]
107 10:bool/raw <- atom-match? x, [abc]
108 memory-should-contain [
113 def first x:&:cell -> result:&:cell [
116 pair:pair, pair?:bool <- maybe-convert *x, pair:variant
117 return-unless pair?, null
118 result <- get pair, first:offset
121 def rest x:&:cell -> result:&:cell [
124 pair:pair, pair?:bool <- maybe-convert *x, pair:variant
125 return-unless pair?, null
126 result <- get pair, rest:offset
129 def set-first base:&:cell, new-first:&:cell -> base:&:cell [
132 pair:pair, is-pair?:bool <- maybe-convert *base, pair:variant
133 return-unless is-pair?
134 pair <- put pair, first:offset, new-first
135 *base <- merge 1/pair, pair
138 def set-rest base:&:cell, new-rest:&:cell -> base:&:cell [
141 pair:pair, is-pair?:bool <- maybe-convert *base, pair:variant
142 return-unless is-pair?
143 pair <- put pair, rest:offset, new-rest
144 *base <- merge 1/pair, pair
147 scenario cell-operations-on-atom [
150 x:&:cell <- new-atom s
151 10:&:cell/raw <- first x
152 11:&:cell/raw <- rest x
153 memory-should-contain [
154 10 <- 0 # first is nil
155 11 <- 0 # rest is nil
159 scenario cell-operations-on-pair [
161 # construct (a . nil)
163 x:&:cell <- new-atom s
164 y:&:cell <- new-pair x, null
166 10:bool/raw <- equal x, x2
167 11:&:cell/raw <- rest y
168 memory-should-contain [
169 10 <- 1 # first is correct
170 11 <- 0 # rest is nil
174 ## convert lambda text to a tree of cells
176 def parse in:text -> out:&:cell [
179 s:&:stream:char <- new-stream in
181 trace 2, [app/parse], out
184 def parse in:&:stream:char -> out:&:cell, in:&:stream:char [
188 in <- skip-whitespace in
189 c:char, eof?:bool <- peek in
191 pair?:bool <- equal c, 40/open-paren
195 buf:&:buffer:char <- new-buffer 30
197 done?:bool <- end-of-stream? in
199 # stop before close paren or space
201 done? <- equal c, 41/close-paren
209 s:text <- buffer-to-array buf
215 read in # skip the open-paren
216 out <- new cell:type # start out with nil
217 # read in first element of pair
219 end?:bool <- end-of-stream? in
220 not-end?:bool <- not end?
221 assert not-end?, [unbalanced '(' in expression]
223 close-paren?:bool <- equal c, 41/close-paren
224 break-if close-paren?
225 first:&:cell, in <- parse in
226 *out <- merge 1/pair, first, null
228 # read in any remaining elements
229 curr:&:cell <- copy out
231 in <- skip-whitespace in
232 end?:bool <- end-of-stream? in
233 not-end?:bool <- not end?
234 assert not-end?, [unbalanced '(' in expression]
235 # termination check: ')'
238 close-paren?:bool <- equal c, 41/close-paren
239 break-unless close-paren?
243 # still here? read next element of pair
244 next:&:cell, in <- parse in
245 is-dot?:bool <- atom-match? next, [.]
248 next-curr:&:cell <- new-pair next, null
249 curr <- set-rest curr, next-curr
254 # deal with dotted pair
255 in <- skip-whitespace in
257 not-close-paren?:bool <- not-equal c, 41/close-paren
258 assert not-close-paren?, [')' cannot immediately follow '.']
259 final:&:cell <- parse in
260 curr <- set-rest curr, final
261 # we're not gonna update curr, so better make sure the next iteration
262 # is going to end the pair
263 in <- skip-whitespace in
265 close-paren?:bool <- equal c, 41/close-paren
266 assert close-paren?, ['.' must be followed by exactly one expression before ')']
274 def skip-whitespace in:&:stream:char -> in:&:stream:char [
278 done?:bool <- end-of-stream? in
279 return-if done?, null
281 space?:bool <- space? c
288 def to-text x:&:cell -> out:text [
291 buf:&:buffer:char <- new-buffer 30
292 buf <- to-buffer x, buf
293 out <- buffer-to-array buf
296 def to-buffer x:&:cell, buf:&:buffer:char -> buf:&:buffer:char [
299 # base case: empty cell
302 buf <- append buf, [<>]
307 s:text, atom?:bool <- maybe-convert *x, atom:variant
312 # recursive case: pair
313 buf <- append buf, [< ]
314 first:&:cell <- first x
315 buf <- to-buffer first, buf
316 buf <- append buf, [ | ]
317 rest:&:cell <- rest x
318 buf <- to-buffer rest, buf
319 buf <- append buf, [ >]
322 scenario parse-single-letter-atom [
326 s2:text, 10:bool/raw <- maybe-convert *x, atom:variant
327 11:@:char/raw <- copy *s2
328 memory-should-contain [
329 10 <- 1 # parse result is an atom
330 11:array:character <- [a]
334 scenario parse-atom [
338 s2:text, 10:bool/raw <- maybe-convert *x, atom:variant
339 11:@:char/raw <- copy *s2
340 memory-should-contain [
341 10 <- 1 # parse result is an atom
342 11:array:character <- [abc]
346 scenario parse-list-of-two-atoms [
348 s:text <- new [(abc def)]
350 trace-should-contain [
351 app/parse: < abc | < def | <> > >
353 10:bool/raw <- is-pair? x
356 s1:text, 11:bool/raw <- maybe-convert *x1, atom:variant
357 12:bool/raw <- is-pair? x2
358 x3:&:cell <- first x2
359 s2:text, 13:bool/raw <- maybe-convert *x3, atom:variant
360 14:&:cell/raw <- rest x2
361 20:@:char/raw <- copy *s1
362 30:@:char/raw <- copy *s2
363 memory-should-contain [
364 10 <- 1 # parse result is a pair
365 11 <- 1 # result.first is an atom
366 12 <- 1 # result.rest is a pair
367 13 <- 1 # result.rest.first is an atom
368 14 <- 0 # result.rest.rest is nil
369 20:array:character <- [abc] # result.first
370 30:array:character <- [def] # result.rest.first
374 scenario parse-list-with-extra-spaces [
376 s:text <- new [ ( abc def ) ] # extra spaces
378 trace-should-contain [
379 app/parse: < abc | < def | <> > >
381 10:bool/raw <- is-pair? x
384 s1:text, 11:bool/raw <- maybe-convert *x1, atom:variant
385 12:bool/raw <- is-pair? x2
386 x3:&:cell <- first x2
387 s2:text, 13:bool/raw <- maybe-convert *x3, atom:variant
388 14:&:cell/raw <- rest x2
389 20:@:char/raw <- copy *s1
390 30:@:char/raw <- copy *s2
391 memory-should-contain [
392 10 <- 1 # parse result is a pair
393 11 <- 1 # result.first is an atom
394 12 <- 1 # result.rest is a pair
395 13 <- 1 # result.rest.first is an atom
396 14 <- 0 # result.rest.rest is nil
397 20:array:character <- [abc] # result.first
398 30:array:character <- [def] # result.rest.first
402 scenario parse-list-of-more-than-two-atoms [
404 s:text <- new [(abc def ghi)]
406 trace-should-contain [
407 app/parse: < abc | < def | < ghi | <> > > >
409 10:bool/raw <- is-pair? x
412 s1:text, 11:bool/raw <- maybe-convert *x1, atom:variant
413 12:bool/raw <- is-pair? x2
414 x3:&:cell <- first x2
415 s2:text, 13:bool/raw <- maybe-convert *x3, atom:variant
417 14:bool/raw <- is-pair? x4
418 x5:&:cell <- first x4
419 s3:text, 15:bool/raw <- maybe-convert *x5, atom:variant
420 16:&:cell/raw <- rest x4
421 20:@:char/raw <- copy *s1
422 30:@:char/raw <- copy *s2
423 40:@:char/raw <- copy *s3
424 memory-should-contain [
425 10 <- 1 # parse result is a pair
426 11 <- 1 # result.first is an atom
427 12 <- 1 # result.rest is a pair
428 13 <- 1 # result.rest.first is an atom
429 14 <- 1 # result.rest.rest is a pair
430 15 <- 1 # result.rest.rest.first is an atom
431 16 <- 0 # result.rest.rest.rest is nil
432 20:array:character <- [abc] # result.first
433 30:array:character <- [def] # result.rest.first
434 40:array:character <- [ghi] # result.rest.rest
438 scenario parse-nested-list [
440 s:text <- new [((abc))]
442 trace-should-contain [
443 app/parse: < < abc | <> > | <> >
445 10:bool/raw <- is-pair? x
447 11:bool/raw <- is-pair? x
448 x2:&:cell <- first x1
449 s1:text, 12:bool/raw <- maybe-convert *x2, atom:variant
450 13:&:cell/raw <- rest x1
451 14:&:cell/raw <- rest x
452 20:@:char/raw <- copy *s1
453 memory-should-contain [
454 10 <- 1 # parse result is a pair
455 11 <- 1 # result.first is a pair
456 12 <- 1 # result.first.first is an atom
457 13 <- 0 # result.first.rest is nil
458 14 <- 0 # result.rest is nil
459 20:array:character <- [abc] # result.first.first
463 scenario parse-nested-list-2 [
465 s:text <- new [((abc) def)]
467 trace-should-contain [
468 app/parse: < < abc | <> > | < def | <> > >
470 10:bool/raw <- is-pair? x
472 11:bool/raw <- is-pair? x
473 x2:&:cell <- first x1
474 s1:text, 12:bool/raw <- maybe-convert *x2, atom:variant
475 13:&:cell/raw <- rest x1
477 x4:&:cell <- first x3
478 s2:text, 14:bool/raw <- maybe-convert *x4, atom:variant
479 15:&:cell/raw <- rest x3
480 20:@:char/raw <- copy *s1
481 30:@:char/raw <- copy *s2
482 memory-should-contain [
483 10 <- 1 # parse result is a pair
484 11 <- 1 # result.first is a pair
485 12 <- 1 # result.first.first is an atom
486 13 <- 0 # result.first.rest is nil
487 14 <- 1 # result.rest.first is an atom
488 15 <- 0 # result.rest.rest is nil
489 20:array:character <- [abc] # result.first.first
490 30:array:character <- [def] # result.rest.first
494 # todo: uncomment these tests after we figure out how to continue tests after
496 #? scenario parse-error [
500 #? x:&:cell <- parse s
502 #? trace-should-contain [
503 #? error: unbalanced '(' in expression
507 #? scenario parse-error-after-element [
509 #? s:text <- new [(abc]
511 #? x:&:cell <- parse s
513 #? trace-should-contain [
514 #? error: unbalanced '(' in expression
518 scenario parse-dotted-list-of-two-atoms [
520 s:text <- new [(abc . def)]
522 trace-should-contain [
523 app/parse: < abc | def >
525 10:bool/raw <- is-pair? x
528 s1:text, 11:bool/raw <- maybe-convert *x1, atom:variant
529 s2:text, 12:bool/raw <- maybe-convert *x2, atom:variant
530 20:@:char/raw <- copy *s1
531 30:@:char/raw <- copy *s2
532 memory-should-contain [
533 # parses to < abc | def >
534 10 <- 1 # parse result is a pair
535 11 <- 1 # result.first is an atom
536 12 <- 1 # result.rest is an atom
537 20:array:character <- [abc] # result.first
538 30:array:character <- [def] # result.rest
542 scenario parse-dotted-list-of-more-than-two-atoms [
544 s:text <- new [(abc def . ghi)]
546 trace-should-contain [
547 app/parse: < abc | < def | ghi > >
549 10:bool/raw <- is-pair? x
552 s1:text, 11:bool/raw <- maybe-convert *x1, atom:variant
553 12:bool/raw <- is-pair? x2
554 x3:&:cell <- first x2
555 s2:text, 13:bool/raw <- maybe-convert *x3, atom:variant
557 s3:text, 14:bool/raw <- maybe-convert *x4, atom:variant
558 20:@:char/raw <- copy *s1
559 30:@:char/raw <- copy *s2
560 40:@:char/raw <- copy *s3
561 memory-should-contain [
562 10 <- 1 # parse result is a pair
563 11 <- 1 # result.first is an atom
564 12 <- 1 # result.rest is a pair
565 13 <- 1 # result.rest.first is an atom
566 14 <- 1 # result.rest.rest is an atom
567 20:array:character <- [abc] # result.first
568 30:array:character <- [def] # result.rest.first
569 40:array:character <- [ghi] # result.rest.rest
573 ## convert tree of cells to Mu text
575 def to-mu in:&:cell -> out:text [
578 buf:&:buffer:char <- new-buffer 30
580 out <- buffer-to-array buf
583 def to-mu in:&:cell, buf:&:buffer:char -> buf:&:buffer:char, result-name:text [
586 # null cell? no change.
587 # pair with all atoms? gensym a new variable
588 # pair containing other pairs? recurse
589 result-name <- copy null