espresso_logic/expression/
parser.rs

1//! Parsing support for boolean expressions
2
3use super::error::{ExpressionParseError, ParseBoolExprError};
4use super::BoolExpr;
5use std::sync::Arc;
6
7// Lalrpop-generated parser module (generated in OUT_DIR at build time)
8#[allow(clippy::all)]
9mod parser_impl {
10    #![allow(clippy::all)]
11    #![allow(dead_code)]
12    #![allow(unused_variables)]
13    #![allow(unused_imports)]
14    #![allow(non_snake_case)]
15    #![allow(non_camel_case_types)]
16    #![allow(non_upper_case_globals)]
17    include!(concat!(env!("OUT_DIR"), "/expression/bool_expr.rs"));
18}
19
20impl BoolExpr {
21    /// Parse a boolean expression from a string
22    ///
23    /// Supports standard boolean operators:
24    /// - `+` or `|` for OR
25    /// - `*` or `&` for AND  
26    /// - `~` or `!` for NOT
27    /// - Parentheses for grouping
28    /// - Constants: `0`, `1`, `true`, `false`
29    pub fn parse(input: &str) -> Result<Self, ParseBoolExprError> {
30        parser_impl::ExprParser::new().parse(input).map_err(|e| {
31            let message = e.to_string();
32            // Try to extract position from lalrpop error message
33            let position = extract_position_from_error(&message);
34            ExpressionParseError::InvalidSyntax {
35                message: Arc::from(message.as_str()),
36                input: Arc::from(input),
37                position,
38            }
39            .into()
40        })
41    }
42}
43
44/// Helper function to extract position information from lalrpop error messages
45///
46/// Lalrpop errors often contain position information in the form "at line X column Y"
47/// or similar patterns. This function attempts to extract that information.
48fn extract_position_from_error(error_msg: &str) -> Option<usize> {
49    // Try to find patterns like "at 5" or "position 5" or similar
50    // Lalrpop errors typically have format like "Unrecognized token `+` at line 1 column 7"
51
52    // Look for "column N" pattern
53    if let Some(col_idx) = error_msg.find("column ") {
54        let after_col = &error_msg[col_idx + 7..];
55        if let Some(end_idx) = after_col.find(|c: char| !c.is_ascii_digit()) {
56            if let Ok(col) = after_col[..end_idx].parse::<usize>() {
57                return Some(col.saturating_sub(1)); // Convert to 0-indexed
58            }
59        }
60    }
61
62    // Look for "at N" pattern
63    if let Some(at_idx) = error_msg.rfind(" at ") {
64        let after_at = &error_msg[at_idx + 4..];
65        if let Some(end_idx) = after_at.find(|c: char| !c.is_ascii_digit()) {
66            if let Ok(pos) = after_at[..end_idx].parse::<usize>() {
67                return Some(pos);
68            }
69        }
70        // Try parsing until end if no non-digit found
71        if let Ok(pos) = after_at.trim().parse::<usize>() {
72            return Some(pos);
73        }
74    }
75
76    None
77}