mini_kraken 0.1.06 → 0.1.11
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/.travis.yml +5 -1
- data/CHANGELOG.md +56 -1
- data/README.md +11 -3
- data/lib/mini_kraken/core/association_walker.rb +1 -1
- data/lib/mini_kraken/core/base_arg.rb +10 -0
- data/lib/mini_kraken/core/conj2.rb +58 -65
- data/lib/mini_kraken/core/cons_cell.rb +49 -43
- data/lib/mini_kraken/core/def_relation.rb +49 -0
- data/lib/mini_kraken/core/disj2.rb +72 -0
- data/lib/mini_kraken/core/duck_fiber.rb +1 -1
- data/lib/mini_kraken/core/environment.rb +1 -1
- data/lib/mini_kraken/core/equals.rb +134 -126
- data/lib/mini_kraken/core/fail.rb +18 -14
- data/lib/mini_kraken/core/formal_arg.rb +22 -0
- data/lib/mini_kraken/core/formal_ref.rb +24 -0
- data/lib/mini_kraken/core/goal_arg.rb +5 -3
- data/lib/mini_kraken/core/goal_relation.rb +13 -0
- data/lib/mini_kraken/core/goal_template.rb +60 -0
- data/lib/mini_kraken/core/k_boolean.rb +31 -0
- data/lib/mini_kraken/core/outcome.rb +40 -24
- data/lib/mini_kraken/core/succeed.rb +17 -13
- data/lib/mini_kraken/core/vocabulary.rb +37 -17
- data/lib/mini_kraken/glue/fresh_env.rb +45 -3
- data/lib/mini_kraken/glue/run_star_expression.rb +45 -19
- data/lib/mini_kraken/version.rb +1 -1
- data/spec/core/conj2_spec.rb +8 -9
- data/spec/core/cons_cell_spec.rb +8 -0
- data/spec/core/def_relation_spec.rb +96 -0
- data/spec/core/disj2_spec.rb +99 -0
- data/spec/core/duck_fiber_spec.rb +12 -1
- data/spec/core/equals_spec.rb +3 -3
- data/spec/core/goal_template_spec.rb +74 -0
- data/spec/core/k_boolean_spec.rb +107 -0
- data/spec/core/outcome_spec.rb +48 -0
- data/spec/core/vocabulary_spec.rb +11 -5
- data/spec/glue/fresh_env_spec.rb +27 -1
- data/spec/glue/run_star_expression_spec.rb +538 -70
- data/spec/mini_kraken_spec.rb +2 -0
- data/spec/spec_helper.rb +0 -1
- data/spec/support/factory_methods.rb +17 -1
- metadata +19 -2
@@ -0,0 +1,31 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'atomic_term'
|
4
|
+
|
5
|
+
module MiniKraken
|
6
|
+
module Core
|
7
|
+
# A specialized atomic term that represents an boolean (true/false) value.
|
8
|
+
# in MiniKraken
|
9
|
+
class KBoolean < AtomicTerm
|
10
|
+
# @param aValue [Boolean, Symbol] Ruby representation of boolean value
|
11
|
+
def initialize(aValue)
|
12
|
+
super(validated_value(aValue))
|
13
|
+
end
|
14
|
+
|
15
|
+
private
|
16
|
+
|
17
|
+
def validated_value(aValue)
|
18
|
+
case aValue
|
19
|
+
when true, false
|
20
|
+
aValue
|
21
|
+
when :"#t", '#t'
|
22
|
+
true
|
23
|
+
when :"#f", '#f'
|
24
|
+
false
|
25
|
+
else
|
26
|
+
raise StandardError, "Invalid boolean literal '#{aValue}'"
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end # class
|
30
|
+
end # module
|
31
|
+
end # module
|
@@ -2,36 +2,52 @@
|
|
2
2
|
|
3
3
|
require_relative 'vocabulary'
|
4
4
|
|
5
|
-
|
6
|
-
module
|
7
|
-
|
8
|
-
|
5
|
+
unless MiniKraken::Core.constants(false).include? :Outcome
|
6
|
+
module MiniKraken
|
7
|
+
module Core
|
8
|
+
class Outcome
|
9
|
+
include Vocabulary # Use mix-in module
|
10
|
+
|
11
|
+
# @return [Symbol] One of: :"#s" (success), :"#u" (failure)
|
12
|
+
attr_reader :resultant
|
13
|
+
|
14
|
+
def initialize(aResult, aParent = nil)
|
15
|
+
init_vocabulary(aParent)
|
16
|
+
@resultant = aResult
|
17
|
+
end
|
9
18
|
|
10
|
-
|
11
|
-
|
19
|
+
def self.failure(aParent = nil)
|
20
|
+
new(:"#u", aParent)
|
21
|
+
end
|
12
22
|
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
end
|
23
|
+
def self.success(aParent = nil)
|
24
|
+
new(:"#s", aParent)
|
25
|
+
end
|
17
26
|
|
18
|
-
|
19
|
-
|
20
|
-
|
27
|
+
def successful?
|
28
|
+
resultant == :"#s"
|
29
|
+
end
|
21
30
|
|
22
|
-
|
23
|
-
|
31
|
+
def ==(other)
|
32
|
+
are_equal = false
|
24
33
|
|
25
|
-
|
26
|
-
|
27
|
-
|
34
|
+
if resultant == other.resultant && parent == other.parent &&
|
35
|
+
associations == other.associations
|
36
|
+
are_equal = true
|
37
|
+
end
|
38
|
+
|
39
|
+
are_equal
|
28
40
|
end
|
29
41
|
|
30
|
-
|
31
|
-
|
32
|
-
|
42
|
+
protected
|
43
|
+
|
44
|
+
def introspect
|
45
|
+
", @resultant=#{resultant}"
|
46
|
+
end
|
47
|
+
end # class
|
33
48
|
|
34
|
-
|
35
|
-
|
49
|
+
Failure = Outcome.new(:"#u")
|
50
|
+
BasicSuccess = Outcome.new(:"#s")
|
51
|
+
end # module
|
36
52
|
end # module
|
37
|
-
end #
|
53
|
+
end # defined
|
@@ -4,19 +4,23 @@ require 'singleton'
|
|
4
4
|
require_relative 'duck_fiber'
|
5
5
|
require_relative 'nullary_relation'
|
6
6
|
|
7
|
-
|
8
|
-
module
|
9
|
-
|
10
|
-
|
11
|
-
|
7
|
+
unless MiniKraken::Core.constants(false).include? :Succeed
|
8
|
+
module MiniKraken
|
9
|
+
module Core
|
10
|
+
# A nullary relation that unconditionally always fails.
|
11
|
+
class Succeed < NullaryRelation
|
12
|
+
include Singleton
|
12
13
|
|
13
|
-
|
14
|
-
|
15
|
-
|
14
|
+
def initialize
|
15
|
+
super('succeed', '#s')
|
16
|
+
end
|
16
17
|
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
18
|
+
def solver_for(_actuals, _env)
|
19
|
+
DuckFiber.new(:success)
|
20
|
+
end
|
21
|
+
end # class
|
22
|
+
|
23
|
+
Succeed.instance.freeze
|
24
|
+
end # module
|
21
25
|
end # module
|
22
|
-
end #
|
26
|
+
end # unless
|
@@ -23,26 +23,28 @@ module MiniKraken
|
|
23
23
|
@rankings = {} unless aParent
|
24
24
|
end
|
25
25
|
|
26
|
-
# Return a
|
26
|
+
# Return a Enumerator object that can iterate over this vocabulary and
|
27
27
|
# all its direct and indirect parent(s).
|
28
|
-
# @return [
|
28
|
+
# @return [Enumerator<Vocabulary, NilClass>]
|
29
29
|
def ancestor_walker
|
30
|
-
|
30
|
+
unless @ancestors # Not yet in cache?...
|
31
|
+
@ancestors = []
|
31
32
|
relative = self
|
32
33
|
while relative
|
33
|
-
|
34
|
+
@ancestors << relative
|
34
35
|
relative = relative.parent
|
35
36
|
end
|
36
|
-
|
37
|
-
Fiber.yield nil # nil marks end of iteration...
|
37
|
+
@ancestors << nil # nil marks end of iteration...
|
38
38
|
end
|
39
|
+
|
40
|
+
@ancestors.to_enum
|
39
41
|
end
|
40
42
|
|
41
43
|
def clear_rankings
|
42
44
|
walker = ancestor_walker
|
43
45
|
orphan = nil
|
44
46
|
loop do
|
45
|
-
orphan_temp = walker.
|
47
|
+
orphan_temp = walker.next
|
46
48
|
break unless orphan_temp
|
47
49
|
|
48
50
|
orphan = orphan_temp
|
@@ -57,7 +59,7 @@ module MiniKraken
|
|
57
59
|
walker = ancestor_walker
|
58
60
|
orphan = nil
|
59
61
|
loop do
|
60
|
-
orphan_temp = walker.
|
62
|
+
orphan_temp = walker.next
|
61
63
|
break unless orphan_temp
|
62
64
|
|
63
65
|
orphan = orphan_temp
|
@@ -65,20 +67,22 @@ module MiniKraken
|
|
65
67
|
|
66
68
|
raise StandardError unless orphan
|
67
69
|
|
70
|
+
rank = nil
|
68
71
|
if orphan.rankings.include?(aName)
|
69
|
-
orphan.rankings[aName]
|
72
|
+
rank = orphan.rankings[aName]
|
70
73
|
else
|
71
74
|
other = alternate_names.find do |a_name|
|
72
|
-
orphan.rankings.include?(a_name)
|
75
|
+
rank = orphan.rankings.include?(a_name)
|
73
76
|
end
|
74
77
|
if other
|
75
|
-
get_rank(other)
|
78
|
+
rank = get_rank(other)
|
76
79
|
else
|
77
80
|
rank = orphan.rankings.keys.size
|
78
81
|
orphan.rankings[aName] = rank
|
79
|
-
rank
|
80
82
|
end
|
81
83
|
end
|
84
|
+
|
85
|
+
rank
|
82
86
|
end
|
83
87
|
|
84
88
|
# Record an association between a variable with given user-defined name
|
@@ -182,7 +186,7 @@ module MiniKraken
|
|
182
186
|
walker = ancestor_walker
|
183
187
|
|
184
188
|
loop do
|
185
|
-
voc = walker.
|
189
|
+
voc = walker.next
|
186
190
|
break unless voc
|
187
191
|
|
188
192
|
if voc.associations.include?(old_i_name)
|
@@ -277,7 +281,7 @@ module MiniKraken
|
|
277
281
|
walker = ancestor_walker
|
278
282
|
|
279
283
|
loop do
|
280
|
-
voc = walker.
|
284
|
+
voc = walker.next
|
281
285
|
if voc
|
282
286
|
next unless voc.respond_to?(:vars) && voc.vars.include?(aName)
|
283
287
|
|
@@ -299,7 +303,7 @@ module MiniKraken
|
|
299
303
|
walker = ancestor_walker
|
300
304
|
|
301
305
|
loop do
|
302
|
-
voc = walker.
|
306
|
+
voc = walker.next
|
303
307
|
if voc
|
304
308
|
next unless voc.respond_to?(:ivars) && voc.ivars.include?(i_name)
|
305
309
|
|
@@ -335,7 +339,7 @@ module MiniKraken
|
|
335
339
|
walker = ancestor_walker
|
336
340
|
|
337
341
|
loop do
|
338
|
-
voc = walker.
|
342
|
+
voc = walker.next
|
339
343
|
break unless voc
|
340
344
|
next unless voc.respond_to?(:ivars)
|
341
345
|
|
@@ -358,7 +362,7 @@ module MiniKraken
|
|
358
362
|
walker = ancestor_walker
|
359
363
|
|
360
364
|
loop do
|
361
|
-
voc = walker.
|
365
|
+
voc = walker.next
|
362
366
|
break unless voc
|
363
367
|
next unless voc.associations.include?(i_name)
|
364
368
|
|
@@ -390,6 +394,18 @@ module MiniKraken
|
|
390
394
|
name2var(aVarName) ? true : false
|
391
395
|
end
|
392
396
|
|
397
|
+
def inspect
|
398
|
+
result = +"#<#{self.class.name}:#{object_id.to_s(16)} @parent="
|
399
|
+
if parent
|
400
|
+
result << "#<#{parent.class.name}:#{parent.object_id.to_s(16)}>"
|
401
|
+
else
|
402
|
+
result << nil
|
403
|
+
end
|
404
|
+
result << introspect
|
405
|
+
result << '>'
|
406
|
+
result
|
407
|
+
end
|
408
|
+
|
393
409
|
protected
|
394
410
|
|
395
411
|
def validated_parent(aParent)
|
@@ -416,6 +432,10 @@ module MiniKraken
|
|
416
432
|
assc
|
417
433
|
end
|
418
434
|
end
|
435
|
+
|
436
|
+
def introspect
|
437
|
+
''
|
438
|
+
end
|
419
439
|
end # class
|
420
440
|
end # module
|
421
441
|
end # module
|
@@ -1,10 +1,14 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
require_relative '../core/environment'
|
4
|
+
require_relative '../core/conj2'
|
4
5
|
require_relative '../core/variable'
|
5
6
|
|
6
7
|
module MiniKraken
|
7
8
|
module Glue
|
9
|
+
# A combination of an Environment (= a scope for one or more variables)
|
10
|
+
# and a goal. It quacks like a Goal object: when receiving the attain message,
|
11
|
+
# it attempt to achieve its given goal.
|
8
12
|
# (fresh (x) (== 'pea q))
|
9
13
|
# Introduces the new variable 'x'
|
10
14
|
# Takes a list of names and a goal-like object
|
@@ -13,11 +17,11 @@ module MiniKraken
|
|
13
17
|
# @return [Goal]
|
14
18
|
attr_reader :goal
|
15
19
|
|
16
|
-
# @param theNames [Array<String>]
|
17
|
-
# @param aGoal [Goal]
|
20
|
+
# @param theNames [Array<String>] The variable names
|
21
|
+
# @param aGoal [Goal, Array<Goal>] The goal to achieve or the conjunction of them.
|
18
22
|
def initialize(theNames, aGoal)
|
19
23
|
super()
|
20
|
-
@goal = aGoal
|
24
|
+
@goal = valid_goal(aGoal)
|
21
25
|
theNames.each { |nm| add_var(Core::Variable.new(nm)) }
|
22
26
|
end
|
23
27
|
|
@@ -28,6 +32,44 @@ module MiniKraken
|
|
28
32
|
self.parent = aParent
|
29
33
|
goal.attain(self)
|
30
34
|
end
|
35
|
+
|
36
|
+
protected
|
37
|
+
|
38
|
+
def introspect
|
39
|
+
+", @vars=[#{vars.keys.join(', ')}]"
|
40
|
+
end
|
41
|
+
|
42
|
+
private
|
43
|
+
|
44
|
+
def valid_goal(aGoal)
|
45
|
+
result = nil
|
46
|
+
|
47
|
+
case aGoal
|
48
|
+
when Core::Goal
|
49
|
+
result = aGoal
|
50
|
+
when FreshEnv
|
51
|
+
result = aGoal
|
52
|
+
when Array # an Array of Goal?..
|
53
|
+
goal_array = aGoal
|
54
|
+
loop do
|
55
|
+
conjunctions = []
|
56
|
+
goal_array.each_slice(2) do |uno_duo|
|
57
|
+
if uno_duo.size == 2
|
58
|
+
conjunctions << Core::Goal.new(Core::Conj2.instance, uno_duo)
|
59
|
+
else
|
60
|
+
conjunctions << uno_duo[0]
|
61
|
+
end
|
62
|
+
end
|
63
|
+
if conjunctions.size == 1
|
64
|
+
result = conjunctions[0]
|
65
|
+
break
|
66
|
+
end
|
67
|
+
goal_array = conjunctions
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
result
|
72
|
+
end
|
31
73
|
end # class
|
32
74
|
end # module
|
33
75
|
end # module
|
@@ -7,38 +7,64 @@ require_relative 'fresh_env'
|
|
7
7
|
module MiniKraken
|
8
8
|
module Glue
|
9
9
|
class RunStarExpression
|
10
|
+
# @return [FreshEnv] The environment in which run* variables will reside.
|
10
11
|
attr_reader :env
|
11
12
|
|
12
|
-
# @param
|
13
|
-
# @param goal [Core::Goal]
|
14
|
-
def initialize(
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
def var
|
19
|
-
env.vars.values.first
|
13
|
+
# @param var_names [String, Array<String>] One variable name or an array of names
|
14
|
+
# @param goal [Core::Goal, Array<Core::Goal>] A single goal or an array of goals to conjunct
|
15
|
+
def initialize(var_names, goal)
|
16
|
+
vnames = var_names.kind_of?(String) ? [var_names] : var_names
|
17
|
+
@env = FreshEnv.new(vnames, goal)
|
20
18
|
end
|
21
19
|
|
22
20
|
def run
|
23
|
-
result =
|
21
|
+
result = []
|
24
22
|
solver = env.goal.attain(env)
|
25
23
|
# require 'debug'
|
26
24
|
loop do
|
25
|
+
env.clear
|
26
|
+
env.clear_rankings
|
27
27
|
outcome = solver.resume
|
28
28
|
break if outcome.nil?
|
29
29
|
|
30
|
-
env.
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
30
|
+
env.propagate(outcome) if result.empty? && outcome.successful?
|
31
|
+
result << build_solution(outcome)
|
32
|
+
end
|
33
|
+
|
34
|
+
format_solutions(result)
|
35
|
+
end
|
36
|
+
|
37
|
+
private
|
38
|
+
|
39
|
+
# @return [Array] A vector of assignment for each variable
|
40
|
+
def build_solution(outcome)
|
41
|
+
sol = env.vars.values.map do |var|
|
42
|
+
outcome.successful? ? var.quote(outcome) : nil
|
43
|
+
end
|
44
|
+
|
45
|
+
sol
|
46
|
+
end
|
47
|
+
|
48
|
+
# Transform the solutions into sequence of conscells.
|
49
|
+
# @param solutions [Array<Array>] An array of solution.
|
50
|
+
# A solution is in itself an array of bindings (one per variable)
|
51
|
+
def format_solutions(solutions)
|
52
|
+
solutions_as_list = solutions.map { |sol| arr2list(sol, true) }
|
53
|
+
arr2list(solutions_as_list, false)
|
54
|
+
end
|
55
|
+
|
56
|
+
# Utility method. Transform an array into a ConsCell-based list.
|
57
|
+
# @param anArray [Array]
|
58
|
+
# @param simplify [Boolean]
|
59
|
+
def arr2list(anArray, simplify)
|
60
|
+
return anArray[0] if anArray.size == 1 && simplify
|
61
|
+
|
62
|
+
new_tail = nil
|
63
|
+
anArray.reverse_each do |elem|
|
64
|
+
new_tail = Core::ConsCell.new(elem, new_tail)
|
39
65
|
end
|
40
66
|
|
41
|
-
|
67
|
+
new_tail
|
42
68
|
end
|
43
69
|
end # class
|
44
70
|
end # module
|
data/lib/mini_kraken/version.rb
CHANGED