Skip to main content

perl_module/boundary/
mod.rs

1//! Boundary-aware standalone Perl module-token scanning.
2//!
3//! Find standalone occurrences of a module token on a single source line
4//! while respecting canonical (`::`) and legacy (`'`) package separator
5//! boundaries.
6
7use crate::token_core::has_standalone_module_token_boundaries;
8
9/// Byte range for a standalone module-token match in a source line.
10#[derive(Debug, Clone, Copy, PartialEq, Eq)]
11pub struct ModuleTokenRange {
12    /// Inclusive byte start offset in the scanned line.
13    pub start: usize,
14    /// Exclusive byte end offset in the scanned line.
15    pub end: usize,
16}
17
18/// Iterator over standalone `module_name` matches in `line`.
19#[derive(Debug, Clone)]
20pub struct ModuleTokenRangeIter<'a> {
21    line: &'a str,
22    module_name: &'a str,
23    search_start: usize,
24    done: bool,
25}
26
27impl<'a> Iterator for ModuleTokenRangeIter<'a> {
28    type Item = ModuleTokenRange;
29
30    fn next(&mut self) -> Option<Self::Item> {
31        if self.done || self.module_name.is_empty() || self.line.is_empty() {
32            self.done = true;
33            return None;
34        }
35
36        while self.search_start <= self.line.len() {
37            let Some(rel_pos) = self.line[self.search_start..].find(self.module_name) else {
38                break;
39            };
40
41            let start = self.search_start + rel_pos;
42            let end = start + self.module_name.len();
43            self.search_start = end;
44
45            if has_standalone_module_token_boundaries(self.line, start, end) {
46                return Some(ModuleTokenRange { start, end });
47            }
48        }
49
50        self.done = true;
51        None
52    }
53}
54
55/// Return an iterator of standalone `module_name` matches in `line`.
56#[must_use]
57pub fn find_standalone_module_token_ranges<'a>(
58    line: &'a str,
59    module_name: &'a str,
60) -> ModuleTokenRangeIter<'a> {
61    ModuleTokenRangeIter { line, module_name, search_start: 0, done: false }
62}
63
64/// Return `true` when `line` contains `module_name` as a standalone module token.
65#[must_use]
66pub fn contains_standalone_module_token(line: &str, module_name: &str) -> bool {
67    find_standalone_module_token_ranges(line, module_name).next().is_some()
68}