floem_css_parser/
parser.rs

1use std::{borrow::Cow, iter::Zip};
2
3use smallvec::SmallVec;
4
5use crate::lexer::Token;
6
7/// Parser that turns lexer tokens into `ParserToken` and builds
8/// a `Rule`
9pub 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    // ::whatever:hover
84    //           ^ find this
85    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    // Important to reverse the iterator
246    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        // Setup initial rules with variables
287        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        // Call the function to replace variables
307        let updated_rules = replace_vars(rules);
308
309        // Check that the variable was replaced correctly
310        assert_eq!(updated_rules[1].values[0], Cow::Borrowed("blue"));
311    }
312}