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