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