// Copyright 2017 the V8 project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#ifndef V8_TORQUE_DECLARABLE_H_
#define V8_TORQUE_DECLARABLE_H_

#include <cassert>
#include <string>

#include "src/base/functional.h"
#include "src/base/logging.h"
#include "src/torque/ast.h"
#include "src/torque/types.h"
#include "src/torque/utils.h"

namespace v8 {
namespace internal {
namespace torque {

class Scope;
class ScopeChain;
class Generic;

class Declarable {
 public:
  virtual ~Declarable() {}
  enum Kind {
    kVariable,
    kParameter,
    kMacro,
    kMacroList,
    kBuiltin,
    kRuntimeFunction,
    kGeneric,
    kGenericList,
    kTypeAlias,
    kLabel,
    kExternConstant,
    kModuleConstant
  };
  Kind kind() const { return kind_; }
  bool IsMacro() const { return kind() == kMacro; }
  bool IsBuiltin() const { return kind() == kBuiltin; }
  bool IsRuntimeFunction() const { return kind() == kRuntimeFunction; }
  bool IsGeneric() const { return kind() == kGeneric; }
  bool IsTypeAlias() const { return kind() == kTypeAlias; }
  bool IsParameter() const { return kind() == kParameter; }
  bool IsLabel() const { return kind() == kLabel; }
  bool IsVariable() const { return kind() == kVariable; }
  bool IsMacroList() const { return kind() == kMacroList; }
  bool IsGenericList() const { return kind() == kGenericList; }
  bool IsExternConstant() const { return kind() == kExternConstant; }
  bool IsModuleConstant() const { return kind() == kModuleConstant; }
  bool IsValue() const {
    return IsVariable() || IsExternConstant() || IsParameter() ||
           IsModuleConstant();
  }
  virtual const char* type_name() const { return "<<unknown>>"; }

 protected:
  explicit Declarable(Kind kind) : kind_(kind) {}

 private:
  const Kind kind_;
};

#define DECLARE_DECLARABLE_BOILERPLATE(x, y)                  \
  static x* cast(Declarable* declarable) {                    \
    DCHECK(declarable->Is##x());                              \
    return static_cast<x*>(declarable);                       \
  }                                                           \
  static const x* cast(const Declarable* declarable) {        \
    DCHECK(declarable->Is##x());                              \
    return static_cast<const x*>(declarable);                 \
  }                                                           \
  const char* type_name() const override { return #y; }       \
  static x* DynamicCast(Declarable* declarable) {             \
    if (!declarable) return nullptr;                          \
    if (!declarable->Is##x()) return nullptr;                 \
    return static_cast<x*>(declarable);                       \
  }                                                           \
  static const x* DynamicCast(const Declarable* declarable) { \
    if (!declarable) return nullptr;                          \
    if (!declarable->Is##x()) return nullptr;                 \
    return static_cast<const x*>(declarable);                 \
  }

class Value : public Declarable {
 public:
  const std::string& name() const { return name_; }
  virtual bool IsConst() const { return true; }
  virtual std::string value() const = 0;
  virtual std::string RValue() const { return value(); }
  DECLARE_DECLARABLE_BOILERPLATE(Value, value);
  const Type* type() const { return type_; }

 protected:
  Value(Kind kind, const Type* type, const std::string& name)
      : Declarable(kind), type_(type), name_(name) {}

 private:
  const Type* type_;
  std::string name_;
};

class Parameter : public Value {
 public:
  DECLARE_DECLARABLE_BOILERPLATE(Parameter, parameter);
  std::string value() const override { return var_name_; }

 private:
  friend class Declarations;
  Parameter(const std::string& name, const Type* type,
            const std::string& var_name)
      : Value(Declarable::kParameter, type, name), var_name_(var_name) {}

  std::string var_name_;
};

class ModuleConstant : public Value {
 public:
  DECLARE_DECLARABLE_BOILERPLATE(ModuleConstant, constant);
  std::string value() const override { UNREACHABLE(); }
  std::string RValue() const override { return name() + "()"; }

 private:
  friend class Declarations;
  explicit ModuleConstant(const std::string& name, const Type* type)
      : Value(Declarable::kModuleConstant, type, name) {}
};

class Variable : public Value {
 public:
  DECLARE_DECLARABLE_BOILERPLATE(Variable, variable);
  bool IsConst() const override { return const_; }
  std::string value() const override { return value_; }
  std::string RValue() const override;
  void Define() {
    if (defined_ && IsConst()) {
      ReportError("Cannot re-define a const-bound variable.");
    }
    defined_ = true;
  }
  bool IsDefined() const { return defined_; }

 private:
  friend class Declarations;
  Variable(const std::string& name, const std::string& value, const Type* type,
           bool is_const)
      : Value(Declarable::kVariable, type, name),
        value_(value),
        defined_(false),
        const_(is_const) {
    DCHECK_IMPLIES(type->IsConstexpr(), IsConst());
  }

  std::string value_;
  bool defined_;
  bool const_;
};

class Label : public Declarable {
 public:
  void AddVariable(Variable* var) { parameters_.push_back(var); }
  std::string name() const { return name_; }
  std::string generated() const { return generated_; }
  Variable* GetParameter(size_t i) const { return parameters_[i]; }
  size_t GetParameterCount() const { return parameters_.size(); }
  const std::vector<Variable*>& GetParameters() const { return parameters_; }

  DECLARE_DECLARABLE_BOILERPLATE(Label, label);
  void MarkUsed() { used_ = true; }
  bool IsUsed() const { return used_; }

 private:
  friend class Declarations;
  explicit Label(const std::string& name)
      : Declarable(Declarable::kLabel),
        name_(name),
        generated_("label_" + name + "_" + std::to_string(next_id_++)),
        used_(false) {}

  std::string name_;
  std::string generated_;
  std::vector<Variable*> parameters_;
  static size_t next_id_;
  bool used_;
};

class ExternConstant : public Value {
 public:
  DECLARE_DECLARABLE_BOILERPLATE(ExternConstant, constant);
  std::string value() const override { return value_; }

 private:
  friend class Declarations;
  explicit ExternConstant(const std::string& name, const Type* type,
                          const std::string& value)
      : Value(Declarable::kExternConstant, type, name), value_(value) {}

  std::string value_;
};

class Callable : public Declarable {
 public:
  static Callable* cast(Declarable* declarable) {
    assert(declarable->IsMacro() || declarable->IsBuiltin() ||
           declarable->IsRuntimeFunction());
    return static_cast<Callable*>(declarable);
  }
  static const Callable* cast(const Declarable* declarable) {
    assert(declarable->IsMacro() || declarable->IsBuiltin() ||
           declarable->IsRuntimeFunction());
    return static_cast<const Callable*>(declarable);
  }
  const std::string& name() const { return name_; }
  const Signature& signature() const { return signature_; }
  const NameVector& parameter_names() const {
    return signature_.parameter_names;
  }
  bool HasReturnValue() const {
    return !signature_.return_type->IsVoidOrNever();
  }
  void IncrementReturns() { ++returns_; }
  bool HasReturns() const { return returns_; }
  base::Optional<Generic*> generic() const { return generic_; }

 protected:
  Callable(Declarable::Kind kind, const std::string& name,
           const Signature& signature, base::Optional<Generic*> generic)
      : Declarable(kind),
        name_(name),
        signature_(signature),
        returns_(0),
        generic_(generic) {}

 private:
  std::string name_;
  Signature signature_;
  size_t returns_;
  base::Optional<Generic*> generic_;
};

class Macro : public Callable {
 public:
  DECLARE_DECLARABLE_BOILERPLATE(Macro, macro);

 private:
  friend class Declarations;
  Macro(const std::string& name, const Signature& signature,
        base::Optional<Generic*> generic)
      : Callable(Declarable::kMacro, name, signature, generic) {
    if (signature.parameter_types.var_args) {
      ReportError("Varargs are not supported for macros.");
    }
  }
};

class MacroList : public Declarable {
 public:
  DECLARE_DECLARABLE_BOILERPLATE(MacroList, macro_list);
  const std::vector<Macro*>& list() { return list_; }
  Macro* AddMacro(Macro* macro) {
    list_.emplace_back(macro);
    return macro;
  }

 private:
  friend class Declarations;
  MacroList() : Declarable(Declarable::kMacroList) {}

  std::vector<Macro*> list_;
};

class Builtin : public Callable {
 public:
  enum Kind { kStub, kFixedArgsJavaScript, kVarArgsJavaScript };
  DECLARE_DECLARABLE_BOILERPLATE(Builtin, builtin);
  Kind kind() const { return kind_; }
  bool IsStub() const { return kind_ == kStub; }
  bool IsVarArgsJavaScript() const { return kind_ == kVarArgsJavaScript; }
  bool IsFixedArgsJavaScript() const { return kind_ == kFixedArgsJavaScript; }
  bool IsExternal() const { return external_; }

 private:
  friend class Declarations;
  Builtin(const std::string& name, Builtin::Kind kind, bool external,
          const Signature& signature, base::Optional<Generic*> generic)
      : Callable(Declarable::kBuiltin, name, signature, generic),
        kind_(kind),
        external_(external) {}

  Kind kind_;
  bool external_;
};

class RuntimeFunction : public Callable {
 public:
  DECLARE_DECLARABLE_BOILERPLATE(RuntimeFunction, runtime);

 private:
  friend class Declarations;
  RuntimeFunction(const std::string& name, const Signature& signature,
                  base::Optional<Generic*> generic)
      : Callable(Declarable::kRuntimeFunction, name, signature, generic) {}
};

class Generic : public Declarable {
 public:
  DECLARE_DECLARABLE_BOILERPLATE(Generic, generic);

  GenericDeclaration* declaration() const { return declaration_; }
  const std::string& name() const { return name_; }
  Module* module() const { return module_; }

 private:
  friend class Declarations;
  Generic(const std::string& name, Module* module,
          GenericDeclaration* declaration)
      : Declarable(Declarable::kGeneric),
        name_(name),
        module_(module),
        declaration_(declaration) {}

  std::string name_;
  Module* module_;
  GenericDeclaration* declaration_;
};

class GenericList : public Declarable {
 public:
  DECLARE_DECLARABLE_BOILERPLATE(GenericList, generic_list);
  const std::vector<Generic*>& list() { return list_; }
  Generic* AddGeneric(Generic* generic) {
    list_.push_back(generic);
    return generic;
  }

 private:
  friend class Declarations;
  GenericList() : Declarable(Declarable::kGenericList) {}

  std::vector<Generic*> list_;
};

typedef std::pair<Generic*, TypeVector> SpecializationKey;

class TypeAlias : public Declarable {
 public:
  DECLARE_DECLARABLE_BOILERPLATE(TypeAlias, type_alias);

  const Type* type() const { return type_; }

 private:
  friend class Declarations;
  explicit TypeAlias(const Type* type)
      : Declarable(Declarable::kTypeAlias), type_(type) {}

  const Type* type_;
};

void PrintLabel(std::ostream& os, const Label& l, bool with_names);

std::ostream& operator<<(std::ostream& os, const Callable& m);
std::ostream& operator<<(std::ostream& os, const Variable& v);
std::ostream& operator<<(std::ostream& os, const Builtin& b);
std::ostream& operator<<(std::ostream& os, const Label& l);
std::ostream& operator<<(std::ostream& os, const RuntimeFunction& b);
std::ostream& operator<<(std::ostream& os, const Generic& g);

#undef DECLARE_DECLARABLE_BOILERPLATE

}  // namespace torque
}  // namespace internal
}  // namespace v8

#endif  // V8_TORQUE_DECLARABLE_H_
