commit 01c9b553a5b35d5661548d59c928c727af7f966f
parent f030c3ffbea3b87d558bed05303fe525e725e4d7
Author: Georges Dupéron <georges.duperon@gmail.com>
Date: Thu, 22 Sep 2016 22:33:31 +0200
Implemented and documented: pre operations, ~lift-rest.
Diffstat:
20 files changed, 1170 insertions(+), 583 deletions(-)
diff --git a/main.rkt b/main.rkt
@@ -3,11 +3,13 @@
(require generic-syntax-expanders
"private/parameters.rkt"
"private/no-order.rkt"
+ "private/pre.rkt"
"private/post.rkt"
"private/global.rkt"
"private/optional.rkt"
"private/mixin.rkt"
"private/try-attribute.rkt"
+ "private/nop.rkt"
(for-template "private/define-syntax+simple-api.rkt")
syntax/parse)
@@ -26,9 +28,13 @@
order-point>
try-order-point<
try-order-point>
+ ~before
+ ~after
+ ~lift-rest
~mixin
~post-check
~post-fail
+ ~maybe/empty
~named-seq
~nop
~optional/else
diff --git a/private/no-order.rkt b/private/no-order.rkt
@@ -22,6 +22,8 @@
;syntax/parse/experimental/eh
generic-syntax-expanders
phc-toolkit/untyped
+ racket/list
+ racket/function
(for-syntax racket/base
syntax/parse
racket/syntax
@@ -34,11 +36,15 @@
(provide define-eh-alternative-mixin
~seq-no-order
~no-order
+ ~before
+ ~after
~order-point
order-point<
order-point>
try-order-point<
try-order-point>
+ ~lift-rest
+ ~omitable-lifted-rest ;; Private
(expander-out eh-mixin))
(define-expander-type eh-mixin)
@@ -73,74 +79,166 @@
(record-disappeared-uses dis)
#'(void))}))
+;; TODO: this does not work when there is a pattern expander which expands to
+;; an ~or^eh
+(define-for-syntax (catch-omitable-lifted-rest stx)
+ (define caught '())
+ (define (r stx)
+ ;(displayln (list r stx))
+ (cond
+ [(syntax? stx) (datum->syntax stx (r (syntax-e stx)) stx stx)]
+ [(and (pair? stx)
+ (identifier? (car stx))
+ (free-identifier=? (car stx) #'~or))
+ (cons (car stx) (l (cdr stx)))]
+ [(and (pair? stx)
+ (identifier? (car stx))
+ (free-identifier=? (car stx) #'~omitable-lifted-rest))
+ (set! caught (cons stx caught))
+ #'{~or}] ;; empty ~or with no eh alternatives
+ [else stx]))
+ (define (l stx)
+ ;(displayln (list l stx))
+ (cond
+ [(syntax? stx) (datum->syntax stx (r (syntax-e stx)) stx stx)]
+ [(list? stx) (map r stx)]
+ [(pair? stx) (cons (r (car stx)) (l (cdr stx)))]
+ [else stx]))
+ (define cleaned (r stx))
+ (values cleaned caught))
+
;; TODO: ~seq-no-order should also be a eh-mixin-expander, so that when there
;; are nested ~seq-no-order, the ~post-fail is caught by the nearest
;; ~seq-no-order.
-(define-syntax ~seq-no-order
- (pattern-expander
- (λ (stx)
- (syntax-case stx ()
- [(self pat ...)
- (with-disappeared-uses*
- (define counter 0)
- (define (increment-counter)
- (begin0 counter
- (set! counter (add1 counter))))
- ;; post-acc gathers some a-patterns which will be added after the
- ;; (~seq (~or ) ...)
- (define post-acc '())
- (define (add-to-post! v) (set! post-acc (cons v post-acc)))
- ;; post-groups-acc gathers some attributes that have to be grouped
- (define post-groups-acc '())
- (define (add-to-post-groups! . v)
- (set! post-groups-acc (cons v post-groups-acc)))
- ;; expand EH alternatives:
- (parameterize ([eh-post-accumulate add-to-post!]
- [eh-post-group add-to-post-groups!]
- [clause-counter increment-counter])
- (define alts
- (expand-all-eh-mixin-expanders #'(~or pat ...)))
- (define post-group-bindings
- (for/list ([group (group-by car
- (reverse post-groups-acc)
- free-identifier=?)])
- ;; each item in `group` is a four-element list:
- ;; (list result-id aggregate-function attribute)
- (define/with-syntax name (first (car group))
- #;(syntax-local-introduce
- (datum->syntax #'here
- (first (car group)))))
- (define/with-syntax f (second (car group)))
- #`[name (f . #,(map (λ (i) #`(attribute #,(third i)))
- group))]))
- (define/with-syntax whole-clause (get-new-clause!))
- (define/with-syntax parse-seq-order-sym-id
- (datum->syntax (parse-seq-order-sym-introducer
- (syntax-local-introduce #'here))
- 'parse-seq-order-sym))
- #`(~delimit-cut
- (~and #,(fix-disappeared-uses)
- {~seq whole-clause (… …)}
- {~do (define parse-seq-order-sym-id
- (gensym 'parse-seq-order))}
- {~parse ({~seq #,alts (… …)})
- #`#,(for/list
- ([xi (in-syntax #'(whole-clause (… …)))]
- [i (in-naturals)])
- ;; Add a syntax property before parsing,
- ;; to track the position of matched elements
- ;; using ~order-point
- (syntax-property xi
- parse-seq-order-sym-id
- i))}
- ~!
- (~bind #,@post-group-bindings)
- #,@(reverse post-acc)))))]))))
-
-(define-syntax ~no-order
- (pattern-expander
- (λ/syntax-case (_ . rest) ()
- #'({~seq-no-order . rest}))))
+
+
+(define-for-syntax ((no-order-ish seq?) stx)
+ (syntax-case stx ()
+ [(self pat ...)
+ (with-disappeared-uses*
+ (define counter 0)
+ (define (increment-counter!)
+ (begin0 counter
+ (set! counter (add1 counter))))
+ ;; pre-acc and post-acc gather some a-patterns which will be added after
+ ;; the (~seq (~or ) ...), before and after the ~! cut respectively
+ (define pre-acc '())
+ (define (add-to-pre! v) (set! pre-acc (cons v pre-acc)))
+ (define post-acc '())
+ (define (add-to-post! v) (set! post-acc (cons v post-acc)))
+ ;; post-groups-acc gathers some attributes that have to be grouped
+ (define post-groups-acc '())
+ (define (add-to-post-groups! . v)
+ (set! post-groups-acc (cons v post-groups-acc)))
+ (define lifted-rest '())
+ (define (add-to-lift-rest! present-clause expanded-pat)
+ (define succeeded-clause (get-new-clause!))
+ (set! lifted-rest (cons (list present-clause
+ expanded-pat
+ succeeded-clause)
+ lifted-rest)))
+ ;; expand EH alternatives:
+ (parameterize ([eh-pre-accumulate add-to-post!]
+ [eh-post-group add-to-post-groups!]
+ [eh-post-accumulate add-to-post!]
+ [clause-counter increment-counter!]
+ [lift-rest add-to-lift-rest!])
+ (define alts
+ (expand-all-eh-mixin-expanders #'(~or pat ...)))
+ ;; NOTE: this works only because eh-mixin-expanders are NOT pattern
+ ;; expanders. If these are merged later on, then this needs to be
+ ;; adjusted
+ (define-values (cleaned-alts caught-omitable-lifted-rest)
+ (catch-omitable-lifted-rest alts))
+ (define post-group-bindings
+ (for/list ([group (group-by car
+ (reverse post-groups-acc)
+ free-identifier=?)])
+ ;; each item in `group` is a four-element list:
+ ;; (list result-id aggregate-function attribute)
+ (define/with-syntax name (first (car group))
+ #;(syntax-local-introduce
+ (datum->syntax #'here
+ (first (car group)))))
+ (define/with-syntax f (second (car group)))
+ #`[name (f . #,(map (λ (i) #`(attribute #,(third i)))
+ group))]))
+ (set! lifted-rest (reverse lifted-rest))
+ (define/with-syntax whole-clause (get-new-clause!))
+ (define/with-syntax rest-clause (get-new-clause!))
+ (define/with-syntax parse-seq-order-sym-id
+ (datum->syntax (parse-seq-order-sym-introducer
+ (syntax-local-introduce #'here))
+ 'parse-seq-order-sym))
+ (define/with-syntax whole-clause-pat
+ (if seq?
+ (begin
+ (when (not (null? lifted-rest))
+ (raise-syntax-error
+ '~seq-no-order
+ (string-append "rest clause must be used within ~no-order,"
+ " but was used within ~seq-no-order")
+ stx))
+ #'{~seq whole-clause (… …) {~bind [(rest-clause 1) (list)]}})
+ #'(whole-clause (… …) . {~and rest-clause {~not (_ . _)}})))
+ (define rest-handlers
+ (if (null? lifted-rest)
+ #'()
+ (map (match-lambda
+ [(list present expanded-pat succeeded)
+ #`{~parse {~or {~and {~parse
+ #t
+ (ormap identity
+ (flatten
+ (attribute #,present)))}
+ #,expanded-pat
+ {~bind [#,succeeded #t]}}
+ _}
+ #'rest-clause}])
+ lifted-rest)))
+ (define check-at-least-one-rest-handler
+ (if (null? lifted-rest)
+ #'()
+ (with-syntax ([([_ _ succeeded] …) lifted-rest])
+ #'({~fail #:unless (or (attribute succeeded) …)
+ "expected one of the rest patterns to match"}))))
+ (define check-no-dup-rest-handlers
+ (if (null? lifted-rest)
+ #'()
+ (with-syntax ([([_ _ succeeded] …) lifted-rest])
+ #'({~fail #:when (> (length
+ (filter (λ (x) x)
+ (list (attribute succeeded) …)))
+ 1)
+ (string-append "more than one of the lifted rest"
+ " patterns matched")}))))
+ ((λ (x) #;(pretty-write (syntax->datum x)) x)
+ #`(~delimit-cut
+ (~and #,(fix-disappeared-uses)
+ whole-clause-pat
+ {~do (define parse-seq-order-sym-id
+ (gensym 'parse-seq-order))}
+ {~parse ({~seq #,cleaned-alts (… …)})
+ #`#,(for/list
+ ([xi (in-syntax #'(whole-clause (… …)))]
+ [i (in-naturals)])
+ ;; Add a syntax property before parsing,
+ ;; to track the position of matched elements
+ ;; using ~order-point
+ (syntax-property xi
+ parse-seq-order-sym-id
+ i))}
+ #,@(reverse pre-acc)
+ #,@caught-omitable-lifted-rest
+ #,@rest-handlers
+ #,@check-at-least-one-rest-handler
+ ~!
+ #,@check-no-dup-rest-handlers
+ (~bind #,@post-group-bindings)
+ #,@(reverse post-acc))))))]))
+
+(define-syntax ~seq-no-order (pattern-expander (no-order-ish #t)))
+(define-syntax ~no-order (pattern-expander (no-order-ish #f)))
(define-eh-mixin-expander ~order-point
(λ (stx)
@@ -168,4 +266,67 @@
(if-attribute a (if-attribute b (order-point< a b) #f) #f))
(define-syntax-rule (try-order-point> a b)
- (if-attribute a (if-attribute b (order-point> a b) #f) #f))
-\ No newline at end of file
+ (if-attribute a (if-attribute b (order-point> a b) #f) #f))
+
+(define-eh-mixin-expander ~before
+ (λ (stx)
+ (syntax-case stx ()
+ [(_ other message pat …)
+ (and (identifier? #'other)
+ (string? (syntax-e #'message))
+ #'{~order-point pt
+ {~seq pat …}
+ {~post-fail message #:when (order-point> pt other)}})])))
+
+(define-eh-mixin-expander ~after
+ (λ (stx)
+ (syntax-case stx ()
+ [(_ other message pat …)
+ (and (identifier? #'other)
+ (string? (syntax-e #'message))
+ #'{~order-point pt
+ {~seq pat …}
+ {~post-fail message #:when (order-point< pt other)}})])))
+
+(define-eh-mixin-expander ~try-before
+ (λ (stx)
+ (syntax-case stx ()
+ [(_ other message pat …)
+ (and (identifier? #'other)
+ (string? (syntax-e #'message))
+ #'{~order-point pt
+ {~seq pat …}
+ {~post-fail message #:when (try-order-point> pt other)}})])))
+
+(define-eh-mixin-expander ~try-after
+ (λ (stx)
+ (syntax-case stx ()
+ [(_ other message pat …)
+ (and (identifier? #'other)
+ (string? (syntax-e #'message))
+ #'{~order-point pt
+ {~seq pat …}
+ {~post-fail message #:when (try-order-point< pt other)}})])))
+
+(define-syntax ~omitable-lifted-rest
+ (pattern-expander
+ (λ (stx)
+ (syntax-case stx ()
+ [(_ expanded-pats clause-present)
+ #'{~and
+ ;; TODO: copy the disappeared uses instead of this hack
+ {~do 'expanded-pats}
+ {~bind [clause-present #t]}}]))))
+
+
+(define-eh-mixin-expander ~lift-rest
+ (λ (stx)
+ (syntax-case stx ()
+ [(_ pat)
+ (let ()
+ (define/with-syntax clause-present (get-new-clause!))
+ (define/with-syntax expanded-pat
+ ;; let the ~post, ~global etc. within pat … be recognized
+ (expand-all-eh-mixin-expanders #'pat))
+ (lift-rest! '~lift-rest #'clause-present #'expanded-pat)
+ #'(~omitable-lifted-rest expanded-pat clause-present))])))
+\ No newline at end of file
diff --git a/private/nop.rkt b/private/nop.rkt
@@ -0,0 +1,10 @@
+#lang racket
+(require syntax/parse
+ (for-syntax syntax/parse
+ phc-toolkit/untyped))
+
+(provide ~nop)
+
+(define-syntax ~nop
+ (pattern-expander
+ (λ/syntax-case (_) () #'(~do))))
diff --git a/private/parameters.rkt b/private/parameters.rkt
@@ -2,13 +2,17 @@
(require (for-syntax racket/base))
-(provide (for-syntax eh-post-accumulate
+(provide (for-syntax eh-pre-accumulate
+ eh-pre-accumulate!
+ eh-post-accumulate
eh-post-accumulate!
eh-post-group
eh-post-group!
clause-counter
get-new-clause!
- is-clause-id-sym?))
+ is-clause-id-sym?
+ lift-rest
+ lift-rest!))
(define-syntax-rule (define-dynamic-accumulator-parameter parameter-name name!)
(begin
@@ -20,8 +24,10 @@
" used outside of ~seq-no-order")))
(apply (parameter-name) args))))
-(define-dynamic-accumulator-parameter eh-post-accumulate eh-post-accumulate!)
+(define-dynamic-accumulator-parameter eh-pre-accumulate eh-pre-accumulate!)
(define-dynamic-accumulator-parameter eh-post-group eh-post-group!)
+(define-dynamic-accumulator-parameter eh-post-accumulate eh-post-accumulate!)
+(define-dynamic-accumulator-parameter lift-rest lift-rest!)
;; This is a crude hack.
(define-for-syntax (is-clause-id-sym? id-sym)
@@ -34,4 +40,4 @@
(error "Use get-new-clause! within (parameterize ([clause-counter …]) …)"))
(datum->syntax #'here
;; keep the spaces, they allow us to recognize clauses later.
- (string->symbol (format " -clause-~a " ((clause-counter))))))
-\ No newline at end of file
+ (string->symbol (format " -clause-~a " ((clause-counter))))))
diff --git a/private/post.rkt b/private/post.rkt
@@ -6,16 +6,11 @@
racket/syntax
phc-toolkit/untyped)
"parameters.rkt"
- "no-order.rkt")
+ "no-order.rkt"
+ "nop.rkt")
-(provide ~nop
- ~post-check
- ~post-fail
- ~named-seq)
-
-(define-syntax ~nop
- (pattern-expander
- (λ/syntax-case (_) () #'(~do))))
+(provide ~post-check
+ ~post-fail)
(define-eh-mixin-expander ~post-check
(λ (stx)
@@ -38,22 +33,6 @@
...))
#'(~and (~bind [clause-present #t]) . pats))])))
-(define-eh-mixin-expander ~named-seq
- (λ (stx)
- (syntax-case stx ()
- [(_ id . pats)
- (identifier? #'id)
- (let ()
- (define/with-syntax clause-present (get-new-clause!))
- (define/with-syntax clause (get-new-clause!))
- (eh-post-accumulate! '~named-seq
- #'(~bind [(id 1) (if (attribute clause-present)
- (attribute clause)
- (list))]))
- #'(~and (~bind [clause-present #t])
- (~seq clause (... ...))
- (~seq . pats)))])))
-
(define-for-syntax (post-fail stx)
(syntax-case stx ()
[(_ message #:when condition)
diff --git a/private/pre.rkt b/private/pre.rkt
@@ -0,0 +1,72 @@
+#lang racket/base
+
+(require syntax/parse
+ (for-syntax racket/base
+ syntax/parse
+ racket/syntax
+ phc-toolkit/untyped)
+ "parameters.rkt"
+ "no-order.rkt"
+ "nop.rkt")
+
+(provide ~pre-check
+ ~pre-fail
+ ~named-seq
+ ~maybe/empty)
+
+(define-eh-mixin-expander ~pre-check
+ (λ (stx)
+ (syntax-case stx ()
+ [(_ pat post)
+ (begin (eh-pre-accumulate! '~pre-check #'post)
+ #'pat)]
+ [(_ post)
+ (begin (eh-pre-accumulate! '~pre-check #'post)
+ #'(~nop))])))
+
+(define-for-syntax (pre-fail stx)
+ (syntax-case stx ()
+ [(_ message #:when condition)
+ (let ()
+ (define/with-syntax clause-present (get-new-clause!))
+ (eh-pre-accumulate! '~pre-fail
+ #`(~fail #:when (and (attribute clause-present)
+ condition)
+ message))
+ #'(~bind [clause-present #t]))]
+ [(self #:when condition message)
+ (pre-fail #'(self message #:when condition))]
+ [(self message #:unless unless-condition)
+ (pre-fail #'(self message #:when (not unless-condition)))]
+ [(self #:unless unless-condition message)
+ (pre-fail #'(self message #:when (not unless-condition)))]))
+
+(define-eh-mixin-expander ~pre-fail pre-fail)
+
+(define-eh-mixin-expander ~named-seq
+ (λ (stx)
+ (syntax-case stx ()
+ [(_ id . pats)
+ (identifier? #'id)
+ (let ()
+ (define/with-syntax clause-present (get-new-clause!))
+ (define/with-syntax clause (get-new-clause!))
+ (eh-pre-accumulate! '~named-seq
+ #'(~bind [(id 1) (if (attribute clause-present)
+ (attribute clause)
+ (list))]))
+ #'(~and (~bind [clause-present #t])
+ (~seq clause (... ...))
+ (~seq . pats)))])))
+
+(define-eh-mixin-expander ~maybe/empty
+ (λ (stx)
+ (syntax-case stx ()
+ [(_ . pats)
+ (let ()
+ (define/with-syntax clause-present (get-new-clause!))
+ (eh-pre-accumulate! '~maybe/empty
+ #'(~parse {~no-order {~seq . pats}}
+ #'(clause (... ...))))
+ #'{~optional {~and {~bind [clause-present #t]}
+ {~seq clause (... ...)}}})])))
+\ No newline at end of file
diff --git a/scribblings/defining-reusable-mixins.scrbl b/scribblings/defining-reusable-mixins.scrbl
@@ -0,0 +1,66 @@
+#lang scribble/manual
+@require[scribble/example
+ "utils.rkt"
+ @for-label[phc-toolkit/untyped
+ extensible-parser-specifications
+ generic-syntax-expanders
+ racket/base
+ syntax/parse
+ (only-in racket/base [... …])]]
+
+@title{Defining reusable parser mixins}
+
+@defform[#:literals (pattern)
+ (define-eh-alternative-mixin name maybe-define-class
+ (pattern clause-or-mixin) ...)
+ #:grammar
+ [(maybe-define-class
+ (code:line #:define-splicing-syntax-class splicing-name))
+ (clause-or-mixin #,ntax-pattern
+ (~mixin #,-alternative-mixin)
+ (~or clause-or-mixin ...)
+ derived-or)]]{
+ Defines an @deftech{eh-alternative mixin}, which is implemented as an @tech{
+ eh-mixin expander}. An eh-alternative mixin is like an
+ @tech[#:doc '(lib "syntax/scribblings/syntax.scrbl")]{ellipsis-head alternative
+ set}, except that it can only appear as part of a @racket[~no-order] (possibly
+ nested under other eh-alternative mixins), and can contain some global
+ constraints. The global constraints, detailed below, allow the parser to
+ perform checks across two or more mixins. For example, given a set of options
+ that can appear in any order, it is possible to specify that two of them are
+ mutually exclusive, or that two other must appear in a certain order,
+ regardless of the order of the other options.
+
+ The @racket[derived-or] term covers any
+ @tech[#:doc '(lib "syntax/scribblings/syntax.scrbl")]{pattern expander} or
+ @tech{eh-mixin expander} application which expands to a
+ @racket[clause-or-mixin].}
+
+@deftogether[[@defthing[#:kind "for-syntax value"
+ eh-mixin-expander-type expander-type?]
+ @defproc[#:kind "for-syntax procedure"
+ (make-eh-mixin-expander)
+ (and/c expander? eh-mixin-expander?)]
+ @defproc[#:kind "for-syntax procedure"
+ (eh-mixin-expander? [v any/c])
+ boolean?]
+ @defform[(define-eh-mixin-expander id transformer-procedure)]
+ @defproc[#:kind "for-syntax procedure"
+ (expand-all-eh-mixin-expanders [stx-tree syntax?])
+ syntax?]]]{
+ These functions and forms allow the creation and manipulation of @deftech{
+ eh-mixin expanders}. These identifiers are generated by
+ @racket[define-expander-type]. For more information, see the documentation for
+ @racket[define-expander-type].}
+
+@section{Using mixins}
+
+@defform[(~mixin #,-alternative-mixin)]{
+ Expands the @racket[#,-alternative-mixin], with no arguments. This is
+ equivalent to @racket[(_eh-alternative-mixin)], but @racket[~mixin]
+ additionally checks that the given @racket[_eh-alternative-mixin] is indeed an
+ @tech{eh-alternative mixin}. Otherwise, with the syntax,
+ @racket[(_eh-alternative-mixin)] the name @racket[_eh-alternative-mixin] would
+ be interpreted as a pattern variable by @racket[syntax-parse] if the expander
+ was not available for some reason (e.g. a missing import).}
+
diff --git a/scribblings/extensible-parser-specifications.scrbl b/scribblings/extensible-parser-specifications.scrbl
@@ -1,5 +1,6 @@
#lang scribble/manual
@require[scribble/example
+ "utils.rkt"
@for-label[phc-toolkit/untyped
extensible-parser-specifications
generic-syntax-expanders
@@ -7,494 +8,17 @@
syntax/parse
(only-in racket/base [... …])]]
-@(define make-evaluator
- (make-eval-factory '(syntax/parse
- extensible-parser-specifications)))
-
@title{extensible-parser-specifications}
@author{@author+email["Georges Dupéron" "georges.duperon@gmail.com"]}
-@(define ntax-pattern (tech #:doc '(lib "syntax/scribblings/syntax.scrbl")
- #:key "syntax pattern"
- "syntax-pattern"))
-
-@(define -alternative-mixin (tech #:key "eh-alternative mixin"
- "eh-alternative-mixin"))
-
-@(define tribute-name (tech #:doc '(lib "syntax/scribblings/syntax.scrbl")
- #:key "attribute"
- "attribute-name"))
-
-@(define A-patte (tech #:doc '(lib "syntax/scribblings/syntax.scrbl")
- #:key "action pattern"
- "A-pattern"))
-
@defmodule[extensible-parser-specifications]
-@defform[#:literals (pattern)
- (define-eh-alternative-mixin name maybe-define-class
- (pattern clause-or-mixin) ...)
- #:grammar
- [(maybe-define-class
- (code:line #:define-splicing-syntax-class splicing-name))
- (clause-or-mixin #,ntax-pattern
- (~mixin #,-alternative-mixin)
- (~or clause-or-mixin ...)
- derived-or)]]{
- Defines an @deftech{eh-alternative mixin}, which is implemented as an @tech{
- eh-mixin expander}. An eh-alternative mixin is like an
- @tech[#:doc '(lib "syntax/scribblings/syntax.scrbl")]{ellipsis-head alternative
- set}, except that it can only appear as part of a @racket[~no-order] (possibly
- nested under other eh-alternative mixins), and can contain some global
- constraints. The global constraints, detailed below, allow the parser to
- perform checks across two or more mixins. For example, given a set of options
- that can appear in any order, it is possible to specify that two of them are
- mutually exclusive, or that two other must appear in a certain order,
- regardless of the order of the other options.
-
- The @racket[derived-or] term covers any
- @tech[#:doc '(lib "syntax/scribblings/syntax.scrbl")]{pattern expander} or
- @tech{eh-mixin expander} application which expands to a
- @racket[clause-or-mixin].}
-
-@deftogether[[@defthing[#:kind "for-syntax value"
- eh-mixin-expander-type expander-type?]
- @defproc[#:kind "for-syntax procedure"
- (make-eh-mixin-expander)
- (and/c expander? eh-mixin-expander?)]
- @defproc[#:kind "for-syntax procedure"
- (eh-mixin-expander? [v any/c])
- boolean?]
- @defform[(define-eh-mixin-expander id transformer-procedure)]
- @defproc[#:kind "for-syntax procedure"
- (expand-all-eh-mixin-expanders [stx-tree syntax?])
- syntax?]]]{
- These functions and forms allow the creation and manipulation of @deftech{
- eh-mixin expanders}. These identifiers are generated by
- @racket[define-expander-type]. For more information, see the documentation for
- @racket[define-expander-type].}
-
-@section{Pattern expanders and eh-mixin expanders}
-
-@defform[#:kind "pattern expander"
- #:literals (~mixin ~or)
- (~seq-no-order clause-or-mixin ...)
- #:grammar
- [(clause-or-mixin #,ntax-pattern
- (~mixin #,-alternative-mixin)
- (~or clause-or-mixin ...)
- derived-or)]]{
- Splicing pattern which matches the given @racket[clause-or-mixin]s in any
- order, enforcing the global constraints expressed within each.
-
- Nested @racket[~or] directly below @racket[~seq-no-order] are recursively
- inlined. In other words, the @racket[~or] present directly below the
- @racket[~seq-no-order] or below such an @racket[~or] clause do not behave as
- "exclusive or", but instead contain clauses which can appear in any order.
- These clauses are not grouped in any way by the @racket[~or], i.e.
- @racket[(~no-order (~or (~or a b) (~or c d)))] is equivalent to
- @racket[(~no-order a b c d)].
-
- The @racket[derived-or] term covers any
- @tech[#:doc '(lib "syntax/scribblings/syntax.scrbl")]{pattern expander} or
- @tech{eh-mixin expander} application which expands to a
- @racket[clause-or-mixin]. The expansion of pattern and eh-mixin expanders
- happens before inlining the top @racket[~or] clauses.}
-
-@defform[#:kind "pattern expander"
- #:literals (~mixin ~or)
- (~no-order clause-or-mixin ...)
- #:grammar
- [(clause-or-mixin #,ntax-pattern
- (~mixin #,-alternative-mixin)
- (~or clause-or-mixin ...)
- derived-or)]]{
-
- Like @racket[~seq-no-order], except that it matches a syntax list, instead of
- being spliced into the surrounding sequence of patterns. In other words,
-
- @racketblock[(~seq-no-order clause-or-mixin ...)]
-
- Equivalent to (notice the extra pair of braces):
-
- @racketblock[({~seq-no-order clause-or-mixin ...})]}
-
-@defform[#:kind "eh-mixin expander"
- (~order-point point-name #,ntax-pattern ...)]{
- When parsing a sequence of elements, @racket[~seq-no-order] and
- @racket[~no-order] associate an increasing number to each element starting from
- zero.
-
- The number associated with the first element matched by
- @racket[#,ntax-pattern ...] is memorised into the attribute
- @racket[point-name].
-
- This allows the position of elements matched by otherwise independent mixins to
- be compared using @racket[order-point<] and @racket[order-point>]}
-
-@defform[(order-point< a b)
- #:grammar
- [(a #,tribute-name)
- (b #,tribute-name)]]{
- Returns @racket[#t] when the first element matched by
- @racket[(~order-point a #,ntax-pattern ...)] occurs before the first element
- matched by @racket[(~order-point b #,ntax-pattern ...)]. Otherwise, returns
- @racket[#f].
-
- This operation does not fail if @racket[a] or @racket[b] are bound to
- @racket[#f] (i.e. their corresponding @racket[_syntax-pattern ...] did not
- match). Instead, in both cases, it returns @racket[#f].}
-
-@defform[(order-point> a b)
- #:grammar
- [(a #,tribute-name)
- (b #,tribute-name)]]{
- Returns @racket[#t] when the first element matched by
- @racket[(~order-point a #,ntax-pattern ...)] occurs after the first element
- matched by @racket[(~order-point b #,ntax-pattern ...)]. Otherwise, returns
- @racket[#f].
-
- This operation does not fail if @racket[a] or @racket[b] are bound to
- @racket[#f] (i.e. their corresponding @racket[_syntax-pattern ...] did not
- match). Instead, in both cases, it returns @racket[#f].}
-
-@defform[(try-order-point< a b)
- #:grammar
- [(a #,tribute-name)
- (b #,tribute-name)]]{
-
- Like @racket[order-point<], except that it does not fail if @racket[a] or
- @racket[b] are not attributes, or if they are bound to @racket[#f]. Instead, in
- all those cases, it returns @racket[#f].
-
- It can be used as follows:
-
- @racketblock[
- (~post-fail "a must appear after b"
- #:when (try-order-point< a b))]
-
- The same caveats as for @racket[try-attribute] apply.}
-
-@defform[(try-order-point> a b)
- #:grammar
- [(a #,tribute-name)
- (b #,tribute-name)]]{
-
- Like @racket[order-point>], except that it does not fail if @racket[a] or
- @racket[b] are not attributes, or if they are bound to @racket[#f]. Instead, in
- all those cases, it returns @racket[#f].
-
- It can be used as follows:
-
- @racketblock[
- (~post-fail "a must appear before b"
- #:when (try-order-point> a b))]
-
- The same caveats as for @racket[try-attribute] apply.}
-
-@defform[(~mixin #,-alternative-mixin)]{
-
- Expands the @racket[#,-alternative-mixin], with no arguments. This is
- equivalent to @racket[(_eh-alternative-mixin)], but @racket[~mixin]
- additionally checks that the given @racket[_eh-alternative-mixin] is indeed an
- @tech{eh-alternative mixin}. Otherwise, with the syntax,
- @racket[(_eh-alternative-mixin)] the name @racket[_eh-alternative-mixin] would
- be interpreted as a pattern variable by @racket[syntax-parse] if the expander
- was not available for some reason (e.g. a missing import).}
-
-@defform[#:kind "pattern expander"
- {~nop}]{
- The @tech[#:doc '(lib "syntax/scribblings/syntax.scrbl")
- #:key "action pattern"]{A-pattern} @racket[~nop] does not perform any
- action. It simply expands to @racket[{~do}].
-}
-
-@section{Post operations and global operations}
-
-@subsection{Post operations}
-
-@defform*[[(~post-check #,ntax-pattern #,A-patte)
- (~post-check #,A-patte)]]{
- Matches @racket[#,ntax-pattern], and executes the given @racket[#,A-patte]
- after the whole @racket[~seq-no-order] or @racket[~no-order] finished matching
- its contents.
-
- If unspecified, the @racket[_syntax-pattern] defaults to @racket[(~nop)].}
-
-@defform*[[(~post-fail message #:when condition)
- (~post-fail #:when condition message)
- (~post-fail message #:unless unless-condition)
- (~post-fail #:unless unless-condition message)]]{
-
- After the whole @racket[~seq-no-order] or @racket[~no-order] finished matching
- its contents, checks whether @racket[condition] or @racket[unless-condition] is
- true or false, respectively. If this is the case the whole
- @racket[~seq-no-order] or @racket[~no-order] is rejected with the given
- @racket[_message].
-
- Note that there is an implicit cut (@racket[~!]) between the no-order patterns
- and the "post" checks, so after a @racket[~post-fail] fails,
- @racket[syntax-parse] does not backtrack and attempt different combinations of
- patterns to match the sequence, nor does it backtrack and attempt to match a
- shorter sequence. This is by design, as it allows for better error messages
- (syntax-parse would otherwise attempt and possibly succeed in matching a
- shorter sequence, then just treat the remaining terms as "unexpected terms").}
-
-@subsection{Global operations}
-
-The global patterns presented below match all of the given
-@racket[#,ntax-pattern]s, like @racket[~and] does, and perform a global
-aggregation over all the values corresponding to successful matches of a global
-pattern using the same @racket[#,tribute-name].
-
-After the whole @racket[~seq-no-order] or @racket[~no-order] finished matching
-its contents, but before "post" operations are executed, the attribute
-@racket[#,tribute-name] is bound to
-@racket[(_aggrgate-function _value₁ ... _valueₙ)], where each @racket[valueᵢ] is
-the value which was passed to an occurrence of @racket[~global-or] with the same
-@racket[_attribute-name], and which successfully matched. The
-@racket[_aggregate-function] will be @racket[or] for @racket[~global-or],
-@racket[and] for @racket[~global-and] or @racket[+] for
-@racket[~global-counter].
-
-Each @racket[valueᵢ] is computed in the context in which it appears, after the
-@racket[_syntax-pattern]s. This means that it can access:
-@itemlist[
- @item{attributes already bound in the current alternative clause within the
- current @racket[~no-order] or @racket[~seq-no-order]}
- @item{attributes bound by the @racket[_syntax-patterns]s}
- @item{attributes already bound outside of the @racket[~no-order] or
- @racket[~seq-no-order]}
- @item{but it cannot access attributes bound in other alternative clauses within
- the current @racket[~no-order] or @racket[~seq-no-order].}]
-
-The @racket[valueᵢ] are aggregated with @racket[or], @racket[and] or @racket[+]
-in the order in which they appear in the @racket[~no-order] or
-@racket[~seq-no-order]. If a @racket[valueᵢ] appears under ellipses, or as part
-of an alternative clause which can match more than once (i.e. not @racket[~once]
-or @racket[~optional]), then each match within that @racket[valueᵢ] group is
-aggregated in the order it appears.
-
-Since this notion of order is rather complex, it is possible that future
-versions of this library will always return a boolean (@racket[#f] or
-@racket[#t] for @racket[~global-or] and @racket[~global-and], which would make
-the notion of order irrelevant.
-
-@defform[(~global-or attribute-name+value #,ntax-pattern ...)
- #:grammar
- [(attribute-name+value #,tribute-name
- [#,tribute-name valueᵢ])]]{
- Matches all of the given @racket[#,ntax-pattern]s, like @racket[~and] does, and
- perform a global @racket[or] over all the values corresponding to successful
- matches of a global pattern using the same @racket[#,tribute-name]. See above
- for a description of how global operations work.
-
- If the @racket[valueᵢ] is omitted, @racket[#t] is used as a default.
-
- The result is always transformed into a boolean, so @racket[_attribute-name] is
- always bound to either @racket[#t] or @racket[#f].}
-
-@defform[(~global-and attribute-name+value #,ntax-pattern ...)
- #:grammar
- [(attribute-name+value [#,tribute-name valueᵢ])]]{
- Matches all of the given @racket[#,ntax-pattern]s, like @racket[~and] does, and
- perform a global @racket[and] over all the values corresponding to successful
- matches of a global pattern using the same @racket[#,tribute-name]. See above
- for a description of how global operations work.
-
- If there is at least one occurrence of @racket[~global-and] for that
- @racket[_attribute-name] which successfully matches, the result of the
- @racket[(and valueᵢ ...)] is always coerced to a boolean, so
- @racket[_attribute-name] is always bound to either @racket[#t] or @racket[#f].
-
- If there are no matches at all, the special value @racket['none] is used
- instead of @racket[#t] as would be produced by @racket[(and)].}
-
-@defform[(~global-counter attribute-name+value #,ntax-pattern ...)
- #:grammar
- [(attribute-name+value #,tribute-name
- [#,tribute-name valueᵢ])]]{
- Matches all of the given @racket[#,ntax-pattern]s, like @racket[~and] does, and
- perform a global @racket[+] over all the values corresponding to successful
- matches of a global pattern using the same @racket[#,tribute-name]. See above
- for a description of how global operations work.
-
- If the @racket[valueᵢ] is omitted, @racket[1] is used as a default.}
-
-@;@defform[(aggregate-global-or)]
-@;@defform[(aggregate-global-and)]
-@;@defform[(aggregate-global-counter)]
-
-@subsection{Order in which the attributes are bound for post operations and
- global operations}
-
-Within the @racket[_A-pattern]s of post operations, the regular attributes bound
-by all the clauses inside @racket[~seq-no-order] or @racket[~no-order] are
-bound. The attributes defined as part of all "global" actions are bound too. The
-attributes defined as part of "post" actions of other clauses are bound only if
-the clause defining them appears before the current clause in the source code.
-For example, the following code works because the clause containing
-@racket[{~post-fail "2 is incompatible with 1" #:when (not (attribute a))}]
-appears after the clause which binds @racket[a] with the "post" action
-@racket[{~post-check {~bind ([a #'the-a])}}].
-
-@racketblock[
- {~seq-no-order
- {~post-check {~and the-a 1} {~bind ([a #'the-a])}}
- {~and 2 {~post-fail "2 is incompatible with 1" #:when (not (attribute a))}}}]
-
-If the two clauses are swapped, then the following code would raise a syntax
-error because @racket[a] is not bound as an attribute in the
-@racket[~post-fail]:
-
-@racketblock[
- {~seq-no-order
- {~and 2 {~post-fail "2 is incompatible with 1" #:when (not (attribute a))}}
- {~post-check {~and the-a 1} {~bind ([a #'the-a])}}}]
-
-On the other hand, the following code, which does not bind @racket[a] as part
-of a post operation, is valid:
-
-@racketblock[
- {~seq-no-order
- {~and 2 {~post-fail "2 is incompatible with 1" #:when (not (attribute a))}}
- {~and the-a 1 {~bind ([a #'the-a])}}}]
-
-Furthermore, the following code still works, as attributes are bound by the
-"global" operations before the "post" operations are executed:
-
-@racketblock[
- {~seq-no-order
- {~and 2 {~post-fail "2 is incompatible with 1" #:when (not (attribute a))}}
- {~global-or a 1}}]
-
-Note that the order in which clauses appear within the @racket[~seq-no-order]
-or @racket[~no-order] does not impact the order in which the elements must
-appear in the matched syntax (aside from issues related to greediness).
-
-@defform[(try-attribute #,tribute-name)]{
- This macro expands to @racket[(attribute #,tribute-name)] if
- @racket[#,tribute-name] is bound as a syntax pattern variable, and to
- @racket[#f] otherwise.
-
- This macro can be used to check for mutual exclusion of an attribute which is
- bound by other mixins that might or might not be present in the final
- @racket[~no-order] or @racket[~seq-no-order].
-
- Use this sparingly, as if an syntax pattern variable with that name is bound by
- an outer scope, the @racket[try-attribute] macro will still access it, ignorant
- of the fact that the current @racket[~seq-no-order] does not contain any mixin
- which binds that attribute.
-
- Instead, it is better practice to use
- @racket[{~global-or [_attribute-name #f]}] or
- @racket[{~global-and [_attribute-name #t]}] to ensure that the attribute is
- declared, while using the operation's neutral element to not alter the final
- result.}
-
-@defform[(if-attribute #,tribute-name if-branch else-branch)]{
- This macro expands to @racket[if-branch] if @racket[#,tribute-name] is bound as
- a syntax pattern variable, and to @racket[else-branch] otherwise.
-
- The same caveats as for @racket[try-attribute] apply.}
-
-@defform[(~named-seq #,tribute-name #,ntax-pattern ...)]{
- Equivalent to @racket[{~seq #,ntax-pattern ...}], but also binds the
- @racket[#,tribute-name] to the whole sequence. If the sequence appears inside
- an @racket[~optional] or @racket[~or] clause that fails, the
- @racket[_attribute-name] is still bound to the empty sequence.
-
- Known issues: this may not behave as expected if @racket[~named-seq] appears
- under ellipses.
-
- This probably should bind the sequence attribute @emph{before} the "global"
- operations, instead of being a "post" operation, and may be changed in that way
- the future.}
-
-@defform[(~optional/else #,ntax-pattern
- maybe-defaults
- else-post-fail ...
- maybe-name)
- #:grammar
- [(maybe-defaults (code:line)
- (code:line #:defaults (default-binding ...)))
- (else-post-fail
- (code:line #:else-post-fail message #:when condition)
- (code:line #:else-post-fail #:when condition message)
- (code:line #:else-post-fail message #:unless unless-condition)
- (code:line #:else-post-fail #:unless unless-condition message))
- (maybe-name (code:line)
- (code:line #:name #,tribute-name))]]{
-
- Like @racket[~optional], but with conditional post-failures when the pattern is
- not matched. An @racket[~optional/else] pattern can be matched zero or one time
- as part of the @racket[~seq-no-order] or @racket[~no-order]. When it is not
- matched (i.e. matched zero times):
- @itemlist[
- @item{it uses the default values for the attributes as specified with
- @racket[#:defaults].}
- @item{for each @racket[#:else-post-fail] clause, it checks whether the
- @racket[condition] or @racket[unless-condition] is true or false,
- respectively. If this is the case the whole @racket[~seq-no-order] or
- @racket[~no-order] is rejected with the given @racket[_message]. The
- behaviour of @racket[#:else-post-fail] is the same as the behaviour of
- @racket[~post-fail], except that the "post" conditional failure can only be
- executed if the optional @racket[_syntax-pattern] was not matched.
-
- Note that there is an implicit cut (@racket[~!]) between the no-order
- patterns and the "post" checks, so after a @racket[~post-fail] fails,
- @racket[syntax-parse] does not backtrack and attempt different combinations
- of patterns to match the sequence, nor does it backtrack and attempt to match
- a shorter sequence. This is by design, as it allows for better error messages
- (syntax-parse would otherwise attempt and possibly succeed in matching a
- shorter sequence, then just treat the remaining terms as
- "unexpected terms").}]
-
- The meaning of @racket[#:name #,tribute-name] option is the same as for
- @racket[~optional].}
-
-
-@section{Chaining macro calls without re-parsing everything}
-
-@defform[(define/syntax-parse+simple (name-or-curry . #,ntax-pattern) . body)
- #:grammar
- [(name-or-curry name
- (name-or-curry arg ...))
- (maybe-define-class #:define-splicing-syntax-class class-id)
- (name identifier?)
- (class-id identifier?)]]{
- This macro works like @racket[define/syntax-parse] from @racket[phc-toolkit],
- except that it also defines the function @racket[_name-forward-attributes],
- which can be used by other macros to forward already parsed attributes to the
- @racket[body], without the need to parse everything a second time.
-
- The syntax pattern for the @racket[name] macro's arguments can be saved in a
- syntax class by specifying the @racket[#:define-splicing-syntax-class] option.
-
- If the caller macro which uses @racket[(_name-forward-attributes)] parsed its
- own @racket[stx] argument using @racket[class-id], then
- @racket[(_name-forward-attributes)] is equivalent to expanding
- @racket[(name stx)].
-
- The @racket[_name-forward-attributes] function is defined at the same meta
- level as @racket[name], i.e. at the same meta-level where this library was
- required. }
-
-
-@defform[#:kind "for-template syntax"
- (define-syntax/parse+simple (name . #,ntax-pattern) . body)]{
- This macro is provided for meta-level -1.
+@include-section{defining-reusable-mixins.scrbl}
+@include-section{no-order.scrbl}
+@include-section{rest.scrbl}
- This is the same as @racket[define/syntax-parse+simple], except that it
- operates at level -1 relative to this library, and defines at that level a
- transformer binding (which therefore executes at the same meta-level as this
- library. In other words,
- @racket[(define-syntax/parse+simple (name . pat) . body)] is roughly equivalent
- to:
+@include-section{pre-global-post-section.scrbl}
- @racketblock[
- (begin-for-syntax
- (define/syntax-parse+simple (tmp . pat) . body)
- (define name-forward-attributes tmp-forward-attributes))
- (define-syntax name tmp)]}
+@include-section{misc.scrbl}
+@include-section{forward-attributes.scrbl}
diff --git a/scribblings/forward-attributes.scrbl b/scribblings/forward-attributes.scrbl
@@ -0,0 +1,53 @@
+#lang scribble/manual
+@require[scribble/example
+ "utils.rkt"
+ @for-label[phc-toolkit/untyped
+ extensible-parser-specifications
+ generic-syntax-expanders
+ racket/base
+ syntax/parse
+ (only-in racket/base [... …])]]
+
+@title{Chaining macro calls without re-parsing everything}
+
+@defform[(define/syntax-parse+simple (name-or-curry . #,ntax-pattern) . body)
+ #:grammar
+ [(name-or-curry name
+ (name-or-curry arg ...))
+ (maybe-define-class #:define-splicing-syntax-class class-id)
+ (name identifier?)
+ (class-id identifier?)]]{
+ This macro works like @racket[define/syntax-parse] from @racket[phc-toolkit],
+ except that it also defines the function @racket[_name-forward-attributes],
+ which can be used by other macros to forward already parsed attributes to the
+ @racket[body], without the need to parse everything a second time.
+
+ The syntax pattern for the @racket[name] macro's arguments can be saved in a
+ syntax class by specifying the @racket[#:define-splicing-syntax-class] option.
+
+ If the caller macro which uses @racket[(_name-forward-attributes)] parsed its
+ own @racket[stx] argument using @racket[class-id], then
+ @racket[(_name-forward-attributes)] is equivalent to expanding
+ @racket[(name stx)].
+
+ The @racket[_name-forward-attributes] function is defined at the same meta
+ level as @racket[name], i.e. at the same meta-level where this library was
+ required. }
+
+
+@defform[#:kind "for-template syntax"
+ (define-syntax/parse+simple (name . #,ntax-pattern) . body)]{
+ This macro is provided for meta-level -1.
+
+ This is the same as @racket[define/syntax-parse+simple], except that it
+ operates at level -1 relative to this library, and defines at that level a
+ transformer binding (which therefore executes at the same meta-level as this
+ library. In other words,
+ @racket[(define-syntax/parse+simple (name . pat) . body)] is roughly equivalent
+ to:
+
+ @racketblock[
+ (begin-for-syntax
+ (define/syntax-parse+simple (tmp . pat) . body)
+ (define name-forward-attributes tmp-forward-attributes))
+ (define-syntax name tmp)]}
diff --git a/scribblings/global.scrbl b/scribblings/global.scrbl
@@ -0,0 +1,97 @@
+#lang scribble/manual
+@require[scribble/example
+ "utils.rkt"
+ @for-label[phc-toolkit/untyped
+ extensible-parser-specifications
+ generic-syntax-expanders
+ racket/base
+ syntax/parse
+ (only-in racket/base [... …])]]
+
+@title{Global operations}
+
+The global patterns presented below match all of the given
+@racket[#,ntax-pattern]s, like @racket[~and] does, and perform a global
+aggregation over all the values corresponding to successful matches of a global
+pattern using the same @racket[#,tribute-name].
+
+After the whole @racket[~seq-no-order] or @racket[~no-order] finished matching
+its contents, but before "post" operations are executed, the attribute
+@racket[#,tribute-name] is bound to
+@racket[(_aggrgate-function _value₁ ... _valueₙ)], where each @racket[valueᵢ] is
+the value which was passed to an occurrence of @racket[~global-or] with the same
+@racket[_attribute-name], and which successfully matched. The
+@racket[_aggregate-function] will be @racket[or] for @racket[~global-or],
+@racket[and] for @racket[~global-and] or @racket[+] for
+@racket[~global-counter].
+
+Each @racket[valueᵢ] is computed in the context in which it appears, after the
+@racket[_syntax-pattern]s. This means that it can access:
+@itemlist[
+ @item{attributes already bound in the current alternative clause within the
+ current @racket[~no-order] or @racket[~seq-no-order]}
+ @item{attributes bound by the @racket[_syntax-patterns]s}
+ @item{attributes already bound outside of the @racket[~no-order] or
+ @racket[~seq-no-order]}
+ @item{but it cannot access attributes bound in other alternative clauses within
+ the current @racket[~no-order] or @racket[~seq-no-order].}]
+
+The @racket[valueᵢ] are aggregated with @racket[or], @racket[and] or @racket[+]
+in the order in which they appear in the @racket[~no-order] or
+@racket[~seq-no-order]. If a @racket[valueᵢ] appears under ellipses, or as part
+of an alternative clause which can match more than once (i.e. not @racket[~once]
+or @racket[~optional]), then each match within that @racket[valueᵢ] group is
+aggregated in the order it appears.
+
+Since this notion of order is rather complex, it is possible that future
+versions of this library will always return a boolean (@racket[#f] or
+@racket[#t] for @racket[~global-or] and @racket[~global-and], which would make
+the notion of order irrelevant.
+
+@defform[#:kind "eh-mixin expander"
+ (~global-or attribute-name+value #,ntax-pattern ...)
+ #:grammar
+ [(attribute-name+value #,tribute-name
+ [#,tribute-name valueᵢ])]]{
+ Matches all of the given @racket[#,ntax-pattern]s, like @racket[~and] does, and
+ perform a global @racket[or] over all the values corresponding to successful
+ matches of a global pattern using the same @racket[#,tribute-name]. See above
+ for a description of how global operations work.
+
+ If the @racket[valueᵢ] is omitted, @racket[#t] is used as a default.
+
+ The result is always transformed into a boolean, so @racket[_attribute-name] is
+ always bound to either @racket[#t] or @racket[#f].}
+
+@defform[#:kind "eh-mixin expander"
+ (~global-and attribute-name+value #,ntax-pattern ...)
+ #:grammar
+ [(attribute-name+value [#,tribute-name valueᵢ])]]{
+ Matches all of the given @racket[#,ntax-pattern]s, like @racket[~and] does, and
+ perform a global @racket[and] over all the values corresponding to successful
+ matches of a global pattern using the same @racket[#,tribute-name]. See above
+ for a description of how global operations work.
+
+ If there is at least one occurrence of @racket[~global-and] for that
+ @racket[_attribute-name] which successfully matches, the result of the
+ @racket[(and valueᵢ ...)] is always coerced to a boolean, so
+ @racket[_attribute-name] is always bound to either @racket[#t] or @racket[#f].
+
+ If there are no matches at all, the special value @racket['none] is used
+ instead of @racket[#t] as would be produced by @racket[(and)].}
+
+@defform[#:kind "eh-mixin expander"
+ (~global-counter attribute-name+value #,ntax-pattern ...)
+ #:grammar
+ [(attribute-name+value #,tribute-name
+ [#,tribute-name valueᵢ])]]{
+ Matches all of the given @racket[#,ntax-pattern]s, like @racket[~and] does, and
+ perform a global @racket[+] over all the values corresponding to successful
+ matches of a global pattern using the same @racket[#,tribute-name]. See above
+ for a description of how global operations work.
+
+ If the @racket[valueᵢ] is omitted, @racket[1] is used as a default.}
+
+@;@defform[(aggregate-global-or)]
+@;@defform[(aggregate-global-and)]
+@;@defform[(aggregate-global-counter)]
diff --git a/scribblings/misc.scrbl b/scribblings/misc.scrbl
@@ -0,0 +1,18 @@
+#lang scribble/manual
+@require[scribble/example
+ "utils.rkt"
+ @for-label[phc-toolkit/untyped
+ extensible-parser-specifications
+ generic-syntax-expanders
+ racket/base
+ syntax/parse
+ (only-in racket/base [... …])]]
+
+@title{Miscellaneous pattern expanders}
+
+@defform[#:kind "pattern expander"
+ {~nop}]{
+ The @tech[#:doc '(lib "syntax/scribblings/syntax.scrbl")
+ #:key "action pattern"]{A-pattern} @racket[~nop] does not perform any
+ action. It simply expands to @racket[{~do}].
+}
diff --git a/scribblings/no-order.scrbl b/scribblings/no-order.scrbl
@@ -0,0 +1,192 @@
+#lang scribble/manual
+@require[scribble/example
+ "utils.rkt"
+ @for-label[phc-toolkit/untyped
+ extensible-parser-specifications
+ generic-syntax-expanders
+ racket/base
+ syntax/parse
+ (only-in racket/base [... …])]]
+
+@title{Matching alternatives in any order}
+
+@defform[#:kind "pattern expander"
+ #:literals (~mixin ~or)
+ (~seq-no-order clause-or-mixin ...)
+ #:grammar
+ [(clause-or-mixin #,ntax-pattern
+ (~mixin #,-alternative-mixin)
+ (~or clause-or-mixin ...)
+ derived-or)]]{
+ Splicing pattern which matches the given @racket[clause-or-mixin]s in any
+ order, enforcing the global constraints expressed within each.
+
+ Nested @racket[~or] directly below @racket[~seq-no-order] are recursively
+ inlined. In other words, the @racket[~or] present directly below the
+ @racket[~seq-no-order] or below such an @racket[~or] clause do not behave as
+ "exclusive or", but instead contain clauses which can appear in any order.
+ These clauses are not grouped in any way by the @racket[~or], i.e.
+ @racket[(~no-order (~or (~or a b) (~or c d)))] is equivalent to
+ @racket[(~no-order a b c d)].
+
+ The @racket[derived-or] term covers any
+ @tech[#:doc '(lib "syntax/scribblings/syntax.scrbl")]{pattern expander} or
+ @tech{eh-mixin expander} application which expands to a
+ @racket[clause-or-mixin]. The expansion of pattern and eh-mixin expanders
+ happens before inlining the top @racket[~or] clauses.}
+
+@defform[#:kind "pattern expander"
+ #:literals (~mixin ~or)
+ (~no-order clause-or-mixin ...)
+ #:grammar
+ [(clause-or-mixin #,ntax-pattern
+ (~mixin #,-alternative-mixin)
+ (~or clause-or-mixin ...)
+ derived-or)]]{
+
+ Like @racket[~seq-no-order], except that it matches a syntax list, instead of
+ being spliced into the surrounding sequence of patterns. In other words,
+
+ @racketblock[(~seq-no-order clause-or-mixin ...)]
+
+ Equivalent to (notice the extra pair of braces):
+
+ @racketblock[({~seq-no-order clause-or-mixin ...})]}
+
+@section{Enforcing a partial order on the alternatives}
+
+@defform[#:kind "eh-mixin expander"
+ (~order-point point-name #,ntax-pattern ...)]{
+ When parsing a sequence of elements, @racket[~seq-no-order] and
+ @racket[~no-order] associate an increasing number to each element starting from
+ zero.
+
+ The number associated with the first element matched by
+ @racket[#,ntax-pattern ...] is memorised into the attribute
+ @racket[point-name].
+
+ This allows the position of elements matched by otherwise independent mixins to
+ be compared using @racket[order-point<] and @racket[order-point>]}
+
+@defform[(order-point< a b)
+ #:grammar
+ [(a #,tribute-name)
+ (b #,tribute-name)]]{
+ Returns @racket[#t] when the first element matched by
+ @racket[(~order-point a #,ntax-pattern ...)] occurs before the first element
+ matched by @racket[(~order-point b #,ntax-pattern ...)]. Otherwise, returns
+ @racket[#f].
+
+ This operation does not fail if @racket[a] or @racket[b] are bound to
+ @racket[#f] (i.e. their corresponding @racket[_syntax-pattern ...] did not
+ match). Instead, in both cases, it returns @racket[#f].}
+
+@defform[(order-point> a b)
+ #:grammar
+ [(a #,tribute-name)
+ (b #,tribute-name)]]{
+ Returns @racket[#t] when the first element matched by
+ @racket[(~order-point a #,ntax-pattern ...)] occurs after the first element
+ matched by @racket[(~order-point b #,ntax-pattern ...)]. Otherwise, returns
+ @racket[#f].
+
+ This operation does not fail if @racket[a] or @racket[b] are bound to
+ @racket[#f] (i.e. their corresponding @racket[_syntax-pattern ...] did not
+ match). Instead, in both cases, it returns @racket[#f].}
+
+@defform[(try-order-point< a b)
+ #:grammar
+ [(a #,tribute-name)
+ (b #,tribute-name)]]{
+
+ Like @racket[order-point<], except that it does not fail if @racket[a] or
+ @racket[b] are not attributes, or if they are bound to @racket[#f]. Instead, in
+ all those cases, it returns @racket[#f].
+
+ It can be used as follows:
+
+ @racketblock[
+ (~post-fail "a must appear after b"
+ #:when (try-order-point< a b))]
+
+ The same caveats as for @racket[try-attribute] apply.}
+
+@defform[(try-order-point> a b)
+ #:grammar
+ [(a #,tribute-name)
+ (b #,tribute-name)]]{
+
+ Like @racket[order-point>], except that it does not fail if @racket[a] or
+ @racket[b] are not attributes, or if they are bound to @racket[#f]. Instead, in
+ all those cases, it returns @racket[#f].
+
+ It can be used as follows:
+
+ @racketblock[
+ (~post-fail "a must appear before b"
+ #:when (try-order-point> a b))]
+
+ The same caveats as for @racket[try-attribute] apply.}
+
+@defform[#:kind "eh-mixin-expander"
+ (~before other message pat ...)]{
+
+ Post-checks that the first element matched by @racket[pat ...] appears before
+ the @racket[other] order-point. This is a shorthand for:
+
+ @racketblock[{~order-point pt
+ {~seq pat ...}
+ {~post-fail message #:when (order-point> pt other)}}]
+
+ Note: Hopefully @racket[~before] will be modified in the future so that it
+ auto-detects if the @racket[other] order-point is not defined as part of the
+ current @racket[~no-order]. Do not rely on comparisons with order points
+ somehow defined outside the current @racket[~no-order], as that behaviour may
+ change in the future.}
+
+@defform[#:kind "eh-mixin-expander"
+ (~after other message pat ...)]{
+ Post-checks that the first element matched by @racket[pat ...] appears after
+ the @racket[other] order-point. This is a shorthand for:
+
+ @racketblock[{~order-point pt
+ {~seq pat ...}
+ {~post-fail message #:when (order-point< pt other)}}]
+
+ Note: Hopefully @racket[~after] will be modified in the future so that it
+ auto-detects if the @racket[other] order-point is not defined as part of the
+ current @racket[~no-order]. Do not rely on comparisons with order points
+ somehow defined outside the current @racket[~no-order], as that behaviour may
+ change in the future.}
+
+@defform[#:kind "eh-mixin-expander"
+ (~try-before other message pat ...)]{
+
+ Post-checks that the first element matched by @racket[pat ...] appears before
+ the @racket[other] order-point. The @racket[try-] version does not cause an
+ error if the order-point @racket[other] is not define (e.g. it was part of
+ another mixin which was not included). This is a shorthand for:
+
+ @racketblock[{~order-point pt
+ {~seq pat ...}
+ {~post-fail message #:when (try-order-point> pt other)}}]
+
+ Note: Hopefully @racket[~before] will be modified in the future so that it
+ auto-detects if the @racket[other] order-point is missing. This form will then
+ be removed.}
+
+@defform[#:kind "eh-mixin-expander"
+ (~try-after other message pat ...)]{
+ Post-checks that the first element matched by @racket[pat ...] appears after
+ the @racket[other] order-point. The @racket[try-] version does not cause an
+ error if the order-point @racket[other] is not define (e.g. it was part of
+ another mixin which was not included). This is a shorthand for:
+
+ @racketblock[{~order-point pt
+ {~seq pat ...}
+ {~post-fail message #:when (try-order-point< pt other)}}]
+
+ Note: Hopefully @racket[~after] will be modified in the future so that it
+ auto-detects if the @racket[other] order-point is missing. This form will then
+ be removed.}
+
diff --git a/scribblings/post.scrbl b/scribblings/post.scrbl
@@ -0,0 +1,40 @@
+#lang scribble/manual
+@require[scribble/example
+ "utils.rkt"
+ @for-label[phc-toolkit/untyped
+ extensible-parser-specifications
+ generic-syntax-expanders
+ racket/base
+ syntax/parse
+ (only-in racket/base [... …])]]
+
+@title{Post operations}
+
+@defform*[#:kind "eh-mixin expander"
+ [(~post-check #,ntax-pattern #,A-patte)
+ (~post-check #,A-patte)]]{
+ Matches @racket[#,ntax-pattern], and executes the given @racket[#,A-patte]
+ after the whole @racket[~seq-no-order] or @racket[~no-order] finished matching
+ its contents.
+
+ If unspecified, the @racket[_syntax-pattern] defaults to @racket[(~nop)].}
+
+@defform*[#:kind "eh-mixin expander"
+ [(~post-fail message #:when condition)
+ (~post-fail #:when condition message)
+ (~post-fail message #:unless unless-condition)
+ (~post-fail #:unless unless-condition message)]]{
+
+ After the whole @racket[~seq-no-order] or @racket[~no-order] finished matching
+ its contents, checks whether @racket[condition] or @racket[unless-condition] is
+ true or false, respectively. If this is the case the whole
+ @racket[~seq-no-order] or @racket[~no-order] is rejected with the given
+ @racket[_message].
+
+ Note that there is an implicit cut (@racket[~!]) between the no-order patterns
+ and the "post" checks, so after a @racket[~post-fail] fails,
+ @racket[syntax-parse] does not backtrack and attempt different combinations of
+ patterns to match the sequence, nor does it backtrack and attempt to match a
+ shorter sequence. This is by design, as it allows for better error messages
+ (syntax-parse would otherwise attempt and possibly succeed in matching a
+ shorter sequence, then just treat the remaining terms as "unexpected terms").}
diff --git a/scribblings/pre-global-post-order.scrbl b/scribblings/pre-global-post-order.scrbl
@@ -0,0 +1,82 @@
+#lang scribble/manual
+@require[scribble/example
+ "utils.rkt"
+ @for-label[phc-toolkit/untyped
+ extensible-parser-specifications
+ generic-syntax-expanders
+ racket/base
+ syntax/parse
+ (only-in racket/base [... …])]]
+
+@title{Order in which the attributes are bound for post operations and
+ global operations}
+
+Within the @racket[_A-pattern]s of post operations, the regular attributes bound
+by all the clauses inside @racket[~seq-no-order] or @racket[~no-order] are
+bound. The attributes defined as part of all "global" actions are bound too. The
+attributes defined as part of "post" actions of other clauses are bound only if
+the clause defining them appears before the current clause in the source code.
+For example, the following code works because the clause containing
+@racket[{~post-fail "2 is incompatible with 1" #:when (not (attribute a))}]
+appears after the clause which binds @racket[a] with the "post" action
+@racket[{~post-check {~bind ([a #'the-a])}}].
+
+@racketblock[
+ {~seq-no-order
+ {~post-check {~and the-a 1} {~bind ([a #'the-a])}}
+ {~and 2 {~post-fail "2 is incompatible with 1" #:when (not (attribute a))}}}]
+
+If the two clauses are swapped, then the following code would raise a syntax
+error because @racket[a] is not bound as an attribute in the
+@racket[~post-fail]:
+
+@racketblock[
+ {~seq-no-order
+ {~and 2 {~post-fail "2 is incompatible with 1" #:when (not (attribute a))}}
+ {~post-check {~and the-a 1} {~bind ([a #'the-a])}}}]
+
+On the other hand, the following code, which does not bind @racket[a] as part
+of a post operation, is valid:
+
+@racketblock[
+ {~seq-no-order
+ {~and 2 {~post-fail "2 is incompatible with 1" #:when (not (attribute a))}}
+ {~and the-a 1 {~bind ([a #'the-a])}}}]
+
+Furthermore, the following code still works, as attributes are bound by the
+"global" operations before the "post" operations are executed:
+
+@racketblock[
+ {~seq-no-order
+ {~and 2 {~post-fail "2 is incompatible with 1" #:when (not (attribute a))}}
+ {~global-or a 1}}]
+
+Note that the order in which clauses appear within the @racket[~seq-no-order]
+or @racket[~no-order] does not impact the order in which the elements must
+appear in the matched syntax (aside from issues related to greediness).
+
+@defform[(try-attribute #,tribute-name)]{
+ This macro expands to @racket[(attribute #,tribute-name)] if
+ @racket[#,tribute-name] is bound as a syntax pattern variable, and to
+ @racket[#f] otherwise.
+
+ This macro can be used to check for mutual exclusion of an attribute which is
+ bound by other mixins that might or might not be present in the final
+ @racket[~no-order] or @racket[~seq-no-order].
+
+ Use this sparingly, as if an syntax pattern variable with that name is bound by
+ an outer scope, the @racket[try-attribute] macro will still access it, ignorant
+ of the fact that the current @racket[~seq-no-order] does not contain any mixin
+ which binds that attribute.
+
+ Instead, it is better practice to use
+ @racket[{~global-or [_attribute-name #f]}] or
+ @racket[{~global-and [_attribute-name #t]}] to ensure that the attribute is
+ declared, while using the operation's neutral element to not alter the final
+ result.}
+
+@defform[(if-attribute #,tribute-name if-branch else-branch)]{
+ This macro expands to @racket[if-branch] if @racket[#,tribute-name] is bound as
+ a syntax pattern variable, and to @racket[else-branch] otherwise.
+
+ The same caveats as for @racket[try-attribute] apply.}
+\ No newline at end of file
diff --git a/scribblings/pre-global-post-section.scrbl b/scribblings/pre-global-post-section.scrbl
@@ -0,0 +1,23 @@
+#lang scribble/manual
+
+@title{Post operations and global operations}
+
+Pre operations happen before the @racket[~!] backtracking cut, so they can
+affect what combination of alternative clauses the parser will choose. Post
+operations happen after the @racket[~!] backtracking cut, and can only reject
+the @racket[~no-order] or @racket[~seq-no-order] as a whole (i.e. different
+orders will not be attempted after a @racket[~post-fail]. Global operations will
+always succeed.
+
+Post operations can access the attributes defined by global and pre operations
+as well as attributes defined by the alternative clauses. Global operations
+cannot access the attributes of post operations, and pre operations cannot
+access the attributes of global and post operations. See
+@secref["Order_in_which_the_attributes_are_bound_for_post_operations_and_global_operations"
+ #:doc '(lib "extensible-parser-specifications/scribblings/extensible-parser-specifications.scrbl")]
+for more details.
+
+@include-section{pre.scrbl}
+@include-section{global.scrbl}
+@include-section{post.scrbl}
+@include-section{pre-global-post-order.scrbl}
diff --git a/scribblings/pre.scrbl b/scribblings/pre.scrbl
@@ -0,0 +1,75 @@
+#lang scribble/manual
+@require[scribble/example
+ "utils.rkt"
+ @for-label[phc-toolkit/untyped
+ extensible-parser-specifications
+ generic-syntax-expanders
+ racket/base
+ syntax/parse
+ (only-in racket/base [... …])]]
+
+@title{Pre operations}
+
+@defform[#:kind "eh-mixin expander"
+ (~named-seq #,tribute-name #,ntax-pattern ...)]{
+ Equivalent to @racket[{~seq #,ntax-pattern ...}], but also binds the
+ @racket[#,tribute-name] to the whole sequence. If the sequence appears inside
+ an @racket[~optional] or @racket[~or] clause that fails, the
+ @racket[_attribute-name] is still bound to the empty sequence.
+
+ Known issues: this may not behave as expected if @racket[~named-seq] appears
+ under ellipses.
+
+ This probably should bind the sequence attribute @emph{before} the "global"
+ operations, instead of being a "post" operation, and may be changed in that way
+ the future.}
+
+@defform[#:kind "eh-mixin expander"
+ (~maybe/empty #,ntax-pattern ...)]{
+
+ Optionally matches @racket[{~seq #,ntax-pattern ...}]. If the match fails, it
+ matches these same sequence of patterns against the empty syntax list
+ @racket[#'()]. This form can be used in an ellipsis-head position. This is
+ implemented in both cases as a "pre" action.}
+
+@defform[#:kind "eh-mixin expander"
+ (~optional/else #,ntax-pattern
+ maybe-defaults
+ else-post-fail ...
+ maybe-name)
+ #:grammar
+ [(maybe-defaults (code:line)
+ (code:line #:defaults (default-binding ...)))
+ (else-post-fail
+ (code:line #:else-post-fail message #:when condition)
+ (code:line #:else-post-fail #:when condition message)
+ (code:line #:else-post-fail message #:unless unless-condition)
+ (code:line #:else-post-fail #:unless unless-condition message))
+ (maybe-name (code:line)
+ (code:line #:name #,tribute-name))]]{
+ Like @racket[~optional], but with conditional post-failures when the pattern is
+ not matched. An @racket[~optional/else] pattern can be matched zero or one time
+ as part of the @racket[~seq-no-order] or @racket[~no-order]. When it is not
+ matched (i.e. matched zero times):
+ @itemlist[
+ @item{it uses the default values for the attributes as specified with
+ @racket[#:defaults].}
+ @item{for each @racket[#:else-post-fail] clause, it checks whether the
+ @racket[condition] or @racket[unless-condition] is true or false,
+ respectively. If this is the case the whole @racket[~seq-no-order] or
+ @racket[~no-order] is rejected with the given @racket[_message]. The
+ behaviour of @racket[#:else-post-fail] is the same as the behaviour of
+ @racket[~post-fail], except that the "post" conditional failure can only be
+ executed if the optional @racket[_syntax-pattern] was not matched.
+
+ Note that there is an implicit cut (@racket[~!]) between the no-order
+ patterns and the "post" checks, so after a @racket[~post-fail] fails,
+ @racket[syntax-parse] does not backtrack and attempt different combinations
+ of patterns to match the sequence, nor does it backtrack and attempt to match
+ a shorter sequence. This is by design, as it allows for better error messages
+ (syntax-parse would otherwise attempt and possibly succeed in matching a
+ shorter sequence, then just treat the remaining terms as
+ "unexpected terms").}]
+
+ The meaning of @racket[#:name #,tribute-name] option is the same as for
+ @racket[~optional].}
diff --git a/scribblings/rest.scrbl b/scribblings/rest.scrbl
@@ -0,0 +1,67 @@
+#lang scribble/manual
+@require[scribble/example
+ "utils.rkt"
+ @for-label[phc-toolkit/untyped
+ extensible-parser-specifications
+ generic-syntax-expanders
+ racket/base
+ syntax/parse
+ (only-in racket/base [... …])]]
+
+@title{Parsing the tail of improper lists}
+
+@defform[#:kind "eh-mixin expander"
+ {~lift-rest pat}]{
+
+ Lifts @racket[pat] out of the current mixin, so that it is used as a pattern to
+ match the tail of the improper list being matched. It is subject to the
+ following restrictions:
+ @itemlist[
+ @item{@racket[~lift-rest] is allowed only within @racket[~no-order], but not
+ within @racket[~seq-no-order]. @racket[~seq-no-order] always matches against
+ a proper sequence of elements, while @racket[~no-order] may match a proper or
+ improper list.}
+ @item{The tail of the improper list must not be a pair, otherwise the
+ @racket[car] would have been included in the main part of the list.}
+ @item{The @racket[pat] is used to match the tail only if its surrounding
+ pattern successfully matched some elements of the main section of the list.
+
+ If the @racket[{~lift-rest pat}] is the only pattern present within an
+ alternative, then it is always used.
+
+ @examples[#:eval (make-evaluator)
+ (syntax-parse #'(x y z . 1)
+ [(~no-order {~lift-rest r:nat} i:id)
+ (syntax->datum #'(r i ...))])]}
+ @item{
+ Among the lifted rest patterns which are considered (see the point
+ above), only one may successfully match. An error is raised if two or more
+ lifted rest patterns successfully match against the tail of the list.
+
+ @examples[#:eval (make-evaluator)
+ (eval:no-prompt
+ (define p
+ (syntax-parser
+ [(~no-order {~and {~literal x}
+ {~lift-rest rn:nat}
+ {~lift-rest ri:id}}
+ {~and {~literal y}
+ {~lift-rest rs:str}
+ {~lift-rest rj:id}})
+ 'match]
+ #;[_
+ 'fail])))
+ (code:line (p #'(x . 1)) (code:comment "rn and ri considered, rn matched"))
+ (code:line (p #'(x . z)) (code:comment "rn and ri considered, ri matched"))
+ (code:line (p #'(y . "a")) (code:comment "rs and rj considered, rs matched"))
+ (code:line (p #'(y . z)) (code:comment "rs and rj considered, rj matched"))
+ (code:line (p #'(x y . 1)) (code:comment "all four considered, rn matched"))
+ (eval:alts (code:line (p #'(x y . z)) (code:comment "all four considered, both ri and rj matched"))
+ (eval:error (p #'(x y . z))))]
+
+ The rationale is that selecting the first lifted rest pattern that matches
+ would result in unclear behaviour, as the order of the alternative clauses
+ should not be significant.}
+ @item{Post and global operations can be used within the @racket[pat]. This
+ combination of features is not thoroughly tested, however. Please report any
+ issues you run into.}]}
diff --git a/scribblings/utils.rkt b/scribblings/utils.rkt
@@ -0,0 +1,26 @@
+#lang racket
+
+(require scribble/manual
+ scribble/example)
+
+(provide (all-defined-out))
+
+(define ntax-pattern (tech #:doc '(lib "syntax/scribblings/syntax.scrbl")
+ #:key "syntax pattern"
+ "syntax-pattern"))
+
+(define -alternative-mixin (tech #:key "eh-alternative mixin"
+ #:doc '(lib "extensible-parser-specifications/scribblings/extensible-parser-specifications.scrbl")
+ "eh-alternative-mixin"))
+
+(define tribute-name (tech #:doc '(lib "syntax/scribblings/syntax.scrbl")
+ #:key "attribute"
+ "attribute-name"))
+
+(define A-patte (tech #:doc '(lib "syntax/scribblings/syntax.scrbl")
+ #:key "action pattern"
+ "A-pattern"))
+
+(define make-evaluator
+ (make-eval-factory '(syntax/parse
+ extensible-parser-specifications)))
diff --git a/test/test-order-point.rkt b/test/test-order-point.rkt
@@ -40,5 +40,5 @@
(check-fail-abc #'(#:c #:b #:a) #px"#:a must appear after #:b")
(check-fail-abc #'(#:b #:c #:a) #px"#:a must appear after #:b")
(check-fail-abc #'(#:b #:a #:c) #px"#:a must appear after #:b")
-(check-fail-abc #'(#:a #:a) #px"unexpected term")
-(check-fail-abc #'(#:c #:c) #px"unexpected term")
-\ No newline at end of file
+(check-fail-abc #'(#:a #:a) #px"expected abc-order")
+(check-fail-abc #'(#:c #:c) #px"expected abc-order")
+\ No newline at end of file
diff --git a/test/test-rest.rkt b/test/test-rest.rkt
@@ -0,0 +1,88 @@
+#lang racket
+
+(require extensible-parser-specifications
+ extensible-parser-specifications/private/no-order
+ racket/require
+ syntax/parse
+ (subtract-in syntax/stx phc-toolkit/untyped)
+ rackunit
+ racket/format
+ phc-toolkit/untyped
+ (for-syntax syntax/parse
+ syntax/stx
+ racket/format))
+
+(check-equal?
+ (syntax-parse #'(1 "ab" . #:kw)
+ [(~no-order {~or {~lift-rest {~and k #:kw}}
+ {~once n:nat}}
+ {~once s:str})
+ (syntax->datum #'(k n s))])
+ '(#:kw 1 "ab"))
+
+
+(check-equal?
+ (syntax-parse #'(1 "ab" . #:kw)
+ [(~no-order {~lift-rest {~and k #:kw}}
+ {~once n:nat}
+ {~once s:str})
+ (syntax->datum #'(k n s))])
+ '(#:kw 1 "ab"))
+
+(check-equal?
+ (syntax-parse #'(1 "ab" . #:kw)
+ [(~no-order {~once {~and n:nat
+ {~lift-rest {~and k #:kw}}}}
+ {~once s:str})
+ (syntax->datum #'(k n s))])
+ '(#:kw 1 "ab"))
+
+(check-equal?
+ (syntax-parse #'(1 "ab" . #:kw)
+ [(~no-order {~once n:nat}
+ {~lift-rest {~and k #:kw}}
+ {~once s:str})
+ (syntax->datum #'(k n s))]
+ [_ #f])
+ '(#:kw 1 "ab"))
+
+(test-begin
+ "Exactly the same as above, but with the post-fail"
+ (check-false
+ (syntax-parse #'(1 "ab" . #:kw)
+ [(~no-order {~once n:nat}
+ {~lift-rest {~and k #:kw
+ {~post-fail "e" #:when (= (syntax-e #'n) 1)}}}
+ {~once s:str})
+ (syntax->datum #'(k n s))]
+ [_ #f])))
+
+(test-begin
+ "Exactly the same as above, but with a different value (2 instead of 1)"
+ (check-equal?
+ (syntax-parse #'(2 "ab" . #:kw)
+ [(~no-order {~once n:nat}
+ {~lift-rest {~and k #:kw
+ {~post-fail "e" #:when (= (syntax-e #'n) 1)}}}
+ {~once s:str})
+ (syntax->datum #'(k n s))]
+ [_ #f])
+ '(#:kw 2 "ab")))
+
+(define p
+ (syntax-parser
+ [(~no-order {~and {~literal x}
+ {~lift-rest rn:nat}
+ {~lift-rest ri:id}}
+ {~and {~literal y}
+ {~lift-rest rs:str}
+ {~lift-rest rj:id}})
+ 'match]))
+
+(check-equal? (p #'(x . 1)) 'match)
+(check-equal? (p #'(x . z)) 'match)
+(check-equal? (p #'(y . "a")) 'match)
+(check-equal? (p #'(y . z)) 'match)
+(check-equal? (p #'(x y . 1)) 'match)
+(check-exn #px"more than one of the lifted rest patterns matched"
+ (λ () (p #'(x y . z))))
+\ No newline at end of file