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
@@ -17,6 +17,7 @@ require_relative '../../lib/mini_kraken/core/def_relation'
17
17
  module MiniKraken
18
18
  module Core
19
19
  describe DefRelation do
20
+ # (defrel (teao t) (== 'tea t))
20
21
  let(:tea) { KSymbol.new(:tea) }
21
22
  let(:formal_t) { FormalArg.new('t') }
22
23
  let(:t_ref) { FormalRef.new('t') }
@@ -53,13 +54,13 @@ module MiniKraken
53
54
  defrel = DefRelation.new('teao', equals_tea, [formal_t])
54
55
  solver = defrel.solver_for([tea], env)
55
56
  outcome = solver.resume
56
- expect(outcome).to be_successful
57
+ expect(outcome).to be_success
57
58
  outcome = solver.resume
58
59
  expect(outcome).to be_nil
59
60
 
60
61
  solver = defrel.solver_for([cup], env)
61
62
  outcome = solver.resume
62
- expect(outcome).not_to be_successful
63
+ expect(outcome).not_to be_success
63
64
  outcome = solver.resume
64
65
  expect(outcome).to be_nil
65
66
  end
@@ -69,7 +70,7 @@ module MiniKraken
69
70
  env.add_var(Variable.new('x'))
70
71
  solver = defrel.solver_for([ref_x], env)
71
72
  outcome = solver.resume
72
- expect(outcome).to be_successful
73
+ expect(outcome).to be_success
73
74
  expect(ref_x.value(outcome)).to eq(tea)
74
75
 
75
76
  outcome = solver.resume
@@ -80,11 +81,11 @@ module MiniKraken
80
81
  env.add_var(Variable.new('x'))
81
82
  solver = subject.solver_for([ref_x], env)
82
83
  outcome = solver.resume
83
- expect(outcome).to be_successful
84
+ expect(outcome).to be_success
84
85
  expect(ref_x.value(outcome)).to eq(tea)
85
86
 
86
87
  outcome = solver.resume
87
- expect(outcome).to be_successful
88
+ expect(outcome).to be_success
88
89
  expect(ref_x.value(outcome)).to eq(cup)
89
90
 
90
91
  outcome = solver.resume
@@ -52,7 +52,7 @@ module MiniKraken
52
52
  it 'should fails if both arguments fail' do
53
53
  # Covers frame 1:55
54
54
  solver = subject.solver_for([fails, fails], env)
55
- expect(solver.resume).not_to be_successful
55
+ expect(solver.resume).not_to be_success
56
56
  expect(solver.resume).to be_nil
57
57
  end
58
58
 
@@ -61,7 +61,7 @@ module MiniKraken
61
61
  subgoal = Goal.new(Equals.instance, [olive, ref_q])
62
62
  solver = subject.solver_for([subgoal, fails], env)
63
63
  outcome = solver.resume
64
- expect(outcome).to be_successful
64
+ expect(outcome).to be_success
65
65
  expect(outcome.associations['q'].first.value).to eq(olive)
66
66
  expect(solver.resume).to be_nil
67
67
  end
@@ -71,7 +71,7 @@ module MiniKraken
71
71
  subgoal = Goal.new(Equals.instance, [oil, ref_q])
72
72
  solver = subject.solver_for([fails, subgoal], env)
73
73
  outcome = solver.resume
74
- expect(outcome).to be_successful
74
+ expect(outcome).to be_success
75
75
  expect(outcome.associations['q'].first.value).to eq(oil)
76
76
  expect(solver.resume).to be_nil
77
77
  end
@@ -84,12 +84,12 @@ module MiniKraken
84
84
 
85
85
  # First solution
86
86
  outcome1 = solver.resume
87
- expect(outcome1).to be_successful
87
+ expect(outcome1).to be_success
88
88
  expect(outcome1.associations['q'].first.value).to eq(olive)
89
89
 
90
90
  # Second solution
91
91
  outcome2 = solver.resume
92
- expect(outcome2).to be_successful
92
+ expect(outcome2).to be_success
93
93
  expect(outcome2.associations['q'].first.value).to eq(oil)
94
94
  expect(solver.resume).to be_nil
95
95
  end
@@ -40,7 +40,7 @@ module MiniKraken
40
40
  succeeding = DuckFiber.new(:success)
41
41
  outcome = nil
42
42
  expect { outcome = succeeding.resume }.not_to raise_error
43
- expect(outcome).to be_successful
43
+ expect(outcome).to be_success
44
44
  expect(outcome.parent).to be_nil
45
45
 
46
46
  # Only one result should be yielded
@@ -52,7 +52,7 @@ module MiniKraken
52
52
  outcome1 = instance1.resume
53
53
 
54
54
  instance2 = DuckFiber.new(:success)
55
- outcome2 = instance1.resume
55
+ outcome2 = instance2.resume
56
56
 
57
57
  expect(outcome1).not_to be_equal(outcome2)
58
58
  end
@@ -106,7 +106,7 @@ module MiniKraken
106
106
  it 'should succeed for a right-handed fresh argument' do
107
107
  result = solve_for(pea, ref_q)
108
108
 
109
- expect(result).to be_successful
109
+ expect(result).to be_success
110
110
  expect(env.associations.size).to eq(1)
111
111
  expect(env.associations['q'].first.value).to eq(pea)
112
112
  expect(var_q.fresh?(result)).to be_falsey
@@ -116,7 +116,7 @@ module MiniKraken
116
116
  it 'should succeed for a left-handed fresh argument' do
117
117
  result = solve_for(ref_q, pea)
118
118
 
119
- expect(result).to be_successful
119
+ expect(result).to be_success
120
120
  expect(env.associations.size).to eq(1)
121
121
  expect(env.associations['q'].first.value).to eq(pea)
122
122
  expect(var_q.fresh?(result)).to be_falsey
@@ -127,7 +127,7 @@ module MiniKraken
127
127
  ref_q.associate(pod, env)
128
128
 
129
129
  result = solve_for(pod, ref_q)
130
- expect(result).to be_successful
130
+ expect(result).to be_success
131
131
  expect(env.associations.size).to eq(1) # No new association
132
132
  expect(ref_q.fresh?(result)).not_to be_truthy
133
133
  expect(ref_q.values(result).first).to eq(pod)
@@ -137,7 +137,7 @@ module MiniKraken
137
137
  ref_q.associate(pod, env)
138
138
 
139
139
  result = solve_for(ref_q, pod)
140
- expect(result).to be_successful
140
+ expect(result).to be_success
141
141
  expect(result.associations).to be_empty
142
142
  expect(ref_q.fresh?(result)).to be_falsey
143
143
  expect(ref_q.values(result).first).to eq(pod)
@@ -147,7 +147,7 @@ module MiniKraken
147
147
  ref_q.associate(pod, env)
148
148
 
149
149
  result = solve_for(pea, ref_q)
150
- expect(result).not_to be_successful
150
+ expect(result).not_to be_success
151
151
  expect(result.associations).to be_empty
152
152
  expect(ref_q.fresh?(result)).to be_falsey
153
153
  expect(ref_q.values(result).first).to eq(pod)
@@ -157,7 +157,7 @@ module MiniKraken
157
157
  ref_q.associate(pod, env)
158
158
 
159
159
  result = solve_for(ref_q, pea)
160
- expect(result).not_to be_successful
160
+ expect(result).not_to be_success
161
161
  expect(result.associations).to be_empty
162
162
  expect(ref_q.fresh?(result)).to be_falsey
163
163
  expect(ref_q.values(result).first).to eq(pod)
@@ -166,7 +166,7 @@ module MiniKraken
166
166
  it 'should succeed for a composite and right-handed fresh argument' do
167
167
  result = solve_for(sample_cons, ref_q)
168
168
 
169
- expect(result).to be_successful
169
+ expect(result).to be_success
170
170
  expect(env.associations.size).to eq(1)
171
171
  expect(ref_q.fresh?(result)).to be_falsey
172
172
  expect(ref_q.values(result).first).to eq(sample_cons)
@@ -175,7 +175,7 @@ module MiniKraken
175
175
  it 'should succeed for composite and left-handed fresh argument' do
176
176
  result = solve_for(ref_q, sample_cons)
177
177
 
178
- expect(result).to be_successful
178
+ expect(result).to be_success
179
179
  expect(env.associations.size).to eq(1)
180
180
  expect(ref_q.fresh?(result)).to be_falsey
181
181
  expect(ref_q.values(result).first).to eq(sample_cons)
@@ -186,7 +186,7 @@ module MiniKraken
186
186
  composite = ConsCell.new(pea)
187
187
  result = solve_for(composite, ref_q)
188
188
 
189
- expect(result).to be_successful
189
+ expect(result).to be_success
190
190
  expect(result.associations).to be_empty
191
191
  expect(ref_q.fresh?(result)).not_to be_truthy
192
192
  expect(ref_q.values(result).first).to eq(sample_cons)
@@ -197,7 +197,7 @@ module MiniKraken
197
197
  composite = ConsCell.new(pea)
198
198
  result = solve_for(ref_q, composite)
199
199
 
200
- expect(result).to be_successful
200
+ expect(result).to be_success
201
201
  expect(result.associations).to be_empty
202
202
  expect(ref_q.fresh?(result)).not_to be_truthy
203
203
  expect(ref_q.values(result).first).to eq(sample_cons)
@@ -208,7 +208,7 @@ module MiniKraken
208
208
  composite = ConsCell.new(pod)
209
209
  result = solve_for(composite, ref_q)
210
210
 
211
- expect(result).not_to be_successful
211
+ expect(result).to be_failure
212
212
  expect(result.associations).to be_empty
213
213
  expect(ref_q.fresh?(result)).not_to be_truthy
214
214
  expect(ref_q.values(result).first).to eq(sample_cons)
@@ -219,7 +219,7 @@ module MiniKraken
219
219
  composite = ConsCell.new(pod)
220
220
  result = solve_for(ref_q, composite)
221
221
 
222
- expect(result).not_to be_successful
222
+ expect(result).not_to be_success
223
223
  expect(result.associations).to be_empty
224
224
  expect(ref_q.fresh?(result)).not_to be_truthy
225
225
  expect(ref_q.values(result).first).to eq(sample_cons)
@@ -228,7 +228,7 @@ module MiniKraken
228
228
  it 'should succeed for both identical fresh arguments' do
229
229
  result = solve_for(ref_q, ref_q)
230
230
 
231
- expect(result).to be_successful
231
+ expect(result).to be_success
232
232
  expect(result.associations).to be_empty
233
233
  expect(ref_q.fresh?(result)).to be_truthy
234
234
  end
@@ -236,7 +236,7 @@ module MiniKraken
236
236
  it 'should succeed for both same fresh arguments' do
237
237
  result = solve_for(ref_q, ref_q_bis)
238
238
 
239
- expect(result).to be_successful
239
+ expect(result).to be_success
240
240
  expect(result.associations).to be_empty
241
241
  expect(ref_q.fresh?(result)).to be_truthy
242
242
  expect(ref_q_bis.fresh?(result)).to be_truthy
@@ -245,7 +245,7 @@ module MiniKraken
245
245
  it 'should succeed for both distinct fresh arguments' do
246
246
  result = solve_for(ref_x, ref_y)
247
247
 
248
- expect(result).to be_successful
248
+ expect(result).to be_success
249
249
  expect(env.associations).to be_empty # Symmetric association
250
250
  expect(ref_x.fresh?(result)).to be_truthy
251
251
  expect(ref_y.fresh?(result)).to be_truthy
@@ -258,7 +258,7 @@ module MiniKraken
258
258
  expect(ref_y.fresh?(env)).to be_falsey
259
259
 
260
260
  result = solve_for(ref_x, ref_y)
261
- expect(result).to be_successful
261
+ expect(result).to be_success
262
262
  expect(result.associations).to be_empty
263
263
  end
264
264
 
@@ -269,19 +269,20 @@ module MiniKraken
269
269
  expect(ref_y.fresh?(env)).to be_falsey
270
270
 
271
271
  result = solve_for(ref_x, ref_y)
272
- expect(result).not_to be_successful
272
+ expect(result).not_to be_success
273
273
  expect(result.associations).to be_empty
274
274
  end
275
275
 
276
276
  it 'should unify composite terms with variables' do
277
277
  # Reasoned S2, frame 1:36
278
278
  # (run* q (fresh (x) (== '(((,q)) ,x) `(((,x)) pod)))) ;; => ('pod)
279
- expr1 = cons(cons(cons(ref_q)), ref_x)
280
- expr2 = cons(cons(cons(ref_x)), pod)
279
+ expr1 = cons(cons(cons(ref_q)), cons(ref_x))
280
+ expect(expr1.to_s).to eq('(((q)) x)')
281
+ expr2 = cons(cons(cons(ref_x)), cons(pod))
282
+ expect(expr2.to_s).to eq('(((x)) :pod)')
281
283
 
282
284
  result = solve_for(expr1, expr2)
283
- # require 'debug'
284
- expect(result).to be_successful
285
+ expect(result).to be_success
285
286
  expect(ref_x.fresh?(env)).to be_falsey
286
287
  expect(ref_q.fresh?(env)).to be_falsey
287
288
  end
@@ -298,6 +299,18 @@ module MiniKraken
298
299
  expect(result.resultant).to eq(:"#u")
299
300
  expect(result.associations).to be_empty
300
301
  end
302
+
303
+ it 'should unify variables with a list' do
304
+ # Variant of Reasoned S2, frame 2:3
305
+ # (== (cons q x) '(a c o r n)) ;; q => :a, x => '(c o r n)
306
+ expr1 = cons(ref_q, ref_x)
307
+ acorn = cons(k_symbol(:a), cons(k_symbol(:c),
308
+ cons(k_symbol(:o), cons(k_symbol(:r), cons(k_symbol(:n))))))
309
+ result = solve_for(expr1, acorn)
310
+ expect(result.resultant).to eq(:"#s")
311
+ q_value = env.associations['q'].first.value
312
+ expect(q_value).to eq(:a)
313
+ end
301
314
  end # context
302
315
  end # describe
303
316
  end # module
@@ -40,7 +40,7 @@ module MiniKraken
40
40
  context 'Provided services:' do
41
41
  it 'should fail if relation does not succeed' do
42
42
  solver = subject.attain(env)
43
- expect(solver.resume).not_to be_successful
43
+ expect(solver.resume).not_to be_success
44
44
 
45
45
  # No more solution...
46
46
  expect(solver.resume).to be_nil
@@ -50,7 +50,7 @@ module MiniKraken
50
50
  instance = Goal.new(binary_relation, [KSymbol.new(:pea), KSymbol.new(:pea)])
51
51
 
52
52
  solver = instance.attain(env)
53
- expect(solver.resume).to be_successful
53
+ expect(solver.resume).to be_success
54
54
 
55
55
  # No more solution...
56
56
  expect(solver.resume).to be_nil
@@ -101,6 +101,12 @@ module MiniKraken
101
101
  # Default Ruby representation, different value
102
102
  expect(subject == false).to be_falsy
103
103
  end
104
+
105
+ it 'provides a text representation of itself' do
106
+ expect(subject.to_s).to eq('true')
107
+ other = KBoolean.new('#f')
108
+ expect(other.to_s).to eq('false')
109
+ end
104
110
  end # context
105
111
  end # describe
106
112
  end # module
@@ -65,6 +65,10 @@ module MiniKraken
65
65
  # Default Ruby representation, different value
66
66
  expect(subject == :pod).to be_falsy
67
67
  end
68
+
69
+ it 'should provide a string representation of itself' do
70
+ expect(subject.to_s).to eq(':pea')
71
+ end
68
72
  end # context
69
73
  end # describe
70
74
  end # module
@@ -25,6 +25,14 @@ module MiniKraken
25
25
  expect(subject.resultant).to eq(:"#s")
26
26
  end
27
27
 
28
+ it 'should know whether it represents a failure' do
29
+ expect(Outcome.new(:"#u")).to be_failure
30
+ end
31
+
32
+ it 'should know whether it represents a success' do
33
+ expect(subject).to be_success
34
+ end
35
+
28
36
  it 'should know its parent' do
29
37
  expect(subject.parent).to eq(voc)
30
38
  end
@@ -21,6 +21,9 @@ module MiniKraken
21
21
  end # context
22
22
 
23
23
  context 'Provided services:' do
24
+ it 'knows its text representation' do
25
+ expect(subject.to_s).to eq('q')
26
+ end
24
27
  end # context
25
28
  end # describe
26
29
  end # module
@@ -0,0 +1,679 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../spec_helper' # Use the RSpec framework
4
+
5
+ # Load the class under test
6
+ require_relative '../../lib/mini_kraken/glue/dsl'
7
+
8
+
9
+ module MiniKraken
10
+ module Glue
11
+ describe 'DSL (Chap 1)' do
12
+ include DSL
13
+
14
+ context 'Chapter 1 examples:' do
15
+ it 'passes frame 1:7' do
16
+ # Reasoned S2, frame 1:7
17
+ # (run* q #u) ;; => ()
18
+
19
+ result = run_star('q', _fail)
20
+ expect(result).to be_null
21
+ end
22
+
23
+ it 'passes frame 1:10' do
24
+ # Reasoned S2, frame 1:10
25
+ # (run* q (== 'pea 'pod) ;; => ()
26
+
27
+ result = run_star('q', equals(:pea, :pod))
28
+ expect(result).to be_null
29
+ end
30
+
31
+ it 'passes frame 1:11' do
32
+ # Reasoned S2, frame 1:11
33
+ # (run* q (== q 'pea) ;; => (pea)
34
+
35
+ result = run_star('q', equals(q, :pea))
36
+ expect(result.car).to eq(:pea)
37
+ end
38
+
39
+ it 'passes frame 1:12' do
40
+ # Reasoned S2, frame 1:12
41
+ # (run* q (== 'pea q) ;; => (pea)
42
+
43
+ result = run_star('q', equals(:pea, q))
44
+ expect(result.car).to eq(:pea)
45
+ end
46
+
47
+ it 'passes frame 1:17' do
48
+ # Reasoned S2, frame 1:17
49
+ # (run* q succeed) ;; => (_0)
50
+
51
+ result = run_star('q', succeed)
52
+ expect(result.car).to eq(:_0)
53
+ end
54
+
55
+ it 'passes frame 1:19' do
56
+ # Reasoned S2, frame 1:19
57
+ # (run* q (== 'pea 'pea)) ;; => (_0)
58
+
59
+ result = run_star('q', equals(:pea, :pea))
60
+ expect(result.car).to eq(:_0)
61
+ end
62
+
63
+ it 'passes frame 1:20' do
64
+ # Reasoned S2, frame 1:20
65
+ # (run* q (== q q)) ;; => (_0)
66
+
67
+ result = run_star('q', equals(q, q))
68
+ expect(result.car).to eq(:_0)
69
+ end
70
+
71
+ it "supports 'fresh' and passes frame 1:21" do
72
+ # Reasoned S2, frame 1:21
73
+ # (run* q (fresh (x) (== 'pea q))) ;; => (pea)
74
+
75
+ result = run_star('q', fresh('x', equals(:pea, q)))
76
+ expect(result.car).to eq(:pea)
77
+ end
78
+
79
+ 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))
85
+ end
86
+
87
+ it 'passes frame 1:31' do
88
+ # Reasoned S2, frame 1:31
89
+ # (run* q (fresh (x) (== x q))) ;; => (_0)
90
+
91
+ result = run_star('q', fresh('x', equals(x, q)))
92
+ expect(result.car).to eq(:_0)
93
+ end
94
+
95
+ it 'passes frame 1:32' do
96
+ # Reasoned S2, frame 1:32
97
+ # (run* q (== '(((pea)) pod) '(((pea)) pod))) ;; => (_0)
98
+
99
+ result = run_star('q', equals(cons(cons(:pea), :pod), cons(cons(:pea), :pod)))
100
+ expect(result.car).to eq(:_0)
101
+ end
102
+
103
+ it 'passes frame 1:33' do
104
+ # Beware: quasiquoting
105
+ # Reasoned S2, frame 1:33
106
+ # (run* q (== '(((pea)) pod) '(((pea)) ,q))) ;; => ('pod)
107
+
108
+ result = run_star('q', equals(cons(cons(:pea), :pod), cons(cons(:pea), q)))
109
+ expect(result.car).to eq(:pod)
110
+ end
111
+
112
+ it 'passes frame 1:34' do
113
+ # Reasoned S2, frame 1:34
114
+ # (run* q (== '(((,q)) pod) `(((pea)) pod))) ;; => ('pea)
115
+
116
+ expr1 = cons(cons(cons(q)), cons(:pod))
117
+ expect(expr1.to_s).to eq('(((q)) :pod)')
118
+ expr2 = cons(cons(cons(:pea)), cons(:pod))
119
+ expect(expr2.to_s).to eq('(((:pea)) :pod)')
120
+ result = run_star('q', equals(expr1, expr2))
121
+ expect(result.car).to eq(:pea)
122
+ end
123
+
124
+ it 'passes frame 1:35' do
125
+ # Reasoned S2, frame 1:35
126
+ # (run* q (fresh (x) (== '(((,q)) pod) `(((,x)) pod)))) ;; => (_0)
127
+
128
+ result = run_star('q', fresh('x', equals(cons(cons(q), :pod), cons(cons(x), :pod))))
129
+ expect(result.car).to eq(:_0)
130
+ end
131
+
132
+ it 'passes frame 1:36' do
133
+ # Reasoned S2, frame 1:36
134
+ # (run* q (fresh (x) (== '(((,q)) ,x) `(((,x)) pod)))) ;; => ('pod)
135
+
136
+ expr1 = cons(cons(cons(q)), cons(x))
137
+ expect(expr1.to_s).to eq('(((q)) x)')
138
+ expr2 = cons(cons(cons(x)), cons(:pod))
139
+ expect(expr2.to_s).to eq('(((x)) :pod)')
140
+ result = run_star('q', fresh('x', equals(expr1, expr2)))
141
+ expect(result.car).to eq(:pod)
142
+ end
143
+
144
+ it 'passes frame 1:37' do
145
+ # Reasoned S2, frame 1:37
146
+ # (run* q (fresh (x) (== '( ,x ,x) q))) ;; => (_0 _0)
147
+
148
+ result = run_star('q', fresh('x', equals(cons(x, cons(x)), q)))
149
+ expect(result.car).to eq(cons(:_0, cons(:_0)))
150
+ end
151
+
152
+ it 'passes frame 1:38' do
153
+ # Reasoned S2, frame 1:38
154
+ # (run* q (fresh (x) (fresh (y) (== '( ,q ,y) '((,x ,y) ,x))))) ;; => (_0 _0)
155
+
156
+ result = run_star('q', fresh('x', fresh('y', equals(cons(q, cons(y)), cons(cons(x, cons(y)), cons(x))))))
157
+ expect(result.car).to eq(cons(:_0, cons(:_0)))
158
+ end
159
+
160
+ it 'passes frame 1:41' do
161
+ # Reasoned S2, frame 1:41
162
+ # (run* q (fresh (x) (fresh (y) (== '( ,x ,y) q)))) ;; => (_0 _1)
163
+
164
+ result = run_star('q', fresh('x', fresh('y', equals(cons(x, cons(y)), q))))
165
+ # q should be bound to '(,x ,y)
166
+ expect(result.car).to eq(cons(:_0, cons(:_1)))
167
+ end
168
+
169
+ it 'passes frame 1:42' do
170
+ # Reasoned S2, frame 1:42
171
+ # (run* s (fresh (t) (fresh (u) (== '( ,t ,u) s)))) ;; => (_0 _1)
172
+
173
+ result = run_star('s', fresh('t', fresh('u', equals(cons(t, cons(u)), s))))
174
+ # s should be bound to '(,t ,u)
175
+ expect(result.car).to eq(cons(:_0, cons(:_1)))
176
+ end
177
+
178
+ it 'passes frame 1:43' do
179
+ # Reasoned S2, frame 1:43
180
+ # (run* q (fresh (x) (fresh (y) (== '( ,x ,y ,x) q)))) ;; => (_0 _1 _0)
181
+
182
+ result = run_star('q', fresh('x', fresh('y', equals(cons(x, cons(y, cons(x))), q))))
183
+ # q should be bound to '(,x ,y, ,x)
184
+ expect(result.car).to eq(cons(:_0, cons(:_1, cons(:_0))))
185
+ end
186
+
187
+ it "supports 'conj2' relation and passes frame 1:50" do
188
+ # Reasoned S2, frame 1:50
189
+ # (run* q (conj2 succeed succeed)) ;; => (_0)
190
+
191
+ result = run_star('q', conj2(succeed, succeed))
192
+ expect(result.car).to eq(:_0)
193
+ end
194
+
195
+ it 'passes frame 1:51' do
196
+ # Reasoned S2, frame 1:51
197
+ # (run* q (conj2 succeed (== 'corn q)) ;; => ('corn)
198
+
199
+ result = run_star('q', conj2(succeed, equals(:corn, q)))
200
+ expect(result.car).to eq(:corn)
201
+ end
202
+
203
+ it 'passes frame 1:52' do
204
+ # Reasoned S2, frame 1:52
205
+ # (run* q (conj2 fail (== 'corn q)) ;; => ()
206
+
207
+ result = run_star('q', conj2(_fail, equals(:corn, q)))
208
+ expect(result).to be_null
209
+ end
210
+
211
+ it 'passes frame 1:53' do
212
+ # Reasoned S2, frame 1:53
213
+ # (run* q (conj2 (== 'corn q)(== 'meal q)) ;; => ()
214
+
215
+ result = run_star('q', conj2(equals(:corn, q), equals(:meal, q)))
216
+ expect(result).to be_null
217
+ end
218
+
219
+ it 'passes frame 1:54' do
220
+ # Reasoned S2, frame 1:54
221
+ # (run* q (conj2 (== 'corn q)(== 'corn q)) ;; => ('corn)
222
+
223
+ result = run_star('q', conj2(equals(:corn, q), equals(:corn, q)))
224
+ expect(result.car).to eq(:corn)
225
+ end
226
+
227
+ it "supports 'disj2' and passes frame 1:55" do
228
+ # Reasoned S2, frame 1:55
229
+ # (run* q (disj2 fail fail)) ;; => ()
230
+
231
+ result = run_star('q', disj2(_fail, _fail))
232
+ expect(result).to be_null
233
+ end
234
+
235
+ it 'passes frame 1:56' do
236
+ # Reasoned S2, frame 1:56
237
+ # (run* q (disj2 (== 'olive q) fail)) ;; => ('olive)
238
+
239
+ result = run_star('q', disj2(equals(:olive, q), _fail))
240
+ expect(result.car).to eq(:olive)
241
+ end
242
+
243
+ it 'passes frame 1:57' do
244
+ # Reasoned S2, frame 1:57
245
+ # (run* q (disj2 fail (== 'oil q))) ;; => (oil)
246
+
247
+ result = run_star('q', disj2(_fail, equals(:oil, q)))
248
+ expect(result.car).to eq(:oil)
249
+ end
250
+
251
+ it 'passes frame 1:58' do
252
+ # Reasoned S2, frame 1:58
253
+ # (run* q (disj2 (== 'olive q) (== 'oil q))) ;; => (olive oil)
254
+
255
+ result = run_star('q', disj2(equals(:olive, q), equals(:oil, q)))
256
+ expect(result.car).to eq(:olive)
257
+ expect(result.cdr.car).to eq(:oil)
258
+ end
259
+
260
+ it 'passes frame 1:59' do
261
+ # Reasoned S2, frame 1:59
262
+ # (run* q (fresh (x) (fresh (y) (disj2 (== '( ,x ,y ) q) (== '( ,x ,y ) q)))))
263
+ # ;; => ((_0 _1) (_0 _1))
264
+
265
+ result = run_star('q', fresh('x', (fresh 'y', disj2(equals(cons(x, cons(y)), q), equals(cons(x, cons(y)), q)))))
266
+ # q should be bound to '(,x ,y), then to '(,y ,x)
267
+ expect(result.car).to eq(cons(:_0, cons(:_1)))
268
+ expect(result.cdr.car).to eq(cons(:_0, cons(:_1)))
269
+ end
270
+
271
+ it 'passes frame 1:62' do
272
+ # Reasoned S2, frame 1:62
273
+ # (run* x (disj2
274
+ # (conj2 (== 'olive x) fail)
275
+ # (== 'oil x))) ;; => (oil)
276
+
277
+ result = run_star('x', disj2(conj2(equals(:olive, x), _fail), equals(:oil, x)))
278
+ expect(result.car).to eq(:oil)
279
+ end
280
+
281
+ it 'passes frame 1:63' do
282
+ # Reasoned S2, frame 1:63
283
+ # (run* x (disj2
284
+ # (conj2 (== 'olive x) succeed)
285
+ # ('oil x))) ;; => (olive oil)
286
+
287
+ result = run_star('x', disj2(conj2(equals(:olive, x), succeed), equals(:oil, x)))
288
+ expect(result).to eq(cons(:olive, cons(:oil)))
289
+ end
290
+
291
+ it 'passes frame 1:64' do
292
+ # Reasoned S2, frame 1:64
293
+ # (run* x (disj2
294
+ # (== 'oil x)
295
+ # (conj2 (== 'olive x) succeed))) ;; => (oil olive)
296
+
297
+ result = run_star('x', disj2(equals(:oil, x), conj2(equals(:olive, x), succeed)))
298
+ expect(result).to eq(cons(:oil, cons(:olive)))
299
+ end
300
+
301
+ it 'passes frame 1:65' do
302
+ # Reasoned S2, frame 1:65
303
+ # (run* x (disj2
304
+ # (conj2(== 'virgin x) fail)
305
+ # (disj2
306
+ # (== 'olive x)
307
+ # (dis2
308
+ # succeed
309
+ # (== 'oil x))))) ;; => (olive _0 oil)
310
+
311
+ result = run_star('x', disj2(conj2(equals(:virgin, x), _fail),
312
+ disj2(equals(:olive, x), disj2(succeed, equals(:oil, x)))))
313
+ expect(result).to eq(cons(:olive, cons(:_0, cons(:oil))))
314
+ end
315
+
316
+ it 'passes frame 1:67' do
317
+ # Reasoned S2, frame 1:67
318
+ # (run* r
319
+ # (fresh x
320
+ # (fresh y
321
+ # (conj2
322
+ # (== 'split x)
323
+ # (conj2
324
+ # (== 'pea y)
325
+ # (== '(,x ,y) r)))))) ;; => ((split pea))
326
+
327
+ result = run_star('r', fresh('x', fresh('y',
328
+ conj2(equals(:split, x),
329
+ conj2(equals(:pea, y), equals(cons(x, cons(y)), r))))))
330
+ expect(result).to eq(cons(cons(:split, cons(:pea))))
331
+ end
332
+
333
+ it 'passes frame 1:68' do
334
+ # Reasoned S2, frame 1:68
335
+ # (run* r
336
+ # (fresh x
337
+ # (fresh y
338
+ # (conj2
339
+ # (conj2
340
+ # (== 'split x)
341
+ # (== 'pea y)
342
+ # (== '(,x ,y) r)))))) ;; => ((split pea))
343
+
344
+ result = run_star('r', fresh('x', fresh('y',
345
+ conj2(conj2(equals(:split, x), equals(:pea, y)),
346
+ equals(cons(x, cons(y)), r)))))
347
+ expect(result).to eq(cons(cons(:split, cons(:pea))))
348
+ end
349
+
350
+ it 'passes frame 1:70' do
351
+ # Reasoned S2, frame 1:70
352
+ # (run* r
353
+ # (fresh (x y)
354
+ # (conj2
355
+ # (conj2
356
+ # (== 'split x)
357
+ # (== 'pea y)
358
+ # (== '(,x ,y) r))))) ;; => ((split pea))
359
+
360
+ result = run_star('r', fresh(%w[x y], conj2(
361
+ conj2(equals(:split, x), equals(:pea, y)),
362
+ equals(cons(x, cons(y)), r))))
363
+ expect(result).to eq(cons(cons(:split, cons(:pea))))
364
+ end
365
+
366
+ it 'passes frame 1:72' do
367
+ # Reasoned S2, frame 1:72
368
+ # (run* (r x y)
369
+ # (conj2
370
+ # (conj2
371
+ # (== 'split x)
372
+ # (== 'pea y))
373
+ # (== '(,x ,y) r))) ;; => (((split pea) split pea))
374
+ # o
375
+ # / \
376
+ # o nil
377
+ # / \
378
+ # / \
379
+ # / \
380
+ # / \
381
+ # / \
382
+ # o o
383
+ # / \ / \
384
+ # split o split o
385
+ # / \ / \
386
+ # pea nil pea nil
387
+
388
+ result = run_star(%w[r x y], conj2(
389
+ conj2(equals(:split, x), equals(:pea, y)),
390
+ equals(cons(x, cons(y)), r)))
391
+ expect(result.car.car.car).to eq(:split)
392
+ expect(result.car.car.cdr.car).to eq(:pea)
393
+ expect(result.car.car.cdr.cdr).to be_nil
394
+ expect(result.car.cdr.car).to eq(:split)
395
+ expect(result.car.cdr.cdr.car).to eq(:pea)
396
+ expect(result.car.cdr.cdr.cdr).to be_nil
397
+ end
398
+
399
+ it 'passes frame 1:75' do
400
+ # Reasoned S2, frame 1:75
401
+ # (run* (x y)
402
+ # (conj2
403
+ # (== 'split x)
404
+ # (== 'pea y))) ;; => ((split pea))
405
+
406
+ result = run_star(%w[x y], conj2(equals(:split, x), equals(:pea, y)))
407
+ expect(result.car).to eq(list(:split, :pea))
408
+ end
409
+
410
+ it 'passes frame 1:76' do
411
+ # Reasoned S2, frame 1:76
412
+ # (run* (x y)
413
+ # (disj2
414
+ # (conj2 (== 'split x) (== 'pea y))
415
+ # (conj2 (== 'red x) (== 'bean y)))) ;; => ((split pea)(red bean))
416
+
417
+ result = run_star(%w[x y], disj2(
418
+ conj2(equals(:split, x), equals(:pea, y)),
419
+ conj2(equals(:red, x), equals(:bean, y))))
420
+ expect(result.car).to eq(list(:split, :pea))
421
+ expect(result.cdr.car).to eq(list(:red, :bean))
422
+ end
423
+
424
+ it 'passes frame 1:77' do
425
+ # Reasoned S2, frame 1:77
426
+ # (run* r
427
+ # (fresh (x y)
428
+ # (conj2
429
+ # (disj2
430
+ # (conj2 (== 'split x) (== 'pea y))
431
+ # (conj2 (== 'red x) (== 'bean y)))
432
+ # (== '(,x ,y soup) r)))) ;; => ((split pea soup) (red bean soup))
433
+
434
+ result = run_star('r',
435
+ fresh(%w[x y], conj2(
436
+ disj2(
437
+ conj2(equals(:split, x), equals(:pea, y)),
438
+ conj2(equals(:red, x), equals(:bean, y))),
439
+ equals(cons(x, cons(y, cons(:soup))), r))))
440
+ expect(result.car).to eq(list(:split, :pea, :soup))
441
+ expect(result.cdr.car).to eq(list(:red, :bean, :soup))
442
+ end
443
+
444
+ it 'passes frame 1:78' do
445
+ # Reasoned S2, frame 1:78
446
+ # (run* r
447
+ # (fresh (x y)
448
+ # (disj2
449
+ # (conj2 (== 'split x) (== 'pea y))
450
+ # (conj2 (== 'red x) (== 'bean y)))
451
+ # (== '(,x ,y soup) r))) ;; => ((split pea soup) (red bean soup))
452
+
453
+ result = run_star('r',
454
+ fresh(%w[x y], [disj2(
455
+ conj2(equals(:split, x), equals(:pea, y)),
456
+ conj2(equals(:red, x), equals(:bean, y))),
457
+ equals(cons(x, cons(y, cons(:soup))), r)]))
458
+ expect(result.car).to eq(list(:split, :pea, :soup))
459
+ expect(result.cdr.car).to eq(list(:red, :bean, :soup))
460
+ end
461
+
462
+ it 'passes frame 1:80' do
463
+ # Reasoned S2, frame 1:80
464
+ # (run* (x y z)
465
+ # (disj2
466
+ # (conj2 (== 'split x) (== 'pea y))
467
+ # (conj2 (== 'red x) (== 'bean y)))
468
+ # (== 'soup z)) ;; => ((split pea soup) (red bean soup))
469
+
470
+ result = run_star(%w[x y z], [disj2(
471
+ conj2(equals(:split, x), equals(:pea, y)),
472
+ conj2(equals(:red, x), equals(:bean, y))),
473
+ equals(:soup, z)])
474
+ expect(result.car).to eq(list(:split, :pea, :soup))
475
+ expect(result.cdr.car).to eq(list(:red, :bean, :soup))
476
+ end
477
+
478
+ it 'passes frame 1:81' do
479
+ # Reasoned S2, frame 1:81
480
+ # (run* (x y)
481
+ # (== 'split x)
482
+ # (== 'pea y)) ;; => ((split pea))
483
+
484
+ result = run_star(%w[x y], [equals(:split, x), equals(:pea, y)])
485
+ expect(result.car).to eq(list(:split, :pea))
486
+ end
487
+
488
+ it "supports 'defrel' and passes frame 1:82" do
489
+ # Reasoned S2, frame 1:82
490
+ # (defrel (teacupo t)
491
+ # (disj2 (== 'tea t) (== 'cup t)))
492
+
493
+ result = defrel('teacupo', 't') do
494
+ disj2(equals(:tea, t), equals(:cup, t))
495
+ end
496
+
497
+ expect(result).to be_kind_of(Core::DefRelation)
498
+ expect(result.name).to eq('teacupo')
499
+ expect(result.formals.size).to eq(1)
500
+ expect(result.formals[0].name).to eq('t')
501
+ g_template = result.goal_template
502
+ expect(g_template).to be_kind_of(Core::GoalTemplate)
503
+ expect(g_template.relation).to eq(Core::Disj2.instance)
504
+
505
+ first_arg = g_template.args[0]
506
+ expect(first_arg).to be_kind_of(Core::GoalTemplate)
507
+ expect(first_arg.relation).to eq(Core::Equals.instance)
508
+ expect(first_arg.args[0]).to eq(:tea)
509
+ expect(first_arg.args[1]).to be_kind_of(Core::FormalRef)
510
+ expect(first_arg.args[1].name).to eq('t')
511
+ second_arg = g_template.args[1]
512
+ expect(second_arg).to be_kind_of(Core::GoalTemplate)
513
+ expect(second_arg.relation).to eq(Core::Equals.instance)
514
+ expect(second_arg.args[0]).to eq(:cup)
515
+ expect(second_arg.args[1]).to be_kind_of(Core::FormalRef)
516
+ expect(second_arg.args[1].name).to eq('t')
517
+ end
518
+
519
+ def defrel_teacupo
520
+ defrel('teacupo', 't') { disj2(equals(:tea, t), equals(:cup, t)) }
521
+ end
522
+
523
+ it "supports the invokation of a 'defrel' and passes frame 1:83" do
524
+ # Reasoned S2, frame 1:83
525
+ # (run* x
526
+ # (teacupo x)) ;; => ((tea cup))
527
+
528
+ defrel_teacupo
529
+ result = run_star('x', teacupo(x))
530
+
531
+ expect(result).to eq(cons(:tea, cons(:cup)))
532
+ end
533
+
534
+ it 'supports booleans and passes frame 1:84' do
535
+ # Reasoned S2, frame 1:84
536
+ # (run* (x y)
537
+ # (disj2
538
+ # (conj2 (teacupo x) (== #t y))
539
+ # (conj2 (== #f x) (== #t y))) ;; => ((#f #t)(tea #t) (cup #t))
540
+
541
+ defrel_teacupo
542
+ result = run_star(%w[x y],
543
+ disj2(
544
+ conj2(teacupo(x), equals('#t', y)),
545
+ conj2(equals('#f', x), equals('#t', y))))
546
+
547
+ # Order of solutions differs from RS book
548
+ expect(result.car).to eq(cons(:tea, cons(true)))
549
+ expect(result.cdr.car).to eq(cons(:cup, cons(true)))
550
+ expect(result.cdr.cdr.car).to eq(cons(false, cons(true)))
551
+ end
552
+
553
+ it 'passes frame 1:85' do
554
+ # Reasoned S2, frame 1:85
555
+ # (run* (x y)
556
+ # (teacupo x)
557
+ # (teacupo y)) ;; => ((tea tea)(tea cup)(cup tea)(cup c))
558
+
559
+ defrel_teacupo
560
+ result = run_star(%w[x y], [teacupo(x), teacupo(y)])
561
+
562
+ expect(result.car).to eq(cons(:tea, cons(:tea)))
563
+ expect(result.cdr.car).to eq(cons(:tea, cons(:cup)))
564
+ expect(result.cdr.cdr.car).to eq(cons(:cup, cons(:tea)))
565
+ expect(result.cdr.cdr.cdr.car).to eq(cons(:cup, cons(:cup)))
566
+ end
567
+
568
+ it 'passes frame 1:86' do
569
+ # Reasoned S2, frame 1:86
570
+ # (run* (x y)
571
+ # (teacupo x)
572
+ # (teacupo x)) ;; => ((tea _0)(cup _0))
573
+
574
+ defrel_teacupo
575
+ result = run_star(%w[x y], [teacupo(x), teacupo(x)])
576
+ expect(result.to_s).to eq('((:tea _0) (:cup _0))')
577
+ end
578
+
579
+ it 'passes frame 1:87' do
580
+ # Reasoned S2, frame 1:87
581
+ # (run* (x y)
582
+ # (disj2
583
+ # (conj2 (teacupo x) (teacupo x))
584
+ # (conj2 (== #f x) (teacupo y)))) ;; => ((#f tea)(#f cup)(tea _0)(cup _0))
585
+
586
+ defrel_teacupo
587
+ result = run_star(%w[x y], disj2(
588
+ conj2(teacupo(x), teacupo(x)),
589
+ conj2(equals('#f', x), teacupo(y))))
590
+
591
+ # Order of solutions differs from RS book
592
+ expected = '((:tea _0) (:cup _0) (false :tea) (false :cup))'
593
+ expect(result.to_s).to eq(expected)
594
+ expect(result.car).to eq(cons(:tea, cons(:_0)))
595
+ expect(result.cdr.car).to eq(cons(:cup, cons(:_0)))
596
+ expect(result.cdr.cdr.car).to eq(cons(false, cons(:tea)))
597
+ expect(result.cdr.cdr.cdr.car).to eq(cons(false, cons(:cup)))
598
+ end
599
+
600
+ it 'supports conde and passes frame 1:88 (i)' do
601
+ # Reasoned S2, frame 1:88
602
+ # (run* (x y)
603
+ # (conde
604
+ # ((teacupo x) (teacupo x))
605
+ # ((== #f x) (teacupo y)))) ;; => ((#f tea)(#f cup)(tea _0)(cup _0))
606
+
607
+ defrel_teacupo
608
+ result = run_star(%w[x y], conde(
609
+ [teacupo(x), teacupo(x)],
610
+ [equals('#f', x), teacupo(y)]))
611
+
612
+ # Order of solutions differs from RS book
613
+ expected = '((:tea _0) (:cup _0) (false :tea) (false :cup))'
614
+ expect(result.to_s).to eq(expected)
615
+ end
616
+
617
+ it 'supports conde and passes frame 1:88 (ii)' do
618
+ # Reasoned S2, frame 1:88 (second part, a rewrite of 1:76)
619
+ # (run* (x y)
620
+ # (conde
621
+ # ((== 'split x) (== 'pea y))
622
+ # ((== 'red x) (== 'bean y)))) ;; => ((split pea)(red bean))
623
+ result = run_star(%w[x y], conde(
624
+ [equals(:split, x), equals(:pea, y)],
625
+ [equals(:red, x), equals(:bean, y)]))
626
+
627
+ expected = '((:split :pea) (:red :bean))'
628
+ expect(result.to_s).to eq(expected)
629
+ end
630
+
631
+ it 'passes frame 1:89' do
632
+ # Reasoned S2, frame 1:89 (rewrite of 1:62)
633
+ # (run* x
634
+ # (conde
635
+ # ((== 'olive x) fail)
636
+ # ('oil x))) ;; => (oil)
637
+
638
+ result = run_star('x', conde(
639
+ [equals(:olive, x), _fail],
640
+ equals(:oil, x)))
641
+
642
+ expect(result.to_s).to eq('(:oil)')
643
+ end
644
+
645
+ it 'passes frame 1:90' do
646
+ # Reasoned S2, frame 1:90
647
+ # (run* (x y)
648
+ # (conde
649
+ # ((fresh (z)
650
+ # (== 'lentil z)))
651
+ # ((== x y)))) ;; => ((_0 _1)(_0 _0))
652
+
653
+ result = run_star(%w[x y], conde(
654
+ [fresh(%w[z], equals(:lentil, z))],
655
+ [equals(x, y)]))
656
+
657
+ expect(result.to_s).to eq('((_0 _1) (_0 _0))')
658
+ end
659
+
660
+ it 'passes frame 1:91' do
661
+ # Reasoned S2, frame 1:91
662
+ # (run* (x y)
663
+ # (conde
664
+ # ((== 'split x) (== 'pea y))
665
+ # ((== 'red x) (== 'bean y))
666
+ # ((== 'green x) (== 'lentil y))))
667
+ # ;; => ((split pea)(red bean)(green lentil))
668
+ result = run_star(%w[x y], conde(
669
+ [equals(:split, x), equals(:pea, y)],
670
+ [equals(:red, x), equals(:bean, y)],
671
+ [equals(:green, x), equals(:lentil, y)]))
672
+
673
+ expected = '((:split :pea) (:red :bean) (:green :lentil))'
674
+ expect(result.to_s).to eq(expected)
675
+ end
676
+ end # context
677
+ end # describe
678
+ end # module
679
+ end # module