/
expander.scm
2844 lines (2558 loc) · 122 KB
/
expander.scm
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
;;;=================================================================================
;;;
;;; R6RS Macros and R6RS libraries:
;;;
;;; Copyright (c) 2006 Andre van Tonder
;;;
;;; Copyright statement at http://srfi.schemers.org/srfi-process.html
;;;
;;;=================================================================================
;;;
;;;=================================================================================
;;;
;;; PORTING COMMENTS:
;;;
;;;=================================================================================
;;;
;;; The file compat-*.scm has to be loaded before loading this expander.
;;;
;;; Compat-*.scm should supply whatever is missing from your implementation of
;;; the following.
;;;
;;; NOTE: A purely r5rs approximation is provided that can be used
;;; as a customization template.
;;;
;;; - Procedures assertion-violation, memp, filter, for-all, pretty-print,
;;; file-exists? and delete-file.
;;; - Procedures make-record-type-descriptor, make-record-constructor-descriptor,
;;; record-constructor, record-predicate and record-accessor.
;;; - Procedure (ex:unique-token) that provides a numeric GUID string once per run.
;;; - Single-character string ex:guid-prefix. No builtin may start with this.
;;; - Single-character string ex:free-prefix. No builtin may start with this.
;;; - Value ex:undefined representing the letrec black hole value.
;;; - Symbol ex:undefined-set! representing the corresponding setter.
;;;
;;; HOOKS:
;;; ------
;;;
;;; For compiler and REPL integration, see the procedures
;;;
;;; - ex:repl : Use this as REPL evaluator. See description below.
;;; - ex:expand-file : Use this to expand a file containing libraries and/or
;;; toplevel programs before loading into an r5rs-type system
;;; or feeding result to an r5rs-type compiler.
;;; Suitable for separate compilation.
;;; - ex:run-r6rs-sequence : Evaluates a sequence of forms of the format
;;; <library>* | <library>* <toplevel program>.
;;; The <toplevel program> environment is separate from the
;;; interactive REPL environment and does not persist
;;; between invocations of run-r6rs-sequence.
;;; For importing and evaluating stuff in the persistent
;;; interactive environment, ex:REPL should be used instead.
;;; - ex:run-r6rs-program : Same as ex:run-r6rs-sequence, except that it reads the
;;; input from a file.
;;; - ex:expand-r5rs-file : For expanding r5rs-like toplevel files in a given environment.
;;; Mainly provided so this expander can expand itself, but may
;;; have other uses. See the documentation below where the
;;; procedure is defined. See also the note below on
;;; metacircularity.
;;;
;;; COMPILATION:
;;; ------------
;;;
;;; Example compilation scripts can be seen in examples.scm.
;;; The expander expands everything to r5rs toplevel definitions
;;; and expressions, so the expanded code should be compilable
;;; with an r5rs compiler.
;;;
;;; REPL:
;;; -----
;;;
;;; Example REPL interaction can be seen in examples.scm.
;;;
;;; The REPL goes beyond r6rs to allow incremental development in
;;; a toplevel environment.
;;; The developer can freely change, replace and make new toplevel
;;; definitions, evaluate toplevel expressions, enter libraries and
;;; <toplevel programs> at the prompt, as well as import libraries
;;; into the toplevel environment.
;;;
;;; EX:REPL evaluates a sequence of library definitions, commands, and top-level
;;; import forms in the interactive environment. The semantics for
;;; evaluating libraries in and importing bindings into the interactive
;;; environment is consistent with the ERR5RS proposal at
;;; http://scheme-punks.cyber-rush.org/wiki/index.php?title=ERR5RS:Libraries.
;;; Bindings in the interactive environment persist between invocations
;;; of REPL.
;;;
;;; An example session where I do all these things is in examples.scm.
;;; All an integrator would need to do is to automate the call to
;;; ex:repl in the development system so users don't have to type
;;; (ex:repl '( <code> )) at each prompt.
;;;
;;; FORMAT OF EXPANDED CODE:
;;; ------------------------
;;;
;;; We expand internal and library definitions, as well as letrec and letrec*
;;; completely to lambda and set! (or more accurately, whatever ex:undefined-set!
;;; is set to). This seems to be the preferred input format for Larceny.
;;; It would be very easy to abstract or change, but we don't bother for now
;;; until implementors other than Larceny show a serious interest.
;;;
;;; METACIRCULARITY AND BOOTSTRAPPING:
;;; ----------------------------------
;;;
;;; This section is mostly of interest for r5rs non-compliant systems.
;;;
;;; The expander relies on r5rs (or r6rs) syntax-rules and letrec-syntax
;;; and should run in a correct r5rs system, but if you don't have
;;; r5rs macros, you may bootstrap it by expanding the expander itself
;;; first on an R5RS system.
;;; Here is how to do it:
;;;
;;; (load "compat-mzscheme.scm") ; for example bootstrapping from mzscheme
;;; (load "runtime.scm")
;;; (load "expander.scm")
;;; (ex:expand-file "standard-libraries.scm" "standard-libraries.exp")
;;; (ex:expand-r5rs-file "expander.scm" "expander.exp" (ex:environment '(rnrs base)))
;;;
;;; The expanded (.exp) files are vanilla Scheme and can then be run on the target
;;; system as follows:
;;;
;;; (load "compat-chez.scm") ; for example
;;; (load "runtime.scm")
;;; (load "standard-libraries.exp")
;;; (load "expander.exp")
;;;
;;; SIZE OF OBJECT CODE:
;;; --------------------
;;;
;;; The minimal runtime prerequisites has been separated into a small
;;; include file runtime.scm, which is all that needs to be present for
;;; executing an expanded program that does not contain runtime
;;; uses the exports of (rnrs syntax-case) or (rnrs eval).
;;; See examples.scm for demonstrations of this.
;;;
;;; Expanded libraries may contain identifier environment information
;;; and visit code that could adversely affect the runtime binary size.
;;; This is not a big problem, for several reasons:
;;; First, note that this information is only present in libraries that
;;; define macros.
;;; Second, the size of the environments saved in the object code can
;;; usually be reduced dramatically by using 'only' imports.
;;; Third, the environments, as well as the visit code, can be discarded
;;; completely from the runtime image of a fully expanded program not
;;; using (rnrs syntax-case) or (rnrs eval) at runtime. It is very
;;; easy to write a little build script that does this.
;;;
;;; The only reason for including this information now in the object code
;;; of a library is to support separate compilation, so one can expand a
;;; library in one session and use macros from the /expanded/ library to
;;; expand another library or program in a new session. The customization
;;; to get rid of separate compilation, if desired, would be trivial.
;;=================================================================================
;;
;; IMPORTS:
;;
;;=================================================================================
;;
;; The include file runtime.scm has to be loaded before loading this expander
;;
;;=================================================================================
;;
;; EXPORTS:
;;
;;=================================================================================
;; Direct exports:
(define ex:make-variable-transformer #f)
(define ex:identifier? #f)
(define ex:bound-identifier=? #f)
(define ex:free-identifier=? #f)
(define ex:generate-temporaries #f)
(define ex:datum->syntax #f)
(define ex:syntax->datum #f)
(define ex:environment #f)
(define ex:environment-bindings #f)
(define ex:eval #f)
(define ex:load #f)
(define ex:syntax-violation #f)
;; System exports:
(define ex:expand-file #f)
(define ex:expand-sequence #f) ;NMOSH
(define ex:expand-sequence-r5rs #f) ;NMOSH
(define ex:expand-sequence/debug #f) ;NMOSH
(define ex:current-program #f)
(define ex:expand-sequence-r5rs/debug #f);NMOSH
(define ex:generate-guid #f) ;NMOSH
(define ex:interaction-environment #f) ;NMOSH
(define ex:current-environment #f) ;NMOSH
(define ex:destructive-eval! #f) ;NMOSH
(define ex:destructive-expand! #f) ;NMOSH
(define ex:repl #f)
(define ex:expand-r5rs-file #f)
(define ex:run-r6rs-sequence #f)
(define ex:run-r6rs-program #f)
;; Indirect exports:
(define ex:invalid-form #f)
(define ex:register-macro! #f)
(define ex:syntax-rename #f)
(define ex:map-while #f)
(define ex:dotted-length #f)
(define ex:dotted-butlast #f)
(define ex:dotted-last #f)
(define ex:uncompress #f)
(define ex:free=? #f)
(define ex:expander-loaded? #t) ;NMOSH
(define ex:expander-unload! #f) ;NMOSH
(letrec-syntax
;; Not everyone has the same parameter API:
((fluid-let
(syntax-rules ()
((fluid-let () be ...)
(begin be ...))
((fluid-let ((p0 e0) (p e) ...) be ...)
(let ((saved p0))
(set! p0 e0)
(call-with-values (lambda ()
(fluid-let ((p e) ...) be ...))
(lambda results
(set! p0 saved)
(apply values results)))))))
;; A trivial but extremely useful s-expression matcher.
;; Implements a subset of Wright's matcher's patterns.
;; Includes additional (syntax id) pattern that matches
;; if input is identifier? and free=? to 'id.
(match
(syntax-rules ()
((match (op arg ...) clause ...)
(let ((x (op arg ...)))
(match x clause ...)))
((match x)
(ex:invalid-form x))
((match x (pat e ...) clause ...)
(matcher "base" pat "done" x (e ...) (lambda () (match x clause ...))))))
(matcher
(syntax-rules (- ___ ? syntax)
((matcher "base" () k arg ...)
(matcher k (lambda (x sk fk) (if (null? x) (sk) (fk))) () arg ...))
((matcher "base" - k arg ...)
(matcher k (lambda (x sk fk) (sk)) () arg ...))
((matcher "base" (syntax id) k arg ...)
(matcher k
(lambda (x sk fk)
(if (ex:free=? x 'id) (sk) (fk)))
()
arg ...))
((matcher "base" (? pred? p) k arg ...)
(matcher "base" p "predicate" pred? k arg ...))
((matcher "predicate" code vars pred? k arg ...)
(matcher k
(lambda (x sk fk)
(if (pred? x)
(code x sk fk)
(fk)))
vars
arg ...))
((matcher "base" (p1 ___ tailp ...) k arg ...)
(matcher "base" p1 "ellipses" (tailp ...) k arg ...))
((matcher "ellipses" code vars (tailp ...) k arg ...)
(matcher k
(lambda (x sk fk)
(let loop ((x x)
(result '()))
(define (match-tail)
(match x
((tailp ...)
(apply sk (if (null? result)
(map (lambda (ignore) '()) 'vars)
(apply map list (reverse result)))))
(- (fk))))
(cond ((null? x) (match-tail))
((pair? x)
(code (car x)
(lambda car-vars
(loop (cdr x) (cons car-vars result)))
match-tail))
(else (fk)))))
vars
arg ...))
((matcher "base" (p1 . p2) k arg ...)
(matcher "base" p1 "pair" p2 k arg ...))
((matcher "pair" car-code car-vars p2 k arg ...)
(matcher "base" p2 "pair-done" car-code car-vars k arg ...))
((matcher "pair-done" cdr-code (cdr-var ...) car-code (car-var ...) k arg ...)
(matcher k
(lambda (x sk fk)
(if (pair? x)
(car-code (car x)
(lambda (car-var ...)
(cdr-code (cdr x)
(lambda (cdr-var ...)
(sk car-var ... cdr-var ...))
fk))
fk)
(fk)))
(car-var ... cdr-var ...)
arg ...))
((matcher "base" #(p ___) k arg ...)
(matcher "base" (p ___) "vector" k arg ...))
((matcher "vector" list-code vars k arg ...)
(matcher k
(lambda (x sk fk)
(if (vector? x)
(list-code (vector->list x)
sk
fk)
(fk)))
vars
arg ...))
((matcher "base" id k arg ...)
(matcher k (lambda (x sk fk) (sk x)) (id) arg ...))
((matcher "done" code vars x (e ...) fk)
(code x (lambda vars e ...) fk)))))
(let* (;;==========================================================================
;;
;; Dynamic parameters:
;;
;;==========================================================================
;; toplevel REPL bindings to be initialized later
(*toplevel-env* #f)
;; current lexical environment to be initialized later
(*usage-env* #f)
;; current phase
(*phase* 0)
;; current color for painting identifiers upon renaming to be initialized
(*color* #f)
;; global table mapping <binding name> of keyword to <macro> object
(*macro-table* '())
;; maps <symbolic key> of reflected environment to actual <environment>
(*env-table* '())
;; current library name as list of symbols or '() for toplevel
(*current-library* '())
;; car of this records bindings already referenced in current body
;; for detecting when later definitions may violate lexical scope
(*used* (list '()))
;; history trace for error reporting
(*trace* '())
;; whether expanded library introduces identifiers via syntax
;; expressions - if not, save lots of space by not including
;; env-table in object code
(*syntax-reflected* #f)
;;MOSH
;; generated programs (alist)
(*programs* '())
;;
(*current-program* "<user program>")
(*current-program-valid?* #f)
;; additional depenents
(*cache-depfiles* '())
;; whether to save cache file
(*cache-save?* #t)
;; whether to install current library
(*library-install?* #t)
;; save debug symbol?
(*DBG?* #f)
;; debug symbol table
(*DBG-SYMS* '())
;;==========================================================================
;;
;; Identifiers:
;;
;;==========================================================================
;; <name> ::= <symbol>
;; <colors> ::= (<color> ...)
;; <transformer-envs> ::= (<env> ...)
;; <displacement> ::= <integer>
;; <maybe-library> ::= (<symbol> ...) | #f
;;
;; where
;; <name> : The symbolic name of the identifier in the source.
;; <colors> : Each time an introduced identifier is renamed, a fresh
;; color gets prepended to its <colors>.
;; <transformer-envs> : List of reflected transformer environments.
;; The environment (env-reify (car <transformer-envs>)) was the
;; usage environment valid during expansion of any (syntax id)
;; expression whose evaluation introduced this identifier, while
;; (cdr <transformer-envs>) are in turn the reflected
;; <transformer-envs> of the original id.
;; <displacement> : Integer that keeps track of shifts in phases
;; between transformer and usage sites of identifier.
;; <maybe-library> : Library name if identifier was introduced by evaluation of
;; a (syntax ...) expression, otherwise #f.
;; The empty name '() is used for toplevel.
;;
;; MOSH: identifiers also have <dbg>, syntactic object source info.
;; MOSH: we use vectors for this purpose.
;; identifier constructor and accessor moved to runtime.scm
;; (these will used by syntax error pretty printer)
)
;; We sequenced stuff in the let* above because r5rs internal
;; definitions use letrec semantics and cannot be used for sequencing.
(define (id-library id)
(or (id-maybe-library id)
*current-library*))
(define (bound-identifier=? x y)
(check x identifier? 'bound-identifier=?)
(check y identifier? 'bound-identifier=?)
(and (eq? (id-name x)
(id-name y))
(equal? (id-colors x)
(id-colors y))))
;; As required by r6rs, when this returns, the result is #t
;; if and only if the two identifiers resolve to the same binding.
;; It also treats unbound identifiers specially.
;; As allowed by R6RS, included phase checking of arguments.
;; An out of phase error is raised if the comparison succeeds but
;; either argument is out of phase. This is sufficient to ensure
;; that literals such as ... in syntax-case are used in the correct phase.
;; For more dicussion on this choice, see the readme and the examples file.
(define (free-identifier=? x y)
(check x identifier? 'free-identifier=?)
(check y identifier? 'free-identifier=?)
(let ((bx (binding x))
(by (binding y)))
(let ((result (if bx
(and by
(eq? (binding-name bx)
(binding-name by)))
(and (not by)
(eq? (id-name x)
(id-name y))))))
(and result
bx
(begin (check-binding-level x bx)
(check-binding-level y by)))
;; A later definition in the same body can only change
;; #t to #f, so only record usage in that case.
(and result
(register-use! x bx)
(register-use! y by))
result)))
;; For internal use
(define (free=? x symbol)
(and (identifier? x)
(let ((bx (binding x)))
(let ((result
(and bx
(eq? (binding-name bx) symbol))))
(and result
bx
(check-binding-level x bx))
(and result
(register-use! x bx))
result))))
;;==========================================================================
;;
;; Infrastructure for generated names:
;;
;;==========================================================================
;; Generate-guid returns a fresh symbol that has a globally
;; unique external representation and is read-write invariant.
;; Your local gensym will probably not satisfy both conditions.
;; Prefix makes it disjoint from all builtins.
;; Uniqueness is important for incremental and separate expansion.
(define generate-guid
(let ((token (ex:unique-token))
(ticks 0))
(lambda (symbol)
(set! ticks (+ ticks 1))
(string->symbol
(string-append ex:guid-prefix
(symbol->string symbol)
"~"
token
"~"
(number->string ticks))))))
;; Used to generate user program toplevel names.
;; Prefix makes it disjoint from all builtins.
;; Prefix makes it disjoint from output of generate-guid.
;; Must be read-write invariant.
(define (make-free-name symbol)
(string->symbol (string-append ex:free-prefix (symbol->string symbol))))
;;=========================================================================
;;
;; Colors to paint identifiers with:
;;
;;=========================================================================
;; Returns <color> ::= globally unique symbol
(define (generate-color)
(generate-guid 'c))
;;=========================================================================
;;
;; Bindings:
;;
;;=========================================================================
;; <binding> ::= (variable <binding-name> (<level> ...) <mutable?> <library-name>)
;; | (macro <binding-name> (<level> ...) #f <library-name>)
;; | (pattern-variable <binding-name> (<level> ...) <dimension> <library-name>)
;; | #f (out of context binding from another library)
;; <mutable> ::= #t | #f
;; <dimension> ::= 0 | 1 | 2 | ...
;; <binding-name> ::= <symbol> uniquely identifying binding.
;; <binding-name> is used for free-identifier=? comparison.
;; For variable and pattern variable bindings, it is the same
;; as the symbol emitted for the binding in the object code.
;; For macro bindings, it is the key for looking up the transformer
;; in the global macro table.
;; MOSH: use vector for binding
(define (make-binding/debug type name levels content library dbg)
(if (and *DBG?* dbg) (set! *DBG-SYMS* (cons (cons name dbg) *DBG-SYMS*)))
(vector type name levels content library))
(define (make-binding type name levels content library)
(make-binding/debug type name levels content library #f))
(define (binding-type b) (vector-ref b 0))
(define (binding-name b) (vector-ref b 1))
(define (binding-levels b) (vector-ref b 2))
(define (binding-mutable? b) (vector-ref b 3))
(define (binding-dimension b) (vector-ref b 3))
(define (binding-library b) (vector-ref b 4))
(define (binding-mutable-set! b x) (vector-set! b 3 x))
;; Looks up binding first in usage environment and
;; then in attached transformer environments.
;; Toplevel forward references are treated specially.
;; Returns <binding> | #f if unbound.
(define (binding id)
(let ((name (id-name id)))
(define (binding-loop env envs colors)
(let ((r (env-lookup (cons name colors) env)))
(if r
r
(if (pair? envs)
(binding-loop (env-reify (car envs)) (cdr envs) (cdr colors))
#f))))
(binding-loop *usage-env* (id-transformer-envs id) (id-colors id))))
;;=========================================================================
;;
;; Mapping in environment: ((<name> <color> ...) . <binding>)
;;
;;=========================================================================
;; Generates a local mapping at the current meta-level
;; that can be added to the usage environment.
(define (make-local-mapping type id content)
(cons (cons (id-name id)
(id-colors id))
(make-binding/debug type
(generate-guid (id-name id))
(list (source-level id))
content
*current-library*
(id-debug id))))
;; Toplevel binding forms use as binding name the free name
;; so that source-level forward references will work in REPL.
;; If identifier is macro-generated, bind it with a fresh name.
;; This ensures that generated toplevel defines are not visible
;; from toplevel source code, thus approximating the behaviour
;; of generated internal definitions.
(define (make-toplevel-mapping type id content)
(if (null? (id-colors id))
(cons (cons (id-name id)
(id-colors id))
(make-binding type
(make-free-name (id-name id))
'(0)
content
*current-library*))
(make-local-mapping type id content)))
;;=========================================================================
;;
;; Infrastructure for binding levels:
;;
;;=========================================================================
; MOSH: show program name instead of internal library name
(define (library-string l)
(if (pair? l)
(let* ((a (car l))
(p (assq a *programs*)))
(if p
(string-append "program " (cdr p))
(string-append "library (" (list->string l " ") ")")))
"<unknown origin>"))
(define (source-level id)
(- *phase* (id-displacement id)))
(define (check-binding-level id binding)
(if binding
(or (memv (source-level id)
(binding-levels binding))
(syntax-violation
"invalid reference"
(string-append "Attempt to use binding of [" (symbol->string (id-name id))
"] in " (library-string (id-library id))
" at invalid level " (number->string (source-level id))
". Binding is only available at levels: "
(list->string (binding-levels binding) " "))
id))
(or (and (null? (id-library id))
(= *phase* 0))
(syntax-violation
"invalid reference"
(string-append "No binding available for [" (symbol->string (id-name id))
"] in " (library-string (id-library id)))
id))))
;;=========================================================================
;;
;; Environments:
;;
;;=========================================================================
;; An environment is a list of frames.
;;
;; <environment> ::= (<frame> ...)
;; <frame> ::= (list ((<key> . <value>) ...))
;;
;; Keys must be comparable with equal? and unique in each frame.
;; Frames can be added, or the leftmost frame can be destructively
;; updated in the case of binding constructs such as bodies where
;; definitions are incrementally discovered.
(define (make-null-env) '())
(define (make-unit-env) (env-extend '() (make-null-env)))
;; MOSH: use hashtable
(define (env-extend-table! h mappings)
(define (step e)
(let* ((k (car e))
(b (cdr e))
(key0 (car k))
(key1 (cdr k))
(a (hashtable-ref h key0 '())))
(hashtable-set! h key0 (cons (cons key1 b) a))))
(for-each step mappings))
(define (env-maketable mappings)
(let ((h (make-eq-hashtable 10))) ;; MOSH: FIXME ..
(env-extend-table! h mappings)
h))
(define (env-assoc key table)
(let* ((key0 (car key))
(key1 (cdr key))
(a (hashtable-ref table key0 '())))
(assoc key1 a)))
;; Adds a new frame containing mappings to env.
(define (env-extend mappings env)
(cons (env-maketable mappings) env))
;; Destructively extends the leftmost frame in env.
(define (env-extend! mappings env)
(let ((frame (car env)))
(env-extend-table! frame mappings)))
;; Returns <object> | #f
(define (env-lookup key env)
(and (pair? env)
(or (let ((probe (env-assoc key (car env)))) ; MOSH:
(and probe
(or (cdr probe)
(syntax-violation
#f "Out of context reference to identifier" (car key)))))
(env-lookup key (cdr env)))))
;; Is id already bound in leftmost frame?
(define (duplicate? id env)
(env-assoc (cons (id-name id)
(id-colors id))
(car env))) ; MOSH:
;; Returns a single-symbol <key> representing an
;; environment that can be included in object code.
(define (env-reflect env)
(cond ((and (not (null? *env-table*)) ; +++
(eq? env (cdar *env-table*))) ; +++
(caar *env-table*)) ; +++
(else
(let ((key (generate-guid 'env)))
(set! *env-table*
(cons (cons key env)
*env-table*))
key))))
;; The inverse of the above.
;; MOSH: it can be #f when executing cached code
(define (env-reify key-or-env)
(if (symbol? key-or-env)
(let ((r (assq key-or-env *env-table*)))
(if r (cdr r) #f))
key-or-env))
;; This makes a much smaller external representation of an
;; environment table by factoring shared structure.
;; MOSH: original compress
(define (plist-compress env-table)
(let ((frame-table '())
(count 0))
(for-each (lambda (entry)
(for-each (lambda (frame)
(if (not (assq frame frame-table))
(begin
(set! frame-table (cons (cons frame count) frame-table))
(set! count (+ 1 count)))))
(cdr entry)))
env-table)
(cons (map (lambda (env-entry)
(cons (car env-entry)
(map (lambda (frame)
(cdr (assq frame frame-table)))
(cdr env-entry))))
env-table)
(map (lambda (frame-entry)
(cons (cdr frame-entry)
(list (map (lambda (mapping)
(cons (car mapping)
(let ((binding (cdr mapping)))
(case (binding-type binding)
;; Pattern variable bindings can never be
;; used in client, so don't waste space.
;; Should really do the same with all local
;; bindings, but there are usually much less
;; of them, so don't bother for now.
((pattern-variable) #f) ; +++
(else binding)))))
(caar frame-entry)))))
frame-table))))
(define (compress1 table)
(call-with-values (lambda () (hashtable-entries table))
(lambda (keys entries) (cons keys entries))))
(define (compress tables)
(define (step l)
(cons (car l) (map compress1 (cdr l))))
(map step tables))
;; MOSH: original uncompress
(define (plist-uncompress compressed-env-table)
(map (lambda (env-entry)
(cons (car env-entry)
(map (lambda (frame-abbrev)
(cdr (assv frame-abbrev (cdr compressed-env-table))))
(cdr env-entry))))
(car compressed-env-table)))
(define (uncompress1 table)
(let ((a (car table))
(d (cdr table)))
(let ((h (make-eq-hashtable)))
(define (step key ent)
(hashtable-set! h key ent))
(for-each step (vector->list a) (vector->list d))
h)))
(define (uncompress tables)
(define (step l)
(cons (car l) (map uncompress1 (cdr l))))
(map step tables))
;;=========================================================================
;;
;; Syntax-reflect and syntax-rename:
;;
;; This is the basic building block of the implicit renaming mechanism for
;; maintaining hygiene. Syntax-reflect generates the expanded code for
;; (syntax id), including the expand-time environment in the
;; external representation. It expands to syntax-rename, which performs
;; the implicit renaming when this expanded code is eventually run.
;; The displacement computations calculate the difference between the
;; usage phase and the transformer phase.
;;
;;=========================================================================
(define (syntax-reflect id)
(set! *syntax-reflected* #t)
`(ex:syntax-rename ',(id-name id)
',(id-colors id)
',(cons (env-reflect *usage-env*)
(id-transformer-envs id))
,(- (- *phase* (id-displacement id)) 1)
',(id-library id)))
(define (syntax-rename name colors transformer-envs transformer-phase source-library)
(make-identifier name
(cons *color* colors)
transformer-envs
(- *phase* transformer-phase)
source-library))
;;=====================================================================
;;
;; Capture and sexp <-> syntax conversions:
;;
;;=====================================================================
(define (datum->syntax tid datum)
(check tid identifier? 'datum->syntax)
(sexp-map/debug #f (lambda (leaf dbg)
(cond ((symbol? leaf)
(make-identifier/debug leaf
(id-colors tid)
(id-transformer-envs tid)
(id-displacement tid)
(id-maybe-library tid)
dbg))
(else leaf)))
datum))
(define (syntax->datum exp)
(sexp-map (lambda (leaf)
(cond ((identifier? leaf) (id-name leaf))
((symbol? leaf)
(assertion-violation 'syntax->datum "A symbol is not a valid syntax object" leaf))
(else leaf)))
exp))
;; Fresh identifiers:
(define (generate-temporaries ls)
(check ls list? 'generate-temporaries)
(map (lambda (ignore)
(make-identifier 'temp
(list (generate-color))
(list (make-null-env))
*phase*
#f))
ls))
;; For use internally as in the explicit renaming system.
(define (rename type symbol)
(make-identifier symbol
(list *color*)
(list (env-extend
(list (cons (cons symbol '())
(make-binding type symbol '(0) #f '())))
(make-null-env)))
*phase*
#f))
;;=========================================================================
;;
;; Macro objects:
;;
;;=========================================================================
;; Expanders are system macros that fully expand
;; their arguments to core Scheme, while
;; transformers and variable transformers are
;; user macros.
;; <type> ::= expander | transformer | variable-transformer
(define (make-macro type proc)
(list type proc))
(define macro-type car)
(define macro-proc cadr)
; MOSH: delayed macro eval
(define (macro-set-proc! macro proc)
(set-car! (cdr macro) proc))
(define (make-expander proc) (make-macro 'expander proc))
(define (make-transformer proc) (make-macro 'transformer proc))
(define (make-variable-transformer proc) (make-macro 'variable-transformer proc))
(define (make-delayed-user-macro code) ; MOSH: delayed
(if (eq? (car code) 'lambda)
(letrec ((r (list 'transformer
(lambda (t)
(let ((proc (eval-core code)))
(macro-set-proc! r proc)
(proc t))))))
r)
;; fallback (macro returning macro-code)
(make-user-macro (eval-core code))))
(define (make-user-macro procedure-or-macro)
(if (procedure? procedure-or-macro)
(make-transformer procedure-or-macro)
procedure-or-macro))
;; Returns <macro>.
(define (binding->macro binding t)
(cond ((assq (binding-name binding) *macro-table*) => cdr)
(else
(syntax-violation
#f "Reference to macro keyword out of context" t))))
;; Registering macro.
(define (register-macro! binding-name procedure-or-macro)
(set! *macro-table* (cons (cons binding-name (make-user-macro procedure-or-macro))
*macro-table*)))
;; Calls a macro with a new color.
(define (invoke-macro macro t)
(set! *color* (generate-color))
((macro-proc macro) t))
;;=========================================================================
;;
;; Expander dispatch:
;;
;;=========================================================================
(define (expand t)
(fluid-let ((*trace* (cons t *trace*)))
(let ((binding (operator-binding t)))
(cond (binding (case (binding-type binding)
((macro)
(let ((macro (binding->macro binding t)))
(let ((expanded-once (invoke-macro macro t)))
(case (macro-type macro)
((expander) expanded-once)
(else
(expand expanded-once))))))
((variable)
(check-implicit-import-of-mutable binding t)
(if (list? t)
(cons (binding-name binding)
(map expand (cdr t)))
(binding-name binding)))
((pattern-variable)
(syntax-violation #f "Pattern variable used outside syntax template" t))))
((list? t) (map expand t))
((identifier? t) (make-free-name (id-name t)))
((pair? t) (syntax-violation #f "Invalid procedure call syntax" t))
((symbol? t) (syntax-violation #f "Symbol may not appear in syntax object" t))
(else t)))))
;; Only expands while t is a user macro invocation.
;; Used by expand-lambda to detect internal definitions.
(define (head-expand t)
(fluid-let ((*trace* (cons t *trace*)))
(let ((binding (operator-binding t)))
(cond (binding (case (binding-type binding)
((macro)
(let ((macro (binding->macro binding t)))
(case (macro-type macro)
((expander) (values t binding))
(else
(head-expand (invoke-macro macro t))))))
(else (values t binding))))
(else (values t binding))))))
;; Returns binding of identifier in operator position | #f if none.
;; Singleton identifiers are also considered operators here for
;; the purpose of discovering identifier macros and variables.
;; Checks level and registers as a use.