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