www

Unnamed repository; edit this file 'description' to name the repository.
Log | Files | Refs | README

commit d2d76231940c8ada99fea4ae110dd7f0207e0236
parent cf92b0fc32a3bc3009c876332bab7135f379514e
Author: Georges Dupéron <georges.duperon@gmail.com>
Date:   Fri, 27 Jan 2017 21:48:40 +0100

Closes FB issue 191 subtemplate: allow #f values for ~optional in syntax/parse

Diffstat:
Minfo.rkt | 3++-
Mmain.rkt | 246++++++++++++++++++++++++++++++++++++++++++++++++++++++++++---------------------
Mtest/test-subtemplate.rkt | 473+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++------
3 files changed, 623 insertions(+), 99 deletions(-)

diff --git a/info.rkt b/info.rkt @@ -5,7 +5,8 @@ "backport-template-pr1514" ;; for the documentation only "phc-toolkit" "srfi-lite-lib" - "stxparse-info")) + "stxparse-info" + "alexis-util")) (define build-deps '("scribble-lib" "racket-doc")) (define scribblings '(("scribblings/subtemplate.scrbl" () (parsing-library)))) diff --git a/main.rkt b/main.rkt @@ -2,11 +2,14 @@ (require racket/require phc-toolkit/untyped + phc-toolkit/untyped-only/syntax-parse racket/stxparam stxparse-info/parse stxparse-info/case stxparse-info/current-pvars stxparse-info/parse/experimental/template + (prefix-in - stxparse-info/parse/private/residual) + (prefix-in dbg: stxparse-info/parse/private/runtime) syntax/id-table (subtract-in racket/syntax stxparse-info/case) (for-syntax "patch-arrows.rkt" @@ -39,10 +42,18 @@ (define (id-is-derived-valvar? id) (define mapping (syntax-local-value id (thunk #f))) (and mapping ;; … defined as syntax - (syntax-pattern-variable? mapping) ;; and is a syntax pattern variable - (derived-valvar? ;; and the pvar's valvar is derived - (syntax-local-value (syntax-mapping-valvar mapping) - (thunk #f))))) + (syntax-pattern-variable? mapping) ; and is a syntax pattern variable + (let () + (define mapping-slv + (syntax-local-value (syntax-mapping-valvar mapping) (thunk #f))) + ;; either a mapping → attribute → derived, + ;; or directly mapping → derived + (or (and (-attribute-mapping? mapping-slv) ;; is an attribute + (derived-valvar? ;; and the pvar's valvar is a derived + (syntax-local-value (-attribute-mapping-var mapping-slv) + (thunk #f)))) + ;; or the pvar's valvar is derived + (derived-valvar? mapping-slv))))) (define/contract (string-suffix a b) (-> string? string? string?) @@ -89,13 +100,19 @@ #`(#,(nest-ellipses stx (sub1 n)) (… …)))) + (define/contract (nest-map f last n) + (-> (-> syntax? syntax?) syntax? exact-nonnegative-integer? syntax?) + (if (= n 0) + last + (f (nest-map f last (sub1 n))))) + (define/contract (find-subscript-binder bound) (-> identifier? (or/c #f (list/c identifier? ; bound (syntax/c (listof identifier?)) ; binders (syntax/c (listof identifier?)) ; unique-at-runtime ids - exact-nonnegative-integer? ; ellipsis-depth - syntax?))) ; check-ellipsis-count + exact-nonnegative-integer?))) ; ellipsis-depth + (let/cc return ;; EARLY RETURN (already a pattern variable) (when (syntax-pattern-variable? @@ -160,23 +177,67 @@ (syntax->list #'(binder …))) bound)) - ;; generate code to check that the bindings have all the same - ;; ellipsis count, by simply generating a dummy syntax object, with - ;; all the given binders nested under the right number of ellipses. - (define/with-syntax check-ellipsis-count-ddd - (nest-ellipses #'(binder …) (car depths))) - ;; FINAL RETURN (list of same-depth binders + their depth) - (list bound - #'(binder …) - #'(unique-at-runtime-id …) - (car depths) - #'check-ellipsis-count-ddd)))) + (return (list bound + #'(binder …) + #'(unique-at-runtime-id …) + (car depths)))))) + +(define/contract (attribute-val/c depth [bottom-predicate any/c]) + (->* {exact-nonnegative-integer?} {flat-contract?} flat-contract?) + (flat-named-contract + (build-compound-type-name 'attribute-val/c depth bottom-predicate) + (λ (l) + (if (= depth 0) + (or (eq? l #f) (bottom-predicate l)) + (or (eq? l #f) + (and (list? l) + (andmap (attribute-val/c (sub1 depth)) l))))))) + +;; ellipsis-count/c works with attributes too, including missing (optional) +;; elements in the lists, at any level. +(define/contract (ellipsis-count/c depth [bottom-predicate any/c]) + (->* {exact-nonnegative-integer?} {flat-contract?} flat-contract?) + (flat-named-contract + (build-compound-type-name 'ellipsis-count/c depth bottom-predicate) + (λ (l*) + (true? + (and (list? l*) + (let ([l* (filter identity l*)]) + (if (= depth 0) + (andmap bottom-predicate l*) + (let ([lengths (map length l*)]) + (and (or (< (length lengths) 2) (apply = lengths)) + (or (empty? l*) + (apply andmap + (λ sublists + ((ellipsis-count/c (sub1 depth) + bottom-predicate) + sublists)) + l*))))))))))) + +(define/contract (map-merge-stx-depth f l* depth) + (->i {[f (-> (listof any/c) any/c)] + [l* (depth) (ellipsis-count/c depth any/c)] + [depth exact-nonnegative-integer?]} + {result (depth l*) + (λ (r) ((ellipsis-count/c depth) (cons r l*)))}) + (let ([l* (filter identity l*)]) + (if (= depth 0) + (f l*) + (if (empty? l*) + #f + (apply map + (λ sublists + (map-merge-stx-depth f + sublists + (sub1 depth))) + l*))))) (define-for-syntax (sub*template self-form tmpl-form) (syntax-parser [(self {~optional {~and #:force-no-stxinfo force-no-stxinfo}} - {~optional {~and #:props maybe-props}} + {~optkw #:props (prop:id ...)} ;; #: marks end of options (so that we can have implicit ?@ later) {~optional #:} tmpl) @@ -224,23 +285,27 @@ ;; like (quasitemplate . tmpl) (define result (quasisyntax/top-loc #'self - (#,tmpl-form tmpl #,@(when-attr maybe-props #'{#:props maybe-props})))) + (#,tmpl-form tmpl + #,@(if (attribute props) #'(#:props (prop ...)) #'())))) ;; Make sure that we remove duplicates, otherwise we'll get errors if we ;; define the same derived id twice. (define/with-syntax ([bound - binders + (binder …) unique-at-runtime-ids - ellipsis-depth - check-ellipsis-count] + ellipsis-depth] …) (remove-duplicates acc bound-identifier=? #:key car)) - #`(let () - (derive bound binders unique-at-runtime-ids ellipsis-depth) + (define/with-syntax whole-form-id (generate-temporary 'whole-subtemplate)) + + #`(let-values () + (define-values (whole-form-id) (quote-syntax #,this-syntax)) + (derive + bound (binder …) unique-at-runtime-ids ellipsis-depth whole-form-id) - (let () - ;; no-op, just to raise an error when they are incompatible - #'(check-ellipsis-count …) ;; TODO: allow #f values for ~optional in syntax/parse ;;;;;;;;;;;;;; + (let-values () + ;; check that all the binders for a given bound are compatible. + ((ellipsis-count/c ellipsis-depth) (list (attribute* binder) …)) … ;; actually call template or quasitemplate #,result))])) @@ -261,36 +326,54 @@ (for ([k (in-list keys)]) (hash-ref! h k val)) val) -(define/contract ((stx-list*+stx/c depth) l) - (-> exact-nonnegative-integer? (-> any/c boolean?)) - (if (= depth 0) - (syntax? l) - (and (syntax? l) - (syntax->list l) - (andmap (λ (lᵢ) ((stx-list*+stx/c (sub1 depth)) lᵢ)) - (syntax->list l))))) +(define formattable/c (or/c number? string? symbol? bytes?)) + +(define/contract + (generate-nested-ids depth bound binder₀ format l* attribute-names whole-form) + (->i {[depth exact-nonnegative-integer?] + [bound identifier?] + [binder₀ identifier?] + [format (-> formattable/c string?)] + [l* (depth) (listof (attribute-val/c depth))] + [attribute-names (l*) (and/c (listof identifier?) + (λ (a) (= (length l*) (length a))))] + [whole-form syntax?]} + #:pre (l* depth attribute-names whole-form bound) + (if ((ellipsis-count/c depth) l*) + #t + (raise-syntax-error + (syntax-case whole-form () + [(self . _) (syntax-e #'self)] + [_ 'subtemplate]) + "incompatible ellipsis match counts for subscripted variables:" + whole-form + bound + attribute-names)) + {result (depth l*) + (and/c (attribute-val/c depth identifier?) + (λ (r) ((ellipsis-count/c depth) (cons r l*))))}) -(define/contract ((list*+stx/c depth) l) - (-> exact-nonnegative-integer? (-> any/c boolean?)) - (if (= depth 0) - (syntax? l) - (and (list? l) - (andmap (λ (lᵢ) ((list*+stx/c (sub1 depth)) lᵢ)) - l)))) + + (define (gen bottom*) + (define v + (let ([vs (filter-map (λ (v) + (cond [(formattable/c v) v] + [(formattable/c (syntax-e v)) (syntax-e v)] + [else #f])) + bottom*)]) + (if (empty? vs) + (syntax-e (generate-temporary binder₀)) + (car vs)))) + (datum->syntax ((make-syntax-introducer) bound) + (string->symbol (format v)))) -(define/contract (destructure-stx-list* l depth) - (->i ([l (depth) (stx-list*+stx/c depth)] - [depth exact-nonnegative-integer?]) - [range (depth) (list*+stx/c depth)]) - (if (= depth 0) - l - (stx-map (λ (lᵢ) (destructure-stx-list* lᵢ (sub1 depth))) - l))) + (map-merge-stx-depth gen l* depth)) (define-syntax/case (derive bound (binder₀ binderᵢ …) (unique-at-runtime-idᵢ …) - ellipsis-depth) () + ellipsis-depth + whole-form-id) () (define depth (syntax-e #'ellipsis-depth)) (define/with-syntax bound-ddd (nest-ellipses #'bound depth)) (define/with-syntax tmp-id @@ -327,7 +410,16 @@ (string-length subscripts)) (string-length subscripts))) (define/with-syntax temp-derived (generate-temporary #'bound)) + (define/with-syntax temp-valvar (generate-temporary #'bound)) (define/with-syntax temp-cached (generate-temporary #'bound)) + ;; works only for syntax patterns, luckily that's all we need since we + ;; produce a tree of (possibly missing) identifiers. + (define/with-syntax copy-attribute-pattern + ;; the ~and is important, to prevent the nested ~or from being treated as + ;; an ellipsis-head pattern. + (nest-map (λ (pat) #`{~or #f ({~and #,pat} (... ...))}) + #'{~or #f {~var bound id}} + (syntax-e #'ellipsis-depth))) ;; HERE: cache the define-temp-ids in the free-id-table, and make sure ;; that we retrieve the cached ones, so that two subtemplate within the same ;; syntax-case or syntax-parse clause use the same derived ids. @@ -342,17 +434,40 @@ ;; zᵢ …))]) ;; the test above is not exactly right (zᵢ will still have the correct ;; binding), but it gives the general idea. - #`(begin (define-temp-ids #:concise tmp-str binder-ddd) - (define temp-cached - (free-id-table-ref! (multi-hash-ref! derived-valvar-cache - (list unique-at-runtime-idᵢ - …) - (make-free-id-table)) - (quote-syntax bound) - (destructure-stx-list* #'tmp-ddd - 'ellipsis-depth))) - (define-syntax temp-derived - (derived-valvar (quote-syntax temp-cached))) - (define-syntax bound - (make-syntax-mapping 'ellipsis-depth (quote-syntax temp-derived))) - (define-pvars bound)))) -\ No newline at end of file + #`(begin ;(define-temp-ids #:concise tmp-str binder-ddd) ;;;;;;;;;;;;;;;;;;;TODO: should fuse all the binder-ddd, so that if any one is not #f for a sublist, that sublist is generated. + ;; TODO: we should check that if the hash-table access worked, + ;; any new pvars are compatible with the old ones on which the cache is + ;; based (in the sense of "no new non-#f positions") + (define temp-cached + (free-id-table-ref! (multi-hash-ref! derived-valvar-cache + (list unique-at-runtime-idᵢ + …) + (make-free-id-table)) + (quote-syntax bound) + (λ () + (generate-nested-ids 'ellipsis-depth + (quote-syntax bound) + (quote-syntax binder₀) + (λ (v) (format tmp-str v)) + (list (attribute* binder₀) + (attribute* binderᵢ) + …) + (list (quote-syntax binder₀) + (quote-syntax binderᵢ) + …) + whole-form-id)))) + #;(define-syntax temp-derived + (derived-valvar (quote-syntax temp-cached))) + #;(define-raw-attribute bound + temp-valvar + temp-cached ;temp-derived + ellipsis-depth + #t) + ;(define temp-cached (attribute* binder₀)) + ;; manually creating the attribute with (make-attribute-mapping …) + ;; works, but the attribute behaves in a bogus way when put inside + ;; an (?@ yᵢ ...). I must be missing some step in the construction + ;; of the attribute + ;; TODO: I used make-attribute-mapping somewhere else, find it and change it !!!!! + (define/syntax-parse copy-attribute-pattern temp-cached) + (define-pvars bound)))) diff --git a/test/test-subtemplate.rkt b/test/test-subtemplate.rkt @@ -1,9 +1,11 @@ #lang racket (require subtemplate stxparse-info/parse + stxparse-info/parse/experimental/template stxparse-info/case phc-toolkit/untyped - rackunit) + rackunit + syntax/macro-testing) #| (define-syntax (tst stx) @@ -24,7 +26,6 @@ |# - (check-equal? (syntax->datum (syntax-parse #'(a b c d) [(_ xⱼ zᵢ …) (subtemplate foo)])) @@ -94,14 +95,14 @@ -#;(let () - (syntax-parse #'a #;(syntax-parse #'(a b c d) - [(_ xⱼ zᵢ …) - (list (subtemplate ([xⱼ wⱼ] foo [zᵢ pᵢ] …)) - (subtemplate ([xⱼ wⱼ] foo [zᵢ pᵢ] …)))]) - [_ #;(([x1 w1] foo1 [z1 p1] [zz1 pp1]) - ([x2 w2] foo2 [z2 p2] [zz2 pp2])) - (check bound-identifier=? #'x1 #'x2)])) +(let () + (syntax-parse (syntax-parse #'(a b c d) + [(_ xⱼ zᵢ …) + (list (subtemplate ([xⱼ wⱼ] foo [zᵢ pᵢ] …)) + (subtemplate ([xⱼ wⱼ] foo [zᵢ pᵢ] …)))]) + [(([x1 w1] foo1 [z1 p1] [zz1 pp1]) + ([x2 w2] foo2 [z2 p2] [zz2 pp2])) + (check bound-identifier=? #'x1 #'x2)])) (syntax-parse (syntax-parse #'(a b c d) [(_ xⱼ zᵢ …) @@ -344,25 +345,19 @@ (check (∘ not bound-identifier=?) #'b3 #'b4) (check (∘ not bound-identifier=?) #'c3 #'c4)]) -#;(map syntax->datum - (syntax-parse #'(a b c) - [(xᵢ …) - (list (syntax-parse #'(d) - [(pᵢ …) #`(#,(quasisubtemplate (xᵢ … pᵢ … zᵢ …)) - #,(quasisubtemplate (xᵢ … pᵢ … zᵢ …)))]) - (syntax-parse #'(e) - [(pᵢ …) (quasisubtemplate (xᵢ … pᵢ … zᵢ …))]))])) - -#;(syntax->datum - (syntax-parse #'(a b c) - [(xᵢ …) - (quasisubtemplate (yᵢ … - #,(syntax-parse #'(d) - [(pᵢ …) (quasisubtemplate (pᵢ … zᵢ …))]) - ;; GIVES WRONG ID (re-uses the one above, shouldn't): - #,(syntax-parse #'(e) - [(pᵢ …) (quasisubtemplate (pᵢ … zᵢ …))]) - wᵢ …))])) +;; Incompatible ellipsis counts +(begin + (check-exn #rx"incompatible ellipsis match counts for subscripted variables" + (λ () + (syntax-case #'([a b c] [d]) () + [([xᵢ …] [pᵢ …]) + (quasisubtemplate ([xᵢ …] [pᵢ …] [zᵢ …]))]))) + + (check-equal? (syntax->datum + (syntax-case #'([a b c] [d]) () + [([xᵢ …] [pᵢ …]) + (quasisubtemplate ([xᵢ …] [pᵢ …]))])) + '([a b c] [d]))) (syntax-parse (syntax-parse #'(a b c) [(xᵢ …) @@ -600,7 +595,7 @@ (check (∘ not bound-identifier=?) #'p1 #'w1) (check (∘ not bound-identifier=?) #'p1 #'pp1)]) -(check-exn #px"incompatible ellipsis match counts for template" +(check-exn #px"incompatible ellipsis match counts for subscripted variables" (λ () (syntax-parse #'() [() @@ -675,4 +670,418 @@ '(() ([a a/y]) ([a a/y] [b b/y]) - ([c c/y] [d d/y] [e e/y] [f f/y]))) -\ No newline at end of file + ([c c/y] [d d/y] [e e/y] [f f/y]))) + +;; ~optional +(begin + ;; whole opt present, yᵢ ... ... + (check-equal? (syntax->datum + (syntax-parse #'([(1 2 3) (a b)]) + [({~optional ((xᵢ ...) ...)}) + (subtemplate {?? (yᵢ ... ...) empty})])) + '(1/y 2/y 3/y a/y b/y)) + + ;; whole opt empty, yᵢ ... ... + (check-equal? (syntax->datum + (syntax-parse #'() + [({~optional ((xᵢ ...) ...)}) + (subtemplate {?? (yᵢ ... ...) empty})])) + 'empty) + + ;; whole opt present, ([xᵢ yᵢ] ...) ... + (check-equal? (syntax->datum + (syntax-parse #'([(1 2 3) (a b)]) + [({~optional ((xᵢ ...) ...)}) + (subtemplate {?? (([xᵢ yᵢ] ...) ...) empty})])) + '(([1 1/y] [2 2/y] [3 3/y]) ([a a/y] [b b/y]))) + + ;; whole opt empty, ([xᵢ yᵢ] ...) ... + (check-equal? (syntax->datum + (syntax-parse #'() + [({~optional ((xᵢ ...) ...)}) + (subtemplate {?? (([xᵢ yᵢ] ...) ...) empty})])) + 'empty) + + ;; whole opt present, (yᵢ ...) ... + (check-equal? (syntax->datum + (syntax-parse #'([(1 2 3) (a b)]) + [({~optional ((xᵢ ...) ...)}) + (subtemplate {?? ((yᵢ ...) ...) empty})])) + '((1/y 2/y 3/y) (a/y b/y))) + + ;; whole opt empty, (yᵢ ...) ... + (check-equal? (syntax->datum + (syntax-parse #'() + [({~optional ((xᵢ ...) ...)}) + (subtemplate {?? (yᵢ ... ...) empty})])) + 'empty) + + ;; level-1 opt, (?@ [xᵢ yᵢ] ...)/empty ... + (check-equal? (syntax->datum + (syntax-parse #'((1 2 3) #:kw (a b) #:kw) + [({~and {~or (xᵢ ...) #:kw}} ...) + (subtemplate ({?? (?@ [xᵢ yᵢ] ...) empty} ...))])) + '([1 1/y] [2 2/y] [3 3/y] empty [a a/y] [b b/y] empty)) + + ;; level-1 opt, (?@ yᵢ ...)/empty ... + (check-equal? (syntax->datum + (syntax-parse #'((1 2 3) #:kw (a b) #:kw) + [({~and {~or (xᵢ ...) #:kw}} ...) + (subtemplate ({?? (?@ yᵢ ...) empty} ...))])) + '(1/y 2/y 3/y empty a/y b/y empty)) + + ;; level-1 opt, ([xᵢ yᵢ] ...)/empty ... + (check-equal? (syntax->datum + (syntax-parse #'((1 2 3) #:kw (a b) #:kw) + [({~and {~or (xᵢ ...) #:kw}} ...) + (subtemplate ({?? ([xᵢ yᵢ] ...) empty} ...))])) + '(([1 1/y] [2 2/y] [3 3/y]) empty ([a a/y] [b b/y]) empty)) + + ;; level-1 opt, (xᵢ ...)/empty ... + (check-equal? (syntax->datum + (syntax-parse #'((1 2 3) #:kw (a b) #:kw) + [({~and {~or (xᵢ ...) #:kw}} ...) + (quasisubtemplate + ({?? (xᵢ ...) empty} ...))])) + '((1 2 3) empty (a b) empty)) + + ;; level-1 opt, (yᵢ ...)/empty ... + (check-equal? (syntax->datum + (syntax-parse #'((1 2 3) #:kw (a b) #:kw) + [({~and {~or (xᵢ ...) #:kw}} ...) + (subtemplate ({?? (yᵢ ...) empty} ...))])) + '((1/y 2/y 3/y) empty (a/y b/y) empty)) + + ;; level-1 opt + same but with all #f filled in. + (begin + ;; level-1 opt + same but with all #f filled in. (wᵢ ...)/empty ... + (check-equal? (syntax->datum + (syntax-parse #'([(e f g) (h i) (j k) (l m n o)] + [(1 2 3) #:kw (a b) #:kw]) + [((({~and {~or wᵢ:id #:k}} ...) ...) + ({~and {~or (xᵢ ...) #:kw}} ...)) + (subtemplate ({?? (wᵢ ...) empty} ...))])) + '((e f g) + (h i) + (j k) + (l m n o))) + + ;; level-1 opt + same but with some filled/missing. (wᵢ/empty ...) ... + (check-equal? (syntax->datum + (syntax-parse #'([(e f g) (h i) (j k) (l m n o)] + [(1 2 3) #:kw (a b) #:kw]) + [((({~and {~or wᵢ:id #:k}} ...) ...) + ({~and {~or (xᵢ ...) #:kw}} ...)) + (subtemplate (({?? wᵢ empty} ...) ...))])) + '((e f g) + (h i) + (j k) + (l m n o))) + + ;; level-1 opt + same but with all #f filled in. ([wᵢ yᵢ] ...)/empty ... + (check-equal? (syntax->datum + (syntax-parse #'([(e f g) (h i) (j k) (l m n o)] + [(1 2 3) #:kw (a b) #:kw]) + [((({~and {~or wᵢ:id #:k}} ...) ...) + ({~and {~or (xᵢ ...) #:kw}} ...)) + (subtemplate ({?? ([wᵢ yᵢ] ...) empty} ...))])) + '(([e 1/y] [f 2/y] [g 3/y]) + ([h h/y] [i i/y]) + ([j a/y] [k b/y]) + ([l l/y] [m m/y] [n n/y] [o o/y]))) + + ;; level-1 opt + same but with all #f filled in. (yᵢ ...)/empty ... + (check-equal? (syntax->datum + (syntax-parse #'([(e f g) (h i) (j k) (l m n o)] + [(1 2 3) #:kw (a b) #:kw]) + [((({~and {~or wᵢ:id #:k}} ...) ...) + ({~and {~or (xᵢ ...) #:kw}} ...)) + (subtemplate ({?? (yᵢ ...) empty} ...))])) + '((1/y 2/y 3/y) + (h/y i/y) + (a/y b/y) + (l/y m/y n/y o/y))) + + ;; level-1 opt + same but with all #f filled in. (yᵢ ...)/empty ... + (check-equal? (syntax->datum + (syntax-parse #'([(e f g) (h i) (j k) (l m n o)] + [(1 2 3) #:kw (a b) #:kw]) + [((({~and {~or wᵢ:id #:k}} ...) ...) + ({~and {~or (xᵢ ...) #:kw}} ...)) + (subtemplate ({?? (?@ yᵢ ...) empty} ...))])) + '(1/y 2/y 3/y h/y i/y a/y b/y l/y m/y n/y o/y)) + + ;; level-1 opt + same but with all #f filled in. ([wᵢ yᵢ/empty] ...) ... + (check-equal? (syntax->datum + (syntax-parse #'([(e f g) (h i) (j k) (l m n o)] + [(1 2 3) #:kw (a b) #:kw]) + [((({~and {~or wᵢ:id #:k}} ...) ...) + ({~and {~or (xᵢ ...) #:kw}} ...)) + (subtemplate (([wᵢ (?? yᵢ empty)] ...) ...))])) + '(([e 1/y] [f 2/y] [g 3/y]) + ([h h/y] [i i/y]) + ([j a/y] [k b/y]) + ([l l/y] [m m/y] [n n/y] [o o/y]))) + + ;; level-1 opt + same but with all #f filled in. (yᵢ/empty ...) ... + (check-equal? (syntax->datum + (syntax-parse #'([(e f g) (h i) (j k) (l m n o)] + [(1 2 3) #:kw (a b) #:kw]) + [((({~and {~or wᵢ:id #:k}} ...) ...) + ({~and {~or (xᵢ ...) #:kw}} ...)) + (subtemplate (((?? yᵢ empty) ...) ...))])) + '((1/y 2/y 3/y) + (h/y i/y) + (a/y b/y) + (l/y m/y n/y o/y))) + + ;; level-1 opt + same but with all #f filled in. yᵢ/empty ... ... + (check-equal? (syntax->datum + (syntax-parse #'([(e f g) (h i) (j k) (l m n o)] + [(1 2 3) #:kw (a b) #:kw]) + [((({~and {~or wᵢ:id #:k}} ...) ...) + ({~and {~or (xᵢ ...) #:kw}} ...)) + (subtemplate ((?? yᵢ empty) ... ...))])) + '(1/y 2/y 3/y + h/y i/y + a/y b/y + l/y m/y n/y o/y))) + + + ;; level-1 opt + same but with some level-1 #f filled in and some missing + (begin + ;; level-1 opt + same with some lvl1 filled/missing. (wᵢ ...)/empty ... + (check-equal? (syntax->datum + (syntax-parse #'([(e f g) #:k (j k) (l m n o)] + [(1 2 3) #:kw (a b) #:kw]) + [(({~and {~or (wᵢ ...) #:k}} ...) + ({~and {~or (xᵢ ...) #:kw}} ...)) + (subtemplate ({?? (wᵢ ...) empty} ...))])) + '((e f g) + empty + (j k) + (l m n o))) + + ;; level-1 opt + same with some lvl1 filled/missing. (wᵢ/empty ...) ... + ;; Invalid because {?? wᵢ empty} ... means to iterate over the known + ;; elements of wᵢ, and put "empty" if one is absent. However, the whole + ;; sublist of wᵢ element is missing, so it does not really have a meaningful + ;; length for the ... + (check-exn + #rx"attribute contains non-syntax value.*#f" + (λ () + (convert-compile-time-error + (check-equal? (syntax->datum + (syntax-parse #'([(e f g) #:k (j k) (l m n o)] + [(1 2 3) #:kw (a b) #:kw]) + [(({~and {~or (wᵢ ...) #:k}} ...) + ({~and {~or (xᵢ ...) #:kw}} ...)) + (subtemplate (({?? wᵢ empty} ...) ...))])) + '((e f g) + empty + (j k) + (l m n o)))))) + + ;; level-1 opt + same with some lvl1 filled/missing. ([wᵢ yᵢ] ...)/empty ... + (check-equal? (syntax->datum + (syntax-parse #'([(e f g) #:k (j k) (l m n o)] + [(1 2 3) #:kw (a b) #:kw]) + [(({~and {~or (wᵢ ...) #:k}} ...) + ({~and {~or (xᵢ ...) #:kw}} ...)) + (subtemplate ({?? ([wᵢ yᵢ] ...) empty} ...))])) + '(([e 1/y] [f 2/y] [g 3/y]) + empty + ([j a/y] [k b/y]) + ([l l/y] [m m/y] [n n/y] [o o/y]))) + + ;; level-1 opt + same with some lvl1 #f filled in. (yᵢ ...)/empty ... + (check-equal? (syntax->datum + (syntax-parse #'([(e f g) #:k (j k) (l m n o)] + [(1 2 3) #:kw (a b) #:kw]) + [(({~and {~or (wᵢ ...) #:k}} ...) + ({~and {~or (xᵢ ...) #:kw}} ...)) + (subtemplate ({?? (yᵢ ...) empty} ...))])) + '((1/y 2/y 3/y) + empty + (a/y b/y) + (l/y m/y n/y o/y))) + + ;; level-1 opt + same with some lvl1 #f filled in. (yᵢ ...)/empty ... + (check-equal? (syntax->datum + (syntax-parse #'([(e f g) #:k (j k) (l m n o)] + [(1 2 3) #:kw (a b) #:kw]) + [(({~and {~or (wᵢ ...) #:k}} ...) + ({~and {~or (xᵢ ...) #:kw}} ...)) + (subtemplate ({?? (?@ yᵢ ...) empty} ...))])) + '(1/y 2/y 3/y + empty + a/y b/y + l/y m/y n/y o/y)) + + ;; level-1 opt + same with some lvl1 #f filled in. ([wᵢ yᵢ/empty] ...) ... + ;; Invalid because {?? wᵢ emptywi} ... means to iterate over the known + ;; elements of wᵢ, and put "empty" if one is absent. However, the whole + ;; sublist of wᵢ element is missing, so it does not really have a meaningful + ;; length for the ... + (check-exn + #rx"attribute contains non-syntax value.*#f" + (λ () + (convert-compile-time-error + (check-equal? (syntax->datum + (syntax-parse #'([(e f g) #:k (j k) (l m n o)] + [(1 2 3) #:kw (a b) #:kw]) + [(({~and {~or (wᵢ ...) #:k}} ...) + ({~and {~or (xᵢ ...) #:kw}} ...)) + (subtemplate + (([(?? wᵢ emptywi) (?? yᵢ empty)] ...) ...))])) + '(([e 1/y] [f 2/y] [g 3/y]) + ([emptywi empty] [emptywi empty]) + ([j a/y] [k b/y]) + ([l l/y] [m m/y] [n n/y] [o o/y])))))) + + ;; level-1 opt + same with some lvl1 #f filled in. (yᵢ/empty ...) ... + ;; Invalid because {?? wᵢ empty} ... means to iterate over the known + ;; elements of wᵢ, and put "empty" if one is absent. However, the whole + ;; sublist of wᵢ element is missing, so it does not really have a meaningful + ;; length for the ... + (check-exn + #rx"attribute contains non-syntax value.*#f" + (λ () + (convert-compile-time-error + (check-equal? (syntax->datum + (syntax-parse #'([(e f g) #:k (j k) (l m n o)] + [(1 2 3) #:kw (a b) #:kw]) + [(({~and {~or (wᵢ ...) #:k}} ...) + ({~and {~or (xᵢ ...) #:kw}} ...)) + (subtemplate (((?? yᵢ empty) ...) ...))])) + '((1/y 2/y 3/y) + empty + (a/y b/y) + (l/y m/y n/y o/y)))))) + + ;; level-1 opt + same with some lvl1 #f filled in. yᵢ/empty ... ... + ;; Invalid because {?? yᵢ empty} ... means to iterate over the known + ;; elements of wᵢ, and put "empty" if one is absent. However, the whole + ;; sublist of wᵢ element is missing, so it does not really have a meaningful + ;; length for the ... + (check-exn + #rx"attribute contains non-syntax value.*#f" + (λ () + (convert-compile-time-error + (check-equal? (syntax->datum + (syntax-parse #'([(e f g) #:k (j k) (l m n o)] + [(1 2 3) #:kw (a b) #:kw]) + [(({~and {~or (wᵢ ...) #:k}} ...) + ({~and {~or (xᵢ ...) #:kw}} ...)) + (subtemplate ((?? yᵢ empty) ... ...))])) + '(1/y 2/y 3/y + empty + a/y b/y + l/y m/y n/y o/y)))))) + + + ;; level-1 opt + same with some level-2 #f filled in and some missing + (begin + ;; level-1 opt + same with some lvl2 filled/missing. (wᵢ ...)/empty ... + (check-match (syntax->datum + (syntax-parse #'([(e f g) (h #:k) (j k) (l m n o)] + [(1 2 3) #:kw (a b) #:kw]) + [((({~and {~or wᵢ:id #:k}} ...) ...) + ({~and {~or (xᵢ ...) #:kw}} ...)) + (subtemplate ({?? (wᵢ ...) empty} ...))])) + '((e f g) + empty + (j k) + (l m n o))) + + ;; level-1 opt + same with some lvl2 filled/missing. (wᵢ/empty ...) ... + (check-match (syntax->datum + (syntax-parse #'([(e f g) (h #:k) (j k) (l m n o)] + [(1 2 3) #:kw (a b) #:kw]) + [((({~and {~or wᵢ:id #:k}} ...) ...) + ({~and {~or (xᵢ ...) #:kw}} ...)) + (subtemplate (({?? wᵢ empty} ...) ...))])) + '((e f g) + (h empty) + (j k) + (l m n o))) + + ;; level-1 opt + same with some lvl2 filled/missing. ([wᵢ yᵢ] ...)/empty ... + (check-match (syntax->datum + (syntax-parse #'([(e f g) (h #:k) (j k) (l m n o)] + [(1 2 3) #:kw (a b) #:kw]) + [((({~and {~or wᵢ:id #:k}} ...) ...) + ({~and {~or (xᵢ ...) #:kw}} ...)) + (subtemplate ({?? ([wᵢ yᵢ] ...) empty} ...))])) + '(([e 1/y] [f 2/y] [g 3/y]) + empty + ([j a/y] [k b/y]) + ([l l/y] [m m/y] [n n/y] [o o/y]))) + + ;; level-1 opt + same but with some #f filled in. (yᵢ ...)/empty ... + (check-match (syntax->datum + (syntax-parse #'([(e f g) (h #:k) (j k) (l m n o)] + [(1 2 3) #:kw (a b) #:kw]) + [((({~and {~or wᵢ:id #:k}} ...) ...) + ({~and {~or (xᵢ ...) #:kw}} ...)) + (subtemplate ({?? (yᵢ ...) empty} ...))])) + `((1/y 2/y 3/y) + (h/y ,(? symbol? + (app symbol->string (regexp #rx"xᵢ[0-9]+/y")))) + (a/y b/y) + (l/y m/y n/y o/y))) + + ;; level-1 opt + same but with some #f filled in. (yᵢ ...)/empty ... + (check-match (syntax->datum + (syntax-parse #'([(e f g) (h #:k) (j k) (l m n o)] + [(1 2 3) #:kw (a b) #:kw]) + [((({~and {~or wᵢ:id #:k}} ...) ...) + ({~and {~or (xᵢ ...) #:kw}} ...)) + (subtemplate ({?? (?@ yᵢ ...) empty} ...))])) + `(1/y 2/y 3/y + h/y ,(? symbol? + (app symbol->string (regexp #rx"xᵢ[0-9]+/y"))) + a/y b/y + l/y m/y n/y o/y)) + + ;; level-1 opt + same but with some #f filled in. ([wᵢ yᵢ/empty] ...) ... + (check-match (syntax->datum + (syntax-parse #'([(e f g) (h #:k) (j k) (l m n o)] + [(1 2 3) #:kw (a b) #:kw]) + [((({~and {~or wᵢ:id #:k}} ...) ...) + ({~and {~or (xᵢ ...) #:kw}} ...)) + (subtemplate + (([(?? wᵢ emptywi) (?? yᵢ empty)] ...) ...))])) + `(([e 1/y] [f 2/y] [g 3/y]) + ([h h/y] + [emptywi + ,(? symbol? + (app symbol->string (regexp #rx"xᵢ[0-9]+/y")))]) + ([j a/y] [k b/y]) + ([l l/y] [m m/y] [n n/y] [o o/y]))) + + ;; level-1 opt + same but with some #f filled in. (yᵢ/empty ...) ... + (check-match (syntax->datum + (syntax-parse #'([(e f g) (h #:k) (j k) (l m n o)] + [(1 2 3) #:kw (a b) #:kw]) + [((({~and {~or wᵢ:id #:k}} ...) ...) + ({~and {~or (xᵢ ...) #:kw}} ...)) + (subtemplate (((?? yᵢ empty) ...) ...))])) + `((1/y 2/y 3/y) + (h/y ,(? symbol? + (app symbol->string (regexp #rx"xᵢ[0-9]+/y")))) + (a/y b/y) + (l/y m/y n/y o/y))) + + ;; level-1 opt + same but with some #f filled in. yᵢ/empty ... ... + (check-match (syntax->datum + (syntax-parse #'([(e f g) (h #:k) (j k) (l m n o)] + [(1 2 3) #:kw (a b) #:kw]) + [((({~and {~or wᵢ:id #:k}} ...) ...) + ({~and {~or (xᵢ ...) #:kw}} ...)) + (subtemplate ((?? yᵢ empty) ... ...))])) + `(1/y 2/y 3/y + h/y ,(? symbol? + (app symbol->string (regexp #rx"xᵢ[0-9]+/y"))) + a/y b/y + l/y m/y n/y o/y)))) +\ No newline at end of file