module Net::IMAP::ResponseParser::ParserUtils::Generator

def def_char_matchers(name, char, token)

we can skip lexer for single character matches, as a shortcut
def def_char_matchers(name, char, token)
  byte = char.ord
  match_name = name.match(/\A[A-Z]/) ? "#{name}!" : name
  char = char.dump
  class_eval <<~RUBY, __FILE__, __LINE__ + 1
    # frozen_string_literal: true
    # force use of #next_token; no string peeking
    def lookahead_#{name}?
      #{LOOKAHEAD}&.symbol == #{token}
    end
    # use token or string peek
    def peek_#{name}?
      @token ? @token.symbol == #{token} : @str.getbyte(@pos) == #{byte}
    end
    # like accept(token_symbols); returns token or nil
    def #{name}?
      if @token&.symbol == #{token}
        #{SHIFT_TOKEN}
        #{char}
      elsif !@token && @str.getbyte(@pos) == #{byte}
        @pos += 1
        #{char}
      end
    end
    # like match(token_symbols); returns token or raises parse_error
    def #{match_name}
      if @token&.symbol == #{token}
        #{SHIFT_TOKEN}
        #{char}
      elsif !@token && @str.getbyte(@pos) == #{byte}
        @pos += 1
        #{char}
      else
        parse_error("unexpected %s (expected %p)",
                    @token&.symbol || @str[@pos].inspect, #{char})
      end
    end
  RUBY
end

def def_token_matchers(name, *token_symbols, coerce: nil, send: nil)

TODO: move coersion to the token.value method?
def def_token_matchers(name, *token_symbols, coerce: nil, send: nil)
  match_name = name.match(/\A[A-Z]/) ? "#{name}!" : name
  if token_symbols.size == 1
    token   = token_symbols.first
    matcher = "token&.symbol == %p" % [token]
    desc    = token
  else
    matcher = "%p.include? token&.symbol" % [token_symbols]
    desc    = token_symbols.join(" or ")
  end
  value = "(token.value)"
  value = coerce.to_s + value   if coerce
  value = [value, send].join(".") if send
  raise_parse_error = <<~RUBY
    parse_error("unexpected %s (expected #{desc})", token&.symbol)
  RUBY
  class_eval <<~RUBY, __FILE__, __LINE__ + 1
    # frozen_string_literal: true
    # lookahead version of match, returning the value
    def lookahead_#{name}!
      token = #{LOOKAHEAD}
      if #{matcher}
        #{value}
      else
        #{raise_parse_error}
      end
    end
    def #{name}?
      token = #{LOOKAHEAD}
      if #{matcher}
        #{SHIFT_TOKEN}
        #{value}
      end
    end
    def #{match_name}
      token = #{LOOKAHEAD}
      if #{matcher}
        #{SHIFT_TOKEN}
        #{value}
      else
        #{raise_parse_error}
      end
    end
  RUBY
end