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#[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 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 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 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 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 InternedSTL,
166}
167
168pub 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