Skip to main content

graphix_package_str/
lib.rs

1use anyhow::{bail, Context, Result};
2use arcstr::{literal, ArcStr};
3use escaping::Escape;
4use graphix_compiler::{
5    err, errf, expr::ExprId, Apply, BuiltIn, Event, ExecCtx, Node, Rt, Scope, UserEvent,
6};
7use graphix_package_core::{deftype, CachedArgs, CachedVals, EvalCached};
8use netidx::{path::Path, subscriber::Value};
9use netidx_value::ValArray;
10use smallvec::SmallVec;
11use std::cell::RefCell;
12
13#[derive(Debug, Default)]
14struct StartsWithEv;
15
16impl EvalCached for StartsWithEv {
17    const NAME: &str = "str_starts_with";
18    deftype!("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,
30        }
31    }
32}
33
34type StartsWith = CachedArgs<StartsWithEv>;
35
36#[derive(Debug, Default)]
37struct EndsWithEv;
38
39impl EvalCached for EndsWithEv {
40    const NAME: &str = "str_ends_with";
41    deftype!("fn(#sfx:string, string) -> bool");
42
43    fn eval(&mut self, from: &CachedVals) -> Option<Value> {
44        match (&from.0[0], &from.0[1]) {
45            (Some(Value::String(sfx)), Some(Value::String(val))) => {
46                if val.ends_with(&**sfx) {
47                    Some(Value::Bool(true))
48                } else {
49                    Some(Value::Bool(false))
50                }
51            }
52            _ => None,
53        }
54    }
55}
56
57type EndsWith = CachedArgs<EndsWithEv>;
58
59#[derive(Debug, Default)]
60struct ContainsEv;
61
62impl EvalCached for ContainsEv {
63    const NAME: &str = "str_contains";
64    deftype!("fn(#part:string, string) -> bool");
65
66    fn eval(&mut self, from: &CachedVals) -> Option<Value> {
67        match (&from.0[0], &from.0[1]) {
68            (Some(Value::String(chs)), Some(Value::String(val))) => {
69                if val.contains(&**chs) {
70                    Some(Value::Bool(true))
71                } else {
72                    Some(Value::Bool(false))
73                }
74            }
75            _ => None,
76        }
77    }
78}
79
80type Contains = CachedArgs<ContainsEv>;
81
82#[derive(Debug, Default)]
83struct StripPrefixEv;
84
85impl EvalCached for StripPrefixEv {
86    const NAME: &str = "str_strip_prefix";
87    deftype!("fn(#pfx:string, string) -> Option<string>");
88
89    fn eval(&mut self, from: &CachedVals) -> Option<Value> {
90        match (&from.0[0], &from.0[1]) {
91            (Some(Value::String(pfx)), Some(Value::String(val))) => val
92                .strip_prefix(&**pfx)
93                .map(|s| Value::String(s.into()))
94                .or(Some(Value::Null)),
95            _ => None,
96        }
97    }
98}
99
100type StripPrefix = CachedArgs<StripPrefixEv>;
101
102#[derive(Debug, Default)]
103struct StripSuffixEv;
104
105impl EvalCached for StripSuffixEv {
106    const NAME: &str = "str_strip_suffix";
107    deftype!("fn(#sfx:string, string) -> Option<string>");
108
109    fn eval(&mut self, from: &CachedVals) -> Option<Value> {
110        match (&from.0[0], &from.0[1]) {
111            (Some(Value::String(sfx)), Some(Value::String(val))) => val
112                .strip_suffix(&**sfx)
113                .map(|s| Value::String(s.into()))
114                .or(Some(Value::Null)),
115            _ => None,
116        }
117    }
118}
119
120type StripSuffix = CachedArgs<StripSuffixEv>;
121
122#[derive(Debug, Default)]
123struct TrimEv;
124
125impl EvalCached for TrimEv {
126    const NAME: &str = "str_trim";
127    deftype!("fn(string) -> string");
128
129    fn eval(&mut self, from: &CachedVals) -> Option<Value> {
130        match &from.0[0] {
131            Some(Value::String(val)) => Some(Value::String(val.trim().into())),
132            _ => None,
133        }
134    }
135}
136
137type Trim = CachedArgs<TrimEv>;
138
139#[derive(Debug, Default)]
140struct TrimStartEv;
141
142impl EvalCached for TrimStartEv {
143    const NAME: &str = "str_trim_start";
144    deftype!("fn(string) -> string");
145
146    fn eval(&mut self, from: &CachedVals) -> Option<Value> {
147        match &from.0[0] {
148            Some(Value::String(val)) => Some(Value::String(val.trim_start().into())),
149            _ => None,
150        }
151    }
152}
153
154type TrimStart = CachedArgs<TrimStartEv>;
155
156#[derive(Debug, Default)]
157struct TrimEndEv;
158
159impl EvalCached for TrimEndEv {
160    const NAME: &str = "str_trim_end";
161    deftype!("fn(string) -> string");
162
163    fn eval(&mut self, from: &CachedVals) -> Option<Value> {
164        match &from.0[0] {
165            Some(Value::String(val)) => Some(Value::String(val.trim_end().into())),
166            _ => None,
167        }
168    }
169}
170
171type TrimEnd = CachedArgs<TrimEndEv>;
172
173#[derive(Debug, Default)]
174struct ReplaceEv;
175
176impl EvalCached for ReplaceEv {
177    const NAME: &str = "str_replace";
178    deftype!("fn(#pat:string, #rep:string, string) -> string");
179
180    fn eval(&mut self, from: &CachedVals) -> Option<Value> {
181        match (&from.0[0], &from.0[1], &from.0[2]) {
182            (
183                Some(Value::String(pat)),
184                Some(Value::String(rep)),
185                Some(Value::String(val)),
186            ) => Some(Value::String(val.replace(&**pat, &**rep).into())),
187            _ => None,
188        }
189    }
190}
191
192type Replace = CachedArgs<ReplaceEv>;
193
194#[derive(Debug, Default)]
195struct DirnameEv;
196
197impl EvalCached for DirnameEv {
198    const NAME: &str = "str_dirname";
199    deftype!("fn(string) -> Option<string>");
200
201    fn eval(&mut self, from: &CachedVals) -> Option<Value> {
202        match &from.0[0] {
203            Some(Value::String(path)) => match Path::dirname(path) {
204                None if path != "/" => Some(Value::String(literal!("/"))),
205                None => Some(Value::Null),
206                Some(dn) => Some(Value::String(dn.into())),
207            },
208            _ => None,
209        }
210    }
211}
212
213type Dirname = CachedArgs<DirnameEv>;
214
215#[derive(Debug, Default)]
216struct BasenameEv;
217
218impl EvalCached for BasenameEv {
219    const NAME: &str = "str_basename";
220    deftype!("fn(string) -> Option<string>");
221
222    fn eval(&mut self, from: &CachedVals) -> Option<Value> {
223        match &from.0[0] {
224            Some(Value::String(path)) => match Path::basename(path) {
225                None => Some(Value::Null),
226                Some(dn) => Some(Value::String(dn.into())),
227            },
228            _ => None,
229        }
230    }
231}
232
233type Basename = CachedArgs<BasenameEv>;
234
235#[derive(Debug, Default)]
236struct StringJoinEv;
237
238impl EvalCached for StringJoinEv {
239    const NAME: &str = "str_join";
240    deftype!("fn(#sep:string, @args: [string, Array<string>]) -> string");
241
242    fn eval(&mut self, from: &CachedVals) -> Option<Value> {
243        thread_local! {
244            static BUF: RefCell<String> = RefCell::new(String::new());
245        }
246        match &from.0[..] {
247            [_] | [] => None,
248            [None, ..] => None,
249            [Some(sep), parts @ ..] => {
250                for p in parts {
251                    if p.is_none() {
252                        return None;
253                    }
254                }
255                let sep = match sep {
256                    Value::String(c) => c.clone(),
257                    sep => match sep.clone().cast_to::<ArcStr>().ok() {
258                        Some(c) => c,
259                        None => return None,
260                    },
261                };
262                BUF.with_borrow_mut(|buf| {
263                    macro_rules! push {
264                        ($c:expr) => {
265                            if buf.is_empty() {
266                                buf.push_str($c.as_str());
267                            } else {
268                                buf.push_str(sep.as_str());
269                                buf.push_str($c.as_str());
270                            }
271                        };
272                    }
273                    buf.clear();
274                    for p in parts {
275                        match p.as_ref().unwrap() {
276                            Value::String(c) => push!(c),
277                            Value::Array(a) => {
278                                for v in a.iter() {
279                                    if let Value::String(c) = v {
280                                        push!(c)
281                                    }
282                                }
283                            }
284                            _ => return None,
285                        }
286                    }
287                    Some(Value::String(buf.as_str().into()))
288                })
289            }
290        }
291    }
292}
293
294type StringJoin = CachedArgs<StringJoinEv>;
295
296#[derive(Debug, Default)]
297struct StringConcatEv;
298
299impl EvalCached for StringConcatEv {
300    const NAME: &str = "str_concat";
301    deftype!("fn(@args: [string, Array<string>]) -> string");
302
303    fn eval(&mut self, from: &CachedVals) -> Option<Value> {
304        thread_local! {
305            static BUF: RefCell<String> = RefCell::new(String::new());
306        }
307        let parts = &from.0[..];
308        for p in parts {
309            if p.is_none() {
310                return None;
311            }
312        }
313        BUF.with_borrow_mut(|buf| {
314            buf.clear();
315            for p in parts {
316                match p.as_ref().unwrap() {
317                    Value::String(c) => buf.push_str(c.as_ref()),
318                    Value::Array(a) => {
319                        for v in a.iter() {
320                            if let Value::String(c) = v {
321                                buf.push_str(c.as_ref())
322                            }
323                        }
324                    }
325                    _ => return None,
326                }
327            }
328            Some(Value::String(buf.as_str().into()))
329        })
330    }
331}
332
333type StringConcat = CachedArgs<StringConcatEv>;
334
335fn build_escape(esc: Value) -> Result<Escape> {
336    fn escape_non_printing(c: char) -> bool {
337        c.is_control()
338    }
339    let [(_, to_escape), (_, escape_char), (_, tr)] =
340        esc.cast_to::<[(ArcStr, Value); 3]>().context("parse escape")?;
341    let escape_char = {
342        let s = escape_char.cast_to::<ArcStr>().context("escape char")?;
343        if s.len() != 1 {
344            bail!("expected a single escape char")
345        }
346        s.chars().next().unwrap()
347    };
348    let to_escape = match to_escape {
349        Value::String(s) => s.chars().collect::<SmallVec<[char; 32]>>(),
350        _ => bail!("escape: expected a string"),
351    };
352    let tr =
353        tr.cast_to::<SmallVec<[(ArcStr, ArcStr); 8]>>().context("escape: parsing tr")?;
354    for (k, _) in &tr {
355        if k.len() != 1 {
356            bail!("escape: tr key {k} is invalid, expected 1 character");
357        }
358    }
359    let tr = tr
360        .into_iter()
361        .map(|(k, v)| (k.chars().next().unwrap(), v))
362        .collect::<SmallVec<[_; 8]>>();
363    let tr = tr.iter().map(|(c, s)| (*c, s.as_str())).collect::<SmallVec<[_; 8]>>();
364    Escape::new(escape_char, &to_escape, &tr, Some(escape_non_printing))
365}
366
367macro_rules! escape_fn {
368    ($name:ident, $builtin_name:literal, $escape:ident) => {
369        #[derive(Debug)]
370        struct $name {
371            escape: Option<Escape>,
372            args: CachedVals,
373        }
374
375        impl<R: Rt, E: UserEvent> BuiltIn<R, E> for $name {
376            const NAME: &str = $builtin_name;
377            deftype!("fn(?#esc:Escape, string) -> Result<string, `StringError(string)>");
378
379            fn init<'a, 'b, 'c>(
380                _ctx: &'a mut ExecCtx<R, E>,
381                _typ: &'a graphix_compiler::typ::FnType,
382                _scope: &'b Scope,
383                from: &'c [Node<R, E>],
384                _top_id: ExprId,
385            ) -> Result<Box<dyn Apply<R, E>>> {
386                Ok(Box::new(Self { escape: None, args: CachedVals::new(from) }))
387            }
388        }
389
390        impl<R: Rt, E: UserEvent> Apply<R, E> for $name {
391            fn update(
392                &mut self,
393                ctx: &mut ExecCtx<R, E>,
394                from: &mut [Node<R, E>],
395                event: &mut Event<E>,
396            ) -> Option<Value> {
397                static TAG: ArcStr = literal!("StringError");
398                let mut up = [false; 2];
399                self.args.update_diff(&mut up, ctx, from, event);
400                if up[0] {
401                    match &self.args.0[0] {
402                        Some(esc) => match build_escape(esc.clone()) {
403                            Err(e) => {
404                                return Some(errf!(TAG, "escape: invalid argument {e:?}"))
405                            }
406                            Ok(esc) => self.escape = Some(esc),
407                        },
408                        _ => return None,
409                    };
410                }
411                match (up, &self.escape, &self.args.0[1]) {
412                    ([_, true], Some(esc), Some(Value::String(s))) => {
413                        Some(Value::String(ArcStr::from(esc.$escape(&s))))
414                    }
415                    (_, _, _) => None,
416                }
417            }
418
419            fn sleep(&mut self, _ctx: &mut ExecCtx<R, E>) {
420                self.escape = None;
421                self.args.clear();
422            }
423        }
424    };
425}
426
427escape_fn!(StringEscape, "str_escape", escape);
428escape_fn!(StringUnescape, "str_unescape", unescape);
429
430macro_rules! string_split {
431    ($name:ident, $final_name:ident, $builtin:literal, $fn:ident) => {
432        #[derive(Debug, Default)]
433        struct $name;
434
435        impl EvalCached for $name {
436            const NAME: &str = $builtin;
437            deftype!("fn(#pat:string, string) -> Array<string>");
438
439            fn eval(&mut self, from: &CachedVals) -> Option<Value> {
440                for p in &from.0[..] {
441                    if p.is_none() {
442                        return None;
443                    }
444                }
445                let pat = match &from.0[0] {
446                    Some(Value::String(s)) => s,
447                    _ => return None,
448                };
449                match &from.0[1] {
450                    Some(Value::String(s)) => Some(Value::Array(ValArray::from_iter(
451                        s.$fn(&**pat).map(|s| Value::String(ArcStr::from(s))),
452                    ))),
453                    _ => None,
454                }
455            }
456        }
457
458        type $final_name = CachedArgs<$name>;
459    };
460}
461
462string_split!(StringSplitEv, StringSplit, "str_split", split);
463string_split!(StringRSplitEv, StringRSplit, "str_rsplit", rsplit);
464
465macro_rules! string_splitn {
466    ($name:ident, $final_name:ident, $builtin:literal, $fn:ident) => {
467        #[derive(Debug, Default)]
468        struct $name;
469
470        impl EvalCached for $name {
471            const NAME: &str = $builtin;
472            deftype!("fn(#pat:string, #n:i64, string) -> Result<Array<string>, `StringSplitError(string)>");
473
474            fn eval(&mut self, from: &CachedVals) -> Option<Value> {
475                static TAG: ArcStr = literal!("StringSplitError");
476                for p in &from.0[..] {
477                    if p.is_none() {
478                        return None;
479                    }
480                }
481                let pat = match &from.0[0] {
482                    Some(Value::String(s)) => s,
483                    _ => return None,
484                };
485                let n = match &from.0[1] {
486                    Some(Value::I64(n)) if *n > 0 => *n as usize,
487                    Some(v) => return Some(errf!(TAG, "splitn: {v} must be a number > 0")),
488                    None => return None,
489                };
490                match &from.0[2] {
491                    Some(Value::String(s)) => Some(Value::Array(ValArray::from_iter(
492                        s.$fn(n, &**pat).map(|s| Value::String(ArcStr::from(s))),
493                    ))),
494                    _ => None,
495                }
496            }
497        }
498
499        type $final_name = CachedArgs<$name>;
500    };
501}
502
503string_splitn!(StringSplitNEv, StringSplitN, "str_splitn", splitn);
504string_splitn!(StringRSplitNEv, StringRSplitN, "str_rsplitn", rsplitn);
505
506#[derive(Debug, Default)]
507struct StringSplitEscapedEv;
508
509impl EvalCached for StringSplitEscapedEv {
510    const NAME: &str = "str_split_escaped";
511    deftype!("fn(#esc:string, #sep:string, string) -> Result<Array<string>, `SplitEscError(string)>");
512
513    fn eval(&mut self, from: &CachedVals) -> Option<Value> {
514        static TAG: ArcStr = literal!("SplitEscError");
515        for p in &from.0[..] {
516            if p.is_none() {
517                return None;
518            }
519        }
520        let esc = match &from.0[0] {
521            Some(Value::String(s)) if s.len() == 1 => s.chars().next().unwrap(),
522            _ => return Some(err!(TAG, "split_escaped: invalid escape char")),
523        };
524        let sep = match &from.0[1] {
525            Some(Value::String(s)) if s.len() == 1 => s.chars().next().unwrap(),
526            _ => return Some(err!(TAG, "split_escaped: invalid separator")),
527        };
528        match &from.0[2] {
529            Some(Value::String(s)) => Some(Value::Array(ValArray::from_iter(
530                escaping::split(s, esc, sep).map(|s| Value::String(ArcStr::from(s))),
531            ))),
532            _ => None,
533        }
534    }
535}
536
537type StringSplitEscaped = CachedArgs<StringSplitEscapedEv>;
538
539#[derive(Debug, Default)]
540struct StringSplitNEscapedEv;
541
542impl EvalCached for StringSplitNEscapedEv {
543    const NAME: &str = "str_splitn_escaped";
544    deftype!(
545        "fn(#n:i64, #esc:string, #sep:string, string) -> Result<Array<string>, `SplitNEscError(string)>"
546    );
547
548    fn eval(&mut self, from: &CachedVals) -> Option<Value> {
549        static TAG: ArcStr = literal!("SplitNEscError");
550        for p in &from.0[..] {
551            if p.is_none() {
552                return None;
553            }
554        }
555        let n = match &from.0[0] {
556            Some(Value::I64(n)) if *n > 0 => *n as usize,
557            Some(v) => return Some(errf!(TAG, "splitn_escaped: invalid n {v}")),
558            None => return None,
559        };
560        let esc = match &from.0[1] {
561            Some(Value::String(s)) if s.len() == 1 => s.chars().next().unwrap(),
562            _ => return Some(err!(TAG, "split_escaped: invalid escape char")),
563        };
564        let sep = match &from.0[2] {
565            Some(Value::String(s)) if s.len() == 1 => s.chars().next().unwrap(),
566            _ => return Some(err!(TAG, "split_escaped: invalid separator")),
567        };
568        match &from.0[3] {
569            Some(Value::String(s)) => Some(Value::Array(ValArray::from_iter(
570                escaping::splitn(s, esc, n, sep).map(|s| Value::String(ArcStr::from(s))),
571            ))),
572            _ => None,
573        }
574    }
575}
576
577type StringSplitNEscaped = CachedArgs<StringSplitNEscapedEv>;
578
579#[derive(Debug, Default)]
580struct StringSplitOnceEv;
581
582impl EvalCached for StringSplitOnceEv {
583    const NAME: &str = "str_split_once";
584    deftype!("fn(#pat:string, string) -> Option<(string, string)>");
585
586    fn eval(&mut self, from: &CachedVals) -> Option<Value> {
587        for p in &from.0[..] {
588            if p.is_none() {
589                return None;
590            }
591        }
592        let pat = match &from.0[0] {
593            Some(Value::String(s)) => s,
594            _ => return None,
595        };
596        match &from.0[1] {
597            Some(Value::String(s)) => match s.split_once(&**pat) {
598                None => Some(Value::Null),
599                Some((s0, s1)) => Some(Value::Array(ValArray::from([
600                    Value::String(s0.into()),
601                    Value::String(s1.into()),
602                ]))),
603            },
604            _ => None,
605        }
606    }
607}
608
609type StringSplitOnce = CachedArgs<StringSplitOnceEv>;
610
611#[derive(Debug, Default)]
612struct StringRSplitOnceEv;
613
614impl EvalCached for StringRSplitOnceEv {
615    const NAME: &str = "str_rsplit_once";
616    deftype!("fn(#pat:string, string) -> Option<(string, string)>");
617
618    fn eval(&mut self, from: &CachedVals) -> Option<Value> {
619        for p in &from.0[..] {
620            if p.is_none() {
621                return None;
622            }
623        }
624        let pat = match &from.0[0] {
625            Some(Value::String(s)) => s,
626            _ => return None,
627        };
628        match &from.0[1] {
629            Some(Value::String(s)) => match s.rsplit_once(&**pat) {
630                None => Some(Value::Null),
631                Some((s0, s1)) => Some(Value::Array(ValArray::from([
632                    Value::String(s0.into()),
633                    Value::String(s1.into()),
634                ]))),
635            },
636            _ => None,
637        }
638    }
639}
640
641type StringRSplitOnce = CachedArgs<StringRSplitOnceEv>;
642
643#[derive(Debug, Default)]
644struct StringToLowerEv;
645
646impl EvalCached for StringToLowerEv {
647    const NAME: &str = "str_to_lower";
648    deftype!("fn(string) -> string");
649
650    fn eval(&mut self, from: &CachedVals) -> Option<Value> {
651        match &from.0[0] {
652            Some(Value::String(s)) => Some(Value::String(s.to_lowercase().into())),
653            _ => None,
654        }
655    }
656}
657
658type StringToLower = CachedArgs<StringToLowerEv>;
659
660#[derive(Debug, Default)]
661struct StringToUpperEv;
662
663impl EvalCached for StringToUpperEv {
664    const NAME: &str = "str_to_upper";
665    deftype!("fn(string) -> string");
666
667    fn eval(&mut self, from: &CachedVals) -> Option<Value> {
668        match &from.0[0] {
669            Some(Value::String(s)) => Some(Value::String(s.to_uppercase().into())),
670            _ => None,
671        }
672    }
673}
674
675type StringToUpper = CachedArgs<StringToUpperEv>;
676
677#[derive(Debug, Default)]
678struct SprintfEv {
679    buf: String,
680    args: Vec<Value>,
681}
682
683impl EvalCached for SprintfEv {
684    const NAME: &str = "str_sprintf";
685    deftype!("fn(string, @args: Any) -> string");
686
687    fn eval(&mut self, from: &CachedVals) -> Option<Value> {
688        match &from.0[..] {
689            [Some(Value::String(fmt)), args @ ..] => {
690                self.buf.clear();
691                self.args.clear();
692                for v in args {
693                    match v {
694                        Some(v) => self.args.push(v.clone()),
695                        None => return None,
696                    }
697                }
698                match netidx_value::printf(&mut self.buf, fmt, &self.args) {
699                    Ok(_) => Some(Value::String(ArcStr::from(&self.buf))),
700                    Err(e) => Some(Value::error(ArcStr::from(e.to_string()))),
701                }
702            }
703            _ => None,
704        }
705    }
706}
707
708type Sprintf = CachedArgs<SprintfEv>;
709
710#[derive(Debug, Default)]
711struct LenEv;
712
713impl EvalCached for LenEv {
714    const NAME: &str = "str_len";
715    deftype!("fn(string) -> i64");
716
717    fn eval(&mut self, from: &CachedVals) -> Option<Value> {
718        match &from.0[0] {
719            Some(Value::String(s)) => Some(Value::I64(s.len() as i64)),
720            _ => None,
721        }
722    }
723}
724
725type Len = CachedArgs<LenEv>;
726
727#[derive(Debug, Default)]
728struct SubEv(String);
729
730impl EvalCached for SubEv {
731    const NAME: &str = "str_sub";
732    deftype!("fn(#start:i64, #len:i64, string) -> Result<string, `SubError(string)>");
733
734    fn eval(&mut self, from: &CachedVals) -> Option<Value> {
735        match &from.0[..] {
736            [Some(Value::I64(start)), Some(Value::I64(len)), Some(Value::String(s))]
737                if *start >= 0 && *len >= 0 =>
738            {
739                let start = *start as usize;
740                let end = start + *len as usize;
741                self.0.clear();
742                for (i, c) in s.chars().enumerate() {
743                    if i >= start && i < end {
744                        self.0.push(c);
745                    }
746                }
747                Some(Value::String(ArcStr::from(&self.0)))
748            }
749            v => Some(errf!(literal!("SubError"), "sub args must be non negative {v:?}")),
750        }
751    }
752}
753
754type Sub = CachedArgs<SubEv>;
755
756#[derive(Debug, Default)]
757struct ParseEv;
758
759impl EvalCached for ParseEv {
760    const NAME: &str = "str_parse";
761    deftype!("fn(string) -> Result<PrimNoErr, Any>");
762
763    fn eval(&mut self, from: &CachedVals) -> Option<Value> {
764        match &from.0[0] {
765            Some(Value::String(s)) => match s.parse::<Value>() {
766                Ok(v) => match v {
767                    Value::Error(e) => Some(errf!(literal!("ParseError"), "{e}")),
768                    v => Some(v),
769                },
770                Err(e) => Some(errf!(literal!("ParseError"), "{e:?}")),
771            },
772            _ => None,
773        }
774    }
775}
776
777type Parse = CachedArgs<ParseEv>;
778
779graphix_derive::defpackage! {
780    builtins => [
781        StartsWith,
782        EndsWith,
783        Contains,
784        StripPrefix,
785        StripSuffix,
786        Trim,
787        TrimStart,
788        TrimEnd,
789        Replace,
790        Dirname,
791        Basename,
792        StringJoin,
793        StringConcat,
794        StringEscape,
795        StringUnescape,
796        StringSplit,
797        StringRSplit,
798        StringSplitN,
799        StringRSplitN,
800        StringSplitOnce,
801        StringRSplitOnce,
802        StringSplitEscaped,
803        StringSplitNEscaped,
804        StringToLower,
805        StringToUpper,
806        Sprintf,
807        Len,
808        Sub,
809        Parse,
810    ],
811}