mini_kraken 0.2.02 → 0.3.02

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 (134) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +378 -333
  3. data/CHANGELOG.md +52 -0
  4. data/README.md +19 -19
  5. data/lib/mini_kraken.rb +0 -1
  6. data/lib/mini_kraken/atomic/all_atomic.rb +5 -0
  7. data/lib/mini_kraken/atomic/atomic_term.rb +96 -0
  8. data/lib/mini_kraken/atomic/k_boolean.rb +42 -0
  9. data/lib/mini_kraken/{core → atomic}/k_integer.rb +2 -5
  10. data/lib/mini_kraken/atomic/k_string.rb +17 -0
  11. data/lib/mini_kraken/{core → atomic}/k_symbol.rb +4 -8
  12. data/lib/mini_kraken/composite/all_composite.rb +4 -0
  13. data/lib/mini_kraken/composite/composite_term.rb +27 -0
  14. data/lib/mini_kraken/composite/cons_cell.rb +299 -0
  15. data/lib/mini_kraken/composite/cons_cell_visitor.rb +50 -0
  16. data/lib/mini_kraken/composite/list.rb +32 -0
  17. data/lib/mini_kraken/core/all_core.rb +8 -0
  18. data/lib/mini_kraken/core/any_value.rb +31 -7
  19. data/lib/mini_kraken/core/arity.rb +69 -0
  20. data/lib/mini_kraken/core/association.rb +29 -4
  21. data/lib/mini_kraken/core/association_copy.rb +50 -0
  22. data/lib/mini_kraken/core/base_term.rb +13 -0
  23. data/lib/mini_kraken/core/blackboard.rb +315 -0
  24. data/lib/mini_kraken/core/bookmark.rb +46 -0
  25. data/lib/mini_kraken/core/context.rb +492 -0
  26. data/lib/mini_kraken/core/duck_fiber.rb +21 -19
  27. data/lib/mini_kraken/core/entry.rb +40 -0
  28. data/lib/mini_kraken/core/fail.rb +20 -18
  29. data/lib/mini_kraken/core/fusion.rb +29 -0
  30. data/lib/mini_kraken/core/goal.rb +20 -29
  31. data/lib/mini_kraken/core/log_var.rb +22 -0
  32. data/lib/mini_kraken/core/log_var_ref.rb +108 -0
  33. data/lib/mini_kraken/core/nullary_relation.rb +2 -9
  34. data/lib/mini_kraken/core/parametrized_term.rb +61 -0
  35. data/lib/mini_kraken/core/relation.rb +14 -28
  36. data/lib/mini_kraken/core/scope.rb +67 -0
  37. data/lib/mini_kraken/core/solver_adapter.rb +58 -0
  38. data/lib/mini_kraken/core/specification.rb +48 -0
  39. data/lib/mini_kraken/core/succeed.rb +21 -17
  40. data/lib/mini_kraken/core/symbol_table.rb +137 -0
  41. data/lib/mini_kraken/core/term.rb +15 -4
  42. data/lib/mini_kraken/glue/dsl.rb +45 -81
  43. data/lib/mini_kraken/glue/run_star_expression.rb +28 -30
  44. data/lib/mini_kraken/rela/all_rela.rb +8 -0
  45. data/lib/mini_kraken/rela/binary_relation.rb +30 -0
  46. data/lib/mini_kraken/rela/conde.rb +143 -0
  47. data/lib/mini_kraken/rela/conj2.rb +65 -0
  48. data/lib/mini_kraken/rela/def_relation.rb +93 -0
  49. data/lib/mini_kraken/rela/disj2.rb +70 -0
  50. data/lib/mini_kraken/rela/fresh.rb +98 -0
  51. data/lib/mini_kraken/{core → rela}/goal_relation.rb +7 -9
  52. data/lib/mini_kraken/rela/unify.rb +258 -0
  53. data/lib/mini_kraken/version.rb +1 -1
  54. data/mini_kraken.gemspec +2 -2
  55. data/spec/.rubocop.yml +1 -1
  56. data/spec/atomic/atomic_term_spec.rb +98 -0
  57. data/spec/{core → atomic}/k_boolean_spec.rb +19 -34
  58. data/spec/{core → atomic}/k_symbol_spec.rb +3 -16
  59. data/spec/composite/cons_cell_spec.rb +225 -0
  60. data/spec/composite/cons_cell_visitor_spec.rb +158 -0
  61. data/spec/composite/list_spec.rb +50 -0
  62. data/spec/core/any_value_spec.rb +52 -0
  63. data/spec/core/arity_spec.rb +92 -0
  64. data/spec/core/association_copy_spec.rb +69 -0
  65. data/spec/core/association_spec.rb +31 -4
  66. data/spec/core/blackboard_spec.rb +287 -0
  67. data/spec/core/bookmark_spec.rb +40 -0
  68. data/spec/core/context_spec.rb +245 -0
  69. data/spec/core/core_spec.rb +40 -0
  70. data/spec/core/duck_fiber_spec.rb +16 -46
  71. data/spec/core/fail_spec.rb +5 -6
  72. data/spec/core/goal_spec.rb +24 -14
  73. data/spec/core/log_var_ref_spec.rb +105 -0
  74. data/spec/core/log_var_spec.rb +64 -0
  75. data/spec/core/nullary_relation_spec.rb +33 -0
  76. data/spec/core/parametrized_tem_spec.rb +39 -0
  77. data/spec/core/relation_spec.rb +33 -0
  78. data/spec/core/scope_spec.rb +73 -0
  79. data/spec/core/solver_adapter_spec.rb +70 -0
  80. data/spec/core/specification_spec.rb +43 -0
  81. data/spec/core/succeed_spec.rb +5 -5
  82. data/spec/core/symbol_table_spec.rb +142 -0
  83. data/spec/glue/dsl_chap1_spec.rb +96 -144
  84. data/spec/glue/dsl_chap2_spec.rb +350 -0
  85. data/spec/glue/run_star_expression_spec.rb +82 -906
  86. data/spec/rela/conde_spec.rb +153 -0
  87. data/spec/rela/conj2_spec.rb +123 -0
  88. data/spec/rela/def_relation_spec.rb +119 -0
  89. data/spec/rela/disj2_spec.rb +117 -0
  90. data/spec/rela/fresh_spec.rb +147 -0
  91. data/spec/rela/unify_spec.rb +369 -0
  92. data/spec/support/factory_atomic.rb +29 -0
  93. data/spec/support/factory_composite.rb +21 -0
  94. data/spec/support/factory_methods.rb +11 -26
  95. metadata +100 -64
  96. data/lib/mini_kraken/core/association_walker.rb +0 -183
  97. data/lib/mini_kraken/core/atomic_term.rb +0 -67
  98. data/lib/mini_kraken/core/base_arg.rb +0 -10
  99. data/lib/mini_kraken/core/binary_relation.rb +0 -63
  100. data/lib/mini_kraken/core/composite_goal.rb +0 -46
  101. data/lib/mini_kraken/core/composite_term.rb +0 -41
  102. data/lib/mini_kraken/core/conde.rb +0 -143
  103. data/lib/mini_kraken/core/conj2.rb +0 -79
  104. data/lib/mini_kraken/core/cons_cell.rb +0 -82
  105. data/lib/mini_kraken/core/def_relation.rb +0 -50
  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 -156
  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 -62
  115. data/lib/mini_kraken/core/k_boolean.rb +0 -35
  116. data/lib/mini_kraken/core/outcome.rb +0 -53
  117. data/lib/mini_kraken/core/variable.rb +0 -41
  118. data/lib/mini_kraken/core/variable_ref.rb +0 -78
  119. data/lib/mini_kraken/core/vocabulary.rb +0 -442
  120. data/lib/mini_kraken/glue/fresh_env.rb +0 -75
  121. data/spec/core/association_walker_spec.rb +0 -192
  122. data/spec/core/conde_spec.rb +0 -147
  123. data/spec/core/conj2_spec.rb +0 -114
  124. data/spec/core/cons_cell_spec.rb +0 -107
  125. data/spec/core/def_relation_spec.rb +0 -96
  126. data/spec/core/disj2_spec.rb +0 -99
  127. data/spec/core/environment_spec.rb +0 -142
  128. data/spec/core/equals_spec.rb +0 -304
  129. data/spec/core/goal_template_spec.rb +0 -74
  130. data/spec/core/outcome_spec.rb +0 -48
  131. data/spec/core/variable_ref_spec.rb +0 -27
  132. data/spec/core/variable_spec.rb +0 -35
  133. data/spec/core/vocabulary_spec.rb +0 -219
  134. data/spec/glue/fresh_env_spec.rb +0 -62
@@ -0,0 +1,46 @@
1
+ # frozen_string_literal: true
2
+
3
+ module MiniKraken
4
+ module Core
5
+ # A bookmark is a placeholder for events of significance for
6
+ # manipulating the move queue of a blackboard.
7
+ # The events that involve bookmarks are:
8
+ # - enter_scope (when executing fresh expression)
9
+ # - leave_scope (when all solutions for given scope were found)
10
+ # - add_bt_point (when a backtrack point must be added)
11
+ # - remove_bt_point (when a backtrack point must be retracted)
12
+ # - next_alternative (when an alternative solution is searched)
13
+ # - fail! (when the current solution fails)
14
+ class Bookmark
15
+ # @return [Symbol] One of: :scope, :bt_point
16
+ attr_reader :kind
17
+
18
+ # @return [Integer] An unique serial number.
19
+ attr_reader :ser_num
20
+
21
+ # @param aKind [Symbol] must be one of: :scope, :bt_point
22
+ # @param aSerialNumber [Integer] a serial number
23
+ def initialize(aKind, aSerialNumber)
24
+ @kind = validated_kind(aKind)
25
+ @ser_num = aSerialNumber
26
+ end
27
+
28
+ # Equality comparison
29
+ # @param other [Bookmark, Object]
30
+ # return [Boolean]
31
+ def ==(other)
32
+ ser_num == other.ser_num
33
+ end
34
+
35
+ private
36
+
37
+ def validated_kind(aKind)
38
+ if aKind != :scope && aKind != :bt_point
39
+ raise StandardError, "Invalid kind: #{aKind}"
40
+ end
41
+
42
+ aKind
43
+ end
44
+ end # class
45
+ end # module
46
+ end # module
@@ -0,0 +1,492 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'securerandom'
4
+ require 'set'
5
+
6
+ require_relative 'any_value'
7
+ require_relative 'association'
8
+ require_relative 'blackboard'
9
+ require_relative 'fusion'
10
+ require_relative 'log_var'
11
+ require_relative 'symbol_table'
12
+ require_relative '../composite/all_composite'
13
+
14
+ module MiniKraken
15
+ module Core
16
+ # The data structure that provides the information required at runtime
17
+ # to determine a MiniKraken computation. One can think of the context
18
+ # object as a container of the symbol table and the blackboard.
19
+ # The symbol table keeps tracks of the different scopes involved in
20
+ # when MiniKraken is executing and the blackboard keeps the progress
21
+ # towards the achievement (or not) of the provided goals.
22
+ class Context
23
+ # An inverse mapping from a combining variable i_name to the fused
24
+ # variables i_name
25
+ # @return [Hash{String => Array<String>}]
26
+ attr_reader :cv2vars
27
+
28
+ # @return [Core::SymbolTable] The MiniKraken symbol table
29
+ attr_reader :symbol_table
30
+
31
+ # @return [Core::Blackboard] Holds variable bindings and backtrack points.
32
+ attr_reader :blackboard
33
+
34
+ # Variables that remain unbound in a solution, are given a rank number.
35
+ # This rank number is used when variable values must be displayed.
36
+ # Since an unbound variable can take any value, the special notation
37
+ # '_' + rank number is used to represent this state .
38
+ # The Reasoned Schemer book calls these variable as "reified".
39
+ # @return [Hash{String => Integer}]
40
+ attr_reader :ranking
41
+
42
+ # Initialize the context to a blank structure
43
+ def initialize
44
+ @vars2cv = {}
45
+ @cv2vars = {}
46
+ @symbol_table = SymbolTable.new
47
+ @blackboard = Blackboard.new
48
+ clear_ranking
49
+ end
50
+
51
+ # Notification that the current goal failed.
52
+ # @return [Core::Context] self
53
+ def failed!
54
+ blackboard.failed!
55
+ self
56
+ end
57
+
58
+ # Notification that the current goal succeeded.
59
+ # @return [Core::Context] self
60
+ def succeeded!
61
+ blackboard.succeeded!
62
+ self
63
+ end
64
+
65
+ # Does the latest result in the context represent a failure?
66
+ # @return [Boolean] true if failure, false otherwise
67
+ def failure?
68
+ blackboard.failure?
69
+ end
70
+
71
+ # Does the latest result in the context represent success?
72
+ # @return [Boolean] true if success, false otherwise
73
+ def success?
74
+ blackboard.success?
75
+ end
76
+
77
+ # Add an entry in the symbol table
78
+ # @param anEntry [Core::LogVar]
79
+ # @return [String] Internal name of the entry
80
+ def insert(anEntry)
81
+ symbol_table.insert(anEntry)
82
+ end
83
+
84
+ # Add one or more logical variable to the current scope
85
+ # @param var_names [String, Array<String>] one or more variable names
86
+ def add_vars(var_names)
87
+ vnames = var_names.kind_of?(String) ? [var_names] : var_names
88
+ vnames.each { |nm| insert(LogVar.new(nm)) }
89
+ end
90
+
91
+ # Search for the object with the given name
92
+ # @param aName [String]
93
+ # @return [Core::LogVar]
94
+ def lookup(aName)
95
+ symbol_table.lookup(aName)
96
+ end
97
+
98
+ # Set the provided scope as the current one
99
+ # @param aScope [Core::Scope]
100
+ def enter_scope(aScope)
101
+ # puts __callee__
102
+ symbol_table.enter_scope(aScope)
103
+ blackboard.enter_scope
104
+ end
105
+
106
+ # Pop the current scope and make its parent the current one
107
+ def leave_scope
108
+ # puts __callee__
109
+ current_scope = symbol_table.current_scope
110
+ parent_scope = current_scope.parent
111
+ return unless parent_scope
112
+
113
+ # Retrieve all i_names from current scope
114
+ i_name_set = Set.new(current_scope.defns.values.map(&:i_name))
115
+
116
+ # Remove all associations from queue until the scope's bookmark
117
+ items = blackboard.leave_scope
118
+ curr_asc, ancestor_asc = items.partition do |a|
119
+ i_name_set.include? a.i_name
120
+ end
121
+ vars_to_keep = Set.new
122
+
123
+ ancestor_asc.each do |assoc|
124
+ if assoc.dependencies(self).intersect?(i_name_set)
125
+ dependents = assoc.dependencies(self).intersection(i_name_set)
126
+ vars_to_keep.merge(dependents)
127
+ end
128
+ enqueue_association(assoc, nil) # parent_scope
129
+ end
130
+
131
+ assocs_to_keep = []
132
+
133
+ unless vars_to_keep.empty?
134
+ loop do
135
+ to_keep, to_consider = curr_asc.partition do |a|
136
+ vars_to_keep.include? a.i_name
137
+ end
138
+ break if to_keep.empty?
139
+
140
+ to_keep.each do |a|
141
+ vars_to_keep.merge(a.dependencies(self).intersection(i_name_set))
142
+ end
143
+ assocs_to_keep.concat(to_keep)
144
+ curr_asc = to_consider
145
+ end
146
+ end
147
+ symbol_table.leave_scope
148
+
149
+ vars_to_keep.each do |i_name|
150
+ v = LogVar.new(i_name)
151
+ v.suffix = ''
152
+ symbol_table.insert(v)
153
+ end
154
+
155
+ assocs_to_keep.each { |a| blackboard.enqueue_association(a) }
156
+ end
157
+
158
+ # Add the given association to the association queue
159
+ # @param anAssociation [Core::Association] something to bind to the variable
160
+ # @param aScope [Core::Scope, NilClass]
161
+ def enqueue_association(anAssociation, aScope = nil)
162
+ if aScope
163
+ raise NotImplementedError
164
+ else
165
+ blackboard.enqueue_association(anAssociation)
166
+ end
167
+ end
168
+
169
+ # Build an assocation and enqueue it.
170
+ # @param aName [String, #name] User-friendly name
171
+ # @param aValue [Core::Term]
172
+ # @param aScope [Core::Scope, NilClass]
173
+ def associate(aName, aValue, aScope = nil)
174
+ name = aName.kind_of?(String) ? aName : aName.name
175
+ if aScope
176
+ raise NotImplementedError
177
+ else
178
+ vr = symbol_table.lookup(name)
179
+ as = Association.new(blackboard.relevant_i_name(vr.i_name), aValue)
180
+ enqueue_association(as)
181
+ end
182
+ end
183
+
184
+ # Retrieve the association(s) for the variable with given name
185
+ # By default, the variable is assumed to belong to top-level scope.
186
+ # @param aName [String] User-friendly name of the logical variable
187
+ # @param aScope [Core::Scope, NilClass]
188
+ # @return [Array<Core::Association>]
189
+ def associations_for(aName, aScope = nil)
190
+ unless aName.kind_of?(String)
191
+ raise StandardError, "Invalid argument #{aName}"
192
+ end
193
+ if aScope
194
+ raise NotImplementedError
195
+ else
196
+ vr = symbol_table.lookup(aName)
197
+ blackboard.associations_for(vr.i_name, true)
198
+ end
199
+ end
200
+
201
+ # Two or more variables have to be fused.
202
+ # - Create a new (combining) variable
203
+ # - Create a fusion object
204
+ # @param names [Array<String>] Array of user-friendly names of variables to fuse.
205
+ def fuse(names)
206
+ return if names.size <= 1
207
+
208
+ vars = names.map { |nm| symbol_table.lookup(nm) }
209
+ i_names = vars.map(&:i_name)
210
+
211
+ # Create a new combining variable
212
+ new_name = fusion_name
213
+ cv_i_name = insert(LogVar.new(new_name))
214
+
215
+ # Update the mappings
216
+ @cv2vars[cv_i_name] = i_names.dup
217
+ i_names.each { |i_nm| @vars2cv[i_nm] = cv_i_name }
218
+
219
+ # Add fusion record to blackboard
220
+ fs = Fusion.new(cv_i_name, i_names)
221
+ blackboard.enqueue_fusion(fs)
222
+ end
223
+
224
+ def place_bt_point
225
+ # puts __callee__
226
+ blackboard.place_bt_point
227
+ end
228
+
229
+ def next_alternative
230
+ # puts __callee__
231
+ blackboard.next_alternative
232
+ end
233
+
234
+ def retract_bt_point
235
+ # puts __callee__
236
+ blackboard.retract_bt_point
237
+ end
238
+
239
+ # Returns a Hash with pairs of the form:
240
+ # { String => Association }, or
241
+ # { String => AnyValue }
242
+ def build_solution
243
+ solution = {}
244
+ return solution if failure?
245
+
246
+ substitutions = {}
247
+
248
+ # Fill in substitutions hash by starting with root variables
249
+ symbol_table.root.defns.each_pair do |_nm, item|
250
+ next unless item.kind_of?(LogVar)
251
+
252
+ add_substitution_for(item.i_name, substitutions)
253
+ end
254
+ # require 'debug'
255
+ handle_unbound_vars(substitutions)
256
+
257
+ # Copy the needed associations by expanding the substitutions
258
+ symbol_table.root.defns.each_pair do |_nm, item|
259
+ next unless item.kind_of?(LogVar)
260
+
261
+ next if item.name =~ /[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}/
262
+
263
+ i_name = item.i_name
264
+ solution[item.name] = expand_value_of(i_name, substitutions)
265
+ end
266
+
267
+ solution
268
+ end
269
+
270
+ =begin
271
+ Method add_substitution_for(q, substitutions):
272
+ Is it already present in substitutions, then return
273
+
274
+ Is it unbound? N
275
+ Is it fused? N
276
+ Get its association (Assumption: one association only)
277
+ Assume q => '( ,x ,y)
278
+ with dependents: Set { x, y}
279
+ foreach dependent variable call
280
+ add_substitution_for(q, substitutions) # Recursive call
281
+ no dependents => pinned
282
+ Add association in substitution
283
+ =end
284
+ # Update the provided substitutions Hash.
285
+ # If the given variable is dependent on other variables,
286
+ # then the substitution is updated recursively.
287
+ # @param iName [String] internal name of a logival variable
288
+ # @param theSubstitutions [Hash {String => Association}]
289
+ def add_substitution_for(iName, theSubstitutions)
290
+ return if theSubstitutions.include? iName # Work already done...
291
+
292
+ i_name = blackboard.fused?(iName) ? blackboard.vars2cv[iName] : iName
293
+ assocs = blackboard.associations_for(i_name, true)
294
+ assocs.delete_if { |e| e.kind_of?(Core::Fusion) }
295
+
296
+ if assocs.empty?
297
+ theSubstitutions[iName] = nil # Unbound variable
298
+ return
299
+ end
300
+ # TODO: cover cases of multiple associations
301
+ a = assocs.first
302
+ theSubstitutions[iName] = a
303
+ a.dependencies(self).each do |i_nm|
304
+ # Recursive call!
305
+ add_substitution_for(i_nm, theSubstitutions)
306
+ end
307
+ end
308
+
309
+ def handle_unbound_vars(theSubstitutions)
310
+ relevant_vars = symbol_table.all_variables.select do |vr|
311
+ i_name = vr.i_name
312
+ included = theSubstitutions.include? i_name
313
+ included & theSubstitutions[i_name].nil?
314
+ end
315
+
316
+ rank_number = 0
317
+ relevant_vars.each do |vr|
318
+ i_name = vr.i_name
319
+ if blackboard.fused?(i_name)
320
+ cv_i_name = blackboard.vars2cv[i_name]
321
+ fused = cv2vars[cv_i_name]
322
+ fused << cv_i_name # TODO: delete this line
323
+ already_ranked = fused.find { |i_nm| !theSubstitutions[i_nm].nil? }
324
+ if already_ranked
325
+ theSubstitutions[i_name] = theSubstitutions[already_ranked]
326
+ else
327
+ theSubstitutions[i_name] = AnyValue.new(rank_number)
328
+ rank_number += 1
329
+ end
330
+ else
331
+ theSubstitutions[i_name] = AnyValue.new(rank_number)
332
+ rank_number += 1
333
+ end
334
+ end
335
+ end
336
+
337
+ def expand_value_of(iName, theSubstitutions)
338
+ replacement = theSubstitutions[iName]
339
+ return replacement if replacement.kind_of?(AnyValue)
340
+
341
+ return replacement.value if replacement.dependencies(self).empty?
342
+
343
+ value_to_expand = replacement.value
344
+ expanded = nil
345
+
346
+ case value_to_expand
347
+ when LogVarRef
348
+ expanded = expand_value_of(value_to_expand.i_name, theSubstitutions)
349
+ when Composite::ConsCell
350
+ expanded = value_to_expand.expand(self, theSubstitutions)
351
+ end
352
+
353
+ expanded
354
+ end
355
+
356
+ private
357
+
358
+ # Clear the current ranking
359
+ def clear_ranking
360
+ @ranking = {}
361
+ end
362
+
363
+ # Calculate the rank of fresh variable(s) from scratch.
364
+ def calc_ranking
365
+ ranked = Set.new # Variables to reify (and rank)
366
+ symbol_table.root.defns.each_value do |entry|
367
+ next unless entry.kind_of?(LogVar)
368
+
369
+ assocs = blackboard.associations_for(entry.i_name, true)
370
+ if assocs.nil? || assocs.empty?
371
+ ranked << entry.i_name
372
+ else
373
+ assocs.each do |a|
374
+ if a.kind_of?(Fusion)
375
+ comb_moves = blackboard.i_name2moves[a.i_name]
376
+ ranked << entry.i_name if comb_moves.size == 1
377
+ else
378
+ dependents = a.dependencies(self)
379
+ dependents.each do |i_name|
380
+ dep_idx = blackboard.i_name2moves[i_name]
381
+ if dep_idx.nil? || dep_idx.empty?
382
+ ranked << i_name
383
+ # TODO: consider transitive closure
384
+ end
385
+ end
386
+ end
387
+ end
388
+ end
389
+ end
390
+ # Rank the variables...
391
+ scope = symbol_table.current_scope
392
+ sorted_entries = []
393
+ loop do
394
+ vars_in_scope = scope.defns.values.select { |e| e.kind_of?(LogVar) }
395
+ vars_in_scope&.reverse_each do |e|
396
+ sorted_entries.unshift(e) if ranked.include? e.i_name
397
+ end
398
+ scope = scope.parent
399
+ break if scope.nil?
400
+ end
401
+
402
+ rk_number = 0
403
+ # Ensure that fused variables have same rank number
404
+ sorted_entries.each do |e|
405
+ if blackboard.fused?(e.i_name)
406
+ siblings = cv2vars[blackboard.vars2cv[e.i_name]]
407
+ if siblings
408
+ occurred = siblings.find do |sb|
409
+ ranking.include? sb
410
+ end
411
+ if occurred
412
+ ranking[e.i_name] = ranking[occurred]
413
+ end
414
+ end
415
+ end
416
+ unless ranking.include? e.i_name
417
+ ranking[e.i_name] = rk_number
418
+ rk_number += 1
419
+ end
420
+ end
421
+ ranking
422
+ end
423
+
424
+ # Replace any unbound variable occurring in the value expression
425
+ # of the given association by an AnyValue instance.
426
+ # @param anAssoc [Association]
427
+ # @return [Term]
428
+ def substitute(anAssoc)
429
+ val = anAssoc.value
430
+ anAssoc.dependencies(self)
431
+ if val.kind_of?(LogVarRef)
432
+ i_name = anAssoc.i_name
433
+ anAssoc.instance_variable_set(:@value, AnyValue.new(ranking[i_name]))
434
+ else
435
+ new_value = substitute_composite(anAssoc)
436
+ anAssoc.instance_variable_set(:@value, new_value)
437
+ end
438
+ end
439
+
440
+ # Replace any unbound variable occurring in the composite expression
441
+ # of the given association by an AnyValue instance.
442
+ # @param anAssoc [Association]
443
+ # @return [Term]
444
+ def substitute_composite(anAssoc)
445
+ # require 'debug'
446
+ val = anAssoc.value
447
+ anAssoc.dependencies(self)
448
+ visitor = Composite::ConsCellVisitor.df_visitor(val)
449
+ result = curr_cell = nil
450
+ path = []
451
+ loop do
452
+ side, obj = visitor.resume
453
+ break if side == :stop
454
+
455
+ member_val = nil
456
+ case obj
457
+ when Composite::ConsCell
458
+ member_val = Composite::ConsCell.new(42) # Workaround: make non-null
459
+ if curr_cell
460
+ curr_cell.set!(side, member_val)
461
+ else
462
+ result = member_val
463
+ member_val.set_car!(nil)
464
+ end
465
+ curr_cell = member_val
466
+ path.push curr_cell
467
+ when LogVarRef
468
+ nm = lookup(obj.name).i_name
469
+ rank = ranking[nm]
470
+ member_val = rank.nil? ? obj : AnyValue.new(rank)
471
+ curr_cell.set!(side, member_val)
472
+ else
473
+ member_val = obj
474
+ curr_cell.set!(side, member_val)
475
+ end
476
+ if side == :cdr
477
+ curr_cell = path.pop
478
+ end
479
+ end
480
+
481
+ result
482
+ end
483
+
484
+ # Return the name of a variable resulting from a fusion of
485
+ # two or more variables.
486
+ # @return [String]
487
+ def fusion_name
488
+ SecureRandom.uuid
489
+ end
490
+ end # class
491
+ end # module
492
+ end # module