210 lines
5.2 KiB
Odin
210 lines
5.2 KiB
Odin
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 := "<unknown>"
|
|
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("<unknown>: 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)
|
|
}
|
|
}
|