porolog 0.0.4 → 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.
Files changed (44) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +30 -5
  3. data/Rakefile +7 -2
  4. data/bin/porolog +58 -1
  5. data/coverage/badge.svg +1 -1
  6. data/coverage/index.html +76733 -2638
  7. data/doc/Array.html +1066 -0
  8. data/doc/Object.html +674 -0
  9. data/doc/Porolog.html +4153 -74
  10. data/doc/Symbol.html +501 -0
  11. data/doc/_index.html +280 -6
  12. data/doc/class_list.html +1 -1
  13. data/doc/file.README.html +34 -39
  14. data/doc/index.html +34 -39
  15. data/doc/method_list.html +1337 -57
  16. data/doc/top-level-namespace.html +4 -2
  17. data/lib/porolog.rb +1144 -4
  18. data/lib/porolog/arguments.rb +28 -24
  19. data/lib/porolog/core_ext.rb +188 -0
  20. data/lib/porolog/error.rb +9 -0
  21. data/lib/porolog/goal.rb +357 -0
  22. data/lib/porolog/instantiation.rb +346 -0
  23. data/lib/porolog/predicate.rb +74 -31
  24. data/lib/porolog/predicate/builtin.rb +825 -0
  25. data/lib/porolog/rule.rb +162 -0
  26. data/lib/porolog/scope.rb +4 -4
  27. data/lib/porolog/tail.rb +57 -0
  28. data/lib/porolog/value.rb +105 -0
  29. data/lib/porolog/variable.rb +325 -0
  30. data/test/porolog/arguments_test.rb +244 -195
  31. data/test/porolog/core_ext_test.rb +290 -0
  32. data/test/porolog/goal_test.rb +891 -0
  33. data/test/porolog/instantiation_test.rb +910 -0
  34. data/test/porolog/porolog_test.rb +2376 -13
  35. data/test/porolog/predicate/builtin_test.rb +1340 -0
  36. data/test/porolog/predicate_test.rb +84 -30
  37. data/test/porolog/rule_test.rb +527 -0
  38. data/test/porolog/scope_test.rb +0 -2
  39. data/test/porolog/tail_test.rb +127 -0
  40. data/test/porolog/value_test.rb +315 -0
  41. data/test/porolog/variable_test.rb +1614 -0
  42. data/test/samples_test.rb +277 -0
  43. data/test/test_helper.rb +115 -0
  44. metadata +34 -7
@@ -11,14 +11,16 @@ module Porolog
11
11
  # A predicate is not like a subroutine, although there are many similarities.
12
12
  # An Arguments represents an instance of a predicate with a specific set of arguments.
13
13
  # This forms the basis for implementing a goal to solve the predicate with those specific arguments.
14
+ #
14
15
  # @author Luis Esteban
16
+ #
15
17
  # @!attribute [r] predicate
16
18
  # The Predicate for which these are the arguments.
17
19
  # @!attribute [r] arguments
18
20
  # The actual arguments.
19
21
  class Arguments
20
22
 
21
- attr_reader :predicate, :arguments
23
+ attr_reader :predicate, :arguments, :block
22
24
 
23
25
  # Unregisters all Arguments
24
26
  # @return [true]
@@ -32,9 +34,10 @@ module Porolog
32
34
  # Creates a new Arguments for a Predicate
33
35
  # @param predicate [Porolog::Predicate] the Predicate for which these are the arguments
34
36
  # @param arguments [Array<Object>] the actual arguments
35
- def initialize(predicate, arguments)
37
+ def initialize(predicate, arguments, &block)
36
38
  @predicate = predicate
37
39
  @arguments = arguments
40
+ @block = block
38
41
  @@arguments << self
39
42
  end
40
43
 
@@ -52,7 +55,8 @@ module Porolog
52
55
 
53
56
  # @return [String] pretty representation
54
57
  def inspect
55
- "#{@predicate.name}(#{@arguments && @arguments.map(&:inspect).join(',')})"
58
+ block_inspect = block.nil? ? '' : "{#{block.inspect}}"
59
+ "#{@predicate&.name}(#{@arguments&.map(&:inspect).join(',')})#{block_inspect}"
56
60
  end
57
61
 
58
62
  # Creates a fact rule that states that these arguments satisfy the Predicate.
@@ -101,9 +105,7 @@ module Porolog
101
105
 
102
106
  # @return [Array<Symbol>] the variables contained in the arguments
103
107
  def variables
104
- @arguments.map{|argument|
105
- argument.variables
106
- }.flatten.uniq
108
+ @arguments.map(&:variables).flatten.uniq
107
109
  end
108
110
 
109
111
  # Creates a Goal for solving this Arguments for the Predicate
@@ -114,22 +116,23 @@ module Porolog
114
116
  end
115
117
 
116
118
  # Returns memoized solutions
117
- # @param number [Integer] the maximum number of solutions to find (nil means find all)
118
- # @return [Array<Hash>] the solutions found (memoized)
119
- def solutions(number = nil)
120
- @solutions ||= solve(number)
121
- @solutions
119
+ # @param max_solutions [Integer] the maximum number of solutions to find (nil means find all)
120
+ # @return [Array<Hash{Symbol => Object}>] the solutions found (memoized)
121
+ def solutions(max_solutions = nil)
122
+ @solutions ||= solve(max_solutions)
123
+ max_solutions && @solutions[0...max_solutions] || @solutions
122
124
  end
123
125
 
124
126
  # Solves the Arguments
125
- # @param number_of_solutions [Integer] the maximum number of solutions to find (nil means find all)
126
- # @return [Array<Hash>] the solutions found
127
- def solve(number_of_solutions = nil)
128
- @solutions = goal.solve(number_of_solutions)
127
+ # @param max_solutions [Integer] the maximum number of solutions to find (nil means find all)
128
+ # @return [Array<Hash{Symbol => Object}>] the solutions found
129
+ def solve(max_solutions = nil)
130
+ @solutions = goal.solve(max_solutions)
129
131
  end
130
132
 
131
133
  # Extracts solution values.
132
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)
133
136
  # @return [Array<Object>] all the values for the variables given
134
137
  # @example
135
138
  # predicate :treasure_at
@@ -138,27 +141,28 @@ module Porolog
138
141
  # xs = arguments.solve_for(:X)
139
142
  # ys = arguments.solve_for(:Y)
140
143
  # coords = xs.zip(ys)
141
- def solve_for(*variables)
144
+ def solve_for(*variables, max_solutions: nil)
142
145
  variables = [*variables]
143
- solutions.map{|solution|
144
- variables.map{|variable| solution[variable] }
146
+ solutions(max_solutions).map{|solution|
147
+ values = variables.map{|variable| solution[variable] }
148
+ if values.size == 1
149
+ values.first
150
+ else
151
+ values
152
+ end
145
153
  }
146
154
  end
147
155
 
148
156
  # @return [Boolean] whether any solutions were found
149
157
  def valid?
150
- !solutions.empty?
158
+ !solutions(1).empty?
151
159
  end
152
160
 
153
161
  # Duplicates the Arguments in the context of the given goal
154
162
  # @param goal [Porolog::Goal] the destination goal
155
163
  # @return [Porolog::Arguments] the duplicated Arguments
156
164
  def dup(goal)
157
- arguments_dup = arguments.dup
158
- (0...arguments_dup.size).each do |i|
159
- arguments_dup[i] = arguments_dup[i].dup(goal) if arguments_dup[i].is_a?(HeadTail)
160
- end
161
- self.class.new @predicate, arguments_dup
165
+ self.class.new @predicate, goal.variablise(arguments), &@block
162
166
  end
163
167
 
164
168
  # @param other [Porolog::Arguments] arguments for comparison
@@ -0,0 +1,188 @@
1
+ #
2
+ # lib/porolog/core_ext.rb - Plain Old Ruby Objects Prolog Engine -- Core Extensions
3
+ #
4
+ # Luis Esteban 2 May 2018
5
+ # created
6
+ #
7
+ # @author Luis Esteban
8
+ # Extends Core Ruy Classes
9
+ #
10
+
11
+ # In Porolog, Objects represent atomic values.
12
+ class Object
13
+
14
+ # A convenience method for testing/debugging.
15
+ # @return [String] the equivalent of inspect.
16
+ def myid
17
+ inspect
18
+ end
19
+
20
+ # @return [Array] embedded variables (for an Object, should be none).
21
+ def variables
22
+ []
23
+ end
24
+
25
+ # @return [Symbol] the type of the object (for an Object, should be :atomic)
26
+ def type
27
+ :atomic
28
+ end
29
+
30
+ # @return [Object] the value of the object, which is itself.
31
+ def value(*)
32
+ self
33
+ end
34
+
35
+ # @param other [Object] the Object which is to be the tail
36
+ # @return [Array] an Array with the Object being the head and the other Object being the tail.
37
+ # @example
38
+ # 'head' / ['body', 'foot'] # ==> ['head', 'body', 'foot']
39
+ # 7.tail(:t) # ==> [7, *:t]
40
+ def tail(other)
41
+ [self]/other
42
+ end
43
+
44
+ # Syntactic sugar to look like Prolog's [H|T]
45
+ alias / :tail
46
+
47
+ # @return [Boolean] whether the Object is an Array with a head and a tail (for an Object, should be false).
48
+ def headtail?
49
+ false
50
+ end
51
+
52
+ end
53
+
54
+
55
+ # In Porolog, Symbols represent Variables.
56
+ class Symbol
57
+
58
+ # A convenience method for testing/debugging.
59
+ # @return [String] the equivalent of inspect.
60
+ def myid
61
+ inspect
62
+ end
63
+
64
+ # @return [Array] embedded variables (for a Symbol, should be itself).
65
+ def variables
66
+ [self]
67
+ end
68
+
69
+ # @return [Symbol] the type of the object (for a Symbol, should be :variable)
70
+ def type
71
+ :variable
72
+ end
73
+
74
+ # @param other [Object] the Object which is to be the tail
75
+ # @return [Array] an Array with the Object being the head and the other Object being the tail.
76
+ def /(other)
77
+ [self]/other
78
+ end
79
+
80
+ end
81
+
82
+
83
+ # In Porolog, Arrays are the equivalent of lists.
84
+ class Array
85
+
86
+ # @return [Array] embedded variables.
87
+ def variables
88
+ map(&:variables).flatten
89
+ end
90
+
91
+ # @return [Array] the values of its elements.
92
+ def value(visited = [])
93
+ return self if visited.include?(self)
94
+ visited = visited + [self]
95
+ flat_map{|element|
96
+ if element.is_a?(Tail)
97
+ tail = element.value(visited)
98
+ if tail.is_a?(Array)
99
+ tail
100
+ elsif tail.is_a?(Variable) || tail.is_a?(Value)
101
+ tail = tail.value(visited)
102
+ if tail.is_a?(Array)
103
+ tail
104
+ elsif tail.is_a?(Variable) || tail.is_a?(Value)
105
+ tail = tail.goal.variablise(tail.value(visited))
106
+ if tail.is_a?(Array)
107
+ tail
108
+ else
109
+ [element]
110
+ end
111
+ else
112
+ [element]
113
+ end
114
+ else
115
+ [element]
116
+ end
117
+ else
118
+ [element.value(visited)]
119
+ end
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
+ }
137
+ end
138
+
139
+ # @return [Symbol] the type of the object (for an Array, should be :array)
140
+ def type
141
+ :array
142
+ end
143
+
144
+ # @param other [Object] the Object which is to be the tail
145
+ # @return [Array] an Array with the Object being the head and the other Object being the tail.
146
+ def /(other)
147
+ if other.is_a?(Porolog::Variable) || other.is_a?(Symbol)
148
+ self + [Tail.new(other)]
149
+ else
150
+ self + [*other]
151
+ end
152
+ end
153
+
154
+ # @param head_size [Integer] specifies the size of the head
155
+ # @return [Object] the head of the Array
156
+ def head(head_size = 1)
157
+ if head_size == 1
158
+ if first == Porolog::UNKNOWN_TAIL
159
+ nil
160
+ else
161
+ first
162
+ end
163
+ else
164
+ self[0...head_size]
165
+ end
166
+ end
167
+
168
+ # @param head_size [Integer] specifies the size of the head
169
+ # @return [Object] the tail of the Array
170
+ def tail(head_size = 1)
171
+ if last == Porolog::UNKNOWN_TAIL
172
+ [*self[head_size..-2], Porolog::UNKNOWN_TAIL]
173
+ else
174
+ [*self[head_size..-1]]
175
+ end
176
+ end
177
+
178
+ # @return [Boolean] whether the Object is an Array with a head and a tail.
179
+ def headtail?
180
+ length == 2 && (last.is_a?(Tail) || last == UNKNOWN_TAIL)
181
+ end
182
+
183
+ # @return [Porolog::Goal] the goal that is most likely to be the goal for this array.
184
+ def goal
185
+ map{|element| element.goal if element.respond_to?(:goal) }.flatten.find{|goal| goal.is_a?(Porolog::Goal) }
186
+ end
187
+
188
+ end
@@ -8,6 +8,15 @@
8
8
  module Porolog
9
9
 
10
10
  # Error class to enable rescuing or detecting any Porolog error.
11
+ #
12
+ # @author Luis Esteban
13
+ #
11
14
  class PorologError < RuntimeError ; end
15
+
16
+ # Error indicating that a Goal is needed but could not be found.
17
+ class NoGoalError < PorologError ; end
18
+
19
+ # Error indicating that a Variable is needed but was not provided.
20
+ class NonVariableError < PorologError ; end
12
21
 
13
22
  end
@@ -0,0 +1,357 @@
1
+ #
2
+ # lib/porolog/goal.rb - Plain Old Ruby Objects Prolog Engine -- Goal
3
+ #
4
+ # Luis Esteban 2 May 2018
5
+ # created
6
+ #
7
+
8
+ module Porolog
9
+
10
+ # A Porolog::Goal finds solutions for specific Arguments of a Predicate.
11
+ #
12
+ # @author Luis Esteban
13
+ #
14
+ # @!attribute calling_goal
15
+ # @return [Porolog::Goal, nil] The parent goal if this goal is a subgoal, otherwise nil.
16
+ # @!attribute arguments
17
+ # @return [Porolog::Arguments] The specific Arguments this goal is trying to solve.
18
+ # @!attribute index
19
+ # @return [Integer, nil] Solution index for builtin predicates.
20
+ # @!attribute result
21
+ # @return [Boolean] Whether any result has been found.
22
+ # @!attribute description
23
+ # @return [String] Description of the Arguments the Goal is attempting to solve.
24
+ # @!attribute log
25
+ # @return [Array<String>] Log of events, namely unification failures.
26
+ #
27
+ class Goal
28
+
29
+ # Error class for rescuing or detecting any Goal error.
30
+ class Error < PorologError ; end
31
+ # Error class indicating an unexpected type of Rule definition.
32
+ class DefinitionError < Error ; end
33
+ # Error class indicating a non-Variable was attempted to be instantiated.
34
+ class NotVariableError < Error ; end
35
+
36
+ # Clears all goals and ensures they are deleted.
37
+ # @return [Boolean] success
38
+ def self.reset
39
+ @@goals ||= []
40
+ goals = @@goals
41
+ @@goals = []
42
+ @@goal_count = 0
43
+ goals.map(&:deleted?)
44
+ true
45
+ end
46
+
47
+ # @return [Integer] the number of goals created
48
+ def self.goal_count
49
+ @@goal_count
50
+ end
51
+
52
+ reset
53
+
54
+ attr_accessor :calling_goal, :arguments, :index, :result, :description, :log
55
+
56
+ # Initializes and registers the Goal.
57
+ # @param arguments [Porolog::Arguments] the Predicate Arguments that this Goal is to solve.
58
+ # @param calling_goal [Porolog::Goal] the parent Goal if this Goal is a subgoal.
59
+ def initialize(arguments, calling_goal = nil)
60
+ @@goals << self
61
+ @@goal_count += 1
62
+
63
+ @arguments = arguments
64
+ @terminate = false
65
+ @calling_goal = calling_goal
66
+ @variables = {}
67
+ @values = {}
68
+ @result = nil
69
+ @description = "Solve #{arguments.inspect}"
70
+ @log = []
71
+
72
+ @arguments = @arguments.dup(self) if @arguments.is_a?(Arguments)
73
+ @solutions = []
74
+ end
75
+
76
+ # @return [Array<Porolog::Goal>] the calling chain for this goal.
77
+ def ancestors
78
+ ancestors = []
79
+ goal = self
80
+
81
+ while goal
82
+ ancestors << goal
83
+ goal = goal.calling_goal
84
+ end
85
+
86
+ ancestors.reverse
87
+ end
88
+
89
+ # A convenience method for testing/debugging.
90
+ # @return [String] the calling chain for this goal as a String.
91
+ def ancestry
92
+ indent = -1
93
+ ancestors.map do |goal|
94
+ indent += 1
95
+ "#{' ' * indent}#{goal.myid} -- #{goal.description} #{goal.variables.inspect}"
96
+ end.join("\n")
97
+ end
98
+
99
+ # A convenience method for testing/debugging.
100
+ # @return [String] the id of the goal.
101
+ def myid
102
+ "Goal#{(@@goals.index(self) || -1000) + 1}"
103
+ end
104
+
105
+ # @return [String] pretty representation.
106
+ def inspect
107
+ "#{myid} -- #{description}"
108
+ end
109
+
110
+ # A convenience method for testing/debugging.
111
+ # @return [Array<Porolog::Goal>]
112
+ def self.goals
113
+ @@goals
114
+ end
115
+
116
+ # Delete the Goal
117
+ # @return [Boolean] whether the Goal has been deleted (memoized)
118
+ def delete!
119
+ @@goals.delete(self)
120
+ deleted?
121
+ end
122
+
123
+ # @return [Boolean] whether the Goal has been deleted (memoized)
124
+ def deleted?
125
+ @deleted ||= check_deleted
126
+ @deleted
127
+ end
128
+
129
+ # Determines whether the Goal has been deleted and removes its Variables and Values if it has.
130
+ # @return [Boolean] whether the Goal has been deleted
131
+ def check_deleted
132
+ return false if @@goals.include?(self)
133
+
134
+ @variables.delete_if do |_name,variable|
135
+ variable.remove
136
+ true
137
+ end
138
+
139
+ @values.delete_if do |_name,value|
140
+ value.remove
141
+ true
142
+ end
143
+
144
+ true
145
+ end
146
+
147
+ # Sets that the goal should no longer search any further rules for the current predicate
148
+ # once the current rule has finished being evaluated.
149
+ # This method implements the :CUT rule.
150
+ # That is, backtracking does not go back past the point of the cut.
151
+ # @return [Boolean] true.
152
+ def terminate!
153
+ @log << 'terminating'
154
+ @terminate = true
155
+ end
156
+
157
+ # @return [Boolean] whether the goal has been cut.
158
+ def terminated?
159
+ @terminate
160
+ end
161
+
162
+ # Converts all embedded Symbols to Variables within this Goal.
163
+ # @param object [Object] the object to variablise.
164
+ # @return [Object] the variablised object.
165
+ def variablise(object)
166
+ case object
167
+ when Symbol,Variable
168
+ variable(object)
169
+ when Array
170
+ object.map{|element| variablise(element) }
171
+ when Arguments
172
+ object.dup(self)
173
+ when Tail
174
+ Tail.new variablise(object.value)
175
+ when Value
176
+ object
177
+ when NilClass
178
+ nil
179
+ else
180
+ if [UNKNOWN_TAIL, UNKNOWN_ARRAY].include?(object)
181
+ object
182
+ else
183
+ value(object)
184
+ end
185
+ end
186
+ end
187
+
188
+ alias [] variablise
189
+
190
+ # @return [Hash{Symbol => Object}] the Variables and their current values of this Goal
191
+ def variables
192
+ @variables.keys.each_with_object({}){|variable,variable_list|
193
+ value = value_of(variable).value.value
194
+ if value.is_a?(Variable)
195
+ variable_list[variable] = nil
196
+ elsif value.is_a?(Array)
197
+ variable_list[variable] = value.clean
198
+ else
199
+ variable_list[variable] = value
200
+ end
201
+ }
202
+ end
203
+
204
+ # A convenience method for testing/debugging.
205
+ # @return [String] a tree representation of all the instantiations of this goal's variables.
206
+ def inspect_variables
207
+ @variables.values.map(&:inspect_with_instantiations).join("\n")
208
+ end
209
+
210
+ # A convenience method for testing/debugging.
211
+ # @return [Array<Object>] the values instantiated for this goal.
212
+ def values
213
+ @values.values.map(&:value)
214
+ end
215
+
216
+ # Finds or tries to create a variable in the goal (as much as possible) otherwise passes the parameter back.
217
+ # @param name [Object] name of variable or variable
218
+ # @return [Porolog::Variable, Object] a variable of the goal or the original parameter
219
+ def variable(name)
220
+ return name unless name.is_a?(Symbol) || name.is_a?(Variable)
221
+
222
+ if name.is_a?(Variable)
223
+ variable = name
224
+ @variables[variable.name] = variable if variable.goal == self
225
+ return variable
226
+ end
227
+
228
+ @variables[name] ||= (name.is_a?(Variable) && name || Variable.new(name, self))
229
+ @variables[name]
230
+ end
231
+
232
+ # @param value [Object] the value that needs to be associated with this Goal.
233
+ # @return [Porolog::Value] a Value, ensuring it has been associated with this Goal so that it can be uninstantiated.
234
+ def value(value)
235
+ @values[value] ||= Value.new(value, self)
236
+ @values[value]
237
+ end
238
+
239
+ # Returns the value of the indicated variable or value
240
+ # @param name [Symbol, Object] name of the variable or value
241
+ # @param index [Object] index is not yet used
242
+ # @param visited [Array] used to avoid infinite recursion
243
+ # @return [Object] the value
244
+ def value_of(name, index = nil, visited = [])
245
+ variable(name).value(visited)
246
+ end
247
+
248
+ # Returns the values of an Object. For most objects, this will basically just be the object.
249
+ # For Arrays, an Array will be returned where the elements are the value of the elements.
250
+ # @param object [Object] the Object to find the values of.
251
+ # @return [Object,Array<Object>] the value(s) of the object.
252
+ def values_of(object)
253
+ case object
254
+ when Array
255
+ object.map{|element| values_of(element) }
256
+ when Variable,Symbol,Value
257
+ value_of(object)
258
+ when Tail
259
+ # TODO: This needs to splat outwards; in the meantime, it splats just the first.
260
+ value_of(object.value).first
261
+ else
262
+ object
263
+ end
264
+ end
265
+
266
+ # Finds all possible solutions to the Predicate for the specific Arguments.
267
+ # @param max_solutions [Integer] if provided, the search is stopped once the number of solutions has been found
268
+ # @return [Array<Hash{Symbol => Object}>]
269
+ def solve(max_solutions = nil)
270
+ return @solutions unless @arguments
271
+
272
+ predicate = @arguments.predicate
273
+
274
+ predicate&.satisfy(self) do |goal|
275
+ # TODO: Refactor to overrideable method (or another solution, say a lambda)
276
+
277
+ @solutions << variables
278
+ @log << "SOLUTION: #{variables}"
279
+ @log << goal.ancestry
280
+ @log << goal.inspect_variables
281
+
282
+ return @solutions if max_solutions && @solutions.size >= max_solutions
283
+ end
284
+
285
+ @solutions
286
+ end
287
+
288
+ # Solves the goal but not as the root goal for a query.
289
+ # That is, solves an intermediate goal.
290
+ # @param block [Proc] code to perform when the Goal is satisfied.
291
+ # @return [Boolean] whether the definition was satisfied.
292
+ def satisfy(&block)
293
+ return false unless @arguments
294
+
295
+ predicate = @arguments.predicate
296
+
297
+ satisfied = false
298
+ predicate&.satisfy(self) do |subgoal|
299
+ subgoal_satisfied = block.call(subgoal)
300
+ satisfied ||= subgoal_satisfied
301
+ end
302
+ satisfied
303
+ end
304
+
305
+ # Instantiates a Variable to another Variable or Value, for this Goal.
306
+ # @param name [Symbol,Porolog::Variable,Object] the name that references the variable to be instantiated.
307
+ # @param other [Object] the value that the variable is to be instantiated to.
308
+ # @param other_goal [Porolog::Goal,nil] the Goal of the other value.
309
+ # @return [Porolog::Instantiation] the instantiation.
310
+ def instantiate(name, other, other_goal = self)
311
+ variable = self.variable(name)
312
+
313
+ other = if other.type == :variable
314
+ other_goal.variable(other)
315
+ else
316
+ other_goal.value(other)
317
+ end
318
+
319
+ variable.instantiate(other)
320
+ end
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
+
355
+ end
356
+
357
+ end