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.
- checksums.yaml +4 -4
- data/README.md +18 -4
- data/bin/porolog +38 -2
- data/coverage/badge.svg +1 -1
- data/coverage/index.html +52327 -6692
- data/doc/Array.html +113 -33
- data/doc/Object.html +11 -17
- data/doc/Porolog.html +3681 -73
- data/doc/Symbol.html +17 -20
- data/doc/_index.html +181 -13
- data/doc/class_list.html +1 -1
- data/doc/file.README.html +28 -38
- data/doc/index.html +28 -38
- data/doc/method_list.html +871 -167
- data/doc/top-level-namespace.html +2 -2
- data/lib/porolog.rb +1015 -2
- data/lib/porolog/arguments.rb +16 -14
- data/lib/porolog/core_ext.rb +6 -0
- data/lib/porolog/error.rb +6 -0
- data/lib/porolog/goal.rb +205 -68
- data/lib/porolog/instantiation.rb +300 -0
- data/lib/porolog/predicate.rb +33 -16
- data/lib/porolog/rule.rb +129 -2
- data/lib/porolog/scope.rb +3 -3
- data/lib/porolog/tail.rb +52 -0
- data/lib/porolog/value.rb +9 -12
- data/lib/porolog/variable.rb +307 -0
- data/test/porolog/arguments_test.rb +217 -135
- data/test/porolog/core_ext_test.rb +24 -17
- data/test/porolog/goal_test.rb +481 -74
- data/test/porolog/instantiation_test.rb +874 -0
- data/test/porolog/porolog_test.rb +2121 -13
- data/test/porolog/predicate_test.rb +1 -1
- data/test/porolog/rule_test.rb +395 -0
- data/test/porolog/scope_test.rb +0 -2
- data/test/porolog/tail_test.rb +127 -0
- data/test/porolog/value_test.rb +1 -1
- data/test/porolog/variable_test.rb +1625 -0
- data/test/test_helper.rb +78 -5
- metadata +12 -4
@@ -0,0 +1,300 @@
|
|
1
|
+
#
|
2
|
+
# lib/porolog/instantiation.rb - Plain Old Ruby Objects Prolog Engine -- Instantiation
|
3
|
+
#
|
4
|
+
# Luis Esteban 2 May 2018
|
5
|
+
# created
|
6
|
+
#
|
7
|
+
|
8
|
+
module Porolog
|
9
|
+
|
10
|
+
# A Porolog::Instantiation implements an instantiation of a Variable of a Goal.
|
11
|
+
# An Instantiation joins two expressions. At least one expression must have a variable.
|
12
|
+
# An index may be provided to index into the value of either expression, or both may have an index.
|
13
|
+
# Thus, an element of each list could be instantiated without reference to the other elements:
|
14
|
+
# x = [a,b,c,d,e]
|
15
|
+
# |
|
16
|
+
# y = [p,q,r,s]
|
17
|
+
#
|
18
|
+
# @author Luis Esteban
|
19
|
+
#
|
20
|
+
# @!attribute variable1
|
21
|
+
# @return [Variable,Object] one end of the instantiation.
|
22
|
+
# @!attribute variable2
|
23
|
+
# @return [Variable,Object] the other end of the instantiation.
|
24
|
+
# @!attribute index1
|
25
|
+
# @return [Object,nil] the index into the value of variable1.
|
26
|
+
# @!attribute index2
|
27
|
+
# @return [Object,nil] the index into the value of variable2.
|
28
|
+
#
|
29
|
+
class Instantiation
|
30
|
+
|
31
|
+
# Error class for rescuing or detecting any Instantiation error.
|
32
|
+
class Error < PorologError ; end
|
33
|
+
# Error class indicating that an instantiation was created without any Variables.
|
34
|
+
class NoVariableError < Error ; end
|
35
|
+
# Error class indicating that an index could not be used although requested.
|
36
|
+
class UnhandledIndexError < Error ; end
|
37
|
+
|
38
|
+
attr_accessor :variable1, :variable2, :index1, :index2
|
39
|
+
|
40
|
+
# Clears all instantiations.
|
41
|
+
# @return [Boolean] success
|
42
|
+
def self.reset
|
43
|
+
@@instantiations = {}
|
44
|
+
true
|
45
|
+
end
|
46
|
+
|
47
|
+
reset
|
48
|
+
|
49
|
+
# @return [Hash{Array => Porolog::Instantiation}] all Instantiations
|
50
|
+
def self.instantiations
|
51
|
+
@@instantiations
|
52
|
+
end
|
53
|
+
|
54
|
+
# Finds or creates a new instantiation. At least one of the variables must be a Variable.
|
55
|
+
# @param variable1 [Porolog::Variable,Porolog::Value,Object] one end of the instantiation to be made.
|
56
|
+
# @param variable2 [Porolog::Variable,Porolog::Value,Object] the other end of the instantiation to be made.
|
57
|
+
# @param index1 [Integer,Symbol,nil] index into the value of variable1.
|
58
|
+
# @param index2 [Integer,Symbol,nil] index into the value of variable2.
|
59
|
+
# @return [Porolog::Instantiation] the found or created Instantiation.
|
60
|
+
def self.new(variable1, index1, variable2, index2)
|
61
|
+
raise NoVariableError, "Cannot instantiate non-variables: #{variable1.inspect} and #{variable2.inspect}" unless variable1.is_a?(Variable) || variable2.is_a?(Variable)
|
62
|
+
|
63
|
+
variable1 = Value.new variable1, variable2.goal unless variable1.is_a?(Variable) || variable1.is_a?(Value)
|
64
|
+
variable2 = Value.new variable2, variable1.goal unless variable2.is_a?(Variable) || variable2.is_a?(Value)
|
65
|
+
|
66
|
+
instantiation = \
|
67
|
+
@@instantiations[[variable1, index1, variable2, index2]] ||
|
68
|
+
@@instantiations[[variable2, index2, variable1, index1]] ||
|
69
|
+
super
|
70
|
+
|
71
|
+
instantiation
|
72
|
+
end
|
73
|
+
|
74
|
+
# Initializes and registers a new Instantiation. At least one of the variables must be a Variable.
|
75
|
+
# @param variable1 [Porolog::Variable,Porolog::Value,Object] one end of the instantiation to be made.
|
76
|
+
# @param variable2 [Porolog::Variable,Porolog::Value,Object] the other end of the instantiation to be made.
|
77
|
+
# @param index1 [Integer,Symbol,nil] index into the value of variable1.
|
78
|
+
# @param index2 [Integer,Symbol,nil] index into the value of variable2.
|
79
|
+
# @return [Porolog::Instantiation] the found or created Instantiation.
|
80
|
+
def initialize(variable1, index1, variable2, index2)
|
81
|
+
@variable1 = variable1
|
82
|
+
@variable2 = variable2
|
83
|
+
@index1 = index1
|
84
|
+
@index2 = index2
|
85
|
+
|
86
|
+
@variable1.instantiations << self
|
87
|
+
@variable2.instantiations << self
|
88
|
+
|
89
|
+
@@instantiations[signature] = self
|
90
|
+
end
|
91
|
+
|
92
|
+
# @return [Array] the signature of the Instantiation, which aids in avoiding duplication instantiations.
|
93
|
+
def signature
|
94
|
+
@signature ||= [@variable1, @index1, @variable2, @index2].freeze
|
95
|
+
@signature
|
96
|
+
end
|
97
|
+
|
98
|
+
# @return [String] pretty representation.
|
99
|
+
def inspect(indent = 0)
|
100
|
+
index1 = @index1 ? "[#{@index1.inspect}]" : ''
|
101
|
+
index2 = @index2 ? "[#{@index2.inspect}]" : ''
|
102
|
+
|
103
|
+
"#{' ' * indent}#{@variable1.inspect}#{index1} = #{@variable2.inspect}#{index2}"
|
104
|
+
end
|
105
|
+
|
106
|
+
# Removes itself from its variables' Instantiations and unregisters itself.
|
107
|
+
# @return [Boolean] success
|
108
|
+
def remove
|
109
|
+
@variable1.instantiations.delete(self) if @variable1.is_a?(Variable) || @variable1.is_a?(Value)
|
110
|
+
@variable2.instantiations.delete(self) if @variable2.is_a?(Variable) || @variable2.is_a?(Value)
|
111
|
+
|
112
|
+
@deleted = true
|
113
|
+
@@instantiations.delete(signature)
|
114
|
+
@deleted
|
115
|
+
end
|
116
|
+
|
117
|
+
# Returns the Goal of the other Variable to the one provided.
|
118
|
+
# @param variable [Porolog::Variable] the provided Variable.
|
119
|
+
# @return [Porolog::Goal,nil] the Goal of the other Variable.
|
120
|
+
def other_goal_to(variable)
|
121
|
+
return nil unless variables.include?(variable)
|
122
|
+
|
123
|
+
other_variable = (variables - [variable]).first
|
124
|
+
other_variable && other_variable.goal
|
125
|
+
end
|
126
|
+
|
127
|
+
# @return [Array<Porolog::Goal>] the Goals of the Variables of the Instantiation.
|
128
|
+
def goals
|
129
|
+
[@variable1.goal,@variable2.goal]
|
130
|
+
end
|
131
|
+
|
132
|
+
# @return [Array<Porolog::Variable,Object>] the Variables of the Instantiation.
|
133
|
+
def variables
|
134
|
+
[@variable1,@variable2]
|
135
|
+
end
|
136
|
+
|
137
|
+
# @param visited [Array] prevents infinite recursion.
|
138
|
+
# @return [Array] the values of the Variables of the Instantiation.
|
139
|
+
def values(visited = [])
|
140
|
+
return [] if visited.include?(self)
|
141
|
+
|
142
|
+
vv1 = values_for(@variable1, visited)
|
143
|
+
vv2 = values_for(@variable2, visited)
|
144
|
+
|
145
|
+
(vv1 + vv2).uniq
|
146
|
+
end
|
147
|
+
|
148
|
+
# @param value [Object] the provided value.
|
149
|
+
# @param index [Integer,Symbol,Array<Integer>] the index.
|
150
|
+
# @param visited [Array] prevents infinite recursion.
|
151
|
+
# @return [Object] the indexed value of the provided value.
|
152
|
+
def value_indexed(value, index, visited = [])
|
153
|
+
return nil unless value
|
154
|
+
return nil if value.type == :variable
|
155
|
+
return nil if value == [] && index == :tail
|
156
|
+
|
157
|
+
visit = [value, index]
|
158
|
+
return nil if visited.include?(visit)
|
159
|
+
visited = visited + [visit]
|
160
|
+
|
161
|
+
case index
|
162
|
+
when Integer
|
163
|
+
if value.respond_to?(:last) && value.last == UNKNOWN_TAIL
|
164
|
+
if index >= value.length - 1
|
165
|
+
UNKNOWN_TAIL
|
166
|
+
else
|
167
|
+
value[index]
|
168
|
+
end
|
169
|
+
elsif value.is_a?(Numeric)
|
170
|
+
value
|
171
|
+
else
|
172
|
+
value[index]
|
173
|
+
end
|
174
|
+
|
175
|
+
when Symbol
|
176
|
+
value_value = value.value(visited)
|
177
|
+
if value_value.respond_to?(index)
|
178
|
+
value_value.send(index)
|
179
|
+
else
|
180
|
+
value
|
181
|
+
end
|
182
|
+
|
183
|
+
when Array
|
184
|
+
if index.empty?
|
185
|
+
value[1..-1]
|
186
|
+
else
|
187
|
+
value[0...index.first]
|
188
|
+
end
|
189
|
+
|
190
|
+
else
|
191
|
+
if index
|
192
|
+
raise UnhandledIndexError, "Unhandled index: #{index.inspect} of #{value.inspect}"
|
193
|
+
else
|
194
|
+
value
|
195
|
+
end
|
196
|
+
end
|
197
|
+
end
|
198
|
+
|
199
|
+
# @param variable [Porolog::Variable,Porolog::Value] the specified variable (or end of the Instantiation).
|
200
|
+
# @param visited [Array] prevents infinite recursion.
|
201
|
+
# @return [Array<Object>] the values for the specified variable by finding the values of the other variable (i.e. the other end) and applying any indexes.
|
202
|
+
def values_for(variable, visited = [])
|
203
|
+
return [] if visited.include?(self)
|
204
|
+
visited = visited + [self]
|
205
|
+
|
206
|
+
if variable == @variable1
|
207
|
+
if @index1
|
208
|
+
[value_at_index(value_indexed(@variable2.value(visited), @index2, visited), @index1)]
|
209
|
+
else
|
210
|
+
if @variable2.is_a?(Variable)
|
211
|
+
[value_indexed(@variable2.value(visited), @index2, visited)]
|
212
|
+
else
|
213
|
+
[value_indexed(@variable2, @index2, visited)]
|
214
|
+
end
|
215
|
+
end
|
216
|
+
elsif variable == @variable2
|
217
|
+
if @index2
|
218
|
+
[value_at_index(value_indexed(@variable1.value(visited), @index1, visited), @index2)]
|
219
|
+
else
|
220
|
+
if @variable1.is_a?(Variable)
|
221
|
+
[value_indexed(@variable1.value(visited), @index1, visited)]
|
222
|
+
else
|
223
|
+
[value_indexed(@variable1, @index1, visited)]
|
224
|
+
end
|
225
|
+
end
|
226
|
+
else
|
227
|
+
[]
|
228
|
+
end.compact
|
229
|
+
end
|
230
|
+
|
231
|
+
# @param value [Object] the known value.
|
232
|
+
# @param index [Integer,Symbol,Array] the known index.
|
233
|
+
# @return [Array] an Array where the known value is at the known index.
|
234
|
+
def value_at_index(value, index)
|
235
|
+
value && case index
|
236
|
+
when Integer
|
237
|
+
result = []
|
238
|
+
result[index] = value
|
239
|
+
result << UNKNOWN_TAIL
|
240
|
+
result
|
241
|
+
|
242
|
+
when Symbol
|
243
|
+
case index
|
244
|
+
when :head
|
245
|
+
[value, UNKNOWN_TAIL]
|
246
|
+
when :tail
|
247
|
+
[nil, *value]
|
248
|
+
else
|
249
|
+
raise UnhandledIndexError, "Unhandled index: #{index.inspect} for #{value.inspect}"
|
250
|
+
end
|
251
|
+
|
252
|
+
when Array
|
253
|
+
if index.empty?
|
254
|
+
[nil, *value]
|
255
|
+
else
|
256
|
+
#result = []
|
257
|
+
#result[0..index.first] = value
|
258
|
+
#result
|
259
|
+
#[*([*value][0..index.first]), UNKNOWN_TAIL]
|
260
|
+
[value, UNKNOWN_TAIL]
|
261
|
+
end
|
262
|
+
|
263
|
+
else
|
264
|
+
if index
|
265
|
+
raise UnhandledIndexError, "Unhandled index: #{index.inspect} for #{value.inspect}"
|
266
|
+
else
|
267
|
+
value
|
268
|
+
end
|
269
|
+
end
|
270
|
+
end
|
271
|
+
|
272
|
+
# @param variable [Porolog::Variable,Porolog::Value] the specified variable.
|
273
|
+
# @return [Boolean] whether the specified variable is not indexed.
|
274
|
+
def without_index_on?(variable)
|
275
|
+
[
|
276
|
+
[@variable1,@index1],
|
277
|
+
[@variable2,@index2],
|
278
|
+
].any?{|pair|
|
279
|
+
pair.first == variable && pair.last.nil?
|
280
|
+
}
|
281
|
+
end
|
282
|
+
|
283
|
+
# @return [Boolean] whether the Instantiation has been deleted (memoized).
|
284
|
+
def deleted?
|
285
|
+
@deleted ||= @variable1.goal.deleted? || @variable2.goal.deleted?
|
286
|
+
@deleted
|
287
|
+
end
|
288
|
+
|
289
|
+
# @param goal [Porolog::Goal] the provided Goal.
|
290
|
+
# @return [Boolean] whether the Instantiation attaches to a variable in the provided Goal.
|
291
|
+
def belongs_to?(goal)
|
292
|
+
[
|
293
|
+
@variable1.goal,
|
294
|
+
@variable2.goal,
|
295
|
+
].include?(goal)
|
296
|
+
end
|
297
|
+
|
298
|
+
end
|
299
|
+
|
300
|
+
end
|
data/lib/porolog/predicate.rb
CHANGED
@@ -10,17 +10,21 @@ module Porolog
|
|
10
10
|
# A Porolog::Predicate corresponds to a Prolog predicate.
|
11
11
|
# It contains rules (Porolog::Rule) and belongs to a Porolog::Scope.
|
12
12
|
# When provided with arguments, it produces a Porolog::Arguments.
|
13
|
+
#
|
13
14
|
# @author Luis Esteban
|
15
|
+
#
|
14
16
|
# @!attribute [r] name
|
15
17
|
# Name of the Predicate.
|
18
|
+
#
|
16
19
|
# @!attribute [r] rules
|
17
20
|
# Rules of the Predicate.
|
21
|
+
#
|
18
22
|
class Predicate
|
19
23
|
|
20
24
|
# Error class for rescuing or detecting any Predicate error.
|
21
|
-
class
|
25
|
+
class Error < PorologError ; end
|
22
26
|
# Error class indicating an error with the name of a Predicate.
|
23
|
-
class NameError <
|
27
|
+
class NameError < Error ; end
|
24
28
|
|
25
29
|
attr_reader :name, :rules
|
26
30
|
|
@@ -40,9 +44,7 @@ module Porolog
|
|
40
44
|
# @param scope_name [Object] the name (or otherwise object) used to register a scope.
|
41
45
|
# @return [Porolog::Scope] the current Scope.
|
42
46
|
def self.scope=(scope_name)
|
43
|
-
if scope_name
|
44
|
-
@@current_scope = Scope[scope_name] || Scope.new(scope_name)
|
45
|
-
end
|
47
|
+
@@current_scope = Scope[scope_name] || Scope.new(scope_name) if scope_name
|
46
48
|
@@current_scope
|
47
49
|
end
|
48
50
|
|
@@ -69,7 +71,7 @@ module Porolog
|
|
69
71
|
@name = name.to_sym
|
70
72
|
@rules = []
|
71
73
|
|
72
|
-
raise NameError
|
74
|
+
raise NameError, "Cannot name a predicate 'predicate'" if @name == :predicate
|
73
75
|
|
74
76
|
scope = Scope[scope_name] || Scope.new(scope_name)
|
75
77
|
scope[@name] = self
|
@@ -83,14 +85,17 @@ module Porolog
|
|
83
85
|
end
|
84
86
|
|
85
87
|
# Create Arguments for the Predicate.
|
86
|
-
# Provides the syntax
|
87
|
-
#
|
88
|
+
# Provides the syntax option:
|
89
|
+
# p.(x,y,z)
|
90
|
+
# for
|
91
|
+
# p.arguments(x,y,z)
|
88
92
|
# @return [Porolog::Arguments] Arguments of the Predicate with the given arguments.
|
89
93
|
def call(*args)
|
90
94
|
Arguments.new(self,args)
|
91
95
|
end
|
92
96
|
|
93
97
|
# Create Arguments for the Predicate.
|
98
|
+
# @return [Porolog::Arguments] Arguments of the Predicate with the given arguments.
|
94
99
|
def arguments(*args)
|
95
100
|
Arguments.new(self,args)
|
96
101
|
end
|
@@ -106,18 +111,13 @@ module Porolog
|
|
106
111
|
# A pretty print String of the Predicate.
|
107
112
|
# @return [String] the Predicate as a String.
|
108
113
|
def inspect
|
109
|
-
lines = []
|
110
|
-
|
111
114
|
if @rules.size == 1
|
112
|
-
|
115
|
+
"#{@name}:-#{@rules.first.inspect}"
|
113
116
|
else
|
114
|
-
|
115
|
-
@rules.each do |rule|
|
117
|
+
@rules.each_with_object(["#{@name}:-"]) do |rule, lines|
|
116
118
|
lines << rule.inspect
|
117
|
-
end
|
119
|
+
end.join("\n")
|
118
120
|
end
|
119
|
-
|
120
|
-
lines.join("\n")
|
121
121
|
end
|
122
122
|
|
123
123
|
# Return a builtin Predicate based on its key.
|
@@ -130,6 +130,23 @@ module Porolog
|
|
130
130
|
self.new("_#{key}_#{@builtin_predicate_ids[key]}")
|
131
131
|
end
|
132
132
|
|
133
|
+
# Satisfy the Predicate within the supplied Goal.
|
134
|
+
# Satisfy of each rule of the Predicate is called with the Goal and success block.
|
135
|
+
# @param goal [Porolog::Goal] the Goal within which to satisfy the Predicate.
|
136
|
+
# @param block [Proc] the block to be called each time a Rule of the Predicate is satisfied.
|
137
|
+
# @return [Boolean] whether any Rule was satisfied.
|
138
|
+
def satisfy(goal, &block)
|
139
|
+
satisfied = false
|
140
|
+
@rules.each do |rule|
|
141
|
+
rule.satisfy(goal) do |subgoal|
|
142
|
+
satisfied = true
|
143
|
+
block.call(subgoal)
|
144
|
+
end
|
145
|
+
break if goal.terminated?
|
146
|
+
end
|
147
|
+
satisfied
|
148
|
+
end
|
149
|
+
|
133
150
|
end
|
134
151
|
|
135
152
|
end
|
data/lib/porolog/rule.rb
CHANGED
@@ -8,17 +8,25 @@
|
|
8
8
|
module Porolog
|
9
9
|
|
10
10
|
# A Porolog::Rule is one clause of a Porolog::Predicate.
|
11
|
+
#
|
11
12
|
# @author Luis Esteban
|
13
|
+
#
|
12
14
|
# @!attribute [r] arguments
|
13
15
|
# The Arguments of the Predicate for which this Rule applies.
|
14
16
|
# @!attribute [r] definition
|
15
17
|
# The definition of the Rule.
|
18
|
+
#
|
16
19
|
class Rule
|
17
20
|
|
21
|
+
# Error class for rescuing or detecting any Rule error.
|
22
|
+
class Error < PorologError ; end
|
23
|
+
# Error class indicating that there is an error in the definition of the Rule.
|
24
|
+
class DefinitionError < Error ; end
|
25
|
+
|
18
26
|
attr_reader :arguments, :definition
|
19
27
|
|
20
28
|
# Clears all Rules.
|
21
|
-
# @return [
|
29
|
+
# @return [Boolean] success
|
22
30
|
def self.reset
|
23
31
|
@@rules = []
|
24
32
|
true
|
@@ -41,11 +49,130 @@ module Porolog
|
|
41
49
|
"Rule#{(@@rules.index(self) || -1000) + 1}"
|
42
50
|
end
|
43
51
|
|
44
|
-
# @return [String] pretty representation
|
52
|
+
# @return [String] pretty representation.
|
45
53
|
def inspect
|
46
54
|
" #{@arguments.inspect}:- #{@definition.inspect}"
|
47
55
|
end
|
48
56
|
|
57
|
+
# Try to satisfy the Rule for the given Goal.
|
58
|
+
# @param goal [Porolog::Goal] the Goal in which to satisfy this Rule.
|
59
|
+
# @param block [Proc] code to perform when the Rule is satisfied.
|
60
|
+
# @return [Boolean] the success of deleting the subgoal.
|
61
|
+
def satisfy(goal, &block)
|
62
|
+
subgoal = Goal.new self.arguments, goal
|
63
|
+
|
64
|
+
unified_goals = unify_goals(goal, subgoal)
|
65
|
+
if unified_goals
|
66
|
+
satisfy_definition(goal, subgoal) do |solution_goal|
|
67
|
+
block.call(solution_goal)
|
68
|
+
end
|
69
|
+
else
|
70
|
+
subgoal.log << "Dead-end: Cannot unify with #{goal.inspect}"
|
71
|
+
end
|
72
|
+
|
73
|
+
subgoal.delete!
|
74
|
+
end
|
75
|
+
|
76
|
+
# Try to satisfy the definition of the Rule for a given Goal.
|
77
|
+
# @param goal [Porolog::Goal] the given Goal for the Rule.
|
78
|
+
# @param subgoal [Porolog::Goal] the subgoal for the Rule's definition.
|
79
|
+
# @param block [Proc] code to perform when the definition is satisfied.
|
80
|
+
# @return [Boolean] whether the definition was satisfied.
|
81
|
+
def satisfy_definition(goal, subgoal, &block)
|
82
|
+
case @definition
|
83
|
+
when TrueClass
|
84
|
+
block.call(subgoal)
|
85
|
+
true
|
86
|
+
|
87
|
+
when FalseClass
|
88
|
+
false
|
89
|
+
|
90
|
+
when Array
|
91
|
+
satisfy_conjunction(goal, subgoal, @definition) do |solution_goal|
|
92
|
+
block.call(solution_goal)
|
93
|
+
end
|
94
|
+
|
95
|
+
else
|
96
|
+
raise DefinitionError, "UNEXPECTED TYPE OF DEFINITION: #{@definition.inspect} (#{@definition.class})"
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
# Try to satisfy the conjunction of the definition of the Rule for a given Goal.
|
101
|
+
# A conjunction is a sequence of expressions where the sequence is true
|
102
|
+
# if all the expressions are true.
|
103
|
+
#
|
104
|
+
# @param goal [Porolog::Goal] the given Goal for the Rule.
|
105
|
+
# @param subgoal [Porolog::Goal] the subgoal for the Rule's definition.
|
106
|
+
# @param conjunction [Array] the conjunction to satisfy.
|
107
|
+
# @param block [Proc] code to perform when the definition is satisfied.
|
108
|
+
# @return [Boolean] whether the definition was satisfied.
|
109
|
+
def satisfy_conjunction(goal, subgoal, conjunction, &block)
|
110
|
+
arguments = conjunction.first
|
111
|
+
conjunction = conjunction[1..-1]
|
112
|
+
|
113
|
+
# -- Handle non-Arguments --
|
114
|
+
case arguments
|
115
|
+
when :CUT, true
|
116
|
+
subgoal.log << "CUTTING #{goal.inspect}..."
|
117
|
+
result = true
|
118
|
+
if conjunction.empty?
|
119
|
+
block.call(subgoal)
|
120
|
+
else
|
121
|
+
result = satisfy_conjunction(goal, subgoal, conjunction, &block)
|
122
|
+
end
|
123
|
+
|
124
|
+
if arguments == :CUT
|
125
|
+
subgoal.calling_goal.terminate!
|
126
|
+
subgoal.calling_goal.log << "TERMINATED by #{subgoal.inspect}"
|
127
|
+
end
|
128
|
+
|
129
|
+
return result
|
130
|
+
|
131
|
+
when false
|
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
|
+
|
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
|
154
|
+
end
|
155
|
+
|
156
|
+
# -- Instantiate Unifications --
|
157
|
+
unified &&= instantiate_unifications(unifications) if unified
|
158
|
+
|
159
|
+
# -- Satisfy Subgoal --
|
160
|
+
result = false
|
161
|
+
unified && subsubgoal.satisfy() do
|
162
|
+
result = true
|
163
|
+
if conjunction.empty?
|
164
|
+
block.call(goal)
|
165
|
+
else
|
166
|
+
result = satisfy_conjunction(goal, subsubgoal, conjunction, &block)
|
167
|
+
end
|
168
|
+
end
|
169
|
+
|
170
|
+
# -- Uninstantiate --
|
171
|
+
subsubgoal.delete!
|
172
|
+
|
173
|
+
result
|
174
|
+
end
|
175
|
+
|
49
176
|
end
|
50
177
|
|
51
178
|
end
|