mini_kraken 0.1.12 → 0.2.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 (49) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +334 -0
  3. data/CHANGELOG.md +54 -0
  4. data/README.md +95 -13
  5. data/lib/mini_kraken.rb +7 -1
  6. data/lib/mini_kraken/core/any_value.rb +5 -1
  7. data/lib/mini_kraken/core/atomic_term.rb +1 -0
  8. data/lib/mini_kraken/core/conde.rb +1 -1
  9. data/lib/mini_kraken/core/conj2.rb +3 -3
  10. data/lib/mini_kraken/core/cons_cell.rb +29 -1
  11. data/lib/mini_kraken/core/cons_cell_visitor.rb +102 -0
  12. data/lib/mini_kraken/core/def_relation.rb +4 -0
  13. data/lib/mini_kraken/core/disj2.rb +2 -2
  14. data/lib/mini_kraken/core/environment.rb +2 -2
  15. data/lib/mini_kraken/core/equals.rb +60 -26
  16. data/lib/mini_kraken/core/formal_ref.rb +2 -1
  17. data/lib/mini_kraken/core/goal.rb +4 -2
  18. data/lib/mini_kraken/core/goal_template.rb +44 -2
  19. data/lib/mini_kraken/core/k_boolean.rb +4 -0
  20. data/lib/mini_kraken/core/k_symbol.rb +11 -0
  21. data/lib/mini_kraken/core/outcome.rb +11 -1
  22. data/lib/mini_kraken/core/variable.rb +10 -4
  23. data/lib/mini_kraken/core/variable_ref.rb +7 -0
  24. data/lib/mini_kraken/core/vocabulary.rb +8 -3
  25. data/lib/mini_kraken/glue/dsl.rb +236 -0
  26. data/lib/mini_kraken/glue/fresh_env.rb +31 -3
  27. data/lib/mini_kraken/glue/fresh_env_factory.rb +83 -0
  28. data/lib/mini_kraken/glue/run_star_expression.rb +3 -5
  29. data/lib/mini_kraken/version.rb +1 -1
  30. data/mini_kraken.gemspec +6 -3
  31. data/spec/.rubocop.yml +13 -0
  32. data/spec/core/conde_spec.rb +10 -10
  33. data/spec/core/conj2_spec.rb +7 -7
  34. data/spec/core/cons_cell_spec.rb +35 -0
  35. data/spec/core/cons_cell_visitor_spec.rb +144 -0
  36. data/spec/core/def_relation_spec.rb +6 -5
  37. data/spec/core/disj2_spec.rb +5 -5
  38. data/spec/core/duck_fiber_spec.rb +2 -2
  39. data/spec/core/equals_spec.rb +34 -21
  40. data/spec/core/goal_spec.rb +2 -2
  41. data/spec/core/k_boolean_spec.rb +6 -0
  42. data/spec/core/k_symbol_spec.rb +4 -0
  43. data/spec/core/outcome_spec.rb +8 -0
  44. data/spec/core/variable_ref_spec.rb +3 -0
  45. data/spec/glue/dsl_chap1_spec.rb +679 -0
  46. data/spec/glue/dsl_chap2_spec.rb +100 -0
  47. data/spec/glue/fresh_env_factory_spec.rb +97 -0
  48. data/spec/glue/run_star_expression_spec.rb +11 -11
  49. metadata +17 -4
@@ -1,8 +1,14 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'mini_kraken/version'
3
+ # This file acts as a jumping-off point for loading dependencies expected
4
+ # for a MiniKraken client.
5
+
6
+ require_relative './mini_kraken/version'
7
+ require_relative './mini_kraken/glue/dsl'
4
8
 
5
9
  module MiniKraken
6
10
  class Error < StandardError; end
7
11
  # Your code goes here...
8
12
  end
13
+
14
+ # End of file
@@ -12,7 +12,11 @@ module MiniKraken
12
12
  end
13
13
 
14
14
  def ==(other)
15
- rank == other.rank
15
+ if other.is_a?(AnyValue)
16
+ rank == other.rank
17
+ elsif other.id2name =~ /_\d+/
18
+ rank == other.id2name.sub(/_/, '').to_i
19
+ end
16
20
  end
17
21
 
18
22
  # Use same text representation as in Reasoned Schemer.
@@ -13,6 +13,7 @@ module MiniKraken
13
13
 
14
14
  # @param aValue [Object] Ruby representation of MiniKraken data value
15
15
  def initialize(aValue)
16
+ super()
16
17
  @value = aValue
17
18
  @value.freeze
18
19
  end
@@ -63,7 +63,7 @@ unless MiniKraken::Core.constants(false).include? :Conde
63
63
  break unless outcome
64
64
 
65
65
  outcome.parent = voc unless outcome.parent
66
- if outcome.successful?
66
+ if outcome.success?
67
67
  success = true
68
68
  Fiber.yield outcome
69
69
  outcome.clear
@@ -43,14 +43,14 @@ unless MiniKraken::Core.constants(false).include? :Conj2
43
43
  break unless outcome1
44
44
 
45
45
  outcome1.parent = voc unless outcome1.parent
46
- if outcome1.successful?
46
+ if outcome1.success?
47
47
  f2 = g2.attain(outcome1)
48
48
  loop do
49
49
  outcome2 = f2.resume
50
50
  break unless outcome2
51
51
 
52
52
  outcome2.parent = voc unless outcome2.parent
53
- if outcome2.successful?
53
+ if outcome2.success?
54
54
  res = Outcome.new(:"#s", voc)
55
55
  res.merge(outcome1)
56
56
  res.merge(outcome2)
@@ -63,7 +63,7 @@ unless MiniKraken::Core.constants(false).include? :Conj2
63
63
  else
64
64
  Fiber.yield outcome1
65
65
  end
66
- if outcome1.successful? && (outcome2&.successful? || outcome2.nil?)
66
+ if outcome1.success? && (outcome2&.success? || outcome2.nil?)
67
67
  voc.clear
68
68
  end
69
69
  end
@@ -10,8 +10,13 @@ unless MiniKraken::Core.constants(false).include? :ConsCell
10
10
  attr_reader :cdr
11
11
 
12
12
  def initialize(obj1, obj2 = nil)
13
+ super()
13
14
  @car = obj1
14
- @cdr = obj2
15
+ if obj2.kind_of?(ConsCell) && obj2.null?
16
+ @cdr = nil
17
+ else
18
+ @cdr = obj2
19
+ end
15
20
  end
16
21
 
17
22
  def children
@@ -42,9 +47,32 @@ unless MiniKraken::Core.constants(false).include? :ConsCell
42
47
  ConsCell.new(new_car, new_cdr)
43
48
  end
44
49
 
50
+ # Use the list notation from Lisp as a text representation.
51
+ def to_s
52
+ return '()' if null?
53
+
54
+ "(#{pair_to_s})"
55
+ end
56
+
45
57
  def append(another)
46
58
  @cdr = another
47
59
  end
60
+
61
+ protected
62
+
63
+ def pair_to_s
64
+ result = +car.to_s
65
+ if cdr
66
+ result << ' '
67
+ if cdr.kind_of?(ConsCell)
68
+ result << cdr.pair_to_s
69
+ else
70
+ result << ". #{cdr}"
71
+ end
72
+ end
73
+
74
+ result
75
+ end
48
76
  end # class
49
77
 
50
78
  # Constant representing the null (empty) list.
@@ -0,0 +1,102 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'set'
4
+ require_relative 'cons_cell'
5
+
6
+ module MiniKraken
7
+ module Core
8
+ # Factory class.
9
+ # Purpose: to create an enumerator specialized in the visit of cons cells.
10
+ class ConsCellVisitor
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.
15
+ # @param aCell [ConsCell]
16
+ # @return [Fiber]
17
+ def self.df_visitor(aCell)
18
+ first = aCell # The visit will start from the provided cons cell
19
+ visitor = Fiber.new do |skipping|
20
+ # Initialization part: will run once
21
+ visitees = Set.new # Keep track of the conscell already visited
22
+ visit_stack = first.nil? ? [] : [[:car, first]] # The LIFO queue of cells to visit
23
+
24
+ until visit_stack.empty? # Traversal part (as a loop)
25
+ to_swap = false
26
+ side, cell = visit_stack.pop
27
+ next if visitees.include?(cell)
28
+
29
+ visitees << cell
30
+
31
+ skip_children = Fiber.yield [side, cell]
32
+ # require 'debug' if skip_children
33
+ next if skip_children || skipping
34
+
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]
53
+ end
54
+ end
55
+
56
+ # Send stop mark
57
+ Fiber.yield [:stop, nil]
58
+ end
59
+
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
+ return visitor
99
+ end
100
+ end # class
101
+ end # module
102
+ end # module
@@ -15,10 +15,12 @@ module MiniKraken
15
15
 
16
16
  # @param aName [String] name of def relation
17
17
  # @param aGoalTemplate [GoalTemplate]
18
+ # @param theFormals [Array<FormalArg>]
18
19
  def initialize(aName, aGoalTemplate, theFormals, alternateName = nil)
19
20
  super(aName, alternateName)
20
21
  @formals = validated_formals(theFormals)
21
22
  @goal_template = validated_goal_template(aGoalTemplate)
23
+ freeze
22
24
  end
23
25
 
24
26
  # Number of arguments for the relation.
@@ -42,6 +44,8 @@ module MiniKraken
42
44
  end
43
45
 
44
46
  def validated_goal_template(aGoalTemplate)
47
+ raise StandardError unless aGoalTemplate
48
+
45
49
  aGoalTemplate
46
50
  end
47
51
  end # class
@@ -44,7 +44,7 @@ unless MiniKraken::Core.constants(false).include? :Disj2
44
44
  break unless outcome1
45
45
 
46
46
  outcome1.parent = voc unless outcome1.parent
47
- if outcome1.successful?
47
+ if outcome1.success?
48
48
  Fiber.yield outcome1
49
49
  outcome1.clear
50
50
  end
@@ -55,7 +55,7 @@ unless MiniKraken::Core.constants(false).include? :Disj2
55
55
  break unless outcome2
56
56
 
57
57
  outcome2.parent = voc unless outcome2.parent
58
- if outcome2.successful?
58
+ if outcome2.success?
59
59
  Fiber.yield outcome2
60
60
  outcome2.clear
61
61
  end
@@ -57,10 +57,10 @@ module MiniKraken
57
57
  # Roll up associations from descendent outcome object
58
58
  # @param descendent [Outcome]
59
59
  def do_propagate(descendent)
60
- return unless descendent.successful?
60
+ return unless descendent.success?
61
61
 
62
62
  vars.each_key do |var_name|
63
- assocs = descendent[var_name]
63
+ # assocs = descendent[var_name]
64
64
  move_assocs(var_name, descendent)
65
65
  end
66
66
  end
@@ -2,7 +2,7 @@
2
2
 
3
3
  require 'singleton'
4
4
  require_relative 'binary_relation'
5
- # require_relative 'any_value'
5
+ require_relative 'cons_cell_visitor'
6
6
  require_relative 'duck_fiber'
7
7
  require_relative 'variable'
8
8
  require_relative 'variable_ref'
@@ -23,7 +23,10 @@ unless MiniKraken::Core.constants(false).include? :Equals
23
23
  # @return [Fiber<Outcome>] A Fiber(-like) instance that yields Outcomes
24
24
  def solver_for(actuals, anEnv)
25
25
  arg1, arg2 = *actuals
26
- DuckFiber.new(:custom) { unification(arg1, arg2, anEnv) }
26
+ DuckFiber.new(:custom) do
27
+ outcome = unification(arg1, arg2, anEnv)
28
+ outcome.prune!
29
+ end
27
30
  end
28
31
 
29
32
  def unification(arg1, arg2, anEnv)
@@ -38,10 +41,7 @@ unless MiniKraken::Core.constants(false).include? :Equals
38
41
  return result
39
42
  end
40
43
  new_arg1, new_arg2 = commute_cond(arg1, arg2, anEnv)
41
- result = do_unification(new_arg1, new_arg2, anEnv)
42
- # anEnv.merge(result) if result.successful? && !result.association.empty?
43
-
44
- result
44
+ do_unification(new_arg1, new_arg2, anEnv)
45
45
  end
46
46
 
47
47
  private
@@ -96,7 +96,10 @@ unless MiniKraken::Core.constants(false).include? :Equals
96
96
  arg1.associate(arg2, result)
97
97
  else
98
98
  # Ground case...
99
- result = unify_composite_terms(arg1_freshness.associated, arg2, anEnv)
99
+ arg1_associated = arg1_freshness.associated
100
+ unless arg1_associated.kind_of?(AtomicTerm)
101
+ result = unify_composite_terms(arg1_associated, arg2, anEnv)
102
+ end
100
103
  end
101
104
  elsif arg2.kind_of?(VariableRef)
102
105
  freshness = [arg1.fresh?(anEnv), arg2.fresh?(anEnv)]
@@ -124,33 +127,64 @@ unless MiniKraken::Core.constants(false).include? :Equals
124
127
  result
125
128
  end
126
129
 
127
- # @return [Freshness]
130
+ # @param arg1 [ConsCell]
131
+ # @param arg2 [ConsCell]
132
+ # @return [Outcome]
128
133
  def unify_composite_terms(arg1, arg2, anEnv)
129
134
  # require 'debug'
130
- result = Outcome.failure(anEnv)
131
- children1 = arg1.children
132
- children2 = arg2.children
133
-
134
- if children1.size == children2.size
135
- i = 0
136
- subresults = children1.map do |child1|
137
- child2 = children2[i]
138
- i += 1
139
- unification(child1, child2, anEnv)
140
- end
141
- total_success = subresults.all?(&:successful?)
142
- if total_success
143
- memo = Outcome.success(anEnv)
144
- associations = subresults.reduce(memo) do |sub_total, outcome|
145
- sub_total.merge(outcome)
146
- sub_total
135
+ result = Outcome.success(anEnv)
136
+ # We'll do parallel iteration
137
+ visitor1 = ConsCellVisitor.df_visitor(arg1)
138
+ visitor2 = ConsCellVisitor.df_visitor(arg2)
139
+ skip_children1 = false
140
+ skip_children2 = false
141
+
142
+ loop do
143
+ side1, cell1 = visitor1.resume(skip_children1)
144
+ side2, cell2 = visitor2.resume(skip_children2)
145
+ if side1 != side2
146
+ result = Outcome.failure(anEnv)
147
+ elsif side1 == :stop
148
+ break
149
+ else
150
+ case [cell1.class, cell2.class] # nil, AtomicTerm, ConsCell, VariableRef
151
+ when [ConsCell, ConsCell]
152
+ skip_children1 = false
153
+ skip_children2 = false
154
+ when [ConsCell, VariableRef]
155
+ skip_children1 = true
156
+ skip_children2 = false
157
+ sub_result = unification(cell1, cell2, anEnv)
158
+ result = merge_results(result, sub_result)
159
+ when [VariableRef, ConsCell]
160
+ skip_children1 = false
161
+ skip_children2 = true
162
+ sub_result = do_unification(cell1, cell2, anEnv)
163
+ result = merge_results(result, sub_result)
164
+ else
165
+ skip_children1 = false
166
+ skip_children2 = false
167
+ sub_result = unification(cell1, cell2, anEnv)
168
+ result = merge_results(result, sub_result)
147
169
  end
148
- result = memo
149
170
  end
171
+
172
+ break if result.failure?
150
173
  end
151
174
 
152
175
  result
153
176
  end
177
+
178
+ def merge_results(result1, result2)
179
+ raise StandardError if result2.kind_of?(Hash)
180
+
181
+ if result2.success?
182
+ result1.merge(result2)
183
+ result1
184
+ else
185
+ result2
186
+ end
187
+ end
154
188
  end # class
155
189
 
156
190
  Equals.instance.freeze
@@ -7,10 +7,11 @@ module MiniKraken
7
7
  # A formal reference represents the occurrence of a formal argument name in a
8
8
  # goal template argument list.
9
9
  class FormalRef < BaseArg
10
- # @return [String]
10
+ # @return [String] The name of a formal argument.
11
11
  attr_reader :name
12
12
 
13
13
  def initialize(aName)
14
+ super()
14
15
  @name = validated_name(aName)
15
16
  end
16
17
 
@@ -16,6 +16,7 @@ module MiniKraken
16
16
  # @param aRelation [Relation] The relation corresponding to this goal
17
17
  # @param args [Array<Term>] The actual aguments of the goal
18
18
  def initialize(aRelation, args)
19
+ super()
19
20
  @relation = aRelation
20
21
  @actuals = validated_actuals(args)
21
22
  end
@@ -35,14 +36,15 @@ module MiniKraken
35
36
  raise StandardError, err_msg
36
37
  end
37
38
 
38
- prefix = 'Invalid goal argument '
39
+ prefix = 'Invalid goal argument'
39
40
  args.each do |actual|
40
41
  if actual.kind_of?(GoalArg) || actual.kind_of?(Environment)
41
42
  next
42
43
  elsif actual.kind_of?(Array)
43
44
  validated_actuals(actual)
44
45
  else
45
- raise StandardError, prefix + actual.to_s
46
+ actual_display = actual.nil? ? 'nil' : actual.to_s
47
+ raise StandardError, "#{prefix} '#{actual_display}'"
46
48
  end
47
49
  end
48
50
 
@@ -1,24 +1,30 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require_relative 'base_arg'
4
+ require_relative 'cons_cell_visitor'
4
5
 
5
6
  module MiniKraken
6
7
  module Core
7
8
  # A meta-goal that is parametrized with generic formal arguments.
8
9
  # The individual goals are instantiated when the formal arguments
9
- # are bound to goal arguments
10
+ # are bound to goal arguments.
10
11
  class GoalTemplate < BaseArg
11
- # @return [Array<BaseArg>}] Arguments of goal template.
12
+ # @return [Array<BaseArg>] Arguments of goal template.
12
13
  attr_reader :args
13
14
 
14
15
  # @return [Relation] Main relation for the goal template
15
16
  attr_reader :relation
16
17
 
18
+ # @param aRelation [Core::Rzlation] the relation
19
+ # @param theArgs [Array<Core::BaseArg>] Arguments of goal template.
17
20
  def initialize(aRelation, theArgs)
21
+ super()
18
22
  @relation = validated_relation(aRelation)
19
23
  @args = validated_args(theArgs)
24
+ args.freeze
20
25
  end
21
26
 
27
+ # Factory method: Create a goal object.
22
28
  # @param formals [Array<FormalArg>] Array of formal arguments
23
29
  # @param actuals [Array<GoalArg>] Array of actual arguments
24
30
  # @return [Goal] instantiate a goal object given the actuals and environment
@@ -48,6 +54,9 @@ module MiniKraken
48
54
  goal_args << formals2actuals[arg.name]
49
55
  elsif arg.kind_of?(GoalTemplate)
50
56
  goal_args << arg.send(:do_instantiate, formals2actuals)
57
+ elsif arg.kind_of?(ConsCell)
58
+ # if list contains a formal_ref it must be replaced by the actual
59
+ goal_args << transform(arg, formals2actuals)
51
60
  else
52
61
  goal_args << arg
53
62
  end
@@ -55,6 +64,39 @@ module MiniKraken
55
64
 
56
65
  Goal.new(relation, goal_args)
57
66
  end
67
+
68
+ private
69
+
70
+ def transform(aConsCell, formals2actuals)
71
+ return aConsCell if aConsCell.null?
72
+
73
+ member = { car: :@car, cdr: :@cdr }
74
+ visitor = ConsCellVisitor.df_visitor(aConsCell)
75
+ side, cell = visitor.resume
76
+ result = ConsCell.new(nil, nil)
77
+ node = result
78
+
79
+ loop do
80
+ side, cell = visitor.resume
81
+ break if side == :stop
82
+
83
+ converted = nil
84
+ case cell
85
+ when FormalRef
86
+ converted = formals2actuals[cell.name]
87
+ when ConsCell
88
+ converted = ConsCell.new(nil, nil)
89
+ when GoalTemplate
90
+ converted = cell.send(:do_instantiate, formals2actuals)
91
+ else
92
+ converted = cell
93
+ end
94
+ node.instance_variable_set(member[side], converted)
95
+ node = converted if converted.kind_of?(ConsCell)
96
+ end
97
+
98
+ result
99
+ end
58
100
  end # class
59
101
  end # module
60
102
  end # module