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#[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 #[default]
54 Library = 0,
55 Executable = 1,
64 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#[derive(Clone)]
140pub struct Module {
141 span: SourceSpan,
143 docs: Option<DocString>,
149 path: PathBuf,
151 kind: ModuleKind,
153 pub(crate) namespace_decl: Option<Span<Arc<Path>>>,
162 pub(crate) extern_packages: Vec<Ident>,
173 pub(crate) submodules: Vec<SubmoduleDecl>,
177 pub(crate) imports: Vec<Import>,
179 pub(crate) items: Vec<Item>,
181 name_map: BTreeMap<String, usize>,
184 name_map_dirty: bool,
186 pub(crate) advice_map: AdviceMap,
188}
189
190impl Module {
192 pub const FILE_EXTENSION: &'static str = "masm";
194
195 pub const ROOT: &'static str = "mod";
197
198 pub const ROOT_FILENAME: &'static str = "mod.masm";
200}
201
202impl Module {
204 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 pub fn new_kernel() -> Self {
226 Self::new(ModuleKind::Kernel, Path::kernel_path())
227 }
228
229 pub fn new_executable() -> Self {
231 Self::new(ModuleKind::Executable, Path::exec_path())
232 }
233
234 pub fn with_span(mut self, span: SourceSpan) -> Self {
237 self.span = span;
238 self
239 }
240
241 pub fn set_path(&mut self, path: impl AsRef<Path>) {
243 self.path = path.as_ref().to_path_buf();
244 }
245
246 pub fn set_parent(&mut self, ns: impl AsRef<Path>) {
251 self.path.set_parent(ns.as_ref());
252 }
253
254 pub fn set_docs(&mut self, docs: Option<Span<String>>) {
256 self.docs = docs.map(DocString::new);
257 }
258
259 pub fn set_span(&mut self, span: SourceSpan) {
261 self.span = span;
262 }
263
264 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 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 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 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 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 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 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 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 if !is_c_like {
427 self.push_export(Item::Type(export.into()))?;
428 return Ok(());
429 }
430 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 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 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 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 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
520impl Module {
522 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 pub fn parser(kind: Option<ModuleKind>) -> ModuleParser {
552 ModuleParser::new(kind)
553 }
554}
555
556impl Module {
558 pub fn name(&self) -> &str {
561 self.path.last().expect("non-empty module path")
562 }
563
564 pub fn path(&self) -> &Path {
566 &self.path
567 }
568
569 pub fn parent(&self) -> Option<&Path> {
571 self.path.parent()
572 }
573
574 pub fn is_in_namespace(&self, namespace: &Path) -> bool {
576 self.path.starts_with(namespace)
577 }
578
579 pub fn docs(&self) -> Option<Span<&str>> {
582 self.docs.as_ref().map(|spanned| spanned.as_spanned_str())
583 }
584
585 pub fn kind(&self) -> ModuleKind {
589 self.kind
590 }
591
592 pub fn set_kind(&mut self, kind: ModuleKind) {
596 self.kind = kind;
597 }
598
599 #[inline(always)]
601 pub fn is_executable(&self) -> bool {
602 self.kind.is_executable()
603 }
604
605 #[inline(always)]
607 pub fn is_kernel(&self) -> bool {
608 self.kind.is_kernel() && self.path.is_kernel_path()
609 }
610
611 #[inline(always)]
613 pub fn is_in_kernel(&self) -> bool {
614 self.kind.is_kernel()
615 }
616
617 pub fn has_entrypoint(&self) -> bool {
620 self.index_of(Item::is_main).is_some()
621 }
622
623 pub fn advice_map(&self) -> &AdviceMap {
625 &self.advice_map
626 }
627
628 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 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 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 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 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 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 pub fn get_import(&self, name: &str) -> Option<&Import> {
681 self.imports.iter().find(|import| import.local_name().as_str() == name)
682 }
683
684 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 pub fn imports(&self) -> impl Iterator<Item = &Import> + '_ {
691 self.imports.iter()
692 }
693
694 pub fn imports_mut(&mut self) -> impl Iterator<Item = &mut Import> + '_ {
696 self.imports.iter_mut()
697 }
698
699 pub fn take_imports(&mut self) -> Vec<Import> {
701 core::mem::take(&mut self.imports)
702 }
703
704 pub fn required_packages(&self) -> &[Ident] {
709 &self.extern_packages
710 }
711
712 pub fn submodules(&self) -> &[SubmoduleDecl] {
714 &self.submodules
715 }
716
717 pub fn items(&self) -> &[Item] {
719 &self.items
720 }
721
722 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 pub fn exported(&self) -> impl Iterator<Item = (ItemIndex, QualifiedProcedureName)> + '_ {
733 self.items.iter().enumerate().filter_map(|(idx, item)| {
734 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 pub fn procedure_signature(&self, id: ItemIndex) -> Option<&FunctionType> {
749 self.items[id.as_usize()].signature()
750 }
751
752 pub fn get(&self, index: ItemIndex) -> Option<&Item> {
757 self.items.get(index.as_usize())
758 }
759
760 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 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
813impl 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
829impl fmt::Display for Module {
833 #[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
845impl 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}