lemma/parser/
expressions.rs

1use crate::ast::{ExpressionIdGenerator, Span};
2use crate::error::LemmaError;
3use crate::parser::Rule;
4use crate::semantic::*;
5use pest::iterators::Pair;
6
7// Helper to create a traceable Expression with source span and unique ID
8fn traceable_expr(
9    kind: ExpressionKind,
10    pair: &Pair<Rule>,
11    id_gen: &mut ExpressionIdGenerator,
12) -> Expression {
13    Expression::new(
14        kind,
15        Some(Span::from_pest_span(pair.as_span())),
16        id_gen.next_id(),
17    )
18}
19
20/// Helper function to parse any literal rule into an Expression.
21/// Handles both wrapped literals (Rule::literal) and direct literal types.
22fn parse_literal_expression(
23    pair: Pair<Rule>,
24    id_gen: &mut ExpressionIdGenerator,
25) -> Result<Expression, LemmaError> {
26    // Handle wrapped literals (Rule::literal contains the actual literal type)
27    let literal_pair = if pair.as_rule() == Rule::literal {
28        pair.into_inner()
29            .next()
30            .ok_or_else(|| LemmaError::Engine("Empty literal wrapper".to_string()))?
31    } else {
32        pair
33    };
34
35    let literal_value = crate::parser::literals::parse_literal(literal_pair.clone())?;
36    Ok(traceable_expr(
37        ExpressionKind::Literal(literal_value),
38        &literal_pair,
39        id_gen,
40    ))
41}
42
43fn parse_primary(
44    pair: Pair<Rule>,
45    id_gen: &mut ExpressionIdGenerator,
46) -> Result<Expression, LemmaError> {
47    // primary = { literal | reference_expression | "(" ~ expression_group ~ ")" }
48    for inner in pair.clone().into_inner() {
49        match inner.as_rule() {
50            Rule::literal
51            | Rule::number_literal
52            | Rule::string_literal
53            | Rule::boolean_literal
54            | Rule::regex_literal
55            | Rule::percentage_literal
56            | Rule::date_time_literal
57            | Rule::time_literal
58            | Rule::unit_literal => {
59                return parse_literal_expression(inner, id_gen);
60            }
61            Rule::reference_expression => {
62                return parse_reference_expression(inner, id_gen);
63            }
64            Rule::rule_reference => {
65                let rule_ref = parse_rule_reference(inner.clone())?;
66                return Ok(traceable_expr(
67                    ExpressionKind::RuleReference(rule_ref),
68                    &inner,
69                    id_gen,
70                ));
71            }
72            Rule::fact_reference => {
73                let fact_ref = parse_fact_reference(inner.clone())?;
74                return Ok(traceable_expr(
75                    ExpressionKind::FactReference(fact_ref),
76                    &inner,
77                    id_gen,
78                ));
79            }
80            Rule::expression_group => {
81                return parse_or_expression(inner, id_gen);
82            }
83            _ => {}
84        }
85    }
86    Err(LemmaError::Engine("Empty primary expression".to_string()))
87}
88
89pub(crate) fn parse_expression(
90    pair: Pair<Rule>,
91    id_gen: &mut ExpressionIdGenerator,
92) -> Result<Expression, LemmaError> {
93    // Check and increment depth
94    if let Err(msg) = id_gen.push_depth() {
95        return Err(LemmaError::ResourceLimitExceeded {
96            limit_name: "max_expression_depth".to_string(),
97            limit_value: id_gen.max_depth().to_string(),
98            actual_value: msg
99                .split_whitespace()
100                .nth(2)
101                .unwrap_or("unknown")
102                .to_string(),
103            suggestion: "Simplify nested expressions to reduce depth".to_string(),
104        });
105    }
106
107    let result = parse_expression_impl(pair, id_gen);
108    id_gen.pop_depth();
109    result
110}
111
112fn parse_expression_impl(
113    pair: Pair<Rule>,
114    id_gen: &mut ExpressionIdGenerator,
115) -> Result<Expression, LemmaError> {
116    // Check the current rule first before descending to children
117    match pair.as_rule() {
118        Rule::comparable_base => return parse_comparable_base(pair, id_gen),
119        Rule::term => return parse_term(pair, id_gen),
120        Rule::power => return parse_power(pair, id_gen),
121        Rule::factor => return parse_factor(pair, id_gen),
122        Rule::primary => return parse_primary(pair, id_gen),
123        Rule::arithmetic_expression => return parse_arithmetic_expression(pair, id_gen),
124        Rule::comparison_expression => return parse_comparison_expression(pair, id_gen),
125        Rule::boolean_expression => return parse_logical_expression(pair, id_gen),
126        // Directly handle mathematical operator nodes here so they don't get flattened
127        Rule::sqrt_expr
128        | Rule::sin_expr
129        | Rule::cos_expr
130        | Rule::tan_expr
131        | Rule::asin_expr
132        | Rule::acos_expr
133        | Rule::atan_expr
134        | Rule::log_expr
135        | Rule::exp_expr
136        | Rule::abs_expr
137        | Rule::floor_expr
138        | Rule::ceil_expr
139        | Rule::round_expr => return parse_logical_expression(pair, id_gen),
140        Rule::and_expression => return parse_and_expression(pair, id_gen),
141        Rule::or_expression => return parse_or_expression(pair, id_gen),
142        Rule::and_operand => return parse_and_operand(pair, id_gen),
143        Rule::expression_group => return parse_or_expression(pair, id_gen),
144        Rule::expression => {} // Continue to iterate children
145        _ => {}
146    }
147
148    for inner_pair in pair.clone().into_inner() {
149        match inner_pair.as_rule() {
150            // Literals - can appear wrapped in Rule::literal or directly as specific types
151            Rule::literal
152            | Rule::number_literal
153            | Rule::string_literal
154            | Rule::boolean_literal
155            | Rule::regex_literal
156            | Rule::percentage_literal
157            | Rule::date_time_literal
158            | Rule::time_literal
159            | Rule::unit_literal => {
160                return parse_literal_expression(inner_pair, id_gen);
161            }
162
163            // References
164            Rule::reference_expression => return parse_reference_expression(inner_pair, id_gen),
165
166            Rule::rule_reference => {
167                let rule_ref = parse_rule_reference(inner_pair.clone())?;
168                return Ok(traceable_expr(
169                    ExpressionKind::RuleReference(rule_ref),
170                    &inner_pair,
171                    id_gen,
172                ));
173            }
174
175            Rule::fact_reference => {
176                let fact_ref = parse_fact_reference(inner_pair.clone())?;
177                return Ok(traceable_expr(
178                    ExpressionKind::FactReference(fact_ref),
179                    &inner_pair,
180                    id_gen,
181                ));
182            }
183
184            Rule::primary
185            | Rule::arithmetic_expression
186            | Rule::comparison_expression
187            | Rule::boolean_expression
188            | Rule::and_expression
189            | Rule::or_expression
190            | Rule::and_operand
191            | Rule::expression_group => {
192                return parse_expression(inner_pair, id_gen);
193            }
194
195            // Logical and mathematical operations
196            Rule::not_expr
197            | Rule::have_expr
198            | Rule::have_not_expr
199            | Rule::not_have_expr
200            | Rule::sqrt_expr
201            | Rule::sin_expr
202            | Rule::cos_expr
203            | Rule::tan_expr
204            | Rule::asin_expr
205            | Rule::acos_expr
206            | Rule::atan_expr
207            | Rule::log_expr
208            | Rule::exp_expr
209            | Rule::abs_expr
210            | Rule::floor_expr
211            | Rule::ceil_expr
212            | Rule::round_expr => {
213                return parse_logical_expression(inner_pair, id_gen);
214            }
215
216            Rule::comparable_base | Rule::term | Rule::power | Rule::factor | Rule::expression => {
217                return parse_expression(inner_pair, id_gen);
218            }
219
220            _ => {}
221        }
222    }
223
224    Err(LemmaError::Engine(format!(
225        "Invalid expression: unable to parse '{}' as any valid expression type. Available rules: {:?}",
226        pair.as_str(),
227        pair.into_inner().map(|p| p.as_rule()).collect::<Vec<_>>()
228    )))
229}
230
231fn parse_reference_expression(
232    pair: Pair<Rule>,
233    id_gen: &mut ExpressionIdGenerator,
234) -> Result<Expression, LemmaError> {
235    if let Some(inner_pair) = pair.clone().into_inner().next() {
236        match inner_pair.as_rule() {
237            Rule::rule_reference => {
238                let rule_ref = parse_rule_reference(inner_pair)?;
239                let kind = ExpressionKind::RuleReference(rule_ref);
240                return Ok(traceable_expr(kind, &pair, id_gen));
241            }
242            Rule::fact_name => {
243                let kind = ExpressionKind::FactReference(FactReference {
244                    reference: vec![inner_pair.as_str().to_string()],
245                });
246                return Ok(traceable_expr(kind, &pair, id_gen));
247            }
248            Rule::fact_reference => {
249                let fact_ref = parse_fact_reference(inner_pair)?;
250                let kind = ExpressionKind::FactReference(fact_ref);
251                return Ok(traceable_expr(kind, &pair, id_gen));
252            }
253            _ => {}
254        }
255    }
256    Err(LemmaError::Engine(
257        "Invalid reference expression".to_string(),
258    ))
259}
260
261fn parse_fact_reference(pair: Pair<Rule>) -> Result<FactReference, LemmaError> {
262    let mut reference = Vec::new();
263    for inner_pair in pair.into_inner() {
264        if inner_pair.as_rule() == Rule::label {
265            reference.push(inner_pair.as_str().to_string());
266        }
267    }
268    Ok(FactReference { reference })
269}
270
271fn parse_rule_reference(pair: Pair<Rule>) -> Result<RuleReference, LemmaError> {
272    let mut reference = Vec::new();
273    for inner_pair in pair.into_inner() {
274        if inner_pair.as_rule() == Rule::label {
275            reference.push(inner_pair.as_str().to_string());
276        }
277    }
278    Ok(RuleReference { reference })
279}
280
281fn parse_and_operand(
282    pair: Pair<Rule>,
283    id_gen: &mut ExpressionIdGenerator,
284) -> Result<Expression, LemmaError> {
285    // Grammar: boolean_expression | comparable_base ~ (SPACE* ~ comp_operator ~ SPACE* ~ comparable_base)?
286    let mut pairs = pair.into_inner();
287    let first = pairs
288        .next()
289        .ok_or_else(|| LemmaError::Engine("Empty and_operand".to_string()))?;
290
291    // Check if it's a boolean_expression
292    if first.as_rule() == Rule::boolean_expression {
293        return parse_logical_expression(first, id_gen);
294    }
295
296    // Otherwise it's comparable_base with optional comparison
297    let left = parse_expression(first, id_gen)?;
298
299    // Check for comparison operator
300    if let Some(op_pair) = pairs.next() {
301        if op_pair.as_rule() == Rule::comp_operator {
302            // Parse the specific operator from within comp_operator
303            let inner_pair = op_pair
304                .clone()
305                .into_inner()
306                .next()
307                .ok_or_else(|| LemmaError::Engine("Empty comparison operator".to_string()))?;
308            let operator = match inner_pair.as_rule() {
309                Rule::comp_gt => ComparisonOperator::GreaterThan,
310                Rule::comp_lt => ComparisonOperator::LessThan,
311                Rule::comp_gte => ComparisonOperator::GreaterThanOrEqual,
312                Rule::comp_lte => ComparisonOperator::LessThanOrEqual,
313                Rule::comp_eq => ComparisonOperator::Equal,
314                Rule::comp_ne => ComparisonOperator::NotEqual,
315                Rule::comp_is => ComparisonOperator::Is,
316                Rule::comp_is_not => ComparisonOperator::IsNot,
317                _ => {
318                    return Err(LemmaError::Engine(format!(
319                        "Invalid comparison operator: {:?}",
320                        inner_pair.as_rule()
321                    )))
322                }
323            };
324            let right = parse_expression(
325                pairs.next().ok_or_else(|| {
326                    LemmaError::Engine("Missing right operand in comparison".to_string())
327                })?,
328                id_gen,
329            )?;
330            let kind = ExpressionKind::Comparison(Box::new(left), operator, Box::new(right));
331            return Ok(traceable_expr(kind, &op_pair, id_gen));
332        }
333    }
334
335    // No operator, just return the left side
336    Ok(left)
337}
338
339fn parse_and_expression(
340    pair: Pair<Rule>,
341    id_gen: &mut ExpressionIdGenerator,
342) -> Result<Expression, LemmaError> {
343    let mut pairs = pair.into_inner();
344    let mut left = parse_and_operand(
345        pairs.next().ok_or_else(|| {
346            LemmaError::Engine("Missing left operand in logical AND expression".to_string())
347        })?,
348        id_gen,
349    )?;
350
351    // The grammar structure is: and_operand ~ (SPACE+ ~ ^"and" ~ SPACE+ ~ and_operand)*
352    // We only process and_operand tokens, skipping SPACE and keywords
353    for right_pair in pairs {
354        if right_pair.as_rule() == Rule::and_operand {
355            let right = parse_and_operand(right_pair.clone(), id_gen)?;
356            let kind = ExpressionKind::LogicalAnd(Box::new(left), Box::new(right));
357            left = traceable_expr(kind, &right_pair, id_gen);
358        }
359    }
360
361    Ok(left)
362}
363
364pub(crate) fn parse_or_expression(
365    pair: Pair<Rule>,
366    id_gen: &mut ExpressionIdGenerator,
367) -> Result<Expression, LemmaError> {
368    // Handle expression_group wrapper: expression_group = { or_expression }
369    let or_pair = if pair.as_rule() == Rule::expression_group {
370        pair.into_inner()
371            .next()
372            .ok_or_else(|| LemmaError::Engine("Empty expression_group".to_string()))?
373    } else {
374        pair
375    };
376
377    let mut pairs = or_pair.into_inner();
378    let mut left = parse_and_expression(
379        pairs.next().ok_or_else(|| {
380            LemmaError::Engine("Missing left operand in logical OR expression".to_string())
381        })?,
382        id_gen,
383    )?;
384
385    // The grammar structure is: and_expression ~ (SPACE+ ~ ^"or" ~ SPACE+ ~ and_expression)*
386    // We only process and_expression tokens, skipping SPACE and keywords
387    for right_pair in pairs {
388        if right_pair.as_rule() == Rule::and_expression {
389            let right = parse_and_expression(right_pair.clone(), id_gen)?;
390            let kind = ExpressionKind::LogicalOr(Box::new(left), Box::new(right));
391            left = traceable_expr(kind, &right_pair, id_gen);
392        }
393    }
394
395    Ok(left)
396}
397
398fn parse_arithmetic_expression(
399    pair: Pair<Rule>,
400    id_gen: &mut ExpressionIdGenerator,
401) -> Result<Expression, LemmaError> {
402    let mut pairs = pair.clone().into_inner();
403    let mut left = parse_term(
404        pairs.next().ok_or_else(|| {
405            LemmaError::Engine("Missing left term in arithmetic expression".to_string())
406        })?,
407        id_gen,
408    )?;
409
410    while let Some(op_pair) = pairs.next() {
411        let operation = match op_pair.as_rule() {
412            Rule::add_plus => ArithmeticOperation::Add,
413            Rule::add_minus => ArithmeticOperation::Subtract,
414            _ => {
415                return Err(LemmaError::Engine(format!(
416                    "Unexpected operator in arithmetic expression: {:?}",
417                    op_pair.as_rule()
418                )))
419            }
420        };
421
422        let right = parse_term(
423            pairs.next().ok_or_else(|| {
424                LemmaError::Engine("Missing right term in arithmetic expression".to_string())
425            })?,
426            id_gen,
427        )?;
428
429        let kind = ExpressionKind::Arithmetic(Box::new(left), operation, Box::new(right));
430        left = traceable_expr(kind, &pair, id_gen);
431    }
432
433    Ok(left)
434}
435
436fn parse_term(
437    pair: Pair<Rule>,
438    id_gen: &mut ExpressionIdGenerator,
439) -> Result<Expression, LemmaError> {
440    let mut pairs = pair.clone().into_inner();
441    let mut left = parse_power(
442        pairs
443            .next()
444            .ok_or_else(|| LemmaError::Engine("Missing left power in term".to_string()))?,
445        id_gen,
446    )?;
447
448    while let Some(op_pair) = pairs.next() {
449        let operation = match op_pair.as_rule() {
450            Rule::mul_star => ArithmeticOperation::Multiply,
451            Rule::mul_slash => ArithmeticOperation::Divide,
452            Rule::mul_percent => ArithmeticOperation::Modulo,
453            _ => {
454                return Err(LemmaError::Engine(format!(
455                    "Unexpected operator in term: {:?}",
456                    op_pair.as_rule()
457                )))
458            }
459        };
460
461        let right = parse_power(
462            pairs
463                .next()
464                .ok_or_else(|| LemmaError::Engine("Missing right power in term".to_string()))?,
465            id_gen,
466        )?;
467
468        let kind = ExpressionKind::Arithmetic(Box::new(left), operation, Box::new(right));
469        left = traceable_expr(kind, &pair, id_gen);
470    }
471
472    Ok(left)
473}
474
475fn parse_power(
476    pair: Pair<Rule>,
477    id_gen: &mut ExpressionIdGenerator,
478) -> Result<Expression, LemmaError> {
479    let mut pairs = pair.clone().into_inner();
480    let left = parse_factor(
481        pairs
482            .next()
483            .ok_or_else(|| LemmaError::Engine("Missing factor in power".to_string()))?,
484        id_gen,
485    )?;
486
487    if let Some(op_pair) = pairs.next() {
488        if op_pair.as_rule() == Rule::pow_caret {
489            let right = parse_power(
490                pairs.next().ok_or_else(|| {
491                    LemmaError::Engine("Missing right power in power expression".to_string())
492                })?,
493                id_gen,
494            )?;
495
496            let kind = ExpressionKind::Arithmetic(
497                Box::new(left),
498                ArithmeticOperation::Power,
499                Box::new(right),
500            );
501            return Ok(traceable_expr(kind, &pair, id_gen));
502        }
503    }
504
505    Ok(left)
506}
507
508fn parse_factor(
509    pair: Pair<Rule>,
510    id_gen: &mut ExpressionIdGenerator,
511) -> Result<Expression, LemmaError> {
512    let mut pairs = pair.clone().into_inner();
513    let mut is_negative = false;
514
515    // Check for unary operators
516    if let Some(first_pair) = pairs.next() {
517        match first_pair.as_rule() {
518            Rule::unary_minus => {
519                is_negative = true;
520            }
521            Rule::unary_plus => {
522                // Just ignore unary plus
523            }
524            _ => {
525                let expr = parse_expression(first_pair, id_gen)?;
526                return Ok(expr);
527            }
528        }
529    }
530
531    // Parse the actual expression after unary operator
532    let expr = if let Some(expr_pair) = pairs.next() {
533        parse_expression(expr_pair, id_gen)?
534    } else {
535        return Err(LemmaError::Engine(
536            "Missing expression after unary operator".to_string(),
537        ));
538    };
539
540    // Apply unary operator if present
541    if is_negative {
542        let zero = traceable_expr(
543            ExpressionKind::Literal(LiteralValue::Number(rust_decimal::Decimal::ZERO)),
544            &pair,
545            id_gen,
546        );
547        let kind = ExpressionKind::Arithmetic(
548            Box::new(zero),
549            ArithmeticOperation::Subtract,
550            Box::new(expr),
551        );
552        Ok(traceable_expr(kind, &pair, id_gen))
553    } else {
554        Ok(expr)
555    }
556}
557
558fn parse_comparison_expression(
559    pair: Pair<Rule>,
560    id_gen: &mut ExpressionIdGenerator,
561) -> Result<Expression, LemmaError> {
562    let mut pairs = pair.clone().into_inner();
563    let left = parse_expression(
564        pairs.next().ok_or_else(|| {
565            LemmaError::Engine("Missing left operand in comparison expression".to_string())
566        })?,
567        id_gen,
568    )?;
569
570    if let Some(op_pair) = pairs.next() {
571        let operator = match op_pair.as_rule() {
572            Rule::comp_operator => {
573                // Parse the specific operator from within comp_operator
574                let inner_pair = op_pair
575                    .into_inner()
576                    .next()
577                    .ok_or_else(|| LemmaError::Engine("Empty comparison operator".to_string()))?;
578                match inner_pair.as_rule() {
579                    Rule::comp_gt => ComparisonOperator::GreaterThan,
580                    Rule::comp_lt => ComparisonOperator::LessThan,
581                    Rule::comp_gte => ComparisonOperator::GreaterThanOrEqual,
582                    Rule::comp_lte => ComparisonOperator::LessThanOrEqual,
583                    Rule::comp_eq => ComparisonOperator::Equal,
584                    Rule::comp_ne => ComparisonOperator::NotEqual,
585                    Rule::comp_is => ComparisonOperator::Is,
586                    Rule::comp_is_not => ComparisonOperator::IsNot,
587                    _ => {
588                        return Err(LemmaError::Engine(format!(
589                            "Invalid comparison operator: {:?}",
590                            inner_pair.as_rule()
591                        )))
592                    }
593                }
594            }
595            Rule::comp_gt => ComparisonOperator::GreaterThan,
596            Rule::comp_lt => ComparisonOperator::LessThan,
597            Rule::comp_gte => ComparisonOperator::GreaterThanOrEqual,
598            Rule::comp_lte => ComparisonOperator::LessThanOrEqual,
599            Rule::comp_eq => ComparisonOperator::Equal,
600            Rule::comp_ne => ComparisonOperator::NotEqual,
601            Rule::comp_is => ComparisonOperator::Is,
602            Rule::comp_is_not => ComparisonOperator::IsNot,
603            _ => {
604                return Err(LemmaError::Engine(format!(
605                    "Invalid comparison operator: {:?}",
606                    op_pair.as_rule()
607                )))
608            }
609        };
610
611        let right = parse_expression(
612            pairs.next().ok_or_else(|| {
613                LemmaError::Engine("Missing right operand in comparison expression".to_string())
614            })?,
615            id_gen,
616        )?;
617
618        let kind = ExpressionKind::Comparison(Box::new(left), operator, Box::new(right));
619        return Ok(traceable_expr(kind, &pair, id_gen));
620    }
621
622    Ok(left)
623}
624
625fn parse_logical_expression(
626    pair: Pair<Rule>,
627    id_gen: &mut ExpressionIdGenerator,
628) -> Result<Expression, LemmaError> {
629    // Handle direct mathematical operator nodes (abs, floor, etc.)
630    match pair.as_rule() {
631        Rule::sqrt_expr
632        | Rule::sin_expr
633        | Rule::cos_expr
634        | Rule::tan_expr
635        | Rule::asin_expr
636        | Rule::acos_expr
637        | Rule::atan_expr
638        | Rule::log_expr
639        | Rule::exp_expr
640        | Rule::abs_expr
641        | Rule::floor_expr
642        | Rule::ceil_expr
643        | Rule::round_expr => {
644            let operator = match pair.as_rule() {
645                Rule::sqrt_expr => MathematicalOperator::Sqrt,
646                Rule::sin_expr => MathematicalOperator::Sin,
647                Rule::cos_expr => MathematicalOperator::Cos,
648                Rule::tan_expr => MathematicalOperator::Tan,
649                Rule::asin_expr => MathematicalOperator::Asin,
650                Rule::acos_expr => MathematicalOperator::Acos,
651                Rule::atan_expr => MathematicalOperator::Atan,
652                Rule::log_expr => MathematicalOperator::Log,
653                Rule::exp_expr => MathematicalOperator::Exp,
654                Rule::abs_expr => MathematicalOperator::Abs,
655                Rule::floor_expr => MathematicalOperator::Floor,
656                Rule::ceil_expr => MathematicalOperator::Ceil,
657                Rule::round_expr => MathematicalOperator::Round,
658                _ => unreachable!(),
659            };
660
661            for inner in pair.clone().into_inner() {
662                if inner.as_rule() == Rule::arithmetic_expression
663                    || inner.as_rule() == Rule::primary
664                {
665                    let operand = parse_expression(inner, id_gen)?;
666                    let kind = ExpressionKind::MathematicalOperator(operator, Box::new(operand));
667                    return Ok(traceable_expr(kind, &pair, id_gen));
668                }
669            }
670            return Err(LemmaError::Engine(
671                "Mathematical operator missing operand".to_string(),
672            ));
673        }
674        _ => {}
675    }
676    if let Some(node) = pair.into_inner().next() {
677        match node.as_rule() {
678            Rule::reference_expression => return parse_reference_expression(node, id_gen),
679            Rule::literal => return parse_expression(node, id_gen),
680            Rule::primary => return parse_primary(node, id_gen),
681            Rule::have_expr => {
682                for inner in node.clone().into_inner() {
683                    if inner.as_rule() == Rule::reference_expression {
684                        let ref_expr = parse_reference_expression(inner.clone(), id_gen)?;
685                        if let ExpressionKind::FactReference(f) = &ref_expr.kind {
686                            let kind = ExpressionKind::FactHasAnyValue(f.clone());
687                            return Ok(traceable_expr(kind, &node, id_gen));
688                        }
689                        return Ok(ref_expr);
690                    }
691                }
692                return Err(LemmaError::Engine("have: missing reference".to_string()));
693            }
694            Rule::have_not_expr | Rule::not_have_expr | Rule::not_expr => {
695                let rule_type = node.as_rule();
696                for inner in node.clone().into_inner() {
697                    if inner.as_rule() == Rule::reference_expression {
698                        let negated_expr = parse_reference_expression(inner, id_gen)?;
699                        let negation_type = match rule_type {
700                            Rule::not_expr => NegationType::Not,
701                            Rule::have_not_expr => NegationType::HaveNot,
702                            Rule::not_have_expr => NegationType::NotHave,
703                            _ => NegationType::Not,
704                        };
705                        let kind =
706                            ExpressionKind::LogicalNegation(Box::new(negated_expr), negation_type);
707                        return Ok(traceable_expr(kind, &node, id_gen));
708                    } else if inner.as_rule() == Rule::primary {
709                        let negated_expr = parse_primary(inner, id_gen)?;
710                        let negation_type = match rule_type {
711                            Rule::not_expr => NegationType::Not,
712                            Rule::have_not_expr => NegationType::HaveNot,
713                            Rule::not_have_expr => NegationType::NotHave,
714                            _ => NegationType::Not,
715                        };
716                        let kind =
717                            ExpressionKind::LogicalNegation(Box::new(negated_expr), negation_type);
718                        return Ok(traceable_expr(kind, &node, id_gen));
719                    } else if inner.as_rule() == Rule::literal {
720                        let negated_expr = parse_expression(inner, id_gen)?;
721                        let negation_type = match rule_type {
722                            Rule::not_expr => NegationType::Not,
723                            Rule::have_not_expr => NegationType::HaveNot,
724                            Rule::not_have_expr => NegationType::NotHave,
725                            _ => NegationType::Not,
726                        };
727                        let kind =
728                            ExpressionKind::LogicalNegation(Box::new(negated_expr), negation_type);
729                        return Ok(traceable_expr(kind, &node, id_gen));
730                    }
731                }
732                return Err(LemmaError::Engine(
733                    "not/have not: missing reference".to_string(),
734                ));
735            }
736            Rule::sqrt_expr
737            | Rule::sin_expr
738            | Rule::cos_expr
739            | Rule::tan_expr
740            | Rule::asin_expr
741            | Rule::acos_expr
742            | Rule::atan_expr
743            | Rule::log_expr
744            | Rule::exp_expr
745            | Rule::abs_expr
746            | Rule::floor_expr
747            | Rule::ceil_expr
748            | Rule::round_expr => {
749                let operator = match node.as_rule() {
750                    Rule::sqrt_expr => MathematicalOperator::Sqrt,
751                    Rule::sin_expr => MathematicalOperator::Sin,
752                    Rule::cos_expr => MathematicalOperator::Cos,
753                    Rule::tan_expr => MathematicalOperator::Tan,
754                    Rule::asin_expr => MathematicalOperator::Asin,
755                    Rule::acos_expr => MathematicalOperator::Acos,
756                    Rule::atan_expr => MathematicalOperator::Atan,
757                    Rule::log_expr => MathematicalOperator::Log,
758                    Rule::exp_expr => MathematicalOperator::Exp,
759                    Rule::abs_expr => MathematicalOperator::Abs,
760                    Rule::floor_expr => MathematicalOperator::Floor,
761                    Rule::ceil_expr => MathematicalOperator::Ceil,
762                    Rule::round_expr => MathematicalOperator::Round,
763                    _ => {
764                        return Err(LemmaError::Engine(
765                            "Unknown mathematical operator".to_string(),
766                        ))
767                    }
768                };
769
770                for inner in node.clone().into_inner() {
771                    if inner.as_rule() == Rule::arithmetic_expression
772                        || inner.as_rule() == Rule::primary
773                    {
774                        let operand = parse_expression(inner, id_gen)?;
775                        let kind =
776                            ExpressionKind::MathematicalOperator(operator, Box::new(operand));
777                        return Ok(traceable_expr(kind, &node, id_gen));
778                    }
779                }
780                return Err(LemmaError::Engine(
781                    "Mathematical operator missing operand".to_string(),
782                ));
783            }
784            _ => {}
785        }
786    }
787    Err(LemmaError::Engine("Empty logical expression".to_string()))
788}
789
790fn parse_comparable_base(
791    pair: Pair<Rule>,
792    id_gen: &mut ExpressionIdGenerator,
793) -> Result<Expression, LemmaError> {
794    // comparable_base = { arithmetic_expression ~ (SPACE+ ~ ^"in" ~ SPACE+ ~ unit_types)? }
795    let mut pairs = pair.clone().into_inner();
796
797    let arith_expr = parse_expression(
798        pairs.next().ok_or_else(|| {
799            LemmaError::Engine("No arithmetic expression in comparable_base".to_string())
800        })?,
801        id_gen,
802    )?;
803
804    // Check for optional "in" unit conversion
805    if let Some(unit_pair) = pairs.next() {
806        if unit_pair.as_rule() == Rule::unit_word {
807            let target_unit = super::units::resolve_conversion_target(unit_pair.as_str())?;
808            let kind = ExpressionKind::UnitConversion(Box::new(arith_expr), target_unit);
809            return Ok(traceable_expr(kind, &pair, id_gen));
810        }
811    }
812
813    // No unit conversion, just return the arithmetic expression
814    Ok(arith_expr)
815}