Skip to main content

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}