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
@@ -1,45 +1,47 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative 'outcome'
3
+ require_relative 'context'
4
4
 
5
5
  module MiniKraken
6
6
  module Core
7
7
  # A mock class that mimicks the behavior of a Fiber instance.
8
+ # More specifically, it responds to `resume` message & returns a Context.
8
9
  class DuckFiber
9
- # @return [Outcome] The sole outcome to yield.
10
- attr_reader :outcome
10
+ # @return [Proc, #call] The callable object to yield.
11
+ attr_reader :callable
11
12
 
12
13
  # @return [Symbol] one of: :initial, :yielded
13
14
  attr_reader :state
14
15
 
15
- # @param outcomeKind [Symbol] One of: :failure, :basic_success, :custom
16
- def initialize(outcomeKind, &customization)
16
+ # Constructor.
17
+ # @param aCallable [Proc, #call] The receiver of the 'call' message.
18
+ def initialize(aCallable)
19
+ @callable = valid_callable(aCallable)
17
20
  @state = :initial
18
- if outcomeKind == :custom && block_given?
19
- @outcome = customization.call
20
- else
21
- @outcome = valid_outcome(outcomeKind)
22
- end
23
21
  end
24
22
 
23
+ # Quacks like a Fiber object.
24
+ # The first time, this method will return a Context objet.
25
+ # Subsequents calls just return nil (= no other solution available)
26
+ # @return [Core::Context, NilClass]
25
27
  def resume(*_args)
26
28
  if state == :initial
27
29
  @state = :yielded
28
- return outcome
30
+ return callable.call
29
31
  else
30
32
  return nil
31
33
  end
32
34
  end
33
35
 
34
- def valid_outcome(outcomeKind)
35
- case outcomeKind
36
- when :failure
37
- Failure
38
- when :success
39
- Outcome.new(:"#s")
40
- else
41
- raise StandardError, "Unknonw outcome kind #{outcomeKind}"
36
+ private
37
+
38
+ def valid_callable(aCallable)
39
+ unless aCallable.kind_of?(Proc) || aCallable.respond_to?(:call)
40
+ err_msg = "Expected a Proc instead of #{aCallable.class}."
41
+ raise StandardError, err_msg
42
42
  end
43
+
44
+ aCallable
43
45
  end
44
46
  end # class
45
47
  end # module
@@ -0,0 +1,40 @@
1
+ # frozen_string_literal: true
2
+
3
+ module MiniKraken
4
+ module Core
5
+ # Mix-in module that implements the expected common behaviour of entries
6
+ # placed in the symbol table.
7
+ module Entry
8
+ # @return [String] User-defined name of the entry.
9
+ attr_reader :name
10
+
11
+ # @return [String] Suffix for building the internal name of the entry.
12
+ attr_accessor :suffix
13
+
14
+ alias label name
15
+
16
+ # Initialize the entry with given name
17
+ # @param aName [String] The name of the entry
18
+ def init_name(aName)
19
+ @name = aName.dup
20
+ @name.freeze
21
+ end
22
+
23
+ # Return the internal name of the entry
24
+ # Internal names used to disambiguate entry names.
25
+ # There might be homonyns between variable because:
26
+ # - A child Scope may have a entry with same name as one of its
27
+ # ancestor(s).
28
+ # - Multiple calls to same defrel or procedure may imply multiple creation
29
+ # of a entry given name...
30
+ # @return [String] internal name
31
+ def i_name
32
+ if suffix =~ /^_/
33
+ label + suffix
34
+ else
35
+ (suffix.nil? || suffix.empty?) ? label : suffix
36
+ end
37
+ end
38
+ end # module
39
+ end # module
40
+ end # module
@@ -4,24 +4,26 @@ require 'singleton'
4
4
  require_relative 'duck_fiber'
5
5
  require_relative 'nullary_relation'
6
6
 
7
- unless MiniKraken::Core.constants(false).include? :Fail
8
- module MiniKraken
9
- module Core
10
- # A nullary relation that unconditionally always fails.
11
- class Fail < NullaryRelation
12
- include Singleton
7
+ module MiniKraken
8
+ module Core
9
+ # A nullary relation that always returns a failure outcome.
10
+ class Fail < NullaryRelation
11
+ include Singleton
13
12
 
14
- def initialize
15
- super('fail', '#u')
16
- end
13
+ # Constructor. Initialize the relation's name & freeze it...
14
+ def initialize
15
+ super('fail')
16
+ end
17
17
 
18
- # @return [DuckFiber]
19
- def solver_for(_actuals, _env)
20
- DuckFiber.new(:failure)
21
- end
22
- end # class
23
-
24
- Fail.instance.freeze
25
- end # module
18
+ # Returns a Fiber-like object (a DuckFiber).
19
+ # When that object receives the message resume, it will
20
+ # signal a failure to the provided context.
21
+ # @param _actuals [Array] MUST be empty array for nullary relation.
22
+ # @param ctx [Core::Context] Runtime context
23
+ # @return [Core::DuckFiber]
24
+ def solver_for(_actuals, ctx)
25
+ DuckFiber.new(-> { ctx.failed! })
26
+ end
27
+ end # class
26
28
  end # module
27
- end # unless
29
+ end # module
@@ -0,0 +1,29 @@
1
+ # frozen_string_literal: true
2
+
3
+
4
+ module MiniKraken
5
+ module Core
6
+ # A record of the fusion / merge of two or more logical variables.
7
+ class Fusion
8
+ # @return [String] i_name of substituting variable.
9
+ attr_reader :i_name
10
+
11
+ # @return [Array<String>] i_names of variables being substituted
12
+ attr_reader :elements
13
+
14
+ # Records the fusion of two or more logical variables.
15
+ # The fused variables are substituted by a new variable
16
+ # @param aName [String] Internal name of the substituting variable
17
+ # @param fused [Array<String>] The i_names of the fused variables
18
+ def initialize(aName, fused)
19
+ @i_name = aName
20
+ @elements = fused
21
+ end
22
+
23
+ # @return [Set] The empty set
24
+ def dependencies(_ctx)
25
+ @dependencies ||= Set.new(elements)
26
+ end
27
+ end # class
28
+ end # module
29
+ end # module
@@ -1,48 +1,39 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative 'environment'
3
+ require_relative 'parametrized_term'
4
+ require_relative 'context'
4
5
 
5
6
  module MiniKraken
6
- require_relative 'goal_arg'
7
-
8
7
  module Core
9
- class Goal < GoalArg
10
- # @return [Relation] The relation corresponding to this goal
11
- attr_reader :relation
12
-
13
- # @return [Array<Term>] The actual aguments of the goal
14
- attr_reader :actuals
15
-
16
- # @param aRelation [Relation] The relation corresponding to this goal
17
- # @param args [Array<Term>] The actual aguments of the goal
18
- def initialize(aRelation, args)
19
- super()
20
- @relation = aRelation
21
- @actuals = validated_actuals(args)
22
- end
23
-
24
- # Attempt to achieve the goal for a given context (environment)
25
- # @param anEnv [Environment] The context in which the goal take place.
26
- # @return [Fiber<Outcome>] A Fiber object that will generate the results.
27
- def attain(anEnv)
28
- relation.solver_for(actuals, anEnv)
8
+ class Goal < ParametrizedTerm
9
+ alias relation specification
10
+
11
+ # Attempt to obtain one or more solutions for the goal in a given context.
12
+ # @param ctx [Core::Context] The context in which the goal takes place.
13
+ # @return [Fiber<Context>] A Fiber object that will generate the results.
14
+ def achieve(ctx)
15
+ relation.solver_for(actuals, ctx)
29
16
  end
30
17
 
31
18
  private
32
19
 
33
- def validated_actuals(args)
34
- if !relation.polyadic? && (args.size != relation.arity)
35
- err_msg = "Goal has #{args.size} arguments, expected #{relation.arity}"
36
- raise StandardError, err_msg
20
+ def validated_specification(theSpec)
21
+ spec = super(theSpec)
22
+ unless spec.kind_of?(Relation)
23
+ raise StandardError, "Expected a Relation instead of #{theSpec.class}."
37
24
  end
38
25
 
39
- prefix = 'Invalid goal argument'
26
+ spec
27
+ end
28
+
29
+ def validated_actuals(args)
40
30
  args.each do |actual|
41
- if actual.kind_of?(GoalArg) || actual.kind_of?(Environment)
31
+ if actual.kind_of?(Term) || actual.respond_to?(:attain)
42
32
  next
43
33
  elsif actual.kind_of?(Array)
44
34
  validated_actuals(actual)
45
35
  else
36
+ prefix = 'Invalid goal argument'
46
37
  actual_display = actual.nil? ? 'nil' : actual.to_s
47
38
  raise StandardError, "#{prefix} '#{actual_display}'"
48
39
  end
@@ -0,0 +1,22 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'entry'
4
+
5
+ module MiniKraken
6
+ module Core
7
+ # Representation of a MiniKraken logical variable.
8
+ # It is a named slot that can be associated with one value at the time.
9
+ # In relational programming, there is no explicit assignment expression.
10
+ # A logical variable acquires a value through an algorithm called
11
+ # 'unification'.
12
+ class LogVar
13
+ include Entry # Add expected behaviour for symbol table entries
14
+
15
+ # Create a logical variable with given name
16
+ # @param aName [String] The name of the variable
17
+ def initialize(aName)
18
+ init_name(aName)
19
+ end
20
+ end # class
21
+ end # module
22
+ end # module
@@ -0,0 +1,108 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'set'
4
+ require_relative 'term'
5
+
6
+ module MiniKraken
7
+ module Core
8
+ # Representation of a reference to a MiniKraken logical variable.
9
+ class LogVarRef < Term
10
+ # @return [String] User-friendly name of the variable.
11
+ attr_reader :name
12
+
13
+ # @return [String] Unique internal name of the variable.
14
+ attr_accessor :i_name
15
+
16
+ # Create a reference to a logical variable with given name
17
+ # @param aName [String] The name of the variable
18
+ def initialize(aName)
19
+ super()
20
+ init_name(aName)
21
+ end
22
+
23
+ # Return a text representation of this logical variable reference.
24
+ # @return [String]
25
+ def to_s
26
+ name
27
+ end
28
+
29
+ # Is the related log variable unbound in the given context?
30
+ # A log var is unbound when there is no association for the variable.
31
+ # @param aContext [Core::Context]
32
+ # @return [Boolean] true if log var is unbound
33
+ def unbound?(aContext)
34
+ vr = aContext.lookup(name)
35
+ raise StandardError, "Unknown variable #{name}" unless vr
36
+
37
+ bindings = aContext.associations_for(name)
38
+ bindings.empty? || (bindings.size == 1 && bindings[0].kind_of?(Fusion))
39
+ end
40
+
41
+ # Does the variable have at least one association AND
42
+ # each of these association refer to at least one unbound variable
43
+ # or a floating variable?
44
+ # @param aContext [Core::Context]
45
+ # @return [Boolean] true if log var is floating
46
+ def floating?(aContext)
47
+ vr = aContext.lookup(name)
48
+ raise StandardError, "Unknown variable #{name}" unless vr
49
+
50
+ assocs = aContext.associations_for(name)
51
+ unless assocs.empty?
52
+ assocs.none? { |as| as.pinned?(aContext) }
53
+ else
54
+ false
55
+ end
56
+ end
57
+
58
+ # Is the variable pinned?
59
+ # In other words, does the referenced variable have a definite value?
60
+ # @param aContext [Core::Context]
61
+ # @return [Boolean] true if log var is pinned
62
+ def pinned?(aContext)
63
+ return true if @pinned
64
+
65
+ vr = aContext.lookup(name)
66
+ raise StandardError, "Unknown variable #{name}" unless vr
67
+
68
+ assocs = aContext.associations_for(name)
69
+ unless assocs.empty?
70
+ @pinned = assocs.all? { |as| as.pinned?(aContext) }
71
+ else
72
+ false
73
+ end
74
+ end
75
+
76
+ # Return the list of variable (i_names) that this term depends on.
77
+ # For a variable reference, it will return the i_names of its variable
78
+ # @param ctx [Core::Context]
79
+ # @return [Set<String>] a set containing the i_name of the variable
80
+ def dependencies(ctx)
81
+ @i_name ||= ctx.lookup(name).i_name
82
+ s = Set.new
83
+ s << i_name
84
+ s
85
+ end
86
+
87
+ # Make a copy of self with all the variable reference being
88
+ # replaced by the corresponding value in the Hash.
89
+ # @param substitutions [Hash {String => Term}]
90
+ # @return [Term]
91
+ def dup_cond(substitutions)
92
+ key = i_name || name
93
+ if substitutions.include? key
94
+ val = substitutions[key]
95
+ val.kind_of?(Term) ? val.dup_cond(substitutions) : val
96
+ else
97
+ dup
98
+ end
99
+ end
100
+
101
+ private
102
+
103
+ def init_name(aName)
104
+ @name = aName.dup
105
+ end
106
+ end # class
107
+ end # module
108
+ end # module
@@ -6,17 +6,10 @@ module MiniKraken
6
6
  module Core
7
7
  class NullaryRelation < Relation
8
8
  # @param aName [String] Name of the relation.
9
- # @param alternateName [String, NilClass] Alternative name (optional).
10
- def initialize(aName, alternateName = nil)
11
- super(aName, alternateName)
9
+ def initialize(aName)
10
+ super(aName, 0)
12
11
  freeze
13
12
  end
14
-
15
- # Number of arguments for the relation.
16
- # @return [Integer]
17
- def arity
18
- 0
19
- end
20
13
  end # class
21
14
  end # module
22
15
  end # module
@@ -0,0 +1,68 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'term'
4
+ require_relative 'specification'
5
+
6
+ module MiniKraken
7
+ module Core
8
+ # A specialization of Term class for objects that take arguments.
9
+ class ParametrizedTerm < Term
10
+ # @return [Specification] The specification that must be invoked with arguments.
11
+ attr_reader :specification
12
+
13
+ # @return [Array<Term>] The actual aguments of the goal
14
+ attr_reader :actuals
15
+
16
+ # Constructor.
17
+ # @param theSpecification [Specification] The callable object.
18
+ # @param theArgs [Array<Term>] The actual aguments
19
+ def initialize(theSpecification, theArgs)
20
+ super()
21
+ @specification = validated_specification(theSpecification)
22
+ args = specification.check_arity(theArgs)
23
+ @actuals = validated_actuals(args)
24
+ end
25
+
26
+ def initialize_copy(orig)
27
+ @specification = orig.specification
28
+ @actuals = []
29
+ end
30
+
31
+ # Make a copy of self with all the variable reference being
32
+ # replaced by the corresponding value in the Hash.
33
+ # @param substitutions [Hash {String => Term}]
34
+ # @return [Term]
35
+ def dup_cond(substitutions)
36
+ duplicate = dup
37
+
38
+ updated_actuals = actuals.map do |e|
39
+ if e.is_a?(Array)
40
+ e.map {|item| item.dup_cond(substitutions) }
41
+ else
42
+ e.dup_cond(substitutions)
43
+ end
44
+ end
45
+ duplicate.actuals.concat(updated_actuals)
46
+
47
+ duplicate
48
+ end
49
+
50
+ protected
51
+
52
+ def validated_specification(theSpecification)
53
+ unless theSpecification.kind_of?(Specification)
54
+ msg_part1 = 'Expected kind_of Specification,'
55
+ msg_part2 = "instead of #{theSpecification.class}."
56
+ raise StandardError, "#{msg_part1} #{msg_part2}"
57
+ end
58
+
59
+ theSpecification
60
+ end
61
+
62
+ # This method should be overridden in subclasses
63
+ def validated_actuals(args)
64
+ args
65
+ end
66
+ end # class
67
+ end # module
68
+ end # module