netidx_bscript/stdfn/
re.rs1use 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}