Skip to main content

vyre_foundation/runtime/
match_result.rs

1//! Native scan match result  -  **legacy scan-domain shim.**
2//!
3//! CRITIQUE_VISION_ALIGNMENT_2026-04-23 V1: this type was the Tier-1
4//! return shape for every byte-range scan in vyre. Its field name
5//! (`pattern_id`) pre-decided that every byte range is a "match"
6//! from a "pattern"  -  a matching-dialect concept that shouldn't
7//! live in foundation. A crypto decoder, an AST-span emitter, or a
8//! capture-group producer would either adopt matching vocabulary
9//! awkwardly or ship a parallel type.
10//!
11//! The canonical neutral name is `ByteRange`. `Match` remains here as
12//! a backward-compat scan-domain shape. Bridges between the two types
13//! are zero-cost (`repr(C)` u32×3 on both sides).
14//!
15//! The full migration removes `Match` entirely; we keep it for one
16//! release so dependent crates don't hard-break.
17
18/// A tagged, half-open byte range `[start, end)`.
19///
20/// `tag` is a producer-chosen 32-bit identifier. A matching dialect can pass a
21/// pattern id, a decoder can pass an encoding id, and a source-span producer
22/// can pass a node kind. Foundation does not interpret the field.
23#[repr(C)]
24#[non_exhaustive]
25#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
26pub struct ByteRange {
27    /// Producer-chosen 32-bit identifier.
28    pub tag: u32,
29    /// Inclusive byte start offset.
30    pub start: u32,
31    /// Exclusive byte end offset.
32    pub end: u32,
33}
34
35impl ByteRange {
36    /// Construct a range. Reversed ranges fail loudly because accepting them
37    /// corrupts every downstream range predicate.
38    #[must_use]
39    pub const fn new(tag: u32, start: u32, end: u32) -> Self {
40        assert!(
41            end >= start,
42            "ByteRange::new requires end >= start. Fix: pass half-open byte ranges as [start, end)."
43        );
44        Self { tag, start, end }
45    }
46
47    /// Length of the range in bytes.
48    #[must_use]
49    pub const fn len(&self) -> u32 {
50        self.end - self.start
51    }
52
53    /// True when the range has zero length.
54    #[must_use]
55    pub const fn is_empty(&self) -> bool {
56        self.end == self.start
57    }
58
59    /// True when `self` contains `other`.
60    #[must_use]
61    pub const fn contains(&self, other: &ByteRange) -> bool {
62        self.start <= other.start && other.end <= self.end
63    }
64
65    /// True when `self` ends at or before `other` starts.
66    #[must_use]
67    pub const fn ends_before(&self, other: &ByteRange) -> bool {
68        self.end <= other.start
69    }
70}
71
72/// A byte-range match emitted by vyre scanning engines.
73///
74/// **Deprecated:** callers should migrate to
75/// [`ByteRange`]. The two types share layout and the `From` bridges are
76/// zero-cost.
77///
78/// Background: `pattern_id` is a matching-dialect concept. The
79/// neutral name on the new type is `tag`; the producer decides what
80/// it means (pattern id, encoding id, AST kind, source index, …).
81/// CRITIQUE_VISION_ALIGNMENT_2026-04-23 V1.
82#[non_exhaustive]
83#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
84pub struct Match {
85    /// Stable pattern identifier that produced the match.
86    pub pattern_id: u32,
87    /// Inclusive byte start offset.
88    pub start: u32,
89    /// Exclusive byte end offset.
90    pub end: u32,
91}
92
93impl Match {
94    /// Construct a match from its pattern id and byte range.
95    ///
96    /// This constructor is a const fn so that engines can emit match
97    /// literals at compile time. The byte range is half-open `[start, end)`
98    /// to match Rust slicing conventions.
99    ///
100    /// # Examples
101    ///
102    /// ```
103    /// use vyre::Match;
104    ///
105    /// let m = Match::new(1, 10, 20);
106    /// assert_eq!(m.pattern_id, 1);
107    /// assert_eq!(m.start, 10);
108    /// assert_eq!(m.end, 20);
109    /// ```
110    #[must_use]
111    pub const fn new(pattern_id: u32, start: u32, end: u32) -> Self {
112        Self {
113            pattern_id,
114            start,
115            end,
116        }
117    }
118}
119
120impl From<Match> for ByteRange {
121    fn from(value: Match) -> Self {
122        ByteRange::new(value.pattern_id, value.start, value.end)
123    }
124}
125
126impl From<ByteRange> for Match {
127    fn from(value: ByteRange) -> Self {
128        Match::new(value.tag, value.start, value.end)
129    }
130}
131
132#[cfg(test)]
133mod tests {
134    use super::*;
135    use std::collections::HashSet;
136
137    #[test]
138    fn construction() {
139        let m = Match::new(1, 10, 20);
140        assert_eq!(m.pattern_id, 1);
141        assert_eq!(m.start, 10);
142        assert_eq!(m.end, 20);
143    }
144
145    #[test]
146    fn ordering() {
147        let a = Match::new(0, 5, 10);
148        let b = Match::new(0, 15, 20);
149        let c = Match::new(1, 0, 5);
150        let mut v = [c, a, b];
151        v.sort();
152        assert_eq!(v[0].start, 5);
153        assert_eq!(v[1].start, 15);
154        assert_eq!(v[2].pattern_id, 1);
155    }
156
157    #[test]
158    fn clone_and_eq() {
159        let a = Match::new(1, 0, 100);
160        let b = a;
161        assert_eq!(a, b);
162    }
163
164    #[test]
165    fn hash_consistency() {
166        let mut set = HashSet::new();
167        let m = Match::new(1, 0, 10);
168        set.insert(m);
169        assert!(set.contains(&Match::new(1, 0, 10)));
170        assert!(!set.contains(&Match::new(2, 0, 10)));
171    }
172
173    #[test]
174    fn byte_range_bridge_preserves_fields() {
175        let range = ByteRange::new(7, 11, 22);
176        let matched: Match = range.into();
177        assert_eq!(matched.pattern_id, 7);
178        assert_eq!(matched.start, 11);
179        assert_eq!(matched.end, 22);
180        let roundtrip: ByteRange = matched.into();
181        assert_eq!(roundtrip, range);
182    }
183
184    #[test]
185    #[should_panic(expected = "ByteRange::new requires end >= start")]
186    fn byte_range_rejects_reversed_ranges() {
187        let _ = ByteRange::new(1, 10, 9);
188    }
189}