-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathexpand.scm
600 lines (542 loc) · 21.5 KB
/
expand.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
;;; Syntax expander for sinjs.
;;; We canonicalize lots of things at the same time since we're walking
;;; the tree and keeping track of bindings.
;;; All literals get embedded in QUOTE forms.
;;; All variable names become globally uniquified.
;;; IF expressions are forced to be double-branched.
;;; Top-level variable references and sets are turned into
;;; TOP-LEVEL-REF and TOP-LEVEL-SET! forms.
;;; Internal definitions are eliminated.
;;;
;;; Only the following special forms remain at the end:
;;; QUOTE SET! IF LAMBDA TOP-LEVEL-REF TOP-LEVEL-SET! BEGIN
;;; FOREIGN-INLINE
;;; In addition, DEFINE remains if it is in a context not matched
;;; by the internal definition handler. Those which are top-level
;;; will be handled specially by the top-level code, and those which
;;; are not will generate errors in CPS transformation.
;;;
(define unique-id 0)
(define (uniquify name)
(set! unique-id (+ unique-id 1))
(define (realname name)
(if (symbol? name) name (realname (identifier->name name))))
(string->symbol (string-append (symbol->string (realname name))
"_"
(number->string unique-id))))
;;; An environment ENV is an alist; each identifier maps to either
;;; a symbol (for a variable) giving its canonical name, or to
;;; a pair (for a macro) thus
;;; ((syntax-rules ...) . env) where ENV
;;; is the environment at the macro definition's location.
;;; unique objects that cannot occur in the user's code. these
;;; are treated like identifiers, but specify a special binding
;;; treatment. they are inserted in macro expansion for identifiers
;;; which are introduced by the macro.
;;; (***special-binding name rename) [#f rename means top level]
;;; If FOO is free in some macro template, then occurrences of FOO
;;; become ***special-binding forms with RENAME copied from the
;;; environment in effect at macro definition time. Moreover,
;;; all these ***special-binding forms must be eq? to eachother.
;;;
;;; RENAME is allowed to be either a symbol or a ((syntax-rules ...) . env)
;;; structure.
;; any old unique value will do for this; we never look inside it.
(define ***special-binding (cons 'special 'binding))
;;; tell whether a form should be treated as an identifier
(define (identifier? form)
(or (symbol? form)
(and (pair? form)
(eq? (car form) ***special-binding))))
;;; turn an identifier into a name. it must pass identifier?.
(define (identifier->name form)
(if (symbol? form)
form
(cadr form)))
;;; report the renaming (or #f for top level) for an identifier
(define (identifier-rename form env)
(cond
((assq form env) => cdr)
((pair? form) (caddr form))
(else #f)))
;;; tell whether OBJ is the toplevel id (in ENV) matching symbol SYM
(define (top-level-id obj env sym)
(and (identifier? obj)
(not (identifier-rename obj env))
(eq? (identifier->name obj) sym)))
;;; return a copy of FORM with all special identifiers cleaned out
;;; (used for returning literals)
(define (clean-ids form)
(cond
((identifier? form) (identifier->name form))
((pair? form) (cons (clean-ids (car form)) (clean-ids (cdr form))))
(else form)))
;;; here is the basic syntax walker
(define (expand form env)
;;; this is what to do if we recognize a form as a combination.
(define (combination) (map (cut expand <> env) form))
(cond
((number? form) `(quote ,form))
((boolean? form) `(quote ,form))
((string? form) `(quote ,form))
((char? form) `(quote ,form))
((identifier? form)
(let ((val (identifier-rename form env)))
(cond
((symbol? val) val)
((pair? val) (error expand "illegal use of syntax keyword"))
((not val) `(top-level-ref ,(identifier->name form))))))
;; combination with nothing special
((and (pair? form)
(not (identifier? (car form))))
(combination))
((pair? form)
(let ((starter (identifier-rename (car form) env)))
(cond
;; if starter is a pair, then this is a macro invocation.
;; expand it and recurse.
((pair? starter)
(if (and (pair? (car starter))
(eq? (caar starter) 'syntax-rules))
(expand (expand-syntax-rules form (car starter) (cdr starter) env)
env)
(error expand "internal bad syntax transformer spec")))
;; if starter is not a pair, and not #f, then it is locally bound,
;; and therefore not a syntactic keyword.
(starter (combination))
;; so we have a top-level binding. if it is a synactic keyword,
;; dtrt, otherwise, it's a combination.
(else
(case (identifier->name (car form))
((quote) (clean-ids form)) ;nothing to do
((foreign-inline)
`(foreign-inline ,(cadr form)
,@(map (cut expand <> env) (cddr form))))
((set!)
(let ((var (cadr form))
(value (expand (caddr form) env)))
(unless (identifier? var)
(error expand "bad identifier in set!"))
(let ((r (identifier-rename var env)))
(cond
((symbol? r) `(set! ,r ,value))
((pair? r) (error expand "illegal use of syntax keyword"))
((not r) `(top-level-set! ,(identifier->name var) ,value))))))
((if)
(if (= (length form) 3)
`(if ,(expand (cadr form) env)
,(expand (caddr form) env)
(quote oh-mickey-youre-so-fine-you-blow-my-mind-hey-mickey))
`(if ,(expand (cadr form) env)
,(expand (caddr form) env)
,(expand (cadddr form) env))))
((begin)
`(begin ,@(map (cut expand <> env) (cdr form))))
;; each binding is a mapping from an identifier to
;; new renamed thing. We leave the identifiers alone,
;; which guarantees that if we are here binding a
;; variable inserted by a macro template, the renaming
;; we stick in the environment refers only to the ID that
;; was in the macro, and not other uses of the same name
;; from the macro call's environment.
((lambda)
#;(display (format "lambda ~s\n" (pform form)))
(let ((bindings (map-formals-flat (lambda (id)
(cons id
(uniquify id)))
(cadr form))))
`(lambda ,(map-formals (lambda (id) (cdr (assq id bindings)))
(cadr form))
,@(expand-body (cddr form) (append bindings env)))))
;; note that let-syntax and letrec-syntax create a
;; LAMBDA and not a BEGIN. this makes sure that
;; they don't get spliced the way BEGIN does, so that
;; internal definitions inside them are treated as
;; properly internal. That is correct for R5RS,
;; but R6RS changes it (see R6RS 11.2).
((let-syntax)
(let ((binding-list (cadr form))
(body (cddr form)))
`((lambda ()
,@(expand-body body
(append
(map (lambda (binding)
(let ((name (car binding))
(transformer (cadr binding)))
(unless (identifier? name)
(error expand
"bad let-syntax syntax"))
(unless (and (pair? transformer)
(top-level-id (car transformer) env
'syntax-rules))
(error expand
(format "bad let-syntax transformer: ~a"
(pform transformer))))
`(,name . ((syntax-rules ,@(cdr transformer)) . ,env))))
binding-list)
env))))))
((letrec-syntax)
(let* ((binding-list (cadr form))
(body (cddr form))
(env-add (map (lambda (binding)
(let ((name (car binding))
(transformer (cadr binding)))
(unless (identifier? name)
(error expand "bad letrec-syntax syntax"))
(unless (and (pair? transformer)
(top-level-id (car transformer) env 'syntax-rules))
(error expand
"bad letrec-syntax transformer"))
`(,name . ((syntax-rules ,@(cdr transformer)) . #f))))
binding-list))
(new-env (append env-add env)))
(for-each
(lambda (env-element)
(set-cdr! (cdr env-element) new-env))
env-add)
`((lambda () ,@(expand-body body new-env)))))
;; top level binding, but not to a syntactic keyword, so it's
;; just a combination
(else (combination)))))))
(else (error expand "improper expression"))))
;;; map across a lambda list preserving dotted list structure
(define (map-formals proc formals)
(cond
((identifier? formals) (proc formals))
((null? formals) '())
((pair? formals)
(if (not (identifier? (car formals)))
(error map-formals "bad lambda list")
(cons (proc (car formals))
(map-formals proc (cdr formals)))))
(else (error map-formals "bad lambda list"))))
;;; map across a lambda list, turning dotted list into proper list structure
(define (map-formals-flat proc formals)
(cond
((identifier? formals) (list (proc formals)))
((null? formals) '())
((pair? formals)
(if (not (identifier? (car formals)))
(error map-formals-flat "bad lambda list")
(cons (proc (car formals))
(map-formals-flat proc (cdr formals)))))
(else (error map-formals-flat "bad lambda list"))))
;;; expand all first-level syntax in a form, but leave the rest alone.
(define (expand-first-syntax form env)
#;(display (format "efs ~s\n" (pform form)))
(if (or (identifier? form)
(not (pair? form))
(not (identifier? (car form))))
form
(let ((starter (identifier-rename (car form) env)))
#;(display (format "starter ~s\n" (pform starter)))
(cond
((or (symbol? starter) (not starter)) form)
((and (pair? (car starter))
(eq? (caar starter) 'syntax-rules))
(expand-first-syntax (expand-syntax-rules form (car starter)
(cdr starter) env)
env))
(else
(error expand-first-syntax "internal bad syntax transform spec"))))))
;;; turn an implicit LAMBDA define (if this is one) into
;;; a correct version. make sure we observe hygiene when inserting
;;; the keyword LAMBDA into the output.
(define (clean-define defn)
(if (identifier? (cadr defn))
;; normal definition
(if (= (length defn) 3)
defn
(error clean-define (format "definition too long: ~s" defn)))
;; implicit lambda
(if (>= (length defn) 3)
(let ((name (caadr defn))
(formals (cdadr defn))
(body (cddr defn))
(magic-lambda (list ***special-binding 'lambda #f)))
`(,(car defn) ,name (,magic-lambda ,formals ,@body))))))
;;; expand a body in the specified environment.
;;; we need expand-first-syntax because we must allow syntax
;;; that produces (define ...) forms, without then going inside them and
;;; doing the identifier renaming (since we don't yet know what the
;;; defined identifiers are!). Thank God R5RS doesn't allow internal
;;; syntax definitions, or this would be nearly impossible.
(define (expand-body forms env)
;;; Peel off internal definitions from the front of FORMS
;;; and when they're all taken care of, use FINISH-BODY to
;;; construct the result.
(define (expand-body1 forms defns)
(if (null? forms)
(finish-body defns forms)
(let ((primo (expand-first-syntax (car forms) env)))
(if (pair? primo)
(cond
((top-level-id (car primo) env 'define)
(expand-body1 (cdr forms) (cons primo defns)))
((top-level-id (car primo) env 'begin)
(expand-body1 (append (cdr primo) (cdr forms)) defns))
(else (finish-body defns forms)))
(finish-body defns forms)))))
;; if there are definitions, turn them into an implicit letrec-type
;; construct and expand that. note that we are careful to make sure
;; the LAMBDA and SET! we insert have their top-level bindings since
;; they are passed to EXPAND.
;; xxx
;; note that this has a bug: the variables are not assigned all
;; at once, after the values have been determined, as they should be.
;; xxx
(define (finish-body defns exprs)
#;(display (format "defns: ~s\nexprs: ~s\n" (map pform defns) (map pform exprs)))
(when (null? exprs)
(error finish-body "lambda expression without body is prohibited"))
(if (null? defns)
(map (cut expand <> env) exprs)
(let* ((defns (map clean-define defns))
(vars (map cadr defns))
(vals (map caddr defns))
(magic-lambda (list ***special-binding 'lambda #f))
(magic-set! (list ***special-binding 'set! #f)))
#;(display (format "clean defs: ~s\nvars: ~s\nvals: ~s\n"
(map pform defns)
(map pform vars)
(map pform vals)))
(let ((result `((,magic-lambda ,vars
,@(map (lambda (var val) `(,magic-set! ,var ,val))
vars vals)
,@exprs)
,@(make-list (length vars)
'(quote implicit-letrec-undefined-value)))))
#;(display (format "result: ~s\n" (pform result)))
(list (expand result env))))))
#;(display (format "\nbody: ~s\n" (map pform forms)))
(expand-body1 forms '()))
;;; Note that everything in syntax transformers could be
;;; ***special-binding objects, if a macro expanded into a syntax binding
;;; construct.
;;; Expand FORM according to the specified syntax-rules TRANSFORMER.
;;; DEF-ENV is the environment in which the transformer was specified;
;;; and USE-ENV is the environment in which FORM was found. If the
;;; transformer is specified in a top-level DEFINE-SYNTAX, then DEF-ENV
;;; will be #f, and the definition environment is the *current* top-level
;;; environment. This way top-level DEFINE-SYNTAX definitions can use
;;; each other recursively. R6RS will require a change to this, since
;;; it allows internal DEFINE-SYNTAX.
(define (expand-syntax-rules form transformer def-env use-env)
(let ((def-env (or def-env top-level-environment)))
(if (identifier? (cadr transformer))
(expand-syntax-rules1 form (cadr transformer) (caddr transformer)
(cdddr transformer) def-env use-env)
(expand-syntax-rules1 form '... (cadr transformer)
(cddr transformer) def-env use-env))))
(define (expand-syntax-rules1 form ellipsis literals rules def-env use-env)
;; return a match list if FORM matches PATTERN, and #f otherwise.
;; A match list is an alist, whose cdr is a pair (depth . val).
;; Depth indicates nesting level within ellipses, and val is the form
;; matching this pattern variable.
(define (match-syntax form pattern)
(cond
((identifier? pattern)
(if (memq pattern literals)
(and (identifier? form)
(eq? (identifier-rename pattern def-env)
(identifier-rename form use-env))
'())
(list `(,pattern 0 . ,form))))
((null? pattern)
(and (null? form)
'()))
((pair? pattern)
(if (and (pair? (cdr pattern))
(eq? ellipsis (cadr pattern)))
(begin
(unless (null? (cddr pattern))
(error match-syntax "non-final ellipsis in pattern"))
(and (list? form)
(not (identifier? form))
(if (null? form)
(null-ellipses (car pattern))
(let ((matches (map (cut match-syntax <> (car pattern))
form)))
(and (every identity matches)
(assemble-ellipsed matches))))))
(and (pair? form)
(not (identifier? form))
(let ((match-car (match-syntax (car form) (car pattern)))
(match-cdr (match-syntax (cdr form) (cdr pattern))))
(and match-car
match-cdr
(append match-car match-cdr))))))
(else
(error match-syntax "unsupported pattern"))))
(define (inc-depth element)
`(,(car element) ,(+ (cadr element) 1) ,@(cddr element)))
(define (dec-depth element)
`(,(car element) ,(- (cadr element) 1) ,@(cddr element)))
;; PATTERN is piece of a syntax pattern, which is being "matched"
;; with ellipses against an empty list. Generate a suitable
;; empty spliced ellipses entry for a match list by scanning
;; through it looking for the relevant identifiers.
(define (null-ellipses pattern)
#;(display (format "null ellipses on ~s\n" (pform pattern)))
(cond
((identifier? pattern)
(if (memq pattern literals)
'()
(list `(,pattern 1 . ()))))
((null? pattern) '())
((pair? pattern)
(if (and (pair? (cdr pattern))
(eq? ellipsis (cadr pattern)))
(begin
(unless (null? (cddr pattern))
(error null-ellipses "non-final ellipsis in pattern"))
;; inner ellipses get the same treatment, but one more
;; level of parens around each key.
(map inc-depth (null-ellipses (car pattern))))
(append (null-ellipses (car pattern))
(null-ellipses (cdr pattern)))))
(else
(error null-ellipses "unsupported pattern"))))
;; this works because toplevel EXPAND-SYNTAX is called at most once
;; inside an invocation of EXPAND-SYNTAX-RULES1.
(define renamings '())
;; expand the syntax template TEMPLATE in accord with the matched
;; PAIRING match alist.
(define (expand-syntax template pairing)
(cond
((identifier? template)
(cond
((assq template pairing) =>
(lambda (element)
(if (zero? (cadr element))
(cddr element)
(error expand-syntax
(string-append
"pattern variable occurs in template with too "
(if (negative? (cadr element)) "many" "few")
" ellipses")))))
;; A literal identifier is being inserted into the expansion.
;; Stick in an appropriate ***special-binding or use the one
;; we already have if this is a repeat.
((assq template renamings) => cdr)
(else
;; the expansion is the value this has inside the
;; macro definition environment.
(let ((renaming (list ***special-binding template
(identifier-rename template def-env))))
(set! renamings (cons (cons template renaming) renamings))
renaming))))
((pair? template)
(if (and (pair? (cdr template))
(eq? ellipsis (cadr template)))
(begin
(unless (null? (cddr template))
(error expand-syntax "non-final ellipsis in template"))
(map (lambda (p) (expand-syntax (car template) p))
(let ((g (gather-ellipsed (find-ellipsed template) pairing)))
#;(display (format " as ~s\n" g))
g)))
(cons (expand-syntax (car template) pairing)
(expand-syntax (cdr template) pairing))))
(else template)))
;; find the variables in TEMPLATE which ellipsis expansion should
;; deal with and return an alist mapping them to their depth.
(define (find-ellipsed template)
(cond
((identifier? template)
(if (eq? ellipsis template)
(error find-ellipsed "misplaced ellipsis in template")
(list (cons template 0))))
((pair? template)
(if (and (pair? (cdr template))
(eq? ellipsis (cadr template)))
(map (lambda (a)
(cons (car a) (+ (cdr a) 1)))
(find-ellipsed (car template)))
(append (find-ellipsed (car template))
(find-ellipsed (cdr template)))))
(else '())))
;; VARS is an alist of pattern variables to the depth they occur in
;; a piece of template.
;; return a list of PAIRINGS for iteration across one ellipsis depth.
(define (gather-ellipsed vars pairing)
;; make sure each pattern variable needed occurs
;; at the right depth in PAIRING.
(for-each
(lambda (varmap)
(let ((element (assq (car varmap) pairing)))
(when (and element
(not (= (cdr varmap) (cadr element))))
(error gather-ellipsed "incorrect number of ellipses in pattern"))))
vars)
#;(display (format " gathering ~s with ~s\n" vars pairing))
;; return a new pairing with the depth reduced a level, and only
;; including variables in VARS.
(let ((new-pairing (filter-map (lambda (elt)
(and (assq (car elt) vars)
(dec-depth elt)))
pairing)))
#;(display (format " new pairing ~s\n" new-pairing))
(let ((vars (map car new-pairing))
(depths (map cadr new-pairing))
(vals (map cddr new-pairing)))
#;(display (format " vars ~s depths ~s vals ~s\n" vars depths vals))
(unless (apply = (map length vals))
(error gather-ellipsed
"lists not of equal length in ellipsis expansion"))
(if (null? vals)
'()
(apply map
(lambda val-set (map (lambda (var depth val)
`(,var ,depth . ,val))
vars depths val-set))
vals)))))
(unless (identifier? ellipsis)
(error expand-syntax-rules "bad ellipsis in syntax-rules"))
(unless (list? literals)
(error expand-syntax-rules "bad literals in syntax-rules"))
(unless (list? rules)
(error expand-syntax-rules "bad ruleset in syntax-rules"))
(let next ((rules rules))
(when (null? rules)
(error expand-syntax-rules (format "syntax match failure: ~a"
(pform form))))
(unless (and (list? (car rules))
(= (length (car rules)) 2))
(error expand-syntax-rules "bad rule in syntax-rules"))
(let ((pairing (match-syntax (cdr form) (cdaar rules))))
(if pairing
(begin
#;(display (format "expanding ~s\nwith pattern~s\n"
(pform form) (pform (caar rules))))
#;(display (format "inside template ~s\n" (pform (cadar rules))))
(let ((x (expand-syntax (cadar rules) pairing)))
#;(display (format "producing ~s\n" (pform x)))
x))
(next (cdr rules))))))
;;; matches is a list of match lists. Splice them together.
(define (assemble-ellipsed matches)
#;(display (format "assembling ~s\n" matches))
(map
(lambda (elt)
`(,(car elt) ,(+ 1 (cadr elt))
. ,(map (lambda (m) (cddr (assq (car elt) m))) matches)))
(car matches)))
;;; like write, but with special magic for identifiers (the problem is
;;; that if an id is special-bound to a syntax transformer, the
;;; transformer includes a reference to its own environment, which in
;;; the case of letrec-syntax, is a circular structure) so we don't
;;; print the renaming of specially bound identifiers.
(define (pform form)
(cond
((symbol? form) (symbol->string form))
((pair? form)
(if (eq? (car form) ***special-binding)
(string-append "SPECIAL-" (symbol->string (cadr form)))
(string-append "(" (pform (car form)) (prest (cdr form)))))
(else (format "~s" form))))
(define (prest form)
(cond
((null? form) ")")
((pair? form) (string-append " " (pform (car form)) (prest (cdr form))))
(else (string-append " . " (pform form) ")"))))