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 the current rule first before descending to children
94    match pair.as_rule() {
95        Rule::comparable_base => return parse_comparable_base(pair, id_gen),
96        Rule::term => return parse_term(pair, id_gen),
97        Rule::power => return parse_power(pair, id_gen),
98        Rule::factor => return parse_factor(pair, id_gen),
99        Rule::primary => return parse_primary(pair, id_gen),
100        Rule::arithmetic_expression => return parse_arithmetic_expression(pair, id_gen),
101        Rule::comparison_expression => return parse_comparison_expression(pair, id_gen),
102        Rule::boolean_expression => return parse_logical_expression(pair, id_gen),
103        Rule::and_expression => return parse_and_expression(pair, id_gen),
104        Rule::or_expression => return parse_or_expression(pair, id_gen),
105        Rule::and_operand => return parse_and_operand(pair, id_gen),
106        Rule::expression_group => return parse_or_expression(pair, id_gen),
107        Rule::expression => {} // Continue to iterate children
108        _ => {}
109    }
110
111    for inner_pair in pair.clone().into_inner() {
112        match inner_pair.as_rule() {
113            // Literals - can appear wrapped in Rule::literal or directly as specific types
114            Rule::literal
115            | Rule::number_literal
116            | Rule::string_literal
117            | Rule::boolean_literal
118            | Rule::regex_literal
119            | Rule::percentage_literal
120            | Rule::date_time_literal
121            | Rule::time_literal
122            | Rule::unit_literal => {
123                return parse_literal_expression(inner_pair, id_gen);
124            }
125
126            // References
127            Rule::reference_expression => return parse_reference_expression(inner_pair, id_gen),
128
129            Rule::rule_reference => {
130                let rule_ref = parse_rule_reference(inner_pair.clone())?;
131                return Ok(traceable_expr(
132                    ExpressionKind::RuleReference(rule_ref),
133                    &inner_pair,
134                    id_gen,
135                ));
136            }
137
138            Rule::fact_reference => {
139                let fact_ref = parse_fact_reference(inner_pair.clone())?;
140                return Ok(traceable_expr(
141                    ExpressionKind::FactReference(fact_ref),
142                    &inner_pair,
143                    id_gen,
144                ));
145            }
146
147            Rule::primary
148            | Rule::arithmetic_expression
149            | Rule::comparison_expression
150            | Rule::boolean_expression
151            | Rule::and_expression
152            | Rule::or_expression
153            | Rule::and_operand
154            | Rule::expression_group => {
155                return parse_expression(inner_pair, id_gen);
156            }
157
158            // Logical and mathematical operations
159            Rule::not_expr
160            | Rule::have_expr
161            | Rule::have_not_expr
162            | Rule::not_have_expr
163            | Rule::sqrt_expr
164            | Rule::sin_expr
165            | Rule::cos_expr
166            | Rule::tan_expr
167            | Rule::asin_expr
168            | Rule::acos_expr
169            | Rule::atan_expr
170            | Rule::log_expr
171            | Rule::exp_expr => {
172                return parse_logical_expression(inner_pair, id_gen);
173            }
174
175            Rule::comparable_base | Rule::term | Rule::power | Rule::factor | Rule::expression => {
176                return parse_expression(inner_pair, id_gen);
177            }
178
179            _ => {}
180        }
181    }
182
183    Err(LemmaError::Engine(format!(
184        "Invalid expression: unable to parse '{}' as any valid expression type. Available rules: {:?}",
185        pair.as_str(),
186        pair.into_inner().map(|p| p.as_rule()).collect::<Vec<_>>()
187    )))
188}
189
190fn parse_reference_expression(
191    pair: Pair<Rule>,
192    id_gen: &mut ExpressionIdGenerator,
193) -> Result<Expression, LemmaError> {
194    if let Some(inner_pair) = pair.clone().into_inner().next() {
195        match inner_pair.as_rule() {
196            Rule::rule_reference => {
197                let rule_ref = parse_rule_reference(inner_pair)?;
198                let kind = ExpressionKind::RuleReference(rule_ref);
199                return Ok(traceable_expr(kind, &pair, id_gen));
200            }
201            Rule::fact_name => {
202                let kind = ExpressionKind::FactReference(FactReference {
203                    reference: vec![inner_pair.as_str().to_string()],
204                });
205                return Ok(traceable_expr(kind, &pair, id_gen));
206            }
207            Rule::fact_reference => {
208                let fact_ref = parse_fact_reference(inner_pair)?;
209                let kind = ExpressionKind::FactReference(fact_ref);
210                return Ok(traceable_expr(kind, &pair, id_gen));
211            }
212            _ => {}
213        }
214    }
215    Err(LemmaError::Engine(
216        "Invalid reference expression".to_string(),
217    ))
218}
219
220fn parse_fact_reference(pair: Pair<Rule>) -> Result<FactReference, LemmaError> {
221    let mut reference = Vec::new();
222    for inner_pair in pair.into_inner() {
223        if inner_pair.as_rule() == Rule::label {
224            reference.push(inner_pair.as_str().to_string());
225        }
226    }
227    Ok(FactReference { reference })
228}
229
230fn parse_rule_reference(pair: Pair<Rule>) -> Result<RuleReference, LemmaError> {
231    let mut reference = Vec::new();
232    for inner_pair in pair.into_inner() {
233        if inner_pair.as_rule() == Rule::label {
234            reference.push(inner_pair.as_str().to_string());
235        }
236    }
237    Ok(RuleReference { reference })
238}
239
240fn parse_and_operand(
241    pair: Pair<Rule>,
242    id_gen: &mut ExpressionIdGenerator,
243) -> Result<Expression, LemmaError> {
244    // Grammar: boolean_expression | comparable_base ~ (SPACE* ~ comp_operator ~ SPACE* ~ comparable_base)?
245    let mut pairs = pair.into_inner();
246    let first = pairs
247        .next()
248        .ok_or_else(|| LemmaError::Engine("Empty and_operand".to_string()))?;
249
250    // Check if it's a boolean_expression
251    if first.as_rule() == Rule::boolean_expression {
252        return parse_logical_expression(first, id_gen);
253    }
254
255    // Otherwise it's comparable_base with optional comparison
256    let left = parse_expression(first, id_gen)?;
257
258    // Check for comparison operator
259    if let Some(op_pair) = pairs.next() {
260        if op_pair.as_rule() == Rule::comp_operator {
261            // Parse the specific operator from within comp_operator
262            let inner_pair = op_pair
263                .clone()
264                .into_inner()
265                .next()
266                .ok_or_else(|| LemmaError::Engine("Empty comparison operator".to_string()))?;
267            let operator = match inner_pair.as_rule() {
268                Rule::comp_gt => ComparisonOperator::GreaterThan,
269                Rule::comp_lt => ComparisonOperator::LessThan,
270                Rule::comp_gte => ComparisonOperator::GreaterThanOrEqual,
271                Rule::comp_lte => ComparisonOperator::LessThanOrEqual,
272                Rule::comp_eq => ComparisonOperator::Equal,
273                Rule::comp_ne => ComparisonOperator::NotEqual,
274                Rule::comp_is => ComparisonOperator::Is,
275                Rule::comp_is_not => ComparisonOperator::IsNot,
276                _ => {
277                    return Err(LemmaError::Engine(format!(
278                        "Invalid comparison operator: {:?}",
279                        inner_pair.as_rule()
280                    )))
281                }
282            };
283            let right = parse_expression(
284                pairs.next().ok_or_else(|| {
285                    LemmaError::Engine("Missing right operand in comparison".to_string())
286                })?,
287                id_gen,
288            )?;
289            let kind = ExpressionKind::Comparison(Box::new(left), operator, Box::new(right));
290            return Ok(traceable_expr(kind, &op_pair, id_gen));
291        }
292    }
293
294    // No operator, just return the left side
295    Ok(left)
296}
297
298fn parse_and_expression(
299    pair: Pair<Rule>,
300    id_gen: &mut ExpressionIdGenerator,
301) -> Result<Expression, LemmaError> {
302    let mut pairs = pair.into_inner();
303    let mut left = parse_and_operand(
304        pairs.next().ok_or_else(|| {
305            LemmaError::Engine("Missing left operand in logical AND expression".to_string())
306        })?,
307        id_gen,
308    )?;
309
310    // The grammar structure is: and_operand ~ (SPACE+ ~ ^"and" ~ SPACE+ ~ and_operand)*
311    // We only process and_operand tokens, skipping SPACE and keywords
312    for right_pair in pairs {
313        if right_pair.as_rule() == Rule::and_operand {
314            let right = parse_and_operand(right_pair.clone(), id_gen)?;
315            let kind = ExpressionKind::LogicalAnd(Box::new(left), Box::new(right));
316            left = traceable_expr(kind, &right_pair, id_gen);
317        }
318    }
319
320    Ok(left)
321}
322
323pub(crate) fn parse_or_expression(
324    pair: Pair<Rule>,
325    id_gen: &mut ExpressionIdGenerator,
326) -> Result<Expression, LemmaError> {
327    // Handle expression_group wrapper: expression_group = { or_expression }
328    let or_pair = if pair.as_rule() == Rule::expression_group {
329        pair.into_inner()
330            .next()
331            .ok_or_else(|| LemmaError::Engine("Empty expression_group".to_string()))?
332    } else {
333        pair
334    };
335
336    let mut pairs = or_pair.into_inner();
337    let mut left = parse_and_expression(
338        pairs.next().ok_or_else(|| {
339            LemmaError::Engine("Missing left operand in logical OR expression".to_string())
340        })?,
341        id_gen,
342    )?;
343
344    // The grammar structure is: and_expression ~ (SPACE+ ~ ^"or" ~ SPACE+ ~ and_expression)*
345    // We only process and_expression tokens, skipping SPACE and keywords
346    for right_pair in pairs {
347        if right_pair.as_rule() == Rule::and_expression {
348            let right = parse_and_expression(right_pair.clone(), id_gen)?;
349            let kind = ExpressionKind::LogicalOr(Box::new(left), Box::new(right));
350            left = traceable_expr(kind, &right_pair, id_gen);
351        }
352    }
353
354    Ok(left)
355}
356
357fn parse_arithmetic_expression(
358    pair: Pair<Rule>,
359    id_gen: &mut ExpressionIdGenerator,
360) -> Result<Expression, LemmaError> {
361    let mut pairs = pair.clone().into_inner();
362    let mut left = parse_term(
363        pairs.next().ok_or_else(|| {
364            LemmaError::Engine("Missing left term in arithmetic expression".to_string())
365        })?,
366        id_gen,
367    )?;
368
369    while let Some(op_pair) = pairs.next() {
370        let operation = match op_pair.as_rule() {
371            Rule::add_plus => ArithmeticOperation::Add,
372            Rule::add_minus => ArithmeticOperation::Subtract,
373            _ => {
374                return Err(LemmaError::Engine(format!(
375                    "Unexpected operator in arithmetic expression: {:?}",
376                    op_pair.as_rule()
377                )))
378            }
379        };
380
381        let right = parse_term(
382            pairs.next().ok_or_else(|| {
383                LemmaError::Engine("Missing right term in arithmetic expression".to_string())
384            })?,
385            id_gen,
386        )?;
387
388        let kind = ExpressionKind::Arithmetic(Box::new(left), operation, Box::new(right));
389        left = traceable_expr(kind, &pair, id_gen);
390    }
391
392    Ok(left)
393}
394
395fn parse_term(
396    pair: Pair<Rule>,
397    id_gen: &mut ExpressionIdGenerator,
398) -> Result<Expression, LemmaError> {
399    let mut pairs = pair.clone().into_inner();
400    let mut left = parse_power(
401        pairs
402            .next()
403            .ok_or_else(|| LemmaError::Engine("Missing left power in term".to_string()))?,
404        id_gen,
405    )?;
406
407    while let Some(op_pair) = pairs.next() {
408        let operation = match op_pair.as_rule() {
409            Rule::mul_star => ArithmeticOperation::Multiply,
410            Rule::mul_slash => ArithmeticOperation::Divide,
411            Rule::mul_percent => ArithmeticOperation::Modulo,
412            _ => {
413                return Err(LemmaError::Engine(format!(
414                    "Unexpected operator in term: {:?}",
415                    op_pair.as_rule()
416                )))
417            }
418        };
419
420        let right = parse_power(
421            pairs
422                .next()
423                .ok_or_else(|| LemmaError::Engine("Missing right power in term".to_string()))?,
424            id_gen,
425        )?;
426
427        let kind = ExpressionKind::Arithmetic(Box::new(left), operation, Box::new(right));
428        left = traceable_expr(kind, &pair, id_gen);
429    }
430
431    Ok(left)
432}
433
434fn parse_power(
435    pair: Pair<Rule>,
436    id_gen: &mut ExpressionIdGenerator,
437) -> Result<Expression, LemmaError> {
438    let mut pairs = pair.clone().into_inner();
439    let left = parse_factor(
440        pairs
441            .next()
442            .ok_or_else(|| LemmaError::Engine("Missing factor in power".to_string()))?,
443        id_gen,
444    )?;
445
446    if let Some(op_pair) = pairs.next() {
447        if op_pair.as_rule() == Rule::pow_caret {
448            let right = parse_power(
449                pairs.next().ok_or_else(|| {
450                    LemmaError::Engine("Missing right power in power expression".to_string())
451                })?,
452                id_gen,
453            )?;
454
455            let kind = ExpressionKind::Arithmetic(
456                Box::new(left),
457                ArithmeticOperation::Power,
458                Box::new(right),
459            );
460            return Ok(traceable_expr(kind, &pair, id_gen));
461        }
462    }
463
464    Ok(left)
465}
466
467fn parse_factor(
468    pair: Pair<Rule>,
469    id_gen: &mut ExpressionIdGenerator,
470) -> Result<Expression, LemmaError> {
471    let mut pairs = pair.clone().into_inner();
472    let mut is_negative = false;
473
474    // Check for unary operators
475    if let Some(first_pair) = pairs.next() {
476        match first_pair.as_rule() {
477            Rule::unary_minus => {
478                is_negative = true;
479            }
480            Rule::unary_plus => {
481                // Just ignore unary plus
482            }
483            _ => {
484                let expr = parse_expression(first_pair, id_gen)?;
485                return Ok(expr);
486            }
487        }
488    }
489
490    // Parse the actual expression after unary operator
491    let expr = if let Some(expr_pair) = pairs.next() {
492        parse_expression(expr_pair, id_gen)?
493    } else {
494        return Err(LemmaError::Engine(
495            "Missing expression after unary operator".to_string(),
496        ));
497    };
498
499    // Apply unary operator if present
500    if is_negative {
501        let zero = traceable_expr(
502            ExpressionKind::Literal(LiteralValue::Number(rust_decimal::Decimal::ZERO)),
503            &pair,
504            id_gen,
505        );
506        let kind = ExpressionKind::Arithmetic(
507            Box::new(zero),
508            ArithmeticOperation::Subtract,
509            Box::new(expr),
510        );
511        Ok(traceable_expr(kind, &pair, id_gen))
512    } else {
513        Ok(expr)
514    }
515}
516
517fn parse_comparison_expression(
518    pair: Pair<Rule>,
519    id_gen: &mut ExpressionIdGenerator,
520) -> Result<Expression, LemmaError> {
521    let mut pairs = pair.clone().into_inner();
522    let left = parse_expression(
523        pairs.next().ok_or_else(|| {
524            LemmaError::Engine("Missing left operand in comparison expression".to_string())
525        })?,
526        id_gen,
527    )?;
528
529    if let Some(op_pair) = pairs.next() {
530        let operator = match op_pair.as_rule() {
531            Rule::comp_operator => {
532                // Parse the specific operator from within comp_operator
533                let inner_pair = op_pair
534                    .into_inner()
535                    .next()
536                    .ok_or_else(|| LemmaError::Engine("Empty comparison operator".to_string()))?;
537                match inner_pair.as_rule() {
538                    Rule::comp_gt => ComparisonOperator::GreaterThan,
539                    Rule::comp_lt => ComparisonOperator::LessThan,
540                    Rule::comp_gte => ComparisonOperator::GreaterThanOrEqual,
541                    Rule::comp_lte => ComparisonOperator::LessThanOrEqual,
542                    Rule::comp_eq => ComparisonOperator::Equal,
543                    Rule::comp_ne => ComparisonOperator::NotEqual,
544                    Rule::comp_is => ComparisonOperator::Is,
545                    Rule::comp_is_not => ComparisonOperator::IsNot,
546                    _ => {
547                        return Err(LemmaError::Engine(format!(
548                            "Invalid comparison operator: {:?}",
549                            inner_pair.as_rule()
550                        )))
551                    }
552                }
553            }
554            Rule::comp_gt => ComparisonOperator::GreaterThan,
555            Rule::comp_lt => ComparisonOperator::LessThan,
556            Rule::comp_gte => ComparisonOperator::GreaterThanOrEqual,
557            Rule::comp_lte => ComparisonOperator::LessThanOrEqual,
558            Rule::comp_eq => ComparisonOperator::Equal,
559            Rule::comp_ne => ComparisonOperator::NotEqual,
560            Rule::comp_is => ComparisonOperator::Is,
561            Rule::comp_is_not => ComparisonOperator::IsNot,
562            _ => {
563                return Err(LemmaError::Engine(format!(
564                    "Invalid comparison operator: {:?}",
565                    op_pair.as_rule()
566                )))
567            }
568        };
569
570        let right = parse_expression(
571            pairs.next().ok_or_else(|| {
572                LemmaError::Engine("Missing right operand in comparison expression".to_string())
573            })?,
574            id_gen,
575        )?;
576
577        let kind = ExpressionKind::Comparison(Box::new(left), operator, Box::new(right));
578        return Ok(traceable_expr(kind, &pair, id_gen));
579    }
580
581    Ok(left)
582}
583
584fn parse_logical_expression(
585    pair: Pair<Rule>,
586    id_gen: &mut ExpressionIdGenerator,
587) -> Result<Expression, LemmaError> {
588    if let Some(node) = pair.into_inner().next() {
589        match node.as_rule() {
590            Rule::reference_expression => return parse_reference_expression(node, id_gen),
591            Rule::literal => return parse_expression(node, id_gen),
592            Rule::primary => return parse_primary(node, id_gen),
593            Rule::have_expr => {
594                for inner in node.clone().into_inner() {
595                    if inner.as_rule() == Rule::reference_expression {
596                        let ref_expr = parse_reference_expression(inner.clone(), id_gen)?;
597                        if let ExpressionKind::FactReference(f) = &ref_expr.kind {
598                            let kind = ExpressionKind::FactHasAnyValue(f.clone());
599                            return Ok(traceable_expr(kind, &node, id_gen));
600                        }
601                        return Ok(ref_expr);
602                    }
603                }
604                return Err(LemmaError::Engine("have: missing reference".to_string()));
605            }
606            Rule::have_not_expr | Rule::not_have_expr | Rule::not_expr => {
607                let rule_type = node.as_rule();
608                for inner in node.clone().into_inner() {
609                    if inner.as_rule() == Rule::reference_expression {
610                        let negated_expr = parse_reference_expression(inner, id_gen)?;
611                        let negation_type = match rule_type {
612                            Rule::not_expr => NegationType::Not,
613                            Rule::have_not_expr => NegationType::HaveNot,
614                            Rule::not_have_expr => NegationType::NotHave,
615                            _ => NegationType::Not,
616                        };
617                        let kind =
618                            ExpressionKind::LogicalNegation(Box::new(negated_expr), negation_type);
619                        return Ok(traceable_expr(kind, &node, id_gen));
620                    } else if inner.as_rule() == Rule::primary {
621                        let negated_expr = parse_primary(inner, id_gen)?;
622                        let negation_type = match rule_type {
623                            Rule::not_expr => NegationType::Not,
624                            Rule::have_not_expr => NegationType::HaveNot,
625                            Rule::not_have_expr => NegationType::NotHave,
626                            _ => NegationType::Not,
627                        };
628                        let kind =
629                            ExpressionKind::LogicalNegation(Box::new(negated_expr), negation_type);
630                        return Ok(traceable_expr(kind, &node, id_gen));
631                    } else if inner.as_rule() == Rule::literal {
632                        let negated_expr = parse_expression(inner, id_gen)?;
633                        let negation_type = match rule_type {
634                            Rule::not_expr => NegationType::Not,
635                            Rule::have_not_expr => NegationType::HaveNot,
636                            Rule::not_have_expr => NegationType::NotHave,
637                            _ => NegationType::Not,
638                        };
639                        let kind =
640                            ExpressionKind::LogicalNegation(Box::new(negated_expr), negation_type);
641                        return Ok(traceable_expr(kind, &node, id_gen));
642                    }
643                }
644                return Err(LemmaError::Engine(
645                    "not/have not: missing reference".to_string(),
646                ));
647            }
648            Rule::sqrt_expr
649            | Rule::sin_expr
650            | Rule::cos_expr
651            | Rule::tan_expr
652            | Rule::asin_expr
653            | Rule::acos_expr
654            | Rule::atan_expr
655            | Rule::log_expr
656            | Rule::exp_expr => {
657                let operator = match node.as_rule() {
658                    Rule::sqrt_expr => MathematicalOperator::Sqrt,
659                    Rule::sin_expr => MathematicalOperator::Sin,
660                    Rule::cos_expr => MathematicalOperator::Cos,
661                    Rule::tan_expr => MathematicalOperator::Tan,
662                    Rule::asin_expr => MathematicalOperator::Asin,
663                    Rule::acos_expr => MathematicalOperator::Acos,
664                    Rule::atan_expr => MathematicalOperator::Atan,
665                    Rule::log_expr => MathematicalOperator::Log,
666                    Rule::exp_expr => MathematicalOperator::Exp,
667                    _ => {
668                        return Err(LemmaError::Engine(
669                            "Unknown mathematical operator".to_string(),
670                        ))
671                    }
672                };
673
674                for inner in node.clone().into_inner() {
675                    if inner.as_rule() == Rule::arithmetic_expression
676                        || inner.as_rule() == Rule::primary
677                    {
678                        let operand = parse_expression(inner, id_gen)?;
679                        let kind =
680                            ExpressionKind::MathematicalOperator(operator, Box::new(operand));
681                        return Ok(traceable_expr(kind, &node, id_gen));
682                    }
683                }
684                return Err(LemmaError::Engine(
685                    "Mathematical operator missing operand".to_string(),
686                ));
687            }
688            _ => {}
689        }
690    }
691    Err(LemmaError::Engine("Empty logical expression".to_string()))
692}
693
694fn parse_comparable_base(
695    pair: Pair<Rule>,
696    id_gen: &mut ExpressionIdGenerator,
697) -> Result<Expression, LemmaError> {
698    // comparable_base = { arithmetic_expression ~ (SPACE+ ~ ^"in" ~ SPACE+ ~ unit_types)? }
699    let mut pairs = pair.clone().into_inner();
700
701    let arith_expr = parse_expression(
702        pairs.next().ok_or_else(|| {
703            LemmaError::Engine("No arithmetic expression in comparable_base".to_string())
704        })?,
705        id_gen,
706    )?;
707
708    // Check for optional "in" unit conversion
709    if let Some(unit_pair) = pairs.next() {
710        if unit_pair.as_rule() == Rule::unit_word {
711            let target_unit = super::units::resolve_conversion_target(unit_pair.as_str())?;
712            let kind = ExpressionKind::UnitConversion(Box::new(arith_expr), target_unit);
713            return Ok(traceable_expr(kind, &pair, id_gen));
714        }
715    }
716
717    // No unit conversion, just return the arithmetic expression
718    Ok(arith_expr)
719}