1use std::cell::RefCell;
9use std::rc::Rc;
10
11use serde::{Deserialize, Serialize};
12
13use crate::interpreter::{ListRepr, RuntimeValue};
14use logicaffeine_base::{Decimal, LanesVal, Word16, Word32, Word64, WordVal};
15
16fn byte_seq(v: &RuntimeValue) -> Result<Vec<u8>, String> {
19 match v {
20 RuntimeValue::List(l) => {
21 let l = l.borrow();
22 let mut out = Vec::with_capacity(l.len());
23 for i in 0..l.len() {
24 match l.get(i) {
25 Some(RuntimeValue::Int(n)) => out.push((n & 0xff) as u8),
26 _ => return Err(format!("expected a Seq of Int (bytes); element {} is not an Int", i + 1)),
27 }
28 }
29 Ok(out)
30 }
31 _ => Err(format!("expected a Seq of Int (bytes), got {}", v.type_name())),
32 }
33}
34
35fn bytes_to_seq(bytes: &[u8]) -> RuntimeValue {
37 RuntimeValue::List(Rc::new(RefCell::new(ListRepr::Ints(
38 bytes.iter().map(|&b| b as i64).collect(),
39 ))))
40}
41
42#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
43pub enum BuiltinId {
44 Length,
45 Format,
46 ParseInt,
47 ParseFloat,
48 Chr,
49 Abs,
50 Sqrt,
51 Min,
52 Max,
53 Floor,
54 Ceil,
55 Round,
56 Pow,
57 Decimal,
61 Complex,
64 Modular,
66 Quantity,
69 Money,
73 SetRate,
76 ToCurrency,
79 SetRates,
83 Uuid,
85 UuidNil,
87 UuidMax,
88 UuidVersion,
90 UuidDns,
92 UuidUrl,
93 UuidOid,
94 UuidX500,
95 TextBytes,
99 TextFromBytes,
102 WireBytes,
106 ReadWireProgram,
111 WriteWireResidual,
115 UuidBytes,
116 UuidFromBytes,
117 Convert,
120 ParseTimestamp,
123 FormatTimestamp,
125 YearOf,
128 MonthOf,
129 DayOf,
130 WeekdayOf,
131 HourOf,
132 MinuteOf,
133 SecondOf,
134 WeekOf,
136 QuarterOf,
138 DateOf,
140 TimeOf,
142 LocalInstant,
145 SecondsBetween,
147 MonthsBetween,
149 YearsBetween,
150 AddSeconds,
152 InZone,
155 Copy,
156 CountOnes,
157 RunAccepted,
158 Word32,
160 Word64,
161 Rotl,
163 Rotr,
164 Wand,
168 Wor,
169 Wnot,
170 Lanes4Word32Make,
174 Lanes4Of,
176 SeqOfLanes4W32,
177 Sha1Rnds4,
178 Sha1Msg1,
179 Sha1Msg2,
180 Sha1Nexte,
181 Lanes16Word8Make,
184 SeqOfLanes16W8,
185 Splat16Word8,
186 Shuffle16,
187 ShrBytes16,
188 InterleaveLo16,
189 InterleaveHi16,
190 ByteAdd16,
191 Maddubs16,
192 Packus16,
193 Lanes8Word32,
195 SeqOfLanes8,
197 Splat8Word32,
199 IntOfWord32,
201 IntOfWord64,
203 Word64Shl,
205 Word64Shr,
207 Word64And,
209 Word32Shr,
212 Word16Make,
214 IntOfWord16,
216 Lanes4Word64,
218 SeqOfLanes4,
220 Mul32x32To64,
222 HsumLanes4,
224 Splat4Word64,
226 AndNot4,
228 Lanes16Word16,
230 SeqOfLanes16,
232 Splat16Word16,
234 Mulhi16,
236 Montmul32,
239 NttBcastLo,
242 NttBcastHi,
244 NttBlend,
247 MapOf,
252 SetOf,
255 RepeatSeq,
259}
260
261pub fn builtin_from_name(name: &str) -> Option<BuiltinId> {
263 Some(match name {
264 "length" => BuiltinId::Length,
265 "format" => BuiltinId::Format,
266 "mapOf" => BuiltinId::MapOf,
267 "setOf" => BuiltinId::SetOf,
268 "repeatSeq" => BuiltinId::RepeatSeq,
269 "parseInt" => BuiltinId::ParseInt,
270 "parseFloat" => BuiltinId::ParseFloat,
271 "chr" => BuiltinId::Chr,
272 "abs" => BuiltinId::Abs,
273 "sqrt" => BuiltinId::Sqrt,
274 "min" => BuiltinId::Min,
275 "max" => BuiltinId::Max,
276 "floor" => BuiltinId::Floor,
277 "ceil" => BuiltinId::Ceil,
278 "round" => BuiltinId::Round,
279 "pow" => BuiltinId::Pow,
280 "decimal" => BuiltinId::Decimal,
281 "complex" => BuiltinId::Complex,
282 "modular" => BuiltinId::Modular,
283 "quantity" => BuiltinId::Quantity,
284 "money" => BuiltinId::Money,
285 "set_rate" => BuiltinId::SetRate,
286 "set_rates" => BuiltinId::SetRates,
287 "to_currency" => BuiltinId::ToCurrency,
288 "uuid" => BuiltinId::Uuid,
289 "uuid_nil" => BuiltinId::UuidNil,
290 "uuid_max" => BuiltinId::UuidMax,
291 "uuid_version" => BuiltinId::UuidVersion,
292 "uuid_dns" => BuiltinId::UuidDns,
293 "uuid_url" => BuiltinId::UuidUrl,
294 "uuid_oid" => BuiltinId::UuidOid,
295 "uuid_x500" => BuiltinId::UuidX500,
296 "text_bytes" => BuiltinId::TextBytes,
297 "text_from_bytes" => BuiltinId::TextFromBytes,
298 "wireBytes" => BuiltinId::WireBytes,
299 "readWireProgram" => BuiltinId::ReadWireProgram,
300 "writeWireResidual" => BuiltinId::WriteWireResidual,
301 "uuid_bytes" => BuiltinId::UuidBytes,
302 "uuid_from_bytes" => BuiltinId::UuidFromBytes,
303 "convert" => BuiltinId::Convert,
304 "parse_timestamp" => BuiltinId::ParseTimestamp,
305 "format_timestamp" => BuiltinId::FormatTimestamp,
306 "year_of" => BuiltinId::YearOf,
307 "month_of" => BuiltinId::MonthOf,
308 "day_of" => BuiltinId::DayOf,
309 "weekday_of" => BuiltinId::WeekdayOf,
310 "hour_of" => BuiltinId::HourOf,
311 "minute_of" => BuiltinId::MinuteOf,
312 "second_of" => BuiltinId::SecondOf,
313 "week_of" => BuiltinId::WeekOf,
314 "quarter_of" => BuiltinId::QuarterOf,
315 "date_of" => BuiltinId::DateOf,
316 "time_of" => BuiltinId::TimeOf,
317 "local_instant" => BuiltinId::LocalInstant,
318 "seconds_between" => BuiltinId::SecondsBetween,
319 "months_between" => BuiltinId::MonthsBetween,
320 "years_between" => BuiltinId::YearsBetween,
321 "add_seconds" => BuiltinId::AddSeconds,
322 "in_zone" => BuiltinId::InZone,
323 "copy" => BuiltinId::Copy,
324 "count_ones" => BuiltinId::CountOnes,
325 "run_accepted" => BuiltinId::RunAccepted,
326 "word32" => BuiltinId::Word32,
327 "word64" => BuiltinId::Word64,
328 "lanes8Word32" => BuiltinId::Lanes8Word32,
329 "seqOfLanes8" => BuiltinId::SeqOfLanes8,
330 "splat8Word32" => BuiltinId::Splat8Word32,
331 "intOfWord32" => BuiltinId::IntOfWord32,
332 "intOfWord64" => BuiltinId::IntOfWord64,
333 "word64Shl" => BuiltinId::Word64Shl,
334 "word64Shr" => BuiltinId::Word64Shr,
335 "word32Shr" => BuiltinId::Word32Shr,
336 "word64And" => BuiltinId::Word64And,
337 "word16" => BuiltinId::Word16Make,
338 "intOfWord16" => BuiltinId::IntOfWord16,
339 "lanes4Word64" => BuiltinId::Lanes4Word64,
340 "seqOfLanes4" => BuiltinId::SeqOfLanes4,
341 "mul32x32to64" => BuiltinId::Mul32x32To64,
342 "hsumLanes4" => BuiltinId::HsumLanes4,
343 "splat4Word64" => BuiltinId::Splat4Word64,
344 "andNot4" => BuiltinId::AndNot4,
345 "lanes16Word16" => BuiltinId::Lanes16Word16,
346 "seqOfLanes16" => BuiltinId::SeqOfLanes16,
347 "splat16Word16" => BuiltinId::Splat16Word16,
348 "mulhi16" => BuiltinId::Mulhi16,
349 "montmul32" => BuiltinId::Montmul32,
350 "nttBcastLo" => BuiltinId::NttBcastLo,
351 "nttBcastHi" => BuiltinId::NttBcastHi,
352 "nttBlend" => BuiltinId::NttBlend,
353 "rotl" => BuiltinId::Rotl,
354 "word_and" => BuiltinId::Wand,
355 "word_or" => BuiltinId::Wor,
356 "word_not" => BuiltinId::Wnot,
357 "lanes4Word32" => BuiltinId::Lanes4Word32Make,
358 "lanes4Of" => BuiltinId::Lanes4Of,
359 "seqOfLanes4W32" => BuiltinId::SeqOfLanes4W32,
360 "sha1rnds4" => BuiltinId::Sha1Rnds4,
361 "sha1msg1" => BuiltinId::Sha1Msg1,
362 "sha1msg2" => BuiltinId::Sha1Msg2,
363 "sha1nexte" => BuiltinId::Sha1Nexte,
364 "lanes16Word8" => BuiltinId::Lanes16Word8Make,
365 "seqOfLanes16W8" => BuiltinId::SeqOfLanes16W8,
366 "splat16Word8" => BuiltinId::Splat16Word8,
367 "shuffle16" => BuiltinId::Shuffle16,
368 "shrBytes16" => BuiltinId::ShrBytes16,
369 "interleaveLo16" => BuiltinId::InterleaveLo16,
370 "interleaveHi16" => BuiltinId::InterleaveHi16,
371 "byteAdd16" => BuiltinId::ByteAdd16,
372 "maddubs16" => BuiltinId::Maddubs16,
373 "packus16" => BuiltinId::Packus16,
374 "rotr" => BuiltinId::Rotr,
375 _ => return None,
376 })
377}
378
379pub fn check_arity(id: BuiltinId, n: usize) -> Result<(), String> {
382 let expected: usize = match id {
383 BuiltinId::Format => return Ok(()),
384 BuiltinId::MapOf => {
385 if n == 0 || n % 2 != 0 {
386 return Err(format!(
387 "mapOf takes flat key/value pairs (an even, nonzero number of arguments), got {}",
388 n
389 ));
390 }
391 return Ok(());
392 }
393 BuiltinId::SetOf => {
394 if n == 0 {
395 return Err("setOf takes at least one element (an empty set is `{} of T`)".to_string());
396 }
397 return Ok(());
398 }
399 BuiltinId::Min | BuiltinId::Max | BuiltinId::Pow => 2,
400 BuiltinId::RepeatSeq => 2,
401 BuiltinId::Complex => 2,
402 BuiltinId::Modular => 2,
403 BuiltinId::Quantity | BuiltinId::Convert | BuiltinId::Money => 2,
404 BuiltinId::SetRate | BuiltinId::ToCurrency => 2,
405 BuiltinId::SetRates => 1,
406 BuiltinId::UuidNil
407 | BuiltinId::UuidMax
408 | BuiltinId::UuidDns
409 | BuiltinId::UuidUrl
410 | BuiltinId::UuidOid
411 | BuiltinId::ReadWireProgram
412 | BuiltinId::UuidX500 => 0,
413 BuiltinId::Uuid | BuiltinId::UuidVersion => 1,
414 BuiltinId::TextBytes | BuiltinId::TextFromBytes | BuiltinId::WireBytes | BuiltinId::WriteWireResidual | BuiltinId::UuidBytes | BuiltinId::UuidFromBytes => 1,
415 BuiltinId::SecondsBetween | BuiltinId::AddSeconds | BuiltinId::InZone => 2,
416 BuiltinId::MonthsBetween | BuiltinId::YearsBetween => 2,
417 BuiltinId::LocalInstant => 2,
418 BuiltinId::Rotl | BuiltinId::Rotr => 2,
419 BuiltinId::Wand | BuiltinId::Wor => 2,
420 BuiltinId::Wnot => 1,
421 BuiltinId::Lanes4Word32Make | BuiltinId::SeqOfLanes4W32 => 1,
422 BuiltinId::AndNot4 => 2,
423 BuiltinId::Lanes4Of => 4,
424 BuiltinId::Sha1Rnds4 => 3,
425 BuiltinId::Sha1Msg1 | BuiltinId::Sha1Msg2 | BuiltinId::Sha1Nexte => 2,
426 BuiltinId::Lanes16Word8Make | BuiltinId::SeqOfLanes16W8 | BuiltinId::Splat16Word8 => 1,
427 BuiltinId::Shuffle16 | BuiltinId::ShrBytes16 => 2,
428 BuiltinId::InterleaveLo16 | BuiltinId::InterleaveHi16 => 2,
429 BuiltinId::ByteAdd16 | BuiltinId::Maddubs16 | BuiltinId::Packus16 => 2,
430 BuiltinId::Mul32x32To64 => 2,
431 BuiltinId::Mulhi16 => 2,
432 BuiltinId::Montmul32 => 4,
433 BuiltinId::Word64Shl | BuiltinId::Word64Shr | BuiltinId::Word64And => 2,
434 BuiltinId::Word32Shr => 2,
435 BuiltinId::NttBcastLo | BuiltinId::NttBcastHi => 2,
436 BuiltinId::NttBlend => 3,
437 BuiltinId::RunAccepted => 4,
440 _ => 1,
441 };
442 if n != expected {
443 let name = match id {
444 BuiltinId::Length => "length",
445 BuiltinId::Format => unreachable!(),
446 BuiltinId::MapOf | BuiltinId::SetOf => unreachable!(),
448 BuiltinId::RepeatSeq => "repeatSeq",
449 BuiltinId::ParseInt => "parseInt",
450 BuiltinId::ParseFloat => "parseFloat",
451 BuiltinId::Chr => "chr",
452 BuiltinId::Abs => "abs",
453 BuiltinId::Sqrt => "sqrt",
454 BuiltinId::Min => "min",
455 BuiltinId::Max => "max",
456 BuiltinId::Floor => "floor",
457 BuiltinId::Ceil => "ceil",
458 BuiltinId::Round => "round",
459 BuiltinId::Pow => "pow",
460 BuiltinId::Decimal => "decimal",
461 BuiltinId::Complex => "complex",
462 BuiltinId::Modular => "modular",
463 BuiltinId::Quantity => "quantity",
464 BuiltinId::Money => "money",
465 BuiltinId::SetRate => "set_rate",
466 BuiltinId::SetRates => "set_rates",
467 BuiltinId::ToCurrency => "to_currency",
468 BuiltinId::Uuid => "uuid",
469 BuiltinId::UuidNil => "uuid_nil",
470 BuiltinId::UuidMax => "uuid_max",
471 BuiltinId::UuidVersion => "uuid_version",
472 BuiltinId::UuidDns => "uuid_dns",
473 BuiltinId::UuidUrl => "uuid_url",
474 BuiltinId::UuidOid => "uuid_oid",
475 BuiltinId::UuidX500 => "uuid_x500",
476 BuiltinId::TextBytes => "text_bytes",
477 BuiltinId::TextFromBytes => "text_from_bytes",
478 BuiltinId::WireBytes => "wireBytes",
479 BuiltinId::ReadWireProgram => "readWireProgram",
480 BuiltinId::WriteWireResidual => "writeWireResidual",
481 BuiltinId::UuidBytes => "uuid_bytes",
482 BuiltinId::UuidFromBytes => "uuid_from_bytes",
483 BuiltinId::Convert => "convert",
484 BuiltinId::ParseTimestamp => "parse_timestamp",
485 BuiltinId::FormatTimestamp => "format_timestamp",
486 BuiltinId::YearOf => "year_of",
487 BuiltinId::MonthOf => "month_of",
488 BuiltinId::DayOf => "day_of",
489 BuiltinId::WeekdayOf => "weekday_of",
490 BuiltinId::HourOf => "hour_of",
491 BuiltinId::MinuteOf => "minute_of",
492 BuiltinId::SecondOf => "second_of",
493 BuiltinId::WeekOf => "week_of",
494 BuiltinId::QuarterOf => "quarter_of",
495 BuiltinId::DateOf => "date_of",
496 BuiltinId::TimeOf => "time_of",
497 BuiltinId::LocalInstant => "local_instant",
498 BuiltinId::SecondsBetween => "seconds_between",
499 BuiltinId::MonthsBetween => "months_between",
500 BuiltinId::YearsBetween => "years_between",
501 BuiltinId::AddSeconds => "add_seconds",
502 BuiltinId::InZone => "in_zone",
503 BuiltinId::Copy => "copy",
504 BuiltinId::CountOnes => "count_ones",
505 BuiltinId::RunAccepted => "run_accepted",
506 BuiltinId::Word32 => "word32",
507 BuiltinId::Word64 => "word64",
508 BuiltinId::Lanes8Word32 => "lanes8Word32",
509 BuiltinId::SeqOfLanes8 => "seqOfLanes8",
510 BuiltinId::Splat8Word32 => "splat8Word32",
511 BuiltinId::IntOfWord32 => "intOfWord32",
512 BuiltinId::IntOfWord64 => "intOfWord64",
513 BuiltinId::Word64Shl => "word64Shl",
514 BuiltinId::Word64Shr => "word64Shr",
515 BuiltinId::Word32Shr => "word32Shr",
516 BuiltinId::Word64And => "word64And",
517 BuiltinId::Word16Make => "word16",
518 BuiltinId::IntOfWord16 => "intOfWord16",
519 BuiltinId::Lanes4Word64 => "lanes4Word64",
520 BuiltinId::SeqOfLanes4 => "seqOfLanes4",
521 BuiltinId::Mul32x32To64 => "mul32x32to64",
522 BuiltinId::HsumLanes4 => "hsumLanes4",
523 BuiltinId::Splat4Word64 => "splat4Word64",
524 BuiltinId::AndNot4 => "andNot4",
525 BuiltinId::Lanes16Word16 => "lanes16Word16",
526 BuiltinId::SeqOfLanes16 => "seqOfLanes16",
527 BuiltinId::Splat16Word16 => "splat16Word16",
528 BuiltinId::Mulhi16 => "mulhi16",
529 BuiltinId::Montmul32 => "montmul32",
530 BuiltinId::NttBcastLo => "nttBcastLo",
531 BuiltinId::NttBcastHi => "nttBcastHi",
532 BuiltinId::NttBlend => "nttBlend",
533 BuiltinId::Rotl => "rotl",
534 BuiltinId::Wand => "word_and",
535 BuiltinId::Wor => "word_or",
536 BuiltinId::Wnot => "word_not",
537 BuiltinId::Lanes4Word32Make => "lanes4Word32",
538 BuiltinId::Lanes4Of => "lanes4Of",
539 BuiltinId::SeqOfLanes4W32 => "seqOfLanes4W32",
540 BuiltinId::Sha1Rnds4 => "sha1rnds4",
541 BuiltinId::Sha1Msg1 => "sha1msg1",
542 BuiltinId::Sha1Msg2 => "sha1msg2",
543 BuiltinId::Sha1Nexte => "sha1nexte",
544 BuiltinId::Lanes16Word8Make => "lanes16Word8",
545 BuiltinId::SeqOfLanes16W8 => "seqOfLanes16W8",
546 BuiltinId::Splat16Word8 => "splat16Word8",
547 BuiltinId::Shuffle16 => "shuffle16",
548 BuiltinId::ShrBytes16 => "shrBytes16",
549 BuiltinId::InterleaveLo16 => "interleaveLo16",
550 BuiltinId::InterleaveHi16 => "interleaveHi16",
551 BuiltinId::ByteAdd16 => "byteAdd16",
552 BuiltinId::Maddubs16 => "maddubs16",
553 BuiltinId::Packus16 => "packus16",
554 BuiltinId::Rotr => "rotr",
555 };
556 return Err(format!(
557 "{}() takes exactly {} argument{}",
558 name,
559 expected,
560 if expected == 1 { "" } else { "s" }
561 ));
562 }
563 Ok(())
564}
565
566fn ntt_stride(v: RuntimeValue, name: &str) -> Result<usize, String> {
571 match v {
572 RuntimeValue::Int(n @ (8 | 4 | 2 | 1)) => Ok(n as usize),
574 RuntimeValue::Int(n) => Err(format!("{name} stride must be 8, 4, 2, or 1, got {n}")),
575 other => Err(format!("{name} stride must be an Int, got {}", other.type_name())),
576 }
577}
578
579pub fn call_builtin(id: BuiltinId, args: Vec<RuntimeValue>) -> Result<RuntimeValue, String> {
580 let mut args = args;
581 match id {
582 BuiltinId::MapOf => {
583 let mut m = crate::interpreter::MapStorage::default();
586 let mut it = args.into_iter();
587 while let (Some(k), Some(v)) = (it.next(), it.next()) {
588 crate::semantics::collections::assert_hashable_key(&k)?;
589 m.insert(k, v);
590 }
591 Ok(RuntimeValue::Map(Rc::new(RefCell::new(m))))
592 }
593 BuiltinId::SetOf => {
594 let set = RuntimeValue::Set(Rc::new(RefCell::new(Vec::new())));
597 for v in args {
598 crate::semantics::collections::set_add(&set, v)?;
599 }
600 Ok(set)
601 }
602 BuiltinId::RepeatSeq => {
603 let count = args.remove(1);
606 let element = args.remove(0);
607 let n = match count {
608 RuntimeValue::Int(n) => n.max(0) as usize,
609 other => return Err(format!("repeatSeq count must be an Int, got {}", other.type_name())),
610 };
611 let slots: Vec<RuntimeValue> = (0..n).map(|_| element.deep_clone()).collect();
612 Ok(RuntimeValue::List(Rc::new(RefCell::new(ListRepr::from_values(slots)))))
613 }
614 BuiltinId::Length => {
615 let val = args.remove(0);
616 match &val {
617 RuntimeValue::List(items) => Ok(RuntimeValue::Int(items.borrow().len() as i64)),
618 RuntimeValue::Text(s) => Ok(RuntimeValue::Int(s.len() as i64)),
619 RuntimeValue::Map(map) => Ok(RuntimeValue::Int(map.borrow().len() as i64)),
620 _ => Err(format!("Cannot get length of {}", val.type_name())),
621 }
622 }
623 BuiltinId::Format => {
624 if args.is_empty() {
625 return Ok(RuntimeValue::Text(Rc::new(String::new())));
626 }
627 let val = args.remove(0);
628 Ok(RuntimeValue::Text(Rc::new(val.to_display_string())))
629 }
630 BuiltinId::ParseInt => {
631 let val = args.remove(0);
632 if let RuntimeValue::Text(s) = &val {
633 Ok(RuntimeValue::Int(
634 s.trim()
635 .parse::<i64>()
636 .map_err(|_| format!("Cannot parse '{}' as Int", s))?,
637 ))
638 } else {
639 Err("parseInt requires a Text argument".to_string())
640 }
641 }
642 BuiltinId::ParseFloat => {
643 let val = args.remove(0);
644 if let RuntimeValue::Text(s) = &val {
645 Ok(RuntimeValue::Float(
646 s.trim()
647 .parse::<f64>()
648 .map_err(|_| format!("Cannot parse '{}' as Float", s))?,
649 ))
650 } else {
651 Err("parseFloat requires a Text argument".to_string())
652 }
653 }
654 BuiltinId::Chr => {
655 let val = args.remove(0);
656 if let RuntimeValue::Int(code) = val {
657 match char::from_u32(code as u32) {
658 Some(c) => Ok(RuntimeValue::Text(Rc::new(c.to_string()))),
659 None => Err(format!("Invalid character code: {}", code)),
660 }
661 } else {
662 Err("chr() requires an Int argument".to_string())
663 }
664 }
665 BuiltinId::Abs => {
666 let val = args.remove(0);
667 match val {
668 RuntimeValue::Int(n) => Ok(match n.checked_abs() {
671 Some(a) => RuntimeValue::Int(a),
672 None => RuntimeValue::from_bigint(logicaffeine_base::BigInt::from_i64(n).abs()),
673 }),
674 RuntimeValue::BigInt(b) => Ok(RuntimeValue::from_bigint(b.abs())),
675 RuntimeValue::Rational(r) => Ok(RuntimeValue::from_rational(r.abs())),
677 RuntimeValue::Decimal(d) => Ok(RuntimeValue::Decimal(Rc::new(d.abs()))),
679 RuntimeValue::Complex(c) => Ok(RuntimeValue::Float(c.abs_f64())),
681 RuntimeValue::Float(f) => Ok(RuntimeValue::Float(f.abs())),
682 _ => Err(format!("abs() requires a number, got {}", val.type_name())),
683 }
684 }
685 BuiltinId::Sqrt => {
686 let val = args.remove(0);
687 match val {
688 RuntimeValue::Float(f) => Ok(RuntimeValue::Float(f.sqrt())),
689 RuntimeValue::Int(n) => Ok(RuntimeValue::Float((n as f64).sqrt())),
690 RuntimeValue::BigInt(b) => Ok(RuntimeValue::Float(b.to_f64().sqrt())),
691 _ => Err(format!("sqrt() requires a number, got {}", val.type_name())),
692 }
693 }
694 BuiltinId::Min => {
695 let b = args.remove(1);
696 let a = args.remove(0);
697 match (&a, &b) {
698 (RuntimeValue::Int(x), RuntimeValue::Int(y)) => Ok(RuntimeValue::Int(*x.min(y))),
699 (RuntimeValue::Float(x), RuntimeValue::Float(y)) => {
700 Ok(RuntimeValue::Float(x.min(*y)))
701 }
702 (RuntimeValue::Int(x), RuntimeValue::Float(y)) => {
703 Ok(RuntimeValue::Float((*x as f64).min(*y)))
704 }
705 (RuntimeValue::Float(x), RuntimeValue::Int(y)) => {
706 Ok(RuntimeValue::Float(x.min(*y as f64)))
707 }
708 (RuntimeValue::Decimal(x), RuntimeValue::Decimal(y)) => {
710 Ok(RuntimeValue::Decimal(if x <= y { x.clone() } else { y.clone() }))
711 }
712 _ => Err("min() requires numbers".to_string()),
713 }
714 }
715 BuiltinId::Max => {
716 let b = args.remove(1);
717 let a = args.remove(0);
718 match (&a, &b) {
719 (RuntimeValue::Int(x), RuntimeValue::Int(y)) => Ok(RuntimeValue::Int(*x.max(y))),
720 (RuntimeValue::Float(x), RuntimeValue::Float(y)) => {
721 Ok(RuntimeValue::Float(x.max(*y)))
722 }
723 (RuntimeValue::Int(x), RuntimeValue::Float(y)) => {
724 Ok(RuntimeValue::Float((*x as f64).max(*y)))
725 }
726 (RuntimeValue::Float(x), RuntimeValue::Int(y)) => {
727 Ok(RuntimeValue::Float(x.max(*y as f64)))
728 }
729 (RuntimeValue::Decimal(x), RuntimeValue::Decimal(y)) => {
731 Ok(RuntimeValue::Decimal(if x >= y { x.clone() } else { y.clone() }))
732 }
733 _ => Err("max() requires numbers".to_string()),
734 }
735 }
736 BuiltinId::Floor => {
737 let val = args.remove(0);
738 match &val {
741 RuntimeValue::Float(f) => Ok(RuntimeValue::Int(f.floor() as i64)),
742 RuntimeValue::Int(_) | RuntimeValue::BigInt(_) => Ok(val.clone()),
743 RuntimeValue::Rational(r) => Ok(RuntimeValue::from_bigint(r.floor())),
744 RuntimeValue::Decimal(d) => Ok(RuntimeValue::from_bigint(d.to_rational().floor())),
745 _ => Err(format!("floor() requires a number, got {}", val.type_name())),
746 }
747 }
748 BuiltinId::Ceil => {
749 let val = args.remove(0);
750 match &val {
751 RuntimeValue::Float(f) => Ok(RuntimeValue::Int(f.ceil() as i64)),
752 RuntimeValue::Int(_) | RuntimeValue::BigInt(_) => Ok(val.clone()),
753 RuntimeValue::Rational(r) => Ok(RuntimeValue::from_bigint(r.ceil())),
754 RuntimeValue::Decimal(d) => Ok(RuntimeValue::from_bigint(d.to_rational().ceil())),
755 _ => Err(format!("ceil() requires a number, got {}", val.type_name())),
756 }
757 }
758 BuiltinId::Round => {
759 let val = args.remove(0);
760 match &val {
761 RuntimeValue::Float(f) => Ok(RuntimeValue::Int(f.round() as i64)),
762 RuntimeValue::Int(_) | RuntimeValue::BigInt(_) => Ok(val.clone()),
763 RuntimeValue::Rational(r) => Ok(RuntimeValue::from_bigint(r.round())),
764 RuntimeValue::Decimal(d) => Ok(RuntimeValue::from_bigint(d.to_rational().round())),
765 _ => Err(format!("round() requires a number, got {}", val.type_name())),
766 }
767 }
768 BuiltinId::Pow => {
769 let exp = args.remove(1);
770 let base = args.remove(0);
771 match (&base, &exp) {
772 (RuntimeValue::Int(b), RuntimeValue::Int(e)) => {
776 if *e >= 0 {
777 Ok(match b.checked_pow(*e as u32) {
778 Some(p) => RuntimeValue::Int(p),
779 None => RuntimeValue::from_bigint(logicaffeine_base::BigInt::from_i64(*b).pow(*e as u32)),
780 })
781 } else {
782 Ok(RuntimeValue::Float((*b as f64).powi(*e as i32)))
783 }
784 }
785 (RuntimeValue::BigInt(b), RuntimeValue::Int(e)) => {
786 if *e >= 0 {
787 Ok(RuntimeValue::from_bigint(b.pow(*e as u32)))
788 } else {
789 Ok(RuntimeValue::Float(b.to_f64().powi(*e as i32)))
790 }
791 }
792 (RuntimeValue::Float(b), RuntimeValue::Int(e)) => {
793 Ok(RuntimeValue::Float(b.powi(*e as i32)))
794 }
795 (RuntimeValue::Float(b), RuntimeValue::Float(e)) => {
796 Ok(RuntimeValue::Float(b.powf(*e)))
797 }
798 (RuntimeValue::Int(b), RuntimeValue::Float(e)) => {
799 Ok(RuntimeValue::Float((*b as f64).powf(*e)))
800 }
801 (RuntimeValue::Modular(b), RuntimeValue::Int(e)) if *e >= 0 => {
803 Ok(RuntimeValue::Modular(Rc::new(b.pow(*e as u64))))
804 }
805 _ => Err("pow() requires numbers".to_string()),
806 }
807 }
808 BuiltinId::Decimal => {
809 let val = args.remove(0);
810 match &val {
811 RuntimeValue::Text(s) => Decimal::parse(s.trim())
813 .map(|d| RuntimeValue::Decimal(Rc::new(d)))
814 .ok_or_else(|| format!("Cannot parse '{}' as Decimal", s)),
815 RuntimeValue::Int(n) => Ok(RuntimeValue::Decimal(Rc::new(Decimal::from_i64(*n)))),
817 RuntimeValue::Decimal(_) => Ok(val.clone()),
819 _ => Err(format!("decimal() requires a Text or Int, got {}", val.type_name())),
820 }
821 }
822 BuiltinId::Complex => {
823 let im = args.remove(1);
824 let re = args.remove(0);
825 let to_rat = |v: &RuntimeValue| -> Option<logicaffeine_base::Rational> {
828 match v {
829 RuntimeValue::Int(n) => Some(logicaffeine_base::Rational::from_i64(*n)),
830 RuntimeValue::BigInt(b) => Some(logicaffeine_base::Rational::from_bigint((**b).clone())),
831 RuntimeValue::Rational(r) => Some((**r).clone()),
832 RuntimeValue::Decimal(d) => Some(d.to_rational()),
833 _ => None,
834 }
835 };
836 match (to_rat(&re), to_rat(&im)) {
837 (Some(re_r), Some(im_r)) => Ok(RuntimeValue::Complex(Rc::new(
838 logicaffeine_base::Complex::new(re_r, im_r),
839 ))),
840 _ => Err(format!(
841 "complex() requires two exact numbers, got {} and {}",
842 re.type_name(),
843 im.type_name()
844 )),
845 }
846 }
847 BuiltinId::Modular => {
848 let modulus = args.remove(1);
849 let value = args.remove(0);
850 let to_int = |v: &RuntimeValue| -> Option<logicaffeine_base::BigInt> {
851 match v {
852 RuntimeValue::Int(n) => Some(logicaffeine_base::BigInt::from_i64(*n)),
853 RuntimeValue::BigInt(b) => Some((**b).clone()),
854 _ => None,
855 }
856 };
857 match (to_int(&value), to_int(&modulus)) {
858 (Some(v), Some(n)) => match logicaffeine_base::Modular::new(v, n) {
859 Some(m) => Ok(RuntimeValue::Modular(Rc::new(m))),
860 None => Err("modular() requires a positive modulus".to_string()),
861 },
862 _ => Err(format!(
863 "modular() requires two integers, got {} and {}",
864 value.type_name(),
865 modulus.type_name()
866 )),
867 }
868 }
869 BuiltinId::Quantity => {
870 let unit_arg = args.remove(1);
871 let value = args.remove(0);
872 let magnitude = match &value {
875 RuntimeValue::Int(n) => logicaffeine_base::Rational::from_i64(*n),
876 RuntimeValue::BigInt(b) => logicaffeine_base::Rational::from_bigint((**b).clone()),
877 RuntimeValue::Rational(r) => (**r).clone(),
878 RuntimeValue::Decimal(d) => d.to_rational(),
879 _ => return Err(format!("quantity() requires an exact number, got {}", value.type_name())),
880 };
881 let unit = match &unit_arg {
882 RuntimeValue::Text(s) => logicaffeine_base::quantity::units::by_name(s)
883 .ok_or_else(|| format!("Unknown unit '{}'", s))?,
884 _ => return Err(format!("quantity() requires a unit name (Text), got {}", unit_arg.type_name())),
885 };
886 let q = logicaffeine_base::Quantity::of(magnitude, &unit);
887 Ok(RuntimeValue::Quantity(Rc::new(crate::interpreter::QuantityValue { q, unit })))
888 }
889 BuiltinId::Money => {
890 let code_arg = args.remove(1);
891 let value = args.remove(0);
892 let amount = match &value {
895 RuntimeValue::Int(n) => logicaffeine_base::Decimal::from_i64(*n),
896 RuntimeValue::Decimal(d) => (**d).clone(),
897 _ => return Err(format!("money() requires an exact base-10 amount (Int or Decimal), got {}", value.type_name())),
898 };
899 let currency = match &code_arg {
900 RuntimeValue::Text(s) => logicaffeine_base::money::currency::by_code(s)
901 .ok_or_else(|| format!("Unknown currency '{}'", s))?,
902 _ => return Err(format!("money() requires a currency code (Text), got {}", code_arg.type_name())),
903 };
904 Ok(RuntimeValue::Money(Rc::new(logicaffeine_base::Money::of(amount, currency))))
905 }
906 BuiltinId::SetRate => {
907 let rate_arg = args.remove(1);
908 let code_arg = args.remove(0);
909 let code = match &code_arg {
910 RuntimeValue::Text(s) => s.to_string(),
911 _ => return Err(format!("set_rate() requires a currency code (Text), got {}", code_arg.type_name())),
912 };
913 let rate = match &rate_arg {
914 RuntimeValue::Int(n) => logicaffeine_base::Rational::from_i64(*n),
915 RuntimeValue::Decimal(d) => d.to_rational(),
916 RuntimeValue::Rational(r) => (**r).clone(),
917 _ => return Err(format!("set_rate() requires an exact rate (Int/Decimal), got {}", rate_arg.type_name())),
918 };
919 logicaffeine_base::money::set_ambient_rate(&code, rate);
920 Ok(RuntimeValue::Nothing)
921 }
922 BuiltinId::SetRates => {
923 let table = args.remove(0);
924 let map = match &table {
925 RuntimeValue::Map(m) => m,
926 _ => {
927 return Err(format!(
928 "set_rates() requires a Map of currency code to rate, got {}",
929 table.type_name()
930 ))
931 }
932 };
933 for (key, value) in map.borrow().iter() {
934 let code = match key {
935 RuntimeValue::Text(s) => s.to_string(),
936 _ => return Err(format!("set_rates() keys must be currency codes (Text), got {}", key.type_name())),
937 };
938 let rate = match value {
939 RuntimeValue::Int(n) => logicaffeine_base::Rational::from_i64(*n),
940 RuntimeValue::Decimal(d) => d.to_rational(),
941 RuntimeValue::Rational(r) => (**r).clone(),
942 _ => return Err(format!("set_rates() values must be exact rates (Int/Decimal), got {}", value.type_name())),
943 };
944 logicaffeine_base::money::set_ambient_rate(&code, rate);
945 }
946 Ok(RuntimeValue::Nothing)
947 }
948 BuiltinId::Uuid => {
949 let arg = args.remove(0);
950 match &arg {
951 RuntimeValue::Text(s) => logicaffeine_base::Uuid::parse(s)
952 .map(|u| RuntimeValue::Uuid(Rc::new(u)))
953 .ok_or_else(|| format!("invalid UUID '{}'", s)),
954 _ => Err(format!("uuid() requires text, got {}", arg.type_name())),
955 }
956 }
957 BuiltinId::UuidNil => Ok(RuntimeValue::Uuid(Rc::new(logicaffeine_base::Uuid::NIL))),
958 BuiltinId::UuidMax => Ok(RuntimeValue::Uuid(Rc::new(logicaffeine_base::Uuid::MAX))),
959 BuiltinId::UuidDns => Ok(RuntimeValue::Uuid(Rc::new(logicaffeine_base::Uuid::NAMESPACE_DNS))),
960 BuiltinId::UuidUrl => Ok(RuntimeValue::Uuid(Rc::new(logicaffeine_base::Uuid::NAMESPACE_URL))),
961 BuiltinId::UuidOid => Ok(RuntimeValue::Uuid(Rc::new(logicaffeine_base::Uuid::NAMESPACE_OID))),
962 BuiltinId::UuidX500 => Ok(RuntimeValue::Uuid(Rc::new(logicaffeine_base::Uuid::NAMESPACE_X500))),
963 BuiltinId::UuidVersion => {
964 let arg = args.remove(0);
965 match &arg {
966 RuntimeValue::Uuid(u) => Ok(RuntimeValue::Int(u.version() as i64)),
967 _ => Err(format!("uuid_version() requires a Uuid, got {}", arg.type_name())),
968 }
969 }
970 BuiltinId::TextBytes => {
971 let arg = args.remove(0);
972 match &arg {
973 RuntimeValue::Text(s) => Ok(bytes_to_seq(s.as_bytes())),
974 _ => Err(format!("text_bytes() requires text, got {}", arg.type_name())),
975 }
976 }
977 BuiltinId::TextFromBytes => {
978 let bytes = byte_seq(&args.remove(0))?;
979 match String::from_utf8(bytes) {
980 Ok(s) => Ok(RuntimeValue::Text(Rc::new(s))),
981 Err(e) => Err(format!("text_from_bytes(): invalid UTF-8: {}", e)),
982 }
983 }
984 BuiltinId::WireBytes => {
985 let arg = args.remove(0);
986 match crate::concurrency::marshal::encode_value_raw(&arg) {
987 Ok(bytes) => Ok(bytes_to_seq(&bytes)),
988 Err(e) => Err(format!("wireBytes(): {}", e)),
989 }
990 }
991 BuiltinId::ReadWireProgram => {
992 use std::io::Read;
993 let mut len = [0u8; 4];
994 if std::io::stdin().read_exact(&mut len).is_err() {
995 std::process::exit(0);
996 }
997 let n = u32::from_le_bytes(len) as usize;
998 let mut buf = vec![0u8; n];
999 std::io::stdin()
1000 .read_exact(&mut buf)
1001 .map_err(|e| format!("readWireProgram(): frame read failed: {e}"))?;
1002 crate::concurrency::marshal::decode_value_raw(&buf)
1003 .ok_or_else(|| "readWireProgram(): malformed wire program".to_string())
1004 }
1005 BuiltinId::WriteWireResidual => {
1006 use std::io::Write;
1007 let arg = args.remove(0);
1008 let s = match &arg {
1009 RuntimeValue::Text(t) => (**t).clone(),
1010 _ => return Err("writeWireResidual() expects text".to_string()),
1011 };
1012 let b = s.as_bytes();
1013 let out = std::io::stdout();
1014 let mut h = out.lock();
1015 h.write_all(&(b.len() as u32).to_le_bytes())
1016 .and_then(|_| h.write_all(b))
1017 .and_then(|_| h.flush())
1018 .map_err(|e| format!("writeWireResidual(): {e}"))?;
1019 Ok(RuntimeValue::Int(b.len() as i64))
1020 }
1021 BuiltinId::UuidBytes => {
1022 let arg = args.remove(0);
1023 match &arg {
1024 RuntimeValue::Uuid(u) => Ok(bytes_to_seq(u.as_bytes())),
1025 _ => Err(format!("uuid_bytes() requires a Uuid, got {}", arg.type_name())),
1026 }
1027 }
1028 BuiltinId::UuidFromBytes => {
1029 let bytes = byte_seq(&args.remove(0))?;
1030 if bytes.len() < 16 {
1031 return Err(format!("uuid_from_bytes() needs 16 bytes, got {}", bytes.len()));
1032 }
1033 let mut b = [0u8; 16];
1034 b.copy_from_slice(&bytes[..16]);
1035 Ok(RuntimeValue::Uuid(Rc::new(logicaffeine_base::Uuid::from_bytes(b))))
1036 }
1037 BuiltinId::ToCurrency => {
1038 let code_arg = args.remove(1);
1039 let money = args.remove(0);
1040 match (&money, &code_arg) {
1041 (RuntimeValue::Money(m), RuntimeValue::Text(code)) => {
1042 let to = logicaffeine_base::money::currency::by_code(code)
1043 .ok_or_else(|| format!("Unknown currency '{}'", code))?;
1044 logicaffeine_base::money::ambient_convert(m, to)
1045 .map(|c| RuntimeValue::Money(Rc::new(c)))
1046 .ok_or_else(|| {
1047 if logicaffeine_base::money::has_ambient_rates() {
1048 format!("no exchange rate for {} or {}", m.currency.code, to.code)
1049 } else {
1050 "no exchange rates in scope (set a rate first)".to_string()
1051 }
1052 })
1053 }
1054 _ => Err(format!(
1055 "to_currency() requires money and a currency code, got {} and {}",
1056 money.type_name(),
1057 code_arg.type_name()
1058 )),
1059 }
1060 }
1061 BuiltinId::Convert => {
1062 let unit_arg = args.remove(1);
1063 let value = args.remove(0);
1064 let unit = match &unit_arg {
1065 RuntimeValue::Text(s) => logicaffeine_base::quantity::units::by_name(s)
1066 .ok_or_else(|| format!("Unknown unit '{}'", s))?,
1067 _ => return Err(format!("convert() requires a unit name (Text), got {}", unit_arg.type_name())),
1068 };
1069 match &value {
1070 RuntimeValue::Quantity(qv) => {
1073 if qv.q.dimension() != unit.dimension {
1074 return Err(format!(
1075 "cannot convert a {} quantity to '{}' — different dimension",
1076 qv.unit.symbol, unit.symbol
1077 ));
1078 }
1079 Ok(RuntimeValue::Quantity(Rc::new(crate::interpreter::QuantityValue {
1080 q: qv.q.clone(),
1081 unit,
1082 })))
1083 }
1084 _ => Err(format!("convert() requires a quantity, got {}", value.type_name())),
1085 }
1086 }
1087 BuiltinId::ParseTimestamp => {
1088 let val = args.remove(0);
1089 match &val {
1090 RuntimeValue::Text(s) => logicaffeine_base::temporal::parse_rfc3339(s.trim())
1091 .map(RuntimeValue::Moment)
1092 .ok_or_else(|| format!("Cannot parse '{}' as an RFC 3339 timestamp", s)),
1093 _ => Err(format!("parse_timestamp() requires a Text, got {}", val.type_name())),
1094 }
1095 }
1096 BuiltinId::FormatTimestamp => {
1097 let val = args.remove(0);
1098 match &val {
1099 RuntimeValue::Moment(nanos) => {
1100 Ok(RuntimeValue::Text(Rc::new(logicaffeine_base::temporal::format_rfc3339(*nanos))))
1101 }
1102 _ => Err(format!("format_timestamp() requires a Moment, got {}", val.type_name())),
1103 }
1104 }
1105 BuiltinId::YearOf
1106 | BuiltinId::MonthOf
1107 | BuiltinId::DayOf
1108 | BuiltinId::WeekdayOf
1109 | BuiltinId::WeekOf
1110 | BuiltinId::QuarterOf
1111 | BuiltinId::HourOf
1112 | BuiltinId::MinuteOf
1113 | BuiltinId::SecondOf => {
1114 let val = args.remove(0);
1115 match &val {
1116 RuntimeValue::Moment(nanos) => {
1117 use logicaffeine_base::temporal;
1118 let civil = temporal::civil_from_unix_nanos(*nanos);
1119 let component = match id {
1120 BuiltinId::YearOf => civil.year,
1121 BuiltinId::MonthOf => civil.month as i64,
1122 BuiltinId::DayOf => civil.day as i64,
1123 BuiltinId::HourOf => civil.hour as i64,
1124 BuiltinId::MinuteOf => civil.minute as i64,
1125 BuiltinId::SecondOf => civil.second as i64,
1126 BuiltinId::WeekdayOf => {
1127 temporal::weekday_from_days(nanos.div_euclid(temporal::NANOS_PER_DAY)) as i64
1128 }
1129 BuiltinId::WeekOf => {
1130 temporal::iso_week_from_days(nanos.div_euclid(temporal::NANOS_PER_DAY)).1 as i64
1131 }
1132 BuiltinId::QuarterOf => (civil.month as i64 - 1) / 3 + 1,
1133 _ => unreachable!(),
1134 };
1135 Ok(RuntimeValue::Int(component))
1136 }
1137 RuntimeValue::Date(days) => {
1140 use logicaffeine_base::temporal;
1141 let (y, m, d) = temporal::civil_from_days(*days as i64);
1142 let component = match id {
1143 BuiltinId::YearOf => y,
1144 BuiltinId::MonthOf => m as i64,
1145 BuiltinId::DayOf => d as i64,
1146 BuiltinId::WeekdayOf => temporal::weekday_from_days(*days as i64) as i64,
1147 BuiltinId::WeekOf => temporal::iso_week_from_days(*days as i64).1 as i64,
1148 BuiltinId::QuarterOf => (m as i64 - 1) / 3 + 1,
1149 BuiltinId::HourOf | BuiltinId::MinuteOf | BuiltinId::SecondOf => {
1150 return Err("a Date has no time-of-day — use a timestamp/Moment".to_string());
1151 }
1152 _ => unreachable!(),
1153 };
1154 Ok(RuntimeValue::Int(component))
1155 }
1156 _ => Err(format!("a date component extractor requires a Moment or Date, got {}", val.type_name())),
1157 }
1158 }
1159 BuiltinId::DateOf => {
1160 let val = args.remove(0);
1161 match &val {
1162 RuntimeValue::Moment(nanos) => Ok(RuntimeValue::Date(
1164 nanos.div_euclid(logicaffeine_base::temporal::NANOS_PER_DAY) as i32,
1165 )),
1166 RuntimeValue::Date(days) => Ok(RuntimeValue::Date(*days)),
1167 _ => Err(format!("date_of() requires a Moment or Date, got {}", val.type_name())),
1168 }
1169 }
1170 BuiltinId::TimeOf => {
1171 let val = args.remove(0);
1172 match &val {
1173 RuntimeValue::Moment(nanos) => Ok(RuntimeValue::Time(
1175 nanos.rem_euclid(logicaffeine_base::temporal::NANOS_PER_DAY),
1176 )),
1177 RuntimeValue::Date(_) => {
1178 Err("a Date has no time-of-day — use a timestamp/Moment".to_string())
1179 }
1180 _ => Err(format!("time_of() requires a Moment, got {}", val.type_name())),
1181 }
1182 }
1183 BuiltinId::SecondsBetween => {
1184 let b = args.remove(1);
1185 let a = args.remove(0);
1186 match (&a, &b) {
1187 (RuntimeValue::Moment(a), RuntimeValue::Moment(b)) => {
1188 Ok(RuntimeValue::Int((b - a) / 1_000_000_000))
1189 }
1190 _ => Err(format!(
1191 "seconds_between() requires two Moments, got {} and {}",
1192 a.type_name(),
1193 b.type_name()
1194 )),
1195 }
1196 }
1197 BuiltinId::MonthsBetween | BuiltinId::YearsBetween => {
1198 let b = args.remove(1);
1199 let a = args.remove(0);
1200 match (&a, &b) {
1201 (RuntimeValue::Moment(a), RuntimeValue::Moment(b)) => {
1202 let n = if matches!(id, BuiltinId::MonthsBetween) {
1203 logicaffeine_base::temporal::months_between(*a, *b)
1204 } else {
1205 logicaffeine_base::temporal::years_between(*a, *b)
1206 };
1207 Ok(RuntimeValue::Int(n))
1208 }
1209 _ => {
1210 let name = if matches!(id, BuiltinId::MonthsBetween) {
1211 "months_between"
1212 } else {
1213 "years_between"
1214 };
1215 Err(format!(
1216 "{name}() requires two Moments, got {} and {}",
1217 a.type_name(),
1218 b.type_name()
1219 ))
1220 }
1221 }
1222 }
1223 BuiltinId::AddSeconds => {
1224 let secs = args.remove(1);
1225 let moment = args.remove(0);
1226 match (&moment, &secs) {
1227 (RuntimeValue::Moment(nanos), RuntimeValue::Int(n)) => {
1228 Ok(RuntimeValue::Moment(nanos + n * 1_000_000_000))
1229 }
1230 _ => Err(format!(
1231 "add_seconds() requires a Moment and an Int, got {} and {}",
1232 moment.type_name(),
1233 secs.type_name()
1234 )),
1235 }
1236 }
1237 BuiltinId::InZone => {
1238 let zone = args.remove(1);
1239 let moment = args.remove(0);
1240 match (&moment, &zone) {
1241 (RuntimeValue::Moment(nanos), RuntimeValue::Text(z)) => {
1242 logicaffeine_base::temporal::format_zoned(*nanos, z)
1243 .map(|s| RuntimeValue::Text(Rc::new(s)))
1244 .ok_or_else(|| format!("Unknown time zone '{}'", z))
1245 }
1246 _ => Err(format!(
1247 "in_zone() requires a Moment and a zone name (Text), got {} and {}",
1248 moment.type_name(),
1249 zone.type_name()
1250 )),
1251 }
1252 }
1253 BuiltinId::LocalInstant => {
1254 let zone = args.remove(1);
1255 let moment = args.remove(0);
1256 match (&moment, &zone) {
1257 (RuntimeValue::Moment(nanos), RuntimeValue::Text(z)) => {
1258 logicaffeine_base::temporal::local_instant_nanos(*nanos, z)
1259 .map(RuntimeValue::Moment)
1260 .ok_or_else(|| format!("Unknown time zone '{}'", z))
1261 }
1262 _ => Err(format!(
1263 "local_instant() requires a Moment and a zone name (Text), got {} and {}",
1264 moment.type_name(),
1265 zone.type_name()
1266 )),
1267 }
1268 }
1269 BuiltinId::Copy => {
1270 let val = args.remove(0);
1271 Ok(val.deep_clone())
1272 }
1273 BuiltinId::CountOnes => {
1274 let val = args.remove(0);
1275 match val {
1276 RuntimeValue::Int(n) => Ok(RuntimeValue::Int((n as u64).count_ones() as i64)),
1279 _ => Err(format!(
1280 "count_ones() requires an Int, got {}",
1281 val.type_name()
1282 )),
1283 }
1284 }
1285 BuiltinId::Word32 => {
1286 let n = args.remove(0);
1287 match n {
1288 RuntimeValue::Int(v) => Ok(RuntimeValue::Word(WordVal::W32(Word32(v as u32)))),
1289 RuntimeValue::Word(w) => Ok(RuntimeValue::Word(WordVal::W32(Word32(w.to_u64() as u32)))),
1290 _ => Err(format!("word32() requires an Int, got {}", n.type_name())),
1291 }
1292 }
1293 BuiltinId::Word64 => {
1294 let n = args.remove(0);
1295 match n {
1296 RuntimeValue::Int(v) => Ok(RuntimeValue::Word(WordVal::W64(Word64(v as u64)))),
1297 RuntimeValue::Word(w) => Ok(RuntimeValue::Word(WordVal::W64(Word64(w.to_u64())))),
1298 _ => Err(format!("word64() requires an Int, got {}", n.type_name())),
1299 }
1300 }
1301 BuiltinId::Rotl | BuiltinId::Rotr => {
1302 let w = args.remove(0);
1303 let n = args.remove(0);
1304 let count = match n {
1305 RuntimeValue::Int(c) => c as u32,
1306 RuntimeValue::Word(c) => c.to_u64() as u32,
1307 _ => return Err(format!("rotate count must be an Int, got {}", n.type_name())),
1308 };
1309 match w {
1310 RuntimeValue::Word(word) => {
1311 let r = if matches!(id, BuiltinId::Rotl) { word.rotl(count) } else { word.rotr(count) };
1312 Ok(RuntimeValue::Word(r))
1313 }
1314 RuntimeValue::Lanes(lanes) if matches!(id, BuiltinId::Rotl) => {
1317 match lanes.rotl(count) {
1318 Some(v) => Ok(RuntimeValue::Lanes(Rc::new(v))),
1319 None => Err(format!("rotl is not defined for {}", lanes.type_name())),
1320 }
1321 }
1322 _ => Err(format!("rotate requires a Word, got {}", w.type_name())),
1323 }
1324 }
1325 BuiltinId::Wand | BuiltinId::Wor => {
1326 let b = args.remove(1);
1327 let a = args.remove(0);
1328 match (&a, &b) {
1329 (RuntimeValue::Word(x), RuntimeValue::Word(y)) => {
1330 let r = if id == BuiltinId::Wand { x.bitand(*y) } else { x.bitor(*y) };
1331 match r {
1332 Some(w) => Ok(RuntimeValue::Word(w)),
1333 None => Err(format!("word_and/or width mismatch: {} vs {}", a.type_name(), b.type_name())),
1334 }
1335 }
1336 (RuntimeValue::Lanes(x), RuntimeValue::Lanes(y)) => {
1338 let r = if id == BuiltinId::Wand { (**x).bitand(**y) } else { (**x).bitor(**y) };
1339 match r {
1340 Some(v) => Ok(RuntimeValue::Lanes(Rc::new(v))),
1341 None => Err(format!(
1342 "word_and/or lane-config mismatch: {} vs {}",
1343 a.type_name(),
1344 b.type_name()
1345 )),
1346 }
1347 }
1348 _ => Err(format!("word_and/or requires two Words, got {} and {}", a.type_name(), b.type_name())),
1349 }
1350 }
1351 BuiltinId::Wnot => {
1352 let a = args.remove(0);
1353 match &a {
1354 RuntimeValue::Word(x) => Ok(RuntimeValue::Word(x.not())),
1355 RuntimeValue::Lanes(x) => match (**x).lane_not() {
1356 Some(v) => Ok(RuntimeValue::Lanes(Rc::new(v))),
1357 None => Err(format!("word_not: lane config has no complement: {}", a.type_name())),
1358 },
1359 _ => Err(format!("word_not requires a Word, got {}", a.type_name())),
1360 }
1361 }
1362 BuiltinId::Lanes4Word32Make => {
1363 let s = args.remove(0);
1365 match s {
1366 RuntimeValue::List(items) => {
1367 let vals = items.borrow().to_values();
1368 let mut words = Vec::with_capacity(vals.len());
1369 for v in &vals {
1370 match v {
1371 RuntimeValue::Word(WordVal::W32(w)) => words.push(*w),
1372 RuntimeValue::Int(n) => words.push(Word32(*n as u32)),
1373 other => {
1374 return Err(format!(
1375 "lanes4Word32() needs a Seq of Word32, found {}",
1376 other.type_name()
1377 ))
1378 }
1379 }
1380 }
1381 Ok(RuntimeValue::Lanes(Rc::new(LanesVal::L4W32(
1382 logicaffeine_base::Lanes4Word32::from_words(&words),
1383 ))))
1384 }
1385 other => Err(format!("lanes4Word32() requires a Seq of Word32, got {}", other.type_name())),
1386 }
1387 }
1388 BuiltinId::Lanes4Of => {
1389 let mut w = [Word32(0); 4];
1392 for slot in w.iter_mut() {
1393 *slot = match args.remove(0) {
1394 RuntimeValue::Word(WordVal::W32(x)) => x,
1395 RuntimeValue::Int(n) => Word32(n as u32),
1396 other => {
1397 return Err(format!("lanes4Of() needs four Word32, found {}", other.type_name()))
1398 }
1399 };
1400 }
1401 Ok(RuntimeValue::Lanes(Rc::new(LanesVal::L4W32(
1402 logicaffeine_base::Lanes4Word32::from_words(&w),
1403 ))))
1404 }
1405 BuiltinId::SeqOfLanes4W32 => {
1406 let v = args.remove(0);
1407 match v {
1408 RuntimeValue::Lanes(lanes) => match *lanes {
1409 LanesVal::L4W32(lv) => {
1410 let vals: Vec<RuntimeValue> = lv
1411 .to_words()
1412 .iter()
1413 .map(|w| RuntimeValue::Word(WordVal::W32(*w)))
1414 .collect();
1415 Ok(RuntimeValue::List(Rc::new(std::cell::RefCell::new(
1416 crate::interpreter::ListRepr::from_values(vals),
1417 ))))
1418 }
1419 other => Err(format!("seqOfLanes4W32() requires a Lanes4Word32, got {}", other.type_name())),
1420 },
1421 other => Err(format!("seqOfLanes4W32() requires a Lanes4Word32, got {}", other.type_name())),
1422 }
1423 }
1424 BuiltinId::Sha1Rnds4 => {
1425 let func = args.remove(2);
1426 let msg = args.remove(1);
1427 let abcd = args.remove(0);
1428 let f = match &func {
1429 RuntimeValue::Int(n) => *n as u32,
1430 _ => return Err(format!("sha1rnds4() func must be an Int, got {}", func.type_name())),
1431 };
1432 match (&abcd, &msg) {
1433 (RuntimeValue::Lanes(a), RuntimeValue::Lanes(b)) => (**a)
1434 .sha1rnds4(**b, f)
1435 .map(|r| RuntimeValue::Lanes(Rc::new(r)))
1436 .ok_or_else(|| "sha1rnds4() requires two Lanes4Word32".to_string()),
1437 _ => Err(format!(
1438 "sha1rnds4() requires two Lanes4Word32, got {} and {}",
1439 abcd.type_name(),
1440 msg.type_name()
1441 )),
1442 }
1443 }
1444 BuiltinId::Sha1Msg1 | BuiltinId::Sha1Msg2 | BuiltinId::Sha1Nexte => {
1445 let b = args.remove(1);
1446 let a = args.remove(0);
1447 match (&a, &b) {
1448 (RuntimeValue::Lanes(x), RuntimeValue::Lanes(y)) => {
1449 let r = match id {
1450 BuiltinId::Sha1Msg1 => (**x).sha1msg1(**y),
1451 BuiltinId::Sha1Msg2 => (**x).sha1msg2(**y),
1452 _ => (**x).sha1nexte(**y),
1453 };
1454 r.map(|v| RuntimeValue::Lanes(Rc::new(v)))
1455 .ok_or_else(|| "sha1msg/nexte requires two Lanes4Word32".to_string())
1456 }
1457 _ => Err(format!(
1458 "sha1 message op requires two Lanes4Word32, got {} and {}",
1459 a.type_name(),
1460 b.type_name()
1461 )),
1462 }
1463 }
1464 BuiltinId::Lanes16Word8Make => {
1465 let bytes = byte_seq(&args.remove(0))?;
1468 Ok(RuntimeValue::Lanes(Rc::new(LanesVal::L16W8(
1469 logicaffeine_base::Lanes16Word8::from_bytes(&bytes),
1470 ))))
1471 }
1472 BuiltinId::SeqOfLanes16W8 => {
1473 let v = args.remove(0);
1474 match v {
1475 RuntimeValue::Lanes(lanes) => match *lanes {
1476 LanesVal::L16W8(lv) => Ok(bytes_to_seq(&lv.to_bytes())),
1477 other => Err(format!(
1478 "seqOfLanes16W8() requires a Lanes16Word8, got {}",
1479 other.type_name()
1480 )),
1481 },
1482 other => Err(format!(
1483 "seqOfLanes16W8() requires a Lanes16Word8, got {}",
1484 other.type_name()
1485 )),
1486 }
1487 }
1488 BuiltinId::Splat16Word8 => match args.remove(0) {
1489 RuntimeValue::Int(n) => Ok(RuntimeValue::Lanes(Rc::new(LanesVal::L16W8(
1490 logicaffeine_base::Lanes16Word8::splat(n as u8),
1491 )))),
1492 other => Err(format!("splat16Word8() requires an Int, got {}", other.type_name())),
1493 },
1494 BuiltinId::Shuffle16
1495 | BuiltinId::InterleaveLo16
1496 | BuiltinId::InterleaveHi16
1497 | BuiltinId::ByteAdd16
1498 | BuiltinId::Maddubs16
1499 | BuiltinId::Packus16 => {
1500 let b = args.remove(1);
1501 let a = args.remove(0);
1502 match (&a, &b) {
1503 (RuntimeValue::Lanes(x), RuntimeValue::Lanes(y)) => {
1504 let r = match id {
1505 BuiltinId::Shuffle16 => (**x).shuffle(**y),
1506 BuiltinId::InterleaveLo16 => (**x).interleave_lo(**y),
1507 BuiltinId::InterleaveHi16 => (**x).interleave_hi(**y),
1508 BuiltinId::ByteAdd16 => (**x).byte_add(**y),
1509 BuiltinId::Maddubs16 => (**x).maddubs_bytes(**y),
1510 _ => (**x).packus_bytes(**y),
1511 };
1512 r.map(|v| RuntimeValue::Lanes(Rc::new(v)))
1513 .ok_or_else(|| "byte-lane op requires two Lanes16Word8".to_string())
1514 }
1515 _ => Err(format!(
1516 "byte-lane op requires two Lanes16Word8, got {} and {}",
1517 a.type_name(),
1518 b.type_name()
1519 )),
1520 }
1521 }
1522 BuiltinId::ShrBytes16 => {
1523 let n = args.remove(1);
1524 let v = args.remove(0);
1525 match (&v, &n) {
1526 (RuntimeValue::Lanes(x), RuntimeValue::Int(k)) => (**x)
1527 .shr_bytes(*k as u32)
1528 .map(|r| RuntimeValue::Lanes(Rc::new(r)))
1529 .ok_or_else(|| "shrBytes16() requires a Lanes16Word8".to_string()),
1530 _ => Err(format!(
1531 "shrBytes16() requires a Lanes16Word8 and an Int, got {} and {}",
1532 v.type_name(),
1533 n.type_name()
1534 )),
1535 }
1536 }
1537 BuiltinId::Lanes8Word32 => {
1538 let s = args.remove(0);
1541 match s {
1542 RuntimeValue::List(items) => {
1543 let vals = items.borrow().to_values();
1544 let mut words = Vec::with_capacity(vals.len());
1545 for v in &vals {
1546 match v {
1547 RuntimeValue::Word(WordVal::W32(w)) => words.push(*w),
1548 RuntimeValue::Int(n) => words.push(Word32(*n as u32)),
1549 other => {
1550 return Err(format!(
1551 "lanes8Word32() needs a Seq of Word32, found {}",
1552 other.type_name()
1553 ))
1554 }
1555 }
1556 }
1557 Ok(RuntimeValue::Lanes(Rc::new(LanesVal::L8W32(
1558 logicaffeine_base::Lanes8Word32::from_words(&words),
1559 ))))
1560 }
1561 other => Err(format!(
1562 "lanes8Word32() requires a Seq of Word32, got {}",
1563 other.type_name()
1564 )),
1565 }
1566 }
1567 BuiltinId::IntOfWord32 => {
1568 let x = args.remove(0);
1570 match x {
1571 RuntimeValue::Word(w) => Ok(RuntimeValue::Int(w.to_u64() as i64)),
1572 RuntimeValue::Int(n) => Ok(RuntimeValue::Int(n)),
1573 other => Err(format!("intOfWord32() requires a Word32, got {}", other.type_name())),
1574 }
1575 }
1576 BuiltinId::IntOfWord64 => {
1577 let x = args.remove(0);
1579 match x {
1580 RuntimeValue::Word(w) => Ok(RuntimeValue::Int(w.to_u64() as i64)),
1581 RuntimeValue::Int(n) => Ok(RuntimeValue::Int(n)),
1582 other => Err(format!("intOfWord64() requires a Word64, got {}", other.type_name())),
1583 }
1584 }
1585 BuiltinId::Word64Shl | BuiltinId::Word64Shr => {
1586 let is_shl = matches!(id, BuiltinId::Word64Shl);
1587 let w = args.remove(0);
1588 let n = args.remove(0);
1589 let wv = match w {
1590 RuntimeValue::Word(w) => w.to_u64(),
1591 RuntimeValue::Int(v) => v as u64,
1592 other => return Err(format!("word64 shift requires a Word64, got {}", other.type_name())),
1593 };
1594 let nv = match n {
1595 RuntimeValue::Int(v) => v as u32,
1596 other => return Err(format!("word64 shift amount requires an Int, got {}", other.type_name())),
1597 };
1598 let r = if is_shl { wv.wrapping_shl(nv) } else { wv.wrapping_shr(nv) };
1599 Ok(RuntimeValue::Word(WordVal::W64(Word64(r))))
1600 }
1601 BuiltinId::Word64And => {
1602 let a = args.remove(0);
1603 let b = args.remove(0);
1604 let av = match a {
1605 RuntimeValue::Word(w) => w.to_u64(),
1606 RuntimeValue::Int(v) => v as u64,
1607 other => return Err(format!("word64And requires a Word64, got {}", other.type_name())),
1608 };
1609 let bv = match b {
1610 RuntimeValue::Word(w) => w.to_u64(),
1611 RuntimeValue::Int(v) => v as u64,
1612 other => return Err(format!("word64And requires a Word64, got {}", other.type_name())),
1613 };
1614 Ok(RuntimeValue::Word(WordVal::W64(Word64(av & bv))))
1615 }
1616 BuiltinId::Word32Shr => {
1617 let w = args.remove(0);
1618 let n = args.remove(0);
1619 let wv = match w {
1620 RuntimeValue::Word(word) => word.to_u64() as u32,
1621 RuntimeValue::Int(v) => v as u32,
1622 other => return Err(format!("word32Shr requires a Word32, got {}", other.type_name())),
1623 };
1624 let nv = match n {
1625 RuntimeValue::Int(v) => v as u32,
1626 other => return Err(format!("word32Shr amount requires an Int, got {}", other.type_name())),
1627 };
1628 Ok(RuntimeValue::Word(WordVal::W32(Word32(wv.wrapping_shr(nv)))))
1629 }
1630 BuiltinId::Word16Make => {
1631 let n = args.remove(0);
1634 match n {
1635 RuntimeValue::Int(v) => Ok(RuntimeValue::Word(WordVal::W32(Word32(v as u16 as u32)))),
1636 RuntimeValue::Word(w) => Ok(RuntimeValue::Word(WordVal::W32(Word32(w.to_u64() as u16 as u32)))),
1637 other => Err(format!("word16() requires an Int, got {}", other.type_name())),
1638 }
1639 }
1640 BuiltinId::IntOfWord16 => {
1641 let x = args.remove(0);
1643 match x {
1644 RuntimeValue::Word(w) => Ok(RuntimeValue::Int(w.to_u64() as u16 as i64)),
1645 RuntimeValue::Int(n) => Ok(RuntimeValue::Int(n as u16 as i64)),
1646 other => Err(format!("intOfWord16() requires a Word16, got {}", other.type_name())),
1647 }
1648 }
1649 BuiltinId::Lanes4Word64 => {
1650 let s = args.remove(0);
1652 match s {
1653 RuntimeValue::List(items) => {
1654 let vals = items.borrow().to_values();
1655 let mut words = Vec::with_capacity(vals.len());
1656 for v in &vals {
1657 match v {
1658 RuntimeValue::Word(WordVal::W64(w)) => words.push(*w),
1659 RuntimeValue::Int(n) => words.push(Word64(*n as u64)),
1660 other => {
1661 return Err(format!(
1662 "lanes4Word64() needs a Seq of Word64/Int, found {}",
1663 other.type_name()
1664 ))
1665 }
1666 }
1667 }
1668 Ok(RuntimeValue::Lanes(Rc::new(LanesVal::L4W64(
1669 logicaffeine_base::Lanes4Word64::from_words(&words),
1670 ))))
1671 }
1672 other => Err(format!("lanes4Word64() requires a Seq, got {}", other.type_name())),
1673 }
1674 }
1675 BuiltinId::SeqOfLanes4 => {
1676 let v = args.remove(0);
1678 match v {
1679 RuntimeValue::Lanes(lanes) => {
1680 let vals: Vec<RuntimeValue> = (0..lanes.lanes())
1681 .map(|i| RuntimeValue::Int(lanes.lane(i) as i64))
1682 .collect();
1683 Ok(RuntimeValue::List(Rc::new(std::cell::RefCell::new(
1684 crate::interpreter::ListRepr::from_values(vals),
1685 ))))
1686 }
1687 other => Err(format!("seqOfLanes4() requires a lane vector, got {}", other.type_name())),
1688 }
1689 }
1690 BuiltinId::Mul32x32To64 => {
1691 let a = args.remove(0);
1693 let b = args.remove(0);
1694 match (a, b) {
1695 (RuntimeValue::Lanes(la), RuntimeValue::Lanes(lb)) => match la.mul_lo32_wide(*lb) {
1696 Some(v) => Ok(RuntimeValue::Lanes(Rc::new(v))),
1697 None => Err(format!(
1698 "mul32x32to64 requires two Lanes4Word64, got {} and {}",
1699 la.type_name(),
1700 lb.type_name()
1701 )),
1702 },
1703 (a, b) => Err(format!(
1704 "mul32x32to64 requires two lane vectors, got {} and {}",
1705 a.type_name(),
1706 b.type_name()
1707 )),
1708 }
1709 }
1710 BuiltinId::HsumLanes4 => {
1711 let v = args.remove(0);
1713 match v {
1714 RuntimeValue::Lanes(lanes) => Ok(RuntimeValue::Int(lanes.hsum())),
1715 other => Err(format!("hsumLanes4 requires a lane vector, got {}", other.type_name())),
1716 }
1717 }
1718 BuiltinId::Splat4Word64 => {
1719 Err("splat4Word64 compiles to an AVX2 lane broadcast — AOT only, not the interpreter".to_string())
1722 }
1723 BuiltinId::AndNot4 => {
1724 Err("andNot4 compiles to an AVX2 lane vpandn — AOT only, not the interpreter".to_string())
1725 }
1726 BuiltinId::Lanes16Word16 => {
1727 let s = args.remove(0);
1729 match s {
1730 RuntimeValue::List(items) => {
1731 let vals = items.borrow().to_values();
1732 let mut words = Vec::with_capacity(vals.len());
1733 for v in &vals {
1734 match v {
1735 RuntimeValue::Word(WordVal::W32(w)) => words.push(Word16(w.0 as u16)),
1736 RuntimeValue::Int(n) => words.push(Word16(*n as u16)),
1737 other => {
1738 return Err(format!(
1739 "lanes16Word16() needs a Seq of Word16/Int, found {}",
1740 other.type_name()
1741 ))
1742 }
1743 }
1744 }
1745 Ok(RuntimeValue::Lanes(Rc::new(LanesVal::L16W16(
1746 logicaffeine_base::Lanes16Word16::from_words(&words),
1747 ))))
1748 }
1749 other => Err(format!("lanes16Word16() requires a Seq, got {}", other.type_name())),
1750 }
1751 }
1752 BuiltinId::SeqOfLanes16 => {
1753 let v = args.remove(0);
1755 match v {
1756 RuntimeValue::Lanes(lanes) => {
1757 let vals: Vec<RuntimeValue> = (0..lanes.lanes())
1758 .map(|i| RuntimeValue::Int(lanes.lane(i) as i64))
1759 .collect();
1760 Ok(RuntimeValue::List(Rc::new(std::cell::RefCell::new(
1761 crate::interpreter::ListRepr::from_values(vals),
1762 ))))
1763 }
1764 other => Err(format!("seqOfLanes16() requires a lane vector, got {}", other.type_name())),
1765 }
1766 }
1767 BuiltinId::Splat16Word16 => {
1768 let x = args.remove(0);
1770 let w = match x {
1771 RuntimeValue::Word(WordVal::W32(w)) => w.0 as u16,
1772 RuntimeValue::Int(n) => n as u16,
1773 other => {
1774 return Err(format!("splat16Word16() requires a Word16/Int, got {}", other.type_name()))
1775 }
1776 };
1777 Ok(RuntimeValue::Lanes(Rc::new(LanesVal::L16W16(
1778 logicaffeine_base::Lanes16Word16::splat(w),
1779 ))))
1780 }
1781 BuiltinId::Mulhi16 => {
1782 let a = args.remove(0);
1784 let b = args.remove(0);
1785 match (a, b) {
1786 (RuntimeValue::Lanes(la), RuntimeValue::Lanes(lb)) => match la.mulhi(*lb) {
1787 Some(v) => Ok(RuntimeValue::Lanes(Rc::new(v))),
1788 None => Err(format!(
1789 "mulhi16 requires two Lanes16Word16, got {} and {}",
1790 la.type_name(),
1791 lb.type_name()
1792 )),
1793 },
1794 (a, b) => Err(format!(
1795 "mulhi16 requires two lane vectors, got {} and {}",
1796 a.type_name(),
1797 b.type_name()
1798 )),
1799 }
1800 }
1801 BuiltinId::Montmul32 => {
1802 let a = args.remove(0);
1804 let b = args.remove(0);
1805 let q = args.remove(0);
1806 let qi = args.remove(0);
1807 match (a, b, q, qi) {
1808 (
1809 RuntimeValue::Lanes(la),
1810 RuntimeValue::Lanes(lb),
1811 RuntimeValue::Lanes(lq),
1812 RuntimeValue::Lanes(lqi),
1813 ) => match la.montmul32(*lb, *lq, *lqi) {
1814 Some(v) => Ok(RuntimeValue::Lanes(Rc::new(v))),
1815 None => Err(format!(
1816 "montmul32 requires four Lanes8Word32, got {}, {}, {}, {}",
1817 la.type_name(),
1818 lb.type_name(),
1819 lq.type_name(),
1820 lqi.type_name()
1821 )),
1822 },
1823 (a, b, q, qi) => Err(format!(
1824 "montmul32 requires four lane vectors, got {}, {}, {}, {}",
1825 a.type_name(),
1826 b.type_name(),
1827 q.type_name(),
1828 qi.type_name()
1829 )),
1830 }
1831 }
1832 BuiltinId::NttBcastLo | BuiltinId::NttBcastHi => {
1833 let is_low = matches!(id, BuiltinId::NttBcastLo);
1835 let name = if is_low { "nttBcastLo" } else { "nttBcastHi" };
1836 let v = args.remove(0);
1837 let h = ntt_stride(args.remove(0), name)?;
1838 match v {
1839 RuntimeValue::Lanes(lv) => {
1840 let r = if is_low { lv.ntt_bcast_lo(h) } else { lv.ntt_bcast_hi(h) };
1841 match r {
1842 Some(out) => Ok(RuntimeValue::Lanes(Rc::new(out))),
1843 None => Err(format!("{} requires a Lanes16Word16, got {}", name, lv.type_name())),
1844 }
1845 }
1846 other => Err(format!("{} requires a lane vector, got {}", name, other.type_name())),
1847 }
1848 }
1849 BuiltinId::NttBlend => {
1850 let a = args.remove(0);
1852 let b = args.remove(0);
1853 let h = ntt_stride(args.remove(0), "nttBlend")?;
1854 match (a, b) {
1855 (RuntimeValue::Lanes(la), RuntimeValue::Lanes(lb)) => match la.ntt_blend(*lb, h) {
1856 Some(v) => Ok(RuntimeValue::Lanes(Rc::new(v))),
1857 None => Err(format!(
1858 "nttBlend requires two Lanes16Word16, got {} and {}",
1859 la.type_name(),
1860 lb.type_name()
1861 )),
1862 },
1863 (a, b) => Err(format!(
1864 "nttBlend requires two lane vectors, got {} and {}",
1865 a.type_name(),
1866 b.type_name()
1867 )),
1868 }
1869 }
1870 BuiltinId::Splat8Word32 => {
1871 let x = args.remove(0);
1873 let w = match x {
1874 RuntimeValue::Word(WordVal::W32(w)) => w,
1875 RuntimeValue::Int(n) => Word32(n as u32),
1876 other => {
1877 return Err(format!("splat8Word32() requires a Word32, got {}", other.type_name()))
1878 }
1879 };
1880 Ok(RuntimeValue::Lanes(Rc::new(LanesVal::L8W32(
1881 logicaffeine_base::Lanes8Word32::splat(w.0),
1882 ))))
1883 }
1884 BuiltinId::SeqOfLanes8 => {
1885 let v = args.remove(0);
1887 match v {
1888 RuntimeValue::Lanes(lanes) => match *lanes {
1889 LanesVal::L8W32(lv) => {
1890 let vals: Vec<RuntimeValue> = lv
1891 .to_words()
1892 .iter()
1893 .map(|w| RuntimeValue::Word(WordVal::W32(*w)))
1894 .collect();
1895 Ok(RuntimeValue::List(Rc::new(std::cell::RefCell::new(
1896 crate::interpreter::ListRepr::from_values(vals),
1897 ))))
1898 }
1899 other => Err(format!(
1900 "seqOfLanes8() requires a Lanes8Word32, got {}",
1901 other.type_name()
1902 )),
1903 },
1904 other => Err(format!(
1905 "seqOfLanes8() requires a Lanes8Word32, got {}",
1906 other.type_name()
1907 )),
1908 }
1909 }
1910 BuiltinId::RunAccepted => {
1911 let int_arg = |v: &RuntimeValue, what: &str| -> Result<i64, String> {
1916 match v {
1917 RuntimeValue::Int(n) => Ok(*n),
1918 other => Err(format!("run_accepted: {what} must be an Int, got {}", other.type_name())),
1919 }
1920 };
1921 let arg = int_arg(&args[1], "the argument")?;
1922 let lo = int_arg(&args[2], "the lower bound")?;
1923 let hi = int_arg(&args[3], "the upper bound")?;
1924 let contract = crate::semantics::acceptance::AcceptanceContract::new(lo, hi);
1925 contract.apply(&args[0], arg).map(RuntimeValue::Int)
1926 }
1927 }
1928}
1929
1930#[cfg(test)]
1931mod tests {
1932 use super::*;
1933
1934 #[test]
1938 fn temporal_extractors_cover_every_component_and_reject_bad_inputs() {
1939 use logicaffeine_base::temporal;
1940 let call = |id, v: &RuntimeValue| call_builtin(id, vec![v.clone()]);
1941 let int = |id, v: &RuntimeValue| match call(id, v) {
1942 Ok(RuntimeValue::Int(n)) => n,
1943 other => panic!("{id:?} on {:?} -> {other:?}", v.type_name()),
1944 };
1945
1946 let m = RuntimeValue::Moment(temporal::parse_rfc3339("2024-03-10T07:30:45Z").unwrap());
1948 assert_eq!(int(BuiltinId::YearOf, &m), 2024);
1949 assert_eq!(int(BuiltinId::MonthOf, &m), 3);
1950 assert_eq!(int(BuiltinId::DayOf, &m), 10);
1951 assert_eq!(int(BuiltinId::WeekdayOf, &m), 0);
1952 assert_eq!(int(BuiltinId::HourOf, &m), 7);
1953 assert_eq!(int(BuiltinId::MinuteOf, &m), 30);
1954 assert_eq!(int(BuiltinId::SecondOf, &m), 45);
1955 assert_eq!(int(BuiltinId::WeekOf, &m), 10);
1956 assert_eq!(int(BuiltinId::QuarterOf, &m), 1);
1957 assert!(matches!(call(BuiltinId::DateOf, &m), Ok(RuntimeValue::Date(_))));
1959 assert!(matches!(call(BuiltinId::TimeOf, &m), Ok(RuntimeValue::Time(_))));
1960
1961 let days = (temporal::parse_rfc3339("2024-03-10T07:30:45Z").unwrap())
1963 .div_euclid(temporal::NANOS_PER_DAY) as i32;
1964 let d = RuntimeValue::Date(days);
1965 assert_eq!(int(BuiltinId::YearOf, &d), 2024);
1966 assert_eq!(int(BuiltinId::MonthOf, &d), 3);
1967 assert_eq!(int(BuiltinId::DayOf, &d), 10);
1968 assert_eq!(int(BuiltinId::WeekdayOf, &d), 0);
1969 assert_eq!(int(BuiltinId::WeekOf, &d), 10);
1970 assert_eq!(int(BuiltinId::QuarterOf, &d), 1);
1971 assert!(matches!(call(BuiltinId::DateOf, &d), Ok(RuntimeValue::Date(x)) if x == days));
1972
1973 for id in [BuiltinId::HourOf, BuiltinId::MinuteOf, BuiltinId::SecondOf, BuiltinId::TimeOf] {
1975 assert!(call(id, &d).unwrap_err().contains("no time-of-day"), "{id:?} should reject a Date");
1976 }
1977
1978 let bogus = RuntimeValue::Int(5);
1980 for id in [BuiltinId::YearOf, BuiltinId::WeekOf, BuiltinId::QuarterOf, BuiltinId::DateOf, BuiltinId::TimeOf] {
1981 assert!(call(id, &bogus).is_err(), "{id:?} should reject a non-temporal value");
1982 }
1983
1984 assert!(call_builtin(BuiltinId::SecondsBetween, vec![m.clone(), bogus.clone()]).is_err());
1986 assert!(call_builtin(BuiltinId::SecondsBetween, vec![bogus.clone(), m.clone()]).is_err());
1987
1988 let pre = RuntimeValue::Moment(temporal::parse_rfc3339("1969-12-31T23:59:58Z").unwrap());
1990 assert_eq!(int(BuiltinId::YearOf, &pre), 1969);
1991 assert_eq!(int(BuiltinId::MonthOf, &pre), 12);
1992 assert_eq!(int(BuiltinId::DayOf, &pre), 31);
1993 assert_eq!(int(BuiltinId::HourOf, &pre), 23);
1994 assert_eq!(int(BuiltinId::MinuteOf, &pre), 59);
1995 assert_eq!(int(BuiltinId::SecondOf, &pre), 58);
1996 }
1997
1998 #[test]
2001 fn date_of_then_components_agree_with_the_moment() {
2002 use logicaffeine_base::temporal;
2003 for ts in ["2024-03-10T07:30:45Z", "2000-02-29T00:00:00Z", "1969-12-31T23:59:58Z"] {
2004 let m = RuntimeValue::Moment(temporal::parse_rfc3339(ts).unwrap());
2005 let d = call_builtin(BuiltinId::DateOf, vec![m.clone()]).unwrap();
2006 for id in [BuiltinId::YearOf, BuiltinId::MonthOf, BuiltinId::DayOf, BuiltinId::WeekdayOf, BuiltinId::WeekOf, BuiltinId::QuarterOf] {
2007 let from_moment = call_builtin(id, vec![m.clone()]).unwrap();
2008 let from_date = call_builtin(id, vec![d.clone()]).unwrap();
2009 assert_eq!(from_moment, from_date, "{id:?} disagrees for {ts}");
2010 }
2011 }
2012 }
2013
2014 #[test]
2015 fn run_accepted_validates_then_runs_a_shipped_computation() {
2016 use crate::concurrency::marshal::GenExpr;
2017 use crate::interpreter::ClosureValue;
2018 use std::collections::HashMap;
2019 use std::rc::Rc;
2020 let mk = || {
2022 let gen = GenExpr::Add(
2023 Box::new(GenExpr::Mul(Box::new(GenExpr::Index), Box::new(GenExpr::Const(3)))),
2024 Box::new(GenExpr::Const(1)),
2025 );
2026 RuntimeValue::Function(Box::new(ClosureValue {
2027 body_index: usize::MAX,
2028 captured_env: HashMap::default(),
2029 param_names: vec![logicaffeine_base::Symbol::from_index(0)],
2030 generated: Some(Rc::new(gen)),
2031 }))
2032 };
2033 match call_builtin(
2035 BuiltinId::RunAccepted,
2036 vec![mk(), RuntimeValue::Int(5), RuntimeValue::Int(0), RuntimeValue::Int(1000)],
2037 ) {
2038 Ok(RuntimeValue::Int(n)) => assert_eq!(n, 16),
2039 other => panic!("in-bounds run_accepted should return Int(16), got {other:?}"),
2040 }
2041 assert!(
2043 call_builtin(
2044 BuiltinId::RunAccepted,
2045 vec![mk(), RuntimeValue::Int(5000), RuntimeValue::Int(0), RuntimeValue::Int(1000)],
2046 )
2047 .is_err(),
2048 "an out-of-range argument must be refused at the contract"
2049 );
2050 }
2051
2052 #[test]
2053 fn arity_messages_match_treewalker() {
2054 assert_eq!(
2055 check_arity(BuiltinId::Length, 2).unwrap_err(),
2056 "length() takes exactly 1 argument"
2057 );
2058 assert_eq!(
2059 check_arity(BuiltinId::Min, 1).unwrap_err(),
2060 "min() takes exactly 2 arguments"
2061 );
2062 assert!(check_arity(BuiltinId::Format, 0).is_ok());
2063 assert!(check_arity(BuiltinId::Format, 5).is_ok());
2064 }
2065
2066 #[test]
2067 fn parse_and_chr_messages() {
2068 let e = call_builtin(
2069 BuiltinId::ParseInt,
2070 vec![RuntimeValue::Text(Rc::new("zz".to_string()))],
2071 )
2072 .unwrap_err();
2073 assert_eq!(e, "Cannot parse 'zz' as Int");
2074 let e = call_builtin(BuiltinId::Chr, vec![RuntimeValue::Int(-1)]).unwrap_err();
2075 assert_eq!(e, "Invalid character code: -1");
2076 let r = call_builtin(BuiltinId::Chr, vec![RuntimeValue::Int(65)]).unwrap();
2077 assert!(matches!(&r, RuntimeValue::Text(s) if **s == "A"));
2078 }
2079
2080 #[test]
2081 fn numeric_builtins_coerce_like_treewalker() {
2082 assert!(matches!(
2083 call_builtin(BuiltinId::Sqrt, vec![RuntimeValue::Int(9)]).unwrap(),
2084 RuntimeValue::Float(f) if f == 3.0
2085 ));
2086 assert!(matches!(
2087 call_builtin(BuiltinId::Min, vec![RuntimeValue::Int(3), RuntimeValue::Float(2.5)]).unwrap(),
2088 RuntimeValue::Float(f) if f == 2.5
2089 ));
2090 assert!(matches!(
2091 call_builtin(BuiltinId::Floor, vec![RuntimeValue::Float(2.9)]).unwrap(),
2092 RuntimeValue::Int(2)
2093 ));
2094 assert!(matches!(
2095 call_builtin(BuiltinId::Pow, vec![RuntimeValue::Int(2), RuntimeValue::Int(10)]).unwrap(),
2096 RuntimeValue::Int(1024)
2097 ));
2098 assert!(matches!(
2100 call_builtin(BuiltinId::Pow, vec![RuntimeValue::Int(2), RuntimeValue::Int(-1)]).unwrap(),
2101 RuntimeValue::Float(f) if f == 0.5
2102 ));
2103 }
2104
2105 #[test]
2106 fn decimal_builtin_constructs_exact_money() {
2107 let d = call_builtin(BuiltinId::Decimal, vec![RuntimeValue::Text(Rc::new("19.99".into()))]).unwrap();
2110 assert!(matches!(&d, RuntimeValue::Decimal(_)));
2111 assert_eq!(d.to_display_string(), "19.99");
2112 let i = call_builtin(BuiltinId::Decimal, vec![RuntimeValue::Int(5)]).unwrap();
2114 assert!(matches!(&i, RuntimeValue::Decimal(_)));
2115 assert_eq!(i.to_display_string(), "5");
2116 let e = call_builtin(BuiltinId::Decimal, vec![RuntimeValue::Text(Rc::new("zz".into()))]).unwrap_err();
2118 assert_eq!(e, "Cannot parse 'zz' as Decimal");
2119 assert_eq!(check_arity(BuiltinId::Decimal, 2).unwrap_err(), "decimal() takes exactly 1 argument");
2121 }
2122
2123 #[test]
2124 fn complex_builtin_constructs_exact_complex_numbers() {
2125 let i = call_builtin(BuiltinId::Complex, vec![RuntimeValue::Int(0), RuntimeValue::Int(1)]).unwrap();
2127 assert!(matches!(&i, RuntimeValue::Complex(_)));
2128 assert_eq!(i.to_display_string(), "i");
2129 let z = call_builtin(BuiltinId::Complex, vec![RuntimeValue::Int(3), RuntimeValue::Int(4)]).unwrap();
2131 assert_eq!(z.to_display_string(), "3+4i");
2132 let neg_i = call_builtin(BuiltinId::Complex, vec![RuntimeValue::Int(0), RuntimeValue::Int(-1)]).unwrap();
2133 assert_eq!(neg_i.to_display_string(), "-i");
2134 let _ = i; assert!(call_builtin(BuiltinId::Complex, vec![RuntimeValue::Float(1.0), RuntimeValue::Int(1)]).is_err());
2137 assert_eq!(check_arity(BuiltinId::Complex, 1).unwrap_err(), "complex() takes exactly 2 arguments");
2139 }
2140
2141 #[test]
2142 fn decimal_and_complex_pass_through_the_numeric_builtins() {
2143 let d = |s: &str| RuntimeValue::Decimal(Rc::new(Decimal::parse(s).unwrap()));
2144 assert_eq!(call_builtin(BuiltinId::Abs, vec![d("-0.05")]).unwrap().to_display_string(), "0.05");
2146 assert_eq!(call_builtin(BuiltinId::Floor, vec![d("19.99")]).unwrap().to_display_string(), "19");
2147 assert_eq!(call_builtin(BuiltinId::Ceil, vec![d("19.01")]).unwrap().to_display_string(), "20");
2148 assert_eq!(call_builtin(BuiltinId::Round, vec![d("2.5")]).unwrap().to_display_string(), "3");
2149 assert_eq!(call_builtin(BuiltinId::Round, vec![d("-19.99")]).unwrap().to_display_string(), "-20");
2150 assert_eq!(call_builtin(BuiltinId::Min, vec![d("0.10"), d("0.2")]).unwrap().to_display_string(), "0.10");
2151 assert_eq!(call_builtin(BuiltinId::Max, vec![d("0.10"), d("0.2")]).unwrap().to_display_string(), "0.2");
2152 let z = RuntimeValue::Complex(Rc::new(logicaffeine_base::Complex::new(
2154 logicaffeine_base::Rational::from_i64(3),
2155 logicaffeine_base::Rational::from_i64(4),
2156 )));
2157 match call_builtin(BuiltinId::Abs, vec![z]).unwrap() {
2158 RuntimeValue::Float(f) => assert!((f - 5.0).abs() < 1e-12),
2159 other => panic!("expected a Float magnitude, got {other:?}"),
2160 }
2161 }
2162
2163 #[test]
2164 fn modular_builtin_constructs_and_exponentiates_in_the_ring() {
2165 let x = call_builtin(BuiltinId::Modular, vec![RuntimeValue::Int(10), RuntimeValue::Int(7)]).unwrap();
2167 assert!(matches!(&x, RuntimeValue::Modular(_)));
2168 assert_eq!(x.to_display_string(), "3 (mod 7)");
2169 let base = call_builtin(BuiltinId::Modular, vec![RuntimeValue::Int(3), RuntimeValue::Int(5)]).unwrap();
2171 let p = call_builtin(BuiltinId::Pow, vec![base, RuntimeValue::Int(4)]).unwrap();
2172 assert_eq!(p.to_display_string(), "1 (mod 5)");
2173 assert!(call_builtin(BuiltinId::Modular, vec![RuntimeValue::Int(3), RuntimeValue::Int(0)]).is_err());
2175 assert_eq!(check_arity(BuiltinId::Modular, 1).unwrap_err(), "modular() takes exactly 2 arguments");
2176 }
2177
2178 #[test]
2179 fn quantity_builtins_construct_convert_and_compute_exactly() {
2180 use crate::semantics::arith::{add, divide, multiply, subtract};
2181 let q = |v: i64, u: &str| {
2182 call_builtin(BuiltinId::Quantity, vec![RuntimeValue::Int(v), RuntimeValue::Text(Rc::new(u.into()))]).unwrap()
2183 };
2184 let conv = |x: RuntimeValue, u: &str| {
2185 call_builtin(BuiltinId::Convert, vec![x, RuntimeValue::Text(Rc::new(u.into()))])
2186 };
2187 assert!(matches!(&q(2, "inch"), RuntimeValue::Quantity(_)));
2189 assert_eq!(q(2, "inch").to_display_string(), "2 in");
2190 assert_eq!(q(20, "celsius").to_display_string(), "20 °C");
2191 let sum = add(q(2, "inch"), q(5, "centimeter")).unwrap();
2193 assert_eq!(conv(sum, "foot").unwrap().to_display_string(), "42/127 ft");
2194 assert_eq!(subtract(q(1, "meter"), q(50, "centimeter")).unwrap().to_display_string(), "1/2 m");
2196 assert_eq!(multiply(q(2, "inch"), RuntimeValue::Int(3)).unwrap().to_display_string(), "6 in");
2198 assert_eq!(multiply(RuntimeValue::Int(3), q(2, "inch")).unwrap().to_display_string(), "6 in");
2199 assert_eq!(divide(q(6, "inch"), RuntimeValue::Int(2)).unwrap().to_display_string(), "3 in");
2200 assert_eq!(multiply(q(3, "meter"), q(4, "meter")).unwrap().to_display_string(), "12 L^2");
2202 assert_eq!(divide(q(100, "meter"), q(10, "second")).unwrap().to_display_string(), "10 L·T^-1");
2204 assert_eq!(q(100, "centimeter"), q(1, "meter"));
2206 assert!(add(q(1, "meter"), q(1, "kilogram")).is_err());
2208 assert!(conv(q(1, "meter"), "kilogram").is_err());
2210 assert!(call_builtin(BuiltinId::Quantity,
2212 vec![RuntimeValue::Int(1), RuntimeValue::Text(Rc::new("zorgle".into()))]).is_err());
2213 assert!(call_builtin(BuiltinId::Quantity,
2214 vec![RuntimeValue::Float(1.5), RuntimeValue::Text(Rc::new("meter".into()))]).is_err());
2215 assert_eq!(check_arity(BuiltinId::Quantity, 1).unwrap_err(), "quantity() takes exactly 2 arguments");
2217 assert_eq!(check_arity(BuiltinId::Convert, 3).unwrap_err(), "convert() takes exactly 2 arguments");
2218 }
2219
2220 #[test]
2221 fn abs_and_pow_are_exact_promoting_past_i64() {
2222 let abs_min = call_builtin(BuiltinId::Abs, vec![RuntimeValue::Int(i64::MIN)]).unwrap();
2225 assert_eq!(abs_min.to_display_string(), "9223372036854775808");
2226 assert_eq!(abs_min.type_name(), "Int", "a BigInt is still an integer type");
2227
2228 let two_pow_63 = call_builtin(BuiltinId::Pow, vec![RuntimeValue::Int(2), RuntimeValue::Int(63)]).unwrap();
2230 assert_eq!(two_pow_63.to_display_string(), "9223372036854775808");
2231
2232 let two_pow_100 = call_builtin(BuiltinId::Pow, vec![RuntimeValue::Int(2), RuntimeValue::Int(100)]).unwrap();
2234 assert_eq!(two_pow_100.to_display_string(), "1267650600228229401496703205376");
2235
2236 assert!(matches!(
2238 call_builtin(BuiltinId::Pow, vec![RuntimeValue::Int(3), RuntimeValue::Int(4)]).unwrap(),
2239 RuntimeValue::Int(81)
2240 ));
2241 assert!(matches!(
2243 call_builtin(BuiltinId::Abs, vec![RuntimeValue::Int(-5)]).unwrap(),
2244 RuntimeValue::Int(5)
2245 ));
2246 }
2247
2248 #[test]
2249 fn copy_is_deep() {
2250 use std::cell::RefCell;
2251 let inner = std::rc::Rc::new(RefCell::new(
2252 crate::interpreter::ListRepr::from_values(vec![RuntimeValue::Int(1)]),
2253 ));
2254 let original = RuntimeValue::List(inner.clone());
2255 let copied = call_builtin(BuiltinId::Copy, vec![original]).unwrap();
2256 if let RuntimeValue::List(copied_items) = &copied {
2257 inner.borrow_mut().push(RuntimeValue::Int(2));
2258 assert_eq!(copied_items.borrow().len(), 1, "copy must not share the allocation");
2259 } else {
2260 panic!("copy changed the type");
2261 }
2262 }
2263}