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
236fn 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
252fn 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}