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
35pub 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 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 pub fn parse<T>(&mut self) -> PResult<T>
66 where
67 T: Parse<'a>,
68 {
69 T::parse(self)
70 }
71
72 #[inline]
74 pub fn recoverable_errors(&self) -> &[Error] {
75 &self.recoverable_errors
76 }
77
78 #[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}