2023-08-17 15:18:41 +01:00
/*
2024-10-04 13:19:50 +02:00
* Copyright ( c ) 2018 - 2022 , Andreas Kling < andreas @ ladybird . org >
2023-08-17 15:18:41 +01:00
* Copyright ( c ) 2020 - 2021 , the SerenityOS developers .
2024-10-15 12:00:29 +01:00
* Copyright ( c ) 2021 - 2024 , Sam Atkins < sam @ ladybird . org >
2023-08-17 15:18:41 +01:00
* Copyright ( c ) 2021 , Tobias Christiansen < tobyase @ serenityos . org >
* Copyright ( c ) 2022 , MacDue < macdue @ dueutil . tech >
*
* SPDX - License - Identifier : BSD - 2 - Clause
*/
# include <AK/Debug.h>
# include <LibWeb/CSS/Parser/Parser.h>
2023-12-10 22:06:55 +13:00
# include <LibWeb/Infra/Strings.h>
2023-08-17 15:18:41 +01:00
namespace Web : : CSS : : Parser {
Optional < SelectorList > Parser : : parse_as_selector ( SelectorParsingMode parsing_mode )
{
auto selector_list = parse_a_selector_list ( m_token_stream , SelectorType : : Standalone , parsing_mode ) ;
if ( ! selector_list . is_error ( ) )
return selector_list . release_value ( ) ;
return { } ;
}
Optional < SelectorList > Parser : : parse_as_relative_selector ( SelectorParsingMode parsing_mode )
{
auto selector_list = parse_a_selector_list ( m_token_stream , SelectorType : : Relative , parsing_mode ) ;
if ( ! selector_list . is_error ( ) )
return selector_list . release_value ( ) ;
return { } ;
}
2024-08-06 12:44:43 +01:00
Optional < Selector : : PseudoElement > Parser : : parse_as_pseudo_element_selector ( )
{
// FIXME: This is quite janky. Selector parsing is not at all designed to allow parsing just a single part of a selector.
// So, this code parses a whole selector, then rejects it if it's not a single pseudo-element simple selector.
// Come back and fix this, future Sam!
auto maybe_selector_list = parse_a_selector_list ( m_token_stream , SelectorType : : Standalone , SelectorParsingMode : : Standard ) ;
if ( maybe_selector_list . is_error ( ) )
return { } ;
auto & selector_list = maybe_selector_list . value ( ) ;
if ( selector_list . size ( ) ! = 1 )
return { } ;
auto & selector = selector_list . first ( ) ;
if ( selector - > compound_selectors ( ) . size ( ) ! = 1 )
return { } ;
auto & first_compound_selector = selector - > compound_selectors ( ) . first ( ) ;
if ( first_compound_selector . simple_selectors . size ( ) ! = 1 )
return { } ;
auto & simple_selector = first_compound_selector . simple_selectors . first ( ) ;
if ( simple_selector . type ! = Selector : : SimpleSelector : : Type : : PseudoElement )
return { } ;
return simple_selector . pseudo_element ( ) ;
}
2024-11-13 15:49:43 +00:00
static NonnullRefPtr < Selector > create_invalid_selector ( Vector < ComponentValue > component_values )
{
// Trim leading and trailing whitespace
while ( ! component_values . is_empty ( ) & & component_values . first ( ) . is ( Token : : Type : : Whitespace ) ) {
component_values . take_first ( ) ;
}
while ( ! component_values . is_empty ( ) & & component_values . last ( ) . is ( Token : : Type : : Whitespace ) ) {
component_values . take_last ( ) ;
}
Selector : : SimpleSelector simple {
. type = Selector : : SimpleSelector : : Type : : Invalid ,
. value = Selector : : SimpleSelector : : Invalid {
. component_values = move ( component_values ) ,
}
} ;
Selector : : CompoundSelector compound {
. combinator = Selector : : Combinator : : None ,
. simple_selectors = { move ( simple ) }
} ;
return Selector : : create ( { move ( compound ) } ) ;
}
2023-08-17 15:18:41 +01:00
template < typename T >
Parser : : ParseErrorOr < SelectorList > Parser : : parse_a_selector_list ( TokenStream < T > & tokens , SelectorType mode , SelectorParsingMode parsing_mode )
{
auto comma_separated_lists = parse_a_comma_separated_list_of_component_values ( tokens ) ;
2024-10-17 12:01:13 +01:00
SelectorList selectors ;
2023-08-17 15:18:41 +01:00
for ( auto & selector_parts : comma_separated_lists ) {
auto stream = TokenStream ( selector_parts ) ;
auto selector = parse_complex_selector ( stream , mode ) ;
if ( selector . is_error ( ) ) {
2024-11-13 15:49:43 +00:00
if ( parsing_mode = = SelectorParsingMode : : Forgiving ) {
// Keep the invalid selector around for serialization and nesting
selectors . append ( create_invalid_selector ( move ( selector_parts ) ) ) ;
2023-08-17 15:18:41 +01:00
continue ;
2024-11-13 15:49:43 +00:00
}
2023-08-17 15:18:41 +01:00
return selector . error ( ) ;
}
selectors . append ( selector . release_value ( ) ) ;
}
if ( selectors . is_empty ( ) & & parsing_mode ! = SelectorParsingMode : : Forgiving )
return ParseError : : SyntaxError ;
return selectors ;
}
template Parser : : ParseErrorOr < SelectorList > Parser : : parse_a_selector_list ( TokenStream < ComponentValue > & , SelectorType , SelectorParsingMode ) ;
template Parser : : ParseErrorOr < SelectorList > Parser : : parse_a_selector_list ( TokenStream < Token > & , SelectorType , SelectorParsingMode ) ;
Parser : : ParseErrorOr < NonnullRefPtr < Selector > > Parser : : parse_complex_selector ( TokenStream < ComponentValue > & tokens , SelectorType mode )
{
Vector < Selector : : CompoundSelector > compound_selectors ;
auto first_selector = TRY ( parse_compound_selector ( tokens ) ) ;
if ( ! first_selector . has_value ( ) )
return ParseError : : SyntaxError ;
if ( mode = = SelectorType : : Standalone ) {
if ( first_selector - > combinator ! = Selector : : Combinator : : Descendant )
return ParseError : : SyntaxError ;
first_selector - > combinator = Selector : : Combinator : : None ;
}
compound_selectors . append ( first_selector . release_value ( ) ) ;
while ( tokens . has_next_token ( ) ) {
auto compound_selector = TRY ( parse_compound_selector ( tokens ) ) ;
if ( ! compound_selector . has_value ( ) )
break ;
compound_selectors . append ( compound_selector . release_value ( ) ) ;
}
if ( compound_selectors . is_empty ( ) )
return ParseError : : SyntaxError ;
return Selector : : create ( move ( compound_selectors ) ) ;
}
Parser : : ParseErrorOr < Optional < Selector : : CompoundSelector > > Parser : : parse_compound_selector ( TokenStream < ComponentValue > & tokens )
{
2024-10-09 12:29:29 +01:00
tokens . discard_whitespace ( ) ;
2023-08-17 15:18:41 +01:00
auto combinator = parse_selector_combinator ( tokens ) . value_or ( Selector : : Combinator : : Descendant ) ;
2024-10-09 12:29:29 +01:00
tokens . discard_whitespace ( ) ;
2023-08-17 15:18:41 +01:00
Vector < Selector : : SimpleSelector > simple_selectors ;
while ( tokens . has_next_token ( ) ) {
auto component = TRY ( parse_simple_selector ( tokens ) ) ;
if ( ! component . has_value ( ) )
break ;
2024-11-08 20:14:46 +00:00
if ( component - > type = = Selector : : SimpleSelector : : Type : : TagName & & ! simple_selectors . is_empty ( ) ) {
// Tag-name selectors can only go at the beginning of a compound selector.
return ParseError : : SyntaxError ;
}
2023-08-17 15:18:41 +01:00
simple_selectors . append ( component . release_value ( ) ) ;
}
if ( simple_selectors . is_empty ( ) )
return Optional < Selector : : CompoundSelector > { } ;
return Selector : : CompoundSelector { combinator , move ( simple_selectors ) } ;
}
Optional < Selector : : Combinator > Parser : : parse_selector_combinator ( TokenStream < ComponentValue > & tokens )
{
2024-10-09 12:29:29 +01:00
auto const & current_value = tokens . consume_a_token ( ) ;
2023-08-17 15:18:41 +01:00
if ( current_value . is ( Token : : Type : : Delim ) ) {
switch ( current_value . token ( ) . delim ( ) ) {
case ' > ' :
return Selector : : Combinator : : ImmediateChild ;
case ' + ' :
return Selector : : Combinator : : NextSibling ;
case ' ~ ' :
return Selector : : Combinator : : SubsequentSibling ;
case ' | ' : {
2024-10-09 12:29:29 +01:00
auto const & next = tokens . next_token ( ) ;
2023-08-17 15:18:41 +01:00
if ( next . is ( Token : : Type : : EndOfFile ) )
return { } ;
if ( next . is_delim ( ' | ' ) ) {
2024-10-09 12:29:29 +01:00
tokens . discard_a_token ( ) ;
2023-08-17 15:18:41 +01:00
return Selector : : Combinator : : Column ;
}
}
}
}
tokens . reconsume_current_input_token ( ) ;
return { } ;
}
Optional < Selector : : SimpleSelector : : QualifiedName > Parser : : parse_selector_qualified_name ( TokenStream < ComponentValue > & tokens , AllowWildcardName allow_wildcard_name )
{
auto is_name = [ ] ( ComponentValue const & token ) {
return token . is_delim ( ' * ' ) | | token . is ( Token : : Type : : Ident ) ;
} ;
auto get_name = [ ] ( ComponentValue const & token ) {
if ( token . is_delim ( ' * ' ) )
2023-12-10 16:18:26 +13:00
return " * " _fly_string ;
return token . token ( ) . ident ( ) ;
2023-08-17 15:18:41 +01:00
} ;
// There are 3 possibilities here:
// (Where <name> and <namespace> are either an <ident> or a `*` delim)
// 1) `|<name>`
// 2) `<namespace>|<name>`
// 3) `<name>`
// Whitespace is forbidden between any of these parts. https://www.w3.org/TR/selectors-4/#white-space
auto transaction = tokens . begin_transaction ( ) ;
2024-10-31 21:43:21 +01:00
auto const & first_token = tokens . consume_a_token ( ) ;
2023-08-17 15:18:41 +01:00
if ( first_token . is_delim ( ' | ' ) ) {
// Case 1: `|<name>`
2024-10-09 12:29:29 +01:00
if ( is_name ( tokens . next_token ( ) ) ) {
2024-10-31 21:43:21 +01:00
auto const & name_token = tokens . consume_a_token ( ) ;
2023-08-17 15:18:41 +01:00
if ( allow_wildcard_name = = AllowWildcardName : : No & & name_token . is_delim ( ' * ' ) )
return { } ;
transaction . commit ( ) ;
return Selector : : SimpleSelector : : QualifiedName {
. namespace_type = Selector : : SimpleSelector : : QualifiedName : : NamespaceType : : None ,
2023-12-10 16:18:26 +13:00
. name = get_name ( name_token ) ,
2023-08-17 15:18:41 +01:00
} ;
}
return { } ;
}
if ( ! is_name ( first_token ) )
return { } ;
2024-10-09 12:29:29 +01:00
if ( tokens . next_token ( ) . is_delim ( ' | ' ) & & is_name ( tokens . peek_token ( 1 ) ) ) {
2023-08-17 15:18:41 +01:00
// Case 2: `<namespace>|<name>`
2024-10-09 12:29:29 +01:00
tokens . discard_a_token ( ) ; // `|`
2023-12-10 16:18:26 +13:00
auto namespace_ = get_name ( first_token ) ;
2024-10-09 12:29:29 +01:00
auto name = get_name ( tokens . consume_a_token ( ) ) ;
2023-08-17 15:18:41 +01:00
if ( allow_wildcard_name = = AllowWildcardName : : No & & name = = " * " sv )
return { } ;
auto namespace_type = namespace_ = = " * " sv
? Selector : : SimpleSelector : : QualifiedName : : NamespaceType : : Any
: Selector : : SimpleSelector : : QualifiedName : : NamespaceType : : Named ;
transaction . commit ( ) ;
return Selector : : SimpleSelector : : QualifiedName {
. namespace_type = namespace_type ,
. namespace_ = namespace_ ,
. name = name ,
} ;
}
// Case 3: `<name>`
auto & name_token = first_token ;
if ( allow_wildcard_name = = AllowWildcardName : : No & & name_token . is_delim ( ' * ' ) )
return { } ;
transaction . commit ( ) ;
return Selector : : SimpleSelector : : QualifiedName {
. namespace_type = Selector : : SimpleSelector : : QualifiedName : : NamespaceType : : Default ,
2023-12-10 16:18:26 +13:00
. name = get_name ( name_token ) ,
2023-08-17 15:18:41 +01:00
} ;
}
Parser : : ParseErrorOr < Selector : : SimpleSelector > Parser : : parse_attribute_simple_selector ( ComponentValue const & first_value )
{
2024-10-11 11:17:10 +01:00
auto attribute_tokens = TokenStream { first_value . block ( ) . value } ;
2023-08-17 15:18:41 +01:00
2024-10-09 12:29:29 +01:00
attribute_tokens . discard_whitespace ( ) ;
2023-08-17 15:18:41 +01:00
if ( ! attribute_tokens . has_next_token ( ) ) {
dbgln_if ( CSS_PARSER_DEBUG , " CSS attribute selector is empty! " ) ;
return ParseError : : SyntaxError ;
}
auto maybe_qualified_name = parse_selector_qualified_name ( attribute_tokens , AllowWildcardName : : No ) ;
if ( ! maybe_qualified_name . has_value ( ) ) {
2024-10-09 12:29:29 +01:00
dbgln_if ( CSS_PARSER_DEBUG , " Expected qualified-name for attribute name, got: '{}' " , attribute_tokens . next_token ( ) . to_debug_string ( ) ) ;
2023-08-17 15:18:41 +01:00
return ParseError : : SyntaxError ;
}
2024-06-22 13:33:48 +02:00
auto qualified_name = maybe_qualified_name . release_value ( ) ;
2023-08-17 15:18:41 +01:00
Selector : : SimpleSelector simple_selector {
. type = Selector : : SimpleSelector : : Type : : Attribute ,
. value = Selector : : SimpleSelector : : Attribute {
. match_type = Selector : : SimpleSelector : : Attribute : : MatchType : : HasAttribute ,
2024-06-22 13:33:48 +02:00
. qualified_name = qualified_name ,
2024-08-18 21:17:08 +01:00
. case_type = Selector : : SimpleSelector : : Attribute : : CaseType : : DefaultMatch ,
2023-08-17 15:18:41 +01:00
}
} ;
2024-10-09 12:29:29 +01:00
attribute_tokens . discard_whitespace ( ) ;
2023-08-17 15:18:41 +01:00
if ( ! attribute_tokens . has_next_token ( ) )
return simple_selector ;
2024-10-09 12:29:29 +01:00
auto const & delim_part = attribute_tokens . consume_a_token ( ) ;
2023-08-17 15:18:41 +01:00
if ( ! delim_part . is ( Token : : Type : : Delim ) ) {
dbgln_if ( CSS_PARSER_DEBUG , " Expected a delim for attribute comparison, got: '{}' " , delim_part . to_debug_string ( ) ) ;
return ParseError : : SyntaxError ;
}
if ( delim_part . token ( ) . delim ( ) = = ' = ' ) {
simple_selector . attribute ( ) . match_type = Selector : : SimpleSelector : : Attribute : : MatchType : : ExactValueMatch ;
} else {
if ( ! attribute_tokens . has_next_token ( ) ) {
dbgln_if ( CSS_PARSER_DEBUG , " Attribute selector ended part way through a match type. " ) ;
return ParseError : : SyntaxError ;
}
2024-10-09 12:29:29 +01:00
auto const & delim_second_part = attribute_tokens . consume_a_token ( ) ;
2023-08-17 15:18:41 +01:00
if ( ! delim_second_part . is_delim ( ' = ' ) ) {
dbgln_if ( CSS_PARSER_DEBUG , " Expected a double delim for attribute comparison, got: '{}{}' " , delim_part . to_debug_string ( ) , delim_second_part . to_debug_string ( ) ) ;
return ParseError : : SyntaxError ;
}
switch ( delim_part . token ( ) . delim ( ) ) {
case ' ~ ' :
simple_selector . attribute ( ) . match_type = Selector : : SimpleSelector : : Attribute : : MatchType : : ContainsWord ;
break ;
case ' * ' :
simple_selector . attribute ( ) . match_type = Selector : : SimpleSelector : : Attribute : : MatchType : : ContainsString ;
break ;
case ' | ' :
simple_selector . attribute ( ) . match_type = Selector : : SimpleSelector : : Attribute : : MatchType : : StartsWithSegment ;
break ;
case ' ^ ' :
simple_selector . attribute ( ) . match_type = Selector : : SimpleSelector : : Attribute : : MatchType : : StartsWithString ;
break ;
case ' $ ' :
simple_selector . attribute ( ) . match_type = Selector : : SimpleSelector : : Attribute : : MatchType : : EndsWithString ;
break ;
default :
attribute_tokens . reconsume_current_input_token ( ) ;
}
}
2024-10-09 12:29:29 +01:00
attribute_tokens . discard_whitespace ( ) ;
2023-08-17 15:18:41 +01:00
if ( ! attribute_tokens . has_next_token ( ) ) {
dbgln_if ( CSS_PARSER_DEBUG , " Attribute selector ended without a value to match. " ) ;
return ParseError : : SyntaxError ;
}
2024-10-09 12:29:29 +01:00
auto const & value_part = attribute_tokens . consume_a_token ( ) ;
2023-08-17 15:18:41 +01:00
if ( ! value_part . is ( Token : : Type : : Ident ) & & ! value_part . is ( Token : : Type : : String ) ) {
dbgln_if ( CSS_PARSER_DEBUG , " Expected a string or ident for the value to match attribute against, got: '{}' " , value_part . to_debug_string ( ) ) ;
return ParseError : : SyntaxError ;
}
2023-12-09 09:24:41 +13:00
auto const & value_string = value_part . token ( ) . is ( Token : : Type : : Ident ) ? value_part . token ( ) . ident ( ) : value_part . token ( ) . string ( ) ;
simple_selector . attribute ( ) . value = value_string . to_string ( ) ;
2023-08-17 15:18:41 +01:00
2024-10-09 12:29:29 +01:00
attribute_tokens . discard_whitespace ( ) ;
2023-08-17 15:18:41 +01:00
// Handle case-sensitivity suffixes. https://www.w3.org/TR/selectors-4/#attribute-case
if ( attribute_tokens . has_next_token ( ) ) {
2024-10-09 12:29:29 +01:00
auto const & case_sensitivity_part = attribute_tokens . consume_a_token ( ) ;
2023-08-17 15:18:41 +01:00
if ( case_sensitivity_part . is ( Token : : Type : : Ident ) ) {
auto case_sensitivity = case_sensitivity_part . token ( ) . ident ( ) ;
if ( case_sensitivity . equals_ignoring_ascii_case ( " i " sv ) ) {
simple_selector . attribute ( ) . case_type = Selector : : SimpleSelector : : Attribute : : CaseType : : CaseInsensitiveMatch ;
} else if ( case_sensitivity . equals_ignoring_ascii_case ( " s " sv ) ) {
simple_selector . attribute ( ) . case_type = Selector : : SimpleSelector : : Attribute : : CaseType : : CaseSensitiveMatch ;
} else {
dbgln_if ( CSS_PARSER_DEBUG , " Expected a \" i \" or \" s \" attribute selector case sensitivity identifier, got: '{}' " , case_sensitivity_part . to_debug_string ( ) ) ;
return ParseError : : SyntaxError ;
}
} else {
dbgln_if ( CSS_PARSER_DEBUG , " Expected an attribute selector case sensitivity identifier, got: '{}' " , case_sensitivity_part . to_debug_string ( ) ) ;
return ParseError : : SyntaxError ;
}
}
if ( attribute_tokens . has_next_token ( ) ) {
dbgln_if ( CSS_PARSER_DEBUG , " Was not expecting anything else inside attribute selector. " ) ;
return ParseError : : SyntaxError ;
}
return simple_selector ;
}
Parser : : ParseErrorOr < Selector : : SimpleSelector > Parser : : parse_pseudo_simple_selector ( TokenStream < ComponentValue > & tokens )
{
auto peek_token_ends_selector = [ & ] ( ) - > bool {
2024-10-09 12:29:29 +01:00
auto const & value = tokens . next_token ( ) ;
2023-08-17 15:18:41 +01:00
return ( value . is ( Token : : Type : : EndOfFile ) | | value . is ( Token : : Type : : Whitespace ) | | value . is ( Token : : Type : : Comma ) ) ;
} ;
if ( peek_token_ends_selector ( ) )
return ParseError : : SyntaxError ;
bool is_pseudo = false ;
2024-10-09 12:29:29 +01:00
if ( tokens . next_token ( ) . is ( Token : : Type : : Colon ) ) {
2023-08-17 15:18:41 +01:00
is_pseudo = true ;
2024-10-09 12:29:29 +01:00
tokens . discard_a_token ( ) ;
2023-08-17 15:18:41 +01:00
if ( peek_token_ends_selector ( ) )
return ParseError : : SyntaxError ;
}
if ( is_pseudo ) {
2024-10-09 12:29:29 +01:00
auto const & name_token = tokens . consume_a_token ( ) ;
2023-08-17 15:18:41 +01:00
if ( ! name_token . is ( Token : : Type : : Ident ) ) {
dbgln_if ( CSS_PARSER_DEBUG , " Expected an ident for pseudo-element, got: '{}' " , name_token . to_debug_string ( ) ) ;
return ParseError : : SyntaxError ;
}
auto pseudo_name = name_token . token ( ) . ident ( ) ;
// Note: We allow the "ignored" -webkit prefix here for -webkit-progress-bar/-webkit-progress-bar
2023-12-10 21:00:03 +13:00
if ( auto pseudo_element = Selector : : PseudoElement : : from_string ( pseudo_name ) ; pseudo_element . has_value ( ) ) {
2023-12-10 17:07:32 +13:00
return Selector : : SimpleSelector {
. type = Selector : : SimpleSelector : : Type : : PseudoElement ,
. value = pseudo_element . release_value ( )
} ;
2023-08-17 15:18:41 +01:00
}
2023-12-10 22:06:55 +13:00
// https://www.w3.org/TR/selectors-4/#compat
// All other pseudo-elements whose names begin with the string “-webkit-” (matched ASCII case-insensitively)
// and that are not functional notations must be treated as valid at parse time. (That is, ::-webkit-asdf is
// valid at parse time, but ::-webkit-jkl() is not.) If they’ re not otherwise recognized and supported, they
// must be treated as matching nothing, and are unknown -webkit- pseudo-elements.
if ( pseudo_name . starts_with_bytes ( " -webkit- " sv , CaseSensitivity : : CaseInsensitive ) ) {
return Selector : : SimpleSelector {
. type = Selector : : SimpleSelector : : Type : : PseudoElement ,
// Unknown -webkit- pseudo-elements must be serialized in ASCII lowercase.
2024-10-14 10:51:15 +02:00
. value = Selector : : PseudoElement { Selector : : PseudoElement : : Type : : UnknownWebKit , pseudo_name . to_string ( ) . to_ascii_lowercase ( ) } ,
2023-12-10 22:06:55 +13:00
} ;
}
2023-12-10 17:07:32 +13:00
if ( has_ignored_vendor_prefix ( pseudo_name ) )
return ParseError : : IncludesIgnoredVendorPrefix ;
dbgln_if ( CSS_PARSER_DEBUG , " Unrecognized pseudo-element: '::{}' " , pseudo_name ) ;
return ParseError : : SyntaxError ;
2023-08-17 15:18:41 +01:00
}
if ( peek_token_ends_selector ( ) )
return ParseError : : SyntaxError ;
2024-10-09 12:29:29 +01:00
auto const & pseudo_class_token = tokens . consume_a_token ( ) ;
2023-08-17 15:18:41 +01:00
if ( pseudo_class_token . is ( Token : : Type : : Ident ) ) {
auto pseudo_name = pseudo_class_token . token ( ) . ident ( ) ;
if ( has_ignored_vendor_prefix ( pseudo_name ) )
return ParseError : : IncludesIgnoredVendorPrefix ;
auto make_pseudo_class_selector = [ ] ( auto pseudo_class ) {
return Selector : : SimpleSelector {
. type = Selector : : SimpleSelector : : Type : : PseudoClass ,
. value = Selector : : SimpleSelector : : PseudoClassSelector { . type = pseudo_class }
} ;
} ;
if ( auto pseudo_class = pseudo_class_from_string ( pseudo_name ) ; pseudo_class . has_value ( ) ) {
if ( ! pseudo_class_metadata ( pseudo_class . value ( ) ) . is_valid_as_identifier ) {
dbgln_if ( CSS_PARSER_DEBUG , " Pseudo-class ':{}' is only valid as a function " , pseudo_name ) ;
return ParseError : : SyntaxError ;
}
return make_pseudo_class_selector ( pseudo_class . value ( ) ) ;
}
// Single-colon syntax allowed for ::after, ::before, ::first-letter and ::first-line for compatibility.
// https://www.w3.org/TR/selectors/#pseudo-element-syntax
2023-12-10 21:00:03 +13:00
if ( auto pseudo_element = Selector : : PseudoElement : : from_string ( pseudo_name ) ; pseudo_element . has_value ( ) ) {
switch ( pseudo_element . value ( ) . type ( ) ) {
case Selector : : PseudoElement : : Type : : After :
case Selector : : PseudoElement : : Type : : Before :
case Selector : : PseudoElement : : Type : : FirstLetter :
case Selector : : PseudoElement : : Type : : FirstLine :
2023-08-17 15:18:41 +01:00
return Selector : : SimpleSelector {
. type = Selector : : SimpleSelector : : Type : : PseudoElement ,
. value = pseudo_element . value ( )
} ;
default :
break ;
}
}
dbgln_if ( CSS_PARSER_DEBUG , " Unrecognized pseudo-class: ':{}' " , pseudo_name ) ;
return ParseError : : SyntaxError ;
}
if ( pseudo_class_token . is_function ( ) ) {
auto parse_nth_child_selector = [ this ] ( auto pseudo_class , Vector < ComponentValue > const & function_values , bool allow_of = false ) - > ParseErrorOr < Selector : : SimpleSelector > {
auto tokens = TokenStream < ComponentValue > ( function_values ) ;
auto nth_child_pattern = parse_a_n_plus_b_pattern ( tokens ) ;
if ( ! nth_child_pattern . has_value ( ) ) {
dbgln_if ( CSS_PARSER_DEBUG , " !!! Invalid An+B format for {} " , pseudo_class_name ( pseudo_class ) ) ;
return ParseError : : SyntaxError ;
}
2024-10-09 12:29:29 +01:00
tokens . discard_whitespace ( ) ;
2023-08-17 15:18:41 +01:00
if ( ! tokens . has_next_token ( ) ) {
return Selector : : SimpleSelector {
. type = Selector : : SimpleSelector : : Type : : PseudoClass ,
. value = Selector : : SimpleSelector : : PseudoClassSelector {
. type = pseudo_class ,
. nth_child_pattern = nth_child_pattern . release_value ( ) }
} ;
}
if ( ! allow_of )
return ParseError : : SyntaxError ;
// Parse the `of <selector-list>` syntax
2024-10-09 12:29:29 +01:00
auto const & maybe_of = tokens . consume_a_token ( ) ;
2023-11-21 12:08:39 +00:00
if ( ! maybe_of . is_ident ( " of " sv ) )
2023-08-17 15:18:41 +01:00
return ParseError : : SyntaxError ;
2024-10-09 12:29:29 +01:00
tokens . discard_whitespace ( ) ;
2023-08-17 15:18:41 +01:00
auto selector_list = TRY ( parse_a_selector_list ( tokens , SelectorType : : Standalone ) ) ;
2024-10-09 12:29:29 +01:00
tokens . discard_whitespace ( ) ;
2023-08-17 15:18:41 +01:00
if ( tokens . has_next_token ( ) )
return ParseError : : SyntaxError ;
return Selector : : SimpleSelector {
. type = Selector : : SimpleSelector : : Type : : PseudoClass ,
. value = Selector : : SimpleSelector : : PseudoClassSelector {
. type = pseudo_class ,
. nth_child_pattern = nth_child_pattern . release_value ( ) ,
. argument_selector_list = move ( selector_list ) }
} ;
} ;
auto const & pseudo_function = pseudo_class_token . function ( ) ;
2024-10-11 11:17:10 +01:00
auto maybe_pseudo_class = pseudo_class_from_string ( pseudo_function . name ) ;
2023-08-17 15:18:41 +01:00
if ( ! maybe_pseudo_class . has_value ( ) ) {
2024-10-11 11:17:10 +01:00
dbgln_if ( CSS_PARSER_DEBUG , " Unrecognized pseudo-class function: ':{}'() " , pseudo_function . name ) ;
2023-08-17 15:18:41 +01:00
return ParseError : : SyntaxError ;
}
auto pseudo_class = maybe_pseudo_class . value ( ) ;
auto metadata = pseudo_class_metadata ( pseudo_class ) ;
if ( ! metadata . is_valid_as_function ) {
2024-10-11 11:17:10 +01:00
dbgln_if ( CSS_PARSER_DEBUG , " Pseudo-class ':{}' is not valid as a function " , pseudo_function . name ) ;
2023-08-17 15:18:41 +01:00
return ParseError : : SyntaxError ;
}
2024-10-11 11:17:10 +01:00
if ( pseudo_function . value . is_empty ( ) ) {
dbgln_if ( CSS_PARSER_DEBUG , " Empty :{}() selector " , pseudo_function . name ) ;
2023-08-17 15:18:41 +01:00
return ParseError : : SyntaxError ;
}
switch ( metadata . parameter_type ) {
case PseudoClassMetadata : : ParameterType : : ANPlusB :
2024-10-11 11:17:10 +01:00
return parse_nth_child_selector ( pseudo_class , pseudo_function . value , false ) ;
2023-08-17 15:18:41 +01:00
case PseudoClassMetadata : : ParameterType : : ANPlusBOf :
2024-10-11 11:17:10 +01:00
return parse_nth_child_selector ( pseudo_class , pseudo_function . value , true ) ;
2023-08-17 15:18:41 +01:00
case PseudoClassMetadata : : ParameterType : : CompoundSelector : {
2024-10-11 11:17:10 +01:00
auto function_token_stream = TokenStream ( pseudo_function . value ) ;
2023-08-17 15:18:41 +01:00
auto compound_selector_or_error = parse_compound_selector ( function_token_stream ) ;
if ( compound_selector_or_error . is_error ( ) | | ! compound_selector_or_error . value ( ) . has_value ( ) ) {
2024-10-11 11:17:10 +01:00
dbgln_if ( CSS_PARSER_DEBUG , " Failed to parse :{}() parameter as a compound selector " , pseudo_function . name ) ;
2023-08-17 15:18:41 +01:00
return ParseError : : SyntaxError ;
}
2024-07-23 15:03:27 +02:00
auto compound_selector = compound_selector_or_error . release_value ( ) . release_value ( ) ;
compound_selector . combinator = Selector : : Combinator : : None ;
Vector compound_selectors { move ( compound_selector ) } ;
2023-08-17 15:18:41 +01:00
auto selector = Selector : : create ( move ( compound_selectors ) ) ;
return Selector : : SimpleSelector {
. type = Selector : : SimpleSelector : : Type : : PseudoClass ,
. value = Selector : : SimpleSelector : : PseudoClassSelector {
. type = pseudo_class ,
. argument_selector_list = { move ( selector ) } }
} ;
}
2024-07-13 09:19:30 -07:00
case PseudoClassMetadata : : ParameterType : : ForgivingRelativeSelectorList :
2023-08-17 15:18:41 +01:00
case PseudoClassMetadata : : ParameterType : : ForgivingSelectorList : {
2024-10-11 11:17:10 +01:00
auto function_token_stream = TokenStream ( pseudo_function . value ) ;
2024-07-13 09:19:30 -07:00
auto selector_type = metadata . parameter_type = = PseudoClassMetadata : : ParameterType : : ForgivingSelectorList
? SelectorType : : Standalone
: SelectorType : : Relative ;
2023-08-17 15:18:41 +01:00
// NOTE: Because it's forgiving, even complete garbage will parse OK as an empty selector-list.
2024-07-13 09:19:30 -07:00
auto argument_selector_list = MUST ( parse_a_selector_list ( function_token_stream , selector_type , SelectorParsingMode : : Forgiving ) ) ;
2023-08-17 15:18:41 +01:00
return Selector : : SimpleSelector {
. type = Selector : : SimpleSelector : : Type : : PseudoClass ,
. value = Selector : : SimpleSelector : : PseudoClassSelector {
. type = pseudo_class ,
. argument_selector_list = move ( argument_selector_list ) }
} ;
}
2023-08-12 18:11:50 +01:00
case PseudoClassMetadata : : ParameterType : : Ident : {
2024-10-11 11:17:10 +01:00
auto function_token_stream = TokenStream ( pseudo_function . value ) ;
2024-10-09 12:29:29 +01:00
function_token_stream . discard_whitespace ( ) ;
2024-10-31 21:43:21 +01:00
auto const & maybe_keyword_token = function_token_stream . consume_a_token ( ) ;
2024-10-09 12:29:29 +01:00
function_token_stream . discard_whitespace ( ) ;
2024-08-14 14:06:03 +01:00
if ( ! maybe_keyword_token . is ( Token : : Type : : Ident ) | | function_token_stream . has_next_token ( ) ) {
2024-10-11 11:17:10 +01:00
dbgln_if ( CSS_PARSER_DEBUG , " Failed to parse :{}() parameter as a keyword: not an ident " , pseudo_function . name ) ;
2023-08-12 18:11:50 +01:00
return ParseError : : SyntaxError ;
}
2024-08-14 14:06:03 +01:00
auto maybe_keyword = keyword_from_string ( maybe_keyword_token . token ( ) . ident ( ) ) ;
if ( ! maybe_keyword . has_value ( ) ) {
2024-10-11 11:17:10 +01:00
dbgln_if ( CSS_PARSER_DEBUG , " Failed to parse :{}() parameter as a keyword: unrecognized keyword " , pseudo_function . name ) ;
2023-08-12 18:11:50 +01:00
return ParseError : : SyntaxError ;
}
return Selector : : SimpleSelector {
. type = Selector : : SimpleSelector : : Type : : PseudoClass ,
. value = Selector : : SimpleSelector : : PseudoClassSelector {
. type = pseudo_class ,
2024-08-14 14:06:03 +01:00
. keyword = maybe_keyword . value ( ) }
2023-08-12 18:11:50 +01:00
} ;
}
2023-08-17 15:18:41 +01:00
case PseudoClassMetadata : : ParameterType : : LanguageRanges : {
Vector < FlyString > languages ;
2024-10-11 11:17:10 +01:00
auto function_token_stream = TokenStream ( pseudo_function . value ) ;
2023-08-17 15:18:41 +01:00
auto language_token_lists = parse_a_comma_separated_list_of_component_values ( function_token_stream ) ;
2024-10-31 21:43:21 +01:00
for ( auto const & language_token_list : language_token_lists ) {
2023-08-17 15:18:41 +01:00
auto language_token_stream = TokenStream ( language_token_list ) ;
2024-10-09 12:29:29 +01:00
language_token_stream . discard_whitespace ( ) ;
2024-10-31 21:43:21 +01:00
auto const & language_token = language_token_stream . consume_a_token ( ) ;
2023-08-17 15:18:41 +01:00
if ( ! ( language_token . is ( Token : : Type : : Ident ) | | language_token . is ( Token : : Type : : String ) ) ) {
2024-10-11 11:17:10 +01:00
dbgln_if ( CSS_PARSER_DEBUG , " Invalid language range in :{}() - not a string/ident " , pseudo_function . name ) ;
2023-08-17 15:18:41 +01:00
return ParseError : : SyntaxError ;
}
auto language_string = language_token . is ( Token : : Type : : String ) ? language_token . token ( ) . string ( ) : language_token . token ( ) . ident ( ) ;
2023-12-10 16:18:26 +13:00
languages . append ( language_string ) ;
2023-08-17 15:18:41 +01:00
2024-10-09 12:29:29 +01:00
language_token_stream . discard_whitespace ( ) ;
2023-08-17 15:18:41 +01:00
if ( language_token_stream . has_next_token ( ) ) {
2024-10-11 11:17:10 +01:00
dbgln_if ( CSS_PARSER_DEBUG , " Invalid language range in :{}() - trailing tokens " , pseudo_function . name ) ;
2023-08-17 15:18:41 +01:00
return ParseError : : SyntaxError ;
}
}
return Selector : : SimpleSelector {
. type = Selector : : SimpleSelector : : Type : : PseudoClass ,
. value = Selector : : SimpleSelector : : PseudoClassSelector {
. type = pseudo_class ,
. languages = move ( languages ) }
} ;
}
case PseudoClassMetadata : : ParameterType : : SelectorList : {
2024-10-11 11:17:10 +01:00
auto function_token_stream = TokenStream ( pseudo_function . value ) ;
2023-08-17 15:18:41 +01:00
auto not_selector = TRY ( parse_a_selector_list ( function_token_stream , SelectorType : : Standalone ) ) ;
return Selector : : SimpleSelector {
. type = Selector : : SimpleSelector : : Type : : PseudoClass ,
. value = Selector : : SimpleSelector : : PseudoClassSelector {
. type = pseudo_class ,
. argument_selector_list = move ( not_selector ) }
} ;
}
case PseudoClassMetadata : : ParameterType : : None :
// `None` means this is not a function-type pseudo-class, so this state should be impossible.
VERIFY_NOT_REACHED ( ) ;
}
}
dbgln_if ( CSS_PARSER_DEBUG , " Unexpected Block in pseudo-class name, expected a function or identifier. '{}' " , pseudo_class_token . to_debug_string ( ) ) ;
return ParseError : : SyntaxError ;
}
Parser : : ParseErrorOr < Optional < Selector : : SimpleSelector > > Parser : : parse_simple_selector ( TokenStream < ComponentValue > & tokens )
{
auto peek_token_ends_selector = [ & ] ( ) - > bool {
2024-10-09 12:29:29 +01:00
auto const & value = tokens . next_token ( ) ;
2023-08-17 15:18:41 +01:00
return ( value . is ( Token : : Type : : EndOfFile ) | | value . is ( Token : : Type : : Whitespace ) | | value . is ( Token : : Type : : Comma ) ) ;
} ;
if ( peek_token_ends_selector ( ) )
return Optional < Selector : : SimpleSelector > { } ;
// Handle universal and tag-name types together, since both can be namespaced
if ( auto qualified_name = parse_selector_qualified_name ( tokens , AllowWildcardName : : Yes ) ; qualified_name . has_value ( ) ) {
if ( qualified_name - > name . name = = " * " sv ) {
return Selector : : SimpleSelector {
. type = Selector : : SimpleSelector : : Type : : Universal ,
. value = qualified_name . release_value ( ) ,
} ;
}
return Selector : : SimpleSelector {
. type = Selector : : SimpleSelector : : Type : : TagName ,
. value = qualified_name . release_value ( ) ,
} ;
}
2024-10-09 12:29:29 +01:00
auto const & first_value = tokens . consume_a_token ( ) ;
2023-08-17 15:18:41 +01:00
if ( first_value . is ( Token : : Type : : Delim ) ) {
u32 delim = first_value . token ( ) . delim ( ) ;
switch ( delim ) {
case ' * ' :
// Handled already
VERIFY_NOT_REACHED ( ) ;
2024-10-15 12:00:29 +01:00
case ' & ' :
return Selector : : SimpleSelector {
. type = Selector : : SimpleSelector : : Type : : Nesting ,
} ;
2023-08-17 15:18:41 +01:00
case ' . ' : {
if ( peek_token_ends_selector ( ) )
return ParseError : : SyntaxError ;
2024-10-09 12:29:29 +01:00
auto const & class_name_value = tokens . consume_a_token ( ) ;
2023-08-17 15:18:41 +01:00
if ( ! class_name_value . is ( Token : : Type : : Ident ) ) {
dbgln_if ( CSS_PARSER_DEBUG , " Expected an ident after '.', got: {} " , class_name_value . to_debug_string ( ) ) ;
return ParseError : : SyntaxError ;
}
return Selector : : SimpleSelector {
. type = Selector : : SimpleSelector : : Type : : Class ,
2023-12-10 16:18:26 +13:00
. value = Selector : : SimpleSelector : : Name { class_name_value . token ( ) . ident ( ) }
2023-08-17 15:18:41 +01:00
} ;
}
case ' > ' :
case ' + ' :
case ' ~ ' :
case ' | ' :
// Whitespace is not required between the compound-selector and a combinator.
// So, if we see a combinator, return that this compound-selector is done, instead of a syntax error.
tokens . reconsume_current_input_token ( ) ;
return Optional < Selector : : SimpleSelector > { } ;
default :
dbgln_if ( CSS_PARSER_DEBUG , " !!! Invalid simple selector! " ) ;
return ParseError : : SyntaxError ;
}
}
if ( first_value . is ( Token : : Type : : Hash ) ) {
if ( first_value . token ( ) . hash_type ( ) ! = Token : : HashType : : Id ) {
dbgln_if ( CSS_PARSER_DEBUG , " Selector contains hash token that is not an id: {} " , first_value . to_debug_string ( ) ) ;
return ParseError : : SyntaxError ;
}
return Selector : : SimpleSelector {
. type = Selector : : SimpleSelector : : Type : : Id ,
2023-12-10 16:18:26 +13:00
. value = Selector : : SimpleSelector : : Name { first_value . token ( ) . hash_value ( ) }
2023-08-17 15:18:41 +01:00
} ;
}
if ( first_value . is_block ( ) & & first_value . block ( ) . is_square ( ) )
return TRY ( parse_attribute_simple_selector ( first_value ) ) ;
if ( first_value . is ( Token : : Type : : Colon ) )
return TRY ( parse_pseudo_simple_selector ( tokens ) ) ;
dbgln_if ( CSS_PARSER_DEBUG , " !!! Invalid simple selector! " ) ;
return ParseError : : SyntaxError ;
}
Optional < Selector : : SimpleSelector : : ANPlusBPattern > Parser : : parse_a_n_plus_b_pattern ( TokenStream < ComponentValue > & values )
{
auto transaction = values . begin_transaction ( ) ;
auto syntax_error = [ & ] ( ) - > Optional < Selector : : SimpleSelector : : ANPlusBPattern > {
if constexpr ( CSS_PARSER_DEBUG ) {
dbgln_if ( CSS_PARSER_DEBUG , " Invalid An+B value: " ) ;
values . dump_all_tokens ( ) ;
}
return { } ;
} ;
auto is_sign = [ ] ( ComponentValue const & value ) - > bool {
return value . is ( Token : : Type : : Delim ) & & ( value . token ( ) . delim ( ) = = ' + ' | | value . token ( ) . delim ( ) = = ' - ' ) ;
} ;
auto is_n_dimension = [ ] ( ComponentValue const & value ) - > bool {
if ( ! value . is ( Token : : Type : : Dimension ) )
return false ;
if ( ! value . token ( ) . number ( ) . is_integer ( ) )
return false ;
if ( ! value . token ( ) . dimension_unit ( ) . equals_ignoring_ascii_case ( " n " sv ) )
return false ;
return true ;
} ;
auto is_ndash_dimension = [ ] ( ComponentValue const & value ) - > bool {
if ( ! value . is ( Token : : Type : : Dimension ) )
return false ;
if ( ! value . token ( ) . number ( ) . is_integer ( ) )
return false ;
if ( ! value . token ( ) . dimension_unit ( ) . equals_ignoring_ascii_case ( " n- " sv ) )
return false ;
return true ;
} ;
auto is_ndashdigit_dimension = [ ] ( ComponentValue const & value ) - > bool {
if ( ! value . is ( Token : : Type : : Dimension ) )
return false ;
if ( ! value . token ( ) . number ( ) . is_integer ( ) )
return false ;
auto dimension_unit = value . token ( ) . dimension_unit ( ) ;
2023-11-26 10:40:08 +13:00
if ( ! dimension_unit . starts_with_bytes ( " n- " sv , CaseSensitivity : : CaseInsensitive ) )
2023-08-17 15:18:41 +01:00
return false ;
2023-11-26 10:40:08 +13:00
for ( size_t i = 2 ; i < dimension_unit . bytes_as_string_view ( ) . length ( ) ; + + i ) {
if ( ! is_ascii_digit ( dimension_unit . bytes_as_string_view ( ) [ i ] ) )
2023-08-17 15:18:41 +01:00
return false ;
}
return true ;
} ;
auto is_ndashdigit_ident = [ ] ( ComponentValue const & value ) - > bool {
if ( ! value . is ( Token : : Type : : Ident ) )
return false ;
auto ident = value . token ( ) . ident ( ) ;
2023-11-07 00:22:33 +13:00
if ( ! ident . starts_with_bytes ( " n- " sv , CaseSensitivity : : CaseInsensitive ) )
2023-08-17 15:18:41 +01:00
return false ;
2023-11-07 00:22:33 +13:00
for ( size_t i = 2 ; i < ident . bytes_as_string_view ( ) . length ( ) ; + + i ) {
if ( ! is_ascii_digit ( ident . bytes_as_string_view ( ) [ i ] ) )
2023-08-17 15:18:41 +01:00
return false ;
}
return true ;
} ;
auto is_dashndashdigit_ident = [ ] ( ComponentValue const & value ) - > bool {
if ( ! value . is ( Token : : Type : : Ident ) )
return false ;
auto ident = value . token ( ) . ident ( ) ;
2023-11-07 00:22:33 +13:00
if ( ! ident . starts_with_bytes ( " -n- " sv , CaseSensitivity : : CaseInsensitive ) )
2023-08-17 15:18:41 +01:00
return false ;
2023-11-07 00:22:33 +13:00
if ( ident . bytes_as_string_view ( ) . length ( ) = = 3 )
2023-08-17 15:18:41 +01:00
return false ;
2023-11-07 00:22:33 +13:00
for ( size_t i = 3 ; i < ident . bytes_as_string_view ( ) . length ( ) ; + + i ) {
if ( ! is_ascii_digit ( ident . bytes_as_string_view ( ) [ i ] ) )
2023-08-17 15:18:41 +01:00
return false ;
}
return true ;
} ;
auto is_integer = [ ] ( ComponentValue const & value ) - > bool {
return value . is ( Token : : Type : : Number ) & & value . token ( ) . number ( ) . is_integer ( ) ;
} ;
auto is_signed_integer = [ ] ( ComponentValue const & value ) - > bool {
return value . is ( Token : : Type : : Number ) & & value . token ( ) . number ( ) . is_integer_with_explicit_sign ( ) ;
} ;
auto is_signless_integer = [ ] ( ComponentValue const & value ) - > bool {
return value . is ( Token : : Type : : Number ) & & ! value . token ( ) . number ( ) . is_integer_with_explicit_sign ( ) ;
} ;
// https://www.w3.org/TR/css-syntax-3/#the-anb-type
// Unfortunately these can't be in the same order as in the spec.
2024-10-09 12:29:29 +01:00
values . discard_whitespace ( ) ;
auto const & first_value = values . consume_a_token ( ) ;
2023-08-17 15:18:41 +01:00
// odd | even
if ( first_value . is ( Token : : Type : : Ident ) ) {
auto ident = first_value . token ( ) . ident ( ) ;
if ( ident . equals_ignoring_ascii_case ( " odd " sv ) ) {
transaction . commit ( ) ;
return Selector : : SimpleSelector : : ANPlusBPattern { 2 , 1 } ;
}
if ( ident . equals_ignoring_ascii_case ( " even " sv ) ) {
transaction . commit ( ) ;
return Selector : : SimpleSelector : : ANPlusBPattern { 2 , 0 } ;
}
}
// <integer>
if ( is_integer ( first_value ) ) {
int b = first_value . token ( ) . to_integer ( ) ;
transaction . commit ( ) ;
return Selector : : SimpleSelector : : ANPlusBPattern { 0 , b } ;
}
// <n-dimension>
// <n-dimension> <signed-integer>
// <n-dimension> ['+' | '-'] <signless-integer>
if ( is_n_dimension ( first_value ) ) {
int a = first_value . token ( ) . dimension_value_int ( ) ;
2024-10-09 12:29:29 +01:00
values . discard_whitespace ( ) ;
2023-08-17 15:18:41 +01:00
// <n-dimension> <signed-integer>
2024-10-09 12:29:29 +01:00
if ( is_signed_integer ( values . next_token ( ) ) ) {
int b = values . consume_a_token ( ) . token ( ) . to_integer ( ) ;
2023-08-17 15:18:41 +01:00
transaction . commit ( ) ;
return Selector : : SimpleSelector : : ANPlusBPattern { a , b } ;
}
// <n-dimension> ['+' | '-'] <signless-integer>
{
auto child_transaction = transaction . create_child ( ) ;
2024-10-09 12:29:29 +01:00
auto const & second_value = values . consume_a_token ( ) ;
values . discard_whitespace ( ) ;
auto const & third_value = values . consume_a_token ( ) ;
2023-08-17 15:18:41 +01:00
if ( is_sign ( second_value ) & & is_signless_integer ( third_value ) ) {
int b = third_value . token ( ) . to_integer ( ) * ( second_value . is_delim ( ' + ' ) ? 1 : - 1 ) ;
child_transaction . commit ( ) ;
return Selector : : SimpleSelector : : ANPlusBPattern { a , b } ;
}
}
// <n-dimension>
transaction . commit ( ) ;
return Selector : : SimpleSelector : : ANPlusBPattern { a , 0 } ;
}
// <ndash-dimension> <signless-integer>
if ( is_ndash_dimension ( first_value ) ) {
2024-10-09 12:29:29 +01:00
values . discard_whitespace ( ) ;
auto const & second_value = values . consume_a_token ( ) ;
2023-08-17 15:18:41 +01:00
if ( is_signless_integer ( second_value ) ) {
int a = first_value . token ( ) . dimension_value_int ( ) ;
int b = - second_value . token ( ) . to_integer ( ) ;
transaction . commit ( ) ;
return Selector : : SimpleSelector : : ANPlusBPattern { a , b } ;
}
return syntax_error ( ) ;
}
// <ndashdigit-dimension>
if ( is_ndashdigit_dimension ( first_value ) ) {
auto const & dimension = first_value . token ( ) ;
int a = dimension . dimension_value_int ( ) ;
2023-12-23 15:59:14 +13:00
auto maybe_b = dimension . dimension_unit ( ) . bytes_as_string_view ( ) . substring_view ( 1 ) . to_number < int > ( ) ;
2023-08-17 15:18:41 +01:00
if ( maybe_b . has_value ( ) ) {
transaction . commit ( ) ;
return Selector : : SimpleSelector : : ANPlusBPattern { a , maybe_b . value ( ) } ;
}
return syntax_error ( ) ;
}
// <dashndashdigit-ident>
if ( is_dashndashdigit_ident ( first_value ) ) {
2023-12-23 15:59:14 +13:00
auto maybe_b = first_value . token ( ) . ident ( ) . bytes_as_string_view ( ) . substring_view ( 2 ) . to_number < int > ( ) ;
2023-08-17 15:18:41 +01:00
if ( maybe_b . has_value ( ) ) {
transaction . commit ( ) ;
return Selector : : SimpleSelector : : ANPlusBPattern { - 1 , maybe_b . value ( ) } ;
}
return syntax_error ( ) ;
}
// -n
// -n <signed-integer>
// -n ['+' | '-'] <signless-integer>
2023-11-21 12:08:39 +00:00
if ( first_value . is_ident ( " -n " sv ) ) {
2024-10-09 12:29:29 +01:00
values . discard_whitespace ( ) ;
2023-08-17 15:18:41 +01:00
// -n <signed-integer>
2024-10-09 12:29:29 +01:00
if ( is_signed_integer ( values . next_token ( ) ) ) {
int b = values . consume_a_token ( ) . token ( ) . to_integer ( ) ;
2023-08-17 15:18:41 +01:00
transaction . commit ( ) ;
return Selector : : SimpleSelector : : ANPlusBPattern { - 1 , b } ;
}
// -n ['+' | '-'] <signless-integer>
{
auto child_transaction = transaction . create_child ( ) ;
2024-10-09 12:29:29 +01:00
auto const & second_value = values . consume_a_token ( ) ;
values . discard_whitespace ( ) ;
auto const & third_value = values . consume_a_token ( ) ;
2023-08-17 15:18:41 +01:00
if ( is_sign ( second_value ) & & is_signless_integer ( third_value ) ) {
int b = third_value . token ( ) . to_integer ( ) * ( second_value . is_delim ( ' + ' ) ? 1 : - 1 ) ;
child_transaction . commit ( ) ;
return Selector : : SimpleSelector : : ANPlusBPattern { - 1 , b } ;
}
}
// -n
transaction . commit ( ) ;
return Selector : : SimpleSelector : : ANPlusBPattern { - 1 , 0 } ;
}
// -n- <signless-integer>
2023-11-21 12:08:39 +00:00
if ( first_value . is_ident ( " -n- " sv ) ) {
2024-10-09 12:29:29 +01:00
values . discard_whitespace ( ) ;
auto const & second_value = values . consume_a_token ( ) ;
2023-08-17 15:18:41 +01:00
if ( is_signless_integer ( second_value ) ) {
int b = - second_value . token ( ) . to_integer ( ) ;
transaction . commit ( ) ;
return Selector : : SimpleSelector : : ANPlusBPattern { - 1 , b } ;
}
return syntax_error ( ) ;
}
// All that's left now are these:
// '+'?† n
// '+'?† n <signed-integer>
// '+'?† n ['+' | '-'] <signless-integer>
// '+'?† n- <signless-integer>
// '+'?† <ndashdigit-ident>
// In all of these cases, the + is optional, and has no effect.
// So, we just skip the +, and carry on.
if ( ! first_value . is_delim ( ' + ' ) ) {
values . reconsume_current_input_token ( ) ;
// We do *not* skip whitespace here.
}
2024-10-09 12:29:29 +01:00
auto const & first_after_plus = values . consume_a_token ( ) ;
2023-08-17 15:18:41 +01:00
// '+'?† n
// '+'?† n <signed-integer>
// '+'?† n ['+' | '-'] <signless-integer>
2023-11-21 12:08:39 +00:00
if ( first_after_plus . is_ident ( " n " sv ) ) {
2024-10-09 12:29:29 +01:00
values . discard_whitespace ( ) ;
2023-08-17 15:18:41 +01:00
// '+'?† n <signed-integer>
2024-10-09 12:29:29 +01:00
if ( is_signed_integer ( values . next_token ( ) ) ) {
int b = values . consume_a_token ( ) . token ( ) . to_integer ( ) ;
2023-08-17 15:18:41 +01:00
transaction . commit ( ) ;
return Selector : : SimpleSelector : : ANPlusBPattern { 1 , b } ;
}
// '+'?† n ['+' | '-'] <signless-integer>
{
auto child_transaction = transaction . create_child ( ) ;
2024-10-09 12:29:29 +01:00
auto const & second_value = values . consume_a_token ( ) ;
values . discard_whitespace ( ) ;
auto const & third_value = values . consume_a_token ( ) ;
2023-08-17 15:18:41 +01:00
if ( is_sign ( second_value ) & & is_signless_integer ( third_value ) ) {
int b = third_value . token ( ) . to_integer ( ) * ( second_value . is_delim ( ' + ' ) ? 1 : - 1 ) ;
child_transaction . commit ( ) ;
return Selector : : SimpleSelector : : ANPlusBPattern { 1 , b } ;
}
}
// '+'?† n
transaction . commit ( ) ;
return Selector : : SimpleSelector : : ANPlusBPattern { 1 , 0 } ;
}
// '+'?† n- <signless-integer>
2023-11-21 12:08:39 +00:00
if ( first_after_plus . is_ident ( " n- " sv ) ) {
2024-10-09 12:29:29 +01:00
values . discard_whitespace ( ) ;
auto const & second_value = values . consume_a_token ( ) ;
2023-08-17 15:18:41 +01:00
if ( is_signless_integer ( second_value ) ) {
int b = - second_value . token ( ) . to_integer ( ) ;
transaction . commit ( ) ;
return Selector : : SimpleSelector : : ANPlusBPattern { 1 , b } ;
}
return syntax_error ( ) ;
}
// '+'?† <ndashdigit-ident>
if ( is_ndashdigit_ident ( first_after_plus ) ) {
2023-12-23 15:59:14 +13:00
auto maybe_b = first_after_plus . token ( ) . ident ( ) . bytes_as_string_view ( ) . substring_view ( 1 ) . to_number < int > ( ) ;
2023-08-17 15:18:41 +01:00
if ( maybe_b . has_value ( ) ) {
transaction . commit ( ) ;
return Selector : : SimpleSelector : : ANPlusBPattern { 1 , maybe_b . value ( ) } ;
}
return syntax_error ( ) ;
}
return syntax_error ( ) ;
}
}