scurry 0.5.0

A component-based object-oriented language
Documentation
use std::cell::RefCell;
use std::rc::Rc;

use crate::ast::{AstType, TypeAnnotation};
use crate::interpreter::object::*;

macro_rules! validate_args_len {
    ($args:expr, $expected:expr, $line:expr) => {
        if $args.len() != $expected {
            return Some(Err(RuntimeError::NotEnoughArgs {
                got: $args.len(),
                want: $expected,
                line: $line,
            }));
        }
    };
}

pub fn len(bound: Object, args: Vec<Object>, line: usize) -> Option<EvalResult> {
    validate_args_len!(args, 0, line);
    if let Object::String(obj) = bound {
        Some(Ok(Object::Int(obj.chars().count() as i32)))
    } else {
        None
    }
}

pub fn trim(bound: Object, args: Vec<Object>, line: usize) -> Option<EvalResult> {
    validate_args_len!(args, 0, line);
    if let Object::String(obj) = bound {
        Some(Ok(Object::String(obj.trim().to_owned())))
    } else {
        None
    }
}

pub fn starts_with(bound: Object, args: Vec<Object>, line: usize) -> Option<EvalResult> {
    validate_args_len!(args, 1, line);
    let Object::String(string) = &args[0] else {
        return Some(Err(RuntimeError::WrongArgType { name: "string".to_owned(), expected: TypeAnnotation::from_iter([AstType::String]), got: args[0].scurry_type().into(), line }));
    };
    if let Object::String(obj) = bound {
        Some(Ok(Object::Bool(obj.starts_with(string))))
    } else {
        None
    }
}

pub fn ends_with(bound: Object, args: Vec<Object>, line: usize) -> Option<EvalResult> {
    validate_args_len!(args, 1, line);
    let Object::String(string) = &args[0] else {
        return Some(Err(RuntimeError::WrongArgType { name: "string".to_owned(), expected: TypeAnnotation::from_iter([AstType::String]), got: args[0].scurry_type().into(), line }));
    };
    if let Object::String(obj) = bound {
        Some(Ok(Object::Bool(obj.ends_with(string))))
    } else {
        None
    }
}

pub fn substring(bound: Object, args: Vec<Object>, line: usize) -> Option<EvalResult> {
    validate_args_len!(args, 2, line);
    let Object::Int(start) = args[0] else {
        return Some(Err(RuntimeError::WrongArgType { name: "start".to_owned(), expected: TypeAnnotation::from_iter([AstType::Int]), got: args[0].scurry_type().into(), line }));
    };
    let Object::Int(end) = args[1] else {
        return Some(Err(RuntimeError::WrongArgType { name: "end".to_owned(), expected: TypeAnnotation::from_iter([AstType::Int]), got: args[1].scurry_type().into(), line }));
    };
    if let Object::String(obj) = bound {
        if start >= end {
            return Some(Err(RuntimeError::IndexOutOfRange {
                obj: Object::String(obj),
                index: start,
                line,
            }));
        }
        if start < 0 || end < 0 {
            return Some(Err(RuntimeError::IndexOutOfRange {
                obj: Object::String(obj),
                index: start,
                line,
            }));
        }
        Some(Ok(Object::String(
            obj.chars()
                .skip(start as usize)
                .take(end as usize)
                .collect(),
        )))
    } else {
        None
    }
}

pub fn split(bound: Object, args: Vec<Object>, line: usize) -> Option<EvalResult> {
    validate_args_len!(args, 1, line);
    let Object::String(delim) = &args[0] else {
        return Some(Err(RuntimeError::WrongArgType { name: "delim".to_owned(), expected: TypeAnnotation::from_iter([AstType::String]), got: args[0].scurry_type().into(), line }));
    };
    if let Object::String(obj) = bound {
        Some(Ok(Object::Array(Rc::new(RefCell::new(
            obj.split(delim)
                .map(|s| Object::String(s.to_owned()))
                .collect(),
        )))))
    } else {
        None
    }
}