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}