tcss_core/
parser.rs

1//! Parser for TCSS - converts tokens into an Abstract Syntax Tree
2//!
3//! This module implements a recursive descent parser that handles:
4//! - Function definitions (@fn)
5//! - Variable declarations (@var)
6//! - Import statements (@import)
7//! - CSS rules with properties
8//! - Python-style indentation
9
10use crate::ast::{ASTNode, BinaryOp, Expr, Program, Property, Statement};
11use crate::token::{Token, TokenType};
12
13/// Parser for TCSS tokens
14pub struct Parser {
15    tokens: Vec<Token>,
16    current: usize,
17}
18
19impl Parser {
20    /// Create a new parser from a vector of tokens
21    pub fn new(tokens: Vec<Token>) -> Self {
22        Self { tokens, current: 0 }
23    }
24
25    /// Parse the tokens into a Program AST
26    pub fn parse(&mut self) -> Result<Program, String> {
27        let mut program = Program::new();
28
29        while !self.is_at_end() {
30            // Skip newlines at top level
31            if self.match_token(&[TokenType::Newline]) {
32                continue;
33            }
34
35            // Skip indent/dedent at top level (shouldn't happen but be safe)
36            if self.match_token(&[TokenType::Indent, TokenType::Dedent]) {
37                continue;
38            }
39
40            if self.is_at_end() {
41                break;
42            }
43
44            let node = self.parse_top_level()?;
45            program.add_node(node);
46        }
47
48        Ok(program)
49    }
50
51    /// Parse a top-level node
52    fn parse_top_level(&mut self) -> Result<ASTNode, String> {
53        match &self.peek().token_type {
54            TokenType::Fn => self.parse_function(),
55            TokenType::Var => self.parse_variable(),
56            TokenType::Import => self.parse_import(),
57            TokenType::Identifier(_) => self.parse_css_rule(),
58            TokenType::Dot => self.parse_css_rule(),
59            TokenType::At => {
60                // CSS at-rules like @media, @keyframes (treat as CSS rule for now)
61                self.parse_css_rule()
62            }
63            _ => Err(format!(
64                "Unexpected token at {}:{}: {:?}",
65                self.peek().line,
66                self.peek().column,
67                self.peek().token_type
68            )),
69        }
70    }
71
72    /// Parse a function definition: @fn name(params): body
73    fn parse_function(&mut self) -> Result<ASTNode, String> {
74        self.consume(&TokenType::Fn, "Expected @fn")?;
75
76        let name = self.consume_identifier("Expected function name")?;
77
78        self.consume(&TokenType::LeftParen, "Expected '(' after function name")?;
79
80        let mut params = Vec::new();
81        if !self.check(&TokenType::RightParen) {
82            loop {
83                let param = self.consume_identifier("Expected parameter name")?;
84                params.push(param);
85
86                if !self.match_token(&[TokenType::Comma]) {
87                    break;
88                }
89            }
90        }
91
92        self.consume(&TokenType::RightParen, "Expected ')' after parameters")?;
93        self.consume(&TokenType::Colon, "Expected ':' after function signature")?;
94
95        // Parse function body (indented block)
96        let body = self.parse_block()?;
97
98        Ok(ASTNode::Function { name, params, body })
99    }
100
101    /// Parse a variable declaration: @var name: value
102    fn parse_variable(&mut self) -> Result<ASTNode, String> {
103        self.consume(&TokenType::Var, "Expected @var")?;
104
105        let name = self.consume_identifier("Expected variable name")?;
106
107        self.consume(&TokenType::Colon, "Expected ':' after variable name")?;
108
109        let value = self.parse_expression()?;
110
111        Ok(ASTNode::Variable { name, value })
112    }
113
114    /// Parse an import statement: @import 'path'
115    fn parse_import(&mut self) -> Result<ASTNode, String> {
116        self.consume(&TokenType::Import, "Expected @import")?;
117
118        let path = match &self.peek().token_type {
119            TokenType::String(s) => {
120                let path = s.clone();
121                self.advance();
122                path
123            }
124            _ => return Err(format!("Expected string after @import at {}:{}", 
125                self.peek().line, self.peek().column)),
126        };
127
128        Ok(ASTNode::Import { path })
129    }
130
131    /// Parse a CSS rule: selector { properties } or selector: properties (indented)
132    fn parse_css_rule(&mut self) -> Result<ASTNode, String> {
133        let selector = self.parse_selector()?;
134
135        self.consume(&TokenType::Colon, "Expected ':' after selector")?;
136
137        // Parse properties (indented block)
138        let properties = self.parse_properties_block()?;
139
140        Ok(ASTNode::CSSRule { selector, properties })
141    }
142
143    /// Parse a CSS selector (simplified - just collect tokens until colon)
144    fn parse_selector(&mut self) -> Result<String, String> {
145        let mut selector_parts = Vec::new();
146
147        loop {
148            match &self.peek().token_type {
149                TokenType::Identifier(id) => {
150                    selector_parts.push(id.clone());
151                    self.advance();
152                }
153                TokenType::Dot => {
154                    selector_parts.push(".".to_string());
155                    self.advance();
156                }
157                TokenType::At => {
158                    selector_parts.push("@".to_string());
159                    self.advance();
160                }
161                TokenType::Colon => break,
162                _ => {
163                    return Err(format!(
164                        "Unexpected token in selector at {}:{}: {:?}",
165                        self.peek().line,
166                        self.peek().column,
167                        self.peek().token_type
168                    ))
169                }
170            }
171        }
172
173        if selector_parts.is_empty() {
174            return Err("Empty selector".to_string());
175        }
176
177        Ok(selector_parts.join(""))
178    }
179
180    /// Parse a block of statements (function body)
181    fn parse_block(&mut self) -> Result<Vec<Statement>, String> {
182        let mut statements = Vec::new();
183
184        // Expect indent
185        self.consume(&TokenType::Indent, "Expected indented block")?;
186
187        while !self.check(&TokenType::Dedent) && !self.is_at_end() {
188            // Skip newlines
189            if self.match_token(&[TokenType::Newline]) {
190                continue;
191            }
192
193            if self.check(&TokenType::Dedent) {
194                break;
195            }
196
197            let stmt = self.parse_statement()?;
198            statements.push(stmt);
199        }
200
201        self.consume(&TokenType::Dedent, "Expected dedent after block")?;
202
203        Ok(statements)
204    }
205
206    /// Parse a statement within a function
207    fn parse_statement(&mut self) -> Result<Statement, String> {
208        match &self.peek().token_type {
209            TokenType::Return => {
210                self.advance();
211                let expr = self.parse_expression()?;
212                Ok(Statement::Return(expr))
213            }
214            TokenType::Identifier(name) => {
215                let name = name.clone();
216                self.advance();
217
218                if self.match_token(&[TokenType::Assign]) {
219                    let value = self.parse_expression()?;
220                    Ok(Statement::Assignment { name, value })
221                } else {
222                    // Put the identifier back and parse as expression
223                    self.current -= 1;
224                    let expr = self.parse_expression()?;
225                    Ok(Statement::Expression(expr))
226                }
227            }
228            _ => {
229                let expr = self.parse_expression()?;
230                Ok(Statement::Expression(expr))
231            }
232        }
233    }
234
235    /// Parse a block of CSS properties
236    fn parse_properties_block(&mut self) -> Result<Vec<Property>, String> {
237        let mut properties = Vec::new();
238
239        // Expect indent
240        self.consume(&TokenType::Indent, "Expected indented block")?;
241
242        while !self.check(&TokenType::Dedent) && !self.is_at_end() {
243            // Skip newlines
244            if self.match_token(&[TokenType::Newline]) {
245                continue;
246            }
247
248            if self.check(&TokenType::Dedent) {
249                break;
250            }
251
252            let property = self.parse_property()?;
253            properties.push(property);
254        }
255
256        self.consume(&TokenType::Dedent, "Expected dedent after properties")?;
257
258        Ok(properties)
259    }
260
261    /// Parse a CSS property: name: value
262    fn parse_property(&mut self) -> Result<Property, String> {
263        let name = self.consume_identifier("Expected property name")?;
264
265        self.consume(&TokenType::Colon, "Expected ':' after property name")?;
266
267        let value = self.parse_expression()?;
268
269        Ok(Property::new(name, value))
270    }
271
272    /// Parse an expression with operator precedence
273    fn parse_expression(&mut self) -> Result<Expr, String> {
274        self.parse_binary_expression(0)
275    }
276
277    /// Parse binary expressions with precedence climbing
278    fn parse_binary_expression(&mut self, min_precedence: u8) -> Result<Expr, String> {
279        let mut left = self.parse_primary()?;
280
281        loop {
282            let op = match &self.peek().token_type {
283                TokenType::Plus => BinaryOp::Add,
284                TokenType::Minus => BinaryOp::Subtract,
285                TokenType::Star => BinaryOp::Multiply,
286                TokenType::Slash => BinaryOp::Divide,
287                TokenType::Percent => BinaryOp::Modulo,
288                TokenType::Equal => BinaryOp::Equal,
289                TokenType::NotEqual => BinaryOp::NotEqual,
290                TokenType::Less => BinaryOp::Less,
291                TokenType::Greater => BinaryOp::Greater,
292                TokenType::LessEqual => BinaryOp::LessEqual,
293                TokenType::GreaterEqual => BinaryOp::GreaterEqual,
294                _ => break,
295            };
296
297            let precedence = op.precedence();
298            if precedence < min_precedence {
299                break;
300            }
301
302            self.advance(); // consume operator
303
304            let right = self.parse_binary_expression(precedence + 1)?;
305
306            left = Expr::Binary {
307                op,
308                left: Box::new(left),
309                right: Box::new(right),
310            };
311        }
312
313        Ok(left)
314    }
315
316    /// Parse a primary expression (literals, variables, function calls, etc.)
317    fn parse_primary(&mut self) -> Result<Expr, String> {
318        match &self.peek().token_type.clone() {
319            TokenType::Number(n) => {
320                let num = *n;
321                self.advance();
322                Ok(Expr::Number(num))
323            }
324            TokenType::Unit(unit) => {
325                let lexeme = self.peek().lexeme.clone();
326                let unit_str = unit.clone();
327                self.advance();
328
329                // Extract number from lexeme
330                let num_str = lexeme.trim_end_matches(&unit_str);
331                let value = num_str.parse::<f64>()
332                    .map_err(|_| format!("Invalid number in unit: {}", num_str))?;
333
334                Ok(Expr::Unit { value, unit: unit_str })
335            }
336            TokenType::String(s) => {
337                let string = s.clone();
338                self.advance();
339                Ok(Expr::Literal(string))
340            }
341            TokenType::Color(c) => {
342                let color = c.clone();
343                self.advance();
344                Ok(Expr::Color(color))
345            }
346            TokenType::Identifier(id) => {
347                let name = id.clone();
348                self.advance();
349
350                // Check if it's a function call
351                if self.check(&TokenType::LeftParen) {
352                    self.advance(); // consume '('
353
354                    let mut args = Vec::new();
355                    if !self.check(&TokenType::RightParen) {
356                        loop {
357                            let arg = self.parse_expression()?;
358                            args.push(arg);
359
360                            if !self.match_token(&[TokenType::Comma]) {
361                                break;
362                            }
363                        }
364                    }
365
366                    self.consume(&TokenType::RightParen, "Expected ')' after arguments")?;
367
368                    Ok(Expr::FunctionCall { name, args })
369                } else {
370                    Ok(Expr::Variable(name))
371                }
372            }
373            TokenType::LeftParen => {
374                self.advance(); // consume '('
375                let expr = self.parse_expression()?;
376                self.consume(&TokenType::RightParen, "Expected ')' after expression")?;
377                Ok(expr)
378            }
379            TokenType::LeftBracket => {
380                self.advance(); // consume '['
381                let mut elements = Vec::new();
382
383                if !self.check(&TokenType::RightBracket) {
384                    loop {
385                        let elem = self.parse_expression()?;
386                        elements.push(elem);
387
388                        if !self.match_token(&[TokenType::Comma]) {
389                            break;
390                        }
391                    }
392                }
393
394                self.consume(&TokenType::RightBracket, "Expected ']' after array elements")?;
395                Ok(Expr::Array(elements))
396            }
397            TokenType::LeftBrace => {
398                self.advance(); // consume '{'
399                let mut pairs = Vec::new();
400
401                if !self.check(&TokenType::RightBrace) {
402                    loop {
403                        let key = self.consume_identifier("Expected object key")?;
404                        self.consume(&TokenType::Colon, "Expected ':' after object key")?;
405                        let value = self.parse_expression()?;
406                        pairs.push((key, value));
407
408                        if !self.match_token(&[TokenType::Comma]) {
409                            break;
410                        }
411                    }
412                }
413
414                self.consume(&TokenType::RightBrace, "Expected '}' after object")?;
415                Ok(Expr::Object(pairs))
416            }
417            _ => Err(format!(
418                "Unexpected token in expression at {}:{}: {:?}",
419                self.peek().line,
420                self.peek().column,
421                self.peek().token_type
422            )),
423        }
424    }
425
426    // Helper methods for token navigation
427
428    fn is_at_end(&self) -> bool {
429        matches!(self.peek().token_type, TokenType::Eof)
430    }
431
432    fn peek(&self) -> &Token {
433        &self.tokens[self.current]
434    }
435
436    fn advance(&mut self) -> &Token {
437        if !self.is_at_end() {
438            self.current += 1;
439        }
440        &self.tokens[self.current - 1]
441    }
442
443    fn check(&self, token_type: &TokenType) -> bool {
444        if self.is_at_end() {
445            return false;
446        }
447        std::mem::discriminant(&self.peek().token_type) == std::mem::discriminant(token_type)
448    }
449
450    fn match_token(&mut self, types: &[TokenType]) -> bool {
451        for token_type in types {
452            if self.check(token_type) {
453                self.advance();
454                return true;
455            }
456        }
457        false
458    }
459
460    fn consume(&mut self, token_type: &TokenType, message: &str) -> Result<&Token, String> {
461        if self.check(token_type) {
462            Ok(self.advance())
463        } else {
464            Err(format!(
465                "{} at {}:{}, found {:?}",
466                message,
467                self.peek().line,
468                self.peek().column,
469                self.peek().token_type
470            ))
471        }
472    }
473
474    fn consume_identifier(&mut self, message: &str) -> Result<String, String> {
475        match &self.peek().token_type {
476            TokenType::Identifier(id) => {
477                let name = id.clone();
478                self.advance();
479                Ok(name)
480            }
481            _ => Err(format!(
482                "{} at {}:{}, found {:?}",
483                message,
484                self.peek().line,
485                self.peek().column,
486                self.peek().token_type
487            )),
488        }
489    }
490}
491
492#[cfg(test)]
493mod tests {
494    use super::*;
495    use crate::lexer::Lexer;
496
497    #[test]
498    fn test_parse_variable() {
499        let source = "@var primary-color: #3498db";
500        let mut lexer = Lexer::new(source);
501        let tokens = lexer.tokenize().unwrap();
502        let mut parser = Parser::new(tokens);
503        let program = parser.parse().unwrap();
504
505        assert_eq!(program.nodes.len(), 1);
506        match &program.nodes[0] {
507            ASTNode::Variable { name, value } => {
508                assert_eq!(name, "primary-color");
509                assert!(matches!(value, Expr::Color(_)));
510            }
511            _ => panic!("Expected variable node"),
512        }
513    }
514
515    #[test]
516    fn test_parse_function() {
517        let source = "@fn spacing(x):\n    return x * 16px";
518        let mut lexer = Lexer::new(source);
519        let tokens = lexer.tokenize().unwrap();
520        let mut parser = Parser::new(tokens);
521        let program = parser.parse().unwrap();
522
523        assert_eq!(program.nodes.len(), 1);
524        match &program.nodes[0] {
525            ASTNode::Function { name, params, body } => {
526                assert_eq!(name, "spacing");
527                assert_eq!(params.len(), 1);
528                assert_eq!(params[0], "x");
529                assert_eq!(body.len(), 1);
530            }
531            _ => panic!("Expected function node"),
532        }
533    }
534
535    #[test]
536    fn test_parse_expression() {
537        let source = "@var result: 2 * 16px + 8px";
538        let mut lexer = Lexer::new(source);
539        let tokens = lexer.tokenize().unwrap();
540        let mut parser = Parser::new(tokens);
541        let program = parser.parse().unwrap();
542
543        assert_eq!(program.nodes.len(), 1);
544        match &program.nodes[0] {
545            ASTNode::Variable { value, .. } => {
546                assert!(matches!(value, Expr::Binary { .. }));
547            }
548            _ => panic!("Expected variable node"),
549        }
550    }
551
552    #[test]
553    fn test_parse_css_rule() {
554        let source = ".button:\n    padding: 16px\n    color: #fff";
555        let mut lexer = Lexer::new(source);
556        let tokens = lexer.tokenize().unwrap();
557        let mut parser = Parser::new(tokens);
558        let program = parser.parse().unwrap();
559
560        assert_eq!(program.nodes.len(), 1);
561        match &program.nodes[0] {
562            ASTNode::CSSRule { selector, properties } => {
563                assert_eq!(selector, ".button");
564                assert_eq!(properties.len(), 2);
565            }
566            _ => panic!("Expected CSS rule node"),
567        }
568    }
569}
570
571
572
573