1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
//! A small expression evaluator for the expressions a workflow carries.
//!
//! `acts-expr` compiles a source string once into an [`Expr`] and evaluates it
//! against a [`Context`] as often as needed — the same compiled expression can
//! be evaluated from any number of threads with different contexts. It has no
//! dependencies, allocates one `Vec` per compiled expression (nodes reference
//! their children by index, not by pointer) and never touches a global.
//!
//! # The expression surface
//!
//! - literals: `12`, `1.5`, `1e3`, `'text'`, `"text"`, `true`, `false`, `null`
//! - arithmetic: `+ - * / %`, unary `-`, with `( )` to group
//! - comparison: `== != < <= > >=` (which do not chain, so `1 < 2 < 3` is a
//! parse error rather than a type error)
//! - logic: `&&`, `||`, unary `!`, both short-circuiting
//! - access: `a.b`, `a['b']`, `list[0]`, and calls `f(x)`, `a.f(x)`
//!
//! `==` compares numbers across `int`, `uint` and `float`, lists and objects
//! element-wise, and values of different kinds are not equal (so a workflow
//! that compares a missing value with a string is `false`, not an error).
//! Arithmetic requires numbers, `+` also concatenates two strings, and
//! `&& || !` require bools. Two ints stay an int (so `/` truncates and `%` is
//! the remainder) and an overflow is an error; a `uint` — what JSON gives a
//! host for a value above `i64::MAX` — stays exact against an int, and any
//! float either side makes the result a float.
//!
//! # Built-in methods
//!
//! A value carries a few methods of its own, so the common test needs nothing
//! injected:
//!
//! - a string: `length()`, `startsWith(p)`, `endsWith(p)`, `indexOf(p)`,
//! `contains(p)`, and `is_match(pattern)` — a regular expression, behind the
//! `regex` feature
//! - a list: `length()`, `contains(value)` (an element, compared by value)
//! - a map: `length()` (its entries)
//!
//! `length()` and `indexOf()` count characters, not bytes. These are found
//! last: the object's own method, then a function of that name in the context,
//! then the built-in — so a host that injects `length` defines what it means.
//!
//! # Injection
//!
//! Everything an expression reads is in the [`Context`]: scalars, lists and
//! objects ([`Value::Map`]) as values, and methods as [`Value::Function`]s.
//! A function is called as `f(x)`, or as a method with its receiver first —
//! `x.f()` — and a function stored *inside* an object is called that way
//! through the object, so an injected object carries its own methods.
//!
//! ```rust
//! use acts_expr::{Context, Expr, Value};
//!
//! let mut context = Context::new();
//! context
//! .set("count", 3)
//! .set("status", "ok")
//! .set("step1", Value::map([
//! ("value", Value::from(7)),
//! ("id", Value::from("step1")),
//! ]))
//! .set(
//! "upper",
//! Value::function(|args| match args {
//! [Value::Str(text)] => Ok(Value::from(text.to_uppercase())),
//! _ => Err(acts_expr::Error::Type { operation: "upper", got: "other" }),
//! }),
//! );
//!
//! let expr = Expr::compile("step1.value + count * 2 >= 13 && status != 'bad'")?;
//! assert_eq!(expr.eval(&context)?, Value::Bool(true));
//!
//! // A method: `upper` from the context, called with `status` as its receiver.
//! let name = Expr::compile("status.upper()")?;
//! assert_eq!(name.eval(&context)?, Value::from("OK"));
//! # Ok::<_, acts_expr::Error>(())
//! ```
//!
//! # Deliberately not in the surface
//!
//! The evaluator answers the expressions a workflow writes — conditions,
//! arithmetic on task data, string and object access — and nothing more. There
//! is no assignment, no statement, no ternary (`?:`), no collection literal
//! (`[...]`, `{...}`), no `in`, no comprehension, and no method beyond the
//! built-ins above — every other method is one the host injected. Each of the
//! absent forms is rejected with a message that says so.
pub use ;
pub use Context;
pub use ;
use Node;
/// A compiled expression.
///
/// Compiling is the only step that reads the source; evaluation is a walk over
/// the compiled nodes, so a host that evaluates the same expression per task —
/// a step condition, a parameter filled per process — compiles it once and
/// keeps the [`Expr`].
/// Compile and evaluate in one step, for a caller that has no reason to keep
/// the compiled expression.