The rule language
rust-runes reads GRL, the rule language of
Grule, following Grule’s grammar
(grulev3.g4).
Rules
rule Name "optional description" salience 10 {
when <condition>
then <statement>; <statement>; ...
}
- Name: unique across the engine;
Retractand errors refer to rules by name. - Description: optional, in quotes.
- Salience: optional priority, an integer (negative too), default 0. When several rules match, the highest salience fires first; see the execution model.
- when: an expression that must evaluate to a boolean.
- then: statements, each ending with
;. The block may be empty.
Keywords (rule, salience, when, then, true, false, nil) are
case-insensitive, and identifiers may use Unicode letters. Comments use // and /* */.
/* Discounts for loyal customers */
rule LoyaltyDiscount "Loyal customers get 10% off" salience 20 {
when
Customer.Years >= 5 && // five years or more
!Order.Discounted
then
Order.Total *= 0.9;
Order.Discounted = true;
}
Values
| Kind | Literals |
|---|---|
| Strings | "..." or '...', with Go’s escapes: \n, \t, \", \\, \x41, \101, é, \U0001F600 |
| Integers | 42, -7, 0x2A (hex), 052 (octal) |
| Floats | 1.5, .5, 1e3, 2.5E-3, 0x1p-2 |
| Booleans | true, false |
| Nil | nil |
Times have no literal; they come from Now(), MakeTime(...) or facts. Facts may also
hold objects and arrays.
Reading facts
Factreads a fact;Fact.Field.Innerreads nested fields.Fact.Items[0]indexes arrays, andFact.Codes["key"]orFact.Codes[404]maps.Fact.Method(args)calls a method, andFunction(args)a function. Calls can be chained:Fact.Name.ToUpper().HasPrefix("A").
Reading a field or element that doesn’t exist is an error. In a condition, errors make the condition false by default (see failed conditions).
Operators
From lowest to highest precedence:
| Operators | Notes |
|---|---|
|| | Logical or, short-circuits |
&& | Logical and, short-circuits |
== != < <= > >= | See comparisons below |
+ - & | | + also joins strings; & and | are bitwise, on integers |
* / % | % takes integers |
! - | Unary not and negation |
.Field [index] .Method() |
Parentheses group as usual. Unlike in many languages, all comparisons share one level and
& and | bind tighter than comparisons, as in Grule.
Arithmetic. Two integers give an integer and anything with a float gives a float,
except /, which always gives a float: 7 / 2 == 3.5. Use Floor or assign to an
integer field to truncate. Division by zero is an error.
Strings. + joins two strings, or a string with a number, boolean or time
("Total: " + 9.5 is "Total: 9.500000", formatting floats as Go’s %f does).
Comparisons. Numbers compare with numbers (integers and floats by value), strings
with strings and times with times; all three can be ordered. A string or boolean is never
equal to a value of another type, nil equals only nil, and comparing a number with a
non-number is an error.
Booleans are strict: &&, || and ! take booleans only, and a condition must be a
boolean. There is no truthiness.
These rules are all checked by tests:
use rust_runes::{Fact, FactValue, RuleEngine};
use std::collections::HashMap;
let mut engine = RuleEngine::new();
engine.add_rules_from_str(r#"
rule Check {
when !R.Done
then
R.Div = 7 / 2;
R.Mod = 7 % 2;
R.Mixed = 2 * 1.5;
R.Joined = "n=" + 3 + ", ok=" + true;
R.Precedence = 1 + 2 * 3 == 7 && 1 | 2 == 3;
R.Done = true;
}
"#)?;
let mut facts = HashMap::from([(
"R".to_string(),
Fact::from_object("R".to_string(), HashMap::from([("Done".to_string(), false.into())])),
)]);
engine.execute(&mut facts)?;
let r = &facts["R"];
assert_eq!(r.get_field("Div"), Some(&FactValue::Float(3.5)));
assert_eq!(r.get_field("Mod"), Some(&FactValue::Integer(1)));
assert_eq!(r.get_field("Mixed"), Some(&FactValue::Float(3.0)));
assert_eq!(r.get_field("Joined"), Some(&FactValue::from("n=3, ok=true")));
assert_eq!(r.get_field("Precedence"), Some(&FactValue::Boolean(true)));
Ok::<(), Box<dyn std::error::Error>>(())
Statements
The then block holds statements:
- Assignments to facts, fields and elements:
=,+=,-=,*=,/=.Order.Lines[0].Price *= 2;changes the nested element in place. - Function calls:
Log("checked");,Retract("ThisRule");,Complete();. - Method calls:
Order.Items.Append("gift");. A method called as a statement on a fact path changes the stored fact.
Assignments keep the type of a field that already has a value; see Facts.
rule Restock "Reorder low stock" salience 5 {
when
Item.Stock < Item.ReorderLevel && !Item.Ordered
then
Item.Order.Quantity = Item.ReorderLevel * 2 - Item.Stock;
Item.History.Append("reordered " + Item.Order.Quantity);
Item.Ordered = true;
Log("Reordering " + Item.Name);
}