runmat-vm 0.4.4

RunMat virtual machine and bytecode interpreter
Documentation
use crate::interpreter::errors::mex;
use crate::interpreter::stack::{pop_args, pop_value};
use runmat_builtins::{
    builtin_functions, get_static_property_value, lookup_method, lookup_property, Access,
    CellArray, Closure, Value,
};
use runmat_runtime::RuntimeError;

pub fn create_closure(
    stack: &mut Vec<Value>,
    func_name: String,
    capture_count: usize,
) -> Result<(), RuntimeError> {
    let mut captures = Vec::with_capacity(capture_count);
    for _ in 0..capture_count {
        captures.push(pop_value(stack)?);
    }
    captures.reverse();
    stack.push(Value::Closure(Closure {
        function_name: func_name,
        captures,
    }));
    Ok(())
}

pub fn load_method_closure(base: Value, name: String) -> Result<Value, RuntimeError> {
    match base {
        Value::Object(obj) => Ok(Value::Closure(Closure {
            function_name: format!("{}.{}", obj.class_name, name),
            captures: vec![Value::Object(obj)],
        })),
        Value::ClassRef(cls) => {
            if let Some((m, _owner)) = lookup_method(&cls, &name) {
                if !m.is_static {
                    return Err(format!("Method '{}' is not static", name).into());
                }
                return Ok(Value::Closure(Closure {
                    function_name: m.function_name,
                    captures: vec![],
                }));
            }
            let qualified = format!("{cls}.{name}");
            if builtin_functions().iter().any(|b| b.name == qualified) {
                Ok(Value::Closure(Closure {
                    function_name: qualified,
                    captures: vec![],
                }))
            } else {
                Err(format!("Unknown static method '{}' on class {}", name, cls).into())
            }
        }
        _ => Err(mex("LoadMethod", "LoadMethod requires object or classref")),
    }
}

pub async fn call_method(base: Value, name: &str, args: Vec<Value>) -> Result<Value, RuntimeError> {
    match base {
        Value::Object(obj) => {
            if let Some((m, _owner)) = lookup_method(&obj.class_name, name) {
                if m.is_static {
                    return Err(format!(
                        "Method '{}' is static; use classref({}).{}",
                        name, obj.class_name, name
                    )
                    .into());
                }
                if m.access == Access::Private {
                    return Err(format!("Method '{}' is private", name).into());
                }
                let mut full_args = Vec::with_capacity(1 + args.len());
                full_args.push(Value::Object(obj));
                full_args.extend(args);
                return runmat_runtime::call_builtin_async(&m.function_name, &full_args).await;
            }
            let qualified = format!("{}.{}", obj.class_name, name);
            let mut full_args = Vec::with_capacity(1 + args.len());
            full_args.push(Value::Object(obj));
            full_args.extend(args.clone());
            if let Ok(v) = runmat_runtime::call_builtin_async(&qualified, &full_args).await {
                Ok(v)
            } else {
                runmat_runtime::call_builtin_async(name, &full_args).await
            }
        }
        _ => Err(mex("CallMethod", "CallMethod on non-object")),
    }
}

pub async fn call_static_method(
    class_name: &str,
    method: &str,
    args: Vec<Value>,
) -> Result<Value, RuntimeError> {
    if let Some((m, _owner)) = lookup_method(class_name, method) {
        if !m.is_static {
            return Err(format!("Method '{}' is not static", method).into());
        }
        if m.access == Access::Private {
            return Err(format!("Method '{}' is private", method).into());
        }
        return runmat_runtime::call_builtin_async(&m.function_name, &args).await;
    }
    let qualified = format!("{}.{}", class_name, method);
    runmat_runtime::call_builtin_async(&qualified, &args).await
}

pub fn load_static_property(class_name: &str, prop: &str) -> Result<Value, RuntimeError> {
    if let Some((p, owner)) = lookup_property(class_name, prop) {
        if !p.is_static {
            return Err(format!("Property '{}' is not static", prop).into());
        }
        if p.get_access == Access::Private {
            return Err(format!("Property '{}' is private", prop).into());
        }
        if let Some(v) = get_static_property_value(&owner, prop) {
            Ok(v)
        } else if let Some(v) = &p.default_value {
            Ok(v.clone())
        } else {
            Ok(Value::Num(0.0))
        }
    } else {
        Err(format!("Unknown property '{}' on class {}", prop, class_name).into())
    }
}

pub async fn call_method_or_member_index(
    base: Value,
    name: String,
    args: Vec<Value>,
) -> Result<Value, RuntimeError> {
    match base {
        Value::Object(obj) => {
            if let Some((m, _owner)) = lookup_method(&obj.class_name, &name) {
                if m.is_static {
                    return Err(format!(
                        "Method '{}' is static; use classref({}).{}",
                        name, obj.class_name, name
                    )
                    .into());
                }
                if m.access == Access::Private {
                    return Err(format!("Method '{}' is private", name).into());
                }
                let mut full_args = Vec::with_capacity(1 + args.len());
                full_args.push(Value::Object(obj));
                full_args.extend(args);
                return runmat_runtime::call_builtin_async(&m.function_name, &full_args).await;
            }

            let mut method_args = Vec::with_capacity(1 + args.len());
            method_args.push(Value::Object(obj.clone()));
            method_args.extend(args.iter().cloned());
            let qualified = format!("{}.{}", obj.class_name, name);
            if let Ok(v) = runmat_runtime::call_builtin_async(&qualified, &method_args).await {
                return Ok(v);
            }
            if let Ok(v) = runmat_runtime::call_builtin_async(&name, &method_args).await {
                return Ok(v);
            }

            let mut getfield_args = Vec::with_capacity(3);
            getfield_args.push(Value::Object(obj));
            getfield_args.push(Value::String(name));
            if !args.is_empty() {
                let idx_count = args.len();
                let idx_cell = CellArray::new(args, 1, idx_count)
                    .map_err(|e| format!("getfield idx build: {e}"))?;
                getfield_args.push(Value::Cell(idx_cell));
            }
            runmat_runtime::call_builtin_async("getfield", &getfield_args).await
        }
        Value::HandleObject(handle) => {
            let mut method_args = Vec::with_capacity(2 + args.len());
            method_args.push(Value::HandleObject(handle.clone()));
            method_args.push(Value::String(name.clone()));
            method_args.extend(args.iter().cloned());
            if let Ok(v) = runmat_runtime::call_builtin_async("call_method", &method_args).await {
                return Ok(v);
            }

            let mut getfield_args = Vec::with_capacity(3);
            getfield_args.push(Value::HandleObject(handle));
            getfield_args.push(Value::String(name));
            if !args.is_empty() {
                let idx_count = args.len();
                let idx_cell = CellArray::new(args, 1, idx_count)
                    .map_err(|e| format!("getfield idx build: {e}"))?;
                getfield_args.push(Value::Cell(idx_cell));
            }
            runmat_runtime::call_builtin_async("getfield", &getfield_args).await
        }
        Value::ClassRef(cls) => {
            if let Some((m, _owner)) = lookup_method(&cls, &name) {
                if !m.is_static {
                    return Err(format!("Method '{}' is not static", name).into());
                }
                return runmat_runtime::call_builtin_async(&m.function_name, &args).await;
            }

            let qualified = format!("{cls}.{name}");
            if let Ok(v) = runmat_runtime::call_builtin_async(&qualified, &args).await {
                return Ok(v);
            }

            if args.is_empty() {
                return load_static_property(&cls, &name);
            }

            Err(format!("Unknown static member '{}' on class {}", name, cls).into())
        }
        other => {
            let mut getfield_args = Vec::with_capacity(3);
            getfield_args.push(other);
            getfield_args.push(Value::String(name));
            if !args.is_empty() {
                let idx_count = args.len();
                let idx_cell = CellArray::new(args, 1, idx_count)
                    .map_err(|e| format!("getfield idx build: {e}"))?;
                getfield_args.push(Value::Cell(idx_cell));
            }
            runmat_runtime::call_builtin_async("getfield", &getfield_args).await
        }
    }
}

pub fn collect_method_args(
    stack: &mut Vec<Value>,
    arg_count: usize,
) -> Result<(Value, Vec<Value>), RuntimeError> {
    let args = pop_args(stack, arg_count)?;
    let base = pop_value(stack)?;
    Ok((base, args))
}