Skip to main content

miden_assembly_syntax/ast/
module.rs

1use alloc::{
2    boxed::Box,
3    collections::BTreeMap,
4    string::{String, ToString},
5    sync::Arc,
6    vec::Vec,
7};
8use core::fmt;
9
10use miden_core::{
11    advice::AdviceMap,
12    serde::{ByteReader, ByteWriter, Deserializable, DeserializationError, Serializable},
13};
14use miden_debug_types::{SourceFile, SourceManager, SourceSpan, Span, Spanned};
15use miden_utils_diagnostics::Report;
16#[cfg(feature = "arbitrary")]
17use proptest::prelude::*;
18use smallvec::SmallVec;
19
20use super::{
21    Constant, Declaration, DocString, EnumType, FunctionType, Import, Item, ItemIndex, Path,
22    Procedure, ProcedureName, QualifiedProcedureName, SubmoduleDecl, TypeAlias, TypeDecl, Variant,
23    Visibility,
24};
25use crate::{
26    PathBuf,
27    ast::{self, Ident},
28    parser::ModuleParser,
29    sema::{LimitKind, SemanticAnalysisError},
30};
31
32// MODULE KIND
33// ================================================================================================
34
35/// Represents the kind of a [Module].
36///
37/// The three different kinds have slightly different rules on what syntax is allowed, as well as
38/// what operations can be performed in the body of procedures defined in the module. See the
39/// documentation for each variant for a summary of these differences.
40#[derive(Debug, Default, Copy, Clone, PartialEq, Eq)]
41#[cfg_attr(
42    all(feature = "arbitrary", test),
43    miden_test_serde_macros::serde_test(binary_serde(true), serde_test(false))
44)]
45#[repr(u8)]
46pub enum ModuleKind {
47    /// A library is a simple container of code that must be included into an executable module to
48    /// form a complete program.
49    ///
50    /// Library modules cannot use the `begin`..`end` syntax, which is used to define the
51    /// entrypoint procedure for an executable. Aside from this, they are free to use all other
52    /// MASM syntax.
53    #[default]
54    Library = 0,
55    /// An executable is the root module of a program, and provides the entrypoint for executing
56    /// that program.
57    ///
58    /// As the executable module is the root module, it may not export procedures for other modules
59    /// to depend on, it may only import and call externally-defined procedures, or private
60    /// locally-defined procedures.
61    ///
62    /// An executable module must contain a `begin`..`end` block.
63    Executable = 1,
64    /// A kernel is like a library module, but is special in a few ways:
65    ///
66    /// * Its code always executes in the root context, so it is stateful in a way that normal
67    ///   libraries cannot replicate. This can be used to provide core services that would otherwise
68    ///   not be possible to implement.
69    ///
70    /// * The procedures exported from the kernel may be the target of the `syscall` instruction,
71    ///   and in fact _must_ be called that way.
72    Kernel = 2,
73}
74
75impl ModuleKind {
76    pub fn is_executable(&self) -> bool {
77        matches!(self, Self::Executable)
78    }
79
80    pub fn is_kernel(&self) -> bool {
81        matches!(self, Self::Kernel)
82    }
83
84    pub fn is_library(&self) -> bool {
85        matches!(self, Self::Library)
86    }
87}
88
89impl fmt::Display for ModuleKind {
90    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
91        match self {
92            Self::Library => f.write_str("library"),
93            Self::Executable => f.write_str("executable"),
94            Self::Kernel => f.write_str("kernel"),
95        }
96    }
97}
98
99#[cfg(feature = "arbitrary")]
100impl Arbitrary for ModuleKind {
101    type Parameters = ();
102    type Strategy = BoxedStrategy<Self>;
103
104    fn arbitrary_with(_args: Self::Parameters) -> Self::Strategy {
105        any::<u8>()
106            .prop_map(|tag| match tag % 3 {
107                0 => Self::Library,
108                1 => Self::Executable,
109                _ => Self::Kernel,
110            })
111            .boxed()
112    }
113}
114
115impl Serializable for ModuleKind {
116    fn write_into<W: ByteWriter>(&self, target: &mut W) {
117        target.write_u8(*self as u8)
118    }
119}
120
121impl Deserializable for ModuleKind {
122    fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
123        match source.read_u8()? {
124            0 => Ok(Self::Library),
125            1 => Ok(Self::Executable),
126            2 => Ok(Self::Kernel),
127            n => Err(DeserializationError::InvalidValue(format!("invalid module kind tag: {n}"))),
128        }
129    }
130}
131
132// MODULE
133// ================================================================================================
134
135/// The abstract syntax tree for a single Miden Assembly module.
136///
137/// All module kinds share this AST representation, as they are largely identical. However, the
138/// [ModuleKind] dictates how the parsed module is semantically analyzed and validated.
139#[derive(Clone)]
140pub struct Module {
141    /// The span covering the entire definition of this module.
142    span: SourceSpan,
143    /// The documentation associated with this module.
144    ///
145    /// Module documentation is provided in Miden Assembly as a documentation comment starting on
146    /// the first line of the module. All other documentation comments are attached to the item the
147    /// precede in the module body.
148    docs: Option<DocString>,
149    /// The fully-qualified path representing the name of this module.
150    path: PathBuf,
151    /// The kind of module this represents.
152    kind: ModuleKind,
153    /// The namespace given by a `namespace` declaration, if present in this module
154    ///
155    /// The `namespace` declaration is optional when the module being parsed is part of a project
156    /// controlled by a `miden-project.toml` manifest. If present at the same time as a controlling
157    /// manifest, the namespace must agree with what is specified in `miden-project.toml`.
158    ///
159    /// The `namespace` declaration is only permitted in the root module of a project - if present
160    /// in any submodules, an error will be raised.
161    pub(crate) namespace_decl: Option<Span<Arc<Path>>>,
162    /// The set of external package requirements declared in this module.
163    ///
164    /// The `extern package "<package-id>"` declaration is optional when either the module being
165    /// parsed is part of a project controlled by a `miden-project.toml` manifest; or when the
166    /// module and its submodules has no external dependencies. If present at the same time as a
167    /// controlling manifest, the set of `extern package` declarations must agree exactly with what
168    /// is specified in `miden-project.toml`.
169    ///
170    /// The `extern package` declaration is only permitted in the root module of a project - if
171    /// present in any submodules, an error will be raised.
172    pub(crate) extern_packages: Vec<Ident>,
173    /// The set of submodule declarations in this module, i.e. `mod foo` or `pub mod foo`
174    ///
175    /// If specified, it is expected that a module with the given
176    pub(crate) submodules: Vec<SubmoduleDecl>,
177    /// The imports declared by this module.
178    pub(crate) imports: Vec<Import>,
179    /// The items (defined or re-exported) in the module body.
180    pub(crate) items: Vec<Item>,
181    /// Maps export name to its position in `items`, for O(log n) conflict checks.
182    /// Must be kept in sync with `items` via `push_export`.
183    name_map: BTreeMap<String, usize>,
184    /// Whether mutable item access may have changed item names without updating `name_map`.
185    name_map_dirty: bool,
186    /// AdviceMap that this module expects to be loaded in the host before executing.
187    pub(crate) advice_map: AdviceMap,
188}
189
190/// Constants
191impl Module {
192    /// File extension for a Assembly Module.
193    pub const FILE_EXTENSION: &'static str = "masm";
194
195    /// Name of the root module.
196    pub const ROOT: &'static str = "mod";
197
198    /// File name of the root module.
199    pub const ROOT_FILENAME: &'static str = "mod.masm";
200}
201
202/// Construction
203impl Module {
204    /// Creates a new [Module] with the specified `kind` and fully-qualified path, e.g.
205    /// `std::math::u64`.
206    pub fn new(kind: ModuleKind, path: impl AsRef<Path>) -> Self {
207        let path = path.as_ref().to_absolute().unwrap().into_owned();
208        Self {
209            span: Default::default(),
210            docs: None,
211            path,
212            kind,
213            namespace_decl: None,
214            extern_packages: Default::default(),
215            submodules: Default::default(),
216            imports: Default::default(),
217            items: Default::default(),
218            name_map: BTreeMap::new(),
219            name_map_dirty: false,
220            advice_map: Default::default(),
221        }
222    }
223
224    /// An alias for creating the default, but empty, `#kernel` [Module].
225    pub fn new_kernel() -> Self {
226        Self::new(ModuleKind::Kernel, Path::kernel_path())
227    }
228
229    /// An alias for creating the default, but empty, `$exec` [Module].
230    pub fn new_executable() -> Self {
231        Self::new(ModuleKind::Executable, Path::exec_path())
232    }
233
234    /// Specifies the source span in the source file in which this module was defined, that covers
235    /// the full definition of this module.
236    pub fn with_span(mut self, span: SourceSpan) -> Self {
237        self.span = span;
238        self
239    }
240
241    /// Sets the [Path] for this module
242    pub fn set_path(&mut self, path: impl AsRef<Path>) {
243        self.path = path.as_ref().to_path_buf();
244    }
245
246    /// Modifies the path of this module by overriding the portion of the path preceding
247    /// [`Self::name`], i.e. the portion returned by [`Self::parent`].
248    ///
249    /// See [`PathBuf::set_parent`] for details.
250    pub fn set_parent(&mut self, ns: impl AsRef<Path>) {
251        self.path.set_parent(ns.as_ref());
252    }
253
254    /// Sets the documentation for this module
255    pub fn set_docs(&mut self, docs: Option<Span<String>>) {
256        self.docs = docs.map(DocString::new);
257    }
258
259    /// Like [Module::with_span], but does not require ownership of the [Module].
260    pub fn set_span(&mut self, span: SourceSpan) {
261        self.span = span;
262    }
263
264    /// Registers that `namespace` was explicitly declared for this module via `namespace <ns>`
265    ///
266    /// This will also override the module path to ensure they agree
267    pub(crate) fn set_declared_namespace(&mut self, namespace: Span<Arc<Path>>) {
268        self.path = namespace.to_path_buf();
269        self.namespace_decl = Some(namespace);
270    }
271
272    fn ensure_item_capacity(&self, span: SourceSpan) -> Result<(), SemanticAnalysisError> {
273        if self.items.len() >= ItemIndex::MAX_ITEMS {
274            return Err(SemanticAnalysisError::LimitExceeded { span, kind: LimitKind::Items });
275        }
276
277        Ok(())
278    }
279
280    pub(crate) fn push_export(&mut self, item: Item) -> Result<(), SemanticAnalysisError> {
281        self.ensure_item_capacity(item.span())?;
282        self.ensure_name_map_current();
283        let idx = self.items.len();
284        let prev = self.name_map.insert(item.name().to_string(), idx);
285        debug_assert!(prev.is_none(), "duplicate export inserted via push_export: {}", item.name());
286        self.items.push(item);
287        Ok(())
288    }
289
290    fn ensure_name_map_current(&mut self) {
291        if !self.name_map_dirty {
292            return;
293        }
294
295        self.name_map.clear();
296        self.name_map.extend(
297            self.items.iter().enumerate().map(|(idx, item)| (item.name().to_string(), idx)),
298        );
299        self.name_map_dirty = false;
300    }
301
302    /// Takes all items from this module, clearing the name index.
303    /// Used by the linker to consume module contents.
304    pub fn take_items(&mut self) -> Vec<Item> {
305        self.name_map.clear();
306        self.name_map_dirty = false;
307        core::mem::take(&mut self.items)
308    }
309
310    /// Get the [Declaration] corresponding to `name` in this module, if `name` has been declared
311    pub(crate) fn get_declaration<'module>(
312        &'module self,
313        name: &str,
314    ) -> Option<Declaration<'module>> {
315        self.get_item(name)
316            .map(Declaration::Item)
317            .or_else(|| self.get_import(name).map(Declaration::Import))
318            .or_else(|| self.get_submodule_declaration(name).map(Declaration::Submodule))
319    }
320
321    #[inline]
322    pub(crate) fn get_item(&self, name: &str) -> Option<&Item> {
323        self.name_map.get(name).copied().map(|idx| &self.items[idx])
324    }
325
326    #[inline]
327    pub(crate) fn get_submodule_declaration(&self, name: &str) -> Option<&SubmoduleDecl> {
328        self.submodules.iter().find(|decl| decl.name.as_str() == name)
329    }
330
331    fn ensure_import_capacity(&self, span: SourceSpan) -> Result<(), SemanticAnalysisError> {
332        if self.imports.len() + self.items.len() >= ItemIndex::MAX_ITEMS {
333            return Err(SemanticAnalysisError::LimitExceeded { span, kind: LimitKind::Imports });
334        }
335
336        Ok(())
337    }
338
339    /// Declares that this module has a submodule named `name`, with the specified `visibility`.
340    ///
341    /// This returns an error if it conflicts with a previous declaration (either a previous
342    /// submodule declaration, or an imported symbol name).
343    pub fn declare_submodule(
344        &mut self,
345        name: Ident,
346        visibility: Visibility,
347    ) -> Result<(), SemanticAnalysisError> {
348        if let Some(decl) = self.get_declaration(name.as_str()) {
349            return Err(SemanticAnalysisError::SymbolConflict {
350                span: name.span(),
351                prev_span: decl.span(),
352            });
353        }
354        self.submodules.push(SubmoduleDecl { visibility, name });
355        Ok(())
356    }
357
358    /// Declares that this module has a dependency on the library target of an external package
359    /// named `name`.
360    ///
361    /// This returns an error if it conflicts with a previous `extern package` declaration.
362    pub fn declare_extern_package(&mut self, name: Ident) -> Result<(), SemanticAnalysisError> {
363        if let Some(prev) = self.extern_packages.iter().find(|ep| *ep == &name) {
364            return Err(SemanticAnalysisError::ExternPackageConflict {
365                span: name.span(),
366                prev_span: prev.span(),
367            });
368        }
369        self.extern_packages.push(name);
370        Ok(())
371    }
372
373    /// Defines a constant, raising an error if the constant conflicts with a previous definition
374    pub fn define_constant(&mut self, constant: Constant) -> Result<(), SemanticAnalysisError> {
375        self.ensure_name_map_current();
376        if let Some(prev) = self.get_declaration(constant.name.as_str()) {
377            return Err(SemanticAnalysisError::SymbolConflict {
378                span: constant.span,
379                prev_span: prev.span(),
380            });
381        }
382        self.push_export(Item::Constant(constant))
383    }
384
385    /// Defines a type alias, raising an error if the alias conflicts with a previous definition
386    pub fn define_type(&mut self, ty: TypeAlias) -> Result<(), SemanticAnalysisError> {
387        self.ensure_name_map_current();
388        if let Some(prev) = self.get_declaration(ty.name.as_str()) {
389            return Err(SemanticAnalysisError::SymbolConflict {
390                span: ty.span(),
391                prev_span: prev.span(),
392            });
393        }
394        self.push_export(Item::Type(ty.into()))
395    }
396
397    /// Define a new enum type `ty` with `visibility`
398    ///
399    /// Returns `Err` if:
400    ///
401    /// * A type alias with the same name as the enum type is already defined
402    /// * Two or more variants of the given enum type have the same name
403    /// * A constant (including those implicitly defined by variants of other enums in this module)
404    ///   with the same name as any of the variants of the given enum type, is already defined
405    /// * The concrete type of the enumeration is not an integral type
406    pub fn define_enum(&mut self, ty: EnumType) -> Result<(), SemanticAnalysisError> {
407        self.ensure_name_map_current();
408        let repr = ty.ty().clone();
409        let is_c_like = ty.is_c_like();
410
411        if !repr.is_integer() {
412            return Err(SemanticAnalysisError::InvalidEnumRepr { span: ty.span() });
413        }
414
415        let export = ty.clone();
416        let (alias, variants) = ty.into_parts();
417
418        if let Some(prev) = self.get_declaration(alias.name().as_str()) {
419            return Err(SemanticAnalysisError::SymbolConflict {
420                span: alias.span(),
421                prev_span: prev.span(),
422            });
423        }
424
425        // We only define constants for C-like enums
426        if !is_c_like {
427            self.push_export(Item::Type(export.into()))?;
428            return Ok(());
429        }
430        // Check that no variant name conflicts with the enum type name itself
431        if let Some(conflict) = variants.iter().find(|v| v.name.as_str() == alias.name.as_str()) {
432            return Err(SemanticAnalysisError::SymbolConflict {
433                span: conflict.name.span(),
434                prev_span: alias.span(),
435            });
436        }
437
438        let mut values = SmallVec::<[Span<u64>; 8]>::new_const();
439
440        for variant in variants {
441            // Validate that the discriminant value is unique amongst all variants
442            let value = match &variant.discriminant {
443                ast::ConstantExpr::Int(value) => (*value).map(|v| v.as_int()),
444                expr => {
445                    return Err(SemanticAnalysisError::InvalidEnumDiscriminant {
446                        span: expr.span(),
447                        repr,
448                    });
449                },
450            };
451            if let Some(prev) = values.iter().find(|v| *v == &value) {
452                return Err(SemanticAnalysisError::EnumDiscriminantConflict {
453                    span: value.span(),
454                    prev: prev.span(),
455                });
456            } else {
457                values.push(value);
458            }
459
460            // Validate that the discriminant is a valid instance of the `repr` type
461            variant.assert_instance_of(&repr)?;
462
463            let Variant {
464                span,
465                docs,
466                name,
467                value_ty: _,
468                discriminant,
469            } = variant;
470
471            self.define_constant(Constant {
472                span,
473                docs,
474                visibility: alias.visibility(),
475                name,
476                value: discriminant,
477            })?;
478        }
479
480        self.push_export(Item::Type(export.into()))?;
481
482        Ok(())
483    }
484
485    /// Defines a procedure, raising an error if the procedure is invalid, or conflicts with a
486    /// previous definition
487    pub fn define_procedure(
488        &mut self,
489        procedure: Procedure,
490        _source_manager: Arc<dyn SourceManager>,
491    ) -> Result<(), SemanticAnalysisError> {
492        self.ensure_name_map_current();
493        if let Some(prev) = self.get_declaration(procedure.name().as_str()) {
494            return Err(SemanticAnalysisError::SymbolConflict {
495                span: procedure.span(),
496                prev_span: prev.span(),
497            });
498        }
499        self.push_export(Item::Procedure(procedure))
500    }
501
502    /// Defines an import, raising an error if the import conflicts with a previous declaration.
503    pub fn define_import(&mut self, import: Import) -> Result<(), SemanticAnalysisError> {
504        self.ensure_name_map_current();
505        self.ensure_import_capacity(import.span())?;
506        if self.is_kernel() && import.visibility().is_public() {
507            return Err(SemanticAnalysisError::ReexportFromKernel { span: import.span() });
508        }
509        if let Some(prev) = self.get_declaration(import.local_name().as_str()) {
510            return Err(SemanticAnalysisError::SymbolConflict {
511                span: import.local_name().span(),
512                prev_span: prev.span(),
513            });
514        }
515        self.imports.push(import);
516        Ok(())
517    }
518}
519
520/// Parsing
521impl Module {
522    /// Parse a [Module], `name`, of the given [ModuleKind], from `source_file`.
523    pub fn parse(
524        name: impl AsRef<Path>,
525        source_file: Arc<SourceFile>,
526        source_manager: Arc<dyn SourceManager>,
527    ) -> Result<Box<Self>, Report> {
528        let name = name.as_ref();
529        let kind = if name.is_kernel_path() {
530            Some(ModuleKind::Kernel)
531        } else {
532            None
533        };
534        let mut parser = Self::parser(kind);
535        parser.parse(Some(name), source_file, source_manager)
536    }
537
538    pub fn parse_kernel(
539        source_file: Arc<SourceFile>,
540        source_manager: Arc<dyn SourceManager>,
541    ) -> Result<Box<Self>, Report> {
542        let mut parser = Self::parser(Some(ModuleKind::Kernel));
543        parser.parse(Some(Path::KERNEL), source_file, source_manager)
544    }
545
546    /// Get a [ModuleParser] for parsing modules of the provided [ModuleKind]
547    ///
548    /// If `kind` is `None`, then the module kind is inferred as either library or executable based
549    /// on whether the module contains a `begin` block. If you wish to parse a kernel module, it
550    /// must be done explicitly.
551    pub fn parser(kind: Option<ModuleKind>) -> ModuleParser {
552        ModuleParser::new(kind)
553    }
554}
555
556/// Metadata
557impl Module {
558    /// Get the name of this specific module, i.e. the last component of the [Path] that
559    /// represents the fully-qualified name of the module, e.g. `u64` in `std::math::u64`
560    pub fn name(&self) -> &str {
561        self.path.last().expect("non-empty module path")
562    }
563
564    /// Get the fully-qualified name of this module, e.g. `std::math::u64`
565    pub fn path(&self) -> &Path {
566        &self.path
567    }
568
569    /// Get the path of the parent module of this module, e.g. `std::math` in `std::math::u64`
570    pub fn parent(&self) -> Option<&Path> {
571        self.path.parent()
572    }
573
574    /// Returns true if this module belongs to the provided namespace.
575    pub fn is_in_namespace(&self, namespace: &Path) -> bool {
576        self.path.starts_with(namespace)
577    }
578
579    /// Get the module documentation for this module, if it was present in the source code the
580    /// module was parsed from
581    pub fn docs(&self) -> Option<Span<&str>> {
582        self.docs.as_ref().map(|spanned| spanned.as_spanned_str())
583    }
584
585    /// Get the type of module this represents:
586    ///
587    /// See [ModuleKind] for details on the different types of modules.
588    pub fn kind(&self) -> ModuleKind {
589        self.kind
590    }
591
592    /// Override the type of module this represents.
593    ///
594    /// See [ModuleKind] for details on what the different types are.
595    pub fn set_kind(&mut self, kind: ModuleKind) {
596        self.kind = kind;
597    }
598
599    /// Returns true if this module is an executable module.
600    #[inline(always)]
601    pub fn is_executable(&self) -> bool {
602        self.kind.is_executable()
603    }
604
605    /// Returns true if this module is the top-level kernel module.
606    #[inline(always)]
607    pub fn is_kernel(&self) -> bool {
608        self.kind.is_kernel() && self.path.is_kernel_path()
609    }
610
611    /// Returns true if this module is a kernel module.
612    #[inline(always)]
613    pub fn is_in_kernel(&self) -> bool {
614        self.kind.is_kernel()
615    }
616
617    /// Returns true if this module has an entrypoint procedure defined,
618    /// i.e. a `begin`..`end` block.
619    pub fn has_entrypoint(&self) -> bool {
620        self.index_of(Item::is_main).is_some()
621    }
622
623    /// Returns a reference to the advice map derived from this module
624    pub fn advice_map(&self) -> &AdviceMap {
625        &self.advice_map
626    }
627
628    /// Get an iterator over the constants defined in this module.
629    pub fn constants(&self) -> impl Iterator<Item = &Constant> + '_ {
630        self.items.iter().filter_map(|item| match item {
631            Item::Constant(item) => Some(item),
632            _ => None,
633        })
634    }
635
636    /// Same as [Module::constants], but returns mutable references.
637    pub fn constants_mut(&mut self) -> impl Iterator<Item = &mut Constant> + '_ {
638        self.name_map_dirty = true;
639        self.items.iter_mut().filter_map(|item| match item {
640            Item::Constant(item) => Some(item),
641            _ => None,
642        })
643    }
644
645    /// Get an iterator over the types defined in this module.
646    pub fn types(&self) -> impl Iterator<Item = &TypeDecl> + '_ {
647        self.items.iter().filter_map(|item| match item {
648            Item::Type(item) => Some(item),
649            _ => None,
650        })
651    }
652
653    /// Same as [Module::types], but returns mutable references.
654    pub fn types_mut(&mut self) -> impl Iterator<Item = &mut TypeDecl> + '_ {
655        self.name_map_dirty = true;
656        self.items.iter_mut().filter_map(|item| match item {
657            Item::Type(item) => Some(item),
658            _ => None,
659        })
660    }
661
662    /// Get an iterator over the procedures defined in this module.
663    pub fn procedures(&self) -> impl Iterator<Item = &Procedure> + '_ {
664        self.items.iter().filter_map(|item| match item {
665            Item::Procedure(item) => Some(item),
666            _ => None,
667        })
668    }
669
670    /// Same as [Module::procedures], but returns mutable references.
671    pub fn procedures_mut(&mut self) -> impl Iterator<Item = &mut Procedure> + '_ {
672        self.name_map_dirty = true;
673        self.items.iter_mut().filter_map(|item| match item {
674            Item::Procedure(item) => Some(item),
675            _ => None,
676        })
677    }
678
679    /// Resolves `name` to an [Import] within the context of this module.
680    pub fn get_import(&self, name: &str) -> Option<&Import> {
681        self.imports.iter().find(|import| import.local_name().as_str() == name)
682    }
683
684    /// Same as [Module::get_import], but returns a mutable reference to the [Import].
685    pub fn get_import_mut(&mut self, name: &str) -> Option<&mut Import> {
686        self.imports.iter_mut().find(|import| import.local_name().as_str() == name)
687    }
688
689    /// Get an iterator over imports in this module.
690    pub fn imports(&self) -> impl Iterator<Item = &Import> + '_ {
691        self.imports.iter()
692    }
693
694    /// Same as [Module::imports], but returns mutable references.
695    pub fn imports_mut(&mut self) -> impl Iterator<Item = &mut Import> + '_ {
696        self.imports.iter_mut()
697    }
698
699    /// Takes all imports from this module.
700    pub fn take_imports(&mut self) -> Vec<Import> {
701        core::mem::take(&mut self.imports)
702    }
703
704    /// Get a reference to the set of package identifiers that this module declares a dependency on
705    ///
706    /// This is only reflects explicit `extern package` declarations of the root project module,
707    /// not actual requirements, i.e. it is not authoritative.
708    pub fn required_packages(&self) -> &[Ident] {
709        &self.extern_packages
710    }
711
712    /// Get a reference to the set of submodule declarations in this module.
713    pub fn submodules(&self) -> &[SubmoduleDecl] {
714        &self.submodules
715    }
716
717    /// Get a reference to the items stored in this module
718    pub fn items(&self) -> &[Item] {
719        &self.items
720    }
721
722    /// Returns a mutable iterator over the items in this module.
723    /// Note: does not expose `Vec` directly to preserve the `name_map` invariant.
724    pub fn items_mut(&mut self) -> impl Iterator<Item = &mut Item> {
725        self.name_map_dirty = true;
726        self.items.iter_mut()
727    }
728
729    /// Returns items exported from this module.
730    ///
731    /// Each exported item is represented by its local item index and a fully qualified name.
732    pub fn exported(&self) -> impl Iterator<Item = (ItemIndex, QualifiedProcedureName)> + '_ {
733        self.items.iter().enumerate().filter_map(|(idx, item)| {
734            // skip un-exported items
735            if !item.visibility().is_public() {
736                return None;
737            }
738
739            let idx = ItemIndex::new(idx);
740            let name = ProcedureName::from_raw_parts(item.name().clone());
741            let fqn = QualifiedProcedureName::new(self.path.clone(), name);
742
743            Some((idx, fqn))
744        })
745    }
746
747    /// Gets the type signature for the given [ItemIndex], if available.
748    pub fn procedure_signature(&self, id: ItemIndex) -> Option<&FunctionType> {
749        self.items[id.as_usize()].signature()
750    }
751
752    /// Get the item at `index` in this module's item table.
753    ///
754    /// The item returned may be either a locally-defined item, or a re-exported item. See [Item]
755    /// for details.
756    pub fn get(&self, index: ItemIndex) -> Option<&Item> {
757        self.items.get(index.as_usize())
758    }
759
760    /// Get the [ItemIndex] for the first item in this module's item table which returns true for
761    /// `predicate`.
762    pub fn index_of<F>(&self, predicate: F) -> Option<ItemIndex>
763    where
764        F: FnMut(&Item) -> bool,
765    {
766        self.items.iter().position(predicate).map(ItemIndex::new)
767    }
768
769    /// Get the [ItemIndex] for the item whose name is `name` in this module's item table, _if_ that
770    /// item is exported.
771    ///
772    /// Non-exported items can be retrieved by using [Module::index_of].
773    pub fn index_of_name(&self, name: &Ident) -> Option<ItemIndex> {
774        self.index_of(|item| item.name() == name && item.visibility().is_public())
775    }
776}
777
778impl core::ops::Index<ItemIndex> for Module {
779    type Output = Item;
780
781    #[inline]
782    fn index(&self, index: ItemIndex) -> &Self::Output {
783        &self.items[index.as_usize()]
784    }
785}
786
787impl core::ops::IndexMut<ItemIndex> for Module {
788    #[inline]
789    fn index_mut(&mut self, index: ItemIndex) -> &mut Self::Output {
790        self.name_map_dirty = true;
791        &mut self.items[index.as_usize()]
792    }
793}
794
795impl Spanned for Module {
796    fn span(&self) -> SourceSpan {
797        self.span
798    }
799}
800
801impl Eq for Module {}
802
803impl PartialEq for Module {
804    fn eq(&self, other: &Self) -> bool {
805        self.kind == other.kind
806            && self.path == other.path
807            && self.docs == other.docs
808            && self.imports == other.imports
809            && self.items == other.items
810    }
811}
812
813/// Debug representation of this module
814impl fmt::Debug for Module {
815    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
816        f.debug_struct("Module")
817            .field("docs", &self.docs)
818            .field("path", &self.path)
819            .field("namespace_decl", &self.namespace_decl)
820            .field("kind", &self.kind)
821            .field("extern_packages", &self.extern_packages)
822            .field("submodules", &self.submodules)
823            .field("imports", &self.imports)
824            .field("items", &self.items)
825            .finish()
826    }
827}
828
829/// Pretty-printed representation of this module as Miden Assembly text format
830///
831/// NOTE: Delegates to the [crate::prettier::PrettyPrint] implementation internally
832impl fmt::Display for Module {
833    /// Writes this [Module] as formatted MASM code into the formatter.
834    ///
835    /// The formatted code puts each instruction on a separate line and preserves correct
836    /// indentation for instruction blocks.
837    #[inline]
838    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
839        use crate::prettier::PrettyPrint;
840
841        self.pretty_print(f)
842    }
843}
844
845/// The pretty-printer for [Module]
846impl crate::prettier::PrettyPrint for Module {
847    fn render(&self) -> crate::prettier::Document {
848        use crate::prettier::*;
849
850        let mut doc = self
851            .docs
852            .as_ref()
853            .map(|docstring| docstring.render() + nl())
854            .unwrap_or(Document::Empty);
855
856        doc += nl()
857            + const_text("namespace")
858            + const_text(" ")
859            + display(self.path().to_relative())
860            + nl();
861
862        for (i, package) in self.extern_packages.iter().enumerate() {
863            if i > 0 {
864                doc += nl();
865            }
866            doc += const_text("extern package") + const_text(" ") + package.render();
867        }
868
869        if !self.extern_packages.is_empty() {
870            doc += nl();
871        }
872
873        for (i, submodule) in self.submodules.iter().enumerate() {
874            if i > 0 {
875                doc += nl();
876            }
877            if submodule.visibility.is_public() {
878                doc += const_text("pub mod");
879            } else {
880                doc += const_text("mod");
881            }
882            doc += const_text(" ") + submodule.name.render();
883        }
884
885        if !self.submodules.is_empty() {
886            doc += nl();
887        }
888
889        for (import_index, import) in self.imports.iter().enumerate() {
890            if import_index > 0 {
891                doc += nl();
892            }
893            doc += import.render();
894        }
895
896        if !self.imports.is_empty() {
897            doc += nl();
898        }
899
900        for (item_index, item) in self.items.iter().enumerate() {
901            if item_index > 0 {
902                doc += nl();
903            }
904            doc += item.render();
905        }
906
907        doc
908    }
909}