Files
react-native/packages/react-native/ReactCommon/react/renderer/css/CSSSyntaxParser.h
T
Nick GerlemanandFacebook GitHub Bot 6482204523 CSSDataTypeParser consume() function (#48986)
Summary:
Pull Request resolved: https://github.com/facebook/react-native/pull/48986

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

fbshipit-source-id: 660e0b4a496136c8a559f4ba47bc1bd8d17aa116
2025-02-04 12:16:40 -08:00

416 lines
13 KiB
C++

/*
* 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.
*/
#pragma once
#include <concepts>
#include <optional>
#include <react/renderer/css/CSSTokenizer.h>
namespace facebook::react {
class CSSSyntaxParser;
/**
* Describes context for a CSS function component value.
*/
struct CSSFunctionBlock {
std::string_view name{};
};
/**
* Describes a preserved token component value.
*/
using CSSPreservedToken = CSSToken;
/**
* Describes context for a CSS function component value.
*/
struct CSSSimpleBlock {
CSSTokenType openBracketType{};
};
/**
* Describes a valid return type for a CSSSyntaxParser visitor
*/
template <typename T>
concept CSSSyntaxVisitorReturn =
std::is_default_constructible_v<T> && std::equality_comparable<T>;
/**
* A CSSFunctionVisitor is called to start parsing a function component value.
* At this point, the Parser is positioned at the start of the function
* component value list. It is expected that the visitor finishes before the end
* of the function block.
*/
template <typename T, typename ReturnT>
concept CSSFunctionVisitor = CSSSyntaxVisitorReturn<ReturnT> &&
requires(T visitor, CSSFunctionBlock func, CSSSyntaxParser& blockParser) {
{ visitor(func, blockParser) } -> std::convertible_to<ReturnT>;
};
/**
* A CSSPreservedTokenVisitor is called after parsing a preserved token
* component value.
*/
template <typename T, typename ReturnT>
concept CSSPreservedTokenVisitor = CSSSyntaxVisitorReturn<ReturnT> &&
requires(T visitor, CSSPreservedToken token) {
{ visitor(token) } -> std::convertible_to<ReturnT>;
};
/**
* A CSSSimpleBlockVisitor is called after parsing a simple block component
* value. It is expected that the visitor finishes before the end
* of the block.
*/
template <typename T, typename ReturnT>
concept CSSSimpleBlockVisitor = CSSSyntaxVisitorReturn<ReturnT> &&
requires(T visitor, CSSSimpleBlock block, CSSSyntaxParser& blockParser) {
{ visitor(block, blockParser) } -> std::convertible_to<ReturnT>;
};
/**
* Any visitor for a component value.
*/
template <typename T, typename ReturnT>
concept CSSComponentValueVisitor = CSSSyntaxVisitorReturn<ReturnT> &&
(CSSFunctionVisitor<T, ReturnT> || CSSPreservedTokenVisitor<T, ReturnT> ||
CSSSimpleBlockVisitor<T, ReturnT>);
/**
* Represents a variadic set of CSSComponentValueVisitor with no more than one
* of a specific type of visitor.
*/
template <typename ReturnT, typename... VisitorsT>
concept CSSUniqueComponentValueVisitors = CSSSyntaxVisitorReturn<ReturnT> &&
(CSSComponentValueVisitor<VisitorsT, ReturnT> && ...) &&
((CSSFunctionVisitor<VisitorsT, ReturnT> ? 1 : 0) + ... + 0) <= 1 &&
((CSSPreservedTokenVisitor<VisitorsT, ReturnT> ? 1 : 0) + ... + 0) <= 1 &&
((CSSSimpleBlockVisitor<VisitorsT, ReturnT> ? 1 : 0) + ... + 0) <= 1;
/**
* Describes the delimeter to expect before the next component value.
*/
enum class CSSDelimiter {
Whitespace,
OptionalWhitespace,
Solidus,
SolidusOrWhitespace,
Comma,
CommaOrWhitespace,
None,
};
/**
* CSSSyntaxParser allows parsing streams of CSS text into "component
* values".
*
* https://www.w3.org/TR/css-syntax-3/#component-value
*/
class CSSSyntaxParser {
template <
CSSSyntaxVisitorReturn ReturnT,
CSSComponentValueVisitor<ReturnT>... VisitorsT>
friend struct CSSComponentValueVisitorDispatcher;
public:
/**
* Construct the parser over the given string_view, which must stay alive for
* the duration of the CSSSyntaxParser.
*/
explicit constexpr CSSSyntaxParser(std::string_view css)
: tokenizer_{css}, currentToken_(tokenizer_.next()) {}
constexpr CSSSyntaxParser(const CSSSyntaxParser&) = default;
constexpr CSSSyntaxParser(CSSSyntaxParser&&) = default;
constexpr CSSSyntaxParser& operator=(const CSSSyntaxParser&) = default;
constexpr CSSSyntaxParser& operator=(CSSSyntaxParser&&) = default;
/**
* Directly consume the next component value. 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.
*
* The state of the parser is reset if a visitor returns a default-constructed
* value for the given return type, even if it previously advanced the parser.
* If no visitor returns a non-default-constructed value, the component value
* will not be consumed.
*
* 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 <CSSSyntaxVisitorReturn ReturnT>
constexpr ReturnT consumeComponentValue(
CSSDelimiter delimiter,
const CSSComponentValueVisitor<ReturnT> auto&... visitors)
requires(CSSUniqueComponentValueVisitors<ReturnT, decltype(visitors)...>);
template <CSSSyntaxVisitorReturn ReturnT>
constexpr ReturnT consumeComponentValue(
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
*/
constexpr bool isFinished() const {
return currentToken_.type() == CSSTokenType::EndOfFile;
}
/**
* Consume any whitespace tokens.
*/
constexpr void consumeWhitespace() {
if (currentToken_.type() == CSSTokenType::WhiteSpace) {
currentToken_ = tokenizer_.next();
}
}
/**
* 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_},
currentToken_{parser.currentToken_},
terminator_{terminator} {}
constexpr const CSSToken& peek() const {
return currentToken_;
}
constexpr CSSToken consumeToken() {
auto prevToken = currentToken_;
currentToken_ = tokenizer_.next();
return prevToken;
}
constexpr void advanceToBlockParser(CSSSyntaxParser& blockParser) {
currentToken_ = blockParser.currentToken_;
tokenizer_ = blockParser.tokenizer_;
}
CSSTokenizer tokenizer_;
CSSToken currentToken_;
CSSTokenType terminator_{CSSTokenType::EndOfFile};
};
template <
CSSSyntaxVisitorReturn ReturnT,
CSSComponentValueVisitor<ReturnT>... VisitorsT>
struct CSSComponentValueVisitorDispatcher {
CSSSyntaxParser& parser;
constexpr ReturnT consumeComponentValue(
CSSDelimiter delimiter,
const VisitorsT&... visitors) {
auto originalParser = parser;
if (!parser.consumeDelimiter(delimiter)) {
parser = originalParser;
return {};
}
if (parser.peek().type() == parser.terminator_) {
parser = originalParser;
return {};
}
switch (parser.peek().type()) {
case CSSTokenType::Function:
if (auto ret = visitFunction(visitors...)) {
return *ret;
}
break;
case CSSTokenType::OpenParen:
if (auto ret =
visitSimpleBlock(CSSTokenType::CloseParen, visitors...)) {
return *ret;
}
break;
case CSSTokenType::OpenSquare:
if (auto ret =
visitSimpleBlock(CSSTokenType::CloseSquare, visitors...)) {
return *ret;
}
break;
case CSSTokenType::OpenCurly:
if (auto ret =
visitSimpleBlock(CSSTokenType::CloseCurly, visitors...)) {
return *ret;
}
break;
default:
if (auto ret = visitPreservedToken(visitors...)) {
return *ret;
}
break;
}
parser = originalParser;
return ReturnT{};
}
constexpr std::optional<ReturnT> visitFunction(
const CSSComponentValueVisitor<ReturnT> auto& visitor,
const CSSComponentValueVisitor<ReturnT> auto&... rest) {
if constexpr (CSSFunctionVisitor<decltype(visitor), ReturnT>) {
auto name = parser.consumeToken().stringValue();
// CSS syntax spec says whitespace is a preserved token, but CSS values
// spec allows whitespace around parens for all function notation, so we
// allow this to let the visitors not need to deal with leading/trailing
// whitespace. https://www.w3.org/TR/css-values-3/#functional-notations
parser.consumeWhitespace();
auto blockParser =
CSSSyntaxParser{parser, CSSTokenType::CloseParen /*terminator*/};
auto functionValue = visitor({name}, blockParser);
parser.advanceToBlockParser(blockParser);
parser.consumeWhitespace();
if (parser.peek().type() == CSSTokenType::CloseParen &&
functionValue != ReturnT{}) {
parser.consumeToken();
return functionValue;
}
return {};
}
return visitFunction(rest...);
}
constexpr std::optional<ReturnT> visitFunction() {
return {};
}
constexpr std::optional<ReturnT> visitSimpleBlock(
CSSTokenType endToken,
const CSSComponentValueVisitor<ReturnT> auto& visitor,
const CSSComponentValueVisitor<ReturnT> auto&... rest) {
if constexpr (CSSSimpleBlockVisitor<decltype(visitor), ReturnT>) {
auto openBracketType = parser.consumeToken().type();
parser.consumeWhitespace();
auto blockParser = CSSSyntaxParser{parser, endToken};
auto blockValue = visitor({openBracketType}, blockParser);
parser.advanceToBlockParser(blockParser);
parser.consumeWhitespace();
if (parser.peek().type() == endToken && blockValue != ReturnT{}) {
parser.consumeToken();
return blockValue;
}
return {};
}
return visitSimpleBlock(endToken, rest...);
}
constexpr std::optional<ReturnT> visitSimpleBlock(CSSTokenType endToken) {
return {};
}
constexpr std::optional<ReturnT> visitPreservedToken(
const CSSComponentValueVisitor<ReturnT> auto& visitor,
const CSSComponentValueVisitor<ReturnT> auto&... rest) {
if constexpr (CSSPreservedTokenVisitor<decltype(visitor), ReturnT>) {
auto ret = visitor(parser.consumeToken());
if (ret != ReturnT{}) {
return ret;
}
}
return visitPreservedToken(rest...);
}
constexpr std::optional<ReturnT> visitPreservedToken() {
return {};
}
};
template <CSSSyntaxVisitorReturn ReturnT>
constexpr ReturnT CSSSyntaxParser::consumeComponentValue(
CSSDelimiter delimiter,
const CSSComponentValueVisitor<ReturnT> auto&... visitors)
requires(CSSUniqueComponentValueVisitors<ReturnT, decltype(visitors)...>)
{
return CSSComponentValueVisitorDispatcher<ReturnT, decltype(visitors)...>{
*this}
.consumeComponentValue(delimiter, visitors...);
}
template <CSSSyntaxVisitorReturn ReturnT>
constexpr ReturnT CSSSyntaxParser::consumeComponentValue(
const CSSComponentValueVisitor<ReturnT> auto&... visitors)
requires(CSSUniqueComponentValueVisitors<ReturnT, decltype(visitors)...>)
{
return consumeComponentValue<ReturnT>(CSSDelimiter::None, visitors...);
}
} // namespace facebook::react