Skip to main content

shape_ast/parser/expressions/
primary.rs

1//! Primary expression parsing
2//!
3//! This module handles parsing of primary expressions and postfix operations:
4//! - Primary expressions (identifiers, literals, grouped expressions)
5//! - Postfix operations (property access, method calls, indexing)
6//! - Expression entry point
7
8use crate::ast::{
9    DataRef, EnumConstructorPayload, Expr, JoinBranch, JoinExpr, JoinKind, RangeKind, Span, Spanned,
10};
11use crate::error::{Result, ShapeError};
12use crate::parser::{Rule, pair_location};
13use pest::iterators::Pair;
14
15use super::super::pair_span;
16
17/// Main entry point for parsing expressions
18pub fn parse_expression(pair: Pair<Rule>) -> Result<Expr> {
19    let pair_loc = pair_location(&pair);
20    match pair.as_rule() {
21        Rule::expression => {
22            let inner = pair
23                .into_inner()
24                .next()
25                .ok_or_else(|| ShapeError::ParseError {
26                    message: "expected expression content".to_string(),
27                    location: Some(pair_loc),
28                })?;
29            parse_expression(inner)
30        }
31        Rule::assignment_expr => super::binary_ops::parse_assignment_expr(pair),
32        Rule::ternary_expr => super::binary_ops::parse_ternary_expr(pair),
33        Rule::null_coalesce_expr => super::binary_ops::parse_null_coalesce_expr(pair),
34        Rule::context_expr => super::binary_ops::parse_context_expr(pair),
35        Rule::or_expr => super::binary_ops::parse_or_expr(pair),
36        Rule::range_expr => super::binary_ops::parse_range_expr(pair),
37        _ => Err(ShapeError::ParseError {
38            message: format!("expected expression, got {:?}", pair.as_rule()),
39            location: Some(pair_loc),
40        }),
41    }
42}
43
44/// Parse postfix expression (property access, method calls, indexing)
45pub fn parse_postfix_expr(pair: Pair<Rule>) -> Result<Expr> {
46    let pair_loc = pair_location(&pair);
47    let mut inner = pair.into_inner();
48    let primary_pair = inner.next().ok_or_else(|| ShapeError::ParseError {
49        message: "expected primary expression".to_string(),
50        location: Some(pair_loc),
51    })?;
52    let mut expr = parse_primary_expr(primary_pair)?;
53
54    // Collect all postfix operations to handle method calls
55    let postfix_ops: Vec<_> = inner.collect();
56    let mut i = 0;
57
58    while i < postfix_ops.len() {
59        let postfix = &postfix_ops[i];
60        match postfix.as_rule() {
61            Rule::property_access | Rule::optional_property_access => {
62                let is_optional = postfix.as_rule() == Rule::optional_property_access;
63                let postfix_loc = pair_location(postfix);
64                let property = postfix
65                    .clone()
66                    .into_inner()
67                    .next()
68                    .ok_or_else(|| ShapeError::ParseError {
69                        message: "expected property name after '.'".to_string(),
70                        location: Some(postfix_loc),
71                    })?
72                    .as_str()
73                    .to_string();
74
75                let full_span = Span::new(expr.span().start, pair_span(postfix).end);
76
77                // Check if next operation is a function call (method call)
78                if i + 1 < postfix_ops.len() && postfix_ops[i + 1].as_rule() == Rule::function_call
79                {
80                    let (args, named_args) =
81                        super::functions::parse_arg_list(postfix_ops[i + 1].clone())?;
82                    expr = Expr::MethodCall {
83                        receiver: Box::new(expr),
84                        method: property,
85                        args,
86                        named_args,
87                        optional: is_optional,
88                        span: full_span,
89                    };
90                    i += 2; // Skip the function call we just processed
91                } else {
92                    expr = Expr::PropertyAccess {
93                        object: Box::new(expr),
94                        property,
95                        optional: is_optional,
96                        span: full_span,
97                    };
98                    i += 1;
99                }
100            }
101            Rule::index_access => {
102                let postfix_loc = pair_location(postfix);
103                let postfix_span = pair_span(postfix);
104                // Special case for data indexing (legacy "candle" identifier support)
105                if let Expr::Identifier(ref id, _) = expr {
106                    if id == "candle" {
107                        let index_expr = postfix.clone().into_inner().next().ok_or_else(|| {
108                            ShapeError::ParseError {
109                                message: "expected index expression in data access".to_string(),
110                                location: Some(postfix_loc.clone()),
111                            }
112                        })?;
113                        let (index, timeframe) = super::data_refs::parse_index_expr(index_expr)?;
114                        expr = Expr::DataRef(DataRef { index, timeframe }, postfix_span);
115                        i += 1;
116                        continue;
117                    }
118                }
119                // Special case for data with timeframe: data(5m)[0]
120                if let Expr::DataRef(ref mut data_ref, _) = expr {
121                    let index_expr = postfix.clone().into_inner().next().ok_or_else(|| {
122                        ShapeError::ParseError {
123                            message: "expected index expression in data access".to_string(),
124                            location: Some(postfix_loc.clone()),
125                        }
126                    })?;
127                    let (index, timeframe_override) =
128                        super::data_refs::parse_index_expr(index_expr)?;
129                    data_ref.index = index;
130                    // If there's a timeframe in the index, it overrides the one from data(5m)
131                    if timeframe_override.is_some() {
132                        data_ref.timeframe = timeframe_override;
133                    }
134                    i += 1;
135                    continue;
136                }
137                // General array/object indexing
138                let index_expr_pair =
139                    postfix
140                        .clone()
141                        .into_inner()
142                        .next()
143                        .ok_or_else(|| ShapeError::ParseError {
144                            message: "expected index expression".to_string(),
145                            location: Some(postfix_loc),
146                        })?;
147                let (index_expr, end_expr) =
148                    super::data_refs::parse_index_expr_general(index_expr_pair)?;
149                expr = Expr::IndexAccess {
150                    object: Box::new(expr),
151                    index: Box::new(index_expr),
152                    end_index: end_expr.map(Box::new),
153                    span: postfix_span,
154                };
155                i += 1;
156            }
157            Rule::function_call => {
158                let (args, named_args) = super::functions::parse_arg_list(postfix.clone())?;
159                if let Expr::Identifier(name, _) = expr {
160                    expr = Expr::FunctionCall {
161                        name,
162                        args,
163                        named_args,
164                        span: pair_span(postfix),
165                    };
166                } else {
167                    // Chained call: expr(args) where expr is not an identifier
168                    let full_span = Span::new(expr.span().start, pair_span(postfix).end);
169                    expr = Expr::MethodCall {
170                        receiver: Box::new(expr),
171                        method: "__call__".to_string(),
172                        args,
173                        named_args,
174                        optional: false,
175                        span: full_span,
176                    };
177                }
178                i += 1;
179            }
180            Rule::type_assertion_suffix => {
181                let mut inner = postfix.clone().into_inner();
182
183                // Skip the as_keyword pair
184                let first = inner.next().ok_or_else(|| ShapeError::ParseError {
185                    message: "Type assertion missing type annotation".to_string(),
186                    location: None,
187                })?;
188                let type_pair = if first.as_rule() == Rule::as_keyword {
189                    inner.next().ok_or_else(|| ShapeError::ParseError {
190                        message: "Type assertion missing type annotation after 'as'".to_string(),
191                        location: None,
192                    })?
193                } else {
194                    first
195                };
196                let type_annotation = crate::parser::parse_type_annotation(type_pair)?;
197
198                // Check for optional comptime field overrides
199                let meta_param_overrides = if let Some(overrides_pair) = inner.next() {
200                    if overrides_pair.as_rule() == Rule::comptime_field_overrides {
201                        Some(crate::parser::types::parse_comptime_field_overrides(
202                            overrides_pair,
203                        )?)
204                    } else {
205                        None
206                    }
207                } else {
208                    None
209                };
210
211                expr = Expr::TypeAssertion {
212                    expr: Box::new(expr),
213                    type_annotation,
214                    meta_param_overrides,
215                    span: pair_span(postfix),
216                };
217                i += 1;
218            }
219            Rule::using_impl_suffix => {
220                let impl_name = postfix
221                    .clone()
222                    .into_inner()
223                    .next()
224                    .ok_or_else(|| ShapeError::ParseError {
225                        message: "Missing impl name after 'using'".to_string(),
226                        location: Some(pair_location(postfix)),
227                    })?
228                    .as_str()
229                    .to_string();
230                let full_span = Span::new(expr.span().start, pair_span(postfix).end);
231                expr = Expr::UsingImpl {
232                    expr: Box::new(expr),
233                    impl_name,
234                    span: full_span,
235                };
236                i += 1;
237            }
238            Rule::try_operator => {
239                // Try operator: expr? - unified fallible propagation (Result/Option)
240                expr = Expr::TryOperator(Box::new(expr), pair_span(postfix));
241                i += 1;
242            }
243            _ => {
244                return Err(ShapeError::ParseError {
245                    message: format!("Unexpected postfix operator: {:?}", postfix.as_rule()),
246                    location: None,
247                });
248            }
249        }
250    }
251
252    Ok(expr)
253}
254
255/// Parse primary expression
256pub fn parse_primary_expr(pair: Pair<Rule>) -> Result<Expr> {
257    let pair_loc = pair_location(&pair);
258    // Handle case where we might have nested primary_expr rules
259    match pair.as_rule() {
260        Rule::primary_expr => {
261            // Check if this is an empty array literal case
262            let inner_pairs: Vec<_> = pair.clone().into_inner().collect();
263            if inner_pairs.is_empty() {
264                // For cases that shouldn't happen
265                return Err(ShapeError::ParseError {
266                    message: "empty primary expression".to_string(),
267                    location: Some(pair_loc),
268                });
269            }
270            let first = inner_pairs
271                .into_iter()
272                .next()
273                .ok_or_else(|| ShapeError::ParseError {
274                    message: "expected primary expression content".to_string(),
275                    location: Some(pair_loc),
276                })?;
277            parse_primary_expr_inner(first)
278        }
279        _ => parse_primary_expr_inner(pair),
280    }
281}
282
283/// Parse the inner primary expression
284fn parse_primary_expr_inner(pair: Pair<Rule>) -> Result<Expr> {
285    let span = pair_span(&pair);
286    let pair_loc = pair_location(&pair);
287
288    match pair.as_rule() {
289        Rule::unit_literal => Ok(Expr::Literal(crate::ast::Literal::Unit, span)),
290        Rule::literal => super::literals::parse_literal(pair),
291        Rule::array_literal => super::literals::parse_array_literal(pair),
292        Rule::data_ref => super::data_refs::parse_data_ref(pair),
293        Rule::time_ref => Ok(Expr::TimeRef(super::temporal::parse_time_ref(pair)?, span)),
294        Rule::datetime_expr => Ok(Expr::DateTime(
295            super::temporal::parse_datetime_expr(pair)?,
296            span,
297        )),
298        Rule::pattern_name => {
299            let name_pair = pair
300                .into_inner()
301                .next()
302                .ok_or_else(|| ShapeError::ParseError {
303                    message: "expected pattern name".to_string(),
304                    location: Some(pair_loc),
305                })?;
306            Ok(Expr::PatternRef(name_pair.as_str().to_string(), span))
307        }
308        Rule::qualified_function_call_expr => parse_qualified_function_call_expr(pair),
309        Rule::enum_constructor_expr => parse_enum_constructor_expr(pair),
310        Rule::ident => Ok(Expr::Identifier(pair.as_str().to_string(), span)),
311        Rule::expression => parse_expression(pair),
312        Rule::temporal_nav => super::temporal::parse_temporal_nav(pair),
313        Rule::timeframe_expr => super::temporal::parse_timeframe_expr(pair),
314        Rule::async_let_expr => super::control_flow::parse_async_let_expr(pair),
315        Rule::async_scope_expr => super::control_flow::parse_async_scope_expr(pair),
316        Rule::if_expr => super::control_flow::parse_if_expr(pair),
317        Rule::while_expr => super::control_flow::parse_while_expr(pair),
318        Rule::for_expr => super::control_flow::parse_for_expr(pair),
319        Rule::loop_expr => super::control_flow::parse_loop_expr(pair),
320        Rule::let_expr => super::control_flow::parse_let_expr(pair),
321        Rule::match_expr => super::control_flow::parse_match_expr(pair),
322        Rule::break_expr => super::control_flow::parse_break_expr(pair),
323        Rule::continue_expr => Ok(Expr::Continue(span)),
324        Rule::return_expr => super::control_flow::parse_return_expr(pair),
325        Rule::block_expr => super::control_flow::parse_block_expr(pair),
326        Rule::object_literal => super::literals::parse_object_literal(pair),
327        Rule::function_expr => super::functions::parse_function_expr(pair),
328        Rule::some_expr => parse_some_expr(pair),
329        Rule::duration => super::temporal::parse_duration(pair),
330        // Handle nested primary_expr (this should recursively call parse_primary_expr)
331        Rule::primary_expr => parse_primary_expr(pair),
332        // Handle postfix_expr (function calls, property access, etc.)
333        Rule::postfix_expr => parse_postfix_expr(pair),
334        Rule::list_comprehension => super::comprehensions::parse_list_comprehension(pair),
335        Rule::await_expr => parse_await_expr(pair),
336        Rule::struct_literal => parse_struct_literal(pair),
337        Rule::from_query_expr => super::control_flow::parse_from_query_expr(pair),
338        Rule::comptime_for_expr => parse_comptime_for_expr(pair),
339        Rule::comptime_block => parse_comptime_block(pair),
340        Rule::annotated_expr => parse_annotated_expr(pair),
341        Rule::datetime_range => {
342            // Parse datetime range - return Range expression if end is present
343            let (start, end) = super::temporal::parse_datetime_range(pair)?;
344            match end {
345                Some(end_expr) => Ok(Expr::Range {
346                    start: Some(Box::new(start)),
347                    end: Some(Box::new(end_expr)),
348                    kind: RangeKind::Inclusive, // DateTime ranges are inclusive
349                    span,
350                }),
351                None => Ok(start),
352            }
353        }
354        _ => Err(ShapeError::ParseError {
355            message: format!("Unexpected primary expression: {:?}", pair.as_rule()),
356            location: None,
357        }),
358    }
359}
360
361fn parse_qualified_function_call_expr(pair: Pair<Rule>) -> Result<Expr> {
362    let span = pair_span(&pair);
363    let pair_loc = pair_location(&pair);
364    let mut inner = pair.into_inner();
365
366    let path_pair = inner.next().ok_or_else(|| ShapeError::ParseError {
367        message: "expected qualified call target".to_string(),
368        location: Some(pair_loc.clone()),
369    })?;
370    let (namespace, function) = parse_enum_variant_path(path_pair)?;
371
372    let call_pair = inner.next().ok_or_else(|| ShapeError::ParseError {
373        message: "expected argument list after qualified call target".to_string(),
374        location: Some(pair_loc),
375    })?;
376    let (args, named_args) = super::functions::parse_arg_list(call_pair)?;
377
378    Ok(Expr::QualifiedFunctionCall {
379        namespace,
380        function,
381        args,
382        named_args,
383        span,
384    })
385}
386
387/// Parse Some expression: Some(value) constructor for Option type
388fn parse_some_expr(pair: Pair<Rule>) -> Result<Expr> {
389    let span = pair_span(&pair);
390    let pair_loc = pair_location(&pair);
391    let inner = pair
392        .into_inner()
393        .next()
394        .ok_or_else(|| ShapeError::ParseError {
395            message: "expected expression in Some()".to_string(),
396            location: Some(pair_loc),
397        })?;
398    let inner_expr = parse_expression(inner)?;
399    Ok(Expr::FunctionCall {
400        name: "Some".to_string(),
401        args: vec![inner_expr],
402        named_args: vec![],
403        span,
404    })
405}
406
407/// Parse await expression: await expr | await @annotation expr | await join kind { ... }
408fn parse_await_expr(pair: Pair<Rule>) -> Result<Expr> {
409    let span = pair_span(&pair);
410    let pair_loc = pair_location(&pair);
411    // Filter out the await_keyword atomic token
412    let inner_pairs: Vec<_> = pair
413        .into_inner()
414        .filter(|p| p.as_rule() != Rule::await_keyword)
415        .collect();
416
417    if inner_pairs.is_empty() {
418        return Err(ShapeError::ParseError {
419            message: "expected expression after 'await'".to_string(),
420            location: Some(pair_loc),
421        });
422    }
423
424    // Check what the first inner element is
425    let first = &inner_pairs[0];
426    match first.as_rule() {
427        Rule::join_expr => {
428            // await join all|race|any|settle { branches }
429            let join = parse_join_expr(first.clone())?;
430            let join_span = pair_span(first);
431            Ok(Expr::Await(
432                Box::new(Expr::Join(Box::new(join), join_span)),
433                span,
434            ))
435        }
436        Rule::annotation => {
437            // await @annotation ... expr — collect annotations and wrap target
438            let mut annotations = Vec::new();
439            let mut target_pair = None;
440
441            for p in &inner_pairs {
442                match p.as_rule() {
443                    Rule::annotation => {
444                        annotations.push(crate::parser::functions::parse_annotation(p.clone())?);
445                    }
446                    Rule::postfix_expr => {
447                        target_pair = Some(p.clone());
448                    }
449                    _ => {}
450                }
451            }
452
453            let target = target_pair.ok_or_else(|| ShapeError::ParseError {
454                message: "expected expression after annotations in await".to_string(),
455                location: Some(pair_loc),
456            })?;
457
458            let mut expr = parse_postfix_expr(target)?;
459
460            // Wrap in Annotated nodes right-to-left so the first annotation is outermost
461            for annotation in annotations.into_iter().rev() {
462                let anno_span = Span::new(annotation.span.start, expr.span().end);
463                expr = Expr::Annotated {
464                    annotation,
465                    target: Box::new(expr),
466                    span: anno_span,
467                };
468            }
469
470            Ok(Expr::Await(Box::new(expr), span))
471        }
472        Rule::postfix_expr => {
473            // await expr (simple case)
474            let expr = parse_postfix_expr(first.clone())?;
475            Ok(Expr::Await(Box::new(expr), span))
476        }
477        _ => Err(ShapeError::ParseError {
478            message: format!(
479                "unexpected token in await expression: {:?}",
480                first.as_rule()
481            ),
482            location: Some(pair_loc),
483        }),
484    }
485}
486
487/// Parse join expression: join all|race|any|settle { branch, ... }
488fn parse_join_expr(pair: Pair<Rule>) -> Result<JoinExpr> {
489    let span = pair_span(&pair);
490    let pair_loc = pair_location(&pair);
491    let mut inner = pair.into_inner();
492
493    // Parse join kind
494    let kind_pair = inner.next().ok_or_else(|| ShapeError::ParseError {
495        message: "expected join strategy (all, race, any, settle)".to_string(),
496        location: Some(pair_loc.clone()),
497    })?;
498    let kind = match kind_pair.as_str() {
499        "all" => JoinKind::All,
500        "race" => JoinKind::Race,
501        "any" => JoinKind::Any,
502        "settle" => JoinKind::Settle,
503        other => {
504            return Err(ShapeError::ParseError {
505                message: format!(
506                    "unknown join strategy: '{}'. Expected all, race, any, or settle",
507                    other
508                ),
509                location: Some(pair_location(&kind_pair)),
510            });
511        }
512    };
513
514    // Parse branch list
515    let branch_list_pair = inner.next().ok_or_else(|| ShapeError::ParseError {
516        message: "expected join branches".to_string(),
517        location: Some(pair_loc),
518    })?;
519
520    let mut branches = Vec::new();
521    for branch_pair in branch_list_pair.into_inner() {
522        if branch_pair.as_rule() == Rule::join_branch {
523            branches.push(parse_join_branch(branch_pair)?);
524        }
525    }
526
527    Ok(JoinExpr {
528        kind,
529        branches,
530        span,
531    })
532}
533
534/// Parse a single join branch: [annotations] [label:] expression
535fn parse_join_branch(pair: Pair<Rule>) -> Result<JoinBranch> {
536    let inner_pairs: Vec<_> = pair.into_inner().collect();
537
538    let mut annotations = Vec::new();
539    let mut label = None;
540    let mut expr = None;
541
542    // The grammar alternatives for join_branch produce different inner sequences:
543    // 1. annotation+ ident ":" expression -> annotations, label, expr
544    // 2. annotation+ expression -> annotations, expr
545    // 3. ident ":" expression -> label, expr
546    // 4. expression -> expr
547    //
548    // We can distinguish by checking types in order.
549    let mut i = 0;
550
551    // Collect leading annotations
552    while i < inner_pairs.len() && inner_pairs[i].as_rule() == Rule::annotation {
553        annotations.push(crate::parser::functions::parse_annotation(
554            inner_pairs[i].clone(),
555        )?);
556        i += 1;
557    }
558
559    // Check if next is ident followed by expression (label case)
560    if i < inner_pairs.len() && inner_pairs[i].as_rule() == Rule::ident {
561        // Could be label:expr or just an expression that starts with ident
562        // In the grammar, the ident alternative in join_branch is specifically
563        // `ident ~ ":" ~ expression`, so if we get Rule::ident here followed
564        // by Rule::expression, it's a labeled branch.
565        if i + 1 < inner_pairs.len() && inner_pairs[i + 1].as_rule() == Rule::expression {
566            label = Some(inner_pairs[i].as_str().to_string());
567            i += 1;
568            expr = Some(parse_expression(inner_pairs[i].clone())?);
569        } else {
570            // Shouldn't happen with correct grammar, but handle gracefully
571            expr = Some(parse_expression(inner_pairs[i].clone())?);
572        }
573    } else if i < inner_pairs.len() {
574        expr = Some(parse_expression(inner_pairs[i].clone())?);
575    }
576
577    let expr = expr.ok_or_else(|| ShapeError::ParseError {
578        message: "expected expression in join branch".to_string(),
579        location: None,
580    })?;
581
582    Ok(JoinBranch {
583        label,
584        expr,
585        annotations,
586    })
587}
588
589/// Parse struct literal: TypeName { field: value, ... }
590///
591/// Grammar: `ident ~ "{" ~ object_fields? ~ "}"`
592/// Reuses object_fields parsing (same as object literals and enum struct payloads)
593fn parse_struct_literal(pair: Pair<Rule>) -> Result<Expr> {
594    let span = pair_span(&pair);
595    let pair_loc = pair_location(&pair);
596    let mut inner = pair.into_inner();
597
598    let name_pair = inner.next().ok_or_else(|| ShapeError::ParseError {
599        message: "expected struct type name".to_string(),
600        location: Some(pair_loc.clone()),
601    })?;
602    let type_name = name_pair.as_str().to_string();
603
604    let mut fields = Vec::new();
605
606    // Parse optional object_fields
607    if let Some(fields_pair) = inner.next() {
608        if fields_pair.as_rule() == Rule::object_fields {
609            for field_item_pair in fields_pair.into_inner() {
610                if field_item_pair.as_rule() != Rule::object_field_item {
611                    continue;
612                }
613                let field_item_loc = pair_location(&field_item_pair);
614                let field_item_inner =
615                    field_item_pair
616                        .into_inner()
617                        .next()
618                        .ok_or_else(|| ShapeError::ParseError {
619                            message: "expected struct field content".to_string(),
620                            location: Some(field_item_loc.clone()),
621                        })?;
622                match field_item_inner.as_rule() {
623                    Rule::object_field => {
624                        let mut field_inner = field_item_inner.into_inner();
625                        let field_kind =
626                            field_inner.next().ok_or_else(|| ShapeError::ParseError {
627                                message: "expected struct field content".to_string(),
628                                location: Some(field_item_loc.clone()),
629                            })?;
630                        match field_kind.as_rule() {
631                            Rule::object_value_field => {
632                                let mut value_inner = field_kind.into_inner();
633                                let key_pair =
634                                    value_inner.next().ok_or_else(|| ShapeError::ParseError {
635                                        message: "expected struct field key".to_string(),
636                                        location: Some(field_item_loc.clone()),
637                                    })?;
638                                let key_pair = if key_pair.as_rule() == Rule::object_field_name {
639                                    key_pair.into_inner().next().ok_or_else(|| {
640                                        ShapeError::ParseError {
641                                            message: "expected struct field key".to_string(),
642                                            location: Some(field_item_loc.clone()),
643                                        }
644                                    })?
645                                } else {
646                                    key_pair
647                                };
648                                let key = key_pair.as_str().to_string();
649                                let value_pair =
650                                    value_inner.next().ok_or_else(|| ShapeError::ParseError {
651                                        message: format!(
652                                            "expected value for struct field '{}'",
653                                            key
654                                        ),
655                                        location: Some(field_item_loc),
656                                    })?;
657                                let value = parse_expression(value_pair)?;
658                                fields.push((key, value));
659                            }
660                            _ => {
661                                return Err(ShapeError::ParseError {
662                                    message: "typed fields and spreads are not supported in struct literals".to_string(),
663                                    location: Some(field_item_loc),
664                                });
665                            }
666                        }
667                    }
668                    Rule::object_spread => {
669                        return Err(ShapeError::ParseError {
670                            message: "spread is not supported in struct literals".to_string(),
671                            location: Some(field_item_loc),
672                        });
673                    }
674                    _ => {}
675                }
676            }
677        }
678    }
679
680    Ok(Expr::StructLiteral {
681        type_name: type_name.into(),
682        fields,
683        span,
684    })
685}
686
687fn parse_enum_constructor_expr(pair: Pair<Rule>) -> Result<Expr> {
688    let span = pair_span(&pair);
689    let pair_loc = pair_location(&pair);
690    let mut inner = pair.into_inner();
691
692    let path_pair = inner.next().ok_or_else(|| ShapeError::ParseError {
693        message: "expected enum variant path".to_string(),
694        location: Some(pair_loc.clone()),
695    })?;
696    let (enum_name, variant) = parse_enum_variant_path(path_pair)?;
697
698    let payload = if let Some(payload_pair) = inner.next() {
699        match payload_pair.as_rule() {
700            Rule::enum_tuple_payload => {
701                let (args, named_args) = super::functions::parse_arg_list(payload_pair)?;
702                if !named_args.is_empty() {
703                    return Err(ShapeError::ParseError {
704                        message: "named arguments are not allowed in enum tuple constructors"
705                            .to_string(),
706                        location: Some(pair_loc),
707                    });
708                }
709                EnumConstructorPayload::Tuple(args)
710            }
711            Rule::enum_struct_payload => {
712                let fields = parse_enum_struct_payload(payload_pair)?;
713                EnumConstructorPayload::Struct(fields)
714            }
715            other => {
716                return Err(ShapeError::ParseError {
717                    message: format!("unexpected enum constructor payload: {:?}", other),
718                    location: Some(pair_loc),
719                });
720            }
721        }
722    } else {
723        EnumConstructorPayload::Unit
724    };
725
726    Ok(Expr::EnumConstructor {
727        enum_name: enum_name.into(),
728        variant,
729        payload,
730        span,
731    })
732}
733
734fn parse_enum_variant_path(pair: Pair<Rule>) -> Result<(String, String)> {
735    let pair_loc = pair_location(&pair);
736    let segments: Vec<&str> = pair
737        .into_inner()
738        .filter(|p| p.as_rule() == Rule::ident || p.as_rule() == Rule::variant_ident)
739        .map(|p| p.as_str())
740        .collect();
741    if segments.len() < 2 {
742        return Err(ShapeError::ParseError {
743            message: "expected at least Enum::Variant in path".to_string(),
744            location: Some(pair_loc),
745        });
746    }
747    let variant = segments.last().unwrap().to_string();
748    let enum_path = segments[..segments.len() - 1].join("::");
749    Ok((enum_path, variant))
750}
751
752fn parse_enum_struct_payload(pair: Pair<Rule>) -> Result<Vec<(String, Expr)>> {
753    let mut fields = Vec::new();
754
755    for inner in pair.into_inner() {
756        if inner.as_rule() != Rule::object_fields {
757            continue;
758        }
759        for field_item_pair in inner.into_inner() {
760            if field_item_pair.as_rule() != Rule::object_field_item {
761                continue;
762            }
763            let field_item_loc = pair_location(&field_item_pair);
764            let field_item_inner =
765                field_item_pair
766                    .into_inner()
767                    .next()
768                    .ok_or_else(|| ShapeError::ParseError {
769                        message: "expected enum struct field content".to_string(),
770                        location: Some(field_item_loc.clone()),
771                    })?;
772            match field_item_inner.as_rule() {
773                Rule::object_field => {
774                    let mut field_inner = field_item_inner.into_inner();
775                    let field_kind = field_inner.next().ok_or_else(|| ShapeError::ParseError {
776                        message: "expected enum struct field content".to_string(),
777                        location: Some(field_item_loc.clone()),
778                    })?;
779                    match field_kind.as_rule() {
780                        Rule::object_value_field => {
781                            let mut value_inner = field_kind.into_inner();
782                            let key_pair =
783                                value_inner.next().ok_or_else(|| ShapeError::ParseError {
784                                    message: "expected enum struct field key".to_string(),
785                                    location: Some(field_item_loc.clone()),
786                                })?;
787                            let key_pair = if key_pair.as_rule() == Rule::object_field_name {
788                                key_pair.into_inner().next().ok_or_else(|| {
789                                    ShapeError::ParseError {
790                                        message: "expected enum struct field key".to_string(),
791                                        location: Some(field_item_loc.clone()),
792                                    }
793                                })?
794                            } else {
795                                key_pair
796                            };
797                            let key = match key_pair.as_rule() {
798                                Rule::ident | Rule::keyword => key_pair.as_str().to_string(),
799                                _ => {
800                                    return Err(ShapeError::ParseError {
801                                        message: "unexpected enum struct field key".to_string(),
802                                        location: Some(pair_location(&key_pair)),
803                                    });
804                                }
805                            };
806                            let value_pair =
807                                value_inner.next().ok_or_else(|| ShapeError::ParseError {
808                                    message: format!("expected value for enum field '{}'", key),
809                                    location: Some(field_item_loc),
810                                })?;
811                            let value = parse_expression(value_pair)?;
812                            fields.push((key, value));
813                        }
814                        Rule::object_typed_field => {
815                            return Err(ShapeError::ParseError {
816                                message: "typed fields are not allowed in enum constructors"
817                                    .to_string(),
818                                location: Some(field_item_loc),
819                            });
820                        }
821                        other => {
822                            return Err(ShapeError::ParseError {
823                                message: format!("unexpected enum field kind: {:?}", other),
824                                location: Some(field_item_loc),
825                            });
826                        }
827                    }
828                }
829                Rule::object_spread => {
830                    return Err(ShapeError::ParseError {
831                        message: "spread fields are not allowed in enum constructors".to_string(),
832                        location: Some(field_item_loc),
833                    });
834                }
835                other => {
836                    return Err(ShapeError::ParseError {
837                        message: format!("unexpected enum struct field: {:?}", other),
838                        location: Some(field_item_loc),
839                    });
840                }
841            }
842        }
843    }
844
845    Ok(fields)
846}
847
848/// Parse comptime block: `comptime { stmts }`
849///
850/// Grammar: `comptime_block = { "comptime" ~ block_expr }`
851/// Produces `Expr::Comptime(Vec<Statement>, Span)`.
852fn parse_comptime_block(pair: Pair<Rule>) -> Result<Expr> {
853    let span = pair_span(&pair);
854    let pair_loc = pair_location(&pair);
855    let block_pair = pair
856        .into_inner()
857        .next()
858        .ok_or_else(|| ShapeError::ParseError {
859            message: "expected block after 'comptime'".to_string(),
860            location: Some(pair_loc),
861        })?;
862
863    // Parse the block_expr into an Expr::Block, then extract statements
864    let block_expr = super::control_flow::parse_block_expr(block_pair)?;
865    let stmts = block_items_to_statements(block_expr, span);
866    Ok(Expr::Comptime(stmts, span))
867}
868
869/// Parse comptime for expression: `comptime for field in target.fields { stmts }`
870///
871/// Grammar: `comptime_for_expr = { "comptime" ~ "for" ~ ident ~ "in" ~ postfix_expr ~ "{" ~ statement* ~ "}" }`
872/// Produces `Expr::ComptimeFor(ComptimeForExpr, Span)`.
873fn parse_comptime_for_expr(pair: Pair<Rule>) -> Result<Expr> {
874    use crate::ast::expr_helpers::ComptimeForExpr;
875    use crate::parser::statements::parse_statement;
876
877    let span = pair_span(&pair);
878    let pair_loc = pair_location(&pair);
879    let mut inner = pair.into_inner();
880
881    // Parse loop variable name
882    let var_pair = inner.next().ok_or_else(|| ShapeError::ParseError {
883        message: "expected loop variable in comptime for".to_string(),
884        location: Some(pair_loc.clone()),
885    })?;
886    let variable = var_pair.as_str().to_string();
887
888    // Parse iterable expression (postfix_expr in grammar)
889    let iter_pair = inner.next().ok_or_else(|| ShapeError::ParseError {
890        message: "expected iterable expression in comptime for".to_string(),
891        location: Some(pair_loc),
892    })?;
893    let iterable = parse_postfix_expr(iter_pair)?;
894
895    // Parse body statements
896    let mut body = Vec::new();
897    for stmt_pair in inner {
898        if stmt_pair.as_rule() == Rule::statement {
899            body.push(parse_statement(stmt_pair)?);
900        }
901    }
902
903    Ok(Expr::ComptimeFor(
904        Box::new(ComptimeForExpr {
905            variable,
906            iterable: Box::new(iterable),
907            body,
908        }),
909        span,
910    ))
911}
912
913/// Convert a Block expression (from block_expr parsing) into a Vec<Statement>.
914/// The last expression in a block becomes a return statement so the comptime
915/// mini-VM can capture the result.
916pub(crate) fn block_items_to_statements(
917    block_expr: Expr,
918    span: Span,
919) -> Vec<crate::ast::Statement> {
920    use crate::ast::{BlockItem, Statement};
921
922    let items = match block_expr {
923        Expr::Block(block, _) => block.items,
924        // If it's a single expression (empty block rules can produce this), wrap it
925        other => return vec![Statement::Return(Some(other), span)],
926    };
927
928    let mut stmts = Vec::new();
929    let len = items.len();
930    for (i, item) in items.into_iter().enumerate() {
931        let is_last = i == len - 1;
932        match item {
933            BlockItem::VariableDecl(decl) => {
934                stmts.push(Statement::VariableDecl(decl, span));
935            }
936            BlockItem::Assignment(assign) => {
937                stmts.push(Statement::Assignment(assign, span));
938            }
939            BlockItem::Statement(stmt) => {
940                stmts.push(stmt);
941            }
942            BlockItem::Expression(expr) => {
943                if is_last {
944                    // Last expression becomes the return value
945                    stmts.push(Statement::Return(Some(expr), span));
946                } else {
947                    stmts.push(Statement::Expression(expr, span));
948                }
949            }
950        }
951    }
952    stmts
953}
954
955/// Parse annotated expression: `@annotation expr`
956///
957/// Grammar: `annotated_expr = { annotation+ ~ postfix_expr }`
958/// Produces nested `Expr::Annotated { annotation, target, span }`.
959/// Multiple annotations nest left-to-right:
960/// `@retry(3) @timeout(5s) fetch()` becomes
961/// `Annotated { @retry(3), target: Annotated { @timeout(5s), target: fetch() } }`
962fn parse_annotated_expr(pair: Pair<Rule>) -> Result<Expr> {
963    let span = pair_span(&pair);
964    let pair_loc = pair_location(&pair);
965    let inner_pairs: Vec<_> = pair.into_inner().collect();
966
967    if inner_pairs.is_empty() {
968        return Err(ShapeError::ParseError {
969            message: "expected annotations and expression".to_string(),
970            location: Some(pair_loc),
971        });
972    }
973
974    // Collect annotations (all but the last pair which is the target expression)
975    let mut annotations = Vec::new();
976    let mut target_pair = None;
977    for p in &inner_pairs {
978        match p.as_rule() {
979            Rule::annotation => {
980                annotations.push(crate::parser::functions::parse_annotation(p.clone())?);
981            }
982            _ => {
983                target_pair = Some(p.clone());
984            }
985        }
986    }
987
988    let target_pair = target_pair.ok_or_else(|| ShapeError::ParseError {
989        message: "expected expression after annotations".to_string(),
990        location: Some(pair_loc),
991    })?;
992    let target = parse_postfix_expr(target_pair)?;
993
994    // Wrap the target in nested Annotated expressions (right-to-left)
995    // @a @b expr -> Annotated(@a, Annotated(@b, expr))
996    let mut result = target;
997    for annotation in annotations.into_iter().rev() {
998        result = Expr::Annotated {
999            annotation,
1000            target: Box::new(result),
1001            span,
1002        };
1003    }
1004
1005    Ok(result)
1006}
1007
1008// Note: backtest { } block syntax has been removed.
1009// Use backtest(strategy, { config }) function instead.