Skip to main content

hedl_core/lex/
expression.rs

1// Dweve HEDL - Hierarchical Entity Data Language
2//
3// Copyright (c) 2025 Dweve IP B.V. and individual contributors.
4//
5// SPDX-License-Identifier: Apache-2.0
6//
7// Licensed under the Apache License, Version 2.0 (the "License");
8// you may not use this file except in compliance with the License.
9// You may obtain a copy of the License in the LICENSE file at the
10// root of this repository or at: http://www.apache.org/licenses/LICENSE-2.0
11//
12// Unless required by applicable law or agreed to in writing, software
13// distributed under the License is distributed on an "AS IS" BASIS,
14// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15// See the License for the specific language governing permissions and
16// limitations under the License.
17
18//! Expression AST for HEDL `$(...)` expressions.
19//!
20//! Expressions follow a minimal function-call grammar:
21//! - Identifiers: `x`, `foo_bar`
22//! - Literals: `42`, `3.5`, `"hello"`, `true`, `false`
23//! - Function calls: `func(arg1, arg2)`
24//! - Field access: `target.field`
25
26use super::error::LexError;
27use super::span::{SourcePos, Span};
28
29/// A parsed expression from `$(...)`.
30#[derive(Debug, Clone, PartialEq)]
31#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
32pub enum Expression {
33    /// A literal value: number, string, or boolean.
34    Literal {
35        /// The literal value.
36        value: ExprLiteral,
37        /// Source span of the literal.
38        span: Span,
39    },
40    /// An identifier: `foo`, `bar_baz`.
41    Identifier {
42        /// The identifier name.
43        name: String,
44        /// Source span of the identifier.
45        span: Span,
46    },
47    /// A function call: `func(arg1, arg2)`.
48    Call {
49        /// Function name.
50        name: String,
51        /// Function arguments.
52        args: Vec<Expression>,
53        /// Source span of the call.
54        span: Span,
55    },
56    /// Field access: `target.field`.
57    Access {
58        /// Target expression.
59        target: Box<Expression>,
60        /// Field name being accessed.
61        field: String,
62        /// Source span of the access.
63        span: Span,
64    },
65}
66
67/// A literal value within an expression.
68#[derive(Debug, Clone, PartialEq)]
69#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
70pub enum ExprLiteral {
71    /// Integer literal.
72    Int(i64),
73    /// Float literal.
74    Float(f64),
75    /// String literal (unquoted content).
76    String(String),
77    /// Boolean literal.
78    Bool(bool),
79}
80
81impl std::fmt::Display for Expression {
82    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
83        match self {
84            Expression::Literal { value, .. } => write!(f, "{}", value),
85            Expression::Identifier { name, .. } => write!(f, "{}", name),
86            Expression::Call { name, args, .. } => {
87                write!(f, "{}(", name)?;
88                for (i, arg) in args.iter().enumerate() {
89                    if i > 0 {
90                        write!(f, ", ")?;
91                    }
92                    write!(f, "{}", arg)?;
93                }
94                write!(f, ")")
95            }
96            Expression::Access { target, field, .. } => {
97                write!(f, "{}.{}", target, field)
98            }
99        }
100    }
101}
102
103impl std::fmt::Display for ExprLiteral {
104    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
105        match self {
106            ExprLiteral::Int(n) => write!(f, "{}", n),
107            ExprLiteral::Float(n) => write!(f, "{}", n),
108            ExprLiteral::String(s) => {
109                // Re-escape the string for display
110                write!(f, "\"")?;
111                for ch in s.chars() {
112                    if ch == '"' {
113                        write!(f, "\"\"")?;
114                    } else {
115                        write!(f, "{}", ch)?;
116                    }
117                }
118                write!(f, "\"")
119            }
120            ExprLiteral::Bool(b) => write!(f, "{}", b),
121        }
122    }
123}
124
125/// Parse an expression from the content inside `$(...)`.
126///
127/// # Grammar (informal):
128/// ```text
129/// expr     = call | access | atom
130/// call     = identifier "(" args ")"
131/// access   = expr "." identifier
132/// atom     = identifier | literal
133/// args     = (expr ("," expr)*)?
134/// literal  = number | string | bool
135/// ```
136pub fn parse_expression(s: &str) -> Result<Expression, LexError> {
137    let mut parser = ExprParser::new(s);
138    let expr = parser.parse_expr()?;
139    parser.skip_whitespace();
140    if parser.pos < parser.chars.len() {
141        return Err(LexError::InvalidToken {
142            message: format!(
143                "unexpected character '{}' at position {}",
144                parser.chars[parser.pos], parser.pos
145            ),
146            pos: SourcePos::default(),
147        });
148    }
149    Ok(expr)
150}
151
152/// Parse expression content from a `$(...)` token.
153///
154/// This extracts the content between `$(` and `)` and parses it.
155pub fn parse_expression_token(s: &str) -> Result<Expression, LexError> {
156    if !s.starts_with("$(") {
157        return Err(LexError::InvalidToken {
158            message: "expression must start with $(".to_string(),
159            pos: SourcePos::default(),
160        });
161    }
162
163    // Find matching closing paren
164    let content = extract_expression_content(s)?;
165    parse_expression(&content)
166}
167
168/// Extract expression content from `$(...)`, handling nested parens and quotes.
169fn extract_expression_content(s: &str) -> Result<String, LexError> {
170    if !s.starts_with("$(") {
171        return Err(LexError::InvalidToken {
172            message: "expression must start with $(".to_string(),
173            pos: SourcePos::default(),
174        });
175    }
176
177    let mut in_quotes = false;
178    let chars: Vec<char> = s.chars().collect();
179    let mut i = 2; // Skip "$("
180    let mut depth = 1;
181    let mut content_end = None;
182
183    while i < chars.len() {
184        let ch = chars[i];
185
186        if ch == '"' {
187            // Check for escaped quote
188            if in_quotes && i + 1 < chars.len() && chars[i + 1] == '"' {
189                i += 2;
190                continue;
191            }
192            in_quotes = !in_quotes;
193        } else if !in_quotes {
194            if ch == '(' {
195                depth += 1;
196            } else if ch == ')' {
197                depth -= 1;
198                if depth == 0 {
199                    content_end = Some(i);
200                    break;
201                }
202            }
203        }
204
205        i += 1;
206    }
207
208    if depth != 0 {
209        return Err(LexError::UnclosedExpression {
210            pos: SourcePos::default(),
211        });
212    }
213
214    let content_end = content_end.ok_or(LexError::UnclosedExpression {
215        pos: SourcePos::default(),
216    })?;
217    let content: String = chars[2..content_end].iter().collect();
218    Ok(content)
219}
220
221struct ExprParser {
222    /// Characters for easy parsing.
223    chars: Vec<char>,
224    /// Current character position.
225    pos: usize,
226    /// Current column (1-indexed).
227    column: usize,
228    /// Starting line number (for embedded expressions).
229    line: usize,
230}
231
232impl ExprParser {
233    fn new(s: &str) -> Self {
234        Self {
235            chars: s.chars().collect(),
236            pos: 0,
237            column: 1,
238            line: 1,
239        }
240    }
241
242    /// Create a span from start column to current column.
243    fn make_span(&self, start_col: usize) -> Span {
244        Span::new(
245            SourcePos::new(self.line, start_col),
246            SourcePos::new(self.line, self.column),
247        )
248    }
249
250    fn skip_whitespace(&mut self) {
251        while self.pos < self.chars.len() && self.chars[self.pos].is_whitespace() {
252            self.pos += 1;
253            self.column += 1;
254        }
255    }
256
257    fn peek(&self) -> Option<char> {
258        if self.pos < self.chars.len() {
259            Some(self.chars[self.pos])
260        } else {
261            None
262        }
263    }
264
265    fn advance(&mut self) -> Option<char> {
266        if self.pos < self.chars.len() {
267            let ch = self.chars[self.pos];
268            self.pos += 1;
269            self.column += 1;
270            Some(ch)
271        } else {
272            None
273        }
274    }
275
276    fn parse_expr(&mut self) -> Result<Expression, LexError> {
277        self.skip_whitespace();
278        let expr_start = self.column;
279
280        let mut expr = self.parse_atom()?;
281
282        // Handle chained operations (call or access)
283        loop {
284            self.skip_whitespace();
285            match self.peek() {
286                Some('(') => {
287                    // Function call - expr must be identifier
288                    let name = match expr {
289                        Expression::Identifier { name, .. } => name,
290                        _ => {
291                            return Err(LexError::InvalidToken {
292                                message: "function call on non-identifier".to_string(),
293                                pos: SourcePos::default(),
294                            });
295                        }
296                    };
297                    self.advance(); // consume '('
298                    let args = self.parse_args()?;
299                    expr = Expression::Call {
300                        name,
301                        args,
302                        span: self.make_span(expr_start),
303                    };
304                }
305                Some('.') => {
306                    // Field access
307                    self.advance(); // consume '.'
308                    self.skip_whitespace();
309                    let field = self.parse_identifier()?;
310                    expr = Expression::Access {
311                        target: Box::new(expr),
312                        field,
313                        span: self.make_span(expr_start),
314                    };
315                }
316                _ => break,
317            }
318        }
319
320        Ok(expr)
321    }
322
323    fn parse_atom(&mut self) -> Result<Expression, LexError> {
324        self.skip_whitespace();
325        let start = self.column;
326
327        match self.peek() {
328            Some('"') => {
329                // String literal
330                let s = self.parse_string()?;
331                Ok(Expression::Literal {
332                    value: ExprLiteral::String(s),
333                    span: self.make_span(start),
334                })
335            }
336            Some(ch) if ch.is_ascii_digit() || ch == '-' => {
337                // Number literal (or negative)
338                self.parse_number_with_start(start)
339            }
340            Some(ch) if ch.is_ascii_alphabetic() || ch == '_' => {
341                // Identifier or boolean
342                let ident = self.parse_identifier()?;
343                let span = self.make_span(start);
344                match ident.as_str() {
345                    "true" => Ok(Expression::Literal {
346                        value: ExprLiteral::Bool(true),
347                        span,
348                    }),
349                    "false" => Ok(Expression::Literal {
350                        value: ExprLiteral::Bool(false),
351                        span,
352                    }),
353                    _ => Ok(Expression::Identifier { name: ident, span }),
354                }
355            }
356            Some('(') => {
357                // Parenthesized expression
358                self.advance(); // consume '('
359                let expr = self.parse_expr()?;
360                self.skip_whitespace();
361                if self.peek() != Some(')') {
362                    return Err(LexError::InvalidToken {
363                        message: "expected ')' after parenthesized expression".to_string(),
364                        pos: SourcePos::default(),
365                    });
366                }
367                self.advance(); // consume ')'
368                Ok(expr)
369            }
370            Some(ch) => Err(LexError::InvalidToken {
371                message: format!("unexpected character '{}' in expression", ch),
372                pos: SourcePos::default(),
373            }),
374            None => Err(LexError::InvalidToken {
375                message: "unexpected end of expression".to_string(),
376                pos: SourcePos::default(),
377            }),
378        }
379    }
380
381    fn parse_identifier(&mut self) -> Result<String, LexError> {
382        let mut ident = String::new();
383
384        match self.peek() {
385            Some(ch) if ch.is_ascii_alphabetic() || ch == '_' => {
386                if let Some(c) = self.advance() {
387                    ident.push(c);
388                } else {
389                    return Err(LexError::InvalidToken {
390                        message: "unexpected end of input while parsing identifier".to_string(),
391                        pos: SourcePos::default(),
392                    });
393                }
394            }
395            _ => {
396                return Err(LexError::InvalidToken {
397                    message: "expected identifier".to_string(),
398                    pos: SourcePos::default(),
399                });
400            }
401        }
402
403        while let Some(ch) = self.peek() {
404            if ch.is_ascii_alphanumeric() || ch == '_' {
405                if let Some(c) = self.advance() {
406                    ident.push(c);
407                } else {
408                    break;
409                }
410            } else {
411                break;
412            }
413        }
414
415        Ok(ident)
416    }
417
418    fn parse_string(&mut self) -> Result<String, LexError> {
419        if self.advance() != Some('"') {
420            return Err(LexError::InvalidToken {
421                message: "expected '\"'".to_string(),
422                pos: SourcePos::default(),
423            });
424        }
425
426        let mut result = String::new();
427
428        loop {
429            match self.advance() {
430                Some('"') => {
431                    // Check for escaped quote
432                    if self.peek() == Some('"') {
433                        self.advance();
434                        result.push('"');
435                    } else {
436                        // End of string
437                        return Ok(result);
438                    }
439                }
440                Some(ch) => result.push(ch),
441                None => {
442                    return Err(LexError::UnclosedQuote {
443                        pos: SourcePos::default(),
444                    })
445                }
446            }
447        }
448    }
449
450    fn parse_number_with_start(&mut self, start_col: usize) -> Result<Expression, LexError> {
451        let mut num_str = String::new();
452        let mut has_dot = false;
453
454        // Handle negative sign
455        if self.peek() == Some('-') {
456            if let Some(c) = self.advance() {
457                num_str.push(c);
458            } else {
459                return Err(LexError::InvalidToken {
460                    message: "unexpected end of input while parsing number".to_string(),
461                    pos: SourcePos::default(),
462                });
463            }
464        }
465
466        // Parse digits
467        while let Some(ch) = self.peek() {
468            if ch.is_ascii_digit() {
469                if let Some(c) = self.advance() {
470                    num_str.push(c);
471                } else {
472                    break;
473                }
474            } else if ch == '.' && !has_dot {
475                // Check if this is a decimal point (not field access)
476                // Look ahead to see if it's followed by digits
477                let next_pos = self.pos + 1;
478                if next_pos < self.chars.len() && self.chars[next_pos].is_ascii_digit() {
479                    has_dot = true;
480                    if let Some(c) = self.advance() {
481                        num_str.push(c);
482                    } else {
483                        break;
484                    }
485                } else {
486                    // This is field access, stop here
487                    break;
488                }
489            } else {
490                break;
491            }
492        }
493
494        let span = self.make_span(start_col);
495
496        if has_dot {
497            let f: f64 = num_str.parse().map_err(|_| LexError::InvalidToken {
498                message: format!("invalid float: {}", num_str),
499                pos: SourcePos::default(),
500            })?;
501            Ok(Expression::Literal {
502                value: ExprLiteral::Float(f),
503                span,
504            })
505        } else {
506            let i: i64 = num_str.parse().map_err(|_| LexError::InvalidToken {
507                message: format!("invalid integer: {}", num_str),
508                pos: SourcePos::default(),
509            })?;
510            Ok(Expression::Literal {
511                value: ExprLiteral::Int(i),
512                span,
513            })
514        }
515    }
516
517    fn parse_args(&mut self) -> Result<Vec<Expression>, LexError> {
518        let mut args = Vec::new();
519
520        self.skip_whitespace();
521        if self.peek() == Some(')') {
522            self.advance(); // consume ')'
523            return Ok(args);
524        }
525
526        loop {
527            let arg = self.parse_expr()?;
528            args.push(arg);
529
530            self.skip_whitespace();
531            match self.peek() {
532                Some(',') => {
533                    self.advance(); // consume ','
534                }
535                Some(')') => {
536                    self.advance(); // consume ')'
537                    return Ok(args);
538                }
539                Some(ch) => {
540                    return Err(LexError::InvalidToken {
541                        message: format!("expected ',' or ')' in argument list, got '{}'", ch),
542                        pos: SourcePos::new(1, 1),
543                    });
544                }
545                None => {
546                    return Err(LexError::InvalidToken {
547                        message: "unexpected end of expression in argument list".to_string(),
548                        pos: SourcePos::new(1, 1),
549                    });
550                }
551            }
552        }
553    }
554}
555
556#[cfg(test)]
557mod tests {
558    use super::*;
559
560    #[test]
561    fn test_parse_identifier() {
562        let expr = parse_expression("foo").unwrap();
563        assert!(matches!(expr, Expression::Identifier { name, .. } if name == "foo"));
564    }
565
566    #[test]
567    fn test_parse_identifier_with_underscore() {
568        let expr = parse_expression("foo_bar").unwrap();
569        assert!(matches!(expr, Expression::Identifier { name, .. } if name == "foo_bar"));
570    }
571
572    #[test]
573    fn test_parse_integer() {
574        let expr = parse_expression("42").unwrap();
575        assert!(matches!(
576            expr,
577            Expression::Literal {
578                value: ExprLiteral::Int(42),
579                ..
580            }
581        ));
582    }
583
584    #[test]
585    fn test_parse_negative_integer() {
586        let expr = parse_expression("-123").unwrap();
587        assert!(matches!(
588            expr,
589            Expression::Literal {
590                value: ExprLiteral::Int(-123),
591                ..
592            }
593        ));
594    }
595
596    #[test]
597    fn test_parse_float() {
598        let expr = parse_expression("3.25").unwrap();
599        assert!(
600            matches!(expr, Expression::Literal { value: ExprLiteral::Float(f), .. } if (f - 3.25).abs() < 0.001)
601        );
602    }
603
604    #[test]
605    fn test_parse_string() {
606        let expr = parse_expression(r#""hello""#).unwrap();
607        assert!(
608            matches!(expr, Expression::Literal { value: ExprLiteral::String(s), .. } if s == "hello")
609        );
610    }
611
612    #[test]
613    fn test_parse_string_with_escaped_quote() {
614        let expr = parse_expression(r#""say ""hello""""#).unwrap();
615        assert!(
616            matches!(expr, Expression::Literal { value: ExprLiteral::String(s), .. } if s == "say \"hello\"")
617        );
618    }
619
620    #[test]
621    fn test_parse_bool_true() {
622        let expr = parse_expression("true").unwrap();
623        assert!(matches!(
624            expr,
625            Expression::Literal {
626                value: ExprLiteral::Bool(true),
627                ..
628            }
629        ));
630    }
631
632    #[test]
633    fn test_parse_bool_false() {
634        let expr = parse_expression("false").unwrap();
635        assert!(matches!(
636            expr,
637            Expression::Literal {
638                value: ExprLiteral::Bool(false),
639                ..
640            }
641        ));
642    }
643
644    #[test]
645    fn test_parse_call_no_args() {
646        let expr = parse_expression("now()").unwrap();
647        assert!(
648            matches!(expr, Expression::Call { name, args, .. } if name == "now" && args.is_empty())
649        );
650    }
651
652    #[test]
653    fn test_parse_call_one_arg() {
654        let expr = parse_expression("upper(x)").unwrap();
655        match expr {
656            Expression::Call { name, args, .. } => {
657                assert_eq!(name, "upper");
658                assert_eq!(args.len(), 1);
659                assert!(matches!(&args[0], Expression::Identifier { name, .. } if name == "x"));
660            }
661            _ => panic!("expected Call"),
662        }
663    }
664
665    #[test]
666    fn test_parse_call_multiple_args() {
667        let expr = parse_expression("concat(a, b, c)").unwrap();
668        match expr {
669            Expression::Call { name, args, .. } => {
670                assert_eq!(name, "concat");
671                assert_eq!(args.len(), 3);
672            }
673            _ => panic!("expected Call"),
674        }
675    }
676
677    #[test]
678    fn test_parse_call_string_args() {
679        let expr = parse_expression(r#"concat("hello", "world")"#).unwrap();
680        match expr {
681            Expression::Call { name, args, .. } => {
682                assert_eq!(name, "concat");
683                assert_eq!(args.len(), 2);
684                assert!(
685                    matches!(&args[0], Expression::Literal { value: ExprLiteral::String(s), .. } if s == "hello")
686                );
687                assert!(
688                    matches!(&args[1], Expression::Literal { value: ExprLiteral::String(s), .. } if s == "world")
689                );
690            }
691            _ => panic!("expected Call"),
692        }
693    }
694
695    #[test]
696    fn test_parse_nested_call() {
697        let expr = parse_expression("outer(inner(x))").unwrap();
698        match expr {
699            Expression::Call { name, args, .. } => {
700                assert_eq!(name, "outer");
701                assert_eq!(args.len(), 1);
702                match &args[0] {
703                    Expression::Call {
704                        name: inner_name, ..
705                    } => {
706                        assert_eq!(inner_name, "inner");
707                    }
708                    _ => panic!("expected nested Call"),
709                }
710            }
711            _ => panic!("expected Call"),
712        }
713    }
714
715    #[test]
716    fn test_parse_field_access() {
717        let expr = parse_expression("user.name").unwrap();
718        match expr {
719            Expression::Access { target, field, .. } => {
720                assert!(matches!(*target, Expression::Identifier { name, .. } if name == "user"));
721                assert_eq!(field, "name");
722            }
723            _ => panic!("expected Access"),
724        }
725    }
726
727    #[test]
728    fn test_parse_chained_access() {
729        let expr = parse_expression("a.b.c").unwrap();
730        match expr {
731            Expression::Access { target, field, .. } => {
732                assert_eq!(field, "c");
733                match *target {
734                    Expression::Access {
735                        target: inner,
736                        field: inner_field,
737                        ..
738                    } => {
739                        assert!(
740                            matches!(*inner, Expression::Identifier { name, .. } if name == "a")
741                        );
742                        assert_eq!(inner_field, "b");
743                    }
744                    _ => panic!("expected nested Access"),
745                }
746            }
747            _ => panic!("expected Access"),
748        }
749    }
750
751    #[test]
752    fn test_parse_call_then_access() {
753        let expr = parse_expression("get_user().name").unwrap();
754        match expr {
755            Expression::Access { target, field, .. } => {
756                assert_eq!(field, "name");
757                assert!(matches!(*target, Expression::Call { name, .. } if name == "get_user"));
758            }
759            _ => panic!("expected Access"),
760        }
761    }
762
763    #[test]
764    fn test_parse_expression_token() {
765        let expr = parse_expression_token("$(now())").unwrap();
766        assert!(
767            matches!(expr, Expression::Call { name, args, .. } if name == "now" && args.is_empty())
768        );
769    }
770
771    #[test]
772    fn test_parse_expression_token_nested_parens() {
773        let expr = parse_expression_token("$(concat(a, b))").unwrap();
774        assert!(matches!(expr, Expression::Call { name, .. } if name == "concat"));
775    }
776
777    #[test]
778    fn test_display_identifier() {
779        let expr = Expression::Identifier {
780            name: "foo".to_string(),
781            span: Span::synthetic(),
782        };
783        assert_eq!(format!("{}", expr), "foo");
784    }
785
786    #[test]
787    fn test_display_call() {
788        let expr = Expression::Call {
789            name: "func".to_string(),
790            args: vec![
791                Expression::Identifier {
792                    name: "x".to_string(),
793                    span: Span::synthetic(),
794                },
795                Expression::Literal {
796                    value: ExprLiteral::Int(42),
797                    span: Span::synthetic(),
798                },
799            ],
800            span: Span::synthetic(),
801        };
802        assert_eq!(format!("{}", expr), "func(x, 42)");
803    }
804
805    #[test]
806    fn test_display_access() {
807        let expr = Expression::Access {
808            target: Box::new(Expression::Identifier {
809                name: "user".to_string(),
810                span: Span::synthetic(),
811            }),
812            field: "name".to_string(),
813            span: Span::synthetic(),
814        };
815        assert_eq!(format!("{}", expr), "user.name");
816    }
817
818    #[test]
819    fn test_display_string_with_quotes() {
820        let expr = Expression::Literal {
821            value: ExprLiteral::String("say \"hi\"".to_string()),
822            span: Span::synthetic(),
823        };
824        assert_eq!(format!("{}", expr), "\"say \"\"hi\"\"\"");
825    }
826
827    #[test]
828    fn test_whitespace_handling() {
829        let expr = parse_expression("  func(  a  ,  b  )  ").unwrap();
830        assert!(
831            matches!(expr, Expression::Call { name, args, .. } if name == "func" && args.len() == 2)
832        );
833    }
834
835    #[test]
836    fn test_error_unclosed_paren() {
837        let result = parse_expression("func(x");
838        assert!(result.is_err());
839    }
840
841    #[test]
842    fn test_error_unclosed_string() {
843        let result = parse_expression(r#""unclosed"#);
844        assert!(result.is_err());
845    }
846
847    #[test]
848    fn test_error_unexpected_char() {
849        let result = parse_expression("func@");
850        assert!(result.is_err());
851    }
852
853    #[test]
854    fn test_parenthesized_expr() {
855        let expr = parse_expression("(foo)").unwrap();
856        assert!(matches!(expr, Expression::Identifier { name, .. } if name == "foo"));
857    }
858
859    // ==================== Additional literal tests ====================
860
861    #[test]
862    fn test_parse_zero() {
863        let expr = parse_expression("0").unwrap();
864        assert!(matches!(
865            expr,
866            Expression::Literal {
867                value: ExprLiteral::Int(0),
868                ..
869            }
870        ));
871    }
872
873    #[test]
874    fn test_parse_large_integer() {
875        let expr = parse_expression("9223372036854775807").unwrap();
876        assert!(matches!(
877            expr,
878            Expression::Literal {
879                value: ExprLiteral::Int(9223372036854775807),
880                ..
881            }
882        ));
883    }
884
885    #[test]
886    fn test_parse_negative_zero() {
887        // -0 is valid and should parse as -0 (which equals 0)
888        let expr = parse_expression("-0").unwrap();
889        assert!(matches!(
890            expr,
891            Expression::Literal {
892                value: ExprLiteral::Int(0),
893                ..
894            }
895        ));
896    }
897
898    #[test]
899    fn test_parse_float_zero() {
900        let expr = parse_expression("0.0").unwrap();
901        assert!(
902            matches!(expr, Expression::Literal { value: ExprLiteral::Float(f), .. } if f == 0.0)
903        );
904    }
905
906    #[test]
907    fn test_parse_negative_float() {
908        let expr = parse_expression("-3.5").unwrap();
909        assert!(
910            matches!(expr, Expression::Literal { value: ExprLiteral::Float(f), .. } if (f - (-3.5)).abs() < 0.001)
911        );
912    }
913
914    #[test]
915    fn test_parse_float_small() {
916        let expr = parse_expression("0.001").unwrap();
917        assert!(
918            matches!(expr, Expression::Literal { value: ExprLiteral::Float(f), .. } if (f - 0.001).abs() < 0.0001)
919        );
920    }
921
922    #[test]
923    fn test_parse_string_empty() {
924        let expr = parse_expression(r#""""#).unwrap();
925        assert!(
926            matches!(expr, Expression::Literal { value: ExprLiteral::String(s), .. } if s.is_empty())
927        );
928    }
929
930    #[test]
931    fn test_parse_string_with_spaces() {
932        let expr = parse_expression(r#""hello world""#).unwrap();
933        assert!(
934            matches!(expr, Expression::Literal { value: ExprLiteral::String(s), .. } if s == "hello world")
935        );
936    }
937
938    #[test]
939    fn test_parse_string_with_special_chars() {
940        let expr = parse_expression(r#""hello!@#$%""#).unwrap();
941        assert!(
942            matches!(expr, Expression::Literal { value: ExprLiteral::String(s), .. } if s == "hello!@#$%")
943        );
944    }
945
946    #[test]
947    fn test_parse_string_unicode() {
948        let expr = parse_expression(r#""日本語 🎉""#).unwrap();
949        assert!(
950            matches!(expr, Expression::Literal { value: ExprLiteral::String(s), .. } if s.contains('🎉'))
951        );
952    }
953
954    #[test]
955    fn test_parse_string_multiple_escaped_quotes() {
956        let expr = parse_expression(r#""""""""#).unwrap();
957        assert!(
958            matches!(expr, Expression::Literal { value: ExprLiteral::String(s), .. } if s == "\"\"")
959        );
960    }
961
962    // ==================== Additional identifier tests ====================
963
964    #[test]
965    fn test_parse_identifier_single_char() {
966        let expr = parse_expression("x").unwrap();
967        assert!(matches!(expr, Expression::Identifier { name, .. } if name == "x"));
968    }
969
970    #[test]
971    fn test_parse_identifier_underscore_only() {
972        let expr = parse_expression("_").unwrap();
973        assert!(matches!(expr, Expression::Identifier { name, .. } if name == "_"));
974    }
975
976    #[test]
977    fn test_parse_identifier_leading_underscore() {
978        let expr = parse_expression("_private").unwrap();
979        assert!(matches!(expr, Expression::Identifier { name, .. } if name == "_private"));
980    }
981
982    #[test]
983    fn test_parse_identifier_double_underscore() {
984        let expr = parse_expression("__dunder__").unwrap();
985        assert!(matches!(expr, Expression::Identifier { name, .. } if name == "__dunder__"));
986    }
987
988    #[test]
989    fn test_parse_identifier_with_numbers() {
990        let expr = parse_expression("var123").unwrap();
991        assert!(matches!(expr, Expression::Identifier { name, .. } if name == "var123"));
992    }
993
994    // ==================== Additional call tests ====================
995
996    #[test]
997    fn test_parse_call_with_literals() {
998        let expr = parse_expression("func(42, 3.5, true)").unwrap();
999        match expr {
1000            Expression::Call { name, args, .. } => {
1001                assert_eq!(name, "func");
1002                assert_eq!(args.len(), 3);
1003                assert!(matches!(
1004                    args[0],
1005                    Expression::Literal {
1006                        value: ExprLiteral::Int(42),
1007                        ..
1008                    }
1009                ));
1010                assert!(matches!(
1011                    args[1],
1012                    Expression::Literal {
1013                        value: ExprLiteral::Float(_),
1014                        ..
1015                    }
1016                ));
1017                assert!(matches!(
1018                    args[2],
1019                    Expression::Literal {
1020                        value: ExprLiteral::Bool(true),
1021                        ..
1022                    }
1023                ));
1024            }
1025            _ => panic!("expected Call"),
1026        }
1027    }
1028
1029    #[test]
1030    fn test_parse_deeply_nested_calls() {
1031        let expr = parse_expression("a(b(c(d(e))))").unwrap();
1032        fn count_depth(e: &Expression) -> usize {
1033            match e {
1034                Expression::Call { args, .. } => {
1035                    if args.is_empty() {
1036                        1
1037                    } else {
1038                        1 + count_depth(&args[0])
1039                    }
1040                }
1041                Expression::Identifier { .. } => 1,
1042                _ => 0,
1043            }
1044        }
1045        assert_eq!(count_depth(&expr), 5);
1046    }
1047
1048    #[test]
1049    fn test_parse_call_with_string_containing_comma() {
1050        let expr = parse_expression(r#"func("a, b", c)"#).unwrap();
1051        match expr {
1052            Expression::Call { name, args, .. } => {
1053                assert_eq!(name, "func");
1054                assert_eq!(args.len(), 2);
1055                assert!(
1056                    matches!(&args[0], Expression::Literal { value: ExprLiteral::String(s), .. } if s == "a, b")
1057                );
1058            }
1059            _ => panic!("expected Call"),
1060        }
1061    }
1062
1063    #[test]
1064    fn test_parse_call_with_string_containing_paren() {
1065        let expr = parse_expression(r#"func("(test)")"#).unwrap();
1066        match expr {
1067            Expression::Call { name, args, .. } => {
1068                assert_eq!(name, "func");
1069                assert_eq!(args.len(), 1);
1070                assert!(
1071                    matches!(&args[0], Expression::Literal { value: ExprLiteral::String(s), .. } if s == "(test)")
1072                );
1073            }
1074            _ => panic!("expected Call"),
1075        }
1076    }
1077
1078    // ==================== Additional access tests ====================
1079
1080    #[test]
1081    fn test_parse_deeply_chained_access() {
1082        let expr = parse_expression("a.b.c.d.e").unwrap();
1083        fn count_access(e: &Expression) -> usize {
1084            match e {
1085                Expression::Access { target, .. } => 1 + count_access(target),
1086                _ => 0,
1087            }
1088        }
1089        assert_eq!(count_access(&expr), 4);
1090    }
1091
1092    #[test]
1093    fn test_parse_access_with_numbers_in_field() {
1094        let expr = parse_expression("obj.field123").unwrap();
1095        match expr {
1096            Expression::Access { field, .. } => {
1097                assert_eq!(field, "field123");
1098            }
1099            _ => panic!("expected Access"),
1100        }
1101    }
1102
1103    #[test]
1104    fn test_parse_access_then_call_error() {
1105        // Access result cannot be called directly - it's not an identifier
1106        // This is a parser limitation: obj.method() doesn't work because
1107        // after access, we have an Access expression, not an identifier
1108        let result = parse_expression("obj.method()");
1109        assert!(result.is_err());
1110    }
1111
1112    #[test]
1113    fn test_parse_method_call_works() {
1114        // But a call on a plain identifier works
1115        let expr = parse_expression("method()").unwrap();
1116        match expr {
1117            Expression::Call { name, args, .. } => {
1118                assert_eq!(name, "method");
1119                assert!(args.is_empty());
1120            }
1121            _ => panic!("expected Call"),
1122        }
1123    }
1124
1125    #[test]
1126    fn test_parse_number_then_access() {
1127        // 42.field should parse 42 as int, then try to access .field
1128        // But actually, this parses as a float attempt which fails
1129        let expr = parse_expression("42.5").unwrap();
1130        assert!(matches!(
1131            expr,
1132            Expression::Literal { value: ExprLiteral::Float(f), .. } if (f - 42.5).abs() < 0.001
1133        ));
1134    }
1135
1136    // ==================== Expression token tests ====================
1137
1138    #[test]
1139    fn test_parse_expression_token_simple() {
1140        let expr = parse_expression_token("$(x)").unwrap();
1141        assert!(matches!(expr, Expression::Identifier { name, .. } if name == "x"));
1142    }
1143
1144    #[test]
1145    fn test_parse_expression_token_complex() {
1146        let expr = parse_expression_token("$(user.profile.name)").unwrap();
1147        match expr {
1148            Expression::Access { field, .. } => {
1149                assert_eq!(field, "name");
1150            }
1151            _ => panic!("expected Access"),
1152        }
1153    }
1154
1155    #[test]
1156    fn test_parse_expression_token_with_quotes() {
1157        let expr = parse_expression_token(r#"$(concat("a", "b"))"#).unwrap();
1158        assert!(matches!(expr, Expression::Call { .. }));
1159    }
1160
1161    #[test]
1162    fn test_parse_expression_token_not_starting_with_dollar() {
1163        let result = parse_expression_token("(x)");
1164        assert!(result.is_err());
1165    }
1166
1167    #[test]
1168    fn test_parse_expression_token_unclosed() {
1169        let result = parse_expression_token("$(x");
1170        assert!(result.is_err());
1171    }
1172
1173    // ==================== Display tests ====================
1174
1175    #[test]
1176    fn test_display_int() {
1177        let lit = ExprLiteral::Int(42);
1178        assert_eq!(format!("{}", lit), "42");
1179    }
1180
1181    #[test]
1182    fn test_display_float() {
1183        let lit = ExprLiteral::Float(3.5);
1184        assert!(format!("{}", lit).starts_with("3.5"));
1185    }
1186
1187    #[test]
1188    fn test_display_bool() {
1189        assert_eq!(format!("{}", ExprLiteral::Bool(true)), "true");
1190        assert_eq!(format!("{}", ExprLiteral::Bool(false)), "false");
1191    }
1192
1193    #[test]
1194    fn test_display_nested_call() {
1195        let expr = Expression::Call {
1196            name: "outer".to_string(),
1197            args: vec![Expression::Call {
1198                name: "inner".to_string(),
1199                args: vec![Expression::Literal {
1200                    value: ExprLiteral::Int(42),
1201                    span: Span::synthetic(),
1202                }],
1203                span: Span::synthetic(),
1204            }],
1205            span: Span::synthetic(),
1206        };
1207        assert_eq!(format!("{}", expr), "outer(inner(42))");
1208    }
1209
1210    #[test]
1211    fn test_display_access_chain() {
1212        let expr = Expression::Access {
1213            target: Box::new(Expression::Access {
1214                target: Box::new(Expression::Identifier {
1215                    name: "a".to_string(),
1216                    span: Span::synthetic(),
1217                }),
1218                field: "b".to_string(),
1219                span: Span::synthetic(),
1220            }),
1221            field: "c".to_string(),
1222            span: Span::synthetic(),
1223        };
1224        assert_eq!(format!("{}", expr), "a.b.c");
1225    }
1226
1227    // ==================== Error handling tests ====================
1228
1229    #[test]
1230    fn test_error_empty_expression() {
1231        let result = parse_expression("");
1232        assert!(result.is_err());
1233    }
1234
1235    #[test]
1236    fn test_error_only_whitespace() {
1237        let result = parse_expression("   ");
1238        assert!(result.is_err());
1239    }
1240
1241    #[test]
1242    fn test_error_trailing_garbage() {
1243        let result = parse_expression("foo bar");
1244        assert!(result.is_err());
1245    }
1246
1247    #[test]
1248    fn test_error_call_on_literal() {
1249        // This parses "42" as literal, then sees "()" and fails
1250        // because 42 is not an identifier
1251        let result = parse_expression("42()");
1252        assert!(result.is_err());
1253    }
1254
1255    #[test]
1256    fn test_error_unclosed_string_in_call() {
1257        let result = parse_expression(r#"func("unclosed)"#);
1258        assert!(result.is_err());
1259    }
1260
1261    #[test]
1262    fn test_error_missing_comma_in_args() {
1263        let result = parse_expression("func(a b)");
1264        assert!(result.is_err());
1265    }
1266
1267    #[test]
1268    fn test_error_trailing_comma_in_args() {
1269        let result = parse_expression("func(a,)");
1270        assert!(result.is_err());
1271    }
1272
1273    #[test]
1274    fn test_error_unclosed_paren_in_nested() {
1275        let result = parse_expression("outer(inner(x)");
1276        assert!(result.is_err());
1277    }
1278
1279    // ==================== Struct equality and clone tests ====================
1280
1281    #[test]
1282    fn test_expression_equality() {
1283        let a = Expression::Identifier {
1284            name: "foo".to_string(),
1285            span: Span::synthetic(),
1286        };
1287        let b = Expression::Identifier {
1288            name: "foo".to_string(),
1289            span: Span::synthetic(),
1290        };
1291        assert_eq!(a, b);
1292    }
1293
1294    #[test]
1295    fn test_expression_clone() {
1296        let original = Expression::Call {
1297            name: "func".to_string(),
1298            args: vec![Expression::Literal {
1299                value: ExprLiteral::Int(42),
1300                span: Span::synthetic(),
1301            }],
1302            span: Span::synthetic(),
1303        };
1304        let cloned = original.clone();
1305        assert_eq!(original, cloned);
1306    }
1307
1308    #[test]
1309    fn test_expr_literal_equality() {
1310        assert_eq!(ExprLiteral::Int(42), ExprLiteral::Int(42));
1311        assert_ne!(ExprLiteral::Int(42), ExprLiteral::Int(43));
1312        assert_eq!(ExprLiteral::Bool(true), ExprLiteral::Bool(true));
1313        assert_eq!(
1314            ExprLiteral::String("test".to_string()),
1315            ExprLiteral::String("test".to_string())
1316        );
1317    }
1318
1319    #[test]
1320    fn test_expr_literal_clone() {
1321        let original = ExprLiteral::String("hello".to_string());
1322        let cloned = original.clone();
1323        assert_eq!(original, cloned);
1324    }
1325
1326    #[test]
1327    fn test_expression_debug() {
1328        let expr = Expression::Call {
1329            name: "func".to_string(),
1330            args: vec![],
1331            span: Span::synthetic(),
1332        };
1333        let debug = format!("{:?}", expr);
1334        assert!(debug.contains("func"));
1335    }
1336
1337    // ==================== Parenthesized expression tests ====================
1338
1339    #[test]
1340    fn test_deeply_nested_parens() {
1341        let expr = parse_expression("(((foo)))").unwrap();
1342        assert!(matches!(expr, Expression::Identifier { name, .. } if name == "foo"));
1343    }
1344
1345    #[test]
1346    fn test_parens_around_call() {
1347        let expr = parse_expression("(func(x))").unwrap();
1348        assert!(matches!(expr, Expression::Call { name, .. } if name == "func"));
1349    }
1350
1351    #[test]
1352    fn test_parens_around_access() {
1353        let expr = parse_expression("(a.b)").unwrap();
1354        assert!(matches!(expr, Expression::Access { field, .. } if field == "b"));
1355    }
1356
1357    #[test]
1358    fn test_unclosed_inner_paren() {
1359        let result = parse_expression("((foo)");
1360        assert!(result.is_err());
1361    }
1362
1363    // ==================== Span tracking tests ====================
1364
1365    #[test]
1366    fn test_identifier_span() {
1367        let expr = parse_expression("foo").unwrap();
1368        if let Expression::Identifier { span, .. } = expr {
1369            assert!(!span.is_synthetic());
1370            assert_eq!(span.start().line(), 1);
1371            assert_eq!(span.start().column(), 1);
1372            assert_eq!(span.end().column(), 4); // "foo" is 3 chars, end is exclusive
1373        } else {
1374            panic!("expected identifier");
1375        }
1376    }
1377
1378    #[test]
1379    fn test_identifier_with_leading_whitespace_span() {
1380        let expr = parse_expression("  bar").unwrap();
1381        if let Expression::Identifier { span, .. } = expr {
1382            assert!(!span.is_synthetic());
1383            assert_eq!(span.start().column(), 3); // After 2 spaces
1384            assert_eq!(span.end().column(), 6);
1385        } else {
1386            panic!("expected identifier");
1387        }
1388    }
1389
1390    #[test]
1391    fn test_integer_span() {
1392        let expr = parse_expression("12345").unwrap();
1393        if let Expression::Literal { span, .. } = expr {
1394            assert!(!span.is_synthetic());
1395            assert_eq!(span.start().column(), 1);
1396            assert_eq!(span.end().column(), 6);
1397        } else {
1398            panic!("expected literal");
1399        }
1400    }
1401
1402    #[test]
1403    fn test_string_span() {
1404        let expr = parse_expression("\"hello\"").unwrap();
1405        if let Expression::Literal { span, .. } = expr {
1406            assert!(!span.is_synthetic());
1407            assert_eq!(span.start().column(), 1);
1408            assert_eq!(span.end().column(), 8); // Including quotes
1409        } else {
1410            panic!("expected literal");
1411        }
1412    }
1413
1414    #[test]
1415    fn test_call_span() {
1416        let expr = parse_expression("func(x, y)").unwrap();
1417        if let Expression::Call { span, .. } = expr {
1418            assert!(!span.is_synthetic());
1419            assert_eq!(span.start().column(), 1);
1420            assert_eq!(span.end().column(), 11);
1421        } else {
1422            panic!("expected call");
1423        }
1424    }
1425
1426    #[test]
1427    fn test_access_span() {
1428        let expr = parse_expression("a.b").unwrap();
1429        if let Expression::Access { span, .. } = expr {
1430            assert!(!span.is_synthetic());
1431            assert_eq!(span.start().column(), 1);
1432            assert_eq!(span.end().column(), 4);
1433        } else {
1434            panic!("expected access");
1435        }
1436    }
1437
1438    #[test]
1439    fn test_boolean_span() {
1440        let expr = parse_expression("true").unwrap();
1441        if let Expression::Literal { span, .. } = expr {
1442            assert!(!span.is_synthetic());
1443            assert_eq!(span.start().column(), 1);
1444            assert_eq!(span.end().column(), 5);
1445        } else {
1446            panic!("expected literal");
1447        }
1448    }
1449
1450    #[test]
1451    fn test_negative_number_span() {
1452        let expr = parse_expression("-42").unwrap();
1453        if let Expression::Literal { span, .. } = expr {
1454            assert!(!span.is_synthetic());
1455            assert_eq!(span.start().column(), 1);
1456            assert_eq!(span.end().column(), 4);
1457        } else {
1458            panic!("expected literal");
1459        }
1460    }
1461}