cjc_runtime/value.rs
1//! The universal runtime value type for the CJC interpreter.
2//!
3//! This module defines [`Value`], the tagged union that represents every
4//! runtime value in both the AST interpreter (`cjc-eval`) and the MIR
5//! executor (`cjc-mir-exec`). It also provides the [`Bf16`] brain-float
6//! type and [`FnValue`] for function references.
7//!
8//! # Memory Model
9//!
10//! - **Scalars** (`Int`, `Float`, `Bool`, `U8`, `Bf16`, `F16`, `Complex`) are
11//! stored inline -- no heap allocation.
12//! - **Heap types** (`String`, `Array`, `Tuple`, `Map`, `Bytes`, `Tensor`)
13//! use `Rc<...>` for O(1) clone with copy-on-write mutation semantics.
14//! - **Type-erased objects** (`GradGraph`, `OptimizerState`, `TidyView`,
15//! `VizorPlot`, `QuantumState`) use `Rc<dyn Any>` or `Rc<RefCell<dyn Any>>`
16//! to avoid circular crate dependencies.
17//!
18//! # NA Semantics
19//!
20//! [`Value::Na`] is the missing-value sentinel. It propagates through
21//! arithmetic (`NA + x -> NA`) and compares unequal to everything including
22//! itself (`NA == NA -> false`). Test with `is_na()`.
23
24use std::any::Any;
25use std::collections::BTreeMap;
26use std::fmt;
27use std::rc::Rc;
28use std::cell::RefCell;
29
30use crate::aligned_pool::AlignedByteSlice;
31use crate::complex;
32use crate::det_map::DetMap;
33use crate::gc::GcRef;
34use crate::paged_kv::PagedKvCache;
35use crate::scratchpad::Scratchpad;
36use crate::sparse::SparseCsr;
37use crate::tensor::Tensor;
38
39// ---------------------------------------------------------------------------
40// 7. Value enum for the interpreter
41// ---------------------------------------------------------------------------
42
43/// Brain-float 16-bit floating-point type (bf16).
44///
45/// Stores a `u16` that represents the upper 16 bits of an IEEE 754 `f32`.
46/// All arithmetic is performed by widening to `f32`, computing, then
47/// narrowing back -- this ensures deterministic results regardless of
48/// hardware bf16 support.
49///
50/// # Determinism
51///
52/// Conversion uses truncation (not rounding) of the lower 16 mantissa bits,
53/// guaranteeing identical results across all platforms.
54#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
55pub struct Bf16(pub u16);
56
57impl Bf16 {
58 /// Convert f32 to bf16 by truncating lower 16 mantissa bits.
59 pub fn from_f32(v: f32) -> Self {
60 Bf16((v.to_bits() >> 16) as u16)
61 }
62
63 /// Convert bf16 to f32 by shifting left 16 bits.
64 pub fn to_f32(self) -> f32 {
65 f32::from_bits((self.0 as u32) << 16)
66 }
67
68 /// Add two bf16 values (widen to f32, add, narrow back).
69 pub fn add(self, rhs: Self) -> Self {
70 Self::from_f32(self.to_f32() + rhs.to_f32())
71 }
72
73 /// Subtract two bf16 values (widen to f32, subtract, narrow back).
74 pub fn sub(self, rhs: Self) -> Self {
75 Self::from_f32(self.to_f32() - rhs.to_f32())
76 }
77
78 /// Multiply two bf16 values (widen to f32, multiply, narrow back).
79 pub fn mul(self, rhs: Self) -> Self {
80 Self::from_f32(self.to_f32() * rhs.to_f32())
81 }
82
83 /// Divide two bf16 values (widen to f32, divide, narrow back).
84 pub fn div(self, rhs: Self) -> Self {
85 Self::from_f32(self.to_f32() / rhs.to_f32())
86 }
87
88 /// Negate a bf16 value (widen to f32, negate, narrow back).
89 pub fn neg(self) -> Self {
90 Self::from_f32(-self.to_f32())
91 }
92}
93
94impl fmt::Display for Bf16 {
95 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
96 write!(f, "{}", self.to_f32())
97 }
98}
99
100/// A CJC function value — either a named function or a closure.
101#[derive(Debug, Clone)]
102pub struct FnValue {
103 /// Function name (or `"<lambda>"` for anonymous closures).
104 pub name: String,
105 /// Number of parameters.
106 pub arity: usize,
107 /// Opaque identifier used by the interpreter to locate the function body.
108 pub body_id: usize,
109}
110
111/// The universal value type for the CJC interpreter.
112///
113/// Every runtime value -- from scalars to tensors to closures -- is
114/// represented as a variant of this enum. Both the AST interpreter
115/// (`cjc-eval`) and the MIR executor (`cjc-mir-exec`) operate on `Value`.
116///
117/// # Clone Semantics
118///
119/// Cloning a `Value` is cheap for heap-backed variants: `String`, `Array`,
120/// `Tuple`, `Map`, `Bytes`, `ByteSlice`, and `Tensor` all use `Rc`
121/// internally, so `clone()` increments a refcount without copying data.
122/// Mutation (e.g., `array_push`) returns a *new* `Value` (functional
123/// immutability), or triggers COW when the `Rc` refcount is 1.
124///
125/// # Display
126///
127/// All variants implement [`fmt::Display`] for user-facing output.
128/// The format matches CJC's `print()` builtin behavior.
129#[derive(Debug, Clone)]
130pub enum Value {
131 /// 64-bit signed integer.
132 Int(i64),
133 /// 64-bit IEEE 754 float.
134 Float(f64),
135 /// Boolean value.
136 Bool(bool),
137 /// Heap-allocated string with COW sharing via `Rc`.
138 String(Rc<String>),
139 /// Owning byte buffer.
140 Bytes(Rc<RefCell<Vec<u8>>>),
141 /// Non-owning byte slice view. In the interpreter this is an owning
142 /// snapshot (Vec<u8>) since we can't borrow across eval boundaries.
143 /// The compiler/MIR path can use true zero-copy slices.
144 ByteSlice(Rc<Vec<u8>>),
145 /// Validated UTF-8 string view (same representation as ByteSlice but
146 /// guaranteed valid UTF-8).
147 StrView(Rc<Vec<u8>>),
148 /// Single byte value (u8).
149 U8(u8),
150 /// N-dimensional tensor backed by [`Buffer<f64>`](crate::buffer::Buffer).
151 Tensor(Tensor),
152 /// Sparse matrix in CSR (Compressed Sparse Row) format.
153 SparseTensor(SparseCsr),
154 /// Deterministic hash map with interior mutability. Iteration order is
155 /// fixed by [`DetMap`]'s MurmurHash3-based bucket ordering.
156 Map(Rc<RefCell<DetMap>>),
157 /// Copy-on-write array. `Rc` provides O(1) clone; `Rc::make_mut()`
158 /// triggers a deep copy only when the array is mutated and shared.
159 Array(Rc<Vec<Value>>),
160 /// Named struct with ordered fields. Field order is deterministic
161 /// because fields are stored in a [`BTreeMap`].
162 Struct {
163 /// The struct type name (e.g., `"Point"`).
164 name: String,
165 /// Field name -> value mapping, ordered alphabetically.
166 fields: BTreeMap<String, Value>,
167 },
168 /// Copy-on-write tuple. Same COW semantics as Array.
169 Tuple(Rc<Vec<Value>>),
170 /// Reference to a GC-managed class instance in the [`GcHeap`](crate::gc::GcHeap).
171 ClassRef(GcRef),
172 /// A named function reference (not a closure -- no captured environment).
173 Fn(FnValue),
174 /// A closure: a function name + captured environment values.
175 Closure {
176 fn_name: String,
177 env: Vec<Value>,
178 /// Arity of the *original* lambda params (not including captures).
179 arity: usize,
180 },
181 /// Enum variant value: `Some(42)`, `None`, `Ok(v)`, `Err(e)`
182 Enum {
183 enum_name: String,
184 variant: String,
185 fields: Vec<Value>,
186 },
187 /// Compiled regex pattern: (pattern, flags)
188 Regex { pattern: String, flags: String },
189 /// bf16 brain-float: u16-backed, deterministic f32 conversions
190 Bf16(Bf16),
191 /// f16 half-precision: u16-backed IEEE 754 binary16
192 F16(crate::f16::F16),
193 /// Complex f64: deterministic fixed-sequence arithmetic
194 Complex(complex::ComplexF64),
195 /// Pre-allocated KV-cache scratchpad for zero-allocation inference.
196 ///
197 /// **Runtime-only:** No corresponding `Type::Scratchpad` exists. Created
198 /// via `Scratchpad.new()` builtin. Type-checking treats it as opaque.
199 Scratchpad(Rc<RefCell<Scratchpad>>),
200 /// Block-paged KV-cache (vLLM-style).
201 ///
202 /// **Runtime-only:** No corresponding `Type::PagedKvCache` exists. Created
203 /// via `PagedKvCache.new()` builtin. Type-checking treats it as opaque.
204 PagedKvCache(Rc<RefCell<PagedKvCache>>),
205 /// Aligned byte slice with 16-byte alignment guarantee.
206 ///
207 /// **Runtime-only:** No corresponding `Type::AlignedBytes` exists. Created
208 /// via `AlignedByteSlice.from_bytes()` builtin. Type-checking treats it
209 /// as opaque.
210 AlignedBytes(AlignedByteSlice),
211 /// Type-erased reverse-mode AD graph. Concrete type: `cjc_ad::GradGraph`.
212 /// Uses `Rc<RefCell<dyn Any>>` because cjc-runtime cannot depend on cjc-ad.
213 /// Construction and method dispatch happen in cjc-eval and cjc-mir-exec.
214 GradGraph(Rc<RefCell<dyn Any>>),
215 /// Type-erased optimizer state. Concrete types: AdamState or SgdState (from ml.rs).
216 /// Uses `Rc<RefCell<dyn Any>>` for interior mutability (step updates internal state).
217 OptimizerState(Rc<RefCell<dyn Any>>),
218 /// Type-erased tidy data view. Concrete type is `cjc_data::TidyView`.
219 /// Wrapped in `Rc<dyn Any>` for cheap cloning without circular deps
220 /// (cjc-runtime cannot depend on cjc-data).
221 ///
222 /// Dispatch is handled by `cjc_data::tidy_dispatch::dispatch_tidy_method`.
223 TidyView(Rc<dyn Any>),
224 /// Type-erased grouped tidy view. Concrete type is
225 /// `cjc_data::GroupedTidyView`. Same erasure strategy as TidyView.
226 GroupedTidyView(Rc<dyn Any>),
227 /// Type-erased Vizor plot specification. Concrete type is
228 /// `cjc_vizor::spec::PlotSpec`. Same erasure strategy as TidyView.
229 ///
230 /// Dispatch is handled by `cjc_vizor::dispatch::dispatch_vizor_method`.
231 VizorPlot(Rc<dyn Any>),
232 /// Type-erased quantum state (circuit or statevector).
233 /// Concrete type: `cjc_quantum::Circuit` or `cjc_quantum::Statevector`.
234 /// Uses `Rc<RefCell<dyn Any>>` for interior mutability (measurement collapses state).
235 /// Construction and method dispatch happen in cjc-eval and cjc-mir-exec.
236 QuantumState(Rc<RefCell<dyn Any>>),
237 /// Missing value sentinel. Propagates through arithmetic (NA + x → NA),
238 /// compares unequal to everything including itself (NA == NA → false).
239 /// Test with `is_na()`. This is the only way to detect NA.
240 Na,
241 Void,
242}
243
244impl Value {
245 /// Return a human-readable type name string for error messages and debugging.
246 ///
247 /// The returned string matches the CJC type system names (e.g., `"Int"`,
248 /// `"Float"`, `"Tensor"`, `"Array"`, `"Struct"`, `"Closure"`).
249 pub fn type_name(&self) -> &str {
250 match self {
251 Value::Int(_) => "Int",
252 Value::Float(_) => "Float",
253 Value::Bool(_) => "Bool",
254 Value::String(_) => "String",
255 Value::Bytes(_) => "Bytes",
256 Value::ByteSlice(_) => "ByteSlice",
257 Value::StrView(_) => "StrView",
258 Value::U8(_) => "u8",
259 Value::Tensor(_) => "Tensor",
260 Value::SparseTensor(_) => "SparseTensor",
261 Value::Map(_) => "Map",
262 Value::Array(_) => "Array",
263 Value::Tuple(_) => "Tuple",
264 Value::Struct { .. } => "Struct",
265 Value::Enum { .. } => "Enum",
266 Value::ClassRef(_) => "ClassRef",
267 Value::Fn(_) => "Fn",
268 Value::Closure { .. } => "Closure",
269 Value::Regex { .. } => "Regex",
270 Value::Bf16(_) => "Bf16",
271 Value::F16(_) => "F16",
272 Value::Complex(_) => "Complex",
273 Value::Scratchpad(_) => "Scratchpad",
274 Value::PagedKvCache(_) => "PagedKvCache",
275 Value::AlignedBytes(_) => "AlignedBytes",
276 Value::GradGraph(_) => "GradGraph",
277 Value::OptimizerState(_) => "OptimizerState",
278 Value::TidyView(_) => "TidyView",
279 Value::GroupedTidyView(_) => "GroupedTidyView",
280 Value::VizorPlot(_) => "VizorPlot",
281 Value::QuantumState(_) => "QuantumState",
282 Value::Na => "Na",
283 Value::Void => "Void",
284 }
285 }
286}
287
288impl fmt::Display for Value {
289 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
290 match self {
291 Value::Int(v) => write!(f, "{v}"),
292 Value::Float(v) => write!(f, "{v}"),
293 Value::Bool(v) => write!(f, "{v}"),
294 Value::String(v) => write!(f, "{v}"),
295 Value::Bytes(b) => {
296 let b = b.borrow();
297 write!(f, "Bytes([")?;
298 for (i, byte) in b.iter().enumerate() {
299 if i > 0 { write!(f, ", ")?; }
300 write!(f, "{byte}")?;
301 }
302 write!(f, "])")
303 }
304 Value::ByteSlice(b) => {
305 // Try to display as UTF-8, fall back to hex
306 match std::str::from_utf8(b) {
307 Ok(s) => write!(f, "b\"{s}\""),
308 Err(_) => {
309 write!(f, "b\"")?;
310 for &byte in b.iter() {
311 if byte.is_ascii_graphic() || byte == b' ' {
312 write!(f, "{}", byte as char)?;
313 } else {
314 write!(f, "\\x{byte:02x}")?;
315 }
316 }
317 write!(f, "\"")
318 }
319 }
320 }
321 Value::StrView(b) => {
322 // StrView is guaranteed valid UTF-8
323 let s = std::str::from_utf8(b).unwrap_or("<invalid utf8>");
324 write!(f, "{s}")
325 }
326 Value::U8(v) => write!(f, "{v}"),
327 Value::Tensor(t) => write!(f, "{t}"),
328 Value::SparseTensor(s) => write!(f, "SparseTensor({}x{}, nnz={})", s.nrows, s.ncols, s.nnz()),
329 Value::Map(m) => {
330 let m = m.borrow();
331 write!(f, "Map({{")?;
332 for (i, (k, v)) in m.iter().enumerate() {
333 if i > 0 {
334 write!(f, ", ")?;
335 }
336 write!(f, "{k}: {v}")?;
337 }
338 write!(f, "}})")
339 }
340 Value::Array(arr) => {
341 write!(f, "[")?;
342 for (i, v) in arr.iter().enumerate() {
343 if i > 0 {
344 write!(f, ", ")?;
345 }
346 write!(f, "{v}")?;
347 }
348 write!(f, "]")
349 }
350 Value::Tuple(elems) => {
351 write!(f, "(")?;
352 for (i, v) in elems.iter().enumerate() {
353 if i > 0 {
354 write!(f, ", ")?;
355 }
356 write!(f, "{v}")?;
357 }
358 write!(f, ")")
359 }
360 Value::Struct { name, fields } => {
361 write!(f, "{name} {{ ")?;
362 for (i, (k, v)) in fields.iter().enumerate() {
363 if i > 0 {
364 write!(f, ", ")?;
365 }
366 write!(f, "{k}: {v}")?;
367 }
368 write!(f, " }}")
369 }
370 Value::Enum {
371 enum_name: _,
372 variant,
373 fields,
374 } => {
375 write!(f, "{variant}")?;
376 if !fields.is_empty() {
377 write!(f, "(")?;
378 for (i, v) in fields.iter().enumerate() {
379 if i > 0 {
380 write!(f, ", ")?;
381 }
382 write!(f, "{v}")?;
383 }
384 write!(f, ")")?;
385 }
386 Ok(())
387 }
388 Value::Regex { pattern, flags } => {
389 write!(f, "/{pattern}/")?;
390 if !flags.is_empty() {
391 write!(f, "{flags}")?;
392 }
393 Ok(())
394 }
395 Value::Bf16(v) => write!(f, "{}", v.to_f32()),
396 Value::F16(v) => write!(f, "{}", v.to_f64()),
397 Value::Complex(z) => write!(f, "{z}"),
398 Value::ClassRef(r) => write!(f, "<object@{}>", r.index),
399 Value::Fn(fv) => write!(f, "<fn {}({})>", fv.name, fv.arity),
400 Value::Closure {
401 fn_name, arity, ..
402 } => write!(f, "<closure {}({})>", fn_name, arity),
403 Value::Scratchpad(s) => write!(f, "{}", s.borrow()),
404 Value::PagedKvCache(c) => write!(f, "{}", c.borrow()),
405 Value::AlignedBytes(a) => write!(f, "{}", a),
406 Value::GradGraph(_) => write!(f, "<GradGraph>"),
407 Value::OptimizerState(_) => write!(f, "<OptimizerState>"),
408 Value::TidyView(_) => write!(f, "<TidyView>"),
409 Value::GroupedTidyView(_) => write!(f, "<GroupedTidyView>"),
410 Value::VizorPlot(_) => write!(f, "<VizorPlot>"),
411 Value::QuantumState(_) => write!(f, "<QuantumState>"),
412 Value::Na => write!(f, "NA"),
413 Value::Void => write!(f, "void"),
414 }
415 }
416}
417
418#[cfg(test)]
419mod tests {
420 use super::*;
421 use std::rc::Rc;
422
423 #[test]
424 fn int_display() {
425 assert_eq!(format!("{}", Value::Int(42)), "42");
426 assert_eq!(format!("{}", Value::Int(-1)), "-1");
427 }
428
429 #[test]
430 fn float_display() {
431 let s = format!("{}", Value::Float(3.14));
432 assert!(s.starts_with("3.14"), "got: {s}");
433 }
434
435 #[test]
436 fn bool_display() {
437 assert_eq!(format!("{}", Value::Bool(true)), "true");
438 assert_eq!(format!("{}", Value::Bool(false)), "false");
439 }
440
441 #[test]
442 fn string_display() {
443 let v = Value::String(Rc::new("hello".to_string()));
444 assert_eq!(format!("{v}"), "hello");
445 }
446
447 #[test]
448 fn void_display() {
449 assert_eq!(format!("{}", Value::Void), "void");
450 }
451
452 #[test]
453 fn type_name_coverage() {
454 assert_eq!(Value::Int(0).type_name(), "Int");
455 assert_eq!(Value::Float(0.0).type_name(), "Float");
456 assert_eq!(Value::Bool(true).type_name(), "Bool");
457 assert_eq!(Value::String(Rc::new(String::new())).type_name(), "String");
458 assert_eq!(Value::Void.type_name(), "Void");
459 }
460
461 #[test]
462 fn tuple_display() {
463 let t = Value::Tuple(Rc::new(vec![
464 Value::Int(1),
465 Value::Bool(true),
466 ]));
467 let s = format!("{t}");
468 assert!(s.contains("1"), "tuple should contain 1, got: {s}");
469 assert!(s.contains("true"), "tuple should contain true, got: {s}");
470 }
471
472 #[test]
473 fn array_display() {
474 let a = Value::Array(Rc::new(vec![
475 Value::Int(10),
476 Value::Int(20),
477 ]));
478 let s = format!("{a}");
479 assert!(s.contains("10"), "array should contain 10, got: {s}");
480 assert!(s.contains("20"), "array should contain 20, got: {s}");
481 }
482
483 #[test]
484 fn struct_value_display() {
485 let mut fields = std::collections::BTreeMap::new();
486 fields.insert("x".to_string(), Value::Int(1));
487 fields.insert("y".to_string(), Value::Int(2));
488 let sv = Value::Struct {
489 name: "Point".to_string(),
490 fields,
491 };
492 let s = format!("{sv}");
493 assert!(s.contains("Point"), "struct display should contain name, got: {s}");
494 }
495
496 #[test]
497 fn enum_value_display() {
498 let ev = Value::Enum {
499 enum_name: "Option".to_string(),
500 variant: "Some".to_string(),
501 fields: vec![Value::Int(42)],
502 };
503 let s = format!("{ev}");
504 assert!(s.contains("Some"), "enum display should contain variant, got: {s}");
505 }
506
507 #[test]
508 fn map_display() {
509 let m = Value::Map(Rc::new(std::cell::RefCell::new(crate::det_map::DetMap::new())));
510 let s = format!("{m}");
511 assert!(s.contains("{") || s.contains("Map"), "map display should be readable, got: {s}");
512 }
513}
514