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 fn name(&self) -> Option<Symbol>;
12 fn output_type(&self, mode: OutputMode) -> OutputType;
14 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 fn write_to_stdout(&self, session: &Session) -> anyhow::Result<()>;
29 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
65pub 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 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 Ok(err) => unsafe { core::ptr::read(&*err) },
262 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 => 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}