midenc_session/
emit.rs

1use alloc::{boxed::Box, fmt, format, string::ToString, sync::Arc, vec};
2use std::{fs::File, io::Write, path::Path};
3
4use miden_core::{prettier::PrettyPrint, utils::Serializable};
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 standard output, inferring the best [OutputMode] based on whether or not
15    /// stdout is a tty or not
16    fn write_to_stdout(&self, session: &Session) -> std::io::Result<()> {
17        use std::io::IsTerminal;
18        let stdout = std::io::stdout().lock();
19        let mode = if stdout.is_terminal() {
20            OutputMode::Text
21        } else {
22            OutputMode::Binary
23        };
24        self.write_to(stdout, mode, session)
25    }
26    /// Write this item to the given file path, using `mode` to determine the output type
27    fn write_to_file(
28        &self,
29        path: &Path,
30        mode: OutputMode,
31        session: &Session,
32    ) -> std::io::Result<()> {
33        if let Some(dir) = path.parent() {
34            std::fs::create_dir_all(dir)?;
35        }
36        let file = File::create(path)?;
37        self.write_to(file, mode, session)
38    }
39    /// Write this item to the given [std::io::Write] handle, using `mode` to determine the output
40    /// type
41    fn write_to<W: Write>(
42        &self,
43        writer: W,
44        mode: OutputMode,
45        session: &Session,
46    ) -> std::io::Result<()>;
47}
48
49impl<'a, T: Emit> Emit for &'a T {
50    #[inline]
51    fn name(&self) -> Option<Symbol> {
52        (**self).name()
53    }
54
55    #[inline]
56    fn output_type(&self, mode: OutputMode) -> OutputType {
57        (**self).output_type(mode)
58    }
59
60    #[inline]
61    fn write_to_stdout(&self, session: &Session) -> std::io::Result<()> {
62        (**self).write_to_stdout(session)
63    }
64
65    #[inline]
66    fn write_to_file(
67        &self,
68        path: &Path,
69        mode: OutputMode,
70        session: &Session,
71    ) -> std::io::Result<()> {
72        (**self).write_to_file(path, mode, session)
73    }
74
75    #[inline]
76    fn write_to<W: Write>(
77        &self,
78        writer: W,
79        mode: OutputMode,
80        session: &Session,
81    ) -> std::io::Result<()> {
82        (**self).write_to(writer, mode, session)
83    }
84}
85
86impl<'a, T: Emit> Emit for &'a mut T {
87    #[inline]
88    fn name(&self) -> Option<Symbol> {
89        (**self).name()
90    }
91
92    #[inline]
93    fn output_type(&self, mode: OutputMode) -> OutputType {
94        (**self).output_type(mode)
95    }
96
97    #[inline]
98    fn write_to_stdout(&self, session: &Session) -> std::io::Result<()> {
99        (**self).write_to_stdout(session)
100    }
101
102    #[inline]
103    fn write_to_file(
104        &self,
105        path: &Path,
106        mode: OutputMode,
107        session: &Session,
108    ) -> std::io::Result<()> {
109        (**self).write_to_file(path, mode, session)
110    }
111
112    #[inline]
113    fn write_to<W: Write>(
114        &self,
115        writer: W,
116        mode: OutputMode,
117        session: &Session,
118    ) -> std::io::Result<()> {
119        (**self).write_to(writer, mode, session)
120    }
121}
122
123impl<T: Emit> Emit for Box<T> {
124    #[inline]
125    fn name(&self) -> Option<Symbol> {
126        (**self).name()
127    }
128
129    #[inline]
130    fn output_type(&self, mode: OutputMode) -> OutputType {
131        (**self).output_type(mode)
132    }
133
134    #[inline]
135    fn write_to_stdout(&self, session: &Session) -> std::io::Result<()> {
136        (**self).write_to_stdout(session)
137    }
138
139    #[inline]
140    fn write_to_file(
141        &self,
142        path: &Path,
143        mode: OutputMode,
144        session: &Session,
145    ) -> std::io::Result<()> {
146        (**self).write_to_file(path, mode, session)
147    }
148
149    #[inline]
150    fn write_to<W: Write>(
151        &self,
152        writer: W,
153        mode: OutputMode,
154        session: &Session,
155    ) -> std::io::Result<()> {
156        (**self).write_to(writer, mode, session)
157    }
158}
159
160impl<T: Emit> Emit for Arc<T> {
161    #[inline]
162    fn name(&self) -> Option<Symbol> {
163        (**self).name()
164    }
165
166    #[inline]
167    fn output_type(&self, mode: OutputMode) -> OutputType {
168        (**self).output_type(mode)
169    }
170
171    #[inline]
172    fn write_to_stdout(&self, session: &Session) -> std::io::Result<()> {
173        (**self).write_to_stdout(session)
174    }
175
176    #[inline]
177    fn write_to_file(
178        &self,
179        path: &Path,
180        mode: OutputMode,
181        session: &Session,
182    ) -> std::io::Result<()> {
183        (**self).write_to_file(path, mode, session)
184    }
185
186    #[inline]
187    fn write_to<W: Write>(
188        &self,
189        writer: W,
190        mode: OutputMode,
191        session: &Session,
192    ) -> std::io::Result<()> {
193        (**self).write_to(writer, mode, session)
194    }
195}
196
197impl Emit for miden_assembly::ast::Module {
198    fn name(&self) -> Option<Symbol> {
199        Some(Symbol::intern(self.path().to_string()))
200    }
201
202    fn output_type(&self, _mode: OutputMode) -> OutputType {
203        OutputType::Masm
204    }
205
206    fn write_to<W: Write>(
207        &self,
208        mut writer: W,
209        mode: OutputMode,
210        _session: &Session,
211    ) -> std::io::Result<()> {
212        assert_eq!(mode, OutputMode::Text, "masm syntax trees do not support binary mode");
213        writer.write_fmt(format_args!("{}\n", self))
214    }
215}
216
217macro_rules! serialize_into {
218    ($serializable:ident, $writer:ident) => {
219        // NOTE: We're protecting against unwinds here due to i/o errors that will get turned into
220        // panics if writing to the underlying file fails. This is because ByteWriter does not have
221        // fallible APIs, thus WriteAdapter has to panic if writes fail. This could be fixed, but
222        // that has to happen upstream in winterfell
223        std::panic::catch_unwind(move || {
224            let mut writer = $writer;
225            $serializable.write_into(&mut writer)
226        })
227        .map_err(|p| {
228            match p.downcast::<std::io::Error>() {
229                // SAFETY: It is guaranteed to be safe to read Box<std::io::Error>
230                Ok(err) => unsafe { core::ptr::read(&*err) },
231                // Propagate unknown panics
232                Err(err) => std::panic::resume_unwind(err),
233            }
234        })
235    };
236}
237
238impl Emit for miden_assembly::Library {
239    fn name(&self) -> Option<Symbol> {
240        None
241    }
242
243    fn output_type(&self, mode: OutputMode) -> OutputType {
244        match mode {
245            OutputMode::Text => OutputType::Mast,
246            OutputMode::Binary => OutputType::Masl,
247        }
248    }
249
250    fn write_to<W: Write>(
251        &self,
252        mut writer: W,
253        mode: OutputMode,
254        _session: &Session,
255    ) -> std::io::Result<()> {
256        struct LibraryTextFormatter<'a>(&'a miden_assembly::Library);
257        impl<'a> miden_core::prettier::PrettyPrint for LibraryTextFormatter<'a> {
258            fn render(&self) -> miden_core::prettier::Document {
259                use miden_core::prettier::*;
260
261                let mast_forest = self.0.mast_forest();
262                let mut library_doc = Document::Empty;
263                for module_info in self.0.module_infos() {
264                    let mut fragments = vec![];
265                    for (_, info) in module_info.procedures() {
266                        if let Some(proc_node_id) = mast_forest.find_procedure_root(info.digest) {
267                            let proc = mast_forest
268                                .get_node_by_id(proc_node_id)
269                                .expect("malformed mast forest")
270                                .to_pretty_print(mast_forest)
271                                .render();
272                            fragments.push(indent(
273                                4,
274                                display(format!("procedure {} ({})", &info.name, &info.digest))
275                                    + nl()
276                                    + proc
277                                    + nl()
278                                    + const_text("end"),
279                            ));
280                        }
281                    }
282                    let module_doc = indent(
283                        4,
284                        display(format!("module {}", module_info.path()))
285                            + nl()
286                            + fragments
287                                .into_iter()
288                                .reduce(|l, r| l + nl() + nl() + r)
289                                .unwrap_or_default()
290                            + const_text("end"),
291                    );
292                    if matches!(library_doc, Document::Empty) {
293                        library_doc = module_doc;
294                    } else {
295                        library_doc += nl() + nl() + module_doc;
296                    }
297                }
298                library_doc
299            }
300        }
301        impl<'a> fmt::Display for LibraryTextFormatter<'a> {
302            #[inline]
303            fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
304                self.pretty_print(f)
305            }
306        }
307
308        match mode {
309            OutputMode::Text => writer.write_fmt(format_args!("{}", LibraryTextFormatter(self))),
310            OutputMode::Binary => {
311                self.write_into(&mut writer);
312                Ok(())
313            }
314        }
315    }
316}
317
318impl Emit for miden_core::Program {
319    fn name(&self) -> Option<Symbol> {
320        None
321    }
322
323    fn output_type(&self, mode: OutputMode) -> OutputType {
324        match mode {
325            OutputMode::Text => OutputType::Mast,
326            OutputMode::Binary => OutputType::Masl,
327        }
328    }
329
330    fn write_to_file(
331        &self,
332        path: &Path,
333        mode: OutputMode,
334        session: &Session,
335    ) -> std::io::Result<()> {
336        if let Some(dir) = path.parent() {
337            std::fs::create_dir_all(dir)?;
338        }
339        let mut file = std::fs::File::create(path)?;
340        match mode {
341            OutputMode::Text => self.write_to(&mut file, mode, session),
342            OutputMode::Binary => serialize_into!(self, file),
343        }
344    }
345
346    fn write_to_stdout(&self, session: &Session) -> std::io::Result<()> {
347        use std::io::IsTerminal;
348        let mut stdout = std::io::stdout().lock();
349        let mode = if stdout.is_terminal() {
350            OutputMode::Text
351        } else {
352            OutputMode::Binary
353        };
354        match mode {
355            OutputMode::Text => self.write_to(&mut stdout, mode, session),
356            OutputMode::Binary => serialize_into!(self, stdout),
357        }
358    }
359
360    fn write_to<W: Write>(
361        &self,
362        mut writer: W,
363        mode: OutputMode,
364        _session: &Session,
365    ) -> std::io::Result<()> {
366        match mode {
367            //OutputMode::Text => writer.write_fmt(format_args!("{}", self)),
368            OutputMode::Text => unimplemented!("emitting mast in text form is currently broken"),
369            OutputMode::Binary => {
370                self.write_into(&mut writer);
371                Ok(())
372            }
373        }
374    }
375}