//===---- ModernizeModuleTest.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 "ClangTidyTest.h"
#include "modernize/IntegralLiteralExpressionMatcher.h"
#include "clang/Lex/Lexer.h"
#include "gtest/gtest.h"

#include <cstring>
#include <iterator>
#include <string>
#include <vector>

namespace clang {
namespace tidy {
namespace test {

static std::vector<Token> tokenify(const char *Text) {
  LangOptions LangOpts;
  std::vector<std::string> Includes;
  LangOptions::setLangDefaults(LangOpts, Language::CXX, llvm::Triple(),
                               Includes, LangStandard::lang_cxx20);
  Lexer Lex(SourceLocation{}, LangOpts, Text, Text, Text + std::strlen(Text));
  std::vector<Token> Tokens;
  bool End = false;
  while (!End) {
    Token Tok;
    End = Lex.LexFromRawLexer(Tok);
    Tokens.push_back(Tok);
  }

  return Tokens;
}

static bool matchText(const char *Text, bool AllowComma) {
  std::vector<Token> Tokens{tokenify(Text)};
  modernize::IntegralLiteralExpressionMatcher Matcher(Tokens, AllowComma);

  return Matcher.match();
}

static modernize::LiteralSize sizeText(const char *Text) {
  std::vector<Token> Tokens{tokenify(Text)};
  modernize::IntegralLiteralExpressionMatcher Matcher(Tokens, true);
  if (Matcher.match())
    return Matcher.largestLiteralSize();
  return modernize::LiteralSize::Unknown;
}

static const char *toString(modernize::LiteralSize Value) {
  switch (Value) {
  case modernize::LiteralSize::Int:
    return "Int";
  case modernize::LiteralSize::UnsignedInt:
    return "UnsignedInt";
  case modernize::LiteralSize::Long:
    return "Long";
  case modernize::LiteralSize::UnsignedLong:
    return "UnsignedLong";
  case modernize::LiteralSize::LongLong:
    return "LongLong";
  case modernize::LiteralSize::UnsignedLongLong:
    return "UnsignedLongLong";
  default:
    return "Unknown";
  }
}

namespace {

struct MatchParam {
  bool AllowComma;
  bool Matched;
  const char *Text;

  friend std::ostream &operator<<(std::ostream &Str, const MatchParam &Value) {
    return Str << "Allow operator,: " << std::boolalpha << Value.AllowComma
               << ", Matched: " << std::boolalpha << Value.Matched
               << ", Text: '" << Value.Text << '\'';
  }
};

struct SizeParam {
  modernize::LiteralSize Size;
  const char *Text;

  friend std::ostream &operator<<(std::ostream &Str, const SizeParam &Value) {
    return Str << "Size: " << toString(Value.Size) << ", Text: '" << Value.Text << '\'';
  }
};

class MatcherTest : public ::testing::TestWithParam<MatchParam> {};

class SizeTest : public ::testing::TestWithParam<SizeParam> {};

} // namespace

static const MatchParam MatchParams[] = {
    // Accept integral literals.
    {true, true, "1"},
    {true, true, "0177"},
    {true, true, "0xdeadbeef"},
    {true, true, "0b1011"},
    {true, true, "'c'"},
    // Reject non-integral literals.
    {true, false, "1.23"},
    {true, false, "0x1p3"},
    {true, false, R"("string")"},
    {true, false, "1i"},

    // Accept literals with these unary operators.
    {true, true, "-1"},
    {true, true, "+1"},
    {true, true, "~1"},
    {true, true, "!1"},
    // Reject invalid unary operators.
    {true, false, "1-"},
    {true, false, "1+"},
    {true, false, "1~"},
    {true, false, "1!"},

    // Accept valid binary operators.
    {true, true, "1+1"},
    {true, true, "1-1"},
    {true, true, "1*1"},
    {true, true, "1/1"},
    {true, true, "1%2"},
    {true, true, "1<<1"},
    {true, true, "1>>1"},
    {true, true, "1<=>1"},
    {true, true, "1<1"},
    {true, true, "1>1"},
    {true, true, "1<=1"},
    {true, true, "1>=1"},
    {true, true, "1==1"},
    {true, true, "1!=1"},
    {true, true, "1&1"},
    {true, true, "1^1"},
    {true, true, "1|1"},
    {true, true, "1&&1"},
    {true, true, "1||1"},
    {true, true, "1+ +1"}, // A space is needed to avoid being tokenized as ++ or --.
    {true, true, "1- -1"},
    // Comma is only valid when inside parentheses.
    {true, true, "(1,1)"},
    // Reject invalid binary operators.
    {true, false, "1+"},
    {true, false, "1-"},
    {true, false, "1*"},
    {true, false, "1/"},
    {true, false, "1%"},
    {true, false, "1<<"},
    {true, false, "1>>"},
    {true, false, "1<=>"},
    {true, false, "1<"},
    {true, false, "1>"},
    {true, false, "1<="},
    {true, false, "1>="},
    {true, false, "1=="},
    {true, false, "1!="},
    {true, false, "1&"},
    {true, false, "1^"},
    {true, false, "1|"},
    {true, false, "1&&"},
    {true, false, "1||"},
    {true, false, "1,"},
    {true, false, ",1"},
    {true, false, "1,1"},

    // Accept valid ternary operators.
    {true, true, "1?1:1"},
    {true, true, "1?:1"}, // A gcc extension treats x ? : y as x ? x : y.
    // Reject invalid ternary operators.
    {true, false, "?"},
    {true, false, "?1"},
    {true, false, "?:"},
    {true, false, "?:1"},
    {true, false, "?1:"},
    {true, false, "?1:1"},
    {true, false, "1?"},
    {true, false, "1?1"},
    {true, false, "1?:"},
    {true, false, "1?1:"},

    // Accept parenthesized expressions.
    {true, true, "(1)"},
    {true, true, "((+1))"},
    {true, true, "((+(1)))"},
    {true, true, "(-1)"},
    {true, true, "-(1)"},
    {true, true, "(+1)"},
    {true, true, "((+1))"},
    {true, true, "+(1)"},
    {true, true, "(~1)"},
    {true, true, "~(1)"},
    {true, true, "(!1)"},
    {true, true, "!(1)"},
    {true, true, "(1+1)"},
    {true, true, "(1-1)"},
    {true, true, "(1*1)"},
    {true, true, "(1/1)"},
    {true, true, "(1%2)"},
    {true, true, "(1<<1)"},
    {true, true, "(1>>1)"},
    {true, true, "(1<=>1)"},
    {true, true, "(1<1)"},
    {true, true, "(1>1)"},
    {true, true, "(1<=1)"},
    {true, true, "(1>=1)"},
    {true, true, "(1==1)"},
    {true, true, "(1!=1)"},
    {true, true, "(1&1)"},
    {true, true, "(1^1)"},
    {true, true, "(1|1)"},
    {true, true, "(1&&1)"},
    {true, true, "(1||1)"},
    {true, true, "(1?1:1)"},

    // Accept more complicated "chained" expressions.
    {true, true, "1+1+1"},
    {true, true, "1+1+1+1"},
    {true, true, "1+1+1+1+1"},
    {true, true, "1*1*1"},
    {true, true, "1*1*1*1"},
    {true, true, "1*1*1*1*1"},
    {true, true, "1<<1<<1"},
    {true, true, "4U>>1>>1"},
    {true, true, "1<1<1"},
    {true, true, "1>1>1"},
    {true, true, "1<=1<=1"},
    {true, true, "1>=1>=1"},
    {true, true, "1==1==1"},
    {true, true, "1!=1!=1"},
    {true, true, "1&1&1"},
    {true, true, "1^1^1"},
    {true, true, "1|1|1"},
    {true, true, "1&&1&&1"},
    {true, true, "1||1||1"},
    {true, true, "(1,1,1)"},

    // Optionally reject comma operator
    {false, false, "1,1"}
};

TEST_P(MatcherTest, MatchResult) {
  const MatchParam &Param = GetParam();
 
  EXPECT_TRUE(matchText(Param.Text, Param.AllowComma) == Param.Matched);
}

INSTANTIATE_TEST_SUITE_P(IntegralLiteralExpressionMatcherTests, MatcherTest,
                         ::testing::ValuesIn(MatchParams));

static const SizeParam SizeParams[] = {
    {modernize::LiteralSize::Int, "1"},
    {modernize::LiteralSize::UnsignedInt, "1U"},
    {modernize::LiteralSize::Long, "1L"},
    {modernize::LiteralSize::UnsignedLong, "1UL"},
    {modernize::LiteralSize::UnsignedLong, "1LU"},
    {modernize::LiteralSize::LongLong, "1LL"},
    {modernize::LiteralSize::UnsignedLongLong, "1ULL"},
    {modernize::LiteralSize::UnsignedLongLong, "1LLU"}};

TEST_P(SizeTest, TokenSize) {
  EXPECT_EQ(sizeText(GetParam().Text), GetParam().Size);
}

INSTANTIATE_TEST_SUITE_P(IntegralLiteralExpressionMatcherTests, SizeTest,
                         ::testing::ValuesIn(SizeParams));

} // namespace test
} // namespace tidy
} // namespace clang
