OR-1 dataflow CPU sketch
1"""Structured error types for OR1 assembler.
2
3Provides error categories, the AssemblyError dataclass, and utilities for
4formatting errors with source context in Rust style.
5"""
6
7from dataclasses import dataclass, field
8from enum import Enum
9
10from asm.ir import SourceLoc
11
12
13class ErrorSeverity(Enum):
14 """Severity level for assembly diagnostics."""
15 ERROR = "error"
16 WARNING = "warning"
17
18
19class ErrorCategory(Enum):
20 """Classification of assembly errors."""
21 PARSE = "parse"
22 NAME = "name"
23 SCOPE = "scope"
24 PLACEMENT = "placement"
25 RESOURCE = "resource"
26 ARITY = "arity"
27 PORT = "port"
28 UNREACHABLE = "unreachable"
29 VALUE = "value"
30 MACRO = "macro"
31 CALL = "call"
32
33
34@dataclass(frozen=True)
35class AssemblyError:
36 """Structured error with source location and context.
37
38 Attributes:
39 loc: Source location where the error occurred
40 category: Error classification (see ErrorCategory)
41 message: Human-readable error message
42 suggestions: Optional list of suggestions for fixing the error
43 context_lines: Optional source lines for context
44 """
45 loc: SourceLoc
46 category: ErrorCategory
47 message: str
48 severity: ErrorSeverity = ErrorSeverity.ERROR
49 suggestions: list[str] = field(default_factory=list)
50 context_lines: list[str] = field(default_factory=list)
51
52
53def format_error(
54 error: AssemblyError,
55 source: str,
56 builtin_line_offset: int = 0,
57) -> str:
58 """Format an error with source context in Rust style.
59
60 Produces output like:
61 error[SCOPE]: Duplicate label '&add' in function '$main'
62 --> line 5, column 3
63 |
64 5 | &add <| sub
65 | ^^^
66 = help: First defined at line 2
67
68 Args:
69 error: The AssemblyError to format
70 source: The original source text
71 builtin_line_offset: Number of lines to subtract from error locations
72 to account for prepended built-in macro definitions
73
74 Returns:
75 Formatted error string with source context
76 """
77 lines = source.split('\n')
78 display_line = error.loc.line
79 in_builtins = False
80 if builtin_line_offset > 0:
81 if display_line > builtin_line_offset:
82 display_line -= builtin_line_offset
83 else:
84 in_builtins = True
85
86 # Build the header
87 result = f"{error.severity.value}[{error.category.name}]: {error.message}\n"
88 if in_builtins:
89 result += f" --> <builtin macros> line {display_line}, column {error.loc.column}\n"
90 else:
91 result += f" --> line {display_line}, column {error.loc.column}\n"
92
93 # Extract and display the source line
94 if 0 < error.loc.line <= len(lines):
95 source_line = lines[error.loc.line - 1]
96
97 # Compute gutter width based on line number
98 gutter_width = len(str(display_line))
99
100 result += " " * (gutter_width + 1) + "|\n"
101 result += f"{display_line:>{gutter_width}} | {source_line}\n"
102
103 # Add carets pointing to the error column(s)
104 caret_col = error.loc.column
105 caret_end_col = error.loc.end_column if error.loc.end_column else caret_col + 1
106 caret_count = max(1, caret_end_col - caret_col)
107 carets = "^" * caret_count
108
109 result += " " * (gutter_width + 1) + "| " + " " * (caret_col - 1) + carets + "\n"
110
111 # Add suggestions
112 for suggestion in error.suggestions:
113 result += f" = help: {suggestion}\n"
114
115 # Add context lines if provided
116 for line in error.context_lines:
117 result += f" |\n | {line}\n"
118
119 return result