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();
41
42                let (builtin_stmt_results, builtin_error) =
43                    run_source_code(builtin_code().as_str(), &mut runtime);
44                let (ok, msg) =
45                    render_run_source_code_output(&runtime, &builtin_stmt_results, &builtin_error);
46                if !ok {
47                    eprintln!("builtin code execution failed: {}", msg);
48                    process::exit(1);
49                }
50                runtime.new_file_path_new_env_new_name_scope("-e");
51
52                let (stmt_results, runtime_error) = run_source_code(code.as_str(), &mut runtime);
53                let output = render_run_source_code_output(&runtime, &stmt_results, &runtime_error);
54                println!("{}", output.1.trim());
55                println!("{}", repl_footer_placeholder());
56                return;
57            }
58            "-f" => {
59                index += 1;
60                let file_path = match read_non_flag_value_after_flag(&args, &mut index, "-f") {
61                    Ok(value) => value,
62                    Err(message) => {
63                        eprintln!("{}", message);
64                        print_help_message();
65                        process::exit(2);
66                    }
67                };
68                main_flag_file(file_path.as_str());
69                return;
70            }
71            "-r" => {
72                index += 1;
73                let repo_path = match read_non_flag_value_after_flag(&args, &mut index, "-r") {
74                    Ok(value) => value,
75                    Err(message) => {
76                        eprintln!("{}", message);
77                        print_help_message();
78                        process::exit(2);
79                    }
80                };
81                let joined = Path::new(repo_path.as_str()).join(MAIN_DOT_LIT);
82                let joined_string = match joined.to_str() {
83                    Some(path_string) => path_string.to_string(),
84                    None => {
85                        eprintln!("Error: repo path is not valid UTF-8");
86                        process::exit(1);
87                    }
88                };
89                main_flag_file(joined_string.as_str());
90                return;
91            }
92            "-latex" => {
93                index += 1;
94                if index >= args.len() {
95                    println!("{}", run_latex_interactive());
96                    return;
97                }
98                let latex_target_flag = match read_any_value_after_flag(&args, &mut index, "-latex")
99                {
100                    Ok(value) => value,
101                    Err(message) => {
102                        eprintln!("{}", message);
103                        print_help_message();
104                        process::exit(2);
105                    }
106                };
107                let latex_output_result = match latex_target_flag.as_str() {
108                    "-f" => {
109                        let file_path =
110                            match read_non_flag_value_after_flag(&args, &mut index, "-f") {
111                                Ok(value) => value,
112                                Err(message) => {
113                                    eprintln!("{}", message);
114                                    print_help_message();
115                                    process::exit(2);
116                                }
117                            };
118                        compile_file_to_latex(file_path.as_str())
119                    }
120                    "-e" => {
121                        let code = match read_non_flag_value_after_flag(&args, &mut index, "-e") {
122                            Ok(value) => value,
123                            Err(message) => {
124                                eprintln!("{}", message);
125                                print_help_message();
126                                process::exit(2);
127                            }
128                        };
129                        compile_code_to_latex(code.as_str())
130                    }
131                    "-r" => {
132                        let repo_path =
133                            match read_non_flag_value_after_flag(&args, &mut index, "-r") {
134                                Ok(value) => value,
135                                Err(message) => {
136                                    eprintln!("{}", message);
137                                    print_help_message();
138                                    process::exit(2);
139                                }
140                            };
141                        let joined = Path::new(repo_path.as_str()).join(MAIN_DOT_LIT);
142                        let joined_string = match joined.to_str() {
143                            Some(path_string) => path_string.to_string(),
144                            None => {
145                                eprintln!("Error: repo path is not valid UTF-8");
146                                process::exit(1);
147                            }
148                        };
149                        compile_file_to_latex(joined_string.as_str())
150                    }
151                    _ => {
152                        eprintln!(
153                            "-latex must be followed by one of: -f <file>, -e <code>, -r <repo>"
154                        );
155                        print_help_message();
156                        process::exit(2);
157                    }
158                };
159                println!("{}", latex_output_result);
160                return;
161            }
162            "-fmt" => {
163                index += 1;
164                let code = match read_non_flag_value_after_flag(&args, &mut index, "-fmt") {
165                    Ok(value) => value,
166                    Err(message) => {
167                        eprintln!("{}", message);
168                        print_help_message();
169                        process::exit(2);
170                    }
171                };
172                println!("{}", format_code(code.as_str()));
173                return;
174            }
175            "-install" => {
176                index += 1;
177                let module_name =
178                    match read_non_flag_value_after_flag(&args, &mut index, "-install") {
179                        Ok(value) => value,
180                        Err(message) => {
181                            eprintln!("{}", message);
182                            print_help_message();
183                            process::exit(2);
184                        }
185                    };
186                install_module(module_name.as_str());
187                return;
188            }
189            "-uninstall" => {
190                index += 1;
191                let module_name =
192                    match read_non_flag_value_after_flag(&args, &mut index, "-uninstall") {
193                        Ok(value) => value,
194                        Err(message) => {
195                            eprintln!("{}", message);
196                            print_help_message();
197                            process::exit(2);
198                        }
199                    };
200                uninstall_module(module_name.as_str());
201                return;
202            }
203            "-list" => {
204                list_installed_modules();
205                return;
206            }
207            "-update" => {
208                index += 1;
209                let module_name = match read_non_flag_value_after_flag(&args, &mut index, "-update")
210                {
211                    Ok(value) => value,
212                    Err(message) => {
213                        eprintln!("{}", message);
214                        print_help_message();
215                        process::exit(2);
216                    }
217                };
218                update_module(module_name.as_str());
219                return;
220            }
221            "-tutorial" => {
222                run_tutorial();
223                return;
224            }
225            other => {
226                eprintln!("unknown argument: {}", other);
227                print_help_message();
228                process::exit(2);
229            }
230        }
231    }
232
233    run_repl(VERSION);
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) {
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(path_string.as_str());
305    println!("{}", string_with_trimmed_outer_newlines(output.as_str()));
306    println!("{}", repl_footer_placeholder());
307}
308
309fn string_with_trimmed_outer_newlines(text: &str) -> String {
310    text.trim().to_string()
311}
312
313fn repl_footer_placeholder() -> String {
314    "(REPL / ret-type footer: not implemented in Rust kernel yet)".to_string()
315}
316
317fn compile_code_to_latex(code: &str) -> String {
318    let code = remove_windows_carriage_return(code);
319    match to_latex_from_source_after_builtins(code.as_str(), "-latex -e") {
320        Ok(s) => s,
321        Err(e) => {
322            let runtime = Runtime::new();
323            display_runtime_error_json(&runtime, &e)
324        }
325    }
326}
327
328fn compile_file_to_latex(file_path: &str) -> String {
329    let source = match fs::read_to_string(file_path) {
330        Ok(content) => remove_windows_carriage_return(&content),
331        Err(e) => return format!("Could not read file {:?}: {}", file_path, e),
332    };
333    match to_latex_from_source_after_builtins(source.as_str(), file_path) {
334        Ok(s) => s,
335        Err(e) => {
336            let runtime = Runtime::new();
337            display_runtime_error_json(&runtime, &e)
338        }
339    }
340}
341
342fn format_code(_code: &str) -> String {
343    return "-fmt: format code is not implemented in the Rust kernel yet".to_string();
344}
345
346fn install_module(module_name: &str) -> String {
347    return format!(
348        "-install: module manager is not implemented in the Rust kernel yet (module: {})",
349        module_name
350    );
351}
352
353fn uninstall_module(module_name: &str) -> String {
354    return format!(
355        "-uninstall: module manager is not implemented in the Rust kernel yet (module: {})",
356        module_name
357    );
358}
359
360fn list_installed_modules() -> String {
361    return "-list: module manager is not implemented in the Rust kernel yet".to_string();
362}
363
364fn update_module(module_name: &str) -> String {
365    return format!(
366        "-update: module manager is not implemented in the Rust kernel yet (module: {})",
367        module_name
368    );
369}
370
371fn run_tutorial() -> String {
372    return "-tutorial: not implemented in the Rust kernel yet".to_string();
373}
374
375fn run_latex_interactive() -> String {
376    return "-latex: interactive LaTeX mode is not implemented in the Rust kernel yet".to_string();
377}
378
379fn help_message() -> String {
380    let result = r#"litex : run Litex interactively in your terminal
381litex -f <file> : run the given file
382litex -r <repo> : run the given repository
383litex -e <code> : execute the given code
384litex -latex : compile the given file or code to LaTeX interactively in your terminal
385litex -latex -f <file> : compile the given file to LaTeX
386litex -latex -e <code> : compile the given code to LaTeX
387litex -latex -r <repo> : compile the given repository to LaTeX
388litex -help : show the help message
389litex -version : show the version
390litex -fmt : format the given code
391litex -install <module> : install the given module
392litex -uninstall <module> : uninstall the given module
393litex -list : list all installed modules
394litex -update <module> : update the given module
395litex -tutorial : run the tutorial
396"#;
397    result.to_string()
398}