#![doc(html_favicon_url = "https://sinesc.github.io/images/itsy/favicon.png")]
#![doc(html_logo_url = "https://sinesc.github.io/images/itsy/logo.png")]
#[path="frontend/frontend.rs"]
#[cfg(feature="compiler")]
mod frontend;
#[macro_use]
#[path="bytecode/bytecode.rs"]
mod bytecode;
#[path="shared/shared.rs"]
mod shared;
mod interface;
mod prelude;
mod config;
pub use interface::*;
use config::*;
mod config_derived {
use crate::{shared::meta::Type, prelude::size_of, StackAddress};
pub type RustFnIndex = crate::ItemIndex;
pub type BuiltinIndex = crate::ItemIndex;
pub type VariantIndex = crate::ItemIndex;
pub const STACK_ADDRESS_TYPE: Type = Type::unsigned(size_of::<StackAddress>());
}
use config_derived::*;
use bytecode::{runtime::vm::VM, runtime::vm::VMState, VMFunc, VMData, Program};
#[cfg(feature="compiler")]
use bytecode::compiler::compile;
#[cfg(feature="compiler")]
use frontend::{parser::parse_module, resolver::resolve};
#[cfg(doc)]
#[macro_export]
macro_rules! itsy_api {
($vis:vis $type_name:ident, $context_type:ty, {
$(
fn $name:tt(&mut $context:ident $(, $arg_name:ident: $($arg_type:tt)+)*) $(-> $ret_type:ident)? $code:block
)*
} ) => { }
}
#[cfg(not(doc))]
#[macro_export]
macro_rules! itsy_api {
(@enum $vis:vis, $type_name:ident $(, $name:tt [ $( $attr:meta ),* ] )* ) => {
#[allow(non_camel_case_types)]
#[derive(Copy, Clone, Debug)]
$vis enum $type_name {
$(
$( #[ $attr ] )*
$name,
)*
#[doc(hidden)]
_dummy
}
};
(@handle_ret $vm:ident, u8, $value:ident) => { $vm.stack.push($value); };
(@handle_ret $vm:ident, u16, $value:ident) => { $vm.stack.push($value); };
(@handle_ret $vm:ident, u32, $value:ident) => { $vm.stack.push($value); };
(@handle_ret $vm:ident, u64, $value:ident) => { $vm.stack.push($value); };
(@handle_ret $vm:ident, i8, $value:ident) => { $vm.stack.push($value); };
(@handle_ret $vm:ident, i16, $value:ident) => { $vm.stack.push($value); };
(@handle_ret $vm:ident, i32, $value:ident) => { $vm.stack.push($value); };
(@handle_ret $vm:ident, i64, $value:ident) => { $vm.stack.push($value); };
(@handle_ret $vm:ident, f32, $value:ident) => { $vm.stack.push($value); };
(@handle_ret $vm:ident, f64, $value:ident) => { $vm.stack.push($value); };
(@handle_ret $vm:ident, bool, $value:ident) => { $vm.stack.push($value as u8); };
(@handle_ret $vm:ident, String, $value:ident) => { {
let raw_bytes = &$value.as_bytes();
let index = $vm.heap.alloc(raw_bytes.to_vec(), $crate::sizes::ItemIndex::MAX);
$vm.stack.push($crate::runtime::heap::HeapRef::new(index, 0));
} };
(@handle_ret $vm:ident, $_:tt, $value:ident) => {
compile_error!("Unsupported return type");
};
(@handle_param $vm:ident, u8) => { $vm.stack.pop() };
(@handle_param $vm:ident, u16) => { $vm.stack.pop() };
(@handle_param $vm:ident, u32) => { $vm.stack.pop() };
(@handle_param $vm:ident, u64) => { $vm.stack.pop() };
(@handle_param $vm:ident, i8) => { $vm.stack.pop() };
(@handle_param $vm:ident, i16) => { $vm.stack.pop() };
(@handle_param $vm:ident, i32) => { $vm.stack.pop() };
(@handle_param $vm:ident, i64) => { $vm.stack.pop() };
(@handle_param $vm:ident, f32) => { $vm.stack.pop() };
(@handle_param $vm:ident, f64) => { $vm.stack.pop() };
(@handle_param $vm:ident, bool) => { { let tmp: u8 = $vm.stack.pop(); tmp != 0 } };
(@handle_param $vm:ident, String) => { $vm.stack.pop() };
(@handle_param $vm:ident, str) => { $vm.stack.pop() };
(@handle_param $vm:ident, $_:tt) => { { compile_error!("Unsupported parameter type") } };
(@handle_param_type String) => { $crate::runtime::heap::HeapRef };
(@handle_param_type str) => { $crate::runtime::heap::HeapRef };
(@handle_param_type $other:ident) => { $other };
(@handle_ref_param $vm:ident, String, $arg_name:ident) => { $vm.heap.string($arg_name).to_string() };
(@handle_ref_param $vm:ident, str, $arg_name:ident) => { $vm.heap.string($arg_name) };
(@handle_ref_param $vm:ident, $other:ident, $arg_name:ident) => { $arg_name };
(@handle_ref_param_type str) => { &str }; (@handle_ref_param_type $other:ident) => { $other };
(@handle_ref_param_free $vm:ident, String, $arg_name:ident) => { $vm.heap.ref_item($arg_name.index(), $crate::runtime::heap::HeapRefOp::Free) };
(@handle_ref_param_free $vm:ident, str, $arg_name:ident) => { $vm.heap.ref_item($arg_name.index(), $crate::runtime::heap::HeapRefOp::Free) };
(@handle_ref_param_free $vm:ident, $other:ident, $arg_name:ident) => { };
(@load_args_reverse $vm:ident [] $($arg_name:ident $arg_type:ident)*) => {
$(
let $arg_name: itsy_api!(@handle_param_type $arg_type) = itsy_api!(@handle_param $vm, $arg_type);
)*
};
(@load_args_reverse $vm:ident [ $first_arg_name:ident $first_arg_type:ident $($rest:tt)* ] $($reversed:tt)*) => {
itsy_api!(@load_args_reverse $vm [ $($rest)* ] $first_arg_name $first_arg_type $($reversed)*)
};
(@trait $type_name:ident, $context_type:ty $(, $name:tt, $context:ident [ $( $arg_name:ident : $arg_type:ident , )* ] [ $($ret_type:ident)? ] $code:block )* ) => {
impl $crate::runtime::VMFunc<$type_name> for $type_name {
fn into_index(self: Self) -> $crate::sizes::RustFnIndex {
self as $crate::sizes::RustFnIndex
}
fn from_index(index: $crate::sizes::RustFnIndex) -> Self {
match index {
$(
x if x == Self::$name as $crate::sizes::RustFnIndex => Self::$name,
)+
_ => panic!("Invalid VMFunc index {}", index),
}
}
#[allow(unused_mut)]
fn resolve_info() -> ::std::collections::HashMap<&'static str, ($crate::sizes::RustFnIndex, &'static str, Vec<&'static str>)> {
let mut map = ::std::collections::HashMap::new();
$(
map.insert(stringify!($name), ($type_name::$name.into_index(), stringify!($($ret_type)?), vec![ $(stringify!( $arg_type )),* ]));
)*
map
}
}
impl $crate::runtime::VMData<$type_name, $context_type> for $type_name {
#[inline(always)]
#[allow(unused_variables, unused_assignments, unused_imports)]
fn exec(self: Self, vm: &mut $crate::runtime::VM<$type_name, $context_type>, context: &mut $context_type) {
use $crate::runtime::stack::StackOp;
match self {
$(
$type_name::$name => {
let $context = context;
itsy_api!(@load_args_reverse vm [ $( $arg_name $arg_type )* ]);
let ret = {
$(
let $arg_name: itsy_api!(@handle_ref_param_type $arg_type) = itsy_api!(@handle_ref_param vm, $arg_type, $arg_name);
)*
$code
};
$(
itsy_api!(@handle_ref_param_free vm, $arg_type, $arg_name);
)*
$(
let ret_typed: $ret_type = ret;
itsy_api!(@handle_ret vm, $ret_type, ret_typed);
)?
},
)*
$type_name::_dummy => panic!("Attempted to execute dummy function")
}
}
}
};
(
$vis:vis $type_name:ident, $context_type:ty, { $(
$( #[ $attr:meta ] )*
fn $name:tt ( & mut $context:ident $( , $arg_name:ident : $( & )? $arg_type:ident )* ) $( -> $ret_type:ident )? $code:block
)* }
) => {
itsy_api!(@enum $vis, $type_name $(, $name [ $( $attr ),* ] )* );
itsy_api!(@trait $type_name, $context_type $(, $name, $context [ $( $arg_name : $arg_type , )* ] [ $( $ret_type )? ] $code )* );
};
}
#[cfg(feature="compiler")]
pub fn build_str<F>(source: &str) -> Result<Program<F>, Error> where F: VMFunc<F> {
use crate::frontend::parser::{types::ParsedProgram, error::ParseError, error::ParseErrorKind};
let parsed = parse_module(source, "")?;
if let Some(module) = parsed.modules().next() {
return Err(Error::ParseError(ParseError::new(
ParseErrorKind::DisabledFeature("build_str() does not support module loading. Please use either build() or parser::parse() and provide a module loader"),
module.position,
"",
)));
}
let mut program = ParsedProgram::new();
program.add_module(parsed);
let resolved = resolve::<F>(program, "main")?;
Ok(compile(resolved)?)
}
#[cfg(feature="compiler")]
pub fn build<F, P>(source_file: P) -> Result<Program<F>, BuildError> where F: VMFunc<F>, P: AsRef<std::path::Path> {
let source_file = source_file.as_ref();
let mut files = std::collections::HashMap::new();
match build_inner(source_file, &mut files) {
Ok(program) => Ok(program),
Err(error) => {
let (filename, source) = files.remove(error.module_path()).unwrap();
Err(BuildError { error, filename, source })
}
}
}
#[cfg(feature="compiler")]
fn build_inner<F>(source_file: &std::path::Path, files: &mut std::collections::HashMap<String, (std::path::PathBuf, String)>) -> Result<Program<F>, Error> where F: VMFunc<F> {
let parsed = parser::parse(|module_path| {
let filename = parser::module_filename(source_file, module_path);
let file = std::fs::read_to_string(&filename).unwrap();
let module = parser::parse_module(&file, module_path);
files.insert(module_path.to_string(), (filename, file));
module
})?;
let resolved = resolve::<F>(parsed, "main")?;
Ok(compile(resolved)?)
}
pub fn run<F, D>(program: &Program<F>, context: &mut D) -> Result<VM<F, D>, Error> where F: VMFunc<F> + VMData<F, D> {
let mut vm = VM::new(program);
match vm.run(context) {
VMState::Ready => Err(Error::RuntimeError), VMState::Terminated => Ok(vm),
VMState::Yielded => Ok(vm),
VMState::RuntimeError => Err(Error::RuntimeError),
}
}