use crate::executor::v2_handlers::v2_array_detect::{
as_v2_typed_array, cmp_element_natural, permute_array, read_element, V2TypedArrayView,
};
use crate::executor::VirtualMachine;
use shape_runtime::context::ExecutionContext;
use shape_value::heap_value::HeapKind;
use shape_value::HeapValue;
use shape_value::{KindedSlot, NativeKind, ValueSlot, VMError};
use std::cmp::Ordering;
use std::sync::Arc;
#[inline]
fn extract_view(op: &'static str, slot: &KindedSlot) -> Result<V2TypedArrayView, VMError> {
if slot.kind != NativeKind::Ptr(HeapKind::TypedArray) {
return Err(VMError::RuntimeError(format!(
"Array.{op}: expected v2 TypedArray receiver, got kind {:?}",
slot.kind
)));
}
as_v2_typed_array(slot.slot.raw(), slot.kind).ok_or_else(|| {
VMError::RuntimeError(format!(
"Array.{op}: receiver bits failed v2 TypedArray detection (kind {:?})",
slot.kind
))
})
}
#[inline]
fn new_array_slot(ptr: *mut u8) -> KindedSlot {
KindedSlot::new(
ValueSlot::from_u64(ptr as usize as u64),
NativeKind::Ptr(HeapKind::TypedArray),
)
}
#[inline]
fn require_closure(op: &str, slot: &KindedSlot) -> Result<(), VMError> {
match slot.kind {
NativeKind::Ptr(HeapKind::Closure) | NativeKind::UInt64 => Ok(()),
other => Err(VMError::RuntimeError(format!(
"{op}: key function must be a closure or function ref, got kind {:?}",
other
))),
}
}
#[inline]
fn bump_closure_share(slot: &KindedSlot) {
if let NativeKind::Ptr(HeapKind::Closure) = slot.kind {
let bits = slot.slot.raw();
if bits != 0 {
unsafe {
Arc::increment_strong_count(bits as *const HeapValue);
}
}
}
}
#[derive(Copy, Clone, Eq, PartialEq, Debug)]
enum SortDirection {
Ascending,
Descending,
}
fn parse_direction(args: &[KindedSlot], op: &str) -> Result<SortDirection, VMError> {
if args.len() < 3 {
return Ok(SortDirection::Ascending);
}
let slot = &args[2];
match slot.kind {
NativeKind::String | NativeKind::StringV2 => {
let s = slot.as_str().unwrap_or("");
match s {
"asc" | "ascending" => Ok(SortDirection::Ascending),
"desc" | "descending" => Ok(SortDirection::Descending),
other => Err(VMError::RuntimeError(format!(
"{op}: direction must be \"asc\" or \"desc\", got {:?}",
other
))),
}
}
other => Err(VMError::RuntimeError(format!(
"{op}: direction must be a string (\"asc\" or \"desc\"), got kind {:?}",
other
))),
}
}
fn cmp_key_kinded(
a: &KindedSlot,
b: &KindedSlot,
op: &str,
) -> Result<Ordering, VMError> {
if a.kind != b.kind {
return Err(VMError::RuntimeError(format!(
"{op}: key function produced heterogeneous result kinds {:?} vs {:?} \
(CLAUDE.md \"No runtime coercion\" — keys must be monomorphic)",
a.kind, b.kind
)));
}
Ok(match a.kind {
NativeKind::Int8
| NativeKind::Int16
| NativeKind::Int32
| NativeKind::Int64
| NativeKind::IntSize => (a.slot.raw() as i64).cmp(&(b.slot.raw() as i64)),
NativeKind::UInt8
| NativeKind::UInt16
| NativeKind::UInt32
| NativeKind::UInt64
| NativeKind::UIntSize => a.slot.raw().cmp(&b.slot.raw()),
NativeKind::Float64 => f64::from_bits(a.slot.raw())
.total_cmp(&f64::from_bits(b.slot.raw())),
NativeKind::Float32 => f32::from_bits(a.slot.raw() as u32)
.total_cmp(&f32::from_bits(b.slot.raw() as u32)),
NativeKind::Bool => (a.slot.raw() != 0).cmp(&(b.slot.raw() != 0)),
NativeKind::Char => (a.slot.raw() as u32).cmp(&(b.slot.raw() as u32)),
NativeKind::String | NativeKind::StringV2 => {
let sa = a.as_str().unwrap_or("");
let sb = b.as_str().unwrap_or("");
sa.cmp(sb)
}
other => {
return Err(VMError::NotImplemented(format!(
"{op}: comparison of key kind {:?} — SURFACE: only scalar / Bool / Char / \
String key kinds dispatched in J.5f v0.3 scope per supervisor D4. \
Heap-aggregate keys (DecimalV2, TypedObject, ...) need an ADR-006 \
§2.7.6 / Q8 per-kind comparator table — v0.4 territory.",
other
)));
}
})
}
fn interpret_comparator_result(result: &KindedSlot, op: &str) -> Result<Ordering, VMError> {
match result.kind {
NativeKind::Int8
| NativeKind::Int16
| NativeKind::Int32
| NativeKind::Int64
| NativeKind::IntSize => {
let v = result.slot.raw() as i64;
Ok(v.cmp(&0))
}
NativeKind::UInt8
| NativeKind::UInt16
| NativeKind::UInt32
| NativeKind::UInt64
| NativeKind::UIntSize => {
let v = result.slot.raw();
Ok(v.cmp(&0))
}
NativeKind::Float64 => {
let v = f64::from_bits(result.slot.raw());
Ok(v.total_cmp(&0.0))
}
NativeKind::Float32 => {
let v = f32::from_bits(result.slot.raw() as u32);
Ok(v.total_cmp(&0.0))
}
other => Err(VMError::RuntimeError(format!(
"{op}: comparator must return an integer sign (negative → first arg sorts \
before second, zero → equal, positive → first arg sorts after second); \
got kind {:?}. Bool-default refused per ADR-006 §2.7.14 + supervisor D3.",
other
))),
}
}
fn sort_by_comparator(
vm: &mut VirtualMachine,
view: &V2TypedArrayView,
closure: &KindedSlot,
mut ctx: Option<&mut ExecutionContext>,
op: &'static str,
) -> Result<Vec<u32>, VMError> {
let len = view.len;
let mut indices: Vec<u32> = (0..len).collect();
if len < 2 {
return Ok(indices);
}
let mut elems: Vec<KindedSlot> = Vec::with_capacity(len as usize);
for i in 0..len {
let (bits, kind) = read_element(view, i).ok_or_else(|| {
VMError::RuntimeError(format!(
"Array.{op}: read_element({i}) returned None for element kind {:?}",
view.elem_type
))
})?;
elems.push(KindedSlot::new(ValueSlot::from_raw(bits), kind));
}
let mut tmp: Vec<u32> = vec![0; len as usize];
let mut cmp_err: Option<VMError> = None;
stable_merge_sort_with_comparator(
&mut indices,
&mut tmp,
&elems,
|a_slot, b_slot| {
if cmp_err.is_some() {
return Ordering::Equal;
}
bump_closure_share(closure);
let result = match vm.call_value_immediate_nb(
closure,
&[a_slot.clone(), b_slot.clone()],
ctx.as_deref_mut(),
) {
Ok(r) => r,
Err(e) => {
cmp_err = Some(e);
return Ordering::Equal;
}
};
match interpret_comparator_result(&result, op) {
Ok(o) => o,
Err(e) => {
cmp_err = Some(e);
Ordering::Equal
}
}
},
);
if let Some(e) = cmp_err {
return Err(e);
}
Ok(indices)
}
fn stable_merge_sort_with_comparator<F>(
indices: &mut [u32],
tmp: &mut [u32],
elems: &[KindedSlot],
mut cmp: F,
) where
F: FnMut(&KindedSlot, &KindedSlot) -> Ordering,
{
let n = indices.len();
if n < 2 {
return;
}
let mut width = 1usize;
while width < n {
let mut i = 0;
while i < n {
let left = i;
let mid = (i + width).min(n);
let right = (i + 2 * width).min(n);
merge_with_comparator(indices, tmp, elems, left, mid, right, &mut cmp);
i += 2 * width;
}
indices[..n].copy_from_slice(&tmp[..n]);
width *= 2;
}
}
fn merge_with_comparator<F>(
indices: &[u32],
tmp: &mut [u32],
elems: &[KindedSlot],
left: usize,
mid: usize,
right: usize,
cmp: &mut F,
) where
F: FnMut(&KindedSlot, &KindedSlot) -> Ordering,
{
let mut i = left;
let mut j = mid;
let mut k = left;
while i < mid && j < right {
let a = &elems[indices[i] as usize];
let b = &elems[indices[j] as usize];
match cmp(a, b) {
Ordering::Less | Ordering::Equal => {
tmp[k] = indices[i];
i += 1;
}
Ordering::Greater => {
tmp[k] = indices[j];
j += 1;
}
}
k += 1;
}
while i < mid {
tmp[k] = indices[i];
i += 1;
k += 1;
}
while j < right {
tmp[k] = indices[j];
j += 1;
k += 1;
}
}
fn sort_by_natural(view: &V2TypedArrayView, op: &'static str) -> Result<Vec<u32>, VMError> {
let len = view.len;
let mut indices: Vec<u32> = (0..len).collect();
if len < 2 {
return Ok(indices);
}
let mut bits_vec: Vec<u64> = Vec::with_capacity(len as usize);
for i in 0..len {
let (bits, _kind) = read_element(view, i).ok_or_else(|| {
VMError::RuntimeError(format!(
"Array.{op}: read_element({i}) returned None for element kind {:?}",
view.elem_type
))
})?;
bits_vec.push(bits);
}
if matches!(
view.elem_type,
crate::executor::v2_handlers::v2_array_detect::V2ElemType::TypedObject
) {
for &bits in &bits_vec {
if bits != 0 {
unsafe {
use shape_value::v2::heap_element::HeapElement;
use shape_value::heap_value::TypedObjectStorage;
<TypedObjectStorage as HeapElement>::release_elem(
bits as *const TypedObjectStorage,
);
}
}
}
return Err(VMError::NotImplemented(format!(
"Array.{op}: natural-ordering sort over Array<TypedObject> is not \
supported in v0.3 (supervisor D4: Bool-default ordering forbidden per \
ADR-006 §2.7.14; canonical Ord trait + per-field projection is v0.4 \
territory). Use `.orderBy(|x| x.<field>)` for an explicit key, or \
pass a `sort(|a, b| ...)` comparator."
)));
}
let mut cmp_err: Option<VMError> = None;
let mut tmp: Vec<u32> = vec![0; len as usize];
stable_merge_sort_with_indices(
&mut indices,
&mut tmp,
|ia, ib| {
if cmp_err.is_some() {
return Ordering::Equal;
}
match cmp_element_natural(view, bits_vec[ia as usize], bits_vec[ib as usize]) {
Some(o) => o,
None => {
cmp_err = Some(VMError::RuntimeError(format!(
"Array.{op}: natural-ordering comparison failed for element kind {:?} \
(no canonical Ord at v0.3 — supervisor D4 / ADR-006 §2.7.14)",
view.elem_type
)));
Ordering::Equal
}
}
},
);
drop_read_shares(view, &bits_vec);
if let Some(e) = cmp_err {
return Err(e);
}
Ok(indices)
}
fn drop_read_shares(view: &V2TypedArrayView, bits_vec: &[u64]) {
use crate::executor::v2_handlers::v2_array_detect::V2ElemType;
use shape_value::v2::heap_element::HeapElement;
match view.elem_type {
V2ElemType::String => {
for &bits in bits_vec {
if bits != 0 {
unsafe {
<shape_value::v2::string_obj::StringObj as HeapElement>::release_elem(
bits as *const shape_value::v2::string_obj::StringObj,
);
}
}
}
}
V2ElemType::Decimal => {
for &bits in bits_vec {
if bits != 0 {
unsafe {
<shape_value::v2::decimal_obj::DecimalObj as HeapElement>::release_elem(
bits as *const shape_value::v2::decimal_obj::DecimalObj,
);
}
}
}
}
V2ElemType::TypedObject => {
for &bits in bits_vec {
if bits != 0 {
unsafe {
<shape_value::heap_value::TypedObjectStorage as HeapElement>::release_elem(
bits as *const shape_value::heap_value::TypedObjectStorage,
);
}
}
}
}
_ => {}
}
}
fn stable_merge_sort_with_indices<F>(
indices: &mut [u32],
tmp: &mut [u32],
mut cmp: F,
) where
F: FnMut(u32, u32) -> Ordering,
{
let n = indices.len();
if n < 2 {
return;
}
let mut width = 1usize;
while width < n {
let mut i = 0;
while i < n {
let left = i;
let mid = (i + width).min(n);
let right = (i + 2 * width).min(n);
merge_with_indices(indices, tmp, left, mid, right, &mut cmp);
i += 2 * width;
}
indices[..n].copy_from_slice(&tmp[..n]);
width *= 2;
}
}
fn merge_with_indices<F>(
indices: &[u32],
tmp: &mut [u32],
left: usize,
mid: usize,
right: usize,
cmp: &mut F,
) where
F: FnMut(u32, u32) -> Ordering,
{
let mut i = left;
let mut j = mid;
let mut k = left;
while i < mid && j < right {
match cmp(indices[i], indices[j]) {
Ordering::Less | Ordering::Equal => {
tmp[k] = indices[i];
i += 1;
}
Ordering::Greater => {
tmp[k] = indices[j];
j += 1;
}
}
k += 1;
}
while i < mid {
tmp[k] = indices[i];
i += 1;
k += 1;
}
while j < right {
tmp[k] = indices[j];
j += 1;
k += 1;
}
}
fn sort_by_key_fn(
vm: &mut VirtualMachine,
view: &V2TypedArrayView,
closure: &KindedSlot,
direction: SortDirection,
mut ctx: Option<&mut ExecutionContext>,
op: &'static str,
) -> Result<Vec<u32>, VMError> {
let len = view.len;
let mut indices: Vec<u32> = (0..len).collect();
if len < 2 {
return Ok(indices);
}
let mut keys: Vec<KindedSlot> = Vec::with_capacity(len as usize);
for i in 0..len {
let (bits, kind) = read_element(view, i).ok_or_else(|| {
VMError::RuntimeError(format!(
"Array.{op}: read_element({i}) returned None for element kind {:?}",
view.elem_type
))
})?;
let elem = KindedSlot::new(ValueSlot::from_raw(bits), kind);
bump_closure_share(closure);
let key = vm.call_value_immediate_nb(closure, &[elem], ctx.as_deref_mut())?;
keys.push(key);
}
let mut cmp_err: Option<VMError> = None;
let mut tmp: Vec<u32> = vec![0; len as usize];
stable_merge_sort_with_indices(
&mut indices,
&mut tmp,
|ia, ib| {
if cmp_err.is_some() {
return Ordering::Equal;
}
let order = match cmp_key_kinded(&keys[ia as usize], &keys[ib as usize], op) {
Ok(o) => o,
Err(e) => {
cmp_err = Some(e);
return Ordering::Equal;
}
};
match direction {
SortDirection::Ascending => order,
SortDirection::Descending => order.reverse(),
}
},
);
if let Some(e) = cmp_err {
return Err(e);
}
Ok(indices)
}
pub(crate) fn handle_sort_v2(
vm: &mut VirtualMachine,
args: &[KindedSlot],
ctx: Option<&mut ExecutionContext>,
) -> Result<KindedSlot, VMError> {
if args.is_empty() {
return Err(VMError::RuntimeError(
"sort: missing receiver".to_string(),
));
}
let view = extract_view("sort", &args[0])?;
let indices = if args.len() >= 2 {
require_closure("sort", &args[1])?;
let closure = &args[1];
sort_by_comparator(vm, &view, closure, ctx, "sort")?
} else {
sort_by_natural(&view, "sort")?
};
let out_ptr = permute_array(&view, &indices);
Ok(new_array_slot(out_ptr))
}
pub(crate) fn handle_order_by_v2(
vm: &mut VirtualMachine,
args: &[KindedSlot],
ctx: Option<&mut ExecutionContext>,
) -> Result<KindedSlot, VMError> {
if args.len() < 2 {
return Err(VMError::RuntimeError(
"orderBy: expected (array, key_fn, direction?)".to_string(),
));
}
require_closure("orderBy", &args[1])?;
let view = extract_view("orderBy", &args[0])?;
let closure = &args[1];
let direction = parse_direction(args, "orderBy")?;
let indices = sort_by_key_fn(vm, &view, closure, direction, ctx, "orderBy")?;
let out_ptr = permute_array(&view, &indices);
Ok(new_array_slot(out_ptr))
}
pub(crate) fn handle_then_by_v2(
vm: &mut VirtualMachine,
args: &[KindedSlot],
ctx: Option<&mut ExecutionContext>,
) -> Result<KindedSlot, VMError> {
if args.len() < 2 {
return Err(VMError::RuntimeError(
"thenBy: expected (array, key_fn, direction?)".to_string(),
));
}
require_closure("thenBy", &args[1])?;
let view = extract_view("thenBy", &args[0])?;
let closure = &args[1];
let direction = parse_direction(args, "thenBy")?;
let indices = sort_by_key_fn(vm, &view, closure, direction, ctx, "thenBy")?;
let out_ptr = permute_array(&view, &indices);
Ok(new_array_slot(out_ptr))
}
pub(crate) fn handle_join_str_v2(
_vm: &mut VirtualMachine,
args: &[KindedSlot],
_ctx: Option<&mut ExecutionContext>,
) -> Result<KindedSlot, VMError> {
if args.len() != 2 {
return Err(VMError::RuntimeError(
"joinStr() requires 2 arguments (array, separator)".to_string(),
));
}
if !matches!(args[1].kind, NativeKind::String | NativeKind::StringV2) {
return Err(VMError::RuntimeError(format!(
"joinStr(): separator must be a string, got {:?}",
args[1].kind
)));
}
Err(VMError::NotImplemented(
"joinStr: SURFACE — per-V2ElemType element stringification primitive \
not yet landed (separate ckpt-3 sub-cluster, not J.5f scope per \
supervisor D4 2026-05-24). Use `.map(|x| x.toString()).reduce(\"\", \
|acc, s| acc + sep + s)` as a pure-Shape workaround until the \
`v2_array_detect::element_to_string` primitive lands."
.to_string(),
))
}