Skip to main content

tatara_lisp_script/stdlib/
regex.rs

1//! Regular expressions (rust `regex` crate under the hood).
2//!
3//!   (re-match? PATTERN STR)      → bool
4//!   (re-find PATTERN STR)        → first match as string, or nil
5//!   (re-find-all PATTERN STR)    → list of match strings
6//!   (re-captures PATTERN STR)    → list of capture groups for first match
7//!                                  (returns nil if no match)
8//!   (re-replace PATTERN STR REPL) → every match replaced; $1/$2… refer
9//!                                    to capture groups in REPL
10//!   (re-split PATTERN STR)       → STR split on every PATTERN match
11
12use std::sync::Arc;
13
14use regex::Regex;
15use tatara_lisp_eval::{Arity, EvalError, Interpreter, Value};
16
17use crate::script_ctx::ScriptCtx;
18use crate::stdlib::env::str_arg;
19
20pub fn install(interp: &mut Interpreter<ScriptCtx>) {
21    interp.register_fn(
22        "re-match?",
23        Arity::Exact(2),
24        |args: &[Value], _ctx: &mut ScriptCtx, sp| {
25            let re = compile(&args[0], sp)?;
26            let s = str_arg(&args[1], "re-match?", sp)?;
27            Ok(Value::Bool(re.is_match(&s)))
28        },
29    );
30
31    interp.register_fn(
32        "re-find",
33        Arity::Exact(2),
34        |args: &[Value], _ctx: &mut ScriptCtx, sp| {
35            let re = compile(&args[0], sp)?;
36            let s = str_arg(&args[1], "re-find", sp)?;
37            Ok(re
38                .find(&s)
39                .map(|m| Value::Str(Arc::from(m.as_str())))
40                .unwrap_or(Value::Nil))
41        },
42    );
43
44    interp.register_fn(
45        "re-find-all",
46        Arity::Exact(2),
47        |args: &[Value], _ctx: &mut ScriptCtx, sp| {
48            let re = compile(&args[0], sp)?;
49            let s = str_arg(&args[1], "re-find-all", sp)?;
50            Ok(Value::list(
51                re.find_iter(&s)
52                    .map(|m| Value::Str(Arc::from(m.as_str())))
53                    .collect::<Vec<_>>(),
54            ))
55        },
56    );
57
58    interp.register_fn(
59        "re-captures",
60        Arity::Exact(2),
61        |args: &[Value], _ctx: &mut ScriptCtx, sp| {
62            let re = compile(&args[0], sp)?;
63            let s = str_arg(&args[1], "re-captures", sp)?;
64            let Some(caps) = re.captures(&s) else {
65                return Ok(Value::Nil);
66            };
67            let groups: Vec<Value> = caps
68                .iter()
69                .map(|m| m.map_or(Value::Nil, |m| Value::Str(Arc::from(m.as_str()))))
70                .collect();
71            Ok(Value::list(groups))
72        },
73    );
74
75    interp.register_fn(
76        "re-replace",
77        Arity::Exact(3),
78        |args: &[Value], _ctx: &mut ScriptCtx, sp| {
79            let re = compile(&args[0], sp)?;
80            let s = str_arg(&args[1], "re-replace", sp)?;
81            let repl = str_arg(&args[2], "re-replace", sp)?;
82            Ok(Value::Str(Arc::from(
83                re.replace_all(&s, &*repl).into_owned(),
84            )))
85        },
86    );
87
88    interp.register_fn(
89        "re-split",
90        Arity::Exact(2),
91        |args: &[Value], _ctx: &mut ScriptCtx, sp| {
92            let re = compile(&args[0], sp)?;
93            let s = str_arg(&args[1], "re-split", sp)?;
94            Ok(Value::list(
95                re.split(&s)
96                    .map(|p| Value::Str(Arc::from(p)))
97                    .collect::<Vec<_>>(),
98            ))
99        },
100    );
101}
102
103fn compile(v: &Value, sp: tatara_lisp::Span) -> Result<Regex, EvalError> {
104    let pat = str_arg(v, "regex", sp)?;
105    Regex::new(&pat)
106        .map_err(|e| EvalError::native_fn("regex", format!("bad pattern {pat:?}: {e}"), sp))
107}