Files
brolang/compiler/source/source.odin
T

246 lines
6.3 KiB
Odin

package source
import "core:fmt"
import "core:mem"
Source_Id :: distinct u32
Diagnostic_Id :: distinct u32
Offset :: distinct u32
INVALID_SOURCE :: Source_Id(0xffff_ffff)
INVALID_DIAGNOSTIC :: Diagnostic_Id(0xffff_ffff)
source_id :: proc(index: int) -> Source_Id {
assert(index >= 0 && u64(index) < u64(INVALID_SOURCE))
return Source_Id(index)
}
diagnostic_id :: proc(index: int) -> Diagnostic_Id {
assert(index >= 0 && u64(index) < u64(INVALID_DIAGNOSTIC))
return Diagnostic_Id(index)
}
source_index :: proc(id: Source_Id, count: int) -> (int, bool) {
index := int(id)
return index, id != INVALID_SOURCE && index < count
}
diagnostic_index :: proc(id: Diagnostic_Id, count: int) -> (int, bool) {
index := int(id)
return index, id != INVALID_DIAGNOSTIC && index < count
}
fits_source_length :: proc(length: u64) -> bool {
return length <= u64(max(Offset))
}
Span :: struct {
file: Source_Id,
start: Offset,
end: Offset,
}
Source :: struct {
id: Source_Id,
path: string,
text: string,
line_starts: []Offset,
}
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]Diagnostic_Id,
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) -> []Offset {
assert(fits_source_length(u64(len(text))))
result: [dynamic]Offset
result.allocator = allocator
append(&result, 0)
for value, offset in transmute([]byte)text {
if value == '\n' {
append(&result, Offset(offset+1))
}
}
return result[:]
}
add_source_owned :: proc(store: ^Store, path: string, text: []byte) -> Source_Id {
assert(fits_source_length(u64(len(text))))
id := source_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) -> Source_Id {
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) -> Diagnostic_Id {
key := Diagnostic_Key{span=span, message=message}
if id, ok := diagnostics.index[key]; ok {
return id
}
id := diagnostic_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) -> Diagnostic_Id {
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 := diagnostic_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: Offset) -> (line, column: int) {
if len(source_file.line_starts) > 0 {
limit := min(int(offset), len(source_file.text))
low := 0
high := len(source_file.line_starts)
for low < high {
middle := low + (high-low)/2
if int(source_file.line_starts[middle]) <= limit {
low = middle+1
} else {
high = middle
}
}
line = max(low, 1)
column = limit-int(source_file.line_starts[line-1])+1
return
}
line = 1
column = 1
limit := min(int(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 {
if index, ok := source_index(span.file, len(diagnostics.store.items)); ok {
return &diagnostics.store.items[index]
}
}
return diagnostics.source
}
format :: proc(diagnostics: ^Diagnostics, id: Diagnostic_Id, allocator := context.allocator) -> string {
index, ok := diagnostic_index(id, len(diagnostics.items))
if !ok {
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[index]
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, diagnostic_id(id))
fmt.eprintln(message)
delete(message)
}
}