Skip to content
This repository

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse code

Lazy list evaluation for-the-win

  • Loading branch information...
commit 61da2b0ebcb387ae4a3d0c31d04d3c1e8f56c3a4 1 parent 495c3e2
Josep M. Bach authored
70 examples/bintree.scm
@@ -8,39 +8,37 @@
8 8 (define (make-tree entry left right)
9 9 (list entry left right))
10 10
11   -(make-tree 3 3 2)
12   -;
13   -; (define (make-new-set)
14   -; '())
15   -;
16   -; (define (element-of-set? x set)
17   -; (cond
18   -; ((null? set) #f)
19   -; ((= x (entry set)) #t)
20   -; ((< x (entry set)) (element-of-set? x (left-branch set)))
21   -; ((> x (entry set)) (element-of-set? x (right-branch set)))))
22   -;
23   -; (define (adjoin-set x set)
24   -; (cond
25   -; ((null? set) (make-tree x '() '()))
26   -; ((= x (entry set)) set)
27   -; ((< x (entry set))
28   -; (make-tree (entry set)
29   -; (adjoin-set x (left-branch set))
30   -; (right-branch set)))
31   -; ((> x (entry set))
32   -; (make-tree (entry set)
33   -; (left-branch set)
34   -; (adjoin-set x (right-branch set))))))
35   -;
36   -;
37   -; (define myset
38   -; (adjoin-set 25
39   -; (adjoin-set 13
40   -; (adjoin-set 72
41   -; (adjoin-set 4 (make-new-set))))))
42   -;
43   -; (write (element-of-set? 4 myset))
44   -; (write (element-of-set? 5 myset))
45   -; (write (element-of-set? 26 myset))
46   -; (write (element-of-set? 25 myset))
  11 +(define (make-new-set)
  12 + '())
  13 +
  14 +(define (element-of-set? x set)
  15 + (cond
  16 + ((null? set) #f)
  17 + ((= x (entry set)) #t)
  18 + ((< x (entry set)) (element-of-set? x (left-branch set)))
  19 + ((> x (entry set)) (element-of-set? x (right-branch set)))))
  20 +
  21 +(define (adjoin-set x set)
  22 + (cond
  23 + ((null? set) (make-tree x '() '()))
  24 + ((= x (entry set)) set)
  25 + ((< x (entry set))
  26 + (make-tree (entry set)
  27 + (adjoin-set x (left-branch set))
  28 + (right-branch set)))
  29 + ((> x (entry set))
  30 + (make-tree (entry set)
  31 + (left-branch set)
  32 + (adjoin-set x (right-branch set))))))
  33 +
  34 +
  35 +(define myset
  36 + (adjoin-set 25
  37 + (adjoin-set 13
  38 + (adjoin-set 72
  39 + (adjoin-set 4 (make-new-set))))))
  40 +
  41 +(write (element-of-set? 4 myset))
  42 +(write (element-of-set? 5 myset))
  43 +(write (element-of-set? 26 myset))
  44 +(write (element-of-set? 25 myset))
112 lib/schemer/ast.rb
@@ -8,6 +8,9 @@ def true?
8 8 def false?
9 9 !true?
10 10 end
  11 + def literal?
  12 + false
  13 + end
11 14 end
12 15
13 16 class Comment < Node
@@ -35,6 +38,10 @@ def initialize(character)
35 38 def inspect
36 39 "#<Char::#{@value}>"
37 40 end
  41 +
  42 + def literal?
  43 + true
  44 + end
38 45 end
39 46
40 47 class Identifier < Node
@@ -65,108 +72,61 @@ def inspect
65 72 end
66 73 end
67 74
68   - class Procedure < Node
69   - attr_reader :proc, :args
70   -
71   - def initialize(procedure)
72   - @proc = procedure[:proc]
73   - @args = procedure[:args].empty? ? nil : procedure[:args].to_a
74   - end
75   -
76   - def eval(context)
77   - # Evaluate arguments
78   - arguments = nil
79   - arguments = args.map do |arg|
80   - if arg.is_a?(Identifier)
81   - arg = context.get_binding(arg.value) || arg
82   - else
83   - begin
84   - arg = arg.eval(context)
85   - rescue
86   - arg
87   - end
88   - end
89   - end if args
90   -
91   - # Evaluate proc
92   - if @proc.respond_to?(:value)
93   - block = context.get_binding(@proc.value)
94   - else
95   - block = @proc
96   - end
  75 + class List < Node
  76 + attr_reader :elements
97 77
98   - block.call(*arguments)
99   - rescue NoMethodError=>e
100   - puts e.inspect
101   - puts e.backtrace.inspect
102   - if e.message =~ /#{@proc.value}/
103   - raise "#{@proc.value} is not a defined procedure."
104   - else
105   - raise e
106   - end
  78 + def initialize(elements)
  79 + @elements = elements
107 80 end
108 81
109 82 def to_a
110   - [@proc, @args].compact.flatten
111   - end
112   -
113   - def inspect
114   - "#<Procedure @proc=#{@proc.inspect} @args=#{@args || 'nil'}>"
  83 + @elements
115 84 end
116   - end
117 85
118   - class List < Node
119   - attr_reader :elements
  86 + def eval(context)
  87 + cdr = @elements.clone
  88 + car = cdr.shift
120 89
121   - def initialize(elements, context = nil)
122   - if context
123   - @elements = elements.map do |element|
124   - begin
125   - evaled = element.eval(context)
126   - evaled || element
127   - rescue
128   - element
  90 + if car.is_a?(Identifier)
  91 + procedure = context.get_binding(car.value)
  92 + if procedure
  93 + return procedure.call(context, *cdr)
  94 + else
  95 + raise "UNKNOWN IDENTIFIER #{car.value}"
  96 + end
  97 + elsif car.is_a?(List)
  98 + new_car = car.eval(context)
  99 + if new_car.is_a?(Identifier)
  100 + procedure = context.get_binding(car.value)
  101 + if procedure
  102 + return procedure.call(context, *cdr)
  103 + else
  104 + raise "UNKNOWN IDENTIFIER #{car.value}"
129 105 end
  106 + elsif new_car.is_a?(Proc)
  107 + return new_car.call(context, *cdr)
130 108 end
131 109 else
132   - @elements = elements
  110 + return self
133 111 end
134 112 end
135 113
136   - def to_a
137   - @elements
138   - end
139   -
140   - def to_list
141   - self
142   - end
143   -
144   - def eval(context)
145   - List.new(@elements, context)
146   - end
147   -
148 114 def empty?
149 115 @elements.empty?
150 116 end
151 117
152 118 def inspect
153   - "#<List @elements=#{@elements}>"
  119 + "#<List #{@elements}>"
154 120 end
155 121 end
156 122
157   - class QuotedList < Node
158   - attr_reader :elements
159   -
160   - def initialize(elements)
161   - @elements = elements
162   - end
163   -
  123 + class QuotedList < List
164 124 def eval(context)
165 125 self
166 126 end
167 127
168 128 def inspect
169   - "#<QuotedList @elements=#{@elements}>"
  129 + "#<QuotedList #{@elements}>"
170 130 end
171 131 end
172 132
3  lib/schemer/core_ext/fixnum.rb
@@ -2,4 +2,7 @@ class Fixnum
2 2 def eval(context)
3 3 self
4 4 end
  5 + def literal?
  6 + true
  7 + end
5 8 end
2  lib/schemer/environment.rb
@@ -13,7 +13,7 @@ def add_binding(name, value)
13 13
14 14 def get_binding(name)
15 15 if got = @bindings[name.to_s]
16   - return got
  16 + got
17 17 elsif ! @parent.nil?
18 18 @parent.get_binding(name)
19 19 else
136 lib/schemer/interpreter.rb
@@ -7,120 +7,122 @@ def initialize(ast = nil)
7 7 @ast = ast
8 8 @env = Environment.new do |env|
9 9
10   - env.add_binding(:+, lambda do |a, b|
11   - a + b
  10 + env.add_binding(:+, lambda do |cxt, a, b|
  11 + a.eval(cxt) + b.eval(cxt)
12 12 end)
13 13
14   - env.add_binding(:-, lambda do |a, b|
15   - a - b
  14 + env.add_binding(:-, lambda do |cxt, a, b|
  15 + a.eval(cxt) - b.eval(cxt)
16 16 end)
17 17
18   - env.add_binding(:*, lambda do |a, b|
19   - a * b
  18 + env.add_binding(:*, lambda do |cxt, a, b|
  19 + a.eval(cxt) * b.eval(cxt)
20 20 end)
21 21
22   - env.add_binding(:/, lambda do |a, b|
23   - a / b
  22 + env.add_binding(:/, lambda do |cxt, a, b|
  23 + a.eval(cxt) / b.eval(cxt)
24 24 end)
25 25
26   - env.add_binding(:write, lambda do |value|
27   - $stdout.print value
  26 + env.add_binding(:write, lambda do |cxt, value|
  27 + $stdout.print value.eval(cxt)
28 28 nil
29 29 end)
30 30
31   - env.add_binding(:inspect, lambda do |object|
32   - object.inspect
  31 + env.add_binding(:inspect, lambda do |cxt, object|
  32 + object.eval(cxt).inspect
33 33 end)
34 34
35   - env.add_binding(:define, lambda do |parameters, body|
  35 + env.add_binding(:define, lambda do |cxt, parameters, body|
36 36 if parameters.is_a?(AST::Identifier)
37   - # We are declaring a variable. We must eager-evaluate the value.
38   - result = if body.is_a?(AST::Procedure)
39   - body.eval(env)
40   - else
41   - body
42   - end
43   - env.add_binding(parameters.value, result)
  37 + binded_value = if body.literal?
  38 + body
  39 + else
  40 + body.eval(cxt)
  41 + end
  42 + cxt.add_binding(parameters.value, binded_value)
44 43 return nil
45 44 end
46   - original_params = parameters.to_a
47   - puts "AAAAA"
48   - puts original_params.inspect
49   - puts body.inspect
50   - name = original_params.shift
51   -
52   - env.add_binding(name.value, lambda do |*args|
53   - # Create a new scope
54   - environment = Environment.new(env)
55   - args.each_with_index do |arg, idx|
56   - puts 'adding to envirnoment....'
57   - puts original_params[idx].value.inspect
58   - puts arg.eval(environment)
59   -
60   - environment.add_binding(original_params[idx].value, arg.eval(environment))
  45 +
  46 + parameters = parameters.to_a
  47 + name = parameters.shift.value
  48 +
  49 + cxt.add_binding(name, lambda do |context, *params|
  50 + context = Environment.new(context)
  51 +
  52 + params.each_with_index do |param, idx|
  53 + context.add_binding(parameters[idx].value, param.eval(context))
61 54 end
62   - puts environment.instance_variable_get(:@bindings).inspect
63   - puts body.inspect
64   - body.eval(environment)
  55 + body.eval(context)
  56 +
65 57 end)
  58 +
66 59 nil
67 60 end)
68 61
69   - env.add_binding(:lambda, lambda do |parameters, body|
70   - original_params = parameters.to_a
71   - lambda do |*args|
72   - environment = Environment.new(env)
73   - args.each_with_index do |arg, idx|
74   - environment.add_binding(original_params[idx].value, arg.eval(environment))
  62 + env.add_binding(:lambda, lambda do |cxt, parameters, body|
  63 +
  64 + parameters = parameters.to_a
  65 +
  66 + lambda do |context, *params|
  67 + context = Environment.new(context)
  68 +
  69 + params.each_with_index do |param, idx|
  70 + context.add_binding(parameters[idx].value, param.eval(context))
75 71 end
76   - body.eval(environment)
  72 + body.eval(context)
77 73 end
78 74 end)
79 75
80   - env.add_binding(:car, lambda do |list|
81   - list.to_a.first
  76 + env.add_binding(:car, lambda do |cxt, list|
  77 + list.eval(cxt).to_a.first
82 78 end)
83 79
84   - env.add_binding(:cdr, lambda do |list|
85   - list.to_a.last
  80 + env.add_binding(:cdr, lambda do |cxt, list|
  81 + list.eval(cxt).to_a.last
86 82 end)
87 83
88   - env.add_binding(:cadr, lambda do |list|
89   - list.to_a.last.elements.first
  84 + env.add_binding(:cadr, lambda do |cxt, list|
  85 + list.eval(cxt).to_a.last.elements.first
90 86 end)
91 87
92   - env.add_binding(:caddr, lambda do |list|
93   - list.to_a.last.elements.last.elements.first
  88 + env.add_binding(:caddr, lambda do |cxt, list|
  89 + puts "evaling caddr #{list.inspect}"
  90 + puts list.eval(cxt).to_a.inspect
  91 + list.eval(cxt).to_a
94 92 end)
95 93
96   - env.add_binding(:list, lambda do |*args|
97   - AST::List.new(args, env)
  94 + env.add_binding(:list, lambda do |cxt, *args|
  95 + args.map! do |arg|
  96 + arg.eval(cxt)
  97 + end
  98 + AST::List.new(args)
98 99 end)
99 100
100   - env.add_binding(:null?, lambda do |object|
101   - (object.respond_to?(:empty?) && object.empty?) || object.nil?
  101 + env.add_binding(:null?, lambda do |cxt, object|
  102 + obj = object.eval(cxt)
  103 + (obj.respond_to?(:empty?) && obj.empty?) || obj.nil?
102 104 end)
103 105
104   - env.add_binding("=", lambda do |one, another|
105   - one.eval(env) == another.eval(env)
  106 + env.add_binding("=", lambda do |cxt, one, another|
  107 + one.eval(cxt) == another.eval(cxt)
106 108 end)
107 109
108   - env.add_binding(:eqv?, lambda do |one, another|
109   - one.eval(env) == another.eval(env)
  110 + env.add_binding(:eqv?, lambda do |cxt, one, another|
  111 + one.eval(cxt) == another.eval(cxt)
110 112 end)
111 113
112   - env.add_binding(:>, lambda do |one, another|
113   - one.eval(env) > another.eval(env)
  114 + env.add_binding(:>, lambda do |cxt, one, another|
  115 + one.eval(cxt) > another.eval(cxt)
114 116 end)
115 117
116   - env.add_binding(:<, lambda do |one, another|
117   - one.eval(env) < another.eval(env)
  118 + env.add_binding(:<, lambda do |cxt, one, another|
  119 + one.eval(cxt) < another.eval(cxt)
118 120 end)
119 121
120   - env.add_binding(:cond, lambda do |*conditions|
  122 + env.add_binding(:cond, lambda do |cxt, *conditions|
121 123 conditions.map!(&:to_a)
122 124 conditions.each do |condition, result|
123   - return result.eval(env) if condition.eval(env)
  125 + return result.eval(cxt) if condition.eval(cxt)
124 126 end
125 127 nil
126 128 end)
2  lib/schemer/lexer.rb
@@ -36,7 +36,7 @@ class Lexer < Parslet::Parser
36 36
37 37 rule(:operator) { `+` | `-` | `*` | `/` | `>=` | `<=` | `>` | `<` | `=` }
38 38
39   - rule(:arg) { (symbol.as(:identifier) | operator | quoted_list | literal | quoted_symbol | pair | vector | list) }
  39 + rule(:arg) { (symbol.as(:identifier) | operator.as(:identifier) | quoted_list | literal | quoted_symbol | pair | vector | list) }
40 40 rule(:args) { (arg >> space?).repeat }
41 41
42 42 rule(:comment) { `;`.repeat(1,3) >> (`\n`.absnt? >> any).repeat.as(:comment) }
427 spec/schemer/interpreter_spec.rb
@@ -14,265 +14,182 @@ module Schemer
14 14 (write (double 12))
15 15 """ }
16 16
17   - # it 'walks the AST visiting every node' do
18   - # ast = parser.apply(lexer.parse text)
19   - # interpreter = Interpreter.new(ast)
20   -
21   - # ast.each do |node|
22   - # node.should_receive(:eval).with(kind_of(Environment))
23   - # end
24   -
25   - # interpreter.walk
26   - # end
27   -
28   - # describe "builtin procedures" do
29   -
30   - # it 'adds' do
31   - # "(+ 3 4)".should evaluate_to(7)
32   - # end
33   -
34   - # it 'subtracts' do
35   - # "(- 3 4)".should evaluate_to(-1)
36   - # end
37   -
38   - # it 'multiplies' do
39   - # "(* 3 4)".should evaluate_to(12)
40   - # end
41   -
42   - # it 'divides' do
43   - # "(/ 4 2)".should evaluate_to(2)
44   - # end
45   -
46   - # describe "#write" do
47   - # it 'prints to the output' do
48   - # $stdout.should_receive(:print).with(4)
49   - # "(write 4)".should evaluate_to(nil)
50   - # end
51   - # end
52   -
53   - # describe "#inspect" do
54   - # it 'returns the object inspected' do
55   - # "(inspect x)".should evaluate_to("#<Identifier::x>")
56   - # end
57   - # end
58   -
59   - # describe "#define" do
60   - # it 'defines a variable' do
61   - # expression = "(define number 3)"
62   - # lexer = Schemer::Lexer.new
63   - # parser = Schemer::Parser.new
64   - # ast = parser.apply(lexer.parse expression)
65   -
66   - # interpreter = Schemer::Interpreter.new(ast)
67   - # interpreter.walk.should be_nil
68   - # interpreter.env.get_binding(:number).should == 3
69   - # end
70   - # it 'defines a variable from an expression' do
71   - # expression = "(define number (* 3 2))"
72   - # lexer = Schemer::Lexer.new
73   - # parser = Schemer::Parser.new
74   - # ast = parser.apply(lexer.parse expression)
75   -
76   - # interpreter = Schemer::Interpreter.new(ast)
77   - # interpreter.walk.should be_nil
78   - # interpreter.env.get_binding(:number).should == 6
79   - # end
80   - # it 'defines a function' do
81   - # expression = "(define (square x) (* x x))(square 5)"
82   - # lexer = Schemer::Lexer.new
83   - # parser = Schemer::Parser.new
84   - # ast = parser.apply(lexer.parse expression)
85   -
86   - # interpreter = Schemer::Interpreter.new(ast)
87   - # interpreter.walk.should == 25
88   - # end
89   - # it 'defines a function (edge case)' do
90   - # expression = "(define (make-list x) (list 3 x))(make-list 5)"
91   - # lexer = Schemer::Lexer.new
92   - # parser = Schemer::Parser.new
93   - # ast = parser.apply(lexer.parse expression)
94   -
95   - # interpreter = Schemer::Interpreter.new(ast)
96   - # result = interpreter.walk
97   - # result.should be_an(AST::List)
98   - # result.elements.first.should eq(3)
99   - # result.elements.last.should eq(5)
100   - # end
101   - # it 'defines a function from a lambda' do
102   - # expression = "(define square (lambda (x) (* x x)))(square 5)"
103   - # lexer = Schemer::Lexer.new
104   - # parser = Schemer::Parser.new
105   - # ast = parser.apply(lexer.parse expression)
106   -
107   - # interpreter = Schemer::Interpreter.new(ast)
108   - # interpreter.walk.should == 25
109   - # end
110   - # end
111   -
112   - # describe "#car" do
113   - # it 'returns the first element from a list' do
114   - # expression = "(car (x 3))"
115   - # lexer = Schemer::Lexer.new
116   - # parser = Schemer::Parser.new
117   - # ast = parser.apply(lexer.parse expression)
118   -
119   - # interpreter = Schemer::Interpreter.new(ast)
120   - # interpreter.walk.should be_a(AST::Identifier)
121   - # end
122   - # end
123   -
124   - # describe "#cdr" do
125   - # it 'returns the last element from a list' do
126   - # expression = "(cdr (x 3))"
127   - # lexer = Schemer::Lexer.new
128   - # parser = Schemer::Parser.new
129   - # ast = parser.apply(lexer.parse expression)
130   -
131   - # interpreter = Schemer::Interpreter.new(ast)
132   - # interpreter.walk.should == 3
133   - # end
134   - # end
135   -
136   - # describe "#cadr" do
137   - # it 'returns the first element from the last element of a list' do
138   - # expression = "(cadr (x (3 2)))"
139   - # lexer = Schemer::Lexer.new
140   - # parser = Schemer::Parser.new
141   - # ast = parser.apply(lexer.parse expression)
142   -
143   - # interpreter = Schemer::Interpreter.new(ast)
144   - # interpreter.walk.should == 3
145   - # end
146   - # end
147   -
148   - # describe "#caddr" do
149   - # it 'returns the first element from the last element of the last element of a list' do
150   - # expression = "(caddr (x (3 (1 2))))"
151   - # lexer = Schemer::Lexer.new
152   - # parser = Schemer::Parser.new
153   - # ast = parser.apply(lexer.parse expression)
154   -
155   - # interpreter = Schemer::Interpreter.new(ast)
156   - # interpreter.walk.should == 1
157   - # end
158   - # end
159   -
160   - # describe "#list" do
161   - # it 'converts elements to a list' do
162   - # expression = "(list 3 4 x y)"
163   - # lexer = Schemer::Lexer.new
164   - # parser = Schemer::Parser.new
165   - # ast = parser.apply(lexer.parse expression)
166   -
167   - # interpreter = Schemer::Interpreter.new(ast)
168   - # output = interpreter.walk
169   - # output.should be_a(AST::List)
170   - # output.should have(4).elements
171   - # end
172   - # end
173   -
174   - # describe "#null?" do
175   - # context 'if the object is nil' do
176   - # it 'returns true' do
177   - # expression = "(null? 3)"
178   - # lexer = Schemer::Lexer.new
179   - # parser = Schemer::Parser.new
180   - # ast = parser.apply(lexer.parse expression)
181   -
182   - # interpreter = Schemer::Interpreter.new(ast)
183   - # interpreter.walk.should be_false
184   - # end
185   - # end
186   - # context 'if the object is empty or nil' do
187   - # it 'returns true' do
188   - # expression = "(null? ())"
189   - # lexer = Schemer::Lexer.new
190   - # parser = Schemer::Parser.new
191   - # ast = parser.apply(lexer.parse expression)
192   -
193   - # interpreter = Schemer::Interpreter.new(ast)
194   - # interpreter.walk.should be_true
195   - # end
196   - # end
197   - # end
198   -
199   - # describe "#=" do
200   - # it 'returns the equality of two arguments' do
201   - # expression = "(= 3 (+ 1 2))"
202   - # lexer = Schemer::Lexer.new
203   - # parser = Schemer::Parser.new
204   - # ast = parser.apply(lexer.parse expression)
205   -
206   - # interpreter = Schemer::Interpreter.new(ast)
207   - # interpreter.walk.should be_true
208   - # end
209   - # end
210   -
211   - # describe "#eqv?" do
212   - # it 'returns the equivalenc of two arguments' do
213   - # expression = "(eqv? 3 (+ 1 2))"
214   - # lexer = Schemer::Lexer.new
215   - # parser = Schemer::Parser.new
216   - # ast = parser.apply(lexer.parse expression)
217   -
218   - # interpreter = Schemer::Interpreter.new(ast)
219   - # interpreter.walk.should be_true
220   - # end
221   - # end
222   -
223   - # describe "#<" do
224   - # it 'returns true if foo is less than bar' do
225   - # expression = "(< 3 (+ 2 2))"
226   - # lexer = Schemer::Lexer.new
227   - # parser = Schemer::Parser.new
228   - # ast = parser.apply(lexer.parse expression)
229   -
230   - # interpreter = Schemer::Interpreter.new(ast)
231   - # interpreter.walk.should be_true
232   - # end
233   - # end
234   -
235   - # describe "#>" do
236   - # it 'returns true if foo is more than bar' do
237   - # expression = "(> 3 (+ 2 2))"
238   - # lexer = Schemer::Lexer.new
239   - # parser = Schemer::Parser.new
240   - # ast = parser.apply(lexer.parse expression)
241   -
242   - # interpreter = Schemer::Interpreter.new(ast)
243   - # interpreter.walk.should be_false
244   - # end
245   - # end
246   -
247   - # describe "#cond" do
248   - # it 'returns the first condition that evaluates to true' do
249   - # expression = "(cond ((< 3 1) 9) ((>3 1) 7) )"
250   - # lexer = Schemer::Lexer.new
251   - # parser = Schemer::Parser.new
252   - # ast = parser.apply(lexer.parse expression)
253   -
254   - # interpreter = Schemer::Interpreter.new(ast)
255   - # interpreter.walk.should == 7
256   - # end
257   - # it 'even if the result is an expression' do
258   - # expression = "(cond ((< 3 1) 9) ((>3 8) 7) ((= 3 3) (+ 3 9)) )"
259   - # lexer = Schemer::Lexer.new
260   - # parser = Schemer::Parser.new
261   - # ast = parser.apply(lexer.parse expression)
262   -
263   - # interpreter = Schemer::Interpreter.new(ast)
264   - # interpreter.walk.should == 12
265   - # end
266   - # end
267   -
268   - # end
269   -
270   - it 'parses bintree.scm' do
  17 + it 'walks the AST visiting every node' do
  18 + ast = parser.apply(lexer.parse text)
  19 + interpreter = Interpreter.new(ast)
  20 +
  21 + ast.each do |node|
  22 + node.should_receive(:eval).with(kind_of(Environment))
  23 + end
  24 +
  25 + interpreter.walk
  26 + end
  27 +
  28 + describe "builtin procedures" do
  29 +
  30 + it 'adds' do
  31 + "(+ 3 4)".should evaluate_to(7)
  32 + end
  33 +
  34 + it 'subtracts' do
  35 + "(- 3 4)".should evaluate_to(-1)
  36 + end
  37 +
  38 + it 'multiplies' do
  39 + "(* 3 4)".should evaluate_to(12)
  40 + end
  41 +
  42 + it 'divides' do
  43 + "(/ 4 2)".should evaluate_to(2)
  44 + end
  45 +
  46 + describe "#write" do
  47 + it 'prints to the output' do
  48 + $stdout.should_receive(:print).with(4)
  49 + "(write 4)".should evaluate_to(nil)
  50 + end
  51 + end
  52 +
  53 + describe "#inspect" do
  54 + it 'returns the object inspected' do
  55 + "(inspect (1 3))".should evaluate_to("#<List [1, 3]>")
  56 + end
  57 + end
  58 +
  59 + describe "#define" do
  60 + it 'defines a variable' do
  61 + expression = "(define number 3)"
  62 + lexer = Schemer::Lexer.new
  63 + parser = Schemer::Parser.new
  64 + ast = parser.apply(lexer.parse expression)
  65 +
  66 + interpreter = Schemer::Interpreter.new(ast)
  67 + interpreter.walk.should be_nil
  68 + interpreter.env.get_binding(:number).should == 3
  69 + end
  70 + it 'defines a variable from an expression' do
  71 + expression = "(define number (* 3 2))"
  72 + lexer = Schemer::Lexer.new
  73 + parser = Schemer::Parser.new
  74 + ast = parser.apply(lexer.parse expression)
  75 +
  76 + interpreter = Schemer::Interpreter.new(ast)
  77 + interpreter.walk.should be_nil
  78 + interpreter.env.get_binding(:number).should == 6
  79 + end
  80 + it 'defines a function' do
  81 + "(define (square x) (* x x))(square 5)".should evaluate_to(25)
  82 + end
  83 + it 'defines a function (edge case)' do
  84 + expression = "(define (make-list x) (list 3 x))(make-list 5)"
  85 + lexer = Schemer::Lexer.new
  86 + parser = Schemer::Parser.new
  87 + ast = parser.apply(lexer.parse expression)
  88 +
  89 + puts ast.inspect
  90 +
  91 + interpreter = Schemer::Interpreter.new(ast)
  92 + result = interpreter.walk
  93 + result.should be_an(AST::List)
  94 + result.elements.first.should eq(3)
  95 + result.elements.last.should eq(5)
  96 + end
  97 + it 'defines a function from a lambda' do
  98 + "(define square (lambda (x) (* x x)))(square 5)".should evaluate_to(25)
  99 + end
  100 + end
  101 +
  102 + describe "#car" do
  103 + it 'returns the first element from a list' do
  104 + "(car (8 3))".should evaluate_to(8)
  105 + end
  106 + end
  107 +
  108 + describe "#cdr" do
  109 + it 'returns the last element from a list' do
  110 + "(cdr (9 3))".should evaluate_to(3)
  111 + end
  112 + end
  113 +
  114 + describe "#cadr" do
  115 + it 'returns the first element from the last element of a list' do
  116 + "(cadr (1 (3 2)))".should evaluate_to(3)
  117 + end
  118 + end
  119 +
  120 + describe "#caddr" do
  121 + it 'returns the first element from the last element of the last element of a list' do
  122 + "(caddr (8 (3 (1 2))))".should evaluate_to(1)
  123 + end
  124 + end
  125 +
  126 + describe "#list" do
  127 + it 'converts elements to a list' do
  128 + expression = "(list 3 4 x y)"
  129 + lexer = Schemer::Lexer.new
  130 + parser = Schemer::Parser.new
  131 + ast = parser.apply(lexer.parse expression)
  132 +
  133 + interpreter = Schemer::Interpreter.new(ast)
  134 + output = interpreter.walk
  135 + output.should be_a(AST::List)
  136 + output.should have(4).elements
  137 + end
  138 + end
  139 +
  140 + describe "#null?" do
  141 + context 'if the object is nil' do
  142 + it 'returns true' do
  143 + "(null? 3)".should evaluate_to(false)
  144 + end
  145 + end
  146 + context 'if the object is empty or nil' do
  147 + it 'returns true' do
  148 + "(null? ())".should evaluate_to(true)
  149 + end
  150 + end
  151 + end
  152 +
  153 + describe "#=" do
  154 + it 'returns the equality of two arguments' do
  155 + "(= 3 (+ 1 2))".should evaluate_to(true)
  156 + end
  157 + end
  158 +
  159 + describe "#eqv?" do
  160 + it 'returns the equivalenc of two arguments' do
  161 + "(eqv? 3 (+ 1 2))".should evaluate_to(true)
  162 + end
  163 + end
  164 +
  165 + describe "#<" do
  166 + it 'returns true if foo is less than bar' do
  167 + "(< 3 (+ 2 2))".should evaluate_to(true)
  168 + end
  169 + end
  170 +
  171 + describe "#>" do
  172 + it 'returns true if foo is more than bar' do
  173 + "(> 3 (+ 2 2))".should evaluate_to(false)
  174 + end
  175 + end
  176 +
  177 + describe "#cond" do
  178 + it 'returns the first condition that evaluates to true' do
  179 + "(cond ((< 3 1) 9) ((>3 1) 7) )".should evaluate_to(7)
  180 + end
  181 + it 'even if the result is an expression' do
  182 + "(cond ((< 3 1) 9) ((>3 8) 7) ((= 3 3) (+ 3 9)) )".should evaluate_to(12)
  183 + end
  184 + end
  185 +
  186 + end
  187 +
  188 + pending 'parses bintree.scm' do
271 189 file = File.read('examples/bintree.scm')
272 190 lexer = Schemer::Lexer.new
273 191 parser = Schemer::Parser.new
274 192 tokens = lexer.parse(file)
275   - pp tokens
276 193 ast = parser.apply(tokens)
277 194
278 195 puts ast.delete_if{|n| n.is_a?(AST::Comment)}.inspect
2  spec/schemer/lexer_spec.rb
@@ -57,7 +57,7 @@ module Schemer
57 57 its(:list) { should parse('((lambda) (bar))') }
58 58 its(:list) { should parse('(lambda (define zara \'zara) (write (eqv? zara \'zara)))') }
59 59 its(:list) { should(parse("(lambda (define (make-new-set?) '()) (define (make-new-set?) '(2 3)))").as do |output|
60   - output[:list].should have(2).lists
  60 + output[:list].should have(3).elements
61 61 end) }
62 62
63 63 # Regression tests

0 comments on commit 61da2b0

Please sign in to comment.
Something went wrong with that request. Please try again.