mini_kraken 0.2.03 → 0.3.03

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.
Files changed (136) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +378 -333
  3. data/CHANGELOG.md +48 -0
  4. data/README.md +29 -21
  5. data/lib/mini_kraken/atomic/all_atomic.rb +5 -0
  6. data/lib/mini_kraken/atomic/atomic_term.rb +96 -0
  7. data/lib/mini_kraken/atomic/k_boolean.rb +42 -0
  8. data/lib/mini_kraken/{core → atomic}/k_integer.rb +2 -5
  9. data/lib/mini_kraken/atomic/k_string.rb +17 -0
  10. data/lib/mini_kraken/{core → atomic}/k_symbol.rb +4 -8
  11. data/lib/mini_kraken/composite/all_composite.rb +4 -0
  12. data/lib/mini_kraken/composite/composite_term.rb +27 -0
  13. data/lib/mini_kraken/composite/cons_cell.rb +301 -0
  14. data/lib/mini_kraken/composite/cons_cell_visitor.rb +50 -0
  15. data/lib/mini_kraken/composite/list.rb +32 -0
  16. data/lib/mini_kraken/core/all_core.rb +8 -0
  17. data/lib/mini_kraken/core/any_value.rb +31 -7
  18. data/lib/mini_kraken/core/arity.rb +69 -0
  19. data/lib/mini_kraken/core/association.rb +29 -4
  20. data/lib/mini_kraken/core/association_copy.rb +50 -0
  21. data/lib/mini_kraken/core/base_term.rb +13 -0
  22. data/lib/mini_kraken/core/blackboard.rb +315 -0
  23. data/lib/mini_kraken/core/bookmark.rb +46 -0
  24. data/lib/mini_kraken/core/context.rb +492 -0
  25. data/lib/mini_kraken/core/duck_fiber.rb +21 -19
  26. data/lib/mini_kraken/core/entry.rb +40 -0
  27. data/lib/mini_kraken/core/fail.rb +20 -18
  28. data/lib/mini_kraken/core/fusion.rb +29 -0
  29. data/lib/mini_kraken/core/goal.rb +20 -29
  30. data/lib/mini_kraken/core/log_var.rb +22 -0
  31. data/lib/mini_kraken/core/log_var_ref.rb +108 -0
  32. data/lib/mini_kraken/core/nullary_relation.rb +2 -9
  33. data/lib/mini_kraken/core/parametrized_term.rb +68 -0
  34. data/lib/mini_kraken/core/relation.rb +14 -28
  35. data/lib/mini_kraken/core/scope.rb +67 -0
  36. data/lib/mini_kraken/core/solver_adapter.rb +58 -0
  37. data/lib/mini_kraken/core/specification.rb +48 -0
  38. data/lib/mini_kraken/core/succeed.rb +21 -17
  39. data/lib/mini_kraken/core/symbol_table.rb +137 -0
  40. data/lib/mini_kraken/core/term.rb +15 -4
  41. data/lib/mini_kraken/glue/dsl.rb +44 -88
  42. data/lib/mini_kraken/glue/run_star_expression.rb +28 -30
  43. data/lib/mini_kraken/rela/all_rela.rb +8 -0
  44. data/lib/mini_kraken/rela/binary_relation.rb +30 -0
  45. data/lib/mini_kraken/rela/conde.rb +143 -0
  46. data/lib/mini_kraken/rela/conj2.rb +65 -0
  47. data/lib/mini_kraken/rela/def_relation.rb +93 -0
  48. data/lib/mini_kraken/rela/disj2.rb +70 -0
  49. data/lib/mini_kraken/rela/fresh.rb +98 -0
  50. data/lib/mini_kraken/{core → rela}/goal_relation.rb +7 -9
  51. data/lib/mini_kraken/rela/unify.rb +265 -0
  52. data/lib/mini_kraken/version.rb +1 -1
  53. data/mini_kraken.gemspec +2 -2
  54. data/spec/.rubocop.yml +1 -1
  55. data/spec/atomic/atomic_term_spec.rb +98 -0
  56. data/spec/{core → atomic}/k_boolean_spec.rb +19 -34
  57. data/spec/{core → atomic}/k_symbol_spec.rb +3 -16
  58. data/spec/composite/cons_cell_spec.rb +225 -0
  59. data/spec/{core → composite}/cons_cell_visitor_spec.rb +36 -20
  60. data/spec/composite/list_spec.rb +50 -0
  61. data/spec/core/any_value_spec.rb +52 -0
  62. data/spec/core/arity_spec.rb +92 -0
  63. data/spec/core/association_copy_spec.rb +69 -0
  64. data/spec/core/association_spec.rb +31 -4
  65. data/spec/core/blackboard_spec.rb +287 -0
  66. data/spec/core/bookmark_spec.rb +40 -0
  67. data/spec/core/context_spec.rb +245 -0
  68. data/spec/core/core_spec.rb +40 -0
  69. data/spec/core/duck_fiber_spec.rb +16 -46
  70. data/spec/core/fail_spec.rb +5 -6
  71. data/spec/core/goal_spec.rb +22 -12
  72. data/spec/core/log_var_ref_spec.rb +105 -0
  73. data/spec/core/log_var_spec.rb +64 -0
  74. data/spec/core/nullary_relation_spec.rb +33 -0
  75. data/spec/core/parametrized_tem_spec.rb +39 -0
  76. data/spec/core/relation_spec.rb +33 -0
  77. data/spec/core/scope_spec.rb +73 -0
  78. data/spec/core/solver_adapter_spec.rb +70 -0
  79. data/spec/core/specification_spec.rb +43 -0
  80. data/spec/core/succeed_spec.rb +5 -5
  81. data/spec/core/symbol_table_spec.rb +142 -0
  82. data/spec/glue/dsl_chap1_spec.rb +88 -144
  83. data/spec/glue/dsl_chap2_spec.rb +454 -19
  84. data/spec/glue/run_star_expression_spec.rb +81 -906
  85. data/spec/rela/conde_spec.rb +153 -0
  86. data/spec/rela/conj2_spec.rb +123 -0
  87. data/spec/rela/def_relation_spec.rb +119 -0
  88. data/spec/rela/disj2_spec.rb +117 -0
  89. data/spec/rela/fresh_spec.rb +147 -0
  90. data/spec/rela/unify_spec.rb +369 -0
  91. data/spec/support/factory_atomic.rb +29 -0
  92. data/spec/support/factory_composite.rb +21 -0
  93. data/spec/support/factory_methods.rb +11 -26
  94. metadata +98 -70
  95. data/lib/mini_kraken/core/association_walker.rb +0 -183
  96. data/lib/mini_kraken/core/atomic_term.rb +0 -67
  97. data/lib/mini_kraken/core/base_arg.rb +0 -10
  98. data/lib/mini_kraken/core/binary_relation.rb +0 -63
  99. data/lib/mini_kraken/core/composite_goal.rb +0 -46
  100. data/lib/mini_kraken/core/composite_term.rb +0 -41
  101. data/lib/mini_kraken/core/conde.rb +0 -143
  102. data/lib/mini_kraken/core/conj2.rb +0 -79
  103. data/lib/mini_kraken/core/cons_cell.rb +0 -82
  104. data/lib/mini_kraken/core/cons_cell_visitor.rb +0 -102
  105. data/lib/mini_kraken/core/def_relation.rb +0 -53
  106. data/lib/mini_kraken/core/designation.rb +0 -55
  107. data/lib/mini_kraken/core/disj2.rb +0 -72
  108. data/lib/mini_kraken/core/environment.rb +0 -73
  109. data/lib/mini_kraken/core/equals.rb +0 -193
  110. data/lib/mini_kraken/core/formal_arg.rb +0 -22
  111. data/lib/mini_kraken/core/formal_ref.rb +0 -25
  112. data/lib/mini_kraken/core/freshness.rb +0 -45
  113. data/lib/mini_kraken/core/goal_arg.rb +0 -12
  114. data/lib/mini_kraken/core/goal_template.rb +0 -102
  115. data/lib/mini_kraken/core/k_boolean.rb +0 -35
  116. data/lib/mini_kraken/core/outcome.rb +0 -63
  117. data/lib/mini_kraken/core/variable.rb +0 -41
  118. data/lib/mini_kraken/core/variable_ref.rb +0 -84
  119. data/lib/mini_kraken/core/vocabulary.rb +0 -446
  120. data/lib/mini_kraken/glue/fresh_env.rb +0 -103
  121. data/lib/mini_kraken/glue/fresh_env_factory.rb +0 -83
  122. data/spec/core/association_walker_spec.rb +0 -192
  123. data/spec/core/conde_spec.rb +0 -147
  124. data/spec/core/conj2_spec.rb +0 -114
  125. data/spec/core/cons_cell_spec.rb +0 -107
  126. data/spec/core/def_relation_spec.rb +0 -97
  127. data/spec/core/disj2_spec.rb +0 -99
  128. data/spec/core/environment_spec.rb +0 -142
  129. data/spec/core/equals_spec.rb +0 -317
  130. data/spec/core/goal_template_spec.rb +0 -74
  131. data/spec/core/outcome_spec.rb +0 -56
  132. data/spec/core/variable_ref_spec.rb +0 -30
  133. data/spec/core/variable_spec.rb +0 -35
  134. data/spec/core/vocabulary_spec.rb +0 -219
  135. data/spec/glue/fresh_env_factory_spec.rb +0 -97
  136. data/spec/glue/fresh_env_spec.rb +0 -62
@@ -0,0 +1,147 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../spec_helper' # Use the RSpec framework
4
+ require_relative '../../lib/mini_kraken/core/all_core'
5
+
6
+ require_relative '../support/factory_atomic'
7
+ require_relative '../support/factory_composite'
8
+ require_relative '../../lib/mini_kraken/rela/all_rela'
9
+
10
+ # Load the class under test
11
+ require_relative '../../lib/mini_kraken/rela/fresh'
12
+
13
+ module MiniKraken
14
+ module Rela
15
+ describe Fresh do
16
+ include MiniKraken::FactoryAtomic # Use mix-in module
17
+ include MiniKraken::FactoryComposite # Use mix-in module
18
+ subject { Fresh.instance }
19
+
20
+
21
+ def var(aName)
22
+ Core::LogVar.new(aName)
23
+ end
24
+
25
+ # Convenience method to factor out repeated statements
26
+ def solve(arg1, arg2)
27
+ solver = subject.solver_for([arg1, arg2], ctx)
28
+ solver.resume
29
+ end
30
+
31
+ context 'Initialization:' do
32
+ it 'should know its relation name' do
33
+ expect(subject.name).to eq('fresh')
34
+ end
35
+
36
+ it 'should know its arity (binary)' do
37
+ expect(subject.arity).to be_binary
38
+ end
39
+
40
+ it 'should be frozen' do
41
+ expect(subject).to be_frozen
42
+ end
43
+ end # context
44
+
45
+ context 'Provided services:' do
46
+ let(:ctx) { Core::Context.new }
47
+ let(:bean) { k_symbol(:bean) }
48
+ let(:pea) { k_symbol(:pea) }
49
+ let(:corn) { k_symbol(:corn) }
50
+ let(:meal) { k_symbol(:meal) }
51
+ let(:red) { k_symbol(:red) }
52
+ let(:soup) { k_symbol(:soup) }
53
+ let(:split) { k_symbol(:split) }
54
+ let(:fails) { Core::Goal.new(Core::Fail.instance, []) }
55
+ let(:succeeds) { Core::Goal.new(Core::Succeed.instance, []) }
56
+ let(:var_q) { var('q') }
57
+ let(:ref_q) { Core::LogVarRef.new('q') }
58
+ let(:ref_r) { Core::LogVarRef.new('r') }
59
+ let(:ref_x) { Core::LogVarRef.new('x') }
60
+ let(:ref_y) { Core::LogVarRef.new('y') }
61
+
62
+ def unify(term1, term2)
63
+ Core::Goal.new(Unify.instance, [term1, term2])
64
+ end
65
+
66
+ def conj2(term1, term2)
67
+ Core::Goal.new(Conj2.instance, [term1, term2])
68
+ end
69
+
70
+ def disj2(term1, term2)
71
+ Core::Goal.new(Disj2.instance, [term1, term2])
72
+ end
73
+
74
+ before(:each) { ctx.add_vars('q') }
75
+
76
+ it 'should create a solver' do
77
+ subgoal = unify(ref_x, ref_q)
78
+ fresh_goal = Core::Goal.new(subject, [k_string('x'), subgoal])
79
+ solver = subject.solver_for(fresh_goal.actuals, ctx)
80
+ expect(solver.resume(ctx)).to be_success
81
+ current_scope = ctx.symbol_table.current_scope
82
+ expect(current_scope.defns.include?('x')).to be_truthy
83
+ expect(current_scope.parent.defns.include?('q')).to be_truthy
84
+ fusion = ctx.blackboard.move_queue.last
85
+ expect(fusion).to be_kind_of(Core::Fusion)
86
+ expect(solver.resume(ctx)).to be_nil
87
+ end
88
+
89
+ it 'should allow the nesting of fresh goals' do
90
+ ref_y = Core::LogVarRef.new('y')
91
+ subsubgoal = unify(ref_y, ref_q)
92
+ fresh_subgoal = Core::Goal.new(subject, [k_string('y'), subsubgoal])
93
+ fresh_goal = Core::Goal.new(subject, [k_string('x'), fresh_subgoal])
94
+ solver = subject.solver_for(fresh_goal.actuals, ctx)
95
+ expect(solver.resume(ctx)).to be_success
96
+ current_scope = ctx.symbol_table.current_scope
97
+ expect(current_scope.defns.include?('y')).to be_truthy
98
+ expect(current_scope.parent.defns.include?('x')).to be_truthy
99
+ expect(current_scope.parent.parent.defns.include?('q')).to be_truthy
100
+ fusion = ctx.blackboard.move_queue.last
101
+ expect(fusion).to be_kind_of(Core::Fusion)
102
+ expect(solver.resume(ctx)).to be_nil
103
+ end
104
+
105
+ it 'should create a simple goal' do
106
+ # Covers frame 1:21
107
+ # (run* q (fresh (x) (== 'pea q))) ;; => (pea)
108
+ subgoal = unify(pea, ref_q)
109
+ goal = Fresh.build_goal('x', subgoal)
110
+
111
+ expect(goal).to be_kind_of(Core::Goal)
112
+ expect(goal.relation).to be_kind_of(Fresh)
113
+ expect(goal.actuals[0]).to eq('x') # Name of local variable
114
+ expect(goal.actuals[1]).to eq(subgoal)
115
+ solver = subject.solver_for(goal.actuals, ctx)
116
+ expect(solver.resume(ctx)).to be_success
117
+ result = ctx.build_solution
118
+ expect(result['q']).to eq(pea)
119
+ end
120
+
121
+ it 'should create a goal with conjuncted sub-goals' do
122
+ # Covers inner part of frame 1:78
123
+ # (fresh (x y)
124
+ # (disj2
125
+ # (conj2 (== 'split x) (== 'pea y))
126
+ # (conj2 (== 'red x) (== 'bean y)))
127
+ # (== '(,x ,y soup) r)))
128
+
129
+ subgoals = [disj2(
130
+ conj2(unify(split, ref_x), unify(pea, ref_y)),
131
+ conj2(unify(red, ref_x), unify(bean, ref_y))),
132
+ unify(cons(ref_x, cons(ref_y, cons(soup))), ref_r)]
133
+ goal = Fresh.build_goal(%w[x y], subgoals)
134
+ expect(goal).to be_kind_of(Core::Goal)
135
+ expect(goal.relation).to be_kind_of(Fresh)
136
+ expect(goal.actuals[0]).to eq(%w[x y]) # local variable names
137
+ expect(goal.actuals[1]).to be_kind_of(Core::Goal)
138
+
139
+ # Check that the created Conj2 is correct
140
+ expect(goal.actuals[1].relation).to eq(Conj2.instance)
141
+ expect(goal.actuals[1].actuals[0]).to eq(subgoals[0])
142
+ expect(goal.actuals[1].actuals[1]).to eq(subgoals[1])
143
+ end
144
+ end # context
145
+ end # describe
146
+ end # module
147
+ end # module
@@ -0,0 +1,369 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../spec_helper' # Use the RSpec framework
4
+ require_relative '../../lib/mini_kraken/core/log_var'
5
+ require_relative '../../lib/mini_kraken/core/log_var_ref'
6
+
7
+ require_relative '../support/factory_atomic'
8
+ require_relative '../support/factory_composite'
9
+
10
+ # Load the class under test
11
+ require_relative '../../lib/mini_kraken/rela/unify'
12
+
13
+ module MiniKraken
14
+ module Rela
15
+ describe Unify do
16
+ include MiniKraken::FactoryAtomic # Use mix-in module
17
+ include MiniKraken::FactoryComposite # Use mix-in module
18
+
19
+ let(:pea) { k_symbol(:pea) }
20
+ let(:pea2) { k_symbol(:pea) } # Same value but distinct object
21
+ let(:pod) { k_symbol(:pod) }
22
+ let(:null) { cons(nil, nil) }
23
+ let(:q_ref) { Core::LogVarRef.new('q') }
24
+ let(:ctx) { Core::Context.new }
25
+ subject { Unify.instance }
26
+
27
+ def var(aName)
28
+ Core::LogVar.new(aName)
29
+ end
30
+
31
+ # Convenience method to factor out repeated statements
32
+ def solve(arg1, arg2)
33
+ solver = subject.solver_for([arg1, arg2], ctx)
34
+ solver.resume(ctx)
35
+ end
36
+
37
+ before(:each) do
38
+ ctx.insert(var('q'))
39
+ end
40
+
41
+ context 'Initialization:' do
42
+ it 'should know its relation name' do
43
+ expect(subject.name).to eq('unify')
44
+ end
45
+
46
+ it 'should know its arity (binary)' do
47
+ expect(subject.arity).to be_binary
48
+ end
49
+
50
+ it 'should be frozen' do
51
+ expect(subject).to be_frozen
52
+ end
53
+ end # context
54
+
55
+ context 'Unifying identical or nil terms:' do
56
+ it 'should succeed for identical arguments' do
57
+ term = double('anything')
58
+ result = solve(term, term)
59
+
60
+ expect(result).to be_kind_of(Core::Context)
61
+ expect(result).to be_success
62
+ expect(result.blackboard).to be_empty
63
+ end
64
+
65
+ it 'should succeed for two nil arguments' do
66
+ result = solve(nil, nil)
67
+ expect(result).to be_success
68
+ expect(result.blackboard).to be_empty
69
+ end
70
+
71
+ it 'should fail for one nil and one non-nil argument' do
72
+ term = double('anything')
73
+ result = solve(term, nil)
74
+ expect(result).to be_failure
75
+
76
+ # Check symmetry
77
+ result = solve(nil, term)
78
+ expect(result).to be_failure
79
+ end
80
+ end # context
81
+
82
+ context 'Unifying atomic terms:' do
83
+ it 'should unify symbols of same value' do
84
+ # Identical literals
85
+ result = solve(pea, pea)
86
+ expect(result).to be_success
87
+
88
+ pea_bis = k_symbol(:pea)
89
+ result = solve(pea, pea_bis)
90
+ expect(result).to be_success
91
+ expect(result.blackboard).to be_empty
92
+
93
+ true_a = k_boolean(false)
94
+ true_b = k_boolean(false)
95
+ result = solve(true_a, true_b)
96
+ expect(result).to be_success
97
+ expect(result.blackboard).to be_empty
98
+ end
99
+
100
+ it 'should fail for atomic terms with different values' do
101
+ result = solve(pea, pod)
102
+ expect(result).to be_failure
103
+
104
+ true_t = k_boolean(true)
105
+ false_t = k_boolean(false)
106
+ result = solve(true_t, false_t)
107
+ expect(result).to be_failure
108
+ end
109
+ end # context
110
+
111
+ context 'Unifying composite with atomic term:' do
112
+ it 'should fail to unify a composite to an atomic term' do
113
+ pair = cons(double('car-fake'), double('cdr-fake'))
114
+
115
+ result = solve(pair, pea)
116
+ expect(result).to be_failure
117
+ expect(result.blackboard).to be_empty
118
+
119
+ # Check symmetry
120
+ result = solve(pea, pair)
121
+ expect(result).to be_failure
122
+ expect(result.blackboard).to be_empty
123
+ end
124
+ end # context
125
+
126
+ context 'Unifying null list with a term:' do
127
+ it 'should unify two null lists' do
128
+ null2 = cons(nil, nil)
129
+
130
+ result = solve(null, null2)
131
+ expect(result).to be_success
132
+ expect(result.blackboard).to be_empty
133
+ end
134
+
135
+ it "shouldn't unify a null list with any non null composite" do
136
+ # Fail to unify null with a non-null list
137
+ list1 = make_list(double('dummy'))
138
+ result = solve(null, list1)
139
+
140
+ expect(result).to be_failure
141
+
142
+ # Check symmetry
143
+ result = solve(list1, null)
144
+ expect(result).to be_failure
145
+ end
146
+
147
+ it 'should unify null list with fresh variable' do
148
+ result = solve(null, q_ref)
149
+
150
+ expect(result).to be_success
151
+ expect(result.blackboard).not_to be_empty
152
+ expect(result.associations_for('q').first.value).to eq(null)
153
+ end
154
+
155
+ it 'should unify null list with variable bound to null list' do
156
+ solve(null, q_ref) # q is bound to null list
157
+
158
+ # Attempting to unify again null list with same variable is OK
159
+ result = solve(null, q_ref)
160
+ expect(result).to be_success
161
+
162
+ # Success, but no redundant association is created...
163
+ expect(result.blackboard.move_queue.size).to eq(1)
164
+ end
165
+
166
+ it "shouldn't unify a null list with a variable bound to atomic" do
167
+ ctx.associate('q', pea)
168
+ result = solve(null, q_ref)
169
+ expect(result).to be_failure
170
+ end
171
+
172
+ it "shouldn't unify a null list with a variable bound to composite" do
173
+ ctx.associate('q', cons(pea))
174
+ result = solve(null, q_ref)
175
+ expect(result).to be_failure
176
+ end
177
+ end # context
178
+
179
+ context 'Unifying two non-null composite terms:' do
180
+ it 'should unify two one-element lists with same atomic terms' do
181
+ list_pea = cons(pea)
182
+ list_pea2 = cons(pea2)
183
+ result = solve(list_pea, list_pea2)
184
+
185
+ expect(result).to be_success
186
+ expect(result.blackboard).to be_empty
187
+ end
188
+
189
+ it "shoudn't unify two one-element lists with unequal atomic terms" do
190
+ list_pea = cons(pea)
191
+ list_pod = cons(pod)
192
+ result = solve(list_pea, list_pod)
193
+
194
+ expect(result).to be_failure
195
+ end
196
+
197
+ it 'should unify two pairs with same atomic terms' do
198
+ pair1 = cons(pea, pea)
199
+ pair2 = cons(pea, pea2)
200
+ result = solve(pair1, pair2)
201
+
202
+ expect(result).to be_success
203
+ expect(result.blackboard).to be_empty
204
+ end
205
+
206
+ it "shoudn't unify two pairs with unequal atomic terms" do
207
+ pair_a = cons(pea, pea)
208
+ pair_b = cons(pea, pod)
209
+ result = solve(pair_a, pair_b)
210
+
211
+ expect(result).to be_failure
212
+ end
213
+
214
+ it 'should unify two element lists with same atomic terms' do
215
+ # Two element lists
216
+ list_a = make_list(pea, pea)
217
+ list_b = make_list(pea, pea2)
218
+ result = solve(list_a, list_b)
219
+
220
+ expect(result).to be_success
221
+ expect(result.blackboard).to be_empty
222
+ end
223
+
224
+ it "shoudn't unify two element lists with with unequal atomic terms" do
225
+ # Two element lists
226
+ list_a = make_list(pea, pea)
227
+ list_b = make_list(pea, pod)
228
+ result = solve(list_a, list_b)
229
+
230
+ expect(result).to be_failure
231
+ end
232
+
233
+ it 'should unify composites with one fresh variable' do
234
+ list_a = make_list(pea, pod)
235
+ list_b = make_list(pea, q_ref)
236
+ result = solve(list_a, list_b)
237
+
238
+ expect(result).to be_success
239
+ expect(result.blackboard.move_queue.size).to eq(1)
240
+ expect(ctx.associations_for('q').first.value).to eq(pod)
241
+ end
242
+
243
+ it 'should unify composites with redundant unification' do
244
+ list_a = make_list(pea, pea2, pod)
245
+
246
+ # Twist: q is paired twice to :pea, which is OK
247
+ list_b = make_list(q_ref, q_ref, pod)
248
+ result = solve(list_a, list_b)
249
+
250
+ expect(result).to be_success
251
+
252
+ # Only one association is created...
253
+ expect(result.blackboard.move_queue.size).to eq(1)
254
+ expect(ctx.associations_for('q').first.value).to eq(pea)
255
+ end
256
+
257
+ it 'should unify composites with same variables at same positions' do
258
+ # Case: q is a fresh variable
259
+ q_ref2 = Core::LogVarRef.new('q') # Other ref to same variable
260
+ list_a = make_list(pea, pod, q_ref)
261
+ list_b = make_list(pea, pod, q_ref2)
262
+ result = solve(list_a, list_b)
263
+
264
+ expect(result).to be_success
265
+ expect(result.blackboard).to be_empty # No association created
266
+
267
+ # Case: q is a bound variable
268
+ ctx.associate('q', pea)
269
+ expect(ctx.blackboard.move_queue.size).to eq(1)
270
+ result = solve(list_a, list_b)
271
+
272
+ expect(result).to be_success
273
+ expect(ctx.blackboard.move_queue.size).to eq(1) # No new association
274
+ end
275
+ end # context
276
+
277
+
278
+ context 'Unifying variable with atomic term:' do
279
+ it 'should unify a fresh variable to an atomic term' do
280
+ result = solve(q_ref, pea)
281
+
282
+ expect(result).to be_success
283
+ expect(result.blackboard).not_to be_empty
284
+ expect(ctx.associations_for('q').size).to eq(1)
285
+ expect(ctx.associations_for('q').first.value).to eq(pea)
286
+ end
287
+
288
+ it 'should unify a left-handed bound variable to the same atomic t.' do
289
+ solve(q_ref, pea)
290
+ result = solve(q_ref, pea) # Try to associate with 'pea' again...
291
+ expect(result).to be_success
292
+
293
+ # But no redundant association is created...
294
+ expect(ctx.associations_for('q').size).to eq(1)
295
+ end
296
+
297
+ it 'should unify a right-handed bound variable to the same atomic t.' do
298
+ solve(pea, q_ref)
299
+ result = solve(pea, q_ref) # Try to associate with 'pea' again...
300
+ expect(result).to be_success
301
+
302
+ # No redundant association was added...
303
+ expect(ctx.associations_for('q').size).to eq(1)
304
+ end
305
+
306
+ it "shouldn't unify a variable bound to another atomic term" do
307
+ solve(q_ref, pea) # q will be bound to :pea
308
+
309
+ # Trying a second time with another value should fail...
310
+ result = solve(q_ref, pod)
311
+ expect(result).to be_failure # Side effect: associations are removed
312
+ expect(result.blackboard).to be_empty
313
+ end
314
+ end # context
315
+
316
+
317
+ context 'Unifying variable with composite term:' do
318
+ it 'should unify a fresh variable to a composite term' do
319
+ list = make_list(pea, pod)
320
+ result = solve(q_ref, list)
321
+
322
+ expect(result).to be_success
323
+ expect(result.blackboard).not_to be_empty
324
+ expect(ctx.associations_for('q').size).to eq(1)
325
+ expect(ctx.associations_for('q').first.value).to eq(list)
326
+ end
327
+
328
+ it 'should unify a bound variable again with equal composite' do
329
+ list_a = make_list(cons(pea, pod), pea2)
330
+ list_b = make_list(cons(pea2, pod), pea)
331
+ result = solve(q_ref, list_a)
332
+
333
+ expect(result).to be_success
334
+ expect(result.blackboard).not_to be_empty
335
+ expect(ctx.associations_for('q').first.value).to eq(list_a)
336
+
337
+ result = solve(q_ref, list_b)
338
+ expect(result).to be_success
339
+ expect(ctx.associations_for('q').size).to eq(1)
340
+ end
341
+
342
+ it "shouldn't unify a bound variable to something different" do
343
+ list_a = make_list(cons(pea, pod), pea2)
344
+ solve(q_ref, list_a)
345
+
346
+ list_b = make_list(cons(pod, pod), pea)
347
+ result = solve(q_ref, list_b)
348
+ expect(result).to be_failure
349
+ end
350
+ end # context
351
+
352
+ context 'Unifying variable with another one:' do
353
+ it 'should unify one left-handed fresh variable to a bound variable' do
354
+ liste = make_list(cons(pea, pod), pea2)
355
+ solve(q_ref, liste)
356
+
357
+ ctx.add_vars('x')
358
+ x_ref = Core::LogVarRef.new('x')
359
+ result = solve(x_ref, q_ref)
360
+ queue = result.blackboard.move_queue
361
+
362
+ expect(queue[-2]).to be_kind_of(Core::Fusion)
363
+ expect(queue[-1]).to be_kind_of(Core::AssociationCopy)
364
+ expect(queue[-1].value).to be_equal(liste)
365
+ end
366
+ end # context
367
+ end # describe
368
+ end # module
369
+ end # module