#![allow(unsafe_op_in_unsafe_fn)]
#[cfg(feature = "python-bindings")]
use pyo3::Py;
#[cfg(feature = "python-bindings")]
use pyo3::exceptions::PyValueError;
#[cfg(feature = "python-bindings")]
use pyo3::prelude::*;
#[cfg(feature = "python-bindings")]
use pyo3::types::{PyDict, PyList};
#[cfg(feature = "python-bindings")]
use crate::{compile_tauq, compile_tauqq, format_to_tauq, minify_tauq_str};
#[cfg(feature = "python-bindings")]
use serde_json::Value as JsonValue;
#[cfg(feature = "python-bindings")]
use std::path::PathBuf;
#[cfg(feature = "python-bindings")]
fn json_to_python(py: Python<'_>, value: &JsonValue) -> PyResult<Py<PyAny>> {
use pyo3::IntoPyObjectExt;
match value {
JsonValue::Null => Ok(py.None()),
JsonValue::Bool(b) => b.into_py_any(py),
JsonValue::Number(n) => {
if let Some(i) = n.as_i64() {
i.into_py_any(py)
} else if let Some(f) = n.as_f64() {
f.into_py_any(py)
} else {
Ok(py.None())
}
}
JsonValue::String(s) => s.into_py_any(py),
JsonValue::Array(arr) => {
let list = PyList::empty(py);
for item in arr {
list.append(json_to_python(py, item)?)?;
}
Ok(list.unbind().into_any())
}
JsonValue::Object(obj) => {
let dict = PyDict::new(py);
for (key, val) in obj {
dict.set_item(key, json_to_python(py, val)?)?;
}
Ok(dict.unbind().into_any())
}
}
}
#[cfg(feature = "python-bindings")]
#[allow(clippy::only_used_in_recursion)]
fn python_to_json(py: Python<'_>, obj: &Bound<'_, PyAny>) -> PyResult<JsonValue> {
if obj.is_none() {
Ok(JsonValue::Null)
} else if let Ok(b) = obj.extract::<bool>() {
Ok(JsonValue::Bool(b))
} else if let Ok(i) = obj.extract::<i64>() {
Ok(JsonValue::Number(i.into()))
} else if let Ok(f) = obj.extract::<f64>() {
Ok(JsonValue::Number(
serde_json::Number::from_f64(f).unwrap_or(serde_json::Number::from(0)),
))
} else if let Ok(s) = obj.extract::<String>() {
Ok(JsonValue::String(s))
} else if let Ok(list) = obj.cast::<PyList>() {
let mut arr = Vec::new();
for item in list.iter() {
arr.push(python_to_json(py, &item)?);
}
Ok(JsonValue::Array(arr))
} else if let Ok(dict) = obj.cast::<PyDict>() {
let mut map = serde_json::Map::new();
for (key, val) in dict.iter() {
let key_str = key.extract::<String>()?;
map.insert(key_str, python_to_json(py, &val)?);
}
Ok(JsonValue::Object(map))
} else {
Err(PyValueError::new_err(format!(
"Cannot convert Python type {} to JSON",
obj.get_type().name()?
)))
}
}
#[cfg(feature = "python-bindings")]
#[pyfunction]
fn loads(py: Python<'_>, source: &str) -> PyResult<Py<PyAny>> {
let json = compile_tauq(source)
.map_err(|e| PyValueError::new_err(format!("Tauq parse error: {}", e)))?;
json_to_python(py, &json)
}
#[cfg(feature = "python-bindings")]
#[pyfunction]
fn load(py: Python<'_>, path: PathBuf) -> PyResult<Py<PyAny>> {
let source = std::fs::read_to_string(&path)
.map_err(|e| PyValueError::new_err(format!("File read error: {}", e)))?;
let json = compile_tauq(&source)
.map_err(|e| PyValueError::new_err(format!("Tauq parse error: {}", e)))?;
json_to_python(py, &json)
}
#[cfg(feature = "python-bindings")]
#[pyfunction]
fn exec_tauqq(py: Python<'_>, source: &str) -> PyResult<Py<PyAny>> {
let json =
compile_tauqq(source, true) .map_err(|e| PyValueError::new_err(format!("TauqQ execution error: {}", e)))?;
json_to_python(py, &json)
}
#[cfg(feature = "python-bindings")]
#[allow(clippy::unsafe_removed_from_name)]
#[pyfunction]
fn exec_tauqq_unsafe(py: Python<'_>, source: &str) -> PyResult<Py<PyAny>> {
let json =
compile_tauqq(source, false) .map_err(|e| PyValueError::new_err(format!("TauqQ execution error: {}", e)))?;
json_to_python(py, &json)
}
#[cfg(feature = "python-bindings")]
#[pyfunction]
fn dumps(py: Python<'_>, obj: Bound<'_, PyAny>) -> PyResult<String> {
let json = python_to_json(py, &obj)?;
Ok(format_to_tauq(&json))
}
#[cfg(feature = "python-bindings")]
#[pyfunction]
fn minify(source: &str) -> PyResult<String> {
let json = compile_tauq(source)
.map_err(|e| PyValueError::new_err(format!("Tauq parse error: {}", e)))?;
Ok(minify_tauq_str(&json))
}
#[cfg(feature = "python-bindings")]
#[pyfunction]
fn dump(py: Python<'_>, obj: Bound<'_, PyAny>, path: PathBuf) -> PyResult<()> {
let json = python_to_json(py, &obj)?;
let tauq_str = format_to_tauq(&json);
std::fs::write(&path, tauq_str)
.map_err(|e| PyValueError::new_err(format!("Write error: {}", e)))?;
Ok(())
}
#[cfg(feature = "python-bindings")]
#[pyfunction]
fn tbf_dumps(py: Python<'_>, obj: Bound<'_, PyAny>) -> PyResult<Py<PyAny>> {
use pyo3::types::PyBytes;
let json = python_to_json(py, &obj)?;
let bytes = crate::tbf::encode_json(&json)
.map_err(|e| PyValueError::new_err(format!("TBF encode error: {}", e)))?;
Ok(PyBytes::new(py, &bytes).unbind().into_any())
}
#[cfg(feature = "python-bindings")]
#[pyfunction]
fn tbf_loads(py: Python<'_>, data: &[u8]) -> PyResult<Py<PyAny>> {
let json = crate::tbf::decode(data)
.map_err(|e| PyValueError::new_err(format!("TBF decode error: {}", e)))?;
json_to_python(py, &json)
}
#[cfg(feature = "python-bindings")]
#[pyfunction]
fn tbf_dump(py: Python<'_>, obj: Bound<'_, PyAny>, path: PathBuf) -> PyResult<()> {
let json = python_to_json(py, &obj)?;
let bytes = crate::tbf::encode_json(&json)
.map_err(|e| PyValueError::new_err(format!("TBF encode error: {}", e)))?;
std::fs::write(&path, bytes)
.map_err(|e| PyValueError::new_err(format!("Write error: {}", e)))?;
Ok(())
}
#[cfg(feature = "python-bindings")]
#[pyfunction]
fn tbf_load(py: Python<'_>, path: PathBuf) -> PyResult<Py<PyAny>> {
let bytes =
std::fs::read(&path).map_err(|e| PyValueError::new_err(format!("Read error: {}", e)))?;
let json = crate::tbf::decode(&bytes)
.map_err(|e| PyValueError::new_err(format!("TBF decode error: {}", e)))?;
json_to_python(py, &json)
}
#[cfg(feature = "python-bindings")]
#[pyfunction]
fn tbf_to_tauq(data: &[u8]) -> PyResult<String> {
crate::tbf::decode_to_tauq(data)
.map_err(|e| PyValueError::new_err(format!("TBF decode error: {}", e)))
}
#[cfg(feature = "python-bindings")]
#[pyclass]
struct TauqStream {
buffer: String,
consumed: usize,
}
#[cfg(feature = "python-bindings")]
#[pymethods]
impl TauqStream {
#[new]
fn new() -> Self {
Self {
buffer: String::new(),
consumed: 0,
}
}
fn push(&mut self, py: Python<'_>, chunk: &str) -> PyResult<Py<PyAny>> {
self.buffer.push_str(chunk);
let source = &self.buffer;
let parser = crate::tauq::streaming::StreamingParser::new(source);
let list = pyo3::types::PyList::empty(py);
let mut record_count: usize = 0;
for result in parser {
let val =
result.map_err(|e| PyValueError::new_err(format!("Stream parse error: {}", e)))?;
if record_count >= self.consumed {
list.append(json_to_python(py, &val)?)?;
}
record_count += 1;
}
self.consumed = record_count;
Ok(list.unbind().into_any())
}
fn finish(&mut self, py: Python<'_>) -> PyResult<Py<PyAny>> {
let source = &self.buffer;
let parser = crate::tauq::streaming::StreamingParser::new(source);
let list = pyo3::types::PyList::empty(py);
let mut record_count: usize = 0;
for result in parser {
let val =
result.map_err(|e| PyValueError::new_err(format!("Stream parse error: {}", e)))?;
if record_count >= self.consumed {
list.append(json_to_python(py, &val)?)?;
}
record_count += 1;
}
self.buffer.clear();
self.consumed = 0;
Ok(list.unbind().into_any())
}
}
#[cfg(feature = "python-bindings")]
#[pymodule]
fn tauq(m: &Bound<'_, PyModule>) -> PyResult<()> {
m.add_class::<TauqStream>()?;
m.add_function(wrap_pyfunction!(loads, m)?)?;
m.add_function(wrap_pyfunction!(load, m)?)?;
m.add_function(wrap_pyfunction!(exec_tauqq, m)?)?;
m.add_function(wrap_pyfunction!(exec_tauqq_unsafe, m)?)?;
m.add_function(wrap_pyfunction!(dumps, m)?)?;
m.add_function(wrap_pyfunction!(minify, m)?)?;
m.add_function(wrap_pyfunction!(dump, m)?)?;
m.add_function(wrap_pyfunction!(tbf_dumps, m)?)?;
m.add_function(wrap_pyfunction!(tbf_loads, m)?)?;
m.add_function(wrap_pyfunction!(tbf_dump, m)?)?;
m.add_function(wrap_pyfunction!(tbf_load, m)?)?;
m.add_function(wrap_pyfunction!(tbf_to_tauq, m)?)?;
m.add("__version__", env!("CARGO_PKG_VERSION"))?;
m.add(
"__doc__",
"Tauq parser for Python - JSON for the AI Era (44% fewer tokens than JSON)",
)?;
Ok(())
}