use crate::ast::{AstPattern, ParseWithHeaderResult};
use pyo3::exceptions::PyValueError;
use pyo3::prelude::*;
use std::collections::HashMap;
#[pyclass]
#[derive(Clone)]
pub struct ParseResult {
#[pyo3(get)]
pub pattern_count: usize,
#[pyo3(get)]
pub identifiers: Vec<String>,
}
#[pymethods]
impl ParseResult {
fn __repr__(&self) -> String {
format!(
"ParseResult(pattern_count={}, identifiers={:?})",
self.pattern_count, self.identifiers
)
}
fn __str__(&self) -> String {
format!(
"Parsed {} pattern(s) with identifiers: {:?}",
self.pattern_count, self.identifiers
)
}
fn to_dict(&self) -> HashMap<String, PyObject> {
Python::with_gil(|py| {
let mut dict = HashMap::new();
dict.insert(
"pattern_count".to_string(),
self.pattern_count.to_object(py),
);
dict.insert("identifiers".to_string(), self.identifiers.to_object(py));
dict
})
}
}
#[pyfunction]
fn parse_gram(input: &str) -> PyResult<ParseResult> {
let patterns = crate::parse_gram(input)
.map_err(|e| PyValueError::new_err(format!("Parse error: {}", e)))?;
let identifiers: Vec<String> = patterns
.iter()
.filter_map(|p| {
let id = &p.value().identity.0;
if !id.is_empty() {
Some(id.clone())
} else {
None
}
})
.collect();
Ok(ParseResult {
pattern_count: patterns.len(),
identifiers,
})
}
#[pyfunction]
fn validate_gram(input: &str) -> bool {
crate::parse_gram(input).is_ok()
}
#[pyfunction]
fn round_trip(input: &str) -> PyResult<String> {
let patterns = crate::parse_gram(input)
.map_err(|e| PyValueError::new_err(format!("Parse error: {}", e)))?;
crate::to_gram(&patterns).map_err(|e| PyValueError::new_err(format!("Serialize error: {}", e)))
}
#[pyfunction]
fn parse_to_ast(py: Python, input: &str) -> PyResult<PyObject> {
let ast = crate::parse_to_ast(input)
.map_err(|e| PyValueError::new_err(format!("Parse error: {}", e)))?;
let json_str = serde_json::to_string(&ast)
.map_err(|e| PyValueError::new_err(format!("Serialization error: {}", e)))?;
let json_module = py.import("json")?;
let loads = json_module.getattr("loads")?;
loads.call1((json_str,)).map(|obj| obj.into())
}
#[pyfunction]
fn version() -> &'static str {
env!("CARGO_PKG_VERSION")
}
#[pyfunction]
fn parse_patterns_as_dicts(py: Python, input: &str) -> PyResult<PyObject> {
use crate::ast::AstPattern;
let patterns = crate::parse_gram(input)
.map_err(|e| pyo3::exceptions::PyValueError::new_err(format!("Parse error: {}", e)))?;
let dicts: Vec<String> = patterns
.iter()
.map(|p| {
let ast = AstPattern::from_pattern(p);
serde_json::to_string(&ast).map_err(|e| {
pyo3::exceptions::PyValueError::new_err(format!("Serialization error: {}", e))
})
})
.collect::<PyResult<Vec<_>>>()?;
let json_array = format!("[{}]", dicts.join(","));
let json_module = py.import("json")?;
let loads = json_module.getattr("loads")?;
loads.call1((json_array,)).map(|obj| obj.into())
}
#[pyfunction(name = "parse")]
fn parse_py(py: Python, input: &str) -> PyResult<PyObject> {
if input.trim().is_empty() {
return Ok(pyo3::types::PyList::empty(py).into());
}
let patterns = crate::parse_gram(input)
.map_err(|e| PyValueError::new_err(format!("Parse error: {}", e)))?;
let asts: Vec<AstPattern> = patterns.iter().map(AstPattern::from_pattern).collect();
pythonize::pythonize(py, &asts)
.map(|b| b.unbind())
.map_err(|e| PyValueError::new_err(format!("Serialization error: {}", e)))
}
#[pyfunction(name = "stringify")]
fn stringify_py(py: Python, patterns_obj: Bound<'_, PyAny>) -> PyResult<String> {
let asts: Vec<AstPattern> = pythonize::depythonize(&patterns_obj)
.map_err(|e| PyValueError::new_err(format!("Deserialization error: {}", e)))?;
let patterns: Vec<crate::Pattern<crate::Subject>> = asts
.iter()
.map(|ast| ast.to_pattern())
.collect::<Result<Vec<_>, _>>()
.map_err(|e| PyValueError::new_err(format!("Conversion error: {}", e)))?;
let _ = py;
crate::to_gram(&patterns).map_err(|e| PyValueError::new_err(format!("Stringify error: {}", e)))
}
#[pyfunction(name = "parse_with_header")]
fn parse_with_header_py(py: Python, input: &str) -> PyResult<PyObject> {
let (header, patterns) = crate::parse_gram_with_header(input)
.map_err(|e| PyValueError::new_err(format!("Parse error: {}", e)))?;
let result = ParseWithHeaderResult::from_parts(header, patterns);
pythonize::pythonize(py, &result)
.map(|b| b.unbind())
.map_err(|e| PyValueError::new_err(format!("Serialization error: {}", e)))
}
#[pyfunction(name = "stringify_with_header")]
fn stringify_with_header_py(py: Python, result_obj: Bound<'_, PyAny>) -> PyResult<String> {
let result: ParseWithHeaderResult = pythonize::depythonize(&result_obj)
.map_err(|e| PyValueError::new_err(format!("Deserialization error: {}", e)))?;
let header = result
.header_to_record()
.map_err(|e| PyValueError::new_err(format!("Conversion error: {}", e)))?
.unwrap_or_default();
let patterns: Vec<crate::Pattern<crate::Subject>> = result
.patterns
.iter()
.map(|ast| ast.to_pattern())
.collect::<Result<Vec<_>, _>>()
.map_err(|e| PyValueError::new_err(format!("Conversion error: {}", e)))?;
let _ = py;
crate::to_gram_with_header(header, &patterns)
.map_err(|e| PyValueError::new_err(format!("Stringify error: {}", e)))
}
#[pyfunction(name = "gram_validate")]
fn gram_validate_py(input: &str) -> Vec<String> {
match crate::validate_gram(input) {
Ok(()) => vec![],
Err(e) => vec![e.to_string()],
}
}
#[pymodule]
fn gram_codec(m: &Bound<'_, PyModule>) -> PyResult<()> {
m.add_function(wrap_pyfunction!(parse_py, m)?)?;
m.add_function(wrap_pyfunction!(stringify_py, m)?)?;
m.add_function(wrap_pyfunction!(parse_with_header_py, m)?)?;
m.add_function(wrap_pyfunction!(stringify_with_header_py, m)?)?;
m.add_function(wrap_pyfunction!(gram_validate_py, m)?)?;
m.add_function(wrap_pyfunction!(parse_gram, m)?)?;
m.add_function(wrap_pyfunction!(parse_to_ast, m)?)?;
m.add_function(wrap_pyfunction!(parse_patterns_as_dicts, m)?)?;
m.add_function(wrap_pyfunction!(validate_gram, m)?)?;
m.add_function(wrap_pyfunction!(round_trip, m)?)?;
m.add_function(wrap_pyfunction!(version, m)?)?;
m.add_class::<ParseResult>()?;
m.add("__version__", env!("CARGO_PKG_VERSION"))?;
Ok(())
}