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
@@ -0,0 +1,162 @@
1
+ #
2
+ # lib/porolog/rule.rb - Plain Old Ruby Objects Prolog Engine -- Rule
3
+ #
4
+ # Luis Esteban 2 May 2018
5
+ # created
6
+ #
7
+
8
+ module Porolog
9
+
10
+ # A Porolog::Rule is one clause of a Porolog::Predicate.
11
+ #
12
+ # @author Luis Esteban
13
+ #
14
+ # @!attribute [r] arguments
15
+ # The Arguments of the Predicate for which this Rule applies.
16
+ # @!attribute [r] definition
17
+ # The definition of the Rule.
18
+ #
19
+ class Rule
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
+
26
+ attr_reader :arguments, :definition
27
+
28
+ # Clears all Rules.
29
+ # @return [Boolean] success
30
+ def self.reset
31
+ @@rules = []
32
+ true
33
+ end
34
+
35
+ reset
36
+
37
+ # Initializes the Rule.
38
+ # @param arguments [Porolog::Arguments] the Arguments of the Predicate for which this Rule applies.
39
+ # @param definition [Object] the definition of the Rule.
40
+ def initialize(arguments, definition = nil)
41
+ @arguments = arguments
42
+ @definition = definition
43
+ @@rules << self
44
+ end
45
+
46
+ # Convenience method for testing/debugging
47
+ # @return [String] pretty identification
48
+ def myid
49
+ "Rule#{(@@rules.index(self) || -1000) + 1}"
50
+ end
51
+
52
+ # @return [String] pretty representation.
53
+ def inspect
54
+ " #{@arguments.inspect}:- #{@definition.inspect}"
55
+ end
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
+ goal.terminate!
126
+ goal.log << "TERMINATED after #{subgoal.inspect}"
127
+ end
128
+
129
+ return result
130
+
131
+ when false
132
+ return false
133
+
134
+ when nil
135
+ block.call(subgoal)
136
+ return true
137
+ end
138
+
139
+ # -- Unify Subsubgoal --
140
+ subsubgoal = arguments.goal(subgoal)
141
+ unified = subsubgoal.inherit_variables(subgoal)
142
+
143
+ # -- Satisfy Subgoal --
144
+ result = false
145
+ unified && subsubgoal.satisfy() do
146
+ result = true
147
+ if conjunction.empty?
148
+ block.call(goal)
149
+ else
150
+ result = satisfy_conjunction(goal, subsubgoal, conjunction, &block)
151
+ end
152
+ end
153
+
154
+ # -- Uninstantiate --
155
+ subsubgoal.delete!
156
+
157
+ result
158
+ end
159
+
160
+ end
161
+
162
+ end
@@ -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
 
@@ -73,8 +73,8 @@ module Porolog
73
73
  # @param predicate [Porolog::Predicate] a Predicate to be assigned to the Scope.
74
74
  # @return [Porolog::Predicate] the Predicate assigned to the Scope.
75
75
  # @raise [NotPredicateError] when provided predicate is not actually a Predicate.
76
- def []=(name,predicate)
77
- raise NotPredicateError.new("#{predicate.inspect} is not a Predicate") unless predicate.is_a?(Predicate)
76
+ def []=(name, 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,57 @@
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
+ # @return [Symbol] the type of the Tail, which should be :tail.
27
+ def type
28
+ :tail
29
+ end
30
+
31
+ # Returns the value of the Tail.
32
+ # The optional arguments are ignored; this is for polymorphic compatibility with Porolog::Value and Porolog::Variable,
33
+ # which are used to prevent inifinite recursion.
34
+ # @return [Object] the value of the Tail.
35
+ def value(*)
36
+ @value
37
+ end
38
+
39
+ # @return [String] pretty representation.
40
+ def inspect
41
+ "*#{@value.inspect}"
42
+ end
43
+
44
+ # @return [Array] embedded variables.
45
+ def variables
46
+ @value.variables
47
+ end
48
+
49
+ # @param other [Object, #value]
50
+ # @return [Boolean] whether the value of the Tail is equal to the value of another Object.
51
+ def ==(other)
52
+ @value == other.value
53
+ end
54
+
55
+ end
56
+
57
+ end
@@ -0,0 +1,105 @@
1
+ #
2
+ # lib/porolog/value.rb - Plain Old Ruby Objects Prolog Engine -- Value
3
+ #
4
+ # Luis Esteban 2 May 2018
5
+ # created
6
+ #
7
+
8
+ module Porolog
9
+
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.
12
+ #
13
+ # @author Luis Esteban
14
+ #
15
+ # @!attribute goal
16
+ # @return [Porolog::Goal] The goal in which the value was instantiated.
17
+ # @!attribute instantiations
18
+ # @return [Array<Porolog::Instantiation>] Instantiations of this value.
19
+ #
20
+ class Value
21
+
22
+ # Error class for rescuing or detecting any Value error.
23
+ class Error < PorologError ; end
24
+ # Error class indicating that the supplied goal is not actually a goal.
25
+ class GoalError < Error ; end
26
+
27
+ attr_accessor :goal, :instantiations
28
+
29
+ # @param value [Object] the value to be associated with a Goal.
30
+ # @param goal [Porolog::Goal] the Goal to be associated.
31
+ # @return [Porolog::Value] the Value.
32
+ def initialize(value, goal)
33
+ raise GoalError, "Not a Goal: #{goal.inspect}" unless goal.is_a?(Goal)
34
+
35
+ @value = value
36
+ @value = value.value if value.is_a?(Value)
37
+ @goal = goal
38
+
39
+ @instantiations = []
40
+ end
41
+
42
+ # Pretty presentation.
43
+ # @return [String] the inspect of the value prefixed by the goal id.
44
+ def inspect
45
+ "#{@goal.myid}.#{@value.inspect}"
46
+ end
47
+
48
+ # Pretty presentation with instantiations and indexes.
49
+ # This method is for polymorphic compatibility with Porolog::Variable.
50
+ # It used by Porolog::Goal#inspect_variables.
51
+ # @param visited [Array] the values already visited (to prevent infinite recursion).
52
+ # @param depth [Integer] the level of indentation that shows containment.
53
+ # @param index [Integer,Symbol,Array] the index into this value.
54
+ # @param self_index [Integer,Symbol,Array] the index of which this value belongs.
55
+ # @return [String] the inspect of the value in the context of the variables of a goal.
56
+ def inspect_with_instantiations(visited = [], depth = 0, index = nil, self_index = nil)
57
+ index_str = index && "[#{index.inspect}]" || ''
58
+ prefix = self_index&.inspect || ''
59
+
60
+ "#{' ' * depth}#{prefix}#{inspect}#{index_str}"
61
+ end
62
+
63
+ # Uninstantiate the Value.
64
+ # @return [Boolean] true
65
+ def remove
66
+ @instantiations.dup.each(&:remove)
67
+ @instantiations[0..-1] = []
68
+ true
69
+ end
70
+
71
+ # Passes on methods to the Value's value.
72
+ def method_missing(method, *args, &block)
73
+ @value.send(method, *args, &block)
74
+ end
75
+
76
+ # Responds to all the Value's value methods as well as its own.
77
+ # @return [Boolean] whether the value responds to the method.
78
+ def respond_to?(method, include_all = false)
79
+ @value.respond_to?(method, include_all) || super
80
+ end
81
+
82
+ # @return [Object] the value of the Value.
83
+ def value(*)
84
+ @value
85
+ end
86
+
87
+ # @return [Symbol] the type of the value.
88
+ def type
89
+ @value.type
90
+ end
91
+
92
+ # Compares Values for equality.
93
+ # @return [Boolean] whether the Values' values are equal.
94
+ def ==(other)
95
+ @value == other.value
96
+ end
97
+
98
+ # @return [Array<Porolog::Variable,Symbol>] variables embedded in the value.
99
+ def variables
100
+ @value.variables
101
+ end
102
+
103
+ end
104
+
105
+ end
@@ -0,0 +1,325 @@
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
+ module Porolog
9
+
10
+ # A Porolog::Variable is used to hold instantiations during the process of satisfying a goal.
11
+ # It implements a variable of a goal.
12
+ # It allows instantiations to be made and removed as the goal
13
+ # is attempted to be satisfied.
14
+ #
15
+ # @author Luis Esteban
16
+ #
17
+ # @!attribute name
18
+ # @return [Symbol] The name of the variable.
19
+ # @!attribute goal
20
+ # @return [Porolog::Goal] The Goal for which this Variable's instantiations are bound.
21
+ # @!attribute instantiations
22
+ # @return [Array<Porolog::Instantiation>] The Instantiations of this Variable.
23
+ # @!attribute values
24
+ # @return [Array<Porolog::Value>] the Values of the Variable when used as an anonymous Variable.
25
+ #
26
+ class Variable
27
+
28
+ # Error class for rescuing or detecting any Variable error.
29
+ class Error < PorologError ; end
30
+ # Error class indicating a Variable has been instantiated to multiple different values at the same time.
31
+ class MultipleValuesError < Error ; end
32
+ # Error class indicating a Variable has been created without a Goal.
33
+ class GoalError < Error ; end
34
+ # Error class indicating a Variable has been instantiated to a value that contains itself.
35
+ class SelfReferentialError < Error ; end
36
+ # Error class indicating an unexpected scenario has occurred.
37
+ class UnexpectedError < Error ; end
38
+
39
+ attr_accessor :name, :goal, :instantiations, :values
40
+
41
+ # Initializes the Variable and attaches it to the Goal.
42
+ # @param name [Object] the name used to refer to the Variable.
43
+ # @param goal [Porolog::Goal] the Goal the Variable is to be attached to.
44
+ def initialize(name, goal)
45
+ raise GoalError, "Not a Goal: #{goal.inspect}" unless goal.is_a?(Goal)
46
+ @goal = goal
47
+ name = name.to_sym if name.is_a?(String)
48
+
49
+ case name
50
+ when Symbol
51
+ @name = name
52
+ @values = []
53
+ when Variable
54
+ @name = name.name
55
+ @values = []
56
+ when Value
57
+ @name = name.value
58
+ @values = [name]
59
+ else
60
+ @name = name.to_s
61
+ @values = [Value.new(name, goal)]
62
+ end
63
+
64
+ @instantiations = []
65
+ @goal.variable(self)
66
+ end
67
+
68
+ # Converts a Variable back to a Symbol.
69
+ # @return [Symbol, nil] the name of the Variable.
70
+ def to_sym
71
+ @name&.to_sym
72
+ end
73
+
74
+ # @return [Symbol] the type of the Variable, which should be :variable.
75
+ def type
76
+ :variable
77
+ end
78
+
79
+ # @return [String] pretty representation.
80
+ def inspect
81
+ "#{@goal.myid}.#{@name.inspect}"
82
+ end
83
+
84
+ # @param visited [Array] used to prevent infinite recursion.
85
+ # @param depth [Integer] the current level of indentation.
86
+ # @param index [Object, nil] the index into this Variable that is instantiated.
87
+ # @param self_index [Object, nil] the index where this Variable is instantiated.
88
+ # @return [String] the inspect of the Variable showing instantiations using indentation.
89
+ def inspect_with_instantiations(visited = [], depth = 0, index = nil, self_index = nil)
90
+ return if visited.include?(self)
91
+
92
+ index_str = index && "[#{index.inspect}]" || ''
93
+ prefix = self_index && "[#{self_index.inspect}]" || ''
94
+ name = "#{' ' * depth}#{prefix}#{@goal.myid}.#{@name.inspect}#{index_str}"
95
+
96
+ name = "#{name} = #{@values.map(&:inspect).join(',')}#{index_str}" if @values && !@values.empty? && @instantiations.empty?
97
+
98
+ others = @instantiations.map{|instantiation|
99
+ [
100
+ instantiation.variable1.inspect_with_instantiations(visited + [self], depth + 1, instantiation.index1, instantiation.index2),
101
+ instantiation.variable2.inspect_with_instantiations(visited + [self], depth + 1, instantiation.index2, instantiation.index1),
102
+ ].compact
103
+ }.reject(&:empty?)
104
+
105
+ [
106
+ name,
107
+ *others,
108
+ ].join("\n")
109
+ end
110
+
111
+ # @return [Object,self] returns the current value of the Variable based on its current instantiations.
112
+ # If there are no concrete instantiations, it returns itself, indicating no value.
113
+ # @param visited [Array] prevents infinite recursion.
114
+ def value(visited = [])
115
+ return nil if visited.include?(self)
116
+ visited = visited + [self]
117
+
118
+ # -- Collect values --
119
+ values = [*@values]
120
+
121
+ @instantiations.each do |instantiation|
122
+ values += instantiation.values_for(self, visited)
123
+ end
124
+
125
+ values.uniq!
126
+
127
+ # -- Filter trivial values --
128
+ values = [[nil]] if values == [[UNKNOWN_TAIL], [nil]] || values == [[nil], [UNKNOWN_TAIL]]
129
+
130
+ values = values.reject{|value| value == UNKNOWN_TAIL }
131
+
132
+ values_values = values.map{|value| value.value(visited) }.reject{|value| value == UNKNOWN_ARRAY }
133
+
134
+ # -- Condense Values --
135
+ result = if values_values.size > 1
136
+ # -- Potentially Multiple Values Found --
137
+ unifications = []
138
+ if values_values.all?{|value| value.is_a?(Array) }
139
+ # -- All Values Are Arrays --
140
+ values = values.reject{|value| value == UNKNOWN_ARRAY } if values.size > 2 && values.include?(UNKNOWN_ARRAY)
141
+ values_goals = values.map{|value|
142
+ value.respond_to?(:goal) && value.goal || value.variables.map(&:goal).first || values.variables.map(&:goal).first
143
+ }
144
+
145
+ if values.size > 2
146
+ merged, unifications = unify_many_arrays(values, values_goals, visited)
147
+ elsif values.size == 2
148
+ no_variables = values.map(&:variables).flatten.empty?
149
+ if no_variables
150
+ left_value = values[0].value.value
151
+ right_value = values[1].value.value
152
+ if left_value.last == UNKNOWN_TAIL && right_value.first == UNKNOWN_TAIL
153
+ return [*left_value[0...-1], *right_value[1..-1]]
154
+ elsif right_value.last == UNKNOWN_TAIL && left_value.first == UNKNOWN_TAIL
155
+ return [*right_value[0...-1], *left_value[1..-1]]
156
+ elsif left_value != right_value
157
+ return nil
158
+ end
159
+ end
160
+ merged, unifications = unify_arrays(*values, *values_goals, visited)
161
+ else
162
+ # :nocov: NOT REACHED
163
+ merged, unifications = values.first, []
164
+ # :nocov:
165
+ end
166
+
167
+ merged.value(visited).to_a
168
+ else
169
+ # -- Not All Values Are Arrays --
170
+ values.each_cons(2){|left,right|
171
+ unification = unify(left, right, @goal, @goal, visited)
172
+ if unification && unifications
173
+ unifications += unification
174
+ else
175
+ unifications = nil
176
+ end
177
+ }
178
+ if unifications
179
+ values.min_by{|value|
180
+ case value
181
+ when Variable, Symbol then 2
182
+ when UNKNOWN_TAIL, UNKNOWN_ARRAY then 9
183
+ else 0
184
+ end
185
+ } || self
186
+ else
187
+ raise MultipleValuesError, "Multiple values detected for #{inspect}: #{values.inspect}"
188
+ end
189
+ end
190
+ else
191
+ # -- One (or None) Value Found --
192
+ values.min_by{|value|
193
+ case value
194
+ when Variable, Symbol then 2
195
+ when UNKNOWN_TAIL, UNKNOWN_ARRAY then 9
196
+ else 0
197
+ end
198
+ } || self
199
+ end
200
+
201
+ # -- Splat Tail --
202
+ if result.is_a?(Array) && result.size == 1 && result.first.is_a?(Tail)
203
+ result = result.first.value
204
+ end
205
+
206
+ result
207
+ end
208
+
209
+ # Instantiates Variable to another experssion.
210
+ # @param other [Object] the other value (or object) being instantiated.
211
+ # @param index_into_other [] the index into the other value.
212
+ # @param index_into_self [] the index into this Variable.
213
+ # @return [Porolog::Instantiation,nil] the instantiation made.
214
+ # @example
215
+ # # To instantiate the third element of x to the second element of y,
216
+ # # x = [a,b,c,d,e]
217
+ # # |
218
+ # # y = [p,q,r,s]
219
+ # x = goal.variable(:x)
220
+ # y = goal.variable(:y)
221
+ # x.instantiate(y, 1, 2)
222
+ # @example
223
+ # # To instantiate the tail of x to y,
224
+ # x.instantiate(y, nil, :tail)
225
+ def instantiate(other, index_into_other = nil, index_into_self = nil)
226
+ raise SelfReferentialError, "Cannot instantiate self-referential definition for #{(self.variables & other.variables).inspect}" unless (self.variables & other.variables).empty?
227
+
228
+ # -- Check Instantiation is Unifiable --
229
+ unless self.value.is_a?(Variable) || other.value.is_a?(Variable)
230
+ # -- Determine Other Goal --
231
+ other_goal = nil
232
+ other_goal = other.goal if other.respond_to?(:goal)
233
+ other_goal ||= self.goal
234
+
235
+ # -- Check Unification --
236
+ unless unify(self, other, self.goal, other_goal)
237
+ self.goal.log << "Cannot unify: #{self.inspect} and #{other.inspect}"
238
+ return nil
239
+ end
240
+ end
241
+
242
+ # -- Create Instantiation --
243
+ instantiation = Instantiation.new(self, index_into_self, other, index_into_other)
244
+
245
+ # -- Create Reverse Assymetric Instantiations --
246
+ if other.value.is_a?(Array) && other.value.last.is_a?(Tail)
247
+ array = other.value
248
+ if array.length == 2
249
+ if array.first.is_a?(Variable)
250
+ Instantiation.new(array.first, nil, self, :head)
251
+ end
252
+ if array.last.is_a?(Tail) && array.last.value.is_a?(Variable)
253
+ Instantiation.new(array.last.value, nil, self, :tail)
254
+ end
255
+ end
256
+ end
257
+
258
+ # -- Return --
259
+ instantiation
260
+ end
261
+
262
+ # Removes this Variable by removing all of its insantiations.
263
+ # @return [Boolean] success.
264
+ def remove
265
+ @instantiations.dup.each(&:remove)
266
+ @instantiations[0..-1] = []
267
+ true
268
+ end
269
+
270
+ # Removes instantiations from another goal.
271
+ # @param other_goal [Porolog::Goal] the Goal of which instantiations are to be removed.
272
+ # @return [Boolean] success.
273
+ def uninstantiate(other_goal)
274
+ @instantiations.delete_if do |instantiation|
275
+ instantiation.remove if instantiation.other_goal_to(self) == other_goal
276
+ end
277
+
278
+ @values.delete_if{|value| value.goal == other_goal }
279
+ true
280
+ end
281
+
282
+ # Indexes the value of the Variable.
283
+ # @param index [Object] the index into the value.
284
+ # @return [Object, nil] the value at the index in the value of the Variable.
285
+ def [](index)
286
+ value = self.value
287
+ value = value.value if value.is_a?(Value)
288
+
289
+ case value
290
+ when Array
291
+ case index
292
+ when Integer
293
+ value[index]
294
+ when Symbol
295
+ case index
296
+ when :head
297
+ value[0]
298
+ when :tail
299
+ value[1..-1]
300
+ else
301
+ nil
302
+ end
303
+ else
304
+ nil
305
+ end
306
+ else
307
+ nil
308
+ end
309
+ end
310
+
311
+ # @return [Array<Porolog::Variable>] the Variables in itself, which is just itself.
312
+ def variables
313
+ [self]
314
+ end
315
+
316
+ # Compares Variables.
317
+ # @param other [Porolog::Variable] the other Variable.
318
+ # @return [Boolean] whether the two Variables have the same name in the same Goal.
319
+ def ==(other)
320
+ other.is_a?(Variable) && @name == other.name && @goal == other.goal
321
+ end
322
+
323
+ end
324
+
325
+ end