From 1a694a8b26a441b12547057d6e0be61a111cced3 Mon Sep 17 00:00:00 2001 From: Spartan322 Date: Sat, 15 Jun 2024 09:40:31 -0400 Subject: Add unit tests Make github action tests run explicit Fix dropping annotation list for Errors Fix potential empty get_errors crashes Fix incorrect csv error behavior Add use_sep for `LineObject` and `std::vector` Remove constexpr of load_from_buffer and load_from_string for parsers Add snitch-org/snitch@d6632123cc8d13bdbc5cd60fd6741b9e0f635e82 Make versioned submodules ignore dirty Add tests/bin/* to gitignore --- .github/workflows/builds.yml | 2 +- .gitignore | 2 + .gitmodules | 6 + SConstruct | 9 +- include/openvic-dataloader/Error.hpp | 7 +- include/openvic-dataloader/NodeLocation.hpp | 10 +- include/openvic-dataloader/csv/LineObject.hpp | 50 ++ include/openvic-dataloader/csv/Parser.hpp | 6 +- .../v2script/AbstractSyntaxTree.hpp | 7 +- include/openvic-dataloader/v2script/Parser.hpp | 6 +- src/openvic-dataloader/csv/Parser.cpp | 20 +- .../v2script/AbstractSyntaxTree.cpp | 10 +- src/openvic-dataloader/v2script/Parser.cpp | 9 +- tests/SCsub | 55 ++ tests/deps/SCsub | 201 ++++++++ tests/deps/snitch | 1 + tests/src/Error.cpp | 230 +++++++++ tests/src/Helper.hpp | 60 +++ tests/src/NodeLocation.cpp | 300 +++++++++++ tests/src/csv/LineObject.cpp | 196 +++++++ tests/src/csv/Parser.cpp | 571 +++++++++++++++++++++ tests/src/ovscript/.gitkeep | 0 tests/src/v2script/AbstractSyntaxTree.cpp | 172 +++++++ tests/src/v2script/Parser.cpp | 437 ++++++++++++++++ 24 files changed, 2337 insertions(+), 30 deletions(-) create mode 100644 tests/SCsub create mode 100644 tests/deps/SCsub create mode 160000 tests/deps/snitch create mode 100644 tests/src/Error.cpp create mode 100644 tests/src/Helper.hpp create mode 100644 tests/src/NodeLocation.cpp create mode 100644 tests/src/csv/LineObject.cpp create mode 100644 tests/src/csv/Parser.cpp create mode 100644 tests/src/ovscript/.gitkeep create mode 100644 tests/src/v2script/AbstractSyntaxTree.cpp create mode 100644 tests/src/v2script/Parser.cpp diff --git a/.github/workflows/builds.yml b/.github/workflows/builds.yml index ce98e16..8f56794 100644 --- a/.github/workflows/builds.yml +++ b/.github/workflows/builds.yml @@ -97,7 +97,7 @@ jobs: with: platform: ${{ matrix.platform }} target: ${{ matrix.target }} - sconsflags: arch=${{ matrix.arch }} build_ovdl_library=yes + sconsflags: arch=${{ matrix.arch }} build_ovdl_library=yes run_ovdl_tests=yes - name: Delete compilation files if: ${{ matrix.platform == 'windows' }} diff --git a/.gitignore b/.gitignore index adae621..3e44a87 100644 --- a/.gitignore +++ b/.gitignore @@ -66,6 +66,8 @@ bin/* *.idb *.exp +tests/bin/* + # Build configuarion. /custom.py diff --git a/.gitmodules b/.gitmodules index 0b7febe..d4c2c55 100644 --- a/.gitmodules +++ b/.gitmodules @@ -11,6 +11,12 @@ [submodule "deps/fmt"] path = deps/fmt url = https://github.com/fmtlib/fmt + ignore = dirty [submodule "deps/range-v3"] path = deps/range-v3 url = https://github.com/ericniebler/range-v3 + ignore = dirty +[submodule "tests/deps/snitch"] + path = tests/deps/snitch + url = https://github.com/snitch-org/snitch + ignore = dirty diff --git a/SConstruct b/SConstruct index 23b2d9d..05547c5 100644 --- a/SConstruct +++ b/SConstruct @@ -14,7 +14,8 @@ env.PrependENVPath("PATH", os.getenv("PATH")) opts = env.SetupOptions() -opts.Add(BoolVariable(key="build_ovdl_library", help="Build the openvic dataloader library.", default=env.get("build_ovdl_library", not env.is_standalone))) +opts.Add(BoolVariable("run_ovdl_tests", "Build and run the openvic dataloader tests", env.is_standalone)) +opts.Add(BoolVariable("build_ovdl_library", "Build the openvic dataloader library.", env.get("build_ovdl_library", not env.is_standalone))) opts.Add(BoolVariable("build_ovdl_headless", "Build the openvic dataloader headless executable", env.is_standalone)) env.FinalizeOptions() @@ -56,6 +57,9 @@ library_name = "libopenvic-dataloader{}{}".format(suffix, env["LIBSUFFIX"]) default_args = [] +if env["run_ovdl_tests"]: + env["build_ovdl_library"] = True + if env["build_ovdl_library"]: library = env.StaticLibrary(target=os.path.join(BINDIR, library_name), source=sources) default_args += [library] @@ -86,6 +90,9 @@ if env["build_ovdl_headless"]: ) default_args += [headless_program] +if env["run_ovdl_tests"]: + SConscript("tests/SCsub", "env") + # Add compiledb if the option is set if env.get("compiledb", False): default_args += ["compiledb"] diff --git a/include/openvic-dataloader/Error.hpp b/include/openvic-dataloader/Error.hpp index a2e13fe..343abd9 100644 --- a/include/openvic-dataloader/Error.hpp +++ b/include/openvic-dataloader/Error.hpp @@ -117,11 +117,10 @@ namespace ovdl::error { protected: explicit AnnotatedError(dryad::node_ctor ctor, ErrorKind kind) : node_base(ctor, kind) { insert_child_list_after(nullptr, AnnotationList {}); - _last_annotation = nullptr; } private: - Annotation* _last_annotation; + Annotation* _last_annotation = nullptr; }; struct ParseError : dryad::abstract_node_range { @@ -207,7 +206,7 @@ namespace ovdl::error { inline void AnnotatedError::push_back(AnnotationList p_annotations) { if (p_annotations.empty()) return; - insert_child_list_after(annotations().end().deref(), p_annotations); - _last_annotation = *p_annotations.end(); + insert_child_list_after(_last_annotation, p_annotations); + _last_annotation = p_annotations.back(); } } \ No newline at end of file diff --git a/include/openvic-dataloader/NodeLocation.hpp b/include/openvic-dataloader/NodeLocation.hpp index ced79e6..7f7a09c 100644 --- a/include/openvic-dataloader/NodeLocation.hpp +++ b/include/openvic-dataloader/NodeLocation.hpp @@ -11,10 +11,12 @@ namespace ovdl { const char_type* _end = nullptr; BasicNodeLocation() = default; - BasicNodeLocation(const char_type* pos) : _begin(pos), - _end(pos) {} - BasicNodeLocation(const char_type* begin, const char_type* end) : _begin(begin), - _end(end) {} + BasicNodeLocation(const char_type* pos) + : _begin(pos), + _end(pos) {} + BasicNodeLocation(const char_type* begin, const char_type* end) + : _begin(begin), + _end(end) {} BasicNodeLocation(const BasicNodeLocation&) noexcept = default; BasicNodeLocation& operator=(const BasicNodeLocation&) = default; diff --git a/include/openvic-dataloader/csv/LineObject.hpp b/include/openvic-dataloader/csv/LineObject.hpp index c839be2..5de55ba 100644 --- a/include/openvic-dataloader/csv/LineObject.hpp +++ b/include/openvic-dataloader/csv/LineObject.hpp @@ -80,6 +80,15 @@ namespace ovdl::csv { constexpr std::size_t value_count() const { return _suffix_end; } + struct SepTransformer { + const LineObject& line_object; + std::string_view separator; + }; + + constexpr SepTransformer use_sep(std::string_view seperator) const { + return { *this, seperator }; + } + private: // Should be position of first valid value on line position_type _prefix_end = 0; @@ -87,6 +96,15 @@ namespace ovdl::csv { position_type _suffix_end = 0; }; + struct VectorSepTransformer { + const std::vector& vector; + std::string_view separator; + }; + + constexpr VectorSepTransformer use_sep(const std::vector& vector, std::string_view separator) { + return { vector, separator }; + } + inline std::ostream& operator<<(std::ostream& stream, const LineObject& line) { static constexpr char SEP = ';'; LineObject::position_type sep_index = 0; @@ -110,4 +128,36 @@ namespace ovdl::csv { } return stream; } + + inline std::ostream& operator<<(std::ostream& stream, const LineObject::SepTransformer& transformer) { + auto quote_check = [&transformer, is_one = transformer.separator.size() == 1](const std::string_view str) { + if (is_one) { + char SEP = transformer.separator[0]; + return std::any_of(str.begin(), str.end(), [SEP](char c) { return c == SEP || std::isspace(c); }); + } + return std::any_of(str.begin(), str.end(), [](char c) { return std::isspace(c); }) || + str.find(transformer.separator) != std::string::npos; + }; + + LineObject::position_type sep_index = 0; + for (const auto& [pos, val] : transformer.line_object) { + while (sep_index < pos) { + stream << transformer.separator; + sep_index++; + } + if (quote_check(val)) { + stream << '"' << val << '"'; + } else { + stream << val; + } + } + return stream; + } + + inline std::ostream& operator<<(std::ostream& stream, const VectorSepTransformer& transformer) { + for (const LineObject& line : transformer.vector) { + stream << line.use_sep(transformer.separator) << '\n'; + } + return stream; + } } \ No newline at end of file diff --git a/include/openvic-dataloader/csv/Parser.hpp b/include/openvic-dataloader/csv/Parser.hpp index 35421c8..1c363c8 100644 --- a/include/openvic-dataloader/csv/Parser.hpp +++ b/include/openvic-dataloader/csv/Parser.hpp @@ -25,9 +25,9 @@ namespace ovdl::csv { static Parser from_file(const char* path, std::optional encoding_fallback = std::nullopt); static Parser from_file(const std::filesystem::path& path, std::optional encoding_fallback = std::nullopt); - constexpr Parser& load_from_buffer(const char* data, std::size_t size, std::optional encoding_fallback = std::nullopt); - constexpr Parser& load_from_buffer(const char* start, const char* end, std::optional encoding_fallback = std::nullopt); - constexpr Parser& load_from_string(const std::string_view string, std::optional encoding_fallback = std::nullopt); + Parser& load_from_buffer(const char* data, std::size_t size, std::optional encoding_fallback = std::nullopt); + Parser& load_from_buffer(const char* start, const char* end, std::optional encoding_fallback = std::nullopt); + Parser& load_from_string(const std::string_view string, std::optional encoding_fallback = std::nullopt); Parser& load_from_file(const char* path, std::optional encoding_fallback = std::nullopt); Parser& load_from_file(const std::filesystem::path& path, std::optional encoding_fallback = std::nullopt); diff --git a/include/openvic-dataloader/v2script/AbstractSyntaxTree.hpp b/include/openvic-dataloader/v2script/AbstractSyntaxTree.hpp index 29e7866..a582187 100644 --- a/include/openvic-dataloader/v2script/AbstractSyntaxTree.hpp +++ b/include/openvic-dataloader/v2script/AbstractSyntaxTree.hpp @@ -108,9 +108,7 @@ namespace ovdl::v2script::ast { struct ListValue : dryad::basic_node> { explicit ListValue(dryad::node_ctor ctor, StatementList statements); explicit ListValue(dryad::node_ctor ctor, AssignStatementList statements); - - explicit ListValue(dryad::node_ctor ctor) : ListValue(ctor, StatementList {}) { - } + explicit ListValue(dryad::node_ctor ctor); DRYAD_CHILD_NODE_RANGE_GETTER(Statement, statements, nullptr, this->node_after(_last_statement)); @@ -167,8 +165,7 @@ namespace ovdl::v2script::ast { struct FileTree : dryad::basic_node> { explicit FileTree(dryad::node_ctor ctor, StatementList statements); explicit FileTree(dryad::node_ctor ctor, AssignStatementList statements); - explicit FileTree(dryad::node_ctor ctor) : FileTree(ctor, StatementList {}) { - } + explicit FileTree(dryad::node_ctor ctor); DRYAD_CHILD_NODE_RANGE_GETTER(Statement, statements, nullptr, this->node_after(_last_node)); diff --git a/include/openvic-dataloader/v2script/Parser.hpp b/include/openvic-dataloader/v2script/Parser.hpp index 1f6b158..2518e61 100644 --- a/include/openvic-dataloader/v2script/Parser.hpp +++ b/include/openvic-dataloader/v2script/Parser.hpp @@ -33,9 +33,9 @@ namespace ovdl::v2script { static Parser from_file(const char* path, std::optional encoding_fallback = std::nullopt); static Parser from_file(const std::filesystem::path& path, std::optional encoding_fallback = std::nullopt); - constexpr Parser& load_from_buffer(const char* data, std::size_t size, std::optional encoding_fallback = std::nullopt); - constexpr Parser& load_from_buffer(const char* start, const char* end, std::optional encoding_fallback = std::nullopt); - constexpr Parser& load_from_string(const std::string_view string, std::optional encoding_fallback = std::nullopt); + Parser& load_from_buffer(const char* data, std::size_t size, std::optional encoding_fallback = std::nullopt); + Parser& load_from_buffer(const char* start, const char* end, std::optional encoding_fallback = std::nullopt); + Parser& load_from_string(const std::string_view string, std::optional encoding_fallback = std::nullopt); Parser& load_from_file(const char* path, std::optional encoding_fallback = std::nullopt); Parser& load_from_file(const std::filesystem::path& path, std::optional encoding_fallback = std::nullopt); diff --git a/src/openvic-dataloader/csv/Parser.cpp b/src/openvic-dataloader/csv/Parser.cpp index 5dbee32..8fe8b17 100644 --- a/src/openvic-dataloader/csv/Parser.cpp +++ b/src/openvic-dataloader/csv/Parser.cpp @@ -3,6 +3,7 @@ #include #include +#include #include #include #include @@ -15,6 +16,8 @@ #include #include +#include + #include "CsvGrammar.hpp" #include "CsvParseState.hpp" #include "detail/NullBuff.hpp" @@ -53,6 +56,13 @@ struct Parser::ParseHandler final : detail::BasicFileParseHandler return _lines; } + Parser::error_range get_errors() { + using iterator = typename decltype(std::declval()->children())::iterator; + if (!is_valid()) + return dryad::make_node_range(iterator::from_ptr(nullptr), iterator::from_ptr(nullptr)); + return parse_state().logger().get_errors(); + } + private: std::vector _lines; }; @@ -126,19 +136,19 @@ constexpr void Parser::_run_load_func(detail::LoadCallback encoding_fallback) { +Parser& Parser::load_from_buffer(const char* data, std::size_t size, std::optional encoding_fallback) { // Type can't be deduced? _run_load_func(std::mem_fn(&ParseHandler::load_buffer_size), data, size, encoding_fallback); return *this; } -constexpr Parser& Parser::load_from_buffer(const char* start, const char* end, std::optional encoding_fallback) { +Parser& Parser::load_from_buffer(const char* start, const char* end, std::optional encoding_fallback) { // Type can't be deduced? _run_load_func(std::mem_fn(&ParseHandler::load_buffer), start, end, encoding_fallback); return *this; } -constexpr Parser& Parser::load_from_string(const std::string_view string, std::optional encoding_fallback) { +Parser& Parser::load_from_string(const std::string_view string, std::optional encoding_fallback) { return load_from_buffer(string.data(), string.size(), encoding_fallback); } @@ -166,7 +176,7 @@ bool Parser::parse_csv(bool handle_strings) { }(); _has_error = _parse_handler->parse_state().logger().errored(); _has_warning = _parse_handler->parse_state().logger().warned(); - if (!errors->empty()) { + if (errors && !errors->empty()) { _has_error = true; _has_fatal_error = true; if (&_error_stream.get() != &detail::cnull) { @@ -182,7 +192,7 @@ const std::vector& Parser::get_lines() const { } typename Parser::error_range Parser::get_errors() const { - return _parse_handler->parse_state().logger().get_errors(); + return _parse_handler->get_errors(); } const FilePosition Parser::get_error_position(const error::Error* error) const { diff --git a/src/openvic-dataloader/v2script/AbstractSyntaxTree.cpp b/src/openvic-dataloader/v2script/AbstractSyntaxTree.cpp index 5a98b40..71985f3 100644 --- a/src/openvic-dataloader/v2script/AbstractSyntaxTree.cpp +++ b/src/openvic-dataloader/v2script/AbstractSyntaxTree.cpp @@ -1,4 +1,4 @@ -#include "openvic-dataloader/v2script/AbstractSyntaxTree.hpp" +#include #include #include @@ -31,6 +31,10 @@ ListValue::ListValue(dryad::node_ctor ctor, AssignStatementList statements) : no } } +ListValue::ListValue(dryad::node_ctor ctor) : node_base(ctor) { + _last_statement = nullptr; +} + FileTree::FileTree(dryad::node_ctor ctor, StatementList statements) : node_base(ctor) { insert_child_list_after(nullptr, statements); if (statements.empty()) { @@ -49,6 +53,10 @@ FileTree::FileTree(dryad::node_ctor ctor, AssignStatementList statements) : node } } +FileTree::FileTree(dryad::node_ctor ctor) : node_base(ctor) { + _last_node = nullptr; +} + std::string FileAbstractSyntaxTree::make_list_visualizer() const { const int INDENT_SIZE = 2; diff --git a/src/openvic-dataloader/v2script/Parser.cpp b/src/openvic-dataloader/v2script/Parser.cpp index 23dada7..a0003b1 100644 --- a/src/openvic-dataloader/v2script/Parser.cpp +++ b/src/openvic-dataloader/v2script/Parser.cpp @@ -76,6 +76,9 @@ struct Parser::ParseHandler final : detail::BasicStateParseHandler()->children())::iterator; + if (!is_valid()) + return dryad::make_node_range(iterator::from_ptr(nullptr), iterator::from_ptr(nullptr)); return parse_state().logger().get_errors(); } }; @@ -149,19 +152,19 @@ constexpr void Parser::_run_load_func(detail::LoadCallback encoding_fallback) { +Parser& Parser::load_from_buffer(const char* data, std::size_t size, std::optional encoding_fallback) { // Type can't be deduced? _run_load_func(std::mem_fn(&ParseHandler::load_buffer_size), data, size, encoding_fallback); return *this; } -constexpr Parser& Parser::load_from_buffer(const char* start, const char* end, std::optional encoding_fallback) { +Parser& Parser::load_from_buffer(const char* start, const char* end, std::optional encoding_fallback) { // Type can't be deduced? _run_load_func(std::mem_fn(&ParseHandler::load_buffer), start, end, encoding_fallback); return *this; } -constexpr Parser& Parser::load_from_string(const std::string_view string, std::optional encoding_fallback) { +Parser& Parser::load_from_string(const std::string_view string, std::optional encoding_fallback) { return load_from_buffer(string.data(), string.size(), encoding_fallback); } diff --git a/tests/SCsub b/tests/SCsub new file mode 100644 index 0000000..0a18777 --- /dev/null +++ b/tests/SCsub @@ -0,0 +1,55 @@ +#!/usr/bin/env python +import os +import subprocess +import platform +import sys + +import SCons +from SCons.Script.SConscript import SConsEnvironment + + +def UnitTestPostAction(target=None, source=None, env=None): + print() + return subprocess.run([target[0].path]).returncode + +def UnitTest(env, **kwargs): + test = env.Program(**kwargs) + unit_test_action = env.Action(UnitTestPostAction, None) + test_post_action = env.AddPostAction(test, unit_test_action) + env.NoCache(test) + env.AlwaysBuild(test_post_action) + return test + +SConsEnvironment.UnitTest = UnitTest + +Import("env") + +BINDIR = "bin" + +env.openvic_dataloader_tests = {} + +# For the reference: +# - CCFLAGS are compilation flags shared between C and C++ +# - CFLAGS are for C-specific compilation flags +# - CXXFLAGS are for C++-specific compilation flags +# - CPPFLAGS are for pre-processor flags +# - CPPDEFINES are for pre-processor defines +# - LINKFLAGS are for linking flags + +# tweak this if you want to use different folders, or more folders, to store your source code in. +source_path = "src" + +tests_name = "openvic-dataloader" +tests_env = env.Clone() +tests_env.Append(CPPDEFINES=["OPENVIC_DATALOADER_TESTS"]) +tests_env.Append(CPPPATH=[tests_env.Dir(source_path)]) +tests_env.tests_sources = env.GlobRecursive("*.cpp", [source_path]) + +SConscript("deps/SCsub", {"env": tests_env }) + +tests_program = tests_env.UnitTest( + source=tests_env.tests_sources, + target=os.path.join(BINDIR, tests_name), + PROGSUFFIX=".tests" + env["PROGSUFFIX"] +) +Default(tests_program) \ No newline at end of file diff --git a/tests/deps/SCsub b/tests/deps/SCsub new file mode 100644 index 0000000..ef7af7a --- /dev/null +++ b/tests/deps/SCsub @@ -0,0 +1,201 @@ +#!/usr/bin/env python +import os +import subprocess +from pathlib import Path + +Import("env") + +def generate_snitch_config_header(target, source, env): + header = [] + header_filename = "snitch_config.hpp" + + header.append("// THIS FILE IS GENERATED. EDITS WILL BE LOST.") + header.append("") + + header_file_path = Path(str(target[0])) + + include_gen_folder = Path(str(header_file_path.parent)) + include_gen_folder.mkdir(parents=True, exist_ok=True) + + header_guard = "SNITCH_CONFIG_HPP" + header.append(f"#ifndef {header_guard}") + header.append(f"#define {header_guard}") + header.append("") + header.append("#include // for C++ feature check macros") + + for key, val in env.config_data.items(): + header +=(f''' +#if !defined({key}) +# define {key} {val} +#endif''').split("\n") + + header += (""" +#if defined(_MSC_VER) +# if defined(_KERNEL_MODE) || (defined(_HAS_EXCEPTIONS) && !_HAS_EXCEPTIONS) +# define SNITCH_EXCEPTIONS_NOT_AVAILABLE +# endif +#elif defined(__clang__) || defined(__GNUC__) +# if !defined(__EXCEPTIONS) +# define SNITCH_EXCEPTIONS_NOT_AVAILABLE +# endif +#endif + +#if defined(SNITCH_EXCEPTIONS_NOT_AVAILABLE) +# undef SNITCH_WITH_EXCEPTIONS +# define SNITCH_WITH_EXCEPTIONS 0 +#endif + +#if SNITCH_WITH_MULTITHREADING +# define SNITCH_THREAD_LOCAL thread_local +#else +# define SNITCH_THREAD_LOCAL +#endif + +#if !defined(__cpp_lib_bit_cast) +# undef SNITCH_CONSTEXPR_FLOAT_USE_BITCAST +# define SNITCH_CONSTEXPR_FLOAT_USE_BITCAST 0 +#endif + +#if (!defined(__cpp_lib_to_chars)) || (defined(_GLIBCXX_RELEASE) && _GLIBCXX_RELEASE <= 11) || \ + (defined(_LIBCPP_VERSION) && _LIBCPP_VERSION <= 14000) || \ + (defined(_MSC_VER) && _MSC_VER <= 1924) +# undef SNITCH_APPEND_TO_CHARS +# define SNITCH_APPEND_TO_CHARS 0 +#endif + +#if SNITCH_SHARED_LIBRARY +# if defined(_MSC_VER) +# if defined(SNITCH_EXPORTS) +# define SNITCH_EXPORT __declspec(dllexport) +# else +# define SNITCH_EXPORT __declspec(dllimport) +# endif +# elif defined(__clang__) || defined(__GNUC__) +# define SNITCH_EXPORT [[gnu::visibility("default")]] +# else +# define SNITCH_EXPORT +# endif +#else +# define SNITCH_EXPORT +#endif + """.split("\n")) + + header.append("") + header.append(f"#endif // {header_guard}") + header.append("") + + with header_file_path.open("w+", encoding="utf-8") as header_file: + header_file.write("\n".join(header)) + +def build_snitch(env): + major_version = 1 + minor_version = 2 + patch_version = 5 + git_run = subprocess.run(["git", "describe", "--tags", "--abbrev=0"], text=True, cwd="snitch/", capture_output=True) + if git_run.returncode == 0: + tag = git_run.stdout.strip().removeprefix("v").split(".") + major_version = tag[0] + minor_version = tag[1] + patch_version = tag[2] + + SHARED_BUILD = False + HEADER_ONLY = False + UNITY_BUILD = True + + SNITCH_VERSION = "{}.{}.{}".format(major_version, minor_version, patch_version) + + snitch_env = env.Clone() + + snitch_env.Append( + BUILDERS={ + "GenerateSnitchConfig": snitch_env.Builder(action=generate_snitch_config_header) + } + ) + + snitch_env.config_data = { + "SNITCH_MAX_TEST_CASES": 5000, #"Maximum number of test cases in a test application." + "SNITCH_MAX_NESTED_SECTIONS": 8,#"Maximum depth of nested sections in a test case." + "SNITCH_MAX_EXPR_LENGTH": 1024, #"Maximum length of a printed expression when reporting failure." + "SNITCH_MAX_MESSAGE_LENGTH": 1024, #"Maximum length of error or status messages." + "SNITCH_MAX_TEST_NAME_LENGTH": 1024, #"Maximum length of a test case name." + "SNITCH_MAX_TAG_LENGTH": 256, #"Maximum length of a test tag." + "SNITCH_MAX_CAPTURES": 8, #"Maximum number of captured expressions in a test case." + "SNITCH_MAX_CAPTURE_LENGTH": 256, #"Maximum length of a captured expression." + "SNITCH_MAX_UNIQUE_TAGS": 1024, #"Maximum number of unique tags in a test application." + "SNITCH_MAX_COMMAND_LINE_ARGS": 1024, #"Maximum number of command line arguments to a test application." + "SNITCH_MAX_REGISTERED_REPORTERS": 8, #"Maximum number of registered reporter that can be selected from the command line." + "SNITCH_MAX_PATH_LENGTH": 1024, #"Maximum length of a file path when writing output to file." + "SNITCH_MAX_REPORTER_SIZE_BYTES": 128, #"Maximum size (in bytes) of a reporter object." + + "SNITCH_DEFINE_MAIN": 1, #"Define main() in snitch -- disable to provide your own main() function." + "SNITCH_WITH_EXCEPTIONS": 0, #"Use exceptions in snitch implementation -- will be forced OFF if exceptions are not available." + "SNITCH_WITH_MULTITHREADING": 0, #"Make the testing framework thread-safe -- disable if multithreading is not needed." + "SNITCH_WITH_TIMINGS": 1, #"Measure the time taken by each test case -- disable to speed up tests." + "SNITCH_WITH_SHORTHAND_MACROS": 1, #"Use short names for test macros -- disable if this causes conflicts." + "SNITCH_CONSTEXPR_FLOAT_USE_BITCAST": 1, #"Use std::bit_cast if available to implement exact constexpr float-to-string conversion." + "SNITCH_APPEND_TO_CHARS": 1, #"Use std::to_chars for string conversions -- disable for greater compatability with a slight performance cost." + "SNITCH_DEFAULT_WITH_COLOR": 1, #"Enable terminal colors by default -- can also be controlled by command line interface." + "SNITCH_DECOMPOSE_SUCCESSFUL_ASSERTIONS": 0, #"Enable expression decomposition even for successful assertions -- more expensive." + "SNITCH_WITH_ALL_REPORTERS": 1, #"Allow all built-in reporters to be selected from the command line -- disable for faster compilation." + "SNITCH_WITH_TEAMCITY_REPORTER": 0, #"Allow the TeamCity reporter to be selected from the command line -- enable if needed." + "SNITCH_WITH_CATCH2_XML_REPORTER": 0, #"Allow the Catch2 XML reporter to be selected from the command line -- enable if needed." + + "SNITCH_HEADER_ONLY": 0, #"Create a single-header header-only version of snitch." + "SNITCH_UNITY_BUILD": ("1" if UNITY_BUILD else "0"), #"Build sources as single file instead of separate files (faster full build)." + "SNITCH_DO_TEST": 1, #"Build tests." + } + + snitch_full_version = "" + git_run = subprocess.run(["git", "log", "-1", "--format=%h"], text=True, cwd="snitch/", capture_output=True) + if git_run.returncode == 0: + snitch_full_version = git_run.stdout.strip() + + snitch_full_version = SNITCH_VERSION + (".{}".format(snitch_full_version) if snitch_full_version else "") + + snitch_env.Append(CPPDEFINES=[ + f'SNITCH_VERSION=\\"{SNITCH_VERSION}\\"', + f'SNITCH_FULL_VERSION=\\"{snitch_full_version}\\"', + f"SNITCH_VERSION_MAJOR={major_version}", + f"SNITCH_VERSION_MINOR={minor_version}", + f"SNITCH_VERSION_PATCH={patch_version}" + ]) + snitch_env.Append(CPPDEFINES=["SNITCH_SHARED_LIBRARY=" + ("1" if not HEADER_ONLY and SHARED_BUILD else "0")]) + + include_path = "snitch/include" + source_path = "snitch/src" + unity_source = "snitch.cpp" + + config = snitch_env.GenerateSnitchConfig(snitch_env.File(Path(include_path) / "snitch" / "snitch_config.hpp"), "SCsub") + + snitch_env.Append(CPPPATH=[[snitch_env.Dir(p) for p in [source_path, include_path]]]) + sources = snitch_env.GlobRecursive("*.cpp", [source_path], unity_source) + + if UNITY_BUILD: + sources = [snitch_env.File(Path(source_path) / unity_source)] + + env.snitch_sources = sources + + if not HEADER_ONLY: + library = None + project_name = "snitch" + library_name = "lib" + project_name + env["LIBSUFFIX"] + if SHARED_BUILD: + if snitch_env.get("is_msvc", False): + pass + else: + snitch_env.Append(CXXFLAGS=["-fvisibility=hidden", "-fvisibility-inlines-hidden"]) + + library = snitch_env.SharedLibrary(target=Path(source_path) / library_name, source=sources) + else: + library = snitch_env.StaticLibrary(target=Path(source_path) / library_name, source=sources) + env.Append(LIBPATH=[snitch_env.Dir(source_path)]) + env.Prepend(LIBS=[library_name]) + + Default([config, library]) + + env.Append(CPPPATH=[snitch_env.Dir(include_path)]) + else: + env.Append(CPPPATH=[[snitch_env.Dir(p) for p in [source_path, include_path]]]) + Default(config) + +build_snitch(env) \ No newline at end of file diff --git a/tests/deps/snitch b/tests/deps/snitch new file mode 160000 index 0000000..d663212 --- /dev/null +++ b/tests/deps/snitch @@ -0,0 +1 @@ +Subproject commit d6632123cc8d13bdbc5cd60fd6741b9e0f635e82 diff --git a/tests/src/Error.cpp b/tests/src/Error.cpp new file mode 100644 index 0000000..136b650 --- /dev/null +++ b/tests/src/Error.cpp @@ -0,0 +1,230 @@ +#include +#include +#include +#include + +#include +#include + +#include "Helper.hpp" +#include +#include + +using namespace ovdl; +using namespace ovdl::error; +using namespace std::string_view_literals; + +struct ErrorTree : SymbolIntern { + using error_range = detail::error_range; + + dryad::node_map map; + dryad::tree tree; + symbol_interner_type symbol_interner; + + NodeLocation location_of(const error::Error* error) const { + auto result = map.lookup(error); + return result ? *result : NodeLocation {}; + } + + template + T* create(BasicNodeLocation loc, Args&&... args) { + using node_creator = dryad::node_creator; + T* result = tree.create(DRYAD_FWD(args)...); + map.insert(result, loc); + return result; + } + + template + T* create(Args&&... args) { + using node_creator = dryad::node_creator; + T* result = tree.create(DRYAD_FWD(args)...); + return result; + } + + error_range get_errors() const { + return tree.root()->errors(); + } + + void insert(error::Error* root) { + tree.root()->insert_back(root); + } +}; + +TEST_CASE("Error Nodes", "[error-nodes]") { + ErrorTree errors; + + auto* buffer_error = errors.create("error"); + CHECK_IF(buffer_error) { + CHECK(buffer_error->kind() == ErrorKind::BufferError); + CHECK(buffer_error->message() == "error"sv); + } + + auto* expect_lit = errors.create("expected lit", "production"); + CHECK_IF(expect_lit) { + CHECK(expect_lit->kind() == ErrorKind::ExpectedLiteral); + CHECK(expect_lit->message() == "expected lit"sv); + CHECK(expect_lit->production_name() == "production"sv); + CHECK(expect_lit->annotations().empty()); + } + + auto* expect_kw = errors.create("expected keyword", "production2"); + CHECK_IF(expect_kw) { + CHECK(expect_kw->kind() == ErrorKind::ExpectedKeyword); + CHECK(expect_kw->message() == "expected keyword"sv); + CHECK(expect_kw->production_name() == "production2"sv); + CHECK(expect_kw->annotations().empty()); + } + + auto* expect_char_c = errors.create("expected char", "production3"); + CHECK_IF(expect_char_c) { + CHECK(expect_char_c->kind() == ErrorKind::ExpectedCharClass); + CHECK(expect_char_c->message() == "expected char"sv); + CHECK(expect_char_c->production_name() == "production3"sv); + CHECK(expect_char_c->annotations().empty()); + } + + auto* generic_error = errors.create("generic error", "production 4"); + CHECK_IF(generic_error) { + CHECK(generic_error->kind() == ErrorKind::GenericParseError); + CHECK(generic_error->message() == "generic error"sv); + CHECK(generic_error->production_name() == "production 4"sv); + CHECK(generic_error->annotations().empty()); + } + + auto* sem_error = errors.create("sem error"); + CHECK_IF(sem_error) { + CHECK(sem_error->kind() == ErrorKind::SemanticError); + CHECK(sem_error->message() == "sem error"sv); + CHECK(sem_error->annotations().empty()); + } + + auto* sem_warn = errors.create("sem warn"); + CHECK_IF(sem_warn) { + CHECK(sem_warn->kind() == ErrorKind::SemanticWarning); + CHECK(sem_warn->message() == "sem warn"sv); + CHECK(sem_warn->annotations().empty()); + } + + auto* sem_info = errors.create("sem info"); + CHECK_IF(sem_info) { + CHECK(sem_info->kind() == ErrorKind::SemanticInfo); + CHECK(sem_info->message() == "sem info"sv); + CHECK(sem_info->annotations().empty()); + } + + auto* sem_debug = errors.create("sem debug"); + CHECK_IF(sem_debug) { + CHECK(sem_debug->kind() == ErrorKind::SemanticDebug); + CHECK(sem_debug->message() == "sem debug"sv); + CHECK(sem_debug->annotations().empty()); + } + + auto* sem_fixit = errors.create("sem fixit"); + CHECK_IF(sem_fixit) { + CHECK(sem_fixit->kind() == ErrorKind::SemanticFixit); + CHECK(sem_fixit->message() == "sem fixit"sv); + CHECK(sem_fixit->annotations().empty()); + } + + auto* sem_help = errors.create("sem help"); + CHECK_IF(sem_help) { + CHECK(sem_help->kind() == ErrorKind::SemanticHelp); + CHECK(sem_help->message() == "sem help"sv); + CHECK(sem_help->annotations().empty()); + } + + auto* prim_annotation = errors.create("primary annotation"); + CHECK_IF(prim_annotation) { + CHECK(prim_annotation->kind() == ErrorKind::PrimaryAnnotation); + CHECK(prim_annotation->message() == "primary annotation"sv); + } + + auto* sec_annotation = errors.create("secondary annotation"); + CHECK_IF(sec_annotation) { + CHECK(sec_annotation->kind() == ErrorKind::SecondaryAnnotation); + CHECK(sec_annotation->message() == "secondary annotation"sv); + } + + AnnotationList annotation_list {}; + annotation_list.push_back(prim_annotation); + annotation_list.push_back(sec_annotation); + CHECK_FALSE(annotation_list.empty()); + + auto* annotated_error = errors.create("annotated error", annotation_list); + CHECK_IF(annotated_error) { + CHECK(annotated_error->kind() == ErrorKind::SemanticError); + CHECK(annotated_error->message() == "annotated error"sv); + auto annotations = annotated_error->annotations(); + CHECK_FALSE(annotations.empty()); + for (const auto [annotation, list_val] : ranges::views::zip(annotations, annotation_list)) { + CHECK_OR_CONTINUE(annotation); + CHECK_OR_CONTINUE(annotation == list_val); + } + } + + auto* root = errors.create(); + CHECK_IF(root) { + CHECK(root->kind() == ErrorKind::Root); + + root->insert_back(annotated_error); + root->insert_back(sem_help); + root->insert_back(sem_fixit); + root->insert_back(sem_debug); + root->insert_back(sem_info); + root->insert_back(sem_warn); + root->insert_back(sem_error); + root->insert_back(generic_error); + root->insert_back(expect_char_c); + root->insert_back(expect_kw); + root->insert_back(expect_lit); + root->insert_back(buffer_error); + + auto errors = root->errors(); + CHECK_FALSE(errors.empty()); + for (const auto [root_index, error] : errors | ranges::views::enumerate) { + CAPTURE(root_index); + CHECK_OR_CONTINUE(error); + switch (root_index) { + case 0: CHECK_OR_CONTINUE(error == annotated_error); break; + case 1: CHECK_OR_CONTINUE(error == sem_help); break; + case 2: CHECK_OR_CONTINUE(error == sem_fixit); break; + case 3: CHECK_OR_CONTINUE(error == sem_debug); break; + case 4: CHECK_OR_CONTINUE(error == sem_info); break; + case 5: CHECK_OR_CONTINUE(error == sem_warn); break; + case 6: CHECK_OR_CONTINUE(error == sem_error); break; + case 7: CHECK_OR_CONTINUE(error == generic_error); break; + case 8: CHECK_OR_CONTINUE(error == expect_char_c); break; + case 9: CHECK_OR_CONTINUE(error == expect_kw); break; + case 10: CHECK_OR_CONTINUE(error == expect_lit); break; + case 11: CHECK_OR_CONTINUE(error == buffer_error); break; + default: CHECK_OR_CONTINUE(false); break; + } + } + } + + errors.tree.set_root(root); + CHECK(errors.tree.has_root()); + CHECK(errors.tree.root() == root); + + errors.tree.clear(); + CHECK_FALSE(errors.tree.has_root()); + CHECK(errors.tree.root() != root); +} + +TEST_CASE("Error Nodes Location", "[error-nodes-location]") { + ErrorTree errors; + + constexpr auto fake_buffer = "id"sv; + + auto* expected_lit = errors.create(NodeLocation::make_from(&fake_buffer[0], &fake_buffer[1]), "expected lit", "production"); + + CHECK_IF(expected_lit) { + CHECK(expected_lit->message() == "expected lit"sv); + CHECK(expected_lit->production_name() == "production"sv); + + auto location = errors.location_of(expected_lit); + CHECK_FALSE(location.is_synthesized()); + CHECK(location.begin() == &fake_buffer[0]); + CHECK(location.end() - 1 == &fake_buffer[1]); + } +} \ No newline at end of file diff --git a/tests/src/Helper.hpp b/tests/src/Helper.hpp new file mode 100644 index 0000000..f12c11d --- /dev/null +++ b/tests/src/Helper.hpp @@ -0,0 +1,60 @@ +#pragma once + +#include + +#define _EXPR(TYPE, EXPECTED, ASSIGN_VALUE, ...) \ + auto SNITCH_CURRENT_EXPRESSION = \ + (snitch::impl::expression_extractor { TYPE, #__VA_ARGS__ } <= __VA_ARGS__) \ + .to_expression(); \ + ASSIGN_VALUE = SNITCH_CURRENT_EXPRESSION.success; + +#define _REQUIRE_IMPL(CHECK, EXPECTED, MAYBE_ABORT, ASSIGN_VALUE, ...) \ + do { \ + auto SNITCH_CURRENT_CHECK = SNITCH_NEW_CHECK; \ + SNITCH_WARNING_PUSH \ + SNITCH_WARNING_DISABLE_PARENTHESES \ + SNITCH_WARNING_DISABLE_CONSTANT_COMPARISON \ + if constexpr (SNITCH_IS_DECOMPOSABLE(__VA_ARGS__)) { \ + _EXPR(CHECK, EXPECTED, ASSIGN_VALUE, __VA_ARGS__); \ + ASSIGN_VALUE = EXPECTED; \ + SNITCH_REPORT_EXPRESSION(MAYBE_ABORT); \ + } else { \ + ASSIGN_VALUE = static_cast(__VA_ARGS__); \ + const auto SNITCH_CURRENT_EXPRESSION = snitch::impl::expression { \ + CHECK, #__VA_ARGS__, {}, ASSIGN_VALUE == EXPECTED \ + }; \ + SNITCH_REPORT_EXPRESSION(MAYBE_ABORT); \ + } \ + SNITCH_WARNING_POP \ + } while (0) + +// clang-format off +#define _OVDL_REQUIRE(NAME, ASSIGN_VALUE, ...) _REQUIRE_IMPL("REQUIRE" NAME, true, SNITCH_TESTING_ABORT, ASSIGN_VALUE, __VA_ARGS__) +#define _OVDL_CHECK(NAME, ASSIGN_VALUE, ...) _REQUIRE_IMPL("CHECK" NAME, true, (void)0, ASSIGN_VALUE, __VA_ARGS__) +#define _OVDL_REQUIRE_FALSE(NAME, ASSIGN_VALUE, ...) _REQUIRE_IMPL("REQUIRE_FALSE" NAME, false, SNITCH_TESTING_ABORT, ASSIGN_VALUE, __VA_ARGS__) +#define _OVDL_CHECK_FALSE(NAME, ASSIGN_VALUE, ...) _REQUIRE_IMPL("CHECK_FALSE" NAME, false, (void)0, ASSIGN_VALUE, __VA_ARGS__) +// clang-format on + +#define _OVDL_CHECK_IF(NAME, ...) \ + if (bool SNITCH_MACRO_CONCAT(result_, __LINE__) = false; [&] { _OVDL_CHECK(NAME, (SNITCH_MACRO_CONCAT(result_, __LINE__)), __VA_ARGS__); }(), (SNITCH_MACRO_CONCAT(result_, __LINE__))) + +#define _OVDL_CHECK_FALSE_IF(NAME, ...) \ + if (bool SNITCH_MACRO_CONCAT(result_, __LINE__) = false; [&] { _OVDL_CHECK_FALSE(NAME, (SNITCH_MACRO_CONCAT(result_, __LINE__)), __VA_ARGS__); }(), (!SNITCH_MACRO_CONCAT(result_, __LINE__))) + +#define CHECK_IF(...) _OVDL_CHECK_IF("_IF", __VA_ARGS__) + +#define CHECK_FALSE_IF(...) _OVDL_CHECK_FALSE_IF("_IF", __VA_ARGS__) + +#define CHECK_OR_RETURN(...) \ + _OVDL_CHECK_IF("_OR_RETURN", __VA_ARGS__); \ + else return +#define CHECK_FALSE_OR_RETURN(...) \ + _OVDL_CHECK_FALSE_IF("_OR_RETURN", __VA_ARGS__); \ + else return + +#define CHECK_OR_CONTINUE(...) \ + _OVDL_CHECK_IF("_OR_CONTINUE", __VA_ARGS__); \ + else continue +#define CHECK_FALSE_OR_CONTINUE(...) \ + _OVDL_CHECK_FALSE_IF("_OR_CONTINUE", __VA_ARGS__); \ + else continue diff --git a/tests/src/NodeLocation.cpp b/tests/src/NodeLocation.cpp new file mode 100644 index 0000000..97e9ac8 --- /dev/null +++ b/tests/src/NodeLocation.cpp @@ -0,0 +1,300 @@ +#include + +#include + +#include + +using namespace ovdl; +using namespace std::string_view_literals; + +TEST_CASE("NodeLocation", "[node-location]") { + BasicNodeLocation char_node_location; + CHECK(char_node_location.begin() == nullptr); + CHECK(char_node_location.end() == nullptr); + CHECK(char_node_location.is_synthesized()); + + BasicNodeLocation uchar_node_location; + CHECK(uchar_node_location.begin() == nullptr); + CHECK(uchar_node_location.end() == nullptr); + CHECK(uchar_node_location.is_synthesized()); + + BasicNodeLocation char_16_node_location; + CHECK(char_16_node_location.begin() == nullptr); + CHECK(char_16_node_location.end() == nullptr); + CHECK(char_16_node_location.is_synthesized()); + + BasicNodeLocation char_32_node_location; + CHECK(char_32_node_location.begin() == nullptr); + CHECK(char_32_node_location.end() == nullptr); + CHECK(char_32_node_location.is_synthesized()); + +#ifdef __cpp_char8_t + BasicNodeLocation char_8_node_location; + CHECK(char_8_node_location.begin() == nullptr); + CHECK(char_8_node_location.end() == nullptr); + CHECK(char_8_node_location.is_synthesized()); +#endif + + static constexpr auto char_buffer = "buffer"sv; + + static constexpr unsigned char uarray[] = "buffer"; + static constexpr std::basic_string_view uchar_buffer = uarray; + + static constexpr auto char_16_buffer = u"buffer"sv; + static constexpr auto char_32_buffer = U"buffer"sv; + +#ifdef __cpp_char8_t + static constexpr auto char_8_buffer = u8"buffer"sv; +#endif + + char_node_location = { &char_buffer[0] }; + CHECK(char_node_location.begin() == &char_buffer[0]); + CHECK(char_node_location.end() == &char_buffer[0]); + CHECK_FALSE(char_node_location.is_synthesized()); + + uchar_node_location = { &uchar_buffer[0] }; + CHECK(uchar_node_location.begin() == &uchar_buffer[0]); + CHECK(uchar_node_location.end() == &uchar_buffer[0]); + CHECK_FALSE(uchar_node_location.is_synthesized()); + + char_16_node_location = { &char_16_buffer[0] }; + CHECK(char_16_node_location.begin() == &char_16_buffer[0]); + CHECK(char_16_node_location.end() == &char_16_buffer[0]); + CHECK_FALSE(char_16_node_location.is_synthesized()); + + char_32_node_location = { &char_32_buffer[0] }; + CHECK(char_32_node_location.begin() == &char_32_buffer[0]); + CHECK(char_32_node_location.end() == &char_32_buffer[0]); + CHECK_FALSE(char_32_node_location.is_synthesized()); + +#ifdef __cpp_char8_t + char_8_node_location = { &char_8_buffer[0] }; + CHECK(char_8_node_location.begin() == &char_8_buffer[0]); + CHECK(char_8_node_location.end() == &char_8_buffer[0]); + CHECK_FALSE(char_8_node_location.is_synthesized()); +#endif + + char_node_location = { &char_buffer[0], &char_buffer.back() }; + CHECK(char_node_location.begin() == &char_buffer[0]); + CHECK(char_node_location.end() == &char_buffer.back()); + CHECK_FALSE(char_node_location.is_synthesized()); + + uchar_node_location = { &uchar_buffer[0], &uchar_buffer.back() }; + CHECK(uchar_node_location.begin() == &uchar_buffer[0]); + CHECK(uchar_node_location.end() == &uchar_buffer.back()); + CHECK_FALSE(uchar_node_location.is_synthesized()); + + char_16_node_location = { &char_16_buffer[0], &char_16_buffer.back() }; + CHECK(char_16_node_location.begin() == &char_16_buffer[0]); + CHECK(char_16_node_location.end() == &char_16_buffer.back()); + CHECK_FALSE(char_16_node_location.is_synthesized()); + + char_32_node_location = { &char_32_buffer[0], &char_32_buffer.back() }; + CHECK(char_32_node_location.begin() == &char_32_buffer[0]); + CHECK(char_32_node_location.end() == &char_32_buffer.back()); + CHECK_FALSE(char_32_node_location.is_synthesized()); + +#ifdef __cpp_char8_t + char_8_node_location = { &char_8_buffer[0], &char_8_buffer.back() }; + CHECK(char_8_node_location.begin() == &char_8_buffer[0]); + CHECK(char_8_node_location.end() == &char_8_buffer.back()); + CHECK_FALSE(char_8_node_location.is_synthesized()); +#endif + + BasicNodeLocation char_node_location_copy = { char_node_location }; + char_node_location_copy._begin++; + CHECK(char_node_location.begin() == &char_buffer[0]); + CHECK(char_node_location_copy.begin() == &char_buffer[1]); + CHECK(char_node_location_copy.end() == &char_buffer.back()); + CHECK_FALSE(char_node_location_copy.is_synthesized()); + + BasicNodeLocation uchar_node_location_copy = { uchar_node_location }; + uchar_node_location_copy._begin++; + CHECK(uchar_node_location.begin() == &uchar_buffer[0]); + CHECK(uchar_node_location_copy.begin() == &uchar_buffer[1]); + CHECK(uchar_node_location_copy.end() == &uchar_buffer.back()); + CHECK_FALSE(uchar_node_location_copy.is_synthesized()); + + BasicNodeLocation char_16_node_location_copy = { char_16_node_location }; + char_16_node_location_copy._begin++; + CHECK(char_16_node_location.begin() == &char_16_buffer[0]); + CHECK(char_16_node_location_copy.begin() == &char_16_buffer[1]); + CHECK(char_16_node_location_copy.end() == &char_16_buffer.back()); + CHECK_FALSE(char_16_node_location_copy.is_synthesized()); + + BasicNodeLocation char_32_node_location_copy = { char_32_node_location }; + char_32_node_location_copy._begin++; + CHECK(char_32_node_location.begin() == &char_32_buffer[0]); + CHECK(char_32_node_location_copy.begin() == &char_32_buffer[1]); + CHECK(char_32_node_location_copy.end() == &char_32_buffer.back()); + CHECK_FALSE(char_32_node_location_copy.is_synthesized()); + +#ifdef __cpp_char8_t + BasicNodeLocation char_8_node_location_copy = { char_8_node_location }; + char_8_node_location_copy._begin++; + CHECK(char_8_node_location.begin() == &char_8_buffer[0]); + CHECK(char_8_node_location_copy.begin() == &char_8_buffer[1]); + CHECK(char_8_node_location_copy.end() == &char_8_buffer.back()); + CHECK_FALSE(char_8_node_location_copy.is_synthesized()); +#endif + + BasicNodeLocation char_node_location_move = { std::move(char_node_location) }; + CHECK(char_node_location_move.begin() == char_node_location.begin()); + CHECK_FALSE(char_node_location.is_synthesized()); + CHECK(char_node_location_move.begin() == &char_buffer[0]); + CHECK(char_node_location_move.end() == &char_buffer.back()); + CHECK_FALSE(char_node_location_move.is_synthesized()); + + BasicNodeLocation uchar_node_location_move = { std::move(uchar_node_location) }; + CHECK(uchar_node_location_move.begin() == uchar_node_location.begin()); + CHECK_FALSE(uchar_node_location.is_synthesized()); + CHECK(uchar_node_location_move.begin() == &uchar_buffer[0]); + CHECK(uchar_node_location_move.end() == &uchar_buffer.back()); + CHECK_FALSE(uchar_node_location_move.is_synthesized()); + + BasicNodeLocation char_16_node_location_move = { std::move(char_16_node_location) }; + CHECK(char_16_node_location_move.begin() == char_16_node_location.begin()); + CHECK_FALSE(char_16_node_location.is_synthesized()); + CHECK(char_16_node_location_move.begin() == &char_16_buffer[0]); + CHECK(char_16_node_location_move.end() == &char_16_buffer.back()); + CHECK_FALSE(char_16_node_location_move.is_synthesized()); + + BasicNodeLocation char_32_node_location_move = { std::move(char_32_node_location) }; + CHECK(char_32_node_location_move.begin() == char_32_node_location.begin()); + CHECK_FALSE(char_32_node_location_move.is_synthesized()); + CHECK(char_32_node_location_move.begin() == &char_32_buffer[0]); + CHECK(char_32_node_location_move.end() == &char_32_buffer.back()); + CHECK_FALSE(char_32_node_location_move.is_synthesized()); + +#ifdef __cpp_char8_t + BasicNodeLocation char_8_node_location_move = { std::move(char_8_node_location) }; + CHECK(char_8_node_location_move.begin() == char_8_node_location.begin()); + CHECK_FALSE(char_8_node_location.is_synthesized()); + CHECK(char_8_node_location_move.begin() == &char_8_buffer[0]); + CHECK(char_8_node_location_move.end() == &char_8_buffer.back()); + CHECK_FALSE(char_8_node_location_move.is_synthesized()); +#endif + + BasicNodeLocation char_node_from; + char_node_from.set_from(char_node_location); + CHECK(char_node_from.begin() == &char_buffer[0]); + CHECK(char_node_from.end() == &char_buffer.back()); + CHECK_FALSE(char_node_from.is_synthesized()); + + BasicNodeLocation uchar_node_from; + uchar_node_from.set_from(uchar_node_location); + CHECK(uchar_node_from.begin() == &uchar_buffer[0]); + CHECK(uchar_node_from.end() == &uchar_buffer.back()); + CHECK_FALSE(uchar_node_from.is_synthesized()); + + BasicNodeLocation char_16_node_from; + char_16_node_from.set_from(char_16_node_location); + CHECK(char_16_node_from.begin() == &char_16_buffer[0]); + CHECK(char_16_node_from.end() == &char_16_buffer.back()); + CHECK_FALSE(char_16_node_from.is_synthesized()); + + BasicNodeLocation char_32_node_from; + char_32_node_from.set_from(char_32_node_location); + CHECK(char_32_node_from.begin() == &char_32_buffer[0]); + CHECK(char_32_node_from.end() == &char_32_buffer.back()); + CHECK_FALSE(char_32_node_from.is_synthesized()); + +#ifdef __cpp_char8_t + BasicNodeLocation char_8_node_from; + char_8_node_from.set_from(char_8_node_location); + CHECK(char_8_node_from.begin() == &char_8_buffer[0]); + CHECK(char_8_node_from.end() == &char_8_buffer.back()); + CHECK_FALSE(char_8_node_from.is_synthesized()); +#endif + + char_node_from.set_from(uchar_node_location); + CHECK(char_node_from.begin() == reinterpret_cast(&uchar_buffer[0])); + CHECK(char_node_from.end() == reinterpret_cast(&uchar_buffer.back())); + CHECK_FALSE(char_node_from.is_synthesized()); + + uchar_node_from.set_from(char_node_location); + CHECK(uchar_node_from.begin() == reinterpret_cast(&char_buffer[0])); + CHECK(uchar_node_from.end() == reinterpret_cast(&char_buffer.back())); + CHECK_FALSE(uchar_node_from.is_synthesized()); + + char_16_node_from.set_from(char_node_location); + CHECK(char_16_node_from.begin() == reinterpret_cast(&char_buffer[0])); + CHECK(char_16_node_from.end() == reinterpret_cast(&char_buffer.back() - 1)); + CHECK_FALSE(char_16_node_from.is_synthesized()); + + char_32_node_from.set_from(char_node_location); + CHECK(char_32_node_from.begin() == reinterpret_cast(&char_buffer[0])); + CHECK(char_32_node_from.end() == reinterpret_cast(&char_buffer.back() - 3)); + CHECK_FALSE(char_32_node_from.is_synthesized()); + +#ifdef __cpp_char8_t + char_8_node_from.set_from(char_node_location); + CHECK(char_8_node_from.begin() == reinterpret_cast(&char_buffer[0])); + CHECK(char_8_node_from.end() == reinterpret_cast(&char_buffer.back())); + CHECK_FALSE(char_8_node_from.is_synthesized()); +#endif + + char_node_from.set_from(char_16_node_location); + CHECK(char_node_from.begin() == reinterpret_cast(&char_16_buffer[0])); + CHECK(char_node_from.end() == reinterpret_cast(&char_16_buffer.back()) + 1); + CHECK_FALSE(char_node_from.is_synthesized()); + + char_node_from.set_from(char_32_node_location); + CHECK(char_node_from.begin() == reinterpret_cast(&char_32_buffer[0])); + CHECK(char_node_from.end() == reinterpret_cast(&char_32_buffer.back()) + 3); + CHECK_FALSE(char_node_from.is_synthesized()); + + auto char_node_make = BasicNodeLocation::make_from(&char_buffer[0], &char_buffer.back()); + CHECK(char_node_make.begin() == &char_buffer[0]); + CHECK(char_node_make.end() == &char_buffer.back() + 1); + CHECK_FALSE(char_node_make.is_synthesized()); + + auto uchar_node_make = BasicNodeLocation::make_from(&uchar_buffer[0], &uchar_buffer.back()); + CHECK(uchar_node_make.begin() == &uchar_buffer[0]); + CHECK(uchar_node_make.end() == &uchar_buffer.back() + 1); + CHECK_FALSE(uchar_node_make.is_synthesized()); + + auto char_16_node_make = BasicNodeLocation::make_from(&char_16_buffer[0], &char_16_buffer.back()); + CHECK(char_16_node_make.begin() == &char_16_buffer[0]); + CHECK(char_16_node_make.end() == &char_16_buffer.back() + 1); + CHECK_FALSE(char_16_node_make.is_synthesized()); + + auto char_32_node_make = BasicNodeLocation::make_from(&char_32_buffer[0], &char_32_buffer.back()); + CHECK(char_32_node_make.begin() == &char_32_buffer[0]); + CHECK(char_32_node_make.end() == &char_32_buffer.back() + 1); + CHECK_FALSE(char_32_node_make.is_synthesized()); + +#ifdef __cpp_char8_t + auto char_8_node_make = BasicNodeLocation::make_from(&char_8_buffer[0], &char_8_buffer.back()); + CHECK(char_8_node_make.begin() == &char_8_buffer[0]); + CHECK(char_8_node_make.end() == &char_8_buffer.back() + 1); + CHECK_FALSE(char_8_node_make.is_synthesized()); +#endif + + char_node_make = BasicNodeLocation::make_from(&char_buffer[0] + 1, &char_buffer[0]); + CHECK(char_node_make.begin() == &char_buffer[0] + 1); + CHECK(char_node_make.end() == &char_buffer[0] + 1); + CHECK_FALSE(char_node_make.is_synthesized()); + + uchar_node_make = BasicNodeLocation::make_from(&uchar_buffer[0] + 1, &uchar_buffer[0]); + CHECK(uchar_node_make.begin() == &uchar_buffer[0] + 1); + CHECK(uchar_node_make.end() == &uchar_buffer[0] + 1); + CHECK_FALSE(uchar_node_make.is_synthesized()); + + char_16_node_make = BasicNodeLocation::make_from(&char_16_buffer[0] + 1, &char_16_buffer[0]); + CHECK(char_16_node_make.begin() == &char_16_buffer[0] + 1); + CHECK(char_16_node_make.end() == &char_16_buffer[0] + 1); + CHECK_FALSE(char_16_node_make.is_synthesized()); + + char_32_node_make = BasicNodeLocation::make_from(&char_32_buffer[0] + 1, &char_32_buffer[0]); + CHECK(char_32_node_make.begin() == &char_32_buffer[0] + 1); + CHECK(char_32_node_make.end() == &char_32_buffer[0] + 1); + CHECK_FALSE(char_32_node_make.is_synthesized()); + +#ifdef __cpp_char8_t + char_8_node_make = BasicNodeLocation::make_from(&char_8_buffer[0] + 1, &char_8_buffer[0]); + CHECK(char_8_node_make.begin() == &char_8_buffer[0] + 1); + CHECK(char_8_node_make.end() == &char_8_buffer[0] + 1); + CHECK_FALSE(char_8_node_make.is_synthesized()); +#endif +} \ No newline at end of file diff --git a/tests/src/csv/LineObject.cpp b/tests/src/csv/LineObject.cpp new file mode 100644 index 0000000..4ab73b6 --- /dev/null +++ b/tests/src/csv/LineObject.cpp @@ -0,0 +1,196 @@ +#include +#include + +#include + +#include +#include + +using namespace ovdl; +using namespace csv; +using namespace std::string_view_literals; + +TEST_CASE("LineObject", "[line-object]") { + LineObject line; + + SECTION("empty") { + CHECK(ranges::size(line) == 0); + + CHECK(line.get_value_for(0) == ""sv); + CHECK(line.get_value_for(1) == ""sv); + CHECK(line.get_value_for(2) == ""sv); + CHECK(line.get_value_for(3) == ""sv); + CHECK(line.get_value_for(4) == ""sv); + + SECTION("ostream print") { + std::stringstream ss; + ss << line << std::flush; + + CHECK(ss.str() == ""sv); + } + } + + SECTION("no prefix") { + line.push_back({ 0, "a" }); + line.push_back({ 1, "b" }); + line.push_back({ 2, "c" }); + line.set_suffix_end(3); + + CHECK(ranges::size(line) == 3); + + CHECK(line.get_value_for(0) == "a"sv); + CHECK(line.get_value_for(1) == "b"sv); + CHECK(line.get_value_for(2) == "c"sv); + CHECK(line.get_value_for(3) == ""sv); + CHECK(line.get_value_for(4) == ""sv); + + SECTION("ostream print") { + std::stringstream ss; + ss << line << std::flush; + + CHECK(ss.str() == "a;b;c"sv); + } + } + + SECTION("no suffix") { + line.push_back({ 0, "a" }); + line.push_back({ 1, "b" }); + line.push_back({ 2, "c" }); + + CHECK(ranges::size(line) == 3); + + CHECK_FALSE(line.get_value_for(0) == "a"sv); + CHECK_FALSE(line.get_value_for(1) == "b"sv); + CHECK_FALSE(line.get_value_for(2) == "c"sv); + CHECK(line.get_value_for(3) == ""sv); + CHECK(line.get_value_for(4) == ""sv); + } + + SECTION("prefix and suffix") { + line.set_prefix_end(1); + line.push_back({ 1, "a" }); + line.push_back({ 2, "b" }); + line.push_back({ 3, "c" }); + line.set_suffix_end(4); + + CHECK(ranges::size(line) == 3); + + CHECK(line.get_value_for(0) == ""sv); + CHECK(line.get_value_for(1) == "a"sv); + CHECK(line.get_value_for(2) == "b"sv); + CHECK(line.get_value_for(3) == "c"sv); + CHECK(line.get_value_for(4) == ""sv); + CHECK(line.get_value_for(5) == ""sv); + + SECTION("ostream print") { + std::stringstream ss; + ss << line << std::flush; + + CHECK(ss.str() == ";a;b;c"sv); + } + } + + SECTION("prefix and suffix with spaces") { + line.set_prefix_end(1); + line.push_back({ 1, "a b" }); + line.push_back({ 2, "c d" }); + line.push_back({ 3, "e f" }); + line.set_suffix_end(4); + + CHECK(ranges::size(line) == 3); + + CHECK(line.get_value_for(0) == ""sv); + CHECK(line.get_value_for(1) == "a b"sv); + CHECK(line.get_value_for(2) == "c d"sv); + CHECK(line.get_value_for(3) == "e f"sv); + CHECK(line.get_value_for(4) == ""sv); + CHECK(line.get_value_for(5) == ""sv); + + SECTION("ostream print") { + std::stringstream ss; + ss << line << std::flush; + + CHECK(ss.str() == ";\"a b\";\"c d\";\"e f\""sv); + } + } + + SECTION("prefix and suffix with separators") { + line.set_prefix_end(1); + line.push_back({ 1, "a;b" }); + line.push_back({ 2, "c;d" }); + line.push_back({ 3, "e;f" }); + line.set_suffix_end(4); + + CHECK(ranges::size(line) == 3); + + CHECK(line.get_value_for(0) == ""sv); + CHECK(line.get_value_for(1) == "a;b"sv); + CHECK(line.get_value_for(2) == "c;d"sv); + CHECK(line.get_value_for(3) == "e;f"sv); + CHECK(line.get_value_for(4) == ""sv); + CHECK(line.get_value_for(5) == ""sv); + + SECTION("ostream print") { + std::stringstream ss; + ss << line << std::flush; + + CHECK(ss.str() == ";\"a;b\";\"c;d\";\"e;f\""sv); + } + } + + SECTION("prefix and suffix with custom char separator") { + line.set_prefix_end(1); + line.push_back({ 1, "a;b" }); + line.push_back({ 2, "c;d" }); + line.push_back({ 3, "e;f" }); + line.set_suffix_end(4); + + CHECK(ranges::size(line) == 3); + + std::stringstream ss; + ss << line.use_sep("|") << std::flush; + + CHECK(ss.str() == "|a;b|c;d|e;f"sv); + } + + SECTION("prefix and suffix with custom char separator and containing the separator") { + line.set_prefix_end(1); + line.push_back({ 1, "a|b" }); + line.push_back({ 2, "c|d" }); + line.push_back({ 3, "e|f" }); + line.set_suffix_end(4); + + CHECK(ranges::size(line) == 3); + + CHECK(line.get_value_for(0) == ""sv); + CHECK(line.get_value_for(1) == "a|b"sv); + CHECK(line.get_value_for(2) == "c|d"sv); + CHECK(line.get_value_for(3) == "e|f"sv); + CHECK(line.get_value_for(4) == ""sv); + CHECK(line.get_value_for(5) == ""sv); + + SECTION("ostream print") { + std::stringstream ss; + ss << line.use_sep("|") << std::flush; + + CHECK(ss.str() == "|\"a|b\"|\"c|d\"|\"e|f\""sv); + } + } + + SECTION("prefix and suffix with custom string_view separator") { + line.set_prefix_end(1); + line.push_back({ 1, "a;b" }); + line.push_back({ 2, "c;d" }); + line.push_back({ 3, "e;f" }); + line.set_suffix_end(4); + + CHECK(ranges::size(line) == 3); + + SECTION("ostream print") { + std::stringstream ss; + ss << line.use_sep("hey") << std::flush; + + CHECK(ss.str() == "heya;bheyc;dheye;f"sv); + } + } +} \ No newline at end of file diff --git a/tests/src/csv/Parser.cpp b/tests/src/csv/Parser.cpp new file mode 100644 index 0000000..e72c02a --- /dev/null +++ b/tests/src/csv/Parser.cpp @@ -0,0 +1,571 @@ +#include +#include +#include + +#include +#include + +#include "Helper.hpp" +#include +#include +#include +#include +#include + +using namespace ovdl; +using namespace csv; +using namespace std::string_view_literals; + +static constexpr auto csv_buffer = "a;b;c"sv; +static constexpr auto csv_path = "file.csv"sv; + +static void SetupFile(std::string_view path) { + std::ofstream stream(path.data()); + stream << csv_buffer << std::flush; +} + +#define CHECK_PARSE(...) \ + CHECK_OR_RETURN(parser.get_errors().empty()); \ + CHECK_OR_RETURN(parser.parse_csv(__VA_ARGS__)); \ + CHECK_OR_RETURN(parser.get_errors().empty()); + +TEST_CASE("CSV Memory Buffer (data, size) Parse", "[csv-memory-parse][buffer][data-size]") { + Parser parser(ovdl::detail::cnull); + + parser.load_from_buffer(csv_buffer.data(), csv_buffer.size()); + + CHECK_PARSE(false); +} + +TEST_CASE("CSV Memory Buffer (begin, end) Parse", "[csv-memory-parse][buffer][begin-end]") { + Parser parser(ovdl::detail::cnull); + + parser.load_from_buffer(csv_buffer.data(), csv_buffer.data() + csv_buffer.size()); + + CHECK_PARSE(false); +} + +TEST_CASE("CSV Memory String Parse", "[csv-memory-parse][string]") { + Parser parser(ovdl::detail::cnull); + + parser.load_from_string(csv_buffer); + + CHECK_PARSE(true); +} + +TEST_CASE("CSV Memory Buffer (data, size) Handle String Parse", "[csv-memory-parse][handle-string][buffer][data-size]") { + Parser parser(ovdl::detail::cnull); + + parser.load_from_buffer(csv_buffer.data(), csv_buffer.size()); + + CHECK_PARSE(true); +} + +TEST_CASE("CSV Memory Buffer (begin, end) Handle String Parse", "[csv-memory-parse][handle-string][buffer][begin-end]") { + Parser parser(ovdl::detail::cnull); + + parser.load_from_buffer(csv_buffer.data(), csv_buffer.data() + csv_buffer.size()); + + CHECK_PARSE(false); +} + +TEST_CASE("CSV Memory String Handle String Parse", "[csv-memory-parse][handle-string][string]") { + Parser parser(ovdl::detail::cnull); + + parser.load_from_string(csv_buffer); + + CHECK_PARSE(true); +} + +TEST_CASE("CSV File (const char*) Parse", "[csv-file-parse][char-ptr]") { + SetupFile(csv_path); + + Parser parser(ovdl::detail::cnull); + + parser.load_from_file(csv_path.data()); + + std::filesystem::remove(csv_path); + + CHECK_PARSE(false); +} + +TEST_CASE("CSV File (filesystem::path) Parse", "[csv-file-parse][filesystem-path]") { + SetupFile(csv_path); + + Parser parser(ovdl::detail::cnull); + + parser.load_from_file(std::filesystem::path(csv_path)); + + std::filesystem::remove(csv_path); + + CHECK_PARSE(false); +} + +TEST_CASE("CSV File (HasCstr) Parse", "[csv-file-parse][has-cstr]") { + SetupFile(csv_path); + + Parser parser(ovdl::detail::cnull); + + parser.load_from_file(std::string { csv_path }); + + std::filesystem::remove(csv_path); + + CHECK_PARSE(false); +} + +TEST_CASE("CSV File (const char*) Handle String Parse", "[csv-file-parse][handle-string][char-ptr]") { + SetupFile(csv_path); + + Parser parser(ovdl::detail::cnull); + + parser.load_from_file(csv_path.data()); + + std::filesystem::remove(csv_path); + + CHECK_PARSE(true); +} + +TEST_CASE("CSV File (filesystem::path) Handle String Parse", "[csv-file-parse][handle-string][filesystem-path]") { + SetupFile(csv_path); + + Parser parser(ovdl::detail::cnull); + + parser.load_from_file(std::filesystem::path(csv_path)); + + std::filesystem::remove(csv_path); + + CHECK_PARSE(true); +} + +TEST_CASE("CSV File (HasCstr) Handle String Parse", "[csv-file-parse][handle-string][has-cstr]") { + SetupFile(csv_path); + + Parser parser(ovdl::detail::cnull); + + parser.load_from_file(std::string { csv_path }); + + std::filesystem::remove(csv_path); + + CHECK_PARSE(true); +} + +TEST_CASE("CSV Parse", "[csv-parse]") { + Parser parser(ovdl::detail::cnull); + + SECTION("a;b;c") { + static constexpr auto buffer = "a;b;c"sv; + parser.load_from_string(buffer); + + CHECK_PARSE(); + + const std::vector& line_list = parser.get_lines(); + CHECK_FALSE(line_list.empty()); + CHECK(ranges::size(line_list) == 1); + + const LineObject& line = line_list.front(); + CHECK_FALSE(line.empty()); + CHECK(ranges::size(line) == 3); + CHECK(line.value_count() == 3); + CHECK(line.prefix_end() == 0); + CHECK(line.suffix_end() == 3); + + for (const auto [index, val] : line | ranges::views::enumerate) { + CAPTURE(index); + CHECK_FALSE_OR_CONTINUE(val.second.empty()); + switch (index) { + case 0: + CHECK_OR_CONTINUE(val.first == 0); + CHECK_OR_CONTINUE(val.second == "a"sv); + break; + case 1: + CHECK_OR_CONTINUE(val.first == 1); + CHECK_OR_CONTINUE(val.second == "b"sv); + break; + case 2: + CHECK_OR_CONTINUE(val.first == 2); + CHECK_OR_CONTINUE(val.second == "c"sv); + break; + default: CHECK_OR_CONTINUE(false); break; + } + } + + CHECK(line.value_count() == 3); + + for (const auto index : ranges::views::iota(size_t(0), line.value_count())) { + CAPTURE(index); + switch (index) { + case 0: CHECK_OR_CONTINUE(line.get_value_for(index) == "a"sv); break; + case 1: CHECK_OR_CONTINUE(line.get_value_for(index) == "b"sv); break; + case 2: CHECK_OR_CONTINUE(line.get_value_for(index) == "c"sv); break; + default: CHECK_OR_CONTINUE(false); break; + } + } + } + + SECTION(";a;b;c") { + static constexpr auto buffer = ";a;b;c"sv; + parser.load_from_string(buffer); + + CHECK_PARSE(); + + const std::vector& line_list = parser.get_lines(); + CHECK_FALSE(line_list.empty()); + CHECK(ranges::size(line_list) == 1); + + const LineObject& line = line_list.front(); + CHECK_FALSE(line.empty()); + CHECK(ranges::size(line) == 3); + CHECK(line.value_count() == 4); + CHECK(line.prefix_end() == 1); + CHECK(line.suffix_end() == 4); + + for (const auto [index, val] : line | ranges::views::enumerate) { + CAPTURE(index); + CHECK_FALSE_OR_CONTINUE(val.second.empty()); + switch (index) { + case 0: + CHECK_OR_CONTINUE(val.first == 1); + CHECK_OR_CONTINUE(val.second == "a"sv); + break; + case 1: + CHECK_OR_CONTINUE(val.first == 2); + CHECK_OR_CONTINUE(val.second == "b"sv); + break; + case 2: + CHECK_OR_CONTINUE(val.first == 3); + CHECK_OR_CONTINUE(val.second == "c"sv); + break; + default: CHECK_OR_CONTINUE(false); break; + } + } + + CHECK(line.value_count() == 4); + + for (const auto index : ranges::views::iota(size_t(0), line.value_count())) { + CAPTURE(index); + switch (index) { + case 0: break; + case 1: CHECK_OR_CONTINUE(line.get_value_for(index) == "a"sv); break; + case 2: CHECK_OR_CONTINUE(line.get_value_for(index) == "b"sv); break; + case 3: CHECK_OR_CONTINUE(line.get_value_for(index) == "c"sv); break; + default: CHECK_OR_CONTINUE(false); break; + } + } + } + + SECTION(";a;b;c;") { + static constexpr auto buffer = ";a;b;c;"sv; + parser.load_from_string(buffer); + + CHECK_PARSE(); + + const std::vector& line_list = parser.get_lines(); + CHECK_FALSE(line_list.empty()); + CHECK(ranges::size(line_list) == 1); + + const LineObject& line = line_list.front(); + CHECK_FALSE(line.empty()); + CHECK(ranges::size(line) == 3); + CHECK(line.value_count() == 4); + CHECK(line.prefix_end() == 1); + CHECK(line.suffix_end() == 4); + + for (const auto [index, val] : line | ranges::views::enumerate) { + CAPTURE(index); + CHECK_FALSE_OR_CONTINUE(val.second.empty()); + switch (index) { + case 0: + CHECK_OR_CONTINUE(val.first == 1); + CHECK_OR_CONTINUE(val.second == "a"sv); + break; + case 1: + CHECK_OR_CONTINUE(val.first == 2); + CHECK_OR_CONTINUE(val.second == "b"sv); + break; + case 2: + CHECK_OR_CONTINUE(val.first == 3); + CHECK_OR_CONTINUE(val.second == "c"sv); + break; + default: CHECK_OR_CONTINUE(false); break; + } + } + + CHECK(line.value_count() == 4); + + for (const auto index : ranges::views::iota(size_t(0), line.value_count())) { + CAPTURE(index); + switch (index) { + case 0: break; + case 1: CHECK_OR_CONTINUE(line.get_value_for(index) == "a"sv); break; + case 2: CHECK_OR_CONTINUE(line.get_value_for(index) == "b"sv); break; + case 3: CHECK_OR_CONTINUE(line.get_value_for(index) == "c"sv); break; + default: CHECK_OR_CONTINUE(false); break; + } + } + } + + SECTION(";;a;b;c;") { + static constexpr auto buffer = ";;a;b;c;"sv; + parser.load_from_string(buffer); + + CHECK_PARSE(); + + const std::vector& line_list = parser.get_lines(); + CHECK_FALSE(line_list.empty()); + CHECK(ranges::size(line_list) == 1); + + const LineObject& line = line_list.front(); + CHECK_FALSE(line.empty()); + CHECK(ranges::size(line) == 3); + CHECK(line.value_count() == 5); + CHECK(line.prefix_end() == 2); + CHECK(line.suffix_end() == 5); + + for (const auto [index, val] : line | ranges::views::enumerate) { + CAPTURE(index); + CHECK_FALSE_OR_CONTINUE(val.second.empty()); + switch (index) { + case 0: + CHECK_OR_CONTINUE(val.first == 2); + CHECK_OR_CONTINUE(val.second == "a"sv); + break; + case 1: + CHECK_OR_CONTINUE(val.first == 3); + CHECK_OR_CONTINUE(val.second == "b"sv); + break; + case 2: + CHECK_OR_CONTINUE(val.first == 4); + CHECK_OR_CONTINUE(val.second == "c"sv); + break; + default: CHECK_OR_CONTINUE(false); break; + } + } + + CHECK(line.value_count() == 5); + + for (const auto index : ranges::views::iota(size_t(0), line.value_count())) { + CAPTURE(index); + switch (index) { + case 0: + case 1: break; + case 2: CHECK_OR_CONTINUE(line.get_value_for(index) == "a"sv); break; + case 3: CHECK_OR_CONTINUE(line.get_value_for(index) == "b"sv); break; + case 4: CHECK_OR_CONTINUE(line.get_value_for(index) == "c"sv); break; + default: CHECK_OR_CONTINUE(false); break; + } + } + } + + SECTION(";;a;b;c;;") { + static constexpr auto buffer = ";;a;b;c;;"sv; + parser.load_from_string(buffer); + + CHECK_PARSE(); + + const std::vector& line_list = parser.get_lines(); + CHECK_FALSE(line_list.empty()); + CHECK(ranges::size(line_list) == 1); + + const LineObject& line = line_list.front(); + CHECK_FALSE(line.empty()); + CHECK(ranges::size(line) == 3); + CHECK(line.value_count() == 6); + CHECK(line.prefix_end() == 2); + CHECK(line.suffix_end() == 6); + + for (const auto [index, val] : line | ranges::views::enumerate) { + CAPTURE(index); + CHECK_FALSE_OR_CONTINUE(val.second.empty()); + switch (index) { + case 0: + CHECK_OR_CONTINUE(val.first == 2); + CHECK_OR_CONTINUE(val.second == "a"sv); + break; + case 1: + CHECK_OR_CONTINUE(val.first == 3); + CHECK_OR_CONTINUE(val.second == "b"sv); + break; + case 2: + CHECK_OR_CONTINUE(val.first == 4); + CHECK_OR_CONTINUE(val.second == "c"sv); + break; + default: CHECK_OR_CONTINUE(false); break; + } + } + + CHECK(line.value_count() == 6); + + for (const auto index : ranges::views::iota(size_t(0), line.value_count())) { + CAPTURE(index); + switch (index) { + case 0: + case 1: + case 5: break; + case 2: CHECK_OR_CONTINUE(line.get_value_for(index) == "a"sv); break; + case 3: CHECK_OR_CONTINUE(line.get_value_for(index) == "b"sv); break; + case 4: CHECK_OR_CONTINUE(line.get_value_for(index) == "c"sv); break; + default: CHECK_OR_CONTINUE(false); break; + } + } + } + + SECTION(";;a;b;;c;;") { + static constexpr auto buffer = ";;a;b;;c;;"sv; + parser.load_from_string(buffer); + + CHECK_PARSE(); + + const std::vector& line_list = parser.get_lines(); + CHECK_FALSE(line_list.empty()); + CHECK(ranges::size(line_list) == 1); + + const LineObject& line = line_list.front(); + CHECK_FALSE(line.empty()); + CHECK(ranges::size(line) == 3); + CHECK(line.value_count() == 7); + CHECK(line.prefix_end() == 2); + CHECK(line.suffix_end() == 7); + + for (const auto [index, val] : line | ranges::views::enumerate) { + CAPTURE(index); + CHECK_FALSE_OR_CONTINUE(val.second.empty()); + switch (index) { + case 0: + CHECK_OR_CONTINUE(val.first == 2); + CHECK_OR_CONTINUE(val.second == "a"sv); + break; + case 1: + CHECK_OR_CONTINUE(val.first == 3); + CHECK_OR_CONTINUE(val.second == "b"sv); + break; + case 2: + CHECK_OR_CONTINUE(val.first == 5); + CHECK_OR_CONTINUE(val.second == "c"sv); + break; + default: CHECK_OR_CONTINUE(false); break; + } + } + + CHECK(line.value_count() == 7); + + for (const auto index : ranges::views::iota(size_t(0), line.value_count())) { + CAPTURE(index); + switch (index) { + case 0: + case 1: + case 4: + case 6: break; + case 2: CHECK_OR_CONTINUE(line.get_value_for(index) == "a"sv); break; + case 3: CHECK_OR_CONTINUE(line.get_value_for(index) == "b"sv); break; + case 5: CHECK_OR_CONTINUE(line.get_value_for(index) == "c"sv); break; + default: CHECK_OR_CONTINUE(false); break; + } + } + } + + SECTION(";;a;b;;c;;\\n;d;e;;f;g;;") { + static constexpr auto buffer = ";;a;b;;c;;\n;d;e;;f;g;;"sv; + parser.load_from_string(buffer); + + CHECK_PARSE(); + + const std::vector& line_list = parser.get_lines(); + CHECK_FALSE(line_list.empty()); + CHECK(ranges::size(line_list) == 2); + + auto it = line_list.begin(); + + const LineObject& line = *it; + CHECK_FALSE(line.empty()); + CHECK(ranges::size(line) == 3); + CHECK(line.value_count() == 7); + CHECK(line.prefix_end() == 2); + CHECK(line.suffix_end() == 7); + + for (const auto [index, val] : line | ranges::views::enumerate) { + CAPTURE(index); + CHECK_FALSE_OR_CONTINUE(val.second.empty()); + switch (index) { + case 0: + CHECK_OR_CONTINUE(val.first == 2); + CHECK_OR_CONTINUE(val.second == "a"sv); + break; + case 1: + CHECK_OR_CONTINUE(val.first == 3); + CHECK_OR_CONTINUE(val.second == "b"sv); + break; + case 2: + CHECK_OR_CONTINUE(val.first == 5); + CHECK_OR_CONTINUE(val.second == "c"sv); + break; + default: CHECK_OR_CONTINUE(false); break; + } + } + + CHECK(line.value_count() == 7); + + for (const auto index : ranges::views::iota(size_t(0), line.value_count())) { + CAPTURE(index); + switch (index) { + case 0: + case 1: + case 4: + case 6: break; + case 2: CHECK_OR_CONTINUE(line.get_value_for(index) == "a"sv); break; + case 3: CHECK_OR_CONTINUE(line.get_value_for(index) == "b"sv); break; + case 5: CHECK_OR_CONTINUE(line.get_value_for(index) == "c"sv); break; + default: CHECK_OR_CONTINUE(false); break; + } + } + + it++; + CHECK(it != line_list.end()); + + const LineObject& line2 = *it; + CHECK_FALSE(line2.empty()); + CHECK(ranges::size(line2) == 4); + CHECK(line2.value_count() == 7); + CHECK(line2.prefix_end() == 1); + CHECK(line2.suffix_end() == 7); + + for (const auto [index, val] : line2 | ranges::views::enumerate) { + CAPTURE(index); + CHECK_FALSE_OR_CONTINUE(val.second.empty()); + switch (index) { + case 0: + CHECK_OR_CONTINUE(val.first == 1); + CHECK_OR_CONTINUE(val.second == "d"sv); + break; + case 1: + CHECK_OR_CONTINUE(val.first == 2); + CHECK_OR_CONTINUE(val.second == "e"sv); + break; + case 2: + CHECK_OR_CONTINUE(val.first == 4); + CHECK_OR_CONTINUE(val.second == "f"sv); + break; + case 3: + CHECK_OR_CONTINUE(val.first == 5); + CHECK_OR_CONTINUE(val.second == "g"sv); + break; + default: CHECK_OR_CONTINUE(false); break; + } + } + + CHECK(line2.value_count() == 7); + + for (const auto index : ranges::views::iota(size_t(0), line2.value_count())) { + CAPTURE(index); + switch (index) { + case 0: + case 3: + case 6: break; + case 1: CHECK_OR_CONTINUE(line2.get_value_for(index) == "d"sv); break; + case 2: CHECK_OR_CONTINUE(line2.get_value_for(index) == "e"sv); break; + case 4: CHECK_OR_CONTINUE(line2.get_value_for(index) == "f"sv); break; + case 5: CHECK_OR_CONTINUE(line2.get_value_for(index) == "g"sv); break; + default: CHECK_OR_CONTINUE(false); break; + } + } + } +} \ No newline at end of file diff --git a/tests/src/ovscript/.gitkeep b/tests/src/ovscript/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/tests/src/v2script/AbstractSyntaxTree.cpp b/tests/src/v2script/AbstractSyntaxTree.cpp new file mode 100644 index 0000000..c06da08 --- /dev/null +++ b/tests/src/v2script/AbstractSyntaxTree.cpp @@ -0,0 +1,172 @@ +#include +#include + +#include +#include +#include + +#include +#include + +#include "Helper.hpp" +#include +#include +#include +#include + +using namespace ovdl; +using namespace ovdl::v2script::ast; +using namespace std::string_view_literals; + +struct Ast : SymbolIntern { + dryad::tree tree; + dryad::node_map map; + symbol_interner_type symbol_interner; + + template + T* create(Args&&... args) { + auto node = tree.template create(DRYAD_FWD(args)...); + return node; + } + + template + T* create_with_intern(std::string_view view, Args&&... args) { + auto intern = symbol_interner.intern(view.data(), view.size()); + auto node = tree.template create(intern, DRYAD_FWD(args)...); + return node; + } + + template + T* create_with_loc(NodeLocation loc, Args&&... args) { + auto node = tree.template create(DRYAD_FWD(args)...); + set_location(node, loc); + return node; + } + + template + T* create_with_loc_and_intern(NodeLocation loc, std::string_view view, Args&&... args) { + auto intern = symbol_interner.intern(view.data(), view.size()); + auto node = tree.template create(intern, DRYAD_FWD(args)...); + set_location(node, loc); + return node; + } + + void set_location(const Node* n, NodeLocation loc) { + map.insert(n, loc); + } + + NodeLocation location_of(const Node* n) const { + auto result = map.lookup(n); + if (result == nullptr) + return {}; + return *result; + } +}; + +TEST_CASE("V2Script Nodes", "[v2script-nodes]") { + Ast ast; + + auto* id = ast.create_with_intern("id"); + CHECK_IF(id) { + CHECK(id->kind() == NodeKind::IdentifierValue); + CHECK(id->value(ast.symbol_interner) == "id"sv); + } + + auto* str = ast.create_with_intern("str"); + CHECK_IF(str) { + CHECK(str->kind() == NodeKind::StringValue); + CHECK(str->value(ast.symbol_interner) == "str"sv); + } + + auto* list = ast.create(); + CHECK_IF(list) { + CHECK(list->kind() == NodeKind::ListValue); + } + + auto* null = ast.create(); + CHECK_IF(null) { + CHECK(null->kind() == NodeKind::NullValue); + } + + auto* event = ast.create(false, list); + CHECK_IF(event) { + CHECK(event->kind() == NodeKind::EventStatement); + CHECK(!event->is_province_event()); + CHECK(event->right() == list); + } + + auto* assign = ast.create(id, str); + CHECK_IF(assign) { + CHECK(assign->kind() == NodeKind::AssignStatement); + CHECK(assign->left() == id); + CHECK(assign->right() == str); + } + + auto* value_statement = ast.create(null); + CHECK_IF(value_statement) { + CHECK(value_statement->kind() == NodeKind::ValueStatement); + CHECK(value_statement->value() == null); + } + + if (!assign || !value_statement || !event || !null || !list || !str || !id) { + return; + } + + StatementList statement_list {}; + statement_list.push_back(assign); + statement_list.push_back(value_statement); + statement_list.push_back(event); + CHECK_FALSE(statement_list.empty()); + CHECK(ranges::distance(statement_list) == 3); + + for (const auto [statement_list_index, statement] : statement_list | ranges::views::enumerate) { + CAPTURE(statement_list_index); + CHECK_OR_CONTINUE(statement); + switch (statement_list_index) { + case 0: CHECK_OR_CONTINUE(statement == assign); break; + case 1: CHECK_OR_CONTINUE(statement == value_statement); break; + case 2: CHECK_OR_CONTINUE(statement == event); break; + default: CHECK_OR_CONTINUE(false); break; + } + } + + auto* file_tree = ast.create(statement_list); + CHECK_IF(file_tree) { + CHECK(file_tree->kind() == NodeKind::FileTree); + + const auto statements = file_tree->statements(); + CHECK_FALSE(statements.empty()); + CHECK(ranges::distance(statements) == 3); + + for (const auto [statement, list_statement] : ranges::views::zip(statements, statement_list)) { + CHECK_OR_CONTINUE(list_statement); + CHECK_OR_CONTINUE(statement); + CHECK_OR_CONTINUE(statement == list_statement); + } + } + + ast.tree.set_root(file_tree); + CHECK(ast.tree.has_root()); + CHECK(ast.tree.root() == file_tree); + + ast.tree.clear(); + CHECK_FALSE(ast.tree.has_root()); + CHECK(ast.tree.root() != file_tree); +} + +TEST_CASE("V2Script Nodes Location", "[v2script-nodes-location]") { + Ast ast; + + constexpr auto fake_buffer = "id"sv; + + auto* id = ast.create_with_loc_and_intern(NodeLocation::make_from(&fake_buffer[0], &fake_buffer[1]), "id"); + + CHECK_IF(id) { + CHECK(id->value(ast.symbol_interner) == "id"sv); + + auto location = ast.location_of(id); + CHECK_FALSE(location.is_synthesized()); + CHECK(location.begin() == &fake_buffer[0]); + CHECK(location.end() - 1 == &fake_buffer[1]); + } +} \ No newline at end of file diff --git a/tests/src/v2script/Parser.cpp b/tests/src/v2script/Parser.cpp new file mode 100644 index 0000000..5ddc49d --- /dev/null +++ b/tests/src/v2script/Parser.cpp @@ -0,0 +1,437 @@ +#include +#include +#include + +#include +#include + +#include + +#include "Helper.hpp" +#include +#include +#include +#include + +using namespace ovdl; +using namespace v2script; +using namespace std::string_view_literals; + +static constexpr auto simple_buffer = "a = b"sv; +static constexpr auto simple_path = "file.txt"sv; + +static void SetupFile(std::string_view path) { + std::ofstream stream(path.data()); + stream << simple_buffer << std::flush; +} + +#define CHECK_PARSE(...) \ + CHECK_OR_RETURN(parser.get_errors().empty()); \ + CHECK_OR_RETURN(parser.simple_parse(__VA_ARGS__)); \ + CHECK_OR_RETURN(parser.get_errors().empty()) + +TEST_CASE("V2Script Memory Buffer (data, size) Simple Parse", "[v2script-memory-simple-parse][buffer][data-size]") { + Parser parser(ovdl::detail::cnull); + + parser.load_from_buffer(simple_buffer.data(), simple_buffer.size()); + + CHECK_PARSE(); +} + +TEST_CASE("V2Script Memory Buffer (begin, end) Simple Parse", "[v2script-memory-simple-parse][buffer][begin-end]") { + Parser parser(ovdl::detail::cnull); + + parser.load_from_buffer(simple_buffer.data(), simple_buffer.data() + simple_buffer.size()); + + CHECK_PARSE(); +} + +TEST_CASE("V2Script Memory Buffer String Simple Parse", "[v2script-memory-simple-parse][buffer][string]") { + Parser parser(ovdl::detail::cnull); + + parser.load_from_string(simple_buffer); + + CHECK_PARSE(); +} + +TEST_CASE("V2Script File (const char*) Simple Parse", "[v2script-file-simple-parse][char-ptr]") { + SetupFile(simple_path); + + Parser parser(ovdl::detail::cnull); + + parser.load_from_file(simple_path.data()); + + std::filesystem::remove(simple_path); + + CHECK_PARSE(); +} + +TEST_CASE("V2Script File (filesystem::path) Simple Parse", "[v2script-file-simple-parse][filesystem-path]") { + SetupFile(simple_path); + + Parser parser(ovdl::detail::cnull); + + parser.load_from_file(std::filesystem::path(simple_path)); + + std::filesystem::remove(simple_path); + + CHECK_PARSE(); +} + +TEST_CASE("V2Script File (HasCstr) Simple Parse", "[v2script-file-simple-parse][has-cstr]") { + SetupFile(simple_path); + + Parser parser(ovdl::detail::cnull); + + parser.load_from_file(std::string { simple_path }); + + std::filesystem::remove(simple_path); + + CHECK_PARSE(); +} + +TEST_CASE("V2Script Identifier Simple Parse", "[v2script-id-simple-parse]") { + Parser parser(ovdl::detail::cnull); + + SECTION("a = b") { + static constexpr auto buffer = "a = b"sv; + parser.load_from_string(buffer); + + CHECK_PARSE(); + + const ast::FileTree* file_tree = parser.get_file_node(); + CHECK(file_tree); + + const auto statements = file_tree->statements(); + CHECK_FALSE(statements.empty()); + CHECK(ranges::distance(statements) == 1); + + const ast::Statement* statement = statements.front(); + CHECK(statement); + + const auto* assign = dryad::node_try_cast(statement); + CHECK(assign); + CHECK(assign->left()); + CHECK(assign->right()); + + const auto* left = dryad::node_try_cast(assign->left()); + CHECK_IF(left) { + CHECK(parser.value(left) == "a"sv); + } + + const auto* right = dryad::node_try_cast(assign->right()); + CHECK_IF(right) { + CHECK(parser.value(right) == "b"sv); + } + } + + SECTION("a b c d") { + static constexpr auto buffer = "a b c d"sv; + parser.load_from_string(buffer); + + CHECK_PARSE(); + + const ast::FileTree* file_tree = parser.get_file_node(); + CHECK(file_tree); + + const auto statements = file_tree->statements(); + CHECK_FALSE(statements.empty()); + CHECK(ranges::distance(statements) == 4); + + for (const auto [statement_index, statement] : statements | ranges::views::enumerate) { + CHECK_OR_CONTINUE(statement); + + const auto* value_statement = dryad::node_try_cast(statement); + CHECK_OR_CONTINUE(value_statement); + + const auto* value = dryad::node_try_cast(value_statement->value()); + CHECK_OR_CONTINUE(value); + switch (statement_index) { + case 0: CHECK_OR_CONTINUE(parser.value(value) == "a"sv); break; + case 1: CHECK_OR_CONTINUE(parser.value(value) == "b"sv); break; + case 2: CHECK_OR_CONTINUE(parser.value(value) == "c"sv); break; + case 3: CHECK_OR_CONTINUE(parser.value(value) == "d"sv); break; + default: CHECK_OR_CONTINUE(false); break; + } + } + } + + SECTION("a = { a = b }") { + static constexpr auto buffer = "a = { a = b }"sv; + parser.load_from_string(buffer); + + CHECK_PARSE(); + + const ast::FileTree* file_tree = parser.get_file_node(); + CHECK(file_tree); + + const auto statements = file_tree->statements(); + CHECK_FALSE(statements.empty()); + CHECK(ranges::distance(statements) == 1); + + const ast::Statement* statement = statements.front(); + CHECK(statement); + + const auto* assign = dryad::node_try_cast(statement); + CHECK(assign); + CHECK(assign->left()); + CHECK(assign->right()); + + const auto* left = dryad::node_try_cast(assign->left()); + CHECK_IF(left) { + CHECK(parser.value(left) == "a"sv); + } + + const auto* right = dryad::node_try_cast(assign->right()); + CHECK_IF(right) { + const auto inner_statements = right->statements(); + CHECK_FALSE(inner_statements.empty()); + CHECK(ranges::distance(inner_statements) == 1); + + const ast::Statement* inner_statement = inner_statements.front(); + CHECK(inner_statement); + + const auto* inner_assign = dryad::node_try_cast(inner_statement); + CHECK(inner_assign); + CHECK(inner_assign->left()); + CHECK(inner_assign->right()); + + const auto* inner_left = dryad::node_try_cast(inner_assign->left()); + CHECK_IF(inner_left) { + CHECK(parser.value(inner_left) == "a"sv); + } + + const auto* inner_right = dryad::node_try_cast(inner_assign->right()); + CHECK_IF(inner_right) { + CHECK(parser.value(inner_right) == "b"sv); + } + } + } + + SECTION("a = { { a } }") { + static constexpr auto buffer = "a = { { a } }"sv; + parser.load_from_string(buffer); + + CHECK_PARSE(); + + const ast::FileTree* file_tree = parser.get_file_node(); + CHECK(file_tree); + + const auto statements = file_tree->statements(); + CHECK_FALSE(statements.empty()); + CHECK(ranges::distance(statements) == 1); + + const ast::Statement* statement = statements.front(); + CHECK(statement); + + const auto* assign = dryad::node_try_cast(statement); + CHECK(assign); + CHECK(assign->left()); + CHECK(assign->right()); + + const auto* left = dryad::node_try_cast(assign->left()); + CHECK_IF(left) { + CHECK(parser.value(left) == "a"sv); + } + + const auto* right = dryad::node_try_cast(assign->right()); + CHECK_IF(right) { + const auto inner_statements = right->statements(); + CHECK_FALSE(inner_statements.empty()); + CHECK(ranges::distance(inner_statements) == 1); + + const ast::Statement* inner_statement = inner_statements.front(); + CHECK(inner_statement); + + const auto* value_statement = dryad::node_try_cast(inner_statement); + CHECK(value_statement); + + const auto* list_value = dryad::node_try_cast(value_statement->value()); + CHECK(list_value); + + const auto list_statements = list_value->statements(); + CHECK_FALSE(list_statements.empty()); + CHECK(ranges::distance(list_statements) == 1); + + const auto* inner_value_statement = dryad::node_try_cast(list_statements.front()); + CHECK(inner_value_statement); + + const auto* id_value = dryad::node_try_cast(inner_value_statement->value()); + CHECK(id_value); + CHECK(parser.value(id_value) == "a"sv); + } + } +} + +TEST_CASE("V2Script String Simple Parse", "[v2script-id-simple-parse]") { + Parser parser(ovdl::detail::cnull); + + SECTION("a = \"b\"") { + static constexpr auto buffer = "a = \"b\""sv; + parser.load_from_string(buffer); + + CHECK_PARSE(); + + const ast::FileTree* file_tree = parser.get_file_node(); + CHECK(file_tree); + + const auto statements = file_tree->statements(); + CHECK_FALSE(statements.empty()); + CHECK(ranges::distance(statements) == 1); + + const ast::Statement* statement = statements.front(); + CHECK(statement); + + const auto* assign = dryad::node_try_cast(statement); + CHECK(assign); + CHECK(assign->left()); + CHECK(assign->right()); + + const auto* left = dryad::node_try_cast(assign->left()); + CHECK_IF(left) { + CHECK(parser.value(left) == "a"sv); + } + + const auto* right = dryad::node_try_cast(assign->right()); + CHECK_IF(right) { + CHECK(parser.value(right) == "b"sv); + } + } + + SECTION("\"a\" \"b\" \"c\" \"d\"") { + static constexpr auto buffer = "\"a\" \"b\" \"c\" \"d\""sv; + parser.load_from_string(buffer); + + CHECK_PARSE(); + + const ast::FileTree* file_tree = parser.get_file_node(); + CHECK(file_tree); + + const auto statements = file_tree->statements(); + CHECK_FALSE(statements.empty()); + CHECK(ranges::distance(statements) == 4); + + for (const auto [statement_index, statement] : statements | ranges::views::enumerate) { + CHECK_OR_CONTINUE(statement); + + const auto* value_statement = dryad::node_try_cast(statement); + CHECK_OR_CONTINUE(value_statement); + + const auto* value = dryad::node_try_cast(value_statement->value()); + CHECK_OR_CONTINUE(value); + switch (statement_index) { + case 0: CHECK_OR_CONTINUE(parser.value(value) == "a"sv); break; + case 1: CHECK_OR_CONTINUE(parser.value(value) == "b"sv); break; + case 2: CHECK_OR_CONTINUE(parser.value(value) == "c"sv); break; + case 3: CHECK_OR_CONTINUE(parser.value(value) == "d"sv); break; + default: CHECK_OR_CONTINUE(false); break; + } + } + } + + SECTION("a = { a = \"b\" }") { + static constexpr auto buffer = "a = { a = \"b\" }"sv; + parser.load_from_string(buffer); + + CHECK_PARSE(); + + const ast::FileTree* file_tree = parser.get_file_node(); + CHECK(file_tree); + + const auto statements = file_tree->statements(); + CHECK_FALSE(statements.empty()); + CHECK(ranges::distance(statements) == 1); + + const ast::Statement* statement = file_tree->statements().front(); + CHECK(statement); + + const auto* assign = dryad::node_try_cast(statement); + CHECK(assign); + CHECK(assign->left()); + CHECK(assign->right()); + + const auto* left = dryad::node_try_cast(assign->left()); + CHECK_IF(left) { + CHECK(parser.value(left) == "a"sv); + } + + const auto* right = dryad::node_try_cast(assign->right()); + CHECK_IF(right) { + const auto inner_statements = right->statements(); + CHECK_FALSE(inner_statements.empty()); + CHECK(ranges::distance(inner_statements) == 1); + + const ast::Statement* inner_statement = inner_statements.front(); + CHECK(inner_statement); + + const auto* inner_assign = dryad::node_try_cast(inner_statement); + CHECK(inner_assign); + CHECK(inner_assign->left()); + CHECK(inner_assign->right()); + + const auto* inner_left = dryad::node_try_cast(inner_assign->left()); + CHECK_IF(inner_left) { + CHECK(parser.value(inner_left) == "a"sv); + } + + const auto* inner_right = dryad::node_try_cast(inner_assign->right()); + CHECK_IF(inner_right) { + CHECK(parser.value(inner_right) == "b"sv); + } + } + } + + SECTION("a = { { \"a\" } }") { + static constexpr auto buffer = "a = { { \"a\" } }"sv; + parser.load_from_string(buffer); + + CHECK_PARSE(); + + const ast::FileTree* file_tree = parser.get_file_node(); + CHECK(file_tree); + + const auto statements = file_tree->statements(); + CHECK_FALSE(statements.empty()); + CHECK(ranges::distance(statements) == 1); + + const ast::Statement* statement = statements.front(); + CHECK(statement); + + const auto* assign = dryad::node_try_cast(statement); + CHECK(assign); + CHECK(assign->left()); + CHECK(assign->right()); + + const auto* left = dryad::node_try_cast(assign->left()); + CHECK_IF(left) { + CHECK(parser.value(left) == "a"sv); + } + + const auto* right = dryad::node_try_cast(assign->right()); + CHECK_IF(right) { + const auto inner_statements = right->statements(); + CHECK_FALSE(inner_statements.empty()); + CHECK(ranges::distance(inner_statements) == 1); + + const ast::Statement* inner_statement = inner_statements.front(); + CHECK(inner_statement); + + const auto* value_statement = dryad::node_try_cast(inner_statement); + CHECK(value_statement); + + const auto* list_value = dryad::node_try_cast(value_statement->value()); + CHECK(list_value); + + const auto list_statements = list_value->statements(); + CHECK_FALSE(list_statements.empty()); + CHECK(ranges::distance(list_statements) == 1); + + const auto* inner_value_statement = dryad::node_try_cast(list_statements.front()); + CHECK_OR_RETURN(inner_value_statement); + + const auto* str_value = dryad::node_try_cast(inner_value_statement->value()); + CHECK_OR_RETURN(str_value); + CHECK(parser.value(str_value) == "a"sv); + } + } +} \ No newline at end of file -- cgit v1.2.3-56-ga3b1