// 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.

#include "src/torque/declarations.h"
#include "src/torque/declarable.h"
#include "src/torque/type-oracle.h"

namespace v8 {
namespace internal {
namespace torque {

Scope* Declarations::GetModuleScope(const Module* module) {
  auto i = module_scopes_.find(module);
  if (i != module_scopes_.end()) return i->second;
  Scope* result = chain_.NewScope();
  module_scopes_[module] = result;
  return result;
}

Scope* Declarations::GetNodeScope(const AstNode* node, bool reset_scope) {
  std::pair<const AstNode*, TypeVector> key(
      node, current_generic_specialization_ == nullptr
                ? TypeVector()
                : current_generic_specialization_->second);
  if (!reset_scope) {
    auto i = scopes_.find(key);
    if (i != scopes_.end()) return i->second;
  }
  Scope* result = chain_.NewScope();
  scopes_[key] = result;
  return result;
}

Scope* Declarations::GetGenericScope(Generic* generic,
                                     const TypeVector& types) {
  std::pair<const AstNode*, TypeVector> key(generic->declaration()->callable,
                                            types);
  auto i = scopes_.find(key);
  if (i != scopes_.end()) return i->second;
  Scope* result = chain_.NewScope();
  scopes_[key] = result;
  return result;
}

bool Declarations::IsDeclaredInCurrentScope(const std::string& name) {
  return chain_.ShallowLookup(name) != nullptr;
}

void Declarations::CheckAlreadyDeclared(const std::string& name,
                                        const char* new_type) {
  if (IsDeclaredInCurrentScope(name)) {
    std::stringstream s;
    s << "cannot redeclare " << name << " (type " << new_type << ")";
    ReportError(s.str());
  }
}

const Type* Declarations::LookupType(const std::string& name) {
  Declarable* raw = Lookup(name);
  if (raw->IsTypeAlias()) {
    return TypeAlias::cast(raw)->type();
  }
  std::stringstream s;
  s << "declaration \"" << name << "\" is not a Type";
  ReportError(s.str());
  return nullptr;
}

const Type* Declarations::LookupGlobalType(const std::string& name) {
  Declarable* raw = LookupGlobalScope(name);
  if (!raw->IsTypeAlias()) {
    std::stringstream s;
    s << "declaration \"" << name << "\" is not a Type";
    ReportError(s.str());
  }
  return TypeAlias::cast(raw)->type();
}

const Type* Declarations::GetType(TypeExpression* type_expression) {
  if (auto* basic = BasicTypeExpression::DynamicCast(type_expression)) {
    std::string name =
        (basic->is_constexpr ? CONSTEXPR_TYPE_PREFIX : "") + basic->name;
    return LookupType(name);
  } else if (auto* union_type = UnionTypeExpression::cast(type_expression)) {
    return TypeOracle::GetUnionType(GetType(union_type->a),
                                    GetType(union_type->b));
  } else {
    auto* function_type_exp = FunctionTypeExpression::cast(type_expression);
    TypeVector argument_types;
    for (TypeExpression* type_exp : function_type_exp->parameters) {
      argument_types.push_back(GetType(type_exp));
    }
    return TypeOracle::GetFunctionPointerType(
        argument_types, GetType(function_type_exp->return_type));
  }
}

Builtin* Declarations::FindSomeInternalBuiltinWithType(
    const FunctionPointerType* type) {
  for (auto& declarable : declarables_) {
    if (Builtin* builtin = Builtin::DynamicCast(declarable.get())) {
      if (!builtin->IsExternal() && builtin->kind() == Builtin::kStub &&
          builtin->signature().return_type == type->return_type() &&
          builtin->signature().parameter_types.types ==
              type->parameter_types()) {
        return builtin;
      }
    }
  }
  return nullptr;
}

Value* Declarations::LookupValue(const std::string& name) {
  Declarable* d = Lookup(name);
  if (!d->IsValue()) {
    std::stringstream s;
    s << "declaration \"" << name << "\" is not a Value";
    ReportError(s.str());
  }
  return Value::cast(d);
}

Label* Declarations::LookupLabel(const std::string& name) {
  Declarable* d = Lookup(name);
  if (!d->IsLabel()) {
    std::stringstream s;
    s << "declaration \"" << name << "\" is not a Label";
    ReportError(s.str());
  }
  return Label::cast(d);
}

Macro* Declarations::TryLookupMacro(const std::string& name,
                                    const TypeVector& types) {
  Declarable* declarable = TryLookup(name);
  if (declarable != nullptr) {
    if (declarable->IsMacroList()) {
      for (auto& m : MacroList::cast(declarable)->list()) {
        if (m->signature().parameter_types.types == types &&
            !m->signature().parameter_types.var_args) {
          return m;
        }
      }
    }
  }
  return nullptr;
}

Macro* Declarations::LookupMacro(const std::string& name,
                                 const TypeVector& types) {
  Macro* result = TryLookupMacro(name, types);
  if (result != nullptr) return result;
  std::stringstream stream;
  stream << "macro " << name << " with parameter types " << types
         << " is not defined";
  ReportError(stream.str());
  return nullptr;
}

Builtin* Declarations::LookupBuiltin(const std::string& name) {
  Declarable* declarable = Lookup(name);
  if (declarable != nullptr) {
    if (declarable->IsBuiltin()) {
      return Builtin::cast(declarable);
    }
    ReportError(name + " is not a builtin");
  }
  ReportError(std::string("builtin ") + name + " is not defined");
  return nullptr;
}

GenericList* Declarations::LookupGeneric(const std::string& name) {
  Declarable* declarable_list = Lookup(name);
  if (declarable_list != nullptr) {
    if (declarable_list->IsGenericList()) {
      return GenericList::cast(declarable_list);
    }
    ReportError(name + " is not a generic");
  }
  ReportError(std::string("generic ") + name + " is not defined");
  return nullptr;
}

ModuleConstant* Declarations::LookupModuleConstant(const std::string& name) {
  Declarable* declarable = Lookup(name);
  if (declarable != nullptr) {
    if (declarable->IsModuleConstant()) {
      return ModuleConstant::cast(declarable);
    }
    ReportError(name + " is not a constant");
  }
  ReportError(std::string("constant \"") + name + "\" is not defined");
  return nullptr;
}

const AbstractType* Declarations::DeclareAbstractType(
    const std::string& name, const std::string& generated,
    base::Optional<const AbstractType*> non_constexpr_version,
    const base::Optional<std::string>& parent) {
  CheckAlreadyDeclared(name, "type");
  const Type* parent_type = nullptr;
  if (parent) {
    Declarable* maybe_parent_type = Lookup(*parent);
    if (maybe_parent_type == nullptr) {
      std::stringstream s;
      s << "cannot find parent type \"" << *parent << "\"";
      ReportError(s.str());
    }
    if (!maybe_parent_type->IsTypeAlias()) {
      std::stringstream s;
      s << "parent \"" << *parent << "\" of type \"" << name << "\""
        << " is not a type";
      ReportError(s.str());
    }
    parent_type = TypeAlias::cast(maybe_parent_type)->type();
  }
  const AbstractType* type = TypeOracle::GetAbstractType(
      parent_type, name, generated, non_constexpr_version);
  DeclareType(name, type);
  return type;
}

void Declarations::DeclareType(const std::string& name, const Type* type) {
  CheckAlreadyDeclared(name, "type");
  TypeAlias* result = new TypeAlias(type);
  Declare(name, std::unique_ptr<TypeAlias>(result));
}

void Declarations::DeclareStruct(Module* module, const std::string& name,
                                 const std::vector<NameAndType>& fields) {
  const StructType* new_type = TypeOracle::GetStructType(module, name, fields);
  DeclareType(name, new_type);
}

Label* Declarations::DeclareLabel(const std::string& name) {
  CheckAlreadyDeclared(name, "label");
  Label* result = new Label(name);
  Declare(name, std::unique_ptr<Declarable>(result));
  return result;
}

MacroList* Declarations::GetMacroListForName(const std::string& name,
                                             const Signature& signature) {
  auto previous = chain_.Lookup(name);
  MacroList* macro_list = nullptr;
  if (previous == nullptr) {
    macro_list = new MacroList();
    Declare(name, std::unique_ptr<Declarable>(macro_list));
  } else if (!previous->IsMacroList()) {
    std::stringstream s;
    s << "cannot redeclare non-macro " << name << " as a macro";
    ReportError(s.str());
  } else {
    macro_list = MacroList::cast(previous);
  }
  for (auto& macro : macro_list->list()) {
    if (signature.parameter_types.types ==
            macro->signature().parameter_types.types &&
        signature.parameter_types.var_args ==
            macro->signature().parameter_types.var_args) {
      std::stringstream s;
      s << "cannot redeclare " << name
        << " as a macro with identical parameter list "
        << signature.parameter_types;
      ReportError(s.str());
    }
  }
  return macro_list;
}

Macro* Declarations::DeclareMacro(const std::string& name,
                                  const Signature& signature,
                                  base::Optional<std::string> op) {
  Macro* macro = RegisterDeclarable(
      std::unique_ptr<Macro>(new Macro(name, signature, GetCurrentGeneric())));
  GetMacroListForName(name, signature)->AddMacro(macro);
  if (op) GetMacroListForName(*op, signature)->AddMacro(macro);
  return macro;
}

Builtin* Declarations::DeclareBuiltin(const std::string& name,
                                      Builtin::Kind kind, bool external,
                                      const Signature& signature) {
  CheckAlreadyDeclared(name, "builtin");
  Builtin* result =
      new Builtin(name, kind, external, signature, GetCurrentGeneric());
  Declare(name, std::unique_ptr<Declarable>(result));
  return result;
}

RuntimeFunction* Declarations::DeclareRuntimeFunction(
    const std::string& name, const Signature& signature) {
  CheckAlreadyDeclared(name, "runtime function");
  RuntimeFunction* result =
      new RuntimeFunction(name, signature, GetCurrentGeneric());
  Declare(name, std::unique_ptr<Declarable>(result));
  return result;
}

Variable* Declarations::DeclareVariable(const std::string& var,
                                        const Type* type, bool is_const) {
  std::string name(var + "_" +
                   std::to_string(GetNextUniqueDeclarationNumber()));
  std::replace(name.begin(), name.end(), '.', '_');
  CheckAlreadyDeclared(var, "variable");
  Variable* result = new Variable(var, name, type, is_const);
  Declare(var, std::unique_ptr<Declarable>(result));
  return result;
}

Parameter* Declarations::DeclareParameter(const std::string& name,
                                          const std::string& var_name,
                                          const Type* type) {
  CheckAlreadyDeclared(name, "parameter");
  Parameter* result = new Parameter(name, type, var_name);
  Declare(name, std::unique_ptr<Declarable>(result));
  return result;
}

Label* Declarations::DeclarePrivateLabel(const std::string& raw_name) {
  std::string name =
      raw_name + "_" + std::to_string(GetNextUniqueDeclarationNumber());
  CheckAlreadyDeclared(name, "label");
  Label* result = new Label(name);
  Declare(name, std::unique_ptr<Declarable>(result));
  return result;
}

void Declarations::DeclareExternConstant(const std::string& name,
                                         const Type* type,
                                         const std::string& value) {
  CheckAlreadyDeclared(name, "constant, parameter or arguments");
  ExternConstant* result = new ExternConstant(name, type, value);
  Declare(name, std::unique_ptr<Declarable>(result));
}

ModuleConstant* Declarations::DeclareModuleConstant(const std::string& name,
                                                    const Type* type) {
  CheckAlreadyDeclared(name, "module constant");
  ModuleConstant* result = new ModuleConstant(name, type);
  Declare(name, std::unique_ptr<Declarable>(result));
  return result;
}

Generic* Declarations::DeclareGeneric(const std::string& name, Module* module,
                                      GenericDeclaration* generic) {
  auto previous = chain_.Lookup(name);
  GenericList* generic_list = nullptr;
  if (previous == nullptr) {
    generic_list = new GenericList();
    Declare(name, std::unique_ptr<Declarable>(generic_list));
  } else if (!previous->IsGenericList()) {
    std::stringstream s;
    s << "cannot redeclare non-generic " << name << " as a generic";
    ReportError(s.str());
  } else {
    generic_list = GenericList::cast(previous);
  }
  Generic* result = RegisterDeclarable(
      std::unique_ptr<Generic>(new Generic(name, module, generic)));
  generic_list->AddGeneric(result);
  generic_declaration_scopes_[result] = GetScopeChainSnapshot();
  return result;
}

TypeVector Declarations::GetCurrentSpecializationTypeNamesVector() {
  TypeVector result;
  if (current_generic_specialization_ != nullptr) {
    result = current_generic_specialization_->second;
  }
  return result;
}

base::Optional<Generic*> Declarations::GetCurrentGeneric() {
  if (current_generic_specialization_ != nullptr) {
    return current_generic_specialization_->first;
  }
  return base::nullopt;
}

std::string GetGeneratedCallableName(const std::string& name,
                                     const TypeVector& specialized_types) {
  std::string result = name;
  for (auto type : specialized_types) {
    std::string type_string = type->MangledName();
    result += std::to_string(type_string.size()) + type_string;
  }
  return result;
}

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