auto-force attributes in syntax templates

This commit is contained in:
Ryan Culpepper 2013-05-31 16:43:07 -04:00
parent aaaebe2e41
commit aff7153494
6 changed files with 182 additions and 79 deletions

View File

@ -696,7 +696,7 @@ Conventions:
(for/fold ([k #'k]) ([side (in-list (reverse (syntax->list #'(side ...))))])
(syntax-case side ()
[#s(clause:attr a expr)
#`(let-attributes ([a (wrap-user-code (check-list^depth a expr))])
#`(let-attributes ([a (wrap-user-code expr)])
#,k)]))]))
(begin-for-syntax

View File

@ -1,7 +1,8 @@
#lang racket/base
(require (for-syntax racket/base)
racket/stxparam
racket/lazy-require)
racket/lazy-require
racket/private/promise)
;; ============================================================
;; Compile-time
@ -33,10 +34,11 @@
[else #f]))
(attribute-mapping-name self))])
#`(let ([value #,(attribute-mapping-var self)])
(check-attr-value-is-syntax '#,(attribute-mapping-depth self)
value
(quote-syntax #,source-name))
value)))))
(if (syntax-list^depth? '#,(attribute-mapping-depth self) value)
value
(check/force-syntax-list^depth '#,(attribute-mapping-depth self)
value
(quote-syntax #,source-name))))))))
)
;; ============================================================
@ -54,7 +56,6 @@
attribute-binding
stx-list-take
stx-list-drop/cx
check-list^depth*
check-literal*
begin-for-syntax/once
@ -131,30 +132,45 @@
(if (syntax? x) x cx)
(sub1 n)))))
;; check-attr-value-is-syntax : nat any id -> boolean
;; returns #t if value is a (listof^depth syntax)
;; used by attribute-mapping code above
(define (check-attr-value-is-syntax depth value source-id)
(define (check-syntax depth value)
(if (zero? depth)
(syntax? value)
(and (list? value)
(for/and ([part (in-list value)])
(check-syntax (sub1 depth) part)))))
(unless (check-syntax depth value)
;; check/force-syntax-list^depth : nat any id -> (listof^depth syntax)
;; Checks that value is (listof^depth syntax); forces promises.
;; Slow path for attribute-mapping code, assumes value is not syntax-list^depth? already.
(define (check/force-syntax-list^depth depth value0 source-id)
(define (bad)
(raise-syntax-error #f
(format "attribute is bound to non-syntax value: ~e" value)
source-id)))
(format "attribute is bound to non-syntax value: ~e" value0)
source-id))
(define (loop depth value)
(cond [(promise? value)
(loop depth (force value))]
[(zero? depth)
(if (syntax? value) value (bad))]
[else (loop-list depth value)]))
(define (loop-list depth value)
(cond [(promise? value)
(loop-list depth (force value))]
[(pair? value)
(let ([new-car (loop (sub1 depth) (car value))]
[new-cdr (loop-list depth (cdr value))])
;; Don't copy unless necessary
(if (and (eq? new-car (car value))
(eq? new-cdr (cdr value)))
value
(cons new-car new-cdr)))]
[(null? value)
null]
[else
(bad)]))
(loop depth value0))
;; check-list^depth* : symbol nat any -> list^depth
(define (check-list^depth* aname n0 v0)
(define (loop n v)
(when (positive? n)
(unless (list? v)
(raise-type-error aname (format "lists nested ~s deep" n0) v))
(for ([x (in-list v)]) (loop (sub1 n) x))))
(loop n0 v0)
v0)
;; syntax-list^depth? : nat any -> boolean
;; Returns true iff value is (listof^depth syntax).
(define (syntax-list^depth? depth value)
(if (zero? depth)
(syntax? value)
(and (list? value)
(for/and ([part (in-list value)])
(syntax-list^depth? (sub1 depth) part)))))
;; check-literal* : id phase phase (listof phase) stx -> void
(define (check-literal* id used-phase mod-phase ok-phases/ct-rel ctx)

View File

@ -24,7 +24,6 @@
let/unpack
defattrs/unpack
check-list^depth
check-literal
no-shadow
@ -142,14 +141,6 @@ residual.rkt.
(define-syntax name (make-syntax-mapping 'depth (quote-syntax stmp)))
...)))]))
;; (check-list^depth attr expr)
(define-syntax (check-list^depth stx)
(syntax-case stx ()
[(_ a expr)
(with-syntax ([#s(attr name depth syntax?) #'a])
(quasisyntax/loc #'expr
(check-list^depth* 'name 'depth expr)))]))
;; (check-literal id phase-level-expr ctx) -> void
(define-syntax (check-literal stx)
(syntax-case stx ()

View File

@ -31,7 +31,8 @@
(lambda (e) (raise (fixup e)))])
(thunk))))])
(make-evaluator 'racket/base
#:requires (let ([mods '(syntax/parse
#:requires (let ([mods '(racket/promise
syntax/parse
syntax/parse/debug
syntax/parse/experimental/splicing
syntax/parse/experimental/contract

View File

@ -215,7 +215,8 @@ clause to be tried multiple times before the next clause is reached.
Evaluates the @racket[expr] in the context of all previous attribute
bindings and binds it to the given attribute. The value of
@racket[expr] need not be syntax.
@racket[expr] need not be, or even contain, syntax---see
@racket[attribute] for details.
}
@specsubform[(code:line #:fail-when condition-expr message-expr)
@ -261,7 +262,7 @@ in a @racket[#:do] block.
@;{----------}
@section{Pattern Variables and Attributes}
@section[#:tag "stxparse-attrs"]{Pattern Variables and Attributes}
An @deftech{attribute} is a name bound by a syntax pattern. An
attribute can be a @tech{pattern variable} itself, or it can be a
@ -270,58 +271,92 @@ variable}. The name of a nested attribute is computed by concatenating
the pattern variable name with the syntax class's exported attribute's
name, separated by a dot (see the example below).
Attribute names cannot be used directly as expressions; that is,
attributes are not variables. Instead, an attribute's value can be
gotten using the @racket[attribute] special form.
Attributes can be used in two ways: with the @racket[attribute] form,
and inside syntax templates via @racket[syntax], @racket[quasisyntax],
etc. Attribute names cannot be used directly as expressions; that is,
attributes are not variables.
@defform[(attribute attr-id)]{
A @deftech{syntax-valued attribute} is an attribute whose value is a
syntax object, a syntax list of the appropriate @tech{ellipsis depth},
or a tree containing @tech[#:doc '(lib
"scribblings/reference/reference.scrbl")]{promises} that when
completely forced produces a suitable syntax object or syntax
list. Syntax-valued attributes can be used within @racket[syntax],
@racket[quasisyntax], etc as part of a syntax template. If an
attribute is used inside a syntax template but it is not
syntax-valued, an error is signaled.
Returns the value associated with the attribute named
@racket[attr-id]. If @racket[attr-id] is not bound as an attribute, an
error is raised.
}
The value of an attribute need not be syntax. Non-syntax-valued
attributes can be used to return a parsed representation of a subterm
or the results of an analysis on the subterm. A non-syntax-valued
attribute should be bound using the @racket[#:attr] directive or a
@racket[~bind] pattern.
The value of an attribute is not required to be syntax.
Non-syntax-valued attributes can be used to return a parsed
representation of a subterm or the results of an analysis on the
subterm. A non-syntax-valued attribute should be bound using the
@racket[#:attr] directive or a @racket[~bind] pattern; @racket[#:with]
and @racket[~parse] will convert the right-hand side to a (possibly
3D) syntax object.
@myexamples[
(define-syntax-class table
(pattern ((key value) ...)
#:attr hash
#:attr hashtable
(for/hash ([k (syntax->datum #'(key ...))]
[v (syntax->datum #'(value ...))])
(values k v))))
(syntax-parse #'((a 1) (b 2) (c 3))
[t:table
(attribute t.hash)])
(values k v))
#:attr [sorted-kv 1]
(delay
(printf "sorting!\n")
(sort (syntax->list #'((key value) ...))
<
#:key (lambda (kv) (cadr (syntax->datum kv)))))))
]
A syntax-valued attribute is an attribute whose value is a syntax
object or a syntax list of the appropriate @tech{ellipsis
depth}. Syntax-valued attributes can be used within @racket[syntax],
@racket[quasisyntax], etc as part of a syntax template. If a
non-syntax-valued attribute is used in a syntax template, a runtime
error is signaled.
The @racket[table] syntax class provides four attributes:
@racket[key], @racket[value], @racket[hashtable], and
@racket[sorted-kv]. The @racket[hashtable] attribute has
@tech{ellipsis depth} 0 and the rest have depth 1; all but
@racket[hashtable] are syntax-valued. The @racket[sorted-kv]
attribute's value is a promise; it will be automatically forced if
used in a syntax template.
@myexamples[
(syntax-parse #'((a 1) (b 2) (c 3))
Syntax-valued attributes can be used in syntax templates:
@myinteraction[
(syntax-parse #'((a 3) (b 2) (c 1))
[t:table
#'(t.key ...)])
(syntax-parse #'((a 3) (b 2) (c 1))
[t:table
#'(t.sorted-kv ...)])]
But non-syntax-valued attributes cannot:
@myinteraction[
(syntax-parse #'((a 3) (b 2) (c 1))
[t:table
#'t.hashtable])
]
Use the @racket[attribute] form to get the value of an attribute
(syntax-valued or not).
@myinteraction[
(syntax-parse #'((a 1) (b 2) (c 3))
[t:table
#'t.hash])
(attribute t.hashtable)])
(syntax-parse #'((a 3) (b 2) (c 1))
[t:table
(attribute t.sorted-kv)])
]
Every attribute has an associated @deftech{ellipsis depth} that
determines how it can be used in a syntax template (see the discussion
of ellipses in @racket[syntax]). For a pattern variable, the ellipsis
depth is the number of ellipses the pattern variable ``occurs under''
in the pattern. For a nested attribute the depth is the sum of the
pattern variable's depth and the depth of the attribute in the syntax
class. Consider the following code:
in the pattern. An attribute bound by @racket[#:attr] has depth 0
unless declared otherwise. For a nested attribute the depth is the sum
of the annotated pattern variable's depth and the depth of the
attribute exported by the syntax class.
Consider the following code:
@racketblock[
(define-syntax-class quark
@ -341,11 +376,20 @@ binds the following nested attributes: @racket[y.a] at depth 2,
depth 1.
An attribute's ellipsis nesting depth is @emph{not} a guarantee that
its value has that level of list nesting. In particular, @racket[~or]
and @racket[~optional] patterns may result in attributes with fewer
than expected levels of list nesting.
it is syntax-valued. In particular, @racket[~or] and
@racket[~optional] patterns may result in attributes with fewer than
expected levels of list nesting, and @racket[#:attr] and
@racket[~bind] can be used to bind attributes to arbitrary values.
@(myexamples
(syntax-parse #'(1 2 3)
[(~or (x:id ...) _)
(attribute x)]))
@myexamples[
(syntax-parse #'(a b 3)
[(~or (x:id ...) _)
(attribute x)])
]
@defform[(attribute attr-id)]{
Returns the value associated with the @tech{attribute} named
@racket[attr-id]. If @racket[attr-id] is not bound as an attribute, an
error is raised.
}

View File

@ -349,6 +349,57 @@
[ns (void)])))
(void))
;; lazy attributes
(test-case "lazy syntax-valued attributes"
(let ([counter 0])
(define-syntax-class foo
(pattern n:nat
#:attr 2n
(delay
(set! counter (add1 counter))
(datum->syntax #'n (* 2 (syntax-e #'n))))))
(syntax-parse #'45
[x:foo
(check-equal? counter 0) ;; hasn't run yet
(attribute x.2n)
(check-pred promise? (attribute x.2n))
(check-equal? counter 0) ;; still hasn't run yet
#'(lambda (q) x.2n)
(check-equal? counter 1) ;; run
#'(lambda (q) x.2n)
(force (attribute x.2n))
(check-equal? counter 1) ;; still only run once
(void)])))
(test-case "lazy syntax-valued attributes, lists"
;; check both (promiseof (listof syntax)) and (listof (promiseof syntax)) work
(let ([counter 0])
(define-syntax-class foo
(pattern (x:id ...)
#:attr [alpha 1]
(delay (set! counter (add1 counter))
(filter (lambda (x)
(regexp-match #rx"^[a-z]+$" (symbol->string (syntax-e x))))
(syntax->list #'(x ...))))
#:attr [alpha-part 1]
(map (lambda (x)
(delay
(set! counter (add1 counter))
(datum->syntax #f
(car (regexp-match #rx"^[a-z]+" (symbol->string (syntax-e x)))))))
(syntax->list #'(x ...)))))
(syntax-parse #'(abc g64 xyz c%)
[f:foo
(check-equal? counter 0)
(check-pred syntax? #'(f.alpha ...))
(check-equal? (syntax->datum #'(f.alpha ...)) '(abc xyz))
(check-equal? counter 1)
(check-pred syntax? #'(f.alpha-part ...))
(check-equal? (syntax->datum #'(f.alpha-part ...)) '("abc" "g" "xyz" "c"))
(check-equal? counter 5)
(void)])))
;; == Lib tests
;; static