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.
@@ -102,9 +102,9 @@
102
102
  </div>
103
103
 
104
104
  <div id="footer">
105
- Generated on Tue Jun 18 08:28:54 2019 by
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.3.4).
107
+ 0.9.19 (ruby-2.6.5).
108
108
  </div>
109
109
 
110
110
  </div>
@@ -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.7'
14
+ VERSION = '0.0.8'.freeze
12
15
  # The most recent date of when the VERSION changed.
13
- VERSION_DATE = '2019-06-18'
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'