regexp_parser 1.8.2 → 2.1.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (54) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +100 -0
  3. data/Gemfile +6 -1
  4. data/README.md +1 -4
  5. data/Rakefile +8 -8
  6. data/lib/regexp_parser/error.rb +4 -0
  7. data/lib/regexp_parser/expression/classes/backref.rb +5 -0
  8. data/lib/regexp_parser/expression/classes/conditional.rb +11 -1
  9. data/lib/regexp_parser/expression/classes/free_space.rb +2 -2
  10. data/lib/regexp_parser/expression/classes/group.rb +28 -3
  11. data/lib/regexp_parser/expression/classes/property.rb +1 -1
  12. data/lib/regexp_parser/expression/classes/root.rb +4 -16
  13. data/lib/regexp_parser/expression/classes/set/range.rb +2 -1
  14. data/lib/regexp_parser/expression/methods/match_length.rb +2 -2
  15. data/lib/regexp_parser/expression/methods/traverse.rb +2 -2
  16. data/lib/regexp_parser/expression/quantifier.rb +10 -1
  17. data/lib/regexp_parser/expression/sequence.rb +3 -19
  18. data/lib/regexp_parser/expression/subexpression.rb +1 -1
  19. data/lib/regexp_parser/expression.rb +7 -19
  20. data/lib/regexp_parser/lexer.rb +2 -2
  21. data/lib/regexp_parser/parser.rb +307 -332
  22. data/lib/regexp_parser/scanner/char_type.rl +11 -11
  23. data/lib/regexp_parser/scanner/property.rl +2 -2
  24. data/lib/regexp_parser/scanner/scanner.rl +209 -240
  25. data/lib/regexp_parser/scanner.rb +1275 -1340
  26. data/lib/regexp_parser/syntax/any.rb +3 -3
  27. data/lib/regexp_parser/syntax/base.rb +1 -1
  28. data/lib/regexp_parser/syntax/version_lookup.rb +2 -2
  29. data/lib/regexp_parser/syntax.rb +8 -6
  30. data/lib/regexp_parser/version.rb +1 -1
  31. data/spec/expression/base_spec.rb +10 -0
  32. data/spec/expression/clone_spec.rb +36 -4
  33. data/spec/expression/free_space_spec.rb +2 -2
  34. data/spec/expression/methods/match_length_spec.rb +2 -2
  35. data/spec/expression/subexpression_spec.rb +1 -1
  36. data/spec/expression/to_s_spec.rb +39 -31
  37. data/spec/lexer/literals_spec.rb +24 -49
  38. data/spec/lexer/refcalls_spec.rb +5 -0
  39. data/spec/parser/all_spec.rb +2 -2
  40. data/spec/parser/errors_spec.rb +1 -1
  41. data/spec/parser/escapes_spec.rb +1 -1
  42. data/spec/parser/quantifiers_spec.rb +16 -0
  43. data/spec/parser/refcalls_spec.rb +5 -0
  44. data/spec/parser/set/ranges_spec.rb +3 -3
  45. data/spec/scanner/escapes_spec.rb +8 -1
  46. data/spec/scanner/groups_spec.rb +10 -1
  47. data/spec/scanner/literals_spec.rb +28 -38
  48. data/spec/scanner/quantifiers_spec.rb +18 -13
  49. data/spec/scanner/refcalls_spec.rb +19 -0
  50. data/spec/scanner/sets_spec.rb +65 -16
  51. data/spec/spec_helper.rb +1 -0
  52. metadata +4 -7
  53. data/spec/expression/root_spec.rb +0 -9
  54. 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: 077b8a0c90d90cf46e44671ec1335a5373eef72c61a0bcf4de43ba5217a188c3
4
+ data.tar.gz: b9aed868af73adcdf40c09720c5d10091b25a53b25a792717ceb5591039a2931
5
5
  SHA512:
6
- metadata.gz: 8dfcd3ba9f2a09370142e892184ccb9a1a973e24dea691582ca43cc670589dd7050eb30dbd924ae9e0584d124183dc2f62adca475628a726ecffaa22fcfb8903
7
- data.tar.gz: cd23a308e4d62af9a07bbec164c96ca3213de5a4f686c450fc90bca56387faa88febb8a3387e810d0fd8b78e06b191bd76777fe8b3f3cda9da5582b9009d1144
6
+ metadata.gz: 9c04d9a6434c6e3f322e97e8e2a1c86b3ddda88bd8821368a37b92f5836e4c3df1dc27a79165303420c3e8d5eea31bda1483824da01a40ce30961b645ba65ddd
7
+ data.tar.gz: 01e5c261e9dca0c4df7c696128dbc0520ca40aa6b9393cc8d6c3bdb8386470aeb773566000b811f98c1407038216c8d2c0b444c7955ea5a881ac759796f8a440
data/CHANGELOG.md CHANGED
@@ -1,5 +1,105 @@
1
1
  ## [Unreleased]
2
2
 
3
+ ## [2.1.1] - 2021-02-23 - [Janosch Müller](mailto:janosch84@gmail.com)
4
+
5
+ ### Fixed
6
+
7
+ - fixed `NameError` when requiring only `'regexp_parser/scanner'` in v2.1.0
8
+ * thanks to [Jared White and Sam Ruby](https://github.com/ruby2js/ruby2js) for the report
9
+
10
+ ## [2.1.0] - 2021-02-22 - [Janosch Müller](mailto:janosch84@gmail.com)
11
+
12
+ ### Added
13
+
14
+ - common ancestor for all scanning/parsing/lexing errors
15
+ * `Regexp::Parser::Error` can now be rescued as a catch-all
16
+ * the following errors (and their many descendants) now inherit from it:
17
+ - `Regexp::Expression::Conditional::TooManyBranches`
18
+ - `Regexp::Parser::ParserError`
19
+ - `Regexp::Scanner::ScannerError`
20
+ - `Regexp::Scanner::ValidationError`
21
+ - `Regexp::Syntax::SyntaxError`
22
+ * it replaces `ArgumentError` in some rare cases (`Regexp::Parser.parse('?')`)
23
+ * thanks to [sandstrom](https://github.com/sandstrom) for the cue
24
+
25
+ ### Fixed
26
+
27
+ - fixed scanning of whole-pattern recursion calls `\g<0>` and `\g'0'`
28
+ * a regression in v2.0.1 had caused them to be scanned as literals
29
+ - fixed scanning of some backreference and subexpression call edge cases
30
+ * e.g. `\k<+1>`, `\g<x-1>`
31
+ - fixed tokenization of some escapes in character sets
32
+ * `.`, `|`, `{`, `}`, `(`, `)`, `^`, `$`, `?`, `+`, `*`
33
+ * all of these correctly emitted `#type` `:literal` and `#token` `:literal` if *not* escaped
34
+ * if escaped, they emitted e.g. `#type` `:escape` and `#token` `:group_open` for `[\(]`
35
+ * the escaped versions now correctly emit `#type` `:escape` and `#token` `:literal`
36
+ - fixed handling of control/metacontrol escapes in character sets
37
+ * e.g. `[\cX]`, `[\M-\C-X]`
38
+ * they were misread as bunch of individual literals, escapes, and ranges
39
+ - fixed some cases where calling `#dup`/`#clone` on expressions led to shared state
40
+
41
+ ## [2.0.3] - 2020-12-28 - [Janosch Müller](mailto:janosch84@gmail.com)
42
+
43
+ ### Fixed
44
+
45
+ - fixed error when scanning some unlikely and redundant but valid charset patterns
46
+ * e.g. `/[[.a-b.]]/`, `/[[=e=]]/`,
47
+ - fixed ancestry of some error classes related to syntax version lookup
48
+ * `NotImplementedError`, `InvalidVersionNameError`, `UnknownSyntaxNameError`
49
+ * they now correctly inherit from `Regexp::Syntax::SyntaxError` instead of Rubys `::SyntaxError`
50
+
51
+ ## [2.0.2] - 2020-12-25 - [Janosch Müller](mailto:janosch84@gmail.com)
52
+
53
+ ### Fixed
54
+
55
+ - fixed `FrozenError` when calling `#to_s` on a frozen `Group::Passive`
56
+ * thanks to [Daniel Gollahon](https://github.com/dgollahon)
57
+
58
+ ## [2.0.1] - 2020-12-20 - [Janosch Müller](mailto:janosch84@gmail.com)
59
+
60
+ ### Fixed
61
+
62
+ - fixed error when scanning some group names
63
+ * this affected names containing hyphens, digits or multibyte chars, e.g. `/(?<a1>a)/`
64
+ * thanks to [Daniel Gollahon](https://github.com/dgollahon) for the report
65
+ - fixed error when scanning hex escapes with just one hex digit
66
+ * e.g. `/\x0A/` was scanned correctly, but the equivalent `/\xA/` was not
67
+ * thanks to [Daniel Gollahon](https://github.com/dgollahon) for the report
68
+
69
+ ## [2.0.0] - 2020-11-25 - [Janosch Müller](mailto:janosch84@gmail.com)
70
+
71
+ ### Changed
72
+
73
+ - some methods that used to return byte-based indices now return char-based indices
74
+ * the returned values have only changed for Regexps that contain multibyte chars
75
+ * this is only a breaking change if you used such methods directly AND relied on them pointing to bytes
76
+ * affected methods:
77
+ * `Regexp::Token` `#length`, `#offset`, `#te`, `#ts`
78
+ * `Regexp::Expression::Base` `#full_length`, `#offset`, `#starts_at`, `#te`, `#ts`
79
+ * thanks to [Akinori MUSHA](https://github.com/knu) for the report
80
+ - removed some deprecated methods/signatures
81
+ * these are rarely used and have been showing deprecation warnings for a long time
82
+ * `Regexp::Expression::Subexpression.new` with 3 arguments
83
+ * `Regexp::Expression::Root.new` without a token argument
84
+ * `Regexp::Expression.parsed`
85
+
86
+ ### Added
87
+
88
+ - `Regexp::Expression::Base#base_length`
89
+ * returns the character count of an expression body, ignoring any quantifier
90
+ - pragmatic, experimental support for chained quantifiers
91
+ * e.g.: `/^a{10}{4,6}$/` matches exactly 40, 50 or 60 `a`s
92
+ * successive quantifiers used to be silently dropped by the parser
93
+ * they are now wrapped with passive groups as if they were written `(?:a{10}){4,6}`
94
+ * thanks to [calfeld](https://github.com/calfeld) for reporting this a while back
95
+
96
+ ### Fixed
97
+
98
+ - incorrect encoding output for non-ascii comments
99
+ * this led to a crash when calling `#to_s` on parse results containing such comments
100
+ * thanks to [Michael Glass](https://github.com/michaelglass) for the report
101
+ - some crashes when scanning contrived patterns such as `'\😋'`
102
+
3
103
  ### [1.8.2] - 2020-10-11 - [Janosch Müller](mailto:janosch84@gmail.com)
4
104
 
5
105
  ### 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
@@ -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
@@ -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
@@ -7,16 +7,6 @@ module Regexp::Expression
7
7
  # Used as the base class for the Alternation alternatives, Conditional
8
8
  # branches, and CharacterSet::Intersection intersected sequences.
9
9
  class Sequence < Regexp::Expression::Subexpression
10
- # TODO: this override is here for backwards compatibility, remove in 2.0.0
11
- def initialize(*args)
12
- if args.count == 3
13
- warn('WARNING: Sequence.new without a Regexp::Token argument is '\
14
- 'deprecated and will be removed in 2.0.0.')
15
- return self.class.at_levels(*args)
16
- end
17
- super
18
- end
19
-
20
10
  class << self
21
11
  def add_to(subexpression, params = {}, active_opts = {})
22
12
  sequence = at_levels(
@@ -51,17 +41,11 @@ module Regexp::Expression
51
41
  alias :ts :starts_at
52
42
 
53
43
  def quantify(token, text, min = nil, max = nil, mode = :greedy)
54
- offset = -1
55
- target = expressions[offset]
56
- while target.is_a?(FreeSpace)
57
- target = expressions[offset -= 1]
58
- end
59
-
60
- target || raise(ArgumentError, "No valid target found for '#{text}' "\
61
- 'quantifier')
44
+ target = expressions.reverse.find { |exp| !exp.is_a?(FreeSpace) }
45
+ target or raise Regexp::Parser::Error,
46
+ "No valid target found for '#{text}' quantifier"
62
47
 
63
48
  target.quantify(token, text, min, max, mode)
64
49
  end
65
50
  end
66
-
67
51
  end