Skip to main content

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}