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
227fn 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
243fn 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}