use pyo3::exceptions::PyValueError;
use pyo3::prelude::*;
use pyo3::types::{PyBool, PyDict, PyFloat, PyInt, PyList, PyString};
use alloc::{string::ToString, sync::Arc, vec::Vec};
use crate::{de, expr::Expr, val::Val};
fn py_to_val(obj: &Bound<'_, PyAny>) -> PyResult<Val> {
if obj.is_none() {
Ok(Val::Nil)
} else if let Ok(b) = obj.downcast::<PyBool>() {
Ok(Val::Bool(b.is_true()))
} else if let Ok(i) = obj.downcast::<PyInt>() {
Ok(Val::Int(i.extract::<i64>()?))
} else if let Ok(f) = obj.downcast::<PyFloat>() {
Ok(Val::Float(f.extract::<f64>()?))
} else if let Ok(s) = obj.downcast::<PyString>() {
Ok(Val::from(s.to_str()?))
} else if let Ok(list) = obj.downcast::<PyList>() {
let items: Vec<Val> = list.iter().map(|item| py_to_val(&item)).collect::<PyResult<_>>()?;
Ok(Val::List(Arc::new(items)))
} else if let Ok(dict) = obj.downcast::<PyDict>() {
let mut map = hashbrown::HashMap::with_capacity(dict.len());
for (k, v) in dict.iter() {
let key = k
.downcast::<PyString>()
.map_err(|_| PyValueError::new_err("dict keys must be strings"))?
.to_str()?
.to_string();
map.insert(key, py_to_val(&v)?);
}
Ok(Val::Map(Arc::new(map)))
} else {
Err(PyValueError::new_err(format!(
"unsupported Python type: {}",
obj.get_type().name()?
)))
}
}
fn val_to_py(py: Python<'_>, val: &Val) -> PyResult<PyObject> {
match val {
Val::Nil => Ok(py.None()),
Val::Bool(b) => Ok(b.into_pyobject(py)?.to_owned().into_any().unbind()),
Val::Int(i) => Ok(i.into_pyobject(py)?.into_any().unbind()),
Val::Float(f) => Ok(f.into_pyobject(py)?.into_any().unbind()),
Val::Str(s) => Ok(s.as_ref().into_pyobject(py)?.into_any().unbind()),
Val::List(l) => {
let items: Vec<PyObject> = l.iter().map(|v| val_to_py(py, v)).collect::<PyResult<_>>()?;
Ok(PyList::new(py, items)?.into_any().unbind())
}
Val::Map(m) => {
let dict = PyDict::new(py);
for (k, v) in m.iter() {
dict.set_item(k, val_to_py(py, v)?)?;
}
Ok(dict.into_any().unbind())
}
}
}
#[pyfunction]
fn eval(py: Python<'_>, expression: &str, context: &Bound<'_, PyAny>) -> PyResult<PyObject> {
let ctx = py_to_val(context)?;
let expr = Expr::parse_cached_arc(expression).map_err(|e| PyValueError::new_err(e.to_string()))?;
let result = expr.eval(&ctx).map_err(|e| PyValueError::new_err(e.to_string()))?;
val_to_py(py, &result)
}
#[pyfunction]
fn eval_json(py: Python<'_>, expression: &str, json_ctx: &str) -> PyResult<PyObject> {
let ctx: Val = de::from_json_str(json_ctx).map_err(|e| PyValueError::new_err(e.to_string()))?;
let expr = Expr::parse_cached_arc(expression).map_err(|e| PyValueError::new_err(e.to_string()))?;
let result = expr.eval(&ctx).map_err(|e| PyValueError::new_err(e.to_string()))?;
val_to_py(py, &result)
}
#[pyfunction]
fn check(expression: &str, context: &Bound<'_, PyAny>) -> PyResult<bool> {
let ctx = py_to_val(context)?;
let expr = Expr::parse_cached_arc(expression).map_err(|e| PyValueError::new_err(e.to_string()))?;
let result = expr.eval(&ctx).map_err(|e| PyValueError::new_err(e.to_string()))?;
Ok(!matches!(result, Val::Bool(false) | Val::Nil))
}
#[pymodule]
fn qcl(m: &Bound<'_, PyModule>) -> PyResult<()> {
m.add_function(wrap_pyfunction!(eval, m)?)?;
m.add_function(wrap_pyfunction!(eval_json, m)?)?;
m.add_function(wrap_pyfunction!(check, m)?)?;
Ok(())
}