// 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 "toolchain/parse/context.h"

namespace Carbon::Parse {

// Handles DeclarationNameAndParamsAs(Optional|Required).
static auto HandleDeclarationNameAndParams(Context& context, State after_name)
    -> void {
  auto state = context.PopState();

  // TODO: Should handle designated names.
  if (auto identifier = context.ConsumeIf(Lex::TokenKind::Identifier)) {
    state.state = after_name;
    context.PushState(state);

    if (context.PositionIs(Lex::TokenKind::Period)) {
      // Because there's a qualifier, we process the first segment as an
      // expression for simplicity. This just means semantics has one less thing
      // to handle here.
      context.AddLeafNode(NodeKind::NameExpression, *identifier);
      state.state = State::PeriodAsDeclaration;
      context.PushState(state);
    } else {
      context.AddLeafNode(NodeKind::Name, *identifier);
    }
  } else {
    CARBON_DIAGNOSTIC(ExpectedDeclarationName, Error,
                      "`{0}` introducer should be followed by a name.",
                      Lex::TokenKind);
    context.emitter().Emit(*context.position(), ExpectedDeclarationName,
                           context.tokens().GetKind(state.token));
    context.ReturnErrorOnState();
    context.AddLeafNode(NodeKind::InvalidParse, *context.position());
  }
}

auto HandleDeclarationNameAndParamsAsNone(Context& context) -> void {
  HandleDeclarationNameAndParams(
      context, State::DeclarationNameAndParamsAfterNameAsNone);
}

auto HandleDeclarationNameAndParamsAsOptional(Context& context) -> void {
  HandleDeclarationNameAndParams(
      context, State::DeclarationNameAndParamsAfterNameAsOptional);
}

auto HandleDeclarationNameAndParamsAsRequired(Context& context) -> void {
  HandleDeclarationNameAndParams(
      context, State::DeclarationNameAndParamsAfterNameAsRequired);
}

enum class Params : int8_t {
  None,
  Optional,
  Required,
};

static auto HandleDeclarationNameAndParamsAfterName(Context& context,
                                                    Params params) -> void {
  auto state = context.PopState();

  if (context.PositionIs(Lex::TokenKind::Period)) {
    // Continue designator processing.
    context.PushState(state);
    state.state = State::PeriodAsDeclaration;
    context.PushState(state);
    return;
  }

  if (params == Params::None) {
    return;
  }

  if (context.PositionIs(Lex::TokenKind::OpenSquareBracket)) {
    context.PushState(State::DeclarationNameAndParamsAfterDeduced);
    context.PushState(State::ParameterListAsDeduced);
  } else if (context.PositionIs(Lex::TokenKind::OpenParen)) {
    context.PushState(State::ParameterListAsRegular);
  } else if (params == Params::Required) {
    CARBON_DIAGNOSTIC(ParametersRequiredByIntroducer, Error,
                      "`{0}` requires a `(` for parameters.", Lex::TokenKind);
    context.emitter().Emit(*context.position(), ParametersRequiredByIntroducer,
                           context.tokens().GetKind(state.token));
    context.ReturnErrorOnState();
  }
}

auto HandleDeclarationNameAndParamsAfterNameAsNone(Context& context) -> void {
  HandleDeclarationNameAndParamsAfterName(context, Params::None);
}

auto HandleDeclarationNameAndParamsAfterNameAsOptional(Context& context)
    -> void {
  HandleDeclarationNameAndParamsAfterName(context, Params::Optional);
}

auto HandleDeclarationNameAndParamsAfterNameAsRequired(Context& context)
    -> void {
  HandleDeclarationNameAndParamsAfterName(context, Params::Required);
}

auto HandleDeclarationNameAndParamsAfterDeduced(Context& context) -> void {
  context.PopAndDiscardState();

  if (context.PositionIs(Lex::TokenKind::OpenParen)) {
    context.PushState(State::ParameterListAsRegular);
  } else {
    CARBON_DIAGNOSTIC(
        ParametersRequiredByDeduced, Error,
        "A `(` for parameters is required after deduced parameters.");
    context.emitter().Emit(*context.position(), ParametersRequiredByDeduced);
    context.ReturnErrorOnState();
  }
}

}  // namespace Carbon::Parse
