ammar/regexp_parser

View on GitHub
lib/regexp_parser/expression/quantifier.rb

Summary

Maintainability
A
35 mins
Test Coverage
module Regexp::Expression
  # TODO: in v3.0.0, maybe put Shared back into Base, and inherit from Base and
  # call super in #initialize, but raise in #quantifier= and #quantify,
  # or introduce an Expression::Quantifiable intermediate class.
  # Or actually allow chaining as a more concise but tricky solution than PR#69.
  class Quantifier
    include Regexp::Expression::Shared

    MODES = %i[greedy possessive reluctant]

    def initialize(*args)
      deprecated_old_init(*args) and return if args.count == 4 || args.count == 5

      init_from_token_and_options(*args)
      # TODO: remove in v3.0.0, stop removing parts of #token (?)
      self.token = token.to_s.sub(/_(greedy|possessive|reluctant)/, '').to_sym
    end

    def to_h
      {
        token: token,
        text:  text,
        mode:  mode,
        min:   min,
        max:   max,
      }
    end

    MODES.each do |mode|
      class_eval <<-RUBY, __FILE__, __LINE__ + 1
        def #{mode}?
          mode.equal?(:#{mode})
        end
      RUBY
    end
    alias :lazy? :reluctant?

    def min
      derived_data[:min]
    end

    def max
      derived_data[:max]
    end

    def mode
      derived_data[:mode]
    end

    private

    def deprecated_old_init(token, text, _min, _max, _mode = :greedy)
      warn "Calling `Expression::Base#quantify` or `#{self.class}.new` with 4+ arguments "\
           "is deprecated.\nIt will no longer be supported in regexp_parser v3.0.0.\n"\
           "Please pass a Regexp::Token instead, e.g. replace `token, text, min, max, mode` "\
           "with `::Regexp::Token.new(:quantifier, token, text)`. min, max, and mode "\
           "will be derived automatically.\n"\
           "Or do `exp.quantifier = #{self.class}.construct(token: token, text: str)`.\n"\
           "This is consistent with how Expression::Base instances are created. "
      @token = token
      @text  = text
    end

    def derived_data
      @derived_data ||= begin
        min, max =
          case text[0]
          when '?'; [0, 1]
          when '*'; [0, -1]
          when '+'; [1, -1]
          else
            int_min = text[/\{(\d*)/, 1]
            int_max = text[/,?(\d*)\}/, 1]
            [int_min.to_i, (int_max.empty? ? -1 : int_max.to_i)]
          end

        mod = text[/.([?+])/, 1]
        mode = (mod == '?' && :reluctant) || (mod == '+' && :possessive) || :greedy

        { min: min, max: max, mode: mode }
      end
    end
  end
end