spl/src/lib.rs
2023-04-08 20:12:35 +02:00

134 lines
4 KiB
Rust

//! # Using SPL as a crate
//!
//! SPL has a complete API for use in applications and libraries.
//! To start a file, use `start_file` with the path, which is relatively straightforward, just like
//! `start_file_in_runtime`, which is the same as `start_file` but doesn't create and set a
//! runtime.
//!
//! To start code more customizably, you will have to create a stack and a runtime yourself, then
//! call `add_std` to include the standard library.
//!
//! Example:
//! ```
//! use spl::*;
//! fn main() -> OError {
//! Runtime::new().set();
//! let mut stack = Stack::new();
//! add_std(&mut stack)?;
//! Words::new(vec![
//! Word::Const(Value::Str("Hello, World!".to_owned())),
//! Word::Call("println".to_owned(), /*pop result:*/ false, /*reference:*/ 0)
//! ]).exec(&mut stack);
//! Ok(())
//! }
//! ```
#![allow(clippy::type_complexity)]
#![allow(clippy::len_without_is_empty)]
pub mod dyn_fns;
pub mod lexer;
pub mod mutex;
pub mod runtime;
pub mod sasm;
pub mod std_fns;
pub mod stdlib;
pub mod stream;
pub use lexer::*;
pub use runtime::*;
use std::fs;
/// Creates a runtime, lexes and executes some SPL code from a file, returning the stack that was
/// used for the operations, which should be empty in most cases.
pub fn start_file(path: &str) -> Result<Stack, Error> {
Runtime::new().set();
(start_file_in_runtime(path), Runtime::reset()).0
}
/// TO START A STANDALONE PIECE OF CODE, USE start_file!!
/// Lexes and starts some SPL code from a file, returning the stack.
pub fn start_file_in_runtime(path: &str) -> Result<Stack, Error> {
let mut stack = Stack::new();
// import stdlib
add_std(&mut stack)?;
// run file
Words::new(vec![
Word::Const(Value::Str(path.to_owned())),
Word::Call("call-main-on-file".to_owned(), false, 0),
])
.exec(&mut stack)?;
Ok(stack)
}
/// Include the standard library in a runtime-stack-pair, where the runtime has been .set().
pub fn add_std(stack: &mut Stack) -> OError {
let f = find_in_splpath("std.spl");
let words = lex(if let Ok(f) = f {
fs::read_to_string(f).unwrap()
} else {
f.unwrap_err()
})
.map_err(|x| stack.error(ErrorKind::LexError(format!("{x:?}"))))?;
words.exec(stack)
}
macro_rules! nofmt {
{$($code:tt)*} => {
$($code)*
};
}
// rustfmt adds infinite indentation to this, incrementing every time it is run.
nofmt! {
#[macro_export]
macro_rules! require_on_stack {
($name:tt, $type:tt, $stack:expr, $fn:literal) => {
let Value::$type($name) = $stack.pop().lock_ro().native.clone() else {
return $stack.err(ErrorKind::InvalidCall($fn.to_owned()))
};
};
}
#[macro_export]
macro_rules! require_int_on_stack {
($name:tt, $stack:expr, $fn:literal) => {
let Value::Int($name) = $stack.pop().lock_ro().native.clone().try_mega_to_int() else {
return $stack.err(ErrorKind::InvalidCall($fn.to_owned()))
};
};
}
#[macro_export]
macro_rules! require_array {
($name:tt, $array:expr, $stack:expr, $fn:literal) => {
let Value::Array(ref $name) = $array.lock_ro().native else {
return $stack.err(ErrorKind::InvalidCall($fn.to_owned()))
};
};
}
#[macro_export]
macro_rules! require_mut_array {
($name:tt, $array:expr, $stack:expr, $fn:literal) => {
let Value::Array(ref mut $name) = $array.lock().native else {
return $stack.err(ErrorKind::InvalidCall($fn.to_owned()))
};
};
}
#[macro_export]
macro_rules! require_array_on_stack {
($name:tt, $stack:expr, $fn:literal) => {
let binding = $stack.pop();
require_array!($name, binding, $stack, $fn)
};
}
#[macro_export]
macro_rules! require_mut_array_on_stack {
($name:tt, $stack:expr, $fn:literal) => {
let binding = $stack.pop();
require_mut_array!($name, binding, $stack, $fn)
};
}
}