flux_verify_api/compiler/
parser.rs1pub fn extract_number_before(text: &str, keyword: &str) -> Option<f64> {
5 let idx = text.find(keyword)?;
6 let prefix = &text[..idx];
7 let num_str = prefix
8 .rsplit(|c: char| !c.is_ascii_digit() && c != '.' && c != '-')
9 .next()?;
10 num_str.parse().ok()
11}
12
13pub fn extract_number_near(text: &str, keyword: &str) -> Option<f64> {
15 let idx = text.find(keyword)?;
16 let start = if idx > 30 { idx - 30 } else { 0 };
17 let window = &text[start..idx];
18 let parts: Vec<&str> = window.split(|c: char| !c.is_ascii_digit() && c != '.' && c != '-')
20 .filter(|s| !s.is_empty())
21 .collect();
22 parts.last().and_then(|s| s.parse().ok())
23}
24
25pub fn extract_number_with_unit<'a>(text: &str, units: &[&'a str]) -> Option<f64> {
27 for unit in units {
28 for part in text.split_whitespace() {
29 if part.ends_with(unit) {
30 let num_str = &part[..part.len() - unit.len()];
31 if let Ok(v) = num_str.parse() {
32 return Some(v);
33 }
34 }
35 }
36 }
37 None
38}
39
40pub fn extract_range(text: &str) -> Option<(f64, f64)> {
42 if let Some(rest) = text.strip_prefix("between ") {
44 let parts: Vec<&str> = rest.split(" and ").collect();
45 if parts.len() == 2 {
46 let a = parts[0].trim().parse::<f64>().ok()?;
47 let b = parts[1].split_whitespace().next()?.parse::<f64>().ok()?;
48 return Some((a.min(b), a.max(b)));
49 }
50 }
51
52 if let Some(idx) = text.find("safe range") {
55 let rest = &text[idx + 10..];
56 let clean = rest.replace("°c", " ").replace("°", " ").replace("celsius", " ");
58 if let Some(range) = extract_range_from_text(clean.trim()) {
59 return Some(range);
60 }
61 }
62 if let Some(idx) = text.find("range of ") {
63 let rest = &text[idx + 9..];
64 let clean = rest.replace("°c", " ").replace("°", " ").replace("celsius", " ");
65 if let Some(range) = extract_range_from_text(clean.trim()) {
66 return Some(range);
67 }
68 }
69
70 for pattern in &["from "] {
71 if let Some(idx) = text.find(pattern) {
72 let rest = &text[idx + pattern.len()..];
73 let parts: Vec<&str> = rest.split(" to ").collect();
74 if parts.len() >= 2 {
75 let a = parts[0].trim().parse::<f64>().ok()?;
76 let b = parts[1].split_whitespace().next()?.parse::<f64>().ok()?;
77 return Some((a.min(b), a.max(b)));
78 }
79 }
80 }
81
82 None
83}
84
85fn extract_range_from_text(text: &str) -> Option<(f64, f64)> {
86 let parts: Vec<&str> = text.split(" to ").collect();
87 if parts.len() >= 2 {
88 let a = parts[0].trim().parse::<f64>().ok()?;
89 let b = parts[1].split(|c: char| !c.is_ascii_digit() && c != '.' && c != '-')
90 .next()?
91 .parse::<f64>()
92 .ok()?;
93 return Some((a.min(b), a.max(b)));
94 }
95 let parts: Vec<&str> = text.split(" - ").collect();
97 if parts.len() >= 2 {
98 let a = parts[0].trim().parse::<f64>().ok()?;
99 let b = parts[1].split(|c: char| !c.is_ascii_digit() && c != '.' && c != '-')
100 .next()?
101 .parse::<f64>()
102 .ok()?;
103 return Some((a.min(b), a.max(b)));
104 }
105 None
106}
107
108pub fn extract_comparison(text: &str) -> Option<(f64, String, f64, String)> {
110 for op in &[">=", "<=", ">", "<", "==", "="] {
112 if let Some(idx) = text.find(op) {
113 let left_str = text[..idx].trim();
114 let right_str = text[idx + op.len()..].trim();
115 let left = left_str.split_whitespace().last()?.parse::<f64>().ok()?;
116 let right = right_str.split_whitespace().next()?.parse::<f64>().ok()?;
117 let op_name = match *op {
118 ">=" => "gte",
119 "<=" => "lte",
120 ">" => "gt",
121 "<" => "lt",
122 "==" | "=" => "eq",
123 _ => "gt",
124 };
125 return Some((left, op_name.to_string(), right, text.to_string()));
126 }
127 }
128
129 let patterns = [
131 ("greater than or equal to", "gte"),
132 ("less than or equal to", "lte"),
133 ("at least", "gte"),
134 ("at most", "lte"),
135 ("greater than", "gt"),
136 ("less than", "lt"),
137 ("equal to", "eq"),
138 ("equals", "eq"),
139 ("is above", "gt"),
140 ("is below", "lt"),
141 ];
142
143 for (phrase, op) in &patterns {
144 if let Some(idx) = text.find(phrase) {
145 let left_str = text[..idx].trim();
146 let right_str = text[idx + phrase.len()..].trim();
147 let left = extract_trailing_number(left_str)?;
149 let right = extract_leading_number(right_str)?;
150 return Some((left, op.to_string(), right, text.to_string()));
151 }
152 }
153
154 None
155}
156
157fn extract_trailing_number(text: &str) -> Option<f64> {
158 let parts: Vec<&str> = text.split(|c: char| !c.is_ascii_digit() && c != '.' && c != '-')
159 .filter(|s| !s.is_empty())
160 .collect();
161 parts.last().and_then(|s| s.parse().ok())
162}
163
164fn extract_leading_number(text: &str) -> Option<f64> {
165 let parts: Vec<&str> = text.split(|c: char| !c.is_ascii_digit() && c != '.' && c != '-')
166 .filter(|s| !s.is_empty())
167 .collect();
168 parts.first().and_then(|s| s.parse().ok())
169}
170
171pub fn extract_range_check(text: &str) -> Option<(f64, f64, f64, String)> {
173 if let Some(idx) = text.find("between ") {
174 let rest = &text[idx + 8..];
175 let parts: Vec<&str> = rest.split(" and ").collect();
176 if parts.len() >= 2 {
177 let min = parts[0].trim().parse::<f64>().ok()?;
178 let right = parts[1].split_whitespace().collect::<Vec<_>>();
179 let max = right.get(0)?.parse::<f64>().ok()?;
180 let prefix = &text[..idx];
182 let value = extract_trailing_number(prefix)?;
183 return Some((value, min, max, text.to_string()));
184 }
185 }
186 for phrase in &["within ", "in "] {
188 if let Some(idx) = text.find(phrase) {
189 let rest = &text[idx + phrase.len()..];
190 let clean = rest.trim_start_matches(|c| c == '[' || c == '(');
191 let parts: Vec<&str> = clean.split(|c| c == ',' || c == ']').collect();
192 if parts.len() >= 2 {
193 let min = parts[0].trim().parse::<f64>().ok()?;
194 let max = parts[1].trim().parse::<f64>().ok()?;
195 let prefix = &text[..idx];
196 let value = extract_trailing_number(prefix)?;
197 return Some((value, min, max, text.to_string()));
198 }
199 }
200 }
201 None
202}
203
204pub fn extract_bound(text: &str) -> Option<(f64, f64, f64, String)> {
206 if let Some(idx) = text.find("within ") {
207 let rest = &text[idx + 7..];
208 let parts: Vec<&str> = rest.split(" of ").collect();
209 if parts.len() >= 2 {
210 let tolerance = parts[0].trim().parse::<f64>().ok()?;
211 let center = extract_leading_number(parts[1])?;
212 let prefix = &text[..idx];
213 let value = extract_trailing_number(prefix)?;
214 return Some((value, center - tolerance, center + tolerance, text.to_string()));
215 }
216 }
217 None
218}