aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
author George L. Albany <Megacake1234@gmail.com>2024-06-22 21:58:35 +0200
committer GitHub <noreply@github.com>2024-06-22 21:58:35 +0200
commitdeed8ec0ae23651529a58125012c1b4aab015d02 (patch)
tree51ca6d5948e92be37b9ee6674cb96801d2cd03f8
parent8b623bf4087aa360842ad31145d4ab6946cee9aa (diff)
parent1a694a8b26a441b12547057d6e0be61a111cced3 (diff)
Merge pull request #49 from OpenVicProject/add/unit-testing
Add unit testing
-rw-r--r--.github/workflows/builds.yml2
-rw-r--r--.gitignore2
-rw-r--r--.gitmodules6
-rw-r--r--SConstruct9
-rw-r--r--include/openvic-dataloader/Error.hpp7
-rw-r--r--include/openvic-dataloader/NodeLocation.hpp10
-rw-r--r--include/openvic-dataloader/csv/LineObject.hpp50
-rw-r--r--include/openvic-dataloader/csv/Parser.hpp6
-rw-r--r--include/openvic-dataloader/v2script/AbstractSyntaxTree.hpp7
-rw-r--r--include/openvic-dataloader/v2script/Parser.hpp6
-rw-r--r--src/openvic-dataloader/csv/Parser.cpp20
-rw-r--r--src/openvic-dataloader/v2script/AbstractSyntaxTree.cpp10
-rw-r--r--src/openvic-dataloader/v2script/Parser.cpp9
-rw-r--r--tests/SCsub55
-rw-r--r--tests/deps/SCsub201
m---------tests/deps/snitch0
-rw-r--r--tests/src/Error.cpp230
-rw-r--r--tests/src/Helper.hpp60
-rw-r--r--tests/src/NodeLocation.cpp300
-rw-r--r--tests/src/csv/LineObject.cpp196
-rw-r--r--tests/src/csv/Parser.cpp571
-rw-r--r--tests/src/ovscript/.gitkeep0
-rw-r--r--tests/src/v2script/AbstractSyntaxTree.cpp172
-rw-r--r--tests/src/v2script/Parser.cpp437
24 files changed, 2336 insertions, 30 deletions
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<AnnotatedError, ErrorKind::FirstParseError, ErrorKind::LastParseError> {
@@ -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<LineObject>& vector;
+ std::string_view separator;
+ };
+
+ constexpr VectorSepTransformer use_sep(const std::vector<LineObject>& 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<detail::Encoding> encoding_fallback = std::nullopt);
static Parser from_file(const std::filesystem::path& path, std::optional<detail::Encoding> encoding_fallback = std::nullopt);
- constexpr Parser& load_from_buffer(const char* data, std::size_t size, std::optional<detail::Encoding> encoding_fallback = std::nullopt);
- constexpr Parser& load_from_buffer(const char* start, const char* end, std::optional<detail::Encoding> encoding_fallback = std::nullopt);
- constexpr Parser& load_from_string(const std::string_view string, std::optional<detail::Encoding> encoding_fallback = std::nullopt);
+ Parser& load_from_buffer(const char* data, std::size_t size, std::optional<detail::Encoding> encoding_fallback = std::nullopt);
+ Parser& load_from_buffer(const char* start, const char* end, std::optional<detail::Encoding> encoding_fallback = std::nullopt);
+ Parser& load_from_string(const std::string_view string, std::optional<detail::Encoding> encoding_fallback = std::nullopt);
Parser& load_from_file(const char* path, std::optional<detail::Encoding> encoding_fallback = std::nullopt);
Parser& load_from_file(const std::filesystem::path& path, std::optional<detail::Encoding> 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<NodeKind::ListValue, dryad::container_node<Value>> {
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<NodeKind::FileTree, dryad::container_node<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<detail::Encoding> encoding_fallback = std::nullopt);
static Parser from_file(const std::filesystem::path& path, std::optional<detail::Encoding> encoding_fallback = std::nullopt);
- constexpr Parser& load_from_buffer(const char* data, std::size_t size, std::optional<detail::Encoding> encoding_fallback = std::nullopt);
- constexpr Parser& load_from_buffer(const char* start, const char* end, std::optional<detail::Encoding> encoding_fallback = std::nullopt);
- constexpr Parser& load_from_string(const std::string_view string, std::optional<detail::Encoding> encoding_fallback = std::nullopt);
+ Parser& load_from_buffer(const char* data, std::size_t size, std::optional<detail::Encoding> encoding_fallback = std::nullopt);
+ Parser& load_from_buffer(const char* start, const char* end, std::optional<detail::Encoding> encoding_fallback = std::nullopt);
+ Parser& load_from_string(const std::string_view string, std::optional<detail::Encoding> encoding_fallback = std::nullopt);
Parser& load_from_file(const char* path, std::optional<detail::Encoding> encoding_fallback = std::nullopt);
Parser& load_from_file(const std::filesystem::path& path, std::optional<detail::Encoding> 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 <type_traits>
#include <vector>
+#include <openvic-dataloader/Error.hpp>
#include <openvic-dataloader/NodeLocation.hpp>
#include <openvic-dataloader/csv/LineObject.hpp>
#include <openvic-dataloader/csv/Parser.hpp>
@@ -15,6 +16,8 @@
#include <lexy/input/buffer.hpp>
#include <lexy/input/file.hpp>
+#include <dryad/node.hpp>
+
#include "CsvGrammar.hpp"
#include "CsvParseState.hpp"
#include "detail/NullBuff.hpp"
@@ -53,6 +56,13 @@ struct Parser::ParseHandler final : detail::BasicFileParseHandler<CsvParseState>
return _lines;
}
+ Parser::error_range get_errors() {
+ using iterator = typename decltype(std::declval<const error::Root*>()->children())::iterator;
+ if (!is_valid())
+ return dryad::make_node_range<error::Error>(iterator::from_ptr(nullptr), iterator::from_ptr(nullptr));
+ return parse_state().logger().get_errors();
+ }
+
private:
std::vector<csv::LineObject> _lines;
};
@@ -126,19 +136,19 @@ constexpr void Parser::_run_load_func(detail::LoadCallback<ParseHandler, Args...
}
}
-constexpr Parser& Parser::load_from_buffer(const char* data, std::size_t size, std::optional<detail::Encoding> encoding_fallback) {
+Parser& Parser::load_from_buffer(const char* data, std::size_t size, std::optional<detail::Encoding> 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<detail::Encoding> encoding_fallback) {
+Parser& Parser::load_from_buffer(const char* start, const char* end, std::optional<detail::Encoding> 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<detail::Encoding> encoding_fallback) {
+Parser& Parser::load_from_string(const std::string_view string, std::optional<detail::Encoding> 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<csv::LineObject>& 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 <openvic-dataloader/v2script/AbstractSyntaxTree.hpp>
#include <lexy/dsl/option.hpp>
#include <lexy/encoding.hpp>
@@ -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<v2script::ast
}
Parser::error_range get_errors() {
+ using iterator = typename decltype(std::declval<const error::Root*>()->children())::iterator;
+ if (!is_valid())
+ return dryad::make_node_range<error::Error>(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<Parser::ParseHandler*
}
}
-constexpr Parser& Parser::load_from_buffer(const char* data, std::size_t size, std::optional<detail::Encoding> encoding_fallback) {
+Parser& Parser::load_from_buffer(const char* data, std::size_t size, std::optional<detail::Encoding> 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<detail::Encoding> encoding_fallback) {
+Parser& Parser::load_from_buffer(const char* start, const char* end, std::optional<detail::Encoding> 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<detail::Encoding> encoding_fallback) {
+Parser& Parser::load_from_string(const std::string_view string, std::optional<detail::Encoding> 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 <version> // 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
+Subproject d6632123cc8d13bdbc5cd60fd6741b9e0f635e8
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 <openvic-dataloader/Error.hpp>
+#include <openvic-dataloader/NodeLocation.hpp>
+#include <openvic-dataloader/detail/ErrorRange.hpp>
+#include <openvic-dataloader/detail/SymbolIntern.hpp>
+
+#include <dryad/node_map.hpp>
+#include <dryad/tree.hpp>
+
+#include "Helper.hpp"
+#include <range/v3/view/enumerate.hpp>
+#include <snitch/snitch.hpp>
+
+using namespace ovdl;
+using namespace ovdl::error;
+using namespace std::string_view_literals;
+
+struct ErrorTree : SymbolIntern {
+ using error_range = detail::error_range<error::Root>;
+
+ dryad::node_map<const error::Error, NodeLocation> map;
+ dryad::tree<error::Root> tree;
+ symbol_interner_type symbol_interner;
+
+ NodeLocation location_of(const error::Error* error) const {
+ auto result = map.lookup(error);
+ return result ? *result : NodeLocation {};
+ }
+
+ template<typename T, typename LocCharT, typename... Args>
+ T* create(BasicNodeLocation<LocCharT> loc, Args&&... args) {
+ using node_creator = dryad::node_creator<decltype(DRYAD_DECLVAL(T).kind()), void>;
+ T* result = tree.create<T>(DRYAD_FWD(args)...);
+ map.insert(result, loc);
+ return result;
+ }
+
+ template<typename T, typename... Args>
+ T* create(Args&&... args) {
+ using node_creator = dryad::node_creator<decltype(DRYAD_DECLVAL(T).kind()), void>;
+ T* result = tree.create<T>(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<BufferError>("error");
+ CHECK_IF(buffer_error) {
+ CHECK(buffer_error->kind() == ErrorKind::BufferError);
+ CHECK(buffer_error->message() == "error"sv);
+ }
+
+ auto* expect_lit = errors.create<ExpectedLiteral>("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<ExpectedKeyword>("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<ExpectedCharClass>("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<GenericParseError>("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<SemanticError>("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<SemanticWarning>("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<SemanticInfo>("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<SemanticDebug>("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<SemanticFixit>("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<SemanticHelp>("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<PrimaryAnnotation>("primary annotation");
+ CHECK_IF(prim_annotation) {
+ CHECK(prim_annotation->kind() == ErrorKind::PrimaryAnnotation);
+ CHECK(prim_annotation->message() == "primary annotation"sv);
+ }
+
+ auto* sec_annotation = errors.create<SecondaryAnnotation>("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<SemanticError>("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<Root>();
+ 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<ExpectedLiteral>(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 <snitch/snitch_macros_utility.hpp>
+
+#define _EXPR(TYPE, EXPECTED, ASSIGN_VALUE, ...) \
+ auto SNITCH_CURRENT_EXPRESSION = \
+ (snitch::impl::expression_extractor<EXPECTED> { 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<bool>(__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 <string_view>
+
+#include <openvic-dataloader/NodeLocation.hpp>
+
+#include <snitch/snitch.hpp>
+
+using namespace ovdl;
+using namespace std::string_view_literals;
+
+TEST_CASE("NodeLocation", "[node-location]") {
+ BasicNodeLocation<char> char_node_location;
+ CHECK(char_node_location.begin() == nullptr);
+ CHECK(char_node_location.end() == nullptr);
+ CHECK(char_node_location.is_synthesized());
+
+ BasicNodeLocation<unsigned char> uchar_node_location;
+ CHECK(uchar_node_location.begin() == nullptr);
+ CHECK(uchar_node_location.end() == nullptr);
+ CHECK(uchar_node_location.is_synthesized());
+
+ BasicNodeLocation<char16_t> 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<char32_t> 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<char8_t> 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<unsigned char> 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> 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<unsigned char> 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<char16_t> 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<char32_t> 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<char8_t> 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> 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<unsigned char> 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<char16_t> 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<char32_t> 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<char8_t> 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> 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<unsigned char> 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<char16_t> 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<char32_t> 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<char8_t> 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<const char*>(&uchar_buffer[0]));
+ CHECK(char_node_from.end() == reinterpret_cast<const char*>(&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<const unsigned char*>(&char_buffer[0]));
+ CHECK(uchar_node_from.end() == reinterpret_cast<const unsigned char*>(&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<const char16_t*>(&char_buffer[0]));
+ CHECK(char_16_node_from.end() == reinterpret_cast<const char16_t*>(&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<const char32_t*>(&char_buffer[0]));
+ CHECK(char_32_node_from.end() == reinterpret_cast<const char32_t*>(&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<const char8_t*>(&char_buffer[0]));
+ CHECK(char_8_node_from.end() == reinterpret_cast<const char8_t*>(&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<const char*>(&char_16_buffer[0]));
+ CHECK(char_node_from.end() == reinterpret_cast<const char*>(&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<const char*>(&char_32_buffer[0]));
+ CHECK(char_node_from.end() == reinterpret_cast<const char*>(&char_32_buffer.back()) + 3);
+ CHECK_FALSE(char_node_from.is_synthesized());
+
+ auto char_node_make = BasicNodeLocation<char>::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<unsigned char>::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<char16_t>::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<char32_t>::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<char8_t>::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<char>::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<unsigned char>::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<char16_t>::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<char32_t>::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<char8_t>::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 <sstream>
+#include <string_view>
+
+#include <openvic-dataloader/csv/LineObject.hpp>
+
+#include <range/v3/range/primitives.hpp>
+#include <snitch/snitch.hpp>
+
+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 <filesystem>
+#include <fstream>
+#include <string_view>
+
+#include <openvic-dataloader/csv/LineObject.hpp>
+#include <openvic-dataloader/csv/Parser.hpp>
+
+#include "Helper.hpp"
+#include <detail/NullBuff.hpp>
+#include <range/v3/range/primitives.hpp>
+#include <range/v3/view/enumerate.hpp>
+#include <range/v3/view/iota.hpp>
+#include <snitch/snitch.hpp>
+
+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<LineObject>& 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<LineObject>& 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<LineObject>& 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<LineObject>& 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<LineObject>& 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<LineObject>& 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<LineObject>& 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
--- /dev/null
+++ b/tests/src/ovscript/.gitkeep
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 <string_view>
+#include <type_traits>
+
+#include <openvic-dataloader/NodeLocation.hpp>
+#include <openvic-dataloader/detail/SymbolIntern.hpp>
+#include <openvic-dataloader/v2script/AbstractSyntaxTree.hpp>
+
+#include <dryad/node_map.hpp>
+#include <dryad/tree.hpp>
+
+#include "Helper.hpp"
+#include <range/v3/iterator/operations.hpp>
+#include <range/v3/view/enumerate.hpp>
+#include <range/v3/view/zip.hpp>
+#include <snitch/snitch.hpp>
+
+using namespace ovdl;
+using namespace ovdl::v2script::ast;
+using namespace std::string_view_literals;
+
+struct Ast : SymbolIntern {
+ dryad::tree<FileTree> tree;
+ dryad::node_map<const Node, NodeLocation> map;
+ symbol_interner_type symbol_interner;
+
+ template<typename T, typename... Args>
+ T* create(Args&&... args) {
+ auto node = tree.template create<T>(DRYAD_FWD(args)...);
+ return node;
+ }
+
+ template<typename T, typename... Args>
+ T* create_with_intern(std::string_view view, Args&&... args) {
+ auto intern = symbol_interner.intern(view.data(), view.size());
+ auto node = tree.template create<T>(intern, DRYAD_FWD(args)...);
+ return node;
+ }
+
+ template<typename T, typename... Args>
+ T* create_with_loc(NodeLocation loc, Args&&... args) {
+ auto node = tree.template create<T>(DRYAD_FWD(args)...);
+ set_location(node, loc);
+ return node;
+ }
+
+ template<typename T, typename... Args>
+ 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<T>(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<IdentifierValue>("id");
+ CHECK_IF(id) {
+ CHECK(id->kind() == NodeKind::IdentifierValue);
+ CHECK(id->value(ast.symbol_interner) == "id"sv);
+ }
+
+ auto* str = ast.create_with_intern<StringValue>("str");
+ CHECK_IF(str) {
+ CHECK(str->kind() == NodeKind::StringValue);
+ CHECK(str->value(ast.symbol_interner) == "str"sv);
+ }
+
+ auto* list = ast.create<ListValue>();
+ CHECK_IF(list) {
+ CHECK(list->kind() == NodeKind::ListValue);
+ }
+
+ auto* null = ast.create<NullValue>();
+ CHECK_IF(null) {
+ CHECK(null->kind() == NodeKind::NullValue);
+ }
+
+ auto* event = ast.create<EventStatement>(false, list);
+ CHECK_IF(event) {
+ CHECK(event->kind() == NodeKind::EventStatement);
+ CHECK(!event->is_province_event());
+ CHECK(event->right() == list);
+ }
+
+ auto* assign = ast.create<AssignStatement>(id, str);
+ CHECK_IF(assign) {
+ CHECK(assign->kind() == NodeKind::AssignStatement);
+ CHECK(assign->left() == id);
+ CHECK(assign->right() == str);
+ }
+
+ auto* value_statement = ast.create<ValueStatement>(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<FileTree>(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<IdentifierValue>(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 <filesystem>
+#include <fstream>
+#include <string_view>
+
+#include <openvic-dataloader/v2script/AbstractSyntaxTree.hpp>
+#include <openvic-dataloader/v2script/Parser.hpp>
+
+#include <dryad/node.hpp>
+
+#include "Helper.hpp"
+#include <detail/NullBuff.hpp>
+#include <range/v3/iterator/operations.hpp>
+#include <range/v3/view/enumerate.hpp>
+#include <snitch/snitch.hpp>
+
+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<ast::AssignStatement>(statement);
+ CHECK(assign);
+ CHECK(assign->left());
+ CHECK(assign->right());
+
+ const auto* left = dryad::node_try_cast<ast::IdentifierValue>(assign->left());
+ CHECK_IF(left) {
+ CHECK(parser.value(left) == "a"sv);
+ }
+
+ const auto* right = dryad::node_try_cast<ast::IdentifierValue>(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<ast::ValueStatement>(statement);
+ CHECK_OR_CONTINUE(value_statement);
+
+ const auto* value = dryad::node_try_cast<ast::IdentifierValue>(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<ast::AssignStatement>(statement);
+ CHECK(assign);
+ CHECK(assign->left());
+ CHECK(assign->right());
+
+ const auto* left = dryad::node_try_cast<ast::IdentifierValue>(assign->left());
+ CHECK_IF(left) {
+ CHECK(parser.value(left) == "a"sv);
+ }
+
+ const auto* right = dryad::node_try_cast<ast::ListValue>(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<ast::AssignStatement>(inner_statement);
+ CHECK(inner_assign);
+ CHECK(inner_assign->left());
+ CHECK(inner_assign->right());
+
+ const auto* inner_left = dryad::node_try_cast<ast::IdentifierValue>(inner_assign->left());
+ CHECK_IF(inner_left) {
+ CHECK(parser.value(inner_left) == "a"sv);
+ }
+
+ const auto* inner_right = dryad::node_try_cast<ast::IdentifierValue>(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<ast::AssignStatement>(statement);
+ CHECK(assign);
+ CHECK(assign->left());
+ CHECK(assign->right());
+
+ const auto* left = dryad::node_try_cast<ast::IdentifierValue>(assign->left());
+ CHECK_IF(left) {
+ CHECK(parser.value(left) == "a"sv);
+ }
+
+ const auto* right = dryad::node_try_cast<ast::ListValue>(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<ast::ValueStatement>(inner_statement);
+ CHECK(value_statement);
+
+ const auto* list_value = dryad::node_try_cast<ast::ListValue>(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<ast::ValueStatement>(list_statements.front());
+ CHECK(inner_value_statement);
+
+ const auto* id_value = dryad::node_try_cast<ast::IdentifierValue>(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<ast::AssignStatement>(statement);
+ CHECK(assign);
+ CHECK(assign->left());
+ CHECK(assign->right());
+
+ const auto* left = dryad::node_try_cast<ast::IdentifierValue>(assign->left());
+ CHECK_IF(left) {
+ CHECK(parser.value(left) == "a"sv);
+ }
+
+ const auto* right = dryad::node_try_cast<ast::StringValue>(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<ast::ValueStatement>(statement);
+ CHECK_OR_CONTINUE(value_statement);
+
+ const auto* value = dryad::node_try_cast<ast::StringValue>(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<ast::AssignStatement>(statement);
+ CHECK(assign);
+ CHECK(assign->left());
+ CHECK(assign->right());
+
+ const auto* left = dryad::node_try_cast<ast::IdentifierValue>(assign->left());
+ CHECK_IF(left) {
+ CHECK(parser.value(left) == "a"sv);
+ }
+
+ const auto* right = dryad::node_try_cast<ast::ListValue>(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<ast::AssignStatement>(inner_statement);
+ CHECK(inner_assign);
+ CHECK(inner_assign->left());
+ CHECK(inner_assign->right());
+
+ const auto* inner_left = dryad::node_try_cast<ast::IdentifierValue>(inner_assign->left());
+ CHECK_IF(inner_left) {
+ CHECK(parser.value(inner_left) == "a"sv);
+ }
+
+ const auto* inner_right = dryad::node_try_cast<ast::StringValue>(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<ast::AssignStatement>(statement);
+ CHECK(assign);
+ CHECK(assign->left());
+ CHECK(assign->right());
+
+ const auto* left = dryad::node_try_cast<ast::IdentifierValue>(assign->left());
+ CHECK_IF(left) {
+ CHECK(parser.value(left) == "a"sv);
+ }
+
+ const auto* right = dryad::node_try_cast<ast::ListValue>(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<ast::ValueStatement>(inner_statement);
+ CHECK(value_statement);
+
+ const auto* list_value = dryad::node_try_cast<ast::ListValue>(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<ast::ValueStatement>(list_statements.front());
+ CHECK_OR_RETURN(inner_value_statement);
+
+ const auto* str_value = dryad::node_try_cast<ast::StringValue>(inner_value_statement->value());
+ CHECK_OR_RETURN(str_value);
+ CHECK(parser.value(str_value) == "a"sv);
+ }
+ }
+} \ No newline at end of file