package source import "core:fmt" import "core:mem" Span :: struct { file: int, start: int, end: int, } Source :: struct { id: int, path: string, text: string, line_starts: []int, } Store :: struct { items: [dynamic]Source, allocator: mem.Allocator, } Diagnostic :: struct { span: Span, message: string, } Diagnostics :: struct { source: ^Source, store: ^Store, items: [dynamic]Diagnostic, index: map[Diagnostic_Key]int, allocator: mem.Allocator, } Diagnostic_Key :: struct { span: Span, message: string, } 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(item.line_starts, store.allocator) } delete(store.items) } make_line_starts :: proc(text: string, allocator: mem.Allocator) -> []int { result: [dynamic]int result.allocator = allocator append(&result, 0) for value, offset in transmute([]byte)text { if value == '\n' { append(&result, offset+1) } } return result[:] } add_source_owned :: proc(store: ^Store, path: string, text: []byte) -> int { id := len(store.items) owned_text := string(text) append(&store.items, Source{ id=id, path=fmt.aprintf("%s", path, allocator=store.allocator), text=owned_text, line_starts=make_line_starts(owned_text, store.allocator), }) return id } add_source :: proc(store: ^Store, path, text: string) -> int { owned := make([]byte, len(text), store.allocator) copy(owned, transmute([]byte)text) return add_source_owned(store, path, owned) } init_diagnostics :: proc(source_file: ^Source, allocator := context.allocator) -> Diagnostics { result: Diagnostics result.source = source_file result.allocator = allocator result.items.allocator = allocator result.index.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 result.index.allocator = allocator return result } destroy_diagnostics :: proc(diagnostics: ^Diagnostics) { delete(diagnostics.index) for diagnostic in diagnostics.items { delete(diagnostic.message, diagnostics.allocator) } delete(diagnostics.items) } add :: proc(diagnostics: ^Diagnostics, span: Span, message: string) -> int { key := Diagnostic_Key{span=span, message=message} if id, ok := diagnostics.index[key]; ok { return id } id := len(diagnostics.items) cloned := fmt.aprintf("%s", message, allocator=diagnostics.allocator) append(&diagnostics.items, Diagnostic{span=span, message=cloned}) diagnostics.index[Diagnostic_Key{span=span, message=cloned}] = id return id } addf :: proc(diagnostics: ^Diagnostics, span: Span, format: string, args: ..any) -> int { message := fmt.aprintf(format, ..args, allocator=diagnostics.allocator) key := Diagnostic_Key{span=span, message=message} if id, ok := diagnostics.index[key]; ok { delete(message, diagnostics.allocator) return id } id := len(diagnostics.items) append(&diagnostics.items, Diagnostic{span=span, message=message}) diagnostics.index[Diagnostic_Key{span=span, message=message}] = id return id } line_and_column :: proc(source_file: ^Source, offset: int) -> (line, column: int) { if len(source_file.line_starts) > 0 { limit := min(max(offset, 0), len(source_file.text)) low := 0 high := len(source_file.line_starts) for low < high { middle := low + (high-low)/2 if source_file.line_starts[middle] <= limit { low = middle+1 } else { high = middle } } line = max(low, 1) column = limit-source_file.line_starts[line-1]+1 return } line = 1 column = 1 limit := min(max(offset, 0), 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) } }