midenc_session/
emit.rs

1use alloc::{boxed::Box, fmt, format, string::ToString, sync::Arc, vec};
2
3use miden_core::{prettier::PrettyPrint, utils::Serializable};
4use miden_mast_package::MastArtifact;
5use midenc_hir_symbol::Symbol;
6
7use crate::{OutputMode, OutputType, Session};
8
9pub trait Emit {
10    /// The name of this item, if applicable
11    fn name(&self) -> Option<Symbol>;
12    /// The output type associated with this item and the given `mode`
13    fn output_type(&self, mode: OutputMode) -> OutputType;
14    /// Write this item to the given [std::io::Write] handle, using `mode` to determine the output
15    /// type
16    fn write_to<W: Writer>(
17        &self,
18        writer: W,
19        mode: OutputMode,
20        session: &Session,
21    ) -> anyhow::Result<()>;
22}
23
24#[cfg(feature = "std")]
25pub trait EmitExt: Emit {
26    /// Write this item to standard output, inferring the best [OutputMode] based on whether or not
27    /// stdout is a tty or not
28    fn write_to_stdout(&self, session: &Session) -> anyhow::Result<()>;
29    /// Write this item to the given file path, using `mode` to determine the output type
30    fn write_to_file(
31        &self,
32        path: &std::path::Path,
33        mode: OutputMode,
34        session: &Session,
35    ) -> anyhow::Result<()>;
36}
37
38#[cfg(feature = "std")]
39impl<T: ?Sized + Emit> EmitExt for T {
40    default fn write_to_stdout(&self, session: &Session) -> anyhow::Result<()> {
41        use std::io::IsTerminal;
42        let stdout = std::io::stdout().lock();
43        let mode = if stdout.is_terminal() {
44            OutputMode::Text
45        } else {
46            OutputMode::Binary
47        };
48        self.write_to(stdout, mode, session)
49    }
50
51    default fn write_to_file(
52        &self,
53        path: &std::path::Path,
54        mode: OutputMode,
55        session: &Session,
56    ) -> anyhow::Result<()> {
57        if let Some(dir) = path.parent() {
58            std::fs::create_dir_all(dir)?;
59        }
60        let file = std::fs::File::create(path)?;
61        self.write_to(file, mode, session)
62    }
63}
64
65/// A trait that provides a subset of the [std::io::Write] functionality that is usable in no-std
66/// contexts.
67pub trait Writer {
68    fn write_fmt(&mut self, fmt: core::fmt::Arguments<'_>) -> anyhow::Result<()>;
69    fn write_all(&mut self, buf: &[u8]) -> anyhow::Result<()>;
70}
71
72#[cfg(feature = "std")]
73impl<W: ?Sized + std::io::Write> Writer for W {
74    fn write_fmt(&mut self, fmt: core::fmt::Arguments<'_>) -> anyhow::Result<()> {
75        <W as std::io::Write>::write_fmt(self, fmt).map_err(|err| err.into())
76    }
77
78    fn write_all(&mut self, buf: &[u8]) -> anyhow::Result<()> {
79        <W as std::io::Write>::write_all(self, buf).map_err(|err| err.into())
80    }
81}
82
83#[cfg(not(feature = "std"))]
84impl Writer for alloc::vec::Vec<u8> {
85    fn write_fmt(&mut self, fmt: core::fmt::Arguments<'_>) -> anyhow::Result<()> {
86        if let Some(s) = fmt.as_str() {
87            self.extend(s.as_bytes());
88        } else {
89            let formatted = fmt.to_string();
90            self.extend(formatted.as_bytes());
91        }
92        Ok(())
93    }
94
95    fn write_all(&mut self, buf: &[u8]) -> anyhow::Result<()> {
96        self.extend(buf);
97        Ok(())
98    }
99}
100
101#[cfg(not(feature = "std"))]
102impl Writer for alloc::string::String {
103    fn write_fmt(&mut self, fmt: core::fmt::Arguments<'_>) -> anyhow::Result<()> {
104        if let Some(s) = fmt.as_str() {
105            self.push_str(s);
106        } else {
107            let formatted = fmt.to_string();
108            self.push_str(&formatted);
109        }
110        Ok(())
111    }
112
113    fn write_all(&mut self, buf: &[u8]) -> anyhow::Result<()> {
114        let s = core::str::from_utf8(buf)?;
115        self.push_str(s);
116        Ok(())
117    }
118}
119
120impl<T: Emit> Emit for &T {
121    #[inline]
122    fn name(&self) -> Option<Symbol> {
123        (**self).name()
124    }
125
126    #[inline]
127    fn output_type(&self, mode: OutputMode) -> OutputType {
128        (**self).output_type(mode)
129    }
130
131    #[inline]
132    fn write_to<W: Writer>(
133        &self,
134        writer: W,
135        mode: OutputMode,
136        session: &Session,
137    ) -> anyhow::Result<()> {
138        (**self).write_to(writer, mode, session)
139    }
140}
141
142impl<T: Emit> Emit for &mut T {
143    #[inline]
144    fn name(&self) -> Option<Symbol> {
145        (**self).name()
146    }
147
148    #[inline]
149    fn output_type(&self, mode: OutputMode) -> OutputType {
150        (**self).output_type(mode)
151    }
152
153    #[inline]
154    fn write_to<W: Writer>(
155        &self,
156        writer: W,
157        mode: OutputMode,
158        session: &Session,
159    ) -> anyhow::Result<()> {
160        (**self).write_to(writer, mode, session)
161    }
162}
163
164impl<T: Emit> Emit for Box<T> {
165    #[inline]
166    fn name(&self) -> Option<Symbol> {
167        (**self).name()
168    }
169
170    #[inline]
171    fn output_type(&self, mode: OutputMode) -> OutputType {
172        (**self).output_type(mode)
173    }
174
175    #[inline]
176    fn write_to<W: Writer>(
177        &self,
178        writer: W,
179        mode: OutputMode,
180        session: &Session,
181    ) -> anyhow::Result<()> {
182        (**self).write_to(writer, mode, session)
183    }
184}
185
186impl<T: Emit> Emit for Arc<T> {
187    #[inline]
188    fn name(&self) -> Option<Symbol> {
189        (**self).name()
190    }
191
192    #[inline]
193    fn output_type(&self, mode: OutputMode) -> OutputType {
194        (**self).output_type(mode)
195    }
196
197    #[inline]
198    fn write_to<W: Writer>(
199        &self,
200        writer: W,
201        mode: OutputMode,
202        session: &Session,
203    ) -> anyhow::Result<()> {
204        (**self).write_to(writer, mode, session)
205    }
206}
207
208impl Emit for alloc::string::String {
209    fn name(&self) -> Option<Symbol> {
210        None
211    }
212
213    fn output_type(&self, _mode: OutputMode) -> OutputType {
214        OutputType::Hir
215    }
216
217    fn write_to<W: Writer>(
218        &self,
219        mut writer: W,
220        _mode: OutputMode,
221        _session: &Session,
222    ) -> anyhow::Result<()> {
223        writer.write_fmt(format_args!("{self}\n"))
224    }
225}
226
227impl Emit for miden_assembly::ast::Module {
228    fn name(&self) -> Option<Symbol> {
229        Some(Symbol::intern(self.path().to_string()))
230    }
231
232    fn output_type(&self, _mode: OutputMode) -> OutputType {
233        OutputType::Masm
234    }
235
236    fn write_to<W: Writer>(
237        &self,
238        mut writer: W,
239        mode: OutputMode,
240        _session: &Session,
241    ) -> anyhow::Result<()> {
242        assert_eq!(mode, OutputMode::Text, "masm syntax trees do not support binary mode");
243        writer.write_fmt(format_args!("{self}\n"))
244    }
245}
246
247#[cfg(feature = "std")]
248macro_rules! serialize_into {
249    ($serializable:ident, $writer:expr) => {
250        // NOTE: We're protecting against unwinds here due to i/o errors that will get turned into
251        // panics if writing to the underlying file fails. This is because ByteWriter does not have
252        // fallible APIs, thus WriteAdapter has to panic if writes fail. This could be fixed, but
253        // that has to happen upstream in winterfell
254        std::panic::catch_unwind(move || {
255            let mut writer = ByteWriterAdapter($writer);
256            $serializable.write_into(&mut writer)
257        })
258        .map_err(|p| {
259            match p.downcast::<anyhow::Error>() {
260                // SAFETY: It is guaranteed to be safe to read Box<anyhow::Error>
261                Ok(err) => unsafe { core::ptr::read(&*err) },
262                // Propagate unknown panics
263                Err(err) => std::panic::resume_unwind(err),
264            }
265        })
266    };
267}
268
269struct ByteWriterAdapter<'a, W>(&'a mut W);
270impl<W: Writer> miden_assembly::utils::ByteWriter for ByteWriterAdapter<'_, W> {
271    fn write_u8(&mut self, value: u8) {
272        self.0.write_all(&[value]).unwrap()
273    }
274
275    fn write_bytes(&mut self, values: &[u8]) {
276        self.0.write_all(values).unwrap()
277    }
278}
279
280impl Emit for miden_assembly::Library {
281    fn name(&self) -> Option<Symbol> {
282        None
283    }
284
285    fn output_type(&self, mode: OutputMode) -> OutputType {
286        match mode {
287            OutputMode::Text => OutputType::Mast,
288            OutputMode::Binary => OutputType::Masl,
289        }
290    }
291
292    fn write_to<W: Writer>(
293        &self,
294        mut writer: W,
295        mode: OutputMode,
296        _session: &Session,
297    ) -> anyhow::Result<()> {
298        struct LibraryTextFormatter<'a>(&'a miden_assembly::Library);
299        impl miden_core::prettier::PrettyPrint for LibraryTextFormatter<'_> {
300            fn render(&self) -> miden_core::prettier::Document {
301                use miden_core::{mast::MastNodeExt, prettier::*};
302
303                let mast_forest = self.0.mast_forest();
304                let mut library_doc = Document::Empty;
305                for module_info in self.0.module_infos() {
306                    let mut fragments = vec![];
307                    for (_, info) in module_info.procedures() {
308                        if let Some(proc_node_id) = mast_forest.find_procedure_root(info.digest) {
309                            let proc = mast_forest
310                                .get_node_by_id(proc_node_id)
311                                .expect("malformed mast forest")
312                                .to_pretty_print(mast_forest)
313                                .render();
314                            fragments.push(indent(
315                                4,
316                                display(format!("procedure {} ({})", &info.name, &info.digest))
317                                    + nl()
318                                    + proc
319                                    + nl()
320                                    + const_text("end"),
321                            ));
322                        }
323                    }
324                    let module_doc = indent(
325                        4,
326                        display(format!("module {}", module_info.path()))
327                            + nl()
328                            + fragments
329                                .into_iter()
330                                .reduce(|l, r| l + nl() + nl() + r)
331                                .unwrap_or_default()
332                            + const_text("end"),
333                    );
334                    if matches!(library_doc, Document::Empty) {
335                        library_doc = module_doc;
336                    } else {
337                        library_doc += nl() + nl() + module_doc;
338                    }
339                }
340                library_doc
341            }
342        }
343        impl fmt::Display for LibraryTextFormatter<'_> {
344            #[inline]
345            fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
346                self.pretty_print(f)
347            }
348        }
349
350        match mode {
351            OutputMode::Text => writer.write_fmt(format_args!("{}", LibraryTextFormatter(self))),
352            OutputMode::Binary => {
353                let mut writer = ByteWriterAdapter(&mut writer);
354                self.write_into(&mut writer);
355                Ok(())
356            }
357        }
358    }
359}
360
361impl Emit for miden_core::Program {
362    fn name(&self) -> Option<Symbol> {
363        None
364    }
365
366    fn output_type(&self, mode: OutputMode) -> OutputType {
367        match mode {
368            OutputMode::Text => OutputType::Mast,
369            OutputMode::Binary => OutputType::Masl,
370        }
371    }
372
373    fn write_to<W: Writer>(
374        &self,
375        mut writer: W,
376        mode: OutputMode,
377        _session: &Session,
378    ) -> anyhow::Result<()> {
379        match mode {
380            //OutputMode::Text => writer.write_fmt(format_args!("{}", self)),
381            OutputMode::Text => unimplemented!("emitting mast in text form is currently broken"),
382            OutputMode::Binary => {
383                let mut writer = ByteWriterAdapter(&mut writer);
384                self.write_into(&mut writer);
385                Ok(())
386            }
387        }
388    }
389}
390
391#[cfg(feature = "std")]
392impl EmitExt for miden_core::Program {
393    fn write_to_file(
394        &self,
395        path: &std::path::Path,
396        mode: OutputMode,
397        session: &Session,
398    ) -> anyhow::Result<()> {
399        if let Some(dir) = path.parent() {
400            std::fs::create_dir_all(dir)?;
401        }
402        let mut file = std::fs::File::create(path)?;
403        match mode {
404            OutputMode::Text => self.write_to(&mut file, mode, session),
405            OutputMode::Binary => serialize_into!(self, &mut file),
406        }
407    }
408
409    fn write_to_stdout(&self, session: &Session) -> anyhow::Result<()> {
410        use std::io::IsTerminal;
411        let mut stdout = std::io::stdout().lock();
412        let mode = if stdout.is_terminal() {
413            OutputMode::Text
414        } else {
415            OutputMode::Binary
416        };
417        match mode {
418            OutputMode::Text => self.write_to(&mut stdout, mode, session),
419            OutputMode::Binary => serialize_into!(self, &mut stdout),
420        }
421    }
422}
423
424impl Emit for miden_mast_package::Package {
425    fn name(&self) -> Option<Symbol> {
426        Some(Symbol::intern(&self.name))
427    }
428
429    fn output_type(&self, mode: OutputMode) -> OutputType {
430        match mode {
431            OutputMode::Text => OutputType::Mast,
432            OutputMode::Binary => OutputType::Masp,
433        }
434    }
435
436    fn write_to<W: Writer>(
437        &self,
438        mut writer: W,
439        mode: OutputMode,
440        session: &Session,
441    ) -> anyhow::Result<()> {
442        match mode {
443            OutputMode::Text => match self.mast {
444                miden_mast_package::MastArtifact::Executable(ref prog) => {
445                    prog.write_to(writer, mode, session)
446                }
447                miden_mast_package::MastArtifact::Library(ref lib) => {
448                    lib.write_to(writer, mode, session)
449                }
450            },
451            OutputMode::Binary => {
452                let bytes = self.to_bytes();
453                writer.write_all(bytes.as_slice())
454            }
455        }
456    }
457}
458
459impl Emit for MastArtifact {
460    fn name(&self) -> Option<Symbol> {
461        None
462    }
463
464    fn output_type(&self, mode: OutputMode) -> OutputType {
465        match mode {
466            OutputMode::Text => OutputType::Mast,
467            OutputMode::Binary => OutputType::Masl,
468        }
469    }
470
471    fn write_to<W: Writer>(
472        &self,
473        mut writer: W,
474        _mode: OutputMode,
475        _session: &Session,
476    ) -> anyhow::Result<()> {
477        let mut writer = ByteWriterAdapter(&mut writer);
478        self.write_into(&mut writer);
479        Ok(())
480    }
481}