porolog 0.0.4 → 1.0.0

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