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 text[..byte_offset.min(text.len())].chars().count()
183}
184
185#[derive(Debug, Clone, PartialEq, Eq)]
187pub struct StructLayout {
188 struct_name: String,
189 field_names: Vec<String>,
190 field_indexes: HashMap<String, usize>,
191}
192
193impl StructLayout {
194 pub fn new(struct_name: impl Into<String>, field_names: Vec<String>) -> Self {
195 let mut deduped = Vec::with_capacity(field_names.len());
196 let mut field_indexes = HashMap::with_capacity(field_names.len());
197 for field_name in field_names {
198 if field_indexes.contains_key(&field_name) {
199 continue;
200 }
201 let index = deduped.len();
202 field_indexes.insert(field_name.clone(), index);
203 deduped.push(field_name);
204 }
205
206 Self {
207 struct_name: struct_name.into(),
208 field_names: deduped,
209 field_indexes,
210 }
211 }
212
213 pub fn from_map(struct_name: impl Into<String>, fields: &crate::value::DictMap) -> Self {
214 Self::new(
215 struct_name,
216 fields.keys().map(|key| key.to_string()).collect(),
217 )
218 }
219
220 pub fn struct_name(&self) -> &str {
221 &self.struct_name
222 }
223
224 pub fn field_names(&self) -> &[String] {
225 &self.field_names
226 }
227
228 pub fn field_index(&self, field_name: &str) -> Option<usize> {
229 if self.field_names.len() <= 8 {
230 return self
231 .field_names
232 .iter()
233 .position(|candidate| candidate == field_name);
234 }
235 self.field_indexes.get(field_name).copied()
236 }
237
238 pub fn with_appended_field(&self, field_name: String) -> Self {
239 if self.field_indexes.contains_key(&field_name) {
240 return self.clone();
241 }
242 let mut field_names = self.field_names.clone();
243 field_names.push(field_name);
244 Self::new(self.struct_name.clone(), field_names)
245 }
246}
247
248#[derive(Debug, Clone)]
250pub struct VmEnumVariant {
251 pub enum_name: HarnStr,
252 pub variant: HarnStr,
253 pub fields: Shared<Vec<VmValue>>,
254}
255
256impl VmEnumVariant {
257 pub fn has_enum_name(&self, enum_name: &str) -> bool {
258 self.enum_name.as_str() == enum_name
259 }
260
261 pub fn is_variant(&self, enum_name: &str, variant: &str) -> bool {
262 self.has_enum_name(enum_name) && self.variant.as_str() == variant
263 }
264}
265
266#[derive(Debug, Clone)]
273pub struct VmBuiltinRefId {
274 pub id: BuiltinId,
275 pub name: HarnStr,
276}
277
278#[derive(Debug, Clone)]
285pub struct StructInstanceData {
286 pub layout: Shared<StructLayout>,
287 pub fields: Shared<Vec<Option<VmValue>>>,
288}
289
290#[derive(Debug, Clone)]
305pub enum VmValue {
306 Int(i64),
307 Float(f64),
308 Decimal(Shared<rust_decimal::Decimal>),
316 String(HarnStr),
317 Bytes(Shared<Vec<u8>>),
318 Bool(bool),
319 Nil,
320 List(Shared<Vec<VmValue>>),
321 Dict(Shared<DictMap>),
322 Closure(Shared<VmClosure>),
323 BuiltinRef(HarnStr),
327 BuiltinRefId(Shared<VmBuiltinRefId>),
332 Duration(i64),
333 EnumVariant(Shared<VmEnumVariant>),
334 StructInstance(Shared<StructInstanceData>),
335 TaskHandle(HarnStr),
336 Channel(Shared<VmChannelHandle>),
337 Atomic(Shared<VmAtomicHandle>),
338 Rng(Shared<VmRngHandle>),
339 SyncPermit(Shared<VmSyncPermitHandle>),
340 Resource(Shared<VmResourceHandle>),
344 ResourceGuard(Shared<VmResourceGuardHandle>),
345 McpClient(Shared<VmMcpClientHandle>),
346 VerdictReceipt(Shared<VmVerdictReceipt>),
350 Set(Shared<VmSet>),
351 Generator(Shared<VmGenerator>),
352 Stream(Shared<VmStream>),
353 Range(Shared<VmRange>),
357 Iter(crate::vm::iter::VmIterHandle),
359 Pair(Shared<(VmValue, VmValue)>),
364 Harness(Shared<VmHarness>),
369}
370
371static ASCII_CHAR_STRINGS: std::sync::LazyLock<[HarnStr; 128]> = std::sync::LazyLock::new(|| {
380 std::array::from_fn(|byte| {
381 let mut buffer = [0u8; 4];
382 HarnStr::from((byte as u8 as char).encode_utf8(&mut buffer))
383 })
384});
385
386impl VmValue {
387 pub fn string(value: impl AsRef<str>) -> Self {
395 VmValue::String(HarnStr::from(value.as_ref()))
396 }
397
398 pub fn decimal(value: rust_decimal::Decimal) -> Self {
403 VmValue::Decimal(Shared::new(value))
404 }
405
406 pub fn char_value(ch: char) -> Self {
410 if ch.is_ascii() {
411 return VmValue::String(ASCII_CHAR_STRINGS[ch as usize].clone());
412 }
413 let mut buffer = [0u8; 4];
414 VmValue::String(HarnStr::from(ch.encode_utf8(&mut buffer)))
415 }
416
417 pub fn chars_list(text: &str) -> Self {
422 VmValue::List(Shared::new(text.chars().map(VmValue::char_value).collect()))
423 }
424
425 pub fn enum_variant(
426 enum_name: impl Into<HarnStr>,
427 variant: impl Into<HarnStr>,
428 fields: Vec<VmValue>,
429 ) -> Self {
430 VmValue::EnumVariant(Shared::new(VmEnumVariant {
431 enum_name: enum_name.into(),
432 variant: variant.into(),
433 fields: Shared::new(fields),
434 }))
435 }
436
437 pub fn task_handle(id: impl Into<HarnStr>) -> Self {
438 VmValue::TaskHandle(id.into())
439 }
440
441 pub fn range(range: VmRange) -> Self {
443 VmValue::Range(Shared::new(range))
444 }
445
446 pub fn builtin_ref_id(id: BuiltinId, name: impl Into<HarnStr>) -> Self {
448 VmValue::BuiltinRefId(Shared::new(VmBuiltinRefId {
449 id,
450 name: name.into(),
451 }))
452 }
453
454 pub fn dict<K: IntoDictKey>(entries: impl IntoIterator<Item = (K, VmValue)>) -> Self {
460 VmValue::Dict(Shared::new(
461 entries
462 .into_iter()
463 .map(|(k, v)| (k.into_dict_key(), v))
464 .collect::<DictMap>(),
465 ))
466 }
467
468 pub fn dict_map(map: DictMap) -> Self {
470 VmValue::Dict(Shared::new(map))
471 }
472
473 pub fn set(values: impl IntoIterator<Item = VmValue>) -> Self {
476 VmValue::Set(Shared::new(values.into_iter().collect::<VmSet>()))
477 }
478
479 pub fn set_value(set: VmSet) -> Self {
481 VmValue::Set(Shared::new(set))
482 }
483
484 pub fn channel(handle: VmChannelHandle) -> Self {
485 VmValue::Channel(Shared::new(handle))
486 }
487
488 pub fn atomic(handle: VmAtomicHandle) -> Self {
489 VmValue::Atomic(Shared::new(handle))
490 }
491
492 pub fn rng(handle: VmRngHandle) -> Self {
493 VmValue::Rng(Shared::new(handle))
494 }
495
496 pub fn sync_permit(handle: VmSyncPermitHandle) -> Self {
497 VmValue::SyncPermit(Shared::new(handle))
498 }
499
500 pub fn resource(handle: VmResourceHandle) -> Self {
501 VmValue::Resource(Shared::new(handle))
502 }
503
504 pub fn resource_guard(handle: VmResourceGuardHandle) -> Self {
505 VmValue::ResourceGuard(Shared::new(handle))
506 }
507
508 pub fn mcp_client(handle: VmMcpClientHandle) -> Self {
509 VmValue::McpClient(Shared::new(handle))
510 }
511
512 pub fn verdict_receipt(receipt: VmVerdictReceipt) -> Self {
516 VmValue::VerdictReceipt(Shared::new(receipt))
517 }
518
519 pub fn generator(generator: VmGenerator) -> Self {
520 VmValue::Generator(Shared::new(generator))
521 }
522
523 pub fn stream(stream: VmStream) -> Self {
524 VmValue::Stream(Shared::new(stream))
525 }
526
527 pub fn harness(handle: VmHarness) -> Self {
528 VmValue::Harness(Shared::new(handle))
529 }
530
531 pub fn struct_instance(
532 struct_name: impl Into<Shared<str>>,
533 fields: crate::value::DictMap,
534 ) -> Self {
535 Self::struct_instance_from_map(struct_name.into().to_string(), fields)
536 }
537
538 pub fn is_truthy(&self) -> bool {
539 match self {
540 VmValue::Bool(b) => *b,
541 VmValue::Nil => false,
542 VmValue::Int(n) => *n != 0,
543 VmValue::Float(n) => *n != 0.0,
544 VmValue::Decimal(d) => **d != rust_decimal::Decimal::ZERO,
545 VmValue::String(s) => !s.is_empty(),
546 VmValue::Bytes(bytes) => !bytes.is_empty(),
547 VmValue::List(l) => !l.is_empty(),
548 VmValue::Dict(d) => !d.is_empty(),
549 VmValue::Closure(_) => true,
550 VmValue::BuiltinRef(_) => true,
551 VmValue::BuiltinRefId(_) => true,
552 VmValue::Duration(ms) => *ms != 0,
553 VmValue::EnumVariant(_) => true,
554 VmValue::StructInstance(_) => true,
555 VmValue::TaskHandle(_) => true,
556 VmValue::Channel(_) => true,
557 VmValue::Atomic(_) => true,
558 VmValue::Rng(_) => true,
559 VmValue::SyncPermit(_) => true,
560 VmValue::Resource(_) => true,
561 VmValue::ResourceGuard(_) => true,
562 VmValue::McpClient(_) => true,
563 VmValue::VerdictReceipt(_) => true,
564 VmValue::Set(s) => !s.is_empty(),
565 VmValue::Generator(_) => true,
566 VmValue::Stream(_) => true,
567 VmValue::Range(_) => true,
570 VmValue::Iter(_) => true,
571 VmValue::Pair(_) => true,
572 VmValue::Harness(_) => true,
573 }
574 }
575
576 pub const ALL_TYPE_NAMES: &'static [&'static str] = &[
582 "string",
583 "bytes",
584 "int",
585 "float",
586 "decimal",
587 "bool",
588 "nil",
589 "list",
590 "dict",
591 "closure",
592 "builtin",
593 "duration",
594 "enum",
595 "struct",
596 "task_handle",
597 "channel",
598 "atomic",
599 "rng",
600 "sync_permit",
601 "resource",
602 "resource_guard",
603 "mcp_client",
604 "verdict_receipt",
605 "set",
606 "generator",
607 "stream",
608 "range",
609 "iter",
610 "pair",
611 ];
612
613 pub fn type_name(&self) -> &'static str {
614 match self {
615 VmValue::String(_) => "string",
616 VmValue::Bytes(_) => "bytes",
617 VmValue::Int(_) => "int",
618 VmValue::Float(_) => "float",
619 VmValue::Decimal(_) => "decimal",
620 VmValue::Bool(_) => "bool",
621 VmValue::Nil => "nil",
622 VmValue::List(_) => "list",
623 VmValue::Dict(_) => "dict",
624 VmValue::Closure(_) => "closure",
625 VmValue::BuiltinRef(_) => "builtin",
626 VmValue::BuiltinRefId(_) => "builtin",
627 VmValue::Duration(_) => "duration",
628 VmValue::EnumVariant(_) => "enum",
629 VmValue::StructInstance(_) => "struct",
630 VmValue::TaskHandle(_) => "task_handle",
631 VmValue::Channel(_) => "channel",
632 VmValue::Atomic(_) => "atomic",
633 VmValue::Rng(_) => "rng",
634 VmValue::SyncPermit(_) => "sync_permit",
635 VmValue::Resource(_) => "resource",
636 VmValue::ResourceGuard(_) => "resource_guard",
637 VmValue::McpClient(_) => "mcp_client",
638 VmValue::VerdictReceipt(_) => "verdict_receipt",
639 VmValue::Set(_) => "set",
640 VmValue::Generator(_) => "generator",
641 VmValue::Stream(_) => "stream",
642 VmValue::Range(_) => "range",
643 VmValue::Iter(_) => "iter",
644 VmValue::Pair(_) => "pair",
645 VmValue::Harness(h) => h.type_name(),
646 }
647 }
648
649 pub fn as_str_cow(&self) -> std::borrow::Cow<'_, str> {
655 match self {
656 VmValue::String(s) => std::borrow::Cow::Borrowed(s.as_str()),
657 other => std::borrow::Cow::Owned(other.display()),
658 }
659 }
660
661 pub fn struct_data(&self) -> Option<&StructInstanceData> {
665 match self {
666 VmValue::StructInstance(data) => Some(data),
667 _ => None,
668 }
669 }
670
671 pub fn struct_name(&self) -> Option<&str> {
672 match self {
673 VmValue::StructInstance(data) => Some(data.layout.struct_name()),
674 _ => None,
675 }
676 }
677
678 pub fn struct_field(&self, field_name: &str) -> Option<&VmValue> {
679 match self {
680 VmValue::StructInstance(data) => data
681 .layout
682 .field_index(field_name)
683 .and_then(|index| data.fields.get(index))
684 .and_then(Option::as_ref),
685 _ => None,
686 }
687 }
688
689 pub fn struct_fields_map(&self) -> Option<crate::value::DictMap> {
690 match self {
691 VmValue::StructInstance(data) => Some(struct_fields_to_map(&data.layout, &data.fields)),
692 _ => None,
693 }
694 }
695
696 pub fn struct_instance_from_map(
697 struct_name: impl Into<String>,
698 fields: crate::value::DictMap,
699 ) -> Self {
700 let layout = Shared::new(StructLayout::from_map(struct_name, &fields));
701 let slots = layout
702 .field_names()
703 .iter()
704 .map(|name| fields.get(name.as_str()).cloned())
705 .collect();
706 VmValue::StructInstance(Shared::new(StructInstanceData {
707 layout,
708 fields: Shared::new(slots),
709 }))
710 }
711
712 pub fn struct_instance_with_layout(
713 struct_name: impl Into<String>,
714 field_names: Vec<String>,
715 field_values: crate::value::DictMap,
716 ) -> Self {
717 let layout = Shared::new(StructLayout::new(struct_name, field_names));
718 let fields = layout
719 .field_names()
720 .iter()
721 .map(|name| field_values.get(name.as_str()).cloned())
722 .collect();
723 VmValue::StructInstance(Shared::new(StructInstanceData {
724 layout,
725 fields: Shared::new(fields),
726 }))
727 }
728
729 pub fn struct_instance_with_property(&self, field_name: &str, value: VmValue) -> Option<Self> {
730 let VmValue::StructInstance(data) = self else {
731 return None;
732 };
733 let (layout, fields) = (&data.layout, &data.fields);
734
735 let mut new_fields = fields.as_ref().clone();
736 let layout = match layout.field_index(field_name) {
737 Some(index) => {
738 if index >= new_fields.len() {
739 new_fields.resize(index + 1, None);
740 }
741 new_fields[index] = Some(value);
742 Shared::clone(layout)
743 }
744 None => {
745 let new_layout = Shared::new(layout.with_appended_field(field_name.to_string()));
746 new_fields.push(Some(value));
747 new_layout
748 }
749 };
750
751 Some(VmValue::StructInstance(Shared::new(StructInstanceData {
752 layout,
753 fields: Shared::new(new_fields),
754 })))
755 }
756
757 pub fn display(&self) -> String {
758 let mut out = String::new();
759 self.write_display(&mut out);
760 out
761 }
762
763 pub fn write_display(&self, out: &mut String) {
766 use std::fmt::Write;
767
768 match self {
769 VmValue::Int(n) => {
770 let _ = write!(out, "{n}");
771 }
772 VmValue::Float(n) => {
773 if *n == (*n as i64) as f64 && n.abs() < 1e15 {
774 let _ = write!(out, "{n:.1}");
775 } else {
776 let _ = write!(out, "{n}");
777 }
778 }
779 VmValue::Decimal(d) => {
784 let _ = write!(out, "{d}");
785 }
786 VmValue::String(s) => out.push_str(s),
787 VmValue::Bytes(bytes) => {
788 const MAX_PREVIEW_BYTES: usize = 32;
789
790 out.push_str("b\"");
791 for byte in bytes.iter().take(MAX_PREVIEW_BYTES) {
792 let _ = write!(out, "{byte:02x}");
793 }
794 if bytes.len() > MAX_PREVIEW_BYTES {
795 let _ = write!(out, "...+{}", bytes.len() - MAX_PREVIEW_BYTES);
796 }
797 out.push('"');
798 }
799 VmValue::Bool(b) => out.push_str(if *b { "true" } else { "false" }),
800 VmValue::Nil => out.push_str("nil"),
801 VmValue::List(items) => {
802 out.push('[');
803 crate::value::recursion::guard_recursion(|| {
804 for (i, item) in items.iter().enumerate() {
805 if i > 0 {
806 out.push_str(", ");
807 }
808 item.write_display(out);
809 }
810 });
811 out.push(']');
812 }
813 VmValue::Dict(map) => {
814 out.push('{');
815 crate::value::recursion::guard_recursion(|| {
816 for (i, (k, v)) in map.iter().enumerate() {
817 if i > 0 {
818 out.push_str(", ");
819 }
820 out.push_str(k);
821 out.push_str(": ");
822 v.write_display(out);
823 }
824 });
825 out.push('}');
826 }
827 VmValue::Closure(c) => {
828 let names: Vec<&str> = c.func.param_names().collect();
829 let _ = write!(out, "<fn({})>", names.join(", "));
830 }
831 VmValue::BuiltinRef(name) => {
832 let _ = write!(out, "<builtin {name}>");
833 }
834 VmValue::BuiltinRefId(r) => {
835 let _ = write!(out, "<builtin {}>", r.name);
836 }
837 VmValue::Duration(ms) => {
838 let sign = if *ms < 0 { "-" } else { "" };
839 let abs_ms = ms.unsigned_abs();
840 if abs_ms >= 604_800_000 && abs_ms % 604_800_000 == 0 {
841 let _ = write!(out, "{}{}w", sign, abs_ms / 604_800_000);
842 } else if abs_ms >= 86_400_000 && abs_ms % 86_400_000 == 0 {
843 let _ = write!(out, "{}{}d", sign, abs_ms / 86_400_000);
844 } else if abs_ms >= 3_600_000 && abs_ms % 3_600_000 == 0 {
845 let _ = write!(out, "{}{}h", sign, abs_ms / 3_600_000);
846 } else if abs_ms >= 60_000 && abs_ms % 60_000 == 0 {
847 let _ = write!(out, "{}{}m", sign, abs_ms / 60_000);
848 } else if abs_ms >= 1000 && abs_ms % 1000 == 0 {
849 let _ = write!(out, "{}{}s", sign, abs_ms / 1000);
850 } else {
851 let _ = write!(out, "{sign}{abs_ms}ms");
852 }
853 }
854 VmValue::EnumVariant(enum_variant) => {
855 if enum_variant.fields.is_empty() {
856 let _ = write!(out, "{}.{}", enum_variant.enum_name, enum_variant.variant);
857 } else {
858 let _ = write!(out, "{}.{}(", enum_variant.enum_name, enum_variant.variant);
859 crate::value::recursion::guard_recursion(|| {
860 for (i, v) in enum_variant.fields.iter().enumerate() {
861 if i > 0 {
862 out.push_str(", ");
863 }
864 v.write_display(out);
865 }
866 });
867 out.push(')');
868 }
869 }
870 VmValue::StructInstance(data) => {
871 let (layout, fields) = (&data.layout, &data.fields);
872 let _ = write!(out, "{} {{", layout.struct_name());
873 crate::value::recursion::guard_recursion(|| {
874 for (i, (k, v)) in struct_fields_to_map(layout, fields).iter().enumerate() {
875 if i > 0 {
876 out.push_str(", ");
877 }
878 out.push_str(k);
879 out.push_str(": ");
880 v.write_display(out);
881 }
882 });
883 out.push('}');
884 }
885 VmValue::TaskHandle(id) => {
886 let _ = write!(out, "<task:{id}>");
887 }
888 VmValue::Channel(ch) => {
889 let _ = write!(out, "<channel:{}>", ch.name);
890 }
891 VmValue::Atomic(a) => {
892 let _ = write!(out, "<atomic:{}>", a.value.load(Ordering::SeqCst));
893 }
894 VmValue::Rng(_) => {
895 out.push_str("<rng>");
896 }
897 VmValue::SyncPermit(p) => {
898 let _ = write!(out, "<sync_permit:{}:{}>", p.kind(), p.key());
899 }
900 VmValue::Resource(resource) => {
901 let _ = write!(out, "<resource:{}>", resource.label());
902 }
903 VmValue::ResourceGuard(guard) => {
904 let _ = write!(out, "<resource_guard:{}>", guard.label());
905 }
906 VmValue::McpClient(c) => {
907 let _ = write!(out, "<mcp_client:{}>", c.name);
908 }
909 VmValue::VerdictReceipt(_) => {
913 out.push_str("<verdict_receipt>");
914 }
915 VmValue::Set(items) => {
916 out.push_str("set(");
917 crate::value::recursion::guard_recursion(|| {
918 for (i, item) in items.iter().enumerate() {
919 if i > 0 {
920 out.push_str(", ");
921 }
922 item.write_display(out);
923 }
924 });
925 out.push(')');
926 }
927 VmValue::Generator(g) => {
928 if g.is_done() {
929 out.push_str("<generator (done)>");
930 } else {
931 out.push_str("<generator>");
932 }
933 }
934 VmValue::Stream(s) => {
935 if s.is_done() {
936 out.push_str("<stream (done)>");
937 } else {
938 out.push_str("<stream>");
939 }
940 }
941 VmValue::Range(r) => {
944 let _ = write!(out, "{} to {}", r.start, r.end);
945 if !r.inclusive {
946 out.push_str(" exclusive");
947 }
948 }
949 VmValue::Iter(h) => {
950 if matches!(&*h.lock(), crate::vm::iter::VmIter::Exhausted) {
951 out.push_str("<iter (exhausted)>");
952 } else {
953 out.push_str("<iter>");
954 }
955 }
956 VmValue::Harness(h) => {
957 let _ = write!(out, "<{}>", h.type_name());
958 }
959 VmValue::Pair(p) => {
960 out.push('(');
961 crate::value::recursion::guard_recursion(|| {
962 p.0.write_display(out);
963 out.push_str(", ");
964 p.1.write_display(out);
965 });
966 out.push(')');
967 }
968 }
969 }
970
971 pub fn as_dict(&self) -> Option<&DictMap> {
973 if let VmValue::Dict(d) = self {
974 Some(d)
975 } else {
976 None
977 }
978 }
979
980 pub fn as_int(&self) -> Option<i64> {
981 if let VmValue::Int(n) = self {
982 Some(*n)
983 } else {
984 None
985 }
986 }
987
988 pub fn as_bytes(&self) -> Option<&[u8]> {
989 if let VmValue::Bytes(bytes) = self {
990 Some(bytes.as_slice())
991 } else {
992 None
993 }
994 }
995}
996
997pub fn struct_fields_to_map(
998 layout: &StructLayout,
999 fields: &[Option<VmValue>],
1000) -> crate::value::DictMap {
1001 layout
1002 .field_names()
1003 .iter()
1004 .enumerate()
1005 .filter_map(|(index, name)| {
1006 fields
1007 .get(index)
1008 .and_then(Option::as_ref)
1009 .map(|value| (intern_key(name), value.clone()))
1010 })
1011 .collect()
1012}
1013
1014pub type VmBuiltinFn =
1016 Arc<dyn Fn(&[VmValue], &mut String) -> Result<VmValue, VmError> + Send + Sync>;
1017
1018#[cfg(test)]
1019mod runtime_type_tag_tests {
1020 use super::VmValue;
1021
1022 #[test]
1026 fn type_name_tags_match_canonical_registry() {
1027 let canonical = harn_builtin_meta::runtime_type_tags::ALL;
1028 for tag in VmValue::ALL_TYPE_NAMES {
1029 assert!(
1030 canonical.contains(tag),
1031 "VmValue::type_name tag `{tag}` missing from harn_builtin_meta::runtime_type_tags::ALL"
1032 );
1033 }
1034 for tag in canonical {
1035 assert!(
1036 VmValue::ALL_TYPE_NAMES.contains(tag),
1037 "canonical tag `{tag}` is not produced by VmValue::type_name; remove it or update ALL_TYPE_NAMES"
1038 );
1039 }
1040 }
1041}