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.
Files changed (120) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +7 -0
  3. data/README.md +16 -16
  4. data/lib/mini_kraken/atomic/all_atomic.rb +1 -0
  5. data/lib/mini_kraken/atomic/atomic_term.rb +32 -17
  6. data/lib/mini_kraken/atomic/k_integer.rb +0 -4
  7. data/lib/mini_kraken/atomic/k_string.rb +17 -0
  8. data/lib/mini_kraken/atomic/k_symbol.rb +0 -6
  9. data/lib/mini_kraken/composite/all_composite.rb +4 -0
  10. data/lib/mini_kraken/composite/composite_term.rb +2 -18
  11. data/lib/mini_kraken/composite/cons_cell.rb +178 -11
  12. data/lib/mini_kraken/composite/cons_cell_visitor.rb +12 -64
  13. data/lib/mini_kraken/composite/list.rb +32 -0
  14. data/lib/mini_kraken/core/all_core.rb +8 -0
  15. data/lib/mini_kraken/core/any_value.rb +31 -7
  16. data/lib/mini_kraken/core/arity.rb +69 -0
  17. data/lib/mini_kraken/core/association.rb +29 -4
  18. data/lib/mini_kraken/core/association_copy.rb +50 -0
  19. data/lib/mini_kraken/core/base_term.rb +13 -0
  20. data/lib/mini_kraken/core/blackboard.rb +315 -0
  21. data/lib/mini_kraken/core/bookmark.rb +46 -0
  22. data/lib/mini_kraken/core/context.rb +624 -0
  23. data/lib/mini_kraken/core/duck_fiber.rb +21 -19
  24. data/lib/mini_kraken/core/entry.rb +40 -0
  25. data/lib/mini_kraken/core/fail.rb +20 -18
  26. data/lib/mini_kraken/core/fusion.rb +29 -0
  27. data/lib/mini_kraken/core/goal.rb +20 -29
  28. data/lib/mini_kraken/core/log_var.rb +4 -30
  29. data/lib/mini_kraken/core/log_var_ref.rb +72 -48
  30. data/lib/mini_kraken/core/nullary_relation.rb +2 -9
  31. data/lib/mini_kraken/core/parametrized_term.rb +61 -0
  32. data/lib/mini_kraken/core/relation.rb +14 -28
  33. data/lib/mini_kraken/core/scope.rb +67 -0
  34. data/lib/mini_kraken/core/solver_adapter.rb +58 -0
  35. data/lib/mini_kraken/core/specification.rb +48 -0
  36. data/lib/mini_kraken/core/succeed.rb +21 -17
  37. data/lib/mini_kraken/core/symbol_table.rb +137 -0
  38. data/lib/mini_kraken/core/term.rb +15 -4
  39. data/lib/mini_kraken/glue/dsl.rb +35 -69
  40. data/lib/mini_kraken/glue/run_star_expression.rb +28 -30
  41. data/lib/mini_kraken/rela/all_rela.rb +8 -0
  42. data/lib/mini_kraken/rela/binary_relation.rb +30 -0
  43. data/lib/mini_kraken/rela/conde.rb +146 -0
  44. data/lib/mini_kraken/rela/conj2.rb +65 -0
  45. data/lib/mini_kraken/rela/def_relation.rb +64 -0
  46. data/lib/mini_kraken/rela/disj2.rb +70 -0
  47. data/lib/mini_kraken/rela/fresh.rb +98 -0
  48. data/lib/mini_kraken/{core → rela}/goal_relation.rb +6 -8
  49. data/lib/mini_kraken/rela/unify.rb +258 -0
  50. data/lib/mini_kraken/version.rb +1 -1
  51. data/spec/atomic/atomic_term_spec.rb +23 -20
  52. data/spec/atomic/k_symbol_spec.rb +0 -5
  53. data/spec/composite/cons_cell_spec.rb +116 -0
  54. data/spec/composite/cons_cell_visitor_spec.rb +16 -3
  55. data/spec/composite/list_spec.rb +50 -0
  56. data/spec/core/any_value_spec.rb +52 -0
  57. data/spec/core/arity_spec.rb +91 -0
  58. data/spec/core/association_copy_spec.rb +69 -0
  59. data/spec/core/association_spec.rb +25 -0
  60. data/spec/core/blackboard_spec.rb +287 -0
  61. data/spec/core/bookmark_spec.rb +40 -0
  62. data/spec/core/context_spec.rb +221 -0
  63. data/spec/core/core_spec.rb +40 -0
  64. data/spec/core/duck_fiber_spec.rb +22 -46
  65. data/spec/core/fail_spec.rb +5 -6
  66. data/spec/core/goal_spec.rb +20 -11
  67. data/spec/core/log_var_ref_spec.rb +80 -5
  68. data/spec/core/log_var_spec.rb +35 -6
  69. data/spec/core/nullary_relation_spec.rb +33 -0
  70. data/spec/core/parametrized_tem_spec.rb +39 -0
  71. data/spec/core/relation_spec.rb +33 -0
  72. data/spec/core/scope_spec.rb +73 -0
  73. data/spec/core/solver_adapter_spec.rb +70 -0
  74. data/spec/core/specification_spec.rb +43 -0
  75. data/spec/core/succeed_spec.rb +5 -5
  76. data/spec/core/symbol_table_spec.rb +142 -0
  77. data/spec/glue/dsl_chap1_spec.rb +88 -99
  78. data/spec/glue/dsl_chap2_spec.rb +59 -41
  79. data/spec/glue/run_star_expression_spec.rb +69 -896
  80. data/spec/{core → rela}/conde_spec.rb +50 -46
  81. data/spec/rela/conj2_spec.rb +123 -0
  82. data/spec/rela/def_relation_spec.rb +119 -0
  83. data/spec/rela/disj2_spec.rb +117 -0
  84. data/spec/rela/fresh_spec.rb +147 -0
  85. data/spec/rela/unify_spec.rb +369 -0
  86. data/spec/support/factory_atomic.rb +7 -0
  87. data/spec/support/factory_composite.rb +21 -0
  88. metadata +71 -48
  89. data/lib/mini_kraken/core/association_walker.rb +0 -183
  90. data/lib/mini_kraken/core/base_arg.rb +0 -10
  91. data/lib/mini_kraken/core/binary_relation.rb +0 -63
  92. data/lib/mini_kraken/core/composite_goal.rb +0 -46
  93. data/lib/mini_kraken/core/conde.rb +0 -143
  94. data/lib/mini_kraken/core/conj2.rb +0 -79
  95. data/lib/mini_kraken/core/def_relation.rb +0 -53
  96. data/lib/mini_kraken/core/designation.rb +0 -55
  97. data/lib/mini_kraken/core/disj2.rb +0 -72
  98. data/lib/mini_kraken/core/environment.rb +0 -73
  99. data/lib/mini_kraken/core/equals.rb +0 -191
  100. data/lib/mini_kraken/core/formal_arg.rb +0 -22
  101. data/lib/mini_kraken/core/formal_ref.rb +0 -25
  102. data/lib/mini_kraken/core/freshness.rb +0 -45
  103. data/lib/mini_kraken/core/goal_arg.rb +0 -12
  104. data/lib/mini_kraken/core/goal_template.rb +0 -102
  105. data/lib/mini_kraken/core/outcome.rb +0 -63
  106. data/lib/mini_kraken/core/tap.rb +0 -46
  107. data/lib/mini_kraken/core/vocabulary.rb +0 -446
  108. data/lib/mini_kraken/glue/fresh_env.rb +0 -108
  109. data/lib/mini_kraken/glue/fresh_env_factory.rb +0 -83
  110. data/spec/core/association_walker_spec.rb +0 -194
  111. data/spec/core/conj2_spec.rb +0 -116
  112. data/spec/core/def_relation_spec.rb +0 -99
  113. data/spec/core/disj2_spec.rb +0 -100
  114. data/spec/core/environment_spec.rb +0 -144
  115. data/spec/core/equals_spec.rb +0 -319
  116. data/spec/core/goal_template_spec.rb +0 -74
  117. data/spec/core/outcome_spec.rb +0 -56
  118. data/spec/core/vocabulary_spec.rb +0 -220
  119. data/spec/glue/fresh_env_factory_spec.rb +0 -99
  120. 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 an enumerator specialized in the visit of cons cells.
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 an Enumerator.
13
- # The enumerator returns couples of the form: [:car or :cdr or :nil, visitee]
14
- # [anExpr] the term to visit.
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
- case cell.car
37
- when ConsCell
38
- visit_stack.push([:car, cell.car])
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
@@ -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