1pub mod ast;
33pub mod ast_transform;
34mod compiler;
35mod datetime;
36pub mod evaluator;
37pub mod functions;
38pub mod parser;
39mod signature;
40pub mod value;
41mod vm;
42
43#[cfg(feature = "bench")]
53pub mod _bench {
54 use crate::ast::AstNode;
55 pub use crate::evaluator::EvaluatorError;
56 use crate::value::JValue;
57
58 pub struct CompiledProgram(crate::vm::BytecodeProgram);
60
61 pub fn compile(ast: &AstNode) -> Option<CompiledProgram> {
67 crate::evaluator::try_compile_expr(ast)
68 .map(|ce| CompiledProgram(crate::compiler::BytecodeCompiler::compile(&ce)))
69 }
70
71 pub fn run(prog: &CompiledProgram, data: &JValue) -> Result<JValue, EvaluatorError> {
73 crate::vm::Vm::with_options(&prog.0, crate::evaluator::EvaluatorOptions::default())
74 .run(data, None)
75 }
76}
77
78const JSONATA_REFERENCE_VERSION: &str = "2.1.0";
82
83#[cfg(feature = "python")]
84use crate::value::JValue;
85#[cfg(feature = "python")]
86use indexmap::IndexMap;
87#[cfg(feature = "python")]
88use pyo3::exceptions::{PyTypeError, PyValueError};
89#[cfg(feature = "python")]
90use pyo3::prelude::*;
91#[cfg(feature = "python")]
92use pyo3::types::{PyBool, PyDict, PyFloat, PyInt, 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")]
169impl JsonataExpression {
170 fn run_eval(
173 &self,
174 py: Python,
175 data: &JValue,
176 bindings: Option<Py<PyAny>>,
177 options: evaluator::EvaluatorOptions,
178 ) -> PyResult<JValue> {
179 if bindings.is_none() {
180 let bytecode = self.bytecode.get_or_init(|| {
181 evaluator::try_compile_expr(&self.ast)
182 .map(|ce| compiler::BytecodeCompiler::compile(&ce))
183 });
184 if let Some(bc) = bytecode {
185 vm::Vm::with_options(bc, options.clone())
186 .run(data, None)
187 .map_err(evaluator_error_to_py)
188 } else {
189 let mut ev = evaluator::Evaluator::with_options(evaluator::Context::new(), options);
190 ev.evaluate(&self.ast, data).map_err(evaluator_error_to_py)
191 }
192 } else {
193 let mut ev = create_evaluator(py, bindings, options)?;
194 ev.evaluate(&self.ast, data).map_err(evaluator_error_to_py)
195 }
196 }
197}
198
199#[cfg(feature = "python")]
200#[pymethods]
201impl JsonataExpression {
202 #[pyo3(signature = (data, bindings=None, timeout=None, max_stack_depth=None, max_sequence_length=None))]
204 #[allow(clippy::too_many_arguments)]
205 fn evaluate(
206 &self,
207 py: Python,
208 data: Py<PyAny>,
209 bindings: Option<Py<PyAny>>,
210 timeout: Option<u64>,
211 max_stack_depth: Option<usize>,
212 max_sequence_length: Option<usize>,
213 ) -> PyResult<Py<PyAny>> {
214 let json_data = python_to_json(py, &data)?;
215 let options = evaluator::EvaluatorOptions {
216 timeout_ms: timeout.or(self.default_options.timeout_ms),
217 max_stack_depth: max_stack_depth.or(self.default_options.max_stack_depth),
218 max_sequence_length: max_sequence_length.or(self.default_options.max_sequence_length),
219 };
220 json_to_python(py, &self.run_eval(py, &json_data, bindings, options)?)
221 }
222
223 #[pyo3(signature = (data, bindings=None, timeout=None, max_stack_depth=None, max_sequence_length=None))]
234 #[allow(clippy::too_many_arguments)]
235 fn evaluate_with_data(
236 &self,
237 py: Python,
238 data: &JsonataData,
239 bindings: Option<Py<PyAny>>,
240 timeout: Option<u64>,
241 max_stack_depth: Option<usize>,
242 max_sequence_length: Option<usize>,
243 ) -> PyResult<Py<PyAny>> {
244 let options = evaluator::EvaluatorOptions {
245 timeout_ms: timeout.or(self.default_options.timeout_ms),
246 max_stack_depth: max_stack_depth.or(self.default_options.max_stack_depth),
247 max_sequence_length: max_sequence_length.or(self.default_options.max_sequence_length),
248 };
249 json_to_python(py, &self.run_eval(py, &data.data, bindings, options)?)
250 }
251
252 #[pyo3(signature = (data, bindings=None, timeout=None, max_stack_depth=None, max_sequence_length=None))]
263 #[allow(clippy::too_many_arguments)]
264 fn evaluate_data_to_json(
265 &self,
266 py: Python,
267 data: &JsonataData,
268 bindings: Option<Py<PyAny>>,
269 timeout: Option<u64>,
270 max_stack_depth: Option<usize>,
271 max_sequence_length: Option<usize>,
272 ) -> PyResult<String> {
273 let options = evaluator::EvaluatorOptions {
274 timeout_ms: timeout.or(self.default_options.timeout_ms),
275 max_stack_depth: max_stack_depth.or(self.default_options.max_stack_depth),
276 max_sequence_length: max_sequence_length.or(self.default_options.max_sequence_length),
277 };
278 self.run_eval(py, &data.data, bindings, options)?
279 .to_json_string()
280 .map_err(|e| PyValueError::new_err(format!("Failed to serialize result: {}", e)))
281 }
282
283 #[pyo3(signature = (json_str, bindings=None, timeout=None, max_stack_depth=None, max_sequence_length=None))]
301 #[allow(clippy::too_many_arguments)]
302 fn evaluate_json(
303 &self,
304 py: Python,
305 json_str: &str,
306 bindings: Option<Py<PyAny>>,
307 timeout: Option<u64>,
308 max_stack_depth: Option<usize>,
309 max_sequence_length: Option<usize>,
310 ) -> PyResult<String> {
311 let json_data = JValue::from_json_str(json_str)
312 .map_err(|e| PyValueError::new_err(format!("Invalid JSON: {}", e)))?;
313 let options = evaluator::EvaluatorOptions {
314 timeout_ms: timeout.or(self.default_options.timeout_ms),
315 max_stack_depth: max_stack_depth.or(self.default_options.max_stack_depth),
316 max_sequence_length: max_sequence_length.or(self.default_options.max_sequence_length),
317 };
318 self.run_eval(py, &json_data, bindings, options)?
319 .to_json_string()
320 .map_err(|e| PyValueError::new_err(format!("Failed to serialize result: {}", e)))
321 }
322}
323
324#[cfg(feature = "python")]
348#[pyfunction]
349#[pyo3(signature = (expression, timeout=None, max_stack_depth=None, max_sequence_length=None))]
350fn compile(
351 expression: &str,
352 timeout: Option<u64>,
353 max_stack_depth: Option<usize>,
354 max_sequence_length: Option<usize>,
355) -> PyResult<JsonataExpression> {
356 let ast = parser::parse(expression).map_err(parser_error_to_py)?;
357
358 Ok(JsonataExpression {
359 ast,
360 bytecode: std::cell::OnceCell::new(),
361 default_options: build_evaluator_options(timeout, max_stack_depth, max_sequence_length),
362 })
363}
364
365#[cfg(feature = "python")]
393#[pyfunction]
394#[pyo3(signature = (expression, data, bindings=None, timeout=None, max_stack_depth=None, max_sequence_length=None))]
395#[allow(clippy::too_many_arguments)]
396fn evaluate(
397 py: Python,
398 expression: &str,
399 data: Py<PyAny>,
400 bindings: Option<Py<PyAny>>,
401 timeout: Option<u64>,
402 max_stack_depth: Option<usize>,
403 max_sequence_length: Option<usize>,
404) -> PyResult<Py<PyAny>> {
405 let expr = compile(expression, None, None, None)?;
406 expr.evaluate(
407 py,
408 data,
409 bindings,
410 timeout,
411 max_stack_depth,
412 max_sequence_length,
413 )
414}
415
416#[cfg(feature = "python")]
426fn python_to_json(py: Python, obj: &Py<PyAny>) -> PyResult<JValue> {
427 python_to_json_bound(obj.bind(py))
428}
429
430#[cfg(feature = "python")]
436fn python_to_json_bound(obj: &Bound<'_, PyAny>) -> PyResult<JValue> {
437 if obj.is_none() {
438 return Ok(JValue::Null);
439 }
440
441 if obj.is_instance_of::<PyBool>() {
443 return Ok(JValue::Bool(obj.extract::<bool>()?));
444 }
445 if obj.is_instance_of::<PyInt>() {
446 return Ok(JValue::Number(obj.extract::<i64>()? as f64));
447 }
448 if obj.is_instance_of::<PyFloat>() {
449 return Ok(JValue::Number(obj.extract::<f64>()?));
450 }
451 if obj.is_instance_of::<PyString>() {
452 return Ok(JValue::string(obj.extract::<String>()?));
453 }
454 if let Ok(list) = obj.cast::<PyList>() {
455 let mut result = Vec::with_capacity(list.len());
456 for item in list.iter() {
457 result.push(python_to_json_bound(&item)?);
458 }
459 return Ok(JValue::array(result));
460 }
461 if let Ok(dict) = obj.cast::<PyDict>() {
462 let mut result = IndexMap::with_capacity(dict.len());
463 for (key, value) in dict.iter() {
464 let key_str = key.extract::<String>()?;
465 result.insert(key_str, python_to_json_bound(&value)?);
466 }
467 return Ok(JValue::object(result));
468 }
469
470 if let Ok(b) = obj.extract::<bool>() {
472 return Ok(JValue::Bool(b));
473 }
474 if let Ok(i) = obj.extract::<i64>() {
475 return Ok(JValue::Number(i as f64));
476 }
477 if let Ok(f) = obj.extract::<f64>() {
478 return Ok(JValue::Number(f));
479 }
480 if let Ok(s) = obj.extract::<String>() {
481 return Ok(JValue::string(s));
482 }
483
484 Err(PyTypeError::new_err(format!(
485 "Cannot convert Python object to JSON: {}",
486 obj.get_type().name()?
487 )))
488}
489
490#[cfg(feature = "python")]
501fn json_to_python(py: Python, value: &JValue) -> PyResult<Py<PyAny>> {
502 match value {
503 JValue::Null | JValue::Undefined => Ok(py.None()),
504
505 JValue::Bool(b) => Ok(b.into_pyobject(py).unwrap().to_owned().into_any().unbind()),
506
507 JValue::Number(n) => {
508 if n.fract() == 0.0 && n.is_finite() && *n >= i64::MIN as f64 && *n <= i64::MAX as f64 {
510 Ok((*n as i64).into_pyobject(py).unwrap().into_any().unbind())
511 } else {
512 Ok(n.into_pyobject(py).unwrap().into_any().unbind())
513 }
514 }
515
516 JValue::String(s) => Ok((&**s).into_pyobject(py).unwrap().into_any().unbind()),
517
518 JValue::Array(arr) => {
519 let all_objects =
522 arr.len() >= 2 && arr.iter().all(|item| matches!(item, JValue::Object(_)));
523 if all_objects {
524 let first_obj = match arr.first() {
525 Some(JValue::Object(obj)) => obj,
526 _ => unreachable!("all_objects guard ensures first element is an object"),
527 };
528
529 let interned_keys: Vec<(&str, Py<PyString>)> = first_obj
532 .keys()
533 .map(|k| (k.as_str(), PyString::new(py, k).unbind()))
534 .collect();
535
536 let items: Vec<Py<PyAny>> = arr
537 .iter()
538 .map(|item| {
539 let obj = match item {
541 JValue::Object(obj) => obj,
542 _ => unreachable!(),
543 };
544 let dict = PyDict::new(py);
545 for (key_str, py_key) in &interned_keys {
546 if let Some(value) = obj.get(*key_str) {
547 dict.set_item(py_key.bind(py), json_to_python(py, value)?)?;
548 }
549 }
550 for (key, value) in obj.iter() {
552 if !first_obj.contains_key(key) {
553 dict.set_item(key, json_to_python(py, value)?)?;
554 }
555 }
556 Ok(dict.unbind().into())
557 })
558 .collect::<PyResult<Vec<_>>>()?;
559 return Ok(PyList::new(py, &items)?.unbind().into());
560 }
561
562 let items: Vec<Py<PyAny>> = arr
564 .iter()
565 .map(|item| json_to_python(py, item))
566 .collect::<PyResult<Vec<_>>>()?;
567 Ok(PyList::new(py, &items)?.unbind().into())
568 }
569
570 JValue::Object(obj) => {
571 let dict = PyDict::new(py);
572 for (key, value) in obj.iter() {
573 dict.set_item(key, json_to_python(py, value)?)?;
574 }
575 Ok(dict.unbind().into())
576 }
577
578 JValue::Lambda { .. } | JValue::Builtin { .. } | JValue::Regex { .. } => Ok(py.None()),
579 }
580}
581
582#[cfg(feature = "python")]
585fn build_evaluator_options(
586 timeout: Option<u64>,
587 max_stack_depth: Option<usize>,
588 max_sequence_length: Option<usize>,
589) -> evaluator::EvaluatorOptions {
590 evaluator::EvaluatorOptions {
591 timeout_ms: timeout,
592 max_stack_depth,
593 max_sequence_length,
594 }
595}
596
597#[cfg(feature = "python")]
599fn create_evaluator(
600 py: Python,
601 bindings: Option<Py<PyAny>>,
602 options: evaluator::EvaluatorOptions,
603) -> PyResult<evaluator::Evaluator> {
604 let mut context = evaluator::Context::new();
605 if let Some(bindings_obj) = bindings {
606 let bindings_json = python_to_json(py, &bindings_obj)?;
607 if let JValue::Object(map) = bindings_json {
608 for (key, value) in map.iter() {
609 context.bind(key.clone(), value.clone());
610 }
611 } else {
612 return Err(PyTypeError::new_err("bindings must be a dictionary"));
613 }
614 }
615 Ok(evaluator::Evaluator::with_options(context, options))
616}
617
618#[cfg(feature = "python")]
620fn evaluator_error_to_py(e: evaluator::EvaluatorError) -> PyErr {
621 match e {
622 evaluator::EvaluatorError::TypeError(msg) => PyValueError::new_err(msg),
623 evaluator::EvaluatorError::ReferenceError(msg) => PyValueError::new_err(msg),
624 evaluator::EvaluatorError::EvaluationError(msg) => PyValueError::new_err(msg),
625 }
626}
627
628fn format_parser_error_message(e: &parser::ParserError) -> String {
638 let msg = e.to_string();
639 if matches!(e, parser::ParserError::Coded { .. }) {
640 msg
642 } else {
643 format!("Parse error: {}", msg)
645 }
646}
647
648#[cfg(feature = "python")]
650fn parser_error_to_py(e: parser::ParserError) -> PyErr {
651 PyValueError::new_err(format_parser_error_message(&e))
652}
653
654#[cfg(feature = "python")]
656#[pymodule]
657fn _jsonatapy(m: &Bound<'_, PyModule>) -> PyResult<()> {
658 m.add_function(wrap_pyfunction!(compile, m)?)?;
659 m.add_function(wrap_pyfunction!(evaluate, m)?)?;
660 m.add_class::<JsonataExpression>()?;
661 m.add_class::<JsonataData>()?;
662
663 m.add("__version__", env!("CARGO_PKG_VERSION"))?;
665 m.add("__jsonata_version__", JSONATA_REFERENCE_VERSION)?;
666
667 Ok(())
668}
669
670#[cfg(test)]
671mod tests {
672 #[test]
673 fn test_module_creation() {
674 assert!(!env!("CARGO_PKG_VERSION").is_empty());
676 }
677
678 mod parser_error_handling {
679 use super::super::*;
680
681 #[test]
690 fn test_parser_error_to_py_coded_error_no_prefix() {
691 let coded_error = parser::ParserError::Coded {
693 code: "S0214",
694 message: "Expected a variable reference after @".to_string(),
695 };
696 let msg = format_parser_error_message(&coded_error);
697
698 assert!(
700 msg.starts_with("S0214:"),
701 "Expected message to start with 'S0214:', got: {}",
702 msg
703 );
704 assert!(
705 !msg.starts_with("Parse error:"),
706 "Expected no 'Parse error:' prefix, got: {}",
707 msg
708 );
709 }
710
711 #[test]
712 fn test_parser_error_to_py_non_coded_error_with_prefix() {
713 let non_coded_error = parser::ParserError::UnexpectedToken("foo".to_string());
715 let msg = format_parser_error_message(&non_coded_error);
716
717 assert!(
719 msg.starts_with("Parse error:"),
720 "Expected message to start with 'Parse error:', got: {}",
721 msg
722 );
723 }
724 }
725}