1use alloc::{
2 borrow::{Cow, ToOwned},
3 collections::BTreeMap,
4 fmt, format,
5 str::FromStr,
6 string::String,
7};
8
9use crate::{Path, PathBuf};
10
11#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
13pub enum OutputMode {
14 Text,
16 Binary,
18}
19
20#[derive(Debug, Copy, Clone, Default, Hash, PartialEq, Eq, PartialOrd, Ord)]
22#[cfg_attr(feature = "std", derive(clap::ValueEnum))]
23pub enum OutputType {
24 Ast,
26 Hir,
28 Masm,
30 Mast,
32 Masl,
34 #[default]
36 Masp,
37}
38impl OutputType {
39 pub fn is_intermediate(&self) -> bool {
41 !matches!(self, Self::Mast | Self::Masl | Self::Masp)
42 }
43
44 pub fn extension(&self) -> &'static str {
45 match self {
46 Self::Ast => "ast",
47 Self::Hir => "hir",
48 Self::Masm => "masm",
49 Self::Mast => "mast",
50 Self::Masl => "masl",
51 Self::Masp => "masp",
52 }
53 }
54
55 pub fn shorthand_display() -> String {
56 format!(
57 "`{}`, `{}`, `{}`, `{}`, `{}`, `{}`",
58 Self::Ast,
59 Self::Hir,
60 Self::Masm,
61 Self::Mast,
62 Self::Masl,
63 Self::Masp,
64 )
65 }
66
67 pub fn all() -> [OutputType; 6] {
68 [
69 OutputType::Ast,
70 OutputType::Hir,
71 OutputType::Masm,
72 OutputType::Mast,
73 OutputType::Masl,
74 OutputType::Masp,
75 ]
76 }
77}
78impl fmt::Display for OutputType {
79 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
80 match self {
81 Self::Ast => f.write_str("ast"),
82 Self::Hir => f.write_str("hir"),
83 Self::Masm => f.write_str("masm"),
84 Self::Mast => f.write_str("mast"),
85 Self::Masl => f.write_str("masl"),
86 Self::Masp => f.write_str("masp"),
87 }
88 }
89}
90impl FromStr for OutputType {
91 type Err = ();
92
93 fn from_str(s: &str) -> Result<Self, Self::Err> {
94 match s {
95 "ast" => Ok(Self::Ast),
96 "hir" => Ok(Self::Hir),
97 "masm" => Ok(Self::Masm),
98 "mast" => Ok(Self::Mast),
99 "masl" => Ok(Self::Masl),
100 "masp" => Ok(Self::Masp),
101 _ => Err(()),
102 }
103 }
104}
105
106#[derive(Debug, Clone)]
107pub enum OutputFile {
108 Real(PathBuf),
109 Stdout,
110}
111impl OutputFile {
112 pub fn parent(&self) -> Option<&Path> {
113 match self {
114 Self::Real(path) => path.parent(),
115 Self::Stdout => None,
116 }
117 }
118
119 pub fn filestem(&self) -> Option<Cow<'_, str>> {
120 match self {
121 Self::Real(path) => path.file_stem().map(|stem| stem.to_string_lossy()),
122 Self::Stdout => None,
123 }
124 }
125
126 pub fn is_stdout(&self) -> bool {
127 matches!(self, Self::Stdout)
128 }
129
130 #[cfg(feature = "std")]
131 pub fn is_tty(&self) -> bool {
132 use std::io::IsTerminal;
133 match self {
134 Self::Real(_) => false,
135 Self::Stdout => std::io::stdout().is_terminal(),
136 }
137 }
138
139 #[cfg(not(feature = "std"))]
140 pub fn is_tty(&self) -> bool {
141 false
142 }
143
144 pub fn as_path(&self) -> Option<&Path> {
145 match self {
146 Self::Real(path) => Some(path.as_ref()),
147 Self::Stdout => None,
148 }
149 }
150
151 pub fn file_for_writing(
152 &self,
153 outputs: &OutputFiles,
154 ty: OutputType,
155 name: Option<&str>,
156 ) -> PathBuf {
157 match self {
158 Self::Real(path) => path.clone(),
159 Self::Stdout => outputs.temp_path(ty, name),
160 }
161 }
162}
163impl fmt::Display for OutputFile {
164 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
165 match self {
166 Self::Real(path) => write!(f, "{}", path.display()),
167 Self::Stdout => write!(f, "stdout"),
168 }
169 }
170}
171
172#[derive(Debug)]
173pub struct OutputFiles {
174 stem: String,
175 pub cwd: PathBuf,
177 pub tmp_dir: PathBuf,
179 pub out_dir: PathBuf,
185 pub out_file: Option<OutputFile>,
189 pub outputs: OutputTypes,
191}
192impl OutputFiles {
193 pub fn new(
194 stem: String,
195 cwd: PathBuf,
196 out_dir: PathBuf,
197 out_file: Option<OutputFile>,
198 tmp_dir: PathBuf,
199 outputs: OutputTypes,
200 ) -> Self {
201 Self {
202 stem,
203 cwd,
204 tmp_dir,
205 out_dir,
206 out_file,
207 outputs,
208 }
209 }
210
211 pub fn output_file(&self, ty: OutputType, name: Option<&str>) -> OutputFile {
214 let default_name = name.unwrap_or(self.stem.as_str());
215 self.outputs
216 .get(&ty)
217 .and_then(|p| p.to_owned())
218 .map(|of| match of {
219 OutputFile::Real(path) => OutputFile::Real({
220 let path = if path.is_absolute() {
221 path
222 } else {
223 self.cwd.join(path)
224 };
225 if path.is_dir() {
226 path.join(default_name).with_extension(ty.extension())
227 } else if let Some(name) = name {
228 path.with_stem_and_extension(name, ty.extension())
229 } else {
230 path
231 }
232 }),
233 out @ OutputFile::Stdout => out,
234 })
235 .unwrap_or_else(|| {
236 let out = if ty.is_intermediate() {
237 self.with_directory_and_extension(&self.tmp_dir, ty.extension())
238 } else if let Some(output_file) = self.out_file.as_ref() {
239 return output_file.clone();
240 } else {
241 self.with_directory_and_extension(&self.out_dir, ty.extension())
242 };
243 OutputFile::Real(if let Some(name) = name {
244 out.with_stem(name)
245 } else {
246 out
247 })
248 })
249 }
250
251 pub fn output_path(&self, ty: OutputType) -> PathBuf {
257 match self.output_file(ty, None) {
258 OutputFile::Real(path) => path,
259 OutputFile::Stdout => {
260 if ty.is_intermediate() {
261 self.with_directory_and_extension(&self.tmp_dir, ty.extension())
262 } else if let Some(output_file) = self.out_file.as_ref().and_then(|of| of.as_path())
263 {
264 output_file.to_path_buf()
265 } else {
266 self.with_directory_and_extension(&self.out_dir, ty.extension())
267 }
268 }
269 }
270 }
271
272 pub fn temp_path(&self, ty: OutputType, name: Option<&str>) -> PathBuf {
277 self.tmp_dir
278 .join(name.unwrap_or(self.stem.as_str()))
279 .with_extension(ty.extension())
280 }
281
282 pub fn with_extension(&self, extension: &str) -> PathBuf {
287 match self.out_file.as_ref() {
288 Some(OutputFile::Real(path)) => path.with_extension(extension),
289 Some(OutputFile::Stdout) | None => {
290 self.with_directory_and_extension(&self.out_dir, extension)
291 }
292 }
293 }
294
295 #[inline]
298 fn with_directory_and_extension(&self, directory: &Path, extension: &str) -> PathBuf {
299 directory.join(&self.stem).with_extension(extension)
300 }
301}
302
303#[derive(Debug, Clone, Default)]
304pub struct OutputTypes(BTreeMap<OutputType, Option<OutputFile>>);
305impl OutputTypes {
306 #[cfg(feature = "std")]
307 pub fn new<I: IntoIterator<Item = OutputTypeSpec>>(entries: I) -> Result<Self, clap::Error> {
308 let entries = entries.into_iter();
309 let mut map = BTreeMap::default();
310 for spec in entries {
311 match spec {
312 OutputTypeSpec::All { path } => {
313 if !map.is_empty() {
314 return Err(clap::Error::raw(
315 clap::error::ErrorKind::ValueValidation,
316 "--emit=all cannot be combined with other --emit types",
317 ));
318 }
319 if let Some(OutputFile::Real(path)) = &path
320 && path.extension().is_some()
321 {
322 return Err(clap::Error::raw(
323 clap::error::ErrorKind::ValueValidation,
324 "invalid path for --emit=all: must be a directory",
325 ));
326 }
327 for ty in OutputType::all() {
328 map.insert(ty, path.clone());
329 }
330 }
331 OutputTypeSpec::Typed { output_type, path } => {
332 if path.is_some() {
333 if matches!(map.get(&output_type), Some(Some(OutputFile::Real(_)))) {
334 return Err(clap::Error::raw(
335 clap::error::ErrorKind::ValueValidation,
336 format!(
337 "conflicting --emit options given for output type \
338 '{output_type}'"
339 ),
340 ));
341 }
342 } else if matches!(map.get(&output_type), Some(Some(_))) {
343 continue;
344 }
345 map.insert(output_type, path);
346 }
347 }
348 }
349 Ok(Self(map))
350 }
351
352 pub fn get(&self, key: &OutputType) -> Option<&Option<OutputFile>> {
353 self.0.get(key)
354 }
355
356 pub fn insert(&mut self, key: OutputType, value: Option<OutputFile>) {
357 self.0.insert(key, value);
358 }
359
360 pub fn clear(&mut self) {
361 self.0.clear();
362 }
363
364 pub fn contains_key(&self, key: &OutputType) -> bool {
365 self.0.contains_key(key)
366 }
367
368 pub fn iter(&self) -> impl Iterator<Item = (&OutputType, &Option<OutputFile>)> + '_ {
369 self.0.iter()
370 }
371
372 pub fn keys(&self) -> impl Iterator<Item = OutputType> + '_ {
373 self.0.keys().copied()
374 }
375
376 pub fn values(&self) -> impl Iterator<Item = Option<&OutputFile>> {
377 self.0.values().map(|v| v.as_ref())
378 }
379
380 #[inline(always)]
381 pub fn is_empty(&self) -> bool {
382 self.0.is_empty()
383 }
384
385 pub fn len(&self) -> usize {
386 self.0.len()
387 }
388
389 pub fn should_link(&self) -> bool {
390 self.0.keys().any(|k| {
391 matches!(
392 k,
393 OutputType::Hir
394 | OutputType::Masm
395 | OutputType::Mast
396 | OutputType::Masl
397 | OutputType::Masp
398 )
399 })
400 }
401
402 pub fn should_codegen(&self) -> bool {
403 self.0.keys().any(|k| {
404 matches!(k, OutputType::Masm | OutputType::Mast | OutputType::Masl | OutputType::Masp)
405 })
406 }
407
408 pub fn should_assemble(&self) -> bool {
409 self.0
410 .keys()
411 .any(|k| matches!(k, OutputType::Mast | OutputType::Masl | OutputType::Masp))
412 }
413}
414
415#[derive(Debug, Clone)]
417pub enum OutputTypeSpec {
418 All {
419 path: Option<OutputFile>,
420 },
421 Typed {
422 output_type: OutputType,
423 path: Option<OutputFile>,
424 },
425}
426
427#[cfg(feature = "std")]
428impl clap::builder::ValueParserFactory for OutputTypeSpec {
429 type Parser = OutputTypeParser;
430
431 fn value_parser() -> Self::Parser {
432 OutputTypeParser
433 }
434}
435
436#[doc(hidden)]
437#[derive(Clone)]
438#[cfg(feature = "std")]
439pub struct OutputTypeParser;
440
441#[cfg(feature = "std")]
442impl clap::builder::TypedValueParser for OutputTypeParser {
443 type Value = OutputTypeSpec;
444
445 fn possible_values(
446 &self,
447 ) -> Option<alloc::boxed::Box<dyn Iterator<Item = clap::builder::PossibleValue> + '_>> {
448 use alloc::boxed::Box;
449
450 use clap::builder::PossibleValue;
451 Some(Box::new(
452 [
453 PossibleValue::new("ast").help("Abstract Syntax Tree (text)"),
454 PossibleValue::new("hir").help("High-level Intermediate Representation (text)"),
455 PossibleValue::new("masm").help("Miden Assembly (text)"),
456 PossibleValue::new("mast").help("Merkelized Abstract Syntax Tree (text)"),
457 PossibleValue::new("masl").help("Merkelized Abstract Syntax Tree (binary)"),
458 PossibleValue::new("masp").help("Miden Assembly Package Format (binary)"),
459 PossibleValue::new("all").help("All of the above"),
460 ]
461 .into_iter(),
462 ))
463 }
464
465 fn parse_ref(
466 &self,
467 _cmd: &clap::Command,
468 _arg: Option<&clap::Arg>,
469 value: &std::ffi::OsStr,
470 ) -> Result<Self::Value, clap::error::Error> {
471 use clap::error::{Error, ErrorKind};
472
473 let output_type = value.to_str().ok_or_else(|| Error::new(ErrorKind::InvalidUtf8))?;
474
475 let (shorthand, path) = match output_type.split_once('=') {
476 None => (output_type, None),
477 Some((shorthand, "-")) => (shorthand, Some(OutputFile::Stdout)),
478 Some((shorthand, path)) => (shorthand, Some(OutputFile::Real(PathBuf::from(path)))),
479 };
480 if shorthand == "all" {
481 return Ok(OutputTypeSpec::All { path });
482 }
483 let output_type = shorthand.parse::<OutputType>().map_err(|_| {
484 Error::raw(
485 ErrorKind::InvalidValue,
486 format!(
487 "invalid output type: `{shorthand}` - expected one of: {display}",
488 display = OutputType::shorthand_display()
489 ),
490 )
491 })?;
492 Ok(OutputTypeSpec::Typed { output_type, path })
493 }
494}
495
496#[cfg(feature = "std")]
497trait PathMut {
498 fn with_stem(self, stem: impl AsRef<std::ffi::OsStr>) -> PathBuf;
499 fn with_stem_and_extension(
500 self,
501 stem: impl AsRef<std::ffi::OsStr>,
502 ext: impl AsRef<std::ffi::OsStr>,
503 ) -> PathBuf;
504}
505#[cfg(feature = "std")]
506impl PathMut for &std::path::Path {
507 fn with_stem(self, stem: impl AsRef<std::ffi::OsStr>) -> std::path::PathBuf {
508 let mut path = self.with_file_name(stem);
509 if let Some(ext) = self.extension() {
510 path.set_extension(ext);
511 }
512 path
513 }
514
515 fn with_stem_and_extension(
516 self,
517 stem: impl AsRef<std::ffi::OsStr>,
518 ext: impl AsRef<std::ffi::OsStr>,
519 ) -> std::path::PathBuf {
520 let mut path = self.with_file_name(stem);
521 path.set_extension(ext);
522 path
523 }
524}
525#[cfg(feature = "std")]
526impl PathMut for std::path::PathBuf {
527 fn with_stem(mut self, stem: impl AsRef<std::ffi::OsStr>) -> std::path::PathBuf {
528 if let Some(ext) = self.extension() {
529 let ext = ext.to_string_lossy().into_owned();
530 self.with_stem_and_extension(stem, ext)
531 } else {
532 self.set_file_name(stem);
533 self
534 }
535 }
536
537 fn with_stem_and_extension(
538 mut self,
539 stem: impl AsRef<std::ffi::OsStr>,
540 ext: impl AsRef<std::ffi::OsStr>,
541 ) -> std::path::PathBuf {
542 self.set_file_name(stem);
543 self.set_extension(ext);
544 self
545 }
546}