mini_kraken 0.1.06 → 0.1.11

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 (42) hide show
  1. checksums.yaml +4 -4
  2. data/.travis.yml +5 -1
  3. data/CHANGELOG.md +56 -1
  4. data/README.md +11 -3
  5. data/lib/mini_kraken/core/association_walker.rb +1 -1
  6. data/lib/mini_kraken/core/base_arg.rb +10 -0
  7. data/lib/mini_kraken/core/conj2.rb +58 -65
  8. data/lib/mini_kraken/core/cons_cell.rb +49 -43
  9. data/lib/mini_kraken/core/def_relation.rb +49 -0
  10. data/lib/mini_kraken/core/disj2.rb +72 -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 -126
  14. data/lib/mini_kraken/core/fail.rb +18 -14
  15. data/lib/mini_kraken/core/formal_arg.rb +22 -0
  16. data/lib/mini_kraken/core/formal_ref.rb +24 -0
  17. data/lib/mini_kraken/core/goal_arg.rb +5 -3
  18. data/lib/mini_kraken/core/goal_relation.rb +13 -0
  19. data/lib/mini_kraken/core/goal_template.rb +60 -0
  20. data/lib/mini_kraken/core/k_boolean.rb +31 -0
  21. data/lib/mini_kraken/core/outcome.rb +40 -24
  22. data/lib/mini_kraken/core/succeed.rb +17 -13
  23. data/lib/mini_kraken/core/vocabulary.rb +37 -17
  24. data/lib/mini_kraken/glue/fresh_env.rb +45 -3
  25. data/lib/mini_kraken/glue/run_star_expression.rb +45 -19
  26. data/lib/mini_kraken/version.rb +1 -1
  27. data/spec/core/conj2_spec.rb +8 -9
  28. data/spec/core/cons_cell_spec.rb +8 -0
  29. data/spec/core/def_relation_spec.rb +96 -0
  30. data/spec/core/disj2_spec.rb +99 -0
  31. data/spec/core/duck_fiber_spec.rb +12 -1
  32. data/spec/core/equals_spec.rb +3 -3
  33. data/spec/core/goal_template_spec.rb +74 -0
  34. data/spec/core/k_boolean_spec.rb +107 -0
  35. data/spec/core/outcome_spec.rb +48 -0
  36. data/spec/core/vocabulary_spec.rb +11 -5
  37. data/spec/glue/fresh_env_spec.rb +27 -1
  38. data/spec/glue/run_star_expression_spec.rb +538 -70
  39. data/spec/mini_kraken_spec.rb +2 -0
  40. data/spec/spec_helper.rb +0 -1
  41. data/spec/support/factory_methods.rb +17 -1
  42. metadata +19 -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,145 +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
- # @param actuals [Array<Term>] A two-elements array
21
- # @param anEnv [Vocabulary] A vocabulary object
22
- # @return [Fiber<Outcome>] A Fiber(-like) instance that yields Outcomes
23
- def solver_for(actuals, anEnv)
24
- arg1, arg2 = *actuals
25
- DuckFiber.new(:custom) { unification(arg1, arg2, anEnv) }
26
- end
27
-
28
- def unification(arg1, arg2, anEnv)
29
- arg1_nil = arg1.nil?
30
- arg2_nil = arg2.nil?
31
- if arg1_nil || arg2_nil
32
- if arg1_nil && arg2_nil
33
- result = Outcome.new(:"#s", anEnv)
34
- else
35
- result = Failure
36
- end
37
- 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', '==')
38
19
  end
39
- new_arg1, new_arg2 = commute_cond(arg1, arg2, anEnv)
40
- result = do_unification(new_arg1, new_arg2, anEnv)
41
- # anEnv.merge(result) if result.successful? && !result.association.empty?
42
-
43
- result
44
- end
45
-
46
- private
47
-
48
- # table: Unification
49
- # | arg1 | arg2 | Criterion || Unification |
50
- # | isa? Atomic | isa? Atomic | arg1.eq? arg2 is true || { "s", [] } |
51
- # | isa? Atomic | isa? Atomic | arg1.eq? arg2 is false || { "u", [] } |
52
- # | isa? CompositeTerm | isa? Atomic | dont_care || { "u", [] } |
53
- # | isa? CompositeTerm | isa? CompositeTerm | unification(arg1.car, arg2.car) => "s" || { "s", [bindings*] } |
54
- # | isa? CompositeTerm | isa? CompositeTerm | unification(arg1.cdr, arg2.cdr) => "u" || { "u", [] ) | |
55
- # | isa? VariableRef | isa? Atomic | arg1.fresh? is true || { "s", [arg2] } |
56
- # | isa? VariableRef | isa? Atomic | arg1.fresh? is false || |
57
- # | | unification(arg1.value, arg2) => "s" || { "s", [bindings*] } |
58
- # | | unification(arg1.value, arg2) => "u" || { "u", [] } |
59
- # | isa? VariableRef | isa? CompositeTerm | arg1.fresh? is true || { "s", [arg2] } | # What if arg1 occurs in arg2?
60
- # | isa? VariableRef | isa? CompositeTerm | arg1.fresh? is false || |
61
- # | | unification(arg1.value, arg2) => "s" || { "s", [bindings*] } |
62
- # | | unification(arg1.value, arg2) => "u" || { "u", [] } |
63
- # | isa? VariableRef | isa? VariableRef | arg1.fresh?, arg2.fresh? => [true, true] || { "s", [arg1 <=> arg2] } |
64
- # | isa? VariableRef | isa? VariableRef | arg1.fresh?, arg2.fresh? => [true, false] || |
65
- # | | unification(arg1, arg2.value) => "s" || { "s", [bindings*] } |
66
- # | | unification(arg1, arg2.value) => "u" || { "u", [] } |
67
- # | isa? VariableRef | isa? VariableRef | arg1.fresh?, arg2.fresh? => [false, false]|| |
68
- # | | unification(arg1, arg2.value) => "s" || { "s", [bindings*] } |
69
- # | | unification(arg1, arg2.value) => "u" || { "u", [] }
70
- def do_unification(arg1, arg2, anEnv)
71
- # require 'debug'
72
- return Outcome.new(:"#s", anEnv) if arg1.equal?(arg2)
73
-
74
- result = Outcome.new(:"#u", anEnv) # default case
75
-
76
- if arg1.kind_of?(AtomicTerm)
77
- result = BasicSuccess if arg1.eql?(arg2)
78
- elsif arg1.kind_of?(CompositeTerm)
79
- if arg2.kind_of?(CompositeTerm) # AtomicTerm is default case => fail
80
- result = unify_composite_terms(arg1, arg2, anEnv)
81
- end
82
- elsif arg1.kind_of?(VariableRef)
83
- arg1_freshness = arg1.freshness(anEnv)
84
- if arg2.kind_of?(AtomicTerm)
85
- if arg1_freshness.degree == :fresh
86
- result = Outcome.new(:"#s", anEnv)
87
- 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)
88
35
  else
89
- result = Outcome.new(:"#s", anEnv) if arg1.value(anEnv).eql?(arg2)
36
+ result = Outcome.failure(anEnv)
90
37
  end
91
- elsif arg2.kind_of?(CompositeTerm)
92
- if arg1_freshness.degree == :fresh
93
- result = Outcome.new(:"#s", anEnv)
94
- arg1.associate(arg2, result)
95
- else
96
- # Ground case...
97
- 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)
98
83
  end
99
- elsif arg2.kind_of?(VariableRef)
100
- freshness = [arg1.fresh?(anEnv), arg2.fresh?(anEnv)]
101
- case freshness
102
- when [false, false] # TODO: confirm this...
103
- result = unification(arg1.value(anEnv), arg2.value(anEnv), anEnv)
104
- when [true, true]
105
- result = Outcome.new(:"#s", anEnv)
106
- 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)
107
114
  arg1.associate(arg2, result)
108
- arg2.associate(arg1, result)
115
+ else
116
+ raise StandardError, "Unsupported freshness combination #{freshness}"
109
117
  end
110
118
  else
111
- raise StandardError, "Unsupported freshness combination #{freshness}"
119
+ arg_kinds = [arg1.class, arg2.class]
120
+ raise StandardError, "Unsupported combination #{arg_kinds}"
112
121
  end
113
- else
114
- arg_kinds = [arg1.class, arg2.class]
115
- raise StandardError, "Unsupported combination #{arg_kinds}"
116
122
  end
123
+
124
+ result
117
125
  end
118
126
 
119
- result
120
- end
121
-
122
- # @return [Freshness]
123
- def unify_composite_terms(arg1, arg2, anEnv)
124
- # require 'debug'
125
- result = Outcome.new(:"#u", anEnv)
126
- children1 = arg1.children
127
- children2 = arg2.children
128
-
129
- if children1.size == children2.size
130
- i = 0
131
- subresults = children1.map do |child1|
132
- child2 = children2[i]
133
- i += 1
134
- unification(child1, child2, anEnv)
135
- end
136
- total_success = subresults.all?(&:successful?)
137
- if total_success
138
- memo = Outcome.new(:"#s", anEnv)
139
- associations = subresults.reduce(memo) do |sub_total, outcome|
140
- sub_total.merge(outcome)
141
- 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
142
149
  end
143
- result = memo
144
150
  end
151
+
152
+ result
145
153
  end
154
+ end # class
146
155
 
147
- result
148
- end
149
- end # class
156
+ Equals.instance.freeze
157
+ end # module
150
158
  end # module
151
- 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
@@ -0,0 +1,22 @@
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 FormalArg
8
+ # @return [String]
9
+ attr_reader :name
10
+
11
+ def initialize(aName)
12
+ @name = validated_name(aName)
13
+ end
14
+
15
+ private
16
+
17
+ def validated_name(aName)
18
+ aName
19
+ end
20
+ end # class
21
+ end # module
22
+ end # module
@@ -0,0 +1,24 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'base_arg'
4
+
5
+ module MiniKraken
6
+ module Core
7
+ # A formal reference represents the occurrence of a formal argument name in a
8
+ # goal template argument list.
9
+ class FormalRef < BaseArg
10
+ # @return [String]
11
+ attr_reader :name
12
+
13
+ def initialize(aName)
14
+ @name = validated_name(aName)
15
+ end
16
+
17
+ private
18
+
19
+ def validated_name(aName)
20
+ aName
21
+ end
22
+ end # class
23
+ end # module
24
+ end # module
@@ -1,10 +1,12 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require_relative 'base_arg'
4
+
3
5
  module MiniKraken
4
6
  module Core
5
- # The generalization of any iem that can be
6
- # passed as arugement to a goal.
7
- class GoalArg
7
+ # The generalization of any item that can be
8
+ # passed as arugement to a goal object
9
+ class GoalArg < BaseArg
8
10
  end # class
9
11
  end # module
10
12
  end # module
@@ -10,6 +10,19 @@ module MiniKraken
10
10
  def arity
11
11
  2
12
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
13
26
  end # class
14
27
  end # module
15
28
  end # module
@@ -0,0 +1,60 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'base_arg'
4
+
5
+ module MiniKraken
6
+ module Core
7
+ # A meta-goal that is parametrized with generic formal arguments.
8
+ # The individual goals are instantiated when the formal arguments
9
+ # are bound to goal arguments
10
+ class GoalTemplate < BaseArg
11
+ # @return [Array<BaseArg>}] Arguments of goal template.
12
+ attr_reader :args
13
+
14
+ # @return [Relation] Main relation for the goal template
15
+ attr_reader :relation
16
+
17
+ def initialize(aRelation, theArgs)
18
+ @relation = validated_relation(aRelation)
19
+ @args = validated_args(theArgs)
20
+ end
21
+
22
+ # @param formals [Array<FormalArg>] Array of formal arguments
23
+ # @param actuals [Array<GoalArg>] Array of actual arguments
24
+ # @return [Goal] instantiate a goal object given the actuals and environment
25
+ def instantiate(formals, actuals)
26
+ formals2actuals = {}
27
+ formals.each_with_index do |frml, i|
28
+ formals2actuals[frml.name] = actuals[i]
29
+ end
30
+
31
+ do_instantiate(formals2actuals)
32
+ end
33
+
34
+ private
35
+
36
+ def validated_relation(aRelation)
37
+ aRelation
38
+ end
39
+
40
+ def validated_args(theArgs)
41
+ theArgs
42
+ end
43
+
44
+ def do_instantiate(formals2actuals)
45
+ goal_args = []
46
+ args.each do |arg|
47
+ if arg.kind_of?(FormalRef)
48
+ goal_args << formals2actuals[arg.name]
49
+ elsif arg.kind_of?(GoalTemplate)
50
+ goal_args << arg.send(:do_instantiate, formals2actuals)
51
+ else
52
+ goal_args << arg
53
+ end
54
+ end
55
+
56
+ Goal.new(relation, goal_args)
57
+ end
58
+ end # class
59
+ end # module
60
+ end # module