robinpath 0.2.0

RobinPath - A lightweight, fast scripting language interpreter for automation and data processing
Documentation
use crate::executor::Environment;
use crate::value::Value;

pub fn register(env: &mut Environment) {
    env.register_builtin("string.length", |args, _| {
        Ok(Value::Number(
            args.first()
                .and_then(|v| v.as_str())
                .map_or(0.0, |s| s.len() as f64),
        ))
    });

    env.register_builtin("string.substring", |args, _| {
        let s = args.first().and_then(|v| v.as_str()).unwrap_or("");
        let start = args.get(1).and_then(|v| v.as_number()).unwrap_or(0.0) as usize;
        let end = args
            .get(2)
            .and_then(|v| v.as_number())
            .map(|n| n as usize)
            .unwrap_or(s.len());
        let start = start.min(s.len());
        let end = end.min(s.len());
        Ok(Value::String(s[start..end].to_string()))
    });

    env.register_builtin("string.toUpperCase", |args, _| {
        Ok(Value::String(
            args.first()
                .and_then(|v| v.as_str())
                .unwrap_or("")
                .to_uppercase(),
        ))
    });

    env.register_builtin("string.toLowerCase", |args, _| {
        Ok(Value::String(
            args.first()
                .and_then(|v| v.as_str())
                .unwrap_or("")
                .to_lowercase(),
        ))
    });

    env.register_builtin("string.trim", |args, _| {
        Ok(Value::String(
            args.first()
                .and_then(|v| v.as_str())
                .unwrap_or("")
                .trim()
                .to_string(),
        ))
    });

    env.register_builtin("string.replace", |args, _| {
        let s = args.first().and_then(|v| v.as_str()).unwrap_or("");
        let from = args.get(1).and_then(|v| v.as_str()).unwrap_or("");
        let to = args.get(2).and_then(|v| v.as_str()).unwrap_or("");
        Ok(Value::String(s.replacen(from, to, 1)))
    });

    env.register_builtin("string.replaceAll", |args, _| {
        let s = args.first().and_then(|v| v.as_str()).unwrap_or("");
        let from = args.get(1).and_then(|v| v.as_str()).unwrap_or("");
        let to = args.get(2).and_then(|v| v.as_str()).unwrap_or("");
        Ok(Value::String(s.replace(from, to)))
    });

    env.register_builtin("string.split", |args, _| {
        let s = args.first().and_then(|v| v.as_str()).unwrap_or("");
        let delim = args.get(1).and_then(|v| v.as_str()).unwrap_or(",");
        let parts: Vec<Value> = s.split(delim).map(|p| Value::String(p.to_string())).collect();
        Ok(Value::Array(parts))
    });

    env.register_builtin("string.join", |args, _| {
        let arr = args.first().and_then(|v| v.as_array());
        let delim = args.get(1).and_then(|v| v.as_str()).unwrap_or(",");
        match arr {
            Some(items) => {
                let strs: Vec<String> = items.iter().map(|v| v.to_display_string()).collect();
                Ok(Value::String(strs.join(delim)))
            }
            None => Ok(Value::String(String::new())),
        }
    });

    env.register_builtin("string.contains", |args, _| {
        let s = args.first().and_then(|v| v.as_str()).unwrap_or("");
        let search = args.get(1).and_then(|v| v.as_str()).unwrap_or("");
        Ok(Value::Bool(s.contains(search)))
    });

    env.register_builtin("string.indexOf", |args, _| {
        let s = args.first().and_then(|v| v.as_str()).unwrap_or("");
        let search = args.get(1).and_then(|v| v.as_str()).unwrap_or("");
        Ok(Value::Number(
            s.find(search).map_or(-1.0, |i| i as f64),
        ))
    });

    env.register_builtin("string.lastIndexOf", |args, _| {
        let s = args.first().and_then(|v| v.as_str()).unwrap_or("");
        let search = args.get(1).and_then(|v| v.as_str()).unwrap_or("");
        Ok(Value::Number(
            s.rfind(search).map_or(-1.0, |i| i as f64),
        ))
    });

    env.register_builtin("string.startsWith", |args, _| {
        let s = args.first().and_then(|v| v.as_str()).unwrap_or("");
        let prefix = args.get(1).and_then(|v| v.as_str()).unwrap_or("");
        Ok(Value::Bool(s.starts_with(prefix)))
    });

    env.register_builtin("string.endsWith", |args, _| {
        let s = args.first().and_then(|v| v.as_str()).unwrap_or("");
        let suffix = args.get(1).and_then(|v| v.as_str()).unwrap_or("");
        Ok(Value::Bool(s.ends_with(suffix)))
    });

    env.register_builtin("string.padStart", |args, _| {
        let s = args.first().and_then(|v| v.as_str()).unwrap_or("");
        let len = args.get(1).and_then(|v| v.as_number()).unwrap_or(0.0) as usize;
        let pad = args.get(2).and_then(|v| v.as_str()).unwrap_or(" ");
        if s.len() >= len {
            Ok(Value::String(s.to_string()))
        } else {
            let padding: String = pad.chars().cycle().take(len - s.len()).collect();
            Ok(Value::String(format!("{}{}", padding, s)))
        }
    });

    env.register_builtin("string.padEnd", |args, _| {
        let s = args.first().and_then(|v| v.as_str()).unwrap_or("");
        let len = args.get(1).and_then(|v| v.as_number()).unwrap_or(0.0) as usize;
        let pad = args.get(2).and_then(|v| v.as_str()).unwrap_or(" ");
        if s.len() >= len {
            Ok(Value::String(s.to_string()))
        } else {
            let padding: String = pad.chars().cycle().take(len - s.len()).collect();
            Ok(Value::String(format!("{}{}", s, padding)))
        }
    });

    env.register_builtin("string.repeat", |args, _| {
        let s = args.first().and_then(|v| v.as_str()).unwrap_or("");
        let count = args.get(1).and_then(|v| v.as_number()).unwrap_or(0.0) as usize;
        Ok(Value::String(s.repeat(count)))
    });

    env.register_builtin("string.charAt", |args, _| {
        let s = args.first().and_then(|v| v.as_str()).unwrap_or("");
        let idx = args.get(1).and_then(|v| v.as_number()).unwrap_or(0.0) as usize;
        Ok(Value::String(
            s.chars().nth(idx).map_or(String::new(), |c| c.to_string()),
        ))
    });

    // Also register as global "length" for common usage
    env.register_builtin("length", |args, _| {
        match args.first() {
            Some(Value::String(s)) => Ok(Value::Number(s.len() as f64)),
            Some(Value::Array(a)) => Ok(Value::Number(a.len() as f64)),
            Some(Value::Object(o)) => Ok(Value::Number(o.len() as f64)),
            _ => Ok(Value::Number(0.0)),
        }
    });

    env.register_builtin("upper", |args, _| {
        Ok(Value::String(
            args.first().map_or(String::new(), |v| v.to_display_string().to_uppercase()),
        ))
    });

    env.register_builtin("lower", |args, _| {
        Ok(Value::String(
            args.first().map_or(String::new(), |v| v.to_display_string().to_lowercase()),
        ))
    });

    env.register_builtin("trim", |args, _| {
        Ok(Value::String(
            args.first().map_or(String::new(), |v| v.to_display_string().trim().to_string()),
        ))
    });

    env.register_builtin("replace", |args, _| {
        let s = args.first().map_or(String::new(), |v| v.to_display_string());
        let from = args.get(1).map_or(String::new(), |v| v.to_display_string());
        let to = args.get(2).map_or(String::new(), |v| v.to_display_string());
        Ok(Value::String(s.replacen(&from, &to, 1)))
    });

    env.register_builtin("split", |args, _| {
        let s = args.first().map_or(String::new(), |v| v.to_display_string());
        let delim = args.get(1).and_then(|v| v.as_str()).unwrap_or(",");
        let parts: Vec<Value> = s.split(delim).map(|p| Value::String(p.to_string())).collect();
        Ok(Value::Array(parts))
    });

    env.register_builtin("join", |args, _| {
        let arr = args.first().and_then(|v| v.as_array());
        let delim = args.get(1).and_then(|v| v.as_str()).unwrap_or(",");
        match arr {
            Some(items) => {
                let strs: Vec<String> = items.iter().map(|v| v.to_display_string()).collect();
                Ok(Value::String(strs.join(delim)))
            }
            None => Ok(Value::String(String::new())),
        }
    });

    env.register_builtin("contains", |args, _| {
        match args.first() {
            Some(Value::String(s)) => {
                let search = args.get(1).and_then(|v| v.as_str()).unwrap_or("");
                Ok(Value::Bool(s.contains(search)))
            }
            Some(Value::Array(arr)) => {
                if let Some(search) = args.get(1) {
                    Ok(Value::Bool(arr.iter().any(|v| v.deep_eq(search))))
                } else {
                    Ok(Value::Bool(false))
                }
            }
            _ => Ok(Value::Bool(false)),
        }
    });

    env.register_builtin("indexOf", |args, _| {
        match args.first() {
            Some(Value::String(s)) => {
                let search = args.get(1).and_then(|v| v.as_str()).unwrap_or("");
                Ok(Value::Number(s.find(search).map_or(-1.0, |i| i as f64)))
            }
            Some(Value::Array(arr)) => {
                if let Some(search) = args.get(1) {
                    Ok(Value::Number(
                        arr.iter()
                            .position(|v| v.deep_eq(search))
                            .map_or(-1.0, |i| i as f64),
                    ))
                } else {
                    Ok(Value::Number(-1.0))
                }
            }
            _ => Ok(Value::Number(-1.0)),
        }
    });

    env.register_builtin("substring", |args, _| {
        let s = args.first().and_then(|v| v.as_str()).unwrap_or("");
        let start = args.get(1).and_then(|v| v.as_number()).unwrap_or(0.0) as usize;
        let end = args
            .get(2)
            .and_then(|v| v.as_number())
            .map(|n| n as usize)
            .unwrap_or(s.len());
        let start = start.min(s.len());
        let end = end.min(s.len());
        Ok(Value::String(s[start..end].to_string()))
    });

    env.register_builtin("string.concat", |args, _| {
        let result: String = args.iter().map(|v| {
            if v.is_null() { String::new() } else { v.to_display_string() }
        }).collect();
        Ok(Value::String(result))
    });

    env.register_builtin("concat", |args, _| {
        let result: String = args.iter().map(|v| {
            if v.is_null() { String::new() } else { v.to_display_string() }
        }).collect();
        Ok(Value::String(result))
    });
}