diff --git a/src/ObjLoading/Parsing/Menu/Matcher/MenuMatcherFactory.cpp b/src/ObjLoading/Parsing/Menu/Matcher/MenuMatcherFactory.cpp index 3adb5043..e8686b47 100644 --- a/src/ObjLoading/Parsing/Menu/Matcher/MenuMatcherFactory.cpp +++ b/src/ObjLoading/Parsing/Menu/Matcher/MenuMatcherFactory.cpp @@ -58,6 +58,20 @@ MatcherFactoryWrapper MenuMatcherFactory::Numeric() const })); } +MatcherFactoryWrapper MenuMatcherFactory::TextExpression() const +{ + return MatcherFactoryWrapper(Or({ + StringChain().Tag(TAG_STRING_CHAIN).Capture(CAPTURE_STRING_CHAIN), + Identifier().Tag(TAG_IDENTIFIER).Capture(CAPTURE_IDENTIFIER), + And({ + Char('(').Capture(CAPTURE_FIRST_TOKEN), + Label(MenuExpressionMatchers::LABEL_EXPRESSION), + Char(')'), + }) + .Tag(TAG_EXPRESSION), + })); +} + MatcherFactoryWrapper MenuMatcherFactory::IntExpression() const { return MatcherFactoryWrapper(Or({ @@ -144,6 +158,42 @@ int MenuMatcherFactory::TokenIntExpressionValue(MenuFileParserState* state, Sequ throw ParsingException(TokenPos(), "TokenIntExpressionValue must be expression or int"); } +std::string MenuMatcherFactory::TokenTextExpressionValue(MenuFileParserState* state, SequenceResult& result) +{ + const auto nextTag = result.PeekTag(); + + assert(nextTag == TAG_STRING_CHAIN || nextTag == TAG_IDENTIFIER || nextTag == TAG_EXPRESSION); + if (nextTag == TAG_STRING_CHAIN) + { + result.NextTag(); + return result.NextCapture(CAPTURE_STRING_CHAIN).StringValue(); + } + + if (nextTag == TAG_IDENTIFIER) + { + result.NextTag(); + return result.NextCapture(CAPTURE_IDENTIFIER).IdentifierValue(); + } + + if (nextTag == TAG_EXPRESSION) + { + result.NextTag(); + const auto expression = MenuExpressionMatchers(state).ProcessExpression(result); + + if (!expression || !expression->IsStatic()) + throw ParsingException(result.NextCapture(CAPTURE_FIRST_TOKEN).GetPos(), "Not a valid static expression"); + + const auto value = expression->EvaluateStatic(); + + if (value.m_type != SimpleExpressionValue::Type::STRING) + throw ParsingException(result.NextCapture(CAPTURE_FIRST_TOKEN).GetPos(), "Expression MUST be string type"); + + return std::move(*value.m_string_value); + } + + throw ParsingException(TokenPos(), "TokenIntExpressionValue must be expression or int"); +} + double MenuMatcherFactory::TokenNumericExpressionValue(MenuFileParserState* state, SequenceResult& result) { const auto nextTag = result.PeekTag(); diff --git a/src/ObjLoading/Parsing/Menu/Matcher/MenuMatcherFactory.h b/src/ObjLoading/Parsing/Menu/Matcher/MenuMatcherFactory.h index c0c8ccf4..562deeed 100644 --- a/src/ObjLoading/Parsing/Menu/Matcher/MenuMatcherFactory.h +++ b/src/ObjLoading/Parsing/Menu/Matcher/MenuMatcherFactory.h @@ -8,13 +8,17 @@ namespace menu { class MenuMatcherFactory : public SimpleMatcherFactory { - static constexpr auto TAG_INT = 1420; - static constexpr auto TAG_NUMERIC = 1421; - static constexpr auto TAG_EXPRESSION = 1422; + static constexpr auto TAG_STRING_CHAIN = 1420; + static constexpr auto TAG_IDENTIFIER = 1421; + static constexpr auto TAG_INT = 1422; + static constexpr auto TAG_NUMERIC = 1423; + static constexpr auto TAG_EXPRESSION = 1424; static constexpr auto CAPTURE_FIRST_TOKEN = 1420; - static constexpr auto CAPTURE_INT = 1421; - static constexpr auto CAPTURE_NUMERIC = 1422; + static constexpr auto CAPTURE_STRING_CHAIN = 1421; + static constexpr auto CAPTURE_IDENTIFIER = 1422; + static constexpr auto CAPTURE_INT = 1423; + static constexpr auto CAPTURE_NUMERIC = 1424; public: explicit MenuMatcherFactory(const IMatcherForLabelSupplier* labelSupplier); @@ -24,6 +28,7 @@ namespace menu _NODISCARD MatcherFactoryWrapper TextNoChain() const; _NODISCARD MatcherFactoryWrapper Numeric() const; + _NODISCARD MatcherFactoryWrapper TextExpression() const; _NODISCARD MatcherFactoryWrapper IntExpression() const; _NODISCARD MatcherFactoryWrapper NumericExpression() const; @@ -31,6 +36,7 @@ namespace menu _NODISCARD static double TokenNumericFloatingPointValue(const SimpleParserValue& value); _NODISCARD static std::string& TokenTextValue(const SimpleParserValue& value); + _NODISCARD static std::string TokenTextExpressionValue(MenuFileParserState* state, SequenceResult& result); _NODISCARD static int TokenIntExpressionValue(MenuFileParserState* state, SequenceResult& result); _NODISCARD static double TokenNumericExpressionValue(MenuFileParserState* state, SequenceResult& result); }; diff --git a/src/ObjLoading/Parsing/Menu/Sequence/Generic/GenericStringPropertySequence.cpp b/src/ObjLoading/Parsing/Menu/Sequence/Generic/GenericStringPropertySequence.cpp index c0faa074..7340db90 100644 --- a/src/ObjLoading/Parsing/Menu/Sequence/Generic/GenericStringPropertySequence.cpp +++ b/src/ObjLoading/Parsing/Menu/Sequence/Generic/GenericStringPropertySequence.cpp @@ -1,5 +1,6 @@ #include "GenericStringPropertySequence.h" +#include "Parsing/Menu/Matcher/MenuExpressionMatchers.h" #include "Parsing/Menu/Matcher/MenuMatcherFactory.h" #include @@ -10,10 +11,11 @@ GenericStringPropertySequence::GenericStringPropertySequence(std::string keyword : m_set_callback(std::move(setCallback)) { const MenuMatcherFactory create(this); + AddLabeledMatchers(MenuExpressionMatchers().Expression(this), MenuExpressionMatchers::LABEL_EXPRESSION); AddMatchers({ create.KeywordIgnoreCase(std::move(keywordName)).Capture(CAPTURE_FIRST_TOKEN), - create.Text().Capture(CAPTURE_VALUE), + create.TextExpression(), }); } @@ -21,7 +23,7 @@ void GenericStringPropertySequence::ProcessMatch(MenuFileParserState* state, Seq { if (m_set_callback) { - const auto& value = MenuMatcherFactory::TokenTextValue(result.NextCapture(CAPTURE_VALUE)); + const auto value = MenuMatcherFactory::TokenTextExpressionValue(state, result); m_set_callback(state, result.NextCapture(CAPTURE_FIRST_TOKEN).GetPos(), value); } } diff --git a/src/ObjLoading/Parsing/Menu/Sequence/Generic/GenericStringPropertySequence.h b/src/ObjLoading/Parsing/Menu/Sequence/Generic/GenericStringPropertySequence.h index c199c295..60a846ba 100644 --- a/src/ObjLoading/Parsing/Menu/Sequence/Generic/GenericStringPropertySequence.h +++ b/src/ObjLoading/Parsing/Menu/Sequence/Generic/GenericStringPropertySequence.h @@ -14,7 +14,6 @@ namespace menu private: static constexpr auto CAPTURE_FIRST_TOKEN = 1; - static constexpr auto CAPTURE_VALUE = 2; const callback_t m_set_callback; diff --git a/test/ObjLoadingTests/Parsing/Menu/Sequence/ItemScopeSequencesTests.cpp b/test/ObjLoadingTests/Parsing/Menu/Sequence/ItemScopeSequencesTests.cpp index adf31fc3..71edcd3c 100644 --- a/test/ObjLoadingTests/Parsing/Menu/Sequence/ItemScopeSequencesTests.cpp +++ b/test/ObjLoadingTests/Parsing/Menu/Sequence/ItemScopeSequencesTests.cpp @@ -64,6 +64,33 @@ namespace test::parsing::menu::sequence::item } }; + TEST_CASE("ItemScopeSequences: Can use static expressions for simple text properties", "[parsing][sequence][menu]") + { + ItemSequenceTestsHelper helper(FeatureLevel::IW4, false); + const TokenPos pos; + helper.Tokens({ + SimpleParserValue::Identifier(pos, new std::string("name")), + SimpleParserValue::Character(pos, '('), + SimpleParserValue::String(pos, new std::string("Hello")), + SimpleParserValue::Character(pos, '+'), + SimpleParserValue::String(pos, new std::string(" ")), + SimpleParserValue::Character(pos, '+'), + SimpleParserValue::String(pos, new std::string("World")), + SimpleParserValue::Character(pos, ')'), + SimpleParserValue::EndOfFile(pos), + }); + + const auto result = helper.PerformTest(); + + REQUIRE(result); + REQUIRE(helper.m_consumed_token_count == 8); + + const auto* item = helper.m_state->m_current_item; + REQUIRE(item); + + REQUIRE(item->m_name == "Hello World"); + } + TEST_CASE("ItemScopeSequences: Rect works with only x,y,w,h as ints", "[parsing][sequence][menu]") { ItemSequenceTestsHelper helper(FeatureLevel::IW4, false);