mini_kraken 0.1.05 → 0.1.10
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 -0
- data/README.md +5 -3
- data/lib/mini_kraken/core/composite_goal.rb +46 -0
- data/lib/mini_kraken/core/composite_term.rb +2 -0
- data/lib/mini_kraken/core/conj2.rb +79 -0
- data/lib/mini_kraken/core/cons_cell.rb +49 -43
- data/lib/mini_kraken/core/designation.rb +55 -0
- data/lib/mini_kraken/core/disj2.rb +71 -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 -132
- data/lib/mini_kraken/core/fail.rb +18 -14
- data/lib/mini_kraken/core/goal.rb +4 -2
- data/lib/mini_kraken/core/goal_arg.rb +10 -0
- data/lib/mini_kraken/core/goal_relation.rb +28 -0
- data/lib/mini_kraken/core/outcome.rb +40 -24
- data/lib/mini_kraken/core/succeed.rb +17 -13
- data/lib/mini_kraken/core/term.rb +5 -2
- data/lib/mini_kraken/core/variable.rb +3 -27
- data/lib/mini_kraken/core/variable_ref.rb +3 -28
- data/lib/mini_kraken/core/vocabulary.rb +39 -19
- data/lib/mini_kraken/glue/fresh_env.rb +45 -3
- data/lib/mini_kraken/glue/run_star_expression.rb +44 -19
- data/lib/mini_kraken/version.rb +1 -1
- data/spec/core/conj2_spec.rb +114 -0
- data/spec/core/cons_cell_spec.rb +8 -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/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 +478 -53
- data/spec/mini_kraken_spec.rb +2 -0
- data/spec/spec_helper.rb +0 -1
- data/spec/support/factory_methods.rb +16 -0
- metadata +14 -2
@@ -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
|
@@ -1,9 +1,12 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require_relative 'goal_arg'
|
4
|
+
|
3
5
|
module MiniKraken
|
4
6
|
module Core
|
5
|
-
# The generalization of data value
|
6
|
-
|
7
|
+
# The generalization of any data value that can be
|
8
|
+
# passed as arugement to a goal.
|
9
|
+
class Term < GoalArg
|
7
10
|
end # class
|
8
11
|
end # module
|
9
12
|
end # module
|
@@ -1,5 +1,6 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require_relative 'designation'
|
3
4
|
require_relative 'any_value'
|
4
5
|
require_relative 'vocabulary'
|
5
6
|
|
@@ -8,32 +9,17 @@ module MiniKraken
|
|
8
9
|
# Representation of a MiniKraken variable.
|
9
10
|
# It is a named slot that can be associated with one value.
|
10
11
|
class Variable
|
11
|
-
#
|
12
|
-
attr_reader :name
|
12
|
+
include Designation # Mixin: Acquire name attribute
|
13
13
|
|
14
14
|
# @return [String] Internal variable name used by MiniKraken
|
15
15
|
attr_accessor :i_name
|
16
16
|
|
17
17
|
# @param aName [String] The name of the variable
|
18
18
|
def initialize(aName)
|
19
|
-
|
19
|
+
init_designation(aName)
|
20
20
|
@i_name = name.dup
|
21
21
|
end
|
22
22
|
|
23
|
-
def fresh?(anEnvironment)
|
24
|
-
anEnvironment.fresh?(self)
|
25
|
-
end
|
26
|
-
|
27
|
-
# @param env [Environment]
|
28
|
-
# @return [Freshness]
|
29
|
-
def freshness(env)
|
30
|
-
env.freshness_ref(self)
|
31
|
-
end
|
32
|
-
|
33
|
-
def ground?(anEnvironment)
|
34
|
-
!fresh?(anEnvironment)
|
35
|
-
end
|
36
|
-
|
37
23
|
def fused?
|
38
24
|
name != i_name
|
39
25
|
end
|
@@ -44,16 +30,6 @@ module MiniKraken
|
|
44
30
|
val = anEnvironment.quote_ref(self)
|
45
31
|
val.nil? ? AnyValue.new(name, anEnvironment) : val
|
46
32
|
end
|
47
|
-
|
48
|
-
private
|
49
|
-
|
50
|
-
def valid_name(aName)
|
51
|
-
if aName.empty?
|
52
|
-
raise StandardError, 'Variable name may not be empty.'
|
53
|
-
end
|
54
|
-
|
55
|
-
aName
|
56
|
-
end
|
57
33
|
end # class
|
58
34
|
end # module
|
59
35
|
end # module
|
@@ -8,31 +8,12 @@ module MiniKraken
|
|
8
8
|
# A variable reference represents the occurrence of a variable (name) in a
|
9
9
|
# MiniKraken term.
|
10
10
|
class VariableRef < Term
|
11
|
-
|
12
|
-
|
11
|
+
include Designation # Mixin: Acquire name attribute
|
12
|
+
alias var_name name
|
13
13
|
|
14
14
|
# @param aName [String] The name of the variable
|
15
15
|
def initialize(aName)
|
16
|
-
|
17
|
-
end
|
18
|
-
|
19
|
-
# @param env [Environment]
|
20
|
-
# @return [Boolean]
|
21
|
-
def fresh?(env)
|
22
|
-
env.fresh?(self)
|
23
|
-
end
|
24
|
-
|
25
|
-
# @param env [Environment]
|
26
|
-
# @return [Boolean]
|
27
|
-
def bound?(env)
|
28
|
-
freshness = env.freshness_ref(self)
|
29
|
-
freshness.degree == :bound
|
30
|
-
end
|
31
|
-
|
32
|
-
# @param env [Environment]
|
33
|
-
# @return [Boolean]
|
34
|
-
def ground?(env)
|
35
|
-
!fresh?(env)
|
16
|
+
init_designation(aName)
|
36
17
|
end
|
37
18
|
|
38
19
|
# @param aValue [Term]
|
@@ -54,12 +35,6 @@ module MiniKraken
|
|
54
35
|
freshness.associated
|
55
36
|
end
|
56
37
|
|
57
|
-
# @param env [Environment]
|
58
|
-
# @return [Freshness]
|
59
|
-
def freshness(env)
|
60
|
-
env.freshness_ref(self)
|
61
|
-
end
|
62
|
-
|
63
38
|
# @param env [Environment]
|
64
39
|
def quote(env)
|
65
40
|
val = env.quote_ref(self)
|
@@ -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)
|
@@ -236,7 +240,7 @@ module MiniKraken
|
|
236
240
|
walker.find_ground(name, self)
|
237
241
|
end
|
238
242
|
|
239
|
-
# @param
|
243
|
+
# @param val [CompositeTerm] the composite term to check.
|
240
244
|
# @return [Boolean]
|
241
245
|
def fresh_value?(val)
|
242
246
|
walker = AssociationWalker.new
|
@@ -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
|
|
@@ -291,7 +295,7 @@ module MiniKraken
|
|
291
295
|
end
|
292
296
|
|
293
297
|
# Return the variable with given internal variable name.
|
294
|
-
# @param
|
298
|
+
# @param i_name [String] internal variable name
|
295
299
|
# @return [Variable]
|
296
300
|
def i_name2var(i_name)
|
297
301
|
var = nil
|
@@ -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
|
@@ -9,36 +9,61 @@ module MiniKraken
|
|
9
9
|
class RunStarExpression
|
10
10
|
attr_reader :env
|
11
11
|
|
12
|
-
# @param
|
13
|
-
# @param goal [Core::Goal]
|
14
|
-
def initialize(
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
def var
|
19
|
-
env.vars.values.first
|
12
|
+
# @param var_names [String, Array<String>] One variable name or an array of names
|
13
|
+
# @param goal [Core::Goal, Array<Core::Goal>] A single goal or an array of goals to conjunct
|
14
|
+
def initialize(var_names, goal)
|
15
|
+
vnames = var_names.kind_of?(String) ? [var_names] : var_names
|
16
|
+
@env = FreshEnv.new(vnames, goal)
|
20
17
|
end
|
21
18
|
|
22
19
|
def run
|
23
|
-
result =
|
20
|
+
result = []
|
24
21
|
solver = env.goal.attain(env)
|
25
22
|
# require 'debug'
|
26
23
|
loop do
|
24
|
+
env.clear
|
25
|
+
env.clear_rankings
|
27
26
|
outcome = solver.resume
|
28
27
|
break if outcome.nil?
|
29
28
|
|
30
|
-
env.
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
29
|
+
env.propagate(outcome) if result.empty? && outcome.successful?
|
30
|
+
result << build_solution(outcome)
|
31
|
+
end
|
32
|
+
|
33
|
+
format_solutions(result)
|
34
|
+
end
|
35
|
+
|
36
|
+
private
|
37
|
+
|
38
|
+
# @return [Array] A vector of assignment for each variable
|
39
|
+
def build_solution(outcome)
|
40
|
+
sol = env.vars.values.map do |var|
|
41
|
+
outcome.successful? ? var.quote(outcome) : nil
|
42
|
+
end
|
43
|
+
|
44
|
+
sol
|
45
|
+
end
|
46
|
+
|
47
|
+
# Transform the solutions into sequence of conscells.
|
48
|
+
# @param solutions [Array<Array>] An array of solution.
|
49
|
+
# A solution is in itself an array of bindings (one per variable)
|
50
|
+
def format_solutions(solutions)
|
51
|
+
solutions_as_list = solutions.map { |sol| arr2list(sol, true) }
|
52
|
+
arr2list(solutions_as_list, false)
|
53
|
+
end
|
54
|
+
|
55
|
+
# Utility method. Transform an array into a ConsCell-based list.
|
56
|
+
# @param anArray [Array]
|
57
|
+
# @param simplify [Boolean]
|
58
|
+
def arr2list(anArray, simplify)
|
59
|
+
return anArray[0] if anArray.size == 1 && simplify
|
60
|
+
|
61
|
+
new_tail = nil
|
62
|
+
anArray.reverse_each do |elem|
|
63
|
+
new_tail = Core::ConsCell.new(elem, new_tail)
|
39
64
|
end
|
40
65
|
|
41
|
-
|
66
|
+
new_tail
|
42
67
|
end
|
43
68
|
end # class
|
44
69
|
end # module
|