ksl 0.1.30

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

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, idx_expr] = if let [v, i] = args {
        [v, i]
    } else {
        return Err(std::sync::Arc::from(format!(
            "Error[ksl::builtin::Index]: Expected 2 parameters, but {} were passed.",
            args.len()
        )));
    };

    let idx_val = eval_apply(idx_expr, env.clone())?;
    let target = eval_apply(val_expr, env)?;

    // Validate index type and value
    let n = if let Value::Number(n) = idx_val {
        if !is_number_eq(n.round(), n) || (n < 1.0 && !is_number_eq(n, -1.0)) {
            return Err(std::sync::Arc::from(format!(
                "Error[ksl::builtin::Index]: Index must be an integer starting from 1 or -1, but got {}.",
                n
            )));
        }
        n
    } else {
        return Err(std::sync::Arc::from(format!(
            "Error[ksl::builtin::Index]: Unexpected value: `{}`.",
            idx_val
        )));
    };

    let is_last = is_number_eq(n, -1.0);

    match target {
        Value::List(elements) => {
            if elements.is_empty() {
                return Err(std::sync::Arc::from(
                    "Error[ksl::builtin::Index]: Empty list.",
                ));
            }
            let idx = if is_last {
                elements.len() - 1
            } else {
                (n as usize) - 1
            };

            elements.get(idx).cloned().ok_or_else(|| {
                std::sync::Arc::from(format!(
                    "Error[ksl::builtin::Index]: Index `{}` out of bounds `{}`.",
                    n,
                    elements.len()
                ))
            })
        }
        Value::String(s) => {
            if s.is_empty() {
                return Err(std::sync::Arc::from(
                    "Error[ksl::builtin::Index]: Empty String.",
                ));
            }

            // Get character by traversing Unicode scalars
            let char_opt = if is_last {
                s.chars().last()
            } else {
                s.chars().nth((n as usize) - 1)
            };

            char_opt
                .map(|c| Value::String(std::sync::Arc::from(c.to_string())))
                .ok_or(std::sync::Arc::from(format!(
                    "Error[ksl::builtin::Index]: Index `{}` out of bounds `{}`.",
                    n,
                    s.chars().count()
                )))
        }
        e => Err(std::sync::Arc::from(format!(
            "Error[ksl::builtin::Index]: Unexpected value: `{}`.",
            e
        ))),
    }
}