From 27141371d9c12368484b0e70e425ec4b567d1b95 Mon Sep 17 00:00:00 2001 From: Spartan322 Date: Tue, 15 Sep 2026 07:04:14 -0400 Subject: [PATCH] Add dataloader::dsl::Traverse for AST processing Add generalized Traverse rules for AST processing Add dataloader analysis diagnostics for AST processing Add DiagnosticItem to represent a diagnostic Add DiagnosticLevel Add DiagnosticRenderer for abstracted rendering of DiagnosticItems Add DiagnosticBag to store DiagnosticItems with Parser pointer Add diagnostic sinks for runtime abstracted diagnostic rendering: - TextSink: generic textual sink - StringSink: string writing sink - FileSink: FILE descriptor sink Add SIZE template parameter to `memory::fmt::basic_memory_buffer` --- src/openvic-simulation/core/Typedefs.hpp | 4 + .../core/memory/Formatting.hpp | 12 +- .../dataloader/diagnostic/DiagnosticBag.cpp | 213 ++++++ .../dataloader/diagnostic/DiagnosticBag.hpp | 92 +++ .../dataloader/diagnostic/DiagnosticCode.cpp | 20 + .../dataloader/diagnostic/DiagnosticCode.hpp | 58 ++ .../dataloader/diagnostic/DiagnosticItem.cpp | 115 ++++ .../dataloader/diagnostic/DiagnosticItem.hpp | 83 +++ .../dataloader/diagnostic/DiagnosticLevel.hpp | 15 + .../diagnostic/DiagnosticRenderer.cpp | 43 ++ .../diagnostic/DiagnosticRenderer.hpp | 68 ++ .../dataloader/diagnostic/DiagnosticSink.hpp | 17 + .../dataloader/diagnostic/FileSink.cpp | 15 + .../dataloader/diagnostic/FileSink.hpp | 23 + .../dataloader/diagnostic/MutexSink.hpp | 59 ++ .../dataloader/diagnostic/StringSink.cpp | 17 + .../dataloader/diagnostic/StringSink.hpp | 18 + .../dataloader/diagnostic/TextSink.cpp | 632 ++++++++++++++++++ .../dataloader/diagnostic/TextSink.hpp | 131 ++++ .../definition/dataloader/dsl/As.hpp | 79 +++ .../definition/dataloader/dsl/Assignment.hpp | 205 ++++++ .../definition/dataloader/dsl/Diagnostics.hpp | 180 +++++ .../definition/dataloader/dsl/Grammar.hpp | 242 +++++++ .../definition/dataloader/dsl/Key.hpp | 143 ++++ .../definition/dataloader/dsl/List.hpp | 250 +++++++ .../definition/dataloader/dsl/ListOptions.hpp | 115 ++++ .../definition/dataloader/dsl/ListSink.hpp | 178 +++++ .../definition/dataloader/dsl/Process.hpp | 22 + .../definition/dataloader/dsl/Ref.hpp | 196 ++++++ .../definition/dataloader/dsl/Sink.hpp | 33 + .../definition/dataloader/dsl/Symbol.hpp | 218 ++++++ .../definition/dataloader/dsl/Traverse.hpp | 109 +++ .../dataloader/dsl/TraverseContext.hpp | 26 + .../definition/dataloader/dsl/ValueParser.hpp | 199 ++++++ .../dataloader/dsl/detail/TupleCatUnique.hpp | 138 ++++ .../dataloader/diagnostic/DiagnosticBag.cpp | 1 + .../dataloader/diagnostic/DiagnosticItem.cpp | 1 + .../diagnostic/DiagnosticRenderer.cpp | 1 + .../definition/dataloader/dsl/Traverse.cpp | 1 + 39 files changed, 3963 insertions(+), 9 deletions(-) create mode 100644 src/openvic-simulation/definition/dataloader/diagnostic/DiagnosticBag.cpp create mode 100644 src/openvic-simulation/definition/dataloader/diagnostic/DiagnosticBag.hpp create mode 100644 src/openvic-simulation/definition/dataloader/diagnostic/DiagnosticCode.cpp create mode 100644 src/openvic-simulation/definition/dataloader/diagnostic/DiagnosticCode.hpp create mode 100644 src/openvic-simulation/definition/dataloader/diagnostic/DiagnosticItem.cpp create mode 100644 src/openvic-simulation/definition/dataloader/diagnostic/DiagnosticItem.hpp create mode 100644 src/openvic-simulation/definition/dataloader/diagnostic/DiagnosticLevel.hpp create mode 100644 src/openvic-simulation/definition/dataloader/diagnostic/DiagnosticRenderer.cpp create mode 100644 src/openvic-simulation/definition/dataloader/diagnostic/DiagnosticRenderer.hpp create mode 100644 src/openvic-simulation/definition/dataloader/diagnostic/DiagnosticSink.hpp create mode 100644 src/openvic-simulation/definition/dataloader/diagnostic/FileSink.cpp create mode 100644 src/openvic-simulation/definition/dataloader/diagnostic/FileSink.hpp create mode 100644 src/openvic-simulation/definition/dataloader/diagnostic/MutexSink.hpp create mode 100644 src/openvic-simulation/definition/dataloader/diagnostic/StringSink.cpp create mode 100644 src/openvic-simulation/definition/dataloader/diagnostic/StringSink.hpp create mode 100644 src/openvic-simulation/definition/dataloader/diagnostic/TextSink.cpp create mode 100644 src/openvic-simulation/definition/dataloader/diagnostic/TextSink.hpp create mode 100644 src/openvic-simulation/definition/dataloader/dsl/As.hpp create mode 100644 src/openvic-simulation/definition/dataloader/dsl/Assignment.hpp create mode 100644 src/openvic-simulation/definition/dataloader/dsl/Diagnostics.hpp create mode 100644 src/openvic-simulation/definition/dataloader/dsl/Grammar.hpp create mode 100644 src/openvic-simulation/definition/dataloader/dsl/Key.hpp create mode 100644 src/openvic-simulation/definition/dataloader/dsl/List.hpp create mode 100644 src/openvic-simulation/definition/dataloader/dsl/ListOptions.hpp create mode 100644 src/openvic-simulation/definition/dataloader/dsl/ListSink.hpp create mode 100644 src/openvic-simulation/definition/dataloader/dsl/Process.hpp create mode 100644 src/openvic-simulation/definition/dataloader/dsl/Ref.hpp create mode 100644 src/openvic-simulation/definition/dataloader/dsl/Sink.hpp create mode 100644 src/openvic-simulation/definition/dataloader/dsl/Symbol.hpp create mode 100644 src/openvic-simulation/definition/dataloader/dsl/Traverse.hpp create mode 100644 src/openvic-simulation/definition/dataloader/dsl/TraverseContext.hpp create mode 100644 src/openvic-simulation/definition/dataloader/dsl/ValueParser.hpp create mode 100644 src/openvic-simulation/definition/dataloader/dsl/detail/TupleCatUnique.hpp create mode 100644 tests/src/definition/dataloader/diagnostic/DiagnosticBag.cpp create mode 100644 tests/src/definition/dataloader/diagnostic/DiagnosticItem.cpp create mode 100644 tests/src/definition/dataloader/diagnostic/DiagnosticRenderer.cpp create mode 100644 tests/src/definition/dataloader/dsl/Traverse.cpp diff --git a/src/openvic-simulation/core/Typedefs.hpp b/src/openvic-simulation/core/Typedefs.hpp index b4c0cfa8e..7bea125f3 100644 --- a/src/openvic-simulation/core/Typedefs.hpp +++ b/src/openvic-simulation/core/Typedefs.hpp @@ -2,6 +2,7 @@ #include #include +#include // IWYU pragma: keep #include #if defined(__GNUC__) @@ -61,6 +62,9 @@ #endif #endif // OV_LIFETIME_BOUND +#define OV_MOV(...) static_cast&&>(__VA_ARGS__) +#define OV_FWD(...) static_cast(__VA_ARGS__) + namespace OpenVic { template constexpr auto substring_as_array(std::string_view str, std::index_sequence) { diff --git a/src/openvic-simulation/core/memory/Formatting.hpp b/src/openvic-simulation/core/memory/Formatting.hpp index 2e229ee18..cd95aab24 100644 --- a/src/openvic-simulation/core/memory/Formatting.hpp +++ b/src/openvic-simulation/core/memory/Formatting.hpp @@ -12,15 +12,9 @@ namespace OpenVic::memory { namespace fmt { - template - using basic_memory_buffer = ::fmt::basic_memory_buffer< - T, - ::fmt::inline_buffer_size, - foonathan::memory::std_allocator< - T, - OpenVic::memory::tracker - > - >; + template + using basic_memory_buffer = + ::fmt::basic_memory_buffer>>; inline static memory::string vformat(::fmt::string_view fmt, ::fmt::format_args args) { memory::fmt::basic_memory_buffer buf {}; diff --git a/src/openvic-simulation/definition/dataloader/diagnostic/DiagnosticBag.cpp b/src/openvic-simulation/definition/dataloader/diagnostic/DiagnosticBag.cpp new file mode 100644 index 000000000..d58f09c71 --- /dev/null +++ b/src/openvic-simulation/definition/dataloader/diagnostic/DiagnosticBag.cpp @@ -0,0 +1,213 @@ +#include "DiagnosticBag.hpp" + +#include + +#include + +#include +#include + +#include "openvic-simulation/core/Typedefs.hpp" +#include "openvic-simulation/core/memory/Formatting.hpp" +#include "openvic-simulation/core/memory/Vector.hpp" +#include "openvic-simulation/definition/dataloader/diagnostic/DiagnosticItem.hpp" +#include "openvic-simulation/definition/dataloader/diagnostic/DiagnosticLevel.hpp" + +using namespace OpenVic::dataloader; + +DiagnosticBag::DiagnosticBag(ovdl::v2script::Parser const* parser) : _parser(parser) {} + +DiagnosticItem& DiagnosticBag::fatal(ovdl::v2script::ast::Node const* node) { + return report(DiagnosticLevel::FATAL, node); +} + +DiagnosticItem& DiagnosticBag::error(ovdl::v2script::ast::Node const* node) { + return report(DiagnosticLevel::ERROR, node); +} + +DiagnosticItem& DiagnosticBag::warning(ovdl::v2script::ast::Node const* node) { + return report(DiagnosticLevel::WARN, node); +} + +DiagnosticItem& DiagnosticBag::info(ovdl::v2script::ast::Node const* node) { + return report(DiagnosticLevel::INFO, node); +} + +DiagnosticItem& DiagnosticBag::hint(ovdl::v2script::ast::Node const* node) { + return report(DiagnosticLevel::HINT, node); +} + +DiagnosticItem& DiagnosticBag::suggestion(ovdl::v2script::ast::Node const* node) { + return report(DiagnosticLevel::SUGGEST, node); +} + +DiagnosticItem& DiagnosticBag::report(DiagnosticLevel level, ovdl::v2script::ast::Node const* node) { + return report({ *this, node }).with_level(level); +} + +DiagnosticItem& DiagnosticBag::report(DiagnosticItem&& item) { + DiagnosticItem& report = *_diagnostics.emplace(std::move(item)); + report._bag = this; + switch (report.level()) { + case DiagnosticLevel::FATAL: _fatal_error = true; [[fallthrough]]; + case DiagnosticLevel::ERROR: _error_count++; break; + case DiagnosticLevel::WARN: _warning_count++; break; + default: break; + } + return report; +} + +void DiagnosticBag::clear() { + _diagnostics.clear(); +} + +void DiagnosticBag::sort() { + if (parser() == nullptr) { + _diagnostics.sort([](DiagnosticItem const& a, DiagnosticItem const& b) -> bool { + if (a.is_primary() != b.is_primary()) { + return a.is_primary(); + } + + if (a.level() != b.level() && a.level() > DiagnosticLevel::WARN && b.level() > DiagnosticLevel::WARN) { + return a.level() > b.level(); + } + + return true; + }); + } + + _diagnostics.sort([](DiagnosticItem const& a, DiagnosticItem const& b) -> bool { + if (a.is_primary() != b.is_primary()) { + return a.is_primary(); + } + + if (a.level() != b.level() && a.level() > DiagnosticLevel::WARN && b.level() > DiagnosticLevel::WARN) { + return a.level() > b.level(); + } + + if (a.location().start_line != b.location().start_line) { + return a.location().start_line < b.location().start_line; + } + + return a.location().start_column < b.location().start_column; + }); +} + +DiagnosticBag DiagnosticBag::as_sorted() const { + DiagnosticBag sorted(*this); + sorted.sort(); + return sorted; +} + +template +void DiagnosticBag::log_to_impl(spdlog::logger* logger) const { + for (DiagnosticItem const& item : _diagnostics) { + if (item.is_secondary()) { + continue; + } + + spdlog::level::level_enum log_level; + switch (item.level()) { + case DiagnosticLevel::NONE: continue; + case DiagnosticLevel::FATAL: + case DiagnosticLevel::ERROR: log_level = spdlog::level::err; break; + case DiagnosticLevel::WARN: log_level = spdlog::level::warn; break; + case DiagnosticLevel::INFO: + case DiagnosticLevel::HINT: + case DiagnosticLevel::SUGGEST: log_level = spdlog::level::info; break; + } + + if (item.related_items().empty()) { + if constexpr (IsParserNullptr) { + logger->log(log_level, item.message()); + } else { + logger->log( + log_level, + "{}:{}:{}: {}", + item.filepath(), + item.location().start_line, + item.location().start_column, + item.message() + ); + } + } else { + memory::fmt::basic_memory_buffer buf; + auto out = fmt::appender(buf); + + DiagnosticItem const* last = item.related_items().back(); + for (DiagnosticItem const* sec : item.related_items()) { + switch (sec->level()) { + case DiagnosticLevel::NONE: break; + case DiagnosticLevel::FATAL: out = fmt::detail::write(out, "fatal: "); break; + case DiagnosticLevel::ERROR: out = fmt::detail::write(out, "error: "); break; + case DiagnosticLevel::WARN: out = fmt::detail::write(out, "warning: "); break; + case DiagnosticLevel::INFO: out = fmt::detail::write(out, "info: "); break; + case DiagnosticLevel::HINT: out = fmt::detail::write(out, "hint: "); break; + case DiagnosticLevel::SUGGEST: out = fmt::detail::write(out, "suggestion: "); break; + } + if constexpr (IsParserNullptr) { + out = fmt::detail::write(out, sec->message()); + } else { + out = fmt::detail::write(out, sec->filepath()); + out = fmt::detail::write(out, ':'); + out = fmt::detail::write(out, sec->filepath()); + out = fmt::detail::write(out, ':'); + out = fmt::detail::write(out, sec->filepath()); + out = fmt::detail::write(out, ": "); + out = fmt::detail::write(out, sec->message()); + } + if (last != sec) { + out = fmt::detail::write(out, "\n\t"); + } + } + + std::string_view sv { buf.data(), buf.size() }; + if constexpr (IsParserNullptr) { + logger->log(log_level, "{}\n\t{}", item.message(), sv); + } else { + logger->log( + log_level, + "{}:{}:{}: {}\n\t{}", + item.filepath(), + item.location().start_line, + item.location().start_column, + item.message(), + sv + ); + } + } + } +} + +void DiagnosticBag::log_to(spdlog::logger* logger) const { + if (parser() == nullptr) { + log_to_impl(logger); + return; + } + + log_to_impl(logger); +} + +ovdl::v2script::Parser const* DiagnosticBag::parser() const { + return _parser; +} + +OpenVic::memory::colony const& DiagnosticBag::diagnostics() const& { + return _diagnostics; +} + +OpenVic::memory::colony DiagnosticBag::diagnostics() && { + return OV_MOV(_diagnostics); +} + +size_t DiagnosticBag::error_count() const { + return _error_count; +} + +size_t DiagnosticBag::warning_count() const { + return _warning_count; +} + +bool DiagnosticBag::has_fatal_error() const { + return _fatal_error; +} diff --git a/src/openvic-simulation/definition/dataloader/diagnostic/DiagnosticBag.hpp b/src/openvic-simulation/definition/dataloader/diagnostic/DiagnosticBag.hpp new file mode 100644 index 000000000..f94be8e8b --- /dev/null +++ b/src/openvic-simulation/definition/dataloader/diagnostic/DiagnosticBag.hpp @@ -0,0 +1,92 @@ +#pragma once + +#include + +#include +#include + +#include + +#include "openvic-simulation/core/Typedefs.hpp" +#include "openvic-simulation/core/memory/Colony.hpp" +#include "openvic-simulation/definition/dataloader/diagnostic/DiagnosticCode.hpp" +#include "openvic-simulation/definition/dataloader/diagnostic/DiagnosticItem.hpp" +#include "openvic-simulation/definition/dataloader/diagnostic/DiagnosticLevel.hpp" + +namespace spdlog { + class logger; +} + +namespace OpenVic::dataloader { + class DiagnosticBag { + public: + friend class DiagnosticItem; + + DiagnosticBag(ovdl::v2script::Parser const* parser); + + DiagnosticItem& fatal(ovdl::v2script::ast::Node const* node) OV_LIFETIME_BOUND; + DiagnosticItem& error(ovdl::v2script::ast::Node const* node) OV_LIFETIME_BOUND; + DiagnosticItem& warning(ovdl::v2script::ast::Node const* node) OV_LIFETIME_BOUND; + DiagnosticItem& info(ovdl::v2script::ast::Node const* node) OV_LIFETIME_BOUND; + DiagnosticItem& hint(ovdl::v2script::ast::Node const* node) OV_LIFETIME_BOUND; + DiagnosticItem& suggestion(ovdl::v2script::ast::Node const* node) OV_LIFETIME_BOUND; + DiagnosticItem& report(DiagnosticLevel level, ovdl::v2script::ast::Node const* node) OV_LIFETIME_BOUND; + + void clear(); + void sort(); + + DiagnosticBag as_sorted() const; + + void log_to(spdlog::logger* logger) const; + + ovdl::v2script::Parser const* parser() const; + + memory::colony const& diagnostics() const & OV_LIFETIME_BOUND; + memory::colony diagnostics() &&; + + size_t error_count() const; + size_t warning_count() const; + bool has_fatal_error() const; + + template + auto filter(Predicate&& predicate) const OV_LIFETIME_BOUND { + return _diagnostics | ranges::views::filter(std::forward(predicate)); + } + + auto errors() const OV_LIFETIME_BOUND { + return filter([](DiagnosticItem const& d) { + return d.level() <= DiagnosticLevel::ERROR; + }); + } + + auto warnings() const OV_LIFETIME_BOUND { + return filter([](DiagnosticItem const& d) { + return d.level() == DiagnosticLevel::WARN; + }); + } + + auto of_level(DiagnosticLevel level) const OV_LIFETIME_BOUND { + return filter([level](DiagnosticItem const& d) { + return d.level() == level; + }); + } + + auto of_code(diagnostic_code_t code) const OV_LIFETIME_BOUND { + return filter([code](DiagnosticItem const& d) { + return d.diagnostic_code() == code; + }); + } + + private: + memory::colony _diagnostics; + ovdl::v2script::Parser const* _parser; + size_t _error_count = 0; + size_t _warning_count = 0; + bool _fatal_error = false; + + DiagnosticItem& report(DiagnosticItem&& item) OV_LIFETIME_BOUND; + + template + void log_to_impl(spdlog::logger* logger) const; + }; +} diff --git a/src/openvic-simulation/definition/dataloader/diagnostic/DiagnosticCode.cpp b/src/openvic-simulation/definition/dataloader/diagnostic/DiagnosticCode.cpp new file mode 100644 index 000000000..c55553028 --- /dev/null +++ b/src/openvic-simulation/definition/dataloader/diagnostic/DiagnosticCode.cpp @@ -0,0 +1,20 @@ +#include "DiagnosticCode.hpp" + +#include +#include + +#include + +using namespace OpenVic::dataloader; + +fmt::format_context::iterator fmt::formatter::format( + diagnostic_code_t value, fmt::format_context& ctx +) const { + if (!_specs.dynamic()) { + return fmt::detail::write(ctx.out(), type_safe::get(value), _specs, ctx.locale()); + } + auto specs = fmt::format_specs(_specs); + fmt::detail::handle_dynamic_spec(specs.dynamic_width(), specs.width, _specs.width_ref, ctx); + fmt::detail::handle_dynamic_spec(specs.dynamic_precision(), specs.precision, _specs.precision_ref, ctx); + return fmt::detail::write(ctx.out(), type_safe::get(value), specs, ctx.locale()); +} diff --git a/src/openvic-simulation/definition/dataloader/diagnostic/DiagnosticCode.hpp b/src/openvic-simulation/definition/dataloader/diagnostic/DiagnosticCode.hpp new file mode 100644 index 000000000..af4bb6ba2 --- /dev/null +++ b/src/openvic-simulation/definition/dataloader/diagnostic/DiagnosticCode.hpp @@ -0,0 +1,58 @@ +#pragma once + +#include + +#include + +#include + +namespace OpenVic::dataloader { + struct diagnostic_code_t + : type_safe::strong_typedef, + type_safe::strong_typedef_op::equality_comparison, + type_safe::strong_typedef_op::mixed_equality_comparison { + using strong_typedef::strong_typedef; + }; +} + +template<> +struct fmt::formatter { + constexpr fmt::format_parse_context::iterator parse(fmt::format_parse_context& ctx) { + _specs.set_type(presentation_type::hex); + if (ctx.begin() == ctx.end() || *ctx.begin() == '}') { + _specs.set_upper(); + return ctx.begin(); + } + + auto begin = ctx.begin(); + auto end = ctx.end(); + + auto c = '\0'; + if (end - begin > 1) { + auto next = fmt::detail::to_ascii(begin[1]); + c = fmt::detail::parse_align(next) == align::none ? fmt::detail::to_ascii(*begin) : '\0'; + } else { + c = fmt::detail::to_ascii(*begin); + } + + // Preserves lowercase behavior if supplied x or b, defaults to uppercase + switch (c) { + case 'x': break; + case 'b': break; + default: _specs.set_upper(); break; + } + + return fmt::detail::parse_format_specs( + ctx.begin(), + ctx.end(), + _specs, + ctx, + fmt::detail::type_constant, char>::value + ); + } + + fmt::format_context::iterator format(OpenVic::dataloader::diagnostic_code_t value, fmt::format_context& ctx) const; + +private: + fmt::detail::dynamic_format_specs _specs; +}; diff --git a/src/openvic-simulation/definition/dataloader/diagnostic/DiagnosticItem.cpp b/src/openvic-simulation/definition/dataloader/diagnostic/DiagnosticItem.cpp new file mode 100644 index 000000000..f96ad12d3 --- /dev/null +++ b/src/openvic-simulation/definition/dataloader/diagnostic/DiagnosticItem.cpp @@ -0,0 +1,115 @@ +#include "DiagnosticItem.hpp" + +#include + +#include + +#include + +#include "openvic-simulation/core/Typedefs.hpp" +#include "openvic-simulation/core/memory/String.hpp" +#include "openvic-simulation/core/memory/Vector.hpp" +#include "openvic-simulation/definition/dataloader/diagnostic/DiagnosticBag.hpp" +#include "openvic-simulation/definition/dataloader/diagnostic/DiagnosticCode.hpp" + +using namespace OpenVic::dataloader; + +DiagnosticItem::DiagnosticItem(DiagnosticBag& bag, node_pointer_type node) : + DiagnosticItem { bag, bag.parser() != nullptr ? bag.parser()->get_position(node) : ovdl::FilePosition {} } {} + +DiagnosticItem::DiagnosticItem(DiagnosticBag& bag, ovdl::FilePosition location) : _bag { &bag }, _location(location) {} + +DiagnosticItem& DiagnosticItem::with_level(DiagnosticLevel level) { + _level = level; + return *this; +} + +DiagnosticItem& DiagnosticItem::with_code(diagnostic_code_t diagnostic_code) { + _diagnostic_code = diagnostic_code; + return *this; +} + +DiagnosticItem& DiagnosticItem::with_code(type_safe::underlying_type diagnostic_code) { + return with_code(diagnostic_code_t { diagnostic_code }); +} + +DiagnosticItem& DiagnosticItem::info(node_pointer_type related_node) { + return item(DiagnosticLevel::INFO, related_node); +} + +DiagnosticItem& DiagnosticItem::info(ovdl::FilePosition related_location) { + return item(DiagnosticLevel::INFO, related_location); +} + +DiagnosticItem& DiagnosticItem::hint(node_pointer_type related_node) { + return item(DiagnosticLevel::HINT, related_node); +} + +DiagnosticItem& DiagnosticItem::hint(ovdl::FilePosition related_location) { + return item(DiagnosticLevel::HINT, related_location); +} + +DiagnosticItem& DiagnosticItem::suggestion(node_pointer_type related_node) { + return item(DiagnosticLevel::SUGGEST, related_node); +} + +DiagnosticItem& DiagnosticItem::suggestion(ovdl::FilePosition related_location) { + return item(DiagnosticLevel::SUGGEST, related_location); +} + +DiagnosticItem& DiagnosticItem::item(DiagnosticLevel level, node_pointer_type related_node) { + return item(level, _bag->parser() != nullptr ? _bag->parser()->get_position(related_node) : ovdl::FilePosition {}); +} + +DiagnosticItem& DiagnosticItem::item(DiagnosticLevel level, ovdl::FilePosition related_location) { + DiagnosticItem& reported = + _bag->report({ *_bag, related_location.is_empty() ? _location : related_location }).with_level(level); + reported._primary = false; + _related_items.emplace_back(&reported); + reported._related_items.emplace_back(this); + return reported; +} + +std::string_view DiagnosticItem::message() const& { + return _message; +} + +OpenVic::memory::string DiagnosticItem::message() && { + return OV_MOV(_message); +} + +std::string_view DiagnosticItem::filepath() const { + return _bag->parser() != nullptr ? _bag->parser()->get_file_path() : std::string_view {}; +} + +OpenVic::memory::vector const& DiagnosticItem::related_items() const { + return _related_items; +} + +ovdl::FilePosition DiagnosticItem::location() const { + return _location; +} + +DiagnosticLevel DiagnosticItem::level() const { + return _level; +} + +bool DiagnosticItem::is_primary() const { + return _primary; +} + +bool DiagnosticItem::is_secondary() const { + return !_primary; +} + +diagnostic_code_t DiagnosticItem::diagnostic_code() const { + return _diagnostic_code; +} + +DiagnosticBag& DiagnosticItem::diagnostic() { + return *_bag; +} + +DiagnosticBag const& DiagnosticItem::diagnostic() const { + return *_bag; +} diff --git a/src/openvic-simulation/definition/dataloader/diagnostic/DiagnosticItem.hpp b/src/openvic-simulation/definition/dataloader/diagnostic/DiagnosticItem.hpp new file mode 100644 index 000000000..d7cb3b253 --- /dev/null +++ b/src/openvic-simulation/definition/dataloader/diagnostic/DiagnosticItem.hpp @@ -0,0 +1,83 @@ +#pragma once + +#include + +#include +#include +#include + +#include + +#include + +#include "openvic-simulation/core/Typedefs.hpp" +#include "openvic-simulation/core/memory/Formatting.hpp" +#include "openvic-simulation/core/memory/String.hpp" +#include "openvic-simulation/core/memory/Vector.hpp" +#include "openvic-simulation/definition/dataloader/diagnostic/DiagnosticCode.hpp" +#include "openvic-simulation/definition/dataloader/diagnostic/DiagnosticLevel.hpp" + +namespace OpenVic::dataloader { + class DiagnosticBag; + + class DiagnosticItem { + public: + friend class DiagnosticBag; + + using node_pointer_type = ovdl::v2script::ast::Node const*; + + DiagnosticItem(DiagnosticBag& bag, node_pointer_type node); + DiagnosticItem(DiagnosticBag& bag, ovdl::FilePosition location); + + DiagnosticItem& with_level(DiagnosticLevel level); + + DiagnosticItem& with_code(diagnostic_code_t diagnostic_code); + DiagnosticItem& with_code(type_safe::underlying_type diagnostic_code); + + template + DiagnosticItem& with_message(fmt::format_string fmt, T&&... args) { + memory::fmt::basic_memory_buffer result {}; + auto out = fmt::appender(result); + out = fmt::format_to(out, fmt, std::forward(args)...); + _message = { result.data(), result.size() }; + return *this; + } + + DiagnosticItem& info(node_pointer_type related_node = nullptr); + DiagnosticItem& info(ovdl::FilePosition related_location); + + DiagnosticItem& hint(node_pointer_type related_node = nullptr); + DiagnosticItem& hint(ovdl::FilePosition related_location); + + DiagnosticItem& suggestion(node_pointer_type related_node = nullptr); + DiagnosticItem& suggestion(ovdl::FilePosition related_location); + + DiagnosticItem& item(DiagnosticLevel level, node_pointer_type related_node = nullptr); + DiagnosticItem& item(DiagnosticLevel level, ovdl::FilePosition related_location); + + std::string_view message() const & OV_LIFETIME_BOUND; + memory::string message() &&; + + std::string_view filepath() const; + + memory::vector const& related_items() const OV_LIFETIME_BOUND; + + ovdl::FilePosition location() const; + DiagnosticLevel level() const; + bool is_primary() const; + bool is_secondary() const; + diagnostic_code_t diagnostic_code() const; + + DiagnosticBag& diagnostic() OV_LIFETIME_BOUND; + DiagnosticBag const& diagnostic() const OV_LIFETIME_BOUND; + + private: + memory::string _message; + memory::vector _related_items; + ovdl::FilePosition _location; + DiagnosticBag* _bag = nullptr; + diagnostic_code_t _diagnostic_code { 0u }; + bool _primary = true; + DiagnosticLevel _level = DiagnosticLevel::NONE; + }; +} diff --git a/src/openvic-simulation/definition/dataloader/diagnostic/DiagnosticLevel.hpp b/src/openvic-simulation/definition/dataloader/diagnostic/DiagnosticLevel.hpp new file mode 100644 index 000000000..e1c9f267d --- /dev/null +++ b/src/openvic-simulation/definition/dataloader/diagnostic/DiagnosticLevel.hpp @@ -0,0 +1,15 @@ +#pragma once + +#include + +namespace OpenVic::dataloader { + enum class DiagnosticLevel : std::uint8_t { + NONE, // Filters out all diagnostic reporting + FATAL, // Critical failure that ends processing + ERROR, + WARN, + INFO, // Helpful context, probably attached to a ERROR or WARN item + HINT, // Non-style hint + SUGGEST, // Style suggestion + }; +} diff --git a/src/openvic-simulation/definition/dataloader/diagnostic/DiagnosticRenderer.cpp b/src/openvic-simulation/definition/dataloader/diagnostic/DiagnosticRenderer.cpp new file mode 100644 index 000000000..5a8532195 --- /dev/null +++ b/src/openvic-simulation/definition/dataloader/diagnostic/DiagnosticRenderer.cpp @@ -0,0 +1,43 @@ +#include "DiagnosticRenderer.hpp" + +#include "openvic-simulation/core/memory/SmartPtr.hpp" +#include "openvic-simulation/definition/dataloader/diagnostic/DiagnosticBag.hpp" +#include "openvic-simulation/definition/dataloader/diagnostic/DiagnosticSink.hpp" + +using namespace OpenVic::dataloader; + +void DiagnosticRenderer::render(DiagnosticItem const& item) const { + for (memory::unique_base_ptr const& sink : _sinks) { + sink->consume(item); + } +} + +void DiagnosticRenderer::render(DiagnosticBag const& bag) const { + render(bag.diagnostics()); +} + +void DiagnosticRenderer::flush() const { + for (memory::unique_base_ptr const& sink : _sinks) { + sink->flush(); + } +} + +DiagnosticSink* DiagnosticRenderer::append_sink(sink_pointer_type sink) { + return _sinks.emplace_back(std::move(sink)).get(); +} + +DiagnosticRenderer::sink_vector::const_iterator DiagnosticRenderer::erase_sink(sink_vector::const_iterator pos) { + return _sinks.erase(pos); +} + +void DiagnosticRenderer::clear_sinks() { + _sinks.clear(); +} + +DiagnosticRenderer::sink_vector const& DiagnosticRenderer::get_sinks() const& { + return _sinks; +} + +DiagnosticRenderer::sink_vector DiagnosticRenderer::get_sinks() && { + return OV_MOV(_sinks); +} diff --git a/src/openvic-simulation/definition/dataloader/diagnostic/DiagnosticRenderer.hpp b/src/openvic-simulation/definition/dataloader/diagnostic/DiagnosticRenderer.hpp new file mode 100644 index 000000000..ef3973854 --- /dev/null +++ b/src/openvic-simulation/definition/dataloader/diagnostic/DiagnosticRenderer.hpp @@ -0,0 +1,68 @@ +#pragma once + +#include + +#include +#include + +#include "openvic-simulation/core/Typedefs.hpp" +#include "openvic-simulation/core/memory/SmartPtr.hpp" +#include "openvic-simulation/core/memory/Vector.hpp" +#include "openvic-simulation/definition/dataloader/diagnostic/DiagnosticSink.hpp" + +namespace OpenVic::dataloader { + class DiagnosticItem; + class DiagnosticBag; + + class DiagnosticRenderer { + public: + using sink_pointer_type = memory::unique_base_ptr; + using sink_vector = memory::vector; + + void render(DiagnosticItem const& item) const; + void render(DiagnosticBag const& bag) const; + + template + requires std::convertible_to, DiagnosticItem const&> + void render(Container const& items) const { + for (sink_pointer_type const& sink : _sinks) { + sink->begin(); + } + + for (DiagnosticItem const& item : items) { + render(item); + } + + for (sink_pointer_type const& sink : _sinks) { + sink->end(); + sink->flush(); + } + } + + void flush() const; + + DiagnosticSink* append_sink(sink_pointer_type sink) OV_LIFETIME_BOUND; + + template T, typename... Args> + T* emplace_sink(Args&&... args) OV_LIFETIME_BOUND { + return static_cast(append_sink(memory::make_unique(std::forward(args)...))); + } + + sink_vector::const_iterator erase_sink(sink_vector::const_iterator pos) OV_LIFETIME_BOUND; + + void clear_sinks(); + + sink_vector const& get_sinks() const& OV_LIFETIME_BOUND; + sink_vector get_sinks() &&; + + template T, typename... Args> + static DiagnosticRenderer make(Args&&... args) { + DiagnosticRenderer renderer; + renderer.emplace_sink(std::forward(args)...); + return renderer; + } + + private: + sink_vector _sinks; + }; +} diff --git a/src/openvic-simulation/definition/dataloader/diagnostic/DiagnosticSink.hpp b/src/openvic-simulation/definition/dataloader/diagnostic/DiagnosticSink.hpp new file mode 100644 index 000000000..9d2a5c7f6 --- /dev/null +++ b/src/openvic-simulation/definition/dataloader/diagnostic/DiagnosticSink.hpp @@ -0,0 +1,17 @@ +#pragma once + +namespace OpenVic::dataloader { + class DiagnosticItem; + + class DiagnosticSink { + public: + virtual ~DiagnosticSink() = default; + + // Called for every diagnostic + virtual void consume(DiagnosticItem const& diag) = 0; + + virtual void begin() {} // start of a batch / report + virtual void end() {} // end of a batch / report + virtual void flush() {} // force output of any buffered data + }; +} diff --git a/src/openvic-simulation/definition/dataloader/diagnostic/FileSink.cpp b/src/openvic-simulation/definition/dataloader/diagnostic/FileSink.cpp new file mode 100644 index 000000000..87faa5be9 --- /dev/null +++ b/src/openvic-simulation/definition/dataloader/diagnostic/FileSink.cpp @@ -0,0 +1,15 @@ +#include "FileSink.hpp" + +#include + +using namespace OpenVic::dataloader; + +FileSink::FileSink(std::FILE* file, Options opts) : _file { file }, _options { opts } {} + +bool FileSink::can_render_style() const { + return _options.use_styles; +} + +void FileSink::render_to(memory_buffer_type& buf) { + fmt::print(_file, "{}", fmt::string_view { buf.data(), buf.size() }); +} diff --git a/src/openvic-simulation/definition/dataloader/diagnostic/FileSink.hpp b/src/openvic-simulation/definition/dataloader/diagnostic/FileSink.hpp new file mode 100644 index 000000000..886aa282a --- /dev/null +++ b/src/openvic-simulation/definition/dataloader/diagnostic/FileSink.hpp @@ -0,0 +1,23 @@ +#pragma once + +#include + +#include "openvic-simulation/definition/dataloader/diagnostic/TextSink.hpp" + +namespace OpenVic::dataloader { + class FileSink final : public TextSink { + public: + struct Options { + bool use_styles = true; + }; + + explicit FileSink(std::FILE* file, Options opts = { .use_styles = true }); + + private: + std::FILE* _file; + Options _options; + + bool can_render_style() const override; + void render_to(memory_buffer_type& buf) override; + }; +} diff --git a/src/openvic-simulation/definition/dataloader/diagnostic/MutexSink.hpp b/src/openvic-simulation/definition/dataloader/diagnostic/MutexSink.hpp new file mode 100644 index 000000000..8dc799127 --- /dev/null +++ b/src/openvic-simulation/definition/dataloader/diagnostic/MutexSink.hpp @@ -0,0 +1,59 @@ +#pragma once + +#include +#include +#include + +#include "openvic-simulation/core/memory/SmartPtr.hpp" +#include "openvic-simulation/definition/dataloader/diagnostic/DiagnosticSink.hpp" + +namespace OpenVic::dataloader { + class DiagnosticItem; + + template DiagnosticSinkT = DiagnosticSink> + class MutexSink final : public DiagnosticSink { + public: + using sink_type = DiagnosticSinkT; + + using sink_pointer_type = + std::conditional_t, memory::unique_ptr, memory::unique_base_ptr>; + + explicit MutexSink(sink_pointer_type sink) : _sink(std::move(sink)) {} + + template + requires std::constructible_from + explicit MutexSink(Args&&... args) : _sink(memory::make_unique(std::forward(args)...)) {} + + ~MutexSink() override = default; + + void begin() override { + std::lock_guard lock(_mutex); + _sink->begin(); + } + + void end() override { + std::lock_guard lock(_mutex); + _sink->end(); + } + + void consume(DiagnosticItem const& diag) override { + std::lock_guard lock(_mutex); + _sink->consume(diag); + } + + void flush() override { + std::lock_guard lock(_mutex); + _sink->flush(); + } + + protected: + mutable MutexT _mutex; + sink_pointer_type _sink; + }; + + template DiagnosticSinkT = DiagnosticSink, typename... Args> + requires std::constructible_from + auto make_mutex_sink(Args&&... args) { + return memory::make_unique(std::forward(args)...); + } +} diff --git a/src/openvic-simulation/definition/dataloader/diagnostic/StringSink.cpp b/src/openvic-simulation/definition/dataloader/diagnostic/StringSink.cpp new file mode 100644 index 000000000..3f61e8ff0 --- /dev/null +++ b/src/openvic-simulation/definition/dataloader/diagnostic/StringSink.cpp @@ -0,0 +1,17 @@ +#include "StringSink.hpp" + +#include "openvic-simulation/core/memory/String.hpp" + +using namespace OpenVic::dataloader; + +StringSink::StringSink(memory::string& out) : _out { out } {} + +StringSink::~StringSink() = default; + +bool StringSink::can_render_style() const { + return false; +} + +void StringSink::render_to(memory_buffer_type& buf) { + _out = { buf.data(), buf.size() }; +} diff --git a/src/openvic-simulation/definition/dataloader/diagnostic/StringSink.hpp b/src/openvic-simulation/definition/dataloader/diagnostic/StringSink.hpp new file mode 100644 index 000000000..f50096dbd --- /dev/null +++ b/src/openvic-simulation/definition/dataloader/diagnostic/StringSink.hpp @@ -0,0 +1,18 @@ +#pragma once + +#include "openvic-simulation/core/memory/String.hpp" +#include "openvic-simulation/definition/dataloader/diagnostic/TextSink.hpp" + +namespace OpenVic::dataloader { + class StringSink final : public TextSink { + public: + explicit StringSink(memory::string& out); + ~StringSink() override; + + private: + memory::string& _out; + + bool can_render_style() const override; + void render_to(memory_buffer_type& buf) override; + }; +} diff --git a/src/openvic-simulation/definition/dataloader/diagnostic/TextSink.cpp b/src/openvic-simulation/definition/dataloader/diagnostic/TextSink.cpp new file mode 100644 index 000000000..0e37ff5f9 --- /dev/null +++ b/src/openvic-simulation/definition/dataloader/diagnostic/TextSink.cpp @@ -0,0 +1,632 @@ +#include "TextSink.hpp" + +#include +#include +#include +#include + +#include + +#include +#include + +#include "openvic-simulation/core/Typedefs.hpp" +#include "openvic-simulation/core/memory/String.hpp" +#include "openvic-simulation/definition/dataloader/diagnostic/DiagnosticItem.hpp" +#include "openvic-simulation/definition/dataloader/diagnostic/DiagnosticLevel.hpp" + +using namespace OpenVic::dataloader; + +static TextSink::text_iterator find_code_point_boundary(TextSink::text_iterator cur, TextSink::text_iterator end) { + // UTF-8 continuation code point unit + while (cur != end && (*cur & 0b1100'0000) == (0b10 << 6)) { + ++cur; + } + return cur; +} + +TextSink::~TextSink() = default; + +void TextSink::consume(DiagnosticItem const& item) { + if (item.is_secondary()) { + return; + } + + open_file(item.filepath()); + + memory_buffer_type buf; + + render_level(item, buf); + render_text(buf, item.message()); + render_text(buf, '\n'); + + render_filepath(item, buf); + render_empty_annotation(buf); + + render_annotation(item, buf, "here"); + + if (item.is_primary()) { + for (DiagnosticItem const* subitem : item.related_items()) { + if (subitem->filepath() != item.filepath()) { + render_filepath(item, buf); + } + render_empty_annotation(buf); + render_annotation(*subitem, buf, subitem->message()); + render_empty_annotation(buf); + } + } + + render_to(buf); +} + +void TextSink::end() { + _file_content.clear(); + _file_content.shrink_to_fit(); +} + +void TextSink::render_text(memory_buffer_type& buf, std::string_view message) { + buf.append(message); +} + +void TextSink::render_text(memory_buffer_type& buf, char c) { + buf.push_back(c); +} + +void TextSink::render_style(memory_buffer_type& buf, fmt::text_style ts) { + if (ts != fmt::text_style {} && can_render_style()) { + if (ts.has_emphasis()) { + fmt::detail::ansi_color_escape emphasis = fmt::detail::make_emphasis(ts.get_emphasis()); + buf.append(emphasis.begin(), emphasis.end()); + } + if (ts.has_foreground()) { + fmt::detail::ansi_color_escape foreground = fmt::detail::make_foreground_color(ts.get_foreground()); + buf.append(foreground.begin(), foreground.end()); + } + if (ts.has_background()) { + fmt::detail::ansi_color_escape background = fmt::detail::make_background_color(ts.get_background()); + buf.append(background.begin(), background.end()); + } + } +} + +void TextSink::render_style_reset(memory_buffer_type& buf) { + if (can_render_style()) { + fmt::detail::reset_color(buf); + } +} + +void TextSink::render_filepath(DiagnosticItem const& item, memory_buffer_type& buf) { + if (item.filepath().empty() || _file_content.empty() || _file_content[0] == '\0') { + return; + } + + render_formatted_text( + buf, + fmt::emphasis::bold | fmt::fg(fmt::color::blue), + "{}:{}:{}\n", + item.filepath(), + item.location().start_line, + item.location().start_column + ); +} + +void TextSink::render_empty_annotation(memory_buffer_type& buf) { + render_formatted_text(buf, " {}\n", get_column()); +} + +static bool is_continuation_byte(unsigned char c) { + return (c & 0xC0) == 0x80; +} + +static bool is_one_width( + TextSink::memory_buffer_type::value_type const*& begin, TextSink::memory_buffer_type::value_type const* end +) { + if (!is_continuation_byte(*begin)) { + return true; + } + + const unsigned char value = static_cast(*begin); + if (value >= 0xC2 && value <= 0xDF) { + if (end - begin >= 2 && is_continuation_byte(begin[1])) { + begin += 1; + return true; + } + } else if (value >= 0xE0 && value <= 0xEF) { + if (end - begin >= 3 && is_continuation_byte(begin[1]) && is_continuation_byte(begin[2])) { + begin += 2; + return true; + } + } else if (value >= 0xF0 && value <= 0xF4) { + if (end - begin >= 4 && is_continuation_byte(begin[1]) && is_continuation_byte(begin[2]) && + is_continuation_byte(begin[3])) { + begin += 3; + return true; + } + } + + return false; +} + +static size_t get_visible_width( + TextSink::memory_buffer_type::value_type const* begin, TextSink::memory_buffer_type::value_type const* end +) { + size_t result = 0; + for (; begin != end; ++begin) { + unsigned char value = static_cast(*begin); + if (value == '\e' && end - begin > 2) { + if (begin[1] == '[') { + begin += 2; + } else { + continue; + } + + for (; begin != end; ++begin) { + unsigned char c = static_cast(*begin); + if (c >= 0x40 && c <= 0x7E) { + break; + } + } + + if (begin == end || begin + 1 == end) { + break; + } + continue; + } + + if (is_one_width(begin, end)) { + ++result; + } + } + return result; +} + +void TextSink::render_annotation(DiagnosticItem const& item, memory_buffer_type& buf, std::string_view annotation) { + content_marker line_marker = find_in_content(item.location().start_line, 1); + content_marker line_marker_end = find_in_content(item.location().start_line + 1, 1, line_marker); + content_marker begin_marker = find_in_content(item.location().start_line, item.location().start_column, line_marker); + content_marker end_marker = find_in_content(item.location().end_line, item.location().end_column - 1, begin_marker); + + text_iterator line_begin = line_marker.position; + text_iterator begin = begin_marker.position; + text_iterator end = end_marker.position; + text_iterator newline_end = line_marker_end.position; + + bool truncated_multiline = false; + if (begin == end) { + if (end != newline_end) { + // Empty range before end of newline; extend by one code unit. + ++end; + } + } else if (end > newline_end) { + end = newline_end; + truncated_multiline = true; + } + + bool rounded_end; + { + text_iterator old_end = end; + end = find_code_point_boundary(end, newline_end); + rounded_end = end != old_end; + } + + struct lexeme { + text_iterator begin, end; + }; + + bool annotated_newline = end > newline_end; + lexeme before = { line_begin, begin }; + lexeme annotated; + lexeme after; + if (!annotated_newline) { + annotated = { begin, end }; + after = { end, newline_end }; + } else { + annotated = { begin, newline_end }; + after = { newline_end, newline_end }; + } + + bool annotate_eof = truncated_multiline && !annotated_newline; + + render_formatted_text(buf, fmt::fg(fmt::color::blue), "{:4} ", item.location().start_line); + render_formatted_text(buf, "{} ", get_column()); + + size_t indent_before; + { + size_t count_start = buf.size(); + render_unicode_display(buf, before.begin, before.end); + memory_buffer_type::value_type const* count_end = buf.end(); + indent_before = get_visible_width(buf.data() + count_start, count_end); + } + + if (item.level() <= DiagnosticLevel::ERROR) { + render_style(buf, get_style_for(item) | fmt::emphasis::bold); + } else { + render_style(buf, get_style_for(item)); + } + size_t underline_count; + { + + size_t count_start = buf.size(); + render_unicode_display(buf, annotated.begin, annotated.end); + memory_buffer_type::value_type const* count_end = buf.end(); + underline_count = get_visible_width(buf.data() + count_start, count_end); + } + render_style_reset(buf); + + render_unicode_display(buf, after.begin, after.end); + render_text(buf, '\n'); + + render_formatted_text(buf, " {} ", get_column()); + { + size_t indent_start = buf.size(); + buf.resize(buf.size() + indent_before); + std::memset(buf.data() + indent_start, ' ', indent_before); + } + + if (item.level() <= DiagnosticLevel::ERROR) { + render_style(buf, get_style_for(item) | fmt::emphasis::bold); + } else { + render_style(buf, get_style_for(item)); + } + buf.reserve(buf.size() + underline_count + 3 + item.message().size()); + std::string_view underline = get_underline_for(item); + for (size_t i = 0; i != underline_count; i += underline.size()) { + render_text(buf, underline); + } + if (underline_count == 0 || annotate_eof) { + render_text(buf, underline); + } + render_text(buf, ' '); + render_text(buf, annotation); + render_text(buf, '\n'); + render_style_reset(buf); +} + +void TextSink::render_level(DiagnosticItem const& item, memory_buffer_type& buf) { + if (item.diagnostic_code() == 0) { + switch (item.level()) { + case DiagnosticLevel::NONE: break; + case DiagnosticLevel::FATAL: render_formatted_text(buf, fmt::emphasis::bold | get_style_for(item), "fatal: "); break; + case DiagnosticLevel::ERROR: render_formatted_text(buf, fmt::emphasis::bold | get_style_for(item), "error: "); break; + case DiagnosticLevel::WARN: render_formatted_text(buf, fmt::emphasis::bold | get_style_for(item), "warning: "); break; + case DiagnosticLevel::INFO: render_formatted_text(buf, fmt::emphasis::bold | get_style_for(item), "info: "); break; + case DiagnosticLevel::HINT: render_formatted_text(buf, fmt::emphasis::bold | get_style_for(item), "hint: "); break; + case DiagnosticLevel::SUGGEST: + render_formatted_text(buf, fmt::emphasis::bold | get_style_for(item), "suggest: "); + break; + } + return; + } + + switch (item.level()) { + case DiagnosticLevel::NONE: break; + case DiagnosticLevel::FATAL: + render_formatted_text(buf, fmt::emphasis::bold | get_style_for(item), "fatal {}: ", item.diagnostic_code()); + break; + case DiagnosticLevel::ERROR: + render_formatted_text(buf, fmt::emphasis::bold | get_style_for(item), "error {}: ", item.diagnostic_code()); + break; + case DiagnosticLevel::WARN: + render_formatted_text(buf, fmt::emphasis::bold | get_style_for(item), "warning {}: ", item.diagnostic_code()); + break; + case DiagnosticLevel::INFO: + render_formatted_text(buf, fmt::emphasis::bold | get_style_for(item), "info {}: ", item.diagnostic_code()); + break; + case DiagnosticLevel::HINT: + render_formatted_text(buf, fmt::emphasis::bold | get_style_for(item), "hint {}: ", item.diagnostic_code()); + break; + case DiagnosticLevel::SUGGEST: + render_formatted_text(buf, fmt::emphasis::bold | get_style_for(item), "suggest {}: ", item.diagnostic_code()); + break; + } +} + +struct code_point_result { + char32_t code_point_value = 0; + enum class error_t { + SUCCESS, + END_OF_FILE, + LEADS_WITH_TRAILING, + MISSING_TRAILING, + SURROGATE, + OVERLONG_SEQUENCE, + OUT_OF_RANGE, + } error {}; + TextSink::text_iterator end; +}; + +static code_point_result parse_code_point(TextSink::text_iterator begin, TextSink::text_iterator end) { + using uchar_t = unsigned char; + constexpr auto payload_lead1 = 0b0111'1111; + constexpr auto payload_lead2 = 0b0001'1111; + constexpr auto payload_lead3 = 0b0000'1111; + constexpr auto payload_lead4 = 0b0000'0111; + constexpr auto payload_cont = 0b0011'1111; + + constexpr auto pattern_lead1 = 0b0 << 7; + constexpr auto pattern_lead2 = 0b110 << 5; + constexpr auto pattern_lead3 = 0b1110 << 4; + constexpr auto pattern_lead4 = 0b11110 << 3; + constexpr auto pattern_cont = 0b10 << 6; + + if (begin == end) { + return { {}, code_point_result::error_t::END_OF_FILE, begin }; + } + + auto first = uchar_t(*begin); + if ((first & ~payload_lead1) == pattern_lead1) { + // ASCII character. + ++begin; + return { first, code_point_result::error_t::SUCCESS, begin }; + } else if ((first & ~payload_cont) == pattern_cont) { + return { {}, code_point_result::error_t::LEADS_WITH_TRAILING, begin }; + } else if ((first & ~payload_lead2) == pattern_lead2) { + ++begin; + + auto second = uchar_t(*begin); + if ((second & ~payload_cont) != pattern_cont) { + return { {}, code_point_result::error_t::MISSING_TRAILING, begin }; + } + ++begin; + + auto result = char32_t(first & payload_lead2); + result <<= 6; + result |= char32_t(second & payload_cont); + + // C0 and C1 are overlong ASCII. + if (first == 0xC0 || first == 0xC1) { + return { result, code_point_result::error_t::OVERLONG_SEQUENCE, begin }; + } else { + return { result, code_point_result::error_t::SUCCESS, begin }; + } + } else if ((first & ~payload_lead3) == pattern_lead3) { + ++begin; + + auto second = uchar_t(*begin); + if ((second & ~payload_cont) != pattern_cont) { + return { {}, code_point_result::error_t::MISSING_TRAILING, begin }; + } + ++begin; + + auto third = uchar_t(*begin); + if ((third & ~payload_cont) != pattern_cont) { + return { {}, code_point_result::error_t::MISSING_TRAILING, begin }; + } + ++begin; + + auto result = char32_t(first & payload_lead3); + result <<= 6; + result |= char32_t(second & payload_cont); + result <<= 6; + result |= char32_t(third & payload_cont); + + auto cp = result; + if (0xD800 <= cp && cp <= 0xDFFF) { + return { cp, code_point_result::error_t::SURROGATE, begin }; + } else if (first == 0xE0 && second < 0xA0) { + return { cp, code_point_result::error_t::OVERLONG_SEQUENCE, begin }; + } else { + return { cp, code_point_result::error_t::SUCCESS, begin }; + } + } else if ((first & ~payload_lead4) == pattern_lead4) { + ++begin; + + auto second = uchar_t(*begin); + if ((second & ~payload_cont) != pattern_cont) { + return { {}, code_point_result::error_t::MISSING_TRAILING, begin }; + } + ++begin; + + auto third = uchar_t(*begin); + if ((third & ~payload_cont) != pattern_cont) { + return { {}, code_point_result::error_t::MISSING_TRAILING, begin }; + } + ++begin; + + auto fourth = uchar_t(*begin); + if ((fourth & ~payload_cont) != pattern_cont) { + return { {}, code_point_result::error_t::MISSING_TRAILING, begin }; + } + ++begin; + + auto result = char32_t(first & payload_lead4); + result <<= 6; + result |= char32_t(second & payload_cont); + result <<= 6; + result |= char32_t(third & payload_cont); + result <<= 6; + result |= char32_t(fourth & payload_cont); + + auto cp = result; + if (cp > 0x10'FFFF) { + return { cp, code_point_result::error_t::OUT_OF_RANGE, begin }; + } else if (first == 0xF0 && second < 0x90) { + return { cp, code_point_result::error_t::OVERLONG_SEQUENCE, begin }; + } else { + return { cp, code_point_result::error_t::SUCCESS, begin }; + } + } else // FE or FF + { + return { {}, code_point_result::error_t::END_OF_FILE, begin }; + } +} + +static TextSink::text_iterator get_recovered_code_point( + TextSink::text_iterator begin, TextSink::text_iterator end, code_point_result result +) { + switch (result.error) { + using enum code_point_result::error_t; + case SUCCESS: + // Consume the entire code point. + return result.end; + case END_OF_FILE: return begin; + + case LEADS_WITH_TRAILING: + // Invalid code unit, consume to recover. + assert(result.end == begin); + return begin + 1; + + case MISSING_TRAILING: + case SURROGATE: + case OUT_OF_RANGE: + case OVERLONG_SEQUENCE: + // Consume all the invalid code units to recover. + return result.end; + } + OpenVic::unreachable(); +} + +void TextSink::render_unicode_display(memory_buffer_type& buf, text_iterator begin, text_iterator end) { + size_t count = 0; + while (true) { + if (code_point_result result = parse_code_point(begin, end); result.error == code_point_result::error_t::END_OF_FILE) { + break; + } else if (result.error == code_point_result::error_t::SUCCESS) { + begin = result.end; + render_code_point(buf, result.code_point_value); + } else { + // Recover from the failed code point. + text_iterator cur = begin; + begin = get_recovered_code_point(begin, end, result); + + // Visualize each skipped code unit as byte. + for (; cur != begin; ++cur) { + render_formatted_text(buf, "0x{:02X}", static_cast(*cur & 0xFF)); + } + } + + ++count; + } +} + +void TextSink::render_code_point(memory_buffer_type& buf, char32_t code_point) { + using namespace std::string_view_literals; + + // If code_point is invalid + if (code_point > 0x10'FFFF) { + return buf.append("U+????"sv); + } + + // If code_point is control character + if (code_point <= 0x1F || (0x7F <= code_point && code_point <= 0x9F)) { + int v = code_point; + switch (v) { + case '\0': return render_text(buf, "⟨NUL⟩"sv); + case '\r': return render_text(buf, "⟨CR⟩"sv); + case '\n': return render_formatted_text(buf, fmt::emphasis::faint, "{}", "⏎"); + case '\t': return render_formatted_text(buf, fmt::emphasis::faint, "{}", "⇨"); + default: break; + } + } + + if (code_point == ' ') { + return render_text(buf, ' '); + } + + if (code_point <= 0x7F) { + return render_text(buf, static_cast(code_point)); + } + + return render_formatted_text(buf, "⟨U+{:04X}⟩", static_cast(code_point)); +} + +std::string_view TextSink::get_column() const { + return reinterpret_cast(u8"│"); +} + +std::string_view TextSink::get_underline_for(DiagnosticItem const& item) const { + if (item.is_secondary()) { + return "~"; + } + return "^"; +} + +fmt::text_style TextSink::get_style_for(DiagnosticItem const& item) const { + switch (item.level()) { + case DiagnosticLevel::NONE: return {}; + case DiagnosticLevel::FATAL: return fmt::fg(fmt::terminal_color::bright_red); + case DiagnosticLevel::ERROR: return fmt::fg(fmt::color::red); + case DiagnosticLevel::WARN: return fmt::fg(fmt::color::yellow); + case DiagnosticLevel::INFO: return fmt::fg(fmt::terminal_color::cyan); + case DiagnosticLevel::HINT: return fmt::fg(fmt::terminal_color::bright_blue); + case DiagnosticLevel::SUGGEST: return fmt::fg(fmt::terminal_color::green); + } + OpenVic::unreachable(); +} + +bool TextSink::open_file(std::string_view filepath) { + if (_file_content.size() == 1 && _file_content[0] == '\0') { + return false; + } + + if (!_file_content.empty()) { + return true; + } + + uintmax_t size = std::filesystem::file_size(filepath); + _file_content.resize(static_cast(size)); + + std::FILE* file; + { + memory::string path { filepath }; + file = std::fopen(path.c_str(), "r+"); + if (!file) { + _file_content.resize(1); + return false; + } + } + + size_t read = std::fread(_file_content.data(), sizeof(memory::string::value_type), size, file); + std::fclose(file); + if (read != _file_content.size()) { + _file_content.resize(1); + return false; + } + + return true; +} + +TextSink::content_marker TextSink::find_in_content(size_t line, size_t column, TextSink::content_marker marker) { + if (marker.line > line || marker.line == line && marker.column > column) { + return marker; + } + + if (line >= _file_content.size() || column >= _file_content.size()) { + return { 0, 0, marker.position }; + } + + if (marker.position == text_iterator {}) { + marker = { 1, 1, _file_content.begin() }; + } + + char const* start_ptr = nullptr; + for (size_t column_count = marker.line == line ? marker.column : 1; + marker.line <= line && marker.position != _file_content.end(); + ++marker.position) { + char const& c = *marker.position; + if (c == '\n') { + ++marker.line; + continue; + } + + if (marker.line != line) { + continue; + } + + if (c == '\r' && marker.position + 1 != _file_content.end() && marker.position[1] == '\n') { + continue; + } + + if (column_count == column) { + start_ptr = &c; + break; + } + ++column_count; + } + + if (start_ptr == nullptr) { + return { 0, 0, marker.position }; + } + + return { line, column, _file_content.begin() + (start_ptr - _file_content.data()) }; +} diff --git a/src/openvic-simulation/definition/dataloader/diagnostic/TextSink.hpp b/src/openvic-simulation/definition/dataloader/diagnostic/TextSink.hpp new file mode 100644 index 000000000..75bbebd28 --- /dev/null +++ b/src/openvic-simulation/definition/dataloader/diagnostic/TextSink.hpp @@ -0,0 +1,131 @@ +#pragma once + +#include +#include + +#include + +#include +#include + +#include "openvic-simulation/core/memory/Formatting.hpp" +#include "openvic-simulation/core/template/Concepts.hpp" +#include "openvic-simulation/definition/dataloader/diagnostic/DiagnosticSink.hpp" + +namespace OpenVic::dataloader { + class DiagnosticItem; + + class TextSink : public DiagnosticSink { + public: + using memory_buffer_type = memory::fmt::basic_memory_buffer; + using text_iterator = memory::string::const_iterator; + + // enum class Color { + // RESET = 0, + // BOLD = 1, + // FAINT = 2, + // ITALIC = 3, + + // BLACK = 30, + // RED = 31, + // GREEN = 32, + // YELLOW = 33, + // BLUE = 34, + // MAGENTA = 35, + // CYAN = 36, + // WHITE = 37, + // }; + + void consume(DiagnosticItem const& item) override; + + void end() override; + + ~TextSink() override; + + protected: + virtual bool can_render_style() const = 0; + virtual void render_to(memory_buffer_type& buf) = 0; + + void render_text(memory_buffer_type& buf, std::string_view message); + void render_text(memory_buffer_type& buf, char c); + + void render_unicode_display(memory_buffer_type& buf, text_iterator begin, text_iterator end); + + void render_style(memory_buffer_type& buf, fmt::text_style ts); + void render_style_reset(memory_buffer_type& buf); + + void render_filepath(DiagnosticItem const& item, memory_buffer_type& buf); + + void render_empty_annotation(memory_buffer_type& buf); + + void render_annotation(DiagnosticItem const& item, memory_buffer_type& buf, std::string_view annotation); + + void render_level(DiagnosticItem const& item, memory_buffer_type& buf); + + void render_code_point(memory_buffer_type& buf, char32_t code_point); + + std::string_view get_column() const; + std::string_view get_underline_for(DiagnosticItem const& item) const; + fmt::text_style get_style_for(DiagnosticItem const& item) const; + + template + void render_formatted_text(memory_buffer_type& buf, fmt::format_string fmt, Args&&... args) { + if (!can_render_style()) { + memory_buffer_type fmt_buf; + auto out = fmt::appender(fmt_buf); + fmt::vformat_to(out, fmt, fmt::make_format_args(remove_style(args)...)); + render_text(buf, std::string_view { fmt_buf.data(), fmt_buf.size() }); + return; + } + + memory_buffer_type fmt_buf; + auto out = fmt::appender(fmt_buf); + fmt::vformat_to(out, fmt, fmt::make_format_args(args...)); + render_text(buf, std::string_view { fmt_buf.data(), fmt_buf.size() }); + } + + template + void render_formatted_text( + memory_buffer_type& buf, fmt::text_style ts, fmt::format_string fmt, Args&&... args + ) { + if (!can_render_style()) { + memory_buffer_type fmt_buf; + auto out = fmt::appender(fmt_buf); + fmt::vformat_to(out, fmt, fmt::make_format_args(remove_style(args)...)); + render_text(buf, std::string_view { fmt_buf.data(), fmt_buf.size() }); + return; + } + + memory_buffer_type fmt_buf; + auto out = fmt::appender(fmt_buf); + fmt::vformat_to(out, ts, fmt, fmt::make_format_args(args...)); + render_text(buf, std::string_view { fmt_buf.data(), fmt_buf.size() }); + } + + private: + memory::string _file_content; + + struct content_marker { + size_t line = 0; + size_t column = 0; + text_iterator position; + + constexpr bool is_invalid() const { + return line == 0 && column == 0; + } + }; + + bool open_file(std::string_view filepath); + content_marker find_in_content(size_t line, size_t column, content_marker begin = { 0, 0 }); + + template + static constexpr decltype(auto) remove_style(T&& value) { + using ref_type = std::remove_cvref_t; + if constexpr (specialization_of) { + return value.value; + } else { + return value; + } + } + }; +} diff --git a/src/openvic-simulation/definition/dataloader/dsl/As.hpp b/src/openvic-simulation/definition/dataloader/dsl/As.hpp new file mode 100644 index 000000000..8fe63a97a --- /dev/null +++ b/src/openvic-simulation/definition/dataloader/dsl/As.hpp @@ -0,0 +1,79 @@ +#pragma once + +#include + +#include + +#include + +#include +#include + +#include "openvic-simulation/core/Typedefs.hpp" +#include "openvic-simulation/definition/dataloader/dsl/Grammar.hpp" +#include "openvic-simulation/definition/dataloader/dsl/TraverseContext.hpp" + +namespace OpenVic::dataloader { + template + struct ValueParser; +} + +namespace OpenVic::dataloader::dsl { + template + struct _assign; + + template + struct _as; + + template + constexpr auto assignment(LeftRule&& left, RightRule&& right); + + // Forcefully optimizes _as stack allocation away + template + constexpr auto assignment(LeftRule&& left, _as) { + return _assign> { OV_FWD(left), {} }; + } + + template + struct _as { + static constexpr std::bool_constant>) != 0; }> + stores_value_parser {}; + + auto event_generate_parsers(TraverseContext& ctx) + requires(stores_value_parser()) + { + auto parser = ValueParser> {}; + if constexpr (requires { parser.make_preparation(ctx); }) { + parser.make_preparation(ctx); + } + return parser; + } + + template + bool event_sink(TraverseContext& ctx, TraverseData& data, Node const* node, Sink&& sink) const + requires invocable_sink_with + { + if constexpr (node_with_value_symbol) { + type_safe::deferred_construction value; + data.template parser_for>().parse(ctx, data, node, type_safe::out(value)); + if (!value.has_value()) { + return false; + } + + return sink(ctx, data, node, OV_MOV(value).value()); + } else { + return visit_flat( + ctx, + data, + node, + [this, sink = OV_FWD(sink)](TraverseContext& ctx, TraverseData& data, ast::FlatValue const* fv) { + return event_sink(ctx, data, fv, OV_FWD(sink)); + } + ); + } + } + }; + + template + inline constexpr _as as {}; +} diff --git a/src/openvic-simulation/definition/dataloader/dsl/Assignment.hpp b/src/openvic-simulation/definition/dataloader/dsl/Assignment.hpp new file mode 100644 index 000000000..7dbfad5f5 --- /dev/null +++ b/src/openvic-simulation/definition/dataloader/dsl/Assignment.hpp @@ -0,0 +1,205 @@ +#pragma once + +#include +#include +#include + +#include + +#include "openvic-simulation/core/Typedefs.hpp" +#include "openvic-simulation/definition/dataloader/dsl/Grammar.hpp" +#include "openvic-simulation/definition/dataloader/dsl/TraverseContext.hpp" + +namespace OpenVic::dataloader::dsl { + template + struct _assign; + + template + struct _as; + + template + constexpr auto assignment(LeftRule&& left, RightRule&& right) { + return _assign { OV_FWD(left), OV_FWD(right) }; + } + + template + struct _assign { + OV_NO_UNIQUE_ADDRESS LeftRule left; + OV_NO_UNIQUE_ADDRESS RightRule right; + + auto event_generate_parsers(TraverseContext& ctx) + requires rule_has_generate_parsers || rule_has_generate_parsers + { + if constexpr (rule_has_generate_parsers && rule_has_generate_parsers) { + using left_return_type = decltype(left.event_generate_parsers(ctx)); + using right_return_type = decltype(right.event_generate_parsers(ctx)); + + if constexpr (!std::is_void_v && !std::is_void_v) { + return std::make_tuple(left.event_generate_parsers(ctx), right.event_generate_parsers(ctx)); + } else if constexpr (std::is_void_v) { + right.event_generate_parsers(ctx); + return left.event_generate_parsers(ctx); + } else { + left.event_generate_parsers(ctx); + return right.event_generate_parsers(ctx); + } + } else if constexpr (rule_has_generate_parsers) { + return left.event_generate_parsers(ctx); + } else if constexpr (rule_has_generate_parsers) { + return right.event_generate_parsers(ctx); + } + } + + void event_prepare(TraverseContext& ctx) + requires rule_has_prepare || rule_has_prepare + { + if constexpr (rule_has_prepare) { + left.event_prepare(ctx); + } + + if constexpr (rule_has_prepare) { + right.event_prepare(ctx); + } + } + + template + auto event_match(TraverseContext& ctx, TraverseData& data, Node const* node) { + if constexpr (node_with_left && choice_rule) { + if (!left.event_match(ctx, data, node->left())) { + return false; + } + } + + if constexpr (node_with_right && choice_rule) { + if (!right.event_match(ctx, data, node->right())) { + return false; + } + } + + if constexpr (node_with_left || node_with_right) { + return true; + } else { + return visit_assign( + ctx, data, node, [this](TraverseContext& ctx, TraverseData& data, ast::AssignStatement const* as) { + return event_match(ctx, data, as); + } + ); + } + } + + template + auto event_process(TraverseContext& ctx, TraverseData& data, Node const* node) { + if constexpr (node_with_left && process_rule) { + if (!left.event_process(ctx, data, node->left())) { + return false; + } + } + + if constexpr (node_with_right && process_rule) { + if (!right.event_process(ctx, data, node->right())) { + return false; + } + } + + if constexpr (node_with_left || node_with_right) { + return true; + } else { + return visit_assign( + ctx, data, node, [this](TraverseContext& ctx, TraverseData& data, ast::AssignStatement const* as) { + return event_process(ctx, data, as); + } + ); + } + } + + template + std::string_view event_expected(TraverseContext& ctx, TraverseData& data, Node const* parent, bool seen) + requires rule_has_expected || rule_has_expected + { + if constexpr (rule_has_expected) { + if (std::string_view lstr = left.event_expected(ctx, data, parent, seen); !lstr.empty()) { + return lstr; + } + } + + if constexpr (rule_has_expected) { + if (std::string_view rstr = right.event_expected(ctx, data, parent, seen); !rstr.empty()) { + return rstr; + } + } + + return {}; + } + + template + bool event_duplicate( + TraverseContext& ctx, TraverseData& data, CurNode const* cur_node, ast::Node const* prev_node, DiagnosticLevel level + ) + requires rule_has_duplicate || rule_has_duplicate + { + if constexpr (rule_has_duplicate) { + if (!left.event_duplicate(ctx, data, cur_node, prev_node, level)) { + return false; + } + } + + if constexpr (rule_has_duplicate) { + if (!right.event_duplicate(ctx, data, cur_node, prev_node, level)) { + return false; + } + } + + return true; + } + + template + struct LeftSink { + Node const* node; + Sink sink; + RightRule& right; + + template + OV_ALWAYS_INLINE auto operator()(TraverseContext& ctx, TraverseData& data, N const*, auto&&... lhs) { + return right.event_sink( + ctx, + data, + node->right(), + [... lhs = OV_FWD(lhs), + sink = OV_FWD(sink)](TraverseContext& ctx, TraverseData& data, N2 const* node, auto&&... rhs) + requires invocable_sink_with + { + return OV_FWD(sink)(ctx, data, node, OV_FWD(lhs)..., OV_FWD(rhs)...); + } + ); + } + }; + + template + auto event_sink(TraverseContext& ctx, TraverseData& data, Node const* node, Sink&& sink) + requires(sink_rule || sink_rule) + { + /*if constexpr ( + node_with_left && node_with_right && sink_rule && sink_rule + ) { + return left.event_sink(ctx, node->left(), LeftSink { node, OV_FWD(sink), right }); + } else*/ + if constexpr (node_with_left && sink_rule) { + return left.event_sink(ctx, data, node->left(), OV_FWD(sink)); + } else if constexpr (node_with_right && sink_rule) { + return right.event_sink(ctx, data, node->right(), OV_FWD(sink)); + } else { + return visit_assign( + ctx, + data, + node, + [this, sink = OV_FWD(sink)](TraverseContext& ctx, TraverseData& data, ast::AssignStatement const* as) { + return event_sink(ctx, data, as, OV_FWD(sink)); + } + ); + } + } + }; + + template + struct rule_is_required<_assign> : rule_is_required {}; +} diff --git a/src/openvic-simulation/definition/dataloader/dsl/Diagnostics.hpp b/src/openvic-simulation/definition/dataloader/dsl/Diagnostics.hpp new file mode 100644 index 000000000..8058d9228 --- /dev/null +++ b/src/openvic-simulation/definition/dataloader/dsl/Diagnostics.hpp @@ -0,0 +1,180 @@ +#pragma once + +#include +#include + +#include + +#include + +#include + +#include "openvic-simulation/definition/dataloader/dsl/TraverseContext.hpp" + +namespace OpenVic::dataloader::diagnostic { + namespace ast = ovdl::v2script::ast; + + namespace code_type { + enum code_type_t : std::uint32_t { + // Expected NodeType + EXPECT_NODE_FILE_TREE_OR_LIST_VALUE = 0xC0100, + EXPECT_NODE_FLAT_VALUE = 0xC0101, + EXPECT_NODE_ASSIGN_STATEMENT = 0xC0102, + + // + TYPE_CONVERSION_FAIL = 0xC0200, + + // Expect Check + EXPECT_KEY = 0xC0300, + EXPECT_VALUE = 0xC0301, + + // Duplicate Check + FOUND_DUPLICATE_KEY = 0xC0400, + FOUND_DUPLICATE_NODE = 0xC0401, + FOUND_DUPLICATE_MAP_KEY = 0xC0402, + + // Unknown Check + FOUND_UNKNOWN_KEY = 0xC0500, + FOUND_UNKNOWN_NODE = 0xC0501 + }; + } + + inline void expected_file_tree_or_list_value(dsl::TraverseContext& ctx, ast::Node const* node) { + ctx.diagnostic.error(node) + .with_message("Expected a file tree or list value, found {}", get_kind_name(node->kind())) + .with_code(code_type::EXPECT_NODE_FILE_TREE_OR_LIST_VALUE); + } + + inline void expected_flat_value(dsl::TraverseContext& ctx, ast::Node const* node) { + ctx.diagnostic.error(node) + .with_message("Expected a flat value, found {}", get_kind_name(node->kind())) + .with_code(code_type::EXPECT_NODE_FLAT_VALUE); + } + + inline void expected_assign_statement(dsl::TraverseContext& ctx, ast::Node const* node) { + ctx.diagnostic.error(node) + .with_message("Expected a assign statement, found {}", get_kind_name(node->kind())) + .with_code(code_type::EXPECT_NODE_ASSIGN_STATEMENT); + } + + inline void type_conversion_failed(dsl::TraverseContext& ctx, ast::Node const* node, std::string_view type_name) { + ctx.diagnostic.error(node) + .with_message("Could not convert to type {}", type_name) + .with_code(code_type::TYPE_CONVERSION_FAIL); + } + + inline void expected_keys( + dsl::TraverseContext& ctx, ast::Node const* node, std::span expected_keys + ) { + if (expected_keys.size() == 1) { + ctx.diagnostic.error(node).with_message("Expected key: {}", expected_keys[0]).with_code(code_type::EXPECT_KEY); + return; + } + + ctx.diagnostic.error(node) + .with_message("Expected keys: [{}]", fmt::join(expected_keys, ", ")) + .with_code(code_type::EXPECT_KEY); + } + + inline void expected_values( + dsl::TraverseContext& ctx, ast::Node const* node, std::span expected_values + ) { + if (expected_values.size() == 1) { + ctx.diagnostic.error(node) + .with_message("Expected value: {}", expected_values[0]) + .with_code(code_type::EXPECT_VALUE); + return; + } + + ctx.diagnostic.error(node) + .with_message("Expected values: [{}]", fmt::join(expected_values, ", ")) + .with_code(code_type::EXPECT_VALUE); + } + + inline void found_duplicate_key( + dsl::TraverseContext& ctx, ast::Node const* current_node, ast::Node const* prev_node, dataloader::DiagnosticLevel level + ) { + dryad::visit_node_all( + current_node, + [&](ast::AssignStatement const* as) { + auto prev_key = dryad::visit_node_all( + prev_node, + [](ast::AssignStatement const* as) { + return as->left(); + }, + [](ast::Node const* node) { + return node; + } + ); + + if (auto const* fv = dryad::node_try_cast(as->left()); fv != nullptr) { + if (auto const* fv2 = dryad::node_try_cast(as->right()); fv2 != nullptr) { + ctx.diagnostic.report(level, fv) + .with_message("Duplicate key with value: [{}, {}]", fv->value().view(), fv2->value().view()) + .with_code(code_type::FOUND_DUPLICATE_KEY) + .hint(prev_key) + .with_message("previous key") + .with_code(code_type::FOUND_DUPLICATE_KEY); + return; + } + + ctx.diagnostic.report(level, fv) + .with_message("Duplicate key: {}", fv->value().view()) + .with_code(code_type::FOUND_DUPLICATE_KEY) + .hint(prev_key) + .with_message("previous key") + .with_code(code_type::FOUND_DUPLICATE_KEY); + } + }, + [&](ast::Node const* node) { + ctx.diagnostic.report(level, node) + .with_message("Duplicate node: {}", get_kind_name(node->kind())) + .with_code(code_type::FOUND_DUPLICATE_NODE) + .hint(prev_node) + .with_message("previous node") + .with_code(code_type::FOUND_DUPLICATE_NODE); + } + ); + } + + inline void found_duplicate_map_key(dsl::TraverseContext& ctx, ast::Node const* node, std::string_view key) { + dryad::visit_node_all( + node, + [&](ast::AssignStatement const* as) { + ctx.diagnostic.warning(as->left()) + .with_message("Duplicate map key failed: {}", key) + .with_code(code_type::FOUND_DUPLICATE_MAP_KEY); + }, + [&](ast::Node const* node) { + ctx.diagnostic.warning(node) + .with_message("Duplicate map key failed: {}", key) + .with_code(code_type::FOUND_DUPLICATE_MAP_KEY); + } + ); + } + + inline void found_unknown_key(dsl::TraverseContext& ctx, ast::Node const* node, dataloader::DiagnosticLevel level) { + dryad::visit_node_all( + node, + [&](ast::AssignStatement const* as) { + if (auto const* fv = dryad::node_try_cast(as->left()); fv != nullptr) { + if (auto const* fv2 = dryad::node_try_cast(as->right()); fv2 != nullptr) { + ctx.diagnostic.report(level, fv) + .with_message("Unknown key with value: [{}, {}]", fv->value().view(), fv2->value().view()) + .with_code(code_type::FOUND_UNKNOWN_KEY); + return; + } + + ctx.diagnostic.report(level, fv) + .with_message("Unknown key: {}", fv->value().view()) + .with_code(code_type::FOUND_UNKNOWN_KEY); + } + }, + [&](ast::Node const* node) { + ctx.diagnostic.report(level, node) + .with_message("Unknown node: {}", get_kind_name(node->kind())) + .with_code(code_type::FOUND_UNKNOWN_NODE); + } + ); + } +} diff --git a/src/openvic-simulation/definition/dataloader/dsl/Grammar.hpp b/src/openvic-simulation/definition/dataloader/dsl/Grammar.hpp new file mode 100644 index 000000000..8c238af70 --- /dev/null +++ b/src/openvic-simulation/definition/dataloader/dsl/Grammar.hpp @@ -0,0 +1,242 @@ +#pragma once + +#include +#include +#include +#include + +#include + +#include + +#include "openvic-simulation/core/Typedefs.hpp" +#include "openvic-simulation/core/template/Concepts.hpp" +#include "openvic-simulation/definition/dataloader/diagnostic/DiagnosticLevel.hpp" +#include "openvic-simulation/definition/dataloader/dsl/Diagnostics.hpp" + +namespace OpenVic::dataloader::dsl { + namespace ast = ovdl::v2script::ast; + + struct TraverseContext; + + template + concept node_with_value_symbol = requires(Node const* node) { + { node->value() } -> std::same_as>; + }; + + template + concept node_with_left = requires(Node const* node) { + { node->left() } -> std::same_as; + }; + + template + concept node_with_right = requires(Node const* node) { + { node->right() } -> std::same_as; + }; + + using node_type = ast::Node; + using statement_range_iterator = node_type::_children_range::iterator; + using statement_range_type = dryad::node_range; + + template + concept node_with_statements = requires(Node const* node) { + { node->statements() } -> std::same_as; + }; + + template + concept invocable = requires(F f, Args... args) { f(args...); }; + + template + concept invocable_r = requires(F f, Args... args) { + { f(args...) } -> std::same_as; + }; + + template + concept invocable_r_convertible = requires(F f, Args... args) { + { f(args...) } -> std::convertible_to; + }; + + struct fake_data { + template + struct FakeValueParser { + template + auto parse(dsl::TraverseContext&, TraverseData&, Node const*, auto) const { + return std::true_type {}; + } + }; + + template + constexpr FakeValueParser parser_for() { + return {}; + } + + template + constexpr FakeValueParser parser_for() const { + return {}; + } + }; + + template + concept invocable_sink_with = + invocable_r_convertible; + + template + concept choice_rule = requires(Rule rule, fake_data& data, TraverseContext& c, Node const* node) { + { rule.event_match(c, data, node) } -> std::convertible_to; + }; + + template + concept sink_rule = requires(Rule rule, fake_data& data, TraverseContext& c, Node const* node) { + { + rule.event_sink(c, data, node, [](TraverseContext&, fake_data&, ast::Node const*, auto&&...) { + return true; + }) + } -> std::convertible_to; + }; + + template + concept process_rule = requires(Rule rule, fake_data& data, TraverseContext& ctx, Node const* node) { + { rule.event_process(ctx, data, node) } -> std::convertible_to; + }; + + template + concept rule_has_key = requires( + Rule rule, fake_data& data, TraverseContext& ctx, ParentNode const* parent_node, bool seen + ) { + { rule.event_has_key(ctx, data, parent_node, seen) } -> std::convertible_to; + }; + + template + concept rule_has_expected = requires( + Rule rule, fake_data& data, TraverseContext& ctx, ParentNode const* parent_node, bool seen + ) { + { rule.event_expected(ctx, data, parent_node, seen) } -> std::convertible_to; + }; + + template + concept rule_has_duplicate = requires( + Rule rule, + fake_data& data, + TraverseContext& ctx, + CurNode const* cur_node, + ast::Node const* prev_node, + DiagnosticLevel level + ) { + { rule.event_duplicate(ctx, data, cur_node, prev_node, level) } -> std::convertible_to; + }; + + template + concept rule_has_prepare = requires(Rule rule, TraverseContext& ctx) { rule.event_prepare(ctx); }; + + template + concept rule_has_generate_parsers = requires(Rule rule, TraverseContext& ctx) { + { rule.event_generate_parsers(ctx) } -> not_same_as; + }; + + template + concept rule = choice_rule || sink_rule || process_rule; + + template + struct rule_is_required : std::false_type {}; + template + struct rule_is_required : rule_is_required {}; + template + struct rule_is_required : rule_is_required {}; + template + struct rule_is_required : rule_is_required {}; + + template + concept required_rule = rule_is_required::value; + + template + struct rule_disables_stop : std::false_type {}; + template + struct rule_disables_stop : rule_disables_stop {}; + template + struct rule_disables_stop : rule_disables_stop {}; + template + struct rule_disables_stop : rule_disables_stop {}; + + template + concept no_stop_rule = rule_disables_stop::value; + + struct empty {}; + + template + concept type_with_failable_try_emplace = requires(T reference, Key&& key, Args&&... args) { + typename T::iterator; + { reference.try_emplace(OV_FWD(key), OV_FWD(args)...) } -> std::same_as>; + }; + + OV_ALWAYS_INLINE std::false_type default_visit_fallback() { + return {}; + } + + template + OV_ALWAYS_INLINE auto visit_list_or_tree( + TraverseContext& ctx, TraverseData& data, Node const* node, F&& list_or_tree_func, Fallback&& fallback + ) { + return dryad::visit_node_all( + node, + [&, list_or_tree_func = OV_FWD(list_or_tree_func)](ast::FileTree const* ft) mutable { + return OV_FWD(list_or_tree_func)(ctx, data, ft); + }, + [&, list_or_tree_func = OV_FWD(list_or_tree_func)](ast::ListValue const* lv) mutable { + return OV_FWD(list_or_tree_func)(ctx, data, lv); + }, + [&, fallback = OV_FWD(fallback)](ast::Node const* n) mutable { + diagnostic::expected_file_tree_or_list_value(ctx, n); + return OV_FWD(fallback)(); + } + ); + } + + template + OV_ALWAYS_INLINE auto visit_list_or_tree( + TraverseContext& ctx, TraverseData& data, Node const* node, F&& list_or_tree_func + ) { + return visit_list_or_tree(ctx, data, node, OV_FWD(list_or_tree_func), default_visit_fallback); + } + + template + OV_ALWAYS_INLINE auto visit_assign( + TraverseContext& ctx, TraverseData& data, Node const* node, F&& assign_func, Fallback&& fallback + ) { + return dryad::visit_node_all( + node, + [&, assign_func = OV_FWD(assign_func)](ast::AssignStatement const* as) mutable { + return OV_FWD(assign_func)(ctx, data, as); + }, + [&, fallback = OV_FWD(fallback)](ast::Node const* n) mutable { + diagnostic::expected_assign_statement(ctx, n); + return OV_FWD(fallback)(); + } + ); + } + + template + OV_ALWAYS_INLINE auto visit_assign(TraverseContext& ctx, TraverseData& data, Node const* node, F&& assign_func) { + return visit_assign(ctx, data, node, OV_FWD(assign_func), default_visit_fallback); + } + + template + OV_ALWAYS_INLINE auto visit_flat( + TraverseContext& ctx, TraverseData& data, Node const* node, F&& flat_func, Fallback&& fallback + ) { + return dryad::visit_node_all( + node, + [&, flat_func = OV_FWD(flat_func)](ast::FlatValue const* v) mutable { + return OV_FWD(flat_func)(ctx, data, v); + }, + [&, fallback = OV_FWD(fallback)](ast::Node const* n) mutable { + diagnostic::expected_flat_value(ctx, n); + return OV_FWD(fallback)(); + } + ); + } + + template + OV_ALWAYS_INLINE auto visit_flat(TraverseContext& ctx, TraverseData& data, Node const* node, F&& flat_func) { + return visit_flat(ctx, data, node, OV_FWD(flat_func), default_visit_fallback); + } +} diff --git a/src/openvic-simulation/definition/dataloader/dsl/Key.hpp b/src/openvic-simulation/definition/dataloader/dsl/Key.hpp new file mode 100644 index 000000000..26ee19911 --- /dev/null +++ b/src/openvic-simulation/definition/dataloader/dsl/Key.hpp @@ -0,0 +1,143 @@ +#pragma once + +#include +#include + +#include +#include + +#include + +#include "openvic-simulation/core/string/StringLiteral.hpp" +#include "openvic-simulation/definition/dataloader/dsl/Assignment.hpp" +#include "openvic-simulation/definition/dataloader/dsl/Diagnostics.hpp" +#include "openvic-simulation/definition/dataloader/dsl/Grammar.hpp" +#include "openvic-simulation/definition/dataloader/dsl/TraverseContext.hpp" + +namespace OpenVic::dataloader::dsl { + template + struct _key : Rule { + ovdl::symbol<> symbol; + + void event_prepare(TraverseContext& ctx) { + symbol = ctx.find_intern(Key); + if constexpr (rule_has_prepare) { + Rule::event_prepare(ctx); + } + } + + template + auto event_match(TraverseContext& ctx, TraverseData& data, Node const* node) const { + if constexpr (node_with_value_symbol) { + if (!symbol) { + if (OV_likely(ctx.parser())) { + return false; + } + + if (Key != node->value().view()) { + return false; + } + } else if constexpr (node_with_value_symbol) { + if (symbol != node->value()) { + return false; + } + } + + if constexpr (choice_rule) { + if (!Rule::event_match(ctx, data, node)) { + return false; + } + } + + return true; + } else { + return visit_flat(ctx, data, node, [this](TraverseContext& ctx, TraverseData& data, ast::FlatValue const* fv) { + return event_match(ctx, data, fv); + }); + } + } + + template + requires rule_has_key + std::string_view event_expected(TraverseContext& ctx, TraverseData& data, Node const* parent_node, bool seen) const { + if (!Rule::event_has_key(ctx, data, parent_node, seen)) { + return Key; + } + return std::string_view {}; + } + + template + requires rule_has_duplicate + bool event_duplicate( + TraverseContext& ctx, TraverseData& data, CurNode const* cur_node, ast::Node const* prev_node, DiagnosticLevel level + ) const { + if (!Rule::event_duplicate(ctx, data, cur_node, prev_node, level)) { + diagnostic::found_duplicate_key(ctx, cur_node, prev_node, level); + return false; + } + + return true; + } + + template + constexpr auto operator=(RightRule&& right_rule) const { + return assignment(_key { *this }, OV_FWD(right_rule)); + } + }; + + struct _kexpect { + template + std::true_type event_process(TraverseContext&, TraverseData&, Node const*) const { + return {}; + } + + template + bool event_has_key(TraverseContext&, TraverseData&, Node const*, bool seen) const { + return seen; + } + + template + auto event_duplicate(TraverseContext&, TraverseData&, CurNode const*, ast::Node const*, DiagnosticLevel) const { + return std::false_type {}; + } + }; + + template<> + struct rule_is_required<_kexpect> : std::true_type {}; + + template + struct rule_is_required<_key> : rule_is_required {}; + + struct _kopt { + template + std::true_type event_process(TraverseContext&, TraverseData&, Node const*) const { + return {}; + } + + template + auto event_duplicate(TraverseContext&, TraverseData&, CurNode const*, ast::Node const*, DiagnosticLevel) const { + return std::false_type {}; + } + }; + + struct _kmany { + template + std::true_type event_process(TraverseContext&, TraverseData&, Node const*) const { + return {}; + } + + template + auto event_duplicate(TraverseContext&, TraverseData&, CurNode const*, ast::Node const*, DiagnosticLevel) const { + return std::true_type {}; + } + }; + + template + inline constexpr _key expect {}; + + template + inline constexpr _key optional {}; + + template + inline constexpr _key many {}; +} diff --git a/src/openvic-simulation/definition/dataloader/dsl/List.hpp b/src/openvic-simulation/definition/dataloader/dsl/List.hpp new file mode 100644 index 000000000..d15c6d1e2 --- /dev/null +++ b/src/openvic-simulation/definition/dataloader/dsl/List.hpp @@ -0,0 +1,250 @@ +#pragma once + +#include +#include +#include +#include +#include +#include + +#include + +#include + +#include "openvic-simulation/core/Typedefs.hpp" +#include "openvic-simulation/definition/dataloader/dsl/Diagnostics.hpp" +#include "openvic-simulation/definition/dataloader/dsl/Grammar.hpp" +#include "openvic-simulation/definition/dataloader/dsl/ListOptions.hpp" +#include "openvic-simulation/definition/dataloader/dsl/TraverseContext.hpp" +#include "openvic-simulation/definition/dataloader/dsl/detail/TupleCatUnique.hpp" + +namespace OpenVic::dataloader::dsl { + template + struct _sink_list; + + template + struct _list { + static constexpr std::bool_constant> has_pre_iterate_func {}; + + OV_NO_UNIQUE_ADDRESS std::tuple rules; + OV_NO_UNIQUE_ADDRESS std::conditional_t pre_iterate_func; + + auto event_generate_parsers(TraverseContext& ctx) { + if constexpr ((rule_has_generate_parsers || ...)) { + return detail::unique_event_generate_parsers(ctx, rules); + } + } + + void event_prepare(TraverseContext& ctx) + requires(rule_has_prepare || ...) + { + std::apply( + [&](auto&&... args) { + ( + [&](TraverseContext& ctx, Rule&& arg) { + if constexpr (rule_has_prepare) { + arg.event_prepare(ctx); + } + }(ctx, OV_FWD(args)), + ...); + }, + rules + ); + } + + template + auto event_match(TraverseContext& ctx, TraverseData& data, Node const* node) { + if constexpr (node_with_statements) { + return std::true_type {}; + } else { + return visit_list_or_tree( + ctx, data, node, [this](TraverseContext& ctx, TraverseData& data, N const* node) { + return event_match(ctx, data, node); + } + ); + } + } + + template + auto event_process(TraverseContext& ctx, TraverseData& data, Node const* node) { + return iterate_statements( + ctx, + data, + node, + []( + TraverseContext& ctx, + TraverseData& data, + N const* rule_node, + Rule& rule, + std::size_t rule_index, + bool& success, + std::bitset::reference seen + ) { + if constexpr (choice_rule) { + if (rule.event_match(ctx, data, rule_node)) { + seen = true; + if constexpr (process_rule) { + success = rule.event_process(ctx, data, rule_node); + } + if constexpr (!no_stop_rule) { + return true; + } + } + return false; + } else if constexpr (process_rule) { + seen = true; + success = rule.event_process(ctx, data, rule_node); + return std::bool_constant> {}; + } else { + return std::false_type {}; + } + } + ); + } + + template + auto iterate_statements(TraverseContext& ctx, TraverseData& data, Node const* node, Func&& func) { + if constexpr (node_with_statements) { + if constexpr (has_pre_iterate_func) { + OV_FWD(pre_iterate_func)(ctx, data, node); + } + + std::bitset seen; + bool success = true; + + for (ast::Statement const* stmt : node->statements()) { + std::size_t rule_index = 0; + bool matched_any = false; + + auto try_one = [&, func = OV_FWD(func)](Rule& rule) -> bool { + bool func_success = success; + bool stop = func(ctx, data, stmt, rule, rule_index, func_success, seen[rule_index]); + success = success && func_success; + matched_any = matched_any || stop; + ++rule_index; + return stop; + }; + + std::apply( + [&](auto&... rs) { + (try_one(rs) || ...); + }, + rules + ); + + if constexpr (Options.unknown_log_level != dataloader::DiagnosticLevel::NONE) { + if (!matched_any) { + diagnostic::found_unknown_key(ctx, stmt, Options.unknown_log_level); + } + } + } + + if constexpr ((rule_has_expected || ...)) { + memory::vector expected; + expected.reserve(sizeof...(Rules)); + + std::size_t index = 0; + auto check_missing = [&](Rule& rule) { + if constexpr (rule_has_expected) { + if (std::string_view expect = rule.event_expected(ctx, data, node, seen[index]); !expect.empty()) { + success = false; + expected.push_back(OV_MOV(expect)); + } + } + ++index; + }; + std::apply( + [&](auto&... rs) { + (check_missing(rs), ...); + }, + rules + ); + + if (!expected.empty()) { + diagnostic::expected_keys(ctx, node, expected); + } + } + + return success; + } else { + return visit_list_or_tree( + ctx, data, node, [this, func = OV_FWD(func)](TraverseContext& ctx, TraverseData& data, auto* node) { + return iterate_statements(ctx, data, node, OV_FWD(func)); + } + ); + } + } + + template PreFunc> + constexpr auto with_pre_iterate(PreFunc&& pre_func) { + return _list { .rules = OV_MOV(rules), .pre_iterate_func = OV_MOV(pre_func) }; + } + + template PreFunc> + constexpr auto sink(Func&& func, PreFunc&& pre_func) { + return with_pre_iterate(OV_FWD(pre_func)).sink(OV_FWD(func)); + } + + template + constexpr auto sink(Func&& func) { + static_assert( + requires { + _sink_list { + .list = { .rules = OV_MOV(rules), .pre_iterate_func = OV_MOV(pre_iterate_func) }, + .func = { OV_FWD(func) } + }; + }, "Requires #include \"openvic-simulation/definition/dataloader/dsl/ListSink.hpp\"" + ); + + return _sink_list { + .list = { .rules = OV_MOV(rules), .pre_iterate_func = OV_MOV(pre_iterate_func) }, .func = { OV_FWD(func) } + }; + } + + template + constexpr auto try_reserve_for(T& reference) { + if constexpr (requires(std::size_t s) { reference.reserve(s); }) { + auto reserve_func = [&reference = reference](TraverseContext& ctx, auto& root, ast::Node const* node) mutable { + statement_range_type range = visit_list_or_tree( + ctx, + root, + node->parent()->parent(), + [](TraverseContext const& ctx, auto, N const* node) { + return node->statements(); + }, + [] { + return dryad::make_node_range( + statement_range_iterator::from_ptr(nullptr), statement_range_iterator::from_ptr(nullptr) + ); + } + ); + + std::size_t index = 0; + for (auto _ : range) { + ++index; + } + reference.reserve(index); + }; + + return with_pre_iterate(OV_MOV(reserve_func)); + } else { + return *this; + } + } + }; + + template + constexpr auto list_with(Rules&&... rules) { + return _list { .rules = { OV_FWD(rules)... } }; + } + + template + requires(sizeof...(Rules) == 0 || ((rule || list_option_tag) || ...)) + constexpr auto list(Rules&&... rules) { + return [... rules = OV_FWD(rules)](std::index_sequence) mutable { + static constexpr auto opts = detail::get_options...>(); + auto all = std::forward_as_tuple(std::forward(rules)...); + return list_with(std::forward>(std::get(all))...); + }(detail::pure_index_sequence...>()); + } +} diff --git a/src/openvic-simulation/definition/dataloader/dsl/ListOptions.hpp b/src/openvic-simulation/definition/dataloader/dsl/ListOptions.hpp new file mode 100644 index 000000000..6b02fcac0 --- /dev/null +++ b/src/openvic-simulation/definition/dataloader/dsl/ListOptions.hpp @@ -0,0 +1,115 @@ +#pragma once + +#include +#include +#include + +#include "openvic-simulation/definition/dataloader/diagnostic/DiagnosticLevel.hpp" + +namespace OpenVic::dataloader::dsl { + struct ListOptions { + dataloader::DiagnosticLevel unknown_log_level = dataloader::DiagnosticLevel::WARN; + dataloader::DiagnosticLevel duplicate_log_level = dataloader::DiagnosticLevel::ERROR; + }; + + struct warn_for_unknown_t { + static constexpr ListOptions get_options() { + return { .unknown_log_level = dataloader::DiagnosticLevel::WARN }; + } + }; + + inline constexpr warn_for_unknown_t warn_for_unknown {}; + + struct silence_unknown_t { + static constexpr ListOptions get_options() { + return { .unknown_log_level = dataloader::DiagnosticLevel::NONE }; + } + }; + + inline constexpr silence_unknown_t silence_unknown {}; + + struct error_for_unknown_t { + static constexpr ListOptions get_options() { + return { .unknown_log_level = dataloader::DiagnosticLevel::ERROR }; + } + }; + + inline constexpr error_for_unknown_t error_for_unknown {}; + + template + struct report_unknown_as_t { + static constexpr ListOptions get_options() { + return { .unknown_log_level = Level }; + } + }; + + template + inline constexpr report_unknown_as_t report_unknown_as {}; + + template + struct set_list_options_t { + static constexpr ListOptions get_options() { + return Options; + } + }; + + template + inline constexpr set_list_options_t set_list_options {}; + + template + concept list_option_tag = requires { + { T::get_options() } -> std::same_as; + }; + + namespace detail { + template + consteval ListOptions get_options() { + ListOptions opts {}; + ( + [&opts]() mutable { + if constexpr (list_option_tag) { + opts = T::get_options(); + } + }.template operator()(), + ...); + return opts; + } + + template + consteval auto pure_index_sequence() { + constexpr std::array is_pure = [] { + if constexpr (sizeof...(Ts) != 0) { + return std::to_array({ !list_option_tag... }); + } else { + return std::array {}; + } + }(); + constexpr std::size_t N = sizeof...(Ts); + + constexpr std::size_t count = [&] { + std::size_t c = 0; + for (bool b : is_pure) { + if (b) { + ++c; + } + } + return c; + }(); + + constexpr auto indices = [&] { + std::array idx {}; + std::size_t out = 0; + for (std::size_t i = 0; i < N; ++i) { + if (is_pure[i]) { + idx[out++] = i; + } + } + return idx; + }(); + + return [&](std::index_sequence) { + return std::index_sequence {}; + }(std::make_index_sequence {}); + } + } +} diff --git a/src/openvic-simulation/definition/dataloader/dsl/ListSink.hpp b/src/openvic-simulation/definition/dataloader/dsl/ListSink.hpp new file mode 100644 index 000000000..52286a982 --- /dev/null +++ b/src/openvic-simulation/definition/dataloader/dsl/ListSink.hpp @@ -0,0 +1,178 @@ +#pragma once + +#include +#include +#include +#include +#include + +#include + +#include + +#include +#include + +#include "openvic-simulation/core/Typedefs.hpp" +#include "openvic-simulation/definition/dataloader/diagnostic/DiagnosticLevel.hpp" +#include "openvic-simulation/definition/dataloader/dsl/Grammar.hpp" +#include "openvic-simulation/definition/dataloader/dsl/List.hpp" +#include "openvic-simulation/definition/dataloader/dsl/ListOptions.hpp" +#include "openvic-simulation/definition/dataloader/dsl/TraverseContext.hpp" + +namespace OpenVic::dataloader::dsl { + template + struct _sink_list { + OV_NO_UNIQUE_ADDRESS _list list; + OV_NO_UNIQUE_ADDRESS Func func; + + auto event_generate_parsers(TraverseContext& ctx) { + list.event_generate_parsers(ctx); + } + + void event_prepare(TraverseContext& ctx) { + list.event_prepare(ctx); + } + + template + [[nodiscard]] auto call_func( + TraverseContext& ctx, + TraverseData& data, + Node const* node, + std::array const& nodes, + Acc&& acc + ) { + return call_func(ctx, data, node, nodes, OV_FWD(acc), OV_FWD(func)); + } + + template + [[nodiscard]] auto call_func( + TraverseContext& ctx, + TraverseData& data, + Node const* node, + std::array const& nodes, + Acc&& acc, + CallFunc&& func_to_call + ) { + static constexpr std::size_t N = sizeof...(Rules); + if constexpr (M == N) { + return std::apply( + [&, func_to_call = OV_FWD(func_to_call)](auto&&... args) + requires invocable_sink_with + { + return OV_FWD(func_to_call)(ctx, data, node, OV_FWD(args)...); + }, + OV_FWD(acc) + ); + } else { + using Rule = std::tuple_element_t>; + Rule& rule = std::get(this->list.rules); + + auto sink = [&, acc = OV_FWD(acc), func_to_call = OV_FWD(func_to_call)]( + TraverseContext& ctx, TraverseData& data, N const*, auto&&... value + ) { + return call_func( + ctx, + data, + node, + nodes, + std::tuple_cat(OV_FWD(acc), std::make_tuple(OV_FWD(value)...)), + OV_FWD(func_to_call) + ); + }; + + if constexpr (required_rule) { + // required keys are guaranteed present when we reach here + return rule.event_sink(ctx, data, nodes[M], OV_MOV(sink)); + } else { + // optional: include only when matched + if (nodes[M]) { + return rule.event_sink(ctx, data, nodes[M], OV_MOV(sink)); + } else { + return static_cast(call_func(ctx, data, node, nodes, OV_FWD(acc), OV_FWD(func_to_call))); + } + } + } + } + + template + auto event_process(TraverseContext& ctx, TraverseData& data, Node const* node) { + return event_sink(ctx, data, node, OV_FWD(func)); + } + + template + auto event_sink(TraverseContext& ctx, TraverseData& data, Node const* node, Sink&& sink) + requires(sizeof...(Rules) == 0 || (sink_rule || ...)) + { + if constexpr (sizeof...(Rules) != 0) { + std::array rule_associated_nodes {}; + + auto result = list.iterate_statements( + ctx, + data, + node, + [&]( + TraverseContext& ctx, + TraverseData& data, + N const* rule_node, + Rule& rule, + std::size_t rule_index, + bool& success, + std::bitset::reference seen + ) { + if constexpr (choice_rule && sink_rule) { + if (rule.event_match(ctx, data, rule_node)) { + seen = true; + if constexpr ( + Options.duplicate_log_level != DiagnosticLevel::NONE && rule_has_duplicate + ) { + if (rule_associated_nodes[rule_index] != nullptr) { + rule.event_duplicate( + ctx, data, rule_node, rule_associated_nodes[rule_index], Options.duplicate_log_level + ); + } + } + if (rule_associated_nodes[rule_index] == nullptr) { + rule_associated_nodes[rule_index] = rule_node; + } + success = true; + } else { + return false; + } + return !no_stop_rule; + } else if constexpr (sink_rule) { + seen = true; + if constexpr (Options.duplicate_log_level != DiagnosticLevel::NONE && rule_has_duplicate) { + if (rule_associated_nodes[rule_index] != nullptr) { + rule.event_duplicate( + ctx, data, rule_node, rule_associated_nodes[rule_index], Options.duplicate_log_level + ); + } + } + if (rule_associated_nodes[rule_index] == nullptr) { + rule_associated_nodes[rule_index] = rule_node; + } + success = true; + return std::bool_constant> {}; + } else { + return std::false_type {}; + } + } + ); + + return result && call_func(ctx, data, node->parent(), rule_associated_nodes, std::tuple {}, OV_FWD(sink)); + } else { + if constexpr (list.has_pre_iterate_func) { + if constexpr (node_with_statements) { + OV_FWD(list.has_pre_iterate_func)(ctx, data, node); + } else { + dsl::visit_list_or_tree(ctx, data, node, [this](TraverseContext& ctx, TraverseData& data, auto* node) { + OV_FWD(list.has_pre_iterate_func)(ctx, data, node); + }); + } + } + return OV_FWD(sink)(ctx, data, node); + } + } + }; +} diff --git a/src/openvic-simulation/definition/dataloader/dsl/Process.hpp b/src/openvic-simulation/definition/dataloader/dsl/Process.hpp new file mode 100644 index 000000000..008dee943 --- /dev/null +++ b/src/openvic-simulation/definition/dataloader/dsl/Process.hpp @@ -0,0 +1,22 @@ +#pragma once + +#include "openvic-simulation/core/Typedefs.hpp" +#include "openvic-simulation/definition/dataloader/dsl/Grammar.hpp" +#include "openvic-simulation/definition/dataloader/dsl/TraverseContext.hpp" + +namespace OpenVic::dataloader::dsl { + template + struct _processr { + OV_NO_UNIQUE_ADDRESS Func func; + + template + auto event_process(TraverseContext& ctx, TraverseData& data, Node const* node) { + return func(ctx, data, node); + } + }; + + template Func> + constexpr auto process_event(Func&& func) { + return _processr { OV_FWD(func) }; + } +} diff --git a/src/openvic-simulation/definition/dataloader/dsl/Ref.hpp b/src/openvic-simulation/definition/dataloader/dsl/Ref.hpp new file mode 100644 index 000000000..341637686 --- /dev/null +++ b/src/openvic-simulation/definition/dataloader/dsl/Ref.hpp @@ -0,0 +1,196 @@ +#pragma once + +#include +#include +#include + +#include + +#include + +#include + +#include "openvic-simulation/core/Typedefs.hpp" +#include "openvic-simulation/definition/dataloader/dsl/Diagnostics.hpp" +#include "openvic-simulation/definition/dataloader/dsl/Grammar.hpp" +#include "openvic-simulation/definition/dataloader/dsl/ListOptions.hpp" +#include "openvic-simulation/definition/dataloader/dsl/TraverseContext.hpp" +#include "openvic-simulation/definition/dataloader/dsl/ValueParser.hpp" + +namespace OpenVic::dataloader::dsl { + template + requires(sizeof...(Rules) == 0 || ((rule || list_option_tag) || ...)) + constexpr auto list(Rules&&... rules); + + template + struct _ref_setter; + + template + struct _ref_sink; + + template + struct _ref { + T& reference; + + template + requires(sizeof...(Rules) == 0 || ((rule || list_option_tag) || ...)) + constexpr auto try_reservable_for(Func&& func, Rules&&... rules) { + static_assert( + requires { list(OV_FWD(rules)...).try_reserve_for(reference).sink(OV_FWD(func)); }, + "Requires #include \"openvic-simulation/definition/dataloader/dsl/List.hpp\"" + ); + + return list(OV_FWD(rules)...).try_reserve_for(reference).sink(OV_FWD(func)); + } + + template + requires(sizeof...(Rules) == 0 || ((rule || list_option_tag) || ...)) + constexpr auto emplace_back(Rules&&... rules) + requires requires(T t) { + typename T::value_type; + t.emplace_back(std::declval()); + } + { + return try_reservable_for( + [&reference = this->reference]( + TraverseContext&, auto, ast::Node const*, Args&&... args + ) mutable + requires std::constructible_from + { + reference.emplace_back(OV_FWD(args)...); + return std::true_type {}; + }, + OV_FWD(rules)... + ); + } + + template + requires(sizeof...(Rules) == 0 || ((rule || list_option_tag) || ...)) + constexpr auto try_emplace(Rules&&... rules) + requires requires(T t) { + typename T::value_type; + typename T::key_type; + typename T::mapped_type; + t.try_emplace(std::declval(), std::declval()); + } + { + return try_reservable_for( + [&reference = this->reference]( + TraverseContext& ctx, auto, Node const* node, Key&& key, Args&&... args + ) mutable + requires(std::convertible_to || + std::constructible_from) && + std::constructible_from + { + if constexpr (type_with_failable_try_emplace) { + auto result = reference.try_emplace(OV_FWD(key), OV_FWD(args)...); + if (!result.second) { + diagnostic::found_duplicate_map_key(ctx, node, key); + } + return result.second; + } else { + reference.try_emplace(OV_FWD(key), OV_FWD(args)...); + return std::true_type {}; + } + }, + OV_FWD(rules)... + ); + } + + template + requires(sizeof...(Rules) == 0 || ((rule || list_option_tag) || ...)) + constexpr auto emplace(Rules&&... rules) + requires requires(T t) { + typename T::value_type; + t.emplace(std::declval()); + } + { + return try_reservable_for( + [&reference = this->reference](TraverseContext&, auto, ast::Node const*, auto&&... args) mutable + requires std::constructible_from + { + reference.emplace(OV_FWD(args)...); + return std::true_type {}; + }, + OV_FWD(rules)... + ); + } + + template + requires(sizeof...(Rules) == 0 || ((rule || list_option_tag) || ...)) + constexpr auto construct(Rules&&... rules) { + auto construct_func = [self = OV_MOV(*this)](TraverseContext&, auto, ast::Node const*, auto&&... args) mutable + requires requires { T(OV_FWD(args)...); } + { + self.reference = T(OV_FWD(args)...); + return std::true_type {}; + }; + + static_assert( + requires { list(OV_FWD(rules)...).sink(OV_MOV(construct_func)); }, + "Requires #include \"openvic-simulation/definition/dataloader/dsl/List.hpp\"" + ); + + return list(OV_FWD(rules)...).sink(OV_MOV(construct_func)); + } + + constexpr auto setter() { + return _ref_setter { reference }; + } + + constexpr auto sink() { + return _ref_sink { reference }; + } + }; + + template + constexpr auto ref(T& ref) { + return _ref { ref }; + } + + template + struct _ref_setter { + static_assert(requires { sizeof(ValueParser); }, "ValueParser must be defined."); + static constexpr std::bool_constant) != 0> stores_value_parser {}; + + T& reference; + + auto event_generate_parsers(TraverseContext& ctx) + requires(stores_value_parser()) + { + auto parser = ValueParser {}; + if constexpr (requires { parser.make_preparation(ctx); }) { + parser.make_preparation(ctx); + } + return parser; + } + + template + auto event_process(TraverseContext& ctx, TraverseData& data, Node const* node) { + if constexpr (node_with_value_symbol) { + return data.template parser_for().parse(ctx, data, node, type_safe::opt_ref(reference)); + } else { + return visit_flat(ctx, data, node, [this](TraverseContext& ctx, TraverseData& data, ast::FlatValue const* fv) { + return event_process(ctx, data, fv); + }); + } + } + + template + bool event_sink(TraverseContext& ctx, TraverseData& data, Node const* node, Sink&& sink) { + return event_process(ctx, data, node); + } + }; + + template + struct _ref_sink { + T const& reference; + + template + bool event_sink(TraverseContext& ctx, TraverseData& data, Node const* node, Sink&& sink) const + requires invocable_sink_with + { + return sink(ctx, data, node, reference); + } + }; +} diff --git a/src/openvic-simulation/definition/dataloader/dsl/Sink.hpp b/src/openvic-simulation/definition/dataloader/dsl/Sink.hpp new file mode 100644 index 000000000..053ed034b --- /dev/null +++ b/src/openvic-simulation/definition/dataloader/dsl/Sink.hpp @@ -0,0 +1,33 @@ +#pragma once + +#include + +#include "openvic-simulation/core/Typedefs.hpp" +#include "openvic-simulation/definition/dataloader/dsl/Grammar.hpp" +#include "openvic-simulation/definition/dataloader/dsl/TraverseContext.hpp" + +namespace OpenVic::dataloader::dsl { + template + struct _sinkr { + OV_NO_UNIQUE_ADDRESS Func func; + + template + auto event_sink(TraverseContext& ctx, TraverseData& data, Node const* node, Sink&& sink) { + return func(ctx, data, node, OV_FWD(sink)); + } + }; + + template + concept event_sink_function = requires(Func func, fake_data& data, TraverseContext& c, Node const* node) { + { + func(c, data, node, [](TraverseContext&, fake_data&, ast::Node const*, auto&&...) { + return true; + }) + } -> std::convertible_to; + }; + + template + constexpr auto sink_event(Func&& func) { + return _sinkr { OV_FWD(func) }; + } +} diff --git a/src/openvic-simulation/definition/dataloader/dsl/Symbol.hpp b/src/openvic-simulation/definition/dataloader/dsl/Symbol.hpp new file mode 100644 index 000000000..d73b7edf0 --- /dev/null +++ b/src/openvic-simulation/definition/dataloader/dsl/Symbol.hpp @@ -0,0 +1,218 @@ +#pragma once + +#include +#include + +#include +#include + +#include + +#include +#include + +#include "openvic-simulation/core/Typedefs.hpp" +#include "openvic-simulation/definition/dataloader/dsl/Assignment.hpp" +#include "openvic-simulation/definition/dataloader/dsl/Grammar.hpp" +#include "openvic-simulation/definition/dataloader/dsl/ListOptions.hpp" +#include "openvic-simulation/definition/dataloader/dsl/TraverseContext.hpp" +#include "openvic-simulation/definition/dataloader/dsl/ValueParser.hpp" + +namespace OpenVic::dataloader::dsl { + template + struct _assign; + + template + struct _symbol; + + template + constexpr auto assignment(LeftRule&& left, RightRule&& right); + + // Forcefully optimizes _symbol stack allocation away + template + constexpr auto assignment(LeftRule&& left, _symbol) { + return _assign> { OV_FWD(left), {} }; + } + + template> + struct _symbol { + static_assert(requires { sizeof(ValueParser); }, "ValueParser must be defined."); + static constexpr std::bool_constant) != 0; }> stores_value_parser {}; + + auto event_generate_parsers(TraverseContext& ctx) + requires(stores_value_parser()) + { + auto parser = ValueParser {}; + if constexpr (requires { parser.make_preparation(ctx); }) { + parser.make_preparation(ctx); + } + return parser; + } + + template + bool event_match(TraverseContext& ctx, TraverseData& data, Node const* node) const { + if constexpr (node_with_value_symbol) { + return std::true_type {}; + } else { + return visit_flat(ctx, data, node, [this](TraverseContext& ctx, TraverseData& data, ast::FlatValue const* fv) { + return event_match(ctx, data, fv); + }); + } + } + + template + bool event_process(TraverseContext& ctx, TraverseData& data, Node const* node) const { + if constexpr (node_with_value_symbol) { + return std::true_type {}; + } else { + return visit_flat(ctx, data, node, [this](TraverseContext& ctx, TraverseData& data, ast::FlatValue const* fv) { + return event_process(ctx, data, fv); + }); + } + } + + template + auto event_sink(TraverseContext& ctx, TraverseData& data, Node const* node, Sink&& sink) const + requires invocable_sink_with + { + if constexpr (node_with_value_symbol) { + if constexpr (std::same_as>) { + return sink(ctx, node, node->value()); + } else { + type_safe::deferred_construction value; + if (!data.template parser_for>().parse( + ctx, data, node, type_safe::out(value) + ) || + !value.has_value()) { + return false; + } + return static_cast(sink(ctx, data, node, OV_MOV(value).value())); + } + } else { + return visit_flat( + ctx, + data, + node, + [this, sink = OV_FWD(sink)](TraverseContext& ctx, TraverseData& data, ast::FlatValue const* fv) { + return event_sink(ctx, data, fv, OV_FWD(sink)); + } + ); + } + } + + template + constexpr auto operator=(RightRule&& right_rule) const { + return assignment(_symbol {}, OV_FWD(right_rule)); + } + }; + + inline constexpr _symbol<> symbol {}; + + template + inline constexpr _symbol symbol_as {}; + + template + struct _sink_list; + + template + struct _sym_assign_sink_list : _sink_list { + using base_type = _sink_list; + + template + auto event_match(TraverseContext& ctx, TraverseData& data, Node const* node) { + if constexpr (node_with_left) { + if (!_symbol {}.event_match(ctx, data, node->left())) { + return false; + } + } + + if constexpr (node_with_right && choice_rule) { + if (!base_type::event_match(ctx, data, node->right())) { + return false; + } + } + + if constexpr (node_with_left || node_with_right) { + return true; + } else { + return visit_assign( + ctx, data, node, [this](TraverseContext& ctx, TraverseData& data, ast::AssignStatement const* as) { + return event_match(ctx, data, as); + } + ); + } + } + + template + auto event_process(TraverseContext& ctx, TraverseData& data, Node const* node) { + if constexpr (node_with_right) { + if (!event_sink(ctx, data, node, OV_MOV(this->func))) { + return false; + } + } + + if constexpr (node_with_left || node_with_right) { + return true; + } else { + return visit_assign( + ctx, data, node, [this](TraverseContext& ctx, TraverseData& data, ast::AssignStatement const* as) { + return event_process(ctx, data, as); + } + ); + } + } + + template + auto event_sink(TraverseContext& ctx, TraverseData& data, Node const* node, Sink&& sink) { + if constexpr (node_with_left) { + bool parse_succeed = false; + auto parse_value = [&] { + type_safe::deferred_construction value; + parse_succeed = data.template parser_for>().parse( + ctx, data, node->left(), type_safe::out(value) + ); + return value; + }; + + if constexpr (node_with_right && sink_rule) { + return static_cast(base_type::event_sink( + ctx, + data, + node->right(), + [value = parse_value(), parse_succeed, sink = OV_FWD(sink)]( + TraverseContext& ctx, TraverseData& data, N const* node, auto&&... input + ) mutable + requires invocable_sink_with + { + if (!parse_succeed || !value.has_value()) { + return false; + } + return OV_FWD(sink)(ctx, data, node, OV_MOV(value).value(), OV_FWD(input)...); + } + )); + } else { + return false; + } + } else { + return visit_assign( + ctx, + data, + node, + [this, sink = OV_FWD(sink)](TraverseContext& ctx, TraverseData& data, ast::AssignStatement const* as) { + return event_sink(ctx, data, as, OV_FWD(sink)); + } + ); + } + } + }; + + template + constexpr auto assignment(_symbol, _sink_list&& right) { + static_assert( + requires { _sym_assign_sink_list { OV_MOV(right) }; }, + "Requires #include \"openvic-simulation/definition/dataloader/dsl/ListSink.hpp\"" + ); + + return _sym_assign_sink_list { OV_MOV(right) }; + } +} diff --git a/src/openvic-simulation/definition/dataloader/dsl/Traverse.hpp b/src/openvic-simulation/definition/dataloader/dsl/Traverse.hpp new file mode 100644 index 000000000..071d490fe --- /dev/null +++ b/src/openvic-simulation/definition/dataloader/dsl/Traverse.hpp @@ -0,0 +1,109 @@ +#pragma once + +#include +#include + +#include + +#include "openvic-simulation/core/Typedefs.hpp" +#include "openvic-simulation/definition/dataloader/diagnostic/DiagnosticBag.hpp" +#include "openvic-simulation/definition/dataloader/dsl/Grammar.hpp" +#include "openvic-simulation/definition/dataloader/dsl/TraverseContext.hpp" +#include "openvic-simulation/definition/dataloader/dsl/ValueParser.hpp" + +namespace OpenVic::dataloader::dsl { + template + struct Traverse_t { + static constexpr auto has_tree_rule = !std::same_as; + + std::conditional_t tree_rule; + + struct TraverseStackData { + using prepare_result_type = + decltype(std::declval().event_generate_parsers(std::declval())); + static constexpr std::bool_constant> has_prepare_result_type {}; + + OV_NO_UNIQUE_ADDRESS std::conditional_t parsers; + + template + struct has_type; + + template + struct has_type : std::false_type {}; + + template + struct has_type : std::false_type {}; + + template + struct empty_test { + char c; + OV_NO_UNIQUE_ADDRESS T t; + }; + + template + static constexpr bool can_be_zero_bytes = sizeof(empty_test) == 1; + + template + struct has_type> : std::disjunction...> {}; + + template + auto parser_for() { + if constexpr (has_type, prepare_result_type>::value) { + return std::get>(parsers); + } else { + static_assert(requires { sizeof(ValueParser); }, "ValueParser must be defined."); + return ValueParser {}; + } + } + + template + auto parser_for() const { + if constexpr (has_type, prepare_result_type>::value) { + return std::get>(parsers); + } else { + static_assert(requires { sizeof(ValueParser); }, "ValueParser must be defined."); + return ValueParser {}; + } + } + }; + + template + constexpr auto operator()(NewTreeRule&& rule) const + requires(!has_tree_rule) + { + return Traverse_t { OV_FWD(rule) }; + } + + TraverseContext operator()(ovdl::v2script::Parser const& parser, node_type const* node) { + return operator()(&parser, node); + } + + TraverseContext operator()(ovdl::v2script::Parser const& parser) { + return operator()(&parser, parser.get_file_node()); + } + + TraverseContext operator()(node_type const* node) { + return operator()(nullptr, node); + } + + TraverseContext operator()(ovdl::v2script::Parser const* parser, node_type const* node) { + TraverseContext ctx { DiagnosticBag { parser } }; + if constexpr (has_tree_rule) { + TraverseStackData data = [&] { + if constexpr (rule_has_generate_parsers && TraverseStackData::has_prepare_result_type) { + return TraverseStackData { tree_rule.event_generate_parsers(ctx) }; + } else { + return TraverseStackData {}; + } + }(); + if constexpr (rule_has_prepare) { + tree_rule.event_prepare(ctx); + } + tree_rule.event_process(ctx, data, node); + } + return ctx; + } + }; + + inline constexpr Traverse_t Traverse {}; +} diff --git a/src/openvic-simulation/definition/dataloader/dsl/TraverseContext.hpp b/src/openvic-simulation/definition/dataloader/dsl/TraverseContext.hpp new file mode 100644 index 000000000..1290f084d --- /dev/null +++ b/src/openvic-simulation/definition/dataloader/dsl/TraverseContext.hpp @@ -0,0 +1,26 @@ +#pragma once + +#include + +#include +#include + +#include "openvic-simulation/definition/dataloader/diagnostic/DiagnosticBag.hpp" + +namespace OpenVic::dataloader::dsl { + struct TraverseContext { + dataloader::DiagnosticBag diagnostic; + + ovdl::v2script::Parser const* parser() const { + return diagnostic.parser(); + } + + ovdl::symbol<> find_intern(std::string_view view) const { + if (parser()) { + return parser()->find_intern(view); + } else { + return {}; + } + } + }; +} diff --git a/src/openvic-simulation/definition/dataloader/dsl/ValueParser.hpp b/src/openvic-simulation/definition/dataloader/dsl/ValueParser.hpp new file mode 100644 index 000000000..54869021b --- /dev/null +++ b/src/openvic-simulation/definition/dataloader/dsl/ValueParser.hpp @@ -0,0 +1,199 @@ +#pragma once + +#include +#include +#include +#include + +#include +#include + +#include +#include + +#include "openvic-simulation/core/Typedefs.hpp" +#include "openvic-simulation/core/object/FixedPoint.hpp" +#include "openvic-simulation/core/object/FixedPoint/String.hpp" +#include "openvic-simulation/core/string/StringLiteral.hpp" +#include "openvic-simulation/definition/dataloader/dsl/Diagnostics.hpp" +#include "openvic-simulation/definition/dataloader/dsl/Grammar.hpp" +#include "openvic-simulation/definition/dataloader/dsl/TraverseContext.hpp" + +namespace OpenVic::dataloader { + namespace ast = ovdl::v2script::ast; + + template + struct ValueParser; + + template + struct ValueParser> { + static_assert(requires { sizeof(ValueParser); }, "ValueParser must be defined."); + + OV_NO_UNIQUE_ADDRESS ValueParser parser; + + void make_preparation(dsl::TraverseContext& ctx) + requires(sizeof(parser) != 0) + { + if constexpr (requires { parser.make_preparation(ctx); }) { + parser.make_preparation(ctx); + } + } + + template + auto parse(dsl::TraverseContext& ctx, TraverseData& data, Node const* value, type_safe::output_parameter out) const { + if constexpr (std::is_trivially_default_constructible_v && std::is_trivially_destructible_v) { + T tmp; + if (!parser.parse(ctx, data, value, type_safe::opt_ref(&tmp))) { + return false; + } + out = OV_MOV(tmp); + return true; + } else { + alignas(T) std::array bytes; + + static constexpr auto dtor = [](T* ptr) { + ptr->~T(); + }; + std::unique_ptr result { new (bytes.data()) T }; + + if (!parser.parse(ctx, data, value, type_safe::opt_ref(result.get()))) { + return false; + } + + if (!result) { + return false; + } + + out = OV_MOV(*result); + return true; + } + } + }; + + template<> + struct ValueParser> { + template + bool parse( + dsl::TraverseContext& ctx, TraverseData& data, Node const* value, type_safe::optional_ref> in_out + ) const { + if constexpr (dsl::node_with_value_symbol) { + if (in_out.has_value()) { + in_out.value() = value->value(); + } + return std::true_type {}; + } else { + return dsl::visit_flat( + ctx, data, value, [this, in_out](dsl::TraverseContext& ctx, TraverseData& data, ast::FlatValue const* fv) { + return parse(ctx, data, fv, in_out); + } + ); + } + } + }; + + template<> + struct ValueParser { + template + auto parse( + dsl::TraverseContext& ctx, TraverseData& data, Node const* value, type_safe::optional_ref in_out + ) const { + ovdl::symbol<> symbol; + if (!ValueParser> {}.parse(ctx, data, value, type_safe::opt_ref(symbol))) { + return false; + } + + if (in_out.has_value()) { + in_out.value() = symbol.view(); + } + + return true; + } + }; + + template<> + struct ValueParser { + template + static auto parse( + dsl::TraverseContext& ctx, TraverseData& data, Node const* value, type_safe::optional_ref in_out + ) { + std::string_view sv; + if (!data.template parser_for().parse(ctx, data, value, type_safe::opt_ref(sv))) { + return false; + } + + fixed_point_t result; + std::from_chars_result from = fp::from_chars(result, sv.data(), sv.data() + sv.size()); + if (from.ec != std::errc {}) { + diagnostic::type_conversion_failed(ctx, value, "OpenVic::fixed_point_t"); + return false; + } + + in_out.value() = OV_MOV(result); + return true; + } + }; + + template + struct KeyValue { + static constexpr auto key = Key; + static constexpr auto value = Value; + }; + + template + inline static constexpr auto KV = KeyValue {}; + + template + struct ValueMapper { + static constexpr std::integral_constant size {}; + static constexpr std::array keys() { + return { std::string_view { KVs.key }... }; + } + + std::array, size> symbols {}; + + void make_preparation(dsl::TraverseContext& ctx) { + std::size_t i = 0; + ((symbols[i++] = ctx.find_intern(KVs.key)), ...); + } + + template + auto parse(dsl::TraverseContext& ctx, TraverseData& data, Node const* value, type_safe::optional_ref in_out) const { + if (OV_unlikely(ctx.parser() == nullptr)) { + std::string_view sv; + if (!data.template parser_for().parse(ctx, data, value, type_safe::opt_ref(sv))) { + return false; + } + + bool success = false; + std::size_t i = 0; + // first matching key wins + ((sv == KVs.key && (in_out.value() = KVs.value, success = true, true)) || ...); + + if (!success) { + diagnostic::expected_values(ctx, value, keys()); + } + + return success; + } + + ovdl::symbol<> symbol; + if (!data.template parser_for>().parse(ctx, data, value, type_safe::opt_ref(symbol))) { + return false; + } + + bool success = false; + std::size_t i = 0; + // first matching key wins + ((symbol && symbol == symbols[i++] && (in_out.value() = KVs.value, success = true, true)) || ...); + + if (!success) { + diagnostic::expected_values(ctx, value, keys()); + } + + return success; + } + }; + + template<> + struct ValueParser : ValueMapper, KV<"no", false>> {}; +} diff --git a/src/openvic-simulation/definition/dataloader/dsl/detail/TupleCatUnique.hpp b/src/openvic-simulation/definition/dataloader/dsl/detail/TupleCatUnique.hpp new file mode 100644 index 000000000..543d9248b --- /dev/null +++ b/src/openvic-simulation/definition/dataloader/dsl/detail/TupleCatUnique.hpp @@ -0,0 +1,138 @@ +#pragma once + +#include +#include +#include +#include + +#include "openvic-simulation/definition/dataloader/dsl/TraverseContext.hpp" + +namespace OpenVic::dataloader::dsl::detail { + template + struct type_list {}; + + template + inline constexpr bool contains_v = false; + template + inline constexpr bool contains_v> = (std::is_same_v || ...); + + template + struct append_unique { + using type = List; + }; + template + struct append_unique, T> { + using type = std::conditional_t>, type_list, type_list>; + }; + + template + struct to_list; + template + struct to_list> { + using type = type_list...>; + }; + + template + struct contrib { + static constexpr bool is_void = false; + static constexpr bool has_prepare = false; + using type = std::tuple<>; + }; + + template + requires requires(Rule& r, TraverseContext& c) { r.event_generate_parsers(c); } + struct contrib { + using R = decltype(std::declval().event_generate_parsers(std::declval())); + static constexpr bool is_void = std::is_void_v; + static constexpr bool has_prepare = true; + + using type = std::conditional_t, std::conditional_t()); + }, R, std::tuple>>; + }; + + // Decide which indices to call + template + struct decide; + + template + struct decide { + using indices = std::index_sequence<>; + }; + + template + struct decide { + using Rule = std::tuple_element_t; + using C = contrib; + using contrib_t = typename C::type; + using CL = typename to_list::type; + + // new types? + static constexpr bool has_new = [](type_list) { + if constexpr (sizeof...(Us) == 0) { + return false; + } else { + return (!contains_v || ...); + } + }(CL {}); + + // Call when: + // - the rule has a prepare function AND + // - (it returns void OR it introduces a new type) + static constexpr bool call_it = C::has_prepare && (C::is_void || has_new); + + // next Seen (only non-void contributions update Seen) + template + struct app; + template + struct app { + using type = L; + }; + template + struct app { + using type = typename app::type, Us...>::type; + }; + template + static auto next(type_list) -> typename app::type; + using next_seen = std::conditional_t< + C::is_void, + Seen, // void does not change Seen + decltype(next(CL {}))>; + + using rest = decide; + + template + static auto prepend(std::index_sequence) -> std::index_sequence; + + using indices = std::conditional_t; + }; + + // Call only the selected indices and concatenate + template + auto call_indices(TraverseContext& ctx, RulesTuple& rules, std::index_sequence) { + auto call = [&]() { + auto& rule = std::get(rules); + if constexpr (requires { rule.event_generate_parsers(ctx); }) { + using Ret = decltype(rule.event_generate_parsers(ctx)); + if constexpr (std::is_void_v) { + rule.event_generate_parsers(ctx); + return std::tuple {}; + } else if constexpr (requires { std::tuple_cat(std::tuple {}, rule.event_generate_parsers(ctx)); }) { + return rule.event_generate_parsers(ctx); + } else { + return std::tuple { rule.event_generate_parsers(ctx) }; + } + } else { + return std::tuple {}; + } + }; + return std::tuple_cat(call.template operator()()...); + } + + template + auto unique_event_generate_parsers(TraverseContext& ctx, RulesTuple& rules) { + constexpr std::size_t N = std::tuple_size_v; + using indices = typename decide, RulesTuple, 0, N>::indices; + return call_indices(ctx, rules, indices {}); + } +} diff --git a/tests/src/definition/dataloader/diagnostic/DiagnosticBag.cpp b/tests/src/definition/dataloader/diagnostic/DiagnosticBag.cpp new file mode 100644 index 000000000..0612e19cf --- /dev/null +++ b/tests/src/definition/dataloader/diagnostic/DiagnosticBag.cpp @@ -0,0 +1 @@ +#include "openvic-simulation/definition/dataloader/diagnostic/DiagnosticBag.hpp" diff --git a/tests/src/definition/dataloader/diagnostic/DiagnosticItem.cpp b/tests/src/definition/dataloader/diagnostic/DiagnosticItem.cpp new file mode 100644 index 000000000..7a6973fdd --- /dev/null +++ b/tests/src/definition/dataloader/diagnostic/DiagnosticItem.cpp @@ -0,0 +1 @@ +#include "openvic-simulation/definition/dataloader/diagnostic/DiagnosticItem.hpp" diff --git a/tests/src/definition/dataloader/diagnostic/DiagnosticRenderer.cpp b/tests/src/definition/dataloader/diagnostic/DiagnosticRenderer.cpp new file mode 100644 index 000000000..d4cfb67d2 --- /dev/null +++ b/tests/src/definition/dataloader/diagnostic/DiagnosticRenderer.cpp @@ -0,0 +1 @@ +#include "openvic-simulation/definition/dataloader/diagnostic/DiagnosticRenderer.hpp" diff --git a/tests/src/definition/dataloader/dsl/Traverse.cpp b/tests/src/definition/dataloader/dsl/Traverse.cpp new file mode 100644 index 000000000..36344feb9 --- /dev/null +++ b/tests/src/definition/dataloader/dsl/Traverse.cpp @@ -0,0 +1 @@ +#include "openvic-simulation/definition/dataloader/dsl/Traverse.hpp"