stxparse-info/case/syntax.rkt

212 lines
7.8 KiB
Racket

#lang racket/base
(require (only-in "stxloc.rkt" syntax-case)
stxparse-info/current-pvars
(for-syntax racket/base
racket/private/sc))
(provide define/with-syntax
current-recorded-disappeared-uses
with-disappeared-uses
syntax-local-value/record
record-disappeared-uses
format-symbol
format-id
current-syntax-context
wrong-syntax
generate-temporary
internal-definition-context-apply
syntax-local-eval
with-syntax*)
;; == Defining pattern variables ==
(define-syntax (define/with-syntax stx)
(syntax-case stx ()
[(define/with-syntax pattern rhs)
(let* ([pvar-env (get-match-vars #'define/with-syntax
stx
#'pattern
'())]
[depthmap (for/list ([x pvar-env])
(let loop ([x x] [d 0])
(if (pair? x)
(loop (car x) (add1 d))
(cons x d))))]
[pvars (map car depthmap)]
[depths (map cdr depthmap)]
[mark (make-syntax-introducer)])
(with-syntax ([(pvar ...) pvars]
[(depth ...) depths]
[(valvar ...) (generate-temporaries pvars)])
#'(begin (define-values (valvar ...)
(with-syntax ([pattern rhs])
(values (pvar-value pvar) ...)))
(define-syntax pvar
(make-syntax-mapping 'depth (quote-syntax valvar)))
...
(define-pvars (pvar ...)))))]))
;; Ryan: alternative name: define/syntax-pattern ??
;; auxiliary macro
(define-syntax (pvar-value stx)
(syntax-case stx ()
[(_ pvar)
(identifier? #'pvar)
(let ([mapping (syntax-local-value #'pvar)])
(unless (syntax-pattern-variable? mapping)
(raise-syntax-error #f "not a pattern variable" #'pvar))
(syntax-mapping-valvar mapping))]))
;; == Disappeared uses ==
(define current-recorded-disappeared-uses (make-parameter #f))
(define-syntax-rule (with-disappeared-uses body-expr ... stx-expr)
(let-values ([(stx disappeared-uses)
(parameterize ((current-recorded-disappeared-uses null))
(let ([result (let () body-expr ... stx-expr)])
(values result (current-recorded-disappeared-uses))))])
(syntax-property stx
'disappeared-use
(append (or (syntax-property stx 'disappeared-use) null)
disappeared-uses))))
(define (syntax-local-value/record id pred)
(unless (identifier? id)
(raise-argument-error 'syntax-local-value/record
"identifier?"
0 id pred))
(unless (and (procedure? pred)
(procedure-arity-includes? pred 1))
(raise-argument-error 'syntax-local-value/record
"(-> any/c boolean?)"
1 id pred))
(let ([value (syntax-local-value id (lambda () #f))])
(and (pred value)
(begin (record-disappeared-uses (list id))
value))))
(define (record-disappeared-uses ids)
(cond
[(identifier? ids) (record-disappeared-uses (list ids))]
[(and (list? ids) (andmap identifier? ids))
(let ([uses (current-recorded-disappeared-uses)])
(when uses
(current-recorded-disappeared-uses
(append
(if (syntax-transforming?)
(map syntax-local-introduce ids)
ids)
uses))))]
[else (raise-argument-error 'record-disappeared-uses
"(or/c identifier? (listof identifier?))"
ids)]))
;; == Identifier formatting ==
(define (format-id lctx
#:source [src #f]
#:props [props #f]
#:cert [cert #f]
fmt . args)
(define (convert x) (->atom x 'format-id))
(check-restricted-format-string 'format-id fmt)
(let* ([args (map convert args)]
[str (apply format fmt args)]
[sym (string->symbol str)])
(datum->syntax lctx sym src props cert)))
;; Eli: This looks very *useful*, but I'd like to see it more convenient to
;; "preserve everything". Maybe add a keyword argument that when #t makes
;; all the others use values lctx, and when syntax makes the others use that
;; syntax?
;; Finally, if you get to add this, then another useful utility in the same
;; spirit is one that concatenates symbols and/or strings and/or identifiers
;; into a new identifier. I considered something like that, which expects a
;; single syntax among its inputs, and will use it for the context etc, or
;; throw an error if there's more or less than 1.
(define (format-symbol fmt . args)
(define (convert x) (->atom x 'format-symbol))
(check-restricted-format-string 'format-symbol fmt)
(let ([args (map convert args)])
(string->symbol (apply format fmt args))))
(define (restricted-format-string? fmt)
(regexp-match? #rx"^(?:[^~]|~[aAn~%])*$" fmt))
(define (check-restricted-format-string who fmt)
(unless (restricted-format-string? fmt)
(raise-arguments-error who
(format "format string should have ~a placeholders"
fmt)
"format string" fmt)))
(define (->atom x err)
(cond [(string? x) x]
[(symbol? x) x]
[(identifier? x) (syntax-e x)]
[(keyword? x) (keyword->string x)]
[(number? x) x]
[(char? x) x]
[else (raise-argument-error err
"(or/c string? symbol? identifier? keyword? char? number?)"
x)]))
;; == Error reporting ==
(define current-syntax-context
(make-parameter #f
(lambda (new-value)
(unless (or (syntax? new-value) (eq? new-value #f))
(raise-argument-error 'current-syntax-context
"(or/c syntax? #f)"
new-value))
new-value)))
(define (wrong-syntax stx #:extra [extras null] format-string . args)
(unless (or (eq? stx #f) (syntax? stx))
(raise-argument-error 'wrong-syntax "(or/c syntax? #f)" 0 (list* stx format-string args)))
(let* ([ctx (current-syntax-context)]
[blame (and (syntax? ctx) (syntax-property ctx 'report-error-as))])
(raise-syntax-error (if (symbol? blame) blame #f)
(apply format format-string args)
ctx
stx
extras)))
;; Eli: The `report-error-as' thing seems arbitrary to me.
;; == Other utilities ==
;; generate-temporary : any -> identifier
(define (generate-temporary [stx 'g])
(car (generate-temporaries (list stx))))
;; Applies the renaming of intdefs to stx.
(define (internal-definition-context-apply intdefs stx)
(let ([qastx (local-expand #`(quote #,stx) 'expression (list #'quote) intdefs)])
(with-syntax ([(q astx) qastx]) #'astx)))
(define (syntax-local-eval stx [intdef0 #f])
(let* ([name (generate-temporary)]
[intdefs (syntax-local-make-definition-context intdef0)])
(syntax-local-bind-syntaxes (list name)
#`(call-with-values (lambda () #,stx) list)
intdefs)
(internal-definition-context-seal intdefs)
(apply values
(syntax-local-value (internal-definition-context-apply intdefs name)
#f intdefs))))
(define-syntax (with-syntax* stx)
(syntax-case stx ()
[(_ (cl) body ...) #'(with-syntax (cl) body ...)]
[(_ (cl cls ...) body ...)
#'(with-syntax (cl) (with-syntax* (cls ...) body ...))]))