porolog 0.0.8 → 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.
@@ -102,7 +102,7 @@
102
102
  </div>
103
103
 
104
104
  <div id="footer">
105
- Generated on Mon Jul 6 22:54:45 2020 by
105
+ Generated on Sun Aug 2 19:24:16 2020 by
106
106
  <a href="http://yardoc.org" title="Yay! A Ruby Documentation Tool" target="_parent">yard</a>
107
107
  0.9.19 (ruby-2.6.5).
108
108
  </div>
@@ -11,9 +11,9 @@
11
11
  module Porolog
12
12
 
13
13
  # The most recent version of the Porolog gem.
14
- VERSION = '0.0.8'.freeze
14
+ VERSION = '1.0.0'.freeze
15
15
  # The most recent date of when the VERSION changed.
16
- VERSION_DATE = '2020-07-06'.freeze
16
+ VERSION_DATE = '2020-08-02'.freeze
17
17
 
18
18
  # Represents an unknown tail of a list.
19
19
  UNKNOWN_TAIL = Object.new
@@ -23,9 +23,15 @@ module Porolog
23
23
  '...'
24
24
  end
25
25
 
26
- # Specifies that the unknown tail is a list.
26
+ # Specifies that the unknown tail is a tail.
27
27
  def UNKNOWN_TAIL.type
28
- :array
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
29
35
  end
30
36
 
31
37
  UNKNOWN_TAIL.freeze
@@ -33,37 +39,106 @@ module Porolog
33
39
  # Represents a list where all elements are unknown.
34
40
  UNKNOWN_ARRAY = [UNKNOWN_TAIL].freeze
35
41
 
42
+ # Stores the next unique anonymous variable name.
43
+ ANONYMOUS = ['_a']
44
+
36
45
  # A convenience method to create a Predicate, along with a method
37
46
  # that returns an Arguments based on the arguments provided to
38
47
  # the method.
39
48
  # @param names [Array<#to_sym>] names of the Predicates to create.
49
+ # @param class_base [Class] class to define the method in.
40
50
  # @return [Porolog::Predicate] Predicate created if only one name is provided
41
51
  # @return [Array<Porolog::Predicate>] Predicates created if multiple names are provided
42
52
  # @example
43
53
  # predicate :combobulator
44
54
  # combobulator(:x,:y) # --> Porolog::Arguments
45
- def predicate(*names)
55
+ def predicate(*names, class_base: Object)
46
56
  names = [names].flatten
47
57
 
48
58
  predicates = names.map{|name|
49
59
  method = name.to_sym
50
60
  predicate = Predicate.new(name)
51
- Object.class_eval{
61
+ class_base.class_eval{
52
62
  remove_method(method) if public_method_defined?(method)
53
63
  define_method(method){|*args|
54
64
  predicate.(*args)
55
65
  }
56
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
73
+ predicate
74
+ }
75
+
76
+ predicates.size > 1 && predicates || predicates.first
77
+ end
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
57
121
  predicate
58
122
  }
59
123
 
60
124
  predicates.size > 1 && predicates || predicates.first
61
125
  end
62
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
+
63
136
  # Unify the Arguments of a Goal and a sub-Goal.
64
137
  # @param goal [Porolog::Goal] a Goal to solve a Predicate for specific Arguments.
65
138
  # @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.
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.
67
142
  def unify_goals(goal, subgoal)
68
143
  if goal.arguments.predicate == subgoal.arguments.predicate
69
144
  unifications = unify(goal.arguments.arguments, subgoal.arguments.arguments, goal, subgoal)
@@ -98,9 +173,30 @@ module Porolog
98
173
  goals_variables[left_goal][left] ||= []
99
174
  goals_variables[left_goal][left] << [right_goal,right]
100
175
 
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
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
104
200
 
105
201
  return false if return_false
106
202
  end
@@ -109,9 +205,9 @@ module Porolog
109
205
  instantiations = []
110
206
  consistent = true
111
207
 
112
- goals_variables.each do |goal,variables|
113
- variables.each do |name,others|
114
- others.each do |other_goal,other|
208
+ goals_variables.each do |goal, variables|
209
+ variables.each do |name, others|
210
+ others.each do |other_goal, other|
115
211
  instantiation = goal.instantiate(name, other, other_goal)
116
212
  if instantiation
117
213
  instantiations << instantiation
@@ -123,9 +219,12 @@ module Porolog
123
219
  end
124
220
 
125
221
  # -- Revert if inconsistent --
126
- instantiations.each(&:remove) unless consistent
127
-
128
- consistent
222
+ if consistent
223
+ instantiations
224
+ else
225
+ instantiations.each(&:remove)
226
+ nil
227
+ end
129
228
  end
130
229
 
131
230
  # Attempt to unify two entities of two goals.
@@ -155,7 +254,7 @@ module Porolog
155
254
  nil
156
255
  end
157
256
 
158
- when [:array, :array]
257
+ when [:array, :array], [:tail, :tail]
159
258
  _merged, unifications = unify_arrays(left, right, left_goal, right_goal, visited)
160
259
  if unifications
161
260
  unifications
@@ -165,6 +264,26 @@ module Porolog
165
264
  nil
166
265
  end
167
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
+
168
287
  when [:variable, :atomic]
169
288
  left_value = left_goal.value_of(left, nil, visited)
170
289
  right_value = right
@@ -188,17 +307,21 @@ module Porolog
188
307
  end
189
308
 
190
309
  when [:variable, :variable]
191
- left_value = left_goal.value_of(left, nil, visited)
192
- right_value = right_goal.value_of(right, nil, visited)
310
+ left_value = left_goal.value_of(left, nil, visited).value
311
+ right_value = right_goal.value_of(right, nil, visited).value
193
312
  if left_value == right_value || left_value.is_a?(Variable) || right_value.is_a?(Variable) || left_value.nil? || right_value.nil?
194
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]]
195
318
  else
196
319
  msg = "Cannot unify because #{left_value.inspect} != #{right_value.inspect} (variable != variable)"
197
320
  goals.each{|goal| goal.log << msg }
198
321
  nil
199
322
  end
200
323
 
201
- when [:variable, :array]
324
+ when [:variable, :array], [:variable, :tail]
202
325
  left_value = left_goal.value_of(left, nil, visited)
203
326
  right_value = right
204
327
  if left_value == right_value || left_value.is_a?(Variable) || left_value == UNKNOWN_ARRAY || left_value.nil?
@@ -218,7 +341,7 @@ module Porolog
218
341
  nil
219
342
  end
220
343
 
221
- when [:array, :variable]
344
+ when [:array, :variable], [:tail, :variable]
222
345
  left_value = left
223
346
  right_value = right_goal.value_of(right, nil, visited)
224
347
  if left_value == right_value || right_value.is_a?(Variable) || right_value == UNKNOWN_ARRAY || right_value.nil?
@@ -238,8 +361,8 @@ module Porolog
238
361
  nil
239
362
  end
240
363
 
241
- when [:array,:atomic], [:atomic,:array]
242
- msg = "Cannot unify #{left.inspect} with #{right.inspect}"
364
+ when [:array, :atomic], [:atomic, :array], [:tail, :atomic], [:atomic, :tail]
365
+ msg = "Cannot unify #{left.inspect} with #{right.inspect} (#{signature.join(' != ')})"
243
366
  goals.each{|goal| goal.log << msg }
244
367
  nil
245
368
 
@@ -261,11 +384,11 @@ module Porolog
261
384
  def unify_arrays(left, right, left_goal, right_goal = left_goal, visited = [])
262
385
  arrays = [left, right]
263
386
  arrays_goals = [left_goal, right_goal]
264
- arrays_values = arrays.map(&:value)
387
+ arrays_values = arrays.map{|array| array.value(visited) }
265
388
 
266
389
  # -- Trivial Unifications --
267
- return [left_goal. variablise(left), []] if right == UNKNOWN_ARRAY
268
- return [right_goal.variablise(right),[]] if left == UNKNOWN_ARRAY
390
+ return [left_goal. variablise(left), []] if right == UNKNOWN_ARRAY
391
+ return [right_goal.variablise(right), []] if left == UNKNOWN_ARRAY
269
392
 
270
393
  # -- Validate Arrays --
271
394
  unless arrays_values.all?{|array| array.is_a?(Array) || array == UNKNOWN_TAIL }
@@ -279,7 +402,7 @@ module Porolog
279
402
  number_of_tails = arrays.count{|array| has_tail?(array) }
280
403
 
281
404
  # -- Handle Tails --
282
- if number_of_tails == 0
405
+ if number_of_tails.zero?
283
406
  unify_arrays_with_no_tails(arrays, arrays_goals, visited)
284
407
  elsif number_of_tails == number_of_arrays
285
408
  unify_arrays_with_all_tails(arrays, arrays_goals, visited)
@@ -295,7 +418,9 @@ module Porolog
295
418
  # @return [Array<Array, Array>] the merged Array and the unifications to be instantiated.
296
419
  # @return [nil] if the Arrays cannot be unified.
297
420
  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 }
421
+ arrays_values = arrays.
422
+ map{|array| expand_splat(array) }.
423
+ map{|array| array.is_a?(Array) ? array.map(&:value) : array.value }
299
424
 
300
425
  unless arrays_values.all?{|array_values| [:array,:variable].include?(array_values.type) }
301
426
  msg = "Cannot unify: #{arrays.map(&:inspect).join(' with ')}"
@@ -303,7 +428,7 @@ module Porolog
303
428
  return nil
304
429
  end
305
430
 
306
- # TODO: Fix
431
+ # TODO: Fix / improve
307
432
  if arrays_values.size == 2 && arrays_values.any?{|array| array == UNKNOWN_ARRAY }
308
433
  merged = arrays_values.reject{|array| array == UNKNOWN_ARRAY }.first
309
434
  return [merged,[]]
@@ -312,7 +437,7 @@ module Porolog
312
437
  number_of_arrays = arrays.size
313
438
  number_of_tails = arrays.count{|array| has_tail?(array) }
314
439
 
315
- if number_of_tails == 0
440
+ if number_of_tails.zero?
316
441
  unify_arrays_with_no_tails(arrays, arrays_goals, visited)
317
442
  elsif number_of_tails == number_of_arrays
318
443
  unify_arrays_with_all_tails(arrays, arrays_goals, visited)
@@ -383,11 +508,13 @@ module Porolog
383
508
 
384
509
  if arrays_goals[index].nil?
385
510
  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
511
+ arrays_goals[index] = value_with_goal.goal if value_with_goal&.goal
387
512
  end
388
513
 
389
514
  if arrays_goals[index].nil?
515
+ # :nocov:
390
516
  raise NoGoalError, "Array #{array.inspect} has no goal for unification"
517
+ # :nocov:
391
518
  end
392
519
 
393
520
  arrays_values[index] = expand_splat(arrays_values[index])
@@ -403,10 +530,10 @@ module Porolog
403
530
 
404
531
  # -- Remap Arrays so that they are variablised and valuised with their Goals --
405
532
  new_arrays = []
406
- arrays_variables.each do |goal,variables|
533
+ arrays_variables.each do |goal, variables|
407
534
  new_array = variables.map{|variable|
408
535
  value = goal.value_of(variable, nil, visited).value
409
- value = variable if value == UNKNOWN_TAIL || value == UNKNOWN_ARRAY
536
+ value = variable if [UNKNOWN_TAIL, UNKNOWN_ARRAY].include?(value)
410
537
 
411
538
  if value.type == :variable
412
539
  value = goal.variable(value)
@@ -440,10 +567,10 @@ module Porolog
440
567
  merged = []
441
568
 
442
569
  # TODO: Change these names
443
- zipped.each_with_index{|values_to_unify, index|
570
+ zipped.each{|values_to_unify|
444
571
  values_to_unify_values = values_to_unify.map{|value|
445
572
  value_value = value.value
446
- if value_value == UNKNOWN_TAIL || value_value == UNKNOWN_ARRAY
573
+ if [UNKNOWN_TAIL, UNKNOWN_ARRAY].include?(value_value)
447
574
  value
448
575
  else
449
576
  value_value
@@ -527,15 +654,10 @@ module Porolog
527
654
  # @return [Array<Array, Array>] the merged Array and the unifications to be instantiated.
528
655
  # @return [nil] if the Arrays cannot be unified.
529
656
  def unify_arrays_with_all_tails(arrays, arrays_goals, visited)
530
- unifications = []
531
- merged = []
532
-
533
657
  # -- All tails --
534
- signature = arrays.map(&:headtail?)
535
658
  arrays = arrays.map{|array|
536
659
  expand_splat(array.headtail? ? array : array.value)
537
660
  }
538
- signature = arrays.map(&:headtail?)
539
661
 
540
662
  # -- Unify Embedded Arrays --
541
663
  if arrays.any?{|array| array.type == :variable }
@@ -565,9 +687,9 @@ module Porolog
565
687
  end
566
688
 
567
689
  if unifications
568
- return [merged, unifications]
690
+ [merged, unifications]
569
691
  else
570
- return nil
692
+ nil
571
693
  end
572
694
  end
573
695
 
@@ -615,10 +737,8 @@ module Porolog
615
737
  first_goal = pair.first.goal if pair.first.respond_to?(:goal)
616
738
  last_goal = pair.last.goal if pair.last.respond_to?(:goal)
617
739
  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
740
+ return nil unless m
741
+ m[-1] = UNKNOWN_TAIL if m[-1].nil?
622
742
  merged_tails += m
623
743
  unifications += u
624
744
  end
@@ -682,19 +802,23 @@ module Porolog
682
802
 
683
803
  # -- Variablise Arrays --
684
804
  arrays = arrays_goals.zip(arrays).map do |goal, array|
685
- goal.variablise(array)
805
+ if array == UNKNOWN_ARRAY
806
+ array
807
+ else
808
+ goal.variablise(array)
809
+ end
686
810
  end
687
811
 
688
812
  # -- Determine the fixed length (if any) --
689
813
  fixed_length = nil
690
814
  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
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
698
822
  end
699
823
  end
700
824
 
@@ -703,7 +827,7 @@ module Porolog
703
827
 
704
828
  # -- Unify All HeadTail Arrays --
705
829
  if headtail_arrays.size > 1
706
- headtail_goals = headtail_arrays.map{|array| array.goal }
830
+ headtail_goals = headtail_arrays.map(&:goal)
707
831
  merged_headtails, headtail_unifications = unify_headtail_with_headtail(headtail_arrays, headtail_goals, visited)
708
832
  unless merged_headtails
709
833
  msg = "Could not unify headtail arrays: #{headtail_arrays.map(&:value).map(&:inspect).join(' with ')}"
@@ -724,7 +848,7 @@ module Porolog
724
848
 
725
849
  # -- Unify All Tail Arrays --
726
850
  if tail_arrays.size > 1
727
- tail_goals = tail_arrays.map{|array| array.goal }
851
+ tail_goals = tail_arrays.map(&:goal)
728
852
  merged_tails, tail_unifications = unify_tail_with_tail(tail_arrays, tail_goals, visited)
729
853
  return nil unless merged_tails
730
854
  unifications += tail_unifications
@@ -812,7 +936,7 @@ module Porolog
812
936
  case element.type
813
937
  when :atomic
814
938
  0
815
- when :array
939
+ when :array, :tail
816
940
  if [UNKNOWN_TAIL, UNKNOWN_ARRAY].include?(element)
817
941
  3
818
942
  else
@@ -931,7 +1055,7 @@ module Porolog
931
1055
  goal = array.goal if array.is_a?(Value)
932
1056
  if goal.nil? && array.is_a?(Array)
933
1057
  v = array.find{|element| element.respond_to?(:goal) }
934
- goal = v && v.goal
1058
+ goal = v&.goal
935
1059
  end
936
1060
  end
937
1061
  goal = arrays_goals.compact.last if goal.nil?
@@ -1001,7 +1125,6 @@ module Porolog
1001
1125
  value_values = value.map(&:value).compact.uniq
1002
1126
  if value_values.size <= 1
1003
1127
  m = value.first.value
1004
- m = nil if m.type == :variable
1005
1128
  merged << m
1006
1129
  else
1007
1130
  if values.variables.empty?
@@ -1014,7 +1137,6 @@ module Porolog
1014
1137
  _variables, nonvariables = values.reject{|v| v.value.nil? }.partition{|element| element.type == :variable }
1015
1138
  if nonvariables.value.uniq.size <= 1
1016
1139
  m = nonvariables.first.value
1017
- m = nil if m.type == :variable
1018
1140
  merged << m
1019
1141
 
1020
1142
  value.combination(2).each do |vl, vr|
@@ -1035,6 +1157,7 @@ module Porolog
1035
1157
  end
1036
1158
  }
1037
1159
 
1160
+ # -- Unify Tails --
1038
1161
  tails.each do |head_size,tail,goal|
1039
1162
  next if tail == UNKNOWN_TAIL
1040
1163
  merged_goals = arrays_goals - [goal] + [goal]
@@ -1048,9 +1171,9 @@ end
1048
1171
 
1049
1172
  require_relative 'porolog/core_ext'
1050
1173
  require_relative 'porolog/error'
1051
- require_relative 'porolog/core_ext'
1052
1174
  require_relative 'porolog/scope'
1053
1175
  require_relative 'porolog/predicate'
1176
+ require_relative 'porolog/predicate/builtin'
1054
1177
  require_relative 'porolog/arguments'
1055
1178
  require_relative 'porolog/rule'
1056
1179
  require_relative 'porolog/goal'