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;
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}