package-type imports
This commit is contained in:
+12
-11
@@ -1,13 +1,14 @@
|
|||||||
brolang
|
/brolang
|
||||||
|
|
||||||
# examples
|
# examples
|
||||||
prototype
|
/prototype
|
||||||
main_i32
|
/main_i32
|
||||||
cycle_used
|
/cycle_used
|
||||||
cycle_unused
|
/cycle_unused
|
||||||
invalid_transitive_used_global
|
/invalid_transitive_used_global
|
||||||
invalid_transitive_unused_global
|
/invalid_transitive_unused_global
|
||||||
runtime_global
|
/runtime_global
|
||||||
missing_main
|
/missing_main
|
||||||
mutable_local
|
/mutable_local
|
||||||
overflow
|
/overflow
|
||||||
|
/deep
|
||||||
|
|||||||
@@ -4,26 +4,42 @@ Prototype error-tolerant Brolang compiler written in Odin.
|
|||||||
|
|
||||||
```sh
|
```sh
|
||||||
odin build . -out:brolang
|
odin build . -out:brolang
|
||||||
./brolang examples/prototype.bro -o /tmp/prototype
|
./brolang examples/programs/prototype -o /tmp/prototype
|
||||||
/tmp/prototype
|
/tmp/prototype
|
||||||
```
|
```
|
||||||
|
|
||||||
Compilation phases are isolated under `compiler/`:
|
Compilation phases are isolated under `compiler/`:
|
||||||
|
|
||||||
```text
|
```text
|
||||||
source -> lexer -> parser/AST -> checker/HIR -> lower/IR -> opt -> LLVM -> zig cc
|
package loader -> per-file lexer/parser/AST -> checker/HIR -> lower/IR -> opt -> LLVM -> zig cc
|
||||||
```
|
```
|
||||||
|
|
||||||
Source diagnostics do not block executable generation. When recovery is
|
Source diagnostics do not block executable generation. When recovery is
|
||||||
possible, invalid code lowers to runtime diagnostic traps and the compiler
|
possible, invalid code lowers to runtime diagnostic traps and the compiler
|
||||||
returns status `1`. Infrastructure or backend failures return status `2`.
|
returns status `1`. Infrastructure or backend failures return status `2`.
|
||||||
|
Top-level function bodies are semantically checked lazily when a concrete
|
||||||
|
specialization is demanded.
|
||||||
|
|
||||||
|
Every immediate `.bro` file in the input directory belongs to the root
|
||||||
|
package. Imports are relative directory paths and are local to the file that
|
||||||
|
declares them:
|
||||||
|
|
||||||
|
```bro
|
||||||
|
import "../math"
|
||||||
|
other_math :: import "../math"
|
||||||
|
|
||||||
|
value :: math.sum(other_math.value, 1)
|
||||||
|
```
|
||||||
|
|
||||||
Current prototype features:
|
Current prototype features:
|
||||||
|
|
||||||
- Newline-terminated, multiline statements and `#` comments
|
- Newline-terminated, multiline statements; `}` may terminate a block's final statement
|
||||||
|
- `#` comments
|
||||||
- Immutable `::` bindings, mutable function-local `=` bindings, and `_` sinks
|
- Immutable `::` bindings, mutable function-local `=` bindings, and `_` sinks
|
||||||
- `i8`, `i16`, `i32`, `i64`, and loose integer-constrained `int`
|
- `i8`, `i16`, `i32`, `i64`, and loose integer-constrained `int`
|
||||||
- Contextual integer constants and compile-time folding of literal addition trees
|
- Contextual integer constants and compile-time folding of literal addition trees
|
||||||
|
- Directory packages with merged declarations and file-local relative imports
|
||||||
|
- Qualified imported globals and functions with package-aware symbol mangling
|
||||||
- Demand-monomorphized Brolang and C-ABI functions
|
- Demand-monomorphized Brolang and C-ABI functions
|
||||||
- Checked signed addition
|
- Checked signed addition
|
||||||
- Static, eager runtime, and deferred problematic globals
|
- Static, eager runtime, and deferred problematic globals
|
||||||
|
|||||||
@@ -1,22 +1,44 @@
|
|||||||
# "quick" fixes
|
# "quick"/"easy" fixes
|
||||||
|
|
||||||
- for global initialization cycles, report also starting and ending lines
|
- for global initialization cycles, report also starting and ending lines
|
||||||
|
|
||||||
|
- intern strings across all phases of the compiler and reference strings by their hash/id
|
||||||
|
- makes string comparison and equality checks faster and takes up less memory
|
||||||
|
|
||||||
# milestones
|
# milestones
|
||||||
|
|
||||||
1. import system:
|
1. get c interop working:
|
||||||
- directory level package style like odin (and go?)
|
|
||||||
- globals in different files under same package are automatically merged into same namespace
|
|
||||||
- globals defined in different packages must be imported via their package
|
|
||||||
```
|
|
||||||
import "some_package" # automatically imports all public globals under `some_package`
|
|
||||||
other_name :: import "some_package" # imports all public globals under `some_package` into `other_name` namespace
|
|
||||||
|
|
||||||
some_package.hello # accesses the `hello` global from `some_package`
|
|
||||||
other_name.hello # accesses the `hello` global from `some_package` via `other_name` namespace
|
|
||||||
```
|
|
||||||
|
|
||||||
2. get c interop working:
|
|
||||||
- link with c / compile c code into binary alongside brolang code
|
- link with c / compile c code into binary alongside brolang code
|
||||||
- create bindings from c headers
|
- create bindings from c headers
|
||||||
|
- figure out how to represent variadic arguments
|
||||||
|
- proposal (from an older document):
|
||||||
|
```
|
||||||
|
# functions with variadic arguments
|
||||||
|
printf :: c func(fmt []u8, args ...) int # `...` is essentially an anonomous tuple type used in function arguments. `...` collects remaining arguments in a type inferred tuple (infer from anchor)
|
||||||
|
```
|
||||||
|
- notes on structs and tuples from same older document:
|
||||||
|
```
|
||||||
|
# structs carry only data, no methods, no behavior
|
||||||
|
Stuff :: struct {
|
||||||
|
first u32
|
||||||
|
second float
|
||||||
|
third [5]i32
|
||||||
|
}
|
||||||
|
|
||||||
|
# tuples are just structs without field names (accessed via index: ``some_tuple.0``, ``some_tuple.1``, etc.)
|
||||||
|
# anonomous tuple type inferred from literal and type anchors?
|
||||||
|
some_tuple :: { 4, "hello" }
|
||||||
|
```
|
||||||
- find out how this should co-exist with the import system
|
- find out how this should co-exist with the import system
|
||||||
|
- maybe c header files should just be treated as individual packages
|
||||||
|
- that is also typically how they're used in c projects as they define an interface to a module (or package)
|
||||||
|
- that means that bindings should be automatically generated by the compiler when the user imports a c header file:
|
||||||
|
- `import "relative/path/to/some_header.h"` - wraps the c header file in a brolang package called `some_header`
|
||||||
|
- `other_header :: import "relative/path/to/some_header.h"` - wraps the c header file in a brolang package called `some_header` into `other_header` namespace
|
||||||
|
- implementation of the functions declared by the generated bindings is provided by the c implementation and requires the c implementation to be linked with the brolang binary
|
||||||
|
- this requires the ability to declare a "bare" function (as an interface) that is linked to the c implementation
|
||||||
|
```
|
||||||
|
# declare the bare extern function for c interop (notice only the signature is provided, not the implementation)
|
||||||
|
# this should only be allowed for extern functions, i.e. `c func`
|
||||||
|
extern_c_sum :: c func(a, b int) int
|
||||||
|
```
|
||||||
|
|||||||
@@ -26,6 +26,7 @@ Expr_Kind :: enum {
|
|||||||
Expr :: struct {
|
Expr :: struct {
|
||||||
kind: Expr_Kind,
|
kind: Expr_Kind,
|
||||||
span: source.Span,
|
span: source.Span,
|
||||||
|
qualifier: string,
|
||||||
text: string,
|
text: string,
|
||||||
integer: i64,
|
integer: i64,
|
||||||
left: int,
|
left: int,
|
||||||
@@ -61,6 +62,8 @@ Stmt :: struct {
|
|||||||
Function :: struct {
|
Function :: struct {
|
||||||
span: source.Span,
|
span: source.Span,
|
||||||
name: string,
|
name: string,
|
||||||
|
pkg: int,
|
||||||
|
file: int,
|
||||||
c_abi: bool,
|
c_abi: bool,
|
||||||
params: []Param,
|
params: []Param,
|
||||||
result: Type_Syntax,
|
result: Type_Syntax,
|
||||||
@@ -71,17 +74,45 @@ Function :: struct {
|
|||||||
Global :: struct {
|
Global :: struct {
|
||||||
span: source.Span,
|
span: source.Span,
|
||||||
name: string,
|
name: string,
|
||||||
|
pkg: int,
|
||||||
|
file: int,
|
||||||
type: Type_Syntax,
|
type: Type_Syntax,
|
||||||
immutable: bool,
|
immutable: bool,
|
||||||
expr: int,
|
expr: int,
|
||||||
diagnostic: 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 {
|
Module :: struct {
|
||||||
exprs: [dynamic]Expr,
|
exprs: [dynamic]Expr,
|
||||||
statements: [dynamic]Stmt,
|
statements: [dynamic]Stmt,
|
||||||
functions: [dynamic]Function,
|
functions: [dynamic]Function,
|
||||||
globals: [dynamic]Global,
|
globals: [dynamic]Global,
|
||||||
|
imports: [dynamic]Import,
|
||||||
|
files: [dynamic]File,
|
||||||
|
packages: [dynamic]Package,
|
||||||
allocator: mem.Allocator,
|
allocator: mem.Allocator,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -92,6 +123,9 @@ init_module :: proc(allocator := context.allocator) -> Module {
|
|||||||
module.statements.allocator = allocator
|
module.statements.allocator = allocator
|
||||||
module.functions.allocator = allocator
|
module.functions.allocator = allocator
|
||||||
module.globals.allocator = allocator
|
module.globals.allocator = allocator
|
||||||
|
module.imports.allocator = allocator
|
||||||
|
module.files.allocator = allocator
|
||||||
|
module.packages.allocator = allocator
|
||||||
return module
|
return module
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -103,8 +137,18 @@ destroy_module :: proc(module: ^Module) {
|
|||||||
delete(function.params, module.allocator)
|
delete(function.params, module.allocator)
|
||||||
delete(function.body, 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.exprs)
|
||||||
delete(module.statements)
|
delete(module.statements)
|
||||||
delete(module.functions)
|
delete(module.functions)
|
||||||
delete(module.globals)
|
delete(module.globals)
|
||||||
|
delete(module.imports)
|
||||||
|
delete(module.files)
|
||||||
|
delete(module.packages)
|
||||||
}
|
}
|
||||||
|
|||||||
+270
-144
@@ -7,6 +7,7 @@ import "../types"
|
|||||||
import "base:intrinsics"
|
import "base:intrinsics"
|
||||||
import "core:fmt"
|
import "core:fmt"
|
||||||
import "core:mem"
|
import "core:mem"
|
||||||
|
import "core:slice"
|
||||||
import "core:strings"
|
import "core:strings"
|
||||||
|
|
||||||
Spec :: struct {
|
Spec :: struct {
|
||||||
@@ -40,11 +41,20 @@ Constant :: struct {
|
|||||||
value: i128,
|
value: i128,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Symbol_Index_Entry :: struct {
|
||||||
|
scope: int,
|
||||||
|
name: string,
|
||||||
|
id: int,
|
||||||
|
}
|
||||||
|
|
||||||
Checker :: struct {
|
Checker :: struct {
|
||||||
ast_module: ^ast.Module,
|
ast_module: ^ast.Module,
|
||||||
diagnostics: ^source.Diagnostics,
|
diagnostics: ^source.Diagnostics,
|
||||||
module: hir.Module,
|
module: hir.Module,
|
||||||
specs: [dynamic]Spec,
|
specs: [dynamic]Spec,
|
||||||
|
function_index: []Symbol_Index_Entry,
|
||||||
|
global_index: []Symbol_Index_Entry,
|
||||||
|
import_index: []Symbol_Index_Entry,
|
||||||
global_types: []types.Type,
|
global_types: []types.Type,
|
||||||
constants: []Constant,
|
constants: []Constant,
|
||||||
allocator: mem.Allocator,
|
allocator: mem.Allocator,
|
||||||
@@ -116,22 +126,123 @@ type_from_syntax :: proc(value: ast.Type_Syntax) -> types.Type {
|
|||||||
return types.INVALID
|
return types.INVALID
|
||||||
}
|
}
|
||||||
|
|
||||||
find_template :: proc(checker: ^Checker, name: string) -> int {
|
symbol_index_less :: proc(left, right: Symbol_Index_Entry) -> bool {
|
||||||
for function, index in checker.ast_module.functions {
|
if left.scope != right.scope {
|
||||||
if function.name == name {
|
return left.scope < right.scope
|
||||||
return index
|
|
||||||
}
|
}
|
||||||
|
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
|
return -1
|
||||||
}
|
}
|
||||||
|
|
||||||
find_global :: proc(checker: ^Checker, name: string) -> int {
|
build_symbol_indexes :: proc(checker: ^Checker) {
|
||||||
for global, index in checker.ast_module.globals {
|
checker.function_index = make([]Symbol_Index_Entry, len(checker.ast_module.functions), checker.allocator)
|
||||||
if global.name == name {
|
for function, id in checker.ast_module.functions {
|
||||||
return index
|
checker.function_index[id] = Symbol_Index_Entry{scope=function.pkg, name=function.name, id=id}
|
||||||
}
|
}
|
||||||
|
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}
|
||||||
}
|
}
|
||||||
return -1
|
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 {
|
contains_name :: proc(names: []string, name: string) -> bool {
|
||||||
@@ -143,41 +254,31 @@ contains_name :: proc(names: []string, name: string) -> bool {
|
|||||||
return false
|
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) {
|
if expr_id < 0 || expr_id >= len(checker.ast_module.exprs) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
expr := checker.ast_module.exprs[expr_id]
|
expr := checker.ast_module.exprs[expr_id]
|
||||||
switch expr.kind {
|
switch expr.kind {
|
||||||
case .Name:
|
case .Name:
|
||||||
if !contains_name(locals, expr.text) && find_global(checker, expr.text) < 0 {
|
if expr.qualifier != "" {
|
||||||
source.addf(checker.diagnostics, expr.span, "unresolved name '%s'", expr.text)
|
_ = find_import(checker, file, expr.qualifier, true)
|
||||||
}
|
}
|
||||||
case .Call:
|
case .Call:
|
||||||
if find_template(checker, expr.text) < 0 {
|
if expr.qualifier != "" {
|
||||||
source.addf(checker.diagnostics, expr.span, "unresolved function '%s'", expr.text)
|
_ = find_import(checker, file, expr.qualifier, true)
|
||||||
}
|
}
|
||||||
for arg in expr.args {
|
for arg in expr.args {
|
||||||
validate_expr_names(checker, arg, locals)
|
mark_expr_imports_used(checker, arg, file)
|
||||||
}
|
}
|
||||||
case .Add:
|
case .Add:
|
||||||
validate_expr_names(checker, expr.left, locals)
|
mark_expr_imports_used(checker, expr.left, file)
|
||||||
validate_expr_names(checker, expr.right, locals)
|
mark_expr_imports_used(checker, expr.right, file)
|
||||||
case .Invalid, .Integer:
|
case .Invalid, .Integer:
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
validate_templates :: proc(checker: ^Checker) {
|
validate_declarations :: 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)
|
|
||||||
}
|
|
||||||
for function in checker.ast_module.functions {
|
for function in checker.ast_module.functions {
|
||||||
locals: [dynamic]string
|
locals: [dynamic]string
|
||||||
locals.allocator = checker.allocator
|
locals.allocator = checker.allocator
|
||||||
@@ -202,36 +303,8 @@ validate_templates :: proc(checker: ^Checker) {
|
|||||||
for statement_id in function.body {
|
for statement_id in function.body {
|
||||||
statement := checker.ast_module.statements[statement_id]
|
statement := checker.ast_module.statements[statement_id]
|
||||||
switch statement.kind {
|
switch statement.kind {
|
||||||
case .Declaration:
|
case .Declaration, .Assignment, .Return, .Expression:
|
||||||
validate_expr_names(checker, statement.expr, locals[:])
|
mark_expr_imports_used(checker, statement.expr, function.file)
|
||||||
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 .Invalid:
|
case .Invalid:
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -268,6 +341,19 @@ specialized_param_type :: proc(syntax: ast.Type_Syntax, actual: types.Type) -> t
|
|||||||
return declared
|
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 {
|
ensure_spec :: proc(checker: ^Checker, template: int, actual_args: []types.Type) -> int {
|
||||||
function := checker.ast_module.functions[template]
|
function := checker.ast_module.functions[template]
|
||||||
signature: [dynamic]types.Type
|
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)
|
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
|
result = types.I32
|
||||||
}
|
}
|
||||||
index := len(checker.specs)
|
index := len(checker.specs)
|
||||||
@@ -297,7 +383,7 @@ ensure_spec :: proc(checker: ^Checker, template: int, actual_args: []types.Type)
|
|||||||
return index
|
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) {
|
if expr_id < 0 || expr_id >= len(checker.ast_module.exprs) {
|
||||||
return types.INVALID
|
return types.INVALID
|
||||||
}
|
}
|
||||||
@@ -315,27 +401,49 @@ infer_expr :: proc(checker: ^Checker, expr_id: int, locals: []Infer_Local) -> ty
|
|||||||
case .Integer:
|
case .Integer:
|
||||||
return types.smallest_signed_for_literal(expr.integer)
|
return types.smallest_signed_for_literal(expr.integer)
|
||||||
case .Name:
|
case .Name:
|
||||||
|
if expr.qualifier == "" {
|
||||||
local_type := find_infer_local(locals, expr.text)
|
local_type := find_infer_local(locals, expr.text)
|
||||||
if types.is_valid(local_type) {
|
if types.is_valid(local_type) {
|
||||||
return 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 {
|
if global >= 0 {
|
||||||
return checker.global_types[global]
|
return checker.global_types[global]
|
||||||
}
|
}
|
||||||
return types.INVALID
|
return types.INVALID
|
||||||
case .Add:
|
case .Add:
|
||||||
left := infer_expr(checker, expr.left, locals)
|
left := infer_expr(checker, expr.left, locals, pkg, file)
|
||||||
right := infer_expr(checker, expr.right, locals)
|
right := infer_expr(checker, expr.right, locals, pkg, file)
|
||||||
return types.widest(left, right)
|
return types.widest(left, right)
|
||||||
case .Call:
|
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 {
|
if template < 0 {
|
||||||
return types.INVALID
|
return types.INVALID
|
||||||
}
|
}
|
||||||
args := make([]types.Type, len(expr.args), checker.allocator)
|
args := make([]types.Type, len(expr.args), checker.allocator)
|
||||||
for arg, index in expr.args {
|
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)
|
spec := ensure_spec(checker, template, args)
|
||||||
delete(args, checker.allocator)
|
delete(args, checker.allocator)
|
||||||
@@ -348,7 +456,7 @@ infer_spec_result :: proc(checker: ^Checker, spec_id: int) -> types.Type {
|
|||||||
spec := checker.specs[spec_id]
|
spec := checker.specs[spec_id]
|
||||||
function := checker.ast_module.functions[spec.template]
|
function := checker.ast_module.functions[spec.template]
|
||||||
declared := type_from_syntax(function.result)
|
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
|
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]
|
statement := checker.ast_module.statements[statement_id]
|
||||||
#partial switch statement.kind {
|
#partial switch statement.kind {
|
||||||
case .Declaration:
|
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)
|
declared_local := type_from_syntax(statement.type)
|
||||||
if declared_local.kind == .Concrete {
|
if declared_local.kind == .Concrete {
|
||||||
value_type = declared_local
|
value_type = declared_local
|
||||||
}
|
}
|
||||||
append(&locals, Infer_Local{name = statement.name, type = value_type})
|
append(&locals, Infer_Local{name = statement.name, type = value_type})
|
||||||
case .Assignment, .Expression:
|
case .Assignment, .Expression:
|
||||||
_ = infer_expr(checker, statement.expr, locals[:])
|
_ = infer_expr(checker, statement.expr, locals[:], function.pkg, function.file)
|
||||||
case .Return:
|
case .Return:
|
||||||
if statement.expr >= 0 {
|
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) {
|
if !types.is_valid(result) {
|
||||||
result = returned
|
result = returned
|
||||||
} else {
|
} else {
|
||||||
@@ -393,6 +501,22 @@ infer_spec_result :: proc(checker: ^Checker, spec_id: int) -> types.Type {
|
|||||||
return declared
|
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) {
|
infer_all :: proc(checker: ^Checker) {
|
||||||
for global, index in checker.ast_module.globals {
|
for global, index in checker.ast_module.globals {
|
||||||
declared := type_from_syntax(global.type)
|
declared := type_from_syntax(global.type)
|
||||||
@@ -400,40 +524,25 @@ infer_all :: proc(checker: ^Checker) {
|
|||||||
checker.global_types[index] = declared
|
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 {
|
if main_template >= 0 {
|
||||||
ensure_spec(checker, main_template, nil)
|
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
|
changed := false
|
||||||
spec_count := len(checker.specs)
|
spec_count := len(checker.specs)
|
||||||
for spec_id in 0 ..< spec_count {
|
for global, index in checker.ast_module.globals {
|
||||||
inferred := infer_spec_result(checker, spec_id)
|
if type_from_syntax(global.type).kind == .Concrete {
|
||||||
if types.is_valid(inferred) && !types.equal(checker.specs[spec_id].result, inferred) {
|
continue
|
||||||
checker.specs[spec_id].result = inferred
|
|
||||||
changed = true
|
|
||||||
}
|
}
|
||||||
|
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 {
|
if len(checker.specs) != spec_count {
|
||||||
changed = true
|
changed = true
|
||||||
@@ -442,14 +551,6 @@ infer_all :: proc(checker: ^Checker) {
|
|||||||
break
|
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 {
|
add_hir_expr :: proc(checker: ^Checker, expr: hir.Expr) -> int {
|
||||||
@@ -589,6 +690,8 @@ build_expr :: proc(
|
|||||||
global_reads: ^[dynamic]int,
|
global_reads: ^[dynamic]int,
|
||||||
calls: ^[dynamic]int,
|
calls: ^[dynamic]int,
|
||||||
expected := types.INVALID,
|
expected := types.INVALID,
|
||||||
|
pkg := 0,
|
||||||
|
file := 0,
|
||||||
) -> int {
|
) -> int {
|
||||||
if expr_id < 0 || expr_id >= len(checker.ast_module.exprs) {
|
if expr_id < 0 || expr_id >= len(checker.ast_module.exprs) {
|
||||||
id := source.add(checker.diagnostics, source.Span{}, "missing expression")
|
id := source.add(checker.diagnostics, source.Span{}, "missing expression")
|
||||||
@@ -605,6 +708,7 @@ build_expr :: proc(
|
|||||||
case .Integer:
|
case .Integer:
|
||||||
unreachable()
|
unreachable()
|
||||||
case .Name:
|
case .Name:
|
||||||
|
if expr.qualifier == "" {
|
||||||
if local, ok := find_build_local(locals, expr.text); ok {
|
if local, ok := find_build_local(locals, expr.text); ok {
|
||||||
return add_hir_expr(
|
return add_hir_expr(
|
||||||
checker,
|
checker,
|
||||||
@@ -619,7 +723,13 @@ build_expr :: proc(
|
|||||||
},
|
},
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
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 {
|
if global >= 0 {
|
||||||
add_unique(global_reads, global)
|
add_unique(global_reads, global)
|
||||||
return add_hir_expr(
|
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)
|
return invalid_hir_expr(checker, expr.span, id)
|
||||||
case .Add:
|
case .Add:
|
||||||
left := build_expr(checker, expr.left, 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)
|
right := build_expr(checker, expr.right, locals, global_reads, calls, types.INVALID, pkg, file)
|
||||||
left_type := checker.module.exprs[left].type
|
left_type := checker.module.exprs[left].type
|
||||||
right_type := checker.module.exprs[right].type
|
right_type := checker.module.exprs[right].type
|
||||||
result := types.widest(left_type, right_type)
|
result := types.widest(left_type, right_type)
|
||||||
@@ -666,14 +776,14 @@ build_expr :: proc(
|
|||||||
},
|
},
|
||||||
)
|
)
|
||||||
case .Call:
|
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 {
|
if template < 0 {
|
||||||
id := source.addf(
|
id := add_call_resolution_diagnostic(checker, expr, target_pkg)
|
||||||
checker.diagnostics,
|
|
||||||
expr.span,
|
|
||||||
"unresolved function '%s'",
|
|
||||||
expr.text,
|
|
||||||
)
|
|
||||||
return invalid_hir_expr(checker, expr.span, id)
|
return invalid_hir_expr(checker, expr.span, id)
|
||||||
}
|
}
|
||||||
if len(expr.args) != len(checker.ast_module.functions[template].params) {
|
if len(expr.args) != len(checker.ast_module.functions[template].params) {
|
||||||
@@ -701,6 +811,8 @@ build_expr :: proc(
|
|||||||
global_reads,
|
global_reads,
|
||||||
calls,
|
calls,
|
||||||
arg_expected,
|
arg_expected,
|
||||||
|
pkg,
|
||||||
|
file,
|
||||||
)
|
)
|
||||||
arg_types[index] = checker.module.exprs[built_args[index]].type
|
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 {
|
make_link_name :: proc(checker: ^Checker, spec_id: int) -> string {
|
||||||
spec := checker.specs[spec_id]
|
spec := checker.specs[spec_id]
|
||||||
function := checker.ast_module.functions[spec.template]
|
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)
|
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)
|
builder := strings.builder_make(checker.allocator)
|
||||||
defer strings.builder_destroy(&builder)
|
defer strings.builder_destroy(&builder)
|
||||||
if !function.c_abi {
|
strings.write_string(&builder, "bro_c__" if function.c_abi else "bro__")
|
||||||
strings.write_string(&builder, "bro__")
|
fmt.sbprintf(&builder, "p%d__", function.pkg)
|
||||||
}
|
|
||||||
strings.write_string(&builder, function.name)
|
strings.write_string(&builder, function.name)
|
||||||
for arg in spec.args {
|
for arg in spec.args {
|
||||||
strings.write_string(&builder, "__")
|
strings.write_string(&builder, "__")
|
||||||
@@ -783,7 +884,7 @@ build_function :: proc(checker: ^Checker, spec_id: int) {
|
|||||||
spec := checker.specs[spec_id]
|
spec := checker.specs[spec_id]
|
||||||
function := checker.ast_module.functions[spec.template]
|
function := checker.ast_module.functions[spec.template]
|
||||||
signature_diagnostic := -1
|
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
|
checker.specs[spec_id].result = types.I64
|
||||||
spec.result = types.I64
|
spec.result = types.I64
|
||||||
signature_diagnostic = source.addf(
|
signature_diagnostic = source.addf(
|
||||||
@@ -862,6 +963,8 @@ build_function :: proc(checker: ^Checker, spec_id: int) {
|
|||||||
&global_reads,
|
&global_reads,
|
||||||
&calls,
|
&calls,
|
||||||
expected,
|
expected,
|
||||||
|
function.pkg,
|
||||||
|
function.file,
|
||||||
)
|
)
|
||||||
value_type := checker.module.exprs[value].type
|
value_type := checker.module.exprs[value].type
|
||||||
if declared.kind == .Concrete {
|
if declared.kind == .Concrete {
|
||||||
@@ -929,7 +1032,7 @@ build_function :: proc(checker: ^Checker, spec_id: int) {
|
|||||||
problematic = problematic || checker.module.exprs[value].kind == .Invalid
|
problematic = problematic || checker.module.exprs[value].kind == .Invalid
|
||||||
case .Assignment:
|
case .Assignment:
|
||||||
if statement.name == "_" {
|
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 {
|
if checker.module.exprs[value].type.kind == .Void {
|
||||||
id := source.add(
|
id := source.add(
|
||||||
checker.diagnostics,
|
checker.diagnostics,
|
||||||
@@ -1013,6 +1116,8 @@ build_function :: proc(checker: ^Checker, spec_id: int) {
|
|||||||
&global_reads,
|
&global_reads,
|
||||||
&calls,
|
&calls,
|
||||||
local.type,
|
local.type,
|
||||||
|
function.pkg,
|
||||||
|
function.file,
|
||||||
)
|
)
|
||||||
value = coerce_expr(checker, value, local.type, statement.span)
|
value = coerce_expr(checker, value, local.type, statement.span)
|
||||||
append(&body, len(checker.module.statements))
|
append(&body, len(checker.module.statements))
|
||||||
@@ -1090,6 +1195,8 @@ build_function :: proc(checker: ^Checker, spec_id: int) {
|
|||||||
&global_reads,
|
&global_reads,
|
||||||
&calls,
|
&calls,
|
||||||
spec.result,
|
spec.result,
|
||||||
|
function.pkg,
|
||||||
|
function.file,
|
||||||
)
|
)
|
||||||
value = coerce_expr(checker, value, spec.result, statement.span)
|
value = coerce_expr(checker, value, spec.result, statement.span)
|
||||||
append(&body, len(checker.module.statements))
|
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
|
problematic = problematic || checker.module.exprs[value].kind == .Invalid
|
||||||
case .Expression:
|
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 {
|
if checker.module.exprs[value].type.kind != .Void {
|
||||||
id := source.add(
|
id := source.add(
|
||||||
checker.diagnostics,
|
checker.diagnostics,
|
||||||
@@ -1173,8 +1280,8 @@ build_function :: proc(checker: ^Checker, spec_id: int) {
|
|||||||
hir.Function {
|
hir.Function {
|
||||||
name = function.name,
|
name = function.name,
|
||||||
link_name = make_link_name(checker, spec_id),
|
link_name = make_link_name(checker, spec_id),
|
||||||
c_abi = function.c_abi || function.name == "main",
|
c_abi = function.c_abi || (function.pkg == 0 && function.name == "main"),
|
||||||
is_main = function.name == "main",
|
is_main = function.pkg == 0 && function.name == "main",
|
||||||
params = params[:],
|
params = params[:],
|
||||||
result = spec.result,
|
result = spec.result,
|
||||||
locals = hir_locals[:],
|
locals = hir_locals[:],
|
||||||
@@ -1221,7 +1328,7 @@ build_globals :: proc(checker: ^Checker) {
|
|||||||
if declared.kind == .Concrete {
|
if declared.kind == .Concrete {
|
||||||
expected = declared
|
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]
|
global_type := checker.global_types[global_id]
|
||||||
if declared.kind == .Concrete {
|
if declared.kind == .Concrete {
|
||||||
expr = coerce_expr(checker, expr, declared, global.span)
|
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) {
|
} else if types.is_concrete_integer(checker.module.exprs[expr].type) {
|
||||||
global_type = 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
|
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 {
|
if global.type == .Void {
|
||||||
diagnostic = source.add(
|
diagnostic = source.add(
|
||||||
checker.diagnostics,
|
checker.diagnostics,
|
||||||
@@ -1389,7 +1503,6 @@ detect_global_cycles_visit :: proc(checker: ^Checker, global_id: int, states: []
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
if states[global_id] == 1 {
|
if states[global_id] == 1 {
|
||||||
if !checker.module.globals[global_id].problematic {
|
|
||||||
id := source.addf(
|
id := source.addf(
|
||||||
checker.diagnostics,
|
checker.diagnostics,
|
||||||
checker.ast_module.globals[global_id].span,
|
checker.ast_module.globals[global_id].span,
|
||||||
@@ -1398,7 +1511,6 @@ detect_global_cycles_visit :: proc(checker: ^Checker, global_id: int, states: []
|
|||||||
)
|
)
|
||||||
checker.module.globals[global_id].diagnostic = id
|
checker.module.globals[global_id].diagnostic = id
|
||||||
checker.module.globals[global_id].problematic = true
|
checker.module.globals[global_id].problematic = true
|
||||||
}
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
states[global_id] = 1
|
states[global_id] = 1
|
||||||
@@ -1478,6 +1590,7 @@ check :: proc(
|
|||||||
allocator = allocator,
|
allocator = allocator,
|
||||||
}
|
}
|
||||||
checker.specs.allocator = allocator
|
checker.specs.allocator = allocator
|
||||||
|
build_symbol_indexes(&checker)
|
||||||
checker.global_types = make([]types.Type, len(ast_module.globals), allocator)
|
checker.global_types = make([]types.Type, len(ast_module.globals), allocator)
|
||||||
checker.constants = make([]Constant, len(ast_module.exprs), allocator)
|
checker.constants = make([]Constant, len(ast_module.exprs), allocator)
|
||||||
defer {
|
defer {
|
||||||
@@ -1485,26 +1598,34 @@ check :: proc(
|
|||||||
delete(spec.args, allocator)
|
delete(spec.args, allocator)
|
||||||
}
|
}
|
||||||
delete(checker.specs)
|
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.global_types, allocator)
|
||||||
delete(checker.constants, allocator)
|
delete(checker.constants, allocator)
|
||||||
}
|
}
|
||||||
|
|
||||||
for function, index in ast_module.functions {
|
for function, index in ast_module.functions {
|
||||||
for previous in ast_module.functions[:index] {
|
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)
|
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 global, index in ast_module.globals {
|
||||||
for previous in ast_module.globals[:index] {
|
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)
|
source.addf(diagnostics, global.span, "duplicate global '%s'", global.name)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
validate_templates(&checker)
|
validate_declarations(&checker)
|
||||||
infer_all(&checker)
|
infer_all(&checker)
|
||||||
build_globals(&checker)
|
build_globals(&checker)
|
||||||
for spec_id := 0; spec_id < len(checker.specs); spec_id += 1 {
|
for spec_id := 0; spec_id < len(checker.specs); spec_id += 1 {
|
||||||
@@ -1513,10 +1634,10 @@ check :: proc(
|
|||||||
resolve_call_targets(&checker)
|
resolve_call_targets(&checker)
|
||||||
propagate_global_reads(&checker)
|
propagate_global_reads(&checker)
|
||||||
|
|
||||||
main_template := find_template(&checker, "main")
|
main_template := find_template(&checker, "main", 0)
|
||||||
main_declarations := 0
|
main_declarations := 0
|
||||||
for function in ast_module.functions {
|
for function in ast_module.functions {
|
||||||
if function.name == "main" {
|
if function.pkg == 0 && function.name == "main" {
|
||||||
main_declarations += 1
|
main_declarations += 1
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1543,5 +1664,10 @@ check :: proc(
|
|||||||
}
|
}
|
||||||
delete(states, allocator)
|
delete(states, allocator)
|
||||||
propagate_problems(&checker)
|
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
|
return checker.module
|
||||||
}
|
}
|
||||||
|
|||||||
+16
-14
@@ -2,27 +2,20 @@ package compiler
|
|||||||
|
|
||||||
import "./backend"
|
import "./backend"
|
||||||
import "./checker"
|
import "./checker"
|
||||||
import "./lexer"
|
|
||||||
import "./llvm"
|
import "./llvm"
|
||||||
|
import "./loader"
|
||||||
import "./lower"
|
import "./lower"
|
||||||
import "./opt"
|
import "./opt"
|
||||||
import "./parser"
|
|
||||||
import "./source"
|
import "./source"
|
||||||
import "core:fmt"
|
import "core:fmt"
|
||||||
import vmem "core:mem/virtual"
|
import vmem "core:mem/virtual"
|
||||||
import "core:os"
|
import "core:os"
|
||||||
import "core:os/os2"
|
import "core:os/os2"
|
||||||
|
|
||||||
compile_file :: proc(input_path, output_path: string) -> int {
|
compile_package :: proc(input_path, output_path: string) -> int {
|
||||||
source_bytes, ok := os.read_entire_file(input_path)
|
sources := source.init_store()
|
||||||
if !ok {
|
defer source.destroy_store(&sources)
|
||||||
fmt.eprintln("failed to read input:", input_path)
|
diagnostics := source.init_store_diagnostics(&sources)
|
||||||
return 2
|
|
||||||
}
|
|
||||||
defer delete(source_bytes)
|
|
||||||
|
|
||||||
source_file := source.Source{path=input_path, text=string(source_bytes)}
|
|
||||||
diagnostics := source.init_diagnostics(&source_file)
|
|
||||||
defer source.destroy_diagnostics(&diagnostics)
|
defer source.destroy_diagnostics(&diagnostics)
|
||||||
|
|
||||||
lexer_arena: vmem.Arena
|
lexer_arena: vmem.Arena
|
||||||
@@ -50,8 +43,17 @@ compile_file :: proc(input_path, output_path: string) -> int {
|
|||||||
}
|
}
|
||||||
defer vmem.arena_destroy(&lower_arena)
|
defer vmem.arena_destroy(&lower_arena)
|
||||||
|
|
||||||
tokens := lexer.lex(&source_file, &diagnostics, vmem.arena_allocator(&lexer_arena))
|
ast_module, loaded := loader.load(
|
||||||
ast_module := parser.parse(&tokens, &diagnostics, vmem.arena_allocator(&parser_arena))
|
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)
|
vmem.arena_free_all(&lexer_arena)
|
||||||
hir_module := checker.check(&ast_module, &diagnostics, vmem.arena_allocator(&checker_arena))
|
hir_module := checker.check(&ast_module, &diagnostics, vmem.arena_allocator(&checker_arena))
|
||||||
vmem.arena_free_all(&parser_arena)
|
vmem.arena_free_all(&parser_arena)
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ keyword_kind :: proc(text: string) -> token.Kind {
|
|||||||
switch text {
|
switch text {
|
||||||
case "c": return .Keyword_C
|
case "c": return .Keyword_C
|
||||||
case "func": return .Keyword_Func
|
case "func": return .Keyword_Func
|
||||||
|
case "import": return .Keyword_Import
|
||||||
case "return": return .Keyword_Return
|
case "return": return .Keyword_Return
|
||||||
case "void": return .Keyword_Void
|
case "void": return .Keyword_Void
|
||||||
case "int": return .Keyword_Int
|
case "int": return .Keyword_Int
|
||||||
@@ -36,7 +37,7 @@ append_token :: proc(
|
|||||||
) {
|
) {
|
||||||
append(&stream.items, token.Token{
|
append(&stream.items, token.Token{
|
||||||
kind=kind,
|
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],
|
text=source_file.text[start:end],
|
||||||
diagnostic=diagnostic,
|
diagnostic=diagnostic,
|
||||||
})
|
})
|
||||||
@@ -71,7 +72,7 @@ lex :: proc(
|
|||||||
cursor += 1
|
cursor += 1
|
||||||
append_token(&stream, source_file, .Colon_Colon, start, cursor)
|
append_token(&stream, source_file, .Colon_Colon, start, cursor)
|
||||||
} else {
|
} 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)
|
append_token(&stream, source_file, .Invalid, start, cursor, id)
|
||||||
}
|
}
|
||||||
case '=':
|
case '=':
|
||||||
@@ -80,6 +81,9 @@ lex :: proc(
|
|||||||
case '+':
|
case '+':
|
||||||
append_token(&stream, source_file, .Plus, cursor, cursor+1)
|
append_token(&stream, source_file, .Plus, cursor, cursor+1)
|
||||||
cursor += 1
|
cursor += 1
|
||||||
|
case '.':
|
||||||
|
append_token(&stream, source_file, .Dot, cursor, cursor+1)
|
||||||
|
cursor += 1
|
||||||
case '(':
|
case '(':
|
||||||
append_token(&stream, source_file, .Left_Paren, cursor, cursor+1)
|
append_token(&stream, source_file, .Left_Paren, cursor, cursor+1)
|
||||||
cursor += 1
|
cursor += 1
|
||||||
@@ -95,10 +99,41 @@ lex :: proc(
|
|||||||
case ',':
|
case ',':
|
||||||
append_token(&stream, source_file, .Comma, cursor, cursor+1)
|
append_token(&stream, source_file, .Comma, cursor, cursor+1)
|
||||||
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 ';':
|
case ';':
|
||||||
id := source.add(
|
id := source.add(
|
||||||
diagnostics,
|
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",
|
"semicolons are invalid; terminate statements with a newline",
|
||||||
)
|
)
|
||||||
append_token(&stream, source_file, .Invalid, cursor, cursor+1, id)
|
append_token(&stream, source_file, .Invalid, cursor, cursor+1, id)
|
||||||
@@ -120,7 +155,7 @@ lex :: proc(
|
|||||||
} else {
|
} else {
|
||||||
id := source.addf(
|
id := source.addf(
|
||||||
diagnostics,
|
diagnostics,
|
||||||
source.Span{start=cursor, end=cursor+1},
|
source.Span{file=source_file.id, start=cursor, end=cursor+1},
|
||||||
"invalid source byte 0x%02x",
|
"invalid source byte 0x%02x",
|
||||||
value,
|
value,
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -65,10 +65,14 @@ diagnostic_message :: proc(emitter: ^Emitter, diagnostic: int, span: source.Span
|
|||||||
delete(message, emitter.allocator)
|
delete(message, emitter.allocator)
|
||||||
return id
|
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(
|
message := fmt.aprintf(
|
||||||
"%s:%d:%d: runtime trap: %s",
|
"%s:%d:%d: runtime trap: %s",
|
||||||
emitter.diagnostics.source.path,
|
source_file.path,
|
||||||
line,
|
line,
|
||||||
column,
|
column,
|
||||||
fallback,
|
fallback,
|
||||||
|
|||||||
@@ -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
@@ -3,16 +3,24 @@ package parser
|
|||||||
import "../ast"
|
import "../ast"
|
||||||
import "../source"
|
import "../source"
|
||||||
import "../token"
|
import "../token"
|
||||||
|
import "core:fmt"
|
||||||
import "core:strconv"
|
import "core:strconv"
|
||||||
|
import "core:strings"
|
||||||
|
|
||||||
Parser :: struct {
|
Parser :: struct {
|
||||||
tokens: ^token.Stream,
|
tokens: ^token.Stream,
|
||||||
diagnostics: ^source.Diagnostics,
|
diagnostics: ^source.Diagnostics,
|
||||||
module: ast.Module,
|
module: ast.Module,
|
||||||
|
pkg: int,
|
||||||
|
file: int,
|
||||||
cursor: int,
|
cursor: int,
|
||||||
delimiter_depth: 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 {
|
current :: proc(parser: ^Parser) -> token.Token {
|
||||||
return parser.tokens.items[min(parser.cursor, len(parser.tokens.items)-1)]
|
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
|
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)
|
left_paren := advance(parser)
|
||||||
parser.delimiter_depth += 1
|
parser.delimiter_depth += 1
|
||||||
defer 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{
|
return add_expr(parser, ast.Expr{
|
||||||
kind=.Call,
|
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,
|
text=name.text,
|
||||||
args=args[:],
|
args=args[:],
|
||||||
left=ast.INVALID_ID,
|
left=ast.INVALID_ID,
|
||||||
@@ -143,14 +152,24 @@ parse_primary :: proc(parser: ^Parser) -> int {
|
|||||||
diagnostic=-1,
|
diagnostic=-1,
|
||||||
})
|
})
|
||||||
case .Identifier:
|
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 {
|
if current(parser).kind == .Left_Paren {
|
||||||
return parse_call(parser, tok)
|
return parse_call(parser, qualifier, first, name)
|
||||||
}
|
}
|
||||||
return add_expr(parser, ast.Expr{
|
return add_expr(parser, ast.Expr{
|
||||||
kind=.Name,
|
kind=.Name,
|
||||||
span=tok.span,
|
span=span_from(first.span, name.span),
|
||||||
text=tok.text,
|
qualifier=qualifier,
|
||||||
|
text=name.text,
|
||||||
left=ast.INVALID_ID,
|
left=ast.INVALID_ID,
|
||||||
right=ast.INVALID_ID,
|
right=ast.INVALID_ID,
|
||||||
diagnostic=-1,
|
diagnostic=-1,
|
||||||
@@ -198,7 +217,7 @@ parse_expression :: proc(parser: ^Parser) -> int {
|
|||||||
right_expr := parser.module.exprs[right]
|
right_expr := parser.module.exprs[right]
|
||||||
left = add_expr(parser, ast.Expr{
|
left = add_expr(parser, ast.Expr{
|
||||||
kind=.Add,
|
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,
|
left=left,
|
||||||
right=right,
|
right=right,
|
||||||
diagnostic=-1,
|
diagnostic=-1,
|
||||||
@@ -210,12 +229,12 @@ parse_expression :: proc(parser: ^Parser) -> int {
|
|||||||
return left
|
return left
|
||||||
}
|
}
|
||||||
|
|
||||||
finish_statement :: proc(parser: ^Parser) -> int {
|
finish_statement :: proc(parser: ^Parser, allow_closing_brace := false) -> int {
|
||||||
if current(parser).kind == .Newline {
|
if current(parser).kind == .Newline {
|
||||||
skip_newlines(parser)
|
skip_newlines(parser)
|
||||||
return -1
|
return -1
|
||||||
}
|
}
|
||||||
if current(parser).kind == .Eof {
|
if current(parser).kind == .Eof || allow_closing_brace && current(parser).kind == .Right_Brace {
|
||||||
return -1
|
return -1
|
||||||
}
|
}
|
||||||
diagnostic := source.add(
|
diagnostic := source.add(
|
||||||
@@ -240,7 +259,7 @@ parse_return :: proc(parser: ^Parser) -> int {
|
|||||||
id := len(parser.module.statements)
|
id := len(parser.module.statements)
|
||||||
append(&parser.module.statements, ast.Stmt{
|
append(&parser.module.statements, ast.Stmt{
|
||||||
kind=.Return,
|
kind=.Return,
|
||||||
span=source.Span{start=start.span.start, end=end.span.end},
|
span=span_from(start.span, end.span),
|
||||||
name="_",
|
name="_",
|
||||||
expr=ast.INVALID_ID,
|
expr=ast.INVALID_ID,
|
||||||
diagnostic=-1,
|
diagnostic=-1,
|
||||||
@@ -251,7 +270,7 @@ parse_return :: proc(parser: ^Parser) -> int {
|
|||||||
id := len(parser.module.statements)
|
id := len(parser.module.statements)
|
||||||
append(&parser.module.statements, ast.Stmt{
|
append(&parser.module.statements, ast.Stmt{
|
||||||
kind=.Return,
|
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,
|
expr=expr,
|
||||||
diagnostic=-1,
|
diagnostic=-1,
|
||||||
})
|
})
|
||||||
@@ -286,7 +305,7 @@ parse_statement :: proc(parser: ^Parser) -> int {
|
|||||||
id := len(parser.module.statements)
|
id := len(parser.module.statements)
|
||||||
append(&parser.module.statements, ast.Stmt{
|
append(&parser.module.statements, ast.Stmt{
|
||||||
kind=kind,
|
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,
|
name=name.text,
|
||||||
type=type_syntax,
|
type=type_syntax,
|
||||||
immutable=immutable,
|
immutable=immutable,
|
||||||
@@ -367,7 +386,7 @@ parse_function :: proc(parser: ^Parser, name: token.Token, c_abi: bool) {
|
|||||||
skip_newlines(parser)
|
skip_newlines(parser)
|
||||||
for current(parser).kind != .Right_Brace && current(parser).kind != .Eof {
|
for current(parser).kind != .Right_Brace && current(parser).kind != .Eof {
|
||||||
append(&body, parse_statement(parser))
|
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)
|
statement_id := len(parser.module.statements)
|
||||||
append(&parser.module.statements, ast.Stmt{
|
append(&parser.module.statements, ast.Stmt{
|
||||||
kind=.Invalid,
|
kind=.Invalid,
|
||||||
@@ -384,8 +403,10 @@ parse_function :: proc(parser: ^Parser, name: token.Token, c_abi: bool) {
|
|||||||
end = func_token
|
end = func_token
|
||||||
}
|
}
|
||||||
append(&parser.module.functions, ast.Function{
|
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,
|
name=name.text,
|
||||||
|
pkg=parser.pkg,
|
||||||
|
file=parser.file,
|
||||||
c_abi=c_abi,
|
c_abi=c_abi,
|
||||||
params=params,
|
params=params,
|
||||||
result=result,
|
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) {
|
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 {
|
if current(parser).kind != .Identifier {
|
||||||
source.add(parser.diagnostics, current(parser).span, "expected a top-level declaration")
|
source.add(parser.diagnostics, current(parser).span, "expected a top-level declaration")
|
||||||
for current(parser).kind != .Newline && current(parser).kind != .Eof {
|
for current(parser).kind != .Newline && current(parser).kind != .Eof {
|
||||||
@@ -404,6 +481,17 @@ parse_top_level :: proc(parser: ^Parser) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
name := advance(parser)
|
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
|
type_syntax := ast.Type_Syntax.Invalid
|
||||||
if is_type_token(current(parser).kind) {
|
if is_type_token(current(parser).kind) {
|
||||||
type_syntax = parse_type(parser)
|
type_syntax = parse_type(parser)
|
||||||
@@ -430,8 +518,10 @@ parse_top_level :: proc(parser: ^Parser) {
|
|||||||
|
|
||||||
expr := parse_expression(parser)
|
expr := parse_expression(parser)
|
||||||
append(&parser.module.globals, ast.Global{
|
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,
|
name=name.text,
|
||||||
|
pkg=parser.pkg,
|
||||||
|
file=parser.file,
|
||||||
type=type_syntax,
|
type=type_syntax,
|
||||||
immutable=operator.kind == .Colon_Colon,
|
immutable=operator.kind == .Colon_Colon,
|
||||||
expr=expr,
|
expr=expr,
|
||||||
@@ -457,3 +547,24 @@ parse :: proc(
|
|||||||
}
|
}
|
||||||
return parser.module
|
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
|
||||||
|
}
|
||||||
|
|||||||
@@ -4,15 +4,22 @@ import "core:fmt"
|
|||||||
import "core:mem"
|
import "core:mem"
|
||||||
|
|
||||||
Span :: struct {
|
Span :: struct {
|
||||||
|
file: int,
|
||||||
start: int,
|
start: int,
|
||||||
end: int,
|
end: int,
|
||||||
}
|
}
|
||||||
|
|
||||||
Source :: struct {
|
Source :: struct {
|
||||||
|
id: int,
|
||||||
path: string,
|
path: string,
|
||||||
text: string,
|
text: string,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Store :: struct {
|
||||||
|
items: [dynamic]Source,
|
||||||
|
allocator: mem.Allocator,
|
||||||
|
}
|
||||||
|
|
||||||
Diagnostic :: struct {
|
Diagnostic :: struct {
|
||||||
span: Span,
|
span: Span,
|
||||||
message: string,
|
message: string,
|
||||||
@@ -20,10 +27,36 @@ Diagnostic :: struct {
|
|||||||
|
|
||||||
Diagnostics :: struct {
|
Diagnostics :: struct {
|
||||||
source: ^Source,
|
source: ^Source,
|
||||||
|
store: ^Store,
|
||||||
items: [dynamic]Diagnostic,
|
items: [dynamic]Diagnostic,
|
||||||
allocator: mem.Allocator,
|
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 {
|
init_diagnostics :: proc(source_file: ^Source, allocator := context.allocator) -> Diagnostics {
|
||||||
result: Diagnostics
|
result: Diagnostics
|
||||||
result.source = source_file
|
result.source = source_file
|
||||||
@@ -32,6 +65,14 @@ init_diagnostics :: proc(source_file: ^Source, allocator := context.allocator) -
|
|||||||
return result
|
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) {
|
destroy_diagnostics :: proc(diagnostics: ^Diagnostics) {
|
||||||
for diagnostic in diagnostics.items {
|
for diagnostic in diagnostics.items {
|
||||||
delete(diagnostic.message, diagnostics.allocator)
|
delete(diagnostic.message, diagnostics.allocator)
|
||||||
@@ -79,15 +120,32 @@ line_and_column :: proc(source_file: ^Source, offset: int) -> (line, column: int
|
|||||||
return
|
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 {
|
format :: proc(diagnostics: ^Diagnostics, id: int, allocator := context.allocator) -> string {
|
||||||
if id < 0 || id >= len(diagnostics.items) {
|
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]
|
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(
|
return fmt.aprintf(
|
||||||
"%s:%d:%d: error: %s",
|
"%s:%d:%d: error: %s",
|
||||||
diagnostics.source.path,
|
source_file.path,
|
||||||
line,
|
line,
|
||||||
column,
|
column,
|
||||||
diagnostic.message,
|
diagnostic.message,
|
||||||
|
|||||||
@@ -8,10 +8,12 @@ Kind :: enum {
|
|||||||
Newline,
|
Newline,
|
||||||
Identifier,
|
Identifier,
|
||||||
Integer,
|
Integer,
|
||||||
|
String,
|
||||||
Underscore,
|
Underscore,
|
||||||
Colon_Colon,
|
Colon_Colon,
|
||||||
Equal,
|
Equal,
|
||||||
Plus,
|
Plus,
|
||||||
|
Dot,
|
||||||
Left_Paren,
|
Left_Paren,
|
||||||
Right_Paren,
|
Right_Paren,
|
||||||
Left_Brace,
|
Left_Brace,
|
||||||
@@ -19,6 +21,7 @@ Kind :: enum {
|
|||||||
Comma,
|
Comma,
|
||||||
Keyword_C,
|
Keyword_C,
|
||||||
Keyword_Func,
|
Keyword_Func,
|
||||||
|
Keyword_Import,
|
||||||
Keyword_Return,
|
Keyword_Return,
|
||||||
Keyword_Void,
|
Keyword_Void,
|
||||||
Keyword_Int,
|
Keyword_Int,
|
||||||
|
|||||||
+622
-26
@@ -7,12 +7,14 @@ import "./compiler/checker"
|
|||||||
import "./compiler/hir"
|
import "./compiler/hir"
|
||||||
import "./compiler/ir"
|
import "./compiler/ir"
|
||||||
import "./compiler/lexer"
|
import "./compiler/lexer"
|
||||||
|
import "./compiler/loader"
|
||||||
import "./compiler/llvm"
|
import "./compiler/llvm"
|
||||||
import "./compiler/lower"
|
import "./compiler/lower"
|
||||||
import "./compiler/parser"
|
import "./compiler/parser"
|
||||||
import "./compiler/source"
|
import "./compiler/source"
|
||||||
import "./compiler/token"
|
import "./compiler/token"
|
||||||
import "./compiler/types"
|
import "./compiler/types"
|
||||||
|
import "core:fmt"
|
||||||
import "core:os"
|
import "core:os"
|
||||||
import "core:os/os2"
|
import "core:os/os2"
|
||||||
import "core:strings"
|
import "core:strings"
|
||||||
@@ -53,6 +55,153 @@ main :: func() void {}
|
|||||||
testing.expect_value(t, len(module.functions[0].params), 2)
|
testing.expect_value(t, len(module.functions[0].params), 2)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@(test)
|
||||||
|
parser_accepts_single_statement_one_line_functions :: proc(t: ^testing.T) {
|
||||||
|
text := `give :: func() i8 { return 7 }
|
||||||
|
main :: func() void { _ = give() }
|
||||||
|
`
|
||||||
|
source_file := source.Source{path="test.bro", text=text}
|
||||||
|
diagnostics := source.init_diagnostics(&source_file)
|
||||||
|
defer source.destroy_diagnostics(&diagnostics)
|
||||||
|
stream := lexer.lex(&source_file, &diagnostics)
|
||||||
|
defer delete(stream.items)
|
||||||
|
module := parser.parse(&stream, &diagnostics)
|
||||||
|
defer ast.destroy_module(&module)
|
||||||
|
|
||||||
|
testing.expect_value(t, len(diagnostics.items), 0)
|
||||||
|
testing.expect_value(t, len(module.functions), 2)
|
||||||
|
testing.expect_value(t, len(module.functions[0].body), 1)
|
||||||
|
testing.expect_value(t, len(module.functions[1].body), 1)
|
||||||
|
testing.expect_value(t, module.statements[module.functions[0].body[0]].kind, ast.Stmt_Kind.Return)
|
||||||
|
testing.expect_value(t, module.statements[module.functions[1].body[0]].kind, ast.Stmt_Kind.Assignment)
|
||||||
|
}
|
||||||
|
|
||||||
|
@(test)
|
||||||
|
parser_accepts_bare_and_aliased_imports :: proc(t: ^testing.T) {
|
||||||
|
text := `import
|
||||||
|
"../math"
|
||||||
|
other :: import "../math"
|
||||||
|
escaped :: import "dir\"name\\tail"
|
||||||
|
main :: func() void {}
|
||||||
|
`
|
||||||
|
source_file := source.Source{path="test.bro", text=text}
|
||||||
|
diagnostics := source.init_diagnostics(&source_file)
|
||||||
|
defer source.destroy_diagnostics(&diagnostics)
|
||||||
|
stream := lexer.lex(&source_file, &diagnostics)
|
||||||
|
defer delete(stream.items)
|
||||||
|
module := parser.parse(&stream, &diagnostics)
|
||||||
|
defer ast.destroy_module(&module)
|
||||||
|
|
||||||
|
testing.expect_value(t, len(diagnostics.items), 0)
|
||||||
|
testing.expect_value(t, len(module.imports), 3)
|
||||||
|
testing.expect_value(t, module.imports[0].alias, "")
|
||||||
|
testing.expect_value(t, module.imports[0].path, "../math")
|
||||||
|
testing.expect_value(t, module.imports[1].alias, "other")
|
||||||
|
testing.expect_value(t, module.imports[2].path, "dir\"name\\tail")
|
||||||
|
}
|
||||||
|
|
||||||
|
@(test)
|
||||||
|
parser_rejects_chained_package_access :: proc(t: ^testing.T) {
|
||||||
|
text := `main :: func() void {
|
||||||
|
_ = first.second.value
|
||||||
|
}
|
||||||
|
`
|
||||||
|
source_file := source.Source{path="test.bro", text=text}
|
||||||
|
diagnostics := source.init_diagnostics(&source_file)
|
||||||
|
defer source.destroy_diagnostics(&diagnostics)
|
||||||
|
stream := lexer.lex(&source_file, &diagnostics)
|
||||||
|
defer delete(stream.items)
|
||||||
|
module := parser.parse(&stream, &diagnostics)
|
||||||
|
defer ast.destroy_module(&module)
|
||||||
|
|
||||||
|
testing.expect(t, len(diagnostics.items) > 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
@(test)
|
||||||
|
lexer_diagnoses_invalid_import_strings :: proc(t: ^testing.T) {
|
||||||
|
text := "import \"bad\\q\"\nimport \"unterminated\n"
|
||||||
|
source_file := source.Source{path="test.bro", text=text}
|
||||||
|
diagnostics := source.init_diagnostics(&source_file)
|
||||||
|
defer source.destroy_diagnostics(&diagnostics)
|
||||||
|
stream := lexer.lex(&source_file, &diagnostics)
|
||||||
|
defer delete(stream.items)
|
||||||
|
|
||||||
|
testing.expect_value(t, len(diagnostics.items), 2)
|
||||||
|
}
|
||||||
|
|
||||||
|
@(test)
|
||||||
|
package_loader_discovers_lexical_immediate_bro_files :: proc(t: ^testing.T) {
|
||||||
|
sources := source.init_store()
|
||||||
|
defer source.destroy_store(&sources)
|
||||||
|
diagnostics := source.init_store_diagnostics(&sources)
|
||||||
|
defer source.destroy_diagnostics(&diagnostics)
|
||||||
|
module, loaded := loader.load("examples/packages/basic/app", &sources, &diagnostics)
|
||||||
|
defer ast.destroy_module(&module)
|
||||||
|
|
||||||
|
testing.expect(t, loaded)
|
||||||
|
testing.expect_value(t, len(diagnostics.items), 0)
|
||||||
|
testing.expect_value(t, len(module.packages), 2)
|
||||||
|
testing.expect_value(t, len(module.files), 3)
|
||||||
|
testing.expect(t, strings.has_suffix(sources.items[module.files[0].source].path, "/main.bro"))
|
||||||
|
testing.expect(t, strings.has_suffix(sources.items[module.files[1].source].path, "/value.bro"))
|
||||||
|
testing.expect(t, strings.has_suffix(sources.items[module.files[2].source].path, "/math.bro"))
|
||||||
|
}
|
||||||
|
|
||||||
|
@(test)
|
||||||
|
multi_source_diagnostics_report_the_originating_file :: proc(t: ^testing.T) {
|
||||||
|
sources := source.init_store()
|
||||||
|
defer source.destroy_store(&sources)
|
||||||
|
diagnostics := source.init_store_diagnostics(&sources)
|
||||||
|
defer source.destroy_diagnostics(&diagnostics)
|
||||||
|
module, loaded := loader.load("examples/packages/file_local/app", &sources, &diagnostics)
|
||||||
|
defer ast.destroy_module(&module)
|
||||||
|
hir_module := checker.check(&module, &diagnostics)
|
||||||
|
defer hir.destroy_module(&hir_module)
|
||||||
|
|
||||||
|
testing.expect(t, loaded)
|
||||||
|
found := false
|
||||||
|
for _, diagnostic_id in diagnostics.items {
|
||||||
|
message := source.format(&diagnostics, diagnostic_id)
|
||||||
|
if strings.contains(message, "/b.bro:2:9:") &&
|
||||||
|
strings.contains(message, "unknown package alias 'math'") {
|
||||||
|
found = true
|
||||||
|
}
|
||||||
|
delete(message)
|
||||||
|
}
|
||||||
|
testing.expect(t, found)
|
||||||
|
}
|
||||||
|
|
||||||
|
@(test)
|
||||||
|
semantic_lookup_diagnoses_wrong_declaration_kinds :: proc(t: ^testing.T) {
|
||||||
|
text := `value :: 1
|
||||||
|
give :: func() i8 {
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
main :: func() void {
|
||||||
|
_ = value()
|
||||||
|
_ = give
|
||||||
|
}
|
||||||
|
`
|
||||||
|
source_file := source.Source{path="test.bro", text=text}
|
||||||
|
diagnostics := source.init_diagnostics(&source_file)
|
||||||
|
defer source.destroy_diagnostics(&diagnostics)
|
||||||
|
stream := lexer.lex(&source_file, &diagnostics)
|
||||||
|
defer delete(stream.items)
|
||||||
|
ast_module := parser.parse(&stream, &diagnostics)
|
||||||
|
defer ast.destroy_module(&ast_module)
|
||||||
|
hir_module := checker.check(&ast_module, &diagnostics)
|
||||||
|
defer hir.destroy_module(&hir_module)
|
||||||
|
|
||||||
|
found_global := false
|
||||||
|
found_function := false
|
||||||
|
for diagnostic in diagnostics.items {
|
||||||
|
found_global = found_global || strings.contains(diagnostic.message, "'value' is a global, not a function")
|
||||||
|
found_function = found_function || strings.contains(diagnostic.message, "'give' is a function, not a global value")
|
||||||
|
}
|
||||||
|
testing.expect(t, found_global)
|
||||||
|
testing.expect(t, found_function)
|
||||||
|
}
|
||||||
|
|
||||||
@(test)
|
@(test)
|
||||||
pipeline_emits_specialized_calling_conventions_and_checked_add :: proc(t: ^testing.T) {
|
pipeline_emits_specialized_calling_conventions_and_checked_add :: proc(t: ^testing.T) {
|
||||||
text := `sum_c :: c func(a, b int) int {
|
text := `sum_c :: c func(a, b int) int {
|
||||||
@@ -84,8 +233,8 @@ main :: func() void {
|
|||||||
|
|
||||||
testing.expect_value(t, len(diagnostics.items), 0)
|
testing.expect_value(t, len(diagnostics.items), 0)
|
||||||
testing.expect_value(t, llvm_text, second_llvm_text)
|
testing.expect_value(t, llvm_text, second_llvm_text)
|
||||||
testing.expect(t, strings.contains(llvm_text, "define i8 @sum_c__i8__i8"))
|
testing.expect(t, strings.contains(llvm_text, "define i8 @bro_c__p0__sum_c__i8__i8"))
|
||||||
testing.expect(t, strings.contains(llvm_text, "define internal fastcc i8 @bro__sum_bro__i8__i8"))
|
testing.expect(t, strings.contains(llvm_text, "define internal fastcc i8 @bro__p0__sum_bro__i8__i8"))
|
||||||
testing.expect(t, strings.contains(llvm_text, "@llvm.sadd.with.overflow.i8"))
|
testing.expect(t, strings.contains(llvm_text, "@llvm.sadd.with.overflow.i8"))
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -123,7 +272,7 @@ main :: func() void {
|
|||||||
defer delete(llvm_text)
|
defer delete(llvm_text)
|
||||||
|
|
||||||
testing.expect_value(t, len(diagnostics.items), 0)
|
testing.expect_value(t, len(diagnostics.items), 0)
|
||||||
testing.expect(t, strings.contains(llvm_text, "@bro__take_int__i16"))
|
testing.expect(t, strings.contains(llvm_text, "@bro__p0__take_int__i16"))
|
||||||
for function in ir_module.functions {
|
for function in ir_module.functions {
|
||||||
for instruction in function.instructions {
|
for instruction in function.instructions {
|
||||||
testing.expect(t, instruction.op != ir.Opcode.Add_Checked)
|
testing.expect(t, instruction.op != ir.Opcode.Add_Checked)
|
||||||
@@ -272,6 +421,92 @@ main :: func() void {
|
|||||||
testing.expect_value(t, len(hir_module.functions), 3)
|
testing.expect_value(t, len(hir_module.functions), 3)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@(test)
|
||||||
|
long_generic_call_chain_reaches_a_fixed_point :: proc(t: ^testing.T) {
|
||||||
|
builder := strings.builder_make()
|
||||||
|
defer strings.builder_destroy(&builder)
|
||||||
|
for index in 0 ..< 70 {
|
||||||
|
fmt.sbprintf(&builder, "f%d :: func(value int) int ", index)
|
||||||
|
strings.write_string(&builder, "{ return ")
|
||||||
|
if index == 69 {
|
||||||
|
strings.write_string(&builder, "value")
|
||||||
|
} else {
|
||||||
|
fmt.sbprintf(&builder, "f%d(value)", index+1)
|
||||||
|
}
|
||||||
|
strings.write_string(&builder, " }\n")
|
||||||
|
}
|
||||||
|
strings.write_string(&builder, "main :: func() i32 { return f0(1) }\n")
|
||||||
|
|
||||||
|
source_file := source.Source{path="test.bro", text=strings.to_string(builder)}
|
||||||
|
diagnostics := source.init_diagnostics(&source_file)
|
||||||
|
defer source.destroy_diagnostics(&diagnostics)
|
||||||
|
stream := lexer.lex(&source_file, &diagnostics)
|
||||||
|
defer delete(stream.items)
|
||||||
|
ast_module := parser.parse(&stream, &diagnostics)
|
||||||
|
defer ast.destroy_module(&ast_module)
|
||||||
|
hir_module := checker.check(&ast_module, &diagnostics)
|
||||||
|
defer hir.destroy_module(&hir_module)
|
||||||
|
|
||||||
|
testing.expect_value(t, len(diagnostics.items), 0)
|
||||||
|
testing.expect_value(t, len(hir_module.functions), 71)
|
||||||
|
}
|
||||||
|
|
||||||
|
@(test)
|
||||||
|
globals_and_generic_results_reach_a_shared_fixed_point :: proc(t: ^testing.T) {
|
||||||
|
text := `derived :: identity(base)
|
||||||
|
base :: make()
|
||||||
|
identity :: func(value int) int {
|
||||||
|
return value
|
||||||
|
}
|
||||||
|
make :: func() int {
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
main :: func() i32 {
|
||||||
|
return derived
|
||||||
|
}
|
||||||
|
`
|
||||||
|
source_file := source.Source{path="test.bro", text=text}
|
||||||
|
diagnostics := source.init_diagnostics(&source_file)
|
||||||
|
defer source.destroy_diagnostics(&diagnostics)
|
||||||
|
stream := lexer.lex(&source_file, &diagnostics)
|
||||||
|
defer delete(stream.items)
|
||||||
|
ast_module := parser.parse(&stream, &diagnostics)
|
||||||
|
defer ast.destroy_module(&ast_module)
|
||||||
|
hir_module := checker.check(&ast_module, &diagnostics)
|
||||||
|
defer hir.destroy_module(&hir_module)
|
||||||
|
|
||||||
|
testing.expect_value(t, len(diagnostics.items), 0)
|
||||||
|
testing.expect_value(t, len(hir_module.functions), 3)
|
||||||
|
for global in hir_module.globals {
|
||||||
|
testing.expect(t, types.equal(global.type, types.I8))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@(test)
|
||||||
|
unused_function_signatures_are_validated_eagerly :: proc(t: ^testing.T) {
|
||||||
|
text := `broken :: func(value, value i8, nope void) void {}
|
||||||
|
main :: func() void {}
|
||||||
|
`
|
||||||
|
source_file := source.Source{path="test.bro", text=text}
|
||||||
|
diagnostics := source.init_diagnostics(&source_file)
|
||||||
|
defer source.destroy_diagnostics(&diagnostics)
|
||||||
|
stream := lexer.lex(&source_file, &diagnostics)
|
||||||
|
defer delete(stream.items)
|
||||||
|
ast_module := parser.parse(&stream, &diagnostics)
|
||||||
|
defer ast.destroy_module(&ast_module)
|
||||||
|
hir_module := checker.check(&ast_module, &diagnostics)
|
||||||
|
defer hir.destroy_module(&hir_module)
|
||||||
|
|
||||||
|
found_duplicate := false
|
||||||
|
found_void := false
|
||||||
|
for diagnostic in diagnostics.items {
|
||||||
|
found_duplicate = found_duplicate || strings.contains(diagnostic.message, "duplicate parameter 'value'")
|
||||||
|
found_void = found_void || strings.contains(diagnostic.message, "void is only valid as a function result type")
|
||||||
|
}
|
||||||
|
testing.expect(t, found_duplicate)
|
||||||
|
testing.expect(t, found_void)
|
||||||
|
}
|
||||||
|
|
||||||
run_executable :: proc(path: string) -> os2.Process_State {
|
run_executable :: proc(path: string) -> os2.Process_State {
|
||||||
state, stdout, stderr, _ := os2.process_exec(
|
state, stdout, stderr, _ := os2.process_exec(
|
||||||
os2.Process_Desc{command=[]string{path}},
|
os2.Process_Desc{command=[]string{path}},
|
||||||
@@ -286,17 +521,27 @@ run_executable :: proc(path: string) -> os2.Process_State {
|
|||||||
valid_program_compiles_and_runs :: proc(t: ^testing.T) {
|
valid_program_compiles_and_runs :: proc(t: ^testing.T) {
|
||||||
output := "/tmp/brolang-test-valid"
|
output := "/tmp/brolang-test-valid"
|
||||||
defer _ = os.remove(output)
|
defer _ = os.remove(output)
|
||||||
status := compiler_core.compile_file("examples/prototype.bro", output)
|
status := compiler_core.compile_package("examples/programs/prototype", output)
|
||||||
testing.expect_value(t, status, 0)
|
testing.expect_value(t, status, 0)
|
||||||
state := run_executable(output)
|
state := run_executable(output)
|
||||||
testing.expect_value(t, state.exit_code, 0)
|
testing.expect_value(t, state.exit_code, 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@(test)
|
||||||
|
one_line_main_compiles_and_runs :: proc(t: ^testing.T) {
|
||||||
|
output := "/tmp/brolang-test-one-line-main"
|
||||||
|
defer _ = os.remove(output)
|
||||||
|
status := compiler_core.compile_package("examples/programs/one_line", output)
|
||||||
|
testing.expect_value(t, status, 0)
|
||||||
|
state := run_executable(output)
|
||||||
|
testing.expect_value(t, state.exit_code, 7)
|
||||||
|
}
|
||||||
|
|
||||||
@(test)
|
@(test)
|
||||||
folded_constant_addition_compiles_and_runs :: proc(t: ^testing.T) {
|
folded_constant_addition_compiles_and_runs :: proc(t: ^testing.T) {
|
||||||
output := "/tmp/brolang-test-constant-fold"
|
output := "/tmp/brolang-test-constant-fold"
|
||||||
defer _ = os.remove(output)
|
defer _ = os.remove(output)
|
||||||
status := compiler_core.compile_file("examples/constant_fold.bro", output)
|
status := compiler_core.compile_package("examples/programs/constant_fold", output)
|
||||||
testing.expect_value(t, status, 0)
|
testing.expect_value(t, status, 0)
|
||||||
state := run_executable(output)
|
state := run_executable(output)
|
||||||
testing.expect_value(t, state.exit_code, 0)
|
testing.expect_value(t, state.exit_code, 0)
|
||||||
@@ -306,7 +551,7 @@ folded_constant_addition_compiles_and_runs :: proc(t: ^testing.T) {
|
|||||||
unused_invalid_global_does_not_trap :: proc(t: ^testing.T) {
|
unused_invalid_global_does_not_trap :: proc(t: ^testing.T) {
|
||||||
output := "/tmp/brolang-test-invalid-unused"
|
output := "/tmp/brolang-test-invalid-unused"
|
||||||
defer _ = os.remove(output)
|
defer _ = os.remove(output)
|
||||||
status := compiler_core.compile_file("examples/invalid_unused_global.bro", output)
|
status := compiler_core.compile_package("examples/programs/invalid_unused_global", output)
|
||||||
testing.expect_value(t, status, 1)
|
testing.expect_value(t, status, 1)
|
||||||
state := run_executable(output)
|
state := run_executable(output)
|
||||||
testing.expect_value(t, state.exit_code, 0)
|
testing.expect_value(t, state.exit_code, 0)
|
||||||
@@ -316,7 +561,7 @@ unused_invalid_global_does_not_trap :: proc(t: ^testing.T) {
|
|||||||
used_invalid_global_traps :: proc(t: ^testing.T) {
|
used_invalid_global_traps :: proc(t: ^testing.T) {
|
||||||
output := "/tmp/brolang-test-invalid-used"
|
output := "/tmp/brolang-test-invalid-used"
|
||||||
defer _ = os.remove(output)
|
defer _ = os.remove(output)
|
||||||
status := compiler_core.compile_file("examples/invalid_used_global.bro", output)
|
status := compiler_core.compile_package("examples/programs/invalid_used_global", output)
|
||||||
testing.expect_value(t, status, 1)
|
testing.expect_value(t, status, 1)
|
||||||
state := run_executable(output)
|
state := run_executable(output)
|
||||||
testing.expect(t, !state.success)
|
testing.expect(t, !state.success)
|
||||||
@@ -326,7 +571,7 @@ used_invalid_global_traps :: proc(t: ^testing.T) {
|
|||||||
main_int_is_constrained_to_i32 :: proc(t: ^testing.T) {
|
main_int_is_constrained_to_i32 :: proc(t: ^testing.T) {
|
||||||
output := "/tmp/brolang-test-main-int"
|
output := "/tmp/brolang-test-main-int"
|
||||||
defer _ = os.remove(output)
|
defer _ = os.remove(output)
|
||||||
status := compiler_core.compile_file("examples/main_int.bro", output)
|
status := compiler_core.compile_package("examples/programs/main_int", output)
|
||||||
testing.expect_value(t, status, 0)
|
testing.expect_value(t, status, 0)
|
||||||
state := run_executable(output)
|
state := run_executable(output)
|
||||||
testing.expect_value(t, state.exit_code, 3)
|
testing.expect_value(t, state.exit_code, 3)
|
||||||
@@ -336,7 +581,7 @@ main_int_is_constrained_to_i32 :: proc(t: ^testing.T) {
|
|||||||
main_i32_returns_directly :: proc(t: ^testing.T) {
|
main_i32_returns_directly :: proc(t: ^testing.T) {
|
||||||
output := "/tmp/brolang-test-main-i32"
|
output := "/tmp/brolang-test-main-i32"
|
||||||
defer _ = os.remove(output)
|
defer _ = os.remove(output)
|
||||||
status := compiler_core.compile_file("examples/main_i32.bro", output)
|
status := compiler_core.compile_package("examples/programs/main_i32", output)
|
||||||
testing.expect_value(t, status, 0)
|
testing.expect_value(t, status, 0)
|
||||||
state := run_executable(output)
|
state := run_executable(output)
|
||||||
testing.expect_value(t, state.exit_code, 4)
|
testing.expect_value(t, state.exit_code, 4)
|
||||||
@@ -346,7 +591,7 @@ main_i32_returns_directly :: proc(t: ^testing.T) {
|
|||||||
transitive_problematic_global_is_deferred :: proc(t: ^testing.T) {
|
transitive_problematic_global_is_deferred :: proc(t: ^testing.T) {
|
||||||
output := "/tmp/brolang-test-transitive-unused"
|
output := "/tmp/brolang-test-transitive-unused"
|
||||||
defer _ = os.remove(output)
|
defer _ = os.remove(output)
|
||||||
status := compiler_core.compile_file("examples/invalid_transitive_unused_global.bro", output)
|
status := compiler_core.compile_package("examples/programs/invalid_transitive_unused_global", output)
|
||||||
testing.expect_value(t, status, 1)
|
testing.expect_value(t, status, 1)
|
||||||
state := run_executable(output)
|
state := run_executable(output)
|
||||||
testing.expect_value(t, state.exit_code, 0)
|
testing.expect_value(t, state.exit_code, 0)
|
||||||
@@ -356,7 +601,7 @@ transitive_problematic_global_is_deferred :: proc(t: ^testing.T) {
|
|||||||
checked_addition_traps_on_overflow :: proc(t: ^testing.T) {
|
checked_addition_traps_on_overflow :: proc(t: ^testing.T) {
|
||||||
output := "/tmp/brolang-test-overflow"
|
output := "/tmp/brolang-test-overflow"
|
||||||
defer _ = os.remove(output)
|
defer _ = os.remove(output)
|
||||||
status := compiler_core.compile_file("examples/overflow.bro", output)
|
status := compiler_core.compile_package("examples/programs/overflow", output)
|
||||||
testing.expect_value(t, status, 0)
|
testing.expect_value(t, status, 0)
|
||||||
state := run_executable(output)
|
state := run_executable(output)
|
||||||
testing.expect(t, !state.success)
|
testing.expect(t, !state.success)
|
||||||
@@ -366,7 +611,7 @@ checked_addition_traps_on_overflow :: proc(t: ^testing.T) {
|
|||||||
constant_that_does_not_fit_context_produces_trap_executable :: proc(t: ^testing.T) {
|
constant_that_does_not_fit_context_produces_trap_executable :: proc(t: ^testing.T) {
|
||||||
output := "/tmp/brolang-test-constant-context-error"
|
output := "/tmp/brolang-test-constant-context-error"
|
||||||
defer _ = os.remove(output)
|
defer _ = os.remove(output)
|
||||||
status := compiler_core.compile_file("examples/constant_context_error.bro", output)
|
status := compiler_core.compile_package("examples/programs/constant_context_error", output)
|
||||||
testing.expect_value(t, status, 1)
|
testing.expect_value(t, status, 1)
|
||||||
state := run_executable(output)
|
state := run_executable(output)
|
||||||
testing.expect(t, !state.success)
|
testing.expect(t, !state.success)
|
||||||
@@ -376,7 +621,7 @@ constant_that_does_not_fit_context_produces_trap_executable :: proc(t: ^testing.
|
|||||||
constant_beyond_i64_produces_trap_executable :: proc(t: ^testing.T) {
|
constant_beyond_i64_produces_trap_executable :: proc(t: ^testing.T) {
|
||||||
output := "/tmp/brolang-test-constant-i64-overflow"
|
output := "/tmp/brolang-test-constant-i64-overflow"
|
||||||
defer _ = os.remove(output)
|
defer _ = os.remove(output)
|
||||||
status := compiler_core.compile_file("examples/constant_i64_overflow.bro", output)
|
status := compiler_core.compile_package("examples/programs/constant_i64_overflow", output)
|
||||||
testing.expect_value(t, status, 1)
|
testing.expect_value(t, status, 1)
|
||||||
state := run_executable(output)
|
state := run_executable(output)
|
||||||
testing.expect(t, !state.success)
|
testing.expect(t, !state.success)
|
||||||
@@ -384,7 +629,7 @@ constant_beyond_i64_produces_trap_executable :: proc(t: ^testing.T) {
|
|||||||
|
|
||||||
@(test)
|
@(test)
|
||||||
same_line_statements_are_diagnosed :: proc(t: ^testing.T) {
|
same_line_statements_are_diagnosed :: proc(t: ^testing.T) {
|
||||||
text := "main :: func() void { _ = 1 _ = 2\n}\n"
|
text := "main :: func() void { _ = 1 _ = 2 }\n"
|
||||||
source_file := source.Source{path="test.bro", text=text}
|
source_file := source.Source{path="test.bro", text=text}
|
||||||
diagnostics := source.init_diagnostics(&source_file)
|
diagnostics := source.init_diagnostics(&source_file)
|
||||||
defer source.destroy_diagnostics(&diagnostics)
|
defer source.destroy_diagnostics(&diagnostics)
|
||||||
@@ -399,7 +644,7 @@ same_line_statements_are_diagnosed :: proc(t: ^testing.T) {
|
|||||||
missing_main_produces_trap_executable :: proc(t: ^testing.T) {
|
missing_main_produces_trap_executable :: proc(t: ^testing.T) {
|
||||||
output := "/tmp/brolang-test-missing-main"
|
output := "/tmp/brolang-test-missing-main"
|
||||||
defer _ = os.remove(output)
|
defer _ = os.remove(output)
|
||||||
status := compiler_core.compile_file("examples/missing_main.bro", output)
|
status := compiler_core.compile_package("examples/programs/missing_main", output)
|
||||||
testing.expect_value(t, status, 1)
|
testing.expect_value(t, status, 1)
|
||||||
state := run_executable(output)
|
state := run_executable(output)
|
||||||
testing.expect(t, !state.success)
|
testing.expect(t, !state.success)
|
||||||
@@ -422,7 +667,7 @@ backend_failure_preserves_existing_output :: proc(t: ^testing.T) {
|
|||||||
mutable_local_reassignment_uses_runtime_storage :: proc(t: ^testing.T) {
|
mutable_local_reassignment_uses_runtime_storage :: proc(t: ^testing.T) {
|
||||||
output := "/tmp/brolang-test-mutable"
|
output := "/tmp/brolang-test-mutable"
|
||||||
defer _ = os.remove(output)
|
defer _ = os.remove(output)
|
||||||
status := compiler_core.compile_file("examples/mutable_local.bro", output)
|
status := compiler_core.compile_package("examples/programs/mutable_local", output)
|
||||||
testing.expect_value(t, status, 0)
|
testing.expect_value(t, status, 0)
|
||||||
state := run_executable(output)
|
state := run_executable(output)
|
||||||
testing.expect_value(t, state.exit_code, 3)
|
testing.expect_value(t, state.exit_code, 3)
|
||||||
@@ -432,7 +677,7 @@ mutable_local_reassignment_uses_runtime_storage :: proc(t: ^testing.T) {
|
|||||||
implicit_narrowing_produces_trap_executable :: proc(t: ^testing.T) {
|
implicit_narrowing_produces_trap_executable :: proc(t: ^testing.T) {
|
||||||
output := "/tmp/brolang-test-narrowing"
|
output := "/tmp/brolang-test-narrowing"
|
||||||
defer _ = os.remove(output)
|
defer _ = os.remove(output)
|
||||||
status := compiler_core.compile_file("examples/narrowing_error.bro", output)
|
status := compiler_core.compile_package("examples/programs/narrowing_error", output)
|
||||||
testing.expect_value(t, status, 1)
|
testing.expect_value(t, status, 1)
|
||||||
state := run_executable(output)
|
state := run_executable(output)
|
||||||
testing.expect(t, !state.success)
|
testing.expect(t, !state.success)
|
||||||
@@ -442,7 +687,7 @@ implicit_narrowing_produces_trap_executable :: proc(t: ^testing.T) {
|
|||||||
valid_runtime_global_initializes_before_main :: proc(t: ^testing.T) {
|
valid_runtime_global_initializes_before_main :: proc(t: ^testing.T) {
|
||||||
output := "/tmp/brolang-test-runtime-global"
|
output := "/tmp/brolang-test-runtime-global"
|
||||||
defer _ = os.remove(output)
|
defer _ = os.remove(output)
|
||||||
status := compiler_core.compile_file("examples/runtime_global.bro", output)
|
status := compiler_core.compile_package("examples/programs/runtime_global", output)
|
||||||
testing.expect_value(t, status, 0)
|
testing.expect_value(t, status, 0)
|
||||||
state := run_executable(output)
|
state := run_executable(output)
|
||||||
testing.expect_value(t, state.exit_code, 0)
|
testing.expect_value(t, state.exit_code, 0)
|
||||||
@@ -452,7 +697,7 @@ valid_runtime_global_initializes_before_main :: proc(t: ^testing.T) {
|
|||||||
unused_global_cycle_is_deferred :: proc(t: ^testing.T) {
|
unused_global_cycle_is_deferred :: proc(t: ^testing.T) {
|
||||||
output := "/tmp/brolang-test-cycle-unused"
|
output := "/tmp/brolang-test-cycle-unused"
|
||||||
defer _ = os.remove(output)
|
defer _ = os.remove(output)
|
||||||
status := compiler_core.compile_file("examples/cycle_unused.bro", output)
|
status := compiler_core.compile_package("examples/programs/cycle_unused", output)
|
||||||
testing.expect_value(t, status, 1)
|
testing.expect_value(t, status, 1)
|
||||||
state := run_executable(output)
|
state := run_executable(output)
|
||||||
testing.expect_value(t, state.exit_code, 0)
|
testing.expect_value(t, state.exit_code, 0)
|
||||||
@@ -462,7 +707,7 @@ unused_global_cycle_is_deferred :: proc(t: ^testing.T) {
|
|||||||
used_global_cycle_traps :: proc(t: ^testing.T) {
|
used_global_cycle_traps :: proc(t: ^testing.T) {
|
||||||
output := "/tmp/brolang-test-cycle-used"
|
output := "/tmp/brolang-test-cycle-used"
|
||||||
defer _ = os.remove(output)
|
defer _ = os.remove(output)
|
||||||
status := compiler_core.compile_file("examples/cycle_used.bro", output)
|
status := compiler_core.compile_package("examples/programs/cycle_used", output)
|
||||||
testing.expect_value(t, status, 1)
|
testing.expect_value(t, status, 1)
|
||||||
state := run_executable(output)
|
state := run_executable(output)
|
||||||
testing.expect(t, !state.success)
|
testing.expect(t, !state.success)
|
||||||
@@ -472,27 +717,37 @@ used_global_cycle_traps :: proc(t: ^testing.T) {
|
|||||||
malformed_typed_values_still_produce_executable :: proc(t: ^testing.T) {
|
malformed_typed_values_still_produce_executable :: proc(t: ^testing.T) {
|
||||||
output := "/tmp/brolang-test-malformed-typed"
|
output := "/tmp/brolang-test-malformed-typed"
|
||||||
defer _ = os.remove(output)
|
defer _ = os.remove(output)
|
||||||
status := compiler_core.compile_file("examples/malformed_typed_recovery.bro", output)
|
status := compiler_core.compile_package("examples/programs/malformed_typed_recovery", output)
|
||||||
testing.expect_value(t, status, 1)
|
testing.expect_value(t, status, 1)
|
||||||
state := run_executable(output)
|
state := run_executable(output)
|
||||||
testing.expect(t, !state.success)
|
testing.expect(t, !state.success)
|
||||||
}
|
}
|
||||||
|
|
||||||
@(test)
|
@(test)
|
||||||
unused_invalid_function_is_diagnosed_but_not_reached :: proc(t: ^testing.T) {
|
unused_invalid_function_body_is_not_checked :: proc(t: ^testing.T) {
|
||||||
output := "/tmp/brolang-test-invalid-unused-function"
|
output := "/tmp/brolang-test-invalid-unused-function"
|
||||||
defer _ = os.remove(output)
|
defer _ = os.remove(output)
|
||||||
status := compiler_core.compile_file("examples/invalid_unused_function.bro", output)
|
status := compiler_core.compile_package("examples/programs/invalid_unused_function", output)
|
||||||
testing.expect_value(t, status, 1)
|
testing.expect_value(t, status, 0)
|
||||||
state := run_executable(output)
|
state := run_executable(output)
|
||||||
testing.expect_value(t, state.exit_code, 0)
|
testing.expect_value(t, state.exit_code, 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@(test)
|
||||||
|
used_invalid_function_is_diagnosed_and_traps :: proc(t: ^testing.T) {
|
||||||
|
output := "/tmp/brolang-test-invalid-used-function"
|
||||||
|
defer _ = os.remove(output)
|
||||||
|
status := compiler_core.compile_package("examples/programs/invalid_used_function", output)
|
||||||
|
testing.expect_value(t, status, 1)
|
||||||
|
state := run_executable(output)
|
||||||
|
testing.expect(t, !state.success)
|
||||||
|
}
|
||||||
|
|
||||||
@(test)
|
@(test)
|
||||||
function_mediated_problematic_global_is_deferred :: proc(t: ^testing.T) {
|
function_mediated_problematic_global_is_deferred :: proc(t: ^testing.T) {
|
||||||
output := "/tmp/brolang-test-function-global-unused"
|
output := "/tmp/brolang-test-function-global-unused"
|
||||||
defer _ = os.remove(output)
|
defer _ = os.remove(output)
|
||||||
status := compiler_core.compile_file("examples/function_global_unused.bro", output)
|
status := compiler_core.compile_package("examples/programs/function_global_unused", output)
|
||||||
testing.expect_value(t, status, 1)
|
testing.expect_value(t, status, 1)
|
||||||
state := run_executable(output)
|
state := run_executable(output)
|
||||||
testing.expect_value(t, state.exit_code, 0)
|
testing.expect_value(t, state.exit_code, 0)
|
||||||
@@ -502,8 +757,349 @@ function_mediated_problematic_global_is_deferred :: proc(t: ^testing.T) {
|
|||||||
function_mediated_problematic_global_traps_when_used :: proc(t: ^testing.T) {
|
function_mediated_problematic_global_traps_when_used :: proc(t: ^testing.T) {
|
||||||
output := "/tmp/brolang-test-function-global-used"
|
output := "/tmp/brolang-test-function-global-used"
|
||||||
defer _ = os.remove(output)
|
defer _ = os.remove(output)
|
||||||
status := compiler_core.compile_file("examples/function_global_used.bro", output)
|
status := compiler_core.compile_package("examples/programs/function_global_used", output)
|
||||||
testing.expect_value(t, status, 1)
|
testing.expect_value(t, status, 1)
|
||||||
state := run_executable(output)
|
state := run_executable(output)
|
||||||
testing.expect(t, !state.success)
|
testing.expect(t, !state.success)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@(test)
|
||||||
|
package_files_merge_and_qualified_imports_run :: proc(t: ^testing.T) {
|
||||||
|
output := "/tmp/brolang-test-package-basic"
|
||||||
|
defer _ = os.remove(output)
|
||||||
|
status := compiler_core.compile_package("examples/packages/basic/app", output)
|
||||||
|
testing.expect_value(t, status, 0)
|
||||||
|
state := run_executable(output)
|
||||||
|
testing.expect_value(t, state.exit_code, 3)
|
||||||
|
}
|
||||||
|
|
||||||
|
@(test)
|
||||||
|
unused_import_is_diagnosed_but_remains_executable :: proc(t: ^testing.T) {
|
||||||
|
output := "/tmp/brolang-test-package-unused"
|
||||||
|
defer _ = os.remove(output)
|
||||||
|
status := compiler_core.compile_package("examples/packages/unused/app", output)
|
||||||
|
testing.expect_value(t, status, 1)
|
||||||
|
state := run_executable(output)
|
||||||
|
testing.expect_value(t, state.exit_code, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
@(test)
|
||||||
|
unused_missing_package_does_not_trap :: proc(t: ^testing.T) {
|
||||||
|
output := "/tmp/brolang-test-package-missing-unused"
|
||||||
|
defer _ = os.remove(output)
|
||||||
|
status := compiler_core.compile_package("examples/packages/missing_unused/app", output)
|
||||||
|
testing.expect_value(t, status, 1)
|
||||||
|
state := run_executable(output)
|
||||||
|
testing.expect_value(t, state.exit_code, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
@(test)
|
||||||
|
referenced_missing_package_traps_at_reference :: proc(t: ^testing.T) {
|
||||||
|
output := "/tmp/brolang-test-package-missing-used"
|
||||||
|
defer _ = os.remove(output)
|
||||||
|
status := compiler_core.compile_package("examples/packages/missing_used/app", output)
|
||||||
|
testing.expect_value(t, status, 1)
|
||||||
|
state, stdout, stderr, _ := os2.process_exec(
|
||||||
|
os2.Process_Desc{command=[]string{output}},
|
||||||
|
context.allocator,
|
||||||
|
)
|
||||||
|
defer delete(stdout)
|
||||||
|
defer delete(stderr)
|
||||||
|
testing.expect(t, !state.success)
|
||||||
|
testing.expect(t, strings.contains(string(stderr), "/missing_used/app/main.bro:4:6:"))
|
||||||
|
}
|
||||||
|
|
||||||
|
@(test)
|
||||||
|
imports_are_file_local :: proc(t: ^testing.T) {
|
||||||
|
output := "/tmp/brolang-test-package-file-local"
|
||||||
|
defer _ = os.remove(output)
|
||||||
|
status := compiler_core.compile_package("examples/packages/file_local/app", output)
|
||||||
|
testing.expect_value(t, status, 1)
|
||||||
|
state := run_executable(output)
|
||||||
|
testing.expect(t, !state.success)
|
||||||
|
}
|
||||||
|
|
||||||
|
@(test)
|
||||||
|
package_import_cycles_are_valid :: proc(t: ^testing.T) {
|
||||||
|
output := "/tmp/brolang-test-package-cycle"
|
||||||
|
defer _ = os.remove(output)
|
||||||
|
status := compiler_core.compile_package("examples/packages/cycle/app", output)
|
||||||
|
testing.expect_value(t, status, 0)
|
||||||
|
state := run_executable(output)
|
||||||
|
testing.expect_value(t, state.exit_code, 4)
|
||||||
|
}
|
||||||
|
|
||||||
|
@(test)
|
||||||
|
imported_main_is_an_ordinary_callable_function :: proc(t: ^testing.T) {
|
||||||
|
output := "/tmp/brolang-test-package-imported-main"
|
||||||
|
defer _ = os.remove(output)
|
||||||
|
status := compiler_core.compile_package("examples/packages/imported_main/app", output)
|
||||||
|
testing.expect_value(t, status, 0)
|
||||||
|
state := run_executable(output)
|
||||||
|
testing.expect_value(t, state.exit_code, 7)
|
||||||
|
}
|
||||||
|
|
||||||
|
@(test)
|
||||||
|
same_named_c_functions_in_packages_do_not_collide :: proc(t: ^testing.T) {
|
||||||
|
output := "/tmp/brolang-test-package-c-symbols"
|
||||||
|
defer _ = os.remove(output)
|
||||||
|
status := compiler_core.compile_package("examples/packages/c_symbols/app", output)
|
||||||
|
testing.expect_value(t, status, 0)
|
||||||
|
state := run_executable(output)
|
||||||
|
testing.expect_value(t, state.exit_code, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
@(test)
|
||||||
|
invalid_root_package_preserves_existing_output :: proc(t: ^testing.T) {
|
||||||
|
output := "/tmp/brolang-test-package-preserved-output"
|
||||||
|
defer _ = os.remove(output)
|
||||||
|
previous := "previous artifact"
|
||||||
|
testing.expect(t, os.write_entire_file(output, transmute([]byte)previous))
|
||||||
|
testing.expect_value(t, compiler_core.compile_package("examples/programs/prototype/main.bro", output), 2)
|
||||||
|
data, ok := os.read_entire_file(output)
|
||||||
|
defer delete(data)
|
||||||
|
testing.expect(t, ok)
|
||||||
|
testing.expect_value(t, string(data), previous)
|
||||||
|
}
|
||||||
|
|
||||||
|
@(test)
|
||||||
|
empty_root_package_is_an_infrastructure_error :: proc(t: ^testing.T) {
|
||||||
|
output := "/tmp/brolang-test-package-empty"
|
||||||
|
defer _ = os.remove(output)
|
||||||
|
testing.expect_value(t, compiler_core.compile_package("examples/packages/empty", output), 2)
|
||||||
|
testing.expect(t, !os.exists(output))
|
||||||
|
}
|
||||||
|
|
||||||
|
@(test)
|
||||||
|
same_package_can_be_imported_under_distinct_aliases :: proc(t: ^testing.T) {
|
||||||
|
output := "/tmp/brolang-test-package-aliases"
|
||||||
|
defer _ = os.remove(output)
|
||||||
|
status := compiler_core.compile_package("examples/packages/aliases/app", output)
|
||||||
|
testing.expect_value(t, status, 0)
|
||||||
|
state := run_executable(output)
|
||||||
|
testing.expect_value(t, state.exit_code, 4)
|
||||||
|
}
|
||||||
|
|
||||||
|
@(test)
|
||||||
|
duplicate_same_file_import_keeps_first_binding :: proc(t: ^testing.T) {
|
||||||
|
output := "/tmp/brolang-test-package-duplicate"
|
||||||
|
defer _ = os.remove(output)
|
||||||
|
status := compiler_core.compile_package("examples/packages/duplicate/app", output)
|
||||||
|
testing.expect_value(t, status, 1)
|
||||||
|
state := run_executable(output)
|
||||||
|
testing.expect_value(t, state.exit_code, 2)
|
||||||
|
}
|
||||||
|
|
||||||
|
@(test)
|
||||||
|
identical_imports_in_different_files_are_independent :: proc(t: ^testing.T) {
|
||||||
|
output := "/tmp/brolang-test-package-repeated"
|
||||||
|
defer _ = os.remove(output)
|
||||||
|
status := compiler_core.compile_package("examples/packages/repeated/app", output)
|
||||||
|
testing.expect_value(t, status, 0)
|
||||||
|
state := run_executable(output)
|
||||||
|
testing.expect_value(t, state.exit_code, 4)
|
||||||
|
}
|
||||||
|
|
||||||
|
@(test)
|
||||||
|
imports_do_not_reexport_members :: proc(t: ^testing.T) {
|
||||||
|
output := "/tmp/brolang-test-package-non-transitive"
|
||||||
|
defer _ = os.remove(output)
|
||||||
|
status := compiler_core.compile_package("examples/packages/non_transitive/app", output)
|
||||||
|
testing.expect_value(t, status, 1)
|
||||||
|
state := run_executable(output)
|
||||||
|
testing.expect(t, !state.success)
|
||||||
|
}
|
||||||
|
|
||||||
|
@(test)
|
||||||
|
self_import_via_dot_is_valid :: proc(t: ^testing.T) {
|
||||||
|
output := "/tmp/brolang-test-package-self"
|
||||||
|
defer _ = os.remove(output)
|
||||||
|
status := compiler_core.compile_package("examples/packages/self_import/app", output)
|
||||||
|
testing.expect_value(t, status, 0)
|
||||||
|
state := run_executable(output)
|
||||||
|
testing.expect_value(t, state.exit_code, 5)
|
||||||
|
}
|
||||||
|
|
||||||
|
@(test)
|
||||||
|
unused_absolute_import_is_diagnosed_without_trapping :: proc(t: ^testing.T) {
|
||||||
|
output := "/tmp/brolang-test-package-absolute"
|
||||||
|
defer _ = os.remove(output)
|
||||||
|
status := compiler_core.compile_package("examples/packages/absolute/app", output)
|
||||||
|
testing.expect_value(t, status, 1)
|
||||||
|
state := run_executable(output)
|
||||||
|
testing.expect_value(t, state.exit_code, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
@(test)
|
||||||
|
unreferenced_problematic_imported_global_is_deferred :: proc(t: ^testing.T) {
|
||||||
|
output := "/tmp/brolang-test-package-problematic-unused"
|
||||||
|
defer _ = os.remove(output)
|
||||||
|
status := compiler_core.compile_package("examples/packages/problematic_unused/app", output)
|
||||||
|
testing.expect_value(t, status, 1)
|
||||||
|
state := run_executable(output)
|
||||||
|
testing.expect_value(t, state.exit_code, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
@(test)
|
||||||
|
valid_eager_global_in_unused_package_still_initializes :: proc(t: ^testing.T) {
|
||||||
|
output := "/tmp/brolang-test-package-eager-unused"
|
||||||
|
defer _ = os.remove(output)
|
||||||
|
status := compiler_core.compile_package("examples/packages/eager_unused/app", output)
|
||||||
|
testing.expect_value(t, status, 1)
|
||||||
|
state := run_executable(output)
|
||||||
|
testing.expect(t, !state.success)
|
||||||
|
}
|
||||||
|
|
||||||
|
@(test)
|
||||||
|
cross_package_global_initialization_cycle_traps_when_used :: proc(t: ^testing.T) {
|
||||||
|
output := "/tmp/brolang-test-package-global-cycle"
|
||||||
|
defer _ = os.remove(output)
|
||||||
|
status := compiler_core.compile_package("examples/packages/global_cycle/app", output)
|
||||||
|
testing.expect_value(t, status, 1)
|
||||||
|
state := run_executable(output)
|
||||||
|
testing.expect(t, !state.success)
|
||||||
|
}
|
||||||
|
|
||||||
|
@(test)
|
||||||
|
cross_package_generic_specializes_from_folded_argument :: proc(t: ^testing.T) {
|
||||||
|
output := "/tmp/brolang-test-package-generic"
|
||||||
|
defer _ = os.remove(output)
|
||||||
|
status := compiler_core.compile_package("examples/packages/generic/app", output)
|
||||||
|
testing.expect_value(t, status, 0)
|
||||||
|
state := run_executable(output)
|
||||||
|
testing.expect_value(t, state.exit_code, 128)
|
||||||
|
}
|
||||||
|
|
||||||
|
@(test)
|
||||||
|
lazy_function_body_marks_import_as_used_without_resolving_it :: proc(t: ^testing.T) {
|
||||||
|
output := "/tmp/brolang-test-package-lazy-import"
|
||||||
|
defer _ = os.remove(output)
|
||||||
|
status := compiler_core.compile_package("examples/packages/lazy_import/app", output)
|
||||||
|
testing.expect_value(t, status, 0)
|
||||||
|
state := run_executable(output)
|
||||||
|
testing.expect_value(t, state.exit_code, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
@(test)
|
||||||
|
qualified_global_inference_ignores_same_named_local :: proc(t: ^testing.T) {
|
||||||
|
output := "/tmp/brolang-test-package-qualified-shadow"
|
||||||
|
defer _ = os.remove(output)
|
||||||
|
status := compiler_core.compile_package("examples/packages/qualified_shadow/app", output)
|
||||||
|
testing.expect_value(t, status, 0)
|
||||||
|
state := run_executable(output)
|
||||||
|
testing.expect_value(t, state.exit_code, 300)
|
||||||
|
}
|
||||||
|
|
||||||
|
@(test)
|
||||||
|
cross_package_recursive_specialization_reaches_fixed_point :: proc(t: ^testing.T) {
|
||||||
|
output := "/tmp/brolang-test-package-recursive"
|
||||||
|
defer _ = os.remove(output)
|
||||||
|
status := compiler_core.compile_package("examples/packages/recursive/app", output)
|
||||||
|
testing.expect_value(t, status, 0)
|
||||||
|
testing.expect(t, os.exists(output))
|
||||||
|
}
|
||||||
|
|
||||||
|
@(test)
|
||||||
|
imported_main_does_not_satisfy_root_main_requirement :: proc(t: ^testing.T) {
|
||||||
|
output := "/tmp/brolang-test-package-missing-root-main"
|
||||||
|
defer _ = os.remove(output)
|
||||||
|
status := compiler_core.compile_package("examples/packages/missing_root_main/app", output)
|
||||||
|
testing.expect_value(t, status, 1)
|
||||||
|
state := run_executable(output)
|
||||||
|
testing.expect(t, !state.success)
|
||||||
|
}
|
||||||
|
|
||||||
|
@(test)
|
||||||
|
package_llvm_is_deterministic_and_symbols_include_package_ids :: proc(t: ^testing.T) {
|
||||||
|
sources := source.init_store()
|
||||||
|
defer source.destroy_store(&sources)
|
||||||
|
diagnostics := source.init_store_diagnostics(&sources)
|
||||||
|
defer source.destroy_diagnostics(&diagnostics)
|
||||||
|
ast_module, loaded := loader.load("examples/packages/c_symbols/app", &sources, &diagnostics)
|
||||||
|
defer ast.destroy_module(&ast_module)
|
||||||
|
hir_module := checker.check(&ast_module, &diagnostics)
|
||||||
|
defer hir.destroy_module(&hir_module)
|
||||||
|
ir_module := lower.lower(&hir_module)
|
||||||
|
defer ir.destroy_module(&ir_module)
|
||||||
|
llvm_text := llvm.emit(&ir_module, &diagnostics)
|
||||||
|
defer delete(llvm_text)
|
||||||
|
second_llvm_text := llvm.emit(&ir_module, &diagnostics)
|
||||||
|
defer delete(second_llvm_text)
|
||||||
|
|
||||||
|
testing.expect(t, loaded)
|
||||||
|
testing.expect_value(t, len(diagnostics.items), 0)
|
||||||
|
testing.expect_value(t, llvm_text, second_llvm_text)
|
||||||
|
testing.expect(t, strings.contains(llvm_text, "define i8 @bro_c__p1__same"))
|
||||||
|
testing.expect(t, strings.contains(llvm_text, "define i8 @bro_c__p2__same"))
|
||||||
|
testing.expect(t, strings.contains(llvm_text, "define i32 @main()"))
|
||||||
|
}
|
||||||
|
|
||||||
|
@(test)
|
||||||
|
invalid_default_alias_requires_an_explicit_alias :: proc(t: ^testing.T) {
|
||||||
|
output := "/tmp/brolang-test-package-default-alias-invalid"
|
||||||
|
defer _ = os.remove(output)
|
||||||
|
status := compiler_core.compile_package("examples/packages/default_alias_invalid/app", output)
|
||||||
|
testing.expect_value(t, status, 1)
|
||||||
|
state := run_executable(output)
|
||||||
|
testing.expect_value(t, state.exit_code, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
@(test)
|
||||||
|
explicit_alias_allows_invalid_directory_basename :: proc(t: ^testing.T) {
|
||||||
|
output := "/tmp/brolang-test-package-explicit-alias"
|
||||||
|
defer _ = os.remove(output)
|
||||||
|
status := compiler_core.compile_package("examples/packages/explicit_alias/app", output)
|
||||||
|
testing.expect_value(t, status, 0)
|
||||||
|
state := run_executable(output)
|
||||||
|
testing.expect_value(t, state.exit_code, 3)
|
||||||
|
}
|
||||||
|
|
||||||
|
@(test)
|
||||||
|
import_alias_conflict_is_diagnosed_without_trapping :: proc(t: ^testing.T) {
|
||||||
|
output := "/tmp/brolang-test-package-alias-conflict"
|
||||||
|
defer _ = os.remove(output)
|
||||||
|
status := compiler_core.compile_package("examples/packages/alias_conflict/app", output)
|
||||||
|
testing.expect_value(t, status, 1)
|
||||||
|
state := run_executable(output)
|
||||||
|
testing.expect_value(t, state.exit_code, 7)
|
||||||
|
}
|
||||||
|
|
||||||
|
@(test)
|
||||||
|
empty_imported_package_is_a_source_diagnostic :: proc(t: ^testing.T) {
|
||||||
|
output := "/tmp/brolang-test-package-empty-import"
|
||||||
|
defer _ = os.remove(output)
|
||||||
|
status := compiler_core.compile_package("examples/packages/empty_import/app", output)
|
||||||
|
testing.expect_value(t, status, 1)
|
||||||
|
state := run_executable(output)
|
||||||
|
testing.expect_value(t, state.exit_code, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
@(test)
|
||||||
|
non_directory_import_is_a_source_diagnostic :: proc(t: ^testing.T) {
|
||||||
|
output := "/tmp/brolang-test-package-file-import"
|
||||||
|
defer _ = os.remove(output)
|
||||||
|
status := compiler_core.compile_package("examples/packages/file_import/app", output)
|
||||||
|
testing.expect_value(t, status, 1)
|
||||||
|
state := run_executable(output)
|
||||||
|
testing.expect_value(t, state.exit_code, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
@(test)
|
||||||
|
import_is_available_before_its_textual_declaration :: proc(t: ^testing.T) {
|
||||||
|
output := "/tmp/brolang-test-package-import-order"
|
||||||
|
defer _ = os.remove(output)
|
||||||
|
status := compiler_core.compile_package("examples/packages/import_order/app", output)
|
||||||
|
testing.expect_value(t, status, 0)
|
||||||
|
state := run_executable(output)
|
||||||
|
testing.expect_value(t, state.exit_code, 6)
|
||||||
|
}
|
||||||
|
|
||||||
|
@(test)
|
||||||
|
deeply_nested_child_packages_load_recursively :: proc(t: ^testing.T) {
|
||||||
|
output := "/tmp/brolang-test-package-deep"
|
||||||
|
defer _ = os.remove(output)
|
||||||
|
status := compiler_core.compile_package("examples/packages/deep/app", output)
|
||||||
|
testing.expect_value(t, status, 0)
|
||||||
|
state := run_executable(output)
|
||||||
|
testing.expect_value(t, state.exit_code, 9)
|
||||||
|
}
|
||||||
|
|||||||
@@ -0,0 +1,3 @@
|
|||||||
|
import "/definitely/not/a/brolang/package"
|
||||||
|
|
||||||
|
main :: func() void {}
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
math i32 :: 7
|
||||||
|
import "../math"
|
||||||
|
|
||||||
|
main :: func() i32 {
|
||||||
|
return math
|
||||||
|
}
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
value i32 :: 2
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
left :: import "../math"
|
||||||
|
right :: import "../math"
|
||||||
|
|
||||||
|
main :: func() i32 {
|
||||||
|
return left.value + right.value
|
||||||
|
}
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
value i32 :: 2
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
This non-Brolang file must not be loaded as part of the package.
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
import "../math"
|
||||||
|
|
||||||
|
main :: func() i32 {
|
||||||
|
return math.sum(local_value, math.value)
|
||||||
|
}
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
this is deliberately invalid and must not be loaded
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
local_value i32 :: 1
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
value i32 :: 2
|
||||||
|
|
||||||
|
sum :: func(a, b i32) i32 {
|
||||||
|
return a + b
|
||||||
|
}
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
import "../left"
|
||||||
|
import "../right"
|
||||||
|
|
||||||
|
main :: func() void {
|
||||||
|
_ = left.same()
|
||||||
|
_ = right.same()
|
||||||
|
}
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
same :: c func() int {
|
||||||
|
return 1
|
||||||
|
}
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
same :: c func() int {
|
||||||
|
return 2
|
||||||
|
}
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
import "../b"
|
||||||
|
|
||||||
|
seed i32 :: 4
|
||||||
|
|
||||||
|
run :: func() i32 {
|
||||||
|
return b.value
|
||||||
|
}
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
import "../a"
|
||||||
|
|
||||||
|
main :: func() i32 {
|
||||||
|
return a.run()
|
||||||
|
}
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
import "../a"
|
||||||
|
|
||||||
|
value i32 :: a.seed
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
value i32 :: 9
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
import "./level_three"
|
||||||
|
|
||||||
|
value :: func() i32 {
|
||||||
|
return level_three.value
|
||||||
|
}
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
import "./level_two"
|
||||||
|
|
||||||
|
value :: func() i32 {
|
||||||
|
return level_two.value()
|
||||||
|
}
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
import "./level_one"
|
||||||
|
|
||||||
|
main :: func() i32 {
|
||||||
|
return level_one.value()
|
||||||
|
}
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
import "../bad-name"
|
||||||
|
|
||||||
|
main :: func() void {}
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
value i32 :: 3
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
math :: import "../math"
|
||||||
|
math :: import "../math"
|
||||||
|
|
||||||
|
main :: func() i32 {
|
||||||
|
return math.value
|
||||||
|
}
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
value i32 :: 2
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
import "../eager"
|
||||||
|
|
||||||
|
main :: func() void {}
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
base i8 :: 127
|
||||||
|
overflow i8 :: base + 1
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
An empty root package has no immediate .bro files and is an infrastructure error.
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
import "../empty"
|
||||||
|
|
||||||
|
main :: func() void {}
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
This imported package intentionally has no .bro files.
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
good :: import "../bad-name"
|
||||||
|
|
||||||
|
main :: func() i32 {
|
||||||
|
return good.value
|
||||||
|
}
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
value i32 :: 3
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
import "../not_package.bro"
|
||||||
|
|
||||||
|
main :: func() void {}
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
main :: func() void {}
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
import "../math"
|
||||||
|
|
||||||
|
used_here :: func() int {
|
||||||
|
return math.value
|
||||||
|
}
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
not_imported_here :: func() int {
|
||||||
|
return math.value
|
||||||
|
}
|
||||||
|
|
||||||
|
main :: func() void {
|
||||||
|
_ = used_here()
|
||||||
|
_ = not_imported_here()
|
||||||
|
}
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
value :: 1
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
import "../math"
|
||||||
|
|
||||||
|
main :: func() i32 {
|
||||||
|
return math.identity(127 + 1)
|
||||||
|
}
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
identity :: func(value int) int {
|
||||||
|
return value
|
||||||
|
}
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
import "../b"
|
||||||
|
|
||||||
|
value i32 :: b.value
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
import "../a"
|
||||||
|
|
||||||
|
main :: func() void {
|
||||||
|
_ = a.value
|
||||||
|
}
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
import "../a"
|
||||||
|
|
||||||
|
value i32 :: a.value
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
main :: func() i32 {
|
||||||
|
return math.value
|
||||||
|
}
|
||||||
|
|
||||||
|
import "../math"
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
value i32 :: 6
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
import "../dep"
|
||||||
|
|
||||||
|
main :: func() i32 {
|
||||||
|
return dep.main()
|
||||||
|
}
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
main :: func() i32 {
|
||||||
|
return 7
|
||||||
|
}
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
import "../math"
|
||||||
|
|
||||||
|
unused :: func() int {
|
||||||
|
return math.missing
|
||||||
|
}
|
||||||
|
|
||||||
|
main :: func() void {}
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
value :: 7
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
import "../dep"
|
||||||
|
|
||||||
|
value :: dep.main()
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
main :: func() i32 {
|
||||||
|
return 1
|
||||||
|
}
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
import "../missing"
|
||||||
|
|
||||||
|
main :: func() void {}
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
import "../missing"
|
||||||
|
|
||||||
|
main :: func() void {
|
||||||
|
_ = missing.value
|
||||||
|
}
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
import "../bridge"
|
||||||
|
|
||||||
|
main :: func() void {
|
||||||
|
_ = bridge.value
|
||||||
|
}
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
import "../math"
|
||||||
|
|
||||||
|
read :: func() i32 {
|
||||||
|
return math.value
|
||||||
|
}
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
value i32 :: 2
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
import "../broken"
|
||||||
|
|
||||||
|
main :: func() void {}
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
bad = 1
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
import "../math"
|
||||||
|
|
||||||
|
read :: func(value i8) int {
|
||||||
|
return math.value
|
||||||
|
}
|
||||||
|
|
||||||
|
main :: func() i32 {
|
||||||
|
return read(1)
|
||||||
|
}
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
value i16 :: 300
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
import "../b"
|
||||||
|
|
||||||
|
one :: func(value int) i32 {
|
||||||
|
return b.two(value)
|
||||||
|
}
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
import "../a"
|
||||||
|
|
||||||
|
main :: func() i32 {
|
||||||
|
return a.one(1)
|
||||||
|
}
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
import "../a"
|
||||||
|
|
||||||
|
two :: func(value int) i32 {
|
||||||
|
return a.one(value)
|
||||||
|
}
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
import "../math"
|
||||||
|
|
||||||
|
from_a :: func() i32 {
|
||||||
|
return math.value
|
||||||
|
}
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
import "../math"
|
||||||
|
|
||||||
|
main :: func() i32 {
|
||||||
|
return from_a() + math.value
|
||||||
|
}
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
value i32 :: 2
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
self :: import "."
|
||||||
|
|
||||||
|
value i32 :: 5
|
||||||
|
|
||||||
|
main :: func() i32 {
|
||||||
|
return self.value
|
||||||
|
}
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
import "../math"
|
||||||
|
|
||||||
|
main :: func() void {}
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
value :: 1
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
broken :: func(value int) int {
|
||||||
|
return missing + value
|
||||||
|
}
|
||||||
|
|
||||||
|
main :: func() void {
|
||||||
|
_ = broken(1)
|
||||||
|
}
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
main :: func() i32 { return 7 }
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user