From 35ef94162bc1e31f93aa5f43636f4ec5cbe46010 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mario=20H=C3=BCttel?= Date: Thu, 1 Jun 2023 22:39:10 +0200 Subject: [PATCH] Add tests and fix minor problems in specifier parser --- CMakeLists.txt | 3 +- include/simple-printf/simple-printf.h | 40 +++++++-- src/simple-printf.c | 29 ++----- test/CMakeLists.txt | 11 +++ test/src/tests.cpp | 114 ++++++++++++++++++++++++++ 5 files changed, 168 insertions(+), 29 deletions(-) create mode 100644 test/CMakeLists.txt create mode 100644 test/src/tests.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 5ec34b5..1a6f880 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -5,4 +5,5 @@ aux_source_directory("src" SOURCES) add_library(${PROJECT_NAME} STATIC ${SOURCES}) target_include_directories(${PROJECT_NAME} PUBLIC include) - +# Add tests. This is not included in the overall build +add_subdirectory(test) \ No newline at end of file diff --git a/include/simple-printf/simple-printf.h b/include/simple-printf/simple-printf.h index 4a0714d..006b6f6 100644 --- a/include/simple-printf/simple-printf.h +++ b/include/simple-printf/simple-printf.h @@ -2,16 +2,44 @@ #define _SIMPLE_PRINTF_H_ #include +#include #ifndef SIMPLE_PRINTF_ATTR_FORMAT -# if ((__GNUC__ > 3) || ((__GNUC__ == 3) && (__GNUC_MINOR__ >= 3))) -# define SIMPLE_PRINTF_ATTR_FORMAT(fmt, args) __attribute__((__format__(__printf__, fmt, args))) -# else -# define SIMPLE_PRINTF_ATTR_FORMAT(fmt, args) -# endif +#if ((__GNUC__ > 3) || ((__GNUC__ == 3) && (__GNUC_MINOR__ >= 3))) +#define SIMPLE_PRINTF_ATTR_FORMAT(fmt, args) __attribute__((__format__(__printf__, fmt, args))) #else -# define SIMPLE_PRINTF_ATTR_FORMAT(fmt, args) +#define SIMPLE_PRINTF_ATTR_FORMAT(fmt, args) #endif +#else +#define SIMPLE_PRINTF_ATTR_FORMAT(fmt, args) +#endif + +enum format_specifier_type { + FMT_INVALID = 0, + FMT_INTEGER, + FMT_UNSIGNED, + FMT_STRING, + FMT_HEXUINT_CAPITALIZED, + FMT_HEXUINT, + FMT_CHAR, + FMT_SIZE, + FMT_PERCENT, +}; + +struct format_specifier { + enum format_specifier_type specifier; + size_t length; /**< @brief lenght of formatted output.*/ + bool zero_pad; + size_t length_of_provided_specifier; +}; + +/** + * @brief Helper function to parse printf format specifiers + * + * @param start Start of parsing + * @param[out] result Result of format specifier parsing + */ +void parse_format_specifier(const char *start, struct format_specifier *result); /** * @brief Simple implementation of snprintf diff --git a/src/simple-printf.c b/src/simple-printf.c index dbce09d..8e48205 100644 --- a/src/simple-printf.c +++ b/src/simple-printf.c @@ -2,25 +2,6 @@ #include #include -enum format_specifier_type { - FMT_INVALID = 0, - FMT_INTEGER, - FMT_UNSIGNED, - FMT_STRING, - FMT_HEXUINT_CAPITALIZED, - FMT_HEXUINT, - FMT_CHAR, - FMT_SIZE, - FMT_PERCENT, -}; - -struct format_specifier { - enum format_specifier_type specifier; - size_t length; /**< @brief lenght of formatted output.*/ - bool zero_pad; - size_t length_of_provided_specifier; -}; - enum format_parser_state { PARSER_PERCENT = 0, PARSER_SECOND, @@ -87,7 +68,7 @@ static enum format_specifier_type resolve_specifier(char c) return ret; } -static void parse_format_specifier(const char *start, struct format_specifier *result) +void parse_format_specifier(const char *start, struct format_specifier *result) { size_t fmt_len = 1ull; bool keep_parsing = true; @@ -115,15 +96,19 @@ static void parse_format_specifier(const char *start, struct format_specifier *r switch (state) { case PARSER_PERCENT: if (*ptr == '%') { - advance_ptr++; + advance_ptr = true; next_state = PARSER_SECOND; + } else { + keep_parsing = false; } break; case PARSER_SECOND: if (*ptr == '0') { result->zero_pad = true; - advance_ptr++; + advance_ptr = true; next_state = PARSER_NUM; + current_token = ptr + 1; + token_length = 0; } else if (*ptr > '9' || *ptr < '0') { /* Specifier */ next_state = PARSER_SPECIFIER; diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt new file mode 100644 index 0000000..431cb09 --- /dev/null +++ b/test/CMakeLists.txt @@ -0,0 +1,11 @@ +project(simple-printf-test) + +add_subdirectory(catch2 EXCLUDE_FROM_ALL) + +add_custom_target(test "${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_NAME}" "-r compact" "-s" DEPENDS ${PROJECT_NAME}) + +include_directories("${CMAKE_CURRENT_SOURCE_DIR}/catch-framework") +aux_source_directory("src" TEST_SOURCES) + +add_executable(${PROJECT_NAME} EXCLUDE_FROM_ALL ${TEST_SOURCES}) +target_link_libraries(${PROJECT_NAME} PRIVATE simple-printf Catch2::Catch2WithMain) \ No newline at end of file diff --git a/test/src/tests.cpp b/test/src/tests.cpp new file mode 100644 index 0000000..c6d01a9 --- /dev/null +++ b/test/src/tests.cpp @@ -0,0 +1,114 @@ +#include +#include +#include +#include +#include + +extern "C" { +#include +} + +TEST_CASE("Parse Invalid Specifier") +{ + const char *tst = "%m"; + const char *tst2 = "%45m"; + const char *tst3 = "%014m"; + struct format_specifier result; + + parse_format_specifier(tst, &result); + REQUIRE(result.specifier == FMT_INVALID); + + parse_format_specifier(tst, &result); + REQUIRE(result.specifier == FMT_INVALID); + + parse_format_specifier(tst, &result); + REQUIRE(result.specifier == FMT_INVALID); +} + +TEST_CASE("Parse Format Specifier c", "Char") +{ + const char *tst = "%c"; + struct format_specifier result; + + parse_format_specifier(tst, &result); + + REQUIRE(result.length_of_provided_specifier == 2); + REQUIRE(result.specifier == FMT_CHAR); + REQUIRE(result.zero_pad == false); +} + +TEST_CASE("Parse Format Specifier s", "String") +{ + const char *tst = "%s"; + struct format_specifier result; + + parse_format_specifier(tst, &result); + + REQUIRE(result.length_of_provided_specifier == 2); + REQUIRE(result.specifier == FMT_STRING); + REQUIRE(result.length == 0); + REQUIRE(result.zero_pad == false); +} + +TEST_CASE("Parse Format Specifier 04s", "String") +{ + const char *tst = "%04s"; + struct format_specifier result; + + parse_format_specifier(tst, &result); + + REQUIRE(result.specifier == FMT_STRING); + REQUIRE(result.length_of_provided_specifier == 4); + REQUIRE(result.length == 4); + REQUIRE(result.zero_pad == true); +} + +TEST_CASE("Parse Format Specifier 08x", "hex") +{ + const char *tst = "%08x"; + struct format_specifier result; + + parse_format_specifier(tst, &result); + + REQUIRE(result.specifier == FMT_HEXUINT); + REQUIRE(result.length_of_provided_specifier == 4); + REQUIRE(result.length == 8); + REQUIRE(result.zero_pad == true); +} + +TEST_CASE("Parse Format Specifier 08x with stuff", "hex") +{ + const std::array tst_arr { + std::move(std::string("%08xff458578")), + std::move(std::string("%08x45578")), + std::move(std::string("%08xx458578")), + std::move(std::string("%08x44458578")), + std::move(std::string("%08x45sdf458578")), + std::move(std::string("%08xsfsdf4558578")), + std::move(std::string("%08xX")), + std::move(std::string("%08xdsda")) + }; + + struct format_specifier result; + + for (const auto &tst : tst_arr) { + parse_format_specifier(tst.c_str(), &result); + REQUIRE(result.specifier == FMT_HEXUINT); + REQUIRE(result.length_of_provided_specifier == 4); + REQUIRE(result.length == 8); + REQUIRE(result.zero_pad == true); + } +} + +TEST_CASE("Parse Percent spefifier") +{ + const char *tst = "%%"; + struct format_specifier result; + + parse_format_specifier(tst, &result); + + REQUIRE(result.specifier == FMT_PERCENT); + REQUIRE(result.length_of_provided_specifier == 2); + REQUIRE(result.length == 0); + REQUIRE(result.zero_pad == false); +} \ No newline at end of file