Skip to content

Commit

Permalink
CSSDataTypeParser consume() function (#48986)
Browse files Browse the repository at this point in the history
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.

Reviewed By: lenaic

Differential Revision: D68735370
  • Loading branch information
NickGerleman authored and facebook-github-bot committed Jan 29, 2025
1 parent 51816ac commit 08e9057
Show file tree
Hide file tree
Showing 5 changed files with 174 additions and 84 deletions.
24 changes: 11 additions & 13 deletions packages/react-native/ReactCommon/react/renderer/css/CSSDataType.h
Original file line number Diff line number Diff line change
Expand Up @@ -48,29 +48,27 @@ concept CSSSimpleBlockSink =
* concrete representation.
*/
template <typename T, typename ReturnT = std::any>
concept CSSPreservedTokenSink =
requires(const CSSPreservedToken& token, CSSSyntaxParser& parser) {
{
T::consumePreservedToken(token, parser)
} -> std::convertible_to<ReturnT>;
};
concept CSSPreservedTokenSink = requires(const CSSPreservedToken& token) {
{ T::consumePreservedToken(token) } -> std::convertible_to<ReturnT>;
};

/**
* 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 <typename T, typename ReturnT = std::any>
concept CSSSimplePreservedTokenSink = requires(const CSSPreservedToken& token) {
{ T::consumePreservedToken(token) } -> std::convertible_to<ReturnT>;
concept CSSParserSink = requires(CSSSyntaxParser& parser) {
{ T::consume(parser) } -> std::convertible_to<ReturnT>;
};

/**
* Represents a valid specialization of CSSDataTypeParser
*/
template <typename T, typename ReturnT = std::any>
concept CSSValidDataTypeParser = CSSFunctionBlockSink<T, ReturnT> ||
CSSSimpleBlockSink<T, ReturnT> || CSSPreservedTokenSink<T, ReturnT> ||
CSSSimplePreservedTokenSink<T, ReturnT>;
concept CSSValidDataTypeParser =
((CSSFunctionBlockSink<T, ReturnT> || CSSSimpleBlockSink<T, ReturnT> ||
CSSPreservedTokenSink<T, ReturnT>) &&
!CSSParserSink<T, ReturnT>) ||
CSSParserSink<T, ReturnT>;

/**
* Concrete representation for a CSS data type
Expand Down
17 changes: 10 additions & 7 deletions packages/react-native/ReactCommon/react/renderer/css/CSSRatio.h
Original file line number Diff line number Diff line change
Expand Up @@ -39,23 +39,26 @@ struct CSSRatio {

template <>
struct CSSDataTypeParser<CSSRatio> {
static constexpr auto consumePreservedToken(
const CSSPreservedToken& token,
CSSSyntaxParser& parser) -> std::optional<CSSRatio> {
static constexpr auto consume(CSSSyntaxParser& parser)
-> std::optional<CSSRatio> {
// <ratio> = <number [0,∞]> [ / <number [0,∞]> ]?
// https://www.w3.org/TR/css-values-4/#ratio
if (token.numericValue() >= 0) {
float numerator = token.numericValue();
auto numerator = parseNextCSSValue<CSSNumber>(parser);
if (!std::holds_alternative<CSSNumber>(numerator)) {
return {};
}

auto numeratorValue = std::get<CSSNumber>(numerator).value;
if (numeratorValue >= 0) {
auto denominator =
peekNextCSSValue<CSSNumber>(parser, CSSDelimiter::Solidus);
if (std::holds_alternative<CSSNumber>(denominator) &&
std::get<CSSNumber>(denominator).value >= 0) {
parseNextCSSValue<CSSNumber>(parser, CSSDelimiter::Solidus);
return CSSRatio{numerator, std::get<CSSNumber>(denominator).value};
return CSSRatio{numeratorValue, std::get<CSSNumber>(denominator).value};
}

return CSSRatio{numerator, 1.0f};
return CSSRatio{numeratorValue, 1.0f};
}

return {};
Expand Down
112 changes: 56 additions & 56 deletions packages/react-native/ReactCommon/react/renderer/css/CSSSyntaxParser.h
Original file line number Diff line number Diff line change
Expand Up @@ -184,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_},
Expand Down Expand Up @@ -220,7 +273,7 @@ struct CSSComponentValueVisitorDispatcher {
CSSDelimiter delimiter,
const VisitorsT&... visitors) {
auto originalParser = parser;
if (!consumeDelimiter(delimiter)) {
if (!parser.consumeDelimiter(delimiter)) {
parser = originalParser;
return {};
}
Expand Down Expand Up @@ -265,59 +318,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<ReturnT> visitFunction(
const CSSComponentValueVisitor<ReturnT> auto& visitor,
const CSSComponentValueVisitor<ReturnT> auto&... rest) {
Expand Down Expand Up @@ -393,7 +393,7 @@ struct CSSComponentValueVisitorDispatcher {
}
};

template <typename ReturnT>
template <CSSSyntaxVisitorReturn ReturnT>
constexpr ReturnT CSSSyntaxParser::consumeComponentValue(
CSSDelimiter delimiter,
const CSSComponentValueVisitor<ReturnT> auto&... visitors)
Expand All @@ -404,7 +404,7 @@ constexpr ReturnT CSSSyntaxParser::consumeComponentValue(
.consumeComponentValue(delimiter, visitors...);
}

template <typename ReturnT>
template <CSSSyntaxVisitorReturn ReturnT>
constexpr ReturnT CSSSyntaxParser::consumeComponentValue(
const CSSComponentValueVisitor<ReturnT> auto&... visitors)
requires(CSSUniqueComponentValueVisitors<ReturnT, decltype(visitors)...>)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,14 @@ class CSSValueParser {
CSSDelimiter delimeter = CSSDelimiter::None) {
using ReturnT = std::variant<std::monostate, AllowedTypesT...>;

auto consumedValue =
tryConsumeParser<ReturnT, CSSDataTypeParser<AllowedTypesT>...>(
delimeter);

if (!std::holds_alternative<std::monostate>(consumedValue)) {
return consumedValue;
}

return parser_.consumeComponentValue<ReturnT>(
delimeter,
[&](const CSSPreservedToken& token) {
Expand Down Expand Up @@ -75,14 +83,6 @@ class CSSValueParser {
CSSValidDataTypeParser... RestParserT>
constexpr ReturnT tryConsumePreservedToken(const CSSPreservedToken& token) {
if constexpr (CSSPreservedTokenSink<ParserT>) {
auto currentParser = parser_;
if (auto ret = ParserT::consumePreservedToken(token, parser_)) {
return *ret;
}
parser_ = currentParser;
}

if constexpr (CSSSimplePreservedTokenSink<ParserT>) {
if (auto ret = ParserT::consumePreservedToken(token)) {
return *ret;
}
Expand Down Expand Up @@ -141,6 +141,29 @@ class CSSValueParser {
return tryConsumeFunctionBlock<ReturnT, RestParserT...>(func, blockParser);
}

template <typename ReturnT>
constexpr ReturnT tryConsumeParser(CSSDelimiter /*delimeter*/) {
return {};
}

template <
typename ReturnT,
CSSValidDataTypeParser ParserT,
CSSValidDataTypeParser... RestParserT>
constexpr ReturnT tryConsumeParser(CSSDelimiter delimeter) {
if constexpr (CSSParserSink<ParserT>) {
auto originalParser = parser_;
if (parser_.consumeDelimiter(delimeter)) {
if (auto ret = ParserT::consume(parser_)) {
return *ret;
}
}
parser_ = originalParser;
}

return tryConsumeParser<ReturnT, RestParserT...>(delimeter);
}

CSSSyntaxParser& parser_;
};

Expand Down
Original file line number Diff line number Diff line change
@@ -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 <gtest/gtest.h>
#include <react/renderer/css/CSSDataType.h>
#include <react/renderer/css/CSSNumber.h>
#include <react/renderer/css/CSSValueParser.h>

namespace facebook::react {

struct ConsumeDataType {
float number{};

constexpr bool operator==(const ConsumeDataType& other) const = default;
};

template <>
struct CSSDataTypeParser<ConsumeDataType> {
constexpr static std::optional<ConsumeDataType> consume(
CSSSyntaxParser& parser) {
auto val = parseNextCSSValue<CSSNumber>(parser);
if (std::holds_alternative<CSSNumber>(val)) {
return ConsumeDataType{std::get<CSSNumber>(val).value};
}

return {};
}
};

static_assert(CSSDataType<ConsumeDataType>);

TEST(CSSValueParser, consume_multiple_with_delimeter) {
CSSSyntaxParser parser{"1 2, 3, 4 / 5"};

auto next = parseNextCSSValue<ConsumeDataType>(parser);
EXPECT_TRUE(std::holds_alternative<ConsumeDataType>(next));
EXPECT_EQ(std::get<ConsumeDataType>(next).number, 1);

next = parseNextCSSValue<ConsumeDataType>(parser, CSSDelimiter::None);
EXPECT_FALSE(std::holds_alternative<ConsumeDataType>(next));

next = parseNextCSSValue<ConsumeDataType>(parser, CSSDelimiter::Whitespace);
EXPECT_TRUE(std::holds_alternative<ConsumeDataType>(next));
EXPECT_EQ(std::get<ConsumeDataType>(next).number, 2);

next = parseNextCSSValue<ConsumeDataType>(parser, CSSDelimiter::Comma);
EXPECT_TRUE(std::holds_alternative<ConsumeDataType>(next));
EXPECT_EQ(std::get<ConsumeDataType>(next).number, 3);

next = parseNextCSSValue<ConsumeDataType>(parser, CSSDelimiter::Comma);
EXPECT_TRUE(std::holds_alternative<ConsumeDataType>(next));
EXPECT_EQ(std::get<ConsumeDataType>(next).number, 4);

next = parseNextCSSValue<ConsumeDataType>(parser, CSSDelimiter::Solidus);
EXPECT_TRUE(std::holds_alternative<ConsumeDataType>(next));
EXPECT_EQ(std::get<ConsumeDataType>(next).number, 5);

next = parseNextCSSValue<ConsumeDataType>(parser);
EXPECT_FALSE(std::holds_alternative<ConsumeDataType>(next));
}

} // namespace facebook::react

0 comments on commit 08e9057

Please sign in to comment.