dcbor_pattern/pattern/structure/
array_pattern.rs

1use std::ops::RangeBounds;
2
3use dcbor::prelude::*;
4
5use crate::{
6    Interval,
7    pattern::{
8        Matcher, MetaPattern, Path, Pattern,
9        meta::{RepeatPattern, SequencePattern},
10        vm::Instr,
11    },
12};
13
14/// Pattern for matching CBOR array structures.
15#[derive(Debug, Clone, PartialEq, Eq)]
16pub enum ArrayPattern {
17    /// Matches any array.
18    Any,
19    /// Matches arrays with elements that match the given pattern.
20    Elements(Box<Pattern>),
21    /// Matches arrays with length in the given interval.
22    Length(Interval),
23}
24
25impl ArrayPattern {
26    /// Creates a new `ArrayPattern` that matches any array.
27    pub fn any() -> Self {
28        ArrayPattern::Any
29    }
30
31    /// Creates a new `ArrayPattern` that matches arrays with elements
32    /// that match the given pattern.
33    pub fn with_elements(pattern: Pattern) -> Self {
34        ArrayPattern::Elements(Box::new(pattern))
35    }
36
37    pub fn with_length_range<R: RangeBounds<usize>>(range: R) -> Self {
38        ArrayPattern::Length(Interval::new(range))
39    }
40
41    /// Creates a new `ArrayPattern` that matches arrays with length in the
42    /// given range.
43    pub fn with_length_interval(interval: Interval) -> Self {
44        ArrayPattern::Length(interval)
45    }
46
47    /// Match a complex sequence against array elements using VM-based matching.
48    /// This handles patterns with repeats and other complex constructs that
49    /// require backtracking and proper quantifier evaluation.
50    fn match_complex_sequence(
51        &self,
52        cbor: &CBOR,
53        pattern: &Pattern,
54    ) -> Vec<Path> {
55        // For complex sequences containing repeats, we need to check if the
56        // pattern can match the array elements in sequence.
57        // The key insight is that a sequence pattern like
58        // `(*)*, 42, (*)*`  should match if there's any way to
59        // arrange the array elements to satisfy the sequence
60        // requirements.
61
62        match cbor.as_case() {
63            CBORCase::Array(arr) => {
64                // Create a synthetic "element sequence" CBOR value to match
65                // against This represents the array elements as
66                // a sequence that the pattern can evaluate
67
68                // For sequences with repeats, we need to check if the pattern
69                // can be satisfied by the array elements in order
70                if self.can_match_sequence_against_array(pattern, arr) {
71                    vec![vec![cbor.clone()]]
72                } else {
73                    vec![]
74                }
75            }
76            _ => vec![], // Not an array
77        }
78    }
79
80    /// Check if a sequence pattern can match against array elements.
81    /// This implements the core logic for matching patterns like
82    /// `(*)*, 42, (*)*` against array elements.
83    fn can_match_sequence_against_array(
84        &self,
85        pattern: &Pattern,
86        arr: &[CBOR],
87    ) -> bool {
88        match pattern {
89            Pattern::Meta(MetaPattern::Sequence(seq_pattern)) => {
90                self.match_sequence_patterns_against_array(seq_pattern, arr)
91            }
92            Pattern::Meta(MetaPattern::Repeat(repeat_pattern)) => {
93                // Single repeat pattern: check if it can consume all array
94                // elements
95                self.match_repeat_pattern_against_array(repeat_pattern, arr)
96            }
97            _ => {
98                // For non-sequence patterns, fall back to simple matching
99                // Create an array CBOR value by creating a string
100                // representation and parsing it
101                let elements_str: Vec<String> =
102                    arr.iter().map(|item| item.to_string()).collect();
103                let array_str = format!("[{}]", elements_str.join(", "));
104                if let Ok(array_cbor) =
105                    dcbor_parse::parse_dcbor_item(&array_str)
106                {
107                    pattern.matches(&array_cbor)
108                } else {
109                    false
110                }
111            }
112        }
113    }
114
115    /// Match a sequence of patterns against array elements.
116    /// This is the core algorithm for handling sequences with repeats.
117    fn match_sequence_patterns_against_array(
118        &self,
119        seq_pattern: &SequencePattern,
120        arr: &[CBOR],
121    ) -> bool {
122        let patterns = seq_pattern.patterns();
123
124        // For patterns like `(*)*, 42, (*)*`:
125        // - First `(*)*` can consume 0 or more elements from the start
126        // - `42` must match exactly one element (which must be 42)
127        // - Last `(*)*` can consume 0 or more elements from the end
128
129        // Simple case: if no patterns, then empty array should match
130        if patterns.is_empty() {
131            return arr.is_empty();
132        }
133
134        // Try to match the sequence using a backtracking approach
135        self.backtrack_sequence_match(patterns, arr, 0, 0)
136    }
137
138    /// Backtracking algorithm to match sequence patterns against array
139    /// elements. pattern_idx: current pattern index in the sequence
140    /// element_idx: current element index in the array
141    #[allow(clippy::only_used_in_recursion)]
142    fn backtrack_sequence_match(
143        &self,
144        patterns: &[Pattern],
145        arr: &[CBOR],
146        pattern_idx: usize,
147        element_idx: usize,
148    ) -> bool {
149        // Base case: if we've matched all patterns
150        if pattern_idx >= patterns.len() {
151            // Success if we've also consumed all elements
152            return element_idx >= arr.len();
153        }
154
155        let current_pattern = &patterns[pattern_idx];
156
157        match current_pattern {
158            Pattern::Meta(MetaPattern::Repeat(repeat_pattern)) => {
159                let quantifier = repeat_pattern.quantifier();
160                let min_count = quantifier.min();
161                // Fix the infinite loop: limit max_count to reasonable bounds
162                let remaining_elements = arr.len().saturating_sub(element_idx);
163                let max_count = quantifier
164                    .max()
165                    .unwrap_or(remaining_elements)
166                    .min(remaining_elements);
167
168                // Try different numbers of repetitions (greedy: start with max)
169                for rep_count in (min_count..=max_count).rev() {
170                    // Check bounds to prevent out-of-bounds access
171                    if element_idx + rep_count <= arr.len() {
172                        let can_match_reps = if rep_count == 0 {
173                            true // Zero repetitions always match for rep_count=0
174                        } else {
175                            (0..rep_count).all(|i| {
176                                repeat_pattern
177                                    .pattern()
178                                    .matches(&arr[element_idx + i])
179                            })
180                        };
181
182                        if can_match_reps {
183                            // Try to match the rest of the sequence
184                            if self.backtrack_sequence_match(
185                                patterns,
186                                arr,
187                                pattern_idx + 1,
188                                element_idx + rep_count,
189                            ) {
190                                return true;
191                            }
192                        }
193                    }
194                }
195                false
196            }
197            _ => {
198                // Non-repeat pattern: must match exactly one element
199                if element_idx < arr.len()
200                    && current_pattern.matches(&arr[element_idx])
201                {
202                    self.backtrack_sequence_match(
203                        patterns,
204                        arr,
205                        pattern_idx + 1,
206                        element_idx + 1,
207                    )
208                } else {
209                    false
210                }
211            }
212        }
213    }
214
215    /// Match a single repeat pattern against array elements.
216    fn match_repeat_pattern_against_array(
217        &self,
218        repeat_pattern: &RepeatPattern,
219        arr: &[CBOR],
220    ) -> bool {
221        let quantifier = repeat_pattern.quantifier();
222        let min_count = quantifier.min();
223        let max_count = quantifier.max().unwrap_or(arr.len());
224
225        // Check if the array length is within the valid range for this repeat
226        if arr.len() < min_count || arr.len() > max_count {
227            return false;
228        }
229
230        // Check if all elements match the repeated pattern
231        arr.iter()
232            .all(|element| repeat_pattern.pattern().matches(element))
233    }
234
235    /// Handle sequence patterns with captures by manually matching elements
236    /// and collecting captures with proper array context.
237    fn handle_sequence_captures(
238        &self,
239        seq_pattern: &SequencePattern,
240        array_cbor: &CBOR,
241        arr: &[CBOR],
242    ) -> (Vec<Path>, std::collections::HashMap<String, Vec<Path>>) {
243        // Use the existing sequence matching logic to find element assignments
244        if let Some(assignments) =
245            self.find_sequence_element_assignments(seq_pattern, arr)
246        {
247            let mut all_captures = std::collections::HashMap::new();
248
249            // For each pattern in the sequence, collect captures from its
250            // assigned element
251            for (pattern_idx, element_idx) in assignments {
252                let pattern = &seq_pattern.patterns()[pattern_idx];
253                let element = &arr[element_idx];
254
255                // Get captures from this pattern matching this element
256                let (_element_paths, element_captures) =
257                    pattern.paths_with_captures(element);
258
259                // Transform captures to include array context
260                for (capture_name, captured_paths) in element_captures {
261                    let mut array_context_paths = Vec::new();
262                    for captured_path in captured_paths {
263                        // Create path: [array] + [element_at_index] +
264                        // rest_of_path
265                        let mut array_path =
266                            vec![array_cbor.clone(), element.clone()];
267                        if captured_path.len() > 1 {
268                            array_path
269                                .extend(captured_path.iter().skip(1).cloned());
270                        }
271                        array_context_paths.push(array_path);
272                    }
273                    all_captures
274                        .entry(capture_name)
275                        .or_insert_with(Vec::new)
276                        .extend(array_context_paths);
277                }
278            }
279
280            // Return the array path and all captures
281            (vec![vec![array_cbor.clone()]], all_captures)
282        } else {
283            // Sequence doesn't match the array
284            (vec![], std::collections::HashMap::new())
285        }
286    }
287
288    /// Find which array elements are assigned to which sequence patterns.
289    /// Returns a vector of (pattern_index, element_index) pairs if the sequence
290    /// matches.
291    fn find_sequence_element_assignments(
292        &self,
293        seq_pattern: &SequencePattern,
294        arr: &[CBOR],
295    ) -> Option<Vec<(usize, usize)>> {
296        let patterns = seq_pattern.patterns();
297
298        // Check if we have any repeat patterns that require backtracking
299        let has_repeat_patterns = patterns.iter().any(|p| {
300            matches!(p, Pattern::Meta(crate::pattern::MetaPattern::Repeat(_)))
301        });
302
303        // Simple case: if pattern count equals element count AND no repeat
304        // patterns, try one-to-one matching
305        if patterns.len() == arr.len() && !has_repeat_patterns {
306            let mut assignments = Vec::new();
307            for (pattern_idx, pattern) in patterns.iter().enumerate() {
308                let element = &arr[pattern_idx];
309                if pattern.matches(element) {
310                    assignments.push((pattern_idx, pattern_idx));
311                } else {
312                    return None; // Pattern doesn't match its corresponding element
313                }
314            }
315            return Some(assignments);
316        }
317
318        // Handle complex cases with repeats and variable assignments
319        // Use the existing backtracking algorithm to find assignments
320        if let Some(assignments) =
321            self.find_sequence_assignments_with_backtracking(patterns, arr)
322        {
323            return Some(assignments);
324        }
325
326        None
327    }
328
329    /// Use backtracking to find sequence pattern assignments.
330    /// This handles cases like `(*)*, @item(42), (*)*` properly.
331    fn find_sequence_assignments_with_backtracking(
332        &self,
333        patterns: &[Pattern],
334        arr: &[CBOR],
335    ) -> Option<Vec<(usize, usize)>> {
336        let mut assignments = Vec::new();
337
338        if self.backtrack_sequence_assignments(
339            patterns,
340            arr,
341            0,
342            0,
343            &mut assignments,
344        ) {
345            Some(assignments)
346        } else {
347            None
348        }
349    }
350
351    /// Backtracking algorithm to find pattern-to-element assignments.
352    #[allow(clippy::only_used_in_recursion)]
353    fn backtrack_sequence_assignments(
354        &self,
355        patterns: &[Pattern],
356        arr: &[CBOR],
357        pattern_idx: usize,
358        element_idx: usize,
359        assignments: &mut Vec<(usize, usize)>,
360    ) -> bool {
361        // Base case: if we've matched all patterns
362        if pattern_idx >= patterns.len() {
363            // Success if we've also consumed all elements
364            return element_idx >= arr.len();
365        }
366
367        let current_pattern = &patterns[pattern_idx];
368
369        match current_pattern {
370            Pattern::Meta(crate::pattern::MetaPattern::Repeat(
371                repeat_pattern,
372            )) => {
373                let quantifier = repeat_pattern.quantifier();
374                let min_count = quantifier.min();
375                let remaining_elements = arr.len().saturating_sub(element_idx);
376                let max_count = quantifier
377                    .max()
378                    .unwrap_or(remaining_elements)
379                    .min(remaining_elements);
380
381                // Try different numbers of repetitions (greedy: start with max)
382                for rep_count in (min_count..=max_count).rev() {
383                    if element_idx + rep_count <= arr.len() {
384                        let can_match_reps = if rep_count == 0 {
385                            true // Zero repetitions always match
386                        } else {
387                            (0..rep_count).all(|i| {
388                                repeat_pattern
389                                    .pattern()
390                                    .matches(&arr[element_idx + i])
391                            })
392                        };
393
394                        if can_match_reps {
395                            // Record assignments for non-capture repeat
396                            // patterns
397                            let old_len = assignments.len();
398
399                            // Add assignments for elements consumed by this
400                            // repeat
401                            for i in 0..rep_count {
402                                assignments
403                                    .push((pattern_idx, element_idx + i));
404                            }
405
406                            // Try to match the rest of the sequence
407                            if self.backtrack_sequence_assignments(
408                                patterns,
409                                arr,
410                                pattern_idx + 1,
411                                element_idx + rep_count,
412                                assignments,
413                            ) {
414                                return true;
415                            }
416
417                            // Backtrack: remove the assignments we added
418                            assignments.truncate(old_len);
419                        }
420                    }
421                }
422                false
423            }
424            _ => {
425                // Non-repeat pattern: must match exactly one element
426                if element_idx < arr.len()
427                    && current_pattern.matches(&arr[element_idx])
428                {
429                    assignments.push((pattern_idx, element_idx));
430
431                    if self.backtrack_sequence_assignments(
432                        patterns,
433                        arr,
434                        pattern_idx + 1,
435                        element_idx + 1,
436                        assignments,
437                    ) {
438                        true
439                    } else {
440                        // Backtrack: remove the assignment
441                        assignments.pop();
442                        false
443                    }
444                } else {
445                    false
446                }
447            }
448        }
449    }
450}
451
452impl Matcher for ArrayPattern {
453    fn paths(&self, haystack: &CBOR) -> Vec<Path> {
454        // First check if this is an array
455        match haystack.as_case() {
456            CBORCase::Array(arr) => {
457                match self {
458                    ArrayPattern::Any => {
459                        // Match any array - return the array itself
460                        vec![vec![haystack.clone()]]
461                    }
462                    ArrayPattern::Elements(pattern) => {
463                        // For unified syntax, the pattern should match against
464                        // the array elements
465                        // as a sequence, not against any individual element.
466                        //
467                        // Examples:
468                        // - [42] should match [42] but not [1, 42, 3]
469                        // - ["a" , "b"] should match ["a", "b"] but not ["a",
470                        //   "x", "b"]
471
472                        // Check if this is a simple single-element case
473                        use crate::pattern::{MetaPattern, Pattern};
474
475                        match pattern.as_ref() {
476                            // Simple case: single pattern should match array
477                            // with exactly one element
478                            Pattern::Value(_) | Pattern::Structure(_) => {
479                                if arr.len() == 1 {
480                                    if pattern.matches(&arr[0]) {
481                                        vec![vec![haystack.clone()]]
482                                    } else {
483                                        vec![]
484                                    }
485                                } else {
486                                    vec![]
487                                }
488                            }
489
490                            // Complex case: sequences, repeats, etc.
491                            Pattern::Meta(MetaPattern::Sequence(
492                                seq_pattern,
493                            )) => {
494                                let patterns = seq_pattern.patterns();
495
496                                // Check if this sequence contains any repeat
497                                // patterns that
498                                // require VM-based matching
499                                let has_repeat_patterns =
500                                    patterns.iter().any(|p| {
501                                        matches!(
502                                            p,
503                                            Pattern::Meta(MetaPattern::Repeat(
504                                                _
505                                            ))
506                                        )
507                                    });
508
509                                if has_repeat_patterns {
510                                    // Use VM-based matching for complex
511                                    // sequences
512                                    self.match_complex_sequence(haystack, pattern)
513                                } else {
514                                    // Simple sequence: match each pattern
515                                    // against consecutive elements
516                                    if patterns.len() == arr.len() {
517                                        // Check if each pattern matches the
518                                        // corresponding array element
519                                        for (i, element_pattern) in
520                                            patterns.iter().enumerate()
521                                        {
522                                            if !element_pattern.matches(&arr[i])
523                                            {
524                                                return vec![];
525                                            }
526                                        }
527                                        vec![vec![haystack.clone()]]
528                                    } else {
529                                        vec![]
530                                    }
531                                }
532                            }
533
534                            // For individual repeat patterns
535                            Pattern::Meta(MetaPattern::Repeat(_)) => {
536                                // Use VM-based matching for repeat patterns
537                                self.match_complex_sequence(haystack, pattern)
538                            }
539
540                            // For other meta patterns, handle them properly
541                            Pattern::Meta(MetaPattern::Capture(
542                                capture_pattern,
543                            )) => {
544                                // Capture patterns should search within array
545                                // elements
546                                // (This is different from non-capture patterns)
547                                let has_matching_element =
548                                    arr.iter().any(|element| {
549                                        capture_pattern
550                                            .pattern()
551                                            .matches(element)
552                                    });
553
554                                if has_matching_element {
555                                    vec![vec![haystack.clone()]]
556                                } else {
557                                    vec![]
558                                }
559                            }
560
561                            // For other meta patterns (or, and, etc.), use the
562                            // old heuristic
563                            // This handles cases like `[(number | text)]`
564                            _ => {
565                                // Check if the pattern matches the array as a
566                                // whole sequence
567                                // For now, use a heuristic: if it's a simple
568                                // meta pattern,
569                                // apply it to each element and require at least
570                                // one match
571                                // This is not perfect but maintains some
572                                // compatibility
573                                let mut result = Vec::new();
574                                for element in arr {
575                                    if pattern.matches(element) {
576                                        result.push(vec![haystack.clone()]);
577                                        break;
578                                    }
579                                }
580                                result
581                            }
582                        }
583                    }
584                    ArrayPattern::Length(interval) => {
585                        if interval.contains(arr.len()) {
586                            vec![vec![haystack.clone()]]
587                        } else {
588                            vec![]
589                        }
590                    }
591                }
592            }
593            _ => {
594                // Not an array, no match
595                vec![]
596            }
597        }
598    }
599
600    fn compile(
601        &self,
602        code: &mut Vec<Instr>,
603        literals: &mut Vec<Pattern>,
604        captures: &mut Vec<String>,
605    ) {
606        // Collect capture names from inner patterns
607        self.collect_capture_names(captures);
608
609        // Check if this pattern has captures
610        let mut capture_names = Vec::new();
611        self.collect_capture_names(&mut capture_names);
612
613        if capture_names.is_empty() {
614            // No captures, use the simple MatchStructure approach
615            let idx = literals.len();
616            literals.push(Pattern::Structure(
617                crate::pattern::StructurePattern::Array(self.clone()),
618            ));
619            code.push(Instr::MatchStructure(idx));
620        } else {
621            // Has captures, compile to VM navigation instructions
622            match self {
623                ArrayPattern::Elements(pattern) => {
624                    // First check that we have an array
625                    let array_check_idx = literals.len();
626                    literals.push(Pattern::Structure(
627                        crate::pattern::StructurePattern::Array(
628                            ArrayPattern::Any,
629                        ),
630                    ));
631                    code.push(Instr::MatchStructure(array_check_idx));
632
633                    // Navigate to array elements
634                    code.push(Instr::PushAxis(
635                        crate::pattern::vm::Axis::ArrayElement,
636                    ));
637
638                    // Compile the inner pattern with captures
639                    pattern.compile(code, literals, captures);
640
641                    // Pop back to array level
642                    code.push(Instr::Pop);
643                }
644                _ => {
645                    // Other array patterns (length-based) don't support
646                    // captures in this context Fall back to
647                    // MatchStructure
648                    let idx = literals.len();
649                    literals.push(Pattern::Structure(
650                        crate::pattern::StructurePattern::Array(self.clone()),
651                    ));
652                    code.push(Instr::MatchStructure(idx));
653                }
654            }
655        }
656    }
657
658    fn collect_capture_names(&self, names: &mut Vec<String>) {
659        match self {
660            ArrayPattern::Any => {
661                // No captures in a simple any pattern
662            }
663            ArrayPattern::Elements(pattern) => {
664                // Collect captures from the element pattern
665                pattern.collect_capture_names(names);
666            }
667            ArrayPattern::Length(_) => {
668                // No captures in length range patterns
669            }
670        }
671    }
672
673    fn paths_with_captures(
674        &self,
675        cbor: &CBOR,
676    ) -> (Vec<Path>, std::collections::HashMap<String, Vec<Path>>) {
677        // For simple cases that never have captures, use the fast path
678        match self {
679            ArrayPattern::Any | ArrayPattern::Length(_) => {
680                return (self.paths(cbor), std::collections::HashMap::new());
681            }
682            ArrayPattern::Elements(pattern) => {
683                // Check if this specific pattern has any captures
684                let mut capture_names = Vec::new();
685                pattern.collect_capture_names(&mut capture_names);
686
687                if capture_names.is_empty() {
688                    // No captures in the element pattern, use the fast path
689                    return (
690                        self.paths(cbor),
691                        std::collections::HashMap::new(),
692                    );
693                }
694
695                // Has captures, continue with complex logic below
696            }
697        }
698
699        match cbor.as_case() {
700            CBORCase::Array(_arr) => {
701                if let ArrayPattern::Elements(pattern) = self {
702                    // First check if this array pattern matches at all
703                    if self.paths(cbor).is_empty() {
704                        return (vec![], std::collections::HashMap::new());
705                    }
706
707                    // For patterns with captures, we need special handling
708                    // depending on the inner pattern type
709                    match pattern.as_ref() {
710                        Pattern::Meta(
711                            crate::pattern::MetaPattern::Sequence(seq_pattern),
712                        ) => {
713                            // Special handling for SequencePattern with
714                            // captures
715                            self.handle_sequence_captures(
716                                seq_pattern,
717                                cbor,
718                                _arr,
719                            )
720                        }
721                        Pattern::Meta(
722                            crate::pattern::MetaPattern::Capture(
723                                _capture_pattern,
724                            ),
725                        ) => {
726                            // For capture patterns like [@item(number)] or
727                            // [@item(42)],
728                            // use the VM approach for consistency with existing
729                            // behavior
730
731                            // Use the VM approach for consistent behavior
732                            let mut code = Vec::new();
733                            let mut literals = Vec::new();
734                            let mut captures_list = Vec::new();
735
736                            // Compile the entire ArrayPattern (not just the
737                            // inner pattern)
738                            let array_pattern = Pattern::Structure(
739                                crate::pattern::StructurePattern::Array(
740                                    self.clone(),
741                                ),
742                            );
743                            array_pattern.compile(
744                                &mut code,
745                                &mut literals,
746                                &mut captures_list,
747                            );
748                            code.push(crate::pattern::vm::Instr::Accept);
749
750                            let program = crate::pattern::vm::Program {
751                                code,
752                                literals,
753                                capture_names: captures_list,
754                            };
755
756                            // Run the VM program against the CBOR
757                            crate::pattern::vm::run(&program, cbor)
758                        }
759                        _ => {
760                            // For non-sequence patterns, use the original VM
761                            // approach
762                            // but start with the main Pattern's VM compilation
763                            // for better compatibility
764                            let mut code = Vec::new();
765                            let mut literals = Vec::new();
766                            let mut captures = Vec::new();
767
768                            // Compile the entire ArrayPattern (not just the
769                            // inner pattern)
770                            let array_pattern = Pattern::Structure(
771                                crate::pattern::StructurePattern::Array(
772                                    self.clone(),
773                                ),
774                            );
775                            array_pattern.compile(
776                                &mut code,
777                                &mut literals,
778                                &mut captures,
779                            );
780                            code.push(crate::pattern::vm::Instr::Accept);
781
782                            let program = crate::pattern::vm::Program {
783                                code,
784                                literals,
785                                capture_names: captures,
786                            };
787
788                            // Run the VM program against the CBOR
789                            crate::pattern::vm::run(&program, cbor)
790                        }
791                    }
792                } else {
793                    // Other array patterns (length-based) don't have inner
794                    // patterns with captures
795                    (self.paths(cbor), std::collections::HashMap::new())
796                }
797            }
798            _ => {
799                // Not an array, no match
800                (vec![], std::collections::HashMap::new())
801            }
802        }
803    }
804}
805
806impl ArrayPattern {
807    // ...existing methods...
808
809    /// Format a pattern for display within array context.
810    /// This handles sequence patterns specially to use commas instead of >.
811    fn format_array_element_pattern(pattern: &Pattern) -> String {
812        match pattern {
813            Pattern::Meta(crate::pattern::MetaPattern::Sequence(
814                seq_pattern,
815            )) => {
816                // For sequence patterns within arrays, use commas instead of >
817                let patterns_str: Vec<String> = seq_pattern
818                    .patterns()
819                    .iter()
820                    .map(Self::format_array_element_pattern)
821                    .collect();
822                patterns_str.join(", ")
823            }
824            _ => pattern.to_string(),
825        }
826    }
827}
828
829impl std::fmt::Display for ArrayPattern {
830    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
831        match self {
832            ArrayPattern::Any => write!(f, "[*]"),
833            ArrayPattern::Elements(pattern) => {
834                let formatted_pattern =
835                    Self::format_array_element_pattern(pattern);
836                write!(f, "[{}]", formatted_pattern)
837            }
838            ArrayPattern::Length(interval) => {
839                write!(f, "[{}]", interval)
840            }
841        }
842    }
843}