mini_kraken 0.1.05 → 0.1.10

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 (39) hide show
  1. checksums.yaml +4 -4
  2. data/.travis.yml +5 -1
  3. data/CHANGELOG.md +56 -0
  4. data/README.md +5 -3
  5. data/lib/mini_kraken/core/composite_goal.rb +46 -0
  6. data/lib/mini_kraken/core/composite_term.rb +2 -0
  7. data/lib/mini_kraken/core/conj2.rb +79 -0
  8. data/lib/mini_kraken/core/cons_cell.rb +49 -43
  9. data/lib/mini_kraken/core/designation.rb +55 -0
  10. data/lib/mini_kraken/core/disj2.rb +71 -0
  11. data/lib/mini_kraken/core/duck_fiber.rb +1 -1
  12. data/lib/mini_kraken/core/environment.rb +1 -1
  13. data/lib/mini_kraken/core/equals.rb +134 -132
  14. data/lib/mini_kraken/core/fail.rb +18 -14
  15. data/lib/mini_kraken/core/goal.rb +4 -2
  16. data/lib/mini_kraken/core/goal_arg.rb +10 -0
  17. data/lib/mini_kraken/core/goal_relation.rb +28 -0
  18. data/lib/mini_kraken/core/outcome.rb +40 -24
  19. data/lib/mini_kraken/core/succeed.rb +17 -13
  20. data/lib/mini_kraken/core/term.rb +5 -2
  21. data/lib/mini_kraken/core/variable.rb +3 -27
  22. data/lib/mini_kraken/core/variable_ref.rb +3 -28
  23. data/lib/mini_kraken/core/vocabulary.rb +39 -19
  24. data/lib/mini_kraken/glue/fresh_env.rb +45 -3
  25. data/lib/mini_kraken/glue/run_star_expression.rb +44 -19
  26. data/lib/mini_kraken/version.rb +1 -1
  27. data/spec/core/conj2_spec.rb +114 -0
  28. data/spec/core/cons_cell_spec.rb +8 -0
  29. data/spec/core/disj2_spec.rb +99 -0
  30. data/spec/core/duck_fiber_spec.rb +12 -1
  31. data/spec/core/equals_spec.rb +3 -3
  32. data/spec/core/outcome_spec.rb +48 -0
  33. data/spec/core/vocabulary_spec.rb +11 -5
  34. data/spec/glue/fresh_env_spec.rb +27 -1
  35. data/spec/glue/run_star_expression_spec.rb +478 -53
  36. data/spec/mini_kraken_spec.rb +2 -0
  37. data/spec/spec_helper.rb +0 -1
  38. data/spec/support/factory_methods.rb +16 -0
  39. metadata +14 -2
@@ -4,19 +4,23 @@ require 'singleton'
4
4
  require_relative 'duck_fiber'
5
5
  require_relative 'nullary_relation'
6
6
 
7
- module MiniKraken
8
- module Core
9
- # A nullary relation that unconditionally always fails.
10
- class Succeed < NullaryRelation
11
- include Singleton
7
+ unless MiniKraken::Core.constants(false).include? :Succeed
8
+ module MiniKraken
9
+ module Core
10
+ # A nullary relation that unconditionally always fails.
11
+ class Succeed < NullaryRelation
12
+ include Singleton
12
13
 
13
- def initialize
14
- super('succeed', '#s')
15
- end
14
+ def initialize
15
+ super('succeed', '#s')
16
+ end
16
17
 
17
- def solver_for(_actuals, _env)
18
- DuckFiber.new(:success)
19
- end
20
- end # class
18
+ def solver_for(_actuals, _env)
19
+ DuckFiber.new(:success)
20
+ end
21
+ end # class
22
+
23
+ Succeed.instance.freeze
24
+ end # module
21
25
  end # module
22
- end # module
26
+ end # unless
@@ -1,9 +1,12 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require_relative 'goal_arg'
4
+
3
5
  module MiniKraken
4
6
  module Core
5
- # The generalization of data value in MiniKraken
6
- class Term
7
+ # The generalization of any data value that can be
8
+ # passed as arugement to a goal.
9
+ class Term < GoalArg
7
10
  end # class
8
11
  end # module
9
12
  end # module
@@ -1,5 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require_relative 'designation'
3
4
  require_relative 'any_value'
4
5
  require_relative 'vocabulary'
5
6
 
@@ -8,32 +9,17 @@ module MiniKraken
8
9
  # Representation of a MiniKraken variable.
9
10
  # It is a named slot that can be associated with one value.
10
11
  class Variable
11
- # @return [String] User-defined name of the variable
12
- attr_reader :name
12
+ include Designation # Mixin: Acquire name attribute
13
13
 
14
14
  # @return [String] Internal variable name used by MiniKraken
15
15
  attr_accessor :i_name
16
16
 
17
17
  # @param aName [String] The name of the variable
18
18
  def initialize(aName)
19
- @name = valid_name(aName)
19
+ init_designation(aName)
20
20
  @i_name = name.dup
21
21
  end
22
22
 
23
- def fresh?(anEnvironment)
24
- anEnvironment.fresh?(self)
25
- end
26
-
27
- # @param env [Environment]
28
- # @return [Freshness]
29
- def freshness(env)
30
- env.freshness_ref(self)
31
- end
32
-
33
- def ground?(anEnvironment)
34
- !fresh?(anEnvironment)
35
- end
36
-
37
23
  def fused?
38
24
  name != i_name
39
25
  end
@@ -44,16 +30,6 @@ module MiniKraken
44
30
  val = anEnvironment.quote_ref(self)
45
31
  val.nil? ? AnyValue.new(name, anEnvironment) : val
46
32
  end
47
-
48
- private
49
-
50
- def valid_name(aName)
51
- if aName.empty?
52
- raise StandardError, 'Variable name may not be empty.'
53
- end
54
-
55
- aName
56
- end
57
33
  end # class
58
34
  end # module
59
35
  end # module
@@ -8,31 +8,12 @@ module MiniKraken
8
8
  # A variable reference represents the occurrence of a variable (name) in a
9
9
  # MiniKraken term.
10
10
  class VariableRef < Term
11
- # @return [String] Name of the variable
12
- attr_reader :var_name
11
+ include Designation # Mixin: Acquire name attribute
12
+ alias var_name name
13
13
 
14
14
  # @param aName [String] The name of the variable
15
15
  def initialize(aName)
16
- @var_name = valid_name(aName)
17
- end
18
-
19
- # @param env [Environment]
20
- # @return [Boolean]
21
- def fresh?(env)
22
- env.fresh?(self)
23
- end
24
-
25
- # @param env [Environment]
26
- # @return [Boolean]
27
- def bound?(env)
28
- freshness = env.freshness_ref(self)
29
- freshness.degree == :bound
30
- end
31
-
32
- # @param env [Environment]
33
- # @return [Boolean]
34
- def ground?(env)
35
- !fresh?(env)
16
+ init_designation(aName)
36
17
  end
37
18
 
38
19
  # @param aValue [Term]
@@ -54,12 +35,6 @@ module MiniKraken
54
35
  freshness.associated
55
36
  end
56
37
 
57
- # @param env [Environment]
58
- # @return [Freshness]
59
- def freshness(env)
60
- env.freshness_ref(self)
61
- end
62
-
63
38
  # @param env [Environment]
64
39
  def quote(env)
65
40
  val = env.quote_ref(self)
@@ -23,26 +23,28 @@ module MiniKraken
23
23
  @rankings = {} unless aParent
24
24
  end
25
25
 
26
- # Return a Fiber object that can iterate over this vocabulary and
26
+ # Return a Enumerator object that can iterate over this vocabulary and
27
27
  # all its direct and indirect parent(s).
28
- # @return [Fiber<Vocabulary, NilClass>]
28
+ # @return [Enumerator<Vocabulary, NilClass>]
29
29
  def ancestor_walker
30
- Fiber.new do
30
+ unless @ancestors # Not yet in cache?...
31
+ @ancestors = []
31
32
  relative = self
32
33
  while relative
33
- Fiber.yield relative
34
+ @ancestors << relative
34
35
  relative = relative.parent
35
36
  end
36
-
37
- Fiber.yield nil # nil marks end of iteration...
37
+ @ancestors << nil # nil marks end of iteration...
38
38
  end
39
+
40
+ @ancestors.to_enum
39
41
  end
40
42
 
41
43
  def clear_rankings
42
44
  walker = ancestor_walker
43
45
  orphan = nil
44
46
  loop do
45
- orphan_temp = walker.resume
47
+ orphan_temp = walker.next
46
48
  break unless orphan_temp
47
49
 
48
50
  orphan = orphan_temp
@@ -57,7 +59,7 @@ module MiniKraken
57
59
  walker = ancestor_walker
58
60
  orphan = nil
59
61
  loop do
60
- orphan_temp = walker.resume
62
+ orphan_temp = walker.next
61
63
  break unless orphan_temp
62
64
 
63
65
  orphan = orphan_temp
@@ -65,20 +67,22 @@ module MiniKraken
65
67
 
66
68
  raise StandardError unless orphan
67
69
 
70
+ rank = nil
68
71
  if orphan.rankings.include?(aName)
69
- orphan.rankings[aName]
72
+ rank = orphan.rankings[aName]
70
73
  else
71
74
  other = alternate_names.find do |a_name|
72
- orphan.rankings.include?(a_name)
75
+ rank = orphan.rankings.include?(a_name)
73
76
  end
74
77
  if other
75
- get_rank(other)
78
+ rank = get_rank(other)
76
79
  else
77
80
  rank = orphan.rankings.keys.size
78
81
  orphan.rankings[aName] = rank
79
- rank
80
82
  end
81
83
  end
84
+
85
+ rank
82
86
  end
83
87
 
84
88
  # Record an association between a variable with given user-defined name
@@ -182,7 +186,7 @@ module MiniKraken
182
186
  walker = ancestor_walker
183
187
 
184
188
  loop do
185
- voc = walker.resume
189
+ voc = walker.next
186
190
  break unless voc
187
191
 
188
192
  if voc.associations.include?(old_i_name)
@@ -236,7 +240,7 @@ module MiniKraken
236
240
  walker.find_ground(name, self)
237
241
  end
238
242
 
239
- # @param var [CompositeTerm] the composite term to check.
243
+ # @param val [CompositeTerm] the composite term to check.
240
244
  # @return [Boolean]
241
245
  def fresh_value?(val)
242
246
  walker = AssociationWalker.new
@@ -277,7 +281,7 @@ module MiniKraken
277
281
  walker = ancestor_walker
278
282
 
279
283
  loop do
280
- voc = walker.resume
284
+ voc = walker.next
281
285
  if voc
282
286
  next unless voc.respond_to?(:vars) && voc.vars.include?(aName)
283
287
 
@@ -291,7 +295,7 @@ module MiniKraken
291
295
  end
292
296
 
293
297
  # Return the variable with given internal variable name.
294
- # @param aName [String] internal variable name
298
+ # @param i_name [String] internal variable name
295
299
  # @return [Variable]
296
300
  def i_name2var(i_name)
297
301
  var = nil
@@ -299,7 +303,7 @@ module MiniKraken
299
303
  walker = ancestor_walker
300
304
 
301
305
  loop do
302
- voc = walker.resume
306
+ voc = walker.next
303
307
  if voc
304
308
  next unless voc.respond_to?(:ivars) && voc.ivars.include?(i_name)
305
309
 
@@ -335,7 +339,7 @@ module MiniKraken
335
339
  walker = ancestor_walker
336
340
 
337
341
  loop do
338
- voc = walker.resume
342
+ voc = walker.next
339
343
  break unless voc
340
344
  next unless voc.respond_to?(:ivars)
341
345
 
@@ -358,7 +362,7 @@ module MiniKraken
358
362
  walker = ancestor_walker
359
363
 
360
364
  loop do
361
- voc = walker.resume
365
+ voc = walker.next
362
366
  break unless voc
363
367
  next unless voc.associations.include?(i_name)
364
368
 
@@ -390,6 +394,18 @@ module MiniKraken
390
394
  name2var(aVarName) ? true : false
391
395
  end
392
396
 
397
+ def inspect
398
+ result = +"#<#{self.class.name}:#{object_id.to_s(16)} @parent="
399
+ if parent
400
+ result << "#<#{parent.class.name}:#{parent.object_id.to_s(16)}>"
401
+ else
402
+ result << nil
403
+ end
404
+ result << introspect
405
+ result << '>'
406
+ result
407
+ end
408
+
393
409
  protected
394
410
 
395
411
  def validated_parent(aParent)
@@ -416,6 +432,10 @@ module MiniKraken
416
432
  assc
417
433
  end
418
434
  end
435
+
436
+ def introspect
437
+ ''
438
+ end
419
439
  end # class
420
440
  end # module
421
441
  end # module
@@ -1,10 +1,14 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require_relative '../core/environment'
4
+ require_relative '../core/conj2'
4
5
  require_relative '../core/variable'
5
6
 
6
7
  module MiniKraken
7
8
  module Glue
9
+ # A combination of an Environment (= a scope for one or more variables)
10
+ # and a goal. It quacks like a Goal object: when receiving the attain message,
11
+ # it attempt to achieve its given goal.
8
12
  # (fresh (x) (== 'pea q))
9
13
  # Introduces the new variable 'x'
10
14
  # Takes a list of names and a goal-like object
@@ -13,11 +17,11 @@ module MiniKraken
13
17
  # @return [Goal]
14
18
  attr_reader :goal
15
19
 
16
- # @param theNames [Array<String>]
17
- # @param aGoal [Goal]
20
+ # @param theNames [Array<String>] The variable names
21
+ # @param aGoal [Goal, Array<Goal>] The goal to achieve or the conjunction of them.
18
22
  def initialize(theNames, aGoal)
19
23
  super()
20
- @goal = aGoal
24
+ @goal = valid_goal(aGoal)
21
25
  theNames.each { |nm| add_var(Core::Variable.new(nm)) }
22
26
  end
23
27
 
@@ -28,6 +32,44 @@ module MiniKraken
28
32
  self.parent = aParent
29
33
  goal.attain(self)
30
34
  end
35
+
36
+ protected
37
+
38
+ def introspect
39
+ +", @vars=[#{vars.keys.join(', ')}]"
40
+ end
41
+
42
+ private
43
+
44
+ def valid_goal(aGoal)
45
+ result = nil
46
+
47
+ case aGoal
48
+ when Core::Goal
49
+ result = aGoal
50
+ when FreshEnv
51
+ result = aGoal
52
+ when Array # an Array of Goal?..
53
+ goal_array = aGoal
54
+ loop do
55
+ conjunctions = []
56
+ goal_array.each_slice(2) do |uno_duo|
57
+ if uno_duo.size == 2
58
+ conjunctions << Core::Goal.new(Core::Conj2.instance, uno_duo)
59
+ else
60
+ conjunctions << uno_duo[0]
61
+ end
62
+ end
63
+ if conjunctions.size == 1
64
+ result = conjunctions[0]
65
+ break
66
+ end
67
+ goal_array = conjunctions
68
+ end
69
+ end
70
+
71
+ result
72
+ end
31
73
  end # class
32
74
  end # module
33
75
  end # module
@@ -9,36 +9,61 @@ module MiniKraken
9
9
  class RunStarExpression
10
10
  attr_reader :env
11
11
 
12
- # @param var_name [String]
13
- # @param goal [Core::Goal]
14
- def initialize(var_name, goal)
15
- @env = FreshEnv.new([var_name], goal)
16
- end
17
-
18
- def var
19
- env.vars.values.first
12
+ # @param var_names [String, Array<String>] One variable name or an array of names
13
+ # @param goal [Core::Goal, Array<Core::Goal>] A single goal or an array of goals to conjunct
14
+ def initialize(var_names, goal)
15
+ vnames = var_names.kind_of?(String) ? [var_names] : var_names
16
+ @env = FreshEnv.new(vnames, goal)
20
17
  end
21
18
 
22
19
  def run
23
- result = nil
20
+ result = []
24
21
  solver = env.goal.attain(env)
25
22
  # require 'debug'
26
23
  loop do
24
+ env.clear
25
+ env.clear_rankings
27
26
  outcome = solver.resume
28
27
  break if outcome.nil?
29
28
 
30
- env.clear
31
- if result # ... more than one result...
32
- elsif outcome.successful?
33
- env.propagate(outcome)
34
- # require 'debug'
35
- result = Core::ConsCell.new(var.quote(outcome))
36
- else
37
- result = Core::NullList
38
- end
29
+ env.propagate(outcome) if result.empty? && outcome.successful?
30
+ result << build_solution(outcome)
31
+ end
32
+
33
+ format_solutions(result)
34
+ end
35
+
36
+ private
37
+
38
+ # @return [Array] A vector of assignment for each variable
39
+ def build_solution(outcome)
40
+ sol = env.vars.values.map do |var|
41
+ outcome.successful? ? var.quote(outcome) : nil
42
+ end
43
+
44
+ sol
45
+ end
46
+
47
+ # Transform the solutions into sequence of conscells.
48
+ # @param solutions [Array<Array>] An array of solution.
49
+ # A solution is in itself an array of bindings (one per variable)
50
+ def format_solutions(solutions)
51
+ solutions_as_list = solutions.map { |sol| arr2list(sol, true) }
52
+ arr2list(solutions_as_list, false)
53
+ end
54
+
55
+ # Utility method. Transform an array into a ConsCell-based list.
56
+ # @param anArray [Array]
57
+ # @param simplify [Boolean]
58
+ def arr2list(anArray, simplify)
59
+ return anArray[0] if anArray.size == 1 && simplify
60
+
61
+ new_tail = nil
62
+ anArray.reverse_each do |elem|
63
+ new_tail = Core::ConsCell.new(elem, new_tail)
39
64
  end
40
65
 
41
- result
66
+ new_tail
42
67
  end
43
68
  end # class
44
69
  end # module