package source import "core:fmt" import "core:mem" Span :: struct { start: int, end: int, } Source :: struct { path: string, text: string, } Diagnostic :: struct { span: Span, message: string, } Diagnostics :: struct { source: ^Source, items: [dynamic]Diagnostic, allocator: mem.Allocator, } init_diagnostics :: proc(source_file: ^Source, allocator := context.allocator) -> Diagnostics { result: Diagnostics result.source = source_file result.allocator = allocator result.items.allocator = allocator return result } destroy_diagnostics :: proc(diagnostics: ^Diagnostics) { for diagnostic in diagnostics.items { delete(diagnostic.message, diagnostics.allocator) } delete(diagnostics.items) } add :: proc(diagnostics: ^Diagnostics, span: Span, message: string) -> int { for diagnostic, id in diagnostics.items { if diagnostic.span == span && diagnostic.message == message { return id } } id := len(diagnostics.items) cloned := fmt.aprintf("%s", message, allocator=diagnostics.allocator) append(&diagnostics.items, Diagnostic{span=span, message=cloned}) return id } addf :: proc(diagnostics: ^Diagnostics, span: Span, format: string, args: ..any) -> int { message := fmt.aprintf(format, ..args, allocator=diagnostics.allocator) for diagnostic, id in diagnostics.items { if diagnostic.span == span && diagnostic.message == message { delete(message, diagnostics.allocator) return id } } id := len(diagnostics.items) append(&diagnostics.items, Diagnostic{span=span, message=message}) return id } line_and_column :: proc(source_file: ^Source, offset: int) -> (line, column: int) { line = 1 column = 1 limit := min(offset, len(source_file.text)) for byte_value in transmute([]byte)source_file.text[:limit] { if byte_value == '\n' { line += 1 column = 1 } else { column += 1 } } return } format :: proc(diagnostics: ^Diagnostics, id: int, allocator := context.allocator) -> string { if id < 0 || id >= len(diagnostics.items) { return fmt.aprintf("%s: compiler recovery error", diagnostics.source.path, allocator=allocator) } diagnostic := diagnostics.items[id] line, column := line_and_column(diagnostics.source, diagnostic.span.start) return fmt.aprintf( "%s:%d:%d: error: %s", diagnostics.source.path, line, column, diagnostic.message, allocator=allocator, ) } print_all :: proc(diagnostics: ^Diagnostics) { for _, id in diagnostics.items { message := format(diagnostics, id) fmt.eprintln(message) delete(message) } }