1use std::collections::BTreeMap;
2use std::rc::Rc;
3use std::sync::atomic::{AtomicBool, AtomicI64, Ordering};
4use std::sync::Arc;
5use std::{cell::RefCell, path::PathBuf};
6
7use crate::chunk::CompiledFunction;
8use crate::mcp::VmMcpClientHandle;
9
10pub type VmAsyncBuiltinFn = Rc<
12 dyn Fn(
13 Vec<VmValue>,
14 ) -> std::pin::Pin<Box<dyn std::future::Future<Output = Result<VmValue, VmError>>>>,
15>;
16
17pub type VmJoinHandle = tokio::task::JoinHandle<Result<(VmValue, String), VmError>>;
19
20pub struct VmTaskHandle {
22 pub handle: VmJoinHandle,
23 pub cancel_token: Arc<AtomicBool>,
25}
26
27#[derive(Debug, Clone)]
29pub struct VmChannelHandle {
30 pub name: String,
31 pub sender: Arc<tokio::sync::mpsc::Sender<VmValue>>,
32 pub receiver: Arc<tokio::sync::Mutex<tokio::sync::mpsc::Receiver<VmValue>>>,
33 pub closed: Arc<AtomicBool>,
34}
35
36#[derive(Debug, Clone)]
38pub struct VmAtomicHandle {
39 pub value: Arc<AtomicI64>,
40}
41
42#[derive(Debug, Clone, Copy)]
54pub struct VmRange {
55 pub start: i64,
56 pub end: i64,
57 pub inclusive: bool,
58}
59
60impl VmRange {
61 pub fn len(&self) -> i64 {
71 if self.inclusive {
72 if self.start > self.end {
73 0
74 } else {
75 self.end.saturating_sub(self.start).saturating_add(1)
76 }
77 } else if self.start >= self.end {
78 0
79 } else {
80 self.end.saturating_sub(self.start)
81 }
82 }
83
84 pub fn is_empty(&self) -> bool {
85 self.len() == 0
86 }
87
88 pub fn get(&self, idx: i64) -> Option<i64> {
92 if idx < 0 || idx >= self.len() {
93 None
94 } else {
95 self.start.checked_add(idx)
96 }
97 }
98
99 pub fn first(&self) -> Option<i64> {
101 if self.is_empty() {
102 None
103 } else {
104 Some(self.start)
105 }
106 }
107
108 pub fn last(&self) -> Option<i64> {
110 if self.is_empty() {
111 None
112 } else if self.inclusive {
113 Some(self.end)
114 } else {
115 Some(self.end - 1)
116 }
117 }
118
119 pub fn contains(&self, v: i64) -> bool {
121 if self.is_empty() {
122 return false;
123 }
124 if self.inclusive {
125 v >= self.start && v <= self.end
126 } else {
127 v >= self.start && v < self.end
128 }
129 }
130
131 pub fn to_vec(&self) -> Vec<VmValue> {
138 let len = self.len();
139 if len <= 0 {
140 return Vec::new();
141 }
142 let cap = len as usize;
143 let mut out = Vec::with_capacity(cap);
144 for i in 0..len {
145 match self.start.checked_add(i) {
146 Some(v) => out.push(VmValue::Int(v)),
147 None => break,
148 }
149 }
150 out
151 }
152}
153
154#[derive(Debug, Clone)]
157pub struct VmGenerator {
158 pub done: Rc<std::cell::Cell<bool>>,
160 pub receiver: Rc<tokio::sync::Mutex<tokio::sync::mpsc::Receiver<VmValue>>>,
164}
165
166#[derive(Debug, Clone)]
168pub enum VmValue {
169 Int(i64),
170 Float(f64),
171 String(Rc<str>),
172 Bool(bool),
173 Nil,
174 List(Rc<Vec<VmValue>>),
175 Dict(Rc<BTreeMap<String, VmValue>>),
176 Closure(Rc<VmClosure>),
177 BuiltinRef(Rc<str>),
181 Duration(u64),
182 EnumVariant {
183 enum_name: String,
184 variant: String,
185 fields: Vec<VmValue>,
186 },
187 StructInstance {
188 struct_name: String,
189 fields: BTreeMap<String, VmValue>,
190 },
191 TaskHandle(String),
192 Channel(VmChannelHandle),
193 Atomic(VmAtomicHandle),
194 McpClient(VmMcpClientHandle),
195 Set(Rc<Vec<VmValue>>),
196 Generator(VmGenerator),
197 Range(VmRange),
198 Iter(Rc<RefCell<crate::vm::iter::VmIter>>),
200 Pair(Rc<(VmValue, VmValue)>),
205}
206
207#[derive(Debug, Clone)]
209pub struct VmClosure {
210 pub func: CompiledFunction,
211 pub env: VmEnv,
212 pub source_dir: Option<PathBuf>,
216 pub module_functions: Option<ModuleFunctionRegistry>,
220 pub module_state: Option<ModuleState>,
233}
234
235pub type ModuleFunctionRegistry = Rc<RefCell<BTreeMap<String, Rc<VmClosure>>>>;
236pub type ModuleState = Rc<RefCell<VmEnv>>;
237
238#[derive(Debug, Clone)]
240pub struct VmEnv {
241 pub(crate) scopes: Vec<Scope>,
242}
243
244#[derive(Debug, Clone)]
245pub(crate) struct Scope {
246 pub(crate) vars: BTreeMap<String, (VmValue, bool)>, }
248
249impl Default for VmEnv {
250 fn default() -> Self {
251 Self::new()
252 }
253}
254
255impl VmEnv {
256 pub fn new() -> Self {
257 Self {
258 scopes: vec![Scope {
259 vars: BTreeMap::new(),
260 }],
261 }
262 }
263
264 pub fn push_scope(&mut self) {
265 self.scopes.push(Scope {
266 vars: BTreeMap::new(),
267 });
268 }
269
270 pub fn pop_scope(&mut self) {
271 if self.scopes.len() > 1 {
272 self.scopes.pop();
273 }
274 }
275
276 pub fn scope_depth(&self) -> usize {
277 self.scopes.len()
278 }
279
280 pub fn truncate_scopes(&mut self, target_depth: usize) {
281 let min_depth = target_depth.max(1);
282 while self.scopes.len() > min_depth {
283 self.scopes.pop();
284 }
285 }
286
287 pub fn get(&self, name: &str) -> Option<VmValue> {
288 for scope in self.scopes.iter().rev() {
289 if let Some((val, _)) = scope.vars.get(name) {
290 return Some(val.clone());
291 }
292 }
293 None
294 }
295
296 pub fn define(&mut self, name: &str, value: VmValue, mutable: bool) -> Result<(), VmError> {
297 if let Some(scope) = self.scopes.last_mut() {
298 if let Some((_, existing_mutable)) = scope.vars.get(name) {
299 if !existing_mutable && !mutable {
300 return Err(VmError::Runtime(format!(
301 "Cannot redeclare immutable variable '{name}' in the same scope (use 'var' for mutable bindings)"
302 )));
303 }
304 }
305 scope.vars.insert(name.to_string(), (value, mutable));
306 }
307 Ok(())
308 }
309
310 pub fn all_variables(&self) -> BTreeMap<String, VmValue> {
311 let mut vars = BTreeMap::new();
312 for scope in &self.scopes {
313 for (name, (value, _)) in &scope.vars {
314 vars.insert(name.clone(), value.clone());
315 }
316 }
317 vars
318 }
319
320 pub fn assign(&mut self, name: &str, value: VmValue) -> Result<(), VmError> {
321 for scope in self.scopes.iter_mut().rev() {
322 if let Some((_, mutable)) = scope.vars.get(name) {
323 if !mutable {
324 return Err(VmError::ImmutableAssignment(name.to_string()));
325 }
326 scope.vars.insert(name.to_string(), (value, true));
327 return Ok(());
328 }
329 }
330 Err(VmError::UndefinedVariable(name.to_string()))
331 }
332
333 pub fn assign_debug(&mut self, name: &str, value: VmValue) -> Result<(), VmError> {
341 for scope in self.scopes.iter_mut().rev() {
342 if let Some((_, mutable)) = scope.vars.get(name) {
343 let mutable = *mutable;
344 scope.vars.insert(name.to_string(), (value, mutable));
345 return Ok(());
346 }
347 }
348 Err(VmError::UndefinedVariable(name.to_string()))
349 }
350}
351
352fn levenshtein(a: &str, b: &str) -> usize {
354 let a: Vec<char> = a.chars().collect();
355 let b: Vec<char> = b.chars().collect();
356 let (m, n) = (a.len(), b.len());
357 let mut prev = (0..=n).collect::<Vec<_>>();
358 let mut curr = vec![0; n + 1];
359 for i in 1..=m {
360 curr[0] = i;
361 for j in 1..=n {
362 let cost = if a[i - 1] == b[j - 1] { 0 } else { 1 };
363 curr[j] = (prev[j] + 1).min(curr[j - 1] + 1).min(prev[j - 1] + cost);
364 }
365 std::mem::swap(&mut prev, &mut curr);
366 }
367 prev[n]
368}
369
370pub fn closest_match<'a>(name: &str, candidates: impl Iterator<Item = &'a str>) -> Option<String> {
373 let max_dist = match name.len() {
374 0..=2 => 1,
375 3..=5 => 2,
376 _ => 3,
377 };
378 candidates
379 .filter(|c| *c != name && !c.starts_with("__"))
380 .map(|c| (c, levenshtein(name, c)))
381 .filter(|(_, d)| *d <= max_dist)
382 .min_by(|(a, da), (b, db)| {
384 da.cmp(db)
385 .then_with(|| {
386 let a_diff = (a.len() as isize - name.len() as isize).unsigned_abs();
387 let b_diff = (b.len() as isize - name.len() as isize).unsigned_abs();
388 a_diff.cmp(&b_diff)
389 })
390 .then_with(|| a.cmp(b))
391 })
392 .map(|(c, _)| c.to_string())
393}
394
395#[derive(Debug, Clone)]
396pub enum VmError {
397 StackUnderflow,
398 StackOverflow,
399 UndefinedVariable(String),
400 UndefinedBuiltin(String),
401 ImmutableAssignment(String),
402 TypeError(String),
403 Runtime(String),
404 DivisionByZero,
405 Thrown(VmValue),
406 CategorizedError {
408 message: String,
409 category: ErrorCategory,
410 },
411 Return(VmValue),
412 InvalidInstruction(u8),
413}
414
415#[derive(Debug, Clone, PartialEq, Eq)]
417pub enum ErrorCategory {
418 Timeout,
420 Auth,
422 RateLimit,
424 Overloaded,
428 ServerError,
430 TransientNetwork,
433 SchemaValidation,
435 ToolError,
437 ToolRejected,
439 Cancelled,
441 NotFound,
443 CircuitOpen,
445 Generic,
447}
448
449impl ErrorCategory {
450 pub fn as_str(&self) -> &'static str {
451 match self {
452 ErrorCategory::Timeout => "timeout",
453 ErrorCategory::Auth => "auth",
454 ErrorCategory::RateLimit => "rate_limit",
455 ErrorCategory::Overloaded => "overloaded",
456 ErrorCategory::ServerError => "server_error",
457 ErrorCategory::TransientNetwork => "transient_network",
458 ErrorCategory::SchemaValidation => "schema_validation",
459 ErrorCategory::ToolError => "tool_error",
460 ErrorCategory::ToolRejected => "tool_rejected",
461 ErrorCategory::Cancelled => "cancelled",
462 ErrorCategory::NotFound => "not_found",
463 ErrorCategory::CircuitOpen => "circuit_open",
464 ErrorCategory::Generic => "generic",
465 }
466 }
467
468 pub fn parse(s: &str) -> Self {
469 match s {
470 "timeout" => ErrorCategory::Timeout,
471 "auth" => ErrorCategory::Auth,
472 "rate_limit" => ErrorCategory::RateLimit,
473 "overloaded" => ErrorCategory::Overloaded,
474 "server_error" => ErrorCategory::ServerError,
475 "transient_network" => ErrorCategory::TransientNetwork,
476 "schema_validation" => ErrorCategory::SchemaValidation,
477 "tool_error" => ErrorCategory::ToolError,
478 "tool_rejected" => ErrorCategory::ToolRejected,
479 "cancelled" => ErrorCategory::Cancelled,
480 "not_found" => ErrorCategory::NotFound,
481 "circuit_open" => ErrorCategory::CircuitOpen,
482 _ => ErrorCategory::Generic,
483 }
484 }
485
486 pub fn is_transient(&self) -> bool {
490 matches!(
491 self,
492 ErrorCategory::Timeout
493 | ErrorCategory::RateLimit
494 | ErrorCategory::Overloaded
495 | ErrorCategory::ServerError
496 | ErrorCategory::TransientNetwork
497 )
498 }
499}
500
501pub fn categorized_error(message: impl Into<String>, category: ErrorCategory) -> VmError {
503 VmError::CategorizedError {
504 message: message.into(),
505 category,
506 }
507}
508
509pub fn error_to_category(err: &VmError) -> ErrorCategory {
518 match err {
519 VmError::CategorizedError { category, .. } => category.clone(),
520 VmError::Thrown(VmValue::Dict(d)) => d
521 .get("category")
522 .map(|v| ErrorCategory::parse(&v.display()))
523 .unwrap_or(ErrorCategory::Generic),
524 VmError::Thrown(VmValue::String(s)) => classify_error_message(s),
525 VmError::Runtime(msg) => classify_error_message(msg),
526 _ => ErrorCategory::Generic,
527 }
528}
529
530pub fn classify_error_message(msg: &str) -> ErrorCategory {
533 if let Some(cat) = classify_by_http_status(msg) {
535 return cat;
536 }
537 if msg.contains("Deadline exceeded") || msg.contains("context deadline exceeded") {
540 return ErrorCategory::Timeout;
541 }
542 if msg.contains("overloaded_error") {
543 return ErrorCategory::Overloaded;
545 }
546 if msg.contains("api_error") {
547 return ErrorCategory::ServerError;
549 }
550 if msg.contains("insufficient_quota") || msg.contains("billing_hard_limit_reached") {
551 return ErrorCategory::RateLimit;
553 }
554 if msg.contains("invalid_api_key") || msg.contains("authentication_error") {
555 return ErrorCategory::Auth;
556 }
557 if msg.contains("not_found_error") || msg.contains("model_not_found") {
558 return ErrorCategory::NotFound;
559 }
560 if msg.contains("circuit_open") {
561 return ErrorCategory::CircuitOpen;
562 }
563 let lower = msg.to_lowercase();
565 if lower.contains("connection reset")
566 || lower.contains("connection refused")
567 || lower.contains("connection closed")
568 || lower.contains("broken pipe")
569 || lower.contains("dns error")
570 || lower.contains("stream error")
571 || lower.contains("unexpected eof")
572 {
573 return ErrorCategory::TransientNetwork;
574 }
575 ErrorCategory::Generic
576}
577
578fn classify_by_http_status(msg: &str) -> Option<ErrorCategory> {
582 for code in extract_http_status_codes(msg) {
585 return Some(match code {
586 401 | 403 => ErrorCategory::Auth,
587 404 | 410 => ErrorCategory::NotFound,
588 408 | 504 | 522 | 524 => ErrorCategory::Timeout,
589 429 => ErrorCategory::RateLimit,
590 503 | 529 => ErrorCategory::Overloaded,
591 500 | 502 => ErrorCategory::ServerError,
592 _ => continue,
593 });
594 }
595 None
596}
597
598fn extract_http_status_codes(msg: &str) -> Vec<u16> {
600 let mut codes = Vec::new();
601 let bytes = msg.as_bytes();
602 for i in 0..bytes.len().saturating_sub(2) {
603 if bytes[i].is_ascii_digit()
605 && bytes[i + 1].is_ascii_digit()
606 && bytes[i + 2].is_ascii_digit()
607 {
608 let before_ok = i == 0 || !bytes[i - 1].is_ascii_digit();
610 let after_ok = i + 3 >= bytes.len() || !bytes[i + 3].is_ascii_digit();
611 if before_ok && after_ok {
612 if let Ok(code) = msg[i..i + 3].parse::<u16>() {
613 if (400..=599).contains(&code) {
614 codes.push(code);
615 }
616 }
617 }
618 }
619 }
620 codes
621}
622
623impl std::fmt::Display for VmError {
624 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
625 match self {
626 VmError::StackUnderflow => write!(f, "Stack underflow"),
627 VmError::StackOverflow => write!(f, "Stack overflow: too many nested calls"),
628 VmError::UndefinedVariable(n) => write!(f, "Undefined variable: {n}"),
629 VmError::UndefinedBuiltin(n) => write!(f, "Undefined builtin: {n}"),
630 VmError::ImmutableAssignment(n) => {
631 write!(f, "Cannot assign to immutable binding: {n}")
632 }
633 VmError::TypeError(msg) => write!(f, "Type error: {msg}"),
634 VmError::Runtime(msg) => write!(f, "Runtime error: {msg}"),
635 VmError::DivisionByZero => write!(f, "Division by zero"),
636 VmError::Thrown(v) => write!(f, "Thrown: {}", v.display()),
637 VmError::CategorizedError { message, category } => {
638 write!(f, "Error [{}]: {}", category.as_str(), message)
639 }
640 VmError::Return(_) => write!(f, "Return from function"),
641 VmError::InvalidInstruction(op) => write!(f, "Invalid instruction: 0x{op:02x}"),
642 }
643 }
644}
645
646impl std::error::Error for VmError {}
647
648impl VmValue {
649 pub fn is_truthy(&self) -> bool {
650 match self {
651 VmValue::Bool(b) => *b,
652 VmValue::Nil => false,
653 VmValue::Int(n) => *n != 0,
654 VmValue::Float(n) => *n != 0.0,
655 VmValue::String(s) => !s.is_empty(),
656 VmValue::List(l) => !l.is_empty(),
657 VmValue::Dict(d) => !d.is_empty(),
658 VmValue::Closure(_) => true,
659 VmValue::BuiltinRef(_) => true,
660 VmValue::Duration(ms) => *ms > 0,
661 VmValue::EnumVariant { .. } => true,
662 VmValue::StructInstance { .. } => true,
663 VmValue::TaskHandle(_) => true,
664 VmValue::Channel(_) => true,
665 VmValue::Atomic(_) => true,
666 VmValue::McpClient(_) => true,
667 VmValue::Set(s) => !s.is_empty(),
668 VmValue::Generator(_) => true,
669 VmValue::Range(_) => true,
672 VmValue::Iter(_) => true,
673 VmValue::Pair(_) => true,
674 }
675 }
676
677 pub fn type_name(&self) -> &'static str {
678 match self {
679 VmValue::String(_) => "string",
680 VmValue::Int(_) => "int",
681 VmValue::Float(_) => "float",
682 VmValue::Bool(_) => "bool",
683 VmValue::Nil => "nil",
684 VmValue::List(_) => "list",
685 VmValue::Dict(_) => "dict",
686 VmValue::Closure(_) => "closure",
687 VmValue::BuiltinRef(_) => "builtin",
688 VmValue::Duration(_) => "duration",
689 VmValue::EnumVariant { .. } => "enum",
690 VmValue::StructInstance { .. } => "struct",
691 VmValue::TaskHandle(_) => "task_handle",
692 VmValue::Channel(_) => "channel",
693 VmValue::Atomic(_) => "atomic",
694 VmValue::McpClient(_) => "mcp_client",
695 VmValue::Set(_) => "set",
696 VmValue::Generator(_) => "generator",
697 VmValue::Range(_) => "range",
698 VmValue::Iter(_) => "iter",
699 VmValue::Pair(_) => "pair",
700 }
701 }
702
703 pub fn display(&self) -> String {
704 let mut out = String::new();
705 self.write_display(&mut out);
706 out
707 }
708
709 pub fn write_display(&self, out: &mut String) {
712 use std::fmt::Write;
713 match self {
714 VmValue::Int(n) => {
715 let _ = write!(out, "{n}");
716 }
717 VmValue::Float(n) => {
718 if *n == (*n as i64) as f64 && n.abs() < 1e15 {
719 let _ = write!(out, "{n:.1}");
720 } else {
721 let _ = write!(out, "{n}");
722 }
723 }
724 VmValue::String(s) => out.push_str(s),
725 VmValue::Bool(b) => out.push_str(if *b { "true" } else { "false" }),
726 VmValue::Nil => out.push_str("nil"),
727 VmValue::List(items) => {
728 out.push('[');
729 for (i, item) in items.iter().enumerate() {
730 if i > 0 {
731 out.push_str(", ");
732 }
733 item.write_display(out);
734 }
735 out.push(']');
736 }
737 VmValue::Dict(map) => {
738 out.push('{');
739 for (i, (k, v)) in map.iter().enumerate() {
740 if i > 0 {
741 out.push_str(", ");
742 }
743 out.push_str(k);
744 out.push_str(": ");
745 v.write_display(out);
746 }
747 out.push('}');
748 }
749 VmValue::Closure(c) => {
750 let _ = write!(out, "<fn({})>", c.func.params.join(", "));
751 }
752 VmValue::BuiltinRef(name) => {
753 let _ = write!(out, "<builtin {name}>");
754 }
755 VmValue::Duration(ms) => {
756 if *ms >= 3_600_000 && ms % 3_600_000 == 0 {
757 let _ = write!(out, "{}h", ms / 3_600_000);
758 } else if *ms >= 60_000 && ms % 60_000 == 0 {
759 let _ = write!(out, "{}m", ms / 60_000);
760 } else if *ms >= 1000 && ms % 1000 == 0 {
761 let _ = write!(out, "{}s", ms / 1000);
762 } else {
763 let _ = write!(out, "{}ms", ms);
764 }
765 }
766 VmValue::EnumVariant {
767 enum_name,
768 variant,
769 fields,
770 } => {
771 if fields.is_empty() {
772 let _ = write!(out, "{enum_name}.{variant}");
773 } else {
774 let _ = write!(out, "{enum_name}.{variant}(");
775 for (i, v) in fields.iter().enumerate() {
776 if i > 0 {
777 out.push_str(", ");
778 }
779 v.write_display(out);
780 }
781 out.push(')');
782 }
783 }
784 VmValue::StructInstance {
785 struct_name,
786 fields,
787 } => {
788 let _ = write!(out, "{struct_name} {{");
789 for (i, (k, v)) in fields.iter().enumerate() {
790 if i > 0 {
791 out.push_str(", ");
792 }
793 out.push_str(k);
794 out.push_str(": ");
795 v.write_display(out);
796 }
797 out.push('}');
798 }
799 VmValue::TaskHandle(id) => {
800 let _ = write!(out, "<task:{id}>");
801 }
802 VmValue::Channel(ch) => {
803 let _ = write!(out, "<channel:{}>", ch.name);
804 }
805 VmValue::Atomic(a) => {
806 let _ = write!(out, "<atomic:{}>", a.value.load(Ordering::SeqCst));
807 }
808 VmValue::McpClient(c) => {
809 let _ = write!(out, "<mcp_client:{}>", c.name);
810 }
811 VmValue::Set(items) => {
812 out.push_str("set(");
813 for (i, item) in items.iter().enumerate() {
814 if i > 0 {
815 out.push_str(", ");
816 }
817 item.write_display(out);
818 }
819 out.push(')');
820 }
821 VmValue::Generator(g) => {
822 if g.done.get() {
823 out.push_str("<generator (done)>");
824 } else {
825 out.push_str("<generator>");
826 }
827 }
828 VmValue::Range(r) => {
831 let _ = write!(out, "{} to {}", r.start, r.end);
832 if !r.inclusive {
833 out.push_str(" exclusive");
834 }
835 }
836 VmValue::Iter(h) => {
837 if matches!(&*h.borrow(), crate::vm::iter::VmIter::Exhausted) {
838 out.push_str("<iter (exhausted)>");
839 } else {
840 out.push_str("<iter>");
841 }
842 }
843 VmValue::Pair(p) => {
844 out.push('(');
845 p.0.write_display(out);
846 out.push_str(", ");
847 p.1.write_display(out);
848 out.push(')');
849 }
850 }
851 }
852
853 pub fn as_dict(&self) -> Option<&BTreeMap<String, VmValue>> {
855 if let VmValue::Dict(d) = self {
856 Some(d)
857 } else {
858 None
859 }
860 }
861
862 pub fn as_int(&self) -> Option<i64> {
863 if let VmValue::Int(n) = self {
864 Some(*n)
865 } else {
866 None
867 }
868 }
869}
870
871pub type VmBuiltinFn = Rc<dyn Fn(&[VmValue], &mut String) -> Result<VmValue, VmError>>;
873
874pub fn values_identical(a: &VmValue, b: &VmValue) -> bool {
879 match (a, b) {
880 (VmValue::List(x), VmValue::List(y)) => Rc::ptr_eq(x, y),
881 (VmValue::Dict(x), VmValue::Dict(y)) => Rc::ptr_eq(x, y),
882 (VmValue::Set(x), VmValue::Set(y)) => Rc::ptr_eq(x, y),
883 (VmValue::Closure(x), VmValue::Closure(y)) => Rc::ptr_eq(x, y),
884 (VmValue::String(x), VmValue::String(y)) => Rc::ptr_eq(x, y) || x == y,
885 (VmValue::BuiltinRef(x), VmValue::BuiltinRef(y)) => x == y,
886 (VmValue::Pair(x), VmValue::Pair(y)) => Rc::ptr_eq(x, y),
887 _ => values_equal(a, b),
889 }
890}
891
892pub fn value_identity_key(v: &VmValue) -> String {
897 match v {
898 VmValue::List(x) => format!("list@{:p}", Rc::as_ptr(x)),
899 VmValue::Dict(x) => format!("dict@{:p}", Rc::as_ptr(x)),
900 VmValue::Set(x) => format!("set@{:p}", Rc::as_ptr(x)),
901 VmValue::Closure(x) => format!("closure@{:p}", Rc::as_ptr(x)),
902 VmValue::String(x) => format!("string@{:p}", x.as_ptr()),
903 VmValue::BuiltinRef(name) => format!("builtin@{name}"),
904 other => format!("{}@{}", other.type_name(), other.display()),
905 }
906}
907
908pub fn value_structural_hash_key(v: &VmValue) -> String {
914 let mut out = String::new();
915 write_structural_hash_key(v, &mut out);
916 out
917}
918
919fn write_structural_hash_key(v: &VmValue, out: &mut String) {
923 match v {
924 VmValue::Nil => out.push('N'),
925 VmValue::Bool(b) => {
926 out.push(if *b { 'T' } else { 'F' });
927 }
928 VmValue::Int(n) => {
929 out.push('i');
930 out.push_str(&n.to_string());
931 out.push(';');
932 }
933 VmValue::Float(n) => {
934 out.push('f');
935 out.push_str(&n.to_bits().to_string());
936 out.push(';');
937 }
938 VmValue::String(s) => {
939 out.push('s');
941 out.push_str(&s.len().to_string());
942 out.push(':');
943 out.push_str(s);
944 }
945 VmValue::Duration(ms) => {
946 out.push('d');
947 out.push_str(&ms.to_string());
948 out.push(';');
949 }
950 VmValue::List(items) => {
951 out.push('L');
952 for item in items.iter() {
953 write_structural_hash_key(item, out);
954 out.push(',');
955 }
956 out.push(']');
957 }
958 VmValue::Dict(map) => {
959 out.push('D');
960 for (k, v) in map.iter() {
961 out.push_str(&k.len().to_string());
963 out.push(':');
964 out.push_str(k);
965 out.push('=');
966 write_structural_hash_key(v, out);
967 out.push(',');
968 }
969 out.push('}');
970 }
971 VmValue::Set(items) => {
972 let mut keys: Vec<String> = items.iter().map(value_structural_hash_key).collect();
974 keys.sort();
975 out.push('S');
976 for k in &keys {
977 out.push_str(k);
978 out.push(',');
979 }
980 out.push('}');
981 }
982 other => {
983 let tn = other.type_name();
984 out.push('o');
985 out.push_str(&tn.len().to_string());
986 out.push(':');
987 out.push_str(tn);
988 let d = other.display();
989 out.push_str(&d.len().to_string());
990 out.push(':');
991 out.push_str(&d);
992 }
993 }
994}
995
996pub fn values_equal(a: &VmValue, b: &VmValue) -> bool {
997 match (a, b) {
998 (VmValue::Int(x), VmValue::Int(y)) => x == y,
999 (VmValue::Float(x), VmValue::Float(y)) => x == y,
1000 (VmValue::String(x), VmValue::String(y)) => x == y,
1001 (VmValue::Bool(x), VmValue::Bool(y)) => x == y,
1002 (VmValue::Nil, VmValue::Nil) => true,
1003 (VmValue::Int(x), VmValue::Float(y)) => (*x as f64) == *y,
1004 (VmValue::Float(x), VmValue::Int(y)) => *x == (*y as f64),
1005 (VmValue::TaskHandle(a), VmValue::TaskHandle(b)) => a == b,
1006 (VmValue::Channel(_), VmValue::Channel(_)) => false, (VmValue::Atomic(a), VmValue::Atomic(b)) => {
1008 a.value.load(Ordering::SeqCst) == b.value.load(Ordering::SeqCst)
1009 }
1010 (VmValue::List(a), VmValue::List(b)) => {
1011 a.len() == b.len() && a.iter().zip(b.iter()).all(|(x, y)| values_equal(x, y))
1012 }
1013 (VmValue::Dict(a), VmValue::Dict(b)) => {
1014 a.len() == b.len()
1015 && a.iter()
1016 .zip(b.iter())
1017 .all(|((k1, v1), (k2, v2))| k1 == k2 && values_equal(v1, v2))
1018 }
1019 (
1020 VmValue::EnumVariant {
1021 enum_name: a_e,
1022 variant: a_v,
1023 fields: a_f,
1024 },
1025 VmValue::EnumVariant {
1026 enum_name: b_e,
1027 variant: b_v,
1028 fields: b_f,
1029 },
1030 ) => {
1031 a_e == b_e
1032 && a_v == b_v
1033 && a_f.len() == b_f.len()
1034 && a_f.iter().zip(b_f.iter()).all(|(x, y)| values_equal(x, y))
1035 }
1036 (
1037 VmValue::StructInstance {
1038 struct_name: a_s,
1039 fields: a_f,
1040 },
1041 VmValue::StructInstance {
1042 struct_name: b_s,
1043 fields: b_f,
1044 },
1045 ) => {
1046 a_s == b_s
1047 && a_f.len() == b_f.len()
1048 && a_f
1049 .iter()
1050 .zip(b_f.iter())
1051 .all(|((k1, v1), (k2, v2))| k1 == k2 && values_equal(v1, v2))
1052 }
1053 (VmValue::Set(a), VmValue::Set(b)) => {
1054 a.len() == b.len() && a.iter().all(|x| b.iter().any(|y| values_equal(x, y)))
1055 }
1056 (VmValue::Generator(_), VmValue::Generator(_)) => false, (VmValue::Range(a), VmValue::Range(b)) => {
1058 a.start == b.start && a.end == b.end && a.inclusive == b.inclusive
1059 }
1060 (VmValue::Iter(a), VmValue::Iter(b)) => Rc::ptr_eq(a, b),
1061 (VmValue::Pair(a), VmValue::Pair(b)) => {
1062 values_equal(&a.0, &b.0) && values_equal(&a.1, &b.1)
1063 }
1064 _ => false,
1065 }
1066}
1067
1068pub fn compare_values(a: &VmValue, b: &VmValue) -> i32 {
1069 match (a, b) {
1070 (VmValue::Int(x), VmValue::Int(y)) => x.cmp(y) as i32,
1071 (VmValue::Float(x), VmValue::Float(y)) => {
1072 if x < y {
1073 -1
1074 } else if x > y {
1075 1
1076 } else {
1077 0
1078 }
1079 }
1080 (VmValue::Int(x), VmValue::Float(y)) => {
1081 let x = *x as f64;
1082 if x < *y {
1083 -1
1084 } else if x > *y {
1085 1
1086 } else {
1087 0
1088 }
1089 }
1090 (VmValue::Float(x), VmValue::Int(y)) => {
1091 let y = *y as f64;
1092 if *x < y {
1093 -1
1094 } else if *x > y {
1095 1
1096 } else {
1097 0
1098 }
1099 }
1100 (VmValue::String(x), VmValue::String(y)) => x.cmp(y) as i32,
1101 (VmValue::Pair(x), VmValue::Pair(y)) => {
1102 let c = compare_values(&x.0, &y.0);
1103 if c != 0 {
1104 c
1105 } else {
1106 compare_values(&x.1, &y.1)
1107 }
1108 }
1109 _ => 0,
1110 }
1111}
1112
1113#[cfg(test)]
1114mod tests {
1115 use super::*;
1116
1117 fn s(val: &str) -> VmValue {
1118 VmValue::String(Rc::from(val))
1119 }
1120 fn i(val: i64) -> VmValue {
1121 VmValue::Int(val)
1122 }
1123 fn list(items: Vec<VmValue>) -> VmValue {
1124 VmValue::List(Rc::new(items))
1125 }
1126 fn dict(pairs: Vec<(&str, VmValue)>) -> VmValue {
1127 VmValue::Dict(Rc::new(
1128 pairs.into_iter().map(|(k, v)| (k.to_string(), v)).collect(),
1129 ))
1130 }
1131
1132 #[test]
1133 fn hash_key_cross_type_distinct() {
1134 let k_int = value_structural_hash_key(&i(1));
1136 let k_str = value_structural_hash_key(&s("1"));
1137 let k_bool = value_structural_hash_key(&VmValue::Bool(true));
1138 assert_ne!(k_int, k_str);
1139 assert_ne!(k_int, k_bool);
1140 assert_ne!(k_str, k_bool);
1141 }
1142
1143 #[test]
1144 fn hash_key_string_with_separator_chars() {
1145 let one_elem = list(vec![s("a,string:b")]);
1147 let two_elem = list(vec![s("a"), s("b")]);
1148 assert_ne!(
1149 value_structural_hash_key(&one_elem),
1150 value_structural_hash_key(&two_elem),
1151 "length-prefixed strings must prevent separator collisions"
1152 );
1153 }
1154
1155 #[test]
1156 fn hash_key_dict_key_with_equals() {
1157 let d1 = dict(vec![("a=b", i(1))]);
1159 let d2 = dict(vec![("a", i(1))]);
1160 assert_ne!(
1161 value_structural_hash_key(&d1),
1162 value_structural_hash_key(&d2)
1163 );
1164 }
1165
1166 #[test]
1167 fn hash_key_nested_list_vs_flat() {
1168 let nested = list(vec![list(vec![i(1)])]);
1170 let flat = list(vec![i(1)]);
1171 assert_ne!(
1172 value_structural_hash_key(&nested),
1173 value_structural_hash_key(&flat)
1174 );
1175 }
1176
1177 #[test]
1178 fn hash_key_nil() {
1179 assert_eq!(
1180 value_structural_hash_key(&VmValue::Nil),
1181 value_structural_hash_key(&VmValue::Nil)
1182 );
1183 }
1184
1185 #[test]
1186 fn hash_key_float_zero_vs_neg_zero() {
1187 let pos = VmValue::Float(0.0);
1188 let neg = VmValue::Float(-0.0);
1189 assert_ne!(
1191 value_structural_hash_key(&pos),
1192 value_structural_hash_key(&neg)
1193 );
1194 }
1195
1196 #[test]
1197 fn hash_key_equal_values_match() {
1198 let a = list(vec![s("hello"), i(42), VmValue::Bool(false)]);
1199 let b = list(vec![s("hello"), i(42), VmValue::Bool(false)]);
1200 assert_eq!(value_structural_hash_key(&a), value_structural_hash_key(&b));
1201 }
1202
1203 #[test]
1204 fn hash_key_dict_with_comma_key() {
1205 let d1 = dict(vec![("a,b", i(1))]);
1206 let d2 = dict(vec![("a", i(1))]);
1207 assert_ne!(
1208 value_structural_hash_key(&d1),
1209 value_structural_hash_key(&d2)
1210 );
1211 }
1212
1213 #[test]
1221 fn vm_range_len_inclusive_saturates_at_i64_max() {
1222 let r = VmRange {
1223 start: i64::MIN,
1224 end: 0,
1225 inclusive: true,
1226 };
1227 assert_eq!(r.len(), i64::MAX);
1229 }
1230
1231 #[test]
1232 fn vm_range_len_exclusive_full_range_saturates() {
1233 let r = VmRange {
1234 start: i64::MIN,
1235 end: i64::MAX,
1236 inclusive: false,
1237 };
1238 assert_eq!(r.len(), i64::MAX);
1239 }
1240
1241 #[test]
1242 fn vm_range_len_inclusive_full_range_saturates() {
1243 let r = VmRange {
1244 start: i64::MIN,
1245 end: i64::MAX,
1246 inclusive: true,
1247 };
1248 assert_eq!(r.len(), i64::MAX);
1249 }
1250
1251 #[test]
1252 fn vm_range_get_near_max_does_not_overflow() {
1253 let r = VmRange {
1254 start: i64::MAX - 2,
1255 end: i64::MAX,
1256 inclusive: true,
1257 };
1258 assert_eq!(r.len(), 3);
1259 assert_eq!(r.get(0), Some(i64::MAX - 2));
1260 assert_eq!(r.get(2), Some(i64::MAX));
1261 assert_eq!(r.get(3), None);
1262 }
1263
1264 #[test]
1265 fn vm_range_reversed_is_empty() {
1266 let r = VmRange {
1267 start: 5,
1268 end: 1,
1269 inclusive: true,
1270 };
1271 assert!(r.is_empty());
1272 assert_eq!(r.len(), 0);
1273 assert_eq!(r.first(), None);
1274 assert_eq!(r.last(), None);
1275 }
1276
1277 #[test]
1278 fn vm_range_contains_near_bounds() {
1279 let r = VmRange {
1280 start: 1,
1281 end: 5,
1282 inclusive: true,
1283 };
1284 assert!(r.contains(1));
1285 assert!(r.contains(5));
1286 assert!(!r.contains(0));
1287 assert!(!r.contains(6));
1288 let r = VmRange {
1289 start: 1,
1290 end: 5,
1291 inclusive: false,
1292 };
1293 assert!(r.contains(1));
1294 assert!(r.contains(4));
1295 assert!(!r.contains(5));
1296 }
1297
1298 #[test]
1299 fn vm_range_to_vec_matches_direct_iteration() {
1300 let r = VmRange {
1301 start: -2,
1302 end: 2,
1303 inclusive: true,
1304 };
1305 let v = r.to_vec();
1306 assert_eq!(v.len(), 5);
1307 assert_eq!(
1308 v.iter()
1309 .map(|x| match x {
1310 VmValue::Int(n) => *n,
1311 _ => panic!("non-int in range"),
1312 })
1313 .collect::<Vec<_>>(),
1314 vec![-2, -1, 0, 1, 2]
1315 );
1316 }
1317}