runmat-vm 0.4.5

RunMat virtual machine and bytecode interpreter
Documentation
use crate::bytecode::EmitLabel;
use crate::bytecode::ExecutionContext;
use crate::interpreter::errors::mex;
use crate::interpreter::stack::{pop2, pop_value};
use crate::runtime::workspace::{
    ensure_workspace_slot_name, mark_workspace_assigned, refresh_workspace_state,
};
use runmat_builtins::{CharArray, Value};
use runmat_runtime::{dispatcher::gather_if_needed_async, RuntimeError};
use std::collections::HashMap;

fn resolve_emit_label_text(
    label: &EmitLabel,
    var_names: &HashMap<usize, String>,
) -> Option<String> {
    match label {
        EmitLabel::Ans => Some("ans".to_string()),
        EmitLabel::Var(idx) => var_names
            .get(idx)
            .cloned()
            .or_else(|| Some(format!("var{idx}"))),
    }
}

pub async fn emit_stack_top(
    stack: &[Value],
    label: &EmitLabel,
    var_names: &HashMap<usize, String>,
) -> Result<(), RuntimeError> {
    if let Some(value) = stack.last() {
        let label_text = resolve_emit_label_text(label, var_names);
        let host_value = gather_if_needed_async(value).await?;
        runmat_runtime::console::record_value_output(label_text.as_deref(), &host_value);
    }
    Ok(())
}

pub async fn emit_var(
    vars: &[Value],
    var_index: usize,
    label: &EmitLabel,
    var_names: &HashMap<usize, String>,
) -> Result<(), RuntimeError> {
    if let Some(value) = vars.get(var_index) {
        let label_text = resolve_emit_label_text(label, var_names);
        let host_value = gather_if_needed_async(value).await?;
        runmat_runtime::console::record_value_output(label_text.as_deref(), &host_value);
    }
    Ok(())
}

#[inline]
pub fn load_const(stack: &mut Vec<Value>, value: f64) {
    stack.push(Value::Num(value));
}

#[inline]
pub fn load_complex(stack: &mut Vec<Value>, re: f64, im: f64) {
    stack.push(Value::Complex(re, im));
}

#[inline]
pub fn load_bool(stack: &mut Vec<Value>, value: bool) {
    stack.push(Value::Bool(value));
}

#[inline]
pub fn load_string(stack: &mut Vec<Value>, value: String) {
    stack.push(Value::String(value));
}

pub fn load_char_row(stack: &mut Vec<Value>, value: String) -> Result<(), RuntimeError> {
    let ca = CharArray::new(value.chars().collect(), 1, value.chars().count())
        .map_err(|e| mex("CharError", &e))?;
    stack.push(Value::CharArray(ca));
    Ok(())
}

pub fn load_local(
    stack: &mut Vec<Value>,
    context: &ExecutionContext,
    vars: &[Value],
    offset: usize,
) -> Result<(), RuntimeError> {
    if let Some(current_frame) = context.call_stack.last() {
        let local_index = current_frame.locals_start + offset;
        if local_index >= context.locals.len() {
            return Err("Local variable index out of bounds".to_string().into());
        }
        stack.push(context.locals[local_index].clone());
    } else if offset < vars.len() {
        stack.push(vars[offset].clone());
    } else {
        stack.push(Value::Num(0.0));
    }
    Ok(())
}

#[inline]
pub fn load_var(stack: &mut Vec<Value>, vars: &[Value], index: usize) {
    stack.push(vars[index].clone());
}

pub fn store_var<BeforeOverwrite, AfterStore>(
    stack: &mut Vec<Value>,
    vars: &mut Vec<Value>,
    index: usize,
    var_names: &HashMap<usize, String>,
    mut before_overwrite: BeforeOverwrite,
    mut after_store: AfterStore,
) -> Result<(), RuntimeError>
where
    BeforeOverwrite: FnMut(&Value, &Value),
    AfterStore: FnMut(usize, &Value),
{
    let value = pop_value(stack)?;
    if index < vars.len() {
        before_overwrite(&vars[index], &value);
    }
    if index >= vars.len() {
        vars.resize(index + 1, Value::Num(0.0));
        refresh_workspace_state(vars);
    }
    vars[index] = value;
    if let Some(name) = var_names.get(&index) {
        ensure_workspace_slot_name(index, name);
    }
    mark_workspace_assigned(index);
    after_store(index, &vars[index]);
    Ok(())
}

pub fn store_local<BeforeLocalOverwrite, BeforeVarOverwrite, AfterFallbackStore>(
    stack: &mut Vec<Value>,
    context: &mut ExecutionContext,
    vars: &mut Vec<Value>,
    offset: usize,
    mut before_local_overwrite: BeforeLocalOverwrite,
    mut before_var_overwrite: BeforeVarOverwrite,
    mut after_fallback_store: AfterFallbackStore,
) -> Result<(), RuntimeError>
where
    BeforeLocalOverwrite: FnMut(&Value, &Value),
    BeforeVarOverwrite: FnMut(&Value, &Value),
    AfterFallbackStore: FnMut(&str, usize, &Value),
{
    let value = pop_value(stack)?;
    if let Some(current_frame) = context.call_stack.last() {
        let local_index = current_frame.locals_start + offset;
        while context.locals.len() <= local_index {
            context.locals.push(Value::Num(0.0));
        }
        before_local_overwrite(&context.locals[local_index], &value);
        context.locals[local_index] = value;
    } else {
        if offset >= vars.len() {
            vars.resize(offset + 1, Value::Num(0.0));
            refresh_workspace_state(vars);
        }
        before_var_overwrite(&vars[offset], &value);
        vars[offset] = value;
        let func_name = context
            .call_stack
            .last()
            .map(|f| f.function_name.as_str())
            .unwrap_or("<main>");
        after_fallback_store(func_name, offset, &vars[offset]);
    }
    Ok(())
}

#[inline]
pub fn pop(stack: &mut Vec<Value>) {
    let _ = stack.pop();
}

#[inline]
pub fn swap(stack: &mut Vec<Value>) -> Result<(), RuntimeError> {
    let (b, a) = pop2(stack)?;
    stack.push(a);
    stack.push(b);
    Ok(())
}