1use std::collections::{BTreeMap, HashSet};
2
3use miden_diagnostics::{DiagnosticsHandler, Severity, SourceSpan, Span, Spanned};
4
5use crate::{ast::*, sema::SemanticAnalysisError};
6
7pub type ModuleId = Identifier;
9
10#[derive(Debug, Copy, Clone, PartialEq, Eq)]
11pub enum ModuleType {
12 Root,
17 Library,
28}
29
30#[derive(Debug, Spanned)]
47pub struct Module {
48 #[span]
49 pub span: SourceSpan,
50 pub name: ModuleId,
51 pub ty: ModuleType,
52 pub imports: BTreeMap<ModuleId, Import>,
53 pub constants: BTreeMap<Identifier, Constant>,
54 pub evaluators: BTreeMap<Identifier, EvaluatorFunction>,
55 pub functions: BTreeMap<Identifier, Function>,
56 pub periodic_columns: BTreeMap<Identifier, PeriodicColumn>,
57 pub public_inputs: BTreeMap<Identifier, PublicInput>,
58 pub trace_columns: Vec<TraceSegment>,
59 pub buses: BTreeMap<Identifier, Bus>,
60 pub boundary_constraints: Option<Span<Vec<Statement>>>,
61 pub integrity_constraints: Option<Span<Vec<Statement>>>,
62}
63impl Module {
64 pub fn new(ty: ModuleType, span: SourceSpan, name: ModuleId) -> Self {
74 Self {
75 span,
76 name,
77 ty,
78 imports: Default::default(),
79 constants: Default::default(),
80 evaluators: Default::default(),
81 functions: Default::default(),
82 buses: Default::default(),
83 periodic_columns: Default::default(),
84 public_inputs: Default::default(),
85 trace_columns: vec![],
86 boundary_constraints: None,
87 integrity_constraints: None,
88 }
89 }
90
91 pub fn from_declarations(
98 diagnostics: &DiagnosticsHandler,
99 ty: ModuleType,
100 span: SourceSpan,
101 name: Identifier,
102 mut declarations: Vec<Declaration>,
103 ) -> Result<Self, SemanticAnalysisError> {
104 let mut module = Self::new(ty, span, name);
105
106 let mut names = HashSet::<NamespacedIdentifier>::default();
112
113 for declaration in declarations.drain(..) {
114 match declaration {
115 Declaration::Import(import) => {
116 module.declare_import(diagnostics, &mut names, import)?;
117 }
118 Declaration::Constant(constant) => {
119 module.declare_constant(diagnostics, &mut names, constant)?;
120 }
121 Declaration::EvaluatorFunction(evaluator) => {
122 module.declare_evaluator(diagnostics, &mut names, evaluator)?;
123 }
124 Declaration::Function(function) => {
125 module.declare_function(diagnostics, &mut names, function)?;
126 }
127 Declaration::PeriodicColumns(mut columns) => {
128 for column in columns.drain(..) {
129 module.declare_periodic_column(diagnostics, &mut names, column)?;
130 }
131 }
132 Declaration::PublicInputs(mut inputs) => {
133 if module.is_library() {
134 invalid_section_in_library(diagnostics, "public_inputs", span);
135 return Err(SemanticAnalysisError::RootSectionInLibrary(span));
136 }
137 for input in inputs.item.drain(..) {
138 module.declare_public_input(diagnostics, &mut names, input)?;
139 }
140 }
141 Declaration::Trace(segments) => {
142 module.declare_trace_segments(diagnostics, &mut names, segments)?;
143 }
144 Declaration::BoundaryConstraints(statements) => {
145 module.declare_boundary_constraints(diagnostics, statements)?;
146 }
147 Declaration::IntegrityConstraints(statements) => {
148 module.declare_integrity_constraints(diagnostics, statements)?;
149 }
150 Declaration::Buses(mut buses) => {
151 for bus in buses.drain(..) {
152 module.declare_bus(diagnostics, &mut names, bus)?;
153 }
154 }
155 }
156 }
157
158 if module.is_root() {
159 if module.trace_columns.is_empty() {
160 diagnostics.diagnostic(Severity::Error)
161 .with_message("missing trace_columns section")
162 .with_note("Root modules must contain a trace_columns section with at least a `main` trace declared")
163 .emit();
164 return Err(SemanticAnalysisError::Invalid);
165 }
166
167 if !module.trace_columns.iter().any(|ts| ts.name == "$main") {
168 diagnostics.diagnostic(Severity::Error)
169 .with_message("missing main trace declaration")
170 .with_note("Root modules must contain a trace_columns section with at least a `main` trace declared")
171 .emit();
172 return Err(SemanticAnalysisError::Invalid);
173 }
174
175 if module.boundary_constraints.is_none() || module.integrity_constraints.is_none() {
176 return Err(SemanticAnalysisError::MissingConstraints);
177 }
178
179 if module.public_inputs.is_empty() {
180 return Err(SemanticAnalysisError::MissingPublicInputs);
181 }
182 }
183
184 Ok(module)
185 }
186
187 fn declare_import(
188 &mut self,
189 diagnostics: &DiagnosticsHandler,
190 names: &mut HashSet<NamespacedIdentifier>,
191 import: Span<Import>,
192 ) -> Result<(), SemanticAnalysisError> {
193 use std::collections::btree_map::Entry;
194
195 let span = import.span();
196 match import.item {
197 Import::All { module: name } => {
198 if name == self.name {
199 return Err(SemanticAnalysisError::ImportSelf(name.span()));
200 }
201 match self.imports.entry(name) {
202 Entry::Occupied(mut entry) => {
203 let first = entry.key().span();
204 match entry.get_mut() {
205 Import::All { .. } => {
206 diagnostics
207 .diagnostic(Severity::Warning)
208 .with_message("duplicate module import")
209 .with_primary_label(span, "duplicate import occurs here")
210 .with_secondary_label(first, "original import was here")
211 .emit();
212 }
213 Import::Partial { items, .. } => {
214 for item in items.iter() {
215 diagnostics
216 .diagnostic(Severity::Warning)
217 .with_message("redundant item import")
218 .with_primary_label(item.span(), "this import is redundant")
219 .with_secondary_label(
220 name.span(),
221 "because this import imports all items already",
222 )
223 .emit();
224 }
225 entry.insert(import.item);
226 }
227 }
228 }
229 Entry::Vacant(entry) => {
230 entry.insert(import.item);
231 }
232 }
233
234 Ok(())
235 }
236 Import::Partial {
237 module: name,
238 mut items,
239 } => {
240 if name == self.name {
241 return Err(SemanticAnalysisError::ImportSelf(name.span()));
242 }
243 match self.imports.entry(name) {
244 Entry::Occupied(mut entry) => match entry.get_mut() {
245 Import::All { module: prev } => {
246 diagnostics
247 .diagnostic(Severity::Warning)
248 .with_message("redundant module import")
249 .with_primary_label(name.span(), "this import is redundant")
250 .with_secondary_label(
251 prev.span(),
252 "because this import includes all items already",
253 )
254 .emit();
255 }
256 Import::Partial {
257 items: prev_items, ..
258 } => {
259 for item in items.drain() {
260 if let Some(prev) = prev_items.get(&item) {
261 diagnostics
262 .diagnostic(Severity::Warning)
263 .with_message("redundant item import")
264 .with_primary_label(item.span(), "this import is redundant")
265 .with_secondary_label(
266 prev.span(),
267 "because it was already imported here",
268 )
269 .emit();
270 continue;
271 }
272 prev_items.insert(item);
273 let name = if item.is_uppercase() {
274 NamespacedIdentifier::Binding(item)
275 } else {
276 NamespacedIdentifier::Function(item)
277 };
278 if let Some(prev) = names.replace(name) {
279 conflicting_declaration(
280 diagnostics,
281 "import",
282 prev.span(),
283 item.span(),
284 );
285 return Err(SemanticAnalysisError::NameConflict(item.span()));
286 }
287 }
288 }
289 },
290 Entry::Vacant(entry) => {
291 for item in items.iter().copied() {
292 let name = if item.is_uppercase() {
293 NamespacedIdentifier::Binding(item)
294 } else {
295 NamespacedIdentifier::Function(item)
296 };
297 if let Some(prev) = names.replace(name) {
298 conflicting_declaration(
299 diagnostics,
300 "import",
301 prev.span(),
302 item.span(),
303 );
304 return Err(SemanticAnalysisError::NameConflict(item.span()));
305 }
306 }
307 entry.insert(Import::Partial {
308 module: name,
309 items,
310 });
311 }
312 }
313
314 Ok(())
315 }
316 }
317 }
318
319 fn declare_constant(
320 &mut self,
321 diagnostics: &DiagnosticsHandler,
322 names: &mut HashSet<NamespacedIdentifier>,
323 constant: Constant,
324 ) -> Result<(), SemanticAnalysisError> {
325 if !constant.name.is_uppercase() {
326 diagnostics
327 .diagnostic(Severity::Error)
328 .with_message("constant identifiers must be uppercase ASCII characters, e.g. FOO")
329 .with_primary_label(
330 constant.name.span(),
331 "this is an invalid constant identifier",
332 )
333 .emit();
334 return Err(SemanticAnalysisError::Invalid);
335 }
336
337 if let Some(prev) = names.replace(NamespacedIdentifier::Binding(constant.name)) {
338 conflicting_declaration(diagnostics, "constant", prev.span(), constant.name.span());
339 return Err(SemanticAnalysisError::NameConflict(constant.name.span()));
340 }
341
342 if let ConstantExpr::Matrix(matrix) = &constant.value {
344 let expected_len = matrix
345 .first()
346 .expect("expected matrix to have at least one row")
347 .len();
348 for vector in matrix.iter().skip(1) {
349 if expected_len != vector.len() {
350 diagnostics
351 .diagnostic(Severity::Error)
352 .with_message("invalid constant")
353 .with_primary_label(
354 constant.span(),
355 "invalid matrix literal: mismatched dimensions",
356 )
357 .with_note(
358 "Matrix constants must have the same number of columns in each row",
359 )
360 .emit();
361 return Err(SemanticAnalysisError::Invalid);
362 }
363 }
364 }
365 assert_eq!(self.constants.insert(constant.name, constant), None);
366
367 Ok(())
368 }
369
370 fn declare_evaluator(
371 &mut self,
372 diagnostics: &DiagnosticsHandler,
373 names: &mut HashSet<NamespacedIdentifier>,
374 evaluator: EvaluatorFunction,
375 ) -> Result<(), SemanticAnalysisError> {
376 if let Some(prev) = names.replace(NamespacedIdentifier::Function(evaluator.name)) {
377 conflicting_declaration(diagnostics, "evaluator", prev.span(), evaluator.name.span());
378 return Err(SemanticAnalysisError::NameConflict(evaluator.name.span()));
379 }
380
381 self.evaluators.insert(evaluator.name, evaluator);
382
383 Ok(())
384 }
385
386 fn declare_function(
387 &mut self,
388 diagnostics: &DiagnosticsHandler,
389 names: &mut HashSet<NamespacedIdentifier>,
390 function: Function,
391 ) -> Result<(), SemanticAnalysisError> {
392 if let Some(prev) = names.replace(NamespacedIdentifier::Function(function.name)) {
393 conflicting_declaration(diagnostics, "function", prev.span(), function.name.span());
394 return Err(SemanticAnalysisError::NameConflict(function.name.span()));
395 }
396
397 self.functions.insert(function.name, function);
398
399 Ok(())
400 }
401
402 fn declare_bus(
403 &mut self,
404 diagnostics: &DiagnosticsHandler,
405 names: &mut HashSet<NamespacedIdentifier>,
406 bus: Bus,
407 ) -> Result<(), SemanticAnalysisError> {
408 if let Some(prev) = names.replace(NamespacedIdentifier::Binding(bus.name)) {
409 conflicting_declaration(diagnostics, "bus", prev.span(), bus.name.span());
410 return Err(SemanticAnalysisError::NameConflict(bus.name.span()));
411 }
412
413 self.buses.insert(bus.name, bus);
414
415 Ok(())
416 }
417
418 fn declare_periodic_column(
419 &mut self,
420 diagnostics: &DiagnosticsHandler,
421 names: &mut HashSet<NamespacedIdentifier>,
422 column: PeriodicColumn,
423 ) -> Result<(), SemanticAnalysisError> {
424 if let Some(prev) = names.replace(NamespacedIdentifier::Binding(column.name)) {
425 conflicting_declaration(
426 diagnostics,
427 "periodic column",
428 prev.span(),
429 column.name.span(),
430 );
431 return Err(SemanticAnalysisError::NameConflict(column.name.span()));
432 }
433
434 match column.period() {
435 n if n > 0 && n.is_power_of_two() => {
436 assert_eq!(self.periodic_columns.insert(column.name, column), None);
437
438 Ok(())
439 }
440 _ => {
441 diagnostics.diagnostic(Severity::Error)
442 .with_message("invalid periodic column declaration")
443 .with_primary_label(column.span(), "periodic columns must have a non-zero cycle length which is a power of two")
444 .emit();
445 Err(SemanticAnalysisError::Invalid)
446 }
447 }
448 }
449
450 fn declare_public_input(
451 &mut self,
452 diagnostics: &DiagnosticsHandler,
453 names: &mut HashSet<NamespacedIdentifier>,
454 input: PublicInput,
455 ) -> Result<(), SemanticAnalysisError> {
456 if self.is_library() {
457 return Err(SemanticAnalysisError::RootSectionInLibrary(input.span()));
458 }
459
460 if let Some(prev) = names.replace(NamespacedIdentifier::Binding(input.name())) {
461 conflicting_declaration(
462 diagnostics,
463 "public input",
464 prev.span(),
465 input.name().span(),
466 );
467 Err(SemanticAnalysisError::NameConflict(input.name().span()))
468 } else {
469 assert_eq!(self.public_inputs.insert(input.name(), input), None);
470 Ok(())
471 }
472 }
473
474 fn declare_trace_segments(
475 &mut self,
476 diagnostics: &DiagnosticsHandler,
477 names: &mut HashSet<NamespacedIdentifier>,
478 mut segments: Span<Vec<TraceSegment>>,
479 ) -> Result<(), SemanticAnalysisError> {
480 let span = segments.span();
481 if self.is_library() {
482 invalid_section_in_library(diagnostics, "trace_columns", span);
483 return Err(SemanticAnalysisError::RootSectionInLibrary(span));
484 }
485
486 for segment in segments.iter() {
487 if let Some(prev) = names.replace(NamespacedIdentifier::Binding(segment.name)) {
488 conflicting_declaration(
489 diagnostics,
490 "trace segment",
491 prev.span(),
492 segment.name.span(),
493 );
494 return Err(SemanticAnalysisError::NameConflict(segment.name.span()));
495 }
496 for binding in segment.bindings.iter() {
497 let binding_name = binding.name.expect("expected binding name");
498 if let Some(prev) = names.replace(NamespacedIdentifier::Binding(binding_name)) {
499 conflicting_declaration(
500 diagnostics,
501 "trace binding",
502 prev.span(),
503 binding_name.span(),
504 );
505 return Err(SemanticAnalysisError::NameConflict(binding_name.span()));
506 }
507 }
508 }
509
510 self.trace_columns.append(&mut segments.item);
511
512 Ok(())
513 }
514
515 fn declare_boundary_constraints(
516 &mut self,
517 diagnostics: &DiagnosticsHandler,
518 statements: Span<Vec<Statement>>,
519 ) -> Result<(), SemanticAnalysisError> {
520 let span = statements.span();
521 if self.is_library() {
522 invalid_section_in_library(diagnostics, "boundary_constraints", span);
523 return Err(SemanticAnalysisError::RootSectionInLibrary(span));
524 }
525
526 if let Some(prev) = self.boundary_constraints.as_ref() {
527 conflicting_declaration(diagnostics, "boundary_constraints", prev.span(), span);
528 return Err(SemanticAnalysisError::Invalid);
529 }
530
531 if !statements.iter().any(|s| s.has_constraints()) {
532 diagnostics
533 .diagnostic(Severity::Error)
534 .with_message("at least one boundary constraint must be declared")
535 .with_primary_label(span, "missing constraint declaration in this section")
536 .emit();
537 return Err(SemanticAnalysisError::Invalid);
538 }
539
540 self.boundary_constraints = Some(statements);
541
542 Ok(())
543 }
544
545 fn declare_integrity_constraints(
546 &mut self,
547 diagnostics: &DiagnosticsHandler,
548 statements: Span<Vec<Statement>>,
549 ) -> Result<(), SemanticAnalysisError> {
550 let span = statements.span();
551 if self.is_library() {
552 invalid_section_in_library(diagnostics, "integrity_constraints", span);
553 return Err(SemanticAnalysisError::RootSectionInLibrary(span));
554 }
555
556 if let Some(prev) = self.integrity_constraints.as_ref() {
557 conflicting_declaration(diagnostics, "integrity_constraints", prev.span(), span);
558 return Err(SemanticAnalysisError::Invalid);
559 }
560
561 if !statements.iter().any(|s| s.has_constraints()) {
562 diagnostics
563 .diagnostic(Severity::Error)
564 .with_message("at least one integrity constraint must be declared")
565 .with_primary_label(span, "missing constraint declaration in this section")
566 .emit();
567 return Err(SemanticAnalysisError::Invalid);
568 }
569
570 self.integrity_constraints = Some(statements);
571
572 Ok(())
573 }
574
575 #[inline(always)]
576 pub fn is_root(&self) -> bool {
577 !self.is_library()
578 }
579
580 #[inline(always)]
581 pub fn is_library(&self) -> bool {
582 self.ty == ModuleType::Library
583 }
584
585 pub fn exports(&self) -> impl Iterator<Item = Export<'_>> + '_ {
587 self.constants
588 .values()
589 .map(Export::Constant)
590 .chain(self.evaluators.values().map(Export::Evaluator))
591 }
592
593 pub fn get(&self, id: &Identifier) -> Option<Export<'_>> {
595 if id.is_uppercase() {
596 self.constants.get(id).map(Export::Constant)
597 } else {
598 self.evaluators.get(id).map(Export::Evaluator)
599 }
600 }
601}
602impl Eq for Module {}
603impl PartialEq for Module {
604 fn eq(&self, other: &Self) -> bool {
605 self.name == other.name
606 && self.ty == other.ty
607 && self.imports == other.imports
608 && self.constants == other.constants
609 && self.evaluators == other.evaluators
610 && self.functions == other.functions
611 && self.periodic_columns == other.periodic_columns
612 && self.public_inputs == other.public_inputs
613 && self.trace_columns == other.trace_columns
614 && self.boundary_constraints == other.boundary_constraints
615 && self.integrity_constraints == other.integrity_constraints
616 }
617}
618
619fn invalid_section_in_library(diagnostics: &DiagnosticsHandler, ty: &str, span: SourceSpan) {
620 diagnostics
621 .diagnostic(Severity::Error)
622 .with_message(format!("invalid {ty} declaration"))
623 .with_primary_label(span, "this section is not permitted in a library module")
624 .emit();
625}
626
627fn conflicting_declaration(
628 diagnostics: &DiagnosticsHandler,
629 ty: &str,
630 prev: SourceSpan,
631 current: SourceSpan,
632) {
633 diagnostics
634 .diagnostic(Severity::Error)
635 .with_message(format!("invalid {ty} declaration"))
636 .with_primary_label(current, "this conflicts with a previous declaration")
637 .with_secondary_label(prev, "previously defined here")
638 .emit();
639}