regexp_parser 1.3.0 → 1.6.0

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 (169) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +53 -1
  3. data/Gemfile +3 -3
  4. data/README.md +10 -14
  5. data/Rakefile +3 -4
  6. data/lib/regexp_parser/expression.rb +28 -53
  7. data/lib/regexp_parser/expression/classes/backref.rb +18 -10
  8. data/lib/regexp_parser/expression/classes/conditional.rb +7 -2
  9. data/lib/regexp_parser/expression/classes/escape.rb +0 -4
  10. data/lib/regexp_parser/expression/classes/group.rb +4 -2
  11. data/lib/regexp_parser/expression/classes/keep.rb +1 -3
  12. data/lib/regexp_parser/expression/methods/match.rb +13 -0
  13. data/lib/regexp_parser/expression/methods/match_length.rb +172 -0
  14. data/lib/regexp_parser/expression/methods/options.rb +35 -0
  15. data/lib/regexp_parser/expression/methods/strfregexp.rb +0 -1
  16. data/lib/regexp_parser/expression/methods/tests.rb +6 -15
  17. data/lib/regexp_parser/expression/quantifier.rb +2 -2
  18. data/lib/regexp_parser/expression/sequence.rb +3 -6
  19. data/lib/regexp_parser/expression/sequence_operation.rb +2 -6
  20. data/lib/regexp_parser/expression/subexpression.rb +3 -5
  21. data/lib/regexp_parser/lexer.rb +30 -44
  22. data/lib/regexp_parser/parser.rb +47 -24
  23. data/lib/regexp_parser/scanner.rb +1159 -1329
  24. data/lib/regexp_parser/scanner/char_type.rl +0 -3
  25. data/lib/regexp_parser/scanner/properties/long.yml +34 -1
  26. data/lib/regexp_parser/scanner/properties/short.yml +12 -0
  27. data/lib/regexp_parser/scanner/scanner.rl +82 -190
  28. data/lib/regexp_parser/syntax/tokens.rb +2 -10
  29. data/lib/regexp_parser/syntax/tokens/unicode_property.rb +72 -21
  30. data/lib/regexp_parser/syntax/versions/2.6.0.rb +10 -0
  31. data/lib/regexp_parser/syntax/versions/2.6.2.rb +10 -0
  32. data/lib/regexp_parser/syntax/versions/2.6.3.rb +10 -0
  33. data/lib/regexp_parser/version.rb +1 -1
  34. data/regexp_parser.gemspec +3 -3
  35. data/spec/expression/base_spec.rb +94 -0
  36. data/spec/expression/clone_spec.rb +120 -0
  37. data/spec/expression/conditional_spec.rb +89 -0
  38. data/spec/expression/free_space_spec.rb +27 -0
  39. data/spec/expression/methods/match_length_spec.rb +154 -0
  40. data/spec/expression/methods/match_spec.rb +25 -0
  41. data/spec/expression/methods/strfregexp_spec.rb +224 -0
  42. data/spec/expression/methods/tests_spec.rb +99 -0
  43. data/spec/expression/methods/traverse_spec.rb +140 -0
  44. data/spec/expression/options_spec.rb +128 -0
  45. data/spec/expression/root_spec.rb +9 -0
  46. data/spec/expression/sequence_spec.rb +9 -0
  47. data/spec/expression/subexpression_spec.rb +50 -0
  48. data/spec/expression/to_h_spec.rb +26 -0
  49. data/spec/expression/to_s_spec.rb +100 -0
  50. data/spec/lexer/all_spec.rb +22 -0
  51. data/spec/lexer/conditionals_spec.rb +53 -0
  52. data/spec/lexer/escapes_spec.rb +14 -0
  53. data/spec/lexer/keep_spec.rb +10 -0
  54. data/spec/lexer/literals_spec.rb +89 -0
  55. data/spec/lexer/nesting_spec.rb +99 -0
  56. data/spec/lexer/refcalls_spec.rb +55 -0
  57. data/spec/parser/all_spec.rb +43 -0
  58. data/spec/parser/alternation_spec.rb +88 -0
  59. data/spec/parser/anchors_spec.rb +17 -0
  60. data/spec/parser/conditionals_spec.rb +179 -0
  61. data/spec/parser/errors_spec.rb +30 -0
  62. data/spec/parser/escapes_spec.rb +121 -0
  63. data/spec/parser/free_space_spec.rb +130 -0
  64. data/spec/parser/groups_spec.rb +108 -0
  65. data/spec/parser/keep_spec.rb +6 -0
  66. data/spec/parser/posix_classes_spec.rb +8 -0
  67. data/spec/parser/properties_spec.rb +115 -0
  68. data/spec/parser/quantifiers_spec.rb +51 -0
  69. data/spec/parser/refcalls_spec.rb +112 -0
  70. data/spec/parser/set/intersections_spec.rb +127 -0
  71. data/spec/parser/set/ranges_spec.rb +111 -0
  72. data/spec/parser/sets_spec.rb +178 -0
  73. data/spec/parser/types_spec.rb +18 -0
  74. data/spec/scanner/all_spec.rb +18 -0
  75. data/spec/scanner/anchors_spec.rb +21 -0
  76. data/spec/scanner/conditionals_spec.rb +128 -0
  77. data/spec/scanner/errors_spec.rb +68 -0
  78. data/spec/scanner/escapes_spec.rb +53 -0
  79. data/spec/scanner/free_space_spec.rb +133 -0
  80. data/spec/scanner/groups_spec.rb +52 -0
  81. data/spec/scanner/keep_spec.rb +10 -0
  82. data/spec/scanner/literals_spec.rb +49 -0
  83. data/spec/scanner/meta_spec.rb +18 -0
  84. data/spec/scanner/properties_spec.rb +64 -0
  85. data/spec/scanner/quantifiers_spec.rb +20 -0
  86. data/spec/scanner/refcalls_spec.rb +36 -0
  87. data/spec/scanner/sets_spec.rb +102 -0
  88. data/spec/scanner/types_spec.rb +14 -0
  89. data/spec/spec_helper.rb +15 -0
  90. data/{test → spec}/support/runner.rb +9 -8
  91. data/spec/support/shared_examples.rb +77 -0
  92. data/{test → spec}/support/warning_extractor.rb +5 -7
  93. data/spec/syntax/syntax_spec.rb +48 -0
  94. data/spec/syntax/syntax_token_map_spec.rb +23 -0
  95. data/spec/syntax/versions/1.8.6_spec.rb +17 -0
  96. data/spec/syntax/versions/1.9.1_spec.rb +10 -0
  97. data/spec/syntax/versions/1.9.3_spec.rb +9 -0
  98. data/spec/syntax/versions/2.0.0_spec.rb +13 -0
  99. data/spec/syntax/versions/2.2.0_spec.rb +9 -0
  100. data/spec/syntax/versions/aliases_spec.rb +37 -0
  101. data/spec/token/token_spec.rb +85 -0
  102. metadata +144 -143
  103. data/test/expression/test_all.rb +0 -12
  104. data/test/expression/test_base.rb +0 -90
  105. data/test/expression/test_clone.rb +0 -89
  106. data/test/expression/test_conditionals.rb +0 -113
  107. data/test/expression/test_free_space.rb +0 -35
  108. data/test/expression/test_set.rb +0 -84
  109. data/test/expression/test_strfregexp.rb +0 -230
  110. data/test/expression/test_subexpression.rb +0 -58
  111. data/test/expression/test_tests.rb +0 -99
  112. data/test/expression/test_to_h.rb +0 -59
  113. data/test/expression/test_to_s.rb +0 -104
  114. data/test/expression/test_traverse.rb +0 -161
  115. data/test/helpers.rb +0 -10
  116. data/test/lexer/test_all.rb +0 -41
  117. data/test/lexer/test_conditionals.rb +0 -127
  118. data/test/lexer/test_keep.rb +0 -24
  119. data/test/lexer/test_literals.rb +0 -130
  120. data/test/lexer/test_nesting.rb +0 -132
  121. data/test/lexer/test_refcalls.rb +0 -56
  122. data/test/parser/set/test_intersections.rb +0 -127
  123. data/test/parser/set/test_ranges.rb +0 -111
  124. data/test/parser/test_all.rb +0 -64
  125. data/test/parser/test_alternation.rb +0 -92
  126. data/test/parser/test_anchors.rb +0 -34
  127. data/test/parser/test_conditionals.rb +0 -187
  128. data/test/parser/test_errors.rb +0 -63
  129. data/test/parser/test_escapes.rb +0 -134
  130. data/test/parser/test_free_space.rb +0 -139
  131. data/test/parser/test_groups.rb +0 -289
  132. data/test/parser/test_keep.rb +0 -21
  133. data/test/parser/test_posix_classes.rb +0 -27
  134. data/test/parser/test_properties.rb +0 -133
  135. data/test/parser/test_quantifiers.rb +0 -301
  136. data/test/parser/test_refcalls.rb +0 -186
  137. data/test/parser/test_sets.rb +0 -179
  138. data/test/parser/test_types.rb +0 -50
  139. data/test/scanner/test_all.rb +0 -38
  140. data/test/scanner/test_anchors.rb +0 -38
  141. data/test/scanner/test_conditionals.rb +0 -184
  142. data/test/scanner/test_errors.rb +0 -91
  143. data/test/scanner/test_escapes.rb +0 -56
  144. data/test/scanner/test_free_space.rb +0 -200
  145. data/test/scanner/test_groups.rb +0 -79
  146. data/test/scanner/test_keep.rb +0 -35
  147. data/test/scanner/test_literals.rb +0 -89
  148. data/test/scanner/test_meta.rb +0 -40
  149. data/test/scanner/test_properties.rb +0 -312
  150. data/test/scanner/test_quantifiers.rb +0 -37
  151. data/test/scanner/test_refcalls.rb +0 -52
  152. data/test/scanner/test_scripts.rb +0 -53
  153. data/test/scanner/test_sets.rb +0 -119
  154. data/test/scanner/test_types.rb +0 -35
  155. data/test/scanner/test_unicode_blocks.rb +0 -30
  156. data/test/support/disable_autotest.rb +0 -8
  157. data/test/syntax/test_all.rb +0 -6
  158. data/test/syntax/test_syntax.rb +0 -61
  159. data/test/syntax/test_syntax_token_map.rb +0 -25
  160. data/test/syntax/versions/test_1.8.rb +0 -55
  161. data/test/syntax/versions/test_1.9.1.rb +0 -36
  162. data/test/syntax/versions/test_1.9.3.rb +0 -32
  163. data/test/syntax/versions/test_2.0.0.rb +0 -37
  164. data/test/syntax/versions/test_2.2.0.rb +0 -32
  165. data/test/syntax/versions/test_aliases.rb +0 -129
  166. data/test/syntax/versions/test_all.rb +0 -5
  167. data/test/test_all.rb +0 -5
  168. data/test/token/test_all.rb +0 -2
  169. data/test/token/test_token.rb +0 -107
@@ -0,0 +1,140 @@
1
+ require 'spec_helper'
2
+
3
+ RSpec.describe('Subexpression#traverse') do
4
+ specify('Subexpression#traverse') do
5
+ root = RP.parse(/a(b(c(d)))|g[h-i]j|klmn/)
6
+
7
+ enters = 0
8
+ visits = 0
9
+ exits = 0
10
+
11
+ root.traverse do |event, _exp, _index|
12
+ enters = (enters + 1) if event == :enter
13
+ visits = (visits + 1) if event == :visit
14
+ exits = (exits + 1) if event == :exit
15
+ end
16
+
17
+ expect(enters).to eq 9
18
+ expect(enters).to eq exits
19
+
20
+ expect(visits).to eq 9
21
+ end
22
+
23
+ specify('Subexpression#traverse including self') do
24
+ root = RP.parse(/a(b(c(d)))|g[h-i]j|klmn/)
25
+
26
+ enters = 0
27
+ visits = 0
28
+ exits = 0
29
+
30
+ root.traverse(true) do |event, _exp, _index|
31
+ enters = (enters + 1) if event == :enter
32
+ visits = (visits + 1) if event == :visit
33
+ exits = (exits + 1) if event == :exit
34
+ end
35
+
36
+ expect(enters).to eq 10
37
+ expect(enters).to eq exits
38
+
39
+ expect(visits).to eq 9
40
+ end
41
+
42
+ specify('Subexpression#walk alias') do
43
+ root = RP.parse(/abc/)
44
+
45
+ expect(root).to respond_to(:walk)
46
+ end
47
+
48
+ specify('Subexpression#each_expression') do
49
+ root = RP.parse(/a(?x:b(c))|g[h-k]/)
50
+
51
+ count = 0
52
+ root.each_expression { count += 1 }
53
+
54
+ expect(count).to eq 13
55
+ end
56
+
57
+ specify('Subexpression#each_expression including self') do
58
+ root = RP.parse(/a(?x:b(c))|g[h-k]/)
59
+
60
+ count = 0
61
+ root.each_expression(true) { count += 1 }
62
+
63
+ expect(count).to eq 14
64
+ end
65
+
66
+ specify('Subexpression#each_expression indices') do
67
+ root = RP.parse(/a(b)c/)
68
+
69
+ indices = []
70
+ root.each_expression { |_exp, index| (indices << index) }
71
+
72
+ expect(indices).to eq [0, 1, 0, 2]
73
+ end
74
+
75
+ specify('Subexpression#each_expression indices including self') do
76
+ root = RP.parse(/a(b)c/)
77
+
78
+ indices = []
79
+ root.each_expression(true) { |_exp, index| (indices << index) }
80
+
81
+ expect(indices).to eq [0, 0, 1, 0, 2]
82
+ end
83
+
84
+ specify('Subexpression#flat_map without block') do
85
+ root = RP.parse(/a(b([c-e]+))?/)
86
+
87
+ array = root.flat_map
88
+
89
+ expect(array).to be_instance_of(Array)
90
+ expect(array.length).to eq 8
91
+
92
+ array.each do |item|
93
+ expect(item).to be_instance_of(Array)
94
+ expect(item.length).to eq 2
95
+ expect(item.first).to be_a(Regexp::Expression::Base)
96
+ expect(item.last).to be_a(Integer)
97
+ end
98
+ end
99
+
100
+ specify('Subexpression#flat_map without block including self') do
101
+ root = RP.parse(/a(b([c-e]+))?/)
102
+
103
+ array = root.flat_map(true)
104
+
105
+ expect(array).to be_instance_of(Array)
106
+ expect(array.length).to eq 9
107
+ end
108
+
109
+ specify('Subexpression#flat_map indices') do
110
+ root = RP.parse(/a(b([c-e]+))?f*g/)
111
+
112
+ indices = root.flat_map { |_exp, index| index }
113
+
114
+ expect(indices).to eq [0, 1, 0, 1, 0, 0, 0, 1, 2, 3]
115
+ end
116
+
117
+ specify('Subexpression#flat_map indices including self') do
118
+ root = RP.parse(/a(b([c-e]+))?f*g/)
119
+
120
+ indices = root.flat_map(true) { |_exp, index| index }
121
+
122
+ expect(indices).to eq [0, 0, 1, 0, 1, 0, 0, 0, 1, 2, 3]
123
+ end
124
+
125
+ specify('Subexpression#flat_map expressions') do
126
+ root = RP.parse(/a(b(c(d)))/)
127
+
128
+ levels = root.flat_map { |exp, _index| [exp.level, exp.text] if exp.terminal? }.compact
129
+
130
+ expect(levels).to eq [[0, 'a'], [1, 'b'], [2, 'c'], [3, 'd']]
131
+ end
132
+
133
+ specify('Subexpression#flat_map expressions including self') do
134
+ root = RP.parse(/a(b(c(d)))/)
135
+
136
+ levels = root.flat_map(true) { |exp, _index| [exp.level, exp.to_s] }.compact
137
+
138
+ expect(levels).to eq [[nil, 'a(b(c(d)))'], [0, 'a'], [0, '(b(c(d)))'], [1, 'b'], [1, '(c(d))'], [2, 'c'], [2, '(d)'], [3, 'd']]
139
+ end
140
+ end
@@ -0,0 +1,128 @@
1
+ require 'spec_helper'
2
+
3
+ RSpec.describe('Expression#options') do
4
+ it 'returns a hash of options/flags that affect the expression' do
5
+ exp = RP.parse(/a/ix)[0]
6
+ expect(exp).to be_a Literal
7
+ expect(exp.options).to eq(i: true, x: true)
8
+ end
9
+
10
+ it 'includes options that are locally enabled via special groups' do
11
+ exp = RP.parse(/(?x)(?m:a)/i)[1][0]
12
+ expect(exp).to be_a Literal
13
+ expect(exp.options).to eq(i: true, m: true, x: true)
14
+ end
15
+
16
+ it 'excludes locally disabled options' do
17
+ exp = RP.parse(/(?x)(?-im:a)/i)[1][0]
18
+ expect(exp).to be_a Literal
19
+ expect(exp.options).to eq(x: true)
20
+ end
21
+
22
+ it 'gives correct precedence to negative options' do
23
+ # Negative options have precedence. E.g. /(?i-i)a/ is case-sensitive.
24
+ regexp = /(?i-i:a)/
25
+ expect(regexp).to match 'a'
26
+ expect(regexp).not_to match 'A'
27
+
28
+ exp = RP.parse(regexp)[0][0]
29
+ expect(exp).to be_a Literal
30
+ expect(exp.options).to eq({})
31
+ end
32
+
33
+ it 'correctly handles multiple negative option parts' do
34
+ regexp = /(?--m--mx--) . /mx
35
+ expect(regexp).to match ' . '
36
+ expect(regexp).not_to match '.'
37
+ expect(regexp).not_to match "\n"
38
+
39
+ exp = RP.parse(regexp)[2]
40
+ expect(exp.options).to eq({})
41
+ end
42
+
43
+ it 'gives correct precedence when encountering multiple encoding flags' do
44
+ # Any encoding flag overrides all previous encoding flags. If there are
45
+ # multiple encoding flags in an options string, the last one wins.
46
+ # E.g. /(?dau)\w/ matches UTF8 chars but /(?dua)\w/ only ASCII chars.
47
+ regexp1 = /(?dau)\w/
48
+ regexp2 = /(?dua)\w/
49
+ expect(regexp1).to match 'ü'
50
+ expect(regexp2).not_to match 'ü'
51
+
52
+ exp1 = RP.parse(regexp1)[1]
53
+ exp2 = RP.parse(regexp2)[1]
54
+ expect(exp1.options).to eq(u: true)
55
+ expect(exp2.options).to eq(a: true)
56
+ end
57
+
58
+ it 'is accessible via shortcuts' do
59
+ exp = Root.build
60
+
61
+ expect { exp.options[:i] = true }
62
+ .to change { exp.i? }.from(false).to(true)
63
+ .and change { exp.ignore_case? }.from(false).to(true)
64
+ .and change { exp.case_insensitive? }.from(false).to(true)
65
+
66
+ expect { exp.options[:m] = true }
67
+ .to change { exp.m? }.from(false).to(true)
68
+ .and change { exp.multiline? }.from(false).to(true)
69
+
70
+ expect { exp.options[:x] = true }
71
+ .to change { exp.x? }.from(false).to(true)
72
+ .and change { exp.extended? }.from(false).to(true)
73
+ .and change { exp.free_spacing? }.from(false).to(true)
74
+
75
+ expect { exp.options[:a] = true }
76
+ .to change { exp.a? }.from(false).to(true)
77
+ .and change { exp.ascii_classes? }.from(false).to(true)
78
+
79
+ expect { exp.options[:d] = true }
80
+ .to change { exp.d? }.from(false).to(true)
81
+ .and change { exp.default_classes? }.from(false).to(true)
82
+
83
+ expect { exp.options[:u] = true }
84
+ .to change { exp.u? }.from(false).to(true)
85
+ .and change { exp.unicode_classes? }.from(false).to(true)
86
+ end
87
+
88
+ RSpec.shared_examples '#options' do |regexp, klass, at: []|
89
+ it "works for expression class #{klass}" do
90
+ exp = RP.parse(/#{regexp.source}/i).dig(*at)
91
+ expect(exp).to be_a(klass)
92
+ expect(exp).to be_i
93
+ expect(exp).not_to be_x
94
+ end
95
+ end
96
+
97
+ include_examples '#options', //, Root
98
+ include_examples '#options', /a/, Literal, at: [0]
99
+ include_examples '#options', /\A/, Anchor::Base, at: [0]
100
+ include_examples '#options', /\d/, CharacterType::Base, at: [0]
101
+ include_examples '#options', /\n/, EscapeSequence::Base, at: [0]
102
+ include_examples '#options', /\K/, Keep::Mark, at: [0]
103
+ include_examples '#options', /./, CharacterType::Any, at: [0]
104
+ include_examples '#options', /(a)/, Group::Base, at: [0]
105
+ include_examples '#options', /(a)/, Literal, at: [0, 0]
106
+ include_examples '#options', /(?=a)/, Assertion::Base, at: [0]
107
+ include_examples '#options', /(?=a)/, Literal, at: [0, 0]
108
+ include_examples '#options', /(a|b)/, Group::Base, at: [0]
109
+ include_examples '#options', /(a|b)/, Alternation, at: [0, 0]
110
+ include_examples '#options', /(a|b)/, Alternative, at: [0, 0, 0]
111
+ include_examples '#options', /(a|b)/, Literal, at: [0, 0, 0, 0]
112
+ include_examples '#options', /(a)\1/, Backreference::Base, at: [1]
113
+ include_examples '#options', /(a)\k<1>/, Backreference::Number, at: [1]
114
+ include_examples '#options', /(a)\g<1>/, Backreference::NumberCall, at: [1]
115
+ include_examples '#options', /[a]/, CharacterSet, at: [0]
116
+ include_examples '#options', /[a]/, Literal, at: [0, 0]
117
+ include_examples '#options', /[a-z]/, CharacterSet::Range, at: [0, 0]
118
+ include_examples '#options', /[a-z]/, Literal, at: [0, 0, 0]
119
+ include_examples '#options', /[a&&z]/, CharacterSet::Intersection, at: [0, 0]
120
+ include_examples '#options', /[a&&z]/, CharacterSet::IntersectedSequence, at: [0, 0, 0]
121
+ include_examples '#options', /[a&&z]/, Literal, at: [0, 0, 0, 0]
122
+ include_examples '#options', /[[:ascii:]]/, PosixClass, at: [0, 0]
123
+ include_examples '#options', /\p{word}/, UnicodeProperty::Base, at: [0]
124
+ include_examples '#options', /(a)(?(1)b|c)/, Conditional::Expression, at: [1]
125
+ include_examples '#options', /(a)(?(1)b|c)/, Conditional::Condition, at: [1, 0]
126
+ include_examples '#options', /(a)(?(1)b|c)/, Conditional::Branch, at: [1, 1]
127
+ include_examples '#options', /(a)(?(1)b|c)/, Literal, at: [1, 1, 0]
128
+ end
@@ -0,0 +1,9 @@
1
+ require 'spec_helper'
2
+
3
+ RSpec.describe(Regexp::Expression::Root) do
4
+ describe('#initialize') do
5
+ it 'supports the old, nonstandard arity for backwards compatibility' do
6
+ expect { Root.new }.to output.to_stderr
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,9 @@
1
+ require 'spec_helper'
2
+
3
+ RSpec.describe(Regexp::Expression::Sequence) do
4
+ describe('#initialize') do
5
+ it 'supports the old, nonstandard arity for backwards compatibility' do
6
+ expect { Sequence.new(0, 0, 0) }.to output.to_stderr
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,50 @@
1
+ require 'spec_helper'
2
+
3
+ RSpec.describe(Regexp::Expression::Subexpression) do
4
+ specify('#ts, #te') do
5
+ regx = /abcd|ghij|klmn|pqur/
6
+ root = RP.parse(regx)
7
+
8
+ alt = root.first
9
+
10
+ { 0 => [0, 4], 1 => [5, 9], 2 => [10, 14], 3 => [15, 19] }.each do |index, span|
11
+ sequence = alt[index]
12
+
13
+ expect(sequence.ts).to eq span[0]
14
+ expect(sequence.te).to eq span[1]
15
+ end
16
+ end
17
+
18
+ specify('#nesting_level') do
19
+ root = RP.parse(/a(b(\d|[ef-g[h]]))/)
20
+
21
+ tests = {
22
+ 'a' => 1,
23
+ 'b' => 2,
24
+ '\d|[ef-g[h]]' => 3, # alternation
25
+ '\d' => 4, # first alternative
26
+ '[ef-g[h]]' => 4, # second alternative
27
+ 'e' => 5,
28
+ 'f-g' => 5,
29
+ 'f' => 6,
30
+ 'g' => 6,
31
+ 'h' => 6,
32
+ }
33
+
34
+ root.each_expression do |exp|
35
+ next unless expected_nesting_level = tests.delete(exp.to_s)
36
+ expect(expected_nesting_level).to eq exp.nesting_level
37
+ end
38
+
39
+ expect(tests).to be_empty
40
+ end
41
+
42
+ specify('#dig') do
43
+ root = RP.parse(/(((a)))/)
44
+
45
+ expect(root.dig(0).to_s).to eq '(((a)))'
46
+ expect(root.dig(0, 0, 0, 0).to_s).to eq 'a'
47
+ expect(root.dig(0, 0, 0, 0, 0)).to be_nil
48
+ expect(root.dig(3, 7)).to be_nil
49
+ end
50
+ end
@@ -0,0 +1,26 @@
1
+ require 'spec_helper'
2
+
3
+ RSpec.describe('Expression#to_h') do
4
+ specify('Root#to_h') do
5
+ root = RP.parse('abc')
6
+
7
+ hash = root.to_h
8
+
9
+ expect(token: :root, type: :expression, text: 'abc', starts_at: 0, length: 3, quantifier: nil, options: {}, level: nil, set_level: nil, conditional_level: nil, expressions: [{ token: :literal, type: :literal, text: 'abc', starts_at: 0, length: 3, quantifier: nil, options: {}, level: 0, set_level: 0, conditional_level: 0 }]).to eq hash
10
+ end
11
+
12
+ specify('Quantifier#to_h') do
13
+ root = RP.parse('a{2,4}')
14
+ exp = root.expressions.at(0)
15
+
16
+ hash = exp.quantifier.to_h
17
+
18
+ expect(max: 4, min: 2, mode: :greedy, text: '{2,4}', token: :interval).to eq hash
19
+ end
20
+
21
+ specify('Conditional#to_h') do
22
+ root = RP.parse('(?<A>a)(?(<A>)b|c)', 'ruby/2.0')
23
+
24
+ expect { root.to_h }.not_to(raise_error)
25
+ end
26
+ end
@@ -0,0 +1,100 @@
1
+ require 'spec_helper'
2
+
3
+ RSpec.describe('Expression#to_s') do
4
+ specify('literal alternation') do
5
+ pattern = 'abcd|ghij|klmn|pqur'
6
+
7
+ expect(RP.parse(pattern).to_s).to eq pattern
8
+ end
9
+
10
+ specify('quantified alternations') do
11
+ pattern = '(?:a?[b]+(c){2}|d+[e]*(f)?)|(?:g+[h]?(i){2,3}|j*[k]{3,5}(l)?)'
12
+
13
+ expect(RP.parse(pattern).to_s).to eq pattern
14
+ end
15
+
16
+ specify('quantified sets') do
17
+ pattern = '[abc]+|[^def]{3,6}'
18
+
19
+ expect(RP.parse(pattern).to_s).to eq pattern
20
+ end
21
+
22
+ specify('property sets') do
23
+ pattern = '[\\a\\b\\p{Lu}\\P{Z}\\c\\d]+'
24
+
25
+ expect(RP.parse(pattern, 'ruby/1.9').to_s).to eq pattern
26
+ end
27
+
28
+ specify('groups') do
29
+ pattern = "(a(?>b(?:c(?<n>d(?'N'e)??f)+g)*+h)*i)++"
30
+
31
+ expect(RP.parse(pattern, 'ruby/1.9').to_s).to eq pattern
32
+ end
33
+
34
+ specify('assertions') do
35
+ pattern = '(a+(?=b+(?!c+(?<=d+(?<!e+)?f+)?g+)?h+)?i+)?'
36
+
37
+ expect(RP.parse(pattern, 'ruby/1.9').to_s).to eq pattern
38
+ end
39
+
40
+ specify('comments') do
41
+ pattern = '(?#start)a(?#middle)b(?#end)'
42
+
43
+ expect(RP.parse(pattern).to_s).to eq pattern
44
+ end
45
+
46
+ specify('options') do
47
+ pattern = '(?mix:start)a(?-mix:middle)b(?i-mx:end)'
48
+
49
+ expect(RP.parse(pattern).to_s).to eq pattern
50
+ end
51
+
52
+ specify('url') do
53
+ pattern = ('(^$)|(^(http|https):\\/\\/[a-z0-9]+([\\-\\.]{1}[a-z0-9]+)*' + '\\.[a-z]{2,5}(([0-9]{1,5})?\\/.*)?$)')
54
+
55
+ expect(RP.parse(pattern).to_s).to eq pattern
56
+ end
57
+
58
+ specify('multiline source') do
59
+ multiline = /
60
+ \A
61
+ a? # One letter
62
+ b{2,5} # Another one
63
+ [c-g]+ # A set
64
+ \z
65
+ /x
66
+
67
+ expect(RP.parse(multiline).to_s).to eq multiline.source
68
+ end
69
+
70
+ specify('multiline #to_s') do
71
+ multiline = /
72
+ \A
73
+ a? # One letter
74
+ b{2,5} # Another one
75
+ [c-g]+ # A set
76
+ \z
77
+ /x
78
+
79
+ expect(RP.parse(multiline.to_s).to_s).to eq multiline.to_s
80
+ end
81
+
82
+ # Free spacing expressions that use spaces between quantifiers and their
83
+ # targets do not produce identical results due to the way quantifiers are
84
+ # applied to expressions (members, not nodes) and the merging of consecutive
85
+ # space nodes. This tests that they produce equivalent results.
86
+ specify('multiline equivalence') do
87
+ multiline = /
88
+ \A
89
+ a ? # One letter
90
+ b {2,5} # Another one
91
+ [c-g] + # A set
92
+ \z
93
+ /x
94
+
95
+ str = 'bbbcged'
96
+ root = RP.parse(multiline)
97
+
98
+ expect(Regexp.new(root.to_s, Regexp::EXTENDED).match(str)[0]).to eq multiline.match(str)[0]
99
+ end
100
+ end