Simplify "rollback" on optional or invalid syntax (#48825)

Summary:
Pull Request resolved: https://github.com/facebook/react-native/pull/48825

Right now we preserve the state of the CSSSyntaxParser across multiple data type parse attempts, so long that a data type parser consumes an additional component value. This requires data type parsers to be careful to not consume additional forward tokens if it may lead to parse error. We can make this model a lot simpler by instead resetting the parser to original state on data type parse error.

We also introduce `peekComponentValue`, and visitor-less `consumeComponentValue` as a convenience, to allow data type parsers to view future component values without advancing, even if the data type parser does return a value, without needing to manually clone the parser.

Changelog: [Internal]

Reviewed By: lenaic

Differential Revision: D68357624

fbshipit-source-id: 6ff77bab8ac9eabd5dccea52daa85bbd32b5f2b6
This commit is contained in:
Nick Gerleman
2025-01-21 20:20:04 -08:00
committed by Facebook GitHub Bot
parent 4ed2b35bf6
commit e73d4ff015
4 changed files with 168 additions and 11 deletions
@@ -35,9 +35,7 @@ struct CSSDataTypeParser<CSSRatio> {
if (isValidRatioPart(token.numericValue())) {
float numerator = token.numericValue();
CSSSyntaxParser lookaheadParser{parser};
auto hasSolidus = lookaheadParser.consumeComponentValue<bool>(
auto hasSolidus = parser.peekComponentValue<bool>(
CSSComponentValueDelimiter::Whitespace,
[&](const CSSPreservedToken& token) {
return token.type() == CSSTokenType::Delim &&
@@ -45,16 +43,16 @@ struct CSSDataTypeParser<CSSRatio> {
});
if (!hasSolidus) {
parser = lookaheadParser;
return CSSRatio{numerator, 1.0f};
}
parser.consumeComponentValue(CSSComponentValueDelimiter::Whitespace);
auto denominator = parseNextCSSValue<CSSNumber>(
lookaheadParser, CSSComponentValueDelimiter::Whitespace);
parser, CSSComponentValueDelimiter::Whitespace);
if (std::holds_alternative<CSSNumber>(denominator) &&
isValidRatioPart(std::get<CSSNumber>(denominator).value)) {
parser = lookaheadParser;
return CSSRatio{numerator, std::get<CSSNumber>(denominator).value};
}
}
@@ -9,7 +9,6 @@
#include <concepts>
#include <optional>
#include <vector>
#include <react/renderer/css/CSSTokenizer.h>
@@ -82,7 +81,7 @@ concept CSSComponentValueVisitor = CSSFunctionVisitor<T, ReturnT> ||
*/
template <typename ReturnT, typename... VisitorsT>
concept CSSUniqueComponentValueVisitors =
(CSSComponentValueVisitor<VisitorsT, ReturnT> && ...) &&
(CSSComponentValueVisitor<VisitorsT, ReturnT> && ... && true) &&
((CSSFunctionVisitor<VisitorsT, ReturnT> ? 1 : 0) + ... + 0) <= 1 &&
((CSSPreservedTokenVisitor<VisitorsT, ReturnT> ? 1 : 0) + ... + 0) <= 1 &&
((CSSSimpleBlockVisitor<VisitorsT, ReturnT> ? 1 : 0) + ... + 0) <= 1;
@@ -140,17 +139,47 @@ class CSSSyntaxParser {
* @returns the visitor returned value, or a default constructed value if no
* visitor was matched, or a syntax error occurred.
*/
template <typename ReturnT>
template <typename ReturnT = std::nullptr_t>
constexpr ReturnT consumeComponentValue(
CSSComponentValueDelimiter delimiter,
const CSSComponentValueVisitor<ReturnT> auto&... visitors)
requires(CSSUniqueComponentValueVisitors<ReturnT, decltype(visitors)...>);
template <typename ReturnT>
template <typename ReturnT = std::nullptr_t>
constexpr ReturnT consumeComponentValue(
const CSSComponentValueVisitor<ReturnT> auto&... visitors)
requires(CSSUniqueComponentValueVisitors<ReturnT, decltype(visitors)...>);
/**
* 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 <ReturnT> 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 <typename ReturnT = std::nullptr_t>
constexpr ReturnT peekComponentValue(
CSSComponentValueDelimiter delimiter,
const CSSComponentValueVisitor<ReturnT> auto&... visitors)
requires(CSSUniqueComponentValueVisitors<ReturnT, decltype(visitors)...>);
template <typename ReturnT = std::nullptr_t>
constexpr ReturnT peekComponentValue(
const CSSComponentValueVisitor<ReturnT> auto&... visitors)
requires(CSSUniqueComponentValueVisitors<ReturnT, decltype(visitors)...>);
/**
* The parser is considered finished when there are no more remaining tokens
* to be processed
@@ -262,6 +291,15 @@ struct CSSComponentValueVisitorDispatcher {
return ReturnT{};
}
constexpr ReturnT peekComponentValue(
CSSComponentValueDelimiter delimiter,
const VisitorsT&... visitors) {
auto originalParser = parser;
auto ret = consumeComponentValue(delimiter, visitors...);
parser = originalParser;
return ret;
}
constexpr std::optional<ReturnT> visitFunction(
const CSSComponentValueVisitor<ReturnT> auto& visitor,
const CSSComponentValueVisitor<ReturnT> auto&... rest) {
@@ -291,6 +329,11 @@ struct CSSComponentValueVisitorDispatcher {
}
constexpr std::optional<ReturnT> visitFunction() {
while (parser.peek().type() != CSSTokenType::CloseParen) {
parser.consumeToken();
}
parser.consumeToken();
return {};
}
@@ -315,7 +358,11 @@ struct CSSComponentValueVisitorDispatcher {
return visitSimpleBlock(endToken, rest...);
}
constexpr std::optional<ReturnT> visitSimpleBlock(CSSTokenType /*endToken*/) {
constexpr std::optional<ReturnT> visitSimpleBlock(CSSTokenType endToken) {
while (parser.peek().type() != endToken) {
parser.consumeToken();
}
parser.consumeToken();
return {};
}
@@ -329,6 +376,7 @@ struct CSSComponentValueVisitorDispatcher {
}
constexpr std::optional<ReturnT> visitPreservedToken() {
parser.consumeToken();
return {};
}
};
@@ -353,4 +401,24 @@ constexpr ReturnT CSSSyntaxParser::consumeComponentValue(
CSSComponentValueDelimiter::None, visitors...);
}
template <typename ReturnT>
constexpr ReturnT CSSSyntaxParser::peekComponentValue(
CSSComponentValueDelimiter delimiter,
const CSSComponentValueVisitor<ReturnT> auto&... visitors)
requires(CSSUniqueComponentValueVisitors<ReturnT, decltype(visitors)...>)
{
return CSSComponentValueVisitorDispatcher<ReturnT, decltype(visitors)...>{
*this}
.peekComponentValue(delimiter, visitors...);
}
template <typename ReturnT>
constexpr ReturnT CSSSyntaxParser::peekComponentValue(
const CSSComponentValueVisitor<ReturnT> auto&... visitors)
requires(CSSUniqueComponentValueVisitors<ReturnT, decltype(visitors)...>)
{
return peekComponentValue<ReturnT>(
CSSComponentValueDelimiter::None, visitors...);
}
} // namespace facebook::react
@@ -75,9 +75,11 @@ 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>) {
@@ -104,9 +106,11 @@ class CSSValueParser {
const CSSSimpleBlock& block,
CSSSyntaxParser& blockParser) {
if constexpr (CSSSimpleBlockSink<ParserT>) {
auto currentParser = blockParser;
if (auto ret = ParserT::consumeSimpleBlock(block, blockParser)) {
return *ret;
}
blockParser = currentParser;
}
return tryConsumeSimpleBlock<ReturnT, RestParserT...>(block, blockParser);
@@ -127,9 +131,11 @@ class CSSValueParser {
const CSSFunctionBlock& func,
CSSSyntaxParser& blockParser) {
if constexpr (CSSFunctionBlockSink<ParserT>) {
auto currentParser = blockParser;
if (auto ret = ParserT::consumeFunctionBlock(func, blockParser)) {
return *ret;
}
blockParser = currentParser;
}
return tryConsumeFunctionBlock<ReturnT, RestParserT...>(func, blockParser);
@@ -392,4 +392,89 @@ TEST(CSSSyntaxParser, unconsumed_simple_block_args) {
EXPECT_EQ(funcValue, std::nullopt);
}
TEST(CSSSyntaxParser, peek_does_not_consume) {
CSSSyntaxParser parser{"foo()"};
auto funcValue = parser.peekComponentValue<std::optional<std::string_view>>(
[&](const CSSFunctionBlock& function, CSSSyntaxParser& /*blockParser*/) {
EXPECT_EQ(function.name, "foo");
return function.name;
});
EXPECT_EQ(funcValue, "foo");
auto funcValue2 = parser.peekComponentValue<std::optional<std::string_view>>(
[&](const CSSFunctionBlock& function, CSSSyntaxParser& /*blockParser*/) {
EXPECT_EQ(function.name, "foo");
return function.name;
});
EXPECT_EQ(funcValue2, "foo");
auto funcValue3 =
parser.consumeComponentValue<std::optional<std::string_view>>(
[&](const CSSFunctionBlock& function,
CSSSyntaxParser& /*blockParser*/) {
EXPECT_EQ(function.name, "foo");
return function.name;
});
EXPECT_EQ(funcValue3, "foo");
auto funcValue4 = parser.peekComponentValue<std::optional<std::string_view>>(
[&](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<std::string_view>(
CSSComponentValueDelimiter::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<std::string_view>(
CSSComponentValueDelimiter::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<std::string_view>(
CSSComponentValueDelimiter::Whitespace,
[](const CSSPreservedToken& token) {
EXPECT_EQ(token.type(), CSSTokenType::Ident);
EXPECT_EQ(token.stringValue(), "bar");
return token.stringValue();
});
EXPECT_EQ(identValue, "bar");
}
} // namespace facebook::react