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" | "random" | "parser"
35 | "cli")
36}
37
38fn dispatch(kind: &str, op: &str, args: &[Value]) -> Result<Value, String> {
39 match (kind, op) {
40 ("str", "is_empty") => Ok(Value::Bool(expect_str(args.first())?.is_empty())),
42 ("str", "len") => Ok(Value::Int(expect_str(args.first())?.len() as i64)),
43 ("str", "concat") => {
44 let a = expect_str(args.first())?;
45 let b = expect_str(args.get(1))?;
46 Ok(Value::Str(format!("{a}{b}")))
47 }
48 ("str", "to_int") => {
49 let s = expect_str(args.first())?;
50 match s.parse::<i64>() {
51 Ok(n) => Ok(some(Value::Int(n))),
52 Err(_) => Ok(none()),
53 }
54 }
55 ("str", "split") => {
56 let s = expect_str(args.first())?;
57 let sep = expect_str(args.get(1))?;
58 let items: Vec<Value> = if sep.is_empty() {
59 s.chars().map(|c| Value::Str(c.to_string())).collect()
60 } else {
61 s.split(sep.as_str()).map(|p| Value::Str(p.to_string())).collect()
62 };
63 Ok(Value::List(items))
64 }
65 ("str", "join") => {
66 let parts = expect_list(args.first())?;
67 let sep = expect_str(args.get(1))?;
68 let mut out = String::new();
69 for (i, p) in parts.iter().enumerate() {
70 if i > 0 { out.push_str(&sep); }
71 match p {
72 Value::Str(s) => out.push_str(s),
73 other => return Err(format!("str.join element must be Str, got {other:?}")),
74 }
75 }
76 Ok(Value::Str(out))
77 }
78 ("str", "starts_with") => {
79 let s = expect_str(args.first())?;
80 let prefix = expect_str(args.get(1))?;
81 Ok(Value::Bool(s.starts_with(prefix.as_str())))
82 }
83 ("str", "ends_with") => {
84 let s = expect_str(args.first())?;
85 let suffix = expect_str(args.get(1))?;
86 Ok(Value::Bool(s.ends_with(suffix.as_str())))
87 }
88 ("str", "contains") => {
89 let s = expect_str(args.first())?;
90 let needle = expect_str(args.get(1))?;
91 Ok(Value::Bool(s.contains(needle.as_str())))
92 }
93 ("str", "replace") => {
94 let s = expect_str(args.first())?;
95 let from = expect_str(args.get(1))?;
96 let to = expect_str(args.get(2))?;
97 Ok(Value::Str(s.replace(from.as_str(), to.as_str())))
98 }
99 ("str", "trim") => Ok(Value::Str(expect_str(args.first())?.trim().to_string())),
100 ("str", "to_upper") => Ok(Value::Str(expect_str(args.first())?.to_uppercase())),
101 ("str", "to_lower") => Ok(Value::Str(expect_str(args.first())?.to_lowercase())),
102 ("str", "strip_prefix") => {
103 let s = expect_str(args.first())?;
104 let prefix = expect_str(args.get(1))?;
105 Ok(match s.strip_prefix(prefix.as_str()) {
106 Some(rest) => some(Value::Str(rest.to_string())),
107 None => none(),
108 })
109 }
110 ("str", "strip_suffix") => {
111 let s = expect_str(args.first())?;
112 let suffix = expect_str(args.get(1))?;
113 Ok(match s.strip_suffix(suffix.as_str()) {
114 Some(rest) => some(Value::Str(rest.to_string())),
115 None => none(),
116 })
117 }
118 ("str", "slice") => {
119 let s = expect_str(args.first())?;
129 let lo_i = expect_int(args.get(1))?;
130 let hi_i = expect_int(args.get(2))?;
131 let lo = (lo_i.max(0) as usize).min(s.len());
132 let hi = (hi_i.max(0) as usize).min(s.len());
133 if lo > hi {
134 return Err(format!(
135 "str.slice: reversed range [{lo}..{hi}] (after clamping to len {})",
136 s.len()));
137 }
138 if !s.is_char_boundary(lo) || !s.is_char_boundary(hi) {
139 return Err(format!("str.slice: [{lo}..{hi}] not on char boundaries"));
140 }
141 Ok(Value::Str(s[lo..hi].to_string()))
142 }
143
144 ("int", "to_str") => Ok(Value::Str(expect_int(args.first())?.to_string())),
146 ("int", "to_float") => Ok(Value::Float(expect_int(args.first())? as f64)),
147 ("float", "to_int") => Ok(Value::Int(expect_float(args.first())? as i64)),
148 ("float", "to_str") => Ok(Value::Str(expect_float(args.first())?.to_string())),
149 ("str", "to_float") => {
150 let s = expect_str(args.first())?;
151 match s.parse::<f64>() {
152 Ok(f) => Ok(some(Value::Float(f))),
153 Err(_) => Ok(none()),
154 }
155 }
156
157 ("list", "len") => Ok(Value::Int(expect_list(args.first())?.len() as i64)),
159 ("list", "is_empty") => Ok(Value::Bool(expect_list(args.first())?.is_empty())),
160 ("list", "head") => {
161 let xs = expect_list(args.first())?;
162 match xs.first() {
163 Some(v) => Ok(some(v.clone())),
164 None => Ok(none()),
165 }
166 }
167 ("list", "tail") => {
168 let xs = expect_list(args.first())?;
169 if xs.is_empty() { Ok(Value::List(Vec::new())) }
170 else { Ok(Value::List(xs[1..].to_vec())) }
171 }
172 ("list", "range") => {
173 let lo = expect_int(args.first())?;
174 let hi = expect_int(args.get(1))?;
175 Ok(Value::List((lo..hi).map(Value::Int).collect()))
176 }
177 ("list", "concat") => {
178 let mut out = expect_list(args.first())?.clone();
179 out.extend(expect_list(args.get(1))?.iter().cloned());
180 Ok(Value::List(out))
181 }
182
183 ("tuple", "fst") => tuple_index(first_arg(args)?, 0),
188 ("tuple", "snd") => tuple_index(first_arg(args)?, 1),
189 ("tuple", "third") => tuple_index(first_arg(args)?, 2),
190 ("tuple", "len") => match first_arg(args)? {
191 Value::Tuple(items) => Ok(Value::Int(items.len() as i64)),
192 other => Err(format!("tuple.len: expected Tuple, got {other:?}")),
193 },
194
195 ("option", "unwrap_or") => {
197 let opt = first_arg(args)?;
198 let default = args.get(1).cloned().unwrap_or(Value::Unit);
199 match opt {
200 Value::Variant { name, args } if name == "Some" && !args.is_empty() => Ok(args[0].clone()),
201 Value::Variant { name, .. } if name == "None" => Ok(default),
202 other => Err(format!("option.unwrap_or expected Option, got {other:?}")),
203 }
204 }
205 ("option", "is_some") => match first_arg(args)? {
206 Value::Variant { name, .. } => Ok(Value::Bool(name == "Some")),
207 other => Err(format!("option.is_some expected Option, got {other:?}")),
208 },
209 ("option", "is_none") => match first_arg(args)? {
210 Value::Variant { name, .. } => Ok(Value::Bool(name == "None")),
211 other => Err(format!("option.is_none expected Option, got {other:?}")),
212 },
213
214 ("result", "is_ok") => match first_arg(args)? {
216 Value::Variant { name, .. } => Ok(Value::Bool(name == "Ok")),
217 other => Err(format!("result.is_ok expected Result, got {other:?}")),
218 },
219 ("result", "is_err") => match first_arg(args)? {
220 Value::Variant { name, .. } => Ok(Value::Bool(name == "Err")),
221 other => Err(format!("result.is_err expected Result, got {other:?}")),
222 },
223 ("result", "unwrap_or") => {
224 let res = first_arg(args)?;
225 let default = args.get(1).cloned().unwrap_or(Value::Unit);
226 match res {
227 Value::Variant { name, args } if name == "Ok" && !args.is_empty() => Ok(args[0].clone()),
228 Value::Variant { name, .. } if name == "Err" => Ok(default),
229 other => Err(format!("result.unwrap_or expected Result, got {other:?}")),
230 }
231 }
232
233 ("json", "stringify") => {
235 let v = first_arg(args)?;
236 Ok(Value::Str(serde_json::to_string(&value_to_json(v)).unwrap_or_default()))
237 }
238 ("json", "parse") => {
239 let s = expect_str(args.first())?;
240 match serde_json::from_str::<serde_json::Value>(&s) {
241 Ok(v) => Ok(ok_v(json_to_value(&v))),
242 Err(e) => Ok(err_v(Value::Str(format!("{e}")))),
243 }
244 }
245 ("json", "parse_strict") => {
249 let s = expect_str(args.first())?;
250 let required = required_field_names(args.get(1))?;
251 match serde_json::from_str::<serde_json::Value>(&s) {
252 Ok(v) => match check_required_fields(&v, &required) {
253 Ok(()) => Ok(ok_v(json_to_value(&v))),
254 Err(e) => Ok(err_v(Value::Str(e))),
255 },
256 Err(e) => Ok(err_v(Value::Str(format!("{e}")))),
257 }
258 }
259
260 ("toml", "parse") => {
265 let s = expect_str(args.first())?;
266 match toml::from_str::<serde_json::Value>(&s) {
267 Ok(mut v) => {
268 unwrap_toml_datetime_markers(&mut v);
269 Ok(ok_v(json_to_value(&v)))
270 }
271 Err(e) => Ok(err_v(Value::Str(format!("{e}")))),
272 }
273 }
274 ("toml", "parse_strict") => {
279 let s = expect_str(args.first())?;
280 let required = required_field_names(args.get(1))?;
281 match toml::from_str::<serde_json::Value>(&s) {
282 Ok(mut v) => {
283 unwrap_toml_datetime_markers(&mut v);
284 match check_required_fields(&v, &required) {
285 Ok(()) => Ok(ok_v(json_to_value(&v))),
286 Err(e) => Ok(err_v(Value::Str(e))),
287 }
288 }
289 Err(e) => Ok(err_v(Value::Str(format!("{e}")))),
290 }
291 }
292 ("toml", "stringify") => {
293 let v = first_arg(args)?;
294 let json = value_to_json(v);
300 match toml::to_string(&json) {
301 Ok(s) => Ok(ok_v(Value::Str(s))),
302 Err(e) => Ok(err_v(Value::Str(format!("toml.stringify: {e}")))),
303 }
304 }
305
306 ("yaml", "parse") => {
312 let s = expect_str(args.first())?;
313 match serde_yaml::from_str::<serde_json::Value>(&s) {
314 Ok(v) => Ok(ok_v(json_to_value(&v))),
315 Err(e) => Ok(err_v(Value::Str(format!("{e}")))),
316 }
317 }
318 ("yaml", "parse_strict") => {
320 let s = expect_str(args.first())?;
321 let required = required_field_names(args.get(1))?;
322 match serde_yaml::from_str::<serde_json::Value>(&s) {
323 Ok(v) => match check_required_fields(&v, &required) {
324 Ok(()) => Ok(ok_v(json_to_value(&v))),
325 Err(e) => Ok(err_v(Value::Str(e))),
326 },
327 Err(e) => Ok(err_v(Value::Str(format!("{e}")))),
328 }
329 }
330 ("yaml", "stringify") => {
331 let v = first_arg(args)?;
332 let json = value_to_json(v);
333 match serde_yaml::to_string(&json) {
334 Ok(s) => Ok(ok_v(Value::Str(s))),
335 Err(e) => Ok(err_v(Value::Str(format!("yaml.stringify: {e}")))),
336 }
337 }
338
339 ("dotenv", "parse") => {
347 use std::collections::BTreeMap;
348 use lex_bytecode::MapKey;
349 let s = expect_str(args.first())?;
350 match parse_dotenv(&s) {
351 Ok(map) => {
352 let mut bt: BTreeMap<MapKey, Value> = BTreeMap::new();
353 for (k, v) in map {
354 bt.insert(MapKey::Str(k), Value::Str(v));
355 }
356 Ok(ok_v(Value::Map(bt)))
357 }
358 Err(e) => Ok(err_v(Value::Str(e))),
359 }
360 }
361
362 ("csv", "parse") => {
367 let s = expect_str(args.first())?;
368 let mut rdr = csv::ReaderBuilder::new()
369 .has_headers(false)
370 .flexible(true)
371 .from_reader(s.as_bytes());
372 let mut rows: Vec<Value> = Vec::new();
373 for r in rdr.records() {
374 match r {
375 Ok(rec) => {
376 let row: Vec<Value> = rec.iter()
377 .map(|f| Value::Str(f.to_string()))
378 .collect();
379 rows.push(Value::List(row));
380 }
381 Err(e) => return Ok(err_v(Value::Str(format!("csv.parse: {e}")))),
382 }
383 }
384 Ok(ok_v(Value::List(rows)))
385 }
386 ("csv", "stringify") => {
387 let v = first_arg(args)?;
392 let rows = match v {
393 Value::List(rs) => rs,
394 _ => return Ok(err_v(Value::Str("csv.stringify expects List[List[Str]]".into()))),
395 };
396 let mut out = Vec::new();
397 {
398 let mut wtr = csv::WriterBuilder::new()
399 .has_headers(false)
400 .from_writer(&mut out);
401 for row in rows {
402 let cells = match row {
403 Value::List(cs) => cs,
404 _ => return Ok(err_v(Value::Str("csv.stringify row must be List[Str]".into()))),
405 };
406 let strs: Vec<String> = cells.iter().map(|c| match c {
407 Value::Str(s) => s.clone(),
408 other => serde_json::to_string(&other.to_json())
409 .unwrap_or_else(|_| String::new()),
410 }).collect();
411 if let Err(e) = wtr.write_record(&strs) {
412 return Ok(err_v(Value::Str(format!("csv.stringify: {e}"))));
413 }
414 }
415 if let Err(e) = wtr.flush() {
416 return Ok(err_v(Value::Str(format!("csv.stringify flush: {e}"))));
417 }
418 }
419 match String::from_utf8(out) {
420 Ok(s) => Ok(ok_v(Value::Str(s))),
421 Err(e) => Ok(err_v(Value::Str(format!("csv.stringify utf8: {e}")))),
422 }
423 }
424
425 ("test", "assert_eq") => {
432 let a = first_arg(args)?;
433 let b = args.get(1).ok_or("test.assert_eq: missing second arg")?;
434 if a == b {
435 Ok(ok_v(Value::Unit))
436 } else {
437 Ok(err_v(Value::Str(format!("assert_eq: lhs {} != rhs {}",
438 value_to_json(a), value_to_json(b)))))
439 }
440 }
441 ("test", "assert_ne") => {
442 let a = first_arg(args)?;
443 let b = args.get(1).ok_or("test.assert_ne: missing second arg")?;
444 if a != b {
445 Ok(ok_v(Value::Unit))
446 } else {
447 Ok(err_v(Value::Str(format!("assert_ne: both sides are {}",
448 value_to_json(a)))))
449 }
450 }
451 ("test", "assert_true") => {
452 match first_arg(args)? {
453 Value::Bool(true) => Ok(ok_v(Value::Unit)),
454 Value::Bool(false) => Ok(err_v(Value::Str("assert_true: was false".into()))),
455 other => Err(format!("test.assert_true expects Bool, got {other:?}")),
456 }
457 }
458 ("test", "assert_false") => {
459 match first_arg(args)? {
460 Value::Bool(false) => Ok(ok_v(Value::Unit)),
461 Value::Bool(true) => Ok(err_v(Value::Str("assert_false: was true".into()))),
462 other => Err(format!("test.assert_false expects Bool, got {other:?}")),
463 }
464 }
465
466 ("bytes", "len") => {
468 let b = expect_bytes(args.first())?;
469 Ok(Value::Int(b.len() as i64))
470 }
471 ("bytes", "eq") => {
472 let a = expect_bytes(args.first())?;
473 let b = expect_bytes(args.get(1))?;
474 Ok(Value::Bool(a == b))
475 }
476 ("bytes", "from_str") => {
477 let s = expect_str(args.first())?;
478 Ok(Value::Bytes(s.into_bytes()))
479 }
480 ("bytes", "to_str") => {
481 let b = expect_bytes(args.first())?;
482 match String::from_utf8(b.to_vec()) {
483 Ok(s) => Ok(ok_v(Value::Str(s))),
484 Err(e) => Ok(err_v(Value::Str(format!("{e}")))),
485 }
486 }
487 ("bytes", "slice") => {
488 let b = expect_bytes(args.first())?;
489 let lo = expect_int(args.get(1))? as usize;
490 let hi = expect_int(args.get(2))? as usize;
491 if lo > hi || hi > b.len() {
492 return Err(format!("bytes.slice: out of range [{lo}..{hi}] of {}", b.len()));
493 }
494 Ok(Value::Bytes(b[lo..hi].to_vec()))
495 }
496 ("bytes", "is_empty") => {
497 let b = expect_bytes(args.first())?;
498 Ok(Value::Bool(b.is_empty()))
499 }
500
501 ("math", "exp") => Ok(Value::Float(expect_float(args.first())?.exp())),
508 ("math", "log") => Ok(Value::Float(expect_float(args.first())?.ln())),
509 ("math", "log2") => Ok(Value::Float(expect_float(args.first())?.log2())),
510 ("math", "log10") => Ok(Value::Float(expect_float(args.first())?.log10())),
511 ("math", "sqrt") => Ok(Value::Float(expect_float(args.first())?.sqrt())),
512 ("math", "abs") => Ok(Value::Float(expect_float(args.first())?.abs())),
513 ("math", "sin") => Ok(Value::Float(expect_float(args.first())?.sin())),
514 ("math", "cos") => Ok(Value::Float(expect_float(args.first())?.cos())),
515 ("math", "tan") => Ok(Value::Float(expect_float(args.first())?.tan())),
516 ("math", "asin") => Ok(Value::Float(expect_float(args.first())?.asin())),
517 ("math", "acos") => Ok(Value::Float(expect_float(args.first())?.acos())),
518 ("math", "atan") => Ok(Value::Float(expect_float(args.first())?.atan())),
519 ("math", "floor") => Ok(Value::Float(expect_float(args.first())?.floor())),
520 ("math", "ceil") => Ok(Value::Float(expect_float(args.first())?.ceil())),
521 ("math", "round") => Ok(Value::Float(expect_float(args.first())?.round())),
522 ("math", "trunc") => Ok(Value::Float(expect_float(args.first())?.trunc())),
523 ("math", "pow") => {
524 let a = expect_float(args.first())?;
525 let b = expect_float(args.get(1))?;
526 Ok(Value::Float(a.powf(b)))
527 }
528 ("math", "atan2") => {
529 let y = expect_float(args.first())?;
530 let x = expect_float(args.get(1))?;
531 Ok(Value::Float(y.atan2(x)))
532 }
533 ("math", "min") => {
534 let a = expect_float(args.first())?;
535 let b = expect_float(args.get(1))?;
536 Ok(Value::Float(a.min(b)))
537 }
538 ("math", "max") => {
539 let a = expect_float(args.first())?;
540 let b = expect_float(args.get(1))?;
541 Ok(Value::Float(a.max(b)))
542 }
543 ("math", "zeros") => {
544 let r = expect_int(args.first())?;
545 let c = expect_int(args.get(1))?;
546 if r < 0 || c < 0 {
547 return Err(format!("math.zeros: negative dim {r}x{c}"));
548 }
549 let r = r as usize; let c = c as usize;
550 Ok(Value::F64Array { rows: r as u32, cols: c as u32, data: vec![0.0; r * c] })
551 }
552 ("math", "ones") => {
553 let r = expect_int(args.first())?;
554 let c = expect_int(args.get(1))?;
555 if r < 0 || c < 0 {
556 return Err(format!("math.ones: negative dim {r}x{c}"));
557 }
558 let r = r as usize; let c = c as usize;
559 Ok(Value::F64Array { rows: r as u32, cols: c as u32, data: vec![1.0; r * c] })
560 }
561 ("math", "from_lists") => {
562 let rows = expect_list(args.first())?;
563 let r = rows.len();
564 if r == 0 {
565 return Ok(Value::F64Array { rows: 0, cols: 0, data: Vec::new() });
566 }
567 let first_row = match &rows[0] {
568 Value::List(xs) => xs,
569 other => return Err(format!("math.from_lists: row 0 not List, got {other:?}")),
570 };
571 let c = first_row.len();
572 let mut data = Vec::with_capacity(r * c);
573 for (i, row) in rows.iter().enumerate() {
574 let row = match row {
575 Value::List(xs) => xs,
576 other => return Err(format!("math.from_lists: row {i} not List, got {other:?}")),
577 };
578 if row.len() != c {
579 return Err(format!("math.from_lists: row {i} has {} cols, expected {c}", row.len()));
580 }
581 for (j, v) in row.iter().enumerate() {
582 let f = match v {
583 Value::Float(f) => *f,
584 Value::Int(n) => *n as f64,
585 other => return Err(format!("math.from_lists: ({i},{j}) not numeric, got {other:?}")),
586 };
587 data.push(f);
588 }
589 }
590 Ok(Value::F64Array { rows: r as u32, cols: c as u32, data })
591 }
592 ("math", "from_flat") => {
593 let r = expect_int(args.first())?;
594 let c = expect_int(args.get(1))?;
595 let xs = expect_list(args.get(2))?;
596 if r < 0 || c < 0 {
597 return Err(format!("math.from_flat: negative dim {r}x{c}"));
598 }
599 let r = r as usize; let c = c as usize;
600 if xs.len() != r * c {
601 return Err(format!("math.from_flat: list len {} != {}*{}", xs.len(), r, c));
602 }
603 let mut data = Vec::with_capacity(r * c);
604 for v in xs {
605 data.push(match v {
606 Value::Float(f) => *f,
607 Value::Int(n) => *n as f64,
608 other => return Err(format!("math.from_flat: non-numeric element {other:?}")),
609 });
610 }
611 Ok(Value::F64Array { rows: r as u32, cols: c as u32, data })
612 }
613 ("math", "rows") => {
614 let (r, _, _) = unpack_matrix(first_arg(args)?)?;
615 Ok(Value::Int(r as i64))
616 }
617 ("math", "cols") => {
618 let (_, c, _) = unpack_matrix(first_arg(args)?)?;
619 Ok(Value::Int(c as i64))
620 }
621 ("math", "get") => {
622 let (r, c, data) = unpack_matrix(first_arg(args)?)?;
623 let i = expect_int(args.get(1))? as usize;
624 let j = expect_int(args.get(2))? as usize;
625 if i >= r || j >= c {
626 return Err(format!("math.get: ({i},{j}) out of {r}x{c}"));
627 }
628 Ok(Value::Float(data[i * c + j]))
629 }
630 ("math", "to_flat") => {
631 let (_, _, data) = unpack_matrix(first_arg(args)?)?;
632 Ok(Value::List(data.into_iter().map(Value::Float).collect()))
633 }
634 ("math", "transpose") => {
635 let (r, c, data) = unpack_matrix(first_arg(args)?)?;
636 let mut out = vec![0.0; r * c];
637 for i in 0..r {
638 for j in 0..c {
639 out[j * r + i] = data[i * c + j];
640 }
641 }
642 Ok(Value::F64Array { rows: c as u32, cols: r as u32, data: out })
643 }
644 ("math", "matmul") => {
645 let (m, k1, a) = unpack_matrix(first_arg(args)?)?;
646 let (k2, n, b) = unpack_matrix(args.get(1).ok_or("math.matmul: missing arg 1")?)?;
647 if k1 != k2 {
648 return Err(format!("math.matmul: dim mismatch {m}x{k1} · {k2}x{n}"));
649 }
650 let mut c = vec![0.0; m * n];
654 for i in 0..m {
655 for kk in 0..k1 {
656 let aik = a[i * k1 + kk];
657 for j in 0..n {
658 c[i * n + j] += aik * b[kk * n + j];
659 }
660 }
661 }
662 Ok(Value::F64Array { rows: m as u32, cols: n as u32, data: c })
663 }
664 ("math", "scale") => {
665 let s = expect_float(args.first())?;
666 let (r, c, mut data) = unpack_matrix(args.get(1).ok_or("math.scale: missing arg 1")?)?;
667 for x in &mut data { *x *= s; }
668 Ok(Value::F64Array { rows: r as u32, cols: c as u32, data })
669 }
670 ("math", "add") | ("math", "sub") => {
671 let (ar, ac, a) = unpack_matrix(first_arg(args)?)?;
672 let (br, bc, b) = unpack_matrix(args.get(1).ok_or("math.add/sub: missing arg 1")?)?;
673 if ar != br || ac != bc {
674 return Err(format!("math.{op}: shape mismatch {ar}x{ac} vs {br}x{bc}"));
675 }
676 let neg = op == "sub";
677 let mut out = a;
678 for (i, x) in out.iter_mut().enumerate() {
679 if neg { *x -= b[i] } else { *x += b[i] }
680 }
681 Ok(Value::F64Array { rows: ar as u32, cols: ac as u32, data: out })
682 }
683 ("math", "sigmoid") => {
684 let (r, c, mut data) = unpack_matrix(first_arg(args)?)?;
685 for x in &mut data { *x = 1.0 / (1.0 + (-*x).exp()); }
686 Ok(Value::F64Array { rows: r as u32, cols: c as u32, data })
687 }
688
689 ("map", "new") => Ok(Value::Map(BTreeMap::new())),
691 ("map", "size") => Ok(Value::Int(expect_map(args.first())?.len() as i64)),
692 ("map", "has") => {
693 let m = expect_map(args.first())?;
694 let k = MapKey::from_value(args.get(1).ok_or("map.has: missing key")?)?;
695 Ok(Value::Bool(m.contains_key(&k)))
696 }
697 ("map", "get") => {
698 let m = expect_map(args.first())?;
699 let k = MapKey::from_value(args.get(1).ok_or("map.get: missing key")?)?;
700 Ok(match m.get(&k) {
701 Some(v) => some(v.clone()),
702 None => none(),
703 })
704 }
705 ("map", "set") => {
706 let mut m = expect_map(args.first())?.clone();
707 let k = MapKey::from_value(args.get(1).ok_or("map.set: missing key")?)?;
708 let v = args.get(2).ok_or("map.set: missing value")?.clone();
709 m.insert(k, v);
710 Ok(Value::Map(m))
711 }
712 ("map", "delete") => {
713 let mut m = expect_map(args.first())?.clone();
714 let k = MapKey::from_value(args.get(1).ok_or("map.delete: missing key")?)?;
715 m.remove(&k);
716 Ok(Value::Map(m))
717 }
718 ("map", "keys") => {
719 let m = expect_map(args.first())?;
720 Ok(Value::List(m.keys().cloned().map(MapKey::into_value).collect()))
721 }
722 ("map", "values") => {
723 let m = expect_map(args.first())?;
724 Ok(Value::List(m.values().cloned().collect()))
725 }
726 ("map", "entries") => {
727 let m = expect_map(args.first())?;
728 Ok(Value::List(m.iter()
729 .map(|(k, v)| Value::Tuple(vec![k.as_value(), v.clone()]))
730 .collect()))
731 }
732 ("map", "from_list") => {
733 let pairs = expect_list(args.first())?;
734 let mut m = BTreeMap::new();
735 for p in pairs {
736 let items = match p {
737 Value::Tuple(items) if items.len() == 2 => items,
738 other => return Err(format!(
739 "map.from_list element must be a 2-tuple, got {other:?}")),
740 };
741 let k = MapKey::from_value(&items[0])?;
742 m.insert(k, items[1].clone());
743 }
744 Ok(Value::Map(m))
745 }
746
747 ("set", "new") => Ok(Value::Set(BTreeSet::new())),
749 ("set", "size") => Ok(Value::Int(expect_set(args.first())?.len() as i64)),
750 ("set", "has") => {
751 let s = expect_set(args.first())?;
752 let k = MapKey::from_value(args.get(1).ok_or("set.has: missing element")?)?;
753 Ok(Value::Bool(s.contains(&k)))
754 }
755 ("set", "add") => {
756 let mut s = expect_set(args.first())?.clone();
757 let k = MapKey::from_value(args.get(1).ok_or("set.add: missing element")?)?;
758 s.insert(k);
759 Ok(Value::Set(s))
760 }
761 ("set", "delete") => {
762 let mut s = expect_set(args.first())?.clone();
763 let k = MapKey::from_value(args.get(1).ok_or("set.delete: missing element")?)?;
764 s.remove(&k);
765 Ok(Value::Set(s))
766 }
767 ("set", "to_list") => {
768 let s = expect_set(args.first())?;
769 Ok(Value::List(s.iter().cloned().map(MapKey::into_value).collect()))
770 }
771 ("set", "from_list") => {
772 let xs = expect_list(args.first())?;
773 let mut s = BTreeSet::new();
774 for x in xs {
775 s.insert(MapKey::from_value(x)?);
776 }
777 Ok(Value::Set(s))
778 }
779 ("set", "union") => {
780 let a = expect_set(args.first())?;
781 let b = expect_set(args.get(1))?;
782 Ok(Value::Set(a.union(b).cloned().collect()))
783 }
784 ("set", "intersect") => {
785 let a = expect_set(args.first())?;
786 let b = expect_set(args.get(1))?;
787 Ok(Value::Set(a.intersection(b).cloned().collect()))
788 }
789 ("set", "diff") => {
790 let a = expect_set(args.first())?;
791 let b = expect_set(args.get(1))?;
792 Ok(Value::Set(a.difference(b).cloned().collect()))
793 }
794 ("set", "is_empty") => Ok(Value::Bool(expect_set(args.first())?.is_empty())),
795 ("set", "is_subset") => {
796 let a = expect_set(args.first())?;
797 let b = expect_set(args.get(1))?;
798 Ok(Value::Bool(a.is_subset(b)))
799 }
800
801 ("map", "merge") => {
803 let a = expect_map(args.first())?.clone();
806 let b = expect_map(args.get(1))?;
807 let mut out = a;
808 for (k, v) in b {
809 out.insert(k.clone(), v.clone());
810 }
811 Ok(Value::Map(out))
812 }
813 ("map", "is_empty") => Ok(Value::Bool(expect_map(args.first())?.is_empty())),
814
815 ("deque", "new") => Ok(Value::Deque(std::collections::VecDeque::new())),
817 ("deque", "size") => Ok(Value::Int(expect_deque(args.first())?.len() as i64)),
818 ("deque", "is_empty") => Ok(Value::Bool(expect_deque(args.first())?.is_empty())),
819 ("deque", "push_back") => {
820 let mut d = expect_deque(args.first())?.clone();
821 let x = args.get(1).ok_or("deque.push_back: missing value")?.clone();
822 d.push_back(x);
823 Ok(Value::Deque(d))
824 }
825 ("deque", "push_front") => {
826 let mut d = expect_deque(args.first())?.clone();
827 let x = args.get(1).ok_or("deque.push_front: missing value")?.clone();
828 d.push_front(x);
829 Ok(Value::Deque(d))
830 }
831 ("deque", "pop_back") => {
832 let mut d = expect_deque(args.first())?.clone();
833 match d.pop_back() {
834 Some(x) => Ok(Value::Variant {
835 name: "Some".into(),
836 args: vec![Value::Tuple(vec![x, Value::Deque(d)])],
837 }),
838 None => Ok(Value::Variant { name: "None".into(), args: vec![] }),
839 }
840 }
841 ("deque", "pop_front") => {
842 let mut d = expect_deque(args.first())?.clone();
843 match d.pop_front() {
844 Some(x) => Ok(Value::Variant {
845 name: "Some".into(),
846 args: vec![Value::Tuple(vec![x, Value::Deque(d)])],
847 }),
848 None => Ok(Value::Variant { name: "None".into(), args: vec![] }),
849 }
850 }
851 ("deque", "peek_back") => {
852 let d = expect_deque(args.first())?;
853 match d.back() {
854 Some(x) => Ok(Value::Variant {
855 name: "Some".into(),
856 args: vec![x.clone()],
857 }),
858 None => Ok(Value::Variant { name: "None".into(), args: vec![] }),
859 }
860 }
861 ("deque", "peek_front") => {
862 let d = expect_deque(args.first())?;
863 match d.front() {
864 Some(x) => Ok(Value::Variant {
865 name: "Some".into(),
866 args: vec![x.clone()],
867 }),
868 None => Ok(Value::Variant { name: "None".into(), args: vec![] }),
869 }
870 }
871 ("deque", "from_list") => {
872 let xs = expect_list(args.first())?;
873 Ok(Value::Deque(xs.iter().cloned().collect()))
874 }
875 ("deque", "to_list") => {
876 let d = expect_deque(args.first())?;
877 Ok(Value::List(d.iter().cloned().collect()))
878 }
879
880 ("crypto", "sha256") => {
883 use sha2::{Digest, Sha256};
884 let data = expect_bytes(args.first())?;
885 let mut h = Sha256::new();
886 h.update(data);
887 Ok(Value::Bytes(h.finalize().to_vec()))
888 }
889 ("crypto", "sha512") => {
890 use sha2::{Digest, Sha512};
891 let data = expect_bytes(args.first())?;
892 let mut h = Sha512::new();
893 h.update(data);
894 Ok(Value::Bytes(h.finalize().to_vec()))
895 }
896 ("crypto", "md5") => {
897 use md5::{Digest, Md5};
898 let data = expect_bytes(args.first())?;
899 let mut h = Md5::new();
900 h.update(data);
901 Ok(Value::Bytes(h.finalize().to_vec()))
902 }
903 ("crypto", "hmac_sha256") => {
904 use hmac::{Hmac, KeyInit, Mac};
905 type HmacSha256 = Hmac<sha2::Sha256>;
906 let key = expect_bytes(args.first())?;
907 let data = expect_bytes(args.get(1))?;
908 let mut mac = HmacSha256::new_from_slice(key)
909 .map_err(|e| format!("hmac_sha256 key: {e}"))?;
910 mac.update(data);
911 Ok(Value::Bytes(mac.finalize().into_bytes().to_vec()))
912 }
913 ("crypto", "hmac_sha512") => {
914 use hmac::{Hmac, KeyInit, Mac};
915 type HmacSha512 = Hmac<sha2::Sha512>;
916 let key = expect_bytes(args.first())?;
917 let data = expect_bytes(args.get(1))?;
918 let mut mac = HmacSha512::new_from_slice(key)
919 .map_err(|e| format!("hmac_sha512 key: {e}"))?;
920 mac.update(data);
921 Ok(Value::Bytes(mac.finalize().into_bytes().to_vec()))
922 }
923 ("crypto", "base64_encode") => {
924 use base64::{Engine, engine::general_purpose::STANDARD};
925 let data = expect_bytes(args.first())?;
926 Ok(Value::Str(STANDARD.encode(data)))
927 }
928 ("crypto", "base64_decode") => {
929 use base64::{Engine, engine::general_purpose::STANDARD};
930 let s = expect_str(args.first())?;
931 match STANDARD.decode(s) {
932 Ok(b) => Ok(ok_v(Value::Bytes(b))),
933 Err(e) => Ok(err_v(Value::Str(format!("base64: {e}")))),
934 }
935 }
936 ("crypto", "hex_encode") => {
937 let data = expect_bytes(args.first())?;
938 Ok(Value::Str(hex::encode(data)))
939 }
940 ("crypto", "hex_decode") => {
941 let s = expect_str(args.first())?;
942 match hex::decode(s) {
943 Ok(b) => Ok(ok_v(Value::Bytes(b))),
944 Err(e) => Ok(err_v(Value::Str(format!("hex: {e}")))),
945 }
946 }
947 ("crypto", "constant_time_eq") => {
948 use subtle::ConstantTimeEq;
949 let a = expect_bytes(args.first())?;
950 let b = expect_bytes(args.get(1))?;
951 let eq = if a.len() == b.len() {
956 a.ct_eq(b).into()
957 } else {
958 false
959 };
960 Ok(Value::Bool(eq))
961 }
962
963 ("random", "seed") => {
970 let s = args.first().ok_or("random.seed: missing arg")?.as_int();
971 let mixed = splitmix64(s as u64).0;
976 Ok(rng_value(mixed))
977 }
978 ("random", "int") => {
979 let state = rng_decode(args.first())?;
980 let lo = args.get(1).ok_or("random.int: missing lo")?.as_int();
981 let hi = args.get(2).ok_or("random.int: missing hi")?.as_int();
982 if hi < lo {
983 return Err(format!(
984 "random.int: hi ({hi}) must be >= lo ({lo})"));
985 }
986 let span = (hi as i128) - (lo as i128) + 1;
987 let (raw, next_state) = splitmix64(state);
988 let drawn = lo as i128 + (raw as u128 % span as u128) as i128;
993 Ok(Value::Tuple(vec![
994 Value::Int(drawn as i64),
995 rng_value(next_state),
996 ]))
997 }
998 ("random", "float") => {
999 let state = rng_decode(args.first())?;
1000 let (raw, next_state) = splitmix64(state);
1001 let f = ((raw >> 11) as f64) / ((1u64 << 53) as f64);
1004 Ok(Value::Tuple(vec![Value::Float(f), rng_value(next_state)]))
1005 }
1006 ("random", "choose") => {
1007 let state = rng_decode(args.first())?;
1008 let xs = match args.get(1) {
1009 Some(Value::List(xs)) => xs,
1010 _ => return Err("random.choose: expected List".into()),
1011 };
1012 if xs.is_empty() {
1013 return Ok(Value::Variant {
1014 name: "None".into(), args: vec![],
1015 });
1016 }
1017 let (raw, next_state) = splitmix64(state);
1018 let idx = (raw as usize) % xs.len();
1019 let pick = xs[idx].clone();
1020 Ok(Value::Variant {
1021 name: "Some".into(),
1022 args: vec![Value::Tuple(vec![pick, rng_value(next_state)])],
1023 })
1024 }
1025
1026 ("parser", "char") => {
1031 let s = expect_str(args.first())?;
1032 if s.chars().count() != 1 {
1033 return Err(format!(
1034 "parser.char: expected 1-character string, got {s:?}"));
1035 }
1036 Ok(parser_node("Char", &[("ch", Value::Str(s))]))
1037 }
1038 ("parser", "string") => {
1039 let s = expect_str(args.first())?;
1040 Ok(parser_node("String", &[("s", Value::Str(s))]))
1041 }
1042 ("parser", "digit") => Ok(parser_node("Digit", &[])),
1043 ("parser", "alpha") => Ok(parser_node("Alpha", &[])),
1044 ("parser", "whitespace") => Ok(parser_node("Whitespace", &[])),
1045 ("parser", "eof") => Ok(parser_node("Eof", &[])),
1046 ("parser", "seq") => {
1047 let a = args.first().cloned()
1048 .ok_or_else(|| "parser.seq: missing first parser".to_string())?;
1049 let b = args.get(1).cloned()
1050 .ok_or_else(|| "parser.seq: missing second parser".to_string())?;
1051 Ok(parser_node("Seq", &[("a", a), ("b", b)]))
1052 }
1053 ("parser", "alt") => {
1054 let a = args.first().cloned()
1055 .ok_or_else(|| "parser.alt: missing first parser".to_string())?;
1056 let b = args.get(1).cloned()
1057 .ok_or_else(|| "parser.alt: missing second parser".to_string())?;
1058 Ok(parser_node("Alt", &[("a", a), ("b", b)]))
1059 }
1060 ("parser", "many") => {
1061 let p = args.first().cloned()
1062 .ok_or_else(|| "parser.many: missing inner parser".to_string())?;
1063 Ok(parser_node("Many", &[("p", p)]))
1064 }
1065 ("parser", "optional") => {
1066 let p = args.first().cloned()
1067 .ok_or_else(|| "parser.optional: missing inner parser".to_string())?;
1068 Ok(parser_node("Optional", &[("p", p)]))
1069 }
1070 ("parser", "map") => {
1074 let p = args.first().cloned()
1075 .ok_or_else(|| "parser.map: missing parser".to_string())?;
1076 let f = args.get(1).cloned()
1077 .ok_or_else(|| "parser.map: missing closure".to_string())?;
1078 Ok(parser_node("Map", &[("p", p), ("f", f)]))
1079 }
1080 ("parser", "and_then") => {
1081 let p = args.first().cloned()
1082 .ok_or_else(|| "parser.and_then: missing parser".to_string())?;
1083 let f = args.get(1).cloned()
1084 .ok_or_else(|| "parser.and_then: missing closure".to_string())?;
1085 Ok(parser_node("AndThen", &[("p", p), ("f", f)]))
1086 }
1087 ("regex", "compile") => {
1097 let pat = expect_str(args.first())?;
1098 match get_or_compile_regex(&pat) {
1099 Ok(_) => Ok(ok_v(Value::Str(pat))),
1100 Err(e) => Ok(err_v(Value::Str(e))),
1101 }
1102 }
1103 ("regex", "is_match") => {
1104 let pat = expect_str(args.first())?;
1105 let s = expect_str(args.get(1))?;
1106 let re = get_or_compile_regex(&pat).map_err(|e| format!("regex.is_match: {e}"))?;
1107 Ok(Value::Bool(re.is_match(&s)))
1108 }
1109 ("regex", "find") => {
1110 let pat = expect_str(args.first())?;
1111 let s = expect_str(args.get(1))?;
1112 let re = get_or_compile_regex(&pat).map_err(|e| format!("regex.find: {e}"))?;
1113 match re.captures(&s) {
1114 Some(caps) => Ok(Value::Variant {
1115 name: "Some".into(),
1116 args: vec![match_value(&caps)],
1117 }),
1118 None => Ok(Value::Variant { name: "None".into(), args: vec![] }),
1119 }
1120 }
1121 ("regex", "find_all") => {
1122 let pat = expect_str(args.first())?;
1123 let s = expect_str(args.get(1))?;
1124 let re = get_or_compile_regex(&pat).map_err(|e| format!("regex.find_all: {e}"))?;
1125 let items: Vec<Value> = re.captures_iter(&s).map(|caps| match_value(&caps)).collect();
1126 Ok(Value::List(items))
1127 }
1128 ("regex", "replace") => {
1129 let pat = expect_str(args.first())?;
1130 let s = expect_str(args.get(1))?;
1131 let rep = expect_str(args.get(2))?;
1132 let re = get_or_compile_regex(&pat).map_err(|e| format!("regex.replace: {e}"))?;
1133 Ok(Value::Str(re.replace(&s, rep.as_str()).into_owned()))
1134 }
1135 ("regex", "replace_all") => {
1136 let pat = expect_str(args.first())?;
1137 let s = expect_str(args.get(1))?;
1138 let rep = expect_str(args.get(2))?;
1139 let re = get_or_compile_regex(&pat).map_err(|e| format!("regex.replace_all: {e}"))?;
1140 Ok(Value::Str(re.replace_all(&s, rep.as_str()).into_owned()))
1141 }
1142 ("datetime", "parse_iso") => {
1145 let s = expect_str(args.first())?;
1146 match chrono::DateTime::parse_from_rfc3339(&s) {
1147 Ok(dt) => Ok(ok_v(Value::Int(instant_from_chrono(dt)))),
1148 Err(e) => Ok(err_v(Value::Str(format!("parse_iso: {e}")))),
1149 }
1150 }
1151 ("datetime", "format_iso") => {
1152 let n = expect_int(args.first())?;
1153 Ok(Value::Str(format_iso(n)))
1154 }
1155 ("datetime", "parse") => {
1156 let s = expect_str(args.first())?;
1157 let fmt = expect_str(args.get(1))?;
1158 match chrono::NaiveDateTime::parse_from_str(&s, &fmt) {
1159 Ok(naive) => {
1160 use chrono::TimeZone;
1161 match chrono::Utc.from_local_datetime(&naive).single() {
1162 Some(dt) => Ok(ok_v(Value::Int(instant_from_chrono(dt)))),
1163 None => Ok(err_v(Value::Str("parse: ambiguous local time".into()))),
1164 }
1165 }
1166 Err(e) => Ok(err_v(Value::Str(format!("parse: {e}")))),
1167 }
1168 }
1169 ("datetime", "format") => {
1170 let n = expect_int(args.first())?;
1171 let fmt = expect_str(args.get(1))?;
1172 let dt = chrono_from_instant(n);
1173 Ok(Value::Str(dt.format(&fmt).to_string()))
1174 }
1175 ("datetime", "to_components") => {
1176 let n = expect_int(args.first())?;
1177 let tz = match parse_tz_arg(args.get(1)) {
1178 Ok(t) => t,
1179 Err(e) => return Ok(err_v(Value::Str(e))),
1180 };
1181 match resolve_tz_to_components(n, &tz) {
1182 Ok(rec) => Ok(ok_v(rec)),
1183 Err(e) => Ok(err_v(Value::Str(e))),
1184 }
1185 }
1186 ("datetime", "from_components") => {
1187 let rec = match args.first() {
1188 Some(Value::Record(r)) => r.clone(),
1189 _ => return Err("from_components: expected DateTime record".into()),
1190 };
1191 match instant_from_components(&rec) {
1192 Ok(n) => Ok(ok_v(Value::Int(n))),
1193 Err(e) => Ok(err_v(Value::Str(e))),
1194 }
1195 }
1196 ("datetime", "add") => {
1197 let a = expect_int(args.first())?;
1198 let d = expect_int(args.get(1))?;
1199 Ok(Value::Int(a.saturating_add(d)))
1200 }
1201 ("datetime", "diff") => {
1202 let a = expect_int(args.first())?;
1203 let b = expect_int(args.get(1))?;
1204 Ok(Value::Int(a.saturating_sub(b)))
1205 }
1206 ("datetime", "duration_seconds") => {
1207 let s = expect_float(args.first())?;
1208 let nanos = (s * 1_000_000_000.0) as i64;
1209 Ok(Value::Int(nanos))
1210 }
1211 ("datetime", "duration_minutes") => {
1212 let m = expect_int(args.first())?;
1213 Ok(Value::Int(m.saturating_mul(60_000_000_000)))
1214 }
1215 ("datetime", "duration_days") => {
1216 let d = expect_int(args.first())?;
1217 Ok(Value::Int(d.saturating_mul(86_400_000_000_000)))
1218 }
1219
1220 ("regex", "split") => {
1221 let pat = expect_str(args.first())?;
1222 let s = expect_str(args.get(1))?;
1223 let re = get_or_compile_regex(&pat).map_err(|e| format!("regex.split: {e}"))?;
1224 let parts: Vec<Value> = re.split(&s).map(|p| Value::Str(p.to_string())).collect();
1225 Ok(Value::List(parts))
1226 }
1227
1228 ("http", "with_header") => {
1231 let req = expect_record_pure(args.first())?.clone();
1232 let k = expect_str(args.get(1))?;
1233 let v = expect_str(args.get(2))?;
1234 Ok(Value::Record(http_set_header(req, &k, &v)))
1235 }
1236 ("http", "with_auth") => {
1237 let req = expect_record_pure(args.first())?.clone();
1238 let scheme = expect_str(args.get(1))?;
1239 let token = expect_str(args.get(2))?;
1240 let value = format!("{scheme} {token}");
1241 Ok(Value::Record(http_set_header(req, "Authorization", &value)))
1242 }
1243 ("http", "with_query") => {
1244 let req = expect_record_pure(args.first())?.clone();
1245 let params = match args.get(1) {
1246 Some(Value::Map(m)) => m.clone(),
1247 Some(other) => return Err(format!(
1248 "http.with_query: params must be Map[Str, Str], got {other:?}")),
1249 None => return Err("http.with_query: missing params argument".into()),
1250 };
1251 Ok(Value::Record(http_append_query(req, ¶ms)))
1252 }
1253 ("http", "with_timeout_ms") => {
1254 let req = expect_record_pure(args.first())?.clone();
1255 let ms = expect_int(args.get(1))?;
1256 let mut out = req;
1257 out.insert("timeout_ms".into(), Value::Variant {
1258 name: "Some".into(),
1259 args: vec![Value::Int(ms)],
1260 });
1261 Ok(Value::Record(out))
1262 }
1263 ("http", "json_body") => {
1264 let resp = expect_record_pure(args.first())?;
1265 let body = match resp.get("body") {
1266 Some(Value::Bytes(b)) => b.clone(),
1267 _ => return Err("http.json_body: HttpResponse.body must be Bytes".into()),
1268 };
1269 let s = match std::str::from_utf8(&body) {
1270 Ok(s) => s,
1271 Err(e) => return Ok(http_decode_err_pure(format!("body not UTF-8: {e}"))),
1272 };
1273 match serde_json::from_str::<serde_json::Value>(s) {
1274 Ok(j) => Ok(ok_v(Value::from_json(&j))),
1275 Err(e) => Ok(http_decode_err_pure(format!("json parse: {e}"))),
1276 }
1277 }
1278 ("http", "text_body") => {
1279 let resp = expect_record_pure(args.first())?;
1280 let body = match resp.get("body") {
1281 Some(Value::Bytes(b)) => b.clone(),
1282 _ => return Err("http.text_body: HttpResponse.body must be Bytes".into()),
1283 };
1284 match String::from_utf8(body) {
1285 Ok(s) => Ok(ok_v(Value::Str(s))),
1286 Err(e) => Ok(http_decode_err_pure(format!("body not UTF-8: {e}"))),
1287 }
1288 }
1289
1290 ("cli", "flag") => {
1294 let name = expect_str(args.first())?;
1295 let short = opt_str(args.get(1));
1296 let help = expect_str(args.get(2))?;
1297 Ok(value_from_json(crate::cli::flag_spec(&name, short.as_deref(), &help)))
1298 }
1299 ("cli", "option") => {
1300 let name = expect_str(args.first())?;
1301 let short = opt_str(args.get(1));
1302 let help = expect_str(args.get(2))?;
1303 let default = opt_str(args.get(3));
1304 Ok(value_from_json(crate::cli::option_spec(&name, short.as_deref(), &help, default.as_deref())))
1305 }
1306 ("cli", "positional") => {
1307 let name = expect_str(args.first())?;
1308 let help = expect_str(args.get(1))?;
1309 let required = expect_bool(args.get(2))?;
1310 Ok(value_from_json(crate::cli::positional_spec(&name, &help, required)))
1311 }
1312 ("cli", "spec") => {
1313 let name = expect_str(args.first())?;
1314 let help = expect_str(args.get(1))?;
1315 let arg_specs: Vec<serde_json::Value> = expect_list(args.get(2))?
1316 .iter().map(value_to_json).collect();
1317 let subs: Vec<serde_json::Value> = expect_list(args.get(3))?
1318 .iter().map(value_to_json).collect();
1319 Ok(value_from_json(crate::cli::build_spec(&name, &help, arg_specs, subs)))
1320 }
1321 ("cli", "parse") => {
1322 let spec = value_to_json(args.first().unwrap_or(&Value::Unit));
1323 let argv: Vec<String> = expect_list(args.get(1))?
1324 .iter().map(|v| match v {
1325 Value::Str(s) => Ok(s.clone()),
1326 other => Err(format!("cli.parse: argv must be List[Str], got {other:?}")),
1327 }).collect::<Result<_, _>>()?;
1328 match crate::cli::parse(&spec, &argv) {
1329 Ok(parsed) => Ok(ok_v(value_from_json(parsed))),
1330 Err(msg) => Ok(err_v(Value::Str(msg))),
1331 }
1332 }
1333 ("cli", "envelope") => {
1334 let ok = expect_bool(args.first())?;
1335 let cmd = expect_str(args.get(1))?;
1336 let data = value_to_json(args.get(2).unwrap_or(&Value::Unit));
1337 Ok(value_from_json(crate::cli::envelope(ok, &cmd, data)))
1338 }
1339 ("cli", "describe") => {
1340 let spec = value_to_json(args.first().unwrap_or(&Value::Unit));
1341 Ok(value_from_json(crate::cli::describe(&spec)))
1342 }
1343 ("cli", "help") => {
1344 let spec = value_to_json(args.first().unwrap_or(&Value::Unit));
1345 Ok(Value::Str(crate::cli::help_text(&spec)))
1346 }
1347
1348 _ => Err(format!("unknown pure builtin: {kind}.{op}")),
1349 }
1350}
1351
1352fn opt_str(arg: Option<&Value>) -> Option<String> {
1356 match arg {
1357 Some(Value::Variant { name, args }) if name == "Some" => {
1358 args.first().and_then(|v| match v {
1359 Value::Str(s) => Some(s.clone()),
1360 _ => None,
1361 })
1362 }
1363 _ => None,
1364 }
1365}
1366
1367fn value_from_json(v: serde_json::Value) -> Value { Value::from_json(&v) }
1368
1369fn regex_cache() -> &'static Mutex<HashMap<String, regex::Regex>> {
1374 static CACHE: OnceLock<Mutex<HashMap<String, regex::Regex>>> = OnceLock::new();
1375 CACHE.get_or_init(|| Mutex::new(HashMap::new()))
1376}
1377
1378fn get_or_compile_regex(pattern: &str) -> Result<regex::Regex, String> {
1379 let cache = regex_cache();
1380 {
1381 let guard = cache.lock().unwrap();
1382 if let Some(re) = guard.get(pattern) {
1383 return Ok(re.clone());
1384 }
1385 }
1386 let re = regex::Regex::new(pattern).map_err(|e| format!("invalid regex: {e}"))?;
1387 let mut guard = cache.lock().unwrap();
1388 guard.insert(pattern.to_string(), re.clone());
1389 Ok(re)
1390}
1391
1392fn match_value(caps: ®ex::Captures) -> Value {
1396 let m0 = caps.get(0).expect("regex match always has group 0");
1397 let mut rec = indexmap::IndexMap::new();
1398 rec.insert("text".into(), Value::Str(m0.as_str().to_string()));
1399 rec.insert("start".into(), Value::Int(m0.start() as i64));
1400 rec.insert("end".into(), Value::Int(m0.end() as i64));
1401 let groups: Vec<Value> = (1..caps.len())
1402 .map(|i| {
1403 Value::Str(
1404 caps.get(i)
1405 .map(|m| m.as_str().to_string())
1406 .unwrap_or_default(),
1407 )
1408 })
1409 .collect();
1410 rec.insert("groups".into(), Value::List(groups));
1411 Value::Record(rec)
1412}
1413
1414fn expect_map(v: Option<&Value>) -> Result<&BTreeMap<MapKey, Value>, String> {
1415 match v {
1416 Some(Value::Map(m)) => Ok(m),
1417 other => Err(format!("expected Map, got {other:?}")),
1418 }
1419}
1420
1421fn expect_set(v: Option<&Value>) -> Result<&BTreeSet<MapKey>, String> {
1422 match v {
1423 Some(Value::Set(s)) => Ok(s),
1424 other => Err(format!("expected Set, got {other:?}")),
1425 }
1426}
1427
1428fn unpack_matrix(v: &Value) -> Result<(usize, usize, Vec<f64>), String> {
1432 if let Value::F64Array { rows, cols, data } = v {
1433 return Ok((*rows as usize, *cols as usize, data.clone()));
1434 }
1435 let rec = match v {
1436 Value::Record(r) => r,
1437 other => return Err(format!("expected matrix, got {other:?}")),
1438 };
1439 let rows = match rec.get("rows") {
1440 Some(Value::Int(n)) => *n as usize,
1441 _ => return Err("matrix: missing/invalid `rows`".into()),
1442 };
1443 let cols = match rec.get("cols") {
1444 Some(Value::Int(n)) => *n as usize,
1445 _ => return Err("matrix: missing/invalid `cols`".into()),
1446 };
1447 let data = match rec.get("data") {
1448 Some(Value::List(items)) => {
1449 let mut out = Vec::with_capacity(items.len());
1450 for it in items {
1451 out.push(match it {
1452 Value::Float(f) => *f,
1453 Value::Int(n) => *n as f64,
1454 other => return Err(format!("matrix data: not numeric, got {other:?}")),
1455 });
1456 }
1457 out
1458 }
1459 _ => return Err("matrix: missing/invalid `data`".into()),
1460 };
1461 if data.len() != rows * cols {
1462 return Err(format!("matrix: data len {} != {rows}*{cols}", data.len()));
1463 }
1464 Ok((rows, cols, data))
1465}
1466
1467fn expect_bytes(v: Option<&Value>) -> Result<&Vec<u8>, String> {
1468 match v {
1469 Some(Value::Bytes(b)) => Ok(b),
1470 Some(other) => Err(format!("expected Bytes, got {other:?}")),
1471 None => Err("missing argument".into()),
1472 }
1473}
1474
1475fn first_arg(args: &[Value]) -> Result<&Value, String> {
1476 args.first().ok_or_else(|| "missing argument".into())
1477}
1478
1479fn tuple_index(v: &Value, i: usize) -> Result<Value, String> {
1480 match v {
1481 Value::Tuple(items) => items.get(i).cloned()
1482 .ok_or_else(|| format!("tuple index {i} out of range (len={})", items.len())),
1483 other => Err(format!("expected Tuple, got {other:?}")),
1484 }
1485}
1486
1487fn expect_str(v: Option<&Value>) -> Result<String, String> {
1488 match v {
1489 Some(Value::Str(s)) => Ok(s.clone()),
1490 Some(other) => Err(format!("expected Str, got {other:?}")),
1491 None => Err("missing argument".into()),
1492 }
1493}
1494
1495fn expect_int(v: Option<&Value>) -> Result<i64, String> {
1496 match v {
1497 Some(Value::Int(n)) => Ok(*n),
1498 Some(other) => Err(format!("expected Int, got {other:?}")),
1499 None => Err("missing argument".into()),
1500 }
1501}
1502
1503fn expect_float(v: Option<&Value>) -> Result<f64, String> {
1504 match v {
1505 Some(Value::Float(f)) => Ok(*f),
1506 Some(other) => Err(format!("expected Float, got {other:?}")),
1507 None => Err("missing argument".into()),
1508 }
1509}
1510
1511fn expect_list(v: Option<&Value>) -> Result<&Vec<Value>, String> {
1512 match v {
1513 Some(Value::List(xs)) => Ok(xs),
1514 Some(other) => Err(format!("expected List, got {other:?}")),
1515 None => Err("missing argument".into()),
1516 }
1517}
1518
1519fn expect_bool(v: Option<&Value>) -> Result<bool, String> {
1520 match v {
1521 Some(Value::Bool(b)) => Ok(*b),
1522 Some(other) => Err(format!("expected Bool, got {other:?}")),
1523 None => Err("missing argument".into()),
1524 }
1525}
1526
1527fn expect_deque(v: Option<&Value>) -> Result<&std::collections::VecDeque<Value>, String> {
1528 match v {
1529 Some(Value::Deque(d)) => Ok(d),
1530 Some(other) => Err(format!("expected Deque, got {other:?}")),
1531 None => Err("missing argument".into()),
1532 }
1533}
1534
1535fn some(v: Value) -> Value { Value::Variant { name: "Some".into(), args: vec![v] } }
1536fn none() -> Value { Value::Variant { name: "None".into(), args: Vec::new() } }
1537fn ok_v(v: Value) -> Value { Value::Variant { name: "Ok".into(), args: vec![v] } }
1538fn err_v(v: Value) -> Value { Value::Variant { name: "Err".into(), args: vec![v] } }
1539
1540fn parser_node(kind: &str, fields: &[(&str, Value)]) -> Value {
1548 let mut r = indexmap::IndexMap::new();
1549 r.insert("kind".into(), Value::Str(kind.into()));
1550 for (k, v) in fields {
1551 r.insert((*k).into(), v.clone());
1552 }
1553 Value::Record(r)
1554}
1555
1556fn splitmix64(state: u64) -> (u64, u64) {
1567 let next = state.wrapping_add(0x9E37_79B9_7F4A_7C15);
1568 let mut z = next;
1569 z = (z ^ (z >> 30)).wrapping_mul(0xBF58_476D_1CE4_E5B9);
1570 z = (z ^ (z >> 27)).wrapping_mul(0x94D0_49BB_1331_11EB);
1571 let z = z ^ (z >> 31);
1572 (z, next)
1573}
1574
1575fn rng_value(state: u64) -> Value {
1579 let mut fields = indexmap::IndexMap::new();
1580 fields.insert("state".into(), Value::Int(state as i64));
1581 Value::Record(fields)
1582}
1583
1584fn rng_decode(v: Option<&Value>) -> Result<u64, String> {
1586 let rec = match v {
1587 Some(Value::Record(r)) => r,
1588 Some(other) => return Err(format!("expected Rng, got {other:?}")),
1589 None => return Err("missing Rng arg".into()),
1590 };
1591 match rec.get("state") {
1592 Some(Value::Int(n)) => Ok(*n as u64),
1593 _ => Err("malformed Rng: missing `state :: Int`".into()),
1594 }
1595}
1596
1597fn expect_record_pure(v: Option<&Value>) -> Result<&indexmap::IndexMap<String, Value>, String> {
1600 match v {
1601 Some(Value::Record(r)) => Ok(r),
1602 Some(other) => Err(format!("expected Record, got {other:?}")),
1603 None => Err("missing Record argument".into()),
1604 }
1605}
1606
1607fn http_decode_err_pure(msg: String) -> Value {
1608 let inner = Value::Variant {
1609 name: "DecodeError".into(),
1610 args: vec![Value::Str(msg)],
1611 };
1612 err_v(inner)
1613}
1614
1615fn http_set_header(
1620 mut req: indexmap::IndexMap<String, Value>,
1621 name: &str,
1622 value: &str,
1623) -> indexmap::IndexMap<String, Value> {
1624 use lex_bytecode::MapKey;
1625 let mut headers = match req.shift_remove("headers") {
1626 Some(Value::Map(m)) => m,
1627 _ => std::collections::BTreeMap::new(),
1628 };
1629 let key = MapKey::Str(name.to_lowercase());
1630 let lowered = name.to_lowercase();
1633 headers.retain(|k, _| match k {
1634 MapKey::Str(s) => s.to_lowercase() != lowered,
1635 _ => true,
1636 });
1637 headers.insert(key, Value::Str(value.to_string()));
1638 req.insert("headers".into(), Value::Map(headers));
1639 req
1640}
1641
1642fn http_append_query(
1648 mut req: indexmap::IndexMap<String, Value>,
1649 params: &std::collections::BTreeMap<lex_bytecode::MapKey, Value>,
1650) -> indexmap::IndexMap<String, Value> {
1651 use lex_bytecode::MapKey;
1652 let url = match req.get("url") {
1653 Some(Value::Str(s)) => s.clone(),
1654 _ => return req,
1655 };
1656 let mut pieces = Vec::new();
1657 for (k, v) in params {
1658 let kk = match k { MapKey::Str(s) => s.clone(), _ => continue };
1659 let vv = match v { Value::Str(s) => s.clone(), _ => continue };
1660 pieces.push(format!("{}={}", url_encode(&kk), url_encode(&vv)));
1661 }
1662 if pieces.is_empty() { return req; }
1663 let sep = if url.contains('?') { '&' } else { '?' };
1664 let new_url = format!("{url}{sep}{}", pieces.join("&"));
1665 req.insert("url".into(), Value::Str(new_url));
1666 req
1667}
1668
1669fn url_encode(s: &str) -> String {
1674 let mut out = String::with_capacity(s.len());
1675 for b in s.bytes() {
1676 match b {
1677 b'A'..=b'Z' | b'a'..=b'z' | b'0'..=b'9' | b'-' | b'_' | b'.' | b'~' => {
1678 out.push(b as char);
1679 }
1680 _ => out.push_str(&format!("%{:02X}", b)),
1681 }
1682 }
1683 out
1684}
1685
1686fn value_to_json(v: &Value) -> serde_json::Value { v.to_json() }
1687
1688fn unwrap_toml_datetime_markers(v: &mut serde_json::Value) {
1696 use serde_json::Value as J;
1697 match v {
1698 J::Object(map) => {
1699 if map.len() == 1 {
1703 if let Some(J::String(s)) = map.get("$__toml_private_datetime") {
1704 let s = s.clone();
1705 *v = J::String(s);
1706 return;
1707 }
1708 }
1709 for (_, child) in map.iter_mut() {
1710 unwrap_toml_datetime_markers(child);
1711 }
1712 }
1713 J::Array(items) => {
1714 for item in items.iter_mut() {
1715 unwrap_toml_datetime_markers(item);
1716 }
1717 }
1718 _ => {}
1719 }
1720}
1721
1722fn json_to_value(v: &serde_json::Value) -> Value { Value::from_json(v) }
1723
1724fn required_field_names(arg: Option<&Value>) -> Result<Vec<String>, String> {
1729 let list = expect_list(arg)?;
1730 let mut out = Vec::with_capacity(list.len());
1731 for v in list {
1732 match v {
1733 Value::Str(s) => out.push(s.clone()),
1734 other => return Err(format!(
1735 "parse_strict: required-fields list must contain Str, got {other:?}"
1736 )),
1737 }
1738 }
1739 Ok(out)
1740}
1741
1742fn check_required_fields(
1763 value: &serde_json::Value,
1764 required: &[String],
1765) -> Result<(), String> {
1766 if required.is_empty() {
1767 return Ok(());
1768 }
1769 if !matches!(value, serde_json::Value::Object(_)) {
1770 return Err(format!(
1771 "parse_strict: expected top-level object with fields {:?}, got {value}",
1772 required
1773 ));
1774 }
1775 let mut missing: Vec<String> = Vec::new();
1776 for path in required {
1777 if !path_exists(value, path) {
1778 missing.push(path.clone());
1779 }
1780 }
1781 if missing.is_empty() {
1782 Ok(())
1783 } else {
1784 Err(format!("missing required field(s): {}", missing.join(", ")))
1785 }
1786}
1787
1788fn path_exists(value: &serde_json::Value, path: &str) -> bool {
1793 let mut cursor = value;
1794 let segments = split_dotted_path(path);
1795 for seg in &segments {
1796 match cursor {
1797 serde_json::Value::Object(o) => match o.get(seg.as_str()) {
1798 Some(next) => cursor = next,
1799 None => return false,
1800 },
1801 _ => return false,
1802 }
1803 }
1804 true
1805}
1806
1807fn split_dotted_path(path: &str) -> Vec<String> {
1811 let mut out: Vec<String> = Vec::new();
1812 let mut cur = String::new();
1813 let mut iter = path.chars().peekable();
1814 while let Some(c) = iter.next() {
1815 if c == '\\' {
1816 if let Some(&'.') = iter.peek() {
1818 cur.push('.');
1819 iter.next();
1820 continue;
1821 }
1822 cur.push(c);
1823 } else if c == '.' {
1824 out.push(std::mem::take(&mut cur));
1825 } else {
1826 cur.push(c);
1827 }
1828 }
1829 out.push(cur);
1830 out
1831}
1832
1833fn parse_dotenv(src: &str) -> Result<indexmap::IndexMap<String, String>, String> {
1844 let mut out = indexmap::IndexMap::new();
1845 for (idx, raw) in src.lines().enumerate() {
1846 let line = raw.trim();
1847 if line.is_empty() || line.starts_with('#') {
1848 continue;
1849 }
1850 let after_export = line.strip_prefix("export ").unwrap_or(line);
1853 let (k, v) = match after_export.split_once('=') {
1854 Some(kv) => kv,
1855 None => return Err(format!("dotenv.parse line {}: missing `=`", idx + 1)),
1856 };
1857 let key = k.trim();
1858 if key.is_empty() {
1859 return Err(format!("dotenv.parse line {}: empty key", idx + 1));
1860 }
1861 let v_trim = v.trim();
1862 let value = if let Some(q) = v_trim.strip_prefix('"').and_then(|s| s.strip_suffix('"')) {
1863 q.to_string()
1864 } else if let Some(q) = v_trim.strip_prefix('\'').and_then(|s| s.strip_suffix('\'')) {
1865 q.to_string()
1866 } else {
1867 v_trim.to_string()
1868 };
1869 out.insert(key.to_string(), value);
1870 }
1871 Ok(out)
1872}
1873
1874fn instant_from_chrono<Tz: chrono::TimeZone>(dt: chrono::DateTime<Tz>) -> i64 {
1880 dt.timestamp_nanos_opt().unwrap_or(i64::MAX)
1881}
1882
1883fn chrono_from_instant(n: i64) -> chrono::DateTime<chrono::Utc> {
1884 let secs = n.div_euclid(1_000_000_000);
1885 let nanos = n.rem_euclid(1_000_000_000) as u32;
1886 use chrono::TimeZone;
1887 chrono::Utc
1888 .timestamp_opt(secs, nanos)
1889 .single()
1890 .unwrap_or_else(chrono::Utc::now)
1891}
1892
1893fn format_iso(n: i64) -> String {
1894 chrono_from_instant(n).to_rfc3339()
1895}
1896
1897enum TzArg {
1900 Utc,
1901 Local,
1902 Offset(i32),
1904 Iana(String),
1906}
1907
1908fn parse_tz_arg(v: Option<&Value>) -> Result<TzArg, String> {
1909 match v {
1910 Some(Value::Variant { name, args }) => match (name.as_str(), args.as_slice()) {
1911 ("Utc", []) => Ok(TzArg::Utc),
1912 ("Local", []) => Ok(TzArg::Local),
1913 ("Offset", [Value::Int(m)]) => {
1914 let m = i32::try_from(*m).map_err(|_| {
1915 format!("Tz::Offset: minutes out of range: {m}")
1916 })?;
1917 Ok(TzArg::Offset(m))
1918 }
1919 ("Iana", [Value::Str(s)]) => Ok(TzArg::Iana(s.clone())),
1920 (other, _) => Err(format!(
1921 "expected Tz variant (Utc | Local | Offset(Int) | Iana(Str)), got `{other}` with {} arg(s)",
1922 args.len()
1923 )),
1924 },
1925 Some(other) => Err(format!("expected Tz variant, got {other:?}")),
1926 None => Err("missing Tz argument".into()),
1927 }
1928}
1929
1930fn resolve_tz_to_components(n: i64, tz: &TzArg) -> Result<Value, String> {
1931 use chrono::{TimeZone, Datelike, Timelike, Offset};
1932 let utc_dt = chrono_from_instant(n);
1933 let (y, m, d, hh, mm, ss, ns, off_min) = match tz {
1934 TzArg::Utc => {
1935 let d = utc_dt;
1936 (d.year(), d.month() as i32, d.day() as i32,
1937 d.hour() as i32, d.minute() as i32, d.second() as i32,
1938 d.nanosecond() as i32, 0)
1939 }
1940 TzArg::Local => {
1941 let d = utc_dt.with_timezone(&chrono::Local);
1942 let off = d.offset().fix().local_minus_utc() / 60;
1943 (d.year(), d.month() as i32, d.day() as i32,
1944 d.hour() as i32, d.minute() as i32, d.second() as i32,
1945 d.nanosecond() as i32, off)
1946 }
1947 TzArg::Offset(off_min) => {
1948 let off_secs = off_min.saturating_mul(60);
1949 let fixed = chrono::FixedOffset::east_opt(off_secs)
1950 .ok_or("to_components: offset out of range")?;
1951 let d = utc_dt.with_timezone(&fixed);
1952 (d.year(), d.month() as i32, d.day() as i32,
1953 d.hour() as i32, d.minute() as i32, d.second() as i32,
1954 d.nanosecond() as i32, *off_min)
1955 }
1956 TzArg::Iana(name) => {
1957 let tz: chrono_tz::Tz = name.parse()
1958 .map_err(|e| format!("to_components: unknown timezone `{name}`: {e}"))?;
1959 let d = utc_dt.with_timezone(&tz);
1960 let off = d.offset().fix().local_minus_utc() / 60;
1961 (d.year(), d.month() as i32, d.day() as i32,
1962 d.hour() as i32, d.minute() as i32, d.second() as i32,
1963 d.nanosecond() as i32, off)
1964 }
1965 };
1966 let mut rec = indexmap::IndexMap::new();
1967 rec.insert("year".into(), Value::Int(y as i64));
1968 rec.insert("month".into(), Value::Int(m as i64));
1969 rec.insert("day".into(), Value::Int(d as i64));
1970 rec.insert("hour".into(), Value::Int(hh as i64));
1971 rec.insert("minute".into(), Value::Int(mm as i64));
1972 rec.insert("second".into(), Value::Int(ss as i64));
1973 rec.insert("nano".into(), Value::Int(ns as i64));
1974 rec.insert("tz_offset_minutes".into(), Value::Int(off_min as i64));
1975 let _ = chrono::Utc.timestamp_opt(0, 0); Ok(Value::Record(rec))
1977}
1978
1979
1980fn instant_from_components(rec: &indexmap::IndexMap<String, Value>) -> Result<i64, String> {
1981 use chrono::TimeZone;
1982 fn get_int(rec: &indexmap::IndexMap<String, Value>, k: &str) -> Result<i64, String> {
1983 match rec.get(k) {
1984 Some(Value::Int(n)) => Ok(*n),
1985 other => Err(format!("from_components: missing or non-int field `{k}`: {other:?}")),
1986 }
1987 }
1988 let y = get_int(rec, "year")? as i32;
1989 let m = get_int(rec, "month")? as u32;
1990 let d = get_int(rec, "day")? as u32;
1991 let hh = get_int(rec, "hour")? as u32;
1992 let mm = get_int(rec, "minute")? as u32;
1993 let ss = get_int(rec, "second")? as u32;
1994 let ns = get_int(rec, "nano")? as u32;
1995 let off_min = get_int(rec, "tz_offset_minutes")? as i32;
1996 let off = chrono::FixedOffset::east_opt(off_min * 60)
1997 .ok_or("from_components: offset out of range")?;
1998 let dt = off
1999 .with_ymd_and_hms(y, m, d, hh, mm, ss)
2000 .single()
2001 .ok_or("from_components: invalid or ambiguous date/time")?;
2002 let dt = dt + chrono::Duration::nanoseconds(ns as i64);
2003 Ok(instant_from_chrono(dt))
2004}