mini_kraken 0.1.03 → 0.1.08

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 (60) hide show
  1. checksums.yaml +4 -4
  2. data/.travis.yml +5 -1
  3. data/CHANGELOG.md +54 -3
  4. data/Gemfile +3 -1
  5. data/README.md +22 -1
  6. data/Rakefile +5 -3
  7. data/lib/mini_kraken.rb +3 -1
  8. data/lib/mini_kraken/core/any_value.rb +9 -7
  9. data/lib/mini_kraken/core/association.rb +20 -7
  10. data/lib/mini_kraken/core/association_walker.rb +5 -1
  11. data/lib/mini_kraken/core/atomic_term.rb +5 -3
  12. data/lib/mini_kraken/core/binary_relation.rb +8 -6
  13. data/lib/mini_kraken/core/composite_goal.rb +46 -0
  14. data/lib/mini_kraken/core/composite_term.rb +7 -20
  15. data/lib/mini_kraken/core/conj2.rb +77 -0
  16. data/lib/mini_kraken/core/cons_cell.rb +51 -41
  17. data/lib/mini_kraken/core/designation.rb +55 -0
  18. data/lib/mini_kraken/core/disj2.rb +71 -0
  19. data/lib/mini_kraken/core/duck_fiber.rb +4 -2
  20. data/lib/mini_kraken/core/environment.rb +25 -11
  21. data/lib/mini_kraken/core/equals.rb +128 -189
  22. data/lib/mini_kraken/core/fail.rb +20 -14
  23. data/lib/mini_kraken/core/freshness.rb +11 -8
  24. data/lib/mini_kraken/core/goal.rb +8 -4
  25. data/lib/mini_kraken/core/goal_arg.rb +10 -0
  26. data/lib/mini_kraken/core/goal_relation.rb +28 -0
  27. data/lib/mini_kraken/core/k_integer.rb +4 -3
  28. data/lib/mini_kraken/core/k_symbol.rb +4 -3
  29. data/lib/mini_kraken/core/nullary_relation.rb +3 -1
  30. data/lib/mini_kraken/core/outcome.rb +29 -25
  31. data/lib/mini_kraken/core/relation.rb +4 -18
  32. data/lib/mini_kraken/core/succeed.rb +20 -14
  33. data/lib/mini_kraken/core/term.rb +7 -2
  34. data/lib/mini_kraken/core/variable.rb +11 -25
  35. data/lib/mini_kraken/core/variable_ref.rb +12 -59
  36. data/lib/mini_kraken/core/vocabulary.rb +267 -48
  37. data/lib/mini_kraken/glue/fresh_env.rb +5 -3
  38. data/lib/mini_kraken/glue/run_star_expression.rb +18 -8
  39. data/lib/mini_kraken/version.rb +3 -1
  40. data/mini_kraken.gemspec +15 -13
  41. data/spec/core/association_spec.rb +4 -4
  42. data/spec/core/association_walker_spec.rb +25 -24
  43. data/spec/core/conj2_spec.rb +114 -0
  44. data/spec/core/cons_cell_spec.rb +12 -3
  45. data/spec/core/disj2_spec.rb +99 -0
  46. data/spec/core/duck_fiber_spec.rb +22 -12
  47. data/spec/core/environment_spec.rb +16 -28
  48. data/spec/core/equals_spec.rb +7 -7
  49. data/spec/core/fail_spec.rb +7 -7
  50. data/spec/core/goal_spec.rb +10 -10
  51. data/spec/core/k_symbol_spec.rb +5 -6
  52. data/spec/core/succeed_spec.rb +4 -4
  53. data/spec/core/variable_ref_spec.rb +0 -4
  54. data/spec/core/vocabulary_spec.rb +33 -27
  55. data/spec/glue/fresh_env_spec.rb +1 -1
  56. data/spec/glue/run_star_expression_spec.rb +213 -60
  57. data/spec/mini_kraken_spec.rb +4 -0
  58. data/spec/spec_helper.rb +3 -2
  59. data/spec/support/factory_methods.rb +20 -2
  60. metadata +12 -2
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module MiniKraken
2
- VERSION = '0.1.03'.freeze
4
+ VERSION = '0.1.08'
3
5
  end
@@ -1,6 +1,8 @@
1
- lib = File.expand_path("lib", __dir__)
1
+ # frozen_string_literal: true
2
+
3
+ lib = File.expand_path('lib', __dir__)
2
4
  $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
3
- require "mini_kraken/version"
5
+ require 'mini_kraken/version'
4
6
 
5
7
  # Implementation module
6
8
  module PkgExtending
@@ -10,7 +12,7 @@ module PkgExtending
10
12
  '.travis.yml',
11
13
  'Gemfile',
12
14
  'Rakefile',
13
- 'CHANGELOG.md',
15
+ 'CHANGELOG.md',
14
16
  'LICENSE.txt',
15
17
  'README.md',
16
18
  'mini_kraken.gemspec',
@@ -31,25 +33,25 @@ module PkgExtending
31
33
  end # module
32
34
 
33
35
  Gem::Specification.new do |spec|
34
- spec.name = "mini_kraken"
36
+ spec.name = 'mini_kraken'
35
37
  spec.version = MiniKraken::VERSION
36
38
  spec.authors = ['Dimitri Geshef']
37
39
  spec.email = ['famished.tiger@yahoo.com']
38
40
 
39
41
  spec.summary = %q{Implementation of Minikanren language in Ruby. WIP}
40
42
  spec.description = %q{Implementation of Minikanren language in Ruby. WIP}
41
- spec.homepage = "https://github.com/famished-tiger/mini_kraken"
42
- spec.license = "MIT"
43
+ spec.homepage = 'https://github.com/famished-tiger/mini_kraken'
44
+ spec.license = 'MIT'
43
45
 
44
46
  # Specify which files should be added to the gem when it is released.
45
- spec.bindir = "exe"
47
+ spec.bindir = 'exe'
46
48
  spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
47
- spec.require_paths = ["lib"]
48
-
49
+ spec.require_paths = ['lib']
50
+
49
51
  PkgExtending.pkg_files(spec)
50
- PkgExtending.pkg_documentation(spec)
52
+ PkgExtending.pkg_documentation(spec)
51
53
 
52
- spec.add_development_dependency "bundler", "~> 2.0"
53
- spec.add_development_dependency "rake", "~> 12.0"
54
- spec.add_development_dependency "rspec", "~> 3.0"
54
+ spec.add_development_dependency 'bundler', '~> 2.0'
55
+ spec.add_development_dependency 'rake', '~> 12.0'
56
+ spec.add_development_dependency 'rspec', '~> 3.0'
55
57
  end
@@ -10,20 +10,20 @@ require_relative '../../lib/mini_kraken/core/association'
10
10
  module MiniKraken
11
11
  module Core
12
12
  describe Association do
13
- let(:pea) {KSymbol.new(:pea) }
13
+ let(:pea) { KSymbol.new(:pea) }
14
14
  subject { Association.new('q', pea) }
15
15
 
16
16
  context 'Initialization:' do
17
17
  it 'should be initialized with a name and a value' do
18
18
  expect { Association.new('q', pea) }.not_to raise_error
19
19
  end
20
-
20
+
21
21
  it 'should be initialized with a variable and a value' do
22
22
  expect { Association.new(Variable.new('p'), pea) }.not_to raise_error
23
- end
23
+ end
24
24
 
25
25
  it 'should know the variable name' do
26
- expect(subject.var_name).to eq('q')
26
+ expect(subject.i_name).to eq('q')
27
27
  end
28
28
 
29
29
  it 'should know the associated value' do
@@ -58,14 +58,14 @@ module MiniKraken
58
58
 
59
59
  it 'should return composite when it has ground composite term(s)' do
60
60
  env.add_var(var_q)
61
- env.add_assoc(Association.new('q', pea))
61
+ env.add_assoc('q', pea)
62
62
  expr1 = cons(pea, cons(pod, cons(ref_q)))
63
63
  expect(subject.walk_value(expr1, env)).to eq(expr1)
64
64
  end
65
65
 
66
66
  it 'should return nil when no assocation exists' do
67
67
  env.add_var(var_q)
68
- env.add_assoc(Association.new('q', pea))
68
+ env.add_assoc('q', pea)
69
69
  env.add_var(var_x)
70
70
 
71
71
  expect(subject.find_ground(var_x.name, env)).to be_nil
@@ -73,7 +73,7 @@ module MiniKraken
73
73
 
74
74
  it 'should find an atomic term directly associated' do
75
75
  env.add_var(var_q)
76
- env.add_assoc(Association.new('q', pea))
76
+ env.add_assoc('q', pea)
77
77
 
78
78
  result = subject.find_ground(var_q.name, env)
79
79
  expect(result).to eq(pea)
@@ -82,8 +82,8 @@ module MiniKraken
82
82
  it 'should find an atomic term directly associated' do
83
83
  env.add_var(var_q)
84
84
  env.add_var(var_x)
85
- env.add_assoc(Association.new('q', ref_x))
86
- env.add_assoc(Association.new('x', pea))
85
+ env.add_assoc('q', ref_x)
86
+ env.add_assoc('x', pea)
87
87
  expect(env['x']).not_to be_nil
88
88
 
89
89
  result = subject.find_ground(var_q.name, env)
@@ -93,9 +93,9 @@ module MiniKraken
93
93
  it 'should cope with cyclic structures' do
94
94
  env.add_var(var_q)
95
95
  env.add_var(var_x)
96
- env.add_assoc(Association.new('q', ref_x))
97
- env.add_assoc(Association.new('x', pea))
98
- env.add_assoc(Association.new('x', ref_q))
96
+ env.add_assoc('q', ref_x)
97
+ env.add_assoc('x', pea)
98
+ env.add_assoc('x', ref_q)
99
99
 
100
100
  result = subject.find_ground(var_q.name, env)
101
101
  expect(result).to eq(pea)
@@ -107,7 +107,7 @@ module MiniKraken
107
107
  it 'should cope with a composite with atomic terms only' do
108
108
  env.add_var(var_q)
109
109
  expr = cons(pea, cons(pod, cons(pea)))
110
- env.add_assoc(Association.new('q', expr))
110
+ env.add_assoc('q', expr)
111
111
 
112
112
  result = subject.find_ground(var_q.name, env)
113
113
  expect(result).to eq(expr)
@@ -117,7 +117,7 @@ module MiniKraken
117
117
  env.add_var(var_q)
118
118
  env.add_var(var_x)
119
119
  expr = cons(pea, cons(pod, cons(ref_x)))
120
- env.add_assoc(Association.new('q', expr))
120
+ env.add_assoc('q', expr)
121
121
 
122
122
  result = subject.find_ground(var_q.name, env)
123
123
  expect(result).to be_nil
@@ -127,14 +127,13 @@ module MiniKraken
127
127
  env.add_var(var_q)
128
128
  env.add_var(var_x)
129
129
  expr = cons(pea, cons(pod, cons(ref_x)))
130
- env.add_assoc(Association.new('q', expr))
131
- env.add_assoc(Association.new('x', pod))
130
+ env.add_assoc('q', expr)
131
+ env.add_assoc('x', pod)
132
132
 
133
133
  result = subject.find_ground(var_q.name, env)
134
134
  expect(result).to eq(expr)
135
135
  end
136
- =begin
137
- =end
136
+
138
137
  it 'should categorize a variable without association as free' do
139
138
  env.add_var(var_q)
140
139
  result = subject.determine_freshness(ref_q, env)
@@ -145,22 +144,24 @@ module MiniKraken
145
144
  it 'should categorize a variable related to fresh variable as bound' do
146
145
  env.add_var(var_q)
147
146
  env.add_var(var_x)
148
- env.add_assoc(Association.new('q', ref_x))
147
+ env.add_assoc('q', ref_x)
149
148
 
150
149
  result = subject.determine_freshness(ref_q, env)
151
150
  expect(result).to be_bound
152
151
  expect(result.associated).to eq(ref_x)
153
152
  end
154
153
 
155
- it 'should categorize a variable even in presence of cycle(s)' do
154
+ it 'should categorize a variable even in presence of fused var(s)' do
156
155
  env.add_var(var_q)
157
156
  env.add_var(var_x)
158
- env.add_assoc(Association.new('q', ref_x))
159
- env.add_assoc(Association.new('x', ref_q))
157
+ env.add_assoc('q', ref_x)
158
+ env.add_assoc('x', ref_q)
159
+ # q and x are fused...
160
160
 
161
161
  result = subject.determine_freshness(ref_q, env)
162
- expect(result).to be_bound
163
- expect(result.associated).to eq(ref_x)
162
+ expect(result).to be_fresh
163
+ expect(var_q.i_name).to eq(var_x.i_name)
164
+ expect(env.associations).to be_empty
164
165
  end
165
166
 
166
167
  it 'should categorize an atomic term as ground term' do
@@ -176,15 +177,15 @@ module MiniKraken
176
177
  expect(result).to be_ground
177
178
  expect(result.associated).to eq(composite)
178
179
  end
179
-
180
+
180
181
  it 'should categorize a composite term as bound term' do
181
- # Bound composite: a composite where at least one member is fresh
182
- env.add_var(var_q)
182
+ # Bound composite: a composite where at least one member is fresh
183
+ env.add_var(var_q)
183
184
  composite = cons(pea, cons(ref_q))
184
185
  result = subject.determine_freshness(composite, env)
185
186
  expect(result).to be_bound
186
187
  expect(result.associated).to eq(composite)
187
- end
188
+ end
188
189
  end # context
189
190
  end # describe
190
191
  end # module
@@ -0,0 +1,114 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../spec_helper' # Use the RSpec framework
4
+ require_relative '../../lib/mini_kraken/core/k_symbol'
5
+ require_relative '../../lib/mini_kraken/core/fail'
6
+ require_relative '../../lib/mini_kraken/core/succeed'
7
+ require_relative '../../lib/mini_kraken/core/equals'
8
+ require_relative '../../lib/mini_kraken/core/environment'
9
+ require_relative '../../lib/mini_kraken/core/variable'
10
+ require_relative '../../lib/mini_kraken/core/variable_ref'
11
+
12
+ # Load the class under test
13
+ require_relative '../../lib/mini_kraken/core/conj2'
14
+
15
+
16
+ module MiniKraken
17
+ module Core
18
+ describe Conj2 do
19
+ subject { Conj2.instance }
20
+
21
+ context 'Initialization:' do
22
+ it 'should be initialized without argument' do
23
+ expect { Conj2.instance }.not_to raise_error
24
+ end
25
+
26
+ it 'should know its name' do
27
+ expect(subject.name).to eq('conj2')
28
+ end
29
+ end # context
30
+
31
+ context 'Provided services:' do
32
+ let(:env) { Environment.new }
33
+ let(:pea) { KSymbol.new(:pea) }
34
+ let(:corn) { KSymbol.new(:corn) }
35
+ let(:meal) { KSymbol.new(:meal) }
36
+ let(:fails) { Goal.new(Fail.instance, []) }
37
+ let(:succeeds) { Goal.new(Succeed.instance, []) }
38
+ let(:var_q) { Variable.new('q') }
39
+ let(:ref_q) { VariableRef.new('q') }
40
+
41
+ it 'should complain when one of its argument is not a goal' do
42
+ err = StandardError
43
+ expect { subject.solver_for([succeeds, pea], env) }.to raise_error(err)
44
+ expect { subject.solver_for([pea, succeeds], env) }.to raise_error(err)
45
+ end
46
+
47
+ it 'should yield one failure if one of the goal is fail' do
48
+ # Fail as first argument
49
+ solver = subject.solver_for([fails, succeeds], env)
50
+ expect(solver.resume).not_to be_successful
51
+ expect(solver.resume).to be_nil
52
+
53
+ # Fail as second argument
54
+ solver = subject.solver_for([succeeds, fails], env)
55
+ expect(solver.resume).not_to be_successful
56
+ expect(solver.resume).to be_nil
57
+ end
58
+
59
+ it 'yield success if both arguments are succeed goals' do
60
+ # Covers frame 1-50
61
+ solver = subject.solver_for([succeeds, succeeds], env)
62
+ outcome = solver.resume
63
+ expect(outcome).to be_successful
64
+ expect(outcome.associations).to be_empty
65
+ expect(solver.resume).to be_nil
66
+ end
67
+
68
+ it 'should yield success and set associations' do
69
+ # Covers frame 1-51
70
+ env.add_var(var_q)
71
+ sub_goal = Goal.new(Equals.instance, [corn, ref_q])
72
+ solver = subject.solver_for([succeeds, sub_goal], env)
73
+ outcome = solver.resume
74
+ expect(outcome).to be_successful
75
+ expect(outcome.associations).not_to be_empty
76
+ expect(outcome.associations['q'].first.value).to eq(corn)
77
+ end
78
+
79
+ it 'should yield fails and set no associations' do
80
+ # Covers frame 1-52
81
+ env.add_var(var_q)
82
+ sub_goal = Goal.new(Equals.instance, [corn, ref_q])
83
+ solver = subject.solver_for([fails, sub_goal], env)
84
+ outcome = solver.resume
85
+ expect(outcome).not_to be_successful
86
+ expect(outcome.associations).to be_empty
87
+ end
88
+
89
+ it 'should yield fails when sub-goals are incompatible' do
90
+ # Covers frame 1-53
91
+ env.add_var(var_q)
92
+ sub_goal1 = Goal.new(Equals.instance, [corn, ref_q])
93
+ sub_goal2 = Goal.new(Equals.instance, [meal, ref_q])
94
+ solver = subject.solver_for([sub_goal1, sub_goal2], env)
95
+ outcome = solver.resume
96
+ expect(outcome).not_to be_successful
97
+ expect(outcome.associations).to be_empty
98
+ end
99
+
100
+ it 'should yield success when sub-goals are same and successful' do
101
+ # Covers frame 1-54
102
+ env.add_var(var_q)
103
+ sub_goal1 = Goal.new(Equals.instance, [corn, ref_q])
104
+ sub_goal2 = Goal.new(Equals.instance, [corn, ref_q])
105
+ solver = subject.solver_for([sub_goal1, sub_goal2], env)
106
+ outcome = solver.resume
107
+ expect(outcome).to be_successful
108
+ expect(outcome.associations).not_to be_empty
109
+ expect(outcome.associations['q'].first.value).to eq(corn)
110
+ end
111
+ end # context
112
+ end # describe
113
+ end # module
114
+ end # module
@@ -9,8 +9,8 @@ require_relative '../../lib/mini_kraken/core/cons_cell'
9
9
  module MiniKraken
10
10
  module Core
11
11
  describe ConsCell do
12
- let(:pea) {KSymbol.new(:pea) }
13
- let(:pod) {KSymbol.new(:pod) }
12
+ let(:pea) { KSymbol.new(:pea) }
13
+ let(:pod) { KSymbol.new(:pod) }
14
14
  subject { ConsCell.new(pea, pod) }
15
15
 
16
16
  context 'Initialization:' do
@@ -44,7 +44,8 @@ module MiniKraken
44
44
  context 'Provided services:' do
45
45
  it 'should compare to itself' do
46
46
  expect(subject.eql?(subject)).to be_truthy
47
- expect(subject == subject).to be_truthy
47
+ synonym = subject
48
+ expect(subject == synonym).to be_truthy
48
49
  end
49
50
 
50
51
  it 'should compare to another instance' do
@@ -57,6 +58,14 @@ module MiniKraken
57
58
  different = ConsCell.new(pea)
58
59
  expect(subject.eql?(different)).to be_falsey
59
60
  end
61
+
62
+ it 'should append another cons cell' do
63
+ instance = ConsCell.new(pea)
64
+ trail = ConsCell.new(pod)
65
+ instance.append(trail)
66
+ expect(instance.car).to eq(pea)
67
+ expect(instance.cdr).to eq(trail)
68
+ end
60
69
  end # context
61
70
  end # describe
62
71
  end # module
@@ -0,0 +1,99 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../spec_helper' # Use the RSpec framework
4
+ require_relative '../../lib/mini_kraken/core/k_symbol'
5
+ require_relative '../../lib/mini_kraken/core/fail'
6
+ require_relative '../../lib/mini_kraken/core/succeed'
7
+ require_relative '../../lib/mini_kraken/core/equals'
8
+ require_relative '../../lib/mini_kraken/core/environment'
9
+ require_relative '../../lib/mini_kraken/core/variable'
10
+ require_relative '../../lib/mini_kraken/core/variable_ref'
11
+
12
+ # Load the class under test
13
+ require_relative '../../lib/mini_kraken/core/disj2'
14
+
15
+ module MiniKraken
16
+ module Core
17
+ describe Disj2 do
18
+ subject { Disj2.instance }
19
+
20
+ context 'Initialization:' do
21
+ it 'should be initialized without argument' do
22
+ expect { Disj2.instance }.not_to raise_error
23
+ end
24
+
25
+ it 'should know its name' do
26
+ expect(subject.name).to eq('disj2')
27
+ end
28
+ end # context
29
+
30
+ context 'Provided services:' do
31
+ let(:corn) { KSymbol.new(:corn) }
32
+ let(:meal) { KSymbol.new(:meal) }
33
+ let(:oil) { KSymbol.new(:oil) }
34
+ let(:olive) { KSymbol.new(:olive) }
35
+ let(:pea) { KSymbol.new(:pea) }
36
+ let(:fails) { Goal.new(Fail.instance, []) }
37
+ let(:succeeds) { Goal.new(Succeed.instance, []) }
38
+ let(:var_q) { Variable.new('q') }
39
+ let(:ref_q) { VariableRef.new('q') }
40
+ let(:env) do
41
+ e = Environment.new
42
+ e.add_var(var_q)
43
+ e
44
+ end
45
+
46
+ it 'should complain when one of its argument is not a goal' do
47
+ err = StandardError
48
+ expect { subject.solver_for([succeeds, pea], env) }.to raise_error(err)
49
+ expect { subject.solver_for([pea, succeeds], env) }.to raise_error(err)
50
+ end
51
+
52
+ it 'should fails if both arguments fail' do
53
+ # Covers frame 1:55
54
+ solver = subject.solver_for([fails, fails], env)
55
+ expect(solver.resume).not_to be_successful
56
+ expect(solver.resume).to be_nil
57
+ end
58
+
59
+ it 'yield success if first argument succeeds' do
60
+ # Covers frame 1:56
61
+ subgoal = Goal.new(Equals.instance, [olive, ref_q])
62
+ solver = subject.solver_for([subgoal, fails], env)
63
+ outcome = solver.resume
64
+ expect(outcome).to be_successful
65
+ expect(outcome.associations['q'].first.value).to eq(olive)
66
+ expect(solver.resume).to be_nil
67
+ end
68
+
69
+ it 'yield success if second argument succeeds' do
70
+ # Covers frame 1:57
71
+ subgoal = Goal.new(Equals.instance, [oil, ref_q])
72
+ solver = subject.solver_for([fails, subgoal], env)
73
+ outcome = solver.resume
74
+ expect(outcome).to be_successful
75
+ expect(outcome.associations['q'].first.value).to eq(oil)
76
+ expect(solver.resume).to be_nil
77
+ end
78
+
79
+ it 'yield two solutions if both arguments succeed' do
80
+ # Covers frame 1:58
81
+ subgoal1 = Goal.new(Equals.instance, [olive, ref_q])
82
+ subgoal2 = Goal.new(Equals.instance, [oil, ref_q])
83
+ solver = subject.solver_for([subgoal1, subgoal2], env)
84
+
85
+ # First solution
86
+ outcome1 = solver.resume
87
+ expect(outcome1).to be_successful
88
+ expect(outcome1.associations['q'].first.value).to eq(olive)
89
+
90
+ # Second solution
91
+ outcome2 = solver.resume
92
+ expect(outcome2).to be_successful
93
+ expect(outcome2.associations['q'].first.value).to eq(oil)
94
+ expect(solver.resume).to be_nil
95
+ end
96
+ end # context
97
+ end # describe
98
+ end # module
99
+ end # module