_formatparse/parser/
findall_iter.rs1use crate::parser::format_parser::FormatParser;
2use crate::parser::matching::{match_with_captures, match_with_captures_raw, CapturedMatchContext};
3use formatparse_core::FieldType;
4use pyo3::prelude::*;
5use pyo3::IntoPyObjectExt;
6use std::collections::HashMap;
7use std::sync::Arc;
8
9#[pyclass(module = "_formatparse", name = "FindallIter")]
17pub struct FindallIter {
18 parser: Arc<FormatParser>,
19 haystack: String,
20 case_sensitive: bool,
21 evaluate_result: bool,
22 fast_path: bool,
23 extra_types: HashMap<String, Py<PyAny>>,
24 max_matches: Option<usize>,
25 matches_emitted: usize,
26 last_end: usize,
27 search_pos: usize,
28}
29
30impl FindallIter {
31 pub fn new(
32 parser: Arc<FormatParser>,
33 haystack: String,
34 case_sensitive: bool,
35 evaluate_result: bool,
36 extra_types: HashMap<String, Py<PyAny>>,
37 max_matches: Option<usize>,
38 ) -> Self {
39 let has_custom_converters = !extra_types.is_empty();
40 let has_nested_dicts = parser.fields.has_nested_dict_fields.iter().any(|&b| b);
41 let has_nested_format_fields = parser
42 .fields
43 .field_specs
44 .iter()
45 .any(|s| matches!(s.field_type, FieldType::Nested));
46 let fast_path = !has_custom_converters
47 && evaluate_result
48 && !has_nested_dicts
49 && !has_nested_format_fields;
50 Self {
51 parser,
52 haystack,
53 case_sensitive,
54 evaluate_result,
55 fast_path,
56 extra_types,
57 max_matches,
58 matches_emitted: 0,
59 last_end: 0,
60 search_pos: 0,
61 }
62 }
63
64 fn at_match_limit(&self) -> bool {
65 self.max_matches
66 .map(|m| self.matches_emitted >= m)
67 .unwrap_or(false)
68 }
69}
70
71#[pymethods]
72impl FindallIter {
73 fn __iter__(slf: PyRef<'_, Self>) -> PyRef<'_, Self> {
74 slf
75 }
76
77 fn __next__(mut slf: PyRefMut<'_, Self>, py: Python<'_>) -> PyResult<Option<Py<PyAny>>> {
78 if slf.at_match_limit() {
79 return Ok(None);
80 }
81 if slf.fast_path {
82 loop {
83 if slf.at_match_limit() || slf.search_pos > slf.haystack.len() {
84 return Ok(None);
85 }
86 let search_regex = slf.parser.get_search_regex(slf.case_sensitive);
87 let Some(caps) = search_regex
88 .captures_from_pos(&slf.haystack, slf.search_pos)
89 .map_err(crate::error::fancy_regex_match_error)?
90 else {
91 return Ok(None);
92 };
93 let Some(m0) = caps.get(0) else {
94 return Err(pyo3::exceptions::PyRuntimeError::new_err(
95 "regex match missing capture group 0",
96 ));
97 };
98 let match_start = m0.start();
99 let match_end = m0.end();
100
101 if match_start < slf.last_end {
102 slf.search_pos = slf.last_end.max(match_start.saturating_add(1));
103 continue;
104 }
105
106 let slices = slf.parser.fields.capture_slices();
107
108 match match_with_captures_raw(&caps, &slf.haystack, match_start, &slices) {
109 Ok(Some(raw_data)) => {
110 slf.last_end = match_end;
111 if match_start == match_end {
112 slf.last_end += 1;
113 }
114 slf.search_pos = slf.last_end;
115 slf.matches_emitted += 1;
116 let pr = raw_data.to_parse_result(py)?;
117 return Ok(Some(pr.into_py_any(py)?));
118 }
119 Ok(None) => {
120 slf.search_pos = match_start.saturating_add(1);
121 continue;
122 }
123 Err(_) => {
124 slf.fast_path = false;
125 if slf.last_end == 0 {
126 slf.search_pos = 0;
127 }
128 break;
129 }
130 }
131 }
132 }
133
134 loop {
135 if slf.at_match_limit() || slf.search_pos > slf.haystack.len() {
136 return Ok(None);
137 }
138 let search_regex = slf.parser.get_search_regex(slf.case_sensitive);
139 let Some(caps) = search_regex
140 .captures_from_pos(&slf.haystack, slf.search_pos)
141 .map_err(crate::error::fancy_regex_match_error)?
142 else {
143 return Ok(None);
144 };
145 let Some(m0) = caps.get(0) else {
146 return Err(pyo3::exceptions::PyRuntimeError::new_err(
147 "regex match missing capture group 0",
148 ));
149 };
150 let match_start = m0.start();
151 let match_end = m0.end();
152
153 if match_start < slf.last_end {
154 slf.search_pos = slf.last_end.max(match_start.saturating_add(1));
155 continue;
156 }
157
158 let ctx = CapturedMatchContext {
159 pattern: &slf.parser.pattern,
160 fields: slf.parser.fields.capture_slices(),
161 py,
162 custom_converters: &slf.extra_types,
163 evaluate_result: slf.evaluate_result,
164 };
165
166 match match_with_captures(&caps, &ctx)? {
167 Some(result) => {
168 slf.last_end = match_end;
169 if match_start == match_end {
170 slf.last_end += 1;
171 }
172 slf.search_pos = slf.last_end;
173 slf.matches_emitted += 1;
174 return Ok(Some(result));
175 }
176 None => {
177 slf.search_pos = match_start.saturating_add(1);
178 continue;
179 }
180 }
181 }
182 }
183
184 fn __repr__(&self) -> String {
185 "<FindallIter>".to_string()
186 }
187}