diff --git a/collects/syntax/keyword.ss b/collects/syntax/keyword.ss new file mode 100644 index 0000000000..b12d0eb2c7 --- /dev/null +++ b/collects/syntax/keyword.ss @@ -0,0 +1,58 @@ +#lang scheme/base +(require scheme/contract + scheme/dict + "private/keyword.ss") + +(define optstx (or/c syntax? false/c)) + +(define checker (-> syntax? optstx any/c)) + +(define stxish any/c) + +(define keyword-table/c + (or/c (listof (cons/c keyword? (listof checker))) + (and/c (not/c list?) + dict?))) + +(define options/c + (listof (cons/c keyword? (cons/c syntax? list?)))) + +(provide/contract + [parse-keyword-options + (->* (syntax? dict?) + (#:context optstx + #:no-duplicates? boolean? + #:incompatible (listof (listof keyword?)) + #:on-incompatible (-> keyword? keyword? options/c stxish optstx + (values options/c stxish)) + #:on-too-short (-> keyword? options/c stxish optstx + (values options/c stxish)) + #:on-not-in-table (-> keyword? options/c stxish optstx + (values options/c stxish))) + (values options/c stxish))] + [parse-keyword-options/eol + (->* (syntax? dict?) + (#:context optstx + #:no-duplicates? boolean? + #:incompatible (listof (listof keyword?)) + #:on-incompatible (-> keyword? keyword? options/c stxish optstx + (values options/c stxish)) + #:on-too-short (-> keyword? options/c stxish optstx + (values options/c stxish)) + #:on-not-in-table (-> keyword? options/c stxish optstx + (values options/c stxish)) + #:on-not-eol (-> options/c stxish optstx + options/c)) + options/c)] + + [options-select + (-> options/c keyword? + (listof list?))] + [options-select-one + (-> options/c keyword? #:default any/c + any/c)] + + [check-expression checker] + [check-identifier checker] + [check-stx-string checker] + [check-stx-listof (-> checker checker)]) diff --git a/collects/syntax/private/keyword.ss b/collects/syntax/private/keyword.ss new file mode 100644 index 0000000000..f1afb120b8 --- /dev/null +++ b/collects/syntax/private/keyword.ss @@ -0,0 +1,195 @@ +#lang scheme/base + +;; No-contract version... + +(require syntax/stx + scheme/dict) +(provide parse-keyword-options + parse-keyword-options/eol + options-select + options-select-one + + check-expression + check-identifier + check-stx-string + check-stx-listof) + +;; Parsing keyword arguments + +;; KeywordTable = (listof (cons keyword (listof CheckProc))) +;; Options = (listof (list* keyword syntax-keyword (listof any))) + +;; CheckProc = syntax syntax -> any +;; The first arg is syntax to check, second arg is context. + +;; incompatible-handler : keyword keyword Options syntax syntax -> (values Options syntax) +(define (default-incompatible kw1 kw2 chunks stx ctx) + (if (eq? kw1 kw2) + (raise-syntax-error #f "duplicate keyword option" ctx (stx-car stx)) + (raise-syntax-error #f + (format "~s option not allowed after ~s option" kw2 kw1) + ctx (stx-car stx)))) + +;; too-short-handler : keyword Options syntax syntax -> (values Options syntax) +(define (default-too-short kw chunks stx ctx) + (raise-syntax-error #f "too few arguments for keyword" ctx (stx-car stx))) + +;; not-in-table-handler : keyword syntax syntax -> (values Options syntax) +(define ((default-not-in-table kws) kw stx ctx) + (raise-syntax-error #f + (format "unexpected keyword, expected one of ~s" kws) + ctx (stx-car stx))) + +;; not-eol-handler : Options syntax syntax -> (values Options syntax) +(define (default-not-eol chunks stx ctx) + (raise-syntax-error #f + "terms left over after keyword options" + ctx + stx)) + +(define (parse-keyword-options/eol stx table + #:context [ctx #f] + #:no-duplicates? [no-duplicates? #f] + #:incompatible [incompatible null] + #:on-incompatible [incompatible-handler default-incompatible] + #:on-too-short [too-short-handler default-too-short] + #:on-not-in-table [not-in-table-handler + (default-not-in-table (map car table))] + #:on-not-eol [not-eol-handler default-not-eol]) + (define-values (chunks rest) + (parse-keyword-options stx table + #:context ctx + #:no-duplicates? no-duplicates? + #:incompatible incompatible + #:on-incompatible incompatible-handler + #:on-too-short too-short-handler + #:on-not-in-table not-in-table-handler)) + (if (stx-null? rest) + chunks + (not-eol-handler chunks stx ctx))) + +(define (list-ne-tails lst) + (if (pair? lst) + (cons lst (list-ne-tails (cdr lst))) + null)) + +;; parse-keyword-options : syntax KeywordTable ... -> (values Options syntax) +;; incompatible-handler is also used for duplicates (same kw arg) +;; incompatible is (listof (list keyword keyword)); reflexive closure taken +(define (parse-keyword-options stx table + #:context [ctx #f] + #:no-duplicates? [no-duplicates? #f] + #:incompatible [incompatible null] + #:on-incompatible [incompatible-handler default-incompatible] + #:on-too-short [too-short-handler default-too-short] + #:on-not-in-table [not-in-table-handler + (default-not-in-table (map car table))]) + (define interfere-table + (let ([table (make-hash)]) + (for ([entry incompatible]) + (for ([tail (list-ne-tails entry)]) + (for ([next (cdr tail)]) + (hash-set! table (list (car tail) next) #t) + (hash-set! table (list next (car tail)) #t)))) + table)) + (define (interferes kw seen) + (for/or ([seen-kw (in-dict-keys seen)]) + (and (hash-ref interfere-table (list seen-kw kw) #f) + seen-kw))) + (define (loop stx rchunks seen) + (syntax-case stx () + [(kw . more) + (keyword? (syntax-e #'kw)) + (let* ([kw-value (syntax-e #'kw)] + [entry (assq kw-value table)]) + (cond [(and no-duplicates? + (hash-ref seen kw-value #f)) + (incompatible-handler kw-value kw-value (reverse rchunks) stx ctx)] + [(interferes kw-value seen) => + (lambda (seen-kw) + (incompatible-handler seen-kw kw-value (reverse rchunks) stx ctx))] + [entry + (let* ([arity (cdr entry)] + [args+rest (stx-split #'more arity)]) + (if args+rest + (let ([args (for/list ([arg (car args+rest)] [proc arity]) + (proc arg ctx))] + [rest (cdr args+rest)]) + (loop rest + (cons (list* kw-value #'kw args) rchunks) + (hash-set seen kw-value #t))) + (too-short-handler kw-value (reverse rchunks) stx ctx)))] + [else + (not-in-table-handler kw-value stx ctx)]))] + [_ + (values (reverse rchunks) stx)])) + (loop stx null (make-immutable-hasheq '()))) + +;; stx-split : stx (listof any) -> (cons (listof stx) stx) +(define (stx-split stx arity) + (define (loop stx arity acc) + (cond [(null? arity) + (cons (reverse acc) stx)] + [(stx-pair? stx) + (loop (stx-cdr stx) (cdr arity) (cons (stx-car stx) acc))] + [else #f])) + (loop stx arity null)) + +;; options-select : Options keyword -> (listof (listof any)) +(define (options-select chunks kw) + (for/list ([chunk chunks] + #:when (eq? kw (car chunk))) + (cddr chunk))) + +;; options-select-one : Options keyword -> any +(define (options-select-one chunks kw + #:default default) + (let ([results (options-select chunks kw)]) + (cond [(null? results) + default] + [(null? (cdr results)) + (car results)] + [else + (error 'options-select-one "multiple occurrences of ~s keyword option" kw)]))) + +;; Check Procedures + +;; check-identifier : stx stx -> identifier +(define (check-identifier stx ctx) + (unless (identifier? stx) + (raise-syntax-error #f "expected identifier" ctx stx)) + stx) + +;; check-expression : stx stx -> stx +(define (check-expression stx ctx) + (when (keyword? (syntax-e stx)) + (raise-syntax-error #f "expected expression" ctx stx)) + stx) + +;; check-stx-string : stx stx -> stx +(define (check-stx-string stx ctx) + (unless (string? (syntax-e stx)) + (raise-syntax-error #f "expected string" ctx stx)) + stx) + +;; check-stx-boolean : stx stx -> stx +(define (check-stx-boolean stx ctx) + (unless (boolean? (syntax-e stx)) + (raise-syntax-error #f "expected boolean" ctx stx)) + stx) + +#| +;; check-nat/f : stx stx -> stx +(define (check-nat/f stx ctx) + (let ([d (syntax-e stx)]) + (unless (or (eq? d #f) (exact-nonnegative-integer? d)) + (raise-syntax-error #f "expected exact nonnegative integer or #f" ctx stx)) + stx)) +|# + +;; check-stx-listof : (stx stx -> A) -> stx stx -> (listof A) +(define ((check-stx-listof check) stx ctx) + (unless (stx-list? stx) + (raise-syntax-error #f "expected list" ctx stx)) + (for/list ([x (stx->list stx)]) + (check x ctx))) diff --git a/collects/syntax/private/stxparse/parse.ss b/collects/syntax/private/stxparse/parse.ss index 2c5163a281..5fb6ed8f90 100644 --- a/collects/syntax/private/stxparse/parse.ss +++ b/collects/syntax/private/stxparse/parse.ss @@ -4,6 +4,7 @@ scheme/private/sc syntax/stx syntax/id-table + syntax/keyword "rep-data.ss" "rep.ss" "codegen-data.ss" @@ -138,7 +139,12 @@ [(parse:clauses x clauses) (let () (define-values (chunks clauses-stx) - (chunk-kw-seq/no-dups #'clauses parse-directive-table)) + (parse-keyword-options #'clauses parse-directive-table + #:context stx + #:no-duplicates? #t)) + (define context + (let ([c (options-select-one chunks '#:context #:default #f)]) + (if c (car c) #'#f))) (define-values (decls0 defs) (get-decls+defs chunks)) (define (for-clause clause) (syntax-case clause () @@ -160,9 +166,10 @@ (with-syntax ([(def ...) defs] [(alternative ...) (map for-clause (stx->list clauses-stx))]) - #`(let () + #`(let ([fail (syntax-patterns-fail #,context)]) def ... - (try alternative ...))))])) + (with-enclosing-fail* fail + (try alternative ...)))))])) (define-for-syntax (wash-literal stx) (syntax-case stx () diff --git a/collects/syntax/private/stxparse/rep.ss b/collects/syntax/private/stxparse/rep.ss index 09205e856d..63096f03e2 100644 --- a/collects/syntax/private/stxparse/rep.ss +++ b/collects/syntax/private/stxparse/rep.ss @@ -6,6 +6,7 @@ scheme/dict syntax/id-table syntax/stx + syntax/keyword "../util.ss" "rep-data.ss" "codegen-data.ss") @@ -29,10 +30,10 @@ (-> DeclEnv/c (values DeclEnv/c (listof syntax?)))] [check-literals-list - (-> syntax? + (-> syntax? syntax? (listof (list/c identifier? identifier?)))] [check-literal-sets-list - (-> syntax? + (-> syntax? syntax? (listof (listof (list/c identifier? identifier?))))] [append-lits+litsets (-> (listof (list/c identifier? identifier?)) @@ -104,7 +105,9 @@ (define (parse-rhs/part1 stx strict? ctx) (define-values (chunks rest) - (chunk-kw-seq/no-dups stx rhs-directive-table #:context ctx)) + (parse-keyword-options stx rhs-directive-table + #:context ctx + #:no-duplicates? #t)) (define desc0 (assq '#:description chunks)) (define trans0 (assq '#:transparent chunks)) (define attrs0 (assq '#:attributes chunks)) @@ -506,16 +509,16 @@ (define (parse-bind-clause clause) (syntax-case clause () [(attr-decl expr) - (make clause:attr (check-attr-arity #'attr-decl) #'expr)] + (make clause:attr (check-attr-arity #'attr-decl #f) #'expr)] [_ (wrong-syntax clause "expected bind clause")])) (define (parse-pat:fail stx decls) (syntax-case stx () [(_ . rest) (let-values ([(chunks rest) - (chunk-kw-seq/no-dups #'rest - fail-directive-table - #:context stx)]) + (parse-keyword-options #'rest fail-directive-table + #:context stx + #:no-duplicates? #t)]) ;; chunks has 0 or 1 of each of #:when, #:unless ;; if has both, second one is bad; report it (when (> (length chunks) 1) @@ -572,13 +575,15 @@ (syntax-case stx (~optional) [(~optional p . options) (let ([head (parse-head-pattern #'p decls)]) - (with-syntax ([((too-many-msg) (name)) - (parse-kw-options #'options - (list (list '#:too-many values) - (list '#:name values)) - (list (list '#:too-many #'#f) - (list '#:name #'#f)) - #:context stx)]) + (define chunks + (parse-keyword-options/eol #'options + (list (list '#:too-many check-expression) + (list '#:name check-expression)) + #:context stx)) + (with-syntax ([(too-many-msg) + (options-select-one chunks '#:too-many #:default #'(#f))] + [(name) + (options-select-one chunks '#:name #:default #'(#f))]) (make ehpat (map attr-make-uncertain (pattern-attrs head)) head (make rep:optional #'name #'too-many-msg))))])) @@ -587,15 +592,18 @@ (syntax-case stx (~once) [(~once p . options) (let ([head (parse-head-pattern #'p decls)]) - (with-syntax ([((too-few-msg) (too-many-msg) (name)) - (parse-kw-options #'options - (list (list '#:too-few values) - (list '#:too-many values) - (list '#:name values)) - (list (list '#:too-few #'#f) - (list '#:too-many #'#f) - (list '#:name #'#f)) - #:context stx)]) + (define chunks + (parse-keyword-options/eol #'options + (list (list '#:too-few check-expression) + (list '#:too-many check-expression) + (list '#:name check-expression)) + #:context stx)) + (with-syntax ([(too-few-msg) + (options-select-one chunks '#:too-few #:default #'(#f))] + [(too-many-msg) + (options-select-one chunks '#:too-many #:default #'(#f))] + [(name) + (options-select-one chunks '#:name #:default #'(#f))]) (make ehpat (pattern-attrs head) head (make rep:once #'name #'too-few-msg #'too-many-msg))))])) @@ -614,18 +622,21 @@ "expected exact nonnegative integer or +inf.0")) (when (> minN maxN) (wrong-syntax stx "minumum larger than maximum repetition constraint")) - (with-syntax ([((too-few-msg) (too-many-msg) (name)) - (parse-kw-options #'options - (list (list '#:too-few values) - (list '#:too-many values) - (list '#:name values)) - (list (list '#:too-few #'#f) - (list '#:too-many #'#f) - (list '#:name #'#f)))]) - (make ehpat (map increase-depth (pattern-attrs head)) - head - (make rep:bounds #'min #'max #'name - #'too-few-msg #'too-many-msg))))])) + (let ([chunks (parse-keyword-options #'options + (list (list '#:too-few check-expression) + (list '#:too-many check-expression) + (list '#:name check-expression)) + #:context stx)]) + (with-syntax ([(too-few-msg) + (options-select-one chunks '#:too-few #:default #'(#f))] + [(too-many-msg) + (options-select-one chunks '#:too-many #:default #'(#f))] + [(name) + (options-select-one chunks '#:name #:default #'(#f))]) + (make ehpat (map increase-depth (pattern-attrs head)) + head + (make rep:bounds #'min #'max #'name + #'too-few-msg #'too-many-msg)))))])) ;; ----- @@ -635,7 +646,7 @@ #:decls [decls #f] #:allow-declare? [allow-declare? #t]) (define-values (chunks rest) - (chunk-kw-seq stx pattern-directive-table)) + (parse-keyword-options stx pattern-directive-table)) (define-values (decls2 chunks2) (if allow-declare? (grab-decls chunks decls) @@ -696,46 +707,39 @@ ;; Keyword Options & Checkers -;; check-lit-string : stx -> string -(define (check-lit-string stx) - (let ([x (syntax-e stx)]) - (unless (string? x) - (wrong-syntax stx "expected string literal")) - x)) - -;; check-attr-arity-list : stx -> (listof SAttr) -(define (check-attr-arity-list stx) +;; check-attr-arity-list : stx stx -> (listof SAttr) +(define (check-attr-arity-list stx ctx) (unless (stx-list? stx) - (wrong-syntax stx "expected list of attribute declarations")) - (let ([iattrs (map check-attr-arity (stx->list stx))]) + (raise-syntax-error #f "expected list of attribute declarations" ctx stx)) + (let ([iattrs (for/list ([x (stx->list stx)]) (check-attr-arity x ctx))]) (iattrs->sattrs (append-iattrs (map list iattrs))))) -;; check-attr-arity : stx -> IAttr -(define (check-attr-arity stx) +;; 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) - (wrong-syntax #'attr "expected attribute name")) + (raise-syntax-error #f "expected attribute name" ctx #'attr)) (unless (exact-nonnegative-integer? (syntax-e #'depth)) - (wrong-syntax #'depth "expected depth (nonnegative integer)")) + (raise-syntax-error #f "expected depth (nonnegative integer)" ctx #'depth)) (make-attr #'attr (syntax-e #'depth) #f))] [_ - (wrong-syntax stx "expected attribute name with optional depth declaration")])) + (raise-syntax-error #f "expected attribute name with optional depth declaration" ctx stx)])) -;; check-literals-list : syntax -> (listof (list id id)) -(define (check-literals-list stx) +;; check-literals-list : stx stx -> (listof (list id id)) +(define (check-literals-list stx ctx) (unless (stx-list? stx) - (wrong-syntax stx "expected literals list")) - (let ([lits (map check-literal-entry (stx->list stx))]) + (raise-syntax-error #f "expected literals list" ctx stx)) + (let ([lits (for/list ([x (stx->list stx)]) (check-literal-entry x ctx))]) (let ([dup (check-duplicate-identifier (map car lits))]) - (when dup (wrong-syntax dup "duplicate literal identifier"))) + (when dup (raise-syntax-error #f "duplicate literal identifier" ctx dup))) lits)) -;; check-literal-entry : syntax -> (list id id) -(define (check-literal-entry stx) +;; check-literal-entry : stx stx -> (list id id) +(define (check-literal-entry stx ctx) (syntax-case stx () [(internal external) (and (identifier? #'internal) (identifier? #'external)) @@ -744,96 +748,103 @@ (identifier? #'id) (list #'id #'id)] [_ - (wrong-syntax stx - "expected literal (identifier or pair of identifiers)")])) + (raise-syntax-error #f "expected literal (identifier or pair of identifiers)" ctx stx)])) -(define (check-literal-sets-list stx) +(define (check-literal-sets-list stx ctx) (unless (stx-list? stx) - (wrong-syntax stx "expected literal-set list")) - (map check-literal-set-entry (stx->list stx))) + (raise-syntax-error #f "expected literal-set list" ctx stx)) + (for/list ([x (stx->list stx)]) + (check-literal-set-entry x ctx))) -(define (check-literal-set-entry stx) - (define (elaborate litset-id context) +(define (check-literal-set-entry stx ctx) + (define (elaborate litset-id lctx) (let ([litset (syntax-local-value litset-id (lambda () #f))]) (unless (literalset? litset) - (wrong-syntax litset-id "expected identifier defined as a literal-set")) - (elaborate-litset litset context stx))) + (raise-syntax-error #f "expected identifier defined as a literal-set" ctx litset-id)) + (elaborate-litset litset lctx stx))) (syntax-case stx () - [(litset #:at context) - (and (identifier? #'litset) (identifier? #'context)) - (elaborate #'litset #'context)] + [(litset #:at lctx) + (and (identifier? #'litset) (identifier? #'lctx)) + (elaborate #'litset #'lctx)] [litset (identifier? #'litset) (elaborate #'litset #'litset)] [_ - (wrong-syntax stx "expected literal-set entry")])) + (raise-syntax-error #f "expected literal-set entry" ctx stx)])) -(define (elaborate-litset litset context ctx) +(define (elaborate-litset litset lctx srcctx) (for/list ([entry (literalset-literals litset)]) - (list (datum->syntax context (car entry) ctx) + (list (datum->syntax lctx (car entry) srcctx) (cadr entry)))) -(define (check-conventions-list stx) +(define (check-conventions-list stx ctx) (unless (stx-list? stx) - (wrong-syntax stx "expected conventions list")) - (map check-conventions (stx->list stx))) + (raise-syntax-error #f "expected conventions list" ctx stx)) + (for/list ([x (stx->list stx)]) + (check-conventions x ctx))) -(define (check-conventions stx) +(define (check-conventions stx ctx) (define (elaborate conventions-id) (let ([cs (syntax-local-value conventions-id (lambda () #f))]) (unless (conventions? cs) - (wrong-syntax conventions-id "expected identifier defined as a conventions")) + (raise-syntax-error #f "expected identifier defined as a conventions" ctx conventions-id)) (conventions-rules cs))) (syntax-case stx () [conventions (identifier? #'conventions) (elaborate #'conventions)] [_ - (wrong-syntax stx "expected conventions entry")])) + (raise-syntax-error "expected conventions entry" ctx stx)])) -(define (check-conventions-rules stx) +(define (check-conventions-rules stx ctx) (unless (stx-list? stx) - (wrong-syntax stx "expected convention rule list")) - (map check-conventions-rule (stx->list stx))) + (raise-syntax-error #f "expected convention rule list" ctx stx)) + (for/list ([x (stx->list stx)]) + (check-conventions-rule x ctx))) -(define (check-conventions-rule stx) +(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 (wrong-syntax blame "expected identifier convention pattern")])) + [else (raise-syntax-error #f "expected identifier convention pattern" ctx blame)])) (define (check-sc-expr x) (syntax-case x () [sc (identifier? #'sc) (list #'sc null)] [(sc arg ...) (identifier? #'sc) (list #'sc #'(arg ...))] - [_ (wrong-syntax x "expected syntax class use")])) + [_ (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))])) -;; parse-directive-table -(define parse-directive-table +;; 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))) +;; 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 values) + (list* (list '#:description check-expression) (list '#:transparent) (list '#:attributes check-attr-arity-list) (list '#:auto-nested-attributes) - parse-directive-table)) + common-parse-directive-table)) ;; pattern-directive-table (define pattern-directive-table - (list (list '#:declare check-id values) - (list '#:fail-when values values) - (list '#:fail-unless values values) - (list '#:with values values) - (list '#:attr check-attr-arity values))) + (list (list '#:declare check-identifier check-expression) + (list '#:fail-when check-expression check-expression) + (list '#:fail-unless check-expression check-expression) + (list '#:with check-expression check-expression) + (list '#:attr check-attr-arity check-expression))) ;; fail-directive-table (define fail-directive-table - (list (list '#:when values) - (list '#:unless values))) + (list (list '#:when check-expression) + (list '#:unless check-expression))) diff --git a/collects/syntax/private/stxparse/runtime-prose.ss b/collects/syntax/private/stxparse/runtime-prose.ss index d5f9fd9aa2..de5cfa0aa4 100644 --- a/collects/syntax/private/stxparse/runtime-prose.ss +++ b/collects/syntax/private/stxparse/runtime-prose.ss @@ -10,13 +10,25 @@ (for-syntax "rep-data.ss") (for-syntax "../util/error.ss") "runtime.ss") -(provide default-failure-handler) +(provide syntax-patterns-fail + current-failure-handler) + +;; Failure reporting parameter & default (define (default-failure-handler stx0 f) (match (simplify-failure f) [(struct failure (x frontier frontier-stx expected)) (report-failure stx0 x (last frontier) frontier-stx expected)])) +(define current-failure-handler + (make-parameter default-failure-handler)) + +(define ((syntax-patterns-fail stx0) f) + (let ([value ((current-failure-handler) stx0 f)]) + (error 'current-failure-handler + "current-failure-handler: did not escape, produced ~e" value))) + + ;; report-failure : stx stx number stx Expectation -> (escapes) (define (report-failure stx0 x index frontier-stx expected) (define (err msg stx0 stx) diff --git a/collects/syntax/private/stxparse/sc.ss b/collects/syntax/private/stxparse/sc.ss index 3f6b00e7f5..f866f30d9e 100644 --- a/collects/syntax/private/stxparse/sc.ss +++ b/collects/syntax/private/stxparse/sc.ss @@ -87,7 +87,7 @@ (unless (identifier? #'name) (raise-syntax-error #f "expected identifier" stx #'name)) (with-syntax ([([entry (def ...)] ...) - (for/list ([line (check-conventions-rules #'(rule ...))]) + (for/list ([line (check-conventions-rules #'(rule ...) stx)]) (let ([rx (car line)] [sc (car (cadr line))] [args (cadr (cadr line))]) @@ -111,7 +111,7 @@ (begin (unless (identifier? #'name) (raise-syntax-error #f "expected identifier" stx #'name)) - (let ([lits (check-literals-list #'(lit ...))]) + (let ([lits (check-literals-list #'(lit ...) stx)]) (with-syntax ([((internal external) ...) lits]) #'(define-syntax name (make-literalset @@ -189,10 +189,7 @@ 'report-errors-as (syntax-e #'report-as)))) #`(let ([x expr]) - (let ([fail (syntax-patterns-fail x)]) - (with-enclosing-fail* fail - (parameterize ((current-expression (or (current-expression) x))) - (parse:clauses x clauses)))))))])) + (parse:clauses x clauses))))])) (define-syntax with-patterns (syntax-rules () @@ -200,14 +197,3 @@ (let () . b)] [(with-patterns ([p x] . more) . b) (syntax-parse x [p (with-patterns more . b)])])) - -;; Failure reporting parameter & default - -(define current-failure-handler - (make-parameter default-failure-handler)) - -(define ((syntax-patterns-fail stx0) f) - (let ([value ((current-failure-handler) stx0 f)]) - (error 'current-failure-handler - "current-failure-handler: did not escape, produced ~e" value))) - diff --git a/collects/syntax/private/util/misc.ss b/collects/syntax/private/util/misc.ss index f7da0ece40..e92246fd9c 100644 --- a/collects/syntax/private/util/misc.ss +++ b/collects/syntax/private/util/misc.ss @@ -23,6 +23,7 @@ in-stx-list in-stx-list/unwrap + #| parse-kw-options extract-kw-option chunk-kw-seq/no-dups @@ -32,7 +33,8 @@ check-id check-nat/f check-string - check-idlist) + check-idlist + |#) ;; Unwrapping syntax diff --git a/collects/syntax/scribblings/keyword.scrbl b/collects/syntax/scribblings/keyword.scrbl new file mode 100644 index 0000000000..80dd1481b0 --- /dev/null +++ b/collects/syntax/scribblings/keyword.scrbl @@ -0,0 +1,255 @@ +#lang scribble/doc +@(require scribble/manual + scribble/struct + scribble/decode + scribble/eval + scheme/sandbox + (for-label scheme/base + scheme/contract + scheme/dict + syntax/keyword)) + +@(begin + (define the-eval + (parameterize ((sandbox-output 'string) + (sandbox-error-output 'string)) + (make-evaluator 'scheme/base #:requires '(syntax/keyword)))) + ;;(void (the-eval '(error-print-source-location #f))) + (define-syntax-rule (myexamples e ...) + (parameterize (#|(error-print-source-location #f)|#) + (examples #:eval the-eval e ...)))) + + +@title[#:tag "stxkeyword"]{Helpers for Processing Keyword Syntax} + +The @schememodname[syntax/keyword] module contains procedures for +parsing keyword options in macros. + +@defmodule[syntax/keyword] + +@schemegrammar[#, @deftech{keyword-table} + (dict-of keyword (listof check-procedure))] + +A keyword-table is a dictionary (@scheme[dict?]) mapping keywords to +lists of @techlink{check-procedures}. (Note that an association list is a +suitable dictionary.) The keyword's arity is the length of the list of +procedures. + +@myexamples[ +(define my-keyword-table + (list (list '#:a check-identifier) + (list '#:b check-expression check-expression))) +] + +@schemegrammar[#, @deftech{check-procedure} + (syntax syntax -> any)] + +A check procedure consumes the syntax to check and a context syntax +object for error reporting and either raises an error to reject the +syntax or returns a value as its parsed representation. + +@myexamples[ +(define (check-stx-string stx context-stx) + (unless (string? (syntax-e stx)) + (raise-syntax-error #f "expected string" context-stx stx)) + stx) +] + +@schemegrammar[#, @deftech{options} + (listof (list keyword syntax-keyword any ...))] + +Parsed options are represented as an list of option entries. Each +entry contains the keyword, the syntax of the keyword (for error +reporting), and the list of parsed values returned by the keyword's +list of check procedures. The list contains the parsed options in the +order they appeared in the input, and a keyword that occurs multiple +times in the input occurs multiple times in the options list. + +@defproc[(parse-keyword-options [stx syntax?] + [table #, @techlink{keyword-table}] + [#:context ctx (or/c false/c syntax?) #f] + [#:no-duplicates? no-duplicates? boolean? #f] + [#:incompatible incompatible (listof (listof keyword?)) '()] + [#:on-incompatible incompatible-handler + (-> keyword? keyword? + #, @techlink{options} syntax? syntax? + (values #, @techlink{options} syntax?)) + (lambda (....) (error ....))] + [#:on-too-short too-short-handler + (-> keyword? #, @techlink{options} syntax? syntax? + (values #, @techlink{options} syntax?)) + (lambda (....) (error ....))] + [#:on-not-in-table not-in-table-handler + (-> keyword? #, @techlink{options} syntax? syntax? + (values #, @techlink{options} syntax?)) + (lambda (....) (error ....))]) + (values #, @techlink{options} any/c)]{ + +Parses the keyword options in the syntax @scheme[stx] (@scheme[stx] +may be an improper syntax list). The keyword options are described in +the @scheme[table] association list. Each entry in @scheme[table] +should be a list whose first element is a keyword and whose subsequent +elements are procedures for checking the arguments following the +keyword. The keyword's arity (number of arguments) is determined by +the number of procedures in the entry. Only fixed-arity keywords are +supported. + +Parsing stops normally when the syntax list does not have a keyword at +its head (it may be empty, start with a non-keyword term, or it may be +a non-list syntax object). Two values are returned: the parsed +@techlink{options} and the rest of the syntax (generally either a +syntax object or a list of syntax objects). + +A variety of errors and exceptional conditions can occur during the +parsing process. The following keyword arguments determine the +behavior in those situations. + +The @scheme[#:context ctx] argument is used to report all errors in +parsing syntax. In addition, @scheme[ctx] is passed as the final +argument to all provided handler procedures. Macros using +@scheme[parse-keyword-options] should generally pass the syntax object +for the whole macro use as @scheme[ctx]. + +If @scheme[no-duplicates?] is a non-false value, then duplicate +keyword options are not allowed. If a duplicate is seen, the keyword's +associated check procedures are not called and an @tech{incompatibility} is +reported. + +The @scheme[incompatible] argument is a list of incompatibility +entries, where each entry is a list of @emph{at least two} +keywords. If any keyword in the entry occurs after any other keyword +in the entry, an @tech{incompatibility} is reported. + +Note that including a keyword in an incompatibility entry does not +prevent it from occurring multiple times. To disallow duplicates of +some keywords (as opposed to all keywords), include those keywords in +the @scheme[incompatible] list as being incompatible with +themselves. That is, include them twice: + +@schemeblock[ +(code:comment "Disallow duplicates of only the #:foo keyword") +(parse-keyword-options .... #:incompatible '((#:foo #:foo))) +] + +When an @deftech{incompatibility} occurs, the +@scheme[incompatible-handler] is tail-called with the two keywords +causing the incompatibility (in the order that they occurred in the +syntax list, so the keyword triggering the incompatibility occurs +second), the syntax list starting with the occurrence of the second +keyword, and the context (@scheme[ctx]). If the incompatibility is due +to a duplicate, the two keywords are the same. + +When a keyword is not followed by enough arguments according to its +arity in @scheme[table], the @scheme[too-short-handler] is tail-called +with the keyword, the @techlink{options} parsed thus far, the syntax list +starting with the occurrence of the keyword, and @scheme[ctx]. + +When a keyword occurs in the syntax list that is not in +@scheme[table], the @scheme[not-in-table-handler] is tail-called with +the keyword, the @techlink{options} parsed thus far, the syntax list +starting with the occurrence of the keyword, and @scheme[ctx]. + +Handlers typically escape---all of the default handlers raise +errors---but if they return, they should return two values: the parsed +@techlink{options} and a syntax object; these are returned as the results +of @scheme[parse-keyword-options]. + +@(myexamples + (parse-keyword-options + #'(#:transparent #:property p (lambda (x) (f x))) + (list (list '#:transparent) + (list '#:inspector check-expression) + (list '#:property check-expression check-expression))) + (parse-keyword-options + #'(#:transparent #:inspector (make-inspector)) + (list (list '#:transparent) + (list '#:inspector check-expression) + (list '#:property check-expression check-expression)) + #:context #'define-struct + #:incompatible '((#:transparent #:inspector) + (#:inspector #:inspector) + (#:inspector #:inspector)))) + +} + +@defproc[(parse-keyword-options/eol [stx syntax?] + [table #, @techlink{keyword-table}] + [#:context ctx (or/c false/c syntax?) #f] + [#:no-duplicates? no-duplicates? boolean? #f] + [#:incompatible incompatible (listof (list keyword? keyword?)) '()] + [#:on-incompatible incompatible-handler + (-> keyword? keyword? + #, @techlink{options} syntax? syntax? + (values #, @techlink{options} syntax?)) + (lambda (....) (error ....))] + [#:on-too-short too-short-handler + (-> keyword? #, @techlink{options} syntax? syntax? + (values #, @techlink{options} syntax?)) + (lambda (....) (error ....))] + [#:on-not-in-table not-in-table-handler + (-> keyword? #, @techlink{options} syntax? syntax? + (values #, @techlink{options} syntax?)) + (lambda (....) (error ....))] + [#:on-not-eol not-eol-handler + (-> #, @techlink{options} syntax? syntax? + #, @techlink{options}) + (lambda (....) (error ....))]) + #, @techlink{options}]{ + +Like @scheme[parse-keyword-options], but checks that there are no +terms left over after parsing all of the keyword options. If there +are, @scheme[not-eol-handler] is tail-called with the @techlink{options} +parsed thus far, the leftover syntax, and @scheme[ctx]. + +} + +@defproc[(options-select [options #, @techlink{options}] + [keyword keyword?]) + (listof list?)]{ + +Selects the values associated with one keyword from the parsed +@techlink{options}. The resulting list has as many items as there were +occurrences of the keyword, and each element is a list whose length is +the arity of the keyword. + +} + +@defproc[(options-select-one [options #, @techlink{options}] + [keyword keyword?] + [#:default default any/c]) + any]{ + +Like @scheme[options-select], except that the given keyword must occur +either zero or one times in @scheme[options]. If the keyword occurs, +the associated list of parsed argument values is returned. Otherwise, +the @scheme[default] list is returned. + +} + +@defproc[(check-identifier [stx syntax?] [ctx (or/c false/c syntax?)]) identifier?]{ + +A @techlink{check-procedure} that accepts only identifiers. + +} + +@defproc[(check-expression [stx syntax?] [ctx (or/c false/c syntax?)]) syntax?]{ + +A @techlink{check-procedure} that accepts any non-keyword term. It does +not actually check that the term is a valid expression. + +} + +@defproc[((check-stx-listof [check #, @techlink{check-procedure}]) + [stx syntax?] [ctx (or/c false/c syntax?)]) + (listof any/c)]{ + +Lifts a @techlink{check-procedure} to accept syntax lists of whatever the +original procedure accepted. + +} + +@defproc[(check-stx-string [stx syntax?] [ctx (or/c false/c syntax?)]) syntax?]{ + +A @techlink{check-procedure} that accepts syntax strings. + +} diff --git a/collects/syntax/scribblings/parse.scrbl b/collects/syntax/scribblings/parse.scrbl index ec02bd8c64..4eb62bcd19 100644 --- a/collects/syntax/scribblings/parse.scrbl +++ b/collects/syntax/scribblings/parse.scrbl @@ -273,7 +273,8 @@ examining its structure. Syntax classes are useful for the same purpose, but @scheme[~and] can be lighter weight. @(interaction-eval #:eval the-eval - (begin (define (check-imports . _) #f))) + (begin (define import #f) + (define (check-imports . _) #f))) @myexamples[ (syntax-parse #'(m (import one two)) diff --git a/collects/syntax/scribblings/syntax-object-helpers.scrbl b/collects/syntax/scribblings/syntax-object-helpers.scrbl index ea843877ce..bdf563a200 100644 --- a/collects/syntax/scribblings/syntax-object-helpers.scrbl +++ b/collects/syntax/scribblings/syntax-object-helpers.scrbl @@ -10,5 +10,6 @@ @include-section["to-string.scrbl"] @include-section["free-vars.scrbl"] @include-section["strip-context.scrbl"] +@include-section["keyword.scrbl"] @include-section["zodiac.scrbl"] diff --git a/collects/tests/stxclass/more-tests.ss b/collects/tests/stxclass/more-tests.ss index 218380babd..515fb304b5 100644 --- a/collects/tests/stxclass/more-tests.ss +++ b/collects/tests/stxclass/more-tests.ss @@ -37,16 +37,16 @@ (define-syntax (m stx) (syntax-parse stx [(_ x) - #:declare x (static-of number? "identifier bound to number") + #:declare x (static number? "identifier bound to number") #`(quote #,(attribute x.value))])) -(test-case "static-of: right error" +(test-case "static: right error" (check-exn (lambda (exn) (regexp-match? #rx"identifier bound to number" (exn-message exn))) (lambda () (convert-syntax-error (m twelve))))) -(test-case "static-of: works" +(test-case "static: works" (check-equal? (convert-syntax-error (m zero)) 0))