From d253d2957107fa59ec4258e37961712561145bed Mon Sep 17 00:00:00 2001 From: Nick Gerleman Date: Mon, 27 Jan 2025 17:35:13 -0800 Subject: [PATCH] CSSDataTypeParser consume() function Summary: This adds a new `consume()` function to data type parsers which passes a raw parser. This can be used for types which are compounds of other data types, where we may want to accept more than the first token. This will be used for shadow parsing, but also fixes a hypothetical future bug with ratios. E.g. `calc(foo) / calc(bar)` may be a valid ratio, not starting with a token. We instead just want to try to parse a number data type from the stream. The form of parsing a preserved token + rest is removed, with the assumption that anything parsing more than a single token should use compound parsing. Differential Revision: D68735370 --- .../react/renderer/css/CSSDataType.h | 24 ++-- .../ReactCommon/react/renderer/css/CSSRatio.h | 17 +-- .../react/renderer/css/CSSSyntaxParser.h | 108 +++++++++--------- .../react/renderer/css/CSSValueParser.h | 39 +++++-- 4 files changed, 106 insertions(+), 82 deletions(-) diff --git a/packages/react-native/ReactCommon/react/renderer/css/CSSDataType.h b/packages/react-native/ReactCommon/react/renderer/css/CSSDataType.h index 111b208bc5d783..5b6a0788e566a1 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, or keywords diff --git a/packages/react-native/ReactCommon/react/renderer/css/CSSRatio.h b/packages/react-native/ReactCommon/react/renderer/css/CSSRatio.h index a2d14978863a48..99844b8590cc18 100644 --- a/packages/react-native/ReactCommon/react/renderer/css/CSSRatio.h +++ b/packages/react-native/ReactCommon/react/renderer/css/CSSRatio.h @@ -37,23 +37,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 9fa449361e7ae1..dfb8f6ea31218c 100644 --- a/packages/react-native/ReactCommon/react/renderer/css/CSSSyntaxParser.h +++ b/packages/react-native/ReactCommon/react/renderer/css/CSSSyntaxParser.h @@ -173,6 +173,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_}, @@ -207,7 +260,7 @@ struct CSSComponentValueVisitorDispatcher { CSSDelimiter delimiter, const VisitorsT&... visitors) { auto originalParser = parser; - if (!consumeDelimiter(delimiter)) { + if (!parser.consumeDelimiter(delimiter)) { parser = originalParser; return {}; } @@ -252,59 +305,6 @@ struct CSSComponentValueVisitorDispatcher { 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 std::optional visitFunction( const CSSComponentValueVisitor auto& visitor, const CSSComponentValueVisitor auto&... rest) { diff --git a/packages/react-native/ReactCommon/react/renderer/css/CSSValueParser.h b/packages/react-native/ReactCommon/react/renderer/css/CSSValueParser.h index 7ce6935e7c690d..f17f427bc6c0af 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 currentParser = parser_; + if (parser_.consumeDelimiter(delimeter)) { + if (auto ret = ParserT::consume(parser_)) { + return *ret; + } + } + parser_ = currentParser; + } + + return tryConsumeParser(delimeter); + } + CSSSyntaxParser& parser_; };