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 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 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 Ok(err) => unsafe { core::ptr::read(&*err) },
243 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 => 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}