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