import "@std/mem" import "@std/arraylist" import "@std/enummap" Span :: struct { start u32 end u32 } DiagnosticId :: distinct u32 no_diagnostic :: maxval!(DiagnosticId) Code :: enum { invalid_character, float_must_end_with_digit, unterminated_string, unexpected_token, duplicate_name, unknown_name, expected_type, type_not_value, invalid_void_type, type_mismatch, invalid_operand, integer_division, not_callable, argument_count_mismatch, cannot_infer_type, missing_return, return_type_mismatch, condition_not_bool, invalid_assignment_target, break_outside_loop, continue_outside_loop, invalid_range_bound, invalid_for_iterable, range_value_not_supported, range_pointer_capture, range_index_capture, missing_entry, invalid_entry, literal_out_of_range, invalid_escape, read_before_initialization, write_before_initialization, initialization_cycle, integer_overflow, } Diagnostic :: struct { code Code span Span } #! owns diagnostic storage and retains its allocator; release with deinit. Store :: struct { list arraylist.ArrayList(Diagnostic) } CapacityError :: enum { diagnostic_limit_reached } #! retains `allocator` for added diagnostics; the caller owns the returned Store. init proc(allocator mem.Allocator) Store { return Store{ list = arraylist.init(allocator) } } #! releases diagnostic storage with the retained allocator. deinit proc(store @mut Store) void { arraylist.deinit(&store.list) } #! allocates from the retained allocator; the returned ID is valid until deinit. add proc(store @mut Store, code Code, span Span) DiagnosticId ! (mem.AllocError | CapacityError) { if (store.list.items.len >= usize(no_diagnostic)) return .diagnostic_limit_reached id :: DiagnosticId(store.list.items.len) try arraylist.append(&store.list, Diagnostic{ code = code, span = span }) return id } get proc(store @Store, id DiagnosticId) ?Diagnostic { if (usize(id) >= store.list.items.len) return null return store.list.items[usize(id)] } count proc(store @Store) usize { return store.list.items.len } #! constructed at compile time so missing entries fail compilation; message bytes live for the process lifetime. messages :: $enummap.init(Code, []u8, { invalid_character = "invalid character", float_must_end_with_digit = "float literal must end with a digit", unterminated_string = "unterminated string literal", unexpected_token = "unexpected token", duplicate_name = "duplicate name or forbidden shadowing", unknown_name = "unknown name", expected_type = "annotation must name a type", type_not_value = "type name cannot be used as a value", invalid_void_type = "void cannot be stored or used as a parameter type", type_mismatch = "incompatible value type", invalid_operand = "operator requires compatible operand types", integer_division = "division requires float operands", not_callable = "value is not callable", argument_count_mismatch = "argument count does not match the function signature", cannot_infer_type = "cannot infer a type without a type anchor", missing_return = "nonvoid function can finish without returning a value", return_type_mismatch = "return does not match the function result type", condition_not_bool = "condition must have type bool", invalid_assignment_target = "assignment requires a mutable local binding", break_outside_loop = "break requires an enclosing loop", continue_outside_loop = "continue requires an enclosing loop", invalid_range_bound = "range bounds must have compatible integer types", invalid_for_iterable = "for-loop iteration currently requires a direct integer range", range_value_not_supported = "first-class range values are not implemented", range_pointer_capture = "range loops do not support pointer captures", range_index_capture = "range loops do not support index captures", missing_entry = "program requires a main function", invalid_entry = "main must be a function with no parameters", literal_out_of_range = "numeric literal is outside its runtime type's range", invalid_escape = "invalid string escape", read_before_initialization = "local value was read before initialization", write_before_initialization = "local value was assigned before initialization", initialization_cycle = "global initialization cycle", integer_overflow = "integer arithmetic overflow", })