mini_kraken 0.1.01 → 0.1.02

Sign up to get free protection for your applications and to get access to all the features.
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