1use std::{ffi::OsString, path::PathBuf, sync::Arc};
2
3use clap::{builder::ArgPredicate, Parser};
4use midenc_session::{
5 diagnostics::{DefaultSourceManager, Emitter},
6 ColorChoice, DebugInfo, InputFile, LinkLibrary, OptLevel, Options, OutputFile, OutputType,
7 OutputTypeSpec, OutputTypes, ProjectType, Session, TargetEnv, Verbosity, Warnings,
8};
9
10#[derive(Debug, Parser)]
12#[command(name = "midenc")]
13pub struct Compiler {
14 #[arg(
18 long,
19 value_name = "DIR",
20 env = "MIDENC_TARGET_DIR",
21 default_value = "target/midenc",
22 help_heading = "Output"
23 )]
24 pub target_dir: PathBuf,
25 #[arg(long, value_name = "DIR", help_heading = "Output")]
29 pub working_dir: Option<PathBuf>,
30 #[arg(
34 long,
35 value_name = "DIR",
36 env = "MIDENC_SYSROOT",
37 help_heading = "Compiler"
38 )]
39 pub sysroot: Option<PathBuf>,
40 #[arg(
42 long,
43 short = 'O',
44 value_name = "DIR",
45 env = "MIDENC_OUT_DIR",
46 help_heading = "Output"
47 )]
48 pub output_dir: Option<PathBuf>,
49 #[arg(long, short = 'o', value_name = "FILENAME", help_heading = "Output")]
51 pub output_file: Option<PathBuf>,
52 #[arg(long, conflicts_with("output_file"), help_heading = "Output")]
54 pub stdout: bool,
55 #[arg(
60 long,
61 short = 'n',
62 value_name = "NAME",
63 default_value = None,
64 help_heading = "Diagnostics"
65 )]
66 pub name: Option<String>,
67 #[arg(
69 long = "verbose",
70 short = 'v',
71 value_enum,
72 value_name = "LEVEL",
73 default_value_t = Verbosity::Info,
74 default_missing_value = "debug",
75 num_args(0..=1),
76 help_heading = "Diagnostics"
77 )]
78 pub verbosity: Verbosity,
79 #[arg(
81 long,
82 short = 'W',
83 value_enum,
84 value_name = "LEVEL",
85 default_value_t = Warnings::All,
86 default_missing_value = "all",
87 num_args(0..=1),
88 help_heading = "Diagnostics"
89 )]
90 pub warn: Warnings,
91 #[arg(
93 long,
94 value_enum,
95 default_value_t = ColorChoice::Auto,
96 default_missing_value = "auto",
97 num_args(0..=1),
98 help_heading = "Diagnostics"
99 )]
100 pub color: ColorChoice,
101 #[arg(
103 long,
104 value_name = "TARGET",
105 default_value_t = TargetEnv::Base,
106 help_heading = "Compiler"
107 )]
108 pub target: TargetEnv,
109 #[arg(long, help_heading = "Compiler", hide(true))]
111 pub entrypoint: Option<String>,
112 #[arg(
116 long = "exe",
117 default_value_t = true,
118 default_value_ifs([
119 ("target", "rollup".into(), Some("false")),
121 ("entrypoint", ArgPredicate::IsPresent, Some("true")),
123 ]),
124 help_heading = "Linker"
125 )]
126 pub is_program: bool,
127 #[arg(
131 long = "lib",
132 conflicts_with("is_program"),
133 conflicts_with("entrypoint"),
134 default_value_t = false,
135 default_value_ifs([
136 ("entrypoint", ArgPredicate::IsPresent, Some("false")),
138 ("target", "rollup".into(), Some("true")),
140 ]),
141 help_heading = "Linker"
142 )]
143 pub is_library: bool,
144 #[arg(
146 long = "search-path",
147 short = 'L',
148 value_name = "PATH",
149 help_heading = "Linker"
150 )]
151 pub search_path: Vec<PathBuf>,
152 #[arg(
163 long = "link-library",
164 short = 'l',
165 value_name = "[KIND=]NAME",
166 value_delimiter = ',',
167 default_value_ifs([
168 ("target", "base", "std"),
169 ("target", "rollup", "std,base"),
170 ]),
171 next_line_help(true),
172 help_heading = "Linker"
173 )]
174 pub link_libraries: Vec<LinkLibrary>,
175 #[arg(
183 long = "emit",
184 value_name = "SPEC",
185 value_delimiter = ',',
186 next_line_help(true),
187 help_heading = "Output"
188 )]
189 pub output_types: Vec<OutputTypeSpec>,
190 #[arg(
192 long,
193 value_enum,
194 value_name = "LEVEL",
195 next_line_help(true),
196 default_value_t = DebugInfo::Full,
197 default_missing_value = "full",
198 num_args(0..=1),
199 help_heading = "Output"
200 )]
201 pub debug: DebugInfo,
202 #[arg(
205 long = "optimize",
206 value_enum,
207 value_name = "LEVEL",
208 next_line_help(true),
209 default_value_t = OptLevel::None,
210 default_missing_value = "balanced",
211 num_args(0..=1),
212 help_heading = "Output"
213 )]
214 pub opt_level: OptLevel,
215 #[arg(
219 long,
220 short = 'C',
221 value_name = "OPT[=VALUE]",
222 help_heading = "Compiler"
223 )]
224 pub codegen: Vec<String>,
225 #[arg(
229 long,
230 short = 'Z',
231 value_name = "OPT[=VALUE]",
232 help_heading = "Compiler"
233 )]
234 pub unstable: Vec<String>,
235}
236
237#[derive(Debug, Clone, Parser)]
238#[command(name = "-C")]
239pub struct CodegenOptions {
240 #[arg(
242 long,
243 conflicts_with_all(["analyze_only", "link_only"]),
244 default_value_t = false,
245 )]
246 pub parse_only: bool,
247 #[arg(
249 long,
250 conflicts_with_all(["parse_only", "link_only"]),
251 default_value_t = false,
252 )]
253 pub analyze_only: bool,
254 #[arg(
256 long,
257 conflicts_with_all(["no_link"]),
258 default_value_t = false,
259 )]
260 pub link_only: bool,
261 #[arg(long, default_value_t = false)]
263 pub no_link: bool,
264}
265
266#[derive(Debug, Clone, Parser)]
267#[command(name = "-Z")]
268pub struct UnstableOptions {
269 #[arg(long, default_value_t = false, help_heading = "Passes")]
271 pub print_cfg_after_all: bool,
272 #[arg(
274 long,
275 value_name = "PASS",
276 value_delimiter = ',',
277 help_heading = "Passes"
278 )]
279 pub print_cfg_after_pass: Vec<String>,
280 #[arg(long, default_value_t = false, help_heading = "Passes")]
282 pub print_ir_after_all: bool,
283 #[arg(
285 long,
286 value_name = "PASS",
287 value_delimiter = ',',
288 help_heading = "Passes"
289 )]
290 pub print_ir_after_pass: Vec<String>,
291}
292
293impl CodegenOptions {
294 fn parse_argv(argv: Vec<String>) -> Self {
295 let command = <CodegenOptions as clap::CommandFactory>::command()
296 .no_binary_name(true)
297 .arg_required_else_help(false)
298 .help_template(
299 "\
300Available codegen options:
301
302Usage: midenc compile -C <opt>
303
304{all-args}{after-help}
305
306NOTE: When specifying these options, strip the leading '--'",
307 );
308
309 let argv = if argv.iter().any(|arg| matches!(arg.as_str(), "--help" | "-h" | "help")) {
310 vec!["--help".to_string()]
311 } else {
312 argv.into_iter()
313 .flat_map(|arg| match arg.split_once('=') {
314 None => vec![format!("--{arg}")],
315 Some((opt, value)) => {
316 vec![format!("--{opt}"), value.to_string()]
317 }
318 })
319 .collect::<Vec<_>>()
320 };
321
322 let mut matches = command.try_get_matches_from(argv).unwrap_or_else(|err| err.exit());
323 <CodegenOptions as clap::FromArgMatches>::from_arg_matches_mut(&mut matches)
324 .map_err(format_error::<CodegenOptions>)
325 .unwrap_or_else(|err| err.exit())
326 }
327}
328
329impl UnstableOptions {
330 fn parse_argv(argv: Vec<String>) -> Self {
331 let command = <UnstableOptions as clap::CommandFactory>::command()
332 .no_binary_name(true)
333 .arg_required_else_help(false)
334 .help_template(
335 "\
336Available unstable options:
337
338Usage: midenc compile -Z <opt>
339
340{all-args}{after-help}
341
342NOTE: When specifying these options, strip the leading '--'",
343 );
344
345 let argv = if argv.iter().any(|arg| matches!(arg.as_str(), "--help" | "-h" | "help")) {
346 vec!["--help".to_string()]
347 } else {
348 argv.into_iter()
349 .flat_map(|arg| match arg.split_once('=') {
350 None => vec![format!("--{arg}")],
351 Some((opt, value)) => {
352 vec![format!("--{opt}"), value.to_string()]
353 }
354 })
355 .collect::<Vec<_>>()
356 };
357
358 let mut matches = command.try_get_matches_from(argv).unwrap_or_else(|err| err.exit());
359 <UnstableOptions as clap::FromArgMatches>::from_arg_matches_mut(&mut matches)
360 .map_err(format_error::<UnstableOptions>)
361 .unwrap_or_else(|err| err.exit())
362 }
363}
364
365impl Compiler {
366 pub fn new_session<I, A, S>(inputs: I, emitter: Option<Arc<dyn Emitter>>, argv: A) -> Session
368 where
369 I: IntoIterator<Item = InputFile>,
370 A: IntoIterator<Item = S>,
371 S: Into<OsString> + Clone,
372 {
373 let argv = [OsString::from("midenc")]
374 .into_iter()
375 .chain(argv.into_iter().map(|arg| arg.into()));
376 let command = <Self as clap::CommandFactory>::command();
377 let command = midenc_session::flags::register_flags(command);
378 let mut matches = command.try_get_matches_from(argv).unwrap_or_else(|err| err.exit());
379 let compile_matches = matches.clone();
380
381 let opts = <Self as clap::FromArgMatches>::from_arg_matches_mut(&mut matches)
382 .map_err(format_error::<Self>)
383 .unwrap_or_else(|err| err.exit());
384
385 let inputs = inputs.into_iter().collect();
386 opts.into_session(inputs, emitter).with_extra_flags(compile_matches.into())
387 }
388
389 pub fn into_session(
391 self,
392 inputs: Vec<InputFile>,
393 emitter: Option<Arc<dyn Emitter>>,
394 ) -> Session {
395 let cwd = self
396 .working_dir
397 .unwrap_or_else(|| std::env::current_dir().expect("no working directory available"));
398
399 let output_file = match self.output_file {
401 Some(path) => Some(OutputFile::Real(path)),
402 None if self.stdout => Some(OutputFile::Stdout),
403 None => None,
404 };
405
406 let mut output_types = OutputTypes::new(self.output_types).unwrap_or_else(|err| err.exit());
408 if output_types.is_empty() {
409 output_types.insert(OutputType::Masp, output_file.clone());
410 } else if output_file.is_some() && output_types.get(&OutputType::Masp).is_some() {
411 output_types.insert(OutputType::Masp, output_file.clone());
413 }
414
415 let project_type = if self.is_program {
417 ProjectType::Program
418 } else {
419 ProjectType::Library
420 };
421
422 let codegen = CodegenOptions::parse_argv(self.codegen);
423 let unstable = UnstableOptions::parse_argv(self.unstable);
424
425 let mut options = Options::new(self.name, self.target, project_type, cwd, self.sysroot)
427 .with_color(self.color)
428 .with_verbosity(self.verbosity)
429 .with_warnings(self.warn)
430 .with_debug_info(self.debug)
431 .with_optimization(self.opt_level)
432 .with_output_types(output_types);
433 options.search_paths = self.search_path;
434 options.link_libraries = self.link_libraries;
435 options.entrypoint = self.entrypoint;
436 options.parse_only = codegen.parse_only;
437 options.analyze_only = codegen.analyze_only;
438 options.link_only = codegen.link_only;
439 options.no_link = codegen.no_link;
440 options.print_cfg_after_all = unstable.print_cfg_after_all;
441 options.print_cfg_after_pass = unstable.print_cfg_after_pass;
442 options.print_ir_after_all = unstable.print_ir_after_all;
443 options.print_ir_after_pass = unstable.print_ir_after_pass;
444
445 let target_dir = if self.target_dir.is_absolute() {
447 self.target_dir
448 } else {
449 options.current_dir.join(&self.target_dir)
450 };
451 std::fs::create_dir_all(&target_dir).unwrap_or_else(|err| {
452 panic!("unable to create --target-dir '{}': {err}", target_dir.display())
453 });
454
455 let source_manager = Arc::new(DefaultSourceManager::default());
456 Session::new(
457 inputs,
458 self.output_dir,
459 output_file,
460 target_dir,
461 options,
462 emitter,
463 source_manager,
464 )
465 }
466}
467
468fn format_error<I: clap::CommandFactory>(err: clap::Error) -> clap::Error {
469 let mut cmd = I::command();
470 err.format(&mut cmd)
471}