File: litconv.rkt

package info (click to toggle)
racket 7.9%2Bdfsg1-2
  • links: PTS, VCS
  • area: main
  • in suites: bullseye
  • size: 178,684 kB
  • sloc: ansic: 282,112; lisp: 234,887; pascal: 70,954; sh: 27,112; asm: 16,268; makefile: 4,613; cpp: 2,715; ada: 1,681; javascript: 1,244; cs: 879; exp: 499; csh: 422; python: 274; xml: 106; perl: 104
file content (284 lines) | stat: -rw-r--r-- 11,501 bytes parent folder | download | duplicates (2)
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
#lang racket/base
(require (for-syntax racket/base
                     racket/lazy-require
                     "sc.rkt"
                     "lib.rkt"
                     "kws.rkt"
                     racket/syntax)
         syntax/parse/private/residual-ct ;; keep abs. path
         syntax/parse/private/residual)   ;; keep abs. path
(begin-for-syntax
 (lazy-require
  [syntax/private/keyword (options-select-value parse-keyword-options)]
  [syntax/parse/private/rep ;; keep abs. path
   (parse-kw-formals
    check-conventions-rules
    check-datum-literals-list
    create-aux-def)]))
;; FIXME: workaround for phase>0 bug in racket/runtime-path (and thus lazy-require)
;; Without this, dependencies don't get collected.
(require racket/runtime-path racket/syntax (for-meta 2 '#%kernel))
(define-runtime-module-path-index _unused_ 'syntax/parse/private/rep)

(provide define-conventions
         define-literal-set
         literal-set->predicate
         kernel-literals)

(define-syntax (define-conventions stx)

  (define-syntax-class header
    #:description "name or name with formal parameters"
    #:commit
    (pattern name:id
             #:with formals #'()
             #:attr arity (arity 0 0 null null))
    (pattern (name:id . formals)
             #:attr arity (parse-kw-formals #'formals #:context stx)))

  (syntax-parse stx
    [(define-conventions h:header rule ...)
     (let ()
       (define rules (check-conventions-rules #'(rule ...) stx))
       (define rxs (map car rules))
       (define dens0 (map cadr rules))
       (define den+defs-list
         (for/list ([den0 (in-list dens0)])
           (let-values ([(den defs) (create-aux-def den0)])
             (cons den defs))))
       (define dens (map car den+defs-list))
       (define defs (apply append (map cdr den+defs-list)))

       (define/with-syntax (rx ...) rxs)
       (define/with-syntax (def ...) defs)
       (define/with-syntax (parser ...)
         (map den:delayed-parser dens))
       (define/with-syntax (class-name ...)
         (map den:delayed-class dens))

       ;; FIXME: could move make-den:delayed to user of conventions
       ;; and eliminate from residual.rkt
       #'(begin
           (define-syntax h.name
             (make-conventions
              (quote-syntax get-parsers)
              (lambda ()
                (let ([class-names (list (quote-syntax class-name) ...)])
                  (map list
                       (list 'rx ...)
                       (map make-den:delayed
                            (generate-temporaries class-names)
                            class-names))))))
           (define get-parsers
             (lambda formals
               def ...
               (list parser ...)))))]))

(define-for-syntax (check-phase-level stx ctx)
  (unless (or (exact-integer? (syntax-e stx))
              (eq? #f (syntax-e stx)))
    (raise-syntax-error #f "expected phase-level (exact integer or #f)" ctx stx))
  stx)

;; check-litset-list : stx stx -> (listof (cons id literalset))
(define-for-syntax (check-litset-list stx ctx)
  (syntax-case stx ()
    [(litset-id ...)
     (for/list ([litset-id (syntax->list #'(litset-id ...))])
       (let* ([val (and (identifier? litset-id)
                        (syntax-local-value/record litset-id literalset?))])
         (if val
             (cons litset-id val)
             (raise-syntax-error #f "expected literal set name" ctx litset-id))))]
    [_ (raise-syntax-error #f "expected list of literal set names" ctx stx)]))

;; check-literal-entry/litset : stx stx -> (list id id)
(define-for-syntax (check-literal-entry/litset stx ctx)
  (syntax-case stx ()
    [(internal external)
     (and (identifier? #'internal) (identifier? #'external))
     (list #'internal #'external)]
    [id
     (identifier? #'id)
     (list #'id #'id)]
    [_ (raise-syntax-error #f "expected literal entry" ctx stx)]))

(define-for-syntax (check-duplicate-literals ctx imports lits datum-lits)
  (let ([lit-t (make-hasheq)]) ;; sym => #t
    (define (check+enter! key blame-stx)
      (when (hash-ref lit-t key #f)
        (raise-syntax-error #f (format "duplicate literal: ~a" key) ctx blame-stx))
      (hash-set! lit-t key #t))
    (for ([id+litset (in-list imports)])
      (let ([litset-id (car id+litset)]
            [litset (cdr id+litset)])
        (for ([entry (in-list (literalset-literals litset))])
          (cond [(lse:lit? entry)
                 (check+enter! (lse:lit-internal entry) litset-id)]
                [(lse:datum-lit? entry)
                 (check+enter! (lse:datum-lit-internal entry) litset-id)]))))
    (for ([datum-lit (in-list datum-lits)])
      (let ([internal (den:datum-lit-internal datum-lit)])
        (check+enter! (syntax-e internal) internal)))
    (for ([lit (in-list lits)])
      (check+enter! (syntax-e (car lit)) (car lit)))))

(define-syntax (define-literal-set stx)
  (syntax-case stx ()
    [(define-literal-set name . rest)
     (let-values ([(chunks rest)
                   (parse-keyword-options
                    #'rest
                    `((#:literal-sets ,check-litset-list)
                      (#:datum-literals ,check-datum-literals-list)
                      (#:phase ,check-phase-level)
                      (#:for-template)
                      (#:for-syntax)
                      (#:for-label))
                    #:incompatible '((#:phase #:for-template #:for-syntax #:for-label))
                    #:context stx
                    #:no-duplicates? #t)])
       (unless (identifier? #'name)
         (raise-syntax-error #f "expected identifier" stx #'name))
       (let ([relphase
              (cond [(assq '#:for-template chunks) -1]
                    [(assq '#:for-syntax chunks) 1]
                    [(assq '#:for-label chunks) #f]
                    [else (options-select-value chunks '#:phase #:default 0)])]
             [datum-lits
              (options-select-value chunks '#:datum-literals #:default null)]
             [lits (syntax-case rest ()
                     [( (lit ...) )
                      (for/list ([lit (in-list (syntax->list #'(lit ...)))])
                        (check-literal-entry/litset lit stx))]
                     [_ (raise-syntax-error #f "bad syntax" stx)])]
             [imports (options-select-value chunks '#:literal-sets #:default null)])
         (check-duplicate-literals stx imports lits datum-lits)
         (with-syntax ([((internal external) ...) lits]
                       [(datum-internal ...) (map den:datum-lit-internal datum-lits)]
                       [(datum-external ...) (map den:datum-lit-external datum-lits)]
                       [(litset-id ...) (map car imports)]
                       [relphase relphase])
           #`(begin
               (define phase-of-literals
                 (and 'relphase
                      (+ (variable-reference->module-base-phase (#%variable-reference))
                         'relphase)))
               (define-syntax name
                 (make-literalset
                  (append (literalset-literals (syntax-local-value (quote-syntax litset-id)))
                          ...
                          (list (make-lse:lit 'internal
                                              (quote-syntax external)
                                              (quote-syntax phase-of-literals))
                                ...
                                (make-lse:datum-lit 'datum-internal
                                                    'datum-external)
                                ...))))
               (begin-for-syntax/once
                (for ([x (in-list (list (quote-syntax external) ...))])
                  (unless (identifier-binding x 'relphase)
                    (raise-syntax-error #f
                                        (format "literal is unbound in phase ~a~a~a"
                                                'relphase
                                                (case 'relphase
                                                  ((1) " (for-syntax)")
                                                  ((-1) " (for-template)")
                                                  ((#f) " (for-label)")
                                                  (else ""))
                                                " relative to the enclosing module")
                                        (quote-syntax #,stx) x))))))))]))

#|
NOTES ON PHASES AND BINDINGS

(module M ....
  .... (define-literal-set LS #:phase PL ....)
  ....)

For the expansion of the define-literal-set form, the bindings of the literals
can be accessed by (identifier-binding lit PL), because the phase of the enclosing
module (M) is 0.

LS may be used, however, in a context where the phase of the enclosing
module is not 0, so each instantiation of LS needs to calculate the
phase of M and add that to PL.

--

Normally, literal sets that define the same name conflict. But it
would be nice to allow them to both be imported in the case where they
refer to the same binding.

Problem: Can't do the check eagerly, because the binding of L may
change between when define-literal-set is compiled and the comparison
involving L. For example:

  (module M racket
    (require syntax/parse)
    (define-literal-set LS (lambda))
    (require (only-in some-other-lang lambda))
    .... LS ....)

The expansion of the LS definition sees a different lambda than the
one that the literal in LS actually refers to.

Similarly, a literal in LS might not be defined when the expander
runs, but might get defined later. (Although I think that will already
cause an error, so don't worry about that case.)
|#

;; FIXME: keep one copy of each identifier (?)

(define-syntax (literal-set->predicate stx)
  (syntax-case stx ()
    [(literal-set->predicate litset-id)
     (let ([val (and (identifier? #'litset-id)
                     (syntax-local-value/record #'litset-id literalset?))])
       (unless val (raise-syntax-error #f "expected literal set name" stx #'litset-id))
       (let ([lits (literalset-literals val)])
         (with-syntax ([((lit phase-var) ...)
                        (for/list ([lit (in-list lits)]
                                   #:when (lse:lit? lit))
                          (list (lse:lit-external lit) (lse:lit-phase lit)))]
                       [(datum-lit ...)
                        (for/list ([lit (in-list lits)]
                                   #:when (lse:datum-lit? lit))
                          (lse:datum-lit-external lit))])
           #'(make-literal-set-predicate (list (list (quote-syntax lit) phase-var) ...)
                                         '(datum-lit ...)))))]))

(define (make-literal-set-predicate lits datum-lits)
  (lambda (x [phase (syntax-local-phase-level)])
    (or (for/or ([lit (in-list lits)])
          (let ([lit-id (car lit)]
                [lit-phase (cadr lit)])
            (free-identifier=? x lit-id phase lit-phase)))
        (and (memq (syntax-e x) datum-lits) #t))))

;; Literal sets

(define-literal-set kernel-literals
  (begin
   begin0
   define-values
   define-syntaxes
   define-values-for-syntax ;; kept for compat.
   begin-for-syntax
   set!
   let-values
   letrec-values
   #%plain-lambda
   case-lambda
   if
   quote
   quote-syntax
   letrec-syntaxes+values
   with-continuation-mark
   #%expression
   #%plain-app
   #%top
   #%datum
   #%variable-reference
   module module* #%provide #%require #%declare
   #%plain-module-begin))