diff --git a/parse/debug.rkt-6-11 b/parse/debug.rkt-6-11 new file mode 100644 index 0000000..efb87b9 --- /dev/null +++ b/parse/debug.rkt-6-11 @@ -0,0 +1,127 @@ +#lang racket/base +(require (for-syntax racket/base + syntax/stx + racket/syntax + syntax/parse/private/rep-data + "private/rep.rkt" + syntax/parse/private/kws) + racket/list + racket/pretty + "../parse.rkt" + (except-in stxparse-info/parse/private/residual + prop:pattern-expander syntax-local-syntax-parse-pattern-introduce) + "private/runtime.rkt" + "private/runtime-progress.rkt" + "private/runtime-report.rkt" + syntax/parse/private/kws) + +;; No lazy loading for this module's dependencies. + +(provide syntax-class-parse + syntax-class-attributes + syntax-class-arity + syntax-class-keywords + + debug-rhs + debug-pattern + debug-parse + debug-syntax-parse!) + +(define-syntax (syntax-class-parse stx) + (syntax-case stx () + [(_ s x arg ...) + (parameterize ((current-syntax-context stx)) + (with-disappeared-uses + (let* ([argu (parse-argu (syntax->list #'(arg ...)) #:context stx)] + [stxclass + (get-stxclass/check-arity #'s stx + (length (arguments-pargs argu)) + (arguments-kws argu))] + [attrs (stxclass-attrs stxclass)]) + (with-syntax ([parser (stxclass-parser stxclass)] + [argu argu] + [(name ...) (map attr-name attrs)] + [(depth ...) (map attr-depth attrs)]) + #'(let ([fh (lambda (fs) fs)]) + (app-argu parser x x (ps-empty x x) #f fh fh #f + (lambda (fh . attr-values) + (map vector '(name ...) '(depth ...) attr-values)) + argu))))))])) + +(define-syntaxes (syntax-class-attributes + syntax-class-arity + syntax-class-keywords) + (let () + (define ((mk handler) stx) + (syntax-case stx () + [(_ s) + (parameterize ((current-syntax-context stx)) + (with-disappeared-uses + (handler (get-stxclass #'s))))])) + (values (mk (lambda (s) + (let ([attrs (stxclass-attrs s)]) + (with-syntax ([(a ...) (map attr-name attrs)] + [(d ...) (map attr-depth attrs)]) + #'(quote ((a d) ...)))))) + (mk (lambda (s) + (let ([a (stxclass-arity s)]) + #`(to-procedure-arity '#,(arity-minpos a) '#,(arity-maxpos a))))) + (mk (lambda (s) + (let ([a (stxclass-arity s)]) + #`(values '#,(arity-minkws a) '#,(arity-maxkws a)))))))) + +(define-syntax (debug-rhs stx) + (syntax-case stx () + [(debug-rhs rhs) + (let ([rhs (parse-rhs #'rhs #f #f #:context stx)]) + #`(quote #,rhs))])) + +(define-syntax (debug-pattern stx) + (syntax-case stx () + [(debug-pattern p . rest) + (let-values ([(rest pattern defs) + (parse-pattern+sides #'p #'rest + #:splicing? #f + #:decls (new-declenv null) + #:context stx)]) + (unless (stx-null? rest) + (raise-syntax-error #f "unexpected terms" stx rest)) + #`(quote ((definitions . #,defs) + (pattern #,pattern))))])) + +(define-syntax-rule (debug-parse x p ...) + (let/ec escape + (parameterize ((current-failure-handler + (lambda (_ fs) + (define-values (raw-fs-sexpr maximal-fs-sexpr) (fs->sexprs fs)) + (escape + `(parse-failure + #:raw-failures + ,raw-fs-sexpr + #:maximal-failures + ,maximal-fs-sexpr))))) + (syntax-parse x [p 'success] ...)))) + +(define (fs->sexprs fs) + (let* ([raw-fs (map invert-failure (reverse (flatten fs)))] + [selected-groups (maximal-failures raw-fs)]) + (values (failureset->sexpr raw-fs) + (let ([selected (map (lambda (fs) + (cons 'progress-class + (map failure->sexpr fs))) + selected-groups)]) + (if (= (length selected) 1) + (car selected) + (cons 'union selected)))))) + +(define (debug-syntax-parse!) + (define old-failure-handler (current-failure-handler)) + (current-failure-handler + (lambda (ctx fs) + (define-values (raw-fs-sexpr maximal-fs-sexpr) (fs->sexprs fs)) + (eprintf "*** syntax-parse debug info ***\n") + (eprintf "Raw failures:\n") + (pretty-write raw-fs-sexpr (current-error-port)) + (eprintf "Maximal failures:\n") + (pretty-write maximal-fs-sexpr (current-error-port)) + (old-failure-handler ctx fs)))) diff --git a/parse/experimental/private/substitute.rkt-6-11 b/parse/experimental/private/substitute.rkt-6-11 new file mode 100644 index 0000000..5d8ba95 --- /dev/null +++ b/parse/experimental/private/substitute.rkt-6-11 @@ -0,0 +1,500 @@ +#lang racket/base +(require syntax/parse/private/minimatch + racket/private/promise + racket/private/stx) ;; syntax/stx +(provide translate + syntax-local-template-metafunction-introduce) + +#| +;; Doesn't seem to make much difference. +(require (rename-in racket/unsafe/ops + [unsafe-vector-ref vector-ref] + [unsafe-vector-set! vector-set!] + [unsafe-car car] + [unsafe-cdr cdr])) +|# + +;; ============================================================ + +#| +A Guide (G) is one of: + - '_ + - VarRef ;; no syntax check + - (vector 'check VarRef) ;; check value is syntax + - (cons G G) + - (vector 'vector G) + - (vector 'struct G) + - (vector 'box G) + - (vector 'dots HG (listof (vector-of VarRef)) nat (listof nat) G) + - (vector 'app HG G) + - (vector 'escaped G) + - (vector 'orelse G G) + - (vector 'metafun integer G) + - (vector 'copy-props G (listof symbol)) + - (vector 'set-props G (listof (cons symbol any))) + - (vector 'unsyntax VarRef) + - (vector 'relocate G) + +A HeadGuide (HG) is one of: + - G + - (vector 'app-opt H) + - (vector 'orelse-h H H) + - (vector 'splice G) + - (vector 'unsyntax-splicing VarRef) + +An VarRef is one of + - positive-exact-integer ;; represents depth=0 pvar ref or metafun ref + - negative-exact-integer ;; represents depth>0 pvar ref (within ellipsis) +|# + +(define (head-guide? x) + (match x + [(vector 'app-opt g) #t] + [(vector 'splice g) #t] + [(vector 'orelse-h g1 g2) #t] + [(vector 'unsyntax-splicing var) #t] + [_ #f])) + +;; ============================================================ + +;; Used to indicate absent pvar in template; ?? catches +;; Note: not an exn, don't need continuation marks +(require (only-in rackunit require/expose)) +#;(require/expose syntax/parse/experimental/private/substitute + (absent-pvar + absent-pvar? + absent-pvar-ctx + absent-pvar-v + absent-pvar-wanted-list?)) +;; this struct is only used in this file, and is not exported, so I guess it's +;; ok to not steal the struct from syntax/parse/experimental/private/substitute +;; Furthermore, the require/expose above does not work reliably. +(struct absent-pvar (ctx v wanted-list?)) + +;; ============================================================ + +;; A translated-template is (vector loop-env -> syntax) +;; A loop-env is either a vector of values or a single value, +;; depending on lenv-mode of enclosing ellipsis ('dots) form. + +(define (translate stx g env-length) + (let ([f (translate-g stx stx g env-length 0)]) + (lambda (env lenv) + (unless (>= (vector-length env) env-length) + (error 'template "internal error: environment too short")) + (with-handlers ([absent-pvar? + (lambda (ap) + (err/not-syntax (absent-pvar-ctx ap) (absent-pvar-v ap)))]) + (f env lenv))))) + +;; lenv-mode is one of +;; - 'one ;; lenv is single value; address as -1 +;; - nat ;; lenv is vector; address as (- -1 index); 0 means no loop env + +(define (translate-g stx0 stx g env-length lenv-mode) + (define (loop stx g) (translate-g stx0 stx g env-length lenv-mode)) + (define (loop-h stx hg) (translate-hg stx0 stx hg env-length lenv-mode)) + (define (get index env lenv) (get-var index env lenv lenv-mode)) + + (match g + + ['_ (lambda (env lenv) stx)] + + [(? exact-integer? index) + (check-var index env-length lenv-mode) + (lambda (env lenv) (get index env lenv))] + + [(vector 'check index) + (check-var index env-length lenv-mode) + (lambda (env lenv) (check-stx stx (get index env lenv)))] + + [(cons g1 g2) + (let ([f1 (loop (stx-car stx) g1)] + [f2 (loop (stx-cdr stx) g2)]) + (cond [(syntax? stx) + (lambda (env lenv) + (restx stx (cons (f1 env lenv) (f2 env lenv))))] + [(eq? g1 '_) + (let ([c1 (stx-car stx)]) + (lambda (env lenv) + (cons c1 (f2 env lenv))))] + [(eq? g2 '_) + (let ([c2 (stx-cdr stx)]) + (lambda (env lenv) + (cons (f1 env lenv) c2)))] + [else + (lambda (env lenv) + (cons (f1 env lenv) (f2 env lenv)))]))] + + [(vector 'dots ghead henv nesting uptos gtail) + ;; At each nesting depth, indexes [0,upto) of lenv* vary; the rest are fixed. + ;; An alternative would be to have a list of henvs, but that would inhibit + ;; the nice simple vector reuse via vector-car/cdr!. + (let* ([lenv*-len (vector-length henv)] + [ghead-is-hg? (head-guide? ghead)] + [ftail (loop (stx-drop (add1 nesting) stx) gtail)]) + (for ([var (in-vector henv)]) + (check-var var env-length lenv-mode)) + (unless (= nesting (length uptos)) + (error 'template "internal error: wrong number of uptos")) + (let ([last-upto + (for/fold ([last 1]) ([upto (in-list uptos)]) + (unless (<= upto lenv*-len) + (error 'template "internal error: upto is too big")) + (unless (>= upto last) + (error 'template "internal error: uptos decreased: ~e" uptos)) + upto)]) + (unless (= lenv*-len last-upto) + (error 'template "internal error: last upto was not full env"))) + (cond [(and (= lenv*-len 1) (= nesting 1) (not ghead-is-hg?) + (equal? ghead '-1)) + ;; Fast path for (pvar ... . T) template + ;; - no list? or syntax? checks needed (because ghead is just raw varref, + ;; no 'check' wrapper) + ;; - avoid trivial map, just append + (let ([var-index (vector-ref henv 0)]) + (lambda (env lenv) + (let ([lenv* (get var-index env lenv)]) + (restx stx (append lenv* (ftail env lenv))))))] + [(and (= lenv*-len 1) (= nesting 1) (not ghead-is-hg?)) + ;; Fast path for (T ... . T) template + ;; - specialize lenv to avoid vector allocation/mutation + ;; - body is deforested (append (map _ _) _) preserving eval order + ;; - could try to eliminate 'check-list', but probably not worth the bother + (let* ([fhead (translate-g stx0 (stx-car stx) ghead env-length 'one)] + [var-index (vector-ref henv 0)]) + (lambda (env lenv) + (restx stx + (let ([lenv* (check-list/depth stx (get var-index env lenv) 1)]) + (let dotsloop ([lenv* lenv*]) + (if (null? lenv*) + (ftail env lenv) + (cons (fhead env (car lenv*)) + (dotsloop (cdr lenv*)))))))))] + [else + ;; Slow/general path for (H ...^n . T) + (let ([fhead (if ghead-is-hg? + (translate-hg stx0 (stx-car stx) ghead env-length lenv*-len) + (translate-g stx0 (stx-car stx) ghead env-length lenv*-len))]) + (lambda (env lenv) + #| + The template is "driven" by pattern variables bound to (listof^n syntax). + For example, in (H ... ... . T), the pvars of H have (listof (listof syntax)), + and we need a doubly-nested loop, like + (for/list ([stxlist^1 (in-list stxlist^2)]) + (for/list ([stx (in-list stxlist^1)]) + ___ fhead ___)) + Since we can have arbitrary numbers of ellipses, we have 'nestloop' recur + over ellipsis levels and 'dotsloop' recur over the contents of the pattern + variables' (listof^n syntax) values. + + Also, we reuse lenv vectors to reduce allocation. There is one aux lenv + vector per nesting level, preallocated in aux-lenvs. For continuation-safety + we must install a continuation barrier around metafunction applications. + |# + (define (nestloop lenv* nesting uptos aux-lenvs) + (cond [(zero? nesting) + (fhead env lenv*)] + [else + (let ([iters (check-lenv/get-iterations stx lenv*)]) + (let ([lenv** (car aux-lenvs)] + [aux-lenvs** (cdr aux-lenvs)] + [upto** (car uptos)] + [uptos** (cdr uptos)]) + (let dotsloop ([iters iters]) + (if (zero? iters) + null + (begin (vector-car/cdr! lenv** lenv* upto**) + (let ([row (nestloop lenv** (sub1 nesting) uptos** aux-lenvs**)]) + (cons row (dotsloop (sub1 iters)))))))))])) + (define initial-lenv* + (vector-map (lambda (index) (get index env lenv)) henv)) + (define aux-lenvs + (for/list ([depth (in-range nesting)]) (make-vector lenv*-len))) + + ;; Check initial-lenv* contains lists of right depths. + ;; At each nesting depth, indexes [0,upto) of lenv* vary; + ;; uptos is monotonic nondecreasing (every variable varies in inner + ;; loop---this is always counterintuitive to me). + (let checkloop ([depth nesting] [uptos uptos] [start 0]) + (when (pair? uptos) + (for ([v (in-vector initial-lenv* start (car uptos))]) + (check-list/depth stx v depth)) + (checkloop (sub1 depth) (cdr uptos) (car uptos)))) + + (define head-results + ;; if ghead-is-hg?, is (listof^(nesting+1) stx) -- extra listof for loop-h + ;; otherwise, is (listof^nesting stx) + (nestloop initial-lenv* nesting uptos aux-lenvs)) + (define tail-result (ftail env lenv)) + (restx stx + (nested-append head-results + (if ghead-is-hg? nesting (sub1 nesting)) + tail-result))))]))] + + [(vector 'app ghead gtail) + (let ([fhead (loop-h (stx-car stx) ghead)] + [ftail (loop (stx-cdr stx) gtail)]) + (lambda (env lenv) + (restx stx (append (fhead env lenv) (ftail env lenv)))))] + + [(vector 'escaped g1) + (loop (stx-cadr stx) g1)] + + [(vector 'orelse g1 g2) + (let ([f1 (loop (stx-cadr stx) g1)] + [f2 (loop (stx-caddr stx) g2)]) + (lambda (env lenv) + (with-handlers ([absent-pvar? + (lambda (_e) + (f2 env lenv))]) + (f1 env lenv))))] + + [(vector 'metafun index g1) + (let ([f1 (loop (stx-cdr stx) g1)]) + (check-var index env-length lenv-mode) + (lambda (env lenv) + (let ([v (restx stx (cons (stx-car stx) (f1 env lenv)))] + [mark (make-syntax-introducer)] + [old-mark (current-template-metafunction-introducer)] + [mf (get index env lenv)]) + (parameterize ((current-template-metafunction-introducer mark) + (old-template-metafunction-introducer old-mark)) + (let ([r (call-with-continuation-barrier (lambda () (mf (mark (old-mark v)))))]) + (unless (syntax? r) + (raise-syntax-error #f "result of template metafunction was not syntax" stx)) + (restx stx (old-mark (mark r))))))))] + + [(vector 'vector g1) + (let ([f1 (loop (vector->list (syntax-e stx)) g1)]) + (lambda (env lenv) + (restx stx (list->vector (f1 env lenv)))))] + + [(vector 'struct g1) + (let ([f1 (loop (cdr (vector->list (struct->vector (syntax-e stx)))) g1)] + [key (prefab-struct-key (syntax-e stx))]) + (lambda (env lenv) + (restx stx (apply make-prefab-struct key (f1 env lenv)))))] + + [(vector 'box g1) + (let ([f1 (loop (unbox (syntax-e stx)) g1)]) + (lambda (env lenv) + (restx stx (box (f1 env lenv)))))] + + [(vector 'copy-props g1 keys) + (let ([f1 (loop stx g1)]) + (lambda (env lenv) + (for/fold ([v (f1 env lenv)]) ([key (in-list keys)]) + (let ([pvalue (syntax-property stx key)]) + (if pvalue + (syntax-property v key pvalue) + v)))))] + + [(vector 'set-props g1 props-alist) + (let ([f1 (loop stx g1)]) + (lambda (env lenv) + (for/fold ([v (f1 env lenv)]) ([entry (in-list props-alist)]) + (syntax-property v (car entry) (cdr entry)))))] + + [(vector 'unsyntax var) + (let ([f1 (loop stx var)]) + (lambda (env lenv) + (restx stx (f1 env lenv))))] + + [(vector 'relocate g1 var) + (let ([f1 (loop stx g1)]) + (lambda (env lenv) + (let ([result (f1 env lenv)] + [loc (get var env lenv)]) + (if (or (syntax-source loc) + (syntax-position loc)) + (datum->syntax result (syntax-e result) loc result) + result))))])) + +(define (translate-hg stx0 stx hg env-length lenv-mode) + (define (loop stx g) (translate-g stx0 stx g env-length lenv-mode)) + (define (loop-h stx hg) (translate-hg stx0 stx hg env-length lenv-mode)) + (define (get index env lenv) (get-var index env lenv lenv-mode)) + + (match hg + + [(vector 'app-opt hg1) + (let ([f1 (loop-h (stx-cadr stx) hg1)]) + (lambda (env lenv) + (with-handlers ([absent-pvar? (lambda (_e) null)]) + (f1 env lenv))))] + + [(vector 'orelse-h hg1 hg2) + (let ([f1 (loop-h (stx-cadr stx) hg1)] + [f2 (loop-h (stx-caddr stx) hg2)]) + (lambda (env lenv) + (with-handlers ([absent-pvar? + (lambda (_e) + (f2 env lenv))]) + (f1 env lenv))))] + + [(vector 'splice g1) + (let ([f1 (loop (stx-cdr stx) g1)]) + (lambda (env lenv) + (let* ([v (f1 env lenv)] + [v* (stx->list v)]) + (unless (list? v*) + (raise-syntax-error 'template + "splicing template did not produce a syntax list" + stx)) + v*)))] + + [(vector 'unsyntax-splicing index) + (check-var index env-length lenv-mode) + (lambda (env lenv) + (let* ([v (get index env lenv)] + [v* (stx->list v)]) + (unless (list? v*) + (raise-syntax-error 'template + "unsyntax-splicing expression did not produce a syntax list" + stx)) + v*))] + + [_ + (let ([f (loop stx hg)]) + (lambda (env lenv) + (list (f env lenv))))])) + +(define (get-var index env lenv lenv-mode) + (cond [(positive? index) + (vector-ref env (sub1 index))] + [(negative? index) + (case lenv-mode + ((one) lenv) + (else (vector-ref lenv (- -1 index))))])) + +(define (check-var index env-length lenv-mode) + (cond [(positive? index) + (unless (< (sub1 index) env-length) + (error/bad-index index))] + [(negative? index) + (unless (< (- -1 index) + (case lenv-mode + ((one) 1) + (else lenv-mode))) + (error/bad-index))])) + +(define (check-lenv/get-iterations stx lenv) + (unless (list? (vector-ref lenv 0)) + (error 'template "pattern variable used in ellipsis pattern is not defined")) + (let ([len0 (length (vector-ref lenv 0))]) + (for ([v (in-vector lenv)]) + (unless (list? v) + (error 'template "pattern variable used in ellipsis pattern is not defined")) + (unless (= len0 (length v)) + (raise-syntax-error 'template + "incompatible ellipsis match counts for template" + stx))) + len0)) + +;; ---- + +(define current-template-metafunction-introducer + (make-parameter + (lambda (stx) + (if (syntax-transforming?) + (syntax-local-introduce stx) + stx)))) + +(define old-template-metafunction-introducer + (make-parameter #f)) + +(define (syntax-local-template-metafunction-introduce stx) + (let ([mark (current-template-metafunction-introducer)] + [old-mark (old-template-metafunction-introducer)]) + (unless old-mark + (error 'syntax-local-template-metafunction-introduce + "must be called within the dynamic extent of a template metafunction")) + (mark (old-mark stx)))) + +;; ---- + +(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) + val)) + +;; nested-append : (listof^(nesting+1) A) nat (listof A) -> (listof A) +;; (Actually, in practice onto is stx, so this is an improper append.) +(define (nested-append lst nesting onto) + (cond [(zero? nesting) (append lst onto)] + [(null? lst) onto] + [else (nested-append (car lst) (sub1 nesting) + (nested-append (cdr lst) nesting onto))])) + +(define (check-stx ctx v) + (let loop ([v v]) + (cond [(syntax? v) + v] + [(promise? v) + (loop (force v))] + [(eq? v #f) + (raise (absent-pvar ctx v #f))] + [else (err/not-syntax ctx v)]))) + +(define (check-list/depth ctx v0 depth0) + (let depthloop ([v v0] [depth depth0]) + (cond [(zero? depth) v] + [(and (= depth 1) (list? v)) v] + [else + (let loop ([v v]) + (cond [(null? v) + null] + [(pair? v) + (let ([new-car (depthloop (car v) (sub1 depth))] + [new-cdr (loop (cdr v))]) + ;; Don't copy unless necessary + (if (and (eq? new-car (car v)) (eq? new-cdr (cdr v))) + v + (cons new-car new-cdr)))] + [(promise? v) + (loop (force v))] + [(eq? v #f) + (raise (absent-pvar ctx v0 #t))] + [else + (err/not-syntax ctx v0)]))]))) + +;; Note: slightly different from error msg in syntax/parse/private/residual: +;; here says "contains" instead of "is bound to", because might be within list +(define (err/not-syntax ctx v) + (raise-syntax-error #f + (format "attribute contains non-syntax value\n value: ~e" v) + ctx)) + +(define (error/bad-index index) + (error 'template "internal error: bad index: ~e" index)) + +(define (vector-car/cdr! dest-v src-v upto) + (let ([len (vector-length dest-v)]) + (let loop ([i 0]) + (when (< i upto) + (let ([p (vector-ref src-v i)]) + (vector-set! dest-v i (car p)) + (vector-set! src-v i (cdr p))) + (loop (add1 i)))) + (let loop ([j upto]) + (when (< j len) + (vector-set! dest-v j (vector-ref src-v j)) + (loop (add1 j)))))) + +(define (vector-map f src-v) + (let* ([len (vector-length src-v)] + [dest-v (make-vector len)]) + (let loop ([i 0]) + (when (< i len) + (vector-set! dest-v i (f (vector-ref src-v i))) + (loop (add1 i)))) + dest-v)) \ No newline at end of file diff --git a/parse/experimental/provide.rkt-6-11 b/parse/experimental/provide.rkt-6-11 new file mode 100644 index 0000000..280a73d --- /dev/null +++ b/parse/experimental/provide.rkt-6-11 @@ -0,0 +1,156 @@ +#lang racket/base +(require racket/contract/base + racket/contract/combinator + syntax/location + (for-syntax racket/base + racket/syntax + syntax/parse/private/minimatch + stxparse-info/parse/pre + syntax/parse/private/residual-ct ;; keep abs. path + syntax/parse/private/kws + syntax/contract)) +(provide provide-syntax-class/contract + syntax-class/c + splicing-syntax-class/c) + +;; FIXME: +;; - seems to get first-requiring-module wrong, not surprising +;; - extend to contracts on attributes? +;; - syntax-class/c etc just a made-up name, for now +;; (connect to dynamic syntax-classes, eventually) + +(define-syntaxes (syntax-class/c splicing-syntax-class/c) + (let ([nope + (lambda (stx) + (raise-syntax-error #f "not within `provide-syntax-class/contract form" stx))]) + (values nope nope))) + +(begin-for-syntax + (define-struct ctcrec (mpcs mkws mkwcs opcs okws okwcs) #:prefab + #:omit-define-syntaxes)) + +(begin-for-syntax + ;; do-one-contract : stx id stxclass ctcrec id -> stx + (define (do-one-contract stx scname stxclass rec pos-module-source) + ;; First, is the contract feasible? + (match (stxclass-arity stxclass) + [(arity minpos maxpos minkws maxkws) + (let* ([minpos* (length (ctcrec-mpcs rec))] + [maxpos* (+ minpos* (length (ctcrec-opcs rec)))] + [minkws* (sort (map syntax-e (ctcrec-mkws rec)) keyword<?)] + [maxkws* (sort (append minkws* (map syntax-e (ctcrec-okws rec))) keyword<?)]) + (define (err msg . args) + (apply wrong-syntax scname msg args)) + (unless (<= minpos minpos*) + (err (string-append "expected a syntax class with at most ~a " + "required positional arguments, got one with ~a") + minpos* minpos)) + (unless (<= maxpos* maxpos) + (err (string-append "expected a syntax class with at least ~a " + "total positional arguments (required and optional), " + "got one with ~a") + maxpos* maxpos)) + (unless (null? (diff/sorted/eq minkws minkws*)) + (err (string-append "expected a syntax class with at most the " + "required keyword arguments ~a, got one with ~a") + (join-sep (map kw->string minkws*) "," "and") + (join-sep (map kw->string minkws) "," "and"))) + (unless (null? (diff/sorted/eq maxkws* maxkws)) + (err (string-append "expected a syntax class with at least the optional " + "keyword arguments ~a, got one with ~a") + (join-sep (map kw->string maxkws*) "," "and") + (join-sep (map kw->string maxkws) "," "and"))) + (with-syntax ([scname scname] + [#s(stxclass name arity attrs parser splicing? opts inline) + stxclass] + [#s(ctcrec (mpc ...) (mkw ...) (mkwc ...) + (opc ...) (okw ...) (okwc ...)) + rec] + [arity* (arity minpos* maxpos* minkws* maxkws*)] + [(parser-contract contracted-parser contracted-scname) + (generate-temporaries #`(contract parser #,scname))]) + (with-syntax ([(mpc-id ...) (generate-temporaries #'(mpc ...))] + [(mkwc-id ...) (generate-temporaries #'(mkwc ...))] + [(opc-id ...) (generate-temporaries #'(opc ...))] + [(okwc-id ...) (generate-temporaries #'(okwc ...))]) + (with-syntax ([((mkw-c-part ...) ...) #'((mkw mkwc-id) ...)] + [((okw-c-part ...) ...) #'((okw okwc-id) ...)] + [((mkw-name-part ...) ...) #'((mkw ,(contract-name mkwc-id)) ...)] + [((okw-name-part ...) ...) #'((okw ,(contract-name okwc-id)) ...)]) + #`(begin + (define parser-contract + (let ([mpc-id mpc] ... + [mkwc-id mkwc] ... + [opc-id opc] ... + [okwc-id okwc] ...) + (rename-contract + (->* (any/c any/c any/c any/c any/c any/c any/c any/c + mpc-id ... mkw-c-part ... ...) + (okw-c-part ... ...) + any) + `(,(if 'splicing? 'splicing-syntax-class/c 'syntax-class/c) + [,(contract-name mpc-id) ... mkw-name-part ... ...] + [okw-name-part ... ...])))) + (define-module-boundary-contract contracted-parser + parser parser-contract #:pos-source #,pos-module-source) + (define-syntax contracted-scname + (make-stxclass + (quote-syntax name) + 'arity* + 'attrs + (quote-syntax contracted-parser) + 'splicing? + 'opts #f)) ;; must disable inlining + (provide (rename-out [contracted-scname scname])))))))]))) + +(define-syntax (provide-syntax-class/contract stx) + + (define-syntax-class stxclass-ctc + #:description "syntax-class/c or splicing-syntax-class/c form" + #:literals (syntax-class/c splicing-syntax-class/c) + #:attributes (rec) + #:commit + (pattern ((~or syntax-class/c splicing-syntax-class/c) + mand:ctclist + (~optional opt:ctclist)) + #:attr rec (make-ctcrec (attribute mand.pc.c) + (attribute mand.kw) + (attribute mand.kwc.c) + (or (attribute opt.pc.c) '()) + (or (attribute opt.kw) '()) + (or (attribute opt.kwc.c) '())))) + + (define-syntax-class ctclist + #:attributes ([pc.c 1] [kw 1] [kwc.c 1]) + #:commit + (pattern ((~or pc:expr (~seq kw:keyword kwc:expr)) ...) + #:with (pc.c ...) (for/list ([pc-expr (in-list (syntax->list #'(pc ...)))]) + (wrap-expr/c #'contract? pc-expr)) + #:with (kwc.c ...) (for/list ([kwc-expr (in-list (syntax->list #'(kwc ...)))]) + (wrap-expr/c #'contract? kwc-expr)))) + + (syntax-parse stx + [(_ [scname c:stxclass-ctc] ...) + #:declare scname (static stxclass? "syntax class") + (parameterize ((current-syntax-context stx)) + (with-disappeared-uses + #`(begin (define pos-module-source (quote-module-name)) + #,@(for/list ([scname (in-list (syntax->list #'(scname ...)))] + [stxclass (in-list (attribute scname.value))] + [rec (in-list (attribute c.rec))]) + (do-one-contract stx scname stxclass rec #'pos-module-source)))))])) + +;; Copied from unstable/contract, +;; which requires racket/contract, not racket/contract/base + +;; rename-contract : contract any/c -> contract +;; If the argument is a flat contract, so is the result. +(define (rename-contract ctc name) + (let ([ctc (coerce-contract 'rename-contract ctc)]) + (if (flat-contract? ctc) + (flat-named-contract name (flat-contract-predicate ctc)) + (let* ([ctc-fo (contract-first-order ctc)] + [late-neg-proj (contract-late-neg-projection ctc)]) + (make-contract #:name name + #:late-neg-projection late-neg-proj + #:first-order ctc-fo))))) diff --git a/parse/experimental/reflect.rkt-6-11 b/parse/experimental/reflect.rkt-6-11 new file mode 100644 index 0000000..460d964 --- /dev/null +++ b/parse/experimental/reflect.rkt-6-11 @@ -0,0 +1,149 @@ +#lang racket/base +(require (for-syntax racket/base + racket/lazy-require + racket/syntax + syntax/parse/private/residual-ct) ;; keep abs.path + racket/contract/base + racket/contract/combinator + syntax/parse/private/minimatch + syntax/parse/private/keywords + "../private/runtime-reflect.rkt" + syntax/parse/private/kws) +(begin-for-syntax + (lazy-require + [syntax/parse/private/rep-data ;; keep abs. path + (get-stxclass)])) +;; FIXME: workaround for phase>0 bug in racket/runtime-path (and thus lazy-require) +;; Without this, dependencies don't get collected. +(require racket/runtime-path (for-meta 2 '#%kernel)) +(define-runtime-module-path-index _unused_ 'syntax/parse/private/rep-data) + +(define-syntax (reify-syntax-class stx) + (if (eq? (syntax-local-context) 'expression) + (syntax-case stx () + [(rsc sc) + (with-disappeared-uses + (let* ([stxclass (get-stxclass #'sc)] + [splicing? (stxclass-splicing? stxclass)]) + (unless (scopts-delimit-cut? (stxclass-opts stxclass)) + (raise-syntax-error #f "cannot reify syntax class with #:no-delimit-cut option" + stx #'sc)) + (with-syntax ([name (stxclass-name stxclass)] + [parser (stxclass-parser stxclass)] + [arity (stxclass-arity stxclass)] + [(#s(attr aname adepth _) ...) (stxclass-attrs stxclass)] + [ctor + (if splicing? + #'reified-splicing-syntax-class + #'reified-syntax-class)]) + #'(ctor 'name parser 'arity '((aname adepth) ...)))))]) + #`(#%expression #,stx))) + +(define (reified-syntax-class-arity r) + (match (reified-arity r) + [(arity minpos maxpos _ _) + (to-procedure-arity minpos maxpos)])) + +(define (reified-syntax-class-keywords r) + (match (reified-arity r) + [(arity _ _ minkws maxkws) + (values minkws maxkws)])) + +(define (reified-syntax-class-attributes r) + (reified-signature r)) + +(define reified-syntax-class-curry + (make-keyword-procedure + (lambda (kws1 kwargs1 r . rest1) + (match r + [(reified name parser arity1 sig) + (let () + (check-curry arity1 (length rest1) kws1 + (lambda (msg) + (raise-mismatch-error 'reified-syntax-class-curry + (string-append msg ": ") r))) + (let* ([curried-arity + (match arity1 + [(arity minpos maxpos minkws maxkws) + (let* ([rest1-length (length rest1)] + [minpos* (- minpos rest1-length)] + [maxpos* (- maxpos rest1-length)] + [minkws* (sort (remq* kws1 minkws) keyword<?)] + [maxkws* (sort (remq* kws1 maxkws) keyword<?)]) + (arity minpos* maxpos* minkws* maxkws*))])] + [curried-parser + (make-keyword-procedure + (lambda (kws2 kwargs2 x cx pr es fh cp rl success . rest2) + (let-values ([(kws kwargs) (merge2 kws1 kws2 kwargs1 kwargs2)]) + (keyword-apply parser kws kwargs x cx pr es fh cp rl success + (append rest1 rest2)))))] + [ctor + (cond [(reified-syntax-class? r) + reified-syntax-class] + [(reified-splicing-syntax-class? r) + reified-splicing-syntax-class] + [else + (error 'curry-reified-syntax-class "INTERNAL ERROR: ~e" r)])]) + (ctor name curried-parser curried-arity sig)))])))) + +(define (merge2 kws1 kws2 kwargs1 kwargs2) + (cond [(null? kws1) + (values kws2 kwargs2)] + [(null? kws2) + (values kws1 kwargs1)] + [(keyword<? (car kws1) (car kws2)) + (let-values ([(m-kws m-kwargs) + (merge2 (cdr kws1) kws2 (cdr kwargs1) kwargs2)]) + (values (cons (car kws1) m-kws) (cons (car kwargs1) m-kwargs)))] + [else + (let-values ([(m-kws m-kwargs) + (merge2 kws1 (cdr kws2) kwargs1 (cdr kwargs2))]) + (values (cons (car kws2) m-kws) (cons (car kwargs2) m-kwargs)))])) + +;; ---- + +(provide reify-syntax-class + ~reflect + ~splicing-reflect) + +(provide/contract + [reified-syntax-class? + (-> any/c boolean?)] + [reified-splicing-syntax-class? + (-> any/c boolean?)] + [reified-syntax-class-attributes + (-> (or/c reified-syntax-class? reified-splicing-syntax-class?) + (listof (list/c symbol? exact-nonnegative-integer?)))] + [reified-syntax-class-arity + (-> (or/c reified-syntax-class? reified-splicing-syntax-class?) + procedure-arity?)] + [reified-syntax-class-keywords + (-> (or/c reified-syntax-class? reified-splicing-syntax-class?) + (values (listof keyword?) + (listof keyword?)))] + [reified-syntax-class-curry + (make-contract #:name '(->* ((or/c reified-syntax-class? reified-splicing-syntax-class/c)) + (#:<kw> any/c ...) + #:rest list? + (or/c reified-syntax-class? reified-splicing-syntax-class/c)) + #:late-neg-projection + (lambda (blame) + (let ([check-reified + ((contract-late-neg-projection + (or/c reified-syntax-class? reified-splicing-syntax-class?)) + (blame-swap blame))]) + (lambda (f neg-party) + (if (and (procedure? f) + (procedure-arity-includes? f 1)) + (make-keyword-procedure + (lambda (kws kwargs r . args) + (keyword-apply f kws kwargs (check-reified r neg-party) args))) + (raise-blame-error + blame #:missing-party neg-party + f + "expected a procedure of at least one argument, given ~e" + f))))) + #:first-order + (lambda (f) + (and (procedure? f) (procedure-arity-includes? f))))]) + diff --git a/parse/experimental/specialize.rkt-6-11 b/parse/experimental/specialize.rkt-6-11 new file mode 100644 index 0000000..72f1e6c --- /dev/null +++ b/parse/experimental/specialize.rkt-6-11 @@ -0,0 +1,40 @@ +#lang racket/base +(require (for-syntax racket/base + racket/syntax + syntax/parse/private/kws + syntax/parse/private/rep-data + "../private/rep.rkt") + "../private/runtime.rkt") +(provide define-syntax-class/specialize) + +(define-syntax (define-syntax-class/specialize stx) + (parameterize ((current-syntax-context stx)) + (syntax-case stx () + [(dscs header sc-expr) + (with-disappeared-uses + (let-values ([(name formals arity) + (let ([p (check-stxclass-header #'header stx)]) + (values (car p) (cadr p) (caddr p)))] + [(target-scname argu) + (let ([p (check-stxclass-application #'sc-expr stx)]) + (values (car p) (cdr p)))]) + (let* ([pos-count (length (arguments-pargs argu))] + [kws (arguments-kws argu)] + [target (get-stxclass/check-arity target-scname target-scname pos-count kws)]) + (with-syntax ([name name] + [formals formals] + [parser (generate-temporary (format-symbol "parser-~a" #'name))] + [splicing? (stxclass-splicing? target)] + [arity arity] + [attrs (stxclass-attrs target)] + [opts (stxclass-opts target)] + [target-parser (stxclass-parser target)] + [argu argu]) + #`(begin (define-syntax name + (stxclass 'name 'arity 'attrs + (quote-syntax parser) + 'splicing? + 'opts #f)) + (define-values (parser) + (lambda (x cx pr es fh0 cp0 rl success . formals) + (app-argu target-parser x cx pr es fh0 cp0 rl success argu))))))))]))) diff --git a/parse/experimental/splicing.rkt-6-11 b/parse/experimental/splicing.rkt-6-11 new file mode 100644 index 0000000..e0694aa --- /dev/null +++ b/parse/experimental/splicing.rkt-6-11 @@ -0,0 +1,95 @@ +#lang racket/base +(require (for-syntax racket/base + stxparse-info/parse + racket/lazy-require + syntax/parse/private/kws) + stxparse-info/parse/private/residual) ;; keep abs. path +(provide define-primitive-splicing-syntax-class) + +(begin-for-syntax + (lazy-require + [syntax/parse/private/rep-attrs + (sort-sattrs)])) +;; FIXME: workaround for phase>0 bug in racket/runtime-path (and thus lazy-require) +;; Without this, dependencies don't get collected. +(require racket/runtime-path (for-meta 2 '#%kernel)) +(define-runtime-module-path-index _unused_ 'syntax/parse/private/rep-attrs) + +(define-syntax (define-primitive-splicing-syntax-class stx) + + (define-syntax-class attr + #:commit + (pattern name:id + #:with depth #'0) + (pattern [name:id depth:nat])) + + (syntax-parse stx + [(dssp (name:id param:id ...) + (~or (~once (~seq #:attributes (a:attr ...)) + #:name "attributes declaration") + (~once (~seq #:description description) + #:name "description declaration")) ... + proc:expr) + #'(begin + (define (get-description param ...) + description) + (define parser + (let ([permute (mk-permute '(a.name ...))]) + (lambda (x cx pr es fh _cp rl success param ...) + (let ([stx (datum->syntax cx x cx)]) + (let ([result + (let/ec escape + (cons 'ok + (proc stx + (lambda ([msg #f] [stx #f]) + (escape (list 'error msg stx))))))]) + (case (car result) + ((ok) + (apply success + ((mk-check-result pr 'name (length '(a.name ...)) permute x cx fh) + (cdr result)))) + ((error) + (let ([es + (es-add-message (cadr result) + (es-add-thing pr (get-description param ...) #f rl es))]) + (fh (failure pr es)))))))))) + (define-syntax name + (stxclass 'name (arity (length '(param ...)) (length '(param ...)) '() '()) + (sort-sattrs '(#s(attr a.name a.depth #f) ...)) + (quote-syntax parser) + #t + (scopts (length '(a.name ...)) #t #t #f) + #f)))])) + +(define (mk-permute unsorted-attrs) + (let ([sorted-attrs + (sort unsorted-attrs string<? #:key symbol->string #:cache-keys? #t)]) + (if (equal? unsorted-attrs sorted-attrs) + values + (let* ([pos-table + (for/hasheq ([a (in-list unsorted-attrs)] [i (in-naturals)]) + (values a i))] + [indexes + (for/vector ([a (in-list sorted-attrs)]) + (hash-ref pos-table a))]) + (lambda (result) + (for/list ([index (in-vector indexes)]) + (list-ref result index))))))) + +(define (mk-check-result pr name attr-count permute x cx fh) + (lambda (result) + (unless (list? result) + (error name "parser returned non-list")) + (let ([rlength (length result)]) + (unless (= rlength (+ 1 attr-count)) + (error name "parser returned list of wrong length; expected length ~s, got ~e" + (+ 1 attr-count) + result)) + (let ([skip (car result)]) + ;; Compute rest-x & rest-cx from skip + (unless (exact-nonnegative-integer? skip) + (error name "expected exact nonnegative integer for first element of result list, got ~e" + skip)) + (let-values ([(rest-x rest-cx) (stx-list-drop/cx x cx skip)]) + (list* fh rest-x rest-cx (ps-add-cdr pr skip) + (permute (cdr result)))))))) diff --git a/parse/experimental/template.rkt-6-11 b/parse/experimental/template.rkt-6-11 new file mode 100644 index 0000000..0cad7a1 --- /dev/null +++ b/parse/experimental/template.rkt-6-11 @@ -0,0 +1,684 @@ +#lang racket/base +(require (for-syntax racket/base + "dset.rkt" + racket/syntax + syntax/parse/private/minimatch + racket/private/stx ;; syntax/stx + racket/private/sc + racket/struct + auto-syntax-e/utils) + stxparse-info/parse/private/residual + "private/substitute.rkt") +(provide template + template/loc + quasitemplate + quasitemplate/loc + define-template-metafunction + syntax-local-template-metafunction-introduce + ?? + ?@ + (for-syntax template-metafunction?)) + +#| +To do: +- improve error messages +|# + +#| +A Template (T) is one of: + - pvar + - const (including () and non-pvar identifiers) + - (metafunction . T) + - (H . T) + - (H ... . T), (H ... ... . T), etc + - (?? T T) + - #(T*) + - #s(prefab-struct-key T*) + * (unquote expr) + +A HeadTemplate (H) is one of: + - T + - (?? H) + - (?? H H) + - (?@ . T) + * (unquote-splicing expr) +|# + +(begin-for-syntax + (define (do-template ctx tstx quasi? loc-id) + (with-disappeared-uses + (parameterize ((current-syntax-context ctx) + (quasi (and quasi? (box null)))) + (let*-values ([(guide deps props-guide) (parse-template tstx loc-id)] + [(vars) + (for/list ([dep (in-vector deps)]) + (cond [(pvar? dep) (pvar-var dep)] + [(template-metafunction? dep) + (template-metafunction-var dep)] + [else + (error 'template + "internal error: bad environment entry: ~e" + dep)]))]) + (with-syntax ([t tstx]) + (syntax-arm + (cond [(equal? guide '1) + ;; was (template pvar), implies props-guide = '_ + (car vars)] + [(and (equal? guide '_) (equal? props-guide '_)) + #'(quote-syntax t)] + [else + (with-syntax ([guide guide] + [props-guide props-guide] + [vars-vector + (if (pair? vars) + #`(vector . #,vars) + #''#())] + [((un-var . un-form) ...) + (if quasi? (reverse (unbox (quasi))) null)]) + #'(let ([un-var (handle-unsyntax un-form)] ...) + (substitute (quote-syntax t) + 'props-guide + 'guide + vars-vector)))])))))))) + +(define-syntax (template stx) + (syntax-case stx () + [(template t) + (do-template stx #'t #f #f)] + [(template t #:properties (prop ...)) + (andmap identifier? (syntax->list #'(prop ...))) + (parameterize ((props-to-serialize (syntax->datum #'(prop ...))) + (props-to-transfer (syntax->datum #'(prop ...)))) + (do-template stx #'t #f #f))])) + +(define-syntax (quasitemplate stx) + (syntax-case stx () + [(quasitemplate t) + (do-template stx #'t #t #f)] + [(quasitemplate t #:properties (prop ...)) + (andmap identifier? (syntax->list #'(prop ...))) + (parameterize ((props-to-serialize (syntax->datum #'(prop ...))) + (props-to-transfer (syntax->datum #'(prop ...)))) + ;; Same as above + (do-template stx #'t #t #f))])) + +(define-syntaxes (template/loc quasitemplate/loc) + ;; FIXME: better to replace unsyntax form, shrink template syntax constant + (let ([make-tx + (lambda (quasi?) + (lambda (stx) + (syntax-case stx () + [(?/loc loc-expr t) + (syntax-arm + (with-syntax ([main-expr (do-template stx #'t quasi? #'loc-stx)]) + #'(let ([loc-stx (handle-loc '?/loc loc-expr)]) + main-expr)))] + [(?/loc loc-expr t #:properties (prop ...)) + (andmap identifier? (syntax->list #'(prop ...))) + (parameterize ((props-to-serialize (syntax->datum #'(prop ...))) + (props-to-transfer (syntax->datum #'(prop ...)))) + ;; Same as above + (syntax-arm + (with-syntax ([main-expr (do-template stx #'t quasi? #'loc-stx)]) + #'(let ([loc-stx (handle-loc '?/loc loc-expr)]) + main-expr))))])))]) + (values (make-tx #f) (make-tx #t)))) + +(define (handle-loc who x) + (if (syntax? x) + x + (raise-argument-error who "syntax?" x))) + +;; FIXME: what lexical context should result of expr get if not syntax? +(define-syntax handle-unsyntax + (syntax-rules (unsyntax unsyntax-splicing) + [(handle-syntax (unsyntax expr)) expr] + [(handle-syntax (unsyntax-splicing expr)) expr])) + +;; substitute-table : hash[stx => translated-template] +;; Cache for closure-compiled templates. Key is just syntax of +;; template, since eq? templates must have equal? guides. +(define substitute-table (make-weak-hasheq)) + +;; props-syntax-table : hash[stx => stx] +(define props-syntax-table (make-weak-hasheq)) + +(define (substitute stx props-guide g main-env) + (let* ([stx (if (eq? props-guide '_) + stx + (or (hash-ref props-syntax-table stx #f) + (let* ([pf (translate stx props-guide 0)] + [pstx (pf '#() #f)]) + (hash-set! props-syntax-table stx pstx) + pstx)))] + [f (or (hash-ref substitute-table stx #f) + (let ([f (translate stx g (vector-length main-env))]) + (hash-set! substitute-table stx f) + f))]) + (f main-env #f))) + +;; ---- + +(define-syntaxes (?? ?@) + (let ([tx (lambda (stx) (raise-syntax-error #f "not allowed as an expression" stx))]) + (values tx tx))) + +;; ============================================================ + +#| +See private/substitute for definition of Guide (G) and HeadGuide (HG). + +A env-entry is one of + - (pvar syntax-mapping attribute-mapping/#f depth-delta) + - template-metafunction + +The depth-delta associated with a depth>0 pattern variable is the difference +between the pattern variable's depth and the depth at which it is used. (For +depth 0 pvars, it's #f.) For example, in + + (with-syntax ([x #'0] + [(y ...) #'(1 2)] + [((z ...) ...) #'((a b) (c d))]) + (template (((x y) ...) ...))) + +the depth-delta for x is #f, the depth-delta for y is 1, and the depth-delta for +z is 0. Coincidentally, the depth-delta is the same as the depth of the ellipsis +form at which the variable should be moved to the loop-env. That is, the +template above should be interpreted as roughly similar to + + (let ([x (pvar-value-of x)] + [y (pvar-value-of y)] + [z (pvar-value-of z)]) + (for ([Lz (in-list z)]) ;; depth 0 + (for ([Ly (in-list y)] ;; depth 1 + [Lz (in-list Lz)]) + (___ x Ly Lz ___)))) + +A Pre-Guide is like a Guide but with env-entry and (setof env-entry) +instead of integers and integer vectors. +|# + +(begin-for-syntax + (struct pvar (sm attr dd) #:prefab)) + +;; ============================================================ + + +;; TODO: once PR https://github.com/racket/racket/pull/1591 is merged, use +;; the exported prop:template-metafunction, template-metafunction? and +;; template-metafunction-accessor. +(define-syntax (define-template-metafunction stx) + (syntax-case stx () + [(dsm (id arg ...) . body) + #'(dsm id (lambda (arg ...) . body))] + [(dsm id expr) + (identifier? #'id) + (with-syntax ([(internal-id) (generate-temporaries #'(id))]) + #'(begin (define internal-id expr) + (define-syntax id + (template-metafunction (quote-syntax internal-id)))))])) + +(begin-for-syntax + (struct template-metafunction (var))) + +;; ============================================================ + +(begin-for-syntax + + ;; props-to-serialize determines what properties are saved even when + ;; code is compiled. (Unwritable values are dropped.) + ;; props-to-transfer determines what properties are transferred from + ;; template to stx constructed. + ;; If a property is in props-to-transfer but not props-to-serialize, + ;; compiling the module may have caused the property to disappear. + ;; If a property is in props-to-serialize but not props-to-transfer, + ;; it will show up only in constant subtrees. + ;; The behavior of 'syntax' is serialize '(), transfer '(paren-shape). + + ;; props-to-serialize : (parameterof (listof symbol)) + (define props-to-serialize (make-parameter '())) + + ;; props-to-transfer : (parameterof (listof symbol)) + (define props-to-transfer (make-parameter '(paren-shape))) + + ;; quasi : (parameterof (or/c #f (list^n (boxof QuasiPairs)))) + ;; each list wrapper represents nested quasi wrapping + ;; QuasiPairs = (listof (cons/c identifier syntax)) + (define quasi (make-parameter #f)) + + ;; parse-template : stx id/#f -> (values guide (vectorof env-entry) guide) + (define (parse-template t loc-id) + (let*-values ([(drivers pre-guide props-guide) (parse-t t 0 #f)] + [(drivers pre-guide) + (if loc-id + (let* ([loc-sm (make-auto-pvar 0 loc-id)] + [loc-pvar (pvar loc-sm #f #f)]) + (values (dset-add drivers loc-pvar) + (relocate-guide pre-guide loc-pvar))) + (values drivers pre-guide))]) + (let* ([main-env (dset->env drivers (hash))] + [guide (guide-resolve-env pre-guide main-env)]) + (values guide + (index-hash->vector main-env) + props-guide)))) + + ;; dset->env : (dsetof env-entry) -> hash[env-entry => nat] + (define (dset->env drivers init-env) + (for/fold ([env init-env]) + ([pvar (in-list (dset->list drivers))] + [n (in-naturals (+ 1 (hash-count init-env)))]) + (hash-set env pvar n))) + + ;; guide-resolve-env : pre-guide hash[env-entry => nat] -> guide + (define (guide-resolve-env g0 main-env) + (define (loop g loop-env) + (define (get-index x) + (let ([loop-index (hash-ref loop-env x #f)]) + (if loop-index + (- loop-index) + (hash-ref main-env x)))) + (match g + ['_ '_] + [(cons g1 g2) + (cons (loop g1 loop-env) (loop g2 loop-env))] + [(? pvar? pvar) + (if (pvar-check? pvar) + (vector 'check (get-index pvar)) + (get-index pvar))] + [(vector 'dots head new-hdrivers/level nesting '#f tail) + (let-values ([(sub-loop-env r-uptos) + (for/fold ([env (hash)] [r-uptos null]) + ([new-hdrivers (in-list new-hdrivers/level)]) + (let ([new-env (dset->env new-hdrivers env)]) + (values new-env (cons (hash-count new-env) r-uptos))))]) + (let ([sub-loop-vector (index-hash->vector sub-loop-env get-index)]) + (vector 'dots + (loop head sub-loop-env) + sub-loop-vector + nesting + (reverse r-uptos) + (loop tail loop-env))))] + [(vector 'app head tail) + (vector 'app (loop head loop-env) (loop tail loop-env))] + [(vector 'escaped g1) + (vector 'escaped (loop g1 loop-env))] + [(vector 'orelse g1 g2) + (vector 'orelse (loop g1 loop-env) (loop g2 loop-env))] + [(vector 'orelse-h g1 g2) + (vector 'orelse-h (loop g1 loop-env) (loop g2 loop-env))] + [(vector 'metafun mf g1) + (vector 'metafun + (get-index mf) + (loop g1 loop-env))] + [(vector 'vector g1) + (vector 'vector (loop g1 loop-env))] + [(vector 'struct g1) + (vector 'struct (loop g1 loop-env))] + [(vector 'box g1) + (vector 'box (loop (unbox g) loop-env))] + [(vector 'copy-props g1 keys) + (vector 'copy-props (loop g1 loop-env) keys)] + [(vector 'set-props g1 props-alist) + (vector 'set-props (loop g1 loop-env) props-alist)] + [(vector 'app-opt g1) + (vector 'app-opt (loop g1 loop-env))] + [(vector 'splice g1) + (vector 'splice (loop g1 loop-env))] + [(vector 'unsyntax var) + (vector 'unsyntax (get-index var))] + [(vector 'unsyntax-splicing var) + (vector 'unsyntax-splicing (get-index var))] + [(vector 'relocate g1 var) + (vector 'relocate (loop g1 loop-env) (get-index var))] + [else (error 'template "internal error: bad pre-guide: ~e" g)])) + (loop g0 '#hash())) + + ;; ---------------------------------------- + + ;; relocate-gude : stx guide -> guide + (define (relocate-guide g0 loc-pvar) + (define (relocate g) + (vector 'relocate g loc-pvar)) + (define (error/no-relocate) + (wrong-syntax #f "cannot apply syntax location to template")) + (define (loop g) + (match g + ['_ + (relocate g)] + [(cons g1 g2) + (relocate g)] + [(? pvar? g) + g] + [(vector 'dots head new-hdrivers/level nesting '#f tail) + ;; Ideally, should error. For perfect backwards compatability, + ;; should relocate. But if there are zero iterations, that + ;; means we'd relocate tail (which might be bad). Making + ;; relocation depend on number of iterations would be + ;; complicated. So just ignore. + g] + [(vector 'escaped g1) + (vector 'escaped (loop g1))] + [(vector 'vector g1) + (relocate g)] + [(vector 'struct g1) + (relocate g)] + [(vector 'box g1) + (relocate g)] + [(vector 'copy-props g1 keys) + (vector 'copy-props (loop g1) keys)] + [(vector 'unsyntax var) + g] + ;; ---- + [(vector 'app ghead gtail) + (match ghead + [(vector 'unsyntax-splicing _) g] + [_ (error/no-relocate)])] + ;; ---- + [(vector 'orelse g1 g2) + (error/no-relocate)] + [(vector 'orelse-h g1 g2) + (error/no-relocate)] + [(vector 'metafun mf g1) + (error/no-relocate)] + [(vector 'app-opt g1) + (error/no-relocate)] + [(vector 'splice g1) + (error/no-relocate)] + [(vector 'unsyntax-splicing var) + g] + [else (error 'template "internal error: bad guide for relocation: ~e" g0)])) + (loop g0)) + + ;; ---------------------------------------- + + (define (wrap-props stx env-set pre-guide props-guide) + (let ([saved-prop-values + (if (syntax? stx) + (for/fold ([entries null]) ([prop (in-list (props-to-serialize))]) + (let ([v (syntax-property stx prop)]) + (if (and v (quotable? v)) + (cons (cons prop v) entries) + entries))) + null)] + [copy-props + (if (syntax? stx) + (for/list ([prop (in-list (props-to-transfer))] + #:when (syntax-property stx prop)) + prop) + null)]) + (values env-set + (cond [(eq? pre-guide '_) + ;; No need to copy props; already on constant + '_] + [(pair? copy-props) + (vector 'copy-props pre-guide copy-props)] + [else pre-guide]) + (if (pair? saved-prop-values) + (vector 'set-props props-guide saved-prop-values) + props-guide)))) + + (define (quotable? v) + (or (null? v) + (string? v) + (bytes? v) + (number? v) + (boolean? v) + (char? v) + (keyword? v) + (regexp? v) + (byte-regexp? v) + (and (box? v) (quotable? (unbox v))) + (and (symbol? v) (symbol-interned? v)) + (and (pair? v) (quotable? (car v)) (quotable? (cdr v))) + (and (vector? v) (andmap quotable? (vector->list v))) + (and (hash? v) (andmap quotable? (hash->list v))) + (and (prefab-struct-key v) (andmap quotable? (struct->list v))))) + + (define (cons-guide g1 g2) + (if (and (eq? g1 '_) (eq? g2 '_)) '_ (cons g1 g2))) + + (define (list-guide . gs) + (foldr cons-guide '_ gs)) + + ;; parse-t : stx nat boolean -> (values (dsetof env-entry) pre-guide props-guide) + (define (parse-t t depth esc?) + (syntax-case t (?? ?@ unsyntax quasitemplate) + [id + (identifier? #'id) + (cond [(or (and (not esc?) + (or (free-identifier=? #'id (quote-syntax ...)) + (free-identifier=? #'id (quote-syntax ??)) + (free-identifier=? #'id (quote-syntax ?@)))) + (and (quasi) + (or (free-identifier=? #'id (quote-syntax unsyntax)) + (free-identifier=? #'id (quote-syntax unsyntax-splicing))))) + (wrong-syntax #'id "illegal use")] + [else + (let ([pvar (lookup #'id depth)]) + (cond [(pvar? pvar) + (values (dset pvar) pvar '_)] + [(template-metafunction? pvar) + (wrong-syntax t "illegal use of syntax metafunction")] + [else + (wrap-props #'id (dset) '_ '_)]))])] + [(mf . template) + (and (not esc?) + (identifier? #'mf) + (template-metafunction? (lookup #'mf #f))) + (let-values ([(mf) (lookup #'mf #f)] + [(drivers guide props-guide) (parse-t #'template depth esc?)]) + (values (dset-add drivers mf) + (vector 'metafun mf guide) + (cons-guide '_ props-guide)))] + [(unsyntax t1) + (quasi) + (let ([qval (quasi)]) + (cond [(box? qval) + (with-syntax ([(tmp) (generate-temporaries #'(unsyntax-expr))]) + (set-box! qval (cons (cons #'tmp t) (unbox qval))) + (let* ([fake-sm (make-auto-pvar 0 #'tmp)] + [fake-pvar (pvar fake-sm #f #f)]) + (values (dset fake-pvar) (vector 'unsyntax fake-pvar) '_)))] + [else + (parameterize ((quasi (car qval))) + (let-values ([(drivers guide props-guide) (parse-t #'t1 depth esc?)]) + (wrap-props t + drivers + (list-guide '_ guide) + (list-guide '_ props-guide))))]))] + [(quasitemplate t1) + ;; quasitemplate escapes inner unsyntaxes + (quasi) + (parameterize ((quasi (list (quasi)))) + (let-values ([(drivers guide props-guide) (parse-t #'t1 depth esc?)]) + (wrap-props t + drivers + (list-guide '_ guide) + (list-guide '_ props-guide))))] + [(DOTS template) + (and (not esc?) + (identifier? #'DOTS) (free-identifier=? #'DOTS (quote-syntax ...))) + (let-values ([(drivers guide props-guide) (parse-t #'template depth #t)]) + (values drivers (vector 'escaped guide) + (list-guide '_ props-guide)))] + [(?? t1 t2) + (not esc?) + (let-values ([(drivers1 guide1 props-guide1) (parse-t #'t1 depth esc?)] + [(drivers2 guide2 props-guide2) (parse-t #'t2 depth esc?)]) + (values (dset-union drivers1 drivers2) + (vector 'orelse guide1 guide2) + (list-guide '_ props-guide1 props-guide2)))] + [(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 ([(hdrivers _hsplice? hguide hprops-guide) + (parse-h #'head (+ depth nesting) esc?)] + [(tdrivers tguide tprops-guide) + (parse-t tail depth esc?)]) + (when (dset-empty? hdrivers) + (wrong-syntax #'head "no pattern variables before ellipsis in template")) + (when (dset-empty? (dset-filter hdrivers (pvar/dd<=? depth))) + ;; FIXME: improve error message? + (let ([bad-dots + ;; select the nestingth (last) ellipsis as the bad one + (stx-car (stx-drop nesting t))]) + (wrong-syntax bad-dots "too many ellipses in template"))) + (wrap-props t + (dset-union hdrivers tdrivers) + ;; pre-guide hdrivers is (listof (setof pvar)) + ;; set of pvars new to each level + (let* ([hdrivers/level + (for/list ([i (in-range nesting)]) + (dset-filter hdrivers (pvar/dd<=? (+ depth i))))] + [new-hdrivers/level + (let loop ([raw hdrivers/level] [last (dset)]) + (cond [(null? raw) null] + [else + (cons (dset-subtract (car raw) last) + (loop (cdr raw) (car raw)))]))]) + (vector 'dots hguide new-hdrivers/level nesting #f tguide)) + (cons-guide hprops-guide (cons-guide '_ tprops-guide)))))] + [(head . tail) + (let-values ([(hdrivers hsplice? hguide hprops-guide) + (parse-h #'head depth esc?)] + [(tdrivers tguide tprops-guide) + (parse-t #'tail depth esc?)]) + (wrap-props t + (dset-union hdrivers tdrivers) + (cond [(and (eq? hguide '_) (eq? tguide '_)) '_] + [hsplice? (vector 'app hguide tguide)] + [else (cons hguide tguide)]) + (cons-guide hprops-guide tprops-guide)))] + [vec + (vector? (syntax-e #'vec)) + (let-values ([(drivers guide props-guide) + (parse-t (vector->list (syntax-e #'vec)) depth esc?)]) + (wrap-props t drivers + (if (eq? guide '_) '_ (vector 'vector guide)) + (if (eq? props-guide '_) '_ (vector 'vector props-guide))))] + [pstruct + (prefab-struct-key (syntax-e #'pstruct)) + (let-values ([(drivers guide props-guide) + (parse-t (cdr (vector->list (struct->vector (syntax-e #'pstruct)))) depth esc?)]) + (wrap-props t drivers + (if (eq? guide '_) '_ (vector 'struct guide)) + (if (eq? props-guide '_) '_ (vector 'struct props-guide))))] + [#&template + (let-values ([(drivers guide props-guide) + (parse-t #'template depth esc?)]) + (wrap-props t drivers + (if (eq? guide '_) '_ (vector 'box guide)) + (if (eq? props-guide '_) '_ (vector 'box props-guide))))] + [const + (wrap-props t (dset) '_ '_)])) + + ;; parse-h : stx nat boolean -> (values (dsetof env-entry) boolean pre-head-guide props-guide) + (define (parse-h h depth esc?) + (syntax-case h (?? ?@ unsyntax-splicing) + [(?? t) + (not esc?) + (let-values ([(drivers splice? guide props-guide) + (parse-h #'t depth esc?)]) + (values drivers #t + (vector 'app-opt guide) + (list-guide '_ props-guide)))] + [(?? t1 t2) + (not esc?) + (let-values ([(drivers1 splice?1 guide1 props-guide1) (parse-h #'t1 depth esc?)] + [(drivers2 splice?2 guide2 props-guide2) (parse-h #'t2 depth esc?)]) + (values (dset-union drivers1 drivers2) + (or splice?1 splice?2) + (vector (if (or splice?1 splice?2) 'orelse-h 'orelse) + guide1 guide2) + (list-guide '_ props-guide1 props-guide2)))] + [(?@ . t) + (not esc?) + (let-values ([(drivers guide props-guide) (parse-t #'t depth esc?)]) + (values drivers #t (vector 'splice guide) (cons-guide '_ props-guide)))] + [(unsyntax-splicing t1) + (quasi) + (let ([qval (quasi)]) + (cond [(box? qval) + (with-syntax ([(tmp) (generate-temporaries #'(unsyntax-splicing-expr))]) + (set-box! qval (cons (cons #'tmp h) (unbox qval))) + (let* ([fake-sm (make-auto-pvar 0 #'tmp)] + [fake-pvar (pvar fake-sm #f #f)]) + (values (dset fake-pvar) #t (vector 'unsyntax-splicing fake-pvar) '_)))] + [else + (parameterize ((quasi (car qval))) + (let*-values ([(drivers guide props-guide) (parse-t #'t1 depth esc?)] + [(drivers guide props-guide) + (wrap-props h + drivers + (list-guide '_ guide) + (list-guide '_ props-guide))]) + (values drivers #f guide props-guide)))]))] + [t + (let-values ([(drivers guide props-guide) (parse-t #'t depth esc?)]) + (values drivers #f guide props-guide))])) + + (define (lookup id depth) + (let ([v (syntax-local-value/record id (lambda (v) (or (syntax-pattern-variable? v) + (template-metafunction? v))))]) + (cond [(syntax-pattern-variable? v) + (let* ([pvar-depth (syntax-mapping-depth v)] + [attr (syntax-local-value (syntax-mapping-valvar v) (lambda () #f))] + [attr (and (attribute-mapping? attr) attr)]) + (cond [(not depth) ;; not looking for pvars, only for metafuns + #f] + [(zero? pvar-depth) + (pvar v attr #f)] + [(>= depth pvar-depth) + (pvar v attr (- depth pvar-depth))] + [else + (wrong-syntax id "missing ellipses with pattern variable in template")]))] + [(template-metafunction? v) + v] + [else + ;; id is a literal; check that for all x s.t. id = x.y, x is not an attribute + (for ([pfx (in-list (dotted-prefixes id))]) + (let ([pfx-v (syntax-local-value pfx (lambda () #f))]) + (when (and (syntax-pattern-variable? pfx-v) + (let ([valvar (syntax-mapping-valvar pfx-v)]) + (attribute-mapping? (syntax-local-value valvar (lambda () #f))))) + (wrong-syntax id "undefined nested attribute of attribute `~a'" (syntax-e pfx))))) + #f]))) + + (define (dotted-prefixes id) + (let* ([id-string (symbol->string (syntax-e id))] + [dot-locations (map car (regexp-match-positions* #rx"\\.[^.]" id-string))]) + (for/list ([loc (in-list dot-locations)]) + (datum->syntax id (string->symbol (substring id-string 0 loc)))))) + + (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 ((pvar/dd<=? expected-dd) x) + (match x + [(pvar sm attr dd) (and dd (<= dd expected-dd))] + [_ #f])) + + (define (pvar-var x) + (match x + [(pvar sm '#f dd) (syntax-mapping-valvar sm)] + [(pvar sm attr dd) (attribute-mapping-var attr)])) + + (define (pvar-check? x) + (match x + [(pvar sm '#f dd) #f] + [(pvar sm attr dd) (not (attribute-mapping-syntax? attr))])) + + (define (stx-drop n x) + (cond [(zero? n) x] + [else (stx-drop (sub1 n) (stx-cdr x))])) + ) diff --git a/parse/pre.rkt-6-11 b/parse/pre.rkt-6-11 new file mode 100644 index 0000000..b9f801e --- /dev/null +++ b/parse/pre.rkt-6-11 @@ -0,0 +1,10 @@ +#lang racket/base +(require "private/sc.rkt" + "private/litconv.rkt" + "private/lib.rkt") +(provide (except-out (all-from-out "private/sc.rkt") + define-integrable-syntax-class + syntax-parser/template + parser/rhs) + (all-from-out "private/litconv.rkt") + (all-from-out "private/lib.rkt")) diff --git a/parse/private/lib.rkt-6-11 b/parse/private/lib.rkt-6-11 new file mode 100644 index 0000000..647e201 --- /dev/null +++ b/parse/private/lib.rkt-6-11 @@ -0,0 +1,75 @@ +#lang racket/base +(require "sc.rkt" + syntax/parse/private/keywords + (for-syntax racket/base)) + +(provide identifier + boolean + str + character + keyword + number + integer + exact-integer + exact-nonnegative-integer + exact-positive-integer + + id + nat + char + + expr + static) + + +(define (expr-stx? x) + (not (keyword-stx? x))) + +(define ((stxof pred?) x) (and (syntax? x) (pred? (syntax-e x)))) +(define keyword-stx? (stxof keyword?)) +(define boolean-stx? (stxof boolean?)) +(define string-stx? (stxof string?)) +(define char-stx? (stxof char?)) +(define number-stx? (stxof number?)) +(define integer-stx? (stxof integer?)) +(define exact-integer-stx? (stxof exact-integer?)) +(define exact-nonnegative-integer-stx? (stxof exact-nonnegative-integer?)) +(define exact-positive-integer-stx? (stxof exact-positive-integer?)) + +;; == Integrable syntax classes == + +(define-integrable-syntax-class identifier (quote "identifier") identifier?) +(define-integrable-syntax-class expr (quote "expression") expr-stx?) +(define-integrable-syntax-class keyword (quote "keyword") keyword-stx?) +(define-integrable-syntax-class boolean (quote "boolean") boolean-stx?) +(define-integrable-syntax-class character (quote "character") char-stx?) +(define-integrable-syntax-class str (quote "string") string-stx?) +(define-integrable-syntax-class number (quote "number") number-stx?) +(define-integrable-syntax-class integer (quote "integer") integer-stx?) +(define-integrable-syntax-class exact-integer (quote "exact-integer") exact-integer-stx?) +(define-integrable-syntax-class exact-nonnegative-integer + (quote "exact-nonnegative-integer") + exact-nonnegative-integer-stx?) +(define-integrable-syntax-class exact-positive-integer + (quote "exact-positive-integer") + exact-positive-integer-stx?) + +;; Aliases +(define-syntax id (make-rename-transformer #'identifier)) +(define-syntax nat (make-rename-transformer #'exact-nonnegative-integer)) +(define-syntax char (make-rename-transformer #'character)) + +;; == Normal syntax classes == + +(define notfound (box 'notfound)) + +(define-syntax-class (static pred [name #f]) + #:attributes (value) + #:description name + #:commit + (pattern x:id + #:fail-unless (syntax-transforming?) + "not within the dynamic extent of a macro transformation" + #:attr value (syntax-local-value #'x (lambda () notfound)) + #:fail-when (eq? (attribute value) notfound) #f + #:fail-unless (pred (attribute value)) #f)) diff --git a/parse/private/parse-aux.rkt-6-11 b/parse/private/parse-aux.rkt-6-11 new file mode 100644 index 0000000..2c0b2d5 --- /dev/null +++ b/parse/private/parse-aux.rkt-6-11 @@ -0,0 +1,21 @@ +#lang racket/base +(require (for-template "parse.rkt")) +(provide id:define-syntax-class + id:define-splicing-syntax-class + id:define-integrable-syntax-class + id:syntax-parse + id:syntax-parser + id:define/syntax-parse + id:syntax-parser/template + id:parser/rhs + id:define-eh-alternative-set) + +(define (id:define-syntax-class) #'define-syntax-class) +(define (id:define-splicing-syntax-class) #'define-splicing-syntax-class) +(define (id:define-integrable-syntax-class) #'define-integrable-syntax-class) +(define (id:syntax-parse) #'syntax-parse) +(define (id:syntax-parser) #'syntax-parser) +(define (id:define/syntax-parse) #'define/syntax-parse) +(define (id:syntax-parser/template) #'syntax-parser/template) +(define (id:parser/rhs) #'parser/rhs) +(define (id:define-eh-alternative-set) #'define-eh-alternative-set) diff --git a/parse/private/parse.rkt-6-11 b/parse/private/parse.rkt-6-11 new file mode 100644 index 0000000..266d2bb --- /dev/null +++ b/parse/private/parse.rkt-6-11 @@ -0,0 +1,1199 @@ +#lang racket/base +(require (for-syntax racket/base + syntax/stx + syntax/private/id-table + syntax/keyword + racket/syntax + syntax/parse/private/minimatch + syntax/parse/private/rep-attrs + syntax/parse/private/rep-data + syntax/parse/private/rep-patterns + "rep.rkt" + syntax/parse/private/kws + "opt.rkt" + "txlift.rkt") + syntax/parse/private/keywords + racket/syntax + racket/stxparam + syntax/stx + stxparse-info/parse/private/residual ;; keep abs. path + stxparse-info/parse/private/runtime ;; keep abs.path + stxparse-info/parse/private/runtime-reflect) ;; keep abs. path + +;; ============================================================ + +(provide define-syntax-class + define-splicing-syntax-class + define-integrable-syntax-class + syntax-parse + syntax-parser + define/syntax-parse + syntax-parser/template + parser/rhs + define-eh-alternative-set + (for-syntax rhs->parser)) + +(begin-for-syntax + ;; constant-desc : Syntax -> String/#f + (define (constant-desc stx) + (syntax-case stx (quote) + [(quote datum) + (let ([d (syntax-e #'datum)]) + (and (string? d) d))] + [expr + (let ([d (syntax-e #'expr)]) + (and (string? d) + (free-identifier=? #'#%datum (datum->syntax #'expr '#%datum)) + d))])) + + (define (tx:define-*-syntax-class stx splicing?) + (syntax-case stx () + [(_ header . rhss) + (parameterize ((current-syntax-context stx)) + (let-values ([(name formals arity) + (let ([p (check-stxclass-header #'header stx)]) + (values (car p) (cadr p) (caddr p)))]) + (let ([the-rhs (parse-rhs #'rhss #f splicing? #:context stx)]) + (with-syntax ([name name] + [formals formals] + [desc (cond [(rhs-description the-rhs) => constant-desc] + [else (symbol->string (syntax-e name))])] + [parser (generate-temporary (format-symbol "parse-~a" name))] + [arity arity] + [attrs (rhs-attrs the-rhs)] + [commit? (rhs-commit? the-rhs)] + [delimit-cut? (rhs-delimit-cut? the-rhs)]) + #`(begin (define-syntax name + (stxclass 'name 'arity + 'attrs + (quote-syntax parser) + '#,splicing? + (scopts (length 'attrs) 'commit? 'delimit-cut? desc) + #f)) + (define-values (parser) + (parser/rhs name formals attrs rhss #,splicing? #,stx)))))))]))) + +(define-syntax define-syntax-class + (lambda (stx) (tx:define-*-syntax-class stx #f))) +(define-syntax define-splicing-syntax-class + (lambda (stx) (tx:define-*-syntax-class stx #t))) + +(define-syntax (define-integrable-syntax-class stx) + (syntax-case stx (quote) + [(_ name (quote description) predicate) + (with-syntax ([parser (generate-temporary (format-symbol "parse-~a" (syntax-e #'name)))] + [no-arity no-arity]) + #'(begin (define-syntax name + (stxclass 'name no-arity '() + (quote-syntax parser) + #f + (scopts 0 #t #t 'description) + (quote-syntax predicate))) + (define (parser x cx pr es fh0 cp0 rl success) + (if (predicate x) + (success fh0) + (let ([es (es-add-thing pr 'description #t rl es)]) + (fh0 (failure* pr es)))))))])) + +(define-syntax (parser/rhs stx) + (syntax-case stx () + [(parser/rhs name formals relsattrs rhss splicing? ctx) + (with-disappeared-uses + (let () + (define the-rhs + (parameterize ((current-syntax-context #'ctx)) + (parse-rhs #'rhss (syntax->datum #'relsattrs) (syntax-e #'splicing?) + #:context #'ctx))) + (rhs->parser #'name #'formals #'relsattrs the-rhs (syntax-e #'splicing?) #'ctx)))])) + +(begin-for-syntax + (define (rhs->parser name formals relsattrs the-rhs splicing? [ctx #f]) + (define-values (transparent? description variants defs commit? delimit-cut?) + (match the-rhs + [(rhs _ transparent? description variants defs commit? delimit-cut?) + (values transparent? description variants defs commit? delimit-cut?)])) + (define vdefss (map variant-definitions variants)) + (define formals* (rewrite-formals formals #'x #'rl)) + (define patterns (map variant-pattern variants)) + (define no-fail? + (and (not splicing?) ;; FIXME: commit? needed? + (patterns-cannot-fail? patterns))) + (when no-fail? (log-syntax-parse-debug "(stxclass) cannot fail: ~e" ctx)) + (define body + (cond [(null? patterns) + #'(fail (failure* pr es))] + [splicing? + (with-syntax ([(alternative ...) + (for/list ([pattern (in-list patterns)]) + (with-syntax ([pattern pattern] + [relsattrs relsattrs] + [iattrs (pattern-attrs pattern)] + [commit? commit?] + [result-pr + (if transparent? + #'rest-pr + #'(ps-pop-opaque rest-pr))]) + #'(parse:H x cx rest-x rest-cx rest-pr pattern pr es + (variant-success relsattrs iattrs (rest-x rest-cx result-pr) + success cp0 commit?))))]) + #'(try alternative ...))] + [else + (with-syntax ([matrix + (optimize-matrix + (for/list ([pattern (in-list patterns)]) + (with-syntax ([iattrs (pattern-attrs pattern)] + [relsattrs relsattrs] + [commit? commit?]) + (pk1 (list pattern) + #'(variant-success relsattrs iattrs () + success cp0 commit?)))))]) + #'(parse:matrix ((x cx pr es)) matrix))])) + (with-syntax ([formals* formals*] + [(def ...) defs] + [((vdef ...) ...) vdefss] + [description (or description (symbol->string (syntax-e name)))] + [transparent? transparent?] + [delimit-cut? delimit-cut?] + [body body]) + #`(lambda (x cx pr es fh0 cp0 rl success . formals*) + (with ([this-syntax x] + [this-role rl]) + def ... + vdef ... ... + (#%expression + (syntax-parameterize ((this-context-syntax + (syntax-rules () + [(tbs) (ps-context-syntax pr)]))) + (let ([es (es-add-thing pr description 'transparent? rl + #,(if no-fail? #'#f #'es))] + [pr (if 'transparent? pr (ps-add-opaque pr))]) + (with ([fail-handler fh0] + [cut-prompt cp0]) + ;; Update the prompt, if required + ;; FIXME: can be optimized away if no cut exposed within variants + (with-maybe-delimit-cut delimit-cut? + body)))))))))) + +(define-syntax (syntax-parse stx) + (syntax-case stx () + [(syntax-parse stx-expr . clauses) + (quasisyntax/loc stx + (let ([x (datum->syntax #f stx-expr)]) + (with ([this-syntax x]) + (parse:clauses x clauses body-sequence #,((make-syntax-introducer) stx)))))])) + +(define-syntax (syntax-parser stx) + (syntax-case stx () + [(syntax-parser . clauses) + (quasisyntax/loc stx + (lambda (x) + (let ([x (datum->syntax #f x)]) + (with ([this-syntax x]) + (parse:clauses x clauses body-sequence #,((make-syntax-introducer) stx))))))])) + +(define-syntax (syntax-parser/template stx) + (syntax-case stx () + [(syntax-parser/template ctx . clauses) + (quasisyntax/loc stx + (lambda (x) + (let ([x (datum->syntax #f x)]) + (with ([this-syntax x]) + (parse:clauses x clauses one-template ctx)))))])) + +(define-syntax (define/syntax-parse stx) + (syntax-case stx () + [(define/syntax-parse pattern . rest) + (with-disappeared-uses + (let-values ([(rest pattern defs) + (parse-pattern+sides #'pattern + #'rest + #:splicing? #f + #:decls (new-declenv null) + #:context stx)]) + (let ([expr + (syntax-case rest () + [( expr ) #'expr] + [_ (raise-syntax-error #f "bad syntax" stx)])] + [attrs (pattern-attrs pattern)]) + (with-syntax ([(a ...) attrs] + [(#s(attr name _ _) ...) attrs] + [pattern pattern] + [(def ...) defs] + [expr expr]) + #'(defattrs/unpack (a ...) + (let* ([x (datum->syntax #f expr)] + [cx x] + [pr (ps-empty x x)] + [es #f] + [fh0 (syntax-patterns-fail x)]) + (parameterize ((current-syntax-context x)) + def ... + (#%expression + (with ([fail-handler fh0] + [cut-prompt fh0]) + (parse:S x cx pattern pr es + (list (attribute name) ...)))))))))))])) + +;; ============================================================ + +#| +Parsing protocols: + +(parse:<X> <X-args> pr es success-expr) : Ans + + <S-args> : x cx + <H-args> : x cx rest-x rest-cx rest-pr + <EH-args> : x cx ??? + <A-args> : x cx + + x is term to parse, usually syntax but can be pair/null (stx-list?) in cdr patterns + cx is most recent syntax object: if x must be coerced to syntax, use cx as lexctx and src + pr, es are progress and expectstack, respectively + rest-x, rest-cx, rest-pr are variable names to bind in context of success-expr + +(stxclass-parser x cx pr es fail-handler cut-prompt role success-proc arg ...) : Ans + + success-proc: + for stxclass, is (fail-handler attr-value ... -> Ans) + for splicing-stxclass, is (fail-handler rest-x rest-cx rest-pr attr-value -> Ans) + fail-handler, cut-prompt : failure -> Ans + +Fail-handler is normally represented with stxparam 'fail-handler', but must be +threaded through stxclass calls (in through stxclass-parser, out through +success-proc) to support backtracking. Cut-prompt is never changed within +stxclass or within alternative, so no threading needed. + +Usually sub-patterns processed in tail position, but *can* do non-tail calls for: + - ~commit + - var of stxclass with ~commit +It is also safe to keep normal tail-call protocol and just adjust fail-handler. +There is no real benefit to specializing ~commit, since it does not involve +creating a success closure. + +Some optimizations: + - commit protocol for stxclasses (but not ~commit, no point) + - avoid continue-vs-end choice point in (EH ... . ()) by eager pair check + - integrable stxclasses, specialize ellipses of integrable stxclasses + - pattern lists that cannot fail set es=#f to disable ExpectStack allocation +|# + +;; ---- + +(begin-for-syntax + (define (wash stx) + (syntax-e stx)) + (define (wash-list washer stx) + (let ([l (stx->list stx)]) + (unless l (raise-type-error 'wash-list "stx-list" stx)) + (map washer l))) + (define (wash-iattr stx) + (with-syntax ([#s(attr name depth syntax?) stx]) + (attr #'name (wash #'depth) (wash #'syntax?)))) + (define (wash-sattr stx) + (with-syntax ([#s(attr name depth syntax?) stx]) + (attr (wash #'name) (wash #'depth) (wash #'syntax?)))) + (define (wash-iattrs stx) + (wash-list wash-iattr stx)) + (define (wash-sattrs stx) + (wash-list wash-sattr stx)) + (define (generate-n-temporaries n) + (generate-temporaries + (for/list ([i (in-range n)]) + (string->symbol (format "g~sx" i)))))) + +;; ---- + +#| +Conventions: + - rhs : RHS + - iattr : IAttr + - relsattr : SAttr + - splicing? : bool + - x : id (var) + - cx : id (var, may be shadowed) + - pr : id (var, may be shadowed) + - es : id (var, may be shadowed) + - success : var (bound to success procedure) + - k : expr + - rest-x, rest-cx, rest-pr : id (to be bound) + - fh, cp, rl : id (var) +|# + +(begin-for-syntax + (define (rewrite-formals fstx x-id rl-id) + (with-syntax ([x x-id] + [rl rl-id]) + (let loop ([fstx fstx]) + (syntax-case fstx () + [([kw arg default] . more) + (keyword? (syntax-e #'kw)) + (cons #'(kw arg (with ([this-syntax x] [this-role rl]) default)) + (loop #'more))] + [([arg default] . more) + (not (keyword? (syntax-e #'kw))) + (cons #'(arg (with ([this-syntax x] [this-role rl]) default)) + (loop #'more))] + [(formal . more) + (cons #'formal (loop #'more))] + [_ fstx]))))) + +;; (with-maybe-delimit-cut bool expr) +(define-syntax with-maybe-delimit-cut + (syntax-rules () + [(wmdc #t k) + (with ([cut-prompt fail-handler]) k)] + [(wmdc #f k) + k])) + +;; (variant-success relsattrs variant (also:id ...) success bool) : expr[Ans] +(define-syntax (variant-success stx) + (syntax-case stx () + [(variant-success relsattrs iattrs (also ...) success cp0 commit?) + #`(with-maybe-reset-fail commit? cp0 + (base-success-expr iattrs relsattrs (also ...) success))])) + +;; (with-maybe-reset-fail bool id expr) +(define-syntax with-maybe-reset-fail + (syntax-rules () + [(wmrs #t cp0 k) + (with ([fail-handler cp0]) k)] + [(wmrs #f cp0 k) + k])) + +;; (base-success-expr iattrs relsattrs (also:id ...) success) : expr[Ans] +(define-syntax (base-success-expr stx) + (syntax-case stx () + [(base-success-expr iattrs relsattrs (also ...) success) + (let ([reliattrs + (reorder-iattrs (wash-sattrs #'relsattrs) + (wash-iattrs #'iattrs))]) + (with-syntax ([(#s(attr name _ _) ...) reliattrs]) + #'(success fail-handler also ... (attribute name) ...)))])) + +;; ---- + +;; (parse:clauses x clauses ctx) +(define-syntax (parse:clauses stx) + (syntax-case stx () + [(parse:clauses x clauses body-mode ctx) + ;; if templates? is true, expect one form after kwargs in clause, wrap it with syntax + ;; otherwise, expect non-empty body sequence (defs and exprs) + (with-disappeared-uses + (with-txlifts + (lambda () + (define who + (syntax-case #'ctx () + [(m . _) (identifier? #'m) #'m] + [_ 'syntax-parse])) + (define-values (chunks clauses-stx) + (parse-keyword-options #'clauses parse-directive-table + #:context #'ctx + #:no-duplicates? #t)) + (define context + (options-select-value chunks '#:context #:default #'x)) + (define colon-notation? + (not (assq '#:disable-colon-notation chunks))) + (define-values (decls0 defs) + (get-decls+defs chunks #t #:context #'ctx)) + ;; for-clause : stx -> (values pattern stx (listof stx)) + (define (for-clause clause) + (syntax-case clause () + [[p . rest] + (let-values ([(rest pattern defs2) + (parameterize ((stxclass-colon-notation? colon-notation?)) + (parse-pattern+sides #'p #'rest + #:splicing? #f + #:decls decls0 + #:context #'ctx))]) + (let ([body-expr + (case (syntax-e #'body-mode) + ((one-template) + (syntax-case rest () + [(template) + #'(syntax template)] + [_ (raise-syntax-error #f "expected exactly one template" #'ctx)])) + ((body-sequence) + (syntax-case rest () + [(e0 e ...) + ;; Should we use a shadower (works on the whole file, unhygienically), + ;; or use the context of the syntax-parse identifier? + (let ([the-#%intdef-begin (datum->syntax #'ctx '#%intdef-begin)]) + (if (syntax-local-value the-#%intdef-begin (λ () #f)) ;; Defined as a macro + #`(let () (#,the-#%intdef-begin e0 e ...)) + #'(let () e0 e ...)))] + [_ (raise-syntax-error #f "expected non-empty clause body" + #'ctx clause)])) + (else + (raise-syntax-error #f "internal error: unknown body mode" #'ctx #'body-mode)))]) + (values pattern body-expr defs2)))] + [_ (raise-syntax-error #f "expected clause" #'ctx clause)])) + (unless (stx-list? clauses-stx) + (raise-syntax-error #f "expected sequence of clauses" #'ctx)) + (define-values (patterns body-exprs defs2s) + (for/lists (patterns body-exprs defs2s) ([clause (in-list (stx->list clauses-stx))]) + (for-clause clause))) + (define no-fail? (patterns-cannot-fail? patterns)) + (when no-fail? (log-syntax-parse-debug "cannot fail: ~e" #'ctx)) + (with-syntax ([(def ...) (apply append (get-txlifts-as-definitions) defs defs2s)]) + #`(let* ([ctx0 (normalize-context '#,who #,context x)] + [pr (ps-empty x (cadr ctx0))] + [es #,(if no-fail? #'#f #'#t)] + [cx x] + [fh0 (syntax-patterns-fail ctx0)]) + def ... + (parameterize ((current-syntax-context (cadr ctx0))) + (with ([fail-handler fh0] + [cut-prompt fh0]) + #,(cond [(pair? patterns) + (with-syntax ([matrix + (optimize-matrix + (for/list ([pattern (in-list patterns)] + [body-expr (in-list body-exprs)]) + (pk1 (list pattern) body-expr)))]) + #'(parse:matrix ((x cx pr es)) matrix)) + #| + (with-syntax ([(alternative ...) + (for/list ([pattern (in-list patterns)] + [body-expr (in-list body-exprs)]) + #`(parse:S x cx #,pattern pr es #,body-expr))]) + #`(try alternative ...)) + |#] + [else + #`(fail (failure* pr es))]))))))))])) + +;; ---- + +;; (parse:matrix ((x cx pr es) ...) (PK ...)) : expr[Ans] +;; (parse:matrix (in1 ... inN) (#s(pk1 (P11 ... P1N) e1) ... #s(pk1 (PM1 ... PMN) eM))) +;; represents the matching matrix +;; [_in1_..._inN_|____] +;; [ P11 ... P1N | e1 ] +;; [ ⋮ ⋮ | ⋮ ] +;; [ PM1 ... PMN | eM ] + +(define-syntax (parse:matrix stx) + (syntax-case stx () + [(parse:matrix ins (pk ...)) + #'(try (parse:pk ins pk) ...)])) + +(define-syntax (parse:pk stx) + (syntax-case stx () + [(parse:pk () #s(pk1 () k)) + #'k] + [(parse:pk ((x cx pr es) . ins) #s(pk1 (pat1 . pats) k)) + #'(parse:S x cx pat1 pr es (parse:pk ins #s(pk1 pats k)))] + [(parse:pk ((x cx pr es) . ins) #s(pk/same pat1 inner)) + #'(parse:S x cx pat1 pr es (parse:matrix ins inner))] + [(parse:pk ((x cx pr es) . ins) #s(pk/pair inner)) + #'(let-values ([(datum tcx) + (if (syntax? x) + (values (syntax-e x) x) + (values x cx))]) + (if (pair? datum) + (let ([hx (car datum)] + [hcx (car datum)] + [hpr (ps-add-car pr)] + [tx (cdr datum)] + [tpr (ps-add-cdr pr)]) + (parse:matrix ((hx hcx hpr es) (tx tcx tpr es) . ins) inner)) + (let ([es* (if (null? datum) (es-add-proper-pair (first-desc:matrix inner) es) es)]) + (fail (failure* pr es*)))))] + [(parse:pk (in1 . ins) #s(pk/and inner)) + #'(parse:matrix (in1 in1 . ins) inner)])) + +(define-syntax (first-desc:matrix stx) + (syntax-case stx () + [(fdm (#s(pk1 (pat1 . pats) k))) + #'(first-desc:S pat1)] + [(fdm (#s(pk/same pat1 pks))) + #'(first-desc:S pat1)] + [(fdm (pk ...)) ;; FIXME + #'#f])) + +;; ---- + +;; (parse:S x cx S-pattern pr es k) : expr[Ans] +;; In k: attrs(S-pattern) are bound. +(define-syntax (parse:S stx) + (syntax-case stx () + [(parse:S x cx pattern0 pr es k) + (syntax-case #'pattern0 () + [#s(internal-rest-pattern rest-x rest-cx rest-pr) + #`(let ([rest-x x] + [rest-cx cx] + [rest-pr pr]) + k)] + [#s(pat:any) + #'k] + [#s(pat:svar name) + #'(let-attributes ([#s(attr name 0 #t) (datum->syntax cx x cx)]) + k)] + [#s(pat:var/p name parser argu (nested-a ...) role + #s(scopts attr-count commit? _delimit? _desc)) + (with-syntax ([(av ...) (generate-n-temporaries (syntax-e #'attr-count))] + [(name-attr ...) + (if (identifier? #'name) + #'([#s(attr name 0 #t) (datum->syntax cx x cx)]) + #'())]) + (if (not (syntax-e #'commit?)) + ;; The normal protocol + #'(app-argu parser x cx pr es fail-handler cut-prompt role + (lambda (fh av ...) + (let-attributes (name-attr ...) + (let-attributes* ((nested-a ...) (av ...)) + (with ([fail-handler fh]) + k)))) + argu) + ;; The commit protocol + ;; (Avoids putting k in procedure) + #'(let-values ([(fs av ...) + (with ([fail-handler (lambda (fs) (values fs (let ([av #f]) av) ...))]) + (with ([cut-prompt fail-handler]) + (app-argu parser x cx pr es fail-handler cut-prompt role + (lambda (fh av ...) (values #f av ...)) + argu)))]) + (if fs + (fail fs) + (let-attributes (name-attr ...) + (let-attributes* ((nested-a ...) (av ...)) + k))))))] + [#s(pat:reflect obj argu attr-decls name (nested-a ...)) + (with-syntax ([(name-attr ...) + (if (identifier? #'name) + #'([#s(attr name 0 #t) (datum->syntax cx x cx)]) + #'())]) + (with-syntax ([arity (arguments->arity (syntax->datum #'argu))]) + #'(let ([parser (reflect-parser obj 'arity 'attr-decls #f)]) + (app-argu parser x cx pr es fail-handler cut-prompt #f + (lambda (fh . result) + (let-attributes (name-attr ...) + (let/unpack ((nested-a ...) result) + (with ([fail-handler fh]) + k)))) + argu))))] + [#s(pat:datum datum) + (with-syntax ([unwrap-x + (if (atomic-datum-stx? #'datum) + #'(if (syntax? x) (syntax-e x) x) + #'(syntax->datum (datum->syntax #f x)))]) + #`(let ([d unwrap-x]) + (if (equal? d (quote datum)) + k + (fail (failure* pr (es-add-atom 'datum es))))))] + [#s(pat:literal literal input-phase lit-phase) + #`(if (and (identifier? x) + (free-identifier=? x (quote-syntax literal) input-phase lit-phase)) + k + (fail (failure* pr (es-add-literal (quote-syntax literal) es))))] + [#s(pat:action action subpattern) + #'(parse:A x cx action pr es (parse:S x cx subpattern pr es k))] + [#s(pat:head head tail) + #`(parse:H x cx rest-x rest-cx rest-pr head pr es + (parse:S rest-x rest-cx tail rest-pr es k))] + [#s(pat:dots head tail) + #`(parse:dots x cx head tail pr es k)] + [#s(pat:and subpatterns) + (for/fold ([k #'k]) ([subpattern (in-list (reverse (syntax->list #'subpatterns)))]) + #`(parse:S x cx #,subpattern pr es #,k))] + [#s(pat:or (a ...) (subpattern ...) (subattrs ...)) + (with-syntax ([(#s(attr id _ _) ...) #'(a ...)]) + #`(let ([success + (lambda (fh id ...) + (let-attributes ([a id] ...) + (with ([fail-handler fh]) + k)))]) + (try (parse:S x cx subpattern pr es + (disjunct subattrs success () (id ...))) + ...)))] + [#s(pat:not subpattern) + #`(let* ([fh0 fail-handler] + [pr0 pr] + [es0 es] + [fail-to-succeed + (lambda (fs) k)]) + ;; ~not implicitly prompts to be safe, + ;; but ~! not allowed within ~not (unless within ~delimit-cut, etc) + ;; (statically checked!) + (with ([fail-handler fail-to-succeed] + [cut-prompt fail-to-succeed]) ;; to be safe + (parse:S x cx subpattern pr es + (fh0 (failure* pr0 es0)))))] + [#s(pat:pair head tail) + #`(let ([datum (if (syntax? x) (syntax-e x) x)] + [cx (if (syntax? x) x cx)]) ;; FIXME: shadowing cx?! + (if (pair? datum) + (let ([hx (car datum)] + [hcx (car datum)] + [hpr (ps-add-car pr)] + [tx (cdr datum)] + [tpr (ps-add-cdr pr)]) + (parse:S hx hcx head hpr es + (parse:S tx cx tail tpr es k))) + (let ([es* (if (null? datum) (es-add-proper-pair (first-desc:S head) es) es)]) + (fail (failure* pr es*)))))] + [#s(pat:vector subpattern) + #`(let ([datum (if (syntax? x) (syntax-e x) x)]) + (if (vector? datum) + (let ([datum (vector->list datum)] + [vcx (if (syntax? x) x cx)] ;; FIXME: (vector? datum) => (syntax? x) ??? + [pr* (ps-add-unvector pr)]) + (parse:S datum vcx subpattern pr* es k)) + (fail (failure* pr es))))] + [#s(pat:box subpattern) + #`(let ([datum (if (syntax? x) (syntax-e x) x)]) + (if (box? datum) + (let ([datum (unbox datum)] + [bcx (if (syntax? x) x cx)] ;; FIXME: (box? datum) => (syntax? x) ??? + [pr* (ps-add-unbox pr)]) + (parse:S datum bcx subpattern pr* es k)) + (fail (failure* pr es))))] + [#s(pat:pstruct key subpattern) + #`(let ([datum (if (syntax? x) (syntax-e x) x)]) + (if (let ([xkey (prefab-struct-key datum)]) + (and xkey (equal? xkey 'key))) + (let ([datum (cdr (vector->list (struct->vector datum)))] + [scx (if (syntax? x) x cx)] ;; FIXME: (struct? datum) => (syntax? x) ??? + [pr* (ps-add-unpstruct pr)]) + (parse:S datum scx subpattern pr* es k)) + (fail (failure* pr es))))] + [#s(pat:describe pattern description transparent? role) + #`(let ([es* (es-add-thing pr description transparent? role es)] + [pr* (if 'transparent? pr (ps-add-opaque pr))]) + (parse:S x cx pattern pr* es* k))] + [#s(pat:delimit pattern) + #`(let ([cp0 cut-prompt]) + (with ([cut-prompt fail-handler]) + (parse:S x cx pattern pr es (with ([cut-prompt cp0]) k))))] + [#s(pat:commit pattern) + #`(let ([fh0 fail-handler] + [cp0 cut-prompt]) + (with ([cut-prompt fh0]) + (parse:S x cx pattern pr es + (with ([cut-prompt cp0] + [fail-handler fh0]) + k))))] + [#s(pat:ord pattern group index) + #`(let ([pr* (ps-add pr '#s(ord group index))]) + (parse:S x cx pattern pr* es k))] + [#s(pat:post pattern) + #`(let ([pr* (ps-add-post pr)]) + (parse:S x cx pattern pr* es k))] + [#s(pat:integrated name predicate description role) + (with-syntax ([(name-attr ...) + (if (identifier? #'name) + #'([#s(attr name 0 #t) x*]) + #'())]) + #'(let ([x* (datum->syntax cx x cx)]) + (if (predicate x*) + (let-attributes (name-attr ...) k) + (let ([es* (es-add-thing pr 'description #t role es)]) + (fail (failure* pr es*))))))])])) + +;; (first-desc:S S-pattern) : expr[FirstDesc] +(define-syntax (first-desc:S stx) + (syntax-case stx () + [(fds p) + (syntax-case #'p () + [#s(pat:any) + #''(any)] + [#s(pat:svar name) + #''(any)] + [#s(pat:var/p _ _ _ _ _ #s(scopts _ _ _ desc)) + #'(quote desc)] + [#s(pat:datum d) + #''(datum d)] + [#s(pat:literal id _ip _lp) + #''(literal id)] + [#s(pat:describe _p desc _t? _role) + #`(quote #,(or (constant-desc #'desc) #'#f))] + [#s(pat:delimit pattern) + #'(first-desc:S pattern)] + [#s(pat:commit pattern) + #'(first-desc:S pattern)] + [#s(pat:ord pattern _ _) + #'(first-desc:S pattern)] + [#s(pat:post pattern) + #'(first-desc:S pattern)] + [#s(pat:integrated _name _pred description _role) + #''description] + [_ #'#f])])) + +;; (first-desc:H HeadPattern) : Expr +(define-syntax (first-desc:H stx) + (syntax-case stx () + [(fdh hpat) + (syntax-case #'hpat () + [#s(hpat:var/p _ _ _ _ _ #s(scopts _ _ _ desc)) #'(quote desc)] + [#s(hpat:seq lp) #'(first-desc:L lp)] + [#s(hpat:describe _hp desc _t? _r) + #`(quote #,(or (constant-desc #'desc) #'#f))] + [#s(hpat:delimit hp) #'(first-desc:H hp)] + [#s(hpat:commit hp) #'(first-desc:H hp)] + [#s(hpat:ord hp _ _) #'(first-desc:H hp)] + [#s(hpat:post hp) #'(first-desc:H hp)] + [_ #'(first-desc:S hpat)])])) + +(define-syntax (first-desc:L stx) + (syntax-case stx () + [(fdl lpat) + (syntax-case #'lpat () + [#s(pat:pair sp lp) #'(first-desc:S sp)] + [_ #'#f])])) + +;; (disjunct (iattr ...) success (pre:expr ...) (id:id ...)) : expr[Ans] +(define-syntax (disjunct stx) + (syntax-case stx () + [(disjunct (#s(attr sub-id _ _) ...) success (pre ...) (id ...)) + (with-syntax ([(alt-sub-id ...) (generate-temporaries #'(sub-id ...))]) + #`(let ([alt-sub-id (attribute sub-id)] ...) + (let ([id #f] ...) + (let ([sub-id alt-sub-id] ...) + (success fail-handler pre ... id ...)))))])) + +;; (parse:A x cx A-pattern pr es k) : expr[Ans] +;; In k: attrs(A-pattern) are bound. +(define-syntax (parse:A stx) + (syntax-case stx () + [(parse:A x cx pattern0 pr es k) + (syntax-case #'pattern0 () + [#s(action:and (action ...)) + (for/fold ([k #'k]) ([action (in-list (reverse (syntax->list #'(action ...))))]) + #`(parse:A x cx #,action pr es #,k))] + [#s(action:cut) + #'(with ([fail-handler cut-prompt]) k)] + [#s(action:bind a expr) + #'(let-attributes ([a (wrap-user-code expr)]) k)] + [#s(action:fail condition message) + #`(let ([c (wrap-user-code condition)]) + (if c + (let ([pr* (if (syntax? c) (ps-add-stx pr c) pr)] + [es* (es-add-message message es)]) + (fail (failure* pr* es*))) + k))] + [#s(action:parse pattern expr) + #`(let* ([y (datum->syntax/with-clause (wrap-user-code expr))] + [cy y] + [pr* (ps-add-stx pr y)]) + (parse:S y cy pattern pr* es k))] + [#s(action:do (stmt ...)) + #'(let () (no-shadow stmt) ... (#%expression k))] + [#s(action:ord pattern group index) + #'(let ([pr* (ps-add pr '#s(ord group index))]) + (parse:A x cx pattern pr* es k))] + [#s(action:post pattern) + #'(let ([pr* (ps-add-post pr)]) + (parse:A x cx pattern pr* es k))])])) + +(begin-for-syntax + ;; convert-list-pattern : ListPattern id -> SinglePattern + ;; Converts '() datum pattern at end of list to bind (cons stx index) + ;; to rest-var. + (define (convert-list-pattern pattern end-pattern) + (syntax-case pattern () + [#s(pat:datum ()) + end-pattern] + [#s(pat:action action tail) + (with-syntax ([tail (convert-list-pattern #'tail end-pattern)]) + #'#s(pat:action action tail))] + [#s(pat:head head tail) + (with-syntax ([tail (convert-list-pattern #'tail end-pattern)]) + #'#s(pat:head head tail))] + [#s(pat:dots head tail) + (with-syntax ([tail (convert-list-pattern #'tail end-pattern)]) + #'#s(pat:dots head tail))] + [#s(pat:pair head-part tail-part) + (with-syntax ([tail-part (convert-list-pattern #'tail-part end-pattern)]) + #'#s(pat:pair head-part tail-part))]))) + +;; (parse:H x cx rest-x rest-cx rest-pr H-pattern pr es k) +;; In k: rest, rest-pr, attrs(H-pattern) are bound. +(define-syntax (parse:H stx) + (syntax-case stx () + [(parse:H x cx rest-x rest-cx rest-pr head pr es k) + (syntax-case #'head () + [#s(hpat:describe pattern description transparent? role) + #`(let ([es* (es-add-thing pr description transparent? role es)] + [pr* (if 'transparent? pr (ps-add-opaque pr))]) + (parse:H x cx rest-x rest-cx rest-pr pattern pr* es* + (let ([rest-pr (if 'transparent? rest-pr (ps-pop-opaque rest-pr))]) + k)))] + [#s(hpat:var/p name parser argu (nested-a ...) role + #s(scopts attr-count commit? _delimit? _desc)) + (with-syntax ([(av ...) (generate-n-temporaries (syntax-e #'attr-count))] + [(name-attr ...) + (if (identifier? #'name) + #'([#s(attr name 0 #t) + (stx-list-take x (ps-difference pr rest-pr))]) + #'())]) + (if (not (syntax-e #'commit?)) + ;; The normal protocol + #`(app-argu parser x cx pr es fail-handler cut-prompt role + (lambda (fh rest-x rest-cx rest-pr av ...) + (let-attributes (name-attr ...) + (let-attributes* ((nested-a ...) (av ...)) + (with ([fail-handler fh]) + k)))) + argu) + ;; The commit protocol + ;; (Avoids putting k in procedure) + #'(let-values ([(fs rest-x rest-cx rest-pr av ...) + (with ([fail-handler (lambda (fs) (values fs #f #f #f (let ([av #f]) av) ...))]) + (with ([cut-prompt fail-handler]) + (app-argu parser x cx pr es fail-handler cut-prompt role + (lambda (fh rest-x rest-cx rest-pr av ...) + (values #f rest-x rest-cx rest-pr av ...)) + argu)))]) + (if fs + (fail fs) + (let-attributes (name-attr ...) + (let-attributes* ((nested-a ...) (av ...)) + k))))))] + [#s(hpat:reflect obj argu attr-decls name (nested-a ...)) + (with-syntax ([(name-attr ...) + (if (identifier? #'name) + #'([#s(attr name 0 #t) + (stx-list-take x (ps-difference pr rest-pr))]) + #'())]) + (with-syntax ([arity (arguments->arity (syntax->datum #'argu))]) + #'(let ([parser (reflect-parser obj 'arity 'attr-decls #t)]) + (app-argu parser x cx pr es fail-handler cut-prompt #f + (lambda (fh rest-x rest-cx rest-pr . result) + (let-attributes (name-attr ...) + (let/unpack ((nested-a ...) result) + (with ([fail-handler fh]) + k)))) + argu))))] + [#s(hpat:and head single) + #`(let ([cx0 cx]) + (parse:H x cx rest-x rest-cx rest-pr head pr es + (let ([lst (stx-list-take x (ps-difference pr rest-pr))]) + (parse:S lst cx0 single pr es k))))] + [#s(hpat:or (a ...) (subpattern ...) (subattrs ...)) + (with-syntax ([(#s(attr id _ _) ...) #'(a ...)]) + #`(let ([success + (lambda (fh rest-x rest-cx rest-pr id ...) + (let-attributes ([a id] ...) + (with ([fail-handler fh]) + k)))]) + (try (parse:H x cx rest-x rest-cx rest-pr subpattern pr es + (disjunct subattrs success (rest-x rest-cx rest-pr) (id ...))) + ...)))] + [#s(hpat:seq pattern) + (with-syntax ([pattern + (convert-list-pattern + #'pattern + #'#s(internal-rest-pattern rest-x rest-cx rest-pr))]) + #'(parse:S x cx pattern pr es k))] + [#s(hpat:action action subpattern) + #'(parse:A x cx action pr es (parse:H x cx rest-x rest-cx rest-pr subpattern pr es k))] + [#s(hpat:delimit pattern) + #'(let ([cp0 cut-prompt]) + (with ([cut-prompt fail-handler]) + (parse:H x cx rest-x rest-cx rest-pr pattern pr es + (with ([cut-prompt cp0]) k))))] + [#s(hpat:commit pattern) + #`(let ([fh0 fail-handler] + [cp0 cut-prompt]) + (with ([cut-prompt fh0]) + (parse:H x cx rest-x rest-cx rest-pr pattern pr es + (with ([cut-prompt cp0] + [fail-handler fh0]) + k))))] + [#s(hpat:ord pattern group index) + #`(let ([pr* (ps-add pr '#s(ord group index))]) + (parse:H x cx rest-x rest-cx rest-pr pattern pr* es + (let ([rest-pr (ps-pop-ord rest-pr)]) k)))] + [#s(hpat:post pattern) + #'(let ([pr* (ps-add-post pr)]) + (parse:H x cx rest-x rest-cx rest-pr pattern pr* es + (let ([rest-pr (ps-pop-post rest-pr)]) k)))] + [#s(hpat:peek pattern) + #`(let ([saved-x x] [saved-cx cx] [saved-pr pr]) + (parse:H x cx dummy-x dummy-cx dummy-pr pattern pr es + (let ([rest-x saved-x] [rest-cx saved-cx] [rest-pr saved-pr]) + k)))] + [#s(hpat:peek-not subpattern) + #`(let* ([fh0 fail-handler] + [pr0 pr] + [es0 es] + [fail-to-succeed + (lambda (fs) + (let ([rest-x x] + [rest-cx cx] + [rest-pr pr]) + k))]) + ;; ~not implicitly prompts to be safe, + ;; but ~! not allowed within ~not (unless within ~delimit-cut, etc) + ;; (statically checked!) + (with ([fail-handler fail-to-succeed] + [cut-prompt fail-to-succeed]) ;; to be safe + (parse:H x cx rest-x rest-cx rest-pr subpattern pr es + (fh0 (failure* pr0 es0)))))] + [_ + #'(parse:S x cx + ;; FIXME: consider proper-list-pattern? (yes is consistent with ~seq) + #s(pat:pair head #s(internal-rest-pattern rest-x rest-cx rest-pr)) + pr es k)])])) + +;; (parse:dots x cx EH-pattern S-pattern pr es k) : expr[Ans] +;; In k: attrs(EH-pattern, S-pattern) are bound. +(define-syntax (parse:dots stx) + (syntax-case stx () + ;; == Specialized cases + ;; -- (x ... . ()) + [(parse:dots x cx (#s(ehpat (attr0) #s(pat:svar name) #f #f)) + #s(pat:datum ()) pr es k) + #'(let-values ([(status result) (predicate-ellipsis-parser x cx pr es void #f #f)]) + (case status + ((ok) (let-attributes ([attr0 result]) k)) + (else (fail result))))] + ;; -- (x:sc ... . ()) where sc is an integrable stxclass like id or expr + [(parse:dots x cx (#s(ehpat (attr0) #s(pat:integrated _name pred? desc role) #f #f)) + #s(pat:datum ()) pr es k) + #'(let-values ([(status result) (predicate-ellipsis-parser x cx pr es pred? desc role)]) + (case status + ((ok) (let-attributes ([attr0 result]) k)) + (else (fail result))))] + ;; -- (x:sc ... . ()) where sc is a stxclass with commit + ;; Since head pattern does commit, no need to thread fail-handler, cut-prompt through. + ;; Microbenchmark suggests this isn't a useful specialization + ;; (probably try-or-pair/null-check already does the useful part) + ;; == General case + [(parse:dots x cx (#s(ehpat head-attrs head head-repc check-null?) ...) tail pr es k) + (let () + (define repcs (wash-list wash #'(head-repc ...))) + (define rep-ids (for/list ([repc (in-list repcs)]) + (and repc (generate-temporary 'rep)))) + (define rel-repcs (filter values repcs)) + (define rel-rep-ids (filter values rep-ids)) + (define rel-heads (for/list ([head (in-list (syntax->list #'(head ...)))] + [repc (in-list repcs)] + #:when repc) + head)) + (define aattrs + (for/list ([head-attrs (in-list (syntax->list #'(head-attrs ...)))] + [repc (in-list repcs)] + #:when #t + [a (in-list (wash-iattrs head-attrs))]) + (cons a repc))) + (define attrs (map car aattrs)) + (define attr-repcs (map cdr aattrs)) + (define ids (map attr-name attrs)) + (define tail-pattern-is-null? (equal? (syntax->datum #'tail) '#s(pat:datum ()))) + (with-syntax ([(id ...) ids] + [(alt-id ...) (generate-temporaries ids)] + [reps rel-rep-ids] + [(head-rep ...) rep-ids] + [(rel-rep ...) rel-rep-ids] + [(rel-repc ...) rel-repcs] + [(rel-head ...) rel-heads] + [(a ...) attrs] + [(attr-repc ...) attr-repcs] + [do-pair/null? + ;; FIXME: do pair/null check only if no nullable head patterns + ;; (and tail-pattern-is-null? (andmap not (syntax->datum #'(nullable? ...)))) + tail-pattern-is-null?]) + (define/with-syntax alt-map #'((id . alt-id) ...)) + (define/with-syntax loop-k + #'(dots-loop dx* dcx* loop-pr* fail-handler rel-rep ... alt-id ...)) + #`(let () + ;; dots-loop : stx progress rel-rep ... alt-id ... -> Ans + (define (dots-loop dx dcx loop-pr fh rel-rep ... alt-id ...) + (with ([fail-handler fh]) + (try-or-pair/null-check do-pair/null? dx dcx loop-pr es + (try (parse:EH dx dcx loop-pr head-attrs check-null? head-repc dx* dcx* loop-pr* + alt-map head-rep head es loop-k) + ...) + (cond [(< rel-rep (rep:min-number rel-repc)) + (let ([es (expectation-of-reps/too-few es rel-rep rel-repc rel-head)]) + (fail (failure* loop-pr es)))] + ... + [else + (let-attributes ([a (rep:finalize a attr-repc alt-id)] ...) + (parse:S dx dcx tail loop-pr es k))])))) + (let ([rel-rep 0] ... + [alt-id (rep:initial-value attr-repc)] ...) + (dots-loop x cx pr fail-handler rel-rep ... alt-id ...)))))])) + +;; (try-or-pair/null-check bool x cx es pr pair-alt maybe-null-alt) +(define-syntax try-or-pair/null-check + (syntax-rules () + [(topc #t x cx pr es pair-alt null-alt) + (cond [(stx-pair? x) pair-alt] + [(stx-null? x) null-alt] + [else (fail (failure* pr es))])] + [(topc _ x cx pr es alt1 alt2) + (try alt1 alt2)])) + +;; (parse:EH x cx pr repc x* cx* pr* alts rep H-pattern es k) : expr[Ans] +;; In k: x*, cx*, pr*, alts`attrs(H-pattern) are bound and rep is shadowed. +(define-syntax (parse:EH stx) + (syntax-case stx () + [(parse:EH x cx pr attrs check-null? repc x* cx* pr* alts rep head es k) + (let () + (define/with-syntax k* + (let* ([main-attrs (wash-iattrs #'attrs)] + [ids (map attr-name main-attrs)] + [alt-ids + (let ([table (make-bound-id-table)]) + (for ([entry (in-list (syntax->list #'alts))]) + (let ([entry (syntax-e entry)]) + (bound-id-table-set! table (car entry) (cdr entry)))) + (for/list ([id (in-list ids)]) (bound-id-table-ref table id)))]) + (with-syntax ([(id ...) ids] + [(alt-id ...) alt-ids]) + #`(let ([alt-id (rep:combine repc (attribute id) alt-id)] ...) + #,(if (syntax->datum #'check-null?) + #'(if (zero? (ps-difference pr pr*)) (error/null-eh-match) k) + #'k))))) + (syntax-case #'repc () + [#f #`(parse:H x cx x* cx* pr* head pr es k*)] + [_ #`(parse:H x cx x* cx* pr* head pr es + (if (< rep (rep:max-number repc)) + (let ([rep (add1 rep)]) k*) + (let ([es* (expectation-of-reps/too-many es rep repc)]) + (fail (failure* pr* es*)))))]))])) + +;; (rep:initial-value RepConstraint) : expr +(define-syntax (rep:initial-value stx) + (syntax-case stx () + [(_ #s(rep:once _ _ _)) #'#f] + [(_ #s(rep:optional _ _ _)) #'#f] + [(_ _) #'null])) + +;; (rep:finalize RepConstraint expr) : expr +(define-syntax (rep:finalize stx) + (syntax-case stx () + [(_ a #s(rep:optional _ _ defaults) v) + (with-syntax ([#s(attr name _ _) #'a] + [(#s(action:bind da de) ...) #'defaults]) + (let ([default + (for/or ([da (in-list (syntax->list #'(da ...)))] + [de (in-list (syntax->list #'(de ...)))]) + (with-syntax ([#s(attr dname _ _) da]) + (and (bound-identifier=? #'name #'dname) de)))]) + (if default + #`(or v #,default) + #'v)))] + [(_ a #s(rep:once _ _ _) v) #'v] + [(_ a _ v) #'(reverse v)])) + +;; (rep:min-number RepConstraint) : expr +(define-syntax (rep:min-number stx) + (syntax-case stx () + [(_ #s(rep:once _ _ _)) #'1] + [(_ #s(rep:optional _ _ _)) #'0] + [(_ #s(rep:bounds min max _ _ _)) #'min])) + +;; (rep:max-number RepConstraint) : expr +(define-syntax (rep:max-number stx) + (syntax-case stx () + [(_ #s(rep:once _ _ _)) #'1] + [(_ #s(rep:optional _ _ _)) #'1] + [(_ #s(rep:bounds min max _ _ _)) #'max])) + +;; (rep:combine RepConstraint expr expr) : expr +(define-syntax (rep:combine stx) + (syntax-case stx () + [(_ #s(rep:once _ _ _) a b) #'a] + [(_ #s(rep:optional _ _ _) a b) #'a] + [(_ _ a b) #'(cons a b)])) + +;; ---- + +(define-syntax expectation-of-reps/too-few + (syntax-rules () + [(_ es rep #s(rep:once name too-few-msg too-many-msg) hpat) + (cond [(or too-few-msg (name->too-few/once name)) + => (lambda (msg) (es-add-message msg es))] + [(first-desc:H hpat) => (lambda (fd) (es-add-proper-pair fd es))] + [else es])] + [(_ es rep #s(rep:optional name too-many-msg _) hpat) + (error 'syntax-parse "INTERNAL ERROR: impossible (too-few)")] + [(_ es rep #s(rep:bounds min max name too-few-msg too-many-msg) hpat) + (cond [(or too-few-msg (name->too-few name)) + => (lambda (msg) (es-add-message msg es))] + [(first-desc:H hpat) => (lambda (fd) (es-add-proper-pair fd es))] + [else es])])) + +(define-syntax expectation-of-reps/too-many + (syntax-rules () + [(_ es rep #s(rep:once name too-few-msg too-many-msg)) + (es-add-message (or too-many-msg (name->too-many name)) es)] + [(_ es rep #s(rep:optional name too-many-msg _)) + (es-add-message (or too-many-msg (name->too-many name)) es)] + [(_ es rep #s(rep:bounds min max name too-few-msg too-many-msg)) + (es-add-message (or too-many-msg (name->too-many name)) es)])) + +;; ==== + +(define-syntax (define-eh-alternative-set stx) + (define (parse-alt x) + (syntax-case x (pattern) + [(pattern alt) + #'alt] + [else + (wrong-syntax x "expected eh-alternative-set alternative")])) + (parameterize ((current-syntax-context stx)) + (syntax-case stx () + [(_ name a ...) + (unless (identifier? #'name) + (wrong-syntax #'name "expected identifier")) + (let* ([alts (map parse-alt (syntax->list #'(a ...)))] + [decls (new-declenv null #:conventions null)] + [ehpat+hstx-list + (apply append + (for/list ([alt (in-list alts)]) + (parse*-ellipsis-head-pattern alt decls #t #:context stx)))] + [eh-alt+defs-list + (for/list ([ehpat+hstx (in-list ehpat+hstx-list)]) + (let ([ehpat (car ehpat+hstx)] + [hstx (cadr ehpat+hstx)]) + (cond [(syntax? hstx) + (with-syntax ([(parser) (generate-temporaries '(eh-alt-parser))]) + (let ([attrs (iattrs->sattrs (pattern-attrs (ehpat-head ehpat)))]) + (list (eh-alternative (ehpat-repc ehpat) attrs #'parser) + (list #`(define parser + (parser/rhs parser () #,attrs + [#:description #f (pattern #,hstx)] + #t + #,stx))))))] + [(eh-alternative? hstx) + (list hstx null)] + [else + (error 'define-eh-alternative-set "internal error: unexpected ~e" + hstx)])))] + [eh-alts (map car eh-alt+defs-list)] + [defs (apply append (map cadr eh-alt+defs-list))]) + (with-syntax ([(def ...) defs] + [(alt-expr ...) + (for/list ([alt (in-list eh-alts)]) + (with-syntax ([repc-expr + ;; repc structs are prefab; recreate using prefab + ;; quasiquote exprs to avoid moving constructors + ;; to residual module + (syntax-case (eh-alternative-repc alt) () + [#f + #''#f] + [#s(rep:once n u o) + #'`#s(rep:once ,(quote-syntax n) + ,(quote-syntax u) + ,(quote-syntax o))] + [#s(rep:optional n o d) + #'`#s(rep:optional ,(quote-syntax n) + ,(quote-syntax o) + ,(quote-syntax d))] + [#s(rep:bounds min max n u o) + #'`#s(rep:bounds ,(quote min) + ,(quote max) + ,(quote-syntax n) + ,(quote-syntax u) + ,(quote-syntax o))])] + [attrs-expr + #`(quote #,(eh-alternative-attrs alt))] + [parser-expr + #`(quote-syntax #,(eh-alternative-parser alt))]) + #'(eh-alternative repc-expr attrs-expr parser-expr)))]) + #'(begin def ... + (define-syntax name + (eh-alternative-set (list alt-expr ...))))))]))) diff --git a/parse/private/rep.rkt-6-11 b/parse/private/rep.rkt-6-11 new file mode 100644 index 0000000..9327159 --- /dev/null +++ b/parse/private/rep.rkt-6-11 @@ -0,0 +1,1646 @@ +#lang racket/base +(require (for-template racket/base + syntax/parse/private/keywords + stxparse-info/parse/private/residual ;; keep abs. path + stxparse-info/parse/private/runtime) + racket/list + racket/contract/base + "make.rkt" + syntax/parse/private/minimatch + syntax/private/id-table + syntax/stx + syntax/keyword + racket/syntax + racket/struct + "txlift.rkt" + syntax/parse/private/rep-attrs + syntax/parse/private/rep-data + syntax/parse/private/rep-patterns + syntax/parse/private/residual-ct ;; keep abs. path + syntax/parse/private/kws) + +;; Error reporting +;; All entry points should have explicit, mandatory #:context arg +;; (mandatory from outside, at least) + +(provide/contract + [atomic-datum-stx? + (-> syntax? + boolean?)] + [parse-rhs + (-> syntax? (or/c false/c (listof sattr?)) boolean? + #:context (or/c false/c syntax?) + rhs?)] + [parse-pattern+sides + (-> syntax? syntax? + #:splicing? boolean? + #:decls DeclEnv/c + #:context syntax? + any)] + [parse*-ellipsis-head-pattern + (-> syntax? DeclEnv/c boolean? + #:context syntax? + any)] + [parse-directive-table any/c] + [get-decls+defs + (-> list? boolean? #:context (or/c false/c syntax?) + (values DeclEnv/c (listof syntax?)))] + [create-aux-def + (-> DeclEntry/c + (values DeclEntry/c (listof syntax?)))] + [parse-argu + (-> (listof syntax?) + #:context syntax? + arguments?)] + [parse-kw-formals + (-> syntax? + #:context syntax? + arity?)] + [check-stxclass-header + (-> syntax? syntax? + (list/c identifier? syntax? arity?))] + [check-stxclass-application + (-> syntax? syntax? + (cons/c identifier? arguments?))] + [check-conventions-rules + (-> syntax? syntax? + (listof (list/c regexp? any/c)))] + [check-datum-literals-list + (-> syntax? syntax? + (listof den:datum-lit?))] + [check-attr-arity-list + (-> syntax? syntax? + (listof sattr?))]) + +;; ---- + +(define (atomic-datum-stx? stx) + (let ([datum (syntax-e stx)]) + (or (null? datum) + (boolean? datum) + (string? datum) + (number? datum) + (keyword? datum) + (bytes? datum) + (char? datum) + (regexp? datum) + (byte-regexp? datum)))) + +(define (id-predicate kw) + (lambda (stx) + (and (identifier? stx) + (free-identifier=? stx kw) + (begin (disappeared! stx) #t)))) + +(define wildcard? (id-predicate (quote-syntax _))) +(define epsilon? (id-predicate (quote-syntax ||))) +(define dots? (id-predicate (quote-syntax ...))) +(define plus-dots? (id-predicate (quote-syntax ...+))) + +(define keywords + (list (quote-syntax _) + (quote-syntax ||) + (quote-syntax ...) + (quote-syntax ~var) + (quote-syntax ~datum) + (quote-syntax ~literal) + (quote-syntax ~and) + (quote-syntax ~or) + (quote-syntax ~not) + (quote-syntax ~seq) + (quote-syntax ~rep) + (quote-syntax ~once) + (quote-syntax ~optional) + (quote-syntax ~between) + (quote-syntax ~rest) + (quote-syntax ~describe) + (quote-syntax ~!) + (quote-syntax ~bind) + (quote-syntax ~fail) + (quote-syntax ~parse) + (quote-syntax ~do) + (quote-syntax ...+) + (quote-syntax ~delimit-cut) + (quote-syntax ~commit) + (quote-syntax ~reflect) + (quote-syntax ~splicing-reflect) + (quote-syntax ~eh-var) + (quote-syntax ~peek) + (quote-syntax ~peek-not))) + +(define (reserved? stx) + (and (identifier? stx) + (for/or ([kw (in-list keywords)]) + (free-identifier=? stx kw)))) + +(define (safe-name? stx) + (and (identifier? stx) + (not (regexp-match? #rx"^~" (symbol->string (syntax-e stx)))))) + +;; cut-allowed? : (paramter/c boolean?) +;; Used to detect ~cut within ~not pattern. +;; (Also #:no-delimit-cut stxclass within ~not) +(define cut-allowed? (make-parameter #t)) + +;; --- + +(define (disappeared! x) + (cond [(identifier? x) + (record-disappeared-uses (list x))] + [(and (stx-pair? x) (identifier? (stx-car x))) + (record-disappeared-uses (list (stx-car x)))] + [else + (raise-type-error 'disappeared! + "identifier or syntax with leading identifier" + x)])) + +;; --- + +;; parse-rhs : stx boolean (or #f (listof SAttr)) stx -> RHS +;; If expected-attrs is true, then referenced stxclasses must be defined and +;; literals must be bound. Set to #f for pass1 (attr collection); +;; parser requires stxclasses to be bound. +(define (parse-rhs stx expected-attrs splicing? #:context ctx) + (call/txlifts + (lambda () + (parameterize ((current-syntax-context ctx)) + (define-values (rest description transp? attributes auto-nested? colon-notation? + decls defs commit? delimit-cut?) + (parse-rhs/part1 stx splicing? (and expected-attrs #t))) + (define variants + (parameterize ((stxclass-lookup-config + (cond [expected-attrs 'yes] + [auto-nested? 'try] + [else 'no])) + (stxclass-colon-notation? colon-notation?)) + (parse-variants rest decls splicing? expected-attrs))) + (let ([sattrs + (or attributes + (intersect-sattrss (map variant-attrs variants)))]) + (make rhs sattrs transp? description variants + (append (get-txlifts-as-definitions) defs) + commit? delimit-cut?)))))) + +(define (parse-rhs/part1 stx splicing? strict?) + (define-values (chunks rest) + (parse-keyword-options stx rhs-directive-table + #:context (current-syntax-context) + #:incompatible '((#:attributes #:auto-nested-attributes) + (#:commit #:no-delimit-cut)) + #:no-duplicates? #t)) + (define description (options-select-value chunks '#:description #:default #f)) + (define opaque? (and (assq '#:opaque chunks) #t)) + (define transparent? (not opaque?)) + (define auto-nested? (and (assq '#:auto-nested-attributes chunks) #t)) + (define colon-notation? (not (assq '#:disable-colon-notation chunks))) + (define commit? + (and (assq '#:commit chunks) #t)) + (define delimit-cut? + (not (assq '#:no-delimit-cut chunks))) + (define attributes (options-select-value chunks '#:attributes #:default #f)) + (define-values (decls defs) (get-decls+defs chunks strict?)) + (values rest description transparent? attributes auto-nested? colon-notation? + decls defs commit? delimit-cut?)) + +;; ---- + +(define (parse-variants rest decls splicing? expected-attrs) + (define (gather-variants stx) + (syntax-case stx (pattern) + [((pattern . _) . rest) + (begin (disappeared! (stx-car stx)) + (cons (parse-variant (stx-car stx) splicing? decls expected-attrs) + (gather-variants #'rest)))] + [(bad-variant . rest) + (wrong-syntax #'bad-variant "expected syntax-class variant")] + [() + null])) + (gather-variants rest)) + +;; get-decls+defs : chunks boolean -> (values DeclEnv (listof syntax)) +(define (get-decls+defs chunks strict? + #:context [ctx (current-syntax-context)]) + (parameterize ((current-syntax-context ctx)) + (let*-values ([(decls defs1) (get-decls chunks strict?)] + [(decls defs2) (decls-create-defs decls)]) + (values decls (append defs1 defs2))))) + +;; get-decls : chunks -> (values DeclEnv (listof syntax)) +(define (get-decls chunks strict?) + (define lits (options-select-value chunks '#:literals #:default null)) + (define datum-lits (options-select-value chunks '#:datum-literals #:default null)) + (define litsets (options-select-value chunks '#:literal-sets #:default null)) + (define convs (options-select-value chunks '#:conventions #:default null)) + (define localconvs (options-select-value chunks '#:local-conventions #:default null)) + (define literals + (append/check-lits+litsets lits datum-lits litsets)) + (define-values (convs-rules convs-defs) + (for/fold ([convs-rules null] [convs-defs null]) + ([conv-entry (in-list convs)]) + (let* ([c (car conv-entry)] + [argu (cdr conv-entry)] + [get-parser-id (conventions-get-procedures c)] + [rules ((conventions-get-rules c))]) + (values (append rules convs-rules) + (cons (make-conventions-def (map cadr rules) get-parser-id argu) + convs-defs))))) + (define convention-rules (append localconvs convs-rules)) + (values (new-declenv literals #:conventions convention-rules) + (reverse convs-defs))) + +;; make-conventions-def : (listof den:delay) id Argument -> syntax +(define (make-conventions-def dens get-parsers-id argu) + (with-syntax ([(parser ...) (map den:delayed-parser dens)] + [get-parsers get-parsers-id] + [argu argu]) + #'(define-values (parser ...) + (apply values (app-argu get-parsers argu))))) + +;; decls-create-defs : DeclEnv -> (values DeclEnv (listof stx)) +(define (decls-create-defs decls0) + (define (updater key value defs) + (let-values ([(value newdefs) (create-aux-def value)]) + (values value (append newdefs defs)))) + (declenv-update/fold decls0 updater null)) + +;; create-aux-def : DeclEntry -> (values DeclEntry (listof stx)) +;; FIXME: replace with txlift mechanism +(define (create-aux-def entry) + (match entry + [(? den:lit?) + (values entry null)] + [(? den:datum-lit?) + (values entry null)] + [(? den:magic-class?) + (values entry null)] + [(den:class name class argu) + ;; FIXME: integrable syntax classes? + ;; FIXME: what if no-arity, no-args? + (cond [(identifier? name) + (let* ([pos-count (length (arguments-pargs argu))] + [kws (arguments-kws argu)] + [sc (get-stxclass/check-arity class class pos-count kws)]) + (with-syntax ([sc-parser (stxclass-parser sc)]) + (with-syntax ([parser (generate-temporary class)]) + (values (make den:parser #'parser + (stxclass-attrs sc) (stxclass/h? sc) + (stxclass-opts sc)) + (list #`(define-values (parser) + (curried-stxclass-parser #,class #,argu)))))))] + [(regexp? name) + ;; Conventions rule; delay class lookup until module/intdefs pass2 + ;; to allow forward references + (with-syntax ([parser (generate-temporary class)] + [description (generate-temporary class)]) + (values (make den:delayed #'parser class) + (list #`(define-values (parser) + (curried-stxclass-parser #,class #,argu)))))])] + [(? den:parser?) + (values entry null)] + [(? den:delayed?) + (values entry null)])) + +;; append/check-lits+litsets : .... -> (listof (U den:lit den:datum-lit)) +(define (append/check-lits+litsets lits datum-lits litsets) + (define seen (make-bound-id-table)) + (define (check-id id [blame-ctx id]) + (if (bound-id-table-ref seen id #f) + (wrong-syntax blame-ctx "duplicate literal declaration: ~s" (syntax-e id)) + (bound-id-table-set! seen id #t)) + id) + (let* ([litsets* + (for/list ([entry (in-list litsets)]) + (let ([litset-id (first entry)] + [litset (second entry)] + [lctx (third entry)] + [input-phase (fourth entry)]) + (define (get/check-id sym) + (check-id (datum->syntax lctx sym) litset-id)) + (for/list ([lse (in-list (literalset-literals litset))]) + (match lse + [(lse:lit internal external lit-phase) + (let ([internal (get/check-id internal)] + [external (syntax-property external 'literal (gensym))]) + (make den:lit internal external input-phase lit-phase))] + [(lse:datum-lit internal external) + (let ([internal (get/check-id internal)]) + (make den:datum-lit internal external))]))))] + [lits* + (for/list ([lit (in-list lits)]) + (check-id (den:lit-internal lit)) + lit)] + [datum-lits* + (for/list ([datum-lit (in-list datum-lits)]) + (check-id (den:datum-lit-internal datum-lit)) + datum-lit)]) + (apply append lits* datum-lits* litsets*))) + +;; parse-variant : stx boolean DeclEnv #f/(listof Sattr) -> RHS +(define (parse-variant stx splicing? decls0 expected-attrs) + (syntax-case stx (pattern) + [(pattern p . rest) + (let-values ([(rest pattern defs) + (parse-pattern+sides #'p #'rest + #:splicing? splicing? + #:decls decls0 + #:context stx)]) + (disappeared! stx) + (unless (stx-null? rest) + (wrong-syntax (if (pair? rest) (car rest) rest) + "unexpected terms after pattern directives")) + (let* ([attrs (pattern-attrs pattern)] + [sattrs (iattrs->sattrs attrs)]) + (when expected-attrs + (parameterize ((current-syntax-context stx)) + ;; Called just for error-reporting + (reorder-iattrs expected-attrs attrs))) + (make variant stx sattrs pattern defs)))])) + +;; parse-pattern+sides : stx stx <options> -> (values stx Pattern (listof stx)) +;; Parses pattern, side clauses; desugars side clauses & merges with pattern +(define (parse-pattern+sides p-stx s-stx + #:splicing? splicing? + #:decls decls0 + #:context ctx) + (let-values ([(rest decls defs sides) + (parse-pattern-directives s-stx + #:allow-declare? #t + #:decls decls0 + #:context ctx)]) + (let* ([pattern0 (parse-whole-pattern p-stx decls splicing? #:context ctx #:kind 'main)] + [pattern (combine-pattern+sides pattern0 sides splicing?)]) + (values rest pattern defs)))) + +;; parse-whole-pattern : stx DeclEnv boolean -> Pattern +;; kind is either 'main or 'with, indicates what kind of pattern declare affects +(define (parse-whole-pattern stx decls [splicing? #f] + #:kind kind + #:context [ctx (current-syntax-context)]) + (parameterize ((current-syntax-context ctx)) + (define pattern + (if splicing? + (parse-head-pattern stx decls) + (parse-single-pattern stx decls))) + (define pvars (map attr-name (pattern-attrs pattern))) + (define excess-domain (declenv-domain-difference decls pvars)) + (when (pair? excess-domain) + (wrong-syntax (car excess-domain) + (string-append + "identifier in #:declare clause does not appear in pattern" + (case kind + [(main) ""] ;; ";\n this #:declare clause affects only the main pattern"] + [(with) ";\n this #:declare clause affects only the preceding #:with pattern"])))) + pattern)) + +;; combine-pattern+sides : Pattern (listof SideClause) -> Pattern +(define (combine-pattern+sides pattern sides splicing?) + (check-pattern + (cond [(pair? sides) + (define actions-pattern + (create-action:and (ord-and-patterns sides (gensym*)))) + (define and-patterns + (ord-and-patterns (list pattern (pat:action actions-pattern (pat:any))) + (gensym*))) + (cond [splicing? (apply hpat:and and-patterns)] + [else (pat:and and-patterns)])] + [else pattern]))) + +;; gensym* : -> UninternedSymbol +;; Like gensym, but with deterministic name from compilation-local counter. +(define gensym*-counter 0) +(define (gensym*) + (set! gensym*-counter (add1 gensym*-counter)) + (string->uninterned-symbol (format "group~a" gensym*-counter))) + +;; ---- + +;; parse-single-pattern : stx DeclEnv -> SinglePattern +(define (parse-single-pattern stx decls) + (parse-*-pattern stx decls #f #f)) + +;; parse-head-pattern : stx DeclEnv -> HeadPattern +(define (parse-head-pattern stx decls) + (parse-*-pattern stx decls #t #f)) + +;; parse-action-pattern : Stx DeclEnv -> ActionPattern +(define (parse-action-pattern stx decls) + (define p (parse-*-pattern stx decls #f #t)) + (unless (action-pattern? p) + (wrong-syntax stx "expected action pattern")) + p) + +(define ((make-not-shadowed? decls) id) + ;; Returns #f if id is in literals/datum-literals list. + ;; Conventions to not shadow pattern-form bindings, under the + ;; theory that conventions only apply to things already determined + ;; to be pattern variables. + (not (declenv-lookup decls id))) +;; suitable as id=? argument to syntax-case* +(define ((make-not-shadowed-id=? decls) lit-id pat-id) + (and (free-identifier=? lit-id pat-id) + (not (declenv-lookup decls pat-id)))) + +;; parse-*-pattern : stx DeclEnv boolean boolean -> Pattern +(define (parse-*-pattern stx decls allow-head? allow-action?) + (define (recur stx) + (parse-*-pattern stx decls allow-head? allow-action?)) + (define (check-head! x) + (unless allow-head? + (wrong-syntax stx "head pattern not allowed here")) + x) + (define (check-action! x) + ;; Coerce to S-pattern IF only S-patterns allowed + (cond [allow-action? x] + [(not allow-head?) (action-pattern->single-pattern x)] + [else + (wrong-syntax stx "action pattern not allowed here")])) + (define not-shadowed? (make-not-shadowed? decls)) + (check-pattern + (syntax-case* stx (~var ~literal ~datum ~and ~or ~not ~rest ~describe + ~seq ~optional ~! ~bind ~fail ~parse ~do + ~post ~peek ~peek-not ~delimit-cut ~commit ~reflect + ~splicing-reflect) + (make-not-shadowed-id=? decls) + [id + (and (identifier? #'id) + (not-shadowed? #'id) + (pattern-expander? (syntax-local-value #'id (λ () #f)))) + (begin (disappeared! #'id) + (recur (expand-pattern (syntax-local-value #'id) stx)))] + [(id . rst) + (and (identifier? #'id) + (not-shadowed? #'id) + (pattern-expander? (syntax-local-value #'id (λ () #f)))) + (begin (disappeared! #'id) + (recur (expand-pattern (syntax-local-value #'id) stx)))] + [wildcard + (and (wildcard? #'wildcard) + (not-shadowed? #'wildcard)) + (begin (disappeared! stx) + (pat:any))] + [~! + (disappeared! stx) + (begin + (unless (cut-allowed?) + (wrong-syntax stx + "cut (~~!) not allowed within ~~not pattern")) + (check-action! + (action:cut)))] + [reserved + (and (reserved? #'reserved) + (not-shadowed? #'reserved)) + (wrong-syntax stx "pattern keyword not allowed here")] + [id + (identifier? #'id) + (parse-pat:id stx decls allow-head?)] + [datum + (atomic-datum-stx? #'datum) + (pat:datum (syntax->datum #'datum))] + [(~var . rest) + (disappeared! stx) + (parse-pat:var stx decls allow-head?)] + [(~datum . rest) + (disappeared! stx) + (syntax-case stx (~datum) + [(~datum d) + (pat:datum (syntax->datum #'d))] + [_ (wrong-syntax stx "bad ~~datum form")])] + [(~literal . rest) + (disappeared! stx) + (parse-pat:literal stx decls)] + [(~and . rest) + (disappeared! stx) + (parse-pat:and stx decls allow-head? allow-action?)] + [(~or . rest) + (disappeared! stx) + (parse-pat:or stx decls allow-head?)] + [(~not . rest) + (disappeared! stx) + (parse-pat:not stx decls)] + [(~rest . rest) + (disappeared! stx) + (parse-pat:rest stx decls)] + [(~describe . rest) + (disappeared! stx) + (parse-pat:describe stx decls allow-head?)] + [(~delimit-cut . rest) + (disappeared! stx) + (parse-pat:delimit stx decls allow-head?)] + [(~commit . rest) + (disappeared! stx) + (parse-pat:commit stx decls allow-head?)] + [(~reflect . rest) + (disappeared! stx) + (parse-pat:reflect stx decls #f)] + [(~seq . rest) + (disappeared! stx) + (check-head! + (parse-hpat:seq stx #'rest decls))] + [(~optional . rest) + (disappeared! stx) + (check-head! + (parse-hpat:optional stx decls))] + [(~splicing-reflect . rest) + (disappeared! stx) + (check-head! + (parse-pat:reflect stx decls #t))] + [(~bind . rest) + (disappeared! stx) + (check-action! + (parse-pat:bind stx decls))] + [(~fail . rest) + (disappeared! stx) + (check-action! + (parse-pat:fail stx decls))] + [(~post . rest) + (disappeared! stx) + (parse-pat:post stx decls allow-head? allow-action?)] + [(~peek . rest) + (disappeared! stx) + (check-head! + (parse-pat:peek stx decls))] + [(~peek-not . rest) + (disappeared! stx) + (check-head! + (parse-pat:peek-not stx decls))] + [(~parse . rest) + (disappeared! stx) + (check-action! + (parse-pat:parse stx decls))] + [(~do . rest) + (disappeared! stx) + (check-action! + (parse-pat:do stx decls))] + [(head dots . tail) + (and (dots? #'dots) (not-shadowed? #'dots)) + (begin (disappeared! #'dots) + (parse-pat:dots stx #'head #'tail decls))] + [(head plus-dots . tail) + (and (plus-dots? #'plus-dots) (not-shadowed? #'plus-dots)) + (begin (disappeared! #'plus-dots) + (parse-pat:plus-dots stx #'head #'tail decls))] + [(head . tail) + (let ([headp (parse-*-pattern #'head decls #t #t)] + [tailp (parse-single-pattern #'tail decls)]) + (cond [(action-pattern? headp) + (pat:action headp tailp)] + [(head-pattern? headp) + (pat:head headp tailp)] + [else (pat:pair headp tailp)]))] + [#(a ...) + (let ([lp (parse-single-pattern (syntax/loc stx (a ...)) decls)]) + (pat:vector lp))] + [b + (box? (syntax-e #'b)) + (let ([bp (parse-single-pattern (unbox (syntax-e #'b)) decls)]) + (pat:box bp))] + [s + (and (struct? (syntax-e #'s)) (prefab-struct-key (syntax-e #'s))) + (let* ([s (syntax-e #'s)] + [key (prefab-struct-key s)] + [contents (struct->list s)]) + (let ([lp (parse-single-pattern (datum->syntax #f contents #'s) decls)]) + (pat:pstruct key lp)))]))) + +;; expand-pattern : pattern-expander Syntax -> Syntax +(define (expand-pattern pe stx) + (let* ([proc (pattern-expander-proc pe)] + [introducer (make-syntax-introducer)] + [mstx (introducer (syntax-local-introduce stx))] + [mresult (parameterize ([current-syntax-parse-pattern-introducer introducer]) + (proc mstx))] + [result (syntax-local-introduce (introducer mresult))]) + result)) + +;; parse-ellipsis-head-pattern : stx DeclEnv -> (listof EllipsisHeadPattern) +(define (parse-ellipsis-head-pattern stx decls) + (for/list ([ehpat+hstx (in-list (parse*-ellipsis-head-pattern stx decls #t))]) + (car ehpat+hstx))) + +;; parse*-ellipsis-head-pattern : stx DeclEnv bool +;; -> (listof (list EllipsisHeadPattern stx/eh-alternative)) +(define (parse*-ellipsis-head-pattern stx decls allow-or? + #:context [ctx (current-syntax-context)]) + (define (recur stx) (parse*-ellipsis-head-pattern stx decls allow-or? #:context ctx)) + (define not-shadowed? (make-not-shadowed? decls)) + (syntax-case* stx (~eh-var ~or ~between ~optional ~once) + (make-not-shadowed-id=? decls) + [id + (and (identifier? #'id) + (not-shadowed? #'id) + (pattern-expander? (syntax-local-value #'id (lambda () #f)))) + (begin (disappeared! #'id) + (recur (expand-pattern (syntax-local-value #'id) stx)))] + [(id . rst) + (and (identifier? #'id) + (not-shadowed? #'id) + (pattern-expander? (syntax-local-value #'id (lambda () #f)))) + (begin (disappeared! #'id) + (recur (expand-pattern (syntax-local-value #'id) stx)))] + [(~eh-var name eh-alt-set-id) + (disappeared! stx) + (let () + (define prefix (name->prefix #'name ".")) + (define eh-alt-set (get-eh-alternative-set #'eh-alt-set-id)) + (for/list ([alt (in-list (eh-alternative-set-alts eh-alt-set))]) + (let* ([iattrs (id-pattern-attrs (eh-alternative-attrs alt) prefix)] + [attr-count (length iattrs)]) + (list (create-ehpat + (hpat:var/p #f (eh-alternative-parser alt) no-arguments iattrs #f + (scopts attr-count #f #t #f)) + (eh-alternative-repc alt) + #f) + (replace-eh-alternative-attrs + alt (iattrs->sattrs iattrs))))))] + [(~or . _) + allow-or? + (begin + (disappeared! stx) + (unless (stx-list? stx) + (wrong-syntax stx "expected sequence of patterns")) + (apply append + (for/list ([sub (in-list (cdr (stx->list stx)))]) + (parse*-ellipsis-head-pattern sub decls allow-or?))))] + [(~optional . _) + (disappeared! stx) + (list (parse*-ehpat/optional stx decls))] + [(~once . _) + (disappeared! stx) + (list (parse*-ehpat/once stx decls))] + [(~between . _) + (disappeared! stx) + (list (parse*-ehpat/bounds stx decls))] + [_ + (let ([head (parse-head-pattern stx decls)]) + (list (list (create-ehpat head #f stx) stx)))])) + +(define (replace-eh-alternative-attrs alt sattrs) + (match alt + [(eh-alternative repc _attrs parser) + (eh-alternative repc sattrs parser)])) + +;; ---- + +(define (check-no-delimit-cut-in-not id delimit-cut?) + (unless (or delimit-cut? (cut-allowed?)) + (wrong-syntax id + (string-append "syntax class with #:no-delimit-cut option " + "not allowed within ~~not pattern")))) + +(define (parse-pat:id id decls allow-head?) + (cond [(declenv-lookup decls id) + => (lambda (entry) (parse-pat:id/entry id allow-head? entry))] + [(not (safe-name? id)) + (wrong-syntax id "expected identifier not starting with ~~ character")] + [else + (let-values ([(name suffix) (split-id/get-stxclass id decls)]) + (cond [(stxclass? suffix) + (parse-pat:var/sc id allow-head? name suffix no-arguments "." #f #f)] + [(or (den:lit? suffix) (den:datum-lit? suffix)) + (pat:and + (list (pat:svar name) + (parse-pat:id/entry id allow-head? suffix)))] + [(declenv-apply-conventions decls id) + => (lambda (entry) (parse-pat:id/entry id allow-head? entry))] + [else (pat:svar name)]))])) + +;; parse-pat:id/entry : Identifier .... DeclEntry -> SinglePattern +;; Handle when meaning of identifier pattern is given by declenv entry. +(define (parse-pat:id/entry id allow-head? entry) + (match entry + [(den:lit internal literal input-phase lit-phase) + (pat:literal literal input-phase lit-phase)] + [(den:datum-lit internal sym) + (pat:datum sym)] + [(den:magic-class name class argu role) + (let* ([pos-count (length (arguments-pargs argu))] + [kws (arguments-kws argu)] + [sc (get-stxclass/check-arity class class pos-count kws)]) + (parse-pat:var/sc id allow-head? id sc argu "." role #f))] + [(den:class _n _c _a) + (error 'parse-pat:id + "(internal error) decls had leftover stxclass entry: ~s" + entry)] + [(den:parser parser attrs splicing? opts) + (check-no-delimit-cut-in-not id (scopts-delimit-cut? opts)) + (cond [splicing? + (unless allow-head? + (wrong-syntax id "splicing syntax class not allowed here")) + (parse-pat:id/h id parser no-arguments attrs "." #f opts)] + [else + (parse-pat:id/s id parser no-arguments attrs "." #f opts)])] + [(den:delayed parser class) + (let ([sc (get-stxclass class)]) + (parse-pat:var/sc id allow-head? id sc no-arguments "." #f parser))])) + +(define (parse-pat:var stx decls allow-head?) + (define name0 + (syntax-case stx () + [(_ name . _) + (unless (identifier? #'name) + (wrong-syntax #'name "expected identifier")) + #'name] + [_ + (wrong-syntax stx "bad ~~var form")])) + (define-values (scname sc+args-stx argu pfx role) + (syntax-case stx () + [(_ _name) + (values #f #f null #f #f)] + [(_ _name sc/sc+args . rest) + (let-values ([(sc argu) + (let ([p (check-stxclass-application #'sc/sc+args stx)]) + (values (car p) (cdr p)))]) + (define chunks + (parse-keyword-options/eol #'rest var-pattern-directive-table + #:no-duplicates? #t + #:context stx)) + (define sep + (options-select-value chunks '#:attr-name-separator #:default #f)) + (define role (options-select-value chunks '#:role #:default #'#f)) + (values sc #'sc/sc+args argu (if sep (syntax-e sep) ".") role))] + [_ + (wrong-syntax stx "bad ~~var form")])) + (cond [(and (epsilon? name0) (not scname)) + (wrong-syntax name0 "illegal pattern variable name")] + [(and (wildcard? name0) (not scname)) + (pat:any)] + [scname + (let ([sc (get-stxclass/check-arity scname sc+args-stx + (length (arguments-pargs argu)) + (arguments-kws argu))]) + (parse-pat:var/sc stx allow-head? name0 sc argu pfx role #f))] + [else ;; Just proper name + (pat:svar name0)])) + +(define (parse-pat:var/sc stx allow-head? name sc argu pfx role parser*) + ;; if parser* not #f, overrides sc parser + (check-no-delimit-cut-in-not stx (scopts-delimit-cut? (stxclass-opts sc))) + (cond [(and (stxclass/s? sc) + (stxclass-inline sc) + (equal? argu no-arguments)) + (parse-pat:id/s/integrate name (stxclass-inline sc) (scopts-desc (stxclass-opts sc)) role)] + [(stxclass/s? sc) + (parse-pat:id/s name + (or parser* (stxclass-parser sc)) + argu + (stxclass-attrs sc) + pfx + role + (stxclass-opts sc))] + [(stxclass/h? sc) + (unless allow-head? + (wrong-syntax stx "splicing syntax class not allowed here")) + (parse-pat:id/h name + (or parser* (stxclass-parser sc)) + argu + (stxclass-attrs sc) + pfx + role + (stxclass-opts sc))])) + +(define (parse-pat:id/s name parser argu attrs pfx role opts) + (define prefix (name->prefix name pfx)) + (define bind (name->bind name)) + (pat:var/p bind parser argu (id-pattern-attrs attrs prefix) role opts)) + +(define (parse-pat:id/s/integrate name predicate description role) + (define bind (name->bind name)) + (pat:integrated bind predicate description role)) + +(define (parse-pat:id/h name parser argu attrs pfx role opts) + (define prefix (name->prefix name pfx)) + (define bind (name->bind name)) + (hpat:var/p bind parser argu (id-pattern-attrs attrs prefix) role opts)) + +(define (name->prefix id pfx) + (cond [(wildcard? id) #f] + [(epsilon? id) id] + [else (format-id id "~a~a" (syntax-e id) pfx #:source id)])) + +(define (name->bind id) + (cond [(wildcard? id) #f] + [(epsilon? id) #f] + [else id])) + +;; id-pattern-attrs : (listof SAttr)IdPrefix -> (listof IAttr) +(define (id-pattern-attrs sattrs prefix) + (if prefix + (for/list ([a (in-list sattrs)]) + (prefix-attr a prefix)) + null)) + +;; prefix-attr : SAttr identifier -> IAttr +(define (prefix-attr a prefix) + (make attr (prefix-attr-name prefix (attr-name a)) + (attr-depth a) + (attr-syntax? a))) + +;; prefix-attr-name : id symbol -> id +(define (prefix-attr-name prefix name) + (orig (format-id prefix "~a~a" (syntax-e prefix) name #:source prefix))) + +(define (orig stx) + (syntax-property stx 'original-for-check-syntax #t)) + +;; ---- + +(define (parse-pat:reflect stx decls splicing?) + (syntax-case stx () + [(_ name (obj arg ...) . maybe-signature) + (let () + (unless (identifier? #'var) + (raise-syntax-error #f "expected identifier" stx #'name)) + (define attr-decls + (syntax-case #'maybe-signature () + [(#:attributes attr-decls) + (check-attr-arity-list #'attr-decls stx)] + [() null] + [_ (raise-syntax-error #f "bad syntax" stx)])) + (define prefix (name->prefix #'name ".")) + (define bind (name->bind #'name)) + (define ctor (if splicing? hpat:reflect pat:reflect)) + (ctor #'obj (parse-argu (syntax->list #'(arg ...))) attr-decls bind + (id-pattern-attrs attr-decls prefix)))])) + +;; --- + +(define (parse-pat:literal stx decls) + (syntax-case stx () + [(_ lit . more) + (unless (identifier? #'lit) + (wrong-syntax #'lit "expected identifier")) + (let* ([chunks (parse-keyword-options/eol #'more phase-directive-table + #:no-duplicates? #t + #:context stx)] + [phase (options-select-value chunks '#:phase + #:default #'(syntax-local-phase-level))]) + ;; FIXME: Duplicates phase expr! + (pat:literal #'lit phase phase))] + [_ + (wrong-syntax stx "bad ~~literal pattern")])) + +(define (parse-pat:describe stx decls allow-head?) + (syntax-case stx () + [(_ . rest) + (let-values ([(chunks rest) + (parse-keyword-options #'rest describe-option-table + #:no-duplicates? #t + #:context stx)]) + (define transparent? (not (assq '#:opaque chunks))) + (define role (options-select-value chunks '#:role #:default #'#f)) + (syntax-case rest () + [(description pattern) + (let ([p (parse-*-pattern #'pattern decls allow-head? #f)]) + (if (head-pattern? p) + (hpat:describe p #'description transparent? role) + (pat:describe p #'description transparent? role)))]))])) + +(define (parse-pat:delimit stx decls allow-head?) + (syntax-case stx () + [(_ pattern) + (let ([p (parameterize ((cut-allowed? #t)) + (parse-*-pattern #'pattern decls allow-head? #f))]) + (if (head-pattern? p) + (hpat:delimit p) + (pat:delimit p)))])) + +(define (parse-pat:commit stx decls allow-head?) + (syntax-case stx () + [(_ pattern) + (let ([p (parameterize ((cut-allowed? #t)) + (parse-*-pattern #'pattern decls allow-head? #f))]) + (if (head-pattern? p) + (hpat:commit p) + (pat:commit p)))])) + +(define (split-prefix xs pred) + (let loop ([xs xs] [rprefix null]) + (cond [(and (pair? xs) (pred (car xs))) + (loop (cdr xs) (cons (car xs) rprefix))] + [else + (values (reverse rprefix) xs)]))) + +(define (parse-pat:and stx decls allow-head? allow-action?) + ;; allow-action? = allowed to *return* pure action pattern; + ;; all ~and patterns are allowed to *contain* action patterns + (define patterns0 (parse-cdr-patterns stx decls allow-head? #t)) + (define patterns1 (ord-and-patterns patterns0 (gensym*))) + (define-values (actions patterns) (split-prefix patterns1 action-pattern?)) + (cond [(null? patterns) + (cond [allow-action? + (action:and actions)] + [allow-head? + (wrong-syntax stx "expected at least one head pattern")] + [else + (wrong-syntax stx "expected at least one single-term pattern")])] + [else + (let ([p (parse-pat:and* stx patterns)]) + (if (head-pattern? p) + (for/fold ([p p]) ([action (in-list (reverse actions))]) + (hpat:action action p)) + (for/fold ([p p]) ([action (in-list (reverse actions))]) + (pat:action action p))))])) + +(define (parse-pat:and* stx patterns) + ;; patterns is non-empty (empty case handled above) + (cond [(null? (cdr patterns)) + (car patterns)] + [(ormap head-pattern? patterns) + ;; Check to make sure *all* are head patterns + (for ([pattern (in-list patterns)] + [pattern-stx (in-list (stx->list (stx-cdr stx)))]) + (unless (or (action-pattern? pattern) (head-pattern? pattern)) + (wrong-syntax + pattern-stx + "single-term pattern not allowed after head pattern"))) + (let ([p0 (car patterns)] + [lps (map action/head-pattern->list-pattern (cdr patterns))]) + (hpat:and p0 (pat:and lps)))] + [else + (pat:and + (for/list ([p (in-list patterns)]) + (if (action-pattern? p) + (action-pattern->single-pattern p) + p)))])) + +(define (parse-pat:or stx decls allow-head?) + (define patterns (parse-cdr-patterns stx decls allow-head? #f)) + (cond [(null? (cdr patterns)) + (car patterns)] + [else + (cond [(ormap head-pattern? patterns) + (create-hpat:or patterns)] + [else + (create-pat:or patterns)])])) + +(define (parse-pat:not stx decls) + (syntax-case stx () + [(_ pattern) + (let ([p (parameterize ((cut-allowed? #f)) + (parse-single-pattern #'pattern decls))]) + (pat:not p))] + [_ + (wrong-syntax stx "expected a single subpattern")])) + +(define (parse-hpat:seq stx list-stx decls) + (define pattern (parse-single-pattern list-stx decls)) + (unless (proper-list-pattern? pattern) + (wrong-syntax stx "expected proper list pattern")) + (hpat:seq pattern)) + +(define (parse-cdr-patterns stx decls allow-head? allow-action?) + (unless (stx-list? stx) + (wrong-syntax stx "expected sequence of patterns")) + (let ([result + (for/list ([sub (in-list (cdr (stx->list stx)))]) + (parse-*-pattern sub decls allow-head? allow-action?))]) + (when (null? result) + (wrong-syntax stx "expected at least one pattern")) + result)) + +(define (parse-pat:dots stx head tail decls) + (define headps (parse-ellipsis-head-pattern head decls)) + (define tailp (parse-single-pattern tail decls)) + (unless (pair? headps) + (wrong-syntax head "expected at least one pattern")) + (pat:dots headps tailp)) + +(define (parse-pat:plus-dots stx head tail decls) + (define headp (parse-head-pattern head decls)) + (define tailp (parse-single-pattern tail decls)) + (define head/rep (create-ehpat headp (make-rep:bounds 1 +inf.0 #f #f #f) head)) + (pat:dots (list head/rep) tailp)) + +(define (parse-pat:bind stx decls) + (syntax-case stx () + [(_ clause ...) + (let ([clauses (check-bind-clause-list #'(clause ...) stx)]) + (create-action:and clauses))])) + +(define (parse-pat:fail stx decls) + (syntax-case stx () + [(_ . rest) + (let-values ([(chunks rest) + (parse-keyword-options #'rest fail-directive-table + #:context stx + #:incompatible '((#:when #:unless)) + #:no-duplicates? #t)]) + (let ([condition + (if (null? chunks) + #'#t + (let ([chunk (car chunks)]) + (if (eq? (car chunk) '#:when) + (caddr chunk) + #`(not #,(caddr chunk)))))]) + (syntax-case rest () + [(message) + (action:fail condition #'message)] + [() + (action:fail condition #''#f)] + [_ + (wrong-syntax stx "bad ~~fail pattern")])))])) + +(define (parse-pat:post stx decls allow-head? allow-action?) + (syntax-case stx () + [(_ pattern) + (let ([p (parse-*-pattern #'pattern decls allow-head? allow-action?)]) + (cond [(action-pattern? p) + (cond [allow-action? (action:post p)] + [(not allow-head?) (pat:post (action-pattern->single-pattern p))] + [else (wrong-syntax stx "action pattern not allowed here")])] + [(head-pattern? p) + (cond [allow-head? (hpat:post p)] + [else (wrong-syntax stx "head pattern now allowed here")])] + [else (pat:post p)]))])) + +(define (parse-pat:peek stx decls) + (syntax-case stx () + [(_ pattern) + (let ([p (parse-head-pattern #'pattern decls)]) + (hpat:peek p))])) + +(define (parse-pat:peek-not stx decls) + (syntax-case stx () + [(_ pattern) + (let ([p (parse-head-pattern #'pattern decls)]) + (hpat:peek-not p))])) + +(define (parse-pat:parse stx decls) + (syntax-case stx () + [(_ pattern expr) + (let ([p (parse-single-pattern #'pattern decls)]) + (action:parse p #'expr))] + [_ + (wrong-syntax stx "bad ~~parse pattern")])) + +(define (parse-pat:do stx decls) + (syntax-case stx () + [(_ stmt ...) + (action:do (syntax->list #'(stmt ...)))] + [_ + (wrong-syntax stx "bad ~~do pattern")])) + +(define (parse-pat:rest stx decls) + (syntax-case stx () + [(_ pattern) + (parse-single-pattern #'pattern decls)])) + +(define (parse-hpat:optional stx decls) + (define-values (head-stx head iattrs _name _tmm defaults) + (parse*-optional-pattern stx decls h-optional-directive-table)) + (create-hpat:or + (list head + (hpat:action (create-action:and defaults) + (hpat:seq (pat:datum '())))))) + +;; parse*-optional-pattern : stx DeclEnv table +;; -> (values Syntax HeadPattern IAttrs Stx Stx (Listof BindClause)) +(define (parse*-optional-pattern stx decls optional-directive-table) + (syntax-case stx () + [(_ p . options) + (let* ([head (parse-head-pattern #'p decls)] + [chunks + (parse-keyword-options/eol #'options optional-directive-table + #:no-duplicates? #t + #:context stx)] + [too-many-msg + (options-select-value chunks '#:too-many #:default #'#f)] + [name + (options-select-value chunks '#:name #:default #'#f)] + [defaults + (options-select-value chunks '#:defaults #:default '())] + [pattern-iattrs (pattern-attrs head)] + [defaults-iattrs + (append-iattrs (map pattern-attrs defaults))] + [all-iattrs + (union-iattrs (list pattern-iattrs defaults-iattrs))]) + (when (eq? (stxclass-lookup-config) 'yes) + ;; Only check that attrs in defaults clause agree with attrs + ;; in pattern when attrs in pattern are known to be complete. + (check-iattrs-subset defaults-iattrs pattern-iattrs stx)) + (values #'p head all-iattrs name too-many-msg defaults))])) + +;; -- EH patterns +;; Only parse the rep-constraint part; don't parse the head pattern within. +;; (To support eh-alternative-sets.) + +;; parse*-ehpat/optional : stx DeclEnv -> (list EllipsisHeadPattern stx) +(define (parse*-ehpat/optional stx decls) + (define-values (head-stx head iattrs name too-many-msg defaults) + (parse*-optional-pattern stx decls eh-optional-directive-table)) + (list (create-ehpat head (make rep:optional name too-many-msg defaults) head-stx) + head-stx)) + +;; parse*-ehpat/once : stx DeclEnv -> (list EllipsisHeadPattern stx) +(define (parse*-ehpat/once stx decls) + (syntax-case stx () + [(_ p . options) + (let* ([head (parse-head-pattern #'p decls)] + [chunks + (parse-keyword-options/eol #'options + (list (list '#:too-few check-expression) + (list '#:too-many check-expression) + (list '#:name check-expression)) + #:context stx)] + [too-few-msg + (options-select-value chunks '#:too-few #:default #'#f)] + [too-many-msg + (options-select-value chunks '#:too-many #:default #'#f)] + [name + (options-select-value chunks '#:name #:default #'#f)]) + (list (create-ehpat head (make rep:once name too-few-msg too-many-msg) #'p) + #'p))])) + +;; parse*-ehpat/bounds : stx DeclEnv -> (list EllipsisHeadPattern stx) +(define (parse*-ehpat/bounds stx decls) + (syntax-case stx () + [(_ p min max . options) + (let () + (define head (parse-head-pattern #'p decls)) + (define minN (syntax-e #'min)) + (define maxN (syntax-e #'max)) + (unless (exact-nonnegative-integer? minN) + (wrong-syntax #'min + "expected exact nonnegative integer")) + (unless (or (exact-nonnegative-integer? maxN) (equal? maxN +inf.0)) + (wrong-syntax #'max + "expected exact nonnegative integer or +inf.0")) + (when (> minN maxN) + (wrong-syntax stx "minimum larger than maximum repetition constraint")) + (let* ([chunks (parse-keyword-options/eol + #'options + (list (list '#:too-few check-expression) + (list '#:too-many check-expression) + (list '#:name check-expression)) + #:context stx)] + [too-few-msg + (options-select-value chunks '#:too-few #:default #'#f)] + [too-many-msg + (options-select-value chunks '#:too-many #:default #'#f)] + [name + (options-select-value chunks '#:name #:default #'#f)]) + (list (create-ehpat head + (make rep:bounds #'min #'max + name too-few-msg too-many-msg) + #'p) + #'p)))])) + +;; ----- + +;; parse-pattern-directives : stxs(PatternDirective) <kw-args> +;; -> stx DeclEnv (listof stx) (listof SideClause) +(define (parse-pattern-directives stx + #:allow-declare? allow-declare? + #:decls decls + #:context ctx) + (parameterize ((current-syntax-context ctx)) + (define-values (chunks rest) + (parse-keyword-options stx pattern-directive-table #:context ctx)) + (define-values (decls2 chunks2) + (if allow-declare? + (grab-decls chunks decls) + (values decls chunks))) + (define sides + ;; NOTE: use *original* decls + ;; because decls2 has #:declares for *above* pattern + (parse-pattern-sides chunks2 decls)) + (define-values (decls3 defs) + (decls-create-defs decls2)) + (values rest decls3 defs sides))) + +;; parse-pattern-sides : (listof chunk) DeclEnv -> (listof SideClause) +;; Invariant: decls contains only literals bindings +(define (parse-pattern-sides chunks decls) + (match chunks + [(cons (list '#:declare declare-stx _ _) rest) + (wrong-syntax declare-stx + "#:declare can only appear immediately after pattern or #:with clause")] + [(cons (list '#:role role-stx _) rest) + (wrong-syntax role-stx "#:role can only appear immediately after #:declare clause")] + [(cons (list '#:fail-when fw-stx when-expr msg-expr) rest) + (cons (create-post-pattern (action:fail when-expr msg-expr)) + (parse-pattern-sides rest decls))] + [(cons (list '#:fail-unless fu-stx unless-expr msg-expr) rest) + (cons (create-post-pattern (action:fail #`(not #,unless-expr) msg-expr)) + (parse-pattern-sides rest decls))] + [(cons (list '#:when w-stx unless-expr) rest) + (cons (create-post-pattern (action:fail #`(not #,unless-expr) #'#f)) + (parse-pattern-sides rest decls))] + [(cons (list '#:with with-stx pattern expr) rest) + (let-values ([(decls2 rest) (grab-decls rest decls)]) + (let-values ([(decls2a defs) (decls-create-defs decls2)]) + (list* (action:do defs) + (create-post-pattern + (action:parse (parse-whole-pattern pattern decls2a #:kind 'with) expr)) + (parse-pattern-sides rest decls))))] + [(cons (list '#:attr attr-stx a expr) rest) + (cons (action:bind a expr) ;; no POST wrapper, cannot fail + (parse-pattern-sides rest decls))] + [(cons (list '#:post post-stx pattern) rest) + (cons (create-post-pattern (parse-action-pattern pattern decls)) + (parse-pattern-sides rest decls))] + [(cons (list '#:and and-stx pattern) rest) + (cons (parse-action-pattern pattern decls) ;; no POST wrapper + (parse-pattern-sides rest decls))] + [(cons (list '#:do do-stx stmts) rest) + (cons (action:do stmts) + (parse-pattern-sides rest decls))] + ['() + '()])) + +;; grab-decls : (listof chunk) DeclEnv +;; -> (values DeclEnv (listof chunk)) +(define (grab-decls chunks decls0) + (define (add-decl stx role-stx decls) + (let ([role + (and role-stx + (syntax-case role-stx () + [(#:role role) #'role]))]) + (syntax-case stx () + [(#:declare name sc) + (identifier? #'sc) + (add-decl* decls #'name #'sc (parse-argu null) role)] + [(#:declare name (sc expr ...)) + (identifier? #'sc) + (add-decl* decls #'name #'sc (parse-argu (syntax->list #'(expr ...))) role)] + [(#:declare name bad-sc) + (wrong-syntax #'bad-sc + "expected syntax class name (possibly with parameters)")]))) + (define (add-decl* decls id sc-name argu role) + (declenv-put-stxclass decls id sc-name argu role)) + (define (loop chunks decls) + (match chunks + [(cons (cons '#:declare decl-stx) + (cons (cons '#:role role-stx) rest)) + (loop rest (add-decl decl-stx role-stx decls))] + [(cons (cons '#:declare decl-stx) rest) + (loop rest (add-decl decl-stx #f decls))] + [_ (values decls chunks)])) + (loop chunks decls0)) + + +;; ---- + +;; Keyword Options & Checkers + +;; check-attr-arity-list : stx stx -> (listof SAttr) +(define (check-attr-arity-list stx ctx) + (unless (stx-list? stx) + (raise-syntax-error #f "expected list of attribute declarations" ctx stx)) + (let ([iattrs + (for/list ([x (in-list (stx->list stx))]) + (check-attr-arity x ctx))]) + (iattrs->sattrs (append-iattrs (map list iattrs))))) + +;; check-attr-arity : stx stx -> IAttr +(define (check-attr-arity stx ctx) + (syntax-case stx () + [attr + (identifier? #'attr) + (make-attr #'attr 0 #f)] + [(attr depth) + (begin (unless (identifier? #'attr) + (raise-syntax-error #f "expected attribute name" ctx #'attr)) + (unless (exact-nonnegative-integer? (syntax-e #'depth)) + (raise-syntax-error #f "expected depth (nonnegative integer)" ctx #'depth)) + (make-attr #'attr (syntax-e #'depth) #f))] + [_ + (raise-syntax-error #f "expected attribute name with optional depth declaration" ctx stx)])) + +;; check-literals-list : stx stx -> (listof den:lit) +;; - txlifts defs of phase expressions +;; - txlifts checks that literals are bound +(define (check-literals-list stx ctx) + (unless (stx-list? stx) + (raise-syntax-error #f "expected literals list" ctx stx)) + (for/list ([x (in-list (stx->list stx))]) + (check-literal-entry x ctx))) + +;; check-literal-entry : stx stx -> den:lit +(define (check-literal-entry stx ctx) + (define (go internal external phase) + (txlift #`(check-literal #,external #,phase #,ctx)) + (let ([external (syntax-property external 'literal (gensym))]) + (make den:lit internal external phase phase))) + (syntax-case stx () + [(internal external #:phase phase) + (and (identifier? #'internal) (identifier? #'external)) + (go #'internal #'external (txlift #'phase))] + [(internal external) + (and (identifier? #'internal) (identifier? #'external)) + (go #'internal #'external #'(syntax-local-phase-level))] + [id + (identifier? #'id) + (go #'id #'id #'(syntax-local-phase-level))] + [_ + (raise-syntax-error #f "expected literal entry" ctx stx)])) + +;; check-datum-literals-list : stx stx -> (listof den:datum-lit) +(define (check-datum-literals-list stx ctx) + (unless (stx-list? stx) + (raise-syntax-error #f "expected datum-literals list" ctx stx)) + (for/list ([x (in-list (stx->list stx))]) + (check-datum-literal-entry x ctx))) + +;; check-datum-literal-entry : stx stx -> den:datum-lit +(define (check-datum-literal-entry stx ctx) + (syntax-case stx () + [(internal external) + (and (identifier? #'internal) (identifier? #'external)) + (make den:datum-lit #'internal (syntax-e #'external))] + [id + (identifier? #'id) + (make den:datum-lit #'id (syntax-e #'id))] + [_ + (raise-syntax-error #f "expected datum-literal entry" ctx stx)])) + +;; Literal sets - Import + +;; check-literal-sets-list : stx stx -> (listof (list id literalset stx stx)) +(define (check-literal-sets-list stx ctx) + (unless (stx-list? stx) + (raise-syntax-error #f "expected literal-set list" ctx stx)) + (for/list ([x (in-list (stx->list stx))]) + (check-literal-set-entry x ctx))) + +;; check-literal-set-entry : stx stx -> (list id literalset stx stx) +(define (check-literal-set-entry stx ctx) + (define (elaborate litset-id lctx phase) + (let ([litset (syntax-local-value/record litset-id literalset?)]) + (unless litset + (raise-syntax-error #f "expected identifier defined as a literal-set" + ctx litset-id)) + (list litset-id litset lctx phase))) + (syntax-case stx () + [(litset . more) + (and (identifier? #'litset)) + (let* ([chunks (parse-keyword-options/eol #'more litset-directive-table + #:no-duplicates? #t + #:context ctx)] + [lctx (options-select-value chunks '#:at #:default #'litset)] + [phase (options-select-value chunks '#:phase + #:default #'(syntax-local-phase-level))]) + (elaborate #'litset lctx (txlift phase)))] + [litset + (identifier? #'litset) + (elaborate #'litset #'litset #'(syntax-local-phase-level))] + [_ + (raise-syntax-error #f "expected literal-set entry" ctx stx)])) + +;; Conventions + +;; returns (listof (cons Conventions (listof syntax))) +(define (check-conventions-list stx ctx) + (unless (stx-list? stx) + (raise-syntax-error #f "expected conventions list" ctx stx)) + (for/list ([x (in-list (stx->list stx))]) + (check-conventions x ctx))) + +;; returns (cons Conventions (listof syntax)) +(define (check-conventions stx ctx) + (define (elaborate conventions-id argu) + (let ([cs (syntax-local-value/record conventions-id conventions?)]) + (unless cs + (raise-syntax-error #f "expected identifier defined as a conventions" + ctx conventions-id)) + (cons cs argu))) + (syntax-case stx () + [(conventions arg ...) + (identifier? #'conventions) + (elaborate #'conventions (parse-argu (syntax->list #'(arg ...))))] + [conventions + (identifier? #'conventions) + (elaborate #'conventions no-arguments)] + [_ + (raise-syntax-error "expected conventions entry" ctx stx)])) + +;; returns (listof (list regexp DeclEntry)) +(define (check-conventions-rules stx ctx) + (unless (stx-list? stx) + (raise-syntax-error #f "expected convention rule list" ctx stx)) + (for/list ([x (in-list (stx->list stx))]) + (check-conventions-rule x ctx))) + +;; returns (list regexp DeclEntry) +(define (check-conventions-rule stx ctx) + (define (check-conventions-pattern x blame) + (cond [(symbol? x) + (regexp (string-append "^" (regexp-quote (symbol->string x)) "$"))] + [(regexp? x) x] + [else + (raise-syntax-error #f "expected identifier convention pattern" + ctx blame)])) + (define (check-sc-expr x rx) + (let ([x (check-stxclass-application x ctx)]) + (make den:class rx (car x) (cdr x)))) + (syntax-case stx () + [(rx sc) + (let ([name-pattern (check-conventions-pattern (syntax-e #'rx) #'rx)]) + (list name-pattern (check-sc-expr #'sc name-pattern)))])) + +(define (check-stxclass-header stx ctx) + (syntax-case stx () + [name + (identifier? #'name) + (list #'name #'() no-arity)] + [(name . formals) + (identifier? #'name) + (list #'name #'formals (parse-kw-formals #'formals #:context ctx))] + [_ (raise-syntax-error #f "expected syntax class header" stx ctx)])) + +(define (check-stxclass-application stx ctx) + ;; Doesn't check "operator" is actually a stxclass + (syntax-case stx () + [op + (identifier? #'op) + (cons #'op no-arguments)] + [(op arg ...) + (identifier? #'op) + (cons #'op (parse-argu (syntax->list #'(arg ...))))] + [_ (raise-syntax-error #f "expected syntax class use" ctx stx)])) + +;; bind clauses +(define (check-bind-clause-list stx ctx) + (unless (stx-list? stx) + (raise-syntax-error #f "expected sequence of bind clauses" ctx stx)) + (for/list ([clause (in-list (stx->list stx))]) + (check-bind-clause clause ctx))) + +(define (check-bind-clause clause ctx) + (syntax-case clause () + [(attr-decl expr) + (action:bind (check-attr-arity #'attr-decl ctx) #'expr)] + [_ (raise-syntax-error #f "expected bind clause" ctx clause)])) + +(define (check-stmt-list stx ctx) + (syntax-case stx () + [(e ...) + (syntax->list #'(e ...))] + [_ + (raise-syntax-error #f "expected list of expressions and definitions" ctx stx)])) + +;; Arguments and Arities + +;; parse-argu : (listof stx) -> Arguments +(define (parse-argu args #:context [ctx (current-syntax-context)]) + (parameterize ((current-syntax-context ctx)) + (define (loop args rpargs rkws rkwargs) + (cond [(null? args) + (arguments (reverse rpargs) (reverse rkws) (reverse rkwargs))] + [(keyword? (syntax-e (car args))) + (let ([kw (syntax-e (car args))] + [rest (cdr args)]) + (cond [(memq kw rkws) + (wrong-syntax (car args) "duplicate keyword")] + [(null? rest) + (wrong-syntax (car args) + "missing argument expression after keyword")] + #| Overzealous, perhaps? + [(keyword? (syntax-e (car rest))) + (wrong-syntax (car rest) "expected expression following keyword")] + |# + [else + (loop (cdr rest) rpargs (cons kw rkws) (cons (car rest) rkwargs))]))] + [else + (loop (cdr args) (cons (car args) rpargs) rkws rkwargs)])) + (loop args null null null))) + +;; parse-kw-formals : stx -> Arity +(define (parse-kw-formals formals #:context [ctx (current-syntax-context)]) + (parameterize ((current-syntax-context ctx)) + (define id-h (make-bound-id-table)) + (define kw-h (make-hasheq)) ;; keyword => 'mandatory or 'optional + (define pos 0) + (define opts 0) + (define (add-id! id) + (when (bound-id-table-ref id-h id #f) + (wrong-syntax id "duplicate formal parameter" )) + (bound-id-table-set! id-h id #t)) + (define (loop formals) + (cond [(and (stx-pair? formals) (keyword? (syntax-e (stx-car formals)))) + (let* ([kw-stx (stx-car formals)] + [kw (syntax-e kw-stx)] + [rest (stx-cdr formals)]) + (cond [(hash-ref kw-h kw #f) + (wrong-syntax kw-stx "duplicate keyword")] + [(stx-null? rest) + (wrong-syntax kw-stx "missing formal parameter after keyword")] + [else + (let-values ([(formal opt?) (parse-formal (stx-car rest))]) + (add-id! formal) + (hash-set! kw-h kw (if opt? 'optional 'mandatory))) + (loop (stx-cdr rest))]))] + [(stx-pair? formals) + (let-values ([(formal opt?) (parse-formal (stx-car formals))]) + (when (and (positive? opts) (not opt?)) + (wrong-syntax (stx-car formals) + "mandatory argument may not follow optional argument")) + (add-id! formal) + (set! pos (add1 pos)) + (when opt? (set! opts (add1 opts))) + (loop (stx-cdr formals)))] + [(identifier? formals) + (add-id! formals) + (finish #t)] + [(stx-null? formals) + (finish #f)] + [else + (wrong-syntax formals "bad argument sequence")])) + (define (finish has-rest?) + (arity (- pos opts) + (if has-rest? +inf.0 pos) + (sort (for/list ([(k v) (in-hash kw-h)] + #:when (eq? v 'mandatory)) + k) + keyword<?) + (sort (hash-map kw-h (lambda (k v) k)) + keyword<?))) + (loop formals))) + +;; parse-formal : stx -> (values id bool) +(define (parse-formal formal) + (syntax-case formal () + [param + (identifier? #'param) + (values #'param #f)] + [(param default) + (identifier? #'param) + (values #'param #t)] + [_ + (wrong-syntax formal + "expected formal parameter with optional default")])) + + +;; Directive tables + +;; common-parse-directive-table +(define common-parse-directive-table + (list (list '#:disable-colon-notation) + (list '#:literals check-literals-list) + (list '#:datum-literals check-datum-literals-list) + (list '#:literal-sets check-literal-sets-list) + (list '#:conventions check-conventions-list) + (list '#:local-conventions check-conventions-rules))) + +;; parse-directive-table +(define parse-directive-table + (list* (list '#:context check-expression) + common-parse-directive-table)) + +;; rhs-directive-table +(define rhs-directive-table + (list* (list '#:description check-expression) + (list '#:transparent) + (list '#:opaque) + (list '#:attributes check-attr-arity-list) + (list '#:auto-nested-attributes) + (list '#:commit) + (list '#:no-delimit-cut) + common-parse-directive-table)) + +;; pattern-directive-table +(define pattern-directive-table + (list (list '#:declare check-identifier check-expression) + (list '#:role check-expression) ;; attached to preceding #:declare + (list '#:fail-when check-expression check-expression) + (list '#:fail-unless check-expression check-expression) + (list '#:when check-expression) + (list '#:with check-expression check-expression) + (list '#:attr check-attr-arity check-expression) + (list '#:and check-expression) + (list '#:post check-expression) + (list '#:do check-stmt-list))) + +;; fail-directive-table +(define fail-directive-table + (list (list '#:when check-expression) + (list '#:unless check-expression))) + +;; describe-option-table +(define describe-option-table + (list (list '#:opaque) + (list '#:role check-expression))) + +;; eh-optional-directive-table +(define eh-optional-directive-table + (list (list '#:too-many check-expression) + (list '#:name check-expression) + (list '#:defaults check-bind-clause-list))) + +;; h-optional-directive-table +(define h-optional-directive-table + (list (list '#:defaults check-bind-clause-list))) + +;; phase-directive-table +(define phase-directive-table + (list (list '#:phase check-expression))) + +;; litset-directive-table +(define litset-directive-table + (cons (list '#:at (lambda (stx ctx) stx)) + phase-directive-table)) + +;; var-pattern-directive-table +(define var-pattern-directive-table + (list (list '#:attr-name-separator check-stx-string) + (list '#:role check-expression))) diff --git a/parse/private/residual.rkt-6-11 b/parse/private/residual.rkt-6-11 new file mode 100644 index 0000000..beafc67 --- /dev/null +++ b/parse/private/residual.rkt-6-11 @@ -0,0 +1,311 @@ +#lang racket/base +(require (for-syntax racket/base) + racket/stxparam + racket/lazy-require + racket/private/promise) + +;; ============================================================ +;; Compile-time + +(require (for-syntax racket/private/sc + syntax/parse/private/residual-ct)) +(provide (for-syntax (all-from-out syntax/parse/private/residual-ct))) + +(begin-for-syntax + ;; == from runtime.rkt + + (provide make-attribute-mapping + attribute-mapping? + attribute-mapping-var + attribute-mapping-name + attribute-mapping-depth + attribute-mapping-syntax?) + + (require (only-in (for-template syntax/parse/private/residual) + make-attribute-mapping + attribute-mapping? + attribute-mapping-var + attribute-mapping-name + attribute-mapping-depth + attribute-mapping-syntax?)) + #;(define-struct attribute-mapping (var name depth syntax?) + #:omit-define-syntaxes + #:property prop:procedure + (lambda (self stx) + (if (attribute-mapping-syntax? self) + #`(#%expression #,(attribute-mapping-var self)) + (let ([source-name + (or (let loop ([p (syntax-property stx 'disappeared-use)]) + (cond [(identifier? p) p] + [(pair? p) (or (loop (car p)) (loop (cdr p)))] + [else #f])) + (attribute-mapping-name self))]) + #`(let ([value #,(attribute-mapping-var self)]) + (if (syntax-list^depth? '#,(attribute-mapping-depth self) value) + value + (check/force-syntax-list^depth '#,(attribute-mapping-depth self) + value + (quote-syntax #,source-name)))))))) + ) + +;; ============================================================ +;; Run-time + +(require "runtime-progress.rkt" + "3d-stx.rkt" + auto-syntax-e + syntax/stx + stxparse-info/current-pvars) + +(provide (all-from-out "runtime-progress.rkt") + + this-syntax + this-role + this-context-syntax + attribute + attribute-binding + stx-list-take + stx-list-drop/cx + datum->syntax/with-clause + check/force-syntax-list^depth + check-literal* + error/null-eh-match + begin-for-syntax/once + + name->too-few/once + name->too-few + name->too-many + normalize-context + syntax-patterns-fail) + +;; == from runtime.rkt + +;; this-syntax +;; Bound to syntax being matched inside of syntax class +(define-syntax-parameter this-syntax + (lambda (stx) + (raise-syntax-error #f "used out of context: not within a syntax class" stx))) + +(define-syntax-parameter this-role + (lambda (stx) + (raise-syntax-error #f "used out of context: not within a syntax class" stx))) + +;; this-context-syntax +;; Bound to (expression that extracts) context syntax (bottom frame in progress) +(define-syntax-parameter this-context-syntax + (lambda (stx) + (raise-syntax-error #f "used out of context: not within a syntax class" stx))) + +(define-syntax (attribute stx) + (syntax-case stx () + [(attribute name) + (identifier? #'name) + (let ([mapping (syntax-local-value #'name (lambda () #f))]) + (unless (syntax-pattern-variable? mapping) + (raise-syntax-error #f "not bound as a pattern variable" stx #'name)) + (let ([var (syntax-mapping-valvar mapping)]) + (let ([attr (syntax-local-value var (lambda () #f))]) + (unless (attribute-mapping? attr) + (raise-syntax-error #f "not bound as an attribute" stx #'name)) + (syntax-property (attribute-mapping-var attr) + 'disappeared-use + (list (syntax-local-introduce #'name))))))])) + +;; (attribute-binding id) +;; mostly for debugging/testing +(define-syntax (attribute-binding stx) + (syntax-case stx () + [(attribute-bound? name) + (identifier? #'name) + (let ([value (syntax-local-value #'name (lambda () #f))]) + (if (syntax-pattern-variable? value) + (let ([value (syntax-local-value (syntax-mapping-valvar value) (lambda () #f))]) + (if (attribute-mapping? value) + #`(quote #,(make-attr (attribute-mapping-name value) + (attribute-mapping-depth value) + (attribute-mapping-syntax? value))) + #'(quote #f))) + #'(quote #f)))])) + +;; stx-list-take : stxish nat -> syntax +(define (stx-list-take stx n) + (datum->syntax #f + (let loop ([stx stx] [n n]) + (if (zero? n) + null + (cons (stx-car stx) + (loop (stx-cdr stx) (sub1 n))))))) + +;; stx-list-drop/cx : stxish stx nat -> (values stxish stx) +(define (stx-list-drop/cx x cx n) + (let loop ([x x] [cx cx] [n n]) + (if (zero? n) + (values x + (if (syntax? x) x cx)) + (loop (stx-cdr x) + (if (syntax? x) x cx) + (sub1 n))))) + +;; 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 sub-depth sub-value) + (attribute-not-syntax-error depth value0 source-id sub-depth sub-value)) + (define (loop depth value) + (cond [(promise? value) + (loop depth (force value))] + [(zero? depth) + (if (syntax? value) value (bad depth value))] + [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 depth value)])) + (loop depth value0)) + +(define (attribute-not-syntax-error depth0 value0 source-id sub-depth sub-value) + (raise-syntax-error #f + (format (string-append "bad attribute value for syntax template" + "\n attribute value: ~e" + "\n expected for attribute: ~a" + "\n sub-value: ~e" + "\n expected for sub-value: ~a") + value0 + (describe-depth depth0) + sub-value + (describe-depth sub-depth)) + source-id)) + +(define (describe-depth depth) + (cond [(zero? depth) "syntax"] + [else (format "list of depth ~s of syntax" depth)])) + +;; 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))))) + +;; datum->syntax/with-clause : any -> syntax +(define (datum->syntax/with-clause x) + (cond [(syntax? x) x] + [(2d-stx? x #:traverse-syntax? #f) + (datum->syntax #f x #f)] + [else + (error 'datum->syntax/with-clause + (string-append + "implicit conversion to 3D syntax\n" + " right-hand side of #:with clause or ~~parse pattern would be 3D syntax\n" + " value: ~e") + x)])) + +;; check-literal* : id phase phase (listof phase) stx -> void +(define (check-literal* id used-phase mod-phase ok-phases/ct-rel ctx) + (unless (or (memv (and used-phase (- used-phase mod-phase)) + ok-phases/ct-rel) + (identifier-binding id used-phase)) + (raise-syntax-error + #f + (format "literal is unbound in phase ~a (phase ~a relative to the enclosing module)" + used-phase + (and used-phase (- used-phase mod-phase))) + ctx id))) + +;; error/null-eh-match : -> (escapes) +(define (error/null-eh-match) + (error 'syntax-parse "an ellipsis-head pattern matched an empty sequence")) + +;; (begin-for-syntax/once expr/phase1 ...) +;; evaluates in pass 2 of module/intdefs expansion +(define-syntax (begin-for-syntax/once stx) + (syntax-case stx () + [(bfs/o e ...) + (cond [(list? (syntax-local-context)) + #`(define-values () + (begin (begin-for-syntax/once e ...) + (values)))] + [else + #'(let-syntax ([m (lambda _ (begin e ...) #'(void))]) + (m))])])) + +;; == parse.rkt + +(define (name->too-few/once name) + (and name (format "missing required occurrence of ~a" name))) + +(define (name->too-few name) + (and name (format "too few occurrences of ~a" name))) + +(define (name->too-many name) + (and name (format "too many occurrences of ~a" name))) + +;; == parse.rkt + +;; normalize-context : Symbol Any Syntax -> (list Symbol/#f Syntax) +(define (normalize-context who ctx stx) + (cond [(syntax? ctx) + (list #f ctx)] + [(symbol? ctx) + (list ctx stx)] + [(eq? ctx #f) + (list #f stx)] + [(and (list? ctx) + (= (length ctx) 2) + (or (symbol? (car ctx)) (eq? #f (car ctx))) + (syntax? (cadr ctx))) + ctx] + [else (error who "bad #:context argument\n expected: ~s\n given: ~e" + '(or/c syntax? symbol? #f (list/c (or/c symbol? #f) syntax?)) + ctx)])) + +;; == parse.rkt + +(lazy-require + ["runtime-report.rkt" + (call-current-failure-handler ctx fs)]) + +;; syntax-patterns-fail : (list Symbol/#f Syntax) -> FailureSet -> (escapes) +(define ((syntax-patterns-fail ctx) fs) + (call-current-failure-handler ctx fs)) + +;; == specialized ellipsis parser +;; returns (values 'ok attr-values) or (values 'fail failure) + +(provide predicate-ellipsis-parser) + +(define (predicate-ellipsis-parser x cx pr es pred? desc rl) + (let ([elems (stx->list x)]) + (if (and elems (list? elems) (andmap pred? elems)) + (values 'ok elems) + (let loop ([x x] [cx cx] [i 0]) + (cond [(syntax? x) + (loop (syntax-e x) x i)] + [(pair? x) + (if (pred? (car x)) + (loop (cdr x) cx (add1 i)) + (let* ([pr (ps-add-cdr pr i)] + [pr (ps-add-car pr)] + [es (es-add-thing pr desc #t rl es)]) + (values 'fail (failure pr es))))] + [else ;; not null, because stx->list failed + (let ([pr (ps-add-cdr pr i)] + #| + ;; Don't extend es! That way we don't get spurious "expected ()" + ;; that *should* have been cancelled out by ineffable pair failures. + |#) + (values 'fail (failure pr es)))]))))) diff --git a/parse/private/runtime-reflect.rkt-6-11 b/parse/private/runtime-reflect.rkt-6-11 new file mode 100644 index 0000000..d561684 --- /dev/null +++ b/parse/private/runtime-reflect.rkt-6-11 @@ -0,0 +1,97 @@ +#lang racket/base +(require stxparse-info/parse/private/residual ;; keep abs. path + (only-in syntax/parse/private/residual-ct ;; keep abs. path + attr-name attr-depth) + syntax/parse/private/kws) +(provide reflect-parser + (struct-out reified) + (struct-out reified-syntax-class) + (struct-out reified-splicing-syntax-class)) + +#| +A Reified is + (reified symbol ParserFunction nat (listof (list symbol nat))) +|# +(require (only-in syntax/parse/private/runtime-reflect + reified + reified? + reified-parser + reified-arity + reified-signature + make-reified + struct:reified + + reified-syntax-class + reified-syntax-class? + make-reified-syntax-class + struct:reified-syntax-class + + reified-splicing-syntax-class + reified-splicing-syntax-class? + make-reified-splicing-syntax-class + struct:reified-splicing-syntax-class)) +#;(define-struct reified-base (name) #:transparent) +#;(define-struct (reified reified-base) (parser arity signature)) +#;(define-struct (reified-syntax-class reified) ()) +#;(define-struct (reified-splicing-syntax-class reified) ()) + +(define (reflect-parser obj e-arity e-attrs splicing?) + ;; e-arity represents single call; min and max are same + (define who (if splicing? 'reflect-splicing-syntax-class 'reflect-syntax-class)) + (if splicing? + (unless (reified-splicing-syntax-class? obj) + (raise-type-error who "reified splicing-syntax-class" obj)) + (unless (reified-syntax-class? obj) + (raise-type-error who "reified syntax-class" obj))) + (check-params who e-arity (reified-arity obj) obj) + (adapt-parser who + (for/list ([a (in-list e-attrs)]) + (list (attr-name a) (attr-depth a))) + (reified-signature obj) + (reified-parser obj) + splicing?)) + +(define (check-params who e-arity r-arity obj) + (let ([e-pos (arity-minpos e-arity)] + [e-kws (arity-minkws e-arity)]) + (check-arity r-arity e-pos e-kws (lambda (msg) (error who "~a" msg))))) + +(define (adapt-parser who esig0 rsig0 parser splicing?) + (if (equal? esig0 rsig0) + parser + (let ([indexes + (let loop ([esig esig0] [rsig rsig0] [index 0]) + (cond [(null? esig) + null] + [(and (pair? rsig) (eq? (caar esig) (caar rsig))) + (unless (= (cadar esig) (cadar rsig)) + (wrong-depth who (car esig) (car rsig))) + (cons index (loop (cdr esig) (cdr rsig) (add1 index)))] + [(and (pair? rsig) + (string>? (symbol->string (caar esig)) + (symbol->string (caar rsig)))) + (loop esig (cdr rsig) (add1 index))] + [else + (error who "reified syntax-class is missing declared attribute `~s'" + (caar esig))]))]) + (define (take-indexes result indexes) + (let loop ([result result] [indexes indexes] [i 0]) + (cond [(null? indexes) null] + [(= (car indexes) i) + (cons (car result) (loop (cdr result) (cdr indexes) (add1 i)))] + [else + (loop (cdr result) indexes (add1 i))]))) + (make-keyword-procedure + (lambda (kws kwargs x cx pr es fh cp rl success . rest) + (keyword-apply parser kws kwargs x cx pr es fh cp rl + (if splicing? + (lambda (fh x cx pr . result) + (apply success fh x cx pr (take-indexes result indexes))) + (lambda (fh . result) + (apply success fh (take-indexes result indexes)))) + rest)))))) + +(define (wrong-depth who a b) + (error who + "reified syntax-class has wrong depth for attribute `~s'; expected ~s, got ~s instead" + (car a) (cadr a) (cadr b))) diff --git a/parse/private/runtime-report.rkt-6-11 b/parse/private/runtime-report.rkt-6-11 new file mode 100644 index 0000000..87429ae --- /dev/null +++ b/parse/private/runtime-report.rkt-6-11 @@ -0,0 +1,784 @@ +#lang racket/base +(require racket/list + racket/format + syntax/stx + racket/struct + syntax/srcloc + syntax/parse/private/minimatch + stxparse-info/parse/private/residual + syntax/parse/private/kws) +(provide call-current-failure-handler + current-failure-handler + invert-failure + maximal-failures + invert-ps + ps->stx+index) + +#| +TODO: given (expect:thing _ D _ R) and (expect:thing _ D _ #f), + simplify to (expect:thing _ D _ #f) + thus, "expected D" rather than "expected D or D for R" (?) +|# + +#| +Note: there is a cyclic dependence between residual.rkt and this module, +broken by a lazy-require of this module into residual.rkt +|# + +(define (call-current-failure-handler ctx fs) + (call-with-values (lambda () ((current-failure-handler) ctx fs)) + (lambda vals + (error 'current-failure-handler + "current-failure-handler: did not escape, produced ~e" + (case (length vals) + ((1) (car vals)) + (else (cons 'values vals))))))) + +(define (default-failure-handler ctx fs) + (handle-failureset ctx fs)) + +(define current-failure-handler + (make-parameter default-failure-handler)) + + +;; ============================================================ +;; Processing failure sets + +#| +We use progress to select the maximal failures and determine the syntax +they're complaining about. After that, we no longer care about progress. + +Old versions of syntax-parse (through 6.4) grouped failures into +progress-equivalence-classes and generated reports by class, but only showed +one report. New syntax-parse just mixes all maximal failures together and +deals with the fact that they might not be talking about the same terms. +|# + +;; handle-failureset : (list Symbol/#f Syntax) FailureSet -> escapes +(define (handle-failureset ctx fs) + (define inverted-fs (map invert-failure (reverse (flatten fs)))) + (define maximal-classes (maximal-failures inverted-fs)) + (define ess (map failure-expectstack (append* maximal-classes))) + (define report (report/sync-shared ess)) + ;; Hack: alternative to new (primitive) phase-crossing exn type is to store + ;; extra information in exn continuation marks. Currently for debugging only. + (with-continuation-mark 'syntax-parse-error + (hasheq 'raw-failures fs + 'maximal maximal-classes) + (error/report ctx report))) + +;; An RFailure is (failure IPS RExpectList) + +;; invert-failure : Failure -> RFailure +(define (invert-failure f) + (match f + [(failure ps es) + (failure (invert-ps ps) (invert-expectstack es (ps->stx+index ps)))])) + +;; A Report is (report String (Listof String) Syntax/#f Syntax/#f) +(define-struct report (message context stx within-stx) #:prefab) + + +;; ============================================================ +;; Progress + +;; maximal-failures : (listof InvFailure) -> (listof (listof InvFailure)) +(define (maximal-failures fs) + (maximal/progress + (for/list ([f (in-list fs)]) + (cons (failure-progress f) f)))) + +#| +Progress ordering +----------------- + +Nearly a lexicographic generalization of partial order on frames. + (( CAR < CDR ) || stx ) < POST ) + - stx incomparable except with self + +But ORD prefixes are sorted out (and discarded) before comparison with +rest of progress. Like post, ord comparable only w/in same group: + - (ord g n1) < (ord g n2) if n1 < n2 + - (ord g1 n1) || (ord g2 n2) when g1 != g2 + + +Progress equality +----------------- + +If ps1 = ps2 then both must "blame" the same term, +ie (ps->stx+index ps1) = (ps->stx+index ps2). +|# + +;; An Inverted PS (IPS) is a PS inverted for easy comparison. +;; An IPS may not contain any 'opaque frames. + +;; invert-ps : PS -> IPS +;; Reverse and truncate at earliest 'opaque frame. +(define (invert-ps ps) + (reverse (ps-truncate-opaque ps))) + +;; ps-truncate-opaque : PS -> PS +;; Returns maximal tail with no 'opaque frame. +(define (ps-truncate-opaque ps) + (let loop ([ps ps] [acc ps]) + ;; acc is the biggest tail that has not been seen to contain 'opaque + (cond [(null? ps) acc] + [(eq? (car ps) 'opaque) + (loop (cdr ps) (cdr ps))] + [else (loop (cdr ps) acc)]))) + +;; maximal/progress : (listof (cons IPS A)) -> (listof (listof A)) +;; Eliminates As with non-maximal progress, then groups As into +;; equivalence classes according to progress. +(define (maximal/progress items) + (cond [(null? items) + null] + [(null? (cdr items)) + (list (list (cdr (car items))))] + [else + (let loop ([items items] [non-ORD-items null]) + (define-values (ORD non-ORD) + (partition (lambda (item) (ord? (item-first-prf item))) items)) + (cond [(pair? ORD) + (loop (maximal-prf1/ord ORD) (append non-ORD non-ORD-items))] + [else + (maximal/prf1 (append non-ORD non-ORD-items))]))])) + +;; maximal/prf1 : (Listof (Cons IPS A) -> (Listof (Listof A)) +(define (maximal/prf1 items) + (define-values (POST rest1) + (partition (lambda (item) (eq? 'post (item-first-prf item))) items)) + (cond [(pair? POST) + (maximal/progress (map item-pop-prf POST))] + [else + (define-values (STX rest2) + (partition (lambda (item) (syntax? (item-first-prf item))) rest1)) + (define-values (CDR rest3) + (partition (lambda (item) (exact-integer? (item-first-prf item))) rest2)) + (define-values (CAR rest4) + (partition (lambda (item) (eq? 'car (item-first-prf item))) rest3)) + (define-values (NULL rest5) + (partition (lambda (item) (eq? '#f (item-first-prf item))) rest4)) + (unless (null? rest5) + (error 'syntax-parse "INTERNAL ERROR: bad progress: ~e\n" rest5)) + (cond [(pair? CDR) + (define leastCDR (apply min (map item-first-prf CDR))) + (append + (maximal/stx STX) + (maximal/progress (map (lambda (item) (item-pop-prf-ncdrs item leastCDR)) CDR)))] + [(pair? CAR) + (append + (maximal/stx STX) + (maximal/progress (map item-pop-prf CAR)))] + [(pair? STX) + (maximal/stx STX)] + [(pair? NULL) + (list (map cdr NULL))] + [else null])])) + +;; maximal-prf1/ord : (NEListof (Cons IPS A)) -> (NEListof (Cons IPS A)) +;; PRE: each item has ORD first frame +;; Keep only maximal by first frame and pop first frame from each item. +(define (maximal-prf1/ord items) + ;; groups : (NEListof (NEListof (cons A IPS))) + (define groups (group-by (lambda (item) (ord-group (item-first-prf item))) items)) + (append* + (for/list ([group (in-list groups)]) + (define group* (filter-max group (lambda (item) (ord-index (item-first-prf item))))) + (map item-pop-prf group*)))) + +;; maximal/stx : (NEListof (cons IPS A)) -> (NEListof (NEListof A)) +;; PRE: Each IPS starts with a stx frame. +(define (maximal/stx items) + ;; groups : (Listof (Listof (cons IPS A))) + (define groups (group-by item-first-prf items)) + (append* + (for/list ([group (in-list groups)]) + (maximal/progress (map item-pop-prf group))))) + +;; filter-max : (Listof X) (X -> Nat) -> (Listof X) +(define (filter-max xs x->nat) + (let loop ([xs xs] [nmax -inf.0] [r-keep null]) + (cond [(null? xs) + (reverse r-keep)] + [else + (define n0 (x->nat (car xs))) + (cond [(> n0 nmax) + (loop (cdr xs) n0 (list (car xs)))] + [(= n0 nmax) + (loop (cdr xs) nmax (cons (car xs) r-keep))] + [else + (loop (cdr xs) nmax r-keep)])]))) + +;; item-first-prf : (cons IPS A) -> prframe/#f +(define (item-first-prf item) + (define ips (car item)) + (and (pair? ips) (car ips))) + +;; item-split-ord : (cons IPS A) -> (cons IPS (cons IPS A)) +(define (item-split-ord item) + (define ips (car item)) + (define a (cdr item)) + (define-values (rest-ips r-ord) + (let loop ([ips ips] [r-ord null]) + (cond [(and (pair? ips) (ord? (car ips))) + (loop (cdr ips) (cons (car ips) r-ord))] + [else (values ips r-ord)]))) + (list* (reverse r-ord) rest-ips a)) + +;; item-pop-prf : (cons IPS A) -> (cons IPS A) +(define (item-pop-prf item) + (let ([ips (car item)] + [a (cdr item)]) + (cons (cdr ips) a))) + +;; item-pop-prf-ncdrs : (cons IPS A) -> (cons IPS A) +;; Assumes first frame is nat > ncdrs. +(define (item-pop-prf-ncdrs item ncdrs) + (let ([ips (car item)] + [a (cdr item)]) + (cond [(= (car ips) ncdrs) (cons (cdr ips) a)] + [else (cons (cons (- (car ips) ncdrs) (cdr ips)) a)]))) + +;; ps->stx+index : Progress -> (cons Syntax Nat) +;; Gets the innermost stx that should have a real srcloc, and the offset +;; (number of cdrs) within that where the progress ends. +(define (ps->stx+index ps) + (define (interp ps) + (match ps + [(cons (? syntax? stx) _) stx] + [(cons 'car parent) + (let* ([d (interp parent)] + [d (if (syntax? d) (syntax-e d) d)]) + (cond [(pair? d) (car d)] + [(vector? d) (vector->list d)] + [(box? d) (unbox d)] + [(prefab-struct-key d) (struct->list d)] + [else (error 'ps->stx+index "INTERNAL ERROR: unexpected: ~e" d)]))] + [(cons (? exact-positive-integer? n) parent) + (for/fold ([stx (interp parent)]) ([i (in-range n)]) + (stx-cdr stx))] + [(cons (? ord?) parent) + (interp parent)] + [(cons 'post parent) + (interp parent)])) + (let ([ps (ps-truncate-opaque ps)]) + (match ps + [(cons (? syntax? stx) _) + (cons stx 0)] + [(cons 'car parent) + (cons (interp ps) 0)] + [(cons (? exact-positive-integer? n) parent) + (cons (interp parent) n)] + [(cons (? ord?) parent) + (ps->stx+index parent)] + [(cons 'post parent) + (ps->stx+index parent)]))) + + +;; ============================================================ +;; Expectation simplification + +;; normalize-expectstack : ExpectStack StxIdx -> ExpectList +;; Converts to list, converts expect:thing term rep, and truncates +;; expectstack after opaque (ie, transparent=#f) frames. +(define (normalize-expectstack es stx+index [truncate-opaque? #t]) + (reverse (invert-expectstack es stx+index truncate-opaque?))) + +;; invert-expectstack : ExpectStack StxIdx -> RExpectList +;; Converts to reversed list, converts expect:thing term rep, +;; and truncates expectstack after opaque (ie, transparent=#f) frames. +(define (invert-expectstack es stx+index [truncate-opaque? #t]) + (let loop ([es es] [acc null]) + (match es + ['#f acc] + ['#t acc] + [(expect:thing ps desc tr? role rest-es) + (cond [(and truncate-opaque? (not tr?)) + (loop rest-es (cons (expect:thing #f desc #t role (ps->stx+index ps)) null))] + [else + (loop rest-es (cons (expect:thing #f desc tr? role (ps->stx+index ps)) acc))])] + [(expect:message message rest-es) + (loop rest-es (cons (expect:message message stx+index) acc))] + [(expect:atom atom rest-es) + (loop rest-es (cons (expect:atom atom stx+index) acc))] + [(expect:literal literal rest-es) + (loop rest-es (cons (expect:literal literal stx+index) acc))] + [(expect:proper-pair first-desc rest-es) + (loop rest-es (cons (expect:proper-pair first-desc stx+index) acc))]))) + +;; expect->stxidx : Expect -> StxIdx +(define (expect->stxidx e) + (cond [(expect:thing? e) (expect:thing-next e)] + [(expect:message? e) (expect:message-next e)] + [(expect:atom? e) (expect:atom-next e)] + [(expect:literal? e) (expect:literal-next e)] + [(expect:proper-pair? e) (expect:proper-pair-next e)] + [(expect:disj? e) (expect:disj-next e)])) + +#| Simplification + +A list of ExpectLists represents a tree, with shared tails meaning shared +branches of the tree. We need a "reasonable" way to simplify it to a list to +show to the user. Here we develop "reasonable" by example. (It would be nice, +of course, to also have some way of exploring the full failure trees.) + +Notation: [A B X] means an ExpectList with class/description A at root and X +at leaf. If the term sequences differ, write [t1:A ...] etc. + +Options: + (o) = "old behavior (through 6.4)" + (f) = "first divergence" + (s) = "sync on shared" + +Case 1: [A B X], [A B Y] + + This is nearly the ideal situation: report as + + expected X or Y, while parsing B, while parsing A + +Case 2: [A X], [A] + + For example, matching #'1 as (~describe A (x:id ...)) yields [A], [A '()], + but we don't want to see "expected ()". + + So simplify to [A]---that is, drop X. + +But there are other cases that are more problematic. + +Case 3: [t1:A t2:B t3:X], [t1:A t2:C t3:Y] + + Could report as: + (o) expected X for t3, while parsing t2 as B, while parsing t1 as A (also other errors) + (f) expected B or C for t2, while parsing t1 as A + (x) expected X or Y for t3, while parsing t2 as B or C, while parsing t1 as A + + (o) is not good + (b) loses the most specific error information + (x) implies spurious contexts (eg, X while parsing C) + + I like (b) best for this situation, but ... + +Case 4: [t1:A t2:B t4:X], [t1:A t3:C t4:Y] + + Could report as: + (f') expected B or C, while parsing t1 as A + (s) expected X or Y for t4, while ..., while parsing t1 as A + (f) expected A for t1 + + (f') is problematic, since terms are different! + (s) okay, but nothing good to put in that ... space + (f) loses a lot of information + +Case 5: [t1:A t2:B t3:X], [t1:A t4:C t5:Y] + + Only feasible choice (no other sync points): + (f,s) expected A for t1 + +Case 6: [t1:A _ t2:B t3:X], [t1:A _ t2:C t3:Y] + + Could report as: + (s') expected X or Y for t3, while parsing t2 as B or C, while ..., while parsing t1 as A + (s) expected X or Y for t3, while ..., while parsing t1 as A + + (s') again implies spurious contexts, bad + (s) okay + +Case 7: [_ t2:B t3:C _], [_ t3:C t2:B _] + + Same frames show up in different orders. (Can this really happen? Probably, + with very weird uses of ~parse.) + +-- + +This suggests the following new algorithm based on (s): +- Step 1: emit an intermediate "unified" expectstack (extended with "..." markers) + - make a list (in order) of frames shared by all expectstacks + - emit those frames with "..." markers if (sometimes) unshared stuff between + - continue processing with the tails after the last shared frame: + - find the last term shared by all expectstacks (if any) + - find the last frame for that term for each expectstack + - combine in expect:disj and emit +- Step 2: + - remove trailing and collapse adjacent "..." markers + +|# + +;; report* : (NEListof RExpectList) ((NEListof (NEListof RExpectList)) -> ExpectList) +;; -> Report +(define (report* ess handle-divergence) + (define es ;; ExpectList + (let loop ([ess ess] [acc null]) + (cond [(ormap null? ess) acc] + [else + (define groups (group-by car ess)) + (cond [(singleton? groups) + (define group (car groups)) + (define frame (car (car group))) + (loop (map cdr group) (cons frame acc))] + [else ;; found point of divergence + (append (handle-divergence groups) acc)])]))) + (define stx+index (if (pair? es) (expect->stxidx (car es)) (cons #f 0))) + (report/expectstack (clean-up es) (car stx+index) (cdr stx+index))) + +;; clean-up : ExpectList -> ExpectList +;; Remove leading and collapse adjacent '... markers +(define (clean-up es) + (if (and (pair? es) (eq? (car es) '...)) + (clean-up (cdr es)) + (let loop ([es es]) + (cond [(null? es) null] + [(eq? (car es) '...) + (cons '... (clean-up es))] + [else (cons (car es) (loop (cdr es)))])))) + +;; -- + +;; report/first-divergence : (NEListof RExpectList) -> Report +;; Generate a single report, using frames from root to first divergence. +(define (report/first-divergence ess) + (report* ess handle-divergence/first)) + +;; handle-divergence/first : (NEListof (NEListof RExpectList)) -> ExpectList +(define (handle-divergence/first ess-groups) + (define representative-ess (map car ess-groups)) + (define first-frames (map car representative-ess)) + ;; Do all of the first frames talk about the same term? + (cond [(all-equal? (map expect->stxidx first-frames)) + (list (expect:disj first-frames #f))] + [else null])) + +;; -- + +;; report/sync-shared : (NEListof RExpectList) -> Report +;; Generate a single report, syncing on shared frames (and later, terms). +(define (report/sync-shared ess) + (report* ess handle-divergence/sync-shared)) + +;; handle-divergence/sync-shared : (NEListof (NEListof RExpectList)) -> ExpectList +(define (handle-divergence/sync-shared ess-groups) + (define ess (append* ess-groups)) ;; (NEListof RExpectList) + (define shared-frames (get-shared ess values)) + ;; rsegs : (NEListof (Rev2n+1-Listof RExpectList)) + (define rsegs (for/list ([es (in-list ess)]) (rsplit es values shared-frames))) + (define final-seg (map car rsegs)) ;; (NEListof RExpectList), no common frames + (define ctx-rsegs (transpose (map cdr rsegs))) ;; (Rev2n-Listof (NEListof RExpectList)) + (append (hd/sync-shared/final final-seg) + (hd/sync-shared/ctx ctx-rsegs))) + +;; hd/sync-shared/final : (NEListof RExpectList) -> ExpectList +;; PRE: ess has no shared frames, but may have shared terms. +(define (hd/sync-shared/final ess0) + (define ess (remove-extensions ess0)) + (define shared-terms (get-shared ess expect->stxidx)) + (cond [(null? shared-terms) null] + [else + ;; split at the last shared term + (define rsegs ;; (NEListof (3-Listof RExpectList)) + (for/list ([es (in-list ess)]) + (rsplit es expect->stxidx (list (last shared-terms))))) + ;; only care about the got segment and pre, not post + (define last-term-ess ;; (NEListof RExpectList) + (map cadr rsegs)) + (define pre-term-ess ;; (NEListof RExpectList) + (map caddr rsegs)) + ;; last is most specific + (append + (list (expect:disj (remove-duplicates (reverse (map last last-term-ess))) + (last shared-terms))) + (if (ormap pair? pre-term-ess) '(...) '()))])) + +;; hd/sync-shared/ctx : (Rev2n-Listof (NEListof RExpectList)) -> ExpectList +;; In [gotN preN ... got1 pre1] order, where 1 is root-most, N is leaf-most. +;; We want leaf-most-first, so just process naturally. +(define (hd/sync-shared/ctx rsegs) + (let loop ([rsegs rsegs]) + (cond [(null? rsegs) null] + [(null? (cdr rsegs)) (error 'syntax-parse "INTERNAL ERROR: bad segments")] + [else (append + ;; shared frame: possible for duplicate ctx frames, but unlikely + (let ([ess (car rsegs)]) (list (car (car ess)))) + ;; inter frames: + (let ([ess (cadr rsegs)]) (if (ormap pair? ess) '(...) '())) + ;; recur + (loop (cddr rsegs)))]))) + +;; transpose : (Listof (Listof X)) -> (Listof (Listof X)) +(define (transpose xss) + (cond [(ormap null? xss) null] + [else (cons (map car xss) (transpose (map cdr xss)))])) + +;; get-shared : (Listof (Listof X)) (X -> Y) -> (Listof Y) +;; Return a list of Ys s.t. occur in order in (map of) each xs in xss. +(define (get-shared xss get-y) + (cond [(null? xss) null] + [else + (define yhs ;; (Listof (Hash Y => Nat)) + (for/list ([xs (in-list xss)]) + (for/hash ([x (in-list xs)] [i (in-naturals 1)]) + (values (get-y x) i)))) + (remove-duplicates + (let loop ([xs (car xss)] [last (for/list ([xs (in-list xss)]) 0)]) + ;; last is list of indexes of last accepted y; only accept next if occurs + ;; after last in every sequence (see Case 7 above) + (cond [(null? xs) null] + [else + (define y (get-y (car xs))) + (define curr (for/list ([yh (in-list yhs)]) (hash-ref yh y -1))) + (cond [(andmap > curr last) + (cons y (loop (cdr xs) curr))] + [else (loop (cdr xs) last)])])))])) + +;; rsplit : (Listof X) (X -> Y) (Listof Y) -> (Listof (Listof X)) +;; Given [y1 ... yN], splits xs into [rest gotN preN ... got1 pre1]. +;; Thus the result has 2N+1 elements. The sublists are in original order. +(define (rsplit xs get-y ys) + (define (loop xs ys segsacc) + (cond [(null? ys) (cons xs segsacc)] + [else (pre-loop xs ys segsacc null)])) + (define (pre-loop xs ys segsacc preacc) + (cond [(and (pair? xs) (equal? (get-y (car xs)) (car ys))) + (got-loop (cdr xs) ys segsacc preacc (list (car xs)))] + [else + (pre-loop (cdr xs) ys segsacc (cons (car xs) preacc))])) + (define (got-loop xs ys segsacc preacc gotacc) + (cond [(and (pair? xs) (equal? (get-y (car xs)) (car ys))) + (got-loop (cdr xs) ys segsacc preacc (cons (car xs) gotacc))] + [else + (loop xs (cdr ys) (list* (reverse gotacc) (reverse preacc) segsacc))])) + (loop xs ys null)) + +;; singleton? : list -> boolean +(define (singleton? x) (and (pair? x) (null? (cdr x)))) + +;; remove-extensions : (Listof (Listof X)) -> (Listof (Listof X)) +;; Remove any element that is an extension of another. +(define (remove-extensions xss) + (cond [(null? xss) null] + [else + (let loop ([xss xss]) + (cond [(singleton? xss) xss] + [(ormap null? xss) (list null)] + [else + (define groups (group-by car xss)) + (append* + (for/list ([group (in-list groups)]) + (define group* (loop (map cdr group))) + (map (lambda (x) (cons (caar group) x)) group*)))]))])) + +;; all-equal? : (Listof Any) -> Boolean +(define (all-equal? xs) (for/and ([x (in-list xs)]) (equal? x (car xs)))) + + +;; ============================================================ +;; Reporting + +;; report/expectstack : ExpectList Syntax Nat -> Report +(define (report/expectstack es stx index) + (define frame-expect (and (pair? es) (car es))) + (define context-frames (if (pair? es) (cdr es) null)) + (define context (append* (map context-prose-for-expect context-frames))) + (cond [(not frame-expect) + (report "bad syntax" context #f #f)] + [else + (define-values (x cx) (stx-list-drop/cx stx stx index)) + (define frame-stx (datum->syntax cx x cx)) + (define within-stx (if (syntax? x) #f cx)) + (cond [(and (match frame-expect [(expect:atom '() _) #t] [_ #f]) + (stx-pair? frame-stx)) + (report "unexpected term" context (stx-car frame-stx) #f)] + [(expect:disj? frame-expect) + (report (prose-for-expects (expect:disj-expects frame-expect)) + context frame-stx within-stx)] + [else + (report (prose-for-expects (list frame-expect)) + context frame-stx within-stx)])])) + +;; prose-for-expects : (listof Expect) -> string +(define (prose-for-expects expects) + (define msgs (filter expect:message? expects)) + (define things (filter expect:thing? expects)) + (define literal (filter expect:literal? expects)) + (define atom/symbol + (filter (lambda (e) (and (expect:atom? e) (symbol? (expect:atom-atom e)))) expects)) + (define atom/nonsym + (filter (lambda (e) (and (expect:atom? e) (not (symbol? (expect:atom-atom e))))) expects)) + (define proper-pairs (filter expect:proper-pair? expects)) + (join-sep + (append (map prose-for-expect (append msgs things)) + (prose-for-expects/literals literal "identifiers") + (prose-for-expects/literals atom/symbol "literal symbols") + (prose-for-expects/literals atom/nonsym "literals") + (prose-for-expects/pairs proper-pairs)) + ";" "or")) + +(define (prose-for-expects/literals expects whats) + (cond [(null? expects) null] + [(singleton? expects) (map prose-for-expect expects)] + [else + (define (prose e) + (match e + [(expect:atom (? symbol? atom) _) + (format "`~s'" atom)] + [(expect:atom atom _) + (format "~s" atom)] + [(expect:literal literal _) + (format "`~s'" (syntax-e literal))])) + (list (string-append "expected one of these " whats ": " + (join-sep (map prose expects) "," "or")))])) + +(define (prose-for-expects/pairs expects) + (if (pair? expects) (list (prose-for-proper-pair-expects expects)) null)) + +;; prose-for-expect : Expect -> string +(define (prose-for-expect e) + (match e + [(expect:thing _ description transparent? role _) + (if role + (format "expected ~a for ~a" description role) + (format "expected ~a" description))] + [(expect:atom (? symbol? atom) _) + (format "expected the literal symbol `~s'" atom)] + [(expect:atom atom _) + (format "expected the literal ~s" atom)] + [(expect:literal literal _) + (format "expected the identifier `~s'" (syntax-e literal))] + [(expect:message message _) + message] + [(expect:proper-pair '#f _) + "expected more terms"])) + +;; prose-for-proper-pair-expects : (listof expect:proper-pair) -> string +(define (prose-for-proper-pair-expects es) + (define descs (remove-duplicates (map expect:proper-pair-first-desc es))) + (cond [(for/or ([desc descs]) (equal? desc #f)) + ;; FIXME: better way to indicate unknown ??? + "expected more terms"] + [else + (format "expected more terms starting with ~a" + (join-sep (map prose-for-first-desc descs) + "," "or"))])) + +;; prose-for-first-desc : FirstDesc -> string +(define (prose-for-first-desc desc) + (match desc + [(? string?) desc] + [(list 'any) "any term"] ;; FIXME: maybe should cancel out other descs ??? + [(list 'literal id) (format "the identifier `~s'" id)] + [(list 'datum (? symbol? s)) (format "the literal symbol `~s'" s)] + [(list 'datum d) (format "the literal ~s" d)])) + +;; context-prose-for-expect : (U '... expect:thing) -> (listof string) +(define (context-prose-for-expect e) + (match e + ['... + (list "while parsing different things...")] + [(expect:thing '#f description transparent? role stx+index) + (let ([stx (stx+index->stx stx+index)]) + (cons (~a "while parsing " description + (if role (~a " for " role) "")) + (if (error-print-source-location) + (list (~a " term: " + (~s (syntax->datum stx) + #:limit-marker "..." + #:max-width 50)) + (~a " location: " + (or (source-location->string stx) "not available"))) + null)))])) + +(define (stx+index->stx stx+index) + (let*-values ([(stx) (car stx+index)] + [(index) (cdr stx+index)] + [(x cx) (stx-list-drop/cx stx stx index)]) + (datum->syntax cx x cx))) + + +;; ============================================================ +;; Raise exception + +(define (error/report ctx report) + (let* ([message (report-message report)] + [context (report-context report)] + [stx (cadr ctx)] + [who (or (car ctx) (infer-who stx))] + [sub-stx (report-stx report)] + [within-stx (report-within-stx report)] + [message + (format "~a: ~a~a~a~a~a" + who message + (format-if "at" (stx-if-loc sub-stx)) + (format-if "within" (stx-if-loc within-stx)) + (format-if "in" (stx-if-loc stx)) + (if (null? context) + "" + (apply string-append + "\n parsing context: " + (for/list ([c (in-list context)]) + (format "\n ~a" c)))))] + [message + (if (error-print-source-location) + (let ([source-stx (or stx sub-stx within-stx)]) + (string-append (source-location->prefix source-stx) message)) + message)]) + (raise + (exn:fail:syntax message (current-continuation-marks) + (map syntax-taint + (cond [within-stx (list within-stx)] + [sub-stx (list sub-stx)] + [stx (list stx)] + [else null])))))) + +(define (format-if prefix val) + (if val + (format "\n ~a: ~a" prefix val) + "")) + +(define (stx-if-loc stx) + (and (syntax? stx) + (error-print-source-location) + (format "~.s" (syntax->datum stx)))) + +(define (infer-who stx) + (let* ([maybe-id (if (stx-pair? stx) (stx-car stx) stx)]) + (if (identifier? maybe-id) (syntax-e maybe-id) '?))) + +(define (comma-list items) + (join-sep items "," "or")) + +(define (improper-stx->list stx) + (syntax-case stx () + [(a . b) (cons #'a (improper-stx->list #'b))] + [() null] + [rest (list #'rest)])) + + +;; ============================================================ +;; Debugging + +(provide failureset->sexpr + failure->sexpr + expectstack->sexpr + expect->sexpr) + +(define (failureset->sexpr fs) + (let ([fs (flatten fs)]) + (case (length fs) + ((1) (failure->sexpr (car fs))) + (else `(union ,@(map failure->sexpr fs)))))) + +(define (failure->sexpr f) + (match f + [(failure progress expectstack) + `(failure ,(progress->sexpr progress) + #:expected ,(expectstack->sexpr expectstack))])) + +(define (expectstack->sexpr es) + (map expect->sexpr es)) + +(define (expect->sexpr e) e) + +(define (progress->sexpr ps) + (for/list ([pf (in-list ps)]) + (match pf + [(? syntax? stx) 'stx] + [_ pf]))) diff --git a/parse/private/runtime.rkt-6-11 b/parse/private/runtime.rkt-6-11 new file mode 100644 index 0000000..7b6cb19 --- /dev/null +++ b/parse/private/runtime.rkt-6-11 @@ -0,0 +1,224 @@ +#lang racket/base +(require racket/stxparam + stxparse-info/parse/private/residual ;; keep abs. path + stxparse-info/current-pvars + (for-syntax racket/base + racket/list + syntax/kerncase + syntax/strip-context + racket/private/sc + auto-syntax-e/utils + racket/syntax + syntax/parse/private/rep-data)) + +(provide with + fail-handler + cut-prompt + wrap-user-code + + fail + try + + let-attributes + let-attributes* + let/unpack + + defattrs/unpack + + check-literal + no-shadow + curried-stxclass-parser + app-argu) + +#| +TODO: rename file + +This file contains "runtime" (ie, phase 0) auxiliary *macros* used in +expansion of syntax-parse etc. This file must not contain any +reference that persists in a compiled program; those must go in +residual.rkt. +|# + +;; == with == + +(define-syntax (with stx) + (syntax-case stx () + [(with ([stxparam expr] ...) . body) + (with-syntax ([(var ...) (generate-temporaries #'(stxparam ...))]) + (syntax/loc stx + (let ([var expr] ...) + (syntax-parameterize ((stxparam (make-rename-transformer (quote-syntax var))) + ...) + . body))))])) + +;; == Control information == + +(define-syntax-parameter fail-handler + (lambda (stx) + (wrong-syntax stx "internal error: fail-handler used out of context"))) +(define-syntax-parameter cut-prompt + (lambda (stx) + (wrong-syntax stx "internal error: cut-prompt used out of context"))) + +(define-syntax-rule (wrap-user-code e) + (with ([fail-handler #f] + [cut-prompt #t]) + e)) + +(define-syntax-rule (fail fs) + (fail-handler fs)) + +(define-syntax (try stx) + (syntax-case stx () + [(try e0 e ...) + (with-syntax ([(re ...) (reverse (syntax->list #'(e ...)))]) + (with-syntax ([(fh ...) (generate-temporaries #'(re ...))]) + (with-syntax ([(next-fh ...) (drop-right (syntax->list #'(fail-handler fh ...)) 1)] + [(last-fh) (take-right (syntax->list #'(fail-handler fh ...)) 1)]) + #'(let* ([fh (lambda (fs1) + (with ([fail-handler + (lambda (fs2) + (next-fh (cons fs1 fs2)))]) + re))] + ...) + (with ([fail-handler last-fh]) + e0)))))])) + +;; == Attributes + +(define-for-syntax (parse-attr x) + (syntax-case x () + [#s(attr name depth syntax?) #'(name depth syntax?)])) + +(define-syntax (let-attributes stx) + (syntax-case stx () + [(let-attributes ([a value] ...) . body) + (with-syntax ([((name depth syntax?) ...) + (map parse-attr (syntax->list #'(a ...)))]) + (with-syntax ([(vtmp ...) (generate-temporaries #'(name ...))] + [(stmp ...) (generate-temporaries #'(name ...))]) + #'(letrec-syntaxes+values + ([(stmp) (make-attribute-mapping (quote-syntax vtmp) + 'name 'depth 'syntax?)] ...) + ([(vtmp) value] ...) + (letrec-syntaxes+values + ([(name) (make-auto-pvar 'depth (quote-syntax stmp))] ...) + () + (with-pvars (name ...) + . body)))))])) + +;; (let-attributes* (([id num] ...) (expr ...)) expr) : expr +;; Special case: empty attrs need not match number of value exprs. +(define-syntax let-attributes* + (syntax-rules () + [(la* (() _) . body) + (let () . body)] + [(la* ((a ...) (val ...)) . body) + (let-attributes ([a val] ...) . body)])) + +;; (let/unpack (([id num] ...) expr) expr) : expr +;; Special case: empty attrs need not match packed length +(define-syntax (let/unpack stx) + (syntax-case stx () + [(let/unpack (() packed) body) + #'body] + [(let/unpack ((a ...) packed) body) + (with-syntax ([(tmp ...) (generate-temporaries #'(a ...))]) + #'(let-values ([(tmp ...) (apply values packed)]) + (let-attributes ([a tmp] ...) body)))])) + +(define-syntax (defattrs/unpack stx) + (syntax-case stx () + [(defattrs (a ...) packed) + (with-syntax ([((name depth syntax?) ...) + (map parse-attr (syntax->list #'(a ...)))]) + (with-syntax ([(vtmp ...) (generate-temporaries #'(name ...))] + [(stmp ...) (generate-temporaries #'(name ...))]) + #'(begin (define-values (vtmp ...) (apply values packed)) + (define-syntax stmp + (make-attribute-mapping (quote-syntax vtmp) + 'name 'depth 'syntax?)) + ... + (define-syntax name (make-auto-pvar 'depth (quote-syntax stmp))) + ... + (define-pvars name ...))))])) + +(define-syntax-rule (phase-of-enclosing-module) + (variable-reference->module-base-phase + (#%variable-reference))) + +;; (check-literal id phase-level-expr ctx) -> void +(define-syntax (check-literal stx) + (syntax-case stx () + [(check-literal id used-phase-expr ctx) + (let* ([ok-phases/ct-rel + ;; id is bound at each of ok-phases/ct-rel + ;; (phase relative to the compilation of the module in which the + ;; 'syntax-parse' (or related) form occurs) + (filter (lambda (p) (identifier-binding #'id p)) '(0 1 -1 #f))]) + ;; so we can avoid run-time call to identifier-binding if + ;; (+ (phase-of-enclosing-module) ok-phase/ct-rel) = used-phase + (with-syntax ([ok-phases/ct-rel ok-phases/ct-rel]) + #`(check-literal* (quote-syntax id) + used-phase-expr + (phase-of-enclosing-module) + 'ok-phases/ct-rel + ;; If context is not stripped, racket complains about + ;; being unable to restore bindings for compiled code; + ;; and all we want is the srcloc, etc. + (quote-syntax #,(strip-context #'ctx)))))])) + +;; ==== + +(begin-for-syntax + (define (check-shadow def) + (syntax-case def () + [(_def (x ...) . _) + (parameterize ((current-syntax-context def)) + (for ([x (in-list (syntax->list #'(x ...)))]) + (let ([v (syntax-local-value x (lambda _ #f))]) + (when (syntax-pattern-variable? v) + (wrong-syntax + x + ;; FIXME: customize "~do pattern" vs "#:do block" as appropriate + "definition in ~~do pattern must not shadow attribute binding")))))]))) + +(define-syntax (no-shadow stx) + (syntax-case stx () + [(no-shadow e) + (let ([ee (local-expand #'e (syntax-local-context) + (kernel-form-identifier-list))]) + (syntax-case ee (begin define-values define-syntaxes) + [(begin d ...) + #'(begin (no-shadow d) ...)] + [(define-values . _) + (begin (check-shadow ee) + ee)] + [(define-syntaxes . _) + (begin (check-shadow ee) + ee)] + [_ + ee]))])) + +(define-syntax (curried-stxclass-parser stx) + (syntax-case stx () + [(_ class argu) + (with-syntax ([#s(arguments (parg ...) (kw ...) _) #'argu]) + (let ([sc (get-stxclass/check-arity #'class #'class + (length (syntax->list #'(parg ...))) + (syntax->datum #'(kw ...)))]) + (with-syntax ([parser (stxclass-parser sc)]) + #'(lambda (x cx pr es fh cp rl success) + (app-argu parser x cx pr es fh cp rl success argu)))))])) + +(define-syntax (app-argu stx) + (syntax-case stx () + [(aa proc extra-parg ... #s(arguments (parg ...) (kw ...) (kwarg ...))) + #| + Use keyword-apply directly? + #'(keyword-apply proc '(kw ...) (list kwarg ...) parg ... null) + If so, create separate no-keyword clause. + |# + ;; For now, let #%app handle it. + (with-syntax ([((kw-part ...) ...) #'((kw kwarg) ...)]) + #'(proc kw-part ... ... extra-parg ... parg ...))])) diff --git a/parse/private/sc.rkt-6-11 b/parse/private/sc.rkt-6-11 new file mode 100644 index 0000000..dc6bdda --- /dev/null +++ b/parse/private/sc.rkt-6-11 @@ -0,0 +1,75 @@ +#lang racket/base +(require (for-syntax racket/base + racket/lazy-require) + syntax/parse/private/keywords) + +;; keep and keep as abs. path -- lazy-loaded macros produce references to this +;; must be required via *absolute module path* from any disappearing module +;; (so for consistency etc, require absolutely from all modules) +(require stxparse-info/parse/private/residual + racket/syntax + racket/stxparam + syntax/stx) + +(begin-for-syntax + (lazy-require + ;; load macro transformers lazily via identifier + ;; This module path must also be absolute (not sure why, + ;; but it definitely breaks on relative module path). + [stxparse-info/parse/private/parse-aux + (id:define-syntax-class + id:define-splicing-syntax-class + id:define-integrable-syntax-class + id:syntax-parse + id:syntax-parser + id:define/syntax-parse + id:syntax-parser/template + id:parser/rhs + id:define-eh-alternative-set)])) +;; FIXME: workaround for phase>0 bug in racket/runtime-path (and thus lazy-require) +;; Without this, dependencies don't get collected. +(require racket/runtime-path (for-meta 2 '#%kernel)) +(define-runtime-module-path-index _unused_ 'stxparse-info/parse/private/parse-aux) + +(provide define-syntax-class + define-splicing-syntax-class + define-integrable-syntax-class + syntax-parse + syntax-parser + define/syntax-parse + + (except-out (all-from-out syntax/parse/private/keywords) + ~reflect + ~splicing-reflect + ~eh-var) + attribute + this-syntax + + syntax-parser/template + parser/rhs + define-eh-alternative-set) + +(define-syntaxes (define-syntax-class + define-splicing-syntax-class + define-integrable-syntax-class + syntax-parse + syntax-parser + define/syntax-parse + syntax-parser/template + parser/rhs + define-eh-alternative-set) + (let ([tx (lambda (get-id) + (lambda (stx) + (syntax-case stx () + [(_ . args) + (datum->syntax stx (cons (get-id) #'args) stx)])))]) + (values + (tx id:define-syntax-class) + (tx id:define-splicing-syntax-class) + (tx id:define-integrable-syntax-class) + (tx id:syntax-parse) + (tx id:syntax-parser) + (tx id:define/syntax-parse) + (tx id:syntax-parser/template) + (tx id:parser/rhs) + (tx id:define-eh-alternative-set)))) diff --git a/scribblings/stxparse-info.scrbl-6-11 b/scribblings/stxparse-info.scrbl-6-11 new file mode 100644 index 0000000..2f0e86a --- /dev/null +++ b/scribblings/stxparse-info.scrbl-6-11 @@ -0,0 +1,350 @@ +#lang scribble/manual +@require[racket/require + @for-label[stxparse-info/parse + stxparse-info/parse/experimental/template + stxparse-info/case + stxparse-info/current-pvars + (subtract-in racket/syntax stxparse-info/case) + (subtract-in racket/base stxparse-info/case)] + version-case + @for-syntax[racket/base] + "ovl.rkt"] + +@; Circumvent https://github.com/racket/scribble/issues/79 +@(require scribble/struct + scribble/decode) +@(define (nested-inset . vs) + (nested #:style 'inset vs)) + +@(version-case + [(version< (version) "6.4") + ] + [else + (require scribble/example) + (define ev ((make-eval-factory '(racket))))]) + +@title{@racketmodname[stxparse-info]: Track @racket[syntax-parse] and @racket[syntax-case] pattern vars} +@author[@author+email["Georges Dupéron" "georges.duperon@gmail.com"]] + +Source code: @url{https://github.com/jsmaniac/stxparse-info} + +@defmodule[stxparse-info] + +This library provides some patched versions of @orig:syntax-parse and of the +@orig:syntax-case family. These patched versions track which syntax pattern +variables are bound. This allows some libraries to change the way syntax +pattern variables work. + +For example, @tt{subtemplate} automatically derives temporary +identifiers when a template contains @racket[yᵢ …], and @racket[xᵢ] is a +pattern variable. To know from which @racket[varᵢ] the @racket[yᵢ …] +identifiers must be derived, @tt{subtemplate} needs to know which +syntax pattern variables are within scope. + +@section{Tracking currently-bound pattern variables with @racket[syntax-parse]} + +@defmodule[stxparse-info/parse] + +The module @racketmodname[stxparse-info/parse] provides patched versions of +@orig:syntax-parse, @orig:syntax-parser and @orig:define/syntax-parse which +track which syntax pattern variables are bound. + +@(ovl syntax/parse + syntax-parse + syntax-parser + define/syntax-parse) + +Additionally, the following identifiers are overridden as they are part of the +duplicated implementation of @racketmodname[syntax/parse]. + +@(ovl #:wrapper nested-inset + syntax/parse + ...+ + attribute + boolean + char + character + define-conventions + define-eh-alternative-set + define-literal-set + define-splicing-syntax-class + define-syntax-class + exact-integer + exact-nonnegative-integer + exact-positive-integer + expr + expr/c + id + identifier + integer + kernel-literals + keyword + literal-set->predicate + nat + number + pattern + static + str + this-syntax + ~! + ~and + ~between + ~bind + ~commit + ~datum + ~delimit-cut + ~describe + ~do + ~fail + ~literal + ~not + ~once + ~optional + ~or + ~parse + ~peek + ~peek-not + ~post + ~rest + ~seq + ~var) + +@(version-case + [(version>= (version) "6.9.0.6") + (ovl #:wrapper nested-inset + syntax/parse + ~alt + ~or*)] + [else (begin)]) + +@(ovl #:wrapper nested-inset + #:require (for-template syntax/parse) + syntax/parse + pattern-expander? + pattern-expander + prop:pattern-expander + syntax-local-syntax-parse-pattern-introduce) + +@section{Tracking currently-bound pattern variables with @racket[syntax-case]} + +@defmodule[stxparse-info/case] + +The module @racketmodname[stxparse-info/case] provides patched versions of +@orig:syntax-case, @orig:syntax-case*, @orig:with-syntax, +@orig:define/with-syntax, @orig:datum-case and @orig:with-datum which +track which syntax or datum pattern variables are bound. + +@(ovl racket/base + syntax-case + syntax-case* + with-syntax) + +@(ovl syntax/datum + datum-case + with-datum) + +@(ovl racket/syntax + define/with-syntax) + +@section{Reading and updating the list of currently-bound pattern variables} + +@defmodule[stxparse-info/current-pvars] + +@defproc[#:kind "procedure at phase 1" + (current-pvars) (listof identifier?)]{ + This for-syntax procedure returns the list of syntax pattern variables which + are known to be bound. The most recently bound variables are at the beginning + of the list. + + It is the responsibility of the reader to check that the identifiers are + bound, and that they are bound to syntax pattern variables, for example using + @racket[identifier-binding] and @racket[syntax-pattern-variable?]. This allows + libraries to also track variables bound by match-like forms, for example.} + +@defproc[#:kind "procedure at phase 1" + (current-pvars+unique) (listof (pairof identifier? identifier?))]{ + This for-syntax procedure works like @racket[current-pvars], but associates + each syntax pattern variable with an identifier containing a unique symbol + which is generated at each execution of the code recording the pattern + variable via @racket[with-pvars] or @racket[define-pvars]. + + The @racket[car] of each pair in the returned list is the syntax pattern + variable (as produced by @racket[current-pvars]). It is the responsibility of + the reader to check that the identifiers present in the @racket[car] of each + element of the returned list are bound, and that they are bound to syntax + pattern variables, for example using @racket[identifier-binding] and + @racket[syntax-pattern-variable?]. This allows libraries to also track + variables bound by match-like forms, for example. + + The @racket[cdr] of each pair is the identifier of a temporary variable. + Reading that temporary variable produces a @racket[gensym]-ed symbol, which + was generated at run-time at the point where @racket[with-pvars] or + @racket[define-pvars] was used to record the corresponding pattern variable. + + This can be used to associate run-time data with each syntax pattern + variable, via a weak hash table created with @racket[make-weak-hasheq]. For + example, the @tt{subtemplate} library implicitly derives + identifiers (similarly to @racket[generate-temporaries]) for uses of + @racket[yᵢ ...] from a @racket[xᵢ] pattern variable bearing the same + subscript. The generated identifiers are associated with @racket[xᵢ] via this + weak hash table mechanism, so that two uses of @racket[yᵢ ...] within the + scope of the same @racket[xᵢ] binding derive the same identifiers. + + The code @racket[(with-pvars (v) body)] roughly expands to: + + @racketblock[ + (let-values ([(tmp) (gensym 'v)]) + (letrec-syntaxes+values ([(shadow-current-pvars) + (list* (cons (quote-syntax v) + (quote-syntax tmp)) + old-current-pvars)]) + body))] + + @bold{Caveat:} this entails that the fresh symbol stored in @racket[tmp] is + generated when @racket[with-pvars] or @racket[define-pvars] is called, not + when the syntax pattern variable is actually bound. For example: + + @RACKETBLOCK[ + (define-syntax (get-current-pvars+unique stx) + #`'#,(current-pvars+unique)) + + (require racket/private/sc) + (let ([my-valvar (quote-syntax x)]) + (let-syntax ([my-pvar (make-syntax-mapping 0 (quote-syntax my-valvar))]) + (with-pvars (x) + (get-current-pvars+unique)) (code:comment "'([x . g123])") + (with-pvars (x) + (get-current-pvars+unique)))) (code:comment "'([x . g124])")] + + Under normal circumstances, @racket[with-pvars] @racket[define-pvars] should + be called immediately after binding the syntax pattern variable, but the code + above shows that it is technically possible to do otherwise. + + This caveat is not meant to dissuade the use of + @racket[current-pvars+unique], it rather serves as an explanation of the + behaviour encountered when @racket[with-pvars] or @racket[define-pvars] are + incorrectly used more than once to record the same pattern variable.} + +@defform[(with-pvars (pvar ...) . body) + #:contracts ([pvar identifier?])]{ + Prepends the given @racket[pvar ...] to the list of pattern variables which + are known to be bound. The @racket[pvar ...] are prepended in reverse order, + so within the body of + + @racketblock[(with-pvars (v₁ v₂ v₃) . body)] + + a call to the for-syntax function @racket[(current-pvars)] returns: + + @racketblock[(list* (quote-syntax v₃) (quote-syntax v₂) (quote-syntax v₁) + old-current-pvars)] + + This can be used to implement macros which work similarly to + @racket[syntax-parse] or @racket[syntax-case], and have them record the syntax + pattern variables which they bind. + + Note that the identifiers @racket[pvar ...] must already be bound to syntax + pattern variables when @racket[with-pvars] is used, e.g. + + @racketblock[ + (let-syntax ([v₁ (make-syntax-mapping depth (quote-syntax valvar))] + [v₂ (make-syntax-mapping depth (quote-syntax valvar))]) + (with-pvars (v₁ v₂) + code))] + + instead of: + + @racketblock[ + (with-pvars (v₁ v₂) + (let-syntax ([v₁ (make-syntax-mapping depth (quote-syntax valvar))] + [v₂ (make-syntax-mapping depth (quote-syntax valvar))]) + code))]} + +@defform[(define-pvars pvar ...) + #:contracts ([pvar identifier?])]{ + + Prepends the given @racket[pvar ...] to the list of pattern variables which + are known to be bound, in the same way as @racket[with-pvars]. Whereas + @racket[with-pvars] makes the modified list visible in the @racket[_body], + @racket[define-pvars] makes the modified list visible in the statements + following @racket[define-pvars]. @racket[define-pvars] can be used multiple + times within the same @racket[let] or equivalent. + + This can be used to implement macros which work similarly to + @racket[define/syntax-parse] or @racket[define/with-syntax], and have them + record the syntax pattern variables which they bind. + + @(version-case + [(version< (version) "6.4") + @RACKETBLOCK[ + (let () + (code:comment "Alternate version of define/syntax-parse which") + (code:comment "contains (define-pvars x) in its expanded form.") + (define/syntax-parse x #'1) + (define/syntax-parse y #'2) + (define-syntax (get-pvars stx) + #`'#,(current-pvars)) + (get-pvars)) + (code:comment "=> '(y x)")]] + [else + @examples[ + #:eval ev + #:hidden + (require stxparse-info/parse + stxparse-info/current-pvars + racket/syntax + (for-syntax racket/base))] + + @examples[ + #:eval ev + #:escape UNSYNTAX + (eval:check + (let () + (code:comment "Alternate version of define/syntax-parse which") + (code:comment "contains (define-pvars x) in its expanded form.") + (define/syntax-parse x #'1) + (define/syntax-parse y #'2) + (define-syntax (get-pvars stx) + #`'#,(current-pvars)) + (get-pvars)) + '(y x))]])} + +@section{Extensions to @racketmodname[syntax/parse/experimental/template]} + +@defmodule[stxparse-info/parse/experimental/template] + +@(orig syntax/parse/experimental/template + define-template-metafunction) + +@defidform[define-template-metafunction]{ + Overloaded version of @orig:define-template-metafunction from + @racketmodname[syntax/parse/experimental/template]. + + Note that currently, template metafunctions defined via + @racketmodname[stxparse-info/parse/experimental/template] are not compatible + with the forms from @racketmodname[syntax/parse/experimental/template], and + vice versa. There is a pending Pull Request which would make the necessary + primitives from @racketmodname[syntax/parse/experimental/template] public, so + hopefully this problem will be solved in future versions.} + +@defform[(syntax-local-template-metafunction-introduce stx)]{ + Like @racket[syntax-local-introduce], but for + @tech[#:doc '(lib "syntax/scribblings/syntax.scrbl")]{template metafunctions}. + + This change is also available in the package + @racketmodname{backport-template-pr1514}. It has been submitted as a Pull + Request to Racket, but can already be used in + @racketmodname[stxparse-info/parse/experimental/template] right now.} + +@(ovl syntax/parse/experimental/template + template + quasitemplate + template/loc + quasitemplate/loc) + +Additionally, the following identifiers are overridden as they are part of the +duplicated implementation of @racketmodname[syntax/parse]. + +@(ovl #:wrapper nested-inset + syntax/parse/experimental/template + ?? + ?@)