sapphire-lang 0.8.0

Gradually typed scripting language where every value is an object and types are optional, checked at runtime
Documentation
use crate::gc::{GcHeap, GcRef};
use crate::vm::{define_native_method, HeapObject, NativeArity, VmError, VmValue};

fn str_recv(recv: &VmValue) -> &str {
    match recv {
        VmValue::Str(s) => s.as_str(),
        _ => unreachable!("String native on non-Str"),
    }
}

pub fn string_bytes(
    heap: &mut GcHeap<HeapObject>,
    recv: &VmValue,
    _args: &[VmValue],
    _line: u32,
) -> Result<VmValue, VmError> {
    let s = str_recv(recv);
    let bytes: Vec<VmValue> = s.bytes().map(|b| VmValue::Int(b as i64)).collect();
    Ok(VmValue::List(heap.alloc(HeapObject::List(bytes))))
}

pub fn string_chars(
    heap: &mut GcHeap<HeapObject>,
    recv: &VmValue,
    _args: &[VmValue],
    _line: u32,
) -> Result<VmValue, VmError> {
    let s = str_recv(recv);
    let chars: Vec<VmValue> = s.chars().map(|c| VmValue::Str(c.to_string())).collect();
    Ok(VmValue::List(heap.alloc(HeapObject::List(chars))))
}

pub fn string_chomp(
    _heap: &mut GcHeap<HeapObject>,
    recv: &VmValue,
    _args: &[VmValue],
    _line: u32,
) -> Result<VmValue, VmError> {
    let s = str_recv(recv);
    Ok(VmValue::Str(s.trim_end_matches('\n').to_string()))
}

pub fn string_downcase(
    _heap: &mut GcHeap<HeapObject>,
    recv: &VmValue,
    _args: &[VmValue],
    _line: u32,
) -> Result<VmValue, VmError> {
    Ok(VmValue::Str(str_recv(recv).to_lowercase()))
}

pub fn string_empty_q(
    _heap: &mut GcHeap<HeapObject>,
    recv: &VmValue,
    _args: &[VmValue],
    _line: u32,
) -> Result<VmValue, VmError> {
    Ok(VmValue::Bool(str_recv(recv).is_empty()))
}

pub fn string_ends_with_q(
    _heap: &mut GcHeap<HeapObject>,
    recv: &VmValue,
    args: &[VmValue],
    line: u32,
) -> Result<VmValue, VmError> {
    let s = str_recv(recv);
    match args {
        [VmValue::Str(pat)] => Ok(VmValue::Bool(s.ends_with(pat.as_str()))),
        [_] => Err(VmError::TypeError {
            message: "ends_with? expects a String".to_string(),
            line,
        }),
        _ => unreachable!("String#ends_with?: expected 1 argument"),
    }
}

pub fn string_include_q(
    _heap: &mut GcHeap<HeapObject>,
    recv: &VmValue,
    args: &[VmValue],
    line: u32,
) -> Result<VmValue, VmError> {
    let s = str_recv(recv);
    match args {
        [VmValue::Str(pat)] => Ok(VmValue::Bool(s.contains(pat.as_str()))),
        [_] => Err(VmError::TypeError {
            message: "include? expects a String".to_string(),
            line,
        }),
        _ => unreachable!("String#include?: expected 1 argument"),
    }
}

pub fn string_lines(
    heap: &mut GcHeap<HeapObject>,
    recv: &VmValue,
    _args: &[VmValue],
    _line: u32,
) -> Result<VmValue, VmError> {
    let s = str_recv(recv);
    let lines: Vec<VmValue> = s.lines().map(|l| VmValue::Str(l.to_string())).collect();
    Ok(VmValue::List(heap.alloc(HeapObject::List(lines))))
}

pub fn string_replace(
    _heap: &mut GcHeap<HeapObject>,
    recv: &VmValue,
    args: &[VmValue],
    line: u32,
) -> Result<VmValue, VmError> {
    let s = str_recv(recv);
    match args {
        [VmValue::Str(from), VmValue::Str(to)] => Ok(VmValue::Str(
            s.replacen(from.as_str(), to.as_str(), 1),
        )),
        [_, _] => Err(VmError::TypeError {
            message: "replace expects two Strings".to_string(),
            line,
        }),
        _ => unreachable!("String#replace: expected 2 arguments"),
    }
}

pub fn string_replace_all(
    _heap: &mut GcHeap<HeapObject>,
    recv: &VmValue,
    args: &[VmValue],
    line: u32,
) -> Result<VmValue, VmError> {
    let s = str_recv(recv);
    match args {
        [VmValue::Str(from), VmValue::Str(to)] => {
            Ok(VmValue::Str(s.replace(from.as_str(), to.as_str())))
        }
        [_, _] => Err(VmError::TypeError {
            message: "replace_all expects two Strings".to_string(),
            line,
        }),
        _ => unreachable!("String#replace_all: expected 2 arguments"),
    }
}

pub fn string_reverse(
    _heap: &mut GcHeap<HeapObject>,
    recv: &VmValue,
    _args: &[VmValue],
    _line: u32,
) -> Result<VmValue, VmError> {
    Ok(VmValue::Str(str_recv(recv).chars().rev().collect()))
}

pub fn string_size(
    _heap: &mut GcHeap<HeapObject>,
    recv: &VmValue,
    _args: &[VmValue],
    _line: u32,
) -> Result<VmValue, VmError> {
    Ok(VmValue::Int(str_recv(recv).chars().count() as i64))
}

pub fn string_slice(
    _heap: &mut GcHeap<HeapObject>,
    recv: &VmValue,
    args: &[VmValue],
    line: u32,
) -> Result<VmValue, VmError> {
    let s = str_recv(recv);
    match args {
        [VmValue::Int(start), VmValue::Int(len)] => {
            let chars: Vec<char> = s.chars().collect();
            let n = chars.len() as i64;
            let start = if *start < 0 {
                (n + start).max(0) as usize
            } else {
                *start as usize
            };
            let len = *len as usize;
            let end = (start + len).min(chars.len());
            Ok(VmValue::Str(chars[start..end].iter().collect()))
        }
        [_, _] => Err(VmError::TypeError {
            message: "slice expects (Int, Int)".to_string(),
            line,
        }),
        _ => unreachable!("String#slice: expected 2 arguments"),
    }
}

pub fn string_split(
    heap: &mut GcHeap<HeapObject>,
    recv: &VmValue,
    args: &[VmValue],
    line: u32,
) -> Result<VmValue, VmError> {
    let s = str_recv(recv);
    match args {
        [] => {
            let parts: Vec<VmValue> = s
                .split_whitespace()
                .map(|p| VmValue::Str(p.to_string()))
                .collect();
            Ok(VmValue::List(heap.alloc(HeapObject::List(parts))))
        }
        [VmValue::Str(sep)] => {
            let parts: Vec<VmValue> = s
                .split(sep.as_str())
                .map(|p| VmValue::Str(p.to_string()))
                .collect();
            Ok(VmValue::List(heap.alloc(HeapObject::List(parts))))
        }
        _ => Err(VmError::TypeError {
            message: "split expects a String delimiter".to_string(),
            line,
        }),
    }
}

pub fn string_starts_with_q(
    _heap: &mut GcHeap<HeapObject>,
    recv: &VmValue,
    args: &[VmValue],
    line: u32,
) -> Result<VmValue, VmError> {
    let s = str_recv(recv);
    match args {
        [VmValue::Str(pat)] => Ok(VmValue::Bool(s.starts_with(pat.as_str()))),
        [_] => Err(VmError::TypeError {
            message: "starts_with? expects a String".to_string(),
            line,
        }),
        _ => unreachable!("String#starts_with?: expected 1 argument"),
    }
}

pub fn string_to_f(
    _heap: &mut GcHeap<HeapObject>,
    recv: &VmValue,
    _args: &[VmValue],
    _line: u32,
) -> Result<VmValue, VmError> {
    Ok(VmValue::Float(str_recv(recv).trim().parse::<f64>().unwrap_or(0.0)))
}

pub fn string_to_i(
    _heap: &mut GcHeap<HeapObject>,
    recv: &VmValue,
    _args: &[VmValue],
    _line: u32,
) -> Result<VmValue, VmError> {
    Ok(VmValue::Int(str_recv(recv).trim().parse::<i64>().unwrap_or(0)))
}

pub fn string_to_s(
    _heap: &mut GcHeap<HeapObject>,
    recv: &VmValue,
    _args: &[VmValue],
    _line: u32,
) -> Result<VmValue, VmError> {
    Ok(VmValue::Str(str_recv(recv).to_string()))
}

pub fn string_trim(
    _heap: &mut GcHeap<HeapObject>,
    recv: &VmValue,
    _args: &[VmValue],
    _line: u32,
) -> Result<VmValue, VmError> {
    Ok(VmValue::Str(str_recv(recv).trim().to_string()))
}

pub fn string_upcase(
    _heap: &mut GcHeap<HeapObject>,
    recv: &VmValue,
    _args: &[VmValue],
    _line: u32,
) -> Result<VmValue, VmError> {
    Ok(VmValue::Str(str_recv(recv).to_uppercase()))
}

pub fn register_methods(heap: &mut GcHeap<HeapObject>, class_ref: GcRef) {
    define_native_method(heap, class_ref, "bytes", 0, string_bytes);
    define_native_method(heap, class_ref, "chars", 0, string_chars);
    define_native_method(heap, class_ref, "chomp", 0, string_chomp);
    define_native_method(heap, class_ref, "downcase", 0, string_downcase);
    define_native_method(heap, class_ref, "empty?", 0, string_empty_q);
    define_native_method(heap, class_ref, "ends_with?", 1, string_ends_with_q);
    define_native_method(heap, class_ref, "include?", 1, string_include_q);
    define_native_method(heap, class_ref, "lines", 0, string_lines);
    define_native_method(heap, class_ref, "replace", 2, string_replace);
    define_native_method(heap, class_ref, "replace_all", 2, string_replace_all);
    define_native_method(heap, class_ref, "reverse", 0, string_reverse);
    define_native_method(heap, class_ref, "size", 0, string_size);
    define_native_method(heap, class_ref, "slice", 2, string_slice);
    define_native_method(heap, class_ref, "starts_with?", 1, string_starts_with_q);
    define_native_method(heap, class_ref, "split", NativeArity { min: 0, max: 1 }, string_split);
    define_native_method(heap, class_ref, "to_f", 0, string_to_f);
    define_native_method(heap, class_ref, "to_i", 0, string_to_i);
    define_native_method(heap, class_ref, "to_s", 0, string_to_s);
    define_native_method(heap, class_ref, "trim", 0, string_trim);
    define_native_method(heap, class_ref, "upcase", 0, string_upcase);
}