rust_rule_engine/parser/
parallel.rs1use super::simd_search;
2use super::zero_copy;
3use rayon::prelude::*;
14
15pub fn parse_rules_parallel(grl_text: &str) -> Vec<ParsedRule> {
20 let rule_slices = zero_copy::split_into_rules_zero_copy(grl_text);
22
23 rule_slices
25 .par_iter()
26 .filter_map(|rule| parse_single_rule(rule.text))
27 .collect()
28}
29
30pub fn parse_rules_parallel_simd(grl_text: &str) -> Vec<ParsedRule> {
32 let rule_slices = simd_search::split_into_rules_simd(grl_text);
34
35 rule_slices
37 .par_iter()
38 .filter_map(|rule_text| parse_single_rule(rule_text))
39 .collect()
40}
41
42#[derive(Debug, Clone)]
44pub struct ParsedRule {
45 pub name: String,
46 pub salience: Option<i32>,
47 pub condition: String,
48 pub action: String,
49 pub no_loop: bool,
50 pub lock_on_active: bool,
51}
52
53fn parse_single_rule(rule_text: &str) -> Option<ParsedRule> {
55 let header = zero_copy::parse_rule_header_zero_copy(rule_text)?;
57 let name = header.name.to_string();
58
59 let after_header = &rule_text[header.consumed..];
61 let attributes_end = after_header.find('{')?;
62 let attributes = &after_header[..attributes_end];
63
64 let salience = zero_copy::extract_salience_zero_copy(attributes);
66
67 let no_loop = zero_copy::has_attribute_zero_copy(attributes, "no-loop");
69 let lock_on_active = zero_copy::has_attribute_zero_copy(attributes, "lock-on-active");
70
71 let body_start = rule_text.find('{')?;
73 let body_end = simd_search::find_matching_brace_simd(rule_text, body_start)?;
74 let body = &rule_text[body_start + 1..body_end];
75
76 let when_then = zero_copy::parse_when_then_zero_copy(body)?;
78
79 Some(ParsedRule {
80 name,
81 salience,
82 condition: when_then.condition.to_string(),
83 action: when_then.action.to_string(),
84 no_loop,
85 lock_on_active,
86 })
87}
88
89pub fn parse_modules_and_rules_parallel(grl_text: &str) -> (Vec<ParsedModule>, Vec<ParsedRule>) {
93 let (module_texts, rules_text) = split_modules_and_rules(grl_text);
95
96 let (modules, rules) = rayon::join(
98 || parse_modules_parallel(&module_texts),
99 || parse_rules_parallel(&rules_text),
100 );
101
102 (modules, rules)
103}
104
105#[derive(Debug, Clone)]
107pub struct ParsedModule {
108 pub name: String,
109 pub export_policy: ExportPolicy,
110 pub imports: Vec<Import>,
111}
112
113#[derive(Debug, Clone, PartialEq)]
114pub enum ExportPolicy {
115 All,
116 None,
117 Specific(Vec<String>),
118}
119
120#[derive(Debug, Clone)]
121pub struct Import {
122 pub module_name: String,
123 pub pattern: Option<String>,
124}
125
126fn parse_modules_parallel(module_texts: &[String]) -> Vec<ParsedModule> {
128 module_texts
129 .par_iter()
130 .filter_map(|text| parse_single_module(text))
131 .collect()
132}
133
134fn parse_single_module(module_text: &str) -> Option<ParsedModule> {
136 let module = zero_copy::parse_module_zero_copy(module_text)?;
137 let name = module.name.to_string();
138 let body = module.body;
139
140 #[allow(clippy::if_same_then_else)]
142 let export_policy = if body.contains("export: all") {
143 ExportPolicy::All
144 } else if body.contains("export: none") {
145 ExportPolicy::None
146 } else {
147 ExportPolicy::None };
149
150 let imports = Vec::new(); Some(ParsedModule {
154 name,
155 export_policy,
156 imports,
157 })
158}
159
160fn split_modules_and_rules(grl_text: &str) -> (Vec<String>, String) {
163 let mut modules = Vec::new();
164 let mut rules_text = String::new();
165 let bytes = grl_text.as_bytes();
166 let mut i = 0;
167 let mut last_copy = 0;
168
169 while i < bytes.len() {
170 if let Some(offset) = memchr::memmem::find(&bytes[i..], b"defmodule ") {
171 let abs_pos = i + offset;
172
173 if abs_pos > last_copy {
175 rules_text.push_str(&grl_text[last_copy..abs_pos]);
176 }
177
178 if let Some(brace_offset) = memchr::memchr(b'{', &bytes[abs_pos..]) {
180 let brace_abs = abs_pos + brace_offset;
181
182 if let Some(close_pos) = simd_search::find_matching_brace_simd(grl_text, brace_abs)
184 {
185 let module_text = &grl_text[abs_pos..=close_pos];
186 modules.push(module_text.to_string());
187 i = close_pos + 1;
188 last_copy = i;
189 continue;
190 }
191 }
192 }
193 i += 1;
194 }
195
196 if last_copy < grl_text.len() {
198 rules_text.push_str(&grl_text[last_copy..]);
199 }
200
201 (modules, rules_text)
202}
203
204pub fn parse_rules_chunked_parallel(grl_text: &str, chunk_size: usize) -> Vec<ParsedRule> {
209 let rule_slices = zero_copy::split_into_rules_zero_copy(grl_text);
211
212 rule_slices
214 .par_chunks(chunk_size)
215 .flat_map(|chunk| {
216 chunk
217 .iter()
218 .filter_map(|rule| parse_single_rule(rule.text))
219 .collect::<Vec<_>>()
220 })
221 .collect()
222}
223
224pub fn parse_rules_adaptive(grl_text: &str) -> Vec<ParsedRule> {
231 let rule_count_estimate = grl_text.matches("rule ").count();
233
234 if rule_count_estimate < 10 {
235 let rule_slices = zero_copy::split_into_rules_zero_copy(grl_text);
237 rule_slices
238 .iter()
239 .filter_map(|rule| parse_single_rule(rule.text))
240 .collect()
241 } else if rule_count_estimate < 100 {
242 parse_rules_parallel(grl_text)
244 } else {
245 let chunk_size = (rule_count_estimate / rayon::current_num_threads()).max(10);
247 parse_rules_chunked_parallel(grl_text, chunk_size)
248 }
249}
250
251#[cfg(test)]
252mod tests {
253 use super::*;
254
255 #[test]
256 fn test_parse_single_rule() {
257 let rule = r#"rule "TestRule" salience 10 {
258 when X > 5
259 then Y = 10
260 }"#;
261
262 let parsed = parse_single_rule(rule).unwrap();
263 assert_eq!(parsed.name, "TestRule");
264 assert_eq!(parsed.salience, Some(10));
265 assert!(parsed.condition.contains("X > 5"));
266 assert!(parsed.action.contains("Y = 10"));
267 }
268
269 #[test]
270 fn test_parse_rules_parallel() {
271 let grl = r#"
272rule "Rule1" salience 10 { when X > 5 then Y = 10 }
273rule "Rule2" salience 20 { when A < 3 then B = 7 }
274rule "Rule3" { when C == 1 then D = 2 }
275 "#;
276
277 let rules = parse_rules_parallel(grl);
278 assert_eq!(rules.len(), 3);
279 assert_eq!(rules[0].name, "Rule1");
280 assert_eq!(rules[1].name, "Rule2");
281 assert_eq!(rules[2].name, "Rule3");
282 }
283
284 #[test]
285 fn test_parse_rules_parallel_simd() {
286 let grl = r#"
287rule "Rule1" { when X > 5 then Y = 10 }
288rule "Rule2" { when A < 3 then B = 7 }
289 "#;
290
291 let rules = parse_rules_parallel_simd(grl);
292 assert_eq!(rules.len(), 2);
293 }
294
295 #[test]
296 fn test_parse_with_no_loop() {
297 let rule = r#"rule "TestRule" no-loop {
298 when X > 5
299 then Y = 10
300 }"#;
301
302 let parsed = parse_single_rule(rule).unwrap();
303 assert!(parsed.no_loop);
304 assert!(!parsed.lock_on_active);
305 }
306
307 #[test]
308 fn test_parse_chunked_parallel() {
309 let mut grl = String::new();
310 for i in 0..50 {
311 grl.push_str(&format!(
312 r#"rule "Rule{}" {{ when X > {} then Y = {} }}"#,
313 i,
314 i,
315 i * 2
316 ));
317 grl.push('\n');
318 }
319
320 let rules = parse_rules_chunked_parallel(&grl, 10);
321 assert_eq!(rules.len(), 50);
322 }
323
324 #[test]
325 fn test_adaptive_parsing_small() {
326 let grl = r#"
327rule "Rule1" { when X > 5 then Y = 10 }
328rule "Rule2" { when A < 3 then B = 7 }
329 "#;
330
331 let rules = parse_rules_adaptive(grl);
332 assert_eq!(rules.len(), 2);
333 }
334
335 #[test]
336 fn test_adaptive_parsing_large() {
337 let mut grl = String::new();
338 for i in 0..150 {
339 grl.push_str(&format!(
340 r#"rule "Rule{}" {{ when X > {} then Y = {} }}"#,
341 i,
342 i,
343 i * 2
344 ));
345 grl.push('\n');
346 }
347
348 let rules = parse_rules_adaptive(&grl);
349 assert_eq!(rules.len(), 150);
350 }
351
352 #[test]
353 fn test_parse_module() {
354 let module_text = r#"defmodule MYMODULE {
355 export: all
356 }"#;
357
358 let module = parse_single_module(module_text).unwrap();
359 assert_eq!(module.name, "MYMODULE");
360 assert_eq!(module.export_policy, ExportPolicy::All);
361 }
362
363 #[test]
364 fn test_parse_modules_and_rules_parallel() {
365 let grl = r#"
366defmodule MODULE1 { export: all }
367rule "Rule1" { when X > 5 then Y = 10 }
368defmodule MODULE2 { export: none }
369rule "Rule2" { when A < 3 then B = 7 }
370 "#;
371
372 let (modules, rules) = parse_modules_and_rules_parallel(grl);
373 assert_eq!(modules.len(), 2);
374 assert_eq!(rules.len(), 2);
375 }
376}