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}