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.
@@ -122,39 +122,23 @@ module Porolog
122
122
  end
123
123
 
124
124
  if arguments == :CUT
125
- subgoal.calling_goal.terminate!
126
- subgoal.calling_goal.log << "TERMINATED by #{subgoal.inspect}"
125
+ goal.terminate!
126
+ goal.log << "TERMINATED after #{subgoal.inspect}"
127
127
  end
128
128
 
129
129
  return result
130
130
 
131
131
  when false
132
132
  return false
133
- end
134
-
135
- # -- Unify Subgoal --
136
- subsubgoal = arguments.goal(subgoal)
137
- variables = (subgoal.arguments.variables + subsubgoal.arguments.variables).map(&:to_sym).uniq
138
- unified = true
139
- unifications = []
140
-
141
- variables.each do |variable|
142
- name = variable
143
133
 
144
- unification = unify(name, name, subgoal, subsubgoal)
145
- unified &&= !!unification
146
- if unified
147
- unifications += unification
148
- else
149
- #:nocov:
150
- subsubgoal.log << "Couldn't unify: #{name.inspect} WITH #{subgoal.myid} AND #{subsubgoal.myid}"
151
- break
152
- #:nocov:
153
- end
134
+ when nil
135
+ block.call(subgoal)
136
+ return true
154
137
  end
155
138
 
156
- # -- Instantiate Unifications --
157
- unified &&= instantiate_unifications(unifications) if unified
139
+ # -- Unify Subsubgoal --
140
+ subsubgoal = arguments.goal(subgoal)
141
+ unified = subsubgoal.inherit_variables(subgoal)
158
142
 
159
143
  # -- Satisfy Subgoal --
160
144
  result = false
@@ -73,7 +73,7 @@ module Porolog
73
73
  # @param predicate [Porolog::Predicate] a Predicate to be assigned to the Scope.
74
74
  # @return [Porolog::Predicate] the Predicate assigned to the Scope.
75
75
  # @raise [NotPredicateError] when provided predicate is not actually a Predicate.
76
- def []=(name,predicate)
76
+ def []=(name, predicate)
77
77
  raise NotPredicateError, "#{predicate.inspect} is not a Predicate" unless predicate.is_a?(Predicate)
78
78
  @predicates[name.to_sym] = predicate
79
79
  end
@@ -23,6 +23,11 @@ module Porolog
23
23
  @value = value
24
24
  end
25
25
 
26
+ # @return [Symbol] the type of the Tail, which should be :tail.
27
+ def type
28
+ :tail
29
+ end
30
+
26
31
  # Returns the value of the Tail.
27
32
  # The optional arguments are ignored; this is for polymorphic compatibility with Porolog::Value and Porolog::Variable,
28
33
  # which are used to prevent inifinite recursion.
@@ -54,8 +54,8 @@ module Porolog
54
54
  # @param self_index [Integer,Symbol,Array] the index of which this value belongs.
55
55
  # @return [String] the inspect of the value in the context of the variables of a goal.
56
56
  def inspect_with_instantiations(visited = [], depth = 0, index = nil, self_index = nil)
57
- index_str = index && "[#{index.inspect}]" || ''
58
- prefix = self_index && "#{self_index.inspect}" || ''
57
+ index_str = index && "[#{index.inspect}]" || ''
58
+ prefix = self_index&.inspect || ''
59
59
 
60
60
  "#{' ' * depth}#{prefix}#{inspect}#{index_str}"
61
61
  end
@@ -75,7 +75,7 @@ module Porolog
75
75
 
76
76
  # Responds to all the Value's value methods as well as its own.
77
77
  # @return [Boolean] whether the value responds to the method.
78
- def respond_to?(method, include_all=false)
78
+ def respond_to?(method, include_all = false)
79
79
  @value.respond_to?(method, include_all) || super
80
80
  end
81
81
 
@@ -5,7 +5,6 @@
5
5
  # created
6
6
  #
7
7
 
8
-
9
8
  module Porolog
10
9
 
11
10
  # A Porolog::Variable is used to hold instantiations during the process of satisfying a goal.
@@ -69,7 +68,7 @@ module Porolog
69
68
  # Converts a Variable back to a Symbol.
70
69
  # @return [Symbol, nil] the name of the Variable.
71
70
  def to_sym
72
- @name && @name.to_sym
71
+ @name&.to_sym
73
72
  end
74
73
 
75
74
  # @return [Symbol] the type of the Variable, which should be :variable.
@@ -135,20 +134,39 @@ module Porolog
135
134
  # -- Condense Values --
136
135
  result = if values_values.size > 1
137
136
  # -- Potentially Multiple Values Found --
137
+ unifications = []
138
138
  if values_values.all?{|value| value.is_a?(Array) }
139
139
  # -- All Values Are Arrays --
140
- unifications = []
141
-
140
+ values = values.reject{|value| value == UNKNOWN_ARRAY } if values.size > 2 && values.include?(UNKNOWN_ARRAY)
142
141
  values_goals = values.map{|value|
143
- value.respond_to?(:goal) && value.goal || value.variables.map(&:goal).first
142
+ value.respond_to?(:goal) && value.goal || value.variables.map(&:goal).first || values.variables.map(&:goal).first
144
143
  }
145
144
 
146
- merged, unifications = unify_many_arrays(values, values_goals, visited)
145
+ if values.size > 2
146
+ merged, unifications = unify_many_arrays(values, values_goals, visited)
147
+ elsif values.size == 2
148
+ no_variables = values.map(&:variables).flatten.empty?
149
+ if no_variables
150
+ left_value = values[0].value.value
151
+ right_value = values[1].value.value
152
+ if left_value.last == UNKNOWN_TAIL && right_value.first == UNKNOWN_TAIL
153
+ return [*left_value[0...-1], *right_value[1..-1]]
154
+ elsif right_value.last == UNKNOWN_TAIL && left_value.first == UNKNOWN_TAIL
155
+ return [*right_value[0...-1], *left_value[1..-1]]
156
+ elsif left_value != right_value
157
+ return nil
158
+ end
159
+ end
160
+ merged, unifications = unify_arrays(*values, *values_goals, visited)
161
+ else
162
+ # :nocov: NOT REACHED
163
+ merged, unifications = values.first, []
164
+ # :nocov:
165
+ end
147
166
 
148
167
  merged.value(visited).to_a
149
168
  else
150
169
  # -- Not All Values Are Arrays --
151
- unifications = []
152
170
  values.each_cons(2){|left,right|
153
171
  unification = unify(left, right, @goal, @goal, visited)
154
172
  if unification && unifications
@@ -158,26 +176,26 @@ module Porolog
158
176
  end
159
177
  }
160
178
  if unifications
161
- values.sort_by{|value|
179
+ values.min_by{|value|
162
180
  case value
163
181
  when Variable, Symbol then 2
164
182
  when UNKNOWN_TAIL, UNKNOWN_ARRAY then 9
165
183
  else 0
166
184
  end
167
- }.first || self
185
+ } || self
168
186
  else
169
187
  raise MultipleValuesError, "Multiple values detected for #{inspect}: #{values.inspect}"
170
188
  end
171
189
  end
172
190
  else
173
191
  # -- One (or None) Value Found --
174
- values.sort_by{|value|
192
+ values.min_by{|value|
175
193
  case value
176
194
  when Variable, Symbol then 2
177
195
  when UNKNOWN_TAIL, UNKNOWN_ARRAY then 9
178
196
  else 0
179
197
  end
180
- }.first || self
198
+ } || self
181
199
  end
182
200
 
183
201
  # -- Splat Tail --
@@ -504,8 +504,7 @@ describe 'Porolog' do
504
504
  end
505
505
 
506
506
  it 'should call a builtin predicate' do
507
- skip 'until StandardPredicates added'
508
-
507
+ builtin :write
509
508
  predicate :callwrite
510
509
 
511
510
  callwrite(:MESSAGE) << [
@@ -522,8 +521,7 @@ describe 'Porolog' do
522
521
  end
523
522
 
524
523
  it 'should pass on instantiations between goals' do
525
- skip 'until StandardPredicates added'
526
-
524
+ builtin :write
527
525
  predicate :passon, :copy
528
526
 
529
527
  copy(:A,:A).fact!
@@ -548,8 +546,7 @@ describe 'Porolog' do
548
546
  end
549
547
 
550
548
  it 'should implement simple recursion' do
551
- skip 'until StandardPredicates added'
552
-
549
+ builtin :write, :is, :gtr
553
550
  predicate :count
554
551
 
555
552
  count(1) << [
@@ -557,10 +554,12 @@ describe 'Porolog' do
557
554
  :CUT
558
555
  ]
559
556
  count(:N) << [
557
+ gtr(:N,1),
560
558
  write("N = ",:N),
561
559
  is(:M,:N){|n| n - 1 },
562
560
  write(" M = ",:M,"\n"),
563
561
  count(:M),
562
+ :CUT
564
563
  ]
565
564
 
566
565
  expected_output = [
@@ -586,8 +585,7 @@ describe 'Porolog' do
586
585
  end
587
586
 
588
587
  it 'should solve tower of Hanoi' do
589
- skip 'until StandardPredicates added'
590
-
588
+ builtin :gtr, :is, :write
591
589
  predicate :move
592
590
 
593
591
  move(1,:X,:Y,:Z) << [
@@ -629,23 +627,22 @@ describe 'Porolog' do
629
627
  end
630
628
 
631
629
  it 'should solve a peeling off predicate' do
632
- skip 'until StandardPredicates added'
633
-
634
- predicate :size
630
+ builtin :is
631
+ predicate :peel
635
632
 
636
- size([],0).cut_fact!
637
- size(:H/:T,:N) << [
638
- size(:T,:NT),
633
+ peel([],0).cut_fact!
634
+ peel(:H/:T,:N) << [
635
+ peel(:T,:NT),
639
636
  is(:N,:NT){|nt| nt + 1 },
640
637
  ]
641
638
 
642
- solutions = size([],:N).solve
639
+ solutions = peel([],:N).solve
643
640
 
644
641
  assert_equal [
645
642
  { N: 0 }
646
643
  ],solutions
647
644
 
648
- solutions = size([13,17,19,23],:N).solve
645
+ solutions = peel([13,17,19,23],:N).solve
649
646
 
650
647
  assert_equal [
651
648
  { N: 4 },
@@ -653,10 +650,10 @@ describe 'Porolog' do
653
650
  end
654
651
 
655
652
  it 'should solve tower of Hanoi with list representation' do
656
- skip 'until StandardPredicates added and converted to list representation'
657
653
  # TODO: convert to list representation
658
654
 
659
- predicate :tower
655
+ builtin :gtr, :is, :append, :write
656
+ predicate :tower, :move
660
657
 
661
658
  tower(1, :X, :Y, :Z, [[:X,:Z]]).fact!
662
659
  tower(:N, :X, :Y, :Z, :S) << [
@@ -668,68 +665,50 @@ describe 'Porolog' do
668
665
  append(:S1, :S2, :S12),
669
666
  append(:S12, :S3, :S),
670
667
  ]
671
- predicate :move
672
-
673
- move(1,:X,:Y,:Z) << [
674
- write('Move top disk from ', :X, ' to ', :Y, "\n"),
675
- ]
676
- move(:N,:X,:Y,:Z) << [
677
- gtr(:N,1),
678
- is(:M,:N){|n| n - 1 },
679
- move(:M,:X,:Z,:Y),
680
- move(1,:X,:Y,:Q),
681
- move(:M,:Z,:Y,:X),
682
- ]
683
-
684
- expected_output = [
685
- 'Move top disk from left to center',
686
- 'Move top disk from left to right',
687
- 'Move top disk from center to right',
688
- 'Move top disk from left to center',
689
- 'Move top disk from right to left',
690
- 'Move top disk from right to center',
691
- 'Move top disk from left to center',
692
- 'Move top disk from center to right',
693
- 'Move top disk from center to left',
694
- 'Move top disk from right to left',
695
- 'Move top disk from center to right',
696
- 'Move top disk from left to center',
697
- 'Move top disk from left to right',
698
- 'Move top disk from center to right',
699
- ].map{|s| "#{s}\n" }.join
700
668
 
701
669
  solutions = tower(3, 'left', 'middle', 'right', :moves).solve
702
670
 
703
671
  expected_solutions = [
704
672
  {
705
673
  moves: [
706
- ['left', 'center'],
707
674
  ['left', 'right'],
708
- ['center', 'right'],
709
- ['left', 'center'],
710
- ['right', 'left'],
711
- ['right', 'center'],
712
- ['left', 'center'],
713
- ['center', 'right'],
714
- ['center', 'left'],
715
- ['right', 'left'],
716
- ['center', 'right'],
717
- ['left', 'center'],
675
+ ['left', 'middle'],
676
+ ['right', 'middle'],
718
677
  ['left', 'right'],
719
- ['center', 'right'],
678
+ ['middle', 'left'],
679
+ ['middle', 'right'],
680
+ ['left', 'right']
720
681
  ]
721
682
  }
722
683
  ]
723
684
 
724
685
  assert_equal expected_solutions, solutions
725
686
 
726
- assert_output expected_output do
727
- solutions = move(4,'left','right','center').solve
728
-
729
- assert_equal [
730
- {},
731
- ],solutions
732
- end
687
+ solutions = tower(4, 'left', 'middle', 'right', :moves).solve
688
+
689
+ expected_solutions = [
690
+ {
691
+ moves: [
692
+ ['left', 'middle'],
693
+ ['left', 'right'],
694
+ ['middle', 'right'],
695
+ ['left', 'middle'],
696
+ ['right', 'left'],
697
+ ['right', 'middle'],
698
+ ['left', 'middle'],
699
+ ['left', 'right'],
700
+ ['middle', 'right'],
701
+ ['middle', 'left'],
702
+ ['right', 'left'],
703
+ ['middle', 'right'],
704
+ ['left', 'middle'],
705
+ ['left', 'right'],
706
+ ['middle', 'right']
707
+ ]
708
+ }
709
+ ]
710
+
711
+ assert_equal expected_solutions, solutions
733
712
  end
734
713
 
735
714
  end
@@ -262,4 +262,29 @@ describe 'Array' do
262
262
 
263
263
  end
264
264
 
265
+ describe '#clean' do
266
+
267
+ let(:predicate1) { Predicate.new :generic }
268
+ let(:arguments1) { predicate1.arguments(:m,:n) }
269
+ let(:goal1) { arguments1.goal }
270
+
271
+ let(:array5) { [1, 2, goal1[:A], 4, [goal1[:B], 6, 7, goal1[:C]], 9] }
272
+ let(:array6) { [1, goal1[:B], 3]/:T }
273
+ let(:array7) { [1, goal1[:B], 3]/goal1[:T] }
274
+
275
+ it 'should return simple Arrays as is' do
276
+ assert_equal [1, 2, :A, 4, [:B, 6, 7, :C], 9], array1.clean
277
+ assert_equal [], array2.clean
278
+ assert_equal [UNKNOWN_TAIL], array3.clean
279
+ assert_equal [1, :B, 3, UNKNOWN_TAIL], array4.clean
280
+ end
281
+
282
+ it 'should return the values of its elements with variables replaced by nil and Tails replaced by UNKNOWN_TAIL' do
283
+ assert_equal [1, 2, nil, 4, [nil, 6, 7, nil], 9], array5.clean
284
+ assert_equal [1, nil, 3, UNKNOWN_TAIL], array6.clean
285
+ assert_equal [1, nil, 3, UNKNOWN_TAIL], array7.clean
286
+ end
287
+
288
+ end
289
+
265
290
  end
@@ -669,23 +669,20 @@ describe 'Porolog' do
669
669
  end
670
670
 
671
671
  it 'should solve a goal recursively' do
672
- #:nocov:
673
- skip 'until Standard Predicates added'
674
-
672
+ builtin :write
675
673
  predicate :recursive
676
674
 
677
675
  recursive([]) << [:CUT, true]
678
676
  recursive(:head/:tail) << [
679
677
  write('(',:head,')'),
680
- alpha(:tail)
678
+ recursive(:tail)
681
679
  ]
682
680
 
683
681
  assert_output '(1)(2)(3)(four)(5)(6)(7)' do
684
- solutions = alpha([1,2,3,'four',5,6,7]).solve
682
+ solutions = recursive([1,2,3,'four',5,6,7]).solve
685
683
 
686
684
  assert_equal [{}], solutions
687
685
  end
688
- #:nocov:
689
686
  end
690
687
 
691
688
  end
@@ -706,14 +703,14 @@ describe 'Porolog' do
706
703
  goal.arguments.expects(:predicate).with().returns(nil).times(1)
707
704
  block.expects(:call).times(0)
708
705
 
709
- assert_nil goal.satisfy(&block), name
706
+ refute goal.satisfy(&block), name
710
707
  end
711
708
 
712
709
  it 'should try to satisfy the predicate with itself' do
713
710
  pred.expects(:satisfy).with(goal).returns(nil).times(1)
714
711
  block.expects(:call).times(0)
715
712
 
716
- assert_nil goal.satisfy(&block), name
713
+ refute goal.satisfy(&block), name
717
714
  end
718
715
 
719
716
  it 'should call the block when the predicate is satisfied' do
@@ -721,7 +718,7 @@ describe 'Porolog' do
721
718
  pred.(3,4).fact!
722
719
  pred.(5,6).fact!
723
720
 
724
- block.expects(:call).times(3)
721
+ block.expects(:call).returns(true).times(3)
725
722
 
726
723
  assert goal.satisfy(&block), name
727
724
  end
@@ -809,6 +806,86 @@ describe 'Porolog' do
809
806
 
810
807
  end
811
808
 
809
+ describe '#inherit_variables' do
810
+
811
+ it 'should return true for an initial goal' do
812
+ goal = new_goal :goal, :x, :y, :z
813
+
814
+ assert goal.inherit_variables
815
+ assert_Goal_variables goal, { x: nil, y: nil, z: nil }, [
816
+ 'Goal1.:x',
817
+ 'Goal1.:y',
818
+ 'Goal1.:z',
819
+ ].join("\n")
820
+ end
821
+
822
+ it 'should instantiate combined variables of both goals' do
823
+ goal = new_goal :goal, :x, :y, :z
824
+ subgoal = new_goal :subgoal, :a, :b, :c
825
+
826
+ assert subgoal.inherit_variables(goal)
827
+ assert_Goal_variables goal, { x: nil, y: nil, z: nil, a: nil, b: nil, c: nil }, [
828
+ 'Goal1.:x',
829
+ ' Goal2.:x',
830
+ 'Goal1.:y',
831
+ ' Goal2.:y',
832
+ 'Goal1.:z',
833
+ ' Goal2.:z',
834
+ 'Goal1.:a',
835
+ ' Goal2.:a',
836
+ 'Goal1.:b',
837
+ ' Goal2.:b',
838
+ 'Goal1.:c',
839
+ ' Goal2.:c',
840
+ ].join("\n")
841
+ assert_Goal_variables subgoal, { a: nil, b: nil, c: nil, x: nil, y: nil, z: nil }, [
842
+ 'Goal2.:a',
843
+ ' Goal1.:a',
844
+ 'Goal2.:b',
845
+ ' Goal1.:b',
846
+ 'Goal2.:c',
847
+ ' Goal1.:c',
848
+ 'Goal2.:x',
849
+ ' Goal1.:x',
850
+ 'Goal2.:y',
851
+ ' Goal1.:y',
852
+ 'Goal2.:z',
853
+ ' Goal1.:z',
854
+ ].join("\n")
855
+ end
856
+
857
+ it 'should not make any instantiations when variables cannot be unified' do
858
+ goal = new_goal :goal, :x, :y, :z
859
+ subgoal = new_goal :subgoal, :a, :b, :c
860
+
861
+ goal[:x].instantiate 1
862
+ subgoal[:x].instantiate 2
863
+
864
+ refute subgoal.inherit_variables(goal)
865
+ assert_equal [
866
+ 'Cannot unify because 1 != 2 (variable != variable)',
867
+ ], goal.log
868
+ assert_equal [
869
+ 'Cannot unify because 1 != 2 (variable != variable)',
870
+ "Couldn't unify: :x WITH Goal1 AND Goal2"
871
+ ], subgoal.log
872
+ assert_Goal_variables goal, { x: 1, y: nil, z: nil }, [
873
+ 'Goal1.:x',
874
+ ' Goal1.1',
875
+ 'Goal1.:y',
876
+ 'Goal1.:z',
877
+ ].join("\n")
878
+ assert_Goal_variables subgoal, { a: nil, b: nil, c: nil, x: 2 }, [
879
+ 'Goal2.:a',
880
+ 'Goal2.:b',
881
+ 'Goal2.:c',
882
+ 'Goal2.:x',
883
+ ' Goal2.2',
884
+ ].join("\n")
885
+ end
886
+
887
+ end
888
+
812
889
  end
813
890
 
814
891
  end