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.
Files changed (136) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +378 -333
  3. data/CHANGELOG.md +48 -0
  4. data/README.md +29 -21
  5. data/lib/mini_kraken/atomic/all_atomic.rb +5 -0
  6. data/lib/mini_kraken/atomic/atomic_term.rb +96 -0
  7. data/lib/mini_kraken/atomic/k_boolean.rb +42 -0
  8. data/lib/mini_kraken/{core → atomic}/k_integer.rb +2 -5
  9. data/lib/mini_kraken/atomic/k_string.rb +17 -0
  10. data/lib/mini_kraken/{core → atomic}/k_symbol.rb +4 -8
  11. data/lib/mini_kraken/composite/all_composite.rb +4 -0
  12. data/lib/mini_kraken/composite/composite_term.rb +27 -0
  13. data/lib/mini_kraken/composite/cons_cell.rb +301 -0
  14. data/lib/mini_kraken/composite/cons_cell_visitor.rb +50 -0
  15. data/lib/mini_kraken/composite/list.rb +32 -0
  16. data/lib/mini_kraken/core/all_core.rb +8 -0
  17. data/lib/mini_kraken/core/any_value.rb +31 -7
  18. data/lib/mini_kraken/core/arity.rb +69 -0
  19. data/lib/mini_kraken/core/association.rb +29 -4
  20. data/lib/mini_kraken/core/association_copy.rb +50 -0
  21. data/lib/mini_kraken/core/base_term.rb +13 -0
  22. data/lib/mini_kraken/core/blackboard.rb +315 -0
  23. data/lib/mini_kraken/core/bookmark.rb +46 -0
  24. data/lib/mini_kraken/core/context.rb +492 -0
  25. data/lib/mini_kraken/core/duck_fiber.rb +21 -19
  26. data/lib/mini_kraken/core/entry.rb +40 -0
  27. data/lib/mini_kraken/core/fail.rb +20 -18
  28. data/lib/mini_kraken/core/fusion.rb +29 -0
  29. data/lib/mini_kraken/core/goal.rb +20 -29
  30. data/lib/mini_kraken/core/log_var.rb +22 -0
  31. data/lib/mini_kraken/core/log_var_ref.rb +108 -0
  32. data/lib/mini_kraken/core/nullary_relation.rb +2 -9
  33. data/lib/mini_kraken/core/parametrized_term.rb +68 -0
  34. data/lib/mini_kraken/core/relation.rb +14 -28
  35. data/lib/mini_kraken/core/scope.rb +67 -0
  36. data/lib/mini_kraken/core/solver_adapter.rb +58 -0
  37. data/lib/mini_kraken/core/specification.rb +48 -0
  38. data/lib/mini_kraken/core/succeed.rb +21 -17
  39. data/lib/mini_kraken/core/symbol_table.rb +137 -0
  40. data/lib/mini_kraken/core/term.rb +15 -4
  41. data/lib/mini_kraken/glue/dsl.rb +44 -88
  42. data/lib/mini_kraken/glue/run_star_expression.rb +28 -30
  43. data/lib/mini_kraken/rela/all_rela.rb +8 -0
  44. data/lib/mini_kraken/rela/binary_relation.rb +30 -0
  45. data/lib/mini_kraken/rela/conde.rb +143 -0
  46. data/lib/mini_kraken/rela/conj2.rb +65 -0
  47. data/lib/mini_kraken/rela/def_relation.rb +93 -0
  48. data/lib/mini_kraken/rela/disj2.rb +70 -0
  49. data/lib/mini_kraken/rela/fresh.rb +98 -0
  50. data/lib/mini_kraken/{core → rela}/goal_relation.rb +7 -9
  51. data/lib/mini_kraken/rela/unify.rb +265 -0
  52. data/lib/mini_kraken/version.rb +1 -1
  53. data/mini_kraken.gemspec +2 -2
  54. data/spec/.rubocop.yml +1 -1
  55. data/spec/atomic/atomic_term_spec.rb +98 -0
  56. data/spec/{core → atomic}/k_boolean_spec.rb +19 -34
  57. data/spec/{core → atomic}/k_symbol_spec.rb +3 -16
  58. data/spec/composite/cons_cell_spec.rb +225 -0
  59. data/spec/{core → composite}/cons_cell_visitor_spec.rb +36 -20
  60. data/spec/composite/list_spec.rb +50 -0
  61. data/spec/core/any_value_spec.rb +52 -0
  62. data/spec/core/arity_spec.rb +92 -0
  63. data/spec/core/association_copy_spec.rb +69 -0
  64. data/spec/core/association_spec.rb +31 -4
  65. data/spec/core/blackboard_spec.rb +287 -0
  66. data/spec/core/bookmark_spec.rb +40 -0
  67. data/spec/core/context_spec.rb +245 -0
  68. data/spec/core/core_spec.rb +40 -0
  69. data/spec/core/duck_fiber_spec.rb +16 -46
  70. data/spec/core/fail_spec.rb +5 -6
  71. data/spec/core/goal_spec.rb +22 -12
  72. data/spec/core/log_var_ref_spec.rb +105 -0
  73. data/spec/core/log_var_spec.rb +64 -0
  74. data/spec/core/nullary_relation_spec.rb +33 -0
  75. data/spec/core/parametrized_tem_spec.rb +39 -0
  76. data/spec/core/relation_spec.rb +33 -0
  77. data/spec/core/scope_spec.rb +73 -0
  78. data/spec/core/solver_adapter_spec.rb +70 -0
  79. data/spec/core/specification_spec.rb +43 -0
  80. data/spec/core/succeed_spec.rb +5 -5
  81. data/spec/core/symbol_table_spec.rb +142 -0
  82. data/spec/glue/dsl_chap1_spec.rb +88 -144
  83. data/spec/glue/dsl_chap2_spec.rb +454 -19
  84. data/spec/glue/run_star_expression_spec.rb +81 -906
  85. data/spec/rela/conde_spec.rb +153 -0
  86. data/spec/rela/conj2_spec.rb +123 -0
  87. data/spec/rela/def_relation_spec.rb +119 -0
  88. data/spec/rela/disj2_spec.rb +117 -0
  89. data/spec/rela/fresh_spec.rb +147 -0
  90. data/spec/rela/unify_spec.rb +369 -0
  91. data/spec/support/factory_atomic.rb +29 -0
  92. data/spec/support/factory_composite.rb +21 -0
  93. data/spec/support/factory_methods.rb +11 -26
  94. metadata +98 -70
  95. data/lib/mini_kraken/core/association_walker.rb +0 -183
  96. data/lib/mini_kraken/core/atomic_term.rb +0 -67
  97. data/lib/mini_kraken/core/base_arg.rb +0 -10
  98. data/lib/mini_kraken/core/binary_relation.rb +0 -63
  99. data/lib/mini_kraken/core/composite_goal.rb +0 -46
  100. data/lib/mini_kraken/core/composite_term.rb +0 -41
  101. data/lib/mini_kraken/core/conde.rb +0 -143
  102. data/lib/mini_kraken/core/conj2.rb +0 -79
  103. data/lib/mini_kraken/core/cons_cell.rb +0 -82
  104. data/lib/mini_kraken/core/cons_cell_visitor.rb +0 -102
  105. data/lib/mini_kraken/core/def_relation.rb +0 -53
  106. data/lib/mini_kraken/core/designation.rb +0 -55
  107. data/lib/mini_kraken/core/disj2.rb +0 -72
  108. data/lib/mini_kraken/core/environment.rb +0 -73
  109. data/lib/mini_kraken/core/equals.rb +0 -193
  110. data/lib/mini_kraken/core/formal_arg.rb +0 -22
  111. data/lib/mini_kraken/core/formal_ref.rb +0 -25
  112. data/lib/mini_kraken/core/freshness.rb +0 -45
  113. data/lib/mini_kraken/core/goal_arg.rb +0 -12
  114. data/lib/mini_kraken/core/goal_template.rb +0 -102
  115. data/lib/mini_kraken/core/k_boolean.rb +0 -35
  116. data/lib/mini_kraken/core/outcome.rb +0 -63
  117. data/lib/mini_kraken/core/variable.rb +0 -41
  118. data/lib/mini_kraken/core/variable_ref.rb +0 -84
  119. data/lib/mini_kraken/core/vocabulary.rb +0 -446
  120. data/lib/mini_kraken/glue/fresh_env.rb +0 -103
  121. data/lib/mini_kraken/glue/fresh_env_factory.rb +0 -83
  122. data/spec/core/association_walker_spec.rb +0 -192
  123. data/spec/core/conde_spec.rb +0 -147
  124. data/spec/core/conj2_spec.rb +0 -114
  125. data/spec/core/cons_cell_spec.rb +0 -107
  126. data/spec/core/def_relation_spec.rb +0 -97
  127. data/spec/core/disj2_spec.rb +0 -99
  128. data/spec/core/environment_spec.rb +0 -142
  129. data/spec/core/equals_spec.rb +0 -317
  130. data/spec/core/goal_template_spec.rb +0 -74
  131. data/spec/core/outcome_spec.rb +0 -56
  132. data/spec/core/variable_ref_spec.rb +0 -30
  133. data/spec/core/variable_spec.rb +0 -35
  134. data/spec/core/vocabulary_spec.rb +0 -219
  135. data/spec/glue/fresh_env_factory_spec.rb +0 -97
  136. 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
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'context'
4
+ require_relative 'fail'
5
+ require_relative 'goal'
6
+ require_relative 'log_var'
7
+ require_relative 'log_var_ref'
8
+ require_relative 'succeed'
@@ -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 aName [String]
9
- # @param anEnv [Vocabulary]
10
- def initialize(aName, anEnv, alternate_names = [])
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 ground?(_env)
40
+ def pinned?(_ctx)
28
41
  false
29
42
  end
30
43
 
31
44
  # @return [AnyValue]
32
- def quote(_env)
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 vairable is associated with a value.
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?(:name) ? aVariable.i_name : aVariable
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