/* * 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 { /** * 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{}; }; /** * 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 concept CSSFunctionVisitor = requires(T visitor, CSSFunctionBlock func) { { visitor(func) } -> std::convertible_to; }; /** * A CSSPreservedTokenVisitor is called after parsing a preserved token * component value. */ template concept CSSPreservedTokenVisitor = requires(T visitor, CSSPreservedToken token) { { visitor(token) } -> std::convertible_to; }; /** * 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 concept CSSSimpleBlockVisitor = requires(T visitor, CSSSimpleBlock block) { { visitor(block) } -> std::convertible_to; }; /** * Any visitor for a component value. */ template concept CSSComponentValueVisitor = CSSFunctionVisitor || CSSPreservedTokenVisitor || CSSSimpleBlockVisitor; /** * Represents a variadic set of CSSComponentValueVisitor with no more than one * of a specific type of visitor. */ template concept CSSUniqueComponentValueVisitors = (CSSComponentValueVisitor && ...) && ((CSSFunctionVisitor ? 1 : 0) + ... + 0) <= 1 && ((CSSPreservedTokenVisitor ? 1 : 0) + ... + 0) <= 1 && ((CSSSimpleBlockVisitor ? 1 : 0) + ... + 0) <= 1; /** * Describes the delimeter to expect before the next component value. */ enum class CSSComponentValueDelimiter { Comma, Whitespace, None, }; /** * CSSSyntaxParser allows parsing streams of CSS text into "component * values". * * https://www.w3.org/TR/css-syntax-3/#component-value */ class CSSSyntaxParser { template ... 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. * * https://www.w3.org/TR/css-syntax-3/#consume-component-value * * @param 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 constexpr ReturnT consumeComponentValue( CSSComponentValueDelimiter delimiter, const CSSComponentValueVisitor auto&... visitors) requires(CSSUniqueComponentValueVisitors); template constexpr ReturnT consumeComponentValue( const CSSComponentValueVisitor auto&... visitors) requires(CSSUniqueComponentValueVisitors); /** * 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(); } } private: constexpr const CSSToken& peek() const { return currentToken_; } constexpr CSSToken consumeToken() { auto prevToken = currentToken_; currentToken_ = tokenizer_.next(); return prevToken; } CSSTokenizer tokenizer_; CSSToken currentToken_; }; template ... VisitorsT> struct CSSComponentValueVisitorDispatcher { CSSSyntaxParser& parser; constexpr ReturnT consumeComponentValue( CSSComponentValueDelimiter delimiter, const VisitorsT&... visitors) { switch (delimiter) { case CSSComponentValueDelimiter::Comma: parser.consumeWhitespace(); if (parser.peek().type() != CSSTokenType::Comma) { return ReturnT{}; } parser.consumeToken(); parser.consumeWhitespace(); break; case CSSComponentValueDelimiter::Whitespace: parser.consumeWhitespace(); break; case CSSComponentValueDelimiter::None: break; } 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; } return ReturnT{}; } constexpr ReturnT consumeNextCommaDelimitedValue( const VisitorsT&... visitors) { parser.consumeWhitespace(); if (parser.consumeToken().type() != CSSTokenType::Comma) { return {}; } parser.consumeWhitespace(); return consumeComponentValue(std::forward(visitors)...); } constexpr ReturnT consumeNextWhitespaceDelimitedValue( const VisitorsT&... visitors) { parser.consumeWhitespace(); return consumeComponentValue(std::forward(visitors)...); } constexpr std::optional visitFunction(const VisitorsT&... visitors) { for (auto visitor : {visitors...}) { if constexpr (CSSFunctionVisitor) { auto functionValue = visitor({.name = parser.consumeToken().stringValue()}); parser.consumeWhitespace(); if (parser.peek().type() == CSSTokenType::CloseParen) { parser.consumeToken(); return functionValue; } return {}; } } return {}; } constexpr std::optional visitSimpleBlock( CSSTokenType endToken, const VisitorsT&... visitors) { for (auto visitor : {visitors...}) { if constexpr (CSSSimpleBlockVisitor) { auto blockValue = visitor({.openBracketType = parser.consumeToken().type()}); parser.consumeWhitespace(); if (parser.peek().type() == endToken) { parser.consumeToken(); return blockValue; } return {}; } } return {}; } constexpr std::optional visitPreservedToken( const VisitorsT&... visitors) { for (auto visitor : {visitors...}) { if constexpr (CSSPreservedTokenVisitor) { return visitor(parser.consumeToken()); } } return {}; } }; template constexpr ReturnT CSSSyntaxParser::consumeComponentValue( CSSComponentValueDelimiter delimiter, const CSSComponentValueVisitor auto&... visitors) requires(CSSUniqueComponentValueVisitors) { return CSSComponentValueVisitorDispatcher{ *this} .consumeComponentValue(delimiter, visitors...); } template constexpr ReturnT CSSSyntaxParser::consumeComponentValue( const CSSComponentValueVisitor auto&... visitors) requires(CSSUniqueComponentValueVisitors) { return consumeComponentValue( CSSComponentValueDelimiter::None, visitors...); } } // namespace facebook::react