mini_kraken 0.2.04 → 0.3.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.
Files changed (120) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +7 -0
  3. data/README.md +16 -16
  4. data/lib/mini_kraken/atomic/all_atomic.rb +1 -0
  5. data/lib/mini_kraken/atomic/atomic_term.rb +32 -17
  6. data/lib/mini_kraken/atomic/k_integer.rb +0 -4
  7. data/lib/mini_kraken/atomic/k_string.rb +17 -0
  8. data/lib/mini_kraken/atomic/k_symbol.rb +0 -6
  9. data/lib/mini_kraken/composite/all_composite.rb +4 -0
  10. data/lib/mini_kraken/composite/composite_term.rb +2 -18
  11. data/lib/mini_kraken/composite/cons_cell.rb +178 -11
  12. data/lib/mini_kraken/composite/cons_cell_visitor.rb +12 -64
  13. data/lib/mini_kraken/composite/list.rb +32 -0
  14. data/lib/mini_kraken/core/all_core.rb +8 -0
  15. data/lib/mini_kraken/core/any_value.rb +31 -7
  16. data/lib/mini_kraken/core/arity.rb +69 -0
  17. data/lib/mini_kraken/core/association.rb +29 -4
  18. data/lib/mini_kraken/core/association_copy.rb +50 -0
  19. data/lib/mini_kraken/core/base_term.rb +13 -0
  20. data/lib/mini_kraken/core/blackboard.rb +315 -0
  21. data/lib/mini_kraken/core/bookmark.rb +46 -0
  22. data/lib/mini_kraken/core/context.rb +624 -0
  23. data/lib/mini_kraken/core/duck_fiber.rb +21 -19
  24. data/lib/mini_kraken/core/entry.rb +40 -0
  25. data/lib/mini_kraken/core/fail.rb +20 -18
  26. data/lib/mini_kraken/core/fusion.rb +29 -0
  27. data/lib/mini_kraken/core/goal.rb +20 -29
  28. data/lib/mini_kraken/core/log_var.rb +4 -30
  29. data/lib/mini_kraken/core/log_var_ref.rb +72 -48
  30. data/lib/mini_kraken/core/nullary_relation.rb +2 -9
  31. data/lib/mini_kraken/core/parametrized_term.rb +61 -0
  32. data/lib/mini_kraken/core/relation.rb +14 -28
  33. data/lib/mini_kraken/core/scope.rb +67 -0
  34. data/lib/mini_kraken/core/solver_adapter.rb +58 -0
  35. data/lib/mini_kraken/core/specification.rb +48 -0
  36. data/lib/mini_kraken/core/succeed.rb +21 -17
  37. data/lib/mini_kraken/core/symbol_table.rb +137 -0
  38. data/lib/mini_kraken/core/term.rb +15 -4
  39. data/lib/mini_kraken/glue/dsl.rb +35 -69
  40. data/lib/mini_kraken/glue/run_star_expression.rb +28 -30
  41. data/lib/mini_kraken/rela/all_rela.rb +8 -0
  42. data/lib/mini_kraken/rela/binary_relation.rb +30 -0
  43. data/lib/mini_kraken/rela/conde.rb +146 -0
  44. data/lib/mini_kraken/rela/conj2.rb +65 -0
  45. data/lib/mini_kraken/rela/def_relation.rb +64 -0
  46. data/lib/mini_kraken/rela/disj2.rb +70 -0
  47. data/lib/mini_kraken/rela/fresh.rb +98 -0
  48. data/lib/mini_kraken/{core → rela}/goal_relation.rb +6 -8
  49. data/lib/mini_kraken/rela/unify.rb +258 -0
  50. data/lib/mini_kraken/version.rb +1 -1
  51. data/spec/atomic/atomic_term_spec.rb +23 -20
  52. data/spec/atomic/k_symbol_spec.rb +0 -5
  53. data/spec/composite/cons_cell_spec.rb +116 -0
  54. data/spec/composite/cons_cell_visitor_spec.rb +16 -3
  55. data/spec/composite/list_spec.rb +50 -0
  56. data/spec/core/any_value_spec.rb +52 -0
  57. data/spec/core/arity_spec.rb +91 -0
  58. data/spec/core/association_copy_spec.rb +69 -0
  59. data/spec/core/association_spec.rb +25 -0
  60. data/spec/core/blackboard_spec.rb +287 -0
  61. data/spec/core/bookmark_spec.rb +40 -0
  62. data/spec/core/context_spec.rb +221 -0
  63. data/spec/core/core_spec.rb +40 -0
  64. data/spec/core/duck_fiber_spec.rb +22 -46
  65. data/spec/core/fail_spec.rb +5 -6
  66. data/spec/core/goal_spec.rb +20 -11
  67. data/spec/core/log_var_ref_spec.rb +80 -5
  68. data/spec/core/log_var_spec.rb +35 -6
  69. data/spec/core/nullary_relation_spec.rb +33 -0
  70. data/spec/core/parametrized_tem_spec.rb +39 -0
  71. data/spec/core/relation_spec.rb +33 -0
  72. data/spec/core/scope_spec.rb +73 -0
  73. data/spec/core/solver_adapter_spec.rb +70 -0
  74. data/spec/core/specification_spec.rb +43 -0
  75. data/spec/core/succeed_spec.rb +5 -5
  76. data/spec/core/symbol_table_spec.rb +142 -0
  77. data/spec/glue/dsl_chap1_spec.rb +88 -99
  78. data/spec/glue/dsl_chap2_spec.rb +59 -41
  79. data/spec/glue/run_star_expression_spec.rb +69 -896
  80. data/spec/{core → rela}/conde_spec.rb +50 -46
  81. data/spec/rela/conj2_spec.rb +123 -0
  82. data/spec/rela/def_relation_spec.rb +119 -0
  83. data/spec/rela/disj2_spec.rb +117 -0
  84. data/spec/rela/fresh_spec.rb +147 -0
  85. data/spec/rela/unify_spec.rb +369 -0
  86. data/spec/support/factory_atomic.rb +7 -0
  87. data/spec/support/factory_composite.rb +21 -0
  88. metadata +71 -48
  89. data/lib/mini_kraken/core/association_walker.rb +0 -183
  90. data/lib/mini_kraken/core/base_arg.rb +0 -10
  91. data/lib/mini_kraken/core/binary_relation.rb +0 -63
  92. data/lib/mini_kraken/core/composite_goal.rb +0 -46
  93. data/lib/mini_kraken/core/conde.rb +0 -143
  94. data/lib/mini_kraken/core/conj2.rb +0 -79
  95. data/lib/mini_kraken/core/def_relation.rb +0 -53
  96. data/lib/mini_kraken/core/designation.rb +0 -55
  97. data/lib/mini_kraken/core/disj2.rb +0 -72
  98. data/lib/mini_kraken/core/environment.rb +0 -73
  99. data/lib/mini_kraken/core/equals.rb +0 -191
  100. data/lib/mini_kraken/core/formal_arg.rb +0 -22
  101. data/lib/mini_kraken/core/formal_ref.rb +0 -25
  102. data/lib/mini_kraken/core/freshness.rb +0 -45
  103. data/lib/mini_kraken/core/goal_arg.rb +0 -12
  104. data/lib/mini_kraken/core/goal_template.rb +0 -102
  105. data/lib/mini_kraken/core/outcome.rb +0 -63
  106. data/lib/mini_kraken/core/tap.rb +0 -46
  107. data/lib/mini_kraken/core/vocabulary.rb +0 -446
  108. data/lib/mini_kraken/glue/fresh_env.rb +0 -108
  109. data/lib/mini_kraken/glue/fresh_env_factory.rb +0 -83
  110. data/spec/core/association_walker_spec.rb +0 -194
  111. data/spec/core/conj2_spec.rb +0 -116
  112. data/spec/core/def_relation_spec.rb +0 -99
  113. data/spec/core/disj2_spec.rb +0 -100
  114. data/spec/core/environment_spec.rb +0 -144
  115. data/spec/core/equals_spec.rb +0 -319
  116. data/spec/core/goal_template_spec.rb +0 -74
  117. data/spec/core/outcome_spec.rb +0 -56
  118. data/spec/core/vocabulary_spec.rb +0 -220
  119. data/spec/glue/fresh_env_factory_spec.rb +0 -99
  120. 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,624 @@
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_zolution
243
+ clear_ranking
244
+ calc_ranking
245
+ solution = {}
246
+ return solution if failure?
247
+
248
+ # require 'debug'
249
+ symbol_table.root.defns.each_pair do |nm, item|
250
+ next unless item.kind_of?(LogVar)
251
+
252
+ if failure?
253
+ solution[nm] = nil
254
+ next
255
+ end
256
+ i_name = item.i_name
257
+ assocs = blackboard.associations_for(i_name, true)
258
+ if assocs.nil? || assocs.empty? ||
259
+ (blackboard.fused?(i_name) && assocs.empty?)
260
+ solution[nm] = AnyValue.new(ranking[i_name])
261
+ else
262
+ my_assocs = []
263
+ assocs.each { |a| my_assocs << a if a.kind_of?(Association) }
264
+ next if my_assocs.empty?
265
+
266
+ # TODO: if multiple associations, conj2 them...
267
+ as = my_assocs.first
268
+
269
+ deps = as.dependencies(self)
270
+ if deps.any? { |i_name_dep| ranking.include? i_name_dep }
271
+ # At least one dependent variable in expression is unbound
272
+ solution[nm] = substitute(as)
273
+ else
274
+ solution[nm] = as.value
275
+ end
276
+ end
277
+ end
278
+ # Take into current scope (e.g. x).
279
+ # e.g. if q depends on other inner scope variable,
280
+ # then one should replace every occurrence of x by AnyValue
281
+
282
+ solution
283
+ end
284
+ =begin
285
+ find a solution for 1:67
286
+ Scope picture:
287
+ s_a:
288
+ q
289
+ ----
290
+ s_b:
291
+ x
292
+ ----
293
+ s_c:
294
+ y
295
+
296
+ Move_queue
297
+ bk(s_b)
298
+ bk(s_c)
299
+ assoc x => :split
300
+ assoc y => :pea
301
+ assoc r => '(,x ,y)
302
+
303
+ Let solution be a Hash i_name => value expression
304
+ Let substitutions be a Hash i_name => value expression
305
+ Start with root variables:
306
+ For each root variable:
307
+ Given q:
308
+ Is it unbound? N
309
+ Is it fused? N
310
+ Get its association (Assumption: one association only)
311
+ q => '( ,x ,y)
312
+ dependents:
313
+ Set { x, y}
314
+ Given x:
315
+ Is it unbound? N
316
+ Is it fused? N
317
+ Get its association
318
+ x => :split
319
+ no dependents => pinned, not a root variable:
320
+ Add x => :split in substitution
321
+ substitution = { x => :split }
322
+ Given y:
323
+ Is it unbound? N
324
+ Is it fused? N
325
+ Get its association
326
+ y => :pea
327
+ no dependents => pinned, not a root variable:
328
+ Add y => :pea in substitution
329
+ substitution = { x => :split, y => :pea }
330
+ All dependents resolved? Y
331
+ Add q => (:split :pea) to solution
332
+ Done with root variables?
333
+ Return solution: q => (split :pea)
334
+
335
+ Let solution be a Hash i_name => value expression
336
+ Let substitutions be a Hash i_name => value expression
337
+ Method add_substitution_for(q, substitutions):
338
+ Is it already present in substitutions, then return
339
+
340
+ Is it unbound? N
341
+ Is it fused? N
342
+ Get its association (Assumption: one association only)
343
+ Assume q => '( ,x ,y)
344
+ with dependents: Set { x, y}
345
+ foreach dependent variable call
346
+ add_substitution_for(q, substitutions) # Recursive call
347
+ no dependents => pinned
348
+ Add association in substitution
349
+
350
+ IDEA: placeholder in term expressions
351
+ '( ,x ,y) is translated into:
352
+ cons(placeholder_100(nil), placeholder_200(nil))
353
+ x => [placeholder_100]
354
+ y => [placeholder_200]
355
+
356
+ class Placeholder {
357
+ subj = nil
358
+ def object_id ; subj.object_id ; end
359
+ def kind_of? ; subj.kind_of? ; end
360
+ def to_s ; subj.to_s ; end
361
+ }
362
+
363
+ Where are placedholders created?
364
+ - In Association.new
365
+
366
+ Do we really need Placeholder?
367
+ Do LogVarRef not fill the bill?
368
+ IDEA: at a given moment, a LogVarRef is instructed to refer to a value
369
+ Difficulty: A variable can take multiple values
370
+ Thus the logVarRef may not embed the value but refer to a value indexed by solution.
371
+
372
+ =end
373
+
374
+ def build_solution
375
+ solution = {}
376
+ return solution if failure?
377
+
378
+ substitutions = {}
379
+
380
+ # Fill in substitutions hash by starting with root variables
381
+ symbol_table.root.defns.each_pair do |_nm, item|
382
+ next unless item.kind_of?(LogVar)
383
+
384
+ add_substitution_for(item.i_name, substitutions)
385
+ end
386
+ # require 'debug'
387
+ handle_unbound_vars(substitutions)
388
+
389
+ # Copy the needed associations by expanding the substitutions
390
+ symbol_table.root.defns.each_pair do |_nm, item|
391
+ next unless item.kind_of?(LogVar)
392
+
393
+ next if item.name =~ /[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}/
394
+
395
+ i_name = item.i_name
396
+ solution[item.name] = expand_value_of(i_name, substitutions)
397
+ end
398
+
399
+ solution
400
+ end
401
+
402
+ =begin
403
+ Method add_substitution_for(q, substitutions):
404
+ Is it already present in substitutions, then return
405
+
406
+ Is it unbound? N
407
+ Is it fused? N
408
+ Get its association (Assumption: one association only)
409
+ Assume q => '( ,x ,y)
410
+ with dependents: Set { x, y}
411
+ foreach dependent variable call
412
+ add_substitution_for(q, substitutions) # Recursive call
413
+ no dependents => pinned
414
+ Add association in substitution
415
+ =end
416
+ # Update the provided substitutions Hash.
417
+ # If the given variable is dependent on other variables,
418
+ # then the substitution is updated recursively.
419
+ # @param iName [String] internal name of a logival variable
420
+ # @param theSubstitutions [Hash {String => Association}]
421
+ def add_substitution_for(iName, theSubstitutions)
422
+ return if theSubstitutions.include? iName # Work already done...
423
+
424
+ i_name = blackboard.fused?(iName) ? blackboard.vars2cv[iName] : iName
425
+ assocs = blackboard.associations_for(i_name, true)
426
+ assocs.delete_if { |e| e.kind_of?(Core::Fusion) }
427
+
428
+ if assocs.empty?
429
+ theSubstitutions[iName] = nil # Unbound variable
430
+ return
431
+ end
432
+ # TODO: cover cases of multiple associations
433
+ a = assocs.first
434
+ theSubstitutions[iName] = a
435
+ a.dependencies(self).each do |i_nm|
436
+ # Recursive call!
437
+ add_substitution_for(i_nm, theSubstitutions)
438
+ end
439
+ end
440
+
441
+ def handle_unbound_vars(theSubstitutions)
442
+ relevant_vars = symbol_table.all_variables.select do |vr|
443
+ i_name = vr.i_name
444
+ included = theSubstitutions.include? i_name
445
+ included & theSubstitutions[i_name].nil?
446
+ end
447
+
448
+ rank_number = 0
449
+ relevant_vars.each do |vr|
450
+ i_name = vr.i_name
451
+ if blackboard.fused?(i_name)
452
+ cv_i_name = blackboard.vars2cv[i_name]
453
+ fused = cv2vars[cv_i_name]
454
+ fused << cv_i_name # TODO: delete this line
455
+ already_ranked = fused.find { |i_nm| !theSubstitutions[i_nm].nil? }
456
+ if already_ranked
457
+ theSubstitutions[i_name] = theSubstitutions[already_ranked]
458
+ else
459
+ theSubstitutions[i_name] = AnyValue.new(rank_number)
460
+ rank_number += 1
461
+ end
462
+ else
463
+ theSubstitutions[i_name] = AnyValue.new(rank_number)
464
+ rank_number += 1
465
+ end
466
+ end
467
+ end
468
+
469
+ def expand_value_of(iName, theSubstitutions)
470
+ replacement = theSubstitutions[iName]
471
+ return replacement if replacement.kind_of?(AnyValue)
472
+
473
+ return replacement.value if replacement.dependencies(self).empty?
474
+
475
+ value_to_expand = replacement.value
476
+ expanded = nil
477
+
478
+ case value_to_expand
479
+ when LogVarRef
480
+ expanded = expand_value_of(value_to_expand.i_name, theSubstitutions)
481
+ when Composite::ConsCell
482
+ expanded = value_to_expand.expand(self, theSubstitutions)
483
+ end
484
+
485
+ expanded
486
+ end
487
+
488
+ private
489
+
490
+ # Clear the current ranking
491
+ def clear_ranking
492
+ @ranking = {}
493
+ end
494
+
495
+ # Calculate the rank of fresh variable(s) from scratch.
496
+ def calc_ranking
497
+ ranked = Set.new # Variables to reify (and rank)
498
+ symbol_table.root.defns.each_value do |entry|
499
+ next unless entry.kind_of?(LogVar)
500
+
501
+ assocs = blackboard.associations_for(entry.i_name, true)
502
+ if assocs.nil? || assocs.empty?
503
+ ranked << entry.i_name
504
+ else
505
+ assocs.each do |a|
506
+ if a.kind_of?(Fusion)
507
+ comb_moves = blackboard.i_name2moves[a.i_name]
508
+ ranked << entry.i_name if comb_moves.size == 1
509
+ else
510
+ dependents = a.dependencies(self)
511
+ dependents.each do |i_name|
512
+ dep_idx = blackboard.i_name2moves[i_name]
513
+ if dep_idx.nil? || dep_idx.empty?
514
+ ranked << i_name
515
+ # TODO: consider transitive closure
516
+ end
517
+ end
518
+ end
519
+ end
520
+ end
521
+ end
522
+ # Rank the variables...
523
+ scope = symbol_table.current_scope
524
+ sorted_entries = []
525
+ loop do
526
+ vars_in_scope = scope.defns.values.select { |e| e.kind_of?(LogVar) }
527
+ vars_in_scope&.reverse_each do |e|
528
+ sorted_entries.unshift(e) if ranked.include? e.i_name
529
+ end
530
+ scope = scope.parent
531
+ break if scope.nil?
532
+ end
533
+
534
+ rk_number = 0
535
+ # Ensure that fused variables have same rank number
536
+ sorted_entries.each do |e|
537
+ if blackboard.fused?(e.i_name)
538
+ siblings = cv2vars[blackboard.vars2cv[e.i_name]]
539
+ if siblings
540
+ occurred = siblings.find do |sb|
541
+ ranking.include? sb
542
+ end
543
+ if occurred
544
+ ranking[e.i_name] = ranking[occurred]
545
+ end
546
+ end
547
+ end
548
+ unless ranking.include? e.i_name
549
+ ranking[e.i_name] = rk_number
550
+ rk_number += 1
551
+ end
552
+ end
553
+ ranking
554
+ end
555
+
556
+ # Replace any unbound variable occurring in the value expression
557
+ # of the given association by an AnyValue instance.
558
+ # @param anAssoc [Association]
559
+ # @return [Term]
560
+ def substitute(anAssoc)
561
+ val = anAssoc.value
562
+ anAssoc.dependencies(self)
563
+ if val.kind_of?(LogVarRef)
564
+ i_name = anAssoc.i_name
565
+ anAssoc.instance_variable_set(:@value, AnyValue.new(ranking[i_name]))
566
+ else
567
+ new_value = substitute_composite(anAssoc)
568
+ anAssoc.instance_variable_set(:@value, new_value)
569
+ end
570
+ end
571
+
572
+ # Replace any unbound variable occurring in the composite expression
573
+ # of the given association by an AnyValue instance.
574
+ # @param anAssoc [Association]
575
+ # @return [Term]
576
+ def substitute_composite(anAssoc)
577
+ # require 'debug'
578
+ val = anAssoc.value
579
+ anAssoc.dependencies(self)
580
+ visitor = Composite::ConsCellVisitor.df_visitor(val)
581
+ result = curr_cell = nil
582
+ path = []
583
+ loop do
584
+ side, obj = visitor.resume
585
+ break if side == :stop
586
+
587
+ member_val = nil
588
+ case obj
589
+ when Composite::ConsCell
590
+ member_val = Composite::ConsCell.new(42) # Workaround: make non-null
591
+ if curr_cell
592
+ curr_cell.set!(side, member_val)
593
+ else
594
+ result = member_val
595
+ member_val.set_car!(nil)
596
+ end
597
+ curr_cell = member_val
598
+ path.push curr_cell
599
+ when LogVarRef
600
+ nm = lookup(obj.name).i_name
601
+ rank = ranking[nm]
602
+ member_val = rank.nil? ? obj : AnyValue.new(rank)
603
+ curr_cell.set!(side, member_val)
604
+ else
605
+ member_val = obj
606
+ curr_cell.set!(side, member_val)
607
+ end
608
+ if side == :cdr
609
+ curr_cell = path.pop
610
+ end
611
+ end
612
+
613
+ result
614
+ end
615
+
616
+ # Return the name of a variable resulting from a fusion of
617
+ # two or more variables.
618
+ # @return [String]
619
+ def fusion_name
620
+ SecureRandom.uuid
621
+ end
622
+ end # class
623
+ end # module
624
+ end # module