rustscript-embedded 0.3.2

Embedded RustScript VMBC runtime with a no_std host-function ABI
Documentation
use std::fmt;
use std::path::Path;

pub use vm::Value;
use vm::{
    JitConfig, SourceMap, Vm, VmStatus, compile_source, compile_source_file,
    compile_source_for_repl, render_source_error,
};

#[derive(Debug, Clone, PartialEq)]
pub enum RunOutcome {
    Halted { stack: Vec<Value> },
}

#[derive(Debug)]
pub enum EmbeddedError {
    Compile(String),
    Runtime(String),
    Io(std::io::Error),
}

impl fmt::Display for EmbeddedError {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self {
            Self::Compile(message) | Self::Runtime(message) => f.write_str(message),
            Self::Io(err) => write!(f, "{err}"),
        }
    }
}

impl std::error::Error for EmbeddedError {}

impl From<std::io::Error> for EmbeddedError {
    fn from(err: std::io::Error) -> Self {
        Self::Io(err)
    }
}

pub type EmbeddedResult<T> = Result<T, EmbeddedError>;

pub fn run_source(source: &str) -> EmbeddedResult<RunOutcome> {
    let compiled = compile_source(source)
        .map_err(|err| render_source_compile_error("<memory>", source, err))?;
    run_program(compiled.program.with_local_count(compiled.locals))
}

pub fn run_source_file(path: impl AsRef<Path>) -> EmbeddedResult<RunOutcome> {
    let path = path.as_ref();
    let compiled =
        compile_source_file(path).map_err(|err| EmbeddedError::Compile(err.to_string()))?;
    run_program(compiled.program.with_local_count(compiled.locals))
}

pub fn eval_repl_entry(source: &str) -> EmbeddedResult<RunOutcome> {
    let compiled = compile_source_for_repl(source)
        .map_err(|err| render_source_compile_error("<repl>", source, err))?;
    run_program(compiled.program.with_local_count(compiled.locals))
}

pub fn embedded_jit_config() -> JitConfig {
    JitConfig {
        enabled: false,
        ..JitConfig::default()
    }
}

pub fn new_vm(program: vm::Program) -> Vm {
    let mut vm = Vm::new_with_jit_config(program, embedded_jit_config());
    vm.set_runtime_print_sink(|rendered| {
        print!("{rendered}");
    });
    vm
}

pub fn render_value(value: &Value) -> String {
    vm::format_value(value)
}

fn run_program(program: vm::Program) -> EmbeddedResult<RunOutcome> {
    let mut vm = new_vm(program);
    loop {
        match vm.run() {
            Ok(VmStatus::Halted) => {
                return Ok(RunOutcome::Halted {
                    stack: vm.stack().to_vec(),
                });
            }
            Ok(VmStatus::Yielded) => continue,
            Ok(VmStatus::Waiting(_)) => vm
                .wait_for_host_op_blocking()
                .map_err(|err| EmbeddedError::Runtime(vm::render_vm_error(&vm, &err)))?,
            Err(err) => return Err(EmbeddedError::Runtime(vm::render_vm_error(&vm, &err))),
        }
    }
}

fn render_source_compile_error(path: &str, source: &str, err: vm::SourceError) -> EmbeddedError {
    match err {
        vm::SourceError::Parse(parse) => {
            let mut source_map = SourceMap::new();
            let source_id = source_map.add_source(path.to_string(), source.to_string());
            let parse = parse.with_line_span_from_source(&source_map, source_id);
            EmbeddedError::Compile(render_source_error(&source_map, &parse, true))
        }
        vm::SourceError::Compile(compile) => {
            let mut source_map = SourceMap::new();
            source_map.add_source(path.to_string(), source.to_string());
            EmbeddedError::Compile(vm::render_compile_error(&source_map, &compile, true))
        }
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn jit_is_disabled() {
        assert!(!embedded_jit_config().enabled);
    }

    #[test]
    fn runs_basic_program() {
        let _ = eval_repl_entry("print(1 + 2);").expect("program should run");
    }
}