1use indexmap::IndexMap;
5use std::collections::HashMap;
6use std::sync::Arc;
7
8use super::value::{TypeTag, Value};
9use super::Interpreter;
10use crate::error::Diagnostic;
11use crate::span::Span;
12
13pub type MethodFn<'a> =
14 fn(&mut Interpreter<'a>, &Value<'a>, &[Value<'a>], Span) -> Result<Value<'a>, Diagnostic>;
15
16pub struct MethodRegistry<'a> {
17 table: HashMap<(TypeTag, &'static str), MethodFn<'a>>,
18}
19
20impl<'a> MethodRegistry<'a> {
21 pub fn new() -> Self {
22 MethodRegistry {
23 table: HashMap::new(),
24 }
25 }
26
27 pub fn register(&mut self, tag: TypeTag, name: &'static str, f: MethodFn<'a>) {
28 self.table.insert((tag, name), f);
29 }
30
31 pub fn get(&self, tag: TypeTag, name: &str) -> Option<MethodFn<'a>> {
32 self.table.get(&(tag, name)).copied()
33 }
34
35 pub fn entries(&self) -> Vec<(TypeTag, &'static str)> {
38 self.table.keys().copied().collect()
39 }
40
41 pub fn builtin() -> Self {
42 let mut r = Self::new();
43 r.register(TypeTag::Str, "upper", m_str_upper);
44 r.register(TypeTag::Str, "lower", m_str_lower);
45 r.register(TypeTag::Str, "len", m_len);
46 r.register(TypeTag::Str, "parse_toml", m_parse_toml);
47 r.register(TypeTag::Str, "parse_duration", m_parse_duration);
48 r.register(TypeTag::Str, "parse_datetime", m_parse_datetime);
49 r.register(TypeTag::Int, "format_duration", m_format_duration);
50 r.register(TypeTag::Int, "format_datetime", m_format_datetime);
51 r.register(TypeTag::Str, "parse_json", m_parse_json);
52 r.register(TypeTag::Str, "parse_yaml", m_parse_yaml);
53 r.register(TypeTag::List, "len", m_len);
54 r.register(TypeTag::List, "compact", m_list_compact);
55 r.register(TypeTag::List, "uniq", m_list_uniq);
56 r.register(TypeTag::List, "map", m_list_map);
57 r.register(TypeTag::List, "filter", m_list_filter);
58 r.register(TypeTag::List, "get", m_get);
59 r.register(TypeTag::List, "contains", m_contains);
60 r.register(TypeTag::List, "join", m_list_join);
61 r.register(TypeTag::Str, "contains", m_contains);
62 r.register(TypeTag::Object, "keys", m_obj_keys);
63 r.register(TypeTag::Object, "values", m_obj_values);
64 r.register(TypeTag::Object, "contains", m_contains);
65 r.register(TypeTag::List, "first", m_list_first);
66 r.register(TypeTag::List, "last", m_list_last);
67 r.register(TypeTag::Object, "len", m_len);
68 r.register(TypeTag::Object, "merge", m_obj_merge);
69 r.register(TypeTag::Object, "get", m_get);
70 for tag in [TypeTag::Object, TypeTag::List] {
71 r.register(tag, "to_json", m_to_json);
72 r.register(tag, "to_yaml", m_to_yaml);
73 r.register(tag, "to_toml", m_to_toml);
74 }
75 r.register(TypeTag::Str, "trim", m_str_trim);
77 r.register(TypeTag::Str, "split", m_str_split);
78 r.register(TypeTag::Str, "replace", m_str_replace);
79 r.register(TypeTag::Str, "starts_with", m_str_starts_with);
80 r.register(TypeTag::Str, "ends_with", m_str_ends_with);
81 r.register(TypeTag::Str, "to_int", m_str_to_int);
82 r.register(TypeTag::Str, "to_float", m_str_to_float);
83 r.register(TypeTag::Str, "sha256", m_str_sha256);
85 r.register(TypeTag::Str, "base64", m_str_base64);
86 r.register(TypeTag::Str, "base64_decode", m_str_base64_decode);
87 r.register(TypeTag::List, "sort", m_list_sort);
89 r.register(TypeTag::List, "reverse", m_list_reverse);
90 r.register(TypeTag::List, "sum", m_list_sum);
91 r.register(TypeTag::List, "min", m_list_min);
92 r.register(TypeTag::List, "max", m_list_max);
93 r.register(TypeTag::List, "flatten", m_list_flatten);
94 r.register(TypeTag::List, "slice", m_list_slice);
95 r.register(TypeTag::Int, "abs", m_num_abs);
97 r.register(TypeTag::Float, "abs", m_num_abs);
98 for tag in [TypeTag::Int, TypeTag::Float, TypeTag::Bool, TypeTag::Str] {
99 r.register(tag, "to_str", m_to_str);
100 }
101 r
102 }
103}
104
105impl Default for MethodRegistry<'_> {
106 fn default() -> Self {
107 Self::builtin()
108 }
109}
110
111fn rt(code: &'static str, msg: impl Into<String>, span: Span) -> Diagnostic {
112 Diagnostic::error(code, msg, span, "in this method call")
113}
114
115fn m_str_upper<'a>(
116 _it: &mut Interpreter<'a>,
117 recv: &Value<'a>,
118 _args: &[Value<'a>],
119 _sp: Span,
120) -> Result<Value<'a>, Diagnostic> {
121 let Value::Str(s) = recv else { unreachable!() };
122 Ok(Value::str(s.to_uppercase()))
123}
124
125fn m_str_lower<'a>(
126 _it: &mut Interpreter<'a>,
127 recv: &Value<'a>,
128 _args: &[Value<'a>],
129 _sp: Span,
130) -> Result<Value<'a>, Diagnostic> {
131 let Value::Str(s) = recv else { unreachable!() };
132 Ok(Value::str(s.to_lowercase()))
133}
134
135fn m_len<'a>(
136 _it: &mut Interpreter<'a>,
137 recv: &Value<'a>,
138 _args: &[Value<'a>],
139 _sp: Span,
140) -> Result<Value<'a>, Diagnostic> {
141 let n = match recv {
142 Value::Str(s) => s.chars().count(),
143 Value::List(xs) => xs.len(),
144 Value::Object(m) => m.len(),
145 _ => unreachable!(),
146 };
147 Ok(Value::Int(n as i64))
148}
149
150fn m_list_compact<'a>(
152 _it: &mut Interpreter<'a>,
153 recv: &Value<'a>,
154 _args: &[Value<'a>],
155 _sp: Span,
156) -> Result<Value<'a>, Diagnostic> {
157 let Value::List(xs) = recv else {
158 unreachable!()
159 };
160 Ok(Value::list(
161 xs.iter()
162 .filter(|v| !matches!(v, Value::Null))
163 .cloned()
164 .collect(),
165 ))
166}
167
168fn m_list_uniq<'a>(
170 _it: &mut Interpreter<'a>,
171 recv: &Value<'a>,
172 _args: &[Value<'a>],
173 _sp: Span,
174) -> Result<Value<'a>, Diagnostic> {
175 let Value::List(xs) = recv else {
176 unreachable!()
177 };
178 let mut out: Vec<Value<'a>> = Vec::with_capacity(xs.len());
179 for v in xs.iter() {
180 if !out.contains(v) {
181 out.push(v.clone());
182 }
183 }
184 Ok(Value::list(out))
185}
186
187fn expect_lambda<'a, 'b>(
188 args: &'b [Value<'a>],
189 name: &str,
190 sp: Span,
191) -> Result<&'b Value<'a>, Diagnostic> {
192 match args.last() {
193 Some(f @ Value::Function(_)) => Ok(f),
194 _ => Err(rt(
195 "E0315",
196 format!("`{name}` requires a lambda argument"),
197 sp,
198 )),
199 }
200}
201
202fn m_list_map<'a>(
204 it: &mut Interpreter<'a>,
205 recv: &Value<'a>,
206 args: &[Value<'a>],
207 sp: Span,
208) -> Result<Value<'a>, Diagnostic> {
209 let Value::List(xs) = recv else {
210 unreachable!()
211 };
212 let f = expect_lambda(args, "map", sp)?.clone();
213 let mut out = Vec::with_capacity(xs.len());
214 for (i, v) in xs.iter().enumerate() {
215 out.push(it.call_value(&f, &[v.clone(), Value::Int(i as i64)], sp)?);
216 }
217 Ok(Value::list(out))
218}
219
220fn m_list_filter<'a>(
221 it: &mut Interpreter<'a>,
222 recv: &Value<'a>,
223 args: &[Value<'a>],
224 sp: Span,
225) -> Result<Value<'a>, Diagnostic> {
226 let Value::List(xs) = recv else {
227 unreachable!()
228 };
229 let f = expect_lambda(args, "filter", sp)?.clone();
230 let mut out = Vec::new();
231 for (i, v) in xs.iter().enumerate() {
232 match it.call_value(&f, &[v.clone(), Value::Int(i as i64)], sp)? {
233 Value::Bool(true) => out.push(v.clone()),
234 Value::Bool(false) => {}
235 other => {
236 return Err(rt(
237 "E0306",
238 format!("filter lambda must return Bool, got {}", other.type_name()),
239 sp,
240 ))
241 }
242 }
243 }
244 Ok(Value::list(out))
245}
246
247fn m_obj_merge<'a>(
249 _it: &mut Interpreter<'a>,
250 recv: &Value<'a>,
251 args: &[Value<'a>],
252 sp: Span,
253) -> Result<Value<'a>, Diagnostic> {
254 let Value::Object(base) = recv else {
255 unreachable!()
256 };
257 let Some(Value::Object(other)) = args.first() else {
258 return Err(rt("E0306", "merge expects an Object argument", sp));
259 };
260 let mut out: IndexMap<String, Value<'a>> = (**base).clone();
261 for (k, v) in other.iter() {
262 out.insert(k.clone(), v.clone());
263 }
264 Ok(Value::object(out))
265}
266
267fn m_parse_toml<'a>(
269 _it: &mut Interpreter<'a>,
270 recv: &Value<'a>,
271 _args: &[Value<'a>],
272 sp: Span,
273) -> Result<Value<'a>, Diagnostic> {
274 let Value::Str(s) = recv else { unreachable!() };
275 let parsed: toml::Value =
276 toml::from_str(s).map_err(|e| rt("E0314", format!("invalid TOML: {e}"), sp))?;
277 Ok(toml_to_value(parsed))
278}
279
280fn m_get<'a>(
282 _it: &mut Interpreter<'a>,
283 recv: &Value<'a>,
284 args: &[Value<'a>],
285 sp: Span,
286) -> Result<Value<'a>, Diagnostic> {
287 let default = || args.get(1).cloned().unwrap_or(Value::Null);
288 match (recv, args.first()) {
289 (Value::List(xs), Some(Value::Int(i))) => Ok(usize::try_from(*i)
290 .ok()
291 .and_then(|i| xs.get(i))
292 .cloned()
293 .unwrap_or_else(default)),
294 (Value::Object(m), Some(Value::Str(k))) => {
295 Ok(m.get(k.as_ref()).cloned().unwrap_or_else(default))
296 }
297 (Value::List(_), _) => Err(rt("E0306", "List.get expects an Int index", sp)),
298 _ => Err(rt("E0306", "Object.get expects a String key", sp)),
299 }
300}
301
302fn m_list_first<'a>(
303 _it: &mut Interpreter<'a>,
304 recv: &Value<'a>,
305 _args: &[Value<'a>],
306 sp: Span,
307) -> Result<Value<'a>, Diagnostic> {
308 let Value::List(xs) = recv else {
309 unreachable!()
310 };
311 xs.first()
312 .cloned()
313 .ok_or_else(|| rt("E0317", "first() on an empty list", sp))
314}
315
316fn m_list_last<'a>(
317 _it: &mut Interpreter<'a>,
318 recv: &Value<'a>,
319 _args: &[Value<'a>],
320 sp: Span,
321) -> Result<Value<'a>, Diagnostic> {
322 let Value::List(xs) = recv else {
323 unreachable!()
324 };
325 xs.last()
326 .cloned()
327 .ok_or_else(|| rt("E0317", "last() on an empty list", sp))
328}
329
330fn m_parse_json<'a>(
331 _it: &mut Interpreter<'a>,
332 recv: &Value<'a>,
333 _args: &[Value<'a>],
334 sp: Span,
335) -> Result<Value<'a>, Diagnostic> {
336 let Value::Str(s) = recv else { unreachable!() };
337 let parsed: serde_json::Value =
338 serde_json::from_str(s).map_err(|e| rt("E0314", format!("invalid JSON: {e}"), sp))?;
339 Ok(json_to_value(parsed))
340}
341
342fn m_parse_yaml<'a>(
343 _it: &mut Interpreter<'a>,
344 recv: &Value<'a>,
345 _args: &[Value<'a>],
346 sp: Span,
347) -> Result<Value<'a>, Diagnostic> {
348 let Value::Str(s) = recv else { unreachable!() };
349 let docs = yaml_rust2::YamlLoader::load_from_str(s)
350 .map_err(|e| rt("E0314", format!("invalid YAML: {e}"), sp))?;
351 let doc = match docs.len() {
354 1 => &docs[0],
355 0 => return Ok(Value::Null),
356 n => {
357 return Err(rt(
358 "E0314",
359 format!("invalid YAML: expected one document, found {n}"),
360 sp,
361 ))
362 }
363 };
364 yaml_to_value(doc, sp)
365}
366
367fn yaml_to_value<'a>(y: &yaml_rust2::Yaml, sp: Span) -> Result<Value<'a>, Diagnostic> {
371 use yaml_rust2::Yaml as Y;
372 Ok(match y {
373 Y::Null => Value::Null,
374 Y::Boolean(b) => Value::Bool(*b),
375 Y::Integer(n) => Value::Int(*n),
376 Y::Real(r) => match r.parse::<f64>() {
378 Ok(f) => Value::Float(f),
379 Err(_) => Value::str(r.clone()),
380 },
381 Y::String(s) => Value::str(s.clone()),
382 Y::Array(xs) => {
383 let mut out = Vec::with_capacity(xs.len());
384 for x in xs {
385 out.push(yaml_to_value(x, sp)?);
386 }
387 Value::list(out)
388 }
389 Y::Hash(h) => {
390 let mut map = indexmap::IndexMap::with_capacity(h.len());
391 for (k, v) in h {
392 let key = match k {
394 Y::String(s) => s.clone(),
395 Y::Integer(n) => n.to_string(),
396 Y::Boolean(b) => b.to_string(),
397 Y::Real(r) => r.clone(),
398 _ => {
399 return Err(rt(
400 "E0314",
401 "invalid YAML: only scalar keys are supported".to_string(),
402 sp,
403 ))
404 }
405 };
406 map.insert(key, yaml_to_value(v, sp)?);
407 }
408 Value::object(map)
409 }
410 Y::Alias(_) | Y::BadValue => {
411 return Err(rt(
412 "E0314",
413 "invalid YAML: aliases are not supported".to_string(),
414 sp,
415 ))
416 }
417 })
418}
419
420fn m_to_json<'a>(
421 _it: &mut Interpreter<'a>,
422 recv: &Value<'a>,
423 _args: &[Value<'a>],
424 sp: Span,
425) -> Result<Value<'a>, Diagnostic> {
426 let json = crate::serialize::to_json(recv).map_err(|d| rt(d.code, d.message, sp))?;
427 Ok(Value::str(
428 serde_json::to_string(&json).expect("valid json"),
429 ))
430}
431
432fn m_to_yaml<'a>(
433 _it: &mut Interpreter<'a>,
434 recv: &Value<'a>,
435 _args: &[Value<'a>],
436 sp: Span,
437) -> Result<Value<'a>, Diagnostic> {
438 crate::serialize::to_yaml_string(recv)
439 .map(Value::str)
440 .map_err(|d| rt(d.code, d.message, sp))
441}
442
443fn m_to_toml<'a>(
444 _it: &mut Interpreter<'a>,
445 recv: &Value<'a>,
446 _args: &[Value<'a>],
447 sp: Span,
448) -> Result<Value<'a>, Diagnostic> {
449 crate::serialize::to_toml_string(recv)
450 .map(Value::str)
451 .map_err(|d| rt(d.code, d.message, sp))
452}
453
454fn m_obj_keys<'a>(
456 _it: &mut Interpreter<'a>,
457 recv: &Value<'a>,
458 _args: &[Value<'a>],
459 _sp: Span,
460) -> Result<Value<'a>, Diagnostic> {
461 let Value::Object(m) = recv else {
462 unreachable!()
463 };
464 Ok(Value::list(m.keys().map(Value::str).collect()))
465}
466
467fn m_obj_values<'a>(
468 _it: &mut Interpreter<'a>,
469 recv: &Value<'a>,
470 _args: &[Value<'a>],
471 _sp: Span,
472) -> Result<Value<'a>, Diagnostic> {
473 let Value::Object(m) = recv else {
474 unreachable!()
475 };
476 Ok(Value::list(m.values().cloned().collect()))
477}
478
479fn m_contains<'a>(
481 _it: &mut Interpreter<'a>,
482 recv: &Value<'a>,
483 args: &[Value<'a>],
484 sp: Span,
485) -> Result<Value<'a>, Diagnostic> {
486 let Some(needle) = args.first() else {
487 return Err(rt("E0306", "contains() expects an argument", sp));
488 };
489 let found = match (recv, needle) {
490 (Value::List(xs), n) => xs.contains(n),
491 (Value::Object(m), Value::Str(k)) => m.contains_key(k.as_ref()),
492 (Value::Str(s), Value::Str(sub)) => s.contains(sub.as_ref()),
493 (Value::Object(_), n) => {
494 return Err(rt(
495 "E0306",
496 format!(
497 "Object.contains expects a String key, got {}",
498 n.type_name()
499 ),
500 sp,
501 ))
502 }
503 (Value::Str(_), n) => {
504 return Err(rt(
505 "E0306",
506 format!("String.contains expects a String, got {}", n.type_name()),
507 sp,
508 ))
509 }
510 _ => unreachable!(),
511 };
512 Ok(Value::Bool(found))
513}
514
515fn m_list_join<'a>(
517 it: &mut Interpreter<'a>,
518 recv: &Value<'a>,
519 args: &[Value<'a>],
520 sp: Span,
521) -> Result<Value<'a>, Diagnostic> {
522 let Value::List(xs) = recv else {
523 unreachable!()
524 };
525 let sep = match args.first() {
526 Some(Value::Str(s)) => s.to_string(),
527 None => String::new(),
528 Some(other) => {
529 return Err(rt(
530 "E0306",
531 format!(
532 "join() expects a String separator, got {}",
533 other.type_name()
534 ),
535 sp,
536 ))
537 }
538 };
539 let parts: Vec<String> = xs
540 .iter()
541 .map(|v| it.display(v, sp))
542 .collect::<Result<_, _>>()?;
543 Ok(Value::str(parts.join(&sep)))
544}
545
546fn m_parse_duration<'a>(
549 _it: &mut Interpreter<'a>,
550 recv: &Value<'a>,
551 _args: &[Value<'a>],
552 sp: Span,
553) -> Result<Value<'a>, Diagnostic> {
554 let Value::Str(s) = recv else { unreachable!() };
555 let err = || {
556 rt(
557 "E0319",
558 format!("invalid duration '{s}': expected e.g. \"1h30m\", units d/h/m/s"),
559 sp,
560 )
561 };
562 let bytes = s.as_bytes();
563 let mut i = 0;
564 let mut total: i64 = 0;
565 let mut components = 0;
566 while i < bytes.len() {
567 let start = i;
568 while i < bytes.len() && bytes[i].is_ascii_digit() {
569 i += 1;
570 }
571 if i == start || i == bytes.len() {
572 return Err(err());
573 }
574 let n: i64 = s[start..i].parse().map_err(|_| err())?;
575 let mult: i64 = match bytes[i] {
576 b'd' => 86400,
577 b'h' => 3600,
578 b'm' => 60,
579 b's' => 1,
580 _ => return Err(err()),
581 };
582 i += 1;
583 total = n
584 .checked_mul(mult)
585 .and_then(|x| total.checked_add(x))
586 .ok_or_else(|| rt("E0304", "duration overflows i64 seconds", sp))?;
587 components += 1;
588 }
589 if components == 0 {
590 return Err(err());
591 }
592 Ok(Value::Int(total))
593}
594
595fn m_format_duration<'a>(
597 _it: &mut Interpreter<'a>,
598 recv: &Value<'a>,
599 _args: &[Value<'a>],
600 sp: Span,
601) -> Result<Value<'a>, Diagnostic> {
602 let Value::Int(total) = recv else {
603 unreachable!()
604 };
605 if *total < 0 {
606 return Err(rt(
607 "E0306",
608 "format_duration expects a non-negative number of seconds",
609 sp,
610 ));
611 }
612 if *total == 0 {
613 return Ok(Value::str("0s"));
614 }
615 let (mut rest, mut out) = (*total, String::new());
616 for (unit, secs) in [("d", 86400), ("h", 3600), ("m", 60), ("s", 1)] {
617 let n = rest / secs;
618 if n > 0 {
619 out.push_str(&format!("{n}{unit}"));
620 rest %= secs;
621 }
622 }
623 Ok(Value::str(out))
624}
625
626fn m_parse_datetime<'a>(
629 _it: &mut Interpreter<'a>,
630 recv: &Value<'a>,
631 _args: &[Value<'a>],
632 sp: Span,
633) -> Result<Value<'a>, Diagnostic> {
634 let Value::Str(s) = recv else { unreachable!() };
635 parse_rfc3339(s).map(Value::Int).ok_or_else(|| {
636 rt(
637 "E0320",
638 format!("invalid datetime '{s}': expected RFC3339, e.g. \"2026-07-18T12:00:00Z\""),
639 sp,
640 )
641 })
642}
643
644fn m_format_datetime<'a>(
646 _it: &mut Interpreter<'a>,
647 recv: &Value<'a>,
648 _args: &[Value<'a>],
649 _sp: Span,
650) -> Result<Value<'a>, Diagnostic> {
651 let Value::Int(epoch) = recv else {
652 unreachable!()
653 };
654 let days = epoch.div_euclid(86400);
655 let secs = epoch.rem_euclid(86400);
656 let (y, m, d) = civil_from_days(days);
657 Ok(Value::str(format!(
658 "{y:04}-{m:02}-{d:02}T{:02}:{:02}:{:02}Z",
659 secs / 3600,
660 (secs % 3600) / 60,
661 secs % 60
662 )))
663}
664
665fn parse_rfc3339(s: &str) -> Option<i64> {
666 let num = |t: &str| -> Option<i64> {
667 if t.bytes().all(|b| b.is_ascii_digit()) && !t.is_empty() {
668 t.parse().ok()
669 } else {
670 None
671 }
672 };
673 let (date, rest) = if s.len() > 10 {
674 if !s.is_char_boundary(10) {
677 return None;
678 }
679 s.split_at(10)
680 } else {
681 (s, "")
682 };
683 let mut dp = date.split('-');
684 let (y, m, d) = (num(dp.next()?)?, num(dp.next()?)?, num(dp.next()?)?);
685 if dp.next().is_some() || !(1..=12).contains(&m) || d < 1 || d > days_in_month(y, m) {
686 return None;
687 }
688 let mut epoch = days_from_civil(y, m, d) * 86400;
689 if rest.is_empty() {
690 return Some(epoch);
691 }
692 let rest = rest.strip_prefix('T')?;
693 if rest.len() < 9 || !rest.is_char_boundary(8) {
694 return None;
695 }
696 let (time, zone) = rest.split_at(8);
697 let mut tp = time.split(':');
698 let (hh, mm, ss) = (num(tp.next()?)?, num(tp.next()?)?, num(tp.next()?)?);
699 if tp.next().is_some() || hh > 23 || mm > 59 || ss > 59 {
700 return None;
701 }
702 epoch += hh * 3600 + mm * 60 + ss;
703 match zone {
704 "Z" => Some(epoch),
705 _ => {
706 let sign = match zone.as_bytes().first()? {
707 b'+' => 1,
708 b'-' => -1,
709 _ => return None,
710 };
711 let (oh, om) = zone[1..].split_once(':')?;
712 let (oh, om) = (num(oh)?, num(om)?);
713 if oh > 23 || om > 59 {
714 return None;
715 }
716 Some(epoch - sign * (oh * 3600 + om * 60))
717 }
718 }
719}
720
721fn days_in_month(y: i64, m: i64) -> i64 {
722 match m {
723 1 | 3 | 5 | 7 | 8 | 10 | 12 => 31,
724 4 | 6 | 9 | 11 => 30,
725 _ => {
726 if y % 4 == 0 && (y % 100 != 0 || y % 400 == 0) {
727 29
728 } else {
729 28
730 }
731 }
732 }
733}
734
735fn days_from_civil(y: i64, m: i64, d: i64) -> i64 {
737 let y = if m <= 2 { y - 1 } else { y };
738 let era = if y >= 0 { y } else { y - 399 } / 400;
739 let yoe = y - era * 400;
740 let doy = (153 * (if m > 2 { m - 3 } else { m + 9 }) + 2) / 5 + d - 1;
741 let doe = yoe * 365 + yoe / 4 - yoe / 100 + doy;
742 era * 146097 + doe - 719468
743}
744
745fn civil_from_days(z: i64) -> (i64, i64, i64) {
746 let z = z + 719468;
747 let era = if z >= 0 { z } else { z - 146096 } / 146097;
748 let doe = z - era * 146097;
749 let yoe = (doe - doe / 1460 + doe / 36524 - doe / 146096) / 365;
750 let y = yoe + era * 400;
751 let doy = doe - (365 * yoe + yoe / 4 - yoe / 100);
752 let mp = (5 * doy + 2) / 153;
753 let d = doy - (153 * mp + 2) / 5 + 1;
754 let m = if mp < 10 { mp + 3 } else { mp - 9 };
755 (if m <= 2 { y + 1 } else { y }, m, d)
756}
757
758fn expect_str_arg<'a>(args: &[Value<'a>], method: &str, sp: Span) -> Result<String, Diagnostic> {
761 match args.first() {
762 Some(Value::Str(s)) => Ok(s.to_string()),
763 Some(other) => Err(rt(
764 "E0306",
765 format!(
766 "{method}() expects a String argument, got {}",
767 other.type_name()
768 ),
769 sp,
770 )),
771 None => Err(rt("E0306", format!("{method}() expects one argument"), sp)),
772 }
773}
774
775fn m_str_trim<'a>(
776 _it: &mut Interpreter<'a>,
777 recv: &Value<'a>,
778 _args: &[Value<'a>],
779 _sp: Span,
780) -> Result<Value<'a>, Diagnostic> {
781 let Value::Str(s) = recv else { unreachable!() };
782 Ok(Value::str(s.trim()))
783}
784
785fn m_str_split<'a>(
787 _it: &mut Interpreter<'a>,
788 recv: &Value<'a>,
789 args: &[Value<'a>],
790 sp: Span,
791) -> Result<Value<'a>, Diagnostic> {
792 let Value::Str(s) = recv else { unreachable!() };
793 let sep = expect_str_arg(args, "split", sp)?;
794 if sep.is_empty() {
795 return Err(rt("E0306", "split() separator must be non-empty", sp));
796 }
797 Ok(Value::list(s.split(&sep).map(Value::str).collect()))
798}
799
800fn m_str_replace<'a>(
802 _it: &mut Interpreter<'a>,
803 recv: &Value<'a>,
804 args: &[Value<'a>],
805 sp: Span,
806) -> Result<Value<'a>, Diagnostic> {
807 let Value::Str(s) = recv else { unreachable!() };
808 let (Some(Value::Str(from)), Some(Value::Str(to))) = (args.first(), args.get(1)) else {
809 return Err(rt(
810 "E0306",
811 "replace(from, to) expects two String arguments",
812 sp,
813 ));
814 };
815 Ok(Value::str(s.replace(from.as_ref(), to)))
816}
817
818fn m_str_starts_with<'a>(
819 _it: &mut Interpreter<'a>,
820 recv: &Value<'a>,
821 args: &[Value<'a>],
822 sp: Span,
823) -> Result<Value<'a>, Diagnostic> {
824 let Value::Str(s) = recv else { unreachable!() };
825 let prefix = expect_str_arg(args, "starts_with", sp)?;
826 Ok(Value::Bool(s.starts_with(&prefix)))
827}
828
829fn m_str_ends_with<'a>(
830 _it: &mut Interpreter<'a>,
831 recv: &Value<'a>,
832 args: &[Value<'a>],
833 sp: Span,
834) -> Result<Value<'a>, Diagnostic> {
835 let Value::Str(s) = recv else { unreachable!() };
836 let suffix = expect_str_arg(args, "ends_with", sp)?;
837 Ok(Value::Bool(s.ends_with(&suffix)))
838}
839
840fn m_str_to_int<'a>(
842 _it: &mut Interpreter<'a>,
843 recv: &Value<'a>,
844 _args: &[Value<'a>],
845 sp: Span,
846) -> Result<Value<'a>, Diagnostic> {
847 let Value::Str(s) = recv else { unreachable!() };
848 s.trim()
849 .parse::<i64>()
850 .map(Value::Int)
851 .map_err(|_| rt("E0314", format!("cannot parse '{s}' as Int"), sp))
852}
853
854fn m_str_to_float<'a>(
855 _it: &mut Interpreter<'a>,
856 recv: &Value<'a>,
857 _args: &[Value<'a>],
858 sp: Span,
859) -> Result<Value<'a>, Diagnostic> {
860 let Value::Str(s) = recv else { unreachable!() };
861 s.trim()
862 .parse::<f64>()
863 .map(Value::Float)
864 .map_err(|_| rt("E0314", format!("cannot parse '{s}' as Float"), sp))
865}
866
867const B64: &[u8; 64] = b"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
875
876fn m_str_sha256<'a>(
878 _it: &mut Interpreter<'a>,
879 recv: &Value<'a>,
880 _args: &[Value<'a>],
881 _sp: Span,
882) -> Result<Value<'a>, Diagnostic> {
883 let Value::Str(s) = recv else { unreachable!() };
884 use sha2::{Digest, Sha256};
885 let digest = Sha256::digest(s.as_bytes());
886 let mut hex = String::with_capacity(digest.len() * 2);
887 for b in digest {
888 hex.push_str(&format!("{b:02x}"));
889 }
890 Ok(Value::str(hex))
891}
892
893fn m_str_base64<'a>(
895 _it: &mut Interpreter<'a>,
896 recv: &Value<'a>,
897 _args: &[Value<'a>],
898 _sp: Span,
899) -> Result<Value<'a>, Diagnostic> {
900 let Value::Str(s) = recv else { unreachable!() };
901 let bytes = s.as_bytes();
902 let mut out = String::with_capacity(bytes.len().div_ceil(3) * 4);
903 for chunk in bytes.chunks(3) {
904 let b = [
905 chunk[0],
906 *chunk.get(1).unwrap_or(&0),
907 *chunk.get(2).unwrap_or(&0),
908 ];
909 let n = (u32::from(b[0]) << 16) | (u32::from(b[1]) << 8) | u32::from(b[2]);
910 for i in 0..4 {
911 if i <= chunk.len() {
912 out.push(B64[((n >> (18 - 6 * i)) & 0x3f) as usize] as char);
913 } else {
914 out.push('=');
915 }
916 }
917 }
918 Ok(Value::str(out))
919}
920
921fn m_str_base64_decode<'a>(
924 _it: &mut Interpreter<'a>,
925 recv: &Value<'a>,
926 _args: &[Value<'a>],
927 sp: Span,
928) -> Result<Value<'a>, Diagnostic> {
929 let Value::Str(s) = recv else { unreachable!() };
930 let err = || rt("E0321", format!("'{s}' is not valid base64"), sp);
931 let src: Vec<u8> = s.bytes().filter(|b| !b.is_ascii_whitespace()).collect();
932 if src.len() % 4 != 0 {
933 return Err(err());
934 }
935 let mut out: Vec<u8> = Vec::with_capacity(src.len() / 4 * 3);
936 for chunk in src.chunks(4) {
937 let pad = chunk.iter().filter(|&&b| b == b'=').count();
938 if pad > 2 || (pad > 0 && chunk[4 - pad..].iter().any(|&b| b != b'=')) {
940 return Err(err());
941 }
942 let mut n = 0u32;
943 for &b in &chunk[..4 - pad] {
944 let v = B64.iter().position(|&c| c == b).ok_or_else(err)? as u32;
945 n = (n << 6) | v;
946 }
947 n <<= 6 * pad as u32;
948 for i in 0..(3 - pad) {
949 out.push(((n >> (16 - 8 * i)) & 0xff) as u8);
950 }
951 }
952 String::from_utf8(out).map(Value::str).map_err(|_| err())
953}
954
955fn scalar_cmp(a: &Value<'_>, b: &Value<'_>) -> Option<std::cmp::Ordering> {
959 use Value::*;
960 match (a, b) {
961 (Int(x), Int(y)) => Some(x.cmp(y)),
962 (Float(x), Float(y)) => x.partial_cmp(y),
963 (Int(x), Float(y)) => (*x as f64).partial_cmp(y),
964 (Float(x), Int(y)) => x.partial_cmp(&(*y as f64)),
965 (Str(x), Str(y)) => Some(x.as_ref().cmp(y.as_ref())),
966 (Bool(x), Bool(y)) => Some(x.cmp(y)),
967 _ => None,
968 }
969}
970
971fn m_list_sort<'a>(
973 _it: &mut Interpreter<'a>,
974 recv: &Value<'a>,
975 _args: &[Value<'a>],
976 sp: Span,
977) -> Result<Value<'a>, Diagnostic> {
978 let Value::List(xs) = recv else {
979 unreachable!()
980 };
981 if let Some(first) = xs.first() {
983 for v in xs.iter() {
984 if scalar_cmp(first, v).is_none() {
985 return Err(rt(
986 "E0306",
987 format!(
988 "sort() cannot compare {} and {}",
989 first.type_name(),
990 v.type_name()
991 ),
992 sp,
993 ));
994 }
995 }
996 }
997 let mut out: Vec<Value<'a>> = xs.to_vec();
998 out.sort_by(|a, b| scalar_cmp(a, b).unwrap_or(std::cmp::Ordering::Equal));
999 Ok(Value::list(out))
1000}
1001
1002fn m_list_reverse<'a>(
1003 _it: &mut Interpreter<'a>,
1004 recv: &Value<'a>,
1005 _args: &[Value<'a>],
1006 _sp: Span,
1007) -> Result<Value<'a>, Diagnostic> {
1008 let Value::List(xs) = recv else {
1009 unreachable!()
1010 };
1011 Ok(Value::list(xs.iter().rev().cloned().collect()))
1012}
1013
1014fn m_list_sum<'a>(
1016 _it: &mut Interpreter<'a>,
1017 recv: &Value<'a>,
1018 args: &[Value<'a>],
1019 sp: Span,
1020) -> Result<Value<'a>, Diagnostic> {
1021 let _ = args;
1022 let Value::List(xs) = recv else {
1023 unreachable!()
1024 };
1025 let any_float = xs.iter().any(|v| matches!(v, Value::Float(_)));
1026 if any_float {
1027 let mut acc = 0.0f64;
1028 for v in xs.iter() {
1029 match v {
1030 Value::Int(n) => acc += *n as f64,
1031 Value::Float(f) => acc += *f,
1032 other => {
1033 return Err(rt(
1034 "E0306",
1035 format!("sum() expects numbers, got {}", other.type_name()),
1036 sp,
1037 ))
1038 }
1039 }
1040 }
1041 Ok(Value::Float(acc))
1042 } else {
1043 let mut acc = 0i64;
1044 for v in xs.iter() {
1045 match v {
1046 Value::Int(n) => {
1047 acc = acc
1048 .checked_add(*n)
1049 .ok_or_else(|| rt("E0304", "sum() overflows i64", sp))?
1050 }
1051 other => {
1052 return Err(rt(
1053 "E0306",
1054 format!("sum() expects numbers, got {}", other.type_name()),
1055 sp,
1056 ))
1057 }
1058 }
1059 }
1060 Ok(Value::Int(acc))
1061 }
1062}
1063
1064fn list_extreme<'a>(
1065 xs: &[Value<'a>],
1066 want_max: bool,
1067 method: &str,
1068 sp: Span,
1069) -> Result<Value<'a>, Diagnostic> {
1070 let mut best = xs
1071 .first()
1072 .ok_or_else(|| rt("E0317", format!("{method}() on an empty list"), sp))?;
1073 for v in &xs[1..] {
1074 let ord = scalar_cmp(v, best).ok_or_else(|| {
1075 rt(
1076 "E0306",
1077 format!(
1078 "{method}() cannot compare {} and {}",
1079 v.type_name(),
1080 best.type_name()
1081 ),
1082 sp,
1083 )
1084 })?;
1085 if (want_max && ord == std::cmp::Ordering::Greater)
1086 || (!want_max && ord == std::cmp::Ordering::Less)
1087 {
1088 best = v;
1089 }
1090 }
1091 Ok(best.clone())
1092}
1093
1094fn m_list_min<'a>(
1095 _it: &mut Interpreter<'a>,
1096 recv: &Value<'a>,
1097 _args: &[Value<'a>],
1098 sp: Span,
1099) -> Result<Value<'a>, Diagnostic> {
1100 let Value::List(xs) = recv else {
1101 unreachable!()
1102 };
1103 list_extreme(xs, false, "min", sp)
1104}
1105
1106fn m_list_max<'a>(
1107 _it: &mut Interpreter<'a>,
1108 recv: &Value<'a>,
1109 _args: &[Value<'a>],
1110 sp: Span,
1111) -> Result<Value<'a>, Diagnostic> {
1112 let Value::List(xs) = recv else {
1113 unreachable!()
1114 };
1115 list_extreme(xs, true, "max", sp)
1116}
1117
1118fn m_list_flatten<'a>(
1120 _it: &mut Interpreter<'a>,
1121 recv: &Value<'a>,
1122 _args: &[Value<'a>],
1123 _sp: Span,
1124) -> Result<Value<'a>, Diagnostic> {
1125 let Value::List(xs) = recv else {
1126 unreachable!()
1127 };
1128 let mut out: Vec<Value<'a>> = Vec::new();
1129 for v in xs.iter() {
1130 match v {
1131 Value::List(inner) => out.extend(inner.iter().cloned()),
1132 other => out.push(other.clone()),
1133 }
1134 }
1135 Ok(Value::list(out))
1136}
1137
1138fn m_list_slice<'a>(
1140 _it: &mut Interpreter<'a>,
1141 recv: &Value<'a>,
1142 args: &[Value<'a>],
1143 sp: Span,
1144) -> Result<Value<'a>, Diagnostic> {
1145 let Value::List(xs) = recv else {
1146 unreachable!()
1147 };
1148 let (Some(Value::Int(start)), Some(Value::Int(end))) = (args.first(), args.get(1)) else {
1149 return Err(rt(
1150 "E0306",
1151 "slice(start, end) expects two Int arguments",
1152 sp,
1153 ));
1154 };
1155 if *start < 0 || *end < 0 {
1156 return Err(rt("E0306", "slice() indices must be non-negative", sp));
1157 }
1158 let len = xs.len();
1159 let s = (*start as usize).min(len);
1160 let e = (*end as usize).clamp(s, len);
1161 Ok(Value::list(xs[s..e].to_vec()))
1162}
1163
1164fn m_num_abs<'a>(
1167 _it: &mut Interpreter<'a>,
1168 recv: &Value<'a>,
1169 _args: &[Value<'a>],
1170 sp: Span,
1171) -> Result<Value<'a>, Diagnostic> {
1172 match recv {
1173 Value::Int(n) => n
1174 .checked_abs()
1175 .map(Value::Int)
1176 .ok_or_else(|| rt("E0304", "abs() overflows i64", sp)),
1177 Value::Float(f) => Ok(Value::Float(f.abs())),
1178 _ => unreachable!(),
1179 }
1180}
1181
1182fn m_to_str<'a>(
1184 it: &mut Interpreter<'a>,
1185 recv: &Value<'a>,
1186 _args: &[Value<'a>],
1187 sp: Span,
1188) -> Result<Value<'a>, Diagnostic> {
1189 Ok(Value::str(it.display(recv, sp)?))
1190}
1191
1192fn json_to_value<'a>(j: serde_json::Value) -> Value<'a> {
1193 match j {
1194 serde_json::Value::Null => Value::Null,
1195 serde_json::Value::Bool(b) => Value::Bool(b),
1196 serde_json::Value::Number(n) => match n.as_i64() {
1197 Some(i) => Value::Int(i),
1198 None => Value::Float(n.as_f64().unwrap_or(f64::NAN)),
1199 },
1200 serde_json::Value::String(s) => Value::str(s),
1201 serde_json::Value::Array(xs) => Value::list(xs.into_iter().map(json_to_value).collect()),
1202 serde_json::Value::Object(m) => Value::Object(Arc::new(
1203 m.into_iter().map(|(k, v)| (k, json_to_value(v))).collect(),
1204 )),
1205 }
1206}
1207
1208fn toml_to_value<'a>(t: toml::Value) -> Value<'a> {
1209 match t {
1210 toml::Value::String(s) => Value::str(s),
1211 toml::Value::Integer(n) => Value::Int(n),
1212 toml::Value::Float(n) => Value::Float(n),
1213 toml::Value::Boolean(b) => Value::Bool(b),
1214 toml::Value::Datetime(d) => Value::str(d.to_string()),
1215 toml::Value::Array(xs) => Value::list(xs.into_iter().map(toml_to_value).collect()),
1216 toml::Value::Table(t) => Value::Object(Arc::new(
1217 t.into_iter().map(|(k, v)| (k, toml_to_value(v))).collect(),
1218 )),
1219 }
1220}