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.
Files changed (39) hide show
  1. checksums.yaml +4 -4
  2. data/.travis.yml +5 -1
  3. data/CHANGELOG.md +56 -0
  4. data/README.md +5 -3
  5. data/lib/mini_kraken/core/composite_goal.rb +46 -0
  6. data/lib/mini_kraken/core/composite_term.rb +2 -0
  7. data/lib/mini_kraken/core/conj2.rb +79 -0
  8. data/lib/mini_kraken/core/cons_cell.rb +49 -43
  9. data/lib/mini_kraken/core/designation.rb +55 -0
  10. data/lib/mini_kraken/core/disj2.rb +71 -0
  11. data/lib/mini_kraken/core/duck_fiber.rb +1 -1
  12. data/lib/mini_kraken/core/environment.rb +1 -1
  13. data/lib/mini_kraken/core/equals.rb +134 -132
  14. data/lib/mini_kraken/core/fail.rb +18 -14
  15. data/lib/mini_kraken/core/goal.rb +4 -2
  16. data/lib/mini_kraken/core/goal_arg.rb +10 -0
  17. data/lib/mini_kraken/core/goal_relation.rb +28 -0
  18. data/lib/mini_kraken/core/outcome.rb +40 -24
  19. data/lib/mini_kraken/core/succeed.rb +17 -13
  20. data/lib/mini_kraken/core/term.rb +5 -2
  21. data/lib/mini_kraken/core/variable.rb +3 -27
  22. data/lib/mini_kraken/core/variable_ref.rb +3 -28
  23. data/lib/mini_kraken/core/vocabulary.rb +39 -19
  24. data/lib/mini_kraken/glue/fresh_env.rb +45 -3
  25. data/lib/mini_kraken/glue/run_star_expression.rb +44 -19
  26. data/lib/mini_kraken/version.rb +1 -1
  27. data/spec/core/conj2_spec.rb +114 -0
  28. data/spec/core/cons_cell_spec.rb +8 -0
  29. data/spec/core/disj2_spec.rb +99 -0
  30. data/spec/core/duck_fiber_spec.rb +12 -1
  31. data/spec/core/equals_spec.rb +3 -3
  32. data/spec/core/outcome_spec.rb +48 -0
  33. data/spec/core/vocabulary_spec.rb +11 -5
  34. data/spec/glue/fresh_env_spec.rb +27 -1
  35. data/spec/glue/run_star_expression_spec.rb +478 -53
  36. data/spec/mini_kraken_spec.rb +2 -0
  37. data/spec/spec_helper.rb +0 -1
  38. data/spec/support/factory_methods.rb +16 -0
  39. metadata +14 -2
@@ -36,7 +36,7 @@ module MiniKraken
36
36
  when :failure
37
37
  Failure
38
38
  when :success
39
- BasicSuccess
39
+ Outcome.new(:"#s")
40
40
  else
41
41
  raise StandardError, "Unknonw outcome kind #{outcomeKind}"
42
42
  end
@@ -47,7 +47,7 @@ module MiniKraken
47
47
  # Rollout associations from hierarchy
48
48
  walker = descendent.ancestor_walker
49
49
  begin
50
- env = walker.resume
50
+ env = walker.next
51
51
  break if env.nil?
52
52
 
53
53
  env.do_propagate(descendent) if env.kind_of?(Environment)
@@ -7,151 +7,153 @@ require_relative 'duck_fiber'
7
7
  require_relative 'variable'
8
8
  require_relative 'variable_ref'
9
9
 
10
- module MiniKraken
11
- module Core
12
- # equals tries to unify two terms
13
- class Equals < BinaryRelation
14
- include Singleton
15
-
16
- def initialize
17
- super('equals', '==')
18
- end
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
- new_arg1, new_arg2 = commute_cond(arg1, arg2, anEnv)
46
- result = do_unification(new_arg1, new_arg2, anEnv)
47
- # anEnv.merge(result) if result.successful? && !result.association.empty?
48
-
49
- result
50
- end
51
-
52
- private
53
-
54
- # table: Unification
55
- # | arg1 | arg2 | Criterion || Unification |
56
- # | isa? Atomic | isa? Atomic | arg1.eq? arg2 is true || { "s", [] } |
57
- # | isa? Atomic | isa? Atomic | arg1.eq? arg2 is false || { "u", [] } |
58
- # | isa? CompositeTerm | isa? Atomic | dont_care || { "u", [] } |
59
- # | isa? CompositeTerm | isa? CompositeTerm | unification(arg1.car, arg2.car) => "s" || { "s", [bindings*] } |
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.new(:"#s", anEnv) if arg1.value(anEnv).eql?(arg2)
36
+ result = Outcome.failure(anEnv)
96
37
  end
97
- elsif arg2.kind_of?(CompositeTerm)
98
- if arg1_freshness.degree == :fresh
99
- result = Outcome.new(:"#s", anEnv)
100
- arg1.associate(arg2, result)
101
- else
102
- # Ground case...
103
- result = unify_composite_terms(arg1_freshness.associated, arg2, anEnv)
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 arg2.kind_of?(VariableRef)
106
- freshness = [arg1.fresh?(anEnv), arg2.fresh?(anEnv)]
107
- case freshness
108
- when [false, false] # TODO: confirm this...
109
- result = unification(arg1.value(anEnv), arg2.value(anEnv), anEnv)
110
- when [true, true]
111
- result = Outcome.new(:"#s", anEnv)
112
- if arg1.var_name != arg2.var_name
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
- arg2.associate(arg1, result)
115
+ else
116
+ raise StandardError, "Unsupported freshness combination #{freshness}"
115
117
  end
116
118
  else
117
- raise StandardError, "Unsupported freshness combination #{freshness}"
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
- result
126
- end
127
-
128
- # @return [Freshness]
129
- def unify_composite_terms(arg1, arg2, anEnv)
130
- # require 'debug'
131
- result = Outcome.new(:"#u", anEnv)
132
- children1 = arg1.children
133
- children2 = arg2.children
134
-
135
- if children1.size == children2.size
136
- i = 0
137
- subresults = children1.map do |child1|
138
- child2 = children2[i]
139
- i += 1
140
- unification(child1, child2, anEnv)
141
- end
142
- total_success = subresults.all?(&:successful?)
143
- if total_success
144
- memo = Outcome.new(:"#s", anEnv)
145
- associations = subresults.reduce(memo) do |sub_total, outcome|
146
- sub_total.merge(outcome)
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
- result
154
- end
155
- end # class
156
+ Equals.instance.freeze
157
+ end # module
156
158
  end # module
157
- end # module
159
+ end # unless
@@ -4,20 +4,24 @@ require 'singleton'
4
4
  require_relative 'duck_fiber'
5
5
  require_relative 'nullary_relation'
6
6
 
7
- module MiniKraken
8
- module Core
9
- # A nullary relation that unconditionally always fails.
10
- class Fail < NullaryRelation
11
- include Singleton
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
- def initialize
14
- super('fail', '#u')
15
- end
14
+ def initialize
15
+ super('fail', '#u')
16
+ end
16
17
 
17
- # @return [DuckFiber]
18
- def solver_for(_actuals, _env)
19
- DuckFiber.new(:failure)
20
- end
21
- end # class
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 # module
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?(Term)
40
+ raise StandardError, prefix + actl.to_s unless actl.kind_of?(GoalArg)
39
41
  end
40
42
 
41
43
  args.dup
@@ -0,0 +1,10 @@
1
+ # frozen_string_literal: true
2
+
3
+ module MiniKraken
4
+ module Core
5
+ # The generalization of any iem that can be
6
+ # passed as arugement to a goal.
7
+ class GoalArg
8
+ end # class
9
+ end # module
10
+ end # module
@@ -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
- module MiniKraken
6
- module Core
7
- class Outcome
8
- include Vocabulary # Use mix-in module
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
- # @return [Symbol] One of: :"#s" (success), :"#u" (failure)
11
- attr_reader :resultant
19
+ def self.failure(aParent = nil)
20
+ new(:"#u", aParent)
21
+ end
12
22
 
13
- def initialize(aResult, aParent = nil)
14
- init_vocabulary(aParent)
15
- @resultant = aResult
16
- end
23
+ def self.success(aParent = nil)
24
+ new(:"#s", aParent)
25
+ end
17
26
 
18
- def successful?
19
- resultant == :"#s"
20
- end
27
+ def successful?
28
+ resultant == :"#s"
29
+ end
21
30
 
22
- def ==(other)
23
- are_equal = false
31
+ def ==(other)
32
+ are_equal = false
24
33
 
25
- if resultant == other.resultant && parent == other.parent &&
26
- associations == other.associations
27
- are_equal = true
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
- are_equal
31
- end
32
- end # class
42
+ protected
43
+
44
+ def introspect
45
+ ", @resultant=#{resultant}"
46
+ end
47
+ end # class
33
48
 
34
- Failure = Outcome.new(:"#u")
35
- BasicSuccess = Outcome.new(:"#s")
49
+ Failure = Outcome.new(:"#u")
50
+ BasicSuccess = Outcome.new(:"#s")
51
+ end # module
36
52
  end # module
37
- end # module
53
+ end # defined