"""Structured error types for OR1 assembler. Provides error categories, the AssemblyError dataclass, and utilities for formatting errors with source context in Rust style. """ from dataclasses import dataclass, field from enum import Enum from asm.ir import SourceLoc class ErrorSeverity(Enum): """Severity level for assembly diagnostics.""" ERROR = "error" WARNING = "warning" class ErrorCategory(Enum): """Classification of assembly errors.""" PARSE = "parse" NAME = "name" SCOPE = "scope" PLACEMENT = "placement" RESOURCE = "resource" ARITY = "arity" PORT = "port" UNREACHABLE = "unreachable" VALUE = "value" MACRO = "macro" CALL = "call" FRAME = "frame" @dataclass(frozen=True) class AssemblyError: """Structured error with source location and context. Attributes: loc: Source location where the error occurred category: Error classification (see ErrorCategory) message: Human-readable error message suggestions: Optional list of suggestions for fixing the error context_lines: Optional source lines for context """ loc: SourceLoc category: ErrorCategory message: str severity: ErrorSeverity = ErrorSeverity.ERROR suggestions: list[str] = field(default_factory=list) context_lines: list[str] = field(default_factory=list) def format_error( error: AssemblyError, source: str, builtin_line_offset: int = 0, ) -> str: """Format an error with source context in Rust style. Produces output like: error[SCOPE]: Duplicate label '&add' in function '$main' --> line 5, column 3 | 5 | &add <| sub | ^^^ = help: First defined at line 2 Args: error: The AssemblyError to format source: The original source text builtin_line_offset: Number of lines to subtract from error locations to account for prepended built-in macro definitions Returns: Formatted error string with source context """ lines = source.split('\n') display_line = error.loc.line in_builtins = False if builtin_line_offset > 0: if display_line > builtin_line_offset: display_line -= builtin_line_offset else: in_builtins = True # Build the header result = f"{error.severity.value}[{error.category.name}]: {error.message}\n" if in_builtins: result += f" --> line {display_line}, column {error.loc.column}\n" else: result += f" --> line {display_line}, column {error.loc.column}\n" # Extract and display the source line if 0 < error.loc.line <= len(lines): source_line = lines[error.loc.line - 1] # Compute gutter width based on line number gutter_width = len(str(display_line)) result += " " * (gutter_width + 1) + "|\n" result += f"{display_line:>{gutter_width}} | {source_line}\n" # Add carets pointing to the error column(s) caret_col = error.loc.column caret_end_col = error.loc.end_column if error.loc.end_column else caret_col + 1 caret_count = max(1, caret_end_col - caret_col) carets = "^" * caret_count result += " " * (gutter_width + 1) + "| " + " " * (caret_col - 1) + carets + "\n" # Add suggestions for suggestion in error.suggestions: result += f" = help: {suggestion}\n" # Add context lines if provided for line in error.context_lines: result += f" |\n | {line}\n" return result