regex_anre/object.rs
1// Copyright (c) 2025 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::transition::Transition;
8
9pub const MAIN_ROUTE_INDEX: usize = 0;
10
11// The `Map` structure:
12//
13// ```diagram
14// Map
15// |
16// |-- Main route --\
17// | |-- node 0 --\
18// | | |- path --> points to the next node
19// | | |- path --> points to the next node
20// | | |- ...
21// | | \- path --> points to the next node
22// | |-- node 1
23// | |-- ...
24// | |-- node N
25// |
26// |-- Sub route (generated by lookaround assertions)
27// |-- Sub route (generated by lookaround assertions)
28// ```
29
30// The `Map` represents the result of compilation.
31//
32// One regular expression generates one map.
33#[derive(Debug)]
34pub struct Map {
35 // Generally, one regular expression consists of a series of nodes, which forming a "route."
36 // However, "lookahead assertions" and "lookbehind assertions" in the regular expression
37 // generate sub-routes. Therefore, one map may contains multiple routes,
38 // in which the first route is the main route, and others are sub-routes.
39 pub routes: Vec<Route>,
40
41 // The capture groups with optional names.
42 //
43 // All capture groups have a unique index, and some of them may have a name.
44 // This vector is used to store the names of capture groups, where
45 // the index of the name corresponds to the capture group index.
46 pub capture_groups: Vec<Option<String>>,
47
48 pub repetition_counter_count: usize,
49}
50
51// A `Route` represents a series of nodes.
52#[derive(Debug)]
53pub struct Route {
54 // A route consists of multiple nodes.
55 // Each node contains multiple transitions (or character patterns),
56 // which are conceptually similar to functions in programming languages.
57 pub nodes: Vec<Node>,
58
59 // The index of the entry node in the node list.
60 pub entry_node_index: usize,
61
62 // The index of the exit node in the node list.
63 pub exit_node_index: usize,
64
65 // True if the expression starts with the line begin boundary assertion `^`,
66 // meaning that if a route match fails,
67 // the process will not move the beginning point forward (one character) and try matching again.
68 // This is also true for "lookahead assertions" and "lookbehind assertions" sub-routes,
69 // thus the lookbehind assertion can only match a fixed number of characters in ANRE.
70 pub is_fixed_cursor_begin_position: bool,
71}
72
73// A route consists of multiple nodes.
74// Each node has one or more paths to the next node,
75// except for the `exit node`, which is the end of the route and has no path.
76#[derive(Debug)]
77pub struct Node {
78 pub path: Vec<Path>,
79}
80
81// Represents a transition item and the index of the next node to go to when the transition executes successfully.
82#[derive(Debug)]
83pub struct Path {
84 // The transition object.
85 // A transition represents a condition to be satisfied for the route to continue to the next node.
86 // It is similar to the condition in an `if` statement in programming languages.
87 pub transition: Transition,
88
89 // The index of the next node to go to when the transition executes successfully.
90 pub target_node_index: usize,
91}
92
93impl Map {
94 pub fn new() -> Self {
95 Map {
96 routes: vec![],
97 capture_groups: vec![],
98 repetition_counter_count: 0,
99 }
100 }
101
102 // Creates a route object and returns its index.
103 pub fn create_route(&mut self) -> usize {
104 let route = Route {
105 nodes: vec![],
106 entry_node_index: 0,
107 exit_node_index: 0,
108 is_fixed_cursor_begin_position: false,
109 };
110
111 let idx = self.routes.len();
112 self.routes.push(route);
113 idx
114 }
115
116 // Creates a capture group object and returns its index.
117 pub fn create_capture_group(&mut self, name: Option<String>) -> usize {
118 let idx = self.capture_groups.len();
119 self.capture_groups.push(name);
120 idx
121 }
122
123 pub fn create_repetition_counter(&mut self) -> usize {
124 let idx = self.repetition_counter_count;
125 self.repetition_counter_count += 1;
126 idx
127 }
128
129 // Retrieves the index of a capture group by its name.
130 //
131 // Returns `None` if there is no capture group with the specified name.
132 pub fn get_capture_group_index_by_name(&self, name: &str) -> Option<usize> {
133 self.capture_groups.iter().position(|item| match item {
134 Some(n) => n == name,
135 None => false,
136 })
137 }
138
139 // Retrieves the name of a capture group by its index.
140 //
141 // Returns `None` if there is no capture group name with the specified index.
142 pub fn get_capture_group_name_by_index(&self, index: usize) -> Option<&str> {
143 let opt_name = &self.capture_groups[index];
144 if let Some(name) = opt_name {
145 Some(name.as_str())
146 } else {
147 None
148 }
149 }
150
151 // For debugging: generates a textual representation of the map.
152 pub fn get_debug_text(&self) -> String {
153 let mut buffer = vec![];
154
155 // Routes
156 if self.routes.len() == 1 {
157 buffer.push(self.routes[0].get_debug_text());
158 } else {
159 for (route_index, route) in self.routes.iter().enumerate() {
160 buffer.push(format!("= route: ${}", route_index));
161 buffer.push(route.get_debug_text());
162 buffer.push("".to_string());
163 }
164 }
165
166 // Capture groups
167 for (capture_group_index, opt_capture_group_name) in self.capture_groups.iter().enumerate()
168 {
169 let s = if let Some(name) = &opt_capture_group_name {
170 format!("# capture: {{{}}}, name: {}", capture_group_index, name)
171 } else {
172 format!("# capture: {{{}}}", capture_group_index)
173 };
174 buffer.push(s);
175 }
176
177 buffer.join("\n")
178 }
179}
180
181impl Default for Map {
182 fn default() -> Self {
183 Self::new()
184 }
185}
186
187impl Route {
188 // Creates a node object and returns its index.
189 pub fn create_node(&mut self) -> usize {
190 let node = Node { path: vec![] };
191 let idx = self.nodes.len();
192 self.nodes.push(node);
193
194 idx
195 }
196
197 // Creates a path object and returns its index.
198 pub fn create_path(
199 &mut self,
200 source_node_index: usize,
201 target_node_index: usize,
202 transition: Transition,
203 ) -> usize {
204 let transition_item = Path {
205 transition,
206 target_node_index,
207 };
208
209 let idx = self.nodes[source_node_index].path.len();
210
211 self.nodes[source_node_index].path.push(transition_item);
212
213 idx
214 }
215
216 // For debugging: generates a textual representation of the route.
217 pub fn get_debug_text(&self) -> String {
218 let mut buffer = vec![];
219
220 for (node_index, node) in self.nodes.iter().enumerate() {
221 // Node
222 if node_index == self.entry_node_index {
223 buffer.push(format!("* node: {} (in)", node_index));
224 } else if node_index == self.exit_node_index {
225 buffer.push(format!("* node: {} (out)", node_index));
226 } else {
227 buffer.push(format!("* node: {}", node_index));
228 }
229
230 // Transition items
231 for transition_item in &node.path {
232 let s = format!(
233 " - {} -> {}",
234 transition_item.transition, transition_item.target_node_index
235 );
236 buffer.push(s);
237 }
238 }
239
240 buffer.join("\n")
241 }
242}
243
244// Component is a logical concept in the compiler, it is used to wrap the nodes and transitions generated by an expression,
245// and provide a unified interface (an "in port" and an "out port") for connecting with other components.
246//
247// In the code views, a component is just a pair of `Node` objects.
248//
249// The following diagram illustrates the simplest case of a component, which is
250// a component that contains a single literal transition.
251//
252// ```diagram
253// /-----------------------------\
254// | literal |
255// | | transition |
256// | v |
257// =====o==-------------------==o=====
258// | in node out node |
259// | |
260// \----- literal component -----/
261// ```
262//
263// Component can be nested, for example a group component can contain another group component.
264//
265// The following diagram illustrates a group component that wraps components
266// and connects them with jump transitions.
267//
268// ```diagram
269// /-------------------------------------------------------------\
270// | jump other components |
271// | | transition | and transitions |
272// | | | |
273// | /-----------\ | /-----------\ | /-----------\ |
274// =====o in out o==-----==o in out o==.....==o in out o=====
275// | \-----------/ \-----------/ \-----------/ |
276// | component component component |
277// | |
278// \----------------------- group component ---------------------/
279// ```
280//
281// A regular expression is compiled into
282// nodes and transitions, but in the program views, they are wrapped into components, at the
283// root level, a program is a single component, and the "in port" of the component is the
284// entry point of the program, and the "out port" of the component is the exit point of the program.
285//
286// The following diagram illustrates a program component that wraps a root expression component
287// with indexed capturing transitions.
288//
289// ```diagram
290// /---------------------------------------------------------\
291// | |
292// | capture start | capture end | |
293// | transition | transition | |
294// | V /-------------\ v |
295// =====o==-------------===o in out o===-------------==o=====
296// | in \-------------/ out |
297// | node root expression node |
298// | component |
299// | |
300// \------------------- program component--------------------/
301// ```
302pub struct Component {
303 pub in_node_index: usize,
304 pub out_node_index: usize,
305}
306
307impl Component {
308 pub fn new(in_node_index: usize, out_node_index: usize) -> Self {
309 Component {
310 in_node_index,
311 out_node_index,
312 }
313 }
314}
315
316#[cfg(test)]
317mod tests {
318 use pretty_assertions::{assert_eq, assert_str_eq};
319
320 use crate::{
321 object::Map,
322 transition::{CharTransition, Transition},
323 };
324
325 #[test]
326 fn test_object_file_create_route() {
327 let mut object = Map::new();
328
329 // Create a route
330 {
331 let route_index = object.create_route();
332 let route = &mut object.routes[route_index];
333
334 // Create a node
335 let _idx0 = route.create_node();
336
337 assert_str_eq!(
338 route.get_debug_text(),
339 "\
340* node: 0 (in)"
341 );
342
343 // Create other nodes
344 let idx1 = route.create_node();
345 let _idx2 = route.create_node();
346 let idx3 = route.create_node();
347
348 route.entry_node_index = idx1;
349 route.exit_node_index = idx3;
350
351 assert_str_eq!(
352 route.get_debug_text(),
353 "\
354* node: 0
355* node: 1 (in)
356* node: 2
357* node: 3 (out)"
358 );
359 }
360
361 // Create another route
362 {
363 let route_index = object.create_route();
364 let route = &mut object.routes[route_index];
365
366 // Create a node
367 let _idx0 = route.create_node();
368
369 assert_str_eq!(
370 object.get_debug_text(),
371 "\
372= route: $0
373* node: 0
374* node: 1 (in)
375* node: 2
376* node: 3 (out)
377
378= route: $1
379* node: 0 (in)
380"
381 );
382 }
383 }
384
385 #[test]
386 fn test_object_file_create_capture_group() {
387 let mut object = Map::new();
388 let route_index = object.create_route();
389 let route = &mut object.routes[route_index];
390
391 route.create_node();
392 route.create_node();
393
394 object.create_capture_group(None);
395 object.create_capture_group(Some("foo".to_string()));
396 object.create_capture_group(None);
397
398 assert_str_eq!(
399 object.get_debug_text(),
400 "\
401* node: 0 (in)
402* node: 1
403# capture: {0}
404# capture: {1}, name: foo
405# capture: {2}"
406 );
407
408 assert_eq!(object.get_capture_group_index_by_name("foo"), Some(1));
409 assert!(object.get_capture_group_index_by_name("bar").is_none());
410 }
411
412 #[test]
413 fn test_object_file_create_path() {
414 let mut object = Map::new();
415 let route_index = object.create_route();
416 let route = &mut object.routes[route_index];
417
418 let node_idx0 = route.create_node();
419 let node_idx1 = route.create_node();
420 let node_idx2 = route.create_node();
421 let node_idx3 = route.create_node();
422
423 let trans_idx0 = route.create_path(
424 node_idx0,
425 node_idx1,
426 Transition::Char(CharTransition::new('a')),
427 );
428
429 assert_str_eq!(
430 route.get_debug_text(),
431 "\
432* node: 0 (in)
433 - Char 'a' -> 1
434* node: 1
435* node: 2
436* node: 3"
437 );
438
439 assert_eq!(trans_idx0, 0);
440
441 let trans_idx1 = route.create_path(
442 node_idx0,
443 node_idx2,
444 Transition::Char(CharTransition::new('b')),
445 );
446
447 let trans_idx2 = route.create_path(
448 node_idx0,
449 node_idx3,
450 Transition::Char(CharTransition::new('c')),
451 );
452
453 assert_str_eq!(
454 route.get_debug_text(),
455 "\
456* node: 0 (in)
457 - Char 'a' -> 1
458 - Char 'b' -> 2
459 - Char 'c' -> 3
460* node: 1
461* node: 2
462* node: 3"
463 );
464
465 assert_eq!(trans_idx1, 1);
466 assert_eq!(trans_idx2, 2);
467
468 let trans_idx3 = route.create_path(
469 node_idx1,
470 node_idx2,
471 Transition::Char(CharTransition::new('x')),
472 );
473
474 assert_str_eq!(
475 route.get_debug_text(),
476 "\
477* node: 0 (in)
478 - Char 'a' -> 1
479 - Char 'b' -> 2
480 - Char 'c' -> 3
481* node: 1
482 - Char 'x' -> 2
483* node: 2
484* node: 3"
485 );
486
487 assert_eq!(trans_idx3, 0);
488 }
489}