diff --git a/private/syntax/compile/binding-spec.rkt b/private/syntax/compile/binding-spec.rkt index cf8667b..9b2eec1 100644 --- a/private/syntax/compile/binding-spec.rkt +++ b/private/syntax/compile/binding-spec.rkt @@ -188,24 +188,8 @@ (elaborate-pvar (attribute v-transformer) (? stxclass-rep?) "syntax class"))] - [(nest v:nonref-id spec:bspec-term) - (nest-one - this-syntax - (elaborate-pvar (attribute v) - (s* nonterm-rep [variant-info (s* nesting-nonterm-info)]) - "nesting nonterminal") - (elaborate-bspec (attribute spec)))] - [(nest ~! v:nonref-id (~and (~literal ...) ooo) ...+ spec:bspec-term) - (define depth (length (attribute ooo))) - (when (> depth 1) - (wrong-syntax/orig this-syntax "nest cannot contain more than one ellipsis")) - (nest - this-syntax - depth - (elaborate-pvar (attribute v) - (s* nonterm-rep [variant-info (s* nesting-nonterm-info)]) - "nesting nonterminal") - (elaborate-bspec (attribute spec)))] + [(nest ~! v:nonref-id rest ...+) + (elaborate-nest #'(nest v rest ...))] [(host ~! v:nonref-id) (suspend this-syntax @@ -225,12 +209,37 @@ [(spec (~and ooo (~literal ...)) ... . specs) ; however many ellipses follow the pattern, wrap the elaborated spec with ; the ellipses struct that many times. - (cons (for/fold ([spec (elaborate-bspec (attribute spec))]) + (cons (for/fold ([spec-elaborated (elaborate-bspec (attribute spec))]) ([ooo (attribute ooo)]) - (ellipsis ooo spec)) + (ellipsis (attribute spec) spec-elaborated)) (elaborate-group (attribute specs)))] [() '()])) +; helps convert (nest x y ... z e) stx +; into an elaborated representation like +; (next-one x (nest y (nest-one z e))) +(define elaborate-nest + (syntax-parser + [(_ spec) (elaborate-bspec #'spec)] + [(_ v:nonref-id (~and (~literal ...) ooo) ...+ rest ...+) + (define depth (length (attribute ooo))) + (when (> depth 1) + (wrong-syntax/syntax-spec this-syntax "nest cannot contain more than one ellipsis")) + (nest + this-syntax + depth + (elaborate-pvar (attribute v) + (s* nonterm-rep [variant-info (s* nesting-nonterm-info)]) + "nesting nonterminal") + (elaborate-nest #'(nest rest ...)))] + [(_ v:nonref-id rest ...+) + (nest-one + this-syntax + (elaborate-pvar (attribute v) + (s* nonterm-rep [variant-info (s* nesting-nonterm-info)]) + "nesting nonterminal") + (elaborate-nest #'(nest rest ...)))])) + ;; Elaborator helpers #;(identifier? -> BSpec) @@ -262,7 +271,7 @@ (define binding (lookup v pvar-rep?)) (when (not binding) (if (identifier? (current-syntax-context)) - (wrong-syntax/orig v "binding spec expected a reference to a pattern variable") + (wrong-syntax/syntax-spec v "binding spec expected a reference to a pattern variable") (wrong-syntax v "expected a reference to a pattern variable"))) (pvar-rep-var-info binding)) @@ -270,7 +279,7 @@ (define binding (lookup v pvar-rep?)) (when (not binding) (if (identifier? (current-syntax-context)) - (wrong-syntax/orig v "binding spec expected a reference to a pattern variable") + (wrong-syntax/syntax-spec v "binding spec expected a reference to a pattern variable") (wrong-syntax v "expected a reference to a pattern variable"))) (pvar-rep-depth binding)) @@ -279,7 +288,7 @@ (define maybe-dup (check-duplicates pvars free-identifier=?)) (when maybe-dup - (wrong-syntax/orig maybe-dup "each pattern variable must occur in the binding spec at most once"))) + (wrong-syntax/syntax-spec maybe-dup "each pattern variable must occur in the binding spec at most once"))) (define (check-ellipsis-depth! bspec) (let loop ([bspec bspec] [depth 0]) @@ -319,9 +328,9 @@ (define ss-depth (lookup-pvar-depth v)) (cond [(< ss-depth bs-depth) - (wrong-syntax/orig v "too many ellipses for pattern variable in binding spec")] + (wrong-syntax/syntax-spec v "too many ellipses for pattern variable in binding spec")] [(< bs-depth ss-depth) - (wrong-syntax/orig v "missing ellipses with pattern variable in binding spec")])) + (wrong-syntax/syntax-spec v "missing ellipses with pattern variable in binding spec")])) ; makes sure you don't mix categories like refs+subexps and binds in the same ellipsis (define (check-ellipsis-homogeneity! bspec) @@ -335,7 +344,7 @@ (define export (find-export spec)) (define representatives (filter values (list ref+subexp bind import export))) (when (< 1 (length representatives)) - (wrong-syntax/orig stx "cannot mix different binding spec categories inside of ellipses")) + (wrong-syntax/syntax-spec stx "cannot mix imports or exports with other kinds of binding specs inside of ellipses")) bspec] [_ bspec])) bspec)) @@ -510,13 +519,13 @@ (f (car specs) (cdr specs)))) (define (binding-scope-error stx) - (wrong-syntax/orig stx "binding must occur within a scope")) + (wrong-syntax/syntax-spec stx "binding must occur within a scope")) (define (export-context-error stx) - (wrong-syntax/orig stx "exports may only occur at the top-level of an exporting binding spec")) + (wrong-syntax/syntax-spec stx "exports may only occur at the top-level of an exporting binding spec")) (define (re-export-context-error stx) - (wrong-syntax/orig stx "re-exports may only occur at the top-level of an exporting binding spec")) + (wrong-syntax/syntax-spec stx "re-exports may only occur at the top-level of an exporting binding spec")) ; spec -> (void) or raised syntax error ; enforces the above grammar for an unscoped expression @@ -532,7 +541,7 @@ [(or (and (s* import) (with-stx stx)) (imports _ (cons (with-stx stx) _))) ; TODO use imports stx once it's sorce location is more refined. - (wrong-syntax/orig stx "import binding groups must occur within a scope")] + (wrong-syntax/syntax-spec stx "import binding groups must occur within a scope")] [(imports _ (list)) ; impossible (void)] @@ -576,14 +585,14 @@ (refs+subexps group-spec (append group-specs specs))] [(group _ (list)) (check-sequence refs+subexps specs)] [(and (or (s* bind) (s* bind-syntax) (s* bind-syntaxes)) (with-stx stx)) - (wrong-syntax/orig stx "bindings must appear first within a scope")] + (wrong-syntax/syntax-spec stx "bindings must appear first within a scope")] [(and (or (s* export) (s* export-syntax) (s* export-syntaxes)) (with-stx stx)) (export-context-error stx)] [(and (s* re-export) (with-stx stx)) (re-export-context-error stx)] [(or (and (s* import) (with-stx stx)) (imports _ (cons (with-stx stx) _))) - (wrong-syntax/orig stx "an import binding group must appear before references and subexpressions")] + (wrong-syntax/syntax-spec stx "an import binding group must appear before references and subexpressions")] [(imports _ (list)) ; impossible (void)] @@ -633,12 +642,12 @@ [(and (or (s* bind) (s* bind-syntax) (s* bind-syntaxes)) (with-stx stx)) (binding-scope-error stx)] [(and (or (s* export) (s* export-syntax) (s* export-syntaxes)) (with-stx stx)) - (wrong-syntax/orig stx "exports must appear first in a exporting spec")] + (wrong-syntax/syntax-spec stx "exports must appear first in a exporting spec")] [(and (s* re-export) (with-stx stx)) - (wrong-syntax/orig stx "re-exports must occur before references and subexpressions")] + (wrong-syntax/syntax-spec stx "re-exports must occur before references and subexpressions")] [(or (and (s* import) (with-stx stx)) (imports _ (cons (and (s* import) (with-stx stx)) _))) - (wrong-syntax/orig stx "import must occur within a scope")] + (wrong-syntax/syntax-spec stx "import must occur within a scope")] [(imports _ (list)) ; impossible (void)] @@ -668,9 +677,9 @@ [(nested-binding) #`(nested)] [(nonterm-rep (nesting-nonterm-info _)) - (wrong-syntax/orig v "nesting nonterminals may only be used with `nest`")] + (wrong-syntax/syntax-spec v "nesting nonterminals must be used with `nest`")] [(nonterm-rep (exporting-nonterm-info _ _)) - (wrong-syntax/orig v "exporting nonterminals may only be used with `import` and `re-export`")] + (wrong-syntax/syntax-spec v "exporting nonterminals must be used with `import` or `re-export`")] [(or (? stxclass-rep?) (? special-syntax-class-binding?)) #`(group (list))])] [(suspend _ (pvar v info)) diff --git a/private/syntax/compile/nonterminal-expander.rkt b/private/syntax/compile/nonterminal-expander.rkt index 00be156..1ce8066 100644 --- a/private/syntax/compile/nonterminal-expander.rkt +++ b/private/syntax/compile/nonterminal-expander.rkt @@ -117,7 +117,7 @@ (syntax-parse (car prods) [(~or (p:form-production) (p:form-rewrite-production)) (when (free-id-table-ref seen-forms #'p.form-name #f) - (wrong-syntax/orig #'p.form-name "all variants of the same-named form must occur together")) + (wrong-syntax/syntax-spec #'p.form-name "all variants of the same-named form must occur together")) (define-values (group remaining-prods) (gather-group prods)) (loop remaining-prods (free-id-table-set seen-forms #'p.form-name #t) (cons group res))] [_ (loop (cdr prods) seen-forms (cons (car prods) res))])))) @@ -214,7 +214,7 @@ (define (generate-macro-clause extclass recur-id) (let ([ext-info (lookup extclass extclass-rep?)]) (when (not ext-info) - (wrong-syntax/orig extclass "expected extension class name")) + (wrong-syntax/syntax-spec extclass "expected extension class name")) (with-syntax ([m-pred (extclass-rep-pred ext-info)] [m-acc (extclass-rep-acc ext-info)] diff --git a/private/syntax/compile/syntax-spec.rkt b/private/syntax/compile/syntax-spec.rkt index dad0aa3..4cb5261 100644 --- a/private/syntax/compile/syntax-spec.rkt +++ b/private/syntax/compile/syntax-spec.rkt @@ -72,7 +72,7 @@ #:do [(define binding (lookup #'r.ref stxclass-rep?))] #:when binding #'(~var r.var r.ref)] - [_ (wrong-syntax/orig this-syntax "expected a syntax spec term")])) + [_ (wrong-syntax/syntax-spec this-syntax "expected a syntax spec term")])) (generate-pattern-form stx)) @@ -143,7 +143,7 @@ [r:ref-id #:with c:special-syntax-class #'r.ref (when (member #'r.var res bound-identifier=?) - (wrong-syntax/orig #'r.ref "duplicate pattern variable")) + (wrong-syntax/syntax-spec #'r.var "duplicate pattern variable")) (bind! #'r.var (pvar-rep (special-syntax-class-binding) depth)) (set! res (cons #'r.var res))] [r:ref-id @@ -154,9 +154,9 @@ (nonterm-rep? v) (stxclass-rep? v))))) (when (not binding) - (wrong-syntax/orig #'r.ref "expected a reference to a binding class, extension class, syntax class, or nonterminal")) + (wrong-syntax/syntax-spec #'r.ref "expected a reference to a binding class, extension class, syntax class, or nonterminal")) (when (member #'r.var res bound-identifier=?) - (wrong-syntax/orig #'r.ref "duplicate pattern variable")) + (wrong-syntax/syntax-spec #'r.var "duplicate pattern variable")) (bind! #'r.var (pvar-rep binding depth)) (set! res (cons #'r.var res))] [_ (void)])) diff --git a/private/syntax/interface.rkt b/private/syntax/interface.rkt index 2e32249..4b8fb5c 100644 --- a/private/syntax/interface.rkt +++ b/private/syntax/interface.rkt @@ -179,19 +179,19 @@ #f))] [(host-interface/expression ~! (name:id . sspec) - (~optional (~seq #:binding bspec)) - parse-body ...+) + bdecl:maybe-binding-decl + c:compiler) (values #f #f #'(define-syntax name (expression-macro (generate-host-interface-transformer - name sspec (~? (bspec) ()) (#:simple) parse-body ...))))] + name sspec (~? (bdecl.bspec) ()) (#:simple) c.body ...))))] [(host-interface/definitions ~! (name:id . sspec) - (~optional (~seq #:binding bspec)) - parse-body ...+) + bdecl:maybe-binding-decl + c:compiler) (values #f #f @@ -200,12 +200,12 @@ (wrap-bind-trampoline (wrap-persist (generate-host-interface-transformer - name sspec (~? (bspec) ()) (#:pass1 #:pass2) parse-body ...))))))] + name sspec (~? (bdecl.bspec) ()) (#:pass1 #:pass2) c.body ...))))))] [(host-interface/definition ~! (name:id . sspec) - (~optional (~seq #:binding bspec)) - #:lhs [name-parse-body ...+] - #:rhs [rhs-parse-body ...+]) + bdecl:maybe-binding-decl + #:lhs [lhs-c:compiler] + #:rhs [rhs-c:compiler]) (values #f #f @@ -217,12 +217,12 @@ (wrap-bind-trampoline (wrap-persist (generate-host-interface-transformer/definition-pass1 - sspec (~? (bspec) ()) [name-parse-body ...] pass2-macro))))) + sspec (~? (bdecl.bspec) ()) [lhs-c.body ...] pass2-macro))))) ;; (before this one) (define-syntax pass2-macro (expression-macro (generate-host-interface-transformer - name sspec (~? (bspec) ()) (#:pass2) rhs-parse-body ...)))))]))) + name sspec (~? (bdecl.bspec) ()) (#:pass2) rhs-c.body ...)))))]))) (begin-for-syntax (define (generate-nonterminal-declarations name-stx opts-stx form-names variant-info-stx) @@ -248,8 +248,7 @@ (define-syntax generate-nonterminal-expander (syntax-parser [(_ orig-stx . decls) - (parameterize ([current-orig-stx #'orig-stx]) - (compile-nonterminal-expander #'decls))])) + (compile-nonterminal-expander #'decls)])) ) ;; diff --git a/private/syntax/syntax-classes.rkt b/private/syntax/syntax-classes.rkt index d3d4e1c..6fdedf5 100644 --- a/private/syntax/syntax-classes.rkt +++ b/private/syntax/syntax-classes.rkt @@ -3,6 +3,7 @@ (provide current-orig-stx wrong-syntax/orig + wrong-syntax/syntax-spec maybe-description maybe-binding-space @@ -47,7 +48,10 @@ extclass-spec nonterminal-options - ) + + compiler + parse-body + maybe-binding-decl) (require racket/string @@ -68,6 +72,18 @@ (define current-orig-stx (make-parameter #f)) +;; Used for meta-level errors where there is no good more-specific form +;; to blame; that is, incorrect syntax-spec syntax, but on something like +;; spec-var:[nt] where the error is not related to the immediately surrounding +;; syntax. For errors where there is more appropriate immediately surrounding +;; syntax, we use plain `wrong-syntax`. +(define (wrong-syntax/syntax-spec + stx #:extra [extras null] format-string . args) + (parameterize ([current-syntax-context (datum->syntax #f 'syntax-spec)]) + (apply wrong-syntax stx #:extra extras format-string args))) + +;; Used by DSL expanders to raise object-level errors; that is, a my-expression +;; was expected by my-interface-macro. (define (wrong-syntax/orig stx #:extra [extras null] format-string . args) (parameterize ([current-syntax-context (current-orig-stx)]) (apply wrong-syntax stx #:extra extras format-string args))) @@ -185,3 +201,14 @@ #:attr space-stx (attribute maybe-space.stx) #:attr space-sym (attribute maybe-space.sym) #:attr ext-classes (if (attribute extensions) (attribute extensions.classes) '()))) + +(define-splicing-syntax-class compiler + #:description "host interface compiler" + (pattern (~seq body:parse-body ...+))) + +(define-syntax-class parse-body + #:description "pattern directive or body" + (pattern _)) + +(define-splicing-syntax-class maybe-binding-decl + (pattern (~optional (~seq #:binding ~! bspec)))) \ No newline at end of file diff --git a/tests/#errors.rkt#1# b/tests/#errors.rkt#1# new file mode 100644 index 0000000..18a5cc2 Binary files /dev/null and b/tests/#errors.rkt#1# differ diff --git a/tests/errors.rkt b/tests/errors.rkt index e9d4e34..241fcaf 100644 --- a/tests/errors.rkt +++ b/tests/errors.rkt @@ -7,7 +7,7 @@ ;; (check-decl-error - #rx"nonterminal: expected extension class name" + #rx"syntax-spec: expected extension class name" (syntax-spec (binding-class var #:description "var") (nonterminal expr @@ -25,19 +25,19 @@ ;; (check-decl-error - #rx"nonterminal: expected a syntax spec term" + #rx"syntax-spec: expected a syntax spec term" (syntax-spec (nonterminal expr 1))) (check-decl-error - #rx"nonterminal: expected a reference to a binding class, extension class, syntax class, or nonterminal" + #rx"syntax-spec: expected a reference to a binding class, extension class, syntax class, or nonterminal" (syntax-spec (nonterminal expr x:unbound-name))) (check-decl-error - #rx"nonterminal: duplicate pattern variable" + #rx"syntax-spec: duplicate pattern variable\n at: x" (syntax-spec (binding-class dsl-var #:description "dsl-var") (nonterminal expr @@ -48,7 +48,7 @@ ;; (check-decl-error - #rx"nonterminal: binding spec expected a reference to a pattern variable" + #rx"syntax-spec: binding spec expected a reference to a pattern variable" (syntax-spec (binding-class dsl-var #:description "DSL variable") (nonterminal expr @@ -64,7 +64,7 @@ #:binding (scope (bind y))))) (check-decl-error - #rx"nonterminal: nesting nonterminals may only be used with `nest`" + #rx"syntax-spec: nesting nonterminals must be used with `nest`" (syntax-spec (binding-class dsl-var #:description "DSL variable") (nonterminal expr @@ -73,6 +73,16 @@ (nonterminal/nesting binding-group (nested) []))) +(check-decl-error + #rx"syntax-spec: nesting nonterminals must be used with `nest`" + (syntax-spec + (nonterminal/nesting binding (nested) + ()) + (host-interface/expression + (my-dsl b:binding) + #:binding b + #''todo))) + (check-decl-error #rx"nest: expected pattern variable associated with a nesting nonterminal" (syntax-spec @@ -103,7 +113,7 @@ (check-decl-error - #rx"nonterminal: exports may only occur at the top-level of an exporting binding spec" + #rx"syntax-spec: exports may only occur at the top-level of an exporting binding spec" (syntax-spec (binding-class var #:description "var") (nonterminal expr @@ -111,7 +121,7 @@ #:binding (export v)))) (check-decl-error - #rx"nonterminal: binding must occur within a scope" + #rx"syntax-spec: binding must occur within a scope" (syntax-spec (binding-class pvar) (nonterminal pat @@ -119,7 +129,7 @@ #:binding (bind x)))) (check-decl-error - #rx"nonterminal: import binding groups must occur within a scope" + #rx"syntax-spec: import binding groups must occur within a scope" (syntax-spec (binding-class var #:description "var") (nonterminal/exporting def @@ -130,7 +140,7 @@ #:binding (import d)))) (check-decl-error - #rx"nonterminal: import binding groups must occur within a scope" + #rx"syntax-spec: import binding groups must occur within a scope" (syntax-spec (binding-class var #:description "var") (nonterminal/exporting def @@ -141,7 +151,18 @@ #:binding [(import d)]))) (check-decl-error - #rx"nonterminal: bindings must appear first within a scope" + #rx"syntax-spec: exporting nonterminals must be used with `import` or `re-export`" + (syntax-spec + (binding-class var #:description "var") + (nonterminal/exporting def + (define x:var e:expr) + #:binding [(export x) e]) + (nonterminal expr + (block d:def) + #:binding [d]))) + +(check-decl-error + #rx"syntax-spec: bindings must appear first within a scope" (syntax-spec (binding-class var) (nonterminal expr @@ -149,7 +170,7 @@ #:binding (scope e (bind x))))) (check-decl-error - #rx"nonterminal: an import binding group must appear before references and subexpressions" + #rx"syntax-spec: an import binding group must appear before references and subexpressions" (syntax-spec (binding-class var) (nonterminal/exporting def @@ -171,7 +192,7 @@ (check-decl-error - #rx"cannot mix different binding spec categories inside of ellipses" + #rx"cannot mix imports or exports with other kinds of binding specs inside of ellipses" (syntax-spec (nonterminal my-expr (my-letrec ([x:racket-var e:racket-expr] ...) body:racket-expr) @@ -198,14 +219,14 @@ #:binding [(export x) e (re-export d)]))) (check-decl-error - #rx"nonterminal: each pattern variable must occur in the binding spec at most once" + #rx"syntax-spec: each pattern variable must occur in the binding spec at most once" (syntax-spec (nonterminal expr (begin e1:expr e2:expr) #:binding [e1 e1]))) (check-decl-error - #rx"nonterminal: all variants of the same-named form must occur together" + #rx"syntax-spec: all variants of the same-named form must occur together" (syntax-spec (nonterminal expr (foo) @@ -213,7 +234,7 @@ (foo #:bar)))) (check-decl-error - #rx"nonterminal: all variants of the same-named form must occur together" + #rx"syntax-spec: all variants of the same-named form must occur together" (syntax-spec (nonterminal expr (foo) @@ -221,21 +242,21 @@ (~>/form (foo #:bar) #'(foo))))) (check-decl-error - #rx"nonterminal: missing ellipses with pattern variable in binding spec" + #rx"syntax-spec: missing ellipses with pattern variable in binding spec" (syntax-spec (nonterminal expr (foo a:racket-var ...) #:binding a))) (check-decl-error - #rx"nonterminal: too many ellipses for pattern variable in binding spec" + #rx"syntax-spec: too many ellipses for pattern variable in binding spec" (syntax-spec (nonterminal expr (foo a:racket-var) #:binding [a ...]))) (check-decl-error - #rx"nonterminal/nesting: too many ellipses for pattern variable in binding spec" + #rx"syntax-spec: too many ellipses for pattern variable in binding spec" (syntax-spec (nonterminal/nesting expr (nested) (foo a:racket-var) @@ -382,6 +403,24 @@ #rx"expand-nonterminal/datum: expected expr1" (expand-nonterminal/datum expr1 [x])) +(check-decl-error + #rx"host-interface/expression: expected more terms starting with pattern directive or body" + (syntax-spec + (nonterminal/nesting binding (nested) + ()) + (host-interface/expression + (my-dsl b:binding) + #:binding (nest b [])))) + +(check-decl-error + #rx"host-interface/definitions: expected more terms starting with pattern directive or body" + (syntax-spec + (nonterminal/nesting binding (nested) + ()) + (host-interface/definitions + (my-dsl b:binding) + #:binding (nest b [])))) + (syntax-spec (host-interface/expression (dsl-expr1-interface e:expr1) diff --git a/tests/multi-nest.rkt b/tests/multi-nest.rkt new file mode 100644 index 0000000..680e00f --- /dev/null +++ b/tests/multi-nest.rkt @@ -0,0 +1,31 @@ +#lang racket/base + +; testing the conversion from (nest x y e) to (nest x (nest y e)) + +(require "../testing.rkt" + syntax/macro-testing) + +(syntax-spec + (nonterminal my-expr + (my-let* (b1:binding ...) + (b2:binding) + body:racket-expr) + #:binding (nest b1 ... b2 body)) + (nonterminal/nesting binding (nested) + [x:racket-var e:racket-expr] + #:binding (scope (bind x) nested)) + (host-interface/expression + (my-dsl e:my-expr) + #'(compile-expr e))) + +(define-syntax compile-expr + (syntax-parser + #:literals (my-let*) + [(_ (my-let* ([x1 e1] ...) + ([x2 e2]) + body)) + #'(let* ([x1 e1] ... [x2 e2]) body)])) + +(check-equal? + (my-dsl (my-let* ([x 1] [y x]) ([z y]) z)) + 1)