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