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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +3 -0
- data/README.md +0 -3
- data/lib/mini_kraken/core/any_value.rb +29 -0
- data/lib/mini_kraken/core/association.rb +21 -0
- data/lib/mini_kraken/core/association_walker.rb +179 -0
- data/lib/mini_kraken/core/atomic_term.rb +64 -0
- data/lib/mini_kraken/core/binary_relation.rb +61 -0
- data/lib/mini_kraken/core/composite_term.rb +54 -0
- data/lib/mini_kraken/core/cons_cell.rb +44 -0
- data/lib/mini_kraken/core/duck_fiber.rb +44 -0
- data/lib/mini_kraken/core/environment.rb +59 -0
- data/lib/mini_kraken/core/equals.rb +216 -0
- data/lib/mini_kraken/core/fail.rb +8 -5
- data/lib/mini_kraken/core/freshness.rb +42 -0
- data/lib/mini_kraken/core/goal.rb +31 -6
- data/lib/mini_kraken/core/k_integer.rb +15 -0
- data/lib/mini_kraken/core/k_symbol.rb +15 -0
- data/lib/mini_kraken/core/nullary_relation.rb +11 -3
- data/lib/mini_kraken/core/outcome.rb +35 -0
- data/lib/mini_kraken/core/relation.rb +31 -4
- data/lib/mini_kraken/core/succeed.rb +13 -1
- data/lib/mini_kraken/core/term.rb +7 -0
- data/lib/mini_kraken/core/variable.rb +45 -3
- data/lib/mini_kraken/core/variable_ref.rb +76 -0
- data/lib/mini_kraken/core/vocabulary.rb +161 -0
- data/lib/mini_kraken/glue/fresh_env.rb +31 -0
- data/lib/mini_kraken/glue/run_star_expression.rb +43 -0
- data/lib/mini_kraken/version.rb +1 -1
- data/spec/core/association_spec.rb +38 -0
- data/spec/core/association_walker_spec.rb +191 -0
- data/spec/core/cons_cell_spec.rb +63 -0
- data/spec/core/duck_fiber_spec.rb +62 -0
- data/spec/core/environment_spec.rb +154 -0
- data/spec/core/equals_spec.rb +289 -0
- data/spec/core/fail_spec.rb +16 -0
- data/spec/core/goal_spec.rb +36 -10
- data/spec/core/k_symbol_spec.rb +72 -0
- data/spec/core/succeed_spec.rb +43 -0
- data/spec/core/variable_ref_spec.rb +31 -0
- data/spec/core/variable_spec.rb +11 -3
- data/spec/core/vocabulary_spec.rb +188 -0
- data/spec/glue/fresh_env_spec.rb +36 -0
- data/spec/glue/run_star_expression_spec.rb +247 -0
- data/spec/support/factory_methods.rb +54 -0
- metadata +46 -13
- data/lib/mini_kraken/core/facade.rb +0 -45
- data/lib/mini_kraken/core/formal_arg.rb +0 -6
- data/lib/mini_kraken/core/publisher.rb +0 -27
- data/lib/mini_kraken/core/run_star_expression.rb +0 -34
- data/lib/mini_kraken/dsl/kraken_dsl.rb +0 -12
- data/spec/core/facade_spec.rb +0 -38
- data/spec/core/run_star_expression_spec.rb +0 -43
- 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
|
-
|
7
|
-
|
8
|
-
|
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
|
-
|
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
|
-
@
|
14
|
+
@alt_name = alternateName
|
10
15
|
end
|
11
16
|
|
17
|
+
# Number of arguments for the relation.
|
18
|
+
# @return [Integer]
|
12
19
|
def arity
|
13
|
-
|
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
|
@@ -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
|