// Part of the Carbon Language project, under the Apache License v2.0 with LLVM
// Exceptions. See /LICENSE for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception

#include <array>

#include "toolchain/lex/token_kind.h"
#include "toolchain/parse/context.h"
#include "toolchain/parse/handle.h"
#include "toolchain/parse/node_kind.h"

namespace Carbon::Parse {

// Finishes an invalid declaration, skipping past its end.
static auto FinishAndSkipInvalidDecl(Context& context, int32_t subtree_start)
    -> void {
  auto cursor = *context.position();
  // Output an invalid parse subtree including everything up to the next `;`
  // or end of line.
  context.ReplacePlaceholderNode(subtree_start, NodeKind::InvalidParseStart,
                                 cursor, /*has_error=*/true);
  context.AddNode(NodeKind::InvalidParseSubtree,
                  context.SkipPastLikelyEnd(cursor), /*has_error=*/true);
}

// Prints a diagnostic and calls FinishAndSkipInvalidDecl.
static auto HandleUnrecognizedDecl(Context& context, int32_t subtree_start)
    -> void {
  CARBON_DIAGNOSTIC(UnrecognizedDecl, Error,
                    "unrecognized declaration introducer");
  context.emitter().Emit(*context.position(), UnrecognizedDecl);
  FinishAndSkipInvalidDecl(context, subtree_start);
}

// Replaces the introducer placeholder node, and pushes the introducer state for
// processing.
static auto ApplyIntroducer(Context& context, Context::State state,
                            NodeKind introducer_kind, StateKind next_state_kind)
    -> void {
  context.ReplacePlaceholderNode(state.subtree_start, introducer_kind,
                                 context.Consume());
  // Reuse state here to retain its `subtree_start`.
  context.PushState(state, next_state_kind);
}

namespace {
// The kind of context in which a declaration appears.
enum DeclContextKind : int8_t {
  RegularContext = 0,
  ClassContext = 1,
  InterfaceContext = 2,
  MaxDeclContextKind = InterfaceContext,
};

// The kind of declaration introduced by an introducer keyword.
enum class DeclIntroducerKind : int8_t {
  Unrecognized,
  PackagingDecl,
  NonPackagingDecl,
};

// Information about a keyword that might be an introducer keyword.
struct DeclIntroducerInfo {
  DeclIntroducerKind introducer_kind;
  NodeKind node_kind;
  StateKind state_kind;
};
}  // namespace

static constexpr auto DeclIntroducers = [] {
  std::array<DeclIntroducerInfo, MaxDeclContextKind + 1> introducers[] = {
#define CARBON_TOKEN(Name)                                \
  {{{.introducer_kind = DeclIntroducerKind::Unrecognized, \
     .node_kind = NodeKind::InvalidParse,                 \
     .state_kind = StateKind::Invalid},                   \
    {.introducer_kind = DeclIntroducerKind::Unrecognized, \
     .node_kind = NodeKind::InvalidParse,                 \
     .state_kind = StateKind::Invalid},                   \
    {.introducer_kind = DeclIntroducerKind::Unrecognized, \
     .node_kind = NodeKind::InvalidParse,                 \
     .state_kind = StateKind::Invalid}}},
#include "toolchain/lex/token_kind.def"
  };
  auto set = [&](Lex::TokenKind token_kind, NodeKind node_kind,
                 StateKind state) {
    for (int i = 0; i <= MaxDeclContextKind; ++i) {
      introducers[token_kind.AsInt()][i] = {
          .introducer_kind = DeclIntroducerKind::NonPackagingDecl,
          .node_kind = node_kind,
          .state_kind = state};
    }
  };
  auto set_contextual = [&](Lex::TokenKind token_kind,
                            DeclContextKind context_kind, NodeKind node_kind,
                            StateKind state) {
    introducers[token_kind.AsInt()][context_kind] = {
        .introducer_kind = DeclIntroducerKind::NonPackagingDecl,
        .node_kind = node_kind,
        .state_kind = state};
  };
  auto set_packaging = [&](Lex::TokenKind token_kind, NodeKind node_kind,
                           StateKind state) {
    for (int i = 0; i <= MaxDeclContextKind; ++i) {
      introducers[token_kind.AsInt()][i] = {
          .introducer_kind = DeclIntroducerKind::PackagingDecl,
          .node_kind = node_kind,
          .state_kind = state};
    }
  };

  set(Lex::TokenKind::Adapt, NodeKind::AdaptIntroducer,
      StateKind::AdaptAfterIntroducer);
  set(Lex::TokenKind::Alias, NodeKind::AliasIntroducer, StateKind::Alias);
  set(Lex::TokenKind::Base, NodeKind::BaseIntroducer,
      StateKind::BaseAfterIntroducer);
  set(Lex::TokenKind::Choice, NodeKind::ChoiceIntroducer,
      StateKind::ChoiceIntroducer);
  set(Lex::TokenKind::Class, NodeKind::ClassIntroducer,
      StateKind::TypeAfterIntroducerAsClass);
  set(Lex::TokenKind::Constraint, NodeKind::NamedConstraintIntroducer,
      StateKind::TypeAfterIntroducerAsNamedConstraint);
  set(Lex::TokenKind::Export, NodeKind::ExportIntroducer,
      StateKind::ExportName);
  // TODO: Treat `extend` as a declaration introducer.
  set(Lex::TokenKind::Fn, NodeKind::FunctionIntroducer,
      StateKind::FunctionIntroducer);
  set(Lex::TokenKind::Impl, NodeKind::ImplIntroducer,
      StateKind::ImplAfterIntroducer);
  set(Lex::TokenKind::Interface, NodeKind::InterfaceIntroducer,
      StateKind::TypeAfterIntroducerAsInterface);
  set(Lex::TokenKind::Namespace, NodeKind::NamespaceStart,
      StateKind::Namespace);
  set_contextual(Lex::TokenKind::Let, RegularContext, NodeKind::LetIntroducer,
                 StateKind::Let);
  set_contextual(Lex::TokenKind::Let, ClassContext, NodeKind::LetIntroducer,
                 StateKind::Let);
  set_contextual(Lex::TokenKind::Let, InterfaceContext,
                 NodeKind::AssociatedConstantIntroducer,
                 StateKind::AssociatedConstant);
  set_contextual(Lex::TokenKind::Var, RegularContext,
                 NodeKind::VariableIntroducer, StateKind::VarAsRegular);
  set_contextual(Lex::TokenKind::Var, ClassContext, NodeKind::FieldIntroducer,
                 StateKind::FieldDecl);

  set_packaging(Lex::TokenKind::Package, NodeKind::PackageIntroducer,
                StateKind::Package);
  set_packaging(Lex::TokenKind::Library, NodeKind::LibraryIntroducer,
                StateKind::Library);
  set_packaging(Lex::TokenKind::Import, NodeKind::ImportIntroducer,
                StateKind::Import);
  return std::to_array(introducers);
}();

// Attempts to handle the current token as a declaration introducer.
// Returns true if the current position is a declaration. If we see a
// declaration introducer keyword token, replace the placeholder node and switch
// to a state to parse the rest of the declaration.
static auto TryHandleAsDecl(Context& context, Context::State state,
                            bool saw_modifier,
                            DeclContextKind decl_context_kind) -> bool {
  const auto& info =
      DeclIntroducers[context.PositionKind().AsInt()][decl_context_kind];

  switch (info.introducer_kind) {
    case DeclIntroducerKind::Unrecognized: {
      // A `;` with no modifiers is an empty declaration.
      if (!saw_modifier) {
        if (auto loc = context.ConsumeIf(Lex::TokenKind::Semi)) {
          context.ReplacePlaceholderNode(state.subtree_start,
                                         NodeKind::EmptyDecl, *loc);
          return true;
        }
      }
      return false;
    }

    case DeclIntroducerKind::PackagingDecl: {
      // Packaging declarations update the packaging state themselves as needed.
      break;
    }

    case DeclIntroducerKind::NonPackagingDecl: {
      // Because a non-packaging keyword was encountered, packaging is complete.
      // Misplaced packaging keywords may lead to this being re-triggered.
      if (context.packaging_state() !=
          Context::PackagingState::AfterNonPackagingDecl) {
        if (!context.first_non_packaging_token().has_value()) {
          context.set_first_non_packaging_token(*context.position());
        }
        context.set_packaging_state(
            Context::PackagingState::AfterNonPackagingDecl);
      }
      break;
    }
  }

  ApplyIntroducer(context, state, info.node_kind, info.state_kind);
  return true;
}

// Returns true if position_kind could be either an introducer or modifier, and
// should be treated as an introducer.
static auto ResolveAmbiguousTokenAsDeclaration(Context& context,
                                               Lex::TokenKind position_kind)
    -> bool {
  switch (position_kind) {
    case Lex::TokenKind::Base:
    case Lex::TokenKind::Export:
    case Lex::TokenKind::Extend:
    case Lex::TokenKind::Impl:
      // This is an ambiguous token, so now we check what the next token is.

      // We use the macro for modifiers, including introducers which are
      // also modifiers (such as `base`). Other introducer tokens need to be
      // added by hand.
      switch (context.PositionKind(Lookahead::NextToken)) {
        case Lex::TokenKind::Adapt:
        case Lex::TokenKind::Alias:
        case Lex::TokenKind::Class:
        case Lex::TokenKind::Constraint:
        case Lex::TokenKind::Extern:
        case Lex::TokenKind::Fn:
        case Lex::TokenKind::Import:
        case Lex::TokenKind::Interface:
        case Lex::TokenKind::Let:
        case Lex::TokenKind::Library:
        case Lex::TokenKind::Namespace:
        case Lex::TokenKind::Var:
#define CARBON_PARSE_NODE_KIND(Name)
#define CARBON_PARSE_NODE_KIND_TOKEN_MODIFIER(Name) case Lex::TokenKind::Name:
#include "toolchain/parse/node_kind.def"

          return false;

        case Lex::TokenKind::Package:
          // `package.foo` is an expression; any other token after `package` is
          // a `package` introducer.
          return context.PositionKind(static_cast<Lookahead>(2)) ==
                 Lex::TokenKind::Period;

        default:
          return true;
      }
      break;
    default:
      return false;
  }
}

// Returns true if the current position is a modifier, handling it if so.
static auto TryHandleAsModifier(Context& context) -> bool {
  auto position_kind = context.PositionKind();
  if (ResolveAmbiguousTokenAsDeclaration(context, position_kind)) {
    return false;
  }

  switch (position_kind) {
#define CARBON_PARSE_NODE_KIND(Name)
#define CARBON_PARSE_NODE_KIND_TOKEN_MODIFIER(Name)                   \
  case Lex::TokenKind::Name:                                          \
    context.AddLeafNode(NodeKind::Name##Modifier, context.Consume()); \
    return true;
#include "toolchain/parse/node_kind.def"

    case Lex::TokenKind::Extern: {
      auto extern_token = context.Consume();
      if (context.PositionIs(Lex::TokenKind::Library)) {
        // `extern library <owning_library>` syntax.
        context.ParseLibrarySpecifier(/*accept_default=*/true);
        // TODO: Consider error recovery when a non-declaration token is next,
        // like a typo of the library name.
        context.AddNode(NodeKind::ExternModifierWithLibrary, extern_token,
                        /*has_error=*/false);
      } else {
        // `extern` syntax without a library.
        context.AddLeafNode(NodeKind::ExternModifier, extern_token);
      }
      return true;
    }

    default:
      return false;
  }
}

static auto HandleDecl(Context& context, DeclContextKind decl_context_kind)
    -> void {
  auto state = context.PopState();

  // Add a placeholder node, to be replaced by the declaration introducer once
  // it is found.
  context.AddLeafNode(NodeKind::Placeholder, *context.position());

  bool saw_modifier = false;
  while (TryHandleAsModifier(context)) {
    saw_modifier = true;
  }
  if (!TryHandleAsDecl(context, state, saw_modifier, decl_context_kind)) {
    HandleUnrecognizedDecl(context, state.subtree_start);
  }
}

auto HandleDeclAsClass(Context& context) -> void {
  HandleDecl(context, ClassContext);
}

auto HandleDeclAsInterface(Context& context) -> void {
  HandleDecl(context, InterfaceContext);
}

auto HandleDeclAsRegular(Context& context) -> void {
  HandleDecl(context, RegularContext);
}

static auto HandleDeclScopeLoop(Context& context, StateKind decl_state_kind)
    -> void {
  // This maintains the current state unless we're at the end of the scope.
  if (context.PositionIs(Lex::TokenKind::CloseCurlyBrace) ||
      context.PositionIs(Lex::TokenKind::FileEnd)) {
    // This is the end of the scope, so the loop state ends.
    context.PopAndDiscardState();
    return;
  }

  context.PushState(decl_state_kind);
}

auto HandleDeclScopeLoopAsClass(Context& context) -> void {
  HandleDeclScopeLoop(context, StateKind::DeclAsClass);
}

auto HandleDeclScopeLoopAsInterface(Context& context) -> void {
  HandleDeclScopeLoop(context, StateKind::DeclAsInterface);
}

auto HandleDeclScopeLoopAsRegular(Context& context) -> void {
  HandleDeclScopeLoop(context, StateKind::DeclAsRegular);
}

}  // namespace Carbon::Parse
