extensible-parser-specifica.../private/no-order.rkt
2016-09-05 23:23:40 +02:00

111 lines
4.3 KiB
Racket

#lang racket/base
;; TODO: it should be possible to specify a partial ordering and/or join the
;; constraints for multiple occurrences of the same ~no-order clause. The goal
;; is to be able to write a pattern for:
;; (some-macro opts … name opts … field … opts …)
;; where opts is a ~no-order, with some constraints like ~once, ~optional etc.
;; I'd like to write something like:
;; (some-macro (~no-order #:kw1
;; (~seq #:kw2 opt2)
;; name:id
;; (~and fields
;; (~seq field:id …)
;; (~global-before name fields))))
;; However, the current implementation uses "(~or no-order-clauses) …" which
;; does not permit a clause to see previously matched clauses.
;; Maybe save this for the unified parser and generator library (see on github
;; the repo jsmaniac/phc-toolkit, more specifically the file
;; scribblings/template.scrbl within).
(require syntax/parse
;syntax/parse/experimental/eh
generic-syntax-expanders
phc-toolkit/untyped
(for-syntax racket/base
syntax/parse
racket/syntax
phc-toolkit/untyped
racket/list
racket/pretty)
"parameters.rkt")
(provide define-eh-alternative-mixin
~seq-no-order
~no-order
(expander-out eh-mixin))
(define-expander-type eh-mixin)
(define-syntax define-eh-alternative-mixin
(syntax-parser
[(_ name (~maybe #:define-splicing-syntax-class splicing-name)
((~literal pattern) pat) ...)
#`(begin
(define-eh-mixin-expander name
(λ (_)
(quote-syntax (~or pat ...))))
#,@(if (attribute splicing-name)
#'((define-splicing-syntax-class splicing-name
(pattern {~seq-no-order {name}})))
#'()))]))
(define-for-syntax (inline-or stx)
(syntax-case stx ()
[(o . rest)
(and (identifier? #'o) (free-identifier=? #'o #'~or))
(apply append (stx-map inline-or #'rest))]
[x (list #'x)]))
;; 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
(let ()
(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:
(define alts
(parameterize ([eh-post-accumulate add-to-post!]
[eh-post-group add-to-post-groups!]
[clause-counter increment-counter])
;(inline-or
(expand-all-eh-mixin-expanders #'(~or pat ...))))
(define post-group-bindings
(for/list ([group (group-by car
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))]))
#`(~delimit-cut
(~and (~seq #,alts (... ...)) ;;(~or . #,alts)
~!
(~bind #,@post-group-bindings)
#,@post-acc))))]))))
(define-syntax ~no-order
(pattern-expander
(λ/syntax-case (_ . rest) ()
#'({~seq-no-order . rest}))))