Skip to main content

litex/cli/
cli.rs

1use crate::prelude::*;
2use crate::to_latex::to_latex_from_source_after_builtins;
3use std::env;
4use std::fs;
5use std::path::{Path, PathBuf};
6use std::process;
7
8pub const MAIN_DOT_LIT: &str = "main.lit";
9
10pub const VERSION: &str = env!("CARGO_PKG_VERSION");
11
12pub fn run_cli() {
13    let args: Vec<String> = env::args().skip(1).collect();
14    let mut index: usize = 0;
15
16    if !args.is_empty() {
17        let head = args[index].as_str();
18
19        match head {
20            "-help" => {
21                print_help_message();
22                println!();
23                println!("If no options are provided, starts interactive REPL mode.");
24                return;
25            }
26            "-version" => {
27                println!("Litex Kernel: litex {}", VERSION);
28                return;
29            }
30            "-e" => {
31                index += 1;
32                let code = match read_non_flag_value_after_flag(&args, &mut index, "-e") {
33                    Ok(value) => value,
34                    Err(message) => {
35                        eprintln!("{}", message);
36                        print_help_message();
37                        process::exit(2);
38                    }
39                };
40                let mut runtime = Runtime::new_with_builtin_code();
41                runtime.new_file_path_new_env_new_name_scope("-e");
42
43                let (stmt_results, runtime_error) = run_source_code(code.as_str(), &mut runtime);
44                let output = render_run_source_code_output(
45                    &runtime,
46                    &stmt_results,
47                    &runtime_error,
48                    true,
49                );
50                println!("{}", output.1.trim());
51                println!("{}", repl_footer_placeholder());
52                return;
53            }
54            "-f" => {
55                index += 1;
56                let file_path = match read_non_flag_value_after_flag(&args, &mut index, "-f") {
57                    Ok(value) => value,
58                    Err(message) => {
59                        eprintln!("{}", message);
60                        print_help_message();
61                        process::exit(2);
62                    }
63                };
64                main_flag_file(file_path.as_str());
65                return;
66            }
67            "-r" => {
68                index += 1;
69                let repo_path = match read_non_flag_value_after_flag(&args, &mut index, "-r") {
70                    Ok(value) => value,
71                    Err(message) => {
72                        eprintln!("{}", message);
73                        print_help_message();
74                        process::exit(2);
75                    }
76                };
77                let joined = Path::new(repo_path.as_str()).join(MAIN_DOT_LIT);
78                let joined_string = match joined.to_str() {
79                    Some(path_string) => path_string.to_string(),
80                    None => {
81                        eprintln!("Error: repo path is not valid UTF-8");
82                        process::exit(1);
83                    }
84                };
85                main_flag_file(joined_string.as_str());
86                return;
87            }
88            "-latex" => {
89                index += 1;
90                if index >= args.len() {
91                    println!("{}", run_latex_interactive());
92                    return;
93                }
94                let latex_target_flag = match read_any_value_after_flag(&args, &mut index, "-latex")
95                {
96                    Ok(value) => value,
97                    Err(message) => {
98                        eprintln!("{}", message);
99                        print_help_message();
100                        process::exit(2);
101                    }
102                };
103                let latex_output_result = match latex_target_flag.as_str() {
104                    "-f" => {
105                        let file_path =
106                            match read_non_flag_value_after_flag(&args, &mut index, "-f") {
107                                Ok(value) => value,
108                                Err(message) => {
109                                    eprintln!("{}", message);
110                                    print_help_message();
111                                    process::exit(2);
112                                }
113                            };
114                        compile_file_to_latex(file_path.as_str())
115                    }
116                    "-e" => {
117                        let code = match read_non_flag_value_after_flag(&args, &mut index, "-e") {
118                            Ok(value) => value,
119                            Err(message) => {
120                                eprintln!("{}", message);
121                                print_help_message();
122                                process::exit(2);
123                            }
124                        };
125                        compile_code_to_latex(code.as_str())
126                    }
127                    "-r" => {
128                        let repo_path =
129                            match read_non_flag_value_after_flag(&args, &mut index, "-r") {
130                                Ok(value) => value,
131                                Err(message) => {
132                                    eprintln!("{}", message);
133                                    print_help_message();
134                                    process::exit(2);
135                                }
136                            };
137                        let joined = Path::new(repo_path.as_str()).join(MAIN_DOT_LIT);
138                        let joined_string = match joined.to_str() {
139                            Some(path_string) => path_string.to_string(),
140                            None => {
141                                eprintln!("Error: repo path is not valid UTF-8");
142                                process::exit(1);
143                            }
144                        };
145                        compile_file_to_latex(joined_string.as_str())
146                    }
147                    _ => {
148                        eprintln!(
149                            "-latex must be followed by one of: -f <file>, -e <code>, -r <repo>"
150                        );
151                        print_help_message();
152                        process::exit(2);
153                    }
154                };
155                println!("{}", latex_output_result);
156                return;
157            }
158            "-fmt" => {
159                index += 1;
160                let code = match read_non_flag_value_after_flag(&args, &mut index, "-fmt") {
161                    Ok(value) => value,
162                    Err(message) => {
163                        eprintln!("{}", message);
164                        print_help_message();
165                        process::exit(2);
166                    }
167                };
168                println!("{}", format_code(code.as_str()));
169                return;
170            }
171            "-install" => {
172                index += 1;
173                let module_name =
174                    match read_non_flag_value_after_flag(&args, &mut index, "-install") {
175                        Ok(value) => value,
176                        Err(message) => {
177                            eprintln!("{}", message);
178                            print_help_message();
179                            process::exit(2);
180                        }
181                    };
182                install_module(module_name.as_str());
183                return;
184            }
185            "-uninstall" => {
186                index += 1;
187                let module_name =
188                    match read_non_flag_value_after_flag(&args, &mut index, "-uninstall") {
189                        Ok(value) => value,
190                        Err(message) => {
191                            eprintln!("{}", message);
192                            print_help_message();
193                            process::exit(2);
194                        }
195                    };
196                uninstall_module(module_name.as_str());
197                return;
198            }
199            "-list" => {
200                list_installed_modules();
201                return;
202            }
203            "-update" => {
204                index += 1;
205                let module_name = match read_non_flag_value_after_flag(&args, &mut index, "-update")
206                {
207                    Ok(value) => value,
208                    Err(message) => {
209                        eprintln!("{}", message);
210                        print_help_message();
211                        process::exit(2);
212                    }
213                };
214                update_module(module_name.as_str());
215                return;
216            }
217            "-tutorial" => {
218                run_tutorial();
219                return;
220            }
221            other => {
222                eprintln!("unknown argument: {}", other);
223                print_help_message();
224                process::exit(2);
225            }
226        }
227    }
228
229    run_repl(VERSION);
230}
231
232/// `index` must point at the first token after the flag; reads one value and advances past it.
233fn read_non_flag_value_after_flag(
234    args: &[String],
235    index: &mut usize,
236    flag_name: &str,
237) -> Result<String, String> {
238    let value = match args.get(*index) {
239        Some(candidate) if !candidate.starts_with('-') => candidate.clone(),
240        _ => {
241            return Err(format!("{} requires a value", flag_name));
242        }
243    };
244    *index += 1;
245    Ok(value)
246}
247
248/// `index` must point at the first token after the flag; reads one token (can be another flag) and advances past it.
249fn read_any_value_after_flag(
250    args: &[String],
251    index: &mut usize,
252    flag_name: &str,
253) -> Result<String, String> {
254    let value = match args.get(*index) {
255        Some(candidate) => candidate.clone(),
256        None => return Err(format!("{} requires a value", flag_name)),
257    };
258    *index += 1;
259    Ok(value)
260}
261
262fn print_help_message() {
263    println!("{}", help_message());
264}
265
266fn remove_windows_carriage_return(path_or_code: &str) -> String {
267    path_or_code.replace('\r', "")
268}
269
270fn main_flag_file(file_flag: &str) {
271    let path = remove_windows_carriage_return(file_flag);
272
273    let abs_file_path: PathBuf = if Path::new(path.as_str()).is_absolute() {
274        PathBuf::from(path.as_str())
275    } else {
276        let working_directory_result = env::current_dir();
277        let working_directory = match working_directory_result {
278            Ok(path) => path,
279            Err(error) => {
280                eprintln!("Error: failed to get current working directory: {}", error);
281                return;
282            }
283        };
284        working_directory.join(path.as_str())
285    };
286
287    if abs_file_path.parent().is_none() {
288        eprintln!("Error: could not get parent directory of file path");
289        return;
290    }
291
292    let path_string = match abs_file_path.to_str() {
293        Some(path_string) => path_string.to_string(),
294        None => {
295            eprintln!("Error: file path is not valid UTF-8");
296            return;
297        }
298    };
299
300    let output = run_source_code_in_file(path_string.as_str());
301    println!("{}", string_with_trimmed_outer_newlines(output.as_str()));
302    println!("{}", repl_footer_placeholder());
303}
304
305fn string_with_trimmed_outer_newlines(text: &str) -> String {
306    text.trim().to_string()
307}
308
309fn repl_footer_placeholder() -> String {
310    "(REPL / ret-type footer: not implemented in Rust kernel yet)".to_string()
311}
312
313fn compile_code_to_latex(code: &str) -> String {
314    let code = remove_windows_carriage_return(code);
315    match to_latex_from_source_after_builtins(code.as_str(), "-latex -e") {
316        Ok(s) => s,
317        Err(e) => {
318            let runtime = Runtime::new();
319            display_runtime_error_json(&runtime, &e, true)
320        }
321    }
322}
323
324fn compile_file_to_latex(file_path: &str) -> String {
325    let source = match fs::read_to_string(file_path) {
326        Ok(content) => remove_windows_carriage_return(&content),
327        Err(e) => return format!("Could not read file {:?}: {}", file_path, e),
328    };
329    match to_latex_from_source_after_builtins(source.as_str(), file_path) {
330        Ok(s) => s,
331        Err(e) => {
332            let runtime = Runtime::new();
333            display_runtime_error_json(&runtime, &e, true)
334        }
335    }
336}
337
338fn format_code(_code: &str) -> String {
339    return "-fmt: format code is not implemented in the Rust kernel yet".to_string();
340}
341
342fn install_module(module_name: &str) -> String {
343    return format!(
344        "-install: module manager is not implemented in the Rust kernel yet (module: {})",
345        module_name
346    );
347}
348
349fn uninstall_module(module_name: &str) -> String {
350    return format!(
351        "-uninstall: module manager is not implemented in the Rust kernel yet (module: {})",
352        module_name
353    );
354}
355
356fn list_installed_modules() -> String {
357    return "-list: module manager is not implemented in the Rust kernel yet".to_string();
358}
359
360fn update_module(module_name: &str) -> String {
361    return format!(
362        "-update: module manager is not implemented in the Rust kernel yet (module: {})",
363        module_name
364    );
365}
366
367fn run_tutorial() -> String {
368    return "-tutorial: not implemented in the Rust kernel yet".to_string();
369}
370
371fn run_latex_interactive() -> String {
372    return "-latex: interactive LaTeX mode is not implemented in the Rust kernel yet".to_string();
373}
374
375fn help_message() -> String {
376    let result = r#"litex : run Litex interactively in your terminal
377litex -f <file> : run the given file
378litex -r <repo> : run the given repository
379litex -e <code> : execute the given code
380litex -latex : compile the given file or code to LaTeX interactively in your terminal
381litex -latex -f <file> : compile the given file to LaTeX
382litex -latex -e <code> : compile the given code to LaTeX
383litex -latex -r <repo> : compile the given repository to LaTeX
384litex -help : show the help message
385litex -version : show the version
386litex -fmt : format the given code
387litex -install <module> : install the given module
388litex -uninstall <module> : uninstall the given module
389litex -list : list all installed modules
390litex -update <module> : update the given module
391litex -tutorial : run the tutorial
392"#;
393    result.to_string()
394}