Skip to main content

kinetik_runtime/
lib.rs

1//! Runtime value model and VM support for Kinetik.
2
3use std::collections::BTreeMap;
4use std::fmt;
5
6/// Core runtime value.
7#[derive(Clone, PartialEq)]
8pub enum Value {
9    /// Absence of a value.
10    Nil,
11    /// Boolean value.
12    Bool(bool),
13    /// Kinetik `number`, represented as an IEEE-754 64-bit float.
14    Number(f64),
15    /// UTF-8 string value.
16    String(String),
17    /// Ordered array value.
18    Array(ArrayValue),
19    /// Plain JSON-native object value.
20    Object(ObjectValue),
21    /// Programmable table value.
22    Table(TableValue),
23    /// Script function handle.
24    Function(FunctionHandle),
25    /// Host/native value handle.
26    Native(NativeHandle),
27    /// Opaque host object handle.
28    Host(HostHandle),
29    /// Task/coroutine handle.
30    Task(TaskHandle),
31}
32
33impl Value {
34    /// Creates a `nil` value.
35    #[must_use]
36    pub const fn nil() -> Self {
37        Self::Nil
38    }
39
40    /// Creates a boolean value.
41    #[must_use]
42    pub const fn bool(value: bool) -> Self {
43        Self::Bool(value)
44    }
45
46    /// Creates a number value.
47    #[must_use]
48    pub const fn number(value: f64) -> Self {
49        Self::Number(value)
50    }
51
52    /// Creates a string value.
53    #[must_use]
54    pub fn string(value: impl Into<String>) -> Self {
55        Self::String(value.into())
56    }
57
58    /// Creates an array value.
59    #[must_use]
60    pub fn array(elements: impl Into<Vec<Value>>) -> Self {
61        Self::Array(ArrayValue::new(elements))
62    }
63
64    /// Creates a plain object value.
65    #[must_use]
66    pub fn object(fields: impl IntoIterator<Item = (String, Value)>) -> Self {
67        Self::Object(ObjectValue::new(fields))
68    }
69
70    /// Creates a programmable table handle value.
71    #[must_use]
72    pub const fn table(id: TableId) -> Self {
73        Self::Table(TableValue::new(id))
74    }
75
76    /// Creates a script function handle value.
77    #[must_use]
78    pub const fn function(id: FunctionId) -> Self {
79        Self::Function(FunctionHandle::new(id))
80    }
81
82    /// Creates a native handle value.
83    #[must_use]
84    pub const fn native(id: NativeId) -> Self {
85        Self::Native(NativeHandle::new(id))
86    }
87
88    /// Creates an opaque host object handle value.
89    #[must_use]
90    pub const fn host(id: HostId, generation: u64) -> Self {
91        Self::Host(HostHandle::new(id, generation))
92    }
93
94    /// Creates a task handle value.
95    #[must_use]
96    pub const fn task(id: TaskId) -> Self {
97        Self::Task(TaskHandle::new(id))
98    }
99
100    /// Returns the stable runtime type name for this value.
101    #[must_use]
102    pub const fn type_name(&self) -> &'static str {
103        match self {
104            Self::Nil => "nil",
105            Self::Bool(_) => "bool",
106            Self::Number(_) => "number",
107            Self::String(_) => "string",
108            Self::Array(_) => "array",
109            Self::Object(_) => "object",
110            Self::Table(_) => "table",
111            Self::Function(_) => "function",
112            Self::Native(_) | Self::Host(_) => "native",
113            Self::Task(_) => "task",
114        }
115    }
116}
117
118impl fmt::Display for Value {
119    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
120        match self {
121            Self::Nil => f.write_str("nil"),
122            Self::Bool(value) => write!(f, "{value}"),
123            Self::Number(value) => write!(f, "{value}"),
124            Self::String(value) => f.write_str(value),
125            Self::Array(value) => fmt_array(f, value.elements()),
126            Self::Object(value) => fmt_object(f, value.fields()),
127            Self::Table(value) => write!(f, "<table #{}>", value.id().get()),
128            Self::Function(value) => write!(f, "<function #{}>", value.id().get()),
129            Self::Native(value) => write!(f, "<native #{}>", value.id().get()),
130            Self::Host(value) => write!(f, "<host #{}>", value.id().get()),
131            Self::Task(value) => write!(f, "<task #{}>", value.id().get()),
132        }
133    }
134}
135
136impl fmt::Debug for Value {
137    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
138        match self {
139            Self::String(value) => f.debug_tuple("String").field(value).finish(),
140            Self::Array(value) => f.debug_tuple("Array").field(value).finish(),
141            Self::Object(value) => f.debug_tuple("Object").field(value).finish(),
142            other => write!(f, "{}({other})", other.type_name()),
143        }
144    }
145}
146
147/// Ordered runtime array.
148#[derive(Clone, Debug, PartialEq)]
149pub struct ArrayValue {
150    elements: Vec<Value>,
151}
152
153impl ArrayValue {
154    /// Creates an array value from ordered elements.
155    #[must_use]
156    pub fn new(elements: impl Into<Vec<Value>>) -> Self {
157        Self {
158            elements: elements.into(),
159        }
160    }
161
162    /// Returns the array elements.
163    #[must_use]
164    pub fn elements(&self) -> &[Value] {
165        &self.elements
166    }
167}
168
169/// Plain JSON-native object value.
170#[derive(Clone, PartialEq)]
171pub struct ObjectValue {
172    fields: BTreeMap<String, Value>,
173}
174
175impl fmt::Debug for ObjectValue {
176    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
177        f.debug_map().entries(self.fields.iter()).finish()
178    }
179}
180
181impl ObjectValue {
182    /// Creates an object value from string-keyed fields.
183    #[must_use]
184    pub fn new(fields: impl IntoIterator<Item = (String, Value)>) -> Self {
185        Self {
186            fields: fields.into_iter().collect(),
187        }
188    }
189
190    /// Returns the object fields in deterministic key order.
191    #[must_use]
192    pub const fn fields(&self) -> &BTreeMap<String, Value> {
193        &self.fields
194    }
195}
196
197/// Programmable table handle.
198#[derive(Clone, Copy, Debug, Eq, PartialEq)]
199pub struct TableValue {
200    id: TableId,
201}
202
203impl TableValue {
204    /// Creates a table handle value.
205    #[must_use]
206    pub const fn new(id: TableId) -> Self {
207        Self { id }
208    }
209
210    /// Returns the table identity.
211    #[must_use]
212    pub const fn id(self) -> TableId {
213        self.id
214    }
215}
216
217/// Script function handle.
218#[derive(Clone, Copy, Debug, Eq, PartialEq)]
219pub struct FunctionHandle {
220    id: FunctionId,
221}
222
223impl FunctionHandle {
224    /// Creates a function handle.
225    #[must_use]
226    pub const fn new(id: FunctionId) -> Self {
227        Self { id }
228    }
229
230    /// Returns the function identity.
231    #[must_use]
232    pub const fn id(self) -> FunctionId {
233        self.id
234    }
235}
236
237/// Host/native value handle.
238#[derive(Clone, Copy, Debug, Eq, PartialEq)]
239pub struct NativeHandle {
240    id: NativeId,
241}
242
243impl NativeHandle {
244    /// Creates a native handle.
245    #[must_use]
246    pub const fn new(id: NativeId) -> Self {
247        Self { id }
248    }
249
250    /// Returns the native handle identity.
251    #[must_use]
252    pub const fn id(self) -> NativeId {
253        self.id
254    }
255}
256
257/// Opaque host object handle.
258#[derive(Clone, Copy, Debug, Eq, PartialEq)]
259pub struct HostHandle {
260    id: HostId,
261    generation: u64,
262}
263
264impl HostHandle {
265    /// Creates a host object handle.
266    #[must_use]
267    pub const fn new(id: HostId, generation: u64) -> Self {
268        Self { id, generation }
269    }
270
271    /// Returns the host object identity.
272    #[must_use]
273    pub const fn id(self) -> HostId {
274        self.id
275    }
276
277    /// Returns the handle generation used to detect stale references.
278    #[must_use]
279    pub const fn generation(self) -> u64 {
280        self.generation
281    }
282}
283
284/// Task/coroutine handle.
285#[derive(Clone, Copy, Debug, Eq, PartialEq)]
286pub struct TaskHandle {
287    id: TaskId,
288}
289
290impl TaskHandle {
291    /// Creates a task handle.
292    #[must_use]
293    pub const fn new(id: TaskId) -> Self {
294        Self { id }
295    }
296
297    /// Returns the task identity.
298    #[must_use]
299    pub const fn id(self) -> TaskId {
300        self.id
301    }
302}
303
304macro_rules! id_type {
305    ($name:ident, $doc:literal) => {
306        #[doc = $doc]
307        #[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
308        pub struct $name(u64);
309
310        impl $name {
311            /// Creates an identity from a raw integer.
312            #[must_use]
313            pub const fn new(raw: u64) -> Self {
314                Self(raw)
315            }
316
317            /// Returns the raw integer identity.
318            #[must_use]
319            pub const fn get(self) -> u64 {
320                self.0
321            }
322        }
323    };
324}
325
326id_type!(TableId, "Stable identity for a runtime table.");
327id_type!(FunctionId, "Stable identity for a script function.");
328id_type!(NativeId, "Stable identity for a host/native value.");
329id_type!(HostId, "Stable identity for an opaque host object.");
330id_type!(TaskId, "Stable identity for a task.");
331
332fn fmt_array(f: &mut fmt::Formatter<'_>, elements: &[Value]) -> fmt::Result {
333    f.write_str("[")?;
334    for (index, element) in elements.iter().enumerate() {
335        if index > 0 {
336            f.write_str(", ")?;
337        }
338        write!(f, "{element:?}")?;
339    }
340    f.write_str("]")
341}
342
343fn fmt_object(f: &mut fmt::Formatter<'_>, fields: &BTreeMap<String, Value>) -> fmt::Result {
344    f.write_str("{")?;
345    for (index, (key, value)) in fields.iter().enumerate() {
346        if index > 0 {
347            f.write_str(", ")?;
348        }
349        write!(f, "{key}: {value:?}")?;
350    }
351    f.write_str("}")
352}
353
354#[cfg(test)]
355mod tests {
356    use super::{FunctionId, HostId, NativeId, TableId, TaskId, Value};
357
358    #[test]
359    fn constructs_core_values() {
360        assert_eq!(Value::nil().type_name(), "nil");
361        assert_eq!(Value::bool(true).type_name(), "bool");
362        assert_eq!(Value::number(1.5).type_name(), "number");
363        assert_eq!(Value::string("hi").type_name(), "string");
364        assert_eq!(Value::array([Value::Nil]).type_name(), "array");
365        assert_eq!(
366            Value::object([(String::from("hp"), Value::number(10.0))]).type_name(),
367            "object"
368        );
369        assert_eq!(Value::table(TableId::new(1)).type_name(), "table");
370        assert_eq!(Value::function(FunctionId::new(2)).type_name(), "function");
371        assert_eq!(Value::native(NativeId::new(3)).type_name(), "native");
372        assert_eq!(Value::host(HostId::new(4), 1).type_name(), "native");
373        assert_eq!(Value::task(TaskId::new(4)).type_name(), "task");
374    }
375
376    #[test]
377    fn formats_values_for_display_and_debug() {
378        let object = Value::object([
379            (String::from("alive"), Value::bool(true)),
380            (String::from("name"), Value::string("Slime")),
381        ]);
382        let array = Value::array([Value::number(1.0), object]);
383
384        assert_eq!(Value::Nil.to_string(), "nil");
385        assert_eq!(Value::string("hello").to_string(), "hello");
386        assert_eq!(Value::table(TableId::new(7)).to_string(), "<table #7>");
387        assert_eq!(Value::host(HostId::new(8), 1).to_string(), "<host #8>");
388        assert_eq!(
389            array.to_string(),
390            "[number(1), Object({\"alive\": bool(true), \"name\": String(\"Slime\")})]"
391        );
392    }
393
394    #[test]
395    fn compares_primitive_and_plain_values() {
396        assert_eq!(Value::nil(), Value::Nil);
397        assert_eq!(Value::number(2.0), Value::number(2.0));
398        assert_ne!(Value::number(f64::NAN), Value::number(f64::NAN));
399
400        let left = Value::object([(String::from("items"), Value::array([Value::string("key")]))]);
401        let right = Value::object([(String::from("items"), Value::array([Value::string("key")]))]);
402
403        assert_eq!(left, right);
404    }
405
406    #[test]
407    fn keeps_objects_and_tables_distinct() {
408        let object = Value::object([(String::from("id"), Value::number(1.0))]);
409        let table = Value::table(TableId::new(1));
410
411        assert_ne!(object, table);
412        assert_eq!(Value::table(TableId::new(1)), Value::table(TableId::new(1)));
413        assert_ne!(Value::table(TableId::new(1)), Value::table(TableId::new(2)));
414    }
415}