Skip to main content

unlab_gpu/
main_loop.rs

1//
2// Copyright (c) 2025-2026 Ɓukasz Szpakowski
3//
4// This Source Code Form is subject to the terms of the Mozilla Public
5// License, v. 2.0. If a copy of the MPL was not distributed with this
6// file, You can obtain one at https://mozilla.org/MPL/2.0/.
7//
8//! A module of main loop.
9use std::env::current_dir;
10use std::ffi::OsString;
11use std::fs::create_dir_all;
12use std::io::Cursor;
13use std::path::PathBuf;
14use std::sync::Arc;
15use std::sync::RwLock;
16#[cfg(feature = "plot")]
17use std::thread;
18use rustyline::error::ReadlineError;
19use rustyline::DefaultEditor;
20#[cfg(feature = "plot")]
21use crate::winit::event_loop::ControlFlow;
22#[cfg(feature = "plot")]
23use crate::winit::event_loop::EventLoop;
24use crate::doc::*;
25use crate::env::*;
26use crate::error::*;
27use crate::interp::*;
28use crate::intr::*;
29use crate::lexer::*;
30use crate::mod_node::*;
31use crate::parser::*;
32#[cfg(feature = "plot")]
33use crate::plot::*;
34use crate::utils::*;
35use crate::value::*;
36
37#[cfg(feature = "plot")]
38fn run_plotter_app<F>(are_plotter_windows: bool, f: F) -> Option<i32>
39    where F: FnOnce(Option<EventLoopProxy>) -> Option<i32> + Send + 'static
40{
41    if are_plotter_windows {
42        let event_loop = match EventLoop::<PlotterAppEvent>::with_user_event().build() {
43            Ok(tmp_event_loop) => tmp_event_loop,
44            Err(err) => {
45                eprintln!("{}", err);
46                return Some(1);
47            },
48        };
49        let event_loop_proxy = event_loop.create_proxy();
50        let thr = thread::spawn(move || f(Some(event_loop_proxy)));
51        event_loop.set_control_flow(ControlFlow::Poll);
52        event_loop.set_control_flow(ControlFlow::Wait);
53        let mut plotter_app = PlotterApp::new(&event_loop);
54        match event_loop.run_app(&mut plotter_app) {
55            Ok(()) => (),
56            Err(err) => {
57                eprintln!("{}", err);
58                return Some(1);
59            },
60        }
61        match thr.join() {
62            Ok(res) => res,
63            Err(_) => {
64                eprintln!("can't join thread");
65                Some(1)
66            },
67        }
68    } else {
69        f(None)
70    }
71}
72
73#[cfg(feature = "plot")]
74fn quit_from_plotter_app(env: &Env) -> bool
75{
76    match rw_lock_read(env.shared_env()) {
77        Ok(shared_env_g) => {
78            match shared_env_g.event_loop_proxy() {
79                Some(event_loop_proxy) => {
80                    match event_loop_proxy.send_event(PlotterAppEvent::Quit) {
81                        Ok(()) => true,
82                        Err(err) => {
83                            eprintln!("{}", err);
84                            false
85                        },
86                    }
87                },
88                None => true,
89            }
90        },
91        Err(err) => {
92            eprint_error(&err);
93            false
94        },
95    }
96}
97
98#[cfg(not(feature = "plot"))]
99fn run_plotter_app<F>(_are_plotter_windows: bool, f: F) -> Option<i32>
100    where F: FnOnce(Option<EventLoopProxy>) -> Option<i32> + Send + Sync + 'static
101{ f(None) }
102
103#[cfg(not(feature = "plot"))]
104fn quit_from_plotter_app(_env: &Env) -> bool
105{ true }
106
107fn non_interactive_main_loop(path: String, args: Vec<String>, root_mod: Arc<RwLock<ModNode<Value, ()>>>, lib_path: OsString, doc_path: OsString, is_ctrl_c_intr_checker: bool, are_plotter_windows: bool) -> Option<i32>
108{
109    run_plotter_app(are_plotter_windows, move |event_loop_proxy| {
110            let intr_checker: Arc<dyn IntrCheck + Send + Sync> = if is_ctrl_c_intr_checker {
111                match CtrlCIntrChecker::initialize() {
112                    Ok(()) => (),
113                    Err(err) => {
114                        eprint_error(&err);
115                        return Some(1);
116                    },
117                }
118                Arc::new(CtrlCIntrChecker::new())
119            } else {
120                Arc::new(EmptyIntrChecker::new())
121            };
122            let shared_env = SharedEnv::new_with_intr_checker_and_event_loop_proxy(lib_path, doc_path, args, intr_checker, event_loop_proxy);
123            let mut env = Env::new_with_script_dir_and_domain_and_shared_env(root_mod, PathBuf::from("."), None, Arc::new(RwLock::new(shared_env)));
124            let mut interp = Interp::new();
125            let res = match parse(path) {
126                Ok(tree) => {
127                    match interp.interpret(&mut env, &tree) {
128                        Ok(()) => None,
129                        Err(Error::Stop(Stop::ErrorPropagation)) => {
130                            eprintln!("{}", interp.ret_value());
131                            Some(1)
132                        },
133                        Err(Error::Stop(Stop::Quit)) => None,
134                        Err(Error::Stop(Stop::Exit(code))) => Some(code),
135                        Err(err @ Error::Intr) => {
136                            eprint_error(&err);
137                            Some(1)
138                        },
139                        Err(err) => {
140                            eprint_error_with_stack_trace(&err, interp.stack_trace());
141                            Some(1)
142                        },
143                    }
144                },
145                Err(err) => {
146                    eprint_error(&err);
147                    Some(1)
148                },
149            };
150            if !quit_from_plotter_app(&env) {
151                return Some(1);
152            }
153            res
154    })
155}
156
157fn interactive_main_loop(args: Vec<String>, history_file: PathBuf, root_mod: Arc<RwLock<ModNode<Value, ()>>>, lib_path: OsString, doc_path: OsString, is_ctrl_c_intr_checker: bool, are_plotter_windows: bool) -> Option<i32>
158{
159    run_plotter_app(are_plotter_windows, move |event_loop_proxy| {
160            let intr_checker: Arc<dyn IntrCheck + Send + Sync> = if is_ctrl_c_intr_checker {
161                match CtrlCIntrChecker::initialize() {
162                    Ok(()) => (),
163                    Err(err) => {
164                        eprint_error(&err);
165                        return Some(1);
166                    },
167                }
168                Arc::new(CtrlCIntrChecker::new())
169            } else {
170                Arc::new(EmptyIntrChecker::new())
171            };
172            let shared_env = SharedEnv::new_with_intr_checker_and_event_loop_proxy(lib_path, doc_path, args, intr_checker, event_loop_proxy);
173            let mut env = Env::new_with_script_dir_and_domain_and_shared_env(root_mod, PathBuf::from("."), None, Arc::new(RwLock::new(shared_env)));
174            let mut interp = Interp::new();
175            let mut editor = match DefaultEditor::new() {
176                Ok(tmp_editor) => tmp_editor,
177                Err(err) => {
178                    eprintln!("{}", err);
179                    return Some(1);
180                },
181            };
182            let mut real_history_file = match current_dir() {
183                Ok(dir) => dir,
184                Err(err) => {
185                    eprintln!("{}", err);
186                    return Some(1);
187                },
188            };
189            real_history_file.push(history_file.as_path());
190            let _res = editor.load_history(real_history_file.as_path());
191            let mut line_num = 1u64;
192            let mut res: Option<i32> = None;
193            loop {
194                match editor.readline(format!("unlab-gpu:{}> ", line_num).as_str()) {
195                    Ok(line) => {
196                        match editor.add_history_entry(line.as_str()) {
197                            Ok(_) => (),
198                            Err(err) => {
199                                eprintln!("{}", err);
200                                return Some(1);
201                            },
202                        }
203                        let mut new_line_num = line_num;
204                        let mut lines = line.clone();
205                        lines.push('\n');
206                        new_line_num += 1;
207                        let tree = loop {
208                            let mut cursor = Cursor::new(lines.as_str());
209                            let mut lexer = Lexer::new_with_line(Arc::new(String::from("(stdin)")), &mut cursor, line_num);
210                            let parser_path = lexer.path().clone();
211                            let tokens: &mut dyn DocIterator<Item = Result<(Token, Pos)>> = &mut lexer;
212                            let mut parser = Parser::new(parser_path, tokens);
213                            match parser.parse() {
214                                Ok(tree) => break Some(tree),
215                                Err(err @ Error::ParserEof(_, ParserEofFlag::Repetition)) => {
216                                    match editor.readline("> ") {
217                                        Ok(next_line) => {
218                                            match editor.add_history_entry(next_line.as_str()) {
219                                                Ok(_) => (),
220                                                Err(err) => {
221                                                    eprintln!("{}", err);
222                                                    return Some(1);
223                                                },
224                                            }
225                                            lines.push_str(next_line.as_str());
226                                            lines.push('\n');
227                                            new_line_num += 1;
228                                        },
229                                        Err(ReadlineError::Interrupted) => (),
230                                        Err(ReadlineError::Eof) => {
231                                            eprint_error(&err);
232                                            break None;
233                                        },
234                                        Err(err) => {
235                                            eprintln!("{}", err);
236                                            return Some(1);
237                                        },
238                                    }
239                                },
240                                Err(err) => {
241                                    eprint_error(&err);
242                                    break None;
243                                },
244                            }
245                        };
246                        line_num = new_line_num;
247                        if is_ctrl_c_intr_checker {
248                            CtrlCIntrChecker::reset();
249                        }
250                        match tree {
251                            Some(tree) => {
252                                match interp.interpret(&mut env, &tree) {
253                                    Ok(()) => (),
254                                    Err(Error::Stop(Stop::ErrorPropagation)) => eprintln!("{}", interp.ret_value()),
255                                    Err(Error::Stop(Stop::Quit)) => break,
256                                    Err(Error::Stop(Stop::Exit(code))) => {
257                                        res = Some(code);
258                                        break;
259                                    },
260                                    Err(err @ Error::Intr) => eprint_error(&err),
261                                    Err(err) => eprint_error_with_stack_trace(&err, interp.stack_trace()),
262                                }
263                                interp.clear_stack_trace();
264                            },
265                            None => (),
266                        }
267                    },
268                    Err(ReadlineError::Interrupted) => (),
269                    Err(ReadlineError::Eof) => break,
270                    Err(err) => {
271                        eprintln!("{}", err);
272                        return Some(1);
273                    },
274                }
275            }
276            interp.clear_stack_trace();
277            let mut real_history_dir = real_history_file.clone();
278            real_history_dir.pop();
279            if real_history_dir != PathBuf::from("") {
280                match create_dir_all(real_history_dir.as_path()) {
281                    Ok(()) => (),
282                    Err(err) => {
283                        eprintln!("{}", err);
284                        return Some(1);
285                    },
286                }
287            }
288            match editor.save_history(real_history_file.as_path()) {
289                Ok(()) => (),
290                Err(err) => {
291                    eprintln!("{}", err);
292                    return Some(1);
293                },
294            }
295            if !quit_from_plotter_app(&env) {
296                return Some(1);
297            }
298            res
299    })
300}
301
302/// A main loop.
303///
304/// The main loop parses and interprets the script in the Unlab scripting language for a
305/// non-interactive mode or lines for an interactive mode. The interactive mode is set if the path
306/// to a script isn't passed. Also, this function takes arguments, a path to history file, a root
307/// module, and paths of libraries and documentations. A flag of `Ctrl-C` interruption checker 
308/// determines whether the main loop uses the `Ctrl-C` interruption checker. The flag of plotter
309/// windows determines whether the plotter windows can be shown.
310pub fn main_loop(path: Option<String>, args: Vec<String>, history_file: PathBuf, root_mod: Arc<RwLock<ModNode<Value, ()>>>, lib_path: OsString, doc_path: OsString, is_ctrl_c_intr_checker: bool, are_plotter_windows: bool) -> Option<i32>
311{
312    match path {
313        Some(path) => non_interactive_main_loop(path, args, root_mod, lib_path, doc_path, is_ctrl_c_intr_checker, are_plotter_windows),
314        None => interactive_main_loop(args, history_file, root_mod, lib_path, doc_path, is_ctrl_c_intr_checker, are_plotter_windows),
315    }
316}