SelectorParsing.cpp 45 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073
  1. /*
  2. * Copyright (c) 2018-2022, Andreas Kling <andreas@ladybird.org>
  3. * Copyright (c) 2020-2021, the SerenityOS developers.
  4. * Copyright (c) 2021-2024, Sam Atkins <sam@ladybird.org>
  5. * Copyright (c) 2021, Tobias Christiansen <tobyase@serenityos.org>
  6. * Copyright (c) 2022, MacDue <macdue@dueutil.tech>
  7. *
  8. * SPDX-License-Identifier: BSD-2-Clause
  9. */
  10. #include <AK/Debug.h>
  11. #include <LibWeb/CSS/Parser/Parser.h>
  12. #include <LibWeb/Infra/Strings.h>
  13. namespace Web::CSS::Parser {
  14. Optional<SelectorList> Parser::parse_as_selector(SelectorParsingMode parsing_mode)
  15. {
  16. auto selector_list = parse_a_selector_list(m_token_stream, SelectorType::Standalone, parsing_mode);
  17. if (!selector_list.is_error())
  18. return selector_list.release_value();
  19. return {};
  20. }
  21. Optional<SelectorList> Parser::parse_as_relative_selector(SelectorParsingMode parsing_mode)
  22. {
  23. auto selector_list = parse_a_selector_list(m_token_stream, SelectorType::Relative, parsing_mode);
  24. if (!selector_list.is_error())
  25. return selector_list.release_value();
  26. return {};
  27. }
  28. Optional<Selector::PseudoElement> Parser::parse_as_pseudo_element_selector()
  29. {
  30. // FIXME: This is quite janky. Selector parsing is not at all designed to allow parsing just a single part of a selector.
  31. // So, this code parses a whole selector, then rejects it if it's not a single pseudo-element simple selector.
  32. // Come back and fix this, future Sam!
  33. auto maybe_selector_list = parse_a_selector_list(m_token_stream, SelectorType::Standalone, SelectorParsingMode::Standard);
  34. if (maybe_selector_list.is_error())
  35. return {};
  36. auto& selector_list = maybe_selector_list.value();
  37. if (selector_list.size() != 1)
  38. return {};
  39. auto& selector = selector_list.first();
  40. if (selector->compound_selectors().size() != 1)
  41. return {};
  42. auto& first_compound_selector = selector->compound_selectors().first();
  43. if (first_compound_selector.simple_selectors.size() != 1)
  44. return {};
  45. auto& simple_selector = first_compound_selector.simple_selectors.first();
  46. if (simple_selector.type != Selector::SimpleSelector::Type::PseudoElement)
  47. return {};
  48. return simple_selector.pseudo_element();
  49. }
  50. static NonnullRefPtr<Selector> create_invalid_selector(Selector::Combinator combinator, Vector<ComponentValue> component_values)
  51. {
  52. // Trim leading and trailing whitespace
  53. while (!component_values.is_empty() && component_values.first().is(Token::Type::Whitespace)) {
  54. component_values.take_first();
  55. }
  56. while (!component_values.is_empty() && component_values.last().is(Token::Type::Whitespace)) {
  57. component_values.take_last();
  58. }
  59. Selector::SimpleSelector simple {
  60. .type = Selector::SimpleSelector::Type::Invalid,
  61. .value = Selector::SimpleSelector::Invalid {
  62. .component_values = move(component_values),
  63. }
  64. };
  65. Selector::CompoundSelector compound {
  66. .combinator = combinator,
  67. .simple_selectors = { move(simple) }
  68. };
  69. return Selector::create({ move(compound) });
  70. }
  71. template<typename T>
  72. Parser::ParseErrorOr<SelectorList> Parser::parse_a_selector_list(TokenStream<T>& tokens, SelectorType mode, SelectorParsingMode parsing_mode)
  73. {
  74. SelectorList selectors;
  75. for (;;) {
  76. auto selector_parts = consume_a_list_of_component_values(tokens, Token::Type::Comma);
  77. auto stream = TokenStream(selector_parts);
  78. auto selector = parse_complex_selector(stream, mode);
  79. if (selector.is_error()) {
  80. if (parsing_mode == SelectorParsingMode::Forgiving) {
  81. // Keep the invalid selector around for serialization and nesting
  82. auto combinator = mode == SelectorType::Standalone ? Selector::Combinator::None : Selector::Combinator::Descendant;
  83. selectors.append(create_invalid_selector(combinator, move(selector_parts)));
  84. } else {
  85. return selector.error();
  86. }
  87. } else {
  88. selectors.append(selector.release_value());
  89. }
  90. if (tokens.is_empty())
  91. break;
  92. tokens.discard_a_token();
  93. }
  94. if (selectors.is_empty() && parsing_mode != SelectorParsingMode::Forgiving)
  95. return ParseError::SyntaxError;
  96. return selectors;
  97. }
  98. template Parser::ParseErrorOr<SelectorList> Parser::parse_a_selector_list(TokenStream<ComponentValue>&, SelectorType, SelectorParsingMode);
  99. template Parser::ParseErrorOr<SelectorList> Parser::parse_a_selector_list(TokenStream<Token>&, SelectorType, SelectorParsingMode);
  100. Parser::ParseErrorOr<NonnullRefPtr<Selector>> Parser::parse_complex_selector(TokenStream<ComponentValue>& tokens, SelectorType mode)
  101. {
  102. Vector<Selector::CompoundSelector> compound_selectors;
  103. auto first_selector = TRY(parse_compound_selector(tokens));
  104. if (!first_selector.has_value())
  105. return ParseError::SyntaxError;
  106. if (mode == SelectorType::Standalone) {
  107. if (first_selector->combinator != Selector::Combinator::Descendant)
  108. return ParseError::SyntaxError;
  109. first_selector->combinator = Selector::Combinator::None;
  110. }
  111. compound_selectors.append(first_selector.release_value());
  112. while (tokens.has_next_token()) {
  113. auto compound_selector = TRY(parse_compound_selector(tokens));
  114. if (!compound_selector.has_value())
  115. break;
  116. compound_selectors.append(compound_selector.release_value());
  117. }
  118. if (compound_selectors.is_empty())
  119. return ParseError::SyntaxError;
  120. return Selector::create(move(compound_selectors));
  121. }
  122. Parser::ParseErrorOr<Optional<Selector::CompoundSelector>> Parser::parse_compound_selector(TokenStream<ComponentValue>& tokens)
  123. {
  124. tokens.discard_whitespace();
  125. auto combinator = parse_selector_combinator(tokens).value_or(Selector::Combinator::Descendant);
  126. tokens.discard_whitespace();
  127. Vector<Selector::SimpleSelector> simple_selectors;
  128. while (tokens.has_next_token()) {
  129. auto component = TRY(parse_simple_selector(tokens));
  130. if (!component.has_value())
  131. break;
  132. if (component->type == Selector::SimpleSelector::Type::TagName && !simple_selectors.is_empty()) {
  133. // Tag-name selectors can only go at the beginning of a compound selector.
  134. return ParseError::SyntaxError;
  135. }
  136. simple_selectors.append(component.release_value());
  137. }
  138. if (simple_selectors.is_empty())
  139. return Optional<Selector::CompoundSelector> {};
  140. return Selector::CompoundSelector { combinator, move(simple_selectors) };
  141. }
  142. Optional<Selector::Combinator> Parser::parse_selector_combinator(TokenStream<ComponentValue>& tokens)
  143. {
  144. auto const& current_value = tokens.consume_a_token();
  145. if (current_value.is(Token::Type::Delim)) {
  146. switch (current_value.token().delim()) {
  147. case '>':
  148. return Selector::Combinator::ImmediateChild;
  149. case '+':
  150. return Selector::Combinator::NextSibling;
  151. case '~':
  152. return Selector::Combinator::SubsequentSibling;
  153. case '|': {
  154. auto const& next = tokens.next_token();
  155. if (next.is(Token::Type::EndOfFile))
  156. return {};
  157. if (next.is_delim('|')) {
  158. tokens.discard_a_token();
  159. return Selector::Combinator::Column;
  160. }
  161. }
  162. }
  163. }
  164. tokens.reconsume_current_input_token();
  165. return {};
  166. }
  167. Optional<Selector::SimpleSelector::QualifiedName> Parser::parse_selector_qualified_name(TokenStream<ComponentValue>& tokens, AllowWildcardName allow_wildcard_name)
  168. {
  169. auto is_name = [](ComponentValue const& token) {
  170. return token.is_delim('*') || token.is(Token::Type::Ident);
  171. };
  172. auto get_name = [](ComponentValue const& token) {
  173. if (token.is_delim('*'))
  174. return "*"_fly_string;
  175. return token.token().ident();
  176. };
  177. // There are 3 possibilities here:
  178. // (Where <name> and <namespace> are either an <ident> or a `*` delim)
  179. // 1) `|<name>`
  180. // 2) `<namespace>|<name>`
  181. // 3) `<name>`
  182. // Whitespace is forbidden between any of these parts. https://www.w3.org/TR/selectors-4/#white-space
  183. auto transaction = tokens.begin_transaction();
  184. auto const& first_token = tokens.consume_a_token();
  185. if (first_token.is_delim('|')) {
  186. // Case 1: `|<name>`
  187. if (is_name(tokens.next_token())) {
  188. auto const& name_token = tokens.consume_a_token();
  189. if (allow_wildcard_name == AllowWildcardName::No && name_token.is_delim('*'))
  190. return {};
  191. transaction.commit();
  192. return Selector::SimpleSelector::QualifiedName {
  193. .namespace_type = Selector::SimpleSelector::QualifiedName::NamespaceType::None,
  194. .name = get_name(name_token),
  195. };
  196. }
  197. return {};
  198. }
  199. if (!is_name(first_token))
  200. return {};
  201. if (tokens.next_token().is_delim('|') && is_name(tokens.peek_token(1))) {
  202. // Case 2: `<namespace>|<name>`
  203. tokens.discard_a_token(); // `|`
  204. auto namespace_ = get_name(first_token);
  205. auto name = get_name(tokens.consume_a_token());
  206. if (allow_wildcard_name == AllowWildcardName::No && name == "*"sv)
  207. return {};
  208. auto namespace_type = namespace_ == "*"sv
  209. ? Selector::SimpleSelector::QualifiedName::NamespaceType::Any
  210. : Selector::SimpleSelector::QualifiedName::NamespaceType::Named;
  211. transaction.commit();
  212. return Selector::SimpleSelector::QualifiedName {
  213. .namespace_type = namespace_type,
  214. .namespace_ = namespace_,
  215. .name = name,
  216. };
  217. }
  218. // Case 3: `<name>`
  219. auto& name_token = first_token;
  220. if (allow_wildcard_name == AllowWildcardName::No && name_token.is_delim('*'))
  221. return {};
  222. transaction.commit();
  223. return Selector::SimpleSelector::QualifiedName {
  224. .namespace_type = Selector::SimpleSelector::QualifiedName::NamespaceType::Default,
  225. .name = get_name(name_token),
  226. };
  227. }
  228. Parser::ParseErrorOr<Selector::SimpleSelector> Parser::parse_attribute_simple_selector(ComponentValue const& first_value)
  229. {
  230. auto attribute_tokens = TokenStream { first_value.block().value };
  231. attribute_tokens.discard_whitespace();
  232. if (!attribute_tokens.has_next_token()) {
  233. dbgln_if(CSS_PARSER_DEBUG, "CSS attribute selector is empty!");
  234. return ParseError::SyntaxError;
  235. }
  236. auto maybe_qualified_name = parse_selector_qualified_name(attribute_tokens, AllowWildcardName::No);
  237. if (!maybe_qualified_name.has_value()) {
  238. dbgln_if(CSS_PARSER_DEBUG, "Expected qualified-name for attribute name, got: '{}'", attribute_tokens.next_token().to_debug_string());
  239. return ParseError::SyntaxError;
  240. }
  241. auto qualified_name = maybe_qualified_name.release_value();
  242. Selector::SimpleSelector simple_selector {
  243. .type = Selector::SimpleSelector::Type::Attribute,
  244. .value = Selector::SimpleSelector::Attribute {
  245. .match_type = Selector::SimpleSelector::Attribute::MatchType::HasAttribute,
  246. .qualified_name = qualified_name,
  247. .case_type = Selector::SimpleSelector::Attribute::CaseType::DefaultMatch,
  248. }
  249. };
  250. attribute_tokens.discard_whitespace();
  251. if (!attribute_tokens.has_next_token())
  252. return simple_selector;
  253. auto const& delim_part = attribute_tokens.consume_a_token();
  254. if (!delim_part.is(Token::Type::Delim)) {
  255. dbgln_if(CSS_PARSER_DEBUG, "Expected a delim for attribute comparison, got: '{}'", delim_part.to_debug_string());
  256. return ParseError::SyntaxError;
  257. }
  258. if (delim_part.token().delim() == '=') {
  259. simple_selector.attribute().match_type = Selector::SimpleSelector::Attribute::MatchType::ExactValueMatch;
  260. } else {
  261. if (!attribute_tokens.has_next_token()) {
  262. dbgln_if(CSS_PARSER_DEBUG, "Attribute selector ended part way through a match type.");
  263. return ParseError::SyntaxError;
  264. }
  265. auto const& delim_second_part = attribute_tokens.consume_a_token();
  266. if (!delim_second_part.is_delim('=')) {
  267. dbgln_if(CSS_PARSER_DEBUG, "Expected a double delim for attribute comparison, got: '{}{}'", delim_part.to_debug_string(), delim_second_part.to_debug_string());
  268. return ParseError::SyntaxError;
  269. }
  270. switch (delim_part.token().delim()) {
  271. case '~':
  272. simple_selector.attribute().match_type = Selector::SimpleSelector::Attribute::MatchType::ContainsWord;
  273. break;
  274. case '*':
  275. simple_selector.attribute().match_type = Selector::SimpleSelector::Attribute::MatchType::ContainsString;
  276. break;
  277. case '|':
  278. simple_selector.attribute().match_type = Selector::SimpleSelector::Attribute::MatchType::StartsWithSegment;
  279. break;
  280. case '^':
  281. simple_selector.attribute().match_type = Selector::SimpleSelector::Attribute::MatchType::StartsWithString;
  282. break;
  283. case '$':
  284. simple_selector.attribute().match_type = Selector::SimpleSelector::Attribute::MatchType::EndsWithString;
  285. break;
  286. default:
  287. attribute_tokens.reconsume_current_input_token();
  288. }
  289. }
  290. attribute_tokens.discard_whitespace();
  291. if (!attribute_tokens.has_next_token()) {
  292. dbgln_if(CSS_PARSER_DEBUG, "Attribute selector ended without a value to match.");
  293. return ParseError::SyntaxError;
  294. }
  295. auto const& value_part = attribute_tokens.consume_a_token();
  296. if (!value_part.is(Token::Type::Ident) && !value_part.is(Token::Type::String)) {
  297. dbgln_if(CSS_PARSER_DEBUG, "Expected a string or ident for the value to match attribute against, got: '{}'", value_part.to_debug_string());
  298. return ParseError::SyntaxError;
  299. }
  300. auto const& value_string = value_part.token().is(Token::Type::Ident) ? value_part.token().ident() : value_part.token().string();
  301. simple_selector.attribute().value = value_string.to_string();
  302. attribute_tokens.discard_whitespace();
  303. // Handle case-sensitivity suffixes. https://www.w3.org/TR/selectors-4/#attribute-case
  304. if (attribute_tokens.has_next_token()) {
  305. auto const& case_sensitivity_part = attribute_tokens.consume_a_token();
  306. if (case_sensitivity_part.is(Token::Type::Ident)) {
  307. auto case_sensitivity = case_sensitivity_part.token().ident();
  308. if (case_sensitivity.equals_ignoring_ascii_case("i"sv)) {
  309. simple_selector.attribute().case_type = Selector::SimpleSelector::Attribute::CaseType::CaseInsensitiveMatch;
  310. } else if (case_sensitivity.equals_ignoring_ascii_case("s"sv)) {
  311. simple_selector.attribute().case_type = Selector::SimpleSelector::Attribute::CaseType::CaseSensitiveMatch;
  312. } else {
  313. dbgln_if(CSS_PARSER_DEBUG, "Expected a \"i\" or \"s\" attribute selector case sensitivity identifier, got: '{}'", case_sensitivity_part.to_debug_string());
  314. return ParseError::SyntaxError;
  315. }
  316. } else {
  317. dbgln_if(CSS_PARSER_DEBUG, "Expected an attribute selector case sensitivity identifier, got: '{}'", case_sensitivity_part.to_debug_string());
  318. return ParseError::SyntaxError;
  319. }
  320. }
  321. if (attribute_tokens.has_next_token()) {
  322. dbgln_if(CSS_PARSER_DEBUG, "Was not expecting anything else inside attribute selector.");
  323. return ParseError::SyntaxError;
  324. }
  325. return simple_selector;
  326. }
  327. Parser::ParseErrorOr<Selector::SimpleSelector> Parser::parse_pseudo_simple_selector(TokenStream<ComponentValue>& tokens)
  328. {
  329. auto peek_token_ends_selector = [&]() -> bool {
  330. auto const& value = tokens.next_token();
  331. return (value.is(Token::Type::EndOfFile) || value.is(Token::Type::Whitespace) || value.is(Token::Type::Comma));
  332. };
  333. if (peek_token_ends_selector())
  334. return ParseError::SyntaxError;
  335. bool is_pseudo = false;
  336. if (tokens.next_token().is(Token::Type::Colon)) {
  337. is_pseudo = true;
  338. tokens.discard_a_token();
  339. if (peek_token_ends_selector())
  340. return ParseError::SyntaxError;
  341. }
  342. if (is_pseudo) {
  343. auto const& name_token = tokens.consume_a_token();
  344. if (!name_token.is(Token::Type::Ident)) {
  345. dbgln_if(CSS_PARSER_DEBUG, "Expected an ident for pseudo-element, got: '{}'", name_token.to_debug_string());
  346. return ParseError::SyntaxError;
  347. }
  348. auto pseudo_name = name_token.token().ident();
  349. // Note: We allow the "ignored" -webkit prefix here for -webkit-progress-bar/-webkit-progress-bar
  350. if (auto pseudo_element = Selector::PseudoElement::from_string(pseudo_name); pseudo_element.has_value()) {
  351. // :has() is fussy about pseudo-elements inside it
  352. if (m_pseudo_class_context.contains_slow(PseudoClass::Has) && !is_has_allowed_pseudo_element(pseudo_element->type())) {
  353. return ParseError::SyntaxError;
  354. }
  355. return Selector::SimpleSelector {
  356. .type = Selector::SimpleSelector::Type::PseudoElement,
  357. .value = pseudo_element.release_value()
  358. };
  359. }
  360. // https://www.w3.org/TR/selectors-4/#compat
  361. // All other pseudo-elements whose names begin with the string “-webkit-” (matched ASCII case-insensitively)
  362. // and that are not functional notations must be treated as valid at parse time. (That is, ::-webkit-asdf is
  363. // valid at parse time, but ::-webkit-jkl() is not.) If they’re not otherwise recognized and supported, they
  364. // must be treated as matching nothing, and are unknown -webkit- pseudo-elements.
  365. if (pseudo_name.starts_with_bytes("-webkit-"sv, CaseSensitivity::CaseInsensitive)) {
  366. // :has() only allows a limited set of pseudo-elements inside it, which doesn't include unknown ones.
  367. if (m_pseudo_class_context.contains_slow(PseudoClass::Has))
  368. return ParseError::SyntaxError;
  369. return Selector::SimpleSelector {
  370. .type = Selector::SimpleSelector::Type::PseudoElement,
  371. // Unknown -webkit- pseudo-elements must be serialized in ASCII lowercase.
  372. .value = Selector::PseudoElement { Selector::PseudoElement::Type::UnknownWebKit, pseudo_name.to_string().to_ascii_lowercase() },
  373. };
  374. }
  375. if (has_ignored_vendor_prefix(pseudo_name))
  376. return ParseError::IncludesIgnoredVendorPrefix;
  377. dbgln_if(CSS_PARSER_DEBUG, "Unrecognized pseudo-element: '::{}'", pseudo_name);
  378. return ParseError::SyntaxError;
  379. }
  380. if (peek_token_ends_selector())
  381. return ParseError::SyntaxError;
  382. auto const& pseudo_class_token = tokens.consume_a_token();
  383. if (pseudo_class_token.is(Token::Type::Ident)) {
  384. auto pseudo_name = pseudo_class_token.token().ident();
  385. if (has_ignored_vendor_prefix(pseudo_name))
  386. return ParseError::IncludesIgnoredVendorPrefix;
  387. auto make_pseudo_class_selector = [](auto pseudo_class) {
  388. return Selector::SimpleSelector {
  389. .type = Selector::SimpleSelector::Type::PseudoClass,
  390. .value = Selector::SimpleSelector::PseudoClassSelector { .type = pseudo_class }
  391. };
  392. };
  393. if (auto pseudo_class = pseudo_class_from_string(pseudo_name); pseudo_class.has_value()) {
  394. if (!pseudo_class_metadata(pseudo_class.value()).is_valid_as_identifier) {
  395. dbgln_if(CSS_PARSER_DEBUG, "Pseudo-class ':{}' is only valid as a function", pseudo_name);
  396. return ParseError::SyntaxError;
  397. }
  398. return make_pseudo_class_selector(pseudo_class.value());
  399. }
  400. // Single-colon syntax allowed for ::after, ::before, ::first-letter and ::first-line for compatibility.
  401. // https://www.w3.org/TR/selectors/#pseudo-element-syntax
  402. if (auto pseudo_element = Selector::PseudoElement::from_string(pseudo_name); pseudo_element.has_value()) {
  403. switch (pseudo_element.value().type()) {
  404. case Selector::PseudoElement::Type::After:
  405. case Selector::PseudoElement::Type::Before:
  406. case Selector::PseudoElement::Type::FirstLetter:
  407. case Selector::PseudoElement::Type::FirstLine:
  408. // :has() is fussy about pseudo-elements inside it
  409. if (m_pseudo_class_context.contains_slow(PseudoClass::Has) && !is_has_allowed_pseudo_element(pseudo_element->type())) {
  410. return ParseError::SyntaxError;
  411. }
  412. return Selector::SimpleSelector {
  413. .type = Selector::SimpleSelector::Type::PseudoElement,
  414. .value = pseudo_element.value()
  415. };
  416. default:
  417. break;
  418. }
  419. }
  420. dbgln_if(CSS_PARSER_DEBUG, "Unrecognized pseudo-class: ':{}'", pseudo_name);
  421. return ParseError::SyntaxError;
  422. }
  423. if (pseudo_class_token.is_function()) {
  424. auto parse_nth_child_selector = [this](auto pseudo_class, Vector<ComponentValue> const& function_values, bool allow_of = false) -> ParseErrorOr<Selector::SimpleSelector> {
  425. auto tokens = TokenStream<ComponentValue>(function_values);
  426. auto nth_child_pattern = parse_a_n_plus_b_pattern(tokens);
  427. if (!nth_child_pattern.has_value()) {
  428. dbgln_if(CSS_PARSER_DEBUG, "!!! Invalid An+B format for {}", pseudo_class_name(pseudo_class));
  429. return ParseError::SyntaxError;
  430. }
  431. tokens.discard_whitespace();
  432. if (!tokens.has_next_token()) {
  433. return Selector::SimpleSelector {
  434. .type = Selector::SimpleSelector::Type::PseudoClass,
  435. .value = Selector::SimpleSelector::PseudoClassSelector {
  436. .type = pseudo_class,
  437. .nth_child_pattern = nth_child_pattern.release_value() }
  438. };
  439. }
  440. if (!allow_of)
  441. return ParseError::SyntaxError;
  442. // Parse the `of <selector-list>` syntax
  443. auto const& maybe_of = tokens.consume_a_token();
  444. if (!maybe_of.is_ident("of"sv))
  445. return ParseError::SyntaxError;
  446. tokens.discard_whitespace();
  447. auto selector_list = TRY(parse_a_selector_list(tokens, SelectorType::Standalone));
  448. tokens.discard_whitespace();
  449. if (tokens.has_next_token())
  450. return ParseError::SyntaxError;
  451. return Selector::SimpleSelector {
  452. .type = Selector::SimpleSelector::Type::PseudoClass,
  453. .value = Selector::SimpleSelector::PseudoClassSelector {
  454. .type = pseudo_class,
  455. .nth_child_pattern = nth_child_pattern.release_value(),
  456. .argument_selector_list = move(selector_list) }
  457. };
  458. };
  459. auto const& pseudo_function = pseudo_class_token.function();
  460. auto maybe_pseudo_class = pseudo_class_from_string(pseudo_function.name);
  461. if (!maybe_pseudo_class.has_value()) {
  462. dbgln_if(CSS_PARSER_DEBUG, "Unrecognized pseudo-class function: ':{}'()", pseudo_function.name);
  463. return ParseError::SyntaxError;
  464. }
  465. auto pseudo_class = maybe_pseudo_class.value();
  466. auto metadata = pseudo_class_metadata(pseudo_class);
  467. if (!metadata.is_valid_as_function) {
  468. dbgln_if(CSS_PARSER_DEBUG, "Pseudo-class ':{}' is not valid as a function", pseudo_function.name);
  469. return ParseError::SyntaxError;
  470. }
  471. if (pseudo_function.value.is_empty()) {
  472. dbgln_if(CSS_PARSER_DEBUG, "Empty :{}() selector", pseudo_function.name);
  473. return ParseError::SyntaxError;
  474. }
  475. // "The :has() pseudo-class cannot be nested; :has() is not valid within :has()."
  476. // https://drafts.csswg.org/selectors/#relational
  477. if (pseudo_class == PseudoClass::Has && m_pseudo_class_context.contains_slow(PseudoClass::Has)) {
  478. dbgln_if(CSS_PARSER_DEBUG, ":has() is not allowed inside :has()");
  479. return ParseError::SyntaxError;
  480. }
  481. m_pseudo_class_context.append(pseudo_class);
  482. ScopeGuard guard = [&] { m_pseudo_class_context.take_last(); };
  483. switch (metadata.parameter_type) {
  484. case PseudoClassMetadata::ParameterType::ANPlusB:
  485. return parse_nth_child_selector(pseudo_class, pseudo_function.value, false);
  486. case PseudoClassMetadata::ParameterType::ANPlusBOf:
  487. return parse_nth_child_selector(pseudo_class, pseudo_function.value, true);
  488. case PseudoClassMetadata::ParameterType::CompoundSelector: {
  489. auto function_token_stream = TokenStream(pseudo_function.value);
  490. auto compound_selector_or_error = parse_compound_selector(function_token_stream);
  491. if (compound_selector_or_error.is_error() || !compound_selector_or_error.value().has_value()) {
  492. dbgln_if(CSS_PARSER_DEBUG, "Failed to parse :{}() parameter as a compound selector", pseudo_function.name);
  493. return ParseError::SyntaxError;
  494. }
  495. auto compound_selector = compound_selector_or_error.release_value().release_value();
  496. compound_selector.combinator = Selector::Combinator::None;
  497. Vector compound_selectors { move(compound_selector) };
  498. auto selector = Selector::create(move(compound_selectors));
  499. return Selector::SimpleSelector {
  500. .type = Selector::SimpleSelector::Type::PseudoClass,
  501. .value = Selector::SimpleSelector::PseudoClassSelector {
  502. .type = pseudo_class,
  503. .argument_selector_list = { move(selector) } }
  504. };
  505. }
  506. case PseudoClassMetadata::ParameterType::ForgivingRelativeSelectorList:
  507. case PseudoClassMetadata::ParameterType::ForgivingSelectorList: {
  508. auto function_token_stream = TokenStream(pseudo_function.value);
  509. auto selector_type = metadata.parameter_type == PseudoClassMetadata::ParameterType::ForgivingSelectorList
  510. ? SelectorType::Standalone
  511. : SelectorType::Relative;
  512. // NOTE: Because it's forgiving, even complete garbage will parse OK as an empty selector-list.
  513. auto argument_selector_list = MUST(parse_a_selector_list(function_token_stream, selector_type, SelectorParsingMode::Forgiving));
  514. return Selector::SimpleSelector {
  515. .type = Selector::SimpleSelector::Type::PseudoClass,
  516. .value = Selector::SimpleSelector::PseudoClassSelector {
  517. .type = pseudo_class,
  518. .is_forgiving = true,
  519. .argument_selector_list = move(argument_selector_list) }
  520. };
  521. }
  522. case PseudoClassMetadata::ParameterType::Ident: {
  523. auto function_token_stream = TokenStream(pseudo_function.value);
  524. function_token_stream.discard_whitespace();
  525. auto const& maybe_keyword_token = function_token_stream.consume_a_token();
  526. function_token_stream.discard_whitespace();
  527. if (!maybe_keyword_token.is(Token::Type::Ident) || function_token_stream.has_next_token()) {
  528. dbgln_if(CSS_PARSER_DEBUG, "Failed to parse :{}() parameter as a keyword: not an ident", pseudo_function.name);
  529. return ParseError::SyntaxError;
  530. }
  531. auto maybe_keyword = keyword_from_string(maybe_keyword_token.token().ident());
  532. if (!maybe_keyword.has_value()) {
  533. dbgln_if(CSS_PARSER_DEBUG, "Failed to parse :{}() parameter as a keyword: unrecognized keyword", pseudo_function.name);
  534. return ParseError::SyntaxError;
  535. }
  536. return Selector::SimpleSelector {
  537. .type = Selector::SimpleSelector::Type::PseudoClass,
  538. .value = Selector::SimpleSelector::PseudoClassSelector {
  539. .type = pseudo_class,
  540. .keyword = maybe_keyword.value() }
  541. };
  542. }
  543. case PseudoClassMetadata::ParameterType::LanguageRanges: {
  544. Vector<FlyString> languages;
  545. auto function_token_stream = TokenStream(pseudo_function.value);
  546. auto language_token_lists = parse_a_comma_separated_list_of_component_values(function_token_stream);
  547. for (auto const& language_token_list : language_token_lists) {
  548. auto language_token_stream = TokenStream(language_token_list);
  549. language_token_stream.discard_whitespace();
  550. auto const& language_token = language_token_stream.consume_a_token();
  551. if (!(language_token.is(Token::Type::Ident) || language_token.is(Token::Type::String))) {
  552. dbgln_if(CSS_PARSER_DEBUG, "Invalid language range in :{}() - not a string/ident", pseudo_function.name);
  553. return ParseError::SyntaxError;
  554. }
  555. auto language_string = language_token.is(Token::Type::String) ? language_token.token().string() : language_token.token().ident();
  556. languages.append(language_string);
  557. language_token_stream.discard_whitespace();
  558. if (language_token_stream.has_next_token()) {
  559. dbgln_if(CSS_PARSER_DEBUG, "Invalid language range in :{}() - trailing tokens", pseudo_function.name);
  560. return ParseError::SyntaxError;
  561. }
  562. }
  563. return Selector::SimpleSelector {
  564. .type = Selector::SimpleSelector::Type::PseudoClass,
  565. .value = Selector::SimpleSelector::PseudoClassSelector {
  566. .type = pseudo_class,
  567. .languages = move(languages) }
  568. };
  569. }
  570. case PseudoClassMetadata::ParameterType::RelativeSelectorList:
  571. case PseudoClassMetadata::ParameterType::SelectorList: {
  572. auto function_token_stream = TokenStream(pseudo_function.value);
  573. auto selector_type = metadata.parameter_type == PseudoClassMetadata::ParameterType::SelectorList
  574. ? SelectorType::Standalone
  575. : SelectorType::Relative;
  576. auto not_selector = TRY(parse_a_selector_list(function_token_stream, selector_type));
  577. return Selector::SimpleSelector {
  578. .type = Selector::SimpleSelector::Type::PseudoClass,
  579. .value = Selector::SimpleSelector::PseudoClassSelector {
  580. .type = pseudo_class,
  581. .argument_selector_list = move(not_selector) }
  582. };
  583. }
  584. case PseudoClassMetadata::ParameterType::None:
  585. // `None` means this is not a function-type pseudo-class, so this state should be impossible.
  586. VERIFY_NOT_REACHED();
  587. }
  588. }
  589. dbgln_if(CSS_PARSER_DEBUG, "Unexpected Block in pseudo-class name, expected a function or identifier. '{}'", pseudo_class_token.to_debug_string());
  590. return ParseError::SyntaxError;
  591. }
  592. Parser::ParseErrorOr<Optional<Selector::SimpleSelector>> Parser::parse_simple_selector(TokenStream<ComponentValue>& tokens)
  593. {
  594. auto peek_token_ends_selector = [&]() -> bool {
  595. auto const& value = tokens.next_token();
  596. return (value.is(Token::Type::EndOfFile) || value.is(Token::Type::Whitespace) || value.is(Token::Type::Comma));
  597. };
  598. if (peek_token_ends_selector())
  599. return Optional<Selector::SimpleSelector> {};
  600. // Handle universal and tag-name types together, since both can be namespaced
  601. if (auto qualified_name = parse_selector_qualified_name(tokens, AllowWildcardName::Yes); qualified_name.has_value()) {
  602. if (qualified_name->name.name == "*"sv) {
  603. return Selector::SimpleSelector {
  604. .type = Selector::SimpleSelector::Type::Universal,
  605. .value = qualified_name.release_value(),
  606. };
  607. }
  608. return Selector::SimpleSelector {
  609. .type = Selector::SimpleSelector::Type::TagName,
  610. .value = qualified_name.release_value(),
  611. };
  612. }
  613. auto const& first_value = tokens.consume_a_token();
  614. if (first_value.is(Token::Type::Delim)) {
  615. u32 delim = first_value.token().delim();
  616. switch (delim) {
  617. case '*':
  618. // Handled already
  619. VERIFY_NOT_REACHED();
  620. case '&':
  621. return Selector::SimpleSelector {
  622. .type = Selector::SimpleSelector::Type::Nesting,
  623. };
  624. case '.': {
  625. if (peek_token_ends_selector())
  626. return ParseError::SyntaxError;
  627. auto const& class_name_value = tokens.consume_a_token();
  628. if (!class_name_value.is(Token::Type::Ident)) {
  629. dbgln_if(CSS_PARSER_DEBUG, "Expected an ident after '.', got: {}", class_name_value.to_debug_string());
  630. return ParseError::SyntaxError;
  631. }
  632. return Selector::SimpleSelector {
  633. .type = Selector::SimpleSelector::Type::Class,
  634. .value = Selector::SimpleSelector::Name { class_name_value.token().ident() }
  635. };
  636. }
  637. case '>':
  638. case '+':
  639. case '~':
  640. case '|':
  641. // Whitespace is not required between the compound-selector and a combinator.
  642. // So, if we see a combinator, return that this compound-selector is done, instead of a syntax error.
  643. tokens.reconsume_current_input_token();
  644. return Optional<Selector::SimpleSelector> {};
  645. default:
  646. dbgln_if(CSS_PARSER_DEBUG, "!!! Invalid simple selector!");
  647. return ParseError::SyntaxError;
  648. }
  649. }
  650. if (first_value.is(Token::Type::Hash)) {
  651. if (first_value.token().hash_type() != Token::HashType::Id) {
  652. dbgln_if(CSS_PARSER_DEBUG, "Selector contains hash token that is not an id: {}", first_value.to_debug_string());
  653. return ParseError::SyntaxError;
  654. }
  655. return Selector::SimpleSelector {
  656. .type = Selector::SimpleSelector::Type::Id,
  657. .value = Selector::SimpleSelector::Name { first_value.token().hash_value() }
  658. };
  659. }
  660. if (first_value.is_block() && first_value.block().is_square())
  661. return TRY(parse_attribute_simple_selector(first_value));
  662. if (first_value.is(Token::Type::Colon))
  663. return TRY(parse_pseudo_simple_selector(tokens));
  664. dbgln_if(CSS_PARSER_DEBUG, "!!! Invalid simple selector!");
  665. return ParseError::SyntaxError;
  666. }
  667. Optional<Selector::SimpleSelector::ANPlusBPattern> Parser::parse_a_n_plus_b_pattern(TokenStream<ComponentValue>& values)
  668. {
  669. auto transaction = values.begin_transaction();
  670. auto syntax_error = [&]() -> Optional<Selector::SimpleSelector::ANPlusBPattern> {
  671. if constexpr (CSS_PARSER_DEBUG) {
  672. dbgln_if(CSS_PARSER_DEBUG, "Invalid An+B value:");
  673. values.dump_all_tokens();
  674. }
  675. return {};
  676. };
  677. auto is_sign = [](ComponentValue const& value) -> bool {
  678. return value.is(Token::Type::Delim) && (value.token().delim() == '+' || value.token().delim() == '-');
  679. };
  680. auto is_n_dimension = [](ComponentValue const& value) -> bool {
  681. if (!value.is(Token::Type::Dimension))
  682. return false;
  683. if (!value.token().number().is_integer())
  684. return false;
  685. if (!value.token().dimension_unit().equals_ignoring_ascii_case("n"sv))
  686. return false;
  687. return true;
  688. };
  689. auto is_ndash_dimension = [](ComponentValue const& value) -> bool {
  690. if (!value.is(Token::Type::Dimension))
  691. return false;
  692. if (!value.token().number().is_integer())
  693. return false;
  694. if (!value.token().dimension_unit().equals_ignoring_ascii_case("n-"sv))
  695. return false;
  696. return true;
  697. };
  698. auto is_ndashdigit_dimension = [](ComponentValue const& value) -> bool {
  699. if (!value.is(Token::Type::Dimension))
  700. return false;
  701. if (!value.token().number().is_integer())
  702. return false;
  703. auto dimension_unit = value.token().dimension_unit();
  704. if (!dimension_unit.starts_with_bytes("n-"sv, CaseSensitivity::CaseInsensitive))
  705. return false;
  706. for (size_t i = 2; i < dimension_unit.bytes_as_string_view().length(); ++i) {
  707. if (!is_ascii_digit(dimension_unit.bytes_as_string_view()[i]))
  708. return false;
  709. }
  710. return true;
  711. };
  712. auto is_ndashdigit_ident = [](ComponentValue const& value) -> bool {
  713. if (!value.is(Token::Type::Ident))
  714. return false;
  715. auto ident = value.token().ident();
  716. if (!ident.starts_with_bytes("n-"sv, CaseSensitivity::CaseInsensitive))
  717. return false;
  718. for (size_t i = 2; i < ident.bytes_as_string_view().length(); ++i) {
  719. if (!is_ascii_digit(ident.bytes_as_string_view()[i]))
  720. return false;
  721. }
  722. return true;
  723. };
  724. auto is_dashndashdigit_ident = [](ComponentValue const& value) -> bool {
  725. if (!value.is(Token::Type::Ident))
  726. return false;
  727. auto ident = value.token().ident();
  728. if (!ident.starts_with_bytes("-n-"sv, CaseSensitivity::CaseInsensitive))
  729. return false;
  730. if (ident.bytes_as_string_view().length() == 3)
  731. return false;
  732. for (size_t i = 3; i < ident.bytes_as_string_view().length(); ++i) {
  733. if (!is_ascii_digit(ident.bytes_as_string_view()[i]))
  734. return false;
  735. }
  736. return true;
  737. };
  738. auto is_integer = [](ComponentValue const& value) -> bool {
  739. return value.is(Token::Type::Number) && value.token().number().is_integer();
  740. };
  741. auto is_signed_integer = [](ComponentValue const& value) -> bool {
  742. return value.is(Token::Type::Number) && value.token().number().is_integer_with_explicit_sign();
  743. };
  744. auto is_signless_integer = [](ComponentValue const& value) -> bool {
  745. return value.is(Token::Type::Number) && !value.token().number().is_integer_with_explicit_sign();
  746. };
  747. // https://www.w3.org/TR/css-syntax-3/#the-anb-type
  748. // Unfortunately these can't be in the same order as in the spec.
  749. values.discard_whitespace();
  750. auto const& first_value = values.consume_a_token();
  751. // odd | even
  752. if (first_value.is(Token::Type::Ident)) {
  753. auto ident = first_value.token().ident();
  754. if (ident.equals_ignoring_ascii_case("odd"sv)) {
  755. transaction.commit();
  756. return Selector::SimpleSelector::ANPlusBPattern { 2, 1 };
  757. }
  758. if (ident.equals_ignoring_ascii_case("even"sv)) {
  759. transaction.commit();
  760. return Selector::SimpleSelector::ANPlusBPattern { 2, 0 };
  761. }
  762. }
  763. // <integer>
  764. if (is_integer(first_value)) {
  765. int b = first_value.token().to_integer();
  766. transaction.commit();
  767. return Selector::SimpleSelector::ANPlusBPattern { 0, b };
  768. }
  769. // <n-dimension>
  770. // <n-dimension> <signed-integer>
  771. // <n-dimension> ['+' | '-'] <signless-integer>
  772. if (is_n_dimension(first_value)) {
  773. int a = first_value.token().dimension_value_int();
  774. values.discard_whitespace();
  775. // <n-dimension> <signed-integer>
  776. if (is_signed_integer(values.next_token())) {
  777. int b = values.consume_a_token().token().to_integer();
  778. transaction.commit();
  779. return Selector::SimpleSelector::ANPlusBPattern { a, b };
  780. }
  781. // <n-dimension> ['+' | '-'] <signless-integer>
  782. {
  783. auto child_transaction = transaction.create_child();
  784. auto const& second_value = values.consume_a_token();
  785. values.discard_whitespace();
  786. auto const& third_value = values.consume_a_token();
  787. if (is_sign(second_value) && is_signless_integer(third_value)) {
  788. int b = third_value.token().to_integer() * (second_value.is_delim('+') ? 1 : -1);
  789. child_transaction.commit();
  790. return Selector::SimpleSelector::ANPlusBPattern { a, b };
  791. }
  792. }
  793. // <n-dimension>
  794. transaction.commit();
  795. return Selector::SimpleSelector::ANPlusBPattern { a, 0 };
  796. }
  797. // <ndash-dimension> <signless-integer>
  798. if (is_ndash_dimension(first_value)) {
  799. values.discard_whitespace();
  800. auto const& second_value = values.consume_a_token();
  801. if (is_signless_integer(second_value)) {
  802. int a = first_value.token().dimension_value_int();
  803. int b = -second_value.token().to_integer();
  804. transaction.commit();
  805. return Selector::SimpleSelector::ANPlusBPattern { a, b };
  806. }
  807. return syntax_error();
  808. }
  809. // <ndashdigit-dimension>
  810. if (is_ndashdigit_dimension(first_value)) {
  811. auto const& dimension = first_value.token();
  812. int a = dimension.dimension_value_int();
  813. auto maybe_b = dimension.dimension_unit().bytes_as_string_view().substring_view(1).to_number<int>();
  814. if (maybe_b.has_value()) {
  815. transaction.commit();
  816. return Selector::SimpleSelector::ANPlusBPattern { a, maybe_b.value() };
  817. }
  818. return syntax_error();
  819. }
  820. // <dashndashdigit-ident>
  821. if (is_dashndashdigit_ident(first_value)) {
  822. auto maybe_b = first_value.token().ident().bytes_as_string_view().substring_view(2).to_number<int>();
  823. if (maybe_b.has_value()) {
  824. transaction.commit();
  825. return Selector::SimpleSelector::ANPlusBPattern { -1, maybe_b.value() };
  826. }
  827. return syntax_error();
  828. }
  829. // -n
  830. // -n <signed-integer>
  831. // -n ['+' | '-'] <signless-integer>
  832. if (first_value.is_ident("-n"sv)) {
  833. values.discard_whitespace();
  834. // -n <signed-integer>
  835. if (is_signed_integer(values.next_token())) {
  836. int b = values.consume_a_token().token().to_integer();
  837. transaction.commit();
  838. return Selector::SimpleSelector::ANPlusBPattern { -1, b };
  839. }
  840. // -n ['+' | '-'] <signless-integer>
  841. {
  842. auto child_transaction = transaction.create_child();
  843. auto const& second_value = values.consume_a_token();
  844. values.discard_whitespace();
  845. auto const& third_value = values.consume_a_token();
  846. if (is_sign(second_value) && is_signless_integer(third_value)) {
  847. int b = third_value.token().to_integer() * (second_value.is_delim('+') ? 1 : -1);
  848. child_transaction.commit();
  849. return Selector::SimpleSelector::ANPlusBPattern { -1, b };
  850. }
  851. }
  852. // -n
  853. transaction.commit();
  854. return Selector::SimpleSelector::ANPlusBPattern { -1, 0 };
  855. }
  856. // -n- <signless-integer>
  857. if (first_value.is_ident("-n-"sv)) {
  858. values.discard_whitespace();
  859. auto const& second_value = values.consume_a_token();
  860. if (is_signless_integer(second_value)) {
  861. int b = -second_value.token().to_integer();
  862. transaction.commit();
  863. return Selector::SimpleSelector::ANPlusBPattern { -1, b };
  864. }
  865. return syntax_error();
  866. }
  867. // All that's left now are these:
  868. // '+'?† n
  869. // '+'?† n <signed-integer>
  870. // '+'?† n ['+' | '-'] <signless-integer>
  871. // '+'?† n- <signless-integer>
  872. // '+'?† <ndashdigit-ident>
  873. // In all of these cases, the + is optional, and has no effect.
  874. // So, we just skip the +, and carry on.
  875. if (!first_value.is_delim('+')) {
  876. values.reconsume_current_input_token();
  877. // We do *not* skip whitespace here.
  878. }
  879. auto const& first_after_plus = values.consume_a_token();
  880. // '+'?† n
  881. // '+'?† n <signed-integer>
  882. // '+'?† n ['+' | '-'] <signless-integer>
  883. if (first_after_plus.is_ident("n"sv)) {
  884. values.discard_whitespace();
  885. // '+'?† n <signed-integer>
  886. if (is_signed_integer(values.next_token())) {
  887. int b = values.consume_a_token().token().to_integer();
  888. transaction.commit();
  889. return Selector::SimpleSelector::ANPlusBPattern { 1, b };
  890. }
  891. // '+'?† n ['+' | '-'] <signless-integer>
  892. {
  893. auto child_transaction = transaction.create_child();
  894. auto const& second_value = values.consume_a_token();
  895. values.discard_whitespace();
  896. auto const& third_value = values.consume_a_token();
  897. if (is_sign(second_value) && is_signless_integer(third_value)) {
  898. int b = third_value.token().to_integer() * (second_value.is_delim('+') ? 1 : -1);
  899. child_transaction.commit();
  900. return Selector::SimpleSelector::ANPlusBPattern { 1, b };
  901. }
  902. }
  903. // '+'?† n
  904. transaction.commit();
  905. return Selector::SimpleSelector::ANPlusBPattern { 1, 0 };
  906. }
  907. // '+'?† n- <signless-integer>
  908. if (first_after_plus.is_ident("n-"sv)) {
  909. values.discard_whitespace();
  910. auto const& second_value = values.consume_a_token();
  911. if (is_signless_integer(second_value)) {
  912. int b = -second_value.token().to_integer();
  913. transaction.commit();
  914. return Selector::SimpleSelector::ANPlusBPattern { 1, b };
  915. }
  916. return syntax_error();
  917. }
  918. // '+'?† <ndashdigit-ident>
  919. if (is_ndashdigit_ident(first_after_plus)) {
  920. auto maybe_b = first_after_plus.token().ident().bytes_as_string_view().substring_view(1).to_number<int>();
  921. if (maybe_b.has_value()) {
  922. transaction.commit();
  923. return Selector::SimpleSelector::ANPlusBPattern { 1, maybe_b.value() };
  924. }
  925. return syntax_error();
  926. }
  927. return syntax_error();
  928. }
  929. }