Skip to main content

graphix_package_str/
lib.rs

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