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.
- checksums.yaml +4 -4
- data/.rubocop.yml +378 -333
- data/CHANGELOG.md +48 -0
- data/README.md +29 -21
- data/lib/mini_kraken/atomic/all_atomic.rb +5 -0
- data/lib/mini_kraken/atomic/atomic_term.rb +96 -0
- data/lib/mini_kraken/atomic/k_boolean.rb +42 -0
- data/lib/mini_kraken/{core → atomic}/k_integer.rb +2 -5
- data/lib/mini_kraken/atomic/k_string.rb +17 -0
- data/lib/mini_kraken/{core → atomic}/k_symbol.rb +4 -8
- data/lib/mini_kraken/composite/all_composite.rb +4 -0
- data/lib/mini_kraken/composite/composite_term.rb +27 -0
- data/lib/mini_kraken/composite/cons_cell.rb +301 -0
- data/lib/mini_kraken/composite/cons_cell_visitor.rb +50 -0
- data/lib/mini_kraken/composite/list.rb +32 -0
- data/lib/mini_kraken/core/all_core.rb +8 -0
- data/lib/mini_kraken/core/any_value.rb +31 -7
- data/lib/mini_kraken/core/arity.rb +69 -0
- data/lib/mini_kraken/core/association.rb +29 -4
- data/lib/mini_kraken/core/association_copy.rb +50 -0
- data/lib/mini_kraken/core/base_term.rb +13 -0
- data/lib/mini_kraken/core/blackboard.rb +315 -0
- data/lib/mini_kraken/core/bookmark.rb +46 -0
- data/lib/mini_kraken/core/context.rb +492 -0
- data/lib/mini_kraken/core/duck_fiber.rb +21 -19
- data/lib/mini_kraken/core/entry.rb +40 -0
- data/lib/mini_kraken/core/fail.rb +20 -18
- data/lib/mini_kraken/core/fusion.rb +29 -0
- data/lib/mini_kraken/core/goal.rb +20 -29
- data/lib/mini_kraken/core/log_var.rb +22 -0
- data/lib/mini_kraken/core/log_var_ref.rb +108 -0
- data/lib/mini_kraken/core/nullary_relation.rb +2 -9
- data/lib/mini_kraken/core/parametrized_term.rb +68 -0
- data/lib/mini_kraken/core/relation.rb +14 -28
- data/lib/mini_kraken/core/scope.rb +67 -0
- data/lib/mini_kraken/core/solver_adapter.rb +58 -0
- data/lib/mini_kraken/core/specification.rb +48 -0
- data/lib/mini_kraken/core/succeed.rb +21 -17
- data/lib/mini_kraken/core/symbol_table.rb +137 -0
- data/lib/mini_kraken/core/term.rb +15 -4
- data/lib/mini_kraken/glue/dsl.rb +44 -88
- data/lib/mini_kraken/glue/run_star_expression.rb +28 -30
- data/lib/mini_kraken/rela/all_rela.rb +8 -0
- data/lib/mini_kraken/rela/binary_relation.rb +30 -0
- data/lib/mini_kraken/rela/conde.rb +143 -0
- data/lib/mini_kraken/rela/conj2.rb +65 -0
- data/lib/mini_kraken/rela/def_relation.rb +93 -0
- data/lib/mini_kraken/rela/disj2.rb +70 -0
- data/lib/mini_kraken/rela/fresh.rb +98 -0
- data/lib/mini_kraken/{core → rela}/goal_relation.rb +7 -9
- data/lib/mini_kraken/rela/unify.rb +265 -0
- data/lib/mini_kraken/version.rb +1 -1
- data/mini_kraken.gemspec +2 -2
- data/spec/.rubocop.yml +1 -1
- data/spec/atomic/atomic_term_spec.rb +98 -0
- data/spec/{core → atomic}/k_boolean_spec.rb +19 -34
- data/spec/{core → atomic}/k_symbol_spec.rb +3 -16
- data/spec/composite/cons_cell_spec.rb +225 -0
- data/spec/{core → composite}/cons_cell_visitor_spec.rb +36 -20
- data/spec/composite/list_spec.rb +50 -0
- data/spec/core/any_value_spec.rb +52 -0
- data/spec/core/arity_spec.rb +92 -0
- data/spec/core/association_copy_spec.rb +69 -0
- data/spec/core/association_spec.rb +31 -4
- data/spec/core/blackboard_spec.rb +287 -0
- data/spec/core/bookmark_spec.rb +40 -0
- data/spec/core/context_spec.rb +245 -0
- data/spec/core/core_spec.rb +40 -0
- data/spec/core/duck_fiber_spec.rb +16 -46
- data/spec/core/fail_spec.rb +5 -6
- data/spec/core/goal_spec.rb +22 -12
- data/spec/core/log_var_ref_spec.rb +105 -0
- data/spec/core/log_var_spec.rb +64 -0
- data/spec/core/nullary_relation_spec.rb +33 -0
- data/spec/core/parametrized_tem_spec.rb +39 -0
- data/spec/core/relation_spec.rb +33 -0
- data/spec/core/scope_spec.rb +73 -0
- data/spec/core/solver_adapter_spec.rb +70 -0
- data/spec/core/specification_spec.rb +43 -0
- data/spec/core/succeed_spec.rb +5 -5
- data/spec/core/symbol_table_spec.rb +142 -0
- data/spec/glue/dsl_chap1_spec.rb +88 -144
- data/spec/glue/dsl_chap2_spec.rb +454 -19
- data/spec/glue/run_star_expression_spec.rb +81 -906
- data/spec/rela/conde_spec.rb +153 -0
- data/spec/rela/conj2_spec.rb +123 -0
- data/spec/rela/def_relation_spec.rb +119 -0
- data/spec/rela/disj2_spec.rb +117 -0
- data/spec/rela/fresh_spec.rb +147 -0
- data/spec/rela/unify_spec.rb +369 -0
- data/spec/support/factory_atomic.rb +29 -0
- data/spec/support/factory_composite.rb +21 -0
- data/spec/support/factory_methods.rb +11 -26
- metadata +98 -70
- data/lib/mini_kraken/core/association_walker.rb +0 -183
- data/lib/mini_kraken/core/atomic_term.rb +0 -67
- data/lib/mini_kraken/core/base_arg.rb +0 -10
- data/lib/mini_kraken/core/binary_relation.rb +0 -63
- data/lib/mini_kraken/core/composite_goal.rb +0 -46
- data/lib/mini_kraken/core/composite_term.rb +0 -41
- data/lib/mini_kraken/core/conde.rb +0 -143
- data/lib/mini_kraken/core/conj2.rb +0 -79
- data/lib/mini_kraken/core/cons_cell.rb +0 -82
- data/lib/mini_kraken/core/cons_cell_visitor.rb +0 -102
- data/lib/mini_kraken/core/def_relation.rb +0 -53
- data/lib/mini_kraken/core/designation.rb +0 -55
- data/lib/mini_kraken/core/disj2.rb +0 -72
- data/lib/mini_kraken/core/environment.rb +0 -73
- data/lib/mini_kraken/core/equals.rb +0 -193
- data/lib/mini_kraken/core/formal_arg.rb +0 -22
- data/lib/mini_kraken/core/formal_ref.rb +0 -25
- data/lib/mini_kraken/core/freshness.rb +0 -45
- data/lib/mini_kraken/core/goal_arg.rb +0 -12
- data/lib/mini_kraken/core/goal_template.rb +0 -102
- data/lib/mini_kraken/core/k_boolean.rb +0 -35
- data/lib/mini_kraken/core/outcome.rb +0 -63
- data/lib/mini_kraken/core/variable.rb +0 -41
- data/lib/mini_kraken/core/variable_ref.rb +0 -84
- data/lib/mini_kraken/core/vocabulary.rb +0 -446
- data/lib/mini_kraken/glue/fresh_env.rb +0 -103
- data/lib/mini_kraken/glue/fresh_env_factory.rb +0 -83
- data/spec/core/association_walker_spec.rb +0 -192
- data/spec/core/conde_spec.rb +0 -147
- data/spec/core/conj2_spec.rb +0 -114
- data/spec/core/cons_cell_spec.rb +0 -107
- data/spec/core/def_relation_spec.rb +0 -97
- data/spec/core/disj2_spec.rb +0 -99
- data/spec/core/environment_spec.rb +0 -142
- data/spec/core/equals_spec.rb +0 -317
- data/spec/core/goal_template_spec.rb +0 -74
- data/spec/core/outcome_spec.rb +0 -56
- data/spec/core/variable_ref_spec.rb +0 -30
- data/spec/core/variable_spec.rb +0 -35
- data/spec/core/vocabulary_spec.rb +0 -219
- data/spec/glue/fresh_env_factory_spec.rb +0 -97
- 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
|