regexp_parser 1.8.2 → 2.1.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 (56) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +93 -0
  3. data/Gemfile +6 -1
  4. data/README.md +1 -4
  5. data/Rakefile +8 -8
  6. data/lib/regexp_parser.rb +1 -0
  7. data/lib/regexp_parser/error.rb +4 -0
  8. data/lib/regexp_parser/expression.rb +5 -18
  9. data/lib/regexp_parser/expression/classes/backref.rb +5 -0
  10. data/lib/regexp_parser/expression/classes/conditional.rb +11 -1
  11. data/lib/regexp_parser/expression/classes/free_space.rb +2 -2
  12. data/lib/regexp_parser/expression/classes/group.rb +28 -3
  13. data/lib/regexp_parser/expression/classes/property.rb +1 -1
  14. data/lib/regexp_parser/expression/classes/root.rb +4 -16
  15. data/lib/regexp_parser/expression/classes/set/range.rb +2 -1
  16. data/lib/regexp_parser/expression/methods/match_length.rb +2 -2
  17. data/lib/regexp_parser/expression/methods/traverse.rb +2 -2
  18. data/lib/regexp_parser/expression/quantifier.rb +10 -1
  19. data/lib/regexp_parser/expression/sequence.rb +3 -19
  20. data/lib/regexp_parser/expression/subexpression.rb +1 -1
  21. data/lib/regexp_parser/lexer.rb +2 -2
  22. data/lib/regexp_parser/parser.rb +306 -332
  23. data/lib/regexp_parser/scanner.rb +1272 -1338
  24. data/lib/regexp_parser/scanner/char_type.rl +11 -11
  25. data/lib/regexp_parser/scanner/property.rl +2 -2
  26. data/lib/regexp_parser/scanner/scanner.rl +206 -238
  27. data/lib/regexp_parser/syntax.rb +7 -7
  28. data/lib/regexp_parser/syntax/any.rb +3 -3
  29. data/lib/regexp_parser/syntax/base.rb +1 -1
  30. data/lib/regexp_parser/syntax/version_lookup.rb +2 -2
  31. data/lib/regexp_parser/syntax/versions.rb +1 -1
  32. data/lib/regexp_parser/version.rb +1 -1
  33. data/spec/expression/base_spec.rb +10 -0
  34. data/spec/expression/clone_spec.rb +36 -4
  35. data/spec/expression/free_space_spec.rb +2 -2
  36. data/spec/expression/methods/match_length_spec.rb +2 -2
  37. data/spec/expression/subexpression_spec.rb +1 -1
  38. data/spec/expression/to_s_spec.rb +39 -31
  39. data/spec/lexer/literals_spec.rb +24 -49
  40. data/spec/lexer/refcalls_spec.rb +5 -0
  41. data/spec/parser/all_spec.rb +2 -2
  42. data/spec/parser/errors_spec.rb +1 -1
  43. data/spec/parser/escapes_spec.rb +1 -1
  44. data/spec/parser/quantifiers_spec.rb +16 -0
  45. data/spec/parser/refcalls_spec.rb +5 -0
  46. data/spec/parser/set/ranges_spec.rb +3 -3
  47. data/spec/scanner/escapes_spec.rb +8 -1
  48. data/spec/scanner/groups_spec.rb +10 -1
  49. data/spec/scanner/literals_spec.rb +28 -38
  50. data/spec/scanner/quantifiers_spec.rb +18 -13
  51. data/spec/scanner/refcalls_spec.rb +19 -0
  52. data/spec/scanner/sets_spec.rb +65 -16
  53. data/spec/spec_helper.rb +1 -0
  54. metadata +4 -7
  55. data/spec/expression/root_spec.rb +0 -9
  56. data/spec/expression/sequence_spec.rb +0 -9
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 2d1c795eb67eee01aa7976a672349b9a0c7f6364262ffb8f53488f6d9949db07
4
- data.tar.gz: a38fddc769f16841e54f5dde90535e31ef7145e5bd41560ccf70443968495493
3
+ metadata.gz: 79c8b7838ef53335c9d0fbd21ffdf6815473ee560380a3687e8fab514d031d53
4
+ data.tar.gz: 2a91f7c7640fc5f2d304c2cbf240886d8e8642994861a9c092f1d4db2ae6b77a
5
5
  SHA512:
6
- metadata.gz: 8dfcd3ba9f2a09370142e892184ccb9a1a973e24dea691582ca43cc670589dd7050eb30dbd924ae9e0584d124183dc2f62adca475628a726ecffaa22fcfb8903
7
- data.tar.gz: cd23a308e4d62af9a07bbec164c96ca3213de5a4f686c450fc90bca56387faa88febb8a3387e810d0fd8b78e06b191bd76777fe8b3f3cda9da5582b9009d1144
6
+ metadata.gz: 3559a8c7af9c0087ab7a54862c9913e40a3703ffa23f62e6919eec50042523424c2aa4c99b3de9d28d03fc0edd14af37e0dcd0eab7bf822b9af73113be468b59
7
+ data.tar.gz: 31ed468565bd41fe2d0bd7b82d53d64e213a15e1ade2108ddf813637c228c18f6f7b456725c7e359a08754188ee19c90d06e013be90775ee6a64723b04fa25f0
data/CHANGELOG.md CHANGED
@@ -1,5 +1,98 @@
1
1
  ## [Unreleased]
2
2
 
3
+ ## [2.1.0] - 2021-02-22 - [Janosch Müller](mailto:janosch84@gmail.com)
4
+
5
+ ### Added
6
+
7
+ - common ancestor for all scanning/parsing/lexing errors
8
+ * `Regexp::Parser::Error` can now be rescued as a catch-all
9
+ * the following errors (and their many descendants) now inherit from it:
10
+ - `Regexp::Expression::Conditional::TooManyBranches`
11
+ - `Regexp::Parser::ParserError`
12
+ - `Regexp::Scanner::ScannerError`
13
+ - `Regexp::Scanner::ValidationError`
14
+ - `Regexp::Syntax::SyntaxError`
15
+ * it replaces `ArgumentError` in some rare cases (`Regexp::Parser.parse('?')`)
16
+ * thanks to [sandstrom](https://github.com/sandstrom) for the cue
17
+
18
+ ### Fixed
19
+
20
+ - fixed scanning of whole-pattern recursion calls `\g<0>` and `\g'0'`
21
+ * a regression in v2.0.1 had caused them to be scanned as literals
22
+ - fixed scanning of some backreference and subexpression call edge cases
23
+ * e.g. `\k<+1>`, `\g<x-1>`
24
+ - fixed tokenization of some escapes in character sets
25
+ * `.`, `|`, `{`, `}`, `(`, `)`, `^`, `$`, `?`, `+`, `*`
26
+ * all of these correctly emitted `#type` `:literal` and `#token` `:literal` if *not* escaped
27
+ * if escaped, they emitted e.g. `#type` `:escape` and `#token` `:group_open` for `[\(]`
28
+ * the escaped versions now correctly emit `#type` `:escape` and `#token` `:literal`
29
+ - fixed handling of control/metacontrol escapes in character sets
30
+ * e.g. `[\cX]`, `[\M-\C-X]`
31
+ * they were misread as bunch of individual literals, escapes, and ranges
32
+ - fixed some cases where calling `#dup`/`#clone` on expressions led to shared state
33
+
34
+ ## [2.0.3] - 2020-12-28 - [Janosch Müller](mailto:janosch84@gmail.com)
35
+
36
+ ### Fixed
37
+
38
+ - fixed error when scanning some unlikely and redundant but valid charset patterns
39
+ * e.g. `/[[.a-b.]]/`, `/[[=e=]]/`,
40
+ - fixed ancestry of some error classes related to syntax version lookup
41
+ * `NotImplementedError`, `InvalidVersionNameError`, `UnknownSyntaxNameError`
42
+ * they now correctly inherit from `Regexp::Syntax::SyntaxError` instead of Rubys `::SyntaxError`
43
+
44
+ ## [2.0.2] - 2020-12-25 - [Janosch Müller](mailto:janosch84@gmail.com)
45
+
46
+ ### Fixed
47
+
48
+ - fixed `FrozenError` when calling `#to_s` on a frozen `Group::Passive`
49
+ * thanks to [Daniel Gollahon](https://github.com/dgollahon)
50
+
51
+ ## [2.0.1] - 2020-12-20 - [Janosch Müller](mailto:janosch84@gmail.com)
52
+
53
+ ### Fixed
54
+
55
+ - fixed error when scanning some group names
56
+ * this affected names containing hyphens, digits or multibyte chars, e.g. `/(?<a1>a)/`
57
+ * thanks to [Daniel Gollahon](https://github.com/dgollahon) for the report
58
+ - fixed error when scanning hex escapes with just one hex digit
59
+ * e.g. `/\x0A/` was scanned correctly, but the equivalent `/\xA/` was not
60
+ * thanks to [Daniel Gollahon](https://github.com/dgollahon) for the report
61
+
62
+ ## [2.0.0] - 2020-11-25 - [Janosch Müller](mailto:janosch84@gmail.com)
63
+
64
+ ### Changed
65
+
66
+ - some methods that used to return byte-based indices now return char-based indices
67
+ * the returned values have only changed for Regexps that contain multibyte chars
68
+ * this is only a breaking change if you used such methods directly AND relied on them pointing to bytes
69
+ * affected methods:
70
+ * `Regexp::Token` `#length`, `#offset`, `#te`, `#ts`
71
+ * `Regexp::Expression::Base` `#full_length`, `#offset`, `#starts_at`, `#te`, `#ts`
72
+ * thanks to [Akinori MUSHA](https://github.com/knu) for the report
73
+ - removed some deprecated methods/signatures
74
+ * these are rarely used and have been showing deprecation warnings for a long time
75
+ * `Regexp::Expression::Subexpression.new` with 3 arguments
76
+ * `Regexp::Expression::Root.new` without a token argument
77
+ * `Regexp::Expression.parsed`
78
+
79
+ ### Added
80
+
81
+ - `Regexp::Expression::Base#base_length`
82
+ * returns the character count of an expression body, ignoring any quantifier
83
+ - pragmatic, experimental support for chained quantifiers
84
+ * e.g.: `/^a{10}{4,6}$/` matches exactly 40, 50 or 60 `a`s
85
+ * successive quantifiers used to be silently dropped by the parser
86
+ * they are now wrapped with passive groups as if they were written `(?:a{10}){4,6}`
87
+ * thanks to [calfeld](https://github.com/calfeld) for reporting this a while back
88
+
89
+ ### Fixed
90
+
91
+ - incorrect encoding output for non-ascii comments
92
+ * this led to a crash when calling `#to_s` on parse results containing such comments
93
+ * thanks to [Michael Glass](https://github.com/michaelglass) for the report
94
+ - some crashes when scanning contrived patterns such as `'\😋'`
95
+
3
96
  ### [1.8.2] - 2020-10-11 - [Janosch Müller](mailto:janosch84@gmail.com)
4
97
 
5
98
  ### Fixed
data/Gemfile CHANGED
@@ -3,7 +3,12 @@ source 'https://rubygems.org'
3
3
  gemspec
4
4
 
5
5
  group :development, :test do
6
+ gem 'ice_nine', '~> 0.11.2'
6
7
  gem 'rake', '~> 13.0'
7
8
  gem 'regexp_property_values', '~> 1.0'
8
- gem 'rspec', '~> 3.8'
9
+ gem 'rspec', '~> 3.10'
10
+ if RUBY_VERSION.to_f >= 2.7
11
+ gem 'gouteur'
12
+ gem 'rubocop', '~> 1.7'
13
+ end
9
14
  end
data/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # Regexp::Parser
2
2
 
3
- [![Gem Version](https://badge.fury.io/rb/regexp_parser.svg)](http://badge.fury.io/rb/regexp_parser) [![Build Status](https://secure.travis-ci.org/ammar/regexp_parser.svg?branch=master)](http://travis-ci.org/ammar/regexp_parser) [![Code Climate](https://codeclimate.com/github/ammar/regexp_parser.svg)](https://codeclimate.com/github/ammar/regexp_parser/badges)
3
+ [![Gem Version](https://badge.fury.io/rb/regexp_parser.svg)](http://badge.fury.io/rb/regexp_parser) [![Build Status](https://github.com/ammar/regexp_parser/workflows/tests/badge.svg)](https://github.com/ammar/regexp_parser/actions) [![Build Status](https://github.com/ammar/regexp_parser/workflows/gouteur/badge.svg)](https://github.com/ammar/regexp_parser/actions) [![Code Climate](https://codeclimate.com/github/ammar/regexp_parser.svg)](https://codeclimate.com/github/ammar/regexp_parser/badges)
4
4
 
5
5
  A Ruby gem for tokenizing, parsing, and transforming regular expressions.
6
6
 
@@ -22,9 +22,6 @@ _For examples of regexp_parser in use, see [Example Projects](#example-projects)
22
22
  * Ragel >= 6.0, but only if you want to build the gem or work on the scanner.
23
23
 
24
24
 
25
- _Note: See the .travis.yml file for covered versions._
26
-
27
-
28
25
  ---
29
26
  ## Install
30
27
 
data/Rakefile CHANGED
@@ -7,8 +7,8 @@ require 'bundler'
7
7
  require 'rubygems/package_task'
8
8
 
9
9
 
10
- RAGEL_SOURCE_DIR = File.expand_path '../lib/regexp_parser/scanner', __FILE__
11
- RAGEL_OUTPUT_DIR = File.expand_path '../lib/regexp_parser', __FILE__
10
+ RAGEL_SOURCE_DIR = File.join(__dir__, 'lib/regexp_parser/scanner')
11
+ RAGEL_OUTPUT_DIR = File.join(__dir__, 'lib/regexp_parser')
12
12
  RAGEL_SOURCE_FILES = %w{scanner} # scanner.rl includes property.rl
13
13
 
14
14
 
@@ -25,11 +25,11 @@ end
25
25
 
26
26
  namespace :ragel do
27
27
  desc "Process the ragel source files and output ruby code"
28
- task :rb do |t|
29
- RAGEL_SOURCE_FILES.each do |file|
30
- output_file = "#{RAGEL_OUTPUT_DIR}/#{file}.rb"
28
+ task :rb do
29
+ RAGEL_SOURCE_FILES.each do |source_file|
30
+ output_file = "#{RAGEL_OUTPUT_DIR}/#{source_file}.rb"
31
31
  # using faster flat table driven FSM, about 25% larger code, but about 30% faster
32
- sh "ragel -F1 -R #{RAGEL_SOURCE_DIR}/#{file}.rl -o #{output_file}"
32
+ sh "ragel -F1 -R #{RAGEL_SOURCE_DIR}/#{source_file}.rl -o #{output_file}"
33
33
 
34
34
  contents = File.read(output_file)
35
35
 
@@ -42,7 +42,7 @@ namespace :ragel do
42
42
  end
43
43
 
44
44
  desc "Delete the ragel generated source file(s)"
45
- task :clean do |t|
45
+ task :clean do
46
46
  RAGEL_SOURCE_FILES.each do |file|
47
47
  sh "rm -f #{RAGEL_OUTPUT_DIR}/#{file}.rb"
48
48
  end
@@ -61,7 +61,7 @@ namespace :props do
61
61
  task :update do
62
62
  require 'regexp_property_values'
63
63
  RegexpPropertyValues.update
64
- dir = File.expand_path('../lib/regexp_parser/scanner/properties', __FILE__)
64
+ dir = File.join(__dir__, 'lib/regexp_parser/scanner/properties')
65
65
 
66
66
  require 'psych'
67
67
  write_hash_to_file = ->(hash, path) do
data/lib/regexp_parser.rb CHANGED
@@ -1,6 +1,7 @@
1
1
  # encoding: utf-8
2
2
 
3
3
  require 'regexp_parser/version'
4
+ require 'regexp_parser/error'
4
5
  require 'regexp_parser/token'
5
6
  require 'regexp_parser/scanner'
6
7
  require 'regexp_parser/syntax'
@@ -0,0 +1,4 @@
1
+ class Regexp::Parser
2
+ # base class for all gem-specific errors (inherited but never raised itself)
3
+ class Error < StandardError; end
4
+ end
@@ -21,7 +21,7 @@ module Regexp::Expression
21
21
  self.options = options
22
22
  end
23
23
 
24
- def initialize_clone(orig)
24
+ def initialize_copy(orig)
25
25
  self.text = (orig.text ? orig.text.dup : nil)
26
26
  self.options = (orig.options ? orig.options.dup : nil)
27
27
  self.quantifier = (orig.quantifier ? orig.quantifier.clone : nil)
@@ -34,6 +34,10 @@ module Regexp::Expression
34
34
 
35
35
  alias :starts_at :ts
36
36
 
37
+ def base_length
38
+ to_s(:base).length
39
+ end
40
+
37
41
  def full_length
38
42
  to_s.length
39
43
  end
@@ -118,23 +122,6 @@ module Regexp::Expression
118
122
  alias :to_h :attributes
119
123
  end
120
124
 
121
- def self.parsed(exp)
122
- warn('WARNING: Regexp::Expression::Base.parsed is buggy and '\
123
- 'will be removed in 2.0.0. Use Regexp::Parser.parse instead.')
124
- case exp
125
- when String
126
- Regexp::Parser.parse(exp)
127
- when Regexp
128
- Regexp::Parser.parse(exp.source) # <- causes loss of root options
129
- when Regexp::Expression # <- never triggers
130
- exp
131
- else
132
- raise ArgumentError, 'Expression.parsed accepts a String, Regexp, or '\
133
- 'a Regexp::Expression as a value for exp, but it '\
134
- "was given #{exp.class.name}."
135
- end
136
- end
137
-
138
125
  end # module Regexp::Expression
139
126
 
140
127
  require 'regexp_parser/expression/quantifier'
@@ -2,6 +2,11 @@ module Regexp::Expression
2
2
  module Backreference
3
3
  class Base < Regexp::Expression::Base
4
4
  attr_accessor :referenced_expression
5
+
6
+ def initialize_copy(orig)
7
+ self.referenced_expression = orig.referenced_expression.dup
8
+ super
9
+ end
5
10
  end
6
11
 
7
12
  class Number < Backreference::Base
@@ -1,6 +1,6 @@
1
1
  module Regexp::Expression
2
2
  module Conditional
3
- class TooManyBranches < StandardError
3
+ class TooManyBranches < Regexp::Parser::Error
4
4
  def initialize
5
5
  super('The conditional expression has more than 2 branches')
6
6
  end
@@ -15,6 +15,11 @@ module Regexp::Expression
15
15
  ref = text.tr("'<>()", "")
16
16
  ref =~ /\D/ ? ref : Integer(ref)
17
17
  end
18
+
19
+ def initialize_copy(orig)
20
+ self.referenced_expression = orig.referenced_expression.dup
21
+ super
22
+ end
18
23
  end
19
24
 
20
25
  class Branch < Regexp::Expression::Sequence; end
@@ -53,6 +58,11 @@ module Regexp::Expression
53
58
  def to_s(format = :full)
54
59
  "#{text}#{condition}#{branches.join('|')})#{quantifier_affix(format)}"
55
60
  end
61
+
62
+ def initialize_copy(orig)
63
+ self.referenced_expression = orig.referenced_expression.dup
64
+ super
65
+ end
56
66
  end
57
67
  end
58
68
  end
@@ -1,8 +1,8 @@
1
1
  module Regexp::Expression
2
2
 
3
3
  class FreeSpace < Regexp::Expression::Base
4
- def quantify(token, text, min = nil, max = nil, mode = :greedy)
5
- raise "Can not quantify a free space object"
4
+ def quantify(_token, _text, _min = nil, _max = nil, _mode = :greedy)
5
+ raise Regexp::Parser::Error, 'Can not quantify a free space object'
6
6
  end
7
7
  end
8
8
 
@@ -10,11 +10,36 @@ module Regexp::Expression
10
10
  def comment?; false end
11
11
  end
12
12
 
13
- class Atomic < Group::Base; end
14
- class Passive < Group::Base; end
13
+ class Passive < Group::Base
14
+ attr_writer :implicit
15
+
16
+ def initialize(*)
17
+ @implicit = false
18
+ super
19
+ end
20
+
21
+ def to_s(format = :full)
22
+ if implicit?
23
+ "#{expressions.join}#{quantifier_affix(format)}"
24
+ else
25
+ super
26
+ end
27
+ end
28
+
29
+ def implicit?
30
+ @implicit
31
+ end
32
+ end
33
+
15
34
  class Absence < Group::Base; end
35
+ class Atomic < Group::Base; end
16
36
  class Options < Group::Base
17
37
  attr_accessor :option_changes
38
+
39
+ def initialize_copy(orig)
40
+ self.option_changes = orig.option_changes.dup
41
+ super
42
+ end
18
43
  end
19
44
 
20
45
  class Capture < Group::Base
@@ -33,7 +58,7 @@ module Regexp::Expression
33
58
  super
34
59
  end
35
60
 
36
- def initialize_clone(orig)
61
+ def initialize_copy(orig)
37
62
  @name = orig.name.dup
38
63
  super
39
64
  end
@@ -7,7 +7,7 @@ module Regexp::Expression
7
7
  end
8
8
 
9
9
  def name
10
- text =~ /\A\\[pP]\{([^}]+)\}\z/; $1
10
+ text[/\A\\[pP]\{([^}]+)\}\z/, 1]
11
11
  end
12
12
 
13
13
  def shortcut
@@ -1,24 +1,12 @@
1
1
  module Regexp::Expression
2
2
 
3
3
  class Root < Regexp::Expression::Subexpression
4
- # TODO: this override is here for backwards compatibility, remove in 2.0.0
5
- def initialize(*args)
6
- unless args.first.is_a?(Regexp::Token)
7
- warn('WARNING: Root.new without a Token argument is deprecated and '\
8
- 'will be removed in 2.0.0. Use Root.build for the old behavior.')
9
- return super(self.class.build_token, *args)
10
- end
11
- super
4
+ def self.build(options = {})
5
+ new(build_token, options)
12
6
  end
13
7
 
14
- class << self
15
- def build(options = {})
16
- new(build_token, options)
17
- end
18
-
19
- def build_token
20
- Regexp::Token.new(:expression, :root, '', 0)
21
- end
8
+ def self.build_token
9
+ Regexp::Token.new(:expression, :root, '', 0)
22
10
  end
23
11
  end
24
12
  end
@@ -7,7 +7,8 @@ module Regexp::Expression
7
7
  alias :ts :starts_at
8
8
 
9
9
  def <<(exp)
10
- complete? && raise("Can't add more than 2 expressions to a Range")
10
+ complete? and raise Regexp::Parser::Error,
11
+ "Can't add more than 2 expressions to a Range"
11
12
  super
12
13
  end
13
14
 
@@ -10,7 +10,7 @@ class Regexp::MatchLength
10
10
  self.exp_class = exp.class
11
11
  self.min_rep = exp.repetitions.min
12
12
  self.max_rep = exp.repetitions.max
13
- if base = opts[:base]
13
+ if (base = opts[:base])
14
14
  self.base_min = base
15
15
  self.base_max = base
16
16
  self.reify = ->{ '.' * base }
@@ -32,7 +32,7 @@ class Regexp::MatchLength
32
32
  end
33
33
  end
34
34
 
35
- def endless_each(&block)
35
+ def endless_each
36
36
  return enum_for(__method__) unless block_given?
37
37
  (min..max).each { |num| yield(num) if include?(num) }
38
38
  end
@@ -36,7 +36,7 @@ module Regexp::Expression
36
36
 
37
37
  # Iterates over the expressions of this expression as an array, passing
38
38
  # the expression and its index within its parent to the given block.
39
- def each_expression(include_self = false, &block)
39
+ def each_expression(include_self = false)
40
40
  return enum_for(__method__, include_self) unless block_given?
41
41
 
42
42
  traverse(include_self) do |event, exp, index|
@@ -47,7 +47,7 @@ module Regexp::Expression
47
47
  # Returns a new array with the results of calling the given block once
48
48
  # for every expression. If a block is not given, returns an array with
49
49
  # each expression and its level index as an array.
50
- def flat_map(include_self = false, &block)
50
+ def flat_map(include_self = false)
51
51
  result = []
52
52
 
53
53
  each_expression(include_self) do |exp, index|
@@ -12,7 +12,7 @@ module Regexp::Expression
12
12
  @max = max
13
13
  end
14
14
 
15
- def initialize_clone(orig)
15
+ def initialize_copy(orig)
16
16
  @text = orig.text.dup
17
17
  super
18
18
  end
@@ -40,5 +40,14 @@ module Regexp::Expression
40
40
  RUBY
41
41
  end
42
42
  alias :lazy? :reluctant?
43
+
44
+ def ==(other)
45
+ other.class == self.class &&
46
+ other.token == token &&
47
+ other.mode == mode &&
48
+ other.min == min &&
49
+ other.max == max
50
+ end
51
+ alias :eq :==
43
52
  end
44
53
  end