mini_kraken 0.1.06 → 0.1.11

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 (42) hide show
  1. checksums.yaml +4 -4
  2. data/.travis.yml +5 -1
  3. data/CHANGELOG.md +56 -1
  4. data/README.md +11 -3
  5. data/lib/mini_kraken/core/association_walker.rb +1 -1
  6. data/lib/mini_kraken/core/base_arg.rb +10 -0
  7. data/lib/mini_kraken/core/conj2.rb +58 -65
  8. data/lib/mini_kraken/core/cons_cell.rb +49 -43
  9. data/lib/mini_kraken/core/def_relation.rb +49 -0
  10. data/lib/mini_kraken/core/disj2.rb +72 -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 -126
  14. data/lib/mini_kraken/core/fail.rb +18 -14
  15. data/lib/mini_kraken/core/formal_arg.rb +22 -0
  16. data/lib/mini_kraken/core/formal_ref.rb +24 -0
  17. data/lib/mini_kraken/core/goal_arg.rb +5 -3
  18. data/lib/mini_kraken/core/goal_relation.rb +13 -0
  19. data/lib/mini_kraken/core/goal_template.rb +60 -0
  20. data/lib/mini_kraken/core/k_boolean.rb +31 -0
  21. data/lib/mini_kraken/core/outcome.rb +40 -24
  22. data/lib/mini_kraken/core/succeed.rb +17 -13
  23. data/lib/mini_kraken/core/vocabulary.rb +37 -17
  24. data/lib/mini_kraken/glue/fresh_env.rb +45 -3
  25. data/lib/mini_kraken/glue/run_star_expression.rb +45 -19
  26. data/lib/mini_kraken/version.rb +1 -1
  27. data/spec/core/conj2_spec.rb +8 -9
  28. data/spec/core/cons_cell_spec.rb +8 -0
  29. data/spec/core/def_relation_spec.rb +96 -0
  30. data/spec/core/disj2_spec.rb +99 -0
  31. data/spec/core/duck_fiber_spec.rb +12 -1
  32. data/spec/core/equals_spec.rb +3 -3
  33. data/spec/core/goal_template_spec.rb +74 -0
  34. data/spec/core/k_boolean_spec.rb +107 -0
  35. data/spec/core/outcome_spec.rb +48 -0
  36. data/spec/core/vocabulary_spec.rb +11 -5
  37. data/spec/glue/fresh_env_spec.rb +27 -1
  38. data/spec/glue/run_star_expression_spec.rb +538 -70
  39. data/spec/mini_kraken_spec.rb +2 -0
  40. data/spec/spec_helper.rb +0 -1
  41. data/spec/support/factory_methods.rb +17 -1
  42. metadata +19 -2
@@ -0,0 +1,31 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'atomic_term'
4
+
5
+ module MiniKraken
6
+ module Core
7
+ # A specialized atomic term that represents an boolean (true/false) value.
8
+ # in MiniKraken
9
+ class KBoolean < AtomicTerm
10
+ # @param aValue [Boolean, Symbol] Ruby representation of boolean value
11
+ def initialize(aValue)
12
+ super(validated_value(aValue))
13
+ end
14
+
15
+ private
16
+
17
+ def validated_value(aValue)
18
+ case aValue
19
+ when true, false
20
+ aValue
21
+ when :"#t", '#t'
22
+ true
23
+ when :"#f", '#f'
24
+ false
25
+ else
26
+ raise StandardError, "Invalid boolean literal '#{aValue}'"
27
+ end
28
+ end
29
+ end # class
30
+ end # module
31
+ end # module
@@ -2,36 +2,52 @@
2
2
 
3
3
  require_relative 'vocabulary'
4
4
 
5
- module MiniKraken
6
- module Core
7
- class Outcome
8
- include Vocabulary # Use mix-in module
5
+ unless MiniKraken::Core.constants(false).include? :Outcome
6
+ module MiniKraken
7
+ module Core
8
+ class Outcome
9
+ include Vocabulary # Use mix-in module
10
+
11
+ # @return [Symbol] One of: :"#s" (success), :"#u" (failure)
12
+ attr_reader :resultant
13
+
14
+ def initialize(aResult, aParent = nil)
15
+ init_vocabulary(aParent)
16
+ @resultant = aResult
17
+ end
9
18
 
10
- # @return [Symbol] One of: :"#s" (success), :"#u" (failure)
11
- attr_reader :resultant
19
+ def self.failure(aParent = nil)
20
+ new(:"#u", aParent)
21
+ end
12
22
 
13
- def initialize(aResult, aParent = nil)
14
- init_vocabulary(aParent)
15
- @resultant = aResult
16
- end
23
+ def self.success(aParent = nil)
24
+ new(:"#s", aParent)
25
+ end
17
26
 
18
- def successful?
19
- resultant == :"#s"
20
- end
27
+ def successful?
28
+ resultant == :"#s"
29
+ end
21
30
 
22
- def ==(other)
23
- are_equal = false
31
+ def ==(other)
32
+ are_equal = false
24
33
 
25
- if resultant == other.resultant && parent == other.parent &&
26
- associations == other.associations
27
- are_equal = true
34
+ if resultant == other.resultant && parent == other.parent &&
35
+ associations == other.associations
36
+ are_equal = true
37
+ end
38
+
39
+ are_equal
28
40
  end
29
41
 
30
- are_equal
31
- end
32
- end # class
42
+ protected
43
+
44
+ def introspect
45
+ ", @resultant=#{resultant}"
46
+ end
47
+ end # class
33
48
 
34
- Failure = Outcome.new(:"#u")
35
- BasicSuccess = Outcome.new(:"#s")
49
+ Failure = Outcome.new(:"#u")
50
+ BasicSuccess = Outcome.new(:"#s")
51
+ end # module
36
52
  end # module
37
- end # module
53
+ end # defined
@@ -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
@@ -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)
@@ -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
 
@@ -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
@@ -7,38 +7,64 @@ require_relative 'fresh_env'
7
7
  module MiniKraken
8
8
  module Glue
9
9
  class RunStarExpression
10
+ # @return [FreshEnv] The environment in which run* variables will reside.
10
11
  attr_reader :env
11
12
 
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
13
+ # @param var_names [String, Array<String>] One variable name or an array of names
14
+ # @param goal [Core::Goal, Array<Core::Goal>] A single goal or an array of goals to conjunct
15
+ def initialize(var_names, goal)
16
+ vnames = var_names.kind_of?(String) ? [var_names] : var_names
17
+ @env = FreshEnv.new(vnames, goal)
20
18
  end
21
19
 
22
20
  def run
23
- result = nil
21
+ result = []
24
22
  solver = env.goal.attain(env)
25
23
  # require 'debug'
26
24
  loop do
25
+ env.clear
26
+ env.clear_rankings
27
27
  outcome = solver.resume
28
28
  break if outcome.nil?
29
29
 
30
- env.clear
31
- if result # ... more than one result...
32
- elsif outcome.successful?
33
- env.propagate(outcome)
34
- result = Core::ConsCell.new(var.quote(outcome))
35
- else
36
- result = Core::NullList
37
- env.associations.freeze
38
- end
30
+ env.propagate(outcome) if result.empty? && outcome.successful?
31
+ result << build_solution(outcome)
32
+ end
33
+
34
+ format_solutions(result)
35
+ end
36
+
37
+ private
38
+
39
+ # @return [Array] A vector of assignment for each variable
40
+ def build_solution(outcome)
41
+ sol = env.vars.values.map do |var|
42
+ outcome.successful? ? var.quote(outcome) : nil
43
+ end
44
+
45
+ sol
46
+ end
47
+
48
+ # Transform the solutions into sequence of conscells.
49
+ # @param solutions [Array<Array>] An array of solution.
50
+ # A solution is in itself an array of bindings (one per variable)
51
+ def format_solutions(solutions)
52
+ solutions_as_list = solutions.map { |sol| arr2list(sol, true) }
53
+ arr2list(solutions_as_list, false)
54
+ end
55
+
56
+ # Utility method. Transform an array into a ConsCell-based list.
57
+ # @param anArray [Array]
58
+ # @param simplify [Boolean]
59
+ def arr2list(anArray, simplify)
60
+ return anArray[0] if anArray.size == 1 && simplify
61
+
62
+ new_tail = nil
63
+ anArray.reverse_each do |elem|
64
+ new_tail = Core::ConsCell.new(elem, new_tail)
39
65
  end
40
66
 
41
- result
67
+ new_tail
42
68
  end
43
69
  end # class
44
70
  end # module
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module MiniKraken
4
- VERSION = '0.1.06'
4
+ VERSION = '0.1.11'
5
5
  end