1pub mod ast;
33pub mod ast_transform;
34#[cfg(feature = "capi")]
35pub mod capi;
36mod compiler;
37mod datetime;
38pub mod evaluator;
39pub mod functions;
40#[cfg(feature = "python")]
41pub mod lazy;
42pub mod parser;
43mod signature;
44pub mod value;
45mod vm;
46
47#[cfg(feature = "bench")]
57pub mod _bench {
58 use crate::ast::AstNode;
59 pub use crate::evaluator::EvaluatorError;
60 use crate::value::JValue;
61
62 pub struct CompiledProgram(crate::vm::BytecodeProgram);
64
65 pub fn compile(ast: &AstNode) -> Option<CompiledProgram> {
71 crate::evaluator::try_compile_expr(ast)
72 .map(|ce| CompiledProgram(crate::compiler::BytecodeCompiler::compile(&ce)))
73 }
74
75 pub fn run(prog: &CompiledProgram, data: &JValue) -> Result<JValue, EvaluatorError> {
77 crate::vm::Vm::with_options(&prog.0, crate::evaluator::EvaluatorOptions::default())
78 .run(data, None)
79 }
80}
81
82const JSONATA_REFERENCE_VERSION: &str = "2.1.0";
86
87#[cfg(feature = "python")]
88use crate::value::JValue;
89#[cfg(feature = "python")]
90use pyo3::exceptions::{PyTypeError, PyValueError};
91#[cfg(feature = "python")]
92use pyo3::prelude::*;
93#[cfg(feature = "python")]
94use pyo3::types::{PyDict, PyList, PyString};
95
96#[cfg(feature = "python")]
111#[pyclass(unsendable)]
112struct JsonataData {
113 data: JValue,
114}
115
116#[cfg(feature = "python")]
117#[pymethods]
118impl JsonataData {
119 #[new]
121 fn new(py: Python, data: Py<PyAny>) -> PyResult<Self> {
122 let jvalue = python_to_json(py, &data)?;
123 Ok(JsonataData { data: jvalue })
124 }
125
126 #[staticmethod]
128 fn from_json(json_str: &str) -> PyResult<Self> {
129 let data = JValue::from_json_str(json_str)
130 .map_err(|e| PyValueError::new_err(format!("Invalid JSON: {}", e)))?;
131 Ok(JsonataData { data })
132 }
133}
134
135#[cfg(feature = "python")]
156#[pyclass(unsendable)]
157struct JsonataExpression {
158 ast: ast::AstNode,
160 bytecode: std::cell::OnceCell<Option<vm::BytecodeProgram>>,
164 default_options: evaluator::EvaluatorOptions,
168}
169
170#[cfg(feature = "python")]
181static FORCE_TREE_WALKER: std::sync::atomic::AtomicBool = std::sync::atomic::AtomicBool::new(false);
182
183#[cfg(feature = "python")]
184fn force_tree_walker() -> bool {
185 FORCE_TREE_WALKER.load(std::sync::atomic::Ordering::Relaxed)
186}
187
188#[cfg(feature = "python")]
191#[pyfunction]
192fn _set_force_tree_walker(on: bool) {
193 FORCE_TREE_WALKER.store(on, std::sync::atomic::Ordering::Relaxed);
194}
195
196#[cfg(feature = "python")]
198#[pyfunction]
199fn _get_force_tree_walker() -> bool {
200 force_tree_walker()
201}
202
203#[cfg(feature = "python")]
204impl JsonataExpression {
205 fn run_eval(
208 &self,
209 py: Python,
210 data: &JValue,
211 bindings: Option<Py<PyAny>>,
212 options: evaluator::EvaluatorOptions,
213 ) -> PyResult<JValue> {
214 if bindings.is_none() && !force_tree_walker() {
215 let bytecode = self.bytecode.get_or_init(|| {
216 evaluator::try_compile_expr(&self.ast)
217 .map(|ce| compiler::BytecodeCompiler::compile(&ce))
218 });
219 if let Some(bc) = bytecode {
220 vm::Vm::with_options(bc, options.clone())
221 .run(data, None)
222 .map_err(evaluator_error_to_py)
223 } else {
224 let mut ev = evaluator::Evaluator::with_options(evaluator::Context::new(), options);
225 ev.evaluate(&self.ast, data).map_err(evaluator_error_to_py)
226 }
227 } else {
228 let mut ev = create_evaluator(py, bindings, options)?;
229 ev.evaluate(&self.ast, data).map_err(evaluator_error_to_py)
230 }
231 }
232}
233
234#[cfg(feature = "python")]
235#[pymethods]
236impl JsonataExpression {
237 #[pyo3(signature = (data, bindings=None, timeout=None, max_stack_depth=None, max_sequence_length=None))]
239 #[allow(clippy::too_many_arguments)]
240 fn evaluate(
241 &self,
242 py: Python,
243 data: Py<PyAny>,
244 bindings: Option<Py<PyAny>>,
245 timeout: Option<u64>,
246 max_stack_depth: Option<usize>,
247 max_sequence_length: Option<usize>,
248 ) -> PyResult<Py<PyAny>> {
249 let json_data = lazy::convert(data.bind(py), true)?;
250 let options = evaluator::EvaluatorOptions {
251 timeout_ms: timeout.or(self.default_options.timeout_ms),
252 max_stack_depth: max_stack_depth.or(self.default_options.max_stack_depth),
253 max_sequence_length: max_sequence_length.or(self.default_options.max_sequence_length),
254 };
255 json_to_python(py, &self.run_eval(py, &json_data, bindings, options)?)
256 }
257
258 #[pyo3(signature = (data, bindings=None, timeout=None, max_stack_depth=None, max_sequence_length=None))]
269 #[allow(clippy::too_many_arguments)]
270 fn evaluate_with_data(
271 &self,
272 py: Python,
273 data: &JsonataData,
274 bindings: Option<Py<PyAny>>,
275 timeout: Option<u64>,
276 max_stack_depth: Option<usize>,
277 max_sequence_length: Option<usize>,
278 ) -> PyResult<Py<PyAny>> {
279 let options = evaluator::EvaluatorOptions {
280 timeout_ms: timeout.or(self.default_options.timeout_ms),
281 max_stack_depth: max_stack_depth.or(self.default_options.max_stack_depth),
282 max_sequence_length: max_sequence_length.or(self.default_options.max_sequence_length),
283 };
284 json_to_python(py, &self.run_eval(py, &data.data, bindings, options)?)
285 }
286
287 #[pyo3(signature = (data, bindings=None, timeout=None, max_stack_depth=None, max_sequence_length=None))]
298 #[allow(clippy::too_many_arguments)]
299 fn evaluate_data_to_json(
300 &self,
301 py: Python,
302 data: &JsonataData,
303 bindings: Option<Py<PyAny>>,
304 timeout: Option<u64>,
305 max_stack_depth: Option<usize>,
306 max_sequence_length: Option<usize>,
307 ) -> PyResult<String> {
308 let options = evaluator::EvaluatorOptions {
309 timeout_ms: timeout.or(self.default_options.timeout_ms),
310 max_stack_depth: max_stack_depth.or(self.default_options.max_stack_depth),
311 max_sequence_length: max_sequence_length.or(self.default_options.max_sequence_length),
312 };
313 self.run_eval(py, &data.data, bindings, options)?
314 .to_json_string()
315 .map_err(|e| PyValueError::new_err(format!("Failed to serialize result: {}", e)))
316 }
317
318 #[pyo3(signature = (json_str, bindings=None, timeout=None, max_stack_depth=None, max_sequence_length=None))]
336 #[allow(clippy::too_many_arguments)]
337 fn evaluate_json(
338 &self,
339 py: Python,
340 json_str: &str,
341 bindings: Option<Py<PyAny>>,
342 timeout: Option<u64>,
343 max_stack_depth: Option<usize>,
344 max_sequence_length: Option<usize>,
345 ) -> PyResult<String> {
346 let json_data = JValue::from_json_str(json_str)
347 .map_err(|e| PyValueError::new_err(format!("Invalid JSON: {}", e)))?;
348 let options = evaluator::EvaluatorOptions {
349 timeout_ms: timeout.or(self.default_options.timeout_ms),
350 max_stack_depth: max_stack_depth.or(self.default_options.max_stack_depth),
351 max_sequence_length: max_sequence_length.or(self.default_options.max_sequence_length),
352 };
353 self.run_eval(py, &json_data, bindings, options)?
354 .to_json_string()
355 .map_err(|e| PyValueError::new_err(format!("Failed to serialize result: {}", e)))
356 }
357
358 #[pyo3(signature = (json_str, bindings=None, timeout=None, max_stack_depth=None, max_sequence_length=None))]
375 #[allow(clippy::too_many_arguments)]
376 fn evaluate_json_or_none(
377 &self,
378 py: Python,
379 json_str: Option<&str>,
380 bindings: Option<Py<PyAny>>,
381 timeout: Option<u64>,
382 max_stack_depth: Option<usize>,
383 max_sequence_length: Option<usize>,
384 ) -> PyResult<Option<String>> {
385 let json_data = match json_str {
386 Some(s) => JValue::from_json_str(s)
387 .map_err(|e| PyValueError::new_err(format!("Invalid JSON: {}", e)))?,
388 None => JValue::Undefined,
389 };
390 let options = evaluator::EvaluatorOptions {
391 timeout_ms: timeout.or(self.default_options.timeout_ms),
392 max_stack_depth: max_stack_depth.or(self.default_options.max_stack_depth),
393 max_sequence_length: max_sequence_length.or(self.default_options.max_sequence_length),
394 };
395 let result = self.run_eval(py, &json_data, bindings, options)?;
396 if result.is_undefined() {
397 return Ok(None);
398 }
399 result
400 .to_json_string()
401 .map(Some)
402 .map_err(|e| PyValueError::new_err(format!("Failed to serialize result: {}", e)))
403 }
404}
405
406#[cfg(feature = "python")]
430#[pyfunction]
431#[pyo3(signature = (expression, timeout=None, max_stack_depth=None, max_sequence_length=None))]
432fn compile(
433 expression: &str,
434 timeout: Option<u64>,
435 max_stack_depth: Option<usize>,
436 max_sequence_length: Option<usize>,
437) -> PyResult<JsonataExpression> {
438 let ast = parser::parse(expression).map_err(parser_error_to_py)?;
439
440 Ok(JsonataExpression {
441 ast,
442 bytecode: std::cell::OnceCell::new(),
443 default_options: build_evaluator_options(timeout, max_stack_depth, max_sequence_length),
444 })
445}
446
447#[cfg(feature = "python")]
475#[pyfunction]
476#[pyo3(signature = (expression, data, bindings=None, timeout=None, max_stack_depth=None, max_sequence_length=None))]
477#[allow(clippy::too_many_arguments)]
478fn evaluate(
479 py: Python,
480 expression: &str,
481 data: Py<PyAny>,
482 bindings: Option<Py<PyAny>>,
483 timeout: Option<u64>,
484 max_stack_depth: Option<usize>,
485 max_sequence_length: Option<usize>,
486) -> PyResult<Py<PyAny>> {
487 let expr = compile(expression, None, None, None)?;
488 expr.evaluate(
489 py,
490 data,
491 bindings,
492 timeout,
493 max_stack_depth,
494 max_sequence_length,
495 )
496}
497
498#[cfg(feature = "python")]
508fn python_to_json(py: Python, obj: &Py<PyAny>) -> PyResult<JValue> {
509 python_to_json_bound(obj.bind(py))
510}
511
512#[cfg(feature = "python")]
516fn python_to_json_bound(obj: &Bound<'_, PyAny>) -> PyResult<JValue> {
517 lazy::convert(obj, false)
518}
519
520#[cfg(feature = "python")]
531fn json_to_python(py: Python, value: &JValue) -> PyResult<Py<PyAny>> {
532 match value {
533 JValue::Null | JValue::Undefined => Ok(py.None()),
534
535 JValue::Bool(b) => Ok(b.into_pyobject(py).unwrap().to_owned().into_any().unbind()),
536
537 JValue::Number(n) => {
538 if n.fract() == 0.0 && n.is_finite() && *n >= i64::MIN as f64 && *n <= i64::MAX as f64 {
540 Ok((*n as i64).into_pyobject(py).unwrap().into_any().unbind())
541 } else {
542 Ok(n.into_pyobject(py).unwrap().into_any().unbind())
543 }
544 }
545
546 JValue::String(s) => Ok((&**s).into_pyobject(py).unwrap().into_any().unbind()),
547
548 JValue::Array(arr) => {
549 let all_objects =
552 arr.len() >= 2 && arr.iter().all(|item| matches!(item, JValue::Object(_)));
553 if all_objects {
554 let first_obj = match arr.first() {
555 Some(JValue::Object(obj)) => obj,
556 _ => unreachable!("all_objects guard ensures first element is an object"),
557 };
558
559 let interned_keys: Vec<(&str, Py<PyString>)> = first_obj
562 .keys()
563 .map(|k| (k.as_str(), PyString::new(py, k).unbind()))
564 .collect();
565
566 let items: Vec<Py<PyAny>> = arr
567 .iter()
568 .map(|item| {
569 let obj = match item {
571 JValue::Object(obj) => obj,
572 _ => unreachable!(),
573 };
574 let dict = PyDict::new(py);
575 for (key_str, py_key) in &interned_keys {
576 if let Some(value) = obj.get(*key_str) {
577 dict.set_item(py_key.bind(py), json_to_python(py, value)?)?;
578 }
579 }
580 for (key, value) in obj.iter() {
582 if !first_obj.contains_key(key) {
583 dict.set_item(key, json_to_python(py, value)?)?;
584 }
585 }
586 Ok(dict.unbind().into())
587 })
588 .collect::<PyResult<Vec<_>>>()?;
589 return Ok(PyList::new(py, &items)?.unbind().into());
590 }
591
592 let items: Vec<Py<PyAny>> = arr
594 .iter()
595 .map(|item| json_to_python(py, item))
596 .collect::<PyResult<Vec<_>>>()?;
597 Ok(PyList::new(py, &items)?.unbind().into())
598 }
599
600 JValue::Object(obj) => {
601 let dict = PyDict::new(py);
602 for (key, value) in obj.iter() {
603 dict.set_item(key, json_to_python(py, value)?)?;
604 }
605 Ok(dict.unbind().into())
606 }
607
608 JValue::Lambda { .. } | JValue::Builtin { .. } | JValue::Regex { .. } => Ok(py.None()),
609
610 JValue::LazyPyDict(lazy) => Ok(lazy.py_object().clone_ref(py).into_any()),
611 }
612}
613
614#[cfg(feature = "python")]
617fn build_evaluator_options(
618 timeout: Option<u64>,
619 max_stack_depth: Option<usize>,
620 max_sequence_length: Option<usize>,
621) -> evaluator::EvaluatorOptions {
622 evaluator::EvaluatorOptions {
623 timeout_ms: timeout,
624 max_stack_depth,
625 max_sequence_length,
626 }
627}
628
629#[cfg(feature = "python")]
631fn create_evaluator(
632 py: Python,
633 bindings: Option<Py<PyAny>>,
634 options: evaluator::EvaluatorOptions,
635) -> PyResult<evaluator::Evaluator> {
636 let mut context = evaluator::Context::new();
637 if let Some(bindings_obj) = bindings {
638 let bindings_json = python_to_json(py, &bindings_obj)?;
639 if let JValue::Object(map) = bindings_json {
640 for (key, value) in map.iter() {
641 context.bind(key.clone(), value.clone());
642 }
643 } else {
644 return Err(PyTypeError::new_err("bindings must be a dictionary"));
645 }
646 }
647 Ok(evaluator::Evaluator::with_options(context, options))
648}
649
650#[cfg(feature = "python")]
652fn evaluator_error_to_py(e: evaluator::EvaluatorError) -> PyErr {
653 match e {
654 evaluator::EvaluatorError::PyConversionError(m) => PyTypeError::new_err(m),
655 other => PyValueError::new_err(other.message().to_string()),
656 }
657}
658
659#[cfg(feature = "python")]
661fn parser_error_to_py(e: parser::ParserError) -> PyErr {
662 PyValueError::new_err(e.display_message())
663}
664
665#[cfg(feature = "python")]
667#[pymodule]
668fn _jsonatapy(m: &Bound<'_, PyModule>) -> PyResult<()> {
669 FORCE_TREE_WALKER.store(
671 std::env::var_os("JSONATAPY_FORCE_TREE_WALKER").is_some_and(|v| !v.is_empty() && v != "0"),
672 std::sync::atomic::Ordering::Relaxed,
673 );
674 m.add_function(wrap_pyfunction!(compile, m)?)?;
675 m.add_function(wrap_pyfunction!(evaluate, m)?)?;
676 m.add_function(wrap_pyfunction!(_set_force_tree_walker, m)?)?;
677 m.add_function(wrap_pyfunction!(_get_force_tree_walker, m)?)?;
678 m.add_class::<JsonataExpression>()?;
679 m.add_class::<JsonataData>()?;
680
681 m.add("__version__", env!("CARGO_PKG_VERSION"))?;
683 m.add("__jsonata_version__", JSONATA_REFERENCE_VERSION)?;
684
685 Ok(())
686}
687
688#[cfg(test)]
689mod tests {
690 #[test]
691 fn test_module_creation() {
692 assert!(!env!("CARGO_PKG_VERSION").is_empty());
694 }
695
696 mod parser_error_handling {
697 use super::super::*;
698
699 #[test]
708 fn test_parser_error_to_py_coded_error_no_prefix() {
709 let coded_error = parser::ParserError::Coded {
711 code: "S0214",
712 message: "Expected a variable reference after @".to_string(),
713 };
714 let msg = coded_error.display_message();
715
716 assert!(
718 msg.starts_with("S0214:"),
719 "Expected message to start with 'S0214:', got: {}",
720 msg
721 );
722 assert!(
723 !msg.starts_with("Parse error:"),
724 "Expected no 'Parse error:' prefix, got: {}",
725 msg
726 );
727 }
728
729 #[test]
730 fn test_parser_error_to_py_non_coded_error_with_prefix() {
731 let non_coded_error = parser::ParserError::UnexpectedToken("foo".to_string());
733 let msg = non_coded_error.display_message();
734
735 assert!(
737 msg.starts_with("Parse error:"),
738 "Expected message to start with 'Parse error:', got: {}",
739 msg
740 );
741 }
742 }
743}