ori_core/style/
parse.rs

1use std::str::FromStr;
2
3use ori_graphics::Color;
4use pest::{error::Error, iterators::Pair, Parser};
5use pest_derive::Parser;
6
7use crate::{
8    StyleAttribute, StyleAttributeKey, StyleAttributeValue, StyleClasses, StyleElement, StyleRule,
9    StyleSelector, StyleSelectors, StyleStates, StyleTransition, Stylesheet, Unit,
10};
11
12#[derive(Parser)]
13#[grammar = "style/grammar.pest"]
14pub struct StyleParser;
15
16pub type StyleParseError = Error<Rule>;
17pub type SelectorParseError = Error<Rule>;
18
19fn parse_number(pair: Pair<'_, Rule>) -> f32 {
20    pair.as_str().parse().unwrap()
21}
22
23fn parse_unit(pair: Pair<'_, Rule>) -> Unit {
24    let mut pairs = pair.into_inner();
25
26    let number_pair = pairs.next().unwrap();
27    let number = parse_number(number_pair);
28
29    match pairs.next().as_ref().map(Pair::as_rule) {
30        Some(Rule::Px) | None => Unit::Px(number),
31        Some(Rule::Pt) => Unit::Pt(number),
32        Some(Rule::Pc) => Unit::Pc(number),
33        Some(Rule::Vw) => Unit::Vw(number),
34        Some(Rule::Vh) => Unit::Vh(number),
35        Some(Rule::Em) => Unit::Em(number),
36        _ => unreachable!(),
37    }
38}
39
40fn parse_color(pair: Pair<'_, Rule>) -> Color {
41    let pair = pair.into_inner().next().unwrap();
42
43    match pair.as_rule() {
44        Rule::HexColor => Color::hex(pair.as_str()),
45        Rule::RgbColor => {
46            let mut iter = pair.into_inner();
47
48            let r = iter.next().unwrap().as_str().parse().unwrap();
49            let g = iter.next().unwrap().as_str().parse().unwrap();
50            let b = iter.next().unwrap().as_str().parse().unwrap();
51
52            Color::rgb(r, g, b)
53        }
54        Rule::RgbaColor => {
55            let mut iter = pair.into_inner();
56
57            let r = iter.next().unwrap().as_str().parse().unwrap();
58            let g = iter.next().unwrap().as_str().parse().unwrap();
59            let b = iter.next().unwrap().as_str().parse().unwrap();
60            let a = iter.next().unwrap().as_str().parse().unwrap();
61
62            Color::rgba(r, g, b, a)
63        }
64        _ => unreachable!(),
65    }
66}
67
68fn parse_transition(pair: Option<Pair<'_, Rule>>) -> Option<StyleTransition> {
69    Some(StyleTransition::new(parse_number(pair?)))
70}
71
72fn parse_value(pair: Pair<'_, Rule>) -> (StyleAttributeValue, Option<StyleTransition>) {
73    let mut pairs = pair.into_inner();
74
75    let value = pairs.next().unwrap();
76    let transition = parse_transition(pairs.next());
77    let value = match value.as_rule() {
78        Rule::String => {
79            let value = &value.as_str()[1..value.as_str().len() - 1];
80            StyleAttributeValue::String(value.to_string())
81        }
82        Rule::Enum => StyleAttributeValue::Enum(value.as_str().to_string()),
83        Rule::Unit => StyleAttributeValue::Unit(parse_unit(value)),
84        Rule::Color => StyleAttributeValue::Color(parse_color(value)),
85        _ => unreachable!(),
86    };
87
88    (value, transition)
89}
90
91fn parse_class(pair: Pair<'_, Rule>) -> String {
92    pair.into_inner().as_str().to_string()
93}
94
95fn parse_state(pair: Pair<'_, Rule>) -> String {
96    pair.into_inner().as_str().to_string()
97}
98
99fn parse_selector(pair: Pair<'_, Rule>) -> StyleSelector {
100    let mut pairs = pair.into_inner();
101
102    let mut element_pairs = pairs.next().unwrap().into_inner();
103    let element_pair = element_pairs.next().unwrap();
104    let element = match element_pair.as_rule() {
105        Rule::Identifier => Some(StyleElement::new(element_pair.as_str())),
106        Rule::Wildcard => None,
107        _ => unreachable!(),
108    };
109
110    let mut selector = StyleSelector {
111        element,
112        classes: StyleClasses::new(),
113        states: StyleStates::new(),
114    };
115
116    for pair in element_pairs {
117        match pair.as_rule() {
118            Rule::State => {
119                selector.states.push(parse_state(pair));
120            }
121            _ => unreachable!(),
122        }
123    }
124
125    for pair in pairs {
126        match pair.as_rule() {
127            Rule::Class => {
128                selector.classes.push(parse_class(pair));
129            }
130            _ => unreachable!(),
131        }
132    }
133
134    selector
135}
136
137fn parse_selectors(pair: Pair<'_, Rule>) -> StyleSelectors {
138    let mut selectors = StyleSelectors::new();
139
140    for pair in pair.into_inner() {
141        match pair.as_rule() {
142            Rule::Selector => {
143                selectors.push(parse_selector(pair));
144            }
145            _ => unreachable!(),
146        }
147    }
148
149    selectors
150}
151
152fn parse_attribute(pair: Pair<'_, Rule>) -> StyleAttribute {
153    let mut iter = pair.into_inner();
154
155    let key = iter.next().unwrap().as_str();
156    let (value, transition) = parse_value(iter.next().unwrap());
157
158    StyleAttribute {
159        key: StyleAttributeKey::new(key),
160        value,
161        transition,
162    }
163}
164
165fn parse_style_rule(pair: Pair<'_, Rule>) -> StyleRule {
166    let mut iter = pair.into_inner();
167
168    let selector = parse_selectors(iter.next().unwrap());
169    let mut rule = StyleRule::new(selector);
170
171    for pair in iter {
172        match pair.as_rule() {
173            Rule::Attribute => {
174                rule.attributes.add(parse_attribute(pair));
175            }
176            _ => unreachable!(),
177        }
178    }
179
180    rule
181}
182
183fn parse_style(input: &str) -> Result<Stylesheet, Error<Rule>> {
184    let pairs = StyleParser::parse(Rule::Style, input)?.next().unwrap();
185    let mut style = Stylesheet::new();
186
187    for pair in pairs.into_inner() {
188        match pair.as_rule() {
189            Rule::StyleRule => {
190                style.add_rule(parse_style_rule(pair));
191            }
192            Rule::EOI => break,
193            _ => unreachable!(),
194        }
195    }
196
197    Ok(style)
198}
199
200impl FromStr for Stylesheet {
201    type Err = StyleParseError;
202
203    fn from_str(input: &str) -> Result<Self, Self::Err> {
204        parse_style(input)
205    }
206}
207
208impl FromStr for StyleSelectors {
209    type Err = SelectorParseError;
210
211    fn from_str(input: &str) -> Result<Self, Self::Err> {
212        let pair = StyleParser::parse(Rule::Selector, input)?.next().unwrap();
213        Ok(parse_selectors(pair))
214    }
215}
216
217#[cfg(test)]
218mod tests {
219    use super::*;
220
221    #[test]
222    fn it_works() {
223        parse_style(include_str!("test.css")).unwrap();
224    }
225}