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