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