Skip to main content

abnf_oracle/
ast.rs

1//! The grammar data model, in two layers (SCOPE.md 6.7, D12, D32).
2//!
3//! The **syntactic** layer — [`Grammar`], [`Definition`], [`Element`] — is what the parser
4//! produces: the ordered list of definitions exactly as written, after local rewrites only. It
5//! does not merge `=/`, resolve names, or carry node ids.
6//!
7//! The **semantic** layer — [`Rule`], [`Node`], [`NodeId`] — is the flat arena that `check`
8//! lowers the merged rule table into. A [`NodeId`] *is* an index into that arena, so the
9//! "pre-order traversal of the merged, normalized rule table" that SCOPE.md 6.7 requires of node
10//! ids falls out of the order nodes are emitted in, with no separate numbering pass.
11//!
12//! Equality at both layers deliberately ignores provenance: spans never participate (see [`Ignored`])
13//! and rule names compare ASCII-case-insensitively (see [`RuleName`]). Without the first,
14//! `*1a` and `[a]` would not compare equal and the round-trip tests in M1 could not hold.
15
16use core::fmt;
17use core::hash::{Hash, Hasher};
18
19/// A byte range in the grammar source text.
20#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash)]
21pub struct Span {
22    /// Byte offset of the first byte, inclusive.
23    pub start: u32,
24    /// Byte offset one past the last byte.
25    pub end: u32,
26}
27
28impl Span {
29    /// Creates a span covering `start..end`.
30    #[must_use]
31    pub const fn new(start: u32, end: u32) -> Self {
32        Self { start, end }
33    }
34
35    /// Returns the span as a range usable for slicing the source text.
36    #[must_use]
37    pub const fn range(self) -> core::ops::Range<usize> {
38        self.start as usize..self.end as usize
39    }
40}
41
42/// A value that is carried but never compared: every `Ignored<T>` equals every other.
43///
44/// Used for spans. Two grammars that differ only in where their elements sit in the source text
45/// are the same grammar — M1 requires `*1a` and `[a]` to parse to *equal* grammars, and those
46/// necessarily have different spans. Wrapping the span rather than hand-writing `PartialEq` for
47/// every type that carries one is what lets those types keep deriving it, so a field added later
48/// cannot be silently left out of a comparison.
49#[derive(Clone, Copy, Debug, Default)]
50pub struct Ignored<T>(
51    /// The carried value.
52    pub T,
53);
54
55impl<T> PartialEq for Ignored<T> {
56    fn eq(&self, _other: &Self) -> bool {
57        true
58    }
59}
60
61impl<T> Eq for Ignored<T> {}
62
63impl<T> Hash for Ignored<T> {
64    fn hash<H: Hasher>(&self, _state: &mut H) {}
65}
66
67/// A rule name.
68///
69/// Rule names are ASCII-case-insensitive (RFC 5234 2.1), so comparison and hashing fold case
70/// while the spelling as written is preserved for `Display`.
71#[derive(Clone, Debug)]
72pub struct RuleName(String);
73
74impl RuleName {
75    /// Creates a rule name from its spelling as written.
76    #[must_use]
77    pub fn new(name: impl Into<String>) -> Self {
78        Self(name.into())
79    }
80
81    /// Returns the name as it was written in the grammar.
82    #[must_use]
83    pub fn as_str(&self) -> &str {
84        &self.0
85    }
86
87    /// Returns the ASCII-lowercased form used to key the rule table.
88    #[must_use]
89    pub fn key(&self) -> String {
90        self.0.to_ascii_lowercase()
91    }
92}
93
94impl PartialEq for RuleName {
95    fn eq(&self, other: &Self) -> bool {
96        self.0.eq_ignore_ascii_case(&other.0)
97    }
98}
99
100impl Eq for RuleName {}
101
102impl Hash for RuleName {
103    fn hash<H: Hasher>(&self, state: &mut H) {
104        for byte in self.0.as_bytes() {
105            state.write_u8(byte.to_ascii_lowercase());
106        }
107        // Terminator, so that ("ab", "c") and ("a", "bc") do not collide when several names are
108        // hashed into the same state.
109        state.write_u8(0xff);
110    }
111}
112
113impl fmt::Display for RuleName {
114    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
115        f.write_str(&self.0)
116    }
117}
118
119/// Syntactic options for parsing. Never semantic (SCOPE.md 4, D30).
120///
121/// Parse options are provenance: they are excluded from [`Grammar`] equality and are never
122/// serialized by `Display`. Nothing that changes what a grammar *means* may be added here.
123#[derive(Clone, Debug, Default, PartialEq, Eq)]
124pub struct ParseOptions {
125    /// Require CRLF line endings, rather than accepting CRLF, LF or CR.
126    pub strict_crlf: bool,
127}
128
129/// Which definition operator introduced a [`Definition`]: `defined-as` in RFC 5234 4.
130#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
131pub enum DefinedAs {
132    /// `name = elements`.
133    Base,
134    /// `name =/ elements`.
135    Incremental,
136}
137
138/// One `name = elements` or `name =/ elements` definition, as written.
139#[derive(Clone, Debug, PartialEq, Eq)]
140pub struct Definition {
141    /// The rule name being defined.
142    pub name: RuleName,
143    /// Which operator was used.
144    pub defined_as: DefinedAs,
145    /// The right-hand side.
146    pub body: Element,
147    /// Where the definition sits in the source.
148    pub span: Ignored<Span>,
149}
150
151/// Repetition bounds, inclusive on both ends.
152///
153/// `max` of `None` is unbounded. A sentinel is deliberately avoided: `*18446744073709551615` is
154/// a legal bound and would collide with one.
155#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
156pub struct Repeat {
157    /// Lower bound.
158    pub min: u64,
159    /// Upper bound, or `None` for unbounded.
160    pub max: Option<u64>,
161}
162
163impl Repeat {
164    /// `n<element>` — exactly `n` repetitions.
165    #[must_use]
166    pub const fn exactly(n: u64) -> Self {
167        Self {
168            min: n,
169            max: Some(n),
170        }
171    }
172
173    /// `n*<element>` — at least `n` repetitions, unbounded above.
174    #[must_use]
175    pub const fn at_least(n: u64) -> Self {
176        Self { min: n, max: None }
177    }
178
179    /// `min*max<element>`.
180    #[must_use]
181    pub const fn bounded(min: u64, max: u64) -> Self {
182        Self {
183            min,
184            max: Some(max),
185        }
186    }
187
188    /// Whether the upper bound is unbounded.
189    #[must_use]
190    pub const fn is_unbounded(&self) -> bool {
191        self.max.is_none()
192    }
193
194    /// Whether `min <= max`.
195    ///
196    /// `check` rejects the inverted case with `InvalidRepeatRange` (D18); the recognizer may
197    /// assume this holds.
198    #[must_use]
199    pub const fn is_valid(&self) -> bool {
200        match self.max {
201            Some(max) => self.min <= max,
202            None => true,
203        }
204    }
205
206    /// Whether the body can never match, i.e. `max == 0`.
207    ///
208    /// Such a body contributes no first-graph edges and holds no coverage units
209    /// (SCOPE.md 6.4, 6.8, D36).
210    #[must_use]
211    pub const fn is_never(&self) -> bool {
212        matches!(self.max, Some(0))
213    }
214}
215
216/// A quoted string terminal: `char-val` in RFC 5234 4.
217#[derive(Clone, Debug, PartialEq, Eq, Hash)]
218pub struct CharVal {
219    /// The characters between the quotes, as written.
220    pub value: String,
221    /// `true` for `%s"..."`, `false` for `"..."` and `%i"..."` (RFC 7405).
222    pub case_sensitive: bool,
223}
224
225/// A numeric terminal, in any of the three radices. The radix is not retained: `%d65`,
226/// `%b1000001` and `%x41` all parse to the same node and print as `%x41` (SCOPE.md 6.7, D37).
227#[derive(Clone, Debug, PartialEq, Eq, Hash)]
228pub enum NumVal {
229    /// A single value, e.g. `%x41`.
230    Scalar(u64),
231    /// An inclusive range, e.g. `%x41-5A`.
232    Range {
233        /// Lower endpoint.
234        lo: u64,
235        /// Upper endpoint.
236        hi: u64,
237    },
238    /// A concatenation, e.g. `%x41.42.43` (Erratum 3076).
239    Concat(Vec<u64>),
240}
241
242/// The largest Unicode scalar value.
243const MAX_SCALAR: u64 = 0x10_FFFF;
244/// The surrogate block, which contains no scalar value and so can never occur in input.
245const SURROGATES: (u64, u64) = (0xD800, 0xDFFF);
246
247/// How many integers `lo..=hi` and `a..=b` have in common.
248const fn overlap(lo: u64, hi: u64, a: u64, b: u64) -> u64 {
249    let start = if lo > a { lo } else { a };
250    let end = if hi < b { hi } else { b };
251    if start > end { 0 } else { end - start + 1 }
252}
253
254impl NumVal {
255    /// Whether this terminal can match anything at all (SCOPE.md 6.1).
256    ///
257    /// The matching domain is Unicode scalar values, so a value above `U+10FFFF` or inside the
258    /// surrogate block matches nothing. Being unrepresentable is *not* a structural error: the
259    /// grammar loads, and only a start rule that can reach the terminal is refused (D4). Real
260    /// grammars written over octets — RFC 9110's `obs-text = %x80-FF` — stay usable this way.
261    #[must_use]
262    pub fn is_representable(&self) -> bool {
263        match self {
264            Self::Scalar(value) => is_scalar(*value),
265            // A range need only *contain* a scalar value. One that merely spans the surrogate
266            // block is fine: surrogates cannot occur in input, so they are never matched.
267            Self::Range { lo, hi } => lo <= hi && Self::scalars_in(*lo, *hi) > 0,
268            Self::Concat(values) => values.iter().copied().all(is_scalar),
269        }
270    }
271
272    /// How many Unicode scalar values `lo..=hi` contains.
273    ///
274    /// The generator picks uniformly among them, so this has to exclude the surrogate block
275    /// rather than just measure the span.
276    #[must_use]
277    pub const fn scalars_in(lo: u64, hi: u64) -> u64 {
278        if lo > hi {
279            return 0;
280        }
281        overlap(lo, hi, 0, MAX_SCALAR) - overlap(lo, hi, SURROGATES.0, SURROGATES.1)
282    }
283
284    /// The `index`-th Unicode scalar value in `lo..=hi`, skipping the surrogate block.
285    ///
286    /// `None` once `index` reaches [`NumVal::scalars_in`], so the generator can index without
287    /// first checking the bound.
288    #[must_use]
289    pub fn nth_scalar(lo: u64, hi: u64, index: u64) -> Option<char> {
290        if index >= Self::scalars_in(lo, hi) {
291            return None;
292        }
293        let below = overlap(lo, hi, 0, SURROGATES.0 - 1);
294        // Step over the surrogate block once the low part is exhausted.
295        let value = if index < below {
296            lo + index
297        } else {
298            SURROGATES.1 + 1 + (index - below)
299        };
300        char::from_u32(u32::try_from(value).ok()?)
301    }
302}
303
304/// Whether `value` is a Unicode scalar value.
305const fn is_scalar(value: u64) -> bool {
306    value <= MAX_SCALAR && (value < SURROGATES.0 || value > SURROGATES.1)
307}
308
309/// An element of a rule body, in the syntactic layer.
310#[derive(Clone, Debug, PartialEq, Eq)]
311pub enum Element {
312    /// `a / b / c`. Unordered (SCOPE.md 2, goal 2).
313    Alt(Vec<Element>),
314    /// `a b c`.
315    Concat(Vec<Element>),
316    /// `2*5a`.
317    Repeat {
318        /// The bounds.
319        repeat: Repeat,
320        /// The repeated element.
321        body: Box<Element>,
322        /// Where the bounds sit in the source, for `InvalidRepeatRange`.
323        span: Ignored<Span>,
324    },
325    /// `[a]`.
326    ///
327    /// Kept distinct from `Repeat { 0, Some(1) }` — which canonicalization rewrites into it —
328    /// so that `Display` prints `[a]` without inspecting bounds, and so that the generator can
329    /// treat it as the two-branch alternation `a / empty` for coverage.
330    Optional(Box<Element>),
331    /// A reference to another rule.
332    RuleRef {
333        /// The referenced name.
334        name: RuleName,
335        /// Where the reference sits in the source.
336        span: Ignored<Span>,
337    },
338    /// A quoted string terminal.
339    CharVal(CharVal),
340    /// A numeric terminal.
341    NumVal {
342        /// The value.
343        value: NumVal,
344        /// Where it sits in the source, for `InvalidNumericRange`.
345        span: Ignored<Span>,
346    },
347    /// `<prose>`: parsed and preserved, never matched or generated (SCOPE.md 6.5).
348    ProseVal {
349        /// The text between the angle brackets.
350        text: String,
351        /// Where the prose value sits in the source.
352        span: Ignored<Span>,
353    },
354}
355
356/// A parsed grammar: the ordered definitions as written, after local rewrites only.
357///
358/// This layer knows nothing about rule resolution. `=/` is still separate from `=`, names are
359/// unresolved, and there are no node ids; all of that is `check`'s job (D32). A `Grammar` never
360/// carries hidden validity state — a grammar with a duplicate definition parses fine and fails
361/// at `check`.
362#[derive(Clone, Debug)]
363pub struct Grammar {
364    defs: Vec<Definition>,
365    opts: ParseOptions,
366}
367
368impl Grammar {
369    /// Creates a grammar from its definitions and the options it was parsed with.
370    #[must_use]
371    pub fn new(defs: Vec<Definition>, opts: ParseOptions) -> Self {
372        Self { defs, opts }
373    }
374
375    /// The definitions, in source order, `=` and `=/` alike.
376    #[must_use]
377    pub fn definitions(&self) -> &[Definition] {
378        &self.defs
379    }
380
381    /// Consumes the grammar, yielding its definitions.
382    ///
383    /// For `check`, which takes ownership (D1) and has no reason to clone every rule body.
384    #[must_use]
385    pub fn into_definitions(self) -> Vec<Definition> {
386        self.defs
387    }
388
389    /// The options this grammar was parsed with.
390    ///
391    /// Provenance only: excluded from equality, never serialized (D21).
392    #[must_use]
393    pub fn parse_options(&self) -> &ParseOptions {
394        &self.opts
395    }
396}
397
398impl PartialEq for Grammar {
399    /// Compares the definition list only. Parse options are provenance and are ignored (D21).
400    fn eq(&self, other: &Self) -> bool {
401        self.defs == other.defs
402    }
403}
404
405impl Eq for Grammar {}
406
407/// An index into a checked grammar's node arena.
408///
409/// Assigned by pre-order traversal of the merged rule table, so two grammars with the same
410/// canonical form have identical node ids (SCOPE.md 6.7, D22).
411#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
412pub struct NodeId(u32);
413
414impl NodeId {
415    /// Creates a node id from an arena index.
416    #[must_use]
417    pub const fn from_index(index: u32) -> Self {
418        Self(index)
419    }
420
421    /// Returns the arena index.
422    #[must_use]
423    pub const fn index(self) -> usize {
424        self.0 as usize
425    }
426}
427
428/// Identifies a rule in the merged table, distinguishing user rules from the implicit
429/// core-rule environment.
430///
431/// The distinction is what makes core-rule resolution hygienic: a reference inside a core body
432/// resolves against core rules only, so a user's `DIGIT = "x"` cannot silently redefine
433/// `HEXDIG` (SCOPE.md 4.1, D33).
434#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
435pub enum RuleId {
436    /// A rule defined by the user grammar.
437    User(u32),
438    /// A rule from RFC 5234 Appendix B.
439    Core(u32),
440}
441
442/// A rule in the merged table: `=/` folded into its base, body lowered into the arena.
443#[derive(Clone, Debug, PartialEq, Eq)]
444pub struct Rule {
445    /// The rule name, spelled as it was first defined.
446    pub name: RuleName,
447    /// The root node of the merged body.
448    pub body: NodeId,
449}
450
451/// A node in the merged-grammar arena.
452///
453/// Mirrors [`Element`], except that children are [`NodeId`]s rather than boxes, and rule
454/// references carry no resolution — resolution lives in a side table indexed by node id, so
455/// that lowering needs no placeholder value.
456#[derive(Clone, Debug, PartialEq, Eq)]
457pub enum Node {
458    /// `a / b / c`.
459    Alt {
460        /// The branches, in source order. Branch indices are part of coverage-unit identity.
461        branches: Vec<NodeId>,
462    },
463    /// `a b c`.
464    Concat {
465        /// The items, in source order.
466        items: Vec<NodeId>,
467    },
468    /// `2*5a`.
469    Repeat {
470        /// The bounds.
471        repeat: Repeat,
472        /// The repeated node.
473        body: NodeId,
474        /// Where the bounds sit in the source.
475        span: Ignored<Span>,
476    },
477    /// `[a]`, modeled as `a / empty` for coverage purposes.
478    Optional {
479        /// The optional node.
480        body: NodeId,
481    },
482    /// A reference to another rule, resolved via the side table.
483    RuleRef {
484        /// The referenced name.
485        name: RuleName,
486        /// Where the reference sits in the source.
487        span: Ignored<Span>,
488    },
489    /// A quoted string terminal.
490    CharVal(CharVal),
491    /// A numeric terminal.
492    NumVal {
493        /// The value.
494        value: NumVal,
495        /// Where it sits in the source.
496        span: Ignored<Span>,
497    },
498    /// `<prose>`.
499    ProseVal(String),
500}
501
502/// The length of the shortest string a node can match (SCOPE.md 6.4, D27).
503///
504/// `Finite` saturates at `u64::MAX`: nested repetitions can overflow even when every literal
505/// bound fits in 64 bits, and a saturated value is still finite, hence still *productive*.
506/// Ordering is the natural one — every `Finite` is less than `Infinite`.
507#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
508pub enum MinLen {
509    /// A shortest match exists and is this long, saturating at `u64::MAX`.
510    Finite(u64),
511    /// The node matches nothing: it is unproductive.
512    Infinite,
513}
514
515impl MinLen {
516    /// The empty match.
517    pub const ZERO: Self = Self::Finite(0);
518
519    /// Whether a shortest match exists, i.e. whether the node is productive.
520    #[must_use]
521    pub const fn is_finite(self) -> bool {
522        matches!(self, Self::Finite(_))
523    }
524
525    /// Concatenation: saturating addition, absorbing on `Infinite`.
526    #[must_use]
527    pub const fn saturating_add(self, other: Self) -> Self {
528        match (self, other) {
529            (Self::Finite(a), Self::Finite(b)) => Self::Finite(a.saturating_add(b)),
530            _ => Self::Infinite,
531        }
532    }
533
534    /// Repetition: `count` copies of `self`, saturating.
535    ///
536    /// Zero copies match the empty string whatever the body is, so `Infinite * 0` is
537    /// `Finite(0)` — which is why `*bad` is productive even when `bad` is not.
538    #[must_use]
539    pub const fn saturating_mul(self, count: u64) -> Self {
540        if count == 0 {
541            return Self::ZERO;
542        }
543        match self {
544            Self::Finite(a) => Self::Finite(a.saturating_mul(count)),
545            Self::Infinite => Self::Infinite,
546        }
547    }
548
549    /// Alternation: the shorter of two alternatives.
550    #[must_use]
551    pub const fn min(self, other: Self) -> Self {
552        match (self, other) {
553            (Self::Finite(a), Self::Finite(b)) => Self::Finite(if a < b { a } else { b }),
554            (Self::Finite(a), Self::Infinite) => Self::Finite(a),
555            (Self::Infinite, other) => other,
556        }
557    }
558}
559
560/// The choice a node's shortest derivation makes (SCOPE.md 6.4, D28).
561///
562/// Recorded by `check` when a node's `min_len` is finalized, so every witness points at a node
563/// that was finalized earlier. Following witnesses therefore terminates, which is what the
564/// generator relies on when its depth budget runs out. Comparing `min_len` magnitudes is not a
565/// substitute: ties can cycle and saturated values compare meaninglessly.
566#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
567pub enum Witness {
568    /// For an alternation or optional: the branch index that attained the shortest match.
569    Branch(u32),
570    /// For a repetition: the count, which is always `min`.
571    Count(u64),
572}
573
574#[cfg(test)]
575mod tests {
576    use super::*;
577
578    #[test]
579    fn rule_names_compare_case_insensitively() {
580        assert_eq!(RuleName::new("Foo"), RuleName::new("fOO"));
581        assert_ne!(RuleName::new("foo"), RuleName::new("foobar"));
582        assert_eq!(RuleName::new("Foo").as_str(), "Foo");
583        assert_eq!(RuleName::new("Foo").key(), "foo");
584    }
585
586    #[test]
587    fn rule_name_hash_agrees_with_eq() {
588        use std::collections::HashSet;
589        let mut set = HashSet::new();
590        set.insert(RuleName::new("DIGIT"));
591        assert!(set.contains(&RuleName::new("digit")));
592    }
593
594    #[test]
595    fn spans_never_affect_equality() {
596        let a = Element::RuleRef {
597            name: RuleName::new("x"),
598            span: Ignored(Span::new(0, 1)),
599        };
600        let b = Element::RuleRef {
601            name: RuleName::new("X"),
602            span: Ignored(Span::new(40, 41)),
603        };
604        assert_eq!(a, b);
605    }
606
607    #[test]
608    fn parse_options_do_not_affect_grammar_equality() {
609        let def = Definition {
610            name: RuleName::new("a"),
611            defined_as: DefinedAs::Base,
612            body: Element::CharVal(CharVal {
613                value: "x".into(),
614                case_sensitive: false,
615            }),
616            span: Ignored(Span::default()),
617        };
618        let lenient = Grammar::new(vec![def.clone()], ParseOptions::default());
619        let strict = Grammar::new(vec![def], ParseOptions { strict_crlf: true });
620        assert_eq!(lenient, strict);
621    }
622
623    #[test]
624    fn repeat_bounds() {
625        assert!(Repeat::exactly(3).is_valid());
626        assert!(Repeat::at_least(3).is_valid());
627        assert!(Repeat::at_least(3).is_unbounded());
628        assert!(!Repeat::bounded(5, 2).is_valid());
629        assert!(Repeat::bounded(0, 0).is_never());
630        assert!(!Repeat::bounded(0, 1).is_never());
631    }
632
633    #[test]
634    fn scalars_are_representable_and_surrogates_are_not() {
635        assert!(NumVal::Scalar(0x41).is_representable());
636        assert!(NumVal::Scalar(0).is_representable());
637        assert!(NumVal::Scalar(0x10_FFFF).is_representable());
638        assert!(
639            !NumVal::Scalar(0x11_0000).is_representable(),
640            "above the last scalar"
641        );
642        assert!(
643            !NumVal::Scalar(0xD800).is_representable(),
644            "a surrogate is not a scalar"
645        );
646        assert!(!NumVal::Scalar(0xDFFF).is_representable());
647        assert!(
648            NumVal::Scalar(0xE000).is_representable(),
649            "just past the surrogates"
650        );
651    }
652
653    #[test]
654    fn a_range_need_only_contain_one_scalar() {
655        assert!(NumVal::Range { lo: 0x41, hi: 0x5A }.is_representable());
656        // Spanning the surrogate block is fine: surrogates simply never match (SCOPE.md 6.1).
657        assert!(
658            NumVal::Range {
659                lo: 0xD7FF,
660                hi: 0xE000
661            }
662            .is_representable()
663        );
664        assert!(
665            NumVal::Range {
666                lo: 0,
667                hi: 0x10_FFFF
668            }
669            .is_representable()
670        );
671        // Wholly inside it, or wholly above the last scalar, and it matches nothing.
672        assert!(
673            !NumVal::Range {
674                lo: 0xD800,
675                hi: 0xDFFF
676            }
677            .is_representable()
678        );
679        assert!(
680            !NumVal::Range {
681                lo: 0xDC00,
682                hi: 0xDD00
683            }
684            .is_representable()
685        );
686        assert!(
687            !NumVal::Range {
688                lo: 0x11_0000,
689                hi: 0x20_0000
690            }
691            .is_representable()
692        );
693    }
694
695    #[test]
696    fn octet_ranges_stay_representable() {
697        // RFC 9110's `obs-text = %x80-FF` and RFC 5322's obs-* rules load and stay usable; the
698        // documented gap is that they mean code points, not bytes (SCOPE.md 3).
699        assert!(NumVal::Range { lo: 0x80, hi: 0xFF }.is_representable());
700        assert!(NumVal::Range { lo: 0, hi: 0xFF }.is_representable());
701    }
702
703    #[test]
704    fn a_concatenation_is_representable_only_if_every_element_is() {
705        assert!(NumVal::Concat(vec![0x41, 0x42, 0x43]).is_representable());
706        assert!(!NumVal::Concat(vec![0x41, 0xD800]).is_representable());
707        assert!(!NumVal::Concat(vec![0x11_0000]).is_representable());
708    }
709
710    #[test]
711    fn counting_scalars_excludes_the_surrogate_block() {
712        assert_eq!(NumVal::scalars_in(0x41, 0x41), 1);
713        assert_eq!(NumVal::scalars_in(0x41, 0x5A), 26);
714        assert_eq!(NumVal::scalars_in(0xD800, 0xDFFF), 0);
715        assert_eq!(
716            NumVal::scalars_in(0xD7FF, 0xE000),
717            2,
718            "the ends, not the 2048 between"
719        );
720        assert_eq!(NumVal::scalars_in(0, 0x10_FFFF), 0x11_0000 - 2048);
721        assert_eq!(
722            NumVal::scalars_in(0x10_FFFF, 0x20_0000),
723            1,
724            "clamped to the last scalar"
725        );
726        assert_eq!(
727            NumVal::scalars_in(5, 4),
728            0,
729            "an inverted range holds nothing"
730        );
731    }
732
733    #[test]
734    fn indexing_a_range_steps_over_the_surrogates() {
735        let at = |lo, hi, i| NumVal::nth_scalar(lo, hi, i).map(u32::from);
736        assert_eq!(at(0x41, 0x5A, 0), Some(0x41));
737        assert_eq!(at(0x41, 0x5A, 25), Some(0x5A));
738        assert_eq!(at(0x41, 0x5A, 26), None, "past the end");
739
740        // The only two scalars in 0xD7FF..=0xE000 are its endpoints.
741        assert_eq!(at(0xD7FF, 0xE000, 0), Some(0xD7FF));
742        assert_eq!(at(0xD7FF, 0xE000, 1), Some(0xE000));
743        assert_eq!(at(0xD7FF, 0xE000, 2), None);
744
745        // A range starting inside the block begins at the first scalar above it.
746        assert_eq!(at(0xDC00, 0xE001, 0), Some(0xE000));
747        assert_eq!(at(0xD800, 0xDFFF, 0), None);
748    }
749
750    #[test]
751    fn every_index_of_a_range_yields_a_distinct_scalar() {
752        // The generator picks uniformly by index, so walk a range that spans the surrogate
753        // block from end to end and check the sequence exactly.
754        let (lo, hi) = (0xD7FD, 0xE002);
755        let count = NumVal::scalars_in(lo, hi);
756        let scalars: Vec<u32> = (0..count)
757            .map(|i| u32::from(NumVal::nth_scalar(lo, hi, i).expect("in range")))
758            .collect();
759        assert_eq!(scalars, [0xD7FD, 0xD7FE, 0xD7FF, 0xE000, 0xE001, 0xE002]);
760    }
761
762    #[test]
763    fn min_len_orders_finite_below_infinite() {
764        assert!(MinLen::Finite(u64::MAX) < MinLen::Infinite);
765        assert!(MinLen::Finite(1) < MinLen::Finite(2));
766        assert!(MinLen::ZERO.is_finite());
767        assert!(!MinLen::Infinite.is_finite());
768    }
769
770    #[test]
771    fn min_len_arithmetic_saturates() {
772        assert_eq!(
773            MinLen::Finite(2).saturating_add(MinLen::Finite(3)),
774            MinLen::Finite(5)
775        );
776        assert_eq!(
777            MinLen::Finite(u64::MAX).saturating_add(MinLen::Finite(1)),
778            MinLen::Finite(u64::MAX)
779        );
780        assert_eq!(
781            MinLen::Finite(1).saturating_add(MinLen::Infinite),
782            MinLen::Infinite
783        );
784        assert_eq!(
785            MinLen::Finite(u64::MAX).saturating_mul(2),
786            MinLen::Finite(u64::MAX),
787            "a saturated value is still finite, hence still productive"
788        );
789    }
790
791    #[test]
792    fn zero_repetitions_of_an_unproductive_body_match_the_empty_string() {
793        // `*bad` is productive even when `bad` is not: zero copies match the empty string.
794        assert_eq!(MinLen::Infinite.saturating_mul(0), MinLen::ZERO);
795        assert_eq!(MinLen::Infinite.saturating_mul(1), MinLen::Infinite);
796    }
797
798    #[test]
799    fn min_picks_the_shorter_alternative() {
800        assert_eq!(MinLen::Finite(3).min(MinLen::Finite(1)), MinLen::Finite(1));
801        assert_eq!(MinLen::Infinite.min(MinLen::Finite(7)), MinLen::Finite(7));
802        assert_eq!(MinLen::Finite(7).min(MinLen::Infinite), MinLen::Finite(7));
803        assert_eq!(MinLen::Infinite.min(MinLen::Infinite), MinLen::Infinite);
804    }
805}