regexp_parser 2.7.0 → 2.12.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 (95) hide show
  1. checksums.yaml +4 -4
  2. data/Gemfile +7 -5
  3. data/LICENSE +1 -1
  4. data/Rakefile +5 -3
  5. data/lib/regexp_parser/error.rb +2 -0
  6. data/lib/regexp_parser/expression/base.rb +2 -7
  7. data/lib/regexp_parser/expression/classes/alternation.rb +3 -1
  8. data/lib/regexp_parser/expression/classes/anchor.rb +2 -0
  9. data/lib/regexp_parser/expression/classes/backreference.rb +7 -26
  10. data/lib/regexp_parser/expression/classes/character_set/intersection.rb +2 -0
  11. data/lib/regexp_parser/expression/classes/character_set/range.rb +4 -7
  12. data/lib/regexp_parser/expression/classes/character_set.rb +6 -8
  13. data/lib/regexp_parser/expression/classes/character_type.rb +2 -0
  14. data/lib/regexp_parser/expression/classes/conditional.rb +4 -28
  15. data/lib/regexp_parser/expression/classes/escape_sequence.rb +29 -96
  16. data/lib/regexp_parser/expression/classes/free_space.rb +5 -1
  17. data/lib/regexp_parser/expression/classes/group.rb +2 -22
  18. data/lib/regexp_parser/expression/classes/keep.rb +3 -1
  19. data/lib/regexp_parser/expression/classes/literal.rb +2 -0
  20. data/lib/regexp_parser/expression/classes/posix_class.rb +7 -5
  21. data/lib/regexp_parser/expression/classes/root.rb +2 -0
  22. data/lib/regexp_parser/expression/classes/unicode_property.rb +13 -11
  23. data/lib/regexp_parser/expression/methods/construct.rb +4 -4
  24. data/lib/regexp_parser/expression/methods/escape_sequence_char.rb +7 -0
  25. data/lib/regexp_parser/expression/methods/escape_sequence_codepoint.rb +76 -0
  26. data/lib/regexp_parser/expression/methods/human_name.rb +2 -0
  27. data/lib/regexp_parser/expression/methods/match.rb +2 -0
  28. data/lib/regexp_parser/expression/methods/match_length.rb +2 -0
  29. data/lib/regexp_parser/expression/methods/negative.rb +22 -0
  30. data/lib/regexp_parser/expression/methods/options.rb +2 -0
  31. data/lib/regexp_parser/expression/methods/parts.rb +25 -0
  32. data/lib/regexp_parser/expression/methods/printing.rb +28 -0
  33. data/lib/regexp_parser/expression/methods/referenced_expressions.rb +30 -0
  34. data/lib/regexp_parser/expression/methods/strfregexp.rb +2 -0
  35. data/lib/regexp_parser/expression/methods/tests.rb +42 -3
  36. data/lib/regexp_parser/expression/methods/traverse.rb +35 -20
  37. data/lib/regexp_parser/expression/quantifier.rb +33 -18
  38. data/lib/regexp_parser/expression/sequence.rb +7 -9
  39. data/lib/regexp_parser/expression/sequence_operation.rb +6 -9
  40. data/lib/regexp_parser/expression/shared.rb +40 -24
  41. data/lib/regexp_parser/expression/subexpression.rb +22 -18
  42. data/lib/regexp_parser/expression.rb +39 -31
  43. data/lib/regexp_parser/lexer.rb +17 -7
  44. data/lib/regexp_parser/parser.rb +101 -95
  45. data/lib/regexp_parser/scanner/errors/premature_end_error.rb +10 -0
  46. data/lib/regexp_parser/scanner/errors/scanner_error.rb +8 -0
  47. data/lib/regexp_parser/scanner/errors/validation_error.rb +65 -0
  48. data/lib/regexp_parser/scanner/properties/long.csv +61 -0
  49. data/lib/regexp_parser/scanner/properties/short.csv +15 -0
  50. data/lib/regexp_parser/scanner/property.rl +1 -1
  51. data/lib/regexp_parser/scanner/scanner.rl +90 -140
  52. data/lib/regexp_parser/scanner.rb +1336 -1450
  53. data/lib/regexp_parser/syntax/any.rb +2 -0
  54. data/lib/regexp_parser/syntax/base.rb +2 -0
  55. data/lib/regexp_parser/syntax/token/anchor.rb +5 -3
  56. data/lib/regexp_parser/syntax/token/assertion.rb +4 -2
  57. data/lib/regexp_parser/syntax/token/backreference.rb +11 -6
  58. data/lib/regexp_parser/syntax/token/character_set.rb +6 -1
  59. data/lib/regexp_parser/syntax/token/character_type.rb +6 -4
  60. data/lib/regexp_parser/syntax/token/conditional.rb +5 -3
  61. data/lib/regexp_parser/syntax/token/escape.rb +12 -8
  62. data/lib/regexp_parser/syntax/token/group.rb +8 -6
  63. data/lib/regexp_parser/syntax/token/keep.rb +3 -1
  64. data/lib/regexp_parser/syntax/token/meta.rb +11 -2
  65. data/lib/regexp_parser/syntax/token/posix_class.rb +4 -2
  66. data/lib/regexp_parser/syntax/token/quantifier.rb +8 -6
  67. data/lib/regexp_parser/syntax/token/unicode_property.rb +164 -70
  68. data/lib/regexp_parser/syntax/token/virtual.rb +13 -0
  69. data/lib/regexp_parser/syntax/token.rb +18 -16
  70. data/lib/regexp_parser/syntax/version_lookup.rb +4 -2
  71. data/lib/regexp_parser/syntax/versions/1.8.6.rb +2 -0
  72. data/lib/regexp_parser/syntax/versions/1.9.1.rb +2 -0
  73. data/lib/regexp_parser/syntax/versions/1.9.3.rb +2 -0
  74. data/lib/regexp_parser/syntax/versions/2.0.0.rb +2 -0
  75. data/lib/regexp_parser/syntax/versions/2.2.0.rb +2 -0
  76. data/lib/regexp_parser/syntax/versions/2.3.0.rb +2 -0
  77. data/lib/regexp_parser/syntax/versions/2.4.0.rb +2 -0
  78. data/lib/regexp_parser/syntax/versions/2.4.1.rb +2 -0
  79. data/lib/regexp_parser/syntax/versions/2.5.0.rb +2 -0
  80. data/lib/regexp_parser/syntax/versions/2.6.0.rb +2 -0
  81. data/lib/regexp_parser/syntax/versions/2.6.2.rb +2 -0
  82. data/lib/regexp_parser/syntax/versions/2.6.3.rb +2 -0
  83. data/lib/regexp_parser/syntax/versions/3.1.0.rb +2 -0
  84. data/lib/regexp_parser/syntax/versions/3.2.0.rb +2 -0
  85. data/lib/regexp_parser/syntax/versions/3.5.0.rb +4 -0
  86. data/lib/regexp_parser/syntax/versions/4.0.0.rb +4 -0
  87. data/lib/regexp_parser/syntax/versions.rb +3 -1
  88. data/lib/regexp_parser/syntax.rb +3 -1
  89. data/lib/regexp_parser/token.rb +2 -0
  90. data/lib/regexp_parser/version.rb +3 -1
  91. data/lib/regexp_parser.rb +8 -6
  92. data/regexp_parser.gemspec +7 -5
  93. metadata +19 -11
  94. data/CHANGELOG.md +0 -632
  95. data/README.md +0 -503
@@ -0,0 +1,76 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Regexp::Expression::EscapeSequence
4
+ AsciiEscape.class_eval { def codepoint; 0x1B end }
5
+ Backspace.class_eval { def codepoint; 0x8 end }
6
+ Bell.class_eval { def codepoint; 0x7 end }
7
+ FormFeed.class_eval { def codepoint; 0xC end }
8
+ Newline.class_eval { def codepoint; 0xA end }
9
+ Return.class_eval { def codepoint; 0xD end }
10
+ Tab.class_eval { def codepoint; 0x9 end }
11
+ VerticalTab.class_eval { def codepoint; 0xB end }
12
+
13
+ Literal.class_eval { def codepoint; text[1].ord end }
14
+
15
+ Octal.class_eval { def codepoint; text[/\d+/].to_i(8) end }
16
+
17
+ Hex.class_eval { def codepoint; text[/\h+/].hex end }
18
+ Codepoint.class_eval { def codepoint; text[/\h+/].hex end }
19
+
20
+ UTF8Hex.class_eval do
21
+ def codepoint
22
+ text.scan(/\h+/).map(&:hex).pack('C*').force_encoding('utf-8').ord
23
+ end
24
+ end
25
+
26
+ CodepointList.class_eval do
27
+ # Maybe this should be a unique top-level expression class?
28
+ def char
29
+ raise NoMethodError, 'CodepointList responds only to #chars'
30
+ end
31
+
32
+ def codepoint
33
+ raise NoMethodError, 'CodepointList responds only to #codepoints'
34
+ end
35
+
36
+ def chars
37
+ codepoints.map { |cp| cp.chr('utf-8') }
38
+ end
39
+
40
+ def codepoints
41
+ text.scan(/\h+/).map(&:hex)
42
+ end
43
+ end
44
+
45
+ AbstractMetaControlSequence.class_eval do
46
+ private
47
+
48
+ def control_sequence_to_s(control_sequence)
49
+ five_lsb = control_sequence.unpack('B*').first[-5..-1]
50
+ ["000#{five_lsb}"].pack('B*')
51
+ end
52
+
53
+ def meta_char_to_codepoint(meta_char)
54
+ byte_value = meta_char.ord
55
+ byte_value < 128 ? byte_value + 128 : byte_value
56
+ end
57
+ end
58
+
59
+ Control.class_eval do
60
+ def codepoint
61
+ control_sequence_to_s(text).ord
62
+ end
63
+ end
64
+
65
+ Meta.class_eval do
66
+ def codepoint
67
+ meta_char_to_codepoint(text[-1])
68
+ end
69
+ end
70
+
71
+ MetaControl.class_eval do
72
+ def codepoint
73
+ meta_char_to_codepoint(control_sequence_to_s(text))
74
+ end
75
+ end
76
+ end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Regexp::Expression
2
4
  module Shared
3
5
  # default implementation, e.g. "atomic group", "hex escape", "word type", ..
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Regexp::Expression
2
4
  class Base
3
5
  def match?(string)
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  class Regexp::MatchLength
2
4
  include Enumerable
3
5
 
@@ -0,0 +1,22 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Regexp::Expression
4
+ module Shared
5
+ def negative?
6
+ false
7
+ end
8
+
9
+ # not an alias so as to respect overrides of #negative?
10
+ def negated?
11
+ negative?
12
+ end
13
+ end
14
+
15
+ Anchor::NonWordBoundary.class_eval { def negative?; true end }
16
+ Assertion::NegativeLookahead.class_eval { def negative?; true end }
17
+ Assertion::NegativeLookbehind.class_eval { def negative?; true end }
18
+ CharacterSet.class_eval { def negative?; negative end }
19
+ CharacterType::Base.class_eval { def negative?; token.to_s.start_with?('non') end }
20
+ PosixClass.class_eval { def negative?; type == :nonposixclass end }
21
+ UnicodeProperty::Base.class_eval { def negative?; type == :nonproperty end }
22
+ end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Regexp::Expression
2
4
  class Base
3
5
  def multiline?
@@ -0,0 +1,25 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Regexp::Expression
4
+ module Shared
5
+ # default implementation
6
+ def parts
7
+ [text.dup]
8
+ end
9
+
10
+ private
11
+
12
+ def intersperse(expressions, separator)
13
+ expressions.flat_map { |exp| [exp, separator] }.slice(0...-1)
14
+ end
15
+ end
16
+
17
+ CharacterSet.class_eval { def parts; ["#{text}#{'^' if negated?}", *expressions, ']'] end }
18
+ CharacterSet::Range.class_eval { def parts; intersperse(expressions, text.dup) end }
19
+ Conditional::Expression.class_eval { def parts; [text.dup, condition, *intersperse(branches, '|'), ')'] end }
20
+ Group::Base.class_eval { def parts; [text.dup, *expressions, ')'] end }
21
+ Group::Passive.class_eval { def parts; implicit? ? expressions : super end }
22
+ Group::Comment.class_eval { def parts; [text.dup] end }
23
+ Subexpression.class_eval { def parts; expressions end }
24
+ SequenceOperation.class_eval { def parts; intersperse(expressions, text.dup) end }
25
+ end
@@ -0,0 +1,28 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Regexp::Expression
4
+ module Shared
5
+ def inspect
6
+ [
7
+ "#<#{self.class}",
8
+ pretty_print_instance_variables.map { |v| " #{v}=#{instance_variable_get(v).inspect}" },
9
+ ">"
10
+ ].join
11
+ end
12
+
13
+ # Make pretty-print work despite #inspect implementation.
14
+ def pretty_print(q)
15
+ q.pp_object(self)
16
+ end
17
+
18
+ # Called by pretty_print (ruby/pp) and #inspect.
19
+ def pretty_print_instance_variables
20
+ [
21
+ (:@text unless text.to_s.empty?),
22
+ (:@quantifier if quantified?),
23
+ (:@options unless options.empty?),
24
+ (:@expressions unless terminal?),
25
+ ].compact
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,30 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Regexp::Expression
4
+ module ReferencedExpressions
5
+ attr_accessor :referenced_expressions
6
+
7
+ def referenced_expression
8
+ referenced_expressions && referenced_expressions.first
9
+ end
10
+
11
+ def initialize_copy(orig)
12
+ exp_id = [self.class, self.starts_at]
13
+
14
+ # prevent infinite recursion for recursive subexp calls
15
+ copied = self.class.instance_eval { @copied_ref_exps ||= {} }
16
+ self.referenced_expressions =
17
+ if copied[exp_id]
18
+ orig.referenced_expressions
19
+ else
20
+ copied[exp_id] = true
21
+ orig.referenced_expressions && orig.referenced_expressions.map(&:dup)
22
+ end
23
+ copied.clear
24
+
25
+ super
26
+ end
27
+ end
28
+
29
+ Base.include ReferencedExpressions
30
+ end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Regexp::Expression
2
4
  class Base
3
5
 
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Regexp::Expression
2
4
  module Shared
3
5
 
@@ -95,12 +97,49 @@ module Regexp::Expression
95
97
  end
96
98
 
97
99
  # Deep-compare two expressions for equality.
100
+ #
101
+ # When changing the conditions, please make sure to update
102
+ # #pretty_print_instance_variables so that it includes all relevant values.
98
103
  def ==(other)
99
- other.class == self.class &&
100
- other.to_s == to_s &&
101
- other.options == options
104
+ self.class == other.class &&
105
+ text == other.text &&
106
+ quantifier == other.quantifier &&
107
+ options == other.options &&
108
+ (terminal? || expressions == other.expressions)
102
109
  end
103
110
  alias :=== :==
104
111
  alias :eql? :==
112
+
113
+ def optional?
114
+ quantified? && quantifier.min == 0
115
+ end
116
+
117
+ def quantified?
118
+ !quantifier.nil?
119
+ end
105
120
  end
121
+
122
+ Shared.class_eval { def terminal?; self.class.terminal? end }
123
+ Shared::ClassMethods.class_eval { def terminal?; true end }
124
+ Subexpression.instance_eval { def terminal?; false end }
125
+
126
+ Shared.class_eval { def capturing?; self.class.capturing? end }
127
+ Shared::ClassMethods.class_eval { def capturing?; false end }
128
+ Group::Capture.instance_eval { def capturing?; true end }
129
+
130
+ Shared.class_eval { def comment?; self.class.comment? end }
131
+ Shared::ClassMethods.class_eval { def comment?; false end }
132
+ Comment.instance_eval { def comment?; true end }
133
+ Group::Comment.instance_eval { def comment?; true end }
134
+
135
+ Shared.class_eval { def decorative?; self.class.decorative? end }
136
+ Shared::ClassMethods.class_eval { def decorative?; false end }
137
+ FreeSpace.instance_eval { def decorative?; true end }
138
+ Group::Comment.instance_eval { def decorative?; true end }
139
+
140
+ Shared.class_eval { def referential?; self.class.referential? end }
141
+ Shared::ClassMethods.class_eval { def referential?; false end }
142
+ Backreference::Base.instance_eval { def referential?; true end }
143
+ Conditional::Condition.instance_eval { def referential?; true end }
144
+ Conditional::Expression.instance_eval { def referential?; true end }
106
145
  end
@@ -1,6 +1,24 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Regexp::Expression
2
4
  class Subexpression < Regexp::Expression::Base
3
5
 
6
+ # Traverses the expression, passing each recursive child to the
7
+ # given block.
8
+ # If the block takes two arguments, the indices of the children within
9
+ # their parents are also passed to it.
10
+ def each_expression(include_self = false, &block)
11
+ return enum_for(__method__, include_self) unless block
12
+
13
+ if block.arity == 1
14
+ block.call(self) if include_self
15
+ each_expression_without_index(&block)
16
+ else
17
+ block.call(self, 0) if include_self
18
+ each_expression_with_index(&block)
19
+ end
20
+ end
21
+
4
22
  # Traverses the subexpression (depth-first, pre-order) and calls the given
5
23
  # block for each expression with three arguments; the traversal event,
6
24
  # the expression, and the index of the expression within its parent.
@@ -34,34 +52,31 @@ module Regexp::Expression
34
52
  end
35
53
  alias :walk :traverse
36
54
 
37
- # Iterates over the expressions of this expression as an array, passing
38
- # the expression and its index within its parent to the given block.
39
- def each_expression(include_self = false, &block)
40
- return enum_for(__method__, include_self) unless block_given?
55
+ # Returns a new array with the results of calling the given block once
56
+ # for every expression. If a block is not given, returns an array with
57
+ # each expression and its level index as an array.
58
+ def flat_map(include_self = false, &block)
59
+ case block && block.arity
60
+ when nil then each_expression(include_self).to_a
61
+ when 2 then each_expression(include_self).map(&block)
62
+ else each_expression(include_self).map { |exp| block.call(exp) }
63
+ end
64
+ end
41
65
 
42
- block.call(self, 0) if include_self
66
+ protected
43
67
 
68
+ def each_expression_with_index(&block)
44
69
  each_with_index do |exp, index|
45
70
  block.call(exp, index)
46
- exp.each_expression(&block) unless exp.terminal?
71
+ exp.each_expression_with_index(&block) unless exp.terminal?
47
72
  end
48
73
  end
49
74
 
50
- # Returns a new array with the results of calling the given block once
51
- # for every expression. If a block is not given, returns an array with
52
- # each expression and its level index as an array.
53
- def flat_map(include_self = false)
54
- result = []
55
-
56
- each_expression(include_self) do |exp, index|
57
- if block_given?
58
- result << yield(exp, index)
59
- else
60
- result << [exp, index]
61
- end
75
+ def each_expression_without_index(&block)
76
+ each do |exp|
77
+ block.call(exp)
78
+ exp.each_expression_without_index(&block) unless exp.terminal?
62
79
  end
63
-
64
- result
65
80
  end
66
81
  end
67
82
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Regexp::Expression
2
4
  # TODO: in v3.0.0, maybe put Shared back into Base, and inherit from Base and
3
5
  # call super in #initialize, but raise in #quantifier= and #quantify,
@@ -6,16 +8,12 @@ module Regexp::Expression
6
8
  class Quantifier
7
9
  include Regexp::Expression::Shared
8
10
 
9
- MODES = %i[greedy possessive reluctant]
10
-
11
- attr_reader :min, :max, :mode
11
+ MODES = %i[greedy possessive reluctant].freeze
12
12
 
13
13
  def initialize(*args)
14
14
  deprecated_old_init(*args) and return if args.count == 4 || args.count == 5
15
15
 
16
16
  init_from_token_and_options(*args)
17
- @mode = (token.to_s[/greedy|reluctant|possessive/] || :greedy).to_sym
18
- @min, @max = minmax
19
17
  # TODO: remove in v3.0.0, stop removing parts of #token (?)
20
18
  self.token = token.to_s.sub(/_(greedy|possessive|reluctant)/, '').to_sym
21
19
  end
@@ -39,9 +37,21 @@ module Regexp::Expression
39
37
  end
40
38
  alias :lazy? :reluctant?
41
39
 
40
+ def min
41
+ derived_data[:min]
42
+ end
43
+
44
+ def max
45
+ derived_data[:max]
46
+ end
47
+
48
+ def mode
49
+ derived_data[:mode]
50
+ end
51
+
42
52
  private
43
53
 
44
- def deprecated_old_init(token, text, min, max, mode = :greedy)
54
+ def deprecated_old_init(token, text, _min, _max, _mode = :greedy)
45
55
  warn "Calling `Expression::Base#quantify` or `#{self.class}.new` with 4+ arguments "\
46
56
  "is deprecated.\nIt will no longer be supported in regexp_parser v3.0.0.\n"\
47
57
  "Please pass a Regexp::Token instead, e.g. replace `token, text, min, max, mode` "\
@@ -51,20 +61,25 @@ module Regexp::Expression
51
61
  "This is consistent with how Expression::Base instances are created. "
52
62
  @token = token
53
63
  @text = text
54
- @min = min
55
- @max = max
56
- @mode = mode
57
64
  end
58
65
 
59
- def minmax
60
- case token
61
- when /zero_or_one/ then [0, 1]
62
- when /zero_or_more/ then [0, -1]
63
- when /one_or_more/ then [1, -1]
64
- when :interval
65
- int_min = text[/\{(\d*)/, 1]
66
- int_max = text[/,?(\d*)\}/, 1]
67
- [int_min.to_i, (int_max.empty? ? -1 : int_max.to_i)]
66
+ def derived_data
67
+ @derived_data ||= begin
68
+ min, max =
69
+ case text[0]
70
+ when '?'; [0, 1]
71
+ when '*'; [0, -1]
72
+ when '+'; [1, -1]
73
+ else
74
+ int_min = text[/\{(\d*)/, 1]
75
+ int_max = text[/,?(\d*)\}/, 1]
76
+ [int_min.to_i, (int_max.empty? ? -1 : int_max.to_i)]
77
+ end
78
+
79
+ mod = text[/.([?+])/, 1]
80
+ mode = (mod == '?' && :reluctant) || (mod == '+' && :possessive) || :greedy
81
+
82
+ { min: min, max: max, mode: mode }
68
83
  end
69
84
  end
70
85
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Regexp::Expression
2
4
  # A sequence of expressions. Differs from a Subexpressions by how it handles
3
5
  # quantifiers, as it applies them to its last element instead of itself as
@@ -12,6 +14,7 @@ module Regexp::Expression
12
14
  level: exp.level,
13
15
  set_level: exp.set_level,
14
16
  conditional_level: params[:conditional_level] || exp.conditional_level,
17
+ ts: params[:ts],
15
18
  )
16
19
  sequence.options = active_opts
17
20
  exp.expressions << sequence
@@ -19,17 +22,12 @@ module Regexp::Expression
19
22
  end
20
23
  end
21
24
 
22
- def starts_at
23
- expressions.first.starts_at
25
+ def ts
26
+ (head = expressions.first) ? head.ts : @ts
24
27
  end
25
- alias :ts :starts_at
26
-
27
- def quantify(*args)
28
- target = expressions.reverse.find { |exp| !exp.is_a?(FreeSpace) }
29
- target or raise Regexp::Parser::Error,
30
- "No valid target found for '#{text}' quantifier"
31
28
 
32
- target.quantify(*args)
29
+ def quantify(token, *args)
30
+ extract_quantifier_target(token.text).quantify(token, *args)
33
31
  end
34
32
  end
35
33
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Regexp::Expression
2
4
  # abstract class
3
5
  class SequenceOperation < Regexp::Expression::Subexpression
@@ -5,21 +7,16 @@ module Regexp::Expression
5
7
  alias :operands :expressions
6
8
  alias :operator :text
7
9
 
8
- def starts_at
9
- expressions.first.starts_at
10
+ def ts
11
+ (head = expressions.first) ? head.ts : @ts
10
12
  end
11
- alias :ts :starts_at
12
13
 
13
14
  def <<(exp)
14
15
  expressions.last << exp
15
16
  end
16
17
 
17
- def add_sequence(active_opts = {})
18
- self.class::OPERAND.add_to(self, {}, active_opts)
19
- end
20
-
21
- def parts
22
- intersperse(expressions, text.dup)
18
+ def add_sequence(active_opts = {}, params = { ts: 0 })
19
+ self.class::OPERAND.add_to(self, params, active_opts)
23
20
  end
24
21
  end
25
22
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Regexp::Expression
2
4
  module Shared
3
5
  module ClassMethods; end # filled in ./methods/*.rb
@@ -8,7 +10,8 @@ module Regexp::Expression
8
10
 
9
11
  attr_accessor :type, :token, :text, :ts, :te,
10
12
  :level, :set_level, :conditional_level,
11
- :options
13
+ :options, :parent,
14
+ :custom_to_s_handling, :pre_quantifier_decorations
12
15
 
13
16
  attr_reader :nesting_level, :quantifier
14
17
  end
@@ -32,6 +35,10 @@ module Regexp::Expression
32
35
  self.text = orig.text.dup if orig.text
33
36
  self.options = orig.options.dup if orig.options
34
37
  self.quantifier = orig.quantifier.clone if orig.quantifier
38
+ self.parent = nil # updated by Subexpression#initialize_copy
39
+ if orig.pre_quantifier_decorations
40
+ self.pre_quantifier_decorations = orig.pre_quantifier_decorations.map(&:dup)
41
+ end
35
42
  super
36
43
  end
37
44
 
@@ -39,35 +46,52 @@ module Regexp::Expression
39
46
  ts
40
47
  end
41
48
 
49
+ def ends_at(include_quantifier = true)
50
+ ts + (include_quantifier ? full_length : base_length)
51
+ end
52
+
42
53
  def base_length
43
54
  to_s(:base).length
44
55
  end
45
56
 
46
57
  def full_length
47
- to_s.length
48
- end
49
-
58
+ to_s(:original).length
59
+ end
60
+
61
+ # #to_s reproduces the original source, as an unparser would.
62
+ #
63
+ # It takes an optional format argument.
64
+ #
65
+ # Example:
66
+ #
67
+ # lit = Regexp::Parser.parse(/a +/x)[0]
68
+ #
69
+ # lit.to_s # => 'a+' # default; with quantifier
70
+ # lit.to_s(:full) # => 'a+' # default; with quantifier
71
+ # lit.to_s(:base) # => 'a' # without quantifier
72
+ # lit.to_s(:original) # => 'a +' # with quantifier AND intermittent decorations
73
+ #
50
74
  def to_s(format = :full)
51
- "#{parts.join}#{quantifier_affix(format)}"
75
+ base = ''.dup
76
+ parts.each do |part|
77
+ if part.instance_of?(String)
78
+ base << part
79
+ elsif !part.custom_to_s_handling
80
+ base << part.to_s(:original)
81
+ end
82
+ end
83
+ "#{base}#{pre_quantifier_decoration(format)}#{quantifier_affix(format)}"
52
84
  end
53
85
  alias :to_str :to_s
54
86
 
55
- def parts
56
- [text.dup]
87
+ def pre_quantifier_decoration(expression_format = :original)
88
+ pre_quantifier_decorations.to_a.join if expression_format == :original
57
89
  end
58
90
 
59
- def quantifier_affix(expression_format)
91
+ def quantifier_affix(expression_format = :full)
60
92
  quantifier.to_s if quantified? && expression_format != :base
61
93
  end
62
94
 
63
- def quantified?
64
- !quantifier.nil?
65
- end
66
-
67
- def optional?
68
- quantified? && quantifier.min == 0
69
- end
70
-
71
95
  def offset
72
96
  [starts_at, full_length]
73
97
  end
@@ -76,14 +100,6 @@ module Regexp::Expression
76
100
  '@%d+%d' % offset
77
101
  end
78
102
 
79
- def terminal?
80
- true # overridden to be false in Expression::Subexpression
81
- end
82
-
83
- def referential?
84
- false # overridden to be true e.g. in Expression::Backreference::Base
85
- end
86
-
87
103
  def nesting_level=(lvl)
88
104
  @nesting_level = lvl
89
105
  quantifier && quantifier.nesting_level = lvl