package source import "core:fmt" import "core:mem" Span :: struct { file: int, start: int, end: int, } Source :: struct { id: int, path: string, text: string, } Store :: struct { items: [dynamic]Source, allocator: mem.Allocator, } Diagnostic :: struct { span: Span, message: string, } Diagnostics :: struct { source: ^Source, store: ^Store, items: [dynamic]Diagnostic, allocator: mem.Allocator, } init_store :: proc(allocator := context.allocator) -> Store { result: Store result.allocator = allocator result.items.allocator = allocator return result } destroy_store :: proc(store: ^Store) { for item in store.items { delete(item.path, store.allocator) delete(item.text, store.allocator) } delete(store.items) } add_source :: proc(store: ^Store, path, text: string) -> int { id := len(store.items) append(&store.items, Source{ id=id, path=fmt.aprintf("%s", path, allocator=store.allocator), text=fmt.aprintf("%s", text, allocator=store.allocator), }) return id } 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 } init_store_diagnostics :: proc(store: ^Store, allocator := context.allocator) -> Diagnostics { result: Diagnostics result.store = store 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 } source_for_span :: proc(diagnostics: ^Diagnostics, span: Span) -> ^Source { if diagnostics.store != nil && span.file >= 0 && span.file < len(diagnostics.store.items) { return &diagnostics.store.items[span.file] } return diagnostics.source } format :: proc(diagnostics: ^Diagnostics, id: int, allocator := context.allocator) -> string { if id < 0 || id >= len(diagnostics.items) { path := "" if diagnostics.source != nil { path = diagnostics.source.path } else if diagnostics.store != nil && len(diagnostics.store.items) > 0 { path = diagnostics.store.items[0].path } return fmt.aprintf("%s: compiler recovery error", path, allocator=allocator) } diagnostic := diagnostics.items[id] source_file := source_for_span(diagnostics, diagnostic.span) if source_file == nil { return fmt.aprintf(": error: %s", diagnostic.message, allocator=allocator) } line, column := line_and_column(source_file, diagnostic.span.start) return fmt.aprintf( "%s:%d:%d: error: %s", source_file.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) } }