mini_kraken 0.2.02 → 0.3.02

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 (134) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +378 -333
  3. data/CHANGELOG.md +52 -0
  4. data/README.md +19 -19
  5. data/lib/mini_kraken.rb +0 -1
  6. data/lib/mini_kraken/atomic/all_atomic.rb +5 -0
  7. data/lib/mini_kraken/atomic/atomic_term.rb +96 -0
  8. data/lib/mini_kraken/atomic/k_boolean.rb +42 -0
  9. data/lib/mini_kraken/{core → atomic}/k_integer.rb +2 -5
  10. data/lib/mini_kraken/atomic/k_string.rb +17 -0
  11. data/lib/mini_kraken/{core → atomic}/k_symbol.rb +4 -8
  12. data/lib/mini_kraken/composite/all_composite.rb +4 -0
  13. data/lib/mini_kraken/composite/composite_term.rb +27 -0
  14. data/lib/mini_kraken/composite/cons_cell.rb +299 -0
  15. data/lib/mini_kraken/composite/cons_cell_visitor.rb +50 -0
  16. data/lib/mini_kraken/composite/list.rb +32 -0
  17. data/lib/mini_kraken/core/all_core.rb +8 -0
  18. data/lib/mini_kraken/core/any_value.rb +31 -7
  19. data/lib/mini_kraken/core/arity.rb +69 -0
  20. data/lib/mini_kraken/core/association.rb +29 -4
  21. data/lib/mini_kraken/core/association_copy.rb +50 -0
  22. data/lib/mini_kraken/core/base_term.rb +13 -0
  23. data/lib/mini_kraken/core/blackboard.rb +315 -0
  24. data/lib/mini_kraken/core/bookmark.rb +46 -0
  25. data/lib/mini_kraken/core/context.rb +492 -0
  26. data/lib/mini_kraken/core/duck_fiber.rb +21 -19
  27. data/lib/mini_kraken/core/entry.rb +40 -0
  28. data/lib/mini_kraken/core/fail.rb +20 -18
  29. data/lib/mini_kraken/core/fusion.rb +29 -0
  30. data/lib/mini_kraken/core/goal.rb +20 -29
  31. data/lib/mini_kraken/core/log_var.rb +22 -0
  32. data/lib/mini_kraken/core/log_var_ref.rb +108 -0
  33. data/lib/mini_kraken/core/nullary_relation.rb +2 -9
  34. data/lib/mini_kraken/core/parametrized_term.rb +61 -0
  35. data/lib/mini_kraken/core/relation.rb +14 -28
  36. data/lib/mini_kraken/core/scope.rb +67 -0
  37. data/lib/mini_kraken/core/solver_adapter.rb +58 -0
  38. data/lib/mini_kraken/core/specification.rb +48 -0
  39. data/lib/mini_kraken/core/succeed.rb +21 -17
  40. data/lib/mini_kraken/core/symbol_table.rb +137 -0
  41. data/lib/mini_kraken/core/term.rb +15 -4
  42. data/lib/mini_kraken/glue/dsl.rb +45 -81
  43. data/lib/mini_kraken/glue/run_star_expression.rb +28 -30
  44. data/lib/mini_kraken/rela/all_rela.rb +8 -0
  45. data/lib/mini_kraken/rela/binary_relation.rb +30 -0
  46. data/lib/mini_kraken/rela/conde.rb +143 -0
  47. data/lib/mini_kraken/rela/conj2.rb +65 -0
  48. data/lib/mini_kraken/rela/def_relation.rb +93 -0
  49. data/lib/mini_kraken/rela/disj2.rb +70 -0
  50. data/lib/mini_kraken/rela/fresh.rb +98 -0
  51. data/lib/mini_kraken/{core → rela}/goal_relation.rb +7 -9
  52. data/lib/mini_kraken/rela/unify.rb +258 -0
  53. data/lib/mini_kraken/version.rb +1 -1
  54. data/mini_kraken.gemspec +2 -2
  55. data/spec/.rubocop.yml +1 -1
  56. data/spec/atomic/atomic_term_spec.rb +98 -0
  57. data/spec/{core → atomic}/k_boolean_spec.rb +19 -34
  58. data/spec/{core → atomic}/k_symbol_spec.rb +3 -16
  59. data/spec/composite/cons_cell_spec.rb +225 -0
  60. data/spec/composite/cons_cell_visitor_spec.rb +158 -0
  61. data/spec/composite/list_spec.rb +50 -0
  62. data/spec/core/any_value_spec.rb +52 -0
  63. data/spec/core/arity_spec.rb +92 -0
  64. data/spec/core/association_copy_spec.rb +69 -0
  65. data/spec/core/association_spec.rb +31 -4
  66. data/spec/core/blackboard_spec.rb +287 -0
  67. data/spec/core/bookmark_spec.rb +40 -0
  68. data/spec/core/context_spec.rb +245 -0
  69. data/spec/core/core_spec.rb +40 -0
  70. data/spec/core/duck_fiber_spec.rb +16 -46
  71. data/spec/core/fail_spec.rb +5 -6
  72. data/spec/core/goal_spec.rb +24 -14
  73. data/spec/core/log_var_ref_spec.rb +105 -0
  74. data/spec/core/log_var_spec.rb +64 -0
  75. data/spec/core/nullary_relation_spec.rb +33 -0
  76. data/spec/core/parametrized_tem_spec.rb +39 -0
  77. data/spec/core/relation_spec.rb +33 -0
  78. data/spec/core/scope_spec.rb +73 -0
  79. data/spec/core/solver_adapter_spec.rb +70 -0
  80. data/spec/core/specification_spec.rb +43 -0
  81. data/spec/core/succeed_spec.rb +5 -5
  82. data/spec/core/symbol_table_spec.rb +142 -0
  83. data/spec/glue/dsl_chap1_spec.rb +96 -144
  84. data/spec/glue/dsl_chap2_spec.rb +350 -0
  85. data/spec/glue/run_star_expression_spec.rb +82 -906
  86. data/spec/rela/conde_spec.rb +153 -0
  87. data/spec/rela/conj2_spec.rb +123 -0
  88. data/spec/rela/def_relation_spec.rb +119 -0
  89. data/spec/rela/disj2_spec.rb +117 -0
  90. data/spec/rela/fresh_spec.rb +147 -0
  91. data/spec/rela/unify_spec.rb +369 -0
  92. data/spec/support/factory_atomic.rb +29 -0
  93. data/spec/support/factory_composite.rb +21 -0
  94. data/spec/support/factory_methods.rb +11 -26
  95. metadata +100 -64
  96. data/lib/mini_kraken/core/association_walker.rb +0 -183
  97. data/lib/mini_kraken/core/atomic_term.rb +0 -67
  98. data/lib/mini_kraken/core/base_arg.rb +0 -10
  99. data/lib/mini_kraken/core/binary_relation.rb +0 -63
  100. data/lib/mini_kraken/core/composite_goal.rb +0 -46
  101. data/lib/mini_kraken/core/composite_term.rb +0 -41
  102. data/lib/mini_kraken/core/conde.rb +0 -143
  103. data/lib/mini_kraken/core/conj2.rb +0 -79
  104. data/lib/mini_kraken/core/cons_cell.rb +0 -82
  105. data/lib/mini_kraken/core/def_relation.rb +0 -50
  106. data/lib/mini_kraken/core/designation.rb +0 -55
  107. data/lib/mini_kraken/core/disj2.rb +0 -72
  108. data/lib/mini_kraken/core/environment.rb +0 -73
  109. data/lib/mini_kraken/core/equals.rb +0 -156
  110. data/lib/mini_kraken/core/formal_arg.rb +0 -22
  111. data/lib/mini_kraken/core/formal_ref.rb +0 -25
  112. data/lib/mini_kraken/core/freshness.rb +0 -45
  113. data/lib/mini_kraken/core/goal_arg.rb +0 -12
  114. data/lib/mini_kraken/core/goal_template.rb +0 -62
  115. data/lib/mini_kraken/core/k_boolean.rb +0 -35
  116. data/lib/mini_kraken/core/outcome.rb +0 -53
  117. data/lib/mini_kraken/core/variable.rb +0 -41
  118. data/lib/mini_kraken/core/variable_ref.rb +0 -78
  119. data/lib/mini_kraken/core/vocabulary.rb +0 -442
  120. data/lib/mini_kraken/glue/fresh_env.rb +0 -75
  121. data/spec/core/association_walker_spec.rb +0 -192
  122. data/spec/core/conde_spec.rb +0 -147
  123. data/spec/core/conj2_spec.rb +0 -114
  124. data/spec/core/cons_cell_spec.rb +0 -107
  125. data/spec/core/def_relation_spec.rb +0 -96
  126. data/spec/core/disj2_spec.rb +0 -99
  127. data/spec/core/environment_spec.rb +0 -142
  128. data/spec/core/equals_spec.rb +0 -304
  129. data/spec/core/goal_template_spec.rb +0 -74
  130. data/spec/core/outcome_spec.rb +0 -48
  131. data/spec/core/variable_ref_spec.rb +0 -27
  132. data/spec/core/variable_spec.rb +0 -35
  133. data/spec/core/vocabulary_spec.rb +0 -219
  134. data/spec/glue/fresh_env_spec.rb +0 -62
@@ -21,17 +21,17 @@ module MiniKraken
21
21
  end # context
22
22
 
23
23
  context 'Provided services:' do
24
- it 'should unconditionally return a success result' do
25
- args = double('fake-args')
26
- env = double('fake-env')
24
+ let(:ctx) { Context.new }
27
25
 
26
+ it 'should unconditionally return a success result' do
28
27
  solver = nil
29
- expect { solver = subject.solver_for(args, env) }.not_to raise_error
28
+ expect { solver = subject.solver_for([], ctx) }.not_to raise_error
30
29
 
31
30
  # Solver should quack like a Fiber
32
31
  dummy_arg = double('dummy-stuff')
33
32
  result = solver.resume(dummy_arg)
34
- expect(result).to eq(BasicSuccess)
33
+ expect(result).to eq(ctx)
34
+ expect(result).to be_success
35
35
 
36
36
  # Only one "solution", next 'resume' call should return nil
37
37
  result = solver.resume(dummy_arg)
@@ -0,0 +1,142 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../spec_helper' # Use the RSpec framework
4
+ require_relative '../../lib/mini_kraken/core/log_var'
5
+
6
+ # Load the class under test
7
+ require_relative '../../lib/mini_kraken/core/symbol_table'
8
+
9
+ module MiniKraken
10
+ module Core
11
+ describe SymbolTable do
12
+ subject { SymbolTable.new }
13
+
14
+ context 'Initialization:' do
15
+ it 'should be initialized without argument' do
16
+ expect { SymbolTable.new }.not_to raise_error
17
+ end
18
+
19
+ it 'should have a root scope' do
20
+ expect(subject.root).not_to be_nil
21
+ expect(subject.current_scope).to eq(subject.root)
22
+ expect(subject.root).to be_kind_of(Core::Scope)
23
+ end
24
+
25
+ it "shouldn't have names at initialization" do
26
+ expect(subject.name2scopes).to be_empty
27
+ end
28
+
29
+ it 'should be empty at initialization' do
30
+ expect(subject).to be_empty
31
+ end
32
+ end # context
33
+
34
+ context 'Provided services:' do
35
+ def var(aName)
36
+ LogVar.new(aName)
37
+ end
38
+
39
+ it 'should allow the addition of a variable' do
40
+ expect { subject.insert(var('q')) }.not_to raise_error
41
+ expect(subject).not_to be_empty
42
+ expect(subject.name2scopes['q']).to be_kind_of(Array)
43
+ expect(subject.name2scopes['q'].size).to eq(1)
44
+ expect(subject.name2scopes['q'].first).to eq(subject.current_scope)
45
+ expect(subject.current_scope.defns['q']).to be_kind_of(Core::LogVar)
46
+ end
47
+
48
+ it 'should allow the addition of several labels for same env' do
49
+ i_name = subject.insert(var('q'))
50
+ expect(i_name).to match(/^q_[0-9a-z]+$/)
51
+
52
+ expect { subject.insert(var('x')) }.not_to raise_error
53
+ expect(subject.name2scopes['x']).to be_kind_of(Array)
54
+ expect(subject.name2scopes['x'].first).to eq(subject.current_scope)
55
+ expect(subject.current_scope.defns['x']).to be_kind_of(Core::LogVar)
56
+ end
57
+
58
+ it 'should allow the entry into a new scope' do
59
+ subject.insert(var('q'))
60
+ new_scope = Core::Scope.new
61
+ expect { subject.enter_scope(new_scope) }.not_to raise_error
62
+ expect(subject.current_scope).to eq(new_scope)
63
+ expect(subject.current_scope.parent).to eq(subject.root)
64
+ expect(subject.name2scopes['q']).to eq([subject.root])
65
+ end
66
+
67
+ it 'should allow the addition of same name in different scopes' do
68
+ subject.insert(var('q'))
69
+ subject.enter_scope(Core::Scope.new)
70
+ subject.insert(var('q'))
71
+ expect(subject.name2scopes['q']).to be_kind_of(Array)
72
+ expect(subject.name2scopes['q'].size).to eq(2)
73
+ expect(subject.name2scopes['q'].first).to eq(subject.root)
74
+ expect(subject.name2scopes['q'].last).to eq(subject.current_scope)
75
+ expect(subject.current_scope.defns['q']).to be_kind_of(Core::LogVar)
76
+ end
77
+
78
+ it 'should allow the removal of a scope' do
79
+ subject.insert(var('q'))
80
+ new_scope = Core::Scope.new
81
+ subject.enter_scope(new_scope)
82
+ subject.insert(var('q'))
83
+ expect(subject.name2scopes['q'].size).to eq(2)
84
+
85
+ expect { subject.leave_scope }.not_to raise_error
86
+ expect(subject.current_scope).to eq(subject.root)
87
+ expect(subject.name2scopes['q'].size).to eq(1)
88
+ expect(subject.name2scopes['q']).to eq([subject.root])
89
+ end
90
+
91
+ it 'should allow the search of an entry based on its name' do
92
+ subject.insert(var('q'))
93
+ subject.insert(var('x'))
94
+ subject.enter_scope(Core::Scope.new)
95
+ subject.insert(var('q'))
96
+ subject.insert(var('y'))
97
+
98
+ # Search for unknown name
99
+ expect(subject.lookup('z')).to be_nil
100
+
101
+ # Search for existing unique names
102
+ expect(subject.lookup('y')).to eq(subject.current_scope.defns['y'])
103
+ expect(subject.lookup('x')).to eq(subject.root.defns['x'])
104
+
105
+ # Search for redefined name
106
+ expect(subject.lookup('q')).to eq(subject.current_scope.defns['q'])
107
+ end
108
+
109
+ it 'should allow the search of an entry based on its i_name' do
110
+ subject.insert(var('q'))
111
+ i_name_x = subject.insert(var('x'))
112
+ subject.enter_scope(Core::Scope.new)
113
+ i_name_q2 = subject.insert(var('q'))
114
+ i_name_y = subject.insert(var('y'))
115
+
116
+ # Search for unknown i_name
117
+ expect(subject.lookup_i_name('dummy')).to be_nil
118
+
119
+ curr_scope = subject.current_scope
120
+ # # Search for existing unique names
121
+ expect(subject.lookup_i_name(i_name_y)).to eq(curr_scope.defns['y'])
122
+ expect(subject.lookup_i_name(i_name_x)).to eq(subject.root.defns['x'])
123
+
124
+ # Search for redefined name
125
+ expect(subject.lookup_i_name(i_name_q2)).to eq(curr_scope.defns['q'])
126
+ end
127
+
128
+ it 'should list all the variables defined in all the szcope chain' do
129
+ subject.insert(var('q'))
130
+ subject.enter_scope(Core::Scope.new)
131
+ subject.insert(var('x'))
132
+ subject.enter_scope(Core::Scope.new)
133
+ subject.insert(var('y'))
134
+ subject.insert(var('x'))
135
+
136
+ vars = subject.all_variables
137
+ expect(vars.map(&:name)).to eq(%w[q x y x])
138
+ end
139
+ end # context
140
+ end # describe
141
+ end # module
142
+ end # module
@@ -13,7 +13,6 @@ module MiniKraken
13
13
 
14
14
  context 'Chapter 1 examples:' do
15
15
  it 'passes frame 1:7' do
16
- # Reasoned S2, frame 1:7
17
16
  # (run* q #u) ;; => ()
18
17
 
19
18
  result = run_star('q', _fail)
@@ -21,18 +20,16 @@ module MiniKraken
21
20
  end
22
21
 
23
22
  it 'passes frame 1:10' do
24
- # Reasoned S2, frame 1:10
25
23
  # (run* q (== 'pea 'pod) ;; => ()
26
24
 
27
- result = run_star('q', equals(:pea, :pod))
25
+ result = run_star('q', unify(:pea, :pod))
28
26
  expect(result).to be_null
29
27
  end
30
28
 
31
29
  it 'passes frame 1:11' do
32
- # Reasoned S2, frame 1:11
33
30
  # (run* q (== q 'pea) ;; => (pea)
34
31
 
35
- result = run_star('q', equals(q, :pea))
32
+ result = run_star('q', unify(q, :pea))
36
33
  expect(result.car).to eq(:pea)
37
34
  end
38
35
 
@@ -40,12 +37,11 @@ module MiniKraken
40
37
  # Reasoned S2, frame 1:12
41
38
  # (run* q (== 'pea q) ;; => (pea)
42
39
 
43
- result = run_star('q', equals(:pea, q))
40
+ result = run_star('q', unify(:pea, q))
44
41
  expect(result.car).to eq(:pea)
45
42
  end
46
43
 
47
44
  it 'passes frame 1:17' do
48
- # Reasoned S2, frame 1:17
49
45
  # (run* q succeed) ;; => (_0)
50
46
 
51
47
  result = run_star('q', succeed)
@@ -56,15 +52,14 @@ module MiniKraken
56
52
  # Reasoned S2, frame 1:19
57
53
  # (run* q (== 'pea 'pea)) ;; => (_0)
58
54
 
59
- result = run_star('q', equals(:pea, :pea))
55
+ result = run_star('q', unify(:pea, :pea))
60
56
  expect(result.car).to eq(:_0)
61
57
  end
62
58
 
63
59
  it 'passes frame 1:20' do
64
- # Reasoned S2, frame 1:20
65
60
  # (run* q (== q q)) ;; => (_0)
66
61
 
67
- result = run_star('q', equals(q, q))
62
+ result = run_star('q', unify(q, q))
68
63
  expect(result.car).to eq(:_0)
69
64
  end
70
65
 
@@ -72,80 +67,81 @@ module MiniKraken
72
67
  # Reasoned S2, frame 1:21
73
68
  # (run* q (fresh (x) (== 'pea q))) ;; => (pea)
74
69
 
75
- result = run_star('q', fresh('x', equals(:pea, q)))
70
+ result = run_star('q', fresh('x', unify(:pea, q)))
76
71
  expect(result.car).to eq(:pea)
77
72
  end
78
73
 
79
74
  it 'passes frame 1:25' do
80
- # Reasoned S2, frame 1:25
81
- # (run* q (fresh (x) (== (cons x '()) q))) ;; => ((_0))
82
- # require 'debug' Invalid goal argument
83
- result = run_star('q', fresh('x', equals(cons(x, null), q)))
84
- expect(result.car).to eq(cons(:_0))
75
+ # (run* q
76
+ # (fresh (x)
77
+ # (== (cons x '()) q))) ;; => ((_0))
78
+
79
+ result = run_star('q', fresh('x', unify(cons(x, null_list), q)))
80
+ expect(result.car.to_s).to eq('(_0)')
85
81
  end
86
82
 
87
83
  it 'passes frame 1:31' do
88
- # Reasoned S2, frame 1:31
89
84
  # (run* q (fresh (x) (== x q))) ;; => (_0)
90
85
 
91
- result = run_star('q', fresh('x', equals(x, q)))
86
+ result = run_star('q', fresh('x', unify(x, q)))
92
87
  expect(result.car).to eq(:_0)
93
88
  end
94
89
 
95
90
  it 'passes frame 1:32' do
96
- # Reasoned S2, frame 1:32
97
91
  # (run* q (== '(((pea)) pod) '(((pea)) pod))) ;; => (_0)
98
92
 
99
- result = run_star('q', equals(cons(cons(:pea), :pod), cons(cons(:pea), :pod)))
93
+ result = run_star('q', unify(cons(cons(:pea), :pod), cons(cons(:pea), :pod)))
100
94
  expect(result.car).to eq(:_0)
101
95
  end
102
96
 
103
97
  it 'passes frame 1:33' do
104
98
  # Beware: quasiquoting
105
- # Reasoned S2, frame 1:33
106
99
  # (run* q (== '(((pea)) pod) '(((pea)) ,q))) ;; => ('pod)
107
100
 
108
- result = run_star('q', equals(cons(cons(:pea), :pod), cons(cons(:pea), q)))
101
+ result = run_star('q', unify(cons(cons(:pea), :pod), cons(cons(:pea), q)))
109
102
  expect(result.car).to eq(:pod)
110
103
  end
111
104
 
112
105
  it 'passes frame 1:34' do
113
- # Reasoned S2, frame 1:34
114
106
  # (run* q (== '(((,q)) pod) `(((pea)) pod))) ;; => ('pea)
115
107
 
116
- result = run_star('q', equals(cons(cons(q), :pod), cons(cons(:pea), q)))
108
+ expr1 = cons(cons(cons(q)), cons(:pod))
109
+ expect(expr1.to_s).to eq('(((q)) :pod)')
110
+ expr2 = cons(cons(cons(:pea)), cons(:pod))
111
+ expect(expr2.to_s).to eq('(((:pea)) :pod)')
112
+ result = run_star('q', unify(expr1, expr2))
117
113
  expect(result.car).to eq(:pea)
118
114
  end
119
115
 
120
116
  it 'passes frame 1:35' do
121
- # Reasoned S2, frame 1:35
122
117
  # (run* q (fresh (x) (== '(((,q)) pod) `(((,x)) pod)))) ;; => (_0)
123
118
 
124
- result = run_star('q', fresh('x', equals(cons(cons(q), :pod), cons(cons(x), :pod))))
119
+ result = run_star('q', fresh('x', unify(cons(cons(q), :pod), cons(cons(x), :pod))))
125
120
  expect(result.car).to eq(:_0)
126
121
  end
127
122
 
128
123
  it 'passes frame 1:36' do
129
- # Reasoned S2, frame 1:36
130
124
  # (run* q (fresh (x) (== '(((,q)) ,x) `(((,x)) pod)))) ;; => ('pod)
131
125
 
132
- result = run_star('q', fresh('x', equals(cons(cons(cons(q)), x), cons(cons(cons(x)), :pod))))
126
+ expr1 = cons(cons(cons(q)), cons(x))
127
+ expect(expr1.to_s).to eq('(((q)) x)')
128
+ expr2 = cons(cons(cons(x)), cons(:pod))
129
+ expect(expr2.to_s).to eq('(((x)) :pod)')
130
+ result = run_star('q', fresh('x', unify(expr1, expr2)))
133
131
  expect(result.car).to eq(:pod)
134
132
  end
135
133
 
136
134
  it 'passes frame 1:37' do
137
- # Reasoned S2, frame 1:37
138
135
  # (run* q (fresh (x) (== '( ,x ,x) q))) ;; => (_0 _0)
139
136
 
140
- result = run_star('q', fresh('x', equals(cons(x, cons(x)), q)))
137
+ result = run_star('q', fresh('x', unify(cons(x, cons(x)), q)))
141
138
  expect(result.car).to eq(cons(:_0, cons(:_0)))
142
139
  end
143
140
 
144
141
  it 'passes frame 1:38' do
145
- # Reasoned S2, frame 1:38
146
142
  # (run* q (fresh (x) (fresh (y) (== '( ,q ,y) '((,x ,y) ,x))))) ;; => (_0 _0)
147
143
 
148
- result = run_star('q', fresh('x', fresh('y', equals(cons(q, cons(y)), cons(cons(x, cons(y)), cons(x))))))
144
+ result = run_star('q', fresh('x', fresh('y', unify(cons(q, cons(y)), cons(cons(x, cons(y)), cons(x))))))
149
145
  expect(result.car).to eq(cons(:_0, cons(:_0)))
150
146
  end
151
147
 
@@ -153,25 +149,23 @@ module MiniKraken
153
149
  # Reasoned S2, frame 1:41
154
150
  # (run* q (fresh (x) (fresh (y) (== '( ,x ,y) q)))) ;; => (_0 _1)
155
151
 
156
- result = run_star('q', fresh('x', fresh('y', equals(cons(x, cons(y)), q))))
152
+ result = run_star('q', fresh('x', fresh('y', unify(cons(x, cons(y)), q))))
157
153
  # q should be bound to '(,x ,y)
158
154
  expect(result.car).to eq(cons(:_0, cons(:_1)))
159
155
  end
160
156
 
161
157
  it 'passes frame 1:42' do
162
- # Reasoned S2, frame 1:42
163
158
  # (run* s (fresh (t) (fresh (u) (== '( ,t ,u) s)))) ;; => (_0 _1)
164
159
 
165
- result = run_star('s', fresh('t', fresh('u', equals(cons(t, cons(u)), s))))
160
+ result = run_star('s', fresh('t', fresh('u', unify(cons(t, cons(u)), s))))
166
161
  # s should be bound to '(,t ,u)
167
162
  expect(result.car).to eq(cons(:_0, cons(:_1)))
168
163
  end
169
164
 
170
165
  it 'passes frame 1:43' do
171
- # Reasoned S2, frame 1:43
172
166
  # (run* q (fresh (x) (fresh (y) (== '( ,x ,y ,x) q)))) ;; => (_0 _1 _0)
173
167
 
174
- result = run_star('q', fresh('x', fresh('y', equals(cons(x, cons(y, cons(x))), q))))
168
+ result = run_star('q', fresh('x', fresh('y', unify(cons(x, cons(y, cons(x))), q))))
175
169
  # q should be bound to '(,x ,y, ,x)
176
170
  expect(result.car).to eq(cons(:_0, cons(:_1, cons(:_0))))
177
171
  end
@@ -185,18 +179,16 @@ module MiniKraken
185
179
  end
186
180
 
187
181
  it 'passes frame 1:51' do
188
- # Reasoned S2, frame 1:51
189
182
  # (run* q (conj2 succeed (== 'corn q)) ;; => ('corn)
190
183
 
191
- result = run_star('q', conj2(succeed, equals(:corn, q)))
184
+ result = run_star('q', conj2(succeed, unify(:corn, q)))
192
185
  expect(result.car).to eq(:corn)
193
186
  end
194
187
 
195
188
  it 'passes frame 1:52' do
196
- # Reasoned S2, frame 1:52
197
189
  # (run* q (conj2 fail (== 'corn q)) ;; => ()
198
190
 
199
- result = run_star('q', conj2(_fail, equals(:corn, q)))
191
+ result = run_star('q', conj2(_fail, unify(:corn, q)))
200
192
  expect(result).to be_null
201
193
  end
202
194
 
@@ -204,20 +196,18 @@ module MiniKraken
204
196
  # Reasoned S2, frame 1:53
205
197
  # (run* q (conj2 (== 'corn q)(== 'meal q)) ;; => ()
206
198
 
207
- result = run_star('q', conj2(equals(:corn, q), equals(:meal, q)))
199
+ result = run_star('q', conj2(unify(:corn, q), unify(:meal, q)))
208
200
  expect(result).to be_null
209
201
  end
210
202
 
211
203
  it 'passes frame 1:54' do
212
- # Reasoned S2, frame 1:54
213
204
  # (run* q (conj2 (== 'corn q)(== 'corn q)) ;; => ('corn)
214
205
 
215
- result = run_star('q', conj2(equals(:corn, q), equals(:corn, q)))
206
+ result = run_star('q', conj2(unify(:corn, q), unify(:corn, q)))
216
207
  expect(result.car).to eq(:corn)
217
208
  end
218
209
 
219
210
  it "supports 'disj2' and passes frame 1:55" do
220
- # Reasoned S2, frame 1:55
221
211
  # (run* q (disj2 fail fail)) ;; => ()
222
212
 
223
213
  result = run_star('q', disj2(_fail, _fail))
@@ -225,73 +215,65 @@ module MiniKraken
225
215
  end
226
216
 
227
217
  it 'passes frame 1:56' do
228
- # Reasoned S2, frame 1:56
229
218
  # (run* q (disj2 (== 'olive q) fail)) ;; => ('olive)
230
219
 
231
- result = run_star('q', disj2(equals(:olive, q), _fail))
220
+ result = run_star('q', disj2(unify(:olive, q), _fail))
232
221
  expect(result.car).to eq(:olive)
233
222
  end
234
223
 
235
224
  it 'passes frame 1:57' do
236
- # Reasoned S2, frame 1:57
237
225
  # (run* q (disj2 fail (== 'oil q))) ;; => (oil)
238
226
 
239
- result = run_star('q', disj2(_fail, equals(:oil, q)))
227
+ result = run_star('q', disj2(_fail, unify(:oil, q)))
240
228
  expect(result.car).to eq(:oil)
241
229
  end
242
230
 
243
231
  it 'passes frame 1:58' do
244
- # Reasoned S2, frame 1:58
245
232
  # (run* q (disj2 (== 'olive q) (== 'oil q))) ;; => (olive oil)
246
233
 
247
- result = run_star('q', disj2(equals(:olive, q), equals(:oil, q)))
234
+ result = run_star('q', disj2(unify(:olive, q), unify(:oil, q)))
248
235
  expect(result.car).to eq(:olive)
249
236
  expect(result.cdr.car).to eq(:oil)
250
237
  end
251
238
 
252
239
  it 'passes frame 1:59' do
253
- # Reasoned S2, frame 1:59
254
240
  # (run* q (fresh (x) (fresh (y) (disj2 (== '( ,x ,y ) q) (== '( ,x ,y ) q)))))
255
241
  # ;; => ((_0 _1) (_0 _1))
256
242
 
257
- result = run_star('q', fresh('x', (fresh 'y', disj2(equals(cons(x, cons(y)), q), equals(cons(x, cons(y)), q)))))
243
+ result = run_star('q', fresh('x', (fresh 'y', disj2(unify(cons(x, cons(y)), q), unify(cons(x, cons(y)), q)))))
258
244
  # q should be bound to '(,x ,y), then to '(,y ,x)
259
245
  expect(result.car).to eq(cons(:_0, cons(:_1)))
260
246
  expect(result.cdr.car).to eq(cons(:_0, cons(:_1)))
261
247
  end
262
248
 
263
249
  it 'passes frame 1:62' do
264
- # Reasoned S2, frame 1:62
265
250
  # (run* x (disj2
266
251
  # (conj2 (== 'olive x) fail)
267
252
  # (== 'oil x))) ;; => (oil)
268
253
 
269
- result = run_star('x', disj2(conj2(equals(:olive, x), _fail), equals(:oil, x)))
254
+ result = run_star('x', disj2(conj2(unify(:olive, x), _fail), unify(:oil, x)))
270
255
  expect(result.car).to eq(:oil)
271
256
  end
272
257
 
273
258
  it 'passes frame 1:63' do
274
- # Reasoned S2, frame 1:63
275
259
  # (run* x (disj2
276
260
  # (conj2 (== 'olive x) succeed)
277
261
  # ('oil x))) ;; => (olive oil)
278
262
 
279
- result = run_star('x', disj2(conj2(equals(:olive, x), succeed), equals(:oil, x)))
263
+ result = run_star('x', disj2(conj2(unify(:olive, x), succeed), unify(:oil, x)))
280
264
  expect(result).to eq(cons(:olive, cons(:oil)))
281
265
  end
282
266
 
283
267
  it 'passes frame 1:64' do
284
- # Reasoned S2, frame 1:64
285
268
  # (run* x (disj2
286
269
  # (== 'oil x)
287
270
  # (conj2 (== 'olive x) succeed))) ;; => (oil olive)
288
271
 
289
- result = run_star('x', disj2(equals(:oil, x), conj2(equals(:olive, x), succeed)))
272
+ result = run_star('x', disj2(unify(:oil, x), conj2(unify(:olive, x), succeed)))
290
273
  expect(result).to eq(cons(:oil, cons(:olive)))
291
274
  end
292
275
 
293
276
  it 'passes frame 1:65' do
294
- # Reasoned S2, frame 1:65
295
277
  # (run* x (disj2
296
278
  # (conj2(== 'virgin x) fail)
297
279
  # (disj2
@@ -300,13 +282,13 @@ module MiniKraken
300
282
  # succeed
301
283
  # (== 'oil x))))) ;; => (olive _0 oil)
302
284
 
303
- result = run_star('x', disj2(conj2(equals(:virgin, x), _fail),
304
- disj2(equals(:olive, x), disj2(succeed, equals(:oil, x)))))
285
+ result = run_star('x', disj2(conj2(unify(:virgin, x), _fail),
286
+ disj2(unify(:olive, x), disj2(succeed, unify(:oil, x)))))
305
287
  expect(result).to eq(cons(:olive, cons(:_0, cons(:oil))))
288
+ expect(result.to_s).to eq('(:olive _0 :oil)')
306
289
  end
307
290
 
308
291
  it 'passes frame 1:67' do
309
- # Reasoned S2, frame 1:67
310
292
  # (run* r
311
293
  # (fresh x
312
294
  # (fresh y
@@ -317,13 +299,12 @@ module MiniKraken
317
299
  # (== '(,x ,y) r)))))) ;; => ((split pea))
318
300
 
319
301
  result = run_star('r', fresh('x', fresh('y',
320
- conj2(equals(:split, x),
321
- conj2(equals(:pea, y), equals(cons(x, cons(y)), r))))))
322
- expect(result).to eq(cons(cons(:split, cons(:pea))))
302
+ conj2(unify(:split, x),
303
+ conj2(unify(:pea, y), unify(cons(x, cons(y)), r))))))
304
+ expect(result.to_s).to eq('((:split :pea))')
323
305
  end
324
306
 
325
307
  it 'passes frame 1:68' do
326
- # Reasoned S2, frame 1:68
327
308
  # (run* r
328
309
  # (fresh x
329
310
  # (fresh y
@@ -334,13 +315,12 @@ module MiniKraken
334
315
  # (== '(,x ,y) r)))))) ;; => ((split pea))
335
316
 
336
317
  result = run_star('r', fresh('x', fresh('y',
337
- conj2(conj2(equals(:split, x), equals(:pea, y)),
338
- equals(cons(x, cons(y)), r)))))
339
- expect(result).to eq(cons(cons(:split, cons(:pea))))
318
+ conj2(conj2(unify(:split, x), unify(:pea, y)),
319
+ unify(cons(x, cons(y)), r)))))
320
+ expect(result.to_s).to eq('((:split :pea))')
340
321
  end
341
322
 
342
323
  it 'passes frame 1:70' do
343
- # Reasoned S2, frame 1:70
344
324
  # (run* r
345
325
  # (fresh (x y)
346
326
  # (conj2
@@ -350,13 +330,12 @@ module MiniKraken
350
330
  # (== '(,x ,y) r))))) ;; => ((split pea))
351
331
 
352
332
  result = run_star('r', fresh(%w[x y], conj2(
353
- conj2(equals(:split, x), equals(:pea, y)),
354
- equals(cons(x, cons(y)), r))))
355
- expect(result).to eq(cons(cons(:split, cons(:pea))))
333
+ conj2(unify(:split, x), unify(:pea, y)),
334
+ unify(cons(x, cons(y)), r))))
335
+ expect(result.to_s).to eq('((:split :pea))')
356
336
  end
357
337
 
358
338
  it 'passes frame 1:72' do
359
- # Reasoned S2, frame 1:72
360
339
  # (run* (r x y)
361
340
  # (conj2
362
341
  # (conj2
@@ -378,43 +357,42 @@ module MiniKraken
378
357
  # pea nil pea nil
379
358
 
380
359
  result = run_star(%w[r x y], conj2(
381
- conj2(equals(:split, x), equals(:pea, y)),
382
- equals(cons(x, cons(y)), r)))
360
+ conj2(unify(:split, x), unify(:pea, y)),
361
+ unify(cons(x, cons(y)), r)))
383
362
  expect(result.car.car.car).to eq(:split)
384
363
  expect(result.car.car.cdr.car).to eq(:pea)
385
364
  expect(result.car.car.cdr.cdr).to be_nil
386
365
  expect(result.car.cdr.car).to eq(:split)
387
366
  expect(result.car.cdr.cdr.car).to eq(:pea)
388
367
  expect(result.car.cdr.cdr.cdr).to be_nil
368
+ expect(result.to_s).to eq('(((:split :pea) :split :pea))')
389
369
  end
390
370
 
391
371
  it 'passes frame 1:75' do
392
- # Reasoned S2, frame 1:75
393
372
  # (run* (x y)
394
373
  # (conj2
395
374
  # (== 'split x)
396
375
  # (== 'pea y))) ;; => ((split pea))
397
376
 
398
- result = run_star(%w[x y], conj2(equals(:split, x), equals(:pea, y)))
399
- expect(result.car).to eq(list(:split, :pea))
377
+ result = run_star(%w[x y], conj2(unify(:split, x), unify(:pea, y)))
378
+ expect(result.to_s).to eq('((:split :pea))')
400
379
  end
401
380
 
402
381
  it 'passes frame 1:76' do
403
- # Reasoned S2, frame 1:76
404
382
  # (run* (x y)
405
383
  # (disj2
406
384
  # (conj2 (== 'split x) (== 'pea y))
407
385
  # (conj2 (== 'red x) (== 'bean y)))) ;; => ((split pea)(red bean))
408
386
 
409
387
  result = run_star(%w[x y], disj2(
410
- conj2(equals(:split, x), equals(:pea, y)),
411
- conj2(equals(:red, x), equals(:bean, y))))
388
+ conj2(unify(:split, x), unify(:pea, y)),
389
+ conj2(unify(:red, x), unify(:bean, y))))
390
+ expect(result.to_s).to eq('((:split :pea) (:red :bean))')
412
391
  expect(result.car).to eq(list(:split, :pea))
413
392
  expect(result.cdr.car).to eq(list(:red, :bean))
414
393
  end
415
394
 
416
395
  it 'passes frame 1:77' do
417
- # Reasoned S2, frame 1:77
418
396
  # (run* r
419
397
  # (fresh (x y)
420
398
  # (conj2
@@ -426,15 +404,15 @@ module MiniKraken
426
404
  result = run_star('r',
427
405
  fresh(%w[x y], conj2(
428
406
  disj2(
429
- conj2(equals(:split, x), equals(:pea, y)),
430
- conj2(equals(:red, x), equals(:bean, y))),
431
- equals(cons(x, cons(y, cons(:soup))), r))))
407
+ conj2(unify(:split, x), unify(:pea, y)),
408
+ conj2(unify(:red, x), unify(:bean, y))),
409
+ unify(cons(x, cons(y, cons(:soup))), r))))
410
+ expect(result.to_s).to eq('((:split :pea :soup) (:red :bean :soup))')
432
411
  expect(result.car).to eq(list(:split, :pea, :soup))
433
412
  expect(result.cdr.car).to eq(list(:red, :bean, :soup))
434
413
  end
435
414
 
436
415
  it 'passes frame 1:78' do
437
- # Reasoned S2, frame 1:78
438
416
  # (run* r
439
417
  # (fresh (x y)
440
418
  # (disj2
@@ -444,15 +422,13 @@ module MiniKraken
444
422
 
445
423
  result = run_star('r',
446
424
  fresh(%w[x y], [disj2(
447
- conj2(equals(:split, x), equals(:pea, y)),
448
- conj2(equals(:red, x), equals(:bean, y))),
449
- equals(cons(x, cons(y, cons(:soup))), r)]))
450
- expect(result.car).to eq(list(:split, :pea, :soup))
451
- expect(result.cdr.car).to eq(list(:red, :bean, :soup))
425
+ conj2(unify(:split, x), unify(:pea, y)),
426
+ conj2(unify(:red, x), unify(:bean, y))),
427
+ unify(cons(x, cons(y, cons(:soup))), r)]))
428
+ expect(result.to_s).to eq('((:split :pea :soup) (:red :bean :soup))')
452
429
  end
453
430
 
454
431
  it 'passes frame 1:80' do
455
- # Reasoned S2, frame 1:80
456
432
  # (run* (x y z)
457
433
  # (disj2
458
434
  # (conj2 (== 'split x) (== 'pea y))
@@ -460,56 +436,38 @@ module MiniKraken
460
436
  # (== 'soup z)) ;; => ((split pea soup) (red bean soup))
461
437
 
462
438
  result = run_star(%w[x y z], [disj2(
463
- conj2(equals(:split, x), equals(:pea, y)),
464
- conj2(equals(:red, x), equals(:bean, y))),
465
- equals(:soup, z)])
439
+ conj2(unify(:split, x), unify(:pea, y)),
440
+ conj2(unify(:red, x), unify(:bean, y))),
441
+ unify(:soup, z)])
466
442
  expect(result.car).to eq(list(:split, :pea, :soup))
467
443
  expect(result.cdr.car).to eq(list(:red, :bean, :soup))
468
444
  end
469
445
 
470
446
  it 'passes frame 1:81' do
471
- # Reasoned S2, frame 1:81
472
447
  # (run* (x y)
473
448
  # (== 'split x)
474
449
  # (== 'pea y)) ;; => ((split pea))
475
450
 
476
- result = run_star(%w[x y], [equals(:split, x), equals(:pea, y)])
451
+ result = run_star(%w[x y], [unify(:split, x), unify(:pea, y)])
477
452
  expect(result.car).to eq(list(:split, :pea))
478
453
  end
479
454
 
480
455
  it "supports 'defrel' and passes frame 1:82" do
481
- # Reasoned S2, frame 1:82
482
456
  # (defrel (teacupo t)
483
457
  # (disj2 (== 'tea t) (== 'cup t)))
484
-
485
- result = defrel('teacupo', 't') do
486
- disj2(equals(:tea, t), equals(:cup, t))
487
- end
488
-
489
- expect(result).to be_kind_of(Core::DefRelation)
458
+ expr = disj2(unify(:tea, t), unify(:cup, t))
459
+ result = defrel('teacupo', ['t'], expr)
460
+ expect(result).to be_kind_of(Rela::DefRelation)
490
461
  expect(result.name).to eq('teacupo')
491
462
  expect(result.formals.size).to eq(1)
492
- expect(result.formals[0].name).to eq('t')
493
- g_template = result.goal_template
494
- expect(g_template).to be_kind_of(Core::GoalTemplate)
495
- expect(g_template.relation).to eq(Core::Disj2.instance)
496
-
497
- first_arg = g_template.args[0]
498
- expect(first_arg).to be_kind_of(Core::GoalTemplate)
499
- expect(first_arg.relation).to eq(Core::Equals.instance)
500
- expect(first_arg.args[0]).to eq(:tea)
501
- expect(first_arg.args[1]).to be_kind_of(Core::FormalRef)
502
- expect(first_arg.args[1].name).to eq('t')
503
- second_arg = g_template.args[1]
504
- expect(second_arg).to be_kind_of(Core::GoalTemplate)
505
- expect(second_arg.relation).to eq(Core::Equals.instance)
506
- expect(second_arg.args[0]).to eq(:cup)
507
- expect(second_arg.args[1]).to be_kind_of(Core::FormalRef)
508
- expect(second_arg.args[1].name).to eq('t')
463
+ expect(result.formals[0]).to match(/^t_/)
464
+ goal_expr = result.expression
465
+ expect(goal_expr).to be_kind_of(Core::Goal)
466
+ expect(goal_expr.relation).to eq(Rela::Disj2.instance)
509
467
  end
510
468
 
511
- def defrel_teacupo
512
- defrel('teacupo', 't') { disj2(equals(:tea, t), equals(:cup, t)) }
469
+ let(:defrel_teacupo) do
470
+ defrel('teacupo', ['t'], disj2(unify(:tea, t), unify(:cup, t)))
513
471
  end
514
472
 
515
473
  it "supports the invokation of a 'defrel' and passes frame 1:83" do
@@ -533,8 +491,8 @@ module MiniKraken
533
491
  defrel_teacupo
534
492
  result = run_star(%w[x y],
535
493
  disj2(
536
- conj2(teacupo(x), equals('#t', y)),
537
- conj2(equals('#f', x), equals('#t', y))))
494
+ conj2(teacupo(x), unify('#t', y)),
495
+ conj2(unify('#f', x), unify('#t', y))))
538
496
 
539
497
  # Order of solutions differs from RS book
540
498
  expect(result.car).to eq(cons(:tea, cons(true)))
@@ -543,7 +501,6 @@ module MiniKraken
543
501
  end
544
502
 
545
503
  it 'passes frame 1:85' do
546
- # Reasoned S2, frame 1:85
547
504
  # (run* (x y)
548
505
  # (teacupo x)
549
506
  # (teacupo y)) ;; => ((tea tea)(tea cup)(cup tea)(cup c))
@@ -558,7 +515,6 @@ module MiniKraken
558
515
  end
559
516
 
560
517
  it 'passes frame 1:86' do
561
- # Reasoned S2, frame 1:86
562
518
  # (run* (x y)
563
519
  # (teacupo x)
564
520
  # (teacupo x)) ;; => ((tea _0)(cup _0))
@@ -569,7 +525,6 @@ module MiniKraken
569
525
  end
570
526
 
571
527
  it 'passes frame 1:87' do
572
- # Reasoned S2, frame 1:87
573
528
  # (run* (x y)
574
529
  # (disj2
575
530
  # (conj2 (teacupo x) (teacupo x))
@@ -578,7 +533,7 @@ module MiniKraken
578
533
  defrel_teacupo
579
534
  result = run_star(%w[x y], disj2(
580
535
  conj2(teacupo(x), teacupo(x)),
581
- conj2(equals('#f', x), teacupo(y))))
536
+ conj2(unify('#f', x), teacupo(y))))
582
537
 
583
538
  # Order of solutions differs from RS book
584
539
  expected = '((:tea _0) (:cup _0) (false :tea) (false :cup))'
@@ -590,7 +545,6 @@ module MiniKraken
590
545
  end
591
546
 
592
547
  it 'supports conde and passes frame 1:88 (i)' do
593
- # Reasoned S2, frame 1:88
594
548
  # (run* (x y)
595
549
  # (conde
596
550
  # ((teacupo x) (teacupo x))
@@ -599,7 +553,7 @@ module MiniKraken
599
553
  defrel_teacupo
600
554
  result = run_star(%w[x y], conde(
601
555
  [teacupo(x), teacupo(x)],
602
- [equals('#f', x), teacupo(y)]))
556
+ [unify('#f', x), teacupo(y)]))
603
557
 
604
558
  # Order of solutions differs from RS book
605
559
  expected = '((:tea _0) (:cup _0) (false :tea) (false :cup))'
@@ -613,8 +567,8 @@ module MiniKraken
613
567
  # ((== 'split x) (== 'pea y))
614
568
  # ((== 'red x) (== 'bean y)))) ;; => ((split pea)(red bean))
615
569
  result = run_star(%w[x y], conde(
616
- [equals(:split, x), equals(:pea, y)],
617
- [equals(:red, x), equals(:bean, y)]))
570
+ [unify(:split, x), unify(:pea, y)],
571
+ [unify(:red, x), unify(:bean, y)]))
618
572
 
619
573
  expected = '((:split :pea) (:red :bean))'
620
574
  expect(result.to_s).to eq(expected)
@@ -628,14 +582,13 @@ module MiniKraken
628
582
  # ('oil x))) ;; => (oil)
629
583
 
630
584
  result = run_star('x', conde(
631
- [equals(:olive, x), _fail],
632
- equals(:oil, x)))
585
+ [unify(:olive, x), _fail],
586
+ unify(:oil, x)))
633
587
 
634
588
  expect(result.to_s).to eq('(:oil)')
635
589
  end
636
590
 
637
591
  it 'passes frame 1:90' do
638
- # Reasoned S2, frame 1:90
639
592
  # (run* (x y)
640
593
  # (conde
641
594
  # ((fresh (z)
@@ -643,14 +596,13 @@ module MiniKraken
643
596
  # ((== x y)))) ;; => ((_0 _1)(_0 _0))
644
597
 
645
598
  result = run_star(%w[x y], conde(
646
- [fresh(%w[z], equals(:lentil, z))],
647
- [equals(x, y)]))
599
+ [fresh(%w[z], unify(:lentil, z))],
600
+ [unify(x, y)]))
648
601
 
649
602
  expect(result.to_s).to eq('((_0 _1) (_0 _0))')
650
603
  end
651
604
 
652
605
  it 'passes frame 1:91' do
653
- # Reasoned S2, frame 1:91
654
606
  # (run* (x y)
655
607
  # (conde
656
608
  # ((== 'split x) (== 'pea y))
@@ -658,9 +610,9 @@ module MiniKraken
658
610
  # ((== 'green x) (== 'lentil y))))
659
611
  # ;; => ((split pea)(red bean)(green lentil))
660
612
  result = run_star(%w[x y], conde(
661
- [equals(:split, x), equals(:pea, y)],
662
- [equals(:red, x), equals(:bean, y)],
663
- [equals(:green, x), equals(:lentil, y)]))
613
+ [unify(:split, x), unify(:pea, y)],
614
+ [unify(:red, x), unify(:bean, y)],
615
+ [unify(:green, x), unify(:lentil, y)]))
664
616
 
665
617
  expected = '((:split :pea) (:red :bean) (:green :lentil))'
666
618
  expect(result.to_s).to eq(expected)