1use crate::{
2 deftype, err,
3 expr::Expr,
4 stdfn::{CachedArgs, CachedVals, EvalCached},
5 Ctx, ExecCtx, UserEvent,
6};
7use arcstr::{literal, ArcStr};
8use netidx::{path::Path, subscriber::Value, utils};
9use netidx_netproto::valarray::ValArray;
10use smallvec::SmallVec;
11use std::cell::RefCell;
12
13#[derive(Debug, Default)]
14struct StartsWithEv;
15
16impl EvalCached for StartsWithEv {
17 const NAME: &str = "starts_with";
18 deftype!("str", "fn(#pfx:string, string) -> bool");
19
20 fn eval(&mut self, from: &CachedVals) -> Option<Value> {
21 match (&from.0[0], &from.0[1]) {
22 (Some(Value::String(pfx)), Some(Value::String(val))) => {
23 if val.starts_with(&**pfx) {
24 Some(Value::Bool(true))
25 } else {
26 Some(Value::Bool(false))
27 }
28 }
29 (None, _) | (_, None) => None,
30 _ => err!("starts_with string arguments"),
31 }
32 }
33}
34
35type StartsWith = CachedArgs<StartsWithEv>;
36
37#[derive(Debug, Default)]
38struct EndsWithEv;
39
40impl EvalCached for EndsWithEv {
41 const NAME: &str = "ends_with";
42 deftype!("str", "fn(#sfx:string, string) -> bool");
43
44 fn eval(&mut self, from: &CachedVals) -> Option<Value> {
45 match (&from.0[0], &from.0[1]) {
46 (Some(Value::String(sfx)), Some(Value::String(val))) => {
47 if val.ends_with(&**sfx) {
48 Some(Value::Bool(true))
49 } else {
50 Some(Value::Bool(false))
51 }
52 }
53 (None, _) | (_, None) => None,
54 _ => err!("ends_with string arguments"),
55 }
56 }
57}
58
59type EndsWith = CachedArgs<EndsWithEv>;
60
61#[derive(Debug, Default)]
62struct ContainsEv;
63
64impl EvalCached for ContainsEv {
65 const NAME: &str = "contains";
66 deftype!("str", "fn(#part:string, string) -> bool");
67
68 fn eval(&mut self, from: &CachedVals) -> Option<Value> {
69 match (&from.0[0], &from.0[1]) {
70 (Some(Value::String(chs)), Some(Value::String(val))) => {
71 if val.contains(&**chs) {
72 Some(Value::Bool(true))
73 } else {
74 Some(Value::Bool(false))
75 }
76 }
77 (None, _) | (_, None) => None,
78 _ => err!("contains expected string"),
79 }
80 }
81}
82
83type Contains = CachedArgs<ContainsEv>;
84
85#[derive(Debug, Default)]
86struct StripPrefixEv;
87
88impl EvalCached for StripPrefixEv {
89 const NAME: &str = "strip_prefix";
90 deftype!("str", "fn(#pfx:string, string) -> [string, null]");
91
92 fn eval(&mut self, from: &CachedVals) -> Option<Value> {
93 match (&from.0[0], &from.0[1]) {
94 (Some(Value::String(pfx)), Some(Value::String(val))) => val
95 .strip_prefix(&**pfx)
96 .map(|s| Value::String(s.into()))
97 .or(Some(Value::Null)),
98 (None, _) | (_, None) => None,
99 _ => err!("strip_prefix expected string"),
100 }
101 }
102}
103
104type StripPrefix = CachedArgs<StripPrefixEv>;
105
106#[derive(Debug, Default)]
107struct StripSuffixEv;
108
109impl EvalCached for StripSuffixEv {
110 const NAME: &str = "strip_suffix";
111 deftype!("str", "fn(#sfx:string, string) -> [string, null]");
112
113 fn eval(&mut self, from: &CachedVals) -> Option<Value> {
114 match (&from.0[0], &from.0[1]) {
115 (Some(Value::String(sfx)), Some(Value::String(val))) => val
116 .strip_suffix(&**sfx)
117 .map(|s| Value::String(s.into()))
118 .or(Some(Value::Null)),
119 (None, _) | (_, None) => None,
120 _ => err!("strip_suffix expected string"),
121 }
122 }
123}
124
125type StripSuffix = CachedArgs<StripSuffixEv>;
126
127#[derive(Debug, Default)]
128struct TrimEv;
129
130impl EvalCached for TrimEv {
131 const NAME: &str = "trim";
132 deftype!("str", "fn(string) -> string");
133
134 fn eval(&mut self, from: &CachedVals) -> Option<Value> {
135 match &from.0[0] {
136 Some(Value::String(val)) => Some(Value::String(val.trim().into())),
137 None => None,
138 _ => err!("trim expected string"),
139 }
140 }
141}
142
143type Trim = CachedArgs<TrimEv>;
144
145#[derive(Debug, Default)]
146struct TrimStartEv;
147
148impl EvalCached for TrimStartEv {
149 const NAME: &str = "trim_start";
150 deftype!("str", "fn(string) -> string");
151
152 fn eval(&mut self, from: &CachedVals) -> Option<Value> {
153 match &from.0[0] {
154 Some(Value::String(val)) => Some(Value::String(val.trim_start().into())),
155 None => None,
156 _ => err!("trim_start expected string"),
157 }
158 }
159}
160
161type TrimStart = CachedArgs<TrimStartEv>;
162
163#[derive(Debug, Default)]
164struct TrimEndEv;
165
166impl EvalCached for TrimEndEv {
167 const NAME: &str = "trim_end";
168 deftype!("str", "fn(string) -> string");
169
170 fn eval(&mut self, from: &CachedVals) -> Option<Value> {
171 match &from.0[0] {
172 Some(Value::String(val)) => Some(Value::String(val.trim_end().into())),
173 None => None,
174 _ => err!("trim_start expected string"),
175 }
176 }
177}
178
179type TrimEnd = CachedArgs<TrimEndEv>;
180
181#[derive(Debug, Default)]
182struct ReplaceEv;
183
184impl EvalCached for ReplaceEv {
185 const NAME: &str = "replace";
186 deftype!("str", "fn(#pat:string, #rep:string, string) -> string");
187
188 fn eval(&mut self, from: &CachedVals) -> Option<Value> {
189 match (&from.0[0], &from.0[1], &from.0[2]) {
190 (
191 Some(Value::String(pat)),
192 Some(Value::String(rep)),
193 Some(Value::String(val)),
194 ) => Some(Value::String(val.replace(&**pat, &**rep).into())),
195 (None, _, _) | (_, None, _) | (_, _, None) => None,
196 _ => err!("replace expected string"),
197 }
198 }
199}
200
201type Replace = CachedArgs<ReplaceEv>;
202
203#[derive(Debug, Default)]
204struct DirnameEv;
205
206impl EvalCached for DirnameEv {
207 const NAME: &str = "dirname";
208 deftype!("str", "fn(string) -> [null, string]");
209
210 fn eval(&mut self, from: &CachedVals) -> Option<Value> {
211 match &from.0[0] {
212 Some(Value::String(path)) => match Path::dirname(path) {
213 None => Some(Value::Null),
214 Some(dn) => Some(Value::String(dn.into())),
215 },
216 None => None,
217 _ => err!("dirname expected string"),
218 }
219 }
220}
221
222type Dirname = CachedArgs<DirnameEv>;
223
224#[derive(Debug, Default)]
225struct BasenameEv;
226
227impl EvalCached for BasenameEv {
228 const NAME: &str = "basename";
229 deftype!("str", "fn(string) -> [null, string]");
230
231 fn eval(&mut self, from: &CachedVals) -> Option<Value> {
232 match &from.0[0] {
233 Some(Value::String(path)) => match Path::basename(path) {
234 None => Some(Value::Null),
235 Some(dn) => Some(Value::String(dn.into())),
236 },
237 None => None,
238 _ => err!("basename expected string"),
239 }
240 }
241}
242
243type Basename = CachedArgs<BasenameEv>;
244
245#[derive(Debug, Default)]
246struct StringJoinEv;
247
248impl EvalCached for StringJoinEv {
249 const NAME: &str = "string_join";
250 deftype!("str", "fn(#sep:string, @args: [string, Array<string>]) -> string");
251
252 fn eval(&mut self, from: &CachedVals) -> Option<Value> {
253 thread_local! {
254 static BUF: RefCell<String> = RefCell::new(String::new());
255 }
256 match &from.0[..] {
257 [_] | [] => None,
258 [None, ..] => None,
259 [Some(sep), parts @ ..] => {
260 for p in parts {
262 if p.is_none() {
263 return None;
264 }
265 }
266 let sep = match sep {
267 Value::String(c) => c.clone(),
268 sep => match sep.clone().cast_to::<ArcStr>().ok() {
269 Some(c) => c,
270 None => return err!("string_join, separator must be a string"),
271 },
272 };
273 BUF.with_borrow_mut(|buf| {
274 macro_rules! push {
275 ($c:expr) => {
276 if buf.is_empty() {
277 buf.push_str($c.as_str());
278 } else {
279 buf.push_str(sep.as_str());
280 buf.push_str($c.as_str());
281 }
282 };
283 }
284 buf.clear();
285 for p in parts {
286 match p.as_ref().unwrap() {
287 Value::String(c) => push!(c),
288 Value::Array(a) => {
289 for v in a.iter() {
290 match v {
291 Value::String(c) => push!(c),
292 _ => {
293 return err!(
294 "string_join, components must be strings"
295 )
296 }
297 }
298 }
299 }
300 _ => return err!("string_join, components must be strings"),
301 }
302 }
303 Some(Value::String(buf.as_str().into()))
304 })
305 }
306 }
307 }
308}
309
310type StringJoin = CachedArgs<StringJoinEv>;
311
312#[derive(Debug, Default)]
313struct StringConcatEv;
314
315impl EvalCached for StringConcatEv {
316 const NAME: &str = "string_concat";
317 deftype!("str", "fn(@args: [string, Array<string>]) -> string");
318
319 fn eval(&mut self, from: &CachedVals) -> Option<Value> {
320 thread_local! {
321 static BUF: RefCell<String> = RefCell::new(String::new());
322 }
323 let parts = &from.0[..];
324 for p in parts {
326 if p.is_none() {
327 return None;
328 }
329 }
330 BUF.with_borrow_mut(|buf| {
331 buf.clear();
332 for p in parts {
333 match p.as_ref().unwrap() {
334 Value::String(c) => buf.push_str(c.as_ref()),
335 Value::Array(a) => {
336 for v in a.iter() {
337 match v {
338 Value::String(c) => buf.push_str(c.as_ref()),
339 _ => {
340 return err!(
341 "string_concat: arguments must be strings"
342 )
343 }
344 }
345 }
346 }
347 _ => return err!("string_concat: arguments must be strings"),
348 }
349 }
350 Some(Value::String(buf.as_str().into()))
351 })
352 }
353}
354
355type StringConcat = CachedArgs<StringConcatEv>;
356
357#[derive(Debug, Default)]
358struct StringEscapeEv {
359 to_escape: SmallVec<[char; 8]>,
360}
361
362impl EvalCached for StringEscapeEv {
363 const NAME: &str = "string_escape";
364 deftype!("str", "fn(?#to_escape:string, ?#escape:string, string) -> [string, error]");
365
366 fn eval(&mut self, from: &CachedVals) -> Option<Value> {
367 for p in &from.0[..] {
369 if p.is_none() {
370 return None;
371 }
372 }
373 match &from.0[0] {
374 Some(Value::String(s)) => {
375 self.to_escape.clear();
376 self.to_escape.extend(s.chars());
377 }
378 _ => return err!("escape: expected a string"),
379 }
380 let ec = match &from.0[1] {
381 Some(Value::String(s)) if s.len() == 1 => s.chars().next().unwrap(),
382 _ => return err!("escape: expected a single escape char"),
383 };
384 match &from.0[2] {
385 Some(Value::String(s)) => {
386 Some(Value::String(utils::escape(s, ec, &self.to_escape).into()))
387 }
388 _ => err!("escape: expected a string"),
389 }
390 }
391}
392
393type StringEscape = CachedArgs<StringEscapeEv>;
394
395#[derive(Debug, Default)]
396struct StringUnescapeEv;
397
398impl EvalCached for StringUnescapeEv {
399 const NAME: &str = "string_unescape";
400 deftype!("str", "fn(?#escape:string, string) -> string");
401
402 fn eval(&mut self, from: &CachedVals) -> Option<Value> {
403 for p in &from.0[..] {
405 if p.is_none() {
406 return None;
407 }
408 }
409 let ec = match &from.0[0] {
410 Some(Value::String(s)) if s.len() == 1 => s.chars().next().unwrap(),
411 _ => return err!("escape: expected a single escape char"),
412 };
413 match &from.0[1] {
414 Some(Value::String(s)) => Some(Value::String(utils::unescape(s, ec).into())),
415 _ => err!("escape: expected a string"),
416 }
417 }
418}
419
420type StringUnescape = CachedArgs<StringUnescapeEv>;
421
422#[derive(Debug, Default)]
423struct StringSplitEv;
424
425impl EvalCached for StringSplitEv {
426 const NAME: &str = "string_split";
427 deftype!("str", "fn(#pat:string, string) -> Array<string>");
428
429 fn eval(&mut self, from: &CachedVals) -> Option<Value> {
430 for p in &from.0[..] {
432 if p.is_none() {
433 return None;
434 }
435 }
436 let pat = match &from.0[0] {
437 Some(Value::String(s)) => s,
438 _ => return err!("split: expected string"),
439 };
440 match &from.0[1] {
441 Some(Value::String(s)) => Some(Value::Array(ValArray::from_iter(
442 s.split(&**pat).map(|s| Value::String(ArcStr::from(s))),
443 ))),
444 _ => err!("split: expected a string"),
445 }
446 }
447}
448
449type StringSplit = CachedArgs<StringSplitEv>;
450
451#[derive(Debug, Default)]
452struct StringSplitOnceEv;
453
454impl EvalCached for StringSplitOnceEv {
455 const NAME: &str = "string_split_once";
456 deftype!("str", "fn(#pat:string, string) -> [null, (string, string)]");
457
458 fn eval(&mut self, from: &CachedVals) -> Option<Value> {
459 for p in &from.0[..] {
461 if p.is_none() {
462 return None;
463 }
464 }
465 let pat = match &from.0[0] {
466 Some(Value::String(s)) => s,
467 _ => return err!("split_once: expected string"),
468 };
469 match &from.0[1] {
470 Some(Value::String(s)) => match s.split_once(&**pat) {
471 None => Some(Value::Null),
472 Some((s0, s1)) => Some(Value::Array(ValArray::from([
473 Value::String(s0.into()),
474 Value::String(s1.into()),
475 ]))),
476 },
477 _ => err!("split_once: expected a string"),
478 }
479 }
480}
481
482type StringSplitOnce = CachedArgs<StringSplitOnceEv>;
483
484#[derive(Debug, Default)]
485struct StringRSplitOnceEv;
486
487impl EvalCached for StringRSplitOnceEv {
488 const NAME: &str = "string_rsplit_once";
489 deftype!("str", "fn(#pat:string, string) -> [null, (string, string)]");
490
491 fn eval(&mut self, from: &CachedVals) -> Option<Value> {
492 for p in &from.0[..] {
494 if p.is_none() {
495 return None;
496 }
497 }
498 let pat = match &from.0[0] {
499 Some(Value::String(s)) => s,
500 _ => return err!("split_once: expected string"),
501 };
502 match &from.0[1] {
503 Some(Value::String(s)) => match s.rsplit_once(&**pat) {
504 None => Some(Value::Null),
505 Some((s0, s1)) => Some(Value::Array(ValArray::from([
506 Value::String(s0.into()),
507 Value::String(s1.into()),
508 ]))),
509 },
510 _ => err!("split_once: expected a string"),
511 }
512 }
513}
514
515type StringRSplitOnce = CachedArgs<StringRSplitOnceEv>;
516
517#[derive(Debug, Default)]
518struct StringToLowerEv;
519
520impl EvalCached for StringToLowerEv {
521 const NAME: &str = "string_to_lower";
522 deftype!("str", "fn(string) -> string");
523
524 fn eval(&mut self, from: &CachedVals) -> Option<Value> {
525 match &from.0[0] {
526 None => None,
527 Some(Value::String(s)) => Some(Value::String(s.to_lowercase().into())),
528 _ => err!("to_lower: expected a string"),
529 }
530 }
531}
532
533type StringToLower = CachedArgs<StringToLowerEv>;
534
535#[derive(Debug, Default)]
536struct StringToUpperEv;
537
538impl EvalCached for StringToUpperEv {
539 const NAME: &str = "string_to_upper";
540 deftype!("str", "fn(string) -> string");
541
542 fn eval(&mut self, from: &CachedVals) -> Option<Value> {
543 match &from.0[0] {
544 None => None,
545 Some(Value::String(s)) => Some(Value::String(s.to_uppercase().into())),
546 _ => err!("to_upper: expected a string"),
547 }
548 }
549}
550
551type StringToUpper = CachedArgs<StringToUpperEv>;
552
553const MOD: &str = r#"
554pub mod str {
555 /// return true if s starts with #pfx, otherwise return false
556 pub let starts_with = |#pfx, s| 'starts_with;
557
558 /// return true if s ends with #sfx otherwise return false
559 pub let ends_with = |#sfx, s| 'ends_with;
560
561 /// return true if s contains #part, otherwise return false
562 pub let contains = |#part, s| 'contains;
563
564 /// if s starts with #pfx then return s with #pfx stripped otherwise return null
565 pub let strip_prefix = |#pfx, s| 'strip_prefix;
566
567 /// if s ends with #sfx then return s with #sfx stripped otherwise return null
568 pub let strip_suffix = |#sfx, s| 'strip_suffix;
569
570 /// return s with leading and trailing whitespace removed
571 pub let trim = |s| 'trim;
572
573 /// return s with leading whitespace removed
574 pub let trim_start = |s| 'trim_start;
575
576 /// return s with trailing whitespace removed
577 pub let trim_end = |s| 'trim_end;
578
579 /// replace all instances of #pat in s with #rep and return s
580 pub let replace = |#pat, #rep, s| 'replace;
581
582 /// return the parent path of s, or null if s does not have a parent path
583 pub let dirname = |path| 'dirname;
584
585 /// return the leaf path of s, or null if s is not a path. e.g. /foo/bar -> bar
586 pub let basename = |path| 'basename;
587
588 /// return a single string with the arguments concatenated and separated by #sep
589 pub let join = |#sep, @args| 'string_join;
590
591 /// concatenate the specified strings into a single string
592 pub let concat = |@args| 'string_concat;
593
594 /// escape all the charachters in #to_escape in s with the escape charachter #escape.
595 /// The escape charachter must appear in #to_escape
596 pub let escape = |#to_escape = "/", #escape = "\\", s| 'string_escape;
597
598 /// unescape all the charachters in s escaped by the specified #escape charachter
599 pub let unescape = |#escape = "\\", s| 'string_unescape;
600
601 /// split the string by the specified #pat and return an array of each part
602 pub let split = |#pat, s| 'string_split;
603
604 /// split the string once from the beginning by #pat and return a
605 /// tuple of strings, or return null if #pat was not found in the string
606 pub let split_once = |#pat, s| 'string_split_once;
607
608 /// split the string once from the end by #pat and return a tuple of strings
609 /// or return null if #pat was not found in the string
610 pub let rsplit_once = |#pat, s| 'string_rsplit_once;
611
612 /// change the string to lowercase
613 pub let to_lower = |s| 'string_to_lower;
614
615 /// change the string to uppercase
616 pub let to_upper = |s| 'string_to_upper
617}
618"#;
619
620pub fn register<C: Ctx, E: UserEvent>(ctx: &mut ExecCtx<C, E>) -> Expr {
621 ctx.register_builtin::<StartsWith>().unwrap();
622 ctx.register_builtin::<EndsWith>().unwrap();
623 ctx.register_builtin::<Contains>().unwrap();
624 ctx.register_builtin::<StripPrefix>().unwrap();
625 ctx.register_builtin::<StripSuffix>().unwrap();
626 ctx.register_builtin::<Trim>().unwrap();
627 ctx.register_builtin::<TrimStart>().unwrap();
628 ctx.register_builtin::<TrimEnd>().unwrap();
629 ctx.register_builtin::<Replace>().unwrap();
630 ctx.register_builtin::<Dirname>().unwrap();
631 ctx.register_builtin::<Basename>().unwrap();
632 ctx.register_builtin::<StringJoin>().unwrap();
633 ctx.register_builtin::<StringConcat>().unwrap();
634 ctx.register_builtin::<StringEscape>().unwrap();
635 ctx.register_builtin::<StringUnescape>().unwrap();
636 ctx.register_builtin::<StringSplit>().unwrap();
637 ctx.register_builtin::<StringSplitOnce>().unwrap();
638 ctx.register_builtin::<StringRSplitOnce>().unwrap();
639 ctx.register_builtin::<StringToLower>().unwrap();
640 ctx.register_builtin::<StringToUpper>().unwrap();
641 MOD.parse().unwrap()
642}