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, VmResourceHandle, VmRngHandle, VmSet, VmStream, VmSyncPermitHandle,
13 VmVerdictReceipt,
14};
15
16pub type VmAsyncBuiltinFn = Arc<
23 dyn Fn(
24 crate::vm::AsyncBuiltinCtx,
25 Vec<VmValue>,
26 ) -> Pin<Box<dyn Future<Output = Result<VmValue, VmError>> + Send>>
27 + Send
28 + Sync,
29>;
30
31type Shared<T> = Arc<T>;
32
33pub type HarnStr = arcstr::ArcStr;
44
45pub type DictMap = imbl::OrdMap<HarnStr, VmValue>;
57
58pub fn intern_key(key: &str) -> HarnStr {
69 const MAX_INTERNED_KEY_LEN: usize = 64;
70 const MAX_INTERNED_KEYS: usize = 8192;
71 static INTERNED_KEYS: std::sync::LazyLock<parking_lot::Mutex<HashMap<Box<str>, HarnStr>>> =
72 std::sync::LazyLock::new(|| parking_lot::Mutex::new(HashMap::new()));
73
74 if key.len() > MAX_INTERNED_KEY_LEN {
75 return HarnStr::from(key);
76 }
77 let mut table = INTERNED_KEYS.lock();
78 if let Some(existing) = table.get(key) {
79 return existing.clone();
80 }
81 let interned = HarnStr::from(key);
82 if table.len() < MAX_INTERNED_KEYS {
83 table.insert(Box::from(key), interned.clone());
84 }
85 interned
86}
87
88pub trait IntoDictKey {
96 fn into_dict_key(self) -> HarnStr;
97}
98
99impl IntoDictKey for String {
100 fn into_dict_key(self) -> HarnStr {
101 intern_key(&self)
102 }
103}
104
105impl IntoDictKey for &str {
106 fn into_dict_key(self) -> HarnStr {
107 intern_key(self)
108 }
109}
110
111impl IntoDictKey for HarnStr {
112 fn into_dict_key(self) -> HarnStr {
113 self
114 }
115}
116
117pub fn string_char_count(text: &str) -> usize {
123 if text.is_ascii() {
124 text.len()
125 } else {
126 text.chars().count()
127 }
128}
129
130pub fn char_to_byte_offset(text: &str, char_index: usize) -> usize {
138 if text.is_ascii() {
139 return char_index.min(text.len());
140 }
141 text.char_indices()
142 .nth(char_index)
143 .map(|(offset, _)| offset)
144 .unwrap_or(text.len())
145}
146
147pub fn char_range_to_byte_range(text: &str, start: usize, end: usize) -> (usize, usize) {
152 let end = end.max(start);
153 if text.is_ascii() {
154 let len = text.len();
155 return (start.min(len), end.min(len));
156 }
157 let mut start_byte = text.len();
160 let mut end_byte = text.len();
161 for (char_index, (offset, _)) in text.char_indices().enumerate() {
162 if char_index == start {
163 start_byte = offset;
164 }
165 if char_index == end {
166 end_byte = offset;
167 break;
168 }
169 }
170 (start_byte, end_byte.max(start_byte))
171}
172
173pub fn byte_offset_to_char_index(text: &str, byte_offset: usize) -> usize {
179 if text.is_ascii() {
180 return byte_offset.min(text.len());
181 }
182 #[expect(
183 clippy::string_slice,
184 reason = "callers pass byte offsets from str searches on the same text"
185 )]
186 let prefix = &text[..byte_offset.min(text.len())];
187 prefix.chars().count()
188}
189
190#[derive(Debug, Clone, PartialEq, Eq)]
192pub struct StructLayout {
193 struct_name: String,
194 field_names: Vec<String>,
195 field_indexes: HashMap<String, usize>,
196}
197
198impl StructLayout {
199 pub fn new(struct_name: impl Into<String>, field_names: Vec<String>) -> Self {
200 let mut deduped = Vec::with_capacity(field_names.len());
201 let mut field_indexes = HashMap::with_capacity(field_names.len());
202 for field_name in field_names {
203 if field_indexes.contains_key(&field_name) {
204 continue;
205 }
206 let index = deduped.len();
207 field_indexes.insert(field_name.clone(), index);
208 deduped.push(field_name);
209 }
210
211 Self {
212 struct_name: struct_name.into(),
213 field_names: deduped,
214 field_indexes,
215 }
216 }
217
218 pub fn from_map(struct_name: impl Into<String>, fields: &crate::value::DictMap) -> Self {
219 Self::new(
220 struct_name,
221 fields.keys().map(|key| key.to_string()).collect(),
222 )
223 }
224
225 pub fn struct_name(&self) -> &str {
226 &self.struct_name
227 }
228
229 pub fn field_names(&self) -> &[String] {
230 &self.field_names
231 }
232
233 pub fn field_index(&self, field_name: &str) -> Option<usize> {
234 if self.field_names.len() <= 8 {
235 return self
236 .field_names
237 .iter()
238 .position(|candidate| candidate == field_name);
239 }
240 self.field_indexes.get(field_name).copied()
241 }
242
243 pub fn with_appended_field(&self, field_name: String) -> Self {
244 if self.field_indexes.contains_key(&field_name) {
245 return self.clone();
246 }
247 let mut field_names = self.field_names.clone();
248 field_names.push(field_name);
249 Self::new(self.struct_name.clone(), field_names)
250 }
251}
252
253#[derive(Debug, Clone)]
255pub struct VmEnumVariant {
256 pub enum_name: HarnStr,
257 pub variant: HarnStr,
258 pub fields: Shared<Vec<VmValue>>,
259}
260
261impl VmEnumVariant {
262 pub fn has_enum_name(&self, enum_name: &str) -> bool {
263 self.enum_name.as_str() == enum_name
264 }
265
266 pub fn is_variant(&self, enum_name: &str, variant: &str) -> bool {
267 self.has_enum_name(enum_name) && self.variant.as_str() == variant
268 }
269}
270
271#[derive(Debug, Clone)]
278pub struct VmBuiltinRefId {
279 pub id: BuiltinId,
280 pub name: HarnStr,
281}
282
283#[derive(Debug, Clone)]
290pub struct StructInstanceData {
291 pub layout: Shared<StructLayout>,
292 pub fields: Shared<Vec<Option<VmValue>>>,
293}
294
295#[derive(Debug, Clone)]
310pub enum VmValue {
311 Int(i64),
312 Float(f64),
313 Decimal(Shared<rust_decimal::Decimal>),
321 String(HarnStr),
322 Bytes(Shared<Vec<u8>>),
323 Bool(bool),
324 Nil,
325 List(Shared<Vec<VmValue>>),
326 Dict(Shared<DictMap>),
327 Closure(Shared<VmClosure>),
328 BuiltinRef(HarnStr),
332 BuiltinRefId(Shared<VmBuiltinRefId>),
337 Duration(i64),
338 EnumVariant(Shared<VmEnumVariant>),
339 StructInstance(Shared<StructInstanceData>),
340 TaskHandle(HarnStr),
341 Channel(Shared<VmChannelHandle>),
342 Atomic(Shared<VmAtomicHandle>),
343 Rng(Shared<VmRngHandle>),
344 SyncPermit(Shared<VmSyncPermitHandle>),
345 Resource(Shared<VmResourceHandle>),
349 ResourceGuard(Shared<VmResourceGuardHandle>),
350 McpClient(Shared<VmMcpClientHandle>),
351 VerdictReceipt(Shared<VmVerdictReceipt>),
355 Set(Shared<VmSet>),
356 Generator(Shared<VmGenerator>),
357 Stream(Shared<VmStream>),
358 Range(Shared<VmRange>),
362 Iter(crate::vm::iter::VmIterHandle),
364 Pair(Shared<(VmValue, VmValue)>),
369 Harness(Shared<VmHarness>),
374}
375
376static ASCII_CHAR_STRINGS: std::sync::LazyLock<[HarnStr; 128]> = std::sync::LazyLock::new(|| {
385 std::array::from_fn(|byte| {
386 let mut buffer = [0u8; 4];
387 HarnStr::from((byte as u8 as char).encode_utf8(&mut buffer))
388 })
389});
390
391impl VmValue {
392 pub fn string(value: impl AsRef<str>) -> Self {
400 VmValue::String(HarnStr::from(value.as_ref()))
401 }
402
403 pub fn decimal(value: rust_decimal::Decimal) -> Self {
408 VmValue::Decimal(Shared::new(value))
409 }
410
411 pub fn char_value(ch: char) -> Self {
415 if ch.is_ascii() {
416 return VmValue::String(ASCII_CHAR_STRINGS[ch as usize].clone());
417 }
418 let mut buffer = [0u8; 4];
419 VmValue::String(HarnStr::from(ch.encode_utf8(&mut buffer)))
420 }
421
422 pub fn chars_list(text: &str) -> Self {
427 VmValue::List(Shared::new(text.chars().map(VmValue::char_value).collect()))
428 }
429
430 pub fn enum_variant(
431 enum_name: impl Into<HarnStr>,
432 variant: impl Into<HarnStr>,
433 fields: Vec<VmValue>,
434 ) -> Self {
435 VmValue::EnumVariant(Shared::new(VmEnumVariant {
436 enum_name: enum_name.into(),
437 variant: variant.into(),
438 fields: Shared::new(fields),
439 }))
440 }
441
442 pub fn task_handle(id: impl Into<HarnStr>) -> Self {
443 VmValue::TaskHandle(id.into())
444 }
445
446 pub fn range(range: VmRange) -> Self {
448 VmValue::Range(Shared::new(range))
449 }
450
451 pub fn builtin_ref_id(id: BuiltinId, name: impl Into<HarnStr>) -> Self {
453 VmValue::BuiltinRefId(Shared::new(VmBuiltinRefId {
454 id,
455 name: name.into(),
456 }))
457 }
458
459 pub fn dict<K: IntoDictKey>(entries: impl IntoIterator<Item = (K, VmValue)>) -> Self {
465 VmValue::Dict(Shared::new(
466 entries
467 .into_iter()
468 .map(|(k, v)| (k.into_dict_key(), v))
469 .collect::<DictMap>(),
470 ))
471 }
472
473 pub fn dict_map(map: DictMap) -> Self {
475 VmValue::Dict(Shared::new(map))
476 }
477
478 pub fn set(values: impl IntoIterator<Item = VmValue>) -> Self {
481 VmValue::Set(Shared::new(values.into_iter().collect::<VmSet>()))
482 }
483
484 pub fn set_value(set: VmSet) -> Self {
486 VmValue::Set(Shared::new(set))
487 }
488
489 pub fn channel(handle: VmChannelHandle) -> Self {
490 VmValue::Channel(Shared::new(handle))
491 }
492
493 pub fn atomic(handle: VmAtomicHandle) -> Self {
494 VmValue::Atomic(Shared::new(handle))
495 }
496
497 pub fn rng(handle: VmRngHandle) -> Self {
498 VmValue::Rng(Shared::new(handle))
499 }
500
501 pub fn sync_permit(handle: VmSyncPermitHandle) -> Self {
502 VmValue::SyncPermit(Shared::new(handle))
503 }
504
505 pub fn resource(handle: VmResourceHandle) -> Self {
506 VmValue::Resource(Shared::new(handle))
507 }
508
509 pub fn resource_guard(handle: VmResourceGuardHandle) -> Self {
510 VmValue::ResourceGuard(Shared::new(handle))
511 }
512
513 pub fn mcp_client(handle: VmMcpClientHandle) -> Self {
514 VmValue::McpClient(Shared::new(handle))
515 }
516
517 pub fn verdict_receipt(receipt: VmVerdictReceipt) -> Self {
521 VmValue::VerdictReceipt(Shared::new(receipt))
522 }
523
524 pub fn generator(generator: VmGenerator) -> Self {
525 VmValue::Generator(Shared::new(generator))
526 }
527
528 pub fn stream(stream: VmStream) -> Self {
529 VmValue::Stream(Shared::new(stream))
530 }
531
532 pub fn harness(handle: VmHarness) -> Self {
533 VmValue::Harness(Shared::new(handle))
534 }
535
536 pub fn struct_instance(
537 struct_name: impl Into<Shared<str>>,
538 fields: crate::value::DictMap,
539 ) -> Self {
540 Self::struct_instance_from_map(struct_name.into().to_string(), fields)
541 }
542
543 pub fn is_truthy(&self) -> bool {
544 match self {
545 VmValue::Bool(b) => *b,
546 VmValue::Nil => false,
547 VmValue::Int(n) => *n != 0,
548 VmValue::Float(n) => *n != 0.0,
549 VmValue::Decimal(d) => **d != rust_decimal::Decimal::ZERO,
550 VmValue::String(s) => !s.is_empty(),
551 VmValue::Bytes(bytes) => !bytes.is_empty(),
552 VmValue::List(l) => !l.is_empty(),
553 VmValue::Dict(d) => !d.is_empty(),
554 VmValue::Closure(_) => true,
555 VmValue::BuiltinRef(_) => true,
556 VmValue::BuiltinRefId(_) => true,
557 VmValue::Duration(ms) => *ms != 0,
558 VmValue::EnumVariant(_) => true,
559 VmValue::StructInstance(_) => true,
560 VmValue::TaskHandle(_) => true,
561 VmValue::Channel(_) => true,
562 VmValue::Atomic(_) => true,
563 VmValue::Rng(_) => true,
564 VmValue::SyncPermit(_) => true,
565 VmValue::Resource(_) => true,
566 VmValue::ResourceGuard(_) => true,
567 VmValue::McpClient(_) => true,
568 VmValue::VerdictReceipt(_) => true,
569 VmValue::Set(s) => !s.is_empty(),
570 VmValue::Generator(_) => true,
571 VmValue::Stream(_) => true,
572 VmValue::Range(_) => true,
575 VmValue::Iter(_) => true,
576 VmValue::Pair(_) => true,
577 VmValue::Harness(_) => true,
578 }
579 }
580
581 pub const ALL_TYPE_NAMES: &'static [&'static str] = &[
587 "string",
588 "bytes",
589 "int",
590 "float",
591 "decimal",
592 "bool",
593 "nil",
594 "list",
595 "dict",
596 "closure",
597 "builtin",
598 "duration",
599 "enum",
600 "struct",
601 "task_handle",
602 "channel",
603 "atomic",
604 "rng",
605 "sync_permit",
606 "resource",
607 "resource_guard",
608 "mcp_client",
609 "verdict_receipt",
610 "set",
611 "generator",
612 "stream",
613 "range",
614 "iter",
615 "pair",
616 ];
617
618 pub fn type_name(&self) -> &'static str {
619 match self {
620 VmValue::String(_) => "string",
621 VmValue::Bytes(_) => "bytes",
622 VmValue::Int(_) => "int",
623 VmValue::Float(_) => "float",
624 VmValue::Decimal(_) => "decimal",
625 VmValue::Bool(_) => "bool",
626 VmValue::Nil => "nil",
627 VmValue::List(_) => "list",
628 VmValue::Dict(_) => "dict",
629 VmValue::Closure(_) => "closure",
630 VmValue::BuiltinRef(_) => "builtin",
631 VmValue::BuiltinRefId(_) => "builtin",
632 VmValue::Duration(_) => "duration",
633 VmValue::EnumVariant(_) => "enum",
634 VmValue::StructInstance(_) => "struct",
635 VmValue::TaskHandle(_) => "task_handle",
636 VmValue::Channel(_) => "channel",
637 VmValue::Atomic(_) => "atomic",
638 VmValue::Rng(_) => "rng",
639 VmValue::SyncPermit(_) => "sync_permit",
640 VmValue::Resource(_) => "resource",
641 VmValue::ResourceGuard(_) => "resource_guard",
642 VmValue::McpClient(_) => "mcp_client",
643 VmValue::VerdictReceipt(_) => "verdict_receipt",
644 VmValue::Set(_) => "set",
645 VmValue::Generator(_) => "generator",
646 VmValue::Stream(_) => "stream",
647 VmValue::Range(_) => "range",
648 VmValue::Iter(_) => "iter",
649 VmValue::Pair(_) => "pair",
650 VmValue::Harness(h) => h.type_name(),
651 }
652 }
653
654 pub fn as_str_cow(&self) -> std::borrow::Cow<'_, str> {
660 match self {
661 VmValue::String(s) => std::borrow::Cow::Borrowed(s.as_str()),
662 other => std::borrow::Cow::Owned(other.display()),
663 }
664 }
665
666 pub fn struct_data(&self) -> Option<&StructInstanceData> {
670 match self {
671 VmValue::StructInstance(data) => Some(data),
672 _ => None,
673 }
674 }
675
676 pub fn struct_name(&self) -> Option<&str> {
677 match self {
678 VmValue::StructInstance(data) => Some(data.layout.struct_name()),
679 _ => None,
680 }
681 }
682
683 pub fn struct_field(&self, field_name: &str) -> Option<&VmValue> {
684 match self {
685 VmValue::StructInstance(data) => data
686 .layout
687 .field_index(field_name)
688 .and_then(|index| data.fields.get(index))
689 .and_then(Option::as_ref),
690 _ => None,
691 }
692 }
693
694 pub fn struct_fields_map(&self) -> Option<crate::value::DictMap> {
695 match self {
696 VmValue::StructInstance(data) => Some(struct_fields_to_map(&data.layout, &data.fields)),
697 _ => None,
698 }
699 }
700
701 pub fn struct_instance_from_map(
702 struct_name: impl Into<String>,
703 fields: crate::value::DictMap,
704 ) -> Self {
705 let layout = Shared::new(StructLayout::from_map(struct_name, &fields));
706 let slots = layout
707 .field_names()
708 .iter()
709 .map(|name| fields.get(name.as_str()).cloned())
710 .collect();
711 VmValue::StructInstance(Shared::new(StructInstanceData {
712 layout,
713 fields: Shared::new(slots),
714 }))
715 }
716
717 pub fn struct_instance_with_layout(
718 struct_name: impl Into<String>,
719 field_names: Vec<String>,
720 field_values: crate::value::DictMap,
721 ) -> Self {
722 let layout = Shared::new(StructLayout::new(struct_name, field_names));
723 let fields = layout
724 .field_names()
725 .iter()
726 .map(|name| field_values.get(name.as_str()).cloned())
727 .collect();
728 VmValue::StructInstance(Shared::new(StructInstanceData {
729 layout,
730 fields: Shared::new(fields),
731 }))
732 }
733
734 pub fn struct_instance_with_property(&self, field_name: &str, value: VmValue) -> Option<Self> {
735 let VmValue::StructInstance(data) = self else {
736 return None;
737 };
738 let (layout, fields) = (&data.layout, &data.fields);
739
740 let mut new_fields = fields.as_ref().clone();
741 let layout = match layout.field_index(field_name) {
742 Some(index) => {
743 if index >= new_fields.len() {
744 new_fields.resize(index + 1, None);
745 }
746 new_fields[index] = Some(value);
747 Shared::clone(layout)
748 }
749 None => {
750 let new_layout = Shared::new(layout.with_appended_field(field_name.to_string()));
751 new_fields.push(Some(value));
752 new_layout
753 }
754 };
755
756 Some(VmValue::StructInstance(Shared::new(StructInstanceData {
757 layout,
758 fields: Shared::new(new_fields),
759 })))
760 }
761
762 pub fn display(&self) -> String {
763 let mut out = String::new();
764 self.write_display(&mut out);
765 out
766 }
767
768 pub fn write_display(&self, out: &mut String) {
771 use std::fmt::Write;
772
773 match self {
774 VmValue::Int(n) => {
775 let _ = write!(out, "{n}");
776 }
777 VmValue::Float(n) => {
778 if *n == (*n as i64) as f64 && n.abs() < 1e15 {
779 let _ = write!(out, "{n:.1}");
780 } else {
781 let _ = write!(out, "{n}");
782 }
783 }
784 VmValue::Decimal(d) => {
789 let _ = write!(out, "{d}");
790 }
791 VmValue::String(s) => out.push_str(s),
792 VmValue::Bytes(bytes) => {
793 const MAX_PREVIEW_BYTES: usize = 32;
794
795 out.push_str("b\"");
796 for byte in bytes.iter().take(MAX_PREVIEW_BYTES) {
797 let _ = write!(out, "{byte:02x}");
798 }
799 if bytes.len() > MAX_PREVIEW_BYTES {
800 let _ = write!(out, "...+{}", bytes.len() - MAX_PREVIEW_BYTES);
801 }
802 out.push('"');
803 }
804 VmValue::Bool(b) => out.push_str(if *b { "true" } else { "false" }),
805 VmValue::Nil => out.push_str("nil"),
806 VmValue::List(items) => {
807 out.push('[');
808 crate::value::recursion::guard_recursion(|| {
809 for (i, item) in items.iter().enumerate() {
810 if i > 0 {
811 out.push_str(", ");
812 }
813 item.write_display(out);
814 }
815 });
816 out.push(']');
817 }
818 VmValue::Dict(map) => {
819 out.push('{');
820 crate::value::recursion::guard_recursion(|| {
821 for (i, (k, v)) in map.iter().enumerate() {
822 if i > 0 {
823 out.push_str(", ");
824 }
825 out.push_str(k);
826 out.push_str(": ");
827 v.write_display(out);
828 }
829 });
830 out.push('}');
831 }
832 VmValue::Closure(c) => {
833 let names: Vec<&str> = c.func.param_names().collect();
834 let _ = write!(out, "<fn({})>", names.join(", "));
835 }
836 VmValue::BuiltinRef(name) => {
837 let _ = write!(out, "<builtin {name}>");
838 }
839 VmValue::BuiltinRefId(r) => {
840 let _ = write!(out, "<builtin {}>", r.name);
841 }
842 VmValue::Duration(ms) => {
843 let sign = if *ms < 0 { "-" } else { "" };
844 let abs_ms = ms.unsigned_abs();
845 if abs_ms >= 604_800_000 && abs_ms % 604_800_000 == 0 {
846 let _ = write!(out, "{}{}w", sign, abs_ms / 604_800_000);
847 } else if abs_ms >= 86_400_000 && abs_ms % 86_400_000 == 0 {
848 let _ = write!(out, "{}{}d", sign, abs_ms / 86_400_000);
849 } else if abs_ms >= 3_600_000 && abs_ms % 3_600_000 == 0 {
850 let _ = write!(out, "{}{}h", sign, abs_ms / 3_600_000);
851 } else if abs_ms >= 60_000 && abs_ms % 60_000 == 0 {
852 let _ = write!(out, "{}{}m", sign, abs_ms / 60_000);
853 } else if abs_ms >= 1000 && abs_ms % 1000 == 0 {
854 let _ = write!(out, "{}{}s", sign, abs_ms / 1000);
855 } else {
856 let _ = write!(out, "{sign}{abs_ms}ms");
857 }
858 }
859 VmValue::EnumVariant(enum_variant) => {
860 if enum_variant.fields.is_empty() {
861 let _ = write!(out, "{}.{}", enum_variant.enum_name, enum_variant.variant);
862 } else {
863 let _ = write!(out, "{}.{}(", enum_variant.enum_name, enum_variant.variant);
864 crate::value::recursion::guard_recursion(|| {
865 for (i, v) in enum_variant.fields.iter().enumerate() {
866 if i > 0 {
867 out.push_str(", ");
868 }
869 v.write_display(out);
870 }
871 });
872 out.push(')');
873 }
874 }
875 VmValue::StructInstance(data) => {
876 let (layout, fields) = (&data.layout, &data.fields);
877 let _ = write!(out, "{} {{", layout.struct_name());
878 crate::value::recursion::guard_recursion(|| {
879 for (i, (k, v)) in struct_fields_to_map(layout, fields).iter().enumerate() {
880 if i > 0 {
881 out.push_str(", ");
882 }
883 out.push_str(k);
884 out.push_str(": ");
885 v.write_display(out);
886 }
887 });
888 out.push('}');
889 }
890 VmValue::TaskHandle(id) => {
891 let _ = write!(out, "<task:{id}>");
892 }
893 VmValue::Channel(ch) => {
894 let _ = write!(out, "<channel:{}>", ch.name);
895 }
896 VmValue::Atomic(a) => {
897 let _ = write!(out, "<atomic:{}>", a.value.load(Ordering::SeqCst));
898 }
899 VmValue::Rng(_) => {
900 out.push_str("<rng>");
901 }
902 VmValue::SyncPermit(p) => {
903 let _ = write!(out, "<sync_permit:{}:{}>", p.kind(), p.key());
904 }
905 VmValue::Resource(resource) => {
906 let _ = write!(out, "<resource:{}>", resource.label());
907 }
908 VmValue::ResourceGuard(guard) => {
909 let _ = write!(out, "<resource_guard:{}>", guard.label());
910 }
911 VmValue::McpClient(c) => {
912 let _ = write!(out, "<mcp_client:{}>", c.name);
913 }
914 VmValue::VerdictReceipt(_) => {
918 out.push_str("<verdict_receipt>");
919 }
920 VmValue::Set(items) => {
921 out.push_str("set(");
922 crate::value::recursion::guard_recursion(|| {
923 for (i, item) in items.iter().enumerate() {
924 if i > 0 {
925 out.push_str(", ");
926 }
927 item.write_display(out);
928 }
929 });
930 out.push(')');
931 }
932 VmValue::Generator(g) => {
933 if g.is_done() {
934 out.push_str("<generator (done)>");
935 } else {
936 out.push_str("<generator>");
937 }
938 }
939 VmValue::Stream(s) => {
940 if s.is_done() {
941 out.push_str("<stream (done)>");
942 } else {
943 out.push_str("<stream>");
944 }
945 }
946 VmValue::Range(r) => {
949 let _ = write!(out, "{} to {}", r.start, r.end);
950 if !r.inclusive {
951 out.push_str(" exclusive");
952 }
953 }
954 VmValue::Iter(h) => {
955 if matches!(&*h.lock(), crate::vm::iter::VmIter::Exhausted) {
956 out.push_str("<iter (exhausted)>");
957 } else {
958 out.push_str("<iter>");
959 }
960 }
961 VmValue::Harness(h) => {
962 let _ = write!(out, "<{}>", h.type_name());
963 }
964 VmValue::Pair(p) => {
965 out.push('(');
966 crate::value::recursion::guard_recursion(|| {
967 p.0.write_display(out);
968 out.push_str(", ");
969 p.1.write_display(out);
970 });
971 out.push(')');
972 }
973 }
974 }
975
976 pub fn as_dict(&self) -> Option<&DictMap> {
978 if let VmValue::Dict(d) = self {
979 Some(d)
980 } else {
981 None
982 }
983 }
984
985 pub fn as_int(&self) -> Option<i64> {
986 if let VmValue::Int(n) = self {
987 Some(*n)
988 } else {
989 None
990 }
991 }
992
993 pub fn as_bytes(&self) -> Option<&[u8]> {
994 if let VmValue::Bytes(bytes) = self {
995 Some(bytes.as_slice())
996 } else {
997 None
998 }
999 }
1000}
1001
1002pub fn struct_fields_to_map(
1003 layout: &StructLayout,
1004 fields: &[Option<VmValue>],
1005) -> crate::value::DictMap {
1006 layout
1007 .field_names()
1008 .iter()
1009 .enumerate()
1010 .filter_map(|(index, name)| {
1011 fields
1012 .get(index)
1013 .and_then(Option::as_ref)
1014 .map(|value| (intern_key(name), value.clone()))
1015 })
1016 .collect()
1017}
1018
1019pub type VmBuiltinFn =
1021 Arc<dyn Fn(&[VmValue], &mut String) -> Result<VmValue, VmError> + Send + Sync>;
1022
1023#[cfg(test)]
1024mod runtime_type_tag_tests {
1025 use super::VmValue;
1026
1027 #[test]
1031 fn type_name_tags_match_canonical_registry() {
1032 let canonical = harn_builtin_meta::runtime_type_tags::ALL;
1033 for tag in VmValue::ALL_TYPE_NAMES {
1034 assert!(
1035 canonical.contains(tag),
1036 "VmValue::type_name tag `{tag}` missing from harn_builtin_meta::runtime_type_tags::ALL"
1037 );
1038 }
1039 for tag in canonical {
1040 assert!(
1041 VmValue::ALL_TYPE_NAMES.contains(tag),
1042 "canonical tag `{tag}` is not produced by VmValue::type_name; remove it or update ALL_TYPE_NAMES"
1043 );
1044 }
1045 }
1046}