heist 0.1.0 → 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- data/History.txt +17 -0
- data/Manifest.txt +23 -19
- data/README.txt +84 -52
- data/lib/builtin/library.scm +208 -10
- data/lib/builtin/primitives.rb +154 -92
- data/lib/builtin/syntax.scm +22 -5
- data/lib/heist.rb +49 -17
- data/lib/parser/nodes.rb +47 -24
- data/lib/parser/ruby.rb +29 -0
- data/lib/parser/scheme.rb +455 -143
- data/lib/parser/scheme.tt +23 -5
- data/lib/repl.rb +19 -16
- data/lib/runtime/binding.rb +24 -2
- data/lib/runtime/callable/continuation.rb +23 -2
- data/lib/runtime/callable/function.rb +122 -21
- data/lib/runtime/callable/macro.rb +169 -123
- data/lib/runtime/callable/macro/expansion.rb +137 -2
- data/lib/runtime/callable/macro/matches.rb +125 -41
- data/lib/runtime/callable/macro/tree.rb +141 -0
- data/lib/runtime/callable/syntax.rb +44 -0
- data/lib/runtime/data/cons.rb +234 -0
- data/lib/runtime/data/expression.rb +15 -6
- data/lib/runtime/data/identifier.rb +19 -2
- data/lib/runtime/frame.rb +102 -35
- data/lib/runtime/runtime.rb +44 -19
- data/lib/runtime/scope.rb +145 -30
- data/lib/runtime/stack.rb +103 -1
- data/lib/runtime/stackless.rb +48 -6
- data/test/arithmetic.scm +11 -2
- data/test/continuations.scm +16 -2
- data/test/equivalence.scm +34 -0
- data/test/functional.scm +4 -0
- data/test/lists.scm +78 -0
- data/test/macro-helpers.scm +1 -0
- data/test/macros.scm +111 -24
- data/test/numbers.scm +30 -8
- data/test/test_heist.rb +67 -12
- metadata +25 -21
- data/lib/builtin/syntax.rb +0 -166
- data/lib/runtime/callable/macro/splice.rb +0 -56
- data/lib/runtime/data/list.rb +0 -36
data/lib/runtime/stackless.rb
CHANGED
@@ -1,7 +1,26 @@
|
|
1
1
|
module Heist
|
2
2
|
class Runtime
|
3
3
|
|
4
|
+
# The +Stackless+ class provides a faster execution model than +Stack+,
|
5
|
+
# as it does not provide fine-grained enough escape points to allow
|
6
|
+
# fully for continuations. Continuations aside, +Stackless+ supports
|
7
|
+
# all the same language features as +Stack+, including using a
|
8
|
+
# trampoline to implement tail call optimisation.
|
4
9
|
class Stackless
|
10
|
+
|
11
|
+
# Returns the result of evaluating the +Expression+ in the given
|
12
|
+
# +Frame+ object. This API probably looks a little weird; it's like
|
13
|
+
# this for consistency with the +Stack+ API so the two can be used
|
14
|
+
# interchangeably without changing the implementation of
|
15
|
+
# <tt>Expression#eval</tt>.
|
16
|
+
#
|
17
|
+
# The expression is evaluated by repeatedly calling <tt>Function</tt>s
|
18
|
+
# until a concrete value is returned. Calling a Scheme procedure
|
19
|
+
# returns a +Body+ object that binds its body to the +Scope+ created
|
20
|
+
# by calling the procedure. As functions do not evaluate themselves
|
21
|
+
# we can turn what would be a recursive process into an iterative
|
22
|
+
# one and optimise tail calls. This technique is known as trampolining.
|
23
|
+
#
|
5
24
|
def <<(frame)
|
6
25
|
@current = frame
|
7
26
|
@current = process! while incomplete?
|
@@ -10,35 +29,58 @@ module Heist
|
|
10
29
|
|
11
30
|
private
|
12
31
|
|
32
|
+
# Process the current +Frame+ or +Body+ on the top of the stack. This
|
33
|
+
# method processes one such object for each call, and we call it
|
34
|
+
# iteratively until the value of <tt>@current</tt> is a concrete value.
|
35
|
+
#
|
36
|
+
# The result of calling a +Function+ (or one of its subclasses) will be
|
37
|
+
# a value (for primitives), a +Body+ or a +Frame+, or a <tt>Macro::Expansion</tt>.
|
38
|
+
# Functions return their bodies rather than evaluating themselves to
|
39
|
+
# allow for trampolining.
|
40
|
+
#
|
13
41
|
def process!
|
14
42
|
expression, scope = @current.expression,
|
15
43
|
@current.scope
|
16
44
|
|
45
|
+
# For function bodies, evaluate all but the last expression and
|
46
|
+
# return the last expression (the tail call) as a new stack frame
|
17
47
|
if Body === @current
|
18
|
-
|
19
|
-
|
48
|
+
limit = expression.size - 1
|
49
|
+
expression.each_with_index do |expr, i|
|
50
|
+
return Frame.new(expr, scope) if i == limit
|
51
|
+
Heist.evaluate(expr, scope)
|
52
|
+
end
|
20
53
|
end
|
21
54
|
|
55
|
+
# Handle single-expression stack frames
|
22
56
|
case expression
|
23
57
|
|
24
|
-
|
25
|
-
|
58
|
+
# If the expression is a list, evaluate the first element and
|
59
|
+
# call the resulting function with the rest of the list
|
60
|
+
when Cons then
|
61
|
+
first = !expression.null? && Heist.evaluate(expression.car, scope)
|
26
62
|
raise SyntaxError.new("Invalid expression: #{expression}") unless Function === first
|
27
|
-
|
28
|
-
value = first.call(scope, expression.rest)
|
63
|
+
value = first.call(scope, expression.cdr)
|
29
64
|
return value unless Macro::Expansion === value
|
30
65
|
|
66
|
+
# If the return value is a macro expansion, inline it and
|
67
|
+
# set the expansion up as the next stack frame to run
|
31
68
|
expression.replace(value.expression)
|
32
69
|
return Frame.new(value.expression, scope)
|
33
70
|
|
71
|
+
# If the expression is an identifier, look up its value in
|
72
|
+
# the current scope
|
34
73
|
when Identifier then
|
35
74
|
scope[expression]
|
36
75
|
|
76
|
+
# Otherwise, assume the expression is data and return it
|
37
77
|
else
|
38
78
|
expression
|
39
79
|
end
|
40
80
|
end
|
41
81
|
|
82
|
+
# Returns +true+ if the current computation is incomplete, that is the
|
83
|
+
# value of <tt>@current</tt> is an expression rather than a value.
|
42
84
|
def incomplete?
|
43
85
|
(Frame === @current) or (Binding === @current)
|
44
86
|
end
|
data/test/arithmetic.scm
CHANGED
@@ -10,7 +10,7 @@
|
|
10
10
|
(assert-equal 8 (expt 2 3))
|
11
11
|
(assert-equal 2 (expt 4 1/2))
|
12
12
|
|
13
|
-
(define (sqrt x)
|
13
|
+
(define (test-sqrt x)
|
14
14
|
(define (square x)
|
15
15
|
(* x x))
|
16
16
|
(define (average x y)
|
@@ -25,7 +25,7 @@
|
|
25
25
|
(sqrt-iter (improve guess))))
|
26
26
|
(sqrt-iter 1.0))
|
27
27
|
|
28
|
-
(assert (< (abs (- (sqrt 9) 3)) 0.0001))
|
28
|
+
(assert (< (abs (- (test-sqrt 9) 3)) 0.0001))
|
29
29
|
|
30
30
|
; http://www.schemers.org/Documents/Standards/R5RS/HTML/r5rs-Z-H-9.html#%_idx_288
|
31
31
|
(assert-equal 1 (modulo 13 4))
|
@@ -42,6 +42,10 @@
|
|
42
42
|
(assert-equal 288 (lcm 32 -36))
|
43
43
|
(assert-equal 288.0 (lcm 32.0 -36)) ; inexact
|
44
44
|
|
45
|
+
(assert-equal 3 (numerator (/ 6 4)))
|
46
|
+
(assert-equal 2 (denominator (/ 6 4)))
|
47
|
+
; (assert-equal 2.0 (denominator (exact->inexact (/ 6 4)))) TODO implement this
|
48
|
+
|
45
49
|
(assert-equal -5.0 (floor -4.3))
|
46
50
|
(assert-equal -4.0 (ceiling -4.3))
|
47
51
|
(assert-equal -4.0 (truncate -4.3))
|
@@ -55,3 +59,8 @@
|
|
55
59
|
(assert-equal 4 (round 7/2)) ; exact
|
56
60
|
(assert-equal 7 (round 7))
|
57
61
|
|
62
|
+
(assert-equal 4 (real-part 4+3i))
|
63
|
+
(assert-equal 3 (imag-part 4+3i))
|
64
|
+
(assert-equal 5 (magnitude 4+3i))
|
65
|
+
(assert-equal 4 (magnitude 4))
|
66
|
+
|
data/test/continuations.scm
CHANGED
@@ -1,5 +1,5 @@
|
|
1
|
-
(define r)
|
2
|
-
(define value)
|
1
|
+
(define r #f)
|
2
|
+
(define value #f)
|
3
3
|
|
4
4
|
; call/cc returning normally
|
5
5
|
(set! value (+ 3 (call/cc (lambda (k) (+ 2 7)))))
|
@@ -142,3 +142,17 @@
|
|
142
142
|
(r 3)
|
143
143
|
(assert-equal 20 value)
|
144
144
|
|
145
|
+
; continuations should work inside loops
|
146
|
+
(set! value (do ((y 10)
|
147
|
+
(acc 1 (* y acc)))
|
148
|
+
((= y 1) acc)
|
149
|
+
(set! y (call/cc (lambda (k)
|
150
|
+
(set! r k)
|
151
|
+
(- y 1))))))
|
152
|
+
(r 1)
|
153
|
+
(assert-equal 362880 value)
|
154
|
+
(r 2)
|
155
|
+
(assert-equal 725760 value)
|
156
|
+
(r 3)
|
157
|
+
(assert-equal 4354560 value)
|
158
|
+
|
@@ -0,0 +1,34 @@
|
|
1
|
+
(assert (eqv? #t #t))
|
2
|
+
(assert (eqv? #f #f))
|
3
|
+
(assert (not (eqv? #t #f)))
|
4
|
+
|
5
|
+
(assert (symbol? 'foo))
|
6
|
+
(assert (eqv? 'foo 'foo))
|
7
|
+
(assert (not (eqv? 'foo 'bar)))
|
8
|
+
|
9
|
+
(assert (eqv? 42 42))
|
10
|
+
(assert (not (eqv? 42 #f)))
|
11
|
+
(assert (not (eqv? 42 42.0)))
|
12
|
+
|
13
|
+
(assert (eqv? '() '()))
|
14
|
+
(assert (not (eqv? '(1 2) '(1 2))))
|
15
|
+
(assert (not (eqv? '() '(1 2))))
|
16
|
+
|
17
|
+
(assert (eqv? ceil ceiling))
|
18
|
+
|
19
|
+
|
20
|
+
; From http://www.schemers.org/Documents/Standards/R5RS/HTML/r5rs-Z-H-9.html#%_idx_210
|
21
|
+
|
22
|
+
(define gen-counter
|
23
|
+
(lambda ()
|
24
|
+
(let ((n 0))
|
25
|
+
(lambda () (set! n (+ n 1)) n))))
|
26
|
+
|
27
|
+
(assert (let ((g (gen-counter)))
|
28
|
+
(eqv? g g)))
|
29
|
+
|
30
|
+
(assert (not (eqv? (gen-counter) (gen-counter))))
|
31
|
+
|
32
|
+
(assert (let ((x '(a)))
|
33
|
+
(eqv? x x)))
|
34
|
+
|
data/test/functional.scm
ADDED
data/test/lists.scm
ADDED
@@ -0,0 +1,78 @@
|
|
1
|
+
(assert (eqv? '() '()))
|
2
|
+
(assert (not (eqv? '(5) '(5))))
|
3
|
+
|
4
|
+
(assert (null? '()))
|
5
|
+
(assert (list? '()))
|
6
|
+
(assert (not (pair? '())))
|
7
|
+
|
8
|
+
(define foo-list (list (+ 3 2) (* 4 5) 6))
|
9
|
+
(assert (not (eqv? '(5 20 6) foo-list)))
|
10
|
+
(assert (equal? '(5 20 6) foo-list))
|
11
|
+
|
12
|
+
(define bar-list (cons 12 foo-list))
|
13
|
+
(assert (equal? '(12 5 20 6) bar-list))
|
14
|
+
(assert (equal? '(5 20 6) foo-list))
|
15
|
+
(assert-equal 4 (length bar-list))
|
16
|
+
(assert-equal 3 (length foo-list))
|
17
|
+
|
18
|
+
(assert-equal 5 (car foo-list))
|
19
|
+
(assert-equal '(20 6) (cdr foo-list))
|
20
|
+
(assert-equal 20 (cadr foo-list))
|
21
|
+
(assert-equal 6 (caddr foo-list))
|
22
|
+
|
23
|
+
(define eggman '(you (walrus (hurt the) one) ((you) love)))
|
24
|
+
(assert-equal '(hurt the) (cadadr eggman))
|
25
|
+
|
26
|
+
(assert (null? (cdddr eggman)))
|
27
|
+
(assert (null? '()))
|
28
|
+
(assert (list? eggman))
|
29
|
+
(assert (pair? eggman))
|
30
|
+
(assert (not (null? eggman)))
|
31
|
+
|
32
|
+
(define my-pair (cons 'foo 'bar))
|
33
|
+
(assert (pair? my-pair))
|
34
|
+
(assert (not (list? my-pair)))
|
35
|
+
(assert (not (null? my-pair)))
|
36
|
+
|
37
|
+
(set-car! my-pair 27)
|
38
|
+
(set-cdr! my-pair (cons 64 '()))
|
39
|
+
(assert (list? my-pair))
|
40
|
+
(assert (equal? '(27 64) my-pair))
|
41
|
+
(assert-equal 2 (length my-pair))
|
42
|
+
|
43
|
+
(define (f) (list 'not-a-constant-list))
|
44
|
+
(define (g) '(constant-list))
|
45
|
+
(assert-equal 3 (set-car! (f) 3))
|
46
|
+
(assert-raise ImmutableError (set-car! (g) 3))
|
47
|
+
|
48
|
+
(assert-raise SyntaxError ())
|
49
|
+
(assert-raise SyntaxError (1 2 3))
|
50
|
+
|
51
|
+
(assert-equal (cons 1 2) '(1 . 2))
|
52
|
+
(assert-equal (cons 1 (cons 2 3)) '(1 2 . 3))
|
53
|
+
|
54
|
+
(assert-equal '(x y) (append '(x) '(y)))
|
55
|
+
(assert-equal '(a b c d) (append '(a) '(b c d)))
|
56
|
+
(assert-equal '(a (b) (c)) (append '(a (b)) '((c))))
|
57
|
+
(assert-equal '(a b c . d) (append '(a b) '(c . d)))
|
58
|
+
(assert-equal 'a (append '() 'a))
|
59
|
+
|
60
|
+
(assert-equal '(4 3 2 1) (reverse '(1 2 3 4)))
|
61
|
+
(assert-equal '(4 (3 5) 2 1) (reverse '(1 2 (3 5) 4)))
|
62
|
+
|
63
|
+
(assert-equal '(3 4) (list-tail '(1 2 3 4) 2))
|
64
|
+
|
65
|
+
(assert-equal '(a b c) (memq 'a '(a b c)))
|
66
|
+
(assert-equal '(b c) (memq 'b '(a b c)))
|
67
|
+
(assert-equal #f (memq 'a '(b c d)))
|
68
|
+
(assert-equal #f (memq (list 'a) '(b (a) c)))
|
69
|
+
(assert-equal '((a) c) (member (list 'a) '(b (a) c)))
|
70
|
+
|
71
|
+
(define e '((a 1) (b 2) (c 3)))
|
72
|
+
(assert-equal '(a 1) (assq 'a e))
|
73
|
+
(assert-equal '(b 2) (assq 'b e))
|
74
|
+
(assert-equal #f (assq 'd e))
|
75
|
+
(assert-equal #f (assq (list 'a) '(((a)) ((b)) ((c)))))
|
76
|
+
(assert-equal '((a)) (assoc (list 'a) '(((a)) ((b)) ((c)))))
|
77
|
+
(assert-equal '(5 7) (assv 5 '((2 3) (5 7) (11 13))))
|
78
|
+
|
data/test/macro-helpers.scm
CHANGED
data/test/macros.scm
CHANGED
@@ -43,6 +43,34 @@
|
|
43
43
|
|
44
44
|
(assert-equal 8 (dont-rename-else #f 6 8))
|
45
45
|
|
46
|
+
; Check that keywords are ignored if locally bound
|
47
|
+
; example from R6RS -- http://www.r6rs.org/final/html/r6rs/r6rs-Z-H-14.html#node_sec_11.19
|
48
|
+
(assert-equal 'ok (let ((=> #f))
|
49
|
+
(cond (#t => 'ok))))
|
50
|
+
|
51
|
+
; These tests come from tinkering with MZScheme
|
52
|
+
(define-syntax keyword-detect (syntax-rules (word)
|
53
|
+
[(_ word) 'keyword]
|
54
|
+
[(_ data) 'data]))
|
55
|
+
(assert-equal 'keyword (keyword-detect word))
|
56
|
+
(assert-equal 'word (let ([word 4]) (keyword-detect word)))
|
57
|
+
(define word 5)
|
58
|
+
(assert-equal 'keyword (keyword-detect word))
|
59
|
+
(define copy word)
|
60
|
+
(assert-equal 'copy (keyword-detect copy))
|
61
|
+
|
62
|
+
(define-syntax bad-keyword (syntax-rules (with)
|
63
|
+
[(_ with x)
|
64
|
+
`(,with ,x)]))
|
65
|
+
|
66
|
+
(let ([with 16])
|
67
|
+
(assert-raise SyntaxError (bad-keyword with 1)))
|
68
|
+
|
69
|
+
(assert-raise UndefinedVariable (bad-keyword with 2))
|
70
|
+
|
71
|
+
(define with 16)
|
72
|
+
(assert-equal '(16 3) (bad-keyword with 3))
|
73
|
+
|
46
74
|
|
47
75
|
; Test literal matching
|
48
76
|
|
@@ -55,6 +83,18 @@
|
|
55
83
|
(assert-equal 3 (iffy 7 #f 3))
|
56
84
|
|
57
85
|
|
86
|
+
; Test improper patterns
|
87
|
+
(define-syntax rest (syntax-rules ()
|
88
|
+
[(_ foo bar . rest)
|
89
|
+
rest]))
|
90
|
+
(assert-equal 10 (rest 4 5 + 3 7))
|
91
|
+
(let-syntax ([foo (syntax-rules ()
|
92
|
+
[(_ expr ...)
|
93
|
+
(list expr ...)])])
|
94
|
+
(assert-equal '(1 2 3) (foo 1 2 3))
|
95
|
+
(assert-raise SyntaxError (foo 1 2 3 . 4)))
|
96
|
+
|
97
|
+
|
58
98
|
; Test input execution - example from R5RS
|
59
99
|
|
60
100
|
(define-syntax my-or
|
@@ -105,6 +145,20 @@
|
|
105
145
|
(assert-equal 13 (one-or-more (+ 2 4) (+ 3 8) (+ 7 6)))
|
106
146
|
|
107
147
|
|
148
|
+
; Test that null lists terminators don't count as input
|
149
|
+
|
150
|
+
(define-syntax table (syntax-rules ()
|
151
|
+
[(_)
|
152
|
+
'()]
|
153
|
+
[(_ key value rest ...)
|
154
|
+
(cons (cons key value) (table rest ...))]))
|
155
|
+
|
156
|
+
(assert-equal (list (cons 1 2) (cons 3 4) (cons 5 6))
|
157
|
+
(table 1 2 3 4 5 6))
|
158
|
+
|
159
|
+
(assert-raise SyntaxError (table 1 2 3))
|
160
|
+
|
161
|
+
|
108
162
|
; Test execution scope using (swap)
|
109
163
|
(define a 4)
|
110
164
|
(define b 7)
|
@@ -151,6 +205,7 @@
|
|
151
205
|
(syntax-rules ()
|
152
206
|
[(swap (x y))
|
153
207
|
(let ([temp x])
|
208
|
+
(set! x temp) ; Force temp in lazy mode
|
154
209
|
(set! x y)
|
155
210
|
(set! y temp))]))
|
156
211
|
|
@@ -301,6 +356,7 @@
|
|
301
356
|
(triple-deep (((foo bar) (it)) ((wont) (matter really anyway)))
|
302
357
|
((5 6) (2)) ((4) (8 7 2))))
|
303
358
|
|
359
|
+
|
304
360
|
(define-syntax triple-deep2
|
305
361
|
(syntax-rules ()
|
306
362
|
[(_ (((name ...) ...) ...) ((value ...) ...) ...)
|
@@ -313,31 +369,62 @@
|
|
313
369
|
((5 6)) ((4) (8 7 2))))
|
314
370
|
|
315
371
|
|
316
|
-
|
317
|
-
|
318
|
-
|
372
|
+
(define-syntax trial (syntax-rules (with)
|
373
|
+
[(_ ((with (value ...) ...) ...) obj ...)
|
374
|
+
'((obj ((value ...) (value value) ...) ... (obj obj)) ...)]))
|
375
|
+
|
376
|
+
(assert-equal '((bar ((4 2 7) (4 4) (2 2) (7 7)) (bar bar)))
|
377
|
+
(trial ((with (4 2 7))) bar))
|
378
|
+
(assert-equal '((bar (bar bar)))
|
379
|
+
(trial ((with)) bar))
|
380
|
+
(assert-raise MacroTemplateMismatch (trial () bar))
|
381
|
+
|
382
|
+
|
383
|
+
(define-syntax trial2 (syntax-rules (with)
|
384
|
+
[(_ (with (value ...) ...) ... obj ...)
|
385
|
+
'((obj ((value ...) (value value) ...) ... (obj obj)) ...)]))
|
319
386
|
|
320
|
-
(
|
321
|
-
|
322
|
-
|
323
|
-
'((obj ((value ...) (value value) ...) ... (obj obj)) ...
|
324
|
-
(((name name) ... obj obj (obj (name ...))) ...))]))
|
387
|
+
(assert-equal '((foo ((a) (a a)) (foo foo))
|
388
|
+
(bar (bar bar)))
|
389
|
+
(trial2 (with (a)) (with) foo bar))
|
325
390
|
|
326
|
-
(assert-equal '((foo ((a
|
327
|
-
(()) ((q c y n) (q q) (c c) (y y) (n n)) (foo foo))
|
391
|
+
(assert-equal '((foo ((a) (a a)) (foo foo))
|
328
392
|
(bar (bar bar))
|
329
|
-
(baz ((
|
330
|
-
|
331
|
-
|
332
|
-
|
333
|
-
|
334
|
-
|
335
|
-
|
336
|
-
|
337
|
-
|
338
|
-
|
339
|
-
|
340
|
-
|
341
|
-
(
|
342
|
-
(
|
393
|
+
(baz ((1 2 3) (1 1) (2 2) (3 3)) (baz baz)))
|
394
|
+
(trial2 (with (a)) (with) (with (1 2 3)) foo bar baz))
|
395
|
+
|
396
|
+
|
397
|
+
; Test nested macros with keywords and nested splices
|
398
|
+
; http://fabiokung.com/2007/10/24/ruby-dsl-to-describe-automata/
|
399
|
+
|
400
|
+
(define-syntax automaton (syntax-rules (:)
|
401
|
+
[(_ init-state
|
402
|
+
[state : response ...]
|
403
|
+
...)
|
404
|
+
(let-syntax ([process-state (syntax-rules (-> accept)
|
405
|
+
[(_ accept)
|
406
|
+
(lambda (stream)
|
407
|
+
(cond [(null? stream) #t]
|
408
|
+
[else #f]))]
|
409
|
+
[(... (_ (label -> target) ...))
|
410
|
+
(lambda (stream)
|
411
|
+
(cond [(null? stream) #f]
|
412
|
+
[else (case (car stream)
|
413
|
+
[(label) (target (cdr stream))]
|
414
|
+
(... ...)
|
415
|
+
[else #f])]))])])
|
416
|
+
(letrec ([state (process-state response ...)]
|
417
|
+
...)
|
418
|
+
init-state))]))
|
419
|
+
|
420
|
+
(define cdar-sequence?
|
421
|
+
(automaton init
|
422
|
+
[init : (c -> more)]
|
423
|
+
[more : (a -> more)
|
424
|
+
(d -> more)
|
425
|
+
(r -> end)]
|
426
|
+
[end : accept]))
|
427
|
+
|
428
|
+
(assert (cdar-sequence? '(c a d a d r)))
|
429
|
+
(assert (not (cdar-sequence? '(a c a d r c))))
|
343
430
|
|