srl_ruby 0.0.1

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 (44) hide show
  1. checksums.yaml +7 -0
  2. data/.rspec +4 -0
  3. data/.rubocop.yml +3 -0
  4. data/.yardopts +6 -0
  5. data/Gemfile +6 -0
  6. data/LICENSE.txt +21 -0
  7. data/README.md +66 -0
  8. data/Rakefile +16 -0
  9. data/bin/srl_ruby +58 -0
  10. data/lib/regex/abstract_method.rb +35 -0
  11. data/lib/regex/alternation.rb +27 -0
  12. data/lib/regex/anchor.rb +45 -0
  13. data/lib/regex/atomic_expression.rb +16 -0
  14. data/lib/regex/capturing_group.rb +51 -0
  15. data/lib/regex/char_class.rb +38 -0
  16. data/lib/regex/char_range.rb +51 -0
  17. data/lib/regex/char_shorthand.rb +50 -0
  18. data/lib/regex/character.rb +204 -0
  19. data/lib/regex/compound_expression.rb +57 -0
  20. data/lib/regex/concatenation.rb +29 -0
  21. data/lib/regex/expression.rb +60 -0
  22. data/lib/regex/lookaround.rb +50 -0
  23. data/lib/regex/match_option.rb +34 -0
  24. data/lib/regex/monadic_expression.rb +28 -0
  25. data/lib/regex/multiplicity.rb +91 -0
  26. data/lib/regex/non_capturing_group.rb +27 -0
  27. data/lib/regex/polyadic_expression.rb +60 -0
  28. data/lib/regex/quantifiable.rb +22 -0
  29. data/lib/regex/repetition.rb +29 -0
  30. data/lib/regex/wildcard.rb +23 -0
  31. data/lib/srl_ruby/ast_builder.rb +384 -0
  32. data/lib/srl_ruby/grammar.rb +106 -0
  33. data/lib/srl_ruby/regex_repr.rb +13 -0
  34. data/lib/srl_ruby/tokenizer.rb +147 -0
  35. data/lib/srl_ruby/version.rb +3 -0
  36. data/lib/srl_ruby.rb +4 -0
  37. data/spec/integration_spec.rb +451 -0
  38. data/spec/regex/character_spec.rb +166 -0
  39. data/spec/regex/multiplicity_spec.rb +79 -0
  40. data/spec/spec_helper.rb +16 -0
  41. data/spec/srl_ruby/srl_ruby_spec.rb +7 -0
  42. data/spec/srl_ruby/tokenizer_spec.rb +147 -0
  43. data/srl_ruby.gemspec +58 -0
  44. metadata +150 -0
@@ -0,0 +1,451 @@
1
+ require_relative 'spec_helper' # Use the RSpec framework
2
+ require_relative '../lib/srl_ruby/tokenizer'
3
+ require_relative '../lib/srl_ruby/grammar'
4
+ require_relative '../lib/srl_ruby/ast_builder'
5
+
6
+ module SrlRuby
7
+ describe 'Integration tests:' do
8
+ def parse(someSRL)
9
+ tokenizer = SrlRuby::Tokenizer.new(someSRL)
10
+ @engine.parse(tokenizer.tokens)
11
+ end
12
+
13
+ def regexp_repr(aResult)
14
+ # Generate an abstract syntax parse tree from the parse result
15
+ tree = @engine.convert(aResult)
16
+ tree.root
17
+ end
18
+
19
+ before(:each) do
20
+ @engine = Rley::Engine.new do |config|
21
+ config.repr_builder = ASTBuilder
22
+ end
23
+ @engine.use_grammar(SrlRuby::Grammar)
24
+ end
25
+
26
+ context 'Parsing character ranges:' do
27
+ it "should parse 'letter from ... to ...' syntax" do
28
+ result = parse('letter from a to f')
29
+ expect(result).to be_success
30
+
31
+ regexp = regexp_repr(result)
32
+ expect(regexp.to_str).to eq('[a-f]')
33
+ end
34
+
35
+ it "should parse 'uppercase letter from ... to ...' syntax" do
36
+ result = parse('UPPERCASE letter from A to F')
37
+ expect(result).to be_success
38
+
39
+ regexp = regexp_repr(result)
40
+ expect(regexp.to_str).to eq('[A-F]')
41
+ end
42
+
43
+ it "should parse 'letter' syntax" do
44
+ result = parse('letter')
45
+ expect(result).to be_success
46
+
47
+ regexp = regexp_repr(result)
48
+ expect(regexp.to_str).to eq('[a-z]')
49
+ end
50
+
51
+ it "should parse 'uppercase letter' syntax" do
52
+ result = parse('uppercase letter')
53
+ expect(result).to be_success
54
+
55
+ regexp = regexp_repr(result)
56
+ expect(regexp.to_str).to eq('[A-Z]')
57
+ end
58
+
59
+ it "should parse 'digit from ... to ...' syntax" do
60
+ result = parse('digit from 1 to 4')
61
+ expect(result).to be_success
62
+
63
+ regexp = regexp_repr(result)
64
+ expect(regexp.to_str).to eq('[1-4]')
65
+ end
66
+ end # context
67
+
68
+ context 'Parsing string literals:' do
69
+ it 'should parse double quotes literal string' do
70
+ result = parse('literally "hello"')
71
+ expect(result).to be_success
72
+
73
+ regexp = regexp_repr(result)
74
+ expect(regexp.to_str).to eq('hello')
75
+ end
76
+
77
+ it 'should parse single quotes literal string' do
78
+ result = parse("literally 'hello'")
79
+ expect(result).to be_success
80
+
81
+ regexp = regexp_repr(result)
82
+ expect(regexp.to_str).to eq('hello')
83
+ end
84
+
85
+ it 'should escape special characters' do
86
+ result = parse("literally '.'")
87
+ expect(result).to be_success
88
+
89
+ regexp = regexp_repr(result)
90
+ expect(regexp.to_str).to eq('\.')
91
+ end
92
+ end
93
+
94
+ context 'Parsing character classes:' do
95
+ it "should parse 'digit' syntax" do
96
+ result = parse('digit')
97
+ expect(result).to be_success
98
+
99
+ regexp = regexp_repr(result)
100
+ expect(regexp.to_str).to eq('\d')
101
+ end
102
+
103
+ it "should parse 'number' syntax" do
104
+ result = parse('number')
105
+ expect(result).to be_success
106
+
107
+ regexp = regexp_repr(result)
108
+ expect(regexp.to_str).to eq('\d')
109
+ end
110
+
111
+ it "should parse 'any character' syntax" do
112
+ result = parse('any character')
113
+ expect(result).to be_success
114
+
115
+ regexp = regexp_repr(result)
116
+ expect(regexp.to_str).to eq('\w')
117
+ end
118
+
119
+ it "should parse 'no character' syntax" do
120
+ result = parse('no character')
121
+ expect(result).to be_success
122
+
123
+ regexp = regexp_repr(result)
124
+ expect(regexp.to_str).to eq('\W')
125
+ end
126
+
127
+ it "should parse 'whitespace' syntax" do
128
+ result = parse('whitespace')
129
+ expect(result).to be_success
130
+
131
+ regexp = regexp_repr(result)
132
+ expect(regexp.to_str).to eq('\s')
133
+ end
134
+
135
+ it "should parse 'no whitespace' syntax" do
136
+ result = parse('no whitespace')
137
+ expect(result).to be_success
138
+
139
+ regexp = regexp_repr(result)
140
+ expect(regexp.to_str).to eq('\S')
141
+ end
142
+
143
+ it "should parse 'anything' syntax" do
144
+ result = parse('anything')
145
+ expect(result).to be_success
146
+
147
+ regexp = regexp_repr(result)
148
+ expect(regexp.to_str).to eq('.')
149
+ end
150
+
151
+ it "should parse 'one of' syntax" do
152
+ result = parse('one of "._%+-"')
153
+ expect(result).to be_success
154
+
155
+ regexp = regexp_repr(result)
156
+ # Remark: reference implementation less readable
157
+ # (escapes more characters than required)
158
+ expect(regexp.to_str).to eq('[._%+\-]')
159
+ end
160
+ end # context
161
+
162
+ context 'Parsing special character declarations:' do
163
+ it "should parse 'tab' syntax" do
164
+ result = parse('tab')
165
+ expect(result).to be_success
166
+
167
+ regexp = regexp_repr(result)
168
+ expect(regexp.to_str).to eq('\t')
169
+ end
170
+
171
+ it "should parse 'backslash' syntax" do
172
+ result = parse('backslash')
173
+ expect(result).to be_success
174
+
175
+ regexp = regexp_repr(result)
176
+ expect(regexp.to_str).to eq('\\')
177
+ end
178
+
179
+ it "should parse 'new line' syntax" do
180
+ result = parse('new line')
181
+ expect(result).to be_success
182
+
183
+ regexp = regexp_repr(result)
184
+ expect(regexp.to_str).to eq('\n')
185
+ end
186
+ end # context
187
+
188
+ context 'Parsing alternations:' do
189
+ it "should parse 'any of' syntax" do
190
+ source = 'any of (any character, one of "._%-+")'
191
+ result = parse(source)
192
+ expect(result).to be_success
193
+
194
+ regexp = regexp_repr(result)
195
+ expect(regexp.to_str).to eq('(?:\w|[._%\-+])')
196
+ end
197
+ end # context
198
+
199
+ context 'Parsing concatenation:' do
200
+ it 'should reject dangling comma' do
201
+ source = 'literally "a",'
202
+ result = parse(source)
203
+ expect(result).not_to be_success
204
+ message_prefix = /Premature end of input after ','/
205
+ expect(result.failure_reason.message).to match(message_prefix)
206
+ end
207
+
208
+ it 'should parse concatenation' do
209
+ result = parse('any of (literally "sample", (digit once or more))')
210
+ expect(result).to be_success
211
+
212
+ regexp = regexp_repr(result)
213
+ expect(regexp.to_str).to eq('(?:sample|(?:\d+))')
214
+ end
215
+
216
+ it 'should parse a long sequence of patterns' do
217
+ source = <<-ENDS
218
+ any of (any character, one of "._%-+") once or more,
219
+ literally "@",
220
+ any of (digit, letter, one of ".-") once or more,
221
+ literally ".",
222
+ letter at least 2 times
223
+ ENDS
224
+
225
+ result = parse(source)
226
+ expect(result).to be_success
227
+
228
+ regexp = regexp_repr(result)
229
+ # SRL: (?:\w|[\._%\-\+])+(?:@)(?:[0-9]|[a-z]|[\.\-])+(?:\.)[a-z]{2,}
230
+ expectation = '(?:\w|[._%\-+])+@(?:\d|[a-z]|[.\-])+\.[a-z]{2,}'
231
+ expect(regexp.to_str).to eq(expectation)
232
+ end
233
+ end # context
234
+
235
+ context 'Parsing quantifiers:' do
236
+ let(:prefix) { 'letter from p to t ' }
237
+
238
+ it "should parse 'once' syntax" do
239
+ result = parse(prefix + 'once')
240
+ expect(result).to be_success
241
+
242
+ regexp = regexp_repr(result)
243
+ expect(regexp.to_str).to eq('[p-t]{1}')
244
+ end
245
+
246
+ it "should parse 'twice' syntax" do
247
+ result = parse('digit twice')
248
+ expect(result).to be_success
249
+
250
+ regexp = regexp_repr(result)
251
+ expect(regexp.to_str).to eq('\d{2}')
252
+ end
253
+
254
+ it "should parse 'optional' syntax" do
255
+ result = parse('anything optional')
256
+ expect(result).to be_success
257
+
258
+ regexp = regexp_repr(result)
259
+ expect(regexp.to_str).to eq('.?')
260
+ end
261
+
262
+ it "should parse 'exactly ... times' syntax" do
263
+ result = parse('letter from a to f exactly 4 times')
264
+ expect(result).to be_success
265
+
266
+ regexp = regexp_repr(result)
267
+ expect(regexp.to_str).to eq('[a-f]{4}')
268
+ end
269
+
270
+ it "should parse 'between ... and ... times' syntax" do
271
+ result = parse(prefix + 'between 2 and 4 times')
272
+ expect(result).to be_success
273
+
274
+ # Dropping 'times' keyword is shorter syntax
275
+ expect(parse(prefix + 'between 2 and 4')).to be_success
276
+
277
+ regexp = regexp_repr(result)
278
+ expect(regexp.to_str).to eq('[p-t]{2,4}')
279
+ end
280
+
281
+ it "should parse 'once or more' syntax" do
282
+ result = parse(prefix + 'once or more')
283
+ expect(result).to be_success
284
+
285
+ regexp = regexp_repr(result)
286
+ expect(regexp.to_str).to eq('[p-t]+')
287
+ end
288
+
289
+ it "should parse 'never or more' syntax" do
290
+ result = parse(prefix + 'never or more')
291
+ expect(result).to be_success
292
+
293
+ regexp = regexp_repr(result)
294
+ expect(regexp.to_str).to eq('[p-t]*')
295
+ end
296
+
297
+ it "should parse 'at least ... times' syntax" do
298
+ result = parse(prefix + 'at least 10 times')
299
+ expect(result).to be_success
300
+
301
+ regexp = regexp_repr(result)
302
+ expect(regexp.to_str).to eq('[p-t]{10,}')
303
+ end
304
+ end # context
305
+
306
+ context 'Parsing lookaround:' do
307
+ it 'should parse positive lookahead' do
308
+ result = parse('letter if followed by (anything once or more, digit)')
309
+ expect(result).to be_success
310
+
311
+ regexp = regexp_repr(result)
312
+ expect(regexp.to_str).to eq('[a-z](?=(?:.+\d))')
313
+ end
314
+
315
+ it 'should parse negative lookahead' do
316
+ result = parse('letter if not followed by (anything once or more, digit)')
317
+ expect(result).to be_success
318
+
319
+ regexp = regexp_repr(result)
320
+ expect(regexp.to_str).to eq('[a-z](?!(?:.+\d))')
321
+ end
322
+
323
+ it 'should parse positive lookbehind' do
324
+ result = parse('literally "bar" if already had literally "foo"')
325
+ expect(result).to be_success
326
+
327
+ regexp = regexp_repr(result)
328
+ expect(regexp.to_str).to eq('bar(?<=foo)')
329
+ end
330
+
331
+ it 'should parse negative lookbehind' do
332
+ result = parse('literally "bar" if not already had literally "foo"')
333
+ expect(result).to be_success
334
+
335
+ regexp = regexp_repr(result)
336
+ expect(regexp.to_str).to eq('bar(?<!foo)')
337
+ end
338
+ end # context
339
+
340
+ context 'Parsing capturing group:' do
341
+ it 'should parse simple anonymous capturing group' do
342
+ result = parse('capture(literally "sample")')
343
+ expect(result).to be_success
344
+
345
+ regexp = regexp_repr(result)
346
+ expect(regexp.to_str).to eq('(sample)')
347
+ end
348
+
349
+ it 'should parse complex anonymous capturing group' do
350
+ source = 'capture(any of (literally "sample", (digit once or more)))'
351
+ result = parse(source)
352
+ expect(result).to be_success
353
+
354
+ regexp = regexp_repr(result)
355
+ expect(regexp.to_str).to eq('((?:sample|(?:\d+)))')
356
+ end
357
+
358
+ it 'should parse simple anonymous until capturing group' do
359
+ result = parse('capture anything once or more until literally "!"')
360
+ expect(result).to be_success
361
+
362
+ regexp = regexp_repr(result)
363
+ expect(regexp.to_str).to eq('(.+)!')
364
+ end
365
+
366
+ it 'should parse complex named capturing group' do
367
+ source = <<-END_SRL
368
+ capture(any of (literally "sample", (digit once or more)))
369
+ as "foo"
370
+ END_SRL
371
+ result = parse(source)
372
+ expect(result).to be_success
373
+
374
+ regexp = regexp_repr(result)
375
+ expect(regexp.to_str).to eq('(?<foo>(?:sample|(?:\d+)))')
376
+ end
377
+
378
+ it 'should parse a sequence with named capturing groups' do
379
+ source = <<-ENDS
380
+ capture (anything once or more) as "first",
381
+ literally " - ",
382
+ capture literally "second part" as "second"
383
+ ENDS
384
+ result = parse(source)
385
+ expect(result).to be_success
386
+
387
+ regexp = regexp_repr(result)
388
+ expect(regexp.to_str).to eq('(?<first>.+) - (?<second>second part)')
389
+ end
390
+
391
+ it 'should parse complex named until capturing group' do
392
+ source = 'capture (anything once or more) as "foo" until literally "m"'
393
+ result = parse(source)
394
+ expect(result).to be_success
395
+
396
+ regexp = regexp_repr(result)
397
+ expect(regexp.to_str).to eq('(?<foo>.+)m')
398
+ end
399
+ end # context
400
+
401
+ context 'Parsing anchors:' do
402
+ it 'should parse begin anchors' do
403
+ result = parse('starts with literally "match"')
404
+ expect(result).to be_success
405
+
406
+ regexp = regexp_repr(result)
407
+ expect(regexp.to_str).to eq('^match')
408
+ end
409
+
410
+ it 'should parse begin anchors (alternative syntax)' do
411
+ result = parse('begin with literally "match"')
412
+ expect(result).to be_success
413
+
414
+ regexp = regexp_repr(result)
415
+ expect(regexp.to_str).to eq('^match')
416
+ end
417
+
418
+ it 'should parse end anchors' do
419
+ result = parse('literally "match" must end')
420
+ expect(result).to be_success
421
+
422
+ regexp = regexp_repr(result)
423
+ expect(regexp.to_str).to eq('match$')
424
+ end
425
+
426
+ it 'should parse combination of begin and end anchors' do
427
+ result = parse('starts with literally "match" must end')
428
+ expect(result).to be_success
429
+
430
+ regexp = regexp_repr(result)
431
+ expect(regexp.to_str).to eq('^match$')
432
+ end
433
+
434
+ it 'should accept anchor with a sequence of patterns' do
435
+ source = <<-ENDS
436
+ begin with any of (digit, letter, one of ".-") once or more,
437
+ literally ".",
438
+ letter at least 2 times must end
439
+ ENDS
440
+
441
+ result = parse(source)
442
+ expect(result).to be_success
443
+
444
+ regexp = regexp_repr(result)
445
+ # SRL: (?:\w|[\._%\-\+])+(?:@)(?:[0-9]|[a-z]|[\.\-])+(?:\.)[a-z]{2,}
446
+ expect(regexp.to_str).to eq('^(?:\d|[a-z]|[.\-])+\.[a-z]{2,}$')
447
+ end
448
+ end # context
449
+ end # describe
450
+ end # module
451
+ # End of file
@@ -0,0 +1,166 @@
1
+ # File: character_spec.rb
2
+ require_relative '../spec_helper' # Use the RSpec test framework
3
+ require_relative '../../lib/regex/character'
4
+
5
+ module Regex # Open this namespace, to get rid of scope qualifiers
6
+ describe Character do
7
+ # This constant holds an arbitrary selection of characters
8
+ SampleChars = [?a, ?\0, ?\u0107].freeze
9
+
10
+ # This constant holds the codepoints of the character selection
11
+ SampleInts = [0x61, 0, 0x0107].freeze
12
+
13
+ # This constant holds an arbitrary selection of two characters (digrams)
14
+ # escape sequences
15
+ SampleDigrams = %w[\n \e \0 \6 \k].freeze
16
+
17
+ # This constant holds an arbitrary selection of escaped octal
18
+ # or hexadecimal literals
19
+ SampleNumEscs = %w[\0 \07 \x07 \xa \x0F \u03a3 \u{a}].freeze
20
+
21
+ before(:all) do
22
+ # Ensure that the set of codepoints is mapping the set of chars...
23
+ expect(SampleChars.map(&:ord)).to eq(SampleInts)
24
+ end
25
+
26
+ context 'Creation & initialization' do
27
+ it 'should be created with a with an integer value (codepoint) or...' do
28
+ SampleInts.each do |aCodepoint|
29
+ expect { Character.new(aCodepoint) }.not_to raise_error
30
+ end
31
+ end
32
+
33
+ it '...could be created with a single character String or...' do
34
+ SampleChars.each do |aChar|
35
+ expect { Character.new(aChar) }.not_to raise_error
36
+ end
37
+ end
38
+
39
+ it '...could be created with an escape sequence' do
40
+ # Case 1: escape sequence is a digram
41
+ SampleDigrams.each do |anEscapeSeq|
42
+ expect { Character.new(anEscapeSeq) }.not_to raise_error
43
+ end
44
+
45
+ # Case 2: escape sequence is an escaped octal or hexadecimal literal
46
+ SampleNumEscs.each do |anEscapeSeq|
47
+ expect { Character.new(anEscapeSeq) }.not_to raise_error
48
+ end
49
+ end
50
+ end # context
51
+
52
+ context 'Provided services' do
53
+ it 'Should know its lexeme if created from a string' do
54
+ # Lexeme is defined when the character was initialised from a text
55
+ SampleChars.each do |aChar|
56
+ ch = Character.new(aChar)
57
+ expect(ch.lexeme).to eq(aChar)
58
+ end
59
+ end
60
+
61
+ it 'Should not know its lexeme representation from a codepoint' do
62
+ SampleInts.each do |aChar|
63
+ ch = Character.new(aChar)
64
+ expect(ch.lexeme).to be_nil
65
+ end
66
+ end
67
+
68
+ it 'should know its String representation' do
69
+ # Try for one character
70
+ newOne = Character.new(?\u03a3)
71
+ expect(newOne.char).to eq('Σ')
72
+ expect(newOne.to_str).to eq("\u03A3")
73
+
74
+ # Try with our chars sample
75
+ SampleChars.each { |aChar| Character.new(aChar).to_str == aChar }
76
+
77
+ # Try with our codepoint sample
78
+ mapped_chars = SampleInts.map do |aCodepoint|
79
+ Character.new(aCodepoint).char
80
+ end
81
+ expect(mapped_chars).to eq(SampleChars)
82
+
83
+ # Try with our escape sequence samples
84
+ (SampleDigrams + SampleNumEscs).each do |anEscSeq|
85
+ expectation = String.class_eval(%Q|"#{anEscSeq}"|, __FILE__, __LINE__)
86
+ Character.new(anEscSeq).to_str == expectation
87
+ end
88
+ end
89
+
90
+ it 'should know its codepoint' do
91
+ # Try for one character
92
+ newOne = Character.new(?\u03a3)
93
+ expect(newOne.codepoint).to eq(0x03a3)
94
+
95
+ # Try with our chars sample
96
+ allCodepoints = SampleChars.map do |aChar|
97
+ Character.new(aChar).codepoint
98
+ end
99
+ expect(allCodepoints).to eq(SampleInts)
100
+
101
+ # Try with our codepoint sample
102
+ mapped_chars = SampleInts.each do |aCodepoint|
103
+ expect(Character.new(aCodepoint).codepoint).to eq(aCodepoint)
104
+ end
105
+
106
+ # Try with our escape sequence samples
107
+ (SampleDigrams + SampleNumEscs).each do |anEscSeq|
108
+ expectation = String.class_eval(%Q|"#{anEscSeq}".ord()|, __FILE__, __LINE__)
109
+ expect(Character.new(anEscSeq).codepoint).to eq(expectation)
110
+ end
111
+ end
112
+
113
+ it 'should known whether it is equal to another Object' do
114
+ newOne = Character.new(?\u03a3)
115
+
116
+ # Case 1: test equality with itself
117
+ expect(newOne).to eq(newOne)
118
+
119
+ # Case 2: test equality with another Character
120
+ expect(newOne).to eq(Character.new(?\u03a3))
121
+ expect(newOne).not_to eq(Character.new(?\u0333))
122
+
123
+ # Case 3: test equality with an integer value
124
+ # (equality based on codepoint value)
125
+ expect(newOne).to eq(0x03a3)
126
+ expect(newOne).not_to eq(0x0333)
127
+
128
+ # Case 4: test equality with a single-character String
129
+ expect(newOne).to eq(?\u03a3)
130
+ expect(newOne).not_to eq(?\u0333)
131
+
132
+ # Case 5: test fails with multiple character strings
133
+ expect(newOne).not_to eq('03a3')
134
+
135
+ # Case 6: equality testing with arbitray object
136
+ expect(newOne).not_to eq(nil)
137
+ expect(newOne).not_to eq(Object.new)
138
+
139
+ # In case 6, equality is based on to_s method.
140
+ simulator = double('fake')
141
+ expect(simulator).to receive(:to_s).and_return(?\u03a3)
142
+ expect(newOne).to eq(simulator)
143
+
144
+ # Create a module that re-defines the existing to_s method
145
+ module Tweak_to_s
146
+ def to_s() # Overwrite the existing to_s method
147
+ return ?\u03a3
148
+ end
149
+ end # module
150
+ weird = Object.new
151
+ weird.extend(Tweak_to_s)
152
+ expect(newOne).to eq(weird)
153
+ end
154
+
155
+ it 'should know its readable description' do
156
+ ch1 = Character.new('a')
157
+ expect(ch1.explain).to eq("the character 'a'")
158
+
159
+ ch2 = Character.new(?\u03a3)
160
+ expect(ch2.explain).to eq("the character '\u03a3'")
161
+ end
162
+ end # context
163
+ end # describe
164
+ end # module
165
+
166
+ # End of file
@@ -0,0 +1,79 @@
1
+ # File: Multiplicity_spec.rb
2
+
3
+ require_relative '../spec_helper' # Use the RSpec test framework
4
+ require_relative '../../lib/regex/multiplicity'
5
+
6
+ module SRL
7
+ # Reopen the module, in order to get rid of fully qualified names
8
+ module Regex # This module is used as a namespace
9
+ describe Multiplicity do
10
+ context 'Creation & initialisation' do
11
+ it 'should be created with 3 arguments' do
12
+ # Valid cases: initialized with two integer values and a policy symbol
13
+ %i[greedy lazy possessive].each do |aPolicy|
14
+ expect { Multiplicity.new(0, 1, aPolicy) }.not_to raise_error
15
+ end
16
+
17
+ # Invalid case: initialized with invalid policy value
18
+ err = StandardError
19
+ msg = "Invalid repetition policy 'KO'."
20
+ expect { Multiplicity.new(0, :more, 'KO') }.to raise_error(err, msg)
21
+ end
22
+ end
23
+
24
+ context 'Provided services' do
25
+ it 'should know its text representation' do
26
+ policy2text = { greedy: '', lazy: '?', possessive: '+' }
27
+
28
+ # Case: zero or one
29
+ policy2text.each_key do |aPolicy|
30
+ multi = Multiplicity.new(0, 1, aPolicy)
31
+ expect(multi.to_str).to eq("?#{policy2text[aPolicy]}")
32
+ end
33
+
34
+ # Case: zero or more
35
+ policy2text.each_key do |aPolicy|
36
+ multi = Multiplicity.new(0, :more, aPolicy)
37
+ expect(multi.to_str).to eq("*#{policy2text[aPolicy]}")
38
+ end
39
+
40
+ # Case: one or more
41
+ policy2text.each_key do |aPolicy|
42
+ multi = Multiplicity.new(1, :more, aPolicy)
43
+ expect(multi.to_str).to eq("+#{policy2text[aPolicy]}")
44
+ end
45
+
46
+ # Case: exactly m times
47
+ policy2text.each_key do |aPolicy|
48
+ samples = [1, 2, 5, 100]
49
+ samples.each do |aCount|
50
+ multi = Multiplicity.new(aCount, aCount, aPolicy)
51
+ expect(multi.to_str).to eq("{#{aCount}}#{policy2text[aPolicy]}")
52
+ end
53
+ end
54
+
55
+ # Case: m, n times
56
+ policy2text.each_key do |aPolicy|
57
+ samples = [1, 2, 5, 100]
58
+ samples.each do |aCount|
59
+ upper = aCount + 1 + rand(20)
60
+ multi = Multiplicity.new(aCount, upper, aPolicy)
61
+ expectation = "{#{aCount},#{upper}}#{policy2text[aPolicy]}"
62
+ expect(multi.to_str).to eq(expectation)
63
+ end
64
+ end
65
+
66
+ # Case: m or more
67
+ policy2text.each_key do |aPolicy|
68
+ samples = [2, 3, 5, 100]
69
+ samples.each do |aCount|
70
+ multi = Multiplicity.new(aCount, :more, aPolicy)
71
+ expect(multi.to_str).to eq("{#{aCount},}#{policy2text[aPolicy]}")
72
+ end
73
+ end
74
+ end
75
+ end
76
+ end
77
+ end # module
78
+ end # module
79
+ # End of file