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; }
18 if (kind, op) == ("crypto", "random_str_hex") { return None; }
19 if (kind, op) == ("datetime", "now") { return None; }
22 if (kind, "send") == (kind, op) && kind == "http" { return None; }
25 if (kind, "get") == (kind, op) && kind == "http" { return None; }
26 if (kind, "post") == (kind, op) && kind == "http" { return None; }
27 Some(dispatch(kind, op, args))
28}
29
30pub fn is_pure_module(kind: &str) -> bool {
33 matches!(kind, "str" | "int" | "float" | "bool" | "list" | "iter"
34 | "option" | "result" | "tuple" | "json" | "bytes" | "flow" | "math"
35 | "map" | "set" | "crypto" | "regex" | "deque" | "datetime" | "duration" | "http"
36 | "toml" | "yaml" | "dotenv" | "csv" | "test" | "random" | "parser"
37 | "cli")
38}
39
40fn dispatch(kind: &str, op: &str, args: &[Value]) -> Result<Value, String> {
41 match (kind, op) {
42 ("str", "is_empty") => Ok(Value::Bool(expect_str(args.first())?.is_empty())),
44 ("str", "len") => Ok(Value::Int(expect_str(args.first())?.len() as i64)),
45 ("str", "concat") => {
46 let a = expect_str(args.first())?;
47 let b = expect_str(args.get(1))?;
48 Ok(Value::Str(format!("{a}{b}")))
49 }
50 ("str", "to_int") => {
51 let s = expect_str(args.first())?;
52 match s.parse::<i64>() {
53 Ok(n) => Ok(some(Value::Int(n))),
54 Err(_) => Ok(none()),
55 }
56 }
57 ("str", "split") => {
58 let s = expect_str(args.first())?;
59 let sep = expect_str(args.get(1))?;
60 let items: Vec<Value> = if sep.is_empty() {
61 s.chars().map(|c| Value::Str(c.to_string())).collect()
62 } else {
63 s.split(sep.as_str()).map(|p| Value::Str(p.to_string())).collect()
64 };
65 Ok(Value::List(items))
66 }
67 ("str", "join") => {
68 let parts = expect_list(args.first())?;
69 let sep = expect_str(args.get(1))?;
70 let mut out = String::new();
71 for (i, p) in parts.iter().enumerate() {
72 if i > 0 { out.push_str(&sep); }
73 match p {
74 Value::Str(s) => out.push_str(s),
75 other => return Err(format!("str.join element must be Str, got {other:?}")),
76 }
77 }
78 Ok(Value::Str(out))
79 }
80 ("str", "starts_with") => {
81 let s = expect_str(args.first())?;
82 let prefix = expect_str(args.get(1))?;
83 Ok(Value::Bool(s.starts_with(prefix.as_str())))
84 }
85 ("str", "ends_with") => {
86 let s = expect_str(args.first())?;
87 let suffix = expect_str(args.get(1))?;
88 Ok(Value::Bool(s.ends_with(suffix.as_str())))
89 }
90 ("str", "contains") => {
91 let s = expect_str(args.first())?;
92 let needle = expect_str(args.get(1))?;
93 Ok(Value::Bool(s.contains(needle.as_str())))
94 }
95 ("str", "replace") => {
96 let s = expect_str(args.first())?;
97 let from = expect_str(args.get(1))?;
98 let to = expect_str(args.get(2))?;
99 Ok(Value::Str(s.replace(from.as_str(), to.as_str())))
100 }
101 ("str", "trim") => Ok(Value::Str(expect_str(args.first())?.trim().to_string())),
102 ("str", "to_upper") => Ok(Value::Str(expect_str(args.first())?.to_uppercase())),
103 ("str", "to_lower") => Ok(Value::Str(expect_str(args.first())?.to_lowercase())),
104 ("str", "strip_prefix") => {
105 let s = expect_str(args.first())?;
106 let prefix = expect_str(args.get(1))?;
107 Ok(match s.strip_prefix(prefix.as_str()) {
108 Some(rest) => some(Value::Str(rest.to_string())),
109 None => none(),
110 })
111 }
112 ("str", "strip_suffix") => {
113 let s = expect_str(args.first())?;
114 let suffix = expect_str(args.get(1))?;
115 Ok(match s.strip_suffix(suffix.as_str()) {
116 Some(rest) => some(Value::Str(rest.to_string())),
117 None => none(),
118 })
119 }
120 ("str", "slice") => {
121 let s = expect_str(args.first())?;
131 let lo_i = expect_int(args.get(1))?;
132 let hi_i = expect_int(args.get(2))?;
133 let lo = (lo_i.max(0) as usize).min(s.len());
134 let hi = (hi_i.max(0) as usize).min(s.len());
135 if lo > hi {
136 return Err(format!(
137 "str.slice: reversed range [{lo}..{hi}] (after clamping to len {})",
138 s.len()));
139 }
140 if !s.is_char_boundary(lo) || !s.is_char_boundary(hi) {
141 return Err(format!("str.slice: [{lo}..{hi}] not on char boundaries"));
142 }
143 Ok(Value::Str(s[lo..hi].to_string()))
144 }
145
146 ("int", "to_str") => Ok(Value::Str(expect_int(args.first())?.to_string())),
148 ("int", "to_float") => Ok(Value::Float(expect_int(args.first())? as f64)),
149 ("float", "to_int") => Ok(Value::Int(expect_float(args.first())? as i64)),
150 ("float", "to_str") => Ok(Value::Str(expect_float(args.first())?.to_string())),
151 ("str", "to_float") => {
152 let s = expect_str(args.first())?;
153 match s.parse::<f64>() {
154 Ok(f) => Ok(some(Value::Float(f))),
155 Err(_) => Ok(none()),
156 }
157 }
158
159 ("list", "len") => Ok(Value::Int(expect_list(args.first())?.len() as i64)),
161 ("list", "is_empty") => Ok(Value::Bool(expect_list(args.first())?.is_empty())),
162 ("list", "head") => {
163 let xs = expect_list(args.first())?;
164 match xs.first() {
165 Some(v) => Ok(some(v.clone())),
166 None => Ok(none()),
167 }
168 }
169 ("list", "tail") => {
170 let xs = expect_list(args.first())?;
171 if xs.is_empty() { Ok(Value::List(Vec::new())) }
172 else { Ok(Value::List(xs[1..].to_vec())) }
173 }
174 ("list", "range") => {
175 let lo = expect_int(args.first())?;
176 let hi = expect_int(args.get(1))?;
177 Ok(Value::List((lo..hi).map(Value::Int).collect()))
178 }
179 ("list", "concat") => {
180 let mut out = expect_list(args.first())?.clone();
181 out.extend(expect_list(args.get(1))?.iter().cloned());
182 Ok(Value::List(out))
183 }
184 ("list", "reverse") => {
185 let mut out = expect_list(args.first())?.clone();
186 out.reverse();
187 Ok(Value::List(out))
188 }
189 ("list", "cons") => {
191 let head = args.first().cloned().unwrap_or(Value::Unit);
192 let mut out = vec![head];
193 out.extend(expect_list(args.get(1))?.iter().cloned());
194 Ok(Value::List(out))
195 }
196 ("list", "enumerate") => {
197 let xs = expect_list(args.first())?;
198 let pairs = xs.iter().cloned().enumerate()
199 .map(|(i, v)| Value::Tuple(vec![Value::Int(i as i64), v]))
200 .collect::<Vec<_>>();
201 Ok(Value::List(pairs))
202 }
203
204 ("tuple", "fst") => tuple_index(first_arg(args)?, 0),
209 ("tuple", "snd") => tuple_index(first_arg(args)?, 1),
210 ("tuple", "third") => tuple_index(first_arg(args)?, 2),
211 ("tuple", "len") => match first_arg(args)? {
212 Value::Tuple(items) => Ok(Value::Int(items.len() as i64)),
213 other => Err(format!("tuple.len: expected Tuple, got {other:?}")),
214 },
215
216 ("option", "unwrap_or") => {
218 let opt = first_arg(args)?;
219 let default = args.get(1).cloned().unwrap_or(Value::Unit);
220 match opt {
221 Value::Variant { name, args } if name == "Some" && !args.is_empty() => Ok(args[0].clone()),
222 Value::Variant { name, .. } if name == "None" => Ok(default),
223 other => Err(format!("option.unwrap_or expected Option, got {other:?}")),
224 }
225 }
226 ("option", "unwrap_or_else") => {
231 let opt = first_arg(args)?;
232 match opt {
233 Value::Variant { name, args } if name == "Some" && !args.is_empty() => Ok(args[0].clone()),
234 Value::Variant { name, .. } if name == "None" => {
235 Ok(args.get(1).cloned().unwrap_or(Value::Unit))
239 }
240 other => Err(format!("option.unwrap_or_else expected Option, got {other:?}")),
241 }
242 }
243 ("option", "is_some") => match first_arg(args)? {
244 Value::Variant { name, .. } => Ok(Value::Bool(name == "Some")),
245 other => Err(format!("option.is_some expected Option, got {other:?}")),
246 },
247 ("option", "is_none") => match first_arg(args)? {
248 Value::Variant { name, .. } => Ok(Value::Bool(name == "None")),
249 other => Err(format!("option.is_none expected Option, got {other:?}")),
250 },
251
252 ("result", "is_ok") => match first_arg(args)? {
254 Value::Variant { name, .. } => Ok(Value::Bool(name == "Ok")),
255 other => Err(format!("result.is_ok expected Result, got {other:?}")),
256 },
257 ("result", "is_err") => match first_arg(args)? {
258 Value::Variant { name, .. } => Ok(Value::Bool(name == "Err")),
259 other => Err(format!("result.is_err expected Result, got {other:?}")),
260 },
261 ("result", "unwrap_or") => {
262 let res = first_arg(args)?;
263 let default = args.get(1).cloned().unwrap_or(Value::Unit);
264 match res {
265 Value::Variant { name, args } if name == "Ok" && !args.is_empty() => Ok(args[0].clone()),
266 Value::Variant { name, .. } if name == "Err" => Ok(default),
267 other => Err(format!("result.unwrap_or expected Result, got {other:?}")),
268 }
269 }
270
271 ("json", "stringify") => {
273 let v = first_arg(args)?;
274 Ok(Value::Str(serde_json::to_string(&value_to_json(v)).unwrap_or_default()))
275 }
276 ("json", "parse") => {
277 let s = expect_str(args.first())?;
278 match serde_json::from_str::<serde_json::Value>(&s) {
279 Ok(v) => Ok(ok_v(json_to_value(&v))),
280 Err(e) => Ok(err_v(Value::Str(format!("{e}")))),
281 }
282 }
283 ("json", "parse_strict") => {
286 let s = expect_str(args.first())?;
287 let required = required_field_names(args.get(1))?;
288 let schema = extract_type_schema(args.get(2));
289 match serde_json::from_str::<serde_json::Value>(&s) {
290 Ok(v) => {
291 if let Err(e) = check_required_fields(&v, &required) {
292 return Ok(err_v(Value::Str(e)));
293 }
294 if let Err(e) = validate_field_types(&v, &schema) {
295 return Ok(err_v(Value::Str(e)));
296 }
297 Ok(ok_v(json_to_value(&v)))
298 }
299 Err(e) => Ok(err_v(Value::Str(format!("{e}")))),
300 }
301 }
302
303 ("toml", "parse") => {
308 let s = expect_str(args.first())?;
309 match toml::from_str::<serde_json::Value>(&s) {
310 Ok(mut v) => {
311 unwrap_toml_datetime_markers(&mut v);
312 Ok(ok_v(json_to_value(&v)))
313 }
314 Err(e) => Ok(err_v(Value::Str(format!("{e}")))),
315 }
316 }
317 ("json", "parse_strict_typed") => {
321 let s = expect_str(args.first())?;
322 let required = required_field_names(args.get(1))?;
323 let schema = extract_type_schema(args.get(2));
324 match serde_json::from_str::<serde_json::Value>(&s) {
325 Ok(v) => {
326 if let Err(e) = check_required_fields(&v, &required) {
327 return Ok(err_v(Value::Str(e)));
328 }
329 if let Err(e) = validate_field_types(&v, &schema) {
330 return Ok(err_v(Value::Str(e)));
331 }
332 Ok(ok_v(json_to_value(&v)))
333 }
334 Err(e) => Ok(err_v(Value::Str(format!("{e}")))),
335 }
336 }
337
338 ("toml", "parse_strict") => {
341 let s = expect_str(args.first())?;
342 let required = required_field_names(args.get(1))?;
343 let schema = extract_type_schema(args.get(2));
344 match toml::from_str::<serde_json::Value>(&s) {
345 Ok(mut v) => {
346 unwrap_toml_datetime_markers(&mut v);
347 if let Err(e) = check_required_fields(&v, &required) {
348 return Ok(err_v(Value::Str(e)));
349 }
350 if let Err(e) = validate_field_types(&v, &schema) {
351 return Ok(err_v(Value::Str(e)));
352 }
353 Ok(ok_v(json_to_value(&v)))
354 }
355 Err(e) => Ok(err_v(Value::Str(format!("{e}")))),
356 }
357 }
358 ("toml", "parse_strict_typed") => {
359 let s = expect_str(args.first())?;
360 let required = required_field_names(args.get(1))?;
361 let schema = extract_type_schema(args.get(2));
362 match toml::from_str::<serde_json::Value>(&s) {
363 Ok(mut v) => {
364 unwrap_toml_datetime_markers(&mut v);
365 if let Err(e) = check_required_fields(&v, &required) {
366 return Ok(err_v(Value::Str(e)));
367 }
368 if let Err(e) = validate_field_types(&v, &schema) {
369 return Ok(err_v(Value::Str(e)));
370 }
371 Ok(ok_v(json_to_value(&v)))
372 }
373 Err(e) => Ok(err_v(Value::Str(format!("{e}")))),
374 }
375 }
376 ("toml", "stringify") => {
377 let v = first_arg(args)?;
378 let json = value_to_json(v);
384 match toml::to_string(&json) {
385 Ok(s) => Ok(ok_v(Value::Str(s))),
386 Err(e) => Ok(err_v(Value::Str(format!("toml.stringify: {e}")))),
387 }
388 }
389
390 ("yaml", "parse") => {
396 let s = expect_str(args.first())?;
397 match serde_yaml::from_str::<serde_json::Value>(&s) {
398 Ok(v) => Ok(ok_v(json_to_value(&v))),
399 Err(e) => Ok(err_v(Value::Str(format!("{e}")))),
400 }
401 }
402 ("yaml", "parse_strict") => {
405 let s = expect_str(args.first())?;
406 let required = required_field_names(args.get(1))?;
407 let schema = extract_type_schema(args.get(2));
408 match serde_yaml::from_str::<serde_json::Value>(&s) {
409 Ok(v) => {
410 if let Err(e) = check_required_fields(&v, &required) {
411 return Ok(err_v(Value::Str(e)));
412 }
413 if let Err(e) = validate_field_types(&v, &schema) {
414 return Ok(err_v(Value::Str(e)));
415 }
416 Ok(ok_v(json_to_value(&v)))
417 }
418 Err(e) => Ok(err_v(Value::Str(format!("{e}")))),
419 }
420 }
421 ("yaml", "parse_strict_typed") => {
422 let s = expect_str(args.first())?;
423 let required = required_field_names(args.get(1))?;
424 let schema = extract_type_schema(args.get(2));
425 match serde_yaml::from_str::<serde_json::Value>(&s) {
426 Ok(v) => {
427 if let Err(e) = check_required_fields(&v, &required) {
428 return Ok(err_v(Value::Str(e)));
429 }
430 if let Err(e) = validate_field_types(&v, &schema) {
431 return Ok(err_v(Value::Str(e)));
432 }
433 Ok(ok_v(json_to_value(&v)))
434 }
435 Err(e) => Ok(err_v(Value::Str(format!("{e}")))),
436 }
437 }
438 ("yaml", "stringify") => {
439 let v = first_arg(args)?;
440 let json = value_to_json(v);
441 match serde_yaml::to_string(&json) {
442 Ok(s) => Ok(ok_v(Value::Str(s))),
443 Err(e) => Ok(err_v(Value::Str(format!("yaml.stringify: {e}")))),
444 }
445 }
446
447 ("dotenv", "parse") => {
455 use std::collections::BTreeMap;
456 use lex_bytecode::MapKey;
457 let s = expect_str(args.first())?;
458 match parse_dotenv(&s) {
459 Ok(map) => {
460 let mut bt: BTreeMap<MapKey, Value> = BTreeMap::new();
461 for (k, v) in map {
462 bt.insert(MapKey::Str(k), Value::Str(v));
463 }
464 Ok(ok_v(Value::Map(bt)))
465 }
466 Err(e) => Ok(err_v(Value::Str(e))),
467 }
468 }
469
470 ("csv", "parse") => {
475 let s = expect_str(args.first())?;
476 let mut rdr = csv::ReaderBuilder::new()
477 .has_headers(false)
478 .flexible(true)
479 .from_reader(s.as_bytes());
480 let mut rows: Vec<Value> = Vec::new();
481 for r in rdr.records() {
482 match r {
483 Ok(rec) => {
484 let row: Vec<Value> = rec.iter()
485 .map(|f| Value::Str(f.to_string()))
486 .collect();
487 rows.push(Value::List(row));
488 }
489 Err(e) => return Ok(err_v(Value::Str(format!("csv.parse: {e}")))),
490 }
491 }
492 Ok(ok_v(Value::List(rows)))
493 }
494 ("csv", "stringify") => {
495 let v = first_arg(args)?;
500 let rows = match v {
501 Value::List(rs) => rs,
502 _ => return Ok(err_v(Value::Str("csv.stringify expects List[List[Str]]".into()))),
503 };
504 let mut out = Vec::new();
505 {
506 let mut wtr = csv::WriterBuilder::new()
507 .has_headers(false)
508 .from_writer(&mut out);
509 for row in rows {
510 let cells = match row {
511 Value::List(cs) => cs,
512 _ => return Ok(err_v(Value::Str("csv.stringify row must be List[Str]".into()))),
513 };
514 let strs: Vec<String> = cells.iter().map(|c| match c {
515 Value::Str(s) => s.clone(),
516 other => serde_json::to_string(&other.to_json())
517 .unwrap_or_else(|_| String::new()),
518 }).collect();
519 if let Err(e) = wtr.write_record(&strs) {
520 return Ok(err_v(Value::Str(format!("csv.stringify: {e}"))));
521 }
522 }
523 if let Err(e) = wtr.flush() {
524 return Ok(err_v(Value::Str(format!("csv.stringify flush: {e}"))));
525 }
526 }
527 match String::from_utf8(out) {
528 Ok(s) => Ok(ok_v(Value::Str(s))),
529 Err(e) => Ok(err_v(Value::Str(format!("csv.stringify utf8: {e}")))),
530 }
531 }
532
533 ("test", "assert_eq") => {
540 let a = first_arg(args)?;
541 let b = args.get(1).ok_or("test.assert_eq: missing second arg")?;
542 if a == b {
543 Ok(ok_v(Value::Unit))
544 } else {
545 Ok(err_v(Value::Str(format!("assert_eq: lhs {} != rhs {}",
546 value_to_json(a), value_to_json(b)))))
547 }
548 }
549 ("test", "assert_ne") => {
550 let a = first_arg(args)?;
551 let b = args.get(1).ok_or("test.assert_ne: missing second arg")?;
552 if a != b {
553 Ok(ok_v(Value::Unit))
554 } else {
555 Ok(err_v(Value::Str(format!("assert_ne: both sides are {}",
556 value_to_json(a)))))
557 }
558 }
559 ("test", "assert_true") => {
560 match first_arg(args)? {
561 Value::Bool(true) => Ok(ok_v(Value::Unit)),
562 Value::Bool(false) => Ok(err_v(Value::Str("assert_true: was false".into()))),
563 other => Err(format!("test.assert_true expects Bool, got {other:?}")),
564 }
565 }
566 ("test", "assert_false") => {
567 match first_arg(args)? {
568 Value::Bool(false) => Ok(ok_v(Value::Unit)),
569 Value::Bool(true) => Ok(err_v(Value::Str("assert_false: was true".into()))),
570 other => Err(format!("test.assert_false expects Bool, got {other:?}")),
571 }
572 }
573
574 ("bytes", "len") => {
576 let b = expect_bytes(args.first())?;
577 Ok(Value::Int(b.len() as i64))
578 }
579 ("bytes", "eq") => {
580 let a = expect_bytes(args.first())?;
581 let b = expect_bytes(args.get(1))?;
582 Ok(Value::Bool(a == b))
583 }
584 ("bytes", "from_str") => {
585 let s = expect_str(args.first())?;
586 Ok(Value::Bytes(s.into_bytes()))
587 }
588 ("bytes", "to_str") => {
589 let b = expect_bytes(args.first())?;
590 match String::from_utf8(b.to_vec()) {
591 Ok(s) => Ok(ok_v(Value::Str(s))),
592 Err(e) => Ok(err_v(Value::Str(format!("{e}")))),
593 }
594 }
595 ("bytes", "slice") => {
596 let b = expect_bytes(args.first())?;
597 let lo = expect_int(args.get(1))? as usize;
598 let hi = expect_int(args.get(2))? as usize;
599 if lo > hi || hi > b.len() {
600 return Err(format!("bytes.slice: out of range [{lo}..{hi}] of {}", b.len()));
601 }
602 Ok(Value::Bytes(b[lo..hi].to_vec()))
603 }
604 ("bytes", "is_empty") => {
605 let b = expect_bytes(args.first())?;
606 Ok(Value::Bool(b.is_empty()))
607 }
608
609 ("math", "exp") => Ok(Value::Float(expect_float(args.first())?.exp())),
616 ("math", "log") => Ok(Value::Float(expect_float(args.first())?.ln())),
617 ("math", "log2") => Ok(Value::Float(expect_float(args.first())?.log2())),
618 ("math", "log10") => Ok(Value::Float(expect_float(args.first())?.log10())),
619 ("math", "sqrt") => Ok(Value::Float(expect_float(args.first())?.sqrt())),
620 ("math", "abs") => Ok(Value::Float(expect_float(args.first())?.abs())),
621 ("math", "sin") => Ok(Value::Float(expect_float(args.first())?.sin())),
622 ("math", "cos") => Ok(Value::Float(expect_float(args.first())?.cos())),
623 ("math", "tan") => Ok(Value::Float(expect_float(args.first())?.tan())),
624 ("math", "asin") => Ok(Value::Float(expect_float(args.first())?.asin())),
625 ("math", "acos") => Ok(Value::Float(expect_float(args.first())?.acos())),
626 ("math", "atan") => Ok(Value::Float(expect_float(args.first())?.atan())),
627 ("math", "floor") => Ok(Value::Float(expect_float(args.first())?.floor())),
628 ("math", "ceil") => Ok(Value::Float(expect_float(args.first())?.ceil())),
629 ("math", "round") => Ok(Value::Float(expect_float(args.first())?.round())),
630 ("math", "trunc") => Ok(Value::Float(expect_float(args.first())?.trunc())),
631 ("math", "pow") => {
632 let a = expect_float(args.first())?;
633 let b = expect_float(args.get(1))?;
634 Ok(Value::Float(a.powf(b)))
635 }
636 ("math", "atan2") => {
637 let y = expect_float(args.first())?;
638 let x = expect_float(args.get(1))?;
639 Ok(Value::Float(y.atan2(x)))
640 }
641 ("math", "min") => {
642 let a = expect_float(args.first())?;
643 let b = expect_float(args.get(1))?;
644 Ok(Value::Float(a.min(b)))
645 }
646 ("math", "max") => {
647 let a = expect_float(args.first())?;
648 let b = expect_float(args.get(1))?;
649 Ok(Value::Float(a.max(b)))
650 }
651 ("math", "zeros") => {
652 let r = expect_int(args.first())?;
653 let c = expect_int(args.get(1))?;
654 if r < 0 || c < 0 {
655 return Err(format!("math.zeros: negative dim {r}x{c}"));
656 }
657 let r = r as usize; let c = c as usize;
658 Ok(Value::F64Array { rows: r as u32, cols: c as u32, data: vec![0.0; r * c] })
659 }
660 ("math", "ones") => {
661 let r = expect_int(args.first())?;
662 let c = expect_int(args.get(1))?;
663 if r < 0 || c < 0 {
664 return Err(format!("math.ones: negative dim {r}x{c}"));
665 }
666 let r = r as usize; let c = c as usize;
667 Ok(Value::F64Array { rows: r as u32, cols: c as u32, data: vec![1.0; r * c] })
668 }
669 ("math", "from_lists") => {
670 let rows = expect_list(args.first())?;
671 let r = rows.len();
672 if r == 0 {
673 return Ok(Value::F64Array { rows: 0, cols: 0, data: Vec::new() });
674 }
675 let first_row = match &rows[0] {
676 Value::List(xs) => xs,
677 other => return Err(format!("math.from_lists: row 0 not List, got {other:?}")),
678 };
679 let c = first_row.len();
680 let mut data = Vec::with_capacity(r * c);
681 for (i, row) in rows.iter().enumerate() {
682 let row = match row {
683 Value::List(xs) => xs,
684 other => return Err(format!("math.from_lists: row {i} not List, got {other:?}")),
685 };
686 if row.len() != c {
687 return Err(format!("math.from_lists: row {i} has {} cols, expected {c}", row.len()));
688 }
689 for (j, v) in row.iter().enumerate() {
690 let f = match v {
691 Value::Float(f) => *f,
692 Value::Int(n) => *n as f64,
693 other => return Err(format!("math.from_lists: ({i},{j}) not numeric, got {other:?}")),
694 };
695 data.push(f);
696 }
697 }
698 Ok(Value::F64Array { rows: r as u32, cols: c as u32, data })
699 }
700 ("math", "from_flat") => {
701 let r = expect_int(args.first())?;
702 let c = expect_int(args.get(1))?;
703 let xs = expect_list(args.get(2))?;
704 if r < 0 || c < 0 {
705 return Err(format!("math.from_flat: negative dim {r}x{c}"));
706 }
707 let r = r as usize; let c = c as usize;
708 if xs.len() != r * c {
709 return Err(format!("math.from_flat: list len {} != {}*{}", xs.len(), r, c));
710 }
711 let mut data = Vec::with_capacity(r * c);
712 for v in xs {
713 data.push(match v {
714 Value::Float(f) => *f,
715 Value::Int(n) => *n as f64,
716 other => return Err(format!("math.from_flat: non-numeric element {other:?}")),
717 });
718 }
719 Ok(Value::F64Array { rows: r as u32, cols: c as u32, data })
720 }
721 ("math", "rows") => {
722 let (r, _, _) = unpack_matrix(first_arg(args)?)?;
723 Ok(Value::Int(r as i64))
724 }
725 ("math", "cols") => {
726 let (_, c, _) = unpack_matrix(first_arg(args)?)?;
727 Ok(Value::Int(c as i64))
728 }
729 ("math", "get") => {
730 let (r, c, data) = unpack_matrix(first_arg(args)?)?;
731 let i = expect_int(args.get(1))? as usize;
732 let j = expect_int(args.get(2))? as usize;
733 if i >= r || j >= c {
734 return Err(format!("math.get: ({i},{j}) out of {r}x{c}"));
735 }
736 Ok(Value::Float(data[i * c + j]))
737 }
738 ("math", "to_flat") => {
739 let (_, _, data) = unpack_matrix(first_arg(args)?)?;
740 Ok(Value::List(data.into_iter().map(Value::Float).collect()))
741 }
742 ("math", "transpose") => {
743 let (r, c, data) = unpack_matrix(first_arg(args)?)?;
744 let mut out = vec![0.0; r * c];
745 for i in 0..r {
746 for j in 0..c {
747 out[j * r + i] = data[i * c + j];
748 }
749 }
750 Ok(Value::F64Array { rows: c as u32, cols: r as u32, data: out })
751 }
752 ("math", "matmul") => {
753 let (m, k1, a) = unpack_matrix(first_arg(args)?)?;
754 let (k2, n, b) = unpack_matrix(args.get(1).ok_or("math.matmul: missing arg 1")?)?;
755 if k1 != k2 {
756 return Err(format!("math.matmul: dim mismatch {m}x{k1} · {k2}x{n}"));
757 }
758 let mut c = vec![0.0; m * n];
762 for i in 0..m {
763 for kk in 0..k1 {
764 let aik = a[i * k1 + kk];
765 for j in 0..n {
766 c[i * n + j] += aik * b[kk * n + j];
767 }
768 }
769 }
770 Ok(Value::F64Array { rows: m as u32, cols: n as u32, data: c })
771 }
772 ("math", "scale") => {
773 let s = expect_float(args.first())?;
774 let (r, c, mut data) = unpack_matrix(args.get(1).ok_or("math.scale: missing arg 1")?)?;
775 for x in &mut data { *x *= s; }
776 Ok(Value::F64Array { rows: r as u32, cols: c as u32, data })
777 }
778 ("math", "add") | ("math", "sub") => {
779 let (ar, ac, a) = unpack_matrix(first_arg(args)?)?;
780 let (br, bc, b) = unpack_matrix(args.get(1).ok_or("math.add/sub: missing arg 1")?)?;
781 if ar != br || ac != bc {
782 return Err(format!("math.{op}: shape mismatch {ar}x{ac} vs {br}x{bc}"));
783 }
784 let neg = op == "sub";
785 let mut out = a;
786 for (i, x) in out.iter_mut().enumerate() {
787 if neg { *x -= b[i] } else { *x += b[i] }
788 }
789 Ok(Value::F64Array { rows: ar as u32, cols: ac as u32, data: out })
790 }
791 ("math", "sigmoid") => {
792 let (r, c, mut data) = unpack_matrix(first_arg(args)?)?;
793 for x in &mut data { *x = 1.0 / (1.0 + (-*x).exp()); }
794 Ok(Value::F64Array { rows: r as u32, cols: c as u32, data })
795 }
796
797 ("map", "new") => Ok(Value::Map(BTreeMap::new())),
799 ("map", "size") => Ok(Value::Int(expect_map(args.first())?.len() as i64)),
800 ("map", "has") => {
801 let m = expect_map(args.first())?;
802 let k = MapKey::from_value(args.get(1).ok_or("map.has: missing key")?)?;
803 Ok(Value::Bool(m.contains_key(&k)))
804 }
805 ("map", "get") => {
806 let m = expect_map(args.first())?;
807 let k = MapKey::from_value(args.get(1).ok_or("map.get: missing key")?)?;
808 Ok(match m.get(&k) {
809 Some(v) => some(v.clone()),
810 None => none(),
811 })
812 }
813 ("map", "set") => {
814 let mut m = expect_map(args.first())?.clone();
815 let k = MapKey::from_value(args.get(1).ok_or("map.set: missing key")?)?;
816 let v = args.get(2).ok_or("map.set: missing value")?.clone();
817 m.insert(k, v);
818 Ok(Value::Map(m))
819 }
820 ("map", "delete") => {
821 let mut m = expect_map(args.first())?.clone();
822 let k = MapKey::from_value(args.get(1).ok_or("map.delete: missing key")?)?;
823 m.remove(&k);
824 Ok(Value::Map(m))
825 }
826 ("map", "keys") => {
827 let m = expect_map(args.first())?;
828 Ok(Value::List(m.keys().cloned().map(MapKey::into_value).collect()))
829 }
830 ("map", "values") => {
831 let m = expect_map(args.first())?;
832 Ok(Value::List(m.values().cloned().collect()))
833 }
834 ("map", "entries") => {
835 let m = expect_map(args.first())?;
836 Ok(Value::List(m.iter()
837 .map(|(k, v)| Value::Tuple(vec![k.as_value(), v.clone()]))
838 .collect()))
839 }
840 ("map", "from_list") => {
841 let pairs = expect_list(args.first())?;
842 let mut m = BTreeMap::new();
843 for p in pairs {
844 let items = match p {
845 Value::Tuple(items) if items.len() == 2 => items,
846 other => return Err(format!(
847 "map.from_list element must be a 2-tuple, got {other:?}")),
848 };
849 let k = MapKey::from_value(&items[0])?;
850 m.insert(k, items[1].clone());
851 }
852 Ok(Value::Map(m))
853 }
854
855 ("set", "new") => Ok(Value::Set(BTreeSet::new())),
857 ("set", "size") => Ok(Value::Int(expect_set(args.first())?.len() as i64)),
858 ("set", "has") => {
859 let s = expect_set(args.first())?;
860 let k = MapKey::from_value(args.get(1).ok_or("set.has: missing element")?)?;
861 Ok(Value::Bool(s.contains(&k)))
862 }
863 ("set", "add") => {
864 let mut s = expect_set(args.first())?.clone();
865 let k = MapKey::from_value(args.get(1).ok_or("set.add: missing element")?)?;
866 s.insert(k);
867 Ok(Value::Set(s))
868 }
869 ("set", "delete") => {
870 let mut s = expect_set(args.first())?.clone();
871 let k = MapKey::from_value(args.get(1).ok_or("set.delete: missing element")?)?;
872 s.remove(&k);
873 Ok(Value::Set(s))
874 }
875 ("set", "to_list") => {
876 let s = expect_set(args.first())?;
877 Ok(Value::List(s.iter().cloned().map(MapKey::into_value).collect()))
878 }
879 ("set", "from_list") => {
880 let xs = expect_list(args.first())?;
881 let mut s = BTreeSet::new();
882 for x in xs {
883 s.insert(MapKey::from_value(x)?);
884 }
885 Ok(Value::Set(s))
886 }
887 ("set", "union") => {
888 let a = expect_set(args.first())?;
889 let b = expect_set(args.get(1))?;
890 Ok(Value::Set(a.union(b).cloned().collect()))
891 }
892 ("set", "intersect") => {
893 let a = expect_set(args.first())?;
894 let b = expect_set(args.get(1))?;
895 Ok(Value::Set(a.intersection(b).cloned().collect()))
896 }
897 ("set", "diff") => {
898 let a = expect_set(args.first())?;
899 let b = expect_set(args.get(1))?;
900 Ok(Value::Set(a.difference(b).cloned().collect()))
901 }
902 ("set", "is_empty") => Ok(Value::Bool(expect_set(args.first())?.is_empty())),
903 ("set", "is_subset") => {
904 let a = expect_set(args.first())?;
905 let b = expect_set(args.get(1))?;
906 Ok(Value::Bool(a.is_subset(b)))
907 }
908
909 ("map", "merge") => {
911 let a = expect_map(args.first())?.clone();
914 let b = expect_map(args.get(1))?;
915 let mut out = a;
916 for (k, v) in b {
917 out.insert(k.clone(), v.clone());
918 }
919 Ok(Value::Map(out))
920 }
921 ("map", "is_empty") => Ok(Value::Bool(expect_map(args.first())?.is_empty())),
922
923 ("deque", "new") => Ok(Value::Deque(std::collections::VecDeque::new())),
925 ("deque", "size") => Ok(Value::Int(expect_deque(args.first())?.len() as i64)),
926 ("deque", "is_empty") => Ok(Value::Bool(expect_deque(args.first())?.is_empty())),
927 ("deque", "push_back") => {
928 let mut d = expect_deque(args.first())?.clone();
929 let x = args.get(1).ok_or("deque.push_back: missing value")?.clone();
930 d.push_back(x);
931 Ok(Value::Deque(d))
932 }
933 ("deque", "push_front") => {
934 let mut d = expect_deque(args.first())?.clone();
935 let x = args.get(1).ok_or("deque.push_front: missing value")?.clone();
936 d.push_front(x);
937 Ok(Value::Deque(d))
938 }
939 ("deque", "pop_back") => {
940 let mut d = expect_deque(args.first())?.clone();
941 match d.pop_back() {
942 Some(x) => Ok(Value::Variant {
943 name: "Some".into(),
944 args: vec![Value::Tuple(vec![x, Value::Deque(d)])],
945 }),
946 None => Ok(Value::Variant { name: "None".into(), args: vec![] }),
947 }
948 }
949 ("deque", "pop_front") => {
950 let mut d = expect_deque(args.first())?.clone();
951 match d.pop_front() {
952 Some(x) => Ok(Value::Variant {
953 name: "Some".into(),
954 args: vec![Value::Tuple(vec![x, Value::Deque(d)])],
955 }),
956 None => Ok(Value::Variant { name: "None".into(), args: vec![] }),
957 }
958 }
959 ("deque", "peek_back") => {
960 let d = expect_deque(args.first())?;
961 match d.back() {
962 Some(x) => Ok(Value::Variant {
963 name: "Some".into(),
964 args: vec![x.clone()],
965 }),
966 None => Ok(Value::Variant { name: "None".into(), args: vec![] }),
967 }
968 }
969 ("deque", "peek_front") => {
970 let d = expect_deque(args.first())?;
971 match d.front() {
972 Some(x) => Ok(Value::Variant {
973 name: "Some".into(),
974 args: vec![x.clone()],
975 }),
976 None => Ok(Value::Variant { name: "None".into(), args: vec![] }),
977 }
978 }
979 ("deque", "from_list") => {
980 let xs = expect_list(args.first())?;
981 Ok(Value::Deque(xs.iter().cloned().collect()))
982 }
983 ("deque", "to_list") => {
984 let d = expect_deque(args.first())?;
985 Ok(Value::List(d.iter().cloned().collect()))
986 }
987
988 ("crypto", "sha256") => {
991 use sha2::{Digest, Sha256};
992 let data = expect_bytes(args.first())?;
993 let mut h = Sha256::new();
994 h.update(data);
995 Ok(Value::Bytes(h.finalize().to_vec()))
996 }
997 ("crypto", "sha512") => {
998 use sha2::{Digest, Sha512};
999 let data = expect_bytes(args.first())?;
1000 let mut h = Sha512::new();
1001 h.update(data);
1002 Ok(Value::Bytes(h.finalize().to_vec()))
1003 }
1004 ("crypto", "md5") => {
1005 use md5::{Digest, Md5};
1006 let data = expect_bytes(args.first())?;
1007 let mut h = Md5::new();
1008 h.update(data);
1009 Ok(Value::Bytes(h.finalize().to_vec()))
1010 }
1011 ("crypto", "blake2b") => {
1015 use blake2::{Blake2b512, Digest};
1016 let data = expect_bytes(args.first())?;
1017 let mut h = Blake2b512::new();
1018 h.update(data);
1019 Ok(Value::Bytes(h.finalize().to_vec()))
1020 }
1021 ("crypto", "sha256_str") => {
1025 use sha2::{Digest, Sha256};
1026 let s = expect_str(args.first())?;
1027 let mut h = Sha256::new();
1028 h.update(s.as_bytes());
1029 Ok(Value::Str(hex::encode(h.finalize())))
1030 }
1031 ("crypto", "sha512_str") => {
1032 use sha2::{Digest, Sha512};
1033 let s = expect_str(args.first())?;
1034 let mut h = Sha512::new();
1035 h.update(s.as_bytes());
1036 Ok(Value::Str(hex::encode(h.finalize())))
1037 }
1038 ("crypto", "hmac_sha256") => {
1039 use hmac::{Hmac, KeyInit, Mac};
1040 type HmacSha256 = Hmac<sha2::Sha256>;
1041 let key = expect_bytes(args.first())?;
1042 let data = expect_bytes(args.get(1))?;
1043 let mut mac = HmacSha256::new_from_slice(key)
1044 .map_err(|e| format!("hmac_sha256 key: {e}"))?;
1045 mac.update(data);
1046 Ok(Value::Bytes(mac.finalize().into_bytes().to_vec()))
1047 }
1048 ("crypto", "hmac_sha512") => {
1049 use hmac::{Hmac, KeyInit, Mac};
1050 type HmacSha512 = Hmac<sha2::Sha512>;
1051 let key = expect_bytes(args.first())?;
1052 let data = expect_bytes(args.get(1))?;
1053 let mut mac = HmacSha512::new_from_slice(key)
1054 .map_err(|e| format!("hmac_sha512 key: {e}"))?;
1055 mac.update(data);
1056 Ok(Value::Bytes(mac.finalize().into_bytes().to_vec()))
1057 }
1058 ("crypto", "base64_encode") => {
1059 use base64::{Engine, engine::general_purpose::STANDARD};
1060 let data = expect_bytes(args.first())?;
1061 Ok(Value::Str(STANDARD.encode(data)))
1062 }
1063 ("crypto", "base64_decode") => {
1064 use base64::{Engine, engine::general_purpose::STANDARD};
1065 let s = expect_str(args.first())?;
1066 match STANDARD.decode(s) {
1067 Ok(b) => Ok(ok_v(Value::Bytes(b))),
1068 Err(e) => Ok(err_v(Value::Str(format!("base64: {e}")))),
1069 }
1070 }
1071 ("crypto", "base64url_encode") => {
1075 use base64::{Engine, engine::general_purpose::URL_SAFE_NO_PAD};
1076 let data = expect_bytes(args.first())?;
1077 Ok(Value::Str(URL_SAFE_NO_PAD.encode(data)))
1078 }
1079 ("crypto", "base64url_decode") => {
1080 use base64::{Engine, engine::general_purpose::URL_SAFE_NO_PAD};
1081 let s = expect_str(args.first())?;
1082 match URL_SAFE_NO_PAD.decode(s) {
1083 Ok(b) => Ok(ok_v(Value::Bytes(b))),
1084 Err(e) => Ok(err_v(Value::Str(format!("base64url: {e}")))),
1085 }
1086 }
1087 ("crypto", "hex_encode") => {
1088 let data = expect_bytes(args.first())?;
1089 Ok(Value::Str(hex::encode(data)))
1090 }
1091 ("crypto", "hex_decode") => {
1092 let s = expect_str(args.first())?;
1093 match hex::decode(s) {
1094 Ok(b) => Ok(ok_v(Value::Bytes(b))),
1095 Err(e) => Ok(err_v(Value::Str(format!("hex: {e}")))),
1096 }
1097 }
1098 ("crypto", "constant_time_eq") | ("crypto", "eq") => {
1099 use subtle::ConstantTimeEq;
1100 let a = expect_bytes(args.first())?;
1101 let b = expect_bytes(args.get(1))?;
1102 let eq = if a.len() == b.len() {
1111 a.ct_eq(b).into()
1112 } else {
1113 false
1114 };
1115 Ok(Value::Bool(eq))
1116 }
1117 ("crypto", "eq_str") => {
1120 use subtle::ConstantTimeEq;
1121 let a = expect_str(args.first())?;
1122 let b = expect_str(args.get(1))?;
1123 let eq = if a.len() == b.len() {
1124 a.as_bytes().ct_eq(b.as_bytes()).into()
1125 } else {
1126 false
1127 };
1128 Ok(Value::Bool(eq))
1129 }
1130
1131 ("crypto", "aes_gcm_seal") => Ok(aes_gcm_seal_impl(args)),
1141 ("crypto", "aes_gcm_open") => Ok(aes_gcm_open_impl(args)),
1142 ("crypto", "chacha20_poly1305_seal") => Ok(chacha20_seal_impl(args)),
1143 ("crypto", "chacha20_poly1305_open") => Ok(chacha20_open_impl(args)),
1144 ("crypto", "pbkdf2_sha256") => Ok(pbkdf2_sha256_impl(args)),
1145 ("crypto", "hkdf_sha256") => Ok(hkdf_sha256_impl(args)),
1146 ("crypto", "argon2id") => Ok(argon2id_impl(args)),
1147
1148 ("random", "seed") => {
1155 let s = args.first().ok_or("random.seed: missing arg")?.as_int();
1156 let mixed = splitmix64(s as u64).0;
1161 Ok(rng_value(mixed))
1162 }
1163 ("random", "int") => {
1164 let state = rng_decode(args.first())?;
1165 let lo = args.get(1).ok_or("random.int: missing lo")?.as_int();
1166 let hi = args.get(2).ok_or("random.int: missing hi")?.as_int();
1167 if hi < lo {
1168 return Err(format!(
1169 "random.int: hi ({hi}) must be >= lo ({lo})"));
1170 }
1171 let span = (hi as i128) - (lo as i128) + 1;
1172 let (raw, next_state) = splitmix64(state);
1173 let drawn = lo as i128 + (raw as u128 % span as u128) as i128;
1178 Ok(Value::Tuple(vec![
1179 Value::Int(drawn as i64),
1180 rng_value(next_state),
1181 ]))
1182 }
1183 ("random", "float") => {
1184 let state = rng_decode(args.first())?;
1185 let (raw, next_state) = splitmix64(state);
1186 let f = ((raw >> 11) as f64) / ((1u64 << 53) as f64);
1189 Ok(Value::Tuple(vec![Value::Float(f), rng_value(next_state)]))
1190 }
1191 ("random", "choose") => {
1192 let state = rng_decode(args.first())?;
1193 let xs = match args.get(1) {
1194 Some(Value::List(xs)) => xs,
1195 _ => return Err("random.choose: expected List".into()),
1196 };
1197 if xs.is_empty() {
1198 return Ok(Value::Variant {
1199 name: "None".into(), args: vec![],
1200 });
1201 }
1202 let (raw, next_state) = splitmix64(state);
1203 let idx = (raw as usize) % xs.len();
1204 let pick = xs[idx].clone();
1205 Ok(Value::Variant {
1206 name: "Some".into(),
1207 args: vec![Value::Tuple(vec![pick, rng_value(next_state)])],
1208 })
1209 }
1210
1211 ("parser", "char") => {
1216 let s = expect_str(args.first())?;
1217 if s.chars().count() != 1 {
1218 return Err(format!(
1219 "parser.char: expected 1-character string, got {s:?}"));
1220 }
1221 Ok(parser_node("Char", &[("ch", Value::Str(s))]))
1222 }
1223 ("parser", "string") => {
1224 let s = expect_str(args.first())?;
1225 Ok(parser_node("String", &[("s", Value::Str(s))]))
1226 }
1227 ("parser", "digit") => Ok(parser_node("Digit", &[])),
1228 ("parser", "alpha") => Ok(parser_node("Alpha", &[])),
1229 ("parser", "whitespace") => Ok(parser_node("Whitespace", &[])),
1230 ("parser", "eof") => Ok(parser_node("Eof", &[])),
1231 ("parser", "seq") => {
1232 let a = args.first().cloned()
1233 .ok_or_else(|| "parser.seq: missing first parser".to_string())?;
1234 let b = args.get(1).cloned()
1235 .ok_or_else(|| "parser.seq: missing second parser".to_string())?;
1236 Ok(parser_node("Seq", &[("a", a), ("b", b)]))
1237 }
1238 ("parser", "alt") => {
1239 let a = args.first().cloned()
1240 .ok_or_else(|| "parser.alt: missing first parser".to_string())?;
1241 let b = args.get(1).cloned()
1242 .ok_or_else(|| "parser.alt: missing second parser".to_string())?;
1243 Ok(parser_node("Alt", &[("a", a), ("b", b)]))
1244 }
1245 ("parser", "many") => {
1246 let p = args.first().cloned()
1247 .ok_or_else(|| "parser.many: missing inner parser".to_string())?;
1248 Ok(parser_node("Many", &[("p", p)]))
1249 }
1250 ("parser", "optional") => {
1251 let p = args.first().cloned()
1252 .ok_or_else(|| "parser.optional: missing inner parser".to_string())?;
1253 Ok(parser_node("Optional", &[("p", p)]))
1254 }
1255 ("parser", "map") => {
1259 let p = args.first().cloned()
1260 .ok_or_else(|| "parser.map: missing parser".to_string())?;
1261 let f = args.get(1).cloned()
1262 .ok_or_else(|| "parser.map: missing closure".to_string())?;
1263 Ok(parser_node("Map", &[("p", p), ("f", f)]))
1264 }
1265 ("parser", "and_then") => {
1266 let p = args.first().cloned()
1267 .ok_or_else(|| "parser.and_then: missing parser".to_string())?;
1268 let f = args.get(1).cloned()
1269 .ok_or_else(|| "parser.and_then: missing closure".to_string())?;
1270 Ok(parser_node("AndThen", &[("p", p), ("f", f)]))
1271 }
1272 ("regex", "compile") => {
1282 let pat = expect_str(args.first())?;
1283 match get_or_compile_regex(&pat) {
1284 Ok(_) => Ok(ok_v(Value::Str(pat))),
1285 Err(e) => Ok(err_v(Value::Str(e))),
1286 }
1287 }
1288 ("regex", "is_match") => {
1289 let pat = expect_str(args.first())?;
1290 let s = expect_str(args.get(1))?;
1291 let re = get_or_compile_regex(&pat).map_err(|e| format!("regex.is_match: {e}"))?;
1292 Ok(Value::Bool(re.is_match(&s)))
1293 }
1294 ("regex", "is_match_str") => {
1299 let pat = expect_str(args.first())?;
1300 let s = expect_str(args.get(1))?;
1301 match get_or_compile_regex(&pat) {
1302 Ok(re) => Ok(Value::Bool(re.is_match(&s))),
1303 Err(_) => Ok(Value::Bool(false)),
1304 }
1305 }
1306 ("regex", "find") => {
1307 let pat = expect_str(args.first())?;
1308 let s = expect_str(args.get(1))?;
1309 let re = get_or_compile_regex(&pat).map_err(|e| format!("regex.find: {e}"))?;
1310 match re.captures(&s) {
1311 Some(caps) => Ok(Value::Variant {
1312 name: "Some".into(),
1313 args: vec![match_value(&caps)],
1314 }),
1315 None => Ok(Value::Variant { name: "None".into(), args: vec![] }),
1316 }
1317 }
1318 ("regex", "find_all") => {
1319 let pat = expect_str(args.first())?;
1320 let s = expect_str(args.get(1))?;
1321 let re = get_or_compile_regex(&pat).map_err(|e| format!("regex.find_all: {e}"))?;
1322 let items: Vec<Value> = re.captures_iter(&s).map(|caps| match_value(&caps)).collect();
1323 Ok(Value::List(items))
1324 }
1325 ("regex", "replace") => {
1326 let pat = expect_str(args.first())?;
1327 let s = expect_str(args.get(1))?;
1328 let rep = expect_str(args.get(2))?;
1329 let re = get_or_compile_regex(&pat).map_err(|e| format!("regex.replace: {e}"))?;
1330 Ok(Value::Str(re.replace(&s, rep.as_str()).into_owned()))
1331 }
1332 ("regex", "replace_all") => {
1333 let pat = expect_str(args.first())?;
1334 let s = expect_str(args.get(1))?;
1335 let rep = expect_str(args.get(2))?;
1336 let re = get_or_compile_regex(&pat).map_err(|e| format!("regex.replace_all: {e}"))?;
1337 Ok(Value::Str(re.replace_all(&s, rep.as_str()).into_owned()))
1338 }
1339 ("datetime", "parse_iso") => {
1342 let s = expect_str(args.first())?;
1343 match chrono::DateTime::parse_from_rfc3339(&s) {
1344 Ok(dt) => Ok(ok_v(Value::Int(instant_from_chrono(dt)))),
1345 Err(e) => Ok(err_v(Value::Str(format!("parse_iso: {e}")))),
1346 }
1347 }
1348 ("datetime", "format_iso") => {
1349 let n = expect_int(args.first())?;
1350 Ok(Value::Str(format_iso(n)))
1351 }
1352 ("datetime", "parse") => {
1353 let s = expect_str(args.first())?;
1354 let fmt = expect_str(args.get(1))?;
1355 match chrono::NaiveDateTime::parse_from_str(&s, &fmt) {
1356 Ok(naive) => {
1357 use chrono::TimeZone;
1358 match chrono::Utc.from_local_datetime(&naive).single() {
1359 Some(dt) => Ok(ok_v(Value::Int(instant_from_chrono(dt)))),
1360 None => Ok(err_v(Value::Str("parse: ambiguous local time".into()))),
1361 }
1362 }
1363 Err(e) => Ok(err_v(Value::Str(format!("parse: {e}")))),
1364 }
1365 }
1366 ("datetime", "format") => {
1367 let n = expect_int(args.first())?;
1368 let fmt = expect_str(args.get(1))?;
1369 let dt = chrono_from_instant(n);
1370 Ok(Value::Str(dt.format(&fmt).to_string()))
1371 }
1372 ("datetime", "to_components") => {
1373 let n = expect_int(args.first())?;
1374 let tz = match parse_tz_arg(args.get(1)) {
1375 Ok(t) => t,
1376 Err(e) => return Ok(err_v(Value::Str(e))),
1377 };
1378 match resolve_tz_to_components(n, &tz) {
1379 Ok(rec) => Ok(ok_v(rec)),
1380 Err(e) => Ok(err_v(Value::Str(e))),
1381 }
1382 }
1383 ("datetime", "from_components") => {
1384 let rec = match args.first() {
1385 Some(Value::Record(r)) => r.clone(),
1386 _ => return Err("from_components: expected DateTime record".into()),
1387 };
1388 match instant_from_components(&rec) {
1389 Ok(n) => Ok(ok_v(Value::Int(n))),
1390 Err(e) => Ok(err_v(Value::Str(e))),
1391 }
1392 }
1393 ("datetime", "add") => {
1394 let a = expect_int(args.first())?;
1395 let d = expect_int(args.get(1))?;
1396 Ok(Value::Int(a.saturating_add(d)))
1397 }
1398 ("datetime", "diff") => {
1399 let a = expect_int(args.first())?;
1400 let b = expect_int(args.get(1))?;
1401 Ok(Value::Int(a.saturating_sub(b)))
1402 }
1403 ("datetime", "duration_seconds") => {
1404 let s = expect_float(args.first())?;
1405 let nanos = (s * 1_000_000_000.0) as i64;
1406 Ok(Value::Int(nanos))
1407 }
1408 ("datetime", "duration_minutes") => {
1409 let m = expect_int(args.first())?;
1410 Ok(Value::Int(m.saturating_mul(60_000_000_000)))
1411 }
1412 ("datetime", "duration_days") => {
1413 let d = expect_int(args.first())?;
1414 Ok(Value::Int(d.saturating_mul(86_400_000_000_000)))
1415 }
1416 ("datetime", "before") => {
1418 let a = expect_int(args.first())?;
1419 let b = expect_int(args.get(1))?;
1420 Ok(Value::Bool(a < b))
1421 }
1422 ("datetime", "after") => {
1423 let a = expect_int(args.first())?;
1424 let b = expect_int(args.get(1))?;
1425 Ok(Value::Bool(a > b))
1426 }
1427 ("datetime", "compare") => {
1428 let a = expect_int(args.first())?;
1429 let b = expect_int(args.get(1))?;
1430 Ok(Value::Int(a.cmp(&b) as i64))
1431 }
1432 ("duration", "seconds") => {
1434 let nanos = expect_int(args.first())?;
1435 Ok(Value::Int(nanos / 1_000_000_000))
1436 }
1437
1438 ("regex", "split") => {
1439 let pat = expect_str(args.first())?;
1440 let s = expect_str(args.get(1))?;
1441 let re = get_or_compile_regex(&pat).map_err(|e| format!("regex.split: {e}"))?;
1442 let parts: Vec<Value> = re.split(&s).map(|p| Value::Str(p.to_string())).collect();
1443 Ok(Value::List(parts))
1444 }
1445
1446 ("http", "with_header") => {
1449 let req = expect_record_pure(args.first())?.clone();
1450 let k = expect_str(args.get(1))?;
1451 let v = expect_str(args.get(2))?;
1452 Ok(Value::Record(http_set_header(req, &k, &v)))
1453 }
1454 ("http", "with_auth") => {
1455 let req = expect_record_pure(args.first())?.clone();
1456 let scheme = expect_str(args.get(1))?;
1457 let token = expect_str(args.get(2))?;
1458 let value = format!("{scheme} {token}");
1459 Ok(Value::Record(http_set_header(req, "Authorization", &value)))
1460 }
1461 ("http", "with_query") => {
1462 let req = expect_record_pure(args.first())?.clone();
1463 let params = match args.get(1) {
1464 Some(Value::Map(m)) => m.clone(),
1465 Some(other) => return Err(format!(
1466 "http.with_query: params must be Map[Str, Str], got {other:?}")),
1467 None => return Err("http.with_query: missing params argument".into()),
1468 };
1469 Ok(Value::Record(http_append_query(req, ¶ms)))
1470 }
1471 ("http", "with_timeout_ms") => {
1472 let req = expect_record_pure(args.first())?.clone();
1473 let ms = expect_int(args.get(1))?;
1474 let mut out = req;
1475 out.insert("timeout_ms".into(), Value::Variant {
1476 name: "Some".into(),
1477 args: vec![Value::Int(ms)],
1478 });
1479 Ok(Value::Record(out))
1480 }
1481 ("http", "json_body") => {
1482 let resp = expect_record_pure(args.first())?;
1483 let body = match resp.get("body") {
1484 Some(Value::Bytes(b)) => b.clone(),
1485 _ => return Err("http.json_body: HttpResponse.body must be Bytes".into()),
1486 };
1487 let s = match std::str::from_utf8(&body) {
1488 Ok(s) => s,
1489 Err(e) => return Ok(http_decode_err_pure(format!("body not UTF-8: {e}"))),
1490 };
1491 match serde_json::from_str::<serde_json::Value>(s) {
1492 Ok(j) => Ok(ok_v(Value::from_json(&j))),
1493 Err(e) => Ok(http_decode_err_pure(format!("json parse: {e}"))),
1494 }
1495 }
1496 ("http", "text_body") => {
1497 let resp = expect_record_pure(args.first())?;
1498 let body = match resp.get("body") {
1499 Some(Value::Bytes(b)) => b.clone(),
1500 _ => return Err("http.text_body: HttpResponse.body must be Bytes".into()),
1501 };
1502 match String::from_utf8(body) {
1503 Ok(s) => Ok(ok_v(Value::Str(s))),
1504 Err(e) => Ok(http_decode_err_pure(format!("body not UTF-8: {e}"))),
1505 }
1506 }
1507
1508 ("cli", "flag") => {
1512 let name = expect_str(args.first())?;
1513 let short = opt_str(args.get(1));
1514 let help = expect_str(args.get(2))?;
1515 Ok(value_from_json(crate::cli::flag_spec(&name, short.as_deref(), &help)))
1516 }
1517 ("cli", "option") => {
1518 let name = expect_str(args.first())?;
1519 let short = opt_str(args.get(1));
1520 let help = expect_str(args.get(2))?;
1521 let default = opt_str(args.get(3));
1522 Ok(value_from_json(crate::cli::option_spec(&name, short.as_deref(), &help, default.as_deref())))
1523 }
1524 ("cli", "positional") => {
1525 let name = expect_str(args.first())?;
1526 let help = expect_str(args.get(1))?;
1527 let required = expect_bool(args.get(2))?;
1528 Ok(value_from_json(crate::cli::positional_spec(&name, &help, required)))
1529 }
1530 ("cli", "spec") => {
1531 let name = expect_str(args.first())?;
1532 let help = expect_str(args.get(1))?;
1533 let arg_specs: Vec<serde_json::Value> = expect_list(args.get(2))?
1534 .iter().map(value_to_json).collect();
1535 let subs: Vec<serde_json::Value> = expect_list(args.get(3))?
1536 .iter().map(value_to_json).collect();
1537 Ok(value_from_json(crate::cli::build_spec(&name, &help, arg_specs, subs)))
1538 }
1539 ("cli", "parse") => {
1540 let spec = value_to_json(args.first().unwrap_or(&Value::Unit));
1541 let argv: Vec<String> = expect_list(args.get(1))?
1542 .iter().map(|v| match v {
1543 Value::Str(s) => Ok(s.clone()),
1544 other => Err(format!("cli.parse: argv must be List[Str], got {other:?}")),
1545 }).collect::<Result<_, _>>()?;
1546 match crate::cli::parse(&spec, &argv) {
1547 Ok(parsed) => Ok(ok_v(value_from_json(parsed))),
1548 Err(msg) => Ok(err_v(Value::Str(msg))),
1549 }
1550 }
1551 ("cli", "envelope") => {
1552 let ok = expect_bool(args.first())?;
1553 let cmd = expect_str(args.get(1))?;
1554 let data = value_to_json(args.get(2).unwrap_or(&Value::Unit));
1555 Ok(value_from_json(crate::cli::envelope(ok, &cmd, data)))
1556 }
1557 ("cli", "describe") => {
1558 let spec = value_to_json(args.first().unwrap_or(&Value::Unit));
1559 Ok(value_from_json(crate::cli::describe(&spec)))
1560 }
1561 ("cli", "help") => {
1562 let spec = value_to_json(args.first().unwrap_or(&Value::Unit));
1563 Ok(Value::Str(crate::cli::help_text(&spec)))
1564 }
1565
1566 _ => Err(format!("unknown pure builtin: {kind}.{op}")),
1567 }
1568}
1569
1570fn opt_str(arg: Option<&Value>) -> Option<String> {
1574 match arg {
1575 Some(Value::Variant { name, args }) if name == "Some" => {
1576 args.first().and_then(|v| match v {
1577 Value::Str(s) => Some(s.clone()),
1578 _ => None,
1579 })
1580 }
1581 _ => None,
1582 }
1583}
1584
1585fn value_from_json(v: serde_json::Value) -> Value { Value::from_json(&v) }
1586
1587fn regex_cache() -> &'static Mutex<HashMap<String, regex::Regex>> {
1592 static CACHE: OnceLock<Mutex<HashMap<String, regex::Regex>>> = OnceLock::new();
1593 CACHE.get_or_init(|| Mutex::new(HashMap::new()))
1594}
1595
1596fn get_or_compile_regex(pattern: &str) -> Result<regex::Regex, String> {
1597 let cache = regex_cache();
1598 {
1599 let guard = cache.lock().unwrap();
1600 if let Some(re) = guard.get(pattern) {
1601 return Ok(re.clone());
1602 }
1603 }
1604 let re = regex::Regex::new(pattern).map_err(|e| format!("invalid regex: {e}"))?;
1605 let mut guard = cache.lock().unwrap();
1606 guard.insert(pattern.to_string(), re.clone());
1607 Ok(re)
1608}
1609
1610fn match_value(caps: ®ex::Captures) -> Value {
1614 let m0 = caps.get(0).expect("regex match always has group 0");
1615 let mut rec = indexmap::IndexMap::new();
1616 rec.insert("text".into(), Value::Str(m0.as_str().to_string()));
1617 rec.insert("start".into(), Value::Int(m0.start() as i64));
1618 rec.insert("end".into(), Value::Int(m0.end() as i64));
1619 let groups: Vec<Value> = (1..caps.len())
1620 .map(|i| {
1621 Value::Str(
1622 caps.get(i)
1623 .map(|m| m.as_str().to_string())
1624 .unwrap_or_default(),
1625 )
1626 })
1627 .collect();
1628 rec.insert("groups".into(), Value::List(groups));
1629 Value::Record(rec)
1630}
1631
1632fn expect_map(v: Option<&Value>) -> Result<&BTreeMap<MapKey, Value>, String> {
1633 match v {
1634 Some(Value::Map(m)) => Ok(m),
1635 other => Err(format!("expected Map, got {other:?}")),
1636 }
1637}
1638
1639fn expect_set(v: Option<&Value>) -> Result<&BTreeSet<MapKey>, String> {
1640 match v {
1641 Some(Value::Set(s)) => Ok(s),
1642 other => Err(format!("expected Set, got {other:?}")),
1643 }
1644}
1645
1646fn unpack_matrix(v: &Value) -> Result<(usize, usize, Vec<f64>), String> {
1650 if let Value::F64Array { rows, cols, data } = v {
1651 return Ok((*rows as usize, *cols as usize, data.clone()));
1652 }
1653 let rec = match v {
1654 Value::Record(r) => r,
1655 other => return Err(format!("expected matrix, got {other:?}")),
1656 };
1657 let rows = match rec.get("rows") {
1658 Some(Value::Int(n)) => *n as usize,
1659 _ => return Err("matrix: missing/invalid `rows`".into()),
1660 };
1661 let cols = match rec.get("cols") {
1662 Some(Value::Int(n)) => *n as usize,
1663 _ => return Err("matrix: missing/invalid `cols`".into()),
1664 };
1665 let data = match rec.get("data") {
1666 Some(Value::List(items)) => {
1667 let mut out = Vec::with_capacity(items.len());
1668 for it in items {
1669 out.push(match it {
1670 Value::Float(f) => *f,
1671 Value::Int(n) => *n as f64,
1672 other => return Err(format!("matrix data: not numeric, got {other:?}")),
1673 });
1674 }
1675 out
1676 }
1677 _ => return Err("matrix: missing/invalid `data`".into()),
1678 };
1679 if data.len() != rows * cols {
1680 return Err(format!("matrix: data len {} != {rows}*{cols}", data.len()));
1681 }
1682 Ok((rows, cols, data))
1683}
1684
1685fn expect_bytes(v: Option<&Value>) -> Result<&Vec<u8>, String> {
1686 match v {
1687 Some(Value::Bytes(b)) => Ok(b),
1688 Some(other) => Err(format!("expected Bytes, got {other:?}")),
1689 None => Err("missing argument".into()),
1690 }
1691}
1692
1693fn first_arg(args: &[Value]) -> Result<&Value, String> {
1694 args.first().ok_or_else(|| "missing argument".into())
1695}
1696
1697fn tuple_index(v: &Value, i: usize) -> Result<Value, String> {
1698 match v {
1699 Value::Tuple(items) => items.get(i).cloned()
1700 .ok_or_else(|| format!("tuple index {i} out of range (len={})", items.len())),
1701 other => Err(format!("expected Tuple, got {other:?}")),
1702 }
1703}
1704
1705fn expect_str(v: Option<&Value>) -> Result<String, String> {
1706 match v {
1707 Some(Value::Str(s)) => Ok(s.clone()),
1708 Some(other) => Err(format!("expected Str, got {other:?}")),
1709 None => Err("missing argument".into()),
1710 }
1711}
1712
1713fn expect_int(v: Option<&Value>) -> Result<i64, String> {
1714 match v {
1715 Some(Value::Int(n)) => Ok(*n),
1716 Some(other) => Err(format!("expected Int, got {other:?}")),
1717 None => Err("missing argument".into()),
1718 }
1719}
1720
1721fn expect_float(v: Option<&Value>) -> Result<f64, String> {
1722 match v {
1723 Some(Value::Float(f)) => Ok(*f),
1724 Some(other) => Err(format!("expected Float, got {other:?}")),
1725 None => Err("missing argument".into()),
1726 }
1727}
1728
1729fn expect_list(v: Option<&Value>) -> Result<&Vec<Value>, String> {
1730 match v {
1731 Some(Value::List(xs)) => Ok(xs),
1732 Some(other) => Err(format!("expected List, got {other:?}")),
1733 None => Err("missing argument".into()),
1734 }
1735}
1736
1737fn expect_bool(v: Option<&Value>) -> Result<bool, String> {
1738 match v {
1739 Some(Value::Bool(b)) => Ok(*b),
1740 Some(other) => Err(format!("expected Bool, got {other:?}")),
1741 None => Err("missing argument".into()),
1742 }
1743}
1744
1745fn expect_deque(v: Option<&Value>) -> Result<&std::collections::VecDeque<Value>, String> {
1746 match v {
1747 Some(Value::Deque(d)) => Ok(d),
1748 Some(other) => Err(format!("expected Deque, got {other:?}")),
1749 None => Err("missing argument".into()),
1750 }
1751}
1752
1753fn some(v: Value) -> Value { Value::Variant { name: "Some".into(), args: vec![v] } }
1754fn none() -> Value { Value::Variant { name: "None".into(), args: Vec::new() } }
1755fn ok_v(v: Value) -> Value { Value::Variant { name: "Ok".into(), args: vec![v] } }
1756fn err_v(v: Value) -> Value { Value::Variant { name: "Err".into(), args: vec![v] } }
1757
1758fn parser_node(kind: &str, fields: &[(&str, Value)]) -> Value {
1766 let mut r = indexmap::IndexMap::new();
1767 r.insert("kind".into(), Value::Str(kind.into()));
1768 for (k, v) in fields {
1769 r.insert((*k).into(), v.clone());
1770 }
1771 Value::Record(r)
1772}
1773
1774fn splitmix64(state: u64) -> (u64, u64) {
1785 let next = state.wrapping_add(0x9E37_79B9_7F4A_7C15);
1786 let mut z = next;
1787 z = (z ^ (z >> 30)).wrapping_mul(0xBF58_476D_1CE4_E5B9);
1788 z = (z ^ (z >> 27)).wrapping_mul(0x94D0_49BB_1331_11EB);
1789 let z = z ^ (z >> 31);
1790 (z, next)
1791}
1792
1793fn rng_value(state: u64) -> Value {
1797 let mut fields = indexmap::IndexMap::new();
1798 fields.insert("state".into(), Value::Int(state as i64));
1799 Value::Record(fields)
1800}
1801
1802fn rng_decode(v: Option<&Value>) -> Result<u64, String> {
1804 let rec = match v {
1805 Some(Value::Record(r)) => r,
1806 Some(other) => return Err(format!("expected Rng, got {other:?}")),
1807 None => return Err("missing Rng arg".into()),
1808 };
1809 match rec.get("state") {
1810 Some(Value::Int(n)) => Ok(*n as u64),
1811 _ => Err("malformed Rng: missing `state :: Int`".into()),
1812 }
1813}
1814
1815fn expect_record_pure(v: Option<&Value>) -> Result<&indexmap::IndexMap<String, Value>, String> {
1818 match v {
1819 Some(Value::Record(r)) => Ok(r),
1820 Some(other) => Err(format!("expected Record, got {other:?}")),
1821 None => Err("missing Record argument".into()),
1822 }
1823}
1824
1825fn http_decode_err_pure(msg: String) -> Value {
1826 let inner = Value::Variant {
1827 name: "DecodeError".into(),
1828 args: vec![Value::Str(msg)],
1829 };
1830 err_v(inner)
1831}
1832
1833fn http_set_header(
1838 mut req: indexmap::IndexMap<String, Value>,
1839 name: &str,
1840 value: &str,
1841) -> indexmap::IndexMap<String, Value> {
1842 use lex_bytecode::MapKey;
1843 let mut headers = match req.shift_remove("headers") {
1844 Some(Value::Map(m)) => m,
1845 _ => std::collections::BTreeMap::new(),
1846 };
1847 let key = MapKey::Str(name.to_lowercase());
1848 let lowered = name.to_lowercase();
1851 headers.retain(|k, _| match k {
1852 MapKey::Str(s) => s.to_lowercase() != lowered,
1853 _ => true,
1854 });
1855 headers.insert(key, Value::Str(value.to_string()));
1856 req.insert("headers".into(), Value::Map(headers));
1857 req
1858}
1859
1860fn http_append_query(
1866 mut req: indexmap::IndexMap<String, Value>,
1867 params: &std::collections::BTreeMap<lex_bytecode::MapKey, Value>,
1868) -> indexmap::IndexMap<String, Value> {
1869 use lex_bytecode::MapKey;
1870 let url = match req.get("url") {
1871 Some(Value::Str(s)) => s.clone(),
1872 _ => return req,
1873 };
1874 let mut pieces = Vec::new();
1875 for (k, v) in params {
1876 let kk = match k { MapKey::Str(s) => s.clone(), _ => continue };
1877 let vv = match v { Value::Str(s) => s.clone(), _ => continue };
1878 pieces.push(format!("{}={}", url_encode(&kk), url_encode(&vv)));
1879 }
1880 if pieces.is_empty() { return req; }
1881 let sep = if url.contains('?') { '&' } else { '?' };
1882 let new_url = format!("{url}{sep}{}", pieces.join("&"));
1883 req.insert("url".into(), Value::Str(new_url));
1884 req
1885}
1886
1887fn url_encode(s: &str) -> String {
1892 let mut out = String::with_capacity(s.len());
1893 for b in s.bytes() {
1894 match b {
1895 b'A'..=b'Z' | b'a'..=b'z' | b'0'..=b'9' | b'-' | b'_' | b'.' | b'~' => {
1896 out.push(b as char);
1897 }
1898 _ => out.push_str(&format!("%{:02X}", b)),
1899 }
1900 }
1901 out
1902}
1903
1904fn value_to_json(v: &Value) -> serde_json::Value { v.to_json() }
1905
1906fn unwrap_toml_datetime_markers(v: &mut serde_json::Value) {
1914 use serde_json::Value as J;
1915 match v {
1916 J::Object(map) => {
1917 if map.len() == 1 {
1921 if let Some(J::String(s)) = map.get("$__toml_private_datetime") {
1922 let s = s.clone();
1923 *v = J::String(s);
1924 return;
1925 }
1926 }
1927 for (_, child) in map.iter_mut() {
1928 unwrap_toml_datetime_markers(child);
1929 }
1930 }
1931 J::Array(items) => {
1932 for item in items.iter_mut() {
1933 unwrap_toml_datetime_markers(item);
1934 }
1935 }
1936 _ => {}
1937 }
1938}
1939
1940fn json_to_value(v: &serde_json::Value) -> Value { Value::from_json(v) }
1941
1942fn required_field_names(arg: Option<&Value>) -> Result<Vec<String>, String> {
1947 let list = expect_list(arg)?;
1948 let mut out = Vec::with_capacity(list.len());
1949 for v in list {
1950 match v {
1951 Value::Str(s) => out.push(s.clone()),
1952 other => return Err(format!(
1953 "parse_strict: required-fields list must contain Str, got {other:?}"
1954 )),
1955 }
1956 }
1957 Ok(out)
1958}
1959
1960fn check_required_fields(
1981 value: &serde_json::Value,
1982 required: &[String],
1983) -> Result<(), String> {
1984 if required.is_empty() {
1985 return Ok(());
1986 }
1987 if !matches!(value, serde_json::Value::Object(_)) {
1988 return Err(format!(
1989 "parse_strict: expected top-level object with fields {:?}, got {value}",
1990 required
1991 ));
1992 }
1993 let mut missing: Vec<String> = Vec::new();
1994 for path in required {
1995 if !path_exists(value, path) {
1996 missing.push(path.clone());
1997 }
1998 }
1999 if missing.is_empty() {
2000 Ok(())
2001 } else {
2002 Err(format!("missing required field(s): {}", missing.join(", ")))
2003 }
2004}
2005
2006fn path_exists(value: &serde_json::Value, path: &str) -> bool {
2011 let mut cursor = value;
2012 let segments = split_dotted_path(path);
2013 for seg in &segments {
2014 match cursor {
2015 serde_json::Value::Object(o) => match o.get(seg.as_str()) {
2016 Some(next) => cursor = next,
2017 None => return false,
2018 },
2019 _ => return false,
2020 }
2021 }
2022 true
2023}
2024
2025fn split_dotted_path(path: &str) -> Vec<String> {
2029 let mut out: Vec<String> = Vec::new();
2030 let mut cur = String::new();
2031 let mut iter = path.chars().peekable();
2032 while let Some(c) = iter.next() {
2033 if c == '\\' {
2034 if let Some(&'.') = iter.peek() {
2036 cur.push('.');
2037 iter.next();
2038 continue;
2039 }
2040 cur.push(c);
2041 } else if c == '.' {
2042 out.push(std::mem::take(&mut cur));
2043 } else {
2044 cur.push(c);
2045 }
2046 }
2047 out.push(cur);
2048 out
2049}
2050
2051fn extract_type_schema(v: Option<&Value>) -> Vec<(String, String)> {
2055 match v {
2056 Some(Value::List(pairs)) => pairs.iter().filter_map(|p| {
2057 if let Value::Tuple(items) = p {
2058 if items.len() == 2 {
2059 if let (Value::Str(name), Value::Str(tag)) = (&items[0], &items[1]) {
2060 return Some((name.clone(), tag.clone()));
2061 }
2062 }
2063 }
2064 None
2065 }).collect(),
2066 _ => vec![],
2067 }
2068}
2069
2070fn validate_field_types(
2076 json: &serde_json::Value,
2077 schema: &[(String, String)],
2078) -> Result<(), String> {
2079 if schema.is_empty() {
2080 return Ok(());
2081 }
2082 let obj = match json.as_object() {
2083 Some(o) => o,
2084 None => return Ok(()), };
2086 for (field, tag) in schema {
2087 if let Some(val) = obj.get(field) {
2088 if let Err(e) = check_json_type(val, tag) {
2089 return Err(format!("field `{field}`: {e}"));
2090 }
2091 }
2092 }
2093 Ok(())
2094}
2095
2096fn check_json_type(val: &serde_json::Value, tag: &str) -> Result<(), String> {
2098 use serde_json::Value as J;
2099 match (tag, val) {
2100 ("Int", J::Number(n)) if n.is_i64() || n.is_u64() => Ok(()),
2101 ("Int", other) => Err(format!("expected Int, got {}", json_type_name(other))),
2102 ("Float", J::Number(_)) => Ok(()),
2103 ("Float", other) => Err(format!("expected Float, got {}", json_type_name(other))),
2104 ("Bool", J::Bool(_)) => Ok(()),
2105 ("Bool", other) => Err(format!("expected Bool, got {}", json_type_name(other))),
2106 ("Str", J::String(_)) => Ok(()),
2107 ("Str", other) => Err(format!("expected Str, got {}", json_type_name(other))),
2108 (tag, J::Null) if tag.starts_with("Option[") => Ok(()),
2110 (tag, val) if tag.starts_with("Option[") && tag.ends_with(']') => {
2111 let inner = &tag[7..tag.len() - 1]; check_json_type(val, inner)
2113 }
2114 (tag, J::Array(items)) if tag.starts_with("List[") && tag.ends_with(']') => {
2116 let inner = &tag[5..tag.len() - 1]; for (i, item) in items.iter().enumerate() {
2118 if let Err(e) = check_json_type(item, inner) {
2119 return Err(format!("[{i}]: {e}"));
2120 }
2121 }
2122 Ok(())
2123 }
2124 ("Record", _) => Ok(()), ("Any", _) => Ok(()), _ => Ok(()), }
2128}
2129
2130fn json_type_name(v: &serde_json::Value) -> &'static str {
2131 match v {
2132 serde_json::Value::Null => "null",
2133 serde_json::Value::Bool(_) => "Bool",
2134 serde_json::Value::Number(_) => "Number",
2135 serde_json::Value::String(_) => "Str",
2136 serde_json::Value::Array(_) => "Array",
2137 serde_json::Value::Object(_) => "Object",
2138 }
2139}
2140
2141fn parse_dotenv(src: &str) -> Result<indexmap::IndexMap<String, String>, String> {
2152 let mut out = indexmap::IndexMap::new();
2153 for (idx, raw) in src.lines().enumerate() {
2154 let line = raw.trim();
2155 if line.is_empty() || line.starts_with('#') {
2156 continue;
2157 }
2158 let after_export = line.strip_prefix("export ").unwrap_or(line);
2161 let (k, v) = match after_export.split_once('=') {
2162 Some(kv) => kv,
2163 None => return Err(format!("dotenv.parse line {}: missing `=`", idx + 1)),
2164 };
2165 let key = k.trim();
2166 if key.is_empty() {
2167 return Err(format!("dotenv.parse line {}: empty key", idx + 1));
2168 }
2169 let v_trim = v.trim();
2170 let value = if let Some(q) = v_trim.strip_prefix('"').and_then(|s| s.strip_suffix('"')) {
2171 q.to_string()
2172 } else if let Some(q) = v_trim.strip_prefix('\'').and_then(|s| s.strip_suffix('\'')) {
2173 q.to_string()
2174 } else {
2175 v_trim.to_string()
2176 };
2177 out.insert(key.to_string(), value);
2178 }
2179 Ok(out)
2180}
2181
2182fn instant_from_chrono<Tz: chrono::TimeZone>(dt: chrono::DateTime<Tz>) -> i64 {
2188 dt.timestamp_nanos_opt().unwrap_or(i64::MAX)
2189}
2190
2191fn chrono_from_instant(n: i64) -> chrono::DateTime<chrono::Utc> {
2192 let secs = n.div_euclid(1_000_000_000);
2193 let nanos = n.rem_euclid(1_000_000_000) as u32;
2194 use chrono::TimeZone;
2195 chrono::Utc
2196 .timestamp_opt(secs, nanos)
2197 .single()
2198 .unwrap_or_else(chrono::Utc::now)
2199}
2200
2201fn format_iso(n: i64) -> String {
2202 chrono_from_instant(n).to_rfc3339()
2203}
2204
2205enum TzArg {
2208 Utc,
2209 Local,
2210 Offset(i32),
2212 Iana(String),
2214}
2215
2216fn parse_tz_arg(v: Option<&Value>) -> Result<TzArg, String> {
2217 match v {
2218 Some(Value::Variant { name, args }) => match (name.as_str(), args.as_slice()) {
2219 ("Utc", []) => Ok(TzArg::Utc),
2220 ("Local", []) => Ok(TzArg::Local),
2221 ("Offset", [Value::Int(m)]) => {
2222 let m = i32::try_from(*m).map_err(|_| {
2223 format!("Tz::Offset: minutes out of range: {m}")
2224 })?;
2225 Ok(TzArg::Offset(m))
2226 }
2227 ("Iana", [Value::Str(s)]) => Ok(TzArg::Iana(s.clone())),
2228 (other, _) => Err(format!(
2229 "expected Tz variant (Utc | Local | Offset(Int) | Iana(Str)), got `{other}` with {} arg(s)",
2230 args.len()
2231 )),
2232 },
2233 Some(other) => Err(format!("expected Tz variant, got {other:?}")),
2234 None => Err("missing Tz argument".into()),
2235 }
2236}
2237
2238fn resolve_tz_to_components(n: i64, tz: &TzArg) -> Result<Value, String> {
2239 use chrono::{TimeZone, Datelike, Timelike, Offset};
2240 let utc_dt = chrono_from_instant(n);
2241 let (y, m, d, hh, mm, ss, ns, off_min) = match tz {
2242 TzArg::Utc => {
2243 let d = utc_dt;
2244 (d.year(), d.month() as i32, d.day() as i32,
2245 d.hour() as i32, d.minute() as i32, d.second() as i32,
2246 d.nanosecond() as i32, 0)
2247 }
2248 TzArg::Local => {
2249 let d = utc_dt.with_timezone(&chrono::Local);
2250 let off = d.offset().fix().local_minus_utc() / 60;
2251 (d.year(), d.month() as i32, d.day() as i32,
2252 d.hour() as i32, d.minute() as i32, d.second() as i32,
2253 d.nanosecond() as i32, off)
2254 }
2255 TzArg::Offset(off_min) => {
2256 let off_secs = off_min.saturating_mul(60);
2257 let fixed = chrono::FixedOffset::east_opt(off_secs)
2258 .ok_or("to_components: offset out of range")?;
2259 let d = utc_dt.with_timezone(&fixed);
2260 (d.year(), d.month() as i32, d.day() as i32,
2261 d.hour() as i32, d.minute() as i32, d.second() as i32,
2262 d.nanosecond() as i32, *off_min)
2263 }
2264 TzArg::Iana(name) => {
2265 let tz: chrono_tz::Tz = name.parse()
2266 .map_err(|e| format!("to_components: unknown timezone `{name}`: {e}"))?;
2267 let d = utc_dt.with_timezone(&tz);
2268 let off = d.offset().fix().local_minus_utc() / 60;
2269 (d.year(), d.month() as i32, d.day() as i32,
2270 d.hour() as i32, d.minute() as i32, d.second() as i32,
2271 d.nanosecond() as i32, off)
2272 }
2273 };
2274 let mut rec = indexmap::IndexMap::new();
2275 rec.insert("year".into(), Value::Int(y as i64));
2276 rec.insert("month".into(), Value::Int(m as i64));
2277 rec.insert("day".into(), Value::Int(d as i64));
2278 rec.insert("hour".into(), Value::Int(hh as i64));
2279 rec.insert("minute".into(), Value::Int(mm as i64));
2280 rec.insert("second".into(), Value::Int(ss as i64));
2281 rec.insert("nano".into(), Value::Int(ns as i64));
2282 rec.insert("tz_offset_minutes".into(), Value::Int(off_min as i64));
2283 let _ = chrono::Utc.timestamp_opt(0, 0); Ok(Value::Record(rec))
2285}
2286
2287
2288fn instant_from_components(rec: &indexmap::IndexMap<String, Value>) -> Result<i64, String> {
2289 use chrono::TimeZone;
2290 fn get_int(rec: &indexmap::IndexMap<String, Value>, k: &str) -> Result<i64, String> {
2291 match rec.get(k) {
2292 Some(Value::Int(n)) => Ok(*n),
2293 other => Err(format!("from_components: missing or non-int field `{k}`: {other:?}")),
2294 }
2295 }
2296 let y = get_int(rec, "year")? as i32;
2297 let m = get_int(rec, "month")? as u32;
2298 let d = get_int(rec, "day")? as u32;
2299 let hh = get_int(rec, "hour")? as u32;
2300 let mm = get_int(rec, "minute")? as u32;
2301 let ss = get_int(rec, "second")? as u32;
2302 let ns = get_int(rec, "nano")? as u32;
2303 let off_min = get_int(rec, "tz_offset_minutes")? as i32;
2304 let off = chrono::FixedOffset::east_opt(off_min * 60)
2305 .ok_or("from_components: offset out of range")?;
2306 let dt = off
2307 .with_ymd_and_hms(y, m, d, hh, mm, ss)
2308 .single()
2309 .ok_or("from_components: invalid or ambiguous date/time")?;
2310 let dt = dt + chrono::Duration::nanoseconds(ns as i64);
2311 Ok(instant_from_chrono(dt))
2312}
2313
2314type Aead4<'a> = (&'a Vec<u8>, &'a Vec<u8>, &'a Vec<u8>, &'a Vec<u8>);
2331
2332type Aead5<'a> = (&'a Vec<u8>, &'a Vec<u8>, &'a Vec<u8>, &'a Vec<u8>, &'a Vec<u8>);
2335
2336fn unpack4_bytes<'a>(
2337 args: &'a [Value],
2338 op: &str,
2339) -> Result<Aead4<'a>, String> {
2340 let pick = |i: usize, name: &str| -> Result<&'a Vec<u8>, String> {
2341 match args.get(i) {
2342 Some(Value::Bytes(b)) => Ok(b),
2343 Some(other) => Err(format!("{op}: {name} must be Bytes, got {other:?}")),
2344 None => Err(format!("{op}: missing {name} argument")),
2345 }
2346 };
2347 Ok((pick(0, "key")?, pick(1, "nonce")?, pick(2, "aad")?, pick(3, "plaintext")?))
2348}
2349
2350fn unpack5_bytes<'a>(
2351 args: &'a [Value],
2352 op: &str,
2353) -> Result<Aead5<'a>, String> {
2354 let pick = |i: usize, name: &str| -> Result<&'a Vec<u8>, String> {
2355 match args.get(i) {
2356 Some(Value::Bytes(b)) => Ok(b),
2357 Some(other) => Err(format!("{op}: {name} must be Bytes, got {other:?}")),
2358 None => Err(format!("{op}: missing {name} argument")),
2359 }
2360 };
2361 Ok((
2362 pick(0, "key")?,
2363 pick(1, "nonce")?,
2364 pick(2, "aad")?,
2365 pick(3, "ciphertext")?,
2366 pick(4, "tag")?,
2367 ))
2368}
2369
2370fn aead_result(ciphertext: Vec<u8>, tag: Vec<u8>) -> Value {
2371 let mut rec = indexmap::IndexMap::new();
2372 rec.insert("ciphertext".into(), Value::Bytes(ciphertext));
2373 rec.insert("tag".into(), Value::Bytes(tag));
2374 Value::Record(rec)
2375}
2376
2377fn aead_err(msg: impl Into<String>) -> Value {
2378 err_v(Value::Str(msg.into()))
2379}
2380
2381fn aes_gcm_seal_impl(args: &[Value]) -> Value {
2382 use aes_gcm::aead::{Aead, KeyInit, Payload};
2383 use aes_gcm::{Aes128Gcm, Aes256Gcm, Nonce};
2384 let (key, nonce, aad, plaintext) = match unpack4_bytes(args, "aes_gcm_seal") {
2385 Ok(t) => t,
2386 Err(e) => return aead_err(e),
2387 };
2388 if nonce.len() != 12 {
2389 return aead_err(format!(
2390 "aes_gcm_seal: nonce must be exactly 12 bytes, got {}", nonce.len()
2391 ));
2392 }
2393 let n = Nonce::from_slice(nonce);
2394 let payload = Payload { msg: plaintext, aad };
2395 let combined = match key.len() {
2398 16 => {
2399 let cipher = Aes128Gcm::new_from_slice(key)
2400 .map_err(|e| e.to_string());
2401 match cipher {
2402 Ok(c) => c.encrypt(n, payload).map_err(|e| format!("aes_gcm_seal: {e}")),
2403 Err(e) => Err(format!("aes_gcm_seal: {e}")),
2404 }
2405 }
2406 32 => {
2407 let cipher = Aes256Gcm::new_from_slice(key)
2408 .map_err(|e| e.to_string());
2409 match cipher {
2410 Ok(c) => c.encrypt(n, payload).map_err(|e| format!("aes_gcm_seal: {e}")),
2411 Err(e) => Err(format!("aes_gcm_seal: {e}")),
2412 }
2413 }
2414 other => return aead_err(format!(
2417 "aes_gcm_seal: key must be 16 or 32 bytes, got {other}"
2418 )),
2419 };
2420 match combined {
2421 Ok(mut buf) => {
2422 let tag_start = buf.len() - 16;
2424 let tag = buf.split_off(tag_start);
2425 ok_v(aead_result(buf, tag))
2426 }
2427 Err(e) => aead_err(e),
2428 }
2429}
2430
2431fn aes_gcm_open_impl(args: &[Value]) -> Value {
2432 use aes_gcm::aead::{Aead, KeyInit, Payload};
2433 use aes_gcm::{Aes128Gcm, Aes256Gcm, Nonce};
2434 let (key, nonce, aad, ciphertext, tag) = match unpack5_bytes(args, "aes_gcm_open") {
2435 Ok(t) => t,
2436 Err(e) => return err_v(Value::Str(e)),
2437 };
2438 if nonce.len() != 12 {
2439 return err_v(Value::Str(format!(
2440 "aes_gcm_open: nonce must be exactly 12 bytes, got {}", nonce.len()
2441 )));
2442 }
2443 if tag.len() != 16 {
2444 return err_v(Value::Str(format!(
2445 "aes_gcm_open: tag must be exactly 16 bytes, got {}", tag.len()
2446 )));
2447 }
2448 let mut combined = Vec::with_capacity(ciphertext.len() + tag.len());
2450 combined.extend_from_slice(ciphertext);
2451 combined.extend_from_slice(tag);
2452 let n = Nonce::from_slice(nonce);
2453 let payload = Payload { msg: &combined, aad };
2454 let plaintext = match key.len() {
2455 16 => Aes128Gcm::new_from_slice(key)
2456 .map_err(|e| format!("aes_gcm_open: {e}"))
2457 .and_then(|c| c.decrypt(n, payload).map_err(|e| format!("aes_gcm_open: {e}"))),
2458 32 => Aes256Gcm::new_from_slice(key)
2459 .map_err(|e| format!("aes_gcm_open: {e}"))
2460 .and_then(|c| c.decrypt(n, payload).map_err(|e| format!("aes_gcm_open: {e}"))),
2461 other => return err_v(Value::Str(format!(
2462 "aes_gcm_open: key must be 16 or 32 bytes, got {other}"
2463 ))),
2464 };
2465 match plaintext {
2466 Ok(p) => ok_v(Value::Bytes(p)),
2467 Err(e) => err_v(Value::Str(e)),
2468 }
2469}
2470
2471fn chacha20_seal_impl(args: &[Value]) -> Value {
2472 use chacha20poly1305::aead::{Aead, KeyInit, Payload};
2473 use chacha20poly1305::{ChaCha20Poly1305, Nonce};
2474 let (key, nonce, aad, plaintext) = match unpack4_bytes(args, "chacha20_poly1305_seal") {
2475 Ok(t) => t,
2476 Err(e) => return aead_err(e),
2477 };
2478 if key.len() != 32 {
2479 return aead_err(format!(
2480 "chacha20_poly1305_seal: key must be exactly 32 bytes, got {}", key.len()
2481 ));
2482 }
2483 if nonce.len() != 12 {
2484 return aead_err(format!(
2485 "chacha20_poly1305_seal: nonce must be exactly 12 bytes, got {}", nonce.len()
2486 ));
2487 }
2488 let cipher = ChaCha20Poly1305::new_from_slice(key)
2489 .map_err(|e| format!("chacha20_poly1305_seal: {e}"));
2490 let n = Nonce::from_slice(nonce);
2491 let payload = Payload { msg: plaintext, aad };
2492 let combined = match cipher {
2493 Ok(c) => c.encrypt(n, payload).map_err(|e| format!("chacha20_poly1305_seal: {e}")),
2494 Err(e) => Err(e),
2495 };
2496 match combined {
2497 Ok(mut buf) => {
2498 let tag_start = buf.len() - 16;
2499 let tag = buf.split_off(tag_start);
2500 ok_v(aead_result(buf, tag))
2501 }
2502 Err(e) => aead_err(e),
2503 }
2504}
2505
2506fn chacha20_open_impl(args: &[Value]) -> Value {
2507 use chacha20poly1305::aead::{Aead, KeyInit, Payload};
2508 use chacha20poly1305::{ChaCha20Poly1305, Nonce};
2509 let (key, nonce, aad, ciphertext, tag) = match unpack5_bytes(args, "chacha20_poly1305_open") {
2510 Ok(t) => t,
2511 Err(e) => return err_v(Value::Str(e)),
2512 };
2513 if key.len() != 32 {
2514 return err_v(Value::Str(format!(
2515 "chacha20_poly1305_open: key must be exactly 32 bytes, got {}", key.len()
2516 )));
2517 }
2518 if nonce.len() != 12 {
2519 return err_v(Value::Str(format!(
2520 "chacha20_poly1305_open: nonce must be exactly 12 bytes, got {}", nonce.len()
2521 )));
2522 }
2523 if tag.len() != 16 {
2524 return err_v(Value::Str(format!(
2525 "chacha20_poly1305_open: tag must be exactly 16 bytes, got {}", tag.len()
2526 )));
2527 }
2528 let mut combined = Vec::with_capacity(ciphertext.len() + tag.len());
2529 combined.extend_from_slice(ciphertext);
2530 combined.extend_from_slice(tag);
2531 let cipher = ChaCha20Poly1305::new_from_slice(key)
2532 .map_err(|e| format!("chacha20_poly1305_open: {e}"));
2533 let n = Nonce::from_slice(nonce);
2534 let payload = Payload { msg: &combined, aad };
2535 match cipher.and_then(|c| c.decrypt(n, payload).map_err(|e| format!("chacha20_poly1305_open: {e}"))) {
2536 Ok(p) => ok_v(Value::Bytes(p)),
2537 Err(e) => err_v(Value::Str(e)),
2538 }
2539}
2540
2541type Kdf4<'a> = (&'a Vec<u8>, &'a Vec<u8>, i64, i64);
2551
2552type Hkdf4<'a> = (&'a Vec<u8>, &'a Vec<u8>, &'a Vec<u8>, i64);
2555
2556type Argon5<'a> = (&'a Vec<u8>, &'a Vec<u8>, i64, i64, i64);
2559
2560fn pick_bytes<'a>(args: &'a [Value], i: usize, op: &str, name: &str)
2561 -> Result<&'a Vec<u8>, String>
2562{
2563 match args.get(i) {
2564 Some(Value::Bytes(b)) => Ok(b),
2565 Some(other) => Err(format!("{op}: {name} must be Bytes, got {other:?}")),
2566 None => Err(format!("{op}: missing {name} argument")),
2567 }
2568}
2569
2570fn pick_int(args: &[Value], i: usize, op: &str, name: &str) -> Result<i64, String> {
2571 match args.get(i) {
2572 Some(Value::Int(n)) => Ok(*n),
2573 Some(other) => Err(format!("{op}: {name} must be Int, got {other:?}")),
2574 None => Err(format!("{op}: missing {name} argument")),
2575 }
2576}
2577
2578fn unpack_kdf4<'a>(args: &'a [Value], op: &str) -> Result<Kdf4<'a>, String> {
2579 Ok((
2580 pick_bytes(args, 0, op, "password")?,
2581 pick_bytes(args, 1, op, "salt")?,
2582 pick_int(args, 2, op, "iterations")?,
2583 pick_int(args, 3, op, "len")?,
2584 ))
2585}
2586
2587fn unpack_hkdf4<'a>(args: &'a [Value], op: &str) -> Result<Hkdf4<'a>, String> {
2588 Ok((
2589 pick_bytes(args, 0, op, "ikm")?,
2590 pick_bytes(args, 1, op, "salt")?,
2591 pick_bytes(args, 2, op, "info")?,
2592 pick_int(args, 3, op, "len")?,
2593 ))
2594}
2595
2596fn unpack_argon5<'a>(args: &'a [Value], op: &str) -> Result<Argon5<'a>, String> {
2597 Ok((
2598 pick_bytes(args, 0, op, "password")?,
2599 pick_bytes(args, 1, op, "salt")?,
2600 pick_int(args, 2, op, "t_cost")?,
2601 pick_int(args, 3, op, "m_cost")?,
2602 pick_int(args, 4, op, "len")?,
2603 ))
2604}
2605
2606const KDF_MAX_LEN: usize = 1024 * 1024;
2611
2612fn check_len(op: &str, len: i64) -> Result<usize, String> {
2613 if len <= 0 {
2614 return Err(format!("{op}: len must be > 0, got {len}"));
2615 }
2616 if (len as u64) > KDF_MAX_LEN as u64 {
2617 return Err(format!(
2618 "{op}: len must be <= {KDF_MAX_LEN}, got {len}"
2619 ));
2620 }
2621 Ok(len as usize)
2622}
2623
2624fn pbkdf2_sha256_impl(args: &[Value]) -> Value {
2625 use hmac::Hmac;
2626 use sha2::Sha256;
2627 let op = "pbkdf2_sha256";
2628 let (password, salt, iterations, len) = match unpack_kdf4(args, op) {
2629 Ok(t) => t,
2630 Err(e) => return err_v(Value::Str(e)),
2631 };
2632 if iterations <= 0 {
2633 return err_v(Value::Str(format!(
2634 "{op}: iterations must be > 0, got {iterations}"
2635 )));
2636 }
2637 let out_len = match check_len(op, len) {
2638 Ok(n) => n,
2639 Err(e) => return err_v(Value::Str(e)),
2640 };
2641 let rounds = match u32::try_from(iterations) {
2642 Ok(r) => r,
2643 Err(_) => {
2644 return err_v(Value::Str(format!(
2645 "{op}: iterations must fit in u32, got {iterations}"
2646 )))
2647 }
2648 };
2649 let mut out = vec![0u8; out_len];
2650 if let Err(e) = pbkdf2::pbkdf2::<Hmac<Sha256>>(password, salt, rounds, &mut out) {
2651 return err_v(Value::Str(format!("{op}: {e}")));
2652 }
2653 ok_v(Value::Bytes(out))
2654}
2655
2656fn hkdf_sha256_impl(args: &[Value]) -> Value {
2657 use hkdf::Hkdf;
2658 use sha2::Sha256;
2659 let op = "hkdf_sha256";
2660 let (ikm, salt, info, len) = match unpack_hkdf4(args, op) {
2661 Ok(t) => t,
2662 Err(e) => return err_v(Value::Str(e)),
2663 };
2664 let out_len = match check_len(op, len) {
2665 Ok(n) => n,
2666 Err(e) => return err_v(Value::Str(e)),
2667 };
2668 let salt_opt: Option<&[u8]> = if salt.is_empty() { None } else { Some(salt) };
2671 let hk = Hkdf::<Sha256>::new(salt_opt, ikm);
2672 let mut out = vec![0u8; out_len];
2673 match hk.expand(info, &mut out) {
2674 Ok(()) => ok_v(Value::Bytes(out)),
2675 Err(e) => err_v(Value::Str(format!("{op}: {e}"))),
2676 }
2677}
2678
2679fn argon2id_impl(args: &[Value]) -> Value {
2680 use argon2::{Algorithm, Argon2, Params, Version};
2681 let op = "argon2id";
2682 let (password, salt, t_cost, m_cost, len) = match unpack_argon5(args, op) {
2683 Ok(t) => t,
2684 Err(e) => return err_v(Value::Str(e)),
2685 };
2686 let out_len = match check_len(op, len) {
2687 Ok(n) => n,
2688 Err(e) => return err_v(Value::Str(e)),
2689 };
2690 let t = match u32::try_from(t_cost) {
2691 Ok(n) if n >= 1 => n,
2692 _ => return err_v(Value::Str(format!(
2693 "{op}: t_cost must be a u32 >= 1, got {t_cost}"
2694 ))),
2695 };
2696 let m = match u32::try_from(m_cost) {
2697 Ok(n) if n >= Params::MIN_M_COST => n,
2698 _ => return err_v(Value::Str(format!(
2699 "{op}: m_cost must be a u32 >= {}, got {m_cost}",
2700 Params::MIN_M_COST
2701 ))),
2702 };
2703 let params = match Params::new(m, t, 1, Some(out_len)) {
2708 Ok(p) => p,
2709 Err(e) => return err_v(Value::Str(format!("{op}: {e}"))),
2710 };
2711 let hasher = Argon2::new(Algorithm::Argon2id, Version::V0x13, params);
2712 let mut out = vec![0u8; out_len];
2713 if let Err(e) = hasher.hash_password_into(password, salt, &mut out) {
2714 return err_v(Value::Str(format!("{op}: {e}")));
2715 }
2716 ok_v(Value::Bytes(out))
2717}