skeem 0.1.03 → 0.2.00
Sign up to get free protection for your applications and to get access to all the features.
- 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
|