Skip to content

Commit 258b481

Browse files
NickGerlemanfacebook-github-bot
authored andcommitted
Revise ComponentValue parsing model (#44561)
Summary: Pull Request resolved: #44561 D57089275 introduced a layer to parse component values out of the token stream. I modeled this similar to the tokenizer, as a flat iterator of component values. Because function components can nest a variable number of child component values, this now looks like storing a fully resolved tree of tokens on the heap during parsing. This diff changes the model, so that `CSSSyntaxParser::consumeComponentValue()` no longer returns a resolved CSS function value. Instead, users of the parser are expected to provide "visitors" which continue parsing, matched based on component value type pattern matched. Visitors can perform parsing specific to their context, and propagate values up the stack, based on their evaluation of the component value. Removing the heap allocated list of tokens here also lets this core CSS parsing stack keep constexpr, so I added that back, though we need to keep expression trees for math expressions in uncommon cases, so the layer up probaly won't keep constexpr. Changelog: [Internal] Reviewed By: joevilches Differential Revision: D57206706 fbshipit-source-id: 25db84d376ef18f6291e60ed953e29c4000a7a26
1 parent 0c7095f commit 258b481

File tree

6 files changed

+657
-279
lines changed

6 files changed

+657
-279
lines changed

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

Lines changed: 261 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -7,51 +7,159 @@
77

88
#pragma once
99

10+
#include <concepts>
1011
#include <optional>
11-
#include <variant>
1212
#include <vector>
1313

1414
#include <react/renderer/css/CSSTokenizer.h>
1515

1616
namespace facebook::react {
1717

1818
/**
19-
* CSSSyntaxParser allows parsing streams of CSS text into "component values",
20-
* being either a preserved token, or a function.
19+
* Describes context for a CSS function component value.
20+
*/
21+
struct CSSFunctionBlock {
22+
std::string_view name{};
23+
};
24+
25+
/**
26+
* Describes a preserved token component value.
27+
*/
28+
using CSSPreservedToken = CSSToken;
29+
30+
/**
31+
* Describes context for a CSS function component value.
32+
*/
33+
struct CSSSimpleBlock {
34+
CSSTokenType openBracketType{};
35+
};
36+
37+
/**
38+
* A CSSFunctionVisitor is called to start parsing a function component value.
39+
* At this point, the Parser is positioned at the start of the function
40+
* component value list. It is expected that the visitor finishes before the end
41+
* of the function block.
42+
*/
43+
template <typename T, typename ReturnT>
44+
concept CSSFunctionVisitor = requires(T visitor, CSSFunctionBlock func) {
45+
{ visitor(func) } -> std::convertible_to<ReturnT>;
46+
};
47+
48+
/**
49+
* A CSSPreservedTokenVisitor is called after parsing a preserved token
50+
* component value.
51+
*/
52+
template <typename T, typename ReturnT>
53+
concept CSSPreservedTokenVisitor =
54+
requires(T visitor, CSSPreservedToken token) {
55+
{ visitor(token) } -> std::convertible_to<ReturnT>;
56+
};
57+
58+
/**
59+
* A CSSSimpleBlockVisitor is called after parsing a simple block component
60+
* value. It is expected that the visitor finishes before the end
61+
* of the block.
62+
*/
63+
template <typename T, typename ReturnT>
64+
concept CSSSimpleBlockVisitor = requires(T visitor, CSSSimpleBlock block) {
65+
{ visitor(block) } -> std::convertible_to<ReturnT>;
66+
};
67+
68+
/**
69+
* Any visitor for a component value.
70+
*/
71+
template <typename T, typename ReturnT>
72+
concept CSSComponentValueVisitor = CSSFunctionVisitor<T, ReturnT> ||
73+
CSSPreservedTokenVisitor<T, ReturnT> || CSSSimpleBlockVisitor<T, ReturnT>;
74+
75+
/**
76+
* Represents a variadic set of CSSComponentValueVisitor with no more than one
77+
* of a specific type of visitor.
78+
*/
79+
template <typename ReturnT, typename... VisitorsT>
80+
concept CSSUniqueComponentValueVisitors =
81+
(CSSComponentValueVisitor<VisitorsT, ReturnT> && ...) &&
82+
((CSSFunctionVisitor<VisitorsT, ReturnT> ? 1 : 0) + ... + 0) <= 1 &&
83+
((CSSPreservedTokenVisitor<VisitorsT, ReturnT> ? 1 : 0) + ... + 0) <= 1 &&
84+
((CSSSimpleBlockVisitor<VisitorsT, ReturnT> ? 1 : 0) + ... + 0) <= 1;
85+
86+
/**
87+
* Describes the delimeter to expect before the next component value.
88+
*/
89+
enum class CSSComponentValueDelimiter {
90+
Comma,
91+
Whitespace,
92+
None,
93+
};
94+
95+
/**
96+
* CSSSyntaxParser allows parsing streams of CSS text into "component
97+
* values".
2198
*
2299
* https://www.w3.org/TR/css-syntax-3/#component-value
23100
*/
24101
class CSSSyntaxParser {
25-
public:
26-
struct Function;
27-
28-
using PreservedToken = CSSToken;
29-
using ComponentValue = std::variant<std::monostate, PreservedToken, Function>;
30-
31-
struct Function {
32-
std::string_view name{};
33-
std::vector<ComponentValue> args{};
34-
};
102+
template <typename ReturnT, CSSComponentValueVisitor<ReturnT>... VisitorsT>
103+
friend struct CSSComponentValueVisitorDispatcher;
35104

105+
public:
36106
/**
37107
* Construct the parser over the given string_view, which must stay alive for
38108
* the duration of the CSSSyntaxParser.
39109
*/
40110
explicit constexpr CSSSyntaxParser(std::string_view css)
41111
: tokenizer_{css}, currentToken_(tokenizer_.next()) {}
42112

113+
constexpr CSSSyntaxParser(const CSSSyntaxParser&) = default;
114+
constexpr CSSSyntaxParser(CSSSyntaxParser&&) = default;
115+
116+
constexpr CSSSyntaxParser& operator=(const CSSSyntaxParser&) = default;
117+
constexpr CSSSyntaxParser& operator=(CSSSyntaxParser&&) = default;
118+
43119
/**
44-
* Directly consume the next component value
120+
* Directly consume the next component value. The component value is provided
121+
* to a passed in "visitor", typically a lambda which accepts the component
122+
* value in a new scope. The visitor may read this component parameter into a
123+
* higher-level data structure, and continue parsing within its scope using
124+
* the same underlying CSSSyntaxParser.
45125
*
46126
* https://www.w3.org/TR/css-syntax-3/#consume-component-value
127+
*
128+
* @param <ReturnT> caller-specified return type of visitors. This type will
129+
* be set to its default constructed state if consuming a component value with
130+
* no matching visitors, or syntax error
131+
* @param visitors A unique list of CSSComponentValueVisitor to be called on a
132+
* match
133+
* @param delimiter The expected delimeter to occur before the next component
134+
* value
135+
* @returns the visitor returned value, or a default constructed value if no
136+
* visitor was matched, or a syntax error occurred.
137+
*/
138+
template <typename ReturnT>
139+
constexpr ReturnT consumeComponentValue(
140+
CSSComponentValueDelimiter delimiter,
141+
const CSSComponentValueVisitor<ReturnT> auto&... visitors)
142+
requires(CSSUniqueComponentValueVisitors<ReturnT, decltype(visitors)...>);
143+
144+
template <typename ReturnT>
145+
constexpr ReturnT consumeComponentValue(
146+
const CSSComponentValueVisitor<ReturnT> auto&... visitors)
147+
requires(CSSUniqueComponentValueVisitors<ReturnT, decltype(visitors)...>);
148+
149+
/**
150+
* The parser is considered finished when there are no more remaining tokens
151+
* to be processed
47152
*/
48-
inline ComponentValue consumeComponentValue() {
49-
if (peek().type() == CSSTokenType::Function) {
50-
auto function = consumeFunction();
51-
return function.has_value() ? ComponentValue{std::move(*function)}
52-
: ComponentValue{};
53-
} else {
54-
return consumeToken();
153+
constexpr bool isFinished() const {
154+
return currentToken_.type() == CSSTokenType::EndOfFile;
155+
}
156+
157+
/**
158+
* Consume any whitespace tokens.
159+
*/
160+
constexpr void consumeWhitespace() {
161+
if (currentToken_.type() == CSSTokenType::WhiteSpace) {
162+
currentToken_ = tokenizer_.next();
55163
}
56164
}
57165

@@ -66,40 +174,149 @@ class CSSSyntaxParser {
66174
return prevToken;
67175
}
68176

69-
inline std::optional<Function> consumeFunction() {
70-
// https://www.w3.org/TR/css-syntax-3/#consume-a-function
71-
Function function{.name = consumeToken().stringValue()};
177+
CSSTokenizer tokenizer_;
178+
CSSToken currentToken_;
179+
};
72180

73-
while (true) {
74-
auto nextValue = consumeComponentValue();
75-
if (std::holds_alternative<std::monostate>(nextValue)) {
76-
return {};
77-
}
181+
template <typename ReturnT, CSSComponentValueVisitor<ReturnT>... VisitorsT>
182+
struct CSSComponentValueVisitorDispatcher {
183+
CSSSyntaxParser& parser;
78184

79-
if (auto token = std::get_if<CSSToken>(&nextValue)) {
80-
if (token->type() == CSSTokenType::CloseParen) {
81-
return function;
185+
constexpr ReturnT consumeComponentValue(
186+
CSSComponentValueDelimiter delimiter,
187+
const VisitorsT&... visitors) {
188+
switch (delimiter) {
189+
case CSSComponentValueDelimiter::Comma:
190+
parser.consumeWhitespace();
191+
if (parser.peek().type() != CSSTokenType::Comma) {
192+
return ReturnT{};
82193
}
83-
if (token->type() == CSSTokenType::EndOfFile) {
84-
return {};
194+
parser.consumeToken();
195+
parser.consumeWhitespace();
196+
break;
197+
case CSSComponentValueDelimiter::Whitespace:
198+
parser.consumeWhitespace();
199+
break;
200+
case CSSComponentValueDelimiter::None:
201+
break;
202+
}
203+
204+
switch (parser.peek().type()) {
205+
case CSSTokenType::Function:
206+
if (auto ret = visitFunction(visitors...)) {
207+
return *ret;
85208
}
86-
function.args.emplace_back(std::move(*token));
87-
continue;
88-
}
209+
break;
210+
case CSSTokenType::OpenParen:
211+
if (auto ret =
212+
visitSimpleBlock(CSSTokenType::CloseParen, visitors...)) {
213+
return *ret;
214+
}
215+
break;
216+
case CSSTokenType::OpenSquare:
217+
if (auto ret =
218+
visitSimpleBlock(CSSTokenType::CloseSquare, visitors...)) {
219+
return *ret;
220+
}
221+
break;
222+
case CSSTokenType::OpenCurly:
223+
if (auto ret =
224+
visitSimpleBlock(CSSTokenType::CloseCurly, visitors...)) {
225+
return *ret;
226+
}
227+
break;
228+
default:
229+
if (auto ret = visitPreservedToken(visitors...)) {
230+
return *ret;
231+
}
232+
break;
233+
}
89234

90-
if (auto func = std::get_if<Function>(&nextValue)) {
91-
function.args.emplace_back(std::move(*func));
92-
continue;
93-
}
235+
return ReturnT{};
236+
}
94237

238+
constexpr ReturnT consumeNextCommaDelimitedValue(
239+
const VisitorsT&... visitors) {
240+
parser.consumeWhitespace();
241+
if (parser.consumeToken().type() != CSSTokenType::Comma) {
95242
return {};
96243
}
244+
parser.consumeWhitespace();
245+
return consumeComponentValue(std::forward<VisitorsT>(visitors)...);
246+
}
97247

98-
return function;
248+
constexpr ReturnT consumeNextWhitespaceDelimitedValue(
249+
const VisitorsT&... visitors) {
250+
parser.consumeWhitespace();
251+
return consumeComponentValue(std::forward<VisitorsT>(visitors)...);
99252
}
100253

101-
CSSTokenizer tokenizer_;
102-
CSSToken currentToken_;
254+
constexpr std::optional<ReturnT> visitFunction(const VisitorsT&... visitors) {
255+
for (auto visitor : {visitors...}) {
256+
if constexpr (CSSFunctionVisitor<decltype(visitor), ReturnT>) {
257+
auto functionValue =
258+
visitor({.name = parser.consumeToken().stringValue()});
259+
parser.consumeWhitespace();
260+
if (parser.peek().type() == CSSTokenType::CloseParen) {
261+
parser.consumeToken();
262+
return functionValue;
263+
}
264+
265+
return {};
266+
}
267+
}
268+
269+
return {};
270+
}
271+
272+
constexpr std::optional<ReturnT> visitSimpleBlock(
273+
CSSTokenType endToken,
274+
const VisitorsT&... visitors) {
275+
for (auto visitor : {visitors...}) {
276+
if constexpr (CSSSimpleBlockVisitor<decltype(visitor), ReturnT>) {
277+
auto blockValue =
278+
visitor({.openBracketType = parser.consumeToken().type()});
279+
parser.consumeWhitespace();
280+
if (parser.peek().type() == endToken) {
281+
parser.consumeToken();
282+
return blockValue;
283+
}
284+
285+
return {};
286+
}
287+
}
288+
return {};
289+
}
290+
291+
constexpr std::optional<ReturnT> visitPreservedToken(
292+
const VisitorsT&... visitors) {
293+
for (auto visitor : {visitors...}) {
294+
if constexpr (CSSPreservedTokenVisitor<decltype(visitor), ReturnT>) {
295+
return visitor(parser.consumeToken());
296+
}
297+
}
298+
return {};
299+
}
103300
};
104301

302+
template <typename ReturnT>
303+
constexpr ReturnT CSSSyntaxParser::consumeComponentValue(
304+
CSSComponentValueDelimiter delimiter,
305+
const CSSComponentValueVisitor<ReturnT> auto&... visitors)
306+
requires(CSSUniqueComponentValueVisitors<ReturnT, decltype(visitors)...>)
307+
{
308+
return CSSComponentValueVisitorDispatcher<ReturnT, decltype(visitors)...>{
309+
*this}
310+
.consumeComponentValue(delimiter, visitors...);
311+
}
312+
313+
template <typename ReturnT>
314+
constexpr ReturnT CSSSyntaxParser::consumeComponentValue(
315+
const CSSComponentValueVisitor<ReturnT> auto&... visitors)
316+
requires(CSSUniqueComponentValueVisitors<ReturnT, decltype(visitors)...>)
317+
{
318+
return consumeComponentValue<ReturnT>(
319+
CSSComponentValueDelimiter::None, visitors...);
320+
}
321+
105322
} // namespace facebook::react

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

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,15 +16,19 @@ namespace facebook::react {
1616
* https://www.w3.org/TR/css-syntax-3/#tokenizer-definitions
1717
*/
1818
enum class CSSTokenType {
19+
CloseCurly,
1920
CloseParen,
21+
CloseSquare,
2022
Comma,
2123
Delim,
2224
Dimension,
2325
EndOfFile,
2426
Function,
2527
Ident,
2628
Number,
29+
OpenCurly,
2730
OpenParen,
31+
OpenSquare,
2832
Percentage,
2933
WhiteSpace,
3034
};

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

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,14 @@ class CSSTokenizer {
4343
return consumeCharacter(CSSTokenType::OpenParen);
4444
case ')':
4545
return consumeCharacter(CSSTokenType::CloseParen);
46+
case '[':
47+
return consumeCharacter(CSSTokenType::OpenSquare);
48+
case ']':
49+
return consumeCharacter(CSSTokenType::CloseSquare);
50+
case '{':
51+
return consumeCharacter(CSSTokenType::OpenCurly);
52+
case '}':
53+
return consumeCharacter(CSSTokenType::CloseCurly);
4654
case ',':
4755
return consumeCharacter(CSSTokenType::Comma);
4856
case '+':

0 commit comments

Comments
 (0)