//===--- 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, OptSpecifier _ID, const char *_Name,
               const OptionGroup *_Group, const Option *_Alias)
  : Kind(_Kind), ID(_ID.getID()), Name(_Name), Group(_Group), Alias(_Alias),
    Unsupported(false), LinkerInput(false), NoOptAsInput(false),
    DriverOption(false), NoArgumentUnused(false), NoForward(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.");

  // Initialize rendering options based on the class.
  switch (Kind) {
  case GroupClass:
  case InputClass:
  case UnknownClass:
    RenderStyle = RenderValuesStyle;
    break;

  case JoinedClass:
  case JoinedAndSeparateClass:
    RenderStyle = RenderJoinedStyle;
    break;

  case CommaJoinedClass:
    RenderStyle = RenderCommaJoinedStyle;
    break;

  case FlagClass:
  case SeparateClass:
  case MultiArgClass:
  case JoinedOrSeparateClass:
    RenderStyle = RenderSeparateStyle;
    break;
  }
}

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(OptSpecifier Opt) const {
  // Aliases are never considered in matching, look through them.
  if (Alias)
    return Alias->matches(Opt);

  // Check exact match.
  if (ID == Opt)
    return true;

  if (Group)
    return Group->matches(Opt);
  return false;
}

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

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

InputOption::InputOption(OptSpecifier ID)
  : Option(Option::InputClass, ID, "<input>", 0, 0) {
}

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

UnknownOption::UnknownOption(OptSpecifier ID)
  : Option(Option::UnknownClass, ID, "<unknown>", 0, 0) {
}

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

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

Arg *FlagOption::accept(const ArgList &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 Arg(getUnaliasedOption(), Index++);
}

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

Arg *JoinedOption::accept(const ArgList &Args, unsigned &Index) const {
  // Always matches.
  const char *Value = Args.getArgString(Index) + strlen(getName());
  return new Arg(getUnaliasedOption(), Index++, Value);
}

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

Arg *CommaJoinedOption::accept(const ArgList &Args,
                               unsigned &Index) const {
  // Always matches.
  const char *Str = Args.getArgString(Index) + strlen(getName());
  Arg *A = new Arg(getUnaliasedOption(), Index++);

  // Parse out the comma separated values.
  const char *Prev = Str;
  for (;; ++Str) {
    char c = *Str;

    if (!c || c == ',') {
      if (Prev != Str) {
        char *Value = new char[Str - Prev + 1];
        memcpy(Value, Prev, Str - Prev);
        Value[Str - Prev] = '\0';
        A->getValues().push_back(Value);
      }

      if (!c)
        break;

      Prev = Str + 1;
    }
  }
  A->setOwnsValues(true);

  return A;
}

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

Arg *SeparateOption::accept(const ArgList &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 Arg(getUnaliasedOption(), Index - 2, Args.getArgString(Index - 1));
}

MultiArgOption::MultiArgOption(OptSpecifier 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 ArgList &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;

  Arg *A = new Arg(getUnaliasedOption(), Index - 1 - NumArgs,
                   Args.getArgString(Index - NumArgs));
  for (unsigned i = 1; i != NumArgs; ++i)
    A->getValues().push_back(Args.getArgString(Index - NumArgs + i));
  return A;
}

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

Arg *JoinedOrSeparateOption::accept(const ArgList &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))) {
    const char *Value = Args.getArgString(Index) + strlen(getName());
    return new Arg(this, Index++, Value);
  }

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

  return new Arg(getUnaliasedOption(), Index - 2, Args.getArgString(Index - 1));
}

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

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

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

  return new Arg(getUnaliasedOption(), Index - 2,
                 Args.getArgString(Index-2)+strlen(getName()),
                 Args.getArgString(Index-1));
}
