Implemented and documented: pre operations, ~lift-rest.
This commit is contained in:
parent
f030c3ffbe
commit
01c9b553a5
6
main.rkt
6
main.rkt
|
@ -3,11 +3,13 @@
|
||||||
(require generic-syntax-expanders
|
(require generic-syntax-expanders
|
||||||
"private/parameters.rkt"
|
"private/parameters.rkt"
|
||||||
"private/no-order.rkt"
|
"private/no-order.rkt"
|
||||||
|
"private/pre.rkt"
|
||||||
"private/post.rkt"
|
"private/post.rkt"
|
||||||
"private/global.rkt"
|
"private/global.rkt"
|
||||||
"private/optional.rkt"
|
"private/optional.rkt"
|
||||||
"private/mixin.rkt"
|
"private/mixin.rkt"
|
||||||
"private/try-attribute.rkt"
|
"private/try-attribute.rkt"
|
||||||
|
"private/nop.rkt"
|
||||||
(for-template "private/define-syntax+simple-api.rkt")
|
(for-template "private/define-syntax+simple-api.rkt")
|
||||||
syntax/parse)
|
syntax/parse)
|
||||||
|
|
||||||
|
@ -26,9 +28,13 @@
|
||||||
order-point>
|
order-point>
|
||||||
try-order-point<
|
try-order-point<
|
||||||
try-order-point>
|
try-order-point>
|
||||||
|
~before
|
||||||
|
~after
|
||||||
|
~lift-rest
|
||||||
~mixin
|
~mixin
|
||||||
~post-check
|
~post-check
|
||||||
~post-fail
|
~post-fail
|
||||||
|
~maybe/empty
|
||||||
~named-seq
|
~named-seq
|
||||||
~nop
|
~nop
|
||||||
~optional/else
|
~optional/else
|
||||||
|
|
|
@ -22,6 +22,8 @@
|
||||||
;syntax/parse/experimental/eh
|
;syntax/parse/experimental/eh
|
||||||
generic-syntax-expanders
|
generic-syntax-expanders
|
||||||
phc-toolkit/untyped
|
phc-toolkit/untyped
|
||||||
|
racket/list
|
||||||
|
racket/function
|
||||||
(for-syntax racket/base
|
(for-syntax racket/base
|
||||||
syntax/parse
|
syntax/parse
|
||||||
racket/syntax
|
racket/syntax
|
||||||
|
@ -34,11 +36,15 @@
|
||||||
(provide define-eh-alternative-mixin
|
(provide define-eh-alternative-mixin
|
||||||
~seq-no-order
|
~seq-no-order
|
||||||
~no-order
|
~no-order
|
||||||
|
~before
|
||||||
|
~after
|
||||||
~order-point
|
~order-point
|
||||||
order-point<
|
order-point<
|
||||||
order-point>
|
order-point>
|
||||||
try-order-point<
|
try-order-point<
|
||||||
try-order-point>
|
try-order-point>
|
||||||
|
~lift-rest
|
||||||
|
~omitable-lifted-rest ;; Private
|
||||||
(expander-out eh-mixin))
|
(expander-out eh-mixin))
|
||||||
|
|
||||||
(define-expander-type eh-mixin)
|
(define-expander-type eh-mixin)
|
||||||
|
@ -73,74 +79,166 @@
|
||||||
(record-disappeared-uses dis)
|
(record-disappeared-uses dis)
|
||||||
#'(void))}))
|
#'(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
|
;; 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
|
;; are nested ~seq-no-order, the ~post-fail is caught by the nearest
|
||||||
;; ~seq-no-order.
|
;; ~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
|
(define-for-syntax ((no-order-ish seq?) stx)
|
||||||
(λ/syntax-case (_ . rest) ()
|
(syntax-case stx ()
|
||||||
#'({~seq-no-order . rest}))))
|
[(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
|
(define-eh-mixin-expander ~order-point
|
||||||
(λ (stx)
|
(λ (stx)
|
||||||
|
@ -168,4 +266,67 @@
|
||||||
(if-attribute a (if-attribute b (order-point< a b) #f) #f))
|
(if-attribute a (if-attribute b (order-point< a b) #f) #f))
|
||||||
|
|
||||||
(define-syntax-rule (try-order-point> a b)
|
(define-syntax-rule (try-order-point> a b)
|
||||||
(if-attribute a (if-attribute b (order-point> a b) #f) #f))
|
(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))])))
|
10
private/nop.rkt
Normal file
10
private/nop.rkt
Normal file
|
@ -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))))
|
|
@ -2,13 +2,17 @@
|
||||||
|
|
||||||
(require (for-syntax racket/base))
|
(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-accumulate!
|
||||||
eh-post-group
|
eh-post-group
|
||||||
eh-post-group!
|
eh-post-group!
|
||||||
clause-counter
|
clause-counter
|
||||||
get-new-clause!
|
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!)
|
(define-syntax-rule (define-dynamic-accumulator-parameter parameter-name name!)
|
||||||
(begin
|
(begin
|
||||||
|
@ -20,8 +24,10 @@
|
||||||
" used outside of ~seq-no-order")))
|
" used outside of ~seq-no-order")))
|
||||||
(apply (parameter-name) args))))
|
(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-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.
|
;; This is a crude hack.
|
||||||
(define-for-syntax (is-clause-id-sym? id-sym)
|
(define-for-syntax (is-clause-id-sym? id-sym)
|
||||||
|
@ -34,4 +40,4 @@
|
||||||
(error "Use get-new-clause! within (parameterize ([clause-counter …]) …)"))
|
(error "Use get-new-clause! within (parameterize ([clause-counter …]) …)"))
|
||||||
(datum->syntax #'here
|
(datum->syntax #'here
|
||||||
;; keep the spaces, they allow us to recognize clauses later.
|
;; keep the spaces, they allow us to recognize clauses later.
|
||||||
(string->symbol (format " -clause-~a " ((clause-counter))))))
|
(string->symbol (format " -clause-~a " ((clause-counter))))))
|
||||||
|
|
|
@ -6,16 +6,11 @@
|
||||||
racket/syntax
|
racket/syntax
|
||||||
phc-toolkit/untyped)
|
phc-toolkit/untyped)
|
||||||
"parameters.rkt"
|
"parameters.rkt"
|
||||||
"no-order.rkt")
|
"no-order.rkt"
|
||||||
|
"nop.rkt")
|
||||||
|
|
||||||
(provide ~nop
|
(provide ~post-check
|
||||||
~post-check
|
~post-fail)
|
||||||
~post-fail
|
|
||||||
~named-seq)
|
|
||||||
|
|
||||||
(define-syntax ~nop
|
|
||||||
(pattern-expander
|
|
||||||
(λ/syntax-case (_) () #'(~do))))
|
|
||||||
|
|
||||||
(define-eh-mixin-expander ~post-check
|
(define-eh-mixin-expander ~post-check
|
||||||
(λ (stx)
|
(λ (stx)
|
||||||
|
@ -38,22 +33,6 @@
|
||||||
...))
|
...))
|
||||||
#'(~and (~bind [clause-present #t]) . pats))])))
|
#'(~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)
|
(define-for-syntax (post-fail stx)
|
||||||
(syntax-case stx ()
|
(syntax-case stx ()
|
||||||
[(_ message #:when condition)
|
[(_ message #:when condition)
|
||||||
|
|
72
private/pre.rkt
Normal file
72
private/pre.rkt
Normal file
|
@ -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 (... ...)}}})])))
|
66
scribblings/defining-reusable-mixins.scrbl
Normal file
66
scribblings/defining-reusable-mixins.scrbl
Normal file
|
@ -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).}
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
#lang scribble/manual
|
#lang scribble/manual
|
||||||
@require[scribble/example
|
@require[scribble/example
|
||||||
|
"utils.rkt"
|
||||||
@for-label[phc-toolkit/untyped
|
@for-label[phc-toolkit/untyped
|
||||||
extensible-parser-specifications
|
extensible-parser-specifications
|
||||||
generic-syntax-expanders
|
generic-syntax-expanders
|
||||||
|
@ -7,494 +8,17 @@
|
||||||
syntax/parse
|
syntax/parse
|
||||||
(only-in racket/base [... …])]]
|
(only-in racket/base [... …])]]
|
||||||
|
|
||||||
@(define make-evaluator
|
|
||||||
(make-eval-factory '(syntax/parse
|
|
||||||
extensible-parser-specifications)))
|
|
||||||
|
|
||||||
@title{extensible-parser-specifications}
|
@title{extensible-parser-specifications}
|
||||||
@author{@author+email["Georges Dupéron" "georges.duperon@gmail.com"]}
|
@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]
|
@defmodule[extensible-parser-specifications]
|
||||||
|
|
||||||
@defform[#:literals (pattern)
|
@include-section{defining-reusable-mixins.scrbl}
|
||||||
(define-eh-alternative-mixin name maybe-define-class
|
@include-section{no-order.scrbl}
|
||||||
(pattern clause-or-mixin) ...)
|
@include-section{rest.scrbl}
|
||||||
#: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
|
@include-section{pre-global-post-section.scrbl}
|
||||||
@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"
|
@include-section{misc.scrbl}
|
||||||
eh-mixin-expander-type expander-type?]
|
@include-section{forward-attributes.scrbl}
|
||||||
@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.
|
|
||||||
|
|
||||||
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)]}
|
|
||||||
|
|
||||||
|
|
53
scribblings/forward-attributes.scrbl
Normal file
53
scribblings/forward-attributes.scrbl
Normal file
|
@ -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)]}
|
97
scribblings/global.scrbl
Normal file
97
scribblings/global.scrbl
Normal file
|
@ -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)]
|
18
scribblings/misc.scrbl
Normal file
18
scribblings/misc.scrbl
Normal file
|
@ -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}].
|
||||||
|
}
|
192
scribblings/no-order.scrbl
Normal file
192
scribblings/no-order.scrbl
Normal file
|
@ -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.}
|
||||||
|
|
40
scribblings/post.scrbl
Normal file
40
scribblings/post.scrbl
Normal file
|
@ -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").}
|
82
scribblings/pre-global-post-order.scrbl
Normal file
82
scribblings/pre-global-post-order.scrbl
Normal file
|
@ -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.}
|
23
scribblings/pre-global-post-section.scrbl
Normal file
23
scribblings/pre-global-post-section.scrbl
Normal file
|
@ -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}
|
75
scribblings/pre.scrbl
Normal file
75
scribblings/pre.scrbl
Normal file
|
@ -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].}
|
67
scribblings/rest.scrbl
Normal file
67
scribblings/rest.scrbl
Normal file
|
@ -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.}]}
|
26
scribblings/utils.rkt
Normal file
26
scribblings/utils.rkt
Normal file
|
@ -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)))
|
|
@ -40,5 +40,5 @@
|
||||||
(check-fail-abc #'(#:c #:b #:a) #px"#:a must appear after #:b")
|
(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 #:c #:a) #px"#:a must appear after #:b")
|
||||||
(check-fail-abc #'(#:b #:a #:c) #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 #'(#:a #:a) #px"expected abc-order")
|
||||||
(check-fail-abc #'(#:c #:c) #px"unexpected term")
|
(check-fail-abc #'(#:c #:c) #px"expected abc-order")
|
88
test/test-rest.rkt
Normal file
88
test/test-rest.rkt
Normal file
|
@ -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))))
|
Loading…
Reference in New Issue
Block a user