//===--- IntegralLiteralExpressionMatcher.cpp - clang-tidy ----------------===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//

#include "IntegralLiteralExpressionMatcher.h"

#include <algorithm>
#include <cctype>

namespace clang::tidy::modernize {

// Validate that this literal token is a valid integer literal.  A literal token
// could be a floating-point token, which isn't acceptable as a value for an
// enumeration.  A floating-point token must either have a decimal point or an
// exponent ('E' or 'P').
static bool isIntegralConstant(const Token &Token) {
  const char *Begin = Token.getLiteralData();
  const char *End = Begin + Token.getLength();

  // Not a hexadecimal floating-point literal.
  if (Token.getLength() > 2 && Begin[0] == '0' && std::toupper(Begin[1]) == 'X')
    return std::none_of(Begin + 2, End, [](char C) {
      return C == '.' || std::toupper(C) == 'P';
    });

  // Not a decimal floating-point literal or complex literal.
  return std::none_of(Begin, End, [](char C) {
    return C == '.' || std::toupper(C) == 'E' || std::toupper(C) == 'I';
  });
}

bool IntegralLiteralExpressionMatcher::advance() {
  ++Current;
  return Current != End;
}

bool IntegralLiteralExpressionMatcher::consume(tok::TokenKind Kind) {
  if (Current->is(Kind)) {
    ++Current;
    return true;
  }

  return false;
}

template <typename NonTerminalFunctor, typename IsKindFunctor>
bool IntegralLiteralExpressionMatcher::nonTerminalChainedExpr(
    const NonTerminalFunctor &NonTerminal, const IsKindFunctor &IsKind) {
  if (!NonTerminal())
    return false;
  if (Current == End)
    return true;

  while (Current != End) {
    if (!IsKind(*Current))
      break;

    if (!advance())
      return false;

    if (!NonTerminal())
      return false;
  }

  return true;
}

template <tok::TokenKind Kind, typename NonTerminalFunctor>
bool IntegralLiteralExpressionMatcher::nonTerminalChainedExpr(
    const NonTerminalFunctor &NonTerminal) {
  return nonTerminalChainedExpr(NonTerminal,
                                [](Token Tok) { return Tok.is(Kind); });
}

template <tok::TokenKind K1, tok::TokenKind K2, tok::TokenKind... Ks,
          typename NonTerminalFunctor>
bool IntegralLiteralExpressionMatcher::nonTerminalChainedExpr(
    const NonTerminalFunctor &NonTerminal) {
  return nonTerminalChainedExpr(
      NonTerminal, [](Token Tok) { return Tok.isOneOf(K1, K2, Ks...); });
}

// Advance over unary operators.
bool IntegralLiteralExpressionMatcher::unaryOperator() {
  if (Current->isOneOf(tok::TokenKind::minus, tok::TokenKind::plus,
                       tok::TokenKind::tilde, tok::TokenKind::exclaim)) {
    return advance();
  }

  return true;
}

static LiteralSize literalTokenSize(const Token &Tok) {
  unsigned int Length = Tok.getLength();
  if (Length <= 1)
    return LiteralSize::Int;

  bool SeenUnsigned = false;
  bool SeenLong = false;
  bool SeenLongLong = false;
  const char *Text = Tok.getLiteralData();
  for (unsigned int End = Length - 1; End > 0; --End) {
    if (std::isdigit(Text[End]))
      break;

    if (std::toupper(Text[End]) == 'U')
      SeenUnsigned = true;
    else if (std::toupper(Text[End]) == 'L') {
      if (SeenLong)
        SeenLongLong = true;
      SeenLong = true;
    }
  }

  if (SeenLongLong) {
    if (SeenUnsigned)
      return LiteralSize::UnsignedLongLong;

    return LiteralSize::LongLong;
  }
  if (SeenLong) {
    if (SeenUnsigned)
      return LiteralSize::UnsignedLong;

    return LiteralSize::Long;
  }
  if (SeenUnsigned)
    return LiteralSize::UnsignedInt;

  return LiteralSize::Int;
}

static bool operator<(LiteralSize LHS, LiteralSize RHS) {
  return static_cast<int>(LHS) < static_cast<int>(RHS);
}

bool IntegralLiteralExpressionMatcher::unaryExpr() {
  if (!unaryOperator())
    return false;

  if (consume(tok::TokenKind::l_paren)) {
    if (Current == End)
      return false;

    if (!expr())
      return false;

    if (Current == End)
      return false;

    return consume(tok::TokenKind::r_paren);
  }

  if (!Current->isLiteral() || isStringLiteral(Current->getKind()) ||
      !isIntegralConstant(*Current)) {
    return false;
  }

  LargestSize = std::max(LargestSize, literalTokenSize(*Current));
  ++Current;

  return true;
}

bool IntegralLiteralExpressionMatcher::multiplicativeExpr() {
  return nonTerminalChainedExpr<tok::TokenKind::star, tok::TokenKind::slash,
                                tok::TokenKind::percent>(
      [this] { return unaryExpr(); });
}

bool IntegralLiteralExpressionMatcher::additiveExpr() {
  return nonTerminalChainedExpr<tok::plus, tok::minus>(
      [this] { return multiplicativeExpr(); });
}

bool IntegralLiteralExpressionMatcher::shiftExpr() {
  return nonTerminalChainedExpr<tok::TokenKind::lessless,
                                tok::TokenKind::greatergreater>(
      [this] { return additiveExpr(); });
}

bool IntegralLiteralExpressionMatcher::compareExpr() {
  if (!shiftExpr())
    return false;
  if (Current == End)
    return true;

  if (Current->is(tok::TokenKind::spaceship)) {
    if (!advance())
      return false;

    if (!shiftExpr())
      return false;
  }

  return true;
}

bool IntegralLiteralExpressionMatcher::relationalExpr() {
  return nonTerminalChainedExpr<tok::TokenKind::less, tok::TokenKind::greater,
                                tok::TokenKind::lessequal,
                                tok::TokenKind::greaterequal>(
      [this] { return compareExpr(); });
}

bool IntegralLiteralExpressionMatcher::equalityExpr() {
  return nonTerminalChainedExpr<tok::TokenKind::equalequal,
                                tok::TokenKind::exclaimequal>(
      [this] { return relationalExpr(); });
}

bool IntegralLiteralExpressionMatcher::andExpr() {
  return nonTerminalChainedExpr<tok::TokenKind::amp>(
      [this] { return equalityExpr(); });
}

bool IntegralLiteralExpressionMatcher::exclusiveOrExpr() {
  return nonTerminalChainedExpr<tok::TokenKind::caret>(
      [this] { return andExpr(); });
}

bool IntegralLiteralExpressionMatcher::inclusiveOrExpr() {
  return nonTerminalChainedExpr<tok::TokenKind::pipe>(
      [this] { return exclusiveOrExpr(); });
}

bool IntegralLiteralExpressionMatcher::logicalAndExpr() {
  return nonTerminalChainedExpr<tok::TokenKind::ampamp>(
      [this] { return inclusiveOrExpr(); });
}

bool IntegralLiteralExpressionMatcher::logicalOrExpr() {
  return nonTerminalChainedExpr<tok::TokenKind::pipepipe>(
      [this] { return logicalAndExpr(); });
}

bool IntegralLiteralExpressionMatcher::conditionalExpr() {
  if (!logicalOrExpr())
    return false;
  if (Current == End)
    return true;

  if (Current->is(tok::TokenKind::question)) {
    if (!advance())
      return false;

    // A gcc extension allows x ? : y as a synonym for x ? x : y.
    if (Current->is(tok::TokenKind::colon)) {
      if (!advance())
        return false;

      if (!expr())
        return false;

      return true;
    }

    if (!expr())
      return false;
    if (Current == End)
      return false;

    if (!Current->is(tok::TokenKind::colon))
      return false;

    if (!advance())
      return false;

    if (!expr())
      return false;
  }
  return true;
}

bool IntegralLiteralExpressionMatcher::commaExpr() {
  auto NonTerminal = [this] { return conditionalExpr(); };
  if (CommaAllowed)
    return nonTerminalChainedExpr<tok::TokenKind::comma>(NonTerminal);
  return nonTerminalChainedExpr(NonTerminal, [](Token) { return false; });
}

bool IntegralLiteralExpressionMatcher::expr() { return commaExpr(); }

bool IntegralLiteralExpressionMatcher::match() {
  // Top-level allowed expression is conditionalExpr(), not expr(), because
  // comma operators are only valid initializers when used inside parentheses.
  return conditionalExpr() && Current == End;
}

LiteralSize IntegralLiteralExpressionMatcher::largestLiteralSize() const {
  return LargestSize;
}

} // namespace clang::tidy::modernize
