//===-- Unittests for the basic scanf converters --------------------------===//
//
// 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 "src/__support/CPP/string_view.h"
#include "src/stdio/scanf_core/converter.h"
#include "src/stdio/scanf_core/core_structs.h"
#include "src/stdio/scanf_core/string_reader.h"

#include "test/UnitTest/Test.h"

TEST(LlvmLibcScanfConverterTest, RawMatchBasic) {
  const char *str = "abcdef";
  LIBC_NAMESPACE::scanf_core::StringReader reader(str, sizeof(str));

  // Reading "abc" should succeed.
  ASSERT_EQ(LIBC_NAMESPACE::scanf_core::raw_match(&reader, "abc"),
            static_cast<int>(LIBC_NAMESPACE::scanf_core::READ_OK));
  ASSERT_EQ(reader.chars_read(), size_t(3));

  // Reading nothing should succeed and not advance.
  ASSERT_EQ(LIBC_NAMESPACE::scanf_core::raw_match(&reader, ""),
            static_cast<int>(LIBC_NAMESPACE::scanf_core::READ_OK));
  ASSERT_EQ(reader.chars_read(), size_t(3));

  // Reading a space where there is none should succeed and not advance.
  ASSERT_EQ(LIBC_NAMESPACE::scanf_core::raw_match(&reader, " "),
            static_cast<int>(LIBC_NAMESPACE::scanf_core::READ_OK));
  ASSERT_EQ(reader.chars_read(), size_t(3));

  // Reading "d" should succeed and advance by 1.
  ASSERT_EQ(LIBC_NAMESPACE::scanf_core::raw_match(&reader, "d"),
            static_cast<int>(LIBC_NAMESPACE::scanf_core::READ_OK));
  ASSERT_EQ(reader.chars_read(), size_t(4));

  // Reading "z" should fail and not advance.
  ASSERT_EQ(LIBC_NAMESPACE::scanf_core::raw_match(&reader, "z"),
            static_cast<int>(LIBC_NAMESPACE::scanf_core::MATCHING_FAILURE));
  ASSERT_EQ(reader.chars_read(), size_t(4));

  // Reading "efgh" should fail but advance to the end.
  ASSERT_EQ(LIBC_NAMESPACE::scanf_core::raw_match(&reader, "efgh"),
            static_cast<int>(LIBC_NAMESPACE::scanf_core::MATCHING_FAILURE));
  ASSERT_EQ(reader.chars_read(), size_t(6));
}

TEST(LlvmLibcScanfConverterTest, RawMatchSpaces) {
  const char *str = " a \t\n b   cd";
  LIBC_NAMESPACE::scanf_core::StringReader reader(str, sizeof(str));

  // Reading "a" should fail and not advance.
  // Since there's nothing in the format string (the second argument to
  // raw_match) to match the space in the buffer it isn't consumed.
  ASSERT_EQ(LIBC_NAMESPACE::scanf_core::raw_match(&reader, "a"),
            static_cast<int>(LIBC_NAMESPACE::scanf_core::MATCHING_FAILURE));
  ASSERT_EQ(reader.chars_read(), size_t(0));

  // Reading "  \t\n  " should succeed and advance past the space.
  // Any number of space characters in the format string match 0 or more space
  // characters in the buffer.
  ASSERT_EQ(LIBC_NAMESPACE::scanf_core::raw_match(&reader, "  \t\n  "),
            static_cast<int>(LIBC_NAMESPACE::scanf_core::READ_OK));
  ASSERT_EQ(reader.chars_read(), size_t(1));

  // Reading "ab" should fail and only advance past the a
  // The a characters match, but the format string doesn't have anything to
  // consume the spaces in the buffer, so it fails.
  ASSERT_EQ(LIBC_NAMESPACE::scanf_core::raw_match(&reader, "ab"),
            static_cast<int>(LIBC_NAMESPACE::scanf_core::MATCHING_FAILURE));
  ASSERT_EQ(reader.chars_read(), size_t(2));

  // Reading "  b" should succeed and advance past the b
  // Any number of space characters in the format string matches 0 or more space
  // characters in the buffer.
  ASSERT_EQ(LIBC_NAMESPACE::scanf_core::raw_match(&reader, "  b"),
            static_cast<int>(LIBC_NAMESPACE::scanf_core::READ_OK));
  ASSERT_EQ(reader.chars_read(), size_t(7));

  // Reading "\t" should succeed and advance past the spaces to the c
  ASSERT_EQ(LIBC_NAMESPACE::scanf_core::raw_match(&reader, "\t"),
            static_cast<int>(LIBC_NAMESPACE::scanf_core::READ_OK));
  ASSERT_EQ(reader.chars_read(), size_t(10));

  // Reading "c d" should succeed and advance past the d.
  // Here the space character in the format string is matching 0 space
  // characters in the buffer.
  ASSERT_EQ(LIBC_NAMESPACE::scanf_core::raw_match(&reader, "c d"),
            static_cast<int>(LIBC_NAMESPACE::scanf_core::READ_OK));
  ASSERT_EQ(reader.chars_read(), size_t(12));
}

TEST(LlvmLibcScanfConverterTest, StringConvSimple) {
  const char *str = "abcDEF123 654LKJihg";
  char result[20];
  LIBC_NAMESPACE::scanf_core::StringReader reader(str, sizeof(str));

  LIBC_NAMESPACE::scanf_core::FormatSection conv;
  conv.has_conv = true;
  conv.conv_name = 's';
  conv.output_ptr = result;

  ASSERT_EQ(LIBC_NAMESPACE::scanf_core::convert(&reader, conv),
            static_cast<int>(LIBC_NAMESPACE::scanf_core::READ_OK));
  ASSERT_EQ(reader.chars_read(), size_t(9));
  ASSERT_STREQ(result, "abcDEF123");

  //%s skips all spaces before beginning to read.
  ASSERT_EQ(LIBC_NAMESPACE::scanf_core::convert(&reader, conv),
            static_cast<int>(LIBC_NAMESPACE::scanf_core::READ_OK));
  ASSERT_EQ(reader.chars_read(), size_t(19));
  ASSERT_STREQ(result, "654LKJihg");
}

TEST(LlvmLibcScanfConverterTest, StringConvNoWrite) {
  const char *str = "abcDEF123 654LKJihg";
  LIBC_NAMESPACE::scanf_core::StringReader reader(str, sizeof(str));

  LIBC_NAMESPACE::scanf_core::FormatSection conv;
  conv.has_conv = true;
  conv.conv_name = 's';
  conv.flags = LIBC_NAMESPACE::scanf_core::NO_WRITE;

  ASSERT_EQ(LIBC_NAMESPACE::scanf_core::convert(&reader, conv),
            static_cast<int>(LIBC_NAMESPACE::scanf_core::READ_OK));
  ASSERT_EQ(reader.chars_read(), size_t(9));

  //%s skips all spaces before beginning to read.
  ASSERT_EQ(LIBC_NAMESPACE::scanf_core::convert(&reader, conv),
            static_cast<int>(LIBC_NAMESPACE::scanf_core::READ_OK));
  ASSERT_EQ(reader.chars_read(), size_t(19));
}

TEST(LlvmLibcScanfConverterTest, StringConvWidth) {
  const char *str = "abcDEF123 654LKJihg";
  char result[6];
  LIBC_NAMESPACE::scanf_core::StringReader reader(str, sizeof(str));

  LIBC_NAMESPACE::scanf_core::FormatSection conv;
  conv.has_conv = true;
  conv.conv_name = 's';
  conv.max_width = 5; // this means the result takes up 6 characters (with \0).
  conv.output_ptr = result;

  ASSERT_EQ(LIBC_NAMESPACE::scanf_core::convert(&reader, conv),
            static_cast<int>(LIBC_NAMESPACE::scanf_core::READ_OK));
  ASSERT_EQ(reader.chars_read(), size_t(5));
  ASSERT_STREQ(result, "abcDE");

  ASSERT_EQ(LIBC_NAMESPACE::scanf_core::convert(&reader, conv),
            static_cast<int>(LIBC_NAMESPACE::scanf_core::READ_OK));
  ASSERT_EQ(reader.chars_read(), size_t(9));
  ASSERT_STREQ(result, "F123");

  //%s skips all spaces before beginning to read.
  ASSERT_EQ(LIBC_NAMESPACE::scanf_core::convert(&reader, conv),
            static_cast<int>(LIBC_NAMESPACE::scanf_core::READ_OK));
  ASSERT_EQ(reader.chars_read(), size_t(15));
  ASSERT_STREQ(result, "654LK");

  ASSERT_EQ(LIBC_NAMESPACE::scanf_core::convert(&reader, conv),
            static_cast<int>(LIBC_NAMESPACE::scanf_core::READ_OK));
  ASSERT_EQ(reader.chars_read(), size_t(19));
  ASSERT_STREQ(result, "Jihg");
}

TEST(LlvmLibcScanfConverterTest, CharsConv) {
  const char *str = "abcDEF123 654LKJihg MNOpqr&*(";
  char result[20];
  LIBC_NAMESPACE::scanf_core::StringReader reader(str, sizeof(str));

  LIBC_NAMESPACE::scanf_core::FormatSection conv;
  conv.has_conv = true;
  conv.conv_name = 'c';
  conv.output_ptr = result;

  ASSERT_EQ(LIBC_NAMESPACE::scanf_core::convert(&reader, conv),
            static_cast<int>(LIBC_NAMESPACE::scanf_core::READ_OK));
  ASSERT_EQ(reader.chars_read(), size_t(1));
  ASSERT_EQ(result[0], 'a');

  ASSERT_EQ(LIBC_NAMESPACE::scanf_core::convert(&reader, conv),
            static_cast<int>(LIBC_NAMESPACE::scanf_core::READ_OK));
  ASSERT_EQ(reader.chars_read(), size_t(2));
  ASSERT_EQ(result[0], 'b');

  ASSERT_EQ(LIBC_NAMESPACE::scanf_core::convert(&reader, conv),
            static_cast<int>(LIBC_NAMESPACE::scanf_core::READ_OK));
  ASSERT_EQ(reader.chars_read(), size_t(3));
  ASSERT_EQ(result[0], 'c');

  // Switch from character by character to 8 at a time.
  conv.max_width = 8;
  LIBC_NAMESPACE::cpp::string_view result_view(result, 8);

  //%c doesn't stop on spaces.
  ASSERT_EQ(LIBC_NAMESPACE::scanf_core::convert(&reader, conv),
            static_cast<int>(LIBC_NAMESPACE::scanf_core::READ_OK));
  ASSERT_EQ(reader.chars_read(), size_t(11));
  ASSERT_EQ(result_view, LIBC_NAMESPACE::cpp::string_view("DEF123 6", 8));

  ASSERT_EQ(LIBC_NAMESPACE::scanf_core::convert(&reader, conv),
            static_cast<int>(LIBC_NAMESPACE::scanf_core::READ_OK));
  ASSERT_EQ(reader.chars_read(), size_t(19));
  ASSERT_EQ(result_view, LIBC_NAMESPACE::cpp::string_view("54LKJihg", 8));

  //%c also doesn't skip spaces at the start.
  ASSERT_EQ(LIBC_NAMESPACE::scanf_core::convert(&reader, conv),
            static_cast<int>(LIBC_NAMESPACE::scanf_core::READ_OK));
  ASSERT_EQ(reader.chars_read(), size_t(27));
  ASSERT_EQ(result_view, LIBC_NAMESPACE::cpp::string_view(" MNOpqr&", 8));

  //%c will stop on a null byte though.
  ASSERT_EQ(LIBC_NAMESPACE::scanf_core::convert(&reader, conv),
            static_cast<int>(LIBC_NAMESPACE::scanf_core::READ_OK));
  ASSERT_EQ(reader.chars_read(), size_t(29));
  ASSERT_EQ(LIBC_NAMESPACE::cpp::string_view(result, 2),
            LIBC_NAMESPACE::cpp::string_view("*(", 2));
}

TEST(LlvmLibcScanfConverterTest, ScansetConv) {
  const char *str = "abcDEF[123] 654LKJihg";
  char result[20];
  LIBC_NAMESPACE::scanf_core::StringReader reader(str, sizeof(str));

  LIBC_NAMESPACE::scanf_core::FormatSection conv;
  conv.has_conv = true;
  conv.conv_name = '[';
  conv.output_ptr = result;

  LIBC_NAMESPACE::cpp::bitset<256> bitset1;
  bitset1.set_range('a', 'c');
  bitset1.set_range('D', 'F');
  bitset1.set_range('1', '6');
  bitset1.set('[');
  bitset1.set(']');

  conv.scan_set = bitset1;

  ASSERT_EQ(LIBC_NAMESPACE::scanf_core::convert(&reader, conv),
            static_cast<int>(LIBC_NAMESPACE::scanf_core::READ_OK));
  ASSERT_EQ(reader.chars_read(), size_t(11));
  ASSERT_EQ(LIBC_NAMESPACE::cpp::string_view(result, 11),
            LIBC_NAMESPACE::cpp::string_view("abcDEF[123]", 11));

  // The scanset conversion doesn't consume leading spaces. If it did it would
  // return "654" here.
  ASSERT_EQ(LIBC_NAMESPACE::scanf_core::convert(&reader, conv),
            static_cast<int>(LIBC_NAMESPACE::scanf_core::MATCHING_FAILURE));
  ASSERT_EQ(reader.chars_read(), size_t(11));

  // This set is everything except for a-g.
  LIBC_NAMESPACE::cpp::bitset<256> bitset2;
  bitset2.set_range('a', 'g');
  bitset2.flip();
  conv.scan_set = bitset2;

  conv.max_width = 5;

  ASSERT_EQ(LIBC_NAMESPACE::scanf_core::convert(&reader, conv),
            static_cast<int>(LIBC_NAMESPACE::scanf_core::READ_OK));
  ASSERT_EQ(reader.chars_read(), size_t(16));
  ASSERT_EQ(LIBC_NAMESPACE::cpp::string_view(result, 5),
            LIBC_NAMESPACE::cpp::string_view(" 654L", 5));

  ASSERT_EQ(LIBC_NAMESPACE::scanf_core::convert(&reader, conv),
            static_cast<int>(LIBC_NAMESPACE::scanf_core::READ_OK));
  ASSERT_EQ(reader.chars_read(), size_t(20));
  ASSERT_EQ(LIBC_NAMESPACE::cpp::string_view(result, 4),
            LIBC_NAMESPACE::cpp::string_view("KJih", 4));

  // This set is g and '\0'.
  LIBC_NAMESPACE::cpp::bitset<256> bitset3;
  bitset3.set('g');
  bitset3.set('\0');
  conv.scan_set = bitset3;

  // Even though '\0' is in the scanset, it should still stop on it.
  ASSERT_EQ(LIBC_NAMESPACE::scanf_core::convert(&reader, conv),
            static_cast<int>(LIBC_NAMESPACE::scanf_core::READ_OK));
  ASSERT_EQ(reader.chars_read(), size_t(21));
  ASSERT_EQ(LIBC_NAMESPACE::cpp::string_view(result, 1),
            LIBC_NAMESPACE::cpp::string_view("g", 1));
}
