1use crate::arrays::drop_common;
11use crate::ast::{AnchorKind, GroupBehavior, NodeKind, RcNode};
12use crate::parse::parse;
13use std::collections::HashSet;
14
15#[derive(Clone, Debug)]
17pub struct DowngradedRegexPattern {
18 pub atomic_group_offsets: HashSet<usize>,
20 pub pattern: String,
22}
23
24#[derive(Clone, Copy, PartialEq, Eq)]
26enum AtomicOrOptional {
27 Atomic,
28 Optional,
29 None,
30}
31
32struct Action {
33 atomic_or_optional: AtomicOrOptional,
34 group: RcNode,
35 reference: RcNode,
36 reference_start: usize,
37 reference_end: usize,
38}
39
40fn utf16_slice_replace(input: &str, replacement: &str, start: usize, end: usize) -> String {
41 let units: Vec<u16> = input.encode_utf16().collect();
42 let mut out: Vec<u16> = Vec::new();
43 out.extend_from_slice(&units[..start]);
44 out.extend(replacement.encode_utf16());
45 out.extend_from_slice(&units[end..]);
46 String::from_utf16_lossy(&out)
47}
48
49fn shift_offsets(offsets: &HashSet<usize>, after: i64, shift_amount: i64) -> HashSet<usize> {
50 offsets
51 .iter()
52 .map(|&offset| {
53 if (offset as i64) > after {
54 (offset as i64 + shift_amount) as usize
55 } else {
56 offset
57 }
58 })
59 .collect()
60}
61
62fn quantifier_iterations_to_string(node: &RcNode) -> String {
63 if let NodeKind::Quantifier {
64 min,
65 max,
66 greedy,
67 symbol,
68 ..
69 } = &node.kind
70 {
71 if let Some(sym) = symbol {
72 return format!("{}{}", sym, if *greedy { "" } else { "?" });
73 }
74 if max.map(|m| m == *min).unwrap_or(false) {
75 return format!("{{{}}}", min);
76 }
77 let max_str = max.map(|m| m.to_string()).unwrap_or_default();
78 return format!("{{{},{}}}{}", min, max_str, if *greedy { "" } else { "?" });
79 }
80 String::new()
81}
82
83pub(crate) struct RawWithoutGroups {
85 pub(crate) references_with_offset: Vec<(RcNode, usize)>,
86 pub(crate) result: String,
87}
88
89pub(crate) fn get_raw_without_capturing_groups_or_lookaheads(root: &RcNode) -> RawWithoutGroups {
92 let mut references_with_offset: Vec<(RcNode, usize)> = Vec::new();
93 let result = walk(root, 0, &mut references_with_offset);
94 RawWithoutGroups {
95 references_with_offset,
96 result,
97 }
98}
99
100fn walk(node: &RcNode, offset: usize, refs: &mut Vec<(RcNode, usize)>) -> String {
101 let walk_all = |nodes: &[RcNode], start_offset: usize, refs: &mut Vec<(RcNode, usize)>| {
102 let mut result = String::new();
103 for n in nodes {
104 let len = result.encode_utf16().count();
105 result.push_str(&walk(n, start_offset + len, refs));
106 }
107 result
108 };
109
110 match &node.kind {
111 NodeKind::Anchor { .. }
112 | NodeKind::CharacterClass { .. }
113 | NodeKind::CharacterClassEscape { .. }
114 | NodeKind::UnicodePropertyEscape { .. }
115 | NodeKind::Value { .. }
116 | NodeKind::Dot => node.raw.clone(),
117 NodeKind::Reference { .. } => {
118 refs.push((node.clone(), offset));
119 node.raw.clone()
120 }
121 NodeKind::Group { behavior, body } => match behavior {
122 GroupBehavior::Normal | GroupBehavior::Ignore => {
123 format!("(?:{})", walk_all(body, offset + 3, refs))
124 }
125 _ => String::new(),
126 },
127 NodeKind::Disjunction { body } => {
128 let mut res = String::new();
129 for (i, child) in body.iter().enumerate() {
130 if i > 0 {
131 res.push('|');
132 }
133 let len = res.encode_utf16().count();
134 res.push_str(&walk(child, offset + len, refs));
135 }
136 res
137 }
138 NodeKind::Alternative { body } => walk_all(body, offset, refs),
139 NodeKind::Quantifier { body, .. } => {
140 format!(
141 "{}{}",
142 walk(body, offset, refs),
143 quantifier_iterations_to_string(node)
144 )
145 }
146 NodeKind::CharacterClassRange { .. } => node.raw.clone(),
147 }
148}
149
150fn already_has_start_anchor_replacement(node: &RcNode) -> bool {
151 let body = match &node.kind {
152 NodeKind::Alternative { body } => body,
153 _ => return false,
154 };
155 if body.is_empty() {
156 return false;
157 }
158 let maybe_quantifier = &body[0];
159 let (min, max, greedy, q_body) = match &maybe_quantifier.kind {
160 NodeKind::Quantifier {
161 min,
162 max,
163 greedy,
164 body,
165 ..
166 } => (*min, *max, *greedy, body),
167 _ => return false,
168 };
169 if min != 0 || max.is_some() || greedy {
170 return false;
171 }
172 match &q_body.kind {
173 NodeKind::CharacterClass { negative, body } => body.is_empty() && *negative,
174 _ => false,
175 }
176}
177
178pub fn is_missing_start_anchor(root: &RcNode) -> bool {
180 if already_has_start_anchor_replacement(root) {
181 return false;
182 }
183 check_anchor(root) == AnchorCheck::ConsumingNode
184}
185
186#[derive(PartialEq, Eq, Clone, Copy)]
187enum AnchorCheck {
188 Anchor,
189 ConsumingNode,
190 Null,
191}
192
193fn check_anchor(node: &RcNode) -> AnchorCheck {
194 match &node.kind {
195 NodeKind::Anchor { kind } => {
196 if *kind == AnchorKind::Start {
197 AnchorCheck::Anchor
198 } else {
199 AnchorCheck::Null
200 }
201 }
202 NodeKind::Quantifier { max, body, min, .. } => {
203 if *max == Some(0) {
204 return AnchorCheck::Null;
205 }
206 let may_be_skipped = *min == 0;
207 check_children(std::slice::from_ref(body), may_be_skipped)
208 }
209 NodeKind::Alternative { body } | NodeKind::Group { body, .. } => {
210 check_children(body, false)
211 }
212 NodeKind::Disjunction { body } => {
213 let results: Vec<AnchorCheck> = body.iter().map(check_anchor).collect();
214 if results.contains(&AnchorCheck::ConsumingNode) {
215 AnchorCheck::ConsumingNode
216 } else if results.iter().all(|r| *r == AnchorCheck::Anchor) {
217 AnchorCheck::Anchor
218 } else {
219 AnchorCheck::Null
220 }
221 }
222 _ => AnchorCheck::ConsumingNode,
223 }
224}
225
226fn check_children(body: &[RcNode], may_be_skipped: bool) -> AnchorCheck {
227 for child in body {
228 let res = check_anchor(child);
229 if res == AnchorCheck::ConsumingNode {
230 return AnchorCheck::ConsumingNode;
231 }
232 if res == AnchorCheck::Anchor {
233 if !may_be_skipped {
234 return AnchorCheck::Anchor;
235 }
236 return AnchorCheck::Null;
237 }
238 }
239 AnchorCheck::Null
240}
241
242pub fn downgrade_pattern(
247 pattern: &str,
248 unicode: bool,
249) -> Result<DowngradedRegexPattern, crate::Error> {
250 let mut last = DowngradedRegexPattern {
251 atomic_group_offsets: HashSet::new(),
252 pattern: pattern.to_string(),
253 };
254 loop {
255 let (need_rerun, result) = run(&last, unicode)?;
256 last = result;
257 if !need_rerun {
258 break;
259 }
260 }
261 Ok(last)
262}
263
264struct GroupEntry {
265 group: RcNode,
266 stack: Vec<RcNode>,
267}
268
269struct Walker {
270 actions: Vec<Action>,
271 needs_start_anchor_replacement: bool,
272 groups: std::collections::HashMap<u64, GroupEntry>,
273 infinite_groups: HashSet<usize>,
274 next_group_index: u64,
275}
276
277fn lookahead_only_contains_group(lookahead: &RcNode, group: &RcNode) -> bool {
278 if let NodeKind::Group { body, .. } = &lookahead.kind {
279 body.len() == 1 && body[0].id == group.id
280 } else {
281 false
282 }
283}
284
285fn run(
286 last: &DowngradedRegexPattern,
287 unicode: bool,
288) -> Result<(bool, DowngradedRegexPattern), crate::Error> {
289 let ast = parse(&last.pattern, unicode).map_err(|e| crate::Error::Parse(e.0))?;
290 let needs_wrapping_in_group = matches!(ast.kind, NodeKind::Disjunction { .. });
291
292 let mut walker = Walker {
293 actions: Vec::new(),
294 needs_start_anchor_replacement: false,
295 groups: std::collections::HashMap::new(),
296 infinite_groups: HashSet::new(),
297 next_group_index: 1,
298 };
299
300 let mut passed = false;
301 walk_downgrade(&ast, &[], &mut passed, None, &mut walker);
302
303 let mut new_pattern = last.pattern.clone();
304 let mut atomic_group_offsets = last.atomic_group_offsets.clone();
305 let mut need_to_rerun = false;
306
307 let mut order: Vec<usize> = (0..walker.actions.len()).collect();
310 order.sort_by(|&a, &b| {
311 walker.actions[b]
312 .reference_start
313 .cmp(&walker.actions[a].reference_start)
314 });
315
316 for &idx in &order {
317 let reference_start = walker.actions[idx].reference_start;
318 let reference_end = walker.actions[idx].reference_end;
319 let atomic_or_optional = walker.actions[idx].atomic_or_optional;
320 let raw = get_raw_without_capturing_groups_or_lookaheads(&walker.actions[idx].group);
321
322 if !raw.references_with_offset.is_empty() {
323 need_to_rerun = true;
324 }
325
326 let replacement = if atomic_or_optional == AtomicOrOptional::Optional {
327 format!("(?:{}?)", raw.result)
328 } else {
329 raw.result.clone()
330 };
331
332 new_pattern =
333 utf16_slice_replace(&new_pattern, &replacement, reference_start, reference_end);
334
335 let shift_amount =
336 replacement.encode_utf16().count() as i64 - (reference_end - reference_start) as i64;
337 atomic_group_offsets =
338 shift_offsets(&atomic_group_offsets, reference_start as i64, shift_amount);
339
340 if atomic_or_optional == AtomicOrOptional::Atomic {
341 atomic_group_offsets.insert(reference_start);
342 }
343
344 for inner in &walker.actions {
347 if inner.atomic_or_optional == AtomicOrOptional::Atomic {
348 if let Some((_, offset)) = raw
349 .references_with_offset
350 .iter()
351 .find(|(r, _)| r.id == inner.reference.id)
352 {
353 atomic_group_offsets.insert(reference_start + offset);
354 }
355 }
356 }
357 }
358
359 if walker.needs_start_anchor_replacement && !already_has_start_anchor_replacement(&ast) {
360 if needs_wrapping_in_group {
361 new_pattern = format!("(?:{})", new_pattern);
362 }
363 new_pattern = format!("[^]*?{}", new_pattern);
364 let shift = if needs_wrapping_in_group { 8 } else { 5 };
365 atomic_group_offsets = shift_offsets(&atomic_group_offsets, -1, shift);
366 }
367
368 Ok((
369 need_to_rerun,
370 DowngradedRegexPattern {
371 atomic_group_offsets,
372 pattern: new_pattern,
373 },
374 ))
375}
376
377#[allow(clippy::too_many_arguments)]
378fn walk_downgrade(
379 node: &RcNode,
380 node_stack: &[RcNode],
381 passed_start_anchor: &mut bool,
382 immediately_preceding_lookahead: Option<&RcNode>,
383 walker: &mut Walker,
384) {
385 let on_consuming_node = |passed: &bool, walker: &mut Walker| {
386 if !passed {
387 walker.needs_start_anchor_replacement = true;
388 }
389 };
390
391 match &node.kind {
392 NodeKind::CharacterClass { .. }
393 | NodeKind::CharacterClassEscape { .. }
394 | NodeKind::UnicodePropertyEscape { .. }
395 | NodeKind::Value { .. }
396 | NodeKind::Dot => {
397 on_consuming_node(passed_start_anchor, walker);
398 }
399 NodeKind::Anchor { kind } => {
400 if *kind == AnchorKind::Start {
401 *passed_start_anchor = true;
402 }
403 }
404 NodeKind::Group { behavior, body } => {
405 let group_index = if *behavior == GroupBehavior::Normal {
406 let idx = walker.next_group_index;
407 walker.next_group_index += 1;
408 Some(idx)
409 } else {
410 None
411 };
412
413 let mut new_stack = node_stack.to_vec();
414 new_stack.push(node.clone());
415 walk_all_serial(body, &new_stack, passed_start_anchor, walker);
416
417 if let Some(idx) = group_index {
418 walker.groups.insert(
419 idx,
420 GroupEntry {
421 group: node.clone(),
422 stack: node_stack.to_vec(),
423 },
424 );
425 }
426 }
427 NodeKind::Disjunction { body } => {
428 let mut new_stack = node_stack.to_vec();
429 new_stack.push(node.clone());
430 walk_all_parallel(body, &new_stack, passed_start_anchor, walker);
431 }
432 NodeKind::Alternative { body } => {
433 let mut new_stack = node_stack.to_vec();
434 new_stack.push(node.clone());
435 walk_all_serial(body, &new_stack, passed_start_anchor, walker);
436 }
437 NodeKind::Quantifier { min, max, body, .. } => {
438 if *max != Some(0) {
439 if max.is_none() {
440 for stack_node in node_stack.iter().rev() {
441 if let NodeKind::Group { behavior, .. } = &stack_node.kind {
442 if behavior.is_lookaround() {
443 break;
444 }
445 walker.infinite_groups.insert(stack_node.id);
446 }
447 }
448 }
449 let mut new_stack = node_stack.to_vec();
450 new_stack.push(node.clone());
451 if *min == 0 {
452 let mut copy = *passed_start_anchor;
453 walk_all_serial(std::slice::from_ref(body), &new_stack, &mut copy, walker);
454 } else {
455 walk_all_serial(
456 std::slice::from_ref(body),
457 &new_stack,
458 passed_start_anchor,
459 walker,
460 );
461 }
462 }
463 }
464 NodeKind::Reference { match_index } => {
465 on_consuming_node(passed_start_anchor, walker);
466 if let Some(entry) = walker.groups.get(match_index) {
467 let group = entry.group.clone();
468 let group_stack = entry.stack.clone();
469 let group_stack_ids: Vec<usize> = group_stack.iter().map(|n| n.id).collect();
470 let node_stack_ids: Vec<usize> = node_stack.iter().map(|n| n.id).collect();
471 let (local_ids, _) = drop_common(&group_stack_ids, &node_stack_ids);
472 let local_stack: Vec<RcNode> = group_stack
473 .iter()
474 .filter(|n| local_ids.contains(&n.id))
475 .cloned()
476 .collect();
477
478 let lookahead_stack: Vec<RcNode> = local_stack
479 .iter()
480 .filter(|n| {
481 matches!(&n.kind, NodeKind::Group { behavior, .. } if behavior.is_lookaround())
482 })
483 .cloned()
484 .collect();
485 let group_may_not_be_reached = local_stack.iter().any(|n| match &n.kind {
486 NodeKind::Disjunction { .. } => true,
487 NodeKind::Quantifier { min, .. } => *min == 0,
488 _ => false,
489 });
490 let group_in_lookahead = !lookahead_stack.is_empty();
491 let group_could_be_set = lookahead_stack.iter().all(|n| {
492 matches!(&n.kind, NodeKind::Group { behavior, .. } if !behavior.is_negative())
493 });
494
495 if group_could_be_set
496 && (group_in_lookahead || walker.infinite_groups.contains(&group.id))
497 {
498 let atomic = immediately_preceding_lookahead
499 .map(|lh| lookahead_only_contains_group(lh, &group))
500 .unwrap_or(false);
501 let optional = group_in_lookahead && group_may_not_be_reached;
502 let atomic_or_optional = if atomic {
503 AtomicOrOptional::Atomic
504 } else if optional {
505 AtomicOrOptional::Optional
506 } else {
507 AtomicOrOptional::None
508 };
509 walker.actions.push(Action {
510 atomic_or_optional,
511 group,
512 reference: node.clone(),
513 reference_start: node.range.0,
514 reference_end: node.range.1,
515 });
516 }
517 }
518 }
519 NodeKind::CharacterClassRange { .. } => {}
520 }
521}
522
523fn walk_all_serial(
524 nodes: &[RcNode],
525 node_stack: &[RcNode],
526 passed_start_anchor: &mut bool,
527 walker: &mut Walker,
528) {
529 let mut just_had_lookahead: Option<RcNode> = None;
530 for expression in nodes {
531 walk_downgrade(
532 expression,
533 node_stack,
534 passed_start_anchor,
535 just_had_lookahead.as_ref(),
536 walker,
537 );
538 just_had_lookahead = match &expression.kind {
539 NodeKind::Group {
540 behavior: GroupBehavior::Lookahead,
541 ..
542 } => Some(expression.clone()),
543 _ => None,
544 };
545 }
546}
547
548fn walk_all_parallel(
549 nodes: &[RcNode],
550 node_stack: &[RcNode],
551 passed_start_anchor: &mut bool,
552 walker: &mut Walker,
553) {
554 for expression in nodes {
555 let mut copy = *passed_start_anchor;
556 walk_downgrade(expression, node_stack, &mut copy, None, walker);
557 }
558}
559
560#[cfg(test)]
561mod tests {
562 use super::*;
563
564 fn missing(pattern: &str) -> bool {
565 let ast = parse(pattern, false).unwrap();
566 is_missing_start_anchor(&ast)
567 }
568
569 #[test]
570 fn is_missing_start_anchor_cases() {
571 assert!(!missing(""));
572 assert!(missing("a"));
573 assert!(!missing("^a"));
574 assert!(!missing("$^a"));
575 assert!(missing("$a"));
576 assert!(missing("a^a"));
577 assert!(!missing("a{0}^a"));
578 assert!(missing("a{0,1}^a"));
579 assert!(missing("a{1}^a"));
580 assert!(missing("^a|b"));
581 assert!(!missing("^a|^b"));
582 assert!(!missing("|"));
583 assert!(!missing("^(a|b)"));
584 assert!(!missing("^ab"));
585 assert!(missing("[ab]^c"));
586 assert!(missing("(^)?c"));
587 assert!(!missing("(^){1}c"));
588 assert!(missing("[^]*a"));
589 assert!(!missing("[^]*?a"));
590 assert!(missing("[^a]*?a"));
591 assert!(missing("[]*?a"));
592 assert!(missing("b*?a"));
593 }
594
595 fn raw(pattern: &str) -> (String, Vec<(String, usize)>) {
596 let ast = parse(pattern, false).unwrap();
597 let result = get_raw_without_capturing_groups_or_lookaheads(&ast);
598 let refs: Vec<(String, usize)> = result
599 .references_with_offset
600 .iter()
601 .map(|(node, offset)| (node.raw.clone(), *offset))
602 .collect();
603 (result.result, refs)
604 }
605
606 #[test]
607 fn get_raw_simple() {
608 let (result, refs) = raw("(a)");
609 assert_eq!(result, "(?:a)");
610 assert!(refs.is_empty());
611 }
612
613 #[test]
614 fn get_raw_every_node_type() {
615 let (result, refs) =
616 raw("^(a)b{1}c+d{1,2}e+(?:f)(?=g)(?!h)(?<=i)(?<!j)(k|l).(m(n))o+?[a\\d]\\1$");
617 assert_eq!(
618 result,
619 "^(?:a)b{1}c+d{1,2}e+(?:f)(?:k|l).(?:m(?:n))o+?[a\\d]\\1$"
620 );
621 assert_eq!(refs, vec![("\\1".to_string(), 51)]);
622 }
623
624 #[test]
625 fn get_raw_multi_reference() {
626 let (result, refs) = raw("()()()()a(\\1)c(d|\\2|f)(?:\\3)(?:\\4)+");
627 assert_eq!(
628 result,
629 "(?:)(?:)(?:)(?:)a(?:\\1)c(?:d|\\2|f)(?:\\3)(?:\\4)+"
630 );
631 assert_eq!(
632 refs,
633 vec![
634 ("\\1".to_string(), 20),
635 ("\\2".to_string(), 29),
636 ("\\3".to_string(), 37),
637 ("\\4".to_string(), 43),
638 ]
639 );
640 }
641}