1use std::sync::LazyLock;
2pub mod cargo_cmds;
3use cargo_cmds::*;
4mod executor;
5pub use executor::Executor;
6mod toolchain;
7pub use toolchain::ToolChain;
8mod main_result;
9pub use main_result::MainResult;
10mod edition;
11pub use edition::Edition;
12mod compile_mode;
13pub use compile_mode::CompileMode;
14
15mod utils;
16
17use std::{
18 io::{self, Write},
19 path::PathBuf,
20 process::{Child, ExitStatus},
21};
22
23type Result<T> = std::result::Result<T, Box<dyn std::error::Error>>;
24
25pub static DEFAULT_EVALUATOR: LazyLock<[String; 2]> =
26 LazyLock::new(|| ["println!(\"{:?}\", {\n".into(), "\n});".into()]);
27
28pub struct EvalConfig<'a, S: ToString> {
29 pub input: S,
30 pub interactive_function: Option<fn(&mut Child) -> Result<()>>,
31 pub color: bool,
32 pub evaluator: &'a [String],
33 pub compile_mode: CompileMode,
34}
35
36#[derive(Debug)]
37pub struct EvalResult {
38 pub output: String,
39 pub status: ExitStatus,
40}
41
42impl From<(ExitStatus, String)> for EvalResult {
43 fn from(result: (ExitStatus, String)) -> Self {
44 Self {
45 output: result.1,
46 status: result.0,
47 }
48 }
49}
50
51#[derive(Debug, Clone)]
52pub struct Repl {
53 body: Vec<String>,
54 cursor: usize,
55 toolchain: ToolChain,
56 executor: Executor,
57 main_result: MainResult,
58 edition: Edition,
59 prelude: Option<PathBuf>,
60 pub cargo: Cargo,
61}
62impl Default for Repl {
63 fn default() -> Self {
64 Repl::new(
65 ToolChain::default(),
66 Executor::default(),
67 MainResult::default(),
68 Edition::default(),
69 None,
70 )
71 .expect("Paniced while trying to create repl")
72 }
73}
74impl Drop for Repl {
75 fn drop(&mut self) {
76 let _ = self.cargo.delete_project();
77 }
78}
79
80const PRELUDE_NAME: &str = "irust_prelude";
81
82impl Repl {
83 pub fn new(
84 toolchain: ToolChain,
85 executor: Executor,
86 main_result: MainResult,
87 edition: Edition,
88 prelude_parent_path: Option<PathBuf>,
89 ) -> Result<Self> {
90 let cargo = Cargo::default();
91 cargo.cargo_new(edition)?;
93 if let Some(ref path) = prelude_parent_path {
94 cargo.cargo_new_lib_simple(path, PRELUDE_NAME)?;
95 cargo.cargo_add_prelude(path.join(PRELUDE_NAME), PRELUDE_NAME)?;
96 }
97 if let Some(dependecy) = executor.dependecy() {
99 cargo.cargo_add_sync(&dependecy)?;
105 }
106 cargo.cargo_build(toolchain)?;
107
108 let (header, footer) = Self::generate_body_delimiters(executor, main_result);
109 let (body, cursor) = if prelude_parent_path.is_some() {
110 (
111 vec![
112 header,
113 format!("#[allow(unused_imports)]use {PRELUDE_NAME}::*;"),
114 footer,
115 "}".to_string(),
116 ],
117 2,
118 )
119 } else {
120 (vec![header, footer, "}".to_string()], 1)
121 };
122 Ok(Self {
123 body,
124 cursor,
125 toolchain,
126 executor,
127 main_result,
128 edition,
129 prelude: prelude_parent_path,
130 cargo,
131 })
132 }
133
134 fn generate_body_delimiters(executor: Executor, main_result: MainResult) -> (String, String) {
135 (
136 executor.main() + " -> " + main_result.ttype() + "{",
137 "#[allow(unreachable_code)]".to_string()
138 + main_result.instance()
139 + " // Do not write past this line (it will corrupt the repl)",
140 )
141 }
142
143 pub fn set_executor(&mut self, executor: Executor) -> Result<()> {
144 if let Some(dependecy) = self.executor.dependecy() {
146 self.cargo.cargo_rm_sync(&dependecy[0])?;
148 }
149
150 self.executor = executor;
152 if let Some(dependecy) = executor.dependecy() {
154 self.cargo.cargo_add_sync(&dependecy)?;
155 }
156 let (header, footer) = Self::generate_body_delimiters(self.executor, self.main_result);
158 let footer_pos = self.body.len() - 2;
159 self.body[0] = header;
160 self.body[footer_pos] = footer;
161 Ok(())
162 }
163
164 pub fn update_from_extern_main_file(&mut self) -> Result<()> {
165 let main_file = std::fs::read_to_string(&self.cargo.paths.main_file_extern)?;
166 let lines_num = main_file.lines().count();
167 if lines_num < 2 {
168 return Err("main.rs file corrupted, resetting irust..".into());
169 }
170 let cursor_pos = lines_num - 2;
171
172 self.body = main_file.lines().map(ToOwned::to_owned).collect();
173 self.cursor = cursor_pos;
174 Ok(())
175 }
176
177 pub fn hard_load(&mut self, code: impl ToString, cursor: usize) {
178 self.body = code.to_string().lines().map(ToOwned::to_owned).collect();
179 self.cursor = cursor;
180 }
181
182 pub fn insert(&mut self, input: impl ToString) {
186 let input = input.to_string();
187 const CRATE_ATTRIBUTE: &str = "#!";
191
192 let outside_main = input.trim_start().starts_with(CRATE_ATTRIBUTE);
193 if outside_main {
194 for line in input.lines() {
195 self.body.insert(0, line.to_owned());
196 self.cursor += 1;
197 }
198 } else {
199 for line in input.lines() {
200 self.body.insert(self.cursor, line.to_owned());
201 self.cursor += 1;
202 }
203 }
204 }
205
206 pub fn reset(&mut self) -> Result<()> {
207 *self = Self::new(
208 self.toolchain,
209 self.executor,
210 self.main_result,
211 self.edition,
212 self.prelude.clone(),
213 )?;
214 Ok(())
215 }
216
217 pub fn show(&self) -> String {
218 let mut current_code = self.body.join("\n");
219 if let Ok(fmt_code) = self.cargo.cargo_fmt(¤t_code) {
221 current_code = fmt_code;
222 }
223 format!("Current Repl Code:\n{current_code}")
224 }
225
226 pub fn eval(&mut self, input: impl ToString) -> Result<EvalResult> {
227 self.eval_inner(input, None, false, &*DEFAULT_EVALUATOR, CompileMode::Debug)
228 }
229 pub fn eval_with_configuration(
231 &mut self,
232 eval_config: EvalConfig<impl ToString>,
233 ) -> Result<EvalResult> {
234 let EvalConfig {
235 input,
236 interactive_function,
237 color,
238 evaluator,
239 compile_mode,
240 } = eval_config;
241 self.eval_inner(input, interactive_function, color, evaluator, compile_mode)
242 }
243
244 fn eval_inner(
245 &mut self,
246 input: impl ToString,
247 interactive_function: Option<fn(&mut Child) -> Result<()>>,
248 color: bool,
249 evaluator: &[String],
250 compile_mode: CompileMode,
251 ) -> Result<EvalResult> {
252 let input = input.to_string();
253 let eval_statement = format!(
255 "{}{}{}std::process::exit(0);", evaluator[0], input, evaluator[1]
257 );
258 let toolchain = self.toolchain;
259
260 let cargo = self.cargo.clone();
261 let (status, eval_result) = self.eval_in_tmp_repl(eval_statement, |_| {
262 cargo.cargo_run(
263 color,
264 compile_mode.is_release(),
265 toolchain,
266 interactive_function,
267 )
268 })?;
269
270 Ok((status, eval_result).into())
271 }
272
273 pub fn eval_build(&mut self, input: impl ToString) -> Result<EvalResult> {
274 let input = input.to_string();
275 let toolchain = self.toolchain;
276 let cargo = self.cargo.clone();
277 Ok(self
278 .eval_in_tmp_repl(input, |_| -> Result<(ExitStatus, String)> {
279 Ok(cargo.cargo_build_output(true, false, toolchain)?)
280 })?
281 .into())
282 }
283
284 pub fn eval_check(&mut self, buffer: String) -> Result<EvalResult> {
285 let toolchain = self.toolchain;
286 let cargo = self.cargo.clone();
287 Ok(self
288 .eval_in_tmp_repl(buffer, |_| Ok(cargo.cargo_check_output(toolchain)?))?
289 .into())
290 }
291
292 pub fn eval_in_tmp_repl_without_io<T>(
293 &mut self,
294 input: String,
295 mut f: impl FnMut(&Self) -> Result<T>,
296 ) -> Result<T> {
297 let orig_body = self.body.clone();
298 let orig_cursor = self.cursor;
299
300 self.insert(input);
301 let result = f(self);
303
304 self.body = orig_body;
305 self.cursor = orig_cursor;
306
307 result
308 }
309 pub fn eval_in_tmp_repl<T>(
310 &mut self,
311 input: String,
312 mut f: impl FnMut(&Self) -> Result<T>,
313 ) -> Result<T> {
314 let orig_body = self.body.clone();
315 let orig_cursor = self.cursor;
316
317 self.insert(input);
318 self.write()?;
319 let result = f(self);
320
321 self.body = orig_body;
322 self.cursor = orig_cursor;
323
324 result
325 }
326
327 pub fn toolchain(&self) -> ToolChain {
328 self.toolchain
329 }
330
331 pub fn set_toolchain(&mut self, toolchain: ToolChain) {
332 self.toolchain = toolchain;
333 }
334
335 pub fn set_main_result(&mut self, main_result: MainResult) {
336 self.main_result = main_result;
337 let (header, footer) = Self::generate_body_delimiters(self.executor, self.main_result);
339 let footer_pos = self.body.len() - 2;
340 self.body[0] = header;
341 self.body[footer_pos] = footer;
342 }
343
344 pub fn add_dep(&self, dep: &[String]) -> std::io::Result<std::process::Child> {
345 self.cargo.cargo_add(dep)
346 }
347
348 pub fn build(&self) -> std::io::Result<std::process::Child> {
349 self.cargo.cargo_build(self.toolchain)
350 }
351
352 pub fn write(&self) -> io::Result<()> {
353 let mut main_file = std::fs::File::create(&self.cargo.paths.main_file)?;
354 write!(main_file, "{}", self.body.join("\n"))?;
355
356 Ok(())
357 }
358
359 pub fn body(&self) -> String {
360 self.body.join("\n")
361 }
362
363 pub fn write_to_extern(&self) -> io::Result<()> {
365 let mut main_file = std::fs::File::create(&self.cargo.paths.main_file_extern)?;
366 write!(main_file, "{}", self.body.join("\n"))?;
367
368 Ok(())
369 }
370
371 fn write_lib(&self) -> io::Result<()> {
372 let mut lib_file = std::fs::File::create(&self.cargo.paths.lib_file)?;
373 let mut body = self.body.clone();
374
375 let main_idx = body
377 .iter()
378 .position(|line| {
379 line == &Self::generate_body_delimiters(self.executor, self.main_result).0
380 })
381 .unwrap();
382 body.remove(main_idx); body.pop(); body.pop(); write!(lib_file, "{}", body.join("\n"))?;
387
388 Ok(())
389 }
390
391 fn remove_lib(&self) -> io::Result<()> {
392 std::fs::remove_file(&self.cargo.paths.lib_file)
393 }
394
395 pub fn with_lib<T>(&self, f: impl Fn() -> T) -> io::Result<T> {
396 self.write_lib()?;
397 let r = f();
398 self.remove_lib()?;
399 Ok(r)
400 }
401
402 pub fn pop(&mut self) {
403 if self.body.len() > 2 {
404 self.body.remove(self.cursor - 1);
405 self.cursor -= 1;
406 }
407 }
408
409 pub fn del(&mut self, line_num: &str) -> Result<()> {
410 if let Ok(line_num) = line_num.parse::<usize>()
411 && line_num != 0
412 && line_num + 1 < self.body.len()
413 {
414 self.body.remove(line_num);
415 self.cursor -= 1;
416 return Ok(());
417 }
418
419 Err("Incorrect line number".into())
420 }
421
422 pub fn lines(&self) -> impl Iterator<Item = &String> {
423 self.body.iter()
424 }
425 pub fn lines_count(&self) -> usize {
426 self.body.len() - 1
427 }
428}