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.
- 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
|