1use lex_bytecode::{MapKey, Value};
6use std::collections::{BTreeMap, BTreeSet, HashMap};
7use std::sync::{Mutex, OnceLock};
8
9pub fn is_pure_call(kind: &str, op: &str) -> bool {
13 if !is_pure_module(kind) { return false; }
14 !matches!(
15 (kind, op),
16 ("crypto", "random")
17 | ("crypto", "random_str_hex")
18 | ("datetime", "now")
19 | ("http", "send")
20 | ("http", "get")
21 | ("http", "post")
22 | ("http", "stream_lines")
23 | ("arrow", "read_csv")
25 | ("arrow", "read_parquet")
27 | ("arrow", "read_parquet_cols")
28 | ("arrow", "write_parquet")
29 | ("arrow", "write_csv")
30 )
31}
32
33pub fn call_pure_builtin(kind: &str, op: &str, args: Vec<Value>) -> Result<Value, String> {
40 if (kind, op) == ("list", "cons") {
41 let mut it = args.into_iter();
42 let head = it.next().unwrap_or(Value::Unit);
43 let mut tail = match it.next() {
44 Some(Value::List(v)) => v,
45 Some(other) => return Err(format!("list.cons: expected List, got {other:?}")),
46 None => std::collections::VecDeque::new(),
47 };
48 tail.push_front(head);
49 return Ok(Value::List(tail));
50 }
51 dispatch(kind, op, &args)
52}
53
54pub fn try_pure_builtin(kind: &str, op: &str, args: &[Value]) -> Option<Result<Value, String>> {
61 if !is_pure_call(kind, op) { return None; }
62 Some(dispatch(kind, op, args))
63}
64
65pub fn is_pure_module(kind: &str) -> bool {
68 matches!(kind, "str" | "int" | "float" | "bool" | "list" | "iter"
69 | "option" | "result" | "tuple" | "json" | "bytes" | "flow" | "math"
70 | "map" | "set" | "crypto" | "regex" | "deque" | "datetime" | "duration" | "http"
71 | "toml" | "yaml" | "dotenv" | "csv" | "test" | "random" | "parser"
72 | "cli" | "arrow" | "df" | "decimal")
73}
74
75fn dispatch(kind: &str, op: &str, args: &[Value]) -> Result<Value, String> {
76 match (kind, op) {
77 ("str", "is_empty") => Ok(Value::Bool(expect_str(args.first())?.is_empty())),
79 ("str", "len") => Ok(Value::Int(expect_str(args.first())?.len() as i64)),
80 ("str", "concat") => {
81 let a = expect_str(args.first())?;
82 let b = expect_str(args.get(1))?;
83 Ok(Value::Str(format!("{a}{b}").into()))
84 }
85 ("str", "to_int") => {
86 let s = expect_str(args.first())?;
87 match s.parse::<i64>() {
88 Ok(n) => Ok(some(Value::Int(n))),
89 Err(_) => Ok(none()),
90 }
91 }
92 ("str", "split") => {
93 let s = expect_str(args.first())?;
94 let sep = expect_str(args.get(1))?;
95 let items: std::collections::VecDeque<Value> = if sep.is_empty() {
96 s.chars().map(|c| Value::Str(c.to_string().into())).collect()
97 } else {
98 s.split(sep.as_str()).map(|p| Value::Str(p.into())).collect()
99 };
100 Ok(Value::List(items))
101 }
102 ("str", "join") => {
103 let parts = expect_list(args.first())?;
104 let sep = expect_str(args.get(1))?;
105 let mut out = String::new();
106 for (i, p) in parts.iter().enumerate() {
107 if i > 0 { out.push_str(&sep); }
108 match p {
109 Value::Str(s) => out.push_str(s),
110 other => return Err(format!("str.join element must be Str, got {other:?}")),
111 }
112 }
113 Ok(Value::Str(out.into()))
114 }
115 ("str", "starts_with") => {
116 let s = expect_str(args.first())?;
117 let prefix = expect_str(args.get(1))?;
118 Ok(Value::Bool(s.starts_with(prefix.as_str())))
119 }
120 ("str", "ends_with") => {
121 let s = expect_str(args.first())?;
122 let suffix = expect_str(args.get(1))?;
123 Ok(Value::Bool(s.ends_with(suffix.as_str())))
124 }
125 ("str", "contains") => {
126 let s = expect_str(args.first())?;
127 let needle = expect_str(args.get(1))?;
128 Ok(Value::Bool(s.contains(needle.as_str())))
129 }
130 ("str", "cmp") => {
131 let a = expect_str(args.first())?;
132 let b = expect_str(args.get(1))?;
133 Ok(Value::Int(match a.as_str().cmp(b.as_str()) {
134 std::cmp::Ordering::Less => -1,
135 std::cmp::Ordering::Equal => 0,
136 std::cmp::Ordering::Greater => 1,
137 }))
138 }
139 ("str", "replace") => {
140 let s = expect_str(args.first())?;
141 let from = expect_str(args.get(1))?;
142 let to = expect_str(args.get(2))?;
143 Ok(Value::Str(s.replace(from.as_str(), to.as_str()).into()))
144 }
145 ("str", "trim") => Ok(Value::Str(expect_str(args.first())?.trim().into())),
146 ("str", "to_upper") => Ok(Value::Str(expect_str(args.first())?.to_uppercase().into())),
147 ("str", "to_lower") => Ok(Value::Str(expect_str(args.first())?.to_lowercase().into())),
148 ("str", "strip_prefix") => {
149 let s = expect_str(args.first())?;
150 let prefix = expect_str(args.get(1))?;
151 Ok(match s.strip_prefix(prefix.as_str()) {
152 Some(rest) => some(Value::Str(rest.into())),
153 None => none(),
154 })
155 }
156 ("str", "strip_suffix") => {
157 let s = expect_str(args.first())?;
158 let suffix = expect_str(args.get(1))?;
159 Ok(match s.strip_suffix(suffix.as_str()) {
160 Some(rest) => some(Value::Str(rest.into())),
161 None => none(),
162 })
163 }
164 ("str", "slice") => {
165 let s = expect_str(args.first())?;
175 let lo_i = expect_int(args.get(1))?;
176 let hi_i = expect_int(args.get(2))?;
177 let lo = (lo_i.max(0) as usize).min(s.len());
178 let hi = (hi_i.max(0) as usize).min(s.len());
179 if lo > hi {
180 return Err(format!(
181 "str.slice: reversed range [{lo}..{hi}] (after clamping to len {})",
182 s.len()));
183 }
184 if !s.is_char_boundary(lo) || !s.is_char_boundary(hi) {
185 return Err(format!("str.slice: [{lo}..{hi}] not on char boundaries"));
186 }
187 Ok(Value::Str(s[lo..hi].into()))
188 }
189
190 ("int", "to_str") => Ok(Value::Str(expect_int(args.first())?.to_string().into())),
192 ("int", "to_float") => Ok(Value::Float(expect_int(args.first())? as f64)),
193 ("float", "to_int") => Ok(Value::Int(expect_float(args.first())? as i64)),
194 ("float", "to_str") => Ok(Value::Str(expect_float(args.first())?.to_string().into())),
195 ("str", "to_float") => {
196 let s = expect_str(args.first())?;
197 match s.parse::<f64>() {
198 Ok(f) => Ok(some(Value::Float(f))),
199 Err(_) => Ok(none()),
200 }
201 }
202
203 ("list", "len") => Ok(Value::Int(expect_list(args.first())?.len() as i64)),
205 ("list", "is_empty") => Ok(Value::Bool(expect_list(args.first())?.is_empty())),
206 ("list", "head") => {
207 let xs = expect_list(args.first())?;
208 match xs.front() {
209 Some(v) => Ok(some(v.clone())),
210 None => Ok(none()),
211 }
212 }
213 ("list", "tail") => {
214 let xs = expect_list(args.first())?;
215 if xs.is_empty() { Ok(Value::List(std::collections::VecDeque::new())) }
216 else { Ok(Value::List(xs.iter().skip(1).cloned().collect::<std::collections::VecDeque<_>>())) }
217 }
218 ("list", "range") => {
219 let lo = expect_int(args.first())?;
220 let hi = expect_int(args.get(1))?;
221 Ok(Value::List((lo..hi).map(Value::Int).collect::<std::collections::VecDeque<_>>()))
222 }
223 ("list", "concat") => {
224 let mut out = expect_list(args.first())?.clone();
225 out.extend(expect_list(args.get(1))?.iter().cloned());
226 Ok(Value::List(out))
227 }
228 ("list", "reverse") => {
229 let out = expect_list(args.first())?.clone();
230 let rev: std::collections::VecDeque<Value> = out.into_iter().rev().collect();
231 Ok(Value::List(rev))
232 }
233 ("list", "cons") => {
237 let head = args.first().cloned().unwrap_or(Value::Unit);
238 let mut out: std::collections::VecDeque<Value> =
239 expect_list(args.get(1))?.iter().cloned().collect();
240 out.push_front(head);
241 Ok(Value::List(out))
242 }
243 ("list", "enumerate") => {
244 let xs = expect_list(args.first())?;
245 let pairs = xs.iter().cloned().enumerate()
246 .map(|(i, v)| Value::Tuple(vec![Value::Int(i as i64), v]))
247 .collect::<std::collections::VecDeque<_>>();
248 Ok(Value::List(pairs))
249 }
250
251 ("tuple", "fst") => tuple_index(first_arg(args)?, 0),
256 ("tuple", "snd") => tuple_index(first_arg(args)?, 1),
257 ("tuple", "third") => tuple_index(first_arg(args)?, 2),
258 ("tuple", "len") => match first_arg(args)? {
259 Value::Tuple(items) => Ok(Value::Int(items.len() as i64)),
260 other => Err(format!("tuple.len: expected Tuple, got {other:?}")),
261 },
262
263 ("option", "unwrap_or") => {
265 let opt = first_arg(args)?;
266 let default = args.get(1).cloned().unwrap_or(Value::Unit);
267 match opt {
268 Value::Variant { name, args } if name == "Some" && !args.is_empty() => Ok(args[0].clone()),
269 Value::Variant { name, .. } if name == "None" => Ok(default),
270 other => Err(format!("option.unwrap_or expected Option, got {other:?}")),
271 }
272 }
273 ("option", "unwrap_or_else") => {
278 let opt = first_arg(args)?;
279 match opt {
280 Value::Variant { name, args } if name == "Some" && !args.is_empty() => Ok(args[0].clone()),
281 Value::Variant { name, .. } if name == "None" => {
282 Ok(args.get(1).cloned().unwrap_or(Value::Unit))
286 }
287 other => Err(format!("option.unwrap_or_else expected Option, got {other:?}")),
288 }
289 }
290 ("option", "is_some") => match first_arg(args)? {
291 Value::Variant { name, .. } => Ok(Value::Bool(name == "Some")),
292 other => Err(format!("option.is_some expected Option, got {other:?}")),
293 },
294 ("option", "is_none") => match first_arg(args)? {
295 Value::Variant { name, .. } => Ok(Value::Bool(name == "None")),
296 other => Err(format!("option.is_none expected Option, got {other:?}")),
297 },
298
299 ("result", "is_ok") => match first_arg(args)? {
301 Value::Variant { name, .. } => Ok(Value::Bool(name == "Ok")),
302 other => Err(format!("result.is_ok expected Result, got {other:?}")),
303 },
304 ("result", "is_err") => match first_arg(args)? {
305 Value::Variant { name, .. } => Ok(Value::Bool(name == "Err")),
306 other => Err(format!("result.is_err expected Result, got {other:?}")),
307 },
308 ("result", "unwrap_or") => {
309 let res = first_arg(args)?;
310 let default = args.get(1).cloned().unwrap_or(Value::Unit);
311 match res {
312 Value::Variant { name, args } if name == "Ok" && !args.is_empty() => Ok(args[0].clone()),
313 Value::Variant { name, .. } if name == "Err" => Ok(default),
314 other => Err(format!("result.unwrap_or expected Result, got {other:?}")),
315 }
316 }
317
318 ("json", "stringify") => {
320 let v = first_arg(args)?;
321 Ok(Value::Str(serde_json::to_string(&value_to_json(v)).unwrap_or_default().into()))
322 }
323 ("json", "parse") => {
324 let s = expect_str(args.first())?;
325 match serde_json::from_str::<serde_json::Value>(&s) {
326 Ok(v) => Ok(ok_v(json_to_value(&v))),
327 Err(e) => Ok(err_v(Value::Str(format!("{e}").into()))),
328 }
329 }
330 ("json", "parse_strict") => {
333 let s = expect_str(args.first())?;
334 let required = required_field_names(args.get(1))?;
335 let schema = extract_type_schema(args.get(2));
336 match serde_json::from_str::<serde_json::Value>(&s) {
337 Ok(v) => {
338 if let Err(e) = check_required_fields(&v, &required) {
339 return Ok(err_v(Value::Str(e.into())));
340 }
341 if let Err(e) = validate_field_types(&v, &schema) {
342 return Ok(err_v(Value::Str(e.into())));
343 }
344 Ok(ok_v(apply_option_wrapping(json_to_value(&v), &v, &schema)))
345 }
346 Err(e) => Ok(err_v(Value::Str(format!("{e}").into()))),
347 }
348 }
349
350 ("toml", "parse") => {
355 let s = expect_str(args.first())?;
356 match toml::from_str::<serde_json::Value>(&s) {
357 Ok(mut v) => {
358 unwrap_toml_datetime_markers(&mut v);
359 Ok(ok_v(json_to_value(&v)))
360 }
361 Err(e) => Ok(err_v(Value::Str(format!("{e}").into()))),
362 }
363 }
364 ("json", "parse_strict_typed") => {
368 let s = expect_str(args.first())?;
369 let required = required_field_names(args.get(1))?;
370 let schema = extract_type_schema(args.get(2));
371 match serde_json::from_str::<serde_json::Value>(&s) {
372 Ok(v) => {
373 if let Err(e) = check_required_fields(&v, &required) {
374 return Ok(err_v(Value::Str(e.into())));
375 }
376 if let Err(e) = validate_field_types(&v, &schema) {
377 return Ok(err_v(Value::Str(e.into())));
378 }
379 Ok(ok_v(apply_option_wrapping(json_to_value(&v), &v, &schema)))
380 }
381 Err(e) => Ok(err_v(Value::Str(format!("{e}").into()))),
382 }
383 }
384
385 ("toml", "parse_strict") => {
388 let s = expect_str(args.first())?;
389 let required = required_field_names(args.get(1))?;
390 let schema = extract_type_schema(args.get(2));
391 match toml::from_str::<serde_json::Value>(&s) {
392 Ok(mut v) => {
393 unwrap_toml_datetime_markers(&mut v);
394 if let Err(e) = check_required_fields(&v, &required) {
395 return Ok(err_v(Value::Str(e.into())));
396 }
397 if let Err(e) = validate_field_types(&v, &schema) {
398 return Ok(err_v(Value::Str(e.into())));
399 }
400 Ok(ok_v(apply_option_wrapping(json_to_value(&v), &v, &schema)))
401 }
402 Err(e) => Ok(err_v(Value::Str(format!("{e}").into()))),
403 }
404 }
405 ("toml", "parse_strict_typed") => {
406 let s = expect_str(args.first())?;
407 let required = required_field_names(args.get(1))?;
408 let schema = extract_type_schema(args.get(2));
409 match toml::from_str::<serde_json::Value>(&s) {
410 Ok(mut v) => {
411 unwrap_toml_datetime_markers(&mut v);
412 if let Err(e) = check_required_fields(&v, &required) {
413 return Ok(err_v(Value::Str(e.into())));
414 }
415 if let Err(e) = validate_field_types(&v, &schema) {
416 return Ok(err_v(Value::Str(e.into())));
417 }
418 Ok(ok_v(apply_option_wrapping(json_to_value(&v), &v, &schema)))
419 }
420 Err(e) => Ok(err_v(Value::Str(format!("{e}").into()))),
421 }
422 }
423 ("toml", "stringify") => {
424 let v = first_arg(args)?;
425 let json = value_to_json(v);
431 match toml::to_string(&json) {
432 Ok(s) => Ok(ok_v(Value::Str(s.into()))),
433 Err(e) => Ok(err_v(Value::Str(format!("toml.stringify: {e}").into()))),
434 }
435 }
436
437 ("yaml", "parse") => {
443 let s = expect_str(args.first())?;
444 match serde_yaml::from_str::<serde_json::Value>(&s) {
445 Ok(v) => Ok(ok_v(json_to_value(&v))),
446 Err(e) => Ok(err_v(Value::Str(format!("{e}").into()))),
447 }
448 }
449 ("yaml", "parse_strict") => {
452 let s = expect_str(args.first())?;
453 let required = required_field_names(args.get(1))?;
454 let schema = extract_type_schema(args.get(2));
455 match serde_yaml::from_str::<serde_json::Value>(&s) {
456 Ok(v) => {
457 if let Err(e) = check_required_fields(&v, &required) {
458 return Ok(err_v(Value::Str(e.into())));
459 }
460 if let Err(e) = validate_field_types(&v, &schema) {
461 return Ok(err_v(Value::Str(e.into())));
462 }
463 Ok(ok_v(json_to_value(&v)))
464 }
465 Err(e) => Ok(err_v(Value::Str(format!("{e}").into()))),
466 }
467 }
468 ("yaml", "parse_strict_typed") => {
469 let s = expect_str(args.first())?;
470 let required = required_field_names(args.get(1))?;
471 let schema = extract_type_schema(args.get(2));
472 match serde_yaml::from_str::<serde_json::Value>(&s) {
473 Ok(v) => {
474 if let Err(e) = check_required_fields(&v, &required) {
475 return Ok(err_v(Value::Str(e.into())));
476 }
477 if let Err(e) = validate_field_types(&v, &schema) {
478 return Ok(err_v(Value::Str(e.into())));
479 }
480 Ok(ok_v(json_to_value(&v)))
481 }
482 Err(e) => Ok(err_v(Value::Str(format!("{e}").into()))),
483 }
484 }
485 ("yaml", "stringify") => {
486 let v = first_arg(args)?;
487 let json = value_to_json(v);
488 match serde_yaml::to_string(&json) {
489 Ok(s) => Ok(ok_v(Value::Str(s.into()))),
490 Err(e) => Ok(err_v(Value::Str(format!("yaml.stringify: {e}").into()))),
491 }
492 }
493
494 ("dotenv", "parse") => {
502 use std::collections::BTreeMap;
503 use lex_bytecode::MapKey;
504 let s = expect_str(args.first())?;
505 match parse_dotenv(&s) {
506 Ok(map) => {
507 let mut bt: BTreeMap<MapKey, Value> = BTreeMap::new();
508 for (k, v) in map {
509 bt.insert(MapKey::Str(k), Value::Str(v.into()));
510 }
511 Ok(ok_v(Value::Map(bt)))
512 }
513 Err(e) => Ok(err_v(Value::Str(e.into()))),
514 }
515 }
516
517 ("csv", "parse") => {
522 let s = expect_str(args.first())?;
523 let mut rdr = csv::ReaderBuilder::new()
524 .has_headers(false)
525 .flexible(true)
526 .from_reader(s.as_bytes());
527 let mut rows: std::collections::VecDeque<Value> = std::collections::VecDeque::new();
528 for r in rdr.records() {
529 match r {
530 Ok(rec) => {
531 let row: std::collections::VecDeque<Value> = rec.iter()
532 .map(|f| Value::Str(f.into()))
533 .collect();
534 rows.push_back(Value::List(row));
535 }
536 Err(e) => return Ok(err_v(Value::Str(format!("csv.parse: {e}").into()))),
537 }
538 }
539 Ok(ok_v(Value::List(rows)))
540 }
541 ("csv", "stringify") => {
542 let v = first_arg(args)?;
547 let rows = match v {
548 Value::List(rs) => rs,
549 _ => return Ok(err_v(Value::Str("csv.stringify expects List[List[Str]]".into()))),
550 };
551 let mut out = Vec::new();
552 {
553 let mut wtr = csv::WriterBuilder::new()
554 .has_headers(false)
555 .from_writer(&mut out);
556 for row in rows {
557 let cells = match row {
558 Value::List(cs) => cs,
559 _ => return Ok(err_v(Value::Str("csv.stringify row must be List[Str]".into()))),
560 };
561 let strs: Vec<String> = cells.iter().map(|c| match c {
562 Value::Str(s) => s.to_string(),
563 other => serde_json::to_string(&other.to_json())
564 .unwrap_or_else(|_| String::new()),
565 }).collect();
566 if let Err(e) = wtr.write_record(&strs) {
567 return Ok(err_v(Value::Str(format!("csv.stringify: {e}").into())));
568 }
569 }
570 if let Err(e) = wtr.flush() {
571 return Ok(err_v(Value::Str(format!("csv.stringify flush: {e}").into())));
572 }
573 }
574 match String::from_utf8(out) {
575 Ok(s) => Ok(ok_v(Value::Str(s.into()))),
576 Err(e) => Ok(err_v(Value::Str(format!("csv.stringify utf8: {e}").into()))),
577 }
578 }
579
580 ("test", "assert_eq") => {
587 let a = first_arg(args)?;
588 let b = args.get(1).ok_or("test.assert_eq: missing second arg")?;
589 if a == b {
590 Ok(ok_v(Value::Unit))
591 } else {
592 Ok(err_v(Value::Str(format!("assert_eq: lhs {} != rhs {}",
593 value_to_json(a), value_to_json(b)).into())))
594 }
595 }
596 ("test", "assert_ne") => {
597 let a = first_arg(args)?;
598 let b = args.get(1).ok_or("test.assert_ne: missing second arg")?;
599 if a != b {
600 Ok(ok_v(Value::Unit))
601 } else {
602 Ok(err_v(Value::Str(format!("assert_ne: both sides are {}",
603 value_to_json(a)).into())))
604 }
605 }
606 ("test", "assert_true") => {
607 match first_arg(args)? {
608 Value::Bool(true) => Ok(ok_v(Value::Unit)),
609 Value::Bool(false) => Ok(err_v(Value::Str("assert_true: was false".into()))),
610 other => Err(format!("test.assert_true expects Bool, got {other:?}")),
611 }
612 }
613 ("test", "assert_false") => {
614 match first_arg(args)? {
615 Value::Bool(false) => Ok(ok_v(Value::Unit)),
616 Value::Bool(true) => Ok(err_v(Value::Str("assert_false: was true".into()))),
617 other => Err(format!("test.assert_false expects Bool, got {other:?}")),
618 }
619 }
620
621 ("bytes", "len") => {
623 let b = expect_bytes(args.first())?;
624 Ok(Value::Int(b.len() as i64))
625 }
626 ("bytes", "eq") => {
627 let a = expect_bytes(args.first())?;
628 let b = expect_bytes(args.get(1))?;
629 Ok(Value::Bool(a == b))
630 }
631 ("bytes", "from_str") => {
632 let s = expect_str(args.first())?;
633 Ok(Value::Bytes(s.into_bytes()))
634 }
635 ("bytes", "to_str") => {
636 let b = expect_bytes(args.first())?;
637 match String::from_utf8(b.to_vec()) {
638 Ok(s) => Ok(ok_v(Value::Str(s.into()))),
639 Err(e) => Ok(err_v(Value::Str(format!("{e}").into()))),
640 }
641 }
642 ("bytes", "slice") => {
643 let b = expect_bytes(args.first())?;
644 let lo = expect_int(args.get(1))? as usize;
645 let hi = expect_int(args.get(2))? as usize;
646 if lo > hi || hi > b.len() {
647 return Err(format!("bytes.slice: out of range [{lo}..{hi}] of {}", b.len()));
648 }
649 Ok(Value::Bytes(b[lo..hi].to_vec()))
650 }
651 ("bytes", "is_empty") => {
652 let b = expect_bytes(args.first())?;
653 Ok(Value::Bool(b.is_empty()))
654 }
655
656 ("math", "exp") => Ok(Value::Float(expect_float(args.first())?.exp())),
663 ("math", "log") => Ok(Value::Float(expect_float(args.first())?.ln())),
664 ("math", "log2") => Ok(Value::Float(expect_float(args.first())?.log2())),
665 ("math", "log10") => Ok(Value::Float(expect_float(args.first())?.log10())),
666 ("math", "sqrt") => Ok(Value::Float(expect_float(args.first())?.sqrt())),
667 ("math", "abs") => Ok(Value::Float(expect_float(args.first())?.abs())),
668 ("math", "sin") => Ok(Value::Float(expect_float(args.first())?.sin())),
669 ("math", "cos") => Ok(Value::Float(expect_float(args.first())?.cos())),
670 ("math", "tan") => Ok(Value::Float(expect_float(args.first())?.tan())),
671 ("math", "asin") => Ok(Value::Float(expect_float(args.first())?.asin())),
672 ("math", "acos") => Ok(Value::Float(expect_float(args.first())?.acos())),
673 ("math", "atan") => Ok(Value::Float(expect_float(args.first())?.atan())),
674 ("math", "floor") => Ok(Value::Float(expect_float(args.first())?.floor())),
675 ("math", "ceil") => Ok(Value::Float(expect_float(args.first())?.ceil())),
676 ("math", "round") => Ok(Value::Float(expect_float(args.first())?.round())),
677 ("math", "trunc") => Ok(Value::Float(expect_float(args.first())?.trunc())),
678 ("math", "pow") => {
679 let a = expect_float(args.first())?;
680 let b = expect_float(args.get(1))?;
681 Ok(Value::Float(a.powf(b)))
682 }
683 ("math", "atan2") => {
684 let y = expect_float(args.first())?;
685 let x = expect_float(args.get(1))?;
686 Ok(Value::Float(y.atan2(x)))
687 }
688 ("math", "min") => {
689 let a = expect_float(args.first())?;
690 let b = expect_float(args.get(1))?;
691 Ok(Value::Float(a.min(b)))
692 }
693 ("math", "max") => {
694 let a = expect_float(args.first())?;
695 let b = expect_float(args.get(1))?;
696 Ok(Value::Float(a.max(b)))
697 }
698 ("math", "zeros") => {
699 let r = expect_int(args.first())?;
700 let c = expect_int(args.get(1))?;
701 if r < 0 || c < 0 {
702 return Err(format!("math.zeros: negative dim {r}x{c}"));
703 }
704 let r = r as usize; let c = c as usize;
705 Ok(Value::F64Array { rows: r as u32, cols: c as u32, data: vec![0.0; r * c] })
706 }
707 ("math", "ones") => {
708 let r = expect_int(args.first())?;
709 let c = expect_int(args.get(1))?;
710 if r < 0 || c < 0 {
711 return Err(format!("math.ones: negative dim {r}x{c}"));
712 }
713 let r = r as usize; let c = c as usize;
714 Ok(Value::F64Array { rows: r as u32, cols: c as u32, data: vec![1.0; r * c] })
715 }
716 ("math", "from_lists") => {
717 let rows = expect_list(args.first())?;
718 let r = rows.len();
719 if r == 0 {
720 return Ok(Value::F64Array { rows: 0, cols: 0, data: Vec::new() });
721 }
722 let first_row = match &rows[0] {
723 Value::List(xs) => xs,
724 other => return Err(format!("math.from_lists: row 0 not List, got {other:?}")),
725 };
726 let c = first_row.len();
727 let mut data = Vec::with_capacity(r * c);
728 for (i, row) in rows.iter().enumerate() {
729 let row = match row {
730 Value::List(xs) => xs,
731 other => return Err(format!("math.from_lists: row {i} not List, got {other:?}")),
732 };
733 if row.len() != c {
734 return Err(format!("math.from_lists: row {i} has {} cols, expected {c}", row.len()));
735 }
736 for (j, v) in row.iter().enumerate() {
737 let f = match v {
738 Value::Float(f) => *f,
739 Value::Int(n) => *n as f64,
740 other => return Err(format!("math.from_lists: ({i},{j}) not numeric, got {other:?}")),
741 };
742 data.push(f);
743 }
744 }
745 Ok(Value::F64Array { rows: r as u32, cols: c as u32, data })
746 }
747 ("math", "from_flat") => {
748 let r = expect_int(args.first())?;
749 let c = expect_int(args.get(1))?;
750 let xs = expect_list(args.get(2))?;
751 if r < 0 || c < 0 {
752 return Err(format!("math.from_flat: negative dim {r}x{c}"));
753 }
754 let r = r as usize; let c = c as usize;
755 if xs.len() != r * c {
756 return Err(format!("math.from_flat: list len {} != {}*{}", xs.len(), r, c));
757 }
758 let mut data = Vec::with_capacity(r * c);
759 for v in xs {
760 data.push(match v {
761 Value::Float(f) => *f,
762 Value::Int(n) => *n as f64,
763 other => return Err(format!("math.from_flat: non-numeric element {other:?}")),
764 });
765 }
766 Ok(Value::F64Array { rows: r as u32, cols: c as u32, data })
767 }
768 ("math", "rows") => {
769 let (r, _, _) = unpack_matrix(first_arg(args)?)?;
770 Ok(Value::Int(r as i64))
771 }
772 ("math", "cols") => {
773 let (_, c, _) = unpack_matrix(first_arg(args)?)?;
774 Ok(Value::Int(c as i64))
775 }
776 ("math", "get") => {
777 let (r, c, data) = unpack_matrix(first_arg(args)?)?;
778 let i = expect_int(args.get(1))? as usize;
779 let j = expect_int(args.get(2))? as usize;
780 if i >= r || j >= c {
781 return Err(format!("math.get: ({i},{j}) out of {r}x{c}"));
782 }
783 Ok(Value::Float(data[i * c + j]))
784 }
785 ("math", "to_flat") => {
786 let (_, _, data) = unpack_matrix(first_arg(args)?)?;
787 Ok(Value::List(data.into_iter().map(Value::Float).collect()))
788 }
789 ("math", "transpose") => {
790 let (r, c, data) = unpack_matrix(first_arg(args)?)?;
791 let mut out = vec![0.0; r * c];
792 for i in 0..r {
793 for j in 0..c {
794 out[j * r + i] = data[i * c + j];
795 }
796 }
797 Ok(Value::F64Array { rows: c as u32, cols: r as u32, data: out })
798 }
799 ("math", "matmul") => {
800 let (m, k1, a) = unpack_matrix(first_arg(args)?)?;
801 let (k2, n, b) = unpack_matrix(args.get(1).ok_or("math.matmul: missing arg 1")?)?;
802 if k1 != k2 {
803 return Err(format!("math.matmul: dim mismatch {m}x{k1} · {k2}x{n}"));
804 }
805 let mut c = vec![0.0; m * n];
809 for i in 0..m {
810 for kk in 0..k1 {
811 let aik = a[i * k1 + kk];
812 for j in 0..n {
813 c[i * n + j] += aik * b[kk * n + j];
814 }
815 }
816 }
817 Ok(Value::F64Array { rows: m as u32, cols: n as u32, data: c })
818 }
819 ("math", "scale") => {
820 let s = expect_float(args.first())?;
821 let (r, c, mut data) = unpack_matrix(args.get(1).ok_or("math.scale: missing arg 1")?)?;
822 for x in &mut data { *x *= s; }
823 Ok(Value::F64Array { rows: r as u32, cols: c as u32, data })
824 }
825 ("math", "add") | ("math", "sub") => {
826 let (ar, ac, a) = unpack_matrix(first_arg(args)?)?;
827 let (br, bc, b) = unpack_matrix(args.get(1).ok_or("math.add/sub: missing arg 1")?)?;
828 if ar != br || ac != bc {
829 return Err(format!("math.{op}: shape mismatch {ar}x{ac} vs {br}x{bc}"));
830 }
831 let neg = op == "sub";
832 let mut out = a;
833 for (i, x) in out.iter_mut().enumerate() {
834 if neg { *x -= b[i] } else { *x += b[i] }
835 }
836 Ok(Value::F64Array { rows: ar as u32, cols: ac as u32, data: out })
837 }
838 ("math", "sigmoid") => {
839 let (r, c, mut data) = unpack_matrix(first_arg(args)?)?;
840 for x in &mut data { *x = 1.0 / (1.0 + (-*x).exp()); }
841 Ok(Value::F64Array { rows: r as u32, cols: c as u32, data })
842 }
843
844 ("map", "new") => Ok(Value::Map(BTreeMap::new())),
846 ("map", "size") => Ok(Value::Int(expect_map(args.first())?.len() as i64)),
847 ("map", "has") => {
848 let m = expect_map(args.first())?;
849 let k = MapKey::from_value(args.get(1).ok_or("map.has: missing key")?)?;
850 Ok(Value::Bool(m.contains_key(&k)))
851 }
852 ("map", "get") => {
853 let m = expect_map(args.first())?;
854 let k = MapKey::from_value(args.get(1).ok_or("map.get: missing key")?)?;
855 Ok(match m.get(&k) {
856 Some(v) => some(v.clone()),
857 None => none(),
858 })
859 }
860 ("map", "set") => {
861 let mut m = expect_map(args.first())?.clone();
862 let k = MapKey::from_value(args.get(1).ok_or("map.set: missing key")?)?;
863 let v = args.get(2).ok_or("map.set: missing value")?.clone();
864 m.insert(k, v);
865 Ok(Value::Map(m))
866 }
867 ("map", "delete") => {
868 let mut m = expect_map(args.first())?.clone();
869 let k = MapKey::from_value(args.get(1).ok_or("map.delete: missing key")?)?;
870 m.remove(&k);
871 Ok(Value::Map(m))
872 }
873 ("map", "keys") => {
874 let m = expect_map(args.first())?;
875 Ok(Value::List(m.keys().cloned().map(MapKey::into_value).collect()))
876 }
877 ("map", "values") => {
878 let m = expect_map(args.first())?;
879 Ok(Value::List(m.values().cloned().collect()))
880 }
881 ("map", "entries") => {
882 let m = expect_map(args.first())?;
883 Ok(Value::List(m.iter()
884 .map(|(k, v)| Value::Tuple(vec![k.as_value(), v.clone()]))
885 .collect()))
886 }
887 ("map", "from_list") => {
888 let pairs = expect_list(args.first())?;
889 let mut m = BTreeMap::new();
890 for p in pairs {
891 let items = match p {
892 Value::Tuple(items) if items.len() == 2 => items,
893 other => return Err(format!(
894 "map.from_list element must be a 2-tuple, got {other:?}")),
895 };
896 let k = MapKey::from_value(&items[0])?;
897 m.insert(k, items[1].clone());
898 }
899 Ok(Value::Map(m))
900 }
901
902 ("set", "new") => Ok(Value::Set(BTreeSet::new())),
904 ("set", "size") => Ok(Value::Int(expect_set(args.first())?.len() as i64)),
905 ("set", "has") => {
906 let s = expect_set(args.first())?;
907 let k = MapKey::from_value(args.get(1).ok_or("set.has: missing element")?)?;
908 Ok(Value::Bool(s.contains(&k)))
909 }
910 ("set", "add") => {
911 let mut s = expect_set(args.first())?.clone();
912 let k = MapKey::from_value(args.get(1).ok_or("set.add: missing element")?)?;
913 s.insert(k);
914 Ok(Value::Set(s))
915 }
916 ("set", "delete") => {
917 let mut s = expect_set(args.first())?.clone();
918 let k = MapKey::from_value(args.get(1).ok_or("set.delete: missing element")?)?;
919 s.remove(&k);
920 Ok(Value::Set(s))
921 }
922 ("set", "to_list") => {
923 let s = expect_set(args.first())?;
924 Ok(Value::List(s.iter().cloned().map(MapKey::into_value).collect()))
925 }
926 ("set", "from_list") => {
927 let xs = expect_list(args.first())?;
928 let mut s = BTreeSet::new();
929 for x in xs {
930 s.insert(MapKey::from_value(x)?);
931 }
932 Ok(Value::Set(s))
933 }
934 ("set", "union") => {
935 let a = expect_set(args.first())?;
936 let b = expect_set(args.get(1))?;
937 Ok(Value::Set(a.union(b).cloned().collect()))
938 }
939 ("set", "intersect") => {
940 let a = expect_set(args.first())?;
941 let b = expect_set(args.get(1))?;
942 Ok(Value::Set(a.intersection(b).cloned().collect()))
943 }
944 ("set", "diff") => {
945 let a = expect_set(args.first())?;
946 let b = expect_set(args.get(1))?;
947 Ok(Value::Set(a.difference(b).cloned().collect()))
948 }
949 ("set", "is_empty") => Ok(Value::Bool(expect_set(args.first())?.is_empty())),
950 ("set", "is_subset") => {
951 let a = expect_set(args.first())?;
952 let b = expect_set(args.get(1))?;
953 Ok(Value::Bool(a.is_subset(b)))
954 }
955
956 ("map", "merge") => {
958 let a = expect_map(args.first())?.clone();
961 let b = expect_map(args.get(1))?;
962 let mut out = a;
963 for (k, v) in b {
964 out.insert(k.clone(), v.clone());
965 }
966 Ok(Value::Map(out))
967 }
968 ("map", "is_empty") => Ok(Value::Bool(expect_map(args.first())?.is_empty())),
969
970 ("deque", "new") => Ok(Value::Deque(std::collections::VecDeque::new())),
972 ("deque", "size") => Ok(Value::Int(expect_deque(args.first())?.len() as i64)),
973 ("deque", "is_empty") => Ok(Value::Bool(expect_deque(args.first())?.is_empty())),
974 ("deque", "push_back") => {
975 let mut d = expect_deque(args.first())?.clone();
976 let x = args.get(1).ok_or("deque.push_back: missing value")?.clone();
977 d.push_back(x);
978 Ok(Value::Deque(d))
979 }
980 ("deque", "push_front") => {
981 let mut d = expect_deque(args.first())?.clone();
982 let x = args.get(1).ok_or("deque.push_front: missing value")?.clone();
983 d.push_front(x);
984 Ok(Value::Deque(d))
985 }
986 ("deque", "pop_back") => {
987 let mut d = expect_deque(args.first())?.clone();
988 match d.pop_back() {
989 Some(x) => Ok(Value::Variant {
990 name: "Some".into(),
991 args: vec![Value::Tuple(vec![x, Value::Deque(d)])],
992 }),
993 None => Ok(Value::Variant { name: "None".into(), args: vec![] }),
994 }
995 }
996 ("deque", "pop_front") => {
997 let mut d = expect_deque(args.first())?.clone();
998 match d.pop_front() {
999 Some(x) => Ok(Value::Variant {
1000 name: "Some".into(),
1001 args: vec![Value::Tuple(vec![x, Value::Deque(d)])],
1002 }),
1003 None => Ok(Value::Variant { name: "None".into(), args: vec![] }),
1004 }
1005 }
1006 ("deque", "peek_back") => {
1007 let d = expect_deque(args.first())?;
1008 match d.back() {
1009 Some(x) => Ok(Value::Variant {
1010 name: "Some".into(),
1011 args: vec![x.clone()],
1012 }),
1013 None => Ok(Value::Variant { name: "None".into(), args: vec![] }),
1014 }
1015 }
1016 ("deque", "peek_front") => {
1017 let d = expect_deque(args.first())?;
1018 match d.front() {
1019 Some(x) => Ok(Value::Variant {
1020 name: "Some".into(),
1021 args: vec![x.clone()],
1022 }),
1023 None => Ok(Value::Variant { name: "None".into(), args: vec![] }),
1024 }
1025 }
1026 ("deque", "from_list") => {
1027 let xs = expect_list(args.first())?;
1028 Ok(Value::Deque(xs.iter().cloned().collect()))
1029 }
1030 ("deque", "to_list") => {
1031 let d = expect_deque(args.first())?;
1032 Ok(Value::List(d.iter().cloned().collect()))
1033 }
1034
1035 ("crypto", "sha256") => {
1038 use sha2::{Digest, Sha256};
1039 let data = expect_bytes(args.first())?;
1040 let mut h = Sha256::new();
1041 h.update(data);
1042 Ok(Value::Bytes(h.finalize().to_vec()))
1043 }
1044 ("crypto", "sha512") => {
1045 use sha2::{Digest, Sha512};
1046 let data = expect_bytes(args.first())?;
1047 let mut h = Sha512::new();
1048 h.update(data);
1049 Ok(Value::Bytes(h.finalize().to_vec()))
1050 }
1051 ("crypto", "md5") => {
1052 use md5::{Digest, Md5};
1053 let data = expect_bytes(args.first())?;
1054 let mut h = Md5::new();
1055 h.update(data);
1056 Ok(Value::Bytes(h.finalize().to_vec()))
1057 }
1058 ("crypto", "blake2b") => {
1062 use blake2::{Blake2b512, Digest};
1063 let data = expect_bytes(args.first())?;
1064 let mut h = Blake2b512::new();
1065 h.update(data);
1066 Ok(Value::Bytes(h.finalize().to_vec()))
1067 }
1068 ("crypto", "sha256_str") => {
1072 use sha2::{Digest, Sha256};
1073 let s = expect_str(args.first())?;
1074 let mut h = Sha256::new();
1075 h.update(s.as_bytes());
1076 Ok(Value::Str(hex::encode(h.finalize()).into()))
1077 }
1078 ("crypto", "sha512_str") => {
1079 use sha2::{Digest, Sha512};
1080 let s = expect_str(args.first())?;
1081 let mut h = Sha512::new();
1082 h.update(s.as_bytes());
1083 Ok(Value::Str(hex::encode(h.finalize()).into()))
1084 }
1085 ("crypto", "hmac_sha256") => {
1086 use hmac::{Hmac, KeyInit, Mac};
1087 type HmacSha256 = Hmac<sha2::Sha256>;
1088 let key = expect_bytes(args.first())?;
1089 let data = expect_bytes(args.get(1))?;
1090 let mut mac = HmacSha256::new_from_slice(key)
1091 .map_err(|e| format!("hmac_sha256 key: {e}"))?;
1092 mac.update(data);
1093 Ok(Value::Bytes(mac.finalize().into_bytes().to_vec()))
1094 }
1095 ("crypto", "hmac_sha512") => {
1096 use hmac::{Hmac, KeyInit, Mac};
1097 type HmacSha512 = Hmac<sha2::Sha512>;
1098 let key = expect_bytes(args.first())?;
1099 let data = expect_bytes(args.get(1))?;
1100 let mut mac = HmacSha512::new_from_slice(key)
1101 .map_err(|e| format!("hmac_sha512 key: {e}"))?;
1102 mac.update(data);
1103 Ok(Value::Bytes(mac.finalize().into_bytes().to_vec()))
1104 }
1105 ("crypto", "base64_encode") => {
1106 use base64::{Engine, engine::general_purpose::STANDARD};
1107 let data = expect_bytes(args.first())?;
1108 Ok(Value::Str(STANDARD.encode(data).into()))
1109 }
1110 ("crypto", "base64_decode") => {
1111 use base64::{Engine, engine::general_purpose::STANDARD};
1112 let s = expect_str(args.first())?;
1113 match STANDARD.decode(s) {
1114 Ok(b) => Ok(ok_v(Value::Bytes(b))),
1115 Err(e) => Ok(err_v(Value::Str(format!("base64: {e}").into()))),
1116 }
1117 }
1118 ("crypto", "base64url_encode") => {
1122 use base64::{Engine, engine::general_purpose::URL_SAFE_NO_PAD};
1123 let data = expect_bytes(args.first())?;
1124 Ok(Value::Str(URL_SAFE_NO_PAD.encode(data).into()))
1125 }
1126 ("crypto", "base64url_decode") => {
1127 use base64::{Engine, engine::general_purpose::URL_SAFE_NO_PAD};
1128 let s = expect_str(args.first())?;
1129 match URL_SAFE_NO_PAD.decode(s) {
1130 Ok(b) => Ok(ok_v(Value::Bytes(b))),
1131 Err(e) => Ok(err_v(Value::Str(format!("base64url: {e}").into()))),
1132 }
1133 }
1134 ("crypto", "hex_encode") => {
1135 let data = expect_bytes(args.first())?;
1136 Ok(Value::Str(hex::encode(data).into()))
1137 }
1138 ("crypto", "hex_decode") => {
1139 let s = expect_str(args.first())?;
1140 match hex::decode(s) {
1141 Ok(b) => Ok(ok_v(Value::Bytes(b))),
1142 Err(e) => Ok(err_v(Value::Str(format!("hex: {e}").into()))),
1143 }
1144 }
1145 ("crypto", "constant_time_eq") | ("crypto", "eq") => {
1146 use subtle::ConstantTimeEq;
1147 let a = expect_bytes(args.first())?;
1148 let b = expect_bytes(args.get(1))?;
1149 let eq = if a.len() == b.len() {
1158 a.ct_eq(b).into()
1159 } else {
1160 false
1161 };
1162 Ok(Value::Bool(eq))
1163 }
1164 ("crypto", "eq_str") => {
1167 use subtle::ConstantTimeEq;
1168 let a = expect_str(args.first())?;
1169 let b = expect_str(args.get(1))?;
1170 let eq = if a.len() == b.len() {
1171 a.as_bytes().ct_eq(b.as_bytes()).into()
1172 } else {
1173 false
1174 };
1175 Ok(Value::Bool(eq))
1176 }
1177
1178 ("crypto", "aes_gcm_seal") => Ok(aes_gcm_seal_impl(args)),
1188 ("crypto", "aes_gcm_open") => Ok(aes_gcm_open_impl(args)),
1189 ("crypto", "chacha20_poly1305_seal") => Ok(chacha20_seal_impl(args)),
1190 ("crypto", "chacha20_poly1305_open") => Ok(chacha20_open_impl(args)),
1191 ("crypto", "pbkdf2_sha256") => Ok(pbkdf2_sha256_impl(args)),
1192 ("crypto", "hkdf_sha256") => Ok(hkdf_sha256_impl(args)),
1193 ("crypto", "argon2id") => Ok(argon2id_impl(args)),
1194
1195 ("random", "seed") => {
1202 let s = args.first().ok_or("random.seed: missing arg")?.as_int();
1203 let mixed = splitmix64(s as u64).0;
1208 Ok(rng_value(mixed))
1209 }
1210 ("random", "int") => {
1211 let state = rng_decode(args.first())?;
1212 let lo = args.get(1).ok_or("random.int: missing lo")?.as_int();
1213 let hi = args.get(2).ok_or("random.int: missing hi")?.as_int();
1214 if hi < lo {
1215 return Err(format!(
1216 "random.int: hi ({hi}) must be >= lo ({lo})"));
1217 }
1218 let span = (hi as i128) - (lo as i128) + 1;
1219 let (raw, next_state) = splitmix64(state);
1220 let drawn = lo as i128 + (raw as u128 % span as u128) as i128;
1225 Ok(Value::Tuple(vec![
1226 Value::Int(drawn as i64),
1227 rng_value(next_state),
1228 ]))
1229 }
1230 ("random", "float") => {
1231 let state = rng_decode(args.first())?;
1232 let (raw, next_state) = splitmix64(state);
1233 let f = ((raw >> 11) as f64) / ((1u64 << 53) as f64);
1236 Ok(Value::Tuple(vec![Value::Float(f), rng_value(next_state)]))
1237 }
1238 ("random", "choose") => {
1239 let state = rng_decode(args.first())?;
1240 let xs = match args.get(1) {
1241 Some(Value::List(xs)) => xs,
1242 _ => return Err("random.choose: expected List".into()),
1243 };
1244 if xs.is_empty() {
1245 return Ok(Value::Variant {
1246 name: "None".into(), args: vec![],
1247 });
1248 }
1249 let (raw, next_state) = splitmix64(state);
1250 let idx = (raw as usize) % xs.len();
1251 let pick = xs[idx].clone();
1252 Ok(Value::Variant {
1253 name: "Some".into(),
1254 args: vec![Value::Tuple(vec![pick, rng_value(next_state)])],
1255 })
1256 }
1257
1258 ("parser", "char") => {
1263 let s = expect_str(args.first())?;
1264 if s.chars().count() != 1 {
1265 return Err(format!(
1266 "parser.char: expected 1-character string, got {s:?}"));
1267 }
1268 Ok(parser_node("Char", &[("ch", Value::Str(s.into()))]))
1269 }
1270 ("parser", "string") => {
1271 let s = expect_str(args.first())?;
1272 Ok(parser_node("String", &[("s", Value::Str(s.into()))]))
1273 }
1274 ("parser", "digit") => Ok(parser_node("Digit", &[])),
1275 ("parser", "alpha") => Ok(parser_node("Alpha", &[])),
1276 ("parser", "whitespace") => Ok(parser_node("Whitespace", &[])),
1277 ("parser", "eof") => Ok(parser_node("Eof", &[])),
1278 ("parser", "seq") => {
1279 let a = args.first().cloned()
1280 .ok_or_else(|| "parser.seq: missing first parser".to_string())?;
1281 let b = args.get(1).cloned()
1282 .ok_or_else(|| "parser.seq: missing second parser".to_string())?;
1283 Ok(parser_node("Seq", &[("a", a), ("b", b)]))
1284 }
1285 ("parser", "alt") => {
1286 let a = args.first().cloned()
1287 .ok_or_else(|| "parser.alt: missing first parser".to_string())?;
1288 let b = args.get(1).cloned()
1289 .ok_or_else(|| "parser.alt: missing second parser".to_string())?;
1290 Ok(parser_node("Alt", &[("a", a), ("b", b)]))
1291 }
1292 ("parser", "many") => {
1293 let p = args.first().cloned()
1294 .ok_or_else(|| "parser.many: missing inner parser".to_string())?;
1295 Ok(parser_node("Many", &[("p", p)]))
1296 }
1297 ("parser", "optional") => {
1298 let p = args.first().cloned()
1299 .ok_or_else(|| "parser.optional: missing inner parser".to_string())?;
1300 Ok(parser_node("Optional", &[("p", p)]))
1301 }
1302 ("parser", "map") => {
1306 let p = args.first().cloned()
1307 .ok_or_else(|| "parser.map: missing parser".to_string())?;
1308 let f = args.get(1).cloned()
1309 .ok_or_else(|| "parser.map: missing closure".to_string())?;
1310 Ok(parser_node("Map", &[("p", p), ("f", f)]))
1311 }
1312 ("parser", "and_then") => {
1313 let p = args.first().cloned()
1314 .ok_or_else(|| "parser.and_then: missing parser".to_string())?;
1315 let f = args.get(1).cloned()
1316 .ok_or_else(|| "parser.and_then: missing closure".to_string())?;
1317 Ok(parser_node("AndThen", &[("p", p), ("f", f)]))
1318 }
1319 ("regex", "compile") => {
1329 let pat = expect_str(args.first())?;
1330 match get_or_compile_regex(&pat) {
1331 Ok(_) => Ok(ok_v(Value::Str(pat.into()))),
1332 Err(e) => Ok(err_v(Value::Str(e.into()))),
1333 }
1334 }
1335 ("regex", "is_match") => {
1336 let pat = expect_str(args.first())?;
1337 let s = expect_str(args.get(1))?;
1338 let re = get_or_compile_regex(&pat).map_err(|e| format!("regex.is_match: {e}"))?;
1339 Ok(Value::Bool(re.is_match(&s)))
1340 }
1341 ("regex", "is_match_str") => {
1346 let pat = expect_str(args.first())?;
1347 let s = expect_str(args.get(1))?;
1348 match get_or_compile_regex(&pat) {
1349 Ok(re) => Ok(Value::Bool(re.is_match(&s))),
1350 Err(_) => Ok(Value::Bool(false)),
1351 }
1352 }
1353 ("regex", "find") => {
1354 let pat = expect_str(args.first())?;
1355 let s = expect_str(args.get(1))?;
1356 let re = get_or_compile_regex(&pat).map_err(|e| format!("regex.find: {e}"))?;
1357 match re.captures(&s) {
1358 Some(caps) => Ok(Value::Variant {
1359 name: "Some".into(),
1360 args: vec![match_value(&caps)],
1361 }),
1362 None => Ok(Value::Variant { name: "None".into(), args: vec![] }),
1363 }
1364 }
1365 ("regex", "find_all") => {
1366 let pat = expect_str(args.first())?;
1367 let s = expect_str(args.get(1))?;
1368 let re = get_or_compile_regex(&pat).map_err(|e| format!("regex.find_all: {e}"))?;
1369 let items: std::collections::VecDeque<Value> = re.captures_iter(&s).map(|caps| match_value(&caps)).collect();
1370 Ok(Value::List(items))
1371 }
1372 ("regex", "replace") => {
1373 let pat = expect_str(args.first())?;
1374 let s = expect_str(args.get(1))?;
1375 let rep = expect_str(args.get(2))?;
1376 let re = get_or_compile_regex(&pat).map_err(|e| format!("regex.replace: {e}"))?;
1377 Ok(Value::Str(re.replace(&s, rep.as_str()).into_owned().into()))
1378 }
1379 ("regex", "replace_all") => {
1380 let pat = expect_str(args.first())?;
1381 let s = expect_str(args.get(1))?;
1382 let rep = expect_str(args.get(2))?;
1383 let re = get_or_compile_regex(&pat).map_err(|e| format!("regex.replace_all: {e}"))?;
1384 Ok(Value::Str(re.replace_all(&s, rep.as_str()).into_owned().into()))
1385 }
1386 ("datetime", "parse_iso") => {
1389 let s = expect_str(args.first())?;
1390 match chrono::DateTime::parse_from_rfc3339(&s) {
1391 Ok(dt) => Ok(ok_v(Value::Int(instant_from_chrono(dt)))),
1392 Err(e) => Ok(err_v(Value::Str(format!("parse_iso: {e}").into()))),
1393 }
1394 }
1395 ("datetime", "format_iso") => {
1396 let n = expect_int(args.first())?;
1397 Ok(Value::Str(format_iso(n).into()))
1398 }
1399 ("datetime", "parse") => {
1400 let s = expect_str(args.first())?;
1401 let fmt = expect_str(args.get(1))?;
1402 match chrono::NaiveDateTime::parse_from_str(&s, &fmt) {
1403 Ok(naive) => {
1404 use chrono::TimeZone;
1405 match chrono::Utc.from_local_datetime(&naive).single() {
1406 Some(dt) => Ok(ok_v(Value::Int(instant_from_chrono(dt)))),
1407 None => Ok(err_v(Value::Str("parse: ambiguous local time".into()))),
1408 }
1409 }
1410 Err(e) => Ok(err_v(Value::Str(format!("parse: {e}").into()))),
1411 }
1412 }
1413 ("datetime", "format") => {
1414 let n = expect_int(args.first())?;
1415 let fmt = expect_str(args.get(1))?;
1416 let dt = chrono_from_instant(n);
1417 Ok(Value::Str(dt.format(&fmt).to_string().into()))
1418 }
1419 ("datetime", "to_components") => {
1420 let n = expect_int(args.first())?;
1421 let tz = match parse_tz_arg(args.get(1)) {
1422 Ok(t) => t,
1423 Err(e) => return Ok(err_v(Value::Str(e.into()))),
1424 };
1425 match resolve_tz_to_components(n, &tz) {
1426 Ok(rec) => Ok(ok_v(rec)),
1427 Err(e) => Ok(err_v(Value::Str(e.into()))),
1428 }
1429 }
1430 ("datetime", "from_components") => {
1431 let rec = match args.first() {
1432 Some(Value::Record { fields: r, .. }) => r.clone(),
1433 _ => return Err("from_components: expected DateTime record".into()),
1434 };
1435 match instant_from_components(&rec) {
1436 Ok(n) => Ok(ok_v(Value::Int(n))),
1437 Err(e) => Ok(err_v(Value::Str(e.into()))),
1438 }
1439 }
1440 ("datetime", "add") => {
1441 let a = expect_int(args.first())?;
1442 let d = expect_int(args.get(1))?;
1443 Ok(Value::Int(a.saturating_add(d)))
1444 }
1445 ("datetime", "diff") => {
1446 let a = expect_int(args.first())?;
1447 let b = expect_int(args.get(1))?;
1448 Ok(Value::Int(a.saturating_sub(b)))
1449 }
1450 ("datetime", "duration_seconds") => {
1451 let s = expect_float(args.first())?;
1452 let nanos = (s * 1_000_000_000.0) as i64;
1453 Ok(Value::Int(nanos))
1454 }
1455 ("datetime", "duration_minutes") => {
1456 let m = expect_int(args.first())?;
1457 Ok(Value::Int(m.saturating_mul(60_000_000_000)))
1458 }
1459 ("datetime", "duration_days") => {
1460 let d = expect_int(args.first())?;
1461 Ok(Value::Int(d.saturating_mul(86_400_000_000_000)))
1462 }
1463 ("datetime", "before") => {
1465 let a = expect_int(args.first())?;
1466 let b = expect_int(args.get(1))?;
1467 Ok(Value::Bool(a < b))
1468 }
1469 ("datetime", "after") => {
1470 let a = expect_int(args.first())?;
1471 let b = expect_int(args.get(1))?;
1472 Ok(Value::Bool(a > b))
1473 }
1474 ("datetime", "compare") => {
1475 let a = expect_int(args.first())?;
1476 let b = expect_int(args.get(1))?;
1477 Ok(Value::Int(a.cmp(&b) as i64))
1478 }
1479 ("duration", "seconds") => {
1481 let nanos = expect_int(args.first())?;
1482 Ok(Value::Int(nanos / 1_000_000_000))
1483 }
1484
1485 ("regex", "split") => {
1486 let pat = expect_str(args.first())?;
1487 let s = expect_str(args.get(1))?;
1488 let re = get_or_compile_regex(&pat).map_err(|e| format!("regex.split: {e}"))?;
1489 let parts: std::collections::VecDeque<Value> = re.split(&s).map(|p| Value::Str(p.into())).collect();
1490 Ok(Value::List(parts))
1491 }
1492
1493 ("http", "with_header") => {
1496 let req = expect_record_pure(args.first())?.clone();
1497 let k = expect_str(args.get(1))?;
1498 let v = expect_str(args.get(2))?;
1499 Ok(Value::record_interned(http_set_header(req, &k, &v)))
1500 }
1501 ("http", "with_auth") => {
1502 let req = expect_record_pure(args.first())?.clone();
1503 let scheme = expect_str(args.get(1))?;
1504 let token = expect_str(args.get(2))?;
1505 let value = format!("{scheme} {token}");
1506 Ok(Value::record_interned(http_set_header(req, "Authorization", &value)))
1507 }
1508 ("http", "with_query") => {
1509 let req = expect_record_pure(args.first())?.clone();
1510 let params = match args.get(1) {
1511 Some(Value::Map(m)) => m.clone(),
1512 Some(other) => return Err(format!(
1513 "http.with_query: params must be Map[Str, Str], got {other:?}")),
1514 None => return Err("http.with_query: missing params argument".into()),
1515 };
1516 Ok(Value::record_interned(http_append_query(req, ¶ms)))
1517 }
1518 ("http", "with_timeout_ms") => {
1519 let req = expect_record_pure(args.first())?.clone();
1520 let ms = expect_int(args.get(1))?;
1521 let mut out = req;
1522 out.insert("timeout_ms".into(), Value::Variant {
1523 name: "Some".into(),
1524 args: vec![Value::Int(ms)],
1525 });
1526 Ok(Value::record_interned(out))
1527 }
1528 ("http", "json_body") => {
1529 let resp = expect_record_pure(args.first())?;
1530 let body = match resp.get("body") {
1531 Some(Value::Bytes(b)) => b.clone(),
1532 _ => return Err("http.json_body: HttpResponse.body must be Bytes".into()),
1533 };
1534 let s = match std::str::from_utf8(&body) {
1535 Ok(s) => s,
1536 Err(e) => return Ok(http_decode_err_pure(format!("body not UTF-8: {e}"))),
1537 };
1538 match serde_json::from_str::<serde_json::Value>(s) {
1539 Ok(j) => Ok(ok_v(Value::from_json(&j))),
1540 Err(e) => Ok(http_decode_err_pure(format!("json parse: {e}"))),
1541 }
1542 }
1543 ("http", "text_body") => {
1544 let resp = expect_record_pure(args.first())?;
1545 let body = match resp.get("body") {
1546 Some(Value::Bytes(b)) => b.clone(),
1547 _ => return Err("http.text_body: HttpResponse.body must be Bytes".into()),
1548 };
1549 match String::from_utf8(body) {
1550 Ok(s) => Ok(ok_v(Value::Str(s.into()))),
1551 Err(e) => Ok(http_decode_err_pure(format!("body not UTF-8: {e}"))),
1552 }
1553 }
1554
1555 ("cli", "flag") => {
1559 let name = expect_str(args.first())?;
1560 let short = opt_str(args.get(1));
1561 let help = expect_str(args.get(2))?;
1562 Ok(value_from_json(crate::cli::flag_spec(&name, short.as_deref(), &help)))
1563 }
1564 ("cli", "option") => {
1565 let name = expect_str(args.first())?;
1566 let short = opt_str(args.get(1));
1567 let help = expect_str(args.get(2))?;
1568 let default = opt_str(args.get(3));
1569 Ok(value_from_json(crate::cli::option_spec(&name, short.as_deref(), &help, default.as_deref())))
1570 }
1571 ("cli", "positional") => {
1572 let name = expect_str(args.first())?;
1573 let help = expect_str(args.get(1))?;
1574 let required = expect_bool(args.get(2))?;
1575 Ok(value_from_json(crate::cli::positional_spec(&name, &help, required)))
1576 }
1577 ("cli", "spec") => {
1578 let name = expect_str(args.first())?;
1579 let help = expect_str(args.get(1))?;
1580 let arg_specs: Vec<serde_json::Value> = expect_list(args.get(2))?
1581 .iter().map(value_to_json).collect();
1582 let subs: Vec<serde_json::Value> = expect_list(args.get(3))?
1583 .iter().map(value_to_json).collect();
1584 Ok(value_from_json(crate::cli::build_spec(&name, &help, arg_specs, subs)))
1585 }
1586 ("cli", "parse") => {
1587 let spec = value_to_json(args.first().unwrap_or(&Value::Unit));
1588 let argv: Vec<String> = expect_list(args.get(1))?
1589 .iter().map(|v| match v {
1590 Value::Str(s) => Ok(s.to_string()),
1591 other => Err(format!("cli.parse: argv must be List[Str], got {other:?}")),
1592 }).collect::<Result<_, _>>()?;
1593 match crate::cli::parse(&spec, &argv) {
1594 Ok(parsed) => Ok(ok_v(value_from_json(parsed))),
1595 Err(msg) => Ok(err_v(Value::Str(msg.into()))),
1596 }
1597 }
1598 ("cli", "envelope") => {
1599 let ok = expect_bool(args.first())?;
1600 let cmd = expect_str(args.get(1))?;
1601 let data = value_to_json(args.get(2).unwrap_or(&Value::Unit));
1602 Ok(value_from_json(crate::cli::envelope(ok, &cmd, data)))
1603 }
1604 ("cli", "describe") => {
1605 let spec = value_to_json(args.first().unwrap_or(&Value::Unit));
1606 Ok(value_from_json(crate::cli::describe(&spec)))
1607 }
1608 ("cli", "help") => {
1609 let spec = value_to_json(args.first().unwrap_or(&Value::Unit));
1610 Ok(Value::Str(crate::cli::help_text(&spec).into()))
1611 }
1612
1613 ("arrow", op) => match crate::arrow::dispatch(op, args) {
1615 Some(r) => r,
1616 None => Err(format!("unknown pure builtin: arrow.{op}")),
1617 },
1618 #[cfg(feature = "df")]
1622 ("df", op) => match crate::df::dispatch(op, args) {
1623 Some(r) => r,
1624 None => Err(format!("unknown pure builtin: df.{op}")),
1625 },
1626 #[cfg(not(feature = "df"))]
1627 ("df", op) => Err(format!(
1628 "df.{op}: this build was compiled without the `df` feature; \
1629 Polars-backed dataframe query ops are unavailable"
1630 )),
1631
1632 ("decimal", "decimal") => {
1639 let coef = expect_int(args.first())?;
1640 let exp = expect_int(args.get(1))?;
1641 Ok(make_decimal(coef, exp))
1642 }
1643 ("decimal", "zero") => Ok(make_decimal(0, 0)),
1644 ("decimal", "one") => Ok(make_decimal(1, 0)),
1645 ("decimal", "from_int") => {
1646 Ok(make_decimal(expect_int(args.first())?, 0))
1647 }
1648 ("decimal", "pow10") => {
1649 Ok(Value::Int(decimal_pow10(expect_int(args.first())?)?))
1650 }
1651 ("decimal", "add") => {
1652 let (ca, ea) = expect_decimal(args.first())?;
1653 let (cb, eb) = expect_decimal(args.get(1))?;
1654 let (a2, b2, e) = decimal_align(ca, ea, cb, eb)?;
1655 Ok(make_decimal(
1656 a2.checked_add(b2).ok_or("decimal.add: overflow")?, e))
1657 }
1658 ("decimal", "sub") => {
1659 let (ca, ea) = expect_decimal(args.first())?;
1660 let (cb, eb) = expect_decimal(args.get(1))?;
1661 let (a2, b2, e) = decimal_align(ca, ea, cb, eb)?;
1662 Ok(make_decimal(
1663 a2.checked_sub(b2).ok_or("decimal.sub: overflow")?, e))
1664 }
1665 ("decimal", "mul") => {
1666 let (ca, ea) = expect_decimal(args.first())?;
1667 let (cb, eb) = expect_decimal(args.get(1))?;
1668 Ok(make_decimal(
1669 ca.checked_mul(cb).ok_or("decimal.mul: overflow")?,
1670 ea.checked_add(eb).ok_or("decimal.mul: exponent overflow")?,
1671 ))
1672 }
1673 ("decimal", "compare") => {
1674 let (ca, ea) = expect_decimal(args.first())?;
1675 let (cb, eb) = expect_decimal(args.get(1))?;
1676 let (a2, b2, _) = decimal_align(ca, ea, cb, eb)?;
1677 Ok(Value::Int(if a2 < b2 { -1 } else if a2 > b2 { 1 } else { 0 }))
1678 }
1679 ("decimal", "is_zero") => {
1680 let (c, _) = expect_decimal(args.first())?;
1681 Ok(Value::Bool(c == 0))
1682 }
1683 ("decimal", "is_positive") => {
1684 let (c, _) = expect_decimal(args.first())?;
1685 Ok(Value::Bool(c > 0))
1686 }
1687 ("decimal", "is_negative") => {
1688 let (c, _) = expect_decimal(args.first())?;
1689 Ok(Value::Bool(c < 0))
1690 }
1691 ("decimal", "negate") => {
1692 let (c, e) = expect_decimal(args.first())?;
1693 Ok(make_decimal(-c, e))
1694 }
1695 ("decimal", "abs") => {
1696 let (c, e) = expect_decimal(args.first())?;
1697 Ok(make_decimal(c.abs(), e))
1698 }
1699 ("decimal", "normalize") => {
1700 let (mut c, mut e) = expect_decimal(args.first())?;
1701 if c == 0 { return Ok(make_decimal(0, 0)); }
1702 while c % 10 == 0 { c /= 10; e += 1; }
1703 Ok(make_decimal(c, e))
1704 }
1705 ("decimal", "round_to") => {
1706 let (c, e) = expect_decimal(args.first())?;
1707 let target_e = expect_int(args.get(1))?;
1708 let mode = expect_str(args.get(2))?;
1709 Ok(make_decimal(decimal_round(c, e, target_e, &mode)?, target_e))
1710 }
1711 ("decimal", "to_str") => {
1712 let (c, e) = expect_decimal(args.first())?;
1713 Ok(Value::Str(decimal_to_str(c, e)?.into()))
1714 }
1715
1716 _ => Err(format!("unknown pure builtin: {kind}.{op}")),
1717 }
1718}
1719
1720fn expect_decimal(v: Option<&Value>) -> Result<(i64, i64), String> {
1724 match v {
1725 Some(Value::Record { fields, .. }) => {
1726 let coef = match fields.get("coefficient") {
1727 Some(Value::Int(n)) => *n,
1728 _ => return Err("decimal: missing or invalid 'coefficient' field".into()),
1729 };
1730 let exp = match fields.get("exponent") {
1731 Some(Value::Int(n)) => *n,
1732 _ => return Err("decimal: missing or invalid 'exponent' field".into()),
1733 };
1734 Ok((coef, exp))
1735 }
1736 Some(other) => Err(format!("decimal: expected {{ coefficient, exponent }} record, got {other:?}")),
1737 None => Err("decimal: missing argument".into()),
1738 }
1739}
1740
1741fn make_decimal(coefficient: i64, exponent: i64) -> Value {
1743 let mut fields = indexmap::IndexMap::new();
1744 fields.insert("coefficient".into(), Value::Int(coefficient));
1745 fields.insert("exponent".into(), Value::Int(exponent));
1746 Value::record_interned(fields)
1747}
1748
1749fn decimal_pow10(n: i64) -> Result<i64, String> {
1751 if n < 0 { return Err(format!("decimal.pow10: negative exponent {n}")); }
1752 if n > 18 { return Err(format!("decimal.pow10: exponent {n} exceeds max (18)")); }
1753 Ok(10i64.pow(n as u32))
1754}
1755
1756fn decimal_align(ca: i64, ea: i64, cb: i64, eb: i64) -> Result<(i64, i64, i64), String> {
1759 if ea == eb { return Ok((ca, cb, ea)); }
1760 if ea > eb {
1761 let scale = decimal_pow10(ea - eb)?;
1762 let ca2 = ca.checked_mul(scale)
1763 .ok_or_else(|| format!("decimal: overflow aligning (shift {})", ea - eb))?;
1764 Ok((ca2, cb, eb))
1765 } else {
1766 let scale = decimal_pow10(eb - ea)?;
1767 let cb2 = cb.checked_mul(scale)
1768 .ok_or_else(|| format!("decimal: overflow aligning (shift {})", eb - ea))?;
1769 Ok((ca, cb2, ea))
1770 }
1771}
1772
1773fn decimal_round(c: i64, e: i64, target_e: i64, mode: &str) -> Result<i64, String> {
1778 if e >= target_e {
1779 let shift = e - target_e;
1781 let scale = decimal_pow10(shift)?;
1782 return c.checked_mul(scale)
1783 .ok_or_else(|| format!("decimal.round_to: overflow scaling (shift {shift})"));
1784 }
1785 let shift = target_e - e;
1787 let divisor = decimal_pow10(shift)?;
1788 let q = c / divisor;
1789 let r = c % divisor; if r == 0 { return Ok(q); }
1792
1793 let abs_r = r.abs();
1794 let positive = r > 0; let rounded = match mode {
1797 "Down" => q,
1798 "Up" => if positive { q + 1 } else { q - 1 },
1799 "Floor" => if positive { q } else { q - 1 },
1800 "Ceiling" => if positive { q + 1 } else { q },
1801 "HalfUp" => {
1802 if abs_r * 2 >= divisor {
1803 if positive { q + 1 } else { q - 1 }
1804 } else { q }
1805 }
1806 "HalfDown" => {
1807 if abs_r * 2 > divisor {
1808 if positive { q + 1 } else { q - 1 }
1809 } else { q }
1810 }
1811 "HalfEven" => {
1812 if abs_r * 2 > divisor {
1813 if positive { q + 1 } else { q - 1 }
1814 } else if abs_r * 2 == divisor {
1815 if q % 2 == 0 { q } else { if positive { q + 1 } else { q - 1 } }
1817 } else { q }
1818 }
1819 other => return Err(format!(
1820 "decimal.round_to: unknown rounding mode {other:?}; \
1821 valid modes: HalfUp HalfDown HalfEven Down Up Ceiling Floor")),
1822 };
1823 Ok(rounded)
1824}
1825
1826fn decimal_to_str(c: i64, e: i64) -> Result<String, String> {
1829 if e == 0 { return Ok(c.to_string()); }
1830 if e > 0 {
1831 let scale = decimal_pow10(e)?;
1832 let val = c.checked_mul(scale)
1833 .ok_or("decimal.to_str: overflow")?;
1834 return Ok(val.to_string());
1835 }
1836 let scale = decimal_pow10(-e)?;
1838 let sign = if c < 0 { "-" } else { "" };
1839 let abs_c = c.abs();
1840 let int_part = abs_c / scale;
1841 let frac_part = abs_c % scale;
1842 let decimal_places = (-e) as usize;
1843 Ok(format!("{sign}{int_part}.{frac_part:0>decimal_places$}"))
1844}
1845
1846fn opt_str(arg: Option<&Value>) -> Option<String> {
1850 match arg {
1851 Some(Value::Variant { name, args }) if name == "Some" => {
1852 args.first().and_then(|v| match v {
1853 Value::Str(s) => Some(s.to_string()),
1854 _ => None,
1855 })
1856 }
1857 _ => None,
1858 }
1859}
1860
1861fn value_from_json(v: serde_json::Value) -> Value { Value::from_json(&v) }
1862
1863fn regex_cache() -> &'static Mutex<HashMap<String, regex::Regex>> {
1868 static CACHE: OnceLock<Mutex<HashMap<String, regex::Regex>>> = OnceLock::new();
1869 CACHE.get_or_init(|| Mutex::new(HashMap::new()))
1870}
1871
1872fn get_or_compile_regex(pattern: &str) -> Result<regex::Regex, String> {
1873 let cache = regex_cache();
1874 {
1875 let guard = cache.lock().unwrap();
1876 if let Some(re) = guard.get(pattern) {
1877 return Ok(re.clone());
1878 }
1879 }
1880 let re = regex::Regex::new(pattern).map_err(|e| format!("invalid regex: {e}"))?;
1881 let mut guard = cache.lock().unwrap();
1882 guard.insert(pattern.to_string(), re.clone());
1883 Ok(re)
1884}
1885
1886fn match_value(caps: ®ex::Captures) -> Value {
1890 let m0 = caps.get(0).expect("regex match always has group 0");
1891 let mut rec = indexmap::IndexMap::new();
1892 rec.insert("text".into(), Value::Str(m0.as_str().into()));
1893 rec.insert("start".into(), Value::Int(m0.start() as i64));
1894 rec.insert("end".into(), Value::Int(m0.end() as i64));
1895 let groups: std::collections::VecDeque<Value> = (1..caps.len())
1896 .map(|i| {
1897 Value::Str(
1898 caps.get(i)
1899 .map(|m| m.as_str())
1900 .unwrap_or_default()
1901 .into(),
1902 )
1903 })
1904 .collect();
1905 rec.insert("groups".into(), Value::List(groups));
1906 Value::record_dynamic(rec)
1907}
1908
1909fn expect_map(v: Option<&Value>) -> Result<&BTreeMap<MapKey, Value>, String> {
1910 match v {
1911 Some(Value::Map(m)) => Ok(m),
1912 other => Err(format!("expected Map, got {other:?}")),
1913 }
1914}
1915
1916fn expect_set(v: Option<&Value>) -> Result<&BTreeSet<MapKey>, String> {
1917 match v {
1918 Some(Value::Set(s)) => Ok(s),
1919 other => Err(format!("expected Set, got {other:?}")),
1920 }
1921}
1922
1923fn unpack_matrix(v: &Value) -> Result<(usize, usize, Vec<f64>), String> {
1927 if let Value::F64Array { rows, cols, data } = v {
1928 return Ok((*rows as usize, *cols as usize, data.clone()));
1929 }
1930 let rec = match v {
1931 Value::Record { fields: r, .. } => r,
1932 other => return Err(format!("expected matrix, got {other:?}")),
1933 };
1934 let rows = match rec.get("rows") {
1935 Some(Value::Int(n)) => *n as usize,
1936 _ => return Err("matrix: missing/invalid `rows`".into()),
1937 };
1938 let cols = match rec.get("cols") {
1939 Some(Value::Int(n)) => *n as usize,
1940 _ => return Err("matrix: missing/invalid `cols`".into()),
1941 };
1942 let data = match rec.get("data") {
1943 Some(Value::List(items)) => {
1944 let mut out = Vec::with_capacity(items.len());
1945 for it in items {
1946 out.push(match it {
1947 Value::Float(f) => *f,
1948 Value::Int(n) => *n as f64,
1949 other => return Err(format!("matrix data: not numeric, got {other:?}")),
1950 });
1951 }
1952 out
1953 }
1954 _ => return Err("matrix: missing/invalid `data`".into()),
1955 };
1956 if data.len() != rows * cols {
1957 return Err(format!("matrix: data len {} != {rows}*{cols}", data.len()));
1958 }
1959 Ok((rows, cols, data))
1960}
1961
1962fn expect_bytes(v: Option<&Value>) -> Result<&Vec<u8>, String> {
1963 match v {
1964 Some(Value::Bytes(b)) => Ok(b),
1965 Some(other) => Err(format!("expected Bytes, got {other:?}")),
1966 None => Err("missing argument".into()),
1967 }
1968}
1969
1970fn first_arg(args: &[Value]) -> Result<&Value, String> {
1971 args.first().ok_or_else(|| "missing argument".into())
1972}
1973
1974fn tuple_index(v: &Value, i: usize) -> Result<Value, String> {
1975 match v {
1976 Value::Tuple(items) => items.get(i).cloned()
1977 .ok_or_else(|| format!("tuple index {i} out of range (len={})", items.len())),
1978 other => Err(format!("expected Tuple, got {other:?}")),
1979 }
1980}
1981
1982fn expect_str(v: Option<&Value>) -> Result<String, String> {
1983 match v {
1984 Some(Value::Str(s)) => Ok(s.to_string()),
1985 Some(other) => Err(format!("expected Str, got {other:?}")),
1986 None => Err("missing argument".into()),
1987 }
1988}
1989
1990fn expect_int(v: Option<&Value>) -> Result<i64, String> {
1991 match v {
1992 Some(Value::Int(n)) => Ok(*n),
1993 Some(other) => Err(format!("expected Int, got {other:?}")),
1994 None => Err("missing argument".into()),
1995 }
1996}
1997
1998fn expect_float(v: Option<&Value>) -> Result<f64, String> {
1999 match v {
2000 Some(Value::Float(f)) => Ok(*f),
2001 Some(other) => Err(format!("expected Float, got {other:?}")),
2002 None => Err("missing argument".into()),
2003 }
2004}
2005
2006fn expect_list(v: Option<&Value>) -> Result<&std::collections::VecDeque<Value>, String> {
2007 match v {
2008 Some(Value::List(xs)) => Ok(xs),
2009 Some(other) => Err(format!("expected List, got {other:?}")),
2010 None => Err("missing argument".into()),
2011 }
2012}
2013
2014fn expect_bool(v: Option<&Value>) -> Result<bool, String> {
2015 match v {
2016 Some(Value::Bool(b)) => Ok(*b),
2017 Some(other) => Err(format!("expected Bool, got {other:?}")),
2018 None => Err("missing argument".into()),
2019 }
2020}
2021
2022fn expect_deque(v: Option<&Value>) -> Result<&std::collections::VecDeque<Value>, String> {
2023 match v {
2024 Some(Value::Deque(d)) => Ok(d),
2025 Some(other) => Err(format!("expected Deque, got {other:?}")),
2026 None => Err("missing argument".into()),
2027 }
2028}
2029
2030fn some(v: Value) -> Value { Value::Variant { name: "Some".into(), args: vec![v] } }
2031fn none() -> Value { Value::Variant { name: "None".into(), args: Vec::new() } }
2032fn ok_v(v: Value) -> Value { Value::Variant { name: "Ok".into(), args: vec![v] } }
2033fn err_v(v: Value) -> Value { Value::Variant { name: "Err".into(), args: vec![v] } }
2034
2035fn parser_node(kind: &str, fields: &[(&str, Value)]) -> Value {
2043 let mut r = indexmap::IndexMap::new();
2044 r.insert("kind".into(), Value::Str(kind.into()));
2045 for (k, v) in fields {
2046 r.insert((*k).into(), v.clone());
2047 }
2048 Value::record_dynamic(r)
2049}
2050
2051fn splitmix64(state: u64) -> (u64, u64) {
2062 let next = state.wrapping_add(0x9E37_79B9_7F4A_7C15);
2063 let mut z = next;
2064 z = (z ^ (z >> 30)).wrapping_mul(0xBF58_476D_1CE4_E5B9);
2065 z = (z ^ (z >> 27)).wrapping_mul(0x94D0_49BB_1331_11EB);
2066 let z = z ^ (z >> 31);
2067 (z, next)
2068}
2069
2070fn rng_value(state: u64) -> Value {
2074 let mut fields = indexmap::IndexMap::new();
2075 fields.insert("state".into(), Value::Int(state as i64));
2076 Value::record_dynamic(fields)
2077}
2078
2079fn rng_decode(v: Option<&Value>) -> Result<u64, String> {
2081 let rec = match v {
2082 Some(Value::Record { fields: r, .. }) => r,
2083 Some(other) => return Err(format!("expected Rng, got {other:?}")),
2084 None => return Err("missing Rng arg".into()),
2085 };
2086 match rec.get("state") {
2087 Some(Value::Int(n)) => Ok(*n as u64),
2088 _ => Err("malformed Rng: missing `state :: Int`".into()),
2089 }
2090}
2091
2092fn expect_record_pure(v: Option<&Value>) -> Result<&indexmap::IndexMap<smol_str::SmolStr, Value>, String> {
2095 match v {
2096 Some(Value::Record { fields: r, .. }) => Ok(r),
2097 Some(other) => Err(format!("expected Record, got {other:?}")),
2098 None => Err("missing Record argument".into()),
2099 }
2100}
2101
2102fn http_decode_err_pure(msg: String) -> Value {
2103 let inner = Value::Variant {
2104 name: "DecodeError".into(),
2105 args: vec![Value::Str(msg.into())],
2106 };
2107 err_v(inner)
2108}
2109
2110fn http_set_header(
2115 mut req: indexmap::IndexMap<smol_str::SmolStr, Value>,
2116 name: &str,
2117 value: &str,
2118) -> indexmap::IndexMap<smol_str::SmolStr, Value> {
2119 use lex_bytecode::MapKey;
2120 let mut headers = match req.shift_remove("headers") {
2121 Some(Value::Map(m)) => m,
2122 _ => std::collections::BTreeMap::new(),
2123 };
2124 let key = MapKey::Str(name.to_lowercase());
2125 let lowered = name.to_lowercase();
2128 headers.retain(|k, _| match k {
2129 MapKey::Str(s) => s.to_lowercase() != lowered,
2130 _ => true,
2131 });
2132 headers.insert(key, Value::Str(value.into()));
2133 req.insert("headers".into(), Value::Map(headers));
2134 req
2135}
2136
2137fn http_append_query(
2143 mut req: indexmap::IndexMap<smol_str::SmolStr, Value>,
2144 params: &std::collections::BTreeMap<lex_bytecode::MapKey, Value>,
2145) -> indexmap::IndexMap<smol_str::SmolStr, Value> {
2146 use lex_bytecode::MapKey;
2147 let url = match req.get("url") {
2148 Some(Value::Str(s)) => s.clone(),
2149 _ => return req,
2150 };
2151 let mut pieces = Vec::new();
2152 for (k, v) in params {
2153 let kk = match k { MapKey::Str(s) => s.to_string(), _ => continue };
2154 let vv = match v { Value::Str(s) => s.to_string(), _ => continue };
2155 pieces.push(format!("{}={}", url_encode(&kk), url_encode(&vv)));
2156 }
2157 if pieces.is_empty() { return req; }
2158 let sep = if url.contains('?') { '&' } else { '?' };
2159 let new_url = format!("{url}{sep}{}", pieces.join("&"));
2160 req.insert("url".into(), Value::Str(new_url.into()));
2161 req
2162}
2163
2164fn url_encode(s: &str) -> String {
2169 let mut out = String::with_capacity(s.len());
2170 for b in s.bytes() {
2171 match b {
2172 b'A'..=b'Z' | b'a'..=b'z' | b'0'..=b'9' | b'-' | b'_' | b'.' | b'~' => {
2173 out.push(b as char);
2174 }
2175 _ => out.push_str(&format!("%{:02X}", b)),
2176 }
2177 }
2178 out
2179}
2180
2181fn value_to_json(v: &Value) -> serde_json::Value { v.to_json() }
2182
2183fn unwrap_toml_datetime_markers(v: &mut serde_json::Value) {
2191 use serde_json::Value as J;
2192 match v {
2193 J::Object(map) => {
2194 if map.len() == 1 {
2198 if let Some(J::String(s)) = map.get("$__toml_private_datetime") {
2199 let s = s.clone();
2200 *v = J::String(s);
2201 return;
2202 }
2203 }
2204 for (_, child) in map.iter_mut() {
2205 unwrap_toml_datetime_markers(child);
2206 }
2207 }
2208 J::Array(items) => {
2209 for item in items.iter_mut() {
2210 unwrap_toml_datetime_markers(item);
2211 }
2212 }
2213 _ => {}
2214 }
2215}
2216
2217fn json_to_value(v: &serde_json::Value) -> Value { Value::from_json(v) }
2218
2219fn required_field_names(arg: Option<&Value>) -> Result<Vec<String>, String> {
2224 let list = expect_list(arg)?;
2225 let mut out = Vec::with_capacity(list.len());
2226 for v in list {
2227 match v {
2228 Value::Str(s) => out.push(s.to_string()),
2229 other => return Err(format!(
2230 "parse_strict: required-fields list must contain Str, got {other:?}"
2231 )),
2232 }
2233 }
2234 Ok(out)
2235}
2236
2237fn check_required_fields(
2258 value: &serde_json::Value,
2259 required: &[String],
2260) -> Result<(), String> {
2261 if required.is_empty() {
2262 return Ok(());
2263 }
2264 if !matches!(value, serde_json::Value::Object(_)) {
2265 return Err(format!(
2266 "parse_strict: expected top-level object with fields {:?}, got {value}",
2267 required
2268 ));
2269 }
2270 let mut missing: Vec<String> = Vec::new();
2271 for path in required {
2272 if !path_exists(value, path) {
2273 missing.push(path.clone());
2274 }
2275 }
2276 if missing.is_empty() {
2277 Ok(())
2278 } else {
2279 Err(format!("missing required field(s): {}", missing.join(", ")))
2280 }
2281}
2282
2283fn path_exists(value: &serde_json::Value, path: &str) -> bool {
2288 let mut cursor = value;
2289 let segments = split_dotted_path(path);
2290 for seg in &segments {
2291 match cursor {
2292 serde_json::Value::Object(o) => match o.get(seg.as_str()) {
2293 Some(next) => cursor = next,
2294 None => return false,
2295 },
2296 _ => return false,
2297 }
2298 }
2299 true
2300}
2301
2302fn split_dotted_path(path: &str) -> Vec<String> {
2306 let mut out: Vec<String> = Vec::new();
2307 let mut cur = String::new();
2308 let mut iter = path.chars().peekable();
2309 while let Some(c) = iter.next() {
2310 if c == '\\' {
2311 if let Some(&'.') = iter.peek() {
2313 cur.push('.');
2314 iter.next();
2315 continue;
2316 }
2317 cur.push(c);
2318 } else if c == '.' {
2319 out.push(std::mem::take(&mut cur));
2320 } else {
2321 cur.push(c);
2322 }
2323 }
2324 out.push(cur);
2325 out
2326}
2327
2328fn extract_type_schema(v: Option<&Value>) -> Vec<(String, String)> {
2332 match v {
2333 Some(Value::List(pairs)) => pairs.iter().filter_map(|p| {
2334 if let Value::Tuple(items) = p {
2335 if items.len() == 2 {
2336 if let (Value::Str(name), Value::Str(tag)) = (&items[0], &items[1]) {
2337 return Some((name.to_string(), tag.to_string()));
2338 }
2339 }
2340 }
2341 None
2342 }).collect(),
2343 _ => vec![],
2344 }
2345}
2346
2347fn validate_field_types(
2353 json: &serde_json::Value,
2354 schema: &[(String, String)],
2355) -> Result<(), String> {
2356 if schema.is_empty() {
2357 return Ok(());
2358 }
2359 let obj = match json.as_object() {
2360 Some(o) => o,
2361 None => return Ok(()), };
2363 for (field, tag) in schema {
2364 if let Some(val) = obj.get(field) {
2365 if let Err(e) = check_json_type(val, tag) {
2366 return Err(format!("field `{field}`: {e}"));
2367 }
2368 }
2369 }
2370 Ok(())
2371}
2372
2373fn apply_option_wrapping(v: Value, json: &serde_json::Value, schema: &[(String, String)]) -> Value {
2378 if schema.is_empty() {
2379 return v;
2380 }
2381 let fields = match v {
2382 Value::Record { fields, .. } => *fields,
2383 other => return other,
2384 };
2385 let json_obj = match json.as_object() {
2386 Some(o) => o,
2387 None => return Value::record_interned(fields),
2388 };
2389 let mut new_fields = fields;
2390 for (field_name, tag) in schema {
2391 if tag.starts_with("Option[") && tag.ends_with(']') {
2392 let json_val = json_obj.get(field_name.as_str());
2393 let wrapped = match json_val {
2394 None | Some(serde_json::Value::Null) => none(),
2395 Some(_) => {
2396 let inner = new_fields
2397 .get(field_name.as_str())
2398 .cloned()
2399 .unwrap_or(Value::Unit);
2400 some(inner)
2401 }
2402 };
2403 new_fields.insert(smol_str::SmolStr::from(field_name.as_str()), wrapped);
2404 }
2405 }
2406 Value::record_interned(new_fields)
2407}
2408
2409fn check_json_type(val: &serde_json::Value, tag: &str) -> Result<(), String> {
2411 use serde_json::Value as J;
2412 match (tag, val) {
2413 ("Int", J::Number(n)) if n.is_i64() || n.is_u64() => Ok(()),
2414 ("Int", other) => Err(format!("expected Int, got {}", json_type_name(other))),
2415 ("Float", J::Number(_)) => Ok(()),
2416 ("Float", other) => Err(format!("expected Float, got {}", json_type_name(other))),
2417 ("Bool", J::Bool(_)) => Ok(()),
2418 ("Bool", other) => Err(format!("expected Bool, got {}", json_type_name(other))),
2419 ("Str", J::String(_)) => Ok(()),
2420 ("Str", other) => Err(format!("expected Str, got {}", json_type_name(other))),
2421 (tag, J::Null) if tag.starts_with("Option[") => Ok(()),
2423 (tag, val) if tag.starts_with("Option[") && tag.ends_with(']') => {
2424 let inner = &tag[7..tag.len() - 1]; check_json_type(val, inner)
2426 }
2427 (tag, J::Array(items)) if tag.starts_with("List[") && tag.ends_with(']') => {
2429 let inner = &tag[5..tag.len() - 1]; for (i, item) in items.iter().enumerate() {
2431 if let Err(e) = check_json_type(item, inner) {
2432 return Err(format!("[{i}]: {e}"));
2433 }
2434 }
2435 Ok(())
2436 }
2437 ("Record", _) => Ok(()), ("Any", _) => Ok(()), _ => Ok(()), }
2441}
2442
2443fn json_type_name(v: &serde_json::Value) -> &'static str {
2444 match v {
2445 serde_json::Value::Null => "null",
2446 serde_json::Value::Bool(_) => "Bool",
2447 serde_json::Value::Number(_) => "Number",
2448 serde_json::Value::String(_) => "Str",
2449 serde_json::Value::Array(_) => "Array",
2450 serde_json::Value::Object(_) => "Object",
2451 }
2452}
2453
2454fn parse_dotenv(src: &str) -> Result<indexmap::IndexMap<String, String>, String> {
2465 let mut out = indexmap::IndexMap::new();
2466 for (idx, raw) in src.lines().enumerate() {
2467 let line = raw.trim();
2468 if line.is_empty() || line.starts_with('#') {
2469 continue;
2470 }
2471 let after_export = line.strip_prefix("export ").unwrap_or(line);
2474 let (k, v) = match after_export.split_once('=') {
2475 Some(kv) => kv,
2476 None => return Err(format!("dotenv.parse line {}: missing `=`", idx + 1)),
2477 };
2478 let key = k.trim();
2479 if key.is_empty() {
2480 return Err(format!("dotenv.parse line {}: empty key", idx + 1));
2481 }
2482 let v_trim = v.trim();
2483 let value = if let Some(q) = v_trim.strip_prefix('"').and_then(|s| s.strip_suffix('"')) {
2484 q.to_string()
2485 } else if let Some(q) = v_trim.strip_prefix('\'').and_then(|s| s.strip_suffix('\'')) {
2486 q.to_string()
2487 } else {
2488 v_trim.to_string()
2489 };
2490 out.insert(key.to_string(), value);
2491 }
2492 Ok(out)
2493}
2494
2495fn instant_from_chrono<Tz: chrono::TimeZone>(dt: chrono::DateTime<Tz>) -> i64 {
2501 dt.timestamp_nanos_opt().unwrap_or(i64::MAX)
2502}
2503
2504fn chrono_from_instant(n: i64) -> chrono::DateTime<chrono::Utc> {
2505 let secs = n.div_euclid(1_000_000_000);
2506 let nanos = n.rem_euclid(1_000_000_000) as u32;
2507 use chrono::TimeZone;
2508 chrono::Utc
2509 .timestamp_opt(secs, nanos)
2510 .single()
2511 .unwrap_or_else(chrono::Utc::now)
2512}
2513
2514fn format_iso(n: i64) -> String {
2515 chrono_from_instant(n).to_rfc3339()
2516}
2517
2518enum TzArg {
2521 Utc,
2522 Local,
2523 Offset(i32),
2525 Iana(String),
2527}
2528
2529fn parse_tz_arg(v: Option<&Value>) -> Result<TzArg, String> {
2530 match v {
2531 Some(Value::Variant { name, args }) => match (name.as_str(), args.as_slice()) {
2532 ("Utc", []) => Ok(TzArg::Utc),
2533 ("Local", []) => Ok(TzArg::Local),
2534 ("Offset", [Value::Int(m)]) => {
2535 let m = i32::try_from(*m).map_err(|_| {
2536 format!("Tz::Offset: minutes out of range: {m}")
2537 })?;
2538 Ok(TzArg::Offset(m))
2539 }
2540 ("Iana", [Value::Str(s)]) => Ok(TzArg::Iana(s.to_string())),
2541 (other, _) => Err(format!(
2542 "expected Tz variant (Utc | Local | Offset(Int) | Iana(Str)), got `{other}` with {} arg(s)",
2543 args.len()
2544 )),
2545 },
2546 Some(other) => Err(format!("expected Tz variant, got {other:?}")),
2547 None => Err("missing Tz argument".into()),
2548 }
2549}
2550
2551fn resolve_tz_to_components(n: i64, tz: &TzArg) -> Result<Value, String> {
2552 use chrono::{TimeZone, Datelike, Timelike, Offset};
2553 let utc_dt = chrono_from_instant(n);
2554 let (y, m, d, hh, mm, ss, ns, off_min) = match tz {
2555 TzArg::Utc => {
2556 let d = utc_dt;
2557 (d.year(), d.month() as i32, d.day() as i32,
2558 d.hour() as i32, d.minute() as i32, d.second() as i32,
2559 d.nanosecond() as i32, 0)
2560 }
2561 TzArg::Local => {
2562 let d = utc_dt.with_timezone(&chrono::Local);
2563 let off = d.offset().fix().local_minus_utc() / 60;
2564 (d.year(), d.month() as i32, d.day() as i32,
2565 d.hour() as i32, d.minute() as i32, d.second() as i32,
2566 d.nanosecond() as i32, off)
2567 }
2568 TzArg::Offset(off_min) => {
2569 let off_secs = off_min.saturating_mul(60);
2570 let fixed = chrono::FixedOffset::east_opt(off_secs)
2571 .ok_or("to_components: offset out of range")?;
2572 let d = utc_dt.with_timezone(&fixed);
2573 (d.year(), d.month() as i32, d.day() as i32,
2574 d.hour() as i32, d.minute() as i32, d.second() as i32,
2575 d.nanosecond() as i32, *off_min)
2576 }
2577 TzArg::Iana(name) => {
2578 let tz: chrono_tz::Tz = name.parse()
2579 .map_err(|e| format!("to_components: unknown timezone `{name}`: {e}"))?;
2580 let d = utc_dt.with_timezone(&tz);
2581 let off = d.offset().fix().local_minus_utc() / 60;
2582 (d.year(), d.month() as i32, d.day() as i32,
2583 d.hour() as i32, d.minute() as i32, d.second() as i32,
2584 d.nanosecond() as i32, off)
2585 }
2586 };
2587 let mut rec = indexmap::IndexMap::new();
2588 rec.insert("year".into(), Value::Int(y as i64));
2589 rec.insert("month".into(), Value::Int(m as i64));
2590 rec.insert("day".into(), Value::Int(d as i64));
2591 rec.insert("hour".into(), Value::Int(hh as i64));
2592 rec.insert("minute".into(), Value::Int(mm as i64));
2593 rec.insert("second".into(), Value::Int(ss as i64));
2594 rec.insert("nano".into(), Value::Int(ns as i64));
2595 rec.insert("tz_offset_minutes".into(), Value::Int(off_min as i64));
2596 let _ = chrono::Utc.timestamp_opt(0, 0); Ok(Value::record_dynamic(rec))
2598}
2599
2600
2601fn instant_from_components(rec: &indexmap::IndexMap<smol_str::SmolStr, Value>) -> Result<i64, String> {
2602 use chrono::TimeZone;
2603 fn get_int(rec: &indexmap::IndexMap<smol_str::SmolStr, Value>, k: &str) -> Result<i64, String> {
2604 match rec.get(k) {
2605 Some(Value::Int(n)) => Ok(*n),
2606 other => Err(format!("from_components: missing or non-int field `{k}`: {other:?}")),
2607 }
2608 }
2609 let y = get_int(rec, "year")? as i32;
2610 let m = get_int(rec, "month")? as u32;
2611 let d = get_int(rec, "day")? as u32;
2612 let hh = get_int(rec, "hour")? as u32;
2613 let mm = get_int(rec, "minute")? as u32;
2614 let ss = get_int(rec, "second")? as u32;
2615 let ns = get_int(rec, "nano")? as u32;
2616 let off_min = get_int(rec, "tz_offset_minutes")? as i32;
2617 let off = chrono::FixedOffset::east_opt(off_min * 60)
2618 .ok_or("from_components: offset out of range")?;
2619 let dt = off
2620 .with_ymd_and_hms(y, m, d, hh, mm, ss)
2621 .single()
2622 .ok_or("from_components: invalid or ambiguous date/time")?;
2623 let dt = dt + chrono::Duration::nanoseconds(ns as i64);
2624 Ok(instant_from_chrono(dt))
2625}
2626
2627type Aead4<'a> = (&'a Vec<u8>, &'a Vec<u8>, &'a Vec<u8>, &'a Vec<u8>);
2644
2645type Aead5<'a> = (&'a Vec<u8>, &'a Vec<u8>, &'a Vec<u8>, &'a Vec<u8>, &'a Vec<u8>);
2648
2649fn unpack4_bytes<'a>(
2650 args: &'a [Value],
2651 op: &str,
2652) -> Result<Aead4<'a>, String> {
2653 let pick = |i: usize, name: &str| -> Result<&'a Vec<u8>, String> {
2654 match args.get(i) {
2655 Some(Value::Bytes(b)) => Ok(b),
2656 Some(other) => Err(format!("{op}: {name} must be Bytes, got {other:?}")),
2657 None => Err(format!("{op}: missing {name} argument")),
2658 }
2659 };
2660 Ok((pick(0, "key")?, pick(1, "nonce")?, pick(2, "aad")?, pick(3, "plaintext")?))
2661}
2662
2663fn unpack5_bytes<'a>(
2664 args: &'a [Value],
2665 op: &str,
2666) -> Result<Aead5<'a>, String> {
2667 let pick = |i: usize, name: &str| -> Result<&'a Vec<u8>, String> {
2668 match args.get(i) {
2669 Some(Value::Bytes(b)) => Ok(b),
2670 Some(other) => Err(format!("{op}: {name} must be Bytes, got {other:?}")),
2671 None => Err(format!("{op}: missing {name} argument")),
2672 }
2673 };
2674 Ok((
2675 pick(0, "key")?,
2676 pick(1, "nonce")?,
2677 pick(2, "aad")?,
2678 pick(3, "ciphertext")?,
2679 pick(4, "tag")?,
2680 ))
2681}
2682
2683fn aead_result(ciphertext: Vec<u8>, tag: Vec<u8>) -> Value {
2684 let mut rec = indexmap::IndexMap::new();
2685 rec.insert("ciphertext".into(), Value::Bytes(ciphertext));
2686 rec.insert("tag".into(), Value::Bytes(tag));
2687 Value::record_dynamic(rec)
2688}
2689
2690fn aead_err(msg: impl Into<String>) -> Value {
2691 let s: String = msg.into();
2692 err_v(Value::Str(s.into()))
2693}
2694
2695fn aes_gcm_seal_impl(args: &[Value]) -> Value {
2696 use aes_gcm::aead::{Aead, KeyInit, Payload};
2697 use aes_gcm::{Aes128Gcm, Aes256Gcm, Nonce};
2698 let (key, nonce, aad, plaintext) = match unpack4_bytes(args, "aes_gcm_seal") {
2699 Ok(t) => t,
2700 Err(e) => return aead_err(e),
2701 };
2702 if nonce.len() != 12 {
2703 return aead_err(format!(
2704 "aes_gcm_seal: nonce must be exactly 12 bytes, got {}", nonce.len()
2705 ));
2706 }
2707 let n = Nonce::from_slice(nonce);
2708 let payload = Payload { msg: plaintext, aad };
2709 let combined = match key.len() {
2712 16 => {
2713 let cipher = Aes128Gcm::new_from_slice(key)
2714 .map_err(|e| e.to_string());
2715 match cipher {
2716 Ok(c) => c.encrypt(n, payload).map_err(|e| format!("aes_gcm_seal: {e}")),
2717 Err(e) => Err(format!("aes_gcm_seal: {e}")),
2718 }
2719 }
2720 32 => {
2721 let cipher = Aes256Gcm::new_from_slice(key)
2722 .map_err(|e| e.to_string());
2723 match cipher {
2724 Ok(c) => c.encrypt(n, payload).map_err(|e| format!("aes_gcm_seal: {e}")),
2725 Err(e) => Err(format!("aes_gcm_seal: {e}")),
2726 }
2727 }
2728 other => return aead_err(format!(
2731 "aes_gcm_seal: key must be 16 or 32 bytes, got {other}"
2732 )),
2733 };
2734 match combined {
2735 Ok(mut buf) => {
2736 let tag_start = buf.len() - 16;
2738 let tag = buf.split_off(tag_start);
2739 ok_v(aead_result(buf, tag))
2740 }
2741 Err(e) => aead_err(e),
2742 }
2743}
2744
2745fn aes_gcm_open_impl(args: &[Value]) -> Value {
2746 use aes_gcm::aead::{Aead, KeyInit, Payload};
2747 use aes_gcm::{Aes128Gcm, Aes256Gcm, Nonce};
2748 let (key, nonce, aad, ciphertext, tag) = match unpack5_bytes(args, "aes_gcm_open") {
2749 Ok(t) => t,
2750 Err(e) => return err_v(Value::Str(e.into())),
2751 };
2752 if nonce.len() != 12 {
2753 return err_v(Value::Str(format!(
2754 "aes_gcm_open: nonce must be exactly 12 bytes, got {}", nonce.len()
2755 ).into()));
2756 }
2757 if tag.len() != 16 {
2758 return err_v(Value::Str(format!(
2759 "aes_gcm_open: tag must be exactly 16 bytes, got {}", tag.len()
2760 ).into()));
2761 }
2762 let mut combined = Vec::with_capacity(ciphertext.len() + tag.len());
2764 combined.extend_from_slice(ciphertext);
2765 combined.extend_from_slice(tag);
2766 let n = Nonce::from_slice(nonce);
2767 let payload = Payload { msg: &combined, aad };
2768 let plaintext = match key.len() {
2769 16 => Aes128Gcm::new_from_slice(key)
2770 .map_err(|e| format!("aes_gcm_open: {e}"))
2771 .and_then(|c| c.decrypt(n, payload).map_err(|e| format!("aes_gcm_open: {e}"))),
2772 32 => Aes256Gcm::new_from_slice(key)
2773 .map_err(|e| format!("aes_gcm_open: {e}"))
2774 .and_then(|c| c.decrypt(n, payload).map_err(|e| format!("aes_gcm_open: {e}"))),
2775 other => return err_v(Value::Str(format!(
2776 "aes_gcm_open: key must be 16 or 32 bytes, got {other}"
2777 ).into())),
2778 };
2779 match plaintext {
2780 Ok(p) => ok_v(Value::Bytes(p)),
2781 Err(e) => err_v(Value::Str(e.into())),
2782 }
2783}
2784
2785fn chacha20_seal_impl(args: &[Value]) -> Value {
2786 use chacha20poly1305::aead::{Aead, KeyInit, Payload};
2787 use chacha20poly1305::{ChaCha20Poly1305, Nonce};
2788 let (key, nonce, aad, plaintext) = match unpack4_bytes(args, "chacha20_poly1305_seal") {
2789 Ok(t) => t,
2790 Err(e) => return aead_err(e),
2791 };
2792 if key.len() != 32 {
2793 return aead_err(format!(
2794 "chacha20_poly1305_seal: key must be exactly 32 bytes, got {}", key.len()
2795 ));
2796 }
2797 if nonce.len() != 12 {
2798 return aead_err(format!(
2799 "chacha20_poly1305_seal: nonce must be exactly 12 bytes, got {}", nonce.len()
2800 ));
2801 }
2802 let cipher = ChaCha20Poly1305::new_from_slice(key)
2803 .map_err(|e| format!("chacha20_poly1305_seal: {e}"));
2804 let n = Nonce::from_slice(nonce);
2805 let payload = Payload { msg: plaintext, aad };
2806 let combined = match cipher {
2807 Ok(c) => c.encrypt(n, payload).map_err(|e| format!("chacha20_poly1305_seal: {e}")),
2808 Err(e) => Err(e),
2809 };
2810 match combined {
2811 Ok(mut buf) => {
2812 let tag_start = buf.len() - 16;
2813 let tag = buf.split_off(tag_start);
2814 ok_v(aead_result(buf, tag))
2815 }
2816 Err(e) => aead_err(e),
2817 }
2818}
2819
2820fn chacha20_open_impl(args: &[Value]) -> Value {
2821 use chacha20poly1305::aead::{Aead, KeyInit, Payload};
2822 use chacha20poly1305::{ChaCha20Poly1305, Nonce};
2823 let (key, nonce, aad, ciphertext, tag) = match unpack5_bytes(args, "chacha20_poly1305_open") {
2824 Ok(t) => t,
2825 Err(e) => return err_v(Value::Str(e.into())),
2826 };
2827 if key.len() != 32 {
2828 return err_v(Value::Str(format!(
2829 "chacha20_poly1305_open: key must be exactly 32 bytes, got {}", key.len()
2830 ).into()));
2831 }
2832 if nonce.len() != 12 {
2833 return err_v(Value::Str(format!(
2834 "chacha20_poly1305_open: nonce must be exactly 12 bytes, got {}", nonce.len()
2835 ).into()));
2836 }
2837 if tag.len() != 16 {
2838 return err_v(Value::Str(format!(
2839 "chacha20_poly1305_open: tag must be exactly 16 bytes, got {}", tag.len()
2840 ).into()));
2841 }
2842 let mut combined = Vec::with_capacity(ciphertext.len() + tag.len());
2843 combined.extend_from_slice(ciphertext);
2844 combined.extend_from_slice(tag);
2845 let cipher = ChaCha20Poly1305::new_from_slice(key)
2846 .map_err(|e| format!("chacha20_poly1305_open: {e}"));
2847 let n = Nonce::from_slice(nonce);
2848 let payload = Payload { msg: &combined, aad };
2849 match cipher.and_then(|c| c.decrypt(n, payload).map_err(|e| format!("chacha20_poly1305_open: {e}"))) {
2850 Ok(p) => ok_v(Value::Bytes(p)),
2851 Err(e) => err_v(Value::Str(e.into())),
2852 }
2853}
2854
2855type Kdf4<'a> = (&'a Vec<u8>, &'a Vec<u8>, i64, i64);
2865
2866type Hkdf4<'a> = (&'a Vec<u8>, &'a Vec<u8>, &'a Vec<u8>, i64);
2869
2870type Argon5<'a> = (&'a Vec<u8>, &'a Vec<u8>, i64, i64, i64);
2873
2874fn pick_bytes<'a>(args: &'a [Value], i: usize, op: &str, name: &str)
2875 -> Result<&'a Vec<u8>, String>
2876{
2877 match args.get(i) {
2878 Some(Value::Bytes(b)) => Ok(b),
2879 Some(other) => Err(format!("{op}: {name} must be Bytes, got {other:?}")),
2880 None => Err(format!("{op}: missing {name} argument")),
2881 }
2882}
2883
2884fn pick_int(args: &[Value], i: usize, op: &str, name: &str) -> Result<i64, String> {
2885 match args.get(i) {
2886 Some(Value::Int(n)) => Ok(*n),
2887 Some(other) => Err(format!("{op}: {name} must be Int, got {other:?}")),
2888 None => Err(format!("{op}: missing {name} argument")),
2889 }
2890}
2891
2892fn unpack_kdf4<'a>(args: &'a [Value], op: &str) -> Result<Kdf4<'a>, String> {
2893 Ok((
2894 pick_bytes(args, 0, op, "password")?,
2895 pick_bytes(args, 1, op, "salt")?,
2896 pick_int(args, 2, op, "iterations")?,
2897 pick_int(args, 3, op, "len")?,
2898 ))
2899}
2900
2901fn unpack_hkdf4<'a>(args: &'a [Value], op: &str) -> Result<Hkdf4<'a>, String> {
2902 Ok((
2903 pick_bytes(args, 0, op, "ikm")?,
2904 pick_bytes(args, 1, op, "salt")?,
2905 pick_bytes(args, 2, op, "info")?,
2906 pick_int(args, 3, op, "len")?,
2907 ))
2908}
2909
2910fn unpack_argon5<'a>(args: &'a [Value], op: &str) -> Result<Argon5<'a>, String> {
2911 Ok((
2912 pick_bytes(args, 0, op, "password")?,
2913 pick_bytes(args, 1, op, "salt")?,
2914 pick_int(args, 2, op, "t_cost")?,
2915 pick_int(args, 3, op, "m_cost")?,
2916 pick_int(args, 4, op, "len")?,
2917 ))
2918}
2919
2920const KDF_MAX_LEN: usize = 1024 * 1024;
2925
2926fn check_len(op: &str, len: i64) -> Result<usize, String> {
2927 if len <= 0 {
2928 return Err(format!("{op}: len must be > 0, got {len}"));
2929 }
2930 if (len as u64) > KDF_MAX_LEN as u64 {
2931 return Err(format!(
2932 "{op}: len must be <= {KDF_MAX_LEN}, got {len}"
2933 ));
2934 }
2935 Ok(len as usize)
2936}
2937
2938fn pbkdf2_sha256_impl(args: &[Value]) -> Value {
2939 use hmac::Hmac;
2940 use sha2::Sha256;
2941 let op = "pbkdf2_sha256";
2942 let (password, salt, iterations, len) = match unpack_kdf4(args, op) {
2943 Ok(t) => t,
2944 Err(e) => return err_v(Value::Str(e.into())),
2945 };
2946 if iterations <= 0 {
2947 return err_v(Value::Str(format!(
2948 "{op}: iterations must be > 0, got {iterations}"
2949 ).into()));
2950 }
2951 let out_len = match check_len(op, len) {
2952 Ok(n) => n,
2953 Err(e) => return err_v(Value::Str(e.into())),
2954 };
2955 let rounds = match u32::try_from(iterations) {
2956 Ok(r) => r,
2957 Err(_) => {
2958 return err_v(Value::Str(format!(
2959 "{op}: iterations must fit in u32, got {iterations}"
2960 ).into()))
2961 }
2962 };
2963 let mut out = vec![0u8; out_len];
2964 if let Err(e) = pbkdf2::pbkdf2::<Hmac<Sha256>>(password, salt, rounds, &mut out) {
2965 return err_v(Value::Str(format!("{op}: {e}").into()));
2966 }
2967 ok_v(Value::Bytes(out))
2968}
2969
2970fn hkdf_sha256_impl(args: &[Value]) -> Value {
2971 use hkdf::Hkdf;
2972 use sha2::Sha256;
2973 let op = "hkdf_sha256";
2974 let (ikm, salt, info, len) = match unpack_hkdf4(args, op) {
2975 Ok(t) => t,
2976 Err(e) => return err_v(Value::Str(e.into())),
2977 };
2978 let out_len = match check_len(op, len) {
2979 Ok(n) => n,
2980 Err(e) => return err_v(Value::Str(e.into())),
2981 };
2982 let salt_opt: Option<&[u8]> = if salt.is_empty() { None } else { Some(salt) };
2985 let hk = Hkdf::<Sha256>::new(salt_opt, ikm);
2986 let mut out = vec![0u8; out_len];
2987 match hk.expand(info, &mut out) {
2988 Ok(()) => ok_v(Value::Bytes(out)),
2989 Err(e) => err_v(Value::Str(format!("{op}: {e}").into())),
2990 }
2991}
2992
2993fn argon2id_impl(args: &[Value]) -> Value {
2994 use argon2::{Algorithm, Argon2, Params, Version};
2995 let op = "argon2id";
2996 let (password, salt, t_cost, m_cost, len) = match unpack_argon5(args, op) {
2997 Ok(t) => t,
2998 Err(e) => return err_v(Value::Str(e.into())),
2999 };
3000 let out_len = match check_len(op, len) {
3001 Ok(n) => n,
3002 Err(e) => return err_v(Value::Str(e.into())),
3003 };
3004 let t = match u32::try_from(t_cost) {
3005 Ok(n) if n >= 1 => n,
3006 _ => return err_v(Value::Str(format!(
3007 "{op}: t_cost must be a u32 >= 1, got {t_cost}"
3008 ).into())),
3009 };
3010 let m = match u32::try_from(m_cost) {
3011 Ok(n) if n >= Params::MIN_M_COST => n,
3012 _ => return err_v(Value::Str(format!(
3013 "{op}: m_cost must be a u32 >= {}, got {m_cost}",
3014 Params::MIN_M_COST
3015 ).into())),
3016 };
3017 let params = match Params::new(m, t, 1, Some(out_len)) {
3022 Ok(p) => p,
3023 Err(e) => return err_v(Value::Str(format!("{op}: {e}").into())),
3024 };
3025 let hasher = Argon2::new(Algorithm::Argon2id, Version::V0x13, params);
3026 let mut out = vec![0u8; out_len];
3027 if let Err(e) = hasher.hash_password_into(password, salt, &mut out) {
3028 return err_v(Value::Str(format!("{op}: {e}").into()));
3029 }
3030 ok_v(Value::Bytes(out))
3031}