regulus/
lib.rs

1#![warn(
2    clippy::nursery,
3    clippy::pedantic,
4    clippy::print_stdout,
5    clippy::print_stderr,
6    clippy::dbg_macro,
7    clippy::allow_attributes
8)]
9#![allow(
10    clippy::missing_errors_doc,
11    clippy::option_if_let_else,
12    clippy::must_use_candidate
13)]
14
15mod argument;
16mod atom;
17mod exception;
18mod function;
19mod macros;
20mod parsing;
21mod state;
22
23mod builtins;
24
25#[rustfmt::skip]
26mod interned_stdlib;
27
28pub mod prelude {
29    pub use crate::{
30        Runner,
31        argument::{Argument, ArgumentData},
32        atom::Atom,
33        exception::{Error, Exception, Result},
34        function::{Function, FunctionCall, FunctionInner},
35        functions, raise, run,
36        state::State,
37    };
38}
39
40use crate::{
41    atom::Atom,
42    exception::Result,
43    parsing::{build_program, tokenize, validate_tokens},
44    state::State,
45};
46use std::path::{Path, PathBuf};
47use std::{env, fs, io};
48
49pub const FILE_EXTENSION: &str = "re";
50
51/// A set of options required for running a Regulus program.
52///
53/// Only `code` must be specified,
54/// `current_dir` and `starting_state` have default values.
55#[must_use]
56pub struct Runner {
57    code: Option<String>,
58    current_dir: Directory,
59    starting_state: Option<State>,
60}
61
62impl Default for Runner {
63    fn default() -> Self {
64        Self::new()
65    }
66}
67
68impl Runner {
69    pub const fn new() -> Self {
70        Self {
71            code: None,
72            current_dir: Directory::InternedSTL,
73            starting_state: None,
74        }
75    }
76
77    /// Sets both the code and the current directory by reading from the given file path.
78    ///
79    /// # Errors
80    /// Returns an error if reading from the file failed.
81    ///
82    /// # Panics
83    /// Panics if the path is invalid.
84    pub fn file(mut self, path: impl AsRef<Path>) -> io::Result<Self> {
85        self.code = Some(fs::read_to_string(&path)?);
86        let mut current_dir = path.as_ref().parent().unwrap().to_path_buf();
87        if current_dir == PathBuf::new() {
88            current_dir = PathBuf::from(".");
89        }
90        self.current_dir = Directory::Regular(current_dir);
91        Ok(self)
92    }
93
94    pub fn code(mut self, code: impl AsRef<str>) -> Self {
95        self.code = Some(code.as_ref().to_owned());
96        self
97    }
98
99    /// Sets the current directory to the given directory path.
100    ///
101    /// This is used to resolve imports of other local files in the same directory.
102    pub fn current_dir(mut self, dir_path: impl AsRef<Path>) -> Self {
103        self.current_dir = Directory::Regular(dir_path.as_ref().to_path_buf());
104        self
105    }
106
107    /// Sets the current directory to the operating systems current working directory.
108    ///
109    /// # Panics
110    /// Panics if [`env::current_dir`] returned an error.
111    pub fn with_cwd(self) -> Self {
112        self.current_dir(env::current_dir().unwrap())
113    }
114
115    pub fn starting_state(mut self, state: State) -> Self {
116        self.starting_state = Some(state);
117        self
118    }
119
120    /// Run the program specified by this configuration.
121    ///
122    /// Returns the result the program returned and the final state.
123    ///
124    /// If `starting_state` is specified, it overrides `current_dir`.
125    ///
126    /// # Panics
127    /// Panics if the configuration is invalid.
128    /// This happens if one of the following cases occurs:
129    /// * `code` is missing
130    pub fn run(self) -> (Result<Atom>, State) {
131        let code = self.code.expect("code is required");
132        let mut state = self
133            .starting_state
134            .unwrap_or_else(|| State::initial_with_dir(self.current_dir));
135
136        macro_rules! return_err {
137            ($val: expr) => {
138                match $val {
139                    Ok(ok) => ok,
140                    Err(err) => return (Err(err), state),
141                }
142            };
143        }
144
145        let tokens = return_err!(tokenize(&code));
146
147        return_err!(validate_tokens(&tokens));
148
149        let program = return_err!(build_program(&tokens, "_"));
150
151        let result = return_err!(program.eval(&mut state));
152
153        if let Some(exit_unwind_value) = &state.exit_unwind_value {
154            return (exit_unwind_value.clone(), state);
155        }
156
157        (Ok(result), state)
158    }
159}
160
161#[derive(Clone)]
162pub(crate) enum Directory {
163    Regular(PathBuf),
164    /// Should only be used internally.
165    InternedSTL,
166}
167
168/// A convenient helper for directly running one file program.
169///
170/// Returns only the result of running the program, not the final state.
171///
172/// For more options, use [`Runner`] instead.
173///
174/// # Panics
175/// Panics if the path is invalid or cannot be read from.
176pub fn run(path: impl AsRef<Path>) -> Result<Atom> {
177    Runner::new().file(path).unwrap().run().0
178}
179
180#[cfg(test)]
181mod tests {
182    use super::*;
183
184    #[test]
185    fn bare_value_program_return() {
186        assert_eq!(
187            Runner::new().code("_(4)").run().0.unwrap().int().unwrap(),
188            4
189        );
190        assert_eq!(Runner::new().code("4").run().0.unwrap().int().unwrap(), 4);
191        assert_eq!(
192            Runner::new()
193                .code("=(x, 4), x")
194                .run()
195                .0
196                .unwrap()
197                .int()
198                .unwrap(),
199            4
200        );
201    }
202}
203/*
204use std::sync::atomic::AtomicUsize;
205use std::sync::atomic::Ordering::Relaxed;
206
207pub static STRING_CLONE_COUNT: AtomicUsize = AtomicUsize::new(0);
208pub static LIST_CLONE_COUNT: AtomicUsize = AtomicUsize::new(0);
209pub static FUNCTION_CLONE_COUNT: AtomicUsize = AtomicUsize::new(0);
210pub static OBJECT_CLONE_COUNT: AtomicUsize = AtomicUsize::new(0);
211
212pub fn clone_investigate(atom: &Atom) {
213    match atom {
214        Atom::String(_) => {
215            STRING_CLONE_COUNT.fetch_add(1, Relaxed);
216        }
217        Atom::List(_) => {
218            LIST_CLONE_COUNT.fetch_add(1, Relaxed);
219        }
220        Atom::Function(_) => {
221            FUNCTION_CLONE_COUNT.fetch_add(1, Relaxed);
222        }
223        Atom::Object(_) => {
224            OBJECT_CLONE_COUNT.fetch_add(1, Relaxed);
225        }
226        Atom::Null | Atom::Int(_) | Atom::Bool(_) => (),
227    }
228}
229*/