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