use crate::bytecode::CompiledProgram;
use crate::runtime::Value;
use std::sync::Arc;
#[derive(Debug, Clone)]
pub enum AdviceKind {
Before,
After,
Around,
}
#[derive(Debug, Clone)]
pub struct Intercept {
pub pattern: String,
pub kind: AdviceKind,
pub code: String,
pub id: u32,
pub program: Arc<CompiledProgram>,
}
#[derive(Debug, Clone)]
pub struct InterceptCall {
pub name: String,
pub args: Vec<Value>,
pub proceeded: bool,
pub result: Value,
}
pub(crate) fn intercept_matches(pattern: &str, fn_name: &str, full_call: &str) -> bool {
if pattern == "*" || pattern == "all" {
return true;
}
if pattern == fn_name {
return true;
}
if pattern.contains('*') || pattern.contains('?') {
return glob_match(pattern, fn_name) || glob_match(pattern, full_call);
}
false
}
fn glob_match(pattern: &str, text: &str) -> bool {
let p: Vec<char> = pattern.chars().collect();
let t: Vec<char> = text.chars().collect();
let (mut pi, mut ti) = (0usize, 0usize);
let mut star: Option<usize> = None;
let mut mark = 0usize;
while ti < t.len() {
if pi < p.len() && (p[pi] == '?' || p[pi] == t[ti]) {
pi += 1;
ti += 1;
} else if pi < p.len() && p[pi] == '*' {
star = Some(pi);
mark = ti;
pi += 1;
} else if let Some(sp) = star {
pi = sp + 1;
mark += 1;
ti = mark;
} else {
return false;
}
}
while pi < p.len() && p[pi] == '*' {
pi += 1;
}
pi == p.len()
}
#[cfg(test)]
mod tests {
use super::*;
fn empty_prog() -> Arc<CompiledProgram> {
let ast = crate::parser::parse_program("BEGIN{}").unwrap();
Arc::new(crate::compiler::Compiler::compile_program(&ast).unwrap())
}
#[test]
fn star_matches_anything() {
assert!(intercept_matches("*", "anything", "anything here"));
assert!(intercept_matches("*", "", ""));
}
#[test]
fn all_matches_anything() {
assert!(intercept_matches("all", "draw", "draw 1 2"));
assert!(intercept_matches("all", "log", "log status"));
}
#[test]
fn exact_match_on_fn_name() {
assert!(intercept_matches("draw", "draw", "draw x"));
assert!(intercept_matches("log", "log", "log a b"));
}
#[test]
fn exact_pattern_does_not_match_different_name() {
assert!(!intercept_matches("draw", "paint", "paint blue"));
assert!(!intercept_matches("log", "login", "login user"));
}
#[test]
fn glob_star_matches_prefix() {
assert!(intercept_matches("draw *", "draw", "draw a b"));
}
#[test]
fn glob_star_underscore_prefix_matches_helper_funcs() {
assert!(intercept_matches("_*", "_helper", "_helper"));
assert!(intercept_matches("_*", "_impl", "_impl"));
}
#[test]
fn glob_star_does_not_match_non_prefix() {
assert!(!intercept_matches("_*", "helper", "helper"));
}
#[test]
fn question_mark_glob_matches_single_char() {
assert!(intercept_matches("f?", "fx", "fx"));
assert!(!intercept_matches("f?", "fxyz", "fxyz"));
}
#[test]
fn glob_star_in_middle_matches() {
assert!(glob_match("a*z", "abcz"));
assert!(glob_match("a*z", "az"));
assert!(!glob_match("a*z", "abc"));
}
#[test]
fn unmatched_pattern_without_glob_chars_returns_false() {
assert!(!intercept_matches("nope", "draw", "draw x"));
}
#[test]
fn invalid_glob_pattern_returns_false() {
assert!(!intercept_matches("[invalid", "draw", "draw x"));
}
#[test]
fn empty_pattern_does_not_match_non_empty_fn() {
assert!(!intercept_matches("", "draw", "draw x"));
}
#[test]
fn empty_pattern_matches_empty_fn_exactly() {
assert!(intercept_matches("", "", ""));
}
#[test]
fn advice_kind_variants_round_trip_clone() {
assert!(matches!(AdviceKind::Before.clone(), AdviceKind::Before));
assert!(matches!(AdviceKind::After.clone(), AdviceKind::After));
assert!(matches!(AdviceKind::Around.clone(), AdviceKind::Around));
}
#[test]
fn intercept_struct_clone_preserves_fields() {
let i = Intercept {
pattern: "draw_*".into(),
kind: AdviceKind::Before,
code: "print \"before\"".into(),
id: 42,
program: empty_prog(),
};
let c = i.clone();
assert_eq!(c.pattern, "draw_*");
assert!(matches!(c.kind, AdviceKind::Before));
assert_eq!(c.code, "print \"before\"");
assert_eq!(c.id, 42);
}
}