Skip to main content

konjure_lang/
value.rs

1//! Owned, serializable runtime values. Records are values; only `spawn` creates entities.
2use crate::Type;
3use konjure_sdk::data::{Bin, Scalar, Str, Tensor};
4use serde::{Deserialize, Serialize};
5use std::collections::BTreeMap;
6
7/// A nominal type instance, copied by value at language boundaries.
8#[cfg_attr(feature = "typescript", derive(ts_rs::TS))]
9#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
10pub struct RecordValue {
11    /// Canonical nominal type key, matching an SDK program declaration.
12    pub class: String,
13    /// Owned type fields in stable name order.
14    pub fields: BTreeMap<String, Value>,
15}
16/// A checked algebraic case carrying its canonical nominal or applied type.
17#[cfg_attr(feature = "typescript", derive(ts_rs::TS))]
18#[derive(Clone, Debug, Serialize, Deserialize)]
19pub struct EnumValue {
20    /// Canonical owning enum, including Res/Opt arguments.
21    pub ty: Type,
22    /// Declared case name.
23    pub case: String,
24    /// Typed payload in declaration order.
25    pub payload: Vec<Value>,
26    /// Source operation that produced a recoverable failure, when available.
27    #[serde(default, skip_deserializing, skip_serializing_if = "Option::is_none")]
28    pub origin: Option<crate::Span>,
29}
30impl PartialEq for EnumValue {
31    fn eq(&self, other: &Self) -> bool {
32        self.ty == other.ty && self.case == other.case && self.payload == other.payload
33    }
34}
35/// An inspectable callable identity and canonical static signature. Values contain
36/// no executable code or host pointer. Machine entry points validate deserialized identities.
37#[cfg_attr(feature = "typescript", derive(ts_rs::TS))]
38#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
39pub struct FunctionValue {
40    pub(crate) target: FunctionTarget,
41    /// Canonical parameter and return types, including nominal module identities.
42    pub signature: Type,
43}
44#[cfg_attr(feature = "typescript", derive(ts_rs::TS))]
45#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
46#[serde(tag = "kind", rename_all = "snake_case")]
47/// Stable callable identity included in an inspectable [`FunctionValue`].
48pub enum FunctionTarget {
49    /// A fundamental data method retaining its immutable copied receiver.
50    DataMethod {
51        /// Canonical receiver type, including collection element types.
52        receiver_type: Type,
53        /// Owned method name.
54        method: String,
55        /// Optional explicit numeric conversion target.
56        type_argument: Option<Box<Type>>,
57        /// Immutable receiver value.
58        receiver: Box<Value>,
59    },
60    /// A typed enum payload constructor.
61    EnumConstructor {
62        /// Canonical enum type.
63        ty: Type,
64        /// Declared case name.
65        case: String,
66    },
67    /// A checked named function from the main module or a supplied module.
68    Named {
69        /// Owning source module.
70        module: String,
71        /// Declared function name.
72        name: String,
73    },
74    /// A registered host operation with no source module.
75    Native {
76        /// Registered operation name.
77        name: String,
78    },
79    /// A polymorphic intrinsic, optionally specialized to one type.
80    Builtin {
81        /// Intrinsic name.
82        name: String,
83        /// Concrete type selected for a polymorphic intrinsic.
84        #[serde(default, skip_serializing_if = "Option::is_none")]
85        type_argument: Option<Box<Type>>,
86    },
87    /// A method whose copied record receiver is retained in the value.
88    BoundMethod {
89        /// Nominal receiver type.
90        class: String,
91        /// Declared method name.
92        method: String,
93        /// Copied record receiver.
94        receiver: Box<Value>,
95    },
96    /// A method whose live component receiver is resolved when invoked.
97    EntityMethod {
98        /// Nominal component type.
99        class: String,
100        /// Declared method name.
101        method: String,
102        /// Stable live entity identity.
103        #[cfg_attr(feature = "typescript", ts(type = "number"))]
104        entity: u64,
105    },
106}
107/// An entity handle paired with a concrete component identity, created by `bind[T]`.
108#[cfg_attr(feature = "typescript", derive(ts_rs::TS))]
109#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
110pub struct ComponentReference {
111    /// Stable entity identity; the component is resolved again at invocation.
112    #[cfg_attr(feature = "typescript", ts(type = "number"))]
113    pub entity: u64,
114    /// Canonical concrete component type.
115    pub class: String,
116}
117// Keep the language reference and Rust variant docs on the same source definition.
118macro_rules! documented_values {
119    ($( $(#[doc = $doc:literal])* $variant:ident $(( $(#[$payload_attr:meta])* $payload:ty ))?, )*) => {
120        /// Stable JSON representation shared by native and WebAssembly hosts.
121        #[cfg_attr(feature = "typescript", derive(ts_rs::TS))]
122        #[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
123        #[serde(tag = "kind", content = "value", rename_all = "snake_case")]
124        pub enum Value {
125            $( $(#[doc = $doc])* $variant $(( $(#[$payload_attr])* $payload ))?, )*
126        }
127
128        pub(crate) fn value_documentation(variant: &str) -> &'static str {
129            match variant {
130                $(stringify!($variant) => concat!($($doc, "\n",)*),)*
131                _ => unreachable!("a primitive reference must name a Value variant"),
132            }
133        }
134    };
135}
136
137documented_values! {
138    /// A nominal algebraic case or a built-in Res/Opt value with checked payloads.
139    ///
140    /// Matching inspects the case and binds payload values. There is no null value
141    /// or exception syntax; recoverable operation failures are ordinary Err values.
142    Enum(Box<EnumValue>),
143    /// A finite IEEE 754 binary64 scalar with canonical language type `f64`.
144    ///
145    /// `Number` remains a source alias for f64 and this host representation remains
146    /// compatible with native spatial interfaces. Literals without numeric context
147    /// default to f64. Exact integer widths and f16/f32/f128 use Scalar instead.
148    /// Numeric variables never implicitly change dtype; use `.convert[T]()` for an
149    /// explicit conversion, which rejects overflow and loss of information.
150    Number(f64),
151    /// An exact width-specific numeric scalar.
152    ///
153    /// Supported widths are u8 through u128, i8 through i128, f16, f32, and f128. Integer arithmetic checks overflow and floating arithmetic
154    /// rounds once to the declared IEEE format. Serialized decimal strings preserve
155    /// values across JSON hosts without passing wide integers through binary64.
156    Scalar(#[cfg_attr(feature = "typescript", ts(type = "ScalarWire"))] Scalar),
157    /// An immutable packed `Tensor[T]` with shared storage and checked shape/stride views.
158    ///
159    /// A tensor stores one numeric dtype and a runtime shape. Comma-separated indices
160    /// select scalar elements; slices preserve tensors and share their allocation.
161    /// Arithmetic broadcasts trailing dimensions; `@` multiplies matrices and batches.
162    /// Arithmetic and indexing return `Res`; `get` returns `Opt` for a scalar lookup.
163    /// Dtypes must match, with contextual typing reserved for literal operands.
164    Tensor(#[cfg_attr(feature = "typescript", ts(type = "TensorWire"))] Box<Tensor>),
165    /// Immutable bytes without an implied text encoding.
166    ///
167    /// Cloning shares a packed Arc allocation. `bytes` constructs `Bin` from `List[u8]`,
168    /// `Str.utf8` encodes text, and `Bin.decode` validates UTF-8 with a `Res` result.
169    /// Indexing returns `Res[u8, DataError]`; slicing produces `Res[Bin, DataError]`.
170    /// Concatenation creates independent data and respects byte limits.
171    Bin(#[cfg_attr(feature = "typescript", ts(type = "Array<number>"))] Bin),
172    /// A Boolean value used by conditions and short-circuit expressions.
173    ///
174    /// `Bool` stores a Rust `bool` and has exactly two values: `true` and `false`.
175    /// `if` and `while` require Bool; numeric zero and empty collections do not
176    /// act as false. `and` and `or` evaluate their right side only when needed.
177    Bool(bool),
178    /// Immutable UTF-8 text with canonical language type `Str` (`String` is an alias).
179    ///
180    /// Clones share an Arc allocation. Concatenation creates a new value without
181    /// implicit conversion. `len`, indexing, and slices count Unicode scalar values,
182    /// while allocation limits count UTF-8 bytes. `utf8` encodes to Bin and `decode`
183    /// validates Bin as UTF-8. Text is the separate visible scene component.
184    Text(#[cfg_attr(feature = "typescript", ts(type = "string"))] Str),
185    /// An ordered owned collection whose elements share one language type.
186    ///
187    /// `List[T]` stores Rust `Vec<Value>` data. The compiler retains T and the
188    /// runtime checks elements at typed boundaries. An empty literal needs a
189    /// contextual element type, such as `let values: List[f64] = [];`.
190    ///
191    /// Indexing starts at zero and rejects out-of-range indices. Assignment
192    /// copies a list value; `.append(value)` returns a new list. `.get(index)`
193    /// returns `Opt[T]`, while indexing returns `Res[T, DataError]`. Collection length and
194    /// nested values remain subject to the embedding host's Limits.
195    List(Vec<Value>),
196    /// A nominal type instance with checked fields.
197    ///
198    /// A RecordValue owns its field map. Constructing a type creates data;
199    /// spawn attaches it to an ECS entity. Prelude types use this same
200    /// representation as user types, rather than separate Rust struct aliases.
201    Record(RecordValue),
202    /// A statically typed callable with inspectable identity.
203    ///
204    /// FunctionValue stores a checked signature and target identity, not executable
205    /// code or a raw pointer. Language and native function values use the same
206    /// argument and result validation. Mutating component methods use bind.
207    Function(Box<FunctionValue>),
208    /// A typed entity/component identity used to capture live component methods.
209    ///
210    /// `ComponentReference[T]` pairs a Rust entity ID with a canonical concrete
211    /// type name. `bind[T](entity)` validates that the component exists; method
212    /// calls resolve it again and commit accepted receiver changes transactionally.
213    ///
214    /// This is an interpreter handle, not a Rust borrow or raw memory reference.
215    /// Access component data with get and set; invoke methods through the
216    /// reference. Invoking a reference after its entity disappears is an error.
217    ComponentReference(ComponentReference),
218    /// An opaque ECS identity created by spawn.
219    ///
220    /// `Entity` stores a Rust u64 handle. IDs are allocated in creation order
221    /// and cannot be constructed from a Number in the language. Copying a handle
222    /// keeps the same identity; it does not duplicate the entity or its components.
223    ///
224    /// Use get, set, has and bind with a concrete component type. A handle can
225    /// outlive its entity, but subsequent access to a removed entity is rejected.
226    Entity(#[cfg_attr(feature = "typescript", ts(type = "number"))] u64),
227    /// The single empty value returned by procedures.
228    ///
229    /// `Unit` is represented by the payload-free Rust Value::Unit variant and
230    /// spelled `()` in the language. A function returning Unit may end without
231    /// a value or use `ret;`. Unit is not null, an absent field, or an optional value.
232    Unit,
233}
234
235impl Value {
236    /// Construct an algebraic value for a host boundary, where its type and payload are validated.
237    pub fn algebraic(value: EnumValue) -> Self {
238        Self::Enum(Box::new(value))
239    }
240    /// Assemble a record for a host input. Machine entry points validate its declared fields.
241    pub fn record(
242        class: impl Into<String>,
243        fields: impl IntoIterator<Item = (String, Value)>,
244    ) -> Self {
245        Self::Record(RecordValue {
246            class: class.into(),
247            fields: fields.into_iter().collect(),
248        })
249    }
250    /// Read a numeric value without coercion.
251    pub fn number(&self) -> Option<f64> {
252        if let Self::Number(n) = self {
253            Some(*n)
254        } else {
255            None
256        }
257    }
258    /// Borrow a field of a type instance, if present.
259    pub fn field(&self, key: &str) -> Option<&Value> {
260        if let Self::Record(r) = self {
261            r.fields.get(key)
262        } else {
263            None
264        }
265    }
266    /// Return the nominal language type name; Str is distinct from type Text.
267    pub fn type_name(&self) -> &str {
268        match self {
269            Self::Enum(value) => match &value.ty {
270                Type::Named(name) | Type::Applied { name, .. } => name,
271                _ => "Enum",
272            },
273            Self::Number(_) => "f64",
274            Self::Scalar(value) => crate::data::dtype_name(value.dtype()),
275            Self::Tensor(_) => "Tensor",
276            Self::Bin(_) => "Bin",
277            Self::Bool(_) => "Bool",
278            Self::Text(_) => "Str",
279            Self::List(_) => "List",
280            Self::Record(r) => &r.class,
281            Self::Entity(_) => "Entity",
282            Self::Unit => "Unit",
283            Self::Function(_) => "Function",
284            Self::ComponentReference(_) => "ComponentReference",
285        }
286    }
287}
288impl std::fmt::Display for Value {
289    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
290        match self {
291            Self::Enum(value) => {
292                write!(f, "{}", value.case)?;
293                if !value.payload.is_empty() {
294                    write!(f, "(")?;
295                    for (index, item) in value.payload.iter().enumerate() {
296                        if index != 0 {
297                            write!(f, ", ")?;
298                        }
299                        write!(f, "{item}")?;
300                    }
301                    write!(f, ")")?;
302                }
303                Ok(())
304            }
305            Self::Number(n) => write!(f, "{n}"),
306            Self::Scalar(n) => write!(f, "{n}"),
307            Self::Tensor(t) => {
308                write!(
309                    f,
310                    "Tensor[{}]{:?} [",
311                    crate::data::dtype_name(t.dtype()),
312                    t.shape()
313                )?;
314                let mut indices = vec![0isize; t.shape().len()];
315                for flat in 0..t.len() {
316                    if flat > 0 {
317                        write!(f, ", ")?;
318                    }
319                    let mut remaining = flat;
320                    for axis in (0..indices.len()).rev() {
321                        indices[axis] = (remaining % t.shape()[axis]) as isize;
322                        remaining /= t.shape()[axis];
323                    }
324                    let scalar = t.get(&indices).map_err(|_| std::fmt::Error)?;
325                    write!(f, "{scalar}")?;
326                }
327                write!(f, "]")
328            }
329            Self::Bin(b) => write!(f, "bytes({:?})", b.as_bytes()),
330            Self::Bool(b) => write!(f, "{b}"),
331            Self::Text(s) => write!(f, "{s}"),
332            Self::Unit => write!(f, "()"),
333            Self::Function(function) => write!(f, "func {:?}", function.target),
334            Self::ComponentReference(reference) => {
335                write!(f, "bind[{}]({})", reference.class, reference.entity)
336            }
337            Self::Entity(id) => write!(f, "entity({id})"),
338            Self::List(values) => {
339                write!(f, "[")?;
340                for (i, v) in values.iter().enumerate() {
341                    if i > 0 {
342                        write!(f, ", ")?;
343                    }
344                    write!(f, "{v}")?;
345                }
346                write!(f, "]")
347            }
348            Self::Record(record) => {
349                write!(f, "{} {{ ", record.class)?;
350                for (i, (key, value)) in record.fields.iter().enumerate() {
351                    if i > 0 {
352                        write!(f, ", ")?;
353                    }
354                    write!(f, "{key}: {value}")?;
355                }
356                write!(f, " }}")
357            }
358        }
359    }
360}