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