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.
- checksums.yaml +4 -4
- data/README.md +11 -1
- data/Rakefile +2 -2
- data/bin/porolog +34 -13
- data/coverage/badge.svg +1 -1
- data/coverage/index.html +43822 -25140
- data/doc/Array.html +158 -51
- data/doc/Object.html +2 -2
- data/doc/Porolog.html +1584 -1113
- data/doc/Symbol.html +2 -2
- data/doc/_index.html +40 -56
- data/doc/class_list.html +1 -1
- data/doc/file.README.html +8 -4
- data/doc/index.html +8 -4
- data/doc/method_list.html +424 -248
- data/doc/top-level-namespace.html +1 -1
- data/lib/porolog.rb +184 -61
- data/lib/porolog/arguments.rb +12 -11
- data/lib/porolog/core_ext.rb +27 -9
- data/lib/porolog/error.rb +3 -0
- data/lib/porolog/goal.rb +57 -15
- data/lib/porolog/instantiation.rb +55 -9
- data/lib/porolog/predicate.rb +52 -26
- data/lib/porolog/predicate/builtin.rb +825 -0
- data/lib/porolog/rule.rb +8 -24
- data/lib/porolog/scope.rb +1 -1
- data/lib/porolog/tail.rb +5 -0
- data/lib/porolog/value.rb +3 -3
- data/lib/porolog/variable.rb +29 -11
- data/test/porolog/arguments_test.rb +45 -66
- data/test/porolog/core_ext_test.rb +25 -0
- data/test/porolog/goal_test.rb +86 -9
- data/test/porolog/instantiation_test.rb +36 -0
- data/test/porolog/porolog_test.rb +285 -30
- data/test/porolog/predicate/builtin_test.rb +1340 -0
- data/test/porolog/predicate_test.rb +78 -16
- data/test/porolog/rule_test.rb +19 -0
- data/test/porolog/variable_test.rb +44 -55
- data/test/samples_test.rb +277 -0
- data/test/test_helper.rb +9 -0
- metadata +9 -5
data/lib/porolog/arguments.rb
CHANGED
@@ -20,7 +20,7 @@ module Porolog
|
|
20
20
|
# The actual arguments.
|
21
21
|
class Arguments
|
22
22
|
|
23
|
-
attr_reader :predicate, :arguments
|
23
|
+
attr_reader :predicate, :arguments, :block
|
24
24
|
|
25
25
|
# Unregisters all Arguments
|
26
26
|
# @return [true]
|
@@ -34,9 +34,10 @@ module Porolog
|
|
34
34
|
# Creates a new Arguments for a Predicate
|
35
35
|
# @param predicate [Porolog::Predicate] the Predicate for which these are the arguments
|
36
36
|
# @param arguments [Array<Object>] the actual arguments
|
37
|
-
def initialize(predicate, arguments)
|
37
|
+
def initialize(predicate, arguments, &block)
|
38
38
|
@predicate = predicate
|
39
39
|
@arguments = arguments
|
40
|
+
@block = block
|
40
41
|
@@arguments << self
|
41
42
|
end
|
42
43
|
|
@@ -54,7 +55,8 @@ module Porolog
|
|
54
55
|
|
55
56
|
# @return [String] pretty representation
|
56
57
|
def inspect
|
57
|
-
|
58
|
+
block_inspect = block.nil? ? '' : "{#{block.inspect}}"
|
59
|
+
"#{@predicate&.name}(#{@arguments&.map(&:inspect).join(',')})#{block_inspect}"
|
58
60
|
end
|
59
61
|
|
60
62
|
# Creates a fact rule that states that these arguments satisfy the Predicate.
|
@@ -103,9 +105,7 @@ module Porolog
|
|
103
105
|
|
104
106
|
# @return [Array<Symbol>] the variables contained in the arguments
|
105
107
|
def variables
|
106
|
-
@arguments.map
|
107
|
-
argument.variables
|
108
|
-
}.flatten.uniq
|
108
|
+
@arguments.map(&:variables).flatten.uniq
|
109
109
|
end
|
110
110
|
|
111
111
|
# Creates a Goal for solving this Arguments for the Predicate
|
@@ -120,7 +120,7 @@ module Porolog
|
|
120
120
|
# @return [Array<Hash{Symbol => Object}>] the solutions found (memoized)
|
121
121
|
def solutions(max_solutions = nil)
|
122
122
|
@solutions ||= solve(max_solutions)
|
123
|
-
@solutions
|
123
|
+
max_solutions && @solutions[0...max_solutions] || @solutions
|
124
124
|
end
|
125
125
|
|
126
126
|
# Solves the Arguments
|
@@ -132,6 +132,7 @@ module Porolog
|
|
132
132
|
|
133
133
|
# Extracts solution values.
|
134
134
|
# @param variables [Symbol, Array<Symbol>] variable or variables
|
135
|
+
# @param max_solutions [Integer] the maximum number of solutions to find (nil means find all)
|
135
136
|
# @return [Array<Object>] all the values for the variables given
|
136
137
|
# @example
|
137
138
|
# predicate :treasure_at
|
@@ -140,9 +141,9 @@ module Porolog
|
|
140
141
|
# xs = arguments.solve_for(:X)
|
141
142
|
# ys = arguments.solve_for(:Y)
|
142
143
|
# coords = xs.zip(ys)
|
143
|
-
def solve_for(*variables)
|
144
|
+
def solve_for(*variables, max_solutions: nil)
|
144
145
|
variables = [*variables]
|
145
|
-
solutions.map{|solution|
|
146
|
+
solutions(max_solutions).map{|solution|
|
146
147
|
values = variables.map{|variable| solution[variable] }
|
147
148
|
if values.size == 1
|
148
149
|
values.first
|
@@ -154,14 +155,14 @@ module Porolog
|
|
154
155
|
|
155
156
|
# @return [Boolean] whether any solutions were found
|
156
157
|
def valid?
|
157
|
-
!solutions.empty?
|
158
|
+
!solutions(1).empty?
|
158
159
|
end
|
159
160
|
|
160
161
|
# Duplicates the Arguments in the context of the given goal
|
161
162
|
# @param goal [Porolog::Goal] the destination goal
|
162
163
|
# @return [Porolog::Arguments] the duplicated Arguments
|
163
164
|
def dup(goal)
|
164
|
-
self.class.new @predicate, goal.variablise(arguments)
|
165
|
+
self.class.new @predicate, goal.variablise(arguments), &@block
|
165
166
|
end
|
166
167
|
|
167
168
|
# @param other [Porolog::Arguments] arguments for comparison
|
data/lib/porolog/core_ext.rb
CHANGED
@@ -14,7 +14,7 @@ class Object
|
|
14
14
|
# A convenience method for testing/debugging.
|
15
15
|
# @return [String] the equivalent of inspect.
|
16
16
|
def myid
|
17
|
-
|
17
|
+
inspect
|
18
18
|
end
|
19
19
|
|
20
20
|
# @return [Array] embedded variables (for an Object, should be none).
|
@@ -58,7 +58,7 @@ class Symbol
|
|
58
58
|
# A convenience method for testing/debugging.
|
59
59
|
# @return [String] the equivalent of inspect.
|
60
60
|
def myid
|
61
|
-
|
61
|
+
inspect
|
62
62
|
end
|
63
63
|
|
64
64
|
# @return [Array] embedded variables (for a Symbol, should be itself).
|
@@ -89,18 +89,20 @@ class Array
|
|
89
89
|
end
|
90
90
|
|
91
91
|
# @return [Array] the values of its elements.
|
92
|
-
def value(
|
93
|
-
|
92
|
+
def value(visited = [])
|
93
|
+
return self if visited.include?(self)
|
94
|
+
visited = visited + [self]
|
95
|
+
flat_map{|element|
|
94
96
|
if element.is_a?(Tail)
|
95
|
-
tail = element.value(
|
97
|
+
tail = element.value(visited)
|
96
98
|
if tail.is_a?(Array)
|
97
99
|
tail
|
98
100
|
elsif tail.is_a?(Variable) || tail.is_a?(Value)
|
99
|
-
tail = tail.value(
|
101
|
+
tail = tail.value(visited)
|
100
102
|
if tail.is_a?(Array)
|
101
103
|
tail
|
102
104
|
elsif tail.is_a?(Variable) || tail.is_a?(Value)
|
103
|
-
tail = tail.goal.variablise(tail.value(
|
105
|
+
tail = tail.goal.variablise(tail.value(visited))
|
104
106
|
if tail.is_a?(Array)
|
105
107
|
tail
|
106
108
|
else
|
@@ -113,9 +115,25 @@ class Array
|
|
113
115
|
[element]
|
114
116
|
end
|
115
117
|
else
|
116
|
-
[element.value(
|
118
|
+
[element.value(visited)]
|
117
119
|
end
|
118
|
-
}
|
120
|
+
}
|
121
|
+
end
|
122
|
+
|
123
|
+
# Removes Porolog processing objects.
|
124
|
+
# @return [Array] the values of its elements with variables replaced by nil and Tails replaced by UNKNOWN_TAIL.
|
125
|
+
def clean
|
126
|
+
value.map{|element|
|
127
|
+
if element.is_a?(Array)
|
128
|
+
element.clean
|
129
|
+
elsif element.is_a?(Tail)
|
130
|
+
UNKNOWN_TAIL
|
131
|
+
elsif element.is_a?(Variable)
|
132
|
+
nil
|
133
|
+
else
|
134
|
+
element.value
|
135
|
+
end
|
136
|
+
}
|
119
137
|
end
|
120
138
|
|
121
139
|
# @return [Symbol] the type of the object (for an Array, should be :array)
|
data/lib/porolog/error.rb
CHANGED
data/lib/porolog/goal.rb
CHANGED
@@ -39,10 +39,16 @@ module Porolog
|
|
39
39
|
@@goals ||= []
|
40
40
|
goals = @@goals
|
41
41
|
@@goals = []
|
42
|
+
@@goal_count = 0
|
42
43
|
goals.map(&:deleted?)
|
43
44
|
true
|
44
45
|
end
|
45
46
|
|
47
|
+
# @return [Integer] the number of goals created
|
48
|
+
def self.goal_count
|
49
|
+
@@goal_count
|
50
|
+
end
|
51
|
+
|
46
52
|
reset
|
47
53
|
|
48
54
|
attr_accessor :calling_goal, :arguments, :index, :result, :description, :log
|
@@ -52,6 +58,7 @@ module Porolog
|
|
52
58
|
# @param calling_goal [Porolog::Goal] the parent Goal if this Goal is a subgoal.
|
53
59
|
def initialize(arguments, calling_goal = nil)
|
54
60
|
@@goals << self
|
61
|
+
@@goal_count += 1
|
55
62
|
|
56
63
|
@arguments = arguments
|
57
64
|
@terminate = false
|
@@ -124,12 +131,12 @@ module Porolog
|
|
124
131
|
def check_deleted
|
125
132
|
return false if @@goals.include?(self)
|
126
133
|
|
127
|
-
@variables.delete_if do |
|
134
|
+
@variables.delete_if do |_name,variable|
|
128
135
|
variable.remove
|
129
136
|
true
|
130
137
|
end
|
131
138
|
|
132
|
-
@values.delete_if do |
|
139
|
+
@values.delete_if do |_name,value|
|
133
140
|
value.remove
|
134
141
|
true
|
135
142
|
end
|
@@ -170,7 +177,7 @@ module Porolog
|
|
170
177
|
when NilClass
|
171
178
|
nil
|
172
179
|
else
|
173
|
-
if
|
180
|
+
if [UNKNOWN_TAIL, UNKNOWN_ARRAY].include?(object)
|
174
181
|
object
|
175
182
|
else
|
176
183
|
value(object)
|
@@ -178,17 +185,16 @@ module Porolog
|
|
178
185
|
end
|
179
186
|
end
|
180
187
|
|
181
|
-
|
188
|
+
alias [] variablise
|
182
189
|
|
183
190
|
# @return [Hash{Symbol => Object}] the Variables and their current values of this Goal
|
184
191
|
def variables
|
185
192
|
@variables.keys.each_with_object({}){|variable,variable_list|
|
186
|
-
value = value_of(variable)
|
187
|
-
value = value.value if value.is_a?(Value)
|
193
|
+
value = value_of(variable).value.value
|
188
194
|
if value.is_a?(Variable)
|
189
195
|
variable_list[variable] = nil
|
190
|
-
elsif value.is_a?(
|
191
|
-
variable_list[variable] = value.
|
196
|
+
elsif value.is_a?(Array)
|
197
|
+
variable_list[variable] = value.clean
|
192
198
|
else
|
193
199
|
variable_list[variable] = value
|
194
200
|
end
|
@@ -198,15 +204,13 @@ module Porolog
|
|
198
204
|
# A convenience method for testing/debugging.
|
199
205
|
# @return [String] a tree representation of all the instantiations of this goal's variables.
|
200
206
|
def inspect_variables
|
201
|
-
@variables.map
|
202
|
-
variable.inspect_with_instantiations
|
203
|
-
}.join("\n")
|
207
|
+
@variables.values.map(&:inspect_with_instantiations).join("\n")
|
204
208
|
end
|
205
209
|
|
206
210
|
# A convenience method for testing/debugging.
|
207
211
|
# @return [Array<Object>] the values instantiated for this goal.
|
208
212
|
def values
|
209
|
-
@values.map
|
213
|
+
@values.values.map(&:value)
|
210
214
|
end
|
211
215
|
|
212
216
|
# Finds or tries to create a variable in the goal (as much as possible) otherwise passes the parameter back.
|
@@ -267,7 +271,9 @@ module Porolog
|
|
267
271
|
|
268
272
|
predicate = @arguments.predicate
|
269
273
|
|
270
|
-
predicate
|
274
|
+
predicate&.satisfy(self) do |goal|
|
275
|
+
# TODO: Refactor to overrideable method (or another solution, say a lambda)
|
276
|
+
|
271
277
|
@solutions << variables
|
272
278
|
@log << "SOLUTION: #{variables}"
|
273
279
|
@log << goal.ancestry
|
@@ -288,9 +294,12 @@ module Porolog
|
|
288
294
|
|
289
295
|
predicate = @arguments.predicate
|
290
296
|
|
291
|
-
|
292
|
-
|
297
|
+
satisfied = false
|
298
|
+
predicate&.satisfy(self) do |subgoal|
|
299
|
+
subgoal_satisfied = block.call(subgoal)
|
300
|
+
satisfied ||= subgoal_satisfied
|
293
301
|
end
|
302
|
+
satisfied
|
294
303
|
end
|
295
304
|
|
296
305
|
# Instantiates a Variable to another Variable or Value, for this Goal.
|
@@ -310,6 +319,39 @@ module Porolog
|
|
310
319
|
variable.instantiate(other)
|
311
320
|
end
|
312
321
|
|
322
|
+
# Inherits variables and their instantiations from another goal.
|
323
|
+
# @param other_goal [Porolog::Goal,nil] the Goal to inherit variables from.
|
324
|
+
# @return [Boolean] whether the variables could be inherited (unified).
|
325
|
+
def inherit_variables(other_goal = @calling_goal)
|
326
|
+
return true unless other_goal
|
327
|
+
|
328
|
+
unified = true
|
329
|
+
unifications = []
|
330
|
+
variables = (
|
331
|
+
other_goal.arguments.variables +
|
332
|
+
other_goal.variables.keys +
|
333
|
+
self.arguments.variables
|
334
|
+
).map(&:to_sym).uniq
|
335
|
+
|
336
|
+
variables.each do |variable|
|
337
|
+
name = variable
|
338
|
+
|
339
|
+
unification = unify(name, name, other_goal, self)
|
340
|
+
unified &&= !!unification
|
341
|
+
if unified
|
342
|
+
unifications += unification
|
343
|
+
else
|
344
|
+
#:nocov:
|
345
|
+
self.log << "Couldn't unify: #{name.inspect} WITH #{other_goal.myid} AND #{self.myid}"
|
346
|
+
break
|
347
|
+
#:nocov:
|
348
|
+
end
|
349
|
+
end
|
350
|
+
unified &&= instantiate_unifications(unifications) if unified
|
351
|
+
|
352
|
+
unified
|
353
|
+
end
|
354
|
+
|
313
355
|
end
|
314
356
|
|
315
357
|
end
|
@@ -121,7 +121,7 @@ module Porolog
|
|
121
121
|
return nil unless variables.include?(variable)
|
122
122
|
|
123
123
|
other_variable = (variables - [variable]).first
|
124
|
-
other_variable
|
124
|
+
other_variable&.goal
|
125
125
|
end
|
126
126
|
|
127
127
|
# @return [Array<Porolog::Goal>] the Goals of the Variables of the Instantiation.
|
@@ -139,10 +139,10 @@ module Porolog
|
|
139
139
|
def values(visited = [])
|
140
140
|
return [] if visited.include?(self)
|
141
141
|
|
142
|
-
|
143
|
-
|
142
|
+
values_for_variable1 = values_for(@variable1, visited)
|
143
|
+
values_for_variable2 = values_for(@variable2, visited)
|
144
144
|
|
145
|
-
(
|
145
|
+
(values_for_variable1 + values_for_variable2).uniq
|
146
146
|
end
|
147
147
|
|
148
148
|
# @param value [Object] the provided value.
|
@@ -174,7 +174,21 @@ module Porolog
|
|
174
174
|
|
175
175
|
when Symbol
|
176
176
|
value_value = value.value(visited)
|
177
|
-
if
|
177
|
+
if index == :flathead
|
178
|
+
# value_value = [..., 4, 5, 6]
|
179
|
+
if value_value.first == UNKNOWN_TAIL
|
180
|
+
UNKNOWN_ARRAY
|
181
|
+
else
|
182
|
+
nil
|
183
|
+
end
|
184
|
+
elsif index == :flattail
|
185
|
+
# value_value = [1, 2, 3, ...]
|
186
|
+
if value_value.first == UNKNOWN_TAIL
|
187
|
+
nil
|
188
|
+
elsif value_value.last == UNKNOWN_TAIL
|
189
|
+
UNKNOWN_ARRAY
|
190
|
+
end
|
191
|
+
elsif value_value.respond_to?(index)
|
178
192
|
value_value.send(index)
|
179
193
|
else
|
180
194
|
value
|
@@ -204,6 +218,22 @@ module Porolog
|
|
204
218
|
visited = visited + [self]
|
205
219
|
|
206
220
|
if variable == @variable1
|
221
|
+
if @index1 == :flathead
|
222
|
+
flathead = value_at_index(value_indexed(@variable2.value(visited), @index2, visited), @index1).value.value
|
223
|
+
if flathead
|
224
|
+
return [[*flathead]]
|
225
|
+
else
|
226
|
+
return []
|
227
|
+
end
|
228
|
+
end
|
229
|
+
if @index1 == :flattail
|
230
|
+
flattail = value_at_index(value_indexed(@variable2.value(visited), @index2, visited), @index1).value.value
|
231
|
+
if flattail
|
232
|
+
return [[UNKNOWN_TAIL, *flattail]]
|
233
|
+
else
|
234
|
+
return []
|
235
|
+
end
|
236
|
+
end
|
207
237
|
if @index1
|
208
238
|
[value_at_index(value_indexed(@variable2.value(visited), @index2, visited), @index1)]
|
209
239
|
else
|
@@ -214,6 +244,22 @@ module Porolog
|
|
214
244
|
end
|
215
245
|
end
|
216
246
|
elsif variable == @variable2
|
247
|
+
if @index2 == :flathead
|
248
|
+
flathead = value_at_index(value_indexed(@variable1.value(visited), @index1, visited), @index2).value.value
|
249
|
+
if flathead
|
250
|
+
return [[*flathead]]
|
251
|
+
else
|
252
|
+
return []
|
253
|
+
end
|
254
|
+
end
|
255
|
+
if @index2 == :flattail
|
256
|
+
flattail = value_at_index(value_indexed(@variable1.value(visited), @index1, visited), @index2).value.value
|
257
|
+
if flattail
|
258
|
+
return [[UNKNOWN_TAIL, *flattail]]
|
259
|
+
else
|
260
|
+
return []
|
261
|
+
end
|
262
|
+
end
|
217
263
|
if @index2
|
218
264
|
[value_at_index(value_indexed(@variable1.value(visited), @index1, visited), @index2)]
|
219
265
|
else
|
@@ -241,10 +287,14 @@ module Porolog
|
|
241
287
|
|
242
288
|
when Symbol
|
243
289
|
case index
|
290
|
+
when :flathead
|
291
|
+
[*value, UNKNOWN_TAIL]
|
244
292
|
when :head
|
245
293
|
[value, UNKNOWN_TAIL]
|
246
294
|
when :tail
|
247
295
|
[nil, *value]
|
296
|
+
when :flattail
|
297
|
+
value
|
248
298
|
else
|
249
299
|
raise UnhandledIndexError, "Unhandled index: #{index.inspect} for #{value.inspect}"
|
250
300
|
end
|
@@ -253,10 +303,6 @@ module Porolog
|
|
253
303
|
if index.empty?
|
254
304
|
[nil, *value]
|
255
305
|
else
|
256
|
-
#result = []
|
257
|
-
#result[0..index.first] = value
|
258
|
-
#result
|
259
|
-
#[*([*value][0..index.first]), UNKNOWN_TAIL]
|
260
306
|
[value, UNKNOWN_TAIL]
|
261
307
|
end
|
262
308
|
|
data/lib/porolog/predicate.rb
CHANGED
@@ -28,6 +28,10 @@ module Porolog
|
|
28
28
|
|
29
29
|
attr_reader :name, :rules
|
30
30
|
|
31
|
+
# A unique value used to verify instantiations.
|
32
|
+
UNIQUE_VALUE = Object.new.freeze
|
33
|
+
private_constant :UNIQUE_VALUE
|
34
|
+
|
31
35
|
# Returns the current scope, or sets the current scope if a paramter is provided
|
32
36
|
# (creating the new scope if necessary).
|
33
37
|
# @param scope_name [Object] the name (or otherwise object) used to register a scope.
|
@@ -67,9 +71,10 @@ module Porolog
|
|
67
71
|
# Initializes a Porolog::Predicate and registers it by its name.
|
68
72
|
# @param name [#to_sym] the input object to read from
|
69
73
|
# @param scope_name the name of the scope in which to register the Predicate; if omitted, defaults to the name of the current scope
|
70
|
-
def initialize(name, scope_name = Predicate.scope.name)
|
71
|
-
@name
|
72
|
-
@rules
|
74
|
+
def initialize(name, scope_name = Predicate.scope.name, builtin: false)
|
75
|
+
@name = name.to_sym
|
76
|
+
@rules = []
|
77
|
+
@builtin = builtin
|
73
78
|
|
74
79
|
raise NameError, "Cannot name a predicate 'predicate'" if @name == :predicate
|
75
80
|
|
@@ -80,8 +85,7 @@ module Porolog
|
|
80
85
|
# Creates a new Predicate, or returns an existing Predicate if one already exists with the given name in the current scope.
|
81
86
|
# @return [Porolog::Predicate] a new or existing Predicate.
|
82
87
|
def self.new(*args)
|
83
|
-
|
84
|
-
scope[name.to_sym] || super
|
88
|
+
scope[args.first.to_sym] || super
|
85
89
|
end
|
86
90
|
|
87
91
|
# Create Arguments for the Predicate.
|
@@ -90,14 +94,14 @@ module Porolog
|
|
90
94
|
# for
|
91
95
|
# p.arguments(x,y,z)
|
92
96
|
# @return [Porolog::Arguments] Arguments of the Predicate with the given arguments.
|
93
|
-
def call(*args)
|
94
|
-
Arguments.new(self,args)
|
97
|
+
def call(*args, &block)
|
98
|
+
Arguments.new(self, args, &block)
|
95
99
|
end
|
96
100
|
|
97
101
|
# Create Arguments for the Predicate.
|
98
102
|
# @return [Porolog::Arguments] Arguments of the Predicate with the given arguments.
|
99
|
-
def arguments(*args)
|
100
|
-
Arguments.new(self,args)
|
103
|
+
def arguments(*args, &block)
|
104
|
+
Arguments.new(self, args, &block)
|
101
105
|
end
|
102
106
|
|
103
107
|
# Add a Rule to the Predicate.
|
@@ -120,31 +124,53 @@ module Porolog
|
|
120
124
|
end
|
121
125
|
end
|
122
126
|
|
123
|
-
# Return a builtin Predicate based on its key.
|
124
|
-
# @param key [Symbol] the name (or otherwise object) used to register a scope.
|
125
|
-
# @return [Porolog::Predicate] a Predicate with the next id based on the key.
|
126
|
-
def self.builtin(key)
|
127
|
-
@builtin_predicate_ids[key] ||= 0
|
128
|
-
@builtin_predicate_ids[key] += 1
|
129
|
-
|
130
|
-
self.new("_#{key}_#{@builtin_predicate_ids[key]}")
|
131
|
-
end
|
132
|
-
|
133
127
|
# Satisfy the Predicate within the supplied Goal.
|
134
128
|
# Satisfy of each rule of the Predicate is called with the Goal and success block.
|
135
129
|
# @param goal [Porolog::Goal] the Goal within which to satisfy the Predicate.
|
136
130
|
# @param block [Proc] the block to be called each time a Rule of the Predicate is satisfied.
|
137
131
|
# @return [Boolean] whether any Rule was satisfied.
|
138
132
|
def satisfy(goal, &block)
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
133
|
+
if builtin?
|
134
|
+
satisfy_builtin(goal, &block)
|
135
|
+
else
|
136
|
+
satisfied = false
|
137
|
+
@rules.each do |rule|
|
138
|
+
rule.satisfy(goal) do |subgoal|
|
139
|
+
satisfied = true
|
140
|
+
block.call(subgoal)
|
141
|
+
end
|
142
|
+
break if goal.terminated?
|
144
143
|
end
|
145
|
-
|
144
|
+
satisfied
|
146
145
|
end
|
147
|
-
|
146
|
+
end
|
147
|
+
|
148
|
+
# @return [Boolean] whether the Predicate is a builtin predicate.
|
149
|
+
def builtin?
|
150
|
+
@builtin
|
151
|
+
end
|
152
|
+
|
153
|
+
# Satisfy the builtin Predicate within the supplied Goal.
|
154
|
+
# Call the builtin Predicate method with the Goal and success block.
|
155
|
+
# The arguments and block are extracted from the Goal's Arguments.
|
156
|
+
# @param goal [Porolog::Goal] the Goal within which to satisfy the Predicate.
|
157
|
+
# @param block [Proc] the block to be called each time a Rule of the Predicate is satisfied.
|
158
|
+
# @return [Boolean] whether any Rule was satisfied.
|
159
|
+
def satisfy_builtin(goal, &block)
|
160
|
+
predicate = goal.arguments.predicate.name
|
161
|
+
arguments = goal.arguments.arguments
|
162
|
+
arg_block = goal.arguments.block
|
163
|
+
|
164
|
+
Predicate.call_builtin(predicate, goal, block, *arguments, &arg_block)
|
165
|
+
end
|
166
|
+
|
167
|
+
# Call a builtin predicate
|
168
|
+
# @param predicate [Symbol] the name of the predicate to call.
|
169
|
+
# @param args [Array<Object>] arguments for the predicate call.
|
170
|
+
# @param arg_block [Proc] the block provided in the Arguments of the Predicate.
|
171
|
+
# @return [Boolean] whether the predicate was satisfied.
|
172
|
+
def self.call_builtin(predicate, *args, &arg_block)
|
173
|
+
Porolog::Predicate::Builtin.instance_method(predicate).bind(self).call(*args, &arg_block)
|
148
174
|
end
|
149
175
|
|
150
176
|
end
|