aboutsummaryrefslogtreecommitdiffstats
path: root/csc/macros.csc
blob: d8903ebecdbff9909e195069c81570983240b8b8 (plain) (blame)
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
(define-library (csc macros)
  (export
    expand
    test-environment)
  (import (scheme base)
          (only (csc format) sprintf)
          (only (csc gensym)
            gensym
            gensym=?)
          (only (csc hash-map)
            alist->map
            hash-bytevector
            insert
            key-not-found-error?
            lookup
            merge)
          (only (csc ir1)
            lexical-ref-gensym
            lexical-ref?
            library-ref-library
            library-ref-name
            library-ref?
            make-call
            make-constant
            make-lambda
            make-lambda-case
            make-library-ref)
          (only (csc list) revappend)
          (only (csc match) match)
          (only (csc strings) join))
  (begin


    (define-record-type <macro-transformer>
      (make-macro-transformer transformer)
      macro-transformer?
      (transformer transformer-function))


    (define-record-type <macro-syntax-error>
      (make-macro-syntax-error message irritants)
      macro-syntax-error?
      (message syntax-error-object-message)
      (irritants syntax-error-object-irritants))


    (define (raise-syntax-error message . irritants)
      (raise (make-macro-syntax-error message irritants)))


    ; symbols is a map with identifiers as keys, and the values can be one of:
    ; - <lexical-ref>,
    ; - <library-ref>,
    ; - or <macro-transformer>.
    ; The first two correspond to variables bound lexically or from a module,
    ; and the third represents a macro transformer bound in the
    ; current context.
    ;
    ; library is the current library name being compiled. A nil library
    ; corresponds to top level expressions.
    (define-record-type <environment>
      (make-environment symbols library)
      environment?
      (symbols environment-substitutions)
      (library environment-library))


    (define (with-binding environment identifier binding)
      (make-environment (insert (environment-substitutions environment) identifier binding) (environment-library environment)))


    (define-record-type <syntax-object>
      (make-syntax-object expression environment marks)
      syntax-object?
      (expression syntax-object-expression)
      (environment syntax-object-environment)
      (marks syntax-object-marks))


    (define (wrap-syntax expression environment)
      (if (syntax-object? expression)
        expression
        (make-syntax-object expression environment '())))


    (define (identifier? s)
      (or (symbol? s)
          (and (syntax-object? s)
               (symbol? (syntax-object-expression s)))))


    (define (marks s)
      (if (syntax-object? s)
        (syntax-object-marks s)
        '()))


    (define (marks=? m1 m2)
      (and
        (= (length m1) (length m2))
        (let loop ((m1 m1)
                   (m2 m2))
          (if (null? m1)
            #t
            (and (= (car m1) (car m2)) (loop (cdr m1) (cdr m2)))))))


    (define (identifier-name s)
      (if (syntax-object? s)
        (syntax-object-expression s)
        s))


    (define (bound-identifier=? s1 s2)
      (and (symbol=? (identifier-name s1) (identifier-name s2))
           (marks=? (marks s1) (marks s2))))


    (define (binding=? b1 b2)
      (or (and (lexical-ref? b1)
               (lexical-ref? b2)
               (gensym=? (lexical-ref-gensym b1) (lexical-ref-gensym b2)))
          (and (library-ref? b1)
               (library-ref? b2)
               (equal? (library-ref-library b1) (library-ref-library b2))
               (symbol=? (library-ref-name b1) (library-ref-name b2)))
          (and (macro-transformer? b1)
               (macro-transformer? b2)
               (eq? (transformer-function b1) (transformer-function b2)))))


    ; free-identifier=? only works on wrapped syntax objects.
    (define (free-identifier=? s1 s2)
      (let ((s1-binding (guard (e ((key-not-found-error? e) #f))
                          (lookup (environment-substitutions (syntax-object-environment s1)) s1)))
            (s2-binding (guard (e ((key-not-found-error? e) #f))
                          (lookup (environment-substitutions (syntax-object-environment s2)) s2))))
        (or (and (not s1-binding) (not s2-binding))
            (binding=? s1-binding s2-binding))))


    (define *next-mark* 0)


    (define (new-mark)
      (let ((m *next-mark*))
        (set! *next-mark* (+ 1 *next-mark*))
        m))


    (define (add-mark mark expression)
      (make-syntax-object
        (syntax-object-expression expression)
        (syntax-object-environment expression)
        (if (and (pair? (syntax-object-marks expression))
                 (not (car (syntax-object-marks expression))))  ; Anti-mark.
          (cdr (syntax-object-marks expression))
          (cons mark (syntax-object-marks expression)))))


    (define (add-marks marks expression)
      (let loop ((marks marks)
                 (expression expression))
        (match marks
          ('() expression)
          ((mark . marks)
           (loop marks (add-mark mark expression))))))


    (define (anti-mark expression)
      (add-mark #f expression))


    (define (decorate marks expression environment)
      (add-marks marks (wrap-syntax expression environment)))


    (define (with-wrap expression parent)
      (decorate (marks parent) expression (syntax-object-environment parent)))


    (define (syntax-map f expr)
      (with-wrap (f (syntax->expression expr)) expr))


    (define-record-type <syntax-case-no-match>
      (make-syntax-case-no-match)
      syntax-case-no-match?)


    (define-syntax syntax-case-match-pattern
      (syntax-rules (_ when)
        ((syntax-case-match-pattern x pattern (when condition) result result* ...)
          (syntax-case-match-pattern x pattern
            (if condition
              (begin result result* ...)
              (raise (make-syntax-case-no-match)))))
        ((syntax-case-match-pattern x _ result result* ...)
          (begin result result* ...))
        ((syntax-case-match-pattern x '() result result* ...)
          (if (null? (syntax->expression x))
            (begin result result* ...)
            (raise (make-syntax-case-no-match))))
        ((syntax-case-match-pattern x (pattern) result result* ...)
          (if (= 1 (length (syntax->expression x)))
            (syntax-case-match-pattern (syntax-map car x) pattern result result* ...)
            (raise (make-syntax-case-no-match))))
        ((syntax-case-match-pattern x (pattern . rest) result result* ...)
          (let ((y x))
            (if (pair? (syntax->expression y))
              (syntax-case-match-pattern (syntax-map car y) pattern
                (syntax-case-match-pattern (syntax-map cdr y) rest result result* ...)))))
        ((syntax-case-match-pattern x ident result result* ...)
          (let ((ident x)) result result* ...))))


    ; Yes, I just defined syntax-case in terms of syntax-rules.
    ; Are we sure this won't create a black hole?
    (define-syntax syntax-case
      (syntax-rules ()
        ((syntax-case x (arm ...))
          (guard (e ((syntax-case-no-match? e) (if #f #f)))
            (syntax-case-match-pattern x arm ...)))
        ((syntax-case x (arm ...) clause clause* ...)
          (let ((y x))
            (guard (e ((syntax-case-no-match? e)
                        (syntax-case y clause clause* ...)))
              (syntax-case-match-pattern y arm ...))))))


    (define (expand-procedure-call procedure arguments)
      (let-values (((expanded-procedure environment) (expand-syntax-object procedure))
                   ((expanded-arguments)
                     (let loop ((arguments arguments)
                                (expanded-arguments '()))
                       (syntax-case arguments
                         ('() (reverse expanded-arguments))
                         ((argument . rest)
                           (let-values (((expanded-argument environment) (expand-syntax-object argument)))
                             (loop
                               rest
                               (cons expanded-argument expanded-arguments))))
                         (_ (raise-syntax-error "arguments to a procedure call must be a list" procedure arguments))))))
        (make-call expanded-procedure expanded-arguments)))


    ; From R6RS:
    ; Each time the expander encounters a macro use, it applies an antimark to
    ; the input form, invokes the associated transformer, then applies a fresh
    ; mark to the output.
    ;
    ; In cute scheme, macros compile themselves, and syntax-rules is therefore
    ; responsible for applying a mark to an expanded expression before
    ; expanding it with expand-syntax-object.
    (define (expand-macro-use transformer syntax)
      ((transformer-function transformer) (anti-mark syntax)))


    (define (expand-syntax-object syntax)
      (syntax-case syntax
        ('() (raise-syntax-error "nil by itself is an error (did you mean to use quote?)" syntax))
        ((macro-name . tail) (when (identifier? macro-name))
          (let ((macro-body
                  (guard (e ((key-not-found-error? e) (raise-syntax-error "undefined symbol" macro-name)))
                    (lookup (environment-substitutions (syntax-object-environment macro-name)) macro-name))))
            (if (macro-transformer? macro-body)
              (expand-macro-use macro-body syntax)
              (values (expand-procedure-call macro-name tail) (syntax-object-environment syntax)))))
        ((procedure . arguments)
          (values (expand-procedure-call procedure arguments) (syntax-object-environment syntax)))
        (_ (when (identifier? syntax))
          (let ((binding
                  (guard (e ((key-not-found-error? e) (raise-syntax-error "undefined symbol" syntax)))
                    (lookup (environment-substitutions (syntax-object-environment syntax)) syntax))))
            (if (macro-transformer? binding)
              (raise-syntax-error "macro is not allowed in this context" syntax)
              (values binding (syntax-object-environment syntax)))))
        (_ (when (let ((expr (syntax->expression syntax)))
                   (or (boolean? expr)
                       (char? expr)
                       (number? expr)
                       (string? expr)
                       (vector? expr))))
          (values (make-constant (syntax->expression syntax)) (syntax-object-environment syntax)))
        (_ (raise-syntax-error "unexpected expression type" syntax))))


    ; expand can be thought of as a compiler from Scheme to IR1. Macros
    ; included in the environment can be used to extend the syntax. Returns an
    ; IR1 expression and an environment which has been modified with any new
    ; bindings introduced by the expression.
    (define (expand expression environment)
      (expand-syntax-object (wrap-syntax expression environment)))


    (define builtin-quote
      (make-macro-transformer
        (lambda (syntax)
          (syntax-case syntax
            ((_ datum) (values (make-constant (syntax->expression datum)) (syntax-object-environment syntax)))
            (_ (raise-syntax-error "invalid form for quote" syntax))))))


    (define (syntax->expression s)
      (if (syntax-object? s)
        (syntax-object-expression s)
        s))


    (define (matches-literals literals object)
      (unless (list? (syntax->expression literals))
        (raise-syntax-error "invalid form in literals, expecting list" literals))
      (let ((literal-identifiers
              (map
                (lambda (lit) (with-wrap lit literals))
                (syntax->expression literals))))
        (let loop ((literal-identifiers literal-identifiers))
          (match literal-identifiers
            ('() #f)
            ((lit . literals)
              (or (free-identifier=? lit object)
                  (loop literals)))))))


    (define (identifier-uuid i)
      ; Join marks by ( because symbols aren't allowed to have ( in the name.
      (sprintf "{}({}" (apply join "(" (map number->string (marks i))) (identifier-name i)))


    (define (hash-identifier i)
      (hash-bytevector (string->utf8 (identifier-uuid i))))


    (define (cmp-identifier i1 i2)
      (cond
        ((string<? (identifier-uuid i1) (identifier-uuid i2)) -1)
        ((string=? (identifier-uuid i1) (identifier-uuid i2)) 0)
        (else 1)))


    (define (alist->substitutions l)
      (alist->map hash-identifier cmp-identifier l))


    (define (is-underscore expression)
      (free-identifier=?
        expression
        (wrap-syntax '_ (make-environment (alist->substitutions (list (cons '_ (make-library-ref '(csc builtins) '_ #t)))) '()))))


    (define (pattern-bindings pattern literals object)
      (syntax-case pattern
        (lit (when (and (free-identifier=? object lit)
                        (matches-literals literals object)))
          (alist->substitutions '()))
        (underscore (when (is-underscore underscore))
          (alist->substitutions '()))
        (ident (when (identifier? ident))
          (alist->substitutions (list (cons ident object))))
        ('()
          (syntax-case object
            ('() (alist->substitutions '()))
            (_ #f)))
        ((p . p*)
          (syntax-case object
            ((e . e*)
              (merge (pattern-bindings p literals e)
                     (pattern-bindings p* literals e*)))
            (_ #f)))
        (constant
          (if (equal? constant (syntax->expression object))
            (alist->substitutions '())
            #f))))


    (define (expand-template substitutions template)
      (syntax-case template
        ('() (with-wrap '() template))
        ((head . tail)
          (with-wrap
            (cons (expand-template substitutions head)
                  (expand-template substitutions tail))
            template))
        (ident (when (identifier? ident))
          (guard (e ((key-not-found-error? e) template))
            (lookup substitutions template)))
        (_ template)))


    (define (syntax-match literals all-rules object)
      (let loop ((rules all-rules))
        (syntax-case rules
          ('() (raise-syntax-error "form did not match any patterns in syntax-rules" object (map car all-rules)))
          ((((_ . pattern) template) . tail)
            (let ((bindings (pattern-bindings pattern literals (with-wrap (cdr (syntax->expression object)) object))))
              (if bindings
                ; Each time the expander encounters a macro use,
                ; it applies an antimark to the input form,
                ; invokes the associated transformer,
                ; then applies a fresh mark to the output.
                ;
                ; We also call expand-syntax-object immediately,
                ; since macros are allowed to be recursive -- rose
                (expand-syntax-object (add-mark (new-mark) (expand-template bindings template)))
                (loop tail))))
          (_ (raise-syntax-error "unexpected form in syntax-rules" all-rules)))))


    (define builtin-syntax-rules
      (make-macro-transformer
        (lambda (syntax-rules-form)
          (syntax-case syntax-rules-form
            ((_ literals . rules)
              (values
                (make-macro-transformer
                  (lambda (input-form)
                    (syntax-match literals rules input-form)))
                (syntax-object-environment syntax-rules-form)))
            (_ (raise-syntax-error "unexpected form in syntax-rules" syntax-rules-form))))))


    (define test-environment
      (make-environment
        (alist->substitutions
          (list (cons 'syntax-rules builtin-syntax-rules)
                (cons 'quote builtin-quote)
                (cons '_ (make-library-ref '(csc builtins) '_ #t))))
        '()))))