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,15 @@
1
+ require_relative 'atomic_term'
2
+
3
+ module MiniKraken
4
+ module Core
5
+ # A specialized atomic term that represents an integer value.
6
+ # in MiniKraken
7
+ class KInteger < AtomicTerm
8
+
9
+ # @param aValue [Integer] Ruby representation of integer value
10
+ def initialize(aValue)
11
+ super(aValue)
12
+ end
13
+ end # class
14
+ end # module
15
+ end # module
@@ -0,0 +1,15 @@
1
+ require_relative 'atomic_term'
2
+
3
+ module MiniKraken
4
+ module Core
5
+ # A specialized atomic term that represents a symbolic value.
6
+ # in MiniKraken
7
+ class KSymbol < AtomicTerm
8
+
9
+ # @param aValue [Symbol] Ruby representation of symbol value
10
+ def initialize(aValue)
11
+ super(aValue)
12
+ end
13
+ end # class
14
+ end # module
15
+ end # module
@@ -3,9 +3,17 @@ require_relative 'relation'
3
3
  module MiniKraken
4
4
  module Core
5
5
  class NullaryRelation < Relation
6
- def initialize(aName)
7
- super(aName)
8
- @tuple.freeze
6
+ # @param aName [String] Name of the relation.
7
+ # @param alternateName [String, NilClass] Alternative name (optional).
8
+ def initialize(aName, alternateName = nil)
9
+ super(aName, alternateName)
10
+ freeze
11
+ end
12
+
13
+ # Number of arguments for the relation.
14
+ # @return [Integer]
15
+ def arity
16
+ 0
9
17
  end
10
18
  end # class
11
19
  end # module
@@ -0,0 +1,35 @@
1
+ require_relative 'vocabulary'
2
+
3
+ module MiniKraken
4
+ module Core
5
+ class Outcome
6
+ include Vocabulary # Use mix-in module
7
+
8
+ # @return [Symbol] One of: :"#s" (success), :"#u" (failure)
9
+ attr_reader :resultant
10
+
11
+ def initialize(aResult, aParent = nil)
12
+ init_vocabulary(aParent)
13
+ @resultant = aResult
14
+ end
15
+
16
+ def successful?
17
+ self.resultant == :"#s"
18
+ end
19
+
20
+ def ==(other)
21
+ are_equal = false
22
+
23
+ if resultant == other.resultant && parent == other.parent &&
24
+ associations == other.associations
25
+ are_equal = true
26
+ end
27
+
28
+ are_equal
29
+ end
30
+ end # class
31
+
32
+ Failure = Outcome.new(:"#u")
33
+ BasicSuccess = Outcome.new(:"#s")
34
+ end # module
35
+ end # module
@@ -1,16 +1,43 @@
1
1
  module MiniKraken
2
2
  module Core
3
3
  class Relation
4
+ # @return [String] Name of the relation.
4
5
  attr_reader :name
5
- attr_reader :tuple
6
6
 
7
- def initialize(aName)
7
+ # @return [String, NilClass] Optional alternative name of the relation.
8
+ attr_reader :alt_name
9
+
10
+ # @param aName [String] Name of the relation.
11
+ # @param alternateName [String, NilClass] Alternative name (optional).
12
+ def initialize(aName, alternateName = nil)
8
13
  @name = aName
9
- @tuple = []
14
+ @alt_name = alternateName
10
15
  end
11
16
 
17
+ # Number of arguments for the relation.
18
+ # @return [Integer]
12
19
  def arity
13
- tuple.size
20
+ raise NotImplementedError
21
+ end
22
+
23
+ # Attempt to achieve the goal for a given context (environment)
24
+ # @param anEnv [Environment] The context in which the goal take place.
25
+ # @return [Fiber<Outcome>] A Fiber object that will generate the results.
26
+ # def solve(args, anEnv)
27
+ # Fiber instance responds to resume(*args) message
28
+ # If too much resume calls => FiberError: dead fiber called message.
29
+
30
+ # Fiber.new do |first_yield_arg| do
31
+ # begin
32
+ # result = relation.solve(actuals, anEnv)
33
+ # Fiber.yield result
34
+ # while result.success?
35
+
36
+ nil
37
+ # end
38
+
39
+ def inspect
40
+ alt_name ? alt_name : name
14
41
  end
15
42
  end # class
16
43
  end # module
@@ -1,8 +1,20 @@
1
+ require 'singleton'
2
+ require_relative 'duck_fiber'
1
3
  require_relative 'nullary_relation'
2
4
 
3
5
  module MiniKraken
4
6
  module Core
7
+ # A nullary relation that unconditionally always fails.
5
8
  class Succeed < NullaryRelation
9
+ include Singleton
10
+
11
+ def initialize
12
+ super('succeed', '#s')
13
+ end
14
+
15
+ def solver_for(_actuals, _env)
16
+ DuckFiber.new(:success)
17
+ end
6
18
  end # class
7
19
  end # module
8
- end # module
20
+ end # module
@@ -0,0 +1,7 @@
1
+ module MiniKraken
2
+ module Core
3
+ # The generalization of data value in MiniKraken
4
+ class Term
5
+ end # class
6
+ end # module
7
+ end # module
@@ -1,11 +1,53 @@
1
+ require_relative 'any_value'
2
+ require_relative 'vocabulary'
3
+
1
4
  module MiniKraken
2
5
  module Core
6
+ # Representation of a MiniKraken variable.
7
+ # It is a named slot that can be associated with one value.
3
8
  class Variable
9
+ # @return [String] Name of the variable
4
10
  attr_reader :name
5
-
11
+
12
+ # @param aName [String] The name of the variable
6
13
  def initialize(aName)
7
- @name = aName
14
+ @name = valid_name(aName)
15
+ end
16
+
17
+ def fresh?(anEnvironment)
18
+ anEnvironment.fresh?(self)
19
+ end
20
+
21
+ # @param env [Environment]
22
+ # @return [Freshness]
23
+ def freshness(env)
24
+ freshness = env.freshness_ref(self)
25
+ end
26
+
27
+ def ground?(anEnvironment)
28
+ !fresh?(anEnvironment)
29
+ end
30
+
31
+ def quote(anEnvironment)
32
+ # raise StandardError, "class #{anEnvironment}" unless anEnvironment.kind_of?(Vocabulary)
33
+ # freshness = anEnvironment.freshness_ref(self)
34
+ # raise StandardError, "class #{freshness}" unless freshness.kind_of?(Freshness)
35
+ # raise StandardError, "class #{freshness.associated}" if freshness.associated.kind_of?(Freshness)
36
+ # freshness.fresh? ? AnyValue.new(0) : freshness.associated.quote(anEnvironment)
37
+
38
+ val = anEnvironment.quote_ref(self)
39
+ val.nil? ? AnyValue.new(0) : val
40
+ end
41
+
42
+ private
43
+
44
+ def valid_name(aName)
45
+ if aName.empty?
46
+ raise StandardError, "Variable name may not be empty."
47
+ end
48
+
49
+ aName
8
50
  end
9
51
  end # class
10
52
  end # module
11
- end # module
53
+ end # module
@@ -0,0 +1,76 @@
1
+ require_relative 'term'
2
+ require_relative 'any_value'
3
+ require_relative 'association'
4
+
5
+ module MiniKraken
6
+ module Core
7
+ # A variable reference represents the occurrence of a variable (name) in a
8
+ # MiniKraken term.
9
+ class VariableRef < Term
10
+ # @return [String] Name of the variable
11
+ attr_reader :var_name
12
+
13
+ # @param aName [String] The name of the variable
14
+ def initialize(aName)
15
+ @var_name = valid_name(aName)
16
+ end
17
+
18
+ # @param env [Environment]
19
+ # @return [Boolean]
20
+ def fresh?(env)
21
+ env.fresh?(self)
22
+ end
23
+
24
+ # @param env [Environment]
25
+ # @return [Boolean]
26
+ def ground?(env)
27
+ !fresh?(env)
28
+ end
29
+
30
+ # @param aValue [Term]
31
+ # @param env [Environment]
32
+ def associate(aValue, env)
33
+ assoc = Association.new(var_name, aValue)
34
+ env.add_assoc(assoc)
35
+ end
36
+
37
+ # @param env [Environment]
38
+ # @return [Array<Term>]
39
+ def values(env)
40
+ env[var_name].map(&:value)
41
+ end
42
+
43
+ # @param env [Environment]
44
+ # @return [Term, NilClass]
45
+ def value(env)
46
+ freshness = env.freshness_ref(self)
47
+ freshness.associated
48
+ end
49
+
50
+ # @param env [Environment]
51
+ # @return [Freshness]
52
+ def freshness(env)
53
+ freshness = env.freshness_ref(self)
54
+ end
55
+
56
+
57
+ # @param env [Environment]
58
+ def quote(env)
59
+ val = env.quote_ref(self)
60
+ val.nil? ? AnyValue.new(0) : val
61
+ end
62
+
63
+ private
64
+
65
+ def valid_name(aName)
66
+ if aName.empty?
67
+ raise StandardError, "Variable name may not be empty."
68
+ end
69
+
70
+ aName
71
+ end
72
+
73
+
74
+ end # class
75
+ end # module
76
+ end # module
@@ -0,0 +1,161 @@
1
+ require_relative 'association_walker'
2
+
3
+ module MiniKraken
4
+ module Core
5
+ module Vocabulary
6
+ # @return [Environment] Parent environment to this one.
7
+ attr_accessor :parent
8
+
9
+ # @return [Hash] Pairs of the kind {String => Array[Association]}
10
+ attr_reader :associations
11
+
12
+ # @param aParent [Environment, NilClass] Parent environment to this one.
13
+ def init_vocabulary(aParent = nil)
14
+ @parent = validated_parent(aParent)
15
+ @associations = {}
16
+ end
17
+
18
+ # Return a Fiber object that can iterate over this vocabulary and
19
+ # all its direct and indirect parent(s).
20
+ # @return [Fiber<Vocabulary, NilClass>]
21
+ def ancestor_walker
22
+ Fiber.new do
23
+ relative = self
24
+ while relative do
25
+ Fiber.yield relative
26
+ relative = relative.parent
27
+ end
28
+
29
+ Fiber.yield nil # nil marks end of iteration...
30
+ end
31
+ end
32
+
33
+ # Record an association between a variable with given name and a term.
34
+ # @param anAssociation [Association]
35
+ def add_assoc(anAssociation)
36
+ name = anAssociation.var_name
37
+ unless include?(name)
38
+ err_msg = "Unknown variable '#{name}'."
39
+ raise StandardError, err_msg
40
+ end
41
+ found_assocs = associations[name]
42
+ if found_assocs
43
+ found_assocs << anAssociation
44
+ else
45
+ associations[name] = [anAssociation]
46
+ end
47
+ end
48
+
49
+ # Handler for the event: an outcome has been produced.
50
+ # Can be overridden in other to propagate associations from child
51
+ # @param _descendent [Outcome]
52
+ def propagate(_descendent)
53
+ #Do nothing...
54
+ end
55
+
56
+ # Remove all the associations.
57
+ def clear
58
+ associations.clear
59
+ end
60
+
61
+ # Merge the associations from another vocabulary-like object.
62
+ # @param another [Vocabulary]
63
+ def merge(another)
64
+ another.associations.each_pair do |_name, assocs|
65
+ assocs.each { |a| add_assoc(a) }
66
+ end
67
+ end
68
+
69
+ # @param var [Variable, VariableRef] the variable to check.
70
+ # @return [Boolean]
71
+ def fresh?(var)
72
+ ground_term = ground_value(var)
73
+ ground_term.nil? ? true : false
74
+ end
75
+
76
+ # @param var [Variable, VariableRef] variable for which the value to retrieve
77
+ # @return [Term, NilClase]
78
+ def ground_value(var)
79
+ name = var.respond_to?(:var_name) ? var.var_name : var.name
80
+
81
+ walker = AssociationWalker.new
82
+ walker.find_ground(name, self)
83
+ end
84
+
85
+ # @param var [CompositeTerm] the composite term to check.
86
+ # @return [Boolean]
87
+ def fresh_value?(val)
88
+ walker = AssociationWalker.new
89
+ ground_term = walker.walk_value(val, self)
90
+ ground_term.nil? ? true : false
91
+ end
92
+
93
+ # A composite term is fresh when all its members are nil or all non-nil members
94
+ # are all fresh
95
+ # A composite term is bound when it is not fresh and not ground
96
+ # A composite term is a ground term when all its non-nil members are ground.
97
+ # @param aComposite [CompositeTerm]
98
+ # @return [Freshness]
99
+ def freshness_composite(aComposite)
100
+ walker = AssociationWalker.new
101
+ walker.freshness_composite(aComposite)
102
+ end
103
+
104
+ # Determine whether the reference points to a fresh, bound or ground term.
105
+ # @param aVariableRef [VariableRef]
106
+ # @return [Freshness]
107
+ def freshness_ref(aVariableRef)
108
+ walker = AssociationWalker.new
109
+ walker.determine_freshness(aVariableRef, self)
110
+ end
111
+
112
+ # @param aVariableRef [VariableRef]
113
+ # @return [Term, NilClass]
114
+ def quote_ref(aVariableRef)
115
+ walker = AssociationWalker.new
116
+ walker.quote_term(aVariableRef, self)
117
+ end
118
+
119
+ # @param aName [String]
120
+ # @return [Array<Association>]
121
+ def [](aName)
122
+ assoc_arr = associations[aName]
123
+ assoc_arr = [] if assoc_arr.nil?
124
+
125
+ assoc_arr.concat(parent[aName]) if parent
126
+ assoc_arr
127
+ end
128
+
129
+ # Check that a variable with given name is defined in this vocabulary
130
+ # of one of its ancestor.
131
+ # @return [Boolean]
132
+ def include?(aVarName)
133
+ var_found = false
134
+ walker = ancestor_walker
135
+ loop do
136
+ voc = walker.resume
137
+ if voc
138
+ next unless voc.respond_to?(:vars) && voc.vars.include?(aVarName)
139
+ var_found = true
140
+ end
141
+
142
+ break
143
+ end
144
+
145
+ var_found
146
+ end
147
+
148
+ protected
149
+
150
+ def validated_parent(aParent)
151
+ if aParent
152
+ unless aParent.kind_of?(Vocabulary)
153
+ raise StandardError, "Invalid parent type #{aParent.class}"
154
+ end
155
+ end
156
+
157
+ aParent
158
+ end
159
+ end # class
160
+ end # module
161
+ end # module