Skip to main content

regex_anre/
context.rs

1// Copyright (c) 2026 Hemashushu <hippospark@gmail.com>, All rights reserved.
2//
3// This Source Code Form is subject to the terms of
4// the Mozilla Public License version 2.0 and additional exceptions.
5// For more details, see the LICENSE, LICENSE.additional, and CONTRIBUTING files.
6
7use crate::object::Map;
8
9/// Context for matching process.
10pub struct Context<'a> {
11    // The UTF-8 byte stream of the source text.
12    pub bytes: &'a [u8],
13
14    // Routines.
15    //
16    // A routine corresponds to a route.
17    // A process has multiple routines since a `Map` can contain multiple routes,
18    // where the first routine is the main routine.
19    //
20    // Routines are similar to threads in programming languages.
21    // But at any given time in processor, only one routine is active, so
22    // the processor is single-threaded.
23    pub routines: Vec<Routine>,
24
25    // Pre-allocated capture group slots.
26    // These are the executing results of the current process.
27    pub matched_slots: Vec<MatchRange>,
28
29    // Pre-allocated counter slots.
30    pub counter_slots: Vec<usize>,
31}
32
33/// Represents a routine generated by a route during process execution.
34///
35/// Routines are similar to threads in programming languages, but at any given time in ANRE engine,
36/// only one routine is executing. When a process enters another routine, the current routine is blocked
37///
38/// a process may have multiple routines since a `Map` can contain multiple routes.
39pub struct Routine {
40    // The specified start position (inclusive, in bytes) of the source text.
41    //
42    // The main routine starts at position 0, and end position is the length of the source text,
43    // but sub-routines (which are generated by look-around assertions) can have different start and end positions.
44    pub range_start: usize,
45
46    // The specified end position (exclusive, in bytes) of the source text.
47    pub range_end: usize,
48
49    // Index of the corresponding route.
50    pub route_index: usize,
51
52    // Transitions are similar to functions in programming languages.
53    // `transition_stack` is similar to a call stack.
54    //
55    // In detail, a route consists of multiple nodes, when a routine is running,
56    // it traverses nodes one by one based on transitions. Each node contains one or more transitions,
57    // these transitions are pushed onto this stack for backtracking purposes.
58    //
59    // When a transition is executed, it is popped from the stack:
60    // - On failure: The next transition is popped and tried. If the stack is empty,
61    //   it indicates that the route matching has failed.
62    // - On success: The routine moves to the next node pointed to by the transition
63    //   and pushes all transitions of the new node onto the stack. If the node is the
64    //   last node (which is called the exit node) of the route, it means the route matching has succeeded.
65    pub transition_stack: Vec<TransitionStackItem>,
66}
67
68impl Routine {
69    pub fn new(range_start: usize, range_end: usize, route_index: usize) -> Routine {
70        Routine {
71            range_start,
72            range_end,
73            route_index,
74            transition_stack: vec![],
75        }
76    }
77}
78
79/// Represents an item in the transition stack of a routine.
80///
81/// The transition stack is similar to a call stack in programming languages.
82/// Note that the `cursor` and `repetition_count` should be stored in the stack item
83/// instead of the context because they are specific to the transition being executed and may change during backtracking.
84pub struct TransitionStackItem {
85    // Current position (in bytes) of the source text.
86    // This position is a cursor that is updated when a transition is executed.
87    pub cursor: usize,
88
89    // Current repetition count for the repetition transition.
90    pub repetition_count: usize,
91
92    // Index of the node holding the transitions.
93    pub current_node_index: usize,
94
95    // Index of the current transition.
96    pub transition_index: usize,
97}
98
99impl TransitionStackItem {
100    pub fn new(
101        cursor: usize,
102        repetition_count: usize,
103        current_node_index: usize,
104        transition_index: usize,
105    ) -> Self {
106        TransitionStackItem {
107            cursor,
108            repetition_count,
109            current_node_index,
110            transition_index,
111        }
112    }
113}
114
115/// Represents the text range of capture group.
116#[derive(Debug, PartialEq, Clone, Default)]
117pub struct MatchRange {
118    // Start position (inclusive) of the source text
119    // (in bytes) for the capture group.
120    pub start: usize,
121
122    // End position (exclusive) of the source text
123    // (in bytes) for the capture group.
124    pub end: usize,
125}
126
127impl MatchRange {
128    pub fn new(start: usize, end: usize) -> Self {
129        MatchRange { start, end }
130    }
131}
132
133impl<'a> Context<'a> {
134    pub fn new(
135        s: &'a str,
136        number_of_capture_groups: usize, // Number of pre-allocated match slots.
137        number_of_counters: usize,       // Number of pre-allocated counter slots.
138    ) -> Self {
139        let bytes = s.as_bytes();
140        Self::from_bytes(bytes, number_of_capture_groups, number_of_counters)
141    }
142
143    pub fn from_bytes(
144        bytes: &'a [u8],
145        number_of_capture_groups: usize, // Number of pre-allocated match slots.
146        number_of_counters: usize,       // Number of pre-allocated counter slots.
147    ) -> Self {
148        Context {
149            bytes,
150            routines: vec![],
151
152            // Allocate the vector of `usize` for the counter slots.
153            counter_slots: vec![0; number_of_counters],
154
155            // Allocate the vector of `MatchRange` for the capture group slots.
156            matched_slots: vec![MatchRange::default(); number_of_capture_groups],
157        }
158    }
159
160    pub fn push_transitions_of_node(
161        &mut self,
162        map: &Map,
163        node_index: usize,
164        cursor: usize,
165        repetition_count: usize,
166    ) {
167        let routine = self.get_current_routine_ref_mut();
168        let route_index = routine.route_index;
169        let node = &map.routes[route_index].nodes[node_index];
170        let transition_count = node.path.len();
171
172        for transition_index in (0..transition_count).rev() {
173            routine.transition_stack.push(TransitionStackItem::new(
174                cursor,
175                repetition_count,
176                node_index,
177                transition_index,
178            ));
179        }
180    }
181
182    pub fn pop_transition_stack_item(&mut self) -> Option<TransitionStackItem> {
183        self.get_current_routine_ref_mut().transition_stack.pop()
184    }
185
186    #[inline]
187    pub fn get_current_routine_ref(&self) -> &Routine {
188        self.routines.last().unwrap()
189    }
190
191    #[inline]
192    pub fn get_current_routine_ref_mut(&mut self) -> &mut Routine {
193        let count = self.routines.len();
194        &mut self.routines[count - 1]
195    }
196}