Class: Porolog::Instantiation

Inherits:
Object
  • Object
show all
Defined in:
lib/porolog/instantiation.rb

Overview

A Porolog::Instantiation implements an instantiation of a Variable of a Goal. An Instantiation joins two expressions. At least one expression must have a variable. An index may be provided to index into the value of either expression, or both may have an index. Thus, an element of each list could be instantiated without reference to the other elements:

x = [a,b,c,d,e]
       |
y =   [p,q,r,s]

Author:

  • Luis Esteban

Defined Under Namespace

Classes: Error, NoVariableError, UnhandledIndexError

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(variable1, index1, variable2, index2) ⇒ Porolog::Instantiation

Initializes and registers a new Instantiation. At least one of the variables must be a Variable.

Parameters:



80
81
82
83
84
85
86
87
88
89
90
# File 'lib/porolog/instantiation.rb', line 80

def initialize(variable1, index1, variable2, index2)
  @variable1 = variable1
  @variable2 = variable2
  @index1    = index1
  @index2    = index2
  
  @variable1.instantiations << self
  @variable2.instantiations << self
  
  @@instantiations[signature] = self
end

Instance Attribute Details

#index1 ⇒ Object?

Returns the index into the value of variable1.

Returns:

  • (Object, nil) —

    the index into the value of variable1.



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
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
# File 'lib/porolog/instantiation.rb', line 29

class Instantiation
  
  # Error class for rescuing or detecting any Instantiation error.
  class Error               < PorologError ; end
  # Error class indicating that an instantiation was created without any Variables.
  class NoVariableError     < Error        ; end
  # Error class indicating that an index could not be used although requested.
  class UnhandledIndexError < Error        ; end
  
  attr_accessor :variable1, :variable2, :index1, :index2
  
  # Clears all instantiations.
  # @return [Boolean] success
  def self.reset
    @@instantiations = {}
    true
  end
  
  reset
  
  # @return [Hash{Array => Porolog::Instantiation}] all Instantiations
  def self.instantiations
    @@instantiations
  end
  
  # Finds or creates a new instantiation.  At least one of the variables must be a Variable.
  # @param variable1 [Porolog::Variable,Porolog::Value,Object] one end of the instantiation to be made.
  # @param variable2 [Porolog::Variable,Porolog::Value,Object] the other end of the instantiation to be made.
  # @param index1 [Integer,Symbol,nil] index into the value of variable1.
  # @param index2 [Integer,Symbol,nil] index into the value of variable2.
  # @return [Porolog::Instantiation] the found or created Instantiation.
  def self.new(variable1, index1, variable2, index2)
    raise NoVariableError, "Cannot instantiate non-variables: #{variable1.inspect} and #{variable2.inspect}" unless variable1.is_a?(Variable) || variable2.is_a?(Variable)
    
    variable1 = Value.new variable1, variable2.goal unless variable1.is_a?(Variable) || variable1.is_a?(Value)
    variable2 = Value.new variable2, variable1.goal unless variable2.is_a?(Variable) || variable2.is_a?(Value)
    
    instantiation = \
      @@instantiations[[variable1, index1, variable2, index2]] ||
      @@instantiations[[variable2, index2, variable1, index1]] ||
      super
    
    instantiation
  end
  
  # Initializes and registers a new Instantiation.  At least one of the variables must be a Variable.
  # @param variable1 [Porolog::Variable,Porolog::Value,Object] one end of the instantiation to be made.
  # @param variable2 [Porolog::Variable,Porolog::Value,Object] the other end of the instantiation to be made.
  # @param index1 [Integer,Symbol,nil] index into the value of variable1.
  # @param index2 [Integer,Symbol,nil] index into the value of variable2.
  # @return [Porolog::Instantiation] the found or created Instantiation.
  def initialize(variable1, index1, variable2, index2)
    @variable1 = variable1
    @variable2 = variable2
    @index1    = index1
    @index2    = index2
    
    @variable1.instantiations << self
    @variable2.instantiations << self
    
    @@instantiations[signature] = self
  end
  
  # @return [Array] the signature of the Instantiation, which aids in avoiding duplication instantiations.
  def signature
    @signature ||= [@variable1, @index1, @variable2, @index2].freeze
    @signature
  end
  
  # @return [String] pretty representation.
  def inspect(indent = 0)
    index1 = @index1 ? "[#{@index1.inspect}]" : ''
    index2 = @index2 ? "[#{@index2.inspect}]" : ''
    
    "#{'  ' * indent}#{@variable1.inspect}#{index1} = #{@variable2.inspect}#{index2}"
  end
  
  # Removes itself from its variables' Instantiations and unregisters itself.
  # @return [Boolean] success
  def remove
    @variable1.instantiations.delete(self)  if @variable1.is_a?(Variable) || @variable1.is_a?(Value)
    @variable2.instantiations.delete(self)  if @variable2.is_a?(Variable) || @variable2.is_a?(Value)
    
    @deleted = true
    @@instantiations.delete(signature)
    @deleted
  end
  
  # Returns the Goal of the other Variable to the one provided.
  # @param variable [Porolog::Variable] the provided Variable.
  # @return [Porolog::Goal,nil] the Goal of the other Variable.
  def other_goal_to(variable)
    return nil unless variables.include?(variable)
    
    other_variable = (variables - [variable]).first
    other_variable&.goal
  end
  
  # @return [Array<Porolog::Goal>] the Goals of the Variables of the Instantiation.
  def goals
    [@variable1.goal,@variable2.goal]
  end
  
  # @return [Array<Porolog::Variable,Object>] the Variables of the Instantiation.
  def variables
    [@variable1,@variable2]
  end
  
  # @param visited [Array] prevents infinite recursion.
  # @return [Array] the values of the Variables of the Instantiation.
  def values(visited = [])
    return [] if visited.include?(self)
    
    values_for_variable1 = values_for(@variable1, visited)
    values_for_variable2 = values_for(@variable2, visited)
    
    (values_for_variable1 + values_for_variable2).uniq
  end
  
  # @param value [Object] the provided value.
  # @param index [Integer,Symbol,Array<Integer>] the index.
  # @param visited [Array] prevents infinite recursion.
  # @return [Object] the indexed value of the provided value.
  def value_indexed(value, index, visited = [])
    return nil unless value
    return nil if value.type == :variable
    return nil if value == [] && index == :tail
    
    visit = [value, index]
    return nil if visited.include?(visit)
    visited = visited + [visit]
    
    case index
      when Integer
        if value.respond_to?(:last) && value.last == UNKNOWN_TAIL
          if index >= value.length - 1
            UNKNOWN_TAIL
          else
            value[index]
          end
        elsif value.is_a?(Numeric)
          value
        else
          value[index]
        end
      
      when Symbol
        value_value = value.value(visited)
        if index == :flathead
          # value_value = [..., 4, 5, 6]
          if value_value.first == UNKNOWN_TAIL
            UNKNOWN_ARRAY
          else
            nil
          end
        elsif index == :flattail
          # value_value = [1, 2, 3, ...]
          if value_value.first == UNKNOWN_TAIL
            nil
          elsif value_value.last == UNKNOWN_TAIL
            UNKNOWN_ARRAY
          end
        elsif value_value.respond_to?(index)
          value_value.send(index)
        else
          value
        end
      
      when Array
        if index.empty?
          value[1..-1]
        else
          value[0...index.first]
        end
      
      else
        if index
          raise UnhandledIndexError, "Unhandled index: #{index.inspect} of #{value.inspect}"
        else
          value
        end
    end
  end
  
  # @param variable [Porolog::Variable,Porolog::Value] the specified variable (or end of the Instantiation).
  # @param visited [Array] prevents infinite recursion.
  # @return [Array<Object>] the values for the specified variable by finding the values of the other variable (i.e. the other end) and applying any indexes.
  def values_for(variable, visited = [])
    return [] if visited.include?(self)
    visited = visited + [self]
    
    if variable == @variable1
      if @index1 == :flathead
        flathead = value_at_index(value_indexed(@variable2.value(visited), @index2, visited), @index1).value.value
        if flathead
          return [[*flathead]]
        else
          return []
        end
      end
      if @index1 == :flattail
        flattail = value_at_index(value_indexed(@variable2.value(visited), @index2, visited), @index1).value.value
        if flattail
          return [[UNKNOWN_TAIL, *flattail]]
        else
          return []
        end
      end
      if @index1
        [value_at_index(value_indexed(@variable2.value(visited), @index2, visited), @index1)]
      else
        if @variable2.is_a?(Variable)
          [value_indexed(@variable2.value(visited), @index2, visited)]
        else
          [value_indexed(@variable2, @index2, visited)]
        end
      end
    elsif variable == @variable2
      if @index2 == :flathead
        flathead = value_at_index(value_indexed(@variable1.value(visited), @index1, visited), @index2).value.value
        if flathead
          return [[*flathead]]
        else
          return []
        end
      end
      if @index2 == :flattail
        flattail = value_at_index(value_indexed(@variable1.value(visited), @index1, visited), @index2).value.value
        if flattail
          return [[UNKNOWN_TAIL, *flattail]]
        else
          return []
        end
      end
      if @index2
        [value_at_index(value_indexed(@variable1.value(visited), @index1, visited), @index2)]
      else
        if @variable1.is_a?(Variable)
          [value_indexed(@variable1.value(visited), @index1, visited)]
        else
          [value_indexed(@variable1, @index1, visited)]
        end
      end
    else
      []
    end.compact
  end
  
  # @param value [Object] the known value.
  # @param index [Integer,Symbol,Array] the known index.
  # @return [Array] an Array where the known value is at the known index.
  def value_at_index(value, index)
    value && case index
      when Integer
        result        = []
        result[index] = value
        result       << UNKNOWN_TAIL
        result
      
      when Symbol
        case index
          when :flathead
            [*value, UNKNOWN_TAIL]
          when :head
            [value, UNKNOWN_TAIL]
          when :tail
            [nil, *value]
          when :flattail
            value
          else
            raise UnhandledIndexError, "Unhandled index: #{index.inspect} for #{value.inspect}"
        end
      
      when Array
        if index.empty?
          [nil, *value]
        else
          [value, UNKNOWN_TAIL]
        end
      
      else
        if index
          raise UnhandledIndexError, "Unhandled index: #{index.inspect} for #{value.inspect}"
        else
          value
        end
    end
  end
  
  # @param variable [Porolog::Variable,Porolog::Value] the specified variable.
  # @return [Boolean] whether the specified variable is not indexed.
  def without_index_on?(variable)
    [
      [@variable1,@index1],
      [@variable2,@index2],
    ].any?{|pair|
      pair.first == variable && pair.last.nil?
    }
  end
  
  # @return [Boolean] whether the Instantiation has been deleted (memoized).
  def deleted?
    @deleted ||= @variable1.goal.deleted? || @variable2.goal.deleted?
    @deleted
  end
  
  # @param goal [Porolog::Goal] the provided Goal.
  # @return [Boolean] whether the Instantiation attaches to a variable in the provided Goal.
  def belongs_to?(goal)
    [
      @variable1.goal,
      @variable2.goal,
    ].include?(goal)
  end
  
end

#index2 ⇒ Object?

Returns the index into the value of variable2.

Returns:

  • (Object, nil) —

    the index into the value of variable2.



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
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
# File 'lib/porolog/instantiation.rb', line 29

class Instantiation
  
  # Error class for rescuing or detecting any Instantiation error.
  class Error               < PorologError ; end
  # Error class indicating that an instantiation was created without any Variables.
  class NoVariableError     < Error        ; end
  # Error class indicating that an index could not be used although requested.
  class UnhandledIndexError < Error        ; end
  
  attr_accessor :variable1, :variable2, :index1, :index2
  
  # Clears all instantiations.
  # @return [Boolean] success
  def self.reset
    @@instantiations = {}
    true
  end
  
  reset
  
  # @return [Hash{Array => Porolog::Instantiation}] all Instantiations
  def self.instantiations
    @@instantiations
  end
  
  # Finds or creates a new instantiation.  At least one of the variables must be a Variable.
  # @param variable1 [Porolog::Variable,Porolog::Value,Object] one end of the instantiation to be made.
  # @param variable2 [Porolog::Variable,Porolog::Value,Object] the other end of the instantiation to be made.
  # @param index1 [Integer,Symbol,nil] index into the value of variable1.
  # @param index2 [Integer,Symbol,nil] index into the value of variable2.
  # @return [Porolog::Instantiation] the found or created Instantiation.
  def self.new(variable1, index1, variable2, index2)
    raise NoVariableError, "Cannot instantiate non-variables: #{variable1.inspect} and #{variable2.inspect}" unless variable1.is_a?(Variable) || variable2.is_a?(Variable)
    
    variable1 = Value.new variable1, variable2.goal unless variable1.is_a?(Variable) || variable1.is_a?(Value)
    variable2 = Value.new variable2, variable1.goal unless variable2.is_a?(Variable) || variable2.is_a?(Value)
    
    instantiation = \
      @@instantiations[[variable1, index1, variable2, index2]] ||
      @@instantiations[[variable2, index2, variable1, index1]] ||
      super
    
    instantiation
  end
  
  # Initializes and registers a new Instantiation.  At least one of the variables must be a Variable.
  # @param variable1 [Porolog::Variable,Porolog::Value,Object] one end of the instantiation to be made.
  # @param variable2 [Porolog::Variable,Porolog::Value,Object] the other end of the instantiation to be made.
  # @param index1 [Integer,Symbol,nil] index into the value of variable1.
  # @param index2 [Integer,Symbol,nil] index into the value of variable2.
  # @return [Porolog::Instantiation] the found or created Instantiation.
  def initialize(variable1, index1, variable2, index2)
    @variable1 = variable1
    @variable2 = variable2
    @index1    = index1
    @index2    = index2
    
    @variable1.instantiations << self
    @variable2.instantiations << self
    
    @@instantiations[signature] = self
  end
  
  # @return [Array] the signature of the Instantiation, which aids in avoiding duplication instantiations.
  def signature
    @signature ||= [@variable1, @index1, @variable2, @index2].freeze
    @signature
  end
  
  # @return [String] pretty representation.
  def inspect(indent = 0)
    index1 = @index1 ? "[#{@index1.inspect}]" : ''
    index2 = @index2 ? "[#{@index2.inspect}]" : ''
    
    "#{'  ' * indent}#{@variable1.inspect}#{index1} = #{@variable2.inspect}#{index2}"
  end
  
  # Removes itself from its variables' Instantiations and unregisters itself.
  # @return [Boolean] success
  def remove
    @variable1.instantiations.delete(self)  if @variable1.is_a?(Variable) || @variable1.is_a?(Value)
    @variable2.instantiations.delete(self)  if @variable2.is_a?(Variable) || @variable2.is_a?(Value)
    
    @deleted = true
    @@instantiations.delete(signature)
    @deleted
  end
  
  # Returns the Goal of the other Variable to the one provided.
  # @param variable [Porolog::Variable] the provided Variable.
  # @return [Porolog::Goal,nil] the Goal of the other Variable.
  def other_goal_to(variable)
    return nil unless variables.include?(variable)
    
    other_variable = (variables - [variable]).first
    other_variable&.goal
  end
  
  # @return [Array<Porolog::Goal>] the Goals of the Variables of the Instantiation.
  def goals
    [@variable1.goal,@variable2.goal]
  end
  
  # @return [Array<Porolog::Variable,Object>] the Variables of the Instantiation.
  def variables
    [@variable1,@variable2]
  end
  
  # @param visited [Array] prevents infinite recursion.
  # @return [Array] the values of the Variables of the Instantiation.
  def values(visited = [])
    return [] if visited.include?(self)
    
    values_for_variable1 = values_for(@variable1, visited)
    values_for_variable2 = values_for(@variable2, visited)
    
    (values_for_variable1 + values_for_variable2).uniq
  end
  
  # @param value [Object] the provided value.
  # @param index [Integer,Symbol,Array<Integer>] the index.
  # @param visited [Array] prevents infinite recursion.
  # @return [Object] the indexed value of the provided value.
  def value_indexed(value, index, visited = [])
    return nil unless value
    return nil if value.type == :variable
    return nil if value == [] && index == :tail
    
    visit = [value, index]
    return nil if visited.include?(visit)
    visited = visited + [visit]
    
    case index
      when Integer
        if value.respond_to?(:last) && value.last == UNKNOWN_TAIL
          if index >= value.length - 1
            UNKNOWN_TAIL
          else
            value[index]
          end
        elsif value.is_a?(Numeric)
          value
        else
          value[index]
        end
      
      when Symbol
        value_value = value.value(visited)
        if index == :flathead
          # value_value = [..., 4, 5, 6]
          if value_value.first == UNKNOWN_TAIL
            UNKNOWN_ARRAY
          else
            nil
          end
        elsif index == :flattail
          # value_value = [1, 2, 3, ...]
          if value_value.first == UNKNOWN_TAIL
            nil
          elsif value_value.last == UNKNOWN_TAIL
            UNKNOWN_ARRAY
          end
        elsif value_value.respond_to?(index)
          value_value.send(index)
        else
          value
        end
      
      when Array
        if index.empty?
          value[1..-1]
        else
          value[0...index.first]
        end
      
      else
        if index
          raise UnhandledIndexError, "Unhandled index: #{index.inspect} of #{value.inspect}"
        else
          value
        end
    end
  end
  
  # @param variable [Porolog::Variable,Porolog::Value] the specified variable (or end of the Instantiation).
  # @param visited [Array] prevents infinite recursion.
  # @return [Array<Object>] the values for the specified variable by finding the values of the other variable (i.e. the other end) and applying any indexes.
  def values_for(variable, visited = [])
    return [] if visited.include?(self)
    visited = visited + [self]
    
    if variable == @variable1
      if @index1 == :flathead
        flathead = value_at_index(value_indexed(@variable2.value(visited), @index2, visited), @index1).value.value
        if flathead
          return [[*flathead]]
        else
          return []
        end
      end
      if @index1 == :flattail
        flattail = value_at_index(value_indexed(@variable2.value(visited), @index2, visited), @index1).value.value
        if flattail
          return [[UNKNOWN_TAIL, *flattail]]
        else
          return []
        end
      end
      if @index1
        [value_at_index(value_indexed(@variable2.value(visited), @index2, visited), @index1)]
      else
        if @variable2.is_a?(Variable)
          [value_indexed(@variable2.value(visited), @index2, visited)]
        else
          [value_indexed(@variable2, @index2, visited)]
        end
      end
    elsif variable == @variable2
      if @index2 == :flathead
        flathead = value_at_index(value_indexed(@variable1.value(visited), @index1, visited), @index2).value.value
        if flathead
          return [[*flathead]]
        else
          return []
        end
      end
      if @index2 == :flattail
        flattail = value_at_index(value_indexed(@variable1.value(visited), @index1, visited), @index2).value.value
        if flattail
          return [[UNKNOWN_TAIL, *flattail]]
        else
          return []
        end
      end
      if @index2
        [value_at_index(value_indexed(@variable1.value(visited), @index1, visited), @index2)]
      else
        if @variable1.is_a?(Variable)
          [value_indexed(@variable1.value(visited), @index1, visited)]
        else
          [value_indexed(@variable1, @index1, visited)]
        end
      end
    else
      []
    end.compact
  end
  
  # @param value [Object] the known value.
  # @param index [Integer,Symbol,Array] the known index.
  # @return [Array] an Array where the known value is at the known index.
  def value_at_index(value, index)
    value && case index
      when Integer
        result        = []
        result[index] = value
        result       << UNKNOWN_TAIL
        result
      
      when Symbol
        case index
          when :flathead
            [*value, UNKNOWN_TAIL]
          when :head
            [value, UNKNOWN_TAIL]
          when :tail
            [nil, *value]
          when :flattail
            value
          else
            raise UnhandledIndexError, "Unhandled index: #{index.inspect} for #{value.inspect}"
        end
      
      when Array
        if index.empty?
          [nil, *value]
        else
          [value, UNKNOWN_TAIL]
        end
      
      else
        if index
          raise UnhandledIndexError, "Unhandled index: #{index.inspect} for #{value.inspect}"
        else
          value
        end
    end
  end
  
  # @param variable [Porolog::Variable,Porolog::Value] the specified variable.
  # @return [Boolean] whether the specified variable is not indexed.
  def without_index_on?(variable)
    [
      [@variable1,@index1],
      [@variable2,@index2],
    ].any?{|pair|
      pair.first == variable && pair.last.nil?
    }
  end
  
  # @return [Boolean] whether the Instantiation has been deleted (memoized).
  def deleted?
    @deleted ||= @variable1.goal.deleted? || @variable2.goal.deleted?
    @deleted
  end
  
  # @param goal [Porolog::Goal] the provided Goal.
  # @return [Boolean] whether the Instantiation attaches to a variable in the provided Goal.
  def belongs_to?(goal)
    [
      @variable1.goal,
      @variable2.goal,
    ].include?(goal)
  end
  
end

#variable1 ⇒ Variable, Object

Returns one end of the instantiation.

Returns:



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
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
# File 'lib/porolog/instantiation.rb', line 29

class Instantiation
  
  # Error class for rescuing or detecting any Instantiation error.
  class Error               < PorologError ; end
  # Error class indicating that an instantiation was created without any Variables.
  class NoVariableError     < Error        ; end
  # Error class indicating that an index could not be used although requested.
  class UnhandledIndexError < Error        ; end
  
  attr_accessor :variable1, :variable2, :index1, :index2
  
  # Clears all instantiations.
  # @return [Boolean] success
  def self.reset
    @@instantiations = {}
    true
  end
  
  reset
  
  # @return [Hash{Array => Porolog::Instantiation}] all Instantiations
  def self.instantiations
    @@instantiations
  end
  
  # Finds or creates a new instantiation.  At least one of the variables must be a Variable.
  # @param variable1 [Porolog::Variable,Porolog::Value,Object] one end of the instantiation to be made.
  # @param variable2 [Porolog::Variable,Porolog::Value,Object] the other end of the instantiation to be made.
  # @param index1 [Integer,Symbol,nil] index into the value of variable1.
  # @param index2 [Integer,Symbol,nil] index into the value of variable2.
  # @return [Porolog::Instantiation] the found or created Instantiation.
  def self.new(variable1, index1, variable2, index2)
    raise NoVariableError, "Cannot instantiate non-variables: #{variable1.inspect} and #{variable2.inspect}" unless variable1.is_a?(Variable) || variable2.is_a?(Variable)
    
    variable1 = Value.new variable1, variable2.goal unless variable1.is_a?(Variable) || variable1.is_a?(Value)
    variable2 = Value.new variable2, variable1.goal unless variable2.is_a?(Variable) || variable2.is_a?(Value)
    
    instantiation = \
      @@instantiations[[variable1, index1, variable2, index2]] ||
      @@instantiations[[variable2, index2, variable1, index1]] ||
      super
    
    instantiation
  end
  
  # Initializes and registers a new Instantiation.  At least one of the variables must be a Variable.
  # @param variable1 [Porolog::Variable,Porolog::Value,Object] one end of the instantiation to be made.
  # @param variable2 [Porolog::Variable,Porolog::Value,Object] the other end of the instantiation to be made.
  # @param index1 [Integer,Symbol,nil] index into the value of variable1.
  # @param index2 [Integer,Symbol,nil] index into the value of variable2.
  # @return [Porolog::Instantiation] the found or created Instantiation.
  def initialize(variable1, index1, variable2, index2)
    @variable1 = variable1
    @variable2 = variable2
    @index1    = index1
    @index2    = index2
    
    @variable1.instantiations << self
    @variable2.instantiations << self
    
    @@instantiations[signature] = self
  end
  
  # @return [Array] the signature of the Instantiation, which aids in avoiding duplication instantiations.
  def signature
    @signature ||= [@variable1, @index1, @variable2, @index2].freeze
    @signature
  end
  
  # @return [String] pretty representation.
  def inspect(indent = 0)
    index1 = @index1 ? "[#{@index1.inspect}]" : ''
    index2 = @index2 ? "[#{@index2.inspect}]" : ''
    
    "#{'  ' * indent}#{@variable1.inspect}#{index1} = #{@variable2.inspect}#{index2}"
  end
  
  # Removes itself from its variables' Instantiations and unregisters itself.
  # @return [Boolean] success
  def remove
    @variable1.instantiations.delete(self)  if @variable1.is_a?(Variable) || @variable1.is_a?(Value)
    @variable2.instantiations.delete(self)  if @variable2.is_a?(Variable) || @variable2.is_a?(Value)
    
    @deleted = true
    @@instantiations.delete(signature)
    @deleted
  end
  
  # Returns the Goal of the other Variable to the one provided.
  # @param variable [Porolog::Variable] the provided Variable.
  # @return [Porolog::Goal,nil] the Goal of the other Variable.
  def other_goal_to(variable)
    return nil unless variables.include?(variable)
    
    other_variable = (variables - [variable]).first
    other_variable&.goal
  end
  
  # @return [Array<Porolog::Goal>] the Goals of the Variables of the Instantiation.
  def goals
    [@variable1.goal,@variable2.goal]
  end
  
  # @return [Array<Porolog::Variable,Object>] the Variables of the Instantiation.
  def variables
    [@variable1,@variable2]
  end
  
  # @param visited [Array] prevents infinite recursion.
  # @return [Array] the values of the Variables of the Instantiation.
  def values(visited = [])
    return [] if visited.include?(self)
    
    values_for_variable1 = values_for(@variable1, visited)
    values_for_variable2 = values_for(@variable2, visited)
    
    (values_for_variable1 + values_for_variable2).uniq
  end
  
  # @param value [Object] the provided value.
  # @param index [Integer,Symbol,Array<Integer>] the index.
  # @param visited [Array] prevents infinite recursion.
  # @return [Object] the indexed value of the provided value.
  def value_indexed(value, index, visited = [])
    return nil unless value
    return nil if value.type == :variable
    return nil if value == [] && index == :tail
    
    visit = [value, index]
    return nil if visited.include?(visit)
    visited = visited + [visit]
    
    case index
      when Integer
        if value.respond_to?(:last) && value.last == UNKNOWN_TAIL
          if index >= value.length - 1
            UNKNOWN_TAIL
          else
            value[index]
          end
        elsif value.is_a?(Numeric)
          value
        else
          value[index]
        end
      
      when Symbol
        value_value = value.value(visited)
        if index == :flathead
          # value_value = [..., 4, 5, 6]
          if value_value.first == UNKNOWN_TAIL
            UNKNOWN_ARRAY
          else
            nil
          end
        elsif index == :flattail
          # value_value = [1, 2, 3, ...]
          if value_value.first == UNKNOWN_TAIL
            nil
          elsif value_value.last == UNKNOWN_TAIL
            UNKNOWN_ARRAY
          end
        elsif value_value.respond_to?(index)
          value_value.send(index)
        else
          value
        end
      
      when Array
        if index.empty?
          value[1..-1]
        else
          value[0...index.first]
        end
      
      else
        if index
          raise UnhandledIndexError, "Unhandled index: #{index.inspect} of #{value.inspect}"
        else
          value
        end
    end
  end
  
  # @param variable [Porolog::Variable,Porolog::Value] the specified variable (or end of the Instantiation).
  # @param visited [Array] prevents infinite recursion.
  # @return [Array<Object>] the values for the specified variable by finding the values of the other variable (i.e. the other end) and applying any indexes.
  def values_for(variable, visited = [])
    return [] if visited.include?(self)
    visited = visited + [self]
    
    if variable == @variable1
      if @index1 == :flathead
        flathead = value_at_index(value_indexed(@variable2.value(visited), @index2, visited), @index1).value.value
        if flathead
          return [[*flathead]]
        else
          return []
        end
      end
      if @index1 == :flattail
        flattail = value_at_index(value_indexed(@variable2.value(visited), @index2, visited), @index1).value.value
        if flattail
          return [[UNKNOWN_TAIL, *flattail]]
        else
          return []
        end
      end
      if @index1
        [value_at_index(value_indexed(@variable2.value(visited), @index2, visited), @index1)]
      else
        if @variable2.is_a?(Variable)
          [value_indexed(@variable2.value(visited), @index2, visited)]
        else
          [value_indexed(@variable2, @index2, visited)]
        end
      end
    elsif variable == @variable2
      if @index2 == :flathead
        flathead = value_at_index(value_indexed(@variable1.value(visited), @index1, visited), @index2).value.value
        if flathead
          return [[*flathead]]
        else
          return []
        end
      end
      if @index2 == :flattail
        flattail = value_at_index(value_indexed(@variable1.value(visited), @index1, visited), @index2).value.value
        if flattail
          return [[UNKNOWN_TAIL, *flattail]]
        else
          return []
        end
      end
      if @index2
        [value_at_index(value_indexed(@variable1.value(visited), @index1, visited), @index2)]
      else
        if @variable1.is_a?(Variable)
          [value_indexed(@variable1.value(visited), @index1, visited)]
        else
          [value_indexed(@variable1, @index1, visited)]
        end
      end
    else
      []
    end.compact
  end
  
  # @param value [Object] the known value.
  # @param index [Integer,Symbol,Array] the known index.
  # @return [Array] an Array where the known value is at the known index.
  def value_at_index(value, index)
    value && case index
      when Integer
        result        = []
        result[index] = value
        result       << UNKNOWN_TAIL
        result
      
      when Symbol
        case index
          when :flathead
            [*value, UNKNOWN_TAIL]
          when :head
            [value, UNKNOWN_TAIL]
          when :tail
            [nil, *value]
          when :flattail
            value
          else
            raise UnhandledIndexError, "Unhandled index: #{index.inspect} for #{value.inspect}"
        end
      
      when Array
        if index.empty?
          [nil, *value]
        else
          [value, UNKNOWN_TAIL]
        end
      
      else
        if index
          raise UnhandledIndexError, "Unhandled index: #{index.inspect} for #{value.inspect}"
        else
          value
        end
    end
  end
  
  # @param variable [Porolog::Variable,Porolog::Value] the specified variable.
  # @return [Boolean] whether the specified variable is not indexed.
  def without_index_on?(variable)
    [
      [@variable1,@index1],
      [@variable2,@index2],
    ].any?{|pair|
      pair.first == variable && pair.last.nil?
    }
  end
  
  # @return [Boolean] whether the Instantiation has been deleted (memoized).
  def deleted?
    @deleted ||= @variable1.goal.deleted? || @variable2.goal.deleted?
    @deleted
  end
  
  # @param goal [Porolog::Goal] the provided Goal.
  # @return [Boolean] whether the Instantiation attaches to a variable in the provided Goal.
  def belongs_to?(goal)
    [
      @variable1.goal,
      @variable2.goal,
    ].include?(goal)
  end
  
end

#variable2 ⇒ Variable, Object

Returns the other end of the instantiation.

Returns:



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
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
# File 'lib/porolog/instantiation.rb', line 29

class Instantiation
  
  # Error class for rescuing or detecting any Instantiation error.
  class Error               < PorologError ; end
  # Error class indicating that an instantiation was created without any Variables.
  class NoVariableError     < Error        ; end
  # Error class indicating that an index could not be used although requested.
  class UnhandledIndexError < Error        ; end
  
  attr_accessor :variable1, :variable2, :index1, :index2
  
  # Clears all instantiations.
  # @return [Boolean] success
  def self.reset
    @@instantiations = {}
    true
  end
  
  reset
  
  # @return [Hash{Array => Porolog::Instantiation}] all Instantiations
  def self.instantiations
    @@instantiations
  end
  
  # Finds or creates a new instantiation.  At least one of the variables must be a Variable.
  # @param variable1 [Porolog::Variable,Porolog::Value,Object] one end of the instantiation to be made.
  # @param variable2 [Porolog::Variable,Porolog::Value,Object] the other end of the instantiation to be made.
  # @param index1 [Integer,Symbol,nil] index into the value of variable1.
  # @param index2 [Integer,Symbol,nil] index into the value of variable2.
  # @return [Porolog::Instantiation] the found or created Instantiation.
  def self.new(variable1, index1, variable2, index2)
    raise NoVariableError, "Cannot instantiate non-variables: #{variable1.inspect} and #{variable2.inspect}" unless variable1.is_a?(Variable) || variable2.is_a?(Variable)
    
    variable1 = Value.new variable1, variable2.goal unless variable1.is_a?(Variable) || variable1.is_a?(Value)
    variable2 = Value.new variable2, variable1.goal unless variable2.is_a?(Variable) || variable2.is_a?(Value)
    
    instantiation = \
      @@instantiations[[variable1, index1, variable2, index2]] ||
      @@instantiations[[variable2, index2, variable1, index1]] ||
      super
    
    instantiation
  end
  
  # Initializes and registers a new Instantiation.  At least one of the variables must be a Variable.
  # @param variable1 [Porolog::Variable,Porolog::Value,Object] one end of the instantiation to be made.
  # @param variable2 [Porolog::Variable,Porolog::Value,Object] the other end of the instantiation to be made.
  # @param index1 [Integer,Symbol,nil] index into the value of variable1.
  # @param index2 [Integer,Symbol,nil] index into the value of variable2.
  # @return [Porolog::Instantiation] the found or created Instantiation.
  def initialize(variable1, index1, variable2, index2)
    @variable1 = variable1
    @variable2 = variable2
    @index1    = index1
    @index2    = index2
    
    @variable1.instantiations << self
    @variable2.instantiations << self
    
    @@instantiations[signature] = self
  end
  
  # @return [Array] the signature of the Instantiation, which aids in avoiding duplication instantiations.
  def signature
    @signature ||= [@variable1, @index1, @variable2, @index2].freeze
    @signature
  end
  
  # @return [String] pretty representation.
  def inspect(indent = 0)
    index1 = @index1 ? "[#{@index1.inspect}]" : ''
    index2 = @index2 ? "[#{@index2.inspect}]" : ''
    
    "#{'  ' * indent}#{@variable1.inspect}#{index1} = #{@variable2.inspect}#{index2}"
  end
  
  # Removes itself from its variables' Instantiations and unregisters itself.
  # @return [Boolean] success
  def remove
    @variable1.instantiations.delete(self)  if @variable1.is_a?(Variable) || @variable1.is_a?(Value)
    @variable2.instantiations.delete(self)  if @variable2.is_a?(Variable) || @variable2.is_a?(Value)
    
    @deleted = true
    @@instantiations.delete(signature)
    @deleted
  end
  
  # Returns the Goal of the other Variable to the one provided.
  # @param variable [Porolog::Variable] the provided Variable.
  # @return [Porolog::Goal,nil] the Goal of the other Variable.
  def other_goal_to(variable)
    return nil unless variables.include?(variable)
    
    other_variable = (variables - [variable]).first
    other_variable&.goal
  end
  
  # @return [Array<Porolog::Goal>] the Goals of the Variables of the Instantiation.
  def goals
    [@variable1.goal,@variable2.goal]
  end
  
  # @return [Array<Porolog::Variable,Object>] the Variables of the Instantiation.
  def variables
    [@variable1,@variable2]
  end
  
  # @param visited [Array] prevents infinite recursion.
  # @return [Array] the values of the Variables of the Instantiation.
  def values(visited = [])
    return [] if visited.include?(self)
    
    values_for_variable1 = values_for(@variable1, visited)
    values_for_variable2 = values_for(@variable2, visited)
    
    (values_for_variable1 + values_for_variable2).uniq
  end
  
  # @param value [Object] the provided value.
  # @param index [Integer,Symbol,Array<Integer>] the index.
  # @param visited [Array] prevents infinite recursion.
  # @return [Object] the indexed value of the provided value.
  def value_indexed(value, index, visited = [])
    return nil unless value
    return nil if value.type == :variable
    return nil if value == [] && index == :tail
    
    visit = [value, index]
    return nil if visited.include?(visit)
    visited = visited + [visit]
    
    case index
      when Integer
        if value.respond_to?(:last) && value.last == UNKNOWN_TAIL
          if index >= value.length - 1
            UNKNOWN_TAIL
          else
            value[index]
          end
        elsif value.is_a?(Numeric)
          value
        else
          value[index]
        end
      
      when Symbol
        value_value = value.value(visited)
        if index == :flathead
          # value_value = [..., 4, 5, 6]
          if value_value.first == UNKNOWN_TAIL
            UNKNOWN_ARRAY
          else
            nil
          end
        elsif index == :flattail
          # value_value = [1, 2, 3, ...]
          if value_value.first == UNKNOWN_TAIL
            nil
          elsif value_value.last == UNKNOWN_TAIL
            UNKNOWN_ARRAY
          end
        elsif value_value.respond_to?(index)
          value_value.send(index)
        else
          value
        end
      
      when Array
        if index.empty?
          value[1..-1]
        else
          value[0...index.first]
        end
      
      else
        if index
          raise UnhandledIndexError, "Unhandled index: #{index.inspect} of #{value.inspect}"
        else
          value
        end
    end
  end
  
  # @param variable [Porolog::Variable,Porolog::Value] the specified variable (or end of the Instantiation).
  # @param visited [Array] prevents infinite recursion.
  # @return [Array<Object>] the values for the specified variable by finding the values of the other variable (i.e. the other end) and applying any indexes.
  def values_for(variable, visited = [])
    return [] if visited.include?(self)
    visited = visited + [self]
    
    if variable == @variable1
      if @index1 == :flathead
        flathead = value_at_index(value_indexed(@variable2.value(visited), @index2, visited), @index1).value.value
        if flathead
          return [[*flathead]]
        else
          return []
        end
      end
      if @index1 == :flattail
        flattail = value_at_index(value_indexed(@variable2.value(visited), @index2, visited), @index1).value.value
        if flattail
          return [[UNKNOWN_TAIL, *flattail]]
        else
          return []
        end
      end
      if @index1
        [value_at_index(value_indexed(@variable2.value(visited), @index2, visited), @index1)]
      else
        if @variable2.is_a?(Variable)
          [value_indexed(@variable2.value(visited), @index2, visited)]
        else
          [value_indexed(@variable2, @index2, visited)]
        end
      end
    elsif variable == @variable2
      if @index2 == :flathead
        flathead = value_at_index(value_indexed(@variable1.value(visited), @index1, visited), @index2).value.value
        if flathead
          return [[*flathead]]
        else
          return []
        end
      end
      if @index2 == :flattail
        flattail = value_at_index(value_indexed(@variable1.value(visited), @index1, visited), @index2).value.value
        if flattail
          return [[UNKNOWN_TAIL, *flattail]]
        else
          return []
        end
      end
      if @index2
        [value_at_index(value_indexed(@variable1.value(visited), @index1, visited), @index2)]
      else
        if @variable1.is_a?(Variable)
          [value_indexed(@variable1.value(visited), @index1, visited)]
        else
          [value_indexed(@variable1, @index1, visited)]
        end
      end
    else
      []
    end.compact
  end
  
  # @param value [Object] the known value.
  # @param index [Integer,Symbol,Array] the known index.
  # @return [Array] an Array where the known value is at the known index.
  def value_at_index(value, index)
    value && case index
      when Integer
        result        = []
        result[index] = value
        result       << UNKNOWN_TAIL
        result
      
      when Symbol
        case index
          when :flathead
            [*value, UNKNOWN_TAIL]
          when :head
            [value, UNKNOWN_TAIL]
          when :tail
            [nil, *value]
          when :flattail
            value
          else
            raise UnhandledIndexError, "Unhandled index: #{index.inspect} for #{value.inspect}"
        end
      
      when Array
        if index.empty?
          [nil, *value]
        else
          [value, UNKNOWN_TAIL]
        end
      
      else
        if index
          raise UnhandledIndexError, "Unhandled index: #{index.inspect} for #{value.inspect}"
        else
          value
        end
    end
  end
  
  # @param variable [Porolog::Variable,Porolog::Value] the specified variable.
  # @return [Boolean] whether the specified variable is not indexed.
  def without_index_on?(variable)
    [
      [@variable1,@index1],
      [@variable2,@index2],
    ].any?{|pair|
      pair.first == variable && pair.last.nil?
    }
  end
  
  # @return [Boolean] whether the Instantiation has been deleted (memoized).
  def deleted?
    @deleted ||= @variable1.goal.deleted? || @variable2.goal.deleted?
    @deleted
  end
  
  # @param goal [Porolog::Goal] the provided Goal.
  # @return [Boolean] whether the Instantiation attaches to a variable in the provided Goal.
  def belongs_to?(goal)
    [
      @variable1.goal,
      @variable2.goal,
    ].include?(goal)
  end
  
end

Class Method Details

.instantiations ⇒ Hash{Array => Porolog::Instantiation}

Returns all Instantiations.

Returns:



50
51
52
# File 'lib/porolog/instantiation.rb', line 50

def self.instantiations
  @@instantiations
end

.new(variable1, index1, variable2, index2) ⇒ Porolog::Instantiation

Finds or creates a new instantiation. At least one of the variables must be a Variable.

Parameters:

Returns:

Raises:



60
61
62
63
64
65
66
67
68
69
70
71
72
# File 'lib/porolog/instantiation.rb', line 60

def self.new(variable1, index1, variable2, index2)
  raise NoVariableError, "Cannot instantiate non-variables: #{variable1.inspect} and #{variable2.inspect}" unless variable1.is_a?(Variable) || variable2.is_a?(Variable)
  
  variable1 = Value.new variable1, variable2.goal unless variable1.is_a?(Variable) || variable1.is_a?(Value)
  variable2 = Value.new variable2, variable1.goal unless variable2.is_a?(Variable) || variable2.is_a?(Value)
  
  instantiation = \
    @@instantiations[[variable1, index1, variable2, index2]] ||
    @@instantiations[[variable2, index2, variable1, index1]] ||
    super
  
  instantiation
end

.reset ⇒ Boolean

Clears all instantiations.

Returns:

  • (Boolean) —

    success



42
43
44
45
# File 'lib/porolog/instantiation.rb', line 42

def self.reset
  @@instantiations = {}
  true
end

Instance Method Details

#belongs_to?(goal) ⇒ Boolean

Returns whether the Instantiation attaches to a variable in the provided Goal.

Parameters:

Returns:

  • (Boolean) —

    whether the Instantiation attaches to a variable in the provided Goal.



337
338
339
340
341
342
# File 'lib/porolog/instantiation.rb', line 337

def belongs_to?(goal)
  [
    @variable1.goal,
    @variable2.goal,
  ].include?(goal)
end

#deleted? ⇒ Boolean

Returns whether the Instantiation has been deleted (memoized).

Returns:

  • (Boolean) —

    whether the Instantiation has been deleted (memoized).



330
331
332
333
# File 'lib/porolog/instantiation.rb', line 330

def deleted?
  @deleted ||= @variable1.goal.deleted? || @variable2.goal.deleted?
  @deleted
end

#goals ⇒ Array<Porolog::Goal>

Returns the Goals of the Variables of the Instantiation.

Returns:



128
129
130
# File 'lib/porolog/instantiation.rb', line 128

def goals
  [@variable1.goal,@variable2.goal]
end

#inspect(indent = 0) ⇒ String

Returns pretty representation.

Returns:

  • (String) —

    pretty representation.



99
100
101
102
103
104
# File 'lib/porolog/instantiation.rb', line 99

def inspect(indent = 0)
  index1 = @index1 ? "[#{@index1.inspect}]" : ''
  index2 = @index2 ? "[#{@index2.inspect}]" : ''
  
  "#{'  ' * indent}#{@variable1.inspect}#{index1} = #{@variable2.inspect}#{index2}"
end

#other_goal_to(variable) ⇒ Porolog::Goal?

Returns the Goal of the other Variable to the one provided.

Parameters:

Returns:



120
121
122
123
124
125
# File 'lib/porolog/instantiation.rb', line 120

def other_goal_to(variable)
  return nil unless variables.include?(variable)
  
  other_variable = (variables - [variable]).first
  other_variable&.goal
end

#remove ⇒ Boolean

Removes itself from its variables' Instantiations and unregisters itself.

Returns:

  • (Boolean) —

    success



108
109
110
111
112
113
114
115
# File 'lib/porolog/instantiation.rb', line 108

def remove
  @variable1.instantiations.delete(self)  if @variable1.is_a?(Variable) || @variable1.is_a?(Value)
  @variable2.instantiations.delete(self)  if @variable2.is_a?(Variable) || @variable2.is_a?(Value)
  
  @deleted = true
  @@instantiations.delete(signature)
  @deleted
end

#signature ⇒ Array

Returns the signature of the Instantiation, which aids in avoiding duplication instantiations.

Returns:

  • (Array) —

    the signature of the Instantiation, which aids in avoiding duplication instantiations.



93
94
95
96
# File 'lib/porolog/instantiation.rb', line 93

def signature
  @signature ||= [@variable1, @index1, @variable2, @index2].freeze
  @signature
end

#value_at_index(value, index) ⇒ Array

Returns an Array where the known value is at the known index.

Parameters:

  • value (Object) —

    the known value.

  • index (Integer, Symbol, Array) —

    the known index.

Returns:

  • (Array) —

    an Array where the known value is at the known index.



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
# File 'lib/porolog/instantiation.rb', line 280

def value_at_index(value, index)
  value && case index
    when Integer
      result        = []
      result[index] = value
      result       << UNKNOWN_TAIL
      result
    
    when Symbol
      case index
        when :flathead
          [*value, UNKNOWN_TAIL]
        when :head
          [value, UNKNOWN_TAIL]
        when :tail
          [nil, *value]
        when :flattail
          value
        else
          raise UnhandledIndexError, "Unhandled index: #{index.inspect} for #{value.inspect}"
      end
    
    when Array
      if index.empty?
        [nil, *value]
      else
        [value, UNKNOWN_TAIL]
      end
    
    else
      if index
        raise UnhandledIndexError, "Unhandled index: #{index.inspect} for #{value.inspect}"
      else
        value
      end
  end
end

#value_indexed(value, index, visited = []) ⇒ Object

Returns the indexed value of the provided value.

Parameters:

  • value (Object) —

    the provided value.

  • index (Integer, Symbol, Array<Integer>) —

    the index.

  • visited (Array) (defaults to: []) —

    prevents infinite recursion.

Returns:

  • (Object) —

    the indexed value of the provided value.



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
# File 'lib/porolog/instantiation.rb', line 152

def value_indexed(value, index, visited = [])
  return nil unless value
  return nil if value.type == :variable
  return nil if value == [] && index == :tail
  
  visit = [value, index]
  return nil if visited.include?(visit)
  visited = visited + [visit]
  
  case index
    when Integer
      if value.respond_to?(:last) && value.last == UNKNOWN_TAIL
        if index >= value.length - 1
          UNKNOWN_TAIL
        else
          value[index]
        end
      elsif value.is_a?(Numeric)
        value
      else
        value[index]
      end
    
    when Symbol
      value_value = value.value(visited)
      if index == :flathead
        # value_value = [..., 4, 5, 6]
        if value_value.first == UNKNOWN_TAIL
          UNKNOWN_ARRAY
        else
          nil
        end
      elsif index == :flattail
        # value_value = [1, 2, 3, ...]
        if value_value.first == UNKNOWN_TAIL
          nil
        elsif value_value.last == UNKNOWN_TAIL
          UNKNOWN_ARRAY
        end
      elsif value_value.respond_to?(index)
        value_value.send(index)
      else
        value
      end
    
    when Array
      if index.empty?
        value[1..-1]
      else
        value[0...index.first]
      end
    
    else
      if index
        raise UnhandledIndexError, "Unhandled index: #{index.inspect} of #{value.inspect}"
      else
        value
      end
  end
end

#values(visited = []) ⇒ Array

Returns the values of the Variables of the Instantiation.

Parameters:

  • visited (Array) (defaults to: []) —

    prevents infinite recursion.

Returns:

  • (Array) —

    the values of the Variables of the Instantiation.



139
140
141
142
143
144
145
146
# File 'lib/porolog/instantiation.rb', line 139

def values(visited = [])
  return [] if visited.include?(self)
  
  values_for_variable1 = values_for(@variable1, visited)
  values_for_variable2 = values_for(@variable2, visited)
  
  (values_for_variable1 + values_for_variable2).uniq
end

#values_for(variable, visited = []) ⇒ Array<Object>

Returns the values for the specified variable by finding the values of the other variable (i.e. the other end) and applying any indexes.

Parameters:

Returns:

  • (Array<Object>) —

    the values for the specified variable by finding the values of the other variable (i.e. the other end) and applying any indexes.



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
# File 'lib/porolog/instantiation.rb', line 216

def values_for(variable, visited = [])
  return [] if visited.include?(self)
  visited = visited + [self]
  
  if variable == @variable1
    if @index1 == :flathead
      flathead = value_at_index(value_indexed(@variable2.value(visited), @index2, visited), @index1).value.value
      if flathead
        return [[*flathead]]
      else
        return []
      end
    end
    if @index1 == :flattail
      flattail = value_at_index(value_indexed(@variable2.value(visited), @index2, visited), @index1).value.value
      if flattail
        return [[UNKNOWN_TAIL, *flattail]]
      else
        return []
      end
    end
    if @index1
      [value_at_index(value_indexed(@variable2.value(visited), @index2, visited), @index1)]
    else
      if @variable2.is_a?(Variable)
        [value_indexed(@variable2.value(visited), @index2, visited)]
      else
        [value_indexed(@variable2, @index2, visited)]
      end
    end
  elsif variable == @variable2
    if @index2 == :flathead
      flathead = value_at_index(value_indexed(@variable1.value(visited), @index1, visited), @index2).value.value
      if flathead
        return [[*flathead]]
      else
        return []
      end
    end
    if @index2 == :flattail
      flattail = value_at_index(value_indexed(@variable1.value(visited), @index1, visited), @index2).value.value
      if flattail
        return [[UNKNOWN_TAIL, *flattail]]
      else
        return []
      end
    end
    if @index2
      [value_at_index(value_indexed(@variable1.value(visited), @index1, visited), @index2)]
    else
      if @variable1.is_a?(Variable)
        [value_indexed(@variable1.value(visited), @index1, visited)]
      else
        [value_indexed(@variable1, @index1, visited)]
      end
    end
  else
    []
  end.compact
end

#variables ⇒ Array<Porolog::Variable,Object>

Returns the Variables of the Instantiation.

Returns:



133
134
135
# File 'lib/porolog/instantiation.rb', line 133

def variables
  [@variable1,@variable2]
end

#without_index_on?(variable) ⇒ Boolean

Returns whether the specified variable is not indexed.

Parameters:

Returns:

  • (Boolean) —

    whether the specified variable is not indexed.



320
321
322
323
324
325
326
327
# File 'lib/porolog/instantiation.rb', line 320

def without_index_on?(variable)
  [
    [@variable1,@index1],
    [@variable2,@index2],
  ].any?{|pair|
    pair.first == variable && pair.last.nil?
  }
end