diff --git a/pkgs/racket-doc/syntax/scribblings/parse/experimental.scrbl b/pkgs/racket-doc/syntax/scribblings/parse/experimental.scrbl index d8b368762e..135f930c59 100644 --- a/pkgs/racket-doc/syntax/scribblings/parse/experimental.scrbl +++ b/pkgs/racket-doc/syntax/scribblings/parse/experimental.scrbl @@ -347,4 +347,37 @@ the context above; instead, @racket[let-values] would report an invalid binding list. } +@deftogether[ + ((defthing prop:template-metafunction struct-type-property?) + (defthing template-metafunction? (-> any/c boolean?)) + (defthing template-metafunction-accessor (-> template-metafunction? identifier?)))]{ + + A structure type property, and the associated predicate and accessor. The + property value is either an identifier, or the index of a field containing an + identifier. The identifier should be bound to the run-time metafunction. The + run-time metafunction should accept a syntax object representing its use, and + produce a new syntax object as a result. + + When an identifier is bound as syntax to a structure instance with this + property, it is treated as a template metafunction as if the identifier had + been declared with define-template-metafunction. + + @examples[#:eval the-eval + (define (my-metafunction-proc stx) + (syntax-case stx () + [(_ n) (datum->syntax #'n (add1 (syntax-e #'n)))])) + + (begin-for-syntax + (struct mf-struct (proc-id) + #:property prop:template-metafunction + (struct-field-index proc-id))) + + (define-syntax mf (mf-struct #'my-metafunction-proc)) + + (template (mf 3)) + (with-syntax ([(x ...) #'(1 2 3)]) + (template ((mf x) ...)))] + + @history[#:added 6.9]} + @(close-eval the-eval) diff --git a/pkgs/racket-test/tests/stxparse/test-template.rkt b/pkgs/racket-test/tests/stxparse/test-template.rkt index 3ccfb7932e..43745990f5 100644 --- a/pkgs/racket-test/tests/stxparse/test-template.rkt +++ b/pkgs/racket-test/tests/stxparse/test-template.rkt @@ -66,3 +66,66 @@ (terx (template (bad-mf)) #rx"result of template metafunction was not syntax") + +;; ============================================================ + +;; Test prop:template-metafunction and template-metafunction? + +(define-syntax (is-metafunction? stx) + (syntax-case stx () + [(_ id) + #`#,(template-metafunction? (syntax-local-value #'id))])) + +(test-case "template-metafunction? on define-template-metafunction" + (define-template-metafunction (mf1 stx) + #'1) + + (check-true (is-metafunction? mf1))) + +(begin-for-syntax + (struct my-template-metafunction2 (proc-id) + #:property prop:template-metafunction (struct-field-index proc-id))) +(test-case + "template-metafunction? on custom prop:template-metafunction with field" + (define (myproc2 stx) #'2) + (define-syntax mf2 (my-template-metafunction2 (quote-syntax myproc2))) + + (check-true (is-metafunction? mf2))) + + +;; must be before the definition of my-template-metafunction3 +(define (myproc3 stx) #'3) +;; must be outside of the (test-case …) form +(begin-for-syntax + (struct my-template-metafunction3 () + #:property prop:template-metafunction (quote-syntax myproc3))) +(test-case "template-metafunction? on custom prop:template-metafunction with id" + (define-syntax mf3 (my-template-metafunction3)) + + (check-true (is-metafunction? mf3))) + +(begin-for-syntax + (struct my-template-metafunction4 (proc-id) + #:property prop:template-metafunction (struct-field-index proc-id))) +(test-case "use custom prop:template-metafunction with field" + (define (myproc4 stx) + (syntax-case stx () + [(_ n) #`#,(add1 (syntax-e #'n))])) + (define-syntax mf4 (my-template-metafunction4 (quote-syntax myproc4))) + + (check-equal? (syntax->datum (template (x (mf4 3) z))) + '(x 4 z))) + +;; must be before the definition of my-template-metafunction5 +(define (myproc5 stx) + (syntax-case stx () + [(_ n) #`#,(* (syntax-e #'n) 2)])) +;; must be outside of the (test-case …) form +(begin-for-syntax + (struct my-template-metafunction5 () + #:property prop:template-metafunction (quote-syntax myproc5))) +(test-case "use custom prop:template-metafunction with id" + (define-syntax mf5 (my-template-metafunction5)) + + (check-equal? (syntax->datum (template (x (mf5 3) z))) + '(x 6 z))) \ No newline at end of file diff --git a/racket/collects/racket/private/prop-template-metafunction.rkt b/racket/collects/racket/private/prop-template-metafunction.rkt new file mode 100644 index 0000000000..f66167247c --- /dev/null +++ b/racket/collects/racket/private/prop-template-metafunction.rkt @@ -0,0 +1,69 @@ +(module prop-template-metafunction '#%kernel + (#%provide (protect metafunction + prop:metafunction + (rename prop:metafunction? metafunction?) + metafunction-accessor)) + + ;; The prop:metafunction structure type property can contain an + ;; identifier bound to the run-time metafunction procedure, or the index of a + ;; field containing such an identifier. + ;; At run-time, when processing the template, the syntax object whose first + ;; element is a metafunction identifiers is passed to this metafunction + ;; procedure. + + (define-values (prop:metafunction-guard) + (lambda (val struct-type-info-list) + (if (exact-nonnegative-integer? val) + ;; Field index, the field must contain an identifier bound to the run-time + ;; metafunction procedure. + (let-values ([(make-struct-accessor) (cadddr struct-type-info-list)]) + (let-values ([(accessor) (make-struct-field-accessor make-struct-accessor val)]) + (λ (instance) + (let-values ([(metafunction-internal-id) (accessor instance)]) + ;; Check that the value contained in the field is correct + ;; (if (identifier? metafunction-internal-id) ...) + (if (if (syntax? metafunction-internal-id) + (symbol? (syntax-e metafunction-internal-id)) + #f) + metafunction-internal-id + (raise-argument-error + 'prop:metafunction-guard + (format (string-append "the value of the ~a-th field should be" + " an identifier") + val) + metafunction-internal-id)))))) + ;;(if (identifier? val) + (if (if (syntax? val) (symbol? (syntax-e val)) #f) + ;; Identifier bound to the run-time metafunction procedure. + (λ (_instance) val) + ;; Otherwise, raise an error. + (raise-argument-error + 'prop:metafunction-guard + (string-append "an identifier, or an exact non-negative integer designating" + " a field index within the structure that should contain an" + " identifier.") + val))))) + + (define-values (prop:metafunction + prop:metafunction? + metafunction-raw-accessor) + (make-struct-type-property 'metafunction + prop:metafunction-guard)) + + (define-values (metafunction-accessor) + (lambda (instance . more-args) + (let-values ([(raw) (if (null? more-args) ;; no failure-result given + (metafunction-raw-accessor instance) + (if (null? (cdr more-args)) + (let-values ([(failure-result) (car more-args)]) + (metafunction-raw-accessor instance failure-result)) + (error "invalid number of arguments [TODO]")))]) + (raw instance)))) + + ;; A default struct type with prop:metafunction. + ;; (struct template-metafunction (proc-id) + ;; #:property prop:template-metafunction (struct-field-index proc-id)) + (define-values (struct:metafunction metafunction metafunction? metafunction-ref _mf-set!) + (make-struct-type 'syntax-metafunction #f 1 0 #f + (list (cons prop:metafunction 0)) + (current-inspector)))) \ No newline at end of file diff --git a/racket/collects/racket/private/template.rkt b/racket/collects/racket/private/template.rkt index 3d55876197..2a21b04456 100644 --- a/racket/collects/racket/private/template.rkt +++ b/racket/collects/racket/private/template.rkt @@ -4,6 +4,7 @@ (rename "define-et-al.rkt" define-syntax -define-syntax) "ellipses.rkt" (for-syntax "stx.rkt" "define-et-al.rkt" "qq-and-or.rkt" "cond.rkt" + "prop-template-metafunction.rkt" (rename "define-et-al.rkt" define -define) (rename "define-et-al.rkt" define-syntax -define-syntax) #;"member.rkt" "sc.rkt" '#%kernel)) @@ -12,14 +13,17 @@ datum ~? ~@ ~@! signal-absent-pvar + (for-syntax prop:metafunction + metafunction + metafunction-accessor + metafunction?) (protect (for-syntax attribute-mapping attribute-mapping? attribute-mapping-name attribute-mapping-var attribute-mapping-depth - attribute-mapping-check - metafunction metafunction?))) + attribute-mapping-check))) ;; ============================================================ ;; Syntax of templates @@ -111,11 +115,6 @@ (define (attribute-mapping-depth a) (attribute-mapping-ref a 2)) (define (attribute-mapping-check a) (attribute-mapping-ref a 3)) - ;; (struct metafunction (var)) - (define-values (struct:metafunction metafunction metafunction? metafunction-ref _mf-set!) - (make-struct-type 'syntax-metafunction #f 1 0 #f null (current-inspector))) - (define (metafunction-var mf) (metafunction-ref mf 0)) - (define (guide-is? x tag) (and (pair? x) (eq? (car x) tag))) (define (ht-guide? x) (guide-is? x 'h-t)) @@ -275,7 +274,7 @@ (unless stx? (wrong-syntax (stx-car t) "metafunctions are not supported")) (disappeared! (stx-car t)) (define guide (parse-t (stx-cdr t) depth esc?)) - `(t-metafun ,(metafunction-var mf) ,guide + `(t-metafun ,(metafunction-accessor mf) ,guide (quote-syntax ,(let ([tstx (and (syntax? t) t)]) (datum->syntax tstx (cons (stx-car t) #f) tstx tstx)))))]