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}