use crate::ast::AstPattern;
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())
}
#[pymodule]
fn gram_codec(m: &Bound<'_, PyModule>) -> PyResult<()> {
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(())
}