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 RowColEv;
244
245impl<R: Rt, E: UserEvent> EvalCached<R, E> for RowColEv {
246 const NAME: &str = "str_row_col";
247 const NEEDS_CALLSITE: bool = false;
248
249 fn eval(&mut self, _ctx: &mut ExecCtx<R, E>, from: &CachedVals) -> Option<Value> {
250 match &from.0[0] {
251 Some(Value::String(path)) => {
252 let col = match Path::basename(path) {
253 Some(s) => s,
254 None => return Some(Value::Null),
255 };
256 let parent = match Path::dirname(path) {
257 Some(s) => s,
258 None => return Some(Value::Null),
259 };
260 let row = match Path::basename(parent) {
261 Some(s) => s,
262 None => return Some(Value::Null),
263 };
264 Some(Value::Array(ValArray::from([
265 Value::String(row.into()),
266 Value::String(col.into()),
267 ])))
268 }
269 _ => None,
270 }
271 }
272}
273
274type RowCol = CachedArgs<RowColEv>;
275
276#[derive(Debug, Default)]
277struct StringJoinEv;
278
279impl<R: Rt, E: UserEvent> EvalCached<R, E> for StringJoinEv {
280 const NAME: &str = "str_join";
281 const NEEDS_CALLSITE: bool = false;
282
283 fn eval(&mut self, _ctx: &mut ExecCtx<R, E>, from: &CachedVals) -> Option<Value> {
284 thread_local! {
285 static BUF: RefCell<String> = RefCell::new(String::new());
286 }
287 match &from.0[..] {
288 [_] | [] => None,
289 [None, ..] => None,
290 [Some(sep), parts @ ..] => {
291 for p in parts {
292 if p.is_none() {
293 return None;
294 }
295 }
296 let sep = match sep {
297 Value::String(c) => c.clone(),
298 sep => match sep.clone().cast_to::<ArcStr>().ok() {
299 Some(c) => c,
300 None => return None,
301 },
302 };
303 BUF.with_borrow_mut(|buf| {
304 macro_rules! push {
305 ($c:expr) => {
306 if buf.is_empty() {
307 buf.push_str($c.as_str());
308 } else {
309 buf.push_str(sep.as_str());
310 buf.push_str($c.as_str());
311 }
312 };
313 }
314 buf.clear();
315 for p in parts {
316 match p.as_ref().unwrap() {
317 Value::String(c) => push!(c),
318 Value::Array(a) => {
319 for v in a.iter() {
320 if let Value::String(c) = v {
321 push!(c)
322 }
323 }
324 }
325 _ => return None,
326 }
327 }
328 Some(Value::String(buf.as_str().into()))
329 })
330 }
331 }
332 }
333}
334
335type StringJoin = CachedArgs<StringJoinEv>;
336
337#[derive(Debug, Default)]
338struct StringConcatEv;
339
340impl<R: Rt, E: UserEvent> EvalCached<R, E> for StringConcatEv {
341 const NAME: &str = "str_concat";
342 const NEEDS_CALLSITE: bool = false;
343
344 fn eval(&mut self, _ctx: &mut ExecCtx<R, E>, from: &CachedVals) -> Option<Value> {
345 thread_local! {
346 static BUF: RefCell<String> = RefCell::new(String::new());
347 }
348 let parts = &from.0[..];
349 for p in parts {
350 if p.is_none() {
351 return None;
352 }
353 }
354 BUF.with_borrow_mut(|buf| {
355 buf.clear();
356 for p in parts {
357 match p.as_ref().unwrap() {
358 Value::String(c) => buf.push_str(c.as_ref()),
359 Value::Array(a) => {
360 for v in a.iter() {
361 if let Value::String(c) = v {
362 buf.push_str(c.as_ref())
363 }
364 }
365 }
366 _ => return None,
367 }
368 }
369 Some(Value::String(buf.as_str().into()))
370 })
371 }
372}
373
374type StringConcat = CachedArgs<StringConcatEv>;
375
376fn build_escape(esc: Value) -> Result<Escape> {
377 fn escape_non_printing(c: char) -> bool {
378 c.is_control()
379 }
380 let [(_, to_escape), (_, escape_char), (_, tr)] =
381 esc.cast_to::<[(ArcStr, Value); 3]>().context("parse escape")?;
382 let escape_char = {
383 let s = escape_char.cast_to::<ArcStr>().context("escape char")?;
384 if s.len() != 1 {
385 bail!("expected a single escape char")
386 }
387 s.chars().next().unwrap()
388 };
389 let to_escape = match to_escape {
390 Value::String(s) => s.chars().collect::<SmallVec<[char; 32]>>(),
391 _ => bail!("escape: expected a string"),
392 };
393 let tr =
394 tr.cast_to::<SmallVec<[(ArcStr, ArcStr); 8]>>().context("escape: parsing tr")?;
395 for (k, _) in &tr {
396 if k.len() != 1 {
397 bail!("escape: tr key {k} is invalid, expected 1 character");
398 }
399 }
400 let tr = tr
401 .into_iter()
402 .map(|(k, v)| (k.chars().next().unwrap(), v))
403 .collect::<SmallVec<[_; 8]>>();
404 let tr = tr.iter().map(|(c, s)| (*c, s.as_str())).collect::<SmallVec<[_; 8]>>();
405 Escape::new(escape_char, &to_escape, &tr, Some(escape_non_printing))
406}
407
408macro_rules! escape_fn {
409 ($name:ident, $builtin_name:literal, $escape:ident) => {
410 #[derive(Debug)]
411 struct $name {
412 escape: Option<Escape>,
413 args: CachedVals,
414 }
415
416 impl<R: Rt, E: UserEvent> BuiltIn<R, E> for $name {
417 const NAME: &str = $builtin_name;
418 const NEEDS_CALLSITE: bool = false;
419
420 fn init<'a, 'b, 'c, 'd>(
421 _ctx: &'a mut ExecCtx<R, E>,
422 _typ: &'a FnType,
423 _resolved: Option<&'d FnType>,
424 _scope: &'b Scope,
425 from: &'c [Node<R, E>],
426 _top_id: ExprId,
427 ) -> Result<Box<dyn Apply<R, E>>> {
428 Ok(Box::new(Self { escape: None, args: CachedVals::new(from) }))
429 }
430 }
431
432 impl<R: Rt, E: UserEvent> Apply<R, E> for $name {
433 fn update(
434 &mut self,
435 ctx: &mut ExecCtx<R, E>,
436 from: &mut [Node<R, E>],
437 event: &mut Event<E>,
438 ) -> Option<Value> {
439 static TAG: ArcStr = literal!("StringError");
440 let mut up = [false; 2];
441 self.args.update_diff(&mut up, ctx, from, event);
442 if up[0] {
443 match &self.args.0[0] {
444 Some(esc) => match build_escape(esc.clone()) {
445 Err(e) => {
446 return Some(errf!(TAG, "escape: invalid argument {e:?}"))
447 }
448 Ok(esc) => self.escape = Some(esc),
449 },
450 _ => return None,
451 };
452 }
453 match (up, &self.escape, &self.args.0[1]) {
454 ([_, true], Some(esc), Some(Value::String(s))) => {
455 Some(Value::String(ArcStr::from(esc.$escape(&s))))
456 }
457 (_, _, _) => None,
458 }
459 }
460
461 fn sleep(&mut self, _ctx: &mut ExecCtx<R, E>) {
462 self.escape = None;
463 self.args.clear();
464 }
465 }
466 };
467}
468
469escape_fn!(StringEscape, "str_escape", escape);
470escape_fn!(StringUnescape, "str_unescape", unescape);
471
472macro_rules! string_split {
473 ($name:ident, $final_name:ident, $builtin:literal, $fn:ident) => {
474 #[derive(Debug, Default)]
475 struct $name;
476
477 impl<R: Rt, E: UserEvent> EvalCached<R, E> for $name {
478 const NAME: &str = $builtin;
479 const NEEDS_CALLSITE: bool = false;
480
481 fn eval(
482 &mut self,
483 _ctx: &mut ExecCtx<R, E>,
484 from: &CachedVals,
485 ) -> Option<Value> {
486 for p in &from.0[..] {
487 if p.is_none() {
488 return None;
489 }
490 }
491 let pat = match &from.0[0] {
492 Some(Value::String(s)) => s,
493 _ => return None,
494 };
495 match &from.0[1] {
496 Some(Value::String(s)) => Some(Value::Array(ValArray::from_iter(
497 s.$fn(&**pat).map(|s| Value::String(ArcStr::from(s))),
498 ))),
499 _ => None,
500 }
501 }
502 }
503
504 type $final_name = CachedArgs<$name>;
505 };
506}
507
508string_split!(StringSplitEv, StringSplit, "str_split", split);
509string_split!(StringRSplitEv, StringRSplit, "str_rsplit", rsplit);
510
511macro_rules! string_splitn {
512 ($name:ident, $final_name:ident, $builtin:literal, $fn:ident) => {
513 #[derive(Debug, Default)]
514 struct $name;
515
516 impl<R: Rt, E: UserEvent> EvalCached<R, E> for $name {
517 const NAME: &str = $builtin;
518 const NEEDS_CALLSITE: bool = false;
519
520 fn eval(
521 &mut self,
522 _ctx: &mut ExecCtx<R, E>,
523 from: &CachedVals,
524 ) -> Option<Value> {
525 static TAG: ArcStr = literal!("StringSplitError");
526 for p in &from.0[..] {
527 if p.is_none() {
528 return None;
529 }
530 }
531 let pat = match &from.0[0] {
532 Some(Value::String(s)) => s,
533 _ => return None,
534 };
535 let n = match &from.0[1] {
536 Some(Value::I64(n)) if *n > 0 => *n as usize,
537 Some(v) => {
538 return Some(errf!(TAG, "splitn: {v} must be a number > 0"))
539 }
540 None => return None,
541 };
542 match &from.0[2] {
543 Some(Value::String(s)) => Some(Value::Array(ValArray::from_iter(
544 s.$fn(n, &**pat).map(|s| Value::String(ArcStr::from(s))),
545 ))),
546 _ => None,
547 }
548 }
549 }
550
551 type $final_name = CachedArgs<$name>;
552 };
553}
554
555string_splitn!(StringSplitNEv, StringSplitN, "str_splitn", splitn);
556string_splitn!(StringRSplitNEv, StringRSplitN, "str_rsplitn", rsplitn);
557
558#[derive(Debug, Default)]
559struct StringSplitEscapedEv;
560
561impl<R: Rt, E: UserEvent> EvalCached<R, E> for StringSplitEscapedEv {
562 const NAME: &str = "str_split_escaped";
563 const NEEDS_CALLSITE: bool = false;
564
565 fn eval(&mut self, _ctx: &mut ExecCtx<R, E>, from: &CachedVals) -> Option<Value> {
566 static TAG: ArcStr = literal!("SplitEscError");
567 for p in &from.0[..] {
568 if p.is_none() {
569 return None;
570 }
571 }
572 let esc = match &from.0[0] {
573 Some(Value::String(s)) if s.len() == 1 => s.chars().next().unwrap(),
574 _ => return Some(err!(TAG, "split_escaped: invalid escape char")),
575 };
576 let sep = 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 separator")),
579 };
580 match &from.0[2] {
581 Some(Value::String(s)) => Some(Value::Array(ValArray::from_iter(
582 escaping::split(s, esc, sep).map(|s| Value::String(ArcStr::from(s))),
583 ))),
584 _ => None,
585 }
586 }
587}
588
589type StringSplitEscaped = CachedArgs<StringSplitEscapedEv>;
590
591#[derive(Debug, Default)]
592struct StringSplitNEscapedEv;
593
594impl<R: Rt, E: UserEvent> EvalCached<R, E> for StringSplitNEscapedEv {
595 const NAME: &str = "str_splitn_escaped";
596 const NEEDS_CALLSITE: bool = false;
597
598 fn eval(&mut self, _ctx: &mut ExecCtx<R, E>, from: &CachedVals) -> Option<Value> {
599 static TAG: ArcStr = literal!("SplitNEscError");
600 for p in &from.0[..] {
601 if p.is_none() {
602 return None;
603 }
604 }
605 let n = match &from.0[0] {
606 Some(Value::I64(n)) if *n > 0 => *n as usize,
607 Some(v) => return Some(errf!(TAG, "splitn_escaped: invalid n {v}")),
608 None => return None,
609 };
610 let esc = match &from.0[1] {
611 Some(Value::String(s)) if s.len() == 1 => s.chars().next().unwrap(),
612 _ => return Some(err!(TAG, "split_escaped: invalid escape char")),
613 };
614 let sep = match &from.0[2] {
615 Some(Value::String(s)) if s.len() == 1 => s.chars().next().unwrap(),
616 _ => return Some(err!(TAG, "split_escaped: invalid separator")),
617 };
618 match &from.0[3] {
619 Some(Value::String(s)) => Some(Value::Array(ValArray::from_iter(
620 escaping::splitn(s, esc, n, sep).map(|s| Value::String(ArcStr::from(s))),
621 ))),
622 _ => None,
623 }
624 }
625}
626
627type StringSplitNEscaped = CachedArgs<StringSplitNEscapedEv>;
628
629#[derive(Debug, Default)]
630struct StringSplitOnceEv;
631
632impl<R: Rt, E: UserEvent> EvalCached<R, E> for StringSplitOnceEv {
633 const NAME: &str = "str_split_once";
634 const NEEDS_CALLSITE: bool = false;
635
636 fn eval(&mut self, _ctx: &mut ExecCtx<R, E>, from: &CachedVals) -> Option<Value> {
637 for p in &from.0[..] {
638 if p.is_none() {
639 return None;
640 }
641 }
642 let pat = match &from.0[0] {
643 Some(Value::String(s)) => s,
644 _ => return None,
645 };
646 match &from.0[1] {
647 Some(Value::String(s)) => match s.split_once(&**pat) {
648 None => Some(Value::Null),
649 Some((s0, s1)) => Some(Value::Array(ValArray::from([
650 Value::String(s0.into()),
651 Value::String(s1.into()),
652 ]))),
653 },
654 _ => None,
655 }
656 }
657}
658
659type StringSplitOnce = CachedArgs<StringSplitOnceEv>;
660
661#[derive(Debug, Default)]
662struct StringRSplitOnceEv;
663
664impl<R: Rt, E: UserEvent> EvalCached<R, E> for StringRSplitOnceEv {
665 const NAME: &str = "str_rsplit_once";
666 const NEEDS_CALLSITE: bool = false;
667
668 fn eval(&mut self, _ctx: &mut ExecCtx<R, E>, from: &CachedVals) -> Option<Value> {
669 for p in &from.0[..] {
670 if p.is_none() {
671 return None;
672 }
673 }
674 let pat = match &from.0[0] {
675 Some(Value::String(s)) => s,
676 _ => return None,
677 };
678 match &from.0[1] {
679 Some(Value::String(s)) => match s.rsplit_once(&**pat) {
680 None => Some(Value::Null),
681 Some((s0, s1)) => Some(Value::Array(ValArray::from([
682 Value::String(s0.into()),
683 Value::String(s1.into()),
684 ]))),
685 },
686 _ => None,
687 }
688 }
689}
690
691type StringRSplitOnce = CachedArgs<StringRSplitOnceEv>;
692
693#[derive(Debug, Default)]
694struct StringToLowerEv;
695
696impl<R: Rt, E: UserEvent> EvalCached<R, E> for StringToLowerEv {
697 const NAME: &str = "str_to_lower";
698 const NEEDS_CALLSITE: bool = false;
699
700 fn eval(&mut self, _ctx: &mut ExecCtx<R, E>, from: &CachedVals) -> Option<Value> {
701 match &from.0[0] {
702 Some(Value::String(s)) => Some(Value::String(s.to_lowercase().into())),
703 _ => None,
704 }
705 }
706}
707
708type StringToLower = CachedArgs<StringToLowerEv>;
709
710#[derive(Debug, Default)]
711struct StringToUpperEv;
712
713impl<R: Rt, E: UserEvent> EvalCached<R, E> for StringToUpperEv {
714 const NAME: &str = "str_to_upper";
715 const NEEDS_CALLSITE: bool = false;
716
717 fn eval(&mut self, _ctx: &mut ExecCtx<R, E>, from: &CachedVals) -> Option<Value> {
718 match &from.0[0] {
719 Some(Value::String(s)) => Some(Value::String(s.to_uppercase().into())),
720 _ => None,
721 }
722 }
723}
724
725type StringToUpper = CachedArgs<StringToUpperEv>;
726
727#[derive(Debug, Default)]
728struct SprintfEv {
729 buf: String,
730 args: Vec<Value>,
731}
732
733impl<R: Rt, E: UserEvent> EvalCached<R, E> for SprintfEv {
734 const NAME: &str = "str_sprintf";
735 const NEEDS_CALLSITE: bool = false;
736
737 fn eval(&mut self, _ctx: &mut ExecCtx<R, E>, from: &CachedVals) -> Option<Value> {
738 match &from.0[..] {
739 [Some(Value::String(fmt)), args @ ..] => {
740 self.buf.clear();
741 self.args.clear();
742 for v in args {
743 match v {
744 Some(v) => self.args.push(v.clone()),
745 None => return None,
746 }
747 }
748 match netidx_value::printf(&mut self.buf, fmt, &self.args) {
749 Ok(_) => Some(Value::String(ArcStr::from(&self.buf))),
750 Err(e) => Some(Value::error(ArcStr::from(e.to_string()))),
751 }
752 }
753 _ => None,
754 }
755 }
756}
757
758type Sprintf = CachedArgs<SprintfEv>;
759
760#[derive(Debug, Default)]
761struct LenEv;
762
763impl<R: Rt, E: UserEvent> EvalCached<R, E> for LenEv {
764 const NAME: &str = "str_len";
765 const NEEDS_CALLSITE: bool = false;
766
767 fn eval(&mut self, _ctx: &mut ExecCtx<R, E>, from: &CachedVals) -> Option<Value> {
768 match &from.0[0] {
769 Some(Value::String(s)) => Some(Value::I64(s.len() as i64)),
770 _ => None,
771 }
772 }
773}
774
775type Len = CachedArgs<LenEv>;
776
777#[derive(Debug, Default)]
778struct SubEv(String);
779
780impl<R: Rt, E: UserEvent> EvalCached<R, E> for SubEv {
781 const NAME: &str = "str_sub";
782 const NEEDS_CALLSITE: bool = false;
783
784 fn eval(&mut self, _ctx: &mut ExecCtx<R, E>, from: &CachedVals) -> Option<Value> {
785 match &from.0[..] {
786 [Some(Value::I64(start)), Some(Value::I64(len)), Some(Value::String(s))]
787 if *start >= 0 && *len >= 0 =>
788 {
789 let start = *start as usize;
790 let end = start + *len as usize;
791 self.0.clear();
792 for (i, c) in s.chars().enumerate() {
793 if i >= start && i < end {
794 self.0.push(c);
795 }
796 }
797 Some(Value::String(ArcStr::from(&self.0)))
798 }
799 v => Some(errf!(literal!("SubError"), "sub args must be non negative {v:?}")),
800 }
801 }
802}
803
804type Sub = CachedArgs<SubEv>;
805
806#[derive(Debug, Default)]
807struct ParseEv {
808 cast_typ: Option<Type>,
809}
810
811impl<R: Rt, E: UserEvent> EvalCached<R, E> for ParseEv {
812 const NAME: &str = "str_parse";
813 const NEEDS_CALLSITE: bool = true;
814
815 fn init(
816 _ctx: &mut ExecCtx<R, E>,
817 _typ: &FnType,
818 resolved: Option<&FnType>,
819 _scope: &Scope,
820 _from: &[Node<R, E>],
821 _top_id: ExprId,
822 ) -> Self {
823 Self { cast_typ: extract_cast_type(resolved) }
824 }
825
826 fn typecheck(
827 &mut self,
828 _ctx: &mut ExecCtx<R, E>,
829 _from: &mut [Node<R, E>],
830 phase: TypecheckPhase<'_>,
831 ) -> Result<()> {
832 match phase {
833 TypecheckPhase::Lambda => Ok(()),
834 TypecheckPhase::CallSite(resolved) => {
835 self.cast_typ = extract_cast_type(Some(resolved));
836 if self.cast_typ.is_none() {
837 bail!("str::parse requires a concrete return type")
838 }
839 Ok(())
840 }
841 }
842 }
843
844 fn eval(&mut self, ctx: &mut ExecCtx<R, E>, from: &CachedVals) -> Option<Value> {
845 let raw = match &from.0[0] {
846 Some(Value::String(s)) => match s.parse::<Value>() {
847 Ok(v) => match v {
848 Value::Error(e) => return Some(errf!(literal!("ParseError"), "{e}")),
849 v => v,
850 },
851 Err(e) => return Some(errf!(literal!("ParseError"), "{e:?}")),
852 },
853 _ => return None,
854 };
855 Some(match &self.cast_typ {
856 Some(typ) => typ.cast_value(&ctx.env, raw),
857 None => errf!("TypeError", "parse requires a concrete type annotation"),
858 })
859 }
860}
861
862type Parse = CachedArgs<ParseEv>;
863
864graphix_derive::defpackage! {
865 builtins => [
866 StartsWith,
867 EndsWith,
868 Contains,
869 StripPrefix,
870 StripSuffix,
871 Trim,
872 TrimStart,
873 TrimEnd,
874 Replace,
875 Dirname,
876 Basename,
877 RowCol,
878 StringJoin,
879 StringConcat,
880 StringEscape,
881 StringUnescape,
882 StringSplit,
883 StringRSplit,
884 StringSplitN,
885 StringRSplitN,
886 StringSplitOnce,
887 StringRSplitOnce,
888 StringSplitEscaped,
889 StringSplitNEscaped,
890 StringToLower,
891 StringToUpper,
892 Sprintf,
893 Len,
894 Sub,
895 Parse,
896 ],
897}