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
@@ -12,6 +12,10 @@ module MiniKraken
12
12
  super(validated_value(aValue))
13
13
  end
14
14
 
15
+ def to_s
16
+ value.to_s
17
+ end
18
+
15
19
  private
16
20
 
17
21
  def validated_value(aValue)
@@ -11,6 +11,17 @@ module MiniKraken
11
11
  def initialize(aValue)
12
12
  super(aValue)
13
13
  end
14
+
15
+ # Returns the name or string corresponding to value.
16
+ # @return [String]
17
+ def id2name
18
+ value.id2name
19
+ end
20
+
21
+ # Returns a string representing the MiniKraken symbol.
22
+ def to_s
23
+ ":#{id2name}"
24
+ end
14
25
  end # class
15
26
  end # module
16
27
  end # module
@@ -24,7 +24,11 @@ unless MiniKraken::Core.constants(false).include? :Outcome
24
24
  new(:"#s", aParent)
25
25
  end
26
26
 
27
- def successful?
27
+ def failure?
28
+ resultant != :"#s"
29
+ end
30
+
31
+ def success?
28
32
  resultant == :"#s"
29
33
  end
30
34
 
@@ -39,6 +43,12 @@ unless MiniKraken::Core.constants(false).include? :Outcome
39
43
  are_equal
40
44
  end
41
45
 
46
+ # Remove associations of variables of this environment, if
47
+ # persistence flag is set to false.
48
+ def prune!
49
+ parent.prune(self)
50
+ end
51
+
42
52
  protected
43
53
 
44
54
  def introspect
@@ -24,11 +24,17 @@ module MiniKraken
24
24
  name != i_name
25
25
  end
26
26
 
27
- def quote(anEnvironment)
28
- raise StandardError, "class #{anEnvironment}" unless anEnvironment.kind_of?(Vocabulary)
27
+ def quote(env)
28
+ raise StandardError, "class #{env}" unless env.kind_of?(Vocabulary)
29
29
 
30
- val = anEnvironment.quote_ref(self)
31
- val.nil? ? AnyValue.new(name, anEnvironment) : val
30
+ val = env.quote_ref(self)
31
+ unless val
32
+ result = AnyValue.new(name, env, env.names_fused(name))
33
+ else
34
+ result = val
35
+ end
36
+
37
+ result
32
38
  end
33
39
  end # class
34
40
  end # module
@@ -1,6 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require_relative 'term'
4
+ require_relative 'designation'
4
5
  require_relative 'any_value'
5
6
 
6
7
  module MiniKraken
@@ -13,7 +14,13 @@ module MiniKraken
13
14
 
14
15
  # @param aName [String] The name of the variable
15
16
  def initialize(aName)
17
+ super()
16
18
  init_designation(aName)
19
+ name.freeze
20
+ end
21
+
22
+ def to_s
23
+ name
17
24
  end
18
25
 
19
26
  # @param aValue [Term]
@@ -171,7 +171,7 @@ module MiniKraken
171
171
  to_fuse.map { |i_name| i_name2var(i_name) }
172
172
  end
173
173
 
174
- # Fuse the given variables, that is:
174
+ # Fuse the given variables:
175
175
  # Collect all their associations
176
176
  # Put them under a new internal name
177
177
  # Remove all entries from old internal names
@@ -331,8 +331,9 @@ module MiniKraken
331
331
  # variable name.
332
332
  # @param aName [String] User-defined variable name
333
333
  def names_fused(aName)
334
+ # require 'debug'
334
335
  var = name2var(aName)
335
- return [] unless var.fused?
336
+ return [] unless var&.fused?
336
337
 
337
338
  i_name = var.i_name
338
339
  names = []
@@ -394,12 +395,16 @@ module MiniKraken
394
395
  name2var(aVarName) ? true : false
395
396
  end
396
397
 
398
+ def prune(anOutcome)
399
+ anOutcome # Don't touch outcome
400
+ end
401
+
397
402
  def inspect
398
403
  result = +"#<#{self.class.name}:#{object_id.to_s(16)} @parent="
399
404
  if parent
400
405
  result << "#<#{parent.class.name}:#{parent.object_id.to_s(16)}>"
401
406
  else
402
- result << nil
407
+ result << 'nil'
403
408
  end
404
409
  result << introspect
405
410
  result << '>'
@@ -0,0 +1,236 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'set'
4
+ require_relative '../core/any_value'
5
+ require_relative '../core/conde'
6
+ require_relative '../core/conj2'
7
+ require_relative '../core/cons_cell'
8
+ require_relative '../core/def_relation'
9
+ require_relative '../core/disj2'
10
+ require_relative '../core/equals'
11
+ require_relative '../core/fail'
12
+ require_relative '../core/formal_arg'
13
+ require_relative '../core/formal_ref'
14
+ require_relative '../glue/fresh_env'
15
+ require_relative '../glue/fresh_env_factory'
16
+ require_relative '../core/goal_template'
17
+ require_relative '../core/k_boolean'
18
+ require_relative '../core/k_symbol'
19
+ require_relative '../core/succeed'
20
+ require_relative '../core/variable_ref'
21
+ require_relative 'fresh_env'
22
+ require_relative 'run_star_expression'
23
+
24
+
25
+ module MiniKraken
26
+ module Glue
27
+ # The mixin module that implements the methods for the DSL
28
+ # (DSL = Domain Specific Langague) that allows MiniKraken
29
+ # users to embed Minikanren in their Ruby code.
30
+ module DSL
31
+ # A run* expression tries to find all the solutions
32
+ # that meet the given goal.
33
+ # @return [Core::ConsCell] A list of solutions
34
+ def run_star(var_names, goal)
35
+ program = RunStarExpression.new(var_names, goal)
36
+ program.run
37
+ end
38
+
39
+ def conde(*goals)
40
+ # require 'debug'
41
+ args = goals.map do |goal_maybe|
42
+ if goal_maybe.kind_of?(Array)
43
+ goal_maybe.map { |g| convert(g) }
44
+ else
45
+ convert(goal_maybe)
46
+ end
47
+ end
48
+
49
+ Core::Goal.new(Core::Conde.instance, args)
50
+ end
51
+
52
+ # conj2 stands for conjunction of two arguments.
53
+ # Returns a goal linked to the Core::Conj2 relation.
54
+ # The rule of that relation succeeds when both arguments succeed.
55
+ # @param arg1 [Core::Goal]
56
+ # @param arg2 [Core::Goal]
57
+ # @return [Core::Failure|Core::Success]
58
+ def conj2(arg1, arg2)
59
+ goal_class.new(Core::Conj2.instance, [convert(arg1), convert(arg2)])
60
+ end
61
+
62
+ def cons(car_item, cdr_item = nil)
63
+ tail = cdr_item.nil? ? cdr_item : convert(cdr_item)
64
+ Core::ConsCell.new(convert(car_item), tail)
65
+ end
66
+
67
+ def defrel(relationName, theFormals, &aGoalTemplateExpr)
68
+ start_defrel
69
+
70
+ case theFormals
71
+ when String
72
+ @defrel_formals << theFormals
73
+ when Array
74
+ @defrel_formals.merge(theFormals)
75
+ end
76
+
77
+ formals = @defrel_formals.map { |name| Core::FormalArg.new(name) }
78
+ g_template = aGoalTemplateExpr.call
79
+ result = Core::DefRelation.new(relationName, g_template, formals)
80
+ add_defrel(result)
81
+
82
+ end_defrel
83
+ result
84
+ end
85
+
86
+ def disj2(arg1, arg2)
87
+ goal_class.new(Core::Disj2.instance, [convert(arg1), convert(arg2)])
88
+ end
89
+
90
+ # @return [Core::Fail] A goal that unconditionally fails.
91
+ def _fail
92
+ goal_class.new(Core::Fail.instance, [])
93
+ end
94
+
95
+ def equals(arg1, arg2)
96
+ # require 'debug'
97
+ goal_class.new(Core::Equals.instance, [convert(arg1), convert(arg2)])
98
+ end
99
+
100
+ def fresh(var_names, goal)
101
+ vars = nil
102
+ if @dsl_mode == :defrel
103
+ if var_names.kind_of?(String)
104
+ vars = [var_names]
105
+ else
106
+ vars = var_names
107
+ end
108
+ FreshEnvFactory.new(vars, goal)
109
+ else
110
+ if var_names.kind_of?(String) || var_names.kind_of?(Core::VariableRef)
111
+ vars = [var_names]
112
+ else
113
+ vars = var_names
114
+ end
115
+
116
+ FreshEnv.new(vars, goal)
117
+ end
118
+ end
119
+
120
+ def list(*members)
121
+ return null if members.empty?
122
+
123
+ head = nil
124
+ members.reverse_each { |elem| head = Core::ConsCell.new(convert(elem), head) }
125
+
126
+ head
127
+ end
128
+
129
+ # @return [ConsCell] Returns an empty list, that is, a pair whose members are nil.
130
+ def null
131
+ Core::ConsCell.new(nil, nil)
132
+ end
133
+
134
+ # @return [Core::Succeed] A goal that unconditionally succeeds.
135
+ def succeed
136
+ goal_class.new(Core::Succeed.instance, [])
137
+ end
138
+
139
+ private
140
+
141
+ def convert(anArgument)
142
+ converted = nil
143
+
144
+ case anArgument
145
+ when Symbol
146
+ if anArgument.id2name =~ /_\d+/
147
+ rank = anArgument.id2name.slice(1..-1).to_i
148
+ any_val = Core::AnyValue.allocate
149
+ any_val.instance_variable_set(:@rank, rank)
150
+ converted = any_val
151
+ elsif anArgument.id2name =~ /^"#[ft]"$/
152
+ converted = Core::KBoolean.new(anArgument)
153
+ else
154
+ converted = Core::KSymbol.new(anArgument)
155
+ end
156
+ when String
157
+ if anArgument =~ /^#[ft]$/
158
+ converted = Core::KBoolean.new(anArgument)
159
+ else
160
+ msg = "Internal error: undefined conversion for #{anArgument.class}"
161
+ raise StandardError, msg
162
+ end
163
+ when false, true
164
+ converted = Core::KBoolean.new(anArgument)
165
+ when Core::KBoolean
166
+ converted = anArgument
167
+ when Core::FormalRef
168
+ converted = anArgument
169
+ when FreshEnv
170
+ converted = anArgument
171
+ when Core::Goal
172
+ converted = anArgument
173
+ when Core::GoalTemplate
174
+ converted = anArgument
175
+ when Core::VariableRef
176
+ converted = anArgument
177
+ when Core::ConsCell
178
+ converted = anArgument
179
+ else
180
+ msg = "Internal error: undefined conversion for #{anArgument.class}"
181
+ raise StandardError, msg
182
+ end
183
+
184
+ converted
185
+ end
186
+
187
+ def default_mode
188
+ @dsl_mode = :default
189
+ @defrel_formals = nil
190
+ end
191
+
192
+ def goal_class
193
+ default_mode unless instance_variable_defined?(:@dsl_mode)
194
+ @dsl_mode == :default ? Core::Goal : Core::GoalTemplate
195
+ end
196
+
197
+ def start_defrel
198
+ @dsl_mode = :defrel
199
+ @defrel_formals = Set.new
200
+ end
201
+
202
+ def end_defrel
203
+ default_mode
204
+ end
205
+
206
+ def add_defrel(aDefRelation)
207
+ @defrels = {} unless instance_variable_defined?(:@defrels)
208
+ @defrels[aDefRelation.name] = aDefRelation
209
+ end
210
+
211
+ def method_missing(mth, *args)
212
+ result = nil
213
+
214
+ begin
215
+ result = super(mth, *args)
216
+ rescue NameError
217
+ name = mth.id2name
218
+ @defrels = {} unless instance_variable_defined?(:@defrels)
219
+ if @defrels.include?(name)
220
+ def_relation = @defrels[name]
221
+ result = Core::Goal.new(def_relation, args.map { |el| convert(el) })
222
+ else
223
+ default_mode unless instance_variable_defined?(:@dsl_mode)
224
+ if @dsl_mode == :defrel && @defrel_formals.include?(name)
225
+ result = Core::FormalRef.new(name)
226
+ else
227
+ result = Core::VariableRef.new(name)
228
+ end
229
+ end
230
+ end
231
+
232
+ result
233
+ end
234
+ end # module
235
+ end # module
236
+ end # module
@@ -2,13 +2,14 @@
2
2
 
3
3
  require_relative '../core/environment'
4
4
  require_relative '../core/conj2'
5
+ require_relative '../core/goal_template'
5
6
  require_relative '../core/variable'
6
7
 
7
8
  module MiniKraken
8
9
  module Glue
9
10
  # A combination of an Environment (= a scope for one or more variables)
10
11
  # and a goal. It quacks like a Goal object: when receiving the attain message,
11
- # it attempt to achieve its given goal.
12
+ # it attempts to achieve its given goal.
12
13
  # (fresh (x) (== 'pea q))
13
14
  # Introduces the new variable 'x'
14
15
  # Takes a list of names and a goal-like object
@@ -17,22 +18,45 @@ module MiniKraken
17
18
  # @return [Goal]
18
19
  attr_reader :goal
19
20
 
21
+ # @return [TrueClass, FalseClass] Do associations persist after goal exec?
22
+ attr_reader :persistent
23
+
20
24
  # @param theNames [Array<String>] The variable names
21
25
  # @param aGoal [Goal, Array<Goal>] The goal to achieve or the conjunction of them.
22
- def initialize(theNames, aGoal)
26
+ def initialize(theNames, aGoal, persistence = true)
23
27
  super()
24
28
  @goal = valid_goal(aGoal)
25
- theNames.each { |nm| add_var(Core::Variable.new(nm)) }
29
+ theNames.each do |nm|
30
+ var = Core::Variable.new(nm)
31
+ add_var(var)
32
+ end
33
+ @persistent = persistence
26
34
  end
27
35
 
28
36
  # Attempt to achieve the goal given this environment
29
37
  # @param aParent [Environment]
30
38
  # @return [Fiber<Outcome>] A Fiber object that will generate the results.
31
39
  def attain(aParent)
40
+ # require 'debug'
32
41
  self.parent = aParent
33
42
  goal.attain(self)
34
43
  end
35
44
 
45
+ # Remove associations of variables of this environment, if
46
+ # persistence flag is set to false.
47
+ def prune(anOutcome)
48
+ return super(anOutcome) if persistent
49
+
50
+ vars.each_value do |v|
51
+ v_name = v.name
52
+ if anOutcome.associations.include?(v_name)
53
+ anOutcome.associations.delete(v_name)
54
+ end
55
+ end
56
+
57
+ anOutcome
58
+ end
59
+
36
60
  protected
37
61
 
38
62
  def introspect
@@ -49,6 +73,8 @@ module MiniKraken
49
73
  result = aGoal
50
74
  when FreshEnv
51
75
  result = aGoal
76
+ when Core::GoalTemplate
77
+ result = aGoal
52
78
  when Array # an Array of Goal?..
53
79
  goal_array = aGoal
54
80
  loop do
@@ -66,6 +92,8 @@ module MiniKraken
66
92
  end
67
93
  goal_array = conjunctions
68
94
  end
95
+ else
96
+ raise StandardError, "Cannot handle argumment type #{aGoal.class}"
69
97
  end
70
98
 
71
99
  result