use std::sync::LazyLock;
use regex::Regex;
static TEXT_NUMBER: LazyLock<Regex> = LazyLock::new(|| {
Regex::new(r"[+-]?(?:\d+(?:\.\d*)?|\.\d+)(?:[eE][+-]?\d+)?")
.expect("a constant pattern compiles")
});
pub(crate) fn extract(text: &str) -> Vec<f64> {
spanned(text).into_iter().map(|(value, _)| value).collect()
}
pub(crate) fn spanned(text: &str) -> Vec<(f64, usize)> {
TEXT_NUMBER
.find_iter(text)
.filter_map(|found| {
found
.as_str()
.parse::<f64>()
.ok()
.filter(|value| value.is_finite())
.map(|value| (value, found.start()))
})
.collect()
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn plain_numbers_are_found() {
assert_eq!(extract("rate 0.0825 on 1000 units"), [0.0825, 1000.0]);
}
#[test]
fn signs_and_exponents_are_read() {
assert_eq!(extract("offset -7e2 and +3"), [-700.0, 3.0]);
}
#[test]
fn a_version_string_has_no_grammar_to_protect_it() {
assert_eq!(extract("v1.2.3"), [1.2, 0.3]);
assert_eq!(extract("3.16.15"), [3.16, 0.15]);
}
#[test]
fn text_without_numbers_yields_nothing() {
assert!(extract("no numbers on this line at all").is_empty());
}
#[test]
fn a_span_points_at_the_run() {
let text = "rate 0.0825 here";
let (value, offset) = spanned(text)[0];
assert_eq!(value, 0.0825);
assert_eq!(&text[offset..offset + 6], "0.0825");
}
#[test]
fn an_overflowing_run_is_dropped() {
assert!(extract("1e400").is_empty());
}
}