1use crate::error;
2use crate::result::ParseResult;
3use crate::types::FieldSpec;
4use pyo3::prelude::*;
5use pyo3::IntoPyObjectExt;
6use std::collections::HashMap;
7
8pub struct MatchInit {
10 pub pattern: String,
11 pub field_specs: Vec<FieldSpec>,
12 pub field_names: Vec<Option<String>>,
13 pub normalized_names: Vec<Option<String>>,
14 pub captures: Vec<Option<String>>,
15 pub named_captures: HashMap<String, String>,
16 pub span: (usize, usize),
17 pub field_spans: HashMap<String, (usize, usize)>,
18}
19
20#[pyclass(skip_from_py_object)]
22#[derive(Clone)]
23pub struct Match {
24 #[pyo3(get)]
25 pattern: String,
26 field_specs: Vec<FieldSpec>,
27 field_names: Vec<Option<String>>,
28 normalized_names: Vec<Option<String>>,
29 captures: Vec<Option<String>>, named_captures: HashMap<String, String>, #[pyo3(get)]
32 pub span: (usize, usize),
33 field_spans: HashMap<String, (usize, usize)>, }
35
36#[pymethods]
37impl Match {
38 #[pyo3(signature = (*, extra_types=None))]
40 fn evaluate_result(
41 &self,
42 py: Python,
43 extra_types: Option<HashMap<String, Py<PyAny>>>,
44 ) -> PyResult<Py<PyAny>> {
45 let custom_converters = extra_types.unwrap_or_default();
46 let mut fixed = Vec::new();
47 let mut named: HashMap<String, Py<PyAny>> = HashMap::new();
48
49 for (i, spec) in self.field_specs.iter().enumerate() {
51 let value_str =
52 if let Some(norm_name) = self.normalized_names.get(i).and_then(|n| n.as_ref()) {
53 self.named_captures
54 .get(norm_name.as_str())
55 .map(|s| s.as_str())
56 } else {
57 self.captures
58 .get(i)
59 .and_then(|s| s.as_ref())
60 .map(|s| s.as_str())
61 };
62
63 if let Some(value_str) = value_str {
64 if !crate::types::conversion::validate_alignment_precision_for_capture(
65 spec, value_str,
66 ) {
67 return Err(pyo3::exceptions::PyValueError::new_err(
68 "capture failed alignment/precision validation",
69 ));
70 }
71 let converted = crate::types::conversion::convert_value(
72 spec,
73 value_str,
74 py,
75 &custom_converters,
76 )?;
77
78 if let Some(original_name) = self.field_names.get(i).and_then(|n| n.as_ref()) {
79 if original_name.contains('[') {
81 let path = crate::parser::parse_field_path(original_name);
83 crate::parser::matching::insert_nested_dict(
84 &mut named, &path, converted, py,
85 )?;
86 } else {
87 if let Some(existing_value) = named.get(original_name.as_str()) {
90 let existing_obj = existing_value.clone_ref(py);
91 let converted_obj = converted.clone_ref(py);
92 let are_equal: bool =
93 existing_obj.bind(py).eq(converted_obj.bind(py))?;
94 if !are_equal {
95 return Err(error::repeated_name_error(original_name));
96 }
97 }
98 named.insert(original_name.clone(), converted);
99 }
100 } else {
101 fixed.push(converted);
102 }
103 }
104 }
105
106 let parse_result =
107 ParseResult::new_with_spans(fixed, named, self.span, self.field_spans.clone());
108 Py::new(py, parse_result)?.into_py_any(py)
110 }
111}
112
113impl Match {
114 pub fn new(init: MatchInit) -> Self {
115 Self {
116 pattern: init.pattern,
117 field_specs: init.field_specs,
118 field_names: init.field_names,
119 normalized_names: init.normalized_names,
120 captures: init.captures,
121 named_captures: init.named_captures,
122 span: init.span,
123 field_spans: init.field_spans,
124 }
125 }
126
127 pub fn with_offset(mut self, offset: usize) -> Self {
128 self.span = (self.span.0 + offset, self.span.1 + offset);
129 self.field_spans = self
130 .field_spans
131 .into_iter()
132 .map(|(k, (start, end))| (k, (start + offset, end + offset)))
133 .collect();
134 self
135 }
136}