1pub mod ast;
33pub mod ast_transform;
34mod compiler;
35mod datetime;
36pub mod evaluator;
37pub mod functions;
38#[cfg(feature = "python")]
39pub mod lazy;
40pub mod parser;
41mod signature;
42pub mod value;
43mod vm;
44
45#[cfg(feature = "bench")]
55pub mod _bench {
56 use crate::ast::AstNode;
57 pub use crate::evaluator::EvaluatorError;
58 use crate::value::JValue;
59
60 pub struct CompiledProgram(crate::vm::BytecodeProgram);
62
63 pub fn compile(ast: &AstNode) -> Option<CompiledProgram> {
69 crate::evaluator::try_compile_expr(ast)
70 .map(|ce| CompiledProgram(crate::compiler::BytecodeCompiler::compile(&ce)))
71 }
72
73 pub fn run(prog: &CompiledProgram, data: &JValue) -> Result<JValue, EvaluatorError> {
75 crate::vm::Vm::with_options(&prog.0, crate::evaluator::EvaluatorOptions::default())
76 .run(data, None)
77 }
78}
79
80const JSONATA_REFERENCE_VERSION: &str = "2.1.0";
84
85#[cfg(feature = "python")]
86use crate::value::JValue;
87#[cfg(feature = "python")]
88use pyo3::exceptions::{PyTypeError, PyValueError};
89#[cfg(feature = "python")]
90use pyo3::prelude::*;
91#[cfg(feature = "python")]
92use pyo3::types::{PyDict, PyList, PyString};
93
94#[cfg(feature = "python")]
109#[pyclass(unsendable)]
110struct JsonataData {
111 data: JValue,
112}
113
114#[cfg(feature = "python")]
115#[pymethods]
116impl JsonataData {
117 #[new]
119 fn new(py: Python, data: Py<PyAny>) -> PyResult<Self> {
120 let jvalue = python_to_json(py, &data)?;
121 Ok(JsonataData { data: jvalue })
122 }
123
124 #[staticmethod]
126 fn from_json(json_str: &str) -> PyResult<Self> {
127 let data = JValue::from_json_str(json_str)
128 .map_err(|e| PyValueError::new_err(format!("Invalid JSON: {}", e)))?;
129 Ok(JsonataData { data })
130 }
131}
132
133#[cfg(feature = "python")]
154#[pyclass(unsendable)]
155struct JsonataExpression {
156 ast: ast::AstNode,
158 bytecode: std::cell::OnceCell<Option<vm::BytecodeProgram>>,
162 default_options: evaluator::EvaluatorOptions,
166}
167
168#[cfg(feature = "python")]
173fn force_tree_walker() -> bool {
174 std::env::var_os("JSONATAPY_FORCE_TREE_WALKER").is_some_and(|v| !v.is_empty() && v != "0")
175}
176
177#[cfg(feature = "python")]
178impl JsonataExpression {
179 fn run_eval(
182 &self,
183 py: Python,
184 data: &JValue,
185 bindings: Option<Py<PyAny>>,
186 options: evaluator::EvaluatorOptions,
187 ) -> PyResult<JValue> {
188 if bindings.is_none() && !force_tree_walker() {
189 let bytecode = self.bytecode.get_or_init(|| {
190 evaluator::try_compile_expr(&self.ast)
191 .map(|ce| compiler::BytecodeCompiler::compile(&ce))
192 });
193 if let Some(bc) = bytecode {
194 vm::Vm::with_options(bc, options.clone())
195 .run(data, None)
196 .map_err(evaluator_error_to_py)
197 } else {
198 let mut ev = evaluator::Evaluator::with_options(evaluator::Context::new(), options);
199 ev.evaluate(&self.ast, data).map_err(evaluator_error_to_py)
200 }
201 } else {
202 let mut ev = create_evaluator(py, bindings, options)?;
203 ev.evaluate(&self.ast, data).map_err(evaluator_error_to_py)
204 }
205 }
206}
207
208#[cfg(feature = "python")]
209#[pymethods]
210impl JsonataExpression {
211 #[pyo3(signature = (data, bindings=None, timeout=None, max_stack_depth=None, max_sequence_length=None))]
213 #[allow(clippy::too_many_arguments)]
214 fn evaluate(
215 &self,
216 py: Python,
217 data: Py<PyAny>,
218 bindings: Option<Py<PyAny>>,
219 timeout: Option<u64>,
220 max_stack_depth: Option<usize>,
221 max_sequence_length: Option<usize>,
222 ) -> PyResult<Py<PyAny>> {
223 let json_data = lazy::convert(data.bind(py), true)?;
224 let options = evaluator::EvaluatorOptions {
225 timeout_ms: timeout.or(self.default_options.timeout_ms),
226 max_stack_depth: max_stack_depth.or(self.default_options.max_stack_depth),
227 max_sequence_length: max_sequence_length.or(self.default_options.max_sequence_length),
228 };
229 json_to_python(py, &self.run_eval(py, &json_data, bindings, options)?)
230 }
231
232 #[pyo3(signature = (data, bindings=None, timeout=None, max_stack_depth=None, max_sequence_length=None))]
243 #[allow(clippy::too_many_arguments)]
244 fn evaluate_with_data(
245 &self,
246 py: Python,
247 data: &JsonataData,
248 bindings: Option<Py<PyAny>>,
249 timeout: Option<u64>,
250 max_stack_depth: Option<usize>,
251 max_sequence_length: Option<usize>,
252 ) -> PyResult<Py<PyAny>> {
253 let options = evaluator::EvaluatorOptions {
254 timeout_ms: timeout.or(self.default_options.timeout_ms),
255 max_stack_depth: max_stack_depth.or(self.default_options.max_stack_depth),
256 max_sequence_length: max_sequence_length.or(self.default_options.max_sequence_length),
257 };
258 json_to_python(py, &self.run_eval(py, &data.data, bindings, options)?)
259 }
260
261 #[pyo3(signature = (data, bindings=None, timeout=None, max_stack_depth=None, max_sequence_length=None))]
272 #[allow(clippy::too_many_arguments)]
273 fn evaluate_data_to_json(
274 &self,
275 py: Python,
276 data: &JsonataData,
277 bindings: Option<Py<PyAny>>,
278 timeout: Option<u64>,
279 max_stack_depth: Option<usize>,
280 max_sequence_length: Option<usize>,
281 ) -> PyResult<String> {
282 let options = evaluator::EvaluatorOptions {
283 timeout_ms: timeout.or(self.default_options.timeout_ms),
284 max_stack_depth: max_stack_depth.or(self.default_options.max_stack_depth),
285 max_sequence_length: max_sequence_length.or(self.default_options.max_sequence_length),
286 };
287 self.run_eval(py, &data.data, bindings, options)?
288 .to_json_string()
289 .map_err(|e| PyValueError::new_err(format!("Failed to serialize result: {}", e)))
290 }
291
292 #[pyo3(signature = (json_str, bindings=None, timeout=None, max_stack_depth=None, max_sequence_length=None))]
310 #[allow(clippy::too_many_arguments)]
311 fn evaluate_json(
312 &self,
313 py: Python,
314 json_str: &str,
315 bindings: Option<Py<PyAny>>,
316 timeout: Option<u64>,
317 max_stack_depth: Option<usize>,
318 max_sequence_length: Option<usize>,
319 ) -> PyResult<String> {
320 let json_data = JValue::from_json_str(json_str)
321 .map_err(|e| PyValueError::new_err(format!("Invalid JSON: {}", e)))?;
322 let options = evaluator::EvaluatorOptions {
323 timeout_ms: timeout.or(self.default_options.timeout_ms),
324 max_stack_depth: max_stack_depth.or(self.default_options.max_stack_depth),
325 max_sequence_length: max_sequence_length.or(self.default_options.max_sequence_length),
326 };
327 self.run_eval(py, &json_data, bindings, options)?
328 .to_json_string()
329 .map_err(|e| PyValueError::new_err(format!("Failed to serialize result: {}", e)))
330 }
331
332 #[pyo3(signature = (json_str, bindings=None, timeout=None, max_stack_depth=None, max_sequence_length=None))]
349 #[allow(clippy::too_many_arguments)]
350 fn evaluate_json_or_none(
351 &self,
352 py: Python,
353 json_str: Option<&str>,
354 bindings: Option<Py<PyAny>>,
355 timeout: Option<u64>,
356 max_stack_depth: Option<usize>,
357 max_sequence_length: Option<usize>,
358 ) -> PyResult<Option<String>> {
359 let json_data = match json_str {
360 Some(s) => JValue::from_json_str(s)
361 .map_err(|e| PyValueError::new_err(format!("Invalid JSON: {}", e)))?,
362 None => JValue::Undefined,
363 };
364 let options = evaluator::EvaluatorOptions {
365 timeout_ms: timeout.or(self.default_options.timeout_ms),
366 max_stack_depth: max_stack_depth.or(self.default_options.max_stack_depth),
367 max_sequence_length: max_sequence_length.or(self.default_options.max_sequence_length),
368 };
369 let result = self.run_eval(py, &json_data, bindings, options)?;
370 if result.is_undefined() {
371 return Ok(None);
372 }
373 result
374 .to_json_string()
375 .map(Some)
376 .map_err(|e| PyValueError::new_err(format!("Failed to serialize result: {}", e)))
377 }
378}
379
380#[cfg(feature = "python")]
404#[pyfunction]
405#[pyo3(signature = (expression, timeout=None, max_stack_depth=None, max_sequence_length=None))]
406fn compile(
407 expression: &str,
408 timeout: Option<u64>,
409 max_stack_depth: Option<usize>,
410 max_sequence_length: Option<usize>,
411) -> PyResult<JsonataExpression> {
412 let ast = parser::parse(expression).map_err(parser_error_to_py)?;
413
414 Ok(JsonataExpression {
415 ast,
416 bytecode: std::cell::OnceCell::new(),
417 default_options: build_evaluator_options(timeout, max_stack_depth, max_sequence_length),
418 })
419}
420
421#[cfg(feature = "python")]
449#[pyfunction]
450#[pyo3(signature = (expression, data, bindings=None, timeout=None, max_stack_depth=None, max_sequence_length=None))]
451#[allow(clippy::too_many_arguments)]
452fn evaluate(
453 py: Python,
454 expression: &str,
455 data: Py<PyAny>,
456 bindings: Option<Py<PyAny>>,
457 timeout: Option<u64>,
458 max_stack_depth: Option<usize>,
459 max_sequence_length: Option<usize>,
460) -> PyResult<Py<PyAny>> {
461 let expr = compile(expression, None, None, None)?;
462 expr.evaluate(
463 py,
464 data,
465 bindings,
466 timeout,
467 max_stack_depth,
468 max_sequence_length,
469 )
470}
471
472#[cfg(feature = "python")]
482fn python_to_json(py: Python, obj: &Py<PyAny>) -> PyResult<JValue> {
483 python_to_json_bound(obj.bind(py))
484}
485
486#[cfg(feature = "python")]
490fn python_to_json_bound(obj: &Bound<'_, PyAny>) -> PyResult<JValue> {
491 lazy::convert(obj, false)
492}
493
494#[cfg(feature = "python")]
505fn json_to_python(py: Python, value: &JValue) -> PyResult<Py<PyAny>> {
506 match value {
507 JValue::Null | JValue::Undefined => Ok(py.None()),
508
509 JValue::Bool(b) => Ok(b.into_pyobject(py).unwrap().to_owned().into_any().unbind()),
510
511 JValue::Number(n) => {
512 if n.fract() == 0.0 && n.is_finite() && *n >= i64::MIN as f64 && *n <= i64::MAX as f64 {
514 Ok((*n as i64).into_pyobject(py).unwrap().into_any().unbind())
515 } else {
516 Ok(n.into_pyobject(py).unwrap().into_any().unbind())
517 }
518 }
519
520 JValue::String(s) => Ok((&**s).into_pyobject(py).unwrap().into_any().unbind()),
521
522 JValue::Array(arr) => {
523 let all_objects =
526 arr.len() >= 2 && arr.iter().all(|item| matches!(item, JValue::Object(_)));
527 if all_objects {
528 let first_obj = match arr.first() {
529 Some(JValue::Object(obj)) => obj,
530 _ => unreachable!("all_objects guard ensures first element is an object"),
531 };
532
533 let interned_keys: Vec<(&str, Py<PyString>)> = first_obj
536 .keys()
537 .map(|k| (k.as_str(), PyString::new(py, k).unbind()))
538 .collect();
539
540 let items: Vec<Py<PyAny>> = arr
541 .iter()
542 .map(|item| {
543 let obj = match item {
545 JValue::Object(obj) => obj,
546 _ => unreachable!(),
547 };
548 let dict = PyDict::new(py);
549 for (key_str, py_key) in &interned_keys {
550 if let Some(value) = obj.get(*key_str) {
551 dict.set_item(py_key.bind(py), json_to_python(py, value)?)?;
552 }
553 }
554 for (key, value) in obj.iter() {
556 if !first_obj.contains_key(key) {
557 dict.set_item(key, json_to_python(py, value)?)?;
558 }
559 }
560 Ok(dict.unbind().into())
561 })
562 .collect::<PyResult<Vec<_>>>()?;
563 return Ok(PyList::new(py, &items)?.unbind().into());
564 }
565
566 let items: Vec<Py<PyAny>> = arr
568 .iter()
569 .map(|item| json_to_python(py, item))
570 .collect::<PyResult<Vec<_>>>()?;
571 Ok(PyList::new(py, &items)?.unbind().into())
572 }
573
574 JValue::Object(obj) => {
575 let dict = PyDict::new(py);
576 for (key, value) in obj.iter() {
577 dict.set_item(key, json_to_python(py, value)?)?;
578 }
579 Ok(dict.unbind().into())
580 }
581
582 JValue::Lambda { .. } | JValue::Builtin { .. } | JValue::Regex { .. } => Ok(py.None()),
583
584 JValue::LazyPyDict(lazy) => Ok(lazy.py_object().clone_ref(py).into_any()),
585 }
586}
587
588#[cfg(feature = "python")]
591fn build_evaluator_options(
592 timeout: Option<u64>,
593 max_stack_depth: Option<usize>,
594 max_sequence_length: Option<usize>,
595) -> evaluator::EvaluatorOptions {
596 evaluator::EvaluatorOptions {
597 timeout_ms: timeout,
598 max_stack_depth,
599 max_sequence_length,
600 }
601}
602
603#[cfg(feature = "python")]
605fn create_evaluator(
606 py: Python,
607 bindings: Option<Py<PyAny>>,
608 options: evaluator::EvaluatorOptions,
609) -> PyResult<evaluator::Evaluator> {
610 let mut context = evaluator::Context::new();
611 if let Some(bindings_obj) = bindings {
612 let bindings_json = python_to_json(py, &bindings_obj)?;
613 if let JValue::Object(map) = bindings_json {
614 for (key, value) in map.iter() {
615 context.bind(key.clone(), value.clone());
616 }
617 } else {
618 return Err(PyTypeError::new_err("bindings must be a dictionary"));
619 }
620 }
621 Ok(evaluator::Evaluator::with_options(context, options))
622}
623
624#[cfg(feature = "python")]
626fn evaluator_error_to_py(e: evaluator::EvaluatorError) -> PyErr {
627 match e {
628 evaluator::EvaluatorError::PyConversionError(m) => PyTypeError::new_err(m),
629 other => PyValueError::new_err(other.message().to_string()),
630 }
631}
632
633#[cfg(feature = "python")]
635fn parser_error_to_py(e: parser::ParserError) -> PyErr {
636 PyValueError::new_err(e.display_message())
637}
638
639#[cfg(feature = "python")]
641#[pymodule]
642fn _jsonatapy(m: &Bound<'_, PyModule>) -> PyResult<()> {
643 m.add_function(wrap_pyfunction!(compile, m)?)?;
644 m.add_function(wrap_pyfunction!(evaluate, m)?)?;
645 m.add_class::<JsonataExpression>()?;
646 m.add_class::<JsonataData>()?;
647
648 m.add("__version__", env!("CARGO_PKG_VERSION"))?;
650 m.add("__jsonata_version__", JSONATA_REFERENCE_VERSION)?;
651
652 Ok(())
653}
654
655#[cfg(test)]
656mod tests {
657 #[test]
658 fn test_module_creation() {
659 assert!(!env!("CARGO_PKG_VERSION").is_empty());
661 }
662
663 mod parser_error_handling {
664 use super::super::*;
665
666 #[test]
675 fn test_parser_error_to_py_coded_error_no_prefix() {
676 let coded_error = parser::ParserError::Coded {
678 code: "S0214",
679 message: "Expected a variable reference after @".to_string(),
680 };
681 let msg = coded_error.display_message();
682
683 assert!(
685 msg.starts_with("S0214:"),
686 "Expected message to start with 'S0214:', got: {}",
687 msg
688 );
689 assert!(
690 !msg.starts_with("Parse error:"),
691 "Expected no 'Parse error:' prefix, got: {}",
692 msg
693 );
694 }
695
696 #[test]
697 fn test_parser_error_to_py_non_coded_error_with_prefix() {
698 let non_coded_error = parser::ParserError::UnexpectedToken("foo".to_string());
700 let msg = non_coded_error.display_message();
701
702 assert!(
704 msg.starts_with("Parse error:"),
705 "Expected message to start with 'Parse error:', got: {}",
706 msg
707 );
708 }
709 }
710}