1use std::env;
5use std::fmt;
6use std::io::{stdin, IsTerminal, Read};
7use std::path::PathBuf;
8use std::process;
9use std::str::FromStr;
10
11use crate::help_messages::{command_message, mode_message, OPTIONS};
12use crate::io::{Input, Output};
13use crate::levenshtein::get_similar_name;
14use crate::normalize_path;
15use crate::python_util::{detect_magic_number, get_python_version, PythonVersion};
16use crate::serialize::{get_magic_num_from_bytes, get_ver_from_magic_num};
17use crate::ArcArray;
18
19#[cfg(not(feature = "pylib"))]
20use erg_proc_macros::{new, pyclass, pymethods};
21#[cfg(feature = "pylib")]
22use pyo3::prelude::*;
23
24#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
25pub enum ErgMode {
26 Lex,
27 Parse,
28 Desugar,
29 TypeCheck,
30 FullCheck,
31 Compile,
32 Transpile,
33 Execute,
34 LanguageServer,
35 Lint,
36 Read,
37 Pack,
38}
39
40impl TryFrom<&str> for ErgMode {
41 type Error = ();
42 fn try_from(s: &str) -> Result<Self, ()> {
43 match s {
44 "lex" | "lexer" => Ok(Self::Lex),
45 "parse" | "parser" => Ok(Self::Parse),
46 "desugar" | "desugarer" => Ok(Self::Desugar),
47 "typecheck" | "lower" | "tc" => Ok(Self::TypeCheck),
48 "fullcheck" | "check" | "checker" => Ok(Self::FullCheck),
49 "comp" | "compile" | "compiler" => Ok(Self::Compile),
50 "trans" | "transpile" | "transpiler" => Ok(Self::Transpile),
51 "run" | "execute" => Ok(Self::Execute),
52 "server" | "language-server" => Ok(Self::LanguageServer),
53 "lint" | "linter" => Ok(Self::Lint),
54 "byteread" | "read" | "reader" | "dis" => Ok(Self::Read),
55 "pack" | "package" => Ok(Self::Pack),
56 _ => Err(()),
57 }
58 }
59}
60
61impl From<ErgMode> for &str {
62 fn from(mode: ErgMode) -> Self {
63 match mode {
64 ErgMode::Lex => "lex",
65 ErgMode::Parse => "parse",
66 ErgMode::Desugar => "desugar",
67 ErgMode::TypeCheck => "typecheck",
68 ErgMode::FullCheck => "fullcheck",
69 ErgMode::Compile => "compile",
70 ErgMode::Transpile => "transpile",
71 ErgMode::Execute => "execute",
72 ErgMode::LanguageServer => "language-server",
73 ErgMode::Lint => "lint",
74 ErgMode::Read => "read",
75 ErgMode::Pack => "pack",
76 }
77 }
78}
79
80impl fmt::Display for ErgMode {
81 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
82 write!(f, "{}", <&str>::from(*self))
83 }
84}
85
86impl ErgMode {
87 pub const fn is_language_server(&self) -> bool {
88 matches!(self, Self::LanguageServer)
89 }
90}
91
92#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
93pub enum TranspileTarget {
94 Python,
95 Json,
96 Toml,
97}
98
99impl From<&str> for TranspileTarget {
100 fn from(s: &str) -> Self {
101 match s {
102 "python" | "py" => Self::Python,
103 "json" => Self::Json,
104 "toml" => Self::Toml,
105 _ => panic!("unsupported transpile target: {s}"),
106 }
107 }
108}
109
110#[pyclass]
111#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
112pub struct Package {
113 pub name: &'static str,
114 pub as_name: &'static str,
115 pub version: &'static str,
116 pub path: Option<&'static str>,
117}
118
119impl Package {
120 pub const fn new(
121 name: &'static str,
122 as_name: &'static str,
123 version: &'static str,
124 path: Option<&'static str>,
125 ) -> Self {
126 Self {
127 name,
128 as_name,
129 version,
130 path,
131 }
132 }
133}
134
135#[pymethods]
136impl Package {
137 #[new]
138 fn _new(name: String, as_name: String, version: String, path: Option<String>) -> Self {
139 Self {
140 name: Box::leak(name.into_boxed_str()),
141 as_name: Box::leak(as_name.into_boxed_str()),
142 version: Box::leak(version.into_boxed_str()),
143 path: path.map(|s| Box::leak(s.into_boxed_str()) as &'static str),
144 }
145 }
146}
147
148#[derive(Debug, Clone)]
149pub struct ErgConfig {
150 pub mode: ErgMode,
151 pub opt_level: u8,
157 pub no_std: bool,
158 pub py_magic_num: Option<u32>, pub py_command: Option<&'static str>,
160 pub target_version: Option<PythonVersion>,
161 pub transpile_target: Option<TranspileTarget>,
162 pub py_server_timeout: u64,
163 pub quiet_repl: bool,
164 pub show_type: bool,
165 pub input: Input,
166 pub output: Output,
167 pub dist_dir: Option<&'static str>,
168 pub module: &'static str,
170 pub verbose: u8,
175 pub ps1: &'static str,
177 pub ps2: &'static str,
178 pub runtime_args: ArcArray<&'static str>,
179 pub packages: ArcArray<Package>,
180 pub effect_check: bool,
182 pub ownership_check: bool,
183 pub use_pylyzer: bool,
184 pub no_infer_fn_type: bool,
185 pub fast_error_report: bool,
186 pub do_not_show_ext_errors: bool,
187 pub respect_pyi: bool,
188}
189
190impl Default for ErgConfig {
191 #[inline]
192 fn default() -> Self {
193 Self {
194 mode: ErgMode::Execute,
195 opt_level: 1,
196 no_std: false,
197 py_magic_num: None,
198 py_command: None,
199 target_version: None,
200 transpile_target: None,
201 py_server_timeout: 10,
202 quiet_repl: false,
203 show_type: false,
204 input: Input::repl(),
205 output: Output::stdout(),
206 dist_dir: None,
207 module: "<module>",
208 verbose: 1,
209 ps1: ">>> ",
210 ps2: "... ",
211 runtime_args: ArcArray::from([]),
212 packages: ArcArray::from([]),
213 effect_check: true,
214 ownership_check: true,
215 use_pylyzer: false,
216 no_infer_fn_type: false,
217 fast_error_report: false,
218 do_not_show_ext_errors: false,
219 respect_pyi: true,
220 }
221 }
222}
223
224impl ErgConfig {
225 pub fn with_main_path(path: PathBuf) -> Self {
226 let path = normalize_path(path);
227 Self {
228 module: "<module>",
229 input: Input::file(path),
230 ..ErgConfig::default()
231 }
232 }
233
234 pub fn string(src: String) -> Self {
235 Self {
236 input: Input::str(src),
237 ..ErgConfig::default()
238 }
239 }
240
241 #[inline]
243 pub fn copy(&self) -> Self {
244 self.clone()
245 }
246
247 pub fn dump_path(&self) -> PathBuf {
248 if let Some(output) = &self.dist_dir {
249 PathBuf::from(format!("{output}/{}", self.input.filename()))
250 } else {
251 self.input.full_path().to_path_buf()
252 }
253 }
254
255 pub fn dump_filename(&self) -> String {
256 if let Some(output) = &self.dist_dir {
257 format!("{output}/{}", self.input.filename())
258 } else {
259 self.input.filename()
260 }
261 }
262
263 pub fn dump_pyc_path(&self) -> PathBuf {
264 let mut dump_path = self.dump_path();
265 dump_path.set_extension("pyc");
266 dump_path
267 }
268
269 pub fn dump_pyc_filename(&self) -> String {
270 let dump_filename = self.dump_filename();
271 if dump_filename.ends_with(".er") {
272 dump_filename.replace(".er", ".pyc")
273 } else {
274 dump_filename + ".pyc"
275 }
276 }
277
278 pub fn inherit(&self, path: PathBuf) -> Self {
279 let path = normalize_path(path);
280 Self {
281 module: Box::leak(path.to_str().unwrap().to_string().into_boxed_str()),
282 input: Input::file(path),
283 ..self.copy()
284 }
285 }
286
287 pub fn parse() -> Self {
288 let mut args = env::args();
289 args.next(); let mut cfg = Self::default();
291 let mut runtime_args: Vec<&'static str> = vec![];
292 let mut packages = vec![];
293 while let Some(arg) = args.next() {
295 match &arg[..] {
296 "--" => {
298 for arg in args {
299 runtime_args.push(Box::leak(arg.into_boxed_str()));
300 }
301 break;
302 }
303 "-c" | "--code" => {
304 cfg.input = Input::str(args.next().expect("the value of `-c` is not passed"));
305 }
306 "--no-std" => {
307 cfg.no_std = true;
308 }
309 "-?" | "-h" | "--help" => {
310 println!("{}", command_message());
311 if let "--mode" = args.next().as_ref().map(|s| &s[..]).unwrap_or("") {
312 println!("{}", mode_message());
313 }
314 process::exit(0);
315 }
316 "-m" | "--module" => {
317 let module = args
318 .next()
319 .expect("the value of `-m` is not passed")
320 .into_boxed_str();
321 cfg.module = Box::leak(module);
322 }
323 "--mode" => {
324 let mode = args.next().expect("the value of `--mode` is not passed");
325 if let "-?" | "-h" | "--help" = &mode[..] {
326 println!("{}", mode_message());
327 process::exit(0);
328 }
329 cfg.mode = ErgMode::try_from(&mode[..]).unwrap_or_else(|_| {
330 eprintln!("invalid mode: {mode}");
331 process::exit(1);
332 });
333 }
334 "--use-package" => {
335 let name = args
336 .next()
337 .expect("`name` of `--use-package` is not passed")
338 .into_boxed_str();
339 let as_name = args
340 .next()
341 .expect("`as_name` of `--use-package` is not passed")
342 .into_boxed_str();
343 let version = args
344 .next()
345 .expect("`version` of `--use-package` is not passed")
346 .into_boxed_str();
347 packages.push(Package::new(
348 Box::leak(name),
349 Box::leak(as_name),
350 Box::leak(version),
351 None,
352 ));
353 }
354 "--use-pylyzer" => {
355 cfg.use_pylyzer = true;
356 }
357 "--use-local-package" => {
358 let name = args
359 .next()
360 .expect("`name` of `--use-package` is not passed")
361 .into_boxed_str();
362 let as_name = args
363 .next()
364 .expect("`as_name` of `--use-package` is not passed")
365 .into_boxed_str();
366 let version = args
367 .next()
368 .expect("`version` of `--use-package` is not passed")
369 .into_boxed_str();
370 let path = args
371 .next()
372 .expect("`path` of `--use-package` is not passed")
373 .into_boxed_str();
374 packages.push(Package::new(
375 Box::leak(name),
376 Box::leak(as_name),
377 Box::leak(version),
378 Some(Box::leak(path)),
379 ));
380 }
381 "--ping" => {
382 println!("pong");
383 process::exit(0);
384 }
385 "--ps1" => {
386 let ps1 = args
387 .next()
388 .expect("the value of `--ps1` is not passed")
389 .into_boxed_str();
390 cfg.ps1 = Box::leak(ps1);
391 }
392 "--ps2" => {
393 let ps2 = args
394 .next()
395 .expect("the value of `--ps2` is not passed")
396 .into_boxed_str();
397 cfg.ps2 = Box::leak(ps2);
398 }
399 "-o" | "--opt-level" | "--optimization-level" => {
400 cfg.opt_level = args
401 .next()
402 .expect("the value of `-o` is not passed")
403 .parse::<u8>()
404 .expect("the value of `-o` is not a number");
405 }
406 "--output-dir" | "--dest" | "--dist" | "--dest-dir" | "--dist-dir" => {
407 let output_dir = args
408 .next()
409 .expect("the value of `--output-dir` is not passed")
410 .into_boxed_str();
411 cfg.dist_dir = Some(Box::leak(output_dir));
412 }
413 "--py-command" | "--python-command" => {
414 let py_command = args
415 .next()
416 .expect("the value of `--py-command` is not passed")
417 .parse::<String>()
418 .expect("the value of `-py-command` is not a valid Python command");
419 cfg.py_magic_num = Some(detect_magic_number(&py_command));
420 cfg.target_version = get_python_version(&py_command);
421 cfg.py_command = Some(Box::leak(py_command.into_boxed_str()));
422 }
423 "--hex-py-magic-num" | "--hex-python-magic-number" => {
424 let s_hex_magic_num = args
425 .next()
426 .expect("the value of `--hex-py-magic-num` is not passed");
427 let first_byte = u8::from_str_radix(&s_hex_magic_num[0..=1], 16).unwrap();
428 let second_byte = u8::from_str_radix(&s_hex_magic_num[2..=3], 16).unwrap();
429 let py_magic_num = get_magic_num_from_bytes(&[first_byte, second_byte, 0, 0]);
430 cfg.py_magic_num = Some(py_magic_num);
431 cfg.target_version = Some(get_ver_from_magic_num(py_magic_num));
432 }
433 "--py-magic-num" | "--python-magic-number" => {
434 let py_magic_num = args
435 .next()
436 .expect("the value of `--py-magic-num` is not passed")
437 .parse::<u32>()
438 .expect("the value of `--py-magic-num` is not a number");
439 cfg.py_magic_num = Some(py_magic_num);
440 cfg.target_version = Some(get_ver_from_magic_num(py_magic_num));
441 }
442 "--py-server-timeout" => {
443 cfg.py_server_timeout = args
444 .next()
445 .expect("the value of `--py-server-timeout` is not passed")
446 .parse::<u64>()
447 .expect("the value of `--py-server-timeout` is not a number");
448 }
449 "-q" | "--quiet-startup" | "--quiet-repl" => {
450 cfg.quiet_repl = true;
451 }
452 "-t" | "--show-type" => {
453 cfg.show_type = true;
454 }
455 "--target-version" => {
456 let target_version = args
457 .next()
458 .expect("the value of `--target-version` is not passed")
459 .parse::<PythonVersion>()
460 .expect("the value of `--target-version` is not a valid Python version");
461 cfg.target_version = Some(target_version);
462 }
463 "--transpile-target" | "--target" => {
464 let transpile_target = args
465 .next()
466 .expect("the value of `--transpile-target` is not passed")
467 .into_boxed_str();
468 cfg.transpile_target = Some(TranspileTarget::from(&transpile_target[..]));
469 }
470 "-v" | "--verbose" => {
471 cfg.verbose = args
472 .next()
473 .expect("the value of `--verbose` is not passed")
474 .parse::<u8>()
475 .expect("the value of `--verbose` is not a number");
476 }
477 "-V" | "--version" => {
478 println!("Erg {}", env!("CARGO_PKG_VERSION"));
479 process::exit(0);
480 }
481 "--build-features" => {
482 #[cfg(feature = "debug")]
483 print!("debug ");
484 #[cfg(feature = "backtrace")]
485 println!("backtrace");
486 #[cfg(feature = "els")]
487 print!("els ");
488 #[cfg(feature = "py_compat")]
489 print!("py_compat ");
490 #[cfg(feature = "japanese")]
491 print!("japanese ");
492 #[cfg(feature = "simplified_chinese")]
493 print!("simplified_chinese ");
494 #[cfg(feature = "traditional_chinese")]
495 print!("traditional_chinese ");
496 #[cfg(feature = "unicode")]
497 print!("unicode ");
498 #[cfg(feature = "pretty")]
499 print!("pretty ");
500 #[cfg(feature = "large_thread")]
501 print!("large_thread");
502 #[cfg(feature = "no_std")]
503 println!("no_std");
504 #[cfg(feature = "full-repl")]
505 println!("full-repl");
506 #[cfg(feature = "experimental")]
507 println!("experimental");
508 #[cfg(feature = "pylib")]
509 println!("pylib");
510 #[cfg(feature = "log-level-error")]
511 println!("log-level-error");
512 #[cfg(feature = "parallel")]
513 println!("parallel");
514 println!();
515 process::exit(0);
516 }
517 other if other.starts_with('-') => {
518 if let Some(option) = get_similar_name(OPTIONS.iter().copied(), other) {
519 eprintln!("invalid option: {other} (did you mean `{option}`?)");
520 } else {
521 eprintln!("invalid option: {other}");
522 }
523 eprintln!(
524 "
525USAGE:
526 erg [OPTIONS] [SUBCOMMAND] [ARGS]...
527
528 For more information try `erg --help`"
529 );
530 process::exit(2);
531 }
532 _ => {
533 if let Ok(mode) = ErgMode::try_from(&arg[..]) {
534 cfg.mode = mode;
535 if cfg.mode == ErgMode::Pack {
536 for arg in args {
537 runtime_args.push(Box::leak(arg.into_boxed_str()));
538 }
539 break;
540 }
541 } else {
542 let path = PathBuf::from_str(&arg[..])
543 .unwrap_or_else(|_| panic!("invalid file path: {arg}"));
544 let path = normalize_path(path);
545 cfg.input = Input::file(path);
546 match args.next().as_ref().map(|s| &s[..]) {
547 Some("--") => {
548 for arg in args {
549 runtime_args.push(Box::leak(arg.into_boxed_str()));
550 }
551 }
552 Some(some) => {
553 println!("invalid argument: {some}");
554 println!("Do not pass options after the file path. If you want to pass runtime arguments, use `--` before them.");
555 process::exit(1);
556 }
557 _ => {}
558 }
559 break;
560 }
561 }
562 }
563 }
564 if cfg.input.is_repl() && cfg.mode != ErgMode::LanguageServer {
565 let is_stdin_piped = !stdin().is_terminal();
566 let input = if is_stdin_piped {
567 let mut buffer = String::new();
568 stdin().read_to_string(&mut buffer).unwrap();
569 Input::pipe(buffer)
570 } else {
571 Input::repl()
572 };
573 cfg.input = input;
574 }
575 cfg.runtime_args = ArcArray::from(runtime_args);
576 cfg.packages = ArcArray::from(packages);
577 cfg
578 }
579}