From ecc8fd4770a961f870739cf37405cc5a4be8896a Mon Sep 17 00:00:00 2001 From: Peter Hill Date: Thu, 30 Jul 2026 14:13:11 +0100 Subject: [PATCH] Replace `Regex` with `lazy-regex` The uses of `Regex` in `formatter.rs` were not being constructed lazily or with `OnceLock`, meaning that the cost of constructing them was being paid every single time the containing function was called. Constructing them lazily gives a significant performance enhancement (~15x in some cases). Using the [`lazy-regex`](https://docs.rs/lazy-regex/latest/lazy_regex/) crate simplifies the construction of lazy+static regexes, and has some other benefits (compile time checking, for instance). --- Cargo.toml | 2 +- src/classifier.rs | 123 ++++++++++++++++++-------------------------- src/formatter.rs | 60 +++++++++------------ src/keyword_norm.rs | 43 +++++++--------- src/whitespace.rs | 18 +++---- 5 files changed, 103 insertions(+), 143 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 891d662..91dcb5f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -9,11 +9,11 @@ keywords = ["fortran", "formatter", "linter", "code-quality"] categories = ["development-tools", "command-line-utilities"] [dependencies] -regex = "1" clap = { version = "4", features = ["derive"] } rayon = "1" ignore = "0.4" toml = "0.8" serde = { version = "1", features = ["derive"] } serde_json = "1" +lazy-regex = "3.3.0" diff --git a/src/classifier.rs b/src/classifier.rs index 61fa816..299946f 100644 --- a/src/classifier.rs +++ b/src/classifier.rs @@ -1,5 +1,4 @@ -use regex::Regex; -use std::sync::OnceLock; +use lazy_regex::{regex, regex_is_match}; /// Classification of a logical line. #[derive(Debug, Clone, Copy, PartialEq, Eq)] @@ -22,22 +21,11 @@ pub enum LineKind { Blank, } -/// Helper to get or compile a case-insensitive regex, cached in a OnceLock. -macro_rules! re { - ($lock:ident, $pat:expr) => {{ - static $lock: OnceLock = OnceLock::new(); - $lock.get_or_init(|| Regex::new($pat).unwrap()) - }}; -} - /// Classify a Fypp line (starts with `#:` or `#!`). fn classify_fypp(trimmed: &str) -> LineKind { - let re_block_open = re!(FYPP_OPEN, r"(?i)^#:\s*(if|for|def|call|block|mute)\b"); - let re_block_close = re!( - FYPP_CLOSE, - r"(?i)^#:\s*(endif|endfor|enddef|endcall|endblock|endmute)\b" - ); - let re_continuation = re!(FYPP_CONT, r"(?i)^#:\s*(elif|else)\b"); + let re_block_open = regex!(r"(?i)^#:\s*(if|for|def|call|block|mute)\b"); + let re_block_close = regex!(r"(?i)^#:\s*(endif|endfor|enddef|endcall|endblock|endmute)\b"); + let re_continuation = regex!(r"(?i)^#:\s*(elif|else)\b"); if re_block_close.is_match(trimmed) { LineKind::FyppBlockClose @@ -53,10 +41,10 @@ fn classify_fypp(trimmed: &str) -> LineKind { /// Classify a preprocessor line (starts with `#` but not `#:` or `#!`). fn classify_preprocessor(trimmed: &str) -> LineKind { - let re_close = re!(CPP_CLOSE, r"(?i)^#\s*endif\b"); - let re_cont = re!(CPP_CONT, r"(?i)^#\s*else\b"); + let re_close = regex!(r"(?i)^#\s*endif\b"); + let re_cont = regex!(r"(?i)^#\s*else\b"); // Note: #else comes before this, so we won't match #elif as #else - let re_elif = re!(CPP_ELIF, r"(?i)^#\s*elif\b"); + let re_elif = regex!(r"(?i)^#\s*elif\b"); if re_close.is_match(trimmed) { LineKind::PreprocessorClose @@ -145,7 +133,7 @@ fn classify_fortran(trimmed: &str) -> LineKind { // so the underlying statement is classified. The label is kept in the output; // stripping happens for classification only. let (trimmed, had_numeric_label) = { - let re_num_label = re!(NUM_LABEL, r"^\d+\s+"); + let re_num_label = regex!(r"^\d+\s+"); if let Some(m) = re_num_label.find(trimmed) { (trimmed[m.end()..].trim_start(), true) } else { @@ -155,7 +143,7 @@ fn classify_fortran(trimmed: &str) -> LineKind { // Strip optional label prefix like "outer: " let line = { - let re_label = re!(LABEL, r"(?i)^\w+\s*:\s*"); + let re_label = regex!(r"(?i)^\w+\s*:\s*"); // Only strip if it looks like a construct label (not a keyword: like "type:") // A label must start with a letter and be followed by ":" if let Some(m) = re_label.find(trimmed) { @@ -220,8 +208,7 @@ fn classify_fortran(trimmed: &str) -> LineKind { let lower = line.to_ascii_lowercase(); // --- Block closers (check first since "end" prefix is distinctive) --- - let re_end_block = re!( - END_BLOCK, + let re_end_block = regex!( r"(?i)^end\s*(if|do|select|subroutine|function|module|submodule|program|interface|type|block|associate|where|forall|enum|critical|team)\b" ); if let Some(m) = re_end_block.find(line) { @@ -232,7 +219,7 @@ fn classify_fortran(trimmed: &str) -> LineKind { // A numeric-labeled "end do" may terminate a non-block labeled DO // ("do 10 ... 10 end do"); since "do