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