Skip to main content

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}