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