css_parser_rs/
lib.rs

1
2/// CSS Lexer Event
3#[derive(Debug)]
4pub enum Event {
5    /// When a selector starts.
6    StartSelector(Vec<String>),
7    /// When a selector ends.
8    EndSelector(Vec<String>),
9    /// When encouter a rule.
10    Rule(String, String),
11    /// When a comment found.
12    Comment(String),
13}
14
15/// CSS Lexer
16pub struct Lexer {
17    content: Vec<char>,
18    data: Vec<Event>,
19    selector_stack: Vec<Vec<String>>,
20}
21
22impl Lexer {
23    /// New CSS Lexer instance.
24    /// 
25    /// `contents` is read css file.
26    pub fn new(contents: &str) -> Self {
27        Self {
28            content: contents.chars().collect::<Vec<char>>(),
29            data: Vec::new(),
30            selector_stack: Vec::new(),
31        }
32    }
33
34    /// Parse the contents.
35    pub fn parse(&mut self) -> &[Event] {
36        while !self.content.is_empty() {
37            self.trim_whitespaces();
38
39            // START SELECTOR
40            // eg, `*` || `html` || `.class` || `#id`
41            if !self.content.is_empty()
42                && (self.content[0] == '*'
43                    || self.content[0] == '.'
44                    || self.content[0] == '#'
45                    || self.content[0].is_alphabetic())
46            {
47                self.parse_start_selector();
48            }
49
50            self.trim_whitespaces();
51
52            // RULES
53            // eg, `justify-content: center;`
54            if !self.content.is_empty() && self.content[0] == '{' {
55                self.parse_rules()
56            }
57
58            self.trim_whitespaces();
59
60            // END SELECTOR
61            // eg, `}`
62            if !self.content.is_empty() && self.content[0] == '}' {
63                self.parse_end_selector();
64            }
65
66            self.trim_whitespaces();
67
68            // COMMENT
69            // eg, `/* comment */`
70            if self.content.len() > 1 && self.content[0] == '/' && self.content[1] == '*' {
71                self.parse_comment();
72            }
73        }
74
75        &self.data
76    }
77
78    /// START SELECTOR
79    ///
80    /// its also implement the multiple selector login.
81    ///
82    /// eg, `*` || `html` || `.class` || `#id`
83    fn parse_start_selector(&mut self) {
84        let mut selectors = Vec::new();
85
86        // multiple selector logic
87        while !self.content.is_empty() && self.content[0] != '{' {
88            let selector = self.take_while(|x| x != ' ' && x != ',' && x != '{');
89
90            self.trim_whitespaces();
91
92            if !self.content.is_empty() && self.content[0] == ',' {
93                self.take_slice(0, 1); // remove `,`
94                self.trim_whitespaces();
95            }
96
97            selectors.push(selector);
98        }
99
100        // remove `{` will occured in `fn parse_rules()`
101        // coz rules are declered inside of `{`
102
103        self.data.push(Event::StartSelector(selectors.clone()));
104        self.selector_stack.push(selectors);
105    }
106
107    /// RULES
108    ///
109    /// TODO: Implement multiple properties.
110    ///
111    /// eg, `justify-content: center;`
112    fn parse_rules(&mut self) {
113        self.take_slice(0, 1); // remove `{`
114
115        self.trim_whitespaces();
116
117        while self.content[0] != '}' {
118            let rule_name = self.take_while(|x| x.is_alphabetic() || x == '-');
119            self.trim_whitespaces();
120
121            if self.content[0] != ':' {
122                eprintln!("ERROR: expecting `:`");
123                break;
124            }
125            self.take_slice(0, 1); // remove `:`
126            self.trim_whitespaces();
127
128            let rule_value = self.take_while(|x| x != ';');
129            self.take_slice(0, 1); // remove `;`
130
131            self.data.push(Event::Rule(rule_name, rule_value));
132
133            self.trim_whitespaces();
134        }
135    }
136
137    /// END SELECTOR
138    /// eg, `}`
139    fn parse_end_selector(&mut self) {
140        self.take_slice(0, 1); // remove `}`
141
142        if let Some(selector) = self.selector_stack.pop() {
143            self.data.push(Event::EndSelector(selector));
144        } else {
145            eprintln!("ERROR: invalid clsoing selector. Need a staring selector.");
146        }
147    }
148
149    /// COMMENT
150    ///
151    /// TODO: Implement multiline comment.
152    ///
153    /// eg, `/* comment */`
154    fn parse_comment(&mut self) {
155        self.take_slice(0, 2); // remove `/*`
156
157        let mut astric_count = 0;
158
159        let comment = self.take_while(|x| {
160            if x == '*' {
161                astric_count += 1;
162            }
163            // end comment
164            else if x == '/' && astric_count == 1 {
165                return false;
166            }
167
168            true
169        });
170
171        self.take_slice(0, 2); // remove `*/`
172
173        // `*` get included in comment
174        // coz, we take `*` as state, THEN we are loking for `/` to stop
175        let comment = comment[..comment.len() - 1].to_string();
176
177        self.data.push(Event::Comment(comment));
178    }
179
180    fn trim_whitespaces(&mut self) -> () {
181        self.take_while(|x| x.is_whitespace());
182    }
183
184    fn take_while<F>(&mut self, predict: F) -> String
185    where
186        F: FnMut(char) -> bool,
187    {
188        self.take_while_from(0, predict)
189    }
190
191    fn take_while_from<F>(&mut self, start: usize, mut predict: F) -> String
192    where
193        F: FnMut(char) -> bool,
194    {
195        let mut i = start;
196
197        while self.content.len() > i && predict(self.content[i]) {
198            i += 1;
199        }
200
201        self.take_slice(start, i)
202    }
203
204    fn take_slice(&mut self, from: usize, to: usize) -> String {
205        let slice = self.content[from..to].iter().collect::<String>();
206        self.content = self.content[to..].to_vec();
207
208        slice
209    }
210}