package-type imports

This commit is contained in:
2026-06-10 00:08:33 +02:00
parent 087fdb45d5
commit d6e03b6f08
104 changed files with 1799 additions and 273 deletions
+44
View File
@@ -26,6 +26,7 @@ Expr_Kind :: enum {
Expr :: struct {
kind: Expr_Kind,
span: source.Span,
qualifier: string,
text: string,
integer: i64,
left: int,
@@ -61,6 +62,8 @@ Stmt :: struct {
Function :: struct {
span: source.Span,
name: string,
pkg: int,
file: int,
c_abi: bool,
params: []Param,
result: Type_Syntax,
@@ -71,17 +74,45 @@ Function :: struct {
Global :: struct {
span: source.Span,
name: string,
pkg: int,
file: int,
type: Type_Syntax,
immutable: bool,
expr: int,
diagnostic: int,
}
Import :: struct {
span: source.Span,
alias: string,
path: string,
pkg: int,
file: int,
target: int,
valid: bool,
used: bool,
diagnostic: int,
}
File :: struct {
source: int,
pkg: int,
}
Package :: struct {
path: string,
name: string,
available: bool,
}
Module :: struct {
exprs: [dynamic]Expr,
statements: [dynamic]Stmt,
functions: [dynamic]Function,
globals: [dynamic]Global,
imports: [dynamic]Import,
files: [dynamic]File,
packages: [dynamic]Package,
allocator: mem.Allocator,
}
@@ -92,6 +123,9 @@ init_module :: proc(allocator := context.allocator) -> Module {
module.statements.allocator = allocator
module.functions.allocator = allocator
module.globals.allocator = allocator
module.imports.allocator = allocator
module.files.allocator = allocator
module.packages.allocator = allocator
return module
}
@@ -103,8 +137,18 @@ destroy_module :: proc(module: ^Module) {
delete(function.params, module.allocator)
delete(function.body, module.allocator)
}
for import_item in module.imports {
delete(import_item.path, module.allocator)
}
for pkg in module.packages {
delete(pkg.path, module.allocator)
delete(pkg.name, module.allocator)
}
delete(module.exprs)
delete(module.statements)
delete(module.functions)
delete(module.globals)
delete(module.imports)
delete(module.files)
delete(module.packages)
}
+302 -176
View File
@@ -7,6 +7,7 @@ import "../types"
import "base:intrinsics"
import "core:fmt"
import "core:mem"
import "core:slice"
import "core:strings"
Spec :: struct {
@@ -40,14 +41,23 @@ Constant :: struct {
value: i128,
}
Symbol_Index_Entry :: struct {
scope: int,
name: string,
id: int,
}
Checker :: struct {
ast_module: ^ast.Module,
diagnostics: ^source.Diagnostics,
module: hir.Module,
specs: [dynamic]Spec,
global_types: []types.Type,
constants: []Constant,
allocator: mem.Allocator,
ast_module: ^ast.Module,
diagnostics: ^source.Diagnostics,
module: hir.Module,
specs: [dynamic]Spec,
function_index: []Symbol_Index_Entry,
global_index: []Symbol_Index_Entry,
import_index: []Symbol_Index_Entry,
global_types: []types.Type,
constants: []Constant,
allocator: mem.Allocator,
}
eval_constant :: proc(checker: ^Checker, expr_id: int) -> Constant {
@@ -116,22 +126,123 @@ type_from_syntax :: proc(value: ast.Type_Syntax) -> types.Type {
return types.INVALID
}
find_template :: proc(checker: ^Checker, name: string) -> int {
for function, index in checker.ast_module.functions {
if function.name == name {
return index
symbol_index_less :: proc(left, right: Symbol_Index_Entry) -> bool {
if left.scope != right.scope {
return left.scope < right.scope
}
if left.name != right.name {
return left.name < right.name
}
return left.id < right.id
}
find_symbol :: proc(index: []Symbol_Index_Entry, scope: int, name: string) -> int {
low := 0
high := len(index)
for low < high {
middle := low + (high-low)/2
entry := index[middle]
if entry.scope < scope || entry.scope == scope && entry.name < name {
low = middle + 1
} else {
high = middle
}
}
if low < len(index) && index[low].scope == scope && index[low].name == name {
return index[low].id
}
return -1
}
find_global :: proc(checker: ^Checker, name: string) -> int {
for global, index in checker.ast_module.globals {
if global.name == name {
return index
}
build_symbol_indexes :: proc(checker: ^Checker) {
checker.function_index = make([]Symbol_Index_Entry, len(checker.ast_module.functions), checker.allocator)
for function, id in checker.ast_module.functions {
checker.function_index[id] = Symbol_Index_Entry{scope=function.pkg, name=function.name, id=id}
}
return -1
slice.sort_by(checker.function_index, symbol_index_less)
checker.global_index = make([]Symbol_Index_Entry, len(checker.ast_module.globals), checker.allocator)
for global, id in checker.ast_module.globals {
checker.global_index[id] = Symbol_Index_Entry{scope=global.pkg, name=global.name, id=id}
}
slice.sort_by(checker.global_index, symbol_index_less)
checker.import_index = make([]Symbol_Index_Entry, len(checker.ast_module.imports), checker.allocator)
for import_item, id in checker.ast_module.imports {
checker.import_index[id] = Symbol_Index_Entry{scope=import_item.file, name=import_item.alias, id=id}
}
slice.sort_by(checker.import_index, symbol_index_less)
}
find_template :: proc(checker: ^Checker, name: string, pkg := 0) -> int {
return find_symbol(checker.function_index, pkg, name)
}
find_global :: proc(checker: ^Checker, name: string, pkg := 0) -> int {
return find_symbol(checker.global_index, pkg, name)
}
find_import :: proc(checker: ^Checker, file: int, alias: string, mark_used := false) -> int {
id := find_symbol(checker.import_index, file, alias)
if id >= 0 && mark_used {
checker.ast_module.imports[id].used = true
}
return id
}
expr_package :: proc(checker: ^Checker, expr: ast.Expr, pkg, file: int, mark_used := false) -> (int, bool) {
if expr.qualifier == "" {
return pkg, true
}
import_id := find_import(checker, file, expr.qualifier, mark_used)
if import_id < 0 {
return -1, false
}
import_item := checker.ast_module.imports[import_id]
if import_item.target < 0 || import_item.target >= len(checker.ast_module.packages) ||
!checker.ast_module.packages[import_item.target].available {
return import_item.target, false
}
return import_item.target, true
}
add_package_resolution_diagnostic :: proc(checker: ^Checker, expr: ast.Expr, file: int) -> int {
if find_import(checker, file, expr.qualifier) < 0 {
return source.addf(checker.diagnostics, expr.span, "unknown package alias '%s'", expr.qualifier)
}
return source.addf(checker.diagnostics, expr.span, "unavailable imported package '%s'", expr.qualifier)
}
add_name_resolution_diagnostic :: proc(checker: ^Checker, expr: ast.Expr, target_pkg: int) -> int {
if find_template(checker, expr.text, target_pkg) >= 0 {
return source.addf(checker.diagnostics, expr.span, "'%s' is a function, not a global value", expr.text)
}
if expr.qualifier != "" {
return source.addf(
checker.diagnostics,
expr.span,
"package '%s' has no member '%s'",
expr.qualifier,
expr.text,
)
}
return source.addf(checker.diagnostics, expr.span, "unresolved global '%s'", expr.text)
}
add_call_resolution_diagnostic :: proc(checker: ^Checker, expr: ast.Expr, target_pkg: int) -> int {
if find_global(checker, expr.text, target_pkg) >= 0 {
return source.addf(checker.diagnostics, expr.span, "'%s' is a global, not a function", expr.text)
}
if expr.qualifier != "" {
return source.addf(
checker.diagnostics,
expr.span,
"package '%s' has no member '%s'",
expr.qualifier,
expr.text,
)
}
return source.addf(checker.diagnostics, expr.span, "unresolved function '%s'", expr.text)
}
contains_name :: proc(names: []string, name: string) -> bool {
@@ -143,41 +254,31 @@ contains_name :: proc(names: []string, name: string) -> bool {
return false
}
validate_expr_names :: proc(checker: ^Checker, expr_id: int, locals: []string) {
mark_expr_imports_used :: proc(checker: ^Checker, expr_id, file: int) {
if expr_id < 0 || expr_id >= len(checker.ast_module.exprs) {
return
}
expr := checker.ast_module.exprs[expr_id]
switch expr.kind {
case .Name:
if !contains_name(locals, expr.text) && find_global(checker, expr.text) < 0 {
source.addf(checker.diagnostics, expr.span, "unresolved name '%s'", expr.text)
if expr.qualifier != "" {
_ = find_import(checker, file, expr.qualifier, true)
}
case .Call:
if find_template(checker, expr.text) < 0 {
source.addf(checker.diagnostics, expr.span, "unresolved function '%s'", expr.text)
if expr.qualifier != "" {
_ = find_import(checker, file, expr.qualifier, true)
}
for arg in expr.args {
validate_expr_names(checker, arg, locals)
mark_expr_imports_used(checker, arg, file)
}
case .Add:
validate_expr_names(checker, expr.left, locals)
validate_expr_names(checker, expr.right, locals)
mark_expr_imports_used(checker, expr.left, file)
mark_expr_imports_used(checker, expr.right, file)
case .Invalid, .Integer:
}
}
validate_templates :: proc(checker: ^Checker) {
for global in checker.ast_module.globals {
if global.type == .Void {
source.add(
checker.diagnostics,
global.span,
"void is only valid as a function result type",
)
}
validate_expr_names(checker, global.expr, nil)
}
validate_declarations :: proc(checker: ^Checker) {
for function in checker.ast_module.functions {
locals: [dynamic]string
locals.allocator = checker.allocator
@@ -202,36 +303,8 @@ validate_templates :: proc(checker: ^Checker) {
for statement_id in function.body {
statement := checker.ast_module.statements[statement_id]
switch statement.kind {
case .Declaration:
validate_expr_names(checker, statement.expr, locals[:])
if statement.type == .Void {
source.add(
checker.diagnostics,
statement.span,
"void is only valid as a function result type",
)
}
if contains_name(locals[:], statement.name) {
source.addf(
checker.diagnostics,
statement.span,
"duplicate local '%s'",
statement.name,
)
}
append(&locals, statement.name)
case .Assignment:
validate_expr_names(checker, statement.expr, locals[:])
if statement.name != "_" && !contains_name(locals[:], statement.name) {
source.addf(
checker.diagnostics,
statement.span,
"cannot assign unresolved local '%s'",
statement.name,
)
}
case .Return, .Expression:
validate_expr_names(checker, statement.expr, locals[:])
case .Declaration, .Assignment, .Return, .Expression:
mark_expr_imports_used(checker, statement.expr, function.file)
case .Invalid:
}
}
@@ -268,6 +341,19 @@ specialized_param_type :: proc(syntax: ast.Type_Syntax, actual: types.Type) -> t
return declared
}
can_specialize :: proc(function: ast.Function, actual_args: []types.Type) -> bool {
for param, index in function.params {
actual := types.INVALID
if index < len(actual_args) {
actual = actual_args[index]
}
if !types.is_concrete_integer(specialized_param_type(param.type, actual)) {
return false
}
}
return true
}
ensure_spec :: proc(checker: ^Checker, template: int, actual_args: []types.Type) -> int {
function := checker.ast_module.functions[template]
signature: [dynamic]types.Type
@@ -286,7 +372,7 @@ ensure_spec :: proc(checker: ^Checker, template: int, actual_args: []types.Type)
}
}
result := type_from_syntax(function.result)
if function.name == "main" && function.result == .Int {
if function.pkg == 0 && function.name == "main" && function.result == .Int {
result = types.I32
}
index := len(checker.specs)
@@ -297,7 +383,7 @@ ensure_spec :: proc(checker: ^Checker, template: int, actual_args: []types.Type)
return index
}
infer_expr :: proc(checker: ^Checker, expr_id: int, locals: []Infer_Local) -> types.Type {
infer_expr :: proc(checker: ^Checker, expr_id: int, locals: []Infer_Local, pkg := 0, file := 0) -> types.Type {
if expr_id < 0 || expr_id >= len(checker.ast_module.exprs) {
return types.INVALID
}
@@ -315,27 +401,49 @@ infer_expr :: proc(checker: ^Checker, expr_id: int, locals: []Infer_Local) -> ty
case .Integer:
return types.smallest_signed_for_literal(expr.integer)
case .Name:
local_type := find_infer_local(locals, expr.text)
if types.is_valid(local_type) {
return local_type
if expr.qualifier == "" {
local_type := find_infer_local(locals, expr.text)
if types.is_valid(local_type) {
return local_type
}
}
global := find_global(checker, expr.text)
target_pkg, available := expr_package(checker, expr, pkg, file)
if !available {
return types.INVALID
}
global := find_global(checker, expr.text, target_pkg)
if global >= 0 {
return checker.global_types[global]
}
return types.INVALID
case .Add:
left := infer_expr(checker, expr.left, locals)
right := infer_expr(checker, expr.right, locals)
left := infer_expr(checker, expr.left, locals, pkg, file)
right := infer_expr(checker, expr.right, locals, pkg, file)
return types.widest(left, right)
case .Call:
template := find_template(checker, expr.text)
target_pkg, available := expr_package(checker, expr, pkg, file)
if !available {
return types.INVALID
}
template := find_template(checker, expr.text, target_pkg)
if template < 0 {
return types.INVALID
}
args := make([]types.Type, len(expr.args), checker.allocator)
for arg, index in expr.args {
args[index] = infer_expr(checker, arg, locals)
args[index] = infer_expr(checker, arg, locals, pkg, file)
}
function := checker.ast_module.functions[template]
if !can_specialize(function, args) {
delete(args, checker.allocator)
declared := type_from_syntax(function.result)
if function.pkg == 0 && function.name == "main" && function.result == .Int {
return types.I32
}
if declared.kind == .Concrete || declared.kind == .Void {
return declared
}
return types.INVALID
}
spec := ensure_spec(checker, template, args)
delete(args, checker.allocator)
@@ -348,7 +456,7 @@ infer_spec_result :: proc(checker: ^Checker, spec_id: int) -> types.Type {
spec := checker.specs[spec_id]
function := checker.ast_module.functions[spec.template]
declared := type_from_syntax(function.result)
if function.name == "main" && function.result == .Int {
if function.pkg == 0 && function.name == "main" && function.result == .Int {
declared = types.I32
}
@@ -368,17 +476,17 @@ infer_spec_result :: proc(checker: ^Checker, spec_id: int) -> types.Type {
statement := checker.ast_module.statements[statement_id]
#partial switch statement.kind {
case .Declaration:
value_type := infer_expr(checker, statement.expr, locals[:])
value_type := infer_expr(checker, statement.expr, locals[:], function.pkg, function.file)
declared_local := type_from_syntax(statement.type)
if declared_local.kind == .Concrete {
value_type = declared_local
}
append(&locals, Infer_Local{name = statement.name, type = value_type})
case .Assignment, .Expression:
_ = infer_expr(checker, statement.expr, locals[:])
_ = infer_expr(checker, statement.expr, locals[:], function.pkg, function.file)
case .Return:
if statement.expr >= 0 {
returned := infer_expr(checker, statement.expr, locals[:])
returned := infer_expr(checker, statement.expr, locals[:], function.pkg, function.file)
if !types.is_valid(result) {
result = returned
} else {
@@ -393,6 +501,22 @@ infer_spec_result :: proc(checker: ^Checker, spec_id: int) -> types.Type {
return declared
}
merge_inferred_type :: proc(current: ^types.Type, inferred: types.Type) -> bool {
if !types.is_concrete_integer(inferred) {
return false
}
if !types.is_concrete_integer(current^) {
current^ = inferred
return true
}
merged := types.widest(current^, inferred)
if types.is_concrete_integer(merged) && !types.equal(current^, merged) {
current^ = merged
return true
}
return false
}
infer_all :: proc(checker: ^Checker) {
for global, index in checker.ast_module.globals {
declared := type_from_syntax(global.type)
@@ -400,40 +524,25 @@ infer_all :: proc(checker: ^Checker) {
checker.global_types[index] = declared
}
}
for _ in 0 ..< max(4, len(checker.ast_module.globals) + 1) {
changed := false
for global, index in checker.ast_module.globals {
if types.is_valid(checker.global_types[index]) {
continue
}
inferred := infer_expr(checker, global.expr, nil)
if types.is_valid(inferred) {
checker.global_types[index] = inferred
changed = true
}
}
if !changed {
break
}
}
main_template := find_template(checker, "main")
main_template := find_template(checker, "main", 0)
if main_template >= 0 {
ensure_spec(checker, main_template, nil)
}
for global in checker.ast_module.globals {
_ = infer_expr(checker, global.expr, nil)
}
for _ in 0 ..< 64 {
for {
changed := false
spec_count := len(checker.specs)
for spec_id in 0 ..< spec_count {
inferred := infer_spec_result(checker, spec_id)
if types.is_valid(inferred) && !types.equal(checker.specs[spec_id].result, inferred) {
checker.specs[spec_id].result = inferred
changed = true
for global, index in checker.ast_module.globals {
if type_from_syntax(global.type).kind == .Concrete {
continue
}
inferred := infer_expr(checker, global.expr, nil, global.pkg, global.file)
changed = merge_inferred_type(&checker.global_types[index], inferred) || changed
}
for spec_id := 0; spec_id < len(checker.specs); spec_id += 1 {
inferred := infer_spec_result(checker, spec_id)
changed = merge_inferred_type(&checker.specs[spec_id].result, inferred) || changed
}
if len(checker.specs) != spec_count {
changed = true
@@ -442,14 +551,6 @@ infer_all :: proc(checker: ^Checker) {
break
}
}
for global, index in checker.ast_module.globals {
if type_from_syntax(global.type).kind != .Concrete {
inferred := infer_expr(checker, global.expr, nil)
if types.is_concrete_integer(inferred) {
checker.global_types[index] = inferred
}
}
}
}
add_hir_expr :: proc(checker: ^Checker, expr: hir.Expr) -> int {
@@ -589,6 +690,8 @@ build_expr :: proc(
global_reads: ^[dynamic]int,
calls: ^[dynamic]int,
expected := types.INVALID,
pkg := 0,
file := 0,
) -> int {
if expr_id < 0 || expr_id >= len(checker.ast_module.exprs) {
id := source.add(checker.diagnostics, source.Span{}, "missing expression")
@@ -605,21 +708,28 @@ build_expr :: proc(
case .Integer:
unreachable()
case .Name:
if local, ok := find_build_local(locals, expr.text); ok {
return add_hir_expr(
checker,
hir.Expr {
kind = .Local,
span = expr.span,
type = local.type,
target = local.id,
left = -1,
right = -1,
diagnostic = -1,
},
)
if expr.qualifier == "" {
if local, ok := find_build_local(locals, expr.text); ok {
return add_hir_expr(
checker,
hir.Expr {
kind = .Local,
span = expr.span,
type = local.type,
target = local.id,
left = -1,
right = -1,
diagnostic = -1,
},
)
}
}
global := find_global(checker, expr.text)
target_pkg, available := expr_package(checker, expr, pkg, file, true)
if !available {
id := add_package_resolution_diagnostic(checker, expr, file)
return invalid_hir_expr(checker, expr.span, id)
}
global := find_global(checker, expr.text, target_pkg)
if global >= 0 {
add_unique(global_reads, global)
return add_hir_expr(
@@ -635,11 +745,11 @@ build_expr :: proc(
},
)
}
id := source.addf(checker.diagnostics, expr.span, "unresolved name '%s'", expr.text)
id := add_name_resolution_diagnostic(checker, expr, target_pkg)
return invalid_hir_expr(checker, expr.span, id)
case .Add:
left := build_expr(checker, expr.left, locals, global_reads, calls)
right := build_expr(checker, expr.right, locals, global_reads, calls)
left := build_expr(checker, expr.left, locals, global_reads, calls, types.INVALID, pkg, file)
right := build_expr(checker, expr.right, locals, global_reads, calls, types.INVALID, pkg, file)
left_type := checker.module.exprs[left].type
right_type := checker.module.exprs[right].type
result := types.widest(left_type, right_type)
@@ -666,14 +776,14 @@ build_expr :: proc(
},
)
case .Call:
template := find_template(checker, expr.text)
target_pkg, available := expr_package(checker, expr, pkg, file, true)
if !available {
id := add_package_resolution_diagnostic(checker, expr, file)
return invalid_hir_expr(checker, expr.span, id)
}
template := find_template(checker, expr.text, target_pkg)
if template < 0 {
id := source.addf(
checker.diagnostics,
expr.span,
"unresolved function '%s'",
expr.text,
)
id := add_call_resolution_diagnostic(checker, expr, target_pkg)
return invalid_hir_expr(checker, expr.span, id)
}
if len(expr.args) != len(checker.ast_module.functions[template].params) {
@@ -701,6 +811,8 @@ build_expr :: proc(
global_reads,
calls,
arg_expected,
pkg,
file,
)
arg_types[index] = checker.module.exprs[built_args[index]].type
}
@@ -750,24 +862,13 @@ build_expr :: proc(
make_link_name :: proc(checker: ^Checker, spec_id: int) -> string {
spec := checker.specs[spec_id]
function := checker.ast_module.functions[spec.template]
if function.name == "main" {
if function.pkg == 0 && function.name == "main" {
return fmt.aprintf("main", allocator = checker.allocator)
}
has_generic_params := false
for param in function.params {
if param.type == .Int {
has_generic_params = true
break
}
}
if function.c_abi && !has_generic_params {
return fmt.aprintf("%s", function.name, allocator = checker.allocator)
}
builder := strings.builder_make(checker.allocator)
defer strings.builder_destroy(&builder)
if !function.c_abi {
strings.write_string(&builder, "bro__")
}
strings.write_string(&builder, "bro_c__" if function.c_abi else "bro__")
fmt.sbprintf(&builder, "p%d__", function.pkg)
strings.write_string(&builder, function.name)
for arg in spec.args {
strings.write_string(&builder, "__")
@@ -783,7 +884,7 @@ build_function :: proc(checker: ^Checker, spec_id: int) {
spec := checker.specs[spec_id]
function := checker.ast_module.functions[spec.template]
signature_diagnostic := -1
if !types.is_valid(spec.result) {
if spec.result.kind != .Void && !types.is_concrete_integer(spec.result) {
checker.specs[spec_id].result = types.I64
spec.result = types.I64
signature_diagnostic = source.addf(
@@ -862,6 +963,8 @@ build_function :: proc(checker: ^Checker, spec_id: int) {
&global_reads,
&calls,
expected,
function.pkg,
function.file,
)
value_type := checker.module.exprs[value].type
if declared.kind == .Concrete {
@@ -929,7 +1032,7 @@ build_function :: proc(checker: ^Checker, spec_id: int) {
problematic = problematic || checker.module.exprs[value].kind == .Invalid
case .Assignment:
if statement.name == "_" {
value := build_expr(checker, statement.expr, locals[:], &global_reads, &calls)
value := build_expr(checker, statement.expr, locals[:], &global_reads, &calls, types.INVALID, function.pkg, function.file)
if checker.module.exprs[value].type.kind == .Void {
id := source.add(
checker.diagnostics,
@@ -1013,6 +1116,8 @@ build_function :: proc(checker: ^Checker, spec_id: int) {
&global_reads,
&calls,
local.type,
function.pkg,
function.file,
)
value = coerce_expr(checker, value, local.type, statement.span)
append(&body, len(checker.module.statements))
@@ -1090,6 +1195,8 @@ build_function :: proc(checker: ^Checker, spec_id: int) {
&global_reads,
&calls,
spec.result,
function.pkg,
function.file,
)
value = coerce_expr(checker, value, spec.result, statement.span)
append(&body, len(checker.module.statements))
@@ -1105,7 +1212,7 @@ build_function :: proc(checker: ^Checker, spec_id: int) {
)
problematic = problematic || checker.module.exprs[value].kind == .Invalid
case .Expression:
value := build_expr(checker, statement.expr, locals[:], &global_reads, &calls)
value := build_expr(checker, statement.expr, locals[:], &global_reads, &calls, types.INVALID, function.pkg, function.file)
if checker.module.exprs[value].type.kind != .Void {
id := source.add(
checker.diagnostics,
@@ -1173,8 +1280,8 @@ build_function :: proc(checker: ^Checker, spec_id: int) {
hir.Function {
name = function.name,
link_name = make_link_name(checker, spec_id),
c_abi = function.c_abi || function.name == "main",
is_main = function.name == "main",
c_abi = function.c_abi || (function.pkg == 0 && function.name == "main"),
is_main = function.pkg == 0 && function.name == "main",
params = params[:],
result = spec.result,
locals = hir_locals[:],
@@ -1221,7 +1328,7 @@ build_globals :: proc(checker: ^Checker) {
if declared.kind == .Concrete {
expected = declared
}
expr := build_expr(checker, global.expr, nil, &dependencies, &calls, expected)
expr := build_expr(checker, global.expr, nil, &dependencies, &calls, expected, global.pkg, global.file)
global_type := checker.global_types[global_id]
if declared.kind == .Concrete {
expr = coerce_expr(checker, expr, declared, global.span)
@@ -1229,10 +1336,17 @@ build_globals :: proc(checker: ^Checker) {
} else if types.is_concrete_integer(checker.module.exprs[expr].type) {
global_type = checker.module.exprs[expr].type
}
if !types.is_concrete_integer(global_type) {
global_type = types.I64
}
diagnostic := -1
if !types.is_concrete_integer(global_type) {
diagnostic = source.addf(
checker.diagnostics,
global.span,
"could not resolve a concrete type for global '%s'",
global.name,
)
global_type = types.I64
expr = invalid_hir_expr(checker, global.span, diagnostic, global_type)
}
if global.type == .Void {
diagnostic = source.add(
checker.diagnostics,
@@ -1389,16 +1503,14 @@ detect_global_cycles_visit :: proc(checker: ^Checker, global_id: int, states: []
return
}
if states[global_id] == 1 {
if !checker.module.globals[global_id].problematic {
id := source.addf(
checker.diagnostics,
checker.ast_module.globals[global_id].span,
"global initialization cycle involving '%s'",
checker.module.globals[global_id].name,
)
checker.module.globals[global_id].diagnostic = id
checker.module.globals[global_id].problematic = true
}
id := source.addf(
checker.diagnostics,
checker.ast_module.globals[global_id].span,
"global initialization cycle involving '%s'",
checker.module.globals[global_id].name,
)
checker.module.globals[global_id].diagnostic = id
checker.module.globals[global_id].problematic = true
return
}
states[global_id] = 1
@@ -1478,6 +1590,7 @@ check :: proc(
allocator = allocator,
}
checker.specs.allocator = allocator
build_symbol_indexes(&checker)
checker.global_types = make([]types.Type, len(ast_module.globals), allocator)
checker.constants = make([]Constant, len(ast_module.exprs), allocator)
defer {
@@ -1485,26 +1598,34 @@ check :: proc(
delete(spec.args, allocator)
}
delete(checker.specs)
delete(checker.function_index, allocator)
delete(checker.global_index, allocator)
delete(checker.import_index, allocator)
delete(checker.global_types, allocator)
delete(checker.constants, allocator)
}
for function, index in ast_module.functions {
for previous in ast_module.functions[:index] {
if previous.name == function.name {
if previous.pkg == function.pkg && previous.name == function.name {
source.addf(diagnostics, function.span, "duplicate function '%s'", function.name)
}
}
for global in ast_module.globals {
if global.pkg == function.pkg && global.name == function.name {
source.addf(diagnostics, function.span, "package declaration '%s' conflicts with a global", function.name)
}
}
}
for global, index in ast_module.globals {
for previous in ast_module.globals[:index] {
if previous.name == global.name {
if previous.pkg == global.pkg && previous.name == global.name {
source.addf(diagnostics, global.span, "duplicate global '%s'", global.name)
}
}
}
validate_templates(&checker)
validate_declarations(&checker)
infer_all(&checker)
build_globals(&checker)
for spec_id := 0; spec_id < len(checker.specs); spec_id += 1 {
@@ -1513,10 +1634,10 @@ check :: proc(
resolve_call_targets(&checker)
propagate_global_reads(&checker)
main_template := find_template(&checker, "main")
main_template := find_template(&checker, "main", 0)
main_declarations := 0
for function in ast_module.functions {
if function.name == "main" {
if function.pkg == 0 && function.name == "main" {
main_declarations += 1
}
}
@@ -1543,5 +1664,10 @@ check :: proc(
}
delete(states, allocator)
propagate_problems(&checker)
for import_item in ast_module.imports {
if import_item.valid && !import_item.used {
source.addf(diagnostics, import_item.span, "unused import '%s'", import_item.alias)
}
}
return checker.module
}
+16 -14
View File
@@ -2,27 +2,20 @@ package compiler
import "./backend"
import "./checker"
import "./lexer"
import "./llvm"
import "./loader"
import "./lower"
import "./opt"
import "./parser"
import "./source"
import "core:fmt"
import vmem "core:mem/virtual"
import "core:os"
import "core:os/os2"
compile_file :: proc(input_path, output_path: string) -> int {
source_bytes, ok := os.read_entire_file(input_path)
if !ok {
fmt.eprintln("failed to read input:", input_path)
return 2
}
defer delete(source_bytes)
source_file := source.Source{path=input_path, text=string(source_bytes)}
diagnostics := source.init_diagnostics(&source_file)
compile_package :: proc(input_path, output_path: string) -> int {
sources := source.init_store()
defer source.destroy_store(&sources)
diagnostics := source.init_store_diagnostics(&sources)
defer source.destroy_diagnostics(&diagnostics)
lexer_arena: vmem.Arena
@@ -50,8 +43,17 @@ compile_file :: proc(input_path, output_path: string) -> int {
}
defer vmem.arena_destroy(&lower_arena)
tokens := lexer.lex(&source_file, &diagnostics, vmem.arena_allocator(&lexer_arena))
ast_module := parser.parse(&tokens, &diagnostics, vmem.arena_allocator(&parser_arena))
ast_module, loaded := loader.load(
input_path,
&sources,
&diagnostics,
vmem.arena_allocator(&lexer_arena),
vmem.arena_allocator(&parser_arena),
)
if !loaded {
fmt.eprintln("failed to load root package directory:", input_path)
return 2
}
vmem.arena_free_all(&lexer_arena)
hir_module := checker.check(&ast_module, &diagnostics, vmem.arena_allocator(&checker_arena))
vmem.arena_free_all(&parser_arena)
+39 -4
View File
@@ -15,6 +15,7 @@ keyword_kind :: proc(text: string) -> token.Kind {
switch text {
case "c": return .Keyword_C
case "func": return .Keyword_Func
case "import": return .Keyword_Import
case "return": return .Keyword_Return
case "void": return .Keyword_Void
case "int": return .Keyword_Int
@@ -36,7 +37,7 @@ append_token :: proc(
) {
append(&stream.items, token.Token{
kind=kind,
span=source.Span{start=start, end=end},
span=source.Span{file=source_file.id, start=start, end=end},
text=source_file.text[start:end],
diagnostic=diagnostic,
})
@@ -71,7 +72,7 @@ lex :: proc(
cursor += 1
append_token(&stream, source_file, .Colon_Colon, start, cursor)
} else {
id := source.add(diagnostics, source.Span{start=start, end=cursor}, "expected a second ':'")
id := source.add(diagnostics, source.Span{file=source_file.id, start=start, end=cursor}, "expected a second ':'")
append_token(&stream, source_file, .Invalid, start, cursor, id)
}
case '=':
@@ -80,6 +81,9 @@ lex :: proc(
case '+':
append_token(&stream, source_file, .Plus, cursor, cursor+1)
cursor += 1
case '.':
append_token(&stream, source_file, .Dot, cursor, cursor+1)
cursor += 1
case '(':
append_token(&stream, source_file, .Left_Paren, cursor, cursor+1)
cursor += 1
@@ -95,10 +99,41 @@ lex :: proc(
case ',':
append_token(&stream, source_file, .Comma, cursor, cursor+1)
cursor += 1
case '"':
start := cursor
cursor += 1
valid := true
for cursor < len(bytes) && bytes[cursor] != '"' && bytes[cursor] != '\n' {
if bytes[cursor] == '\\' {
cursor += 1
if cursor >= len(bytes) || (bytes[cursor] != '\\' && bytes[cursor] != '"') {
source.add(
diagnostics,
source.Span{file=source_file.id, start=max(cursor-1, start), end=min(cursor+1, len(bytes))},
"import strings only support '\\\\' and '\\\"' escapes",
)
valid = false
}
}
if cursor < len(bytes) && bytes[cursor] != '\n' {
cursor += 1
}
}
if cursor < len(bytes) && bytes[cursor] == '"' {
cursor += 1
append_token(&stream, source_file, .String if valid else .Invalid, start, cursor)
} else {
id := source.add(
diagnostics,
source.Span{file=source_file.id, start=start, end=cursor},
"unterminated import string",
)
append_token(&stream, source_file, .Invalid, start, cursor, id)
}
case ';':
id := source.add(
diagnostics,
source.Span{start=cursor, end=cursor+1},
source.Span{file=source_file.id, start=cursor, end=cursor+1},
"semicolons are invalid; terminate statements with a newline",
)
append_token(&stream, source_file, .Invalid, cursor, cursor+1, id)
@@ -120,7 +155,7 @@ lex :: proc(
} else {
id := source.addf(
diagnostics,
source.Span{start=cursor, end=cursor+1},
source.Span{file=source_file.id, start=cursor, end=cursor+1},
"invalid source byte 0x%02x",
value,
)
+6 -2
View File
@@ -65,10 +65,14 @@ diagnostic_message :: proc(emitter: ^Emitter, diagnostic: int, span: source.Span
delete(message, emitter.allocator)
return id
}
line, column := source.line_and_column(emitter.diagnostics.source, span.start)
source_file := source.source_for_span(emitter.diagnostics, span)
if source_file == nil {
return register_message(emitter, fallback)
}
line, column := source.line_and_column(source_file, span.start)
message := fmt.aprintf(
"%s:%d:%d: runtime trap: %s",
emitter.diagnostics.source.path,
source_file.path,
line,
column,
fallback,
+264
View File
@@ -0,0 +1,264 @@
package loader
import "../ast"
import "../lexer"
import "../parser"
import "../source"
import "core:mem"
import "core:os"
import "core:path/filepath"
import "core:slice"
import "core:strings"
State :: struct {
module: ^ast.Module,
sources: ^source.Store,
diagnostics: ^source.Diagnostics,
token_allocator: mem.Allocator,
allocator: mem.Allocator,
root_failed: bool,
}
is_identifier :: proc(value: string) -> bool {
if len(value) == 0 {
return false
}
is_start := proc(value: byte) -> bool {
return value == '_' || value >= 'a' && value <= 'z' || value >= 'A' && value <= 'Z'
}
if !is_start(value[0]) {
return false
}
for byte_value in transmute([]byte)value[1:] {
if !is_start(byte_value) && !(byte_value >= '0' && byte_value <= '9') {
return false
}
}
return true
}
find_package :: proc(state: ^State, path: string) -> int {
for pkg, id in state.module.packages {
if pkg.path == path {
return id
}
}
return -1
}
add_placeholder :: proc(state: ^State, path: string) -> int {
if existing := find_package(state, path); existing >= 0 {
return existing
}
id := len(state.module.packages)
append(&state.module.packages, ast.Package{
path=strings.clone(path, state.allocator),
name=strings.clone(filepath.base(path), state.allocator),
available=false,
})
return id
}
read_package_files :: proc(state: ^State, path: string) -> ([]os.File_Info, bool) {
handle, open_error := os.open(path, os.O_RDONLY)
if open_error != nil {
return nil, false
}
defer os.close(handle)
entries, read_error := os.read_dir(handle, -1, state.allocator)
if read_error != nil {
return nil, false
}
slice.sort_by(entries, proc(a, b: os.File_Info) -> bool {
return a.name < b.name
})
files: [dynamic]os.File_Info
files.allocator = state.allocator
for entry in entries {
if !entry.is_dir && filepath.ext(entry.name) == ".bro" {
append(&files, entry)
} else {
os.file_info_delete(entry, state.allocator)
}
}
delete(entries, state.allocator)
return files[:], true
}
resolve_import_path :: proc(state: ^State, importing_path, import_path: string) -> (string, bool) {
if filepath.is_abs(import_path) {
return "", false
}
joined, join_error := filepath.join({importing_path, import_path}, state.allocator)
if join_error != nil {
return "", false
}
canonical, ok := filepath.abs(joined, state.allocator)
if ok {
delete(joined, state.allocator)
return canonical, true
}
return joined, false
}
load_package :: proc(state: ^State, path: string, import_span: source.Span, is_root := false) -> int {
canonical, ok := filepath.abs(path, state.allocator)
if !ok || !os.is_dir(path) {
if is_root {
state.root_failed = true
if len(canonical) > 0 {
delete(canonical, state.allocator)
}
return -1
}
placeholder := path
if len(canonical) > 0 {
placeholder = canonical
}
id := add_placeholder(state, placeholder)
source.addf(state.diagnostics, import_span, "could not import package directory '%s'", path)
if len(canonical) > 0 {
delete(canonical, state.allocator)
}
return id
}
if existing := find_package(state, canonical); existing >= 0 {
delete(canonical, state.allocator)
return existing
}
pkg_id := len(state.module.packages)
append(&state.module.packages, ast.Package{
path=canonical,
name=strings.clone(filepath.base(canonical), state.allocator),
available=true,
})
files, files_ok := read_package_files(state, canonical)
if !files_ok {
state.root_failed = true
return pkg_id
}
if len(files) == 0 {
if is_root {
state.root_failed = true
} else {
source.addf(state.diagnostics, import_span, "package '%s' contains no readable .bro files", canonical)
state.module.packages[pkg_id].available = false
}
os.file_info_slice_delete(files, state.allocator)
return pkg_id
}
for file_info in files {
bytes, read_ok := os.read_entire_file(file_info.fullpath)
if !read_ok {
state.root_failed = true
continue
}
source_id := source.add_source(state.sources, file_info.fullpath, string(bytes))
delete(bytes)
file_id := len(state.module.files)
append(&state.module.files, ast.File{source=source_id, pkg=pkg_id})
stream := lexer.lex(&state.sources.items[source_id], state.diagnostics, state.token_allocator)
parser.parse_into(&stream, state.diagnostics, state.module, pkg_id, file_id)
delete(stream.items)
}
os.file_info_slice_delete(files, state.allocator)
import_count := len(state.module.imports)
for import_id in 0..<import_count {
import_item := state.module.imports[import_id]
if import_item.pkg != pkg_id || import_item.target >= 0 {
continue
}
if filepath.is_abs(import_item.path) {
state.module.imports[import_id].diagnostic = source.add(state.diagnostics, import_item.span, "absolute import paths are invalid")
state.module.imports[import_id].valid = false
state.module.imports[import_id].target = add_placeholder(state, import_item.path)
continue
}
target_path, target_ok := resolve_import_path(state, canonical, import_item.path)
target := load_package(state, target_path, import_item.span)
state.module.imports[import_id].target = target
if !target_ok || target < 0 || !state.module.packages[target].available {
state.module.imports[import_id].valid = false
}
delete(target_path, state.allocator)
}
return pkg_id
}
declaration_conflicts :: proc(module: ^ast.Module, pkg: int, name: string) -> bool {
for function in module.functions {
if function.pkg == pkg && function.name == name {
return true
}
}
for global in module.globals {
if global.pkg == pkg && global.name == name {
return true
}
}
return false
}
validate_imports :: proc(state: ^State) {
for import_item, import_id in state.module.imports {
if import_item.alias == "" && import_item.target >= 0 {
state.module.imports[import_id].alias = state.module.packages[import_item.target].name
}
alias := state.module.imports[import_id].alias
if !is_identifier(alias) {
state.module.imports[import_id].diagnostic = source.add(
state.diagnostics,
import_item.span,
"import requires an explicit valid identifier alias",
)
state.module.imports[import_id].valid = false
}
if declaration_conflicts(state.module, import_item.pkg, alias) {
state.module.imports[import_id].diagnostic = source.addf(
state.diagnostics,
import_item.span,
"import alias '%s' conflicts with a package declaration",
alias,
)
state.module.imports[import_id].valid = false
}
for previous in state.module.imports[:import_id] {
if previous.file == import_item.file && previous.alias == alias {
state.module.imports[import_id].diagnostic = source.addf(
state.diagnostics,
import_item.span,
"duplicate import alias '%s' in the same file",
alias,
)
state.module.imports[import_id].valid = false
break
}
}
}
}
load :: proc(
root_path: string,
sources: ^source.Store,
diagnostics: ^source.Diagnostics,
token_allocator := context.allocator,
allocator := context.allocator,
) -> (ast.Module, bool) {
module := ast.init_module(allocator)
state := State{
module=&module,
sources=sources,
diagnostics=diagnostics,
token_allocator=token_allocator,
allocator=allocator,
}
root := load_package(&state, root_path, source.Span{}, true)
if root != 0 && root >= 0 {
state.root_failed = true
}
validate_imports(&state)
return module, !state.root_failed
}
+126 -15
View File
@@ -3,16 +3,24 @@ package parser
import "../ast"
import "../source"
import "../token"
import "core:fmt"
import "core:strconv"
import "core:strings"
Parser :: struct {
tokens: ^token.Stream,
diagnostics: ^source.Diagnostics,
module: ast.Module,
pkg: int,
file: int,
cursor: int,
delimiter_depth: int,
}
span_from :: proc(first, last: source.Span) -> source.Span {
return source.Span{file=first.file, start=first.start, end=last.end}
}
current :: proc(parser: ^Parser) -> token.Token {
return parser.tokens.items[min(parser.cursor, len(parser.tokens.items)-1)]
}
@@ -93,7 +101,7 @@ parse_type :: proc(parser: ^Parser) -> ast.Type_Syntax {
return .Invalid
}
parse_call :: proc(parser: ^Parser, name: token.Token) -> int {
parse_call :: proc(parser: ^Parser, qualifier: string, first, name: token.Token) -> int {
left_paren := advance(parser)
parser.delimiter_depth += 1
defer parser.delimiter_depth -= 1
@@ -116,7 +124,8 @@ parse_call :: proc(parser: ^Parser, name: token.Token) -> int {
}
return add_expr(parser, ast.Expr{
kind=.Call,
span=source.Span{start=name.span.start, end=right_paren.span.end},
span=source.Span{file=name.span.file, start=first.span.start, end=right_paren.span.end},
qualifier=qualifier,
text=name.text,
args=args[:],
left=ast.INVALID_ID,
@@ -143,14 +152,24 @@ parse_primary :: proc(parser: ^Parser) -> int {
diagnostic=-1,
})
case .Identifier:
advance(parser)
first := advance(parser)
name := first
qualifier := ""
if _, ok := allow(parser, .Dot); ok {
if current(parser).kind != .Identifier {
return invalid_expr(parser, current(parser).span, "expected a package member after '.'")
}
qualifier = first.text
name = advance(parser)
}
if current(parser).kind == .Left_Paren {
return parse_call(parser, tok)
return parse_call(parser, qualifier, first, name)
}
return add_expr(parser, ast.Expr{
kind=.Name,
span=tok.span,
text=tok.text,
span=span_from(first.span, name.span),
qualifier=qualifier,
text=name.text,
left=ast.INVALID_ID,
right=ast.INVALID_ID,
diagnostic=-1,
@@ -198,7 +217,7 @@ parse_expression :: proc(parser: ^Parser) -> int {
right_expr := parser.module.exprs[right]
left = add_expr(parser, ast.Expr{
kind=.Add,
span=source.Span{start=left_expr.span.start, end=right_expr.span.end},
span=span_from(left_expr.span, right_expr.span),
left=left,
right=right,
diagnostic=-1,
@@ -210,12 +229,12 @@ parse_expression :: proc(parser: ^Parser) -> int {
return left
}
finish_statement :: proc(parser: ^Parser) -> int {
finish_statement :: proc(parser: ^Parser, allow_closing_brace := false) -> int {
if current(parser).kind == .Newline {
skip_newlines(parser)
return -1
}
if current(parser).kind == .Eof {
if current(parser).kind == .Eof || allow_closing_brace && current(parser).kind == .Right_Brace {
return -1
}
diagnostic := source.add(
@@ -240,7 +259,7 @@ parse_return :: proc(parser: ^Parser) -> int {
id := len(parser.module.statements)
append(&parser.module.statements, ast.Stmt{
kind=.Return,
span=source.Span{start=start.span.start, end=end.span.end},
span=span_from(start.span, end.span),
name="_",
expr=ast.INVALID_ID,
diagnostic=-1,
@@ -251,7 +270,7 @@ parse_return :: proc(parser: ^Parser) -> int {
id := len(parser.module.statements)
append(&parser.module.statements, ast.Stmt{
kind=.Return,
span=source.Span{start=start.span.start, end=parser.module.exprs[expr].span.end},
span=span_from(start.span, parser.module.exprs[expr].span),
expr=expr,
diagnostic=-1,
})
@@ -286,7 +305,7 @@ parse_statement :: proc(parser: ^Parser) -> int {
id := len(parser.module.statements)
append(&parser.module.statements, ast.Stmt{
kind=kind,
span=source.Span{start=name.span.start, end=parser.module.exprs[expr].span.end},
span=span_from(name.span, parser.module.exprs[expr].span),
name=name.text,
type=type_syntax,
immutable=immutable,
@@ -367,7 +386,7 @@ parse_function :: proc(parser: ^Parser, name: token.Token, c_abi: bool) {
skip_newlines(parser)
for current(parser).kind != .Right_Brace && current(parser).kind != .Eof {
append(&body, parse_statement(parser))
if diagnostic := finish_statement(parser); diagnostic >= 0 {
if diagnostic := finish_statement(parser, true); diagnostic >= 0 {
statement_id := len(parser.module.statements)
append(&parser.module.statements, ast.Stmt{
kind=.Invalid,
@@ -384,8 +403,10 @@ parse_function :: proc(parser: ^Parser, name: token.Token, c_abi: bool) {
end = func_token
}
append(&parser.module.functions, ast.Function{
span=source.Span{start=name.span.start, end=end.span.end},
span=span_from(name.span, end.span),
name=name.text,
pkg=parser.pkg,
file=parser.file,
c_abi=c_abi,
params=params,
result=result,
@@ -394,7 +415,63 @@ parse_function :: proc(parser: ^Parser, name: token.Token, c_abi: bool) {
})
}
decode_import_path :: proc(parser: ^Parser, tok: token.Token) -> string {
if len(tok.text) < 2 {
return fmt.aprintf("", allocator=parser.module.allocator)
}
builder := strings.builder_make(parser.module.allocator)
defer strings.builder_destroy(&builder)
for index := 1; index < len(tok.text)-1; index += 1 {
value := tok.text[index]
if value == '\\' && index+1 < len(tok.text)-1 {
index += 1
value = tok.text[index]
}
strings.write_byte(&builder, value)
}
return fmt.aprintf("%s", strings.to_string(builder), allocator=parser.module.allocator)
}
parse_import :: proc(parser: ^Parser, alias: token.Token, start: token.Token) {
skip_newlines(parser)
path_token := current(parser)
if path_token.kind != .String {
source.add(parser.diagnostics, path_token.span, "expected an import path string")
if path_token.kind != .Newline && path_token.kind != .Eof {
advance(parser)
}
append(&parser.module.imports, ast.Import{
span=start.span,
alias=alias.text,
pkg=parser.pkg,
file=parser.file,
target=-1,
valid=false,
diagnostic=-1,
})
_ = finish_statement(parser)
return
}
advance(parser)
append(&parser.module.imports, ast.Import{
span=span_from(start.span, path_token.span),
alias=alias.text,
path=decode_import_path(parser, path_token),
pkg=parser.pkg,
file=parser.file,
target=-1,
valid=true,
diagnostic=-1,
})
_ = finish_statement(parser)
}
parse_top_level :: proc(parser: ^Parser) {
if current(parser).kind == .Keyword_Import {
start := advance(parser)
parse_import(parser, token.Token{}, start)
return
}
if current(parser).kind != .Identifier {
source.add(parser.diagnostics, current(parser).span, "expected a top-level declaration")
for current(parser).kind != .Newline && current(parser).kind != .Eof {
@@ -404,6 +481,17 @@ parse_top_level :: proc(parser: ^Parser) {
return
}
name := advance(parser)
if current(parser).kind == .Colon_Colon {
saved := parser.cursor
advance(parser)
skip_newlines(parser)
if current(parser).kind == .Keyword_Import {
start := advance(parser)
parse_import(parser, name, start)
return
}
parser.cursor = saved
}
type_syntax := ast.Type_Syntax.Invalid
if is_type_token(current(parser).kind) {
type_syntax = parse_type(parser)
@@ -430,8 +518,10 @@ parse_top_level :: proc(parser: ^Parser) {
expr := parse_expression(parser)
append(&parser.module.globals, ast.Global{
span=source.Span{start=name.span.start, end=parser.module.exprs[expr].span.end},
span=span_from(name.span, parser.module.exprs[expr].span),
name=name.text,
pkg=parser.pkg,
file=parser.file,
type=type_syntax,
immutable=operator.kind == .Colon_Colon,
expr=expr,
@@ -457,3 +547,24 @@ parse :: proc(
}
return parser.module
}
parse_into :: proc(
stream: ^token.Stream,
diagnostics: ^source.Diagnostics,
module: ^ast.Module,
pkg, file: int,
) {
parser := Parser{
tokens=stream,
diagnostics=diagnostics,
module=module^,
pkg=pkg,
file=file,
}
skip_newlines(&parser)
for current(&parser).kind != .Eof {
parse_top_level(&parser)
skip_newlines(&parser)
}
module^ = parser.module
}
+64 -6
View File
@@ -4,24 +4,57 @@ 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,
items: [dynamic]Diagnostic,
allocator: mem.Allocator,
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 {
@@ -32,6 +65,14 @@ init_diagnostics :: proc(source_file: ^Source, allocator := context.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)
@@ -79,15 +120,32 @@ line_and_column :: proc(source_file: ^Source, offset: int) -> (line, column: int
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) {
return fmt.aprintf("%s: compiler recovery error", diagnostics.source.path, allocator=allocator)
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]
line, column := line_and_column(diagnostics.source, diagnostic.span.start)
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",
diagnostics.source.path,
source_file.path,
line,
column,
diagnostic.message,
+3
View File
@@ -8,10 +8,12 @@ Kind :: enum {
Newline,
Identifier,
Integer,
String,
Underscore,
Colon_Colon,
Equal,
Plus,
Dot,
Left_Paren,
Right_Paren,
Left_Brace,
@@ -19,6 +21,7 @@ Kind :: enum {
Comma,
Keyword_C,
Keyword_Func,
Keyword_Import,
Keyword_Return,
Keyword_Void,
Keyword_Int,