mini_kraken 0.1.03 → 0.1.08
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/.travis.yml +5 -1
- data/CHANGELOG.md +54 -3
- data/Gemfile +3 -1
- data/README.md +22 -1
- data/Rakefile +5 -3
- data/lib/mini_kraken.rb +3 -1
- data/lib/mini_kraken/core/any_value.rb +9 -7
- data/lib/mini_kraken/core/association.rb +20 -7
- data/lib/mini_kraken/core/association_walker.rb +5 -1
- data/lib/mini_kraken/core/atomic_term.rb +5 -3
- data/lib/mini_kraken/core/binary_relation.rb +8 -6
- data/lib/mini_kraken/core/composite_goal.rb +46 -0
- data/lib/mini_kraken/core/composite_term.rb +7 -20
- data/lib/mini_kraken/core/conj2.rb +77 -0
- data/lib/mini_kraken/core/cons_cell.rb +51 -41
- data/lib/mini_kraken/core/designation.rb +55 -0
- data/lib/mini_kraken/core/disj2.rb +71 -0
- data/lib/mini_kraken/core/duck_fiber.rb +4 -2
- data/lib/mini_kraken/core/environment.rb +25 -11
- data/lib/mini_kraken/core/equals.rb +128 -189
- data/lib/mini_kraken/core/fail.rb +20 -14
- data/lib/mini_kraken/core/freshness.rb +11 -8
- data/lib/mini_kraken/core/goal.rb +8 -4
- data/lib/mini_kraken/core/goal_arg.rb +10 -0
- data/lib/mini_kraken/core/goal_relation.rb +28 -0
- data/lib/mini_kraken/core/k_integer.rb +4 -3
- data/lib/mini_kraken/core/k_symbol.rb +4 -3
- data/lib/mini_kraken/core/nullary_relation.rb +3 -1
- data/lib/mini_kraken/core/outcome.rb +29 -25
- data/lib/mini_kraken/core/relation.rb +4 -18
- data/lib/mini_kraken/core/succeed.rb +20 -14
- data/lib/mini_kraken/core/term.rb +7 -2
- data/lib/mini_kraken/core/variable.rb +11 -25
- data/lib/mini_kraken/core/variable_ref.rb +12 -59
- data/lib/mini_kraken/core/vocabulary.rb +267 -48
- data/lib/mini_kraken/glue/fresh_env.rb +5 -3
- data/lib/mini_kraken/glue/run_star_expression.rb +18 -8
- data/lib/mini_kraken/version.rb +3 -1
- data/mini_kraken.gemspec +15 -13
- data/spec/core/association_spec.rb +4 -4
- data/spec/core/association_walker_spec.rb +25 -24
- data/spec/core/conj2_spec.rb +114 -0
- data/spec/core/cons_cell_spec.rb +12 -3
- data/spec/core/disj2_spec.rb +99 -0
- data/spec/core/duck_fiber_spec.rb +22 -12
- data/spec/core/environment_spec.rb +16 -28
- data/spec/core/equals_spec.rb +7 -7
- data/spec/core/fail_spec.rb +7 -7
- data/spec/core/goal_spec.rb +10 -10
- data/spec/core/k_symbol_spec.rb +5 -6
- data/spec/core/succeed_spec.rb +4 -4
- data/spec/core/variable_ref_spec.rb +0 -4
- data/spec/core/vocabulary_spec.rb +33 -27
- data/spec/glue/fresh_env_spec.rb +1 -1
- data/spec/glue/run_star_expression_spec.rb +213 -60
- data/spec/mini_kraken_spec.rb +4 -0
- data/spec/spec_helper.rb +3 -2
- data/spec/support/factory_methods.rb +20 -2
- metadata +12 -2
@@ -1,3 +1,7 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'set'
|
4
|
+
require_relative 'association'
|
1
5
|
require_relative 'association_walker'
|
2
6
|
|
3
7
|
module MiniKraken
|
@@ -19,31 +23,34 @@ module MiniKraken
|
|
19
23
|
@rankings = {} unless aParent
|
20
24
|
end
|
21
25
|
|
22
|
-
# Return a
|
26
|
+
# Return a Enumerator object that can iterate over this vocabulary and
|
23
27
|
# all its direct and indirect parent(s).
|
24
|
-
# @return [
|
28
|
+
# @return [Enumerator<Vocabulary, NilClass>]
|
25
29
|
def ancestor_walker
|
26
|
-
|
30
|
+
unless @ancestors # Not yet in cache?...
|
31
|
+
@ancestors = []
|
27
32
|
relative = self
|
28
|
-
while relative
|
29
|
-
|
33
|
+
while relative
|
34
|
+
@ancestors << relative
|
30
35
|
relative = relative.parent
|
31
36
|
end
|
32
|
-
|
33
|
-
Fiber.yield nil # nil marks end of iteration...
|
37
|
+
@ancestors << nil # nil marks end of iteration...
|
34
38
|
end
|
39
|
+
|
40
|
+
@ancestors.to_enum
|
35
41
|
end
|
36
42
|
|
37
43
|
def clear_rankings
|
38
44
|
walker = ancestor_walker
|
39
45
|
orphan = nil
|
40
46
|
loop do
|
41
|
-
orphan_temp = walker.
|
47
|
+
orphan_temp = walker.next
|
42
48
|
break unless orphan_temp
|
49
|
+
|
43
50
|
orphan = orphan_temp
|
44
51
|
end
|
45
52
|
|
46
|
-
orphan.rankings
|
53
|
+
orphan.rankings&.clear
|
47
54
|
end
|
48
55
|
|
49
56
|
# @param aName [String]
|
@@ -52,42 +59,50 @@ module MiniKraken
|
|
52
59
|
walker = ancestor_walker
|
53
60
|
orphan = nil
|
54
61
|
loop do
|
55
|
-
orphan_temp = walker.
|
62
|
+
orphan_temp = walker.next
|
56
63
|
break unless orphan_temp
|
64
|
+
|
57
65
|
orphan = orphan_temp
|
58
66
|
end
|
59
67
|
|
60
68
|
raise StandardError unless orphan
|
61
69
|
|
70
|
+
rank = nil
|
62
71
|
if orphan.rankings.include?(aName)
|
63
|
-
orphan.rankings[aName]
|
72
|
+
rank = orphan.rankings[aName]
|
64
73
|
else
|
65
|
-
other = alternate_names.find do |a_name|
|
66
|
-
orphan.rankings.include?(a_name)
|
74
|
+
other = alternate_names.find do |a_name|
|
75
|
+
rank = orphan.rankings.include?(a_name)
|
67
76
|
end
|
68
77
|
if other
|
69
|
-
get_rank(other)
|
78
|
+
rank = get_rank(other)
|
70
79
|
else
|
71
80
|
rank = orphan.rankings.keys.size
|
72
81
|
orphan.rankings[aName] = rank
|
73
|
-
rank
|
74
82
|
end
|
75
83
|
end
|
84
|
+
|
85
|
+
rank
|
76
86
|
end
|
77
87
|
|
78
|
-
# Record an association between a variable with given name
|
79
|
-
#
|
80
|
-
|
81
|
-
|
82
|
-
|
88
|
+
# Record an association between a variable with given user-defined name
|
89
|
+
# and a term.
|
90
|
+
# @param aName [String, Variable] A user-defined variable name
|
91
|
+
# @param aTerm [Term] A term to associate with the variable
|
92
|
+
def add_assoc(aName, aTerm)
|
93
|
+
name = aName.respond_to?(:name) ? aName.name : aName
|
94
|
+
|
95
|
+
var = name2var(name)
|
96
|
+
unless var
|
83
97
|
err_msg = "Unknown variable '#{name}'."
|
84
98
|
raise StandardError, err_msg
|
85
99
|
end
|
86
|
-
|
87
|
-
if
|
88
|
-
|
100
|
+
siblings = detect_fuse(var, aTerm)
|
101
|
+
if siblings.empty?
|
102
|
+
anAssociation = Association.new(var.i_name, aTerm)
|
103
|
+
do_add_assocs([anAssociation]).first
|
89
104
|
else
|
90
|
-
|
105
|
+
fuse_vars(siblings << var)
|
91
106
|
end
|
92
107
|
end
|
93
108
|
|
@@ -95,19 +110,117 @@ module MiniKraken
|
|
95
110
|
# Can be overridden in other to propagate associations from child
|
96
111
|
# @param _descendent [Outcome]
|
97
112
|
def propagate(_descendent)
|
98
|
-
#Do nothing...
|
113
|
+
# Do nothing...
|
99
114
|
end
|
100
115
|
|
101
|
-
# Remove all the associations
|
116
|
+
# Remove all the associations of this vocabulary
|
102
117
|
def clear
|
103
118
|
associations.clear
|
104
119
|
end
|
105
120
|
|
121
|
+
# @param aVarName [String] A user-defined variable name
|
122
|
+
# @param other [Vocabulary]
|
123
|
+
def move_assocs(aVarName, other)
|
124
|
+
i_name = to_internal(aVarName)
|
125
|
+
assocs = other.associations[i_name]
|
126
|
+
if assocs
|
127
|
+
do_add_assocs(assocs)
|
128
|
+
other.associations.delete(i_name)
|
129
|
+
end
|
130
|
+
end
|
131
|
+
|
106
132
|
# Merge the associations from another vocabulary-like object.
|
107
133
|
# @param another [Vocabulary]
|
108
134
|
def merge(another)
|
109
|
-
another.associations.
|
110
|
-
|
135
|
+
another.associations.each_value { |assocs| do_add_assocs(assocs) }
|
136
|
+
end
|
137
|
+
|
138
|
+
# Check that the provided variable must be fused with the argument.
|
139
|
+
# @return [Array<Variable>]
|
140
|
+
def detect_fuse(aVariable, aTerm)
|
141
|
+
return [] unless aTerm.kind_of?(VariableRef)
|
142
|
+
|
143
|
+
assocs = self[aTerm.var_name]
|
144
|
+
# Simplified implementation: cope with binary cycles only...
|
145
|
+
# TODO: Extend to n-ary (n > 2) cycles
|
146
|
+
assoc_refs = assocs.select { |a| a.value.kind_of?(VariableRef) }
|
147
|
+
return [] if assoc_refs.empty? # No relevant association...
|
148
|
+
|
149
|
+
visitees = Set.new
|
150
|
+
to_fuse = []
|
151
|
+
to_visit = assoc_refs
|
152
|
+
loop do
|
153
|
+
assc = to_visit.shift
|
154
|
+
next if visitees.include?(assc)
|
155
|
+
|
156
|
+
visitees.add(assc)
|
157
|
+
ref = assc.value
|
158
|
+
if ref.var_name == aVariable.name
|
159
|
+
to_fuse << assc.i_name unless assc.i_name == aVariable.i_name
|
160
|
+
end
|
161
|
+
other_assocs = self[ref.var_name]
|
162
|
+
other_assoc_refs = other_assocs.select { |a| a.value.kind_of?(VariableRef) }
|
163
|
+
other_assoc_refs.each do |a|
|
164
|
+
to_visit << a unless visitess.include?(a)
|
165
|
+
end
|
166
|
+
|
167
|
+
|
168
|
+
break if to_visit.empty?
|
169
|
+
end
|
170
|
+
|
171
|
+
to_fuse.map { |i_name| i_name2var(i_name) }
|
172
|
+
end
|
173
|
+
|
174
|
+
# Fuse the given variables, that is:
|
175
|
+
# Collect all their associations
|
176
|
+
# Put them under a new internal name
|
177
|
+
# Remove all entries from old internal names
|
178
|
+
# For all fused variables, change internal names
|
179
|
+
# @param theVars [Array<Variable>]
|
180
|
+
def fuse_vars(theVars)
|
181
|
+
new_i_name = Object.new.object_id.to_s
|
182
|
+
fused_vars = theVars.dup
|
183
|
+
fused_vars.each do |a_var|
|
184
|
+
old_i_name = a_var.i_name
|
185
|
+
old_names = fused_vars.map(&:name)
|
186
|
+
walker = ancestor_walker
|
187
|
+
|
188
|
+
loop do
|
189
|
+
voc = walker.next
|
190
|
+
break unless voc
|
191
|
+
|
192
|
+
if voc.associations.include?(old_i_name)
|
193
|
+
assocs = voc.associations[old_i_name]
|
194
|
+
keep_assocs = assocs.reject do |assc|
|
195
|
+
assc.value.kind_of?(VariableRef) && old_names.include?(assc.value.var_name)
|
196
|
+
end
|
197
|
+
unless keep_assocs.empty?
|
198
|
+
keep_assocs.each { |assc| assc.i_name = new_i_name }
|
199
|
+
if voc.associations.include?(new_i_name)
|
200
|
+
voc.associations[new_i_name].concat(keep_assocs)
|
201
|
+
else
|
202
|
+
voc.associations[new_i_name] = keep_assocs
|
203
|
+
end
|
204
|
+
end
|
205
|
+
voc.associations.delete(old_i_name)
|
206
|
+
end
|
207
|
+
next unless voc.respond_to?(:vars) && voc.vars.include?(a_var.name)
|
208
|
+
|
209
|
+
user_names = voc.ivars[old_i_name]
|
210
|
+
unseen = user_names.reject { |nm| old_names.include?(nm) }
|
211
|
+
unseen.each do |usr_name|
|
212
|
+
new_var = name2var(usr_name)
|
213
|
+
fused_vars << new_var
|
214
|
+
end
|
215
|
+
unless voc.ivars.include?(new_i_name)
|
216
|
+
voc.ivars[new_i_name] = user_names
|
217
|
+
else
|
218
|
+
voc.ivars[new_i_name].merge(user_names)
|
219
|
+
end
|
220
|
+
voc.ivars.delete(old_i_name)
|
221
|
+
break
|
222
|
+
end
|
223
|
+
a_var.i_name = new_i_name
|
111
224
|
end
|
112
225
|
end
|
113
226
|
|
@@ -119,7 +232,7 @@ module MiniKraken
|
|
119
232
|
end
|
120
233
|
|
121
234
|
# @param var [Variable, VariableRef] variable for which the value to retrieve
|
122
|
-
# @return [Term,
|
235
|
+
# @return [Term, NilClass]
|
123
236
|
def ground_value(var)
|
124
237
|
name = var.respond_to?(:var_name) ? var.var_name : var.name
|
125
238
|
|
@@ -127,7 +240,7 @@ module MiniKraken
|
|
127
240
|
walker.find_ground(name, self)
|
128
241
|
end
|
129
242
|
|
130
|
-
# @param
|
243
|
+
# @param val [CompositeTerm] the composite term to check.
|
131
244
|
# @return [Boolean]
|
132
245
|
def fresh_value?(val)
|
133
246
|
walker = AssociationWalker.new
|
@@ -149,7 +262,7 @@ module MiniKraken
|
|
149
262
|
# Determine whether the reference points to a fresh, bound or ground term.
|
150
263
|
# @param aVariableRef [VariableRef]
|
151
264
|
# @return [Freshness]
|
152
|
-
|
265
|
+
def freshness_ref(aVariableRef)
|
153
266
|
walker = AssociationWalker.new
|
154
267
|
walker.determine_freshness(aVariableRef, self)
|
155
268
|
end
|
@@ -161,33 +274,124 @@ module MiniKraken
|
|
161
274
|
walker.quote_term(aVariableRef, self)
|
162
275
|
end
|
163
276
|
|
164
|
-
#
|
165
|
-
# @
|
166
|
-
def
|
167
|
-
|
168
|
-
|
277
|
+
# Return the variable with given user-defined variable name.
|
278
|
+
# @param aName [String] User-defined variable name
|
279
|
+
def name2var(aName)
|
280
|
+
var = nil
|
281
|
+
walker = ancestor_walker
|
169
282
|
|
170
|
-
|
171
|
-
|
283
|
+
loop do
|
284
|
+
voc = walker.next
|
285
|
+
if voc
|
286
|
+
next unless voc.respond_to?(:vars) && voc.vars.include?(aName)
|
287
|
+
|
288
|
+
var = voc.vars[aName]
|
289
|
+
end
|
290
|
+
|
291
|
+
break
|
292
|
+
end
|
293
|
+
|
294
|
+
var
|
172
295
|
end
|
173
296
|
|
174
|
-
#
|
175
|
-
#
|
176
|
-
# @return [
|
177
|
-
def
|
178
|
-
|
297
|
+
# Return the variable with given internal variable name.
|
298
|
+
# @param i_name [String] internal variable name
|
299
|
+
# @return [Variable]
|
300
|
+
def i_name2var(i_name)
|
301
|
+
var = nil
|
302
|
+
voc = nil
|
179
303
|
walker = ancestor_walker
|
304
|
+
|
180
305
|
loop do
|
181
|
-
voc = walker.
|
306
|
+
voc = walker.next
|
182
307
|
if voc
|
183
|
-
next unless voc.respond_to?(:
|
184
|
-
|
308
|
+
next unless voc.respond_to?(:ivars) && voc.ivars.include?(i_name)
|
309
|
+
|
310
|
+
var_name = voc.ivars[i_name].first # TODO: what if multiple vars?
|
311
|
+
var = voc.vars[var_name]
|
185
312
|
end
|
186
313
|
|
187
314
|
break
|
188
315
|
end
|
189
316
|
|
190
|
-
|
317
|
+
raise StandardError, 'Nil variable object' if var.nil?
|
318
|
+
|
319
|
+
var
|
320
|
+
end
|
321
|
+
|
322
|
+
# Return the internal name to corresponding to a given user-defined
|
323
|
+
# variable name.
|
324
|
+
# @param aName [String] User-defined variable name
|
325
|
+
def to_internal(aName)
|
326
|
+
var = name2var(aName)
|
327
|
+
var ? var.i_name : nil
|
328
|
+
end
|
329
|
+
|
330
|
+
# Return the internal names fused with given user-defined
|
331
|
+
# variable name.
|
332
|
+
# @param aName [String] User-defined variable name
|
333
|
+
def names_fused(aName)
|
334
|
+
var = name2var(aName)
|
335
|
+
return [] unless var.fused?
|
336
|
+
|
337
|
+
i_name = var.i_name
|
338
|
+
names = []
|
339
|
+
walker = ancestor_walker
|
340
|
+
|
341
|
+
loop do
|
342
|
+
voc = walker.next
|
343
|
+
break unless voc
|
344
|
+
next unless voc.respond_to?(:ivars)
|
345
|
+
|
346
|
+
if voc.ivars.include?(i_name)
|
347
|
+
fused = voc.ivars[i_name]
|
348
|
+
names.concat(fused.to_a)
|
349
|
+
end
|
350
|
+
end
|
351
|
+
|
352
|
+
names.uniq!
|
353
|
+
names.reject { |nm| nm == aName }
|
354
|
+
end
|
355
|
+
|
356
|
+
# Retrieve all the associations for a given variable
|
357
|
+
# @param aVariable [Variable]
|
358
|
+
# @return [Array<Association>]
|
359
|
+
def assocs4var(aVariable)
|
360
|
+
i_name = aVariable.i_name
|
361
|
+
assocs = []
|
362
|
+
walker = ancestor_walker
|
363
|
+
|
364
|
+
loop do
|
365
|
+
voc = walker.next
|
366
|
+
break unless voc
|
367
|
+
next unless voc.associations.include?(i_name)
|
368
|
+
|
369
|
+
assocs.concat(voc.associations[i_name])
|
370
|
+
end
|
371
|
+
|
372
|
+
assocs
|
373
|
+
end
|
374
|
+
|
375
|
+
# @param aName [String] User-defined variable name
|
376
|
+
# @return [Array<Association>]
|
377
|
+
def [](aName)
|
378
|
+
iname = to_internal(aName)
|
379
|
+
return [] unless iname
|
380
|
+
|
381
|
+
assoc_arr = associations[iname]
|
382
|
+
assoc_arr = [] if assoc_arr.nil?
|
383
|
+
|
384
|
+
# TODO: Optimize
|
385
|
+
assoc_arr.concat(parent[aName]) if parent
|
386
|
+
assoc_arr
|
387
|
+
end
|
388
|
+
|
389
|
+
# Check that a variable with given name is defined in this vocabulary
|
390
|
+
# or one of its ancestor.
|
391
|
+
# @param aVarName [String] A user-defined variable name.
|
392
|
+
# @return [Boolean]
|
393
|
+
def include?(aVarName)
|
394
|
+
name2var(aVarName) ? true : false
|
191
395
|
end
|
192
396
|
|
193
397
|
protected
|
@@ -201,6 +405,21 @@ module MiniKraken
|
|
201
405
|
|
202
406
|
aParent
|
203
407
|
end
|
408
|
+
|
409
|
+
# @param theAssociations [Array<Association>]
|
410
|
+
def do_add_assocs(theAssociations)
|
411
|
+
theAssociations.each do |assc|
|
412
|
+
i_name = assc.i_name
|
413
|
+
found_assocs = associations[i_name]
|
414
|
+
if found_assocs
|
415
|
+
found_assocs << assc
|
416
|
+
else
|
417
|
+
associations[i_name] = [assc]
|
418
|
+
end
|
419
|
+
|
420
|
+
assc
|
421
|
+
end
|
422
|
+
end
|
204
423
|
end # class
|
205
424
|
end # module
|
206
|
-
end # module
|
425
|
+
end # module
|
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require_relative '../core/environment'
|
2
4
|
require_relative '../core/variable'
|
3
5
|
|
@@ -20,12 +22,12 @@ module MiniKraken
|
|
20
22
|
end
|
21
23
|
|
22
24
|
# Attempt to achieve the goal given this environment
|
23
|
-
# @param aParent [Environment]
|
24
|
-
# @return [Fiber<Outcome>] A Fiber object that will generate the results.
|
25
|
+
# @param aParent [Environment]
|
26
|
+
# @return [Fiber<Outcome>] A Fiber object that will generate the results.
|
25
27
|
def attain(aParent)
|
26
28
|
self.parent = aParent
|
27
29
|
goal.attain(self)
|
28
30
|
end
|
29
31
|
end # class
|
30
32
|
end # module
|
31
|
-
end # module
|
33
|
+
end # module
|
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require_relative '../core/any_value'
|
2
4
|
require_relative '../core/cons_cell'
|
3
5
|
require_relative 'fresh_env'
|
@@ -19,21 +21,29 @@ module MiniKraken
|
|
19
21
|
|
20
22
|
def run
|
21
23
|
result = nil
|
24
|
+
next_result = nil
|
22
25
|
solver = env.goal.attain(env)
|
23
|
-
# require 'debug'
|
26
|
+
# require 'debug'
|
24
27
|
loop do
|
28
|
+
env.clear
|
29
|
+
env.clear_rankings
|
25
30
|
outcome = solver.resume
|
26
31
|
break if outcome.nil?
|
27
|
-
|
32
|
+
|
28
33
|
if result # ... more than one result...
|
29
|
-
else
|
30
34
|
if outcome.successful?
|
31
|
-
|
32
|
-
# require 'debug'
|
33
|
-
result = Core::ConsCell.new(var.quote(outcome))
|
35
|
+
next_result.append(Core::ConsCell.new(var.quote(outcome)))
|
34
36
|
else
|
35
|
-
|
37
|
+
next_result.append(Core::NullList)
|
36
38
|
end
|
39
|
+
next_result = next_result.cdr
|
40
|
+
elsif outcome.successful?
|
41
|
+
env.propagate(outcome)
|
42
|
+
result = Core::ConsCell.new(var.quote(outcome))
|
43
|
+
next_result = result
|
44
|
+
else
|
45
|
+
result = Core::NullList
|
46
|
+
next_result = result
|
37
47
|
end
|
38
48
|
end
|
39
49
|
|
@@ -41,4 +51,4 @@ module MiniKraken
|
|
41
51
|
end
|
42
52
|
end # class
|
43
53
|
end # module
|
44
|
-
end # module
|
54
|
+
end # module
|