1use lex_bytecode::{MapKey, Value};
6use std::collections::{BTreeMap, BTreeSet, HashMap};
7use std::sync::{Mutex, OnceLock};
8
9pub fn try_pure_builtin(kind: &str, op: &str, args: &[Value]) -> Option<Result<Value, String>> {
12 if !is_pure_module(kind) { return None; }
13 if (kind, op) == ("crypto", "random") { return None; }
17 if (kind, op) == ("datetime", "now") { return None; }
20 if (kind, "send") == (kind, op) && kind == "http" { return None; }
23 if (kind, "get") == (kind, op) && kind == "http" { return None; }
24 if (kind, "post") == (kind, op) && kind == "http" { return None; }
25 Some(dispatch(kind, op, args))
26}
27
28pub fn is_pure_module(kind: &str) -> bool {
31 matches!(kind, "str" | "int" | "float" | "bool" | "list"
32 | "option" | "result" | "tuple" | "json" | "bytes" | "flow" | "math"
33 | "map" | "set" | "crypto" | "regex" | "deque" | "datetime" | "http"
34 | "toml" | "yaml" | "dotenv" | "csv" | "test")
35}
36
37fn dispatch(kind: &str, op: &str, args: &[Value]) -> Result<Value, String> {
38 match (kind, op) {
39 ("str", "is_empty") => Ok(Value::Bool(expect_str(args.first())?.is_empty())),
41 ("str", "len") => Ok(Value::Int(expect_str(args.first())?.len() as i64)),
42 ("str", "concat") => {
43 let a = expect_str(args.first())?;
44 let b = expect_str(args.get(1))?;
45 Ok(Value::Str(format!("{a}{b}")))
46 }
47 ("str", "to_int") => {
48 let s = expect_str(args.first())?;
49 match s.parse::<i64>() {
50 Ok(n) => Ok(some(Value::Int(n))),
51 Err(_) => Ok(none()),
52 }
53 }
54 ("str", "split") => {
55 let s = expect_str(args.first())?;
56 let sep = expect_str(args.get(1))?;
57 let items: Vec<Value> = if sep.is_empty() {
58 s.chars().map(|c| Value::Str(c.to_string())).collect()
59 } else {
60 s.split(sep.as_str()).map(|p| Value::Str(p.to_string())).collect()
61 };
62 Ok(Value::List(items))
63 }
64 ("str", "join") => {
65 let parts = expect_list(args.first())?;
66 let sep = expect_str(args.get(1))?;
67 let mut out = String::new();
68 for (i, p) in parts.iter().enumerate() {
69 if i > 0 { out.push_str(&sep); }
70 match p {
71 Value::Str(s) => out.push_str(s),
72 other => return Err(format!("str.join element must be Str, got {other:?}")),
73 }
74 }
75 Ok(Value::Str(out))
76 }
77 ("str", "starts_with") => {
78 let s = expect_str(args.first())?;
79 let prefix = expect_str(args.get(1))?;
80 Ok(Value::Bool(s.starts_with(prefix.as_str())))
81 }
82 ("str", "ends_with") => {
83 let s = expect_str(args.first())?;
84 let suffix = expect_str(args.get(1))?;
85 Ok(Value::Bool(s.ends_with(suffix.as_str())))
86 }
87 ("str", "contains") => {
88 let s = expect_str(args.first())?;
89 let needle = expect_str(args.get(1))?;
90 Ok(Value::Bool(s.contains(needle.as_str())))
91 }
92 ("str", "replace") => {
93 let s = expect_str(args.first())?;
94 let from = expect_str(args.get(1))?;
95 let to = expect_str(args.get(2))?;
96 Ok(Value::Str(s.replace(from.as_str(), to.as_str())))
97 }
98 ("str", "trim") => Ok(Value::Str(expect_str(args.first())?.trim().to_string())),
99 ("str", "to_upper") => Ok(Value::Str(expect_str(args.first())?.to_uppercase())),
100 ("str", "to_lower") => Ok(Value::Str(expect_str(args.first())?.to_lowercase())),
101 ("str", "strip_prefix") => {
102 let s = expect_str(args.first())?;
103 let prefix = expect_str(args.get(1))?;
104 Ok(match s.strip_prefix(prefix.as_str()) {
105 Some(rest) => some(Value::Str(rest.to_string())),
106 None => none(),
107 })
108 }
109 ("str", "strip_suffix") => {
110 let s = expect_str(args.first())?;
111 let suffix = expect_str(args.get(1))?;
112 Ok(match s.strip_suffix(suffix.as_str()) {
113 Some(rest) => some(Value::Str(rest.to_string())),
114 None => none(),
115 })
116 }
117 ("str", "slice") => {
118 let s = expect_str(args.first())?;
123 let lo = expect_int(args.get(1))? as usize;
124 let hi = expect_int(args.get(2))? as usize;
125 if lo > hi || hi > s.len() {
126 return Err(format!("str.slice: out of range [{lo}..{hi}] of len {}", s.len()));
127 }
128 if !s.is_char_boundary(lo) || !s.is_char_boundary(hi) {
129 return Err(format!("str.slice: [{lo}..{hi}] not on char boundaries"));
130 }
131 Ok(Value::Str(s[lo..hi].to_string()))
132 }
133
134 ("int", "to_str") => Ok(Value::Str(expect_int(args.first())?.to_string())),
136 ("int", "to_float") => Ok(Value::Float(expect_int(args.first())? as f64)),
137 ("float", "to_int") => Ok(Value::Int(expect_float(args.first())? as i64)),
138 ("float", "to_str") => Ok(Value::Str(expect_float(args.first())?.to_string())),
139 ("str", "to_float") => {
140 let s = expect_str(args.first())?;
141 match s.parse::<f64>() {
142 Ok(f) => Ok(some(Value::Float(f))),
143 Err(_) => Ok(none()),
144 }
145 }
146
147 ("list", "len") => Ok(Value::Int(expect_list(args.first())?.len() as i64)),
149 ("list", "is_empty") => Ok(Value::Bool(expect_list(args.first())?.is_empty())),
150 ("list", "head") => {
151 let xs = expect_list(args.first())?;
152 match xs.first() {
153 Some(v) => Ok(some(v.clone())),
154 None => Ok(none()),
155 }
156 }
157 ("list", "tail") => {
158 let xs = expect_list(args.first())?;
159 if xs.is_empty() { Ok(Value::List(Vec::new())) }
160 else { Ok(Value::List(xs[1..].to_vec())) }
161 }
162 ("list", "range") => {
163 let lo = expect_int(args.first())?;
164 let hi = expect_int(args.get(1))?;
165 Ok(Value::List((lo..hi).map(Value::Int).collect()))
166 }
167 ("list", "concat") => {
168 let mut out = expect_list(args.first())?.clone();
169 out.extend(expect_list(args.get(1))?.iter().cloned());
170 Ok(Value::List(out))
171 }
172
173 ("tuple", "fst") => tuple_index(first_arg(args)?, 0),
178 ("tuple", "snd") => tuple_index(first_arg(args)?, 1),
179 ("tuple", "third") => tuple_index(first_arg(args)?, 2),
180 ("tuple", "len") => match first_arg(args)? {
181 Value::Tuple(items) => Ok(Value::Int(items.len() as i64)),
182 other => Err(format!("tuple.len: expected Tuple, got {other:?}")),
183 },
184
185 ("option", "unwrap_or") => {
187 let opt = first_arg(args)?;
188 let default = args.get(1).cloned().unwrap_or(Value::Unit);
189 match opt {
190 Value::Variant { name, args } if name == "Some" && !args.is_empty() => Ok(args[0].clone()),
191 Value::Variant { name, .. } if name == "None" => Ok(default),
192 other => Err(format!("option.unwrap_or expected Option, got {other:?}")),
193 }
194 }
195 ("option", "is_some") => match first_arg(args)? {
196 Value::Variant { name, .. } => Ok(Value::Bool(name == "Some")),
197 other => Err(format!("option.is_some expected Option, got {other:?}")),
198 },
199 ("option", "is_none") => match first_arg(args)? {
200 Value::Variant { name, .. } => Ok(Value::Bool(name == "None")),
201 other => Err(format!("option.is_none expected Option, got {other:?}")),
202 },
203
204 ("result", "is_ok") => match first_arg(args)? {
206 Value::Variant { name, .. } => Ok(Value::Bool(name == "Ok")),
207 other => Err(format!("result.is_ok expected Result, got {other:?}")),
208 },
209 ("result", "is_err") => match first_arg(args)? {
210 Value::Variant { name, .. } => Ok(Value::Bool(name == "Err")),
211 other => Err(format!("result.is_err expected Result, got {other:?}")),
212 },
213 ("result", "unwrap_or") => {
214 let res = first_arg(args)?;
215 let default = args.get(1).cloned().unwrap_or(Value::Unit);
216 match res {
217 Value::Variant { name, args } if name == "Ok" && !args.is_empty() => Ok(args[0].clone()),
218 Value::Variant { name, .. } if name == "Err" => Ok(default),
219 other => Err(format!("result.unwrap_or expected Result, got {other:?}")),
220 }
221 }
222
223 ("json", "stringify") => {
225 let v = first_arg(args)?;
226 Ok(Value::Str(serde_json::to_string(&value_to_json(v)).unwrap_or_default()))
227 }
228 ("json", "parse") => {
229 let s = expect_str(args.first())?;
230 match serde_json::from_str::<serde_json::Value>(&s) {
231 Ok(v) => Ok(ok_v(json_to_value(&v))),
232 Err(e) => Ok(err_v(Value::Str(format!("{e}")))),
233 }
234 }
235 ("json", "parse_strict") => {
239 let s = expect_str(args.first())?;
240 let required = required_field_names(args.get(1))?;
241 match serde_json::from_str::<serde_json::Value>(&s) {
242 Ok(v) => match check_required_fields(&v, &required) {
243 Ok(()) => Ok(ok_v(json_to_value(&v))),
244 Err(e) => Ok(err_v(Value::Str(e))),
245 },
246 Err(e) => Ok(err_v(Value::Str(format!("{e}")))),
247 }
248 }
249
250 ("toml", "parse") => {
255 let s = expect_str(args.first())?;
256 match toml::from_str::<serde_json::Value>(&s) {
257 Ok(mut v) => {
258 unwrap_toml_datetime_markers(&mut v);
259 Ok(ok_v(json_to_value(&v)))
260 }
261 Err(e) => Ok(err_v(Value::Str(format!("{e}")))),
262 }
263 }
264 ("toml", "parse_strict") => {
269 let s = expect_str(args.first())?;
270 let required = required_field_names(args.get(1))?;
271 match toml::from_str::<serde_json::Value>(&s) {
272 Ok(mut v) => {
273 unwrap_toml_datetime_markers(&mut v);
274 match check_required_fields(&v, &required) {
275 Ok(()) => Ok(ok_v(json_to_value(&v))),
276 Err(e) => Ok(err_v(Value::Str(e))),
277 }
278 }
279 Err(e) => Ok(err_v(Value::Str(format!("{e}")))),
280 }
281 }
282 ("toml", "stringify") => {
283 let v = first_arg(args)?;
284 let json = value_to_json(v);
290 match toml::to_string(&json) {
291 Ok(s) => Ok(ok_v(Value::Str(s))),
292 Err(e) => Ok(err_v(Value::Str(format!("toml.stringify: {e}")))),
293 }
294 }
295
296 ("yaml", "parse") => {
302 let s = expect_str(args.first())?;
303 match serde_yaml::from_str::<serde_json::Value>(&s) {
304 Ok(v) => Ok(ok_v(json_to_value(&v))),
305 Err(e) => Ok(err_v(Value::Str(format!("{e}")))),
306 }
307 }
308 ("yaml", "parse_strict") => {
310 let s = expect_str(args.first())?;
311 let required = required_field_names(args.get(1))?;
312 match serde_yaml::from_str::<serde_json::Value>(&s) {
313 Ok(v) => match check_required_fields(&v, &required) {
314 Ok(()) => Ok(ok_v(json_to_value(&v))),
315 Err(e) => Ok(err_v(Value::Str(e))),
316 },
317 Err(e) => Ok(err_v(Value::Str(format!("{e}")))),
318 }
319 }
320 ("yaml", "stringify") => {
321 let v = first_arg(args)?;
322 let json = value_to_json(v);
323 match serde_yaml::to_string(&json) {
324 Ok(s) => Ok(ok_v(Value::Str(s))),
325 Err(e) => Ok(err_v(Value::Str(format!("yaml.stringify: {e}")))),
326 }
327 }
328
329 ("dotenv", "parse") => {
337 use std::collections::BTreeMap;
338 use lex_bytecode::MapKey;
339 let s = expect_str(args.first())?;
340 match parse_dotenv(&s) {
341 Ok(map) => {
342 let mut bt: BTreeMap<MapKey, Value> = BTreeMap::new();
343 for (k, v) in map {
344 bt.insert(MapKey::Str(k), Value::Str(v));
345 }
346 Ok(ok_v(Value::Map(bt)))
347 }
348 Err(e) => Ok(err_v(Value::Str(e))),
349 }
350 }
351
352 ("csv", "parse") => {
357 let s = expect_str(args.first())?;
358 let mut rdr = csv::ReaderBuilder::new()
359 .has_headers(false)
360 .flexible(true)
361 .from_reader(s.as_bytes());
362 let mut rows: Vec<Value> = Vec::new();
363 for r in rdr.records() {
364 match r {
365 Ok(rec) => {
366 let row: Vec<Value> = rec.iter()
367 .map(|f| Value::Str(f.to_string()))
368 .collect();
369 rows.push(Value::List(row));
370 }
371 Err(e) => return Ok(err_v(Value::Str(format!("csv.parse: {e}")))),
372 }
373 }
374 Ok(ok_v(Value::List(rows)))
375 }
376 ("csv", "stringify") => {
377 let v = first_arg(args)?;
382 let rows = match v {
383 Value::List(rs) => rs,
384 _ => return Ok(err_v(Value::Str("csv.stringify expects List[List[Str]]".into()))),
385 };
386 let mut out = Vec::new();
387 {
388 let mut wtr = csv::WriterBuilder::new()
389 .has_headers(false)
390 .from_writer(&mut out);
391 for row in rows {
392 let cells = match row {
393 Value::List(cs) => cs,
394 _ => return Ok(err_v(Value::Str("csv.stringify row must be List[Str]".into()))),
395 };
396 let strs: Vec<String> = cells.iter().map(|c| match c {
397 Value::Str(s) => s.clone(),
398 other => serde_json::to_string(&other.to_json())
399 .unwrap_or_else(|_| String::new()),
400 }).collect();
401 if let Err(e) = wtr.write_record(&strs) {
402 return Ok(err_v(Value::Str(format!("csv.stringify: {e}"))));
403 }
404 }
405 if let Err(e) = wtr.flush() {
406 return Ok(err_v(Value::Str(format!("csv.stringify flush: {e}"))));
407 }
408 }
409 match String::from_utf8(out) {
410 Ok(s) => Ok(ok_v(Value::Str(s))),
411 Err(e) => Ok(err_v(Value::Str(format!("csv.stringify utf8: {e}")))),
412 }
413 }
414
415 ("test", "assert_eq") => {
422 let a = first_arg(args)?;
423 let b = args.get(1).ok_or("test.assert_eq: missing second arg")?;
424 if a == b {
425 Ok(ok_v(Value::Unit))
426 } else {
427 Ok(err_v(Value::Str(format!("assert_eq: lhs {} != rhs {}",
428 value_to_json(a), value_to_json(b)))))
429 }
430 }
431 ("test", "assert_ne") => {
432 let a = first_arg(args)?;
433 let b = args.get(1).ok_or("test.assert_ne: missing second arg")?;
434 if a != b {
435 Ok(ok_v(Value::Unit))
436 } else {
437 Ok(err_v(Value::Str(format!("assert_ne: both sides are {}",
438 value_to_json(a)))))
439 }
440 }
441 ("test", "assert_true") => {
442 match first_arg(args)? {
443 Value::Bool(true) => Ok(ok_v(Value::Unit)),
444 Value::Bool(false) => Ok(err_v(Value::Str("assert_true: was false".into()))),
445 other => Err(format!("test.assert_true expects Bool, got {other:?}")),
446 }
447 }
448 ("test", "assert_false") => {
449 match first_arg(args)? {
450 Value::Bool(false) => Ok(ok_v(Value::Unit)),
451 Value::Bool(true) => Ok(err_v(Value::Str("assert_false: was true".into()))),
452 other => Err(format!("test.assert_false expects Bool, got {other:?}")),
453 }
454 }
455
456 ("bytes", "len") => {
458 let b = expect_bytes(args.first())?;
459 Ok(Value::Int(b.len() as i64))
460 }
461 ("bytes", "eq") => {
462 let a = expect_bytes(args.first())?;
463 let b = expect_bytes(args.get(1))?;
464 Ok(Value::Bool(a == b))
465 }
466 ("bytes", "from_str") => {
467 let s = expect_str(args.first())?;
468 Ok(Value::Bytes(s.into_bytes()))
469 }
470 ("bytes", "to_str") => {
471 let b = expect_bytes(args.first())?;
472 match String::from_utf8(b.to_vec()) {
473 Ok(s) => Ok(ok_v(Value::Str(s))),
474 Err(e) => Ok(err_v(Value::Str(format!("{e}")))),
475 }
476 }
477 ("bytes", "slice") => {
478 let b = expect_bytes(args.first())?;
479 let lo = expect_int(args.get(1))? as usize;
480 let hi = expect_int(args.get(2))? as usize;
481 if lo > hi || hi > b.len() {
482 return Err(format!("bytes.slice: out of range [{lo}..{hi}] of {}", b.len()));
483 }
484 Ok(Value::Bytes(b[lo..hi].to_vec()))
485 }
486 ("bytes", "is_empty") => {
487 let b = expect_bytes(args.first())?;
488 Ok(Value::Bool(b.is_empty()))
489 }
490
491 ("math", "exp") => Ok(Value::Float(expect_float(args.first())?.exp())),
498 ("math", "log") => Ok(Value::Float(expect_float(args.first())?.ln())),
499 ("math", "sqrt") => Ok(Value::Float(expect_float(args.first())?.sqrt())),
500 ("math", "abs") => Ok(Value::Float(expect_float(args.first())?.abs())),
501 ("math", "zeros") => {
502 let r = expect_int(args.first())?;
503 let c = expect_int(args.get(1))?;
504 if r < 0 || c < 0 {
505 return Err(format!("math.zeros: negative dim {r}x{c}"));
506 }
507 let r = r as usize; let c = c as usize;
508 Ok(Value::F64Array { rows: r as u32, cols: c as u32, data: vec![0.0; r * c] })
509 }
510 ("math", "ones") => {
511 let r = expect_int(args.first())?;
512 let c = expect_int(args.get(1))?;
513 if r < 0 || c < 0 {
514 return Err(format!("math.ones: negative dim {r}x{c}"));
515 }
516 let r = r as usize; let c = c as usize;
517 Ok(Value::F64Array { rows: r as u32, cols: c as u32, data: vec![1.0; r * c] })
518 }
519 ("math", "from_lists") => {
520 let rows = expect_list(args.first())?;
521 let r = rows.len();
522 if r == 0 {
523 return Ok(Value::F64Array { rows: 0, cols: 0, data: Vec::new() });
524 }
525 let first_row = match &rows[0] {
526 Value::List(xs) => xs,
527 other => return Err(format!("math.from_lists: row 0 not List, got {other:?}")),
528 };
529 let c = first_row.len();
530 let mut data = Vec::with_capacity(r * c);
531 for (i, row) in rows.iter().enumerate() {
532 let row = match row {
533 Value::List(xs) => xs,
534 other => return Err(format!("math.from_lists: row {i} not List, got {other:?}")),
535 };
536 if row.len() != c {
537 return Err(format!("math.from_lists: row {i} has {} cols, expected {c}", row.len()));
538 }
539 for (j, v) in row.iter().enumerate() {
540 let f = match v {
541 Value::Float(f) => *f,
542 Value::Int(n) => *n as f64,
543 other => return Err(format!("math.from_lists: ({i},{j}) not numeric, got {other:?}")),
544 };
545 data.push(f);
546 }
547 }
548 Ok(Value::F64Array { rows: r as u32, cols: c as u32, data })
549 }
550 ("math", "from_flat") => {
551 let r = expect_int(args.first())?;
552 let c = expect_int(args.get(1))?;
553 let xs = expect_list(args.get(2))?;
554 if r < 0 || c < 0 {
555 return Err(format!("math.from_flat: negative dim {r}x{c}"));
556 }
557 let r = r as usize; let c = c as usize;
558 if xs.len() != r * c {
559 return Err(format!("math.from_flat: list len {} != {}*{}", xs.len(), r, c));
560 }
561 let mut data = Vec::with_capacity(r * c);
562 for v in xs {
563 data.push(match v {
564 Value::Float(f) => *f,
565 Value::Int(n) => *n as f64,
566 other => return Err(format!("math.from_flat: non-numeric element {other:?}")),
567 });
568 }
569 Ok(Value::F64Array { rows: r as u32, cols: c as u32, data })
570 }
571 ("math", "rows") => {
572 let (r, _, _) = unpack_matrix(first_arg(args)?)?;
573 Ok(Value::Int(r as i64))
574 }
575 ("math", "cols") => {
576 let (_, c, _) = unpack_matrix(first_arg(args)?)?;
577 Ok(Value::Int(c as i64))
578 }
579 ("math", "get") => {
580 let (r, c, data) = unpack_matrix(first_arg(args)?)?;
581 let i = expect_int(args.get(1))? as usize;
582 let j = expect_int(args.get(2))? as usize;
583 if i >= r || j >= c {
584 return Err(format!("math.get: ({i},{j}) out of {r}x{c}"));
585 }
586 Ok(Value::Float(data[i * c + j]))
587 }
588 ("math", "to_flat") => {
589 let (_, _, data) = unpack_matrix(first_arg(args)?)?;
590 Ok(Value::List(data.into_iter().map(Value::Float).collect()))
591 }
592 ("math", "transpose") => {
593 let (r, c, data) = unpack_matrix(first_arg(args)?)?;
594 let mut out = vec![0.0; r * c];
595 for i in 0..r {
596 for j in 0..c {
597 out[j * r + i] = data[i * c + j];
598 }
599 }
600 Ok(Value::F64Array { rows: c as u32, cols: r as u32, data: out })
601 }
602 ("math", "matmul") => {
603 let (m, k1, a) = unpack_matrix(first_arg(args)?)?;
604 let (k2, n, b) = unpack_matrix(args.get(1).ok_or("math.matmul: missing arg 1")?)?;
605 if k1 != k2 {
606 return Err(format!("math.matmul: dim mismatch {m}x{k1} · {k2}x{n}"));
607 }
608 let mut c = vec![0.0; m * n];
612 for i in 0..m {
613 for kk in 0..k1 {
614 let aik = a[i * k1 + kk];
615 for j in 0..n {
616 c[i * n + j] += aik * b[kk * n + j];
617 }
618 }
619 }
620 Ok(Value::F64Array { rows: m as u32, cols: n as u32, data: c })
621 }
622 ("math", "scale") => {
623 let s = expect_float(args.first())?;
624 let (r, c, mut data) = unpack_matrix(args.get(1).ok_or("math.scale: missing arg 1")?)?;
625 for x in &mut data { *x *= s; }
626 Ok(Value::F64Array { rows: r as u32, cols: c as u32, data })
627 }
628 ("math", "add") | ("math", "sub") => {
629 let (ar, ac, a) = unpack_matrix(first_arg(args)?)?;
630 let (br, bc, b) = unpack_matrix(args.get(1).ok_or("math.add/sub: missing arg 1")?)?;
631 if ar != br || ac != bc {
632 return Err(format!("math.{op}: shape mismatch {ar}x{ac} vs {br}x{bc}"));
633 }
634 let neg = op == "sub";
635 let mut out = a;
636 for (i, x) in out.iter_mut().enumerate() {
637 if neg { *x -= b[i] } else { *x += b[i] }
638 }
639 Ok(Value::F64Array { rows: ar as u32, cols: ac as u32, data: out })
640 }
641 ("math", "sigmoid") => {
642 let (r, c, mut data) = unpack_matrix(first_arg(args)?)?;
643 for x in &mut data { *x = 1.0 / (1.0 + (-*x).exp()); }
644 Ok(Value::F64Array { rows: r as u32, cols: c as u32, data })
645 }
646
647 ("map", "new") => Ok(Value::Map(BTreeMap::new())),
649 ("map", "size") => Ok(Value::Int(expect_map(args.first())?.len() as i64)),
650 ("map", "has") => {
651 let m = expect_map(args.first())?;
652 let k = MapKey::from_value(args.get(1).ok_or("map.has: missing key")?)?;
653 Ok(Value::Bool(m.contains_key(&k)))
654 }
655 ("map", "get") => {
656 let m = expect_map(args.first())?;
657 let k = MapKey::from_value(args.get(1).ok_or("map.get: missing key")?)?;
658 Ok(match m.get(&k) {
659 Some(v) => some(v.clone()),
660 None => none(),
661 })
662 }
663 ("map", "set") => {
664 let mut m = expect_map(args.first())?.clone();
665 let k = MapKey::from_value(args.get(1).ok_or("map.set: missing key")?)?;
666 let v = args.get(2).ok_or("map.set: missing value")?.clone();
667 m.insert(k, v);
668 Ok(Value::Map(m))
669 }
670 ("map", "delete") => {
671 let mut m = expect_map(args.first())?.clone();
672 let k = MapKey::from_value(args.get(1).ok_or("map.delete: missing key")?)?;
673 m.remove(&k);
674 Ok(Value::Map(m))
675 }
676 ("map", "keys") => {
677 let m = expect_map(args.first())?;
678 Ok(Value::List(m.keys().cloned().map(MapKey::into_value).collect()))
679 }
680 ("map", "values") => {
681 let m = expect_map(args.first())?;
682 Ok(Value::List(m.values().cloned().collect()))
683 }
684 ("map", "entries") => {
685 let m = expect_map(args.first())?;
686 Ok(Value::List(m.iter()
687 .map(|(k, v)| Value::Tuple(vec![k.as_value(), v.clone()]))
688 .collect()))
689 }
690 ("map", "from_list") => {
691 let pairs = expect_list(args.first())?;
692 let mut m = BTreeMap::new();
693 for p in pairs {
694 let items = match p {
695 Value::Tuple(items) if items.len() == 2 => items,
696 other => return Err(format!(
697 "map.from_list element must be a 2-tuple, got {other:?}")),
698 };
699 let k = MapKey::from_value(&items[0])?;
700 m.insert(k, items[1].clone());
701 }
702 Ok(Value::Map(m))
703 }
704
705 ("set", "new") => Ok(Value::Set(BTreeSet::new())),
707 ("set", "size") => Ok(Value::Int(expect_set(args.first())?.len() as i64)),
708 ("set", "has") => {
709 let s = expect_set(args.first())?;
710 let k = MapKey::from_value(args.get(1).ok_or("set.has: missing element")?)?;
711 Ok(Value::Bool(s.contains(&k)))
712 }
713 ("set", "add") => {
714 let mut s = expect_set(args.first())?.clone();
715 let k = MapKey::from_value(args.get(1).ok_or("set.add: missing element")?)?;
716 s.insert(k);
717 Ok(Value::Set(s))
718 }
719 ("set", "delete") => {
720 let mut s = expect_set(args.first())?.clone();
721 let k = MapKey::from_value(args.get(1).ok_or("set.delete: missing element")?)?;
722 s.remove(&k);
723 Ok(Value::Set(s))
724 }
725 ("set", "to_list") => {
726 let s = expect_set(args.first())?;
727 Ok(Value::List(s.iter().cloned().map(MapKey::into_value).collect()))
728 }
729 ("set", "from_list") => {
730 let xs = expect_list(args.first())?;
731 let mut s = BTreeSet::new();
732 for x in xs {
733 s.insert(MapKey::from_value(x)?);
734 }
735 Ok(Value::Set(s))
736 }
737 ("set", "union") => {
738 let a = expect_set(args.first())?;
739 let b = expect_set(args.get(1))?;
740 Ok(Value::Set(a.union(b).cloned().collect()))
741 }
742 ("set", "intersect") => {
743 let a = expect_set(args.first())?;
744 let b = expect_set(args.get(1))?;
745 Ok(Value::Set(a.intersection(b).cloned().collect()))
746 }
747 ("set", "diff") => {
748 let a = expect_set(args.first())?;
749 let b = expect_set(args.get(1))?;
750 Ok(Value::Set(a.difference(b).cloned().collect()))
751 }
752 ("set", "is_empty") => Ok(Value::Bool(expect_set(args.first())?.is_empty())),
753 ("set", "is_subset") => {
754 let a = expect_set(args.first())?;
755 let b = expect_set(args.get(1))?;
756 Ok(Value::Bool(a.is_subset(b)))
757 }
758
759 ("map", "merge") => {
761 let a = expect_map(args.first())?.clone();
764 let b = expect_map(args.get(1))?;
765 let mut out = a;
766 for (k, v) in b {
767 out.insert(k.clone(), v.clone());
768 }
769 Ok(Value::Map(out))
770 }
771 ("map", "is_empty") => Ok(Value::Bool(expect_map(args.first())?.is_empty())),
772
773 ("deque", "new") => Ok(Value::Deque(std::collections::VecDeque::new())),
775 ("deque", "size") => Ok(Value::Int(expect_deque(args.first())?.len() as i64)),
776 ("deque", "is_empty") => Ok(Value::Bool(expect_deque(args.first())?.is_empty())),
777 ("deque", "push_back") => {
778 let mut d = expect_deque(args.first())?.clone();
779 let x = args.get(1).ok_or("deque.push_back: missing value")?.clone();
780 d.push_back(x);
781 Ok(Value::Deque(d))
782 }
783 ("deque", "push_front") => {
784 let mut d = expect_deque(args.first())?.clone();
785 let x = args.get(1).ok_or("deque.push_front: missing value")?.clone();
786 d.push_front(x);
787 Ok(Value::Deque(d))
788 }
789 ("deque", "pop_back") => {
790 let mut d = expect_deque(args.first())?.clone();
791 match d.pop_back() {
792 Some(x) => Ok(Value::Variant {
793 name: "Some".into(),
794 args: vec![Value::Tuple(vec![x, Value::Deque(d)])],
795 }),
796 None => Ok(Value::Variant { name: "None".into(), args: vec![] }),
797 }
798 }
799 ("deque", "pop_front") => {
800 let mut d = expect_deque(args.first())?.clone();
801 match d.pop_front() {
802 Some(x) => Ok(Value::Variant {
803 name: "Some".into(),
804 args: vec![Value::Tuple(vec![x, Value::Deque(d)])],
805 }),
806 None => Ok(Value::Variant { name: "None".into(), args: vec![] }),
807 }
808 }
809 ("deque", "peek_back") => {
810 let d = expect_deque(args.first())?;
811 match d.back() {
812 Some(x) => Ok(Value::Variant {
813 name: "Some".into(),
814 args: vec![x.clone()],
815 }),
816 None => Ok(Value::Variant { name: "None".into(), args: vec![] }),
817 }
818 }
819 ("deque", "peek_front") => {
820 let d = expect_deque(args.first())?;
821 match d.front() {
822 Some(x) => Ok(Value::Variant {
823 name: "Some".into(),
824 args: vec![x.clone()],
825 }),
826 None => Ok(Value::Variant { name: "None".into(), args: vec![] }),
827 }
828 }
829 ("deque", "from_list") => {
830 let xs = expect_list(args.first())?;
831 Ok(Value::Deque(xs.iter().cloned().collect()))
832 }
833 ("deque", "to_list") => {
834 let d = expect_deque(args.first())?;
835 Ok(Value::List(d.iter().cloned().collect()))
836 }
837
838 ("crypto", "sha256") => {
841 use sha2::{Digest, Sha256};
842 let data = expect_bytes(args.first())?;
843 let mut h = Sha256::new();
844 h.update(data);
845 Ok(Value::Bytes(h.finalize().to_vec()))
846 }
847 ("crypto", "sha512") => {
848 use sha2::{Digest, Sha512};
849 let data = expect_bytes(args.first())?;
850 let mut h = Sha512::new();
851 h.update(data);
852 Ok(Value::Bytes(h.finalize().to_vec()))
853 }
854 ("crypto", "md5") => {
855 use md5::{Digest, Md5};
856 let data = expect_bytes(args.first())?;
857 let mut h = Md5::new();
858 h.update(data);
859 Ok(Value::Bytes(h.finalize().to_vec()))
860 }
861 ("crypto", "hmac_sha256") => {
862 use hmac::{Hmac, KeyInit, Mac};
863 type HmacSha256 = Hmac<sha2::Sha256>;
864 let key = expect_bytes(args.first())?;
865 let data = expect_bytes(args.get(1))?;
866 let mut mac = HmacSha256::new_from_slice(key)
867 .map_err(|e| format!("hmac_sha256 key: {e}"))?;
868 mac.update(data);
869 Ok(Value::Bytes(mac.finalize().into_bytes().to_vec()))
870 }
871 ("crypto", "hmac_sha512") => {
872 use hmac::{Hmac, KeyInit, Mac};
873 type HmacSha512 = Hmac<sha2::Sha512>;
874 let key = expect_bytes(args.first())?;
875 let data = expect_bytes(args.get(1))?;
876 let mut mac = HmacSha512::new_from_slice(key)
877 .map_err(|e| format!("hmac_sha512 key: {e}"))?;
878 mac.update(data);
879 Ok(Value::Bytes(mac.finalize().into_bytes().to_vec()))
880 }
881 ("crypto", "base64_encode") => {
882 use base64::{Engine, engine::general_purpose::STANDARD};
883 let data = expect_bytes(args.first())?;
884 Ok(Value::Str(STANDARD.encode(data)))
885 }
886 ("crypto", "base64_decode") => {
887 use base64::{Engine, engine::general_purpose::STANDARD};
888 let s = expect_str(args.first())?;
889 match STANDARD.decode(s) {
890 Ok(b) => Ok(ok_v(Value::Bytes(b))),
891 Err(e) => Ok(err_v(Value::Str(format!("base64: {e}")))),
892 }
893 }
894 ("crypto", "hex_encode") => {
895 let data = expect_bytes(args.first())?;
896 Ok(Value::Str(hex::encode(data)))
897 }
898 ("crypto", "hex_decode") => {
899 let s = expect_str(args.first())?;
900 match hex::decode(s) {
901 Ok(b) => Ok(ok_v(Value::Bytes(b))),
902 Err(e) => Ok(err_v(Value::Str(format!("hex: {e}")))),
903 }
904 }
905 ("crypto", "constant_time_eq") => {
906 use subtle::ConstantTimeEq;
907 let a = expect_bytes(args.first())?;
908 let b = expect_bytes(args.get(1))?;
909 let eq = if a.len() == b.len() {
914 a.ct_eq(b).into()
915 } else {
916 false
917 };
918 Ok(Value::Bool(eq))
919 }
920
921 ("regex", "compile") => {
925 let pat = expect_str(args.first())?;
926 match get_or_compile_regex(&pat) {
927 Ok(_) => Ok(ok_v(Value::Str(pat))),
928 Err(e) => Ok(err_v(Value::Str(e))),
929 }
930 }
931 ("regex", "is_match") => {
932 let pat = expect_str(args.first())?;
933 let s = expect_str(args.get(1))?;
934 let re = get_or_compile_regex(&pat).map_err(|e| format!("regex.is_match: {e}"))?;
935 Ok(Value::Bool(re.is_match(&s)))
936 }
937 ("regex", "find") => {
938 let pat = expect_str(args.first())?;
939 let s = expect_str(args.get(1))?;
940 let re = get_or_compile_regex(&pat).map_err(|e| format!("regex.find: {e}"))?;
941 match re.captures(&s) {
942 Some(caps) => Ok(Value::Variant {
943 name: "Some".into(),
944 args: vec![match_value(&caps)],
945 }),
946 None => Ok(Value::Variant { name: "None".into(), args: vec![] }),
947 }
948 }
949 ("regex", "find_all") => {
950 let pat = expect_str(args.first())?;
951 let s = expect_str(args.get(1))?;
952 let re = get_or_compile_regex(&pat).map_err(|e| format!("regex.find_all: {e}"))?;
953 let items: Vec<Value> = re.captures_iter(&s).map(|caps| match_value(&caps)).collect();
954 Ok(Value::List(items))
955 }
956 ("regex", "replace") => {
957 let pat = expect_str(args.first())?;
958 let s = expect_str(args.get(1))?;
959 let rep = expect_str(args.get(2))?;
960 let re = get_or_compile_regex(&pat).map_err(|e| format!("regex.replace: {e}"))?;
961 Ok(Value::Str(re.replace(&s, rep.as_str()).into_owned()))
962 }
963 ("regex", "replace_all") => {
964 let pat = expect_str(args.first())?;
965 let s = expect_str(args.get(1))?;
966 let rep = expect_str(args.get(2))?;
967 let re = get_or_compile_regex(&pat).map_err(|e| format!("regex.replace_all: {e}"))?;
968 Ok(Value::Str(re.replace_all(&s, rep.as_str()).into_owned()))
969 }
970 ("datetime", "parse_iso") => {
973 let s = expect_str(args.first())?;
974 match chrono::DateTime::parse_from_rfc3339(&s) {
975 Ok(dt) => Ok(ok_v(Value::Int(instant_from_chrono(dt)))),
976 Err(e) => Ok(err_v(Value::Str(format!("parse_iso: {e}")))),
977 }
978 }
979 ("datetime", "format_iso") => {
980 let n = expect_int(args.first())?;
981 Ok(Value::Str(format_iso(n)))
982 }
983 ("datetime", "parse") => {
984 let s = expect_str(args.first())?;
985 let fmt = expect_str(args.get(1))?;
986 match chrono::NaiveDateTime::parse_from_str(&s, &fmt) {
987 Ok(naive) => {
988 use chrono::TimeZone;
989 match chrono::Utc.from_local_datetime(&naive).single() {
990 Some(dt) => Ok(ok_v(Value::Int(instant_from_chrono(dt)))),
991 None => Ok(err_v(Value::Str("parse: ambiguous local time".into()))),
992 }
993 }
994 Err(e) => Ok(err_v(Value::Str(format!("parse: {e}")))),
995 }
996 }
997 ("datetime", "format") => {
998 let n = expect_int(args.first())?;
999 let fmt = expect_str(args.get(1))?;
1000 let dt = chrono_from_instant(n);
1001 Ok(Value::Str(dt.format(&fmt).to_string()))
1002 }
1003 ("datetime", "to_components") => {
1004 let n = expect_int(args.first())?;
1005 let tz = match parse_tz_arg(args.get(1)) {
1006 Ok(t) => t,
1007 Err(e) => return Ok(err_v(Value::Str(e))),
1008 };
1009 match resolve_tz_to_components(n, &tz) {
1010 Ok(rec) => Ok(ok_v(rec)),
1011 Err(e) => Ok(err_v(Value::Str(e))),
1012 }
1013 }
1014 ("datetime", "from_components") => {
1015 let rec = match args.first() {
1016 Some(Value::Record(r)) => r.clone(),
1017 _ => return Err("from_components: expected DateTime record".into()),
1018 };
1019 match instant_from_components(&rec) {
1020 Ok(n) => Ok(ok_v(Value::Int(n))),
1021 Err(e) => Ok(err_v(Value::Str(e))),
1022 }
1023 }
1024 ("datetime", "add") => {
1025 let a = expect_int(args.first())?;
1026 let d = expect_int(args.get(1))?;
1027 Ok(Value::Int(a.saturating_add(d)))
1028 }
1029 ("datetime", "diff") => {
1030 let a = expect_int(args.first())?;
1031 let b = expect_int(args.get(1))?;
1032 Ok(Value::Int(a.saturating_sub(b)))
1033 }
1034 ("datetime", "duration_seconds") => {
1035 let s = expect_float(args.first())?;
1036 let nanos = (s * 1_000_000_000.0) as i64;
1037 Ok(Value::Int(nanos))
1038 }
1039 ("datetime", "duration_minutes") => {
1040 let m = expect_int(args.first())?;
1041 Ok(Value::Int(m.saturating_mul(60_000_000_000)))
1042 }
1043 ("datetime", "duration_days") => {
1044 let d = expect_int(args.first())?;
1045 Ok(Value::Int(d.saturating_mul(86_400_000_000_000)))
1046 }
1047
1048 ("regex", "split") => {
1049 let pat = expect_str(args.first())?;
1050 let s = expect_str(args.get(1))?;
1051 let re = get_or_compile_regex(&pat).map_err(|e| format!("regex.split: {e}"))?;
1052 let parts: Vec<Value> = re.split(&s).map(|p| Value::Str(p.to_string())).collect();
1053 Ok(Value::List(parts))
1054 }
1055
1056 ("http", "with_header") => {
1059 let req = expect_record_pure(args.first())?.clone();
1060 let k = expect_str(args.get(1))?;
1061 let v = expect_str(args.get(2))?;
1062 Ok(Value::Record(http_set_header(req, &k, &v)))
1063 }
1064 ("http", "with_auth") => {
1065 let req = expect_record_pure(args.first())?.clone();
1066 let scheme = expect_str(args.get(1))?;
1067 let token = expect_str(args.get(2))?;
1068 let value = format!("{scheme} {token}");
1069 Ok(Value::Record(http_set_header(req, "Authorization", &value)))
1070 }
1071 ("http", "with_query") => {
1072 let req = expect_record_pure(args.first())?.clone();
1073 let params = match args.get(1) {
1074 Some(Value::Map(m)) => m.clone(),
1075 Some(other) => return Err(format!(
1076 "http.with_query: params must be Map[Str, Str], got {other:?}")),
1077 None => return Err("http.with_query: missing params argument".into()),
1078 };
1079 Ok(Value::Record(http_append_query(req, ¶ms)))
1080 }
1081 ("http", "with_timeout_ms") => {
1082 let req = expect_record_pure(args.first())?.clone();
1083 let ms = expect_int(args.get(1))?;
1084 let mut out = req;
1085 out.insert("timeout_ms".into(), Value::Variant {
1086 name: "Some".into(),
1087 args: vec![Value::Int(ms)],
1088 });
1089 Ok(Value::Record(out))
1090 }
1091 ("http", "json_body") => {
1092 let resp = expect_record_pure(args.first())?;
1093 let body = match resp.get("body") {
1094 Some(Value::Bytes(b)) => b.clone(),
1095 _ => return Err("http.json_body: HttpResponse.body must be Bytes".into()),
1096 };
1097 let s = match std::str::from_utf8(&body) {
1098 Ok(s) => s,
1099 Err(e) => return Ok(http_decode_err_pure(format!("body not UTF-8: {e}"))),
1100 };
1101 match serde_json::from_str::<serde_json::Value>(s) {
1102 Ok(j) => Ok(ok_v(Value::from_json(&j))),
1103 Err(e) => Ok(http_decode_err_pure(format!("json parse: {e}"))),
1104 }
1105 }
1106 ("http", "text_body") => {
1107 let resp = expect_record_pure(args.first())?;
1108 let body = match resp.get("body") {
1109 Some(Value::Bytes(b)) => b.clone(),
1110 _ => return Err("http.text_body: HttpResponse.body must be Bytes".into()),
1111 };
1112 match String::from_utf8(body) {
1113 Ok(s) => Ok(ok_v(Value::Str(s))),
1114 Err(e) => Ok(http_decode_err_pure(format!("body not UTF-8: {e}"))),
1115 }
1116 }
1117
1118 _ => Err(format!("unknown pure builtin: {kind}.{op}")),
1119 }
1120}
1121
1122fn regex_cache() -> &'static Mutex<HashMap<String, regex::Regex>> {
1127 static CACHE: OnceLock<Mutex<HashMap<String, regex::Regex>>> = OnceLock::new();
1128 CACHE.get_or_init(|| Mutex::new(HashMap::new()))
1129}
1130
1131fn get_or_compile_regex(pattern: &str) -> Result<regex::Regex, String> {
1132 let cache = regex_cache();
1133 {
1134 let guard = cache.lock().unwrap();
1135 if let Some(re) = guard.get(pattern) {
1136 return Ok(re.clone());
1137 }
1138 }
1139 let re = regex::Regex::new(pattern).map_err(|e| format!("invalid regex: {e}"))?;
1140 let mut guard = cache.lock().unwrap();
1141 guard.insert(pattern.to_string(), re.clone());
1142 Ok(re)
1143}
1144
1145fn match_value(caps: ®ex::Captures) -> Value {
1149 let m0 = caps.get(0).expect("regex match always has group 0");
1150 let mut rec = indexmap::IndexMap::new();
1151 rec.insert("text".into(), Value::Str(m0.as_str().to_string()));
1152 rec.insert("start".into(), Value::Int(m0.start() as i64));
1153 rec.insert("end".into(), Value::Int(m0.end() as i64));
1154 let groups: Vec<Value> = (1..caps.len())
1155 .map(|i| {
1156 Value::Str(
1157 caps.get(i)
1158 .map(|m| m.as_str().to_string())
1159 .unwrap_or_default(),
1160 )
1161 })
1162 .collect();
1163 rec.insert("groups".into(), Value::List(groups));
1164 Value::Record(rec)
1165}
1166
1167fn expect_map(v: Option<&Value>) -> Result<&BTreeMap<MapKey, Value>, String> {
1168 match v {
1169 Some(Value::Map(m)) => Ok(m),
1170 other => Err(format!("expected Map, got {other:?}")),
1171 }
1172}
1173
1174fn expect_set(v: Option<&Value>) -> Result<&BTreeSet<MapKey>, String> {
1175 match v {
1176 Some(Value::Set(s)) => Ok(s),
1177 other => Err(format!("expected Set, got {other:?}")),
1178 }
1179}
1180
1181fn unpack_matrix(v: &Value) -> Result<(usize, usize, Vec<f64>), String> {
1185 if let Value::F64Array { rows, cols, data } = v {
1186 return Ok((*rows as usize, *cols as usize, data.clone()));
1187 }
1188 let rec = match v {
1189 Value::Record(r) => r,
1190 other => return Err(format!("expected matrix, got {other:?}")),
1191 };
1192 let rows = match rec.get("rows") {
1193 Some(Value::Int(n)) => *n as usize,
1194 _ => return Err("matrix: missing/invalid `rows`".into()),
1195 };
1196 let cols = match rec.get("cols") {
1197 Some(Value::Int(n)) => *n as usize,
1198 _ => return Err("matrix: missing/invalid `cols`".into()),
1199 };
1200 let data = match rec.get("data") {
1201 Some(Value::List(items)) => {
1202 let mut out = Vec::with_capacity(items.len());
1203 for it in items {
1204 out.push(match it {
1205 Value::Float(f) => *f,
1206 Value::Int(n) => *n as f64,
1207 other => return Err(format!("matrix data: not numeric, got {other:?}")),
1208 });
1209 }
1210 out
1211 }
1212 _ => return Err("matrix: missing/invalid `data`".into()),
1213 };
1214 if data.len() != rows * cols {
1215 return Err(format!("matrix: data len {} != {rows}*{cols}", data.len()));
1216 }
1217 Ok((rows, cols, data))
1218}
1219
1220fn expect_bytes(v: Option<&Value>) -> Result<&Vec<u8>, String> {
1221 match v {
1222 Some(Value::Bytes(b)) => Ok(b),
1223 Some(other) => Err(format!("expected Bytes, got {other:?}")),
1224 None => Err("missing argument".into()),
1225 }
1226}
1227
1228fn first_arg(args: &[Value]) -> Result<&Value, String> {
1229 args.first().ok_or_else(|| "missing argument".into())
1230}
1231
1232fn tuple_index(v: &Value, i: usize) -> Result<Value, String> {
1233 match v {
1234 Value::Tuple(items) => items.get(i).cloned()
1235 .ok_or_else(|| format!("tuple index {i} out of range (len={})", items.len())),
1236 other => Err(format!("expected Tuple, got {other:?}")),
1237 }
1238}
1239
1240fn expect_str(v: Option<&Value>) -> Result<String, String> {
1241 match v {
1242 Some(Value::Str(s)) => Ok(s.clone()),
1243 Some(other) => Err(format!("expected Str, got {other:?}")),
1244 None => Err("missing argument".into()),
1245 }
1246}
1247
1248fn expect_int(v: Option<&Value>) -> Result<i64, String> {
1249 match v {
1250 Some(Value::Int(n)) => Ok(*n),
1251 Some(other) => Err(format!("expected Int, got {other:?}")),
1252 None => Err("missing argument".into()),
1253 }
1254}
1255
1256fn expect_float(v: Option<&Value>) -> Result<f64, String> {
1257 match v {
1258 Some(Value::Float(f)) => Ok(*f),
1259 Some(other) => Err(format!("expected Float, got {other:?}")),
1260 None => Err("missing argument".into()),
1261 }
1262}
1263
1264fn expect_list(v: Option<&Value>) -> Result<&Vec<Value>, String> {
1265 match v {
1266 Some(Value::List(xs)) => Ok(xs),
1267 Some(other) => Err(format!("expected List, got {other:?}")),
1268 None => Err("missing argument".into()),
1269 }
1270}
1271
1272fn expect_deque(v: Option<&Value>) -> Result<&std::collections::VecDeque<Value>, String> {
1273 match v {
1274 Some(Value::Deque(d)) => Ok(d),
1275 Some(other) => Err(format!("expected Deque, got {other:?}")),
1276 None => Err("missing argument".into()),
1277 }
1278}
1279
1280fn some(v: Value) -> Value { Value::Variant { name: "Some".into(), args: vec![v] } }
1281fn none() -> Value { Value::Variant { name: "None".into(), args: Vec::new() } }
1282fn ok_v(v: Value) -> Value { Value::Variant { name: "Ok".into(), args: vec![v] } }
1283fn err_v(v: Value) -> Value { Value::Variant { name: "Err".into(), args: vec![v] } }
1284
1285fn expect_record_pure(v: Option<&Value>) -> Result<&indexmap::IndexMap<String, Value>, String> {
1288 match v {
1289 Some(Value::Record(r)) => Ok(r),
1290 Some(other) => Err(format!("expected Record, got {other:?}")),
1291 None => Err("missing Record argument".into()),
1292 }
1293}
1294
1295fn http_decode_err_pure(msg: String) -> Value {
1296 let inner = Value::Variant {
1297 name: "DecodeError".into(),
1298 args: vec![Value::Str(msg)],
1299 };
1300 err_v(inner)
1301}
1302
1303fn http_set_header(
1308 mut req: indexmap::IndexMap<String, Value>,
1309 name: &str,
1310 value: &str,
1311) -> indexmap::IndexMap<String, Value> {
1312 use lex_bytecode::MapKey;
1313 let mut headers = match req.shift_remove("headers") {
1314 Some(Value::Map(m)) => m,
1315 _ => std::collections::BTreeMap::new(),
1316 };
1317 let key = MapKey::Str(name.to_lowercase());
1318 let lowered = name.to_lowercase();
1321 headers.retain(|k, _| match k {
1322 MapKey::Str(s) => s.to_lowercase() != lowered,
1323 _ => true,
1324 });
1325 headers.insert(key, Value::Str(value.to_string()));
1326 req.insert("headers".into(), Value::Map(headers));
1327 req
1328}
1329
1330fn http_append_query(
1336 mut req: indexmap::IndexMap<String, Value>,
1337 params: &std::collections::BTreeMap<lex_bytecode::MapKey, Value>,
1338) -> indexmap::IndexMap<String, Value> {
1339 use lex_bytecode::MapKey;
1340 let url = match req.get("url") {
1341 Some(Value::Str(s)) => s.clone(),
1342 _ => return req,
1343 };
1344 let mut pieces = Vec::new();
1345 for (k, v) in params {
1346 let kk = match k { MapKey::Str(s) => s.clone(), _ => continue };
1347 let vv = match v { Value::Str(s) => s.clone(), _ => continue };
1348 pieces.push(format!("{}={}", url_encode(&kk), url_encode(&vv)));
1349 }
1350 if pieces.is_empty() { return req; }
1351 let sep = if url.contains('?') { '&' } else { '?' };
1352 let new_url = format!("{url}{sep}{}", pieces.join("&"));
1353 req.insert("url".into(), Value::Str(new_url));
1354 req
1355}
1356
1357fn url_encode(s: &str) -> String {
1362 let mut out = String::with_capacity(s.len());
1363 for b in s.bytes() {
1364 match b {
1365 b'A'..=b'Z' | b'a'..=b'z' | b'0'..=b'9' | b'-' | b'_' | b'.' | b'~' => {
1366 out.push(b as char);
1367 }
1368 _ => out.push_str(&format!("%{:02X}", b)),
1369 }
1370 }
1371 out
1372}
1373
1374fn value_to_json(v: &Value) -> serde_json::Value { v.to_json() }
1375
1376fn unwrap_toml_datetime_markers(v: &mut serde_json::Value) {
1384 use serde_json::Value as J;
1385 match v {
1386 J::Object(map) => {
1387 if map.len() == 1 {
1391 if let Some(J::String(s)) = map.get("$__toml_private_datetime") {
1392 let s = s.clone();
1393 *v = J::String(s);
1394 return;
1395 }
1396 }
1397 for (_, child) in map.iter_mut() {
1398 unwrap_toml_datetime_markers(child);
1399 }
1400 }
1401 J::Array(items) => {
1402 for item in items.iter_mut() {
1403 unwrap_toml_datetime_markers(item);
1404 }
1405 }
1406 _ => {}
1407 }
1408}
1409
1410fn json_to_value(v: &serde_json::Value) -> Value { Value::from_json(v) }
1411
1412fn required_field_names(arg: Option<&Value>) -> Result<Vec<String>, String> {
1417 let list = expect_list(arg)?;
1418 let mut out = Vec::with_capacity(list.len());
1419 for v in list {
1420 match v {
1421 Value::Str(s) => out.push(s.clone()),
1422 other => return Err(format!(
1423 "parse_strict: required-fields list must contain Str, got {other:?}"
1424 )),
1425 }
1426 }
1427 Ok(out)
1428}
1429
1430fn check_required_fields(
1441 value: &serde_json::Value,
1442 required: &[String],
1443) -> Result<(), String> {
1444 if required.is_empty() {
1445 return Ok(());
1446 }
1447 let obj = match value {
1448 serde_json::Value::Object(o) => o,
1449 _ => return Err(format!(
1450 "parse_strict: expected top-level object with fields {:?}, got {value}",
1451 required
1452 )),
1453 };
1454 let missing: Vec<&str> = required.iter()
1455 .filter(|n| !obj.contains_key(n.as_str()))
1456 .map(String::as_str)
1457 .collect();
1458 if missing.is_empty() {
1459 Ok(())
1460 } else {
1461 Err(format!("missing required field(s): {}", missing.join(", ")))
1462 }
1463}
1464
1465fn parse_dotenv(src: &str) -> Result<indexmap::IndexMap<String, String>, String> {
1476 let mut out = indexmap::IndexMap::new();
1477 for (idx, raw) in src.lines().enumerate() {
1478 let line = raw.trim();
1479 if line.is_empty() || line.starts_with('#') {
1480 continue;
1481 }
1482 let after_export = line.strip_prefix("export ").unwrap_or(line);
1485 let (k, v) = match after_export.split_once('=') {
1486 Some(kv) => kv,
1487 None => return Err(format!("dotenv.parse line {}: missing `=`", idx + 1)),
1488 };
1489 let key = k.trim();
1490 if key.is_empty() {
1491 return Err(format!("dotenv.parse line {}: empty key", idx + 1));
1492 }
1493 let v_trim = v.trim();
1494 let value = if let Some(q) = v_trim.strip_prefix('"').and_then(|s| s.strip_suffix('"')) {
1495 q.to_string()
1496 } else if let Some(q) = v_trim.strip_prefix('\'').and_then(|s| s.strip_suffix('\'')) {
1497 q.to_string()
1498 } else {
1499 v_trim.to_string()
1500 };
1501 out.insert(key.to_string(), value);
1502 }
1503 Ok(out)
1504}
1505
1506fn instant_from_chrono<Tz: chrono::TimeZone>(dt: chrono::DateTime<Tz>) -> i64 {
1512 dt.timestamp_nanos_opt().unwrap_or(i64::MAX)
1513}
1514
1515fn chrono_from_instant(n: i64) -> chrono::DateTime<chrono::Utc> {
1516 let secs = n.div_euclid(1_000_000_000);
1517 let nanos = n.rem_euclid(1_000_000_000) as u32;
1518 use chrono::TimeZone;
1519 chrono::Utc
1520 .timestamp_opt(secs, nanos)
1521 .single()
1522 .unwrap_or_else(chrono::Utc::now)
1523}
1524
1525fn format_iso(n: i64) -> String {
1526 chrono_from_instant(n).to_rfc3339()
1527}
1528
1529enum TzArg {
1532 Utc,
1533 Local,
1534 Offset(i32),
1536 Iana(String),
1538}
1539
1540fn parse_tz_arg(v: Option<&Value>) -> Result<TzArg, String> {
1541 match v {
1542 Some(Value::Variant { name, args }) => match (name.as_str(), args.as_slice()) {
1543 ("Utc", []) => Ok(TzArg::Utc),
1544 ("Local", []) => Ok(TzArg::Local),
1545 ("Offset", [Value::Int(m)]) => {
1546 let m = i32::try_from(*m).map_err(|_| {
1547 format!("Tz::Offset: minutes out of range: {m}")
1548 })?;
1549 Ok(TzArg::Offset(m))
1550 }
1551 ("Iana", [Value::Str(s)]) => Ok(TzArg::Iana(s.clone())),
1552 (other, _) => Err(format!(
1553 "expected Tz variant (Utc | Local | Offset(Int) | Iana(Str)), got `{other}` with {} arg(s)",
1554 args.len()
1555 )),
1556 },
1557 Some(other) => Err(format!("expected Tz variant, got {other:?}")),
1558 None => Err("missing Tz argument".into()),
1559 }
1560}
1561
1562fn resolve_tz_to_components(n: i64, tz: &TzArg) -> Result<Value, String> {
1563 use chrono::{TimeZone, Datelike, Timelike, Offset};
1564 let utc_dt = chrono_from_instant(n);
1565 let (y, m, d, hh, mm, ss, ns, off_min) = match tz {
1566 TzArg::Utc => {
1567 let d = utc_dt;
1568 (d.year(), d.month() as i32, d.day() as i32,
1569 d.hour() as i32, d.minute() as i32, d.second() as i32,
1570 d.nanosecond() as i32, 0)
1571 }
1572 TzArg::Local => {
1573 let d = utc_dt.with_timezone(&chrono::Local);
1574 let off = d.offset().fix().local_minus_utc() / 60;
1575 (d.year(), d.month() as i32, d.day() as i32,
1576 d.hour() as i32, d.minute() as i32, d.second() as i32,
1577 d.nanosecond() as i32, off)
1578 }
1579 TzArg::Offset(off_min) => {
1580 let off_secs = off_min.saturating_mul(60);
1581 let fixed = chrono::FixedOffset::east_opt(off_secs)
1582 .ok_or("to_components: offset out of range")?;
1583 let d = utc_dt.with_timezone(&fixed);
1584 (d.year(), d.month() as i32, d.day() as i32,
1585 d.hour() as i32, d.minute() as i32, d.second() as i32,
1586 d.nanosecond() as i32, *off_min)
1587 }
1588 TzArg::Iana(name) => {
1589 let tz: chrono_tz::Tz = name.parse()
1590 .map_err(|e| format!("to_components: unknown timezone `{name}`: {e}"))?;
1591 let d = utc_dt.with_timezone(&tz);
1592 let off = d.offset().fix().local_minus_utc() / 60;
1593 (d.year(), d.month() as i32, d.day() as i32,
1594 d.hour() as i32, d.minute() as i32, d.second() as i32,
1595 d.nanosecond() as i32, off)
1596 }
1597 };
1598 let mut rec = indexmap::IndexMap::new();
1599 rec.insert("year".into(), Value::Int(y as i64));
1600 rec.insert("month".into(), Value::Int(m as i64));
1601 rec.insert("day".into(), Value::Int(d as i64));
1602 rec.insert("hour".into(), Value::Int(hh as i64));
1603 rec.insert("minute".into(), Value::Int(mm as i64));
1604 rec.insert("second".into(), Value::Int(ss as i64));
1605 rec.insert("nano".into(), Value::Int(ns as i64));
1606 rec.insert("tz_offset_minutes".into(), Value::Int(off_min as i64));
1607 let _ = chrono::Utc.timestamp_opt(0, 0); Ok(Value::Record(rec))
1609}
1610
1611
1612fn instant_from_components(rec: &indexmap::IndexMap<String, Value>) -> Result<i64, String> {
1613 use chrono::TimeZone;
1614 fn get_int(rec: &indexmap::IndexMap<String, Value>, k: &str) -> Result<i64, String> {
1615 match rec.get(k) {
1616 Some(Value::Int(n)) => Ok(*n),
1617 other => Err(format!("from_components: missing or non-int field `{k}`: {other:?}")),
1618 }
1619 }
1620 let y = get_int(rec, "year")? as i32;
1621 let m = get_int(rec, "month")? as u32;
1622 let d = get_int(rec, "day")? as u32;
1623 let hh = get_int(rec, "hour")? as u32;
1624 let mm = get_int(rec, "minute")? as u32;
1625 let ss = get_int(rec, "second")? as u32;
1626 let ns = get_int(rec, "nano")? as u32;
1627 let off_min = get_int(rec, "tz_offset_minutes")? as i32;
1628 let off = chrono::FixedOffset::east_opt(off_min * 60)
1629 .ok_or("from_components: offset out of range")?;
1630 let dt = off
1631 .with_ymd_and_hms(y, m, d, hh, mm, ss)
1632 .single()
1633 .ok_or("from_components: invalid or ambiguous date/time")?;
1634 let dt = dt + chrono::Duration::nanoseconds(ns as i64);
1635 Ok(instant_from_chrono(dt))
1636}