sapphire-lang 0.7.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, VmError, VmValue};

const METHOD_ARITIES: &[(&str, usize)] = &[
    ("first", 0),
    ("include?", 1),
    ("last", 0),
    ("max", 0),
    ("min", 0),
    ("size", 0),
    ("to_a", 0),
    ("to_s", 0),
];

fn arg_error(name: &str, argc: usize, line: u32) -> VmError {
    let msg = METHOD_ARITIES
        .iter()
        .find(|(n, _)| *n == name)
        .map(|(_, arity)| format!("Range.{name} expects {arity} argument(s), got {argc}"))
        .unwrap_or_else(|| format!("Range has no method '{name}'"));
    VmError::TypeError { message: msg, line }
}

fn range_recv(recv: &VmValue) -> (i64, i64) {
    match recv {
        VmValue::Range { from, to } => (*from, *to),
        _ => unreachable!("Range native on non-Range"),
    }
}

pub fn dispatch_range_method(
    heap: &mut GcHeap<HeapObject>,
    from: i64,
    to: i64,
    recv: &VmValue,
    name: &str,
    args: &[VmValue],
    line: u32,
) -> Result<VmValue, VmError> {
    match (name, args) {
        ("first", []) => Ok(VmValue::Int(from)),
        ("include?", [VmValue::Int(n)]) => Ok(VmValue::Bool(n >= &from && n < &to)),
        ("last", []) => Ok(VmValue::Int(to - 1)),
        ("max", []) => Ok(VmValue::Int(to - 1)),
        ("min", []) => Ok(VmValue::Int(from)),
        ("size", []) => Ok(VmValue::Int((to - from).max(0))),
        ("to_a", []) => {
            let v: Vec<VmValue> = (from..to).map(VmValue::Int).collect();
            Ok(VmValue::List(heap.alloc(HeapObject::List(v))))
        }
        ("to_s", []) => Ok(VmValue::Str(format!("{}", recv))),
        ("include?", [_]) => Err(VmError::TypeError {
            message: "include? expects an Int".to_string(),
            line,
        }),
        _ => Err(arg_error(name, args.len(), line)),
    }
}

macro_rules! range_native {
    ($fn:ident, $name:literal) => {
        pub fn $fn(
            heap: &mut GcHeap<HeapObject>,
            recv: &VmValue,
            args: &[VmValue],
            line: u32,
        ) -> Result<VmValue, VmError> {
            let (from, to) = range_recv(recv);
            dispatch_range_method(heap, from, to, recv, $name, args, line)
        }
    };
}

range_native!(range_first, "first");
range_native!(range_include_q, "include?");
range_native!(range_last, "last");
range_native!(range_max, "max");
range_native!(range_min, "min");
range_native!(range_size, "size");
range_native!(range_to_a, "to_a");
range_native!(range_to_s, "to_s");

pub fn register_methods(heap: &mut GcHeap<HeapObject>, class_ref: GcRef) {
    define_native_method(heap, class_ref, "first", 0, range_first);
    define_native_method(heap, class_ref, "include?", 1, range_include_q);
    define_native_method(heap, class_ref, "last", 0, range_last);
    define_native_method(heap, class_ref, "max", 0, range_max);
    define_native_method(heap, class_ref, "min", 0, range_min);
    define_native_method(heap, class_ref, "size", 0, range_size);
    define_native_method(heap, class_ref, "to_a", 0, range_to_a);
    define_native_method(heap, class_ref, "to_s", 0, range_to_s);
}