syntax/parse: added experimental template form

Also added more atomic patterns to syntax-parse (previously overlooked).
This commit is contained in:
Ryan Culpepper 2012-02-10 04:10:51 -07:00
parent 5a6ed440a1
commit 06979954fa
5 changed files with 561 additions and 1 deletions

View File

@ -0,0 +1,359 @@
#lang racket/base
(require (for-syntax racket/base
racket/set
racket/syntax
racket/match
racket/private/sc
unstable/struct)
racket/match
racket/vector
syntax/stx
syntax/parse/private/residual
unstable/struct)
(provide template
??
?@)
#|
To do:
- improve error messages
- support flexible depths, eg
(with-syntax ([(a ...) #'(1 2 3)]
[((b ...) ...) #'((1 2 3) (4 5 6) (7 8 9))])
#'(((a b) ...) ...)) ;; a has depth 1, used at depth 2
|#
#|
A Template (T) is one of:
- pvar
- atom (including (), not pvar)
- (H . T)
- (H ... . T), (H ... ... . T), etc
- (?? T T)
- ... other standard compound forms
A HeadTemplate (H) is one of:
- T
- (?? T)
- (?@ . T)
|#
(define-syntax (template stx)
(parameterize ((current-syntax-context stx))
(syntax-case stx ()
[(template t)
(let-values ([(guide pvars) (parse-template #'t)])
;; (eprintf "guide = ~s\n" guide)
(with-syntax ([guide
guide]
[(var ...)
(for/list ([pvar (in-vector pvars)])
(let* ([valvar (syntax-mapping-valvar pvar)]
[attr (syntax-local-value valvar (lambda () #f))])
(cond [(attribute-mapping? attr)
(attribute-mapping-var attr)]
[else valvar])))])
(syntax-arm #'(substitute (quote-syntax t) 'guide (vector var ...)))))])))
(define-syntaxes (?? ?@)
(let ([tx (lambda (stx) (raise-syntax-error #f "not allowed as an expression" stx))])
(values tx tx)))
;; ============================================================
#|
A Guide (G) is one of:
- _
- (G . G)
- positive-integer
- negative-integer
- #s(stxvector G)
- #s(stxstruct G)
- #&G
- #s(dots HG (vector-of integer) nat G)
- #s(app HG G)
- #s(escaped G)
- #s(orelse G (vector-of integer) G)
A HeadGuide (HG) is one of:
- G
- #s(app-opt G (vector-of integer))
- #s(splice G)
|#
(define-syntax-rule (begin-both-phases form ...)
(begin (begin-for-syntax form ...)
(begin form ...)))
(begin-both-phases
(struct stxvector (g) #:prefab)
(struct stxstruct (g) #:prefab)
(struct dots (head hdrivers nesting tail) #:prefab)
(struct app (head tail) #:prefab)
(struct escaped (body) #:prefab)
(struct orelse (g1 drivers1 g2) #:prefab)
(struct app-opt (g drivers) #:prefab)
(struct splice (g) #:prefab))
;; ============================================================
(begin-for-syntax
(define (parse-template t)
(let-values ([(_const? drivers pre-guide) (parse-t t 0 #f)])
;; (eprintf "pre-guide = ~s\n" pre-guide)
(define (pvar-set->env drivers)
(for/hash ([pvar (in-set drivers)]
[n (in-naturals 1)])
(values pvar n)))
(define main-env (pvar-set->env drivers))
(define (loop g loop-env)
(define (pvar->index pvar)
(let ([loop-index (hash-ref loop-env pvar #f)])
(if loop-index
(- loop-index)
(hash-ref main-env pvar))))
(match g
['_ '_]
[(cons g1 g2) (cons (loop g1 loop-env) (loop g2 loop-env))]
[(? syntax-pattern-variable? pvar) (pvar->index pvar)]
[(dots head hdrivers nesting tail)
(let* ([sub-loop-env (pvar-set->env hdrivers)]
[sub-loop-vector (index-hash->vector sub-loop-env pvar->index)])
(dots (loop head sub-loop-env)
sub-loop-vector
nesting
(loop tail loop-env)))]
[(app head tail)
(app (loop head loop-env) (loop tail loop-env))]
[(escaped g1)
(escaped (loop g1 loop-env))]
[(orelse g1 drivers1 g2)
(orelse (loop g1 loop-env)
(for/vector ([pvar (in-set drivers1)])
(pvar->index pvar))
(loop g2 loop-env))]
[(stxvector g1)
(stxvector (loop g1 loop-env))]
[(stxstruct g1)
(stxstruct (loop g1 loop-env))]
[(? box?)
(box (loop (unbox g) loop-env))]
[(app-opt g1 drivers1)
(app-opt (loop g1 loop-env)
(for/vector ([pvar (in-set drivers1)])
(pvar->index pvar)))]
[(splice g1)
(splice (loop g1 loop-env))]
[else (error 'parse:convert "bad pre-guide: ~e" g)]))
(define guide (loop pre-guide #hash()))
(values guide
(index-hash->vector main-env))))
;; ----------------------------------------
(define (parse-t t depth esc?)
(syntax-case t (?? ?@)
[id
(identifier? #'id)
(cond [(and (not esc?)
(or (free-identifier=? #'id (quote-syntax ...))
(free-identifier=? #'id (quote-syntax ??))
(free-identifier=? #'id (quote-syntax ?@))))
(wrong-syntax #'id "illegal use")]
[else
(let ([pvar (lookup-pvar #'id depth)])
(cond [pvar (values #f (set pvar) pvar)]
[else (values #t (set) '_)]))])]
[atom
(atom? (syntax-e #'atom))
(values #t (set) '_)]
[(head DOTS . tail)
(and (not esc?)
(identifier? #'DOTS) (free-identifier=? #'DOTS (quote-syntax ...)))
(let-values ([(nesting tail)
(let loop ([nesting 1] [tail #'tail])
(syntax-case tail ()
[(DOTS . tail)
(and (identifier? #'DOTS) (free-identifier=? #'DOTS (quote-syntax ...)))
(loop (add1 nesting) #'tail)]
[else (values nesting tail)]))])
(let-values ([(hconst? hdrivers _hsplice? hguide) (parse-h #'head (+ depth nesting) esc?)]
[(tconst? tdrivers tguide) (parse-t tail depth esc?)])
(values #f
(set-union hdrivers tdrivers)
(dots hguide hdrivers nesting tguide))))]
[(DOTS template)
(and (not esc?)
(identifier? #'DOTS) (free-identifier=? #'DOTS (quote-syntax ...)))
(let-values ([(const? drivers guide) (parse-t #'template depth #t)])
(values #f drivers (escaped guide)))]
[(?? t1 t2)
(not esc?)
(let-values ([(const1? drivers1 guide1) (parse-t #'t1 depth esc?)]
[(const2? drivers2 guide2) (parse-t #'t2 depth esc?)])
(values #f
(set-union drivers1 drivers2)
(orelse guide1 drivers1 guide2)))]
[(?? . _)
(not esc?)
(wrong-syntax t "bad pattern")]
[(head . tail)
(let-values ([(hconst? hdrivers hsplice? hguide) (parse-h #'head depth esc?)]
[(tconst? tdrivers tguide) (parse-t #'tail depth esc?)])
(let ([const? (and hconst? tconst?)])
(values const?
(set-union hdrivers tdrivers)
(cond [const? '_]
[hsplice? (app hguide tguide)]
[else (cons hguide tguide)]))))]
[vec
(vector? (syntax-e #'vec))
(let-values ([(const? drivers guide) (parse-t (vector->list (syntax-e #'vec)) depth esc?)])
(values const? drivers (if const? '_ (stxvector guide))))]
[pstruct
(prefab-struct-key (syntax-e #'pstruct))
(let-values ([(const? drivers guide) (parse-t (struct->list (syntax-e #'pstruct)) depth esc?)])
(values const? drivers (if const? '_ (stxstruct guide))))]
[#&template
(let-values ([(const? drivers guide) (parse-t #'template depth esc?)])
(values const? drivers (if const? '_ (box guide))))]
[_ (wrong-syntax t "bad pattern")]))
(define (parse-h h depth esc?)
(syntax-case h (?? ?@)
[(?? t)
(not esc?)
(let-values ([(const? drivers guide) (parse-t #'t depth esc?)])
(values #f drivers #t (app-opt guide drivers)))]
[(?@ . t)
(not esc?)
(let-values ([(const? drivers guide) (parse-t #'t depth esc?)])
(values #f drivers #t (splice guide)))]
[t
(let-values ([(const? drivers guide) (parse-t #'t depth esc?)])
(values const? drivers #f guide))]))
(define (atom? x)
(or (null? x)
(number? x)
(boolean? x)
(string? x)
(bytes? x)
(keyword? x)
(regexp? x)
(char? x)))
(define (lookup-pvar id depth)
(let ([v (syntax-local-value id (lambda () #f))])
(cond [(syntax-pattern-variable? v)
(unless (or (= (syntax-mapping-depth v) depth)
(= (syntax-mapping-depth v) 0))
(wrong-syntax id
"pattern variable used at wrong ellipsis depth (expected ~s, used at ~s)"
(syntax-mapping-depth v)
depth))
v]
[else #f])))
(define (index-hash->vector hash [f values])
(let ([vec (make-vector (hash-count hash))])
(for ([(value index) (in-hash hash)])
(vector-set! vec (sub1 index) (f value)))
vec))
)
;; ============================================================
(define (substitute stx g main-env)
;; (eprintf "main-env = ~s\n" main-env)
(define (get index lenv)
(cond [(positive? index)
(vector-ref main-env (sub1 index))]
[else
(vector-ref lenv (- -1 index))]))
(define (loop stx g lenv)
(match g
['_ stx]
[(cons g1 g2)
(restx stx (cons (loop (stx-car stx) g1 lenv) (loop (stx-cdr stx) g2 lenv)))]
[(? exact-integer? index)
(let ([v (get index lenv)])
(unless (syntax? v)
(error 'template "syntax pattern variable is not syntax-valued"))
v)]
[(dots ghead henv nesting gtail)
(define head-stx (stx-car stx))
(define (nestloop lenv* nesting)
(cond [(zero? nesting)
(loop-h head-stx ghead lenv*)]
[else
(for ([v (in-vector lenv*)])
(unless v (error 'template "loop variable is not defined")))
(let ([len0 (length (vector-ref lenv* 0))])
(for ([v (in-vector lenv*)])
(unless (= len0 (length v))
(error 'template "loop variable count mismatch")))
(let dotsloop ([len0 len0] [lenv* lenv*])
(if (zero? len0)
null
(let ([lenv** (vector-map car lenv*)])
(cons (nestloop lenv** (sub1 nesting))
(dotsloop (sub1 len0) (vector-map! cdr lenv*)))))))]))
(let ([head-results ;; (listof^nesting (listof stx)) -- extra listof for loop-h
(nestloop (vector-map (lambda (index) (get index lenv)) henv) nesting)]
[tail-result (loop (stx-drop nesting (stx-cdr stx)) gtail lenv)])
(restx stx (deep-append head-results nesting tail-result)))]
[(app ghead gtail)
(restx stx (append (loop-h (stx-car stx) ghead lenv)
(loop (stx-cdr stx) gtail lenv)))]
[(escaped g1)
(loop (stx-cadr stx) g1 lenv)]
[(orelse g1 drivers1 g2)
(if (for/and ([index (in-vector drivers1)]) (get index lenv))
(loop (stx-cadr stx) g1 lenv)
(loop (stx-caddr stx) g2 lenv))]
[(stxvector g1)
(restx stx (list->vector (loop (vector->list (syntax-e stx)) g1 lenv)))]
[(stxstruct g1)
(let ([s (syntax-e stx)])
(restx stx (apply make-prefab-struct
(prefab-struct-key s)
(loop (struct->list s) g1 lenv))))]
[(box g1)
(restx stx (box (loop (unbox (syntax-e stx)) g1 lenv)))]))
(define (loop-h stx hg lenv)
(match hg
[(app-opt g1 drivers1)
(if (for/and ([index (in-vector drivers1)]) (get index lenv))
(list (loop (stx-cadr stx) g1 lenv))
null)]
[(splice g1)
(let* ([v (loop (stx-cdr stx) g1 lenv)]
[v* (stx->list v)])
(unless v*
(error 'template "not a syntax list: ~e" v))
v*)]
[else (list (loop stx hg lenv))]))
(loop stx g #f))
(define (stx-cadr x) (stx-car (stx-cdr x)))
(define (stx-cddr x) (stx-cdr (stx-cdr x)))
(define (stx-caddr x) (stx-car (stx-cdr (stx-cdr x))))
(define (stx-drop n x)
(cond [(zero? n) x]
[else (stx-drop (sub1 n) (stx-cdr x))]))
(define (restx basis val)
(if (syntax? basis)
(datum->syntax basis val basis basis)
val))
;; deep-append : (listof^(nesting+1) A) nat (listof A) -> (listof A)
;; (Actually, in practice onto is stx, so this is an improper append.)
(define (deep-append lst nesting onto)
(cond [(null? lst) onto]
[(zero? nesting) (append lst onto)]
[else (deep-append (car lst) (sub1 nesting)
(deep-append (cdr lst) nesting onto))]))

View File

@ -76,7 +76,10 @@
(boolean? datum)
(string? datum)
(number? datum)
(keyword? datum))))
(keyword? datum)
(bytes? datum)
(char? datum)
(regexp? datum))))
(define (id-predicate kw)
(lambda (stx)

View File

@ -264,3 +264,95 @@ patterns as @racket[target-stxclass-id] but with the given
(syntax-parse #'(8 9) [(n:nat>10 ...) 'ok])
]
}
@section{Syntax Templates}
@defmodule[syntax/parse/experimental/template]
@(define literal-ellipsis (racket ...))
@defform/subs[#:literals (?? ?@)
(template tmpl)
([tmpl pattern-variable-id
atomic-tmpl
(head-tmpl . tmpl)
(head-tmpl ellipsis ...+ . tmpl)
(?? tmpl tmpl)
#(@#,svar[tmpl] ...)
#s(prefab-struct-key @#,svar[tmpl] ...)
#&@#,svar[tmpl]]
[head-templ tmpl
(?? tmpl)
(?@ . tmpl)]
[ellipsis @#,literal-ellipsis])]{
Constructs a syntax object from a syntax template, like
@racket[syntax], but provides additional templating forms for dealing
with optional terms and splicing sequences of terms.
@specsubform[#:literals (??)
(?? tmpl alt-tmpl)]{
Produces @racket[tmpl] unless any attribute used in @racket[tmpl] has
an absent value; in that case, @racket[alt-tmpl] is used instead.
@examples[#:eval the-eval
(syntax-parse #'(m 1 2 3)
[(_ (~optional (~seq #:op op:expr)) arg:expr ...)
(template ((?? op +) arg ...))])
(syntax-parse #'(m #:op max 1 2 3)
[(_ (~optional (~seq #:op op:expr)) arg:expr ...)
(template ((?? op +) arg ...))])
]
}
@specsubform[#:literals (??)
(?? tmpl)]{
Produces @racket[tmpl] unless any attribute used in @racket[tmpl] has
an absent value; in that case, the term is omitted. Can only occur in
head position in a template.
@examples[#:eval the-eval
(syntax-parse #'(m 1)
[(_ x:expr (~optional y:expr))
(template (m2 x (?? y)))])
(syntax-parse #'(m 1 2)
[(_ x:expr (~optional y:expr))
(template (m2 x (?? y)))])
]
}
@specsubform[#:literals (?@)
(?@ . tmpl)]{
Similar to @racket[unquote-splicing], splices the result of
@racket[tmpl] (which must be a syntax list) into the surrounding
template. Can only occur in head position in a template.
@examples[#:eval the-eval
(syntax-parse #'(m #:a 1 #:b 2 3 4 #:e 5)
[(_ (~or pos:expr (~seq kw:keyword kwarg:expr)) ...)
(template (m2 (?@ kw kwarg) ... pos ...))])
]
The @racket[tmpl] must produce proper syntax lists, but it does not
itself need to be expressed as a proper list. For example, to unpack
pattern variables that contain syntax lists, use a ``dotted''
template:
@examples[#:eval the-eval
(with-syntax ([x #'(a b c)])
(template ((?@ . x) d)))
(with-syntax ([(x ...) #'((1 2 3) (4 5))])
(template ((?@ . x) ...)))
]
}
}
@deftogether[[
@defidform[??]
@defidform[?@]
]]{
Auxiliary forms used by @racket[template].
}

View File

@ -37,6 +37,7 @@
syntax/parse/experimental/contract
syntax/parse/experimental/reflect
syntax/parse/experimental/specialize
syntax/parse/experimental/template
syntax/parse/experimental/eh)])
`((for-syntax racket/base ,@mods)
,@mods)))))))
@ -106,6 +107,7 @@
syntax/parse/experimental/reflect
syntax/parse/experimental/provide
syntax/parse/experimental/specialize
syntax/parse/experimental/template
syntax/parse/experimental/eh
"parse-dummy-bindings.rkt"))
(provide (for-label (all-from-out racket/base)
@ -117,5 +119,6 @@
(all-from-out syntax/parse/experimental/reflect)
(all-from-out syntax/parse/experimental/provide)
(all-from-out syntax/parse/experimental/specialize)
(all-from-out syntax/parse/experimental/template)
(all-from-out syntax/parse/experimental/eh)
(all-from-out "parse-dummy-bindings.rkt")))

View File

@ -0,0 +1,103 @@
#lang racket/base
(require (for-syntax racket/base)
rackunit
racket/syntax
syntax/parse
syntax/parse/experimental/template)
;; FIXME: need to test errors, too
(define-syntax (tc stx)
(syntax-case stx ()
[(tc expr expected)
#`(test-equal? (format "line ~s" #,(syntax-line stx))
(syntax->datum expr)
expected)]))
;; ----------------------------------------
;; Common pattern variable definitions
;; (avoids having to have 'with-syntax' in every test case)
(define/with-syntax uu #'ABC)
(define/with-syntax (aa ...) #'(a b c))
(define/with-syntax (xx ...) #'(x y z))
(define/with-syntax (nn ...) #'(1 2 3))
(define/with-syntax ((yy ...) ...) #'((1 2 3) (4 5 6) (7 8 9)))
(define/syntax-parse (~or oo:nat _:id) #'x)
(define/syntax-parse ((~describe "x" (~or pp:nat _:id)) ...) #'(a 1 b 2 3))
;; ----------------------------------------
(tc (template uu) 'ABC)
;; FIXME: add other atoms when supported
;; FIXME: add other compound stx when supported
(tc (template abz) 'abz)
(tc (template ()) '())
(tc (template 5) '5)
(tc (template (1 2 #f #t "hey")) '(1 2 #f #t "hey"))
(tc (template (1 . b)) '(1 . b))
(tc (template (1 . uu)) '(1 . ABC))
(tc (template #(aa ... done))
'#(a b c done))
(tc (template #s(blah xx ...))
'#s(blah x y z))
(tc (template (aa ...))
'(a b c))
(tc (template ((aa aa) ...))
'((a a) (b b) (c c)))
(tc (template (start (aa ok) ... done))
'(start (a ok) (b ok) (c ok) done))
(tc (template ((aa nn xx) ...))
'((a 1 x) (b 2 y) (c 3 z)))
(tc (template (aa ... ((nn xx) ...)))
'(a b c ((1 x) (2 y) (3 z))))
(tc (template (aa ... (nn xx) ...))
'(a b c (1 x) (2 y) (3 z)))
(tc (template (aa ... ((yy ok) ...) ...))
'(a b c ((1 ok) (2 ok) (3 ok)) ((4 ok) (5 ok) (6 ok)) ((7 ok) (8 ok) (9 ok))))
(tc (template ((?@ 1 2) 3))
'(1 2 3))
(tc (with-syntax ([w '(1 2 3)])
(template ((?@ 0 . w) 4)))
'(0 1 2 3 4))
(tc (template ((?@ aa ok) ...))
'(a ok b ok c ok))
(tc (template ((?@ aa nn) ...))
'(a 1 b 2 c 3))
(tc (template (aa ... (?@ nn xx) ...))
'(a b c 1 x 2 y 3 z))
;; escape
(tc (template (abc (xx (... (q ...))) ...))
'(abc (x (q ...)) (y (q ...)) (z (q ...))))
(tc (template (abc (xx (... (q ... nn))) ...))
'(abc (x (q ... 1)) (y (q ... 2)) (z (q ... 3))))
;; consecutive ellipses
(tc (template (yy ... ...))
'(1 2 3 4 5 6 7 8 9))
;; ??
(tc (template (?? (ok oo go) nah))
'nah)
(tc (template ((?? (ready oo)) done))
'(done))
;; ----------------------------------------
;; combined ?? ?@
(tc (syntax-parse #'(a b c 1 2 3)
[(a:id ... (~optional s:str) n:nat ...)
(template (a ... n ... (?@ . (?? (string: s) ()))))])
'(a b c 1 2 3))
(tc (syntax-parse #'(a b c "hello!" 1 2 3)
[(a:id ... (~optional s:str) n:nat ...)
(template (a ... n ... (?@ . (?? (string: s) ()))))])
'(a b c 1 2 3 string: "hello!"))