1use crate::types::Position;
12
13type Dimensions = (usize, usize, usize);
14
15#[derive(Debug, Clone, PartialEq, Eq)]
17pub enum Direction {
18 Positive,
19 Negative,
20}
21
22#[derive(Debug, Clone, PartialEq)]
24pub enum PatternElement {
25 Wildcard,
27 Skip,
29 Exclude,
31 Exact(i32),
33 DirectionExclusive(Direction),
35 DirectionInclusive(Direction),
37 Offset(i32),
39 Range(i32, i32),
41 AbsoluteRange(i32, i32),
43}
44
45impl PatternElement {
46 pub fn from_value(value: &str) -> Self {
48 match value {
49 "*" => PatternElement::Wildcard,
50 "?" => PatternElement::Skip,
51 "!" => PatternElement::Exclude,
52 "?+" => PatternElement::DirectionExclusive(Direction::Positive),
53 "?-" => PatternElement::DirectionExclusive(Direction::Negative),
54 "?+=" => PatternElement::DirectionInclusive(Direction::Positive),
55 "?-=" => PatternElement::DirectionInclusive(Direction::Negative),
56 _ => {
57 if let Some(range_str) = Self::try_parse_range(value) {
58 return range_str;
59 }
60 if let Some(abs_range) = Self::try_parse_absolute_range(value) {
61 return abs_range;
62 }
63 if let Some(offset) = Self::try_parse_offset(value) {
64 return offset;
65 }
66 if let Ok(num) = value.parse::<i32>() {
67 PatternElement::Exact(num)
68 } else {
69 PatternElement::Wildcard
70 }
71 }
72 }
73 }
74
75 pub fn from_int(value: i32) -> Self {
77 match value {
78 -1 => PatternElement::Wildcard,
79 -2 => PatternElement::Skip,
80 -3 => PatternElement::Exclude,
81 -10 => PatternElement::DirectionExclusive(Direction::Positive),
82 -11 => PatternElement::DirectionExclusive(Direction::Negative),
83 -12 => PatternElement::DirectionInclusive(Direction::Positive),
84 -13 => PatternElement::DirectionInclusive(Direction::Negative),
85 _ => PatternElement::Exact(value),
86 }
87 }
88
89 fn try_parse_range(value: &str) -> Option<PatternElement> {
92 let parts: Vec<&str> = value.split(':').collect();
93 if parts.len() != 2 {
94 return None;
95 }
96 let lo = Self::extract_relative_offset(parts[0])?;
97 let hi = Self::extract_relative_offset(parts[1])?;
98 Some(PatternElement::Range(lo, hi))
99 }
100
101 fn try_parse_absolute_range(value: &str) -> Option<PatternElement> {
103 let idx = value.find("..")?;
104 if value[idx + 2..].contains("..") {
105 return None;
106 }
107 let lo = value[..idx].parse::<i32>().ok()?;
108 let hi = value[idx + 2..].parse::<i32>().ok()?;
109 Some(PatternElement::AbsoluteRange(lo, hi))
110 }
111
112 fn try_parse_offset(value: &str) -> Option<PatternElement> {
114 let offset = Self::extract_relative_offset(value)?;
115 Some(PatternElement::Offset(offset))
116 }
117
118 fn extract_relative_offset(s: &str) -> Option<i32> {
120 if !s.starts_with('?') {
121 return None;
122 }
123 let rest = &s[1..];
124 if rest.is_empty() {
125 return None;
126 }
127 if rest == "+" || rest == "-" || rest == "+=" || rest == "-=" {
128 return None;
129 }
130 rest.parse::<i32>().ok()
131 }
132}
133
134pub type Pattern3D = (PatternElement, PatternElement, PatternElement);
136
137pub fn match_pattern_element(element: &PatternElement, coordinate: i32, src_coord: i32) -> bool {
139 match element {
140 PatternElement::Wildcard => true,
141 PatternElement::Skip => coordinate == src_coord,
142 PatternElement::Exclude => coordinate != src_coord,
143 PatternElement::Exact(val) => coordinate == *val,
144 PatternElement::DirectionExclusive(Direction::Positive) => coordinate > src_coord,
145 PatternElement::DirectionExclusive(Direction::Negative) => coordinate < src_coord,
146 PatternElement::DirectionInclusive(Direction::Positive) => coordinate >= src_coord,
147 PatternElement::DirectionInclusive(Direction::Negative) => coordinate <= src_coord,
148 PatternElement::Offset(off) => coordinate == src_coord + off,
149 PatternElement::Range(lo, hi) => {
150 coordinate >= src_coord + lo && coordinate <= src_coord + hi
151 }
152 PatternElement::AbsoluteRange(lo, hi) => coordinate >= *lo && coordinate <= *hi,
153 }
154}
155
156fn expand_axis(element: &PatternElement, src_coord: u32, dim: usize) -> Vec<u32> {
158 match element {
159 PatternElement::Wildcard => (0..dim as u32).collect(),
160 PatternElement::Skip => {
161 if (src_coord as usize) < dim {
162 vec![src_coord]
163 } else {
164 vec![]
165 }
166 }
167 PatternElement::Exclude => (0..dim as u32).filter(|&c| c != src_coord).collect(),
168 PatternElement::Exact(val) => {
169 if *val >= 0 && (*val as usize) < dim {
170 vec![*val as u32]
171 } else {
172 vec![]
173 }
174 }
175 PatternElement::DirectionExclusive(Direction::Positive) => {
176 ((src_coord + 1)..dim as u32).collect()
177 }
178 PatternElement::DirectionExclusive(Direction::Negative) => (0..src_coord).collect(),
179 PatternElement::DirectionInclusive(Direction::Positive) => {
180 (src_coord..dim as u32).collect()
181 }
182 PatternElement::DirectionInclusive(Direction::Negative) => {
183 (0..=src_coord).filter(|&c| (c as usize) < dim).collect()
184 }
185 PatternElement::Offset(off) => {
186 let target = src_coord as i32 + off;
187 if target >= 0 && (target as usize) < dim {
188 vec![target as u32]
189 } else {
190 vec![]
191 }
192 }
193 PatternElement::Range(lo, hi) => {
194 let start = (src_coord as i32 + lo).max(0) as u32;
195 let end_exclusive = ((src_coord as i32 + hi) + 1).min(dim as i32) as u32;
196 if start >= end_exclusive {
197 vec![]
198 } else {
199 (start..end_exclusive).collect()
200 }
201 }
202 PatternElement::AbsoluteRange(lo, hi) => expand_absolute_range(*lo, *hi, dim),
203 }
204}
205
206fn expand_absolute_range(lo: i32, hi: i32, dim: usize) -> Vec<u32> {
208 if lo > hi {
209 return vec![];
210 }
211 let start = lo.max(0) as u32;
212 let end_exclusive = (hi + 1).min(dim as i32).max(0) as u32;
213 if start >= end_exclusive {
214 vec![]
215 } else {
216 (start..end_exclusive).collect()
217 }
218}
219
220fn expand_source_axis(element: &PatternElement, dim: usize) -> Vec<u32> {
222 match element {
223 PatternElement::Wildcard => (0..dim as u32).collect(),
224 PatternElement::Exact(val) => {
225 if *val >= 0 && (*val as usize) < dim {
226 vec![*val as u32]
227 } else {
228 vec![]
229 }
230 }
231 PatternElement::AbsoluteRange(lo, hi) => expand_absolute_range(*lo, *hi, dim),
232 _ => (0..dim as u32).collect(),
233 }
234}
235
236fn source_axis_matches(element: &PatternElement, coord: u32) -> bool {
238 match element {
239 PatternElement::Wildcard => true,
240 PatternElement::Exact(val) => *val >= 0 && coord == (*val as u32),
241 PatternElement::AbsoluteRange(lo, hi) => {
242 let value = coord as i32;
243 value >= *lo && value <= *hi
244 }
245 _ => true,
246 }
247}
248
249pub fn find_destination_coordinates(
251 dst_dimensions: Dimensions,
252 src_coordinate: Position,
253 _src_pattern: &Pattern3D,
254 dst_pattern: &Pattern3D,
255) -> Vec<Position> {
256 let (dst_width, dst_height, dst_depth) = dst_dimensions;
257 let (src_x, src_y, src_z) = src_coordinate;
258
259 let x_range = expand_axis(&dst_pattern.0, src_x, dst_width);
260 let y_range = expand_axis(&dst_pattern.1, src_y, dst_height);
261 let z_range = expand_axis(&dst_pattern.2, src_z, dst_depth);
262
263 let mut results = Vec::with_capacity(x_range.len() * y_range.len() * z_range.len());
264 for x in &x_range {
265 for y in &y_range {
266 for z in &z_range {
267 results.push((*x, *y, *z));
268 }
269 }
270 }
271
272 results
273}
274
275pub fn find_source_coordinates(
279 src_pattern: &Pattern3D,
280 src_dimensions: Dimensions,
281) -> Vec<Position> {
282 let (src_width, src_height, src_depth) = src_dimensions;
283
284 let x_range = expand_source_axis(&src_pattern.0, src_width);
285 let y_range = expand_source_axis(&src_pattern.1, src_height);
286 let z_range = expand_source_axis(&src_pattern.2, src_depth);
287
288 let mut results = Vec::with_capacity(x_range.len() * y_range.len() * z_range.len());
289 for x in &x_range {
290 for y in &y_range {
291 for z in &z_range {
292 results.push((*x, *y, *z));
293 }
294 }
295 }
296
297 results
298}
299
300pub fn match_patterns_batch(
302 src_coordinate: Position,
303 patterns: &[(Pattern3D, Pattern3D)],
304 _src_dimensions: Dimensions,
305 dst_dimensions: Dimensions,
306) -> Vec<Position> {
307 let mut all_results = Vec::new();
308
309 for (src_pattern, dst_pattern) in patterns {
310 let (src_x, src_y, src_z) = src_coordinate;
311
312 let x_match = source_axis_matches(&src_pattern.0, src_x);
313 let y_match = source_axis_matches(&src_pattern.1, src_y);
314 let z_match = source_axis_matches(&src_pattern.2, src_z);
315
316 if x_match && y_match && z_match {
317 let mut results = find_destination_coordinates(
318 dst_dimensions,
319 src_coordinate,
320 src_pattern,
321 dst_pattern,
322 );
323 all_results.append(&mut results);
324 }
325 }
326
327 all_results.sort_unstable();
328 all_results.dedup();
329
330 all_results
331}
332
333#[cfg(test)]
334mod tests {
335 use super::*;
336
337 #[test]
338 fn test_wildcard_pattern() {
339 let src_pattern = (
340 PatternElement::Wildcard,
341 PatternElement::Wildcard,
342 PatternElement::Exact(0),
343 );
344 let dst_pattern = (
345 PatternElement::Skip,
346 PatternElement::Skip,
347 PatternElement::Exact(1),
348 );
349
350 let results =
351 find_destination_coordinates((10, 10, 10), (5, 5, 0), &src_pattern, &dst_pattern);
352
353 assert_eq!(results.len(), 1);
354 assert_eq!(results[0], (5, 5, 1));
355 }
356
357 #[test]
358 fn test_exact_pattern() {
359 let src_pattern = (
360 PatternElement::Exact(0),
361 PatternElement::Exact(0),
362 PatternElement::Exact(0),
363 );
364 let dst_pattern = (
365 PatternElement::Exact(1),
366 PatternElement::Exact(2),
367 PatternElement::Exact(3),
368 );
369
370 let results =
371 find_destination_coordinates((10, 10, 10), (0, 0, 0), &src_pattern, &dst_pattern);
372
373 assert_eq!(results.len(), 1);
374 assert_eq!(results[0], (1, 2, 3));
375 }
376
377 #[test]
378 fn test_exclude_pattern() {
379 let src_pattern = (
380 PatternElement::Wildcard,
381 PatternElement::Wildcard,
382 PatternElement::Wildcard,
383 );
384 let dst_pattern = (
385 PatternElement::Exclude,
386 PatternElement::Exact(0),
387 PatternElement::Exact(0),
388 );
389
390 let results =
391 find_destination_coordinates((3, 1, 1), (1, 0, 0), &src_pattern, &dst_pattern);
392
393 assert_eq!(results.len(), 2);
394 assert!(results.contains(&(0, 0, 0)));
395 assert!(results.contains(&(2, 0, 0)));
396 }
397
398 #[test]
399 fn test_direction_positive_exclusive() {
400 let src_pattern = (
401 PatternElement::Wildcard,
402 PatternElement::Wildcard,
403 PatternElement::Wildcard,
404 );
405 let dst_pattern = (
406 PatternElement::DirectionExclusive(Direction::Positive),
407 PatternElement::Skip,
408 PatternElement::Skip,
409 );
410
411 let results =
412 find_destination_coordinates((8, 4, 2), (3, 1, 0), &src_pattern, &dst_pattern);
413
414 assert_eq!(results, vec![(4, 1, 0), (5, 1, 0), (6, 1, 0), (7, 1, 0)]);
415 }
416
417 #[test]
418 fn test_direction_negative_exclusive() {
419 let src_pattern = (
420 PatternElement::Wildcard,
421 PatternElement::Wildcard,
422 PatternElement::Wildcard,
423 );
424 let dst_pattern = (
425 PatternElement::DirectionExclusive(Direction::Negative),
426 PatternElement::Skip,
427 PatternElement::Skip,
428 );
429
430 let results =
431 find_destination_coordinates((8, 4, 2), (3, 1, 0), &src_pattern, &dst_pattern);
432
433 assert_eq!(results, vec![(0, 1, 0), (1, 1, 0), (2, 1, 0)]);
434 }
435
436 #[test]
437 fn test_direction_positive_inclusive() {
438 let dst_pattern = (
439 PatternElement::DirectionInclusive(Direction::Positive),
440 PatternElement::Skip,
441 PatternElement::Skip,
442 );
443 let src_pattern = (
444 PatternElement::Wildcard,
445 PatternElement::Wildcard,
446 PatternElement::Wildcard,
447 );
448
449 let results =
450 find_destination_coordinates((6, 3, 1), (2, 1, 0), &src_pattern, &dst_pattern);
451
452 assert_eq!(results, vec![(2, 1, 0), (3, 1, 0), (4, 1, 0), (5, 1, 0)]);
453 }
454
455 #[test]
456 fn test_direction_negative_inclusive() {
457 let dst_pattern = (
458 PatternElement::DirectionInclusive(Direction::Negative),
459 PatternElement::Skip,
460 PatternElement::Skip,
461 );
462 let src_pattern = (
463 PatternElement::Wildcard,
464 PatternElement::Wildcard,
465 PatternElement::Wildcard,
466 );
467
468 let results =
469 find_destination_coordinates((6, 3, 1), (2, 1, 0), &src_pattern, &dst_pattern);
470
471 assert_eq!(results, vec![(0, 1, 0), (1, 1, 0), (2, 1, 0)]);
472 }
473
474 #[test]
475 fn test_offset_positive() {
476 let dst_pattern = (
477 PatternElement::Offset(2),
478 PatternElement::Skip,
479 PatternElement::Skip,
480 );
481 let src_pattern = (
482 PatternElement::Wildcard,
483 PatternElement::Wildcard,
484 PatternElement::Wildcard,
485 );
486
487 let results =
488 find_destination_coordinates((10, 10, 10), (3, 5, 7), &src_pattern, &dst_pattern);
489
490 assert_eq!(results, vec![(5, 5, 7)]);
491 }
492
493 #[test]
494 fn test_offset_negative() {
495 let dst_pattern = (
496 PatternElement::Offset(-2),
497 PatternElement::Skip,
498 PatternElement::Skip,
499 );
500 let src_pattern = (
501 PatternElement::Wildcard,
502 PatternElement::Wildcard,
503 PatternElement::Wildcard,
504 );
505
506 let results =
507 find_destination_coordinates((10, 10, 10), (3, 5, 7), &src_pattern, &dst_pattern);
508
509 assert_eq!(results, vec![(1, 5, 7)]);
510 }
511
512 #[test]
513 fn test_offset_out_of_bounds() {
514 let dst_pattern = (
515 PatternElement::Offset(5),
516 PatternElement::Skip,
517 PatternElement::Skip,
518 );
519 let src_pattern = (
520 PatternElement::Wildcard,
521 PatternElement::Wildcard,
522 PatternElement::Wildcard,
523 );
524
525 let results =
526 find_destination_coordinates((6, 5, 5), (4, 2, 2), &src_pattern, &dst_pattern);
527
528 assert!(results.is_empty());
529 }
530
531 #[test]
532 fn test_range_symmetric() {
533 let dst_pattern = (
534 PatternElement::Range(-1, 1),
535 PatternElement::Range(-1, 1),
536 PatternElement::Skip,
537 );
538 let src_pattern = (
539 PatternElement::Wildcard,
540 PatternElement::Wildcard,
541 PatternElement::Wildcard,
542 );
543
544 let results =
545 find_destination_coordinates((10, 10, 5), (5, 5, 2), &src_pattern, &dst_pattern);
546
547 assert_eq!(results.len(), 9); assert!(results.contains(&(4, 4, 2)));
549 assert!(results.contains(&(5, 5, 2)));
550 assert!(results.contains(&(6, 6, 2)));
551 }
552
553 #[test]
554 fn test_range_clamped_at_boundary() {
555 let dst_pattern = (
556 PatternElement::Range(-3, 3),
557 PatternElement::Skip,
558 PatternElement::Skip,
559 );
560 let src_pattern = (
561 PatternElement::Wildcard,
562 PatternElement::Wildcard,
563 PatternElement::Wildcard,
564 );
565
566 let results =
568 find_destination_coordinates((8, 1, 1), (1, 0, 0), &src_pattern, &dst_pattern);
569
570 assert_eq!(
571 results,
572 vec![(0, 0, 0), (1, 0, 0), (2, 0, 0), (3, 0, 0), (4, 0, 0)]
573 );
574 }
575
576 #[test]
577 fn test_range_forward_only() {
578 let dst_pattern = (
579 PatternElement::Range(1, 3),
580 PatternElement::Skip,
581 PatternElement::Skip,
582 );
583 let src_pattern = (
584 PatternElement::Wildcard,
585 PatternElement::Wildcard,
586 PatternElement::Wildcard,
587 );
588
589 let results =
590 find_destination_coordinates((10, 1, 1), (2, 0, 0), &src_pattern, &dst_pattern);
591
592 assert_eq!(results, vec![(3, 0, 0), (4, 0, 0), (5, 0, 0)]);
593 }
594
595 #[test]
596 fn test_direction_at_edge() {
597 let dst_pattern = (
598 PatternElement::DirectionExclusive(Direction::Negative),
599 PatternElement::Skip,
600 PatternElement::Skip,
601 );
602 let src_pattern = (
603 PatternElement::Wildcard,
604 PatternElement::Wildcard,
605 PatternElement::Wildcard,
606 );
607
608 let results =
610 find_destination_coordinates((10, 1, 1), (0, 0, 0), &src_pattern, &dst_pattern);
611
612 assert!(results.is_empty());
613 }
614
615 #[test]
616 fn test_from_value_new_patterns() {
617 assert_eq!(
618 PatternElement::from_value("?+"),
619 PatternElement::DirectionExclusive(Direction::Positive)
620 );
621 assert_eq!(
622 PatternElement::from_value("?-"),
623 PatternElement::DirectionExclusive(Direction::Negative)
624 );
625 assert_eq!(
626 PatternElement::from_value("?+="),
627 PatternElement::DirectionInclusive(Direction::Positive)
628 );
629 assert_eq!(
630 PatternElement::from_value("?-="),
631 PatternElement::DirectionInclusive(Direction::Negative)
632 );
633 assert_eq!(PatternElement::from_value("?+3"), PatternElement::Offset(3));
634 assert_eq!(
635 PatternElement::from_value("?-2"),
636 PatternElement::Offset(-2)
637 );
638 assert_eq!(
639 PatternElement::from_value("?-1:?+1"),
640 PatternElement::Range(-1, 1)
641 );
642 assert_eq!(
643 PatternElement::from_value("?+2:?+5"),
644 PatternElement::Range(2, 5)
645 );
646 assert_eq!(
647 PatternElement::from_value("1..98"),
648 PatternElement::AbsoluteRange(1, 98)
649 );
650 }
651
652 #[test]
653 fn test_from_value_backward_compat() {
654 assert_eq!(PatternElement::from_value("*"), PatternElement::Wildcard);
655 assert_eq!(PatternElement::from_value("?"), PatternElement::Skip);
656 assert_eq!(PatternElement::from_value("!"), PatternElement::Exclude);
657 assert_eq!(PatternElement::from_value("7"), PatternElement::Exact(7));
658 }
659
660 #[test]
661 fn test_from_int_new_encodings() {
662 assert_eq!(
663 PatternElement::from_int(-10),
664 PatternElement::DirectionExclusive(Direction::Positive)
665 );
666 assert_eq!(
667 PatternElement::from_int(-11),
668 PatternElement::DirectionExclusive(Direction::Negative)
669 );
670 assert_eq!(
671 PatternElement::from_int(-12),
672 PatternElement::DirectionInclusive(Direction::Positive)
673 );
674 assert_eq!(
675 PatternElement::from_int(-13),
676 PatternElement::DirectionInclusive(Direction::Negative)
677 );
678 }
679
680 #[test]
681 fn test_batch_with_directional() {
682 let patterns = vec![(
683 (
684 PatternElement::Wildcard,
685 PatternElement::Wildcard,
686 PatternElement::Wildcard,
687 ),
688 (
689 PatternElement::DirectionExclusive(Direction::Positive),
690 PatternElement::Skip,
691 PatternElement::Skip,
692 ),
693 )];
694
695 let results = match_patterns_batch((3, 0, 0), &patterns, (8, 1, 1), (8, 1, 1));
696
697 assert_eq!(results, vec![(4, 0, 0), (5, 0, 0), (6, 0, 0), (7, 0, 0)]);
698 }
699
700 #[test]
701 fn test_absolute_range_filters_source_x() {
702 let src_pattern = (
703 PatternElement::from_value("1..3"),
704 PatternElement::Wildcard,
705 PatternElement::Wildcard,
706 );
707 let sources = find_source_coordinates(&src_pattern, (8, 1, 1));
708 assert_eq!(sources, vec![(1, 0, 0), (2, 0, 0), (3, 0, 0)]);
709 }
710
711 #[test]
712 fn test_absolute_range_destination_expand() {
713 let src_pattern = (
714 PatternElement::Wildcard,
715 PatternElement::Wildcard,
716 PatternElement::Wildcard,
717 );
718 let dst_pattern = (
719 PatternElement::AbsoluteRange(1, 3),
720 PatternElement::Exact(0),
721 PatternElement::Exact(0),
722 );
723 let results =
724 find_destination_coordinates((10, 1, 1), (7, 0, 0), &src_pattern, &dst_pattern);
725 assert_eq!(results, vec![(1, 0, 0), (2, 0, 0), (3, 0, 0)]);
726 }
727
728 #[test]
729 fn test_absolute_range_batch_skips_outside_source() {
730 let patterns = [(
731 (
732 PatternElement::AbsoluteRange(1, 3),
733 PatternElement::Wildcard,
734 PatternElement::Wildcard,
735 ),
736 (
737 PatternElement::Skip,
738 PatternElement::Skip,
739 PatternElement::Exact(0),
740 ),
741 )];
742 let inside = match_patterns_batch((2, 0, 0), &patterns, (8, 1, 1), (8, 1, 1));
743 assert_eq!(inside, vec![(2, 0, 0)]);
744 let outside = match_patterns_batch((5, 0, 0), &patterns, (8, 1, 1), (8, 1, 1));
745 assert!(outside.is_empty());
746 }
747
748 #[test]
749 fn test_absolute_range_inverted_is_empty() {
750 let results = expand_absolute_range(5, 1, 10);
751 assert!(results.is_empty());
752 }
753}