racket/collects/tests/stxparse/test.rkt

486 lines
13 KiB
Racket

#lang racket
(require rackunit
syntax/parse
syntax/parse/debug
"setup.rkt"
(for-syntax syntax/parse))
;; Main syntax class and pattern tests
;; ========
(define-syntax-class one
(pattern (a)))
(define-syntax-class two
(pattern (a b)))
;; ========
;; -- S patterns
;; name patterns
(tok 1 a
(and (bound (a 0)) (s= a 1)))
(tok (a b c) a
(and (bound (a 0)) (s= a '(a b c))))
(tok 1 a
'ok
#:pre [] #:post [1])
;; wildcard patterns
(tok 1 _)
(tok (a b c) _)
(tok (a b) (_ _)) ;; multiple _'s allowed
;; sc tests -> lib tests
(tok (1) x:one
(and (bound (x 0) (x.a 0)) (s= x '(1)) (s= x.a 1)))
(tok (1 2) x:two
(and (bound (x 0) (x.a 0) (x.b 0)) (s= x '(1 2)) (s= x.a 1) (s= x.b 2)))
(tok (1 2) x:two
'ok
#:pre [x:one] #:post [])
(tok (1) x:one
'ok
#:pre [()] #:post [x:two])
;; check if wildcard, no attr bound
(terx (1) _:two "expected two")
;(terx (1 2) _:one "expected one")
(terx (1 (2 3)) (_:one _:two) "expected one")
(terx ((1) 2) (_:one _:two) "expected two")
;; datum patterns
(tok 1 1
'ok)
(tok 1 _
#t
#:pre [2] #:post [])
(tok "here" "here"
'ok
#:pre ["there"] #:post [])
(tok #f #f
'ok
#:pre [#t 0] #:post [_])
(terx 1 2 "literal 2")
(terx (1 2) 1 "literal 1")
(terx (1 2) (1 1) "literal 1")
;; literal patterns
(test-case "literals: +"
(syntax-parse #'+ #:literals (+ -)
[+ (void)]))
(test-case "literals: - +"
(syntax-parse #'+ #:literals (+ -)
[- (error 'wrong)]
[+ (void)]))
(test-case "literals: + _"
(syntax-parse #'+ #:literals (+ -)
[+ (void)]
[_ (error 'wrong)]))
(test-case "datum literals"
(syntax-parse #'one #:datum-literals (one)
[one (void)]))
(test-case "datum literals (not id=?)"
(let ([one 1])
(syntax-parse (let ([one 2]) #'one) #:datum-literals (one)
[one (void)])))
;; compound patterns
(tok (a b c) (x y z)
(and (bound (x 0) (y 0) (z 0)) (s= x 'a) (s= y 'b))
#:pre [(x y)] #:post [])
(tok (a . b) (x . y)
(and (bound (x 0) (y 0)) (s= x 'a) (s= y 'b))
#:pre [(x y)] #:post [])
(tok #(a b c) #(x y z)
(and (bound (x 0) (y 0) (z 0)) (s= x 'a) (s= y 'b)))
(tok #(a b c) #(x y z)
(and (bound (x 0) (y 0) (z 0)) (s= x 'a) (s= y 'b)))
(tok #(1 2 3 4 5) #(a b ~rest c)
(s= c '(3 4 5)))
(tok #&1 #&x
(and (bound (x 0)) (s= x 1)))
(tok #s(foo 1 2) #s(foo a b)
(and (s= a 1) (s= b 2)))
(tok #s(foo 1 2 3 4 5) #s(foo a b ~rest c)
(s= c '(3 4 5)))
;; head patterns
;; See H-patterns
;; dots patterns
;; See EH-patterns
;; and patterns
(tok 1 (~and a 1)
(and (bound (a 0)) (s= a 1)))
(tok 1 (~and 1 1)
'ok
#:pre [(~and 1 2)] #:post [(~and 2 2)])
(tok (1 2 3) (~and w (x y z))
(and (bound (w 0) (x 0) (y 0) (z 0))
(s= w '(1 2 3)) (s= x 1)))
(tok (1 2 3) (~and (1 _ _) (_ 2 _) (_ _ 3))
'ok)
(tok (1 2 3) (~and (x _ _) (_ y _) (_ _ z))
(and (bound (x 0) (y 0) (z 0))))
;; and scoping
(tok 1 (~and a (~fail #:unless (equal? (syntax->datum #'a) 1))))
;; or patterns
(tok 1 (~or 1 2 3)
'ok)
(tok 3 (~or 1 2 3)
'ok)
(tok (1) (~or (a) (a b) (a b c))
(and (bound (a 0 #t) (b 0 #f) (c 0 #f)) (s= a 1) (a= b #f) (a= c #f)))
(tok (1 2 3) (~or (a) (a b) (a b c))
(and (bound (a 0 #t) (b 0 #f) (c 0 #f)) (s= a 1) (s= b 2) (s= c 3)))
(tok 1 (~or 5 _)
'ok)
(tok #t (~or #t #f)
'ok)
(tok #t (~or (~and #t x) (~and #f x))
(and (bound (x 0 #t))))
;; describe
(tok ((1 2) 3) ((~describe "one-two" (1 2)) 3))
(terx ((1 3) 3) ((~describe #:opaque "one-two" (1 2)) 3)
"one-two")
(terx ((1 3) 3) ((~describe "one-two" (1 2)) 3)
"2")
(terx (1 3) ((~describe "one-two" (1 2)) 3)
"one-two")
;; epsilon-name patterns
(tok (1) :one
(and (bound (a 0)) (s= a 1)))
(tok (1 2) :two
(and (bound (a 0) (b 0)) (s= a 1) (s= b 2)))
(tok (1 2) (~and x:two :two)
(and (bound (x 0) (x.a 0) (a 0)) (s= x '(1 2)) (s= x.a 1) (s= a 1)))
;; delimit-cut
(tok (1 (2 3)) (1 (~or (~delimit-cut (2 ~! 4)) (2 3))))
(tok (1 2 3) (1 2 3)
'ok
#:pre [(~delimit-cut (1 2 ~! 4))] #:post [])
(define-syntax-class def
#:no-delimit-cut
#:literals (define-values)
(pattern (define-values ~! (x:id ...) e:expr)))
(tok (define-values (a b c) 1) d:def
'ok)
(terx (define-values (a 2) 3) (~or d:def e:expr)
#rx"expected identifier")
(terx* (define-values (a 2) 3) [d:def e:expr]
#rx"expected identifier")
;; commit
(define-syntax-class xyseq
#:commit
(pattern ((~or x y) ...)))
(tok (1 2 3 4 5 6 7 8)
(~and ((~or s.x s.y) ...)
(~fail #:unless (= (apply + (syntax->datum #'(s.x ...)))
(apply + (syntax->datum #'(s.y ...))))
"nope"))
(equal? (syntax->datum #'(s.x ...)) '(1 2 3 4 8)))
(terx (1 2 3 4 5 6 7 8)
(~and s:xyseq
(~fail #:unless (= (apply + (syntax->datum #'(s.x ...)))
(apply + (syntax->datum #'(s.y ...))))
"nope"))
#rx"nope")
(terx (1 2 3 4 5 6 7 8)
(~and (~commit ((~or s.x s.y) ...))
(~fail #:unless (= (apply + (syntax->datum #'(s.x ...)))
(apply + (syntax->datum #'(s.y ...))))
"nope"))
#rx"nope")
;; -- H patterns
;; seq
(tok (1 2 3) ((~seq 1 2) 3))
(tok (1 2 3) (1 (~seq 2) 3))
(tok (1 2 3) ((~seq) 1 2 3))
;; or
(tok (1 2 3) ((~or (~seq 1 2) 1) 3))
(tok (1 2 3) ((~or 1 (~seq 1 2)) 3))
(tok (1 2 3) ((~or (~seq 1) (~seq 1 2)) 3))
(tok (1 2 3) ((~or (~seq 1) (~seq)) 1 2 3))
(tok (1 2 3) ((~or (~seq 1) (~seq)) 1 2 3 (~or (~seq 4) (~seq))))
;; describe
(tok (1 2 3) ((~describe "one-two" (~seq 1 2)) 3))
(terx (1 3 3) ((~describe #:opaque "one-two" (~seq 1 2)) 3)
"one-two")
;; Regression (2/2/2010)
(define-splicing-syntax-class twoseq
(pattern (~seq a b)))
(tok (1 2 3 4) (x:twoseq ...))
;; -- A patterns
;; cut patterns
(terx* (1 2 3) [(1 ~! 4) (1 _:nat 3)]
"4" (not "exact nonnegative integer"))
;; cut-in-and
(terx* 1 [(~and a:nat ~! 2) b:nat]
"2")
;; bind patterns
(tok 1 (~and x (~bind [y #'x]))
(s= y '1))
(tok 1 (~or x:id (~bind [x #'default]))
(s= x 'default))
;; fail patterns
(tok (1 2 3) _
'ok
#:pre [(~fail "pass") (error 'wrong)] #:post [])
(terx 1 (~fail "wanted 2")
#rx"wanted 2")
(terx 1 (~and n:nat (~fail #:unless (even? (syntax-e #'n)) "wanted even number"))
#rx"wanted even number")
;; fail as S-pattern
(terx 1 (~fail "grr")
#rx"grr")
(tok (1 2 3) (x:nat y:nat (~parse (~or 2 3) (+ (syntax-e #'x) (syntax-e #'y))) z:nat))
(terx (1 2 3) (x:nat y:nat (~parse 4 (+ (syntax-e #'x) (syntax-e #'y))) z:nat)
"expected the literal 4")
(terx (1 2 3) (x:nat y:nat (~parse (2 4) #'(x y)))
"expected the literal 2")
;; == syntax-parse: other feature tests
(test-case "syntax-parse: #:context"
(check-exn
(lambda (exn)
(regexp-match #rx"me: expected exact-nonnegative-integer" (exn-message exn)))
(lambda ()
(syntax-parse #'(m x) #:context #'me
[(_ n:nat) 'ok])))
(void))
(test-case "syntax-parse: #:literals"
(syntax-parse #'(0 + 1 * 2)
#:literals (+ [times *])
[(a + b * c) (void)]))
;; == syntax classes: other feature tests
;; #:auto-nested-attributes
(define-syntax-class square0
(pattern (x:two y:two)))
(define-syntax-class square
#:auto-nested-attributes
(pattern (x:two y:two)))
(test-case "nested attributes omitted by default"
(check-equal? (syntax-class-attributes square0)
'((x 0) (y 0)))
(void))
(test-case "nested attributes work okay"
(check-equal? (syntax-class-attributes square)
'((x 0) (x.a 0) (x.b 0) (y 0) (y.a 0) (y.b 0)))
(void))
;; conventions
(define-syntax-class (nat> bound)
#:description (format "natural number greater than ~s" bound)
(pattern n:nat #:when (> (syntax-e #'n) bound)))
(define-conventions nat-convs
[N (nat> 0)])
(test-case "syntax-parse: #:conventions"
(syntax-parse #'(5 4)
#:conventions (nat-convs)
[(N ...) (void)]))
(test-case "syntax-parse: #:conventions fail"
(check-exn
(lambda (exn)
(check regexp-match? #rx"expected natural number greater than 0"
(exn-message exn)))
(lambda ()
(syntax-parse #'(4 0)
#:conventions (nat-convs)
[(N ...) (void)])))
(void))
;; local conventions
(define-syntax-class (nats> bound)
#:local-conventions ([N (nat> bound)])
(pattern (N ...)))
(test-case "local conventions 1"
(syntax-parse #'(1 2 3)
#:local-conventions ([ns (nats> 0)])
[ns (void)]))
(test-case "local conventions 2"
(check-exn
(lambda (exn)
(check regexp-match? #rx"expected natural number greater than 2"
(exn-message exn)))
(lambda ()
(syntax-parse #'(1 2 3)
#:local-conventions ([ns (nats> 2)])
[ns (void)])))
(void))
;; == Lib tests
;; static
(tcerr "static: correct error"
(let ()
(define-syntax zero 0)
(define-syntax (m stx)
(syntax-parse stx
[(_ x)
#:declare x (static number? "identifier bound to number")
#`(quote #,(attribute x.value))]))
(m twelve))
#rx"identifier bound to number")
(test-case "static: works"
(check-equal?
(convert-syntax-error
(let ()
(define-syntax zero 0)
(define-syntax (m stx)
(syntax-parse stx
[(_ x)
#:declare x (static number? "identifier bound to number")
#`(quote #,(attribute x.value))]))
(m zero)))
0)
(void))
;; -- test #:declare scoping
(test-case "#:declare magical scoping"
(syntax-parse #'(1 2)
[(a b)
#:declare a nat
#:declare b (nat> (syntax-e #'a))
(void)]))
(tcerr "#:declare magical scoping 2"
(syntax-parse #'(1 1)
[(a b)
#:declare a nat
#:declare b (nat> (syntax-e #'a))
(void)]))
;; ---- Regression tests
(test-case "pvar is syntax"
;; from clklein 9/21/2011
(check-true (syntax-parse #'(m 1 1 2 1 2 3)
[(_ 1 ... . after-ones:expr)
(syntax? #'after-ones)]))
(void))
(begin
;; from samth 2/4/2012
;; opaque head patterns used to propagate progress *with opaque marker* to tail
(test-case "opaque H, ok"
(check-equal? (syntax-parse #'(a b)
[((~describe #:opaque "x" (~seq x)) y:id) 'ok])
'ok))
(test-case "opaque splicing stxclass, ok"
(check-equal? (let ()
(define-splicing-syntax-class foo
#:opaque
#:description "foo"
(pattern (~seq x)))
(syntax-parse #'(a b)
[(f:foo y:id) 'ok]))
'ok))
(test-case "opaque empty H, ok"
(check-equal? (syntax-parse #'(b)
[((~describe #:opaque "x" (~seq)) y:id) 'ok])
'ok))
(test-case "opaque empty splicing stxclass, ok"
(check-equal? (let ()
(define-splicing-syntax-class foo
#:opaque
#:description "foo"
(pattern (~seq)))
(syntax-parse #'(b)
[(f:foo y:id) 'ok]))
'ok))
(tcerr "extent of opaque in H pattern"
(syntax-parse #'(a b)
[((~describe #:opaque "x" (~seq x)) y:nat) (void)])
(not #rx"expected x") ;; y:nat was incorrectly considered part of opaque region
#rx"expected exact-nonnegative-integer")
(tcerr "extent of opaque in splicing stxclass"
(let ()
(define-splicing-syntax-class foo
#:description "foo"
#:opaque
(pattern (~seq x)))
(syntax-parse #'(a b)
[(f:foo n:nat) (void)]))
(not #rx"expected foo") ;; y:nat was incorrectly considered part of opaque region
#rx"expected exact-nonnegative-integer")
(tcerr "extent of opaque in empty H pattern"
(syntax-parse #'(b)
[((~describe #:opaque "x" (~seq)) y:nat) (void)])
(not #rx"expected x") ;; y:nat was incorrectly considered part of opaque region
#rx"expected exact-nonnegative-integer")
(tcerr "extent of opaque in empty splicing stxclass"
(let ()
(define-splicing-syntax-class foo
#:description "foo"
#:opaque
(pattern (~seq)))
(syntax-parse #'(b)
[(f:foo n:nat) (void)]))
(not #rx"expected foo") ;; y:nat was incorrectly considered part of opaque region
#rx"expected exact-nonnegative-integer")
)
;; from Neil Van Dyke (7/28/2012)
(test-case "specialized predicate-ellipsis-parser"
;; test that it works on improper lists
;; ... when input is syntax
(check-eq? (syntax-parse #'(a b c . d) [(x:id ...) #t] [_ #f]) #f)
;; ... and when input is stx pair (but not syntax)
(check-eq? (syntax-parse #'(a b c . d) [(_ x:id ...) #t] [_ #f]) #f)
;; test that it works on proper lists w/ embedded stxpairs
(check-eq? (syntax-parse #'(a b . (c d)) [(x:id ...) #t] [_ #f]) #t)
(check-eq? (syntax-parse #'(a b . (c d)) [(_ x:id ...) #t] [_ #f]) #t))
;; from Eric Dobson (11/30/2012)
(terx (x y) ((~describe #:opaque "an X" x:id) n:number)
#rx"expected number"
(not #rx"expected an X"))