Skip to main content

oak_css/parser/
mod.rs

1#![doc = include_str!("readme.md")]
2pub mod element_type;
3use crate::{
4    language::CssLanguage,
5    lexer::{CssLexer, CssTokenType},
6};
7pub use element_type::CssElementType;
8use oak_core::{
9    GreenNode, OakError, TextEdit,
10    parser::{ParseCache, ParseOutput, Parser, ParserState, parse_with_lexer},
11    source::Source,
12};
13
14pub(crate) type State<'a, S> = ParserState<'a, CssLanguage, S>;
15
16/// Parser for the CSS language.
17pub struct CssParser<'config> {
18    /// Language configuration.
19    pub(crate) _config: &'config CssLanguage,
20}
21
22impl<'config> CssParser<'config> {
23    /// Creates a new `CssParser` with the given language configuration.
24    pub fn new(config: &'config CssLanguage) -> Self {
25        Self { _config: config }
26    }
27}
28
29impl<'config> Parser<CssLanguage> for CssParser<'config> {
30    /// Parses the CSS source code into a green tree.
31    fn parse<'a, S: Source + ?Sized>(&self, text: &'a S, edits: &[TextEdit], cache: &'a mut impl ParseCache<CssLanguage>) -> ParseOutput<'a, CssLanguage> {
32        let lexer = CssLexer::new(self._config);
33        parse_with_lexer(&lexer, text, edits, cache, |state| self.parse_root_internal(state))
34    }
35}
36
37impl<'config> CssParser<'config> {
38    /// Internal entry point for parsing the CSS root.
39    pub(crate) fn parse_root_internal<'a, S: Source + ?Sized>(&self, state: &mut State<'a, S>) -> Result<&'a GreenNode<'a, CssLanguage>, OakError> {
40        let cp = state.checkpoint();
41
42        while state.not_at_end() {
43            if state.at(CssTokenType::AtRule) || state.at(CssTokenType::AtImport) || state.at(CssTokenType::AtMedia) { self.parse_at_rule(state)? } else { self.parse_ruleset(state)? }
44        }
45
46        Ok(state.finish_at(cp, CssElementType::SourceFile))
47    }
48
49    /// Parses a CSS at-rule (e.g., `@import`, `@media`).
50    fn parse_at_rule<'a, S: Source + ?Sized>(&self, state: &mut State<'a, S>) -> Result<(), OakError> {
51        let cp = state.checkpoint();
52        state.bump(); // Consume the at-keyword
53
54        while state.not_at_end() && !state.at(CssTokenType::Semicolon) && !state.at(CssTokenType::LeftBrace) {
55            state.bump()
56        }
57
58        if state.at(CssTokenType::LeftBrace) {
59            state.expect(CssTokenType::LeftBrace).ok();
60            while state.not_at_end() && !state.at(CssTokenType::RightBrace) {
61                self.parse_ruleset(state)?
62            }
63            state.expect(CssTokenType::RightBrace).ok();
64        }
65        else if state.at(CssTokenType::Semicolon) {
66            state.expect(CssTokenType::Semicolon).ok();
67        }
68
69        state.finish_at(cp, CssElementType::AtRule);
70        Ok(())
71    }
72
73    /// Parses a CSS rule set (selector + declaration block).
74    fn parse_ruleset<'a, S: Source + ?Sized>(&self, state: &mut State<'a, S>) -> Result<(), OakError> {
75        let cp = state.checkpoint();
76
77        // Parse selector(s)
78        self.parse_selectors(state)?;
79
80        // Parse declaration block
81        let cp_block = state.checkpoint();
82        state.expect(CssTokenType::LeftBrace).ok();
83        while state.not_at_end() && !state.at(CssTokenType::RightBrace) {
84            self.parse_declaration(state)?;
85            if state.at(CssTokenType::Semicolon) {
86                state.expect(CssTokenType::Semicolon).ok();
87            }
88            else if !state.at(CssTokenType::RightBrace) {
89                // Potential error, but we try to continue
90                break;
91            }
92        }
93        state.expect(CssTokenType::RightBrace).ok();
94        state.finish_at(cp_block, CssElementType::DeclarationBlock);
95
96        state.finish_at(cp, CssElementType::RuleSet);
97        Ok(())
98    }
99
100    /// Parses CSS selectors.
101    fn parse_selectors<'a, S: Source + ?Sized>(&self, state: &mut State<'a, S>) -> Result<(), OakError> {
102        let cp = state.checkpoint();
103        while state.not_at_end() && !state.at(CssTokenType::LeftBrace) {
104            state.bump()
105        }
106        state.finish_at(cp, CssElementType::SelectorList);
107        Ok(())
108    }
109
110    /// Parses a CSS declaration (property: value).
111    fn parse_declaration<'a, S: Source + ?Sized>(&self, state: &mut State<'a, S>) -> Result<(), OakError> {
112        let cp = state.checkpoint();
113
114        // Property
115        let cp_prop = state.checkpoint();
116        while state.not_at_end() && !state.at(CssTokenType::Colon) && !state.at(CssTokenType::Semicolon) && !state.at(CssTokenType::RightBrace) {
117            state.bump()
118        }
119        state.finish_at(cp_prop, CssElementType::Property);
120
121        if state.at(CssTokenType::Colon) {
122            state.expect(CssTokenType::Colon).ok();
123
124            // Value
125            let cp_val = state.checkpoint();
126            while state.not_at_end() && !state.at(CssTokenType::Semicolon) && !state.at(CssTokenType::RightBrace) {
127                state.bump()
128            }
129            state.finish_at(cp_val, CssElementType::Value);
130        }
131
132        state.finish_at(cp, CssElementType::Declaration);
133        Ok(())
134    }
135}