Skip to main content

oxc_css_parser/parser/
mod.rs

1use self::state::ParserState;
2use crate::{
3    ParserOptions,
4    ast::{
5        Dimension, Ident, InterpolableIdentStaticPart, InterpolableStrStaticPart,
6        InterpolableUrlStaticPart, Str,
7    },
8    config::Syntax,
9    error::{Error, ErrorKind, PResult},
10    pos::Span,
11    tokenizer::{Token, TokenWithSpan, Tokenizer, token},
12    util,
13};
14pub use builder::ParserBuilder;
15use oxc_allocator::{Allocator, Vec as ArenaVec};
16
17mod at_rule;
18mod builder;
19mod convert;
20mod less;
21mod postcss_simple_vars;
22mod sass;
23mod selector;
24mod state;
25mod stmt;
26mod token_seq;
27mod value;
28
29pub trait Parse<'a>: Sized {
30    fn parse(input: &mut Parser<'a>) -> PResult<Self>;
31}
32
33pub(in crate::parser) struct ParserCursor<'a> {
34    tokenizer: Tokenizer<'a>,
35    cached_token: Option<TokenWithSpan<'a>>,
36    source: &'a str,
37}
38
39impl<'a> ParserCursor<'a> {
40    #[inline]
41    fn new(tokenizer: Tokenizer<'a>, source: &'a str) -> Self {
42        Self { tokenizer, cached_token: None, source }
43    }
44
45    #[inline]
46    fn bump(&mut self) -> PResult<TokenWithSpan<'a>> {
47        match self.cached_token.take() {
48            Some(token_with_span) => Ok(token_with_span),
49            None => self.tokenizer.bump(),
50        }
51    }
52
53    #[inline]
54    fn peek(&mut self) -> PResult<&TokenWithSpan<'a>> {
55        if self.cached_token.is_none() {
56            let token = self.tokenizer.bump()?;
57            self.cached_token = Some(token);
58        }
59        match self.cached_token.as_ref() {
60            Some(token_with_span) => Ok(token_with_span),
61            None => unreachable!(),
62        }
63    }
64
65    #[inline]
66    fn eat_token<T>(
67        &mut self,
68        extract: impl FnOnce(&TokenWithSpan<'a>, &'a str) -> Option<T>,
69    ) -> PResult<Option<(T, Span)>> {
70        let token_with_span = self.bump()?;
71        if let Some(token) = extract(&token_with_span, self.source) {
72            Ok(Some((token, token_with_span.span)))
73        } else {
74            self.cached_token = Some(token_with_span);
75            Ok(None)
76        }
77    }
78
79    #[inline]
80    fn expect_token<T>(
81        &mut self,
82        expected: &'static str,
83        extract: impl FnOnce(&TokenWithSpan<'a>, &'a str) -> Option<T>,
84    ) -> PResult<(T, Span)> {
85        let token_with_span = self.bump()?;
86        if let Some(token) = extract(&token_with_span, self.source) {
87            Ok((token, token_with_span.span))
88        } else {
89            Err(Error {
90                kind: ErrorKind::Unexpected(expected, token_with_span.token.symbol()),
91                span: token_with_span.span,
92            })
93        }
94    }
95
96    #[inline]
97    fn eat_kind<T>(
98        &mut self,
99        extract: impl FnOnce(Token<'a>) -> Option<T>,
100    ) -> PResult<Option<(T, Span)>> {
101        self.eat_token(|token, _| extract(token.token))
102    }
103
104    #[inline]
105    fn expect_kind<T>(
106        &mut self,
107        expected: &'static str,
108        extract: impl FnOnce(Token<'a>) -> Option<T>,
109    ) -> PResult<(T, Span)> {
110        self.expect_token(expected, |token, _| extract(token.token))
111    }
112
113    #[inline]
114    fn expect_ampersand(&mut self) -> PResult<(token::Ampersand, Span)> {
115        self.expect_kind("&", |token| match token {
116            Token::Ampersand(token) => Some(token),
117            _ => None,
118        })
119    }
120
121    #[inline]
122    fn expect_at(&mut self) -> PResult<(token::At, Span)> {
123        self.expect_kind("@", |token| match token {
124            Token::At(token) => Some(token),
125            _ => None,
126        })
127    }
128
129    #[inline]
130    fn expect_at_keyword(&mut self) -> PResult<(token::AtKeyword<'a>, Span)> {
131        self.expect_token("<at-keyword>", |token, source| token.at_keyword(source))
132    }
133
134    #[inline]
135    fn expect_at_l_brace_var(&mut self) -> PResult<(token::AtLBraceVar<'a>, Span)> {
136        self.expect_token("@{", |token, source| token.at_l_brace_var(source))
137    }
138
139    #[inline]
140    fn expect_backtick_code(&mut self) -> PResult<(token::BacktickCode<'a>, Span)> {
141        self.expect_token("<backtick code>", |token, source| token.backtick_code(source))
142    }
143
144    #[inline]
145    fn expect_bar(&mut self) -> PResult<(token::Bar, Span)> {
146        self.expect_kind("|", |token| match token {
147            Token::Bar(token) => Some(token),
148            _ => None,
149        })
150    }
151
152    #[inline]
153    fn expect_colon(&mut self) -> PResult<(token::Colon, Span)> {
154        self.expect_kind(":", |token| match token {
155            Token::Colon(token) => Some(token),
156            _ => None,
157        })
158    }
159
160    #[inline]
161    fn expect_colon_colon(&mut self) -> PResult<(token::ColonColon, Span)> {
162        self.expect_kind("::", |token| match token {
163            Token::ColonColon(token) => Some(token),
164            _ => None,
165        })
166    }
167
168    #[inline]
169    fn expect_comma(&mut self) -> PResult<(token::Comma, Span)> {
170        self.expect_kind(",", |token| match token {
171            Token::Comma(token) => Some(token),
172            _ => None,
173        })
174    }
175
176    #[inline]
177    fn expect_dimension(&mut self) -> PResult<(token::Dimension<'a>, Span)> {
178        self.expect_token("<dimension>", |token, source| token.dimension(source))
179    }
180
181    #[inline]
182    fn expect_dollar_l_brace_var(&mut self) -> PResult<(token::DollarLBraceVar<'a>, Span)> {
183        self.expect_token("${", |token, source| token.dollar_l_brace_var(source))
184    }
185
186    #[inline]
187    fn expect_dollar_var(&mut self) -> PResult<(token::DollarVar<'a>, Span)> {
188        self.expect_token("$var", |token, source| token.dollar_var(source))
189    }
190
191    #[inline]
192    fn expect_dot(&mut self) -> PResult<(token::Dot, Span)> {
193        self.expect_kind(".", |token| match token {
194            Token::Dot(token) => Some(token),
195            _ => None,
196        })
197    }
198
199    #[inline]
200    fn expect_eof(&mut self) -> PResult<(token::Eof, Span)> {
201        self.expect_kind("<eof>", |token| match token {
202            Token::Eof(token) => Some(token),
203            _ => None,
204        })
205    }
206
207    #[inline]
208    fn expect_exclamation(&mut self) -> PResult<(token::Exclamation, Span)> {
209        self.expect_kind("!", |token| match token {
210            Token::Exclamation(token) => Some(token),
211            _ => None,
212        })
213    }
214
215    #[inline]
216    fn expect_hash(&mut self) -> PResult<(token::Hash<'a>, Span)> {
217        self.expect_token("<hash>", |token, source| token.hash(source))
218    }
219
220    #[inline]
221    fn expect_hash_l_brace(&mut self) -> PResult<(token::HashLBrace, Span)> {
222        self.expect_kind("#{", |token| match token {
223            Token::HashLBrace(token) => Some(token),
224            _ => None,
225        })
226    }
227
228    #[inline]
229    fn expect_ident(&mut self) -> PResult<(token::Ident<'a>, Span)> {
230        self.expect_token("<ident>", |token, source| token.ident(source))
231    }
232
233    #[inline]
234    fn expect_l_brace(&mut self) -> PResult<(token::LBrace, Span)> {
235        self.expect_kind("{", |token| match token {
236            Token::LBrace(token) => Some(token),
237            _ => None,
238        })
239    }
240
241    #[inline]
242    fn expect_l_bracket(&mut self) -> PResult<(token::LBracket, Span)> {
243        self.expect_kind("[", |token| match token {
244            Token::LBracket(token) => Some(token),
245            _ => None,
246        })
247    }
248
249    #[inline]
250    fn expect_linebreak(&mut self) -> PResult<(token::Linebreak, Span)> {
251        self.expect_kind("<linebreak>", |token| match token {
252            Token::Linebreak(token) => Some(token),
253            _ => None,
254        })
255    }
256
257    #[inline]
258    fn expect_l_paren(&mut self) -> PResult<(token::LParen, Span)> {
259        self.expect_kind("(", |token| match token {
260            Token::LParen(token) => Some(token),
261            _ => None,
262        })
263    }
264
265    #[inline]
266    fn expect_minus(&mut self) -> PResult<(token::Minus, Span)> {
267        self.expect_kind("-", |token| match token {
268            Token::Minus(token) => Some(token),
269            _ => None,
270        })
271    }
272
273    #[inline]
274    fn expect_number(&mut self) -> PResult<(token::Number<'a>, Span)> {
275        self.expect_token("<number>", |token, source| token.number(source))
276    }
277
278    #[inline]
279    fn expect_percent(&mut self) -> PResult<(token::Percent, Span)> {
280        self.expect_kind("%", |token| match token {
281            Token::Percent(token) => Some(token),
282            _ => None,
283        })
284    }
285
286    #[inline]
287    fn expect_percentage(&mut self) -> PResult<(token::Percentage<'a>, Span)> {
288        self.expect_token("<percentage>", |token, source| token.percentage(source))
289    }
290
291    #[inline]
292    fn expect_placeholder(&mut self) -> PResult<(token::Placeholder<'a>, Span)> {
293        self.expect_token("<placeholder>", |token, source| token.placeholder(source))
294    }
295
296    #[inline]
297    fn expect_r_brace(&mut self) -> PResult<(token::RBrace, Span)> {
298        self.expect_kind("}", |token| match token {
299            Token::RBrace(token) => Some(token),
300            _ => None,
301        })
302    }
303
304    #[inline]
305    fn expect_r_bracket(&mut self) -> PResult<(token::RBracket, Span)> {
306        self.expect_kind("]", |token| match token {
307            Token::RBracket(token) => Some(token),
308            _ => None,
309        })
310    }
311
312    #[inline]
313    fn expect_r_paren(&mut self) -> PResult<(token::RParen, Span)> {
314        self.expect_kind(")", |token| match token {
315            Token::RParen(token) => Some(token),
316            _ => None,
317        })
318    }
319
320    #[inline]
321    fn expect_semicolon(&mut self) -> PResult<(token::Semicolon, Span)> {
322        self.expect_kind(";", |token| match token {
323            Token::Semicolon(token) => Some(token),
324            _ => None,
325        })
326    }
327
328    #[inline]
329    fn expect_solidus(&mut self) -> PResult<(token::Solidus, Span)> {
330        self.expect_kind("/", |token| match token {
331            Token::Solidus(token) => Some(token),
332            _ => None,
333        })
334    }
335
336    #[inline]
337    fn expect_str(&mut self) -> PResult<(token::Str<'a>, Span)> {
338        self.expect_token("<string>", |token, source| token.str(source))
339    }
340
341    #[inline]
342    fn expect_str_template(&mut self) -> PResult<(token::StrTemplate<'a>, Span)> {
343        self.expect_token("<string template>", |token, source| token.str_template(source))
344    }
345
346    #[inline]
347    fn expect_tilde(&mut self) -> PResult<(token::Tilde, Span)> {
348        self.expect_kind("~", |token| match token {
349            Token::Tilde(token) => Some(token),
350            _ => None,
351        })
352    }
353
354    #[inline]
355    fn expect_without_ws_or_comments<T>(
356        &mut self,
357        expected: &'static str,
358        extract: impl FnOnce(Token<'a>) -> Option<T>,
359    ) -> PResult<(T, Span)> {
360        debug_assert!(self.cached_token.is_none());
361        let TokenWithSpan { token, span } = self.tokenizer.bump_without_ws_or_comments()?;
362        if let Some(token) = extract(token) {
363            Ok((token, span))
364        } else {
365            Err(Error { kind: ErrorKind::Unexpected(expected, token.symbol()), span })
366        }
367    }
368
369    #[inline]
370    fn expect_ident_without_ws_or_comments(
371        &mut self,
372        allow_leading_digit: bool,
373    ) -> PResult<(token::Ident<'a>, Span)> {
374        debug_assert!(self.cached_token.is_none());
375        if self.tokenizer.is_start_of_ident()
376            || (allow_leading_digit && self.tokenizer.is_start_of_digit())
377        {
378            self.tokenizer.scan_ident_sequence(allow_leading_digit)
379        } else {
380            let TokenWithSpan { token, span } = self.tokenizer.bump_without_ws_or_comments()?;
381            Err(Error { kind: ErrorKind::Unexpected("<ident>", token.symbol()), span })
382        }
383    }
384
385    #[inline]
386    fn expect_asterisk_without_ws_or_comments(&mut self) -> PResult<(token::Asterisk, Span)> {
387        self.expect_without_ws_or_comments("*", |token| match token {
388            Token::Asterisk(token) => Some(token),
389            _ => None,
390        })
391    }
392
393    #[inline]
394    fn expect_l_paren_without_ws_or_comments(&mut self) -> PResult<(token::LParen, Span)> {
395        self.expect_without_ws_or_comments("(", |token| match token {
396            Token::LParen(token) => Some(token),
397            _ => None,
398        })
399    }
400
401    #[inline]
402    fn expect_solidus_without_ws_or_comments(&mut self) -> PResult<(token::Solidus, Span)> {
403        self.expect_without_ws_or_comments("/", |token| match token {
404            Token::Solidus(token) => Some(token),
405            _ => None,
406        })
407    }
408
409    #[inline]
410    fn eat_bar(&mut self) -> PResult<Option<(token::Bar, Span)>> {
411        self.eat_kind(|token| match token {
412            Token::Bar(token) => Some(token),
413            _ => None,
414        })
415    }
416
417    #[inline]
418    fn eat_colon(&mut self) -> PResult<Option<(token::Colon, Span)>> {
419        self.eat_kind(|token| match token {
420            Token::Colon(token) => Some(token),
421            _ => None,
422        })
423    }
424
425    #[inline]
426    fn eat_comma(&mut self) -> PResult<Option<(token::Comma, Span)>> {
427        self.eat_kind(|token| match token {
428            Token::Comma(token) => Some(token),
429            _ => None,
430        })
431    }
432
433    #[inline]
434    fn eat_dot_dot_dot(&mut self) -> PResult<Option<(token::DotDotDot, Span)>> {
435        self.eat_kind(|token| match token {
436            Token::DotDotDot(token) => Some(token),
437            _ => None,
438        })
439    }
440
441    #[inline]
442    fn eat_exclamation(&mut self) -> PResult<Option<(token::Exclamation, Span)>> {
443        self.eat_kind(|token| match token {
444            Token::Exclamation(token) => Some(token),
445            _ => None,
446        })
447    }
448
449    #[inline]
450    fn eat_greater_than(&mut self) -> PResult<Option<(token::GreaterThan, Span)>> {
451        self.eat_kind(|token| match token {
452            Token::GreaterThan(token) => Some(token),
453            _ => None,
454        })
455    }
456
457    #[inline]
458    fn eat_ident(&mut self) -> PResult<Option<(token::Ident<'a>, Span)>> {
459        self.eat_token(|token, source| token.ident(source))
460    }
461
462    #[inline]
463    fn eat_indent(&mut self) -> PResult<Option<(token::Indent, Span)>> {
464        self.eat_kind(|token| match token {
465            Token::Indent(token) => Some(token),
466            _ => None,
467        })
468    }
469
470    #[inline]
471    fn eat_linebreak(&mut self) -> PResult<Option<(token::Linebreak, Span)>> {
472        self.eat_kind(|token| match token {
473            Token::Linebreak(token) => Some(token),
474            _ => None,
475        })
476    }
477
478    #[inline]
479    fn eat_l_paren(&mut self) -> PResult<Option<(token::LParen, Span)>> {
480        self.eat_kind(|token| match token {
481            Token::LParen(token) => Some(token),
482            _ => None,
483        })
484    }
485
486    #[inline]
487    fn eat_r_paren(&mut self) -> PResult<Option<(token::RParen, Span)>> {
488        self.eat_kind(|token| match token {
489            Token::RParen(token) => Some(token),
490            _ => None,
491        })
492    }
493
494    #[inline]
495    fn eat_semicolon(&mut self) -> PResult<Option<(token::Semicolon, Span)>> {
496        self.eat_kind(|token| match token {
497            Token::Semicolon(token) => Some(token),
498            _ => None,
499        })
500    }
501
502    #[inline]
503    fn eat_tilde(&mut self) -> PResult<Option<(token::Tilde, Span)>> {
504        self.eat_kind(|token| match token {
505            Token::Tilde(token) => Some(token),
506            _ => None,
507        })
508    }
509}
510
511/// Create a parser with some source code, then parse it.
512pub struct Parser<'a> {
513    allocator: &'a Allocator,
514    source: &'a str,
515    syntax: Syntax,
516    options: ParserOptions,
517    cursor: ParserCursor<'a>,
518    state: ParserState,
519    recoverable_errors: Vec<Error>,
520    /// Indented syntax only: `Indent` tokens consumed as mid-statement line
521    /// continuations (`@for $i\n  from 1...`). The statement's own block then
522    /// starts "virtually" at that depth (see [`SimpleBlock`]'s parse), and any
523    /// unconsumed levels are drained against `Dedent` tokens after the
524    /// statement.
525    sass_pending_indents: u32,
526}
527
528impl<'a> Parser<'a> {
529    /// Create a parser with the given source code and specified syntax.
530    /// If you need to control more options, please use [`ParserBuilder`].
531    pub fn new(allocator: &'a Allocator, source: &'a str, syntax: Syntax) -> Self {
532        let source = source.strip_prefix('\u{feff}').unwrap_or(source);
533        Parser {
534            allocator,
535            source,
536            syntax,
537            options: Default::default(),
538            cursor: ParserCursor::new(
539                Tokenizer::new(allocator, source, syntax, None, false),
540                source,
541            ),
542            state: Default::default(),
543            recoverable_errors: vec![],
544            sass_pending_indents: 0,
545        }
546    }
547
548    /// Start to parse.
549    pub fn parse<T>(&mut self) -> PResult<T>
550    where
551        T: Parse<'a>,
552    {
553        T::parse(self)
554    }
555
556    /// Retrieve recoverable errors.
557    #[inline]
558    pub fn recoverable_errors(&self) -> &[Error] {
559        &self.recoverable_errors
560    }
561
562    /// Retrieve collected comments.
563    #[inline]
564    pub fn comments(&self) -> &[crate::tokenizer::token::Comment<'a>] {
565        self.cursor.tokenizer.comments()
566    }
567
568    #[inline]
569    pub(crate) fn alloc<T>(&self, value: T) -> oxc_allocator::Box<'a, T> {
570        oxc_allocator::Box::new_in(value, &self.allocator)
571    }
572
573    #[inline]
574    pub(crate) fn vec<T>(&self) -> ArenaVec<'a, T> {
575        ArenaVec::new_in(&self.allocator)
576    }
577
578    #[inline]
579    pub(crate) fn vec1<T>(&self, value: T) -> ArenaVec<'a, T> {
580        let mut vec = self.vec_with_capacity(1);
581        vec.push(value);
582        vec
583    }
584
585    #[inline]
586    pub(crate) fn vec_with_capacity<T>(&self, capacity: usize) -> ArenaVec<'a, T> {
587        ArenaVec::with_capacity_in(capacity, &self.allocator)
588    }
589
590    #[inline]
591    pub(crate) fn ident(&self, token: token::Ident<'a>, span: crate::Span) -> Ident<'a> {
592        Ident { name: self.ident_name(&token), raw: token.raw, span }
593    }
594
595    // A `$`-prefixed variable name: '$' <ident>  (Sass variables, Less property
596    // accessors, postcss-simple-vars).
597    pub(super) fn parse_dollar_var_ident(&mut self) -> PResult<(Ident<'a>, Span)> {
598        let (dollar_var, span) = self.cursor.expect_dollar_var()?;
599        let name = self.ident(dollar_var.ident, Span { start: span.start + 1, end: span.end });
600        Ok((name, span))
601    }
602
603    pub(crate) fn dimension(
604        &self,
605        token: token::Dimension<'a>,
606        span: crate::Span,
607    ) -> PResult<Dimension<'a>> {
608        let value_span = crate::Span { start: span.start, end: span.start + token.value.raw.len() };
609        let unit_span = crate::Span { start: span.start + token.value.raw.len(), end: span.end };
610        let value = (token.value, value_span).try_into()?;
611        let unit = self.ident(token.unit, unit_span);
612        let kind = convert::dimension_kind(unit.name);
613        Ok(Dimension { value, unit, kind, span })
614    }
615
616    #[inline]
617    pub(crate) fn interpolable_ident_static_part(
618        &self,
619        token: token::Ident<'a>,
620        span: crate::Span,
621    ) -> InterpolableIdentStaticPart<'a> {
622        InterpolableIdentStaticPart { value: self.ident_name(&token), raw: token.raw, span }
623    }
624
625    #[inline]
626    pub(crate) fn str(&self, token: token::Str<'a>, span: crate::Span) -> Str<'a> {
627        let raw_without_quotes = unsafe { token.raw.get_unchecked(1..token.raw.len() - 1) };
628        let value = if token.escaped {
629            util::handle_escape_in(raw_without_quotes, self.allocator)
630        } else {
631            raw_without_quotes
632        };
633        Str { value, raw: token.raw, span }
634    }
635
636    #[inline]
637    pub(crate) fn interpolable_str_static_part(
638        &self,
639        token: token::StrTemplate<'a>,
640        span: crate::Span,
641    ) -> InterpolableStrStaticPart<'a> {
642        let raw_without_quotes = if token.tail {
643            unsafe { token.raw.get_unchecked(0..token.raw.len() - 1) }
644        } else if token.head {
645            unsafe { token.raw.get_unchecked(1..token.raw.len()) }
646        } else {
647            token.raw
648        };
649        let value = if token.escaped {
650            util::handle_escape_in(raw_without_quotes, self.allocator)
651        } else {
652            raw_without_quotes
653        };
654        InterpolableStrStaticPart { value, raw: token.raw, span }
655    }
656
657    #[inline]
658    pub(crate) fn interpolable_url_static_part(
659        &self,
660        token: token::UrlTemplate<'a>,
661        span: crate::Span,
662    ) -> InterpolableUrlStaticPart<'a> {
663        let value = if token.escaped {
664            util::handle_escape_in(token.raw, self.allocator)
665        } else {
666            token.raw
667        };
668        InterpolableUrlStaticPart { value, raw: token.raw, span }
669    }
670
671    #[inline]
672    fn ident_name(&self, token: &token::Ident<'a>) -> &'a str {
673        if token.escaped { util::handle_escape_in(token.raw, self.allocator) } else { token.raw }
674    }
675
676    fn try_parse<R, F: FnOnce(&mut Self) -> PResult<R>>(&mut self, f: F) -> PResult<R> {
677        let tokenizer_state = self.cursor.tokenizer.state.clone();
678        let comments_count = self.cursor.tokenizer.comments_count();
679        let recoverable_errors_count = self.recoverable_errors.len();
680        let cached_token = self.cursor.cached_token;
681        let sass_pending_indents = self.sass_pending_indents;
682        let result = f(self);
683        if result.is_err() {
684            self.cursor.tokenizer.state = tokenizer_state;
685            self.cursor.tokenizer.truncate_comments(comments_count);
686            self.recoverable_errors.truncate(recoverable_errors_count);
687            self.cursor.cached_token = cached_token;
688            self.sass_pending_indents = sass_pending_indents;
689        }
690        result
691    }
692}