mini_kraken 0.2.03 → 0.3.03
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/.rubocop.yml +378 -333
- data/CHANGELOG.md +48 -0
- data/README.md +29 -21
- data/lib/mini_kraken/atomic/all_atomic.rb +5 -0
- data/lib/mini_kraken/atomic/atomic_term.rb +96 -0
- data/lib/mini_kraken/atomic/k_boolean.rb +42 -0
- data/lib/mini_kraken/{core → atomic}/k_integer.rb +2 -5
- data/lib/mini_kraken/atomic/k_string.rb +17 -0
- data/lib/mini_kraken/{core → atomic}/k_symbol.rb +4 -8
- data/lib/mini_kraken/composite/all_composite.rb +4 -0
- data/lib/mini_kraken/composite/composite_term.rb +27 -0
- data/lib/mini_kraken/composite/cons_cell.rb +301 -0
- data/lib/mini_kraken/composite/cons_cell_visitor.rb +50 -0
- 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 +492 -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 +22 -0
- data/lib/mini_kraken/core/log_var_ref.rb +108 -0
- data/lib/mini_kraken/core/nullary_relation.rb +2 -9
- data/lib/mini_kraken/core/parametrized_term.rb +68 -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 +44 -88
- 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 +143 -0
- data/lib/mini_kraken/rela/conj2.rb +65 -0
- data/lib/mini_kraken/rela/def_relation.rb +93 -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 +7 -9
- data/lib/mini_kraken/rela/unify.rb +265 -0
- data/lib/mini_kraken/version.rb +1 -1
- data/mini_kraken.gemspec +2 -2
- data/spec/.rubocop.yml +1 -1
- data/spec/atomic/atomic_term_spec.rb +98 -0
- data/spec/{core → atomic}/k_boolean_spec.rb +19 -34
- data/spec/{core → atomic}/k_symbol_spec.rb +3 -16
- data/spec/composite/cons_cell_spec.rb +225 -0
- data/spec/{core → composite}/cons_cell_visitor_spec.rb +36 -20
- data/spec/composite/list_spec.rb +50 -0
- data/spec/core/any_value_spec.rb +52 -0
- data/spec/core/arity_spec.rb +92 -0
- data/spec/core/association_copy_spec.rb +69 -0
- data/spec/core/association_spec.rb +31 -4
- data/spec/core/blackboard_spec.rb +287 -0
- data/spec/core/bookmark_spec.rb +40 -0
- data/spec/core/context_spec.rb +245 -0
- data/spec/core/core_spec.rb +40 -0
- data/spec/core/duck_fiber_spec.rb +16 -46
- data/spec/core/fail_spec.rb +5 -6
- data/spec/core/goal_spec.rb +22 -12
- data/spec/core/log_var_ref_spec.rb +105 -0
- data/spec/core/log_var_spec.rb +64 -0
- 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 -144
- data/spec/glue/dsl_chap2_spec.rb +454 -19
- data/spec/glue/run_star_expression_spec.rb +81 -906
- data/spec/rela/conde_spec.rb +153 -0
- 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 +29 -0
- data/spec/support/factory_composite.rb +21 -0
- data/spec/support/factory_methods.rb +11 -26
- metadata +98 -70
- data/lib/mini_kraken/core/association_walker.rb +0 -183
- data/lib/mini_kraken/core/atomic_term.rb +0 -67
- 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/composite_term.rb +0 -41
- data/lib/mini_kraken/core/conde.rb +0 -143
- data/lib/mini_kraken/core/conj2.rb +0 -79
- data/lib/mini_kraken/core/cons_cell.rb +0 -82
- data/lib/mini_kraken/core/cons_cell_visitor.rb +0 -102
- 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 -193
- 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/k_boolean.rb +0 -35
- data/lib/mini_kraken/core/outcome.rb +0 -63
- data/lib/mini_kraken/core/variable.rb +0 -41
- data/lib/mini_kraken/core/variable_ref.rb +0 -84
- data/lib/mini_kraken/core/vocabulary.rb +0 -446
- data/lib/mini_kraken/glue/fresh_env.rb +0 -103
- data/lib/mini_kraken/glue/fresh_env_factory.rb +0 -83
- data/spec/core/association_walker_spec.rb +0 -192
- data/spec/core/conde_spec.rb +0 -147
- data/spec/core/conj2_spec.rb +0 -114
- data/spec/core/cons_cell_spec.rb +0 -107
- data/spec/core/def_relation_spec.rb +0 -97
- data/spec/core/disj2_spec.rb +0 -99
- data/spec/core/environment_spec.rb +0 -142
- data/spec/core/equals_spec.rb +0 -317
- data/spec/core/goal_template_spec.rb +0 -74
- data/spec/core/outcome_spec.rb +0 -56
- data/spec/core/variable_ref_spec.rb +0 -30
- data/spec/core/variable_spec.rb +0 -35
- data/spec/core/vocabulary_spec.rb +0 -219
- data/spec/glue/fresh_env_factory_spec.rb +0 -97
- data/spec/glue/fresh_env_spec.rb +0 -62
@@ -0,0 +1,50 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'set'
|
4
|
+
require_relative 'cons_cell'
|
5
|
+
|
6
|
+
module MiniKraken
|
7
|
+
module Composite
|
8
|
+
# Factory class.
|
9
|
+
# Purpose: to create Fiber specialized in the visit of cons cells.
|
10
|
+
class ConsCellVisitor
|
11
|
+
# Build a depth-first in-order expression tree visitor.
|
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]
|
16
|
+
# @param aCell [ConsCell]
|
17
|
+
# @return [Fiber] A Fiber that yields couples
|
18
|
+
def self.df_visitor(aCell)
|
19
|
+
first = aCell # The visit will start from the provided cons cell
|
20
|
+
# puts "#{__callee__} called with #{aCell.object_id.to_s(16)}"
|
21
|
+
visitor = Fiber.new do |skipping|
|
22
|
+
# Initialization part: will run once
|
23
|
+
visitees = Set.new # Keep track of the conscell already visited
|
24
|
+
visit_stack = first.nil? ? [] : [[:car, first]] # The LIFO queue of cells to visit
|
25
|
+
|
26
|
+
until visit_stack.empty? # Traversal part (as a loop)
|
27
|
+
side, cell = visit_stack.pop
|
28
|
+
next if visitees.include?(cell) && side == :car
|
29
|
+
|
30
|
+
visitees << cell if cell.kind_of?(ConsCell)
|
31
|
+
|
32
|
+
skip_children = Fiber.yield [side, cell]
|
33
|
+
next if skip_children || skipping
|
34
|
+
|
35
|
+
skipping = false
|
36
|
+
if cell.is_a?(ConsCell) && !cell.null?
|
37
|
+
visit_stack.push([:cdr, cell.cdr])
|
38
|
+
visit_stack.push([:car, cell.car])
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
# Send stop mark
|
43
|
+
Fiber.yield [:stop, nil]
|
44
|
+
end
|
45
|
+
|
46
|
+
return visitor
|
47
|
+
end
|
48
|
+
end # class
|
49
|
+
end # module
|
50
|
+
end # module
|
@@ -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
|