diff options
author | Spartan322 <Megacake1234@gmail.com> | 2023-07-28 06:52:00 +0200 |
---|---|---|
committer | Spartan322 <Megacake1234@gmail.com> | 2023-09-02 14:28:21 +0200 |
commit | 7440a5d1433eec4bf87e3723022db187e7f61b1a (patch) | |
tree | 2bb062c320fa2227b18956617b94d0e8800420d8 /src/openvic-dataloader/v2script | |
parent | e941573f47fb867ff75c8a2cf78302b754ffbeee (diff) |
Rework Grammar and Parser
Add proper headless binary construction:
Includes basic validation
Add Error and Warning structs to Parser
Add FileNode pointer getter to Parser
Change all `char8_t*` and `const char8_t` to `const char*` in Parser
Add Parser move operators and Parser deconstructor
Add BufferHandler PIMPL object to Parser
Add UTF-8 file Warning to v2script
Add proper Grammar value retrieval
Add AbstractSyntaxTree for v2script data parser:
Has compile-time embedded type information accessible at compile-time and runtime
Has Tab-based print functionality
Fix wrong environment reference for headless construction in SConstruct
Add error retrieval
Add BasicCallbackOStreamBuffer for callback streaming
Add CallbackStreamBuffer for char
Add CallbackWStreamBuffer for wchar_t
Add BasicCallbackStream
Add CallbackStream for char
Add CallbackWStream for wchar_t
Add grammar for events and decisions
Add event_parse to Parser
Add decision_parse to Parser
Add .clang-format
Ignore dirty lexy module
Add CSV parser and grammar:
Creates std::vector<csv::LineObject> for a list of lines
Add BasicParser and BasicBufferHandler to reduce code reduplication
Diffstat (limited to 'src/openvic-dataloader/v2script')
-rw-r--r-- | src/openvic-dataloader/v2script/AbstractSyntaxTree.cpp | 247 | ||||
-rw-r--r-- | src/openvic-dataloader/v2script/AiBehaviorGrammar.hpp | 34 | ||||
-rw-r--r-- | src/openvic-dataloader/v2script/DecisionGrammar.hpp | 128 | ||||
-rw-r--r-- | src/openvic-dataloader/v2script/EffectGrammar.hpp | 42 | ||||
-rw-r--r-- | src/openvic-dataloader/v2script/EventGrammar.hpp | 183 | ||||
-rw-r--r-- | src/openvic-dataloader/v2script/Grammar.cpp | 74 | ||||
-rw-r--r-- | src/openvic-dataloader/v2script/ModifierGrammar.hpp | 33 | ||||
-rw-r--r-- | src/openvic-dataloader/v2script/Parser.cpp | 224 | ||||
-rw-r--r-- | src/openvic-dataloader/v2script/SimpleGrammar.hpp | 120 | ||||
-rw-r--r-- | src/openvic-dataloader/v2script/TriggerGrammar.hpp | 42 |
10 files changed, 1053 insertions, 74 deletions
diff --git a/src/openvic-dataloader/v2script/AbstractSyntaxTree.cpp b/src/openvic-dataloader/v2script/AbstractSyntaxTree.cpp new file mode 100644 index 0000000..f9fb716 --- /dev/null +++ b/src/openvic-dataloader/v2script/AbstractSyntaxTree.cpp @@ -0,0 +1,247 @@ +#include <iomanip> +#include <sstream> +#include <string> +#include <utility> +#include <vector> + +#include <stddef.h> + +#include <openvic-dataloader/v2script/AbstractSyntaxTree.hpp> + +using namespace ovdl::v2script::ast; + +void ovdl::v2script::ast::copy_into_node_ptr_vector(const std::vector<NodePtr>& source, std::vector<NodeUPtr>& dest) { + dest.clear(); + dest.reserve(source.size()); + for (auto&& p : source) { + dest.push_back(NodeUPtr { p }); + } +} + +AbstractStringNode::AbstractStringNode(std::string&& name) : _name(std::move(name)) {} +std::ostream& AbstractStringNode::print(std::ostream& stream, size_t indent) const { + return stream << _name; +} + +#define OVDL_AST_STRING_NODE_DEF(NAME, ...) \ + NAME::NAME(std::string&& name) : AbstractStringNode(std::move(name)) {} \ + std::ostream& NAME::print(std::ostream& stream, size_t indent) const __VA_ARGS__ + +OVDL_AST_STRING_NODE_DEF(IdentifierNode, { + return stream << _name; +}); + +OVDL_AST_STRING_NODE_DEF(StringNode, { + return stream << '"' << _name << '"'; +}); + +OVDL_AST_STRING_NODE_DEF(FactorNode, { + return stream << "factor = " << _name; +}); + +OVDL_AST_STRING_NODE_DEF(MonthNode, { + return stream << "months = " << _name; +}); + +OVDL_AST_STRING_NODE_DEF(NameNode, { + return stream << "name = " << _name; +}); + +OVDL_AST_STRING_NODE_DEF(FireOnlyNode, { + return stream << "fire_only_once = " << _name; +}); + +OVDL_AST_STRING_NODE_DEF(IdNode, { + return stream << "id = " << _name; +}); + +OVDL_AST_STRING_NODE_DEF(TitleNode, { + return stream << "title = " << _name; +}); + +OVDL_AST_STRING_NODE_DEF(DescNode, { + return stream << "desc = " << _name; +}); + +OVDL_AST_STRING_NODE_DEF(PictureNode, { + return stream << "picture = " << _name; +}); + +OVDL_AST_STRING_NODE_DEF(IsTriggeredNode, { + return stream << "is_triggered_only = " << _name; +}); + +#undef OVDL_AST_STRING_NODE_DEF + +AssignNode::AssignNode(NodeCPtr name, NodePtr init) + : _initializer(std::move(init)) { + if (name->is_type<IdentifierNode>()) { + _name = cast_node_cptr<IdentifierNode>(name)._name; + } +} + +std::ostream& Node::print_ptr(std::ostream& stream, NodeCPtr node, size_t indent) { + return node != nullptr ? node->print(stream, indent) : stream << "<NULL>"; +} + +static std::ostream& print_newline_indent(std::ostream& stream, size_t indent) { + return stream << "\n" + << std::setw(indent) << std::setfill('\t') << ""; +} + +/* Starts with a newline and ends at the end of a line, and so + * should be followed by a call to print_newline_indent. + */ +static std::ostream& print_nodeuptr_vector(const std::vector<NodeUPtr>& nodes, + std::ostream& stream, size_t indent) { + for (NodeUPtr const& node : nodes) { + print_newline_indent(stream, indent); + Node::print_ptr(stream, node.get(), indent); + } + return stream; +} + +AbstractListNode::AbstractListNode(const std::vector<NodePtr>& statements) { + copy_into_node_ptr_vector(statements, _statements); +} +std::ostream& AbstractListNode::print(std::ostream& stream, size_t indent) const { + stream << '{'; + if (!_statements.empty()) { + print_nodeuptr_vector(_statements, stream, indent + 1); + print_newline_indent(stream, indent); + } + return stream << "}"; +} + +#define OVDL_AST_LIST_NODE_DEF(NAME, ...) \ + NAME::NAME(const std::vector<NodePtr>& statements) : AbstractListNode(statements) {} \ + std::ostream& NAME::print(std::ostream& stream, size_t indent) const __VA_ARGS__ + +OVDL_AST_LIST_NODE_DEF(FileNode, { + print_nodeuptr_vector(_statements, stream, indent); + return print_newline_indent(stream, indent); +}); + +OVDL_AST_LIST_NODE_DEF(ListNode, { + stream << '{'; + if (!_statements.empty()) { + print_nodeuptr_vector(_statements, stream, indent + 1); + print_newline_indent(stream, indent); + } + return stream << "}"; +}); + +OVDL_AST_LIST_NODE_DEF(ModifierNode, { + stream << "modifier = {"; + if (!_statements.empty()) { + print_nodeuptr_vector(_statements, stream, indent + 1); + print_newline_indent(stream, indent); + } + return stream << '}'; +}); + +OVDL_AST_LIST_NODE_DEF(MtthNode, { + stream << "mean_time_to_happen = {"; + if (!_statements.empty()) { + print_nodeuptr_vector(_statements, stream, indent + 1); + print_newline_indent(stream, indent); + } + return stream << '}'; +}); + +OVDL_AST_LIST_NODE_DEF(EventOptionNode, { + stream << "option = {"; + if (!_statements.empty()) { + print_nodeuptr_vector(_statements, stream, indent + 1); + print_newline_indent(stream, indent); + } + return stream << '}'; +}); + +OVDL_AST_LIST_NODE_DEF(BehaviorListNode, { + stream << "ai_chance = {"; // may be ai_chance or ai_will_do + if (!_statements.empty()) { + print_nodeuptr_vector(_statements, stream, indent + 1); + print_newline_indent(stream, indent); + } + return stream << '}'; +}); + +OVDL_AST_LIST_NODE_DEF(DecisionListNode, { + stream << "political_decisions = {"; + if (!_statements.empty()) { + print_nodeuptr_vector(_statements, stream, indent + 1); + print_newline_indent(stream, indent); + } + return stream << '}'; +}); + +#undef OVDL_AST_LIST_NODE_DEF + +EventNode::EventNode(Type type, const std::vector<NodePtr>& statements) : _type(type) { + copy_into_node_ptr_vector(statements, _statements); +} +std::ostream& EventNode::print(std::ostream& stream, size_t indent) const { + switch (_type) { + case Type::Country: stream << "country_event = "; break; + case Type::Province: stream << "province_event = "; break; + } + stream << '{'; + if (!_statements.empty()) { + print_nodeuptr_vector(_statements, stream, indent + 1); + print_newline_indent(stream, indent); + } + return stream << '}'; +} + +DecisionNode::DecisionNode(NodePtr name, const std::vector<NodePtr>& statements) : _name(std::move(name)) { + copy_into_node_ptr_vector(statements, _statements); +} +std::ostream& DecisionNode::print(std::ostream& stream, size_t indent) const { + print_ptr(stream, _name.get(), indent) << " = {"; + if (!_statements.empty()) { + print_nodeuptr_vector(_statements, stream, indent + 1); + print_newline_indent(stream, indent); + } + return stream << '}'; +} + +ExecutionNode::ExecutionNode(Type type, NodePtr name, NodePtr init) : _type(type), + _name(std::move(name)), + _initializer(std::move(init)) { +} +std::ostream& ExecutionNode::print(std::ostream& stream, size_t indent) const { + print_ptr(stream, _name.get(), indent) << " = "; + if (_initializer) { + Node::print_ptr(stream, _initializer.get(), indent + 1); + } + return stream; +} + +ExecutionListNode::ExecutionListNode(ExecutionNode::Type type, const std::vector<NodePtr>& statements) : _type(type) { + copy_into_node_ptr_vector(statements, _statements); +} +std::ostream& ExecutionListNode::print(std::ostream& stream, size_t indent) const { + // Only way to make a valid declared parsable file + stream << "{ "; + switch (_type) { + case ExecutionNode::Type::Effect: stream << "effect = {"; break; + case ExecutionNode::Type::Trigger: stream << "trigger = {"; break; + } + if (!_statements.empty()) { + print_nodeuptr_vector(_statements, stream, indent + 1); + print_newline_indent(stream, indent); + } + return stream << "}}"; +} + +Node::operator std::string() const { + std::stringstream ss; + ss << *this; + return ss.str(); +} + +std::ostream& AssignNode::print(std::ostream& stream, size_t indent) const { + stream << _name << " = "; + return Node::print_ptr(stream, _initializer.get(), indent); +} diff --git a/src/openvic-dataloader/v2script/AiBehaviorGrammar.hpp b/src/openvic-dataloader/v2script/AiBehaviorGrammar.hpp new file mode 100644 index 0000000..012820f --- /dev/null +++ b/src/openvic-dataloader/v2script/AiBehaviorGrammar.hpp @@ -0,0 +1,34 @@ +#pragma once + +#include <openvic-dataloader/v2script/AbstractSyntaxTree.hpp> + +#include <lexy/dsl.hpp> + +#include "ModifierGrammar.hpp" +#include "SimpleGrammar.hpp" +#include "TriggerGrammar.hpp" + +namespace ovdl::v2script::grammar { + struct AiBehaviorList { + static constexpr auto rule = lexy::dsl::list(lexy::dsl::p<FactorStatement> | lexy::dsl::p<ModifierStatement>); + + static constexpr auto value = + lexy::as_list<std::vector<ast::NodePtr>> >> + lexy::callback<ast::NodePtr>( + [](auto&& list) { + return ast::make_node_ptr<ast::BehaviorListNode>(LEXY_MOV(list)); + }); + }; + + struct AiBehaviorBlock { + static constexpr auto rule = lexy::dsl::curly_bracketed.opt(lexy::dsl::p<AiBehaviorList>); + + static constexpr auto value = lexy::callback<ast::NodePtr>( + [](auto&& list) { + return LEXY_MOV(list); + }, + [](lexy::nullopt = {}) { + return lexy::nullopt {}; + }); + }; +}
\ No newline at end of file diff --git a/src/openvic-dataloader/v2script/DecisionGrammar.hpp b/src/openvic-dataloader/v2script/DecisionGrammar.hpp new file mode 100644 index 0000000..93ba0f5 --- /dev/null +++ b/src/openvic-dataloader/v2script/DecisionGrammar.hpp @@ -0,0 +1,128 @@ +#pragma once + +#include <memory> +#include <string> +#include <vector> + +#include <openvic-dataloader/v2script/AbstractSyntaxTree.hpp> + +#include <lexy/callback.hpp> +#include <lexy/callback/adapter.hpp> +#include <lexy/callback/container.hpp> +#include <lexy/dsl.hpp> +#include <lexy/dsl/option.hpp> + +#include "AiBehaviorGrammar.hpp" +#include "EffectGrammar.hpp" +#include "SimpleGrammar.hpp" +#include "TriggerGrammar.hpp" + +// Decision Grammar Definitions // +namespace ovdl::v2script::grammar { + ////////////////// + // Macros + ////////////////// +// Produces <KW_NAME>_rule and <KW_NAME>_p +#define OVDL_GRAMMAR_KEYWORD_DEFINE(KW_NAME) \ + struct KW_NAME##_rule { \ + static constexpr auto keyword = LEXY_KEYWORD(#KW_NAME, lexy::dsl::inline_<Identifier>); \ + static constexpr auto rule = keyword >> lexy::dsl::equal_sign; \ + static constexpr auto value = lexy::noop; \ + }; \ + static constexpr auto KW_NAME##_p = lexy::dsl::p<KW_NAME##_rule> + +// Produces <KW_NAME>_rule and <KW_NAME>_p and <KW_NAME>_rule::flag and <KW_NAME>_rule::too_many_error +#define OVDL_GRAMMAR_KEYWORD_FLAG_DEFINE(KW_NAME) \ + struct KW_NAME##_rule { \ + static constexpr auto keyword = LEXY_KEYWORD(#KW_NAME, lexy::dsl::inline_<Identifier>); \ + static constexpr auto rule = keyword >> lexy::dsl::equal_sign; \ + static constexpr auto value = lexy::noop; \ + static constexpr auto flag = lexy::dsl::context_flag<struct KW_NAME##_context>; \ + struct too_many_error { \ + static constexpr auto name = "expected left side " #KW_NAME " to be found once"; \ + }; \ + }; \ + static constexpr auto KW_NAME##_p = lexy::dsl::p<KW_NAME##_rule> >> (lexy::dsl::must(KW_NAME##_rule::flag.is_reset()).error<KW_NAME##_rule::too_many_error> + KW_NAME##_rule::flag.set()) + ////////////////// + // Macros + ////////////////// + struct DecisionStatement { + template<auto Production, typename AstNode> + struct _StringStatement { + static constexpr auto rule = Production >> (lexy::dsl::p<StringExpression> | lexy::dsl::p<Identifier>); + static constexpr auto value = lexy::forward<ast::NodePtr>; + }; + template<auto Production, typename AstNode> + static constexpr auto StringStatement = lexy::dsl::p<_StringStatement<Production, AstNode>>; + + OVDL_GRAMMAR_KEYWORD_FLAG_DEFINE(potential); + OVDL_GRAMMAR_KEYWORD_FLAG_DEFINE(allow); + OVDL_GRAMMAR_KEYWORD_FLAG_DEFINE(effect); + OVDL_GRAMMAR_KEYWORD_FLAG_DEFINE(ai_will_do); + + static constexpr auto rule = [] { + constexpr auto create_flags = + potential_rule::flag.create() + + allow_rule::flag.create() + + effect_rule::flag.create() + + ai_will_do_rule::flag.create(); + + constexpr auto potential_statement = potential_p >> lexy::dsl::p<TriggerBlock>; + constexpr auto allow_statement = allow_p >> lexy::dsl::p<TriggerBlock>; + constexpr auto effect_statement = effect_p >> lexy::dsl::p<TriggerBlock>; + constexpr auto ai_will_do_statement = ai_will_do_p >> lexy::dsl::p<AiBehaviorBlock>; + + return lexy::dsl::p<Identifier> >> + (create_flags + lexy::dsl::equal_sign + + lexy::dsl::curly_bracketed.list( + potential_statement | + allow_statement | + effect_statement | + ai_will_do_statement | + lexy::dsl::p<SimpleAssignmentStatement>)); + }(); + + static constexpr auto value = + lexy::as_list<std::vector<ast::NodePtr>> >> + lexy::callback<ast::NodePtr>( + [](auto&& name, auto&& list) { + return ast::make_node_ptr<ast::DecisionNode>(LEXY_MOV(name), LEXY_MOV(list)); + }, + [](auto&& name, lexy::nullopt = {}) { + return ast::make_node_ptr<ast::DecisionNode>(LEXY_MOV(name)); + }); + }; + + struct DecisionList { + static constexpr auto rule = + LEXY_KEYWORD("political_decisions", lexy::dsl::inline_<Identifier>) >> + (lexy::dsl::equal_sign + lexy::dsl::curly_bracketed.opt_list(lexy::dsl::p<DecisionStatement>)); + + static constexpr auto value = + lexy::as_list<std::vector<ast::NodePtr>> >> + lexy::callback<ast::NodePtr>( + [](auto&& list) { + return ast::make_node_ptr<ast::DecisionListNode>(LEXY_MOV(list)); + }, + [](lexy::nullopt = {}) { + return lexy::nullopt {}; + }); + }; + + struct DecisionFile { + // Allow arbitrary spaces between individual tokens. + static constexpr auto whitespace = whitespace_specifier | comment_specifier; + + static constexpr auto rule = + lexy::dsl::terminator(lexy::dsl::eof).list( // + lexy::dsl::p<DecisionList> | // + lexy::dsl::p<SimpleAssignmentStatement>); + + static constexpr auto value = lexy::as_list<std::vector<ast::NodePtr>> >> lexy::new_<ast::FileNode, ast::NodePtr>; + }; + +#undef OVDL_GRAMMAR_KEYWORD_DEFINE +#undef OVDL_GRAMMAR_KEYWORD_FLAG_DEFINE +#undef OVDL_GRAMMAR_KEYWORD_STATEMENT +#undef OVDL_GRAMMAR_KEYWORD_FLAG_STATEMENT +}
\ No newline at end of file diff --git a/src/openvic-dataloader/v2script/EffectGrammar.hpp b/src/openvic-dataloader/v2script/EffectGrammar.hpp new file mode 100644 index 0000000..9f164b2 --- /dev/null +++ b/src/openvic-dataloader/v2script/EffectGrammar.hpp @@ -0,0 +1,42 @@ +#pragma once + +#include <openvic-dataloader/v2script/AbstractSyntaxTree.hpp> + +#include <lexy/callback.hpp> +#include <lexy/dsl.hpp> + +#include "SimpleGrammar.hpp" + +namespace ovdl::v2script::grammar { + struct EffectStatement { + static constexpr auto rule = lexy::dsl::inline_<SimpleAssignmentStatement>; + + static constexpr auto value = lexy::callback<ast::NodePtr>( + [](auto name, auto&& initalizer) { + return ast::make_node_ptr<ast::ExecutionNode>(ast::ExecutionNode::Type::Effect, LEXY_MOV(name), LEXY_MOV(initalizer)); + }); + }; + + struct EffectList { + static constexpr auto rule = lexy::dsl::list(lexy::dsl::p<SimpleAssignmentStatement>); + + static constexpr auto value = + lexy::as_list<std::vector<ast::NodePtr>> >> + lexy::callback<ast::NodePtr>( + [](auto&& list) { + return ast::make_node_ptr<ast::ExecutionListNode>(ast::ExecutionNode::Type::Effect, LEXY_MOV(list)); + }); + }; + + struct EffectBlock { + static constexpr auto rule = lexy::dsl::curly_bracketed.opt(lexy::dsl::p<EffectList>); + + static constexpr auto value = lexy::callback<ast::NodePtr>( + [](auto&& list) { + return LEXY_MOV(list); + }, + [](lexy::nullopt = {}) { + return lexy::nullopt {}; + }); + }; +}
\ No newline at end of file diff --git a/src/openvic-dataloader/v2script/EventGrammar.hpp b/src/openvic-dataloader/v2script/EventGrammar.hpp new file mode 100644 index 0000000..93a52bf --- /dev/null +++ b/src/openvic-dataloader/v2script/EventGrammar.hpp @@ -0,0 +1,183 @@ +#pragma once + +#include <memory> +#include <string> +#include <vector> + +#include <openvic-dataloader/v2script/AbstractSyntaxTree.hpp> + +#include <lexy/callback.hpp> +#include <lexy/dsl.hpp> + +#include "AiBehaviorGrammar.hpp" +#include "EffectGrammar.hpp" +#include "ModifierGrammar.hpp" +#include "SimpleGrammar.hpp" +#include "TriggerGrammar.hpp" + +// Event Grammar Definitions // +namespace ovdl::v2script::grammar { + ////////////////// + // Macros + ////////////////// +// Produces <KW_NAME>_rule and <KW_NAME>_p +#define OVDL_GRAMMAR_KEYWORD_DEFINE(KW_NAME) \ + struct KW_NAME##_rule { \ + static constexpr auto keyword = LEXY_KEYWORD(#KW_NAME, lexy::dsl::inline_<Identifier>); \ + static constexpr auto rule = keyword >> lexy::dsl::equal_sign; \ + static constexpr auto value = lexy::noop; \ + }; \ + static constexpr auto KW_NAME##_p = lexy::dsl::p<KW_NAME##_rule> + +// Produces <KW_NAME>_rule and <KW_NAME>_p and <KW_NAME>_rule::flag and <KW_NAME>_rule::too_many_error +#define OVDL_GRAMMAR_KEYWORD_FLAG_DEFINE(KW_NAME) \ + struct KW_NAME##_rule { \ + static constexpr auto keyword = LEXY_KEYWORD(#KW_NAME, lexy::dsl::inline_<Identifier>); \ + static constexpr auto rule = keyword >> lexy::dsl::equal_sign; \ + static constexpr auto value = lexy::noop; \ + static constexpr auto flag = lexy::dsl::context_flag<struct KW_NAME##_context>; \ + struct too_many_error { \ + static constexpr auto name = "expected left side " #KW_NAME " to be found once"; \ + }; \ + }; \ + static constexpr auto KW_NAME##_p = lexy::dsl::p<KW_NAME##_rule> >> (lexy::dsl::must(KW_NAME##_rule::flag.is_reset()).error<KW_NAME##_rule::too_many_error> + KW_NAME##_rule::flag.set()) + ////////////////// + // Macros + ////////////////// + static constexpr auto event_symbols = lexy::symbol_table<ast::EventNode::Type> // + .map<LEXY_SYMBOL("country_event")>(ast::EventNode::Type::Country) + .map<LEXY_SYMBOL("province_event")>(ast::EventNode::Type::Province); + + struct EventMtthStatement { + OVDL_GRAMMAR_KEYWORD_DEFINE(months); + + struct MonthValue { + static constexpr auto rule = lexy::dsl::inline_<Identifier>; + static constexpr auto value = lexy::as_string<std::string> | lexy::new_<ast::MonthNode, ast::NodePtr>; + }; + + static constexpr auto rule = lexy::dsl::list( + (months_p >> lexy::dsl::p<MonthValue>) | + lexy::dsl::p<ModifierStatement>); + + static constexpr auto value = + lexy::as_list<std::vector<ast::NodePtr>> >> + lexy::callback<ast::NodePtr>( + [](auto&& list) { + return ast::make_node_ptr<ast::MtthNode>(LEXY_MOV(list)); + }); + }; + + template<auto Production, typename AstNode> + struct _StringStatement { + static constexpr auto rule = Production >> (lexy::dsl::p<StringExpression> | lexy::dsl::p<Identifier>); + static constexpr auto value = + lexy::callback<ast::NodePtr>( + [](auto&& value) { + auto result = ast::make_node_ptr<AstNode>(std::move(static_cast<ast::AbstractStringNode*>(value)->_name)); + delete value; + return result; + }); + }; + template<auto Production, typename AstNode> + static constexpr auto StringStatement = lexy::dsl::p<_StringStatement<Production, AstNode>>; + + struct EventOptionList { + OVDL_GRAMMAR_KEYWORD_FLAG_DEFINE(name); + OVDL_GRAMMAR_KEYWORD_FLAG_DEFINE(ai_chance); + + static constexpr auto rule = [] { + constexpr auto create_flags = name_rule::flag.create() + ai_chance_rule::flag.create(); + + constexpr auto name_statement = StringStatement<name_p, ast::NameNode>; + constexpr auto ai_chance_statement = ai_chance_p >> lexy::dsl::curly_bracketed(lexy::dsl::p<AiBehaviorList>); + + return create_flags + lexy::dsl::list(name_statement | ai_chance_statement | lexy::dsl::p<EffectList>); + }(); + + static constexpr auto value = + lexy::as_list<std::vector<ast::NodePtr>> >> + lexy::callback<ast::NodePtr>( + [](auto&& list) { + return ast::make_node_ptr<ast::EventOptionNode>(LEXY_MOV(list)); + }); + }; + + struct EventStatement { + OVDL_GRAMMAR_KEYWORD_FLAG_DEFINE(id); + OVDL_GRAMMAR_KEYWORD_FLAG_DEFINE(title); + OVDL_GRAMMAR_KEYWORD_FLAG_DEFINE(desc); + OVDL_GRAMMAR_KEYWORD_FLAG_DEFINE(picture); + OVDL_GRAMMAR_KEYWORD_FLAG_DEFINE(is_triggered_only); + OVDL_GRAMMAR_KEYWORD_FLAG_DEFINE(fire_only_once); + OVDL_GRAMMAR_KEYWORD_FLAG_DEFINE(immediate); + OVDL_GRAMMAR_KEYWORD_FLAG_DEFINE(mean_time_to_happen); + OVDL_GRAMMAR_KEYWORD_DEFINE(trigger); + OVDL_GRAMMAR_KEYWORD_DEFINE(option); + + static constexpr auto rule = [] { + constexpr auto symbol_value = lexy::dsl::symbol<event_symbols>(lexy::dsl::inline_<Identifier>); + + constexpr auto create_flags = + id_rule::flag.create() + + title_rule::flag.create() + + desc_rule::flag.create() + + picture_rule::flag.create() + + is_triggered_only_rule::flag.create() + + fire_only_once_rule::flag.create() + + immediate_rule::flag.create() + + mean_time_to_happen_rule::flag.create(); + + constexpr auto id_statement = StringStatement<id_p, ast::IdNode>; + constexpr auto title_statement = StringStatement<title_p, ast::TitleNode>; + constexpr auto desc_statement = StringStatement<desc_p, ast::DescNode>; + constexpr auto picture_statement = StringStatement<picture_p, ast::PictureNode>; + constexpr auto is_triggered_only_statement = StringStatement<is_triggered_only_p, ast::IsTriggeredNode>; + constexpr auto fire_only_once_statement = StringStatement<fire_only_once_p, ast::FireOnlyNode>; + constexpr auto immediate_statement = immediate_p >> lexy::dsl::p<EffectBlock>; + constexpr auto mean_time_to_happen_statement = mean_time_to_happen_p >> lexy::dsl::curly_bracketed(lexy::dsl::p<EventMtthStatement>); + + constexpr auto trigger_statement = trigger_p >> lexy::dsl::curly_bracketed.opt(lexy::dsl::p<TriggerList>); + constexpr auto option_statement = option_p >> lexy::dsl::curly_bracketed(lexy::dsl::p<EventOptionList>); + + return symbol_value >> + (create_flags + lexy::dsl::equal_sign + + lexy::dsl::curly_bracketed.opt_list( + id_statement | + title_statement | + desc_statement | + picture_statement | + is_triggered_only_statement | + fire_only_once_statement | + immediate_statement | + mean_time_to_happen_statement | + trigger_statement | + option_statement | + lexy::dsl::p<SimpleAssignmentStatement>)); + }(); + + static constexpr auto value = + lexy::as_list<std::vector<ast::NodePtr>> >> + lexy::callback<ast::NodePtr>( + [](auto& type, auto&& list) { + return ast::make_node_ptr<ast::EventNode>(type, LEXY_MOV(list)); + }, + [](auto& type, lexy::nullopt = {}) { + return ast::make_node_ptr<ast::EventNode>(type); + }); + }; + + struct EventFile { + // Allow arbitrary spaces between individual tokens. + static constexpr auto whitespace = whitespace_specifier | comment_specifier; + + static constexpr auto rule = lexy::dsl::terminator(lexy::dsl::eof).list(lexy::dsl::p<EventStatement> | lexy::dsl::p<SimpleAssignmentStatement>); + + static constexpr auto value = lexy::as_list<std::vector<ast::NodePtr>> >> lexy::new_<ast::FileNode, ast::NodePtr>; + }; + +#undef OVDL_GRAMMAR_KEYWORD_DEFINE +#undef OVDL_GRAMMAR_KEYWORD_FLAG_DEFINE +#undef OVDL_GRAMMAR_KEYWORD_STATEMENT +#undef OVDL_GRAMMAR_KEYWORD_FLAG_STATEMENT +}
\ No newline at end of file diff --git a/src/openvic-dataloader/v2script/Grammar.cpp b/src/openvic-dataloader/v2script/Grammar.cpp deleted file mode 100644 index ec9fac2..0000000 --- a/src/openvic-dataloader/v2script/Grammar.cpp +++ /dev/null @@ -1,74 +0,0 @@ -#include <lexy/dsl.hpp> -#include <openvic-dataloader/v2script/Parser.hpp> - -using namespace ovdl::v2script; - -// Node Definitions // -namespace dsl = lexy::dsl; - -namespace ovdl::v2script::nodes { - struct StatementListBlock; - - static constexpr auto whitespace_specifier = dsl::code_point.range<0x09, 0x0A>() / dsl::lit_cp<0x0D> / dsl::lit_cp<0x20>; - static constexpr auto comment_specifier = LEXY_LIT("#") >> dsl::until(dsl::newline).or_eof(); - - static constexpr auto data_specifier = - dsl::ascii::alpha_digit_underscore / - dsl::code_point.range<0x25, 0x27>() / dsl::lit_cp<0x2B> / dsl::code_point.range<0x2D, 0x2E>() / - dsl::lit_cp<0x3A> / - dsl::lit_cp<0x8A> / dsl::lit_cp<0x8C> / dsl::lit_cp<0x8E> / - dsl::lit_cp<0x92> / dsl::lit_cp<0x9A> / dsl::lit_cp<0x9C> / dsl::code_point.range<0x9E, 0x9F>() / - dsl::code_point.range<0xC0, 0xD6>() / dsl::code_point.range<0xD8, 0xF6>() / dsl::code_point.range<0xF8, 0xFF>(); - - static constexpr auto data_char_class = LEXY_CHAR_CLASS("DataSpecifier", data_specifier); - - struct Identifier { - static constexpr auto rule = dsl::identifier(data_char_class); - }; - - struct StringExpression { - static constexpr auto escaped_symbols = lexy::symbol_table<char> // - .map<'"'>('"') - .map<'\''>('\'') - .map<'\\'>('\\') - .map<'/'>('/') - .map<'b'>('\b') - .map<'f'>('\f') - .map<'n'>('\n') - .map<'r'>('\r') - .map<'t'>('\t'); - static constexpr auto rule = [] { - // Arbitrary code points that aren't control characters. - auto c = -dsl::unicode::control; - - // Escape sequences start with a backlash. - // They either map one of the symbols, - // or a Unicode code point of the form uXXXX. - auto escape = dsl::backslash_escape // - .symbol<escaped_symbols>() - .rule(dsl::lit_c<'u'> >> dsl::code_point_id<4>); - return dsl::quoted(c, escape); - }(); - }; - - struct AssignmentStatement { - static constexpr auto rule = dsl::p<Identifier> >> - (dsl::equal_sign >> - (dsl::p<Identifier> | dsl::p<StringExpression> | dsl::recurse_branch<StatementListBlock>) | - dsl::else_ >> dsl::return_); - }; - - struct StatementListBlock { - static constexpr auto rule = - dsl::curly_bracketed.open() >> - dsl::opt(dsl::list(dsl::p<AssignmentStatement>)) + dsl::opt(dsl::semicolon) + - dsl::curly_bracketed.close(); - }; - - struct File { - // Allow arbitrary spaces between individual tokens. - static constexpr auto whitespace = whitespace_specifier | comment_specifier; - - static constexpr auto rule = dsl::terminator(dsl::eof).list(dsl::p<AssignmentStatement>); - }; -} diff --git a/src/openvic-dataloader/v2script/ModifierGrammar.hpp b/src/openvic-dataloader/v2script/ModifierGrammar.hpp new file mode 100644 index 0000000..ad0704a --- /dev/null +++ b/src/openvic-dataloader/v2script/ModifierGrammar.hpp @@ -0,0 +1,33 @@ +#pragma once + +#include <openvic-dataloader/v2script/AbstractSyntaxTree.hpp> + +#include <lexy/dsl.hpp> + +#include "SimpleGrammar.hpp" +#include "TriggerGrammar.hpp" + +namespace ovdl::v2script::grammar { + constexpr auto modifier_keyword = LEXY_KEYWORD("modifier", lexy::dsl::inline_<Identifier>); + constexpr auto factor_keyword = LEXY_KEYWORD("factor", lexy::dsl::inline_<Identifier>); + + struct FactorStatement { + static constexpr auto rule = factor_keyword >> lexy::dsl::equal_sign + lexy::dsl::inline_<Identifier>; + static constexpr auto value = lexy::as_string<std::string> | lexy::new_<ast::FactorNode, ast::NodePtr>; + }; + + struct ModifierStatement { + static constexpr auto rule = + modifier_keyword >> + lexy::dsl::curly_bracketed.list( + lexy::dsl::p<FactorStatement> | + lexy::dsl::p<TriggerList>); + + static constexpr auto value = + lexy::as_list<std::vector<ast::NodePtr>> >> + lexy::callback<ast::NodePtr>( + [](auto&& list) { + return make_node_ptr<ast::ModifierNode>(LEXY_MOV(list)); + }); + }; +}
\ No newline at end of file diff --git a/src/openvic-dataloader/v2script/Parser.cpp b/src/openvic-dataloader/v2script/Parser.cpp new file mode 100644 index 0000000..07d6455 --- /dev/null +++ b/src/openvic-dataloader/v2script/Parser.cpp @@ -0,0 +1,224 @@ +#include "openvic-dataloader/v2script/Parser.hpp" + +#include <functional> +#include <iostream> +#include <memory> +#include <optional> +#include <string> +#include <utility> +#include <vector> + +#include <openvic-dataloader/ParseError.hpp> +#include <openvic-dataloader/ParseWarning.hpp> +#include <openvic-dataloader/detail/Concepts.hpp> +#include <openvic-dataloader/v2script/AbstractSyntaxTree.hpp> + +#include <lexy/action/parse.hpp> +#include <lexy/encoding.hpp> +#include <lexy/input/buffer.hpp> +#include <lexy/input/file.hpp> +#include <lexy/lexeme.hpp> +#include <lexy/visualize.hpp> + +#include "SimpleGrammar.hpp" +#include "detail/BasicBufferHandler.hpp" +#include "detail/DetectUtf8.hpp" +#include "detail/Errors.hpp" +#include "detail/LexyReportError.hpp" +#include "detail/NullBuff.hpp" +#include "detail/OStreamOutputIterator.hpp" +#include "detail/Warnings.hpp" +#include "v2script/DecisionGrammar.hpp" +#include "v2script/EventGrammar.hpp" + +using namespace ovdl; +using namespace ovdl::v2script; + +/// BufferHandler /// + +class Parser::BufferHandler final : public detail::BasicBufferHandler<> { +public: + constexpr bool is_exclusive_utf8() const { + return detail::is_utf8_no_ascii(_buffer); + } + + template<typename Node, typename ErrorCallback> + std::optional<std::vector<ParseError>> parse(const ErrorCallback& callback) { + auto result = lexy::parse<Node>(_buffer, callback); + if (!result) { + return result.errors(); + } + // This is mighty frustrating + _root = std::unique_ptr<ast::Node>(result.value()); + return std::nullopt; + } + + std::unique_ptr<ast::Node>& get_root() { + return _root; + } + +private: + std::unique_ptr<ast::Node> _root; +}; + +/// BufferHandler /// + +Parser::Parser() + : _buffer_handler(std::make_unique<BufferHandler>()) { + set_error_log_to_stderr(); +} + +Parser::Parser(Parser&&) = default; +Parser& Parser::operator=(Parser&&) = default; +Parser::~Parser() = default; + +Parser Parser::from_buffer(const char* data, std::size_t size) { + Parser result; + return std::move(result.load_from_buffer(data, size)); +} + +Parser Parser::from_buffer(const char* start, const char* end) { + Parser result; + return std::move(result.load_from_buffer(start, end)); +} + +Parser Parser::from_string(const std::string_view string) { + Parser result; + return std::move(result.load_from_string(string)); +} + +Parser Parser::from_file(const char* path) { + Parser result; + return std::move(result.load_from_file(path)); +} + +Parser Parser::from_file(const std::filesystem::path& path) { + Parser result; + return std::move(result.load_from_file(path)); +} + +/// +/// @brief Executes a function on _buffer_handler that is expected to load a buffer +/// +/// Expected Use: +/// @code {.cpp} +/// _run_load_func(&BufferHandler::<load_function>, <arguments>); +/// @endcode +/// +/// @tparam Type +/// @tparam Args +/// @param func +/// @param args +/// +template<typename... Args> +constexpr void Parser::_run_load_func(detail::LoadCallback<BufferHandler, Args...> auto func, Args... args) { + _warnings.clear(); + _errors.clear(); + _has_fatal_error = false; + if (auto error = func(_buffer_handler.get(), std::forward<Args>(args)...); error) { + _has_fatal_error = error.value().type == ParseError::Type::Fatal; + _errors.push_back(error.value()); + _error_stream.get() << "Error: " << _errors.back().message << '\n'; + } +} + +constexpr Parser& Parser::load_from_buffer(const char* data, std::size_t size) { + // Type can't be deduced? + _run_load_func(std::mem_fn(&BufferHandler::load_buffer_size), data, size); + return *this; +} + +constexpr Parser& Parser::load_from_buffer(const char* start, const char* end) { + // Type can't be deduced? + _run_load_func(std::mem_fn(&BufferHandler::load_buffer), start, end); + return *this; +} + +constexpr Parser& Parser::load_from_string(const std::string_view string) { + return load_from_buffer(string.data(), string.size()); +} + +constexpr Parser& Parser::load_from_file(const char* path) { + _file_path = path; + // Type can be deduced?? + _run_load_func(std::mem_fn(&BufferHandler::load_file), path); + return *this; +} + +Parser& Parser::load_from_file(const std::filesystem::path& path) { + return load_from_file(path.string().c_str()); +} + +constexpr Parser& Parser::load_from_file(const detail::Has_c_str auto& path) { + return load_from_file(path.c_str()); +} + +bool Parser::simple_parse() { + if (!_buffer_handler->is_valid()) { + return false; + } + + if (_buffer_handler->is_exclusive_utf8()) { + _warnings.push_back(warnings::make_utf8_warning(_file_path)); + } + + auto errors = _buffer_handler->parse<grammar::File>(ovdl::detail::ReporError.path(_file_path).to(detail::OStreamOutputIterator { _error_stream })); + if (errors) { + _errors.reserve(errors->size()); + for (auto& err : errors.value()) { + _has_fatal_error |= err.type == ParseError::Type::Fatal; + _errors.push_back(err); + } + return false; + } + _file_node.reset(static_cast<ast::FileNode*>(_buffer_handler->get_root().release())); + return true; +} + +bool Parser::event_parse() { + if (!_buffer_handler->is_valid()) { + return false; + } + + if (_buffer_handler->is_exclusive_utf8()) { + _warnings.push_back(warnings::make_utf8_warning(_file_path)); + } + + auto errors = _buffer_handler->parse<grammar::EventFile>(ovdl::detail::ReporError.path(_file_path).to(detail::OStreamOutputIterator { _error_stream })); + if (errors) { + _errors.reserve(errors->size()); + for (auto& err : errors.value()) { + _has_fatal_error |= err.type == ParseError::Type::Fatal; + _errors.push_back(err); + } + return false; + } + _file_node.reset(static_cast<ast::FileNode*>(_buffer_handler->get_root().release())); + return true; +} + +bool Parser::decision_parse() { + if (!_buffer_handler->is_valid()) { + return false; + } + + if (_buffer_handler->is_exclusive_utf8()) { + _warnings.push_back(warnings::make_utf8_warning(_file_path)); + } + + auto errors = _buffer_handler->parse<grammar::DecisionFile>(ovdl::detail::ReporError.path(_file_path).to(detail::OStreamOutputIterator { _error_stream })); + if (errors) { + _errors.reserve(errors->size()); + for (auto& err : errors.value()) { + _has_fatal_error |= err.type == ParseError::Type::Fatal; + _errors.push_back(err); + } + return false; + } + _file_node.reset(static_cast<ast::FileNode*>(_buffer_handler->get_root().release())); + return true; +} + +const FileNode* Parser::get_file_node() const { + return _file_node.get(); +}
\ No newline at end of file diff --git a/src/openvic-dataloader/v2script/SimpleGrammar.hpp b/src/openvic-dataloader/v2script/SimpleGrammar.hpp new file mode 100644 index 0000000..c91935e --- /dev/null +++ b/src/openvic-dataloader/v2script/SimpleGrammar.hpp @@ -0,0 +1,120 @@ +#pragma once + +#include <memory> +#include <string> +#include <vector> + +#include <openvic-dataloader/v2script/AbstractSyntaxTree.hpp> + +#include <lexy/callback.hpp> +#include <lexy/dsl.hpp> + +#include "detail/LexyLitRange.hpp" + +// Grammar Definitions // +namespace ovdl::v2script::grammar { + struct StatementListBlock; + + static constexpr auto whitespace_specifier = lexy::dsl::ascii::blank / lexy::dsl::ascii::newline; + static constexpr auto comment_specifier = LEXY_LIT("#") >> lexy::dsl::until(lexy::dsl::newline).or_eof(); + + static constexpr auto data_specifier = + lexy::dsl::ascii::alpha_digit_underscore / + LEXY_ASCII_ONE_OF("%&'") / lexy::dsl::lit_c<0x2B> / LEXY_ASCII_ONE_OF("-.") / + lexy::dsl::ascii::digit / lexy::dsl::lit_c<0x3A> / + lexy::dsl::lit_c<0x40> / lexy::dsl::ascii::upper / lexy::dsl::lit_c<0x5F> / + lexy::dsl::ascii::lower / lexy::dsl::lit_b<0x8A> / lexy::dsl::lit_b<0x8C> / lexy::dsl::lit_b<0x8E> / + lexy::dsl::lit_b<0x92> / lexy::dsl::lit_b<0x97> / lexy::dsl::lit_b<0x9A> / lexy::dsl::lit_b<0x9C> / lexy::dsl::lit_b<0x9E> / lexy::dsl::lit_b<0x9F> / + lexy::dsl::lit_b<0xC0> / + ovdl::detail::lexydsl::make_range<0xC0, 0xD6>() / ovdl::detail::lexydsl::make_range<0xD8, 0xF6>() / ovdl::detail::lexydsl::make_range<0xF8, 0xFF>(); + + static constexpr auto data_char_class = LEXY_CHAR_CLASS("DataSpecifier", data_specifier); + + struct Identifier { + static constexpr auto rule = lexy::dsl::identifier(data_char_class); + static constexpr auto value = lexy::as_string<std::string> | lexy::new_<ast::IdentifierNode, ast::NodePtr>; + }; + + struct StringExpression { + static constexpr auto escaped_symbols = lexy::symbol_table<char> // + .map<'"'>('"') + .map<'\''>('\'') + .map<'\\'>('\\') + .map<'/'>('/') + .map<'b'>('\b') + .map<'f'>('\f') + .map<'n'>('\n') + .map<'r'>('\r') + .map<'t'>('\t'); + static constexpr auto rule = [] { + // Arbitrary code points that aren't control characters. + auto c = ovdl::detail::lexydsl::make_range<0x20, 0xFF>() - lexy::dsl::ascii::control; + + // Escape sequences start with a backlash. + // They either map one of the symbols, + // or a Unicode code point of the form uXXXX. + auto escape = lexy::dsl::backslash_escape // + .symbol<escaped_symbols>(); + return lexy::dsl::quoted(c, escape); + }(); + + static constexpr auto value = lexy::as_string<std::string> >> lexy::new_<ast::StringNode, ast::NodePtr>; + }; + + struct SimpleAssignmentStatement { + static constexpr auto rule = + lexy::dsl::p<Identifier> >> + lexy::dsl::equal_sign + + (lexy::dsl::p<Identifier> | lexy::dsl::p<StringExpression> | lexy::dsl::recurse_branch<StatementListBlock>); + + static constexpr auto value = lexy::callback<ast::NodePtr>( + [](auto name, auto&& initalizer) { + return make_node_ptr<ast::AssignNode>(LEXY_MOV(name), LEXY_MOV(initalizer)); + }); + }; + + struct AssignmentStatement { + static constexpr auto rule = + lexy::dsl::p<Identifier> >> + (lexy::dsl::equal_sign >> + (lexy::dsl::p<Identifier> | lexy::dsl::p<StringExpression> | lexy::dsl::recurse_branch<StatementListBlock>) | + lexy::dsl::else_ >> lexy::dsl::return_) | + lexy::dsl::p<StringExpression>; + + static constexpr auto value = lexy::callback<ast::NodePtr>( + [](auto name, lexy::nullopt = {}) { + return LEXY_MOV(name); + }, + [](auto name, auto&& initalizer) { + return make_node_ptr<ast::AssignNode>(LEXY_MOV(name), LEXY_MOV(initalizer)); + }); + }; + + struct StatementListBlock { + static constexpr auto rule = + lexy::dsl::curly_bracketed( + lexy::dsl::opt(lexy::dsl::list(lexy::dsl::p<AssignmentStatement>)) + lexy::dsl::opt(lexy::dsl::semicolon)); + + static constexpr auto value = + lexy::as_list<std::vector<ast::NodePtr>> >> + lexy::callback<ast::NodePtr>( + [](lexy::nullopt = {}, lexy::nullopt = {}) { + return ast::make_node_ptr<ast::ListNode>(); + }, + [](auto&& list, lexy::nullopt = {}) { + return make_node_ptr<ast::ListNode>(LEXY_MOV(list)); + }, + [](auto& list) { + return make_node_ptr<ast::ListNode>(list); + }); + }; + + struct File { + // Allow arbitrary spaces between individual tokens. + static constexpr auto whitespace = whitespace_specifier | comment_specifier; + + static constexpr auto rule = lexy::dsl::terminator(lexy::dsl::eof).list(lexy::dsl::p<AssignmentStatement>); + + static constexpr auto value = lexy::as_list<std::vector<ast::NodePtr>> >> lexy::new_<ast::FileNode, ast::NodePtr>; + }; +} diff --git a/src/openvic-dataloader/v2script/TriggerGrammar.hpp b/src/openvic-dataloader/v2script/TriggerGrammar.hpp new file mode 100644 index 0000000..290fb70 --- /dev/null +++ b/src/openvic-dataloader/v2script/TriggerGrammar.hpp @@ -0,0 +1,42 @@ +#pragma once + +#include <openvic-dataloader/v2script/AbstractSyntaxTree.hpp> + +#include <lexy/callback.hpp> +#include <lexy/dsl.hpp> + +#include "SimpleGrammar.hpp" + +namespace ovdl::v2script::grammar { + struct TriggerStatement { + static constexpr auto rule = lexy::dsl::inline_<SimpleAssignmentStatement>; + + static constexpr auto value = lexy::callback<ast::NodePtr>( + [](auto name, auto&& initalizer) { + return ast::make_node_ptr<ast::ExecutionNode>(ast::ExecutionNode::Type::Trigger, LEXY_MOV(name), LEXY_MOV(initalizer)); + }); + }; + + struct TriggerList { + static constexpr auto rule = lexy::dsl::list(lexy::dsl::p<SimpleAssignmentStatement>); + + static constexpr auto value = + lexy::as_list<std::vector<ast::NodePtr>> >> + lexy::callback<ast::NodePtr>( + [](auto&& list) { + return ast::make_node_ptr<ast::ExecutionListNode>(ast::ExecutionNode::Type::Trigger, LEXY_MOV(list)); + }); + }; + + struct TriggerBlock { + static constexpr auto rule = lexy::dsl::curly_bracketed.opt(lexy::dsl::p<TriggerList>); + + static constexpr auto value = lexy::callback<ast::NodePtr>( + [](auto&& list) { + return LEXY_MOV(list); + }, + [](lexy::nullopt = {}) { + return lexy::nullopt {}; + }); + }; +}
\ No newline at end of file |