Skip to main content

shape_ast/parser/expressions/
binary_ops.rs

1//! Binary operator expression parsing
2//!
3//! This module handles parsing of all binary operators with proper precedence:
4//! - Pipe operator (|>)
5//! - Ternary operator (?:)
6//! - Null coalescing (??)
7//! - Error context wrapping (!!)
8//! - Logical operators (||, &&)
9//! - Comparison operators (>, <, >=, <=, ==, !=, ~=, ~>, ~<)
10//! - Range operator (..)
11//! - Arithmetic operators (+, -, *, /, %, ^)
12
13use super::super::pair_span;
14use crate::ast::operators::{FuzzyOp, FuzzyTolerance};
15use crate::ast::{AssignExpr, BinaryOp, Expr, IfExpr, RangeKind, Span, UnaryOp};
16use crate::error::{Result, ShapeError};
17use crate::parser::{Rule, pair_location};
18use pest::iterators::Pair;
19
20/// Parse pipe expression (a |> b |> c)
21/// Pipes the left value into the right function
22pub fn parse_pipe_expr(pair: Pair<Rule>) -> Result<Expr> {
23    let span = pair_span(&pair);
24    let pair_loc = pair_location(&pair);
25    let mut inner = pair.into_inner();
26    let first = inner.next().ok_or_else(|| ShapeError::ParseError {
27        message: "expected expression in pipe".to_string(),
28        location: Some(pair_loc),
29    })?;
30    let mut left = parse_ternary_expr(first)?;
31
32    // Chain pipe operations: left |> right |> more
33    for ternary_pair in inner {
34        let right = parse_ternary_expr(ternary_pair)?;
35        left = Expr::BinaryOp {
36            left: Box::new(left),
37            op: BinaryOp::Pipe,
38            right: Box::new(right),
39            span,
40        };
41    }
42
43    Ok(left)
44}
45
46/// Parse ternary expression (condition ? then : else)
47pub fn parse_ternary_expr(pair: Pair<Rule>) -> Result<Expr> {
48    let span = pair_span(&pair);
49    let pair_loc = pair_location(&pair);
50    let mut inner = pair.into_inner();
51    let condition_pair = inner.next().ok_or_else(|| ShapeError::ParseError {
52        message: "expected condition expression in ternary".to_string(),
53        location: Some(pair_loc.clone()),
54    })?;
55    let condition_expr = parse_null_coalesce_expr(condition_pair)?;
56
57    // Check if we have a ternary operator
58    if let Some(then_pair) = inner.next() {
59        // We have ? expr : expr
60        let then_expr = parse_ternary_branch(then_pair)?;
61        let else_pair = inner.next().ok_or_else(|| ShapeError::ParseError {
62            message: "expected else expression after ':' in ternary".to_string(),
63            location: Some(pair_loc),
64        })?;
65        let else_expr = parse_ternary_branch(else_pair)?;
66
67        Ok(Expr::If(
68            Box::new(IfExpr {
69                condition: Box::new(condition_expr),
70                then_branch: Box::new(then_expr),
71                else_branch: Some(Box::new(else_expr)),
72            }),
73            span,
74        ))
75    } else {
76        // No ternary, just return the null_coalesce_expr
77        Ok(condition_expr)
78    }
79}
80
81fn parse_ternary_branch(pair: Pair<Rule>) -> Result<Expr> {
82    let pair_loc = pair_location(&pair);
83    match pair.as_rule() {
84        Rule::ternary_branch => {
85            let inner = pair
86                .into_inner()
87                .next()
88                .ok_or_else(|| ShapeError::ParseError {
89                    message: "expected expression in ternary branch".to_string(),
90                    location: Some(pair_loc),
91                })?;
92            parse_assignment_expr_no_range(inner)
93        }
94        Rule::assignment_expr_no_range => parse_assignment_expr_no_range(pair),
95        _ => super::primary::parse_expression(pair),
96    }
97}
98
99/// Map compound assignment operator string to BinaryOp
100fn compound_op_to_binary(op_str: &str) -> Option<BinaryOp> {
101    match op_str {
102        "+=" => Some(BinaryOp::Add),
103        "-=" => Some(BinaryOp::Sub),
104        "*=" => Some(BinaryOp::Mul),
105        "/=" => Some(BinaryOp::Div),
106        "%=" => Some(BinaryOp::Mod),
107        "**=" => Some(BinaryOp::Pow),
108        "^=" => Some(BinaryOp::BitXor),
109        "&=" => Some(BinaryOp::BitAnd),
110        "|=" => Some(BinaryOp::BitOr),
111        "<<=" => Some(BinaryOp::BitShl),
112        ">>=" => Some(BinaryOp::BitShr),
113        _ => None,
114    }
115}
116
117/// Parse assignment expression (target = value or target += value)
118pub fn parse_assignment_expr(pair: Pair<Rule>) -> Result<Expr> {
119    let span = pair_span(&pair);
120    let pair_loc = pair_location(&pair);
121    let mut inner = pair.into_inner();
122    let first = inner.next().ok_or_else(|| ShapeError::ParseError {
123        message: "expected expression".to_string(),
124        location: Some(pair_loc.clone()),
125    })?;
126
127    if let Some(second) = inner.next() {
128        // Check if second pair is a compound_assign_op
129        if second.as_rule() == Rule::compound_assign_op {
130            let target = super::primary::parse_postfix_expr(first)?;
131            if !matches!(
132                target,
133                Expr::Identifier(_, _) | Expr::PropertyAccess { .. } | Expr::IndexAccess { .. }
134            ) {
135                return Err(ShapeError::ParseError {
136                    message: "invalid assignment target".to_string(),
137                    location: Some(pair_loc),
138                });
139            }
140            let bin_op =
141                compound_op_to_binary(second.as_str()).ok_or_else(|| ShapeError::ParseError {
142                    message: format!("Unknown compound operator: {}", second.as_str()),
143                    location: None,
144                })?;
145            let value_pair = inner.next().ok_or_else(|| ShapeError::ParseError {
146                message: "expected value after compound assignment".to_string(),
147                location: None,
148            })?;
149            let value = parse_assignment_expr(value_pair)?;
150            // Desugar: x += v → x = x + v
151            let desugared = Expr::BinaryOp {
152                left: Box::new(target.clone()),
153                op: bin_op,
154                right: Box::new(value),
155                span,
156            };
157            Ok(Expr::Assign(
158                Box::new(AssignExpr {
159                    target: Box::new(target),
160                    value: Box::new(desugared),
161                }),
162                span,
163            ))
164        } else if second.as_rule() == Rule::assign_op {
165            // Plain assignment: target assign_op value
166            let target = super::primary::parse_postfix_expr(first)?;
167            if !matches!(
168                target,
169                Expr::Identifier(_, _) | Expr::PropertyAccess { .. } | Expr::IndexAccess { .. }
170            ) {
171                return Err(ShapeError::ParseError {
172                    message: "invalid assignment target".to_string(),
173                    location: Some(pair_loc),
174                });
175            }
176            let value_pair = inner.next().ok_or_else(|| ShapeError::ParseError {
177                message: "expected value after assignment".to_string(),
178                location: None,
179            })?;
180            let value = parse_assignment_expr(value_pair)?;
181            Ok(Expr::Assign(
182                Box::new(AssignExpr {
183                    target: Box::new(target),
184                    value: Box::new(value),
185                }),
186                span,
187            ))
188        } else {
189            // Fallback: parse as pipe expression
190            match first.as_rule() {
191                Rule::pipe_expr => parse_pipe_expr(first),
192                Rule::ternary_expr => parse_ternary_expr(first),
193                _ => parse_pipe_expr(first),
194            }
195        }
196    } else {
197        // Check if this is a pipe_expr rule
198        match first.as_rule() {
199            Rule::pipe_expr => parse_pipe_expr(first),
200            Rule::ternary_expr => parse_ternary_expr(first),
201            _ => parse_pipe_expr(first),
202        }
203    }
204}
205
206fn parse_assignment_expr_no_range(pair: Pair<Rule>) -> Result<Expr> {
207    let span = pair_span(&pair);
208    let pair_loc = pair_location(&pair);
209    let mut inner = pair.into_inner();
210    let first = inner.next().ok_or_else(|| ShapeError::ParseError {
211        message: "expected expression".to_string(),
212        location: Some(pair_loc.clone()),
213    })?;
214
215    if let Some(second) = inner.next() {
216        if second.as_rule() == Rule::compound_assign_op {
217            let target = super::primary::parse_postfix_expr(first)?;
218            if !matches!(
219                target,
220                Expr::Identifier(_, _) | Expr::PropertyAccess { .. } | Expr::IndexAccess { .. }
221            ) {
222                return Err(ShapeError::ParseError {
223                    message: "invalid assignment target".to_string(),
224                    location: Some(pair_loc),
225                });
226            }
227            let bin_op =
228                compound_op_to_binary(second.as_str()).ok_or_else(|| ShapeError::ParseError {
229                    message: format!("Unknown compound operator: {}", second.as_str()),
230                    location: None,
231                })?;
232            let value_pair = inner.next().ok_or_else(|| ShapeError::ParseError {
233                message: "expected value after compound assignment".to_string(),
234                location: None,
235            })?;
236            let value = parse_assignment_expr_no_range(value_pair)?;
237            let desugared = Expr::BinaryOp {
238                left: Box::new(target.clone()),
239                op: bin_op,
240                right: Box::new(value),
241                span,
242            };
243            Ok(Expr::Assign(
244                Box::new(AssignExpr {
245                    target: Box::new(target),
246                    value: Box::new(desugared),
247                }),
248                span,
249            ))
250        } else if second.as_rule() == Rule::assign_op {
251            // Plain assignment: target assign_op value
252            let target = super::primary::parse_postfix_expr(first)?;
253            if !matches!(
254                target,
255                Expr::Identifier(_, _) | Expr::PropertyAccess { .. } | Expr::IndexAccess { .. }
256            ) {
257                return Err(ShapeError::ParseError {
258                    message: "invalid assignment target".to_string(),
259                    location: Some(pair_loc),
260                });
261            }
262            let value_pair = inner.next().ok_or_else(|| ShapeError::ParseError {
263                message: "expected value after assignment".to_string(),
264                location: None,
265            })?;
266            let value = parse_assignment_expr_no_range(value_pair)?;
267            Ok(Expr::Assign(
268                Box::new(AssignExpr {
269                    target: Box::new(target),
270                    value: Box::new(value),
271                }),
272                span,
273            ))
274        } else {
275            // Fallback: parse as null coalesce expression
276            parse_null_coalesce_expr_no_range(first)
277        }
278    } else {
279        parse_null_coalesce_expr_no_range(first)
280    }
281}
282
283/// Parse null coalescing expression (a ?? b)
284pub fn parse_null_coalesce_expr(pair: Pair<Rule>) -> Result<Expr> {
285    let span = pair_span(&pair);
286    let pair_loc = pair_location(&pair);
287    let mut inner = pair.into_inner();
288    let first = inner.next().ok_or_else(|| ShapeError::ParseError {
289        message: "expected expression in null coalesce".to_string(),
290        location: Some(pair_loc),
291    })?;
292    let mut left = parse_context_expr(first)?;
293
294    for context_expr in inner {
295        let right = parse_context_expr(context_expr)?;
296        left = Expr::BinaryOp {
297            left: Box::new(left),
298            op: BinaryOp::NullCoalesce,
299            right: Box::new(right),
300            span,
301        };
302    }
303
304    Ok(left)
305}
306
307fn parse_null_coalesce_expr_no_range(pair: Pair<Rule>) -> Result<Expr> {
308    let span = pair_span(&pair);
309    let pair_loc = pair_location(&pair);
310    let mut inner = pair.into_inner();
311    let first = inner.next().ok_or_else(|| ShapeError::ParseError {
312        message: "expected expression".to_string(),
313        location: Some(pair_loc),
314    })?;
315    let mut left = parse_context_expr_no_range(first)?;
316
317    for context_expr in inner {
318        let right = parse_context_expr_no_range(context_expr)?;
319        left = Expr::BinaryOp {
320            left: Box::new(left),
321            op: BinaryOp::NullCoalesce,
322            right: Box::new(right),
323            span,
324        };
325    }
326
327    Ok(left)
328}
329
330/// Parse error context expression (lhs !! rhs).
331pub fn parse_context_expr(pair: Pair<Rule>) -> Result<Expr> {
332    let span = pair_span(&pair);
333    let pair_loc = pair_location(&pair);
334    let mut inner = pair.into_inner();
335    let first = inner.next().ok_or_else(|| ShapeError::ParseError {
336        message: "expected expression in error context".to_string(),
337        location: Some(pair_loc),
338    })?;
339    let mut left = parse_or_expr(first)?;
340
341    for or_expr in inner {
342        let rhs_source = or_expr.as_str().trim().to_string();
343        let right = parse_or_expr(or_expr)?;
344        let is_grouped_rhs = rhs_source.starts_with('(') && rhs_source.ends_with(')');
345
346        match right {
347            Expr::TryOperator(inner_try, try_span) if !is_grouped_rhs => {
348                // Ergonomic special-case: `lhs !! rhs?` means `(lhs !! rhs)?`.
349                // Use explicit parentheses for `lhs !! (rhs?)`.
350                let context_expr = Expr::BinaryOp {
351                    left: Box::new(left),
352                    op: BinaryOp::ErrorContext,
353                    right: inner_try,
354                    span,
355                };
356                left = Expr::TryOperator(Box::new(context_expr), try_span);
357            }
358            right => {
359                left = Expr::BinaryOp {
360                    left: Box::new(left),
361                    op: BinaryOp::ErrorContext,
362                    right: Box::new(right),
363                    span,
364                };
365            }
366        }
367    }
368
369    Ok(left)
370}
371
372fn parse_context_expr_no_range(pair: Pair<Rule>) -> Result<Expr> {
373    let span = pair_span(&pair);
374    let pair_loc = pair_location(&pair);
375    let mut inner = pair.into_inner();
376    let first = inner.next().ok_or_else(|| ShapeError::ParseError {
377        message: "expected expression".to_string(),
378        location: Some(pair_loc),
379    })?;
380    let mut left = parse_or_expr_no_range(first)?;
381
382    for or_expr in inner {
383        let rhs_source = or_expr.as_str().trim().to_string();
384        let right = parse_or_expr_no_range(or_expr)?;
385        let is_grouped_rhs = rhs_source.starts_with('(') && rhs_source.ends_with(')');
386
387        match right {
388            Expr::TryOperator(inner_try, try_span) if !is_grouped_rhs => {
389                // Keep context + try ergonomic in ternary branches too.
390                let context_expr = Expr::BinaryOp {
391                    left: Box::new(left),
392                    op: BinaryOp::ErrorContext,
393                    right: inner_try,
394                    span,
395                };
396                left = Expr::TryOperator(Box::new(context_expr), try_span);
397            }
398            right => {
399                left = Expr::BinaryOp {
400                    left: Box::new(left),
401                    op: BinaryOp::ErrorContext,
402                    right: Box::new(right),
403                    span,
404                };
405            }
406        }
407    }
408
409    Ok(left)
410}
411
412/// Parse logical OR expression (a || b)
413pub fn parse_or_expr(pair: Pair<Rule>) -> Result<Expr> {
414    let span = pair_span(&pair);
415    let pair_loc = pair_location(&pair);
416    let mut inner = pair.into_inner();
417    let first = inner.next().ok_or_else(|| ShapeError::ParseError {
418        message: "expected expression in logical OR".to_string(),
419        location: Some(pair_loc),
420    })?;
421    let mut left = parse_and_expr(first)?;
422
423    for and_expr in inner {
424        let right = parse_and_expr(and_expr)?;
425        left = Expr::BinaryOp {
426            left: Box::new(left),
427            op: BinaryOp::Or,
428            right: Box::new(right),
429            span,
430        };
431    }
432
433    Ok(left)
434}
435
436fn parse_or_expr_no_range(pair: Pair<Rule>) -> Result<Expr> {
437    let span = pair_span(&pair);
438    let pair_loc = pair_location(&pair);
439    let mut inner = pair.into_inner();
440    let first = inner.next().ok_or_else(|| ShapeError::ParseError {
441        message: "expected expression".to_string(),
442        location: Some(pair_loc),
443    })?;
444    let mut left = parse_and_expr_no_range(first)?;
445
446    for and_expr in inner {
447        let right = parse_and_expr_no_range(and_expr)?;
448        left = Expr::BinaryOp {
449            left: Box::new(left),
450            op: BinaryOp::Or,
451            right: Box::new(right),
452            span,
453        };
454    }
455
456    Ok(left)
457}
458
459/// Parse logical AND expression (a && b)
460pub fn parse_and_expr(pair: Pair<Rule>) -> Result<Expr> {
461    let span = pair_span(&pair);
462    let pair_loc = pair_location(&pair);
463    let mut inner = pair.into_inner();
464    let first = inner.next().ok_or_else(|| ShapeError::ParseError {
465        message: "expected expression in logical AND".to_string(),
466        location: Some(pair_loc),
467    })?;
468    let mut left = parse_bitwise_or_expr(first)?;
469
470    for expr in inner {
471        let right = parse_bitwise_or_expr(expr)?;
472        left = Expr::BinaryOp {
473            left: Box::new(left),
474            op: BinaryOp::And,
475            right: Box::new(right),
476            span,
477        };
478    }
479
480    Ok(left)
481}
482
483fn parse_and_expr_no_range(pair: Pair<Rule>) -> Result<Expr> {
484    let span = pair_span(&pair);
485    let pair_loc = pair_location(&pair);
486    let mut inner = pair.into_inner();
487    let first = inner.next().ok_or_else(|| ShapeError::ParseError {
488        message: "expected expression".to_string(),
489        location: Some(pair_loc),
490    })?;
491    let mut left = parse_bitwise_or_expr_no_range(first)?;
492
493    for expr in inner {
494        let right = parse_bitwise_or_expr_no_range(expr)?;
495        left = Expr::BinaryOp {
496            left: Box::new(left),
497            op: BinaryOp::And,
498            right: Box::new(right),
499            span,
500        };
501    }
502
503    Ok(left)
504}
505
506/// Parse bitwise OR expression (a | b)
507fn parse_bitwise_or_expr(pair: Pair<Rule>) -> Result<Expr> {
508    let span = pair_span(&pair);
509    let pair_loc = pair_location(&pair);
510    let mut inner = pair.into_inner();
511    let first = inner.next().ok_or_else(|| ShapeError::ParseError {
512        message: "expected expression in bitwise OR".to_string(),
513        location: Some(pair_loc),
514    })?;
515    let mut left = parse_bitwise_xor_expr(first)?;
516
517    for expr in inner {
518        let right = parse_bitwise_xor_expr(expr)?;
519        left = Expr::BinaryOp {
520            left: Box::new(left),
521            op: BinaryOp::BitOr,
522            right: Box::new(right),
523            span,
524        };
525    }
526
527    Ok(left)
528}
529
530fn parse_bitwise_or_expr_no_range(pair: Pair<Rule>) -> Result<Expr> {
531    let span = pair_span(&pair);
532    let pair_loc = pair_location(&pair);
533    let mut inner = pair.into_inner();
534    let first = inner.next().ok_or_else(|| ShapeError::ParseError {
535        message: "expected expression".to_string(),
536        location: Some(pair_loc),
537    })?;
538    let mut left = parse_bitwise_xor_expr_no_range(first)?;
539
540    for expr in inner {
541        let right = parse_bitwise_xor_expr_no_range(expr)?;
542        left = Expr::BinaryOp {
543            left: Box::new(left),
544            op: BinaryOp::BitOr,
545            right: Box::new(right),
546            span,
547        };
548    }
549
550    Ok(left)
551}
552
553/// Parse bitwise XOR expression (a ^ b)
554fn parse_bitwise_xor_expr(pair: Pair<Rule>) -> Result<Expr> {
555    let span = pair_span(&pair);
556    let pair_loc = pair_location(&pair);
557    let mut inner = pair.into_inner();
558    let first = inner.next().ok_or_else(|| ShapeError::ParseError {
559        message: "expected expression in bitwise XOR".to_string(),
560        location: Some(pair_loc),
561    })?;
562    let mut left = parse_bitwise_and_expr(first)?;
563
564    for expr in inner {
565        let right = parse_bitwise_and_expr(expr)?;
566        left = Expr::BinaryOp {
567            left: Box::new(left),
568            op: BinaryOp::BitXor,
569            right: Box::new(right),
570            span,
571        };
572    }
573
574    Ok(left)
575}
576
577fn parse_bitwise_xor_expr_no_range(pair: Pair<Rule>) -> Result<Expr> {
578    let span = pair_span(&pair);
579    let pair_loc = pair_location(&pair);
580    let mut inner = pair.into_inner();
581    let first = inner.next().ok_or_else(|| ShapeError::ParseError {
582        message: "expected expression".to_string(),
583        location: Some(pair_loc),
584    })?;
585    let mut left = parse_bitwise_and_expr_no_range(first)?;
586
587    for expr in inner {
588        let right = parse_bitwise_and_expr_no_range(expr)?;
589        left = Expr::BinaryOp {
590            left: Box::new(left),
591            op: BinaryOp::BitXor,
592            right: Box::new(right),
593            span,
594        };
595    }
596
597    Ok(left)
598}
599
600/// Parse bitwise AND expression (a & b)
601fn parse_bitwise_and_expr(pair: Pair<Rule>) -> Result<Expr> {
602    let span = pair_span(&pair);
603    let pair_loc = pair_location(&pair);
604    let mut inner = pair.into_inner();
605    let first = inner.next().ok_or_else(|| ShapeError::ParseError {
606        message: "expected expression in bitwise AND".to_string(),
607        location: Some(pair_loc),
608    })?;
609    let mut left = parse_comparison_expr(first)?;
610
611    for expr in inner {
612        let right = parse_comparison_expr(expr)?;
613        left = Expr::BinaryOp {
614            left: Box::new(left),
615            op: BinaryOp::BitAnd,
616            right: Box::new(right),
617            span,
618        };
619    }
620
621    Ok(left)
622}
623
624fn parse_bitwise_and_expr_no_range(pair: Pair<Rule>) -> Result<Expr> {
625    let span = pair_span(&pair);
626    let pair_loc = pair_location(&pair);
627    let mut inner = pair.into_inner();
628    let first = inner.next().ok_or_else(|| ShapeError::ParseError {
629        message: "expected expression".to_string(),
630        location: Some(pair_loc),
631    })?;
632    let mut left = parse_comparison_expr_no_range(first)?;
633
634    for expr in inner {
635        let right = parse_comparison_expr_no_range(expr)?;
636        left = Expr::BinaryOp {
637            left: Box::new(left),
638            op: BinaryOp::BitAnd,
639            right: Box::new(right),
640            span,
641        };
642    }
643
644    Ok(left)
645}
646
647/// Parse comparison expression (a > b, a == b, etc.)
648pub fn parse_comparison_expr(pair: Pair<Rule>) -> Result<Expr> {
649    let span = pair_span(&pair);
650    let pair_loc = pair_location(&pair);
651    let mut inner = pair.into_inner();
652    let first = inner.next().ok_or_else(|| ShapeError::ParseError {
653        message: "expected expression in comparison".to_string(),
654        location: Some(pair_loc),
655    })?;
656    let mut left = parse_range_expr(first)?;
657
658    for tail in inner {
659        left = apply_comparison_tail(left, tail, span, parse_range_expr)?;
660    }
661
662    Ok(left)
663}
664
665fn parse_comparison_expr_no_range(pair: Pair<Rule>) -> Result<Expr> {
666    let span = pair_span(&pair);
667    let pair_loc = pair_location(&pair);
668    let mut inner = pair.into_inner();
669    let first = inner.next().ok_or_else(|| ShapeError::ParseError {
670        message: "expected expression".to_string(),
671        location: Some(pair_loc),
672    })?;
673    let mut left = parse_additive_expr(first)?;
674
675    for tail in inner {
676        left = apply_comparison_tail(left, tail, span, parse_additive_expr)?;
677    }
678
679    Ok(left)
680}
681
682fn apply_comparison_tail<F>(left: Expr, tail: Pair<Rule>, span: Span, parse_rhs: F) -> Result<Expr>
683where
684    F: Fn(Pair<Rule>) -> Result<Expr>,
685{
686    let mut tail_inner = tail.into_inner();
687    let first = tail_inner.next().ok_or_else(|| ShapeError::ParseError {
688        message: "Empty comparison tail".to_string(),
689        location: None,
690    })?;
691
692    match first.as_rule() {
693        Rule::fuzzy_comparison_tail | Rule::fuzzy_comparison_tail_no_range => {
694            // Parse fuzzy_comparison_tail: fuzzy_op ~ range_expr ~ within_clause?
695            let mut fuzzy_inner = first.into_inner();
696
697            let fuzzy_op_pair = fuzzy_inner.next().ok_or_else(|| ShapeError::ParseError {
698                message: "Fuzzy comparison missing operator".to_string(),
699                location: None,
700            })?;
701            let op = parse_fuzzy_op(fuzzy_op_pair)?;
702
703            let rhs_pair = fuzzy_inner.next().ok_or_else(|| ShapeError::ParseError {
704                message: "Fuzzy comparison missing right-hand side".to_string(),
705                location: None,
706            })?;
707            let right = parse_rhs(rhs_pair)?;
708
709            // Parse optional within_clause
710            let tolerance = if let Some(within_clause) = fuzzy_inner.next() {
711                parse_within_clause(within_clause)?
712            } else {
713                // Default to 2% tolerance if no explicit tolerance specified
714                FuzzyTolerance::Percentage(0.02)
715            };
716
717            Ok(Expr::FuzzyComparison {
718                left: Box::new(left),
719                op,
720                right: Box::new(right),
721                tolerance,
722                span,
723            })
724        }
725        Rule::comparison_op => {
726            let op = parse_comparison_op(first)?;
727            let rhs_pair = tail_inner.next().ok_or_else(|| ShapeError::ParseError {
728                message: "Comparison operator missing right-hand side".to_string(),
729                location: None,
730            })?;
731            let right = parse_rhs(rhs_pair)?;
732            Ok(Expr::BinaryOp {
733                left: Box::new(left),
734                op,
735                right: Box::new(right),
736                span,
737            })
738        }
739        Rule::type_annotation => {
740            let type_annotation = crate::parser::parse_type_annotation(first)?;
741            Ok(Expr::InstanceOf {
742                expr: Box::new(left),
743                type_annotation,
744                span,
745            })
746        }
747        _ => Err(ShapeError::ParseError {
748            message: format!("Unexpected comparison tail: {:?}", first.as_rule()),
749            location: None,
750        }),
751    }
752}
753
754/// Parse fuzzy operator (~=, ~<, ~>)
755fn parse_fuzzy_op(pair: Pair<Rule>) -> Result<FuzzyOp> {
756    match pair.as_str() {
757        "~=" => Ok(FuzzyOp::Equal),
758        "~>" => Ok(FuzzyOp::Greater),
759        "~<" => Ok(FuzzyOp::Less),
760        _ => Err(ShapeError::ParseError {
761            message: format!("Unknown fuzzy operator: {}", pair.as_str()),
762            location: None,
763        }),
764    }
765}
766
767/// Parse within_clause: "within" ~ tolerance_spec
768fn parse_within_clause(pair: Pair<Rule>) -> Result<FuzzyTolerance> {
769    let mut inner = pair.into_inner();
770    let tolerance_spec = inner.next().ok_or_else(|| ShapeError::ParseError {
771        message: "Within clause missing tolerance value".to_string(),
772        location: None,
773    })?;
774    parse_tolerance_spec(tolerance_spec)
775}
776
777/// Parse tolerance_spec: number ~ "%"?
778fn parse_tolerance_spec(pair: Pair<Rule>) -> Result<FuzzyTolerance> {
779    let text = pair.as_str().trim();
780
781    if text.ends_with('%') {
782        // Percentage tolerance: "2%" or "0.5%"
783        let num_str = text.trim_end_matches('%');
784        let value: f64 = num_str.parse().map_err(|_| ShapeError::ParseError {
785            message: format!("Invalid tolerance percentage: {}", text),
786            location: None,
787        })?;
788        // Convert percentage to fraction (e.g., 2% -> 0.02)
789        Ok(FuzzyTolerance::Percentage(value / 100.0))
790    } else {
791        // Absolute tolerance: "0.02" or "5"
792        let value: f64 = text.parse().map_err(|_| ShapeError::ParseError {
793            message: format!("Invalid tolerance value: {}", text),
794            location: None,
795        })?;
796        Ok(FuzzyTolerance::Absolute(value))
797    }
798}
799
800/// Parse range expression (a..b)
801/// Parse a range expression with Rust-style syntax
802/// Supports: start..end, start..=end, ..end, ..=end, start.., ..
803pub fn parse_range_expr(pair: Pair<Rule>) -> Result<Expr> {
804    let span = pair_span(&pair);
805    let pair_loc = pair_location(&pair);
806    let mut inner = pair.into_inner().peekable();
807
808    // Check if first token is a range_op (for ..end, ..=end, or .. forms)
809    let first = inner.next().ok_or_else(|| ShapeError::ParseError {
810        message: "expected expression in range".to_string(),
811        location: Some(pair_loc.clone()),
812    })?;
813
814    match first.as_rule() {
815        Rule::range_op => {
816            // Forms: ..end, ..=end, or .. (full range)
817            let kind = parse_range_op(&first);
818            if let Some(end_pair) = inner.next() {
819                // ..end or ..=end
820                let end = parse_additive_expr(end_pair)?;
821                Ok(Expr::Range {
822                    start: None,
823                    end: Some(Box::new(end)),
824                    kind,
825                    span,
826                })
827            } else {
828                // Full range: ..
829                Ok(Expr::Range {
830                    start: None,
831                    end: None,
832                    kind,
833                    span,
834                })
835            }
836        }
837        Rule::additive_expr => {
838            // Forms: start..end, start..=end, start.., or just expr
839            let start = parse_additive_expr(first)?;
840
841            if let Some(next) = inner.next() {
842                match next.as_rule() {
843                    Rule::range_op => {
844                        // start..end or start..=end or start..
845                        let kind = parse_range_op(&next);
846                        if let Some(end_pair) = inner.next() {
847                            // start..end or start..=end
848                            let end = parse_additive_expr(end_pair)?;
849                            Ok(Expr::Range {
850                                start: Some(Box::new(start)),
851                                end: Some(Box::new(end)),
852                                kind,
853                                span,
854                            })
855                        } else {
856                            // start.. (range from)
857                            Ok(Expr::Range {
858                                start: Some(Box::new(start)),
859                                end: None,
860                                kind,
861                                span,
862                            })
863                        }
864                    }
865                    _ => {
866                        // Unexpected token after start expression
867                        Err(ShapeError::ParseError {
868                            message: format!(
869                                "unexpected token in range expression: {:?}",
870                                next.as_rule()
871                            ),
872                            location: Some(pair_loc),
873                        })
874                    }
875                }
876            } else {
877                // Just a single expression (not a range)
878                Ok(start)
879            }
880        }
881        _ => {
882            // Try to parse as additive_expr anyway (fallback)
883            parse_additive_expr(first)
884        }
885    }
886}
887
888/// Parse range operator and return RangeKind
889fn parse_range_op(pair: &Pair<Rule>) -> RangeKind {
890    if pair.as_str() == "..=" {
891        RangeKind::Inclusive
892    } else {
893        RangeKind::Exclusive
894    }
895}
896
897/// Parse comparison operator
898pub fn parse_comparison_op(pair: Pair<Rule>) -> Result<BinaryOp> {
899    match pair.as_str() {
900        ">" => Ok(BinaryOp::Greater),
901        "<" => Ok(BinaryOp::Less),
902        ">=" => Ok(BinaryOp::GreaterEq),
903        "<=" => Ok(BinaryOp::LessEq),
904        "==" => Ok(BinaryOp::Equal),
905        "!=" => Ok(BinaryOp::NotEqual),
906        "~=" => Ok(BinaryOp::FuzzyEqual),
907        "~>" => Ok(BinaryOp::FuzzyGreater),
908        "~<" => Ok(BinaryOp::FuzzyLess),
909        _ => Err(ShapeError::ParseError {
910            message: format!("Unknown comparison operator: {}", pair.as_str()),
911            location: None,
912        }),
913    }
914}
915
916/// Parse additive expression (a + b, a - b)
917pub fn parse_additive_expr(pair: Pair<Rule>) -> Result<Expr> {
918    // In Pest, the entire additive_expr contains the full string
919    // We need to parse it by extracting operators from the original string
920    let span = pair_span(&pair);
921    let expr_str = pair.as_str();
922    let inner_pairs: Vec<_> = pair.into_inner().collect();
923
924    if inner_pairs.is_empty() {
925        return Err(ShapeError::ParseError {
926            message: "Empty additive expression".to_string(),
927            location: None,
928        });
929    }
930
931    // Parse the first shift expression
932    let mut left = parse_shift_expr(inner_pairs[0].clone())?;
933
934    // If there's only one pair, no operators
935    if inner_pairs.len() == 1 {
936        return Ok(left);
937    }
938
939    // For expressions with operators, we need to find operators in the original string
940    // between the shift expressions
941    let mut current_pos = inner_pairs[0].as_str().len();
942
943    for i in 1..inner_pairs.len() {
944        // Find the operator between previous and current expression
945        let expr_start = expr_str[current_pos..]
946            .find(inner_pairs[i].as_str())
947            .ok_or_else(|| ShapeError::ParseError {
948                message: "Cannot find expression in string".to_string(),
949                location: None,
950            })?;
951        let op_str = expr_str[current_pos..current_pos + expr_start].trim();
952
953        let right = parse_shift_expr(inner_pairs[i].clone())?;
954
955        left = Expr::BinaryOp {
956            left: Box::new(left),
957            op: match op_str {
958                "+" => BinaryOp::Add,
959                "-" => BinaryOp::Sub,
960                _ => {
961                    return Err(ShapeError::ParseError {
962                        message: format!("Unknown additive operator: '{}'", op_str),
963                        location: None,
964                    });
965                }
966            },
967            right: Box::new(right),
968            span,
969        };
970
971        current_pos += expr_start + inner_pairs[i].as_str().len();
972    }
973
974    Ok(left)
975}
976
977/// Parse shift expression (a << b, a >> b)
978pub fn parse_shift_expr(pair: Pair<Rule>) -> Result<Expr> {
979    let span = pair_span(&pair);
980    let expr_str = pair.as_str();
981    let inner_pairs: Vec<_> = pair.into_inner().collect();
982
983    if inner_pairs.is_empty() {
984        return Err(ShapeError::ParseError {
985            message: "Empty shift expression".to_string(),
986            location: None,
987        });
988    }
989
990    let mut left = parse_multiplicative_expr(inner_pairs[0].clone())?;
991
992    if inner_pairs.len() == 1 {
993        return Ok(left);
994    }
995
996    let mut current_pos = inner_pairs[0].as_str().len();
997
998    for i in 1..inner_pairs.len() {
999        let expr_start = expr_str[current_pos..]
1000            .find(inner_pairs[i].as_str())
1001            .ok_or_else(|| ShapeError::ParseError {
1002                message: "Cannot find expression in string".to_string(),
1003                location: None,
1004            })?;
1005        let op_str = expr_str[current_pos..current_pos + expr_start].trim();
1006
1007        let right = parse_multiplicative_expr(inner_pairs[i].clone())?;
1008
1009        left = Expr::BinaryOp {
1010            left: Box::new(left),
1011            op: match op_str {
1012                "<<" => BinaryOp::BitShl,
1013                ">>" => BinaryOp::BitShr,
1014                _ => {
1015                    return Err(ShapeError::ParseError {
1016                        message: format!("Unknown shift operator: '{}'", op_str),
1017                        location: None,
1018                    });
1019                }
1020            },
1021            right: Box::new(right),
1022            span,
1023        };
1024
1025        current_pos += expr_start + inner_pairs[i].as_str().len();
1026    }
1027
1028    Ok(left)
1029}
1030
1031/// Parse multiplicative expression (a * b, a / b, a % b)
1032pub fn parse_multiplicative_expr(pair: Pair<Rule>) -> Result<Expr> {
1033    // Similar to additive_expr, we need to extract operators from the original string
1034    let span = pair_span(&pair);
1035    let expr_str = pair.as_str();
1036    let inner_pairs: Vec<_> = pair.into_inner().collect();
1037
1038    if inner_pairs.is_empty() {
1039        return Err(ShapeError::ParseError {
1040            message: "Empty multiplicative expression".to_string(),
1041            location: None,
1042        });
1043    }
1044
1045    // Parse the first exponential expression
1046    let mut left = parse_exponential_expr(inner_pairs[0].clone())?;
1047
1048    // If there's only one pair, no operators
1049    if inner_pairs.len() == 1 {
1050        return Ok(left);
1051    }
1052
1053    // For expressions with operators, we need to find operators in the original string
1054    // between the unary expressions
1055    let mut current_pos = inner_pairs[0].as_str().len();
1056
1057    for i in 1..inner_pairs.len() {
1058        // Find the operator between previous and current expression
1059        let expr_start = expr_str[current_pos..]
1060            .find(inner_pairs[i].as_str())
1061            .ok_or_else(|| ShapeError::ParseError {
1062                message: "Cannot find expression in string".to_string(),
1063                location: None,
1064            })?;
1065        let op_str = expr_str[current_pos..current_pos + expr_start].trim();
1066
1067        let right = parse_exponential_expr(inner_pairs[i].clone())?;
1068
1069        left = Expr::BinaryOp {
1070            left: Box::new(left),
1071            op: match op_str {
1072                "*" => BinaryOp::Mul,
1073                "/" => BinaryOp::Div,
1074                "%" => BinaryOp::Mod,
1075                _ => {
1076                    return Err(ShapeError::ParseError {
1077                        message: format!("Unknown multiplicative operator: '{}'", op_str),
1078                        location: None,
1079                    });
1080                }
1081            },
1082            right: Box::new(right),
1083            span,
1084        };
1085
1086        current_pos += expr_start + inner_pairs[i].as_str().len();
1087    }
1088
1089    Ok(left)
1090}
1091
1092/// Parse exponential expression (a ** b)
1093pub fn parse_exponential_expr(pair: Pair<Rule>) -> Result<Expr> {
1094    // Exponentiation is right-associative, so we need to parse differently
1095    let span = pair_span(&pair);
1096    let inner_pairs: Vec<_> = pair.into_inner().collect();
1097
1098    if inner_pairs.is_empty() {
1099        return Err(ShapeError::ParseError {
1100            message: "Empty exponential expression".to_string(),
1101            location: None,
1102        });
1103    }
1104
1105    // Parse all unary expressions
1106    let mut exprs: Vec<Expr> = Vec::new();
1107    for p in inner_pairs {
1108        exprs.push(parse_unary_expr(p)?);
1109    }
1110
1111    // If there's only one expression, return it
1112    if exprs.len() == 1 {
1113        return Ok(exprs.into_iter().next().unwrap());
1114    }
1115
1116    // For right-associative parsing, we build from right to left
1117    // Example: a ** b ** c should be parsed as a ** (b ** c)
1118    let mut result = exprs.pop().unwrap(); // Start with the rightmost expression
1119
1120    while let Some(left_expr) = exprs.pop() {
1121        result = Expr::BinaryOp {
1122            left: Box::new(left_expr),
1123            op: BinaryOp::Pow,
1124            right: Box::new(result),
1125            span,
1126        };
1127    }
1128
1129    Ok(result)
1130}
1131
1132/// Parse unary expression (!a, -a)
1133pub fn parse_unary_expr(pair: Pair<Rule>) -> Result<Expr> {
1134    let span = pair_span(&pair);
1135    let pair_str = pair.as_str();
1136    let pair_loc = pair_location(&pair);
1137    let mut inner = pair.into_inner();
1138    let first = inner.next().ok_or_else(|| ShapeError::ParseError {
1139        message: "expected expression in unary operation".to_string(),
1140        location: Some(pair_loc.clone()),
1141    })?;
1142
1143    // Check if this is a ref_expr rule (& or &mut prefix)
1144    if first.as_rule() == Rule::ref_expr {
1145        let ref_span = pair_span(&first);
1146        let ref_inner = first.into_inner();
1147        let mut is_mutable = false;
1148        let mut expr_pair = None;
1149        for child in ref_inner {
1150            match child.as_rule() {
1151                Rule::ref_mut_keyword => {
1152                    is_mutable = true;
1153                }
1154                _ => {
1155                    // The postfix_expr (the referenced expression)
1156                    expr_pair = Some(child);
1157                }
1158            }
1159        }
1160        let inner_expr = expr_pair.ok_or_else(|| ShapeError::ParseError {
1161            message: "expected expression after &".to_string(),
1162            location: Some(pair_loc),
1163        })?;
1164        let operand = super::primary::parse_postfix_expr(inner_expr)?;
1165        return Ok(Expr::Reference {
1166            expr: Box::new(operand),
1167            is_mutable,
1168            span: ref_span,
1169        });
1170    }
1171
1172    // Check if this unary expression starts with an operator
1173    if pair_str.starts_with('!') {
1174        Ok(Expr::UnaryOp {
1175            op: UnaryOp::Not,
1176            operand: Box::new(parse_unary_expr(first)?),
1177            span,
1178        })
1179    } else if pair_str.starts_with('~') {
1180        Ok(Expr::UnaryOp {
1181            op: UnaryOp::BitNot,
1182            operand: Box::new(parse_unary_expr(first)?),
1183            span,
1184        })
1185    } else if pair_str.starts_with('-') {
1186        Ok(Expr::UnaryOp {
1187            op: UnaryOp::Neg,
1188            operand: Box::new(parse_unary_expr(first)?),
1189            span,
1190        })
1191    } else {
1192        // No unary operator, parse as postfix expression
1193        super::primary::parse_postfix_expr(first)
1194    }
1195}