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, PResult},
10    expect,
11    pos::Span,
12    tokenizer::{TokenWithSpan, Tokenizer, token},
13    util,
14};
15pub use builder::ParserBuilder;
16use oxc_allocator::{Allocator, Vec as ArenaVec};
17
18mod at_rule;
19mod builder;
20mod convert;
21mod less;
22mod macros;
23mod postcss_simple_vars;
24mod sass;
25mod selector;
26mod state;
27mod stmt;
28mod token_seq;
29mod value;
30
31pub trait Parse<'a>: Sized {
32    fn parse(input: &mut Parser<'a>) -> PResult<Self>;
33}
34
35/// Create a parser with some source code, then parse it.
36pub struct Parser<'a> {
37    allocator: &'a Allocator,
38    source: &'a str,
39    syntax: Syntax,
40    options: ParserOptions,
41    tokenizer: Tokenizer<'a>,
42    state: ParserState,
43    recoverable_errors: Vec<Error>,
44    cached_token: Option<TokenWithSpan<'a>>,
45}
46
47impl<'a> Parser<'a> {
48    /// Create a parser with the given source code and specified syntax.
49    /// If you need to control more options, please use [`ParserBuilder`].
50    pub fn new(allocator: &'a Allocator, source: &'a str, syntax: Syntax) -> Self {
51        let source = source.strip_prefix('\u{feff}').unwrap_or(source);
52        Parser {
53            allocator,
54            source,
55            syntax,
56            options: Default::default(),
57            tokenizer: Tokenizer::new(allocator, source, syntax, None, false),
58            state: Default::default(),
59            recoverable_errors: vec![],
60            cached_token: None,
61        }
62    }
63
64    /// Start to parse.
65    pub fn parse<T>(&mut self) -> PResult<T>
66    where
67        T: Parse<'a>,
68    {
69        T::parse(self)
70    }
71
72    /// Retrieve recoverable errors.
73    #[inline]
74    pub fn recoverable_errors(&self) -> &[Error] {
75        &self.recoverable_errors
76    }
77
78    /// Retrieve collected comments.
79    #[inline]
80    pub fn comments(&self) -> &[crate::tokenizer::token::Comment<'a>] {
81        self.tokenizer.comments()
82    }
83
84    #[inline]
85    pub(crate) fn allocator(&self) -> &'a Allocator {
86        self.allocator
87    }
88
89    #[inline]
90    pub(crate) fn vec<T>(&self) -> ArenaVec<'a, T> {
91        ArenaVec::new_in(&self.allocator)
92    }
93
94    #[inline]
95    pub(crate) fn vec_with_capacity<T>(&self, capacity: usize) -> ArenaVec<'a, T> {
96        ArenaVec::with_capacity_in(capacity, &self.allocator)
97    }
98
99    #[inline]
100    pub(crate) fn ident(&self, token: token::Ident<'a>, span: crate::Span) -> Ident<'a> {
101        Ident { name: self.ident_name(&token), raw: token.raw, span }
102    }
103
104    pub(super) fn parse_dollar_var_ident(&mut self) -> PResult<(Ident<'a>, Span)> {
105        let (dollar_var, span) = expect!(self, DollarVar);
106        let name = self.ident(dollar_var.ident, Span { start: span.start + 1, end: span.end });
107        Ok((name, span))
108    }
109
110    pub(crate) fn dimension(
111        &self,
112        token: token::Dimension<'a>,
113        span: crate::Span,
114    ) -> PResult<Dimension<'a>> {
115        let value_span = crate::Span { start: span.start, end: span.start + token.value.raw.len() };
116        let unit_span = crate::Span { start: span.start + token.value.raw.len(), end: span.end };
117        let value = (token.value, value_span).try_into()?;
118        let unit = self.ident(token.unit, unit_span);
119        let kind = convert::dimension_kind(unit.name);
120        Ok(Dimension { value, unit, kind, span })
121    }
122
123    #[inline]
124    pub(crate) fn interpolable_ident_static_part(
125        &self,
126        token: token::Ident<'a>,
127        span: crate::Span,
128    ) -> InterpolableIdentStaticPart<'a> {
129        InterpolableIdentStaticPart { value: self.ident_name(&token), raw: token.raw, span }
130    }
131
132    #[inline]
133    pub(crate) fn str(&self, token: token::Str<'a>, span: crate::Span) -> Str<'a> {
134        let raw_without_quotes = unsafe { token.raw.get_unchecked(1..token.raw.len() - 1) };
135        let value = if token.escaped {
136            util::handle_escape_in(raw_without_quotes, self.allocator)
137        } else {
138            raw_without_quotes
139        };
140        Str { value, raw: token.raw, span }
141    }
142
143    #[inline]
144    pub(crate) fn interpolable_str_static_part(
145        &self,
146        token: token::StrTemplate<'a>,
147        span: crate::Span,
148    ) -> InterpolableStrStaticPart<'a> {
149        let raw_without_quotes = if token.tail {
150            unsafe { token.raw.get_unchecked(0..token.raw.len() - 1) }
151        } else if token.head {
152            unsafe { token.raw.get_unchecked(1..token.raw.len()) }
153        } else {
154            token.raw
155        };
156        let value = if token.escaped {
157            util::handle_escape_in(raw_without_quotes, self.allocator)
158        } else {
159            raw_without_quotes
160        };
161        InterpolableStrStaticPart { value, raw: token.raw, span }
162    }
163
164    #[inline]
165    pub(crate) fn interpolable_url_static_part(
166        &self,
167        token: token::UrlTemplate<'a>,
168        span: crate::Span,
169    ) -> InterpolableUrlStaticPart<'a> {
170        let value = if token.escaped {
171            util::handle_escape_in(token.raw, self.allocator)
172        } else {
173            token.raw
174        };
175        InterpolableUrlStaticPart { value, raw: token.raw, span }
176    }
177
178    #[inline]
179    fn ident_name(&self, token: &token::Ident<'a>) -> &'a str {
180        if token.escaped { util::handle_escape_in(token.raw, self.allocator) } else { token.raw }
181    }
182
183    fn try_parse<R, F: FnOnce(&mut Self) -> PResult<R>>(&mut self, f: F) -> PResult<R> {
184        let tokenizer_state = self.tokenizer.state.clone();
185        let comments_count = self.tokenizer.comments_count();
186        let recoverable_errors_count = self.recoverable_errors.len();
187        let cached_token = self.cached_token.clone();
188        let result = f(self);
189        if result.is_err() {
190            self.tokenizer.state = tokenizer_state;
191            self.tokenizer.truncate_comments(comments_count);
192            self.recoverable_errors.truncate(recoverable_errors_count);
193            self.cached_token = cached_token;
194        }
195        result
196    }
197}