use crate::error;
use crate::result::ParseResult;
use crate::unicode_offsets::byte_to_char_index;
use crate::types::FieldSpec;
use pyo3::prelude::*;
use pyo3::IntoPyObjectExt;
use std::collections::HashMap;
pub struct MatchInit {
pub pattern: String,
pub field_specs: Vec<FieldSpec>,
pub field_names: Vec<Option<String>>,
pub normalized_names: Vec<Option<String>>,
pub captures: Vec<Option<String>>,
pub named_captures: HashMap<String, String>,
pub span: (usize, usize),
pub field_spans: HashMap<String, (usize, usize)>,
}
#[pyclass(skip_from_py_object)]
#[derive(Clone)]
pub struct Match {
#[pyo3(get)]
pattern: String,
field_specs: Vec<FieldSpec>,
field_names: Vec<Option<String>>,
normalized_names: Vec<Option<String>>,
captures: Vec<Option<String>>, named_captures: HashMap<String, String>, #[pyo3(get)]
pub span: (usize, usize),
field_spans: HashMap<String, (usize, usize)>, }
#[pymethods]
impl Match {
#[pyo3(signature = (*, extra_types=None))]
fn evaluate_result(
&self,
py: Python,
extra_types: Option<HashMap<String, Py<PyAny>>>,
) -> PyResult<Py<PyAny>> {
let custom_converters = extra_types.unwrap_or_default();
let mut fixed = Vec::new();
let mut named: HashMap<String, Py<PyAny>> = HashMap::new();
for (i, spec) in self.field_specs.iter().enumerate() {
let value_str =
if let Some(norm_name) = self.normalized_names.get(i).and_then(|n| n.as_ref()) {
self.named_captures
.get(norm_name.as_str())
.map(|s| s.as_str())
} else {
self.captures
.get(i)
.and_then(|s| s.as_ref())
.map(|s| s.as_str())
};
if let Some(value_str) = value_str {
if !crate::types::conversion::validate_alignment_precision_for_capture(
spec, value_str,
) {
return Err(pyo3::exceptions::PyValueError::new_err(
"capture failed alignment/precision validation",
));
}
let converted = crate::types::conversion::convert_value(
spec,
value_str,
py,
&custom_converters,
)?;
if let Some(original_name) = self.field_names.get(i).and_then(|n| n.as_ref()) {
if original_name.contains('[') {
let path = crate::parser::parse_field_path(original_name);
crate::parser::matching::insert_nested_dict(
&mut named, &path, converted, py,
)?;
} else {
if let Some(existing_value) = named.get(original_name.as_str()) {
let existing_obj = existing_value.clone_ref(py);
let converted_obj = converted.clone_ref(py);
let are_equal: bool =
existing_obj.bind(py).eq(converted_obj.bind(py))?;
if !are_equal {
return Err(error::repeated_name_error(original_name));
}
}
named.insert(original_name.clone(), converted);
}
} else {
fixed.push(converted);
}
}
}
let parse_result =
ParseResult::new_with_spans(fixed, named, self.span, self.field_spans.clone());
Py::new(py, parse_result)?.into_py_any(py)
}
}
impl Match {
pub fn new(init: MatchInit) -> Self {
Self {
pattern: init.pattern,
field_specs: init.field_specs,
field_names: init.field_names,
normalized_names: init.normalized_names,
captures: init.captures,
named_captures: init.named_captures,
span: init.span,
field_spans: init.field_spans,
}
}
pub fn with_offset(mut self, offset: usize) -> Self {
self.span = (self.span.0 + offset, self.span.1 + offset);
self.field_spans = self
.field_spans
.into_iter()
.map(|(k, (start, end))| (k, (start + offset, end + offset)))
.collect();
self
}
pub fn spans_as_char_indices(mut self, haystack: &str) -> Self {
self.span = (
byte_to_char_index(haystack, self.span.0),
byte_to_char_index(haystack, self.span.1),
);
self.field_spans = self
.field_spans
.into_iter()
.map(|(k, (start, end))| {
(
k,
(
byte_to_char_index(haystack, start),
byte_to_char_index(haystack, end),
),
)
})
.collect();
self
}
}