diff --git a/packages/react-native/ReactCommon/react/renderer/css/CSSCommaSeparatedList.h b/packages/react-native/ReactCommon/react/renderer/css/CSSCommaSeparatedList.h new file mode 100644 index 00000000000000..ee640e65e9d3ef --- /dev/null +++ b/packages/react-native/ReactCommon/react/renderer/css/CSSCommaSeparatedList.h @@ -0,0 +1,45 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +#pragma once + +#include +#include + +#include +#include + +namespace facebook::react { + +/** + * Represents a comma-separated repetition of a given single type. + * https://www.w3.org/TR/css-values-4/#mult-comma + */ +template +struct CSSCommaSeparatedList : public std::vector {}; + +template +struct CSSDataTypeParser> { + static inline auto consume(CSSSyntaxParser& parser) + -> std::optional> { + CSSCommaSeparatedList result; + for (auto nextValue = parseNextCSSValue(parser); + !std::holds_alternative(nextValue); + nextValue = + parseNextCSSValue(parser, CSSDelimiter::Comma)) { + result.push_back(std::move(std::get(nextValue))); + } + + if (result.empty()) { + return {}; + } + + return result; + } +}; + +} // namespace facebook::react diff --git a/packages/react-native/ReactCommon/react/renderer/css/CSSDataType.h b/packages/react-native/ReactCommon/react/renderer/css/CSSDataType.h index 3147bb39af86b0..ac533422ee1615 100644 --- a/packages/react-native/ReactCommon/react/renderer/css/CSSDataType.h +++ b/packages/react-native/ReactCommon/react/renderer/css/CSSDataType.h @@ -48,29 +48,27 @@ concept CSSSimpleBlockSink = * concrete representation. */ template -concept CSSPreservedTokenSink = - requires(const CSSPreservedToken& token, CSSSyntaxParser& parser) { - { - T::consumePreservedToken(token, parser) - } -> std::convertible_to; - }; +concept CSSPreservedTokenSink = requires(const CSSPreservedToken& token) { + { T::consumePreservedToken(token) } -> std::convertible_to; +}; /** - * Accepts a CSS preserved token and may parse it into a concrete - * representation. + * Accepts a raw syntax parser, to be able to parse compounded values */ template -concept CSSSimplePreservedTokenSink = requires(const CSSPreservedToken& token) { - { T::consumePreservedToken(token) } -> std::convertible_to; +concept CSSParserSink = requires(CSSSyntaxParser& parser) { + { T::consume(parser) } -> std::convertible_to; }; /** * Represents a valid specialization of CSSDataTypeParser */ template -concept CSSValidDataTypeParser = CSSFunctionBlockSink || - CSSSimpleBlockSink || CSSPreservedTokenSink || - CSSSimplePreservedTokenSink; +concept CSSValidDataTypeParser = + ((CSSFunctionBlockSink || CSSSimpleBlockSink || + CSSPreservedTokenSink) && + !CSSParserSink) || + CSSParserSink; /** * Concrete representation for a CSS data type diff --git a/packages/react-native/ReactCommon/react/renderer/css/CSSKeyword.h b/packages/react-native/ReactCommon/react/renderer/css/CSSKeyword.h index 07bca3f5e99acc..c9dc5c9693542c 100644 --- a/packages/react-native/ReactCommon/react/renderer/css/CSSKeyword.h +++ b/packages/react-native/ReactCommon/react/renderer/css/CSSKeyword.h @@ -307,6 +307,11 @@ constexpr std::optional parseCSSKeyword(std::string_view ident) { return KeywordT::InlineGrid; } break; + case fnv1a("inset"): + if constexpr (detail::hasInset) { + return KeywordT::Inset; + } + break; case fnv1a("ltr"): if constexpr (detail::hasLtr) { return KeywordT::Ltr; diff --git a/packages/react-native/ReactCommon/react/renderer/css/CSSRatio.h b/packages/react-native/ReactCommon/react/renderer/css/CSSRatio.h index 92111bbcc507f0..9adb77a8cd9216 100644 --- a/packages/react-native/ReactCommon/react/renderer/css/CSSRatio.h +++ b/packages/react-native/ReactCommon/react/renderer/css/CSSRatio.h @@ -39,23 +39,26 @@ struct CSSRatio { template <> struct CSSDataTypeParser { - static constexpr auto consumePreservedToken( - const CSSPreservedToken& token, - CSSSyntaxParser& parser) -> std::optional { + static constexpr auto consume(CSSSyntaxParser& parser) + -> std::optional { // = [ / ]? // https://www.w3.org/TR/css-values-4/#ratio - if (token.numericValue() >= 0) { - float numerator = token.numericValue(); + auto numerator = parseNextCSSValue(parser); + if (!std::holds_alternative(numerator)) { + return {}; + } + auto numeratorValue = std::get(numerator).value; + if (numeratorValue >= 0) { auto denominator = peekNextCSSValue(parser, CSSDelimiter::Solidus); if (std::holds_alternative(denominator) && std::get(denominator).value >= 0) { parseNextCSSValue(parser, CSSDelimiter::Solidus); - return CSSRatio{numerator, std::get(denominator).value}; + return CSSRatio{numeratorValue, std::get(denominator).value}; } - return CSSRatio{numerator, 1.0f}; + return CSSRatio{numeratorValue, 1.0f}; } return {}; diff --git a/packages/react-native/ReactCommon/react/renderer/css/CSSSyntaxParser.h b/packages/react-native/ReactCommon/react/renderer/css/CSSSyntaxParser.h index 22c96834b9f971..9b84a75461123a 100644 --- a/packages/react-native/ReactCommon/react/renderer/css/CSSSyntaxParser.h +++ b/packages/react-native/ReactCommon/react/renderer/css/CSSSyntaxParser.h @@ -35,6 +35,13 @@ struct CSSSimpleBlock { CSSTokenType openBracketType{}; }; +/** + * Describes a valid return type for a CSSSyntaxParser visitor + */ +template +concept CSSSyntaxVisitorReturn = + std::is_default_constructible_v && std::equality_comparable; + /** * A CSSFunctionVisitor is called to start parsing a function component value. * At this point, the Parser is positioned at the start of the function @@ -42,7 +49,7 @@ struct CSSSimpleBlock { * of the function block. */ template -concept CSSFunctionVisitor = +concept CSSFunctionVisitor = CSSSyntaxVisitorReturn && requires(T visitor, CSSFunctionBlock func, CSSSyntaxParser& blockParser) { { visitor(func, blockParser) } -> std::convertible_to; }; @@ -52,7 +59,7 @@ concept CSSFunctionVisitor = * component value. */ template -concept CSSPreservedTokenVisitor = +concept CSSPreservedTokenVisitor = CSSSyntaxVisitorReturn && requires(T visitor, CSSPreservedToken token) { { visitor(token) } -> std::convertible_to; }; @@ -63,7 +70,7 @@ concept CSSPreservedTokenVisitor = * of the block. */ template -concept CSSSimpleBlockVisitor = +concept CSSSimpleBlockVisitor = CSSSyntaxVisitorReturn && requires(T visitor, CSSSimpleBlock block, CSSSyntaxParser& blockParser) { { visitor(block, blockParser) } -> std::convertible_to; }; @@ -72,16 +79,17 @@ concept CSSSimpleBlockVisitor = * Any visitor for a component value. */ template -concept CSSComponentValueVisitor = CSSFunctionVisitor || - CSSPreservedTokenVisitor || CSSSimpleBlockVisitor; +concept CSSComponentValueVisitor = CSSSyntaxVisitorReturn && + (CSSFunctionVisitor || CSSPreservedTokenVisitor || + CSSSimpleBlockVisitor); /** * Represents a variadic set of CSSComponentValueVisitor with no more than one * of a specific type of visitor. */ template -concept CSSUniqueComponentValueVisitors = - (CSSComponentValueVisitor && ... && true) && +concept CSSUniqueComponentValueVisitors = CSSSyntaxVisitorReturn && + (CSSComponentValueVisitor && ...) && ((CSSFunctionVisitor ? 1 : 0) + ... + 0) <= 1 && ((CSSPreservedTokenVisitor ? 1 : 0) + ... + 0) <= 1 && ((CSSSimpleBlockVisitor ? 1 : 0) + ... + 0) <= 1; @@ -106,7 +114,9 @@ enum class CSSDelimiter { * https://www.w3.org/TR/css-syntax-3/#component-value */ class CSSSyntaxParser { - template ... VisitorsT> + template < + CSSSyntaxVisitorReturn ReturnT, + CSSComponentValueVisitor... VisitorsT> friend struct CSSComponentValueVisitorDispatcher; public: @@ -130,6 +140,11 @@ class CSSSyntaxParser { * higher-level data structure, and continue parsing within its scope using * the same underlying CSSSyntaxParser. * + * The state of the parser is reset if a visitor returns a default-constructed + * value for the given return type, even if it previously advanced the parser. + * If no visitor returns a non-default-constructed value, the component value + * will not be consumed. + * * https://www.w3.org/TR/css-syntax-3/#consume-component-value * * @param caller-specified return type of visitors. This type will @@ -142,47 +157,16 @@ class CSSSyntaxParser { * @returns the visitor returned value, or a default constructed value if no * visitor was matched, or a syntax error occurred. */ - template + template constexpr ReturnT consumeComponentValue( CSSDelimiter delimiter, const CSSComponentValueVisitor auto&... visitors) requires(CSSUniqueComponentValueVisitors); - template + template constexpr ReturnT consumeComponentValue( const CSSComponentValueVisitor auto&... visitors) requires(CSSUniqueComponentValueVisitors); - - /** - * Peek at the next component value without consuming it. The component value - * is provided to a passed in "visitor", typically a lambda which accepts the - * component value in a new scope. The visitor may read this component - * parameter into a higher-level data structure, and continue parsing within - * its scope using the same underlying CSSSyntaxParser. - * - * https://www.w3.org/TR/css-syntax-3/#consume-component-value - * - * @param caller-specified return type of visitors. This type will - * be set to its default constructed state if consuming a component value with - * no matching visitors, or syntax error - * @param visitors A unique list of CSSComponentValueVisitor to be called on a - * match - * @param delimiter The expected delimeter to occur before the next component - * value - * @returns the visitor returned value, or a default constructed value if no - * visitor was matched, or a syntax error occurred. - */ - template - constexpr ReturnT peekComponentValue( - CSSDelimiter delimiter, - const CSSComponentValueVisitor auto&... visitors) - requires(CSSUniqueComponentValueVisitors); - - template - constexpr ReturnT peekComponentValue( - const CSSComponentValueVisitor auto&... visitors) - requires(CSSUniqueComponentValueVisitors); - /** * The parser is considered finished when there are no more remaining tokens * to be processed @@ -200,6 +184,59 @@ class CSSSyntaxParser { } } + /** + * Consume a delimiter, returning false if a required delimiter is not found. + */ + constexpr bool consumeDelimiter(CSSDelimiter delimiter) { + if (delimiter == CSSDelimiter::None) { + return true; + } + + bool hasWhiteSpace = peek().type() == CSSTokenType::WhiteSpace; + consumeWhitespace(); + + switch (delimiter) { + case CSSDelimiter::Comma: + if (peek().type() == CSSTokenType::Comma) { + consumeToken(); + consumeWhitespace(); + return true; + } + return false; + case CSSDelimiter::Whitespace: + return hasWhiteSpace; + case CSSDelimiter::OptionalWhitespace: + return true; + case CSSDelimiter::CommaOrWhitespace: + if (peek().type() == CSSTokenType::Comma) { + consumeToken(); + consumeWhitespace(); + return true; + } + return hasWhiteSpace; + case CSSDelimiter::Solidus: + if (peek().type() == CSSTokenType::Delim && + peek().stringValue() == "/") { + consumeToken(); + consumeWhitespace(); + return true; + } + return false; + case CSSDelimiter::SolidusOrWhitespace: + if (peek().type() == CSSTokenType::Delim && + peek().stringValue() == "/") { + consumeToken(); + consumeWhitespace(); + return true; + } + return hasWhiteSpace; + case CSSDelimiter::None: + return true; + } + + return false; + } + private: constexpr CSSSyntaxParser(CSSSyntaxParser& parser, CSSTokenType terminator) : tokenizer_{parser.tokenizer_}, @@ -226,7 +263,9 @@ class CSSSyntaxParser { CSSTokenType terminator_{CSSTokenType::EndOfFile}; }; -template ... VisitorsT> +template < + CSSSyntaxVisitorReturn ReturnT, + CSSComponentValueVisitor... VisitorsT> struct CSSComponentValueVisitorDispatcher { CSSSyntaxParser& parser; @@ -234,7 +273,7 @@ struct CSSComponentValueVisitorDispatcher { CSSDelimiter delimiter, const VisitorsT&... visitors) { auto originalParser = parser; - if (!consumeDelimiter(delimiter)) { + if (!parser.consumeDelimiter(delimiter)) { parser = originalParser; return {}; } @@ -275,69 +314,8 @@ struct CSSComponentValueVisitorDispatcher { break; } - return ReturnT{}; - } - - /** - * Consume a delimiter, returning false if a required delimiter is not found. - */ - constexpr bool consumeDelimiter(CSSDelimiter delimiter) { - if (delimiter == CSSDelimiter::None) { - return true; - } - - bool hasWhiteSpace = parser.peek().type() == CSSTokenType::WhiteSpace; - parser.consumeWhitespace(); - - switch (delimiter) { - case CSSDelimiter::Comma: - if (parser.peek().type() == CSSTokenType::Comma) { - parser.consumeToken(); - parser.consumeWhitespace(); - return true; - } - return false; - case CSSDelimiter::Whitespace: - return hasWhiteSpace; - case CSSDelimiter::OptionalWhitespace: - return true; - case CSSDelimiter::CommaOrWhitespace: - if (parser.peek().type() == CSSTokenType::Comma) { - parser.consumeToken(); - parser.consumeWhitespace(); - return true; - } - return hasWhiteSpace; - case CSSDelimiter::Solidus: - if (parser.peek().type() == CSSTokenType::Delim && - parser.peek().stringValue() == "/") { - parser.consumeToken(); - parser.consumeWhitespace(); - return true; - } - return false; - case CSSDelimiter::SolidusOrWhitespace: - if (parser.peek().type() == CSSTokenType::Delim && - parser.peek().stringValue() == "/") { - parser.consumeToken(); - parser.consumeWhitespace(); - return true; - } - return hasWhiteSpace; - case CSSDelimiter::None: - return true; - } - - return false; - } - - constexpr ReturnT peekComponentValue( - CSSDelimiter delimiter, - const VisitorsT&... visitors) { - auto originalParser = parser; - auto ret = consumeComponentValue(delimiter, visitors...); parser = originalParser; - return ret; + return ReturnT{}; } constexpr std::optional visitFunction( @@ -357,7 +335,8 @@ struct CSSComponentValueVisitorDispatcher { auto functionValue = visitor({name}, blockParser); parser.advanceToBlockParser(blockParser); parser.consumeWhitespace(); - if (parser.peek().type() == CSSTokenType::CloseParen) { + if (parser.peek().type() == CSSTokenType::CloseParen && + functionValue != ReturnT{}) { parser.consumeToken(); return functionValue; } @@ -369,11 +348,6 @@ struct CSSComponentValueVisitorDispatcher { } constexpr std::optional visitFunction() { - while (parser.peek().type() != CSSTokenType::CloseParen) { - parser.consumeToken(); - } - parser.consumeToken(); - return {}; } @@ -388,7 +362,7 @@ struct CSSComponentValueVisitorDispatcher { auto blockValue = visitor({openBracketType}, blockParser); parser.advanceToBlockParser(blockParser); parser.consumeWhitespace(); - if (parser.peek().type() == endToken) { + if (parser.peek().type() == endToken && blockValue != ReturnT{}) { parser.consumeToken(); return blockValue; } @@ -399,10 +373,6 @@ struct CSSComponentValueVisitorDispatcher { } constexpr std::optional visitSimpleBlock(CSSTokenType endToken) { - while (parser.peek().type() != endToken) { - parser.consumeToken(); - } - parser.consumeToken(); return {}; } @@ -410,18 +380,20 @@ struct CSSComponentValueVisitorDispatcher { const CSSComponentValueVisitor auto& visitor, const CSSComponentValueVisitor auto&... rest) { if constexpr (CSSPreservedTokenVisitor) { - return visitor(parser.consumeToken()); + auto ret = visitor(parser.consumeToken()); + if (ret != ReturnT{}) { + return ret; + } } return visitPreservedToken(rest...); } constexpr std::optional visitPreservedToken() { - parser.consumeToken(); return {}; } }; -template +template constexpr ReturnT CSSSyntaxParser::consumeComponentValue( CSSDelimiter delimiter, const CSSComponentValueVisitor auto&... visitors) @@ -432,7 +404,7 @@ constexpr ReturnT CSSSyntaxParser::consumeComponentValue( .consumeComponentValue(delimiter, visitors...); } -template +template constexpr ReturnT CSSSyntaxParser::consumeComponentValue( const CSSComponentValueVisitor auto&... visitors) requires(CSSUniqueComponentValueVisitors) @@ -440,23 +412,4 @@ constexpr ReturnT CSSSyntaxParser::consumeComponentValue( return consumeComponentValue(CSSDelimiter::None, visitors...); } -template -constexpr ReturnT CSSSyntaxParser::peekComponentValue( - CSSDelimiter delimiter, - const CSSComponentValueVisitor auto&... visitors) - requires(CSSUniqueComponentValueVisitors) -{ - return CSSComponentValueVisitorDispatcher{ - *this} - .peekComponentValue(delimiter, visitors...); -} - -template -constexpr ReturnT CSSSyntaxParser::peekComponentValue( - const CSSComponentValueVisitor auto&... visitors) - requires(CSSUniqueComponentValueVisitors) -{ - return peekComponentValue(CSSDelimiter::None, visitors...); -} - } // namespace facebook::react diff --git a/packages/react-native/ReactCommon/react/renderer/css/CSSValueParser.h b/packages/react-native/ReactCommon/react/renderer/css/CSSValueParser.h index 7ce6935e7c690d..bfd9cd65566b44 100644 --- a/packages/react-native/ReactCommon/react/renderer/css/CSSValueParser.h +++ b/packages/react-native/ReactCommon/react/renderer/css/CSSValueParser.h @@ -35,6 +35,14 @@ class CSSValueParser { CSSDelimiter delimeter = CSSDelimiter::None) { using ReturnT = std::variant; + auto consumedValue = + tryConsumeParser...>( + delimeter); + + if (!std::holds_alternative(consumedValue)) { + return consumedValue; + } + return parser_.consumeComponentValue( delimeter, [&](const CSSPreservedToken& token) { @@ -75,14 +83,6 @@ class CSSValueParser { CSSValidDataTypeParser... RestParserT> constexpr ReturnT tryConsumePreservedToken(const CSSPreservedToken& token) { if constexpr (CSSPreservedTokenSink) { - auto currentParser = parser_; - if (auto ret = ParserT::consumePreservedToken(token, parser_)) { - return *ret; - } - parser_ = currentParser; - } - - if constexpr (CSSSimplePreservedTokenSink) { if (auto ret = ParserT::consumePreservedToken(token)) { return *ret; } @@ -141,6 +141,29 @@ class CSSValueParser { return tryConsumeFunctionBlock(func, blockParser); } + template + constexpr ReturnT tryConsumeParser(CSSDelimiter /*delimeter*/) { + return {}; + } + + template < + typename ReturnT, + CSSValidDataTypeParser ParserT, + CSSValidDataTypeParser... RestParserT> + constexpr ReturnT tryConsumeParser(CSSDelimiter delimeter) { + if constexpr (CSSParserSink) { + auto originalParser = parser_; + if (parser_.consumeDelimiter(delimeter)) { + if (auto ret = ParserT::consume(parser_)) { + return *ret; + } + } + parser_ = originalParser; + } + + return tryConsumeParser(delimeter); + } + CSSSyntaxParser& parser_; }; diff --git a/packages/react-native/ReactCommon/react/renderer/css/tests/CSSCommaSeparatedListTest.cpp b/packages/react-native/ReactCommon/react/renderer/css/tests/CSSCommaSeparatedListTest.cpp new file mode 100644 index 00000000000000..d96ef2f2073d7c --- /dev/null +++ b/packages/react-native/ReactCommon/react/renderer/css/tests/CSSCommaSeparatedListTest.cpp @@ -0,0 +1,92 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +#include +#include +#include +#include + +namespace facebook::react { + +TEST(CSSCommaSeparatedList, empty_values) { + auto emptyValue = parseCSSProperty>(""); + EXPECT_TRUE(std::holds_alternative(emptyValue)); + + auto whitespaceValue = + parseCSSProperty>(" "); + EXPECT_TRUE(std::holds_alternative(whitespaceValue)); + + auto commaValue = parseCSSProperty>(","); +} + +TEST(CSSCommaSeparatedList, single_value) { + auto simpleValue = parseCSSProperty>("20"); + EXPECT_TRUE( + std::holds_alternative>(simpleValue)); + EXPECT_EQ(std::get>(simpleValue).size(), 1); + EXPECT_EQ( + std::get>(simpleValue)[0].value, 20); + + auto whitespaceValue = + parseCSSProperty>(" 20 "); + EXPECT_TRUE(std::holds_alternative>( + whitespaceValue)); + EXPECT_EQ( + std::get>(whitespaceValue).size(), 1); + EXPECT_EQ( + std::get>(whitespaceValue)[0].value, 20); +} + +TEST(CSSCommaSeparatedList, wrong_type) { + auto simpleValue = parseCSSProperty>("20px"); + EXPECT_TRUE(std::holds_alternative(simpleValue)); +} + +TEST(CSSCommaSeparatedList, multiple_values) { + auto simpleValue = + parseCSSProperty>("20, 30, 40"); + EXPECT_TRUE( + std::holds_alternative>(simpleValue)); + EXPECT_EQ(std::get>(simpleValue).size(), 3); + EXPECT_EQ( + std::get>(simpleValue)[0].value, 20); + EXPECT_EQ( + std::get>(simpleValue)[1].value, 30); + EXPECT_EQ( + std::get>(simpleValue)[2].value, 40); + + auto whitespaceValue = + parseCSSProperty>(" 20 , 30 , 40 "); + EXPECT_TRUE(std::holds_alternative>( + whitespaceValue)); + EXPECT_EQ( + std::get>(whitespaceValue).size(), 3); + EXPECT_EQ( + std::get>(whitespaceValue)[0].value, 20); + EXPECT_EQ( + std::get>(whitespaceValue)[1].value, 30); + EXPECT_EQ( + std::get>(whitespaceValue)[2].value, 40); +} + +TEST(CSSCommaSeparatedList, extra_tokens) { + auto extraTokensValue = + parseCSSProperty>("20, 30, 40 50"); + EXPECT_TRUE(std::holds_alternative(extraTokensValue)); +} + +TEST(CSSCommaSeparatedList, extra_commas) { + auto prefixCommaValue = + parseCSSProperty>(",20"); + EXPECT_TRUE(std::holds_alternative(prefixCommaValue)); + + auto suffixCommaValue = + parseCSSProperty>("20,"); + EXPECT_TRUE(std::holds_alternative(suffixCommaValue)); +} + +} // namespace facebook::react diff --git a/packages/react-native/ReactCommon/react/renderer/css/tests/CSSSyntaxParserTest.cpp b/packages/react-native/ReactCommon/react/renderer/css/tests/CSSSyntaxParserTest.cpp index 86336b104de1e3..494d4fc464ea08 100644 --- a/packages/react-native/ReactCommon/react/renderer/css/tests/CSSSyntaxParserTest.cpp +++ b/packages/react-native/ReactCommon/react/renderer/css/tests/CSSSyntaxParserTest.cpp @@ -382,88 +382,6 @@ TEST(CSSSyntaxParser, unconsumed_simple_block_args) { EXPECT_EQ(funcValue, std::nullopt); } -TEST(CSSSyntaxParser, peek_does_not_consume) { - CSSSyntaxParser parser{"foo()"}; - auto funcValue = parser.peekComponentValue>( - [&](const CSSFunctionBlock& function, CSSSyntaxParser& /*blockParser*/) { - EXPECT_EQ(function.name, "foo"); - return function.name; - }); - - EXPECT_EQ(funcValue, "foo"); - - auto funcValue2 = parser.peekComponentValue>( - [&](const CSSFunctionBlock& function, CSSSyntaxParser& /*blockParser*/) { - EXPECT_EQ(function.name, "foo"); - return function.name; - }); - - EXPECT_EQ(funcValue2, "foo"); - - auto funcValue3 = - parser.consumeComponentValue>( - [&](const CSSFunctionBlock& function, - CSSSyntaxParser& /*blockParser*/) { - EXPECT_EQ(function.name, "foo"); - return function.name; - }); - - EXPECT_EQ(funcValue3, "foo"); - - auto funcValue4 = parser.peekComponentValue>( - [&](const CSSFunctionBlock& function, CSSSyntaxParser& /*blockParser*/) { - EXPECT_EQ(function.name, "foo"); - return function.name; - }); - - EXPECT_EQ(funcValue4, std::nullopt); -} - -TEST(CSSSyntaxParser, preserved_token_without_visitor_consumed) { - CSSSyntaxParser parser{"foo bar"}; - - parser.consumeComponentValue(); - - auto identValue = parser.consumeComponentValue( - CSSDelimiter::Whitespace, [](const CSSPreservedToken& token) { - EXPECT_EQ(token.type(), CSSTokenType::Ident); - EXPECT_EQ(token.stringValue(), "bar"); - return token.stringValue(); - }); - - EXPECT_EQ(identValue, "bar"); -} - -TEST(CSSSyntaxParser, function_without_visitor_consumed) { - CSSSyntaxParser parser{"foo(a, b, c) bar"}; - - parser.consumeComponentValue(); - - auto identValue = parser.consumeComponentValue( - CSSDelimiter::Whitespace, [](const CSSPreservedToken& token) { - EXPECT_EQ(token.type(), CSSTokenType::Ident); - EXPECT_EQ(token.stringValue(), "bar"); - return token.stringValue(); - }); - - EXPECT_EQ(identValue, "bar"); -} - -TEST(CSSSyntaxParser, simple_block_without_visitor_consumed) { - CSSSyntaxParser parser{"{a foo(abc)} bar"}; - - parser.consumeComponentValue(); - - auto identValue = parser.consumeComponentValue( - CSSDelimiter::Whitespace, [](const CSSPreservedToken& token) { - EXPECT_EQ(token.type(), CSSTokenType::Ident); - EXPECT_EQ(token.stringValue(), "bar"); - return token.stringValue(); - }); - - EXPECT_EQ(identValue, "bar"); -} - TEST(CSSSyntaxParser, solidus_delimiter) { CSSSyntaxParser parser{"foo / bar"}; @@ -606,4 +524,39 @@ TEST(CSSSyntaxParser, delimeter_not_consumed_when_no_component_value) { EXPECT_TRUE(hasComma); } +TEST(CSSSyntaxParser, component_value_not_consumed_on_visitor_failure) { + CSSSyntaxParser parser{"foo"}; + + bool visitor1Attempted = false; + bool visitor1Ret = + parser.consumeComponentValue([&](const CSSPreservedToken& token) { + EXPECT_EQ(token.stringValue(), "foo"); + visitor1Attempted = true; + return false; + }); + + EXPECT_TRUE(visitor1Attempted); + EXPECT_FALSE(visitor1Ret); + + bool visitor2Attempted = false; + parser.consumeComponentValue([&](const CSSPreservedToken& token) { + EXPECT_EQ(token.stringValue(), "foo"); + visitor2Attempted = true; + return true; + }); + + EXPECT_TRUE(visitor2Attempted); + EXPECT_TRUE(visitor2Attempted); + + bool visitor3Attempted = false; + bool visitor3Ret = + parser.consumeComponentValue([&](const CSSPreservedToken& token) { + visitor3Attempted = true; + return true; + }); + + EXPECT_FALSE(visitor3Attempted); + EXPECT_FALSE(visitor3Ret); +} + } // namespace facebook::react diff --git a/packages/react-native/ReactCommon/react/renderer/css/tests/CSSValueParserTest.cpp b/packages/react-native/ReactCommon/react/renderer/css/tests/CSSValueParserTest.cpp new file mode 100644 index 00000000000000..b2384abbb94ca5 --- /dev/null +++ b/packages/react-native/ReactCommon/react/renderer/css/tests/CSSValueParserTest.cpp @@ -0,0 +1,66 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +#include +#include +#include +#include + +namespace facebook::react { + +struct ConsumeDataType { + float number{}; + + constexpr bool operator==(const ConsumeDataType& other) const = default; +}; + +template <> +struct CSSDataTypeParser { + constexpr static std::optional consume( + CSSSyntaxParser& parser) { + auto val = parseNextCSSValue(parser); + if (std::holds_alternative(val)) { + return ConsumeDataType{std::get(val).value}; + } + + return {}; + } +}; + +static_assert(CSSDataType); + +TEST(CSSValueParser, consume_multiple_with_delimeter) { + CSSSyntaxParser parser{"1 2, 3, 4 / 5"}; + + auto next = parseNextCSSValue(parser); + EXPECT_TRUE(std::holds_alternative(next)); + EXPECT_EQ(std::get(next).number, 1); + + next = parseNextCSSValue(parser, CSSDelimiter::None); + EXPECT_FALSE(std::holds_alternative(next)); + + next = parseNextCSSValue(parser, CSSDelimiter::Whitespace); + EXPECT_TRUE(std::holds_alternative(next)); + EXPECT_EQ(std::get(next).number, 2); + + next = parseNextCSSValue(parser, CSSDelimiter::Comma); + EXPECT_TRUE(std::holds_alternative(next)); + EXPECT_EQ(std::get(next).number, 3); + + next = parseNextCSSValue(parser, CSSDelimiter::Comma); + EXPECT_TRUE(std::holds_alternative(next)); + EXPECT_EQ(std::get(next).number, 4); + + next = parseNextCSSValue(parser, CSSDelimiter::Solidus); + EXPECT_TRUE(std::holds_alternative(next)); + EXPECT_EQ(std::get(next).number, 5); + + next = parseNextCSSValue(parser); + EXPECT_FALSE(std::holds_alternative(next)); +} + +} // namespace facebook::react