shape_vm/executor/vm_impl/builtins.rs
1//! Builtin dispatch slice (ADR-006 §2.7.6 / Q8).
2//!
3//! Wave 5a (phase-1b-vm) flipped the dispatch SHAPE here: every arm now
4//! produces / consumes `Vec<KindedSlot>` (and `&[KindedSlot]`), aligned
5//! with the carrier-API bound spec'd at §2.7.6. The body interiors
6//! (math kernels, array kernels, content builders, type-introspection,
7//! stats, intrinsics, JSON helpers, table builders, content / DateTime /
8//! concurrency constructors) are deferred to Waves 5b-5e.
9//!
10//! - **Wave 5b (LANDED)**: math + array + utility bodies (`builtin_abs`,
11//! `builtin_push`, `builtin_object_rest`, `builtin_snapshot`,
12//! `builtin_exit`, etc.) are now `Fn(&[KindedSlot], ...) -> Result<KindedSlot, VMError>`
13//! and the dispatch arms call them directly.
14//! - **Wave 5c**: type-introspection + conversion + native-interop bodies
15//! (`builtin_is_*`, `builtin_to_*`, `dispatch_native_interop_builtin`).
16//! - **Wave 5d**: closure-driven array builtins (`map`, `filter`, `reduce`,
17//! etc.) + intrinsic dispatch (`handle_intrinsic_builtin`,
18//! `handle_vector_intrinsic`, `handle_matrix_intrinsic`).
19//! - **Wave 5e**: content + DateTime + concurrency constructors + window /
20//! join / reflect / state-builtin bodies + `executor/printing.rs` formatter.
21//!
22//! The companion §2.7.6 / Q8 carrier-API bound: NO per-heap-variant
23//! accessors on `KindedSlot`; bodies that inspect heap payloads use
24//! `slot.as_heap_value()` + `HeapValue` match. NO cross-kind accessors
25//! (`as_number_coerce`, etc.) on the carrier; coercion lives at
26//! `executor/builtins/kind_coerce.rs` (free helper at the body site).
27//!
28//! # `pop_builtin_args` runtime semantics (Wave 6: kinded stack ABI)
29//!
30//! Wave 6 (ADR-006 §2.7.7 / Q9) added a parallel `Vec<NativeKind>` track
31//! to the VM stack. `pop_builtin_args` now reads the per-arg `NativeKind`
32//! directly from the parallel track via `pop_kinded()`. Wave 5b's
33//! transitional `NativeKind::Bool` sentinel is removed — every arg's kind
34//! is the kind that the producing opcode emitted into the parallel track
35//! at push time.
36//!
37//! **Ownership transfer**: `pop_kinded()` moves one strong-count share
38//! (for heap-bearing kinds) out of the stack slot into the returned
39//! tuple. Wrapping it in a `KindedSlot` transfers that share to the
40//! carrier; `KindedSlot::Drop` retires the share when the args `Vec` is
41//! dropped at the end of the builtin call. **No `clone_with_kind`
42//! needed** here — that's only for `read_owned_kinded` (which keeps the
43//! slot live on the stack while handing a share out).
44
45use super::super::*;
46use shape_value::{KindedSlot, VMError, ValueSlot};
47
48impl VirtualMachine {
49 /// Pop the builtin call's args off the typed VM stack into a
50 /// `Vec<KindedSlot>` (ADR-006 §2.7.7 / Q9).
51 ///
52 /// The topmost stack slot is the arg count (pushed as a numeric
53 /// constant by the compiler). Each subsequent pop hands back the raw
54 /// u64 bits **plus** the `NativeKind` recorded by the producing opcode
55 /// in the parallel kinds track.
56 ///
57 /// **Ownership**: `pop_kinded()` transfers the slot's strong-count
58 /// share into the returned tuple; wrapping it in a `KindedSlot`
59 /// transfers ownership to the carrier. `KindedSlot::Drop` retires the
60 /// share when the returned `Vec` goes out of scope.
61 pub(crate) fn pop_builtin_args(&mut self) -> Result<Vec<KindedSlot>, VMError> {
62 // Top of stack: the arg count, pushed as a typed integer constant
63 // by the compiler (`PushConst(Int(arg_count as i64))`). The count
64 // slot is an integer-family inline scalar; `int_operand` dispatches
65 // per the §2.7.6 heterogeneous-kind body pattern (same shape as
66 // `op_call` / `op_call_value` use).
67 //
68 // Historical note (W17-make-closure): prior to the arg-count emit
69 // migration the compiler emitted `Number(arg_count as f64)` and
70 // this body decoded `f64::from_bits(count_bits) as usize`. That
71 // shape made `op_call` (which uses `int_operand`) reject the same
72 // arg-count slot, surfacing as the smoke-2 "Expected integer for
73 // arg count" failure. The fix landed here together with the
74 // call-site emit changes in `compiler/expressions/function_calls.rs`.
75 let (count_bits, count_kind) = self.pop_kinded()?;
76 let count_slot = KindedSlot::new(ValueSlot::from_raw(count_bits), count_kind);
77 let count = crate::executor::builtins::kind_coerce::int_operand(&count_slot)
78 .map_err(|_| {
79 VMError::RuntimeError(format!(
80 "pop_builtin_args: arg-count slot must be integer-family, got kind {:?}",
81 count_kind
82 ))
83 })? as usize;
84 // Drop the arg-count's share (inline scalar — no-op for integer
85 // kinds, but the discipline lives at the §2.7.7 parallel-kind
86 // boundary).
87 crate::executor::vm_impl::stack::drop_with_kind(count_bits, count_kind);
88
89 let mut args: Vec<KindedSlot> = Vec::with_capacity(count);
90 for _ in 0..count {
91 let (bits, kind) = self.pop_kinded()?;
92 // The pop transferred the slot's share to us; wrap it in a
93 // KindedSlot which will Drop-retire the share when the
94 // builtin call's arg vec is dropped.
95 args.push(KindedSlot::new(ValueSlot::from_raw(bits), kind));
96 }
97 args.reverse();
98 Ok(args)
99 }
100
101 /// Push a `KindedSlot` result back onto the stack. The carrier's
102 /// share transfers into the slot; we `mem::forget` the carrier so its
103 /// `Drop` does not retire the share that the slot now owns.
104 #[inline]
105 pub(crate) fn push_kinded_slot(&mut self, slot: KindedSlot) -> Result<(), VMError> {
106 let bits = slot.slot().raw();
107 let kind = slot.kind();
108 std::mem::forget(slot);
109 self.push_kinded(bits, kind)
110 }
111
112 // ========================================================================
113 // Builtin Dispatch
114 //
115 // Wave 5a flipped the dispatch SHAPE: every arm produces /
116 // consumes `Vec<KindedSlot>`. Wave 5b lands the math/array/utility
117 // body migrations and wires the dispatch arms.
118
119 pub fn op_builtin_call(
120 &mut self,
121 instruction: &Instruction,
122 ctx: Option<&mut shape_runtime::context::ExecutionContext>,
123 ) -> Result<(), VMError> {
124 if let Some(Operand::Builtin(builtin)) = instruction.operand {
125 let _ctx = ctx;
126 match builtin {
127 // ── Wave 5b: math builtins ────────────────────────────────
128 BuiltinFunction::Abs => {
129 let args = self.pop_builtin_args()?;
130 let r = super::super::builtins::math::builtin_abs(&args)?;
131 self.push_kinded_slot(r)?;
132 }
133 BuiltinFunction::Sqrt => {
134 let args = self.pop_builtin_args()?;
135 let r = super::super::builtins::math::builtin_sqrt(&args)?;
136 self.push_kinded_slot(r)?;
137 }
138 BuiltinFunction::Ln => {
139 let args = self.pop_builtin_args()?;
140 let r = super::super::builtins::math::builtin_ln(&args)?;
141 self.push_kinded_slot(r)?;
142 }
143 BuiltinFunction::Pow => {
144 let args = self.pop_builtin_args()?;
145 let r = super::super::builtins::math::builtin_pow(&args)?;
146 self.push_kinded_slot(r)?;
147 }
148 BuiltinFunction::Exp => {
149 let args = self.pop_builtin_args()?;
150 let r = super::super::builtins::math::builtin_exp(&args)?;
151 self.push_kinded_slot(r)?;
152 }
153 BuiltinFunction::Log => {
154 let args = self.pop_builtin_args()?;
155 let r = super::super::builtins::math::builtin_log(&args)?;
156 self.push_kinded_slot(r)?;
157 }
158 BuiltinFunction::Floor => {
159 let args = self.pop_builtin_args()?;
160 let r = super::super::builtins::math::builtin_floor(&args)?;
161 self.push_kinded_slot(r)?;
162 }
163 BuiltinFunction::Ceil => {
164 let args = self.pop_builtin_args()?;
165 let r = super::super::builtins::math::builtin_ceil(&args)?;
166 self.push_kinded_slot(r)?;
167 }
168 BuiltinFunction::Round => {
169 let args = self.pop_builtin_args()?;
170 let r = super::super::builtins::math::builtin_round(&args)?;
171 self.push_kinded_slot(r)?;
172 }
173 BuiltinFunction::Sin => {
174 let args = self.pop_builtin_args()?;
175 let r = super::super::builtins::math::builtin_sin(&args)?;
176 self.push_kinded_slot(r)?;
177 }
178 BuiltinFunction::Cos => {
179 let args = self.pop_builtin_args()?;
180 let r = super::super::builtins::math::builtin_cos(&args)?;
181 self.push_kinded_slot(r)?;
182 }
183 BuiltinFunction::Tan => {
184 let args = self.pop_builtin_args()?;
185 let r = super::super::builtins::math::builtin_tan(&args)?;
186 self.push_kinded_slot(r)?;
187 }
188 BuiltinFunction::Asin => {
189 let args = self.pop_builtin_args()?;
190 let r = super::super::builtins::math::builtin_asin(&args)?;
191 self.push_kinded_slot(r)?;
192 }
193 BuiltinFunction::Acos => {
194 let args = self.pop_builtin_args()?;
195 let r = super::super::builtins::math::builtin_acos(&args)?;
196 self.push_kinded_slot(r)?;
197 }
198 BuiltinFunction::Atan => {
199 let args = self.pop_builtin_args()?;
200 let r = super::super::builtins::math::builtin_atan(&args)?;
201 self.push_kinded_slot(r)?;
202 }
203 BuiltinFunction::Min => {
204 let args = self.pop_builtin_args()?;
205 let r = super::super::builtins::math::builtin_min(&args)?;
206 self.push_kinded_slot(r)?;
207 }
208 BuiltinFunction::Max => {
209 let args = self.pop_builtin_args()?;
210 let r = super::super::builtins::math::builtin_max(&args)?;
211 self.push_kinded_slot(r)?;
212 }
213 BuiltinFunction::StdDev => {
214 let args = self.pop_builtin_args()?;
215 let r = super::super::builtins::math::builtin_stddev(&args)?;
216 self.push_kinded_slot(r)?;
217 }
218 BuiltinFunction::Sign => {
219 let args = self.pop_builtin_args()?;
220 let r = super::super::builtins::math::builtin_sign(&args)?;
221 self.push_kinded_slot(r)?;
222 }
223 BuiltinFunction::Gcd => {
224 let args = self.pop_builtin_args()?;
225 let r = super::super::builtins::math::builtin_gcd(&args)?;
226 self.push_kinded_slot(r)?;
227 }
228 BuiltinFunction::Lcm => {
229 let args = self.pop_builtin_args()?;
230 let r = super::super::builtins::math::builtin_lcm(&args)?;
231 self.push_kinded_slot(r)?;
232 }
233 BuiltinFunction::Hypot => {
234 let args = self.pop_builtin_args()?;
235 let r = super::super::builtins::math::builtin_hypot(&args)?;
236 self.push_kinded_slot(r)?;
237 }
238 BuiltinFunction::Clamp => {
239 let args = self.pop_builtin_args()?;
240 let r = super::super::builtins::math::builtin_clamp(&args)?;
241 self.push_kinded_slot(r)?;
242 }
243 BuiltinFunction::IsNaN => {
244 let args = self.pop_builtin_args()?;
245 let r = super::super::builtins::math::builtin_is_nan(&args)?;
246 self.push_kinded_slot(r)?;
247 }
248 BuiltinFunction::IsFinite => {
249 let args = self.pop_builtin_args()?;
250 let r = super::super::builtins::math::builtin_is_finite(&args)?;
251 self.push_kinded_slot(r)?;
252 }
253
254 // ── Wave 5b: array builtins ───────────────────────────────
255 BuiltinFunction::Push => {
256 let args = self.pop_builtin_args()?;
257 let r = super::super::builtins::array_ops::builtin_push(&args)?;
258 self.push_kinded_slot(r)?;
259 }
260 BuiltinFunction::Pop => {
261 let args = self.pop_builtin_args()?;
262 let r = super::super::builtins::array_ops::builtin_pop(&args)?;
263 self.push_kinded_slot(r)?;
264 }
265 BuiltinFunction::First => {
266 let args = self.pop_builtin_args()?;
267 let r = super::super::builtins::array_ops::builtin_first(&args)?;
268 self.push_kinded_slot(r)?;
269 }
270 BuiltinFunction::Last => {
271 let args = self.pop_builtin_args()?;
272 let r = super::super::builtins::array_ops::builtin_last(&args)?;
273 self.push_kinded_slot(r)?;
274 }
275 BuiltinFunction::Zip => {
276 let args = self.pop_builtin_args()?;
277 let r = super::super::builtins::array_ops::builtin_zip(&args)?;
278 self.push_kinded_slot(r)?;
279 }
280 BuiltinFunction::Filled => {
281 let args = self.pop_builtin_args()?;
282 let r = super::super::builtins::array_ops::builtin_filled(&args)?;
283 self.push_kinded_slot(r)?;
284 }
285 BuiltinFunction::Range => {
286 let args = self.pop_builtin_args()?;
287 let r = super::super::builtins::array_ops::builtin_range(&args)?;
288 self.push_kinded_slot(r)?;
289 }
290 BuiltinFunction::Slice => {
291 let args = self.pop_builtin_args()?;
292 let r = super::super::builtins::array_ops::builtin_slice(&args)?;
293 self.push_kinded_slot(r)?;
294 }
295
296 // ── Wave 5b: utility builtins ─────────────────────────────
297 BuiltinFunction::ObjectRest => {
298 let args = self.pop_builtin_args()?;
299 let r = self.builtin_object_rest(&args)?;
300 self.push_kinded_slot(r)?;
301 }
302 BuiltinFunction::Snapshot => {
303 // Snapshot suspends execution; never returns a value.
304 let _args = self.pop_builtin_args()?;
305 return Err(VMError::Suspended {
306 future_id: SNAPSHOT_FUTURE_ID,
307 resume_ip: self.ip,
308 });
309 }
310 BuiltinFunction::Exit => {
311 let args = self.pop_builtin_args()?;
312 let code = if args.is_empty() {
313 0
314 } else {
315 // Best-effort code extraction. The arg comes in as
316 // Bool-kinded (Wave 6 stack-ABI gap); reinterpret the
317 // raw bits as i64 since `exit(code)` is documented to
318 // take an int.
319 args[0].slot.raw() as i64 as i32
320 };
321 std::process::exit(code);
322 }
323 BuiltinFunction::Print => {
324 // ADR-006 §2.7.4 — pop the kinded args, format each
325 // through `ValueFormatter::format_kinded` (top-level
326 // unquoted-string rendering, nested quotes inside
327 // containers), join with spaces, surface to the
328 // `OutputAdapter::print` of the active
329 // `ExecutionContext`. Returns the unit/null sentinel
330 // per the §2.7.4 GENERIC_CARRIER ABI.
331 //
332 // The pushed result is a `Ptr(HeapKind::String)`-kind
333 // null slot rather than `KindedSlot::none()`'s
334 // `Bool=0` shape: `wire_conversion::slot_to_wire`
335 // projects `Ptr(_)` with bits=0 to `WireValue::Null`,
336 // which the script runner suppresses when printing
337 // the program's final value (`script_cmd.rs:1353`).
338 // The `Bool=0` sentinel would otherwise surface as a
339 // spurious `false` line after every `print()`.
340 let args = self.pop_builtin_args()?;
341 self.builtin_print(&args, _ctx)?;
342 let null_slot = KindedSlot::new(
343 ValueSlot::from_raw(0),
344 shape_value::NativeKind::Ptr(
345 shape_value::HeapKind::String,
346 ),
347 );
348 self.push_kinded_slot(null_slot)?;
349 }
350 BuiltinFunction::Format
351 | BuiltinFunction::FormatValueWithMeta => {
352 // Universal value-to-string. `Format` joins multiple
353 // args without separator (Shape's `format("a", "b")`
354 // → `"ab"` legacy semantics); `FormatValueWithMeta`
355 // is the single-arg `expr.to_string()` /
356 // `f"{expr}"` interpolation path.
357 let args = self.pop_builtin_args()?;
358 let r = self.builtin_format(&args)?;
359 self.push_kinded_slot(r)?;
360 }
361 BuiltinFunction::FormatValueWithSpec => {
362 // Args: [value, spec_tag, …spec-payload]. Currently
363 // routes the basic FORMAT_SPEC_FIXED path; the Table
364 // arm surfaces as `NotImplemented` per W13 playbook
365 // §7.4 surface-and-stop.
366 let args = self.pop_builtin_args()?;
367 let r = self.builtin_format_with_spec(&args)?;
368 self.push_kinded_slot(r)?;
369 }
370
371 // ── Wave 5c: type-introspection + conversion + native-interop ──
372 BuiltinFunction::IsNumber
373 | BuiltinFunction::IsString
374 | BuiltinFunction::IsBool
375 | BuiltinFunction::IsArray
376 | BuiltinFunction::IsObject
377 | BuiltinFunction::IsDataRow => {
378 // SURFACE per ADR-006 §2.7.14: phase-1b-vm wave 5c —
379 // is_* type-check body migration deferred. Drain args
380 // to keep the §2.7.7 parallel-kind track balanced,
381 // then return a structured `NotImplemented` so the
382 // dispatch arm fails loudly without panicking. The
383 // re-fill rebuilds bodies against the `KindedSlot`
384 // carrier (Q8 carrier-API-bound).
385 let _args: Vec<KindedSlot> = self.pop_builtin_args()?;
386 return Err(VMError::NotImplemented(format!(
387 "phase-1b-vm-wave-5c-is-type-check: {:?} body \
388 migration to kinded carrier (ADR-006 §2.7.6) \
389 pending (v0.4 / planned)",
390 builtin
391 )));
392 }
393 BuiltinFunction::ToString
394 | BuiltinFunction::ToNumber
395 | BuiltinFunction::ToBool => {
396 // SURFACE per ADR-006 §2.7.14: phase-1b-vm wave 5c —
397 // conversion body migration (`dispatch_conversion_builtin`)
398 // deferred. Drain args to balance the §2.7.7 parallel-
399 // kind track before surfacing.
400 let _args: Vec<KindedSlot> = self.pop_builtin_args()?;
401 return Err(VMError::NotImplemented(format!(
402 "phase-1b-vm-wave-5c-conversion: {:?} body \
403 migration to kinded carrier (dispatch_conversion_builtin) \
404 pending (v0.4 / planned)",
405 builtin
406 )));
407 }
408 BuiltinFunction::NativePtrSize
409 | BuiltinFunction::NativePtrNewCell
410 | BuiltinFunction::NativePtrFreeCell
411 | BuiltinFunction::NativePtrReadPtr
412 | BuiltinFunction::NativePtrWritePtr
413 | BuiltinFunction::NativeTableFromArrowC
414 | BuiltinFunction::NativeTableFromArrowCTyped
415 | BuiltinFunction::NativeTableBindType => {
416 // SURFACE per ADR-006 §2.7.14: phase-1b-vm wave 5c —
417 // native-interop body migration
418 // (`dispatch_native_interop_builtin`) deferred. Drain
419 // args to balance the §2.7.7 parallel-kind track.
420 let _args: Vec<KindedSlot> = self.pop_builtin_args()?;
421 return Err(VMError::NotImplemented(format!(
422 "phase-1b-vm-wave-5c-native-interop: {:?} body \
423 migration to kinded carrier (dispatch_native_interop_builtin) \
424 pending (v0.4 / planned)",
425 builtin
426 )));
427 }
428 BuiltinFunction::TypeOf => {
429 // SURFACE per ADR-006 §2.7.14: phase-1b-vm wave 5c —
430 // `TypeOf` body migration pending. The legacy body
431 // popped via the deleted raw-bits stack shim; needs a
432 // kinded-carrier rebuild per ADR-006 §2.7.6. No args
433 // are popped because the legacy emit shape did not
434 // emit an arity prefix here.
435 return Err(VMError::NotImplemented(
436 "phase-1b-vm-wave-5c-typeof: TypeOf body migration \
437 to kinded carrier (ADR-006 §2.7.6) pending \
438 (v0.4 / planned)"
439 .to_string(),
440 ));
441 }
442
443 // ── Wave 5d: closure-driven array builtins + intrinsics ──────
444 BuiltinFunction::Map
445 | BuiltinFunction::Filter
446 | BuiltinFunction::Reduce
447 | BuiltinFunction::ForEach
448 | BuiltinFunction::Find
449 | BuiltinFunction::FindIndex
450 | BuiltinFunction::Some
451 | BuiltinFunction::Every
452 | BuiltinFunction::ControlFold => {
453 // SURFACE per ADR-006 §2.7.14: phase-1b-vm wave 5d —
454 // closure-driven array builtin body migration deferred.
455 // The rebuild routes through the §2.7.11 / Q12 kinded
456 // value-call ABI for the closure invocation. Drain
457 // args to balance the §2.7.7 parallel-kind track.
458 let _args: Vec<KindedSlot> = self.pop_builtin_args()?;
459 return Err(VMError::NotImplemented(format!(
460 "phase-1b-vm-wave-5d-closure-array: {:?} body \
461 migration to kinded carrier + value-call ABI \
462 (ADR-006 §2.7.11/Q12) pending (v0.4 / planned)",
463 builtin
464 )));
465 }
466 BuiltinFunction::IntrinsicVecAbs
467 | BuiltinFunction::IntrinsicVecSqrt
468 | BuiltinFunction::IntrinsicVecLn
469 | BuiltinFunction::IntrinsicVecExp
470 | BuiltinFunction::IntrinsicVecAdd
471 | BuiltinFunction::IntrinsicVecSub
472 | BuiltinFunction::IntrinsicVecMul
473 | BuiltinFunction::IntrinsicVecDiv
474 | BuiltinFunction::IntrinsicVecMax
475 | BuiltinFunction::IntrinsicVecMin
476 | BuiltinFunction::IntrinsicVecSelect
477 | BuiltinFunction::IntrinsicVecAddI64 => {
478 // SURFACE per ADR-006 §2.7.14: phase-1b-vm wave 5d —
479 // SIMD vector intrinsic body migration
480 // (`handle_vector_intrinsic`) deferred. No arg pop
481 // because the legacy dispatcher handled it internally;
482 // surface immediately so the operator sees a clean
483 // error before any state mutation. Stack rebalancing
484 // is the rebuild's responsibility per
485 // `executor/builtins/vector_intrinsics.rs`.
486 return Err(VMError::NotImplemented(format!(
487 "phase-1b-vm-wave-5d-vec-intrinsic: {:?} body \
488 migration to kinded carrier (handle_vector_intrinsic) \
489 pending (v0.4 / planned)",
490 builtin
491 )));
492 }
493 BuiltinFunction::IntrinsicMatMulVec
494 | BuiltinFunction::IntrinsicMatMulMat
495 | BuiltinFunction::IntrinsicMatAdd
496 | BuiltinFunction::IntrinsicMatSub => {
497 // SURFACE per ADR-006 §2.7.14: phase-1b-vm wave 5d —
498 // matrix intrinsic body migration
499 // (`handle_matrix_intrinsic`) deferred. See sibling
500 // vector arm above for arg-pop rationale.
501 return Err(VMError::NotImplemented(format!(
502 "phase-1b-vm-wave-5d-mat-intrinsic: {:?} body \
503 migration to kinded carrier (handle_matrix_intrinsic) \
504 pending (v0.4 / planned)",
505 builtin
506 )));
507 }
508 BuiltinFunction::IntrinsicMinimize => {
509 // SURFACE per ADR-006 §2.7.14: phase-1b-vm wave 5d —
510 // `minimize` intrinsic body deferred. Drain args to
511 // balance the §2.7.7 parallel-kind track.
512 let _args: Vec<KindedSlot> = self.pop_builtin_args()?;
513 return Err(VMError::NotImplemented(
514 "phase-1b-vm-wave-5d-minimize: minimize() intrinsic \
515 body migration to kinded carrier + value-call ABI \
516 (ADR-006 §2.7.11/Q12) pending (v0.4 / planned)"
517 .to_string(),
518 ));
519 }
520 // W12-stdlib-intrinsic-collapse (Wave-2-Agent-G, 2026-05-14):
521 // `BuiltinFunction::IntrinsicSum` deleted as the canonical
522 // 7th defection-attractor instance (parallel-implementation
523 // across producer/consumer carrier-shape boundaries — the
524 // old handler body's own comment said "mirror of
525 // v2_int_sum/v2_float_sum exactly"). Stdlib
526 // `pub fn sum(series) { series.sum() }` now routes through
527 // the PHF `.sum()` method dispatch — single discriminator
528 // per ADR-005 §1, `MethodFnV2` ABI per ADR-006 §2.7.10/Q11.
529 BuiltinFunction::IntrinsicBspline2_3dBatch
530 | BuiltinFunction::IntrinsicMean
531 | BuiltinFunction::IntrinsicMin
532 | BuiltinFunction::IntrinsicMax
533 | BuiltinFunction::IntrinsicStd
534 | BuiltinFunction::IntrinsicVariance
535 | BuiltinFunction::IntrinsicRandom
536 | BuiltinFunction::IntrinsicRandomInt
537 | BuiltinFunction::IntrinsicRandomSeed
538 | BuiltinFunction::IntrinsicRandomNormal
539 | BuiltinFunction::IntrinsicRandomArray
540 | BuiltinFunction::IntrinsicDistUniform
541 | BuiltinFunction::IntrinsicDistLognormal
542 | BuiltinFunction::IntrinsicDistExponential
543 | BuiltinFunction::IntrinsicDistPoisson
544 | BuiltinFunction::IntrinsicDistSampleN
545 | BuiltinFunction::IntrinsicBrownianMotion
546 | BuiltinFunction::IntrinsicGbm
547 | BuiltinFunction::IntrinsicOuProcess
548 | BuiltinFunction::IntrinsicRandomWalk
549 | BuiltinFunction::IntrinsicRollingSum
550 | BuiltinFunction::IntrinsicRollingMean
551 | BuiltinFunction::IntrinsicRollingStd
552 | BuiltinFunction::IntrinsicRollingMin
553 | BuiltinFunction::IntrinsicRollingMax
554 | BuiltinFunction::IntrinsicEma
555 | BuiltinFunction::IntrinsicLinearRecurrence
556 | BuiltinFunction::IntrinsicShift
557 | BuiltinFunction::IntrinsicDiff
558 | BuiltinFunction::IntrinsicPctChange
559 | BuiltinFunction::IntrinsicFillna
560 | BuiltinFunction::IntrinsicCumsum
561 | BuiltinFunction::IntrinsicCumprod
562 | BuiltinFunction::IntrinsicClip
563 | BuiltinFunction::IntrinsicCorrelation
564 | BuiltinFunction::IntrinsicCovariance
565 | BuiltinFunction::IntrinsicPercentile
566 | BuiltinFunction::IntrinsicMedian
567 | BuiltinFunction::IntrinsicAtan2
568 | BuiltinFunction::IntrinsicSinh
569 | BuiltinFunction::IntrinsicCosh
570 | BuiltinFunction::IntrinsicTanh
571 | BuiltinFunction::IntrinsicCharCode
572 | BuiltinFunction::IntrinsicFromCharCode
573 | BuiltinFunction::IntrinsicSeries => {
574 // SURFACE per ADR-006 §2.7.14: phase-1b-vm wave 5d —
575 // stats / random / stochastic / rolling / scalar-math /
576 // series intrinsic body migration
577 // (`handle_intrinsic_builtin`) deferred. Covers the
578 // ~37-variant cluster (Bspline2_3dBatch, Mean, Min,
579 // Max, Std, Variance, Random*, Dist*, BrownianMotion,
580 // Gbm, OuProcess, RandomWalk, Rolling*, Ema,
581 // LinearRecurrence, Shift, Diff, PctChange, Fillna,
582 // Cumsum, Cumprod, Clip, Correlation, Covariance,
583 // Percentile, Median, Atan2, Sinh, Cosh, Tanh,
584 // CharCode, FromCharCode, Series). No arg pop because
585 // the legacy dispatcher handled per-variant arity; the
586 // rebuild lives at `executor/builtins/intrinsics/`
587 // (math.rs / statistical.rs / signal.rs).
588 return Err(VMError::NotImplemented(format!(
589 "phase-1b-vm-wave-5d-intrinsic: {:?} body migration \
590 to kinded carrier (handle_intrinsic_builtin) \
591 pending (v0.4 / planned)",
592 builtin
593 )));
594 }
595
596 // ── Wave 5e: constructors (Result/Option, Set, Deque,
597 // PriorityQueue, HashMap, Mutex/Atomic/Lazy/Channel),
598 // Content builders, DateTime constructors, Table from
599 // rows, JSON navigation helpers, Window functions, Join,
600 // Reflect, MatFromFlat, MakeContent*. ─────────────────────
601 BuiltinFunction::SomeCtor => {
602 // Wave 14 W14-variant-codegen (ADR-006 §2.7.17 / Q18,
603 // 2026-05-10): `Some(x)` builds a fresh
604 // `Arc<OptionData>` carrier with `is_some=true` and
605 // the popped argument as the typed payload share.
606 // The `pop_builtin_args` carrier owns one strong-
607 // count share per heap-bearing kind; ownership
608 // transfers into the `OptionData::payload` slot
609 // verbatim (the carrier is moved, not cloned).
610 let mut args: Vec<KindedSlot> = self.pop_builtin_args()?;
611 if args.len() != 1 {
612 return Err(VMError::RuntimeError(format!(
613 "Some() expects 1 argument, got {}",
614 args.len()
615 )));
616 }
617 let payload = args.remove(0);
618 let opt = std::sync::Arc::new(
619 shape_value::heap_value::OptionData::some(payload),
620 );
621 self.push_kinded_slot(KindedSlot::from_option(opt))?;
622 }
623 BuiltinFunction::OkCtor => {
624 // Wave 14 W14-variant-codegen (ADR-006 §2.7.17 / Q18,
625 // 2026-05-10): `Ok(x)` builds a fresh
626 // `Arc<ResultData>` carrier with `is_ok=true` and
627 // the popped argument as the typed payload share.
628 let mut args: Vec<KindedSlot> = self.pop_builtin_args()?;
629 if args.len() != 1 {
630 return Err(VMError::RuntimeError(format!(
631 "Ok() expects 1 argument, got {}",
632 args.len()
633 )));
634 }
635 let payload = args.remove(0);
636 let res = std::sync::Arc::new(
637 shape_value::heap_value::ResultData::ok(payload),
638 );
639 self.push_kinded_slot(KindedSlot::from_result(res))?;
640 }
641 BuiltinFunction::ErrCtor => {
642 // Wave 14 W14-variant-codegen (ADR-006 §2.7.17 / Q18,
643 // 2026-05-10): `Err(e)` builds a fresh
644 // `Arc<ResultData>` carrier with `is_ok=false` and
645 // the popped argument as the typed payload share.
646 let mut args: Vec<KindedSlot> = self.pop_builtin_args()?;
647 if args.len() != 1 {
648 return Err(VMError::RuntimeError(format!(
649 "Err() expects 1 argument, got {}",
650 args.len()
651 )));
652 }
653 let payload = args.remove(0);
654 let res = std::sync::Arc::new(
655 shape_value::heap_value::ResultData::err(payload),
656 );
657 self.push_kinded_slot(KindedSlot::from_result(res))?;
658 }
659 BuiltinFunction::HashMapCtor => {
660 // Wave 2 Round 3b C2-joint ckpt-2 (2026-05-14): per
661 // ADR-006 §2.7.24 Q25.B SUPERSEDED, `let m = HashMap()`
662 // produces a fresh empty `Arc<HashMapKindedRef>` slot.
663 // Default empty variant chosen is `String` (the typical
664 // initial element type in user code; the variant tag
665 // gets specialized on first insert via clone-on-write
666 // — ckpt-3 mutation-API rebuild). Reader contract: kind
667 // == Ptr(HeapKind::HashMap), bits =
668 // Arc::into_raw::<HashMapKindedRef>.
669 let _args: Vec<KindedSlot> = self.pop_builtin_args()?;
670 let empty_kref = shape_value::heap_value::HashMapKindedRef::String(
671 std::sync::Arc::new(
672 shape_value::heap_value::HashMapData::<
673 *const shape_value::v2::string_obj::StringObj,
674 >::new(),
675 ),
676 );
677 let hm = std::sync::Arc::new(empty_kref);
678 self.push_kinded_slot(KindedSlot::from_hashmap(hm))?;
679 }
680 BuiltinFunction::SetCtor => {
681 // Wave 13 W13-hashset-rebuild (ADR-006 §2.7.15 / Q16,
682 // 2026-05-10): empty Set ctor — `Set()` takes no
683 // args at landing; `Set([elements])` initialization
684 // is a follow-up. Build empty Arc<HashSetData> and
685 // push via KindedSlot::from_hashset.
686 let _args: Vec<KindedSlot> = self.pop_builtin_args()?;
687 let empty = std::sync::Arc::new(
688 shape_value::heap_value::HashSetData::new(),
689 );
690 let result = KindedSlot::from_hashset(empty);
691 self.push_kinded_slot(result)?;
692 }
693 BuiltinFunction::DequeCtor => {
694 // Wave 15 W15-deque (ADR-006 §2.7.19 / Q20,
695 // 2026-05-10): empty Deque ctor — `Deque()` takes
696 // no args at landing; `Deque([elements])`
697 // initialization is a follow-up. Build empty
698 // Arc<DequeData> and push via KindedSlot::from_deque.
699 // Reader contract: kind == Ptr(HeapKind::Deque),
700 // bits = Arc::into_raw::<DequeData>.
701 let _args: Vec<KindedSlot> = self.pop_builtin_args()?;
702 let empty = std::sync::Arc::new(
703 shape_value::heap_value::DequeData::new(),
704 );
705 let result = KindedSlot::from_deque(empty);
706 self.push_kinded_slot(result)?;
707 }
708 BuiltinFunction::PriorityQueueCtor => {
709 // Wave 15 W15-priority-queue (ADR-006 §2.7.18 /
710 // Q19, 2026-05-10): empty PriorityQueue ctor —
711 // discard any args (the surface form
712 // `PriorityQueue()` takes no args at landing;
713 // `PriorityQueue([elements])` initialization is a
714 // follow-up). Build an empty
715 // `Arc::new(PriorityQueueData::new())` and push as
716 // a `KindedSlot::from_priority_queue(...)`.
717 let _args: Vec<KindedSlot> = self.pop_builtin_args()?;
718 let empty = std::sync::Arc::new(
719 shape_value::heap_value::PriorityQueueData::new(),
720 );
721 let result = KindedSlot::from_priority_queue(empty);
722 self.push_kinded_slot(result)?;
723 }
724 BuiltinFunction::ChannelCtor => {
725 // Wave 15 W15-channel-rebuild (ADR-006 §2.7.20 / Q21,
726 // 2026-05-10): empty Channel ctor — `Channel()`
727 // takes no args at landing; bounded-capacity
728 // initialization is a follow-up. Build empty
729 // `Arc<ChannelData>` (interior `Mutex<ChannelInner>`)
730 // and push via `KindedSlot::from_channel`. Reader
731 // contract: kind == Ptr(HeapKind::Channel),
732 // bits = Arc::into_raw::<ChannelData>.
733 //
734 // Cross-task blocking `recv()` requires the §2.7.4
735 // task-scheduler boundary and is SURFACE'd at the
736 // method body — see
737 // `executor/objects/channel_methods.rs`.
738 let _args: Vec<KindedSlot> = self.pop_builtin_args()?;
739 let empty = std::sync::Arc::new(
740 shape_value::heap_value::ChannelData::new(),
741 );
742 let result = KindedSlot::from_channel(empty);
743 self.push_kinded_slot(result)?;
744 }
745 BuiltinFunction::MutexCtor => {
746 // W17-concurrency (ADR-006 §2.7.25, 2026-05-11):
747 // `Mutex(initial_value)` builds an `Arc<MutexData>`
748 // wrapping the initial value `KindedSlot` (any
749 // kind). The initial-value share moves into the
750 // MutexInner cell — `pop_builtin_args` already
751 // consumed the arg's stack share, and
752 // `MutexData::new` takes ownership of the slot.
753 let mut args: Vec<KindedSlot> = self.pop_builtin_args()?;
754 if args.len() != 1 {
755 return Err(VMError::RuntimeError(format!(
756 "Mutex() requires exactly 1 argument \
757 (initial value), got {}",
758 args.len()
759 )));
760 }
761 let initial = args.remove(0);
762 let m = std::sync::Arc::new(
763 shape_value::heap_value::MutexData::new(initial),
764 );
765 let result = KindedSlot::from_mutex(m);
766 self.push_kinded_slot(result)?;
767 }
768 BuiltinFunction::AtomicCtor => {
769 // W17-concurrency (ADR-006 §2.7.25, 2026-05-11):
770 // `Atomic(initial)` builds an `Arc<AtomicData>`
771 // wrapping a `std::sync::atomic::AtomicI64`.
772 // i64-only at landing — non-int args error.
773 let args: Vec<KindedSlot> = self.pop_builtin_args()?;
774 if args.len() != 1 {
775 return Err(VMError::RuntimeError(format!(
776 "Atomic() requires exactly 1 argument \
777 (initial int value), got {}",
778 args.len()
779 )));
780 }
781 let initial = args[0].as_i64().ok_or_else(|| {
782 VMError::RuntimeError(format!(
783 "Atomic() argument must be an int (got \
784 kind {:?}); typed-payload Atomic<T> is a \
785 future amendment per ADR-006 §2.7.25",
786 args[0].kind
787 ))
788 })?;
789 let a = std::sync::Arc::new(
790 shape_value::heap_value::AtomicData::new(initial),
791 );
792 let result = KindedSlot::from_atomic(a);
793 self.push_kinded_slot(result)?;
794 }
795 BuiltinFunction::LazyCtor => {
796 // W17-concurrency (ADR-006 §2.7.25, 2026-05-11):
797 // `Lazy(|| ...)` builds an `Arc<LazyData>` wrapping
798 // the initializer closure. The closure share moves
799 // into the LazyInner cell — the handler tier's
800 // `lazy.get()` takes the initializer back out via
801 // `take_initializer()` for the
802 // `vm.call_value_immediate_nb` invocation.
803 let mut args: Vec<KindedSlot> = self.pop_builtin_args()?;
804 if args.len() != 1 {
805 return Err(VMError::RuntimeError(format!(
806 "Lazy() requires exactly 1 argument \
807 (initializer closure), got {}",
808 args.len()
809 )));
810 }
811 let initializer = args.remove(0);
812 // Kind-validate: must be a Closure (closure-call
813 // path goes through `call_value_immediate_nb` which
814 // requires Ptr(HeapKind::Closure) callee kind).
815 if !matches!(
816 initializer.kind,
817 shape_value::NativeKind::Ptr(
818 shape_value::heap_value::HeapKind::Closure
819 )
820 ) {
821 return Err(VMError::RuntimeError(format!(
822 "Lazy() argument must be a closure (got \
823 kind {:?})",
824 initializer.kind
825 )));
826 }
827 let l = std::sync::Arc::new(
828 shape_value::heap_value::LazyData::new(initializer),
829 );
830 let result = KindedSlot::from_lazy(l);
831 self.push_kinded_slot(result)?;
832 }
833 BuiltinFunction::ContentTextCtor => {
834 // W18.6 (R8 W3 2026-05-24 — supervisor D3+D4):
835 // `Content.text(s: string) -> content` user-facing
836 // constructor. Pops one string arg, wraps as
837 // `ContentNode::plain(s)`, pushes as a
838 // `Ptr(HeapKind::Content)` kinded slot via the new
839 // `KindedSlot::from_content` constructor. The slot
840 // owns one `Arc<ContentNode>` strong-count share;
841 // Drop / Clone arms for `HeapKind::Content` already
842 // dispatch the matching retain/release.
843 let args: Vec<KindedSlot> = self.pop_builtin_args()?;
844 if args.len() != 1 {
845 return Err(VMError::RuntimeError(format!(
846 "Content.text() requires exactly 1 argument \
847 (string), got {}",
848 args.len()
849 )));
850 }
851 let s = args[0].as_str().ok_or_else(|| {
852 VMError::RuntimeError(format!(
853 "Content.text() argument must be a string (got \
854 kind {:?})",
855 args[0].kind
856 ))
857 })?;
858 let node = shape_value::content::ContentNode::plain(s);
859 let result = KindedSlot::from_content(std::sync::Arc::new(node));
860 self.push_kinded_slot(result)?;
861 }
862 BuiltinFunction::ContentCodeCtor => {
863 // W18.6 (R8 W3 2026-05-24): `Content.code(source: string)
864 // -> content` minimum-viable constructor (single-arg
865 // form; no language label). Mirror of ContentTextCtor
866 // for the `ContentNode::Code` variant. Per-renderer
867 // dispatch handles the styling.
868 let args: Vec<KindedSlot> = self.pop_builtin_args()?;
869 if args.len() != 1 {
870 return Err(VMError::RuntimeError(format!(
871 "Content.code() requires exactly 1 argument \
872 (source string), got {}",
873 args.len()
874 )));
875 }
876 let s = args[0].as_str().ok_or_else(|| {
877 VMError::RuntimeError(format!(
878 "Content.code() argument must be a string (got \
879 kind {:?})",
880 args[0].kind
881 ))
882 })?;
883 let node = shape_value::content::ContentNode::Code {
884 language: None,
885 source: s.to_string(),
886 };
887 let result = KindedSlot::from_content(std::sync::Arc::new(node));
888 self.push_kinded_slot(result)?;
889 }
890 BuiltinFunction::ContentChart => {
891 // Chart constructor is v0.4 scope per supervisor D4
892 // (R8 W3, 2026-05-24) — ECharts integration is its own
893 // workstream. W18.5 ships Table / Code / KeyValue only;
894 // surfacing the Chart MVP keeps the dispatch arm honest
895 // (no Bool-default kinded shim per playbook §4 #9).
896 let _args: Vec<KindedSlot> = self.pop_builtin_args()?;
897 return Err(VMError::NotImplemented(
898 "Content.chart(...) is v0.4 scope per supervisor D4 \
899 (R8 W3, 2026-05-24) — ECharts integration is its \
900 own workstream. W18.5 ships Table / Code / KeyValue \
901 builders only."
902 .to_string(),
903 ));
904 }
905 BuiltinFunction::ContentTableCtor => {
906 // W18.5 (R8 W4, 2026-05-24 — supervisor D4):
907 // `Content.table(headers: Array<string>, rows: Array<Array<string>>)`
908 // direct ctor. Builds a `ContentNode::Table` with the
909 // provided headers + rows + default border. Sibling to
910 // the per-type `Table::new().headers(...).row(...).build()`
911 // builder (TableBuilderNew + Content method chain). Per
912 // supervisor D4 "shortest path builder → content →
913 // renderer", returns a `Ptr(HeapKind::Content)` slot
914 // directly — no intermediate typed Table value.
915 let args: Vec<KindedSlot> = self.pop_builtin_args()?;
916 let table = build_table_from_headers_and_rows(&args)?;
917 let node = shape_value::content::ContentNode::Table(table);
918 let result = KindedSlot::from_content(std::sync::Arc::new(node));
919 self.push_kinded_slot(result)?;
920 }
921 BuiltinFunction::ContentKvCtor => {
922 // W18.5 (R8 W4, 2026-05-24 — supervisor D4):
923 // `Content.kv(keys: Array<string>, values: Array<*>)`
924 // direct ctor. Pairs each key with its corresponding
925 // value formatted as a `ContentNode::plain`. Mirrors
926 // the per-type `KeyValue::new().pair("k", v).build()`
927 // builder. ContentNode::KeyValue stores `Vec<(String,
928 // ContentNode)>` so heterogeneous value types coerce
929 // through `format_kinded` (numeric / bool / string /
930 // nested content all render).
931 let args: Vec<KindedSlot> = self.pop_builtin_args()?;
932 let pairs = build_kv_pairs_from_keys_values(self, &args)?;
933 let node = shape_value::content::ContentNode::KeyValue(pairs);
934 let result = KindedSlot::from_content(std::sync::Arc::new(node));
935 self.push_kinded_slot(result)?;
936 }
937 BuiltinFunction::ContentFragmentCtor => {
938 // W18.5 (R8 W4, 2026-05-24 — supervisor D4):
939 // `Content.fragment(parts: Array<content>)` direct
940 // ctor. Wraps a sequence of Content nodes into a
941 // single `ContentNode::Fragment` for composition.
942 let args: Vec<KindedSlot> = self.pop_builtin_args()?;
943 let parts = collect_content_nodes_from_array_arg(&args)?;
944 let node = shape_value::content::ContentNode::Fragment(parts);
945 let result = KindedSlot::from_content(std::sync::Arc::new(node));
946 self.push_kinded_slot(result)?;
947 }
948 BuiltinFunction::TableBuilderNew => {
949 // W18.5 (R8 W4, 2026-05-24 — supervisor D4):
950 // `Table::new()` returns an empty `ContentNode::Table`
951 // seed. Chainable methods (`headers`, `row`, `border`,
952 // `build`) are registered in `CONTENT_METHODS` PHF and
953 // dispatched on the Content receiver. `.build()` is
954 // identity — returns the receiver. Each chained method
955 // immutably clones + mutates the underlying ContentNode
956 // and pushes a fresh `Ptr(HeapKind::Content)` slot.
957 let args: Vec<KindedSlot> = self.pop_builtin_args()?;
958 if !args.is_empty() {
959 return Err(VMError::RuntimeError(format!(
960 "Table::new() takes no arguments, got {}",
961 args.len()
962 )));
963 }
964 let empty = shape_value::content::ContentTable {
965 headers: Vec::new(),
966 rows: Vec::new(),
967 border: shape_value::content::BorderStyle::default(),
968 max_rows: None,
969 column_types: None,
970 total_rows: None,
971 sortable: false,
972 };
973 let node = shape_value::content::ContentNode::Table(empty);
974 let result = KindedSlot::from_content(std::sync::Arc::new(node));
975 self.push_kinded_slot(result)?;
976 }
977 BuiltinFunction::CodeBuilderNew => {
978 // W18.5 (R8 W4, 2026-05-24 — supervisor D4):
979 // `Code::new()` returns an empty `ContentNode::Code`
980 // seed with no language and empty source. Chainable
981 // methods (`language`, `source`, `build`) live in
982 // `CONTENT_METHODS`. The W18.6 `Content.code(s)`
983 // one-liner ctor coexists — keep both per task spec:
984 // Content.code(s) is single-arg, Code::new() builder
985 // is the multi-property form.
986 let args: Vec<KindedSlot> = self.pop_builtin_args()?;
987 if !args.is_empty() {
988 return Err(VMError::RuntimeError(format!(
989 "Code::new() takes no arguments, got {}",
990 args.len()
991 )));
992 }
993 let node = shape_value::content::ContentNode::Code {
994 language: None,
995 source: String::new(),
996 };
997 let result = KindedSlot::from_content(std::sync::Arc::new(node));
998 self.push_kinded_slot(result)?;
999 }
1000 BuiltinFunction::KeyValueBuilderNew => {
1001 // W18.5 (R8 W4, 2026-05-24 — supervisor D4):
1002 // `KeyValue::new()` returns an empty
1003 // `ContentNode::KeyValue` seed with no pairs. Chainable
1004 // `.pair(key, value)` accumulates the pair; `.build()`
1005 // is identity.
1006 let args: Vec<KindedSlot> = self.pop_builtin_args()?;
1007 if !args.is_empty() {
1008 return Err(VMError::RuntimeError(format!(
1009 "KeyValue::new() takes no arguments, got {}",
1010 args.len()
1011 )));
1012 }
1013 let node = shape_value::content::ContentNode::KeyValue(Vec::new());
1014 let result = KindedSlot::from_content(std::sync::Arc::new(node));
1015 self.push_kinded_slot(result)?;
1016 }
1017 BuiltinFunction::FStringContentText => {
1018 // R8 W4 W18.4: wrap a string as `ContentNode::plain` for
1019 // literal segments of a styled f-string.
1020 let args: Vec<KindedSlot> = self.pop_builtin_args()?;
1021 if args.len() != 1 {
1022 return Err(VMError::RuntimeError(format!(
1023 "FStringContentText requires exactly 1 argument \
1024 (string), got {}",
1025 args.len()
1026 )));
1027 }
1028 let s = args[0].as_str().ok_or_else(|| {
1029 VMError::RuntimeError(format!(
1030 "FStringContentText argument must be a string \
1031 (got kind {:?})",
1032 args[0].kind
1033 ))
1034 })?;
1035 let node = shape_value::content::ContentNode::plain(s);
1036 let result =
1037 KindedSlot::from_content(std::sync::Arc::new(node));
1038 self.push_kinded_slot(result)?;
1039 }
1040 BuiltinFunction::FStringContentStyledText => {
1041 // R8 W4 W18.4: wrap a string as a styled
1042 // `ContentNode::Text` (single span). Args:
1043 // `[value_str, fg_kind, fg_payload, bg_kind, bg_payload,
1044 // flags]` (see opcode_defs comment for encoding).
1045 let args: Vec<KindedSlot> = self.pop_builtin_args()?;
1046 if args.len() != 6 {
1047 return Err(VMError::RuntimeError(format!(
1048 "FStringContentStyledText requires 6 arguments \
1049 (value, fg_kind, fg_payload, bg_kind, \
1050 bg_payload, flags), got {}",
1051 args.len()
1052 )));
1053 }
1054 let value = args[0].as_str().ok_or_else(|| {
1055 VMError::RuntimeError(format!(
1056 "FStringContentStyledText value must be a string \
1057 (got kind {:?})",
1058 args[0].kind
1059 ))
1060 })?;
1061 let fg_kind = args[1].as_i64().ok_or_else(|| {
1062 VMError::RuntimeError(
1063 "FStringContentStyledText fg_kind must be int"
1064 .to_string(),
1065 )
1066 })?;
1067 let fg_payload = args[2].as_i64().ok_or_else(|| {
1068 VMError::RuntimeError(
1069 "FStringContentStyledText fg_payload must be int"
1070 .to_string(),
1071 )
1072 })?;
1073 let bg_kind = args[3].as_i64().ok_or_else(|| {
1074 VMError::RuntimeError(
1075 "FStringContentStyledText bg_kind must be int"
1076 .to_string(),
1077 )
1078 })?;
1079 let bg_payload = args[4].as_i64().ok_or_else(|| {
1080 VMError::RuntimeError(
1081 "FStringContentStyledText bg_payload must be int"
1082 .to_string(),
1083 )
1084 })?;
1085 let flags = args[5].as_i64().ok_or_else(|| {
1086 VMError::RuntimeError(
1087 "FStringContentStyledText flags must be int"
1088 .to_string(),
1089 )
1090 })?;
1091
1092 let style = decode_fstring_style(
1093 fg_kind, fg_payload, bg_kind, bg_payload, flags,
1094 )?;
1095 let node = shape_value::content::ContentNode::styled(
1096 value, style,
1097 );
1098 let result =
1099 KindedSlot::from_content(std::sync::Arc::new(node));
1100 self.push_kinded_slot(result)?;
1101 }
1102 BuiltinFunction::FStringContentFragment => {
1103 // R8 W4 W18.4: combine N content nodes into a Fragment.
1104 // Per ADR-006 §2.3 v2-raw-heap: `Ptr(HeapKind::Content)`
1105 // slots store `Arc::into_raw(Arc<ContentNode>)` bits
1106 // directly (NOT a `Box<HeapValue>` wrapper). Classify
1107 // on `args[i].kind`, then deref the raw `*const
1108 // ContentNode` (mirror of `printing.rs::format_heap_
1109 // kind`'s Content arm and the `Arc::decrement_strong_
1110 // count::<ContentNode>` in `heap_value.rs::drop_with_
1111 // kind` HeapKind::Content arm).
1112 let args: Vec<KindedSlot> = self.pop_builtin_args()?;
1113 let mut nodes: Vec<shape_value::content::ContentNode> =
1114 Vec::with_capacity(args.len());
1115 for (i, arg) in args.iter().enumerate() {
1116 if arg.kind
1117 != shape_value::NativeKind::Ptr(
1118 shape_value::HeapKind::Content,
1119 )
1120 {
1121 return Err(VMError::RuntimeError(format!(
1122 "FStringContentFragment arg #{} must be a \
1123 content value (got kind {:?})",
1124 i, arg.kind
1125 )));
1126 }
1127 let bits = arg.slot.raw();
1128 if bits == 0 {
1129 return Err(VMError::RuntimeError(format!(
1130 "FStringContentFragment arg #{} is a null \
1131 content pointer",
1132 i
1133 )));
1134 }
1135 // SAFETY: per the `KindedSlot::from_content`
1136 // construction contract (and §heap_value.rs::
1137 // drop_with_kind HeapKind::Content arm), a
1138 // `Ptr(HeapKind::Content)` slot's bits are
1139 // `Arc::into_raw(Arc<ContentNode>)`. The borrow is
1140 // bounded by `args`'s lifetime; the underlying
1141 // share survives because `args[i]` still owns it.
1142 // We deep-clone the `ContentNode` (cheap — the
1143 // node enum carries owned strings + Vecs) into the
1144 // Fragment because the receiving Vec owns its
1145 // elements.
1146 let node: &shape_value::content::ContentNode = unsafe {
1147 &*(bits as *const shape_value::content::ContentNode)
1148 };
1149 nodes.push(node.clone());
1150 }
1151 let node = shape_value::content::ContentNode::Fragment(
1152 nodes,
1153 );
1154 let result =
1155 KindedSlot::from_content(std::sync::Arc::new(node));
1156 self.push_kinded_slot(result)?;
1157 }
1158 // ── Wave 5e: DateTime constructor builtins ────────────────
1159 //
1160 // DateTime values are `HeapValue::Temporal` carrying
1161 // `TemporalData::DateTime` (ADR-006 §2.3 typed-Arc payload);
1162 // the constructor bodies live in
1163 // `executor/builtins/datetime_builtins.rs` on the
1164 // `&[KindedSlot] -> Result<KindedSlot, VMError>` carrier ABI.
1165 BuiltinFunction::DateTimeNow => {
1166 let args = self.pop_builtin_args()?;
1167 let r = super::super::builtins::datetime_builtins::builtin_datetime_now(
1168 &args,
1169 )?;
1170 self.push_kinded_slot(r)?;
1171 }
1172 BuiltinFunction::DateTimeUtc => {
1173 let args = self.pop_builtin_args()?;
1174 let r = super::super::builtins::datetime_builtins::builtin_datetime_utc(
1175 &args,
1176 )?;
1177 self.push_kinded_slot(r)?;
1178 }
1179 BuiltinFunction::DateTimeParse => {
1180 let args = self.pop_builtin_args()?;
1181 let r = super::super::builtins::datetime_builtins::builtin_datetime_parse(
1182 &args,
1183 )?;
1184 self.push_kinded_slot(r)?;
1185 }
1186 BuiltinFunction::DateTimeFromEpoch => {
1187 let args = self.pop_builtin_args()?;
1188 let r =
1189 super::super::builtins::datetime_builtins::builtin_datetime_from_epoch(
1190 &args,
1191 )?;
1192 self.push_kinded_slot(r)?;
1193 }
1194 BuiltinFunction::DateTimeFromParts => {
1195 let args = self.pop_builtin_args()?;
1196 let r =
1197 super::super::builtins::datetime_builtins::builtin_datetime_from_parts(
1198 &args,
1199 )?;
1200 self.push_kinded_slot(r)?;
1201 }
1202 BuiltinFunction::DateTimeFromUnixSecs => {
1203 let args = self.pop_builtin_args()?;
1204 let r = super::super::builtins::datetime_builtins
1205 ::builtin_datetime_from_unix_secs(&args)?;
1206 self.push_kinded_slot(r)?;
1207 }
1208 // ── Wave 5e: mat() row-major matrix constructor ───────────
1209 BuiltinFunction::MatFromFlat => {
1210 let args = self.pop_builtin_args()?;
1211 let r = super::super::builtins::datetime_builtins::builtin_mat_from_flat(
1212 &args,
1213 )?;
1214 self.push_kinded_slot(r)?;
1215 }
1216 // ── Wave 5e: Table<T> from-rows constructor ───────────────
1217 BuiltinFunction::MakeTableFromRows => {
1218 let args = self.pop_builtin_args()?;
1219 let r = self.builtin_make_table_from_rows(&args)?;
1220 self.push_kinded_slot(r)?;
1221 }
1222 BuiltinFunction::JsonObjectGet
1223 | BuiltinFunction::JsonArrayAt
1224 | BuiltinFunction::JsonObjectKeys
1225 | BuiltinFunction::JsonArrayLen
1226 | BuiltinFunction::JsonObjectLen => {
1227 // SURFACE per ADR-006 §2.7.14: phase-1b-vm wave 5e —
1228 // JSON navigation helper body migration deferred.
1229 // Rebuild target lives at
1230 // `executor/builtins/json_helpers.rs`. Drain args to
1231 // balance the §2.7.7 parallel-kind track.
1232 let _args: Vec<KindedSlot> = self.pop_builtin_args()?;
1233 return Err(VMError::NotImplemented(format!(
1234 "phase-1b-vm-wave-5e-json-nav: {:?} body migration \
1235 to kinded carrier (executor/builtins/json_helpers.rs) \
1236 pending (v0.4 / planned)",
1237 builtin
1238 )));
1239 }
1240 // ── W8-WJ: window function dispatch (ADR-006 §2.7.10/Q11) ──
1241 //
1242 // Each handler is a free fn matching the MethodFnV2 body
1243 // shape: `fn(&mut VM, &[KindedSlot], Option<&mut Ctx>) ->
1244 // Result<KindedSlot, VMError>`. The dispatch shell pops
1245 // builtin args via `pop_builtin_args` (which constructs
1246 // `Vec<KindedSlot>` from the §2.7.7 stack parallel-kind
1247 // track), borrows it as `&[KindedSlot]` to the handler,
1248 // then re-pushes the kinded result via `push_kinded_slot`.
1249 BuiltinFunction::WindowRowNumber
1250 | BuiltinFunction::WindowRank
1251 | BuiltinFunction::WindowDenseRank
1252 | BuiltinFunction::WindowNtile => {
1253 let args = self.pop_builtin_args()?;
1254 let r = super::super::window_join::handle_window_row_number_v2(
1255 self, &args, _ctx,
1256 )?;
1257 self.push_kinded_slot(r)?;
1258 }
1259 BuiltinFunction::WindowLag | BuiltinFunction::WindowLead => {
1260 let args = self.pop_builtin_args()?;
1261 let r = super::super::window_join::handle_window_lag_v2(
1262 self, &args, _ctx,
1263 )?;
1264 self.push_kinded_slot(r)?;
1265 }
1266 BuiltinFunction::WindowFirstValue
1267 | BuiltinFunction::WindowLastValue
1268 | BuiltinFunction::WindowNthValue => {
1269 let args = self.pop_builtin_args()?;
1270 let r = super::super::window_join::handle_window_first_value_v2(
1271 self, &args, _ctx,
1272 )?;
1273 self.push_kinded_slot(r)?;
1274 }
1275 BuiltinFunction::WindowSum => {
1276 let args = self.pop_builtin_args()?;
1277 let r = super::super::window_join::handle_window_sum_v2(
1278 self, &args, _ctx,
1279 )?;
1280 self.push_kinded_slot(r)?;
1281 }
1282 BuiltinFunction::WindowAvg => {
1283 let args = self.pop_builtin_args()?;
1284 let r = super::super::window_join::handle_window_avg_v2(
1285 self, &args, _ctx,
1286 )?;
1287 self.push_kinded_slot(r)?;
1288 }
1289 BuiltinFunction::WindowMin => {
1290 let args = self.pop_builtin_args()?;
1291 let r = super::super::window_join::handle_window_min_v2(
1292 self, &args, _ctx,
1293 )?;
1294 self.push_kinded_slot(r)?;
1295 }
1296 BuiltinFunction::WindowMax => {
1297 let args = self.pop_builtin_args()?;
1298 let r = super::super::window_join::handle_window_max_v2(
1299 self, &args, _ctx,
1300 )?;
1301 self.push_kinded_slot(r)?;
1302 }
1303 BuiltinFunction::WindowCount => {
1304 let args = self.pop_builtin_args()?;
1305 let r = super::super::window_join::handle_window_count_v2(
1306 self, &args, _ctx,
1307 )?;
1308 self.push_kinded_slot(r)?;
1309 }
1310 BuiltinFunction::JoinExecute => {
1311 // SURFACE — cross-cluster cascade with
1312 // `datatable_methods::joins` ABI flip (W9 method-body
1313 // re-fill). Drains stack args to keep the parallel-
1314 // kind track balanced, then surfaces.
1315 let _args: Vec<KindedSlot> = self.pop_builtin_args()?;
1316 return self.handle_join_execute();
1317 }
1318 BuiltinFunction::Reflect => {
1319 // SURFACE per ADR-006 §2.7.14: phase-1b-vm wave 5e —
1320 // `reflect()` builtin body migration deferred. No arg
1321 // pop because the legacy emit shape did not emit an
1322 // arity prefix at this dispatch arm; the rebuild
1323 // wires arg-popping along with the body re-fill.
1324 return Err(VMError::NotImplemented(
1325 "phase-1b-vm-wave-5e-reflect: reflect() body \
1326 migration to kinded carrier pending \
1327 (v0.4 / planned)"
1328 .to_string(),
1329 ));
1330 }
1331
1332 // ── Eval-* removed-feature stubs (preserved as runtime
1333 // errors per pre-Wave 5a behaviour). These do not need
1334 // body migration; their semantics is already terminal. ──
1335 BuiltinFunction::EvalTimeRef => {
1336 return Err(VMError::NotImplemented(
1337 "eval_time_ref() (VM-only mode)".to_string(),
1338 ));
1339 }
1340 BuiltinFunction::EvalDateTimeExpr => {
1341 // C1-temporal-lowering (Phase 2d Wave 2): the
1342 // `compiler/expressions/temporal.rs::compile_expr_datetime`
1343 // emit sequence is PushConst(DateTimeExpr) +
1344 // BuiltinCall(EvalDateTimeExpr). The
1345 // `Constant::DateTimeExpr` arm in `op_push_const`
1346 // (`stack_ops/mod.rs`) now evaluates the AST via
1347 // `eval_datetime_expr_recursive` and pushes a
1348 // `NativeKind::Ptr(HeapKind::Temporal)` Temporal::DateTime
1349 // slot directly. There is therefore no work for this
1350 // builtin to do — the value the legacy semantics
1351 // produced ("pop DateTimeExpr Temporal, evaluate, push
1352 // DateTime Temporal") is already on the stack. Skip
1353 // arg-count pop: the compiler does not emit one, and
1354 // re-adding it would require changing the legacy emit
1355 // shape compiler-side without an upstream benefit.
1356 // ADR-006 §2.7.4.
1357 }
1358 BuiltinFunction::EvalDataDateTimeRef
1359 | BuiltinFunction::EvalDataSet
1360 | BuiltinFunction::EvalDataRelative
1361 | BuiltinFunction::EvalDataRelativeRange => {
1362 return Err(VMError::RuntimeError(
1363 "DataReference / DataRow type has been removed"
1364 .to_string(),
1365 ));
1366 }
1367 }
1368 } else {
1369 return Err(VMError::InvalidOperand);
1370 }
1371 Ok(())
1372 }
1373
1374 // ===== Print / Format helpers (ADR-006 §2.7.4) =====
1375
1376 /// Format every arg via `ValueFormatter::format_kinded`, join the
1377 /// rendered fragments with a space, then route through the active
1378 /// `ExecutionContext`'s [`OutputAdapter::print`] (or fall back to
1379 /// stdout when no context is plumbed — e.g. the bytecode-level
1380 /// `eval_*` helpers used by tests).
1381 ///
1382 /// W18.6 (R8 W3 2026-05-24 — supervisor D3+D4): TypedObject args
1383 /// whose schema's source-level type has a user-defined `Display` impl
1384 /// dispatch through `<TypeName>::display() -> content`. The returned
1385 /// `Content` KindedSlot is then routed through the formatter's
1386 /// `HeapKind::Content` arm (W18.2-wired TerminalRenderer path), so
1387 /// `print(Point { x: 1, y: 2 })` produces the user's
1388 /// `Content.text("(1, 2)")` projection rather than the schema-walk
1389 /// `{x: 1, y: 2}` fallback.
1390 pub(crate) fn builtin_print(
1391 &mut self,
1392 args: &[KindedSlot],
1393 ctx: Option<&mut shape_runtime::context::ExecutionContext>,
1394 ) -> Result<(), VMError> {
1395 // The TypedObject schema names live on `self.program.type_schema_registry`
1396 // (the BytecodeProgram-bound registry that `lookup_schema` reads).
1397 // The ExecutionContext's registry is the runtime-tier copy populated
1398 // via stdlib loading; both are searched so user-defined types and
1399 // stdlib types both resolve.
1400 //
1401 // W18.6: walk args; for each TypedObject arg whose source-level
1402 // schema name has a registered `Display::display` impl, invoke
1403 // it and substitute the returned `content` carrier before formatting.
1404 // We need to materialize owned KindedSlots either way (the
1405 // formatter borrows; Display dispatch produces fresh ones), so
1406 // the loop builds a `Vec<KindedSlot>` of values-to-format.
1407 let mut to_format: Vec<KindedSlot> = Vec::with_capacity(args.len());
1408 for a in args {
1409 if let Some(replaced) = self.try_dispatch_display(a)? {
1410 to_format.push(replaced);
1411 } else {
1412 // Borrow-share: Clone bumps refcount so the owned slot
1413 // can drop without disturbing the caller's share.
1414 to_format.push(a.clone());
1415 }
1416 }
1417
1418 let rendered = {
1419 let formatter =
1420 super::super::printing::ValueFormatter::new(&self.program.type_schema_registry);
1421 to_format
1422 .iter()
1423 .map(|a| formatter.format_kinded(a))
1424 .collect::<Vec<_>>()
1425 .join(" ")
1426 };
1427
1428 let result = shape_runtime::print_result::PrintResult {
1429 rendered,
1430 spans: Vec::new(),
1431 };
1432 // Route to the active `OutputAdapter` when an `ExecutionContext`
1433 // is plumbed (script runner, REPL, shape-server playground /
1434 // notebook — all of which install a capture/REPL/stdout adapter).
1435 // Fall back to stdout only when no context was supplied, e.g.
1436 // the bytecode-level `eval_*` helpers in tests.
1437 //
1438 // W18.6 (R8 W3 2026-05-24) originally dropped `ctx` here because
1439 // the Display dispatch loop took `&mut self` for an opaque
1440 // duration — but `try_dispatch_display` does not need `ctx` (the
1441 // Display body is expected to be pure per the inline doc on
1442 // `try_dispatch_display`), so the `&mut` borrow on `ctx` is
1443 // free at this point. Routing the rendered line to the adapter
1444 // restores hosted-embedder capture (`SharedCaptureAdapter` for
1445 // shape-server, `ReplAdapter` for REPL spans) without touching
1446 // the W18.6 Display-trait dispatch above.
1447 if let Some(ctx) = ctx {
1448 ctx.output_adapter_mut().print(result);
1449 } else {
1450 println!("{}", result.rendered);
1451 }
1452 Ok(())
1453 }
1454
1455 /// W18.6 (R8 W3 2026-05-24 — supervisor D3+D4): if `arg` is a
1456 /// TypedObject whose source-level schema name has a registered
1457 /// `Display::display` trait impl, invoke `<TypeName>::display()` and
1458 /// return the produced `content` KindedSlot. Returns `Ok(None)` when
1459 /// the arg is not a TypedObject, the schema has no name, or no
1460 /// Display impl is registered.
1461 ///
1462 /// `_ctx` is currently dropped — `execute_function_by_name` accepts
1463 /// an `Option<&mut ExecutionContext>`, but the borrow lifetime in
1464 /// the caller's loop is incompatible with re-acquiring `ctx` per
1465 /// iteration. Pre-W18.6 the print path also took `ctx` by &mut and
1466 /// did not pass it to nested calls; the Display body is expected to
1467 /// be pure / cheap (a single `Content.text(...)` wrap is the
1468 /// canonical pattern).
1469 fn try_dispatch_display(
1470 &mut self,
1471 arg: &KindedSlot,
1472 ) -> Result<Option<KindedSlot>, VMError> {
1473 use shape_value::heap_value::HeapKind;
1474 // Only TypedObject receivers can have user-defined Display impls.
1475 let shape_value::NativeKind::Ptr(HeapKind::TypedObject) = arg.kind else {
1476 return Ok(None);
1477 };
1478 let bits = arg.slot.raw();
1479 if bits == 0 {
1480 return Ok(None);
1481 }
1482 // SAFETY: per the `KindedSlot::from_typed_object` construction-
1483 // side contract, `Ptr(TypedObject)` bits are
1484 // `Arc::into_raw(Arc<TypedObjectStorage>)`. The borrow is
1485 // bounded by the caller's `args` lifetime.
1486 let storage: &shape_value::heap_value::TypedObjectStorage =
1487 unsafe { &*(bits as *const shape_value::heap_value::TypedObjectStorage) };
1488 let schema = self
1489 .program
1490 .type_schema_registry
1491 .get_by_id(storage.schema_id as u32);
1492 let Some(schema) = schema else {
1493 return Ok(None);
1494 };
1495 let type_name = schema.name.clone();
1496 if type_name.is_empty() {
1497 return Ok(None);
1498 }
1499 // Skip enum types — their `format_enum_typed_object` path
1500 // already produces the right `Variant(payload)` shape and
1501 // W18.0 enum-variant-display did not introduce a Display impl
1502 // for enums.
1503 if schema.is_enum() {
1504 return Ok(None);
1505 }
1506 let Some(func_name) = self
1507 .program
1508 .find_default_trait_impl_for_type_method(&type_name, "display")
1509 .map(|s| s.to_string())
1510 else {
1511 return Ok(None);
1512 };
1513 // Build the args vector: receiver (self) is the TypedObject
1514 // arg. `execute_function_by_id` takes `Vec<KindedSlot>` by value
1515 // and drops each slot at scope exit, retiring one share per
1516 // arg. `.clone()` bumps the receiver's refcount so the caller's
1517 // share is preserved.
1518 let receiver = arg.clone();
1519 let result = self.execute_function_by_name(&func_name, vec![receiver], None)?;
1520 Ok(Some(result))
1521 }
1522
1523 /// Format every arg via `ValueFormatter::format_kinded` and
1524 /// concatenate (no separator). Returns the rendered text wrapped in
1525 /// a `String`-kinded `KindedSlot`. Used by `format(…)` (multi-arg
1526 /// concat) and by `FormatValueWithMeta` (single-arg
1527 /// `expr.to_string()` / interpolation).
1528 pub(crate) fn builtin_format(
1529 &mut self,
1530 args: &[KindedSlot],
1531 ) -> Result<KindedSlot, VMError> {
1532 let formatter =
1533 super::super::printing::ValueFormatter::new(&self.program.type_schema_registry);
1534 let mut out = String::new();
1535 for a in args {
1536 out.push_str(&formatter.format_kinded(a));
1537 }
1538 Ok(KindedSlot::from_string_arc(std::sync::Arc::new(out)))
1539 }
1540
1541 /// `FormatValueWithSpec`: `[value, spec_tag, …spec-payload]`. Routes
1542 /// the FORMAT_SPEC_FIXED arm (precision-controlled f64 rendering);
1543 /// the Table arm surfaces per W13 playbook §7.4 surface-and-stop.
1544 pub(crate) fn builtin_format_with_spec(
1545 &mut self,
1546 args: &[KindedSlot],
1547 ) -> Result<KindedSlot, VMError> {
1548 const FORMAT_SPEC_FIXED: i64 = 1;
1549 const FORMAT_SPEC_TABLE: i64 = 2;
1550
1551 if args.is_empty() {
1552 return Err(VMError::RuntimeError(
1553 "FormatValueWithSpec requires at least 1 argument".to_string(),
1554 ));
1555 }
1556
1557 // The spec_tag arrives as an `int` constant (`PushConst(Constant::Int(_))`)
1558 // — kind `Int64` in the post-§2.7.7 stack ABI. Read defensively:
1559 // kind-mismatch falls through to the meta path so a malformed
1560 // dispatch still produces a string rather than crashing.
1561 let spec_tag = args.get(1).and_then(|s| match s.kind {
1562 shape_value::NativeKind::Int64
1563 | shape_value::NativeKind::Int32
1564 | shape_value::NativeKind::Int16
1565 | shape_value::NativeKind::Int8
1566 | shape_value::NativeKind::IntSize => Some(s.slot.as_i64()),
1567 _ => None,
1568 });
1569
1570 match spec_tag {
1571 Some(tag) if tag == FORMAT_SPEC_FIXED => {
1572 let precision = args.get(2).and_then(|s| match s.kind {
1573 shape_value::NativeKind::Int64
1574 | shape_value::NativeKind::Int32
1575 | shape_value::NativeKind::Int16
1576 | shape_value::NativeKind::Int8
1577 | shape_value::NativeKind::IntSize => Some(s.slot.as_i64()),
1578 _ => None,
1579 });
1580 let v = &args[0];
1581 // Coerce numeric kinds; non-numeric fall back to default
1582 // formatting so the spec is a no-op rather than an error.
1583 let f = match v.kind {
1584 shape_value::NativeKind::Float64
1585 | shape_value::NativeKind::NullableFloat64 => Some(v.slot.as_f64()),
1586 shape_value::NativeKind::Int64
1587 | shape_value::NativeKind::Int32
1588 | shape_value::NativeKind::Int16
1589 | shape_value::NativeKind::Int8
1590 | shape_value::NativeKind::IntSize => Some(v.slot.as_i64() as f64),
1591 shape_value::NativeKind::UInt64
1592 | shape_value::NativeKind::UInt32
1593 | shape_value::NativeKind::UInt16
1594 | shape_value::NativeKind::UInt8
1595 | shape_value::NativeKind::UIntSize => Some(v.slot.as_u64() as f64),
1596 _ => None,
1597 };
1598 let rendered = match (f, precision) {
1599 (Some(f), Some(p)) if p >= 0 => {
1600 format!("{:.*}", p as usize, f)
1601 }
1602 _ => self.builtin_format(&args[..1])?.as_str().unwrap_or("").to_string(),
1603 };
1604 Ok(KindedSlot::from_string_arc(std::sync::Arc::new(rendered)))
1605 }
1606 Some(tag) if tag == FORMAT_SPEC_TABLE => {
1607 Err(VMError::NotImplemented(
1608 "FormatValueWithSpec: FORMAT_SPEC_TABLE rendering deferred — \
1609 W13-print-formatter scope is the FORMAT_SPEC_FIXED + \
1610 no-spec path. Table rendering reuses the DataTable / \
1611 TableView Display impls; surface-and-stop pending the \
1612 next pass per W13 playbook §7.4."
1613 .to_string(),
1614 ))
1615 }
1616 _ => self.builtin_format(&args[..1]),
1617 }
1618 }
1619
1620 // Runtime bridge functions (pop_builtin_args impl, eval_runtime_*)
1621 // moved to builtins/runtime_bridge.rs.
1622 // map_runtime_error and type_of_name moved to module_registry module.
1623
1624 // ===== Helper Methods =====
1625 // binary_arithmetic, eval_runtime_binary_op_value, binary_comparison
1626 // moved to arithmetic/mod.rs
1627}
1628
1629// ─────────────────────────────────────────────────────────────────────────
1630// W18.5 content builder helpers (R8 W4, 2026-05-24 — supervisor D4).
1631//
1632// Free-function helpers (not VirtualMachine methods) used by the
1633// `Content.table` / `Content.kv` / `Content.fragment` constructor arms and
1634// the Content method handlers in `objects/content_methods.rs`. The
1635// builder pattern relies on these helpers to:
1636// - read a `Ptr(HeapKind::Content)` slot back into a `ContentNode`
1637// - read string elements from a v2 `TypedArray<*const StringObj>`
1638// - format a heterogeneous `KindedSlot` value into a `ContentNode::plain`
1639// cell (for `Table.row(...)` / `KeyValue.pair("k", v)` value coercion)
1640//
1641// Per supervisor D4 "shortest path builder → content → renderer", these
1642// helpers stay at the dispatch shell — no cross-crate detour into
1643// shape-runtime, no parallel-implementation of styling spec types (the
1644// W18.4 shared spec module is a follow-up — see commit message).
1645// ─────────────────────────────────────────────────────────────────────────
1646
1647/// Read string elements from a v2 `TypedArray<*const StringObj>` slot.
1648///
1649/// Returns `None` if the slot is not a v2-raw typed array of strings; the
1650/// caller surfaces a typed error. The returned `Vec<String>` owns its
1651/// contents — each element is copied out of the array's interned UTF-8.
1652pub(in crate::executor) fn read_string_array(slot: &KindedSlot) -> Option<Vec<String>> {
1653 use crate::executor::v2_handlers::v2_array_detect::{
1654 as_v2_typed_array, read_element, V2ElemType,
1655 };
1656 use shape_value::{HeapKind, NativeKind};
1657 if slot.kind != NativeKind::Ptr(HeapKind::TypedArray) {
1658 return None;
1659 }
1660 let view = as_v2_typed_array(slot.slot.raw(), slot.kind)?;
1661 if view.elem_type != V2ElemType::String {
1662 return None;
1663 }
1664 let mut out = Vec::with_capacity(view.len as usize);
1665 for i in 0..view.len {
1666 let (bits, kind) = read_element(&view, i)?;
1667 // `read_element` retains a fresh share on the element header per
1668 // its Wave-2-Agent-A2 contract (see
1669 // `executor/v2_handlers/v2_array_detect.rs:378-388`); wrap into
1670 // a `KindedSlot` so the share retires on drop after we've copied
1671 // the UTF-8 out via `as_str`.
1672 let elem_slot = KindedSlot::new(ValueSlot::from_raw(bits), kind);
1673 let s = elem_slot.as_str()?.to_string();
1674 // `elem_slot` drops at end of iteration — releases the share.
1675 let _ = elem_slot;
1676 out.push(s);
1677 }
1678 Some(out)
1679}
1680
1681/// Read a `Ptr(HeapKind::Content)` slot as an `Arc<ContentNode>`.
1682///
1683/// Returns `None` if the kind doesn't match. The returned Arc is a fresh
1684/// strong-count share (incremented from the slot's bits); the caller is
1685/// responsible for the share-accounting of the returned Arc.
1686pub(in crate::executor) fn read_content_arc(
1687 slot: &KindedSlot,
1688) -> Option<std::sync::Arc<shape_value::content::ContentNode>> {
1689 use shape_value::{HeapKind, NativeKind};
1690 if slot.kind != NativeKind::Ptr(HeapKind::Content) {
1691 return None;
1692 }
1693 let bits = slot.slot.raw();
1694 if bits == 0 {
1695 return None;
1696 }
1697 // SAFETY: by construction `Ptr(HeapKind::Content)` slot bits are
1698 // `Arc::into_raw(Arc<ContentNode>) as u64` (set by
1699 // `KindedSlot::from_content` and its producers — `ContentTextCtor`,
1700 // `ContentCodeCtor`, this module's W18.5 ctors, Display.display()
1701 // returns, etc.). The slot owns one strong-count share for the
1702 // dispatch duration. We borrow the inner Arc by reconstituting it,
1703 // cloning to get a fresh share, then `mem::forget`-ing the original
1704 // reconstitution so the slot's share remains intact.
1705 unsafe {
1706 let raw = bits as *const shape_value::content::ContentNode;
1707 let arc = std::sync::Arc::from_raw(raw);
1708 let cloned = arc.clone();
1709 std::mem::forget(arc);
1710 Some(cloned)
1711 }
1712}
1713
1714/// Build a `ContentTable` from a `Content.table(headers, rows)` arg list.
1715///
1716/// `args[0]` is the headers array (`Array<string>`), `args[1]` is the
1717/// rows array (`Array<Array<string>>`). Cell values render as
1718/// `ContentNode::plain` strings — string-typed MVP per supervisor D4
1719/// "string-typed-MVP follow-up surfaced for W18.4 spec-types swap".
1720fn build_table_from_headers_and_rows(
1721 args: &[KindedSlot],
1722) -> Result<shape_value::content::ContentTable, VMError> {
1723 if args.len() != 2 {
1724 return Err(VMError::RuntimeError(format!(
1725 "Content.table(headers, rows) requires exactly 2 arguments, \
1726 got {}",
1727 args.len()
1728 )));
1729 }
1730 let headers = read_string_array(&args[0]).ok_or_else(|| {
1731 VMError::RuntimeError(format!(
1732 "Content.table: headers argument must be Array<string>, got \
1733 kind {:?}",
1734 args[0].kind
1735 ))
1736 })?;
1737 // Rows is an Array<Array<string>>. The outer array carries
1738 // TypedArray-of-TypedArray pointers. Read each inner row via
1739 // `read_string_array`.
1740 use crate::executor::v2_handlers::v2_array_detect::{
1741 as_v2_typed_array, read_element,
1742 };
1743 use shape_value::{HeapKind, NativeKind};
1744 if args[1].kind != NativeKind::Ptr(HeapKind::TypedArray) {
1745 return Err(VMError::RuntimeError(format!(
1746 "Content.table: rows argument must be Array<Array<string>>, \
1747 got kind {:?}",
1748 args[1].kind
1749 )));
1750 }
1751 let outer_view = as_v2_typed_array(args[1].slot.raw(), args[1].kind)
1752 .ok_or_else(|| {
1753 VMError::RuntimeError(
1754 "Content.table: rows array has invalid v2 header".to_string(),
1755 )
1756 })?;
1757 let mut rows: Vec<Vec<shape_value::content::ContentNode>> =
1758 Vec::with_capacity(outer_view.len as usize);
1759 for i in 0..outer_view.len {
1760 let (bits, kind) = read_element(&outer_view, i).ok_or_else(|| {
1761 VMError::RuntimeError(format!(
1762 "Content.table: failed to read row {} from rows array",
1763 i
1764 ))
1765 })?;
1766 let row_slot = KindedSlot::new(ValueSlot::from_raw(bits), kind);
1767 let cells = read_string_array(&row_slot).ok_or_else(|| {
1768 VMError::RuntimeError(format!(
1769 "Content.table: row {} must be Array<string>, got kind {:?}",
1770 i, row_slot.kind
1771 ))
1772 })?;
1773 // Drop row_slot to release the share held by `read_element`.
1774 drop(row_slot);
1775 let row_nodes: Vec<shape_value::content::ContentNode> = cells
1776 .into_iter()
1777 .map(shape_value::content::ContentNode::plain)
1778 .collect();
1779 rows.push(row_nodes);
1780 }
1781 Ok(shape_value::content::ContentTable {
1782 headers,
1783 rows,
1784 border: shape_value::content::BorderStyle::default(),
1785 max_rows: None,
1786 column_types: None,
1787 total_rows: None,
1788 sortable: false,
1789 })
1790}
1791
1792/// Build a `Vec<(String, ContentNode)>` from a `Content.kv(keys, values)`
1793/// arg list — keys array is `Array<string>`, values array is the parallel
1794/// `Array<*>` whose elements coerce through `format_kinded`.
1795fn build_kv_pairs_from_keys_values(
1796 vm: &VirtualMachine,
1797 args: &[KindedSlot],
1798) -> Result<Vec<(String, shape_value::content::ContentNode)>, VMError> {
1799 if args.len() != 2 {
1800 return Err(VMError::RuntimeError(format!(
1801 "Content.kv(keys, values) requires exactly 2 arguments, got {}",
1802 args.len()
1803 )));
1804 }
1805 let keys = read_string_array(&args[0]).ok_or_else(|| {
1806 VMError::RuntimeError(format!(
1807 "Content.kv: keys argument must be Array<string>, got kind {:?}",
1808 args[0].kind
1809 ))
1810 })?;
1811 use crate::executor::v2_handlers::v2_array_detect::{
1812 as_v2_typed_array, read_element,
1813 };
1814 use shape_value::{HeapKind, NativeKind};
1815 if args[1].kind != NativeKind::Ptr(HeapKind::TypedArray) {
1816 return Err(VMError::RuntimeError(format!(
1817 "Content.kv: values argument must be an Array, got kind {:?}",
1818 args[1].kind
1819 )));
1820 }
1821 let view = as_v2_typed_array(args[1].slot.raw(), args[1].kind).ok_or_else(|| {
1822 VMError::RuntimeError("Content.kv: values array has invalid v2 header".to_string())
1823 })?;
1824 if (view.len as usize) != keys.len() {
1825 return Err(VMError::RuntimeError(format!(
1826 "Content.kv: keys.len() ({}) != values.len() ({})",
1827 keys.len(),
1828 view.len
1829 )));
1830 }
1831 let formatter = super::super::printing::ValueFormatter::new(&vm.program.type_schema_registry);
1832 let mut pairs = Vec::with_capacity(keys.len());
1833 for (i, key) in keys.into_iter().enumerate() {
1834 let (bits, kind) = read_element(&view, i as u32).ok_or_else(|| {
1835 VMError::RuntimeError(format!("Content.kv: failed to read value at index {}", i))
1836 })?;
1837 let val_slot = KindedSlot::new(ValueSlot::from_raw(bits), kind);
1838 let rendered = formatter.format_kinded(&val_slot);
1839 // val_slot drops here — releases the share.
1840 drop(val_slot);
1841 pairs.push((key, shape_value::content::ContentNode::plain(rendered)));
1842 }
1843 Ok(pairs)
1844}
1845
1846/// Read an `Array<content>` argument and collect each element as an
1847/// owned `ContentNode`. Each element of the array must have kind
1848/// `Ptr(HeapKind::Content)`; the function clones the inner ContentNode
1849/// out of the read-share Arc returned by `read_content_arc`.
1850fn collect_content_nodes_from_array_arg(
1851 args: &[KindedSlot],
1852) -> Result<Vec<shape_value::content::ContentNode>, VMError> {
1853 if args.len() != 1 {
1854 return Err(VMError::RuntimeError(format!(
1855 "Content.fragment(parts) requires exactly 1 argument \
1856 (Array<content>), got {}",
1857 args.len()
1858 )));
1859 }
1860 use crate::executor::v2_handlers::v2_array_detect::{
1861 as_v2_typed_array, read_element,
1862 };
1863 use shape_value::{HeapKind, NativeKind};
1864 if args[0].kind != NativeKind::Ptr(HeapKind::TypedArray) {
1865 return Err(VMError::RuntimeError(format!(
1866 "Content.fragment: parts argument must be Array<content>, got \
1867 kind {:?}",
1868 args[0].kind
1869 )));
1870 }
1871 let view = as_v2_typed_array(args[0].slot.raw(), args[0].kind).ok_or_else(|| {
1872 VMError::RuntimeError(
1873 "Content.fragment: parts array has invalid v2 header".to_string(),
1874 )
1875 })?;
1876 let mut parts = Vec::with_capacity(view.len as usize);
1877 for i in 0..view.len {
1878 let (bits, kind) = read_element(&view, i).ok_or_else(|| {
1879 VMError::RuntimeError(format!(
1880 "Content.fragment: failed to read element {}",
1881 i
1882 ))
1883 })?;
1884 let elem_slot = KindedSlot::new(ValueSlot::from_raw(bits), kind);
1885 let arc = read_content_arc(&elem_slot).ok_or_else(|| {
1886 VMError::RuntimeError(format!(
1887 "Content.fragment: element {} must be a content value, got \
1888 kind {:?}",
1889 i, elem_slot.kind
1890 ))
1891 })?;
1892 // Clone the underlying ContentNode out of the Arc (cheap — most
1893 // variants are themselves Arc-shaped vectors / structs that share
1894 // their interior storage on clone).
1895 parts.push((*arc).clone());
1896 drop(elem_slot);
1897 }
1898 Ok(parts)
1899}
1900
1901// W12-stdlib-intrinsic-collapse (Wave-2-Agent-G, 2026-05-14): the
1902// `intrinsic_sum_tests` module previously here exercised the deleted
1903// `BuiltinFunction::IntrinsicSum` opcode body. Equivalent coverage lives
1904// in the PHF method-dispatch handlers' own test surface
1905// (`typed_array_methods` / `array_aggregation` / `typed_int_array_methods`
1906// / `typed_number_array_methods`) — single discriminator per ADR-005 §1.
1907
1908// ===== R8 W4 W18.4: f-string styled-content lowering helpers =====
1909//
1910// (supervisor 2026-05-24 D1 + (a-modified) REVIVE-WITH-SHARED-MODULE)
1911//
1912// The compiler at `compiler/string_interpolation.rs` lowers each styled
1913// interpolation `{x:bold,red}` to a `FStringContentStyledText` builtin
1914// call whose i64-encoded payload is decoded here back into a
1915// `shape_value::content::Style`.
1916
1917const FSTRING_COLOR_NONE: i64 = -1;
1918const FSTRING_COLOR_NAMED: i64 = 0;
1919const FSTRING_COLOR_RGB: i64 = 1;
1920
1921const FSTRING_FLAG_BOLD: i64 = 1;
1922const FSTRING_FLAG_ITALIC: i64 = 2;
1923const FSTRING_FLAG_UNDERLINE: i64 = 4;
1924const FSTRING_FLAG_DIM: i64 = 8;
1925
1926fn decode_fstring_color(kind: i64, payload: i64) -> Result<Option<shape_value::content::Color>, VMError> {
1927 use shape_value::content::{Color, NamedColor};
1928 match kind {
1929 FSTRING_COLOR_NONE => Ok(None),
1930 FSTRING_COLOR_NAMED => {
1931 let named = match payload {
1932 0 => NamedColor::Red,
1933 1 => NamedColor::Green,
1934 2 => NamedColor::Blue,
1935 3 => NamedColor::Yellow,
1936 4 => NamedColor::Magenta,
1937 5 => NamedColor::Cyan,
1938 6 => NamedColor::White,
1939 7 => NamedColor::Default,
1940 other => {
1941 return Err(VMError::RuntimeError(format!(
1942 "decode_fstring_color: invalid named-color id {}",
1943 other
1944 )));
1945 }
1946 };
1947 Ok(Some(Color::Named(named)))
1948 }
1949 FSTRING_COLOR_RGB => {
1950 let r = ((payload >> 16) & 0xFF) as u8;
1951 let g = ((payload >> 8) & 0xFF) as u8;
1952 let b = (payload & 0xFF) as u8;
1953 Ok(Some(Color::Rgb(r, g, b)))
1954 }
1955 other => Err(VMError::RuntimeError(format!(
1956 "decode_fstring_color: invalid color kind {}",
1957 other
1958 ))),
1959 }
1960}
1961
1962fn decode_fstring_style(
1963 fg_kind: i64,
1964 fg_payload: i64,
1965 bg_kind: i64,
1966 bg_payload: i64,
1967 flags: i64,
1968) -> Result<shape_value::content::Style, VMError> {
1969 Ok(shape_value::content::Style {
1970 fg: decode_fstring_color(fg_kind, fg_payload)?,
1971 bg: decode_fstring_color(bg_kind, bg_payload)?,
1972 bold: (flags & FSTRING_FLAG_BOLD) != 0,
1973 italic: (flags & FSTRING_FLAG_ITALIC) != 0,
1974 underline: (flags & FSTRING_FLAG_UNDERLINE) != 0,
1975 dim: (flags & FSTRING_FLAG_DIM) != 0,
1976 })
1977}
1978
1979#[cfg(test)]
1980mod fstring_decode_tests {
1981 use super::*;
1982
1983 #[test]
1984 fn decode_color_none() {
1985 assert_eq!(decode_fstring_color(FSTRING_COLOR_NONE, 0).unwrap(), None);
1986 }
1987
1988 #[test]
1989 fn decode_color_named_red() {
1990 use shape_value::content::{Color, NamedColor};
1991 assert_eq!(
1992 decode_fstring_color(FSTRING_COLOR_NAMED, 0).unwrap(),
1993 Some(Color::Named(NamedColor::Red))
1994 );
1995 }
1996
1997 #[test]
1998 fn decode_color_rgb_roundtrip() {
1999 use shape_value::content::Color;
2000 // (10 << 16) | (20 << 8) | 30
2001 let payload = (10 << 16) | (20 << 8) | 30;
2002 assert_eq!(
2003 decode_fstring_color(FSTRING_COLOR_RGB, payload).unwrap(),
2004 Some(Color::Rgb(10, 20, 30))
2005 );
2006 }
2007
2008 #[test]
2009 fn decode_style_bold_red() {
2010 use shape_value::content::{Color, NamedColor};
2011 let style = decode_fstring_style(
2012 FSTRING_COLOR_NAMED,
2013 0, // Red
2014 FSTRING_COLOR_NONE,
2015 0,
2016 FSTRING_FLAG_BOLD,
2017 )
2018 .unwrap();
2019 assert!(style.bold);
2020 assert!(!style.italic);
2021 assert_eq!(style.fg, Some(Color::Named(NamedColor::Red)));
2022 assert_eq!(style.bg, None);
2023 }
2024
2025 #[test]
2026 fn decode_style_all_flags() {
2027 let style = decode_fstring_style(
2028 FSTRING_COLOR_NONE,
2029 0,
2030 FSTRING_COLOR_NONE,
2031 0,
2032 FSTRING_FLAG_BOLD
2033 | FSTRING_FLAG_ITALIC
2034 | FSTRING_FLAG_UNDERLINE
2035 | FSTRING_FLAG_DIM,
2036 )
2037 .unwrap();
2038 assert!(style.bold);
2039 assert!(style.italic);
2040 assert!(style.underline);
2041 assert!(style.dim);
2042 }
2043
2044 #[test]
2045 fn decode_color_invalid_kind() {
2046 assert!(decode_fstring_color(99, 0).is_err());
2047 }
2048
2049 #[test]
2050 fn decode_color_invalid_named_id() {
2051 assert!(decode_fstring_color(FSTRING_COLOR_NAMED, 99).is_err());
2052 }
2053}