use crate::executor::VirtualMachine;
use shape_runtime::context::ExecutionContext;
use shape_value::{KindedSlot, VMError};
use std::sync::Arc;
#[cold]
#[inline(never)]
fn ckpt5_string_array_surface(op: &'static str) -> VMError {
VMError::NotImplemented(format!(
"String.{op}: SURFACE — V3-S5 ckpt-5 consumer-cascade tier 3 \
surface. The deleted typed-array-data String `Arc<Buf<Arc<String>>>` \
result carrier DELETED at V3-S5 ckpt-1..ckpt-4 per W12-typed-\
array-data-deletion audit §3.5 + §3.6 + §B + ADR-006 §2.7.24 \
Q25.A SUPERSEDED. Rebuild lands at ckpt-6 STRICT close per the \
v2-raw `TypedArray<*const StringObj>` carrier shape. REFUSED ON \
SIGHT: TypedArrayData resurrection under any rename (Refusal #1).",
op = op,
))
}
#[inline]
fn receiver_str<'a>(args: &'a [KindedSlot]) -> Result<&'a str, VMError> {
args.first()
.and_then(|a| a.as_str())
.ok_or(VMError::TypeError {
expected: "string receiver",
got: "non-string kind",
})
}
#[inline]
fn int_arg(args: &[KindedSlot], idx: usize) -> Result<i64, VMError> {
args.get(idx)
.and_then(|a| a.as_i64())
.ok_or(VMError::TypeError {
expected: "int argument",
got: "non-int kind",
})
}
#[inline]
fn str_arg<'a>(args: &'a [KindedSlot], idx: usize) -> Result<&'a str, VMError> {
args.get(idx)
.and_then(|a| a.as_str())
.ok_or(VMError::TypeError {
expected: "string argument",
got: "non-string kind",
})
}
#[inline]
fn string_result(s: String) -> KindedSlot {
KindedSlot::from_string_arc(Arc::new(s))
}
pub fn v2_string_len(
_vm: &mut VirtualMachine,
args: &[KindedSlot],
_ctx: Option<&mut ExecutionContext>,
) -> Result<KindedSlot, VMError> {
let s = receiver_str(args)?;
Ok(KindedSlot::from_int(s.chars().count() as i64))
}
pub fn v2_string_to_upper(
_vm: &mut VirtualMachine,
args: &[KindedSlot],
_ctx: Option<&mut ExecutionContext>,
) -> Result<KindedSlot, VMError> {
let s = receiver_str(args)?;
Ok(string_result(s.to_uppercase()))
}
pub fn v2_string_to_lower(
_vm: &mut VirtualMachine,
args: &[KindedSlot],
_ctx: Option<&mut ExecutionContext>,
) -> Result<KindedSlot, VMError> {
let s = receiver_str(args)?;
Ok(string_result(s.to_lowercase()))
}
pub fn v2_string_trim(
_vm: &mut VirtualMachine,
args: &[KindedSlot],
_ctx: Option<&mut ExecutionContext>,
) -> Result<KindedSlot, VMError> {
let s = receiver_str(args)?;
Ok(string_result(s.trim().to_string()))
}
pub fn v2_string_trim_start(
_vm: &mut VirtualMachine,
args: &[KindedSlot],
_ctx: Option<&mut ExecutionContext>,
) -> Result<KindedSlot, VMError> {
let s = receiver_str(args)?;
Ok(string_result(s.trim_start().to_string()))
}
pub fn v2_string_trim_end(
_vm: &mut VirtualMachine,
args: &[KindedSlot],
_ctx: Option<&mut ExecutionContext>,
) -> Result<KindedSlot, VMError> {
let s = receiver_str(args)?;
Ok(string_result(s.trim_end().to_string()))
}
pub fn v2_string_to_string(
_vm: &mut VirtualMachine,
args: &[KindedSlot],
_ctx: Option<&mut ExecutionContext>,
) -> Result<KindedSlot, VMError> {
let s = receiver_str(args)?;
Ok(string_result(s.to_string()))
}
pub fn v2_string_starts_with(
_vm: &mut VirtualMachine,
args: &[KindedSlot],
_ctx: Option<&mut ExecutionContext>,
) -> Result<KindedSlot, VMError> {
let s = receiver_str(args)?;
let prefix = str_arg(args, 1)?;
Ok(KindedSlot::from_bool(s.starts_with(prefix)))
}
pub fn v2_string_ends_with(
_vm: &mut VirtualMachine,
args: &[KindedSlot],
_ctx: Option<&mut ExecutionContext>,
) -> Result<KindedSlot, VMError> {
let s = receiver_str(args)?;
let suffix = str_arg(args, 1)?;
Ok(KindedSlot::from_bool(s.ends_with(suffix)))
}
pub fn v2_string_contains(
_vm: &mut VirtualMachine,
args: &[KindedSlot],
_ctx: Option<&mut ExecutionContext>,
) -> Result<KindedSlot, VMError> {
let s = receiver_str(args)?;
let needle = str_arg(args, 1)?;
Ok(KindedSlot::from_bool(s.contains(needle)))
}
pub fn v2_string_index_of(
_vm: &mut VirtualMachine,
args: &[KindedSlot],
_ctx: Option<&mut ExecutionContext>,
) -> Result<KindedSlot, VMError> {
let s = receiver_str(args)?;
let needle = str_arg(args, 1)?;
let idx = match s.find(needle) {
Some(byte_idx) => {
s[..byte_idx].chars().count() as i64
}
None => -1,
};
Ok(KindedSlot::from_int(idx))
}
pub fn v2_string_repeat(
_vm: &mut VirtualMachine,
args: &[KindedSlot],
_ctx: Option<&mut ExecutionContext>,
) -> Result<KindedSlot, VMError> {
let s = receiver_str(args)?;
let n = int_arg(args, 1)?;
if n < 0 {
return Err(VMError::RuntimeError(
"string.repeat(n): n must be non-negative".to_string(),
));
}
Ok(string_result(s.repeat(n as usize)))
}
pub fn v2_string_char_at(
_vm: &mut VirtualMachine,
args: &[KindedSlot],
_ctx: Option<&mut ExecutionContext>,
) -> Result<KindedSlot, VMError> {
let s = receiver_str(args)?;
let i = int_arg(args, 1)?;
if i < 0 {
return Ok(KindedSlot::from_char('\0'));
}
let c = s.chars().nth(i as usize).unwrap_or('\0');
Ok(KindedSlot::from_char(c))
}
pub fn v2_string_reverse(
_vm: &mut VirtualMachine,
args: &[KindedSlot],
_ctx: Option<&mut ExecutionContext>,
) -> Result<KindedSlot, VMError> {
let s = receiver_str(args)?;
Ok(string_result(s.chars().rev().collect()))
}
pub fn v2_string_is_digit(
_vm: &mut VirtualMachine,
args: &[KindedSlot],
_ctx: Option<&mut ExecutionContext>,
) -> Result<KindedSlot, VMError> {
let s = receiver_str(args)?;
Ok(KindedSlot::from_bool(
!s.is_empty() && s.chars().all(|c| c.is_ascii_digit()),
))
}
pub fn v2_string_is_alpha(
_vm: &mut VirtualMachine,
args: &[KindedSlot],
_ctx: Option<&mut ExecutionContext>,
) -> Result<KindedSlot, VMError> {
let s = receiver_str(args)?;
Ok(KindedSlot::from_bool(
!s.is_empty() && s.chars().all(|c| c.is_alphabetic()),
))
}
pub fn v2_string_is_ascii(
_vm: &mut VirtualMachine,
args: &[KindedSlot],
_ctx: Option<&mut ExecutionContext>,
) -> Result<KindedSlot, VMError> {
let s = receiver_str(args)?;
Ok(KindedSlot::from_bool(s.is_ascii()))
}
pub fn v2_string_to_int(
_vm: &mut VirtualMachine,
args: &[KindedSlot],
_ctx: Option<&mut ExecutionContext>,
) -> Result<KindedSlot, VMError> {
let s = receiver_str(args)?;
let i: i64 = s.trim().parse().unwrap_or(0);
Ok(KindedSlot::from_int(i))
}
pub fn v2_string_to_number(
_vm: &mut VirtualMachine,
args: &[KindedSlot],
_ctx: Option<&mut ExecutionContext>,
) -> Result<KindedSlot, VMError> {
let s = receiver_str(args)?;
let n: f64 = s.trim().parse().unwrap_or(0.0);
Ok(KindedSlot::from_number(n))
}
pub fn v2_string_code_point_at(
_vm: &mut VirtualMachine,
args: &[KindedSlot],
_ctx: Option<&mut ExecutionContext>,
) -> Result<KindedSlot, VMError> {
let s = receiver_str(args)?;
let i = int_arg(args, 1)?;
if i < 0 {
return Ok(KindedSlot::from_int(-1));
}
let cp = s
.chars()
.nth(i as usize)
.map(|c| c as i64)
.unwrap_or(-1);
Ok(KindedSlot::from_int(cp))
}
pub fn v2_string_grapheme_len(
_vm: &mut VirtualMachine,
args: &[KindedSlot],
_ctx: Option<&mut ExecutionContext>,
) -> Result<KindedSlot, VMError> {
let s = receiver_str(args)?;
Ok(KindedSlot::from_int(s.chars().count() as i64))
}
pub fn v2_string_pad_start(
_vm: &mut VirtualMachine,
args: &[KindedSlot],
_ctx: Option<&mut ExecutionContext>,
) -> Result<KindedSlot, VMError> {
let s = receiver_str(args)?;
let target_len = int_arg(args, 1)?;
let pad = args
.get(2)
.and_then(|a| a.as_str())
.unwrap_or(" ");
Ok(string_result(pad_to(s, target_len, pad, true)))
}
pub fn v2_string_pad_end(
_vm: &mut VirtualMachine,
args: &[KindedSlot],
_ctx: Option<&mut ExecutionContext>,
) -> Result<KindedSlot, VMError> {
let s = receiver_str(args)?;
let target_len = int_arg(args, 1)?;
let pad = args
.get(2)
.and_then(|a| a.as_str())
.unwrap_or(" ");
Ok(string_result(pad_to(s, target_len, pad, false)))
}
pub fn v2_string_split(
_vm: &mut VirtualMachine,
args: &[KindedSlot],
_ctx: Option<&mut ExecutionContext>,
) -> Result<KindedSlot, VMError> {
let _ = receiver_str(args)?;
let _ = str_arg(args, 1)?;
let _: Option<Arc<String>> = None;
Err(ckpt5_string_array_surface("split"))
}
pub fn v2_string_replace(
_vm: &mut VirtualMachine,
args: &[KindedSlot],
_ctx: Option<&mut ExecutionContext>,
) -> Result<KindedSlot, VMError> {
let s = receiver_str(args)?;
let from = str_arg(args, 1)?;
let to = str_arg(args, 2)?;
Ok(string_result(s.replace(from, to)))
}
pub fn v2_string_substring(
_vm: &mut VirtualMachine,
args: &[KindedSlot],
_ctx: Option<&mut ExecutionContext>,
) -> Result<KindedSlot, VMError> {
let s = receiver_str(args)?;
let start = int_arg(args, 1)?;
let total = s.chars().count() as i64;
let end = match args.get(2).and_then(|a| a.as_i64()) {
Some(e) => e,
None => total,
};
let s_idx = start.clamp(0, total) as usize;
let e_idx = end.clamp(0, total) as usize;
if s_idx >= e_idx {
return Ok(string_result(String::new()));
}
let result: String = s.chars().skip(s_idx).take(e_idx - s_idx).collect();
Ok(string_result(result))
}
pub fn v2_string_join(
_vm: &mut VirtualMachine,
args: &[KindedSlot],
_ctx: Option<&mut ExecutionContext>,
) -> Result<KindedSlot, VMError> {
let _ = str_arg(args, 1)?;
let _f = format_f64;
Err(ckpt5_string_array_surface("join"))
}
pub fn v2_string_graphemes(
_vm: &mut VirtualMachine,
args: &[KindedSlot],
_ctx: Option<&mut ExecutionContext>,
) -> Result<KindedSlot, VMError> {
let _ = receiver_str(args)?;
let _: Option<Arc<String>> = None;
Err(ckpt5_string_array_surface("graphemes"))
}
pub fn v2_string_normalize(
_vm: &mut VirtualMachine,
args: &[KindedSlot],
_ctx: Option<&mut ExecutionContext>,
) -> Result<KindedSlot, VMError> {
let s = receiver_str(args)?;
Ok(string_result(s.to_string()))
}
pub fn v2_string_iter(
vm: &mut VirtualMachine,
args: &[KindedSlot],
ctx: Option<&mut ExecutionContext>,
) -> Result<KindedSlot, VMError> {
crate::executor::objects::iterator_methods::handle_string_iter(vm, args, ctx)
}
fn pad_to(s: &str, target_len: i64, pad: &str, at_start: bool) -> String {
if target_len <= 0 || pad.is_empty() {
return s.to_string();
}
let s_chars = s.chars().count();
let target = target_len as usize;
if s_chars >= target {
return s.to_string();
}
let need = target - s_chars;
let pad_chars: Vec<char> = pad.chars().collect();
let pad_len = pad_chars.len();
if pad_len == 0 {
return s.to_string();
}
let mut prefix = String::with_capacity(need);
for i in 0..need {
prefix.push(pad_chars[i % pad_len]);
}
if at_start {
format!("{}{}", prefix, s)
} else {
format!("{}{}", s, prefix)
}
}
fn format_f64(v: &f64) -> String {
if v.is_finite() && *v == v.trunc() && v.abs() < 1e15 {
format!("{}", *v as i64)
} else {
format!("{}", v)
}
}