-
Notifications
You must be signed in to change notification settings - Fork 0
/
pmacros.scm
1435 lines (1231 loc) · 45.8 KB
/
pmacros.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
;; Preprocessor-like macro support.
;; Copyright (C) 2000, 2009, 2010 Red Hat, Inc.
;; This file is part of CGEN.
;; See file COPYING.CGEN for details.
;; TODO:
;; - Like C preprocessor macros, there is no scoping [one can argue
;; there should be]. Maybe in time (??? Hmmm... done?)
;; - Support for multiple macro tables.
;; Non-standard required routines:
;; Provided by Guile:
;; make-hash-table, hashq-ref, hashq-set!, symbol-append,
;; source-properties
;; Provided by CGEN:
;; location-property, location-property-set!,
;; source-properties-location->string,
;; single-location->string, location-top, unspecified-location,
;; reader-process-expanded!, num-args-ok?, *UNSPECIFIED*.
;; The convention we use says `-' begins "local" objects.
;; At some point this might also use the Guile module system.
;; This uses Guile's source-properties system to track source location.
;; The chain of macro invocations is tracked and stored in the result as
;; object property "location-property".
;; Exported routines:
;;
;; pmacro-init! - initialize the pmacro system
;;
;; define-pmacro - define a symbolic or procedural pmacro
;;
;; (define-pmacro symbol ["comment"] expansion)
;; (define-pmacro (symbol [args]) ["comment"] (expansion))
;;
;; ARGS is a list of `symbol' or `(symbol default-value)' elements.
;;
;; pmacro-expand - expand all pmacros in an expression
;;
;; (pmacro-expand expression loc)
;;
;; pmacro-trace - same as pmacro-expand, but trace macro expansion
;; Output is sent to current-error-port.
;;
;; (pmacro-trace expression loc)
;;
;; pmacro-dump - expand all pmacros in an expression, for debugging purposes
;;
;; (pmacro-dump expression)
;; pmacro-debug - expand all pmacros in an expression,
;; printing various debugging messages.
;; This does not process %exec.
;;
;; (pmacro-debug expression)
;; Builtin pmacros:
;;
;; (%sym symbol1 symbol2 ...) - symbolstr-append
;; (%str string1 string2 ...) - stringsym-append
;; (%hex number [width]) - convert to hex string
;; (%upcase string)
;; (%downcase string)
;; (%substring string start end) - get part of a string
;; (%splice a b (%unsplice c) d e ...) - splice list into another list
;; (%iota count [start [increment]]) - number generator
;; (%map pmacro arg1 . arg-rest)
;; (%for-each pmacro arg1 . arg-rest)
;; (%eval expr) - expand (or evaluate it) expr
;; (%exec expr) - execute expr immediately
;; (%apply pmacro-name arg)
;; (%pmacro (arg-list) expansion) - akin go lambda in Scheme
;; (%pmacro? arg)
;; (%let (var-list) expr1 . expr-rest) - akin to let in Scheme
;; (%let* (var-list) expr1 . expr-rest) - akin to let* in Scheme
;; (%if expr then [else])
;; (%case expr ((case-list1) stmt) [case-expr-stmt-list] [(else stmt)])
;; (%cond (expr stmt) [(cond-expr-stmt-list)] [(else stmt)])
;; (%begin . stmt-list)
;; (%print . exprs) - for debugging messages
;; (%dump expr) - dump expr in readable format
;; (%error . message) - print error message and exit
;; (%list . exprs)
;; (%ref l n) - extract the n'th element of list l
;; (%length x) - length of symbol, string, or list
;; (%replicate n expr) - return list of expr replicated n times
;; (%find pred l) - return elements of list l matching pred
;; (%equal? x y) - deep comparison
;; (%andif expr . rest) - && in C
;; (%orif expr . rest) - || in C
;; (%not expr) - ! in C
;; (%eq x y)
;; (%ne x y)
;; (%lt x y)
;; (%gt x y)
;; (%le x y)
;; (%ge x y)
;; (%add x y)
;; (%sub x y)
;; (%mul x y)
;; (%div x y) - integer division
;; (%rem x y) - integer remainder
;; (%sll x n) - shift left logical
;; (%srl x n) - shift right logical
;; (%sra x n) - shift right arithmetic
;; (%and x y) - bitwise and
;; (%or x y) - bitwise or
;; (%xor x y) - bitwise xor
;; (%inv x) - bitwise invert
;; (%car l)
;; (%cdr l)
;; (%caar l)
;; (%cadr l)
;; (%cdar l)
;; (%cddr l)
;; (%internal-test expr) - testsuite internal use only
;;
;; NOTE: %cons currently absent on purpose
;;
;; %sym and %str convert numbers to symbols/strings as necessary (base 10).
;;
;; %pmacro is for constructing pmacros on-the-fly, like lambda, and is currently
;; only valid as arguments to other pmacros or assigned to a local in a {%let}
;; or {%let*}.
;;
;; NOTE: While Scheme requires tail recursion to be implemented as a loop,
;; we do not. We might some day, but not today.
;;
;; ??? Methinks .foo isn't a valid R5RS symbol. May need to change
;; to something else.
;; True if doing pmacro expansion via pmacro-debug.
(define /pmacro-debug? #f)
;; True if doing pmacro expansion via pmacro-trace.
(define /pmacro-trace? #f)
;; The original prefix to pmacro names.
(define /pmacro-orig-prefix ".")
;; The prefix to pmacro names.
(define /pmacro-prefix "%")
;; The pmacro table.
(define /pmacro-table #f)
(define (/pmacro-lookup name) (hashq-ref /pmacro-table name #f))
(define (/pmacro-set! name val) (hashq-set! /pmacro-table name val))
;; A copy of syntactic pmacros is kept separately.
(define /smacro-table #f)
(define (/smacro-lookup name) (hashq-ref /smacro-table name #f))
(define (/smacro-set! name val) (hashq-set! /smacro-table name val))
;; Marker to indicate a value is a pmacro.
;; NOTE: Naming this "<pmacro>" is intentional. It makes them look like
;; objects of class <pmacro>. However we don't use COS in part to avoid
;; a dependency on COS and in part because displaying COS objects isn't well
;; supported (displaying them in debugging dumps adds a lot of noise).
(define /pmacro-marker '<pmacro>)
;; Utilities to create and access pmacros.
(define (/pmacro-make name arg-spec default-values
syntactic-form? transformer comment)
(vector /pmacro-marker name arg-spec default-values
syntactic-form? transformer comment)
)
(define (/pmacro? x) (and (vector? x) (eq? (vector-ref x 0) /pmacro-marker)))
(define (/pmacro-name pmac) (vector-ref pmac 1))
(define (/pmacro-arg-spec pmac) (vector-ref pmac 2))
(define (/pmacro-default-values pmac) (vector-ref pmac 3))
(define (/pmacro-syntactic-form? pmac) (vector-ref pmac 4))
(define (/pmacro-transformer pmac) (vector-ref pmac 5))
(define (/pmacro-comment pmac) (vector-ref pmac 6))
;; Create a new environment, prepending NAMES to PREV-ENV.
(define (/pmacro-env-make loc prev-env names values)
(if (= (length names) (length values))
(append! (map cons names values) prev-env)
(/pmacro-loc-error loc
(string-append "invalid number of parameters, expected "
(number->string (length names)))
values))
)
;; Look up NAME in ENV.
(define (/pmacro-env-ref env name) (assq name env))
;; Error message generator.
(define (/pmacro-error msg expr)
(error (string-append
(or (port-filename (current-input-port)) "<input>")
":"
(number->string (port-line (current-input-port)))
":"
msg
":")
expr)
)
;; Error message generator when we have a location.
(define (/pmacro-loc-error loc errmsg expr)
(let* ((top-sloc (location-top loc))
(intro "During pmacro expansion")
(text (string-append "Error: " errmsg)))
(error (simple-format
#f
"\n~A:\n@ ~A:\n\n~A: ~A:"
intro
(location->string loc)
(single-location->simple-string top-sloc)
text)
expr))
)
;; Issue an error where a number was expected.
(define (/pmacro-expected-number op n)
(/pmacro-error (string-append "invalid arg for " op ", expected number") n)
)
;; Verify N is a number.
(define (/pmacro-verify-number op n)
(if (not (number? n))
(/pmacro-expected-number op n))
)
;; Issue an error where an integer was expected.
(define (/pmacro-expected-integer op n)
(/pmacro-error (string-append "invalid arg for " op ", expected integer") n)
)
;; Verify N is an integer.
(define (/pmacro-verify-integer op n)
(if (not (integer? n))
(/pmacro-expected-integer op n))
)
;; Issue an error where a non-negative integer was expected.
(define (/pmacro-expected-non-negative-integer op n)
(/pmacro-error (string-append "invalid arg for " op ", expected non-negative integer") n)
)
;; Verify N is a non-negative integer.
(define (/pmacro-verify-non-negative-integer op n)
(if (or (not (integer? n))
(< n 0))
(/pmacro-expected-non-negative-integer op n))
)
;; Expand a list of expressions, in order.
;; The result is the value of the last one.
(define (/pmacro-expand-expr-list exprs env loc)
(let ((result nil))
(for-each (lambda (expr)
(set! result (/pmacro-expand expr env loc)))
exprs)
result)
)
;; Process list of keyword/value specified arguments.
(define (/pmacro-process-keyworded-args arg-spec default-values args)
;; Build a list of default values, then override ones specified in ARGS,
(let ((result-alist (alist-copy default-values)))
(let loop ((args args))
(cond ((null? args)
#f) ;; done
((and (pair? args) (keyword? (car args)))
(let ((elm (assq (car args) result-alist)))
(if (not elm)
(/pmacro-error "not an argument name" (car args)))
(if (null? (cdr args))
(/pmacro-error "missing argument to #:keyword" (car args)))
(set-cdr! elm (cadr args))
(loop (cddr args))))
(else
(/pmacro-error "bad keyword/value argument list" args))))
;; Ensure each element has a value.
(let loop ((to-scan result-alist))
(if (null? to-scan)
#f ;; done
(begin
(if (not (cdar to-scan))
(/pmacro-error "argument value not specified" (caar to-scan)))
(loop (cdr to-scan)))))
;; If varargs pmacro, adjust result.
(if (list? arg-spec)
(map cdr result-alist) ;; not varargs
(let ((nr-args (length (result-alist))))
(append! (map cdr (list-head result-alist (- nr-args 1)))
(cdr (list-tail result-alist (- nr-args 1)))))))
)
;; Process a pmacro argument list.
;; ARGS is either a fully specified position dependent argument list,
;; or is a list of keyword/value pairs with missing values coming from
;; DEFAULT-VALUES.
(define (/pmacro-process-args-1 arg-spec default-values args)
(if (and (pair? args) (keyword? (car args)))
(/pmacro-process-keyworded-args arg-spec default-values args)
args)
)
;; Subroutine of /pmacro-apply,/smacro-apply to simplify them.
;; Process the arguments, verify the correct number is present.
(define (/pmacro-process-args macro args)
(let ((arg-spec (/pmacro-arg-spec macro))
(default-values (/pmacro-default-values macro)))
(let ((processed-args (/pmacro-process-args-1 arg-spec default-values args)))
(if (not (num-args-ok? (length processed-args) arg-spec))
(/pmacro-error (string-append
"wrong number of arguments to pmacro "
(with-output-to-string
(lambda ()
(write (cons (/pmacro-name macro)
(/pmacro-arg-spec macro))))))
args))
processed-args))
)
;; Invoke a pmacro.
(define (/pmacro-apply macro args)
(apply (/pmacro-transformer macro)
(/pmacro-process-args macro args))
)
;; Invoke a syntactic-form pmacro.
;; ENV, LOC are handed down from /pmacro-expand.
(define (/smacro-apply macro args env loc)
(apply (/pmacro-transformer macro)
(cons loc (cons env (/pmacro-process-args macro args))))
)
;; Expand expression EXP using ENV, an alist of variable assignments.
;; LOC is the location stack thus far.
(define (/pmacro-expand exp env loc)
(define cep (current-error-port))
;; If the symbol is in `env', return its value.
;; Otherwise see if symbol is a globally defined pmacro.
;; Otherwise return the symbol unchanged.
(define (scan-symbol sym)
(let ((val (/pmacro-env-ref env sym)))
(if val
(cdr val) ;; cdr is value of (name . value) pair
(let ((val (/pmacro-lookup sym)))
(if val
;; Symbol is a pmacro.
;; If this is a procedural pmacro, let caller perform expansion.
;; Otherwise, return the pmacro's value.
(if (procedure? (/pmacro-transformer val))
val
(/pmacro-transformer val))
;; Return symbol unchanged.
sym)))))
;; See if (car exp) is a pmacro.
;; Return pmacro or #f.
(define (check-pmacro exp)
(if /pmacro-debug?
(begin
(display "Checking for pmacro: " cep)
(write exp cep)
(newline cep)))
(and (/pmacro? (car exp)) (car exp)))
;; Subroutine of scan-list to simplify it.
;; Macro expand EXP which is known to be a non-null list.
;; LOC is the location stack thus far.
(define (scan-list1 exp loc)
;; Check for syntactic forms.
;; They are handled differently in that we leave it to the transformer
;; routine to evaluate the arguments.
;; Note that we also don't support passing syntactic form functions
;; as arguments: We look up (car exp) here, not its expansion.
(let ((sform (/smacro-lookup (car exp))))
(if sform
(begin
;; ??? Is it useful to trace these?
(/smacro-apply sform (cdr exp) env loc))
;; Not a syntactic form.
;; See if we have a pmacro. Do this before evaluating all the
;; arguments (even though we will eventually evaluate all the
;; arguments before invoking the pmacro) so that tracing is more
;; legible (we print the expression we're about to evaluate *before*
;; we evaluate its arguments).
(let ((scanned-car (scan (car exp) loc)))
(if (/pmacro? scanned-car)
(begin
;; Trace expansion here, we know we have a pmacro.
(if /pmacro-trace?
(let ((src-props (source-properties exp))
(indent (spaces (* 2 (length (location-list loc))))))
;; We use `write' to display `exp' to see strings quoted.
(display indent cep)
(display "Expanding: " cep)
(write exp cep)
(newline cep)
(display indent cep)
(display " env: " cep)
(write env cep)
(newline cep)
(if (not (null? src-props))
(begin
(display indent cep)
(display " location: " cep)
(display (source-properties-location->string src-props) cep)
(newline cep)))))
;; Evaluate all the arguments before invoking the pmacro.
(let* ((scanned-args (map (lambda (e) (scan e loc))
(cdr exp)))
(result (if (procedure? (/pmacro-transformer scanned-car))
(/pmacro-apply scanned-car scanned-args)
(cons (/pmacro-transformer scanned-car) scanned-args))))
(if /pmacro-trace?
(let ((indent (spaces (* 2 (length (location-list loc))))))
(display indent cep)
(display " result: " cep)
(write result cep)
(newline cep)))
result))
;; Not a pmacro.
(cons scanned-car (map (lambda (e) (scan e loc))
(cdr exp))))))))
;; Macro expand EXP which is known to be a non-null list.
;; LOC is the location stack thus far.
;;
;; This uses scan-list1 to do the real work, this handles location tracking.
(define (scan-list exp loc)
(let ((src-props (source-properties exp))
(new-loc loc))
(if (not (null? src-props))
(let ((file (assq-ref src-props 'filename))
(line (assq-ref src-props 'line))
(column (assq-ref src-props 'column)))
(set! new-loc (location-push-single loc file line column #f))))
(let ((result (scan-list1 exp new-loc)))
(if (pair? result) ;; pair? -> cheap non-null-list?
(begin
;; Copy source location to new expression.
(if (null? (source-properties result))
(set-source-properties! result src-props))
(let ((loc-prop (location-property result)))
(if loc-prop
(location-property-set! result (location-push new-loc loc-prop))
(location-property-set! result new-loc)))))
result)))
;; Scan EXP, an arbitrary value.
;; LOC is the location stack thus far.
(define (scan exp loc)
(let ((result (cond ((symbol? exp)
(scan-symbol exp))
((pair? exp) ;; pair? -> cheap non-null-list?
(scan-list exp loc))
;; Not a symbol or expression, return unchanged.
(else
exp))))
;; Re-examining `result' to see if it is another pmacro invocation
;; allows doing things like ((%sym a b c) arg1 arg2)
;; where `abc' is a pmacro. Scheme doesn't work this way, but then
;; this is CGEN.
(if (symbol? result) (scan-symbol result) result)))
(scan exp loc)
)
;; Return the argument spec from ARGS.
;; ARGS is a [possibly improper] list of `symbol' or `(symbol default-value)'
;; elements. For varargs pmacros, ARGS must be an improper list
;; (e.g. (a b . c)) with the last element being a symbol.
(define (/pmacro-get-arg-spec args)
(let ((parse-arg
(lambda (arg)
(cond ((symbol? arg)
arg)
((and (pair? arg) (symbol? (car arg)))
(car arg))
(else
(/pmacro-error "argument not `symbol' or `(symbol . default-value)'"
arg))))))
(if (list? args)
(map parse-arg args)
(letrec ((parse-improper-list
(lambda (args)
(cond ((symbol? args)
args)
((pair? args)
(cons (parse-arg (car args))
(parse-improper-list (cdr args))))
(else
(/pmacro-error "argument not `symbol' or `(symbol . default-value)'"
args))))))
(parse-improper-list args))))
)
;; Return the default values specified in ARGS.
;; The result is an alist of (#:arg-name . default-value) elements.
;; ARGS is a [possibly improper] list of `symbol' or `(symbol . default-value)'
;; elements. For varargs pmacros, ARGS must be an improper list
;; (e.g. (a b . c)) with the last element being a symbol.
;; Unspecified default values are recorded as #f.
(define (/pmacro-get-default-values args)
(let ((parse-arg
(lambda (arg)
(cond ((symbol? arg)
(cons (symbol->keyword arg) #f))
((and (pair? arg) (symbol? (car arg)))
(cons (symbol->keyword (car arg)) (cdr arg)))
(else
(/pmacro-error "argument not `symbol' or `(symbol . default-value)'"
arg))))))
(if (list? args)
(map parse-arg args)
(letrec ((parse-improper-list
(lambda (args)
(cond ((symbol? args)
(cons (parse-arg args) nil))
((pair? args)
(cons (parse-arg (car args))
(parse-improper-list (cdr args))))
(else
(/pmacro-error "argument not `symbol' or `(symbol . default-value)'"
args))))))
(parse-improper-list args))))
)
;; Build a procedure that performs a pmacro expansion.
;; Earlier version, doesn't work with LOC as a <location> object,
;; COS objects don't pass through eval1.
;(define (/pmacro-build-lambda prev-env params expansion)
;; (eval1 `(lambda ,params
;; (/pmacro-expand ',expansion
;; (/pmacro-env-make ',prev-env
;; ',params (list ,@params))))
;;)
(define (/pmacro-build-lambda loc prev-env params expansion)
(lambda args
(/pmacro-expand expansion
(/pmacro-env-make loc prev-env params args)
loc))
)
;; While using `define-macro' seems preferable, boot-9.scm uses it and
;; I'd rather not risk a collision. I could of course make the association
;; during parsing, maybe later.
;; On the other hand, calling them pmacros removes all ambiguity.
;; In the end the ambiguity removal is the deciding win.
;;
;; The syntax is one of:
;; (define-pmacro symbol expansion)
;; (define-pmacro symbol ["comment"] expansion)
;; (define-pmacro (name args ...) expansion)
;; (define-pmacro (name args ...) "documentation" expansion)
;;
;; If `expansion' is the name of a pmacro, its value is used (rather than its
;; name).
;; ??? The goal here is to follow Scheme's define/lambda, but not all variants
;; are supported yet. There's also the difference that we treat undefined
;; symbols as being themselves (i.e. "self quoting" so-to-speak).
;;
;; ??? We may want user-definable "syntactic" pmacros some day. Later.
(define (define-pmacro header arg1 . arg-rest)
(if (and (not (symbol? header))
(not (list? header)))
(/pmacro-error "invalid pmacro header" header))
(let ((name (if (symbol? header) header (car header)))
(arg-spec (if (symbol? header) #f (/pmacro-get-arg-spec (cdr header))))
(default-values (if (symbol? header) #f (/pmacro-get-default-values (cdr header))))
(comment (if (null? arg-rest) "" arg1))
(expansion (if (null? arg-rest) arg1 (car arg-rest))))
;;(if (> (length arg-rest) 1)
;;(/pmacro-error "extraneous arguments to define-pmacro" (cdr arg-rest)))
;;(if (not (string? comment))
;;(/pmacro-error "invalid pmacro comment, expected string" comment))
(if (symbol? header)
(if (symbol? expansion)
(let ((maybe-pmacro (/pmacro-lookup expansion)))
(if maybe-pmacro
(/pmacro-set! name
(/pmacro-make name
(/pmacro-arg-spec maybe-pmacro)
(/pmacro-default-values maybe-pmacro)
#f ;; syntactic-form?
(/pmacro-transformer maybe-pmacro)
comment))
(/pmacro-set! name (/pmacro-make name #f #f #f expansion comment))))
(/pmacro-set! name (/pmacro-make name #f #f #f expansion comment)))
(/pmacro-set! name
(/pmacro-make name arg-spec default-values #f
(/pmacro-build-lambda (current-reader-location)
nil
arg-spec
expansion)
comment))))
*UNSPECIFIED*
)
;; Expand any pmacros in EXPR.
;; LOC is the <location> of EXPR.
(define (pmacro-expand expr loc)
(/pmacro-expand expr '() loc)
)
;; Debugging routine to trace pmacro expansion.
(define (pmacro-trace expr loc)
;; FIXME: Need unwind protection.
(let ((old-trace /pmacro-trace?)
(src-props (and (pair? expr) (source-properties expr)))
(cep (current-error-port)))
(set! /pmacro-trace? #t)
;; We use `write' to display `expr' to see strings quoted.
(display "Pmacro expanding: " cep) (write expr cep) (newline cep)
;;(display "Top level env: " cep) (display nil cep) (newline cep)
(display "Pmacro location: " cep)
(if (and src-props (not (null? src-props)))
(display (source-properties-location->string src-props) cep)
(display (single-location->string (location-top loc)) cep))
(newline cep)
(let ((result (/pmacro-expand expr '() loc)))
(display "Pmacro result: " cep) (write result cep) (newline cep)
(set! /pmacro-trace? old-trace)
result))
)
;; Debugging utility to expand a pmacro, with no initial source location.
(define (pmacro-dump expr)
(/pmacro-expand expr '() (unspecified-location))
)
;; Expand any pmacros in EXPR, printing various debugging messages.
;; This does not process %exec.
(define (pmacro-debug expr)
;; FIXME: Need unwind protection.
(let ((old-debug /pmacro-debug?))
(set! /pmacro-debug? #t)
(let ((result (pmacro-trace expr (unspecified-location))))
(set! /pmacro-debug? old-debug)
result))
)
;; Builtin pmacros.
;; (%sym symbol1 symbol2 ...) - symbol-append, auto-convert numbers
(define /pmacro-builtin-sym
(lambda args
(string->symbol
(apply string-append
(map (lambda (elm)
(cond ((number? elm) (number->string elm))
((symbol? elm) (symbol->string elm))
((string? elm) elm)
(else
(/pmacro-error "invalid argument to %sym" elm))))
args))))
)
;; (%str string1 string2 ...) - string-append, auto-convert numbers
(define /pmacro-builtin-str
(lambda args
(apply string-append
(map (lambda (elm)
(cond ((number? elm) (number->string elm))
((symbol? elm) (symbol->string elm))
((string? elm) elm)
(else
(/pmacro-error "invalid argument to %str" elm))))
args)))
)
;; (%hex number [width]) - convert number to hex string
;; WIDTH, if present, is the number of characters in the result, beginning
;; from the least significant digit.
(define (/pmacro-builtin-hex num . width)
(if (> (length width) 1)
(/pmacro-error "wrong number of arguments to %hex"
(cons '%hex (cons num width))))
(let ((str (number->string num 16)))
(if (null? width)
str
(let ((len (string-length str)))
(substring (string-append (make-string (car width) #\0) str)
len (+ len (car width))))))
)
;; (%upcase string) - convert a string or symbol to uppercase
(define (/pmacro-builtin-upcase str)
(cond
((string? str) (string-upcase str))
((symbol? str) (string->symbol (string-upcase (symbol->string str))))
(else (/pmacro-error "invalid argument to %upcase" str)))
)
;; (%downcase string) - convert a string or symbol to lowercase
(define (/pmacro-builtin-downcase str)
(cond
((string? str) (string-downcase str))
((symbol? str) (string->symbol (string-downcase (symbol->string str))))
(else (/pmacro-error "invalid argument to %downcase" str)))
)
;; (%substring string start end) - get part of a string
;; `end' can be the symbol `end'.
(define (/pmacro-builtin-substring str start end)
(if (not (integer? start)) ;; FIXME: non-negative-integer
(/pmacro-error "start not an integer" start))
(if (and (not (integer? end))
(not (eq? end 'end)))
(/pmacro-error "end not an integer nor symbol `end'" end))
(cond ((string? str)
(if (eq? end 'end)
(substring str start)
(substring str start end)))
((symbol? str)
(if (eq? end 'end)
(string->symbol (substring (symbol->string str) start))
(string->symbol (substring (symbol->string str) start end))))
(else
(/pmacro-error "invalid argument to %substring" str)))
)
;; %splice - splicing support
;; Splice lists into the outer list.
;;
;; E.g. (define-pmacro '(splice-test a b c) '(%splice a (%unsplice b) c))
;; (pmacro-expand '(splice-test (1 (2) 3))) --> (1 2 3)
;;
;; Similar to `(1 ,@'(2) 3) in Scheme, though the terminology is slightly
;; different (??? may need to revisit). In Scheme there's quasi-quote,
;; unquote, unquote-splicing. Here we have splice, unsplice; with the proviso
;; that pmacros don't have the concept of "quoting", thus all subexpressions
;; are macro-expanded first, before performing any unsplicing.
;; [??? Some may want a quoting facility, but I'd like to defer adding it as
;; long as possible (and ideally never add it).]
;;
;; NOTE: The implementation relies on %unsplice being undefined so that
;; (%unsplice (42)) is expanded unchanged.
(define /pmacro-builtin-splice
(lambda arg-list
;; ??? Not the most efficient implementation.
(let* ((unsplice-str (if (rtl-version-at-least? 0 9) "%unsplice" ".unsplice"))
(unsplice-sym (string->symbol unsplice-str)))
(let loop ((arg-list arg-list) (result '()))
(cond ((null? arg-list) result)
((and (pair? (car arg-list)) (eq? unsplice-sym (caar arg-list)))
(if (= (length (car arg-list)) 2)
(if (list? (cadar arg-list))
(loop (cdr arg-list) (append result (cadar arg-list)))
(/pmacro-error (string-append "argument to " unsplice-str " must be a list")
(car arg-list)))
(/pmacro-error (string-append "wrong number of arguments to " unsplice-str)
(car arg-list))))
(else
(loop (cdr arg-list) (append result (list (car arg-list)))))))))
)
;; %iota
;; Usage:
;; (%iota count) ;; start=0, incr=1
;; (%iota count start) ;; incr=1
;; (%iota count start incr)
(define (/pmacro-builtin-iota count . start-incr)
(if (> (length start-incr) 2)
(/pmacro-error "wrong number of arguments to %iota"
(cons '%iota (cons count start-incr))))
(if (< count 0)
(/pmacro-error "count must be non-negative"
(cons '%iota (cons count start-incr))))
(let ((start (if (pair? start-incr) (car start-incr) 0))
(incr (if (= (length start-incr) 2) (cadr start-incr) 1)))
(let loop ((i start) (count count) (result '()))
(if (= count 0)
(reverse! result)
(loop (+ i incr) (- count 1) (cons i result)))))
)
;; (%map pmacro arg1 . arg-rest)
(define (/pmacro-builtin-map pmacro arg1 . arg-rest)
(if (not (/pmacro? pmacro))
(/pmacro-error "not a pmacro" pmacro))
(let ((transformer (/pmacro-transformer pmacro)))
(if (not (procedure? transformer))
(/pmacro-error "not a procedural pmacro" pmacro))
(apply map (cons transformer (cons arg1 arg-rest))))
)
;; (%for-each pmacro arg1 . arg-rest)
(define (/pmacro-builtin-for-each pmacro arg1 . arg-rest)
(if (not (/pmacro? pmacro))
(/pmacro-error "not a pmacro" pmacro))
(let ((transformer (/pmacro-transformer pmacro)))
(if (not (procedure? transformer))
(/pmacro-error "not a procedural pmacro" pmacro))
(apply for-each (cons transformer (cons arg1 arg-rest)))
nil) ;; need to return something the reader will accept and ignore
)
;; (%eval expr)
;; NOTE: This is implemented as a syntactic form in order to get ENV and LOC.
;; That's an implementation detail, and this is not really a syntactic form.
;;
;; ??? I debated whether to call this %expand, %eval has been a source of
;; confusion/headaches.
(define (/pmacro-builtin-eval loc env expr)
;; /pmacro-expand is invoked twice because we're implemented as a syntactic
;; form: We *want* to be passed an evaluated expression, and then we
;; re-evaluate it. But syntactic forms pass parameters unevaluated, so we
;; have to do the first one ourselves.
(/pmacro-expand (/pmacro-expand expr env loc) env loc)
)
;; (%exec expr)
(define (/pmacro-builtin-exec expr)
;; If we're expanding pmacros for debugging purposes, don't execute,
;; just return unchanged.
(if /pmacro-debug?
(list '%exec expr)
(begin
(reader-process-expanded! expr)
nil)) ;; need to return something the reader will accept and ignore
)
;; (%apply pmacro-name arg)
(define (/pmacro-builtin-apply pmacro arg-list)
(if (not (/pmacro? pmacro))
(/pmacro-error "not a pmacro" pmacro))
(let ((transformer (/pmacro-transformer pmacro)))
(if (not (procedure? transformer))
(/pmacro-error "not a procedural pmacro" pmacro))
(apply transformer arg-list))
)
;; (%pmacro (arg-list) expansion)
;; NOTE: syntactic form
(define (/pmacro-builtin-pmacro loc env params expansion)
;; ??? Prohibiting improper lists seems unnecessarily restrictive here.
;; e.g. (define (foo bar . baz) ...)
(if (not (list? params))
(/pmacro-error "%pmacro parameter-spec is not a list" params))
(/pmacro-make '%anonymous params #f #f
(/pmacro-build-lambda loc env params expansion) "")
)
;; (%pmacro? arg)
(define (/pmacro-builtin-pmacro? arg)
(/pmacro? arg)
)
;; (%let (var-list) expr1 . expr-rest)
;; NOTE: syntactic form
(define (/pmacro-builtin-let loc env locals expr1 . expr-rest)
(if (not (list? locals))
(/pmacro-error "locals is not a list" locals))
(if (not (all-true? (map (lambda (l)
(and (list? l)
(= (length l) 2)
(symbol? (car l))))
locals)))
(/pmacro-error "syntax error in locals list" locals))
(let* ((evald-locals (map (lambda (l)
(cons (car l) (/pmacro-expand (cadr l) env loc)))
locals))
(new-env (append! evald-locals env)))
(/pmacro-expand-expr-list (cons expr1 expr-rest) new-env loc))
)
;; (%let* (var-list) expr1 . expr-rest)
;; NOTE: syntactic form
(define (/pmacro-builtin-let* loc env locals expr1 . expr-rest)
(if (not (list? locals))
(/pmacro-error "locals is not a list" locals))
(if (not (all-true? (map (lambda (l)
(and (list? l)
(= (length l) 2)
(symbol? (car l))))
locals)))
(/pmacro-error "syntax error in locals list" locals))
(let loop ((locals locals) (new-env env))
(if (null? locals)
(/pmacro-expand-expr-list (cons expr1 expr-rest) new-env loc)
(loop (cdr locals) (acons (caar locals)
(/pmacro-expand (cadar locals) new-env loc)
new-env))))
)
;; (%if expr then [else])
;; NOTE: syntactic form
(define (/pmacro-builtin-if loc env expr then-clause . else-clause)
(case (length else-clause)
((0) (if (/pmacro-expand expr env loc)
(/pmacro-expand then-clause env loc)
nil))
((1) (if (/pmacro-expand expr env loc)
(/pmacro-expand then-clause env loc)
(/pmacro-expand (car else-clause) env loc)))
(else (/pmacro-error "too many elements in else-clause, expecting 0 or 1" else-clause)))
)
;; (%case expr ((case-list1) stmt) [case-expr-stmt-list] [(else stmt)])
;; NOTE: syntactic form
;; NOTE: this uses "member" for case comparison (Scheme uses memq I think)
(define (/pmacro-builtin-case loc env expr case1 . rest)
(let ((evald-expr (/pmacro-expand expr env loc)))
(let loop ((cases (cons case1 rest)))
(if (null? cases)
nil
(begin
(if (not (list? (car cases)))
(/pmacro-error "case statement not a list" (car cases)))
(if (= (length (car cases)) 1)
(/pmacro-error "case statement has case but no expr" (car cases)))
(if (and (not (eq? (caar cases) 'else))
(not (list? (caar cases))))
(/pmacro-error "case must be \"else\" or list of choices" (caar cases)))
(cond ((eq? (caar cases) 'else)
(/pmacro-expand-expr-list (cdar cases) env loc))
((member evald-expr (caar cases))
(/pmacro-expand-expr-list (cdar cases) env loc))
(else
(loop (cdr cases))))))))
)
;; (%cond (expr stmt) [(cond-expr-stmt-list)] [(else stmt)])
;; NOTE: syntactic form
(define (/pmacro-builtin-cond loc env expr1 . rest)
(let loop ((exprs (cons expr1 rest)))
(cond ((null? exprs)
nil)
((eq? (car exprs) 'else)
(/pmacro-expand-expr-list (cdar exprs) env loc))
(else
(let ((evald-expr (/pmacro-expand (caar exprs) env loc)))
(if evald-expr
(/pmacro-expand-expr-list (cdar exprs) env loc)
(loop (cdr exprs)))))))
)
;; (%begin . stmt-list)
;; NOTE: syntactic form
(define (/pmacro-builtin-begin loc env . rest)
(/pmacro-expand-expr-list rest env loc)
)
;; (%print . expr)
;; Strings have quotes removed.