Rust · 5789 bytes Raw Blame History
1 // Library surface for ARMFORTAS.
2 //
3 // The binary remains the user-facing compiler entrypoint, while the library
4 // exposes internal pipeline stages for the bench harness and other tooling.
5 #![allow(dead_code)]
6
7 pub mod ast;
8 pub mod codegen;
9 pub mod driver;
10 pub mod ir;
11 pub mod lexer;
12 pub mod opt;
13 pub mod parser;
14 pub mod preprocess;
15 pub mod runtime;
16 pub mod sema;
17 pub mod testing;
18
19 /// CLI entry point shared by both the `armfortas` and `afs` binaries.
20 /// Both binaries are built from the same source path; this function
21 /// holds the actual logic so the bin files are one-liners and Cargo
22 /// stops warning about a duplicated build target.
23 ///
24 /// Exit codes (sprint 32):
25 /// 0 success
26 /// 1 compile error (syntax, type, semantic)
27 /// 2 linker error
28 /// 3 I/O error (cannot read input, cannot write output)
29 /// 4 internal compiler error (panic / assertion)
30 pub fn cli_entry() -> ! {
31 use std::env;
32 use std::panic::{catch_unwind, AssertUnwindSafe};
33 use std::process;
34 const EXIT_COMPILE: i32 = 1;
35 const EXIT_LINK: i32 = 2;
36 const EXIT_IO: i32 = 3;
37 const EXIT_ICE: i32 = 4;
38 let program_name = driver::program_name();
39
40 let args: Vec<String> = env::args().skip(1).collect();
41 if args.is_empty() {
42 print!("{}", driver::HELP_TEXT);
43 process::exit(0);
44 }
45
46 let parsed = match driver::parse_cli(&args) {
47 Ok(p) => p,
48 Err(e) => {
49 if e == "no input file" {
50 eprintln!("{}: {}", program_name, e);
51 print!("{}", driver::HELP_TEXT);
52 process::exit(0);
53 }
54 eprintln!("{}: {}", program_name, e);
55 process::exit(classify_compile_error(&e, EXIT_COMPILE, EXIT_LINK, EXIT_IO));
56 }
57 };
58
59 match parsed {
60 driver::ParsedCli::Info(driver::InfoAction::Help) => {
61 print!("{}", driver::HELP_TEXT);
62 process::exit(0);
63 }
64 driver::ParsedCli::Info(driver::InfoAction::Version) => {
65 println!("{}", driver::version_string());
66 process::exit(0);
67 }
68 driver::ParsedCli::Info(driver::InfoAction::DumpVersion) => {
69 println!("{}", driver::dump_version_string());
70 process::exit(0);
71 }
72 driver::ParsedCli::Compile(opts) => {
73 if emit_cli_warnings(&program_name, &opts) {
74 process::exit(EXIT_COMPILE);
75 }
76 // Capture the input path before opts is moved into the
77 // compile thunk so we can include it in any ICE report.
78 let input_for_ice = opts.input.display().to_string();
79 install_ice_hook();
80 let result = catch_unwind(AssertUnwindSafe(|| driver::execute(&opts)));
81 match result {
82 Ok(Ok(())) => process::exit(0),
83 Ok(Err(e)) => {
84 eprintln!("{}: {}", program_name, e);
85 process::exit(classify_compile_error(&e, EXIT_COMPILE, EXIT_LINK, EXIT_IO));
86 }
87 Err(_panic) => {
88 print_ice_report(&input_for_ice);
89 process::exit(EXIT_ICE);
90 }
91 }
92 }
93 }
94 }
95
96 fn emit_cli_warnings(program_name: &str, opts: &driver::Options) -> bool {
97 if opts.cli_warnings.is_empty() {
98 return false;
99 }
100
101 let label = if opts.warn_as_error {
102 "error"
103 } else {
104 "warning"
105 };
106 for warning in &opts.cli_warnings {
107 eprintln!("{}: {}: {}", program_name, label, warning);
108 }
109 opts.warn_as_error
110 }
111
112 /// Map a compile error message to the appropriate exit code. Today
113 /// this is heuristic on the string content; a future structured
114 /// error type would let us be precise.
115 fn classify_compile_error(msg: &str, compile: i32, link: i32, io: i32) -> i32 {
116 if msg.contains("cannot read")
117 || msg.contains("cannot write")
118 || msg.contains("No such file")
119 || msg.contains("cannot open")
120 {
121 io
122 } else if msg.contains("linker failed") || msg.contains("ld:") {
123 link
124 } else {
125 compile
126 }
127 }
128
129 /// Install a panic hook that prints a compact ICE banner BEFORE the
130 /// stack trace. catch_unwind in `cli_entry` then formats the
131 /// "please report this" footer once the panic is caught. Splitting
132 /// it this way means the user sees the bug-report template even when
133 /// `RUST_BACKTRACE` is unset (which suppresses the default hook
134 /// output).
135 fn install_ice_hook() {
136 let prior = std::panic::take_hook();
137 std::panic::set_hook(Box::new(move |info| {
138 eprintln!();
139 eprintln!("INTERNAL COMPILER ERROR");
140 if let Some(loc) = info.location() {
141 eprintln!(" at {}:{}:{}", loc.file(), loc.line(), loc.column());
142 }
143 if let Some(msg) = info.payload().downcast_ref::<&'static str>() {
144 eprintln!(" message: {}", msg);
145 } else if let Some(msg) = info.payload().downcast_ref::<String>() {
146 eprintln!(" message: {}", msg);
147 }
148 // Defer to the prior hook for the backtrace (only emitted
149 // when RUST_BACKTRACE=1, but we want to keep that behaviour).
150 prior(info);
151 }));
152 }
153
154 fn print_ice_report(input: &str) {
155 eprintln!();
156 eprintln!("This is a bug in ARMFORTAS. Please report it at:");
157 eprintln!(" https://github.com/FortranGoingOnForty/armfortas/issues");
158 eprintln!();
159 eprintln!("Include this information:");
160 eprintln!(" - ARMFORTAS version: {}", env!("CARGO_PKG_VERSION"));
161 eprintln!(
162 " - Platform: {}-{}-{}",
163 std::env::consts::ARCH,
164 std::env::consts::FAMILY,
165 std::env::consts::OS
166 );
167 eprintln!(" - Source file: {}", input);
168 eprintln!(" - Re-run with RUST_BACKTRACE=1 for a stack trace.");
169 }
170