mini_kraken 0.2.04 → 0.3.00
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +7 -0
- data/README.md +16 -16
- data/lib/mini_kraken/atomic/all_atomic.rb +1 -0
- data/lib/mini_kraken/atomic/atomic_term.rb +32 -17
- data/lib/mini_kraken/atomic/k_integer.rb +0 -4
- data/lib/mini_kraken/atomic/k_string.rb +17 -0
- data/lib/mini_kraken/atomic/k_symbol.rb +0 -6
- data/lib/mini_kraken/composite/all_composite.rb +4 -0
- data/lib/mini_kraken/composite/composite_term.rb +2 -18
- data/lib/mini_kraken/composite/cons_cell.rb +178 -11
- data/lib/mini_kraken/composite/cons_cell_visitor.rb +12 -64
- data/lib/mini_kraken/composite/list.rb +32 -0
- data/lib/mini_kraken/core/all_core.rb +8 -0
- data/lib/mini_kraken/core/any_value.rb +31 -7
- data/lib/mini_kraken/core/arity.rb +69 -0
- data/lib/mini_kraken/core/association.rb +29 -4
- data/lib/mini_kraken/core/association_copy.rb +50 -0
- data/lib/mini_kraken/core/base_term.rb +13 -0
- data/lib/mini_kraken/core/blackboard.rb +315 -0
- data/lib/mini_kraken/core/bookmark.rb +46 -0
- data/lib/mini_kraken/core/context.rb +624 -0
- data/lib/mini_kraken/core/duck_fiber.rb +21 -19
- data/lib/mini_kraken/core/entry.rb +40 -0
- data/lib/mini_kraken/core/fail.rb +20 -18
- data/lib/mini_kraken/core/fusion.rb +29 -0
- data/lib/mini_kraken/core/goal.rb +20 -29
- data/lib/mini_kraken/core/log_var.rb +4 -30
- data/lib/mini_kraken/core/log_var_ref.rb +72 -48
- data/lib/mini_kraken/core/nullary_relation.rb +2 -9
- data/lib/mini_kraken/core/parametrized_term.rb +61 -0
- data/lib/mini_kraken/core/relation.rb +14 -28
- data/lib/mini_kraken/core/scope.rb +67 -0
- data/lib/mini_kraken/core/solver_adapter.rb +58 -0
- data/lib/mini_kraken/core/specification.rb +48 -0
- data/lib/mini_kraken/core/succeed.rb +21 -17
- data/lib/mini_kraken/core/symbol_table.rb +137 -0
- data/lib/mini_kraken/core/term.rb +15 -4
- data/lib/mini_kraken/glue/dsl.rb +35 -69
- data/lib/mini_kraken/glue/run_star_expression.rb +28 -30
- data/lib/mini_kraken/rela/all_rela.rb +8 -0
- data/lib/mini_kraken/rela/binary_relation.rb +30 -0
- data/lib/mini_kraken/rela/conde.rb +146 -0
- data/lib/mini_kraken/rela/conj2.rb +65 -0
- data/lib/mini_kraken/rela/def_relation.rb +64 -0
- data/lib/mini_kraken/rela/disj2.rb +70 -0
- data/lib/mini_kraken/rela/fresh.rb +98 -0
- data/lib/mini_kraken/{core → rela}/goal_relation.rb +6 -8
- data/lib/mini_kraken/rela/unify.rb +258 -0
- data/lib/mini_kraken/version.rb +1 -1
- data/spec/atomic/atomic_term_spec.rb +23 -20
- data/spec/atomic/k_symbol_spec.rb +0 -5
- data/spec/composite/cons_cell_spec.rb +116 -0
- data/spec/composite/cons_cell_visitor_spec.rb +16 -3
- data/spec/composite/list_spec.rb +50 -0
- data/spec/core/any_value_spec.rb +52 -0
- data/spec/core/arity_spec.rb +91 -0
- data/spec/core/association_copy_spec.rb +69 -0
- data/spec/core/association_spec.rb +25 -0
- data/spec/core/blackboard_spec.rb +287 -0
- data/spec/core/bookmark_spec.rb +40 -0
- data/spec/core/context_spec.rb +221 -0
- data/spec/core/core_spec.rb +40 -0
- data/spec/core/duck_fiber_spec.rb +22 -46
- data/spec/core/fail_spec.rb +5 -6
- data/spec/core/goal_spec.rb +20 -11
- data/spec/core/log_var_ref_spec.rb +80 -5
- data/spec/core/log_var_spec.rb +35 -6
- data/spec/core/nullary_relation_spec.rb +33 -0
- data/spec/core/parametrized_tem_spec.rb +39 -0
- data/spec/core/relation_spec.rb +33 -0
- data/spec/core/scope_spec.rb +73 -0
- data/spec/core/solver_adapter_spec.rb +70 -0
- data/spec/core/specification_spec.rb +43 -0
- data/spec/core/succeed_spec.rb +5 -5
- data/spec/core/symbol_table_spec.rb +142 -0
- data/spec/glue/dsl_chap1_spec.rb +88 -99
- data/spec/glue/dsl_chap2_spec.rb +59 -41
- data/spec/glue/run_star_expression_spec.rb +69 -896
- data/spec/{core → rela}/conde_spec.rb +50 -46
- data/spec/rela/conj2_spec.rb +123 -0
- data/spec/rela/def_relation_spec.rb +119 -0
- data/spec/rela/disj2_spec.rb +117 -0
- data/spec/rela/fresh_spec.rb +147 -0
- data/spec/rela/unify_spec.rb +369 -0
- data/spec/support/factory_atomic.rb +7 -0
- data/spec/support/factory_composite.rb +21 -0
- metadata +71 -48
- data/lib/mini_kraken/core/association_walker.rb +0 -183
- data/lib/mini_kraken/core/base_arg.rb +0 -10
- data/lib/mini_kraken/core/binary_relation.rb +0 -63
- data/lib/mini_kraken/core/composite_goal.rb +0 -46
- data/lib/mini_kraken/core/conde.rb +0 -143
- data/lib/mini_kraken/core/conj2.rb +0 -79
- data/lib/mini_kraken/core/def_relation.rb +0 -53
- data/lib/mini_kraken/core/designation.rb +0 -55
- data/lib/mini_kraken/core/disj2.rb +0 -72
- data/lib/mini_kraken/core/environment.rb +0 -73
- data/lib/mini_kraken/core/equals.rb +0 -191
- data/lib/mini_kraken/core/formal_arg.rb +0 -22
- data/lib/mini_kraken/core/formal_ref.rb +0 -25
- data/lib/mini_kraken/core/freshness.rb +0 -45
- data/lib/mini_kraken/core/goal_arg.rb +0 -12
- data/lib/mini_kraken/core/goal_template.rb +0 -102
- data/lib/mini_kraken/core/outcome.rb +0 -63
- data/lib/mini_kraken/core/tap.rb +0 -46
- data/lib/mini_kraken/core/vocabulary.rb +0 -446
- data/lib/mini_kraken/glue/fresh_env.rb +0 -108
- data/lib/mini_kraken/glue/fresh_env_factory.rb +0 -83
- data/spec/core/association_walker_spec.rb +0 -194
- data/spec/core/conj2_spec.rb +0 -116
- data/spec/core/def_relation_spec.rb +0 -99
- data/spec/core/disj2_spec.rb +0 -100
- data/spec/core/environment_spec.rb +0 -144
- data/spec/core/equals_spec.rb +0 -319
- data/spec/core/goal_template_spec.rb +0 -74
- data/spec/core/outcome_spec.rb +0 -56
- data/spec/core/vocabulary_spec.rb +0 -220
- data/spec/glue/fresh_env_factory_spec.rb +0 -99
- data/spec/glue/fresh_env_spec.rb +0 -62
@@ -6,50 +6,36 @@ require_relative 'cons_cell'
|
|
6
6
|
module MiniKraken
|
7
7
|
module Composite
|
8
8
|
# Factory class.
|
9
|
-
# Purpose: to create
|
9
|
+
# Purpose: to create Fiber specialized in the visit of cons cells.
|
10
10
|
class ConsCellVisitor
|
11
11
|
# Build a depth-first in-order expression tree visitor.
|
12
|
-
# The visitor is implemented as
|
13
|
-
# The
|
14
|
-
#
|
12
|
+
# The visitor is implemented as a Fiber.
|
13
|
+
# The Fiber yields couples of the form: [member, visitee]
|
14
|
+
# where member is one of :car, :cdr
|
15
|
+
# The end of visit is signalled with the couple [:stop, nil]
|
15
16
|
# @param aCell [ConsCell]
|
16
|
-
# @return [Fiber]
|
17
|
+
# @return [Fiber] A Fiber that yields couples
|
17
18
|
def self.df_visitor(aCell)
|
18
19
|
first = aCell # The visit will start from the provided cons cell
|
20
|
+
# puts "#{__callee__} called with #{aCell.object_id.to_s(16)}"
|
19
21
|
visitor = Fiber.new do |skipping|
|
20
22
|
# Initialization part: will run once
|
21
23
|
visitees = Set.new # Keep track of the conscell already visited
|
22
24
|
visit_stack = first.nil? ? [] : [[:car, first]] # The LIFO queue of cells to visit
|
23
25
|
|
24
26
|
until visit_stack.empty? # Traversal part (as a loop)
|
25
|
-
to_swap = false
|
26
27
|
side, cell = visit_stack.pop
|
27
|
-
next if visitees.include?(cell)
|
28
|
+
next if visitees.include?(cell) && side == :car
|
28
29
|
|
29
|
-
visitees << cell
|
30
|
+
visitees << cell if cell.kind_of?(ConsCell)
|
30
31
|
|
31
32
|
skip_children = Fiber.yield [side, cell]
|
32
|
-
# require 'debug' if skip_children
|
33
33
|
next if skip_children || skipping
|
34
34
|
|
35
35
|
skipping = false
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
to_swap = true
|
40
|
-
else
|
41
|
-
Fiber.yield [:car, cell.car]
|
42
|
-
end
|
43
|
-
|
44
|
-
case cell.cdr
|
45
|
-
when ConsCell
|
46
|
-
if to_swap
|
47
|
-
visit_stack.insert(-2, [:cdr, cell.cdr])
|
48
|
-
else
|
49
|
-
visit_stack.push([:cdr, cell.cdr])
|
50
|
-
end
|
51
|
-
else
|
52
|
-
Fiber.yield [:cdr, cell.cdr]
|
36
|
+
if cell.is_a?(ConsCell)
|
37
|
+
visit_stack.push([:cdr, cell.cdr])
|
38
|
+
visit_stack.push([:car, cell.car])
|
53
39
|
end
|
54
40
|
end
|
55
41
|
|
@@ -57,44 +43,6 @@ module MiniKraken
|
|
57
43
|
Fiber.yield [:stop, nil]
|
58
44
|
end
|
59
45
|
|
60
|
-
=begin
|
61
|
-
visitor = Enumerator.new do |requester| # requester argument is a Yielder
|
62
|
-
# Initialization part: will run once
|
63
|
-
visitees = Set.new # Keep track of the conscell already visited
|
64
|
-
visit_stack = first.nil? ? [] : [[ :car, first ]] # The LIFO queue of cells to visit
|
65
|
-
|
66
|
-
until visit_stack.empty? # Traversal part (as a loop)
|
67
|
-
to_swap = false
|
68
|
-
side, cell = visit_stack.pop()
|
69
|
-
next if visitees.include?(cell)
|
70
|
-
|
71
|
-
requester << [side, cell]
|
72
|
-
case cell.car
|
73
|
-
when ConsCell
|
74
|
-
visit_stack.push([:car, cell.car])
|
75
|
-
to_swap = true
|
76
|
-
else
|
77
|
-
requester << [:car, cell.car]
|
78
|
-
end
|
79
|
-
|
80
|
-
case cell.cdr
|
81
|
-
when ConsCell
|
82
|
-
if to_swap
|
83
|
-
visit_stack.insert(-2, [:cdr, cell.cdr])
|
84
|
-
else
|
85
|
-
visit_stack.push([:cdr, cell.cdr])
|
86
|
-
end
|
87
|
-
else
|
88
|
-
requester << [:cdr, cell.cdr]
|
89
|
-
end
|
90
|
-
|
91
|
-
visitees << cell
|
92
|
-
end
|
93
|
-
|
94
|
-
# Send stop mark
|
95
|
-
requester << [:stop, nil]
|
96
|
-
end
|
97
|
-
=end
|
98
46
|
return visitor
|
99
47
|
end
|
100
48
|
end # class
|
@@ -0,0 +1,32 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'cons_cell'
|
4
|
+
|
5
|
+
module MiniKraken
|
6
|
+
module Composite
|
7
|
+
# Module that implements convenience methods for manipulating
|
8
|
+
# proper lists represented with ConsCell objects.
|
9
|
+
module List
|
10
|
+
# Factory method for constructing a ConsCell pair.
|
11
|
+
# @param obj1 [Term]
|
12
|
+
# @param obj2 [Term]
|
13
|
+
# @return [Composite::ConsCell]
|
14
|
+
def self.cons(obj1, obj2 = nil)
|
15
|
+
ConsCell.new(obj1, obj2)
|
16
|
+
end
|
17
|
+
|
18
|
+
# Factory method. Build a proper list with elements of given array.
|
19
|
+
# @param arr [Array] Array of elements to put in a new list
|
20
|
+
# @return [Composite::ConsCell] Head nnode (cell) of created list.
|
21
|
+
def self.make_list(arr)
|
22
|
+
return cons(nil, nil) if arr.empty?
|
23
|
+
|
24
|
+
reversed = arr.reverse
|
25
|
+
|
26
|
+
reversed.reduce(nil) do |sub_result, elem|
|
27
|
+
ConsCell.new(elem, sub_result)
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end # module
|
31
|
+
end # module
|
32
|
+
end # module
|
@@ -2,36 +2,60 @@
|
|
2
2
|
|
3
3
|
module MiniKraken
|
4
4
|
module Core
|
5
|
+
# When MiniKraken successfully finds a solution but cannot associate
|
6
|
+
# a definite value to one or more logical variable(s), then it is
|
7
|
+
# useful to "assign" such unbound variable a placeholder that stands
|
8
|
+
# for any possible value. Following the practice of the "Reasoned Scheme"
|
9
|
+
# book, we associate a rank number to such a placeholder in order
|
10
|
+
# to distinguish arbitrary values from independent variables.
|
5
11
|
class AnyValue
|
12
|
+
# The rank number helps to differentiate independent variables.
|
13
|
+
# @return [Integer]
|
6
14
|
attr_reader :rank
|
7
15
|
|
8
|
-
# @param
|
9
|
-
|
10
|
-
|
11
|
-
@rank = anEnv.get_rank(aName, alternate_names)
|
16
|
+
# @param aRank [Integer] The rank of the variable that must reified.
|
17
|
+
def initialize(aRank)
|
18
|
+
@rank = aRank
|
12
19
|
end
|
13
20
|
|
21
|
+
# Compare with another instance.
|
22
|
+
# @param other [AnyValue, Integer, Symbol]
|
23
|
+
# @return [Boolean]
|
14
24
|
def ==(other)
|
15
25
|
if other.is_a?(AnyValue)
|
16
26
|
rank == other.rank
|
27
|
+
elsif other.is_a?(Integer)
|
28
|
+
rank == other
|
17
29
|
elsif other.id2name =~ /_\d+/
|
18
30
|
rank == other.id2name.sub(/_/, '').to_i
|
19
31
|
end
|
20
32
|
end
|
21
33
|
|
22
|
-
# Use same text representation as in Reasoned Schemer.
|
34
|
+
# Use same text representation as in "Reasoned Schemer" book.
|
35
|
+
# return [String]
|
23
36
|
def to_s
|
24
37
|
"_#{rank}"
|
25
38
|
end
|
26
39
|
|
27
|
-
def
|
40
|
+
def pinned?(_ctx)
|
28
41
|
false
|
29
42
|
end
|
30
43
|
|
31
44
|
# @return [AnyValue]
|
32
|
-
def quote(
|
45
|
+
def quote(_ctx)
|
33
46
|
self
|
34
47
|
end
|
48
|
+
|
49
|
+
private
|
50
|
+
|
51
|
+
def valid_rank(aRank)
|
52
|
+
unless aRank.kind_of?(Integer)
|
53
|
+
msg = "Rank number MUST be an Integer, found a #{aRank.class}"
|
54
|
+
raise StandardError, msg
|
55
|
+
end
|
56
|
+
|
57
|
+
aRank
|
58
|
+
end
|
35
59
|
end # class
|
36
60
|
end # module
|
37
61
|
end # module
|
@@ -0,0 +1,69 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module MiniKraken
|
4
|
+
module Core
|
5
|
+
# The arity is the number of arguments a relations or a function can take.
|
6
|
+
Arity = Struct.new(:low, :high) do
|
7
|
+
# Is the arity constrained to a single, unique value?
|
8
|
+
# In other words, are the low and high bound equal?
|
9
|
+
# @return [Boolean] true iff both bounds have same value
|
10
|
+
def unique?
|
11
|
+
low == high
|
12
|
+
end
|
13
|
+
|
14
|
+
# Is the arity set to zero?
|
15
|
+
# @return [Boolean] true if arity is exactly zero
|
16
|
+
def nullary?
|
17
|
+
unique? && low.zero?
|
18
|
+
end
|
19
|
+
|
20
|
+
# Is the arity set to one?
|
21
|
+
# @return [Boolean] true if arity is exactly one
|
22
|
+
def unary?
|
23
|
+
unique? && low == 1
|
24
|
+
end
|
25
|
+
|
26
|
+
# Is the arity set to two?
|
27
|
+
# @return [Boolean] true if arity is exactly two
|
28
|
+
def binary?
|
29
|
+
unique? && low == 2
|
30
|
+
end
|
31
|
+
|
32
|
+
# Can the arity take arbitrary values?
|
33
|
+
# @return [Boolean] true if arity has no fixed high bound
|
34
|
+
def variadic?
|
35
|
+
high == '*'
|
36
|
+
end
|
37
|
+
|
38
|
+
# Does the given argument value fits within the boundary values?
|
39
|
+
# @param aCount [Integer]
|
40
|
+
# @return [Boolean]
|
41
|
+
def match?(aCount)
|
42
|
+
is_matching = aCount >= low
|
43
|
+
is_matching &&= aCount <= high unless variadic?
|
44
|
+
|
45
|
+
is_matching
|
46
|
+
end
|
47
|
+
|
48
|
+
# Equality check
|
49
|
+
# @param other [Arity, Array, Integer]
|
50
|
+
# @return [Boolean] true if 'other' has same boundary values.
|
51
|
+
def ==(other)
|
52
|
+
return true if object_id == other.object_id
|
53
|
+
|
54
|
+
result = false
|
55
|
+
|
56
|
+
case other
|
57
|
+
when Arity
|
58
|
+
result = true if (low == other.low) && (high == other.high)
|
59
|
+
when Array
|
60
|
+
result = true if (low == other.first) && (high == other.last)
|
61
|
+
when Integer
|
62
|
+
result = true if (low == other) && (high == other)
|
63
|
+
end
|
64
|
+
|
65
|
+
result
|
66
|
+
end
|
67
|
+
end # struct
|
68
|
+
end # module
|
69
|
+
end # module
|
@@ -2,7 +2,7 @@
|
|
2
2
|
|
3
3
|
module MiniKraken
|
4
4
|
module Core
|
5
|
-
# A record that a given
|
5
|
+
# A record that a given variable is associated with a value.
|
6
6
|
class Association
|
7
7
|
# @return [String] internal name of variable being associated the value.
|
8
8
|
attr_accessor :i_name
|
@@ -10,13 +10,38 @@ module MiniKraken
|
|
10
10
|
# @return [Term] the MiniKraken value associated with the variable
|
11
11
|
attr_reader :value
|
12
12
|
|
13
|
-
|
14
|
-
# @param aVariable [Variable, String] A variable or its name.
|
13
|
+
# @param aVariable [Variable, String] A variable or its internal name.
|
15
14
|
# @param aValue [Term] value being associated to the variable.
|
16
15
|
def initialize(aVariable, aValue)
|
17
|
-
a_name = aVariable.respond_to?(:
|
16
|
+
a_name = aVariable.respond_to?(:i_name) ? aVariable.i_name : aVariable
|
18
17
|
@i_name = validated_name(a_name)
|
19
18
|
@value = aValue
|
19
|
+
@dependencies = nil
|
20
|
+
end
|
21
|
+
|
22
|
+
# Is the associated value floating, that is, it does contain
|
23
|
+
# a variable that is either unbound or floating?
|
24
|
+
# @param ctx [Core::Context]
|
25
|
+
# @return [Boolean]
|
26
|
+
def floating?(ctx)
|
27
|
+
value.floating?(ctx)
|
28
|
+
end
|
29
|
+
|
30
|
+
# Is the associated value pinned, that is, doesn't contain
|
31
|
+
# an unbound or floating variable?
|
32
|
+
# @param ctx [Core::Context]
|
33
|
+
# @return [Boolean]
|
34
|
+
def pinned?(ctx)
|
35
|
+
@pinned ||= value.pinned?(ctx)
|
36
|
+
@pinned
|
37
|
+
end
|
38
|
+
|
39
|
+
# @return [Array<String>] The i_names of direct dependent variables
|
40
|
+
def dependencies(ctx)
|
41
|
+
@dependencies ||= value.dependencies(ctx)
|
42
|
+
raise StandardError unless @dependencies.kind_of?(Set) || @dependencies.kind_of?(NilClass)
|
43
|
+
|
44
|
+
@dependencies
|
20
45
|
end
|
21
46
|
|
22
47
|
private
|
@@ -0,0 +1,50 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'association'
|
4
|
+
|
5
|
+
module MiniKraken
|
6
|
+
module Core
|
7
|
+
# A specialized association that bind a variable to a value from another
|
8
|
+
# association.
|
9
|
+
class AssociationCopy < Association
|
10
|
+
# @return [String] internal name of variable being associated the value.
|
11
|
+
attr_accessor :i_name
|
12
|
+
|
13
|
+
# @return [Association] the association from which the value is shared.
|
14
|
+
attr_reader :source
|
15
|
+
|
16
|
+
# @param aVariable [Variable, String] A variable or its internal name.
|
17
|
+
# @param anAssoc [Association] an association that shares its value.
|
18
|
+
def initialize(aVariable, anAssoc)
|
19
|
+
super(aVariable, nil)
|
20
|
+
@source = anAssoc
|
21
|
+
end
|
22
|
+
|
23
|
+
# Is the associated value floating, that is, it does contain
|
24
|
+
# a variable that is either unbound or floating?
|
25
|
+
# @param ctx [Core::Context]
|
26
|
+
# @return [Boolean]
|
27
|
+
def floating?(ctx)
|
28
|
+
source.floating?(ctx)
|
29
|
+
end
|
30
|
+
|
31
|
+
# Is the associated value pinned, that is, doesn't contain
|
32
|
+
# an unbound or floating variable?
|
33
|
+
# @param ctx [Core::Context]
|
34
|
+
# @return [Boolean]
|
35
|
+
def pinned?(ctx)
|
36
|
+
source.pinned?(ctx)
|
37
|
+
end
|
38
|
+
|
39
|
+
# @return [Term] the MiniKraken value associated with the variable
|
40
|
+
def value
|
41
|
+
source.value
|
42
|
+
end
|
43
|
+
|
44
|
+
# @return [Array<String>] The i_names of direct dependent variables
|
45
|
+
def dependencies(ctx)
|
46
|
+
source.dependencies(ctx)
|
47
|
+
end
|
48
|
+
end # class
|
49
|
+
end # module
|
50
|
+
end # module
|
@@ -0,0 +1,13 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module MiniKraken
|
4
|
+
# The namespace for the classes that central in the implementation of MiniKraken.
|
5
|
+
# By 'central', one means that there a dependency between the remaining classes
|
6
|
+
# and the classes in this module.
|
7
|
+
module Core
|
8
|
+
# Abstract class that is a generalization for goal actual arguments or
|
9
|
+
# for arguments of goal template.
|
10
|
+
class BaseTerm
|
11
|
+
end # class
|
12
|
+
end # module
|
13
|
+
end # module
|
@@ -0,0 +1,315 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'association_copy'
|
4
|
+
require_relative 'bookmark'
|
5
|
+
require_relative 'fusion'
|
6
|
+
|
7
|
+
module MiniKraken
|
8
|
+
module Core
|
9
|
+
# A data structure that keeps the progress of a MiniKraken search.
|
10
|
+
class Blackboard
|
11
|
+
# This Hash answers to the question: given an i_name, what are
|
12
|
+
# its related moves (associations, fusions) ?
|
13
|
+
# @return [Hash{String=>Array<Integer>}] LIFO queue
|
14
|
+
attr_reader :i_name2moves
|
15
|
+
|
16
|
+
# A mapping from fused variable's i_name to a combining variable i_name
|
17
|
+
# @return [Hash{String => String}]
|
18
|
+
attr_reader :vars2cv
|
19
|
+
|
20
|
+
# A stack of indices of bookmarks. The indices corresponds to
|
21
|
+
# the positions of the bookmarks on the move_queue.
|
22
|
+
# The events that involve bookmarks are:
|
23
|
+
# - enter_scope (when executing fresh expression)
|
24
|
+
# - leave_scope (when all solutions for given scope found)
|
25
|
+
# - add_bk_point (when a backtrack point must be added)
|
26
|
+
# - remove_bk_point (when a backtrack point must be retracted)
|
27
|
+
# - next_alternative (when a new solution is searched)
|
28
|
+
# - fail! (when the current solution fails)
|
29
|
+
# @return [Array<Integer>]
|
30
|
+
attr_reader :bookmarks
|
31
|
+
|
32
|
+
# Serial numbers are assigned sequentially to bookmark objects.
|
33
|
+
# This attribute holds the next available serial number.
|
34
|
+
# @return [Integer] Next serial number to assign
|
35
|
+
attr_reader :next_serial_num
|
36
|
+
|
37
|
+
# A queue of entries that embodies the progress towards a solution.
|
38
|
+
# @return [Array<Association, Bookmark, Fusion>]
|
39
|
+
attr_reader :move_queue
|
40
|
+
|
41
|
+
# @return [Symbol] One of: :"#s" (success), :"#u" (failure)
|
42
|
+
attr_reader :resultant
|
43
|
+
|
44
|
+
# Constructor.
|
45
|
+
def initialize
|
46
|
+
@i_name2moves = {}
|
47
|
+
@vars2cv = {}
|
48
|
+
# @bookmarks = []
|
49
|
+
@next_serial_num = 0
|
50
|
+
@move_queue = []
|
51
|
+
end
|
52
|
+
|
53
|
+
# Returns iff there is no entry in the association queue
|
54
|
+
# @return [Boolean]
|
55
|
+
def empty?
|
56
|
+
move_queue.empty?
|
57
|
+
end
|
58
|
+
|
59
|
+
# Does the latest result represent a failure?
|
60
|
+
# @return [Boolean] true if failure, false otherwise
|
61
|
+
def failure?
|
62
|
+
resultant != :"#s"
|
63
|
+
end
|
64
|
+
|
65
|
+
# Does the latest result represent a success?
|
66
|
+
# @return [Boolean] true if success, false otherwise
|
67
|
+
def success?
|
68
|
+
resultant == :"#s"
|
69
|
+
end
|
70
|
+
|
71
|
+
# Return the most recent move.
|
72
|
+
# @return [Association, Bookmark, Fusion]
|
73
|
+
def last_move
|
74
|
+
move_queue.last
|
75
|
+
end
|
76
|
+
|
77
|
+
# Indicate whether the variable is fused with another one
|
78
|
+
# @param iName [String] Internal name of a logical variable
|
79
|
+
# @return [Boolean]
|
80
|
+
def fused?(iName)
|
81
|
+
vars2cv.include? iName
|
82
|
+
end
|
83
|
+
|
84
|
+
# If the variable is fused, then return the internal name of the
|
85
|
+
# combining variable, otherwise return the input value as is.
|
86
|
+
# @param iName [String] Internal name of a logical variable
|
87
|
+
# @return [String] Internal name of variable or combining variable.
|
88
|
+
def relevant_i_name(iName)
|
89
|
+
fused?(iName) ? vars2cv[iName] : iName
|
90
|
+
end
|
91
|
+
|
92
|
+
# Retrieve the associations for the given internal name.
|
93
|
+
# If requested, add the association(s) shared through the fusion
|
94
|
+
# with another variable.
|
95
|
+
# @param iName [String] Internal name of variable
|
96
|
+
# @param shared [Boolean]
|
97
|
+
# @return [Array<Association>]
|
98
|
+
def associations_for(iName, shared = false)
|
99
|
+
assocs_idx = nil
|
100
|
+
if shared && fused?(iName)
|
101
|
+
assocs_idx = i_name2moves[vars2cv[iName]]
|
102
|
+
if assocs_idx && move_queue[assocs_idx.first].kind_of?(Fusion)
|
103
|
+
assocs_idx = assocs_idx.dup
|
104
|
+
assocs_idx.shift
|
105
|
+
end
|
106
|
+
else
|
107
|
+
indices = i_name2moves[iName]
|
108
|
+
assocs_idx = indices.dup if indices
|
109
|
+
end
|
110
|
+
|
111
|
+
if assocs_idx
|
112
|
+
assocs_idx.map { |i| move_queue[i] }
|
113
|
+
else
|
114
|
+
[]
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
# Push the given association onto the move queue
|
119
|
+
# @param anAssociation [Association]
|
120
|
+
# @return [Association]
|
121
|
+
def enqueue_association(anAssociation)
|
122
|
+
unless anAssociation.kind_of?(Association)
|
123
|
+
raise StandardError, "Unsupported item class #{anAssociation.class}"
|
124
|
+
end
|
125
|
+
|
126
|
+
enqueue_move(anAssociation)
|
127
|
+
anAssociation
|
128
|
+
end
|
129
|
+
|
130
|
+
# Push the given fusion object onto the move queue
|
131
|
+
# @param aFusion [Fusion]
|
132
|
+
# @return [Fusion]
|
133
|
+
def enqueue_fusion(aFusion)
|
134
|
+
aFusion.elements.each do |fused_i_nm|
|
135
|
+
vars2cv[fused_i_nm] = aFusion.i_name
|
136
|
+
end
|
137
|
+
enqueue_move(aFusion)
|
138
|
+
|
139
|
+
# If there is any existing association for the fused variables...
|
140
|
+
# Add them to the associations of the combining variables
|
141
|
+
bound = aFusion.elements.select { |i_nm| i_name2moves.include? i_nm }
|
142
|
+
unless bound.empty?
|
143
|
+
bound.each do |i_nm|
|
144
|
+
to_copy = i_name2moves[i_nm]
|
145
|
+
to_copy.each do |i|
|
146
|
+
as = move_queue[i]
|
147
|
+
new_as = AssociationCopy.new(aFusion.i_name, as)
|
148
|
+
enqueue_association(new_as)
|
149
|
+
end
|
150
|
+
end
|
151
|
+
end
|
152
|
+
|
153
|
+
aFusion
|
154
|
+
end
|
155
|
+
|
156
|
+
# Notification of failure of last executed goal.
|
157
|
+
# All moves up to most recent backtrack point are dropped.
|
158
|
+
def failed!
|
159
|
+
@resultant = :"#u"
|
160
|
+
|
161
|
+
# Remove all items until most recent backtrack point.
|
162
|
+
until move_queue.empty? ||
|
163
|
+
(last_move.kind_of?(Bookmark) && last_move.kind == :bt_point)
|
164
|
+
dequeue_item
|
165
|
+
end
|
166
|
+
end
|
167
|
+
|
168
|
+
# Notify success of last executed goal
|
169
|
+
def succeeded!
|
170
|
+
@resultant = :"#s"
|
171
|
+
end
|
172
|
+
|
173
|
+
# Place a backtrack point as a bookmark on move queue.
|
174
|
+
# @return [Integer] serial number of created bookmark
|
175
|
+
def place_bt_point
|
176
|
+
add_bookmark(:bt_point)
|
177
|
+
end
|
178
|
+
|
179
|
+
# Remove all items until most recent backtrack bookmark found.
|
180
|
+
# The boobmark is not remove from the queue
|
181
|
+
# @return [Array<Association, Bookmark, Fusion>]
|
182
|
+
def next_alternative
|
183
|
+
removed = []
|
184
|
+
|
185
|
+
# Remove all items until most recent scope bookmark.
|
186
|
+
until move_queue.empty? ||
|
187
|
+
(last_move.kind_of?(Bookmark) && last_move.kind == :bt_point)
|
188
|
+
removed << dequeue_item
|
189
|
+
end
|
190
|
+
|
191
|
+
removed
|
192
|
+
end
|
193
|
+
|
194
|
+
# Remove all items until most recent backtrack bookmark found.
|
195
|
+
# @return [Array<Association, Bookmark, Fusion>]
|
196
|
+
def retract_bt_point
|
197
|
+
removed = next_alternative
|
198
|
+
dequeue_item unless move_queue.empty? # Remove the bookmark (if any)
|
199
|
+
|
200
|
+
removed
|
201
|
+
end
|
202
|
+
|
203
|
+
# React to event 'enter_scope' by putting a scope bookmark on move queue.
|
204
|
+
# @return [Integer] serial number of created bookmark
|
205
|
+
def enter_scope
|
206
|
+
add_bookmark(:scope)
|
207
|
+
end
|
208
|
+
|
209
|
+
# Remove all items until most recent scope bookmark found.
|
210
|
+
# @return [Array<Association, Bookmark, Fusion>]
|
211
|
+
def leave_scope
|
212
|
+
removed = []
|
213
|
+
|
214
|
+
# Remove all items until most recent scope bookmark.
|
215
|
+
until move_queue.empty? ||
|
216
|
+
(last_move.kind_of?(Bookmark) && last_move.kind == :scope)
|
217
|
+
removed << dequeue_item
|
218
|
+
end
|
219
|
+
dequeue_item unless move_queue.empty? # Remove the bookmark (if any)
|
220
|
+
|
221
|
+
removed
|
222
|
+
end
|
223
|
+
|
224
|
+
private
|
225
|
+
|
226
|
+
# Push given move onto the queue and update the lookups.
|
227
|
+
# @param [aMove [Association, Fusion]
|
228
|
+
def enqueue_move(aMove)
|
229
|
+
last_index = move_queue.size
|
230
|
+
move_queue.push(aMove)
|
231
|
+
iname = aMove.i_name
|
232
|
+
if i_name2moves.include?(iname)
|
233
|
+
i_name2moves[iname] << last_index
|
234
|
+
else
|
235
|
+
i_name2moves[iname] = [last_index]
|
236
|
+
end
|
237
|
+
|
238
|
+
aMove
|
239
|
+
end
|
240
|
+
|
241
|
+
# Remove the last item inserted in the queue
|
242
|
+
# @return [Association, Bookmark, Fusion] Last item removed from LIFO queue
|
243
|
+
def dequeue_item
|
244
|
+
result = nil
|
245
|
+
|
246
|
+
# require 'debug'
|
247
|
+
return result unless last_move
|
248
|
+
|
249
|
+
result = case last_move
|
250
|
+
when Association
|
251
|
+
dequeue_move
|
252
|
+
when Bookmark
|
253
|
+
remove_bookmark
|
254
|
+
when Fusion
|
255
|
+
remove_fusion
|
256
|
+
end
|
257
|
+
|
258
|
+
return result
|
259
|
+
end
|
260
|
+
|
261
|
+
# Low-level bookmark addition method.
|
262
|
+
# @param aKind [Symbol] One of: :scope, :bt_point
|
263
|
+
# @return [Integer] Serial number of new bookmark
|
264
|
+
def add_bookmark(aKind)
|
265
|
+
move_queue.size
|
266
|
+
serial_number = next_serial_num
|
267
|
+
@move_queue << Bookmark.new(aKind, serial_number)
|
268
|
+
@next_serial_num += 1
|
269
|
+
|
270
|
+
serial_number
|
271
|
+
end
|
272
|
+
|
273
|
+
# Low-level association removal method.
|
274
|
+
# Pre-condition: association to remove is on top of stack
|
275
|
+
def dequeue_move
|
276
|
+
unless last_move.kind_of?(Association) || last_move.kind_of?(Fusion)
|
277
|
+
raise StandardError, 'Expected Assocation or Fusion on top of stack.'
|
278
|
+
end
|
279
|
+
|
280
|
+
i_name = last_move.i_name
|
281
|
+
assocs_idx = i_name2moves[i_name]
|
282
|
+
|
283
|
+
idx_last = assocs_idx.pop
|
284
|
+
unless idx_last == (move_queue.size - 1)
|
285
|
+
raise StandardError, 'Internal error'
|
286
|
+
end
|
287
|
+
|
288
|
+
i_name2moves.delete(i_name) if assocs_idx.empty?
|
289
|
+
move_queue.pop
|
290
|
+
end
|
291
|
+
|
292
|
+
# Low-level bookmark removal method.
|
293
|
+
# Pre-condition: bookmark to remove is on top of stack
|
294
|
+
def remove_bookmark
|
295
|
+
unless move_queue.last.kind_of?(Bookmark)
|
296
|
+
raise StandardError, 'Expected a bookmark on top of stack.'
|
297
|
+
end
|
298
|
+
|
299
|
+
move_queue.pop
|
300
|
+
end
|
301
|
+
|
302
|
+
# Low-level fusion removal method.
|
303
|
+
# Pre-condition: fusion to remove is on top of stack
|
304
|
+
def remove_fusion
|
305
|
+
unless last_move.kind_of?(Fusion)
|
306
|
+
raise StandardError, 'Fusion on top of stack.'
|
307
|
+
end
|
308
|
+
|
309
|
+
last_move.elements.each { |e| vars2cv.delete(e) }
|
310
|
+
|
311
|
+
dequeue_move
|
312
|
+
end
|
313
|
+
end # class
|
314
|
+
end # module
|
315
|
+
end # module
|