ksl 0.1.30

KSL core library and interpreter
Documentation
//! # ksl::builtin::substring
//!
//! Built-in function `SubString`.

use crate::{Environment, eval::apply::eval_apply, is_number_eq, value::Value};

pub(crate) fn builtin(args: &[Value], env: Environment) -> Result<Value, std::sync::Arc<str>> {
    // Validate argument count
    let [val_expr, start_expr, end_expr] = if let [v, s, e] = args {
        [v, s, e]
    } else {
        return Err(std::sync::Arc::from(format!(
            "Error[ksl::builtin::SubString]: Expected 3 parameters, but {} were passed.",
            args.len()
        )));
    };

    // Evaluate arguments
    let val = eval_apply(val_expr, env.clone())?;
    let start_val = eval_apply(start_expr, env.clone())?;
    let end_val = eval_apply(end_expr, env)?;

    // Type checking and core logic
    match (val, start_val, end_val) {
        (Value::String(text), Value::Number(s), Value::Number(e)) => {
            let is_int = |n: f64| is_number_eq(n.round(), n);
            // Validate start index: must be integer >= 1
            if !is_int(s) || s < 1.0 {
                return Err(std::sync::Arc::from(format!(
                    concat!(
                        "Error[ksl::builtin::SubString]: ",
                        "Index must be an integer starting from 1 or -1, but got: `{}`."
                    ),
                    s
                )));
            }
            // Validate end index: must be integer
            if !is_int(e) || (e < 1.0 && !is_number_eq(e, -1.0)) {
                return Err(std::sync::Arc::from(format!(
                    concat!(
                        "Error[ksl::builtin::SubString]: ",
                        "Index must be an integer starting from 1 or -1, but got: `{}`."
                    ),
                    e
                )));
            }
            let skip_count = (s as usize).saturating_sub(1);
            let is_to_end = is_number_eq(e, -1.0);

            let mut chars = text.char_indices();
            let start_byte = chars.nth(skip_count).map_or(text.len(), |(idx, _)| idx);
            let end_byte = if is_to_end {
                text.len()
            } else {
                let take_count = (e as usize).saturating_sub(skip_count);
                if take_count <= 1 {
                    // if take_count is 0 or 1
                    if take_count == 0 {
                        start_byte
                    } else {
                        chars.next().map_or(text.len(), |(idx, _)| idx)
                    }
                } else {
                    // skip (take_count - 1) more to find the end byte
                    chars.nth(take_count - 2).map_or(text.len(), |_| {
                        chars.next().map_or(text.len(), |(idx, _)| idx)
                    })
                }
            };

            let final_end = if end_byte < start_byte {
                start_byte
            } else {
                end_byte
            };

            Ok(Value::String(std::sync::Arc::from(
                &text[start_byte..final_end],
            )))
        }
        // Error handling for invalid types
        (Value::String(_), Value::Number(_), e) => Err(std::sync::Arc::from(format!(
            "Error[ksl::builtin::SubString]: Expected a Number for end index, but got `{}`.",
            e
        ))),
        (Value::String(_), e, _) => Err(std::sync::Arc::from(format!(
            "Error[ksl::builtin::SubString]: Expected a Number for start index, but got `{}`.",
            e
        ))),
        (e, _, _) => Err(std::sync::Arc::from(format!(
            "Error[ksl::builtin::SubString]: Expected a String, but got `{}`.",
            e
        ))),
    }
}