Skip to main content

lumen_engine/expr/
parser.rs

1use std::ops::Range;
2
3use crate::{
4    error::ExpressionError,
5    expr::ast::{
6        BinaryOp, BuiltinFn, ExprNode, Expression, ExpressionId, ExpressionReference,
7        ExpressionValue, GlobalVar, PropertyPath, UnaryOp,
8    },
9    node::NodeId,
10};
11
12#[derive(Debug, Clone, PartialEq)]
13enum TokenKind {
14    Identifier(String),
15    Number(f64),
16    String(String),
17    LParen,
18    RParen,
19    Comma,
20    Dot,
21    Plus,
22    Minus,
23    Star,
24    Slash,
25    Percent,
26    Bang,
27    Gt,
28    Lt,
29    Gte,
30    Lte,
31    EqEq,
32    Neq,
33    AndAnd,
34    OrOr,
35    Eof,
36}
37
38#[derive(Debug, Clone, PartialEq)]
39struct Token {
40    kind: TokenKind,
41    span: Range<usize>,
42}
43
44struct Lexer<'a> {
45    source: &'a str,
46    position: usize,
47}
48
49impl<'a> Lexer<'a> {
50    fn new(source: &'a str) -> Self {
51        Self {
52            source,
53            position: 0,
54        }
55    }
56
57    fn next_token(&mut self) -> Result<Token, ExpressionError> {
58        self.skip_whitespace();
59        let start = self.position;
60        let Some(ch) = self.peek_char() else {
61            return Ok(Token {
62                kind: TokenKind::Eof,
63                span: start..start,
64            });
65        };
66
67        let kind = match ch {
68            '(' => {
69                self.bump_char();
70                TokenKind::LParen
71            }
72            ')' => {
73                self.bump_char();
74                TokenKind::RParen
75            }
76            ',' => {
77                self.bump_char();
78                TokenKind::Comma
79            }
80            '.' => {
81                self.bump_char();
82                TokenKind::Dot
83            }
84            '+' => {
85                self.bump_char();
86                TokenKind::Plus
87            }
88            '-' => {
89                self.bump_char();
90                TokenKind::Minus
91            }
92            '*' => {
93                self.bump_char();
94                TokenKind::Star
95            }
96            '/' => {
97                self.bump_char();
98                TokenKind::Slash
99            }
100            '%' => {
101                self.bump_char();
102                TokenKind::Percent
103            }
104            '!' => {
105                self.bump_char();
106                if self.peek_char() == Some('=') {
107                    self.bump_char();
108                    TokenKind::Neq
109                } else {
110                    TokenKind::Bang
111                }
112            }
113            '=' => {
114                self.bump_char();
115                if self.peek_char() == Some('=') {
116                    self.bump_char();
117                    TokenKind::EqEq
118                } else {
119                    return Err(ExpressionError::Parse {
120                        path: None,
121                        details: "unexpected `=`; use `==` for equality".to_string(),
122                    });
123                }
124            }
125            '>' => {
126                self.bump_char();
127                if self.peek_char() == Some('=') {
128                    self.bump_char();
129                    TokenKind::Gte
130                } else {
131                    TokenKind::Gt
132                }
133            }
134            '<' => {
135                self.bump_char();
136                if self.peek_char() == Some('=') {
137                    self.bump_char();
138                    TokenKind::Lte
139                } else {
140                    TokenKind::Lt
141                }
142            }
143            '&' => {
144                self.bump_char();
145                if self.peek_char() == Some('&') {
146                    self.bump_char();
147                    TokenKind::AndAnd
148                } else {
149                    return Err(ExpressionError::Parse {
150                        path: None,
151                        details: "unexpected `&`; use `&&`".to_string(),
152                    });
153                }
154            }
155            '|' => {
156                self.bump_char();
157                if self.peek_char() == Some('|') {
158                    self.bump_char();
159                    TokenKind::OrOr
160                } else {
161                    return Err(ExpressionError::Parse {
162                        path: None,
163                        details: "unexpected `|`; use `||`".to_string(),
164                    });
165                }
166            }
167            '\'' | '"' => TokenKind::String(self.read_string(ch)?),
168            c if c.is_ascii_digit() => TokenKind::Number(self.read_number()?),
169            c if is_ident_start(c) => TokenKind::Identifier(self.read_identifier()),
170            other => {
171                return Err(ExpressionError::Parse {
172                    path: None,
173                    details: format!("unexpected character `{other}` at byte {start}"),
174                });
175            }
176        };
177
178        Ok(Token {
179            kind,
180            span: start..self.position,
181        })
182    }
183
184    fn skip_whitespace(&mut self) {
185        while matches!(self.peek_char(), Some(ch) if ch.is_whitespace()) {
186            self.bump_char();
187        }
188    }
189
190    fn read_string(&mut self, quote: char) -> Result<String, ExpressionError> {
191        let start = self.position;
192        self.bump_char();
193        let mut output = String::new();
194        loop {
195            let Some(ch) = self.peek_char() else {
196                return Err(ExpressionError::Parse {
197                    path: None,
198                    details: format!("unterminated string literal starting at byte {start}"),
199                });
200            };
201            self.bump_char();
202            match ch {
203                c if c == quote => return Ok(output),
204                '\\' => {
205                    let Some(escaped) = self.peek_char() else {
206                        return Err(ExpressionError::Parse {
207                            path: None,
208                            details: "unterminated escape sequence".to_string(),
209                        });
210                    };
211                    self.bump_char();
212                    let decoded = match escaped {
213                        '\\' => '\\',
214                        '\'' => '\'',
215                        '"' => '"',
216                        'n' => '\n',
217                        't' => '\t',
218                        'r' => '\r',
219                        other => other,
220                    };
221                    output.push(decoded);
222                }
223                other => output.push(other),
224            }
225        }
226    }
227
228    fn read_number(&mut self) -> Result<f64, ExpressionError> {
229        let start = self.position;
230        while matches!(self.peek_char(), Some(ch) if ch.is_ascii_digit()) {
231            self.bump_char();
232        }
233        if self.peek_char() == Some('.') {
234            self.bump_char();
235            while matches!(self.peek_char(), Some(ch) if ch.is_ascii_digit()) {
236                self.bump_char();
237            }
238        }
239
240        self.source[start..self.position]
241            .parse::<f64>()
242            .map_err(|error| ExpressionError::Parse {
243                path: None,
244                details: format!("invalid number at byte {start}: {error}"),
245            })
246    }
247
248    fn read_identifier(&mut self) -> String {
249        let start = self.position;
250        self.bump_char();
251        while matches!(self.peek_char(), Some(ch) if is_ident_continue(ch)) {
252            self.bump_char();
253        }
254        self.source[start..self.position].to_string()
255    }
256
257    fn peek_char(&self) -> Option<char> {
258        self.source[self.position..].chars().next()
259    }
260
261    fn bump_char(&mut self) -> Option<char> {
262        let ch = self.peek_char()?;
263        self.position += ch.len_utf8();
264        Some(ch)
265    }
266}
267
268fn is_ident_start(ch: char) -> bool {
269    ch == '_' || ch.is_ascii_alphabetic()
270}
271
272fn is_ident_continue(ch: char) -> bool {
273    is_ident_start(ch) || ch.is_ascii_digit()
274}
275
276pub fn parse_expression(source: &str) -> Result<Expression, ExpressionError> {
277    let mut parser = Parser::new(source);
278    parser.parse()
279}
280
281struct Parser<'a> {
282    source: &'a str,
283    tokens: Vec<Token>,
284    position: usize,
285    references: Vec<ExpressionReference>,
286}
287
288impl<'a> Parser<'a> {
289    fn new(source: &'a str) -> Self {
290        Self {
291            source,
292            tokens: Vec::new(),
293            position: 0,
294            references: Vec::new(),
295        }
296    }
297
298    fn parse(&mut self) -> Result<Expression, ExpressionError> {
299        self.tokenize()?;
300        let ast = self.parse_or()?;
301        self.expect(TokenExpectation::Eof)?;
302        Ok(Expression {
303            id: ExpressionId(0),
304            ast,
305            references: self.references.clone(),
306            source: self.source.to_string(),
307        })
308    }
309
310    fn tokenize(&mut self) -> Result<(), ExpressionError> {
311        let mut lexer = Lexer::new(self.source);
312        loop {
313            let token = lexer.next_token()?;
314            let is_eof = matches!(token.kind, TokenKind::Eof);
315            self.tokens.push(token);
316            if is_eof {
317                break;
318            }
319        }
320        Ok(())
321    }
322
323    fn parse_or(&mut self) -> Result<ExprNode, ExpressionError> {
324        let mut node = self.parse_and()?;
325        while self.match_token(|kind| matches!(kind, TokenKind::OrOr)) {
326            let rhs = self.parse_and()?;
327            node = ExprNode::Binary(Box::new(node), BinaryOp::Or, Box::new(rhs));
328        }
329        Ok(node)
330    }
331
332    fn parse_and(&mut self) -> Result<ExprNode, ExpressionError> {
333        let mut node = self.parse_equality()?;
334        while self.match_token(|kind| matches!(kind, TokenKind::AndAnd)) {
335            let rhs = self.parse_equality()?;
336            node = ExprNode::Binary(Box::new(node), BinaryOp::And, Box::new(rhs));
337        }
338        Ok(node)
339    }
340
341    fn parse_equality(&mut self) -> Result<ExprNode, ExpressionError> {
342        let mut node = self.parse_comparison()?;
343        loop {
344            let op = if self.match_token(|kind| matches!(kind, TokenKind::EqEq)) {
345                Some(BinaryOp::Eq)
346            } else if self.match_token(|kind| matches!(kind, TokenKind::Neq)) {
347                Some(BinaryOp::Neq)
348            } else {
349                None
350            };
351            let Some(op) = op else {
352                break;
353            };
354            let rhs = self.parse_comparison()?;
355            node = ExprNode::Binary(Box::new(node), op, Box::new(rhs));
356        }
357        Ok(node)
358    }
359
360    fn parse_comparison(&mut self) -> Result<ExprNode, ExpressionError> {
361        let mut node = self.parse_term()?;
362        loop {
363            let op = if self.match_token(|kind| matches!(kind, TokenKind::Gte)) {
364                Some(BinaryOp::Gte)
365            } else if self.match_token(|kind| matches!(kind, TokenKind::Lte)) {
366                Some(BinaryOp::Lte)
367            } else if self.match_token(|kind| matches!(kind, TokenKind::Gt)) {
368                Some(BinaryOp::Gt)
369            } else if self.match_token(|kind| matches!(kind, TokenKind::Lt)) {
370                Some(BinaryOp::Lt)
371            } else {
372                None
373            };
374            let Some(op) = op else {
375                break;
376            };
377            let rhs = self.parse_term()?;
378            node = ExprNode::Binary(Box::new(node), op, Box::new(rhs));
379        }
380        Ok(node)
381    }
382
383    fn parse_term(&mut self) -> Result<ExprNode, ExpressionError> {
384        let mut node = self.parse_factor()?;
385        loop {
386            let op = if self.match_token(|kind| matches!(kind, TokenKind::Plus)) {
387                Some(BinaryOp::Add)
388            } else if self.match_token(|kind| matches!(kind, TokenKind::Minus)) {
389                Some(BinaryOp::Sub)
390            } else {
391                None
392            };
393            let Some(op) = op else {
394                break;
395            };
396            let rhs = self.parse_factor()?;
397            node = ExprNode::Binary(Box::new(node), op, Box::new(rhs));
398        }
399        Ok(node)
400    }
401
402    fn parse_factor(&mut self) -> Result<ExprNode, ExpressionError> {
403        let mut node = self.parse_unary()?;
404        loop {
405            let op = if self.match_token(|kind| matches!(kind, TokenKind::Star)) {
406                Some(BinaryOp::Mul)
407            } else if self.match_token(|kind| matches!(kind, TokenKind::Slash)) {
408                Some(BinaryOp::Div)
409            } else if self.match_token(|kind| matches!(kind, TokenKind::Percent)) {
410                Some(BinaryOp::Mod)
411            } else {
412                None
413            };
414            let Some(op) = op else {
415                break;
416            };
417            let rhs = self.parse_unary()?;
418            node = ExprNode::Binary(Box::new(node), op, Box::new(rhs));
419        }
420        Ok(node)
421    }
422
423    fn parse_unary(&mut self) -> Result<ExprNode, ExpressionError> {
424        if self.match_token(|kind| matches!(kind, TokenKind::Bang)) {
425            return Ok(ExprNode::Unary(UnaryOp::Not, Box::new(self.parse_unary()?)));
426        }
427        if self.match_token(|kind| matches!(kind, TokenKind::Minus)) {
428            return Ok(ExprNode::Unary(UnaryOp::Neg, Box::new(self.parse_unary()?)));
429        }
430        self.parse_primary()
431    }
432
433    fn parse_primary(&mut self) -> Result<ExprNode, ExpressionError> {
434        let token = self.peek().cloned().ok_or(ExpressionError::Parse {
435            path: None,
436            details: "unexpected end of expression".to_string(),
437        })?;
438        match token.kind {
439            TokenKind::Number(value) => {
440                self.position += 1;
441                Ok(ExprNode::Literal(ExpressionValue::Number(value)))
442            }
443            TokenKind::String(value) => {
444                self.position += 1;
445                Ok(ExprNode::Literal(ExpressionValue::String(value)))
446            }
447            TokenKind::Identifier(identifier) => {
448                self.position += 1;
449                self.parse_identifier(identifier)
450            }
451            TokenKind::LParen => {
452                self.position += 1;
453                let expr = self.parse_or()?;
454                self.expect(TokenExpectation::RParen)?;
455                Ok(expr)
456            }
457            _ => Err(ExpressionError::Parse {
458                path: None,
459                details: format!("expected expression at byte {}", token.span.start),
460            }),
461        }
462    }
463
464    fn parse_identifier(&mut self, identifier: String) -> Result<ExprNode, ExpressionError> {
465        let mut dotted_segments = vec![identifier.clone()];
466        while self.match_token(|kind| matches!(kind, TokenKind::Dot)) {
467            let token = self.peek().cloned().ok_or(ExpressionError::Parse {
468                path: None,
469                details: "expected identifier after `.`".to_string(),
470            })?;
471            match token.kind {
472                TokenKind::Identifier(next_segment) => {
473                    self.position += 1;
474                    dotted_segments.push(next_segment);
475                }
476                _ => {
477                    return Err(ExpressionError::Parse {
478                        path: None,
479                        details: "expected identifier after `.`".to_string(),
480                    });
481                }
482            }
483        }
484        if dotted_segments.len() > 1 {
485            self.references.push(ExpressionReference::SymbolicPath {
486                segments: dotted_segments.clone(),
487            });
488            return Ok(ExprNode::SymbolicPath(dotted_segments));
489        }
490
491        if identifier == "true" {
492            return Ok(ExprNode::Literal(ExpressionValue::Boolean(true)));
493        }
494        if identifier == "false" {
495            return Ok(ExprNode::Literal(ExpressionValue::Boolean(false)));
496        }
497
498        if self.match_token(|kind| matches!(kind, TokenKind::LParen)) {
499            let mut args = Vec::new();
500            if !self.match_token(|kind| matches!(kind, TokenKind::RParen)) {
501                loop {
502                    args.push(self.parse_or()?);
503                    if self.match_token(|kind| matches!(kind, TokenKind::Comma)) {
504                        continue;
505                    }
506                    self.expect(TokenExpectation::RParen)?;
507                    break;
508                }
509            }
510
511            if identifier == "if" {
512                if args.len() != 3 {
513                    return Err(ExpressionError::Parse {
514                        path: None,
515                        details: "if(cond, then, else) requires exactly 3 arguments".to_string(),
516                    });
517                }
518                return Ok(ExprNode::Conditional(
519                    Box::new(args[0].clone()),
520                    Box::new(args[1].clone()),
521                    Box::new(args[2].clone()),
522                ));
523            }
524
525            if identifier == "node" {
526                return self.parse_node_reference(args);
527            }
528
529            let builtin = builtin_for_name(&identifier).ok_or(ExpressionError::Parse {
530                path: None,
531                details: format!("unknown function `{identifier}`"),
532            })?;
533            return Ok(ExprNode::Builtin(builtin, args));
534        }
535
536        Ok(ExprNode::Global(match identifier.as_str() {
537            "frame" => GlobalVar::Frame,
538            "time" => GlobalVar::Time,
539            "fps" => GlobalVar::Fps,
540            "width" => GlobalVar::Width,
541            "height" => GlobalVar::Height,
542            _ => GlobalVar::Custom(identifier),
543        }))
544    }
545
546    fn parse_node_reference(&mut self, args: Vec<ExprNode>) -> Result<ExprNode, ExpressionError> {
547        if args.is_empty() || args.len() > 2 {
548            return Err(ExpressionError::Parse {
549                path: None,
550                details: "node(id) or node(id, property_path) requires 1 or 2 arguments"
551                    .to_string(),
552            });
553        }
554
555        let node_id = match &args[0] {
556            ExprNode::Literal(ExpressionValue::Number(value)) => {
557                if *value < 0.0 || value.fract() != 0.0 {
558                    return Err(ExpressionError::Parse {
559                        path: None,
560                        details: "node(id, ..) expects an unsigned integer id".to_string(),
561                    });
562                }
563                NodeId(*value as u64)
564            }
565            _ => {
566                return Err(ExpressionError::Parse {
567                    path: None,
568                    details: "node(id, ..) first argument must be a numeric node id".to_string(),
569                });
570            }
571        };
572
573        if args.len() == 1 {
574            self.references.push(ExpressionReference::Node { node_id });
575            return Ok(ExprNode::Node(node_id));
576        }
577
578        let property_path = match &args[1] {
579            ExprNode::Literal(ExpressionValue::String(value)) => PropertyPath::new(value.clone()),
580            _ => {
581                return Err(ExpressionError::Parse {
582                    path: None,
583                    details: "node(.., property_path) second argument must be a string".to_string(),
584                });
585            }
586        };
587
588        self.references.push(ExpressionReference::PropertyValue {
589            node_id,
590            property_path: property_path.clone(),
591        });
592
593        Ok(ExprNode::PropertyValue(node_id, property_path))
594    }
595
596    fn match_token(&mut self, predicate: impl Fn(&TokenKind) -> bool) -> bool {
597        match self.peek() {
598            Some(token) if predicate(&token.kind) => {
599                self.position += 1;
600                true
601            }
602            _ => false,
603        }
604    }
605
606    fn expect(&mut self, expected: TokenExpectation) -> Result<(), ExpressionError> {
607        let token = self.peek().ok_or(ExpressionError::Parse {
608            path: None,
609            details: format!("expected {}", expected.describe()),
610        })?;
611
612        let matches = match expected {
613            TokenExpectation::RParen => matches!(token.kind, TokenKind::RParen),
614            TokenExpectation::Eof => matches!(token.kind, TokenKind::Eof),
615        };
616        if matches {
617            self.position += 1;
618            Ok(())
619        } else {
620            Err(ExpressionError::Parse {
621                path: None,
622                details: format!("expected {}", expected.describe()),
623            })
624        }
625    }
626
627    fn peek(&self) -> Option<&Token> {
628        self.tokens.get(self.position)
629    }
630}
631
632enum TokenExpectation {
633    RParen,
634    Eof,
635}
636
637impl TokenExpectation {
638    fn describe(&self) -> &'static str {
639        match self {
640            Self::RParen => "`)`",
641            Self::Eof => "end of input",
642        }
643    }
644}
645
646fn builtin_for_name(name: &str) -> Option<BuiltinFn> {
647    match name {
648        "min" => Some(BuiltinFn::Min),
649        "max" => Some(BuiltinFn::Max),
650        "abs" => Some(BuiltinFn::Abs),
651        "floor" => Some(BuiltinFn::Floor),
652        "ceil" => Some(BuiltinFn::Ceil),
653        "round" => Some(BuiltinFn::Round),
654        "sin" => Some(BuiltinFn::Sin),
655        "cos" => Some(BuiltinFn::Cos),
656        "clamp" => Some(BuiltinFn::Clamp),
657        "lerp" => Some(BuiltinFn::Lerp),
658        "pow" => Some(BuiltinFn::Pow),
659        "mod" => Some(BuiltinFn::Mod),
660        "fract" => Some(BuiltinFn::Fract),
661        "smoothstep" => Some(BuiltinFn::Smoothstep),
662        "linear" => Some(BuiltinFn::Linear),
663        "step" => Some(BuiltinFn::Step),
664        "text_height" => Some(BuiltinFn::TextHeight),
665        "text_width" => Some(BuiltinFn::TextWidth),
666        "uppercase" => Some(BuiltinFn::Uppercase),
667        "lowercase" => Some(BuiltinFn::Lowercase),
668        _ => None,
669    }
670}