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