1use std::collections::HashMap;
2use std::sync::atomic::Ordering;
3use std::sync::Arc;
4use std::{future::Future, pin::Pin};
5
6use crate::harness::VmHarness;
7use crate::mcp::VmMcpClientHandle;
8use crate::BuiltinId;
9
10use super::{
11 VmAtomicHandle, VmChannelHandle, VmClosure, VmError, VmGenerator, VmRange,
12 VmResourceGuardHandle, VmRngHandle, VmSet, VmStream, VmSyncPermitHandle, VmVerdictReceipt,
13};
14
15pub type VmAsyncBuiltinFn = Arc<
22 dyn Fn(
23 crate::vm::AsyncBuiltinCtx,
24 Vec<VmValue>,
25 ) -> Pin<Box<dyn Future<Output = Result<VmValue, VmError>> + Send>>
26 + Send
27 + Sync,
28>;
29
30type Shared<T> = Arc<T>;
31
32pub type HarnStr = arcstr::ArcStr;
43
44pub type DictMap = imbl::OrdMap<HarnStr, VmValue>;
56
57pub fn intern_key(key: &str) -> HarnStr {
68 const MAX_INTERNED_KEY_LEN: usize = 64;
69 const MAX_INTERNED_KEYS: usize = 8192;
70 static INTERNED_KEYS: std::sync::LazyLock<parking_lot::Mutex<HashMap<Box<str>, HarnStr>>> =
71 std::sync::LazyLock::new(|| parking_lot::Mutex::new(HashMap::new()));
72
73 if key.len() > MAX_INTERNED_KEY_LEN {
74 return HarnStr::from(key);
75 }
76 let mut table = INTERNED_KEYS.lock();
77 if let Some(existing) = table.get(key) {
78 return existing.clone();
79 }
80 let interned = HarnStr::from(key);
81 if table.len() < MAX_INTERNED_KEYS {
82 table.insert(Box::from(key), interned.clone());
83 }
84 interned
85}
86
87pub trait IntoDictKey {
95 fn into_dict_key(self) -> HarnStr;
96}
97
98impl IntoDictKey for String {
99 fn into_dict_key(self) -> HarnStr {
100 intern_key(&self)
101 }
102}
103
104impl IntoDictKey for &str {
105 fn into_dict_key(self) -> HarnStr {
106 intern_key(self)
107 }
108}
109
110impl IntoDictKey for HarnStr {
111 fn into_dict_key(self) -> HarnStr {
112 self
113 }
114}
115
116pub fn string_char_count(text: &str) -> usize {
122 if text.is_ascii() {
123 text.len()
124 } else {
125 text.chars().count()
126 }
127}
128
129#[derive(Debug, Clone, PartialEq, Eq)]
131pub struct StructLayout {
132 struct_name: String,
133 field_names: Vec<String>,
134 field_indexes: HashMap<String, usize>,
135}
136
137impl StructLayout {
138 pub fn new(struct_name: impl Into<String>, field_names: Vec<String>) -> Self {
139 let mut deduped = Vec::with_capacity(field_names.len());
140 let mut field_indexes = HashMap::with_capacity(field_names.len());
141 for field_name in field_names {
142 if field_indexes.contains_key(&field_name) {
143 continue;
144 }
145 let index = deduped.len();
146 field_indexes.insert(field_name.clone(), index);
147 deduped.push(field_name);
148 }
149
150 Self {
151 struct_name: struct_name.into(),
152 field_names: deduped,
153 field_indexes,
154 }
155 }
156
157 pub fn from_map(struct_name: impl Into<String>, fields: &crate::value::DictMap) -> Self {
158 Self::new(
159 struct_name,
160 fields.keys().map(|key| key.to_string()).collect(),
161 )
162 }
163
164 pub fn struct_name(&self) -> &str {
165 &self.struct_name
166 }
167
168 pub fn field_names(&self) -> &[String] {
169 &self.field_names
170 }
171
172 pub fn field_index(&self, field_name: &str) -> Option<usize> {
173 if self.field_names.len() <= 8 {
174 return self
175 .field_names
176 .iter()
177 .position(|candidate| candidate == field_name);
178 }
179 self.field_indexes.get(field_name).copied()
180 }
181
182 pub fn with_appended_field(&self, field_name: String) -> Self {
183 if self.field_indexes.contains_key(&field_name) {
184 return self.clone();
185 }
186 let mut field_names = self.field_names.clone();
187 field_names.push(field_name);
188 Self::new(self.struct_name.clone(), field_names)
189 }
190}
191
192#[derive(Debug, Clone)]
194pub struct VmEnumVariant {
195 pub enum_name: HarnStr,
196 pub variant: HarnStr,
197 pub fields: Shared<Vec<VmValue>>,
198}
199
200impl VmEnumVariant {
201 pub fn has_enum_name(&self, enum_name: &str) -> bool {
202 self.enum_name.as_str() == enum_name
203 }
204
205 pub fn is_variant(&self, enum_name: &str, variant: &str) -> bool {
206 self.has_enum_name(enum_name) && self.variant.as_str() == variant
207 }
208}
209
210#[derive(Debug, Clone)]
217pub struct VmBuiltinRefId {
218 pub id: BuiltinId,
219 pub name: HarnStr,
220}
221
222#[derive(Debug, Clone)]
229pub struct StructInstanceData {
230 pub layout: Shared<StructLayout>,
231 pub fields: Shared<Vec<Option<VmValue>>>,
232}
233
234#[derive(Debug, Clone)]
249pub enum VmValue {
250 Int(i64),
251 Float(f64),
252 Decimal(Shared<rust_decimal::Decimal>),
260 String(HarnStr),
261 Bytes(Shared<Vec<u8>>),
262 Bool(bool),
263 Nil,
264 List(Shared<Vec<VmValue>>),
265 Dict(Shared<DictMap>),
266 Closure(Shared<VmClosure>),
267 BuiltinRef(HarnStr),
271 BuiltinRefId(Shared<VmBuiltinRefId>),
276 Duration(i64),
277 EnumVariant(Shared<VmEnumVariant>),
278 StructInstance(Shared<StructInstanceData>),
279 TaskHandle(HarnStr),
280 Channel(Shared<VmChannelHandle>),
281 Atomic(Shared<VmAtomicHandle>),
282 Rng(Shared<VmRngHandle>),
283 SyncPermit(Shared<VmSyncPermitHandle>),
284 ResourceGuard(Shared<VmResourceGuardHandle>),
285 McpClient(Shared<VmMcpClientHandle>),
286 VerdictReceipt(Shared<VmVerdictReceipt>),
290 Set(Shared<VmSet>),
291 Generator(Shared<VmGenerator>),
292 Stream(Shared<VmStream>),
293 Range(Shared<VmRange>),
297 Iter(crate::vm::iter::VmIterHandle),
299 Pair(Shared<(VmValue, VmValue)>),
304 Harness(Shared<VmHarness>),
309}
310
311static ASCII_CHAR_STRINGS: std::sync::LazyLock<[HarnStr; 128]> = std::sync::LazyLock::new(|| {
320 std::array::from_fn(|byte| {
321 let mut buffer = [0u8; 4];
322 HarnStr::from((byte as u8 as char).encode_utf8(&mut buffer))
323 })
324});
325
326impl VmValue {
327 pub fn string(value: impl AsRef<str>) -> Self {
335 VmValue::String(HarnStr::from(value.as_ref()))
336 }
337
338 pub fn decimal(value: rust_decimal::Decimal) -> Self {
343 VmValue::Decimal(Shared::new(value))
344 }
345
346 pub fn char_value(ch: char) -> Self {
350 if ch.is_ascii() {
351 return VmValue::String(ASCII_CHAR_STRINGS[ch as usize].clone());
352 }
353 let mut buffer = [0u8; 4];
354 VmValue::String(HarnStr::from(ch.encode_utf8(&mut buffer)))
355 }
356
357 pub fn chars_list(text: &str) -> Self {
362 VmValue::List(Shared::new(text.chars().map(VmValue::char_value).collect()))
363 }
364
365 pub fn enum_variant(
366 enum_name: impl Into<HarnStr>,
367 variant: impl Into<HarnStr>,
368 fields: Vec<VmValue>,
369 ) -> Self {
370 VmValue::EnumVariant(Shared::new(VmEnumVariant {
371 enum_name: enum_name.into(),
372 variant: variant.into(),
373 fields: Shared::new(fields),
374 }))
375 }
376
377 pub fn task_handle(id: impl Into<HarnStr>) -> Self {
378 VmValue::TaskHandle(id.into())
379 }
380
381 pub fn range(range: VmRange) -> Self {
383 VmValue::Range(Shared::new(range))
384 }
385
386 pub fn builtin_ref_id(id: BuiltinId, name: impl Into<HarnStr>) -> Self {
388 VmValue::BuiltinRefId(Shared::new(VmBuiltinRefId {
389 id,
390 name: name.into(),
391 }))
392 }
393
394 pub fn dict<K: IntoDictKey>(entries: impl IntoIterator<Item = (K, VmValue)>) -> Self {
400 VmValue::Dict(Shared::new(
401 entries
402 .into_iter()
403 .map(|(k, v)| (k.into_dict_key(), v))
404 .collect::<DictMap>(),
405 ))
406 }
407
408 pub fn dict_map(map: DictMap) -> Self {
410 VmValue::Dict(Shared::new(map))
411 }
412
413 pub fn set(values: impl IntoIterator<Item = VmValue>) -> Self {
416 VmValue::Set(Shared::new(values.into_iter().collect::<VmSet>()))
417 }
418
419 pub fn set_value(set: VmSet) -> Self {
421 VmValue::Set(Shared::new(set))
422 }
423
424 pub fn channel(handle: VmChannelHandle) -> Self {
425 VmValue::Channel(Shared::new(handle))
426 }
427
428 pub fn atomic(handle: VmAtomicHandle) -> Self {
429 VmValue::Atomic(Shared::new(handle))
430 }
431
432 pub fn rng(handle: VmRngHandle) -> Self {
433 VmValue::Rng(Shared::new(handle))
434 }
435
436 pub fn sync_permit(handle: VmSyncPermitHandle) -> Self {
437 VmValue::SyncPermit(Shared::new(handle))
438 }
439
440 pub fn resource_guard(handle: VmResourceGuardHandle) -> Self {
441 VmValue::ResourceGuard(Shared::new(handle))
442 }
443
444 pub fn mcp_client(handle: VmMcpClientHandle) -> Self {
445 VmValue::McpClient(Shared::new(handle))
446 }
447
448 pub fn verdict_receipt(receipt: VmVerdictReceipt) -> Self {
452 VmValue::VerdictReceipt(Shared::new(receipt))
453 }
454
455 pub fn generator(generator: VmGenerator) -> Self {
456 VmValue::Generator(Shared::new(generator))
457 }
458
459 pub fn stream(stream: VmStream) -> Self {
460 VmValue::Stream(Shared::new(stream))
461 }
462
463 pub fn harness(handle: VmHarness) -> Self {
464 VmValue::Harness(Shared::new(handle))
465 }
466
467 pub fn struct_instance(
468 struct_name: impl Into<Shared<str>>,
469 fields: crate::value::DictMap,
470 ) -> Self {
471 Self::struct_instance_from_map(struct_name.into().to_string(), fields)
472 }
473
474 pub fn is_truthy(&self) -> bool {
475 match self {
476 VmValue::Bool(b) => *b,
477 VmValue::Nil => false,
478 VmValue::Int(n) => *n != 0,
479 VmValue::Float(n) => *n != 0.0,
480 VmValue::Decimal(d) => **d != rust_decimal::Decimal::ZERO,
481 VmValue::String(s) => !s.is_empty(),
482 VmValue::Bytes(bytes) => !bytes.is_empty(),
483 VmValue::List(l) => !l.is_empty(),
484 VmValue::Dict(d) => !d.is_empty(),
485 VmValue::Closure(_) => true,
486 VmValue::BuiltinRef(_) => true,
487 VmValue::BuiltinRefId(_) => true,
488 VmValue::Duration(ms) => *ms != 0,
489 VmValue::EnumVariant(_) => true,
490 VmValue::StructInstance(_) => true,
491 VmValue::TaskHandle(_) => true,
492 VmValue::Channel(_) => true,
493 VmValue::Atomic(_) => true,
494 VmValue::Rng(_) => true,
495 VmValue::SyncPermit(_) => true,
496 VmValue::ResourceGuard(_) => true,
497 VmValue::McpClient(_) => true,
498 VmValue::VerdictReceipt(_) => true,
499 VmValue::Set(s) => !s.is_empty(),
500 VmValue::Generator(_) => true,
501 VmValue::Stream(_) => true,
502 VmValue::Range(_) => true,
505 VmValue::Iter(_) => true,
506 VmValue::Pair(_) => true,
507 VmValue::Harness(_) => true,
508 }
509 }
510
511 pub const ALL_TYPE_NAMES: &'static [&'static str] = &[
517 "string",
518 "bytes",
519 "int",
520 "float",
521 "decimal",
522 "bool",
523 "nil",
524 "list",
525 "dict",
526 "closure",
527 "builtin",
528 "duration",
529 "enum",
530 "struct",
531 "task_handle",
532 "channel",
533 "atomic",
534 "rng",
535 "sync_permit",
536 "resource_guard",
537 "mcp_client",
538 "verdict_receipt",
539 "set",
540 "generator",
541 "stream",
542 "range",
543 "iter",
544 "pair",
545 ];
546
547 pub fn type_name(&self) -> &'static str {
548 match self {
549 VmValue::String(_) => "string",
550 VmValue::Bytes(_) => "bytes",
551 VmValue::Int(_) => "int",
552 VmValue::Float(_) => "float",
553 VmValue::Decimal(_) => "decimal",
554 VmValue::Bool(_) => "bool",
555 VmValue::Nil => "nil",
556 VmValue::List(_) => "list",
557 VmValue::Dict(_) => "dict",
558 VmValue::Closure(_) => "closure",
559 VmValue::BuiltinRef(_) => "builtin",
560 VmValue::BuiltinRefId(_) => "builtin",
561 VmValue::Duration(_) => "duration",
562 VmValue::EnumVariant(_) => "enum",
563 VmValue::StructInstance(_) => "struct",
564 VmValue::TaskHandle(_) => "task_handle",
565 VmValue::Channel(_) => "channel",
566 VmValue::Atomic(_) => "atomic",
567 VmValue::Rng(_) => "rng",
568 VmValue::SyncPermit(_) => "sync_permit",
569 VmValue::ResourceGuard(_) => "resource_guard",
570 VmValue::McpClient(_) => "mcp_client",
571 VmValue::VerdictReceipt(_) => "verdict_receipt",
572 VmValue::Set(_) => "set",
573 VmValue::Generator(_) => "generator",
574 VmValue::Stream(_) => "stream",
575 VmValue::Range(_) => "range",
576 VmValue::Iter(_) => "iter",
577 VmValue::Pair(_) => "pair",
578 VmValue::Harness(h) => h.type_name(),
579 }
580 }
581
582 pub fn as_str_cow(&self) -> std::borrow::Cow<'_, str> {
588 match self {
589 VmValue::String(s) => std::borrow::Cow::Borrowed(s.as_str()),
590 other => std::borrow::Cow::Owned(other.display()),
591 }
592 }
593
594 pub fn struct_data(&self) -> Option<&StructInstanceData> {
598 match self {
599 VmValue::StructInstance(data) => Some(data),
600 _ => None,
601 }
602 }
603
604 pub fn struct_name(&self) -> Option<&str> {
605 match self {
606 VmValue::StructInstance(data) => Some(data.layout.struct_name()),
607 _ => None,
608 }
609 }
610
611 pub fn struct_field(&self, field_name: &str) -> Option<&VmValue> {
612 match self {
613 VmValue::StructInstance(data) => data
614 .layout
615 .field_index(field_name)
616 .and_then(|index| data.fields.get(index))
617 .and_then(Option::as_ref),
618 _ => None,
619 }
620 }
621
622 pub fn struct_fields_map(&self) -> Option<crate::value::DictMap> {
623 match self {
624 VmValue::StructInstance(data) => Some(struct_fields_to_map(&data.layout, &data.fields)),
625 _ => None,
626 }
627 }
628
629 pub fn struct_instance_from_map(
630 struct_name: impl Into<String>,
631 fields: crate::value::DictMap,
632 ) -> Self {
633 let layout = Shared::new(StructLayout::from_map(struct_name, &fields));
634 let slots = layout
635 .field_names()
636 .iter()
637 .map(|name| fields.get(name.as_str()).cloned())
638 .collect();
639 VmValue::StructInstance(Shared::new(StructInstanceData {
640 layout,
641 fields: Shared::new(slots),
642 }))
643 }
644
645 pub fn struct_instance_with_layout(
646 struct_name: impl Into<String>,
647 field_names: Vec<String>,
648 field_values: crate::value::DictMap,
649 ) -> Self {
650 let layout = Shared::new(StructLayout::new(struct_name, field_names));
651 let fields = layout
652 .field_names()
653 .iter()
654 .map(|name| field_values.get(name.as_str()).cloned())
655 .collect();
656 VmValue::StructInstance(Shared::new(StructInstanceData {
657 layout,
658 fields: Shared::new(fields),
659 }))
660 }
661
662 pub fn struct_instance_with_property(&self, field_name: &str, value: VmValue) -> Option<Self> {
663 let VmValue::StructInstance(data) = self else {
664 return None;
665 };
666 let (layout, fields) = (&data.layout, &data.fields);
667
668 let mut new_fields = fields.as_ref().clone();
669 let layout = match layout.field_index(field_name) {
670 Some(index) => {
671 if index >= new_fields.len() {
672 new_fields.resize(index + 1, None);
673 }
674 new_fields[index] = Some(value);
675 Shared::clone(layout)
676 }
677 None => {
678 let new_layout = Shared::new(layout.with_appended_field(field_name.to_string()));
679 new_fields.push(Some(value));
680 new_layout
681 }
682 };
683
684 Some(VmValue::StructInstance(Shared::new(StructInstanceData {
685 layout,
686 fields: Shared::new(new_fields),
687 })))
688 }
689
690 pub fn display(&self) -> String {
691 let mut out = String::new();
692 self.write_display(&mut out);
693 out
694 }
695
696 pub fn write_display(&self, out: &mut String) {
699 use std::fmt::Write;
700
701 match self {
702 VmValue::Int(n) => {
703 let _ = write!(out, "{n}");
704 }
705 VmValue::Float(n) => {
706 if *n == (*n as i64) as f64 && n.abs() < 1e15 {
707 let _ = write!(out, "{n:.1}");
708 } else {
709 let _ = write!(out, "{n}");
710 }
711 }
712 VmValue::Decimal(d) => {
717 let _ = write!(out, "{d}");
718 }
719 VmValue::String(s) => out.push_str(s),
720 VmValue::Bytes(bytes) => {
721 const MAX_PREVIEW_BYTES: usize = 32;
722
723 out.push_str("b\"");
724 for byte in bytes.iter().take(MAX_PREVIEW_BYTES) {
725 let _ = write!(out, "{byte:02x}");
726 }
727 if bytes.len() > MAX_PREVIEW_BYTES {
728 let _ = write!(out, "...+{}", bytes.len() - MAX_PREVIEW_BYTES);
729 }
730 out.push('"');
731 }
732 VmValue::Bool(b) => out.push_str(if *b { "true" } else { "false" }),
733 VmValue::Nil => out.push_str("nil"),
734 VmValue::List(items) => {
735 out.push('[');
736 crate::value::recursion::guard_recursion(|| {
737 for (i, item) in items.iter().enumerate() {
738 if i > 0 {
739 out.push_str(", ");
740 }
741 item.write_display(out);
742 }
743 });
744 out.push(']');
745 }
746 VmValue::Dict(map) => {
747 out.push('{');
748 crate::value::recursion::guard_recursion(|| {
749 for (i, (k, v)) in map.iter().enumerate() {
750 if i > 0 {
751 out.push_str(", ");
752 }
753 out.push_str(k);
754 out.push_str(": ");
755 v.write_display(out);
756 }
757 });
758 out.push('}');
759 }
760 VmValue::Closure(c) => {
761 let names: Vec<&str> = c.func.param_names().collect();
762 let _ = write!(out, "<fn({})>", names.join(", "));
763 }
764 VmValue::BuiltinRef(name) => {
765 let _ = write!(out, "<builtin {name}>");
766 }
767 VmValue::BuiltinRefId(r) => {
768 let _ = write!(out, "<builtin {}>", r.name);
769 }
770 VmValue::Duration(ms) => {
771 let sign = if *ms < 0 { "-" } else { "" };
772 let abs_ms = ms.unsigned_abs();
773 if abs_ms >= 604_800_000 && abs_ms % 604_800_000 == 0 {
774 let _ = write!(out, "{}{}w", sign, abs_ms / 604_800_000);
775 } else if abs_ms >= 86_400_000 && abs_ms % 86_400_000 == 0 {
776 let _ = write!(out, "{}{}d", sign, abs_ms / 86_400_000);
777 } else if abs_ms >= 3_600_000 && abs_ms % 3_600_000 == 0 {
778 let _ = write!(out, "{}{}h", sign, abs_ms / 3_600_000);
779 } else if abs_ms >= 60_000 && abs_ms % 60_000 == 0 {
780 let _ = write!(out, "{}{}m", sign, abs_ms / 60_000);
781 } else if abs_ms >= 1000 && abs_ms % 1000 == 0 {
782 let _ = write!(out, "{}{}s", sign, abs_ms / 1000);
783 } else {
784 let _ = write!(out, "{sign}{abs_ms}ms");
785 }
786 }
787 VmValue::EnumVariant(enum_variant) => {
788 if enum_variant.fields.is_empty() {
789 let _ = write!(out, "{}.{}", enum_variant.enum_name, enum_variant.variant);
790 } else {
791 let _ = write!(out, "{}.{}(", enum_variant.enum_name, enum_variant.variant);
792 crate::value::recursion::guard_recursion(|| {
793 for (i, v) in enum_variant.fields.iter().enumerate() {
794 if i > 0 {
795 out.push_str(", ");
796 }
797 v.write_display(out);
798 }
799 });
800 out.push(')');
801 }
802 }
803 VmValue::StructInstance(data) => {
804 let (layout, fields) = (&data.layout, &data.fields);
805 let _ = write!(out, "{} {{", layout.struct_name());
806 crate::value::recursion::guard_recursion(|| {
807 for (i, (k, v)) in struct_fields_to_map(layout, fields).iter().enumerate() {
808 if i > 0 {
809 out.push_str(", ");
810 }
811 out.push_str(k);
812 out.push_str(": ");
813 v.write_display(out);
814 }
815 });
816 out.push('}');
817 }
818 VmValue::TaskHandle(id) => {
819 let _ = write!(out, "<task:{id}>");
820 }
821 VmValue::Channel(ch) => {
822 let _ = write!(out, "<channel:{}>", ch.name);
823 }
824 VmValue::Atomic(a) => {
825 let _ = write!(out, "<atomic:{}>", a.value.load(Ordering::SeqCst));
826 }
827 VmValue::Rng(_) => {
828 out.push_str("<rng>");
829 }
830 VmValue::SyncPermit(p) => {
831 let _ = write!(out, "<sync_permit:{}:{}>", p.kind(), p.key());
832 }
833 VmValue::ResourceGuard(guard) => {
834 let _ = write!(out, "<resource_guard:{}>", guard.label());
835 }
836 VmValue::McpClient(c) => {
837 let _ = write!(out, "<mcp_client:{}>", c.name);
838 }
839 VmValue::VerdictReceipt(_) => {
843 out.push_str("<verdict_receipt>");
844 }
845 VmValue::Set(items) => {
846 out.push_str("set(");
847 crate::value::recursion::guard_recursion(|| {
848 for (i, item) in items.iter().enumerate() {
849 if i > 0 {
850 out.push_str(", ");
851 }
852 item.write_display(out);
853 }
854 });
855 out.push(')');
856 }
857 VmValue::Generator(g) => {
858 if g.is_done() {
859 out.push_str("<generator (done)>");
860 } else {
861 out.push_str("<generator>");
862 }
863 }
864 VmValue::Stream(s) => {
865 if s.is_done() {
866 out.push_str("<stream (done)>");
867 } else {
868 out.push_str("<stream>");
869 }
870 }
871 VmValue::Range(r) => {
874 let _ = write!(out, "{} to {}", r.start, r.end);
875 if !r.inclusive {
876 out.push_str(" exclusive");
877 }
878 }
879 VmValue::Iter(h) => {
880 if matches!(&*h.lock(), crate::vm::iter::VmIter::Exhausted) {
881 out.push_str("<iter (exhausted)>");
882 } else {
883 out.push_str("<iter>");
884 }
885 }
886 VmValue::Harness(h) => {
887 let _ = write!(out, "<{}>", h.type_name());
888 }
889 VmValue::Pair(p) => {
890 out.push('(');
891 crate::value::recursion::guard_recursion(|| {
892 p.0.write_display(out);
893 out.push_str(", ");
894 p.1.write_display(out);
895 });
896 out.push(')');
897 }
898 }
899 }
900
901 pub fn as_dict(&self) -> Option<&DictMap> {
903 if let VmValue::Dict(d) = self {
904 Some(d)
905 } else {
906 None
907 }
908 }
909
910 pub fn as_int(&self) -> Option<i64> {
911 if let VmValue::Int(n) = self {
912 Some(*n)
913 } else {
914 None
915 }
916 }
917
918 pub fn as_bytes(&self) -> Option<&[u8]> {
919 if let VmValue::Bytes(bytes) = self {
920 Some(bytes.as_slice())
921 } else {
922 None
923 }
924 }
925}
926
927pub fn struct_fields_to_map(
928 layout: &StructLayout,
929 fields: &[Option<VmValue>],
930) -> crate::value::DictMap {
931 layout
932 .field_names()
933 .iter()
934 .enumerate()
935 .filter_map(|(index, name)| {
936 fields
937 .get(index)
938 .and_then(Option::as_ref)
939 .map(|value| (intern_key(name), value.clone()))
940 })
941 .collect()
942}
943
944pub type VmBuiltinFn =
946 Arc<dyn Fn(&[VmValue], &mut String) -> Result<VmValue, VmError> + Send + Sync>;
947
948#[cfg(test)]
949mod runtime_type_tag_tests {
950 use super::VmValue;
951
952 #[test]
956 fn type_name_tags_match_canonical_registry() {
957 let canonical = harn_builtin_meta::runtime_type_tags::ALL;
958 for tag in VmValue::ALL_TYPE_NAMES {
959 assert!(
960 canonical.contains(tag),
961 "VmValue::type_name tag `{tag}` missing from harn_builtin_meta::runtime_type_tags::ALL"
962 );
963 }
964 for tag in canonical {
965 assert!(
966 VmValue::ALL_TYPE_NAMES.contains(tag),
967 "canonical tag `{tag}` is not produced by VmValue::type_name; remove it or update ALL_TYPE_NAMES"
968 );
969 }
970 }
971}