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 let _ = ctx;
1418
1419 let rendered = {
1420 let formatter =
1421 super::super::printing::ValueFormatter::new(&self.program.type_schema_registry);
1422 to_format
1423 .iter()
1424 .map(|a| formatter.format_kinded(a))
1425 .collect::<Vec<_>>()
1426 .join(" ")
1427 };
1428
1429 let result = shape_runtime::print_result::PrintResult {
1430 rendered,
1431 spans: Vec::new(),
1432 };
1433 // `ctx` was consumed by the dispatch loop above; re-acquire via
1434 // the shared output adapter on the VM-level executor context if
1435 // present. For W18.6 we route to stdout unconditionally when no
1436 // adapter was supplied — same fallback as the pre-W18.6 path.
1437 println!("{}", result.rendered);
1438 Ok(())
1439 }
1440
1441 /// W18.6 (R8 W3 2026-05-24 — supervisor D3+D4): if `arg` is a
1442 /// TypedObject whose source-level schema name has a registered
1443 /// `Display::display` trait impl, invoke `<TypeName>::display()` and
1444 /// return the produced `content` KindedSlot. Returns `Ok(None)` when
1445 /// the arg is not a TypedObject, the schema has no name, or no
1446 /// Display impl is registered.
1447 ///
1448 /// `_ctx` is currently dropped — `execute_function_by_name` accepts
1449 /// an `Option<&mut ExecutionContext>`, but the borrow lifetime in
1450 /// the caller's loop is incompatible with re-acquiring `ctx` per
1451 /// iteration. Pre-W18.6 the print path also took `ctx` by &mut and
1452 /// did not pass it to nested calls; the Display body is expected to
1453 /// be pure / cheap (a single `Content.text(...)` wrap is the
1454 /// canonical pattern).
1455 fn try_dispatch_display(
1456 &mut self,
1457 arg: &KindedSlot,
1458 ) -> Result<Option<KindedSlot>, VMError> {
1459 use shape_value::heap_value::HeapKind;
1460 // Only TypedObject receivers can have user-defined Display impls.
1461 let shape_value::NativeKind::Ptr(HeapKind::TypedObject) = arg.kind else {
1462 return Ok(None);
1463 };
1464 let bits = arg.slot.raw();
1465 if bits == 0 {
1466 return Ok(None);
1467 }
1468 // SAFETY: per the `KindedSlot::from_typed_object` construction-
1469 // side contract, `Ptr(TypedObject)` bits are
1470 // `Arc::into_raw(Arc<TypedObjectStorage>)`. The borrow is
1471 // bounded by the caller's `args` lifetime.
1472 let storage: &shape_value::heap_value::TypedObjectStorage =
1473 unsafe { &*(bits as *const shape_value::heap_value::TypedObjectStorage) };
1474 let schema = self
1475 .program
1476 .type_schema_registry
1477 .get_by_id(storage.schema_id as u32);
1478 let Some(schema) = schema else {
1479 return Ok(None);
1480 };
1481 let type_name = schema.name.clone();
1482 if type_name.is_empty() {
1483 return Ok(None);
1484 }
1485 // Skip enum types — their `format_enum_typed_object` path
1486 // already produces the right `Variant(payload)` shape and
1487 // W18.0 enum-variant-display did not introduce a Display impl
1488 // for enums.
1489 if schema.is_enum() {
1490 return Ok(None);
1491 }
1492 let Some(func_name) = self
1493 .program
1494 .find_default_trait_impl_for_type_method(&type_name, "display")
1495 .map(|s| s.to_string())
1496 else {
1497 return Ok(None);
1498 };
1499 // Build the args vector: receiver (self) is the TypedObject
1500 // arg. `execute_function_by_id` takes `Vec<KindedSlot>` by value
1501 // and drops each slot at scope exit, retiring one share per
1502 // arg. `.clone()` bumps the receiver's refcount so the caller's
1503 // share is preserved.
1504 let receiver = arg.clone();
1505 let result = self.execute_function_by_name(&func_name, vec![receiver], None)?;
1506 Ok(Some(result))
1507 }
1508
1509 /// Format every arg via `ValueFormatter::format_kinded` and
1510 /// concatenate (no separator). Returns the rendered text wrapped in
1511 /// a `String`-kinded `KindedSlot`. Used by `format(…)` (multi-arg
1512 /// concat) and by `FormatValueWithMeta` (single-arg
1513 /// `expr.to_string()` / interpolation).
1514 pub(crate) fn builtin_format(
1515 &mut self,
1516 args: &[KindedSlot],
1517 ) -> Result<KindedSlot, VMError> {
1518 let formatter =
1519 super::super::printing::ValueFormatter::new(&self.program.type_schema_registry);
1520 let mut out = String::new();
1521 for a in args {
1522 out.push_str(&formatter.format_kinded(a));
1523 }
1524 Ok(KindedSlot::from_string_arc(std::sync::Arc::new(out)))
1525 }
1526
1527 /// `FormatValueWithSpec`: `[value, spec_tag, …spec-payload]`. Routes
1528 /// the FORMAT_SPEC_FIXED arm (precision-controlled f64 rendering);
1529 /// the Table arm surfaces per W13 playbook §7.4 surface-and-stop.
1530 pub(crate) fn builtin_format_with_spec(
1531 &mut self,
1532 args: &[KindedSlot],
1533 ) -> Result<KindedSlot, VMError> {
1534 const FORMAT_SPEC_FIXED: i64 = 1;
1535 const FORMAT_SPEC_TABLE: i64 = 2;
1536
1537 if args.is_empty() {
1538 return Err(VMError::RuntimeError(
1539 "FormatValueWithSpec requires at least 1 argument".to_string(),
1540 ));
1541 }
1542
1543 // The spec_tag arrives as an `int` constant (`PushConst(Constant::Int(_))`)
1544 // — kind `Int64` in the post-§2.7.7 stack ABI. Read defensively:
1545 // kind-mismatch falls through to the meta path so a malformed
1546 // dispatch still produces a string rather than crashing.
1547 let spec_tag = args.get(1).and_then(|s| match s.kind {
1548 shape_value::NativeKind::Int64
1549 | shape_value::NativeKind::Int32
1550 | shape_value::NativeKind::Int16
1551 | shape_value::NativeKind::Int8
1552 | shape_value::NativeKind::IntSize => Some(s.slot.as_i64()),
1553 _ => None,
1554 });
1555
1556 match spec_tag {
1557 Some(tag) if tag == FORMAT_SPEC_FIXED => {
1558 let precision = args.get(2).and_then(|s| match s.kind {
1559 shape_value::NativeKind::Int64
1560 | shape_value::NativeKind::Int32
1561 | shape_value::NativeKind::Int16
1562 | shape_value::NativeKind::Int8
1563 | shape_value::NativeKind::IntSize => Some(s.slot.as_i64()),
1564 _ => None,
1565 });
1566 let v = &args[0];
1567 // Coerce numeric kinds; non-numeric fall back to default
1568 // formatting so the spec is a no-op rather than an error.
1569 let f = match v.kind {
1570 shape_value::NativeKind::Float64
1571 | shape_value::NativeKind::NullableFloat64 => Some(v.slot.as_f64()),
1572 shape_value::NativeKind::Int64
1573 | shape_value::NativeKind::Int32
1574 | shape_value::NativeKind::Int16
1575 | shape_value::NativeKind::Int8
1576 | shape_value::NativeKind::IntSize => Some(v.slot.as_i64() as f64),
1577 shape_value::NativeKind::UInt64
1578 | shape_value::NativeKind::UInt32
1579 | shape_value::NativeKind::UInt16
1580 | shape_value::NativeKind::UInt8
1581 | shape_value::NativeKind::UIntSize => Some(v.slot.as_u64() as f64),
1582 _ => None,
1583 };
1584 let rendered = match (f, precision) {
1585 (Some(f), Some(p)) if p >= 0 => {
1586 format!("{:.*}", p as usize, f)
1587 }
1588 _ => self.builtin_format(&args[..1])?.as_str().unwrap_or("").to_string(),
1589 };
1590 Ok(KindedSlot::from_string_arc(std::sync::Arc::new(rendered)))
1591 }
1592 Some(tag) if tag == FORMAT_SPEC_TABLE => {
1593 Err(VMError::NotImplemented(
1594 "FormatValueWithSpec: FORMAT_SPEC_TABLE rendering deferred — \
1595 W13-print-formatter scope is the FORMAT_SPEC_FIXED + \
1596 no-spec path. Table rendering reuses the DataTable / \
1597 TableView Display impls; surface-and-stop pending the \
1598 next pass per W13 playbook §7.4."
1599 .to_string(),
1600 ))
1601 }
1602 _ => self.builtin_format(&args[..1]),
1603 }
1604 }
1605
1606 // Runtime bridge functions (pop_builtin_args impl, eval_runtime_*)
1607 // moved to builtins/runtime_bridge.rs.
1608 // map_runtime_error and type_of_name moved to module_registry module.
1609
1610 // ===== Helper Methods =====
1611 // binary_arithmetic, eval_runtime_binary_op_value, binary_comparison
1612 // moved to arithmetic/mod.rs
1613}
1614
1615// ─────────────────────────────────────────────────────────────────────────
1616// W18.5 content builder helpers (R8 W4, 2026-05-24 — supervisor D4).
1617//
1618// Free-function helpers (not VirtualMachine methods) used by the
1619// `Content.table` / `Content.kv` / `Content.fragment` constructor arms and
1620// the Content method handlers in `objects/content_methods.rs`. The
1621// builder pattern relies on these helpers to:
1622// - read a `Ptr(HeapKind::Content)` slot back into a `ContentNode`
1623// - read string elements from a v2 `TypedArray<*const StringObj>`
1624// - format a heterogeneous `KindedSlot` value into a `ContentNode::plain`
1625// cell (for `Table.row(...)` / `KeyValue.pair("k", v)` value coercion)
1626//
1627// Per supervisor D4 "shortest path builder → content → renderer", these
1628// helpers stay at the dispatch shell — no cross-crate detour into
1629// shape-runtime, no parallel-implementation of styling spec types (the
1630// W18.4 shared spec module is a follow-up — see commit message).
1631// ─────────────────────────────────────────────────────────────────────────
1632
1633/// Read string elements from a v2 `TypedArray<*const StringObj>` slot.
1634///
1635/// Returns `None` if the slot is not a v2-raw typed array of strings; the
1636/// caller surfaces a typed error. The returned `Vec<String>` owns its
1637/// contents — each element is copied out of the array's interned UTF-8.
1638pub(in crate::executor) fn read_string_array(slot: &KindedSlot) -> Option<Vec<String>> {
1639 use crate::executor::v2_handlers::v2_array_detect::{
1640 as_v2_typed_array, read_element, V2ElemType,
1641 };
1642 use shape_value::{HeapKind, NativeKind};
1643 if slot.kind != NativeKind::Ptr(HeapKind::TypedArray) {
1644 return None;
1645 }
1646 let view = as_v2_typed_array(slot.slot.raw(), slot.kind)?;
1647 if view.elem_type != V2ElemType::String {
1648 return None;
1649 }
1650 let mut out = Vec::with_capacity(view.len as usize);
1651 for i in 0..view.len {
1652 let (bits, kind) = read_element(&view, i)?;
1653 // `read_element` retains a fresh share on the element header per
1654 // its Wave-2-Agent-A2 contract (see
1655 // `executor/v2_handlers/v2_array_detect.rs:378-388`); wrap into
1656 // a `KindedSlot` so the share retires on drop after we've copied
1657 // the UTF-8 out via `as_str`.
1658 let elem_slot = KindedSlot::new(ValueSlot::from_raw(bits), kind);
1659 let s = elem_slot.as_str()?.to_string();
1660 // `elem_slot` drops at end of iteration — releases the share.
1661 let _ = elem_slot;
1662 out.push(s);
1663 }
1664 Some(out)
1665}
1666
1667/// Read a `Ptr(HeapKind::Content)` slot as an `Arc<ContentNode>`.
1668///
1669/// Returns `None` if the kind doesn't match. The returned Arc is a fresh
1670/// strong-count share (incremented from the slot's bits); the caller is
1671/// responsible for the share-accounting of the returned Arc.
1672pub(in crate::executor) fn read_content_arc(
1673 slot: &KindedSlot,
1674) -> Option<std::sync::Arc<shape_value::content::ContentNode>> {
1675 use shape_value::{HeapKind, NativeKind};
1676 if slot.kind != NativeKind::Ptr(HeapKind::Content) {
1677 return None;
1678 }
1679 let bits = slot.slot.raw();
1680 if bits == 0 {
1681 return None;
1682 }
1683 // SAFETY: by construction `Ptr(HeapKind::Content)` slot bits are
1684 // `Arc::into_raw(Arc<ContentNode>) as u64` (set by
1685 // `KindedSlot::from_content` and its producers — `ContentTextCtor`,
1686 // `ContentCodeCtor`, this module's W18.5 ctors, Display.display()
1687 // returns, etc.). The slot owns one strong-count share for the
1688 // dispatch duration. We borrow the inner Arc by reconstituting it,
1689 // cloning to get a fresh share, then `mem::forget`-ing the original
1690 // reconstitution so the slot's share remains intact.
1691 unsafe {
1692 let raw = bits as *const shape_value::content::ContentNode;
1693 let arc = std::sync::Arc::from_raw(raw);
1694 let cloned = arc.clone();
1695 std::mem::forget(arc);
1696 Some(cloned)
1697 }
1698}
1699
1700/// Build a `ContentTable` from a `Content.table(headers, rows)` arg list.
1701///
1702/// `args[0]` is the headers array (`Array<string>`), `args[1]` is the
1703/// rows array (`Array<Array<string>>`). Cell values render as
1704/// `ContentNode::plain` strings — string-typed MVP per supervisor D4
1705/// "string-typed-MVP follow-up surfaced for W18.4 spec-types swap".
1706fn build_table_from_headers_and_rows(
1707 args: &[KindedSlot],
1708) -> Result<shape_value::content::ContentTable, VMError> {
1709 if args.len() != 2 {
1710 return Err(VMError::RuntimeError(format!(
1711 "Content.table(headers, rows) requires exactly 2 arguments, \
1712 got {}",
1713 args.len()
1714 )));
1715 }
1716 let headers = read_string_array(&args[0]).ok_or_else(|| {
1717 VMError::RuntimeError(format!(
1718 "Content.table: headers argument must be Array<string>, got \
1719 kind {:?}",
1720 args[0].kind
1721 ))
1722 })?;
1723 // Rows is an Array<Array<string>>. The outer array carries
1724 // TypedArray-of-TypedArray pointers. Read each inner row via
1725 // `read_string_array`.
1726 use crate::executor::v2_handlers::v2_array_detect::{
1727 as_v2_typed_array, read_element,
1728 };
1729 use shape_value::{HeapKind, NativeKind};
1730 if args[1].kind != NativeKind::Ptr(HeapKind::TypedArray) {
1731 return Err(VMError::RuntimeError(format!(
1732 "Content.table: rows argument must be Array<Array<string>>, \
1733 got kind {:?}",
1734 args[1].kind
1735 )));
1736 }
1737 let outer_view = as_v2_typed_array(args[1].slot.raw(), args[1].kind)
1738 .ok_or_else(|| {
1739 VMError::RuntimeError(
1740 "Content.table: rows array has invalid v2 header".to_string(),
1741 )
1742 })?;
1743 let mut rows: Vec<Vec<shape_value::content::ContentNode>> =
1744 Vec::with_capacity(outer_view.len as usize);
1745 for i in 0..outer_view.len {
1746 let (bits, kind) = read_element(&outer_view, i).ok_or_else(|| {
1747 VMError::RuntimeError(format!(
1748 "Content.table: failed to read row {} from rows array",
1749 i
1750 ))
1751 })?;
1752 let row_slot = KindedSlot::new(ValueSlot::from_raw(bits), kind);
1753 let cells = read_string_array(&row_slot).ok_or_else(|| {
1754 VMError::RuntimeError(format!(
1755 "Content.table: row {} must be Array<string>, got kind {:?}",
1756 i, row_slot.kind
1757 ))
1758 })?;
1759 // Drop row_slot to release the share held by `read_element`.
1760 drop(row_slot);
1761 let row_nodes: Vec<shape_value::content::ContentNode> = cells
1762 .into_iter()
1763 .map(shape_value::content::ContentNode::plain)
1764 .collect();
1765 rows.push(row_nodes);
1766 }
1767 Ok(shape_value::content::ContentTable {
1768 headers,
1769 rows,
1770 border: shape_value::content::BorderStyle::default(),
1771 max_rows: None,
1772 column_types: None,
1773 total_rows: None,
1774 sortable: false,
1775 })
1776}
1777
1778/// Build a `Vec<(String, ContentNode)>` from a `Content.kv(keys, values)`
1779/// arg list — keys array is `Array<string>`, values array is the parallel
1780/// `Array<*>` whose elements coerce through `format_kinded`.
1781fn build_kv_pairs_from_keys_values(
1782 vm: &VirtualMachine,
1783 args: &[KindedSlot],
1784) -> Result<Vec<(String, shape_value::content::ContentNode)>, VMError> {
1785 if args.len() != 2 {
1786 return Err(VMError::RuntimeError(format!(
1787 "Content.kv(keys, values) requires exactly 2 arguments, got {}",
1788 args.len()
1789 )));
1790 }
1791 let keys = read_string_array(&args[0]).ok_or_else(|| {
1792 VMError::RuntimeError(format!(
1793 "Content.kv: keys argument must be Array<string>, got kind {:?}",
1794 args[0].kind
1795 ))
1796 })?;
1797 use crate::executor::v2_handlers::v2_array_detect::{
1798 as_v2_typed_array, read_element,
1799 };
1800 use shape_value::{HeapKind, NativeKind};
1801 if args[1].kind != NativeKind::Ptr(HeapKind::TypedArray) {
1802 return Err(VMError::RuntimeError(format!(
1803 "Content.kv: values argument must be an Array, got kind {:?}",
1804 args[1].kind
1805 )));
1806 }
1807 let view = as_v2_typed_array(args[1].slot.raw(), args[1].kind).ok_or_else(|| {
1808 VMError::RuntimeError("Content.kv: values array has invalid v2 header".to_string())
1809 })?;
1810 if (view.len as usize) != keys.len() {
1811 return Err(VMError::RuntimeError(format!(
1812 "Content.kv: keys.len() ({}) != values.len() ({})",
1813 keys.len(),
1814 view.len
1815 )));
1816 }
1817 let formatter = super::super::printing::ValueFormatter::new(&vm.program.type_schema_registry);
1818 let mut pairs = Vec::with_capacity(keys.len());
1819 for (i, key) in keys.into_iter().enumerate() {
1820 let (bits, kind) = read_element(&view, i as u32).ok_or_else(|| {
1821 VMError::RuntimeError(format!("Content.kv: failed to read value at index {}", i))
1822 })?;
1823 let val_slot = KindedSlot::new(ValueSlot::from_raw(bits), kind);
1824 let rendered = formatter.format_kinded(&val_slot);
1825 // val_slot drops here — releases the share.
1826 drop(val_slot);
1827 pairs.push((key, shape_value::content::ContentNode::plain(rendered)));
1828 }
1829 Ok(pairs)
1830}
1831
1832/// Read an `Array<content>` argument and collect each element as an
1833/// owned `ContentNode`. Each element of the array must have kind
1834/// `Ptr(HeapKind::Content)`; the function clones the inner ContentNode
1835/// out of the read-share Arc returned by `read_content_arc`.
1836fn collect_content_nodes_from_array_arg(
1837 args: &[KindedSlot],
1838) -> Result<Vec<shape_value::content::ContentNode>, VMError> {
1839 if args.len() != 1 {
1840 return Err(VMError::RuntimeError(format!(
1841 "Content.fragment(parts) requires exactly 1 argument \
1842 (Array<content>), got {}",
1843 args.len()
1844 )));
1845 }
1846 use crate::executor::v2_handlers::v2_array_detect::{
1847 as_v2_typed_array, read_element,
1848 };
1849 use shape_value::{HeapKind, NativeKind};
1850 if args[0].kind != NativeKind::Ptr(HeapKind::TypedArray) {
1851 return Err(VMError::RuntimeError(format!(
1852 "Content.fragment: parts argument must be Array<content>, got \
1853 kind {:?}",
1854 args[0].kind
1855 )));
1856 }
1857 let view = as_v2_typed_array(args[0].slot.raw(), args[0].kind).ok_or_else(|| {
1858 VMError::RuntimeError(
1859 "Content.fragment: parts array has invalid v2 header".to_string(),
1860 )
1861 })?;
1862 let mut parts = Vec::with_capacity(view.len as usize);
1863 for i in 0..view.len {
1864 let (bits, kind) = read_element(&view, i).ok_or_else(|| {
1865 VMError::RuntimeError(format!(
1866 "Content.fragment: failed to read element {}",
1867 i
1868 ))
1869 })?;
1870 let elem_slot = KindedSlot::new(ValueSlot::from_raw(bits), kind);
1871 let arc = read_content_arc(&elem_slot).ok_or_else(|| {
1872 VMError::RuntimeError(format!(
1873 "Content.fragment: element {} must be a content value, got \
1874 kind {:?}",
1875 i, elem_slot.kind
1876 ))
1877 })?;
1878 // Clone the underlying ContentNode out of the Arc (cheap — most
1879 // variants are themselves Arc-shaped vectors / structs that share
1880 // their interior storage on clone).
1881 parts.push((*arc).clone());
1882 drop(elem_slot);
1883 }
1884 Ok(parts)
1885}
1886
1887// W12-stdlib-intrinsic-collapse (Wave-2-Agent-G, 2026-05-14): the
1888// `intrinsic_sum_tests` module previously here exercised the deleted
1889// `BuiltinFunction::IntrinsicSum` opcode body. Equivalent coverage lives
1890// in the PHF method-dispatch handlers' own test surface
1891// (`typed_array_methods` / `array_aggregation` / `typed_int_array_methods`
1892// / `typed_number_array_methods`) — single discriminator per ADR-005 §1.
1893
1894// ===== R8 W4 W18.4: f-string styled-content lowering helpers =====
1895//
1896// (supervisor 2026-05-24 D1 + (a-modified) REVIVE-WITH-SHARED-MODULE)
1897//
1898// The compiler at `compiler/string_interpolation.rs` lowers each styled
1899// interpolation `{x:bold,red}` to a `FStringContentStyledText` builtin
1900// call whose i64-encoded payload is decoded here back into a
1901// `shape_value::content::Style`.
1902
1903const FSTRING_COLOR_NONE: i64 = -1;
1904const FSTRING_COLOR_NAMED: i64 = 0;
1905const FSTRING_COLOR_RGB: i64 = 1;
1906
1907const FSTRING_FLAG_BOLD: i64 = 1;
1908const FSTRING_FLAG_ITALIC: i64 = 2;
1909const FSTRING_FLAG_UNDERLINE: i64 = 4;
1910const FSTRING_FLAG_DIM: i64 = 8;
1911
1912fn decode_fstring_color(kind: i64, payload: i64) -> Result<Option<shape_value::content::Color>, VMError> {
1913 use shape_value::content::{Color, NamedColor};
1914 match kind {
1915 FSTRING_COLOR_NONE => Ok(None),
1916 FSTRING_COLOR_NAMED => {
1917 let named = match payload {
1918 0 => NamedColor::Red,
1919 1 => NamedColor::Green,
1920 2 => NamedColor::Blue,
1921 3 => NamedColor::Yellow,
1922 4 => NamedColor::Magenta,
1923 5 => NamedColor::Cyan,
1924 6 => NamedColor::White,
1925 7 => NamedColor::Default,
1926 other => {
1927 return Err(VMError::RuntimeError(format!(
1928 "decode_fstring_color: invalid named-color id {}",
1929 other
1930 )));
1931 }
1932 };
1933 Ok(Some(Color::Named(named)))
1934 }
1935 FSTRING_COLOR_RGB => {
1936 let r = ((payload >> 16) & 0xFF) as u8;
1937 let g = ((payload >> 8) & 0xFF) as u8;
1938 let b = (payload & 0xFF) as u8;
1939 Ok(Some(Color::Rgb(r, g, b)))
1940 }
1941 other => Err(VMError::RuntimeError(format!(
1942 "decode_fstring_color: invalid color kind {}",
1943 other
1944 ))),
1945 }
1946}
1947
1948fn decode_fstring_style(
1949 fg_kind: i64,
1950 fg_payload: i64,
1951 bg_kind: i64,
1952 bg_payload: i64,
1953 flags: i64,
1954) -> Result<shape_value::content::Style, VMError> {
1955 Ok(shape_value::content::Style {
1956 fg: decode_fstring_color(fg_kind, fg_payload)?,
1957 bg: decode_fstring_color(bg_kind, bg_payload)?,
1958 bold: (flags & FSTRING_FLAG_BOLD) != 0,
1959 italic: (flags & FSTRING_FLAG_ITALIC) != 0,
1960 underline: (flags & FSTRING_FLAG_UNDERLINE) != 0,
1961 dim: (flags & FSTRING_FLAG_DIM) != 0,
1962 })
1963}
1964
1965#[cfg(test)]
1966mod fstring_decode_tests {
1967 use super::*;
1968
1969 #[test]
1970 fn decode_color_none() {
1971 assert_eq!(decode_fstring_color(FSTRING_COLOR_NONE, 0).unwrap(), None);
1972 }
1973
1974 #[test]
1975 fn decode_color_named_red() {
1976 use shape_value::content::{Color, NamedColor};
1977 assert_eq!(
1978 decode_fstring_color(FSTRING_COLOR_NAMED, 0).unwrap(),
1979 Some(Color::Named(NamedColor::Red))
1980 );
1981 }
1982
1983 #[test]
1984 fn decode_color_rgb_roundtrip() {
1985 use shape_value::content::Color;
1986 // (10 << 16) | (20 << 8) | 30
1987 let payload = (10 << 16) | (20 << 8) | 30;
1988 assert_eq!(
1989 decode_fstring_color(FSTRING_COLOR_RGB, payload).unwrap(),
1990 Some(Color::Rgb(10, 20, 30))
1991 );
1992 }
1993
1994 #[test]
1995 fn decode_style_bold_red() {
1996 use shape_value::content::{Color, NamedColor};
1997 let style = decode_fstring_style(
1998 FSTRING_COLOR_NAMED,
1999 0, // Red
2000 FSTRING_COLOR_NONE,
2001 0,
2002 FSTRING_FLAG_BOLD,
2003 )
2004 .unwrap();
2005 assert!(style.bold);
2006 assert!(!style.italic);
2007 assert_eq!(style.fg, Some(Color::Named(NamedColor::Red)));
2008 assert_eq!(style.bg, None);
2009 }
2010
2011 #[test]
2012 fn decode_style_all_flags() {
2013 let style = decode_fstring_style(
2014 FSTRING_COLOR_NONE,
2015 0,
2016 FSTRING_COLOR_NONE,
2017 0,
2018 FSTRING_FLAG_BOLD
2019 | FSTRING_FLAG_ITALIC
2020 | FSTRING_FLAG_UNDERLINE
2021 | FSTRING_FLAG_DIM,
2022 )
2023 .unwrap();
2024 assert!(style.bold);
2025 assert!(style.italic);
2026 assert!(style.underline);
2027 assert!(style.dim);
2028 }
2029
2030 #[test]
2031 fn decode_color_invalid_kind() {
2032 assert!(decode_fstring_color(99, 0).is_err());
2033 }
2034
2035 #[test]
2036 fn decode_color_invalid_named_id() {
2037 assert!(decode_fstring_color(FSTRING_COLOR_NAMED, 99).is_err());
2038 }
2039}