racket/collects/mzlib/private/render-test-list.scm
Sam Tobin-Hochstadt d96e47c4b7 plt-match.ss/match.ss:
- don't export match:test-no-order, which is only used in generated code

test-no-order.ss
- reformat code
- use ormap instead of let loop

render-test-list:
- add define/opt sugar
- remove a lot of pointless stx arguments
- remove a lot of [quasi]syntax/loc

gen-match:
- use internal define instead of let
- remove quasisyntax/loc
- reformat
- remove pointlessly optional argument

coupling-and-binding:
- reformat
- use memf instead of custom loops

svn: r908
2005-09-23 19:55:12 +00:00

1350 lines
65 KiB
Scheme

;; This library is used by match.ss
(module render-test-list mzscheme
(provide render-test-list)
(require (lib "etc.ss"))
(require (lib "stx.ss" "syntax"))
(require (rename (lib "1.ss" "srfi") map-append append-map))
(require "match-error.ss"
"match-helper.ss"
"test-structure.scm"
"coupling-and-binding.scm"
"update-counts.scm"
"update-binding-counts.scm"
"reorder-tests.scm"
"match-expander-struct.ss"
;; the following are only used by render-test-list
"render-helpers.ss"
"test-no-order.ss")
(require-for-syntax "match-helper.ss"
"match-expander-struct.ss"
"test-no-order.ss")
(require-for-template mzscheme
"match-error.ss"
"test-no-order.ss"
"match-helper.ss")
(define-syntax define/opt
(syntax-rules ()
[(_ (nm args ...) body ...)
(define nm (opt-lambda (args ...) body ...))]))
;; BEGIN SPECIAL-GENERATORS.SCM
;;!(function or-gen
;; (form (or-gen exp orpatlist sf bv ks kf let-bound)
;; ->
;; syntax)
;; (contract (syntax list list list (list list -> syntax)
;; (list list -> syntax) list)
;; ->
;; syntax))
;; The function or-gen is very similar to the function gen except
;; that it is called when an or pattern is compiled. An or
;; pattern is essentially the same as a match pattern with several
;; clauses. The key differences are that it exists within a
;; larger pattern and the state of compilation has information
;; that will help optimaize its compilation. And the success of
;; any pattern results in the same outcome.
(define (or-gen exp orpatlist sf bv ks kf let-bound)
(define rendered-list
(map
(lambda (pat)
(cons (render-test-list pat exp)
(lambda (fail let-bound)
(lambda (sf bv)
(let ((bv (map
(lambda (bind)
(cons (car bind)
(subst-bindings (cdr bind)
let-bound)))
bv)))
(ks sf bv))))))
orpatlist))
(update-counts rendered-list)
(update-binding-counts rendered-list)
((meta-couple (reorder-all-lists rendered-list) kf let-bound bv) sf bv))
;;!(function next-outer
;; (form (next-outer p ae sf bv let-bound kf ks syntax bool)
;; ->
;; syntax)
;; (contract (syntax syntax list list list (list list -> syntax)
;; (list list -> syntax) syntax bool)
;; ->
;; syntax))
;; The function next-outer is basically a throw-back to the next
;; function of the original match compiler. It compiles a pattern
;; or sub-pattern of a clause and does not yield a list of
;; partially compiled test structs. This function is called
;; inside of test constructs that cannot be eliminated because of
;; a related presence in the test-so-far list. So, instead of
;; partially compiling patterns this function fully compiles patterns.
(define/opt (next-outer
p
ae ;; this is the actual expression
sf
bv
let-bound
kf
ks
[stx (syntax '())]
[opt #f])
(next-outer-helper p ae sf bv let-bound
(lambda (x) kf) (lambda (a b) ks) stx opt))
;;!(function next-outer-helper
;; (form (next-outer p ae sf bv let-bound kf-func ks-func syntax bool)
;; ->
;; syntax)
;; (contract (syntax syntax list list list (list list -> syntax)
;; (list list -> syntax) syntax bool)
;; ->
;; syntax))
;; The function next-outer-helper contains the meat of next-outer
;; and allows the programmer to pass higher order functions
;; ks-func and kf-func that will be given compile time imformation
;; about let-bindings etc. which in turn will allow the programmer
;; to take advantage of this info.
(define/opt (next-outer-helper
p
ae ;; this is the actual expression
sf
bv
let-bound
kf-func
ks-func
[stx (syntax '())]
[opt #f])
;; right now this does not bind new variables
(let ((rendered-list (render-test-list p ae stx)))
;; no need to reorder lists although I suspect that it may be
;; better to put shape tests first
(update-binding-count rendered-list)
((couple-tests rendered-list ks-func kf-func let-bound) sf bv)))
;;!(function create-test-func
;; (form (create-test-func p sf let-bound bind-map last-test)
;; ->
;; syntax)
;; (contract (syntax list list a-list bool) -> syntax))
;; This function creates a runtime function that is used as an
;; individual test in a list of tests for the list-no-order
;; pattern.
;; <pre>
;; bindmap - a-list of bindings mapped to their expressions
;; last-test - a boolean value that indicates whether this function
;; is collecting one value or a list of values.</pre>
(define (create-test-func p sf let-bound bind-map last-test)
#`(lambda (exp)
#,(next-outer-helper
p #'exp sf '() let-bound
(lambda (let-bound)
(lambda (sf bv)
#'#f))
(lambda (fail let-bound)
(lambda (sf bv)
#`(begin
#,@(map (lambda (bind)
(let ((binding-name (get-bind-val (car bind) bind-map))
(exp-to-bind
(subst-bindings (cdr bind) let-bound)))
(if last-test
#`(set! #,binding-name
(cons #,exp-to-bind #,binding-name))
#`(set! #,binding-name
#,exp-to-bind))))
bv)
#t))))))
;;!(function getbindings
;; (form (getbindings pat-syntax) -> list)
;; (contract syntax -> list))
;; This function given a pattern returns a list of pattern
;; variable names which are found in the pattern.
(define (getbindings pat-syntax)
(let/cc out
(next-outer
pat-syntax
(quote-syntax dummy)
'()
'()
'()
(lambda (sf bv) #'(dummy-symbol))
(lambda (sf bv) (out (map car bv))))))
;; END SPECIAL-GENERATORS.SCM
;; BEGIN DDK
;; END DDK
;; BEGIN DDK-HANDLERS.SCM
;;!(function handle-end-ddk-list
;; (form (handle-end-ddk-list ae kf ks pat
;; dot-dot-k
;; let-bound)
;; ->
;; ((list list) -> syntax))
;; (contract (syntax
;; ((list list) -> syntax)
;; ((list list) -> syntax)
;; syntax
;; syntax
;; list)
;; ->
;; ((list list) -> syntax)))
;; This returns a function which generates the code for
;; a pattern that ends with a ddk. This function is only applied to the
;; last pattern and the ddk.
;; Args:
;; ae - the expression being matched
;; kf - a failure function
;; ks - a success function
;; pat - the pattern to be matched repeatedly
;; dot-dot-k - the ddk pattern
;; let-bound - a list of let bindings
(define ((handle-end-ddk-list ae kf ks pat dot-dot-k let-bound) sf bv)
(let* ((k (stx-dot-dot-k? dot-dot-k))
(ksucc (lambda (sf bv)
(let ((bound (getbindings pat)))
(if (syntax? bound)
(kf sf bv)
(syntax-case pat (_)
(_ (ks sf bv))
(the-pat
(null? bound)
(with-syntax ((exp-sym (syntax exp-sym)))
(let* ((ptst (next-outer
pat
(syntax exp-sym)
sf
bv
let-bound
(lambda (sf bv) (syntax #f))
(lambda (sf bv) (syntax #t))))
(tst (syntax-case ptst ()
((pred eta)
(and (identifier?
(syntax pred))
;free-identifier=?
(stx-equal?
(syntax eta)
(syntax exp-sym)))
(syntax pred))
(whatever
#`(lambda (exp-sym)
#,ptst)))))
(assm #`(andmap #,tst
#,(subst-bindings ae let-bound))
(kf sf bv)
(ks sf bv)))))
(id
(and (identifier? (syntax id))
(stx-equal? (syntax id)
(car bound)))
(next-outer (syntax id) ae sf bv let-bound kf ks))
(the-pat
(let ((binding-list-names
(map (lambda (x)
(datum->syntax-object
(quote-syntax here)
(symbol-append
(gensym (syntax-object->datum x))
'-bindings)))
bound))
(loop-name (gensym 'loop))
(exp-name (gensym 'exp)))
#`(let #,loop-name
((#,exp-name #,(subst-bindings ae let-bound))
#,@(map
(lambda (x)
#`(#,x '()))
binding-list-names))
(if (null? #,exp-name)
#,(ks sf
(append
(map cons
bound
(map
(lambda (x)
#`(reverse #,x))
binding-list-names))
bv))
#,(next-outer #'the-pat
#`(car #,exp-name)
sf
bv ;; we always start
;; over with the old
;; bindings
let-bound
kf
(lambda (sf bv)
#`(#,loop-name
(cdr #,exp-name)
#,@(map
(lambda
(b-var
bindings-var)
#`(cons
#,(get-bind-val
b-var
bv)
#,bindings-var))
bound binding-list-names))))))))))))))
(case k
((0) (ksucc sf bv))
((1) (emit (lambda (exp) #`(pair? #,exp))
ae
let-bound
sf bv kf ksucc))
(else (emit (lambda (exp) #`(>= (length #,exp) #,k))
ae
let-bound
sf bv kf ksucc)))))
;;!(function handle-inner-ddk-list
;; (form (handle-inner-ddk-list ae kf ks pat
;; dot-dot-k pat-rest
;; let-bound)
;; ->
;; ((list list) -> syntax))
;; (contract (syntax
;; ((list list) -> syntax)
;; ((list list) -> syntax)
;; syntax
;; syntax
;; syntax
;; list)
;; ->
;; ((list list) -> syntax)))
;; This returns a function which generates the code for a list
;; pattern that contains with a ddk that occurs before the end of
;; the list. This code is extremely similar to the code in
;; handle-end-ddk-list but there are enough differences to warrant
;; having a separate method for readability.
;; Args:
;; ae - the expression being matched
;; kf - a failure function
;; ks - a success function
;; pat - the pattern that preceeds the ddk
;; dot-dot-k - the ddk pattern
;; pat-rest - the rest of the list pattern that occurs after the ddk
;; let-bound - a list of let bindings
(define ((handle-inner-ddk-list ae kf ks pat dot-dot-k pat-rest let-bound) sf bv)
(let* ((k (stx-dot-dot-k? dot-dot-k)))
(let ((bound (getbindings pat)))
(if (syntax? bound)
(kf sf bv)
(syntax-case pat (_)
(_
(stx-null? pat-rest)
(ks sf bv))
(the-pat
(null? bound)
(with-syntax ((exp-sym (syntax exp-sym)))
(let* ((ptst (next-outer
pat
(syntax exp-sym)
sf
bv
let-bound
(lambda (sf bv) (syntax #f))
(lambda (sf bv) (syntax #t))))
(tst (syntax-case ptst ()
((pred eta)
(and (identifier?
(syntax pred))
;free-identifier=?
(stx-equal?
(syntax eta)
(syntax exp-sym)))
(syntax pred))
(whatever
#`(lambda (exp-sym) #,ptst))))
(loop-name (gensym 'ddnnl))
(exp-name (gensym 'exp))
(count-name (gensym 'count)))
(quasisyntax/loc
(syntax the-pat)
(let #,loop-name ((#,exp-name
#,(subst-bindings ae let-bound))
(#,count-name 0))
(if (and (not (null? #,exp-name))
;; added for improper ddk
(pair? #,exp-name)
(#,tst (car #,exp-name)))
(#,loop-name (cdr #,exp-name)
(add1 #,count-name))
;; testing the count is not neccessary
;; if the count is zero
#,(let ((succ (next-outer
pat-rest
(quasisyntax/loc
(syntax the-pat) #,exp-name)
sf
bv
let-bound
kf
ks)))
(if (zero? k)
succ
(quasisyntax/loc
(syntax the-pat)
(if (>= #,count-name #,k)
#,succ
#,(kf sf bv)))))))))))
(the-pat
(let* ((binding-list-names
(map (lambda (x)
(datum->syntax-object
(quote-syntax here)
(symbol-append
(gensym (syntax-object->datum x))
'-bindings)))
bound))
(loop-name (quasisyntax/loc
(syntax the-pat)
#,(gensym 'loop)))
(exp-name (quasisyntax/loc
(syntax the-pat)
#,(gensym 'exp)))
(fail-name (quasisyntax/loc
(syntax the-pat)
#,(gensym 'fail)))
(count-name (quasisyntax/loc
(syntax the-pat)
#,(gensym 'count)))
(new-bv (append
(map cons
bound
(map
(lambda (x) #`(reverse #,x))
binding-list-names)) bv)))
(quasisyntax/loc
(syntax the-pat)
(let #,loop-name
((#,exp-name #,(subst-bindings ae let-bound))
(#,count-name 0)
#,@(map
(lambda (x) (quasisyntax/loc
(syntax the-pat)
(#,x '())))
binding-list-names))
(let ((#,fail-name
(lambda ()
#,(let ((succ (next-outer
pat-rest
(quasisyntax/loc
(syntax the-pat)
#,exp-name)
sf
new-bv
let-bound
kf
ks)))
(if (zero? k)
succ
(quasisyntax/loc
(syntax the-pat)
(if (>= #,count-name #,k)
#,succ
#,(kf sf new-bv))))))))
(if (or (null? #,exp-name)
(not (pair? #,exp-name)))
(#,fail-name)
#,(next-outer (syntax the-pat)
(quasisyntax/loc
(syntax the-pat)
(car #,exp-name))
sf
bv ;; we always start
;; over with the old
;; bindings
let-bound
(lambda (sf bv)
(quasisyntax/loc
(syntax the-pat)
(#,fail-name)))
(lambda (sf bv)
#`(#,loop-name
(cdr #,exp-name)
(add1 #,count-name)
#,@(map
(lambda
(b-var
bindings-var)
#`(cons
#,(get-bind-val
b-var
bv)
#,bindings-var))
bound
binding-list-names)))))))))))))))
;;!(function handle-ddk-vector
;; (form (handle-ddk-vector ae kf ks let-bound)
;; ->
;; ((list list) -> syntax))
;; (contract (syntax
;; ((list list) -> syntax)
;; ((list list) -> syntax)
;; list)
;; ->
;; ((list list) -> syntax)))
;; This returns a function which generates the code for a vector
;; pattern that contains a ddk that occurs at the end of the
;; vector.
;; Args:
;; ae - the expression being matched
;; kf - a failure function
;; ks - a success function
;; pt - the whole vector pattern
;; let-bound - a list of let bindings
(define (handle-ddk-vector ae kf ks pt let-bound)
(let* ((vec-stx (syntax-e pt))
(vlen (- (vector-length vec-stx) 2)) ;; length minus
;; the pat ...
(k (stx-dot-dot-k? (vector-ref vec-stx (add1 vlen))))
(minlen (+ vlen k))
;; get the bindings for the second to last element:
;; 'pat' in pat ...
(bound (getbindings (vector-ref vec-stx vlen)))
(exp-name (gensym 'exnm)))
(lambda (sf bv)
(if (syntax? bound)
(kf sf bv)
(quasisyntax/loc
pt
(let ((#,exp-name #,(subst-bindings ae let-bound)))
#,(assm #`(>= (vector-length #,exp-name) #,minlen)
(kf sf bv)
((let vloop ((n 0))
(lambda (sf bv)
(cond
((not (= n vlen))
(next-outer
(vector-ref vec-stx n)
#`(vector-ref #,exp-name #,n)
sf
bv
let-bound
kf
(vloop (+ 1 n))))
((eq? (syntax-object->datum
(vector-ref vec-stx vlen))
'_)
(ks sf bv))
(else
(let* ((binding-list-names
(map (lambda (x)
(datum->syntax-object
(quote-syntax here)
(symbol-append
(gensym (syntax-object->datum x))
'-bindings)))
bound))
(vloop-name (gensym 'vloop))
(index-name (gensym 'index)))
#`(let #,vloop-name
((#,index-name (- (vector-length #,exp-name) 1))
#,@(map (lambda (x) #`(#,x '()))
binding-list-names))
(if (> #,vlen #,index-name)
#,(ks sf
(append (map cons bound
binding-list-names)
bv))
#,(next-outer
(vector-ref vec-stx n)
#`(vector-ref #,exp-name #,index-name)
sf
bv ;; we alway start over
;; with the old bindings
let-bound
kf
(lambda (sf bv)
#`(#,vloop-name
(- #,index-name 1)
#,@(map
(lambda (b-var
bindings-var)
#`(cons
#,(get-bind-val
b-var
bv)
#,bindings-var))
bound
binding-list-names)))))))))))
sf
bv))))))))
;;!(function handle-ddk-vector-inner
;; (form (handle-ddk-vector-inner ae kf ks pt let-bound)
;; ->
;; ((list list) -> syntax))
;; (contract (syntax
;; ((list list) -> syntax)
;; ((list list) -> syntax)
;; syntax
;; list)
;; ->
;; ((list list) -> syntax)))
;; This returns a function which generates the code for a vector
;; pattern that contains a ddk that occurs before another pattern
;; in the list.
;; Args:
;; ae - the expression being matched
;; kf - a failure function
;; ks - a success function
;; pt - the whole vector pattern
;; let-bound - a list of let bindings
(define (handle-ddk-vector-inner ae kf ks pt let-bound)
(let* ((vec-stx (syntax-e pt))
;; vlen as an index points at the pattern before the ddk
(vlen (- (vector-length vec-stx) 2)) ;; length minus
;; the pat ...
(vec-len (vector-length vec-stx))
(total-k (ddk-in-vec? vec-stx pt))
;; (k (stx-dot-dot-k? (vector-ref vec-stx (add1 vlen))))
(minlen (+ vec-len total-k))
(length-of-vector-name (gensym 'lv))
(exp-name (gensym 'exnm)))
;; get the bindings for the second to last element:
;; 'pat' in pat ...
;;(bound (getbindings (vector-ref vec-stx vlen))))
;; we have to look at the first pattern and see if a ddk follows it
;; if so handle that case else handle the pattern
(lambda (sf bv)
;; minlen here could be the lentgh plus the k's - 1 for each ddk
#`(let ((#,exp-name #,(subst-bindings ae let-bound)))
(let ((#,length-of-vector-name (vector-length #,exp-name)))
#,(assm #`(>= #,length-of-vector-name #,minlen)
(kf sf bv)
(let ((current-index-name (gensym 'curr-ind)))
#`(let ((#,current-index-name 0))
#,((let vloop ((n 0)
(count-offset-name-passover
current-index-name))
(lambda (sf bv)
(cond
((= n vec-len) ;; at the end of the patterns
(quasisyntax/loc
pt
(if (>= #,count-offset-name-passover
#,length-of-vector-name)
#,(ks sf bv)
#,(kf sf bv))))
((stx-dot-dot-k? (vector-ref vec-stx n))
;;this could be it
(match:syntax-err
pt
"should not get here"))
;; if the next one is not a ddk do a normal pattern match
;; on element
((or (= n (sub1 vec-len))
(not (stx-dot-dot-k? (vector-ref vec-stx
(add1 n)))))
(quasisyntax/loc
pt
(if (= #,count-offset-name-passover
#,length-of-vector-name)
#,(kf sf bv)
#,(next-outer
(vector-ref vec-stx n) ;this could be it
#`(vector-ref #,exp-name #,count-offset-name-passover)
'() ;we don't want these tests to take part in future
; elimination or to be eliminated
bv
let-bound
kf
(lambda (bsf bv)
;(set! current-index-name #`(add1 #,current-index-name))
(let ((cindnm (gensym 'cindnm)))
#`(let ((#,cindnm (add1 #,count-offset-name-passover)))
#,((vloop (+ 1 n) cindnm) sf bv))))))))
((and (eq? (syntax-object->datum
(vector-ref vec-stx n)) ;this could be it
'_)
(>= (- vec-len n 1)
(stx-dot-dot-k? (vector-ref vec-stx (add1 n)))))
(ks sf bv))
(else ;; we now know that the next pattern is a ddk
(let ((bound (getbindings (vector-ref vec-stx n))))
(if (syntax? bound)
(kf sf bv)
(let* ((k (stx-dot-dot-k? (vector-ref vec-stx (add1 n))))
(binding-list-names
(map (lambda (x)
(datum->syntax-object
(quote-syntax here)
(symbol-append
(gensym (syntax-object->datum x))
'-bindings)))
bound))
(vloop-name (gensym 'vloop))
(count-name (gensym 'count))
(index-name (gensym 'index)))
#`(let #,vloop-name
((#,count-name #,count-offset-name-passover)
#,@(map (lambda (x) #`(#,x '()))
binding-list-names))
#,(let ((fail-name (gensym 'fail))
(count-offset-name (gensym 'count-offset))
(index-name (gensym 'index))
)
#`(let ((#,fail-name
(lambda (#,count-offset-name #,index-name)
#,(let ((body ((vloop (+ n 2) index-name) sf
(append (map (lambda (b bln)
(cons b
(quasisyntax/loc
pt
(reverse #,bln))))
bound
binding-list-names)
bv)
)))
(if (> k 0)
(quasisyntax/loc
pt
(if (>= #,count-offset-name #,k)
#,body
#,(kf sf bv)))
body)))))
(if (= #,length-of-vector-name #,count-name)
(#,fail-name
(- #,count-name #,count-offset-name-passover)
#,count-name)
#,(next-outer
(vector-ref vec-stx n) ;this could be it
#`(vector-ref #,exp-name #,count-name)
'() ;sf
bv ;; we alway start over
;; with the old bindings
let-bound
(lambda (sf bv)
#`(#,fail-name
(- #,count-name
#,count-offset-name-passover)
#,count-name))
(lambda (sf bv)
#`(let ((arglist
(list
#,@(map
(lambda (b-var
bindings-var)
#`(cons
#,(get-bind-val
b-var
bv)
#,bindings-var))
bound
binding-list-names))))
(apply
#,vloop-name
(add1 #,count-name)
arglist)))))))))))))))
sf
bv)))))))))
;; END DDK-HANDLERS.SCM
;; some convenient syntax for make-reg-test and make-shape-test
(define make-test-gen
(case-lambda
[(constructor test ae emitter) (make-test-gen constructor test ae emitter ae)]
[(constructor test ae emitter ae2)
(constructor test ae
(lambda (ks kf let-bound)
(lambda (sf bv)
(emit emitter ae2 let-bound sf bv kf ks))))]))
(define (reg-test . args) (apply make-test-gen make-reg-test args))
(define (shape-test . args) (apply make-test-gen make-shape-test args))
;; expand the regexp-matcher into an (and) with string?
(define (regexp-matcher ae stx pred)
(render-test-list #`(and (? string?) #,pred) ae stx))
;; produce a matcher for the empty list
(define (emit-null ae)
(list (reg-test `(null? ,(syntax-object->datum ae))
ae (lambda (exp) #`(null? #,exp)))))
;; generic helper for producing set/get matchers
(define-syntax (set/get-matcher stx)
(syntax-case stx (set! get!)
[(_ set!/get! ae p arg set/get-func) #`(set/get-matcher set!/get! ae p let-bound arg set/get-func)]
[(_ set!/get! ae p let-bound arg set/get-func)
(with-syntax ([sym (syntax-case #'set!/get! (set! get!) ['set! #''set!-pat] ['get! #''get!-pat])])
#`(syntax-case arg ()
[(ident)
(identifier? (syntax ident))
(list (make-act
sym
ae
(lambda (ks kf let-bound)
(lambda (sf bv)
(ks sf (cons (cons #'ident
set/get-func)
bv))))))]
[() (match:syntax-err p
(format "there should be an identifier after ~a in pattern" set!/get!))]
[(_) (match:syntax-err p
(format " ~a followed by something that is not an identifier" set!/get!))]
[(_ (... ...))
(match:syntax-err p
(format "there should be only one identifier after ~a in pattern" set!/get!))]
[_ (match:syntax-err p
(format "invalid ~a pattern syntax" set!/get!))]))]))
;;!(function render-test-list
;; (form (render-test-list p ae stx) -> test-list)
;; (contract (syntax syntax syntax) -> list))
;; This is the most important function of the entire compiler.
;; This is where the functionality of each pattern is implemented.
;; This function maps out how each pattern is compiled. While it
;; only returns a list of tests, the comp field of those tests
;; contains a function which inturn knows enough to compile the
;; pattern.
;; <p>This is implemented in what Wright terms as mock-continuation-passing
;; style. The functions that create the syntax for a match success and failure
;; are passed forward
;; but they are always called in emit. This is extremely effective for
;; handling the different structures that are matched. This way we can
;; specify ahead of time how the rest of the elements of a list or vector
;; should be handled. Otherwise we would have to pass more information
;; forward in the argument list of next and then test for it later and
;; then take the appropriate action. To understand this better take a
;; look at how proper and improper lists are handled.
(define/opt (render-test-list p ae [stx #'here])
(syntax-case*
p
(_ list quote quasiquote vector box ? app and or not struct set! var
list-rest get! ... ___ unquote unquote-splicing cons
list-no-order hash-table regexp pregexp cons) stx-equal?
;; this is how we extend match
[(expander args ...)
(and (identifier? #'expander)
(match-expander? (syntax-local-value #'expander (lambda () #f))))
(let ([transformer (match-expander-plt-match-xform (syntax-local-value #'expander))])
(if (not transformer)
(match:syntax-err #'expander
"This expander only works with standard match.")
(render-test-list (transformer #'(expander args ...))
ae stx)))]
;; underscore is reserved to match nothing
(_ '()) ;(ks sf bv let-bound))
;; plain identifiers expand into (var) patterns
(pt
(and (pattern-var? (syntax pt))
(not (stx-dot-dot-k? (syntax pt))))
(render-test-list #'(var pt) ae stx))
;; for variable patterns, we do bindings, and check if we've seen this variable before
((var pt)
(identifier? (syntax pt))
(list (make-act `bind-var-pat
ae
(lambda (ks kf let-bound)
(lambda (sf bv)
(cond [(ormap (lambda (x)
(if (stx-equal? #'pt (car x))
(cdr x) #f)) bv)
=> (lambda (bound-exp)
(emit (lambda (exp)
#`((match-equality-test) #,exp #,(subst-bindings bound-exp let-bound)))
ae
let-bound
sf bv kf ks))]
[else
(ks sf (cons (cons (syntax pt) ae) bv))]))))))
;; Recognize the empty list
((list) (emit-null ae))
('() (emit-null ae))
;; This recognizes constants such strings
[pt
(let ([pt (syntax-object->datum #'pt)])
(or (string? pt)
(boolean? pt)
(char? pt)
(number? pt)))
(list
(reg-test
`(equal? ,(syntax-object->datum ae)
,(syntax-object->datum (syntax pt)))
ae (lambda (exp) #`(equal? #,exp pt))))]
;(pt
; (stx-? regexp? (syntax pt))
; (render-test-list (syntax/loc p (regex pt)) ae stx))
;; match a quoted datum
;; this is very similar to the previous pattern, except for the second argument to equal?
[(quote _)
(list
(reg-test
`(equal? ,(syntax-object->datum ae)
,(syntax-object->datum p))
ae (lambda (exp) #`(equal? #,exp #,p))))]
;; I do not understand this, or why it is ever matched, but removing it causes test failures
('item
(list
(reg-test
`(equal? ,(syntax-object->datum ae)
,(syntax-object->datum p))
ae (lambda (exp) #`(equal? #,exp #,p)))))
(`quasi-pat
(render-test-list (parse-quasi #'quasi-pat) ae stx))
;; check for predicate patterns
;; could we check to see if a predicate is a procedure here?
((? pred?)
(list (reg-test
`(,(syntax-object->datum #'pred?)
,(syntax-object->datum ae))
ae (lambda (exp) #`(pred? #,exp)))))
;; predicate patterns with binders are redundant with and patterns
((? pred? pats ...)
(render-test-list #'(and (? pred?) pats ...) ae stx))
;; syntax checking
((? anything ...)
(match:syntax-err
p
(if (zero? (length (syntax-e #'(anything ...))))
"a predicate pattern must have a predicate following the ?"
"syntax error in predicate pattern")))
((regexp reg-exp)
(regexp-matcher ae stx #'(? (lambda (x) (regexp-match reg-exp x)))))
((pregexp reg-exp)
(regexp-matcher ae stx #'(? (lambda (x) (pregexp-match-with-error reg-exp x)))))
((regexp reg-exp pat)
(regexp-matcher ae stx #'(app (lambda (x) (regexp-match reg-exp x)) pat)))
((pregexp reg-exp pat)
(regexp-matcher ae stx #'(app (lambda (x) (pregexp-match-with-error reg-exp x)) pat)))
;; app patterns just apply their operation. I'm not sure why they exist.
((app op pat)
(render-test-list #'pat #`(op #,ae) stx))
;; syntax checking
((app . op)
(match:syntax-err
p
(if (zero? (length (syntax-e #'op)))
"an operation pattern must have a procedure following the app"
"there should be one pattern following the operator")))
((and . pats)
(let loop
((p #'pats))
(syntax-case p ()
;; empty and always succeeds
[() '()] ;(ks seensofar boundvars let-bound))
[(pat . rest)
(append (render-test-list #'pat ae stx)
(loop #'rest))])))
((or . pats)
(list (make-act
'or-pat ;`(or-pat ,(syntax-object->datum ae))
ae
(lambda (ks kf let-bound)
(lambda (sf bv)
(or-gen ae (syntax-e #'pats)
sf bv ks kf let-bound))))))
((not pat)
(list (make-act
'not-pat ;`(not-pat ,(syntax-object->datum ae))
ae
(lambda (ks kf let-bound)
(lambda (sf bv)
;; swap success and fail
(next-outer #'pat ae sf bv let-bound ks kf))))))
;; (cons a b) == (list-rest a b)
[(cons p1 p2) (render-test-list #'(list-rest p1 p2) ae stx)]
;; could try to catch syntax local value error and rethrow syntax error
((list-no-order pats ...)
(if (stx-null? (syntax (pats ...)))
(render-test-list #'(list) ae stx)
(let* ((pat-list (syntax->list (syntax (pats ...))))
(ddk-list (ddk-in-list? pat-list))
(ddk (ddk-only-at-end-of-list? pat-list)))
(if (or (not ddk-list)
(and ddk-list ddk))
(let* ((bound (getbindings (append-if-necc 'list
(syntax (pats ...)))))
(bind-map
(map (lambda (x)
(cons x #`#,(gensym (syntax-object->datum x))))
bound)))
(list
(shape-test
`(list? ,(syntax-object->datum ae))
ae (lambda (exp) #`(list? #,exp)))
(make-act
'list-no-order
ae
(lambda (ks kf let-bound)
(lambda (sf bv)
(let ((last-test
(if ddk
(let ((pl (cdr (reverse pat-list))))
(begin
(set! pat-list (reverse (cdr pl)))
(create-test-func (car pl)
sf
let-bound
bind-map
#t)))
#f)))
#`(let #,(map (lambda (b)
#`(#,(cdr b) '()))
bind-map)
(let ((last-test #,last-test)
(test-list
(list
#,@(map (lambda (p)
(create-test-func
p
sf
let-bound
bind-map
#f))
pat-list))))
(if (match:test-no-order test-list
#,ae
last-test
#,ddk)
#,(ks sf (append bind-map bv))
#,(kf sf bv))))))))))
(match:syntax-err
p
(string-append "dot dot k can only appear at "
"the end of unordered match patterns"))))))
((hash-table pats ...)
;; must check the structure
(proper-hash-table-pattern? (syntax->list (syntax (pats ...))))
(list
(shape-test
`(hash-table? ,(syntax-object->datum ae))
ae (lambda (exp) #`(hash-table? #,exp)))
(let ((mod-pat
(lambda (pat)
(syntax-case pat ()
((key value) (syntax (list key value)))
(ddk
(stx-dot-dot-k? (syntax ddk))
(syntax ddk))
(id
(and (pattern-var? (syntax id))
(not (stx-dot-dot-k? (syntax id))))
(syntax id))
(p (match:syntax-err
(syntax/loc stx p)
"poorly formed hash-table pattern"))))))
(make-act
'hash-table-pat
ae
(lambda (ks kf let-bound)
(lambda (sf bv)
(let ((hash-name (gensym 'hash)))
#`(let ((#,hash-name
(hash-table-map #,(subst-bindings ae
let-bound)
(lambda (k v) (list k v)))))
#,(next-outer #`(list-no-order #,@(map mod-pat (syntax->list (syntax (pats ...)))))
#`#,hash-name
sf
;; these tests have to be true
;;(append (list
;; '(pair? exp)
;; '(pair? (cdr exp))
;; '(null? (cdr (cdr exp))))
;; sf)
bv
let-bound
kf
ks)))))))))
((hash-table . pats)
(match:syntax-err
p
"improperly formed hash table pattern"))
((struct struct-name (fields ...))
(identifier? (syntax struct-name))
(let*-values ([(field-pats) (syntax->list (syntax (fields ...)))]
[(num-of-fields) (length field-pats)]
[(pred accessors mutators parental-chain)
(struct-pred-accessors-mutators #'struct-name)]
;; check that we have the right number of fields
[(dif) (- (length accessors) num-of-fields)])
(unless (zero? dif)
(match:syntax-err
p
(string-append
(if (> dif 0) "not enough " "too many ")
"fields for structure in pattern")))
(cons
(shape-test
`(struct-pred ,(syntax-object->datum pred)
,(map syntax-object->datum parental-chain)
,(syntax-object->datum ae))
ae (lambda (exp) #`(struct-pred #,pred #,parental-chain #,exp)))
(map-append
(lambda (cur-pat cur-mutator cur-accessor)
(syntax-case cur-pat (set! get!)
[(set! . rest)
(unless cur-mutator (match:syntax-err cur-pat "Cannot use set! pattern with immutable fields"))
(set/get-matcher 'set! ae p #'rest
#`(lambda (y)
(#,cur-mutator #,ae y)))]
[(get! . rest)
(set/get-matcher 'get! ae p #'rest
#`(lambda ()
(#,cur-accessor #,ae)))]
[_ (render-test-list
cur-pat
(quasisyntax/loc stx (#,cur-accessor #,ae))
stx)]))
field-pats mutators accessors))))
;; syntax checking
((struct ident ...)
(match:syntax-err
p
(if (zero? (length (syntax-e (syntax (ident ...)))))
(format "~a~n~a~n~a"
"a structure pattern must have the name "
"of a defined structure followed by a list of patterns "
"to match each field of that structure")
"syntax error in structure pattern")))
;; use a helper macro to match set/get patterns.
;; we give it the whole rest so that it can do error-checking and reporting
[(set! . rest)
(set/get-matcher 'set! ae p let-bound (syntax rest)
(setter ae p let-bound))]
[(get! . rest)
(set/get-matcher 'get! ae p let-bound (syntax rest)
(getter ae p let-bound))]
;; list pattern with ooo or ook
((list pat dot-dot-k pat-rest ...)
(and (not (or (memq (syntax-e (syntax pat))
'(unquote unquote-splicing ... ___))
(stx-dot-dot-k? (syntax pat))))
(stx-dot-dot-k? (syntax dot-dot-k)))
(list
(shape-test
`(list? ,(syntax-object->datum ae))
ae (lambda (exp) #`(list? #,exp)))
(make-act
'list-ddk-pat
ae
(lambda (ks kf let-bound)
(if (stx-null? (syntax (pat-rest ...)))
(handle-end-ddk-list ae kf ks
(syntax pat)
(syntax dot-dot-k)
let-bound)
(handle-inner-ddk-list ae kf ks
(syntax pat)
(syntax dot-dot-k)
(append-if-necc 'list
(syntax (pat-rest ...)))
let-bound))))))
;; list-rest pattern with a ooo or ook pattern
((list-rest pat dot-dot-k pat-rest ...)
(and (not (or (memq (syntax-e (syntax pat))
'(unquote unquote-splicing ... ___))
(stx-dot-dot-k? (syntax pat))
(stx-null? (syntax (pat-rest ...)))))
(stx-dot-dot-k? (syntax dot-dot-k)))
(list
(shape-test
`(pair? ,(syntax-object->datum ae))
ae (lambda (exp) #`(pair? #,exp)))
(make-act
'list-ddk-pat
ae
(lambda (ks kf let-bound)
(handle-inner-ddk-list
ae kf ks
(syntax pat)
(syntax dot-dot-k)
(if (= 1 (length
(syntax->list (syntax (pat-rest ...)))))
(stx-car (syntax (pat-rest ...)))
(append-if-necc 'list-rest
(syntax (pat-rest ...))))
let-bound)))))
;; list-rest pattern for improper lists
;; handle proper and improper lists
((list-rest car-pat cdr-pat) ;pattern ;(pat1 pats ...)
(not (or (memq (syntax-e (syntax car-pat))
'(unquote unquote-splicing))
(stx-dot-dot-k? (syntax car-pat))))
(cons
(shape-test
`(pair? ,(syntax-object->datum ae))
ae (lambda (exp) #`(pair? #,exp)))
(append
(render-test-list (syntax car-pat)
(quasisyntax/loc (syntax car-pat) (car #,ae))
stx) ;(add-a e)
(render-test-list
(syntax cdr-pat)
#`(cdr #,ae)
stx))))
;; list-rest pattern
((list-rest car-pat cdr-pat ...) ;pattern ;(pat1 pats ...)
(not (or (memq (syntax-e (syntax car-pat))
'(unquote unquote-splicing))
(stx-dot-dot-k? (syntax car-pat))))
(cons
(shape-test
`(pair? ,(syntax-object->datum ae))
ae (lambda (exp) #`(pair? #,exp)))
(append
(render-test-list (syntax car-pat)
#`(car #,ae)
stx) ;(add-a e)
(render-test-list
(append-if-necc 'list-rest (syntax (cdr-pat ...)))
#`(cdr #,ae)
stx))))
;; general list pattern
((list car-pat cdr-pat ...) ;pattern ;(pat1 pats ...)
(not (or (memq (syntax-e (syntax car-pat))
'(unquote unquote-splicing))
(stx-dot-dot-k? (syntax car-pat))))
(cons
(shape-test
`(pair? ,(syntax-object->datum ae))
ae (lambda (exp) #`(pair? #,exp)))
(append
(render-test-list (syntax car-pat)
#`(car #,ae)
stx) ;(add-a e)
(if (stx-null? (syntax (cdr-pat ...)))
(list
(shape-test
`(null? (cdr ,(syntax-object->datum ae)))
ae (lambda (exp) #`(null? #,exp)) #`(cdr #,ae)))
(render-test-list
(append-if-necc 'list (syntax (cdr-pat ...)))
#`(cdr #,ae)
stx)))))
;; vector pattern with ooo or ook at end
((vector pats ...)
(ddk-only-at-end-of-list? (syntax-e (syntax (pats ...))))
(list
(shape-test
`(vector? ,(syntax-object->datum ae))
ae (lambda (exp) #`(vector? #,exp)))
(make-act
'vec-ddk-pat
ae
(lambda (ks kf let-bound)
(handle-ddk-vector ae kf ks
#'#(pats ...)
let-bound)))))
;; vector pattern with ooo or ook, but not at end
((vector pats ...)
(let* ((temp (syntax-e (syntax (pats ...))))
(len (length temp)))
(and (>= len 2)
(ddk-in-list? temp)))
;; make this contains ddk with no ddks consecutive
;;(stx-dot-dot-k? (vector-ref temp (sub1 len))))))
(list
(shape-test
`(vector? ,(syntax-object->datum ae))
ae (lambda (exp) #`(vector? #,exp)))
;; we have to look at the first pattern and see if a ddk follows it
;; if so handle that case else handle the pattern
(make-act
'vec-ddk-pat
ae
(lambda (ks kf let-bound)
(handle-ddk-vector-inner ae kf ks
#'#(pats ...)
let-bound)))))
;; plain old vector pattern
((vector pats ...)
(let* ((syntax-vec (list->vector (syntax->list (syntax (pats ...)))))
(vlen (vector-length syntax-vec)))
(list*
(shape-test
`(vector? ,(syntax-object->datum ae)) ae
(lambda (exp) #`(vector? #,exp)))
(shape-test
`(equal? (vector-length ,(syntax-object->datum ae)) ,vlen)
ae (lambda (exp) #`(equal? (vector-length #,exp) #,vlen)))
(let vloop ((n 0))
(if (= n vlen)
'()
(append
(render-test-list
(vector-ref syntax-vec n)
#`(vector-ref #,ae #,n)
stx)
(vloop (+ 1 n))))))))
((box pat)
(cons
(shape-test
`(box? ,(syntax-object->datum ae))
ae (lambda (exp) #`(box? #,exp)))
(render-test-list
#'pat #`(unbox #,ae) stx)))
;; This pattern wasn't a valid form.
(got-too-far
(match:syntax-err
#'got-too-far
"syntax error in pattern"))))
)