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