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.
- checksums.yaml +4 -4
- data/.rubocop.yml +334 -0
- data/CHANGELOG.md +54 -0
- data/README.md +95 -13
- data/lib/mini_kraken.rb +7 -1
- data/lib/mini_kraken/core/any_value.rb +5 -1
- data/lib/mini_kraken/core/atomic_term.rb +1 -0
- data/lib/mini_kraken/core/conde.rb +1 -1
- data/lib/mini_kraken/core/conj2.rb +3 -3
- data/lib/mini_kraken/core/cons_cell.rb +29 -1
- data/lib/mini_kraken/core/cons_cell_visitor.rb +102 -0
- data/lib/mini_kraken/core/def_relation.rb +4 -0
- data/lib/mini_kraken/core/disj2.rb +2 -2
- data/lib/mini_kraken/core/environment.rb +2 -2
- data/lib/mini_kraken/core/equals.rb +60 -26
- data/lib/mini_kraken/core/formal_ref.rb +2 -1
- data/lib/mini_kraken/core/goal.rb +4 -2
- data/lib/mini_kraken/core/goal_template.rb +44 -2
- data/lib/mini_kraken/core/k_boolean.rb +4 -0
- data/lib/mini_kraken/core/k_symbol.rb +11 -0
- data/lib/mini_kraken/core/outcome.rb +11 -1
- data/lib/mini_kraken/core/variable.rb +10 -4
- data/lib/mini_kraken/core/variable_ref.rb +7 -0
- data/lib/mini_kraken/core/vocabulary.rb +8 -3
- data/lib/mini_kraken/glue/dsl.rb +236 -0
- data/lib/mini_kraken/glue/fresh_env.rb +31 -3
- data/lib/mini_kraken/glue/fresh_env_factory.rb +83 -0
- data/lib/mini_kraken/glue/run_star_expression.rb +3 -5
- data/lib/mini_kraken/version.rb +1 -1
- data/mini_kraken.gemspec +6 -3
- data/spec/.rubocop.yml +13 -0
- data/spec/core/conde_spec.rb +10 -10
- data/spec/core/conj2_spec.rb +7 -7
- data/spec/core/cons_cell_spec.rb +35 -0
- data/spec/core/cons_cell_visitor_spec.rb +144 -0
- data/spec/core/def_relation_spec.rb +6 -5
- data/spec/core/disj2_spec.rb +5 -5
- data/spec/core/duck_fiber_spec.rb +2 -2
- data/spec/core/equals_spec.rb +34 -21
- data/spec/core/goal_spec.rb +2 -2
- data/spec/core/k_boolean_spec.rb +6 -0
- data/spec/core/k_symbol_spec.rb +4 -0
- data/spec/core/outcome_spec.rb +8 -0
- data/spec/core/variable_ref_spec.rb +3 -0
- data/spec/glue/dsl_chap1_spec.rb +679 -0
- data/spec/glue/dsl_chap2_spec.rb +100 -0
- data/spec/glue/fresh_env_factory_spec.rb +97 -0
- data/spec/glue/run_star_expression_spec.rb +11 -11
- metadata +17 -4
@@ -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
|
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(
|
28
|
-
raise StandardError, "class #{
|
27
|
+
def quote(env)
|
28
|
+
raise StandardError, "class #{env}" unless env.kind_of?(Vocabulary)
|
29
29
|
|
30
|
-
val =
|
31
|
-
|
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
|
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
|
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
|
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
|
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
|