netidx_bscript/stdfn/
re.rs

1use crate::{
2    deftype, errf,
3    expr::Expr,
4    stdfn::{CachedArgs, CachedVals, EvalCached},
5    Ctx, ExecCtx, UserEvent,
6};
7use anyhow::Result;
8use arcstr::ArcStr;
9use compact_str::format_compact;
10use netidx::subscriber::Value;
11use netidx_netproto::valarray::ValArray;
12use regex::Regex;
13
14fn maybe_compile(s: &str, re: &mut Option<Regex>) -> Result<()> {
15    let compile = match re {
16        None => true,
17        Some(re) => re.as_str() != s,
18    };
19    if compile {
20        *re = Some(Regex::new(s)?)
21    }
22    Ok(())
23}
24
25#[derive(Debug, Default)]
26struct IsMatchEv {
27    re: Option<Regex>,
28}
29
30impl EvalCached for IsMatchEv {
31    const NAME: &str = "re_is_match";
32    deftype!("re", "fn(#pat:string, string) -> [bool, error]");
33
34    fn eval(&mut self, from: &CachedVals) -> Option<Value> {
35        if let Some(Value::String(s)) = &from.0[0] {
36            if let Err(e) = maybe_compile(s, &mut self.re) {
37                return errf!("{e:?}");
38            }
39        }
40        if let Some(Value::String(s)) = &from.0[1] {
41            if let Some(re) = self.re.as_ref() {
42                return Some(Value::Bool(re.is_match(s)));
43            }
44        }
45        None
46    }
47}
48
49type IsMatch = CachedArgs<IsMatchEv>;
50
51#[derive(Debug, Default)]
52struct FindEv {
53    re: Option<Regex>,
54}
55
56impl EvalCached for FindEv {
57    const NAME: &str = "re_find";
58    deftype!("re", "fn(#pat:string, string) -> [Array<string>, error]");
59
60    fn eval(&mut self, from: &CachedVals) -> Option<Value> {
61        if let Some(Value::String(s)) = &from.0[0] {
62            if let Err(e) = maybe_compile(s, &mut self.re) {
63                return errf!("{e:?}");
64            }
65        }
66        if let Some(Value::String(s)) = &from.0[1] {
67            if let Some(re) = self.re.as_ref() {
68                let a = ValArray::from_iter(
69                    re.find_iter(s).map(|s| Value::String(s.as_str().into())),
70                );
71                return Some(Value::Array(a));
72            }
73        }
74        None
75    }
76}
77
78type Find = CachedArgs<FindEv>;
79
80#[derive(Debug, Default)]
81struct CapturesEv {
82    re: Option<Regex>,
83}
84
85impl EvalCached for CapturesEv {
86    const NAME: &str = "re_captures";
87    deftype!("re", "fn(#pat:string, string) -> [Array<Array<[null, string]>>, error]");
88
89    fn eval(&mut self, from: &CachedVals) -> Option<Value> {
90        if let Some(Value::String(s)) = &from.0[0] {
91            if let Err(e) = maybe_compile(s, &mut self.re) {
92                return errf!("{e:?}");
93            }
94        }
95        if let Some(Value::String(s)) = &from.0[1] {
96            if let Some(re) = self.re.as_ref() {
97                let a = ValArray::from_iter(re.captures_iter(s).map(|c| {
98                    let a = ValArray::from_iter(c.iter().map(|m| match m {
99                        None => Value::Null,
100                        Some(m) => Value::String(m.as_str().into()),
101                    }));
102                    Value::Array(a)
103                }));
104                return Some(Value::Array(a));
105            }
106        }
107        None
108    }
109}
110
111type Captures = CachedArgs<CapturesEv>;
112
113#[derive(Debug, Default)]
114struct SplitEv {
115    re: Option<Regex>,
116}
117
118impl EvalCached for SplitEv {
119    const NAME: &str = "re_split";
120    deftype!("re", "fn(#pat:string, string) -> [Array<string>, error]");
121
122    fn eval(&mut self, from: &CachedVals) -> Option<Value> {
123        if let Some(Value::String(s)) = &from.0[0] {
124            if let Err(e) = maybe_compile(s, &mut self.re) {
125                return errf!("{e:?}");
126            }
127        }
128        if let Some(Value::String(s)) = &from.0[1] {
129            if let Some(re) = self.re.as_ref() {
130                let a = ValArray::from_iter(re.split(s).map(|s| Value::String(s.into())));
131                return Some(Value::Array(a));
132            }
133        }
134        None
135    }
136}
137
138type Split = CachedArgs<SplitEv>;
139
140#[derive(Debug, Default)]
141struct SplitNEv {
142    re: Option<Regex>,
143    lim: Option<usize>,
144}
145
146impl EvalCached for SplitNEv {
147    const NAME: &str = "re_splitn";
148    deftype!("re", "fn(#pat:string, #limit:u64, string) -> [Array<string>, error]");
149
150    fn eval(&mut self, from: &CachedVals) -> Option<Value> {
151        if let Some(Value::String(s)) = &from.0[0] {
152            if let Err(e) = maybe_compile(s, &mut self.re) {
153                return errf!("{e:?}");
154            }
155        }
156        if let Some(Value::U64(lim)) = &from.0[1] {
157            self.lim = Some(*lim as usize);
158        }
159        if let Some(Value::String(s)) = &from.0[2] {
160            if let Some(lim) = self.lim {
161                if let Some(re) = self.re.as_ref() {
162                    let a = ValArray::from_iter(
163                        re.splitn(s, lim).map(|s| Value::String(s.into())),
164                    );
165                    return Some(Value::Array(a));
166                }
167            }
168        }
169        None
170    }
171}
172
173type SplitN = CachedArgs<SplitNEv>;
174
175const MOD: &str = r#"
176pub mod re {
177  /// return true if the string is matched by #pat, otherwise return false.
178  /// return an error if #pat is invalid.
179  pub let is_match = |#pat, s| 're_is_match;
180
181  /// return an array of instances of #pat in s. return an error if #pat is
182  /// invalid.
183  pub let find = |#pat, s| 're_find;
184
185  /// return an array of captures matched by #pat. The array will have an element for each
186  /// capture, regardless of whether it matched or not. If it did not match the corresponding
187  /// element will be null. Return an error if #pat is invalid.
188  pub let captures = |#pat, s| 're_captures;
189
190  /// return an array of strings split by #pat. return an error if #pat is invalid.
191  pub let split = |#pat, s| 're_split;
192
193  /// split the string by #pat at most #limit times and return an array of the parts.
194  /// return an error if #pat is invalid
195  pub let splitn = |#pat, #limit, s| 're_splitn
196}
197"#;
198
199pub fn register<C: Ctx, E: UserEvent>(ctx: &mut ExecCtx<C, E>) -> Expr {
200    ctx.register_builtin::<IsMatch>().unwrap();
201    ctx.register_builtin::<Find>().unwrap();
202    ctx.register_builtin::<Captures>().unwrap();
203    ctx.register_builtin::<Split>().unwrap();
204    ctx.register_builtin::<SplitN>().unwrap();
205    MOD.parse().unwrap()
206}