From 7e47df0a411d23cce31f37305ae3d14459e79a43 Mon Sep 17 00:00:00 2001 From: Nick Gerleman Date: Tue, 13 Feb 2024 20:13:22 -0800 Subject: [PATCH] Add CSSValueVariant and parseCSSValue() Summary: This adds: 1. `CSSValueVariant`: A union-y type, mapping to a collection of CSS basic data types, and a set of allowed keywords. The aim here is to more closely model the data types after the CSS spec, to allow RN to store them correctly, while not taking up too much space. These types will form the foundation of Yoga prop storage (and probably some other props down the line), so compactness is a priority. 2. `parseCSSValue()`: This uses the previously added Tokenizer, along with parsing rules, to be able to parse a single component value, into a literal keyword, ``, ``, ``, or ``. This will be wired to the props parsing infrastructure. See D53461299 for an example of what this will look like in props storage. Changelog: [Internal] Reviewed By: rozele Differential Revision: D53342595 fbshipit-source-id: 3f00dfd7c0ead3dbef4605a61e9859cf69945fe5 --- .../react/renderer/css/CSSKeywords.h | 419 ++++++++++++++++++ .../react/renderer/css/CSSLengthUnit.h | 165 +++++++ .../react/renderer/css/CSSParser.h | 91 ++++ .../react/renderer/css/CSSTokenizer.cpp | 2 +- .../react/renderer/css/CSSTokenizer.h | 2 +- .../ReactCommon/react/renderer/css/CSSValue.h | 169 +++++++ .../renderer/css/tests/CSSParserTest.cpp | 125 ++++++ .../renderer/css/tests/CSSTokenizerTest.cpp | 4 +- .../ReactCommon/react/utils/PackTraits.h | 40 ++ .../ReactCommon/react/utils/fnv1a.h | 7 +- .../ReactCommon/react/utils/to_underlying.h | 22 + 11 files changed, 1041 insertions(+), 5 deletions(-) create mode 100644 packages/react-native/ReactCommon/react/renderer/css/CSSKeywords.h create mode 100644 packages/react-native/ReactCommon/react/renderer/css/CSSLengthUnit.h create mode 100644 packages/react-native/ReactCommon/react/renderer/css/CSSParser.h create mode 100644 packages/react-native/ReactCommon/react/renderer/css/CSSValue.h create mode 100644 packages/react-native/ReactCommon/react/renderer/css/tests/CSSParserTest.cpp create mode 100644 packages/react-native/ReactCommon/react/utils/PackTraits.h create mode 100644 packages/react-native/ReactCommon/react/utils/to_underlying.h diff --git a/packages/react-native/ReactCommon/react/renderer/css/CSSKeywords.h b/packages/react-native/ReactCommon/react/renderer/css/CSSKeywords.h new file mode 100644 index 00000000000..1eecb1558cb --- /dev/null +++ b/packages/react-native/ReactCommon/react/renderer/css/CSSKeywords.h @@ -0,0 +1,419 @@ +/* + * 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 +#include +#include +#include + +#include +#include + +namespace facebook::react { + +/** + * One of any predefined CSS keywords. + * https://www.w3.org/TR/css-values-3/#keywords + */ +enum class CSSKeyword : uint8_t { + Absolute, + Auto, + Baseline, + Center, + Column, + ColumnReverse, + Flex, + FlexEnd, + FlexStart, + Hidden, + Inherit, + Initial, + Inline, + Ltr, + None, + NoWrap, + Relative, + Row, + RowReverse, + Rtl, + Scroll, + SpaceAround, + SpaceBetween, + SpaceEvenly, + Static, + Stretch, + Unset, + Visible, + Wrap, + WrapReverse, +}; + +/** + * Represents a set of CSS keywords, including CSS-wide keywords. + */ +template +concept CSSKeywordSet = std::is_enum_v && requires { + { T::Inherit } -> std::same_as; + { T::Initial } -> std::same_as; + { T::Unset } -> std::same_as; +}; + +/** + * Defines a new set of CSS keywords + */ +#define CSS_DEFINE_KEYWORD_SET(name, ...) \ + enum class name : uint8_t { \ + Inherit = to_underlying(CSSKeyword::Inherit), \ + Initial = to_underlying(CSSKeyword::Initial), \ + Unset = to_underlying(CSSKeyword::Unset), \ + __VA_ARGS__ \ + }; + +/** + * CSS-wide keywords. + * https://www.w3.org/TR/css-values-4/#common-keywords + */ +CSS_DEFINE_KEYWORD_SET(CSSWideKeyword) + +/** + * CSS-wide keywords along with a context-dependent "auto" keyword. + */ +CSS_DEFINE_KEYWORD_SET(CSSAutoKeyword, Auto = to_underlying(CSSKeyword::Auto)) + +/** + * Keywords for the CSS "align-content" property. + * https://www.w3.org/TR/css-flexbox-1/#align-content-property + * https://www.w3.org/TR/css-align-3/#align-justify-content + */ +CSS_DEFINE_KEYWORD_SET( + CSSAlignContent, + Center = to_underlying(CSSKeyword::Center), + FlexEnd = to_underlying(CSSKeyword::FlexEnd), + FlexStart = to_underlying(CSSKeyword::FlexStart), + SpaceAround = to_underlying(CSSKeyword::SpaceAround), + SpaceBetween = to_underlying(CSSKeyword::SpaceBetween), + SpaceEvenly = to_underlying(CSSKeyword::SpaceEvenly), + Stretch = to_underlying(CSSKeyword::Stretch)) + +/** + * Keywords for the CSS "align-items" property. + * https://www.w3.org/TR/css-flexbox-1/#align-items-property + * https://www.w3.org/TR/css-align-3/#align-items-property + */ +CSS_DEFINE_KEYWORD_SET( + CSSAlignItems, + Baseline = to_underlying(CSSKeyword::Baseline), + Center = to_underlying(CSSKeyword::Center), + FlexEnd = to_underlying(CSSKeyword::FlexEnd), + FlexStart = to_underlying(CSSKeyword::FlexStart), + Stretch = to_underlying(CSSKeyword::Stretch)) + +/** + * Keywords for the CSS "align-items" property. + * https://www.w3.org/TR/css-flexbox-1/#align-self-property + * https://www.w3.org/TR/css-align-3/#align-self-property + */ +CSS_DEFINE_KEYWORD_SET( + CSSAlignSelf, + Auto = to_underlying(CSSKeyword::Auto), + Baseline = to_underlying(CSSKeyword::Baseline), + Center = to_underlying(CSSKeyword::Center), + FlexEnd = to_underlying(CSSKeyword::FlexEnd), + FlexStart = to_underlying(CSSKeyword::FlexStart), + Stretch = to_underlying(CSSKeyword::Stretch)) + +/** + * Keywords for the CSS "direction" property. + * https://www.w3.org/TR/css-writing-modes-3/#direction + */ +CSS_DEFINE_KEYWORD_SET( + CSSDirection, + Ltr = to_underlying(CSSKeyword::Ltr), + Rtl = to_underlying(CSSKeyword::Rtl)) + +/** + * Keywords for the CSS "display" property. + * https://www.w3.org/TR/css-display-3/#display-type + */ +CSS_DEFINE_KEYWORD_SET( + CSSDisplay, + Flex = to_underlying(CSSKeyword::Flex), + Inline = to_underlying(CSSKeyword::Inline), + None = to_underlying(CSSKeyword::None)) + +/** + * Keywords for the CSS "flex-direction" property. + * https://www.w3.org/TR/css-flexbox-1/#flex-direction-property + */ +CSS_DEFINE_KEYWORD_SET( + CSSFlexDirection, + Column = to_underlying(CSSKeyword::Column), + ColumnReverse = to_underlying(CSSKeyword::ColumnReverse), + Row = to_underlying(CSSKeyword::Row), + RowReverse = to_underlying(CSSKeyword::RowReverse)) + +/** + * Keywords for the CSS "flex-wrap" property. + * https://www.w3.org/TR/css-flexbox-1/#flex-wrap-property + */ +CSS_DEFINE_KEYWORD_SET( + CSSFlexWrap, + NoWrap = to_underlying(CSSKeyword::NoWrap), + Wrap = to_underlying(CSSKeyword::Wrap), + WrapReverse = to_underlying(CSSKeyword::WrapReverse)) + +/** + * Keywords for the CSS "justify-content" property. + * https://www.w3.org/TR/css-flexbox-1/#justify-content-property + * https://www.w3.org/TR/css-align-3/#align-justify-content + */ +CSS_DEFINE_KEYWORD_SET( + CSSJustifyContent, + Center = to_underlying(CSSKeyword::Center), + FlexEnd = to_underlying(CSSKeyword::FlexEnd), + FlexStart = to_underlying(CSSKeyword::FlexStart), + SpaceAround = to_underlying(CSSKeyword::SpaceAround), + SpaceBetween = to_underlying(CSSKeyword::SpaceBetween), + SpaceEvenly = to_underlying(CSSKeyword::SpaceEvenly)) + +/** + * Keywords for the CSS "overflow" property. + * https://www.w3.org/TR/css-overflow-3/#overflow-control + */ +CSS_DEFINE_KEYWORD_SET( + CSSOverflow, + Hidden = to_underlying(CSSKeyword::Hidden), + Scroll = to_underlying(CSSKeyword::Scroll), + Visible = to_underlying(CSSKeyword::Visible)) + +/** + * Keywords for the CSS "position" property. + * https://www.w3.org/TR/css-position-3/#position-property + */ +CSS_DEFINE_KEYWORD_SET( + CSSPosition, + Absolute = to_underlying(CSSKeyword::Absolute), + Relative = to_underlying(CSSKeyword::Relative), + Static = to_underlying(CSSKeyword::Static)) + +/** + * Compare two keywords of any representation + */ +constexpr bool operator==(CSSKeywordSet auto lhs, CSSKeywordSet auto rhs) { + return to_underlying(lhs) == to_underlying(rhs); +} + +/** + * Defines a concept for whether an enum has a given member. + */ +#define CSS_DEFINE_KEYWORD_CONEPTS(name) \ + namespace detail { \ + template \ + concept has##name = (CSSKeywordSet && requires() { T::name; }); \ + } + +CSS_DEFINE_KEYWORD_CONEPTS(Absolute) +CSS_DEFINE_KEYWORD_CONEPTS(Auto) +CSS_DEFINE_KEYWORD_CONEPTS(Baseline) +CSS_DEFINE_KEYWORD_CONEPTS(Center) +CSS_DEFINE_KEYWORD_CONEPTS(Column) +CSS_DEFINE_KEYWORD_CONEPTS(ColumnReverse) +CSS_DEFINE_KEYWORD_CONEPTS(Flex) +CSS_DEFINE_KEYWORD_CONEPTS(FlexEnd) +CSS_DEFINE_KEYWORD_CONEPTS(FlexStart) +CSS_DEFINE_KEYWORD_CONEPTS(Hidden) +CSS_DEFINE_KEYWORD_CONEPTS(Inherit) +CSS_DEFINE_KEYWORD_CONEPTS(Initial) +CSS_DEFINE_KEYWORD_CONEPTS(Inline) +CSS_DEFINE_KEYWORD_CONEPTS(Ltr) +CSS_DEFINE_KEYWORD_CONEPTS(None) +CSS_DEFINE_KEYWORD_CONEPTS(NoWrap) +CSS_DEFINE_KEYWORD_CONEPTS(Relative) +CSS_DEFINE_KEYWORD_CONEPTS(Row) +CSS_DEFINE_KEYWORD_CONEPTS(RowReverse) +CSS_DEFINE_KEYWORD_CONEPTS(Rtl) +CSS_DEFINE_KEYWORD_CONEPTS(Scroll) +CSS_DEFINE_KEYWORD_CONEPTS(SpaceAround) +CSS_DEFINE_KEYWORD_CONEPTS(SpaceBetween) +CSS_DEFINE_KEYWORD_CONEPTS(SpaceEvenly) +CSS_DEFINE_KEYWORD_CONEPTS(Static) +CSS_DEFINE_KEYWORD_CONEPTS(Stretch) +CSS_DEFINE_KEYWORD_CONEPTS(Unset) +CSS_DEFINE_KEYWORD_CONEPTS(Visible) +CSS_DEFINE_KEYWORD_CONEPTS(Wrap) +CSS_DEFINE_KEYWORD_CONEPTS(WrapReverse) + +/** + * Parses an ident token, case-insensitive, into a keyword. + * + * Returns KeywordT::Unset if the ident does not match any entries + * in the keyword-set, or CSS-wide keywords. + */ +template +constexpr std::optional parseCSSKeyword(std::string_view ident) { + struct LowerCaseTransform { + char operator()(char c) const { + return static_cast(tolower(c)); + } + }; + + switch (fnv1a(ident)) { + case fnv1a("absolute"): + if constexpr (detail::hasAbsolute) { + return KeywordT::Absolute; + } + break; + case fnv1a("auto"): + if constexpr (detail::hasAuto) { + return KeywordT::Auto; + } + break; + case fnv1a("baseline"): + if constexpr (detail::hasBaseline) { + return KeywordT::Baseline; + } + break; + case fnv1a("center"): + if constexpr (detail::hasCenter) { + return KeywordT::Center; + } + break; + case fnv1a("column"): + if constexpr (detail::hasColumn) { + return KeywordT::Column; + } + break; + case fnv1a("column-reverse"): + if constexpr (detail::hasColumnReverse) { + return KeywordT::ColumnReverse; + } + break; + case fnv1a("flex"): + if constexpr (detail::hasFlex) { + return KeywordT::Flex; + } + break; + case fnv1a("flex-end"): + if constexpr (detail::hasFlexEnd) { + return KeywordT::FlexEnd; + } + break; + case fnv1a("flex-start"): + if constexpr (detail::hasFlexStart) { + return KeywordT::FlexStart; + } + break; + case fnv1a("hidden"): + if constexpr (detail::hasHidden) { + return KeywordT::Hidden; + } + break; + case fnv1a("inherit"): + if constexpr (detail::hasInherit) { + return KeywordT::Inherit; + } + break; + case fnv1a("inline"): + if constexpr (detail::hasInline) { + return KeywordT::Inline; + } + break; + case fnv1a("ltr"): + if constexpr (detail::hasLtr) { + return KeywordT::Ltr; + } + break; + case fnv1a("none"): + if constexpr (detail::hasNone) { + return KeywordT::None; + } + break; + case fnv1a("no-wrap"): + if constexpr (detail::hasNoWrap) { + return KeywordT::NoWrap; + } + break; + case fnv1a("relative"): + if constexpr (detail::hasRelative) { + return KeywordT::Relative; + } + break; + case fnv1a("row"): + if constexpr (detail::hasRow) { + return KeywordT::Row; + } + break; + case fnv1a("row-reverse"): + if constexpr (detail::hasRowReverse) { + return KeywordT::RowReverse; + } + break; + case fnv1a("rtl"): + if constexpr (detail::hasRtl) { + return KeywordT::Rtl; + } + break; + case fnv1a("space-between"): + if constexpr (detail::hasSpaceBetween) { + return KeywordT::SpaceBetween; + } + break; + case fnv1a("space-around"): + if constexpr (detail::hasSpaceAround) { + return KeywordT::SpaceAround; + } + break; + case fnv1a("space-evenly"): + if constexpr (detail::hasSpaceEvenly) { + return KeywordT::SpaceEvenly; + } + break; + case fnv1a("scroll"): + if constexpr (detail::hasScroll) { + return KeywordT::Scroll; + } + break; + case fnv1a("static"): + if constexpr (detail::hasStatic) { + return KeywordT::Static; + } + break; + case fnv1a("stretch"): + if constexpr (detail::hasStretch) { + return KeywordT::Stretch; + } + break; + case fnv1a("unset"): + if constexpr (detail::hasUnset) { + return KeywordT::Unset; + } + break; + case fnv1a("visible"): + if constexpr (detail::hasVisible) { + return KeywordT::Visible; + } + break; + case fnv1a("wrap"): + if constexpr (detail::hasWrap) { + return KeywordT::Wrap; + } + break; + case fnv1a("wrap-reverse"): + if constexpr (detail::hasWrapReverse) { + return KeywordT::WrapReverse; + } + break; + default: + break; + } + + return std::nullopt; +} + +} // namespace facebook::react diff --git a/packages/react-native/ReactCommon/react/renderer/css/CSSLengthUnit.h b/packages/react-native/ReactCommon/react/renderer/css/CSSLengthUnit.h new file mode 100644 index 00000000000..dea830d6727 --- /dev/null +++ b/packages/react-native/ReactCommon/react/renderer/css/CSSLengthUnit.h @@ -0,0 +1,165 @@ +/* + * 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 +#include +#include + +#include + +namespace facebook::react { + +/** + * Unit for the CSS type. + * https://www.w3.org/TR/css-values-4/#lengths + */ +enum class CSSLengthUnit : uint8_t { + Cap, + Ch, + Cm, + Dvb, + Dvh, + Dvi, + Dvmax, + Dvmin, + Dvw, + Em, + Ex, + Ic, + In, + Lh, + Lvb, + Lvh, + Lvi, + Lvmax, + Lvmin, + Lvw, + Mm, + Pc, + Pt, + Px, + Q, + Rcap, + Rch, + Rem, + Rex, + Ric, + Rlh, + Svb, + Svh, + Svi, + Svmax, + Svmin, + Svw, + Vb, + Vh, + Vi, + Vmax, + Vmin, + Vw, +}; + +/** + * Parses a unit from a dimension token into a CSS length unit. + */ +constexpr std::optional parseCSSLengthUnit( + std::string_view unit) { + switch (fnv1a(unit)) { + case fnv1a("cap"): + return CSSLengthUnit::Cap; + case fnv1a("ch"): + return CSSLengthUnit::Ch; + case fnv1a("cm"): + return CSSLengthUnit::Cm; + case fnv1a("dvb"): + return CSSLengthUnit::Dvb; + case fnv1a("dvh"): + return CSSLengthUnit::Dvh; + case fnv1a("dvi"): + return CSSLengthUnit::Dvi; + case fnv1a("dvmax"): + return CSSLengthUnit::Dvmax; + case fnv1a("dvmin"): + return CSSLengthUnit::Dvmin; + case fnv1a("dvw"): + return CSSLengthUnit::Dvw; + case fnv1a("em"): + return CSSLengthUnit::Em; + case fnv1a("ex"): + return CSSLengthUnit::Ex; + case fnv1a("ic"): + return CSSLengthUnit::Ic; + case fnv1a("in"): + return CSSLengthUnit::In; + case fnv1a("lh"): + return CSSLengthUnit::Lh; + case fnv1a("lvb"): + return CSSLengthUnit::Lvb; + case fnv1a("lvh"): + return CSSLengthUnit::Lvh; + case fnv1a("lvi"): + return CSSLengthUnit::Lvi; + case fnv1a("lvmax"): + return CSSLengthUnit::Lvmax; + case fnv1a("lvmin"): + return CSSLengthUnit::Lvmin; + case fnv1a("lvw"): + return CSSLengthUnit::Lvw; + case fnv1a("mm"): + return CSSLengthUnit::Mm; + case fnv1a("pc"): + return CSSLengthUnit::Pc; + case fnv1a("pt"): + return CSSLengthUnit::Pt; + case fnv1a("px"): + return CSSLengthUnit::Px; + case fnv1a("q"): + return CSSLengthUnit::Q; + case fnv1a("rcap"): + return CSSLengthUnit::Rcap; + case fnv1a("rch"): + return CSSLengthUnit::Rch; + case fnv1a("rem"): + return CSSLengthUnit::Rem; + case fnv1a("rex"): + return CSSLengthUnit::Rex; + case fnv1a("ric"): + return CSSLengthUnit::Ric; + case fnv1a("rlh"): + return CSSLengthUnit::Rlh; + case fnv1a("svb"): + return CSSLengthUnit::Svb; + case fnv1a("svh"): + return CSSLengthUnit::Svh; + case fnv1a("svi"): + return CSSLengthUnit::Svi; + case fnv1a("svmax"): + return CSSLengthUnit::Svmax; + case fnv1a("svmin"): + return CSSLengthUnit::Svmin; + case fnv1a("svw"): + return CSSLengthUnit::Svw; + case fnv1a("vb"): + return CSSLengthUnit::Vb; + case fnv1a("vh"): + return CSSLengthUnit::Vh; + case fnv1a("vi"): + return CSSLengthUnit::Vi; + case fnv1a("vmax"): + return CSSLengthUnit::Vmax; + case fnv1a("vmin"): + return CSSLengthUnit::Vmin; + case fnv1a("vw"): + return CSSLengthUnit::Vw; + default: + return std::nullopt; + } +} + +} // namespace facebook::react diff --git a/packages/react-native/ReactCommon/react/renderer/css/CSSParser.h b/packages/react-native/ReactCommon/react/renderer/css/CSSParser.h new file mode 100644 index 00000000000..c31cbf26fd2 --- /dev/null +++ b/packages/react-native/ReactCommon/react/renderer/css/CSSParser.h @@ -0,0 +1,91 @@ +/* + * 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 + +#include +#include +#include +#include +#include + +namespace facebook::react { + +namespace detail { +template +CSSValueVariant consumeComponentValue( + const CSSToken& token) { + using CSSValueT = CSSValueVariant; + switch (token.type()) { + case CSSTokenType::Ident: + if (auto keyword = parseCSSKeyword(token.stringValue())) { + return CSSValueT::keyword(*keyword); + } + break; + case CSSTokenType::Dimension: + if constexpr (traits::containsType()) { + if (auto unit = parseCSSLengthUnit(token.unit())) { + return CSSValueT::length(token.numericValue(), *unit); + } + } + break; + case CSSTokenType::Percentage: + if constexpr (traits::containsType()) { + return CSSValueT::percentage(token.numericValue()); + } + break; + case CSSTokenType::Number: + // For zero lengths the unit identifier is optional (i.e. can be + // syntactically represented as the 0). However, if a 0 could + // be parsed as either a or a in a property (such as + // line-height), it must parse as a . + // https://www.w3.org/TR/css-values-4/#lengths + if constexpr (traits::containsType()) { + return CSSValueT::number(token.numericValue()); + } else if constexpr (traits::containsType()) { + if (token.numericValue() == 0) { + return CSSValueT::length(0, CSSLengthUnit::Px); + } + } + break; + default: + break; + } + return CSSValueT{}; +} +} // namespace detail + +/* + * Parse a single CSS component value as a keyword constrained to those + * allowable by KeywordRepresentationT. + * https://www.w3.org/TR/css-syntax-3/#parse-component-value + */ +template +CSSValueVariant parseCSSValue(std::string_view css) { + CSSTokenizer tokenizer(css); + + auto token = tokenizer.next(); + while (token.type() == CSSTokenType::WhiteSpace) { + token = tokenizer.next(); + } + + auto value = detail::consumeComponentValue(token); + + token = tokenizer.next(); + while (token.type() == CSSTokenType::WhiteSpace) { + token = tokenizer.next(); + } + if (token.type() == CSSTokenType::EndOfFile) { + return value; + } + + return {}; +} + +} // namespace facebook::react diff --git a/packages/react-native/ReactCommon/react/renderer/css/CSSTokenizer.cpp b/packages/react-native/ReactCommon/react/renderer/css/CSSTokenizer.cpp index 99ddce3a209..3443786de5f 100644 --- a/packages/react-native/ReactCommon/react/renderer/css/CSSTokenizer.cpp +++ b/packages/react-native/ReactCommon/react/renderer/css/CSSTokenizer.cpp @@ -141,7 +141,7 @@ CSSToken CSSTokenizer::consumeNumeric() { } else if (peek() == '%') { advance(); consumeRunningValue(); - return {CSSTokenType::Percent, numberToken.numericValue()}; + return {CSSTokenType::Percentage, numberToken.numericValue()}; } else { return numberToken; } diff --git a/packages/react-native/ReactCommon/react/renderer/css/CSSTokenizer.h b/packages/react-native/ReactCommon/react/renderer/css/CSSTokenizer.h index 647e9bd8789..400d2916cd1 100644 --- a/packages/react-native/ReactCommon/react/renderer/css/CSSTokenizer.h +++ b/packages/react-native/ReactCommon/react/renderer/css/CSSTokenizer.h @@ -21,7 +21,7 @@ enum class CSSTokenType { EndOfFile, Ident, Number, - Percent, + Percentage, WhiteSpace, }; diff --git a/packages/react-native/ReactCommon/react/renderer/css/CSSValue.h b/packages/react-native/ReactCommon/react/renderer/css/CSSValue.h new file mode 100644 index 00000000000..6f6659c97a4 --- /dev/null +++ b/packages/react-native/ReactCommon/react/renderer/css/CSSValue.h @@ -0,0 +1,169 @@ +/* + * 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 +#include +#include +#include + +#include +#include +#include + +namespace facebook::react { + +/** + * Represents a CSS component value type. + * https://www.w3.org/TR/css-values-3/#component-types + */ +enum class CSSValueType : uint8_t { + Keyword, + Length, + Number, + Percentage, +}; + +/** + * Concrete representation for a CSS basic data type. + * https://www.w3.org/TR/css-values-3/#component-types + */ +template +concept CSSBasicDataType = std::is_trivially_destructible_v && + std::is_default_constructible_v && requires() { + sizeof(T); +}; + +#pragma pack(push, 1) +/** + * Representation of CSS data type + * https://www.w3.org/TR/css-values-3/#lengths + */ +struct CSSLength { + float value{}; + CSSLengthUnit unit{CSSLengthUnit::Px}; + constexpr bool operator==(const CSSLength& rhs) const = default; +}; +#pragma pack(pop) + +/** + * Representation of CSS data type + * https://www.w3.org/TR/css-values-3/#percentages + */ +struct CSSPercentage { + float value{}; + constexpr bool operator==(const CSSPercentage& rhs) const = default; +}; + +/** + * Representation of CSS data type + * https://www.w3.org/TR/css-values-3/#numbers + */ +struct CSSNumber { + float value{}; + constexpr bool operator==(const CSSNumber& rhs) const = default; +}; + +/** + * CSSValueVariant represents a CSS component value: + * https://www.w3.org/TR/css-values-3/#component-types + * + * A CSSValueVariant must be constrained to the set of possible CSS types it may + * encounter. E.g. a dimension which accepts a CSS-wide keywords, "auto" or a + * would be modeled as + * `CSSValueVariant`. This + * allows for efficient storage, and customizing parsing based on the allowed + * set of values. + */ +#pragma pack(push, 1) +template +class CSSValueVariant { + template + constexpr ValueT getIf() const { + if (type_ == Type) { + return *std::launder(reinterpret_cast(data_.data())); + } else { + return ValueT{}; + } + } + + template + static constexpr bool canRepresent() { + return traits::containsType(); + } + + public: + constexpr CSSValueVariant() + : CSSValueVariant(CSSValueType::Keyword, KeywordT::Unset) {} + + static constexpr CSSValueVariant keyword(KeywordT keyword) { + return CSSValueVariant(CSSValueType::Keyword, KeywordT{keyword}); + } + + static constexpr CSSValueVariant length( + float value, + CSSLengthUnit unit) requires(canRepresent()) { + return CSSValueVariant(CSSValueType::Length, CSSLength{value, unit}); + } + + static constexpr CSSValueVariant number(float value) requires( + canRepresent()) { + return CSSValueVariant(CSSValueType::Number, CSSNumber{value}); + } + + static constexpr CSSValueVariant percentage(float value) requires( + canRepresent()) { + return CSSValueVariant(CSSValueType::Percentage, CSSPercentage{value}); + } + + constexpr CSSValueType type() const { + return type_; + } + + constexpr KeywordT getKeyword() const { + return getIf(); + } + + constexpr CSSLength getLength() const requires(canRepresent()) { + return getIf(); + } + + constexpr CSSNumber getNumber() const requires(canRepresent()) { + return getIf(); + } + + constexpr CSSPercentage getPercentage() const + requires(canRepresent()) { + return getIf(); + } + + constexpr operator bool() const { + return *this != CSSValueVariant{}; + } + + constexpr bool operator==(const CSSValueVariant& rhs) const = default; + + private: + constexpr CSSValueVariant(CSSValueType type, CSSBasicDataType auto&& value) + : type_(type) { + new (data_.data()) std::remove_cvref_t{ + std::forward(value)}; + } + + CSSValueType type_; + std::array()> data_; +}; +#pragma pack(pop) + +static_assert(sizeof(CSSValueVariant) == 2); +static_assert(sizeof(CSSValueVariant) == 6); +static_assert( + sizeof(CSSValueVariant) == 6); +static_assert(sizeof(CSSValueVariant) == 5); + +} // namespace facebook::react diff --git a/packages/react-native/ReactCommon/react/renderer/css/tests/CSSParserTest.cpp b/packages/react-native/ReactCommon/react/renderer/css/tests/CSSParserTest.cpp new file mode 100644 index 00000000000..ecde3849121 --- /dev/null +++ b/packages/react-native/ReactCommon/react/renderer/css/tests/CSSParserTest.cpp @@ -0,0 +1,125 @@ +/* + * 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. + */ + +#include +#include + +namespace facebook::react { + +TEST(CSSParser, keyword_values) { + auto emptyValue = parseCSSValue(""); + EXPECT_EQ(emptyValue.type(), CSSValueType::Keyword); + EXPECT_EQ(emptyValue.getKeyword(), CSSKeyword::Unset); + + auto autoValue = parseCSSValue("auto"); + EXPECT_EQ(autoValue.type(), CSSValueType::Keyword); + EXPECT_EQ(autoValue.getKeyword(), CSSKeyword::Auto); + + auto autoCapsValue = parseCSSValue("AuTO"); + EXPECT_EQ(autoCapsValue.type(), CSSValueType::Keyword); + EXPECT_EQ(autoCapsValue.getKeyword(), CSSKeyword::Auto); + + auto autoDisallowedValue = parseCSSValue("auto"); + EXPECT_EQ(autoDisallowedValue.type(), CSSValueType::Keyword); + EXPECT_EQ(autoDisallowedValue.getKeyword(), CSSKeyword::Unset); + + auto whitespaceValue = parseCSSValue(" flex-start "); + EXPECT_EQ(whitespaceValue.type(), CSSValueType::Keyword); + EXPECT_EQ(whitespaceValue.getKeyword(), CSSAlignItems::FlexStart); + + auto badIdentValue = parseCSSValue("bad"); + EXPECT_EQ(badIdentValue.type(), CSSValueType::Keyword); + EXPECT_EQ(badIdentValue.getKeyword(), CSSKeyword::Unset); + + auto pxValue = parseCSSValue("20px"); + EXPECT_EQ(pxValue.type(), CSSValueType::Keyword); + EXPECT_EQ(pxValue.getKeyword(), CSSKeyword::Unset); + + auto multiValue = parseCSSValue("auto flex-start"); + EXPECT_EQ(multiValue.type(), CSSValueType::Keyword); + EXPECT_EQ(multiValue.getKeyword(), CSSKeyword::Unset); +} + +TEST(CSSParser, length_values) { + auto emptyValue = parseCSSValue(""); + EXPECT_EQ(emptyValue.type(), CSSValueType::Keyword); + EXPECT_EQ(emptyValue.getKeyword(), CSSKeyword::Unset); + + auto autoValue = parseCSSValue("auto"); + EXPECT_EQ(autoValue.type(), CSSValueType::Keyword); + EXPECT_EQ(autoValue.getKeyword(), CSSKeyword::Auto); + + auto pxValue = parseCSSValue("20px"); + EXPECT_EQ(pxValue.type(), CSSValueType::Length); + EXPECT_EQ(pxValue.getLength().value, 20.0f); + EXPECT_EQ(pxValue.getLength().unit, CSSLengthUnit::Px); + + auto cmValue = parseCSSValue("453cm"); + EXPECT_EQ(cmValue.type(), CSSValueType::Length); + EXPECT_EQ(cmValue.getLength().value, 453.0f); + EXPECT_EQ(cmValue.getLength().unit, CSSLengthUnit::Cm); + + auto unitlessZeroValue = parseCSSValue("0"); + EXPECT_EQ(unitlessZeroValue.type(), CSSValueType::Length); + EXPECT_EQ(unitlessZeroValue.getLength().value, 0.0f); + EXPECT_EQ(unitlessZeroValue.getLength().unit, CSSLengthUnit::Px); + + auto unitlessNonzeroValue = parseCSSValue("123"); + EXPECT_EQ(unitlessNonzeroValue.type(), CSSValueType::Keyword); + EXPECT_EQ(unitlessNonzeroValue.getKeyword(), CSSKeyword::Unset); + + auto pctValue = parseCSSValue("-40%"); + EXPECT_EQ(pctValue.type(), CSSValueType::Keyword); + EXPECT_EQ(pctValue.getKeyword(), CSSKeyword::Unset); +} + +TEST(CSSParser, length_percentage_values) { + auto emptyValue = parseCSSValue(""); + EXPECT_EQ(emptyValue.type(), CSSValueType::Keyword); + EXPECT_EQ(emptyValue.getKeyword(), CSSKeyword::Unset); + + auto autoValue = + parseCSSValue("auto"); + EXPECT_EQ(autoValue.type(), CSSValueType::Keyword); + EXPECT_EQ(autoValue.getKeyword(), CSSKeyword::Auto); + + auto pxValue = + parseCSSValue("20px"); + EXPECT_EQ(pxValue.type(), CSSValueType::Length); + EXPECT_EQ(pxValue.getLength().value, 20.0f); + EXPECT_EQ(pxValue.getLength().unit, CSSLengthUnit::Px); + + auto pctValue = + parseCSSValue("-40%"); + EXPECT_EQ(pctValue.type(), CSSValueType::Percentage); + EXPECT_EQ(pctValue.getPercentage().value, -40.0f); +} + +TEST(CSSParser, number_values) { + auto emptyValue = parseCSSValue(""); + EXPECT_EQ(emptyValue.type(), CSSValueType::Keyword); + EXPECT_EQ(emptyValue.getKeyword(), CSSKeyword::Unset); + + auto inheritValue = parseCSSValue("inherit"); + EXPECT_EQ(inheritValue.type(), CSSValueType::Keyword); + EXPECT_EQ(inheritValue.getKeyword(), CSSKeyword::Inherit); + + auto pxValue = parseCSSValue("20px"); + EXPECT_EQ(pxValue.type(), CSSValueType::Keyword); + EXPECT_EQ(pxValue.getKeyword(), CSSKeyword::Unset); + + auto numberValue = parseCSSValue("123.456"); + EXPECT_EQ(numberValue.type(), CSSValueType::Number); + EXPECT_EQ(numberValue.getNumber().value, 123.456f); + + auto unitlessZeroValue = + parseCSSValue("0"); + EXPECT_EQ(unitlessZeroValue.type(), CSSValueType::Number); + EXPECT_EQ(unitlessZeroValue.getNumber().value, 0.0f); +} + +} // namespace facebook::react diff --git a/packages/react-native/ReactCommon/react/renderer/css/tests/CSSTokenizerTest.cpp b/packages/react-native/ReactCommon/react/renderer/css/tests/CSSTokenizerTest.cpp index cac2e8885b8..e40d289c677 100644 --- a/packages/react-native/ReactCommon/react/renderer/css/tests/CSSTokenizerTest.cpp +++ b/packages/react-native/ReactCommon/react/renderer/css/tests/CSSTokenizerTest.cpp @@ -104,12 +104,12 @@ TEST(CSSTokenizer, dimension_values) { TEST(CSSTokenizer, percent_values) { expectTokens( "12%", - {CSSToken{CSSTokenType::Percent, 12.0f}, + {CSSToken{CSSTokenType::Percentage, 12.0f}, CSSToken{CSSTokenType::EndOfFile}}); expectTokens( "-28.5%", - {CSSToken{CSSTokenType::Percent, -28.5f}, + {CSSToken{CSSTokenType::Percentage, -28.5f}, CSSToken{CSSTokenType::EndOfFile}}); } diff --git a/packages/react-native/ReactCommon/react/utils/PackTraits.h b/packages/react-native/ReactCommon/react/utils/PackTraits.h new file mode 100644 index 00000000000..0f14add9652 --- /dev/null +++ b/packages/react-native/ReactCommon/react/utils/PackTraits.h @@ -0,0 +1,40 @@ +/* + * 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 +#include +#include + +namespace facebook::react::traits { + +template +static constexpr size_t maxSizeof() { + if constexpr (sizeof...(RestT) > 0) { + return std::max(sizeof(T), maxSizeof()); + } else { + return sizeof(T); + } +} + +template +static constexpr bool containsType() { + return false; +} + +template +static constexpr bool containsType() { + if constexpr (sizeof...(RestT) > 0) { + return std::is_same_v || + containsType(); + } else { + return std::is_same_v; + } +} + +} // namespace facebook::react::traits diff --git a/packages/react-native/ReactCommon/react/utils/fnv1a.h b/packages/react-native/ReactCommon/react/utils/fnv1a.h index fe2721d4bc0..20c5b341e18 100644 --- a/packages/react-native/ReactCommon/react/utils/fnv1a.h +++ b/packages/react-native/ReactCommon/react/utils/fnv1a.h @@ -7,6 +7,10 @@ #pragma once +#include +#include +#include + namespace facebook::react { /** @@ -17,13 +21,14 @@ namespace facebook::react { * when std::hash does not provide the needed functionality. For example, * constexpr. */ +template constexpr uint32_t fnv1a(std::string_view string) noexcept { constexpr uint32_t offset_basis = 2166136261; uint32_t hash = offset_basis; for (auto const& c : string) { - hash ^= static_cast(c); + hash ^= static_cast(CharTransformT{}(c)); // Using shifts and adds instead of multiplication with a prime number. // This is faster when compiled with optimizations. hash += diff --git a/packages/react-native/ReactCommon/react/utils/to_underlying.h b/packages/react-native/ReactCommon/react/utils/to_underlying.h new file mode 100644 index 00000000000..6d8d19d2b34 --- /dev/null +++ b/packages/react-native/ReactCommon/react/utils/to_underlying.h @@ -0,0 +1,22 @@ +/* + * 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 + +namespace facebook::react { + +/** + * Polyfill of C++ 23 to_underlying() + * https://en.cppreference.com/w/cpp/utility/to_underlying + */ +constexpr auto to_underlying(auto e) noexcept { + return static_cast>(e); +} + +} // namespace facebook::react