sequitur 0.1.13 → 0.1.14

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,111 +1,102 @@
1
- require_relative '../spec_helper'
2
-
3
- # Load the class under test
4
- require_relative '../../lib/sequitur/production'
5
- require_relative '../../lib/sequitur/production_ref'
6
-
7
- module Sequitur # Re-open the module to get rid of qualified names
8
-
9
- describe ProductionRef do
10
-
11
- let(:target) { Production.new }
12
- let(:another_target) { Production.new }
13
-
14
- subject { ProductionRef.new(target) }
15
-
16
- context 'Creation & initialization:' do
17
-
18
- it 'should be created with a production argument' do
19
- expect { ProductionRef.new(target) }.not_to raise_error
20
- expect(target.refcount).to eq(1)
21
- end
22
-
23
- it 'should clone with reference count incrementing' do
24
- expect(target.refcount).to eq(0)
25
- expect(subject.production.refcount).to eq(1)
26
- klone = subject.clone
27
- expect(klone.production.refcount).to eq(2)
28
- duplicate = subject.dup
29
- expect(duplicate.production.refcount).to eq(3)
30
- end
31
-
32
- it 'should know its referenced production' do
33
- instance = ProductionRef.new(target)
34
- expect(instance.production).to eq(target)
35
- end
36
-
37
-
38
- end # context
39
-
40
- context 'Provided services:' do
41
-
42
- it 'should render its referenced production' do
43
- expect(subject.to_s).to eq(target.object_id.to_s)
44
- end
45
-
46
- it 'should unbind itself from its production' do
47
- expect(target.refcount).to eq(0)
48
- expect(subject).not_to be_unbound
49
- expect(target.refcount).to eq(1)
50
- subject.unbind
51
- expect(target.refcount).to eq(0)
52
- expect(subject.production).to be_nil
53
- expect(subject).to be_unbound
54
- end
55
-
56
- it 'should bind to a production' do
57
- expect(target.refcount).to eq(0)
58
-
59
- expect(subject).not_to be_unbound
60
- expect(target.refcount).to eq(1)
61
-
62
- # Case: bind again to same production
63
- expect { subject.bind_to(target) }.not_to raise_error
64
- expect(target.refcount).to eq(1)
65
-
66
- # Case: bind to another production
67
- expect(another_target.refcount).to eq(0)
68
- subject.bind_to(another_target)
69
- expect(target.refcount).to eq(0)
70
- expect(another_target.refcount).to eq(1)
71
- end
72
-
73
- it 'should complain when binding to something else than production' do
74
- subject.bind_to(target)
75
- msg = 'Illegal production type String'
76
- expect { subject.bind_to('WRONG') }.to raise_error(StandardError, msg)
77
- end
78
-
79
- it 'should compare to other production (reference)' do
80
- same = ProductionRef.new(target)
81
- expect(subject).to eq(subject) # Strict identity
82
- expect(subject).to eq(same) # 2 references pointing to same production
83
- expect(subject).to eq(target)
84
- end
85
-
86
- it 'should return the hash value of its production' do
87
- expectation = target.hash
88
- expect(subject.hash).to eq(expectation)
89
- end
90
-
91
- it 'should complain when requested for a hash and unbound' do
92
- subject.unbind
93
- expect { subject.hash }.to raise_error(StandardError)
94
- end
95
-
96
- it 'should accept a visitor' do
97
- # Use a mock visitor
98
- fake = double('fake_visitor')
99
-
100
- # Visitor should receive a visit message
101
- expect(fake).to receive(:visit_prod_ref).once
102
- expect { subject.accept(fake) }.not_to raise_error
103
- end
104
-
105
- end # context
106
-
107
- end # describe
108
-
109
- end # module
110
-
111
- # End of file
1
+ require_relative '../spec_helper'
2
+
3
+ # Load the class under test
4
+ require_relative '../../lib/sequitur/production'
5
+ require_relative '../../lib/sequitur/production_ref'
6
+
7
+ module Sequitur # Re-open the module to get rid of qualified names
8
+ describe ProductionRef do
9
+ let(:target) { Production.new }
10
+ let(:another_target) { Production.new }
11
+
12
+ subject { ProductionRef.new(target) }
13
+
14
+ context 'Creation & initialization:' do
15
+ it 'should be created with a production argument' do
16
+ expect { ProductionRef.new(target) }.not_to raise_error
17
+ expect(target.refcount).to eq(1)
18
+ end
19
+
20
+ it 'should clone with reference count incrementing' do
21
+ expect(target.refcount).to eq(0)
22
+ expect(subject.production.refcount).to eq(1)
23
+ klone = subject.clone
24
+ expect(klone.production.refcount).to eq(2)
25
+ duplicate = subject.dup
26
+ expect(duplicate.production.refcount).to eq(3)
27
+ end
28
+
29
+ it 'should know its referenced production' do
30
+ instance = ProductionRef.new(target)
31
+ expect(instance.production).to eq(target)
32
+ end
33
+ end # context
34
+
35
+ context 'Provided services:' do
36
+ it 'should render its referenced production' do
37
+ expect(subject.to_s).to eq(target.object_id.to_s)
38
+ end
39
+
40
+ it 'should unbind itself from its production' do
41
+ expect(target.refcount).to eq(0)
42
+ expect(subject).not_to be_unbound
43
+ expect(target.refcount).to eq(1)
44
+ subject.unbind
45
+ expect(target.refcount).to eq(0)
46
+ expect(subject.production).to be_nil
47
+ expect(subject).to be_unbound
48
+ end
49
+
50
+ it 'should bind to a production' do
51
+ expect(target.refcount).to eq(0)
52
+
53
+ expect(subject).not_to be_unbound
54
+ expect(target.refcount).to eq(1)
55
+
56
+ # Case: bind again to same production
57
+ expect { subject.bind_to(target) }.not_to raise_error
58
+ expect(target.refcount).to eq(1)
59
+
60
+ # Case: bind to another production
61
+ expect(another_target.refcount).to eq(0)
62
+ subject.bind_to(another_target)
63
+ expect(target.refcount).to eq(0)
64
+ expect(another_target.refcount).to eq(1)
65
+ end
66
+
67
+ it 'should complain when binding to something else than production' do
68
+ subject.bind_to(target)
69
+ msg = 'Illegal production type String'
70
+ expect { subject.bind_to('WRONG') }.to raise_error(StandardError, msg)
71
+ end
72
+
73
+ it 'should compare to other production (reference)' do
74
+ same = ProductionRef.new(target)
75
+ expect(subject).to eq(subject) # Strict identity
76
+ expect(subject).to eq(same) # 2 references pointing to same production
77
+ expect(subject).to eq(target)
78
+ end
79
+
80
+ it 'should return the hash value of its production' do
81
+ expectation = target.hash
82
+ expect(subject.hash).to eq(expectation)
83
+ end
84
+
85
+ it 'should complain when requested for a hash and unbound' do
86
+ subject.unbind
87
+ expect { subject.hash }.to raise_error(StandardError)
88
+ end
89
+
90
+ it 'should accept a visitor' do
91
+ # Use a mock visitor
92
+ fake = double('fake_visitor')
93
+
94
+ # Visitor should receive a visit message
95
+ expect(fake).to receive(:visit_prod_ref).once
96
+ expect { subject.accept(fake) }.not_to raise_error
97
+ end
98
+ end # context
99
+ end # describe
100
+ end # module
101
+
102
+ # End of file
@@ -1,376 +1,361 @@
1
- require_relative '../spec_helper'
2
-
3
- # Load the class under test
4
- require_relative '../../lib/sequitur/production'
5
-
6
- module Sequitur # Re-open the module to get rid of qualified names
7
-
8
- describe Production do
9
- # Helper method: convert list of digrams into an array
10
- # of symbol couples.
11
- def to_symbols(theDigrams)
12
- return theDigrams.map(&:symbols)
13
- end
14
-
15
- let(:p_a) do
16
- instance = Production.new
17
- instance.append_symbol(:a)
18
- instance
19
- end
20
-
21
- let(:p_bc) do
22
- instance = Production.new
23
- instance.append_symbol('b')
24
- instance.append_symbol('c')
25
- instance
26
- end
27
-
28
- context 'Creation & initialization:' do
29
- it 'should be created without argument' do
30
- expect { Production.new }.not_to raise_error
31
- end
32
-
33
- it 'should not referenced yet' do
34
- expect(subject.refcount).to eq(0)
35
- end
36
-
37
- it 'should be empty at creation' do
38
- expect(subject).to be_empty
39
- end
40
-
41
- it 'should not have digram' do
42
- expect(subject.digrams).to be_empty
43
- expect(subject.last_digram).to be_nil
44
- end
45
- end # context
46
-
47
- context 'Provided services:' do
48
-
49
- it 'should compare to another production' do
50
- expect(p_a).to eq(p_a)
51
- expect(p_a).not_to eq(p_bc)
52
- end
53
-
54
- it 'should compare to a production reference' do
55
- ref_a = ProductionRef.new(p_a)
56
- expect(p_a).to eq(ref_a)
57
- expect(p_bc).not_to eq(ref_a)
58
-
59
- ref_bc = ProductionRef.new(p_bc)
60
- expect(p_a).not_to eq(ref_bc)
61
- expect(p_bc).to eq(ref_bc)
62
- end
63
- end # context
64
-
65
- context 'Knowing its rhs:' do
66
-
67
- it 'should know the productions in its rhs' do
68
- # Case 1: empty production
69
- expect(subject.references).to be_empty
70
-
71
- # Case 2: production without references
72
- symbols = [:a, :b, :c]
73
- symbols.each { |symb| subject.append_symbol(symb) }
74
- expect(subject.references).to be_empty
75
- expect(subject.references_of(p_a)).to be_empty
76
-
77
- # Case 2: production with one reference
78
- subject.append_symbol(p_a)
79
- expect(subject.references).to eq([p_a])
80
- expect(subject.references_of(p_a).map(&:production)).to eq([p_a])
81
-
82
-
83
- # Case 3: production with repeated references
84
- subject.append_symbol(p_a) # second time
85
- expect(subject.references).to eq([p_a, p_a])
86
- expect(subject.references_of(p_a).map(&:production)).to eq([p_a, p_a])
87
-
88
-
89
- # Case 4: production with multiple distinct references
90
- subject.append_symbol(p_bc)
91
- expect(subject.references).to eq([p_a, p_a, p_bc])
92
- expect(subject.references_of(p_bc).map(&:production)).to eq([p_bc])
93
- end
94
-
95
- it 'should know the position(s) of a given digram' do
96
- sequence1 = [:a, :b, :c, :a, :b, :a, :b, :d]
97
- sequence1.each { |symb| subject.append_symbol(symb) }
98
- positions = [0, 3, 5]
99
- expect(subject.positions_of(:a, :b)).to eq(positions)
100
-
101
- subject.clear_rhs
102
- # Case of overlapping digrams
103
- sequence2 = [:a, :a, :b, :a, :a, :a, :c, :d]
104
- sequence2.each { |symb| subject.append_symbol(symb) }
105
- positions = [0, 3]
106
- expect(subject.positions_of(:a, :a)).to eq(positions)
107
- end
108
-
109
- end # context
110
-
111
- context 'Appending a symbol:' do
112
-
113
- it 'should append a symbol when empty' do
114
- expect { subject.append_symbol(:a) }.not_to raise_error
115
- expect(subject.rhs).to eq([:a])
116
- expect(subject.last_digram).to be_nil
117
- end
118
-
119
- it 'should append a symbol when has one symbol' do
120
- subject.append_symbol(:a)
121
- subject.append_symbol(:b)
122
- expect(subject.rhs).to eq([:a, :b])
123
- expect(subject.last_digram.symbols).to eq([:a, :b])
124
- end
125
-
126
- it 'should append a symbol when rhs has several symbols' do
127
- symbols = [:a, :b, :c, :d, :e, :f]
128
- symbols.each { |symb| subject.append_symbol(symb) }
129
- expect(subject.rhs).to eq(symbols)
130
- expect(subject.last_digram.symbols).to eq([:e, :f])
131
- end
132
-
133
- it 'should append a production in its rhs' do
134
- # Side-effect: refcount of production to append is incremented
135
- expect(p_a.refcount).to be(0)
136
-
137
- input = [p_a, :b, :c, :d, p_a, :e, :f] # p_a appears twice
138
- input.each { |symb| subject.append_symbol(symb) }
139
- expect(p_a.refcount).to be(2)
140
- end
141
-
142
- it 'should append a production ref in its rhs' do
143
- # Side-effect: refcount of production to append is incremented
144
- ref_a = ProductionRef.new(p_a)
145
- expect(p_a.refcount).to be(1)
146
-
147
- input = [ref_a, :b, :c, :d, ref_a] # ref_a appears twice
148
- input.each { |symb| subject.append_symbol(symb) }
149
-
150
- # References in rhs should point to p_a...
151
- # ...but should be distinct reference objects
152
- expect(subject.rhs[0]).to eq(p_a)
153
- expect(subject.rhs[0].object_id).not_to eq(ref_a.object_id)
154
- expect(subject.rhs[-1]).to eq(p_a)
155
- expect(subject.rhs[-1].object_id).not_to eq(ref_a.object_id)
156
-
157
- # Reference count should be updated
158
- expect(p_a.refcount).to be(3)
159
- end
160
-
161
- it 'should complain when appending ref to nil production' do
162
- # Side-effect: refcount of production to append is incremented
163
- ref_a = ProductionRef.new(p_a)
164
- expect(p_a.refcount).to be(1)
165
-
166
- # Unbind the reference
167
- ref_a.unbind
168
-
169
- expect { subject.append_symbol(ref_a) }.to raise_error(StandardError)
170
- end
171
-
172
- end # context
173
-
174
-
175
- context 'Text representation of a production rule:' do
176
-
177
- it 'should emit minimal text when empty' do
178
- expectation = "#{subject.object_id} : ."
179
- expect(subject.to_string).to eq(expectation)
180
- end
181
-
182
- it 'should emit its text representation' do
183
- instance = Production.new
184
- symbols = [:a, :b, 'c', :d, :e, 1000, instance]
185
- symbols.each { |symb| subject.append_symbol(symb) }
186
- expectation = "#{subject.object_id} : "
187
- expectation << "a b 'c' d e 1000 #{instance.object_id}."
188
- expect(subject.to_string).to eq(expectation)
189
- end
190
-
191
- end # context
192
-
193
- context 'Detecting digram repetition:' do
194
- it 'should report no repetition when empty' do
195
- expect(subject.repeated_digram?).to be_falsey
196
- end
197
-
198
- it 'should report no repetition when rhs has less than 3 symbols' do
199
- subject.append_symbol(:a)
200
- expect(subject.repeated_digram?).to be_falsey
201
-
202
- subject.append_symbol(:a)
203
- expect(subject.repeated_digram?).to be_falsey
204
- end
205
-
206
- it 'should detect shortest repetition' do
207
- 'aaa'.each_char { |symb| subject.append_symbol(symb) }
208
- expect(subject.repeated_digram?).to be_truthy
209
- end
210
-
211
- it 'should detect any repetition pattern' do
212
- # Positive cases
213
- cases = %w(abab abcdab abcdcd abcdefcd )
214
- cases.each do |word|
215
- instance = Production.new
216
- word.each_char { |symb| instance.append_symbol(symb) }
217
- expect(instance.repeated_digram?).to be_truthy
218
- end
219
-
220
- # Negative cases
221
- cases = %w(abc abb abba abcdef)
222
- cases.each do |word|
223
- instance = Production.new
224
- word.each_char { |symb| instance.append_symbol(symb) }
225
- expect(instance.repeated_digram?).to be_falsey
226
- end
227
- end
228
- end # context
229
-
230
- context 'Replacing a digram by a production:' do
231
-
232
- it 'should have not effect on empty production' do
233
- subject.reduce_step(p_bc)
234
- expect(subject.rhs).to be_empty
235
- expect(p_bc.refcount).to eq(0)
236
- end
237
-
238
-
239
- it 'should replace two-symbol sequence' do
240
- %w(a b c d e b c e).each { |symb| subject.append_symbol(symb) }
241
- p_bc_before = p_bc.to_string
242
- subject.reduce_step(p_bc)
243
-
244
- expect(subject.rhs.size).to eq(6)
245
- expect(subject.rhs).to eq(['a', p_bc, 'd', 'e', p_bc, 'e'])
246
- expect(p_bc.refcount).to eq(2)
247
- expect(p_bc.to_string).to eq(p_bc_before)
248
- end
249
-
250
-
251
- it 'should replace a starting two-symbol sequence' do
252
- %w(b c d e b c e).each { |symb| subject.append_symbol(symb) }
253
- subject.reduce_step(p_bc)
254
-
255
- expect(subject.rhs.size).to eq(5)
256
- expect(subject.rhs).to eq([p_bc, 'd', 'e', p_bc, 'e'])
257
- expect(p_bc.refcount).to eq(2)
258
- end
259
-
260
-
261
- it 'should replace an ending two-symbol sequence' do
262
- %w(a b c d e b c).each { |symb| subject.append_symbol(symb) }
263
- subject.reduce_step(p_bc)
264
-
265
- expect(subject.rhs.size).to eq(5)
266
- expect(subject.rhs).to eq(['a', p_bc, 'd', 'e', p_bc])
267
- expect(p_bc.refcount).to eq(2)
268
- end
269
-
270
- it 'should replace two consecutive two-symbol sequences' do
271
- %w(a b c b c d).each { |symb| subject.append_symbol(symb) }
272
- subject.reduce_step(p_bc)
273
-
274
- expect(subject.rhs.size).to eq(4)
275
- expect(subject.rhs).to eq(['a', p_bc, p_bc, 'd'])
276
- expect(p_bc.refcount).to eq(2)
277
- end
278
-
279
- end # context
280
-
281
- context 'Replacing a production occurrence by its rhs:' do
282
-
283
- it 'should have not effect on empty production' do
284
- subject.derive_step(p_bc)
285
- expect(subject.rhs).to be_empty
286
- end
287
-
288
- it 'should replace a production at the start' do
289
- [p_bc, 'd'].each { |symb| subject.append_symbol(symb) }
290
- expect(p_bc.refcount).to eq(1)
291
-
292
- subject.derive_step(p_bc)
293
- expect(subject.rhs.size).to eq(3)
294
- expect(subject.rhs).to eq(%w(b c d))
295
- expect(p_bc.refcount).to eq(0)
296
- end
297
-
298
-
299
- it 'should replace a production at the end' do
300
- ['d', p_bc].each { |symb| subject.append_symbol(symb) }
301
- expect(p_bc.refcount).to eq(1)
302
- subject.derive_step(p_bc)
303
-
304
- expect(subject.rhs.size).to eq(3)
305
- expect(subject.rhs).to eq(%w(d b c))
306
- expect(p_bc.refcount).to eq(0)
307
- end
308
-
309
- it 'should replace a production as sole symbol' do
310
- subject.append_symbol(p_bc)
311
- subject.derive_step(p_bc)
312
-
313
- expect(subject.rhs.size).to eq(2)
314
- expect(subject.rhs).to eq(%w(b c))
315
- end
316
-
317
- it 'should replace a production in the middle' do
318
- ['a', p_bc, 'd'].each { |symb| subject.append_symbol(symb) }
319
- subject.derive_step(p_bc)
320
-
321
- expect(subject.rhs.size).to eq(4)
322
- expect(subject.rhs).to eq(%w(a b c d))
323
- end
324
-
325
- end # context
326
-
327
- context 'Visiting:' do
328
- it 'should accept a visitor when its rhs is empty' do
329
- # Use a mock visitor
330
- fake = double('fake_visitor')
331
-
332
- # Empty production: visitor will receive a start and end visit messages
333
- expect(fake).to receive(:start_visit_production).once.ordered
334
- expect(fake).to receive(:start_visit_rhs).once.ordered
335
- expect(fake).to receive(:end_visit_rhs).once.ordered
336
- expect(fake).to receive(:end_visit_production).once.ordered
337
-
338
- expect { subject.accept(fake) }.not_to raise_error
339
- end
340
-
341
- it 'should accept a visitor when rhs consists of terminals only' do
342
- # Use a mock visitor
343
- fake = double('fake_visitor')
344
- expect(fake).to receive(:start_visit_production).once.ordered
345
- expect(fake).to receive(:start_visit_rhs).once.ordered
346
- expect(fake).to receive(:visit_terminal).with('b').ordered
347
- expect(fake).to receive(:visit_terminal).with('c').ordered
348
- expect(fake).to receive(:end_visit_rhs).once.ordered
349
- expect(fake).to receive(:end_visit_production).once.ordered
350
-
351
- expect { p_bc.accept(fake) }.not_to raise_error
352
- end
353
-
354
- it 'should accept a visitor when rhs consists of non-terminals' do
355
- # Add two production references (=non-terminals) to RHS of subject
356
- subject.append_symbol(p_a)
357
- subject.append_symbol(p_bc)
358
-
359
- fake = double('fake_visitor')
360
- expect(fake).to receive(:start_visit_production).once.ordered
361
- expect(fake).to receive(:start_visit_rhs).once.ordered
362
- expect(fake).to receive(:visit_prod_ref).with(p_a).ordered
363
- expect(fake).to receive(:visit_prod_ref).with(p_bc).ordered
364
- expect(fake).to receive(:end_visit_rhs).once.ordered
365
- expect(fake).to receive(:end_visit_production).once.ordered
366
-
367
- expect { subject.accept(fake) }.not_to raise_error
368
- end
369
-
370
- end # context
371
-
372
- end # describe
373
-
374
- end # module
375
-
376
- # End of file
1
+ require_relative '../spec_helper'
2
+
3
+ # Load the class under test
4
+ require_relative '../../lib/sequitur/production'
5
+
6
+ module Sequitur # Re-open the module to get rid of qualified names
7
+ describe Production do
8
+ # Helper method: convert list of digrams into an array
9
+ # of symbol couples.
10
+ def to_symbols(theDigrams)
11
+ return theDigrams.map(&:symbols)
12
+ end
13
+
14
+ let(:p_a) do
15
+ instance = Production.new
16
+ instance.append_symbol(:a)
17
+ instance
18
+ end
19
+
20
+ let(:p_bc) do
21
+ instance = Production.new
22
+ instance.append_symbol('b')
23
+ instance.append_symbol('c')
24
+ instance
25
+ end
26
+
27
+ context 'Creation & initialization:' do
28
+ it 'should be created without argument' do
29
+ expect { Production.new }.not_to raise_error
30
+ end
31
+
32
+ it 'should not referenced yet' do
33
+ expect(subject.refcount).to eq(0)
34
+ end
35
+
36
+ it 'should be empty at creation' do
37
+ expect(subject).to be_empty
38
+ end
39
+
40
+ it 'should not have digram' do
41
+ expect(subject.digrams).to be_empty
42
+ expect(subject.last_digram).to be_nil
43
+ end
44
+ end # context
45
+
46
+ context 'Provided services:' do
47
+ it 'should compare to another production' do
48
+ expect(p_a).to eq(p_a)
49
+ expect(p_a).not_to eq(p_bc)
50
+ end
51
+
52
+ it 'should compare to a production reference' do
53
+ ref_a = ProductionRef.new(p_a)
54
+ expect(p_a).to eq(ref_a)
55
+ expect(p_bc).not_to eq(ref_a)
56
+
57
+ ref_bc = ProductionRef.new(p_bc)
58
+ expect(p_a).not_to eq(ref_bc)
59
+ expect(p_bc).to eq(ref_bc)
60
+ end
61
+ end # context
62
+
63
+ context 'Knowing its rhs:' do
64
+ it 'should know the productions in its rhs' do
65
+ # Case 1: empty production
66
+ expect(subject.references).to be_empty
67
+
68
+ # Case 2: production without references
69
+ symbols = [:a, :b, :c]
70
+ symbols.each { |symb| subject.append_symbol(symb) }
71
+ expect(subject.references).to be_empty
72
+ expect(subject.references_of(p_a)).to be_empty
73
+
74
+ # Case 2: production with one reference
75
+ subject.append_symbol(p_a)
76
+ expect(subject.references).to eq([p_a])
77
+ expect(subject.references_of(p_a).map(&:production)).to eq([p_a])
78
+
79
+
80
+ # Case 3: production with repeated references
81
+ subject.append_symbol(p_a) # second time
82
+ expect(subject.references).to eq([p_a, p_a])
83
+ expect(subject.references_of(p_a).map(&:production)).to eq([p_a, p_a])
84
+
85
+
86
+ # Case 4: production with multiple distinct references
87
+ subject.append_symbol(p_bc)
88
+ expect(subject.references).to eq([p_a, p_a, p_bc])
89
+ expect(subject.references_of(p_bc).map(&:production)).to eq([p_bc])
90
+ end
91
+
92
+ it 'should know the position(s) of a given digram' do
93
+ sequence1 = [:a, :b, :c, :a, :b, :a, :b, :d]
94
+ sequence1.each { |symb| subject.append_symbol(symb) }
95
+ positions = [0, 3, 5]
96
+ expect(subject.positions_of(:a, :b)).to eq(positions)
97
+
98
+ subject.clear_rhs
99
+ # Case of overlapping digrams
100
+ sequence2 = [:a, :a, :b, :a, :a, :a, :c, :d]
101
+ sequence2.each { |symb| subject.append_symbol(symb) }
102
+ positions = [0, 3]
103
+ expect(subject.positions_of(:a, :a)).to eq(positions)
104
+ end
105
+ end # context
106
+
107
+ context 'Appending a symbol:' do
108
+ it 'should append a symbol when empty' do
109
+ expect { subject.append_symbol(:a) }.not_to raise_error
110
+ expect(subject.rhs).to eq([:a])
111
+ expect(subject.last_digram).to be_nil
112
+ end
113
+
114
+ it 'should append a symbol when has one symbol' do
115
+ subject.append_symbol(:a)
116
+ subject.append_symbol(:b)
117
+ expect(subject.rhs).to eq([:a, :b])
118
+ expect(subject.last_digram.symbols).to eq([:a, :b])
119
+ end
120
+
121
+ it 'should append a symbol when rhs has several symbols' do
122
+ symbols = [:a, :b, :c, :d, :e, :f]
123
+ symbols.each { |symb| subject.append_symbol(symb) }
124
+ expect(subject.rhs).to eq(symbols)
125
+ expect(subject.last_digram.symbols).to eq([:e, :f])
126
+ end
127
+
128
+ it 'should append a production in its rhs' do
129
+ # Side-effect: refcount of production to append is incremented
130
+ expect(p_a.refcount).to be(0)
131
+
132
+ input = [p_a, :b, :c, :d, p_a, :e, :f] # p_a appears twice
133
+ input.each { |symb| subject.append_symbol(symb) }
134
+ expect(p_a.refcount).to be(2)
135
+ end
136
+
137
+ it 'should append a production ref in its rhs' do
138
+ # Side-effect: refcount of production to append is incremented
139
+ ref_a = ProductionRef.new(p_a)
140
+ expect(p_a.refcount).to be(1)
141
+
142
+ input = [ref_a, :b, :c, :d, ref_a] # ref_a appears twice
143
+ input.each { |symb| subject.append_symbol(symb) }
144
+
145
+ # References in rhs should point to p_a...
146
+ # ...but should be distinct reference objects
147
+ expect(subject.rhs[0]).to eq(p_a)
148
+ expect(subject.rhs[0].object_id).not_to eq(ref_a.object_id)
149
+ expect(subject.rhs[-1]).to eq(p_a)
150
+ expect(subject.rhs[-1].object_id).not_to eq(ref_a.object_id)
151
+
152
+ # Reference count should be updated
153
+ expect(p_a.refcount).to be(3)
154
+ end
155
+
156
+ it 'should complain when appending ref to nil production' do
157
+ # Side-effect: refcount of production to append is incremented
158
+ ref_a = ProductionRef.new(p_a)
159
+ expect(p_a.refcount).to be(1)
160
+
161
+ # Unbind the reference
162
+ ref_a.unbind
163
+
164
+ expect { subject.append_symbol(ref_a) }.to raise_error(StandardError)
165
+ end
166
+ end # context
167
+
168
+
169
+ context 'Text representation of a production rule:' do
170
+ it 'should emit minimal text when empty' do
171
+ expectation = "#{subject.object_id} : ."
172
+ expect(subject.to_string).to eq(expectation)
173
+ end
174
+
175
+ it 'should emit its text representation' do
176
+ instance = Production.new
177
+ symbols = [:a, :b, 'c', :d, :e, 1000, instance]
178
+ symbols.each { |symb| subject.append_symbol(symb) }
179
+ expectation = "#{subject.object_id} : "
180
+ expectation << "a b 'c' d e 1000 #{instance.object_id}."
181
+ expect(subject.to_string).to eq(expectation)
182
+ end
183
+ end # context
184
+
185
+ context 'Detecting digram repetition:' do
186
+ it 'should report no repetition when empty' do
187
+ expect(subject.repeated_digram?).to be_falsey
188
+ end
189
+
190
+ it 'should report no repetition when rhs has less than 3 symbols' do
191
+ subject.append_symbol(:a)
192
+ expect(subject.repeated_digram?).to be_falsey
193
+
194
+ subject.append_symbol(:a)
195
+ expect(subject.repeated_digram?).to be_falsey
196
+ end
197
+
198
+ it 'should detect shortest repetition' do
199
+ 'aaa'.each_char { |symb| subject.append_symbol(symb) }
200
+ expect(subject.repeated_digram?).to be_truthy
201
+ end
202
+
203
+ it 'should detect any repetition pattern' do
204
+ # Positive cases
205
+ cases = %w(abab abcdab abcdcd abcdefcd )
206
+ cases.each do |word|
207
+ instance = Production.new
208
+ word.each_char { |symb| instance.append_symbol(symb) }
209
+ expect(instance.repeated_digram?).to be_truthy
210
+ end
211
+
212
+ # Negative cases
213
+ cases = %w(abc abb abba abcdef)
214
+ cases.each do |word|
215
+ instance = Production.new
216
+ word.each_char { |symb| instance.append_symbol(symb) }
217
+ expect(instance.repeated_digram?).to be_falsey
218
+ end
219
+ end
220
+ end # context
221
+
222
+ context 'Replacing a digram by a production:' do
223
+ it 'should have not effect on empty production' do
224
+ subject.reduce_step(p_bc)
225
+ expect(subject.rhs).to be_empty
226
+ expect(p_bc.refcount).to eq(0)
227
+ end
228
+
229
+
230
+ it 'should replace two-symbol sequence' do
231
+ %w(a b c d e b c e).each { |symb| subject.append_symbol(symb) }
232
+ p_bc_before = p_bc.to_string
233
+ subject.reduce_step(p_bc)
234
+
235
+ expect(subject.rhs.size).to eq(6)
236
+ expect(subject.rhs).to eq(['a', p_bc, 'd', 'e', p_bc, 'e'])
237
+ expect(p_bc.refcount).to eq(2)
238
+ expect(p_bc.to_string).to eq(p_bc_before)
239
+ end
240
+
241
+
242
+ it 'should replace a starting two-symbol sequence' do
243
+ %w(b c d e b c e).each { |symb| subject.append_symbol(symb) }
244
+ subject.reduce_step(p_bc)
245
+
246
+ expect(subject.rhs.size).to eq(5)
247
+ expect(subject.rhs).to eq([p_bc, 'd', 'e', p_bc, 'e'])
248
+ expect(p_bc.refcount).to eq(2)
249
+ end
250
+
251
+
252
+ it 'should replace an ending two-symbol sequence' do
253
+ %w(a b c d e b c).each { |symb| subject.append_symbol(symb) }
254
+ subject.reduce_step(p_bc)
255
+
256
+ expect(subject.rhs.size).to eq(5)
257
+ expect(subject.rhs).to eq(['a', p_bc, 'd', 'e', p_bc])
258
+ expect(p_bc.refcount).to eq(2)
259
+ end
260
+
261
+ it 'should replace two consecutive two-symbol sequences' do
262
+ %w(a b c b c d).each { |symb| subject.append_symbol(symb) }
263
+ subject.reduce_step(p_bc)
264
+
265
+ expect(subject.rhs.size).to eq(4)
266
+ expect(subject.rhs).to eq(['a', p_bc, p_bc, 'd'])
267
+ expect(p_bc.refcount).to eq(2)
268
+ end
269
+ end # context
270
+
271
+ context 'Replacing a production occurrence by its rhs:' do
272
+ it 'should have not effect on empty production' do
273
+ subject.derive_step(p_bc)
274
+ expect(subject.rhs).to be_empty
275
+ end
276
+
277
+ it 'should replace a production at the start' do
278
+ [p_bc, 'd'].each { |symb| subject.append_symbol(symb) }
279
+ expect(p_bc.refcount).to eq(1)
280
+
281
+ subject.derive_step(p_bc)
282
+ expect(subject.rhs.size).to eq(3)
283
+ expect(subject.rhs).to eq(%w(b c d))
284
+ expect(p_bc.refcount).to eq(0)
285
+ end
286
+
287
+
288
+ it 'should replace a production at the end' do
289
+ ['d', p_bc].each { |symb| subject.append_symbol(symb) }
290
+ expect(p_bc.refcount).to eq(1)
291
+ subject.derive_step(p_bc)
292
+
293
+ expect(subject.rhs.size).to eq(3)
294
+ expect(subject.rhs).to eq(%w(d b c))
295
+ expect(p_bc.refcount).to eq(0)
296
+ end
297
+
298
+ it 'should replace a production as sole symbol' do
299
+ subject.append_symbol(p_bc)
300
+ subject.derive_step(p_bc)
301
+
302
+ expect(subject.rhs.size).to eq(2)
303
+ expect(subject.rhs).to eq(%w(b c))
304
+ end
305
+
306
+ it 'should replace a production in the middle' do
307
+ ['a', p_bc, 'd'].each { |symb| subject.append_symbol(symb) }
308
+ subject.derive_step(p_bc)
309
+
310
+ expect(subject.rhs.size).to eq(4)
311
+ expect(subject.rhs).to eq(%w(a b c d))
312
+ end
313
+ end # context
314
+
315
+ context 'Visiting:' do
316
+ it 'should accept a visitor when its rhs is empty' do
317
+ # Use a mock visitor
318
+ fake = double('fake_visitor')
319
+
320
+ # Empty production: visitor will receive a start and end visit messages
321
+ expect(fake).to receive(:start_visit_production).once.ordered
322
+ expect(fake).to receive(:start_visit_rhs).once.ordered
323
+ expect(fake).to receive(:end_visit_rhs).once.ordered
324
+ expect(fake).to receive(:end_visit_production).once.ordered
325
+
326
+ expect { subject.accept(fake) }.not_to raise_error
327
+ end
328
+
329
+ it 'should accept a visitor when rhs consists of terminals only' do
330
+ # Use a mock visitor
331
+ fake = double('fake_visitor')
332
+ expect(fake).to receive(:start_visit_production).once.ordered
333
+ expect(fake).to receive(:start_visit_rhs).once.ordered
334
+ expect(fake).to receive(:visit_terminal).with('b').ordered
335
+ expect(fake).to receive(:visit_terminal).with('c').ordered
336
+ expect(fake).to receive(:end_visit_rhs).once.ordered
337
+ expect(fake).to receive(:end_visit_production).once.ordered
338
+
339
+ expect { p_bc.accept(fake) }.not_to raise_error
340
+ end
341
+
342
+ it 'should accept a visitor when rhs consists of non-terminals' do
343
+ # Add two production references (=non-terminals) to RHS of subject
344
+ subject.append_symbol(p_a)
345
+ subject.append_symbol(p_bc)
346
+
347
+ fake = double('fake_visitor')
348
+ expect(fake).to receive(:start_visit_production).once.ordered
349
+ expect(fake).to receive(:start_visit_rhs).once.ordered
350
+ expect(fake).to receive(:visit_prod_ref).with(p_a).ordered
351
+ expect(fake).to receive(:visit_prod_ref).with(p_bc).ordered
352
+ expect(fake).to receive(:end_visit_rhs).once.ordered
353
+ expect(fake).to receive(:end_visit_production).once.ordered
354
+
355
+ expect { subject.accept(fake) }.not_to raise_error
356
+ end
357
+ end # context
358
+ end # describe
359
+ end # module
360
+
361
+ # End of file