Skip to main content

oak_scss/parser/
mod.rs

1/// Element type definitions for the SCSS parser.
2pub mod element_type;
3
4use crate::{
5    language::ScssLanguage,
6    lexer::{ScssLexer, token_type::ScssTokenType},
7    parser::element_type::ScssElementType,
8};
9use oak_core::{
10    errors::OakError,
11    parser::{ParseCache, ParseOutput, Parser, ParserState, parse_with_lexer},
12    source::{Source, TextEdit},
13};
14
15pub(crate) type State<'a, S> = ParserState<'a, ScssLanguage, S>;
16
17/// Parser for the SCSS language.
18pub struct ScssParser<'config> {
19    pub(crate) config: &'config ScssLanguage,
20}
21
22impl<'config> ScssParser<'config> {
23    /// Creates a new `ScssParser` with the given configuration.
24    pub fn new(config: &'config ScssLanguage) -> Self {
25        Self { config }
26    }
27
28    fn parse_node<'a, S: Source + ?Sized>(&self, state: &mut State<'a, S>) -> Result<(), OakError> {
29        match state.peek_kind() {
30            Some(ScssTokenType::Dollar) => self.parse_variable_declaration(state),
31            Some(ScssTokenType::At) => self.parse_at_rule(state),
32            Some(ScssTokenType::Identifier) | Some(ScssTokenType::Dot) | Some(ScssTokenType::Hash) | Some(ScssTokenType::Star) => self.parse_rule(state),
33            Some(ScssTokenType::Comment) => {
34                let cp = state.checkpoint();
35                state.bump();
36                state.finish_at(cp, ScssElementType::Comment);
37                Ok(())
38            }
39            _ => {
40                state.advance();
41                Ok(())
42            }
43        }
44    }
45
46    fn parse_at_rule<'a, S: Source + ?Sized>(&self, state: &mut State<'a, S>) -> Result<(), OakError> {
47        let cp = state.checkpoint();
48        state.expect(ScssTokenType::At)?;
49
50        if let Some(ScssTokenType::Identifier) = state.peek_kind() {
51            let name = state.peek_text().map(|s| s.into_owned());
52            state.bump();
53
54            match name.as_deref() {
55                Some("mixin") => self.parse_mixin(state, cp),
56                Some("include") => self.parse_include(state, cp),
57                Some("function") => self.parse_function(state, cp),
58                Some("return") => self.parse_return(state, cp),
59                Some("import") => self.parse_import(state, cp),
60                Some("if") => self.parse_if(state, cp),
61                Some("for") => self.parse_for(state, cp),
62                Some("each") => self.parse_each(state, cp),
63                Some("while") => self.parse_while(state, cp),
64                _ => {
65                    // Generic at-rule
66                    while state.not_at_end() && !state.at(ScssTokenType::Semicolon) && !state.at(ScssTokenType::LeftBrace) {
67                        state.advance();
68                    }
69                    if state.at(ScssTokenType::LeftBrace) {
70                        self.parse_block(state)?;
71                    }
72                    else {
73                        state.eat(ScssTokenType::Semicolon);
74                    }
75                    state.finish_at(cp, ScssElementType::RuleSet);
76                    Ok(())
77                }
78            }
79        }
80        else {
81            state.finish_at(cp, ScssElementType::Error);
82            Ok(())
83        }
84    }
85
86    fn parse_mixin<'a, S: Source + ?Sized>(&self, state: &mut State<'a, S>, cp: (usize, usize)) -> Result<(), OakError> {
87        state.expect(ScssTokenType::Identifier)?;
88        if state.at(ScssTokenType::LeftParen) {
89            self.parse_parameters(state)?;
90        }
91        self.parse_block(state)?;
92        state.finish_at(cp, ScssElementType::MixinDeclaration);
93        Ok(())
94    }
95
96    fn parse_include<'a, S: Source + ?Sized>(&self, state: &mut State<'a, S>, cp: (usize, usize)) -> Result<(), OakError> {
97        state.expect(ScssTokenType::Identifier)?;
98        if state.at(ScssTokenType::LeftParen) {
99            self.parse_arguments(state)?;
100        }
101        if state.at(ScssTokenType::LeftBrace) {
102            self.parse_block(state)?;
103        }
104        else {
105            state.eat(ScssTokenType::Semicolon);
106        }
107        state.finish_at(cp, ScssElementType::IncludeStatement);
108        Ok(())
109    }
110
111    fn parse_function<'a, S: Source + ?Sized>(&self, state: &mut State<'a, S>, cp: (usize, usize)) -> Result<(), OakError> {
112        state.expect(ScssTokenType::Identifier)?;
113        self.parse_parameters(state)?;
114        self.parse_block(state)?;
115        state.finish_at(cp, ScssElementType::FunctionDeclaration);
116        Ok(())
117    }
118
119    fn parse_import<'a, S: Source + ?Sized>(&self, state: &mut State<'a, S>, cp: (usize, usize)) -> Result<(), OakError> {
120        while state.not_at_end() && !state.at(ScssTokenType::Semicolon) {
121            state.advance();
122        }
123        state.eat(ScssTokenType::Semicolon);
124        state.finish_at(cp, ScssElementType::ImportStatement);
125        Ok(())
126    }
127
128    fn parse_if<'a, S: Source + ?Sized>(&self, state: &mut State<'a, S>, cp: (usize, usize)) -> Result<(), OakError> {
129        while state.not_at_end() && !state.at(ScssTokenType::LeftBrace) {
130            state.advance();
131        }
132        self.parse_block(state)?;
133        // Handle else if / else
134        state.finish_at(cp, ScssElementType::IfStatement);
135        Ok(())
136    }
137
138    fn parse_for<'a, S: Source + ?Sized>(&self, state: &mut State<'a, S>, cp: (usize, usize)) -> Result<(), OakError> {
139        while state.not_at_end() && !state.at(ScssTokenType::LeftBrace) {
140            state.advance();
141        }
142        self.parse_block(state)?;
143        state.finish_at(cp, ScssElementType::ForStatement);
144        Ok(())
145    }
146
147    fn parse_each<'a, S: Source + ?Sized>(&self, state: &mut State<'a, S>, cp: (usize, usize)) -> Result<(), OakError> {
148        while state.not_at_end() && !state.at(ScssTokenType::LeftBrace) {
149            state.advance();
150        }
151        self.parse_block(state)?;
152        state.finish_at(cp, ScssElementType::EachStatement);
153        Ok(())
154    }
155
156    fn parse_while<'a, S: Source + ?Sized>(&self, state: &mut State<'a, S>, cp: (usize, usize)) -> Result<(), OakError> {
157        while state.not_at_end() && !state.at(ScssTokenType::LeftBrace) {
158            state.advance();
159        }
160        self.parse_block(state)?;
161        state.finish_at(cp, ScssElementType::WhileStatement);
162        Ok(())
163    }
164
165    fn parse_parameters<'a, S: Source + ?Sized>(&self, state: &mut State<'a, S>) -> Result<(), OakError> {
166        state.expect(ScssTokenType::LeftParen)?;
167        while state.not_at_end() && !state.at(ScssTokenType::RightParen) {
168            state.advance();
169        }
170        state.expect(ScssTokenType::RightParen)?;
171        Ok(())
172    }
173
174    fn parse_arguments<'a, S: Source + ?Sized>(&self, state: &mut State<'a, S>) -> Result<(), OakError> {
175        state.expect(ScssTokenType::LeftParen)?;
176        while state.not_at_end() && !state.at(ScssTokenType::RightParen) {
177            state.advance();
178        }
179        state.expect(ScssTokenType::RightParen)?;
180        Ok(())
181    }
182
183    fn parse_block<'a, S: Source + ?Sized>(&self, state: &mut State<'a, S>) -> Result<(), OakError> {
184        let cp = state.checkpoint();
185        state.expect(ScssTokenType::LeftBrace)?;
186        while state.not_at_end() && !state.at(ScssTokenType::RightBrace) {
187            if state.at(ScssTokenType::Identifier) && self.is_declaration(state) {
188                self.parse_declaration(state)?;
189            }
190            else {
191                self.parse_node(state)?;
192            }
193        }
194        state.expect(ScssTokenType::RightBrace)?;
195        state.finish_at(cp, ScssElementType::Block);
196        Ok(())
197    }
198
199    fn is_declaration<'a, S: Source + ?Sized>(&self, state: &State<'a, S>) -> bool {
200        // Simple lookahead for colon
201        state.peek_kind_at(1) == Some(ScssTokenType::Colon)
202    }
203
204    fn parse_declaration<'a, S: Source + ?Sized>(&self, state: &mut State<'a, S>) -> Result<(), OakError> {
205        let cp = state.checkpoint();
206        let prop_cp = state.checkpoint();
207        state.expect(ScssTokenType::Identifier)?;
208        state.finish_at(prop_cp, ScssElementType::Property);
209
210        state.expect(ScssTokenType::Colon)?;
211
212        let val_cp = state.checkpoint();
213        while state.not_at_end() && !state.at(ScssTokenType::Semicolon) && !state.at(ScssTokenType::RightBrace) {
214            state.advance();
215        }
216        state.finish_at(val_cp, ScssElementType::ValueNode);
217
218        state.eat(ScssTokenType::Semicolon);
219        state.finish_at(cp, ScssElementType::Declaration);
220        Ok(())
221    }
222
223    fn parse_variable_declaration<'a, S: Source + ?Sized>(&self, state: &mut State<'a, S>) -> Result<(), OakError> {
224        let cp = state.checkpoint();
225        state.expect(ScssTokenType::Dollar)?;
226        state.expect(ScssTokenType::Identifier)?;
227        state.expect(ScssTokenType::Colon)?;
228        while state.not_at_end() && !state.at(ScssTokenType::Semicolon) {
229            state.advance();
230        }
231        state.eat(ScssTokenType::Semicolon);
232        state.finish_at(cp, ScssElementType::VariableDeclaration);
233        Ok(())
234    }
235
236    fn parse_return<'a, S: Source + ?Sized>(&self, state: &mut State<'a, S>, cp: (usize, usize)) -> Result<(), OakError> {
237        let val_cp = state.checkpoint();
238        while state.not_at_end() && !state.at(ScssTokenType::Semicolon) {
239            state.advance();
240        }
241        state.finish_at(val_cp, ScssElementType::ValueNode);
242        state.eat(ScssTokenType::Semicolon);
243        state.finish_at(cp, ScssElementType::ReturnStatement);
244        Ok(())
245    }
246
247    fn parse_rule<'a, S: Source + ?Sized>(&self, state: &mut State<'a, S>) -> Result<(), OakError> {
248        let cp = state.checkpoint();
249        // Parse selector
250        let sel_cp = state.checkpoint();
251        while state.not_at_end() && !state.at(ScssTokenType::LeftBrace) {
252            state.advance();
253        }
254        state.finish_at(sel_cp, ScssElementType::Selector);
255
256        if state.at(ScssTokenType::LeftBrace) {
257            self.parse_block(state)?;
258        }
259
260        state.finish_at(cp, ScssElementType::RuleSet);
261        Ok(())
262    }
263}
264
265impl<'config> Parser<ScssLanguage> for ScssParser<'config> {
266    fn parse<'a, S: Source + ?Sized>(&self, text: &'a S, edits: &[TextEdit], cache: &'a mut impl ParseCache<ScssLanguage>) -> ParseOutput<'a, ScssLanguage> {
267        let lexer = ScssLexer::new(&self.config);
268        parse_with_lexer(&lexer, text, edits, cache, |state| {
269            let checkpoint = state.checkpoint();
270
271            while state.not_at_end() {
272                self.parse_node(state)?;
273            }
274
275            Ok(state.finish_at(checkpoint, ScssElementType::SourceFile))
276        })
277    }
278}