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