shape_vm/executor/utils/extraction_helpers.rs
1//! Extraction helpers to reduce duplication across method handlers.
2//!
3//! Common patterns like "get array from first arg" and "coerce ValueWord to string"
4//! are centralized here so every call site is a single function call.
5
6use shape_value::{ArrayView, VMError, ValueWord};
7
8// ─── Arg-count and type-mismatch helpers ─────────────────────────────
9
10/// Check that `args` has at least `min` elements (receiver + arguments).
11///
12/// Returns `Ok(())` on success or a `VMError::RuntimeError` like
13/// `"Set.add requires an argument"` on failure.
14///
15/// `method_label` should be the human-readable method name used in the
16/// error message (e.g. `"Set.add"`).
17/// `hint` describes what is missing (e.g. `"an argument"`,
18/// `"a function argument"`, `"exactly 5 arguments"`).
19#[inline]
20pub(crate) fn check_arg_count(
21 args: &[ValueWord],
22 min: usize,
23 method_label: &str,
24 hint: &str,
25) -> Result<(), VMError> {
26 if args.len() < min {
27 Err(VMError::RuntimeError(format!(
28 "{} requires {}",
29 method_label, hint
30 )))
31 } else {
32 Ok(())
33 }
34}
35
36/// Produce a `VMError::RuntimeError` of the form
37/// `"<method> called on non-<expected_type> value"`.
38///
39/// This consolidates the ~77 occurrences of that pattern across the
40/// collection method handlers.
41#[inline]
42pub(crate) fn type_mismatch_error(method_name: &str, expected_type: &str) -> VMError {
43 VMError::RuntimeError(format!(
44 "{} called on non-{} value",
45 method_name, expected_type
46 ))
47}
48
49/// Extract a unified array view from the first element of `args`.
50/// Handles all array variants: generic Array, IntArray, FloatArray, BoolArray.
51#[inline]
52pub(crate) fn require_any_array_arg<'a>(args: &'a [ValueWord]) -> Result<ArrayView<'a>, VMError> {
53 args.first()
54 .ok_or(VMError::StackUnderflow)?
55 .as_any_array()
56 .ok_or_else(|| VMError::TypeError {
57 expected: "array",
58 got: "other",
59 })
60}
61
62/// Coerce a ValueWord value to a String representation.
63///
64/// Handles: string, f64, i64, bool, none → "null", fallback → Debug format.
65/// This replaces the 13-line pattern found in join_str, array_sort, etc.
66#[inline]
67pub(crate) fn nb_to_string_coerce(nb: &ValueWord) -> String {
68 if let Some(s) = nb.as_str() {
69 s.to_string()
70 } else if let Some(n) = nb.as_f64() {
71 n.to_string()
72 } else if let Some(i) = nb.as_i64() {
73 i.to_string()
74 } else if let Some(b) = nb.as_bool() {
75 b.to_string()
76 } else if nb.is_none() {
77 "null".to_string()
78 } else {
79 format!("{:?}", nb)
80 }
81}