mini_kraken 0.1.05 → 0.1.10
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/.travis.yml +5 -1
- data/CHANGELOG.md +56 -0
- data/README.md +5 -3
- data/lib/mini_kraken/core/composite_goal.rb +46 -0
- data/lib/mini_kraken/core/composite_term.rb +2 -0
- data/lib/mini_kraken/core/conj2.rb +79 -0
- data/lib/mini_kraken/core/cons_cell.rb +49 -43
- data/lib/mini_kraken/core/designation.rb +55 -0
- data/lib/mini_kraken/core/disj2.rb +71 -0
- data/lib/mini_kraken/core/duck_fiber.rb +1 -1
- data/lib/mini_kraken/core/environment.rb +1 -1
- data/lib/mini_kraken/core/equals.rb +134 -132
- data/lib/mini_kraken/core/fail.rb +18 -14
- data/lib/mini_kraken/core/goal.rb +4 -2
- data/lib/mini_kraken/core/goal_arg.rb +10 -0
- data/lib/mini_kraken/core/goal_relation.rb +28 -0
- data/lib/mini_kraken/core/outcome.rb +40 -24
- data/lib/mini_kraken/core/succeed.rb +17 -13
- data/lib/mini_kraken/core/term.rb +5 -2
- data/lib/mini_kraken/core/variable.rb +3 -27
- data/lib/mini_kraken/core/variable_ref.rb +3 -28
- data/lib/mini_kraken/core/vocabulary.rb +39 -19
- data/lib/mini_kraken/glue/fresh_env.rb +45 -3
- data/lib/mini_kraken/glue/run_star_expression.rb +44 -19
- data/lib/mini_kraken/version.rb +1 -1
- data/spec/core/conj2_spec.rb +114 -0
- data/spec/core/cons_cell_spec.rb +8 -0
- data/spec/core/disj2_spec.rb +99 -0
- data/spec/core/duck_fiber_spec.rb +12 -1
- data/spec/core/equals_spec.rb +3 -3
- data/spec/core/outcome_spec.rb +48 -0
- data/spec/core/vocabulary_spec.rb +11 -5
- data/spec/glue/fresh_env_spec.rb +27 -1
- data/spec/glue/run_star_expression_spec.rb +478 -53
- data/spec/mini_kraken_spec.rb +2 -0
- data/spec/spec_helper.rb +0 -1
- data/spec/support/factory_methods.rb +16 -0
- metadata +14 -2
@@ -7,151 +7,153 @@ require_relative 'duck_fiber'
|
|
7
7
|
require_relative 'variable'
|
8
8
|
require_relative 'variable_ref'
|
9
9
|
|
10
|
-
|
11
|
-
module
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
=begin
|
21
|
-
double data flow:
|
22
|
-
A goal has actual arguments
|
23
|
-
When its corresponding relation is invoked
|
24
|
-
this one will return two things: (a success, the bindings/constraints
|
25
|
-
resulting from the relation)
|
26
|
-
the bindings/constraints can be undone if enclosing fails, otherwise
|
27
|
-
the bindings/constraints are rolled up.
|
28
|
-
=end
|
29
|
-
def solver_for(actuals, anEnv)
|
30
|
-
arg1, arg2 = *actuals
|
31
|
-
DuckFiber.new(:custom) { unification(arg1, arg2, anEnv) }
|
32
|
-
end
|
33
|
-
|
34
|
-
def unification(arg1, arg2, anEnv)
|
35
|
-
arg1_nil = arg1.nil?
|
36
|
-
arg2_nil = arg2.nil?
|
37
|
-
if arg1_nil || arg2_nil
|
38
|
-
if arg1_nil && arg2_nil
|
39
|
-
result = Outcome.new(:"#s", anEnv)
|
40
|
-
else
|
41
|
-
result = Failure
|
42
|
-
end
|
43
|
-
return result
|
10
|
+
unless MiniKraken::Core.constants(false).include? :Equals
|
11
|
+
module MiniKraken
|
12
|
+
module Core
|
13
|
+
# equals tries to unify two terms
|
14
|
+
class Equals < BinaryRelation
|
15
|
+
include Singleton
|
16
|
+
|
17
|
+
def initialize
|
18
|
+
super('equals', '==')
|
44
19
|
end
|
45
|
-
|
46
|
-
|
47
|
-
# anEnv
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
# | isa? CompositeTerm | isa? CompositeTerm | unification(arg1.cdr, arg2.cdr) => "u" || { "u", [] ) | |
|
61
|
-
# | isa? VariableRef | isa? Atomic | arg1.fresh? is true || { "s", [arg2] } |
|
62
|
-
# | isa? VariableRef | isa? Atomic | arg1.fresh? is false || |
|
63
|
-
# | | unification(arg1.value, arg2) => "s" || { "s", [bindings*] } |
|
64
|
-
# | | unification(arg1.value, arg2) => "u" || { "u", [] } |
|
65
|
-
# | isa? VariableRef | isa? CompositeTerm | arg1.fresh? is true || { "s", [arg2] } | # What if arg1 occurs in arg2?
|
66
|
-
# | isa? VariableRef | isa? CompositeTerm | arg1.fresh? is false || |
|
67
|
-
# | | unification(arg1.value, arg2) => "s" || { "s", [bindings*] } |
|
68
|
-
# | | unification(arg1.value, arg2) => "u" || { "u", [] } |
|
69
|
-
# | isa? VariableRef | isa? VariableRef | arg1.fresh?, arg2.fresh? => [true, true] || { "s", [arg1 <=> arg2] } |
|
70
|
-
# | isa? VariableRef | isa? VariableRef | arg1.fresh?, arg2.fresh? => [true, false] || |
|
71
|
-
# | | unification(arg1, arg2.value) => "s" || { "s", [bindings*] } |
|
72
|
-
# | | unification(arg1, arg2.value) => "u" || { "u", [] } |
|
73
|
-
# | isa? VariableRef | isa? VariableRef | arg1.fresh?, arg2.fresh? => [false, false]|| |
|
74
|
-
# | | unification(arg1, arg2.value) => "s" || { "s", [bindings*] } |
|
75
|
-
# | | unification(arg1, arg2.value) => "u" || { "u", [] }
|
76
|
-
def do_unification(arg1, arg2, anEnv)
|
77
|
-
# require 'debug'
|
78
|
-
return Outcome.new(:"#s", anEnv) if arg1.equal?(arg2)
|
79
|
-
|
80
|
-
result = Outcome.new(:"#u", anEnv) # default case
|
81
|
-
|
82
|
-
if arg1.kind_of?(AtomicTerm)
|
83
|
-
result = BasicSuccess if arg1.eql?(arg2)
|
84
|
-
elsif arg1.kind_of?(CompositeTerm)
|
85
|
-
if arg2.kind_of?(CompositeTerm) # AtomicTerm is default case => fail
|
86
|
-
result = unify_composite_terms(arg1, arg2, anEnv)
|
87
|
-
end
|
88
|
-
elsif arg1.kind_of?(VariableRef)
|
89
|
-
arg1_freshness = arg1.freshness(anEnv)
|
90
|
-
if arg2.kind_of?(AtomicTerm)
|
91
|
-
if arg1_freshness.degree == :fresh
|
92
|
-
result = Outcome.new(:"#s", anEnv)
|
93
|
-
arg1.associate(arg2, result)
|
20
|
+
|
21
|
+
# @param actuals [Array<Term>] A two-elements array
|
22
|
+
# @param anEnv [Vocabulary] A vocabulary object
|
23
|
+
# @return [Fiber<Outcome>] A Fiber(-like) instance that yields Outcomes
|
24
|
+
def solver_for(actuals, anEnv)
|
25
|
+
arg1, arg2 = *actuals
|
26
|
+
DuckFiber.new(:custom) { unification(arg1, arg2, anEnv) }
|
27
|
+
end
|
28
|
+
|
29
|
+
def unification(arg1, arg2, anEnv)
|
30
|
+
arg1_nil = arg1.nil?
|
31
|
+
arg2_nil = arg2.nil?
|
32
|
+
if arg1_nil || arg2_nil
|
33
|
+
if arg1_nil && arg2_nil
|
34
|
+
result = Outcome.success(anEnv)
|
94
35
|
else
|
95
|
-
result = Outcome.
|
36
|
+
result = Outcome.failure(anEnv)
|
96
37
|
end
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
38
|
+
return result
|
39
|
+
end
|
40
|
+
new_arg1, new_arg2 = commute_cond(arg1, arg2, anEnv)
|
41
|
+
result = do_unification(new_arg1, new_arg2, anEnv)
|
42
|
+
# anEnv.merge(result) if result.successful? && !result.association.empty?
|
43
|
+
|
44
|
+
result
|
45
|
+
end
|
46
|
+
|
47
|
+
private
|
48
|
+
|
49
|
+
|
50
|
+
# table: Unification
|
51
|
+
# | arg1 | arg2 | Criterion || Unification |
|
52
|
+
# | isa? Atomic | isa? Atomic | arg1.eq? arg2 is true || { "s", [] } |
|
53
|
+
# | isa? Atomic | isa? Atomic | arg1.eq? arg2 is false || { "u", [] } |
|
54
|
+
# | isa? CompositeTerm | isa? Atomic | dont_care || { "u", [] } |
|
55
|
+
# | isa? CompositeTerm | isa? CompositeTerm | unification(arg1.car, arg2.car) => "s" || { "s", [bindings*] } |
|
56
|
+
# | isa? CompositeTerm | isa? CompositeTerm | unification(arg1.cdr, arg2.cdr) => "u" || { "u", [] ) | |
|
57
|
+
# | isa? VariableRef | isa? Atomic | arg1.fresh? is true || { "s", [arg2] } |
|
58
|
+
# | isa? VariableRef | isa? Atomic | arg1.fresh? is false || |
|
59
|
+
# | | unification(arg1.value, arg2) => "s" || { "s", [bindings*] } |
|
60
|
+
# | | unification(arg1.value, arg2) => "u" || { "u", [] } |
|
61
|
+
# | isa? VariableRef | isa? CompositeTerm | arg1.fresh? is true || { "s", [arg2] } | # What if arg1 occurs in arg2?
|
62
|
+
# | isa? VariableRef | isa? CompositeTerm | arg1.fresh? is false || |
|
63
|
+
# | | unification(arg1.value, arg2) => "s" || { "s", [bindings*] } |
|
64
|
+
# | | unification(arg1.value, arg2) => "u" || { "u", [] } |
|
65
|
+
# | isa? VariableRef | isa? VariableRef | arg1.fresh?, arg2.fresh? => [true, true] || { "s", [arg1 <=> arg2] } |
|
66
|
+
# | isa? VariableRef | isa? VariableRef | arg1.fresh?, arg2.fresh? => [true, false] || |
|
67
|
+
# | | unification(arg1, arg2.value) => "s" || { "s", [bindings*] } |
|
68
|
+
# | | unification(arg1, arg2.value) => "u" || { "u", [] } |
|
69
|
+
# | isa? VariableRef | isa? VariableRef | arg1.fresh?, arg2.fresh? => [false, false]|| |
|
70
|
+
# | | unification(arg1, arg2.value) => "s" || { "s", [bindings*] } |
|
71
|
+
# | | unification(arg1, arg2.value) => "u" || { "u", [] }
|
72
|
+
def do_unification(arg1, arg2, anEnv)
|
73
|
+
# require 'debug'
|
74
|
+
return Outcome.success(anEnv) if arg1.equal?(arg2)
|
75
|
+
|
76
|
+
result = Outcome.failure(anEnv) # default case
|
77
|
+
|
78
|
+
if arg1.kind_of?(AtomicTerm)
|
79
|
+
result = Outcome.success(anEnv) if arg1.eql?(arg2)
|
80
|
+
elsif arg1.kind_of?(CompositeTerm)
|
81
|
+
if arg2.kind_of?(CompositeTerm) # AtomicTerm is default case => fail
|
82
|
+
result = unify_composite_terms(arg1, arg2, anEnv)
|
104
83
|
end
|
105
|
-
elsif
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
84
|
+
elsif arg1.kind_of?(VariableRef)
|
85
|
+
arg1_freshness = arg1.freshness(anEnv)
|
86
|
+
if arg2.kind_of?(AtomicTerm)
|
87
|
+
if arg1_freshness.degree == :fresh
|
88
|
+
result = Outcome.success(anEnv)
|
89
|
+
arg1.associate(arg2, result)
|
90
|
+
else
|
91
|
+
result = Outcome.success(anEnv) if arg1.value(anEnv).eql?(arg2)
|
92
|
+
end
|
93
|
+
elsif arg2.kind_of?(CompositeTerm)
|
94
|
+
if arg1_freshness.degree == :fresh
|
95
|
+
result = Outcome.success(anEnv)
|
96
|
+
arg1.associate(arg2, result)
|
97
|
+
else
|
98
|
+
# Ground case...
|
99
|
+
result = unify_composite_terms(arg1_freshness.associated, arg2, anEnv)
|
100
|
+
end
|
101
|
+
elsif arg2.kind_of?(VariableRef)
|
102
|
+
freshness = [arg1.fresh?(anEnv), arg2.fresh?(anEnv)]
|
103
|
+
case freshness
|
104
|
+
when [false, false] # TODO: confirm this...
|
105
|
+
result = unification(arg1.value(anEnv), arg2.value(anEnv), anEnv)
|
106
|
+
when [true, true]
|
107
|
+
result = Outcome.success(anEnv)
|
108
|
+
if arg1.var_name != arg2.var_name
|
109
|
+
arg1.associate(arg2, result)
|
110
|
+
arg2.associate(arg1, result)
|
111
|
+
end
|
112
|
+
when [true, false]
|
113
|
+
result = Outcome.success(anEnv)
|
113
114
|
arg1.associate(arg2, result)
|
114
|
-
|
115
|
+
else
|
116
|
+
raise StandardError, "Unsupported freshness combination #{freshness}"
|
115
117
|
end
|
116
118
|
else
|
117
|
-
|
119
|
+
arg_kinds = [arg1.class, arg2.class]
|
120
|
+
raise StandardError, "Unsupported combination #{arg_kinds}"
|
118
121
|
end
|
119
|
-
else
|
120
|
-
arg_kinds = [arg1.class, arg2.class]
|
121
|
-
raise StandardError, "Unsupported combination #{arg_kinds}"
|
122
122
|
end
|
123
|
+
|
124
|
+
result
|
123
125
|
end
|
124
126
|
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
sub_total
|
127
|
+
# @return [Freshness]
|
128
|
+
def unify_composite_terms(arg1, arg2, anEnv)
|
129
|
+
# require 'debug'
|
130
|
+
result = Outcome.failure(anEnv)
|
131
|
+
children1 = arg1.children
|
132
|
+
children2 = arg2.children
|
133
|
+
|
134
|
+
if children1.size == children2.size
|
135
|
+
i = 0
|
136
|
+
subresults = children1.map do |child1|
|
137
|
+
child2 = children2[i]
|
138
|
+
i += 1
|
139
|
+
unification(child1, child2, anEnv)
|
140
|
+
end
|
141
|
+
total_success = subresults.all?(&:successful?)
|
142
|
+
if total_success
|
143
|
+
memo = Outcome.success(anEnv)
|
144
|
+
associations = subresults.reduce(memo) do |sub_total, outcome|
|
145
|
+
sub_total.merge(outcome)
|
146
|
+
sub_total
|
147
|
+
end
|
148
|
+
result = memo
|
148
149
|
end
|
149
|
-
result = memo
|
150
150
|
end
|
151
|
+
|
152
|
+
result
|
151
153
|
end
|
154
|
+
end # class
|
152
155
|
|
153
|
-
|
154
|
-
|
155
|
-
end # class
|
156
|
+
Equals.instance.freeze
|
157
|
+
end # module
|
156
158
|
end # module
|
157
|
-
end #
|
159
|
+
end # unless
|
@@ -4,20 +4,24 @@ require 'singleton'
|
|
4
4
|
require_relative 'duck_fiber'
|
5
5
|
require_relative 'nullary_relation'
|
6
6
|
|
7
|
-
|
8
|
-
module
|
9
|
-
|
10
|
-
|
11
|
-
|
7
|
+
unless MiniKraken::Core.constants(false).include? :Fail
|
8
|
+
module MiniKraken
|
9
|
+
module Core
|
10
|
+
# A nullary relation that unconditionally always fails.
|
11
|
+
class Fail < NullaryRelation
|
12
|
+
include Singleton
|
12
13
|
|
13
|
-
|
14
|
-
|
15
|
-
|
14
|
+
def initialize
|
15
|
+
super('fail', '#u')
|
16
|
+
end
|
16
17
|
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
18
|
+
# @return [DuckFiber]
|
19
|
+
def solver_for(_actuals, _env)
|
20
|
+
DuckFiber.new(:failure)
|
21
|
+
end
|
22
|
+
end # class
|
23
|
+
|
24
|
+
Fail.instance.freeze
|
25
|
+
end # module
|
22
26
|
end # module
|
23
|
-
end #
|
27
|
+
end # unless
|
@@ -3,8 +3,10 @@
|
|
3
3
|
require_relative 'environment'
|
4
4
|
|
5
5
|
module MiniKraken
|
6
|
+
require_relative 'goal_arg'
|
7
|
+
|
6
8
|
module Core
|
7
|
-
class Goal
|
9
|
+
class Goal < GoalArg
|
8
10
|
# @return [Relation] The relation corresponding to this goal
|
9
11
|
attr_reader :relation
|
10
12
|
|
@@ -35,7 +37,7 @@ module MiniKraken
|
|
35
37
|
|
36
38
|
prefix = 'Invalid goal argument '
|
37
39
|
args.each do |actl|
|
38
|
-
raise StandardError, prefix + actl.to_s unless actl.kind_of?(
|
40
|
+
raise StandardError, prefix + actl.to_s unless actl.kind_of?(GoalArg)
|
39
41
|
end
|
40
42
|
|
41
43
|
args.dup
|
@@ -0,0 +1,28 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'relation'
|
4
|
+
|
5
|
+
module MiniKraken
|
6
|
+
module Core
|
7
|
+
# A specialization of a relation that accepts only goal(s)
|
8
|
+
# as its arguments.
|
9
|
+
class GoalRelation < Relation
|
10
|
+
def arity
|
11
|
+
2
|
12
|
+
end
|
13
|
+
|
14
|
+
protected
|
15
|
+
|
16
|
+
def validated_args(actuals)
|
17
|
+
actuals.each do |arg|
|
18
|
+
unless arg.kind_of?(Goal)
|
19
|
+
prefix = "#{name} expects goal as argument, found a "
|
20
|
+
raise StandardError, prefix + "'#{arg.class}'"
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
actuals
|
25
|
+
end
|
26
|
+
end # class
|
27
|
+
end # module
|
28
|
+
end # module
|
@@ -2,36 +2,52 @@
|
|
2
2
|
|
3
3
|
require_relative 'vocabulary'
|
4
4
|
|
5
|
-
|
6
|
-
module
|
7
|
-
|
8
|
-
|
5
|
+
unless MiniKraken::Core.constants(false).include? :Outcome
|
6
|
+
module MiniKraken
|
7
|
+
module Core
|
8
|
+
class Outcome
|
9
|
+
include Vocabulary # Use mix-in module
|
10
|
+
|
11
|
+
# @return [Symbol] One of: :"#s" (success), :"#u" (failure)
|
12
|
+
attr_reader :resultant
|
13
|
+
|
14
|
+
def initialize(aResult, aParent = nil)
|
15
|
+
init_vocabulary(aParent)
|
16
|
+
@resultant = aResult
|
17
|
+
end
|
9
18
|
|
10
|
-
|
11
|
-
|
19
|
+
def self.failure(aParent = nil)
|
20
|
+
new(:"#u", aParent)
|
21
|
+
end
|
12
22
|
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
end
|
23
|
+
def self.success(aParent = nil)
|
24
|
+
new(:"#s", aParent)
|
25
|
+
end
|
17
26
|
|
18
|
-
|
19
|
-
|
20
|
-
|
27
|
+
def successful?
|
28
|
+
resultant == :"#s"
|
29
|
+
end
|
21
30
|
|
22
|
-
|
23
|
-
|
31
|
+
def ==(other)
|
32
|
+
are_equal = false
|
24
33
|
|
25
|
-
|
26
|
-
|
27
|
-
|
34
|
+
if resultant == other.resultant && parent == other.parent &&
|
35
|
+
associations == other.associations
|
36
|
+
are_equal = true
|
37
|
+
end
|
38
|
+
|
39
|
+
are_equal
|
28
40
|
end
|
29
41
|
|
30
|
-
|
31
|
-
|
32
|
-
|
42
|
+
protected
|
43
|
+
|
44
|
+
def introspect
|
45
|
+
", @resultant=#{resultant}"
|
46
|
+
end
|
47
|
+
end # class
|
33
48
|
|
34
|
-
|
35
|
-
|
49
|
+
Failure = Outcome.new(:"#u")
|
50
|
+
BasicSuccess = Outcome.new(:"#s")
|
51
|
+
end # module
|
36
52
|
end # module
|
37
|
-
end #
|
53
|
+
end # defined
|