regex-anre 2.0.0

Regex-anre is a full-featured, zero-dependency regular expression engine that supports both standard and ANRE regular expressions.
Documentation
// Copyright (c) 2026 Hemashushu <hippospark@gmail.com>, All rights reserved.
//
// This Source Code Form is subject to the terms of
// the Mozilla Public License version 2.0 and additional exceptions.
// For more details, see the LICENSE, LICENSE.additional, and CONTRIBUTING files.

use crate::object_file::Map;

/// Context for matching process.
pub struct Context<'a> {
    // The UTF-8 byte stream of the source text.
    pub bytes: &'a [u8],

    // Routines.
    //
    // A routine corresponds to a route.
    // A process has multiple routines since a `Map` can contain multiple routes,
    // where the first routine is the main routine.
    //
    // Routines are similar to threads in programming languages.
    // But at any given time in ANRE engine, only one routine is executing.
    pub routines: Vec<Routine>,

    // Pre-allocated capture group slots.
    // These are the executing results of the current process.
    pub match_range_slots: Vec<MatchRange>,

    // Stack to store repetition counts.
    //
    // Repetition transitions may be nested.
    //
    // For example:
    //
    // `(ab\d+){2,}`
    //
    // generates a route with a repetition transition that
    // contains another repetition transition.
    //
    // ```anre
    // repeat_from(
    //     (
    //          "ab",
    //          one_or_more(char_digit)
    //     ),
    //     2
    // )
    // ```
    //
    // It is necessary to store the current repetition counter for each repetition transition
    // before entering a new transition.
    // This stack is directly used by `Transition::CounterSave` and `Transition::CounterInc`.
    pub counter_stack: Vec<usize>,
}

/// Represents a routine generated by a route during process execution.
///
/// Routines are similar to threads in programming languages, but at any given time in ANRE engine,
/// only one routine is executing. When a process enters another routine, the current routine is blocked
///
/// a process may have multiple routines since a `Map` can contain multiple routes.
pub struct Routine {
    // The specified start position (inclusive, in bytes) of the source text.
    // The main routine starts at position 0, and end position is the length of the source text,
    // but sub-routines (which are generated by look-around assertions) can have different start and end positions.
    pub start_position: usize,

    // The specified end position (exclusive, in bytes) of the source text.
    pub end_position: usize,

    // Index of the corresponding route.
    pub route_index: usize,

    // Transitions are similar to functions in programming languages.
    // `transition_stack` is similar to a call stack.
    //
    // In detail, a route consists of multiple nodes, when a routine is running,
    // it traverses nodes one by one based on transitions. Each node contains one or more transitions,
    // these transitions are pushed onto this stack for backtracking purposes.
    //
    // When a transition is executed, it is popped from the stack:
    // - On failure: The next transition is popped and tried. If the stack is empty,
    //   it indicates that the route matching has failed.
    // - On success: The routine moves to the next node pointed to by the transition
    //   and pushes all transitions of the new node onto the stack. If the node is the
    //   last node (which is called the exit node) of the route, it means the route matching has succeeded.
    pub transition_stack: Vec<TransitionStackItem>,
}

impl Routine {
    pub fn new(start_position: usize, end_position: usize, route_index: usize) -> Routine {
        Routine {
            start_position,
            end_position,
            route_index,
            transition_stack: vec![],
        }
    }
}

pub struct TransitionStackItem {
    // Current position (in bytes) of the source text.
    // This position is a cursor that is updated when a transition is executed.
    pub position: usize,

    // Counter for repetition.
    pub repetition_count: usize,

    // Index of the node holding the transitions.
    pub current_node_index: usize,

    // Index of the current transition.
    pub transition_index: usize,
}

impl TransitionStackItem {
    pub fn new(
        position: usize,
        repetition_count: usize,
        current_node_index: usize,
        transition_index: usize,
    ) -> Self {
        TransitionStackItem {
            position,
            repetition_count,
            current_node_index,
            transition_index,
        }
    }
}

/// Represents the text range of capture group.
#[derive(Debug, PartialEq, Clone, Default)]
pub struct MatchRange {
    // Start position (inclusive) of the source text
    // (in bytes) for the capture group.
    pub start: usize,

    // End position (exclusive) of the source text
    // (in bytes) for the capture group.
    pub end: usize,
}

impl MatchRange {
    pub fn new(start: usize, end: usize) -> Self {
        MatchRange { start, end }
    }
}

impl<'a> Context<'a> {
    pub fn new(
        s: &'a str,
        number_of_capture_groups: usize, // Number of pre-allocated match slots.
    ) -> Self {
        let bytes = s.as_bytes();
        Self::from_bytes(bytes, number_of_capture_groups)
    }

    pub fn from_bytes(
        bytes: &'a [u8],
        number_of_capture_groups: usize, // Number of pre-allocated match slots.
    ) -> Self {
        Context {
            bytes,
            routines: vec![],
            counter_stack: vec![],

            // Allocate the vector of `MatchRange` for the capture group slots.
            match_range_slots: vec![MatchRange::default(); number_of_capture_groups],
        }
    }

    pub fn push_transitions_of_node(
        &mut self,
        map: &Map,
        node_index: usize,
        position: usize,
        repetition_count: usize,
    ) {
        let routine = self.get_current_routine_ref_mut();
        let route_index = routine.route_index;
        let node = &map.routes[route_index].nodes[node_index];
        let transition_count = node.path.len();

        for transition_index in (0..transition_count).rev() {
            routine.transition_stack.push(TransitionStackItem::new(
                position,
                repetition_count,
                node_index,
                transition_index,
            ));
        }
    }

    pub fn pop_transition_stack_item(&mut self) -> Option<TransitionStackItem> {
        self.get_current_routine_ref_mut().transition_stack.pop()
    }

    #[inline]
    pub fn get_current_routine_ref(&self) -> &Routine {
        self.routines.last().unwrap()
    }

    #[inline]
    pub fn get_current_routine_ref_mut(&mut self) -> &mut Routine {
        let count = self.routines.len();
        &mut self.routines[count - 1]
    }
}