sequitur 0.1.13 → 0.1.14

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.
@@ -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