//===--- Option.cpp - Abstract Driver Options ---------------------------*-===//
//
//                     The LLVM Compiler Infrastructure
//
// This file is distributed under the University of Illinois Open Source
// License. See LICENSE.TXT for details.
//
//===----------------------------------------------------------------------===//

#include "clang/Driver/Option.h"

#include "clang/Driver/Arg.h"
#include "clang/Driver/ArgList.h"
#include "llvm/Support/raw_ostream.h"
#include <cassert>
#include <algorithm>
using namespace clang::driver;

Option::Option(OptionClass _Kind, options::ID _ID, const char *_Name,
               const OptionGroup *_Group, const Option *_Alias) 
  : Kind(_Kind), ID(_ID), Name(_Name), Group(_Group), Alias(_Alias),
    Unsupported(false), LinkerInput(false), NoOptAsInput(false),
    ForceSeparateRender(false), ForceJoinedRender(false),
    DriverOption(false)
{

  // Multi-level aliases are not supported, and alias options cannot
  // have groups. This just simplifies option tracking, it is not an
  // inherent limitation.
  assert((!Alias || (!Alias->Alias && !Group)) &&
         "Multi-level aliases and aliases with groups are unsupported.");    
}

Option::~Option() {
}

void Option::dump() const {
  llvm::errs() << "<";
  switch (Kind) {
  default:
    assert(0 && "Invalid kind");
#define P(N) case N: llvm::errs() << #N; break
    P(GroupClass);
    P(InputClass);
    P(UnknownClass);
    P(FlagClass);
    P(JoinedClass);
    P(SeparateClass);
    P(CommaJoinedClass);
    P(MultiArgClass);
    P(JoinedOrSeparateClass);
    P(JoinedAndSeparateClass);
#undef P
  }

  llvm::errs() << " Name:\"" << Name << '"';

  if (Group) {
    llvm::errs() << " Group:";
    Group->dump();
  }
  
  if (Alias) {
    llvm::errs() << " Alias:";
    Alias->dump();
  }
  
  if (const MultiArgOption *MOA = dyn_cast<MultiArgOption>(this))
    llvm::errs() << " NumArgs:" << MOA->getNumArgs();

  llvm::errs() << ">\n";
}

bool Option::matches(const Option *Opt) const {
  // Aliases are never considered in matching.
  if (Opt->getAlias())
    return matches(Opt->getAlias());
  if (Alias)
    return Alias->matches(Opt);
  
  if (this == Opt)
    return true;
  
  if (Group)
    return Group->matches(Opt);
  return false;
}

bool Option::matches(options::ID Id) const {
  // FIXME: Decide what to do here; we should either pull out the
  // handling of alias on the option for Id from the other matches, or
  // find some other solution (which hopefully doesn't require using
  // the option table).
  if (Alias)
    return Alias->matches(Id);
  
  if (ID == Id)
    return true;
  
  if (Group)
    return Group->matches(Id);
  return false;
}

OptionGroup::OptionGroup(options::ID ID, const char *Name, 
                         const OptionGroup *Group)
  : Option(Option::GroupClass, ID, Name, Group, 0) {
}

Arg *OptionGroup::accept(const InputArgList &Args, unsigned &Index) const {
  assert(0 && "accept() should never be called on an OptionGroup");
  return 0;
}

InputOption::InputOption()
  : Option(Option::InputClass, options::OPT_INPUT, "<input>", 0, 0) {
}

Arg *InputOption::accept(const InputArgList &Args, unsigned &Index) const {
  assert(0 && "accept() should never be called on an InputOption");
  return 0;
}

UnknownOption::UnknownOption()
  : Option(Option::UnknownClass, options::OPT_UNKNOWN, "<unknown>", 0, 0) {
}

Arg *UnknownOption::accept(const InputArgList &Args, unsigned &Index) const {
  assert(0 && "accept() should never be called on an UnknownOption");
  return 0;
}

FlagOption::FlagOption(options::ID ID, const char *Name, 
                       const OptionGroup *Group, const Option *Alias)
  : Option(Option::FlagClass, ID, Name, Group, Alias) {
}

Arg *FlagOption::accept(const InputArgList &Args, unsigned &Index) const {
  // Matches iff this is an exact match.  
  // FIXME: Avoid strlen.
  if (strlen(getName()) != strlen(Args.getArgString(Index)))
    return 0;

  return new FlagArg(this, Index++);
}

JoinedOption::JoinedOption(options::ID ID, const char *Name, 
                           const OptionGroup *Group, const Option *Alias)
  : Option(Option::JoinedClass, ID, Name, Group, Alias) {
}

Arg *JoinedOption::accept(const InputArgList &Args, unsigned &Index) const {
  // Always matches.
  return new JoinedArg(this, Index++);
}

CommaJoinedOption::CommaJoinedOption(options::ID ID, const char *Name, 
                                     const OptionGroup *Group, 
                                     const Option *Alias)
  : Option(Option::CommaJoinedClass, ID, Name, Group, Alias) {
}

Arg *CommaJoinedOption::accept(const InputArgList &Args, 
                               unsigned &Index) const {
  // Always matches. We count the commas now so we can answer
  // getNumValues easily.
  
  // Get the suffix string.
  // FIXME: Avoid strlen, and move to helper method?
  const char *Suffix = Args.getArgString(Index) + strlen(getName());
  return new CommaJoinedArg(this, Index++, Suffix);
}

SeparateOption::SeparateOption(options::ID ID, const char *Name, 
                               const OptionGroup *Group, const Option *Alias)
  : Option(Option::SeparateClass, ID, Name, Group, Alias) {
}

Arg *SeparateOption::accept(const InputArgList &Args, unsigned &Index) const {
  // Matches iff this is an exact match.  
  // FIXME: Avoid strlen.
  if (strlen(getName()) != strlen(Args.getArgString(Index)))
    return 0;

  Index += 2;
  if (Index > Args.getNumInputArgStrings())
    return 0;

  return new SeparateArg(this, Index - 2, 1);
}

MultiArgOption::MultiArgOption(options::ID ID, const char *Name, 
                               const OptionGroup *Group, const Option *Alias, 
                               unsigned _NumArgs)
  : Option(Option::MultiArgClass, ID, Name, Group, Alias), NumArgs(_NumArgs) {
  assert(NumArgs > 1  && "Invalid MultiArgOption!");
}

Arg *MultiArgOption::accept(const InputArgList &Args, unsigned &Index) const {
  // Matches iff this is an exact match.  
  // FIXME: Avoid strlen.
  if (strlen(getName()) != strlen(Args.getArgString(Index)))
    return 0;

  Index += 1 + NumArgs;
  if (Index > Args.getNumInputArgStrings())
    return 0;

  return new SeparateArg(this, Index - 1 - NumArgs, NumArgs);
}

JoinedOrSeparateOption::JoinedOrSeparateOption(options::ID ID, const char *Name,
                                               const OptionGroup *Group, 
                                               const Option *Alias)
  : Option(Option::JoinedOrSeparateClass, ID, Name, Group, Alias) {
}

Arg *JoinedOrSeparateOption::accept(const InputArgList &Args, 
                                    unsigned &Index) const {
  // If this is not an exact match, it is a joined arg.
  // FIXME: Avoid strlen.
  if (strlen(getName()) != strlen(Args.getArgString(Index)))
    return new JoinedArg(this, Index++);

  // Otherwise it must be separate.
  Index += 2;
  if (Index > Args.getNumInputArgStrings())
    return 0;

  return new SeparateArg(this, Index - 2, 1);  
}

JoinedAndSeparateOption::JoinedAndSeparateOption(options::ID ID,
                                                 const char *Name, 
                                                 const OptionGroup *Group, 
                                                 const Option *Alias)
  : Option(Option::JoinedAndSeparateClass, ID, Name, Group, Alias) {
}

Arg *JoinedAndSeparateOption::accept(const InputArgList &Args, 
                                     unsigned &Index) const {
  // Always matches.

  Index += 2;
  if (Index > Args.getNumInputArgStrings())
    return 0;

  return new JoinedAndSeparateArg(this, Index - 2);
}

