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}