Skip to main content

rexact_core/
term.rs

1//! Extended regular-expression terms with Brzozowski derivatives
2//! (architecture section 3, FR-B01, FR-B02, FR-B07, FR-B08).
3//!
4//! Contract: terms live in a [`TermStore`] that interns them, so equal terms
5//! share one [`TermId`]. Smart constructors keep terms normalised: unions and
6//! intersections are flattened, sorted and de-duplicated, concatenation is
7//! right-nested, `Empty`/`Epsilon` are absorbed, and `Star(Star(x)) = Star(x)`,
8//! `Not(Not(x)) = x`. Derivatives of normalised terms stay inside a finite set,
9//! which is what makes the automaton construction terminate.
10//!
11//! Zero-width constructs (M6):
12//! * `Assert` (anchors, word boundaries) are evaluated against a [`Ctx`] that
13//!   remembers whether the input just started, which auxiliary character sets
14//!   the previous character belonged to, and the state of every lookbehind
15//!   tracker. The derivative therefore takes the context and the next
16//!   character; at the end of input, acceptance is evaluated with no next
17//!   character.
18//! * `LookAhead(X)` has no meaning on its own: whatever follows it in the
19//!   enclosing pattern decides. Its derivative is therefore computed with an
20//!   explicit continuation `k` (everything that follows): `(?=X)·k` has the
21//!   language `(X·Σ*) ∩ k` and `(?!X)·k` has `¬(X·Σ*) ∩ k`, so
22//!   `d((?=X)·k) = d(X·Σ*) ∩ d(k)`. A lookahead left at the very end of a term
23//!   holds iff `X` accepts the empty remainder. Recursion through zero-width
24//!   loops is cut with a re-entrancy guard that returns the empty language,
25//!   which is the least fixpoint of the (monotone) equations.
26//! * `LookBehind(X)` holds iff the consumed input ends with a match of `X`.
27//!   It is answered by a tracker automaton for `Σ*·X` kept in the context.
28//!
29//! Invariants: every `Class` set is non-empty and a subset of the universe;
30//! `Not` complements relative to `universe*`; lookbehind bodies contain no
31//! context-dependent construct.
32//!
33//! Failure modes: constructors are total and never panic. Registering more
34//! than [`MAX_AUX`] distinct auxiliary sets or [`MAX_TRACKERS`] lookbehinds
35//! returns `None` so the caller can report `RX-U005`.
36
37use crate::charset::{refine, refine_all, CharSet};
38use std::collections::HashMap;
39use std::sync::Arc;
40
41/// Handle to an interned term.
42#[derive(Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord, Debug)]
43pub struct TermId(u32);
44
45impl TermId {
46    /// Dense index, for tables keyed by term.
47    pub fn index(self) -> usize {
48        self.0 as usize
49    }
50}
51
52/// Marks an unused auxiliary-set slot in an assertion.
53pub const NO_AUX: u8 = 255;
54/// Most distinct auxiliary sets (word / newline definitions) in one store.
55pub const MAX_AUX: usize = 48;
56/// Most lookbehind trackers in one store.
57pub const MAX_TRACKERS: usize = 16;
58
59/// Zero-width assertions on the neighbouring characters.
60#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)]
61pub enum AssertKind {
62    /// Start of the input.
63    TextStart,
64    /// End of the input.
65    TextEnd,
66    /// Start of input or just after a character of the line set.
67    LineStart,
68    /// End of input or just before a character of the line set.
69    LineEnd,
70    /// Previous and next characters differ in membership of the word set.
71    WordBoundary,
72    /// Previous and next characters agree in membership of the word set.
73    NotWordBoundary,
74    /// Not after a word character, before a word character.
75    WordStart,
76    /// After a word character, not before one.
77    WordEnd,
78}
79
80impl AssertKind {
81    /// Letter used in the interchange format.
82    pub const fn letter(self) -> char {
83        match self {
84            AssertKind::TextStart => 'A',
85            AssertKind::TextEnd => 'z',
86            AssertKind::LineStart => '^',
87            AssertKind::LineEnd => '$',
88            AssertKind::WordBoundary => 'b',
89            AssertKind::NotWordBoundary => 'B',
90            AssertKind::WordStart => '<',
91            AssertKind::WordEnd => '>',
92        }
93    }
94}
95
96/// Shape of a term.
97#[derive(Clone, PartialEq, Eq, Hash, Debug)]
98pub enum TermKind {
99    /// The empty language.
100    Empty,
101    /// Only the empty string.
102    Epsilon,
103    /// One symbol from a non-empty set.
104    Class(CharSet),
105    /// Concatenation; the left side is never itself a `Concat`.
106    Concat(TermId, TermId),
107    /// Union of at least two distinct, sorted, non-`Union` terms.
108    Union(Arc<[TermId]>),
109    /// Kleene star.
110    Star(TermId),
111    /// Intersection of at least two distinct, sorted, non-`Inter` terms.
112    Inter(Arc<[TermId]>),
113    /// Complement relative to all strings over the universe.
114    Not(TermId),
115    /// A zero-width assertion with the indices of its auxiliary sets.
116    Assert {
117        /// Which assertion.
118        kind: AssertKind,
119        /// Index of the word set ([`NO_AUX`] when unused).
120        word: u8,
121        /// Index of the line-terminator set ([`NO_AUX`] when unused).
122        line: u8,
123    },
124    /// `(?=body)` / `(?!body)`.
125    LookAhead {
126        /// Negative form.
127        neg: bool,
128        /// The looked-at pattern, complete in itself.
129        body: TermId,
130    },
131    /// `(?<=body)` / `(?<!body)`, answered by tracker number `tracker`.
132    LookBehind {
133        /// Negative form.
134        neg: bool,
135        /// Index into the store's tracker table.
136        tracker: u8,
137    },
138}
139
140struct Node {
141    kind: TermKind,
142    /// Accepts the empty string, ignoring context (only meaningful when `!ctx_dep`).
143    nullable: bool,
144    /// Contains an assertion or lookaround, so its behaviour depends on context.
145    ctx_dep: bool,
146    /// Contains a lookahead (continuation must be distributed over it).
147    has_look: bool,
148}
149
150/// What the matcher has seen before the current position.
151#[derive(Clone, PartialEq, Eq, Hash, Debug)]
152pub struct Ctx {
153    /// No character consumed yet.
154    pub start: bool,
155    /// Bit `i` set when the previous character belongs to auxiliary set `i`.
156    pub bits: u64,
157    /// State of each lookbehind tracker (derivative of `Σ*·body`).
158    pub tracks: Vec<TermId>,
159}
160
161/// A lookbehind tracker: the body and the root of its `Σ*·body` automaton.
162#[derive(Clone, Copy, Debug)]
163struct Tracker {
164    body: TermId,
165    root: TermId,
166}
167
168/// Interning store and derivative engine.
169pub struct TermStore {
170    nodes: Vec<Node>,
171    index: HashMap<TermKind, TermId>,
172    universe: CharSet,
173    empty: TermId,
174    epsilon: TermId,
175    universal: TermId,
176    deriv_cache: HashMap<(TermId, u32, u32), TermId>,
177    part_cache: HashMap<TermId, Arc<Vec<CharSet>>>,
178    look_cache: HashMap<(TermId, u32, u32), TermId>,
179    aux_sets: Vec<CharSet>,
180    trackers: Vec<Tracker>,
181    ctxs: Vec<Ctx>,
182    ctx_index: HashMap<Ctx, u32>,
183    node_bytes: u64,
184}
185
186/// Id of the context used for terms that do not depend on context.
187pub const NO_CTX: u32 = 0;
188
189impl TermStore {
190    /// New store over the given universe of symbols.
191    pub fn new(universe: CharSet) -> Self {
192        let mut s = TermStore {
193            nodes: Vec::new(),
194            index: HashMap::new(),
195            universe,
196            empty: TermId(0),
197            epsilon: TermId(0),
198            universal: TermId(0),
199            deriv_cache: HashMap::new(),
200            part_cache: HashMap::new(),
201            look_cache: HashMap::new(),
202            aux_sets: Vec::new(),
203            trackers: Vec::new(),
204            ctxs: Vec::new(),
205            ctx_index: HashMap::new(),
206            node_bytes: 0,
207        };
208        s.empty = s.intern(TermKind::Empty);
209        s.epsilon = s.intern(TermKind::Epsilon);
210        s.universal = s.intern(TermKind::Not(s.empty));
211        // Placeholder for terms that do not depend on context: a value no real context can have.
212        s.intern_ctx(Ctx {
213            start: false,
214            bits: u64::MAX,
215            tracks: Vec::new(),
216        });
217        s
218    }
219
220    /// The universe of symbols.
221    pub fn universe(&self) -> &CharSet {
222        &self.universe
223    }
224    /// Number of interned terms (used for memory estimates).
225    pub fn len(&self) -> usize {
226        self.nodes.len()
227    }
228    /// Estimated heap bytes held by terms and memo tables (for the memory limit).
229    pub fn approx_bytes(&self) -> u64 {
230        self.node_bytes
231            + 64 * (self.deriv_cache.len() + self.part_cache.len() + self.look_cache.len()) as u64
232    }
233    /// True when only the three built-in terms exist.
234    pub fn is_empty(&self) -> bool {
235        self.nodes.len() <= 3
236    }
237    /// The empty language.
238    pub fn empty(&self) -> TermId {
239        self.empty
240    }
241    /// The language containing only the empty string.
242    pub fn epsilon(&self) -> TermId {
243        self.epsilon
244    }
245    /// All strings over the universe.
246    pub fn universal(&self) -> TermId {
247        self.universal
248    }
249    /// Shape of a term.
250    pub fn kind(&self, t: TermId) -> &TermKind {
251        &self.nodes[t.index()].kind
252    }
253    /// Does the term accept the empty string, ignoring context? Exact only when
254    /// [`TermStore::ctx_dep`] is false; use [`TermStore::accepting`] otherwise.
255    pub fn nullable(&self, t: TermId) -> bool {
256        self.nodes[t.index()].nullable
257    }
258    /// Does the term contain an assertion or lookaround?
259    pub fn ctx_dep(&self, t: TermId) -> bool {
260        self.nodes[t.index()].ctx_dep
261    }
262    /// The auxiliary sets referenced by assertions, by index.
263    pub fn aux_sets(&self) -> &[CharSet] {
264        &self.aux_sets
265    }
266    /// Body of lookbehind tracker `i`.
267    pub fn tracker_body(&self, i: u8) -> Option<TermId> {
268        self.trackers.get(i as usize).map(|t| t.body)
269    }
270
271    fn intern(&mut self, kind: TermKind) -> TermId {
272        if let Some(&id) = self.index.get(&kind) {
273            return id;
274        }
275        self.node_bytes += 128
276            + match &kind {
277                TermKind::Union(v) | TermKind::Inter(v) => 8 * v.len() as u64,
278                _ => 0,
279            };
280        let n = |s: &Self, x: TermId| (s.nullable(x), s.ctx_dep(x), s.nodes[x.index()].has_look);
281        let (nullable, ctx_dep, has_look) = match &kind {
282            TermKind::Empty | TermKind::Class(_) => (false, false, false),
283            TermKind::Epsilon | TermKind::Star(_) => {
284                let child = if let TermKind::Star(x) = &kind {
285                    n(self, *x)
286                } else {
287                    (true, false, false)
288                };
289                (true, child.1, child.2)
290            }
291            TermKind::Concat(a, b) => {
292                let (x, y) = (n(self, *a), n(self, *b));
293                (x.0 && y.0, x.1 || y.1, x.2 || y.2)
294            }
295            TermKind::Union(v) => {
296                let cs: Vec<_> = v.iter().map(|&x| n(self, x)).collect();
297                (
298                    cs.iter().any(|c| c.0),
299                    cs.iter().any(|c| c.1),
300                    cs.iter().any(|c| c.2),
301                )
302            }
303            TermKind::Inter(v) => {
304                let cs: Vec<_> = v.iter().map(|&x| n(self, x)).collect();
305                (
306                    cs.iter().all(|c| c.0),
307                    cs.iter().any(|c| c.1),
308                    cs.iter().any(|c| c.2),
309                )
310            }
311            TermKind::Not(x) => {
312                let c = n(self, *x);
313                (!c.0, c.1, c.2)
314            }
315            TermKind::Assert { .. } | TermKind::LookBehind { .. } => (false, true, false),
316            TermKind::LookAhead { .. } => (false, true, true),
317        };
318        let id = TermId(self.nodes.len() as u32);
319        self.nodes.push(Node {
320            kind: kind.clone(),
321            nullable,
322            ctx_dep,
323            has_look,
324        });
325        self.index.insert(kind, id);
326        id
327    }
328
329    fn intern_ctx(&mut self, c: Ctx) -> u32 {
330        if let Some(&id) = self.ctx_index.get(&c) {
331            return id;
332        }
333        let id = self.ctxs.len() as u32;
334        self.ctxs.push(c.clone());
335        self.ctx_index.insert(c, id);
336        id
337    }
338
339    // ------------------------------------------------------- constructors
340
341    /// One symbol from `set` (restricted to the universe).
342    pub fn class(&mut self, set: &CharSet) -> TermId {
343        let s = set.intersect(&self.universe);
344        if s.is_empty() {
345            self.empty
346        } else {
347            self.intern(TermKind::Class(s))
348        }
349    }
350
351    /// Any one symbol of the universe.
352    pub fn any_symbol(&mut self) -> TermId {
353        let u = self.universe.clone();
354        self.class(&u)
355    }
356
357    /// Concatenation of `factors`, right-nested and normalised. Iterative along
358    /// the sequence, so long chains cannot overflow the stack.
359    pub fn concat_all(&mut self, factors: &[TermId]) -> TermId {
360        let mut acc = self.epsilon;
361        for &f in factors.iter().rev() {
362            acc = self.prepend(f, acc);
363        }
364        acc
365    }
366
367    /// `f · acc`, expanding `f`'s own concatenation chain.
368    fn prepend(&mut self, f: TermId, acc: TermId) -> TermId {
369        if f == self.empty || acc == self.empty {
370            return self.empty;
371        }
372        let mut items = Vec::new();
373        let mut cur = f;
374        loop {
375            match self.kind(cur) {
376                TermKind::Concat(a, b) => {
377                    items.push(*a);
378                    cur = *b;
379                }
380                TermKind::Epsilon => break,
381                _ => {
382                    items.push(cur);
383                    break;
384                }
385            }
386        }
387        let mut acc = acc;
388        for &item in items.iter().rev() {
389            acc = self.cons(item, acc);
390        }
391        acc
392    }
393
394    /// One factor in front of a continuation (no rewriting: lookaheads are
395    /// resolved by the continuation-passing derivative).
396    fn cons(&mut self, f: TermId, acc: TermId) -> TermId {
397        if acc == self.empty || f == self.empty {
398            return self.empty;
399        }
400        if acc == self.epsilon {
401            return f;
402        }
403        self.intern(TermKind::Concat(f, acc))
404    }
405
406    /// Binary concatenation.
407    pub fn concat(&mut self, a: TermId, b: TermId) -> TermId {
408        self.prepend(a, b)
409    }
410
411    /// Union, flattened, sorted and de-duplicated; classes are merged.
412    pub fn union(&mut self, items: &[TermId]) -> TermId {
413        let mut flat: Vec<TermId> = Vec::new();
414        let mut merged = CharSet::empty();
415        for &t in items {
416            self.collect_union(t, &mut flat, &mut merged);
417        }
418        if flat.contains(&self.universal) {
419            return self.universal;
420        }
421        if !merged.is_empty() {
422            let c = self.class(&merged);
423            flat.push(c);
424        }
425        flat.sort_unstable();
426        flat.dedup();
427        match flat.len() {
428            0 => self.empty,
429            1 => flat[0],
430            _ => self.intern(TermKind::Union(flat.into())),
431        }
432    }
433
434    fn collect_union(&self, t: TermId, flat: &mut Vec<TermId>, merged: &mut CharSet) {
435        match self.kind(t) {
436            TermKind::Empty => {}
437            TermKind::Union(v) => {
438                for &x in v.iter() {
439                    self.collect_union(x, flat, merged);
440                }
441            }
442            TermKind::Class(s) => *merged = merged.union(s),
443            _ => flat.push(t),
444        }
445    }
446
447    /// Intersection, flattened, sorted and de-duplicated; classes are merged.
448    pub fn inter(&mut self, items: &[TermId]) -> TermId {
449        let mut flat: Vec<TermId> = Vec::new();
450        let mut merged: Option<CharSet> = None;
451        for &t in items {
452            if !self.collect_inter(t, &mut flat, &mut merged) {
453                return self.empty;
454            }
455        }
456        if let Some(m) = merged {
457            let c = self.class(&m);
458            if c == self.empty {
459                return self.empty;
460            }
461            flat.push(c);
462        }
463        flat.sort_unstable();
464        flat.dedup();
465        // x & !x is empty.
466        for &t in &flat {
467            if let TermKind::Not(y) = self.kind(t) {
468                if flat.contains(y) {
469                    return self.empty;
470                }
471            }
472        }
473        match flat.len() {
474            0 => self.universal,
475            1 => flat[0],
476            _ => self.intern(TermKind::Inter(flat.into())),
477        }
478    }
479
480    /// Returns false when the intersection is certainly empty.
481    fn collect_inter(
482        &self,
483        t: TermId,
484        flat: &mut Vec<TermId>,
485        merged: &mut Option<CharSet>,
486    ) -> bool {
487        match self.kind(t) {
488            TermKind::Empty => return false,
489            TermKind::Inter(v) => {
490                for &x in v.iter() {
491                    if !self.collect_inter(x, flat, merged) {
492                        return false;
493                    }
494                }
495            }
496            TermKind::Class(s) => {
497                *merged = Some(match merged.take() {
498                    Some(m) => m.intersect(s),
499                    None => s.clone(),
500                });
501            }
502            _ if t == self.universal => {}
503            _ => flat.push(t),
504        }
505        true
506    }
507
508    /// Kleene star.
509    pub fn star(&mut self, t: TermId) -> TermId {
510        match self.kind(t).clone() {
511            TermKind::Empty | TermKind::Epsilon => self.epsilon,
512            TermKind::Star(_) => t,
513            // Repeating something that cannot consume input adds nothing to the
514            // language: zero repetitions are always allowed.
515            _ if !self.may_consume(t) => self.epsilon,
516            // (e|x)* = x*: drop epsilon from a union body.
517            TermKind::Union(v) if v.contains(&self.epsilon) => {
518                let rest: Vec<TermId> = v.iter().copied().filter(|&x| x != self.epsilon).collect();
519                let u = self.union(&rest);
520                self.star(u)
521            }
522            _ => self.intern(TermKind::Star(t)),
523        }
524    }
525
526    /// Complement.
527    pub fn not(&mut self, t: TermId) -> TermId {
528        match self.kind(t) {
529            TermKind::Not(x) => *x,
530            _ => self.intern(TermKind::Not(t)),
531        }
532    }
533
534    /// `x?`
535    pub fn opt(&mut self, t: TermId) -> TermId {
536        let e = self.epsilon;
537        self.union(&[e, t])
538    }
539
540    // --------------------------------------------------- zero-width terms
541
542    fn aux_set(&mut self, set: &CharSet) -> Option<u8> {
543        if let Some(i) = self.aux_sets.iter().position(|s| s == set) {
544            return Some(i as u8);
545        }
546        if self.aux_sets.len() >= MAX_AUX {
547            return None;
548        }
549        self.aux_sets.push(set.clone());
550        Some((self.aux_sets.len() - 1) as u8)
551    }
552
553    /// An assertion. `word` is the word-character set for word assertions,
554    /// `line` the line-terminator set for line assertions. `None` when too many
555    /// distinct sets are in use.
556    pub fn assertion(
557        &mut self,
558        kind: AssertKind,
559        word: Option<&CharSet>,
560        line: Option<&CharSet>,
561    ) -> Option<TermId> {
562        let w = match (kind, word) {
563            (
564                AssertKind::WordBoundary
565                | AssertKind::NotWordBoundary
566                | AssertKind::WordStart
567                | AssertKind::WordEnd,
568                Some(s),
569            ) => self.aux_set(s)?,
570            _ => NO_AUX,
571        };
572        let l = match (kind, line) {
573            (AssertKind::LineStart | AssertKind::LineEnd, Some(s)) => self.aux_set(s)?,
574            _ => NO_AUX,
575        };
576        Some(self.intern(TermKind::Assert {
577            kind,
578            word: w,
579            line: l,
580        }))
581    }
582
583    /// `(?=body)` or `(?!body)`; the body is a complete pattern.
584    pub fn look_ahead(&mut self, neg: bool, body: TermId) -> TermId {
585        self.intern(TermKind::LookAhead { neg, body })
586    }
587
588    /// `(?<=body)` or `(?<!body)`. `None` when the body is context-dependent or
589    /// too many trackers are in use.
590    pub fn look_behind(&mut self, neg: bool, body: TermId) -> Option<TermId> {
591        if self.ctx_dep(body) {
592            return None;
593        }
594        let tracker = match self.trackers.iter().position(|t| t.body == body) {
595            Some(i) => i,
596            None => {
597                if self.trackers.len() >= MAX_TRACKERS {
598                    return None;
599                }
600                let any = self.any_symbol();
601                let sigma = self.star(any);
602                let root = self.concat_all(&[sigma, body]);
603                self.trackers.push(Tracker { body, root });
604                self.trackers.len() - 1
605            }
606        };
607        Some(self.intern(TermKind::LookBehind {
608            neg,
609            tracker: tracker as u8,
610        }))
611    }
612
613    // ------------------------------------------------------------ context
614
615    /// Context before any input has been consumed.
616    pub fn ctx_start(&mut self) -> u32 {
617        let tracks = self.trackers.iter().map(|t| t.root).collect();
618        self.intern_ctx(Ctx {
619            start: true,
620            bits: 0,
621            tracks,
622        })
623    }
624
625    /// The context object for an id.
626    pub fn ctx(&self, id: u32) -> &Ctx {
627        &self.ctxs[id as usize]
628    }
629
630    /// Context after consuming symbol `c` in context `id`.
631    pub fn advance_ctx(&mut self, id: u32, c: u32) -> u32 {
632        let cur = self.ctxs[id as usize].clone();
633        let mut bits = 0u64;
634        for (i, s) in self.aux_sets.iter().enumerate() {
635            if s.contains(c) {
636                bits |= 1 << i;
637            }
638        }
639        let tracks: Vec<TermId> = cur
640            .tracks
641            .iter()
642            .map(|&t| self.deriv_in(t, c, NO_CTX))
643            .collect();
644        self.intern_ctx(Ctx {
645            start: false,
646            bits,
647            tracks,
648        })
649    }
650
651    fn eval_assert(&self, kind: AssertKind, w: u8, l: u8, ctx: &Ctx, next: Option<u32>) -> bool {
652        let in_next =
653            |id: u8| id != NO_AUX && next.is_some_and(|c| self.aux_sets[id as usize].contains(c));
654        let in_prev = |id: u8| id != NO_AUX && (ctx.bits >> id) & 1 == 1;
655        match kind {
656            AssertKind::TextStart => ctx.start,
657            AssertKind::TextEnd => next.is_none(),
658            AssertKind::LineStart => ctx.start || in_prev(l),
659            AssertKind::LineEnd => next.is_none() || in_next(l),
660            AssertKind::WordBoundary => in_prev(w) != in_next(w),
661            AssertKind::NotWordBoundary => in_prev(w) == in_next(w),
662            AssertKind::WordStart => !in_prev(w) && in_next(w),
663            AssertKind::WordEnd => in_prev(w) && !in_next(w),
664        }
665    }
666
667    /// Does `t` accept the empty remainder at a position described by `ctx`,
668    /// when the next input symbol is `next` (`None` = end of input)?
669    pub fn nullable_in(&self, t: TermId, ctx: &Ctx, next: Option<u32>) -> bool {
670        let mut cur = t;
671        loop {
672            let node = &self.nodes[cur.index()];
673            if !node.ctx_dep {
674                return node.nullable;
675            }
676            return match &node.kind {
677                TermKind::Concat(a, b) => {
678                    if !self.nullable_in(*a, ctx, next) {
679                        return false;
680                    }
681                    cur = *b;
682                    continue;
683                }
684                TermKind::Union(v) => v.iter().any(|&x| self.nullable_in(x, ctx, next)),
685                TermKind::Inter(v) => v.iter().all(|&x| self.nullable_in(x, ctx, next)),
686                TermKind::Not(x) => !self.nullable_in(*x, ctx, next),
687                TermKind::Star(_) => true,
688                TermKind::Assert { kind, word, line } => {
689                    self.eval_assert(*kind, *word, *line, ctx, next)
690                }
691                // A lookahead with nothing after it needs the remainder to be empty.
692                TermKind::LookAhead { neg, body } => {
693                    next.is_none() && (self.nullable_in(*body, ctx, None) != *neg)
694                }
695                TermKind::LookBehind { neg, tracker } => {
696                    let holds = ctx
697                        .tracks
698                        .get(*tracker as usize)
699                        .is_some_and(|&t| self.nodes[t.index()].nullable);
700                    holds != *neg
701                }
702                TermKind::Empty | TermKind::Class(_) => false,
703                TermKind::Epsilon => true,
704            };
705        }
706    }
707
708    /// Acceptance at the end of input in context `ctx_id`.
709    pub fn accepting(&self, t: TermId, ctx_id: u32) -> bool {
710        self.nullable_in(t, &self.ctxs[ctx_id as usize], None)
711    }
712
713    // --------------------------------------------------------- derivatives
714
715    /// Derivative of `t` with respect to `c`, for terms that do not depend on
716    /// context. For general terms use [`TermStore::deriv_in`].
717    pub fn deriv(&mut self, t: TermId, c: u32) -> TermId {
718        self.deriv_in(t, c, NO_CTX)
719    }
720
721    /// Brzozowski derivative of `t` with respect to the symbol `c`, where the
722    /// input so far is described by context `ctx_id`.
723    ///
724    /// Iterative (explicit work stack): terms from counted repetition are
725    /// thousands of levels deep, and recursion would overflow the stack
726    /// (NFR-R01).
727    pub fn deriv_in(&mut self, t: TermId, c: u32, ctx_id: u32) -> TermId {
728        enum Step {
729            Enter(TermId),
730            Exit(TermId),
731        }
732        if self.nodes[t.index()].has_look {
733            return self.deriv_look(t, c, ctx_id);
734        }
735        let ctx = self.ctxs[ctx_id as usize].clone();
736        let key = |s: &Self, x: TermId| (x, c, if s.ctx_dep(x) { ctx_id } else { NO_CTX });
737        let mut stack = vec![Step::Enter(t)];
738        while let Some(step) = stack.pop() {
739            match step {
740                Step::Enter(x) => {
741                    if self.deriv_cache.contains_key(&key(self, x)) {
742                        continue;
743                    }
744                    match self.kind(x).clone() {
745                        TermKind::Empty
746                        | TermKind::Epsilon
747                        | TermKind::Assert { .. }
748                        | TermKind::LookAhead { .. }
749                        | TermKind::LookBehind { .. } => {
750                            let k = key(self, x);
751                            self.deriv_cache.insert(k, self.empty);
752                        }
753                        TermKind::Class(s) => {
754                            let r = if s.contains(c) {
755                                self.epsilon
756                            } else {
757                                self.empty
758                            };
759                            let k = key(self, x);
760                            self.deriv_cache.insert(k, r);
761                        }
762                        TermKind::Concat(a, b) => {
763                            stack.push(Step::Exit(x));
764                            stack.push(Step::Enter(a));
765                            if self.nullable_in(a, &ctx, Some(c)) {
766                                stack.push(Step::Enter(b));
767                            }
768                        }
769                        TermKind::Union(v) | TermKind::Inter(v) => {
770                            stack.push(Step::Exit(x));
771                            stack.extend(v.iter().map(|&y| Step::Enter(y)));
772                        }
773                        TermKind::Star(y) | TermKind::Not(y) => {
774                            stack.push(Step::Exit(x));
775                            stack.push(Step::Enter(y));
776                        }
777                    }
778                }
779                Step::Exit(x) => {
780                    // Every child was resolved before this frame ran.
781                    let d = |s: &Self, y: TermId| s.deriv_cache[&key(s, y)];
782                    let r = match self.kind(x).clone() {
783                        TermKind::Concat(a, b) => {
784                            let left = self.concat(d(self, a), b);
785                            if self.nullable_in(a, &ctx, Some(c)) {
786                                let db = d(self, b);
787                                self.union(&[left, db])
788                            } else {
789                                left
790                            }
791                        }
792                        TermKind::Union(v) => {
793                            let ds: Vec<TermId> = v.iter().map(|&y| d(self, y)).collect();
794                            self.union(&ds)
795                        }
796                        TermKind::Inter(v) => {
797                            let ds: Vec<TermId> = v.iter().map(|&y| d(self, y)).collect();
798                            self.inter(&ds)
799                        }
800                        TermKind::Star(y) => self.concat(d(self, y), x),
801                        TermKind::Not(y) => self.not(d(self, y)),
802                        // Leaves never push an Exit frame.
803                        _ => self.empty,
804                    };
805                    let k = key(self, x);
806                    self.deriv_cache.insert(k, r);
807                }
808            }
809        }
810        self.deriv_cache[&key(self, t)]
811    }
812
813    /// Can the term match a non-empty string? (Conservative: `true` when unsure.)
814    pub fn may_consume(&self, t: TermId) -> bool {
815        match self.kind(t) {
816            TermKind::Empty
817            | TermKind::Epsilon
818            | TermKind::Assert { .. }
819            | TermKind::LookAhead { .. }
820            | TermKind::LookBehind { .. } => false,
821            TermKind::Class(_) | TermKind::Inter(_) | TermKind::Not(_) => true,
822            TermKind::Concat(a, b) => self.may_consume(*a) || self.may_consume(*b),
823            TermKind::Union(v) => v.iter().any(|&x| self.may_consume(x)),
824            TermKind::Star(x) => self.may_consume(*x),
825        }
826    }
827
828    /// Derivative of a term that contains lookaheads (see the module notes).
829    fn deriv_look(&mut self, t: TermId, c: u32, ctx_id: u32) -> TermId {
830        let key = (t, c, ctx_id);
831        if let Some(&r) = self.look_cache.get(&key) {
832            return r;
833        }
834        let ctx = self.ctxs[ctx_id as usize].clone();
835        let mut guard = std::collections::HashSet::new();
836        let eps = self.epsilon;
837        let r = self.dk(t, eps, c, &ctx, ctx_id, &mut guard);
838        self.look_cache.insert(key, r);
839        r
840    }
841
842    /// Derivative of `t · k` with respect to `c`.
843    fn dk(
844        &mut self,
845        t: TermId,
846        k: TermId,
847        c: u32,
848        ctx: &Ctx,
849        ctx_id: u32,
850        guard: &mut std::collections::HashSet<(TermId, TermId)>,
851    ) -> TermId {
852        // Without lookaheads on either side the ordinary derivative applies.
853        if !self.nodes[t.index()].has_look && !self.nodes[k.index()].has_look {
854            let tk = self.concat(t, k);
855            return self.deriv_in(tk, c, ctx_id);
856        }
857        if !guard.insert((t, k)) {
858            return self.empty; // zero-width cycle: least fixpoint
859        }
860        let eps = self.epsilon;
861        let r = match self.kind(t).clone() {
862            TermKind::Class(s) => {
863                if s.contains(c) {
864                    k
865                } else {
866                    self.empty
867                }
868            }
869            TermKind::Empty
870            | TermKind::Epsilon
871            | TermKind::Assert { .. }
872            | TermKind::LookBehind { .. } => self.empty,
873            TermKind::Concat(a, b) => {
874                let bk = self.concat(b, k);
875                let left = self.dk(a, bk, c, ctx, ctx_id, guard);
876                if self.nullable_in(a, ctx, Some(c)) {
877                    let right = self.dk(b, k, c, ctx, ctx_id, guard);
878                    self.union(&[left, right])
879                } else {
880                    left
881                }
882            }
883            TermKind::Union(v) => {
884                let ds: Vec<TermId> = v
885                    .iter()
886                    .map(|&x| self.dk(x, k, c, ctx, ctx_id, guard))
887                    .collect();
888                self.union(&ds)
889            }
890            TermKind::Star(x) => {
891                let tk = self.concat(t, k);
892                self.dk(x, tk, c, ctx, ctx_id, guard)
893            }
894            TermKind::Inter(v) => {
895                let ds: Vec<TermId> = v
896                    .iter()
897                    .map(|&x| self.dk(x, eps, c, ctx, ctx_id, guard))
898                    .collect();
899                let inner = self.inter(&ds);
900                self.concat(inner, k)
901            }
902            TermKind::Not(x) => {
903                let d = self.dk(x, eps, c, ctx, ctx_id, guard);
904                let inner = self.not(d);
905                self.concat(inner, k)
906            }
907            TermKind::LookAhead { neg, body } => {
908                // (?=X)·k = (X·Σ*) ∩ k  and  (?!X)·k = ¬(X·Σ*) ∩ k
909                let any = self.any_symbol();
910                let sigma = self.star(any);
911                let reach = self.concat_all(&[body, sigma]);
912                let dreach = self.dk(reach, eps, c, ctx, ctx_id, guard);
913                let dcond = if neg { self.not(dreach) } else { dreach };
914                let dk_ = self.dk(k, eps, c, ctx, ctx_id, guard);
915                self.inter(&[dcond, dk_])
916            }
917        };
918        guard.remove(&(t, k));
919        r
920    }
921
922    // ------------------------------------------------------- serialisation
923
924    fn ranges_text(s: &CharSet) -> String {
925        let mut out = String::from("[");
926        for (i, &(lo, hi)) in s.ranges().iter().enumerate() {
927            if i > 0 {
928                out.push(',');
929            }
930            if lo == hi {
931                out.push_str(&format!("{lo:X}"));
932            } else {
933                out.push_str(&format!("{lo:X}-{hi:X}"));
934            }
935        }
936        out.push(']');
937        out
938    }
939
940    /// Serialise a term in the documented interchange format shared with the
941    /// independent checker: `0` empty, `e` epsilon, `[lo-hi,lo]` hex ranges,
942    /// `(. a b)` concatenation, `(| ...)` union, `(& ...)` intersection,
943    /// `(* a)` star, `(! a)` complement, `(#K w l)` assertion (`_` for an
944    /// unused set), `(?= x)` / `(?! x)` lookahead and `(<= x)` / `(<! x)`
945    /// lookbehind. Iterative, so deep terms are fine.
946    pub fn to_spec(&self, t: TermId) -> String {
947        enum Item {
948            T(TermId),
949            S(&'static str),
950        }
951        let mut out = String::new();
952        let mut stack = vec![Item::T(t)];
953        while let Some(item) = stack.pop() {
954            match item {
955                Item::S(s) => out.push_str(s),
956                Item::T(x) => match self.kind(x) {
957                    TermKind::Empty => out.push('0'),
958                    TermKind::Epsilon => out.push('e'),
959                    TermKind::Class(s) => out.push_str(&Self::ranges_text(s)),
960                    TermKind::Concat(a, b) => {
961                        out.push_str("(. ");
962                        stack.push(Item::S(")"));
963                        stack.push(Item::T(*b));
964                        stack.push(Item::S(" "));
965                        stack.push(Item::T(*a));
966                    }
967                    TermKind::Union(v) | TermKind::Inter(v) => {
968                        out.push_str(if matches!(self.kind(x), TermKind::Union(_)) {
969                            "(|"
970                        } else {
971                            "(&"
972                        });
973                        stack.push(Item::S(")"));
974                        for &y in v.iter().rev() {
975                            stack.push(Item::T(y));
976                            stack.push(Item::S(" "));
977                        }
978                    }
979                    TermKind::Star(y) => {
980                        out.push_str("(* ");
981                        stack.push(Item::S(")"));
982                        stack.push(Item::T(*y));
983                    }
984                    TermKind::Not(y) => {
985                        out.push_str("(! ");
986                        stack.push(Item::S(")"));
987                        stack.push(Item::T(*y));
988                    }
989                    TermKind::Assert { kind, word, line } => {
990                        let set = |i: u8| {
991                            if i == NO_AUX {
992                                "_".to_string()
993                            } else {
994                                Self::ranges_text(&self.aux_sets[i as usize])
995                            }
996                        };
997                        out.push_str(&format!(
998                            "(#{} {} {})",
999                            kind.letter(),
1000                            set(*word),
1001                            set(*line)
1002                        ));
1003                    }
1004                    TermKind::LookAhead { neg, body } => {
1005                        out.push_str(if *neg { "(?! " } else { "(?= " });
1006                        stack.push(Item::S(")"));
1007                        stack.push(Item::T(*body));
1008                    }
1009                    TermKind::LookBehind { neg, tracker } => {
1010                        out.push_str(if *neg { "(<! " } else { "(<= " });
1011                        stack.push(Item::S(")"));
1012                        if let Some(b) = self.tracker_body(*tracker) {
1013                            stack.push(Item::T(b));
1014                        }
1015                    }
1016                },
1017            }
1018        }
1019        out
1020    }
1021
1022    // ----------------------------------------------------------- partition
1023
1024    /// Coarsest partition of the universe such that every class set occurring
1025    /// in `t` (including the sets used by assertions) is a union of blocks (the
1026    /// term's symbol classes, FR-B02). Blocks are ordered by smallest member.
1027    /// Derivatives never introduce new sets, so the partition of the start term
1028    /// serves for the whole automaton. Iterative for the same reason as
1029    /// [`TermStore::deriv_in`].
1030    pub fn partition(&mut self, t: TermId) -> Arc<Vec<CharSet>> {
1031        enum Step {
1032            Enter(TermId),
1033            Exit(TermId),
1034        }
1035        let mut stack = vec![Step::Enter(t)];
1036        while let Some(step) = stack.pop() {
1037            match step {
1038                Step::Enter(x) => {
1039                    if self.part_cache.contains_key(&x) {
1040                        continue;
1041                    }
1042                    match self.kind(x).clone() {
1043                        TermKind::Empty | TermKind::Epsilon => {
1044                            let whole = Arc::new(vec![self.universe.clone()]);
1045                            self.part_cache.insert(x, whole);
1046                        }
1047                        TermKind::Class(s) => {
1048                            let rest = self.universe.difference(&s);
1049                            let mut v = vec![s];
1050                            if !rest.is_empty() {
1051                                v.push(rest);
1052                            }
1053                            v.sort_by_key(|b| b.smallest());
1054                            self.part_cache.insert(x, Arc::new(v));
1055                        }
1056                        TermKind::Assert { word, line, .. } => {
1057                            let mut p = vec![self.universe.clone()];
1058                            for id in [word, line] {
1059                                if id != NO_AUX {
1060                                    p = refine(&p, &self.aux_sets[id as usize]);
1061                                }
1062                            }
1063                            p.sort_by_key(|b| b.smallest());
1064                            self.part_cache.insert(x, Arc::new(p));
1065                        }
1066                        TermKind::Concat(a, b) => {
1067                            stack.push(Step::Exit(x));
1068                            stack.push(Step::Enter(a));
1069                            stack.push(Step::Enter(b));
1070                        }
1071                        TermKind::Union(v) | TermKind::Inter(v) => {
1072                            stack.push(Step::Exit(x));
1073                            stack.extend(v.iter().map(|&y| Step::Enter(y)));
1074                        }
1075                        TermKind::Star(y)
1076                        | TermKind::Not(y)
1077                        | TermKind::LookAhead { body: y, .. } => {
1078                            stack.push(Step::Exit(x));
1079                            stack.push(Step::Enter(y));
1080                        }
1081                        TermKind::LookBehind { tracker, .. } => {
1082                            stack.push(Step::Exit(x));
1083                            if let Some(b) = self.tracker_body(tracker) {
1084                                stack.push(Step::Enter(b));
1085                            }
1086                        }
1087                    }
1088                }
1089                Step::Exit(x) => {
1090                    let p: Vec<CharSet> = match self.kind(x).clone() {
1091                        TermKind::Concat(a, b) => {
1092                            refine_all(&self.part_cache[&a], &self.part_cache[&b])
1093                        }
1094                        TermKind::Union(v) | TermKind::Inter(v) => {
1095                            let mut acc = vec![self.universe.clone()];
1096                            for y in v.iter() {
1097                                acc = refine_all(&acc, &self.part_cache[y]);
1098                            }
1099                            acc
1100                        }
1101                        TermKind::Star(y)
1102                        | TermKind::Not(y)
1103                        | TermKind::LookAhead { body: y, .. } => (*self.part_cache[&y]).clone(),
1104                        TermKind::LookBehind { tracker, .. } => match self.tracker_body(tracker) {
1105                            Some(b) => (*self.part_cache[&b]).clone(),
1106                            None => vec![self.universe.clone()],
1107                        },
1108                        _ => vec![self.universe.clone()],
1109                    };
1110                    self.part_cache.insert(x, Arc::new(p));
1111                }
1112            }
1113        }
1114        self.part_cache[&t].clone()
1115    }
1116}
1117
1118#[cfg(test)]
1119mod tests {
1120    use super::*;
1121
1122    fn store() -> TermStore {
1123        TermStore::new(CharSet::range(0, 0x7F))
1124    }
1125    fn ch(s: &mut TermStore, c: char) -> TermId {
1126        s.class(&CharSet::single(c as u32))
1127    }
1128
1129    #[test]
1130    fn interning_gives_one_id_per_term() {
1131        let mut s = store();
1132        let (a1, a2) = (ch(&mut s, 'a'), ch(&mut s, 'a'));
1133        assert_eq!(a1, a2);
1134    }
1135
1136    #[test]
1137    fn normalisation_laws() {
1138        let mut s = store();
1139        let (a, b, c) = (ch(&mut s, 'a'), ch(&mut s, 'b'), ch(&mut s, 'c'));
1140        let (e, z) = (s.epsilon(), s.empty());
1141        // union: idempotent, commutative, associative, absorbs Empty
1142        assert_eq!(s.union(&[a, a]), a);
1143        assert_eq!(s.union(&[a, b]), s.union(&[b, a]));
1144        let ab = s.union(&[a, b]);
1145        let bc = s.union(&[b, c]);
1146        assert_eq!(s.union(&[ab, c]), s.union(&[a, bc]));
1147        assert_eq!(s.union(&[a, z]), a);
1148        // concat: identity, annihilator, associativity
1149        assert_eq!(s.concat(a, e), a);
1150        assert_eq!(s.concat(e, a), a);
1151        assert_eq!(s.concat(a, z), z);
1152        let (x, y) = (s.concat(a, b), s.concat(b, c));
1153        assert_eq!(s.concat(x, c), s.concat(a, y));
1154        // star, not
1155        let sa = s.star(a);
1156        assert_eq!(s.star(sa), sa);
1157        assert_eq!(s.star(z), e);
1158        let na = s.not(a);
1159        assert_eq!(s.not(na), a);
1160        let u = s.universal();
1161        assert_eq!(s.union(&[a, u]), u);
1162        assert_eq!(s.inter(&[a, u]), a);
1163        assert_eq!(s.inter(&[a, na]), z);
1164        // classes merge: a|b is the single class [ab]
1165        assert!(matches!(s.kind(ab), TermKind::Class(set) if set.count() == 2));
1166    }
1167
1168    #[test]
1169    fn nullable_follows_the_definitions() {
1170        let mut s = store();
1171        let a = ch(&mut s, 'a');
1172        let (e, z) = (s.epsilon(), s.empty());
1173        let sa = s.star(a);
1174        let na = s.not(a);
1175        assert!(!s.nullable(a) && s.nullable(e) && !s.nullable(z) && s.nullable(sa));
1176        assert!(s.nullable(na)); // "" is not 'a'
1177        let u = s.universal();
1178        assert!(s.nullable(u));
1179    }
1180
1181    #[test]
1182    fn derivative_of_a_star_b() {
1183        let mut s = store();
1184        let (a, b) = (ch(&mut s, 'a'), ch(&mut s, 'b'));
1185        let sa = s.star(a);
1186        let t = s.concat(sa, b); // a*b
1187        assert_eq!(s.deriv(t, 'a' as u32), t);
1188        assert_eq!(s.deriv(t, 'b' as u32), s.epsilon());
1189        assert_eq!(s.deriv(t, 'c' as u32), s.empty());
1190    }
1191
1192    #[test]
1193    fn partition_separates_exactly_what_the_term_distinguishes() {
1194        let mut s = store();
1195        let (a, b) = (ch(&mut s, 'a'), ch(&mut s, 'b'));
1196        let t = s.concat(a, b);
1197        let p = s.partition(t);
1198        // {a}, {b}, everything else
1199        assert_eq!(p.len(), 3);
1200        assert_eq!(p.iter().map(|x| x.count()).sum::<u64>(), 128);
1201    }
1202
1203    #[test]
1204    fn long_concat_chain_does_not_recurse() {
1205        let mut s = store();
1206        let a = ch(&mut s, 'a');
1207        let t = s.concat_all(&vec![a; 100_000]);
1208        assert!(!s.nullable(t));
1209    }
1210}