1use std::{borrow::Cow, iter::Zip};
2
3use smallvec::SmallVec;
4
5use crate::lexer::Token;
6
7pub struct Parser<'a> {
10 tokens: Vec<Token<'a>>,
11}
12
13pub enum ParserToken<'a> {
14 Selector { value: &'a str },
15 Property { value: &'a str },
16 Value { value: &'a str },
17}
18
19impl<'a> ParserToken<'a> {
20 #[must_use]
21 pub const fn from_token(token: &Token<'a>) -> Option<Self> {
22 match token {
23 Token::Selector { value, .. } => Some(ParserToken::Selector { value }),
24 Token::Property { value, .. } => Some(ParserToken::Property { value }),
25 Token::Value { value, .. } => Some(ParserToken::Value { value }),
26 _ => None,
27 }
28 }
29}
30
31#[derive(Clone, Copy)]
32pub enum PseudoClass {
33 Hover,
34 Active,
35 ActiveHover,
36 Disabled,
37 DisabledHover,
38 Focus,
39 FocusHover,
40 Placeholder,
41 Selection,
42}
43
44impl PseudoClass {
45 pub const fn parse_str(s: &str) -> Option<Self> {
46 let val = match s.as_bytes() {
47 b":hover" => Self::Hover,
48 b":focus" => Self::Focus,
49 b":focus:hover" => Self::FocusHover,
50 b":active" => Self::Active,
51 b":active:hover" => Self::ActiveHover,
52 b":disabled" => Self::Disabled,
53 b":disabled:hover" => Self::DisabledHover,
54 b"::placeholder" => Self::Placeholder,
55 b"::selection" => Self::Selection,
56 _ => return None,
57 };
58 Some(val)
59 }
60}
61
62pub struct Selector<'a> {
63 pub selector: &'a str,
64 pub pseudo_class: Option<PseudoClass>,
65}
66
67fn split_value(value: &str) -> Selector {
68 if let Some(colon_column) = value.find(':') {
69 Selector {
70 selector: &value[..colon_column],
71 pseudo_class: PseudoClass::parse_str(&value[colon_column..]),
72 }
73 } else {
74 Selector {
75 selector: value,
76 pseudo_class: None,
77 }
78 }
79}
80
81#[cold]
82fn split_double_colon(value: &str) -> Selector {
83 if let Some(colon_column) = value[2..].find(':') {
86 Selector {
87 selector: &value[..colon_column],
88 pseudo_class: PseudoClass::parse_str(&value[colon_column..]),
89 }
90 } else {
91 Selector {
92 selector: value,
93 pseudo_class: None,
94 }
95 }
96}
97
98impl<'a> From<&'a str> for Selector<'a> {
99 #[inline]
100 fn from(value: &'a str) -> Self {
101 if value == ":root" {
102 return Self {
103 selector: value,
104 pseudo_class: None,
105 };
106 }
107 if value.starts_with("::") {
108 return split_double_colon(value);
109 }
110 split_value(value)
111 }
112}
113
114pub struct Rule<'a> {
115 pub selectors: SmallVec<[Selector<'a>; 4]>,
116 pub properties: SmallVec<[Cow<'a, str>; 4]>,
117 pub values: SmallVec<[Cow<'a, str>; 4]>,
118}
119
120impl Rule<'_> {
121 #[must_use]
122 pub const fn new_const() -> Self {
123 Self {
124 selectors: SmallVec::<[Selector<'_>; 4]>::new_const(),
125 properties: SmallVec::<[Cow<'_, str>; 4]>::new_const(),
126 values: SmallVec::<[Cow<'_, str>; 4]>::new_const(),
127 }
128 }
129
130 pub fn iter_props(
131 &self,
132 ) -> Zip<std::slice::Iter<'_, Cow<'_, str>>, std::slice::Iter<'_, Cow<'_, str>>> {
133 self.properties.iter().zip(self.values.iter())
134 }
135
136 pub fn remove(&mut self, index: usize) {
137 self.properties.remove(index);
138 self.values.remove(index);
139 }
140}
141
142impl<'a> Parser<'a> {
143 #[must_use]
144 pub const fn new(tokens: Vec<Token<'a>>) -> Self {
145 Self { tokens }
146 }
147
148 fn selector_count(&self) -> usize {
149 self.tokens
150 .iter()
151 .filter(|t| matches!(t, Token::Selector { .. }))
152 .count()
153 }
154
155 #[must_use]
156 pub fn parse(self) -> Vec<Rule<'a>> {
157 let mut rules = Vec::with_capacity(self.selector_count());
158 let mut props = SmallVec::<[ParserToken; 16]>::new_const();
159 let mut tokens = self
160 .tokens
161 .iter()
162 .filter_map(ParserToken::from_token)
163 .peekable();
164 'main: loop {
165 let Some(token) = tokens.next() else {
166 break 'main;
167 };
168
169 let ParserToken::Selector { value: selector } = token else {
170 continue 'main;
171 };
172
173 let mut rule = Rule::new_const();
174 props.clear();
175 rule.selectors
176 .extend(selector.split(',').map(str::trim).map(Selector::from));
177 'props: loop {
178 let Some(peek) = tokens.peek() else {
179 break 'props;
180 };
181 if matches!(peek, ParserToken::Selector { .. }) {
182 break 'props;
183 }
184 if let Some(next) = tokens.next() {
185 props.push(next);
186 }
187 }
188 for chunk in props.chunks_exact(2) {
189 if let [ParserToken::Property { value: prop_value }, ParserToken::Value { value }] =
190 chunk
191 {
192 rule.properties.push(Cow::Borrowed(prop_value));
193 rule.values.push(Cow::Borrowed(value));
194 }
195 }
196 rules.push(rule);
197 }
198 rules
199 }
200}
201
202pub(crate) fn replace_vars(mut rules: Vec<Rule<'_>>) -> Vec<Rule<'_>> {
203 let Some(root_idx) = rules
204 .iter()
205 .position(|rule| rule.selectors.iter().any(|s| s.selector == ":root"))
206 else {
207 return rules;
208 };
209
210 replace_root_vars(root_idx, &mut rules);
211
212 rules
213}
214
215fn replace_root_vars(root_idx: usize, rules: &mut Vec<Rule>) {
216 let root = &mut rules[root_idx];
217 let mut indexes = SmallVec::<[usize; 16]>::new();
218 let mut keys = SmallVec::<[String; 16]>::new();
219 let mut values = SmallVec::<[String; 16]>::new();
220 find_vars(root, &mut indexes, &mut keys, &mut values);
221 remove_vars(root, &indexes);
222 let mut replace_indexes = SmallVec::<[usize; 16]>::new();
223 for rule in rules {
224 replace_indexes.clear();
225 let indexes_iter = rule
226 .values
227 .iter()
228 .enumerate()
229 .filter_map(|(i, v)| is_var_reference(v).then_some(i));
230 replace_indexes.extend(indexes_iter);
231 for idx in replace_indexes.iter() {
232 replace_var(&mut rule.values[*idx], &keys, &values);
233 }
234 }
235}
236
237fn replace_var(value: &mut Cow<'_, str>, keys: &[String], values: &[String]) {
238 let var_name = get_var_name(value);
239 if let Some(idx) = keys.iter().position(|k| k == var_name) {
240 *value = Cow::Owned(values[idx].clone());
241 }
242}
243
244fn remove_vars(root: &mut Rule, indexes: &[usize]) {
245 for index in indexes.iter().rev() {
247 root.remove(*index);
248 }
249}
250
251fn find_vars(
252 root: &mut Rule,
253 indexes: &mut SmallVec<[usize; 16]>,
254 keys: &mut SmallVec<[String; 16]>,
255 values: &mut SmallVec<[String; 16]>,
256) {
257 for (i, (k, v)) in root.iter_props().enumerate() {
258 if is_var_definition(k) {
259 indexes.push(i);
260 keys.push(k.to_string());
261 values.push(v.to_string());
262 }
263 }
264}
265
266fn is_var_definition(value: &str) -> bool {
267 value.starts_with("--")
268}
269
270fn is_var_reference(value: &str) -> bool {
271 value.ends_with(')') && value.starts_with("var(")
272}
273
274#[cold]
275#[inline(never)]
276fn get_var_name(value: &str) -> &str {
277 &value[4..value.len() - 1]
278}
279
280#[cfg(test)]
281mod tests {
282 use super::*;
283
284 #[test]
285 fn replace_vars_ok() {
286 let rules = vec![
288 Rule {
289 selectors: SmallVec::from_vec(vec![Selector {
290 selector: ":root",
291 pseudo_class: None,
292 }]),
293 properties: SmallVec::from_vec(vec![Cow::Borrowed("--main-color")]),
294 values: SmallVec::from_vec(vec![Cow::Borrowed("blue")]),
295 },
296 Rule {
297 selectors: SmallVec::from_vec(vec![Selector {
298 selector: ".button",
299 pseudo_class: None,
300 }]),
301 properties: SmallVec::from_vec(vec![Cow::Borrowed("background-color")]),
302 values: SmallVec::from_vec(vec![Cow::Borrowed("var(--main-color)")]),
303 },
304 ];
305
306 let updated_rules = replace_vars(rules);
308
309 assert_eq!(updated_rules[1].values[0], Cow::Borrowed("blue"));
311 }
312}