mini_kraken 0.1.01 → 0.1.02

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 (54) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +3 -0
  3. data/README.md +0 -3
  4. data/lib/mini_kraken/core/any_value.rb +29 -0
  5. data/lib/mini_kraken/core/association.rb +21 -0
  6. data/lib/mini_kraken/core/association_walker.rb +179 -0
  7. data/lib/mini_kraken/core/atomic_term.rb +64 -0
  8. data/lib/mini_kraken/core/binary_relation.rb +61 -0
  9. data/lib/mini_kraken/core/composite_term.rb +54 -0
  10. data/lib/mini_kraken/core/cons_cell.rb +44 -0
  11. data/lib/mini_kraken/core/duck_fiber.rb +44 -0
  12. data/lib/mini_kraken/core/environment.rb +59 -0
  13. data/lib/mini_kraken/core/equals.rb +216 -0
  14. data/lib/mini_kraken/core/fail.rb +8 -5
  15. data/lib/mini_kraken/core/freshness.rb +42 -0
  16. data/lib/mini_kraken/core/goal.rb +31 -6
  17. data/lib/mini_kraken/core/k_integer.rb +15 -0
  18. data/lib/mini_kraken/core/k_symbol.rb +15 -0
  19. data/lib/mini_kraken/core/nullary_relation.rb +11 -3
  20. data/lib/mini_kraken/core/outcome.rb +35 -0
  21. data/lib/mini_kraken/core/relation.rb +31 -4
  22. data/lib/mini_kraken/core/succeed.rb +13 -1
  23. data/lib/mini_kraken/core/term.rb +7 -0
  24. data/lib/mini_kraken/core/variable.rb +45 -3
  25. data/lib/mini_kraken/core/variable_ref.rb +76 -0
  26. data/lib/mini_kraken/core/vocabulary.rb +161 -0
  27. data/lib/mini_kraken/glue/fresh_env.rb +31 -0
  28. data/lib/mini_kraken/glue/run_star_expression.rb +43 -0
  29. data/lib/mini_kraken/version.rb +1 -1
  30. data/spec/core/association_spec.rb +38 -0
  31. data/spec/core/association_walker_spec.rb +191 -0
  32. data/spec/core/cons_cell_spec.rb +63 -0
  33. data/spec/core/duck_fiber_spec.rb +62 -0
  34. data/spec/core/environment_spec.rb +154 -0
  35. data/spec/core/equals_spec.rb +289 -0
  36. data/spec/core/fail_spec.rb +16 -0
  37. data/spec/core/goal_spec.rb +36 -10
  38. data/spec/core/k_symbol_spec.rb +72 -0
  39. data/spec/core/succeed_spec.rb +43 -0
  40. data/spec/core/variable_ref_spec.rb +31 -0
  41. data/spec/core/variable_spec.rb +11 -3
  42. data/spec/core/vocabulary_spec.rb +188 -0
  43. data/spec/glue/fresh_env_spec.rb +36 -0
  44. data/spec/glue/run_star_expression_spec.rb +247 -0
  45. data/spec/support/factory_methods.rb +54 -0
  46. metadata +46 -13
  47. data/lib/mini_kraken/core/facade.rb +0 -45
  48. data/lib/mini_kraken/core/formal_arg.rb +0 -6
  49. data/lib/mini_kraken/core/publisher.rb +0 -27
  50. data/lib/mini_kraken/core/run_star_expression.rb +0 -34
  51. data/lib/mini_kraken/dsl/kraken_dsl.rb +0 -12
  52. data/spec/core/facade_spec.rb +0 -38
  53. data/spec/core/run_star_expression_spec.rb +0 -43
  54. data/spec/dsl/kraken_dsl_spec.rb +0 -31
@@ -0,0 +1,44 @@
1
+ require_relative 'composite_term'
2
+
3
+ module MiniKraken
4
+ module Core
5
+ class ConsCell < CompositeTerm
6
+ attr_reader :car
7
+ attr_reader :cdr
8
+
9
+ def initialize(obj1, obj2 = nil)
10
+ @car = obj1
11
+ @cdr = obj2
12
+ end
13
+
14
+ def children
15
+ [car, cdr]
16
+ end
17
+
18
+ # Return true if it is an empty list, otherwise false.
19
+ # A list is empty, when both car and cdr fields are nil.
20
+ def null?
21
+ car.nil? && cdr.nil?
22
+ end
23
+
24
+ def ==(other)
25
+ return false unless other.respond_to?(:car)
26
+ (car == other.car) && (cdr == other.cdr)
27
+ end
28
+
29
+ def eql?(other)
30
+ (self.class == other.class) && car.eql?(other.car) && cdr.eql?(other.cdr)
31
+ end
32
+
33
+ def quote(anEnv)
34
+ return self if null?
35
+ new_car = car.nil? ? nil : car.quote(anEnv)
36
+ new_cdr = cdr.nil? ? nil : cdr.quote(anEnv)
37
+ ConsCell.new(new_car, new_cdr)
38
+ end
39
+ end # class
40
+
41
+ # Constant representing the null (empty) list.
42
+ NullList = ConsCell.new(nil, nil).freeze
43
+ end # module
44
+ end # module
@@ -0,0 +1,44 @@
1
+ require_relative 'outcome'
2
+
3
+ module MiniKraken
4
+ module Core
5
+ # A mock class that mimicks the behavior of a Fiber instance.
6
+ class DuckFiber
7
+ # @return [Outcome] The sole outcome to yield.
8
+ attr_reader :outcome
9
+
10
+ # @return [Symbol] one of: :initial, :yielded
11
+ attr_reader :state
12
+
13
+ # @param outcomeKind [Symbol] One of: :failure, :basic_success, :custom
14
+ def initialize(outcomeKind, &customization)
15
+ @state = :initial
16
+ if outcomeKind == :custom && block_given?
17
+ @outcome = customization.call
18
+ else
19
+ @outcome = valid_outcome(outcomeKind)
20
+ end
21
+ end
22
+
23
+ def resume(*_args)
24
+ if state == :initial
25
+ @state = :yielded
26
+ return outcome
27
+ else
28
+ return nil
29
+ end
30
+ end
31
+
32
+ def valid_outcome(outcomeKind)
33
+ case outcomeKind
34
+ when :failure
35
+ Failure
36
+ when :success
37
+ BasicSuccess
38
+ else
39
+ raise StandardError, "Unknonw outcome kind #{outcomeKind}"
40
+ end
41
+ end
42
+ end # class
43
+ end # module
44
+ end # module
@@ -0,0 +1,59 @@
1
+ require_relative 'vocabulary'
2
+
3
+ module MiniKraken
4
+ module Core
5
+ class Environment
6
+ include Vocabulary # Use mix-in module
7
+
8
+ # @return [Hash] Pairs of the kind {String => Variable}
9
+ attr_reader :vars
10
+
11
+ # @param aParent [Environment, NilClass] Parent environment to this one.
12
+ def initialize(aParent = nil)
13
+ init_vocabulary(aParent)
14
+ @vars = {}
15
+ end
16
+
17
+ # @param aVariable [Variable]
18
+ def add_var(aVariable)
19
+ name = aVariable.name
20
+ if vars.include?(name)
21
+ err_msg = "Variable with name '#{name}' already exists."
22
+ raise StandardError, err_msg
23
+ end
24
+ vars[name] = aVariable
25
+ end
26
+
27
+ # Handler for the event: an outcome has been produced.
28
+ # Can be overridden in other to propagate associations from child
29
+ # @param descendent [Outcome]
30
+ def propagate(descendent)
31
+ # Rollout associations from hierarchy
32
+ walker = descendent.ancestor_walker
33
+ begin
34
+ env = walker.resume
35
+ break if env.nil?
36
+ env.do_propagate(descendent) if env.kind_of?(Environment)
37
+ end until env.equal?(self)
38
+ end
39
+
40
+ # Move associations from descendent outcome object
41
+ def do_propagate(descendent)
42
+ return unless descendent.successful?
43
+
44
+ vars.each_key do |var_name|
45
+ assocs = descendent[var_name]
46
+ assocs.each do |assoc|
47
+ own = self[var_name]
48
+ add_assoc(assoc) unless assoc.equal?(own)
49
+ end
50
+ descendent.associations.delete(var_name) unless assocs.empty?
51
+ end
52
+ end
53
+
54
+ def merge_vars(descendent)
55
+ descendent.vars.each_value { |vr| add_var(vr) }
56
+ end
57
+ end # class
58
+ end # module
59
+ end # module
@@ -0,0 +1,216 @@
1
+ require 'singleton'
2
+ require_relative 'binary_relation'
3
+ # require_relative 'any_value'
4
+ require_relative 'duck_fiber'
5
+ require_relative 'variable'
6
+ require_relative 'variable_ref'
7
+
8
+ module MiniKraken
9
+ module Core
10
+ # equals tries to unify two terms
11
+ class Equals < BinaryRelation
12
+ include Singleton
13
+
14
+ def initialize
15
+ super('equals', '==')
16
+ end
17
+
18
+ =begin
19
+ double data flow:
20
+ A goal has actual arguments
21
+ When its corresponding relation is invoked
22
+ this one will return two things: (a success, the bindings/constraints
23
+ resulting from the relation)
24
+ the bindings/constraints can be undone if enclosing fails, otherwise
25
+ the bindings/constraints are rolled up.
26
+ =end
27
+ =begin
28
+ def unify(aGoal, vars)
29
+ arg1, arg2 = aGoal.actuals
30
+ arg_kinds = [arg1.kind_of?(VariableRef), arg2.kind_of?(VariableRef)]
31
+ case arg_kinds
32
+ when [false, false]
33
+ if arg1.eql?(arg2)
34
+ result = Outcome.new(:"#s", [])
35
+ else
36
+ result = Failure
37
+ end
38
+ when [false, true]
39
+ if arg2.fresh?
40
+ arg2.bind_to(arg1)
41
+ result = Outcome.new(:"#s", [arg1])
42
+ else
43
+ if arg2.value.eql?(arg1)
44
+ result = Outcome.new(:"#s", [arg1])
45
+ else
46
+ result = Failure
47
+ end
48
+ end
49
+ when [true, false]
50
+ if arg1.fresh?
51
+ arg1.bind_to(arg2)
52
+ result = Outcome.new(:"#s", [arg2])
53
+ else
54
+ if arg1.value.eql?(arg2)
55
+ result = Outcome.new(:"#s", [arg2])
56
+ else
57
+ result = Failure
58
+ end
59
+ end
60
+ when [true, true]
61
+ case [arg1.fresh?, arg2.fresh?]
62
+ when [false, false]
63
+ if arg1.value == arg2.value
64
+ result = Outcome.new(:"#s", [arg1.value])
65
+ else
66
+ result = Failure
67
+ end
68
+ when [false, true]
69
+ arg2.bind_to(arg1)
70
+ result = Outcome.new(:"#s", arg1.value)
71
+ when [true, false]
72
+ arg1.bind_to(arg2)
73
+ result = Outcome.new(:"#s", arg2.value)
74
+ when [true, true]
75
+ if arg1.variable.name == arg2.variable.name
76
+ result = Outcome.new(:"#s", [])
77
+ else
78
+ # TODO: add constraints
79
+ result = Outcome.new(:"#s", [])
80
+ end
81
+ end
82
+ end
83
+
84
+ result
85
+ end
86
+ =end
87
+
88
+ def solver_for(actuals, anEnv)
89
+ arg1, arg2 = *actuals
90
+ DuckFiber.new(:custom) { unification(arg1, arg2, anEnv) }
91
+ end
92
+
93
+ def unification(arg1, arg2, anEnv)
94
+ arg1_nil = arg1.nil?
95
+ arg2_nil = arg2.nil?
96
+ if arg1_nil || arg2_nil
97
+ if arg1_nil && arg2_nil
98
+ result = Outcome.new(:"#s", anEnv)
99
+ else
100
+ result = Failure
101
+ end
102
+ return result
103
+ end
104
+ new_arg1, new_arg2 = commute_cond(arg1, arg2, anEnv)
105
+ result = do_unification(new_arg1, new_arg2, anEnv)
106
+ # anEnv.merge(result) if result.successful? && !result.association.empty?
107
+
108
+ result
109
+ end
110
+
111
+ private
112
+
113
+ # table: Unification
114
+ # | arg1 | arg2 | Criterion || Unification |
115
+ # | isa? Atomic | isa? Atomic | arg1.eq? arg2 is true || { "s", [] } |
116
+ # | isa? Atomic | isa? Atomic | arg1.eq? arg2 is false || { "u", [] } |
117
+ # | isa? CompositeTerm | isa? Atomic | dont_care || { "u", [] } |
118
+ # | isa? CompositeTerm | isa? CompositeTerm | unification(arg1.car, arg2.car) => "s" || { "s", [bindings*] } |
119
+ # | isa? CompositeTerm | isa? CompositeTerm | unification(arg1.cdr, arg2.cdr) => "u" || { "u", [] ) | |
120
+ # | isa? VariableRef | isa? Atomic | arg1.fresh? is true || { "s", [arg2] } |
121
+ # | isa? VariableRef | isa? Atomic | arg1.fresh? is false || |
122
+ # | | unification(arg1.value, arg2) => "s" || { "s", [bindings*] } |
123
+ # | | unification(arg1.value, arg2) => "u" || { "u", [] } |
124
+ # | isa? VariableRef | isa? CompositeTerm | arg1.fresh? is true || { "s", [arg2] } | # What if arg1 occurs in arg2?
125
+ # | isa? VariableRef | isa? CompositeTerm | arg1.fresh? is false || |
126
+ # | | unification(arg1.value, arg2) => "s" || { "s", [bindings*] } |
127
+ # | | unification(arg1.value, arg2) => "u" || { "u", [] } |
128
+ # | isa? VariableRef | isa? VariableRef | arg1.fresh?, arg2.fresh? => [true, true] || { "s", [arg1 <=> arg2] } |
129
+ # | isa? VariableRef | isa? VariableRef | arg1.fresh?, arg2.fresh? => [true, false] || |
130
+ # | | unification(arg1, arg2.value) => "s" || { "s", [bindings*] } |
131
+ # | | unification(arg1, arg2.value) => "u" || { "u", [] } |
132
+ # | isa? VariableRef | isa? VariableRef | arg1.fresh?, arg2.fresh? => [false, false]|| |
133
+ # | | unification(arg1, arg2.value) => "s" || { "s", [bindings*] } |
134
+ # | | unification(arg1, arg2.value) => "u" || { "u", [] }
135
+ def do_unification(arg1, arg2, anEnv)
136
+ # require 'debug'
137
+ return Outcome.new(:"#s", anEnv) if arg1.equal?(arg2)
138
+ result = Outcome.new(:"#u", anEnv) # default case
139
+
140
+ if arg1.kind_of?(AtomicTerm)
141
+ result = BasicSuccess if arg1.eql?(arg2)
142
+ elsif arg1.kind_of?(CompositeTerm)
143
+ if arg2.kind_of?(CompositeTerm) # AtomicTerm is default case => fail
144
+ result = unify_composite_terms(arg1, arg2, anEnv)
145
+ end
146
+ elsif arg1.kind_of?(VariableRef)
147
+ arg1_freshness = arg1.freshness(anEnv)
148
+ if arg2.kind_of?(AtomicTerm)
149
+ if arg1_freshness.degree == :fresh
150
+ result = Outcome.new(:"#s", anEnv)
151
+ arg1.associate(arg2, result)
152
+ else
153
+ result = Outcome.new(:"#s", anEnv) if arg1.value(anEnv).eql?(arg2)
154
+ end
155
+ elsif arg2.kind_of?(CompositeTerm)
156
+ if arg1_freshness.degree == :fresh
157
+ result = Outcome.new(:"#s", anEnv)
158
+ arg1.associate(arg2, result)
159
+ else
160
+ # Ground case...
161
+ result = unify_composite_terms(arg1_freshness.associated, arg2, anEnv)
162
+ end
163
+ elsif arg2.kind_of?(VariableRef)
164
+ freshness = [arg1.fresh?(anEnv), arg2.fresh?(anEnv)]
165
+ case freshness
166
+ when [false, false] # TODO: confirm this...
167
+ result = unification(arg1.value(anEnv), arg2.value(anEnv), anEnv)
168
+ when [true, true]
169
+ result = Outcome.new(:"#s", anEnv)
170
+ if arg1.var_name != arg2.var_name
171
+ arg1.associate(arg2, result)
172
+ arg2.associate(arg1, result)
173
+ end
174
+ else
175
+ raise StandardError, "Unsupported freshness combination #{freshness}"
176
+ end
177
+ else
178
+ arg_kinds = [arg1.class, arg2.class]
179
+ raise StandardError, "Unsupported combination #{arg_kinds}"
180
+ end
181
+ end
182
+
183
+ result
184
+ end
185
+
186
+ # @return [Freshness]
187
+ def unify_composite_terms(arg1, arg2, anEnv)
188
+ # require 'debug'
189
+ result = Outcome.new(:"#u", anEnv)
190
+ children1 = arg1.children
191
+ children2 = arg2.children
192
+
193
+ if children1.size == children2.size
194
+ i = 0
195
+ subresults = children1.map do |child1|
196
+ child2 = children2[i]
197
+ i += 1
198
+ unification(child1, child2, anEnv)
199
+ end
200
+ total_success = subresults.all?(&:successful?)
201
+ if total_success
202
+ memo = Outcome.new(:"#s", anEnv)
203
+ associations = subresults.reduce(memo) do |sub_total, outcome|
204
+ sub_total.merge(outcome)
205
+ sub_total
206
+ end
207
+ result = memo
208
+ end
209
+ end
210
+
211
+ result
212
+ end
213
+
214
+ end # class
215
+ end # module
216
+ end # module
@@ -1,17 +1,20 @@
1
1
  require 'singleton'
2
+ require_relative 'duck_fiber'
2
3
  require_relative 'nullary_relation'
3
4
 
4
5
  module MiniKraken
5
6
  module Core
7
+ # A nullary relation that unconditionally always fails.
6
8
  class Fail < NullaryRelation
7
9
  include Singleton
8
-
10
+
9
11
  def initialize
10
- super('fail')
12
+ super('fail', '#u')
11
13
  end
12
-
13
- def unify(aGoal, vars)
14
- []
14
+
15
+ # @return [DuckFiber]
16
+ def solver_for(_actuals, _env)
17
+ DuckFiber.new(:failure)
15
18
  end
16
19
  end # class
17
20
  end # module
@@ -0,0 +1,42 @@
1
+ module MiniKraken
2
+ module Core
3
+ # Freshness: fresh, bound, ground
4
+ # fresh: no association at all
5
+ # bound: associated to something that is itself not ground.
6
+ # ground: associated to something that is either an atomic, a composite with ground members,
7
+ # a variable reference to something that is itself ground.
8
+ # RS fresh == fresh or bound
9
+ # RS not fresh == ground
10
+ # RS result == fresh => any or bound => expr(any)
11
+ Freshness = Struct.new(:degree, :associated) do
12
+ def initialize(aDegree, anAssociated)
13
+ super(aDegree, valid_associated(anAssociated))
14
+ end
15
+
16
+ def fresh?
17
+ self.degree == :fresh
18
+ end
19
+
20
+ def bound?
21
+ self.degree == :bound
22
+ end
23
+
24
+ def ground?
25
+ self.degree == :ground
26
+ end
27
+
28
+ # Does this instance represent something fresh according to
29
+ # "Reasoned Schemer" book ?
30
+ def rs_fresh?
31
+ self.degree != ground
32
+ end
33
+
34
+ private
35
+
36
+ def valid_associated(anAssociated)
37
+ raise StandardError, 'Wrong argument' if anAssociated.kind_of?(self.class)
38
+ anAssociated
39
+ end
40
+ end # struct
41
+ end # module
42
+ end # module
@@ -1,17 +1,42 @@
1
+ require_relative 'environment'
2
+
1
3
  module MiniKraken
2
4
  module Core
3
5
  class Goal
6
+ # @return [Relation] The relation corresponding to this goal
4
7
  attr_reader :relation
5
8
 
9
+ # @return [Array<Term>] The actual aguments of the goal
10
+ attr_reader :actuals
11
+
12
+ # @param aRelation [Relation] The relation corresponding to this goal
13
+ # @param args [Array<Term>] The actual aguments of the goal
6
14
  def initialize(aRelation, args)
7
15
  @relation = aRelation
16
+ @actuals = validated_actuals(args)
17
+ end
18
+
19
+ # Attempt to achieve the goal for a given context (environment)
20
+ # @param anEnv [Environment] The context in which the goal take place.
21
+ # @return [Fiber<Outcome>] A Fiber object that will generate the results.
22
+ def attain(anEnv)
23
+ relation.solver_for(actuals, anEnv)
8
24
  end
9
-
10
- def attain(aPublisher, vars)
11
- aPublisher.broadcast_entry(self, vars)
12
- outcome = relation.unify(self, vars)
13
- aPublisher.broadcast_exit(self, vars, outcome)
14
- outcome
25
+
26
+ private
27
+
28
+ def validated_actuals(args)
29
+ if args.size != relation.arity
30
+ err_msg = "Goal has #{args.size} arguments, expected #{relation.arity}"
31
+ raise StandardError, err_msg
32
+ end
33
+
34
+ prefix = "Invalid goal argument "
35
+ args.each do |actl|
36
+ raise StandardError, prefix + actl.to_s unless actl.kind_of?(Term)
37
+ end
38
+
39
+ args.dup
15
40
  end
16
41
  end # class
17
42
  end # module