regexp_parser 0.1.6 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (84) hide show
  1. checksums.yaml +4 -4
  2. data/ChangeLog +57 -0
  3. data/Gemfile +8 -0
  4. data/LICENSE +1 -1
  5. data/README.md +225 -206
  6. data/Rakefile +9 -3
  7. data/lib/regexp_parser.rb +7 -11
  8. data/lib/regexp_parser/expression.rb +72 -14
  9. data/lib/regexp_parser/expression/classes/alternation.rb +3 -16
  10. data/lib/regexp_parser/expression/classes/conditional.rb +57 -0
  11. data/lib/regexp_parser/expression/classes/free_space.rb +17 -0
  12. data/lib/regexp_parser/expression/classes/keep.rb +7 -0
  13. data/lib/regexp_parser/expression/classes/set.rb +28 -7
  14. data/lib/regexp_parser/expression/methods/strfregexp.rb +113 -0
  15. data/lib/regexp_parser/expression/methods/tests.rb +116 -0
  16. data/lib/regexp_parser/expression/methods/traverse.rb +63 -0
  17. data/lib/regexp_parser/expression/quantifier.rb +10 -0
  18. data/lib/regexp_parser/expression/sequence.rb +45 -0
  19. data/lib/regexp_parser/expression/subexpression.rb +29 -1
  20. data/lib/regexp_parser/lexer.rb +31 -8
  21. data/lib/regexp_parser/parser.rb +118 -45
  22. data/lib/regexp_parser/scanner.rb +1745 -1404
  23. data/lib/regexp_parser/scanner/property.rl +57 -3
  24. data/lib/regexp_parser/scanner/scanner.rl +161 -34
  25. data/lib/regexp_parser/syntax.rb +12 -2
  26. data/lib/regexp_parser/syntax/ruby/1.9.1.rb +3 -3
  27. data/lib/regexp_parser/syntax/ruby/1.9.3.rb +2 -7
  28. data/lib/regexp_parser/syntax/ruby/2.0.0.rb +4 -1
  29. data/lib/regexp_parser/syntax/ruby/2.1.4.rb +13 -0
  30. data/lib/regexp_parser/syntax/ruby/2.1.5.rb +13 -0
  31. data/lib/regexp_parser/syntax/ruby/2.1.rb +2 -2
  32. data/lib/regexp_parser/syntax/ruby/2.2.0.rb +16 -0
  33. data/lib/regexp_parser/syntax/ruby/2.2.rb +8 -0
  34. data/lib/regexp_parser/syntax/tokens.rb +19 -2
  35. data/lib/regexp_parser/syntax/tokens/conditional.rb +22 -0
  36. data/lib/regexp_parser/syntax/tokens/keep.rb +14 -0
  37. data/lib/regexp_parser/syntax/tokens/unicode_property.rb +45 -4
  38. data/lib/regexp_parser/token.rb +23 -8
  39. data/lib/regexp_parser/version.rb +5 -0
  40. data/regexp_parser.gemspec +35 -0
  41. data/test/expression/test_all.rb +6 -1
  42. data/test/expression/test_base.rb +19 -0
  43. data/test/expression/test_conditionals.rb +114 -0
  44. data/test/expression/test_free_space.rb +33 -0
  45. data/test/expression/test_set.rb +61 -0
  46. data/test/expression/test_strfregexp.rb +214 -0
  47. data/test/expression/test_subexpression.rb +24 -0
  48. data/test/expression/test_tests.rb +99 -0
  49. data/test/expression/test_to_h.rb +48 -0
  50. data/test/expression/test_to_s.rb +46 -0
  51. data/test/expression/test_traverse.rb +164 -0
  52. data/test/lexer/test_all.rb +16 -3
  53. data/test/lexer/test_conditionals.rb +101 -0
  54. data/test/lexer/test_keep.rb +24 -0
  55. data/test/lexer/test_literals.rb +51 -51
  56. data/test/lexer/test_nesting.rb +62 -62
  57. data/test/lexer/test_refcalls.rb +18 -20
  58. data/test/parser/test_all.rb +18 -3
  59. data/test/parser/test_alternation.rb +11 -14
  60. data/test/parser/test_conditionals.rb +148 -0
  61. data/test/parser/test_escapes.rb +29 -5
  62. data/test/parser/test_free_space.rb +139 -0
  63. data/test/parser/test_groups.rb +40 -0
  64. data/test/parser/test_keep.rb +21 -0
  65. data/test/scanner/test_all.rb +8 -2
  66. data/test/scanner/test_conditionals.rb +166 -0
  67. data/test/scanner/test_escapes.rb +8 -5
  68. data/test/scanner/test_free_space.rb +133 -0
  69. data/test/scanner/test_groups.rb +28 -0
  70. data/test/scanner/test_keep.rb +33 -0
  71. data/test/scanner/test_properties.rb +4 -0
  72. data/test/scanner/test_scripts.rb +71 -1
  73. data/test/syntax/ruby/test_1.9.3.rb +2 -2
  74. data/test/syntax/ruby/test_2.0.0.rb +38 -0
  75. data/test/syntax/ruby/test_2.2.0.rb +38 -0
  76. data/test/syntax/ruby/test_all.rb +1 -8
  77. data/test/syntax/ruby/test_files.rb +104 -0
  78. data/test/test_all.rb +2 -1
  79. data/test/token/test_all.rb +2 -0
  80. data/test/token/test_token.rb +109 -0
  81. metadata +75 -21
  82. data/VERSION.yml +0 -5
  83. data/lib/regexp_parser/ctype.rb +0 -48
  84. data/test/syntax/ruby/test_2.x.rb +0 -46
@@ -2,10 +2,6 @@ require File.expand_path("../../helpers", __FILE__)
2
2
 
3
3
  class TestParserEscapes < Test::Unit::TestCase
4
4
 
5
- def test_parse_control_sequence_short
6
- #root = RP.parse(/\b\d\\\c2\C-C\M-\C-2/)
7
- end
8
-
9
5
  tests = {
10
6
  /a\ac/ => [1, :escape, :bell, EscapeSequence::Bell],
11
7
  /a\ec/ => [1, :escape, :escape, EscapeSequence::AsciiEscape],
@@ -33,7 +29,7 @@ class TestParserEscapes < Test::Unit::TestCase
33
29
 
34
30
  count = 0
35
31
  tests.each do |pattern, test|
36
- define_method "test_parse_anchor_#{test[2]}_#{count+=1}" do
32
+ define_method "test_parse_escape_#{test[2]}_#{count+=1}" do
37
33
  root = RP.parse(pattern, 'ruby/1.9')
38
34
  exp = root.expressions[test[0]]
39
35
 
@@ -45,4 +41,32 @@ class TestParserEscapes < Test::Unit::TestCase
45
41
  end
46
42
  end
47
43
 
44
+ def test_parse_escape_control_sequence_lower
45
+ root = RP.parse(/a\\\c2b/)
46
+
47
+ assert_equal( EscapeSequence::Control, root[2].class )
48
+ assert_equal( '\\c2', root[2].text )
49
+ end
50
+
51
+ def test_parse_escape_control_sequence_upper
52
+ root = RP.parse(/\d\\\C-C\w/)
53
+
54
+ assert_equal( EscapeSequence::Control, root[2].class )
55
+ assert_equal( '\\C-C', root[2].text )
56
+ end
57
+
58
+ def test_parse_escape_meta_sequence
59
+ root = RP.parse(/\Z\\\M-Z/n)
60
+
61
+ assert_equal( EscapeSequence::Meta, root[2].class )
62
+ assert_equal( '\\M-Z', root[2].text )
63
+ end
64
+
65
+ def test_parse_escape_meta_control_sequence
66
+ root = RP.parse(/\A\\\M-\C-X/n)
67
+
68
+ assert_equal( EscapeSequence::MetaControl, root[2].class )
69
+ assert_equal( '\\M-\\C-X', root[2].text )
70
+ end
71
+
48
72
  end
@@ -0,0 +1,139 @@
1
+ require File.expand_path("../../helpers", __FILE__)
2
+
3
+ class ParserFreeSpace < Test::Unit::TestCase
4
+
5
+ def test_parse_free_space_spaces
6
+ regexp = /a ? b * c + d{2,4}/x
7
+ root = RP.parse(regexp)
8
+
9
+ 0.upto(6) do |i|
10
+ if i%2 == 1
11
+ # Consecutive spaces get merged by the parser, thus the two spaces.
12
+ assert_equal( WhiteSpace, root[i].class )
13
+ assert_equal( ' ', root[i].text )
14
+ else
15
+ assert_equal( Literal, root[i].class )
16
+ assert_equal( true, root[i].quantified? )
17
+ end
18
+ end
19
+ end
20
+
21
+ def test_parse_non_free_space_literals
22
+ regexp = /a b c d/
23
+ root = RP.parse(regexp)
24
+
25
+ assert_equal( Literal, root.first.class )
26
+ assert_equal( 'a b c d', root.first.text )
27
+ end
28
+
29
+ def test_parse_free_space_comments
30
+ regexp = %r{
31
+ a ? # One letter
32
+ b {2,5} # Another one
33
+ [c-g] + # A set
34
+ (h|i|j) | # A group
35
+ klm *
36
+ nop +
37
+ }x
38
+
39
+ root = RP.parse(regexp)
40
+
41
+ alt = root.first
42
+ assert_equal( Alternation, alt.class )
43
+
44
+ alt_1 = alt.alternatives.first
45
+ assert_equal( Alternative, alt_1.class )
46
+ assert_equal( 15, alt_1.length )
47
+
48
+ [0, 2, 4, 6, 8, 12, 14].each do |i|
49
+ assert_equal( WhiteSpace, alt_1[i].class )
50
+ end
51
+
52
+ [3, 7, 11].each do |i|
53
+ assert_equal( Comment, alt_1[i].class )
54
+ end
55
+
56
+ alt_2 = alt.alternatives.last
57
+ assert_equal( Alternative, alt_2.class )
58
+ assert_equal( 7, alt_2.length )
59
+
60
+ [0, 2, 4, 6].each do |i|
61
+ assert_equal( WhiteSpace, alt_2[i].class )
62
+ end
63
+
64
+ assert_equal( Comment, alt_2[1].class )
65
+ end
66
+
67
+ def test_parse_free_space_nested_comments
68
+ # Tests depend on spacing and indentation, obviously.
69
+ regexp = %r{
70
+ # Group one
71
+ (
72
+ abc # Comment one
73
+ \d? # Optional \d
74
+ )+
75
+
76
+ # Group two
77
+ (
78
+ def # Comment two
79
+ \s? # Optional \s
80
+ )?
81
+ }x
82
+
83
+ root = RP.parse(regexp)
84
+
85
+ top_comment_1 = root[1]
86
+ assert_equal( Comment, top_comment_1.class )
87
+ assert_equal( "# Group one\n", top_comment_1.text )
88
+ assert_equal( 7, top_comment_1.starts_at )
89
+
90
+ top_comment_2 = root[5]
91
+ assert_equal( Comment, top_comment_2.class )
92
+ assert_equal( "# Group two\n", top_comment_2.text )
93
+ assert_equal( 95, top_comment_2.starts_at )
94
+
95
+ # Nested comments
96
+ [3, 7].each_with_index do |g, i|
97
+ group = root[g]
98
+
99
+ [3, 7].each do |c|
100
+ comment = group[c]
101
+ assert_equal( Comment, comment.class )
102
+ assert_equal( 14, comment.text.length )
103
+ end
104
+ end
105
+ end
106
+
107
+ def test_parse_free_space_quantifiers
108
+ regexp = %r{
109
+ a
110
+ # comment 1
111
+ ?
112
+ (
113
+ b # comment 2
114
+ # comment 3
115
+ +
116
+ )
117
+ # comment 4
118
+ *
119
+ }x
120
+
121
+ root = RP.parse(regexp)
122
+
123
+ literal_1 = root[1]
124
+ assert_equal( Literal, literal_1.class )
125
+ assert_equal( true, literal_1.quantified? )
126
+ assert_equal( :zero_or_one, literal_1.quantifier.token )
127
+
128
+ group = root[5]
129
+ assert_equal( Group::Capture, group.class )
130
+ assert_equal( true, group.quantified? )
131
+ assert_equal( :zero_or_more, group.quantifier.token )
132
+
133
+ literal_2 = group[1]
134
+ assert_equal( Literal, literal_2.class )
135
+ assert_equal( true, literal_2.quantified? )
136
+ assert_equal( :one_or_more, literal_2.quantifier.token )
137
+ end
138
+
139
+ end
@@ -34,6 +34,46 @@ class TestParserGroups < Test::Unit::TestCase
34
34
  assert_equal( false, t.expressions[0].expressions[1].x? )
35
35
  end
36
36
 
37
+ if RUBY_VERSION >= '2.0'
38
+ def test_parse_options_dau
39
+ t = RP.parse('(?dua:abc)')
40
+
41
+ assert_equal( true, t.expressions[0].d? )
42
+ assert_equal( true, t.expressions[0].a? )
43
+ assert_equal( true, t.expressions[0].u? )
44
+ end
45
+
46
+ def test_parse_nested_options_dau
47
+ t = RP.parse('(?u:a(?d:b))')
48
+
49
+ assert_equal( true, t.expressions[0].u? )
50
+ assert_equal( false, t.expressions[0].d? )
51
+ assert_equal( false, t.expressions[0].a? )
52
+
53
+ assert_equal( true, t.expressions[0].expressions[1].d? )
54
+ assert_equal( false, t.expressions[0].expressions[1].a? )
55
+ assert_equal( false, t.expressions[0].expressions[1].u? )
56
+ end
57
+
58
+ def test_parse_nested_options_da
59
+ t = RP.parse('(?di-xm:a(?da-x:b))')
60
+
61
+ assert_equal( true, t.expressions[0].d? )
62
+ assert_equal( true, t.expressions[0].i? )
63
+ assert_equal( false, t.expressions[0].m? )
64
+ assert_equal( false, t.expressions[0].x? )
65
+ assert_equal( false, t.expressions[0].a? )
66
+ assert_equal( false, t.expressions[0].u? )
67
+
68
+ assert_equal( true, t.expressions[0].expressions[1].d? )
69
+ assert_equal( true, t.expressions[0].expressions[1].a? )
70
+ assert_equal( false, t.expressions[0].expressions[1].u? )
71
+ assert_equal( false, t.expressions[0].expressions[1].x? )
72
+ assert_equal( false, t.expressions[0].expressions[1].m? )
73
+ assert_equal( false, t.expressions[0].expressions[1].i? )
74
+ end
75
+ end
76
+
37
77
  def test_parse_lookahead
38
78
  t = RP.parse('(?=abc)(?!def)', 'ruby/1.8')
39
79
 
@@ -0,0 +1,21 @@
1
+ require File.expand_path("../../helpers", __FILE__)
2
+
3
+ class ParserKeep < Test::Unit::TestCase
4
+
5
+ def test_parse_keep
6
+ regexp = /ab\Kcd/
7
+ root = RP.parse(regexp)
8
+
9
+ assert_equal( Keep::Mark, root[1].class )
10
+ assert_equal( '\\K', root[1].text )
11
+ end
12
+
13
+ def test_parse_keep_nested
14
+ regexp = /(a\\\Kb)/
15
+ root = RP.parse(regexp)
16
+
17
+ assert_equal( Keep::Mark, root[0][2].class )
18
+ assert_equal( '\\K', root[0][2].text )
19
+ end
20
+
21
+ end
@@ -1,12 +1,18 @@
1
1
  require File.expand_path("../../helpers", __FILE__)
2
2
 
3
3
  %w{
4
- anchors errors escapes groups literals meta properties
5
- quantifiers scripts sets types
4
+ anchors errors escapes free_space groups literals
5
+ meta properties quantifiers scripts sets types
6
6
  }.each do|tc|
7
7
  require File.expand_path("../test_#{tc}", __FILE__)
8
8
  end
9
9
 
10
+ if RUBY_VERSION >= '2.0.0'
11
+ %w{conditionals keep}.each do|tc|
12
+ require File.expand_path("../test_#{tc}", __FILE__)
13
+ end
14
+ end
15
+
10
16
  class TestRegexpScanner < Test::Unit::TestCase
11
17
 
12
18
  def test_scanner_returns_an_array
@@ -0,0 +1,166 @@
1
+ require File.expand_path("../../helpers", __FILE__)
2
+
3
+ class ScannerConditionals < Test::Unit::TestCase
4
+
5
+ # Basic conditional scan token tests
6
+ tests = {
7
+ /(?(1)T|F)/ => [0, :conditional, :open, '(?', 0, 2],
8
+ /(?(2)T|F)/ => [1, :conditional, :condition_open, '(', 2, 3],
9
+ /(?(3)T|F)/ => [2, :conditional, :condition, '3', 3, 4],
10
+ /(?(4)T|F)/ => [3, :conditional, :condition_close, ')', 4, 5],
11
+ /(?(5)T|F)/ => [4, :literal, :literal, 'T', 5, 6],
12
+ /(?(6)T|F)/ => [5, :conditional, :separator, '|', 6, 7],
13
+ /(?(7)T|F)/ => [6, :literal, :literal, 'F', 7, 8],
14
+ /(?(8)T|F)/ => [7, :conditional, :close, ')', 8, 9],
15
+
16
+ /(?(1)TRUE)/ => [5, :conditional, :close, ')', 9, 10],
17
+
18
+ /(?(1)TRUE|)/ => [5, :conditional, :separator, '|', 9, 10],
19
+ /(?(2)TRUE|)/ => [6, :conditional, :close, ')', 10, 11],
20
+
21
+ /(?<N>A)(?(<N>)T|F)/ => [5, :conditional, :condition, '<N>', 10, 13],
22
+ /(?'N'A)(?('N')T|F)/ => [5, :conditional, :condition, "'N'", 10, 13],
23
+ }
24
+
25
+ count = 0
26
+ tests.each do |pattern, test|
27
+ define_method "test_scan_#{test[1]}_#{test[2]}_#{count+=1}" do
28
+
29
+ tokens = RS.scan(pattern)
30
+ token = tokens[test[0]]
31
+ assert_equal( test[1,5], token )
32
+
33
+ end
34
+ end
35
+
36
+ def test_scan_conditional_nested
37
+ regexp = /(a(b(c)))(?(1)(?(2)d|(?(3)e|f))|(?(2)(?(1)g|h)))/
38
+ tokens = RS.scan(regexp)
39
+
40
+ [ [ 0, :group, :capture, '(', 0, 1],
41
+ [ 1, :literal, :literal, 'a', 1, 2],
42
+ [ 2, :group, :capture, '(', 2, 3],
43
+ [ 3, :literal, :literal, 'b', 3, 4],
44
+ [ 4, :group, :capture, '(', 4, 5],
45
+ [ 5, :literal, :literal, 'c', 5, 6],
46
+ [ 6, :group, :close, ')', 6, 7],
47
+ [ 7, :group, :close, ')', 7, 8],
48
+ [ 8, :group, :close, ')', 8, 9],
49
+ [ 9, :conditional, :open, '(?', 9, 11],
50
+ [10, :conditional, :condition_open, '(', 11, 12],
51
+ [11, :conditional, :condition, '1', 12, 13],
52
+ [12, :conditional, :condition_close, ')', 13, 14],
53
+ [13, :conditional, :open, '(?', 14, 16],
54
+ [14, :conditional, :condition_open, '(', 16, 17],
55
+ [15, :conditional, :condition, '2', 17, 18],
56
+ [16, :conditional, :condition_close, ')', 18, 19],
57
+ [17, :literal, :literal, 'd', 19, 20],
58
+ [18, :conditional, :separator, '|', 20, 21],
59
+ [19, :conditional, :open, '(?', 21, 23],
60
+ [20, :conditional, :condition_open, '(', 23, 24],
61
+ [21, :conditional, :condition, '3', 24, 25],
62
+ [22, :conditional, :condition_close, ')', 25, 26],
63
+ [23, :literal, :literal, 'e', 26, 27],
64
+ [24, :conditional, :separator, '|', 27, 28],
65
+ [25, :literal, :literal, 'f', 28, 29],
66
+ [26, :conditional, :close, ')', 29, 30],
67
+ [27, :conditional, :close, ')', 30, 31],
68
+ [28, :conditional, :separator, '|', 31, 32],
69
+ [29, :conditional, :open, '(?', 32, 34],
70
+ [30, :conditional, :condition_open, '(', 34, 35],
71
+ [31, :conditional, :condition, '2', 35, 36],
72
+ [32, :conditional, :condition_close, ')', 36, 37],
73
+ [33, :conditional, :open, '(?', 37, 39],
74
+ [34, :conditional, :condition_open, '(', 39, 40],
75
+ [35, :conditional, :condition, '1', 40, 41],
76
+ [36, :conditional, :condition_close, ')', 41, 42],
77
+ [37, :literal, :literal, 'g', 42, 43],
78
+ [38, :conditional, :separator, '|', 43, 44],
79
+ [39, :literal, :literal, 'h', 44, 45],
80
+ [40, :conditional, :close, ')', 45, 46],
81
+ [41, :conditional, :close, ')', 46, 47],
82
+ [42, :conditional, :close, ')', 47, 48]
83
+ ].each do |test|
84
+ assert_equal( test[1,5], tokens[test[0]] )
85
+ end
86
+ end
87
+
88
+ def test_scan_conditional_nested_groups
89
+ regexp = /((a)|(b)|((?(2)(c(d|e)+)?|(?(3)f|(?(4)(g|(h)(i)))))))/
90
+ tokens = RS.scan(regexp)
91
+
92
+ [ [ 0, :group, :capture, '(', 0, 1],
93
+ [ 1, :group, :capture, '(', 1, 2],
94
+ [ 2, :literal, :literal, 'a', 2, 3],
95
+ [ 3, :group, :close, ')', 3, 4],
96
+ [ 4, :meta, :alternation, '|', 4, 5],
97
+ [ 5, :group, :capture, '(', 5, 6],
98
+ [ 6, :literal, :literal, 'b', 6, 7],
99
+ [ 7, :group, :close, ')', 7, 8],
100
+ [ 8, :meta, :alternation, '|', 8, 9],
101
+ [ 9, :group, :capture, '(', 9, 10],
102
+ [10, :conditional, :open, '(?', 10, 12],
103
+ [11, :conditional, :condition_open, '(', 12, 13],
104
+ [12, :conditional, :condition, '2', 13, 14],
105
+ [13, :conditional, :condition_close, ')', 14, 15],
106
+ [14, :group, :capture, '(', 15, 16],
107
+ [15, :literal, :literal, 'c', 16, 17],
108
+ [16, :group, :capture, '(', 17, 18],
109
+ [17, :literal, :literal, 'd', 18, 19],
110
+ [18, :meta, :alternation, '|', 19, 20],
111
+ [19, :literal, :literal, 'e', 20, 21],
112
+ [20, :group, :close, ')', 21, 22],
113
+ [21, :quantifier, :one_or_more, '+', 22, 23],
114
+ [22, :group, :close, ')', 23, 24],
115
+ [23, :quantifier, :zero_or_one, '?', 24, 25],
116
+ [24, :conditional, :separator, '|', 25, 26],
117
+ [25, :conditional, :open, '(?', 26, 28],
118
+ [26, :conditional, :condition_open, '(', 28, 29],
119
+ [27, :conditional, :condition, '3', 29, 30],
120
+ [28, :conditional, :condition_close, ')', 30, 31],
121
+ [29, :literal, :literal, 'f', 31, 32],
122
+ [30, :conditional, :separator, '|', 32, 33],
123
+ [31, :conditional, :open, '(?', 33, 35],
124
+ [32, :conditional, :condition_open, '(', 35, 36],
125
+ [33, :conditional, :condition, '4', 36, 37],
126
+ [34, :conditional, :condition_close, ')', 37, 38],
127
+ [35, :group, :capture, '(', 38, 39],
128
+ [36, :literal, :literal, 'g', 39, 40],
129
+ [37, :meta, :alternation, '|', 40, 41],
130
+ [38, :group, :capture, '(', 41, 42],
131
+ [39, :literal, :literal, 'h', 42, 43],
132
+ [40, :group, :close, ')', 43, 44],
133
+ [41, :group, :capture, '(', 44, 45],
134
+ [42, :literal, :literal, 'i', 45, 46],
135
+ [43, :group, :close, ')', 46, 47],
136
+ [44, :group, :close, ')', 47, 48],
137
+ [45, :conditional, :close, ')', 48, 49],
138
+ [46, :conditional, :close, ')', 49, 50],
139
+ [47, :conditional, :close, ')', 50, 51],
140
+ [48, :group, :close, ')', 51, 52],
141
+ [49, :group, :close, ')', 52, 53]
142
+ ].each do |test|
143
+ assert_equal( test[1,5], tokens[test[0]] )
144
+ end
145
+ end
146
+
147
+ def test_scan_conditional_nested_alternation
148
+ regexp = /(a)(?(1)(b|c|d)|(e|f|g))(h)(?(2)(i|j|k)|(l|m|n))|o|p/
149
+ tokens = RS.scan(regexp)
150
+
151
+ [9, 11, 17, 19, 32, 34, 40, 42, 46, 48].each do |token|
152
+ assert_equal(:meta, tokens[token][0])
153
+ assert_equal(:alternation, tokens[token][1])
154
+ assert_equal('|', tokens[token][2])
155
+ assert_equal(1, tokens[token][4] - tokens[token][3])
156
+ end
157
+
158
+ [14, 37].each do |token|
159
+ assert_equal(:conditional, tokens[token][0])
160
+ assert_equal(:separator, tokens[token][1])
161
+ assert_equal('|', tokens[token][2])
162
+ assert_equal(1, tokens[token][4] - tokens[token][3])
163
+ end
164
+ end
165
+
166
+ end