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::native::vm_value_partial_cmp;
use crate::vm::{
    define_native_method, format_value_with_heap, HeapObject, NativeArity, VmError, VmValue,
};

fn list_r(recv: &VmValue) -> GcRef {
    match recv {
        VmValue::List(r) => *r,
        _ => unreachable!("List native on non-List"),
    }
}

fn flatten_value(heap: &GcHeap<HeapObject>, v: &VmValue) -> Vec<VmValue> {
    match v {
        VmValue::List(inner) => heap
            .get_list(*inner)
            .iter()
            .flat_map(|el| flatten_value(heap, el))
            .collect(),
        other => vec![other.clone()],
    }
}

pub fn list_all_q(
    _heap: &mut GcHeap<HeapObject>,
    _recv: &VmValue,
    _args: &[VmValue],
    line: u32,
) -> Result<VmValue, VmError> {
    Err(VmError::TypeError {
        message: "all? requires a block".to_string(),
        line,
    })
}

pub fn list_any_q(
    _heap: &mut GcHeap<HeapObject>,
    _recv: &VmValue,
    _args: &[VmValue],
    line: u32,
) -> Result<VmValue, VmError> {
    Err(VmError::TypeError {
        message: "any? requires a block".to_string(),
        line,
    })
}

pub fn list_append(
    heap: &mut GcHeap<HeapObject>,
    recv: &VmValue,
    args: &[VmValue],
    _line: u32,
) -> Result<VmValue, VmError> {
    let r = list_r(recv);
    heap.get_list_mut(r).push(args[0].clone());
    Ok(recv.clone())
}

pub fn list_concat(
    heap: &mut GcHeap<HeapObject>,
    recv: &VmValue,
    args: &[VmValue],
    line: u32,
) -> Result<VmValue, VmError> {
    let r = list_r(recv);
    match &args[0] {
        VmValue::List(other_r) => {
            let other_items: Vec<VmValue> = heap.get_list(*other_r).clone();
            heap.get_list_mut(r).extend(other_items);
            Ok(recv.clone())
        }
        _ => Err(VmError::TypeError {
            message: "concat expects a List".to_string(),
            line,
        }),
    }
}

pub fn list_empty_q(
    heap: &mut GcHeap<HeapObject>,
    recv: &VmValue,
    _args: &[VmValue],
    _line: u32,
) -> Result<VmValue, VmError> {
    let r = list_r(recv);
    Ok(VmValue::Bool(heap.get_list(r).is_empty()))
}

pub fn list_first(
    heap: &mut GcHeap<HeapObject>,
    recv: &VmValue,
    _args: &[VmValue],
    _line: u32,
) -> Result<VmValue, VmError> {
    let r = list_r(recv);
    Ok(heap.get_list(r).first().cloned().unwrap_or(VmValue::Nil))
}

pub fn list_flatten(
    heap: &mut GcHeap<HeapObject>,
    recv: &VmValue,
    _args: &[VmValue],
    _line: u32,
) -> Result<VmValue, VmError> {
    let r = list_r(recv);
    let v: Vec<VmValue> = heap
        .get_list(r)
        .iter()
        .flat_map(|el| flatten_value(heap, el))
        .collect();
    Ok(VmValue::List(heap.alloc(HeapObject::List(v))))
}

pub fn list_include_q(
    heap: &mut GcHeap<HeapObject>,
    recv: &VmValue,
    args: &[VmValue],
    _line: u32,
) -> Result<VmValue, VmError> {
    let r = list_r(recv);
    Ok(VmValue::Bool(heap.get_list(r).contains(&args[0])))
}

pub fn list_join(
    heap: &mut GcHeap<HeapObject>,
    recv: &VmValue,
    args: &[VmValue],
    line: u32,
) -> Result<VmValue, VmError> {
    let r = list_r(recv);
    let sep = match args {
        [] => "",
        [VmValue::Str(s)] => s.as_str(),
        _ => {
            return Err(VmError::TypeError {
                message: "join expects a String".to_string(),
                line,
            })
        }
    };
    let s = heap
        .get_list(r)
        .iter()
        .map(|v| format_value_with_heap(heap, v))
        .collect::<Vec<_>>()
        .join(sep);
    Ok(VmValue::Str(s))
}

pub fn list_last(
    heap: &mut GcHeap<HeapObject>,
    recv: &VmValue,
    _args: &[VmValue],
    _line: u32,
) -> Result<VmValue, VmError> {
    let r = list_r(recv);
    Ok(heap.get_list(r).last().cloned().unwrap_or(VmValue::Nil))
}

pub fn list_max(
    heap: &mut GcHeap<HeapObject>,
    recv: &VmValue,
    _args: &[VmValue],
    _line: u32,
) -> Result<VmValue, VmError> {
    let r = list_r(recv);
    let v = heap.get_list(r);
    if v.is_empty() {
        return Ok(VmValue::Nil);
    }
    Ok(v.iter().max_by(|a, b| vm_value_partial_cmp(a, b)).cloned().unwrap())
}

pub fn list_min(
    heap: &mut GcHeap<HeapObject>,
    recv: &VmValue,
    _args: &[VmValue],
    _line: u32,
) -> Result<VmValue, VmError> {
    let r = list_r(recv);
    let v = heap.get_list(r);
    if v.is_empty() {
        return Ok(VmValue::Nil);
    }
    Ok(v.iter().min_by(|a, b| vm_value_partial_cmp(a, b)).cloned().unwrap())
}

pub fn list_pop(
    heap: &mut GcHeap<HeapObject>,
    recv: &VmValue,
    _args: &[VmValue],
    _line: u32,
) -> Result<VmValue, VmError> {
    let r = list_r(recv);
    Ok(heap.get_list_mut(r).pop().unwrap_or(VmValue::Nil))
}

pub fn list_prepend(
    heap: &mut GcHeap<HeapObject>,
    recv: &VmValue,
    args: &[VmValue],
    _line: u32,
) -> Result<VmValue, VmError> {
    let r = list_r(recv);
    heap.get_list_mut(r).insert(0, args[0].clone());
    Ok(recv.clone())
}

pub fn list_reverse(
    heap: &mut GcHeap<HeapObject>,
    recv: &VmValue,
    _args: &[VmValue],
    _line: u32,
) -> Result<VmValue, VmError> {
    let r = list_r(recv);
    let v: Vec<VmValue> = heap.get_list(r).iter().cloned().rev().collect();
    Ok(VmValue::List(heap.alloc(HeapObject::List(v))))
}

pub fn list_size(
    heap: &mut GcHeap<HeapObject>,
    recv: &VmValue,
    _args: &[VmValue],
    _line: u32,
) -> Result<VmValue, VmError> {
    let r = list_r(recv);
    Ok(VmValue::Int(heap.get_list(r).len() as i64))
}

pub fn list_sort(
    heap: &mut GcHeap<HeapObject>,
    recv: &VmValue,
    _args: &[VmValue],
    _line: u32,
) -> Result<VmValue, VmError> {
    let r = list_r(recv);
    let mut v: Vec<VmValue> = heap.get_list(r).clone();
    v.sort_by(vm_value_partial_cmp);
    Ok(VmValue::List(heap.alloc(HeapObject::List(v))))
}

pub fn list_sum(
    heap: &mut GcHeap<HeapObject>,
    recv: &VmValue,
    _args: &[VmValue],
    line: u32,
) -> Result<VmValue, VmError> {
    let r = list_r(recv);
    let items: Vec<VmValue> = heap.get_list(r).clone();
    let mut acc = VmValue::Int(0);
    for item in items.iter() {
        acc = match (&acc, item) {
            (VmValue::Int(a), VmValue::Int(b)) => VmValue::Int(a + b),
            (VmValue::Float(a), VmValue::Float(b)) => VmValue::Float(a + b),
            (VmValue::Int(a), VmValue::Float(b)) => VmValue::Float(*a as f64 + b),
            (VmValue::Float(a), VmValue::Int(b)) => VmValue::Float(a + *b as f64),
            _ => {
                return Err(VmError::TypeError {
                    message: "sum: non-numeric element".to_string(),
                    line,
                })
            }
        };
    }
    Ok(acc)
}

pub fn list_to_s(
    heap: &mut GcHeap<HeapObject>,
    recv: &VmValue,
    _args: &[VmValue],
    _line: u32,
) -> Result<VmValue, VmError> {
    Ok(VmValue::Str(format_value_with_heap(heap, recv)))
}

pub fn list_uniq(
    heap: &mut GcHeap<HeapObject>,
    recv: &VmValue,
    _args: &[VmValue],
    _line: u32,
) -> Result<VmValue, VmError> {
    let r = list_r(recv);
    let mut seen = Vec::new();
    for item in heap.get_list(r).iter() {
        if !seen.contains(item) {
            seen.push(item.clone());
        }
    }
    Ok(VmValue::List(heap.alloc(HeapObject::List(seen))))
}

pub fn register_methods(heap: &mut GcHeap<HeapObject>, class_ref: GcRef) {
    define_native_method(heap, class_ref, "all?", 0, list_all_q);
    define_native_method(heap, class_ref, "any?", 0, list_any_q);
    define_native_method(heap, class_ref, "append", 1, list_append);
    define_native_method(heap, class_ref, "concat", 1, list_concat);
    define_native_method(heap, class_ref, "empty?", 0, list_empty_q);
    define_native_method(heap, class_ref, "first", 0, list_first);
    define_native_method(heap, class_ref, "flatten", 0, list_flatten);
    define_native_method(heap, class_ref, "include?", 1, list_include_q);
    define_native_method(
        heap,
        class_ref,
        "join",
        NativeArity::at_least(0),
        list_join,
    );
    define_native_method(heap, class_ref, "last", 0, list_last);
    define_native_method(heap, class_ref, "max", 0, list_max);
    define_native_method(heap, class_ref, "min", 0, list_min);
    define_native_method(heap, class_ref, "pop", 0, list_pop);
    define_native_method(heap, class_ref, "prepend", 1, list_prepend);
    define_native_method(heap, class_ref, "reverse", 0, list_reverse);
    define_native_method(heap, class_ref, "size", 0, list_size);
    define_native_method(heap, class_ref, "sort", 0, list_sort);
    define_native_method(heap, class_ref, "sum", 0, list_sum);
    define_native_method(heap, class_ref, "to_s", 0, list_to_s);
    define_native_method(heap, class_ref, "uniq", 0, list_uniq);
}