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
@@ -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
@@ -1,47 +1,21 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative 'designation'
4
- require_relative 'any_value'
5
- require_relative 'vocabulary'
3
+ require_relative 'entry'
6
4
 
7
5
  module MiniKraken
8
6
  module Core
9
7
  # Representation of a MiniKraken logical variable.
10
8
  # It is a named slot that can be associated with one value at the time.
11
9
  # In relational programming, there is no explicit assignment expression.
12
- # A logical variable acquire a value through an algorithm called
10
+ # A logical variable acquires a value through an algorithm called
13
11
  # 'unification'.
14
12
  class LogVar
15
- include Designation # Mixin: Acquire name attribute
16
-
17
- # @return [String] Internal variable name used by MiniKraken
18
- attr_accessor :i_name
13
+ include Entry # Add expected behaviour for symbol table entries
19
14
 
20
15
  # Create a logical variable with given name
21
16
  # @param aName [String] The name of the variable
22
17
  def initialize(aName)
23
- init_designation(aName)
24
- @i_name = name.dup
25
- end
26
-
27
- # Indicate whether this variable is fused with another one.
28
- # @return [Boolean]
29
- def fused?
30
- name != i_name
31
- end
32
-
33
- # @param [Core::Vocabulary]
34
- def quote(env)
35
- raise StandardError, "class #{env}" unless env.kind_of?(Vocabulary)
36
-
37
- val = env.quote_ref(self)
38
- unless val
39
- result = AnyValue.new(name, env, env.names_fused(name))
40
- else
41
- result = val
42
- end
43
-
44
- result
18
+ init_name(aName)
45
19
  end
46
20
  end # class
47
21
  end # module
@@ -1,83 +1,107 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require 'set'
3
4
  require_relative 'term'
4
- require_relative 'designation'
5
- require_relative 'any_value'
6
5
 
7
6
  module MiniKraken
8
7
  module Core
9
- # A variable reference represents the occurrence of a variable (name) in a
10
- # MiniKraken term.
8
+ # Representation of a reference to a MiniKraken logical variable.
11
9
  class LogVarRef < Term
12
- include Designation # Mixin: Acquire name attribute
13
- alias var_name name
10
+ # @return [String] User-friendly name of the variable.
11
+ attr_reader :name
14
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
15
17
  # @param aName [String] The name of the variable
16
18
  def initialize(aName)
17
19
  super()
18
- init_designation(aName)
19
- name.freeze
20
+ init_name(aName)
20
21
  end
21
22
 
23
+ # Return a text representation of this logical variable reference.
24
+ # @return [String]
22
25
  def to_s
23
26
  name
24
27
  end
25
28
 
26
- # @param aValue [Term]
27
- # @param env [Environment]
28
- def associate(aValue, env)
29
- env.add_assoc(var_name, aValue)
30
- end
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
31
36
 
32
- # @param env [Environment]
33
- # @return [Array<Term>]
34
- def values(env)
35
- env[var_name].map(&:value)
37
+ bindings = aContext.associations_for(name)
38
+ bindings.empty? || (bindings.size == 1 && bindings[0].kind_of?(Fusion))
36
39
  end
37
40
 
38
- # @param env [Environment]
39
- # @return [Term, NilClass]
40
- def value(env)
41
- freshness = env.freshness_ref(self)
42
- freshness.associated
43
- end
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
44
49
 
45
- # @param env [Environment]
46
- def quote(env)
47
- val = env.quote_ref(self)
48
- val.nil? ? AnyValue.new(var_name, env, names_fused(env)) : val
50
+ assocs = aContext.associations_for(name)
51
+ unless assocs.empty?
52
+ assocs.none? { |as| as.pinned?(aContext) }
53
+ else
54
+ false
55
+ end
49
56
  end
50
57
 
51
- # param another [LogVarRef]
52
- # @param env [Environment]
53
- # @return [Boolean]
54
- def fused_with?(another, env)
55
- my_var = env.name2var(var_name)
56
- return false unless my_var.fused?
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
57
67
 
58
- other_var = env.name2var(another.var_name)
59
- return my_var.i_name == other_var.i_name
68
+ assocs = aContext.associations_for(name)
69
+ unless assocs.empty?
70
+ @pinned = assocs.all? { |as| as.pinned?(aContext) }
71
+ else
72
+ false
73
+ end
60
74
  end
61
75
 
62
- def names_fused(env)
63
- env.names_fused(var_name)
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
64
85
  end
65
86
 
66
- # param another [LogVarRef]
67
- # @param env [Environment]
68
- # @return [Boolean]
69
- def different_from?(another, env)
70
- !fused_with?(another, env)
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
71
99
  end
72
100
 
73
101
  private
74
102
 
75
- def valid_name(aName)
76
- if aName.empty?
77
- raise StandardError, 'Variable name may not be empty.'
78
- end
79
-
80
- aName
103
+ def init_name(aName)
104
+ @name = aName.dup
81
105
  end
82
106
  end # class
83
107
  end # module