irust_repl/
lib.rs

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        // NOTE: All the code in new should always not block
92        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        // check for required dependencies (in case of async)
98        if let Some(dependecy) = executor.dependecy() {
99            // needs to be sync
100            // repl::new(Tokio)
101            // repl.eval(5); // cargo-edit may not have written to Cargo.toml yet
102            //
103            // NOTE: This code blocks
104            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        // remove old dependecy if it exists
145        if let Some(dependecy) = self.executor.dependecy() {
146            // cargo rm needs only the crate name
147            self.cargo.cargo_rm_sync(&dependecy[0])?;
148        }
149
150        // use the new executor
151        self.executor = executor;
152        // check for required dependencies (in case of async)
153        if let Some(dependecy) = executor.dependecy() {
154            self.cargo.cargo_add_sync(&dependecy)?;
155        }
156        // finally set the correct main function
157        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    // Note: Insert must be followed by write_to_extern if persistance is needed
183    // Or else it will be overwritten by the main_extern thread
184    // Fix this
185    pub fn insert(&mut self, input: impl ToString) {
186        let input = input.to_string();
187        // CRATE_ATTRIBUTE are special in the sense that they should be inserted outside of the main function
188        // #![feature(unboxed_closures)]
189        // fn main() {}
190        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 cargo fmt is present format output else ignore
220        if let Ok(fmt_code) = self.cargo.cargo_fmt(&current_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    //Note: These inputs should become a Config struct
230    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        // `\n{}\n` to avoid print appearing in error messages
254        let eval_statement = format!(
255            "{}{}{}std::process::exit(0);", // exit(0) allows :hard_load functions to inspect variables that are used after this line
256            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        // self.write()?;
302        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        // rebuild main fn
338        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    // Used for external editors
364    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        // safe unwrap
376        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); // remove fn main
383        body.pop(); // remove result type [() | Ok(())]
384        body.pop(); // remove last }
385
386        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}