pub mod ast;
mod compiler;
mod datetime;
pub mod evaluator;
pub mod functions;
pub mod parser;
mod signature;
pub mod value;
mod vm;
#[cfg(feature = "bench")]
pub mod _bench {
use crate::ast::AstNode;
pub use crate::evaluator::EvaluatorError;
use crate::value::JValue;
pub struct CompiledProgram(crate::vm::BytecodeProgram);
pub fn compile(ast: &AstNode) -> Option<CompiledProgram> {
crate::evaluator::try_compile_expr(ast)
.map(|ce| CompiledProgram(crate::compiler::BytecodeCompiler::compile(&ce)))
}
pub fn run(prog: &CompiledProgram, data: &JValue) -> Result<JValue, EvaluatorError> {
crate::vm::Vm::new(&prog.0).run(data, None)
}
}
const JSONATA_REFERENCE_VERSION: &str = "2.1.0";
#[cfg(feature = "python")]
use crate::value::JValue;
#[cfg(feature = "python")]
use indexmap::IndexMap;
#[cfg(feature = "python")]
use pyo3::exceptions::{PyTypeError, PyValueError};
#[cfg(feature = "python")]
use pyo3::prelude::*;
#[cfg(feature = "python")]
use pyo3::types::{PyBool, PyDict, PyFloat, PyInt, PyList, PyString};
#[cfg(feature = "python")]
#[pyclass(unsendable)]
struct JsonataData {
data: JValue,
}
#[cfg(feature = "python")]
#[pymethods]
impl JsonataData {
#[new]
fn new(py: Python, data: Py<PyAny>) -> PyResult<Self> {
let jvalue = python_to_json(py, &data)?;
Ok(JsonataData { data: jvalue })
}
#[staticmethod]
fn from_json(json_str: &str) -> PyResult<Self> {
let data = JValue::from_json_str(json_str)
.map_err(|e| PyValueError::new_err(format!("Invalid JSON: {}", e)))?;
Ok(JsonataData { data })
}
}
#[cfg(feature = "python")]
#[pyclass(unsendable)]
struct JsonataExpression {
ast: ast::AstNode,
bytecode: std::cell::OnceCell<Option<vm::BytecodeProgram>>,
}
#[cfg(feature = "python")]
impl JsonataExpression {
fn run_eval(&self, py: Python, data: &JValue, bindings: Option<Py<PyAny>>) -> PyResult<JValue> {
if bindings.is_none() {
let bytecode = self.bytecode.get_or_init(|| {
evaluator::try_compile_expr(&self.ast)
.map(|ce| compiler::BytecodeCompiler::compile(&ce))
});
if let Some(bc) = bytecode {
vm::Vm::new(bc)
.run(data, None)
.map_err(evaluator_error_to_py)
} else {
let mut ev = evaluator::Evaluator::new();
ev.evaluate(&self.ast, data).map_err(evaluator_error_to_py)
}
} else {
let mut ev = create_evaluator(py, bindings)?;
ev.evaluate(&self.ast, data).map_err(evaluator_error_to_py)
}
}
}
#[cfg(feature = "python")]
#[pymethods]
impl JsonataExpression {
#[pyo3(signature = (data, bindings=None))]
fn evaluate(
&self,
py: Python,
data: Py<PyAny>,
bindings: Option<Py<PyAny>>,
) -> PyResult<Py<PyAny>> {
let json_data = python_to_json(py, &data)?;
json_to_python(py, &self.run_eval(py, &json_data, bindings)?)
}
#[pyo3(signature = (data, bindings=None))]
fn evaluate_with_data(
&self,
py: Python,
data: &JsonataData,
bindings: Option<Py<PyAny>>,
) -> PyResult<Py<PyAny>> {
json_to_python(py, &self.run_eval(py, &data.data, bindings)?)
}
#[pyo3(signature = (data, bindings=None))]
fn evaluate_data_to_json(
&self,
py: Python,
data: &JsonataData,
bindings: Option<Py<PyAny>>,
) -> PyResult<String> {
self.run_eval(py, &data.data, bindings)?
.to_json_string()
.map_err(|e| PyValueError::new_err(format!("Failed to serialize result: {}", e)))
}
#[pyo3(signature = (json_str, bindings=None))]
fn evaluate_json(
&self,
py: Python,
json_str: &str,
bindings: Option<Py<PyAny>>,
) -> PyResult<String> {
let json_data = JValue::from_json_str(json_str)
.map_err(|e| PyValueError::new_err(format!("Invalid JSON: {}", e)))?;
self.run_eval(py, &json_data, bindings)?
.to_json_string()
.map_err(|e| PyValueError::new_err(format!("Failed to serialize result: {}", e)))
}
}
#[cfg(feature = "python")]
#[pyfunction]
fn compile(expression: &str) -> PyResult<JsonataExpression> {
let ast = parser::parse(expression)
.map_err(|e| PyValueError::new_err(format!("Parse error: {}", e)))?;
Ok(JsonataExpression {
ast,
bytecode: std::cell::OnceCell::new(),
})
}
#[cfg(feature = "python")]
#[pyfunction]
#[pyo3(signature = (expression, data, bindings=None))]
fn evaluate(
py: Python,
expression: &str,
data: Py<PyAny>,
bindings: Option<Py<PyAny>>,
) -> PyResult<Py<PyAny>> {
let expr = compile(expression)?;
expr.evaluate(py, data, bindings)
}
#[cfg(feature = "python")]
fn python_to_json(py: Python, obj: &Py<PyAny>) -> PyResult<JValue> {
python_to_json_bound(obj.bind(py))
}
#[cfg(feature = "python")]
fn python_to_json_bound(obj: &Bound<'_, PyAny>) -> PyResult<JValue> {
if obj.is_none() {
return Ok(JValue::Null);
}
if obj.is_instance_of::<PyBool>() {
return Ok(JValue::Bool(obj.extract::<bool>()?));
}
if obj.is_instance_of::<PyInt>() {
return Ok(JValue::Number(obj.extract::<i64>()? as f64));
}
if obj.is_instance_of::<PyFloat>() {
return Ok(JValue::Number(obj.extract::<f64>()?));
}
if obj.is_instance_of::<PyString>() {
return Ok(JValue::string(obj.extract::<String>()?));
}
if let Ok(list) = obj.cast::<PyList>() {
let mut result = Vec::with_capacity(list.len());
for item in list.iter() {
result.push(python_to_json_bound(&item)?);
}
return Ok(JValue::array(result));
}
if let Ok(dict) = obj.cast::<PyDict>() {
let mut result = IndexMap::with_capacity(dict.len());
for (key, value) in dict.iter() {
let key_str = key.extract::<String>()?;
result.insert(key_str, python_to_json_bound(&value)?);
}
return Ok(JValue::object(result));
}
if let Ok(b) = obj.extract::<bool>() {
return Ok(JValue::Bool(b));
}
if let Ok(i) = obj.extract::<i64>() {
return Ok(JValue::Number(i as f64));
}
if let Ok(f) = obj.extract::<f64>() {
return Ok(JValue::Number(f));
}
if let Ok(s) = obj.extract::<String>() {
return Ok(JValue::string(s));
}
Err(PyTypeError::new_err(format!(
"Cannot convert Python object to JSON: {}",
obj.get_type().name()?
)))
}
#[cfg(feature = "python")]
fn json_to_python(py: Python, value: &JValue) -> PyResult<Py<PyAny>> {
match value {
JValue::Null | JValue::Undefined => Ok(py.None()),
JValue::Bool(b) => Ok(b.into_pyobject(py).unwrap().to_owned().into_any().unbind()),
JValue::Number(n) => {
if n.fract() == 0.0 && n.is_finite() && *n >= i64::MIN as f64 && *n <= i64::MAX as f64 {
Ok((*n as i64).into_pyobject(py).unwrap().into_any().unbind())
} else {
Ok(n.into_pyobject(py).unwrap().into_any().unbind())
}
}
JValue::String(s) => Ok((&**s).into_pyobject(py).unwrap().into_any().unbind()),
JValue::Array(arr) => {
let all_objects =
arr.len() >= 2 && arr.iter().all(|item| matches!(item, JValue::Object(_)));
if all_objects {
let first_obj = match arr.first() {
Some(JValue::Object(obj)) => obj,
_ => unreachable!("all_objects guard ensures first element is an object"),
};
let interned_keys: Vec<(&str, Py<PyString>)> = first_obj
.keys()
.map(|k| (k.as_str(), PyString::new(py, k).unbind()))
.collect();
let items: Vec<Py<PyAny>> = arr
.iter()
.map(|item| {
let obj = match item {
JValue::Object(obj) => obj,
_ => unreachable!(),
};
let dict = PyDict::new(py);
for (key_str, py_key) in &interned_keys {
if let Some(value) = obj.get(*key_str) {
dict.set_item(py_key.bind(py), json_to_python(py, value)?)?;
}
}
for (key, value) in obj.iter() {
if !first_obj.contains_key(key) {
dict.set_item(key, json_to_python(py, value)?)?;
}
}
Ok(dict.unbind().into())
})
.collect::<PyResult<Vec<_>>>()?;
return Ok(PyList::new(py, &items)?.unbind().into());
}
let items: Vec<Py<PyAny>> = arr
.iter()
.map(|item| json_to_python(py, item))
.collect::<PyResult<Vec<_>>>()?;
Ok(PyList::new(py, &items)?.unbind().into())
}
JValue::Object(obj) => {
let dict = PyDict::new(py);
for (key, value) in obj.iter() {
dict.set_item(key, json_to_python(py, value)?)?;
}
Ok(dict.unbind().into())
}
JValue::Lambda { .. } | JValue::Builtin { .. } | JValue::Regex { .. } => Ok(py.None()),
}
}
#[cfg(feature = "python")]
fn create_evaluator(py: Python, bindings: Option<Py<PyAny>>) -> PyResult<evaluator::Evaluator> {
if let Some(bindings_obj) = bindings {
let bindings_json = python_to_json(py, &bindings_obj)?;
let mut context = evaluator::Context::new();
if let JValue::Object(map) = bindings_json {
for (key, value) in map.iter() {
context.bind(key.clone(), value.clone());
}
} else {
return Err(PyTypeError::new_err("bindings must be a dictionary"));
}
Ok(evaluator::Evaluator::with_context(context))
} else {
Ok(evaluator::Evaluator::new())
}
}
#[cfg(feature = "python")]
fn evaluator_error_to_py(e: evaluator::EvaluatorError) -> PyErr {
match e {
evaluator::EvaluatorError::TypeError(msg) => PyValueError::new_err(msg),
evaluator::EvaluatorError::ReferenceError(msg) => PyValueError::new_err(msg),
evaluator::EvaluatorError::EvaluationError(msg) => PyValueError::new_err(msg),
}
}
#[cfg(feature = "python")]
#[pymodule]
fn _jsonatapy(m: &Bound<'_, PyModule>) -> PyResult<()> {
m.add_function(wrap_pyfunction!(compile, m)?)?;
m.add_function(wrap_pyfunction!(evaluate, m)?)?;
m.add_class::<JsonataExpression>()?;
m.add_class::<JsonataData>()?;
m.add("__version__", env!("CARGO_PKG_VERSION"))?;
m.add("__jsonata_version__", JSONATA_REFERENCE_VERSION)?;
Ok(())
}
#[cfg(test)]
mod tests {
#[test]
fn test_module_creation() {
assert!(!env!("CARGO_PKG_VERSION").is_empty());
}
}