skeem 0.1.03 → 0.2.00

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,114 @@
1
+ require_relative '../spec_helper' # Use the RSpec framework
2
+
3
+ # Load the class under test
4
+ require_relative '../../lib/skeem/interpreter'
5
+
6
+ module Skeem
7
+ describe 'The interpreter and compound procedures' do
8
+ subject do
9
+ # We load the interpreter with the primitive procedures only
10
+ Interpreter.new { |interp| interp.add_primitives(interp.runtime) }
11
+ end
12
+
13
+ let(:definition_set) do
14
+ source = <<-SKEEM
15
+ (define square
16
+ (lambda (x)
17
+ (* x x)))
18
+
19
+ (define sum-of-squares
20
+ (lambda (x y)
21
+ (+ (square x) (square y))))
22
+
23
+ (define f
24
+ (lambda (a)
25
+ (sum-of-squares (+ a 1) (* a 2))))
26
+ SKEEM
27
+ source
28
+ end
29
+
30
+ context 'Defining compound procedures:' do
31
+ it 'should accept the definition of simple procedure with arity 1' do
32
+ source = definition_set + "\n" + 'square'
33
+ result = subject.run(source)
34
+
35
+ square = result.last
36
+ expect(square).to be_kind_of(SkmLambda)
37
+ expect(square.arity).to eq(1)
38
+ expect(square.environment).to eq(subject.runtime.environment)
39
+ end
40
+
41
+ it 'should accept the definition of simple procedure with arity 2' do
42
+ source = definition_set + "\n" + 'sum-of-squares'
43
+ result = subject.run(source)
44
+
45
+ square = result.last
46
+ expect(square).to be_kind_of(SkmLambda)
47
+ expect(square.arity).to eq(2)
48
+ expect(square.environment).to eq(subject.runtime.environment)
49
+ end
50
+ end # context
51
+
52
+ context 'Calling compound procedures:' do
53
+ it 'should support the call to a simple procedure with arity 1' do
54
+ # Case 1: argument is a simple datum
55
+ subject.run(definition_set)
56
+ result = subject.run('(square 2)')
57
+ expect(result).to eq(4)
58
+
59
+ # Case 2: argument is a sub-expression
60
+ ptree = subject.parse('(square (+ 2 1))')
61
+ proc_call = ptree.root
62
+ expect(proc_call.evaluate(subject.runtime)).to eq(9)
63
+ end
64
+
65
+ it 'should support the call to a simple procedure with arity 2' do
66
+ source = definition_set + "\n" + '(sum-of-squares 3 4)'
67
+ result = subject.run(source)
68
+
69
+ expect(result.last).to eq(25)
70
+ end
71
+
72
+ it 'should support the call to a nested lambda procedure' do
73
+ source = definition_set + "\n" + '(f 5)'
74
+ result = subject.run(source)
75
+
76
+ expect(result.last).to eq(136)
77
+ end
78
+
79
+ it 'should accept calls to anonymous procedures' do
80
+ source = '((lambda (x) (+ x x)) 4)'
81
+ result = subject.run(source)
82
+ expect(result).to eq(8)
83
+ end
84
+
85
+ it 'should accept unary second-order lambdas' do
86
+ source = <<-SKEEM
87
+ (define add-with
88
+ (lambda (x) (lambda (y) (+ x y)))
89
+ )
90
+ (define add4 (add-with 4))
91
+ SKEEM
92
+ subject.run(source)
93
+ result = subject.run('(add4 3)')
94
+ expect(result).to eq(7)
95
+ end
96
+ end # context
97
+
98
+ context 'More advanced features:' do
99
+ subject { Interpreter.new }
100
+
101
+ it 'should implement binary second-order functions' do
102
+ source = <<-SKEEM
103
+ (define compose
104
+ (lambda (f g)
105
+ (lambda (x)
106
+ (f (g x)))))
107
+ SKEEM
108
+ result = subject.run(source)
109
+ result = subject.run('((compose list square) 5)')
110
+ expect(result.last).to eq(25)
111
+ end
112
+ end # context
113
+ end # describe
114
+ end # module
@@ -6,7 +6,10 @@ require_relative '../../../lib/skeem/interpreter'
6
6
  module Skeem
7
7
  module Primitive
8
8
  describe 'Testing primitive procedures' do
9
- subject { Interpreter.new }
9
+ subject do
10
+ # We load the interpreter with the primitive procedures only
11
+ Interpreter.new { |interp| interp.add_primitives(interp.runtime) }
12
+ end
10
13
 
11
14
  context 'Arithmetic operators:' do
12
15
  it 'should implement the set! form' do
@@ -16,6 +19,7 @@ module Skeem
16
19
  SKEEM
17
20
  result = subject.run(skeem1)
18
21
  expect(result.last).to eq(3) # x is bound to value 2
22
+
19
23
  skeem2 = <<-SKEEM
20
24
  (set! x 4)
21
25
  (+ x 1)
@@ -448,8 +452,8 @@ SKEEM
448
452
  ['(list? #f)', false],
449
453
  ['(list? 1)', false],
450
454
  ['(list? "bar")', false],
451
- ['(list? (list 1 2 3))', true],
452
- ['(list? (list))', true]
455
+ ["(list? '(1 2 3))", true],
456
+ ["(list? '())", true]
453
457
  ]
454
458
  checks.each do |(skeem_expr, expectation)|
455
459
  result = subject.run(skeem_expr)
@@ -464,8 +468,8 @@ SKEEM
464
468
  ['(null? 0)', false],
465
469
  ['(null? "bar")', false],
466
470
  ['(null? "")', false],
467
- ['(null? (list 1 2 3))', false],
468
- ['(list? (list))', true]
471
+ ["(null? '(1 2 3))", false],
472
+ ["(list? '())", true]
469
473
  ]
470
474
  checks.each do |(skeem_expr, expectation)|
471
475
  result = subject.run(skeem_expr)
@@ -548,10 +552,10 @@ SKEEM
548
552
 
549
553
  it 'should implement the length procedure' do
550
554
  checks = [
551
- ['(length (list))', 0],
552
- ['(length (list 1))', 1],
553
- ['(length (list 1 2))', 2],
554
- ['(length (list 1 2 3))', 3]
555
+ ["(length '())", 0],
556
+ ["(length '(1))", 1],
557
+ ["(length '(1 2))", 2],
558
+ ["(length '(1 2 3))", 3]
555
559
  ]
556
560
  checks.each do |(skeem_expr, expectation)|
557
561
  result = subject.run(skeem_expr)
@@ -597,7 +601,7 @@ SKEEM
597
601
  ['(vector? #f)', false],
598
602
  ['(vector? 1)', false],
599
603
  ['(vector? "bar")', false],
600
- ['(vector? (list 1 2 3))', false],
604
+ ["(vector? '(1 2 3))", false],
601
605
  ['(vector? #(1 #f "cool"))', true]
602
606
  ]
603
607
  checks.each do |(skeem_expr, expectation)|
@@ -680,7 +684,7 @@ SKEEM
680
684
  source = <<-SKEEM
681
685
  (define x 2)
682
686
  (define y 1)
683
- (assert (> x y))
687
+ (test-assert (> x y))
684
688
  SKEEM
685
689
  expect(subject.run(source).last).to eq(true)
686
690
  end
@@ -689,7 +693,7 @@ SKEEM
689
693
  source = <<-SKEEM
690
694
  (define x 1)
691
695
  (define y 2)
692
- (assert (> x y))
696
+ (test-assert (> x y))
693
697
  SKEEM
694
698
  err = StandardError
695
699
  msg = 'Error: assertion failed on line 3, column 4'
@@ -6,10 +6,6 @@ require_relative '../../../lib/skeem/primitive/primitive_procedure'
6
6
  module Skeem
7
7
  module Primitive
8
8
  describe PrimitiveProcedure do
9
- def call_proc(aName, args)
10
- ProcedureCall.new(nil, aName, args)
11
- end
12
-
13
9
  let(:nullary) { SkmArity.new(0, 0) }
14
10
  let(:unary) { SkmArity.new(1, 1) }
15
11
  let(:binary) { SkmArity.new(2, 2) }
@@ -93,30 +89,27 @@ module Skeem
93
89
  pproc = PrimitiveProcedure.new('newline', nullary, newline_code)
94
90
  rtime = double('fake-runtime')
95
91
 
96
- invokation = call_proc('newline', nil)
97
- expect(pproc.call(rtime, invokation)).to eq("\n")
92
+ expect(pproc.call(rtime, [])).to eq("\n")
98
93
 
99
- too_much = call_proc('newline', ['superfluous'])
100
94
  err = StandardError
101
95
  ms1 = 'Wrong number of arguments for #<Procedure newline>'
102
96
  ms2 = ' (required at least 0, got 1)'
103
- expect { pproc.call(rtime, too_much) }.to raise_error(err, ms1 + ms2)
97
+ expect { pproc.call(rtime, ['superfluous']) }.to raise_error(err, ms1 + ms2)
104
98
  end
105
99
 
106
100
  it 'should support Skeem unary procedure' do
107
101
  pproc = PrimitiveProcedure.new('cube', unary, cube)
108
102
  rtime = double('fake-runtime')
109
103
 
110
- invokation = call_proc('cube', [SkmInteger.create(3)])
111
- expect(pproc.call(rtime, invokation)).to eq(27)
104
+ args = [SkmInteger.create(3)]
105
+ expect(pproc.call(rtime, args)).to eq(27)
112
106
 
113
- too_few = call_proc('cube', nil)
114
107
  err = StandardError
115
108
  ms1 = 'Wrong number of arguments for #<Procedure cube>'
116
109
  ms2 = ' (required at least 1, got 0)'
117
- expect { pproc.call(rtime, too_few) }.to raise_error(err, ms1 + ms2)
110
+ expect { pproc.call(rtime, []) }.to raise_error(err, ms1 + ms2)
118
111
 
119
- too_much = call_proc('newline', ['foo', 'bar'])
112
+ too_much = ['foo', 'bar']
120
113
  err = StandardError
121
114
  ms1 = 'Wrong number of arguments for #<Procedure cube>'
122
115
  ms2 = ' (required at least 1, got 2)'
@@ -127,16 +120,16 @@ module Skeem
127
120
  pproc = PrimitiveProcedure.new('sum', binary, sum)
128
121
  rtime = double('fake-runtime')
129
122
 
130
- invokation = call_proc('sum', [SkmInteger.create(3), SkmInteger.create(5)])
131
- expect(pproc.call(rtime, invokation)).to eq(8)
123
+ args = [SkmInteger.create(3), SkmInteger.create(5)]
124
+ expect(pproc.call(rtime, args)).to eq(8)
132
125
 
133
- too_few = call_proc('sum', [SkmInteger.create(3)])
126
+ too_few = [SkmInteger.create(3)]
134
127
  err = StandardError
135
128
  ms1 = 'Wrong number of arguments for #<Procedure sum>'
136
129
  ms2 = ' (required at least 2, got 1)'
137
130
  expect { pproc.call(rtime, too_few) }.to raise_error(err, ms1 + ms2)
138
131
 
139
- too_much = call_proc('cube', ['foo', 'bar', 'quux'])
132
+ too_much = ['foo', 'bar', 'quux']
140
133
  err = StandardError
141
134
  ms1 = 'Wrong number of arguments for #<Procedure sum>'
142
135
  ms2 = ' (required at least 2, got 3)'
@@ -147,13 +140,13 @@ module Skeem
147
140
  pproc = PrimitiveProcedure.new('length', zero_or_more, length)
148
141
  rtime = double('fake-runtime')
149
142
 
150
- invokation = call_proc('length', [SkmInteger.create(3), SkmInteger.create(5)])
151
- expect(pproc.call(rtime, invokation)).to eq(2)
143
+ args = [SkmInteger.create(3), SkmInteger.create(5)]
144
+ expect(pproc.call(rtime, args)).to eq(2)
152
145
 
153
- no_arg = call_proc('sum', nil)
146
+ no_arg = []
154
147
  expect(pproc.call(rtime, no_arg)).to eq(0)
155
148
 
156
- many = call_proc('cube', ['foo', 'bar', 'quux'])
149
+ many = ['foo', 'bar', 'quux']
157
150
  expect( pproc.call(rtime, many)).to eq(3)
158
151
  end
159
152
  end # context
@@ -9,12 +9,12 @@ module Skeem
9
9
  describe Runtime do
10
10
  include DatumDSL
11
11
 
12
- let(:some_env) { Environment.new }
12
+ let(:some_env) { SkmFrame.new }
13
13
  subject { Runtime.new(some_env) }
14
14
 
15
15
  context 'Initialization:' do
16
16
  it 'should be initialized with an environment' do
17
- expect { Runtime.new(Environment.new) }.not_to raise_error
17
+ expect { Runtime.new(SkmFrame.new) }.not_to raise_error
18
18
  end
19
19
 
20
20
  it 'should know the environment' do
@@ -29,14 +29,16 @@ module Skeem
29
29
  context 'Provided services:' do
30
30
  it 'should add entries to the environment' do
31
31
  entry = double('dummy')
32
- subject.define('dummy', entry)
32
+ expect(entry).to receive(:bound!)
33
+ subject.add_binding('dummy', entry)
33
34
  expect(subject.environment.size).to eq(1)
34
35
  end
35
36
 
36
37
  it 'should know the keys in the environment' do
37
38
  expect(subject.include?('dummy')).to be_falsey
38
39
  entry = double('dummy')
39
- subject.define('dummy', entry)
40
+ expect(entry).to receive(:bound!)
41
+ subject.add_binding('dummy', entry)
40
42
  expect(subject.include?('dummy')).to be_truthy
41
43
  end
42
44
  end # context
@@ -63,24 +65,24 @@ module Skeem
63
65
 
64
66
  context 'Environment nesting:' do
65
67
  it 'should add nested environment' do
66
- expect(subject.depth).to be_zero
68
+ expect(subject.depth).to eq(1)
67
69
  env_before = subject.environment
68
70
  subject.nest
69
71
 
70
72
  expect(subject.environment).not_to eq(env_before)
71
- expect(subject.environment.outer).to eq(env_before)
72
- expect(subject.depth).to eq(1)
73
+ expect(subject.environment.parent).to eq(env_before)
74
+ expect(subject.depth).to eq(2)
73
75
  end
74
76
 
75
77
  it 'should remove nested environment' do
76
- expect(subject.depth).to be_zero
77
- subject.nest
78
- outer_before = subject.environment.outer
79
78
  expect(subject.depth).to eq(1)
79
+ subject.nest
80
+ parent_before = subject.environment.parent
81
+ expect(subject.depth).to eq(2)
80
82
 
81
83
  subject.unnest
82
- expect(subject.environment).to eq(outer_before)
83
- expect(subject.depth).to be_zero
84
+ expect(subject.environment).to eq(parent_before)
85
+ expect(subject.depth).to eq(1)
84
86
  end
85
87
  end # context
86
88
 
@@ -5,84 +5,6 @@ require_relative '../../lib/skeem/primitive/primitive_builder'
5
5
  require_relative '../../lib/skeem/s_expr_nodes' # Load the classes under test
6
6
 
7
7
  module Skeem
8
- describe SkmDefinition do
9
- let(:pos) { double('fake-position') }
10
- let(:sample_symbol) { SkmIdentifier.create('ten') }
11
- let(:sample_expr) { SkmInteger.create(10) }
12
-
13
- subject { SkmDefinition.new(pos, sample_symbol, sample_expr) }
14
-
15
- context 'Initialization:' do
16
- it 'should be initialized with a symbol and an expression' do
17
- expect{ SkmDefinition.new(pos, sample_symbol, sample_expr) }.not_to raise_error
18
- end
19
-
20
- it 'should know its variable' do
21
- expect(subject.variable).to eq(sample_symbol)
22
- end
23
-
24
- it 'should know its expression' do
25
- expect(subject.expression).to eq(sample_expr)
26
- end
27
- end # context
28
-
29
- context 'Provided services:' do
30
- include Primitive::PrimitiveBuilder
31
-
32
- let(:runtime) { Runtime.new(Environment.new) }
33
-
34
- it 'should create an entry when evaluating' do
35
- expect(runtime).not_to include(sample_symbol)
36
- subject.evaluate(runtime)
37
- expect(runtime).to include(sample_symbol)
38
- end
39
-
40
- it 'should optimize an entry that aliases a primitive proc' do
41
- add_primitives(runtime)
42
- identifier = SkmIdentifier.create('plus')
43
- proc_name = SkmIdentifier.create('+')
44
- var_ref = SkmVariableReference.new(nil, proc_name)
45
- instance = SkmDefinition.new(nil, identifier, var_ref) # (define plus +)
46
- expect(instance.expression).to eq(var_ref)
47
- instance.evaluate(runtime)
48
- # Optimization by getting rid of indirection
49
- expect(instance.expression).to be_kind_of(Primitive::PrimitiveProcedure)
50
- end
51
-
52
- it 'should optimize an entry that aliases a lambda proc' do
53
- # Let's define a (dummy) lambda
54
- formals = SkmFormals.new([], :fixed)
55
- dummy_body = { :defs => [], :sequence => [SkmInteger.create(3)]}
56
- lbd = SkmLambda.new(nil, formals, dummy_body)
57
- id = SkmIdentifier.create('some-lambda')
58
- def1 = SkmDefinition.new(nil, id, lbd)
59
- def1.evaluate(runtime)
60
-
61
- # Let's create an alias to the lambda
62
- other_name = SkmIdentifier.create('aliased-lambda')
63
- var_ref = SkmVariableReference.new(nil, id)
64
-
65
- # (define aliased-lambda some-lambda)
66
- def2 = SkmDefinition.new(nil, other_name, var_ref)
67
- expect(def2.expression).to eq(var_ref)
68
- def2.evaluate(runtime)
69
- # Optimization by getting rid of indirection
70
- expect(def2.expression).to eq(lbd)
71
- end
72
-
73
- it 'should quasiquote its variable and expression' do
74
- alter_ego = subject.quasiquote(runtime)
75
- expect(alter_ego).to eq(subject)
76
- end
77
-
78
- it 'should return its text representation' do
79
- txt1 = '<Skeem::SkmDefinition: <Skeem::SkmIdentifier: ten>,'
80
- txt2 = ' <Skeem::SkmInteger: 10>>'
81
- expect(subject.inspect).to eq(txt1 + txt2)
82
- end
83
- end # context
84
- end # describe
85
-
86
8
  describe ProcedureCall do
87
9
  let(:pos) { double('fake-position') }
88
10
  let(:operator) { SkmIdentifier.create('+') }
@@ -180,9 +102,11 @@ module Skeem
180
102
  it 'should return its text representation' do
181
103
  txt1 = '<Skeem::SkmLambda: @formals #<Double "fake-formals">, '
182
104
  txt2 = '@definitions #<Double "fake-definitions">, '
183
- txt3 = '@sequence #<Double "fake-sequence">>'
184
- expect(subject.inspect).to eq(txt1 + txt2 + txt3)
185
- end
105
+ txt3 = '@sequence #<Double "fake-sequence">>>'
106
+ # Remove "unpredictable" part of actual text
107
+ expectation = subject.inspect.gsub(/@object_id=[0-9a-z]+, /, '')
108
+ expect(expectation).to eq(txt1 + txt2 + txt3)
109
+ end
186
110
  end # context
187
111
  end # describe
188
112
  end # module
@@ -0,0 +1,119 @@
1
+ require_relative '../spec_helper' # Use the RSpec framework
2
+ require_relative '../../lib/skeem/datum_dsl'
3
+ require_relative '../../lib/skeem/skm_frame' # Load the class under test
4
+
5
+ module Skeem
6
+ describe SkmFrame do
7
+ include DatumDSL
8
+
9
+ let(:sample_env) { SkmFrame.new }
10
+ context 'Initialization:' do
11
+ it 'could be initialized without argument' do
12
+ expect { SkmFrame.new() }.not_to raise_error
13
+ end
14
+
15
+ it 'could be initialized with optional argument' do
16
+ expect { SkmFrame.new(sample_env) }.not_to raise_error
17
+ end
18
+
19
+ it 'should have no default bindings' do
20
+ expect(subject).to be_empty
21
+ end
22
+
23
+ it 'should have depth of zero or one' do
24
+ expect(subject.depth).to be_zero
25
+
26
+ instance = SkmFrame.new(sample_env)
27
+ expect(instance.depth).to eq(1)
28
+ end
29
+ end # context
30
+
31
+ context 'Provided services:' do
32
+ it 'should add binding' do
33
+ entry = double('original-dummy')
34
+ expect(entry).to receive(:bound!).with(subject)
35
+
36
+ subject.add_binding('dummy', entry)
37
+ expect(subject.size).to eq(1)
38
+ expect(subject.bindings['dummy']).not_to be_nil
39
+ expect(subject.bindings['dummy']).to eq(entry)
40
+
41
+ # A child frame may shadow a parent's variable
42
+ child = SkmFrame.new(subject)
43
+ entry2 = double('dummy')
44
+ expect(entry2).to receive(:bound!).with(child)
45
+ child.add_binding(identifier('dummy'), entry2)
46
+
47
+ expect(child.bindings['dummy']).to eq(entry2)
48
+ expect(subject.bindings['dummy']).to eq(entry)
49
+ end
50
+
51
+ it 'should update bindings' do
52
+ entry1 = double('dummy')
53
+ expect(entry1).to receive(:bound!).with(subject)
54
+
55
+ # Case 1: entry defined in this frame
56
+ subject.add_binding('dummy', entry1)
57
+ expect(subject.bindings['dummy']).to eq(entry1)
58
+
59
+ entry2 = double('still-dummy')
60
+ expect(entry2).to receive(:bound!).with(subject)
61
+ subject.update_binding(identifier('dummy'), entry2)
62
+ expect(subject.bindings['dummy']).to eq(entry2)
63
+
64
+ # Case 2: entry defined in parent frame
65
+ child = SkmFrame.new(subject)
66
+ entry3 = double('still-dummy')
67
+ expect(entry3).to receive(:bound!).with(subject)
68
+ child.update_binding('dummy', entry3)
69
+ expect(subject.bindings['dummy']).to eq(entry3)
70
+ end
71
+
72
+ it 'should retrieve entries' do
73
+ # Case 1: non-existing entry
74
+ expect(subject.fetch('dummy')).to be_nil
75
+
76
+ # Case 2: existing entry
77
+ entry = double('dummy')
78
+ expect(entry).to receive(:bound!).with(subject)
79
+ subject.add_binding('dummy', entry)
80
+ expect(subject.fetch('dummy')).to eq(entry)
81
+
82
+ # Case 3: entry defined in parent frame
83
+ child = SkmFrame.new(subject)
84
+ expect(child.fetch(identifier('dummy'))).to eq(entry)
85
+ end
86
+
87
+ it 'should know whether it is empty' do
88
+ # Case 1: no entry
89
+ expect(subject.empty?).to be_truthy
90
+
91
+ # Case 2: existing entry
92
+ entry = double('dummy')
93
+ expect(entry).to receive(:bound!).with(subject)
94
+ subject.add_binding('dummy', entry)
95
+ expect(subject.empty?).to be_falsey
96
+
97
+ # Case 3: entry defined in parent frame
98
+ nested = SkmFrame.new(subject)
99
+ expect(nested.empty?).to be_falsey
100
+ end
101
+
102
+ it 'should know the total number of bindings' do
103
+ # Case 1: non-existing entry
104
+ expect(subject.size).to be_zero
105
+
106
+ # Case 2: existing entry
107
+ entry = double('dummy')
108
+ expect(entry).to receive(:bound!).with(subject)
109
+ subject.add_binding('dummy', entry)
110
+ expect(subject.size).to eq(1)
111
+
112
+ # Case 3: entry defined in parent environment
113
+ nested = SkmFrame.new(subject)
114
+ expect(nested.size).to eq(1)
115
+ end
116
+ end # context
117
+
118
+ end # describe
119
+ end # module