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