mod bytecode;
mod call_frame;
mod config;
mod constants;
mod error;
mod func;
mod hash_map;
mod instruction_stream;
mod intrinsics;
mod list;
mod native_func;
mod loading;
mod operations;
mod stack;
mod string;
mod tagged_value;
mod type_objects;
mod value;
mod vm;
#[cfg(test)]
mod tests;
use std::io::Write;
use std::io::Read;
use crate::codegen::Func as CodegenFunc;
use crate::compile::compile;
use crate::parser::ParseError;
use crate::symbol_map::SymbolMap;
use self::error::RuntimeErrorKind as ErrorKind;
use self::loading::load_program;
use self::vm::ExitCode;
use sandpit::*;
pub use config::Config;
pub use self::error::{RuntimeError, RuntimeErrorKind, Backtrace, BacktraceCall};
pub use bytecode::ByteCode;
pub use vm::VM;
pub use value::Value;
pub use tagged_value::{TaggedValue, ValueTag};
pub use func::Func;
pub use list::List;
pub use string::VMString;
pub use hash_map::GcHashMap;
pub use native_func::{NativeFunc, NativeFn};
#[derive(Debug)]
pub enum InterpreterError {
RuntimeError(RuntimeError),
ParseError(ParseError),
}
impl InterpreterError {
pub fn render(&self) -> String {
match self {
InterpreterError::RuntimeError(err) => err.render(),
InterpreterError::ParseError(err) => err.render(),
}
}
}
impl From<ParseError> for InterpreterError {
fn from(value: ParseError) -> Self {
Self::ParseError(value)
}
}
pub struct Runtime {
arena: Arena<Root![VM<'_>]>,
syms: SymbolMap,
config: Config,
}
impl Runtime {
pub fn init(syms: SymbolMap, config: Config) -> Result<Self, InterpreterError> {
crate::macros::instrument!(crate::benchmark::Action::Start);
let arena = Arena::new(|mu| VM::new(mu));
let mut runtime = Runtime {
arena,
syms,
config,
};
match runtime.load_std() {
Err(err) => {
eprintln!("ERROR: failed to load std lib");
Err(err)
}
Ok(_) => Ok(runtime)
}
}
fn load_std(&mut self) -> Result<(), InterpreterError> {
let std_lib_path =
if let Some(arg) = &self.config.std_lib {
if let Some(path) = arg {
path.clone()
} else {
return Ok(());
}
} else {
#[cfg(not(debug_assertions))]
{
let home = std::env::var("HOME")
.or_else(|_| std::env::var("USERPROFILE")) .unwrap_or_else(|_| ".".to_string());
format!("{home}/.nilang/std/main.nl")
}
#[cfg(debug_assertions)]
{
"./std/main.nl".to_string()
}
};
self.load_module(&std_lib_path)?;
self.run()?;
Ok(())
}
pub fn run(&mut self) -> Result<(), InterpreterError> {
#[cfg(feature = "benchmark")]
crate::benchmark::register_sandpit_metrics(self.arena.metrics());
if self.config.dry_run {
println!("DRY RUN: program compiled successfully");
return Ok(());
}
let mut vm_result = Ok(ExitCode::Yield);
let mut output = self.config.get_output();
#[cfg(feature = "benchmark")]
let mut vm_exit_time: Option<std::time::Instant> = None;
loop {
crate::macros::instrument!(crate::benchmark::Action::IncrementDispatchLoops);
self.arena.mutate(|mu, vm| {
#[cfg(feature = "benchmark")]
if let Some(t) = vm_exit_time {
let gap_ns = t.elapsed().as_nanos() as u64;
crate::macros::instrument!(crate::benchmark::Action::RecordGcPauseNs(gap_ns));
crate::benchmark::record_gc_metrics(self.arena.metrics());
}
crate::macros::instrument_timed!(DISPATCH_TIME_US, {
vm_result = vm.run(mu, &mut self.syms);
});
#[cfg(feature = "benchmark")]
{
vm_exit_time = Some(std::time::Instant::now());
}
});
match vm_result {
Ok(ExitCode::Exit) => {
crate::macros::instrument!(crate::benchmark::Action::WriteReport(
self.config.file.as_deref().unwrap_or("inline")
));
return Ok(());
},
Ok(ExitCode::Yield) => {}
Ok(ExitCode::Print) => self.print(&mut output)?,
Ok(ExitCode::LoadModule(ref path)) => self.load_module(path)?,
Ok(ExitCode::LoadSo(ref path)) => self.load_so(path)?,
Ok(ExitCode::Read) => self.read_input()?,
Err(err) => return Err(InterpreterError::RuntimeError(err)),
}
}
}
pub fn clear_program(&mut self) {
self.arena.mutate(|_mu, vm| {
vm.clear_stack()
});
}
pub fn load_inline(&mut self, source: &str) -> Result<(), InterpreterError> {
match self.compile_with_config(source, None) {
Err(err) => {
return Err(InterpreterError::ParseError(err));
}
Ok(program) => self.load_program(program, None)?,
}
Ok(())
}
pub fn load_module(&mut self, path: &String) -> Result<(), InterpreterError> {
match std::fs::read_to_string(path) {
Ok(module) => match self.compile_with_config(&module, Some(path.clone())) {
Err(err) => Err(InterpreterError::ParseError(err)),
Ok(program) => {
self.load_program(program, Some(path))?;
Ok(())
}
}
Err(fs_err) => {
let runtime_error = RuntimeError::new(
ErrorKind::FailedImport,
Some(fs_err.to_string()),
None, );
Err(InterpreterError::RuntimeError(runtime_error))
}
}
}
fn load_so(&mut self, path: &str) -> Result<(), InterpreterError> {
unsafe {
let lib = libloading::Library::new(path).map_err(|e| {
InterpreterError::RuntimeError(RuntimeError::new(
ErrorKind::FailedImport,
Some(e.to_string()),
None,
))
})?;
let init: libloading::Symbol<NativeFn> = lib.get(b"nilang_init").map_err(|e| {
InterpreterError::RuntimeError(RuntimeError::new(
ErrorKind::FailedImport,
Some(format!("missing nilang_init symbol: {}", e)),
None,
))
})?;
let init_fn: NativeFn = *init;
std::mem::forget(lib);
let mut result = Ok(());
self.arena.mutate(|mu, vm| {
match init_fn(&[], mu) {
Ok(value) => {
if let Err(err) = vm.set_import_return_value(mu, value) {
result = Err(InterpreterError::RuntimeError(err));
}
}
Err(err) => {
result = Err(InterpreterError::RuntimeError(err));
}
}
});
result
}
}
fn load_program(&mut self, program: Vec<CodegenFunc>, path: Option<&str>) -> Result<(), InterpreterError> {
crate::macros::instrument_timed!(VM_LOAD_TIME_US, {
let mut result = Ok(());
self.arena.mutate(|mu, vm| {
let loaded_program = load_program(program, path, mu);
let module_func = loaded_program.last().unwrap().clone();
if let Err(err) = vm.load_module(mu, module_func) {
result = Err(InterpreterError::RuntimeError(err));
}
});
result
})
}
pub fn print(&mut self, output: &mut impl Write) -> Result<(), InterpreterError> {
self.arena.view(|vm| {
vm.write_output(output, &mut self.syms).expect("writing failed");
});
Ok(())
}
fn compile_with_config(&mut self, source: &str, path: Option<String>) -> Result<Vec<CodegenFunc>, ParseError> {
compile(
&mut self.syms,
source,
path,
!self.config.no_optimize,
self.config.pretty_ir,
self.config.ast_output_path.clone(),
self.config.ir_output_path.clone(),
self.config.bytecode_output_path.clone()
)
}
fn read_input(&self) -> Result<(), InterpreterError> {
use std::io::IsTerminal;
let mut stdin = std::io::stdin();
let mut buf = String::new();
let mut vm_result = Ok(());
if stdin.is_terminal() {
stdin.read_line(&mut buf).expect("failed to read from stdin");
} else {
stdin.read_to_string(&mut buf).expect("failed to read from stdin");
}
buf = buf.to_string();
self.arena.mutate(|mu, vm| {
if let Err(err) = vm.read_input_hook(buf, mu) {
vm_result = Err(InterpreterError::RuntimeError(err));
}
});
vm_result
}
}