porolog 0.0.7 → 0.0.8
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/README.md +18 -4
- data/bin/porolog +38 -2
- data/coverage/badge.svg +1 -1
- data/coverage/index.html +52327 -6692
- data/doc/Array.html +113 -33
- data/doc/Object.html +11 -17
- data/doc/Porolog.html +3681 -73
- data/doc/Symbol.html +17 -20
- data/doc/_index.html +181 -13
- data/doc/class_list.html +1 -1
- data/doc/file.README.html +28 -38
- data/doc/index.html +28 -38
- data/doc/method_list.html +871 -167
- data/doc/top-level-namespace.html +2 -2
- data/lib/porolog.rb +1015 -2
- data/lib/porolog/arguments.rb +16 -14
- data/lib/porolog/core_ext.rb +6 -0
- data/lib/porolog/error.rb +6 -0
- data/lib/porolog/goal.rb +205 -68
- data/lib/porolog/instantiation.rb +300 -0
- data/lib/porolog/predicate.rb +33 -16
- data/lib/porolog/rule.rb +129 -2
- data/lib/porolog/scope.rb +3 -3
- data/lib/porolog/tail.rb +52 -0
- data/lib/porolog/value.rb +9 -12
- data/lib/porolog/variable.rb +307 -0
- data/test/porolog/arguments_test.rb +217 -135
- data/test/porolog/core_ext_test.rb +24 -17
- data/test/porolog/goal_test.rb +481 -74
- data/test/porolog/instantiation_test.rb +874 -0
- data/test/porolog/porolog_test.rb +2121 -13
- data/test/porolog/predicate_test.rb +1 -1
- data/test/porolog/rule_test.rb +395 -0
- data/test/porolog/scope_test.rb +0 -2
- data/test/porolog/tail_test.rb +127 -0
- data/test/porolog/value_test.rb +1 -1
- data/test/porolog/variable_test.rb +1625 -0
- data/test/test_helper.rb +78 -5
- metadata +12 -4
@@ -102,9 +102,9 @@
|
|
102
102
|
</div>
|
103
103
|
|
104
104
|
<div id="footer">
|
105
|
-
Generated on
|
105
|
+
Generated on Mon Jul 6 22:54:45 2020 by
|
106
106
|
<a href="http://yardoc.org" title="Yay! A Ruby Documentation Tool" target="_parent">yard</a>
|
107
|
-
0.9.19 (ruby-2.
|
107
|
+
0.9.19 (ruby-2.6.5).
|
108
108
|
</div>
|
109
109
|
|
110
110
|
</div>
|
data/lib/porolog.rb
CHANGED
@@ -5,12 +5,33 @@
|
|
5
5
|
# created
|
6
6
|
#
|
7
7
|
|
8
|
+
#
|
9
|
+
# @author Luis Esteban
|
10
|
+
#
|
8
11
|
module Porolog
|
9
12
|
|
10
13
|
# The most recent version of the Porolog gem.
|
11
|
-
VERSION = '0.0.
|
14
|
+
VERSION = '0.0.8'.freeze
|
12
15
|
# The most recent date of when the VERSION changed.
|
13
|
-
VERSION_DATE = '
|
16
|
+
VERSION_DATE = '2020-07-06'.freeze
|
17
|
+
|
18
|
+
# Represents an unknown tail of a list.
|
19
|
+
UNKNOWN_TAIL = Object.new
|
20
|
+
|
21
|
+
# Defines pretty version of an unknown tail.
|
22
|
+
def UNKNOWN_TAIL.inspect
|
23
|
+
'...'
|
24
|
+
end
|
25
|
+
|
26
|
+
# Specifies that the unknown tail is a list.
|
27
|
+
def UNKNOWN_TAIL.type
|
28
|
+
:array
|
29
|
+
end
|
30
|
+
|
31
|
+
UNKNOWN_TAIL.freeze
|
32
|
+
|
33
|
+
# Represents a list where all elements are unknown.
|
34
|
+
UNKNOWN_ARRAY = [UNKNOWN_TAIL].freeze
|
14
35
|
|
15
36
|
# A convenience method to create a Predicate, along with a method
|
16
37
|
# that returns an Arguments based on the arguments provided to
|
@@ -18,6 +39,9 @@ module Porolog
|
|
18
39
|
# @param names [Array<#to_sym>] names of the Predicates to create.
|
19
40
|
# @return [Porolog::Predicate] Predicate created if only one name is provided
|
20
41
|
# @return [Array<Porolog::Predicate>] Predicates created if multiple names are provided
|
42
|
+
# @example
|
43
|
+
# predicate :combobulator
|
44
|
+
# combobulator(:x,:y) # --> Porolog::Arguments
|
21
45
|
def predicate(*names)
|
22
46
|
names = [names].flatten
|
23
47
|
|
@@ -25,6 +49,7 @@ module Porolog
|
|
25
49
|
method = name.to_sym
|
26
50
|
predicate = Predicate.new(name)
|
27
51
|
Object.class_eval{
|
52
|
+
remove_method(method) if public_method_defined?(method)
|
28
53
|
define_method(method){|*args|
|
29
54
|
predicate.(*args)
|
30
55
|
}
|
@@ -35,13 +60,1001 @@ module Porolog
|
|
35
60
|
predicates.size > 1 && predicates || predicates.first
|
36
61
|
end
|
37
62
|
|
63
|
+
# Unify the Arguments of a Goal and a sub-Goal.
|
64
|
+
# @param goal [Porolog::Goal] a Goal to solve a Predicate for specific Arguments.
|
65
|
+
# @param subgoal [Porolog::Goal] a sub-Goal to solve the Goal following the Rules of the Predicate.
|
66
|
+
# @return [Boolean] whether the Goals can be unified.
|
67
|
+
def unify_goals(goal, subgoal)
|
68
|
+
if goal.arguments.predicate == subgoal.arguments.predicate
|
69
|
+
unifications = unify(goal.arguments.arguments, subgoal.arguments.arguments, goal, subgoal)
|
70
|
+
if unifications
|
71
|
+
instantiate_unifications(unifications)
|
72
|
+
else
|
73
|
+
msg = "Could not unify goals: #{goal.arguments.arguments.inspect} !~ #{subgoal.arguments.arguments.inspect}"
|
74
|
+
goal.log << msg
|
75
|
+
subgoal.log << msg
|
76
|
+
false
|
77
|
+
end
|
78
|
+
else
|
79
|
+
msg = "Cannot unify goals because they are for different predicates: #{goal.arguments.predicate.name.inspect} and #{subgoal.arguments.predicate.name.inspect}"
|
80
|
+
goal.log << msg
|
81
|
+
subgoal.log << msg
|
82
|
+
false
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
# Instantiates the unifications from an attempt to unify.
|
87
|
+
# @param unifications [Array] unifications to instantiate.
|
88
|
+
# @return [Boolean] whether the instantiations could be made.
|
89
|
+
def instantiate_unifications(unifications)
|
90
|
+
# -- Gather Unifications --
|
91
|
+
goals_variables = {}
|
92
|
+
|
93
|
+
return_false = false
|
94
|
+
unifications.each do |unification|
|
95
|
+
left, right, left_goal, right_goal = unification
|
96
|
+
|
97
|
+
goals_variables[left_goal] ||= {}
|
98
|
+
goals_variables[left_goal][left] ||= []
|
99
|
+
goals_variables[left_goal][left] << [right_goal,right]
|
100
|
+
|
101
|
+
return_false = true if goals_variables[left_goal][left].map(&:last).reject{|value|
|
102
|
+
value.is_a?(Variable) || value.is_a?(Symbol)
|
103
|
+
}.uniq.size > 1
|
104
|
+
|
105
|
+
return false if return_false
|
106
|
+
end
|
107
|
+
|
108
|
+
# -- Make Instantiations --
|
109
|
+
instantiations = []
|
110
|
+
consistent = true
|
111
|
+
|
112
|
+
goals_variables.each do |goal,variables|
|
113
|
+
variables.each do |name,others|
|
114
|
+
others.each do |other_goal,other|
|
115
|
+
instantiation = goal.instantiate(name, other, other_goal)
|
116
|
+
if instantiation
|
117
|
+
instantiations << instantiation
|
118
|
+
else
|
119
|
+
consistent = false
|
120
|
+
end
|
121
|
+
end
|
122
|
+
end
|
123
|
+
end
|
124
|
+
|
125
|
+
# -- Revert if inconsistent --
|
126
|
+
instantiations.each(&:remove) unless consistent
|
127
|
+
|
128
|
+
consistent
|
129
|
+
end
|
130
|
+
|
131
|
+
# Attempt to unify two entities of two goals.
|
132
|
+
# @param left [Object] left hand side entity.
|
133
|
+
# @param right [Object] right hand side entity.
|
134
|
+
# @param left_goal [Porolog::Goal] goal of left hand side entity.
|
135
|
+
# @param right_goal [Porolog::Goal] goal of right hand side entity.
|
136
|
+
# @param visited [Array] prevents infinite recursion.
|
137
|
+
# @return [Array] an Array of unifications when the left hand side can be unified with the right hand.
|
138
|
+
# @return [nil] nil if they cannot be unified.
|
139
|
+
def unify(left, right, left_goal, right_goal = left_goal, visited = [])
|
140
|
+
right_goal ||= left_goal
|
141
|
+
goals = [left_goal, right_goal].uniq
|
142
|
+
signature = [left.type, right.type]
|
143
|
+
|
144
|
+
# -- Nil is Uninstantiated (can always unify) --
|
145
|
+
return [] unless left && right
|
146
|
+
|
147
|
+
# -- Set HeadTail Goals --
|
148
|
+
case signature
|
149
|
+
when [:atomic, :atomic]
|
150
|
+
if left == right
|
151
|
+
[]
|
152
|
+
else
|
153
|
+
msg = "Cannot unify because #{left.inspect} != #{right.inspect} (atomic != atomic)"
|
154
|
+
goals.each{|goal| goal.log << msg }
|
155
|
+
nil
|
156
|
+
end
|
157
|
+
|
158
|
+
when [:array, :array]
|
159
|
+
_merged, unifications = unify_arrays(left, right, left_goal, right_goal, visited)
|
160
|
+
if unifications
|
161
|
+
unifications
|
162
|
+
else
|
163
|
+
msg = "Cannot unify because #{left.inspect} != #{right.inspect} (array != array)"
|
164
|
+
goals.each{|goal| goal.log << msg }
|
165
|
+
nil
|
166
|
+
end
|
167
|
+
|
168
|
+
when [:variable, :atomic]
|
169
|
+
left_value = left_goal.value_of(left, nil, visited)
|
170
|
+
right_value = right
|
171
|
+
if left_value == right_value || left_value.is_a?(Variable) || left_value.nil?
|
172
|
+
[[left, right, left_goal, right_goal]]
|
173
|
+
else
|
174
|
+
msg = "Cannot unify because #{left_value.inspect} != #{right_value.inspect} (variable != atomic)"
|
175
|
+
goals.each{|goal| goal.log << msg }
|
176
|
+
nil
|
177
|
+
end
|
178
|
+
|
179
|
+
when [:atomic, :variable]
|
180
|
+
left_value = left
|
181
|
+
right_value = right_goal.value_of(right, nil, visited)
|
182
|
+
if left == right_value || right_value.is_a?(Variable) || right_value.nil?
|
183
|
+
[[right,left,right_goal,left_goal]]
|
184
|
+
else
|
185
|
+
msg = "Cannot unify because #{left_value.inspect} != #{right_value.inspect} (atomic != variable)"
|
186
|
+
goals.each{|goal| goal.log << msg }
|
187
|
+
nil
|
188
|
+
end
|
189
|
+
|
190
|
+
when [:variable, :variable]
|
191
|
+
left_value = left_goal.value_of(left, nil, visited)
|
192
|
+
right_value = right_goal.value_of(right, nil, visited)
|
193
|
+
if left_value == right_value || left_value.is_a?(Variable) || right_value.is_a?(Variable) || left_value.nil? || right_value.nil?
|
194
|
+
[[left, right, left_goal, right_goal]]
|
195
|
+
else
|
196
|
+
msg = "Cannot unify because #{left_value.inspect} != #{right_value.inspect} (variable != variable)"
|
197
|
+
goals.each{|goal| goal.log << msg }
|
198
|
+
nil
|
199
|
+
end
|
200
|
+
|
201
|
+
when [:variable, :array]
|
202
|
+
left_value = left_goal.value_of(left, nil, visited)
|
203
|
+
right_value = right
|
204
|
+
if left_value == right_value || left_value.is_a?(Variable) || left_value == UNKNOWN_ARRAY || left_value.nil?
|
205
|
+
[[left, right, left_goal, right_goal]]
|
206
|
+
elsif left_value.type == :array
|
207
|
+
_merged, unifications = unify_arrays(left_value, right, left_goal, right_goal, visited)
|
208
|
+
if unifications
|
209
|
+
unifications
|
210
|
+
else
|
211
|
+
msg = "Cannot unify because #{left_value.inspect} != #{right.inspect} (variable/array != array)"
|
212
|
+
goals.each{|goal| goal.log << msg }
|
213
|
+
nil
|
214
|
+
end
|
215
|
+
else
|
216
|
+
msg = "Cannot unify because #{left_value.inspect} != #{right_value.inspect} (variable != array)"
|
217
|
+
goals.each{|goal| goal.log << msg }
|
218
|
+
nil
|
219
|
+
end
|
220
|
+
|
221
|
+
when [:array, :variable]
|
222
|
+
left_value = left
|
223
|
+
right_value = right_goal.value_of(right, nil, visited)
|
224
|
+
if left_value == right_value || right_value.is_a?(Variable) || right_value == UNKNOWN_ARRAY || right_value.nil?
|
225
|
+
[[right, left, right_goal, left_goal]]
|
226
|
+
elsif right_value.type == :array
|
227
|
+
_merged, unifications = unify_arrays(left, right_value, left_goal, right_goal, visited)
|
228
|
+
if unifications
|
229
|
+
unifications
|
230
|
+
else
|
231
|
+
msg = "Cannot unify because #{left.inspect} != #{right_value.inspect} (variable/array != array)"
|
232
|
+
goals.each{|goal| goal.log << msg }
|
233
|
+
nil
|
234
|
+
end
|
235
|
+
else
|
236
|
+
msg = "Cannot unify because #{left_value.inspect} != #{right_value.inspect} (array != variable)"
|
237
|
+
goals.each{|goal| goal.log << msg }
|
238
|
+
nil
|
239
|
+
end
|
240
|
+
|
241
|
+
when [:array,:atomic], [:atomic,:array]
|
242
|
+
msg = "Cannot unify #{left.inspect} with #{right.inspect}"
|
243
|
+
goals.each{|goal| goal.log << msg }
|
244
|
+
nil
|
245
|
+
|
246
|
+
else
|
247
|
+
# :nocov:
|
248
|
+
raise UnknownUnificationSignature, "UNKNOWN UNIFICATION SIGNATURE: #{signature.inspect}"
|
249
|
+
# :nocov:
|
250
|
+
end
|
251
|
+
end
|
252
|
+
|
253
|
+
# Attempt to unify two Arrays.
|
254
|
+
# @param left [Array] left hand side Array.
|
255
|
+
# @param right [Array] right hand side Array.
|
256
|
+
# @param left_goal [Porolog::Goal] goal of left hand side Array.
|
257
|
+
# @param right_goal [Porolog::Goal] goal of right hand side Array.
|
258
|
+
# @param visited [Array] prevents infinite recursion.
|
259
|
+
# @return [Array<Array, Array>] the merged Array and the unifications to be instantiated.
|
260
|
+
# @return [nil] if the Arrays cannot be unified.
|
261
|
+
def unify_arrays(left, right, left_goal, right_goal = left_goal, visited = [])
|
262
|
+
arrays = [left, right]
|
263
|
+
arrays_goals = [left_goal, right_goal]
|
264
|
+
arrays_values = arrays.map(&:value)
|
265
|
+
|
266
|
+
# -- Trivial Unifications --
|
267
|
+
return [left_goal. variablise(left), []] if right == UNKNOWN_ARRAY
|
268
|
+
return [right_goal.variablise(right),[]] if left == UNKNOWN_ARRAY
|
269
|
+
|
270
|
+
# -- Validate Arrays --
|
271
|
+
unless arrays_values.all?{|array| array.is_a?(Array) || array == UNKNOWN_TAIL }
|
272
|
+
msg = "Cannot unify a non-array with an array: #{left.inspect} with #{right.inspect}"
|
273
|
+
arrays_goals.uniq.each{|goal| goal.log << msg }
|
274
|
+
return nil
|
275
|
+
end
|
276
|
+
|
277
|
+
# -- Count Tails --
|
278
|
+
number_of_arrays = arrays.size
|
279
|
+
number_of_tails = arrays.count{|array| has_tail?(array) }
|
280
|
+
|
281
|
+
# -- Handle Tails --
|
282
|
+
if number_of_tails == 0
|
283
|
+
unify_arrays_with_no_tails(arrays, arrays_goals, visited)
|
284
|
+
elsif number_of_tails == number_of_arrays
|
285
|
+
unify_arrays_with_all_tails(arrays, arrays_goals, visited)
|
286
|
+
else
|
287
|
+
unify_arrays_with_some_tails(arrays, arrays_goals, visited)
|
288
|
+
end
|
289
|
+
end
|
290
|
+
|
291
|
+
# Attempt to unify multiple Arrays.
|
292
|
+
# @param arrays [Array<Array>] the Arrays to be unified.
|
293
|
+
# @param arrays_goals [Array<Porolog::Goal>] the Goals of the Arrays.
|
294
|
+
# @param visited [Array] prevents infinite recursion.
|
295
|
+
# @return [Array<Array, Array>] the merged Array and the unifications to be instantiated.
|
296
|
+
# @return [nil] if the Arrays cannot be unified.
|
297
|
+
def unify_many_arrays(arrays, arrays_goals, visited = [])
|
298
|
+
arrays_values = arrays.map{|array| expand_splat(array) }.map{|array| array.is_a?(Array) ? array.map{|element| element.value } : array.value }
|
299
|
+
|
300
|
+
unless arrays_values.all?{|array_values| [:array,:variable].include?(array_values.type) }
|
301
|
+
msg = "Cannot unify: #{arrays.map(&:inspect).join(' with ')}"
|
302
|
+
arrays_goals.uniq.each{|goal| goal.log << msg }
|
303
|
+
return nil
|
304
|
+
end
|
305
|
+
|
306
|
+
# TODO: Fix
|
307
|
+
if arrays_values.size == 2 && arrays_values.any?{|array| array == UNKNOWN_ARRAY }
|
308
|
+
merged = arrays_values.reject{|array| array == UNKNOWN_ARRAY }.first
|
309
|
+
return [merged,[]]
|
310
|
+
end
|
311
|
+
|
312
|
+
number_of_arrays = arrays.size
|
313
|
+
number_of_tails = arrays.count{|array| has_tail?(array) }
|
314
|
+
|
315
|
+
if number_of_tails == 0
|
316
|
+
unify_arrays_with_no_tails(arrays, arrays_goals, visited)
|
317
|
+
elsif number_of_tails == number_of_arrays
|
318
|
+
unify_arrays_with_all_tails(arrays, arrays_goals, visited)
|
319
|
+
else
|
320
|
+
unify_arrays_with_some_tails(arrays, arrays_goals, visited)
|
321
|
+
end
|
322
|
+
end
|
323
|
+
|
324
|
+
# @return [Boolean] whether the provided value has a Tail.
|
325
|
+
# @param value [Object] the provided value.
|
326
|
+
def has_tail?(value, apply_value = true)
|
327
|
+
value = value.value if value.respond_to?(:value) && apply_value
|
328
|
+
|
329
|
+
if value.is_a?(Array)
|
330
|
+
value.last == UNKNOWN_TAIL || value.last.is_a?(Tail)
|
331
|
+
else
|
332
|
+
false
|
333
|
+
end
|
334
|
+
end
|
335
|
+
|
336
|
+
# Expands a superfluous splat.
|
337
|
+
# @param array [Array] the given Array.
|
338
|
+
# @return [Array] the given Array with any superfluous splats expanded.
|
339
|
+
def expand_splat(array)
|
340
|
+
array = array.first.value if array.is_a?(Array) && array.length == 1 && array.first.is_a?(Tail)
|
341
|
+
|
342
|
+
if array.is_a?(Array) && array.last.is_a?(Tail) && array.last.value.is_a?(Array)
|
343
|
+
array = [*array[0...-1], *array.last.value]
|
344
|
+
end
|
345
|
+
|
346
|
+
if array.is_a?(Array)
|
347
|
+
array = array.map{|element|
|
348
|
+
expand_splat(element)
|
349
|
+
}
|
350
|
+
end
|
351
|
+
|
352
|
+
array
|
353
|
+
end
|
354
|
+
|
355
|
+
# Unifies Arrays where no Array has a Tail.
|
356
|
+
# @param arrays [Array<Array>] the Arrays to be unified.
|
357
|
+
# @param arrays_goals [Array<Porolog::Goal>] the Goals of the Arrays to be unified.
|
358
|
+
# @param visited [Array] prevents infinite recursion.
|
359
|
+
# @return [Array<Array, Array>] the merged Array and the unifications to be instantiated.
|
360
|
+
# @return [nil] if the Arrays cannot be unified.
|
361
|
+
def unify_arrays_with_no_tails(arrays, arrays_goals, visited)
|
362
|
+
# -- Validate Arrays --
|
363
|
+
if arrays.any?{|array| has_tail?(array) }
|
364
|
+
msg = "Wrong unification method called: no_tails but one or more of #{arrays.inspect} has a tail"
|
365
|
+
arrays_goals.uniq.each do |goal|
|
366
|
+
goal.log << msg
|
367
|
+
end
|
368
|
+
return nil
|
369
|
+
end
|
370
|
+
|
371
|
+
# -- Trivial Unifications --
|
372
|
+
return [[],[]] if arrays.empty?
|
373
|
+
|
374
|
+
# -- Ensure Arrays elements have Goals --
|
375
|
+
arrays_values = arrays.dup
|
376
|
+
arrays_goals = arrays_goals.dup
|
377
|
+
|
378
|
+
arrays.each_with_index do |array, index|
|
379
|
+
if array.is_a?(Value)
|
380
|
+
arrays_goals[index] ||= array.goal
|
381
|
+
arrays_values[index] = array.value
|
382
|
+
end
|
383
|
+
|
384
|
+
if arrays_goals[index].nil?
|
385
|
+
value_with_goal = array.find{|element| element.respond_to?(:goal) }
|
386
|
+
arrays_goals[index] = value_with_goal.goal if value_with_goal && value_with_goal.goal
|
387
|
+
end
|
388
|
+
|
389
|
+
if arrays_goals[index].nil?
|
390
|
+
raise NoGoalError, "Array #{array.inspect} has no goal for unification"
|
391
|
+
end
|
392
|
+
|
393
|
+
arrays_values[index] = expand_splat(arrays_values[index])
|
394
|
+
end
|
395
|
+
|
396
|
+
# -- Check whether all arrays are an Array --
|
397
|
+
unless arrays_values.all?{|array| array.is_a?(Array) }
|
398
|
+
merged, unifications = unify_many_arrays(arrays.map{|array| [array] }, arrays_goals, visited)
|
399
|
+
return [merged] + [unifications]
|
400
|
+
end
|
401
|
+
|
402
|
+
arrays_variables = arrays_goals.zip(arrays)
|
403
|
+
|
404
|
+
# -- Remap Arrays so that they are variablised and valuised with their Goals --
|
405
|
+
new_arrays = []
|
406
|
+
arrays_variables.each do |goal,variables|
|
407
|
+
new_array = variables.map{|variable|
|
408
|
+
value = goal.value_of(variable, nil, visited).value
|
409
|
+
value = variable if value == UNKNOWN_TAIL || value == UNKNOWN_ARRAY
|
410
|
+
|
411
|
+
if value.type == :variable
|
412
|
+
value = goal.variable(value)
|
413
|
+
elsif value.is_a?(Array)
|
414
|
+
value = value.map{|element|
|
415
|
+
if element.type == :variable
|
416
|
+
goal.variable(element)
|
417
|
+
elsif element.is_a?(Value)
|
418
|
+
element
|
419
|
+
elsif element.is_a?(Tail)
|
420
|
+
element
|
421
|
+
else
|
422
|
+
goal.value(element)
|
423
|
+
end
|
424
|
+
}
|
425
|
+
elsif !value.is_a?(Value)
|
426
|
+
value = goal.value(value)
|
427
|
+
end
|
428
|
+
value
|
429
|
+
}
|
430
|
+
new_arrays << new_array
|
431
|
+
end
|
432
|
+
arrays = new_arrays
|
433
|
+
|
434
|
+
# -- Unify All Elements --
|
435
|
+
sizes = arrays.map(&:length).uniq
|
436
|
+
if sizes.size <= 1
|
437
|
+
# -- Arrays are the same length --
|
438
|
+
zipped = arrays[0].zip(*arrays[1..-1]).map(&:uniq).map(&:compact).uniq
|
439
|
+
unifications = []
|
440
|
+
merged = []
|
441
|
+
|
442
|
+
# TODO: Change these names
|
443
|
+
zipped.each_with_index{|values_to_unify, index|
|
444
|
+
values_to_unify_values = values_to_unify.map{|value|
|
445
|
+
value_value = value.value
|
446
|
+
if value_value == UNKNOWN_TAIL || value_value == UNKNOWN_ARRAY
|
447
|
+
value
|
448
|
+
else
|
449
|
+
value_value
|
450
|
+
end
|
451
|
+
}.compact.uniq
|
452
|
+
|
453
|
+
if values_to_unify_values.size <= 1
|
454
|
+
# -- One Value --
|
455
|
+
value = values_to_unify_values.first
|
456
|
+
if value.type == :variable
|
457
|
+
merged << nil
|
458
|
+
else
|
459
|
+
merged << value.value
|
460
|
+
end
|
461
|
+
else
|
462
|
+
if values_to_unify_values.all?{|value| value.is_a?(Array) }
|
463
|
+
submerged, subunifications = unify_many_arrays(values_to_unify_values, arrays_goals)
|
464
|
+
if subunifications
|
465
|
+
merged << submerged
|
466
|
+
unifications += subunifications
|
467
|
+
else
|
468
|
+
msg = "Cannot unify: #{values_to_unify_values.map(&:inspect).join(' with ')}"
|
469
|
+
arrays_goals.uniq.each{|goal| goal.log << msg }
|
470
|
+
return nil
|
471
|
+
end
|
472
|
+
elsif values_to_unify.variables.empty?
|
473
|
+
# -- Incompatible Values --
|
474
|
+
incompatible_values = values_to_unify.map(&:value)
|
475
|
+
msg = "Cannot unify incompatible values: #{incompatible_values.compact.map(&:inspect).join(' with ')}"
|
476
|
+
arrays_goals.uniq.each{|goal| goal.log << msg }
|
477
|
+
return nil
|
478
|
+
else
|
479
|
+
# -- Potentially Compatible Values/Variables --
|
480
|
+
nonvariables = values_to_unify.map(&:value).compact.reject{|value| value.type == :variable }
|
481
|
+
|
482
|
+
values_to_unify.reject{|value| value.value.nil? }.combination(2).each do |vl, vr|
|
483
|
+
subunifications = unify(vl, vr, vl.goal, vr.goal, visited)
|
484
|
+
if subunifications
|
485
|
+
unifications += subunifications
|
486
|
+
else
|
487
|
+
msg = "Cannot unify: #{vl.inspect} with #{vr.inspect}"
|
488
|
+
[vl.goal, vr.goal].uniq.each{|goal| goal.log << msg }
|
489
|
+
return nil
|
490
|
+
end
|
491
|
+
end
|
492
|
+
|
493
|
+
if nonvariables.size > 1
|
494
|
+
subgoals = arrays_goals.dup
|
495
|
+
nonvariables.each_with_index do |nonvariable, index|
|
496
|
+
if nonvariable.respond_to?(:goal)
|
497
|
+
subgoal = nonvariable.goal
|
498
|
+
subgoals[index] = subgoal if subgoal
|
499
|
+
end
|
500
|
+
end
|
501
|
+
subgoals = subgoals[0...nonvariables.size]
|
502
|
+
merged_nonvariables, _nonvariable_unifications = unify_many_arrays(nonvariables, subgoals, visited)
|
503
|
+
else
|
504
|
+
merged_nonvariables = nonvariables
|
505
|
+
end
|
506
|
+
|
507
|
+
merged_value = merged_nonvariables
|
508
|
+
merged_value = [nonvariables.first.value] if merged_value.nil? || merged_value == []
|
509
|
+
merged += merged_value
|
510
|
+
end
|
511
|
+
end
|
512
|
+
}
|
513
|
+
|
514
|
+
[merged, unifications]
|
515
|
+
else
|
516
|
+
# -- Arrays are different lengths --
|
517
|
+
msg = "Cannot unify arrays of different lengths: #{arrays_values.map(&:inspect).join(' with ')}"
|
518
|
+
arrays_goals.uniq.each{|goal| goal.log << msg }
|
519
|
+
nil
|
520
|
+
end
|
521
|
+
end
|
522
|
+
|
523
|
+
# Unifies Arrays where each Array has a Tail.
|
524
|
+
# @param arrays [Array<Array>] the Arrays to be unified.
|
525
|
+
# @param arrays_goals [Array<Porolog::Goal>] the Goals of the Arrays to be unified.
|
526
|
+
# @param visited [Array] prevents infinite recursion.
|
527
|
+
# @return [Array<Array, Array>] the merged Array and the unifications to be instantiated.
|
528
|
+
# @return [nil] if the Arrays cannot be unified.
|
529
|
+
def unify_arrays_with_all_tails(arrays, arrays_goals, visited)
|
530
|
+
unifications = []
|
531
|
+
merged = []
|
532
|
+
|
533
|
+
# -- All tails --
|
534
|
+
signature = arrays.map(&:headtail?)
|
535
|
+
arrays = arrays.map{|array|
|
536
|
+
expand_splat(array.headtail? ? array : array.value)
|
537
|
+
}
|
538
|
+
signature = arrays.map(&:headtail?)
|
539
|
+
|
540
|
+
# -- Unify Embedded Arrays --
|
541
|
+
if arrays.any?{|array| array.type == :variable }
|
542
|
+
unifications = unify(*arrays[0...2], *arrays_goals[0...2], visited)
|
543
|
+
if unifications
|
544
|
+
merged = arrays.reject{|value| value.type == :variable }.first || nil
|
545
|
+
return [merged, unifications]
|
546
|
+
else
|
547
|
+
msg = "Cannot unify embedded arrays: #{arrays[0...2].map(&:value).map(&:inspect).join(' with ')}"
|
548
|
+
arrays_goals.uniq.each do |goal|
|
549
|
+
goal.log << msg
|
550
|
+
end
|
551
|
+
return nil
|
552
|
+
end
|
553
|
+
end
|
554
|
+
|
555
|
+
signature = arrays.map(&:headtail?)
|
556
|
+
unifications = []
|
557
|
+
merged = []
|
558
|
+
|
559
|
+
if signature.all?
|
560
|
+
merged, unifications = unify_headtail_with_headtail(arrays, arrays_goals, visited)
|
561
|
+
elsif signature.map(&:!).all?
|
562
|
+
merged, unifications = unify_tail_with_tail(arrays, arrays_goals, visited)
|
563
|
+
else
|
564
|
+
merged, unifications = unify_headtail_with_tail(arrays, arrays_goals, visited)
|
565
|
+
end
|
566
|
+
|
567
|
+
if unifications
|
568
|
+
return [merged, unifications]
|
569
|
+
else
|
570
|
+
return nil
|
571
|
+
end
|
572
|
+
end
|
573
|
+
|
574
|
+
# Unifies Arrays where each Array is not a Head/Tail array.
|
575
|
+
# @param arrays [Array<Array>] the Arrays to be unified.
|
576
|
+
# @param arrays_goals [Array<Porolog::Goal>] the Goals of the Arrays to be unified.
|
577
|
+
# @param visited [Array] prevents infinite recursion.
|
578
|
+
# @return [Array<Array, Array>] the merged Array and the unifications to be instantiated.
|
579
|
+
# @return [nil] if the Arrays cannot be unified.
|
580
|
+
def unify_tail_with_tail(arrays, arrays_goals, visited)
|
581
|
+
# -- Validate Arrays --
|
582
|
+
unless arrays.map(&:headtail?).map(&:!).all?
|
583
|
+
msg = "Wrong method called to unify #{arrays.inspect}"
|
584
|
+
arrays_goals.uniq.each do |goal|
|
585
|
+
goal.log << msg
|
586
|
+
end
|
587
|
+
return nil
|
588
|
+
end
|
589
|
+
|
590
|
+
# -- Variablise Arrays --
|
591
|
+
arrays = arrays_goals.zip(arrays).map do |goal, array|
|
592
|
+
goal.variablise(array)
|
593
|
+
end
|
594
|
+
|
595
|
+
# == [1,2,3]/:tail ~~ [1,2,3]/:tail ==
|
596
|
+
# -- Extract Elements --
|
597
|
+
merged = []
|
598
|
+
unifications = []
|
599
|
+
|
600
|
+
arrays = arrays.sort_by{|array| -array.length }
|
601
|
+
|
602
|
+
arrays[0].zip(*arrays[1..-1]).each_with_index do |values,index|
|
603
|
+
if values.any?{|value| value.is_a?(Tail) }
|
604
|
+
# -- Unify Remaining Values and Tails --
|
605
|
+
tails = arrays.map{|array| expand_splat(array[index..-1]) }
|
606
|
+
merged_tails = []
|
607
|
+
tails.combination(2).each do |pair|
|
608
|
+
next if pair.compact.size < 2
|
609
|
+
if pair.any?{|tail| tail == UNKNOWN_ARRAY }
|
610
|
+
merged_tails += pair.reject{|tail| tail == UNKNOWN_ARRAY }
|
611
|
+
next
|
612
|
+
end
|
613
|
+
first_goal = nil
|
614
|
+
last_goal = nil
|
615
|
+
first_goal = pair.first.goal if pair.first.respond_to?(:goal)
|
616
|
+
last_goal = pair.last.goal if pair.last.respond_to?(:goal)
|
617
|
+
m,u = unify_arrays(first_goal.variablise([pair.first]), last_goal.variablise([pair.last]), first_goal, last_goal, visited)
|
618
|
+
unless m
|
619
|
+
return nil
|
620
|
+
end
|
621
|
+
m[-1] = UNKNOWN_TAIL if m[-1] == nil
|
622
|
+
merged_tails += m
|
623
|
+
unifications += u
|
624
|
+
end
|
625
|
+
merged_tails.uniq!
|
626
|
+
merged_tails_unifications = []
|
627
|
+
# TODO: Fix [first_goal] * arrays.size
|
628
|
+
if merged_tails.size > 1
|
629
|
+
merged_tails, merged_tails_unifications = unify_many_arrays(merged_tails, [arrays_goals.first] * arrays.size, visited)
|
630
|
+
end
|
631
|
+
merged += merged_tails.flatten(1)
|
632
|
+
unifications += merged_tails_unifications - unifications
|
633
|
+
break
|
634
|
+
else
|
635
|
+
# -- Unify Values at Index --
|
636
|
+
merged_value = []
|
637
|
+
values.combination(2).each do |pair|
|
638
|
+
if pair.any?(&:nil?)
|
639
|
+
merged_value += pair.compact
|
640
|
+
next
|
641
|
+
end
|
642
|
+
first_goal = nil
|
643
|
+
last_goal = nil
|
644
|
+
first_goal = pair.first.goal if pair.first.respond_to?(:goal)
|
645
|
+
last_goal = pair.last.goal if pair.last.respond_to?(:goal)
|
646
|
+
|
647
|
+
m,u = unify_arrays([pair.first], [pair.last], first_goal, last_goal, visited)
|
648
|
+
if m.nil?
|
649
|
+
[first_goal, last_goal].uniq.each do |goal|
|
650
|
+
goal.log << "Cannot unify #{pair.first.inspect} with #{pair.last.inspect}"
|
651
|
+
end
|
652
|
+
return nil
|
653
|
+
end
|
654
|
+
merged_value += m
|
655
|
+
unifications += u
|
656
|
+
end
|
657
|
+
merged << merged_value.compact.uniq.first || nil
|
658
|
+
end
|
659
|
+
end
|
660
|
+
|
661
|
+
[merged, unifications]
|
662
|
+
end
|
663
|
+
|
664
|
+
# Unifies Arrays where the Arrays are a mixture of Head/Tail and non-Head/Tail arrays.
|
665
|
+
# @param arrays [Array<Array>] the Arrays to be unified.
|
666
|
+
# @param arrays_goals [Array<Porolog::Goal>] the Goals of the Arrays to be unified.
|
667
|
+
# @param visited [Array] prevents infinite recursion.
|
668
|
+
# @return [Array<Array, Array>] the merged Array and the unifications to be instantiated.
|
669
|
+
# @return [nil] if the Arrays cannot be unified.
|
670
|
+
def unify_headtail_with_tail(arrays, arrays_goals, visited)
|
671
|
+
# -- Validate Arrays --
|
672
|
+
unless arrays.all?{|array| has_tail?(array, false) }
|
673
|
+
msg = "Wrong method called to unify #{arrays.inspect}"
|
674
|
+
arrays_goals.uniq.each do |goal|
|
675
|
+
goal.log << msg
|
676
|
+
end
|
677
|
+
return nil
|
678
|
+
end
|
679
|
+
|
680
|
+
merged = []
|
681
|
+
unifications = []
|
682
|
+
|
683
|
+
# -- Variablise Arrays --
|
684
|
+
arrays = arrays_goals.zip(arrays).map do |goal, array|
|
685
|
+
goal.variablise(array)
|
686
|
+
end
|
687
|
+
|
688
|
+
# -- Determine the fixed length (if any) --
|
689
|
+
fixed_length = nil
|
690
|
+
arrays.each do |array|
|
691
|
+
unless has_tail?(array)
|
692
|
+
array_length = array.value.size
|
693
|
+
fixed_length ||= array_length
|
694
|
+
unless fixed_length == array_length
|
695
|
+
array.goal.log << "Cannot unify #{array.value.inspect} because it has a different length from #{fixed_length}"
|
696
|
+
return nil
|
697
|
+
end
|
698
|
+
end
|
699
|
+
end
|
700
|
+
|
701
|
+
# -- Partition Arrays --
|
702
|
+
headtail_arrays, tail_arrays = arrays.partition(&:headtail?)
|
703
|
+
|
704
|
+
# -- Unify All HeadTail Arrays --
|
705
|
+
if headtail_arrays.size > 1
|
706
|
+
headtail_goals = headtail_arrays.map{|array| array.goal }
|
707
|
+
merged_headtails, headtail_unifications = unify_headtail_with_headtail(headtail_arrays, headtail_goals, visited)
|
708
|
+
unless merged_headtails
|
709
|
+
msg = "Could not unify headtail arrays: #{headtail_arrays.map(&:value).map(&:inspect).join(' with ')}"
|
710
|
+
headtail_goals.uniq.each do |goal|
|
711
|
+
goal.log << msg
|
712
|
+
end
|
713
|
+
return nil
|
714
|
+
end
|
715
|
+
unifications += headtail_unifications
|
716
|
+
|
717
|
+
if merged_headtails.length > merged.length
|
718
|
+
merged += [[]] * (merged_headtails.length - merged.length)
|
719
|
+
end
|
720
|
+
|
721
|
+
# TODO: Remove flatten
|
722
|
+
merged = merged.zip(merged_headtails).map(&:flatten)
|
723
|
+
end
|
724
|
+
|
725
|
+
# -- Unify All Tail Arrays --
|
726
|
+
if tail_arrays.size > 1
|
727
|
+
tail_goals = tail_arrays.map{|array| array.goal }
|
728
|
+
merged_tails, tail_unifications = unify_tail_with_tail(tail_arrays, tail_goals, visited)
|
729
|
+
return nil unless merged_tails
|
730
|
+
unifications += tail_unifications
|
731
|
+
|
732
|
+
if merged_tails.length > merged.length
|
733
|
+
merged += [[]] * (merged_tails.length - merged.length)
|
734
|
+
end
|
735
|
+
|
736
|
+
# TODO: Remove flatten
|
737
|
+
merged = merged.zip(merged_tails).map(&:flatten).map{|merge_values|
|
738
|
+
merge_values.map{|value|
|
739
|
+
if value == UNKNOWN_TAIL
|
740
|
+
nil
|
741
|
+
else
|
742
|
+
value
|
743
|
+
end
|
744
|
+
}
|
745
|
+
}.map(&:compact)
|
746
|
+
end
|
747
|
+
|
748
|
+
# -- Combine HeadTail Arrays and Tail Arrays --
|
749
|
+
headtail_arrays.product(tail_arrays).each do |pair|
|
750
|
+
# == :head/:tail ~~ [1,2,3]/:tail ==
|
751
|
+
# -- Extract Elements --
|
752
|
+
left = expand_splat(pair.first)
|
753
|
+
left_head = left.head
|
754
|
+
left_tail = left.tail
|
755
|
+
|
756
|
+
right = expand_splat(pair.last)
|
757
|
+
right_head = right.head
|
758
|
+
right_tail = right.tail
|
759
|
+
|
760
|
+
# -- Expand Tail --
|
761
|
+
left_tail = expand_splat(left_tail)
|
762
|
+
right_tail = expand_splat(right_tail)
|
763
|
+
|
764
|
+
# -- Determine Goals --
|
765
|
+
left_goal = pair.first.goal
|
766
|
+
right_goal = pair.last.goal
|
767
|
+
|
768
|
+
# -- Unify Heads --
|
769
|
+
head_unifications = unify(left_head, right_head, left_goal, right_goal, visited)
|
770
|
+
if head_unifications.nil?
|
771
|
+
msg = "Cannot unify heads: #{left_head.inspect} with #{right_head.inspect}"
|
772
|
+
left_goal.log << msg
|
773
|
+
right_goal.log << msg unless right_goal == left_goal
|
774
|
+
return nil
|
775
|
+
end
|
776
|
+
unifications += head_unifications
|
777
|
+
|
778
|
+
# -- Unify Tails --
|
779
|
+
tail_unifications = unify(left_tail, right_tail, left_goal, right_goal, visited)
|
780
|
+
if tail_unifications.nil?
|
781
|
+
msg = "Cannot unify tails: #{left_tail.inspect} with #{right_tail.inspect}"
|
782
|
+
left_goal.log << msg
|
783
|
+
right_goal.log << msg unless right_goal == left_goal
|
784
|
+
return nil
|
785
|
+
end
|
786
|
+
unifications += tail_unifications
|
787
|
+
|
788
|
+
# -- Determine Merged and Unifications --
|
789
|
+
left_reassembled = [left_head, *left_tail ]
|
790
|
+
right_reassembled = [right_head, *right_tail]
|
791
|
+
max_length = [left_reassembled.length, right_reassembled.length].max
|
792
|
+
|
793
|
+
if max_length > merged.length
|
794
|
+
merged += [[]] * (max_length - merged.length)
|
795
|
+
end
|
796
|
+
|
797
|
+
merged = merged.zip(left_reassembled ).map(&:flatten)
|
798
|
+
merged = merged.zip(right_reassembled).map(&:flatten)
|
799
|
+
end
|
800
|
+
|
801
|
+
merged = merged.value
|
802
|
+
merged = merged.map(&:value)
|
803
|
+
|
804
|
+
# TODO: Cleanup names
|
805
|
+
# TODO: Flatten out tails
|
806
|
+
# E.g. [nil, [2, 3], ...] should be [nil, 2, 3, ...]
|
807
|
+
is_tails = []
|
808
|
+
merged = merged.value.map{|elements|
|
809
|
+
sorted_elements = elements.reject{|element|
|
810
|
+
element.is_a?(Variable)
|
811
|
+
}.uniq.compact.sort_by{|element|
|
812
|
+
case element.type
|
813
|
+
when :atomic
|
814
|
+
0
|
815
|
+
when :array
|
816
|
+
if [UNKNOWN_TAIL, UNKNOWN_ARRAY].include?(element)
|
817
|
+
3
|
818
|
+
else
|
819
|
+
1
|
820
|
+
end
|
821
|
+
else
|
822
|
+
# :nocov:
|
823
|
+
# There are only 3 types and variables have already been filtered.
|
824
|
+
2
|
825
|
+
# :nocov:
|
826
|
+
end
|
827
|
+
}
|
828
|
+
|
829
|
+
is_tails << sorted_elements.any?{|element| element.is_a?(Tail) || element == UNKNOWN_TAIL }
|
830
|
+
|
831
|
+
merged_value = sorted_elements.first
|
832
|
+
|
833
|
+
if merged_value.is_a?(Tail) && merged_value.value.is_a?(Variable)
|
834
|
+
UNKNOWN_TAIL
|
835
|
+
else
|
836
|
+
merged_value
|
837
|
+
end
|
838
|
+
}
|
839
|
+
merged[0...-1] = merged[0...-1].map{|value|
|
840
|
+
if value == UNKNOWN_TAIL
|
841
|
+
nil
|
842
|
+
else
|
843
|
+
value
|
844
|
+
end
|
845
|
+
}
|
846
|
+
|
847
|
+
merged = merged.map{|value|
|
848
|
+
if is_tails.shift
|
849
|
+
value
|
850
|
+
else
|
851
|
+
[value]
|
852
|
+
end
|
853
|
+
}
|
854
|
+
merged = merged.flatten(1)
|
855
|
+
merged = merged[0...fixed_length] if fixed_length
|
856
|
+
|
857
|
+
[merged, unifications]
|
858
|
+
end
|
859
|
+
|
860
|
+
# Unifies Arrays where each Array is a Head/Tail array.
|
861
|
+
# @param arrays [Array<Array>] the Arrays to be unified.
|
862
|
+
# @param arrays_goals [Array<Porolog::Goal>] the Goals of the Arrays to be unified.
|
863
|
+
# @param visited [Array] prevents infinite recursion.
|
864
|
+
# @return [Array<Array, Array>] the merged Array and the unifications to be instantiated.
|
865
|
+
# @return [nil] if the Arrays cannot be unified.
|
866
|
+
def unify_headtail_with_headtail(arrays, arrays_goals, visited)
|
867
|
+
unless arrays.all?(&:headtail?)
|
868
|
+
msg = "Wrong method called to unify #{arrays.inspect}"
|
869
|
+
arrays_goals.uniq.each do |goal|
|
870
|
+
goal.log << msg
|
871
|
+
end
|
872
|
+
return nil
|
873
|
+
end
|
874
|
+
|
875
|
+
unifications = []
|
876
|
+
|
877
|
+
arrays.combination(2).each do |pair|
|
878
|
+
# -- Collect Goals --
|
879
|
+
pair_goals = pair.map{|array| arrays_goals[arrays.index(array)] }
|
880
|
+
|
881
|
+
# -- Unify Heads --
|
882
|
+
heads = pair.map(&:first)
|
883
|
+
subunifications = unify(*heads, *pair_goals, visited)
|
884
|
+
if subunifications
|
885
|
+
unifications += subunifications
|
886
|
+
else
|
887
|
+
unifications = nil
|
888
|
+
msg = "Cannot unify headtail heads: #{heads.map(&:inspect).join(' with ').inspect}"
|
889
|
+
pair_goals.uniq.each do |goal|
|
890
|
+
goal.log << msg
|
891
|
+
end
|
892
|
+
|
893
|
+
return nil
|
894
|
+
end
|
895
|
+
|
896
|
+
# -- Unify Tails --
|
897
|
+
tails = pair.map(&:last).map{|tail| tail.value(visited) }
|
898
|
+
subunifications = unify(*tails, *pair_goals, visited)
|
899
|
+
if subunifications
|
900
|
+
unifications += subunifications
|
901
|
+
else
|
902
|
+
unifications = nil
|
903
|
+
msg = "Cannot unify headtail tails: #{tails.map(&:inspect).join(' with ')}"
|
904
|
+
pair_goals.uniq.each do |goal|
|
905
|
+
goal.log << msg
|
906
|
+
end
|
907
|
+
|
908
|
+
return nil
|
909
|
+
end
|
910
|
+
end
|
911
|
+
|
912
|
+
# -- Determine Merged --
|
913
|
+
merged = [
|
914
|
+
arrays.map(&:first).map{|head| head.value(visited) }.reject{|head| head.type == :variable }.first,
|
915
|
+
*arrays.map(&:last).map{|tail| tail.value(visited) }.reject{|tail| tail.type == :variable || tail == UNKNOWN_TAIL }.first || UNKNOWN_TAIL,
|
916
|
+
]
|
917
|
+
|
918
|
+
[merged, unifications]
|
919
|
+
end
|
920
|
+
|
921
|
+
# Unifies Arrays where Arrays with a Tail are unified with Arrays without a Tail.
|
922
|
+
# @param arrays [Array<Array>] the Arrays to be unified.
|
923
|
+
# @param arrays_goals [Array<Porolog::Goal>] the Goals of the Arrays to be unified.
|
924
|
+
# @param visited [Array] prevents infinite recursion.
|
925
|
+
# @return [Array<Array, Array>] the merged Array and the unifications to be instantiated.
|
926
|
+
# @return [nil] if the Arrays cannot be unified.
|
927
|
+
def unify_arrays_with_some_tails(arrays, arrays_goals, visited)
|
928
|
+
# -- Variablise Arrays --
|
929
|
+
arrays = arrays_goals.zip(arrays).map{|goal,array|
|
930
|
+
if goal.nil?
|
931
|
+
goal = array.goal if array.is_a?(Value)
|
932
|
+
if goal.nil? && array.is_a?(Array)
|
933
|
+
v = array.find{|element| element.respond_to?(:goal) }
|
934
|
+
goal = v && v.goal
|
935
|
+
end
|
936
|
+
end
|
937
|
+
goal = arrays_goals.compact.last if goal.nil?
|
938
|
+
raise NoGoalError, "#{array.inspect} has no associated goal! Cannot variablise!" if goal.nil? || !goal.is_a?(Goal)
|
939
|
+
goal.variablise(array)
|
940
|
+
}
|
941
|
+
|
942
|
+
# -- Some unknown tails --
|
943
|
+
tailed_arrays, finite_arrays = arrays.partition{|array| has_tail?(array) }
|
944
|
+
|
945
|
+
finite_sizes = finite_arrays.reject{|finite_array| finite_array.type == :variable }.map(&:size).uniq
|
946
|
+
|
947
|
+
unless finite_sizes.size == 1
|
948
|
+
msg = "Cannot unify different sizes of arrays: #{arrays.map(&:inspect).join(' with ')}"
|
949
|
+
arrays_goals.uniq.each do |goal|
|
950
|
+
goal.log << msg
|
951
|
+
end
|
952
|
+
return nil
|
953
|
+
end
|
954
|
+
|
955
|
+
exact_size = finite_sizes.first
|
956
|
+
|
957
|
+
tails = tailed_arrays.map{|array| [array[0...-1].size,array.last,arrays_goals[arrays.index(array)]] }
|
958
|
+
tailed_arrays = tailed_arrays.map{|array| array[0...-1] }
|
959
|
+
|
960
|
+
# -- Fail --
|
961
|
+
# [nil,nil,nil,nil,...]
|
962
|
+
# [1, 2, 3]
|
963
|
+
min_tailed_size = tailed_arrays.map(&:size).max
|
964
|
+
|
965
|
+
if min_tailed_size > exact_size
|
966
|
+
msg = "Cannot unify enough elements: #{arrays.map(&:inspect).join(' with ')}"
|
967
|
+
arrays_goals.uniq.each do |goal|
|
968
|
+
goal.log << msg
|
969
|
+
end
|
970
|
+
return nil
|
971
|
+
end
|
972
|
+
|
973
|
+
# -- Succeed --
|
974
|
+
# [nil,nil,...]
|
975
|
+
# [1, 2, 3]
|
976
|
+
arrays = tailed_arrays + finite_arrays
|
977
|
+
|
978
|
+
zip_arrays = arrays.map{|array|
|
979
|
+
if array.is_a?(Value) && array.value.is_a?(Array)
|
980
|
+
array.value.map{|v|
|
981
|
+
if v.type == :variable
|
982
|
+
array.goal.variable(v)
|
983
|
+
else
|
984
|
+
array.goal.value(v)
|
985
|
+
end
|
986
|
+
}
|
987
|
+
elsif array.type == :variable
|
988
|
+
array.goal.value_of(array)
|
989
|
+
else
|
990
|
+
array
|
991
|
+
end
|
992
|
+
}.select{|array| array.is_a?(Array) }
|
993
|
+
|
994
|
+
zipped = ([nil] * exact_size).zip(*zip_arrays).map(&:uniq).map(&:compact)
|
995
|
+
merged = []
|
996
|
+
unifications = []
|
997
|
+
|
998
|
+
zipped.each{|zipped_values|
|
999
|
+
values = zipped_values
|
1000
|
+
value = values.reject{|v| v.value(visited).nil? }.compact.uniq
|
1001
|
+
value_values = value.map(&:value).compact.uniq
|
1002
|
+
if value_values.size <= 1
|
1003
|
+
m = value.first.value
|
1004
|
+
m = nil if m.type == :variable
|
1005
|
+
merged << m
|
1006
|
+
else
|
1007
|
+
if values.variables.empty?
|
1008
|
+
msg = "Cannot unify enough elements: #{values.map(&:inspect).join(' with ')}"
|
1009
|
+
arrays_goals.uniq.each do |goal|
|
1010
|
+
goal.log << msg
|
1011
|
+
end
|
1012
|
+
return nil
|
1013
|
+
else
|
1014
|
+
_variables, nonvariables = values.reject{|v| v.value.nil? }.partition{|element| element.type == :variable }
|
1015
|
+
if nonvariables.value.uniq.size <= 1
|
1016
|
+
m = nonvariables.first.value
|
1017
|
+
m = nil if m.type == :variable
|
1018
|
+
merged << m
|
1019
|
+
|
1020
|
+
value.combination(2).each do |vl, vr|
|
1021
|
+
if vl.type == :variable
|
1022
|
+
unifications << [vl, vr, vl.goal, vr.goal]
|
1023
|
+
elsif vr.type == :variable
|
1024
|
+
unifications << [vr, vl, vr.goal, vl.goal]
|
1025
|
+
end
|
1026
|
+
end
|
1027
|
+
else
|
1028
|
+
msg = "Cannot unify non-variables: #{nonvariables.value.uniq.map(&:inspect).join(' with ')}"
|
1029
|
+
arrays_goals.uniq.each do |goal|
|
1030
|
+
goal.log << msg
|
1031
|
+
end
|
1032
|
+
return nil
|
1033
|
+
end
|
1034
|
+
end
|
1035
|
+
end
|
1036
|
+
}
|
1037
|
+
|
1038
|
+
tails.each do |head_size,tail,goal|
|
1039
|
+
next if tail == UNKNOWN_TAIL
|
1040
|
+
merged_goals = arrays_goals - [goal] + [goal]
|
1041
|
+
unifications << [tail.value(visited).value(visited), merged[head_size..-1], goal, merged_goals.compact.first]
|
1042
|
+
end
|
1043
|
+
|
1044
|
+
[merged, unifications]
|
1045
|
+
end
|
1046
|
+
|
38
1047
|
end
|
39
1048
|
|
40
1049
|
require_relative 'porolog/core_ext'
|
41
1050
|
require_relative 'porolog/error'
|
1051
|
+
require_relative 'porolog/core_ext'
|
42
1052
|
require_relative 'porolog/scope'
|
43
1053
|
require_relative 'porolog/predicate'
|
44
1054
|
require_relative 'porolog/arguments'
|
45
1055
|
require_relative 'porolog/rule'
|
46
1056
|
require_relative 'porolog/goal'
|
1057
|
+
require_relative 'porolog/variable'
|
47
1058
|
require_relative 'porolog/value'
|
1059
|
+
require_relative 'porolog/instantiation'
|
1060
|
+
require_relative 'porolog/tail'
|