Skip to main content

harn_vm/value/
handles.rs

1use std::rc::Rc;
2use std::sync::atomic::{AtomicBool, AtomicI64};
3use std::sync::Arc;
4
5use super::{VmError, VmValue};
6
7/// The raw join handle type for spawned tasks.
8pub type VmJoinHandle = tokio::task::JoinHandle<Result<(VmValue, String), VmError>>;
9
10/// A spawned async task handle with cancellation support.
11pub struct VmTaskHandle {
12    pub handle: VmJoinHandle,
13    /// Cooperative cancellation token. Set to true to request graceful shutdown.
14    pub cancel_token: Arc<AtomicBool>,
15}
16
17/// A channel handle for the VM (uses tokio mpsc).
18#[derive(Debug, Clone)]
19pub struct VmChannelHandle {
20    pub name: Rc<str>,
21    pub sender: Arc<tokio::sync::mpsc::Sender<VmValue>>,
22    pub receiver: Arc<tokio::sync::Mutex<tokio::sync::mpsc::Receiver<VmValue>>>,
23    pub closed: Arc<AtomicBool>,
24}
25
26/// An atomic integer handle for the VM.
27#[derive(Debug, Clone)]
28pub struct VmAtomicHandle {
29    pub value: Arc<AtomicI64>,
30}
31
32/// A held synchronization permit for mutex/semaphore/gate primitives.
33#[derive(Debug, Clone)]
34pub struct VmSyncPermitHandle {
35    pub(crate) lease: Arc<crate::synchronization::VmSyncLease>,
36}
37
38impl VmSyncPermitHandle {
39    pub(crate) fn release(&self) -> bool {
40        self.lease.release()
41    }
42
43    pub(crate) fn kind(&self) -> &str {
44        self.lease.kind()
45    }
46
47    pub(crate) fn key(&self) -> &str {
48        self.lease.key()
49    }
50}
51
52/// A lazy integer range — Python-style. Stores only `(start, end, inclusive)`
53/// so the in-memory footprint is O(1) regardless of the range's length.
54/// `len()`, indexing (`r[k]`), `.contains(x)`, `.first()`, `.last()` are all
55/// O(1); direct iteration walks step-by-step without materializing a list.
56///
57/// Empty-range convention (Python-consistent):
58/// - Inclusive empty when `start > end`.
59/// - Exclusive empty when `start >= end`.
60///
61/// Negative / reversed ranges are NOT supported in v1: `5 to 1` is simply
62/// empty. Authors who want reverse iteration should call `.to_list().reverse()`.
63#[derive(Debug, Clone, Copy)]
64pub struct VmRange {
65    pub start: i64,
66    pub end: i64,
67    pub inclusive: bool,
68}
69
70impl VmRange {
71    /// Number of elements this range yields.
72    ///
73    /// Uses saturating arithmetic so that pathological ranges near
74    /// `i64::MAX`/`i64::MIN` do not panic on overflow. Because a range's
75    /// element count must fit in `i64` the returned length saturates at
76    /// `i64::MAX` for ranges whose width exceeds that (e.g. `i64::MIN to
77    /// i64::MAX` inclusive). Callers that later narrow to `usize` for
78    /// allocation should still guard against huge lengths — see
79    /// `to_vec` / `get` for the indexable-range invariants.
80    pub fn len(&self) -> i64 {
81        if self.inclusive {
82            if self.start > self.end {
83                0
84            } else {
85                self.end.saturating_sub(self.start).saturating_add(1)
86            }
87        } else if self.start >= self.end {
88            0
89        } else {
90            self.end.saturating_sub(self.start)
91        }
92    }
93
94    pub fn is_empty(&self) -> bool {
95        self.len() == 0
96    }
97
98    /// Element at the given 0-based index, bounds-checked.
99    /// Returns `None` when out of bounds or when `start + idx` would
100    /// overflow (which can only happen when `len()` saturated).
101    pub fn get(&self, idx: i64) -> Option<i64> {
102        if idx < 0 || idx >= self.len() {
103            None
104        } else {
105            self.start.checked_add(idx)
106        }
107    }
108
109    /// First element or `None` when empty.
110    pub fn first(&self) -> Option<i64> {
111        if self.is_empty() {
112            None
113        } else {
114            Some(self.start)
115        }
116    }
117
118    /// Last element or `None` when empty.
119    pub fn last(&self) -> Option<i64> {
120        if self.is_empty() {
121            None
122        } else if self.inclusive {
123            Some(self.end)
124        } else {
125            Some(self.end - 1)
126        }
127    }
128
129    /// Whether `v` falls inside the range (O(1)).
130    pub fn contains(&self, v: i64) -> bool {
131        if self.is_empty() {
132            return false;
133        }
134        if self.inclusive {
135            v >= self.start && v <= self.end
136        } else {
137            v >= self.start && v < self.end
138        }
139    }
140
141    /// Materialize to a `Vec<VmValue>` — the explicit escape hatch.
142    ///
143    /// Uses `checked_add` on the per-element index so a range near
144    /// `i64::MAX` stops at the representable bound instead of panicking.
145    /// Callers should still treat a very long range as unwise to
146    /// materialize (the whole point of `VmRange` is to avoid this).
147    pub fn to_vec(&self) -> Vec<VmValue> {
148        let len = self.len();
149        if len <= 0 {
150            return Vec::new();
151        }
152        let cap = len as usize;
153        let mut out = Vec::with_capacity(cap);
154        for i in 0..len {
155            match self.start.checked_add(i) {
156                Some(v) => out.push(VmValue::Int(v)),
157                None => break,
158            }
159        }
160        out
161    }
162}
163
164/// A generator object: lazily produces values via yield.
165/// The generator body runs as a spawned task that sends values through a channel.
166#[derive(Debug, Clone)]
167pub struct VmGenerator {
168    /// Whether the generator has finished (returned or exhausted).
169    pub done: Rc<std::cell::Cell<bool>>,
170    /// Receiver end of the yield channel (generator sends values here).
171    /// Wrapped in a shared async mutex so recv() can be called without holding
172    /// a RefCell borrow across await points.
173    pub receiver: Rc<tokio::sync::Mutex<tokio::sync::mpsc::Receiver<VmValue>>>,
174}