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 miden_assembly::ast::Module {
209    fn name(&self) -> Option<Symbol> {
210        Some(Symbol::intern(self.path().to_string()))
211    }
212
213    fn output_type(&self, _mode: OutputMode) -> OutputType {
214        OutputType::Masm
215    }
216
217    fn write_to<W: Writer>(
218        &self,
219        mut writer: W,
220        mode: OutputMode,
221        _session: &Session,
222    ) -> anyhow::Result<()> {
223        assert_eq!(mode, OutputMode::Text, "masm syntax trees do not support binary mode");
224        writer.write_fmt(format_args!("{self}\n"))
225    }
226}
227
228#[cfg(feature = "std")]
229macro_rules! serialize_into {
230    ($serializable:ident, $writer:expr) => {
231        // NOTE: We're protecting against unwinds here due to i/o errors that will get turned into
232        // panics if writing to the underlying file fails. This is because ByteWriter does not have
233        // fallible APIs, thus WriteAdapter has to panic if writes fail. This could be fixed, but
234        // that has to happen upstream in winterfell
235        std::panic::catch_unwind(move || {
236            let mut writer = ByteWriterAdapter($writer);
237            $serializable.write_into(&mut writer)
238        })
239        .map_err(|p| {
240            match p.downcast::<anyhow::Error>() {
241                // SAFETY: It is guaranteed to be safe to read Box<anyhow::Error>
242                Ok(err) => unsafe { core::ptr::read(&*err) },
243                // Propagate unknown panics
244                Err(err) => std::panic::resume_unwind(err),
245            }
246        })
247    };
248}
249
250struct ByteWriterAdapter<'a, W>(&'a mut W);
251impl<W: Writer> miden_assembly::utils::ByteWriter for ByteWriterAdapter<'_, W> {
252    fn write_u8(&mut self, value: u8) {
253        self.0.write_all(&[value]).unwrap()
254    }
255
256    fn write_bytes(&mut self, values: &[u8]) {
257        self.0.write_all(values).unwrap()
258    }
259}
260
261impl Emit for miden_assembly::Library {
262    fn name(&self) -> Option<Symbol> {
263        None
264    }
265
266    fn output_type(&self, mode: OutputMode) -> OutputType {
267        match mode {
268            OutputMode::Text => OutputType::Mast,
269            OutputMode::Binary => OutputType::Masl,
270        }
271    }
272
273    fn write_to<W: Writer>(
274        &self,
275        mut writer: W,
276        mode: OutputMode,
277        _session: &Session,
278    ) -> anyhow::Result<()> {
279        struct LibraryTextFormatter<'a>(&'a miden_assembly::Library);
280        impl miden_core::prettier::PrettyPrint for LibraryTextFormatter<'_> {
281            fn render(&self) -> miden_core::prettier::Document {
282                use miden_core::prettier::*;
283
284                let mast_forest = self.0.mast_forest();
285                let mut library_doc = Document::Empty;
286                for module_info in self.0.module_infos() {
287                    let mut fragments = vec![];
288                    for (_, info) in module_info.procedures() {
289                        if let Some(proc_node_id) = mast_forest.find_procedure_root(info.digest) {
290                            let proc = mast_forest
291                                .get_node_by_id(proc_node_id)
292                                .expect("malformed mast forest")
293                                .to_pretty_print(mast_forest)
294                                .render();
295                            fragments.push(indent(
296                                4,
297                                display(format!("procedure {} ({})", &info.name, &info.digest))
298                                    + nl()
299                                    + proc
300                                    + nl()
301                                    + const_text("end"),
302                            ));
303                        }
304                    }
305                    let module_doc = indent(
306                        4,
307                        display(format!("module {}", module_info.path()))
308                            + nl()
309                            + fragments
310                                .into_iter()
311                                .reduce(|l, r| l + nl() + nl() + r)
312                                .unwrap_or_default()
313                            + const_text("end"),
314                    );
315                    if matches!(library_doc, Document::Empty) {
316                        library_doc = module_doc;
317                    } else {
318                        library_doc += nl() + nl() + module_doc;
319                    }
320                }
321                library_doc
322            }
323        }
324        impl fmt::Display for LibraryTextFormatter<'_> {
325            #[inline]
326            fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
327                self.pretty_print(f)
328            }
329        }
330
331        match mode {
332            OutputMode::Text => writer.write_fmt(format_args!("{}", LibraryTextFormatter(self))),
333            OutputMode::Binary => {
334                let mut writer = ByteWriterAdapter(&mut writer);
335                self.write_into(&mut writer);
336                Ok(())
337            }
338        }
339    }
340}
341
342impl Emit for miden_core::Program {
343    fn name(&self) -> Option<Symbol> {
344        None
345    }
346
347    fn output_type(&self, mode: OutputMode) -> OutputType {
348        match mode {
349            OutputMode::Text => OutputType::Mast,
350            OutputMode::Binary => OutputType::Masl,
351        }
352    }
353
354    fn write_to<W: Writer>(
355        &self,
356        mut writer: W,
357        mode: OutputMode,
358        _session: &Session,
359    ) -> anyhow::Result<()> {
360        match mode {
361            //OutputMode::Text => writer.write_fmt(format_args!("{}", self)),
362            OutputMode::Text => unimplemented!("emitting mast in text form is currently broken"),
363            OutputMode::Binary => {
364                let mut writer = ByteWriterAdapter(&mut writer);
365                self.write_into(&mut writer);
366                Ok(())
367            }
368        }
369    }
370}
371
372#[cfg(feature = "std")]
373impl EmitExt for miden_core::Program {
374    fn write_to_file(
375        &self,
376        path: &std::path::Path,
377        mode: OutputMode,
378        session: &Session,
379    ) -> anyhow::Result<()> {
380        if let Some(dir) = path.parent() {
381            std::fs::create_dir_all(dir)?;
382        }
383        let mut file = std::fs::File::create(path)?;
384        match mode {
385            OutputMode::Text => self.write_to(&mut file, mode, session),
386            OutputMode::Binary => serialize_into!(self, &mut file),
387        }
388    }
389
390    fn write_to_stdout(&self, session: &Session) -> anyhow::Result<()> {
391        use std::io::IsTerminal;
392        let mut stdout = std::io::stdout().lock();
393        let mode = if stdout.is_terminal() {
394            OutputMode::Text
395        } else {
396            OutputMode::Binary
397        };
398        match mode {
399            OutputMode::Text => self.write_to(&mut stdout, mode, session),
400            OutputMode::Binary => serialize_into!(self, &mut stdout),
401        }
402    }
403}
404
405impl Emit for miden_mast_package::Package {
406    fn name(&self) -> Option<Symbol> {
407        Some(Symbol::intern(&self.name))
408    }
409
410    fn output_type(&self, mode: OutputMode) -> OutputType {
411        match mode {
412            OutputMode::Text => OutputType::Mast,
413            OutputMode::Binary => OutputType::Masp,
414        }
415    }
416
417    fn write_to<W: Writer>(
418        &self,
419        mut writer: W,
420        mode: OutputMode,
421        session: &Session,
422    ) -> anyhow::Result<()> {
423        match mode {
424            OutputMode::Text => match self.mast {
425                miden_mast_package::MastArtifact::Executable(ref prog) => {
426                    prog.write_to(writer, mode, session)
427                }
428                miden_mast_package::MastArtifact::Library(ref lib) => {
429                    lib.write_to(writer, mode, session)
430                }
431            },
432            OutputMode::Binary => {
433                let bytes = self.to_bytes();
434                writer.write_all(bytes.as_slice())
435            }
436        }
437    }
438}
439
440impl Emit for MastArtifact {
441    fn name(&self) -> Option<Symbol> {
442        None
443    }
444
445    fn output_type(&self, mode: OutputMode) -> OutputType {
446        match mode {
447            OutputMode::Text => OutputType::Mast,
448            OutputMode::Binary => OutputType::Masl,
449        }
450    }
451
452    fn write_to<W: Writer>(
453        &self,
454        writer: W,
455        mode: OutputMode,
456        session: &Session,
457    ) -> anyhow::Result<()> {
458        match self {
459            Self::Executable(ref prog) => {
460                if matches!(mode, OutputMode::Binary) {
461                    log::warn!(
462                        "unable to write 'masl' output type for miden_core::Program: skipping.."
463                    );
464                }
465                prog.write_to(writer, mode, session)
466            }
467            Self::Library(ref lib) => lib.write_to(writer, mode, session),
468        }
469    }
470}