sentri_dsl_parser/
parser.rs1use crate::grammar::{Grammar, Rule};
4use pest::Parser;
5use sentri_core::model::{BinaryOp, Expression, Invariant};
6use sentri_core::Result;
7
8pub struct InvariantParser;
10
11impl InvariantParser {
12 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 let (layers, expr_idx) = if inner_items.len() > 2 {
34 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 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 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
246pub 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}