1use lex_bytecode::{MapKey, Value};
6use smol_str::SmolStr;
7use std::collections::{BTreeMap, BTreeSet, HashMap};
8use std::sync::{Mutex, OnceLock};
9
10pub fn is_pure_call(kind: &str, op: &str) -> bool {
14 if let Some(def) = lex_types::stdlib_spec::lookup(kind, op) {
18 return def.kind != lex_types::stdlib_spec::BuiltinKind::Effect;
19 }
20 if !is_pure_module(kind) { return false; }
21 !matches!(
22 (kind, op),
23 ("crypto", "random")
24 | ("crypto", "random_str_hex")
25 | ("crypto", "p256_generate")
28 | ("crypto", "secp256k1_generate")
30 | ("datetime", "now")
31 | ("http", "send")
32 | ("http", "get")
33 | ("http", "post")
34 | ("http", "stream_lines")
35 | ("arrow", "read_csv")
37 | ("arrow", "read_parquet")
39 | ("arrow", "read_parquet_cols")
40 | ("arrow", "write_parquet")
41 | ("arrow", "write_csv")
42 )
43}
44
45pub fn call_pure_builtin(kind: &str, op: &str, args: Vec<Value>) -> Result<Value, String> {
52 if let Some(f) = crate::stdlib::lookup(kind, op) {
55 return f(args);
56 }
57 dispatch(kind, op, &args)
58}
59
60pub fn try_pure_builtin(kind: &str, op: &str, args: &[Value]) -> Option<Result<Value, String>> {
67 if !is_pure_call(kind, op) { return None; }
68 Some(dispatch(kind, op, args))
69}
70
71pub fn is_pure_module(kind: &str) -> bool {
74 matches!(kind, "str" | "int" | "float" | "bool" | "list" | "iter"
75 | "option" | "result" | "tuple" | "json" | "bytes" | "flow" | "math"
76 | "map" | "set" | "crypto" | "regex" | "deque" | "datetime" | "duration" | "http"
77 | "toml" | "yaml" | "dotenv" | "csv" | "test" | "random" | "parser"
78 | "cli" | "arrow" | "df" | "decimal")
79}
80
81fn dispatch(kind: &str, op: &str, args: &[Value]) -> Result<Value, String> {
82 if let Some(f) = crate::stdlib::lookup(kind, op) {
85 return f(args.to_vec());
86 }
87 match (kind, op) {
88
89 ("int", "to_str") => Ok(Value::Str(expect_int(args.first())?.to_string().into())),
91 ("int", "to_float") => Ok(Value::Float(expect_int(args.first())? as f64)),
92 ("int", "abs") => Ok(Value::Int(expect_int(args.first())?.abs())),
94 ("int", "min") => Ok(Value::Int(expect_int(args.first())?.min(expect_int(args.get(1))?))),
95 ("int", "max") => Ok(Value::Int(expect_int(args.first())?.max(expect_int(args.get(1))?))),
96 ("float", "to_int") => Ok(Value::Int(expect_float(args.first())? as i64)),
97 ("float", "to_str") => Ok(Value::Str(expect_float(args.first())?.to_string().into())),
98
99
100 ("tuple", "fst") => tuple_index(first_arg(args)?, 0),
105 ("tuple", "snd") => tuple_index(first_arg(args)?, 1),
106 ("tuple", "third") => tuple_index(first_arg(args)?, 2),
107 ("tuple", "len") => match first_arg(args)? {
108 Value::Tuple(items) => Ok(Value::Int(items.len() as i64)),
109 other => Err(format!("tuple.len: expected Tuple, got {other:?}")),
110 },
111
112 ("option", "unwrap_or") => {
114 let opt = first_arg(args)?;
115 let default = args.get(1).cloned().unwrap_or(Value::Unit);
116 match opt {
117 Value::Variant { name, args } if name == "Some" && !args.is_empty() => Ok(args[0].clone()),
118 Value::Variant { name, .. } if name == "None" => Ok(default),
119 other => Err(format!("option.unwrap_or expected Option, got {other:?}")),
120 }
121 }
122 ("option", "unwrap_or_else") => {
127 let opt = first_arg(args)?;
128 match opt {
129 Value::Variant { name, args } if name == "Some" && !args.is_empty() => Ok(args[0].clone()),
130 Value::Variant { name, .. } if name == "None" => {
131 Ok(args.get(1).cloned().unwrap_or(Value::Unit))
135 }
136 other => Err(format!("option.unwrap_or_else expected Option, got {other:?}")),
137 }
138 }
139 ("option", "is_some") => match first_arg(args)? {
140 Value::Variant { name, .. } => Ok(Value::Bool(name == "Some")),
141 other => Err(format!("option.is_some expected Option, got {other:?}")),
142 },
143 ("option", "is_none") => match first_arg(args)? {
144 Value::Variant { name, .. } => Ok(Value::Bool(name == "None")),
145 other => Err(format!("option.is_none expected Option, got {other:?}")),
146 },
147 ("option", "ok_or") => {
149 let opt = first_arg(args)?;
150 let err_val = args.get(1).cloned().unwrap_or(Value::Unit);
151 match opt {
152 Value::Variant { name, args } if name == "Some" && !args.is_empty() =>
153 Ok(ok_v(args[0].clone())),
154 Value::Variant { name, .. } if name == "None" => Ok(err_v(err_val)),
155 other => Err(format!("option.ok_or expected Option, got {other:?}")),
156 }
157 }
158
159 ("result", "is_ok") => match first_arg(args)? {
161 Value::Variant { name, .. } => Ok(Value::Bool(name == "Ok")),
162 other => Err(format!("result.is_ok expected Result, got {other:?}")),
163 },
164 ("result", "is_err") => match first_arg(args)? {
165 Value::Variant { name, .. } => Ok(Value::Bool(name == "Err")),
166 other => Err(format!("result.is_err expected Result, got {other:?}")),
167 },
168 ("result", "unwrap_or") => {
169 let res = first_arg(args)?;
170 let default = args.get(1).cloned().unwrap_or(Value::Unit);
171 match res {
172 Value::Variant { name, args } if name == "Ok" && !args.is_empty() => Ok(args[0].clone()),
173 Value::Variant { name, .. } if name == "Err" => Ok(default),
174 other => Err(format!("result.unwrap_or expected Result, got {other:?}")),
175 }
176 }
177 ("result", "unwrap_or_else") => {
182 let res = first_arg(args)?;
183 match res {
184 Value::Variant { name, args } if name == "Ok" && !args.is_empty() => Ok(args[0].clone()),
185 Value::Variant { name, .. } if name == "Err" =>
186 Ok(args.get(1).cloned().unwrap_or(Value::Unit)),
187 other => Err(format!("result.unwrap_or_else expected Result, got {other:?}")),
188 }
189 }
190
191 ("json", "stringify") => {
193 let v = first_arg(args)?;
194 Ok(Value::Str(serde_json::to_string(&value_to_json(v)).unwrap_or_default().into()))
195 }
196 ("json", "parse") => {
197 let s = expect_str(args.first())?;
198 match serde_json::from_str::<serde_json::Value>(s) {
199 Ok(v) => Ok(ok_v(json_to_value(&v))),
200 Err(e) => Ok(err_v(Value::Str(format!("{e}").into()))),
201 }
202 }
203 ("json", "parse_strict") => {
206 let s = expect_str(args.first())?;
207 let required = required_field_names(args.get(1))?;
208 let schema = extract_type_schema(args.get(2));
209 match serde_json::from_str::<serde_json::Value>(s) {
210 Ok(v) => {
211 if let Err(e) = check_required_fields(&v, &required) {
212 return Ok(err_v(Value::Str(e.into())));
213 }
214 if let Err(e) = validate_field_types(&v, &schema) {
215 return Ok(err_v(Value::Str(e.into())));
216 }
217 Ok(ok_v(apply_option_wrapping(json_to_value(&v), &v, &schema)))
218 }
219 Err(e) => Ok(err_v(Value::Str(format!("{e}").into()))),
220 }
221 }
222
223 ("toml", "parse") => {
228 let s = expect_str(args.first())?;
229 match toml::from_str::<serde_json::Value>(s) {
230 Ok(mut v) => {
231 unwrap_toml_datetime_markers(&mut v);
232 Ok(ok_v(json_to_value(&v)))
233 }
234 Err(e) => Ok(err_v(Value::Str(format!("{e}").into()))),
235 }
236 }
237 ("json", "parse_strict_typed") => {
241 let s = expect_str(args.first())?;
242 let required = required_field_names(args.get(1))?;
243 let schema = extract_type_schema(args.get(2));
244 match serde_json::from_str::<serde_json::Value>(s) {
245 Ok(v) => {
246 if let Err(e) = check_required_fields(&v, &required) {
247 return Ok(err_v(Value::Str(e.into())));
248 }
249 if let Err(e) = validate_field_types(&v, &schema) {
250 return Ok(err_v(Value::Str(e.into())));
251 }
252 Ok(ok_v(apply_option_wrapping(json_to_value(&v), &v, &schema)))
253 }
254 Err(e) => Ok(err_v(Value::Str(format!("{e}").into()))),
255 }
256 }
257
258 ("toml", "parse_strict") => {
261 let s = expect_str(args.first())?;
262 let required = required_field_names(args.get(1))?;
263 let schema = extract_type_schema(args.get(2));
264 match toml::from_str::<serde_json::Value>(s) {
265 Ok(mut v) => {
266 unwrap_toml_datetime_markers(&mut v);
267 if let Err(e) = check_required_fields(&v, &required) {
268 return Ok(err_v(Value::Str(e.into())));
269 }
270 if let Err(e) = validate_field_types(&v, &schema) {
271 return Ok(err_v(Value::Str(e.into())));
272 }
273 Ok(ok_v(apply_option_wrapping(json_to_value(&v), &v, &schema)))
274 }
275 Err(e) => Ok(err_v(Value::Str(format!("{e}").into()))),
276 }
277 }
278 ("toml", "parse_strict_typed") => {
279 let s = expect_str(args.first())?;
280 let required = required_field_names(args.get(1))?;
281 let schema = extract_type_schema(args.get(2));
282 match toml::from_str::<serde_json::Value>(s) {
283 Ok(mut v) => {
284 unwrap_toml_datetime_markers(&mut v);
285 if let Err(e) = check_required_fields(&v, &required) {
286 return Ok(err_v(Value::Str(e.into())));
287 }
288 if let Err(e) = validate_field_types(&v, &schema) {
289 return Ok(err_v(Value::Str(e.into())));
290 }
291 Ok(ok_v(apply_option_wrapping(json_to_value(&v), &v, &schema)))
292 }
293 Err(e) => Ok(err_v(Value::Str(format!("{e}").into()))),
294 }
295 }
296 ("toml", "stringify") => {
297 let v = first_arg(args)?;
298 let json = value_to_json(v);
304 match toml::to_string(&json) {
305 Ok(s) => Ok(ok_v(Value::Str(s.into()))),
306 Err(e) => Ok(err_v(Value::Str(format!("toml.stringify: {e}").into()))),
307 }
308 }
309
310 ("yaml", "parse") => {
316 let s = expect_str(args.first())?;
317 match serde_yaml::from_str::<serde_json::Value>(s) {
318 Ok(v) => Ok(ok_v(json_to_value(&v))),
319 Err(e) => Ok(err_v(Value::Str(format!("{e}").into()))),
320 }
321 }
322 ("yaml", "parse_strict") => {
325 let s = expect_str(args.first())?;
326 let required = required_field_names(args.get(1))?;
327 let schema = extract_type_schema(args.get(2));
328 match serde_yaml::from_str::<serde_json::Value>(s) {
329 Ok(v) => {
330 if let Err(e) = check_required_fields(&v, &required) {
331 return Ok(err_v(Value::Str(e.into())));
332 }
333 if let Err(e) = validate_field_types(&v, &schema) {
334 return Ok(err_v(Value::Str(e.into())));
335 }
336 Ok(ok_v(apply_option_wrapping(json_to_value(&v), &v, &schema)))
337 }
338 Err(e) => Ok(err_v(Value::Str(format!("{e}").into()))),
339 }
340 }
341 ("yaml", "parse_strict_typed") => {
342 let s = expect_str(args.first())?;
343 let required = required_field_names(args.get(1))?;
344 let schema = extract_type_schema(args.get(2));
345 match serde_yaml::from_str::<serde_json::Value>(s) {
346 Ok(v) => {
347 if let Err(e) = check_required_fields(&v, &required) {
348 return Ok(err_v(Value::Str(e.into())));
349 }
350 if let Err(e) = validate_field_types(&v, &schema) {
351 return Ok(err_v(Value::Str(e.into())));
352 }
353 Ok(ok_v(apply_option_wrapping(json_to_value(&v), &v, &schema)))
354 }
355 Err(e) => Ok(err_v(Value::Str(format!("{e}").into()))),
356 }
357 }
358 ("yaml", "stringify") => {
359 let v = first_arg(args)?;
360 let json = value_to_json(v);
361 match serde_yaml::to_string(&json) {
362 Ok(s) => Ok(ok_v(Value::Str(s.into()))),
363 Err(e) => Ok(err_v(Value::Str(format!("yaml.stringify: {e}").into()))),
364 }
365 }
366
367 ("dotenv", "parse") => {
375 use std::collections::BTreeMap;
376 use lex_bytecode::MapKey;
377 let s = expect_str(args.first())?;
378 match parse_dotenv(s) {
379 Ok(map) => {
380 let mut bt: BTreeMap<MapKey, Value> = BTreeMap::new();
381 for (k, v) in map {
382 bt.insert(MapKey::Str(k), Value::Str(v.into()));
383 }
384 Ok(ok_v(Value::Map(bt)))
385 }
386 Err(e) => Ok(err_v(Value::Str(e.into()))),
387 }
388 }
389
390 ("csv", "parse") => {
395 let s = expect_str(args.first())?;
396 let mut rdr = csv::ReaderBuilder::new()
397 .has_headers(false)
398 .flexible(true)
399 .from_reader(s.as_bytes());
400 let mut rows: std::collections::VecDeque<Value> = std::collections::VecDeque::new();
401 for r in rdr.records() {
402 match r {
403 Ok(rec) => {
404 let row: std::collections::VecDeque<Value> = rec.iter()
405 .map(|f| Value::Str(f.into()))
406 .collect();
407 rows.push_back(Value::List(row.into()));
408 }
409 Err(e) => return Ok(err_v(Value::Str(format!("csv.parse: {e}").into()))),
410 }
411 }
412 Ok(ok_v(Value::List(rows.into())))
413 }
414 ("csv", "stringify") => {
415 let v = first_arg(args)?;
420 let rows = match v {
421 Value::List(rs) => rs,
422 _ => return Ok(err_v(Value::Str("csv.stringify expects List[List[Str]]".into()))),
423 };
424 let mut out = Vec::new();
425 {
426 let mut wtr = csv::WriterBuilder::new()
427 .has_headers(false)
428 .from_writer(&mut out);
429 for row in rows {
430 let cells = match row {
431 Value::List(cs) => cs,
432 _ => return Ok(err_v(Value::Str("csv.stringify row must be List[Str]".into()))),
433 };
434 let strs: Vec<String> = cells.iter().map(|c| match c {
435 Value::Str(s) => s.to_string(),
436 other => serde_json::to_string(&other.to_json())
437 .unwrap_or_else(|_| String::new()),
438 }).collect();
439 if let Err(e) = wtr.write_record(&strs) {
440 return Ok(err_v(Value::Str(format!("csv.stringify: {e}").into())));
441 }
442 }
443 if let Err(e) = wtr.flush() {
444 return Ok(err_v(Value::Str(format!("csv.stringify flush: {e}").into())));
445 }
446 }
447 match String::from_utf8(out) {
448 Ok(s) => Ok(ok_v(Value::Str(s.into()))),
449 Err(e) => Ok(err_v(Value::Str(format!("csv.stringify utf8: {e}").into()))),
450 }
451 }
452
453 ("test", "assert_eq") => {
460 let a = first_arg(args)?;
461 let b = args.get(1).ok_or("test.assert_eq: missing second arg")?;
462 if a == b {
463 Ok(ok_v(Value::Unit))
464 } else {
465 Ok(err_v(Value::Str(format!("assert_eq: lhs {} != rhs {}",
466 value_to_json(a), value_to_json(b)).into())))
467 }
468 }
469 ("test", "assert_ne") => {
470 let a = first_arg(args)?;
471 let b = args.get(1).ok_or("test.assert_ne: missing second arg")?;
472 if a != b {
473 Ok(ok_v(Value::Unit))
474 } else {
475 Ok(err_v(Value::Str(format!("assert_ne: both sides are {}",
476 value_to_json(a)).into())))
477 }
478 }
479 ("test", "assert_true") => {
480 match first_arg(args)? {
481 Value::Bool(true) => Ok(ok_v(Value::Unit)),
482 Value::Bool(false) => Ok(err_v(Value::Str("assert_true: was false".into()))),
483 other => Err(format!("test.assert_true expects Bool, got {other:?}")),
484 }
485 }
486 ("test", "assert_false") => {
487 match first_arg(args)? {
488 Value::Bool(false) => Ok(ok_v(Value::Unit)),
489 Value::Bool(true) => Ok(err_v(Value::Str("assert_false: was true".into()))),
490 other => Err(format!("test.assert_false expects Bool, got {other:?}")),
491 }
492 }
493
494 ("bytes", "len") => {
496 let b = expect_bytes(args.first())?;
497 Ok(Value::Int(b.len() as i64))
498 }
499 ("bytes", "eq") => {
500 let a = expect_bytes(args.first())?;
501 let b = expect_bytes(args.get(1))?;
502 Ok(Value::Bool(a == b))
503 }
504 ("bytes", "from_str") => {
505 let s = expect_str(args.first())?;
506 Ok(Value::Bytes(s.as_bytes().to_vec()))
507 }
508 ("bytes", "to_str") => {
509 let b = expect_bytes(args.first())?;
510 match String::from_utf8(b.to_vec()) {
511 Ok(s) => Ok(ok_v(Value::Str(s.into()))),
512 Err(e) => Ok(err_v(Value::Str(format!("{e}").into()))),
513 }
514 }
515 ("bytes", "slice") => {
516 let b = expect_bytes(args.first())?;
517 let lo = expect_int(args.get(1))? as usize;
518 let hi = expect_int(args.get(2))? as usize;
519 if lo > hi || hi > b.len() {
520 return Err(format!("bytes.slice: out of range [{lo}..{hi}] of {}", b.len()));
521 }
522 Ok(Value::Bytes(b[lo..hi].to_vec()))
523 }
524 ("bytes", "is_empty") => {
525 let b = expect_bytes(args.first())?;
526 Ok(Value::Bool(b.is_empty()))
527 }
528 ("bytes", "concat") => {
529 let a = expect_bytes(args.first())?;
530 let b = expect_bytes(args.get(1))?;
531 let mut out = a.clone();
532 out.extend_from_slice(b);
533 Ok(Value::Bytes(out))
534 }
535 ("bytes", "concat_all") => {
536 let items = match args.first() {
537 Some(Value::List(xs)) => xs,
538 other => return Err(format!("bytes.concat_all expects a List[Bytes], got {other:?}")),
539 };
540 let mut out = Vec::new();
541 for item in items {
542 match item {
543 Value::Bytes(b) => out.extend_from_slice(b),
544 other => return Err(format!("bytes.concat_all: list element is not Bytes, got {other:?}")),
545 }
546 }
547 Ok(Value::Bytes(out))
548 }
549 ("bytes", "u8") => {
550 let n = expect_int(args.first())?;
551 Ok(Value::Bytes(vec![n as u8]))
552 }
553 ("bytes", "u16_le") => {
554 let n = expect_int(args.first())?;
555 Ok(Value::Bytes((n as u16).to_le_bytes().to_vec()))
556 }
557 ("bytes", "u32_le") => {
558 let n = expect_int(args.first())?;
559 Ok(Value::Bytes((n as u32).to_le_bytes().to_vec()))
560 }
561 ("bytes", "u64_le") => {
562 let n = expect_int(args.first())?;
563 Ok(Value::Bytes((n as u64).to_le_bytes().to_vec()))
564 }
565 ("bytes", "u8_at") => {
566 let b = expect_bytes(args.first())?;
567 let off = expect_int(args.get(1))? as usize;
568 match b.get(off) {
569 Some(&byte) => Ok(ok_v(Value::Int(byte as i64))),
570 None => Ok(err_v(Value::Str(format!("bytes.u8_at: offset {off} out of range of {} bytes", b.len()).into()))),
571 }
572 }
573 ("bytes", "u16_le_at") => {
574 let b = expect_bytes(args.first())?;
575 let off = expect_int(args.get(1))? as usize;
576 match b.get(off..off + 2) {
577 Some(slice) => {
578 let arr: [u8; 2] = slice.try_into().unwrap();
579 Ok(ok_v(Value::Int(u16::from_le_bytes(arr) as i64)))
580 }
581 None => Ok(err_v(Value::Str(format!("bytes.u16_le_at: offset {off} out of range of {} bytes", b.len()).into()))),
582 }
583 }
584 ("bytes", "u32_le_at") => {
585 let b = expect_bytes(args.first())?;
586 let off = expect_int(args.get(1))? as usize;
587 match b.get(off..off + 4) {
588 Some(slice) => {
589 let arr: [u8; 4] = slice.try_into().unwrap();
590 Ok(ok_v(Value::Int(u32::from_le_bytes(arr) as i64)))
591 }
592 None => Ok(err_v(Value::Str(format!("bytes.u32_le_at: offset {off} out of range of {} bytes", b.len()).into()))),
593 }
594 }
595 ("bytes", "u64_le_at") => {
596 let b = expect_bytes(args.first())?;
597 let off = expect_int(args.get(1))? as usize;
598 match b.get(off..off + 8) {
599 Some(slice) => {
600 let arr: [u8; 8] = slice.try_into().unwrap();
601 Ok(ok_v(Value::Int(u64::from_le_bytes(arr) as i64)))
602 }
603 None => Ok(err_v(Value::Str(format!("bytes.u64_le_at: offset {off} out of range of {} bytes", b.len()).into()))),
604 }
605 }
606
607 ("math", "exp") => Ok(Value::Float(expect_float(args.first())?.exp())),
614 ("math", "log") => Ok(Value::Float(expect_float(args.first())?.ln())),
615 ("math", "log2") => Ok(Value::Float(expect_float(args.first())?.log2())),
616 ("math", "log10") => Ok(Value::Float(expect_float(args.first())?.log10())),
617 ("math", "sqrt") => Ok(Value::Float(expect_float(args.first())?.sqrt())),
618 ("math", "abs") => Ok(Value::Float(expect_float(args.first())?.abs())),
619 ("math", "sin") => Ok(Value::Float(expect_float(args.first())?.sin())),
620 ("math", "cos") => Ok(Value::Float(expect_float(args.first())?.cos())),
621 ("math", "tan") => Ok(Value::Float(expect_float(args.first())?.tan())),
622 ("math", "asin") => Ok(Value::Float(expect_float(args.first())?.asin())),
623 ("math", "acos") => Ok(Value::Float(expect_float(args.first())?.acos())),
624 ("math", "atan") => Ok(Value::Float(expect_float(args.first())?.atan())),
625 ("math", "floor") => Ok(Value::Float(expect_float(args.first())?.floor())),
626 ("math", "ceil") => Ok(Value::Float(expect_float(args.first())?.ceil())),
627 ("math", "round") => Ok(Value::Float(expect_float(args.first())?.round())),
628 ("math", "trunc") => Ok(Value::Float(expect_float(args.first())?.trunc())),
629 ("math", "pow") => {
630 let a = expect_float(args.first())?;
631 let b = expect_float(args.get(1))?;
632 Ok(Value::Float(a.powf(b)))
633 }
634 ("math", "atan2") => {
635 let y = expect_float(args.first())?;
636 let x = expect_float(args.get(1))?;
637 Ok(Value::Float(y.atan2(x)))
638 }
639 ("math", "min") => {
640 let a = expect_float(args.first())?;
641 let b = expect_float(args.get(1))?;
642 Ok(Value::Float(a.min(b)))
643 }
644 ("math", "max") => {
645 let a = expect_float(args.first())?;
646 let b = expect_float(args.get(1))?;
647 Ok(Value::Float(a.max(b)))
648 }
649 ("math", "zeros") => {
650 let r = expect_int(args.first())?;
651 let c = expect_int(args.get(1))?;
652 if r < 0 || c < 0 {
653 return Err(format!("math.zeros: negative dim {r}x{c}"));
654 }
655 let r = r as usize; let c = c as usize;
656 Ok(Value::F64Array { rows: r as u32, cols: c as u32, data: vec![0.0; r * c] })
657 }
658 ("math", "ones") => {
659 let r = expect_int(args.first())?;
660 let c = expect_int(args.get(1))?;
661 if r < 0 || c < 0 {
662 return Err(format!("math.ones: negative dim {r}x{c}"));
663 }
664 let r = r as usize; let c = c as usize;
665 Ok(Value::F64Array { rows: r as u32, cols: c as u32, data: vec![1.0; r * c] })
666 }
667 ("math", "from_lists") => {
668 let rows = expect_list(args.first())?;
669 let r = rows.len();
670 if r == 0 {
671 return Ok(Value::F64Array { rows: 0, cols: 0, data: Vec::new() });
672 }
673 let first_row = match &rows[0] {
674 Value::List(xs) => xs,
675 other => return Err(format!("math.from_lists: row 0 not List, got {other:?}")),
676 };
677 let c = first_row.len();
678 let mut data = Vec::with_capacity(r * c);
679 for (i, row) in rows.iter().enumerate() {
680 let row = match row {
681 Value::List(xs) => xs,
682 other => return Err(format!("math.from_lists: row {i} not List, got {other:?}")),
683 };
684 if row.len() != c {
685 return Err(format!("math.from_lists: row {i} has {} cols, expected {c}", row.len()));
686 }
687 for (j, v) in row.iter().enumerate() {
688 let f = match v {
689 Value::Float(f) => *f,
690 Value::Int(n) => *n as f64,
691 other => return Err(format!("math.from_lists: ({i},{j}) not numeric, got {other:?}")),
692 };
693 data.push(f);
694 }
695 }
696 Ok(Value::F64Array { rows: r as u32, cols: c as u32, data })
697 }
698 ("math", "from_flat") => {
699 let r = expect_int(args.first())?;
700 let c = expect_int(args.get(1))?;
701 let xs = expect_list(args.get(2))?;
702 if r < 0 || c < 0 {
703 return Err(format!("math.from_flat: negative dim {r}x{c}"));
704 }
705 let r = r as usize; let c = c as usize;
706 if xs.len() != r * c {
707 return Err(format!("math.from_flat: list len {} != {}*{}", xs.len(), r, c));
708 }
709 let mut data = Vec::with_capacity(r * c);
710 for v in xs {
711 data.push(match v {
712 Value::Float(f) => *f,
713 Value::Int(n) => *n as f64,
714 other => return Err(format!("math.from_flat: non-numeric element {other:?}")),
715 });
716 }
717 Ok(Value::F64Array { rows: r as u32, cols: c as u32, data })
718 }
719 ("math", "rows") => {
720 let (r, _, _) = unpack_matrix(first_arg(args)?)?;
721 Ok(Value::Int(r as i64))
722 }
723 ("math", "cols") => {
724 let (_, c, _) = unpack_matrix(first_arg(args)?)?;
725 Ok(Value::Int(c as i64))
726 }
727 ("math", "get") => {
728 let (r, c, data) = unpack_matrix(first_arg(args)?)?;
729 let i = expect_int(args.get(1))? as usize;
730 let j = expect_int(args.get(2))? as usize;
731 if i >= r || j >= c {
732 return Err(format!("math.get: ({i},{j}) out of {r}x{c}"));
733 }
734 Ok(Value::Float(data[i * c + j]))
735 }
736 ("math", "to_flat") => {
737 let (_, _, data) = unpack_matrix(first_arg(args)?)?;
738 Ok(Value::List(data.into_iter().map(Value::Float).collect()))
739 }
740 ("math", "transpose") => {
741 let (r, c, data) = unpack_matrix(first_arg(args)?)?;
742 let mut out = vec![0.0; r * c];
743 for i in 0..r {
744 for j in 0..c {
745 out[j * r + i] = data[i * c + j];
746 }
747 }
748 Ok(Value::F64Array { rows: c as u32, cols: r as u32, data: out })
749 }
750 ("math", "matmul") => {
751 let (m, k1, a) = unpack_matrix(first_arg(args)?)?;
752 let (k2, n, b) = unpack_matrix(args.get(1).ok_or("math.matmul: missing arg 1")?)?;
753 if k1 != k2 {
754 return Err(format!("math.matmul: dim mismatch {m}x{k1} · {k2}x{n}"));
755 }
756 let mut c = vec![0.0; m * n];
760 for i in 0..m {
761 for kk in 0..k1 {
762 let aik = a[i * k1 + kk];
763 for j in 0..n {
764 c[i * n + j] += aik * b[kk * n + j];
765 }
766 }
767 }
768 Ok(Value::F64Array { rows: m as u32, cols: n as u32, data: c })
769 }
770 ("math", "scale") => {
771 let s = expect_float(args.first())?;
772 let (r, c, mut data) = unpack_matrix(args.get(1).ok_or("math.scale: missing arg 1")?)?;
773 for x in &mut data { *x *= s; }
774 Ok(Value::F64Array { rows: r as u32, cols: c as u32, data })
775 }
776 ("math", "add") | ("math", "sub") => {
777 let (ar, ac, a) = unpack_matrix(first_arg(args)?)?;
778 let (br, bc, b) = unpack_matrix(args.get(1).ok_or("math.add/sub: missing arg 1")?)?;
779 if ar != br || ac != bc {
780 return Err(format!("math.{op}: shape mismatch {ar}x{ac} vs {br}x{bc}"));
781 }
782 let neg = op == "sub";
783 let mut out = a;
784 for (i, x) in out.iter_mut().enumerate() {
785 if neg { *x -= b[i] } else { *x += b[i] }
786 }
787 Ok(Value::F64Array { rows: ar as u32, cols: ac as u32, data: out })
788 }
789 ("math", "sigmoid") => {
790 let (r, c, mut data) = unpack_matrix(first_arg(args)?)?;
791 for x in &mut data { *x = 1.0 / (1.0 + (-*x).exp()); }
792 Ok(Value::F64Array { rows: r as u32, cols: c as u32, data })
793 }
794
795 ("map", "new") => Ok(Value::Map(BTreeMap::new())),
797 ("map", "size") => Ok(Value::Int(expect_map(args.first())?.len() as i64)),
798 ("map", "has") => {
799 let m = expect_map(args.first())?;
800 let k = MapKey::from_value(args.get(1).ok_or("map.has: missing key")?)?;
801 Ok(Value::Bool(m.contains_key(&k)))
802 }
803 ("map", "get") => {
804 let m = expect_map(args.first())?;
805 let k = MapKey::from_value(args.get(1).ok_or("map.get: missing key")?)?;
806 Ok(match m.get(&k) {
807 Some(v) => some(v.clone()),
808 None => none(),
809 })
810 }
811 ("map", "set") => {
812 let mut m = expect_map(args.first())?.clone();
813 let k = MapKey::from_value(args.get(1).ok_or("map.set: missing key")?)?;
814 let v = args.get(2).ok_or("map.set: missing value")?.clone();
815 m.insert(k, v);
816 Ok(Value::Map(m))
817 }
818 ("map", "delete") => {
819 let mut m = expect_map(args.first())?.clone();
820 let k = MapKey::from_value(args.get(1).ok_or("map.delete: missing key")?)?;
821 m.remove(&k);
822 Ok(Value::Map(m))
823 }
824 ("map", "keys") => {
825 let m = expect_map(args.first())?;
826 Ok(Value::List(m.keys().cloned().map(MapKey::into_value).collect()))
827 }
828 ("map", "values") => {
829 let m = expect_map(args.first())?;
830 Ok(Value::List(m.values().cloned().collect()))
831 }
832 ("map", "entries") => {
833 let m = expect_map(args.first())?;
834 Ok(Value::List(m.iter()
835 .map(|(k, v)| Value::Tuple(vec![k.as_value(), v.clone()]))
836 .collect()))
837 }
838 ("map", "from_list") => {
839 let pairs = expect_list(args.first())?;
840 let mut m = BTreeMap::new();
841 for p in pairs {
842 let items = match p {
843 Value::Tuple(items) if items.len() == 2 => items,
844 other => return Err(format!(
845 "map.from_list element must be a 2-tuple, got {other:?}")),
846 };
847 let k = MapKey::from_value(&items[0])?;
848 m.insert(k, items[1].clone());
849 }
850 Ok(Value::Map(m))
851 }
852
853 ("set", "new") => Ok(Value::Set(BTreeSet::new())),
855 ("set", "size") => Ok(Value::Int(expect_set(args.first())?.len() as i64)),
856 ("set", "has") => {
857 let s = expect_set(args.first())?;
858 let k = MapKey::from_value(args.get(1).ok_or("set.has: missing element")?)?;
859 Ok(Value::Bool(s.contains(&k)))
860 }
861 ("set", "add") => {
862 let mut s = expect_set(args.first())?.clone();
863 let k = MapKey::from_value(args.get(1).ok_or("set.add: missing element")?)?;
864 s.insert(k);
865 Ok(Value::Set(s))
866 }
867 ("set", "delete") => {
868 let mut s = expect_set(args.first())?.clone();
869 let k = MapKey::from_value(args.get(1).ok_or("set.delete: missing element")?)?;
870 s.remove(&k);
871 Ok(Value::Set(s))
872 }
873 ("set", "to_list") => {
874 let s = expect_set(args.first())?;
875 Ok(Value::List(s.iter().cloned().map(MapKey::into_value).collect()))
876 }
877 ("set", "from_list") => {
878 let xs = expect_list(args.first())?;
879 let mut s = BTreeSet::new();
880 for x in xs {
881 s.insert(MapKey::from_value(x)?);
882 }
883 Ok(Value::Set(s))
884 }
885 ("set", "union") => {
886 let a = expect_set(args.first())?;
887 let b = expect_set(args.get(1))?;
888 Ok(Value::Set(a.union(b).cloned().collect()))
889 }
890 ("set", "intersect") => {
891 let a = expect_set(args.first())?;
892 let b = expect_set(args.get(1))?;
893 Ok(Value::Set(a.intersection(b).cloned().collect()))
894 }
895 ("set", "diff") => {
896 let a = expect_set(args.first())?;
897 let b = expect_set(args.get(1))?;
898 Ok(Value::Set(a.difference(b).cloned().collect()))
899 }
900 ("set", "is_empty") => Ok(Value::Bool(expect_set(args.first())?.is_empty())),
901 ("set", "is_subset") => {
902 let a = expect_set(args.first())?;
903 let b = expect_set(args.get(1))?;
904 Ok(Value::Bool(a.is_subset(b)))
905 }
906
907 ("map", "merge") => {
909 let a = expect_map(args.first())?.clone();
912 let b = expect_map(args.get(1))?;
913 let mut out = a;
914 for (k, v) in b {
915 out.insert(k.clone(), v.clone());
916 }
917 Ok(Value::Map(out))
918 }
919 ("map", "is_empty") => Ok(Value::Bool(expect_map(args.first())?.is_empty())),
920
921 ("deque", "new") => Ok(Value::Deque(std::collections::VecDeque::new())),
923 ("deque", "size") => Ok(Value::Int(expect_deque(args.first())?.len() as i64)),
924 ("deque", "is_empty") => Ok(Value::Bool(expect_deque(args.first())?.is_empty())),
925 ("deque", "push_back") => {
926 let mut d = expect_deque(args.first())?.clone();
927 let x = args.get(1).ok_or("deque.push_back: missing value")?.clone();
928 d.push_back(x);
929 Ok(Value::Deque(d))
930 }
931 ("deque", "push_front") => {
932 let mut d = expect_deque(args.first())?.clone();
933 let x = args.get(1).ok_or("deque.push_front: missing value")?.clone();
934 d.push_front(x);
935 Ok(Value::Deque(d))
936 }
937 ("deque", "pop_back") => {
938 let mut d = expect_deque(args.first())?.clone();
939 match d.pop_back() {
940 Some(x) => Ok(Value::Variant {
941 name: "Some".into(),
942 args: vec![Value::Tuple(vec![x, Value::Deque(d)])],
943 }),
944 None => Ok(Value::Variant { name: "None".into(), args: vec![] }),
945 }
946 }
947 ("deque", "pop_front") => {
948 let mut d = expect_deque(args.first())?.clone();
949 match d.pop_front() {
950 Some(x) => Ok(Value::Variant {
951 name: "Some".into(),
952 args: vec![Value::Tuple(vec![x, Value::Deque(d)])],
953 }),
954 None => Ok(Value::Variant { name: "None".into(), args: vec![] }),
955 }
956 }
957 ("deque", "peek_back") => {
958 let d = expect_deque(args.first())?;
959 match d.back() {
960 Some(x) => Ok(Value::Variant {
961 name: "Some".into(),
962 args: vec![x.clone()],
963 }),
964 None => Ok(Value::Variant { name: "None".into(), args: vec![] }),
965 }
966 }
967 ("deque", "peek_front") => {
968 let d = expect_deque(args.first())?;
969 match d.front() {
970 Some(x) => Ok(Value::Variant {
971 name: "Some".into(),
972 args: vec![x.clone()],
973 }),
974 None => Ok(Value::Variant { name: "None".into(), args: vec![] }),
975 }
976 }
977 ("deque", "from_list") => {
978 let xs = expect_list(args.first())?;
979 Ok(Value::Deque(xs.iter().cloned().collect()))
980 }
981 ("deque", "to_list") => {
982 let d = expect_deque(args.first())?;
983 Ok(Value::List(d.iter().cloned().collect()))
984 }
985
986 ("crypto", "sha256") => {
989 use sha2::{Digest, Sha256};
990 let data = expect_bytes(args.first())?;
991 let mut h = Sha256::new();
992 h.update(data);
993 Ok(Value::Bytes(h.finalize().to_vec()))
994 }
995 ("crypto", "sha512") => {
996 use sha2::{Digest, Sha512};
997 let data = expect_bytes(args.first())?;
998 let mut h = Sha512::new();
999 h.update(data);
1000 Ok(Value::Bytes(h.finalize().to_vec()))
1001 }
1002 ("crypto", "md5") => {
1003 use md5::{Digest, Md5};
1004 let data = expect_bytes(args.first())?;
1005 let mut h = Md5::new();
1006 h.update(data);
1007 Ok(Value::Bytes(h.finalize().to_vec()))
1008 }
1009 ("crypto", "blake2b") => {
1013 use blake2::{Blake2b512, Digest};
1014 let data = expect_bytes(args.first())?;
1015 let mut h = Blake2b512::new();
1016 h.update(data);
1017 Ok(Value::Bytes(h.finalize().to_vec()))
1018 }
1019 ("crypto", "keccak256") => {
1024 use sha3::{Digest, Keccak256};
1025 let data = expect_bytes(args.first())?;
1026 let mut h = Keccak256::new();
1027 h.update(data);
1028 Ok(Value::Bytes(h.finalize().to_vec()))
1029 }
1030 ("crypto", "sha256_str") => {
1034 use sha2::{Digest, Sha256};
1035 let s = expect_str(args.first())?;
1036 let mut h = Sha256::new();
1037 h.update(s.as_bytes());
1038 Ok(Value::Str(hex::encode(h.finalize()).into()))
1039 }
1040 ("crypto", "sha512_str") => {
1041 use sha2::{Digest, Sha512};
1042 let s = expect_str(args.first())?;
1043 let mut h = Sha512::new();
1044 h.update(s.as_bytes());
1045 Ok(Value::Str(hex::encode(h.finalize()).into()))
1046 }
1047 ("crypto", "hmac_sha256") => {
1048 use hmac::{Hmac, KeyInit, Mac};
1049 type HmacSha256 = Hmac<sha2::Sha256>;
1050 let key = expect_bytes(args.first())?;
1051 let data = expect_bytes(args.get(1))?;
1052 let mut mac = HmacSha256::new_from_slice(key)
1053 .map_err(|e| format!("hmac_sha256 key: {e}"))?;
1054 mac.update(data);
1055 Ok(Value::Bytes(mac.finalize().into_bytes().to_vec()))
1056 }
1057 ("crypto", "hmac_sha512") => {
1058 use hmac::{Hmac, KeyInit, Mac};
1059 type HmacSha512 = Hmac<sha2::Sha512>;
1060 let key = expect_bytes(args.first())?;
1061 let data = expect_bytes(args.get(1))?;
1062 let mut mac = HmacSha512::new_from_slice(key)
1063 .map_err(|e| format!("hmac_sha512 key: {e}"))?;
1064 mac.update(data);
1065 Ok(Value::Bytes(mac.finalize().into_bytes().to_vec()))
1066 }
1067 ("crypto", "ed25519_public_key") => {
1071 use ed25519_dalek::SigningKey;
1072 let secret = expect_bytes(args.first())?;
1073 let seed: [u8; 32] = match secret.as_slice().try_into() {
1074 Ok(s) => s,
1075 Err(_) => return Ok(err_v(Value::Str("ed25519_public_key: secret must be 32 bytes".into()))),
1076 };
1077 let sk = SigningKey::from_bytes(&seed);
1078 Ok(ok_v(Value::Bytes(sk.verifying_key().to_bytes().to_vec())))
1079 }
1080 ("crypto", "ed25519_sign") => {
1081 use ed25519_dalek::{Signer, SigningKey};
1082 let secret = expect_bytes(args.first())?;
1083 let message = expect_bytes(args.get(1))?;
1084 let seed: [u8; 32] = match secret.as_slice().try_into() {
1085 Ok(s) => s,
1086 Err(_) => return Ok(err_v(Value::Str("ed25519_sign: secret must be 32 bytes".into()))),
1087 };
1088 let sk = SigningKey::from_bytes(&seed);
1089 Ok(ok_v(Value::Bytes(sk.sign(message).to_bytes().to_vec())))
1090 }
1091 ("crypto", "ed25519_verify") => {
1092 use ed25519_dalek::{Signature, Verifier, VerifyingKey};
1093 let public = expect_bytes(args.first())?;
1094 let message = expect_bytes(args.get(1))?;
1095 let sig_bytes = expect_bytes(args.get(2))?;
1096 let pk_arr: [u8; 32] = match public.as_slice().try_into() {
1097 Ok(p) => p,
1098 Err(_) => return Ok(Value::Bool(false)),
1099 };
1100 let sig_arr: [u8; 64] = match sig_bytes.as_slice().try_into() {
1101 Ok(s) => s,
1102 Err(_) => return Ok(Value::Bool(false)),
1103 };
1104 let vk = match VerifyingKey::from_bytes(&pk_arr) {
1105 Ok(v) => v,
1106 Err(_) => return Ok(Value::Bool(false)),
1107 };
1108 let sig = Signature::from_bytes(&sig_arr);
1109 Ok(Value::Bool(vk.verify(message, &sig).is_ok()))
1110 }
1111 ("crypto", "ed25519_is_valid_point") => {
1112 use ed25519_dalek::VerifyingKey;
1113 let candidate = expect_bytes(args.first())?;
1114 let arr: [u8; 32] = match candidate.as_slice().try_into() {
1115 Ok(a) => a,
1116 Err(_) => return Ok(Value::Bool(false)),
1117 };
1118 Ok(Value::Bool(VerifyingKey::from_bytes(&arr).is_ok()))
1119 }
1120 ("crypto", "p256_public_key") => {
1132 use p256::ecdsa::SigningKey;
1133 let secret = expect_bytes(args.first())?;
1134 let sk = match SigningKey::from_slice(secret) {
1135 Ok(k) => k,
1136 Err(_) => return Ok(err_v(Value::Str(
1137 "p256_public_key: secret must be a 32-byte P-256 scalar".into()))),
1138 };
1139 let point = sk.verifying_key().to_encoded_point(true);
1140 Ok(ok_v(Value::Bytes(point.as_bytes().to_vec())))
1141 }
1142 ("crypto", "p256_sign") => {
1143 use p256::ecdsa::{signature::Signer, Signature, SigningKey};
1144 let secret = expect_bytes(args.first())?;
1145 let message = expect_bytes(args.get(1))?;
1146 let sk = match SigningKey::from_slice(secret) {
1147 Ok(k) => k,
1148 Err(_) => return Ok(err_v(Value::Str(
1149 "p256_sign: secret must be a 32-byte P-256 scalar".into()))),
1150 };
1151 let sig: Signature = sk.sign(message);
1152 Ok(ok_v(Value::Bytes(sig.to_der().as_bytes().to_vec())))
1153 }
1154 ("crypto", "p256_verify") => {
1155 use p256::ecdsa::{signature::Verifier, Signature, VerifyingKey};
1156 let public = expect_bytes(args.first())?;
1157 let message = expect_bytes(args.get(1))?;
1158 let sig_bytes = expect_bytes(args.get(2))?;
1159 let vk = match VerifyingKey::from_sec1_bytes(public) {
1160 Ok(v) => v,
1161 Err(_) => return Ok(Value::Bool(false)),
1162 };
1163 let sig = match Signature::from_der(sig_bytes) {
1164 Ok(s) => s,
1165 Err(_) => return Ok(Value::Bool(false)),
1166 };
1167 Ok(Value::Bool(vk.verify(message, &sig).is_ok()))
1168 }
1169 ("crypto", "secp256k1_public_key") => {
1180 use k256::ecdsa::SigningKey;
1181 let secret = expect_bytes(args.first())?;
1182 let sk = match SigningKey::from_slice(secret) {
1183 Ok(k) => k,
1184 Err(_) => return Ok(err_v(Value::Str(
1185 "secp256k1_public_key: secret must be a 32-byte secp256k1 scalar".into()))),
1186 };
1187 let point = sk.verifying_key().to_encoded_point(false);
1190 Ok(ok_v(Value::Bytes(point.as_bytes().to_vec())))
1191 }
1192 ("crypto", "secp256k1_sign_digest") => {
1193 use k256::ecdsa::SigningKey;
1194 let secret = expect_bytes(args.first())?;
1195 let digest = expect_bytes(args.get(1))?;
1196 if digest.len() != 32 {
1197 return Ok(err_v(Value::Str(
1198 "secp256k1_sign_digest: digest must be exactly 32 bytes".into())));
1199 }
1200 let sk = match SigningKey::from_slice(secret) {
1201 Ok(k) => k,
1202 Err(_) => return Ok(err_v(Value::Str(
1203 "secp256k1_sign_digest: secret must be a 32-byte secp256k1 scalar".into()))),
1204 };
1205 match sk.sign_prehash_recoverable(digest) {
1208 Ok((sig, recid)) => {
1209 let mut out = sig.to_bytes().to_vec(); out.push(27u8 + recid.to_byte());
1211 Ok(ok_v(Value::Bytes(out)))
1212 }
1213 Err(e) => Ok(err_v(Value::Str(
1214 format!("secp256k1_sign_digest: {e}").into()))),
1215 }
1216 }
1217 ("crypto", "secp256k1_recover") => {
1218 use k256::ecdsa::{RecoveryId, Signature, VerifyingKey};
1219 let digest = expect_bytes(args.first())?;
1220 let sig_bytes = expect_bytes(args.get(1))?;
1221 if digest.len() != 32 {
1222 return Ok(err_v(Value::Str(
1223 "secp256k1_recover: digest must be exactly 32 bytes".into())));
1224 }
1225 if sig_bytes.len() != 65 {
1226 return Ok(err_v(Value::Str(
1227 "secp256k1_recover: signature must be 65 bytes (r‖s‖v)".into())));
1228 }
1229 let sig = match Signature::from_slice(&sig_bytes[..64]) {
1230 Ok(s) => s,
1231 Err(_) => return Ok(err_v(Value::Str(
1232 "secp256k1_recover: malformed r‖s".into()))),
1233 };
1234 let v = sig_bytes[64];
1236 let recid_byte = if v >= 27 { v - 27 } else { v };
1237 let recid = match RecoveryId::from_byte(recid_byte) {
1238 Some(r) => r,
1239 None => return Ok(err_v(Value::Str(
1240 "secp256k1_recover: invalid recovery id".into()))),
1241 };
1242 match VerifyingKey::recover_from_prehash(digest, &sig, recid) {
1243 Ok(vk) => Ok(ok_v(Value::Bytes(
1244 vk.to_encoded_point(false).as_bytes().to_vec()))),
1245 Err(e) => Ok(err_v(Value::Str(
1246 format!("secp256k1_recover: {e}").into()))),
1247 }
1248 }
1249 ("crypto", "secp256k1_verify") => {
1250 use k256::ecdsa::{signature::hazmat::PrehashVerifier, Signature, VerifyingKey};
1251 let public = expect_bytes(args.first())?;
1252 let digest = expect_bytes(args.get(1))?;
1253 let sig_bytes = expect_bytes(args.get(2))?;
1254 if digest.len() != 32 {
1255 return Ok(Value::Bool(false));
1256 }
1257 let vk = match VerifyingKey::from_sec1_bytes(public) {
1258 Ok(v) => v,
1259 Err(_) => return Ok(Value::Bool(false)),
1260 };
1261 let rs = if sig_bytes.len() == 65 { &sig_bytes[..64] } else { sig_bytes.as_slice() };
1263 let sig = match Signature::from_slice(rs) {
1264 Ok(s) => s,
1265 Err(_) => return Ok(Value::Bool(false)),
1266 };
1267 Ok(Value::Bool(vk.verify_prehash(digest, &sig).is_ok()))
1268 }
1269 ("crypto", "base64_encode") => {
1270 use base64::{Engine, engine::general_purpose::STANDARD};
1271 let data = expect_bytes(args.first())?;
1272 Ok(Value::Str(STANDARD.encode(data).into()))
1273 }
1274 ("crypto", "base64_decode") => {
1275 use base64::{Engine, engine::general_purpose::STANDARD};
1276 let s = expect_str(args.first())?;
1277 match STANDARD.decode(s) {
1278 Ok(b) => Ok(ok_v(Value::Bytes(b))),
1279 Err(e) => Ok(err_v(Value::Str(format!("base64: {e}").into()))),
1280 }
1281 }
1282 ("crypto", "base64url_encode") => {
1286 use base64::{Engine, engine::general_purpose::URL_SAFE_NO_PAD};
1287 let data = expect_bytes(args.first())?;
1288 Ok(Value::Str(URL_SAFE_NO_PAD.encode(data).into()))
1289 }
1290 ("crypto", "base64url_decode") => {
1291 use base64::{Engine, engine::general_purpose::URL_SAFE_NO_PAD};
1292 let s = expect_str(args.first())?;
1293 match URL_SAFE_NO_PAD.decode(s) {
1294 Ok(b) => Ok(ok_v(Value::Bytes(b))),
1295 Err(e) => Ok(err_v(Value::Str(format!("base64url: {e}").into()))),
1296 }
1297 }
1298 ("crypto", "hex_encode") => {
1299 let data = expect_bytes(args.first())?;
1300 Ok(Value::Str(hex::encode(data).into()))
1301 }
1302 ("crypto", "hex_decode") => {
1303 let s = expect_str(args.first())?;
1304 match hex::decode(s) {
1305 Ok(b) => Ok(ok_v(Value::Bytes(b))),
1306 Err(e) => Ok(err_v(Value::Str(format!("hex: {e}").into()))),
1307 }
1308 }
1309 ("crypto", "base58_encode") => {
1313 let data = expect_bytes(args.first())?;
1314 Ok(Value::Str(bs58::encode(data).into_string().into()))
1315 }
1316 ("crypto", "base58_decode") => {
1317 let s = expect_str(args.first())?;
1318 match bs58::decode(s).into_vec() {
1319 Ok(b) => Ok(ok_v(Value::Bytes(b))),
1320 Err(e) => Ok(err_v(Value::Str(format!("base58: {e}").into()))),
1321 }
1322 }
1323 ("crypto", "constant_time_eq") | ("crypto", "eq") => {
1324 use subtle::ConstantTimeEq;
1325 let a = expect_bytes(args.first())?;
1326 let b = expect_bytes(args.get(1))?;
1327 let eq = if a.len() == b.len() {
1336 a.ct_eq(b).into()
1337 } else {
1338 false
1339 };
1340 Ok(Value::Bool(eq))
1341 }
1342 ("crypto", "eq_str") => {
1345 use subtle::ConstantTimeEq;
1346 let a = expect_str(args.first())?;
1347 let b = expect_str(args.get(1))?;
1348 let eq = if a.len() == b.len() {
1349 a.as_bytes().ct_eq(b.as_bytes()).into()
1350 } else {
1351 false
1352 };
1353 Ok(Value::Bool(eq))
1354 }
1355
1356 ("crypto", "aes_gcm_seal") => Ok(aes_gcm_seal_impl(args)),
1366 ("crypto", "aes_gcm_open") => Ok(aes_gcm_open_impl(args)),
1367 ("crypto", "chacha20_poly1305_seal") => Ok(chacha20_seal_impl(args)),
1368 ("crypto", "chacha20_poly1305_open") => Ok(chacha20_open_impl(args)),
1369 ("crypto", "aes_cbc_encrypt_raw") => Ok(aes_cbc_impl(args, true)),
1370 ("crypto", "aes_cbc_decrypt_raw") => Ok(aes_cbc_impl(args, false)),
1371 ("crypto", "pbkdf2_sha256") => Ok(pbkdf2_sha256_impl(args)),
1372 ("crypto", "hkdf_sha256") => Ok(hkdf_sha256_impl(args)),
1373 ("crypto", "argon2id") => Ok(argon2id_impl(args)),
1374
1375 ("random", "seed") => {
1382 let s = args.first().ok_or("random.seed: missing arg")?.as_int();
1383 let mixed = splitmix64(s as u64).0;
1388 Ok(rng_value(mixed))
1389 }
1390 ("random", "int") => {
1391 let state = rng_decode(args.first())?;
1392 let lo = args.get(1).ok_or("random.int: missing lo")?.as_int();
1393 let hi = args.get(2).ok_or("random.int: missing hi")?.as_int();
1394 if hi < lo {
1395 return Err(format!(
1396 "random.int: hi ({hi}) must be >= lo ({lo})"));
1397 }
1398 let span = (hi as i128) - (lo as i128) + 1;
1399 let (raw, next_state) = splitmix64(state);
1400 let drawn = lo as i128 + (raw as u128 % span as u128) as i128;
1405 Ok(Value::Tuple(vec![
1406 Value::Int(drawn as i64),
1407 rng_value(next_state),
1408 ]))
1409 }
1410 ("random", "float") => {
1411 let state = rng_decode(args.first())?;
1412 let (raw, next_state) = splitmix64(state);
1413 let f = ((raw >> 11) as f64) / ((1u64 << 53) as f64);
1416 Ok(Value::Tuple(vec![Value::Float(f), rng_value(next_state)]))
1417 }
1418 ("random", "choose") => {
1419 let state = rng_decode(args.first())?;
1420 let xs = match args.get(1) {
1421 Some(Value::List(xs)) => xs,
1422 _ => return Err("random.choose: expected List".into()),
1423 };
1424 if xs.is_empty() {
1425 return Ok(Value::Variant {
1426 name: "None".into(), args: vec![],
1427 });
1428 }
1429 let (raw, next_state) = splitmix64(state);
1430 let idx = (raw as usize) % xs.len();
1431 let pick = xs[idx].clone();
1432 Ok(Value::Variant {
1433 name: "Some".into(),
1434 args: vec![Value::Tuple(vec![pick, rng_value(next_state)])],
1435 })
1436 }
1437
1438 ("parser", "char") => {
1443 let s = expect_str(args.first())?;
1444 if s.chars().count() != 1 {
1445 return Err(format!(
1446 "parser.char: expected 1-character string, got {s:?}"));
1447 }
1448 Ok(parser_node("Char", &[("ch", Value::Str(s.into()))]))
1449 }
1450 ("parser", "string") => {
1451 let s = expect_str(args.first())?;
1452 Ok(parser_node("String", &[("s", Value::Str(s.into()))]))
1453 }
1454 ("parser", "digit") => Ok(parser_node("Digit", &[])),
1455 ("parser", "alpha") => Ok(parser_node("Alpha", &[])),
1456 ("parser", "whitespace") => Ok(parser_node("Whitespace", &[])),
1457 ("parser", "eof") => Ok(parser_node("Eof", &[])),
1458 ("parser", "seq") => {
1459 let a = args.first().cloned()
1460 .ok_or_else(|| "parser.seq: missing first parser".to_string())?;
1461 let b = args.get(1).cloned()
1462 .ok_or_else(|| "parser.seq: missing second parser".to_string())?;
1463 Ok(parser_node("Seq", &[("a", a), ("b", b)]))
1464 }
1465 ("parser", "alt") => {
1466 let a = args.first().cloned()
1467 .ok_or_else(|| "parser.alt: missing first parser".to_string())?;
1468 let b = args.get(1).cloned()
1469 .ok_or_else(|| "parser.alt: missing second parser".to_string())?;
1470 Ok(parser_node("Alt", &[("a", a), ("b", b)]))
1471 }
1472 ("parser", "many") => {
1473 let p = args.first().cloned()
1474 .ok_or_else(|| "parser.many: missing inner parser".to_string())?;
1475 Ok(parser_node("Many", &[("p", p)]))
1476 }
1477 ("parser", "optional") => {
1478 let p = args.first().cloned()
1479 .ok_or_else(|| "parser.optional: missing inner parser".to_string())?;
1480 Ok(parser_node("Optional", &[("p", p)]))
1481 }
1482 ("parser", "map") => {
1486 let p = args.first().cloned()
1487 .ok_or_else(|| "parser.map: missing parser".to_string())?;
1488 let f = args.get(1).cloned()
1489 .ok_or_else(|| "parser.map: missing closure".to_string())?;
1490 Ok(parser_node("Map", &[("p", p), ("f", f)]))
1491 }
1492 ("parser", "and_then") => {
1493 let p = args.first().cloned()
1494 .ok_or_else(|| "parser.and_then: missing parser".to_string())?;
1495 let f = args.get(1).cloned()
1496 .ok_or_else(|| "parser.and_then: missing closure".to_string())?;
1497 Ok(parser_node("AndThen", &[("p", p), ("f", f)]))
1498 }
1499 ("regex", "compile") => {
1509 let pat = expect_str(args.first())?;
1510 match get_or_compile_regex(pat) {
1511 Ok(_) => Ok(ok_v(Value::Str(pat.into()))),
1512 Err(e) => Ok(err_v(Value::Str(e.into()))),
1513 }
1514 }
1515 ("regex", "is_match") => {
1516 let pat = expect_str(args.first())?;
1517 let s = expect_str(args.get(1))?;
1518 let re = get_or_compile_regex(pat).map_err(|e| format!("regex.is_match: {e}"))?;
1519 Ok(Value::Bool(re.is_match(s)))
1520 }
1521 ("regex", "is_match_str") => {
1526 let pat = expect_str(args.first())?;
1527 let s = expect_str(args.get(1))?;
1528 match get_or_compile_regex(pat) {
1529 Ok(re) => Ok(Value::Bool(re.is_match(s))),
1530 Err(_) => Ok(Value::Bool(false)),
1531 }
1532 }
1533 ("regex", "find") => {
1534 let pat = expect_str(args.first())?;
1535 let s = expect_str(args.get(1))?;
1536 let re = get_or_compile_regex(pat).map_err(|e| format!("regex.find: {e}"))?;
1537 match re.captures(s) {
1538 Some(caps) => Ok(Value::Variant {
1539 name: "Some".into(),
1540 args: vec![match_value(&caps)],
1541 }),
1542 None => Ok(Value::Variant { name: "None".into(), args: vec![] }),
1543 }
1544 }
1545 ("regex", "find_all") => {
1546 let pat = expect_str(args.first())?;
1547 let s = expect_str(args.get(1))?;
1548 let re = get_or_compile_regex(pat).map_err(|e| format!("regex.find_all: {e}"))?;
1549 let items: std::collections::VecDeque<Value> = re.captures_iter(s).map(|caps| match_value(&caps)).collect();
1550 Ok(Value::List(items.into()))
1551 }
1552 ("regex", "replace") => {
1553 let pat = expect_str(args.first())?;
1554 let s = expect_str(args.get(1))?;
1555 let rep = expect_str(args.get(2))?;
1556 let re = get_or_compile_regex(pat).map_err(|e| format!("regex.replace: {e}"))?;
1557 Ok(Value::Str(re.replace(s, rep).into_owned().into()))
1558 }
1559 ("regex", "replace_all") => {
1560 let pat = expect_str(args.first())?;
1561 let s = expect_str(args.get(1))?;
1562 let rep = expect_str(args.get(2))?;
1563 let re = get_or_compile_regex(pat).map_err(|e| format!("regex.replace_all: {e}"))?;
1564 Ok(Value::Str(re.replace_all(s, rep).into_owned().into()))
1565 }
1566 ("datetime", "parse_iso") => {
1569 let s = expect_str(args.first())?;
1570 match chrono::DateTime::parse_from_rfc3339(s) {
1571 Ok(dt) => Ok(ok_v(Value::Int(instant_from_chrono(dt)))),
1572 Err(e) => Ok(err_v(Value::Str(format!("parse_iso: {e}").into()))),
1573 }
1574 }
1575 ("datetime", "format_iso") => {
1576 let n = expect_int(args.first())?;
1577 Ok(Value::Str(format_iso(n).into()))
1578 }
1579 ("datetime", "parse") => {
1580 let s = expect_str(args.first())?;
1581 let fmt = expect_str(args.get(1))?;
1582 match chrono::NaiveDateTime::parse_from_str(s, fmt) {
1583 Ok(naive) => {
1584 use chrono::TimeZone;
1585 match chrono::Utc.from_local_datetime(&naive).single() {
1586 Some(dt) => Ok(ok_v(Value::Int(instant_from_chrono(dt)))),
1587 None => Ok(err_v(Value::Str("parse: ambiguous local time".into()))),
1588 }
1589 }
1590 Err(e) => Ok(err_v(Value::Str(format!("parse: {e}").into()))),
1591 }
1592 }
1593 ("datetime", "format") => {
1594 let n = expect_int(args.first())?;
1595 let fmt = expect_str(args.get(1))?;
1596 let dt = chrono_from_instant(n);
1597 Ok(Value::Str(dt.format(fmt).to_string().into()))
1598 }
1599 ("datetime", "to_components") => {
1600 let n = expect_int(args.first())?;
1601 let tz = match parse_tz_arg(args.get(1)) {
1602 Ok(t) => t,
1603 Err(e) => return Ok(err_v(Value::Str(e.into()))),
1604 };
1605 match resolve_tz_to_components(n, &tz) {
1606 Ok(rec) => Ok(ok_v(rec)),
1607 Err(e) => Ok(err_v(Value::Str(e.into()))),
1608 }
1609 }
1610 ("datetime", "from_components") => {
1611 let rec = match args.first() {
1612 Some(Value::Record { fields: r, .. }) => r.clone(),
1613 _ => return Err("from_components: expected DateTime record".into()),
1614 };
1615 match instant_from_components(&rec) {
1616 Ok(n) => Ok(ok_v(Value::Int(n))),
1617 Err(e) => Ok(err_v(Value::Str(e.into()))),
1618 }
1619 }
1620 ("datetime", "add") => {
1621 let a = expect_int(args.first())?;
1622 let d = expect_int(args.get(1))?;
1623 Ok(Value::Int(a.saturating_add(d)))
1624 }
1625 ("datetime", "diff") => {
1626 let a = expect_int(args.first())?;
1627 let b = expect_int(args.get(1))?;
1628 Ok(Value::Int(a.saturating_sub(b)))
1629 }
1630 ("datetime", "duration_seconds") => {
1631 let s = expect_float(args.first())?;
1632 let nanos = (s * 1_000_000_000.0) as i64;
1633 Ok(Value::Int(nanos))
1634 }
1635 ("datetime", "duration_minutes") => {
1636 let m = expect_int(args.first())?;
1637 Ok(Value::Int(m.saturating_mul(60_000_000_000)))
1638 }
1639 ("datetime", "duration_days") => {
1640 let d = expect_int(args.first())?;
1641 Ok(Value::Int(d.saturating_mul(86_400_000_000_000)))
1642 }
1643 ("datetime", "before") => {
1645 let a = expect_int(args.first())?;
1646 let b = expect_int(args.get(1))?;
1647 Ok(Value::Bool(a < b))
1648 }
1649 ("datetime", "after") => {
1650 let a = expect_int(args.first())?;
1651 let b = expect_int(args.get(1))?;
1652 Ok(Value::Bool(a > b))
1653 }
1654 ("datetime", "compare") => {
1655 let a = expect_int(args.first())?;
1656 let b = expect_int(args.get(1))?;
1657 Ok(Value::Int(a.cmp(&b) as i64))
1658 }
1659 ("duration", "millis") => Ok(Value::Int(expect_int(args.first())? / 1_000_000)),
1662 ("duration", "seconds") => Ok(Value::Int(expect_int(args.first())? / 1_000_000_000)),
1663 ("duration", "minutes") => Ok(Value::Int(expect_int(args.first())? / 60_000_000_000)),
1664 ("duration", "hours") => Ok(Value::Int(expect_int(args.first())? / 3_600_000_000_000)),
1665 ("duration", "days") => Ok(Value::Int(expect_int(args.first())? / 86_400_000_000_000)),
1666
1667 ("regex", "split") => {
1668 let pat = expect_str(args.first())?;
1669 let s = expect_str(args.get(1))?;
1670 let re = get_or_compile_regex(pat).map_err(|e| format!("regex.split: {e}"))?;
1671 let parts: std::collections::VecDeque<Value> = re.split(s).map(|p| Value::Str(p.into())).collect();
1672 Ok(Value::List(parts.into()))
1673 }
1674
1675 ("http", "with_header") => {
1678 let req = expect_record_pure(args.first())?.clone();
1679 let k = expect_str(args.get(1))?;
1680 let v = expect_str(args.get(2))?;
1681 Ok(Value::record_interned(http_set_header(req, k, v)))
1682 }
1683 ("http", "with_auth") => {
1684 let req = expect_record_pure(args.first())?.clone();
1685 let scheme = expect_str(args.get(1))?;
1686 let token = expect_str(args.get(2))?;
1687 let value = format!("{scheme} {token}");
1688 Ok(Value::record_interned(http_set_header(req, "Authorization", &value)))
1689 }
1690 ("http", "with_query") => {
1691 let req = expect_record_pure(args.first())?.clone();
1692 let params = match args.get(1) {
1693 Some(Value::Map(m)) => m.clone(),
1694 Some(other) => return Err(format!(
1695 "http.with_query: params must be Map[Str, Str], got {other:?}")),
1696 None => return Err("http.with_query: missing params argument".into()),
1697 };
1698 Ok(Value::record_interned(http_append_query(req, ¶ms)))
1699 }
1700 ("http", "with_timeout_ms") => {
1701 let req = expect_record_pure(args.first())?.clone();
1702 let ms = expect_int(args.get(1))?;
1703 let mut out = req;
1704 out.insert("timeout_ms".into(), Value::Variant {
1705 name: "Some".into(),
1706 args: vec![Value::Int(ms)],
1707 });
1708 Ok(Value::record_interned(out))
1709 }
1710 ("http", "json_body") => {
1711 let resp = expect_record_pure(args.first())?;
1712 let body = match resp.get("body") {
1713 Some(Value::Bytes(b)) => b.clone(),
1714 _ => return Err("http.json_body: HttpResponse.body must be Bytes".into()),
1715 };
1716 let s = match std::str::from_utf8(&body) {
1717 Ok(s) => s,
1718 Err(e) => return Ok(http_decode_err_pure(format!("body not UTF-8: {e}"))),
1719 };
1720 match serde_json::from_str::<serde_json::Value>(s) {
1721 Ok(j) => Ok(ok_v(Value::from_json(&j))),
1722 Err(e) => Ok(http_decode_err_pure(format!("json parse: {e}"))),
1723 }
1724 }
1725 ("http", "json_body_typed") => {
1733 let resp = expect_record_pure(args.first())?;
1734 let required = required_field_names(args.get(1))?;
1735 let schema = extract_type_schema(args.get(2));
1736 let body = match resp.get("body") {
1737 Some(Value::Bytes(b)) => b.clone(),
1738 _ => return Err("http.json_body: HttpResponse.body must be Bytes".into()),
1739 };
1740 let s = match std::str::from_utf8(&body) {
1741 Ok(s) => s,
1742 Err(e) => return Ok(http_decode_err_pure(format!("body not UTF-8: {e}"))),
1743 };
1744 match serde_json::from_str::<serde_json::Value>(s) {
1745 Ok(j) => {
1746 if let Err(e) = check_required_fields(&j, &required) {
1747 return Ok(http_decode_err_pure(e));
1748 }
1749 if let Err(e) = validate_field_types(&j, &schema) {
1750 return Ok(http_decode_err_pure(e));
1751 }
1752 Ok(ok_v(apply_option_wrapping(json_to_value(&j), &j, &schema)))
1753 }
1754 Err(e) => Ok(http_decode_err_pure(format!("json parse: {e}"))),
1755 }
1756 }
1757 ("http", "text_body") => {
1758 let resp = expect_record_pure(args.first())?;
1759 let body = match resp.get("body") {
1760 Some(Value::Bytes(b)) => b.clone(),
1761 _ => return Err("http.text_body: HttpResponse.body must be Bytes".into()),
1762 };
1763 match String::from_utf8(body) {
1764 Ok(s) => Ok(ok_v(Value::Str(s.into()))),
1765 Err(e) => Ok(http_decode_err_pure(format!("body not UTF-8: {e}"))),
1766 }
1767 }
1768
1769 ("cli", "flag") => {
1773 let name = expect_str(args.first())?;
1774 let short = opt_str(args.get(1));
1775 let help = expect_str(args.get(2))?;
1776 Ok(value_from_json(crate::cli::flag_spec(name, short.as_deref(), help)))
1777 }
1778 ("cli", "option") => {
1779 let name = expect_str(args.first())?;
1780 let short = opt_str(args.get(1));
1781 let help = expect_str(args.get(2))?;
1782 let default = opt_str(args.get(3));
1783 Ok(value_from_json(crate::cli::option_spec(name, short.as_deref(), help, default.as_deref())))
1784 }
1785 ("cli", "positional") => {
1786 let name = expect_str(args.first())?;
1787 let help = expect_str(args.get(1))?;
1788 let required = expect_bool(args.get(2))?;
1789 Ok(value_from_json(crate::cli::positional_spec(name, help, required)))
1790 }
1791 ("cli", "spec") => {
1792 let name = expect_str(args.first())?;
1793 let help = expect_str(args.get(1))?;
1794 let arg_specs: Vec<serde_json::Value> = expect_list(args.get(2))?
1795 .iter().map(value_to_json).collect();
1796 let subs: Vec<serde_json::Value> = expect_list(args.get(3))?
1797 .iter().map(value_to_json).collect();
1798 Ok(value_from_json(crate::cli::build_spec(name, help, arg_specs, subs)))
1799 }
1800 ("cli", "parse") => {
1801 let spec = value_to_json(args.first().unwrap_or(&Value::Unit));
1802 let argv: Vec<String> = expect_list(args.get(1))?
1803 .iter().map(|v| match v {
1804 Value::Str(s) => Ok(s.to_string()),
1805 other => Err(format!("cli.parse: argv must be List[Str], got {other:?}")),
1806 }).collect::<Result<_, _>>()?;
1807 match crate::cli::parse(&spec, &argv) {
1808 Ok(parsed) => Ok(ok_v(value_from_json(parsed))),
1809 Err(msg) => Ok(err_v(Value::Str(msg.into()))),
1810 }
1811 }
1812 ("cli", "envelope") => {
1813 let ok = expect_bool(args.first())?;
1814 let cmd = expect_str(args.get(1))?;
1815 let data = value_to_json(args.get(2).unwrap_or(&Value::Unit));
1816 Ok(value_from_json(crate::cli::envelope(ok, cmd, data)))
1817 }
1818 ("cli", "describe") => {
1819 let spec = value_to_json(args.first().unwrap_or(&Value::Unit));
1820 Ok(value_from_json(crate::cli::describe(&spec)))
1821 }
1822 ("cli", "help") => {
1823 let spec = value_to_json(args.first().unwrap_or(&Value::Unit));
1824 Ok(Value::Str(crate::cli::help_text(&spec).into()))
1825 }
1826
1827 ("arrow", op) => match crate::arrow::dispatch(op, args) {
1829 Some(r) => r,
1830 None => Err(format!("unknown pure builtin: arrow.{op}")),
1831 },
1832 #[cfg(feature = "df")]
1836 ("df", op) => match crate::df::dispatch(op, args) {
1837 Some(r) => r,
1838 None => Err(format!("unknown pure builtin: df.{op}")),
1839 },
1840 #[cfg(not(feature = "df"))]
1841 ("df", op) => Err(format!(
1842 "df.{op}: this build was compiled without the `df` feature; \
1843 Polars-backed dataframe query ops are unavailable"
1844 )),
1845
1846 ("decimal", "decimal") => {
1853 let coef = expect_int(args.first())?;
1854 let exp = expect_int(args.get(1))?;
1855 Ok(make_decimal(coef, exp))
1856 }
1857 ("decimal", "zero") => Ok(make_decimal(0, 0)),
1858 ("decimal", "one") => Ok(make_decimal(1, 0)),
1859 ("decimal", "from_int") => {
1860 Ok(make_decimal(expect_int(args.first())?, 0))
1861 }
1862 ("decimal", "pow10") => {
1863 Ok(Value::Int(decimal_pow10(expect_int(args.first())?)?))
1864 }
1865 ("decimal", "add") => {
1866 let (ca, ea) = expect_decimal(args.first())?;
1867 let (cb, eb) = expect_decimal(args.get(1))?;
1868 let (a2, b2, e) = decimal_align(ca, ea, cb, eb)?;
1869 Ok(make_decimal(
1870 a2.checked_add(b2).ok_or("decimal.add: overflow")?, e))
1871 }
1872 ("decimal", "sub") => {
1873 let (ca, ea) = expect_decimal(args.first())?;
1874 let (cb, eb) = expect_decimal(args.get(1))?;
1875 let (a2, b2, e) = decimal_align(ca, ea, cb, eb)?;
1876 Ok(make_decimal(
1877 a2.checked_sub(b2).ok_or("decimal.sub: overflow")?, e))
1878 }
1879 ("decimal", "mul") => {
1880 let (ca, ea) = expect_decimal(args.first())?;
1881 let (cb, eb) = expect_decimal(args.get(1))?;
1882 Ok(make_decimal(
1883 ca.checked_mul(cb).ok_or("decimal.mul: overflow")?,
1884 ea.checked_add(eb).ok_or("decimal.mul: exponent overflow")?,
1885 ))
1886 }
1887 ("decimal", "compare") => {
1888 let (ca, ea) = expect_decimal(args.first())?;
1889 let (cb, eb) = expect_decimal(args.get(1))?;
1890 let (a2, b2, _) = decimal_align(ca, ea, cb, eb)?;
1891 Ok(Value::Int(if a2 < b2 { -1 } else if a2 > b2 { 1 } else { 0 }))
1892 }
1893 ("decimal", "is_zero") => {
1894 let (c, _) = expect_decimal(args.first())?;
1895 Ok(Value::Bool(c == 0))
1896 }
1897 ("decimal", "is_positive") => {
1898 let (c, _) = expect_decimal(args.first())?;
1899 Ok(Value::Bool(c > 0))
1900 }
1901 ("decimal", "is_negative") => {
1902 let (c, _) = expect_decimal(args.first())?;
1903 Ok(Value::Bool(c < 0))
1904 }
1905 ("decimal", "negate") => {
1906 let (c, e) = expect_decimal(args.first())?;
1907 Ok(make_decimal(-c, e))
1908 }
1909 ("decimal", "abs") => {
1910 let (c, e) = expect_decimal(args.first())?;
1911 Ok(make_decimal(c.abs(), e))
1912 }
1913 ("decimal", "normalize") => {
1914 let (mut c, mut e) = expect_decimal(args.first())?;
1915 if c == 0 { return Ok(make_decimal(0, 0)); }
1916 while c % 10 == 0 { c /= 10; e += 1; }
1917 Ok(make_decimal(c, e))
1918 }
1919 ("decimal", "round_to") => {
1920 let (c, e) = expect_decimal(args.first())?;
1921 let target_e = expect_int(args.get(1))?;
1922 let mode = expect_str(args.get(2))?;
1923 Ok(make_decimal(decimal_round(c, e, target_e, mode)?, target_e))
1924 }
1925 ("decimal", "to_str") => {
1926 let (c, e) = expect_decimal(args.first())?;
1927 Ok(Value::Str(decimal_to_str(c, e)?.into()))
1928 }
1929
1930 _ => Err(format!("unknown pure builtin: {kind}.{op}")),
1931 }
1932}
1933
1934fn expect_decimal(v: Option<&Value>) -> Result<(i64, i64), String> {
1938 match v {
1939 Some(Value::Record { fields, .. }) => {
1940 let coef = match fields.get("coefficient") {
1941 Some(Value::Int(n)) => *n,
1942 _ => return Err("decimal: missing or invalid 'coefficient' field".into()),
1943 };
1944 let exp = match fields.get("exponent") {
1945 Some(Value::Int(n)) => *n,
1946 _ => return Err("decimal: missing or invalid 'exponent' field".into()),
1947 };
1948 Ok((coef, exp))
1949 }
1950 Some(other) => Err(format!("decimal: expected {{ coefficient, exponent }} record, got {other:?}")),
1951 None => Err("decimal: missing argument".into()),
1952 }
1953}
1954
1955fn make_decimal(coefficient: i64, exponent: i64) -> Value {
1957 let mut fields = indexmap::IndexMap::new();
1958 fields.insert("coefficient".into(), Value::Int(coefficient));
1959 fields.insert("exponent".into(), Value::Int(exponent));
1960 Value::record_interned(fields)
1961}
1962
1963fn decimal_pow10(n: i64) -> Result<i64, String> {
1965 if n < 0 { return Err(format!("decimal.pow10: negative exponent {n}")); }
1966 if n > 18 { return Err(format!("decimal.pow10: exponent {n} exceeds max (18)")); }
1967 Ok(10i64.pow(n as u32))
1968}
1969
1970fn decimal_align(ca: i64, ea: i64, cb: i64, eb: i64) -> Result<(i64, i64, i64), String> {
1973 if ea == eb { return Ok((ca, cb, ea)); }
1974 if ea > eb {
1975 let scale = decimal_pow10(ea - eb)?;
1976 let ca2 = ca.checked_mul(scale)
1977 .ok_or_else(|| format!("decimal: overflow aligning (shift {})", ea - eb))?;
1978 Ok((ca2, cb, eb))
1979 } else {
1980 let scale = decimal_pow10(eb - ea)?;
1981 let cb2 = cb.checked_mul(scale)
1982 .ok_or_else(|| format!("decimal: overflow aligning (shift {})", eb - ea))?;
1983 Ok((ca, cb2, ea))
1984 }
1985}
1986
1987fn decimal_round(c: i64, e: i64, target_e: i64, mode: &str) -> Result<i64, String> {
1992 if e >= target_e {
1993 let shift = e - target_e;
1995 let scale = decimal_pow10(shift)?;
1996 return c.checked_mul(scale)
1997 .ok_or_else(|| format!("decimal.round_to: overflow scaling (shift {shift})"));
1998 }
1999 let shift = target_e - e;
2001 let divisor = decimal_pow10(shift)?;
2002 let q = c / divisor;
2003 let r = c % divisor; if r == 0 { return Ok(q); }
2006
2007 let abs_r = r.abs();
2008 let positive = r > 0; let rounded = match mode {
2011 "Down" => q,
2012 "Up" => if positive { q + 1 } else { q - 1 },
2013 "Floor" => if positive { q } else { q - 1 },
2014 "Ceiling" => if positive { q + 1 } else { q },
2015 "HalfUp" => {
2016 if abs_r * 2 >= divisor {
2017 if positive { q + 1 } else { q - 1 }
2018 } else { q }
2019 }
2020 "HalfDown" => {
2021 if abs_r * 2 > divisor {
2022 if positive { q + 1 } else { q - 1 }
2023 } else { q }
2024 }
2025 "HalfEven" => {
2026 if abs_r * 2 > divisor {
2027 if positive { q + 1 } else { q - 1 }
2028 } else if abs_r * 2 == divisor {
2029 if q % 2 == 0 { q } else { if positive { q + 1 } else { q - 1 } }
2031 } else { q }
2032 }
2033 other => return Err(format!(
2034 "decimal.round_to: unknown rounding mode {other:?}; \
2035 valid modes: HalfUp HalfDown HalfEven Down Up Ceiling Floor")),
2036 };
2037 Ok(rounded)
2038}
2039
2040fn decimal_to_str(c: i64, e: i64) -> Result<String, String> {
2043 if e == 0 { return Ok(c.to_string()); }
2044 if e > 0 {
2045 let scale = decimal_pow10(e)?;
2046 let val = c.checked_mul(scale)
2047 .ok_or("decimal.to_str: overflow")?;
2048 return Ok(val.to_string());
2049 }
2050 let scale = decimal_pow10(-e)?;
2052 let sign = if c < 0 { "-" } else { "" };
2053 let abs_c = c.abs();
2054 let int_part = abs_c / scale;
2055 let frac_part = abs_c % scale;
2056 let decimal_places = (-e) as usize;
2057 Ok(format!("{sign}{int_part}.{frac_part:0>decimal_places$}"))
2058}
2059
2060fn opt_str(arg: Option<&Value>) -> Option<String> {
2064 match arg {
2065 Some(Value::Variant { name, args }) if name == "Some" => {
2066 args.first().and_then(|v| match v {
2067 Value::Str(s) => Some(s.to_string()),
2068 _ => None,
2069 })
2070 }
2071 _ => None,
2072 }
2073}
2074
2075fn value_from_json(v: serde_json::Value) -> Value { Value::from_json(&v) }
2076
2077fn regex_cache() -> &'static Mutex<HashMap<String, regex::Regex>> {
2082 static CACHE: OnceLock<Mutex<HashMap<String, regex::Regex>>> = OnceLock::new();
2083 CACHE.get_or_init(|| Mutex::new(HashMap::new()))
2084}
2085
2086fn get_or_compile_regex(pattern: &str) -> Result<regex::Regex, String> {
2087 let cache = regex_cache();
2088 {
2089 let guard = cache.lock().unwrap();
2090 if let Some(re) = guard.get(pattern) {
2091 return Ok(re.clone());
2092 }
2093 }
2094 let re = regex::Regex::new(pattern).map_err(|e| format!("invalid regex: {e}"))?;
2095 let mut guard = cache.lock().unwrap();
2096 guard.insert(pattern.to_string(), re.clone());
2097 Ok(re)
2098}
2099
2100fn match_value(caps: ®ex::Captures) -> Value {
2104 let m0 = caps.get(0).expect("regex match always has group 0");
2105 let mut rec = indexmap::IndexMap::new();
2106 rec.insert("text".into(), Value::Str(m0.as_str().into()));
2107 rec.insert("start".into(), Value::Int(m0.start() as i64));
2108 rec.insert("end".into(), Value::Int(m0.end() as i64));
2109 let groups: std::collections::VecDeque<Value> = (1..caps.len())
2110 .map(|i| {
2111 Value::Str(
2112 caps.get(i)
2113 .map(|m| m.as_str())
2114 .unwrap_or_default()
2115 .into(),
2116 )
2117 })
2118 .collect();
2119 rec.insert("groups".into(), Value::List(groups.into()));
2120 Value::record_dynamic(rec)
2121}
2122
2123fn expect_map(v: Option<&Value>) -> Result<&BTreeMap<MapKey, Value>, String> {
2124 match v {
2125 Some(Value::Map(m)) => Ok(m),
2126 other => Err(format!("expected Map, got {other:?}")),
2127 }
2128}
2129
2130fn expect_set(v: Option<&Value>) -> Result<&BTreeSet<MapKey>, String> {
2131 match v {
2132 Some(Value::Set(s)) => Ok(s),
2133 other => Err(format!("expected Set, got {other:?}")),
2134 }
2135}
2136
2137fn unpack_matrix(v: &Value) -> Result<(usize, usize, Vec<f64>), String> {
2141 if let Value::F64Array { rows, cols, data } = v {
2142 return Ok((*rows as usize, *cols as usize, data.clone()));
2143 }
2144 let rec = match v {
2145 Value::Record { fields: r, .. } => r,
2146 other => return Err(format!("expected matrix, got {other:?}")),
2147 };
2148 let rows = match rec.get("rows") {
2149 Some(Value::Int(n)) => *n as usize,
2150 _ => return Err("matrix: missing/invalid `rows`".into()),
2151 };
2152 let cols = match rec.get("cols") {
2153 Some(Value::Int(n)) => *n as usize,
2154 _ => return Err("matrix: missing/invalid `cols`".into()),
2155 };
2156 let data = match rec.get("data") {
2157 Some(Value::List(items)) => {
2158 let mut out = Vec::with_capacity(items.len());
2159 for it in items {
2160 out.push(match it {
2161 Value::Float(f) => *f,
2162 Value::Int(n) => *n as f64,
2163 other => return Err(format!("matrix data: not numeric, got {other:?}")),
2164 });
2165 }
2166 out
2167 }
2168 _ => return Err("matrix: missing/invalid `data`".into()),
2169 };
2170 if data.len() != rows * cols {
2171 return Err(format!("matrix: data len {} != {rows}*{cols}", data.len()));
2172 }
2173 Ok((rows, cols, data))
2174}
2175
2176fn expect_bytes(v: Option<&Value>) -> Result<&Vec<u8>, String> {
2177 match v {
2178 Some(Value::Bytes(b)) => Ok(b),
2179 Some(other) => Err(format!("expected Bytes, got {other:?}")),
2180 None => Err("missing argument".into()),
2181 }
2182}
2183
2184fn first_arg(args: &[Value]) -> Result<&Value, String> {
2185 args.first().ok_or_else(|| "missing argument".into())
2186}
2187
2188fn tuple_index(v: &Value, i: usize) -> Result<Value, String> {
2189 match v {
2190 Value::Tuple(items) => items.get(i).cloned()
2191 .ok_or_else(|| format!("tuple index {i} out of range (len={})", items.len())),
2192 other => Err(format!("expected Tuple, got {other:?}")),
2193 }
2194}
2195
2196pub(crate) fn str_arg(v: Option<&Value>) -> Result<&SmolStr, String> {
2199 match v {
2200 Some(Value::Str(s)) => Ok(s),
2201 Some(other) => Err(format!("expected Str, got {other:?}")),
2202 None => Err("missing argument".into()),
2203 }
2204}
2205
2206thread_local! {
2207 static CP_CURSOR: std::cell::RefCell<Option<(SmolStr, usize, usize)>> =
2218 const { std::cell::RefCell::new(None) };
2219}
2220
2221pub(crate) fn cp_to_byte(s: &SmolStr, cp: usize) -> usize {
2230 CP_CURSOR.with(|cur| {
2231 let mut cur = cur.borrow_mut();
2232 let cached = match &*cur {
2233 Some((cached, ccp, cbyte))
2234 if cached.as_ptr() == s.as_ptr() && cached.len() == s.len() =>
2235 {
2236 Some((*ccp, *cbyte))
2237 }
2238 _ => None,
2239 };
2240 let byte = match cached {
2241 Some((ccp, cbyte)) if ccp <= cp => s[cbyte..]
2242 .char_indices()
2243 .nth(cp - ccp)
2244 .map(|(b, _)| cbyte + b)
2245 .unwrap_or(s.len()),
2246 Some((ccp, cbyte)) if ccp - cp < cp => {
2249 let mut b = cbyte;
2250 for _ in 0..(ccp - cp) {
2251 b -= 1;
2252 while b > 0 && !s.is_char_boundary(b) {
2253 b -= 1;
2254 }
2255 }
2256 b
2257 }
2258 _ => s.char_indices().nth(cp).map(|(b, _)| b).unwrap_or(s.len()),
2259 };
2260 if byte < s.len() {
2261 *cur = Some((s.clone(), cp, byte));
2262 }
2263 byte
2264 })
2265}
2266
2267pub(crate) fn remember_cursor(s: &SmolStr, cp: usize, byte: usize) {
2269 if byte < s.len() {
2270 CP_CURSOR.with(|cur| *cur.borrow_mut() = Some((s.clone(), cp, byte)));
2271 }
2272}
2273
2274pub(crate) fn expect_str(v: Option<&Value>) -> Result<&str, String> {
2280 match v {
2281 Some(Value::Str(s)) => Ok(s.as_str()),
2282 Some(other) => Err(format!("expected Str, got {other:?}")),
2283 None => Err("missing argument".into()),
2284 }
2285}
2286
2287pub(crate) fn expect_int(v: Option<&Value>) -> Result<i64, String> {
2288 match v {
2289 Some(Value::Int(n)) => Ok(*n),
2290 Some(other) => Err(format!("expected Int, got {other:?}")),
2291 None => Err("missing argument".into()),
2292 }
2293}
2294
2295fn expect_float(v: Option<&Value>) -> Result<f64, String> {
2296 match v {
2297 Some(Value::Float(f)) => Ok(*f),
2298 Some(other) => Err(format!("expected Float, got {other:?}")),
2299 None => Err("missing argument".into()),
2300 }
2301}
2302
2303pub(crate) fn expect_list(v: Option<&Value>) -> Result<&std::collections::VecDeque<Value>, String> {
2304 match v {
2305 Some(Value::List(xs)) => Ok(xs),
2306 Some(other) => Err(format!("expected List, got {other:?}")),
2307 None => Err("missing argument".into()),
2308 }
2309}
2310
2311fn expect_bool(v: Option<&Value>) -> Result<bool, String> {
2312 match v {
2313 Some(Value::Bool(b)) => Ok(*b),
2314 Some(other) => Err(format!("expected Bool, got {other:?}")),
2315 None => Err("missing argument".into()),
2316 }
2317}
2318
2319fn expect_deque(v: Option<&Value>) -> Result<&std::collections::VecDeque<Value>, String> {
2320 match v {
2321 Some(Value::Deque(d)) => Ok(d),
2322 Some(other) => Err(format!("expected Deque, got {other:?}")),
2323 None => Err("missing argument".into()),
2324 }
2325}
2326
2327pub(crate) fn some(v: Value) -> Value { Value::Variant { name: "Some".into(), args: vec![v] } }
2328pub(crate) fn none() -> Value { Value::Variant { name: "None".into(), args: Vec::new() } }
2329fn ok_v(v: Value) -> Value { Value::Variant { name: "Ok".into(), args: vec![v] } }
2330fn err_v(v: Value) -> Value { Value::Variant { name: "Err".into(), args: vec![v] } }
2331
2332fn parser_node(kind: &str, fields: &[(&str, Value)]) -> Value {
2340 let mut r = indexmap::IndexMap::new();
2341 r.insert("kind".into(), Value::Str(kind.into()));
2342 for (k, v) in fields {
2343 r.insert((*k).into(), v.clone());
2344 }
2345 Value::record_dynamic(r)
2346}
2347
2348fn splitmix64(state: u64) -> (u64, u64) {
2359 let next = state.wrapping_add(0x9E37_79B9_7F4A_7C15);
2360 let mut z = next;
2361 z = (z ^ (z >> 30)).wrapping_mul(0xBF58_476D_1CE4_E5B9);
2362 z = (z ^ (z >> 27)).wrapping_mul(0x94D0_49BB_1331_11EB);
2363 let z = z ^ (z >> 31);
2364 (z, next)
2365}
2366
2367fn rng_value(state: u64) -> Value {
2371 let mut fields = indexmap::IndexMap::new();
2372 fields.insert("state".into(), Value::Int(state as i64));
2373 Value::record_dynamic(fields)
2374}
2375
2376fn rng_decode(v: Option<&Value>) -> Result<u64, String> {
2378 let rec = match v {
2379 Some(Value::Record { fields: r, .. }) => r,
2380 Some(other) => return Err(format!("expected Rng, got {other:?}")),
2381 None => return Err("missing Rng arg".into()),
2382 };
2383 match rec.get("state") {
2384 Some(Value::Int(n)) => Ok(*n as u64),
2385 _ => Err("malformed Rng: missing `state :: Int`".into()),
2386 }
2387}
2388
2389fn expect_record_pure(v: Option<&Value>) -> Result<&indexmap::IndexMap<smol_str::SmolStr, Value>, String> {
2392 match v {
2393 Some(Value::Record { fields: r, .. }) => Ok(r),
2394 Some(other) => Err(format!("expected Record, got {other:?}")),
2395 None => Err("missing Record argument".into()),
2396 }
2397}
2398
2399fn http_decode_err_pure(msg: String) -> Value {
2400 let inner = Value::Variant {
2401 name: "DecodeError".into(),
2402 args: vec![Value::Str(msg.into())],
2403 };
2404 err_v(inner)
2405}
2406
2407fn http_set_header(
2412 mut req: indexmap::IndexMap<smol_str::SmolStr, Value>,
2413 name: &str,
2414 value: &str,
2415) -> indexmap::IndexMap<smol_str::SmolStr, Value> {
2416 use lex_bytecode::MapKey;
2417 let mut headers = match req.shift_remove("headers") {
2418 Some(Value::Map(m)) => m,
2419 _ => std::collections::BTreeMap::new(),
2420 };
2421 let key = MapKey::Str(name.to_lowercase());
2422 let lowered = name.to_lowercase();
2425 headers.retain(|k, _| match k {
2426 MapKey::Str(s) => s.to_lowercase() != lowered,
2427 _ => true,
2428 });
2429 headers.insert(key, Value::Str(value.into()));
2430 req.insert("headers".into(), Value::Map(headers));
2431 req
2432}
2433
2434fn http_append_query(
2440 mut req: indexmap::IndexMap<smol_str::SmolStr, Value>,
2441 params: &std::collections::BTreeMap<lex_bytecode::MapKey, Value>,
2442) -> indexmap::IndexMap<smol_str::SmolStr, Value> {
2443 use lex_bytecode::MapKey;
2444 let url = match req.get("url") {
2445 Some(Value::Str(s)) => s.clone(),
2446 _ => return req,
2447 };
2448 let mut pieces = Vec::new();
2449 for (k, v) in params {
2450 let kk = match k { MapKey::Str(s) => s.to_string(), _ => continue };
2451 let vv = match v { Value::Str(s) => s.to_string(), _ => continue };
2452 pieces.push(format!("{}={}", url_encode(&kk), url_encode(&vv)));
2453 }
2454 if pieces.is_empty() { return req; }
2455 let sep = if url.contains('?') { '&' } else { '?' };
2456 let new_url = format!("{url}{sep}{}", pieces.join("&"));
2457 req.insert("url".into(), Value::Str(new_url.into()));
2458 req
2459}
2460
2461fn url_encode(s: &str) -> String {
2466 let mut out = String::with_capacity(s.len());
2467 for b in s.bytes() {
2468 match b {
2469 b'A'..=b'Z' | b'a'..=b'z' | b'0'..=b'9' | b'-' | b'_' | b'.' | b'~' => {
2470 out.push(b as char);
2471 }
2472 _ => out.push_str(&format!("%{:02X}", b)),
2473 }
2474 }
2475 out
2476}
2477
2478fn value_to_json(v: &Value) -> serde_json::Value { v.to_json() }
2479
2480fn unwrap_toml_datetime_markers(v: &mut serde_json::Value) {
2488 use serde_json::Value as J;
2489 match v {
2490 J::Object(map) => {
2491 if map.len() == 1 {
2495 if let Some(J::String(s)) = map.get("$__toml_private_datetime") {
2496 let s = s.clone();
2497 *v = J::String(s);
2498 return;
2499 }
2500 }
2501 for (_, child) in map.iter_mut() {
2502 unwrap_toml_datetime_markers(child);
2503 }
2504 }
2505 J::Array(items) => {
2506 for item in items.iter_mut() {
2507 unwrap_toml_datetime_markers(item);
2508 }
2509 }
2510 _ => {}
2511 }
2512}
2513
2514fn json_to_value(v: &serde_json::Value) -> Value { Value::from_json(v) }
2515
2516fn required_field_names(arg: Option<&Value>) -> Result<Vec<String>, String> {
2521 let list = expect_list(arg)?;
2522 let mut out = Vec::with_capacity(list.len());
2523 for v in list {
2524 match v {
2525 Value::Str(s) => out.push(s.to_string()),
2526 other => return Err(format!(
2527 "parse_strict: required-fields list must contain Str, got {other:?}"
2528 )),
2529 }
2530 }
2531 Ok(out)
2532}
2533
2534fn check_required_fields(
2555 value: &serde_json::Value,
2556 required: &[String],
2557) -> Result<(), String> {
2558 if required.is_empty() {
2559 return Ok(());
2560 }
2561 if !matches!(value, serde_json::Value::Object(_)) {
2562 return Err(format!(
2563 "parse_strict: expected top-level object with fields {:?}, got {value}",
2564 required
2565 ));
2566 }
2567 let mut missing: Vec<String> = Vec::new();
2568 for path in required {
2569 if !path_exists(value, path) {
2570 missing.push(path.clone());
2571 }
2572 }
2573 if missing.is_empty() {
2574 Ok(())
2575 } else {
2576 Err(format!("missing required field(s): {}", missing.join(", ")))
2577 }
2578}
2579
2580fn path_exists(value: &serde_json::Value, path: &str) -> bool {
2585 let mut cursor = value;
2586 let segments = split_dotted_path(path);
2587 for seg in &segments {
2588 match cursor {
2589 serde_json::Value::Object(o) => match o.get(seg.as_str()) {
2590 Some(next) => cursor = next,
2591 None => return false,
2592 },
2593 _ => return false,
2594 }
2595 }
2596 true
2597}
2598
2599fn split_dotted_path(path: &str) -> Vec<String> {
2603 let mut out: Vec<String> = Vec::new();
2604 let mut cur = String::new();
2605 let mut iter = path.chars().peekable();
2606 while let Some(c) = iter.next() {
2607 if c == '\\' {
2608 if let Some(&'.') = iter.peek() {
2610 cur.push('.');
2611 iter.next();
2612 continue;
2613 }
2614 cur.push(c);
2615 } else if c == '.' {
2616 out.push(std::mem::take(&mut cur));
2617 } else {
2618 cur.push(c);
2619 }
2620 }
2621 out.push(cur);
2622 out
2623}
2624
2625fn extract_type_schema(v: Option<&Value>) -> Vec<(String, String)> {
2629 match v {
2630 Some(Value::List(pairs)) => pairs.iter().filter_map(|p| {
2631 if let Value::Tuple(items) = p {
2632 if items.len() == 2 {
2633 if let (Value::Str(name), Value::Str(tag)) = (&items[0], &items[1]) {
2634 return Some((name.to_string(), tag.to_string()));
2635 }
2636 }
2637 }
2638 None
2639 }).collect(),
2640 _ => vec![],
2641 }
2642}
2643
2644fn validate_field_types(
2650 json: &serde_json::Value,
2651 schema: &[(String, String)],
2652) -> Result<(), String> {
2653 if schema.is_empty() {
2654 return Ok(());
2655 }
2656 let obj = match json.as_object() {
2657 Some(o) => o,
2658 None => return Ok(()), };
2660 for (field, tag) in schema {
2661 if let Some(val) = obj.get(field) {
2662 if let Err(e) = check_json_type(val, tag) {
2663 return Err(format!("field `{field}`: {e}"));
2664 }
2665 }
2666 }
2667 Ok(())
2668}
2669
2670fn apply_option_wrapping(v: Value, json: &serde_json::Value, schema: &[(String, String)]) -> Value {
2675 if schema.is_empty() {
2676 return v;
2677 }
2678 let fields = match v {
2679 Value::Record { fields, .. } => *fields,
2680 other => return other,
2681 };
2682 let json_obj = match json.as_object() {
2683 Some(o) => o,
2684 None => return Value::record_interned(fields),
2685 };
2686 let mut new_fields = fields;
2687 for (field_name, tag) in schema {
2688 if tag.starts_with("Option[") && tag.ends_with(']') {
2689 let json_val = json_obj.get(field_name.as_str());
2690 let wrapped = match json_val {
2691 None | Some(serde_json::Value::Null) => none(),
2692 Some(_) => {
2693 let inner = new_fields
2694 .get(field_name.as_str())
2695 .cloned()
2696 .unwrap_or(Value::Unit);
2697 some(inner)
2698 }
2699 };
2700 new_fields.insert(smol_str::SmolStr::from(field_name.as_str()), wrapped);
2701 }
2702 }
2703 Value::record_interned(new_fields)
2704}
2705
2706fn check_json_type(val: &serde_json::Value, tag: &str) -> Result<(), String> {
2708 use serde_json::Value as J;
2709 match (tag, val) {
2710 ("Int", J::Number(n)) if n.is_i64() || n.is_u64() => Ok(()),
2711 ("Int", other) => Err(format!("expected Int, got {}", json_type_name(other))),
2712 ("Float", J::Number(_)) => Ok(()),
2713 ("Float", other) => Err(format!("expected Float, got {}", json_type_name(other))),
2714 ("Bool", J::Bool(_)) => Ok(()),
2715 ("Bool", other) => Err(format!("expected Bool, got {}", json_type_name(other))),
2716 ("Str", J::String(_)) => Ok(()),
2717 ("Str", other) => Err(format!("expected Str, got {}", json_type_name(other))),
2718 (tag, J::Null) if tag.starts_with("Option[") => Ok(()),
2720 (tag, val) if tag.starts_with("Option[") && tag.ends_with(']') => {
2721 let inner = &tag[7..tag.len() - 1]; check_json_type(val, inner)
2723 }
2724 (tag, J::Array(items)) if tag.starts_with("List[") && tag.ends_with(']') => {
2726 let inner = &tag[5..tag.len() - 1]; for (i, item) in items.iter().enumerate() {
2728 if let Err(e) = check_json_type(item, inner) {
2729 return Err(format!("[{i}]: {e}"));
2730 }
2731 }
2732 Ok(())
2733 }
2734 ("Record", _) => Ok(()), ("Any", _) => Ok(()), _ => Ok(()), }
2738}
2739
2740fn json_type_name(v: &serde_json::Value) -> &'static str {
2741 match v {
2742 serde_json::Value::Null => "null",
2743 serde_json::Value::Bool(_) => "Bool",
2744 serde_json::Value::Number(_) => "Number",
2745 serde_json::Value::String(_) => "Str",
2746 serde_json::Value::Array(_) => "Array",
2747 serde_json::Value::Object(_) => "Object",
2748 }
2749}
2750
2751fn parse_dotenv(src: &str) -> Result<indexmap::IndexMap<String, String>, String> {
2762 let mut out = indexmap::IndexMap::new();
2763 for (idx, raw) in src.lines().enumerate() {
2764 let line = raw.trim();
2765 if line.is_empty() || line.starts_with('#') {
2766 continue;
2767 }
2768 let after_export = line.strip_prefix("export ").unwrap_or(line);
2771 let (k, v) = match after_export.split_once('=') {
2772 Some(kv) => kv,
2773 None => return Err(format!("dotenv.parse line {}: missing `=`", idx + 1)),
2774 };
2775 let key = k.trim();
2776 if key.is_empty() {
2777 return Err(format!("dotenv.parse line {}: empty key", idx + 1));
2778 }
2779 let v_trim = v.trim();
2780 let value = if let Some(q) = v_trim.strip_prefix('"').and_then(|s| s.strip_suffix('"')) {
2781 q.to_string()
2782 } else if let Some(q) = v_trim.strip_prefix('\'').and_then(|s| s.strip_suffix('\'')) {
2783 q.to_string()
2784 } else {
2785 v_trim.to_string()
2786 };
2787 out.insert(key.to_string(), value);
2788 }
2789 Ok(out)
2790}
2791
2792fn instant_from_chrono<Tz: chrono::TimeZone>(dt: chrono::DateTime<Tz>) -> i64 {
2798 dt.timestamp_nanos_opt().unwrap_or(i64::MAX)
2799}
2800
2801fn chrono_from_instant(n: i64) -> chrono::DateTime<chrono::Utc> {
2802 let secs = n.div_euclid(1_000_000_000);
2803 let nanos = n.rem_euclid(1_000_000_000) as u32;
2804 use chrono::TimeZone;
2805 chrono::Utc
2806 .timestamp_opt(secs, nanos)
2807 .single()
2808 .unwrap_or_else(chrono::Utc::now)
2809}
2810
2811fn format_iso(n: i64) -> String {
2812 chrono_from_instant(n).to_rfc3339()
2813}
2814
2815enum TzArg {
2818 Utc,
2819 Local,
2820 Offset(i32),
2822 Iana(String),
2824}
2825
2826fn parse_tz_arg(v: Option<&Value>) -> Result<TzArg, String> {
2827 match v {
2828 Some(Value::Variant { name, args }) => match (name.as_str(), args.as_slice()) {
2829 ("Utc", []) => Ok(TzArg::Utc),
2830 ("Local", []) => Ok(TzArg::Local),
2831 ("Offset", [Value::Int(m)]) => {
2832 let m = i32::try_from(*m).map_err(|_| {
2833 format!("Tz::Offset: minutes out of range: {m}")
2834 })?;
2835 Ok(TzArg::Offset(m))
2836 }
2837 ("Iana", [Value::Str(s)]) => Ok(TzArg::Iana(s.to_string())),
2838 (other, _) => Err(format!(
2839 "expected Tz variant (Utc | Local | Offset(Int) | Iana(Str)), got `{other}` with {} arg(s)",
2840 args.len()
2841 )),
2842 },
2843 Some(other) => Err(format!("expected Tz variant, got {other:?}")),
2844 None => Err("missing Tz argument".into()),
2845 }
2846}
2847
2848fn resolve_tz_to_components(n: i64, tz: &TzArg) -> Result<Value, String> {
2849 use chrono::{TimeZone, Datelike, Timelike, Offset};
2850 let utc_dt = chrono_from_instant(n);
2851 let (y, m, d, hh, mm, ss, ns, off_min) = match tz {
2852 TzArg::Utc => {
2853 let d = utc_dt;
2854 (d.year(), d.month() as i32, d.day() as i32,
2855 d.hour() as i32, d.minute() as i32, d.second() as i32,
2856 d.nanosecond() as i32, 0)
2857 }
2858 TzArg::Local => {
2859 let d = utc_dt.with_timezone(&chrono::Local);
2860 let off = d.offset().fix().local_minus_utc() / 60;
2861 (d.year(), d.month() as i32, d.day() as i32,
2862 d.hour() as i32, d.minute() as i32, d.second() as i32,
2863 d.nanosecond() as i32, off)
2864 }
2865 TzArg::Offset(off_min) => {
2866 let off_secs = off_min.saturating_mul(60);
2867 let fixed = chrono::FixedOffset::east_opt(off_secs)
2868 .ok_or("to_components: offset out of range")?;
2869 let d = utc_dt.with_timezone(&fixed);
2870 (d.year(), d.month() as i32, d.day() as i32,
2871 d.hour() as i32, d.minute() as i32, d.second() as i32,
2872 d.nanosecond() as i32, *off_min)
2873 }
2874 TzArg::Iana(name) => {
2875 let tz: chrono_tz::Tz = name.parse()
2876 .map_err(|e| format!("to_components: unknown timezone `{name}`: {e}"))?;
2877 let d = utc_dt.with_timezone(&tz);
2878 let off = d.offset().fix().local_minus_utc() / 60;
2879 (d.year(), d.month() as i32, d.day() as i32,
2880 d.hour() as i32, d.minute() as i32, d.second() as i32,
2881 d.nanosecond() as i32, off)
2882 }
2883 };
2884 let mut rec = indexmap::IndexMap::new();
2885 rec.insert("year".into(), Value::Int(y as i64));
2886 rec.insert("month".into(), Value::Int(m as i64));
2887 rec.insert("day".into(), Value::Int(d as i64));
2888 rec.insert("hour".into(), Value::Int(hh as i64));
2889 rec.insert("minute".into(), Value::Int(mm as i64));
2890 rec.insert("second".into(), Value::Int(ss as i64));
2891 rec.insert("nano".into(), Value::Int(ns as i64));
2892 rec.insert("tz_offset_minutes".into(), Value::Int(off_min as i64));
2893 let _ = chrono::Utc.timestamp_opt(0, 0); Ok(Value::record_dynamic(rec))
2895}
2896
2897
2898fn instant_from_components(rec: &indexmap::IndexMap<smol_str::SmolStr, Value>) -> Result<i64, String> {
2899 use chrono::TimeZone;
2900 fn get_int(rec: &indexmap::IndexMap<smol_str::SmolStr, Value>, k: &str) -> Result<i64, String> {
2901 match rec.get(k) {
2902 Some(Value::Int(n)) => Ok(*n),
2903 other => Err(format!("from_components: missing or non-int field `{k}`: {other:?}")),
2904 }
2905 }
2906 let y = get_int(rec, "year")? as i32;
2907 let m = get_int(rec, "month")? as u32;
2908 let d = get_int(rec, "day")? as u32;
2909 let hh = get_int(rec, "hour")? as u32;
2910 let mm = get_int(rec, "minute")? as u32;
2911 let ss = get_int(rec, "second")? as u32;
2912 let ns = get_int(rec, "nano")? as u32;
2913 let off_min = get_int(rec, "tz_offset_minutes")? as i32;
2914 let off = chrono::FixedOffset::east_opt(off_min * 60)
2915 .ok_or("from_components: offset out of range")?;
2916 let dt = off
2917 .with_ymd_and_hms(y, m, d, hh, mm, ss)
2918 .single()
2919 .ok_or("from_components: invalid or ambiguous date/time")?;
2920 let dt = dt + chrono::Duration::nanoseconds(ns as i64);
2921 Ok(instant_from_chrono(dt))
2922}
2923
2924type Aead4<'a> = (&'a Vec<u8>, &'a Vec<u8>, &'a Vec<u8>, &'a Vec<u8>);
2941
2942type Aead5<'a> = (&'a Vec<u8>, &'a Vec<u8>, &'a Vec<u8>, &'a Vec<u8>, &'a Vec<u8>);
2945
2946fn unpack4_bytes<'a>(
2947 args: &'a [Value],
2948 op: &str,
2949) -> Result<Aead4<'a>, String> {
2950 let pick = |i: usize, name: &str| -> Result<&'a Vec<u8>, String> {
2951 match args.get(i) {
2952 Some(Value::Bytes(b)) => Ok(b),
2953 Some(other) => Err(format!("{op}: {name} must be Bytes, got {other:?}")),
2954 None => Err(format!("{op}: missing {name} argument")),
2955 }
2956 };
2957 Ok((pick(0, "key")?, pick(1, "nonce")?, pick(2, "aad")?, pick(3, "plaintext")?))
2958}
2959
2960fn unpack5_bytes<'a>(
2961 args: &'a [Value],
2962 op: &str,
2963) -> Result<Aead5<'a>, String> {
2964 let pick = |i: usize, name: &str| -> Result<&'a Vec<u8>, String> {
2965 match args.get(i) {
2966 Some(Value::Bytes(b)) => Ok(b),
2967 Some(other) => Err(format!("{op}: {name} must be Bytes, got {other:?}")),
2968 None => Err(format!("{op}: missing {name} argument")),
2969 }
2970 };
2971 Ok((
2972 pick(0, "key")?,
2973 pick(1, "nonce")?,
2974 pick(2, "aad")?,
2975 pick(3, "ciphertext")?,
2976 pick(4, "tag")?,
2977 ))
2978}
2979
2980fn aead_result(ciphertext: Vec<u8>, tag: Vec<u8>) -> Value {
2981 let mut rec = indexmap::IndexMap::new();
2982 rec.insert("ciphertext".into(), Value::Bytes(ciphertext));
2983 rec.insert("tag".into(), Value::Bytes(tag));
2984 Value::record_dynamic(rec)
2985}
2986
2987fn aead_err(msg: impl Into<String>) -> Value {
2988 let s: String = msg.into();
2989 err_v(Value::Str(s.into()))
2990}
2991
2992fn aes_cbc_impl(args: &[Value], encrypt: bool) -> Value {
3002 use aes::{Aes128, Aes192, Aes256};
3003 use cbc::cipher::{BlockDecryptMut, BlockEncryptMut, KeyIvInit, block_padding::Pkcs7};
3004
3005 let op = if encrypt { "aes_cbc_encrypt_raw" } else { "aes_cbc_decrypt_raw" };
3006 let pick = |i: usize, name: &str| -> Result<&Vec<u8>, String> {
3007 match args.get(i) {
3008 Some(Value::Bytes(b)) => Ok(b),
3009 Some(other) => Err(format!("{op}: {name} must be Bytes, got {other:?}")),
3010 None => Err(format!("{op}: missing {name} argument")),
3011 }
3012 };
3013 let (key, iv, data) = match (pick(0, "key"), pick(1, "iv"), pick(2, "data")) {
3014 (Ok(k), Ok(v), Ok(d)) => (k, v, d),
3015 (Err(e), _, _) | (_, Err(e), _) | (_, _, Err(e)) => return aead_err(e),
3016 };
3017 if iv.len() != 16 {
3018 return aead_err(format!("{op}: iv must be exactly 16 bytes, got {}", iv.len()));
3019 }
3020 if !encrypt && (data.is_empty() || data.len() % 16 != 0) {
3024 return aead_err(format!(
3025 "{op}: ciphertext must be a non-zero multiple of the 16-byte block size, got {}",
3026 data.len()
3027 ));
3028 }
3029
3030 macro_rules! run {
3034 ($cipher:ty) => {
3035 if encrypt {
3036 type Enc = cbc::Encryptor<$cipher>;
3037 Ok(Enc::new(key.as_slice().into(), iv.as_slice().into())
3038 .encrypt_padded_vec_mut::<Pkcs7>(data))
3039 } else {
3040 type Dec = cbc::Decryptor<$cipher>;
3041 Dec::new(key.as_slice().into(), iv.as_slice().into())
3042 .decrypt_padded_vec_mut::<Pkcs7>(data)
3043 .map_err(|_| {
3044 format!("{op}: invalid padding or wrong key/iv")
3049 })
3050 }
3051 };
3052 }
3053 let out: Result<Vec<u8>, String> = match key.len() {
3054 16 => run!(Aes128),
3055 24 => run!(Aes192),
3056 32 => run!(Aes256),
3057 n => Err(format!("{op}: key must be 16, 24, or 32 bytes, got {n}")),
3058 };
3059 match out {
3060 Ok(bytes) => ok_v(Value::Bytes(bytes)),
3061 Err(e) => aead_err(e),
3062 }
3063}
3064
3065fn aes_gcm_seal_impl(args: &[Value]) -> Value {
3066 use aes_gcm::aead::{Aead, KeyInit, Payload};
3067 use aes_gcm::{Aes128Gcm, Aes256Gcm, Nonce};
3068 let (key, nonce, aad, plaintext) = match unpack4_bytes(args, "aes_gcm_seal") {
3069 Ok(t) => t,
3070 Err(e) => return aead_err(e),
3071 };
3072 if nonce.len() != 12 {
3073 return aead_err(format!(
3074 "aes_gcm_seal: nonce must be exactly 12 bytes, got {}", nonce.len()
3075 ));
3076 }
3077 let n = Nonce::from_slice(nonce);
3078 let payload = Payload { msg: plaintext, aad };
3079 let combined = match key.len() {
3082 16 => {
3083 let cipher = Aes128Gcm::new_from_slice(key)
3084 .map_err(|e| e.to_string());
3085 match cipher {
3086 Ok(c) => c.encrypt(n, payload).map_err(|e| format!("aes_gcm_seal: {e}")),
3087 Err(e) => Err(format!("aes_gcm_seal: {e}")),
3088 }
3089 }
3090 32 => {
3091 let cipher = Aes256Gcm::new_from_slice(key)
3092 .map_err(|e| e.to_string());
3093 match cipher {
3094 Ok(c) => c.encrypt(n, payload).map_err(|e| format!("aes_gcm_seal: {e}")),
3095 Err(e) => Err(format!("aes_gcm_seal: {e}")),
3096 }
3097 }
3098 other => return aead_err(format!(
3101 "aes_gcm_seal: key must be 16 or 32 bytes, got {other}"
3102 )),
3103 };
3104 match combined {
3105 Ok(mut buf) => {
3106 let tag_start = buf.len() - 16;
3108 let tag = buf.split_off(tag_start);
3109 ok_v(aead_result(buf, tag))
3110 }
3111 Err(e) => aead_err(e),
3112 }
3113}
3114
3115fn aes_gcm_open_impl(args: &[Value]) -> Value {
3116 use aes_gcm::aead::{Aead, KeyInit, Payload};
3117 use aes_gcm::{Aes128Gcm, Aes256Gcm, Nonce};
3118 let (key, nonce, aad, ciphertext, tag) = match unpack5_bytes(args, "aes_gcm_open") {
3119 Ok(t) => t,
3120 Err(e) => return err_v(Value::Str(e.into())),
3121 };
3122 if nonce.len() != 12 {
3123 return err_v(Value::Str(format!(
3124 "aes_gcm_open: nonce must be exactly 12 bytes, got {}", nonce.len()
3125 ).into()));
3126 }
3127 if tag.len() != 16 {
3128 return err_v(Value::Str(format!(
3129 "aes_gcm_open: tag must be exactly 16 bytes, got {}", tag.len()
3130 ).into()));
3131 }
3132 let mut combined = Vec::with_capacity(ciphertext.len() + tag.len());
3134 combined.extend_from_slice(ciphertext);
3135 combined.extend_from_slice(tag);
3136 let n = Nonce::from_slice(nonce);
3137 let payload = Payload { msg: &combined, aad };
3138 let plaintext = match key.len() {
3139 16 => Aes128Gcm::new_from_slice(key)
3140 .map_err(|e| format!("aes_gcm_open: {e}"))
3141 .and_then(|c| c.decrypt(n, payload).map_err(|e| format!("aes_gcm_open: {e}"))),
3142 32 => Aes256Gcm::new_from_slice(key)
3143 .map_err(|e| format!("aes_gcm_open: {e}"))
3144 .and_then(|c| c.decrypt(n, payload).map_err(|e| format!("aes_gcm_open: {e}"))),
3145 other => return err_v(Value::Str(format!(
3146 "aes_gcm_open: key must be 16 or 32 bytes, got {other}"
3147 ).into())),
3148 };
3149 match plaintext {
3150 Ok(p) => ok_v(Value::Bytes(p)),
3151 Err(e) => err_v(Value::Str(e.into())),
3152 }
3153}
3154
3155fn chacha20_seal_impl(args: &[Value]) -> Value {
3156 use chacha20poly1305::aead::{Aead, KeyInit, Payload};
3157 use chacha20poly1305::{ChaCha20Poly1305, Nonce};
3158 let (key, nonce, aad, plaintext) = match unpack4_bytes(args, "chacha20_poly1305_seal") {
3159 Ok(t) => t,
3160 Err(e) => return aead_err(e),
3161 };
3162 if key.len() != 32 {
3163 return aead_err(format!(
3164 "chacha20_poly1305_seal: key must be exactly 32 bytes, got {}", key.len()
3165 ));
3166 }
3167 if nonce.len() != 12 {
3168 return aead_err(format!(
3169 "chacha20_poly1305_seal: nonce must be exactly 12 bytes, got {}", nonce.len()
3170 ));
3171 }
3172 let cipher = ChaCha20Poly1305::new_from_slice(key)
3173 .map_err(|e| format!("chacha20_poly1305_seal: {e}"));
3174 let n = Nonce::from_slice(nonce);
3175 let payload = Payload { msg: plaintext, aad };
3176 let combined = match cipher {
3177 Ok(c) => c.encrypt(n, payload).map_err(|e| format!("chacha20_poly1305_seal: {e}")),
3178 Err(e) => Err(e),
3179 };
3180 match combined {
3181 Ok(mut buf) => {
3182 let tag_start = buf.len() - 16;
3183 let tag = buf.split_off(tag_start);
3184 ok_v(aead_result(buf, tag))
3185 }
3186 Err(e) => aead_err(e),
3187 }
3188}
3189
3190fn chacha20_open_impl(args: &[Value]) -> Value {
3191 use chacha20poly1305::aead::{Aead, KeyInit, Payload};
3192 use chacha20poly1305::{ChaCha20Poly1305, Nonce};
3193 let (key, nonce, aad, ciphertext, tag) = match unpack5_bytes(args, "chacha20_poly1305_open") {
3194 Ok(t) => t,
3195 Err(e) => return err_v(Value::Str(e.into())),
3196 };
3197 if key.len() != 32 {
3198 return err_v(Value::Str(format!(
3199 "chacha20_poly1305_open: key must be exactly 32 bytes, got {}", key.len()
3200 ).into()));
3201 }
3202 if nonce.len() != 12 {
3203 return err_v(Value::Str(format!(
3204 "chacha20_poly1305_open: nonce must be exactly 12 bytes, got {}", nonce.len()
3205 ).into()));
3206 }
3207 if tag.len() != 16 {
3208 return err_v(Value::Str(format!(
3209 "chacha20_poly1305_open: tag must be exactly 16 bytes, got {}", tag.len()
3210 ).into()));
3211 }
3212 let mut combined = Vec::with_capacity(ciphertext.len() + tag.len());
3213 combined.extend_from_slice(ciphertext);
3214 combined.extend_from_slice(tag);
3215 let cipher = ChaCha20Poly1305::new_from_slice(key)
3216 .map_err(|e| format!("chacha20_poly1305_open: {e}"));
3217 let n = Nonce::from_slice(nonce);
3218 let payload = Payload { msg: &combined, aad };
3219 match cipher.and_then(|c| c.decrypt(n, payload).map_err(|e| format!("chacha20_poly1305_open: {e}"))) {
3220 Ok(p) => ok_v(Value::Bytes(p)),
3221 Err(e) => err_v(Value::Str(e.into())),
3222 }
3223}
3224
3225type Kdf4<'a> = (&'a Vec<u8>, &'a Vec<u8>, i64, i64);
3235
3236type Hkdf4<'a> = (&'a Vec<u8>, &'a Vec<u8>, &'a Vec<u8>, i64);
3239
3240type Argon5<'a> = (&'a Vec<u8>, &'a Vec<u8>, i64, i64, i64);
3243
3244fn pick_bytes<'a>(args: &'a [Value], i: usize, op: &str, name: &str)
3245 -> Result<&'a Vec<u8>, String>
3246{
3247 match args.get(i) {
3248 Some(Value::Bytes(b)) => Ok(b),
3249 Some(other) => Err(format!("{op}: {name} must be Bytes, got {other:?}")),
3250 None => Err(format!("{op}: missing {name} argument")),
3251 }
3252}
3253
3254fn pick_int(args: &[Value], i: usize, op: &str, name: &str) -> Result<i64, String> {
3255 match args.get(i) {
3256 Some(Value::Int(n)) => Ok(*n),
3257 Some(other) => Err(format!("{op}: {name} must be Int, got {other:?}")),
3258 None => Err(format!("{op}: missing {name} argument")),
3259 }
3260}
3261
3262fn unpack_kdf4<'a>(args: &'a [Value], op: &str) -> Result<Kdf4<'a>, String> {
3263 Ok((
3264 pick_bytes(args, 0, op, "password")?,
3265 pick_bytes(args, 1, op, "salt")?,
3266 pick_int(args, 2, op, "iterations")?,
3267 pick_int(args, 3, op, "len")?,
3268 ))
3269}
3270
3271fn unpack_hkdf4<'a>(args: &'a [Value], op: &str) -> Result<Hkdf4<'a>, String> {
3272 Ok((
3273 pick_bytes(args, 0, op, "ikm")?,
3274 pick_bytes(args, 1, op, "salt")?,
3275 pick_bytes(args, 2, op, "info")?,
3276 pick_int(args, 3, op, "len")?,
3277 ))
3278}
3279
3280fn unpack_argon5<'a>(args: &'a [Value], op: &str) -> Result<Argon5<'a>, String> {
3281 Ok((
3282 pick_bytes(args, 0, op, "password")?,
3283 pick_bytes(args, 1, op, "salt")?,
3284 pick_int(args, 2, op, "t_cost")?,
3285 pick_int(args, 3, op, "m_cost")?,
3286 pick_int(args, 4, op, "len")?,
3287 ))
3288}
3289
3290const KDF_MAX_LEN: usize = 1024 * 1024;
3295
3296fn check_len(op: &str, len: i64) -> Result<usize, String> {
3297 if len <= 0 {
3298 return Err(format!("{op}: len must be > 0, got {len}"));
3299 }
3300 if (len as u64) > KDF_MAX_LEN as u64 {
3301 return Err(format!(
3302 "{op}: len must be <= {KDF_MAX_LEN}, got {len}"
3303 ));
3304 }
3305 Ok(len as usize)
3306}
3307
3308fn pbkdf2_sha256_impl(args: &[Value]) -> Value {
3309 use hmac::Hmac;
3310 use sha2::Sha256;
3311 let op = "pbkdf2_sha256";
3312 let (password, salt, iterations, len) = match unpack_kdf4(args, op) {
3313 Ok(t) => t,
3314 Err(e) => return err_v(Value::Str(e.into())),
3315 };
3316 if iterations <= 0 {
3317 return err_v(Value::Str(format!(
3318 "{op}: iterations must be > 0, got {iterations}"
3319 ).into()));
3320 }
3321 let out_len = match check_len(op, len) {
3322 Ok(n) => n,
3323 Err(e) => return err_v(Value::Str(e.into())),
3324 };
3325 let rounds = match u32::try_from(iterations) {
3326 Ok(r) => r,
3327 Err(_) => {
3328 return err_v(Value::Str(format!(
3329 "{op}: iterations must fit in u32, got {iterations}"
3330 ).into()))
3331 }
3332 };
3333 let mut out = vec![0u8; out_len];
3334 if let Err(e) = pbkdf2::pbkdf2::<Hmac<Sha256>>(password, salt, rounds, &mut out) {
3335 return err_v(Value::Str(format!("{op}: {e}").into()));
3336 }
3337 ok_v(Value::Bytes(out))
3338}
3339
3340fn hkdf_sha256_impl(args: &[Value]) -> Value {
3341 use hkdf::Hkdf;
3342 use sha2::Sha256;
3343 let op = "hkdf_sha256";
3344 let (ikm, salt, info, len) = match unpack_hkdf4(args, op) {
3345 Ok(t) => t,
3346 Err(e) => return err_v(Value::Str(e.into())),
3347 };
3348 let out_len = match check_len(op, len) {
3349 Ok(n) => n,
3350 Err(e) => return err_v(Value::Str(e.into())),
3351 };
3352 let salt_opt: Option<&[u8]> = if salt.is_empty() { None } else { Some(salt) };
3355 let hk = Hkdf::<Sha256>::new(salt_opt, ikm);
3356 let mut out = vec![0u8; out_len];
3357 match hk.expand(info, &mut out) {
3358 Ok(()) => ok_v(Value::Bytes(out)),
3359 Err(e) => err_v(Value::Str(format!("{op}: {e}").into())),
3360 }
3361}
3362
3363fn argon2id_impl(args: &[Value]) -> Value {
3364 use argon2::{Algorithm, Argon2, Params, Version};
3365 let op = "argon2id";
3366 let (password, salt, t_cost, m_cost, len) = match unpack_argon5(args, op) {
3367 Ok(t) => t,
3368 Err(e) => return err_v(Value::Str(e.into())),
3369 };
3370 let out_len = match check_len(op, len) {
3371 Ok(n) => n,
3372 Err(e) => return err_v(Value::Str(e.into())),
3373 };
3374 let t = match u32::try_from(t_cost) {
3375 Ok(n) if n >= 1 => n,
3376 _ => return err_v(Value::Str(format!(
3377 "{op}: t_cost must be a u32 >= 1, got {t_cost}"
3378 ).into())),
3379 };
3380 let m = match u32::try_from(m_cost) {
3381 Ok(n) if n >= Params::MIN_M_COST => n,
3382 _ => return err_v(Value::Str(format!(
3383 "{op}: m_cost must be a u32 >= {}, got {m_cost}",
3384 Params::MIN_M_COST
3385 ).into())),
3386 };
3387 let params = match Params::new(m, t, 1, Some(out_len)) {
3392 Ok(p) => p,
3393 Err(e) => return err_v(Value::Str(format!("{op}: {e}").into())),
3394 };
3395 let hasher = Argon2::new(Algorithm::Argon2id, Version::V0x13, params);
3396 let mut out = vec![0u8; out_len];
3397 if let Err(e) = hasher.hash_password_into(password, salt, &mut out) {
3398 return err_v(Value::Str(format!("{op}: {e}").into()));
3399 }
3400 ok_v(Value::Bytes(out))
3401}
3402
3403#[cfg(test)]
3404mod bytes_builtin_tests {
3405 use super::*;
3406
3407 fn call(op: &str, args: Vec<Value>) -> Result<Value, String> {
3408 dispatch("bytes", op, &args)
3409 }
3410
3411 #[test]
3412 fn concat_appends_in_order() {
3413 let a = Value::Bytes(b"AB".to_vec());
3414 let b = Value::Bytes(b"CD".to_vec());
3415 let got = call("concat", vec![a, b]).unwrap();
3416 assert_eq!(got, Value::Bytes(b"ABCD".to_vec()));
3417 }
3418
3419 #[test]
3420 fn concat_all_joins_a_list_in_order() {
3421 let items = Value::List(vec![
3422 Value::Bytes(b"Hi".to_vec()),
3423 Value::Bytes(b", ".to_vec()),
3424 Value::Bytes(b"there".to_vec()),
3425 ].into());
3426 let got = call("concat_all", vec![items]).unwrap();
3427 assert_eq!(got, Value::Bytes(b"Hi, there".to_vec()));
3428 }
3429
3430 #[test]
3431 fn concat_all_rejects_non_bytes_elements() {
3432 let items = Value::List(vec![Value::Int(1)].into());
3433 assert!(call("concat_all", vec![items]).is_err());
3434 }
3435
3436 #[test]
3437 fn u8_encodes_and_truncates_to_one_byte() {
3438 let got = call("u8", vec![Value::Int(65)]).unwrap();
3439 assert_eq!(got, Value::Bytes(vec![65]));
3440 let wrapped = call("u8", vec![Value::Int(256 + 65)]).unwrap();
3444 assert_eq!(wrapped, Value::Bytes(vec![65]));
3445 }
3446
3447 #[test]
3448 fn u16_le_round_trips() {
3449 let encoded = call("u16_le", vec![Value::Int(258)]).unwrap();
3450 assert_eq!(encoded, Value::Bytes(vec![2, 1]));
3451 let decoded = call("u16_le_at", vec![encoded, Value::Int(0)]).unwrap();
3452 assert_eq!(decoded, ok_v(Value::Int(258)));
3453 }
3454
3455 #[test]
3456 fn u32_le_round_trips() {
3457 let encoded = call("u32_le", vec![Value::Int(70000)]).unwrap();
3458 let decoded = call("u32_le_at", vec![encoded, Value::Int(0)]).unwrap();
3459 assert_eq!(decoded, ok_v(Value::Int(70000)));
3460 }
3461
3462 #[test]
3463 fn u64_le_round_trips_beyond_u32_range() {
3464 let n = 9_007_199_254_740_993_i64; let encoded = call("u64_le", vec![Value::Int(n)]).unwrap();
3466 let decoded = call("u64_le_at", vec![encoded, Value::Int(0)]).unwrap();
3467 assert_eq!(decoded, ok_v(Value::Int(n)));
3468 }
3469
3470 #[test]
3471 fn decoders_report_out_of_range_offsets_as_err_not_panic() {
3472 let one_byte = Value::Bytes(vec![5]);
3473 let got = call("u16_le_at", vec![one_byte, Value::Int(0)]).unwrap();
3474 match got {
3475 Value::Variant { name, .. } => assert_eq!(name, "Err"),
3476 other => panic!("expected an Err variant, got {other:?}"),
3477 }
3478 }
3479
3480 #[test]
3481 fn u16_le_at_reads_at_a_nonzero_offset() {
3482 let buf = Value::Bytes(vec![0xFF, 2, 1]);
3485 let decoded = call("u16_le_at", vec![buf, Value::Int(1)]).unwrap();
3486 assert_eq!(decoded, ok_v(Value::Int(258)));
3487 }
3488}
3489
3490#[cfg(test)]
3491mod ed25519_curve_tests {
3492 use super::*;
3493
3494 fn call(op: &str, args: Vec<Value>) -> Result<Value, String> {
3495 dispatch("crypto", op, &args)
3496 }
3497
3498 #[test]
3499 fn a_real_public_key_is_a_valid_point() {
3500 use ed25519_dalek::SigningKey;
3501 let seed = [7u8; 32];
3502 let sk = SigningKey::from_bytes(&seed);
3503 let pk = sk.verifying_key().to_bytes().to_vec();
3504 let got = call("ed25519_is_valid_point", vec![Value::Bytes(pk)]).unwrap();
3505 assert_eq!(got, Value::Bool(true));
3506 }
3507
3508 #[test]
3509 fn wrong_length_is_not_a_valid_point() {
3510 let got = call("ed25519_is_valid_point", vec![Value::Bytes(vec![1, 2, 3])]).unwrap();
3511 assert_eq!(got, Value::Bool(false));
3512 }
3513
3514 #[test]
3515 fn a_non_curve_point_is_rejected() {
3516 let mut candidate = [1u8; 32];
3521 candidate[31] = 0;
3522 let got = call("ed25519_is_valid_point", vec![Value::Bytes(candidate.to_vec())]).unwrap();
3523 assert_eq!(got, Value::Bool(false));
3524 }
3525}