Skip to main content

jetro_core/
lib.rs

1//! Jetro core — parser, compiler, and VM for the Jetro JSON query language.
2//!
3//! This crate is storage-free.  For the embedded B+ tree store, named
4//! expressions, graph queries, joins, and [`Session`](../jetrodb/struct.Session.html),
5//! depend on the sibling `jetrodb` crate, or pull the umbrella `jetro` crate
6//! which re-exports both.
7//!
8//! # Architecture
9//!
10//! ```text
11//!   source text
12//!       │
13//!       ▼
14//!   parser.rs  ── pest grammar → [ast::Expr] tree
15//!       │
16//!       ▼
17//!   vm::Compiler::emit      ── Expr → Vec<Opcode>
18//!       │
19//!       ▼
20//!   vm::Compiler::optimize  ── peephole passes (root_chain, filter/count,
21//!                              filter/map fusion, strength reduction,
22//!                              constant folding, nullness-driven specialisation)
23//!       │
24//!       ▼
25//!   Compiler::compile runs:
26//!       • AST rewrite: reorder_and_operands        (selectivity-based)
27//!       • post-pass  : analysis::dedup_subprograms (CSE on Arc<Program>)
28//!       │
29//!       ▼
30//!   vm::VM::execute          ── stack machine over &serde_json::Value
31//!                                with thread-local pointer cache.
32//! ```
33//!
34//! # Quick start
35//!
36//! ```rust
37//! use jetro_core::Jetro;
38//! use serde_json::json;
39//!
40//! let j = Jetro::new(json!({
41//!     "store": {
42//!         "books": [
43//!             {"title": "Dune",        "price": 12.99},
44//!             {"title": "Foundation",  "price":  9.99}
45//!         ]
46//!     }
47//! }));
48//!
49//! let count = j.collect("$.store.books.len()").unwrap();
50//! assert_eq!(count, json!(2));
51//! ```
52
53pub mod ast;
54pub mod engine;
55pub mod eval;
56pub mod expr;
57pub mod graph;
58pub mod parser;
59pub mod vm;
60pub mod analysis;
61pub mod schema;
62pub mod plan;
63pub mod cfg;
64pub mod ssa;
65
66#[cfg(test)]
67mod tests;
68#[cfg(test)]
69mod examples;
70
71use std::cell::RefCell;
72use std::sync::Arc;
73use serde_json::Value;
74
75pub use engine::Engine;
76pub use eval::EvalError;
77pub use eval::{Method, MethodRegistry};
78pub use expr::Expr;
79pub use graph::Graph;
80pub use parser::ParseError;
81pub use vm::{VM, Compiler, Program};
82
83/// Trait implemented by `#[derive(JetroSchema)]` — pairs a type with a
84/// fixed set of named expressions.
85///
86/// ```ignore
87/// use jetro_core::JetroSchema;
88///
89/// #[derive(JetroSchema)]
90/// #[expr(titles = "$.books.map(title)")]
91/// #[expr(count  = "$.books.len()")]
92/// struct BookView;
93///
94/// for (name, src) in BookView::exprs() { /* register on a bucket */ }
95/// ```
96pub trait JetroSchema {
97    const EXPRS: &'static [(&'static str, &'static str)];
98    fn exprs() -> &'static [(&'static str, &'static str)];
99    fn names() -> &'static [&'static str];
100}
101
102// ── Error ─────────────────────────────────────────────────────────────────────
103
104/// Engine-side error type.  Either a parse failure or an evaluation failure.
105///
106/// Storage and IO errors are carried by `jetrodb::DbError` in the sibling
107/// crate.  The umbrella `jetro` crate unifies both into a flatter
108/// `jetro::Error` for callers that want a single match arm per variant.
109#[derive(Debug)]
110pub enum Error {
111    Parse(ParseError),
112    Eval(EvalError),
113}
114
115pub type Result<T> = std::result::Result<T, Error>;
116
117impl std::fmt::Display for Error {
118    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
119        match self {
120            Error::Parse(e) => write!(f, "{}", e),
121            Error::Eval(e)  => write!(f, "{}", e),
122        }
123    }
124}
125impl std::error::Error for Error {
126    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
127        match self {
128            Error::Parse(e) => Some(e),
129            Error::Eval(_)  => None,
130        }
131    }
132}
133
134impl From<ParseError> for Error { fn from(e: ParseError) -> Self { Error::Parse(e) } }
135impl From<EvalError>  for Error { fn from(e: EvalError)  -> Self { Error::Eval(e)  } }
136
137/// Evaluate a Jetro expression against a JSON value.
138pub fn query(expr: &str, doc: &Value) -> Result<Value> {
139    let ast = parser::parse(expr)?;
140    Ok(eval::evaluate(&ast, doc)?)
141}
142
143/// Evaluate a Jetro expression with a custom method registry.
144pub fn query_with(expr: &str, doc: &Value, registry: Arc<MethodRegistry>) -> Result<Value> {
145    let ast = parser::parse(expr)?;
146    Ok(eval::evaluate_with(&ast, doc, registry)?)
147}
148
149// ── Jetro ─────────────────────────────────────────────────────────────────────
150
151thread_local! {
152    static THREAD_VM: RefCell<VM> = RefCell::new(VM::new());
153}
154
155/// Primary entry point for evaluating Jetro expressions.
156///
157/// Holds a JSON document and evaluates expressions against it.  Internally
158/// delegates to a thread-local [`VM`] so the compile cache and resolution
159/// cache accumulate over the lifetime of the thread.
160pub struct Jetro {
161    document: Value,
162}
163
164impl Jetro {
165    pub fn new(document: Value) -> Self {
166        Self { document }
167    }
168
169    /// Evaluate `expr` against the document.
170    pub fn collect<S: AsRef<str>>(&self, expr: S) -> std::result::Result<Value, EvalError> {
171        let expr = expr.as_ref();
172        THREAD_VM.with(|cell| match cell.try_borrow_mut() {
173            Ok(mut vm) => vm.run_str(expr, &self.document),
174            Err(_)     => VM::new().run_str(expr, &self.document),
175        })
176    }
177}
178
179impl From<Value> for Jetro {
180    fn from(v: Value) -> Self {
181        Self::new(v)
182    }
183}