Skip to main content

sentri_dsl_parser/
parser.rs

1//! Parser for invariant DSL expressions.
2
3use crate::grammar::{Grammar, Rule};
4use pest::Parser;
5use sentri_core::model::{BinaryOp, Expression, Invariant};
6use sentri_core::Result;
7
8/// Parser for invariant DSL.
9pub struct InvariantParser;
10
11impl InvariantParser {
12    /// Parse a single invariant definition.
13    pub fn parse_invariant(input: &str) -> Result<Invariant> {
14        let parsed = Grammar::parse(Rule::invariant_def, input)
15            .map_err(|e| sentri_core::InvarError::ConfigError(e.to_string()))?;
16
17        let invariant_rule = parsed.into_iter().next().ok_or_else(|| {
18            sentri_core::InvarError::ConfigError("No invariant found".to_string())
19        })?;
20
21        let inner = invariant_rule.into_inner();
22        let inner_items: Vec<_> = inner.collect();
23
24        if inner_items.is_empty() {
25            return Err(sentri_core::InvarError::ConfigError(
26                "Expected invariant name and expression".to_string(),
27            ));
28        }
29
30        let name = inner_items[0].as_str().to_string();
31
32        // Parse optional layer specifications and expression
33        let (layers, expr_idx) = if inner_items.len() > 2 {
34            // Check if second item is layer specification
35            let second_item = &inner_items[1];
36            if second_item.as_rule() == Rule::layer_name
37                || (second_item.as_str().starts_with('(') && second_item.as_str().contains(','))
38            {
39                // This is a layer specification, parse all layer names
40                let mut layer_specs = Vec::new();
41                for item in inner_items.iter().take(inner_items.len() - 1).skip(1) {
42                    let item_str = item
43                        .as_str()
44                        .trim_matches(|c| c == '(' || c == ')' || c == ',')
45                        .trim();
46                    if !item_str.is_empty() {
47                        layer_specs.push(item_str.to_string());
48                    }
49                }
50                (layer_specs, inner_items.len() - 1)
51            } else {
52                (vec![], 1)
53            }
54        } else {
55            (vec![], 1)
56        };
57
58        let expression = Self::parse_expr(inner_items[expr_idx].clone())?;
59
60        Ok(Invariant {
61            name,
62            description: None,
63            expression,
64            severity: "medium".to_string(),
65            category: "general".to_string(),
66            is_always_true: true,
67            layers,
68            phases: vec![],
69        })
70    }
71
72    fn parse_expr(rule: pest::iterators::Pair<Rule>) -> Result<Expression> {
73        use pest::iterators::Pair;
74
75        fn parse_pair(pair: Pair<Rule>) -> Result<Expression> {
76            match pair.as_rule() {
77                Rule::expr
78                | Rule::logical_or
79                | Rule::logical_and
80                | Rule::comparison
81                | Rule::unary => {
82                    let items: Vec<_> = pair.into_inner().collect();
83                    if items.is_empty() {
84                        return Err(sentri_core::InvarError::ConfigError(
85                            "Expected expression".to_string(),
86                        ));
87                    }
88
89                    let mut left = parse_pair(items[0].clone())?;
90                    let mut i = 1;
91
92                    while i < items.len() {
93                        let operator = &items[i];
94                        i += 1;
95
96                        if i >= items.len() {
97                            return Err(sentri_core::InvarError::ConfigError(
98                                "Expected operand after operator".to_string(),
99                            ));
100                        }
101
102                        let right = parse_pair(items[i].clone())?;
103                        i += 1;
104
105                        match operator.as_rule() {
106                            Rule::and => {
107                                left = Expression::Logical {
108                                    left: Box::new(left),
109                                    op: sentri_core::model::LogicalOp::And,
110                                    right: Box::new(right),
111                                };
112                            }
113                            Rule::or => {
114                                left = Expression::Logical {
115                                    left: Box::new(left),
116                                    op: sentri_core::model::LogicalOp::Or,
117                                    right: Box::new(right),
118                                };
119                            }
120                            Rule::eq => {
121                                left = Expression::BinaryOp {
122                                    left: Box::new(left),
123                                    op: BinaryOp::Eq,
124                                    right: Box::new(right),
125                                };
126                            }
127                            Rule::neq => {
128                                left = Expression::BinaryOp {
129                                    left: Box::new(left),
130                                    op: BinaryOp::Neq,
131                                    right: Box::new(right),
132                                };
133                            }
134                            Rule::lt => {
135                                left = Expression::BinaryOp {
136                                    left: Box::new(left),
137                                    op: BinaryOp::Lt,
138                                    right: Box::new(right),
139                                };
140                            }
141                            Rule::gt => {
142                                left = Expression::BinaryOp {
143                                    left: Box::new(left),
144                                    op: BinaryOp::Gt,
145                                    right: Box::new(right),
146                                };
147                            }
148                            Rule::lte => {
149                                left = Expression::BinaryOp {
150                                    left: Box::new(left),
151                                    op: BinaryOp::Lte,
152                                    right: Box::new(right),
153                                };
154                            }
155                            Rule::gte => {
156                                left = Expression::BinaryOp {
157                                    left: Box::new(left),
158                                    op: BinaryOp::Gte,
159                                    right: Box::new(right),
160                                };
161                            }
162                            _ => {}
163                        }
164                    }
165                    Ok(left)
166                }
167                Rule::primary => {
168                    let mut inner = pair.into_inner();
169                    let next = inner.next();
170                    if let Some(inner_pair) = next {
171                        parse_pair(inner_pair)
172                    } else {
173                        // Empty primary - should not happen in well-formed grammar
174                        Err(sentri_core::InvarError::ConfigError(
175                            "Unexpected empty primary expression".to_string(),
176                        ))
177                    }
178                }
179                Rule::function_call => {
180                    let items: Vec<_> = pair.into_inner().collect();
181                    if items.is_empty() {
182                        return Err(sentri_core::InvarError::ConfigError(
183                            "Expected function name".to_string(),
184                        ));
185                    }
186                    let name = items[0].as_str().to_string();
187                    let args: Result<Vec<_>> = items[1..]
188                        .iter()
189                        .map(|arg| parse_pair(arg.clone()))
190                        .collect();
191                    Ok(Expression::FunctionCall { name, args: args? })
192                }
193                Rule::boolean => {
194                    let val = pair.as_str() == "true";
195                    Ok(Expression::Boolean(val))
196                }
197                Rule::integer => {
198                    let val = pair.as_str().parse::<i128>().map_err(|_| {
199                        sentri_core::InvarError::ConfigError("Invalid integer".to_string())
200                    })?;
201                    Ok(Expression::Int(val))
202                }
203                Rule::identifier => Ok(Expression::Var(pair.as_str().to_string())),
204                Rule::qualified_id => {
205                    let items: Vec<_> = pair.into_inner().collect();
206                    if items.len() != 2 {
207                        return Err(sentri_core::InvarError::ConfigError(
208                            "Expected layer::identifier".to_string(),
209                        ));
210                    }
211                    let layer = items[0].as_str().to_string();
212                    let var = items[1].as_str().to_string();
213                    Ok(Expression::LayerVar { layer, var })
214                }
215                Rule::var_id => {
216                    let mut inner = pair.into_inner();
217                    if let Some(first) = inner.next() {
218                        if first.as_rule() == Rule::qualified_id {
219                            let items: Vec<_> = first.into_inner().collect();
220                            if items.len() == 2 {
221                                let layer = items[0].as_str().to_string();
222                                let var = items[1].as_str().to_string();
223                                return Ok(Expression::LayerVar { layer, var });
224                            }
225                        } else if first.as_rule() == Rule::simple_id {
226                            return Ok(Expression::Var(first.as_str().to_string()));
227                        } else {
228                            return parse_pair(first);
229                        }
230                    }
231                    Err(sentri_core::InvarError::ConfigError(
232                        "Expected identifier or layer::identifier".to_string(),
233                    ))
234                }
235                _ => Err(sentri_core::InvarError::ConfigError(format!(
236                    "Unexpected rule: {:?}",
237                    pair.as_rule()
238                ))),
239            }
240        }
241
242        parse_pair(rule)
243    }
244}
245
246/// Parse a complete invariant definition string.
247pub fn parse_invariant(input: &str) -> Result<Invariant> {
248    InvariantParser::parse_invariant(input)
249}
250
251#[cfg(test)]
252mod tests {
253    use super::*;
254
255    #[test]
256    fn test_parse_simple_invariant() {
257        let input = r#"invariant BalancePositive { balance >= 0 }"#;
258        let result = parse_invariant(input);
259        if let Err(ref e) = result {
260            eprintln!("Parse error: {}", e);
261        }
262        assert!(result.is_ok());
263        let inv = result.unwrap();
264        assert_eq!(inv.name, "BalancePositive");
265    }
266
267    #[test]
268    fn test_parse_invariant_with_and() {
269        let input = r#"invariant MultiCondition { balance >= 0 && total_supply > 0 }"#;
270        let result = parse_invariant(input);
271        if let Err(ref e) = result {
272            eprintln!("Parse error: {}", e);
273        }
274        assert!(result.is_ok());
275    }
276
277    #[test]
278    fn test_invalid_invariant_no_expression() {
279        let input = r#"invariant Empty { }"#;
280        let result = parse_invariant(input);
281        assert!(result.is_err());
282    }
283}