/* * 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 { namespace detail { class CSSParser { public: explicit constexpr CSSParser(std::string_view css) : tokenizer_{css}, currentToken_(tokenizer_.next()) {} template constexpr CSSValueVariant consumeComponentValue() { using CSSValueT = CSSValueVariant; switch (peek().type()) { case CSSTokenType::Ident: if (auto keywordValue = consumeIdentToken()) { return *keywordValue; } break; case CSSTokenType::Dimension: if (auto dimensionValue = consumeDimensionToken()) { return *dimensionValue; } break; case CSSTokenType::Percentage: if (auto percentageValue = consumePercentageToken()) { return *percentageValue; } break; case CSSTokenType::Number: if (auto numberValue = consumeNumberToken()) { return *numberValue; } break; default: break; } consumeToken(); return {}; } constexpr void consumeWhitespace() { while (peek().type() == CSSTokenType::WhiteSpace) { consumeToken(); } } constexpr bool hasMoreTokens() const { return peek().type() != CSSTokenType::EndOfFile; } private: constexpr const CSSToken& peek() const { return currentToken_; } constexpr CSSToken consumeToken() { auto prevToken = currentToken_; currentToken_ = tokenizer_.next(); return prevToken; } template constexpr std::optional consumeIdentToken() { if constexpr (!std::is_same_v) { if (auto keyword = parseCSSKeyword( peek().stringValue())) { consumeToken(); return CSSValueT::keyword(*keyword); } } if constexpr (traits::containsType()) { if (auto keyword = parseCSSKeyword(peek().stringValue())) { consumeToken(); return CSSValueT::cssWideKeyword(*keyword); } } return {}; } template constexpr std::optional consumeDimensionToken() { if constexpr (traits::containsType()) { if (auto unit = parseCSSLengthUnit(peek().unit())) { return CSSValueT::length(consumeToken().numericValue(), *unit); } } return {}; } template constexpr std::optional consumePercentageToken() { if constexpr (traits::containsType()) { return CSSValueT::percentage(consumeToken().numericValue()); } return {}; } template constexpr std::optional consumeNumberToken() { // = [ / ]? // https://www.w3.org/TR/css-values-4/#ratio if constexpr (traits::containsType()) { if (isValidRatioPart(peek().numericValue())) { float numerator = consumeToken().numericValue(); float denominator = 1.0; consumeWhitespace(); if (peek().type() != CSSTokenType::Ident && peek().stringValue() == "/") { consumeToken(); consumeWhitespace(); if (peek().type() == CSSTokenType::Number && isValidRatioPart(peek().numericValue())) { denominator = consumeToken().numericValue(); } else { return CSSValueT{}; } } return CSSValueT::ratio(numerator, denominator); } } if constexpr (traits::containsType()) { return CSSValueT::number(consumeToken().numericValue()); } // 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()) { if (peek().numericValue() == 0) { return CSSValueT::length( consumeToken().numericValue(), CSSLengthUnit::Px); } } return {}; } static constexpr bool isValidRatioPart(float value) { // If either number in the is 0 or infinite, it represents a // degenerate ratio (and, generally, won’t do anything). // https://www.w3.org/TR/css-values-4/#ratios return value > 0.0f && value != +std::numeric_limits::infinity() && value != -std::numeric_limits::infinity(); } CSSTokenizer tokenizer_; CSSToken currentToken_; }; } // namespace detail /* * Parse a single CSS component value as a keyword constrained to those * allowable by KeywordRepresentationT. Returns a default-constructed * CSSValueVariant (KeywordT::Unset) on syntax error. * * https://www.w3.org/TR/css-syntax-3/#parse-component-value */ template constexpr void parseCSSComponentValue( std::string_view css, CSSValueVariant& value) { detail::CSSParser parser(css); parser.consumeWhitespace(); auto componentValue = parser.consumeComponentValue(); parser.consumeWhitespace(); if (parser.hasMoreTokens()) { value = {}; } else { value = componentValue; } }; template CSSValueVariant parseCSSComponentValue(std::string_view css) { CSSValueVariant value; parseCSSComponentValue(css, value); return value; }; template constexpr auto parseCSSProp(std::string_view css) { // For now we only allow parsing props composed of a single component value. CSSDeclaredValue value; parseCSSComponentValue(css, value); return value; } } // namespace facebook::react