heist 0.1.0 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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
- expression[0...-1].each { |expr| Heist.evaluate(expr, scope) }
19
- return Frame.new(expression.last, scope)
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
- when List then
25
- first = Heist.evaluate(expression.first, scope)
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
+
@@ -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
+
@@ -0,0 +1,4 @@
1
+ (assert-equal 10 (apply + '(1 2 3 4)))
2
+ (assert-equal '(1 4 9 16) (map (lambda (x) (* x x)) '(1 2 3 4)))
3
+ (assert-equal '(5 7 9) (map + '(1 2 3) '(4 5 6)))
4
+
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
+
@@ -7,6 +7,7 @@
7
7
  (define-syntax swap (syntax-rules ()
8
8
  [(swap x y)
9
9
  (let ([temp x])
10
+ (set! x temp) ; Force evaluation of temp in lazy mode
10
11
  (set! x y)
11
12
  (set! y temp))]))
12
13
 
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
- ; Really nasty nested repetition. PLT won't run this in its entirity
317
- ; due to overuse of infix ellipses, but comparison output for
318
- ; subsets of this macro can be seen in plt-macros.txt
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
- (define-syntax convoluted
321
- (syntax-rules (with)
322
- [(_ (with (value ...) ...) ... thing ((name ...) ...) obj ...)
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 u) (a a) (u u)) ((j e n k l) (j j) (e e) (n n) (k k) (l l))
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 ((b) (b b)) ((d f) (d d) (f f)) (baz baz))
330
- (what ((k l e) (k k) (l l) (e e)) ((s) (s s)) ((u n) (u u) (n n))
331
- ((f i k w) (f f) (i i) (k k) (w w)) ((p) (p p)) (what what))
332
- (((8 8) (3 3) (2 2) (9 9) foo foo (foo (8 3 2 9)))
333
- ((2 2) (3 3) bar bar (bar (2 3)))
334
- ((1 1) (0 0) (4 4) baz baz (baz (1 0 4)))
335
- ((8 8) (3 3) (2 2) (1 1) (7 7) what what (what (8 3 2 1 7)))))
336
- (convoluted (with (a u) (j e n k l) () (q c y n)) (with)
337
- (with (b) (d f)) (with (k l e) (s) (u n) (f i k w) (p))
338
- thing ((8 3 2 9) (2 3) (1 0 4) (8 3 2 1 7))
339
- foo bar baz what))
340
-
341
- (assert-raise MacroTemplateMismatch (convoluted (with (a)) (with (b)) thing () foo))
342
- (assert-raise SyntaxError (convoluted nothing))
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