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.
@@ -16,9 +16,9 @@ module Porolog
16
16
  class Scope
17
17
 
18
18
  # Error class for rescuing or detecting any Scope error.
19
- class ScopeError < PorologError ; end
19
+ class Error < PorologError ; end
20
20
  # Error class indicating a non-Predicate was assigned to a Scope.
21
- class NotPredicateError < ScopeError ; end
21
+ class NotPredicateError < Error ; end
22
22
 
23
23
  attr_reader :name
24
24
 
@@ -74,7 +74,7 @@ module Porolog
74
74
  # @return [Porolog::Predicate] the Predicate assigned to the Scope.
75
75
  # @raise [NotPredicateError] when provided predicate is not actually a Predicate.
76
76
  def []=(name,predicate)
77
- raise NotPredicateError.new("#{predicate.inspect} is not a Predicate") unless predicate.is_a?(Predicate)
77
+ raise NotPredicateError, "#{predicate.inspect} is not a Predicate" unless predicate.is_a?(Predicate)
78
78
  @predicates[name.to_sym] = predicate
79
79
  end
80
80
 
@@ -0,0 +1,52 @@
1
+ #
2
+ # lib/porolog/tail.rb - Plain Old Ruby Objects Prolog Engine -- Tail
3
+ #
4
+ # Luis Esteban 2 May 2018
5
+ # created
6
+ #
7
+
8
+ module Porolog
9
+
10
+ # A Porolog::Tail is used to represent the tail of a list.
11
+ #
12
+ # It corresponds to the use of the splat operator within an Array.
13
+ #
14
+ # @author Luis Esteban
15
+ #
16
+ # @!attribute value
17
+ # @return [Object] The value of the tail.
18
+ class Tail
19
+
20
+ # Creates a new Tail for an Array.
21
+ # @param value [Object] the value of the tail.
22
+ def initialize(value = UNKNOWN_TAIL)
23
+ @value = value
24
+ end
25
+
26
+ # Returns the value of the Tail.
27
+ # The optional arguments are ignored; this is for polymorphic compatibility with Porolog::Value and Porolog::Variable,
28
+ # which are used to prevent inifinite recursion.
29
+ # @return [Object] the value of the Tail.
30
+ def value(*)
31
+ @value
32
+ end
33
+
34
+ # @return [String] pretty representation.
35
+ def inspect
36
+ "*#{@value.inspect}"
37
+ end
38
+
39
+ # @return [Array] embedded variables.
40
+ def variables
41
+ @value.variables
42
+ end
43
+
44
+ # @param other [Object, #value]
45
+ # @return [Boolean] whether the value of the Tail is equal to the value of another Object.
46
+ def ==(other)
47
+ @value == other.value
48
+ end
49
+
50
+ end
51
+
52
+ end
@@ -7,22 +7,22 @@
7
7
 
8
8
  module Porolog
9
9
 
10
- # Porolog::Value
11
- #
12
- # A Porolog::Value combines a value with a goal so that when the goal
13
- # is closed, the value can be uninstantiated at the same time.
10
+ # A Porolog::Value combines a value with a goal so that when the goal
11
+ # is closed, the value can be uninstantiated at the same time.
14
12
  #
15
13
  # @author Luis Esteban
14
+ #
16
15
  # @!attribute goal
17
16
  # @return [Porolog::Goal] The goal in which the value was instantiated.
18
17
  # @!attribute instantiations
19
18
  # @return [Array<Porolog::Instantiation>] Instantiations of this value.
19
+ #
20
20
  class Value
21
21
 
22
22
  # Error class for rescuing or detecting any Value error.
23
- class Error < PorologError ; end
23
+ class Error < PorologError ; end
24
24
  # Error class indicating that the supplied goal is not actually a goal.
25
- class GoalError < Error ; end
25
+ class GoalError < Error ; end
26
26
 
27
27
  attr_accessor :goal, :instantiations
28
28
 
@@ -30,13 +30,10 @@ module Porolog
30
30
  # @param goal [Porolog::Goal] the Goal to be associated.
31
31
  # @return [Porolog::Value] the Value.
32
32
  def initialize(value, goal)
33
- raise GoalError.new("Not a Goal: #{goal.inspect}") unless goal.is_a?(Goal)
33
+ raise GoalError, "Not a Goal: #{goal.inspect}" unless goal.is_a?(Goal)
34
34
 
35
- if value.is_a?(Value)
36
- @value = value.value
37
- else
38
- @value = value
39
- end
35
+ @value = value
36
+ @value = value.value if value.is_a?(Value)
40
37
  @goal = goal
41
38
 
42
39
  @instantiations = []
@@ -0,0 +1,307 @@
1
+ #
2
+ # lib/porolog/variable.rb - Plain Old Ruby Objects Prolog Engine -- Variable
3
+ #
4
+ # Luis Esteban 2 May 2018
5
+ # created
6
+ #
7
+
8
+
9
+ module Porolog
10
+
11
+ # A Porolog::Variable is used to hold instantiations during the process of satisfying a goal.
12
+ # It implements a variable of a goal.
13
+ # It allows instantiations to be made and removed as the goal
14
+ # is attempted to be satisfied.
15
+ #
16
+ # @author Luis Esteban
17
+ #
18
+ # @!attribute name
19
+ # @return [Symbol] The name of the variable.
20
+ # @!attribute goal
21
+ # @return [Porolog::Goal] The Goal for which this Variable's instantiations are bound.
22
+ # @!attribute instantiations
23
+ # @return [Array<Porolog::Instantiation>] The Instantiations of this Variable.
24
+ # @!attribute values
25
+ # @return [Array<Porolog::Value>] the Values of the Variable when used as an anonymous Variable.
26
+ #
27
+ class Variable
28
+
29
+ # Error class for rescuing or detecting any Variable error.
30
+ class Error < PorologError ; end
31
+ # Error class indicating a Variable has been instantiated to multiple different values at the same time.
32
+ class MultipleValuesError < Error ; end
33
+ # Error class indicating a Variable has been created without a Goal.
34
+ class GoalError < Error ; end
35
+ # Error class indicating a Variable has been instantiated to a value that contains itself.
36
+ class SelfReferentialError < Error ; end
37
+ # Error class indicating an unexpected scenario has occurred.
38
+ class UnexpectedError < Error ; end
39
+
40
+ attr_accessor :name, :goal, :instantiations, :values
41
+
42
+ # Initializes the Variable and attaches it to the Goal.
43
+ # @param name [Object] the name used to refer to the Variable.
44
+ # @param goal [Porolog::Goal] the Goal the Variable is to be attached to.
45
+ def initialize(name, goal)
46
+ raise GoalError, "Not a Goal: #{goal.inspect}" unless goal.is_a?(Goal)
47
+ @goal = goal
48
+ name = name.to_sym if name.is_a?(String)
49
+
50
+ case name
51
+ when Symbol
52
+ @name = name
53
+ @values = []
54
+ when Variable
55
+ @name = name.name
56
+ @values = []
57
+ when Value
58
+ @name = name.value
59
+ @values = [name]
60
+ else
61
+ @name = name.to_s
62
+ @values = [Value.new(name, goal)]
63
+ end
64
+
65
+ @instantiations = []
66
+ @goal.variable(self)
67
+ end
68
+
69
+ # Converts a Variable back to a Symbol.
70
+ # @return [Symbol, nil] the name of the Variable.
71
+ def to_sym
72
+ @name && @name.to_sym
73
+ end
74
+
75
+ # @return [Symbol] the type of the Variable, which should be :variable.
76
+ def type
77
+ :variable
78
+ end
79
+
80
+ # @return [String] pretty representation.
81
+ def inspect
82
+ "#{@goal.myid}.#{@name.inspect}"
83
+ end
84
+
85
+ # @param visited [Array] used to prevent infinite recursion.
86
+ # @param depth [Integer] the current level of indentation.
87
+ # @param index [Object, nil] the index into this Variable that is instantiated.
88
+ # @param self_index [Object, nil] the index where this Variable is instantiated.
89
+ # @return [String] the inspect of the Variable showing instantiations using indentation.
90
+ def inspect_with_instantiations(visited = [], depth = 0, index = nil, self_index = nil)
91
+ return if visited.include?(self)
92
+
93
+ index_str = index && "[#{index.inspect}]" || ''
94
+ prefix = self_index && "[#{self_index.inspect}]" || ''
95
+ name = "#{' ' * depth}#{prefix}#{@goal.myid}.#{@name.inspect}#{index_str}"
96
+
97
+ name = "#{name} = #{@values.map(&:inspect).join(',')}#{index_str}" if @values && !@values.empty? && @instantiations.empty?
98
+
99
+ others = @instantiations.map{|instantiation|
100
+ [
101
+ instantiation.variable1.inspect_with_instantiations(visited + [self], depth + 1, instantiation.index1, instantiation.index2),
102
+ instantiation.variable2.inspect_with_instantiations(visited + [self], depth + 1, instantiation.index2, instantiation.index1),
103
+ ].compact
104
+ }.reject(&:empty?)
105
+
106
+ [
107
+ name,
108
+ *others,
109
+ ].join("\n")
110
+ end
111
+
112
+ # @return [Object,self] returns the current value of the Variable based on its current instantiations.
113
+ # If there are no concrete instantiations, it returns itself, indicating no value.
114
+ # @param visited [Array] prevents infinite recursion.
115
+ def value(visited = [])
116
+ return nil if visited.include?(self)
117
+ visited = visited + [self]
118
+
119
+ # -- Collect values --
120
+ values = [*@values]
121
+
122
+ @instantiations.each do |instantiation|
123
+ values += instantiation.values_for(self, visited)
124
+ end
125
+
126
+ values.uniq!
127
+
128
+ # -- Filter trivial values --
129
+ values = [[nil]] if values == [[UNKNOWN_TAIL], [nil]] || values == [[nil], [UNKNOWN_TAIL]]
130
+
131
+ values = values.reject{|value| value == UNKNOWN_TAIL }
132
+
133
+ values_values = values.map{|value| value.value(visited) }.reject{|value| value == UNKNOWN_ARRAY }
134
+
135
+ # -- Condense Values --
136
+ result = if values_values.size > 1
137
+ # -- Potentially Multiple Values Found --
138
+ if values_values.all?{|value| value.is_a?(Array) }
139
+ # -- All Values Are Arrays --
140
+ unifications = []
141
+
142
+ values_goals = values.map{|value|
143
+ value.respond_to?(:goal) && value.goal || value.variables.map(&:goal).first
144
+ }
145
+
146
+ merged, unifications = unify_many_arrays(values, values_goals, visited)
147
+
148
+ merged.value(visited).to_a
149
+ else
150
+ # -- Not All Values Are Arrays --
151
+ unifications = []
152
+ values.each_cons(2){|left,right|
153
+ unification = unify(left, right, @goal, @goal, visited)
154
+ if unification && unifications
155
+ unifications += unification
156
+ else
157
+ unifications = nil
158
+ end
159
+ }
160
+ if unifications
161
+ values.sort_by{|value|
162
+ case value
163
+ when Variable, Symbol then 2
164
+ when UNKNOWN_TAIL, UNKNOWN_ARRAY then 9
165
+ else 0
166
+ end
167
+ }.first || self
168
+ else
169
+ raise MultipleValuesError, "Multiple values detected for #{inspect}: #{values.inspect}"
170
+ end
171
+ end
172
+ else
173
+ # -- One (or None) Value Found --
174
+ values.sort_by{|value|
175
+ case value
176
+ when Variable, Symbol then 2
177
+ when UNKNOWN_TAIL, UNKNOWN_ARRAY then 9
178
+ else 0
179
+ end
180
+ }.first || self
181
+ end
182
+
183
+ # -- Splat Tail --
184
+ if result.is_a?(Array) && result.size == 1 && result.first.is_a?(Tail)
185
+ result = result.first.value
186
+ end
187
+
188
+ result
189
+ end
190
+
191
+ # Instantiates Variable to another experssion.
192
+ # @param other [Object] the other value (or object) being instantiated.
193
+ # @param index_into_other [] the index into the other value.
194
+ # @param index_into_self [] the index into this Variable.
195
+ # @return [Porolog::Instantiation,nil] the instantiation made.
196
+ # @example
197
+ # # To instantiate the third element of x to the second element of y,
198
+ # # x = [a,b,c,d,e]
199
+ # # |
200
+ # # y = [p,q,r,s]
201
+ # x = goal.variable(:x)
202
+ # y = goal.variable(:y)
203
+ # x.instantiate(y, 1, 2)
204
+ # @example
205
+ # # To instantiate the tail of x to y,
206
+ # x.instantiate(y, nil, :tail)
207
+ def instantiate(other, index_into_other = nil, index_into_self = nil)
208
+ raise SelfReferentialError, "Cannot instantiate self-referential definition for #{(self.variables & other.variables).inspect}" unless (self.variables & other.variables).empty?
209
+
210
+ # -- Check Instantiation is Unifiable --
211
+ unless self.value.is_a?(Variable) || other.value.is_a?(Variable)
212
+ # -- Determine Other Goal --
213
+ other_goal = nil
214
+ other_goal = other.goal if other.respond_to?(:goal)
215
+ other_goal ||= self.goal
216
+
217
+ # -- Check Unification --
218
+ unless unify(self, other, self.goal, other_goal)
219
+ self.goal.log << "Cannot unify: #{self.inspect} and #{other.inspect}"
220
+ return nil
221
+ end
222
+ end
223
+
224
+ # -- Create Instantiation --
225
+ instantiation = Instantiation.new(self, index_into_self, other, index_into_other)
226
+
227
+ # -- Create Reverse Assymetric Instantiations --
228
+ if other.value.is_a?(Array) && other.value.last.is_a?(Tail)
229
+ array = other.value
230
+ if array.length == 2
231
+ if array.first.is_a?(Variable)
232
+ Instantiation.new(array.first, nil, self, :head)
233
+ end
234
+ if array.last.is_a?(Tail) && array.last.value.is_a?(Variable)
235
+ Instantiation.new(array.last.value, nil, self, :tail)
236
+ end
237
+ end
238
+ end
239
+
240
+ # -- Return --
241
+ instantiation
242
+ end
243
+
244
+ # Removes this Variable by removing all of its insantiations.
245
+ # @return [Boolean] success.
246
+ def remove
247
+ @instantiations.dup.each(&:remove)
248
+ @instantiations[0..-1] = []
249
+ true
250
+ end
251
+
252
+ # Removes instantiations from another goal.
253
+ # @param other_goal [Porolog::Goal] the Goal of which instantiations are to be removed.
254
+ # @return [Boolean] success.
255
+ def uninstantiate(other_goal)
256
+ @instantiations.delete_if do |instantiation|
257
+ instantiation.remove if instantiation.other_goal_to(self) == other_goal
258
+ end
259
+
260
+ @values.delete_if{|value| value.goal == other_goal }
261
+ true
262
+ end
263
+
264
+ # Indexes the value of the Variable.
265
+ # @param index [Object] the index into the value.
266
+ # @return [Object, nil] the value at the index in the value of the Variable.
267
+ def [](index)
268
+ value = self.value
269
+ value = value.value if value.is_a?(Value)
270
+
271
+ case value
272
+ when Array
273
+ case index
274
+ when Integer
275
+ value[index]
276
+ when Symbol
277
+ case index
278
+ when :head
279
+ value[0]
280
+ when :tail
281
+ value[1..-1]
282
+ else
283
+ nil
284
+ end
285
+ else
286
+ nil
287
+ end
288
+ else
289
+ nil
290
+ end
291
+ end
292
+
293
+ # @return [Array<Porolog::Variable>] the Variables in itself, which is just itself.
294
+ def variables
295
+ [self]
296
+ end
297
+
298
+ # Compares Variables.
299
+ # @param other [Porolog::Variable] the other Variable.
300
+ # @return [Boolean] whether the two Variables have the same name in the same Goal.
301
+ def ==(other)
302
+ other.is_a?(Variable) && @name == other.name && @goal == other.goal
303
+ end
304
+
305
+ end
306
+
307
+ end
@@ -13,16 +13,19 @@ describe 'Porolog' do
13
13
  reset
14
14
  end
15
15
 
16
+ let(:pred_name) { :pred }
17
+ let(:pred) { Predicate.new pred_name }
18
+ let(:pred1) { Predicate.new :p }
19
+ let(:pred2) { Predicate.new :q }
20
+
16
21
  describe 'Arguments' do
17
22
 
18
23
  describe '.reset' do
19
24
 
20
25
  it 'should delete/unregister all Arguments' do
21
- p = Predicate.new :p
22
-
23
- args1 = Arguments.new p, [1,:x,'word',[2,:y,0.3]]
24
- args2 = Arguments.new p, [2]
25
- args3 = Arguments.new p, [3,:x,:y,:z]
26
+ args1 = Arguments.new pred, [1,:x,'word',[2,:y,0.3]]
27
+ args2 = Arguments.new pred, [2]
28
+ args3 = Arguments.new pred, [3,:x,:y,:z]
26
29
 
27
30
  assert_equal 'Arguments1', args1.myid
28
31
  assert_equal 'Arguments2', args2.myid
@@ -30,8 +33,8 @@ describe 'Porolog' do
30
33
 
31
34
  Arguments.reset
32
35
 
33
- args4 = Arguments.new p, [4,[1,2,3]]
34
- args5 = Arguments.new p, [5,[]]
36
+ args4 = Arguments.new pred, [4,[1,2,3]]
37
+ args5 = Arguments.new pred, [5,[]]
35
38
 
36
39
  assert_equal 'Arguments-999', args1.myid
37
40
  assert_equal 'Arguments-999', args2.myid
@@ -53,10 +56,9 @@ describe 'Porolog' do
53
56
  describe '.new' do
54
57
 
55
58
  it 'should create a new Arguments' do
56
- predicate = Predicate.new :p
57
- arguments = Arguments.new predicate, [1,:x,'word',[2,:y,0.3]]
59
+ arguments = Arguments.new pred, [1, :x, 'word', [2, :y, 0.3]]
58
60
 
59
- assert_Arguments arguments, :p, [1,:x,'word',[2,:y,0.3]]
61
+ assert_Arguments arguments, :pred, [1, :x, 'word', [2, :y, 0.3]]
60
62
  end
61
63
 
62
64
  end
@@ -64,21 +66,23 @@ describe 'Porolog' do
64
66
  describe '.arguments' do
65
67
 
66
68
  it 'should return all registered arguments' do
67
- assert_equal 0, Arguments.arguments.size
69
+ # -- No Arguments --
70
+ assert_equal 0, Arguments.arguments.size
68
71
 
69
- predicate1 = Predicate.new :p
70
- arguments1 = Arguments.new predicate1, [:x,:y,:z]
72
+ # -- One Arguments --
73
+ arguments1 = Arguments.new pred1, [:x,:y,:z]
71
74
 
72
- assert_equal 1, Arguments.arguments.size
75
+ assert_equal 1, Arguments.arguments.size
73
76
  assert_Arguments Arguments.arguments.last, :p, [:x,:y,:z]
74
77
 
75
- predicate2 = Predicate.new :q
76
- arguments2 = Arguments.new predicate2, [:a,:b,:c,:d]
78
+ # -- Two Arguments --
79
+ arguments2 = Arguments.new pred2, [:a,:b,:c,:d]
77
80
 
78
- assert_equal 2, Arguments.arguments.size
81
+ assert_equal 2, Arguments.arguments.size
79
82
  assert_Arguments Arguments.arguments.last, :q, [:a,:b,:c,:d]
80
83
 
81
- assert_equal [arguments1,arguments2], Arguments.arguments
84
+
85
+ assert_equal [arguments1, arguments2], Arguments.arguments
82
86
  end
83
87
 
84
88
  end
@@ -86,18 +90,16 @@ describe 'Porolog' do
86
90
  describe '#initialize' do
87
91
 
88
92
  it 'should initialize predicate and arguments' do
89
- predicate = Predicate.new :p
90
- arguments = Arguments.new predicate, [:x,:y,:z]
93
+ arguments = Arguments.new pred, [:x,:y,:z]
91
94
 
92
- assert_equal predicate, arguments.predicate
95
+ assert_equal pred, arguments.predicate
93
96
  assert_equal [:x,:y,:z], arguments.arguments
94
97
  end
95
98
 
96
99
  it 'should register the arguments' do
97
- predicate = Predicate.new :p
98
- arguments1 = Arguments.new predicate, [:x]
99
- arguments2 = Arguments.new predicate, [:x,:y]
100
- arguments3 = Arguments.new predicate, [:x,:y,:z]
100
+ arguments1 = Arguments.new pred, [:x]
101
+ arguments2 = Arguments.new pred, [:x,:y]
102
+ arguments3 = Arguments.new pred, [:x,:y,:z]
101
103
 
102
104
  assert_equal [
103
105
  arguments1,
@@ -111,10 +113,9 @@ describe 'Porolog' do
111
113
  describe '#myid' do
112
114
 
113
115
  it 'should return its class and index as a String' do
114
- predicate = Predicate.new :p
115
- arguments1 = Arguments.new predicate, [:x]
116
- arguments2 = Arguments.new predicate, [:x,:y]
117
- arguments3 = Arguments.new predicate, [:x,:y,:z]
116
+ arguments1 = Arguments.new pred, [:x]
117
+ arguments2 = Arguments.new pred, [:x,:y]
118
+ arguments3 = Arguments.new pred, [:x,:y,:z]
118
119
 
119
120
  assert_equal 'Arguments1', arguments1.myid
120
121
  assert_equal 'Arguments2', arguments2.myid
@@ -122,10 +123,9 @@ describe 'Porolog' do
122
123
  end
123
124
 
124
125
  it 'should return its class and -999 as a String when deleted/reset' do
125
- predicate = Predicate.new :p
126
- arguments1 = Arguments.new predicate, [:x]
127
- arguments2 = Arguments.new predicate, [:x,:y]
128
- arguments3 = Arguments.new predicate, [:x,:y,:z]
126
+ arguments1 = Arguments.new pred, [:x]
127
+ arguments2 = Arguments.new pred, [:x,:y]
128
+ arguments3 = Arguments.new pred, [:x,:y,:z]
129
129
 
130
130
  Arguments.reset
131
131
 
@@ -142,6 +142,7 @@ describe 'Porolog' do
142
142
  predicate1 = Predicate.new :p
143
143
  predicate2 = Predicate.new :q
144
144
  predicate3 = Predicate.new :generic
145
+
145
146
  arguments1 = Arguments.new predicate1, [:x]
146
147
  arguments2 = Arguments.new predicate2, [:list, [1,2,3]]
147
148
  arguments3 = Arguments.new predicate3, [:a,:b,:c]
@@ -156,12 +157,11 @@ describe 'Porolog' do
156
157
  describe '#fact!' do
157
158
 
158
159
  it 'should create a fact for its predicate' do
159
- predicate1 = Predicate.new :predicate1
160
- arguments1 = predicate1.(1,'a',0.1)
160
+ arguments1 = pred.(1,'a',0.1)
161
161
 
162
162
  arguments1.fact!
163
163
 
164
- assert_equal '[ predicate1(1,"a",0.1):- true]', predicate1.rules.inspect
164
+ assert_equal '[ pred(1,"a",0.1):- true]', pred.rules.inspect
165
165
  end
166
166
 
167
167
  end
@@ -169,12 +169,11 @@ describe 'Porolog' do
169
169
  describe '#falicy!' do
170
170
 
171
171
  it 'should create a falicy for its predicate' do
172
- predicate1 = Predicate.new :predicate1
173
- arguments1 = predicate1.(1,'a',0.1)
172
+ arguments1 = pred.(1,'a',0.1)
174
173
 
175
174
  arguments1.falicy!
176
175
 
177
- assert_equal '[ predicate1(1,"a",0.1):- false]', predicate1.rules.inspect
176
+ assert_equal '[ pred(1,"a",0.1):- false]', pred.rules.inspect
178
177
  end
179
178
 
180
179
  end
@@ -182,12 +181,11 @@ describe 'Porolog' do
182
181
  describe '#cut_fact!' do
183
182
 
184
183
  it 'should create a fact for its predicate and terminate solving the goal' do
185
- predicate1 = Predicate.new :predicate1
186
- arguments1 = predicate1.(1,'a',0.1)
184
+ arguments1 = pred.(1,'a',0.1)
187
185
 
188
186
  arguments1.cut_fact!
189
187
 
190
- assert_equal '[ predicate1(1,"a",0.1):- [:CUT, true]]', predicate1.rules.inspect
188
+ assert_equal '[ pred(1,"a",0.1):- [:CUT, true]]', pred.rules.inspect
191
189
  end
192
190
 
193
191
  end
@@ -195,12 +193,11 @@ describe 'Porolog' do
195
193
  describe '#cut_falicy!' do
196
194
 
197
195
  it 'should create a falicy for its predicate and terminate solving the goal' do
198
- predicate1 = Predicate.new :predicate1
199
- arguments1 = predicate1.(1,'a',0.1)
196
+ arguments1 = pred.(1,'a',0.1)
200
197
 
201
198
  arguments1.cut_falicy!
202
199
 
203
- assert_equal '[ predicate1(1,"a",0.1):- [:CUT, false]]', predicate1.rules.inspect
200
+ assert_equal '[ pred(1,"a",0.1):- [:CUT, false]]', pred.rules.inspect
204
201
  end
205
202
 
206
203
  end
@@ -208,14 +205,13 @@ describe 'Porolog' do
208
205
  describe '#<<' do
209
206
 
210
207
  it 'should create a rule for its predicate' do
211
- predicate1 = Predicate.new :predicate1
212
- arguments1 = predicate1.(1,'a',0.1)
208
+ arguments1 = pred.(1,'a',0.1)
213
209
 
214
210
  arguments1 << [
215
- 1,2,3
211
+ pred1.(1,2,3)
216
212
  ]
217
213
 
218
- assert_equal '[ predicate1(1,"a",0.1):- [1, 2, 3]]', predicate1.rules.inspect
214
+ assert_equal '[ pred(1,"a",0.1):- [p(1,2,3)]]', pred.rules.inspect
219
215
  end
220
216
 
221
217
  end
@@ -223,16 +219,17 @@ describe 'Porolog' do
223
219
  describe '#evaluates' do
224
220
 
225
221
  it 'should add a block as a rule to its predicate' do
226
- predicate1 = Predicate.new :predicate1
227
- arguments1 = predicate1.(1,'a',0.1)
222
+ arguments1 = pred.(1,'a',0.1)
228
223
 
229
224
  line = __LINE__ ; arguments2 = arguments1.evaluates do |*args|
225
+ #:nocov:
230
226
  $stderr.puts "args = #{args.inspect}"
227
+ #:nocov:
231
228
  end
232
229
 
233
230
  assert_instance_of Arguments, arguments2
234
231
 
235
- part1 = "[ predicate1(1,\"a\",0.1):- #<Proc:0x"
232
+ part1 = "[ pred(1,\"a\",0.1):- #<Proc:0x"
236
233
  part2 = "@#{__FILE__}:#{line}>]"
237
234
 
238
235
  matcher = Regexp.new(
@@ -242,41 +239,28 @@ describe 'Porolog' do
242
239
  Regexp.escape(part2) +
243
240
  '\Z'
244
241
  )
245
- assert_match matcher, predicate1.rules.inspect
242
+ assert_match matcher, pred.rules.inspect
246
243
  end
247
244
  end
248
245
 
249
246
  describe '#variables' do
250
247
 
251
248
  it 'should find variable arguments' do
252
- skip 'until CoreExt added'
253
-
254
- predicate1 = Predicate.new :alpha
255
- arguments1 = predicate1.(1,'a',0.1,:a,2.2,:b,'b',:C,1234)
249
+ arguments1 = pred.(1,'a',0.1,:a,2.2,:b,'b',:C,1234)
256
250
  variables1 = arguments1.variables
257
251
 
258
252
  assert_equal [:a,:b,:C], variables1
259
253
  end
260
254
 
261
255
  it 'should find nested variable arguments' do
262
- skip 'until CoreExt added'
263
-
264
- predicate1 = Predicate.new :alpha
265
- predicate2 = Predicate.new :bravo
266
-
267
- arguments = predicate1.(1,'a',0.1,:a,predicate2.(:P,4,:Q),2.2,:b,'b',:C,1234)
256
+ arguments = pred1.(1, 'a', 0.1, :a, pred2.(:P, 4, :Q), 2.2, :b, 'b', :C, 1234)
268
257
  variables = arguments.variables
269
258
 
270
259
  assert_equal [:a,:P,:Q,:b,:C], variables
271
260
  end
272
261
 
273
262
  it 'should find embedded variable arguments' do
274
- skip 'until CoreExt added'
275
-
276
- predicate1 = Predicate.new :alpha
277
- predicate2 = Predicate.new :bravo
278
-
279
- arguments = predicate1.(1,[:e1],'a',:h/:t,0.1,:a,predicate2.(:P,[6,:r,8]/predicate2.([:z]),4,:Q),2.2,:b,'b',:C,1234)
263
+ arguments = pred1.(1, [:e1], 'a', :h/:t, 0.1, :a, pred2.(:P, [6, :r, 8]/pred2.([:z]), 4, :Q), 2.2, :b, 'b', :C, 1234)
280
264
  variables = arguments.variables
281
265
 
282
266
  assert_equal [:e1,:h,:t,:a,:P,:r,:z,:Q,:b,:C], variables
@@ -287,15 +271,11 @@ describe 'Porolog' do
287
271
  describe '#goal' do
288
272
 
289
273
  it 'should return a new goal for solving the arguments' do
290
- skip 'until Goal added'
291
-
292
- predicate1 = Predicate.new :alpha
293
- arguments1 = predicate1.(1,'a',0.1,:a,2.2,:b,'b',:C,1234)
274
+ arguments1 = pred1.(1, 'a', 0.1, :a, 2.2, :b, 'b', :C, 1234)
294
275
 
295
276
  goal = arguments1.goal
296
277
 
297
- assert_instance_of Goal, goal
298
- assert_equal arguments1, goal.arguments
278
+ assert_Goal goal, :p, [1, 'a', 0.1, :a, 2.2, :b, 'b', :C, 1234]
299
279
  end
300
280
 
301
281
  end
@@ -303,8 +283,20 @@ describe 'Porolog' do
303
283
  describe '#solutions' do
304
284
 
305
285
  it 'should memoize solutions' do
306
- skip 'until Goal added'
307
- # TODO: it 'should memoize solutions' do
286
+ args1 = Arguments.new pred, [1,2]
287
+ args2 = Arguments.new pred, [:p,:q]
288
+
289
+ args1.fact!
290
+
291
+ solve_spy = Spy.on(args2, :solve).and_call_through
292
+
293
+ assert_equal [{ p: 1, q: 2 }], args2.solutions
294
+ assert_equal [{ p: 1, q: 2 }], args2.solutions
295
+ assert_equal [{ p: 1, q: 2 }], args2.solutions
296
+ assert_equal [{ p: 1, q: 2 }], args2.solutions
297
+ assert_equal [{ p: 1, q: 2 }], args2.solutions
298
+
299
+ assert_equal 1, solve_spy.calls.size
308
300
  end
309
301
 
310
302
  end
@@ -312,11 +304,8 @@ describe 'Porolog' do
312
304
  describe '#solve' do
313
305
 
314
306
  it 'should unify and solve a simple predicate' do
315
- skip 'until Goal added'
316
-
317
- alpha = Predicate.new :alpha
318
- args1 = Arguments.new alpha, [1,2]
319
- args2 = Arguments.new alpha, [:p,:q]
307
+ args1 = Arguments.new pred, [1,2]
308
+ args2 = Arguments.new pred, [:p,:q]
320
309
 
321
310
  args1.fact!
322
311
 
@@ -326,14 +315,12 @@ describe 'Porolog' do
326
315
  end
327
316
 
328
317
  it 'should unify and solve a simple predicate with multiple solutions' do
329
- skip 'until Goal added'
330
-
331
- predicate :alpha
318
+ predicate :simple
332
319
 
333
- alpha(1,2).fact!
334
- alpha(3,4).fact!
320
+ simple(1,2).fact!
321
+ simple(3,4).fact!
335
322
 
336
- solutions = alpha(:p,:q).solve
323
+ solutions = simple(:p,:q).solve
337
324
 
338
325
  assert_equal [
339
326
  { p: 1, q: 2 },
@@ -342,14 +329,12 @@ describe 'Porolog' do
342
329
  end
343
330
 
344
331
  it 'should unify and solve a simple predicate with multiple solutions involving a head and tail' do
345
- skip 'until Goal added'
332
+ predicate :basic
346
333
 
347
- predicate :alpha
334
+ basic([1,2,3]).fact!
335
+ basic([3,4,5]).fact!
348
336
 
349
- alpha([1,2,3]).fact!
350
- alpha([3,4,5]).fact!
351
-
352
- solutions = alpha(:p/:q).solve
337
+ solutions = basic(:p/:q).solve
353
338
 
354
339
  assert_equal [
355
340
  { p: 1, q: [2,3] },
@@ -357,9 +342,7 @@ describe 'Porolog' do
357
342
  ],solutions
358
343
  end
359
344
 
360
- it 'should unify and solve a basic predicate' do
361
- skip 'until Goal added'
362
-
345
+ it 'should unify and solve a normal predicate' do
363
346
  predicate :likes
364
347
 
365
348
  likes('mary','food').fact!
@@ -373,17 +356,28 @@ describe 'Porolog' do
373
356
 
374
357
  solutions = likes(:who,:what).solve
375
358
 
376
- assert_equal [
359
+ assert_equal [
377
360
  { who: 'mary', what: 'food' },
378
361
  { who: 'mary', what: 'wine' },
379
362
  { who: 'john', what: 'wine' },
380
363
  { who: 'john', what: 'mary' },
381
- ],solutions
364
+ ], solutions
382
365
  end
383
366
 
384
- it 'should unify and solve a deeper predicate' do
385
- skip 'until Goal added'
367
+ it 'should allow isnt to be defined' do
368
+ predicate :isnt
369
+
370
+ isnt(:A,:A) << [:CUT, false]
371
+ isnt(:A,:B) << [:CUT, true]
372
+
373
+ solutions = isnt(123,123).solve
374
+ assert_equal [], solutions
386
375
 
376
+ solutions = isnt(123,124).solve
377
+ assert_equal [{}], solutions
378
+ end
379
+
380
+ it 'should unify and solve a deeper predicate' do
387
381
  predicate :male, :female, :parent
388
382
 
389
383
  male('james1').fact!
@@ -420,6 +414,7 @@ describe 'Porolog' do
420
414
  { X: 'james2' },
421
415
  ], solutions
422
416
 
417
+
423
418
  predicate :mother, :father, :sibling, :isnt
424
419
 
425
420
  mother(:X,:M) << [
@@ -473,27 +468,38 @@ describe 'Porolog' do
473
468
  end
474
469
 
475
470
  it 'should unify and solve a predicate involving a head and tail' do
476
- skip 'until Goal added'
477
-
478
- predicate :alpha, :beta, :gamma
471
+ predicate :join, :split, :head, :tail
479
472
 
480
- gamma([1,2]).fact!
481
- gamma([2,3]).fact!
473
+ head(1).fact!
474
+ head(4).fact!
475
+ tail([2,3]).fact!
476
+ tail([5,6]).fact!
482
477
 
483
- gamma([:h/:t]) << [
484
- gamma(:h),
485
- gamma(:t),
478
+ split(:h/:t) << [
479
+ head(:h),
480
+ tail(:t),
486
481
  ]
487
482
 
488
- alpha(:l) << [
489
- gamma(:l)
483
+ join(:l) << [
484
+ split(:l)
490
485
  ]
491
486
 
492
- solutions = alpha(:p/:q).solve
487
+ solutions = join(:l).solve
493
488
 
494
489
  assert_equal [
495
- { p: 1, q: 2 },
496
- { p: 2, q: 3 },
490
+ { l: [1,2,3] },
491
+ { l: [1,5,6] },
492
+ { l: [4,2,3] },
493
+ { l: [4,5,6] },
494
+ ],solutions
495
+
496
+ solutions = join(:p/:q).solve
497
+
498
+ assert_equal [
499
+ { p: 1, q: [2,3] },
500
+ { p: 1, q: [5,6] },
501
+ { p: 4, q: [2,3] },
502
+ { p: 4, q: [5,6] },
497
503
  ],solutions
498
504
  end
499
505
 
@@ -541,7 +547,7 @@ describe 'Porolog' do
541
547
  end
542
548
  end
543
549
 
544
- it 'should pass on instantiations between goals' do
550
+ it 'should implement simple recursion' do
545
551
  skip 'until StandardPredicates added'
546
552
 
547
553
  predicate :count
@@ -623,7 +629,7 @@ describe 'Porolog' do
623
629
  end
624
630
 
625
631
  it 'should solve a peeling off predicate' do
626
- skip 'until Goal added'
632
+ skip 'until StandardPredicates added'
627
633
 
628
634
  predicate :size
629
635
 
@@ -650,6 +656,18 @@ describe 'Porolog' do
650
656
  skip 'until StandardPredicates added and converted to list representation'
651
657
  # TODO: convert to list representation
652
658
 
659
+ predicate :tower
660
+
661
+ tower(1, :X, :Y, :Z, [[:X,:Z]]).fact!
662
+ tower(:N, :X, :Y, :Z, :S) << [
663
+ gtr(:N,1),
664
+ is(:M,:N){|n| n - 1 },
665
+ tower(:M, :X, :Z, :Y, :S1),
666
+ tower( 1, :X, :Y, :Z, :S2),
667
+ tower(:M, :Y, :X, :Z, :S3),
668
+ append(:S1, :S2, :S12),
669
+ append(:S12, :S3, :S),
670
+ ]
653
671
  predicate :move
654
672
 
655
673
  move(1,:X,:Y,:Z) << [
@@ -671,7 +689,6 @@ describe 'Porolog' do
671
689
  'Move top disk from right to left',
672
690
  'Move top disk from right to center',
673
691
  'Move top disk from left to center',
674
- 'Move top disk from left to right',
675
692
  'Move top disk from center to right',
676
693
  'Move top disk from center to left',
677
694
  'Move top disk from right to left',
@@ -681,6 +698,31 @@ describe 'Porolog' do
681
698
  'Move top disk from center to right',
682
699
  ].map{|s| "#{s}\n" }.join
683
700
 
701
+ solutions = tower(3, 'left', 'middle', 'right', :moves).solve
702
+
703
+ expected_solutions = [
704
+ {
705
+ moves: [
706
+ ['left', 'center'],
707
+ ['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'],
718
+ ['left', 'right'],
719
+ ['center', 'right'],
720
+ ]
721
+ }
722
+ ]
723
+
724
+ assert_equal expected_solutions, solutions
725
+
684
726
  assert_output expected_output do
685
727
  solutions = move(4,'left','right','center').solve
686
728
 
@@ -695,20 +737,27 @@ describe 'Porolog' do
695
737
  describe '#solve_for' do
696
738
 
697
739
  it 'should solve a predicate for specified variables' do
698
- skip 'until Goal added'
740
+ predicate :alpha
699
741
 
742
+ alpha(1,2).fact!
743
+
744
+ solutions = alpha(:p,:q).solve_for(:q,:p)
745
+
746
+ assert_equal [[2,1]], solutions
747
+ end
748
+
749
+ it 'should solve a predicate for a specified variable' do
700
750
  predicate :alpha
701
751
 
702
752
  alpha(1,2).fact!
753
+ alpha(3,5).fact!
703
754
 
704
- solutions = alpha(:p,:q).solve_for(:p,:q)
755
+ solutions = alpha(:p,:q).solve_for(:q)
705
756
 
706
- assert_equal [[1,2]], solutions
757
+ assert_equal [2,5], solutions
707
758
  end
708
759
 
709
760
  it 'should solve a predicate with multiple solutions for specified variables' do
710
- skip 'until Goal added'
711
-
712
761
  predicate :alpha
713
762
 
714
763
  alpha(1,2).fact!
@@ -718,12 +767,7 @@ describe 'Porolog' do
718
767
 
719
768
  solutions = alpha(:p,:q).solve_for(:p)
720
769
 
721
- assert_equal [
722
- [1],
723
- [3],
724
- [5],
725
- [5],
726
- ], solutions
770
+ assert_equal [1,3,5,5], solutions
727
771
  end
728
772
 
729
773
  end
@@ -731,20 +775,48 @@ describe 'Porolog' do
731
775
  describe '#valid?' do
732
776
 
733
777
  it 'should return true when a solution is found' do
734
- # TODO: it 'should return true when a solution is found' do
778
+ predicate :f
779
+
780
+ f(3).fact!
781
+
782
+ assert f(3).valid?, name
735
783
  end
736
784
 
737
785
  it 'should return false when no solution is found' do
738
- # TODO: it 'should return false when no solution is found' do
786
+ predicate :f
787
+
788
+ f(1).fact!
789
+ f(2).fact!
790
+ f(4).fact!
791
+ f(5).fact!
792
+
793
+ refute f(3).valid?, name
794
+ end
795
+
796
+ it 'should return false when a falicy is found' do
797
+ predicate :f
798
+
799
+ f(3).falicy!
800
+
801
+ refute f(3).valid?, name
739
802
  end
740
803
 
741
804
  end
742
805
 
743
806
  describe '#dup' do
744
807
 
745
- it 'should create a duplicate arguments for another goal' do
746
- skip 'until HeadTail added'
747
- # TODO: it 'should create a duplicate arguments for another goal' do
808
+ let(:args1) { pred.(1,'a',0.1,:a,2.2,:b,'b',:C,1234) }
809
+ let(:goal) { args1.goal }
810
+ let(:args2) { args1.dup(goal) }
811
+
812
+ it 'should create a new Arguments' do
813
+ assert_Arguments args1, :pred, [1,'a',0.1,:a,2.2,:b,'b',:C,1234]
814
+
815
+ refute_equal args2.__id__, args1.__id__
816
+ end
817
+
818
+ it 'should variablise the arguments for the goal' do
819
+ assert_Arguments args2, :pred, [1, 'a', 0.1, goal.variable(:a), 2.2, goal.variable(:b), 'b', goal.variable(:C), 1234]
748
820
  end
749
821
 
750
822
  end
@@ -761,14 +833,24 @@ describe 'Porolog' do
761
833
  assert arguments1 == arguments2, 'Arguments with identical predicates and arguments should return true'
762
834
  end
763
835
 
764
- it 'should return false for Arguments with identical predicates and arguments' do
836
+ it 'should return false for Arguments with non-identical arguments' do
765
837
  predicate1 = Predicate.new :omega
766
838
  arguments1 = predicate1.(1,'a',0.1)
767
839
 
768
840
  predicate2 = Predicate.new :omega
769
841
  arguments2 = predicate2.(1,'a',0.2)
770
842
 
771
- refute arguments1 == arguments2, 'Arguments with non-identical predicates and arguments should return false'
843
+ refute arguments1 == arguments2, 'Arguments with non-identical arguments should return false'
844
+ end
845
+
846
+ it 'should return false for Arguments with non-identical predicates' do
847
+ predicate1 = Predicate.new :omega
848
+ arguments1 = predicate1.(1,'a',0.1)
849
+
850
+ predicate2 = Predicate.new :omegb
851
+ arguments2 = predicate2.(1,'a',0.1)
852
+
853
+ refute arguments1 == arguments2, 'Arguments with non-identical predicates should return false'
772
854
  end
773
855
 
774
856
  end