class Gem::RequestSet::Lockfile::Parser

frozen_string_literal: true

def get expected_types = nil, expected_value = nil # :nodoc:

:nodoc:
def get expected_types = nil, expected_value = nil # :nodoc:
  token = @tokens.shift
  if expected_types and not Array(expected_types).include? token.type then
    unget token
    message = "unexpected token [#{token.type.inspect}, #{token.value.inspect}], " +
              "expected #{expected_types.inspect}"
    raise Gem::RequestSet::Lockfile::ParseError.new message, token.column, token.line, @filename
  end
  if expected_value and expected_value != token.value then
    unget token
    message = "unexpected token [#{token.type.inspect}, #{token.value.inspect}], " +
              "expected [#{expected_types.inspect}, " +
              "#{expected_value.inspect}]"
    raise Gem::RequestSet::Lockfile::ParseError.new message, token.column, token.line, @filename
  end
  token
end

def initialize tokenizer, set, platforms, filename = nil

def initialize tokenizer, set, platforms, filename = nil
  @tokens    = tokenizer
  @filename  = filename
  @set       = set
  @platforms = platforms
end

def parse

def parse
  until @tokens.empty? do
    token = get
    case token.type
    when :section then
      @tokens.skip :newline
      case token.value
      when 'DEPENDENCIES' then
        parse_DEPENDENCIES
      when 'GIT' then
        parse_GIT
      when 'GEM' then
        parse_GEM
      when 'PATH' then
        parse_PATH
      when 'PLATFORMS' then
        parse_PLATFORMS
      else
        token = get until @tokens.empty? or peek.first == :section
      end
    else
      raise "BUG: unhandled token #{token.type} (#{token.value.inspect}) at line #{token.line} column #{token.column}"
    end
  end
end

def parse_DEPENDENCIES # :nodoc:

:nodoc:
def parse_DEPENDENCIES # :nodoc:
  while not @tokens.empty? and :text == peek.type do
    token = get :text
    requirements = []
    case peek[0]
    when :bang then
      get :bang
      requirements << pinned_requirement(token.value)
    when :l_paren then
      get :l_paren
      loop do
        op      = get(:requirement).value
        version = get(:text).value
        requirements << "#{op} #{version}"
        break unless peek.type == :comma
        get :comma
      end
      get :r_paren
      if peek[0] == :bang then
        requirements.clear
        requirements << pinned_requirement(token.value)
        get :bang
      end
    end
    @set.gem token.value, *requirements
    skip :newline
  end
end

def parse_GEM # :nodoc:

:nodoc:
def parse_GEM # :nodoc:
  sources = []
  while [:entry, 'remote'] == peek.first(2) do
    get :entry, 'remote'
    data = get(:text).value
    skip :newline
    sources << Gem::Source.new(data)
  end
  sources << Gem::Source.new(Gem::DEFAULT_HOST) if sources.empty?
  get :entry, 'specs'
  skip :newline
  set = Gem::Resolver::LockSet.new sources
  last_specs = nil
  while not @tokens.empty? and :text == peek.type do
    token = get :text
    name = token.value
    column = token.column
    case peek[0]
    when :newline then
      last_specs.each do |spec|
        spec.add_dependency Gem::Dependency.new name if column == 6
      end
    when :l_paren then
      get :l_paren
      token = get [:text, :requirement]
      type = token.type
      data = token.value
      if type == :text and column == 4 then
        version, platform = data.split '-', 2
        platform =
          platform ? Gem::Platform.new(platform) : Gem::Platform::RUBY
        last_specs = set.add name, version, platform
      else
        dependency = parse_dependency name, data
        last_specs.each do |spec|
          spec.add_dependency dependency
        end
      end
      get :r_paren
    else
      raise "BUG: unknown token #{peek}"
    end
    skip :newline
  end
  @set.sets << set
end

def parse_GIT # :nodoc:

:nodoc:
def parse_GIT # :nodoc:
  get :entry, 'remote'
  repository = get(:text).value
  skip :newline
  get :entry, 'revision'
  revision = get(:text).value
  skip :newline
  type = peek.type
  value = peek.value
  if type == :entry and %w[branch ref tag].include? value then
    get
    get :text
    skip :newline
  end
  get :entry, 'specs'
  skip :newline
  set = Gem::Resolver::GitSet.new
  set.root_dir = @set.install_dir
  last_spec = nil
  while not @tokens.empty? and :text == peek.type do
    token = get :text
    name = token.value
    column = token.column
    case peek[0]
    when :newline then
      last_spec.add_dependency Gem::Dependency.new name if column == 6
    when :l_paren then
      get :l_paren
      token = get [:text, :requirement]
      type = token.type
      data = token.value
      if type == :text and column == 4 then
        last_spec = set.add_git_spec name, data, repository, revision, true
      else
        dependency = parse_dependency name, data
        last_spec.add_dependency dependency
      end
      get :r_paren
    else
      raise "BUG: unknown token #{peek}"
    end
    skip :newline
  end
  @set.sets << set
end

def parse_PATH # :nodoc:

:nodoc:
def parse_PATH # :nodoc:
  get :entry, 'remote'
  directory = get(:text).value
  skip :newline
  get :entry, 'specs'
  skip :newline
  set = Gem::Resolver::VendorSet.new
  last_spec = nil
  while not @tokens.empty? and :text == peek.first do
    token = get :text
    name = token.value
    column = token.column
    case peek[0]
    when :newline then
      last_spec.add_dependency Gem::Dependency.new name if column == 6
    when :l_paren then
      get :l_paren
      token = get [:text, :requirement]
      type = token.type
      data = token.value
      if type == :text and column == 4 then
        last_spec = set.add_vendor_gem name, directory
      else
        dependency = parse_dependency name, data
        last_spec.dependencies << dependency
      end
      get :r_paren
    else
      raise "BUG: unknown token #{peek}"
    end
    skip :newline
  end
  @set.sets << set
end

def parse_PLATFORMS # :nodoc:

:nodoc:
def parse_PLATFORMS # :nodoc:
  while not @tokens.empty? and :text == peek.first do
    name = get(:text).value
    @platforms << name
    skip :newline
  end
end

def parse_dependency name, op # :nodoc:

:nodoc:
def parse_dependency name, op # :nodoc:
  return Gem::Dependency.new name, op unless peek[0] == :text
  version = get(:text).value
  requirements = ["#{op} #{version}"]
  while peek.type == :comma do
    get :comma
    op      = get(:requirement).value
    version = get(:text).value
    requirements << "#{op} #{version}"
  end
  Gem::Dependency.new name, requirements
end

def peek # :nodoc:

:nodoc:
def peek # :nodoc:
  @tokens.peek
end

def pinned_requirement name # :nodoc:

:nodoc:
def pinned_requirement name # :nodoc:
  requirement = Gem::Dependency.new name
  specification = @set.sets.flat_map { |set|
    set.find_all(requirement)
  }.compact.first
  specification && specification.version
end

def pinned_requirement name # :nodoc:

:nodoc:
FIXME: remove when 1.8 is dropped
def pinned_requirement name # :nodoc:
  requirement = Gem::Dependency.new name
  specification = @set.sets.map { |set|
    set.find_all(requirement)
  }.flatten(1).compact.first
  specification && specification.version
end

def skip type # :nodoc:

:nodoc:
def skip type # :nodoc:
  @tokens.skip type
end

def unget token # :nodoc:

:nodoc:
def unget token # :nodoc:
  @tokens.unshift token
end