/rust/registry/src/index.crates.io-1949cf8c6b5b557f/cssparser-0.35.0/src/parser.rs
Line | Count | Source |
1 | | /* This Source Code Form is subject to the terms of the Mozilla Public |
2 | | * License, v. 2.0. If a copy of the MPL was not distributed with this |
3 | | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ |
4 | | |
5 | | use crate::cow_rc_str::CowRcStr; |
6 | | use crate::tokenizer::{SourceLocation, SourcePosition, Token, Tokenizer}; |
7 | | use smallvec::SmallVec; |
8 | | use std::fmt; |
9 | | use std::ops::BitOr; |
10 | | use std::ops::Range; |
11 | | |
12 | | /// A capture of the internal state of a `Parser` (including the position within the input), |
13 | | /// obtained from the `Parser::position` method. |
14 | | /// |
15 | | /// Can be used with the `Parser::reset` method to restore that state. |
16 | | /// Should only be used with the `Parser` instance it came from. |
17 | | #[derive(Debug, Clone)] |
18 | | pub struct ParserState { |
19 | | pub(crate) position: usize, |
20 | | pub(crate) current_line_start_position: usize, |
21 | | pub(crate) current_line_number: u32, |
22 | | pub(crate) at_start_of: Option<BlockType>, |
23 | | } |
24 | | |
25 | | impl ParserState { |
26 | | /// The position from the start of the input, counted in UTF-8 bytes. |
27 | | #[inline] |
28 | 0 | pub fn position(&self) -> SourcePosition { |
29 | 0 | SourcePosition(self.position) |
30 | 0 | } |
31 | | |
32 | | /// The line number and column number |
33 | | #[inline] |
34 | 0 | pub fn source_location(&self) -> SourceLocation { |
35 | 0 | SourceLocation { |
36 | 0 | line: self.current_line_number, |
37 | 0 | column: (self.position - self.current_line_start_position + 1) as u32, |
38 | 0 | } |
39 | 0 | } Unexecuted instantiation: <cssparser::parser::ParserState>::source_location Unexecuted instantiation: <cssparser::parser::ParserState>::source_location |
40 | | } |
41 | | |
42 | | /// When parsing until a given token, sometimes the caller knows that parsing is going to restart |
43 | | /// at some earlier point, and consuming until we find a top level delimiter is just wasted work. |
44 | | /// |
45 | | /// In that case, callers can pass ParseUntilErrorBehavior::Stop to avoid doing all that wasted |
46 | | /// work. |
47 | | /// |
48 | | /// This is important for things like CSS nesting, where something like: |
49 | | /// |
50 | | /// foo:is(..) { |
51 | | /// ... |
52 | | /// } |
53 | | /// |
54 | | /// Would need to scan the whole {} block to find a semicolon, only for parsing getting restarted |
55 | | /// as a qualified rule later. |
56 | | #[derive(Clone, Copy, Debug, Eq, PartialEq)] |
57 | | pub enum ParseUntilErrorBehavior { |
58 | | /// Consume until we see the relevant delimiter or the end of the stream. |
59 | | Consume, |
60 | | /// Eagerly error. |
61 | | Stop, |
62 | | } |
63 | | |
64 | | /// Details about a `BasicParseError` |
65 | | #[derive(Clone, Debug, PartialEq)] |
66 | | pub enum BasicParseErrorKind<'i> { |
67 | | /// An unexpected token was encountered. |
68 | | UnexpectedToken(Token<'i>), |
69 | | /// The end of the input was encountered unexpectedly. |
70 | | EndOfInput, |
71 | | /// An `@` rule was encountered that was invalid. |
72 | | AtRuleInvalid(CowRcStr<'i>), |
73 | | /// The body of an '@' rule was invalid. |
74 | | AtRuleBodyInvalid, |
75 | | /// A qualified rule was encountered that was invalid. |
76 | | QualifiedRuleInvalid, |
77 | | } |
78 | | |
79 | | impl fmt::Display for BasicParseErrorKind<'_> { |
80 | 0 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { |
81 | 0 | match self { |
82 | 0 | BasicParseErrorKind::UnexpectedToken(token) => { |
83 | 0 | write!(f, "unexpected token: {:?}", token) |
84 | | } |
85 | 0 | BasicParseErrorKind::EndOfInput => write!(f, "unexpected end of input"), |
86 | 0 | BasicParseErrorKind::AtRuleInvalid(rule) => { |
87 | 0 | write!(f, "invalid @ rule encountered: '@{}'", rule) |
88 | | } |
89 | 0 | BasicParseErrorKind::AtRuleBodyInvalid => write!(f, "invalid @ rule body encountered"), |
90 | | BasicParseErrorKind::QualifiedRuleInvalid => { |
91 | 0 | write!(f, "invalid qualified rule encountered") |
92 | | } |
93 | | } |
94 | 0 | } |
95 | | } |
96 | | |
97 | | /// The fundamental parsing errors that can be triggered by built-in parsing routines. |
98 | | #[derive(Clone, Debug, PartialEq)] |
99 | | pub struct BasicParseError<'i> { |
100 | | /// Details of this error |
101 | | pub kind: BasicParseErrorKind<'i>, |
102 | | /// Location where this error occurred |
103 | | pub location: SourceLocation, |
104 | | } |
105 | | |
106 | | impl<'i, T> From<BasicParseError<'i>> for ParseError<'i, T> { |
107 | | #[inline] |
108 | 0 | fn from(this: BasicParseError<'i>) -> ParseError<'i, T> { |
109 | 0 | ParseError { |
110 | 0 | kind: ParseErrorKind::Basic(this.kind), |
111 | 0 | location: this.location, |
112 | 0 | } |
113 | 0 | } Unexecuted instantiation: <cssparser::parser::ParseError<()> as core::convert::From<cssparser::parser::BasicParseError>>::from Unexecuted instantiation: <cssparser::parser::ParseError<_> as core::convert::From<cssparser::parser::BasicParseError>>::from |
114 | | } |
115 | | |
116 | | impl SourceLocation { |
117 | | /// Create a new BasicParseError at this location for an unexpected token |
118 | | #[inline] |
119 | 0 | pub fn new_basic_unexpected_token_error(self, token: Token<'_>) -> BasicParseError<'_> { |
120 | 0 | self.new_basic_error(BasicParseErrorKind::UnexpectedToken(token)) |
121 | 0 | } Unexecuted instantiation: <cssparser::tokenizer::SourceLocation>::new_basic_unexpected_token_error Unexecuted instantiation: <cssparser::tokenizer::SourceLocation>::new_basic_unexpected_token_error |
122 | | |
123 | | /// Create a new BasicParseError at this location |
124 | | #[inline] |
125 | 0 | pub fn new_basic_error(self, kind: BasicParseErrorKind<'_>) -> BasicParseError<'_> { |
126 | 0 | BasicParseError { |
127 | 0 | kind, |
128 | 0 | location: self, |
129 | 0 | } |
130 | 0 | } Unexecuted instantiation: <cssparser::tokenizer::SourceLocation>::new_basic_error Unexecuted instantiation: <cssparser::tokenizer::SourceLocation>::new_basic_error |
131 | | |
132 | | /// Create a new ParseError at this location for an unexpected token |
133 | | #[inline] |
134 | 0 | pub fn new_unexpected_token_error<E>(self, token: Token<'_>) -> ParseError<'_, E> { |
135 | 0 | self.new_error(BasicParseErrorKind::UnexpectedToken(token)) |
136 | 0 | } |
137 | | |
138 | | /// Create a new basic ParseError at the current location |
139 | | #[inline] |
140 | 0 | pub fn new_error<E>(self, kind: BasicParseErrorKind<'_>) -> ParseError<'_, E> { |
141 | 0 | ParseError { |
142 | 0 | kind: ParseErrorKind::Basic(kind), |
143 | 0 | location: self, |
144 | 0 | } |
145 | 0 | } Unexecuted instantiation: <cssparser::tokenizer::SourceLocation>::new_error::<()> Unexecuted instantiation: <cssparser::tokenizer::SourceLocation>::new_error::<_> |
146 | | |
147 | | /// Create a new custom ParseError at this location |
148 | | #[inline] |
149 | 0 | pub fn new_custom_error<'i, E1: Into<E2>, E2>(self, error: E1) -> ParseError<'i, E2> { |
150 | 0 | ParseError { |
151 | 0 | kind: ParseErrorKind::Custom(error.into()), |
152 | 0 | location: self, |
153 | 0 | } |
154 | 0 | } |
155 | | } |
156 | | |
157 | | /// Details of a `ParseError` |
158 | | #[derive(Clone, Debug, PartialEq)] |
159 | | pub enum ParseErrorKind<'i, T: 'i> { |
160 | | /// A fundamental parse error from a built-in parsing routine. |
161 | | Basic(BasicParseErrorKind<'i>), |
162 | | /// A parse error reported by downstream consumer code. |
163 | | Custom(T), |
164 | | } |
165 | | |
166 | | impl<'i, T> ParseErrorKind<'i, T> { |
167 | | /// Like `std::convert::Into::into` |
168 | 0 | pub fn into<U>(self) -> ParseErrorKind<'i, U> |
169 | 0 | where |
170 | 0 | T: Into<U>, |
171 | | { |
172 | 0 | match self { |
173 | 0 | ParseErrorKind::Basic(basic) => ParseErrorKind::Basic(basic), |
174 | 0 | ParseErrorKind::Custom(custom) => ParseErrorKind::Custom(custom.into()), |
175 | | } |
176 | 0 | } |
177 | | } |
178 | | |
179 | | impl<E: fmt::Display> fmt::Display for ParseErrorKind<'_, E> { |
180 | 0 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { |
181 | 0 | match self { |
182 | 0 | ParseErrorKind::Basic(ref basic) => basic.fmt(f), |
183 | 0 | ParseErrorKind::Custom(ref custom) => custom.fmt(f), |
184 | | } |
185 | 0 | } |
186 | | } |
187 | | |
188 | | /// Extensible parse errors that can be encountered by client parsing implementations. |
189 | | #[derive(Clone, Debug, PartialEq)] |
190 | | pub struct ParseError<'i, E> { |
191 | | /// Details of this error |
192 | | pub kind: ParseErrorKind<'i, E>, |
193 | | /// Location where this error occurred |
194 | | pub location: SourceLocation, |
195 | | } |
196 | | |
197 | | impl<'i, T> ParseError<'i, T> { |
198 | | /// Extract the fundamental parse error from an extensible error. |
199 | 0 | pub fn basic(self) -> BasicParseError<'i> { |
200 | 0 | match self.kind { |
201 | 0 | ParseErrorKind::Basic(kind) => BasicParseError { |
202 | 0 | kind, |
203 | 0 | location: self.location, |
204 | 0 | }, |
205 | 0 | ParseErrorKind::Custom(_) => panic!("Not a basic parse error"), |
206 | | } |
207 | 0 | } |
208 | | |
209 | | /// Like `std::convert::Into::into` |
210 | 0 | pub fn into<U>(self) -> ParseError<'i, U> |
211 | 0 | where |
212 | 0 | T: Into<U>, |
213 | | { |
214 | 0 | ParseError { |
215 | 0 | kind: self.kind.into(), |
216 | 0 | location: self.location, |
217 | 0 | } |
218 | 0 | } |
219 | | } |
220 | | |
221 | | impl<E: fmt::Display> fmt::Display for ParseError<'_, E> { |
222 | 0 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { |
223 | 0 | self.kind.fmt(f) |
224 | 0 | } |
225 | | } |
226 | | |
227 | | impl<E: fmt::Display + fmt::Debug> std::error::Error for ParseError<'_, E> {} |
228 | | |
229 | | /// The owned input for a parser. |
230 | | pub struct ParserInput<'i> { |
231 | | tokenizer: Tokenizer<'i>, |
232 | | cached_token: Option<CachedToken<'i>>, |
233 | | } |
234 | | |
235 | | struct CachedToken<'i> { |
236 | | token: Token<'i>, |
237 | | start_position: SourcePosition, |
238 | | end_state: ParserState, |
239 | | } |
240 | | |
241 | | impl<'i> ParserInput<'i> { |
242 | | /// Create a new input for a parser. |
243 | 0 | pub fn new(input: &'i str) -> ParserInput<'i> { |
244 | 0 | ParserInput { |
245 | 0 | tokenizer: Tokenizer::new(input), |
246 | 0 | cached_token: None, |
247 | 0 | } |
248 | 0 | } |
249 | | |
250 | | #[inline] |
251 | 0 | fn cached_token_ref(&self) -> &Token<'i> { |
252 | 0 | &self.cached_token.as_ref().unwrap().token |
253 | 0 | } |
254 | | } |
255 | | |
256 | | /// A CSS parser that borrows its `&str` input, |
257 | | /// yields `Token`s, |
258 | | /// and keeps track of nested blocks and functions. |
259 | | pub struct Parser<'i, 't> { |
260 | | input: &'t mut ParserInput<'i>, |
261 | | /// If `Some(_)`, .parse_nested_block() can be called. |
262 | | at_start_of: Option<BlockType>, |
263 | | /// For parsers from `parse_until` or `parse_nested_block` |
264 | | stop_before: Delimiters, |
265 | | } |
266 | | |
267 | | #[derive(Copy, Clone, PartialEq, Eq, Debug)] |
268 | | pub(crate) enum BlockType { |
269 | | Parenthesis, |
270 | | SquareBracket, |
271 | | CurlyBracket, |
272 | | } |
273 | | |
274 | | impl BlockType { |
275 | 0 | fn opening(token: &Token) -> Option<BlockType> { |
276 | 0 | match *token { |
277 | 0 | Token::Function(_) | Token::ParenthesisBlock => Some(BlockType::Parenthesis), |
278 | 0 | Token::SquareBracketBlock => Some(BlockType::SquareBracket), |
279 | 0 | Token::CurlyBracketBlock => Some(BlockType::CurlyBracket), |
280 | 0 | _ => None, |
281 | | } |
282 | 0 | } |
283 | | |
284 | 0 | fn closing(token: &Token) -> Option<BlockType> { |
285 | 0 | match *token { |
286 | 0 | Token::CloseParenthesis => Some(BlockType::Parenthesis), |
287 | 0 | Token::CloseSquareBracket => Some(BlockType::SquareBracket), |
288 | 0 | Token::CloseCurlyBracket => Some(BlockType::CurlyBracket), |
289 | 0 | _ => None, |
290 | | } |
291 | 0 | } |
292 | | } |
293 | | |
294 | | /// A set of characters, to be used with the `Parser::parse_until*` methods. |
295 | | /// |
296 | | /// The union of two sets can be obtained with the `|` operator. Example: |
297 | | /// |
298 | | /// ```{rust,ignore} |
299 | | /// input.parse_until_before(Delimiter::CurlyBracketBlock | Delimiter::Semicolon) |
300 | | /// ``` |
301 | | #[derive(Copy, Clone, PartialEq, Eq, Debug)] |
302 | | pub struct Delimiters { |
303 | | bits: u8, |
304 | | } |
305 | | |
306 | | /// `Delimiters` constants. |
307 | | #[allow(non_upper_case_globals, non_snake_case)] |
308 | | pub mod Delimiter { |
309 | | use super::Delimiters; |
310 | | |
311 | | /// The empty delimiter set |
312 | | pub const None: Delimiters = Delimiters { bits: 0 }; |
313 | | /// The delimiter set with only the `{` opening curly bracket |
314 | | pub const CurlyBracketBlock: Delimiters = Delimiters { bits: 1 << 1 }; |
315 | | /// The delimiter set with only the `;` semicolon |
316 | | pub const Semicolon: Delimiters = Delimiters { bits: 1 << 2 }; |
317 | | /// The delimiter set with only the `!` exclamation point |
318 | | pub const Bang: Delimiters = Delimiters { bits: 1 << 3 }; |
319 | | /// The delimiter set with only the `,` comma |
320 | | pub const Comma: Delimiters = Delimiters { bits: 1 << 4 }; |
321 | | } |
322 | | |
323 | | #[allow(non_upper_case_globals, non_snake_case)] |
324 | | mod ClosingDelimiter { |
325 | | use super::Delimiters; |
326 | | |
327 | | pub const CloseCurlyBracket: Delimiters = Delimiters { bits: 1 << 5 }; |
328 | | pub const CloseSquareBracket: Delimiters = Delimiters { bits: 1 << 6 }; |
329 | | pub const CloseParenthesis: Delimiters = Delimiters { bits: 1 << 7 }; |
330 | | } |
331 | | |
332 | | impl BitOr<Delimiters> for Delimiters { |
333 | | type Output = Delimiters; |
334 | | |
335 | | #[inline] |
336 | 0 | fn bitor(self, other: Delimiters) -> Delimiters { |
337 | 0 | Delimiters { |
338 | 0 | bits: self.bits | other.bits, |
339 | 0 | } |
340 | 0 | } |
341 | | } |
342 | | |
343 | | impl Delimiters { |
344 | | #[inline] |
345 | 0 | fn contains(self, other: Delimiters) -> bool { |
346 | 0 | (self.bits & other.bits) != 0 |
347 | 0 | } |
348 | | |
349 | | #[inline] |
350 | 0 | pub(crate) fn from_byte(byte: Option<u8>) -> Delimiters { |
351 | | const TABLE: [Delimiters; 256] = { |
352 | | let mut table = [Delimiter::None; 256]; |
353 | | table[b';' as usize] = Delimiter::Semicolon; |
354 | | table[b'!' as usize] = Delimiter::Bang; |
355 | | table[b',' as usize] = Delimiter::Comma; |
356 | | table[b'{' as usize] = Delimiter::CurlyBracketBlock; |
357 | | table[b'}' as usize] = ClosingDelimiter::CloseCurlyBracket; |
358 | | table[b']' as usize] = ClosingDelimiter::CloseSquareBracket; |
359 | | table[b')' as usize] = ClosingDelimiter::CloseParenthesis; |
360 | | table |
361 | | }; |
362 | | |
363 | 0 | assert_eq!(TABLE[0], Delimiter::None); |
364 | 0 | TABLE[byte.unwrap_or(0) as usize] |
365 | 0 | } |
366 | | } |
367 | | |
368 | | /// Used in some `fn expect_*` methods |
369 | | macro_rules! expect { |
370 | | ($parser: ident, $($branches: tt)+) => { |
371 | | { |
372 | | let start_location = $parser.current_source_location(); |
373 | | match *$parser.next()? { |
374 | | $($branches)+ |
375 | | ref token => { |
376 | | return Err(start_location.new_basic_unexpected_token_error(token.clone())) |
377 | | } |
378 | | } |
379 | | } |
380 | | } |
381 | | } |
382 | | |
383 | | impl<'i: 't, 't> Parser<'i, 't> { |
384 | | /// Create a new parser |
385 | | #[inline] |
386 | 0 | pub fn new(input: &'t mut ParserInput<'i>) -> Parser<'i, 't> { |
387 | 0 | Parser { |
388 | 0 | input, |
389 | 0 | at_start_of: None, |
390 | 0 | stop_before: Delimiter::None, |
391 | 0 | } |
392 | 0 | } Unexecuted instantiation: <cssparser::parser::Parser>::new Unexecuted instantiation: <cssparser::parser::Parser>::new |
393 | | |
394 | | /// Return the current line that is being parsed. |
395 | 0 | pub fn current_line(&self) -> &'i str { |
396 | 0 | self.input.tokenizer.current_source_line() |
397 | 0 | } |
398 | | |
399 | | /// Check whether the input is exhausted. That is, if `.next()` would return a token. |
400 | | /// |
401 | | /// This ignores whitespace and comments. |
402 | | #[inline] |
403 | 0 | pub fn is_exhausted(&mut self) -> bool { |
404 | 0 | self.expect_exhausted().is_ok() |
405 | 0 | } |
406 | | |
407 | | /// Check whether the input is exhausted. That is, if `.next()` would return a token. |
408 | | /// Return a `Result` so that the `?` operator can be used: `input.expect_exhausted()?` |
409 | | /// |
410 | | /// This ignores whitespace and comments. |
411 | | #[inline] |
412 | 0 | pub fn expect_exhausted(&mut self) -> Result<(), BasicParseError<'i>> { |
413 | 0 | let start = self.state(); |
414 | 0 | let result = match self.next() { |
415 | | Err(BasicParseError { |
416 | | kind: BasicParseErrorKind::EndOfInput, |
417 | | .. |
418 | 0 | }) => Ok(()), |
419 | 0 | Err(e) => unreachable!("Unexpected error encountered: {:?}", e), |
420 | 0 | Ok(t) => Err(start |
421 | 0 | .source_location() |
422 | 0 | .new_basic_unexpected_token_error(t.clone())), |
423 | | }; |
424 | 0 | self.reset(&start); |
425 | 0 | result |
426 | 0 | } Unexecuted instantiation: <cssparser::parser::Parser>::expect_exhausted Unexecuted instantiation: <cssparser::parser::Parser>::expect_exhausted |
427 | | |
428 | | /// Return the current position within the input. |
429 | | /// |
430 | | /// This can be used with the `Parser::slice` and `slice_from` methods. |
431 | | #[inline] |
432 | 0 | pub fn position(&self) -> SourcePosition { |
433 | 0 | self.input.tokenizer.position() |
434 | 0 | } |
435 | | |
436 | | /// The current line number and column number. |
437 | | #[inline] |
438 | 0 | pub fn current_source_location(&self) -> SourceLocation { |
439 | 0 | self.input.tokenizer.current_source_location() |
440 | 0 | } Unexecuted instantiation: <cssparser::parser::Parser>::current_source_location Unexecuted instantiation: <cssparser::parser::Parser>::current_source_location |
441 | | |
442 | | /// The source map URL, if known. |
443 | | /// |
444 | | /// The source map URL is extracted from a specially formatted |
445 | | /// comment. The last such comment is used, so this value may |
446 | | /// change as parsing proceeds. |
447 | 0 | pub fn current_source_map_url(&self) -> Option<&str> { |
448 | 0 | self.input.tokenizer.current_source_map_url() |
449 | 0 | } |
450 | | |
451 | | /// The source URL, if known. |
452 | | /// |
453 | | /// The source URL is extracted from a specially formatted |
454 | | /// comment. The last such comment is used, so this value may |
455 | | /// change as parsing proceeds. |
456 | 0 | pub fn current_source_url(&self) -> Option<&str> { |
457 | 0 | self.input.tokenizer.current_source_url() |
458 | 0 | } |
459 | | |
460 | | /// Create a new BasicParseError at the current location |
461 | | #[inline] |
462 | 0 | pub fn new_basic_error(&self, kind: BasicParseErrorKind<'i>) -> BasicParseError<'i> { |
463 | 0 | self.current_source_location().new_basic_error(kind) |
464 | 0 | } |
465 | | |
466 | | /// Create a new basic ParseError at the current location |
467 | | #[inline] |
468 | 0 | pub fn new_error<E>(&self, kind: BasicParseErrorKind<'i>) -> ParseError<'i, E> { |
469 | 0 | self.current_source_location().new_error(kind) |
470 | 0 | } Unexecuted instantiation: <cssparser::parser::Parser>::new_error::<()> Unexecuted instantiation: <cssparser::parser::Parser>::new_error::<_> |
471 | | |
472 | | /// Create a new custom BasicParseError at the current location |
473 | | #[inline] |
474 | 0 | pub fn new_custom_error<E1: Into<E2>, E2>(&self, error: E1) -> ParseError<'i, E2> { |
475 | 0 | self.current_source_location().new_custom_error(error) |
476 | 0 | } |
477 | | |
478 | | /// Create a new unexpected token BasicParseError at the current location |
479 | | #[inline] |
480 | 0 | pub fn new_basic_unexpected_token_error(&self, token: Token<'i>) -> BasicParseError<'i> { |
481 | 0 | self.new_basic_error(BasicParseErrorKind::UnexpectedToken(token)) |
482 | 0 | } |
483 | | |
484 | | /// Create a new unexpected token ParseError at the current location |
485 | | #[inline] |
486 | 0 | pub fn new_unexpected_token_error<E>(&self, token: Token<'i>) -> ParseError<'i, E> { |
487 | 0 | self.new_error(BasicParseErrorKind::UnexpectedToken(token)) |
488 | 0 | } |
489 | | |
490 | | /// Create a new unexpected token or EOF ParseError at the current location |
491 | | #[inline] |
492 | 0 | pub fn new_error_for_next_token<E>(&mut self) -> ParseError<'i, E> { |
493 | 0 | let token = match self.next() { |
494 | 0 | Ok(token) => token.clone(), |
495 | 0 | Err(e) => return e.into(), |
496 | | }; |
497 | 0 | self.new_error(BasicParseErrorKind::UnexpectedToken(token)) |
498 | 0 | } |
499 | | |
500 | | /// Return the current internal state of the parser (including position within the input). |
501 | | /// |
502 | | /// This state can later be restored with the `Parser::reset` method. |
503 | | #[inline] |
504 | 0 | pub fn state(&self) -> ParserState { |
505 | 0 | ParserState { |
506 | 0 | at_start_of: self.at_start_of, |
507 | 0 | ..self.input.tokenizer.state() |
508 | 0 | } |
509 | 0 | } Unexecuted instantiation: <cssparser::parser::Parser>::state Unexecuted instantiation: <cssparser::parser::Parser>::state |
510 | | |
511 | | /// Advance the input until the next token that’s not whitespace or a comment. |
512 | | #[inline] |
513 | 0 | pub fn skip_whitespace(&mut self) { |
514 | 0 | if let Some(block_type) = self.at_start_of.take() { |
515 | 0 | consume_until_end_of_block(block_type, &mut self.input.tokenizer); |
516 | 0 | } |
517 | | |
518 | 0 | self.input.tokenizer.skip_whitespace() |
519 | 0 | } |
520 | | |
521 | | #[inline] |
522 | 0 | pub(crate) fn skip_cdc_and_cdo(&mut self) { |
523 | 0 | if let Some(block_type) = self.at_start_of.take() { |
524 | 0 | consume_until_end_of_block(block_type, &mut self.input.tokenizer); |
525 | 0 | } |
526 | | |
527 | 0 | self.input.tokenizer.skip_cdc_and_cdo() |
528 | 0 | } |
529 | | |
530 | | #[inline] |
531 | 0 | pub(crate) fn next_byte(&self) -> Option<u8> { |
532 | 0 | let byte = self.input.tokenizer.next_byte(); |
533 | 0 | if self.stop_before.contains(Delimiters::from_byte(byte)) { |
534 | 0 | return None; |
535 | 0 | } |
536 | 0 | byte |
537 | 0 | } |
538 | | |
539 | | /// Restore the internal state of the parser (including position within the input) |
540 | | /// to what was previously saved by the `Parser::position` method. |
541 | | /// |
542 | | /// Should only be used with `SourcePosition` values from the same `Parser` instance. |
543 | | #[inline] |
544 | 0 | pub fn reset(&mut self, state: &ParserState) { |
545 | 0 | self.input.tokenizer.reset(state); |
546 | 0 | self.at_start_of = state.at_start_of; |
547 | 0 | } Unexecuted instantiation: <cssparser::parser::Parser>::reset Unexecuted instantiation: <cssparser::parser::Parser>::reset |
548 | | |
549 | | /// Start looking for `var()` / `env()` functions. (See the |
550 | | /// `.seen_var_or_env_functions()` method.) |
551 | | #[inline] |
552 | 0 | pub fn look_for_var_or_env_functions(&mut self) { |
553 | 0 | self.input.tokenizer.look_for_var_or_env_functions() |
554 | 0 | } |
555 | | |
556 | | /// Return whether a `var()` or `env()` function has been seen by the |
557 | | /// tokenizer since either `look_for_var_or_env_functions` was called, and |
558 | | /// stop looking. |
559 | | #[inline] |
560 | 0 | pub fn seen_var_or_env_functions(&mut self) -> bool { |
561 | 0 | self.input.tokenizer.seen_var_or_env_functions() |
562 | 0 | } |
563 | | |
564 | | /// The old name of `try_parse`, which requires raw identifiers in the Rust 2018 edition. |
565 | | #[inline] |
566 | 0 | pub fn r#try<F, T, E>(&mut self, thing: F) -> Result<T, E> |
567 | 0 | where |
568 | 0 | F: FnOnce(&mut Parser<'i, 't>) -> Result<T, E>, |
569 | | { |
570 | 0 | self.try_parse(thing) |
571 | 0 | } |
572 | | |
573 | | /// Execute the given closure, passing it the parser. |
574 | | /// If the result (returned unchanged) is `Err`, |
575 | | /// the internal state of the parser (including position within the input) |
576 | | /// is restored to what it was before the call. |
577 | | #[inline] |
578 | 0 | pub fn try_parse<F, T, E>(&mut self, thing: F) -> Result<T, E> |
579 | 0 | where |
580 | 0 | F: FnOnce(&mut Parser<'i, 't>) -> Result<T, E>, |
581 | | { |
582 | 0 | let start = self.state(); |
583 | 0 | let result = thing(self); |
584 | 0 | if result.is_err() { |
585 | 0 | self.reset(&start) |
586 | 0 | } |
587 | 0 | result |
588 | 0 | } |
589 | | |
590 | | /// Return a slice of the CSS input |
591 | | #[inline] |
592 | 0 | pub fn slice(&self, range: Range<SourcePosition>) -> &'i str { |
593 | 0 | self.input.tokenizer.slice(range) |
594 | 0 | } |
595 | | |
596 | | /// Return a slice of the CSS input, from the given position to the current one. |
597 | | #[inline] |
598 | 0 | pub fn slice_from(&self, start_position: SourcePosition) -> &'i str { |
599 | 0 | self.input.tokenizer.slice_from(start_position) |
600 | 0 | } |
601 | | |
602 | | /// Return the next token in the input that is neither whitespace or a comment, |
603 | | /// and advance the position accordingly. |
604 | | /// |
605 | | /// After returning a `Function`, `ParenthesisBlock`, |
606 | | /// `CurlyBracketBlock`, or `SquareBracketBlock` token, |
607 | | /// the next call will skip until after the matching `CloseParenthesis`, |
608 | | /// `CloseCurlyBracket`, or `CloseSquareBracket` token. |
609 | | /// |
610 | | /// See the `Parser::parse_nested_block` method to parse the content of functions or blocks. |
611 | | /// |
612 | | /// This only returns a closing token when it is unmatched (and therefore an error). |
613 | | #[allow(clippy::should_implement_trait)] |
614 | 0 | pub fn next(&mut self) -> Result<&Token<'i>, BasicParseError<'i>> { |
615 | 0 | self.skip_whitespace(); |
616 | 0 | self.next_including_whitespace_and_comments() |
617 | 0 | } |
618 | | |
619 | | /// Same as `Parser::next`, but does not skip whitespace tokens. |
620 | 0 | pub fn next_including_whitespace(&mut self) -> Result<&Token<'i>, BasicParseError<'i>> { |
621 | | loop { |
622 | 0 | match self.next_including_whitespace_and_comments() { |
623 | 0 | Err(e) => return Err(e), |
624 | 0 | Ok(&Token::Comment(_)) => {} |
625 | 0 | _ => break, |
626 | | } |
627 | | } |
628 | 0 | Ok(self.input.cached_token_ref()) |
629 | 0 | } |
630 | | |
631 | | /// Same as `Parser::next`, but does not skip whitespace or comment tokens. |
632 | | /// |
633 | | /// **Note**: This should only be used in contexts like a CSS pre-processor |
634 | | /// where comments are preserved. |
635 | | /// When parsing higher-level values, per the CSS Syntax specification, |
636 | | /// comments should always be ignored between tokens. |
637 | 0 | pub fn next_including_whitespace_and_comments( |
638 | 0 | &mut self, |
639 | 0 | ) -> Result<&Token<'i>, BasicParseError<'i>> { |
640 | 0 | if let Some(block_type) = self.at_start_of.take() { |
641 | 0 | consume_until_end_of_block(block_type, &mut self.input.tokenizer); |
642 | 0 | } |
643 | | |
644 | 0 | let byte = self.input.tokenizer.next_byte(); |
645 | 0 | if self.stop_before.contains(Delimiters::from_byte(byte)) { |
646 | 0 | return Err(self.new_basic_error(BasicParseErrorKind::EndOfInput)); |
647 | 0 | } |
648 | | |
649 | 0 | let token_start_position = self.input.tokenizer.position(); |
650 | 0 | let using_cached_token = self |
651 | 0 | .input |
652 | 0 | .cached_token |
653 | 0 | .as_ref() |
654 | 0 | .map_or(false, |cached_token| { |
655 | 0 | cached_token.start_position == token_start_position |
656 | 0 | }); |
657 | 0 | let token = if using_cached_token { |
658 | 0 | let cached_token = self.input.cached_token.as_ref().unwrap(); |
659 | 0 | self.input.tokenizer.reset(&cached_token.end_state); |
660 | 0 | if let Token::Function(ref name) = cached_token.token { |
661 | 0 | self.input.tokenizer.see_function(name) |
662 | 0 | } |
663 | 0 | &cached_token.token |
664 | | } else { |
665 | 0 | let new_token = self |
666 | 0 | .input |
667 | 0 | .tokenizer |
668 | 0 | .next() |
669 | 0 | .map_err(|()| self.new_basic_error(BasicParseErrorKind::EndOfInput))?; |
670 | 0 | self.input.cached_token = Some(CachedToken { |
671 | 0 | token: new_token, |
672 | 0 | start_position: token_start_position, |
673 | 0 | end_state: self.input.tokenizer.state(), |
674 | 0 | }); |
675 | 0 | self.input.cached_token_ref() |
676 | | }; |
677 | | |
678 | 0 | if let Some(block_type) = BlockType::opening(token) { |
679 | 0 | self.at_start_of = Some(block_type); |
680 | 0 | } |
681 | 0 | Ok(token) |
682 | 0 | } |
683 | | |
684 | | /// Have the given closure parse something, then check the the input is exhausted. |
685 | | /// The result is overridden to an `Err(..)` if some input remains. |
686 | | /// |
687 | | /// This can help tell e.g. `color: green;` from `color: green 4px;` |
688 | | #[inline] |
689 | 0 | pub fn parse_entirely<F, T, E>(&mut self, parse: F) -> Result<T, ParseError<'i, E>> |
690 | 0 | where |
691 | 0 | F: FnOnce(&mut Parser<'i, 't>) -> Result<T, ParseError<'i, E>>, |
692 | | { |
693 | 0 | let result = parse(self)?; |
694 | 0 | self.expect_exhausted()?; |
695 | 0 | Ok(result) |
696 | 0 | } Unexecuted instantiation: <cssparser::parser::Parser>::parse_entirely::<<ammonia::style::Declarations as cssparser::rules_and_declarations::DeclarationParser>::parse_value::{closure#0}, (), ()>Unexecuted instantiation: <cssparser::parser::Parser>::parse_entirely::<_, _, _> |
697 | | |
698 | | /// Parse a list of comma-separated values, all with the same syntax. |
699 | | /// |
700 | | /// The given closure is called repeatedly with a "delimited" parser |
701 | | /// (see the `Parser::parse_until_before` method) so that it can over |
702 | | /// consume the input past a comma at this block/function nesting level. |
703 | | /// |
704 | | /// Successful results are accumulated in a vector. |
705 | | /// |
706 | | /// This method returns an`Err(..)` the first time that a closure call does, |
707 | | /// or if a closure call leaves some input before the next comma or the end |
708 | | /// of the input. |
709 | | #[inline] |
710 | 0 | pub fn parse_comma_separated<F, T, E>( |
711 | 0 | &mut self, |
712 | 0 | parse_one: F, |
713 | 0 | ) -> Result<Vec<T>, ParseError<'i, E>> |
714 | 0 | where |
715 | 0 | F: for<'tt> FnMut(&mut Parser<'i, 'tt>) -> Result<T, ParseError<'i, E>>, |
716 | | { |
717 | 0 | self.parse_comma_separated_internal(parse_one, /* ignore_errors = */ false) |
718 | 0 | } |
719 | | |
720 | | /// Like `parse_comma_separated`, but ignores errors on unknown components, |
721 | | /// rather than erroring out in the whole list. |
722 | | /// |
723 | | /// Caller must deal with the fact that the resulting list might be empty, |
724 | | /// if there's no valid component on the list. |
725 | | #[inline] |
726 | 0 | pub fn parse_comma_separated_ignoring_errors<F, T, E: 'i>(&mut self, parse_one: F) -> Vec<T> |
727 | 0 | where |
728 | 0 | F: for<'tt> FnMut(&mut Parser<'i, 'tt>) -> Result<T, ParseError<'i, E>>, |
729 | | { |
730 | 0 | match self.parse_comma_separated_internal(parse_one, /* ignore_errors = */ true) { |
731 | 0 | Ok(values) => values, |
732 | 0 | Err(..) => unreachable!(), |
733 | | } |
734 | 0 | } |
735 | | |
736 | | #[inline] |
737 | 0 | fn parse_comma_separated_internal<F, T, E>( |
738 | 0 | &mut self, |
739 | 0 | mut parse_one: F, |
740 | 0 | ignore_errors: bool, |
741 | 0 | ) -> Result<Vec<T>, ParseError<'i, E>> |
742 | 0 | where |
743 | 0 | F: for<'tt> FnMut(&mut Parser<'i, 'tt>) -> Result<T, ParseError<'i, E>>, |
744 | | { |
745 | | // Vec grows from 0 to 4 by default on first push(). So allocate with |
746 | | // capacity 1, so in the somewhat common case of only one item we don't |
747 | | // way overallocate. Note that we always push at least one item if |
748 | | // parsing succeeds. |
749 | 0 | let mut values = Vec::with_capacity(1); |
750 | | loop { |
751 | 0 | self.skip_whitespace(); // Unnecessary for correctness, but may help try() in parse_one rewind less. |
752 | 0 | match self.parse_until_before(Delimiter::Comma, &mut parse_one) { |
753 | 0 | Ok(v) => values.push(v), |
754 | 0 | Err(e) if !ignore_errors => return Err(e), |
755 | 0 | Err(_) => {} |
756 | | } |
757 | 0 | match self.next() { |
758 | 0 | Err(_) => return Ok(values), |
759 | 0 | Ok(&Token::Comma) => continue, |
760 | 0 | Ok(_) => unreachable!(), |
761 | | } |
762 | | } |
763 | 0 | } |
764 | | |
765 | | /// Parse the content of a block or function. |
766 | | /// |
767 | | /// This method panics if the last token yielded by this parser |
768 | | /// (from one of the `next*` methods) |
769 | | /// is not a on that marks the start of a block or function: |
770 | | /// a `Function`, `ParenthesisBlock`, `CurlyBracketBlock`, or `SquareBracketBlock`. |
771 | | /// |
772 | | /// The given closure is called with a "delimited" parser |
773 | | /// that stops at the end of the block or function (at the matching closing token). |
774 | | /// |
775 | | /// The result is overridden to an `Err(..)` if the closure leaves some input before that point. |
776 | | #[inline] |
777 | 0 | pub fn parse_nested_block<F, T, E>(&mut self, parse: F) -> Result<T, ParseError<'i, E>> |
778 | 0 | where |
779 | 0 | F: for<'tt> FnOnce(&mut Parser<'i, 'tt>) -> Result<T, ParseError<'i, E>>, |
780 | | { |
781 | 0 | parse_nested_block(self, parse) |
782 | 0 | } Unexecuted instantiation: <cssparser::parser::Parser>::parse_nested_block::<<ammonia::style::Declarations as cssparser::rules_and_declarations::DeclarationParser>::parse_value::{closure#0}, (), ()>Unexecuted instantiation: <cssparser::parser::Parser>::parse_nested_block::<_, _, _> |
783 | | |
784 | | /// Limit parsing to until a given delimiter or the end of the input. (E.g. |
785 | | /// a semicolon for a property value.) |
786 | | /// |
787 | | /// The given closure is called with a "delimited" parser |
788 | | /// that stops before the first character at this block/function nesting level |
789 | | /// that matches the given set of delimiters, or at the end of the input. |
790 | | /// |
791 | | /// The result is overridden to an `Err(..)` if the closure leaves some input before that point. |
792 | | #[inline] |
793 | 0 | pub fn parse_until_before<F, T, E>( |
794 | 0 | &mut self, |
795 | 0 | delimiters: Delimiters, |
796 | 0 | parse: F, |
797 | 0 | ) -> Result<T, ParseError<'i, E>> |
798 | 0 | where |
799 | 0 | F: for<'tt> FnOnce(&mut Parser<'i, 'tt>) -> Result<T, ParseError<'i, E>>, |
800 | | { |
801 | 0 | parse_until_before(self, delimiters, ParseUntilErrorBehavior::Consume, parse) |
802 | 0 | } |
803 | | |
804 | | /// Like `parse_until_before`, but also consume the delimiter token. |
805 | | /// |
806 | | /// This can be useful when you don’t need to know which delimiter it was |
807 | | /// (e.g. if these is only one in the given set) |
808 | | /// or if it was there at all (as opposed to reaching the end of the input). |
809 | | #[inline] |
810 | 0 | pub fn parse_until_after<F, T, E>( |
811 | 0 | &mut self, |
812 | 0 | delimiters: Delimiters, |
813 | 0 | parse: F, |
814 | 0 | ) -> Result<T, ParseError<'i, E>> |
815 | 0 | where |
816 | 0 | F: for<'tt> FnOnce(&mut Parser<'i, 'tt>) -> Result<T, ParseError<'i, E>>, |
817 | | { |
818 | 0 | parse_until_after(self, delimiters, ParseUntilErrorBehavior::Consume, parse) |
819 | 0 | } |
820 | | |
821 | | /// Parse a <whitespace-token> and return its value. |
822 | | #[inline] |
823 | 0 | pub fn expect_whitespace(&mut self) -> Result<&'i str, BasicParseError<'i>> { |
824 | 0 | let start_location = self.current_source_location(); |
825 | 0 | match *self.next_including_whitespace()? { |
826 | 0 | Token::WhiteSpace(value) => Ok(value), |
827 | 0 | ref t => Err(start_location.new_basic_unexpected_token_error(t.clone())), |
828 | | } |
829 | 0 | } |
830 | | |
831 | | /// Parse a <ident-token> and return the unescaped value. |
832 | | #[inline] |
833 | 0 | pub fn expect_ident(&mut self) -> Result<&CowRcStr<'i>, BasicParseError<'i>> { |
834 | 0 | expect! {self, |
835 | 0 | Token::Ident(ref value) => Ok(value), |
836 | | } |
837 | 0 | } Unexecuted instantiation: <cssparser::parser::Parser>::expect_ident Unexecuted instantiation: <cssparser::parser::Parser>::expect_ident |
838 | | |
839 | | /// expect_ident, but clone the CowRcStr |
840 | | #[inline] |
841 | 0 | pub fn expect_ident_cloned(&mut self) -> Result<CowRcStr<'i>, BasicParseError<'i>> { |
842 | 0 | self.expect_ident().cloned() |
843 | 0 | } |
844 | | |
845 | | /// Parse a <ident-token> whose unescaped value is an ASCII-insensitive match for the given value. |
846 | | #[inline] |
847 | 0 | pub fn expect_ident_matching( |
848 | 0 | &mut self, |
849 | 0 | expected_value: &str, |
850 | 0 | ) -> Result<(), BasicParseError<'i>> { |
851 | 0 | expect! {self, |
852 | 0 | Token::Ident(ref value) if value.eq_ignore_ascii_case(expected_value) => Ok(()), |
853 | | } |
854 | 0 | } |
855 | | |
856 | | /// Parse a <string-token> and return the unescaped value. |
857 | | #[inline] |
858 | 0 | pub fn expect_string(&mut self) -> Result<&CowRcStr<'i>, BasicParseError<'i>> { |
859 | 0 | expect! {self, |
860 | 0 | Token::QuotedString(ref value) => Ok(value), |
861 | | } |
862 | 0 | } |
863 | | |
864 | | /// expect_string, but clone the CowRcStr |
865 | | #[inline] |
866 | 0 | pub fn expect_string_cloned(&mut self) -> Result<CowRcStr<'i>, BasicParseError<'i>> { |
867 | 0 | self.expect_string().cloned() |
868 | 0 | } |
869 | | |
870 | | /// Parse either a <ident-token> or a <string-token>, and return the unescaped value. |
871 | | #[inline] |
872 | 0 | pub fn expect_ident_or_string(&mut self) -> Result<&CowRcStr<'i>, BasicParseError<'i>> { |
873 | 0 | expect! {self, |
874 | 0 | Token::Ident(ref value) => Ok(value), |
875 | 0 | Token::QuotedString(ref value) => Ok(value), |
876 | | } |
877 | 0 | } |
878 | | |
879 | | /// Parse a <url-token> and return the unescaped value. |
880 | | #[inline] |
881 | 0 | pub fn expect_url(&mut self) -> Result<CowRcStr<'i>, BasicParseError<'i>> { |
882 | 0 | expect! {self, |
883 | 0 | Token::UnquotedUrl(ref value) => Ok(value.clone()), |
884 | 0 | Token::Function(ref name) if name.eq_ignore_ascii_case("url") => { |
885 | 0 | self.parse_nested_block(|input| { |
886 | 0 | input.expect_string().map_err(Into::into).cloned() |
887 | 0 | }) |
888 | 0 | .map_err(ParseError::<()>::basic) |
889 | | } |
890 | | } |
891 | 0 | } |
892 | | |
893 | | /// Parse either a <url-token> or a <string-token>, and return the unescaped value. |
894 | | #[inline] |
895 | 0 | pub fn expect_url_or_string(&mut self) -> Result<CowRcStr<'i>, BasicParseError<'i>> { |
896 | 0 | expect! {self, |
897 | 0 | Token::UnquotedUrl(ref value) => Ok(value.clone()), |
898 | 0 | Token::QuotedString(ref value) => Ok(value.clone()), |
899 | 0 | Token::Function(ref name) if name.eq_ignore_ascii_case("url") => { |
900 | 0 | self.parse_nested_block(|input| { |
901 | 0 | input.expect_string().map_err(Into::into).cloned() |
902 | 0 | }) |
903 | 0 | .map_err(ParseError::<()>::basic) |
904 | | } |
905 | | } |
906 | 0 | } |
907 | | |
908 | | /// Parse a <number-token> and return the integer value. |
909 | | #[inline] |
910 | 0 | pub fn expect_number(&mut self) -> Result<f32, BasicParseError<'i>> { |
911 | 0 | expect! {self, |
912 | 0 | Token::Number { value, .. } => Ok(value), |
913 | | } |
914 | 0 | } |
915 | | |
916 | | /// Parse a <number-token> that does not have a fractional part, and return the integer value. |
917 | | #[inline] |
918 | 0 | pub fn expect_integer(&mut self) -> Result<i32, BasicParseError<'i>> { |
919 | 0 | expect! {self, |
920 | 0 | Token::Number { int_value: Some(int_value), .. } => Ok(int_value), |
921 | | } |
922 | 0 | } |
923 | | |
924 | | /// Parse a <percentage-token> and return the value. |
925 | | /// `0%` and `100%` map to `0.0` and `1.0` (not `100.0`), respectively. |
926 | | #[inline] |
927 | 0 | pub fn expect_percentage(&mut self) -> Result<f32, BasicParseError<'i>> { |
928 | 0 | expect! {self, |
929 | 0 | Token::Percentage { unit_value, .. } => Ok(unit_value), |
930 | | } |
931 | 0 | } |
932 | | |
933 | | /// Parse a `:` <colon-token>. |
934 | | #[inline] |
935 | 0 | pub fn expect_colon(&mut self) -> Result<(), BasicParseError<'i>> { |
936 | 0 | expect! {self, |
937 | 0 | Token::Colon => Ok(()), |
938 | | } |
939 | 0 | } Unexecuted instantiation: <cssparser::parser::Parser>::expect_colon Unexecuted instantiation: <cssparser::parser::Parser>::expect_colon |
940 | | |
941 | | /// Parse a `;` <semicolon-token>. |
942 | | #[inline] |
943 | 0 | pub fn expect_semicolon(&mut self) -> Result<(), BasicParseError<'i>> { |
944 | 0 | expect! {self, |
945 | 0 | Token::Semicolon => Ok(()), |
946 | | } |
947 | 0 | } |
948 | | |
949 | | /// Parse a `,` <comma-token>. |
950 | | #[inline] |
951 | 0 | pub fn expect_comma(&mut self) -> Result<(), BasicParseError<'i>> { |
952 | 0 | expect! {self, |
953 | 0 | Token::Comma => Ok(()), |
954 | | } |
955 | 0 | } |
956 | | |
957 | | /// Parse a <delim-token> with the given value. |
958 | | #[inline] |
959 | 0 | pub fn expect_delim(&mut self, expected_value: char) -> Result<(), BasicParseError<'i>> { |
960 | 0 | expect! {self, |
961 | 0 | Token::Delim(value) if value == expected_value => Ok(()), |
962 | | } |
963 | 0 | } |
964 | | |
965 | | /// Parse a `{ /* ... */ }` curly brackets block. |
966 | | /// |
967 | | /// If the result is `Ok`, you can then call the `Parser::parse_nested_block` method. |
968 | | #[inline] |
969 | 0 | pub fn expect_curly_bracket_block(&mut self) -> Result<(), BasicParseError<'i>> { |
970 | 0 | expect! {self, |
971 | 0 | Token::CurlyBracketBlock => Ok(()), |
972 | | } |
973 | 0 | } |
974 | | |
975 | | /// Parse a `[ /* ... */ ]` square brackets block. |
976 | | /// |
977 | | /// If the result is `Ok`, you can then call the `Parser::parse_nested_block` method. |
978 | | #[inline] |
979 | 0 | pub fn expect_square_bracket_block(&mut self) -> Result<(), BasicParseError<'i>> { |
980 | 0 | expect! {self, |
981 | 0 | Token::SquareBracketBlock => Ok(()), |
982 | | } |
983 | 0 | } |
984 | | |
985 | | /// Parse a `( /* ... */ )` parenthesis block. |
986 | | /// |
987 | | /// If the result is `Ok`, you can then call the `Parser::parse_nested_block` method. |
988 | | #[inline] |
989 | 0 | pub fn expect_parenthesis_block(&mut self) -> Result<(), BasicParseError<'i>> { |
990 | 0 | expect! {self, |
991 | 0 | Token::ParenthesisBlock => Ok(()), |
992 | | } |
993 | 0 | } |
994 | | |
995 | | /// Parse a <function> token and return its name. |
996 | | /// |
997 | | /// If the result is `Ok`, you can then call the `Parser::parse_nested_block` method. |
998 | | #[inline] |
999 | 0 | pub fn expect_function(&mut self) -> Result<&CowRcStr<'i>, BasicParseError<'i>> { |
1000 | 0 | expect! {self, |
1001 | 0 | Token::Function(ref name) => Ok(name), |
1002 | | } |
1003 | 0 | } |
1004 | | |
1005 | | /// Parse a <function> token whose name is an ASCII-insensitive match for the given value. |
1006 | | /// |
1007 | | /// If the result is `Ok`, you can then call the `Parser::parse_nested_block` method. |
1008 | | #[inline] |
1009 | 0 | pub fn expect_function_matching( |
1010 | 0 | &mut self, |
1011 | 0 | expected_name: &str, |
1012 | 0 | ) -> Result<(), BasicParseError<'i>> { |
1013 | 0 | expect! {self, |
1014 | 0 | Token::Function(ref name) if name.eq_ignore_ascii_case(expected_name) => Ok(()), |
1015 | | } |
1016 | 0 | } |
1017 | | |
1018 | | /// Parse the input until exhaustion and check that it contains no “error” token. |
1019 | | /// |
1020 | | /// See `Token::is_parse_error`. This also checks nested blocks and functions recursively. |
1021 | | #[inline] |
1022 | 0 | pub fn expect_no_error_token(&mut self) -> Result<(), BasicParseError<'i>> { |
1023 | | loop { |
1024 | 0 | match self.next_including_whitespace_and_comments() { |
1025 | | Ok(&Token::Function(_)) |
1026 | | | Ok(&Token::ParenthesisBlock) |
1027 | | | Ok(&Token::SquareBracketBlock) |
1028 | 0 | | Ok(&Token::CurlyBracketBlock) => self |
1029 | 0 | .parse_nested_block(|input| input.expect_no_error_token().map_err(Into::into)) |
1030 | 0 | .map_err(ParseError::<()>::basic)?, |
1031 | 0 | Ok(t) => { |
1032 | | // FIXME: maybe these should be separate variants of |
1033 | | // BasicParseError instead? |
1034 | 0 | if t.is_parse_error() { |
1035 | 0 | let token = t.clone(); |
1036 | 0 | return Err(self.new_basic_unexpected_token_error(token)); |
1037 | 0 | } |
1038 | | } |
1039 | 0 | Err(_) => return Ok(()), |
1040 | | } |
1041 | | } |
1042 | 0 | } |
1043 | | } |
1044 | | |
1045 | 0 | pub fn parse_until_before<'i: 't, 't, F, T, E>( |
1046 | 0 | parser: &mut Parser<'i, 't>, |
1047 | 0 | delimiters: Delimiters, |
1048 | 0 | error_behavior: ParseUntilErrorBehavior, |
1049 | 0 | parse: F, |
1050 | 0 | ) -> Result<T, ParseError<'i, E>> |
1051 | 0 | where |
1052 | 0 | F: for<'tt> FnOnce(&mut Parser<'i, 'tt>) -> Result<T, ParseError<'i, E>>, |
1053 | | { |
1054 | 0 | let delimiters = parser.stop_before | delimiters; |
1055 | | let result; |
1056 | | // Introduce a new scope to limit duration of nested_parser’s borrow |
1057 | | { |
1058 | 0 | let mut delimited_parser = Parser { |
1059 | 0 | input: parser.input, |
1060 | 0 | at_start_of: parser.at_start_of.take(), |
1061 | 0 | stop_before: delimiters, |
1062 | 0 | }; |
1063 | 0 | result = delimited_parser.parse_entirely(parse); |
1064 | 0 | if error_behavior == ParseUntilErrorBehavior::Stop && result.is_err() { |
1065 | 0 | return result; |
1066 | 0 | } |
1067 | 0 | if let Some(block_type) = delimited_parser.at_start_of { |
1068 | 0 | consume_until_end_of_block(block_type, &mut delimited_parser.input.tokenizer); |
1069 | 0 | } |
1070 | | } |
1071 | | // FIXME: have a special-purpose tokenizer method for this that does less work. |
1072 | | loop { |
1073 | 0 | if delimiters.contains(Delimiters::from_byte(parser.input.tokenizer.next_byte())) { |
1074 | 0 | break; |
1075 | 0 | } |
1076 | 0 | if let Ok(token) = parser.input.tokenizer.next() { |
1077 | 0 | if let Some(block_type) = BlockType::opening(&token) { |
1078 | 0 | consume_until_end_of_block(block_type, &mut parser.input.tokenizer); |
1079 | 0 | } |
1080 | | } else { |
1081 | 0 | break; |
1082 | | } |
1083 | | } |
1084 | 0 | result |
1085 | 0 | } |
1086 | | |
1087 | 0 | pub fn parse_until_after<'i: 't, 't, F, T, E>( |
1088 | 0 | parser: &mut Parser<'i, 't>, |
1089 | 0 | delimiters: Delimiters, |
1090 | 0 | error_behavior: ParseUntilErrorBehavior, |
1091 | 0 | parse: F, |
1092 | 0 | ) -> Result<T, ParseError<'i, E>> |
1093 | 0 | where |
1094 | 0 | F: for<'tt> FnOnce(&mut Parser<'i, 'tt>) -> Result<T, ParseError<'i, E>>, |
1095 | | { |
1096 | 0 | let result = parse_until_before(parser, delimiters, error_behavior, parse); |
1097 | 0 | if error_behavior == ParseUntilErrorBehavior::Stop && result.is_err() { |
1098 | 0 | return result; |
1099 | 0 | } |
1100 | 0 | let next_byte = parser.input.tokenizer.next_byte(); |
1101 | 0 | if next_byte.is_some() |
1102 | 0 | && !parser |
1103 | 0 | .stop_before |
1104 | 0 | .contains(Delimiters::from_byte(next_byte)) |
1105 | | { |
1106 | 0 | debug_assert!(delimiters.contains(Delimiters::from_byte(next_byte))); |
1107 | | // We know this byte is ASCII. |
1108 | 0 | parser.input.tokenizer.advance(1); |
1109 | 0 | if next_byte == Some(b'{') { |
1110 | 0 | consume_until_end_of_block(BlockType::CurlyBracket, &mut parser.input.tokenizer); |
1111 | 0 | } |
1112 | 0 | } |
1113 | 0 | result |
1114 | 0 | } |
1115 | | |
1116 | 0 | pub fn parse_nested_block<'i: 't, 't, F, T, E>( |
1117 | 0 | parser: &mut Parser<'i, 't>, |
1118 | 0 | parse: F, |
1119 | 0 | ) -> Result<T, ParseError<'i, E>> |
1120 | 0 | where |
1121 | 0 | F: for<'tt> FnOnce(&mut Parser<'i, 'tt>) -> Result<T, ParseError<'i, E>>, |
1122 | | { |
1123 | 0 | let block_type = parser.at_start_of.take().expect( |
1124 | 0 | "\ |
1125 | 0 | A nested parser can only be created when a Function, \ |
1126 | 0 | ParenthesisBlock, SquareBracketBlock, or CurlyBracketBlock \ |
1127 | 0 | token was just consumed.\ |
1128 | 0 | ", |
1129 | | ); |
1130 | 0 | let closing_delimiter = match block_type { |
1131 | 0 | BlockType::CurlyBracket => ClosingDelimiter::CloseCurlyBracket, |
1132 | 0 | BlockType::SquareBracket => ClosingDelimiter::CloseSquareBracket, |
1133 | 0 | BlockType::Parenthesis => ClosingDelimiter::CloseParenthesis, |
1134 | | }; |
1135 | | let result; |
1136 | | // Introduce a new scope to limit duration of nested_parser’s borrow |
1137 | | { |
1138 | 0 | let mut nested_parser = Parser { |
1139 | 0 | input: parser.input, |
1140 | 0 | at_start_of: None, |
1141 | 0 | stop_before: closing_delimiter, |
1142 | 0 | }; |
1143 | 0 | result = nested_parser.parse_entirely(parse); |
1144 | 0 | if let Some(block_type) = nested_parser.at_start_of { |
1145 | 0 | consume_until_end_of_block(block_type, &mut nested_parser.input.tokenizer); |
1146 | 0 | } |
1147 | | } |
1148 | 0 | consume_until_end_of_block(block_type, &mut parser.input.tokenizer); |
1149 | 0 | result |
1150 | 0 | } Unexecuted instantiation: cssparser::parser::parse_nested_block::<<ammonia::style::Declarations as cssparser::rules_and_declarations::DeclarationParser>::parse_value::{closure#0}, (), ()>Unexecuted instantiation: cssparser::parser::parse_nested_block::<_, _, _> |
1151 | | |
1152 | | #[inline(never)] |
1153 | | #[cold] |
1154 | 0 | fn consume_until_end_of_block(block_type: BlockType, tokenizer: &mut Tokenizer) { |
1155 | 0 | let mut stack = SmallVec::<[BlockType; 16]>::new(); |
1156 | 0 | stack.push(block_type); |
1157 | | |
1158 | | // FIXME: have a special-purpose tokenizer method for this that does less work. |
1159 | 0 | while let Ok(ref token) = tokenizer.next() { |
1160 | 0 | if let Some(b) = BlockType::closing(token) { |
1161 | 0 | if *stack.last().unwrap() == b { |
1162 | 0 | stack.pop(); |
1163 | 0 | if stack.is_empty() { |
1164 | 0 | return; |
1165 | 0 | } |
1166 | 0 | } |
1167 | 0 | } |
1168 | | |
1169 | 0 | if let Some(block_type) = BlockType::opening(token) { |
1170 | 0 | stack.push(block_type); |
1171 | 0 | } |
1172 | | } |
1173 | 0 | } |