Skip to content

Commit 55d6ae6

Browse files
NickGerlemanfacebook-github-bot
authored andcommitted
[skip ci] CSSDataTypeParser consume() function (#48986)
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. Changelog: [Internal] Reviewed By: lenaic Differential Revision: D68735370
1 parent 7465607 commit 55d6ae6

File tree

5 files changed

+174
-84
lines changed

5 files changed

+174
-84
lines changed

packages/react-native/ReactCommon/react/renderer/css/CSSDataType.h

+11-13
Original file line numberDiff line numberDiff line change
@@ -48,29 +48,27 @@ concept CSSSimpleBlockSink =
4848
* concrete representation.
4949
*/
5050
template <typename T, typename ReturnT = std::any>
51-
concept CSSPreservedTokenSink =
52-
requires(const CSSPreservedToken& token, CSSSyntaxParser& parser) {
53-
{
54-
T::consumePreservedToken(token, parser)
55-
} -> std::convertible_to<ReturnT>;
56-
};
51+
concept CSSPreservedTokenSink = requires(const CSSPreservedToken& token) {
52+
{ T::consumePreservedToken(token) } -> std::convertible_to<ReturnT>;
53+
};
5754

5855
/**
59-
* Accepts a CSS preserved token and may parse it into a concrete
60-
* representation.
56+
* Accepts a raw syntax parser, to be able to parse compounded values
6157
*/
6258
template <typename T, typename ReturnT = std::any>
63-
concept CSSSimplePreservedTokenSink = requires(const CSSPreservedToken& token) {
64-
{ T::consumePreservedToken(token) } -> std::convertible_to<ReturnT>;
59+
concept CSSParserSink = requires(CSSSyntaxParser& parser) {
60+
{ T::consume(parser) } -> std::convertible_to<ReturnT>;
6561
};
6662

6763
/**
6864
* Represents a valid specialization of CSSDataTypeParser
6965
*/
7066
template <typename T, typename ReturnT = std::any>
71-
concept CSSValidDataTypeParser = CSSFunctionBlockSink<T, ReturnT> ||
72-
CSSSimpleBlockSink<T, ReturnT> || CSSPreservedTokenSink<T, ReturnT> ||
73-
CSSSimplePreservedTokenSink<T, ReturnT>;
67+
concept CSSValidDataTypeParser =
68+
((CSSFunctionBlockSink<T, ReturnT> || CSSSimpleBlockSink<T, ReturnT> ||
69+
CSSPreservedTokenSink<T, ReturnT>) &&
70+
!CSSParserSink<T, ReturnT>) ||
71+
CSSParserSink<T, ReturnT>;
7472

7573
/**
7674
* Concrete representation for a CSS data type

packages/react-native/ReactCommon/react/renderer/css/CSSRatio.h

+10-7
Original file line numberDiff line numberDiff line change
@@ -39,23 +39,26 @@ struct CSSRatio {
3939

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

51+
auto numeratorValue = std::get<CSSNumber>(numerator).value;
52+
if (numeratorValue >= 0) {
5053
auto denominator =
5154
peekNextCSSValue<CSSNumber>(parser, CSSDelimiter::Solidus);
5255
if (std::holds_alternative<CSSNumber>(denominator) &&
5356
std::get<CSSNumber>(denominator).value >= 0) {
5457
parseNextCSSValue<CSSNumber>(parser, CSSDelimiter::Solidus);
55-
return CSSRatio{numerator, std::get<CSSNumber>(denominator).value};
58+
return CSSRatio{numeratorValue, std::get<CSSNumber>(denominator).value};
5659
}
5760

58-
return CSSRatio{numerator, 1.0f};
61+
return CSSRatio{numeratorValue, 1.0f};
5962
}
6063

6164
return {};

packages/react-native/ReactCommon/react/renderer/css/CSSSyntaxParser.h

+56-56
Original file line numberDiff line numberDiff line change
@@ -184,6 +184,59 @@ class CSSSyntaxParser {
184184
}
185185
}
186186

187+
/**
188+
* Consume a delimiter, returning false if a required delimiter is not found.
189+
*/
190+
constexpr bool consumeDelimiter(CSSDelimiter delimiter) {
191+
if (delimiter == CSSDelimiter::None) {
192+
return true;
193+
}
194+
195+
bool hasWhiteSpace = peek().type() == CSSTokenType::WhiteSpace;
196+
consumeWhitespace();
197+
198+
switch (delimiter) {
199+
case CSSDelimiter::Comma:
200+
if (peek().type() == CSSTokenType::Comma) {
201+
consumeToken();
202+
consumeWhitespace();
203+
return true;
204+
}
205+
return false;
206+
case CSSDelimiter::Whitespace:
207+
return hasWhiteSpace;
208+
case CSSDelimiter::OptionalWhitespace:
209+
return true;
210+
case CSSDelimiter::CommaOrWhitespace:
211+
if (peek().type() == CSSTokenType::Comma) {
212+
consumeToken();
213+
consumeWhitespace();
214+
return true;
215+
}
216+
return hasWhiteSpace;
217+
case CSSDelimiter::Solidus:
218+
if (peek().type() == CSSTokenType::Delim &&
219+
peek().stringValue() == "/") {
220+
consumeToken();
221+
consumeWhitespace();
222+
return true;
223+
}
224+
return false;
225+
case CSSDelimiter::SolidusOrWhitespace:
226+
if (peek().type() == CSSTokenType::Delim &&
227+
peek().stringValue() == "/") {
228+
consumeToken();
229+
consumeWhitespace();
230+
return true;
231+
}
232+
return hasWhiteSpace;
233+
case CSSDelimiter::None:
234+
return true;
235+
}
236+
237+
return false;
238+
}
239+
187240
private:
188241
constexpr CSSSyntaxParser(CSSSyntaxParser& parser, CSSTokenType terminator)
189242
: tokenizer_{parser.tokenizer_},
@@ -220,7 +273,7 @@ struct CSSComponentValueVisitorDispatcher {
220273
CSSDelimiter delimiter,
221274
const VisitorsT&... visitors) {
222275
auto originalParser = parser;
223-
if (!consumeDelimiter(delimiter)) {
276+
if (!parser.consumeDelimiter(delimiter)) {
224277
parser = originalParser;
225278
return {};
226279
}
@@ -265,59 +318,6 @@ struct CSSComponentValueVisitorDispatcher {
265318
return ReturnT{};
266319
}
267320

268-
/**
269-
* Consume a delimiter, returning false if a required delimiter is not found.
270-
*/
271-
constexpr bool consumeDelimiter(CSSDelimiter delimiter) {
272-
if (delimiter == CSSDelimiter::None) {
273-
return true;
274-
}
275-
276-
bool hasWhiteSpace = parser.peek().type() == CSSTokenType::WhiteSpace;
277-
parser.consumeWhitespace();
278-
279-
switch (delimiter) {
280-
case CSSDelimiter::Comma:
281-
if (parser.peek().type() == CSSTokenType::Comma) {
282-
parser.consumeToken();
283-
parser.consumeWhitespace();
284-
return true;
285-
}
286-
return false;
287-
case CSSDelimiter::Whitespace:
288-
return hasWhiteSpace;
289-
case CSSDelimiter::OptionalWhitespace:
290-
return true;
291-
case CSSDelimiter::CommaOrWhitespace:
292-
if (parser.peek().type() == CSSTokenType::Comma) {
293-
parser.consumeToken();
294-
parser.consumeWhitespace();
295-
return true;
296-
}
297-
return hasWhiteSpace;
298-
case CSSDelimiter::Solidus:
299-
if (parser.peek().type() == CSSTokenType::Delim &&
300-
parser.peek().stringValue() == "/") {
301-
parser.consumeToken();
302-
parser.consumeWhitespace();
303-
return true;
304-
}
305-
return false;
306-
case CSSDelimiter::SolidusOrWhitespace:
307-
if (parser.peek().type() == CSSTokenType::Delim &&
308-
parser.peek().stringValue() == "/") {
309-
parser.consumeToken();
310-
parser.consumeWhitespace();
311-
return true;
312-
}
313-
return hasWhiteSpace;
314-
case CSSDelimiter::None:
315-
return true;
316-
}
317-
318-
return false;
319-
}
320-
321321
constexpr std::optional<ReturnT> visitFunction(
322322
const CSSComponentValueVisitor<ReturnT> auto& visitor,
323323
const CSSComponentValueVisitor<ReturnT> auto&... rest) {
@@ -393,7 +393,7 @@ struct CSSComponentValueVisitorDispatcher {
393393
}
394394
};
395395

396-
template <typename ReturnT>
396+
template <CSSSyntaxVisitorReturn ReturnT>
397397
constexpr ReturnT CSSSyntaxParser::consumeComponentValue(
398398
CSSDelimiter delimiter,
399399
const CSSComponentValueVisitor<ReturnT> auto&... visitors)
@@ -404,7 +404,7 @@ constexpr ReturnT CSSSyntaxParser::consumeComponentValue(
404404
.consumeComponentValue(delimiter, visitors...);
405405
}
406406

407-
template <typename ReturnT>
407+
template <CSSSyntaxVisitorReturn ReturnT>
408408
constexpr ReturnT CSSSyntaxParser::consumeComponentValue(
409409
const CSSComponentValueVisitor<ReturnT> auto&... visitors)
410410
requires(CSSUniqueComponentValueVisitors<ReturnT, decltype(visitors)...>)

packages/react-native/ReactCommon/react/renderer/css/CSSValueParser.h

+31-8
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,14 @@ class CSSValueParser {
3535
CSSDelimiter delimeter = CSSDelimiter::None) {
3636
using ReturnT = std::variant<std::monostate, AllowedTypesT...>;
3737

38+
auto consumedValue =
39+
tryConsumeParser<ReturnT, CSSDataTypeParser<AllowedTypesT>...>(
40+
delimeter);
41+
42+
if (!std::holds_alternative<std::monostate>(consumedValue)) {
43+
return consumedValue;
44+
}
45+
3846
return parser_.consumeComponentValue<ReturnT>(
3947
delimeter,
4048
[&](const CSSPreservedToken& token) {
@@ -75,14 +83,6 @@ class CSSValueParser {
7583
CSSValidDataTypeParser... RestParserT>
7684
constexpr ReturnT tryConsumePreservedToken(const CSSPreservedToken& token) {
7785
if constexpr (CSSPreservedTokenSink<ParserT>) {
78-
auto currentParser = parser_;
79-
if (auto ret = ParserT::consumePreservedToken(token, parser_)) {
80-
return *ret;
81-
}
82-
parser_ = currentParser;
83-
}
84-
85-
if constexpr (CSSSimplePreservedTokenSink<ParserT>) {
8686
if (auto ret = ParserT::consumePreservedToken(token)) {
8787
return *ret;
8888
}
@@ -141,6 +141,29 @@ class CSSValueParser {
141141
return tryConsumeFunctionBlock<ReturnT, RestParserT...>(func, blockParser);
142142
}
143143

144+
template <typename ReturnT>
145+
constexpr ReturnT tryConsumeParser(CSSDelimiter /*delimeter*/) {
146+
return {};
147+
}
148+
149+
template <
150+
typename ReturnT,
151+
CSSValidDataTypeParser ParserT,
152+
CSSValidDataTypeParser... RestParserT>
153+
constexpr ReturnT tryConsumeParser(CSSDelimiter delimeter) {
154+
if constexpr (CSSParserSink<ParserT>) {
155+
auto originalParser = parser_;
156+
if (parser_.consumeDelimiter(delimeter)) {
157+
if (auto ret = ParserT::consume(parser_)) {
158+
return *ret;
159+
}
160+
}
161+
parser_ = originalParser;
162+
}
163+
164+
return tryConsumeParser<ReturnT, RestParserT...>(delimeter);
165+
}
166+
144167
CSSSyntaxParser& parser_;
145168
};
146169

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
/*
2+
* Copyright (c) Meta Platforms, Inc. and affiliates.
3+
*
4+
* This source code is licensed under the MIT license found in the
5+
* LICENSE file in the root directory of this source tree.
6+
*/
7+
8+
#include <gtest/gtest.h>
9+
#include <react/renderer/css/CSSDataType.h>
10+
#include <react/renderer/css/CSSNumber.h>
11+
#include <react/renderer/css/CSSValueParser.h>
12+
13+
namespace facebook::react {
14+
15+
struct ConsumeDataType {
16+
float number{};
17+
18+
constexpr bool operator==(const ConsumeDataType& other) const = default;
19+
};
20+
21+
template <>
22+
struct CSSDataTypeParser<ConsumeDataType> {
23+
constexpr static std::optional<ConsumeDataType> consume(
24+
CSSSyntaxParser& parser) {
25+
auto val = parseNextCSSValue<CSSNumber>(parser);
26+
if (std::holds_alternative<CSSNumber>(val)) {
27+
return ConsumeDataType{std::get<CSSNumber>(val).value};
28+
}
29+
30+
return {};
31+
}
32+
};
33+
34+
static_assert(CSSDataType<ConsumeDataType>);
35+
36+
TEST(CSSValueParser, consume_multiple_with_delimeter) {
37+
CSSSyntaxParser parser{"1 2, 3, 4 / 5"};
38+
39+
auto next = parseNextCSSValue<ConsumeDataType>(parser);
40+
EXPECT_TRUE(std::holds_alternative<ConsumeDataType>(next));
41+
EXPECT_EQ(std::get<ConsumeDataType>(next).number, 1);
42+
43+
next = parseNextCSSValue<ConsumeDataType>(parser, CSSDelimiter::None);
44+
EXPECT_FALSE(std::holds_alternative<ConsumeDataType>(next));
45+
46+
next = parseNextCSSValue<ConsumeDataType>(parser, CSSDelimiter::Whitespace);
47+
EXPECT_TRUE(std::holds_alternative<ConsumeDataType>(next));
48+
EXPECT_EQ(std::get<ConsumeDataType>(next).number, 2);
49+
50+
next = parseNextCSSValue<ConsumeDataType>(parser, CSSDelimiter::Comma);
51+
EXPECT_TRUE(std::holds_alternative<ConsumeDataType>(next));
52+
EXPECT_EQ(std::get<ConsumeDataType>(next).number, 3);
53+
54+
next = parseNextCSSValue<ConsumeDataType>(parser, CSSDelimiter::Comma);
55+
EXPECT_TRUE(std::holds_alternative<ConsumeDataType>(next));
56+
EXPECT_EQ(std::get<ConsumeDataType>(next).number, 4);
57+
58+
next = parseNextCSSValue<ConsumeDataType>(parser, CSSDelimiter::Solidus);
59+
EXPECT_TRUE(std::holds_alternative<ConsumeDataType>(next));
60+
EXPECT_EQ(std::get<ConsumeDataType>(next).number, 5);
61+
62+
next = parseNextCSSValue<ConsumeDataType>(parser);
63+
EXPECT_FALSE(std::holds_alternative<ConsumeDataType>(next));
64+
}
65+
66+
} // namespace facebook::react

0 commit comments

Comments
 (0)