Files
react-native/packages/react-native/ReactCommon/react/renderer/css/CSSValueParser.h
T
Nick Gerleman 69f761f1ac Allow parser to support generic data types (#48718)
Summary:
Pull Request resolved: https://github.com/facebook/react-native/pull/48718

This diff looks a bit scary, but it's mostly just structural changes of existing code and some deletion, on a well tested path 😅.

The current parser implementation tries to special case "basic" data types. When I was looking at how to add in support for more complex values, such as lists, function notations, and more compounded types, this distinction ends up not making much sense.

Instead of treating some types as basic, this diff instead moves to a model where a user can declare any structure as a `CSSDataType`, so long as they also supply a parser, which may be visited when iterating through CSS syntax blocks (preserved tokens, function blocks, or simple blocks, which probably won't be used). The user then specifies a list of supported CSS data types to parse, which invokes said parser, calling any defined methods for specific syntax. E.g.

```cpp
struct CSSNumber {
  float value{};
};

template <>
struct CSSDataTypeParser<CSSNumber> {
  static constexpr auto consumePreservedToken(const CSSPreservedToken& token)
      -> std::optional<CSSNumber> {
    if (token.type() == CSSTokenType::Number) {
      return CSSNumber{token.numericValue()};
    }

    return {};
  }

  // Could also accept function block here as well (e.g. for future math
  // expressions)
};

static_assert(CSSDataType<CSSNumber>);
```

```cpp
// Can be one of std::monostate (variant null-type), CSSWideKeyword,
// CSSNumber, CSSLength, or CSSPercentage. In this case, a CSSLength.
auto value = parseCSSProperty<CSSNumber, CSSLength, CSSPercentage>("5px");
```

This breaks a whole lot of assumptions I made a year ago, especially around `CSSValueVariant` which must now be able to handle arbitrary values. For now, for the sake of simplicity, I threw this out, and migrated parser code to use plain-old `std::variant`, which has a downside of being a bit less optimized in terms of storage. I also ended up completely throwing out `CSSDeclaredStyle`, since it would majorly need to change, and we're not going to be migrating style storage quite yet. This change also broke the `CSSProperties.h` property definitions and parsing shorthand a bit, which we will need for value processing later. I also opted to delete this for now (a big centralized list is the wrong structure anyways), but will likely copy bits from its source history later.

Another particular hairy bit, that likely won't bite us in practice, is that some strings may be parseable under different data types. This just adds caller requirement to order the types correctly, instead of precedence being implemented as part of the parser.

Changelog: [Internal]

Reviewed By: lenaic

Differential Revision: D68245734

fbshipit-source-id: 132b11053cf41f57483c89176a9a6dceebb69fad
2025-01-16 11:42:34 -08:00

171 lines
4.8 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 <optional>
#include <type_traits>
#include <variant>
#include <react/renderer/css/CSSDataType.h>
#include <react/renderer/css/CSSKeyword.h>
#include <react/renderer/css/CSSSyntaxParser.h>
namespace facebook::react {
namespace detail {
class CSSValueParser {
public:
explicit constexpr CSSValueParser(CSSSyntaxParser& parser)
: parser_{parser} {}
/*
* Attempts to parse the characters starting at the current component value
* into one of the given data types. Types are attempted in order, which the
* caller must consider (e.g. a number token should be preferred to be a
* <number> before <length>).
*/
template <CSSDataType... AllowedTypesT>
constexpr std::variant<std::monostate, AllowedTypesT...> consumeValue(
CSSComponentValueDelimiter delimeter = CSSComponentValueDelimiter::None) {
using ReturnT = std::variant<std::monostate, AllowedTypesT...>;
return parser_.consumeComponentValue<ReturnT>(
delimeter,
[&](const CSSPreservedToken& token) {
return tryConsumePreservedToken<
ReturnT,
CSSDataTypeParser<AllowedTypesT>...>(token);
},
[&](const CSSSimpleBlock& block) {
return tryConsumeSimpleBlock<
ReturnT,
CSSDataTypeParser<AllowedTypesT>...>(block);
},
[&](const CSSFunctionBlock& func) {
return tryConsumeFunctionBlock<
ReturnT,
CSSDataTypeParser<AllowedTypesT>...>(func);
});
}
constexpr bool isFinished() const {
return parser_.isFinished();
}
constexpr void consumeWhitespace() {
parser_.consumeWhitespace();
}
private:
template <typename ReturnT>
constexpr ReturnT tryConsumePreservedToken(
const CSSPreservedToken& /*token*/) {
return {};
}
template <
typename ReturnT,
CSSValidDataTypeParser ParserT,
CSSValidDataTypeParser... RestParserT>
constexpr ReturnT tryConsumePreservedToken(const CSSPreservedToken& token) {
if constexpr (CSSPreservedTokenSink<ParserT>) {
if (auto ret = ParserT::consumePreservedToken(token, parser_)) {
return *ret;
}
}
if constexpr (CSSSimplePreservedTokenSink<ParserT>) {
if (auto ret = ParserT::consumePreservedToken(token)) {
return *ret;
}
}
return tryConsumePreservedToken<ReturnT, RestParserT...>(token);
}
template <typename ReturnT>
constexpr ReturnT tryConsumeSimpleBlock(const CSSSimpleBlock& /*token*/) {
return {};
}
template <
typename ReturnT,
CSSValidDataTypeParser ParserT,
CSSValidDataTypeParser... RestParserT>
constexpr ReturnT tryConsumeSimpleBlock(const CSSSimpleBlock& block) {
if constexpr (CSSSimpleBlockSink<ParserT>) {
if (auto ret = ParserT::consumeSimpleBlock(block, parser_)) {
return *ret;
}
}
return tryConsumeSimpleBlock<ReturnT, RestParserT...>(block);
}
template <typename ReturnT>
constexpr ReturnT tryConsumeFunctionBlock(const CSSFunctionBlock& /*func*/) {
return {};
}
template <
typename ReturnT,
CSSValidDataTypeParser ParserT,
CSSValidDataTypeParser... RestParserT>
constexpr ReturnT tryConsumeFunctionBlock(const CSSFunctionBlock& func) {
if constexpr (CSSFunctionBlockSink<ParserT>) {
if (auto ret = ParserT::consumeFunctionBlock(func, parser_)) {
return *ret;
}
}
return tryConsumeFunctionBlock<ReturnT, RestParserT...>(func);
}
CSSSyntaxParser& parser_;
};
} // namespace detail
/**
* Parse a single CSS property value. Returns a variant holding std::monostate
* on syntax error.
*/
template <CSSDataType... AllowedTypesT>
constexpr auto parseCSSProperty(std::string_view css)
-> std::variant<std::monostate, CSSWideKeyword, AllowedTypesT...> {
CSSSyntaxParser syntaxParser(css);
detail::CSSValueParser parser(syntaxParser);
parser.consumeWhitespace();
auto value = parser.consumeValue<CSSWideKeyword, AllowedTypesT...>();
parser.consumeWhitespace();
if (parser.isFinished()) {
return value;
}
return {};
};
/**
* Attempts to parse the next CSS value of a given set of data types, at the
* current location of the syntax parser, advancing the syntax parser if
* successful.
*/
template <CSSDataType... AllowedTypesT>
constexpr auto parseNextCSSValue(
CSSSyntaxParser& syntaxParser,
CSSComponentValueDelimiter delimeter = CSSComponentValueDelimiter::None)
-> std::variant<std::monostate, AllowedTypesT...> {
detail::CSSValueParser valueParser(syntaxParser);
return valueParser.consumeValue<AllowedTypesT...>(delimeter);
}
} // namespace facebook::react