miden_assembly_syntax/ast/
module.rs

1use alloc::{boxed::Box, string::String, sync::Arc, vec::Vec};
2use core::fmt;
3
4use miden_core::{
5    AdviceMap,
6    utils::{ByteReader, ByteWriter, Deserializable, DeserializationError, Serializable},
7};
8use miden_debug_types::{SourceFile, SourceSpan, Span, Spanned};
9use miden_utils_diagnostics::Report;
10use smallvec::SmallVec;
11
12use super::{
13    Constant, DocString, EnumType, Export, FunctionType, Import, LocalNameResolver, ProcedureIndex,
14    ProcedureName, QualifiedProcedureName, ResolvedProcedure, TypeAlias, TypeDecl, Variant,
15};
16use crate::{
17    LibraryNamespace, LibraryPath,
18    ast::{self, AliasTarget, Ident, types},
19    parser::ModuleParser,
20    sema::SemanticAnalysisError,
21};
22
23// MODULE KIND
24// ================================================================================================
25
26/// Represents the kind of a [Module].
27///
28/// The three different kinds have slightly different rules on what syntax is allowed, as well as
29/// what operations can be performed in the body of procedures defined in the module. See the
30/// documentation for each variant for a summary of these differences.
31#[derive(Debug, Default, Copy, Clone, PartialEq, Eq)]
32#[repr(u8)]
33pub enum ModuleKind {
34    /// A library is a simple container of code that must be included into an executable module to
35    /// form a complete program.
36    ///
37    /// Library modules cannot use the `begin`..`end` syntax, which is used to define the
38    /// entrypoint procedure for an executable. Aside from this, they are free to use all other
39    /// MASM syntax.
40    #[default]
41    Library = 0,
42    /// An executable is the root module of a program, and provides the entrypoint for executing
43    /// that program.
44    ///
45    /// As the executable module is the root module, it may not export procedures for other modules
46    /// to depend on, it may only import and call externally-defined procedures, or private
47    /// locally-defined procedures.
48    ///
49    /// An executable module must contain a `begin`..`end` block.
50    Executable = 1,
51    /// A kernel is like a library module, but is special in a few ways:
52    ///
53    /// * Its code always executes in the root context, so it is stateful in a way that normal
54    ///   libraries cannot replicate. This can be used to provide core services that would otherwise
55    ///   not be possible to implement.
56    ///
57    /// * The procedures exported from the kernel may be the target of the `syscall` instruction,
58    ///   and in fact _must_ be called that way.
59    ///
60    /// * Kernels may not use `syscall` or `call` instructions internally.
61    Kernel = 2,
62}
63
64impl ModuleKind {
65    pub fn is_executable(&self) -> bool {
66        matches!(self, Self::Executable)
67    }
68
69    pub fn is_kernel(&self) -> bool {
70        matches!(self, Self::Kernel)
71    }
72
73    pub fn is_library(&self) -> bool {
74        matches!(self, Self::Library)
75    }
76}
77
78impl fmt::Display for ModuleKind {
79    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
80        match self {
81            Self::Library => f.write_str("library"),
82            Self::Executable => f.write_str("executable"),
83            Self::Kernel => f.write_str("kernel"),
84        }
85    }
86}
87
88impl Serializable for ModuleKind {
89    fn write_into<W: ByteWriter>(&self, target: &mut W) {
90        target.write_u8(*self as u8)
91    }
92}
93
94impl Deserializable for ModuleKind {
95    fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
96        match source.read_u8()? {
97            0 => Ok(Self::Library),
98            1 => Ok(Self::Executable),
99            2 => Ok(Self::Kernel),
100            n => Err(DeserializationError::InvalidValue(format!("invalid module kind tag: {n}"))),
101        }
102    }
103}
104
105// MODULE
106// ================================================================================================
107
108/// The abstract syntax tree for a single Miden Assembly module.
109///
110/// All module kinds share this AST representation, as they are largely identical. However, the
111/// [ModuleKind] dictates how the parsed module is semantically analyzed and validated.
112#[derive(Clone)]
113pub struct Module {
114    /// The span covering the entire definition of this module.
115    span: SourceSpan,
116    /// The documentation associated with this module.
117    ///
118    /// Module documentation is provided in Miden Assembly as a documentation comment starting on
119    /// the first line of the module. All other documentation comments are attached to the item the
120    /// precede in the module body.
121    docs: Option<DocString>,
122    /// The fully-qualified path representing the name of this module.
123    path: LibraryPath,
124    /// The kind of module this represents.
125    kind: ModuleKind,
126    /// The constants defined in the module body.
127    pub(crate) constants: Vec<Constant>,
128    /// The types defined in the module body.
129    pub(crate) types: Vec<TypeDecl>,
130    /// The imports defined in the module body.
131    pub(crate) imports: Vec<Import>,
132    /// The procedures (defined or re-exported) in the module body.
133    ///
134    /// NOTE: Despite the name, the procedures in this set are not necessarily exported, the
135    /// individual procedure item must be checked to determine visibility.
136    pub(crate) procedures: Vec<Export>,
137    /// AdviceMap that this module expects to be loaded in the host before executing.
138    pub(crate) advice_map: AdviceMap,
139}
140
141/// Constants
142impl Module {
143    /// File extension for a Assembly Module.
144    pub const FILE_EXTENSION: &'static str = "masm";
145
146    /// Name of the root module.
147    pub const ROOT: &'static str = "mod";
148
149    /// File name of the root module.
150    pub const ROOT_FILENAME: &'static str = "mod.masm";
151}
152
153/// Construction
154impl Module {
155    /// Creates a new [Module] with the specified `kind` and fully-qualified path, e.g.
156    /// `std::math::u64`.
157    pub fn new(kind: ModuleKind, path: LibraryPath) -> Self {
158        Self {
159            span: Default::default(),
160            docs: None,
161            path,
162            kind,
163            constants: Default::default(),
164            types: Default::default(),
165            imports: Default::default(),
166            procedures: Default::default(),
167            advice_map: Default::default(),
168        }
169    }
170
171    /// An alias for creating the default, but empty, `#kernel` [Module].
172    pub fn new_kernel() -> Self {
173        Self::new(ModuleKind::Kernel, LibraryNamespace::Kernel.into())
174    }
175
176    /// An alias for creating the default, but empty, `$exec` [Module].
177    pub fn new_executable() -> Self {
178        Self::new(ModuleKind::Executable, LibraryNamespace::Exec.into())
179    }
180
181    /// Specifies the source span in the source file in which this module was defined, that covers
182    /// the full definition of this module.
183    pub fn with_span(mut self, span: SourceSpan) -> Self {
184        self.span = span;
185        self
186    }
187
188    /// Sets the [LibraryPath] for this module
189    pub fn set_path(&mut self, path: LibraryPath) {
190        self.path = path;
191    }
192
193    /// Sets the [LibraryNamespace] for this module
194    pub fn set_namespace(&mut self, ns: LibraryNamespace) {
195        self.path.set_namespace(ns);
196    }
197
198    /// Sets the documentation for this module
199    pub fn set_docs(&mut self, docs: Option<Span<String>>) {
200        self.docs = docs.map(DocString::new);
201    }
202
203    /// Like [Module::with_span], but does not require ownership of the [Module].
204    pub fn set_span(&mut self, span: SourceSpan) {
205        self.span = span;
206    }
207
208    /// Defines a constant, raising an error if the constant conflicts with a previous definition
209    pub fn define_constant(&mut self, constant: Constant) -> Result<(), SemanticAnalysisError> {
210        for c in self.constants.iter() {
211            if c.name == constant.name {
212                return Err(SemanticAnalysisError::SymbolConflict {
213                    span: constant.span,
214                    prev_span: c.span,
215                });
216            }
217        }
218        self.constants.push(constant);
219        Ok(())
220    }
221
222    /// Defines a type alias, raising an error if the alias conflicts with a previous definition
223    pub fn define_type(&mut self, ty: TypeAlias) -> Result<(), SemanticAnalysisError> {
224        for t in self.types.iter() {
225            if t.name() == &ty.name {
226                return Err(SemanticAnalysisError::SymbolConflict {
227                    span: ty.span(),
228                    prev_span: t.span(),
229                });
230            }
231        }
232        self.types.push(ty.into());
233        Ok(())
234    }
235
236    /// Define a new enum type `ty`
237    ///
238    /// Returns `Err` if:
239    ///
240    /// * A type alias with the same name as the enum type is already defined
241    /// * Two or more variants of the given enum type have the same name
242    /// * A constant (including those implicitly defined by variants of other enums in this module)
243    ///   with the same name as any of the variants of the given enum type, is already defined
244    /// * The concrete type of the enumeration is not an integral type
245    pub fn define_enum(&mut self, ty: EnumType) -> Result<(), SemanticAnalysisError> {
246        let repr = ty.ty().clone();
247
248        if !repr.is_integer() {
249            return Err(SemanticAnalysisError::InvalidEnumRepr { span: ty.span() });
250        }
251
252        let (alias, variants) = ty.into_parts();
253
254        if let Some(prev) = self.types.iter().find(|t| t.name() == &alias.name) {
255            return Err(SemanticAnalysisError::SymbolConflict {
256                span: alias.span(),
257                prev_span: prev.span(),
258            });
259        }
260
261        let mut values = SmallVec::<[Span<u64>; 8]>::new_const();
262
263        for variant in variants {
264            // Validate that the discriminant value is unique amongst all variants
265            let value = match &variant.discriminant {
266                ast::ConstantExpr::Int(value) => (*value).map(|v| v.as_int()),
267                expr => {
268                    return Err(SemanticAnalysisError::InvalidEnumDiscriminant {
269                        span: expr.span(),
270                        repr,
271                    });
272                },
273            };
274            if let Some(prev) = values.iter().find(|v| *v == &value) {
275                return Err(SemanticAnalysisError::EnumDiscriminantConflict {
276                    span: value.span(),
277                    prev: prev.span(),
278                });
279            } else {
280                values.push(value);
281            }
282
283            // Validate that the discriminant is a valid instance of the `repr` type
284            variant.assert_instance_of(&repr)?;
285
286            let Variant { span, docs, name, discriminant } = variant;
287
288            self.define_constant(Constant { span, docs, name, value: discriminant })?;
289        }
290
291        self.types.push(alias.into());
292
293        Ok(())
294    }
295
296    /// Defines a procedure, raising an error if the procedure is invalid, or conflicts with a
297    /// previous definition
298    pub fn define_procedure(&mut self, export: Export) -> Result<(), SemanticAnalysisError> {
299        if self.is_kernel() && matches!(export, Export::Alias(_)) {
300            return Err(SemanticAnalysisError::ReexportFromKernel { span: export.span() });
301        }
302        if let Some(prev) = self.resolve(export.name()) {
303            let prev_span = prev.span();
304            Err(SemanticAnalysisError::SymbolConflict { span: export.span(), prev_span })
305        } else {
306            self.procedures.push(export);
307            Ok(())
308        }
309    }
310
311    /// Defines an import, raising an error if the import is invalid, or conflicts with a previous
312    /// definition.
313    pub fn define_import(&mut self, import: Import) -> Result<(), SemanticAnalysisError> {
314        if let Some(prev_import) = self.resolve_import(&import.name) {
315            let prev_span = prev_import.span;
316            return Err(SemanticAnalysisError::ImportConflict { span: import.span, prev_span });
317        }
318
319        if let Some(prev_defined) = self.procedures.iter().find(|e| e.name().eq(&import.name)) {
320            let prev_span = prev_defined.span();
321            return Err(SemanticAnalysisError::SymbolConflict { span: import.span, prev_span });
322        }
323
324        self.imports.push(import);
325
326        Ok(())
327    }
328}
329
330/// Parsing
331impl Module {
332    /// Parse a [Module], `name`, of the given [ModuleKind], from `source_file`.
333    pub fn parse(
334        name: LibraryPath,
335        kind: ModuleKind,
336        source_file: Arc<SourceFile>,
337    ) -> Result<Box<Self>, Report> {
338        let mut parser = Self::parser(kind);
339        parser.parse(name, source_file)
340    }
341
342    /// Get a [ModuleParser] for parsing modules of the provided [ModuleKind]
343    pub fn parser(kind: ModuleKind) -> ModuleParser {
344        ModuleParser::new(kind)
345    }
346}
347
348/// Metadata
349impl Module {
350    /// Get the name of this specific module, i.e. the last component of the [LibraryPath] that
351    /// represents the fully-qualified name of the module, e.g. `u64` in `std::math::u64`
352    pub fn name(&self) -> &str {
353        self.path.last()
354    }
355
356    /// Get the fully-qualified name of this module, e.g. `std::math::u64`
357    pub fn path(&self) -> &LibraryPath {
358        &self.path
359    }
360
361    /// Get the namespace of this module, e.g. `std` in `std::math::u64`
362    pub fn namespace(&self) -> &LibraryNamespace {
363        self.path.namespace()
364    }
365
366    /// Returns true if this module belongs to the provided namespace.
367    pub fn is_in_namespace(&self, namespace: &LibraryNamespace) -> bool {
368        self.path.namespace() == namespace
369    }
370
371    /// Get the module documentation for this module, if it was present in the source code the
372    /// module was parsed from
373    pub fn docs(&self) -> Option<Span<&str>> {
374        self.docs.as_ref().map(|spanned| spanned.as_spanned_str())
375    }
376
377    /// Get the type of module this represents:
378    ///
379    /// See [ModuleKind] for details on the different types of modules.
380    pub fn kind(&self) -> ModuleKind {
381        self.kind
382    }
383
384    /// Override the type of module this represents.
385    ///
386    /// See [ModuleKind] for details on what the different types are.
387    pub fn set_kind(&mut self, kind: ModuleKind) {
388        self.kind = kind;
389    }
390
391    /// Returns true if this module is an executable module.
392    #[inline(always)]
393    pub fn is_executable(&self) -> bool {
394        self.kind.is_executable()
395    }
396
397    /// Returns true if this module is the top-level kernel module.
398    #[inline(always)]
399    pub fn is_kernel(&self) -> bool {
400        self.kind.is_kernel() && self.path.is_kernel_path()
401    }
402
403    /// Returns true if this module is a kernel module.
404    #[inline(always)]
405    pub fn is_in_kernel(&self) -> bool {
406        self.kind.is_kernel()
407    }
408
409    /// Returns true if this module has an entrypoint procedure defined,
410    /// i.e. a `begin`..`end` block.
411    pub fn has_entrypoint(&self) -> bool {
412        self.index_of(|p| p.is_main()).is_some()
413    }
414
415    /// Returns a reference to the advice map derived from this module
416    pub fn advice_map(&self) -> &AdviceMap {
417        &self.advice_map
418    }
419
420    /// Get an iterator over the constants defined in this module.
421    pub fn constants(&self) -> core::slice::Iter<'_, Constant> {
422        self.constants.iter()
423    }
424
425    /// Same as [Module::constants], but returns mutable references.
426    pub fn constants_mut(&mut self) -> core::slice::IterMut<'_, Constant> {
427        self.constants.iter_mut()
428    }
429
430    /// Get an iterator over the types defined in this module.
431    pub fn types(&self) -> core::slice::Iter<'_, TypeDecl> {
432        self.types.iter()
433    }
434
435    /// Same as [Module::types], but returns mutable references.
436    pub fn types_mut(&mut self) -> core::slice::IterMut<'_, TypeDecl> {
437        self.types.iter_mut()
438    }
439
440    /// Get an iterator over the procedures defined in this module.
441    ///
442    /// The entity returned is an [Export], which abstracts over locally-defined procedures and
443    /// re-exported procedures from imported modules.
444    pub fn procedures(&self) -> core::slice::Iter<'_, Export> {
445        self.procedures.iter()
446    }
447
448    /// Same as [Module::procedures], but returns mutable references.
449    pub fn procedures_mut(&mut self) -> core::slice::IterMut<'_, Export> {
450        self.procedures.iter_mut()
451    }
452
453    /// Returns procedures exported from this module.
454    ///
455    /// Each exported procedure is represented by its local procedure index and a fully qualified
456    /// name.
457    pub fn exported_procedures(
458        &self,
459    ) -> impl Iterator<Item = (ProcedureIndex, QualifiedProcedureName)> + '_ {
460        self.procedures.iter().enumerate().filter_map(|(proc_idx, p)| {
461            // skip un-exported procedures
462            if !p.visibility().is_exported() {
463                return None;
464            }
465
466            let proc_idx = ProcedureIndex::new(proc_idx);
467            let fqn = QualifiedProcedureName::new(self.path().clone(), p.name().clone());
468
469            Some((proc_idx, fqn))
470        })
471    }
472
473    /// Gets the type signature for the given [ProcedureIndex], if available.
474    pub fn procedure_signature(&self, id: ProcedureIndex) -> Option<&FunctionType> {
475        self.procedures[id.as_usize()].signature()
476    }
477
478    /// Get an iterator over the imports declared in this module.
479    ///
480    /// See [Import] for details on what information is available for imports.
481    pub fn imports(&self) -> core::slice::Iter<'_, Import> {
482        self.imports.iter()
483    }
484
485    /// Same as [Self::imports], but returns mutable references to each import.
486    pub fn imports_mut(&mut self) -> core::slice::IterMut<'_, Import> {
487        self.imports.iter_mut()
488    }
489
490    /// Get an iterator over the "dependencies" of a module, i.e. what library namespaces we expect
491    /// to find imported procedures in.
492    ///
493    /// For example, if we have imported `std::math::u64`, then we would expect to find a library
494    /// on disk named `std.masl`, although that isn't a strict requirement. This notion of
495    /// dependencies may go away with future packaging-related changed.
496    pub fn dependencies(&self) -> impl Iterator<Item = &LibraryNamespace> {
497        self.import_paths().map(|import| import.namespace())
498    }
499
500    /// Get the procedure at `index` in this module's procedure table.
501    ///
502    /// The procedure returned may be either a locally-defined procedure, or a re-exported
503    /// procedure. See [Export] for details.
504    pub fn get(&self, index: ProcedureIndex) -> Option<&Export> {
505        self.procedures.get(index.as_usize())
506    }
507
508    /// Get the [ProcedureIndex] for the first procedure in this module's procedure table which
509    /// returns true for `predicate`.
510    pub fn index_of<F>(&self, predicate: F) -> Option<ProcedureIndex>
511    where
512        F: FnMut(&Export) -> bool,
513    {
514        self.procedures.iter().position(predicate).map(ProcedureIndex::new)
515    }
516
517    /// Get the [ProcedureIndex] for the procedure whose name is `name` in this module's procedure
518    /// table, _if_ that procedure is exported.
519    ///
520    /// Non-exported procedures can be retrieved by using [Module::index_of].
521    pub fn index_of_name(&self, name: &ProcedureName) -> Option<ProcedureIndex> {
522        self.index_of(|p| p.name() == name && p.visibility().is_exported())
523    }
524
525    /// Resolves `name` to a procedure within the local scope of this module
526    pub fn resolve(&self, name: &ProcedureName) -> Option<ResolvedProcedure> {
527        let index =
528            self.procedures.iter().position(|p| p.name() == name).map(ProcedureIndex::new)?;
529        match &self.procedures[index.as_usize()] {
530            Export::Procedure(proc) => {
531                Some(ResolvedProcedure::Local(Span::new(proc.name().span(), index)))
532            },
533            Export::Alias(alias) => match alias.target() {
534                AliasTarget::MastRoot(digest) => Some(ResolvedProcedure::MastRoot(**digest)),
535                AliasTarget::ProcedurePath(path) | AliasTarget::AbsoluteProcedurePath(path) => {
536                    Some(ResolvedProcedure::External(path.clone()))
537                },
538            },
539        }
540    }
541
542    /// Construct a search structure that can resolve procedure names local to this module
543    pub fn resolver(&self) -> LocalNameResolver {
544        LocalNameResolver::from_iter(self.procedures.iter().enumerate().map(|(i, p)| match p {
545            Export::Procedure(p) => (
546                p.name().clone(),
547                ResolvedProcedure::Local(Span::new(p.name().span(), ProcedureIndex::new(i))),
548            ),
549            Export::Alias(p) => {
550                let target = match p.target() {
551                    AliasTarget::MastRoot(digest) => ResolvedProcedure::MastRoot(**digest),
552                    AliasTarget::ProcedurePath(path) | AliasTarget::AbsoluteProcedurePath(path) => {
553                        ResolvedProcedure::External(path.clone())
554                    },
555                };
556                (p.name().clone(), target)
557            },
558        }))
559        .with_imports(
560            self.imports
561                .iter()
562                .map(|import| (import.name.clone(), Span::new(import.span(), import.path.clone()))),
563        )
564    }
565
566    /// Resolves `module_name` to an [Import] within the context of this module
567    pub fn resolve_import(&self, module_name: &Ident) -> Option<&Import> {
568        self.imports.iter().find(|import| &import.name == module_name)
569    }
570
571    /// Same as [Module::resolve_import], but returns a mutable reference to the [Import]
572    pub fn resolve_import_mut(&mut self, module_name: &Ident) -> Option<&mut Import> {
573        self.imports.iter_mut().find(|import| &import.name == module_name)
574    }
575
576    /// Return an iterator over the paths of all imports in this module
577    pub fn import_paths(&self) -> impl Iterator<Item = &LibraryPath> + '_ {
578        self.imports.iter().map(|import| &import.path)
579    }
580
581    /// Resolves a user-expressed type, `ty`, to a concrete type
582    pub fn resolve_type(&self, ty: &ast::TypeExpr) -> Option<types::Type> {
583        match ty {
584            ast::TypeExpr::Ref(name) => match self.types.iter().find(|t| t.name() == name)? {
585                ast::TypeDecl::Alias(alias) => self.resolve_type(&alias.ty),
586                ast::TypeDecl::Enum(enum_ty) => Some(enum_ty.ty().clone()),
587            },
588            ast::TypeExpr::Primitive(t) => Some(t.inner().clone()),
589            ast::TypeExpr::Array(t) => {
590                let elem = self.resolve_type(&t.elem)?;
591                Some(types::Type::Array(Arc::new(types::ArrayType::new(elem, t.arity))))
592            },
593            ast::TypeExpr::Ptr(ty) => {
594                let pointee = self.resolve_type(&ty.pointee)?;
595                Some(types::Type::Ptr(Arc::new(types::PointerType::new(pointee))))
596            },
597            ast::TypeExpr::Struct(t) => {
598                let mut fields = Vec::with_capacity(t.fields.len());
599                for field in t.fields.iter() {
600                    let field_ty = self.resolve_type(&field.ty)?;
601                    fields.push(field_ty);
602                }
603                Some(types::Type::Struct(Arc::new(types::StructType::new(fields))))
604            },
605        }
606    }
607}
608
609impl core::ops::Index<ProcedureIndex> for Module {
610    type Output = Export;
611
612    #[inline]
613    fn index(&self, index: ProcedureIndex) -> &Self::Output {
614        &self.procedures[index.as_usize()]
615    }
616}
617
618impl core::ops::IndexMut<ProcedureIndex> for Module {
619    #[inline]
620    fn index_mut(&mut self, index: ProcedureIndex) -> &mut Self::Output {
621        &mut self.procedures[index.as_usize()]
622    }
623}
624
625impl Spanned for Module {
626    fn span(&self) -> SourceSpan {
627        self.span
628    }
629}
630
631impl Eq for Module {}
632
633impl PartialEq for Module {
634    fn eq(&self, other: &Self) -> bool {
635        self.kind == other.kind
636            && self.path == other.path
637            && self.docs == other.docs
638            && self.constants == other.constants
639            && self.types == other.types
640            && self.imports == other.imports
641            && self.procedures == other.procedures
642    }
643}
644
645/// Debug representation of this module
646impl fmt::Debug for Module {
647    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
648        f.debug_struct("Module")
649            .field("docs", &self.docs)
650            .field("path", &self.path)
651            .field("kind", &self.kind)
652            .field("constants", &self.constants)
653            .field("types", &self.types)
654            .field("imports", &self.imports)
655            .field("procedures", &self.procedures)
656            .finish()
657    }
658}
659
660/// Pretty-printed representation of this module as Miden Assembly text format
661///
662/// NOTE: Delegates to the [crate::prettier::PrettyPrint] implementation internally
663impl fmt::Display for Module {
664    /// Writes this [Module] as formatted MASM code into the formatter.
665    ///
666    /// The formatted code puts each instruction on a separate line and preserves correct
667    /// indentation for instruction blocks.
668    #[inline]
669    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
670        use crate::prettier::PrettyPrint;
671
672        self.pretty_print(f)
673    }
674}
675
676/// The pretty-printer for [Module]
677impl crate::prettier::PrettyPrint for Module {
678    fn render(&self) -> crate::prettier::Document {
679        use crate::prettier::*;
680
681        let mut doc = self
682            .docs
683            .as_ref()
684            .map(|docstring| docstring.render() + nl())
685            .unwrap_or(Document::Empty);
686
687        for (i, import) in self.imports.iter().enumerate() {
688            if i > 0 {
689                doc += nl();
690            }
691            doc += import.render();
692        }
693
694        if !self.imports.is_empty() {
695            doc += nl();
696        }
697
698        for (i, constant) in self.constants.iter().enumerate() {
699            if i > 0 {
700                doc += nl();
701            }
702            doc += constant.render();
703        }
704
705        if !self.constants.is_empty() {
706            doc += nl();
707        }
708
709        let mut export_index = 0;
710        for export in self.procedures.iter() {
711            if export.is_main() {
712                continue;
713            }
714            if export_index > 0 {
715                doc += nl();
716            }
717            doc += export.render();
718            export_index += 1;
719        }
720
721        if let Some(main) = self.procedures().find(|p| p.is_main()) {
722            if export_index > 0 {
723                doc += nl();
724            }
725            doc += main.render();
726        }
727
728        doc
729    }
730}