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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +7 -0
- data/README.md +1 -1
- data/lib/skeem/grammar.rb +4 -2
- data/lib/skeem/interpreter.rb +24 -9
- data/lib/skeem/primitive/primitive_builder.rb +18 -18
- data/lib/skeem/primitive/primitive_procedure.rb +32 -6
- data/lib/skeem/runtime.rb +34 -32
- data/lib/skeem/s_expr_builder.rb +9 -3
- data/lib/skeem/s_expr_nodes.rb +203 -188
- data/lib/skeem/skm_binding.rb +103 -0
- data/lib/skeem/skm_element.rb +20 -10
- data/lib/skeem/skm_frame.rb +137 -0
- data/lib/skeem/skm_pair.rb +2 -2
- data/lib/skeem/skm_procedure_exec.rb +31 -0
- data/lib/skeem/standard/base.skm +15 -1
- data/lib/skeem/tokenizer.rb +2 -1
- data/lib/skeem/version.rb +1 -1
- data/spec/skeem/interpreter_spec.rb +30 -16
- data/spec/skeem/lambda_spec.rb +114 -0
- data/spec/skeem/primitive/primitive_builder_spec.rb +16 -12
- data/spec/skeem/primitive/primitive_procedure_spec.rb +14 -21
- data/spec/skeem/runtime_spec.rb +14 -12
- data/spec/skeem/s_expr_nodes_spec.rb +5 -81
- data/spec/skeem/skm_frame_spec.rb +119 -0
- data/spec/skeem/skm_procedure_exec_spec.rb +63 -0
- data/spec/skeem/skm_unary_expression_spec.rb +2 -3
- metadata +11 -2
@@ -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
|
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
|
-
[
|
452
|
-
[
|
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
|
-
[
|
468
|
-
[
|
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
|
-
[
|
552
|
-
[
|
553
|
-
[
|
554
|
-
[
|
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
|
-
[
|
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
|
-
|
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,
|
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
|
-
|
111
|
-
expect(pproc.call(rtime,
|
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,
|
110
|
+
expect { pproc.call(rtime, []) }.to raise_error(err, ms1 + ms2)
|
118
111
|
|
119
|
-
too_much =
|
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
|
-
|
131
|
-
expect(pproc.call(rtime,
|
123
|
+
args = [SkmInteger.create(3), SkmInteger.create(5)]
|
124
|
+
expect(pproc.call(rtime, args)).to eq(8)
|
132
125
|
|
133
|
-
too_few =
|
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 =
|
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
|
-
|
151
|
-
expect(pproc.call(rtime,
|
143
|
+
args = [SkmInteger.create(3), SkmInteger.create(5)]
|
144
|
+
expect(pproc.call(rtime, args)).to eq(2)
|
152
145
|
|
153
|
-
no_arg =
|
146
|
+
no_arg = []
|
154
147
|
expect(pproc.call(rtime, no_arg)).to eq(0)
|
155
148
|
|
156
|
-
many =
|
149
|
+
many = ['foo', 'bar', 'quux']
|
157
150
|
expect( pproc.call(rtime, many)).to eq(3)
|
158
151
|
end
|
159
152
|
end # context
|
data/spec/skeem/runtime_spec.rb
CHANGED
@@ -9,12 +9,12 @@ module Skeem
|
|
9
9
|
describe Runtime do
|
10
10
|
include DatumDSL
|
11
11
|
|
12
|
-
let(:some_env) {
|
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(
|
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
|
-
|
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
|
-
|
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
|
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.
|
72
|
-
expect(subject.depth).to eq(
|
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(
|
83
|
-
expect(subject.depth).to
|
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
|
-
|
185
|
-
|
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
|