jlens_core/lib.rs
1//! # jlens-core
2//!
3//! The JSONata language engine behind jlens: lexer, parser, evaluator, function library and
4//! the evaluation audit subsystem.
5//!
6//! Compile an expression once, then evaluate it against any number of JSON documents:
7//!
8//! ```
9//! use jlens_core::{Bindings, Expression};
10//! use serde_json::json;
11//!
12//! let expr = Expression::compile("$sum(items[type = $kind].(price * qty))").unwrap();
13//! let input = json!({"items": [
14//! {"type": "a", "price": 2.5, "qty": 4},
15//! {"type": "b", "price": 1.0, "qty": 1},
16//! ]});
17//! let bindings = Bindings::new().var("kind", "a");
18//! assert_eq!(expr.evaluate(Some(&input), &bindings).unwrap(), Some(json!(10)));
19//!
20//! let err = Expression::compile("Account.Order[").unwrap_err();
21//! assert_eq!(err.code(), "S0203");
22//! ```
23//!
24//! For editors, [`parse_recover`] keeps going after syntax errors and returns a partial tree
25//! together with [`Diagnostic`]s:
26//!
27//! ```
28//! let ast = jlens_core::parse_recover("Account.");
29//! assert!(ast.has_errors());
30//! assert_eq!(ast.diagnostics[0].code, "S0207");
31//! ```
32//!
33//! Parts of this crate are ported from [jsonata-js](https://github.com/jsonata-js/jsonata)
34//! (MIT license, reproduced in `LICENSE`); the ported modules say so in their documentation.
35
36pub mod annotation;
37pub mod ast;
38pub mod audit;
39mod datetime;
40pub mod debug;
41mod document;
42pub mod error;
43mod eval;
44pub mod functions;
45mod host;
46pub mod input;
47mod json;
48mod lexer;
49mod parser;
50mod process;
51mod regex;
52pub mod signature;
53pub mod stack;
54mod value;
55
56pub use annotation::Annotation;
57pub use ast::{Ast, Node, NodeKind, Span};
58pub use document::Document;
59pub use error::{Diagnostic, Error, Result, Severity};
60pub use functions::{Category, FunctionInfo, catalog as function_catalog};
61pub use host::{Callable, HostCall, HostFunction};
62pub use json::format_json;
63
64use std::sync::Arc;
65use std::sync::atomic::{AtomicBool, Ordering};
66use std::time::Duration;
67
68use indexmap::IndexMap;
69use serde_json::Value as JsonValue;
70
71/// Parses an expression, failing on the first syntax error.
72///
73/// Warnings (such as misplaced annotations) are returned in [`Ast::diagnostics`].
74pub fn parse(source: &str) -> Result<Ast> {
75 let (raw, errors, warnings) = parser::parse(source, false);
76 debug_assert!(errors.is_empty());
77 let mut processor = process::Processor::new(source, false);
78 let root = processor.process_root(raw?)?;
79 Ok(Ast {
80 root,
81 slots: processor.slots,
82 diagnostics: warnings,
83 source: source.to_owned(),
84 })
85}
86
87/// Parses an expression, recovering from syntax errors.
88///
89/// The returned tree contains [`NodeKind::Error`] nodes where the input could not be parsed,
90/// and [`Ast::diagnostics`] lists every error and warning, ordered by position. This is the
91/// entry point for editors and language tooling.
92pub fn parse_recover(source: &str) -> Ast {
93 let (raw, parse_errors, warnings) = parser::parse(source, true);
94 let mut processor = process::Processor::new(source, true);
95 let root = raw
96 .and_then(|raw| processor.process_root(raw))
97 .unwrap_or_else(|err| {
98 let span = err.span().unwrap_or_default();
99 processor.errors.push(err.clone());
100 Node::new(
101 NodeKind::Error {
102 error: Box::new(err),
103 },
104 span,
105 span.end,
106 )
107 });
108 let mut diagnostics: Vec<Diagnostic> = parse_errors
109 .iter()
110 .chain(&processor.errors)
111 .map(Diagnostic::from)
112 .collect();
113 diagnostics.extend(warnings);
114 diagnostics.sort_by_key(|d| d.span.start);
115 Ast {
116 root,
117 slots: processor.slots,
118 diagnostics,
119 source: source.to_owned(),
120 }
121}
122
123/// A compiled JSONata expression.
124///
125/// Compile once with [`Expression::compile`] and evaluate many times. A compiled expression is
126/// immutable and can be shared between threads.
127#[derive(Debug, Clone)]
128pub struct Expression {
129 ast: Arc<Ast>,
130}
131
132impl Expression {
133 /// Parses and compiles a JSONata expression.
134 pub fn compile(source: &str) -> Result<Self> {
135 Ok(Expression {
136 ast: Arc::new(parse(source)?),
137 })
138 }
139
140 /// Returns the source text this expression was compiled from.
141 pub fn source(&self) -> &str {
142 &self.ast.source
143 }
144
145 /// The syntax tree of the expression.
146 pub fn ast(&self) -> &Ast {
147 &self.ast
148 }
149
150 /// Evaluates the expression against `input` with default [`Options`].
151 ///
152 /// `None` as input means *undefined* (no input document). A result of `Ok(None)` means
153 /// the expression evaluated to *undefined*.
154 pub fn evaluate(
155 &self,
156 input: Option<&JsonValue>,
157 bindings: &Bindings,
158 ) -> Result<Option<JsonValue>> {
159 self.evaluate_with(input, bindings, &Options::default())
160 }
161
162 /// Evaluates the expression with explicit [`Options`] (limits and guardrails).
163 pub fn evaluate_with(
164 &self,
165 input: Option<&JsonValue>,
166 bindings: &Bindings,
167 options: &Options,
168 ) -> Result<Option<JsonValue>> {
169 let shared = value::Shared::new();
170 eval::evaluate(
171 &self.ast,
172 input.map_or(value::Value::Undefined, |json| {
173 value::Value::lazy(json, &shared)
174 }),
175 bindings
176 .iter()
177 .map(|(name, json)| (name, value::Value::lazy(json, &shared))),
178 bindings,
179 limits(options),
180 None,
181 None,
182 )
183 }
184
185 /// Evaluates the expression against an input read from a [`Source`](input::Source), one
186 /// level at a time, as the evaluation reaches it. Use it for an input that is not a
187 /// `serde_json::Value`, without converting it first.
188 ///
189 /// `input` writes the input document as the only value of its level, or nothing for
190 /// *undefined*. A level that the source fails to read stops the evaluation with `U1004`.
191 pub fn evaluate_source<'a>(
192 &self,
193 input: &dyn input::Source<'a>,
194 bindings: &'a Bindings,
195 options: &Options,
196 ) -> Result<Option<JsonValue>> {
197 let shared = value::Shared::new();
198 eval::evaluate(
199 &self.ast,
200 value::Value::from_source(input, &shared)?,
201 bindings
202 .iter()
203 .map(|(name, json)| (name, value::Value::lazy(json, &shared))),
204 bindings,
205 limits(options),
206 None,
207 Some(shared.clone()),
208 )
209 }
210
211 /// Evaluates the expression against a [`Document`] converted beforehand. Use it to
212 /// evaluate several expressions, or the same one several times, against one large document
213 /// without converting it each time.
214 pub fn evaluate_document(
215 &self,
216 document: &Document,
217 bindings: &Bindings,
218 options: &Options,
219 ) -> Result<Option<JsonValue>> {
220 eval::evaluate(
221 &self.ast,
222 document.value().clone(),
223 bindings
224 .iter()
225 .map(|(name, json)| (name, value::Value::from_json(json))),
226 bindings,
227 limits(options),
228 None,
229 None,
230 )
231 }
232
233 /// Evaluates the expression, reporting every evaluation event to `tracer`.
234 ///
235 /// See the [`audit`] module for the events and the tracers jlens provides.
236 pub fn evaluate_traced(
237 &self,
238 input: Option<&JsonValue>,
239 bindings: &Bindings,
240 options: &Options,
241 tracer: &mut dyn audit::Tracer,
242 ) -> Result<Option<JsonValue>> {
243 let shared = value::Shared::new();
244 eval::evaluate(
245 &self.ast,
246 input.map_or(value::Value::Undefined, |json| {
247 value::Value::lazy(json, &shared)
248 }),
249 bindings
250 .iter()
251 .map(|(name, json)| (name, value::Value::lazy(json, &shared))),
252 bindings,
253 limits(options),
254 Some(tracer),
255 None,
256 )
257 }
258
259 /// Evaluates the expression and records an [`audit::AuditTree`] of how the result was
260 /// reached. The audit is returned even when the evaluation fails.
261 ///
262 /// ```
263 /// use jlens_core::{Bindings, Expression, Options};
264 /// use jlens_core::audit::{AuditOptions, OutcomeKind};
265 ///
266 /// let expr = Expression::compile("$x > 10").unwrap();
267 /// let bindings = Bindings::new().var("x", 3);
268 /// let (result, audit) =
269 /// expr.evaluate_audited(None, &bindings, &Options::default(), &AuditOptions::decisions());
270 /// assert_eq!(result.unwrap(), Some(serde_json::json!(false)));
271 /// let root = audit.root.unwrap();
272 /// assert_eq!(root.outcome, OutcomeKind::False);
273 /// assert_eq!(root.operands, Some([Some(3.into()), Some(10.into())]));
274 /// ```
275 pub fn evaluate_audited(
276 &self,
277 input: Option<&JsonValue>,
278 bindings: &Bindings,
279 options: &Options,
280 audit: &audit::AuditOptions,
281 ) -> (Result<Option<JsonValue>>, audit::AuditTree) {
282 let mut collector = audit::AuditCollector::new(*audit, input);
283 let result = self.evaluate_traced(input, bindings, options, &mut collector);
284 (result, collector.finish())
285 }
286}
287
288fn limits(options: &Options) -> eval::Limits {
289 eval::Limits {
290 max_depth: options.max_depth.unwrap_or(eval::DEFAULT_MAX_DEPTH),
291 timeout: options.timeout,
292 max_string_length: options
293 .max_string_length
294 .unwrap_or(eval::DEFAULT_MAX_STRING_LENGTH),
295 max_array_length: options
296 .max_array_length
297 .unwrap_or(eval::DEFAULT_MAX_ARRAY_LENGTH),
298 max_result_size: options
299 .max_result_size
300 .unwrap_or(eval::DEFAULT_MAX_RESULT_SIZE),
301 cancel_token: options.cancel_token.clone(),
302 now: options.now,
303 seed: options.seed,
304 }
305}
306
307/// Variables and host functions made available to an expression as `$name`.
308///
309/// A name holds either a value or a [`HostFunction`]: binding one replaces the other. Bindings
310/// take precedence over the built-in functions of the same name, as in jsonata-js.
311#[derive(Debug, Clone, Default)]
312pub struct Bindings {
313 vars: IndexMap<String, JsonValue>,
314 functions: IndexMap<String, HostFunction>,
315}
316
317impl Bindings {
318 /// Creates an empty set of bindings.
319 pub fn new() -> Self {
320 Self::default()
321 }
322
323 /// Adds (or replaces) a variable binding. The name is given without the leading `$`.
324 pub fn var(mut self, name: impl Into<String>, value: impl Into<JsonValue>) -> Self {
325 let name = name.into();
326 self.functions.shift_remove(&name);
327 self.vars.insert(name, value.into());
328 self
329 }
330
331 /// Adds (or replaces) a host function, called as `$name(...)`. The name is given without
332 /// the leading `$`.
333 pub fn function(mut self, name: impl Into<String>, function: HostFunction) -> Self {
334 let name = name.into();
335 self.vars.shift_remove(&name);
336 self.functions.insert(name, function);
337 self
338 }
339
340 /// Looks up a variable binding by name (without the leading `$`).
341 pub fn get(&self, name: &str) -> Option<&JsonValue> {
342 self.vars.get(name)
343 }
344
345 /// Looks up a host function by name (without the leading `$`).
346 pub fn get_function(&self, name: &str) -> Option<&HostFunction> {
347 self.functions.get(name)
348 }
349
350 /// Iterates over the variable bindings in insertion order.
351 pub fn iter(&self) -> impl Iterator<Item = (&str, &JsonValue)> {
352 self.vars.iter().map(|(k, v)| (k.as_str(), v))
353 }
354
355 /// Iterates over the host functions in insertion order.
356 pub fn functions(&self) -> impl Iterator<Item = (&str, &HostFunction)> {
357 self.functions.iter().map(|(k, f)| (k.as_str(), f))
358 }
359}
360
361impl FromIterator<(String, JsonValue)> for Bindings {
362 fn from_iter<T: IntoIterator<Item = (String, JsonValue)>>(iter: T) -> Self {
363 Bindings {
364 vars: iter.into_iter().collect(),
365 functions: IndexMap::new(),
366 }
367 }
368}
369
370/// Evaluation guardrails, and the clock and seed that make an evaluation reproducible.
371///
372/// The struct is `#[non_exhaustive]`, so new options can be added without breaking callers:
373/// start from [`Options::new`] (or `Options::default()`) and chain the builder methods.
374///
375/// ```
376/// use jlens_core::Options;
377/// use std::time::Duration;
378///
379/// let options = Options::new()
380/// .timeout(Duration::from_millis(100))
381/// .max_depth(500);
382/// assert_eq!(options.max_depth, Some(500));
383/// ```
384#[derive(Debug, Clone, Default)]
385#[non_exhaustive]
386pub struct Options {
387 /// Maximum wall-clock time for one evaluation. Exceeding it raises `D1012`.
388 pub timeout: Option<Duration>,
389 /// Maximum evaluation depth (default 50,000; 500 on WebAssembly). Exceeding it raises
390 /// `D1011`.
391 pub max_depth: Option<usize>,
392 /// The evaluation's timestamp, in milliseconds since the epoch, for `$now()` and
393 /// `$millis()`. Default: the system clock, read once, the first time the evaluation needs
394 /// it.
395 pub now: Option<i64>,
396 /// Seed for `$random()` and `$shuffle()`. Default: one taken from the system clock.
397 pub seed: Option<u64>,
398 /// Stops the evaluation with `U1003` once the token is cancelled, from any thread.
399 pub cancel_token: Option<CancelToken>,
400 /// Maximum length, in bytes of UTF-8, of a string that the evaluation builds (default
401 /// 268,435,456: 256 MiB). Exceeding it raises `U1001`.
402 pub max_string_length: Option<usize>,
403 /// Maximum number of items in an array or sequence that the evaluation builds (default
404 /// 16,777,216). Exceeding it raises `U1001`.
405 pub max_array_length: Option<usize>,
406 /// Maximum size of the result as compact JSON text, in bytes (default 268,435,456:
407 /// 256 MiB), which also limits the arguments of a host function. A value that appears
408 /// several times counts each time, as it does in the text. Exceeding it raises `U1001`.
409 pub max_result_size: Option<usize>,
410}
411
412impl Options {
413 /// Creates options with no timeout, the default depth and size limits, the system clock
414 /// and an unseeded generator.
415 pub fn new() -> Self {
416 Self::default()
417 }
418
419 /// Fixes the evaluation's timestamp (`$now()`, `$millis()`), in milliseconds since the
420 /// epoch.
421 pub fn now(mut self, millis: i64) -> Self {
422 self.now = Some(millis);
423 self
424 }
425
426 /// Seeds `$random()` and `$shuffle()`: the same seed gives the same sequence.
427 pub fn seed(mut self, seed: u64) -> Self {
428 self.seed = Some(seed);
429 self
430 }
431
432 /// Sets the maximum wall-clock time for one evaluation (`D1012` when exceeded).
433 pub fn timeout(mut self, timeout: Duration) -> Self {
434 self.timeout = Some(timeout);
435 self
436 }
437
438 /// Sets the maximum evaluation depth (`D1011` when exceeded).
439 pub fn max_depth(mut self, max_depth: usize) -> Self {
440 self.max_depth = Some(max_depth);
441 self
442 }
443
444 /// Sets the maximum length, in bytes, of a string the evaluation builds (`U1001` when
445 /// exceeded).
446 pub fn max_string_length(mut self, bytes: usize) -> Self {
447 self.max_string_length = Some(bytes);
448 self
449 }
450
451 /// Sets the maximum number of items of an array the evaluation builds (`U1001` when
452 /// exceeded).
453 pub fn max_array_length(mut self, items: usize) -> Self {
454 self.max_array_length = Some(items);
455 self
456 }
457
458 /// Sets the maximum size, in bytes of compact JSON text, of the result and of the
459 /// arguments of a host function (`U1001` when exceeded).
460 pub fn max_result_size(mut self, bytes: usize) -> Self {
461 self.max_result_size = Some(bytes);
462 self
463 }
464
465 /// Lets `token` stop the evaluation (`U1003` once it is cancelled).
466 pub fn cancel_token(mut self, token: CancelToken) -> Self {
467 self.cancel_token = Some(token);
468 self
469 }
470}
471
472/// A handle that stops evaluations from another thread.
473///
474/// Clones share one flag: pass a clone in [`Options::cancel_token`], keep another, and call
475/// [`CancelToken::cancel`] to stop every evaluation that uses it. The evaluation checks the
476/// flag between steps, as it checks the timeout, and fails with `U1003`; a host function
477/// runs to completion first. Cancelling cannot be undone: an evaluation started with a
478/// cancelled token fails at once, so use a new token for each job.
479///
480/// ```
481/// use jlens_core::{Bindings, CancelToken, Expression, Options};
482///
483/// let token = CancelToken::new();
484/// let options = Options::new().cancel_token(token.clone());
485/// token.cancel();
486/// let err = Expression::compile("1 + 1")
487/// .unwrap()
488/// .evaluate_with(None, &Bindings::new(), &options)
489/// .unwrap_err();
490/// assert_eq!(err.code(), "U1003");
491/// ```
492#[derive(Debug, Clone, Default)]
493pub struct CancelToken(Arc<AtomicBool>);
494
495impl CancelToken {
496 /// Creates a token that is not cancelled.
497 pub fn new() -> Self {
498 Self::default()
499 }
500
501 /// Cancels the token: evaluations that use it stop with `U1003`.
502 pub fn cancel(&self) {
503 self.0.store(true, Ordering::Relaxed);
504 }
505
506 /// Returns whether [`CancelToken::cancel`] has been called on this token or a clone.
507 pub fn is_cancelled(&self) -> bool {
508 self.0.load(Ordering::Relaxed)
509 }
510}