1use crate::ast::Expression;
2use crate::rule::Rule;
3use pest::iterators::{Pair, Pairs};
4use pest::pratt_parser::{Assoc, Op, PrattParser};
5use pest::Parser;
6
7mod grammar {
10 #[derive(pest_derive::Parser)]
11 #[grammar = "grl.pest"]
12 pub struct GrlGrammar;
13}
14
15use grammar::{GrlGrammar, Rule as GrammarRule};
16
17pub struct GrlParser {
22 pratt: PrattParser<GrammarRule>,
23}
24
25impl GrlParser {
26 pub fn new() -> Self {
28 let pratt = PrattParser::new()
31 .op(Op::infix(GrammarRule::or, Assoc::Left))
32 .op(Op::infix(GrammarRule::and, Assoc::Left))
33 .op(Op::infix(GrammarRule::eq, Assoc::Left)
34 | Op::infix(GrammarRule::ne, Assoc::Left)
35 | Op::infix(GrammarRule::lt, Assoc::Left)
36 | Op::infix(GrammarRule::le, Assoc::Left)
37 | Op::infix(GrammarRule::gt, Assoc::Left)
38 | Op::infix(GrammarRule::ge, Assoc::Left))
39 .op(Op::infix(GrammarRule::add, Assoc::Left)
40 | Op::infix(GrammarRule::sub, Assoc::Left)
41 | Op::infix(GrammarRule::bitand, Assoc::Left)
42 | Op::infix(GrammarRule::bitor, Assoc::Left))
43 .op(Op::infix(GrammarRule::mul, Assoc::Left)
44 | Op::infix(GrammarRule::div, Assoc::Left)
45 | Op::infix(GrammarRule::modulo, Assoc::Left))
46 .op(Op::prefix(GrammarRule::not) | Op::prefix(GrammarRule::neg))
47 .op(Op::postfix(GrammarRule::method)
48 | Op::postfix(GrammarRule::field)
49 | Op::postfix(GrammarRule::index));
50
51 Self { pratt }
52 }
53
54 pub fn parse_rule(&self, grl_text: &str) -> std::result::Result<Rule, String> {
56 let mut rules = self.parse_rules(grl_text)?;
57 match rules.len() {
58 1 => Ok(rules.remove(0)),
59 n => Err(format!("Expected exactly one rule, found {}", n)),
60 }
61 }
62
63 pub fn parse_rules(&self, grl_text: &str) -> std::result::Result<Vec<Rule>, String> {
65 let grl = GrlGrammar::parse(GrammarRule::grl, grl_text)
66 .map_err(|e| format!("Invalid GRL syntax:\n{}", e))?
67 .next()
68 .unwrap();
69
70 grl.into_inner()
71 .filter(|pair| pair.as_rule() == GrammarRule::rule_def)
72 .map(|pair| self.parse_rule_def(pair))
73 .collect()
74 }
75
76 fn parse_rule_def(&self, pair: Pair<GrammarRule>) -> std::result::Result<Rule, String> {
77 let mut name = String::new();
78 let mut description = None;
79 let mut salience = 0;
80 let mut when_condition = None;
81 let mut then_actions = Vec::new();
82
83 for part in pair.into_inner() {
84 match part.as_rule() {
85 GrammarRule::ident => name = part.as_str().to_string(),
86 GrammarRule::description => {
87 description = Some(parse_string(part.into_inner().next().unwrap())?)
88 }
89 GrammarRule::salience => {
90 let value = part.into_inner().next_back().unwrap();
91 salience = parse_integer(value.as_str())
92 .and_then(|n| i32::try_from(n).map_err(|e| e.to_string()))
93 .map_err(|e| format!("Invalid salience {}: {}", value.as_str(), e))?;
94 }
95 GrammarRule::expr => when_condition = Some(self.parse_expr(part.into_inner())?),
96 GrammarRule::action => then_actions.push(self.parse_action(part)?),
97 _ => {} }
99 }
100
101 let mut rule = Rule::new(name, salience, when_condition.unwrap(), then_actions);
102 if let Some(desc) = description {
103 rule = rule.with_description(desc);
104 }
105 Ok(rule)
106 }
107
108 fn parse_action(&self, pair: Pair<GrammarRule>) -> std::result::Result<Expression, String> {
109 let (line, col) = pair.line_col();
110 let mut parts = pair.into_inner();
111 let target_pair = parts.next().unwrap();
112 let target_text = target_pair.as_str().trim_end().to_string();
113 let target = self.parse_expr(target_pair.into_inner())?;
114
115 let Some(op) = parts.next() else {
116 return match target {
117 Expression::Call(..) | Expression::MethodCall(..) => Ok(target),
118 _ => Err(format!(
119 "{}:{}: expected an assignment or function call, found `{}`",
120 line, col, target_text
121 )),
122 };
123 };
124 if !target.is_assignable() {
125 return Err(format!(
126 "{}:{}: cannot assign to `{}`",
127 line, col, target_text
128 ));
129 }
130
131 let mut value = self.parse_expr(parts.next().unwrap().into_inner())?;
132
133 if op.as_str() != "=" {
135 let current = Box::new(target.clone());
136 let rhs = Box::new(value);
137 value = match op.as_str() {
138 "+=" => Expression::Add(current, rhs),
139 "-=" => Expression::Subtract(current, rhs),
140 "*=" => Expression::Multiply(current, rhs),
141 "/=" => Expression::Divide(current, rhs),
142 other => unreachable!("unknown assignment operator {}", other),
143 };
144 }
145
146 Ok(Expression::Assign(Box::new(target), Box::new(value)))
147 }
148
149 fn parse_args(&self, pair: Pair<GrammarRule>) -> std::result::Result<Vec<Expression>, String> {
150 pair.into_inner()
151 .map(|arg| self.parse_expr(arg.into_inner()))
152 .collect()
153 }
154
155 fn parse_expr(&self, pairs: Pairs<GrammarRule>) -> std::result::Result<Expression, String> {
156 self.pratt
157 .map_primary(|primary| match primary.as_rule() {
158 GrammarRule::integer => parse_integer(primary.as_str())
159 .map(Expression::Integer)
160 .map_err(|e| format!("Invalid integer {}: {}", primary.as_str(), e)),
161 GrammarRule::float => parse_float(primary.as_str())
162 .map(Expression::Float)
163 .map_err(|e| format!("Invalid number {}: {}", primary.as_str(), e)),
164 GrammarRule::string => parse_string(primary).map(Expression::String),
165 GrammarRule::boolean => Ok(Expression::Boolean(
166 primary.as_str().eq_ignore_ascii_case("true"),
167 )),
168 GrammarRule::nil => Ok(Expression::Nil),
169 GrammarRule::ident => Ok(Expression::Variable(primary.as_str().to_string())),
170 GrammarRule::call => {
171 let mut inner = primary.into_inner();
172 let name = inner.next().unwrap().as_str().to_string();
173 Ok(Expression::Call(
174 name,
175 self.parse_args(inner.next().unwrap())?,
176 ))
177 }
178 GrammarRule::expr => self.parse_expr(primary.into_inner()),
179 rule => unreachable!("unexpected primary {:?}", rule),
180 })
181 .map_prefix(|op, operand| {
182 let operand = operand?;
183 Ok(match op.as_rule() {
184 GrammarRule::not => Expression::Not(Box::new(operand)),
185 GrammarRule::neg => match operand {
186 Expression::Integer(n) => Expression::Integer(n.wrapping_neg()),
187 Expression::Float(n) => Expression::Float(-n),
188 other => {
189 Expression::Subtract(Box::new(Expression::Integer(0)), Box::new(other))
190 }
191 },
192 rule => unreachable!("unexpected prefix {:?}", rule),
193 })
194 })
195 .map_infix(|lhs, op, rhs| {
196 let (lhs, rhs) = (Box::new(lhs?), Box::new(rhs?));
197 Ok(match op.as_rule() {
198 GrammarRule::or => Expression::Or(lhs, rhs),
199 GrammarRule::and => Expression::And(lhs, rhs),
200 GrammarRule::eq => Expression::Equal(lhs, rhs),
201 GrammarRule::ne => Expression::NotEqual(lhs, rhs),
202 GrammarRule::lt => Expression::LessThan(lhs, rhs),
203 GrammarRule::le => Expression::LessEqual(lhs, rhs),
204 GrammarRule::gt => Expression::GreaterThan(lhs, rhs),
205 GrammarRule::ge => Expression::GreaterEqual(lhs, rhs),
206 GrammarRule::add => Expression::Add(lhs, rhs),
207 GrammarRule::sub => Expression::Subtract(lhs, rhs),
208 GrammarRule::mul => Expression::Multiply(lhs, rhs),
209 GrammarRule::div => Expression::Divide(lhs, rhs),
210 GrammarRule::modulo => Expression::Modulo(lhs, rhs),
211 GrammarRule::bitand => Expression::BitAnd(lhs, rhs),
212 GrammarRule::bitor => Expression::BitOr(lhs, rhs),
213 rule => unreachable!("unexpected infix {:?}", rule),
214 })
215 })
216 .map_postfix(|lhs, op| {
217 let lhs = Box::new(lhs?);
218 let mut inner = op.clone().into_inner();
219 Ok(match op.as_rule() {
220 GrammarRule::field => {
221 Expression::FieldAccess(lhs, inner.next().unwrap().as_str().to_string())
222 }
223 GrammarRule::index => Expression::Index(
224 lhs,
225 Box::new(self.parse_expr(inner.next().unwrap().into_inner())?),
226 ),
227 GrammarRule::method => {
228 let name = inner.next().unwrap().as_str().to_string();
229 Expression::MethodCall(lhs, name, self.parse_args(inner.next().unwrap())?)
230 }
231 rule => unreachable!("unexpected postfix {:?}", rule),
232 })
233 })
234 .parse(pairs)
235 }
236}
237
238fn parse_string(pair: Pair<GrammarRule>) -> std::result::Result<String, String> {
239 let inner = pair
241 .into_inner()
242 .next()
243 .unwrap()
244 .into_inner()
245 .next()
246 .unwrap();
247 let quote = if inner.as_rule() == GrammarRule::dq_inner {
248 '"'
249 } else {
250 '\''
251 };
252 unescape(inner.as_str(), quote)
253}
254
255fn unescape(raw: &str, quote: char) -> std::result::Result<String, String> {
259 let mut out: Vec<u8> = Vec::with_capacity(raw.len());
260 let mut chars = raw.chars().peekable();
261 let digits = |chars: &mut std::iter::Peekable<std::str::Chars>, count: usize, radix: u32| {
262 let text: String = chars.by_ref().take(count).collect();
263 if text.len() != count {
264 return Err(format!("Incomplete escape sequence: {}", text));
265 }
266 u32::from_str_radix(&text, radix).map_err(|_| format!("Invalid escape sequence: {}", text))
267 };
268 while let Some(c) = chars.next() {
269 if c == quote {
270 chars.next();
272 out.push(quote as u8);
273 continue;
274 }
275 if c != '\\' {
276 out.extend_from_slice(c.encode_utf8(&mut [0; 4]).as_bytes());
277 continue;
278 }
279 let Some(escape) = chars.next() else {
280 return Err("Unterminated escape sequence".to_string());
281 };
282 let byte = match escape {
283 'a' => 0x07,
284 'b' => 0x08,
285 'f' => 0x0c,
286 'n' => b'\n',
287 'r' => b'\r',
288 't' => b'\t',
289 'v' => 0x0b,
290 '\\' => b'\\',
291 '\'' => b'\'',
292 '"' => b'"',
293 'x' => digits(&mut chars, 2, 16)? as u8,
294 '0'..='7' => {
295 let rest = digits(&mut chars, 2, 8)?;
296 let value = escape.to_digit(8).unwrap() * 64 + rest;
297 u8::try_from(value).map_err(|_| format!("Octal escape out of range: {}", value))?
298 }
299 'u' | 'U' => {
300 let code = digits(&mut chars, if escape == 'u' { 4 } else { 8 }, 16)?;
301 let c = char::from_u32(code)
302 .ok_or_else(|| format!("Invalid Unicode code point: {:X}", code))?;
303 out.extend_from_slice(c.encode_utf8(&mut [0; 4]).as_bytes());
304 continue;
305 }
306 other => return Err(format!("Invalid escape sequence: \\{}", other)),
307 };
308 out.push(byte);
309 }
310 String::from_utf8(out).map_err(|_| "String literal is not valid UTF-8".to_string())
311}
312
313fn parse_integer(text: &str) -> std::result::Result<i64, String> {
316 let (negative, digits) = match text.strip_prefix('-') {
317 Some(rest) => (true, rest),
318 None => (false, text),
319 };
320 let magnitude = if let Some(hex) = digits
321 .strip_prefix("0x")
322 .or_else(|| digits.strip_prefix("0X"))
323 {
324 i64::from_str_radix(hex, 16)
325 } else if digits.len() > 1 && digits.starts_with('0') {
326 i64::from_str_radix(&digits[1..], 8)
327 } else {
328 digits.parse()
329 }
330 .map_err(|e| e.to_string())?;
331 Ok(if negative { -magnitude } else { magnitude })
332}
333
334fn parse_float(text: &str) -> std::result::Result<f64, String> {
336 let Some(hex) = text.strip_prefix("0x").or_else(|| text.strip_prefix("0X")) else {
337 return text
338 .parse()
339 .map_err(|e: std::num::ParseFloatError| e.to_string());
340 };
341 let (mantissa, exponent) = hex.split_once(['p', 'P']).ok_or("missing exponent")?;
342 let (whole, fraction) = mantissa.split_once('.').unwrap_or((mantissa, ""));
343 let mut value = 0.0;
344 for digit in whole.chars().chain(fraction.chars()) {
345 value = value * 16.0 + digit.to_digit(16).ok_or("invalid hex digit")? as f64;
346 }
347 let exponent: i32 = exponent
348 .parse()
349 .map_err(|e: std::num::ParseIntError| e.to_string())?;
350 Ok(value * 2f64.powi(exponent - 4 * fraction.len() as i32))
351}
352
353impl Default for GrlParser {
354 fn default() -> Self {
355 Self::new()
356 }
357}
358
359#[cfg(test)]
360mod tests {
361 use super::*;
362 use crate::engine::RuleEngine;
363 use crate::facts::{Fact, FactValue};
364 use std::collections::HashMap;
365
366 fn var(name: &str) -> Box<Expression> {
367 Box::new(Expression::Variable(name.to_string()))
368 }
369
370 fn num(n: i64) -> Box<Expression> {
371 Box::new(Expression::Integer(n))
372 }
373
374 fn when_of(condition: &str) -> Expression {
375 let grl = format!("rule R {{ when {} then }}", condition);
376 GrlParser::new().parse_rule(&grl).unwrap().when_condition
377 }
378
379 #[test]
380 fn test_arithmetic_precedence() {
381 assert_eq!(
383 when_of("a + b * c > 0"),
384 Expression::GreaterThan(
385 Box::new(Expression::Add(
386 var("a"),
387 Box::new(Expression::Multiply(var("b"), var("c")))
388 )),
389 num(0),
390 )
391 );
392 }
393
394 #[test]
395 fn test_logical_precedence() {
396 assert_eq!(
398 when_of("a || b && c"),
399 Expression::Or(var("a"), Box::new(Expression::And(var("b"), var("c"))))
400 );
401 }
402
403 #[test]
404 fn test_parentheses_override_precedence() {
405 assert_eq!(
406 when_of("(a || b) && c"),
407 Expression::And(Box::new(Expression::Or(var("a"), var("b"))), var("c"))
408 );
409 assert_eq!(
410 when_of("(a + b) * c == 1"),
411 Expression::Equal(
412 Box::new(Expression::Multiply(
413 Box::new(Expression::Add(var("a"), var("b"))),
414 var("c")
415 )),
416 num(1),
417 )
418 );
419 }
420
421 #[test]
422 fn test_left_associativity() {
423 assert_eq!(
425 when_of("a - b - c == 0"),
426 Expression::Equal(
427 Box::new(Expression::Subtract(
428 Box::new(Expression::Subtract(var("a"), var("b"))),
429 var("c")
430 )),
431 num(0),
432 )
433 );
434 }
435
436 #[test]
437 fn test_unary_operators() {
438 assert_eq!(
439 when_of("!done && x > -5"),
440 Expression::And(
441 Box::new(Expression::Not(var("done"))),
442 Box::new(Expression::GreaterThan(var("x"), num(-5))),
443 )
444 );
445 assert_eq!(
446 when_of("-x < 0"),
447 Expression::LessThan(Box::new(Expression::Subtract(num(0), var("x"))), num(0))
448 );
449 }
450
451 #[test]
452 fn test_nested_field_access() {
453 assert_eq!(
454 when_of("order.customer.age >= 18"),
455 Expression::GreaterEqual(
456 Box::new(Expression::FieldAccess(
457 Box::new(Expression::FieldAccess(
458 var("order"),
459 "customer".to_string()
460 )),
461 "age".to_string(),
462 )),
463 num(18),
464 )
465 );
466 }
467
468 #[test]
469 fn test_string_escapes() {
470 assert_eq!(
471 when_of(r#"msg == "say \"hi\"\n""#),
472 Expression::Equal(
473 var("msg"),
474 Box::new(Expression::String("say \"hi\"\n".to_string()))
475 )
476 );
477 }
478
479 #[test]
480 fn test_identifiers_starting_with_keywords() {
481 assert_eq!(when_of("ruleset && thenable && trueish"), {
482 Expression::And(
483 Box::new(Expression::And(var("ruleset"), var("thenable"))),
484 var("trueish"),
485 )
486 });
487 }
488
489 #[test]
490 fn test_compound_assignment() {
491 let rule = GrlParser::new()
492 .parse_rule("rule R { when true then x += 1; car.Speed *= 2; }")
493 .unwrap();
494 assert_eq!(
495 rule.then_actions,
496 vec![
497 Expression::assign(&["x"], Expression::Add(var("x"), num(1))),
498 Expression::assign(
499 &["car", "Speed"],
500 Expression::Multiply(
501 Box::new(Expression::FieldAccess(var("car"), "Speed".to_string())),
502 num(2),
503 ),
504 ),
505 ]
506 );
507 }
508
509 #[test]
510 fn test_multiple_rules_with_comments() {
511 let grl = r#"
512 // Line comment
513 rule First "first rule" salience 10 {
514 when x > 1 /* inline comment */ then y = 1;
515 }
516
517 /* Block
518 comment */
519 rule Second salience -5 {
520 when true
521 then z = "two";
522 }
523 "#;
524 let rules = GrlParser::new().parse_rules(grl).unwrap();
525 assert_eq!(rules.len(), 2);
526 assert_eq!(rules[0].name, "First");
527 assert_eq!(rules[0].description, Some("first rule".to_string()));
528 assert_eq!(rules[0].salience, 10);
529 assert_eq!(rules[1].name, "Second");
530 assert_eq!(rules[1].description, None);
531 assert_eq!(rules[1].salience, -5);
532 }
533
534 #[test]
535 fn test_parse_rule_requires_exactly_one_rule() {
536 let parser = GrlParser::new();
537 assert!(parser.parse_rule("").is_err());
538 assert!(parser
539 .parse_rule("rule A { when true then } rule B { when true then }")
540 .is_err());
541 }
542
543 #[test]
544 fn test_syntax_errors_report_location() {
545 let parser = GrlParser::new();
546 let err = parser
547 .parse_rule("rule Broken {\n when x > \n then y = 1;\n}")
548 .unwrap_err();
549 assert!(err.contains("3:"), "error should point at line 3: {}", err);
550
551 assert!(parser
552 .parse_rule("rule R { when x > 1 then y = 1 }")
553 .is_err()); assert!(parser
555 .parse_rule("rule R { when (x > 1 then y = 1; }")
556 .is_err()); assert!(parser.parse_rule("rule then { when true then }").is_err()); }
559
560 #[test]
561 fn test_nested_field_assignment() {
562 let rule = GrlParser::new()
563 .parse_rule("rule R { when true then a.b.c = 1; a.b.c += 2; }")
564 .unwrap();
565 let current = Expression::FieldAccess(
566 Box::new(Expression::FieldAccess(var("a"), "b".to_string())),
567 "c".to_string(),
568 );
569 assert_eq!(
570 rule.then_actions,
571 vec![
572 Expression::assign(&["a", "b", "c"], Expression::Integer(1)),
573 Expression::assign(&["a", "b", "c"], Expression::Add(Box::new(current), num(2))),
574 ]
575 );
576 }
577
578 #[test]
579 fn test_parsed_rule_executes() {
580 let grl = r#"
581 rule CheckEligibility "Check if customer is eligible" salience 10 {
582 when
583 customer.age >= 18 && (customer.balance > 1000 || customer.vip == true)
584 then
585 customer.eligible = true;
586 message = "Customer is eligible for premium service";
587 Retract("CheckEligibility");
588 }
589 "#;
590 let mut engine = RuleEngine::new();
591 engine
592 .add_rule(GrlParser::new().parse_rule(grl).unwrap())
593 .unwrap();
594
595 let mut facts = HashMap::new();
596 facts.insert(
597 "customer".to_string(),
598 Fact::from_object(
599 "customer".to_string(),
600 HashMap::from([
601 ("age".to_string(), FactValue::Integer(30)),
602 ("balance".to_string(), FactValue::Integer(500)),
603 ("vip".to_string(), FactValue::Boolean(true)),
604 ]),
605 ),
606 );
607
608 let result = engine.execute(&mut facts).unwrap();
609 assert_eq!(result.rules_fired, vec!["CheckEligibility".to_string()]);
610 assert_eq!(
611 facts["customer"].get_field("eligible"),
612 Some(&FactValue::Boolean(true))
613 );
614 assert_eq!(
615 facts["message"].value,
616 FactValue::String("Customer is eligible for premium service".to_string())
617 );
618 }
619}