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 FRAME = "frame"
33
34
35@dataclass(frozen=True)
36class AssemblyError:
37 """Structured error with source location and context.
38
39 Attributes:
40 loc: Source location where the error occurred
41 category: Error classification (see ErrorCategory)
42 message: Human-readable error message
43 suggestions: Optional list of suggestions for fixing the error
44 context_lines: Optional source lines for context
45 """
46 loc: SourceLoc
47 category: ErrorCategory
48 message: str
49 severity: ErrorSeverity = ErrorSeverity.ERROR
50 suggestions: list[str] = field(default_factory=list)
51 context_lines: list[str] = field(default_factory=list)
52
53
54def format_error(
55 error: AssemblyError,
56 source: str,
57 builtin_line_offset: int = 0,
58) -> str:
59 """Format an error with source context in Rust style.
60
61 Produces output like:
62 error[SCOPE]: Duplicate label '&add' in function '$main'
63 --> line 5, column 3
64 |
65 5 | &add <| sub
66 | ^^^
67 = help: First defined at line 2
68
69 Args:
70 error: The AssemblyError to format
71 source: The original source text
72 builtin_line_offset: Number of lines to subtract from error locations
73 to account for prepended built-in macro definitions
74
75 Returns:
76 Formatted error string with source context
77 """
78 lines = source.split('\n')
79 display_line = error.loc.line
80 in_builtins = False
81 if builtin_line_offset > 0:
82 if display_line > builtin_line_offset:
83 display_line -= builtin_line_offset
84 else:
85 in_builtins = True
86
87 # Build the header
88 result = f"{error.severity.value}[{error.category.name}]: {error.message}\n"
89 if in_builtins:
90 result += f" --> <builtin macros> line {display_line}, column {error.loc.column}\n"
91 else:
92 result += f" --> line {display_line}, column {error.loc.column}\n"
93
94 # Extract and display the source line
95 if 0 < error.loc.line <= len(lines):
96 source_line = lines[error.loc.line - 1]
97
98 # Compute gutter width based on line number
99 gutter_width = len(str(display_line))
100
101 result += " " * (gutter_width + 1) + "|\n"
102 result += f"{display_line:>{gutter_width}} | {source_line}\n"
103
104 # Add carets pointing to the error column(s)
105 caret_col = error.loc.column
106 caret_end_col = error.loc.end_column if error.loc.end_column else caret_col + 1
107 caret_count = max(1, caret_end_col - caret_col)
108 carets = "^" * caret_count
109
110 result += " " * (gutter_width + 1) + "| " + " " * (caret_col - 1) + carets + "\n"
111
112 # Add suggestions
113 for suggestion in error.suggestions:
114 result += f" = help: {suggestion}\n"
115
116 # Add context lines if provided
117 for line in error.context_lines:
118 result += f" |\n | {line}\n"
119
120 return result