heist 0.1.0 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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