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 midenc_hir_type::FunctionType;
11
12use super::{
13    Constant, DocString, EnumType, Export, Import, LocalNameResolver, ProcedureIndex,
14    ProcedureName, QualifiedProcedureName, ResolvedProcedure, TypeAlias, TypeDecl, Variant,
15};
16use crate::{
17    LibraryNamespace, LibraryPath,
18    ast::{AliasTarget, Ident},
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        if !ty.ty().is_integer() {
247            return Err(SemanticAnalysisError::InvalidEnumRepr { span: ty.span() });
248        }
249
250        if let Some(prev) = self.types.iter().find(|t| t.name() == ty.name()) {
251            return Err(SemanticAnalysisError::SymbolConflict {
252                span: ty.span(),
253                prev_span: prev.span(),
254            });
255        }
256
257        for variant in ty.variants() {
258            let Variant { span, docs, name, discriminant } = variant;
259            self.define_constant(Constant {
260                span: *span,
261                docs: docs.clone(),
262                name: name.clone(),
263                value: discriminant.clone(),
264            })?;
265        }
266
267        self.types.push(ty.into());
268
269        Ok(())
270    }
271
272    /// Defines a procedure, raising an error if the procedure is invalid, or conflicts with a
273    /// previous definition
274    pub fn define_procedure(&mut self, export: Export) -> Result<(), SemanticAnalysisError> {
275        if self.is_kernel() && matches!(export, Export::Alias(_)) {
276            return Err(SemanticAnalysisError::ReexportFromKernel { span: export.span() });
277        }
278        if let Some(prev) = self.resolve(export.name()) {
279            let prev_span = prev.span();
280            Err(SemanticAnalysisError::SymbolConflict { span: export.span(), prev_span })
281        } else {
282            self.procedures.push(export);
283            Ok(())
284        }
285    }
286
287    /// Defines an import, raising an error if the import is invalid, or conflicts with a previous
288    /// definition.
289    pub fn define_import(&mut self, import: Import) -> Result<(), SemanticAnalysisError> {
290        if let Some(prev_import) = self.resolve_import(&import.name) {
291            let prev_span = prev_import.span;
292            return Err(SemanticAnalysisError::ImportConflict { span: import.span, prev_span });
293        }
294
295        if let Some(prev_defined) = self.procedures.iter().find(|e| e.name().eq(&import.name)) {
296            let prev_span = prev_defined.span();
297            return Err(SemanticAnalysisError::SymbolConflict { span: import.span, prev_span });
298        }
299
300        self.imports.push(import);
301
302        Ok(())
303    }
304}
305
306/// Parsing
307impl Module {
308    /// Parse a [Module], `name`, of the given [ModuleKind], from `source_file`.
309    pub fn parse(
310        name: LibraryPath,
311        kind: ModuleKind,
312        source_file: Arc<SourceFile>,
313    ) -> Result<Box<Self>, Report> {
314        let mut parser = Self::parser(kind);
315        parser.parse(name, source_file)
316    }
317
318    /// Get a [ModuleParser] for parsing modules of the provided [ModuleKind]
319    pub fn parser(kind: ModuleKind) -> ModuleParser {
320        ModuleParser::new(kind)
321    }
322}
323
324/// Metadata
325impl Module {
326    /// Get the name of this specific module, i.e. the last component of the [LibraryPath] that
327    /// represents the fully-qualified name of the module, e.g. `u64` in `std::math::u64`
328    pub fn name(&self) -> &str {
329        self.path.last()
330    }
331
332    /// Get the fully-qualified name of this module, e.g. `std::math::u64`
333    pub fn path(&self) -> &LibraryPath {
334        &self.path
335    }
336
337    /// Get the namespace of this module, e.g. `std` in `std::math::u64`
338    pub fn namespace(&self) -> &LibraryNamespace {
339        self.path.namespace()
340    }
341
342    /// Returns true if this module belongs to the provided namespace.
343    pub fn is_in_namespace(&self, namespace: &LibraryNamespace) -> bool {
344        self.path.namespace() == namespace
345    }
346
347    /// Get the module documentation for this module, if it was present in the source code the
348    /// module was parsed from
349    pub fn docs(&self) -> Option<Span<&str>> {
350        self.docs.as_ref().map(|spanned| spanned.as_spanned_str())
351    }
352
353    /// Get the type of module this represents:
354    ///
355    /// See [ModuleKind] for details on the different types of modules.
356    pub fn kind(&self) -> ModuleKind {
357        self.kind
358    }
359
360    /// Override the type of module this represents.
361    ///
362    /// See [ModuleKind] for details on what the different types are.
363    pub fn set_kind(&mut self, kind: ModuleKind) {
364        self.kind = kind;
365    }
366
367    /// Returns true if this module is an executable module.
368    #[inline(always)]
369    pub fn is_executable(&self) -> bool {
370        self.kind.is_executable()
371    }
372
373    /// Returns true if this module is the top-level kernel module.
374    #[inline(always)]
375    pub fn is_kernel(&self) -> bool {
376        self.kind.is_kernel() && self.path.is_kernel_path()
377    }
378
379    /// Returns true if this module is a kernel module.
380    #[inline(always)]
381    pub fn is_in_kernel(&self) -> bool {
382        self.kind.is_kernel()
383    }
384
385    /// Returns true if this module has an entrypoint procedure defined,
386    /// i.e. a `begin`..`end` block.
387    pub fn has_entrypoint(&self) -> bool {
388        self.index_of(|p| p.is_main()).is_some()
389    }
390
391    /// Returns a reference to the advice map derived from this module
392    pub fn advice_map(&self) -> &AdviceMap {
393        &self.advice_map
394    }
395
396    /// Get an iterator over the constants defined in this module.
397    pub fn constants(&self) -> core::slice::Iter<'_, Constant> {
398        self.constants.iter()
399    }
400
401    /// Same as [Module::constants], but returns mutable references.
402    pub fn constants_mut(&mut self) -> core::slice::IterMut<'_, Constant> {
403        self.constants.iter_mut()
404    }
405
406    /// Get an iterator over the types defined in this module.
407    pub fn types(&self) -> core::slice::Iter<'_, TypeDecl> {
408        self.types.iter()
409    }
410
411    /// Same as [Module::types], but returns mutable references.
412    pub fn types_mut(&mut self) -> core::slice::IterMut<'_, TypeDecl> {
413        self.types.iter_mut()
414    }
415
416    /// Get an iterator over the procedures defined in this module.
417    ///
418    /// The entity returned is an [Export], which abstracts over locally-defined procedures and
419    /// re-exported procedures from imported modules.
420    pub fn procedures(&self) -> core::slice::Iter<'_, Export> {
421        self.procedures.iter()
422    }
423
424    /// Same as [Module::procedures], but returns mutable references.
425    pub fn procedures_mut(&mut self) -> core::slice::IterMut<'_, Export> {
426        self.procedures.iter_mut()
427    }
428
429    /// Returns procedures exported from this module.
430    ///
431    /// Each exported procedure is represented by its local procedure index and a fully qualified
432    /// name.
433    pub fn exported_procedures(
434        &self,
435    ) -> impl Iterator<Item = (ProcedureIndex, QualifiedProcedureName)> + '_ {
436        self.procedures.iter().enumerate().filter_map(|(proc_idx, p)| {
437            // skip un-exported procedures
438            if !p.visibility().is_exported() {
439                return None;
440            }
441
442            let proc_idx = ProcedureIndex::new(proc_idx);
443            let fqn = QualifiedProcedureName::new(self.path().clone(), p.name().clone());
444
445            Some((proc_idx, fqn))
446        })
447    }
448
449    /// Gets the type signature for the given [ProcedureIndex], if available.
450    pub fn procedure_signature(&self, id: ProcedureIndex) -> Option<&FunctionType> {
451        self.procedures[id.as_usize()].signature()
452    }
453
454    /// Get an iterator over the imports declared in this module.
455    ///
456    /// See [Import] for details on what information is available for imports.
457    pub fn imports(&self) -> core::slice::Iter<'_, Import> {
458        self.imports.iter()
459    }
460
461    /// Same as [Self::imports], but returns mutable references to each import.
462    pub fn imports_mut(&mut self) -> core::slice::IterMut<'_, Import> {
463        self.imports.iter_mut()
464    }
465
466    /// Get an iterator over the "dependencies" of a module, i.e. what library namespaces we expect
467    /// to find imported procedures in.
468    ///
469    /// For example, if we have imported `std::math::u64`, then we would expect to find a library
470    /// on disk named `std.masl`, although that isn't a strict requirement. This notion of
471    /// dependencies may go away with future packaging-related changed.
472    pub fn dependencies(&self) -> impl Iterator<Item = &LibraryNamespace> {
473        self.import_paths().map(|import| import.namespace())
474    }
475
476    /// Get the procedure at `index` in this module's procedure table.
477    ///
478    /// The procedure returned may be either a locally-defined procedure, or a re-exported
479    /// procedure. See [Export] for details.
480    pub fn get(&self, index: ProcedureIndex) -> Option<&Export> {
481        self.procedures.get(index.as_usize())
482    }
483
484    /// Get the [ProcedureIndex] for the first procedure in this module's procedure table which
485    /// returns true for `predicate`.
486    pub fn index_of<F>(&self, predicate: F) -> Option<ProcedureIndex>
487    where
488        F: FnMut(&Export) -> bool,
489    {
490        self.procedures.iter().position(predicate).map(ProcedureIndex::new)
491    }
492
493    /// Get the [ProcedureIndex] for the procedure whose name is `name` in this module's procedure
494    /// table, _if_ that procedure is exported.
495    ///
496    /// Non-exported procedures can be retrieved by using [Module::index_of].
497    pub fn index_of_name(&self, name: &ProcedureName) -> Option<ProcedureIndex> {
498        self.index_of(|p| p.name() == name && p.visibility().is_exported())
499    }
500
501    /// Resolves `name` to a procedure within the local scope of this module
502    pub fn resolve(&self, name: &ProcedureName) -> Option<ResolvedProcedure> {
503        let index =
504            self.procedures.iter().position(|p| p.name() == name).map(ProcedureIndex::new)?;
505        match &self.procedures[index.as_usize()] {
506            Export::Procedure(proc) => {
507                Some(ResolvedProcedure::Local(Span::new(proc.name().span(), index)))
508            },
509            Export::Alias(alias) => match alias.target() {
510                AliasTarget::MastRoot(digest) => Some(ResolvedProcedure::MastRoot(**digest)),
511                AliasTarget::ProcedurePath(path) | AliasTarget::AbsoluteProcedurePath(path) => {
512                    Some(ResolvedProcedure::External(path.clone()))
513                },
514            },
515        }
516    }
517
518    /// Construct a search structure that can resolve procedure names local to this module
519    pub fn resolver(&self) -> LocalNameResolver {
520        LocalNameResolver::from_iter(self.procedures.iter().enumerate().map(|(i, p)| match p {
521            Export::Procedure(p) => (
522                p.name().clone(),
523                ResolvedProcedure::Local(Span::new(p.name().span(), ProcedureIndex::new(i))),
524            ),
525            Export::Alias(p) => {
526                let target = match p.target() {
527                    AliasTarget::MastRoot(digest) => ResolvedProcedure::MastRoot(**digest),
528                    AliasTarget::ProcedurePath(path) | AliasTarget::AbsoluteProcedurePath(path) => {
529                        ResolvedProcedure::External(path.clone())
530                    },
531                };
532                (p.name().clone(), target)
533            },
534        }))
535        .with_imports(
536            self.imports
537                .iter()
538                .map(|import| (import.name.clone(), Span::new(import.span(), import.path.clone()))),
539        )
540    }
541
542    /// Resolves `module_name` to an [Import] within the context of this module
543    pub fn resolve_import(&self, module_name: &Ident) -> Option<&Import> {
544        self.imports.iter().find(|import| &import.name == module_name)
545    }
546
547    /// Same as [Module::resolve_import], but returns a mutable reference to the [Import]
548    pub fn resolve_import_mut(&mut self, module_name: &Ident) -> Option<&mut Import> {
549        self.imports.iter_mut().find(|import| &import.name == module_name)
550    }
551
552    /// Return an iterator over the paths of all imports in this module
553    pub fn import_paths(&self) -> impl Iterator<Item = &LibraryPath> + '_ {
554        self.imports.iter().map(|import| &import.path)
555    }
556}
557
558impl core::ops::Index<ProcedureIndex> for Module {
559    type Output = Export;
560
561    #[inline]
562    fn index(&self, index: ProcedureIndex) -> &Self::Output {
563        &self.procedures[index.as_usize()]
564    }
565}
566
567impl core::ops::IndexMut<ProcedureIndex> for Module {
568    #[inline]
569    fn index_mut(&mut self, index: ProcedureIndex) -> &mut Self::Output {
570        &mut self.procedures[index.as_usize()]
571    }
572}
573
574impl Spanned for Module {
575    fn span(&self) -> SourceSpan {
576        self.span
577    }
578}
579
580impl Eq for Module {}
581
582impl PartialEq for Module {
583    fn eq(&self, other: &Self) -> bool {
584        self.kind == other.kind
585            && self.path == other.path
586            && self.docs == other.docs
587            && self.constants == other.constants
588            && self.types == other.types
589            && self.imports == other.imports
590            && self.procedures == other.procedures
591    }
592}
593
594/// Debug representation of this module
595impl fmt::Debug for Module {
596    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
597        f.debug_struct("Module")
598            .field("docs", &self.docs)
599            .field("path", &self.path)
600            .field("kind", &self.kind)
601            .field("constants", &self.constants)
602            .field("types", &self.types)
603            .field("imports", &self.imports)
604            .field("procedures", &self.procedures)
605            .finish()
606    }
607}
608
609/// Pretty-printed representation of this module as Miden Assembly text format
610///
611/// NOTE: Delegates to the [crate::prettier::PrettyPrint] implementation internally
612impl fmt::Display for Module {
613    /// Writes this [Module] as formatted MASM code into the formatter.
614    ///
615    /// The formatted code puts each instruction on a separate line and preserves correct
616    /// indentation for instruction blocks.
617    #[inline]
618    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
619        use crate::prettier::PrettyPrint;
620
621        self.pretty_print(f)
622    }
623}
624
625/// The pretty-printer for [Module]
626impl crate::prettier::PrettyPrint for Module {
627    fn render(&self) -> crate::prettier::Document {
628        use crate::prettier::*;
629
630        let mut doc = self
631            .docs
632            .as_ref()
633            .map(|docstring| docstring.render() + nl())
634            .unwrap_or(Document::Empty);
635
636        for (i, import) in self.imports.iter().enumerate() {
637            if i > 0 {
638                doc += nl();
639            }
640            doc += import.render();
641        }
642
643        if !self.imports.is_empty() {
644            doc += nl();
645        }
646
647        for (i, constant) in self.constants.iter().enumerate() {
648            if i > 0 {
649                doc += nl();
650            }
651            doc += constant.render();
652        }
653
654        if !self.constants.is_empty() {
655            doc += nl();
656        }
657
658        let mut export_index = 0;
659        for export in self.procedures.iter() {
660            if export.is_main() {
661                continue;
662            }
663            if export_index > 0 {
664                doc += nl();
665            }
666            doc += export.render();
667            export_index += 1;
668        }
669
670        if let Some(main) = self.procedures().find(|p| p.is_main()) {
671            if export_index > 0 {
672                doc += nl();
673            }
674            doc += main.render();
675        }
676
677        doc
678    }
679}