diff --git a/collects/syntax/private/stxparse/lib.ss b/collects/syntax/private/stxparse/lib.ss index ac911fd58d..259441d75f 100644 --- a/collects/syntax/private/stxparse/lib.ss +++ b/collects/syntax/private/stxparse/lib.ss @@ -3,11 +3,9 @@ (require "sc.ss" "../util.ss" syntax/stx - syntax/kerncase scheme/struct-info scheme/contract/private/helpers (for-syntax scheme/base - syntax/kerncase "rep.ss" (only-in "rep-data.ss" make-literalset)) (for-template scheme/base @@ -115,9 +113,26 @@ (quote-syntax #,(syntax/loc #'x ()))))) ;; Literal sets - -(define-syntax kernel-literals - (make-literalset - (list* (list '#%plain-module-begin (quote-syntax #%plain-module-begin)) - (for/list ([id (kernel-form-identifier-list)]) - (list (syntax-e id) id))))) + +(define-literal-set kernel-literals + (begin + begin0 + define-values + define-syntaxes + define-values-for-syntax + set! + let-values + letrec-values + #%plain-lambda + case-lambda + if + quote + letrec-syntaxes+values + with-continuation-mark + #%expression + #%plain-app + #%top + #%datum + #%variable-reference + module #%provide #%require + #%plain-module-begin)) diff --git a/collects/syntax/private/stxparse/minimatch.ss b/collects/syntax/private/stxparse/minimatch.ss index 65c3889edd..bd177143b0 100644 --- a/collects/syntax/private/stxparse/minimatch.ss +++ b/collects/syntax/private/stxparse/minimatch.ss @@ -77,6 +77,7 @@ (match-p xps (list p ...) success failure)) failure)))])) +#; (define-syntax struct (lambda (stx) (raise-syntax-error #f "illegal use of keyword" stx))) diff --git a/collects/syntax/private/stxparse/parse.ss b/collects/syntax/private/stxparse/parse.ss index fa1ac61e5b..e4b34eacb7 100644 --- a/collects/syntax/private/stxparse/parse.ss +++ b/collects/syntax/private/stxparse/parse.ss @@ -52,15 +52,18 @@ (syntax-case stx () [(parse:rhs #s(rhs _ _ transparent? _ variants (def ...)) relsattrs (arg ...) get-description splicing?) - #`(lambda (x arg ...) - (define (fail-rhs failure) - (expectation-of-thing (get-description arg ...) - transparent? - (if transparent? failure #f))) - def ... - (syntax-parameterize ((this-syntax (make-rename-transformer #'x))) - (with-enclosing-fail* fail-rhs - (parse:variants x relsattrs variants splicing?))))])) + #`(with-error-collector + (make-parser + (lambda (x arg ...) + (define (fail-rhs failure) + (expectation-of-thing (get-description arg ...) + transparent? + (if transparent? failure #f))) + def ... + (syntax-parameterize ((this-syntax (make-rename-transformer #'x))) + (with-enclosing-fail* fail-rhs + (parse:variants x relsattrs variants splicing?)))) + (collect-error)))])) ;; (parse:variants id (SAttr ...) (Variant ...) boolean) ;; : expr[SyntaxClassResult] @@ -566,17 +569,19 @@ ;; (expectation Pattern) (define-syntax (expectation stx) (syntax-case stx () - [(_ #s(pat:datum attrs datum)) - #'(make-expect:atom 'datum)] - [(_ #s(pat:literal attrs literal)) - #'(make-expect:literal (quote-syntax literal))] + [(_ #s(pat:datum attrs d)) + #'(begin (collect-error '(datum d)) + (make-expect:atom 'd))] + [(_ #s(pat:literal attrs lit)) + #'(begin (collect-error '(literal lit)) + (make-expect:literal (quote-syntax lit)))] ;; 2 pat:compound patterns ;;[(_ #s(pat:compound attrs #:pair (head-pattern tail-pattern))) ;; #'(make-expect:pair)] [(_ #s(pat:compound attrs kind0 (part-pattern ...))) - #''ineffable] + #'(collect-error 'ineffable)] [(_ #s(pat:not _ pattern)) - #''ineffable] + #'(collect-error 'ineffable)] [(_ #s(ghost:fail _ condition message)) #'(expectation-of-message message)])) @@ -586,8 +591,10 @@ (make-expect:thing description transparent? chained)) (define-syntax-rule (expectation-of-message message) - (let ([msg message]) - (if msg (make-expect:message msg) 'ineffable))) + (let ([msg (collect-error message)]) + (if msg + (make-expect:message msg) + 'ineffable))) (define-syntax expectation-of-reps/too-few (syntax-rules () @@ -607,18 +614,43 @@ [(_ rep #s(rep:bounds min max name too-few-msg too-many-msg)) (expectation-of-message/too-many too-many-msg name)])) -(define-syntax-rule (expectation-of-message/too-few msg name) - (expectation-of-message - (or msg - (let ([n name]) - (if n - (format "missing required occurrence of ~a" n) - "repetition constraint violated"))))) +(define-syntax expectation-of-message/too-few + (syntax-rules () + [(emtf #f #f) + (collect-error "repetition constraint violated")] + [(emtf #f name) + (collect-error (format "missing required occurrence of ~a" name))] + [(emtf msg _) + (collect-error msg)])) -(define-syntax-rule (expectation-of-message/too-many msg name) - (expectation-of-message - (or msg - (let ([n name]) - (if n - (format "too many occurrences of ~a" n) - "repetition constraint violated"))))) +(define-syntax expectation-of-message/too-many + (syntax-rules () + [(emtm #f #f) + (collect-error (format "repetition constraint violated"))] + [(emtm #f name) + (collect-error (format "too many occurrences of ~a" name))] + [(emtm msg _) + (collect-error msg)])) + + +;; + +(define-syntax-parameter collect-error + (syntax-rules () + [(ce thing) thing] + [(ce) '()])) + +(define-syntax-rule (with-error-collector body) + (... + (let-syntax ([tmp (box null)]) + (syntax-parameterize ((collect-error + (lambda (stx) + (let ([b (syntax-local-value #'tmp)]) + (syntax-case stx () + [(ce thing) + (begin (set-box! b (cons #''thing (unbox b))) + #'thing)] + [(ce) + (with-syntax ([(thing ...) (reverse (unbox b))]) + #'(list thing ...))]))))) + body)))) diff --git a/collects/syntax/private/stxparse/rep-data.ss b/collects/syntax/private/stxparse/rep-data.ss index 9e9fd549bd..1d4330d088 100644 --- a/collects/syntax/private/stxparse/rep-data.ss +++ b/collects/syntax/private/stxparse/rep-data.ss @@ -4,6 +4,7 @@ syntax/stx syntax/id-table "../util.ss" + "minimatch.ss" "rep-attrs.ss" "rep-patterns.ss") (provide (all-from-out "rep-attrs.ss") @@ -81,14 +82,18 @@ A LiteralSet is DeclEnv = (make-declenv immutable-bound-id-mapping[id => DeclEntry] (listof ConventionRule)) + DeclEntry = - (list 'literal id id) - (list 'stxclass id id (listof stx)) - (list 'parser id id (listof IAttr)) - #f + (make-den:lit id id) + (make-den:class id id (listof syntax) bool) + (make-den:parser id id (listof SAttr) bool) |# (define-struct declenv (table conventions)) +(define-struct den:lit (internal external)) +(define-struct den:class (name class args)) +(define-struct den:parser (parser description attrs splicing?)) + (define (new-declenv literals #:conventions [conventions null]) (for/fold ([decls (make-declenv (make-immutable-bound-id-table) conventions)]) ([literal literals]) @@ -104,45 +109,63 @@ DeclEntry = ;; Order goes: literals, pattern, declares ;; So blame-declare? only applies to stxclass declares (let ([val (declenv-lookup env id #:use-conventions? #f)]) - (when val - (cond [(eq? 'literal (car val)) - (wrong-syntax id "identifier previously declared as literal")] - [(and blame-declare? stxclass-name) - (wrong-syntax (cadr val) - "identifier previously declared with syntax class ~a" - stxclass-name)] - [else - (wrong-syntax (if blame-declare? (cadr val) id) - "identifier previously declared")])))) + (match val + [(struct den:lit (_i _e)) + (wrong-syntax id "identifier previously declared as literal")] + [(struct den:class (name _c _a)) + (if (and blame-declare? stxclass-name) + (wrong-syntax name + "identifier previously declared with syntax class ~a" + stxclass-name) + (wrong-syntax (if blame-declare? name id) + "identifier previously declared"))] + [(struct den:parser (_p _d _a _sp)) + (wrong-syntax id "(internal error) late unbound check")] + ['#f (void)]))) (define (declenv-put-literal env internal-id lit-id) (declenv-check-unbound env internal-id) (make-declenv (bound-id-table-set (declenv-table env) internal-id - (list 'literal internal-id lit-id)) + (make den:lit internal-id lit-id)) (declenv-conventions env))) (define (declenv-put-stxclass env id stxclass-name args) (declenv-check-unbound env id) (make-declenv (bound-id-table-set (declenv-table env) id - (list 'stxclass id stxclass-name args)) + (make den:class id stxclass-name args)) (declenv-conventions env))) (define (declenv-put-parser env id parser get-description attrs splicing?) ;; no unbound check, since replacing 'stxclass entry (make-declenv (bound-id-table-set (declenv-table env) id - (list (if splicing? 'splicing-parser 'parser) - parser get-description attrs)) + (make den:parser parser get-description attrs splicing?)) (declenv-conventions env))) +;; declenv-update/fold : DeclEnv (Id/Regexp DeclEntry a -> DeclEntry a) a +;; -> (values DeclEnv a) +(define (declenv-update/fold env0 f acc0) + (define-values (acc1 rules1) + (for/fold ([acc acc0] [newrules null]) + ([rule (declenv-conventions env0)]) + (let-values ([(val acc) (f (car rule) (cadr rule) acc)]) + (values acc (cons (list (car rule) val) newrules))))) + (define-values (acc2 table2) + (for/fold ([acc acc1] [table (make-immutable-bound-id-table)]) + ([(k v) (in-dict (declenv-table env0))]) + (let-values ([(val acc) (f k v acc)]) + (values acc (bound-id-table-set table k val))))) + (values (make-declenv table2 (reverse rules1)) + acc2)) + ;; returns ids in domain of env but not in given list (define (declenv-domain-difference env ids) (define idbm (make-bound-id-table)) (for ([id ids]) (bound-id-table-set! idbm id #t)) (for/list ([(k v) (in-dict (declenv-table env))] - #:when (and (pair? v) (not (eq? (car v) 'literal))) + #:when (or (den:class? v) (den:parser? v)) #:when (not (bound-id-table-ref idbm k #f))) k)) @@ -158,11 +181,19 @@ DeclEntry = (define DeclEnv/c (flat-named-contract 'DeclEnv declenv?)) +(define DeclEntry/c + (flat-named-contract 'DeclEntry (or/c den:lit? den:class? den:parser?))) + (define SideClause/c (or/c clause:fail? clause:with? clause:attr?)) +(provide (struct-out den:lit) + (struct-out den:class) + (struct-out den:parser)) + (provide/contract [DeclEnv/c contract?] + [DeclEntry/c contract?] [SideClause/c contract?] [make-dummy-stxclass (-> identifier? stxclass?)] @@ -177,14 +208,20 @@ DeclEntry = [declenv-put-stxclass (-> DeclEnv/c identifier? identifier? (listof syntax?) DeclEnv/c)] + [declenv-put-literal + (-> DeclEnv/c identifier? identifier? + DeclEnv/c)] [declenv-put-parser (-> DeclEnv/c identifier? any/c any/c (listof sattr?) boolean? DeclEnv/c)] [declenv-domain-difference (-> DeclEnv/c (listof identifier?) (listof identifier?))] - [declenv-table - (-> DeclEnv/c any)] + [declenv-update/fold + (-> DeclEnv/c + (-> (or/c identifier? regexp?) DeclEntry/c any/c (values DeclEntry/c any/c)) + any/c + (values DeclEnv/c any/c))] [get-stxclass (-> identifier? any)] diff --git a/collects/syntax/private/stxparse/rep.ss b/collects/syntax/private/stxparse/rep.ss index b0a31a43c3..ec1ce9f8f5 100644 --- a/collects/syntax/private/stxparse/rep.ss +++ b/collects/syntax/private/stxparse/rep.ss @@ -18,7 +18,7 @@ (provide/contract [parse-rhs - (-> syntax? boolean? boolean? #:context (or/c false/c syntax?) + (-> syntax? (or/c false/c (listof sattr?)) boolean? #:context (or/c false/c syntax?) rhs?)] [parse-whole-pattern (-> syntax? DeclEnv/c #:context (or/c false/c syntax?) @@ -39,8 +39,8 @@ (values DeclEnv/c (listof syntax?)))] |# [create-aux-def - (-> list? ;; DeclEntry - (values identifier? identifier? (listof sattr?) (listof syntax?) boolean?))] + (-> DeclEntry/c + (values DeclEntry/c (listof syntax?)))] [check-literals-list (-> syntax? syntax? (listof (list/c identifier? identifier?)))] @@ -67,9 +67,10 @@ (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 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 _) @@ -85,13 +86,14 @@ (quote-syntax ~rep) (quote-syntax ~once) (quote-syntax ~optional) - (quote-syntax ~bounds) + (quote-syntax ~between) (quote-syntax ~rest) (quote-syntax ~describe) (quote-syntax ~!) (quote-syntax ~bind) (quote-syntax ~fail) - (quote-syntax ~parse))) + (quote-syntax ~parse) + (quote-syntax ...+))) (define (reserved? stx) (and (identifier? stx) @@ -116,20 +118,20 @@ ;; --- -;; parse-rhs : stx boolean boolean stx -> RHS -;; If strict? is true, then referenced stxclasses must be defined and +;; 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 strict? splicing? #:context ctx) +(define (parse-rhs stx expected-attrs splicing? #:context ctx) (parameterize ((current-syntax-context ctx)) (define-values (rest description transp? attributes auto-nested? decls defs) - (parse-rhs/part1 stx strict?)) + (parse-rhs/part1 stx (and expected-attrs #t))) (define patterns (parameterize ((stxclass-lookup-config - (cond [strict? 'yes] + (cond [expected-attrs 'yes] [auto-nested? 'try] [else 'no]))) - (parse-variants rest decls splicing?))) + (parse-variants rest decls splicing? expected-attrs))) (when (null? patterns) (wrong-syntax #f "expected at least one variant")) (let ([sattrs @@ -151,12 +153,12 @@ (define-values (decls defs) (get-decls+defs chunks strict?)) (values rest description transparent? attributes auto-nested? decls defs)) -(define (parse-variants rest decls splicing?) +(define (parse-variants rest decls splicing? expected-attrs) (define (gather-patterns stx) (syntax-case stx (pattern) [((pattern . _) . rest) (begin (disappeared! (stx-car stx)) - (cons (parse-variant (stx-car stx) splicing? decls) + (cons (parse-variant (stx-car stx) splicing? decls expected-attrs) (gather-patterns #'rest)))] [(bad-variant . rest) (wrong-syntax #'bad-variant "expected syntax-class variant")] @@ -175,10 +177,11 @@ (define lits (options-select-value chunks '#: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-lits+litsets (check-literals-bound lits strict?) litsets)) - (define convention-rules (apply append convs)) + (define convention-rules (apply append (cons localconvs convs))) (new-declenv literals #:conventions convention-rules)) (define (check-literals-bound lits strict?) @@ -195,31 +198,34 @@ ;; decls-create-defs : DeclEnv -> (values DeclEnv (listof stx)) (define (decls-create-defs decls0) - (for/fold ([decls decls0] [defs null]) - ([(k v) (in-dict (declenv-table decls0))] - #:when (memq (car v) '(stxclass splicing-stxclass))) - (let-values ([(parser description attrs new-defs splicing?) (create-aux-def v)]) - (values (declenv-put-parser decls k parser description attrs splicing?) - (append new-defs defs))))) + (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 id id (listof SAttr) (listof stx) boolean) +;; create-aux-def : DeclEntry -> (values DeclEntry (listof stx)) (define (create-aux-def entry) - (let ([sc-name (caddr entry)] - [args (cadddr entry)]) - (let ([sc (get-stxclass/check-arg-count sc-name (length args))]) - (with-syntax ([sc-parser (stxclass-parser-name sc)] - [sc-description (stxclass-description sc)]) - (if (pair? args) - (with-syntax ([x (generate-temporary 'x)] - [parser (generate-temporary sc-name)] - [description (generate-temporary sc-name)] - [(arg ...) args]) - (values #'parser #'description (stxclass-attrs sc) - (list #'(define (parser x) (sc-parser x arg ...)) - #'(define (description) (description arg ...))) - (stxclass/h? sc))) - (values #'sc-parser #'sc-description (stxclass-attrs sc) - null (stxclass/h? sc))))))) + (match entry + [(struct den:lit (_i _e)) + (values entry null)] + [(struct den:class (name class args)) + (let ([sc (get-stxclass/check-arg-count class (length args))]) + (with-syntax ([sc-parser (stxclass-parser-name sc)] + [sc-description (stxclass-description sc)]) + (if (pair? args) + (with-syntax ([x (generate-temporary 'x)] + [parser (generate-temporary class)] + [description (generate-temporary class)] + [(arg ...) args]) + (values (make den:parser #'parser #'description + (stxclass-attrs sc) (stxclass/h? sc)) + (list #'(define (parser x) (sc-parser x arg ...)) + #'(define (description) (description arg ...))))) + (values (make den:parser #'sc-parser #'sc-description + (stxclass-attrs sc) (stxclass/h? sc)) + null))))] + [(struct den:parser (_p _d _a _sp)) + (values entry null)])) (define (append-lits+litsets lits litsets) (define seen (make-bound-id-table lits)) @@ -230,8 +236,8 @@ (bound-id-table-set! seen (car lit) #t))) (apply append lits litsets)) -;; parse-variant : stx boolean DeclEnv -> RHS -(define (parse-variant stx splicing? decls0) +;; 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 decls defs clauses) @@ -249,6 +255,10 @@ (cons (pattern-attrs pattern) (side-clauses-attrss clauses)))] [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 clauses defs)))])) (define (side-clauses-attrss clauses) @@ -367,6 +377,10 @@ (dots? #'dots) (begin (disappeared! #'dots) (parse-pat:dots stx #'head #'tail decls))] + [(head plus-dots . tail) + (plus-dots? #'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)]) @@ -394,47 +408,50 @@ (let ([lp (parse-single-pattern (datum->syntax #f contents #'s) decls)]) (create-pat:compound `(#:pstruct ,key) (list lp))))])) -;; parse-ellipsis-head-pattern : stx DeclEnv number -> EllipsisHeadPattern +;; parse-ellipsis-head-pattern : stx DeclEnv number -> (listof EllipsisHeadPattern) (define (parse-ellipsis-head-pattern stx decls) - (syntax-case stx (~bounds ~optional ~once) + (syntax-case stx (~or ~between ~optional ~once) + [(~or . _) + (begin + (unless (stx-list? stx) + (wrong-syntax stx "expected sequence of patterns")) + (apply append + (for/list ([sub (cdr (stx->list stx))]) + (parse-ellipsis-head-pattern sub decls))))] [(~optional . _) (disappeared! stx) - (parse-ehpat/optional stx decls)] + (list (parse-ehpat/optional stx decls))] [(~once . _) (disappeared! stx) - (parse-ehpat/once stx decls)] - [(~bounds . _) + (list (parse-ehpat/once stx decls))] + [(~between . _) (disappeared! stx) - (parse-ehpat/bounds stx decls)] + (list (parse-ehpat/bounds stx decls))] [_ (let ([head (parse-head-pattern stx decls)]) - (make ehpat (map increase-depth (pattern-attrs head)) - head - #f))])) + (list (make ehpat (map increase-depth (pattern-attrs head)) + head + #f)))])) ;; ---- (define (parse-pat:id id decls allow-head?) (define entry (declenv-lookup decls id)) (match entry - [(list 'literal internal-id literal-id) - (create-pat:literal literal-id)] - [(list 'stxclass _ _ _) + [(struct den:lit (internal literal)) + (create-pat:literal literal)] + [(struct den:class (_n _c _a)) (error 'parse-pat:id - "(internal error) decls had leftover 'stxclass entry: ~s" + "(internal error) decls had leftover stxclass entry: ~s" entry)] - [(list 'splicing-stxclass _ _ _) - (error 'parse-pat:id - "(internal error) decls had leftover 'splicing-stxclass entry: ~s" - entry)] - [(list 'parser parser description attrs) - (parse-pat:id/s id parser null attrs)] - [(list 'splicing-parser parser description attrs) - (parse-pat:id/h id parser null attrs)] + [(struct den:parser (parser desc attrs splicing?)) + (if splicing? + (parse-pat:id/h id parser null attrs) + (parse-pat:id/s id parser null attrs))] ['#f - (when #f ;; FIXME: enable? + (when #t ;; FIXME: right place??? (unless (safe-name? id) - (wrong-syntax id "expected identifier not starting with ~ character"))) + (wrong-syntax id "expected identifier not starting with ~~ character"))) (let-values ([(name sc) (split-id/get-stxclass id decls)]) (if sc (parse-pat:var* id allow-head? name sc null) @@ -631,21 +648,21 @@ result)) (define (parse-pat:dots stx head tail decls) - (define headps - (syntax-case head (~or) - [(~or . _) - (begin - (unless (stx-list? head) - (wrong-syntax head "expected sequence of patterns")) - (unless (stx-pair? (stx-cdr head)) - (wrong-syntax head "expected at least one pattern")) - (for/list ([sub (cdr (stx->list head))]) - (parse-ellipsis-head-pattern sub decls)))] - [_ - (list (parse-ellipsis-head-pattern head 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")) (create-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 + (make-ehpat (map increase-depth (pattern-attrs headp)) + headp + (make-rep:bounds 1 +inf.0 #f #f #f))) + (create-pat:dots (list head/rep) tailp)) + (define (parse-pat:bind stx decls) (syntax-case stx () [(_ clause ...) @@ -758,8 +775,8 @@ (make rep:once name too-few-msg too-many-msg))))])) (define (parse-ehpat/bounds stx decls) - (syntax-case stx (~bounds) - [(~bounds p min max . options) + (syntax-case stx (~between) + [(~between p min max . options) (let ([head (parse-head-pattern #'p decls)]) (define minN (syntax-e #'min)) (define maxN (syntax-e #'max)) @@ -959,26 +976,32 @@ [_ (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 (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) + (define (check-sc-expr x rx) (syntax-case x () - [sc (identifier? #'sc) (list #'sc null)] - [(sc arg ...) (identifier? #'sc) (list #'sc (syntax->list #'(arg ...)))] + [sc + (identifier? #'sc) + (make den:class rx #'sc null)] + [(sc arg ...) + (identifier? #'sc) + (make den:class rx #'sc (syntax->list #'(arg ...)))] [_ (raise-syntax-error #f "expected syntax class use" ctx x)])) (syntax-case stx () [(rx sc) (list (check-conventions-pattern (syntax-e #'rx) #'rx) - (check-sc-expr #'sc))])) + (check-sc-expr #'sc #'rx))])) ;; bind clauses (define (check-bind-clause-list stx ctx) @@ -993,11 +1016,15 @@ (make clause:attr (check-attr-arity #'attr-decl ctx) #'expr)] [_ (raise-syntax-error #f "expected bind clause" ctx clause)])) + +;; Directive tables + ;; common-parse-directive-table (define common-parse-directive-table (list (list '#:literals check-literals-list) (list '#:literal-sets check-literal-sets-list) - (list '#:conventions check-conventions-list))) + (list '#:conventions check-conventions-list) + (list '#:local-conventions check-conventions-rules))) ;; parse-directive-table (define parse-directive-table diff --git a/collects/syntax/private/stxparse/runtime.ss b/collects/syntax/private/stxparse/runtime.ss index 5b34ed2353..db5283ee20 100644 --- a/collects/syntax/private/stxparse/runtime.ss +++ b/collects/syntax/private/stxparse/runtime.ss @@ -19,7 +19,7 @@ ~or ~not ~seq - ~bounds + ~between ~once ~optional ~rest @@ -28,6 +28,7 @@ ~bind ~fail ~parse + ...+ current-expression current-macro-name @@ -84,7 +85,7 @@ (define-keyword ~or) (define-keyword ~not) (define-keyword ~seq) -(define-keyword ~bounds) +(define-keyword ~between) (define-keyword ~once) (define-keyword ~optional) (define-keyword ~rest) @@ -93,6 +94,7 @@ (define-keyword ~bind) (define-keyword ~fail) (define-keyword ~parse) +(define-keyword ...+) ;; == Parameters & Syntax Parameters @@ -569,3 +571,11 @@ An Expectation is one of [(make expect:thing thing '#t chained) (make expect:thing thing #t (failure->sexpr chained))] [_ expectation])) + + +;; + +(provide (struct-out parser)) + +(define-struct parser (proc errors) + #:property prop:procedure (struct-field-index proc)) diff --git a/collects/syntax/private/stxparse/sc.ss b/collects/syntax/private/stxparse/sc.ss index e449d5e1c6..0105e234ac 100644 --- a/collects/syntax/private/stxparse/sc.ss +++ b/collects/syntax/private/stxparse/sc.ss @@ -17,6 +17,7 @@ define-conventions syntax-class-parse syntax-class-attributes + syntax-class-possible-errors debug-rhs debug-pattern @@ -33,7 +34,7 @@ ~or ~not ~seq - ~bounds + ~between ~once ~optional ~rest @@ -42,6 +43,7 @@ ~bind ~fail ~parse + ...+ attribute this-syntax) @@ -93,15 +95,14 @@ (with-syntax ([([entry (def ...)] ...) (for/list ([line (check-conventions-rules #'(rule ...) stx)]) (let ([rx (car line)] - [sc (car (cadr line))] - [args (cadr (cadr line))]) - (let-values ([(parser description attrs defs splicing?) - (create-aux-def (list 'stxclass rx sc args))]) + [den (cadr line)]) + (let-values ([(den defs) (create-aux-def den)]) (list #`(list (quote #,rx) - (list (quote #,(if splicing? 'splicing-parser 'parser)) - (quote-syntax #,parser) - (quote-syntax #,description) - (quote #,attrs))) + (make-den:parser + (quote-syntax #,(den:parser-parser den)) + (quote-syntax #,(den:parser-description den)) + (quote #,(den:parser-attrs den)) + (quote #,(den:parser-splicing? den)))) defs))))]) #'(begin def ... ... @@ -129,7 +130,8 @@ (with-disappeared-uses (let ([rhs (parameterize ((current-syntax-context #'ctx)) - (parse-rhs #'rhss #t (syntax-e #'splicing?) #:context #'ctx))]) + (parse-rhs #'rhss (syntax->datum #'attrs) (syntax-e #'splicing?) + #:context #'ctx))]) #`(let ([get-description (lambda args #,(or (rhs-description rhs) @@ -165,6 +167,13 @@ [(depth ...) (map attr-depth attrs)]) #'(quote ((a depth) ...)))))])) +(define-syntax (syntax-class-possible-errors stx) + (syntax-case stx () + [(_ s) + (parameterize ((current-syntax-context stx)) + (with-syntax ([p (stxclass-parser-name (get-stxclass #'s))]) + #'(parser-errors p)))])) + (define-syntax (debug-rhs stx) (syntax-case stx () [(debug-rhs rhs) diff --git a/collects/syntax/scribblings/parse-patterns.scrbl b/collects/syntax/scribblings/parse-patterns.scrbl index 81cdceb011..5bf8b17edc 100644 --- a/collects/syntax/scribblings/parse-patterns.scrbl +++ b/collects/syntax/scribblings/parse-patterns.scrbl @@ -97,8 +97,8 @@ means specifically @tech{@Spattern}. (~datum datum) (H-pattern . S-pattern) (A-pattern . S-pattern) - ((@#,ref[~or eh] EH-pattern ...+) #,ellipses . S-pattern) (EH-pattern #,ellipses . S-pattern) + (H-pattern @#,(scheme ...+) . S-pattern) (@#,ref[~and s] proper-S/A-pattern ...+) (@#,ref[~or s] S-pattern ...+) (~not S-pattern) @@ -112,8 +112,8 @@ means specifically @tech{@Spattern}. () (A-pattern . L-pattern) (H-pattern . L-pattern) - ((@#,ref[~or eh] EH-pattern ...+) #,ellipses . L-pattern) (EH-pattern #,ellipses . L-pattern) + (H-pattern @#,(scheme ...+) . L-pattern) (~rest L-pattern)] [H-pattern pvar-id:splicing-syntax-class-id @@ -125,8 +125,10 @@ means specifically @tech{@Spattern}. (@#,ref[~describe h] expr H-pattern) proper-S-pattern] [EH-pattern + (@#,ref[~or eh] EH-pattern ...) (~once H-pattern once-option ...) (@#,ref[~optional eh] H-pattern optional-option ...) + (~between H min-number max-number between-option) H-pattern] [A-pattern ~! @@ -160,10 +162,10 @@ One of @ref[~and s], @ref[~and h], or @ref[~and a]: @defidform[~or]{ -One of @ref[~or s], @ref[~or h]), or @ref[~or eh]: +One of @ref[~or s], @ref[~or h], or @ref[~or eh]: @itemize[ @item{@ref[~or eh] if the pattern occurs directly before ellipses - (@ellipses)} + (@ellipses) or immediately within another @ref[~or eh] pattern} @item{@ref[~or h] if any of the disjuncts is a @tech{proper @Hpattern}} @item{@ref[~or s] otherwise} ] @@ -396,11 +398,11 @@ words, @Apatterns ``don't take up space.'' See @tech{@Apatterns} for more information. } -@specsubform[((@#,def[~or eh] EH-pattern ...+) #,ellipses . S-pattern)]{ +@specsubform[(EH-pattern #,ellipses . S-pattern)]{ Matches any term that can be decomposed into a list head matching some -number of repetitions of @scheme[EH-pattern] alternatives (subject to -its repetition constraints) followed by a list tail matching +number of repetitions of the @scheme[EH-pattern] alternatives (subject +to its repetition constraints) followed by a list tail matching @scheme[S-pattern]. In other words, the whole pattern matches either the second pattern @@ -411,10 +413,25 @@ the whole sequence pattern. See @tech{@EHpatterns} for more information. } -@specsubform[(EH-pattern #,ellipses . S-pattern)]{ +@specsubform[(H-pattern @#,defhere[...+] . S-pattern)]{ + +Like an ellipses (@ellipses) pattern, but requires at one occurrence +of the head pattern to be present. + +That is, the following patterns are equivalent: +@itemize[ +@item[@scheme[(H ...+ . S)]] +@item[@scheme[((~between H 1 +inf.0) ... . S)]] +] + +@myexamples[ +(syntax-parse #'(1 2 3) + [(n:nat ...+) 'ok]) +(syntax-parse #'() + [(n:nat ...+) 'ok] + [_ 'none]) +] -The @scheme[~or]-free variant of ellipses (@ellipses) pattern is -equivalent to the @scheme[~or] variant with just one alternative. } @specsubform[(@#,def[~and s] S/A-pattern ...)]{ @@ -704,7 +721,8 @@ An @deftech{@EHpattern} (abbreviated @svar[EH-pattern]) is pattern that describes some number of terms, like a @tech{@Hpattern}, but may also place contraints on the number of times it occurs in a repetition. They are useful for matching keyword arguments where the -keywords may come in any order. +keywords may come in any order. Multiple alternatives can be grouped +together via @ref[~or eh]. @myexamples[ (define parser1 @@ -725,6 +743,12 @@ arguments. The ``pieces'' can occur in any order. Here are the variants of @elem{@EHpattern}: +@specsubform[(@#,def[~or eh] EH-pattern ...)]{ + +Matches if any of the inner @scheme[EH-pattern] alternatives match. + +} + @specsubform/subs[(@#,defhere[~once] H-pattern once-option ...) ([once-option (code:line #:name name-expr) (code:line #:too-few too-few-message-expr) @@ -734,11 +758,11 @@ Matches if the inner @scheme[H-pattern] matches. This pattern must be selected exactly once in the match of the entire repetition sequence. If the pattern is not chosen in the repetition sequence, then an error -is raised with a message, either @scheme[too-few-message-expr] or +is raised with the message either @scheme[too-few-message-expr] or @schemevalfont{"missing required occurrence of @scheme[name-expr]"}. If the pattern is chosen more than once in the repetition sequence, -then an error is raised with a message, either +then an error is raised with the message either @scheme[too-many-message-expr] or @schemevalfont{"too many occurrences of @scheme[name-expr]"}. } @@ -752,7 +776,7 @@ Matches if the inner @scheme[H-pattern] matches. This pattern may be used at most once in the match of the entire repetition. If the pattern is chosen more than once in the repetition sequence, -then an error is raised with a message, either +then an error is raised with the message either @scheme[too-many-message-expr] or @schemevalfont{"too many occurrences of @scheme[name-expr]"}. @@ -762,6 +786,25 @@ sequence. The default attributes must be a subset of the subpattern's attributes. } +@specsubform/subs[(@#,defhere[~between] H-pattern min-number max-number between-option ...) + ([reps-option (code:line #:name name-expr) + (code:line #:too-few too-few-message-expr) + (code:line #:too-many too-many-message-expr)])]{ + +Matches if the inner @scheme[H-pattern] matches. This pattern must be +selected at least @scheme[min-number] and at most @scheme[max-number] +times in the entire repetition. + +If the pattern is chosen too few times, then an error is raised with a +message, either @scheme[too-few-message-expr] or @schemevalfont{"too +few occurrences of @scheme[name-expr]"}. + +If the pattern is chosen too many times, then an error is raised with +the message either @scheme[too-many-message-expr] or +@schemevalfont{"too few occurrences of @scheme[name-expr]"}. +} + + @;{--------} diff --git a/collects/syntax/scribblings/parse.scrbl b/collects/syntax/scribblings/parse.scrbl index 891cc685e7..8ca55f2a37 100644 --- a/collects/syntax/scribblings/parse.scrbl +++ b/collects/syntax/scribblings/parse.scrbl @@ -283,7 +283,8 @@ Two parsing forms are provided: @scheme[syntax-parse] and ([parse-option (code:line #:context context-expr) (code:line #:literals (literal ...)) (code:line #:literal-sets (literal-set ...)) - (code:line #:conventions (convention-id ...))] + (code:line #:conventions (convention-id ...)) + (code:line #:local-conventions (convention-rule ...))] [literal literal-id (pattern-id literal-id)] [literal-set literal-set-id @@ -352,6 +353,13 @@ Imports @tech{convention}s that give default syntax classes to pattern variables that do not explicitly specify a syntax class. } +@specsubform[(code:line #:local-conventions (convention-rule ...))]{ + +Uses the @tech{conventions} specified. The advantage of +@scheme[#:local-conventions] over @scheme[#:conventions] is that local +conventions can be in the scope of syntax-class parameter bindings. +} + Each clause consists of a @tech{syntax pattern}, an optional sequence of @tech{pattern directives}, and a non-empty sequence of body expressions. @@ -386,7 +394,8 @@ structures can share syntax class definitions. (code:line #:opaque) (code:line #:literals (literal-entry ...)) (code:line #:literal-sets (literal-set ...)) - (code:line #:conventions (convention-id ...))] + (code:line #:conventions (convention-id ...)) + (code:line #:local-conventions (convention-rule ...))] [attr-arity-decl attr-name-id (attr-name-id depth)] @@ -713,8 +722,9 @@ identifiers the literal matches. ] } -@defform/subs[(define-conventions name-id (id-pattern syntax-class) ...) - ([name-pattern exact-id +@defform/subs[(define-conventions name-id convention-rule ...) + ([convention-rule (name-pattern syntax-class)] + [name-pattern exact-id name-rx] [syntax-class syntax-class-id (syntax-class-id expr ...)])]{