1use super::ast::{DepthTracker, Span};
2use super::Rule;
3use crate::error::LemmaError;
4use crate::semantic::*;
5use crate::Source;
6use pest::iterators::Pair;
7use std::sync::Arc;
8
9pub(crate) fn parse_rule_definition(
10 pair: Pair<Rule>,
11 depth_tracker: &mut DepthTracker,
12 attribute: &str,
13 doc_name: &str,
14) -> Result<LemmaRule, LemmaError> {
15 let span = Span::from_pest_span(pair.as_span());
16 let pair_str = pair.as_str();
17 let mut rule_name = None;
18 let mut rule_expression = None;
19
20 for inner_pair in pair.into_inner() {
21 match inner_pair.as_rule() {
22 Rule::rule_name => rule_name = Some(inner_pair.as_str().to_string()),
23 Rule::rule_expression => {
24 rule_expression = Some(parse_rule_expression(
25 inner_pair,
26 depth_tracker,
27 attribute,
28 doc_name,
29 )?)
30 }
31 _ => {}
32 }
33 }
34
35 let name = rule_name.ok_or_else(|| {
36 LemmaError::engine(
37 "Grammar error: rule_definition missing rule_name",
38 span.clone(),
39 attribute,
40 Arc::from(pair_str),
41 doc_name,
42 1,
43 None::<String>,
44 )
45 })?;
46 let (expression, unless_clauses) = rule_expression.ok_or_else(|| {
47 LemmaError::engine(
48 "Grammar error: rule_definition missing rule_expression",
49 span.clone(),
50 attribute,
51 Arc::from(pair_str),
52 doc_name,
53 1,
54 None::<String>,
55 )
56 })?;
57
58 Ok(LemmaRule {
59 name,
60 expression,
61 unless_clauses,
62 source_location: Some(Source::new(
63 attribute.to_string(),
64 span.clone(),
65 doc_name.to_string(),
66 )),
67 })
68}
69
70fn parse_rule_expression(
71 pair: Pair<Rule>,
72 depth_tracker: &mut DepthTracker,
73 attribute: &str,
74 doc_name: &str,
75) -> Result<(Expression, Vec<UnlessClause>), LemmaError> {
76 let span = Span::from_pest_span(pair.as_span());
77 let pair_str = pair.as_str();
78 let mut expression = None;
79 let mut unless_clauses = Vec::new();
80
81 for inner_pair in pair.into_inner() {
82 match inner_pair.as_rule() {
83 Rule::expression => {
84 expression = Some(crate::parsing::expressions::parse_expression(
85 inner_pair,
86 depth_tracker,
87 attribute,
88 doc_name,
89 )?);
90 }
91 Rule::veto_expression => {
92 expression = Some(parse_veto_expression(inner_pair, attribute, doc_name)?);
93 }
94 Rule::unless_statement => {
95 let unless_clause =
96 parse_unless_statement(inner_pair, depth_tracker, attribute, doc_name)?;
97 unless_clauses.push(unless_clause);
98 }
99 _ => {}
100 }
101 }
102
103 let expr = expression.ok_or_else(|| {
104 LemmaError::engine(
105 "Grammar error: rule_expression missing expression",
106 span,
107 attribute,
108 Arc::from(pair_str),
109 doc_name,
110 1,
111 None::<String>,
112 )
113 })?;
114 Ok((expr, unless_clauses))
115}
116
117fn parse_veto_expression(
118 pair: Pair<Rule>,
119 attribute: &str,
120 doc_name: &str,
121) -> Result<Expression, LemmaError> {
122 let veto_span = Span::from_pest_span(pair.as_span());
123 let message = pair
126 .clone()
127 .into_inner()
128 .find(|p| p.as_rule() == Rule::text_literal)
129 .map(|string_pair| {
130 let content = string_pair.as_str();
131 content[1..content.len() - 1].to_string()
132 });
133 let kind = ExpressionKind::Veto(VetoExpression { message });
134 Ok(Expression::new(
135 kind,
136 Some(Source::new(
137 attribute.to_string(),
138 veto_span,
139 doc_name.to_string(),
140 )),
141 ))
142}
143
144fn parse_unless_statement(
145 pair: Pair<Rule>,
146 depth_tracker: &mut DepthTracker,
147 attribute: &str,
148 doc_name: &str,
149) -> Result<UnlessClause, LemmaError> {
150 let span = Span::from_pest_span(pair.as_span());
151 let mut condition = None;
152 let mut result = None;
153
154 for inner_pair in pair.clone().into_inner() {
155 match inner_pair.as_rule() {
156 Rule::expression => {
157 if condition.is_none() {
158 condition = Some(crate::parsing::expressions::parse_expression(
159 inner_pair,
160 depth_tracker,
161 attribute,
162 doc_name,
163 )?);
164 } else {
165 result = Some(crate::parsing::expressions::parse_expression(
166 inner_pair,
167 depth_tracker,
168 attribute,
169 doc_name,
170 )?);
171 }
172 }
173 Rule::veto_expression => {
174 result = Some(parse_veto_expression(inner_pair, attribute, doc_name)?);
175 }
176 _ => {}
177 }
178 }
179
180 let cond = condition.ok_or_else(|| {
181 LemmaError::engine(
182 "Grammar error: unless_statement missing condition",
183 span.clone(),
184 attribute,
185 Arc::from(pair.as_str()),
186 doc_name,
187 1,
188 None::<String>,
189 )
190 })?;
191 let res = result.ok_or_else(|| {
192 LemmaError::engine(
193 "Grammar error: unless_statement missing result",
194 span.clone(),
195 attribute,
196 Arc::from(pair.as_str()),
197 doc_name,
198 1,
199 None::<String>,
200 )
201 })?;
202
203 Ok(UnlessClause {
204 condition: cond,
205 result: res,
206 source_location: Some(Source::new(
207 attribute.to_string(),
208 span.clone(),
209 doc_name.to_string(),
210 )),
211 })
212}
213
214#[cfg(test)]
219mod tests {
220 use crate::parsing::parse;
221 use crate::{ExpressionKind, ResourceLimits, Value};
222
223 #[test]
224 fn parse_document_with_unless_clause_records_unless_clause() {
225 let input = r#"doc person
226rule is_active = service_started? and not service_ended?
227unless maintenance_mode then false"#;
228 let result = parse(input, "test.lemma", &ResourceLimits::default()).unwrap();
229 assert_eq!(result.len(), 1);
230 assert_eq!(result[0].rules.len(), 1);
231 assert_eq!(result[0].rules[0].unless_clauses.len(), 1);
232 }
233
234 #[test]
235 fn parse_multiple_unless_clauses_records_all_unless_clauses() {
236 let input = r#"doc test
237rule is_eligible = age >= 18 and has_license
238unless emergency_mode then true
239unless system_override then accept"#;
240
241 let result = parse(input, "test.lemma", &ResourceLimits::default()).unwrap();
242 assert_eq!(result.len(), 1);
243 assert_eq!(result[0].rules.len(), 1);
244 assert_eq!(result[0].rules[0].unless_clauses.len(), 2);
245 }
246
247 #[test]
248 fn parse_multiple_rules_in_document_preserves_rule_names() {
249 let input = r#"doc test
250rule is_adult = age >= 18
251rule is_senior = age >= 65
252rule is_minor = age < 18
253rule can_vote = age >= 18 and is_citizen"#;
254
255 let result = parse(input, "test.lemma", &ResourceLimits::default()).unwrap();
256 assert_eq!(result.len(), 1);
257 assert_eq!(result[0].rules.len(), 4);
258 assert_eq!(result[0].rules[0].name, "is_adult");
259 assert_eq!(result[0].rules[1].name, "is_senior");
260 assert_eq!(result[0].rules[2].name, "is_minor");
261 assert_eq!(result[0].rules[3].name, "can_vote");
262 }
263
264 #[test]
265 fn veto_in_unless_clauses_parses_with_message() {
266 let input = r#"doc test
267rule is_adult = age >= 18 unless age < 0 then veto "Age must be 0 or higher""#;
268 let docs = parse(input, "test.lemma", &ResourceLimits::default()).unwrap();
269 assert_eq!(docs.len(), 1);
270 assert_eq!(docs[0].rules.len(), 1);
271
272 let rule = &docs[0].rules[0];
273 assert_eq!(rule.name, "is_adult");
274 assert_eq!(rule.unless_clauses.len(), 1);
275
276 match &rule.unless_clauses[0].result.kind {
277 ExpressionKind::Veto(veto) => {
278 assert_eq!(veto.message, Some("Age must be 0 or higher".to_string()));
279 }
280 other => panic!("Expected veto expression, got {:?}", other),
281 }
282
283 let input = r#"doc test
284rule is_adult = age >= 18
285 unless age > 150 then veto "Age cannot be over 150"
286 unless age < 0 then veto "Age must be 0 or higher""#;
287 let docs = parse(input, "test.lemma", &ResourceLimits::default()).unwrap();
288 let rule = &docs[0].rules[0];
289 assert_eq!(rule.unless_clauses.len(), 2);
290
291 match &rule.unless_clauses[0].result.kind {
292 ExpressionKind::Veto(veto) => {
293 assert_eq!(veto.message, Some("Age cannot be over 150".to_string()));
294 }
295 other => panic!("Expected veto expression, got {:?}", other),
296 }
297
298 match &rule.unless_clauses[1].result.kind {
299 ExpressionKind::Veto(veto) => {
300 assert_eq!(veto.message, Some("Age must be 0 or higher".to_string()));
301 }
302 other => panic!("Expected veto expression, got {:?}", other),
303 }
304 }
305
306 #[test]
307 fn veto_without_message_parses_as_veto_with_no_message() {
308 let input = r#"doc test
309rule adult = age >= 18 unless age > 150 then veto"#;
310 let docs = parse(input, "test.lemma", &ResourceLimits::default()).unwrap();
311 let rule = &docs[0].rules[0];
312 assert_eq!(rule.unless_clauses.len(), 1);
313
314 match &rule.unless_clauses[0].result.kind {
315 ExpressionKind::Veto(veto) => {
316 assert_eq!(veto.message, None);
317 }
318 other => panic!("Expected veto expression, got {:?}", other),
319 }
320 }
321
322 #[test]
323 fn mixed_veto_and_regular_unless_parses_both_results() {
324 let input = r#"doc test
325rule adjusted_age = age + 1
326 unless age < 0 then veto "Invalid age"
327 unless age > 100 then 100"#;
328 let docs = parse(input, "test.lemma", &ResourceLimits::default()).unwrap();
329 let rule = &docs[0].rules[0];
330 assert_eq!(rule.unless_clauses.len(), 2);
331
332 match &rule.unless_clauses[0].result.kind {
333 ExpressionKind::Veto(veto) => {
334 assert_eq!(veto.message, Some("Invalid age".to_string()));
335 }
336 other => panic!("Expected veto expression, got {:?}", other),
337 }
338
339 match &rule.unless_clauses[1].result.kind {
340 ExpressionKind::Literal(lit) => match &lit.value {
341 Value::Number(n) => assert_eq!(*n, rust_decimal::Decimal::new(100, 0)),
342 other => panic!("Expected literal number, got {:?}", other),
343 },
344 other => panic!("Expected literal result, got {:?}", other),
345 }
346 }
347}