spl/
lib.rs

1//! # Using SPL as a crate
2//!
3//! SPL has a complete API for use in applications and libraries.
4//! To start a file, use `start_file` with the path, which is relatively straightforward, just like
5//! `start_file_in_runtime`, which is the same as `start_file` but doesn't create and set a
6//! runtime.
7//!
8//! To start code more customizably, you will have to create a stack and a runtime yourself, then
9//! call `add_std` to include the standard library.
10//!
11//! Example:
12//! ```
13//! use spl::*;
14//! fn main() -> OError {
15//!     Runtime::new().set();
16//!     let mut stack = Stack::new();
17//!     add_std(&mut stack)?;
18//!     Words::new(vec![
19//!         Word::Const(Value::Str("Hello, World!".to_owned())),
20//!         Word::Call("println".to_owned(), /*pop result:*/ false, /*reference:*/ 0)
21//!     ]).exec(&mut stack);
22//!     Ok(())
23//! }
24//! ```
25
26#![allow(clippy::type_complexity)]
27#![allow(clippy::len_without_is_empty)]
28
29pub mod dyn_fns;
30pub mod lexer;
31pub mod mutex;
32pub mod oxidizer;
33pub mod runtime;
34pub mod sasm;
35pub mod std_fns;
36pub mod stdlib;
37pub mod stream;
38
39pub use lexer::*;
40pub use runtime::*;
41
42use std::fs;
43
44/// Creates a runtime, lexes and executes some SPL code from a file, returning the stack that was
45/// used for the operations, which should be empty in most cases.
46pub fn start_file(path: &str) -> Result<Stack, Error> {
47    Runtime::new().set();
48    (start_file_in_runtime(path), Runtime::reset()).0
49}
50
51/// TO START A STANDALONE PIECE OF CODE, USE start_file!!
52/// Lexes and starts some SPL code from a file, returning the stack.
53pub fn start_file_in_runtime(path: &str) -> Result<Stack, Error> {
54    let mut stack = Stack::new();
55    // import stdlib
56    add_std(&mut stack)?;
57
58    if path.ends_with(".isbpl") {
59        Words::new(vec![
60            Word::Const(Value::Str("#isbpl.spl".to_owned())),
61            Word::Call("import".to_owned(), false, 0),
62        ])
63        .exec(&mut stack)?;
64    }
65
66    // run file
67    Words::new(vec![
68        Word::Const(Value::Str(path.to_owned())),
69        Word::Call("call-main-on-file".to_owned(), false, 0),
70    ])
71    .exec(&mut stack)?;
72
73    Ok(stack)
74}
75
76/// Include the standard library in a runtime-stack-pair, where the runtime has been .set().
77pub fn add_std(stack: &mut Stack) -> OError {
78    let f = find_in_splpath("std.spl");
79    let words = match f {
80        Ok(f) => lex(f.ends_with(".isbpl"), fs::read_to_string(f).unwrap()),
81        Err(content) => lex(false, content),
82    }
83    .map_err(|x| stack.error(ErrorKind::LexError(format!("{x:?}"))))?;
84    words.exec(stack)
85}
86
87macro_rules! nofmt {
88    {$($code:tt)*} => {
89        $($code)*
90    };
91}
92
93// rustfmt adds infinite indentation to this, incrementing every time it is run.
94nofmt! {
95    #[macro_export]
96    macro_rules! require_on_stack {
97        ($name:tt, $type:tt, $stack:expr, $fn:literal) => {
98            let Value::$type($name) = $stack.pop().lock_ro().native.clone() else {
99                return $stack.err(ErrorKind::InvalidCall($fn.to_owned()))
100            };
101        };
102    }
103
104    #[macro_export]
105    macro_rules! require_mut_on_stack {
106        ($name:tt, $type:tt, $stack:expr, $fn:literal) => {
107            let binding = $stack.pop();
108            let Value::$type(ref mut $name) = binding.lock().native else {
109                return $stack.err(ErrorKind::InvalidCall($fn.to_owned()))
110            };
111        };
112    }
113    #[macro_export]
114    macro_rules! require_mut {
115        ($name:tt, $type:tt, $binding:expr, $stack:expr, $fn:literal) => {
116            let Value::$type(ref mut $name) = $binding.lock().native else {
117                return $stack.err(ErrorKind::InvalidCall($fn.to_owned()))
118            };
119        };
120    }
121
122    #[macro_export]
123    macro_rules! require_int_on_stack {
124        ($name:tt, $stack:expr, $fn:literal) => {
125            let Value::Int($name) = $stack.pop().lock_ro().native.clone().try_mega_to_int() else {
126                return $stack.err(ErrorKind::InvalidCall($fn.to_owned()))
127            };
128        };
129    }
130    #[macro_export]
131    macro_rules! require_array {
132        ($name:tt, $array:expr, $stack:expr, $fn:literal) => {
133            let Value::Array(ref $name) = $array.lock_ro().native else {
134                return $stack.err(ErrorKind::InvalidCall($fn.to_owned()))
135            };
136        };
137    }
138    #[macro_export]
139    macro_rules! require_mut_array {
140        ($name:tt, $array:expr, $stack:expr, $fn:literal) => {
141            let Value::Array(ref mut $name) = $array.lock().native else {
142                return $stack.err(ErrorKind::InvalidCall($fn.to_owned()))
143            };
144        };
145    }
146    #[macro_export]
147    macro_rules! require_array_on_stack {
148        ($name:tt, $stack:expr, $fn:literal) => {
149            let binding = $stack.pop();
150            require_array!($name, binding, $stack, $fn)
151        };
152    }
153    #[macro_export]
154    macro_rules! require_byte_array_on_stack {
155        ($name:tt, $stack:expr, $fn:literal) => {
156            let binding = $stack.pop();
157            let $name = match binding.lock_ro().native {
158                Value::Array(ref x) => x
159                    .iter()
160                    .cloned()
161                    .map(|x| {
162                        Ok(match &x.lock_ro().native {
163                            Value::Int(x) => *x as u8,
164                            Value::Long(x) => *x as u8,
165                            Value::Mega(x) => *x as u8,
166                            _ => $stack.err(ErrorKind::InvalidType(
167                                x.lock_ro().kind.lock_ro().get_name(),
168                                "byte".to_owned(),
169                            ))?,
170                        })
171                    })
172                    .collect::<Result<Vec<_>, _>>()?,
173                Value::ByteArray(ref x) => x.clone(),
174                _ => return $stack.err(ErrorKind::InvalidCall($fn.to_owned())),
175            };
176        };
177    }
178    #[macro_export]
179    macro_rules! require_mut_array_on_stack {
180        ($name:tt, $stack:expr, $fn:literal) => {
181            let binding = $stack.pop();
182            require_mut_array!($name, binding, $stack, $fn)
183        };
184    }
185}