skeem 0.1.03 → 0.2.00

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.
@@ -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