initial draft
This commit is contained in:
+13
@@ -0,0 +1,13 @@
|
|||||||
|
brolang
|
||||||
|
|
||||||
|
# examples
|
||||||
|
prototype
|
||||||
|
main_i32
|
||||||
|
cycle_used
|
||||||
|
cycle_unused
|
||||||
|
invalid_transitive_used_global
|
||||||
|
invalid_transitive_unused_global
|
||||||
|
runtime_global
|
||||||
|
missing_main
|
||||||
|
mutable_local
|
||||||
|
overflow
|
||||||
@@ -0,0 +1,36 @@
|
|||||||
|
# brolang
|
||||||
|
|
||||||
|
Prototype error-tolerant Brolang compiler written in Odin.
|
||||||
|
|
||||||
|
```sh
|
||||||
|
odin build . -out:brolang
|
||||||
|
./brolang examples/prototype.bro -o /tmp/prototype
|
||||||
|
/tmp/prototype
|
||||||
|
```
|
||||||
|
|
||||||
|
Compilation phases are isolated under `compiler/`:
|
||||||
|
|
||||||
|
```text
|
||||||
|
source -> lexer -> parser/AST -> checker/HIR -> lower/IR -> opt -> LLVM -> zig cc
|
||||||
|
```
|
||||||
|
|
||||||
|
Source diagnostics do not block executable generation. When recovery is
|
||||||
|
possible, invalid code lowers to runtime diagnostic traps and the compiler
|
||||||
|
returns status `1`. Infrastructure or backend failures return status `2`.
|
||||||
|
|
||||||
|
Current prototype features:
|
||||||
|
|
||||||
|
- Newline-terminated, multiline statements and `#` comments
|
||||||
|
- Immutable `::` bindings, mutable function-local `=` bindings, and `_` sinks
|
||||||
|
- `i8`, `i16`, `i32`, `i64`, and loose integer-constrained `int`
|
||||||
|
- Contextual integer constants and compile-time folding of literal addition trees
|
||||||
|
- Demand-monomorphized Brolang and C-ABI functions
|
||||||
|
- Checked signed addition
|
||||||
|
- Static, eager runtime, and deferred problematic globals
|
||||||
|
- Runtime diagnostics followed by `llvm.trap`
|
||||||
|
|
||||||
|
Compiler exit statuses:
|
||||||
|
|
||||||
|
- `0`: executable produced without source diagnostics
|
||||||
|
- `1`: executable produced with source diagnostics and embedded traps
|
||||||
|
- `2`: executable could not be produced
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
# "quick" fixes
|
||||||
|
|
||||||
|
- for global initialization cycles, report also starting and ending lines
|
||||||
|
|
||||||
|
# milestones
|
||||||
|
|
||||||
|
1. import system:
|
||||||
|
- 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
|
||||||
|
- create bindings from c headers
|
||||||
|
- find out how this should co-exist with the import system
|
||||||
@@ -0,0 +1,110 @@
|
|||||||
|
package ast
|
||||||
|
|
||||||
|
import "../source"
|
||||||
|
import "core:mem"
|
||||||
|
|
||||||
|
INVALID_ID :: -1
|
||||||
|
|
||||||
|
Type_Syntax :: enum {
|
||||||
|
Invalid,
|
||||||
|
Int,
|
||||||
|
I8,
|
||||||
|
I16,
|
||||||
|
I32,
|
||||||
|
I64,
|
||||||
|
Void,
|
||||||
|
}
|
||||||
|
|
||||||
|
Expr_Kind :: enum {
|
||||||
|
Invalid,
|
||||||
|
Integer,
|
||||||
|
Name,
|
||||||
|
Add,
|
||||||
|
Call,
|
||||||
|
}
|
||||||
|
|
||||||
|
Expr :: struct {
|
||||||
|
kind: Expr_Kind,
|
||||||
|
span: source.Span,
|
||||||
|
text: string,
|
||||||
|
integer: i64,
|
||||||
|
left: int,
|
||||||
|
right: int,
|
||||||
|
args: []int,
|
||||||
|
diagnostic: int,
|
||||||
|
}
|
||||||
|
|
||||||
|
Param :: struct {
|
||||||
|
name: string,
|
||||||
|
span: source.Span,
|
||||||
|
type: Type_Syntax,
|
||||||
|
}
|
||||||
|
|
||||||
|
Stmt_Kind :: enum {
|
||||||
|
Invalid,
|
||||||
|
Declaration,
|
||||||
|
Assignment,
|
||||||
|
Return,
|
||||||
|
Expression,
|
||||||
|
}
|
||||||
|
|
||||||
|
Stmt :: struct {
|
||||||
|
kind: Stmt_Kind,
|
||||||
|
span: source.Span,
|
||||||
|
name: string,
|
||||||
|
type: Type_Syntax,
|
||||||
|
immutable: bool,
|
||||||
|
expr: int,
|
||||||
|
diagnostic: int,
|
||||||
|
}
|
||||||
|
|
||||||
|
Function :: struct {
|
||||||
|
span: source.Span,
|
||||||
|
name: string,
|
||||||
|
c_abi: bool,
|
||||||
|
params: []Param,
|
||||||
|
result: Type_Syntax,
|
||||||
|
body: []int,
|
||||||
|
diagnostic: int,
|
||||||
|
}
|
||||||
|
|
||||||
|
Global :: struct {
|
||||||
|
span: source.Span,
|
||||||
|
name: string,
|
||||||
|
type: Type_Syntax,
|
||||||
|
immutable: bool,
|
||||||
|
expr: int,
|
||||||
|
diagnostic: int,
|
||||||
|
}
|
||||||
|
|
||||||
|
Module :: struct {
|
||||||
|
exprs: [dynamic]Expr,
|
||||||
|
statements: [dynamic]Stmt,
|
||||||
|
functions: [dynamic]Function,
|
||||||
|
globals: [dynamic]Global,
|
||||||
|
allocator: mem.Allocator,
|
||||||
|
}
|
||||||
|
|
||||||
|
init_module :: proc(allocator := context.allocator) -> Module {
|
||||||
|
module: Module
|
||||||
|
module.allocator = allocator
|
||||||
|
module.exprs.allocator = allocator
|
||||||
|
module.statements.allocator = allocator
|
||||||
|
module.functions.allocator = allocator
|
||||||
|
module.globals.allocator = allocator
|
||||||
|
return module
|
||||||
|
}
|
||||||
|
|
||||||
|
destroy_module :: proc(module: ^Module) {
|
||||||
|
for expr in module.exprs {
|
||||||
|
delete(expr.args, module.allocator)
|
||||||
|
}
|
||||||
|
for function in module.functions {
|
||||||
|
delete(function.params, module.allocator)
|
||||||
|
delete(function.body, module.allocator)
|
||||||
|
}
|
||||||
|
delete(module.exprs)
|
||||||
|
delete(module.statements)
|
||||||
|
delete(module.functions)
|
||||||
|
delete(module.globals)
|
||||||
|
}
|
||||||
@@ -0,0 +1,46 @@
|
|||||||
|
package backend
|
||||||
|
|
||||||
|
import "core:fmt"
|
||||||
|
import "core:os"
|
||||||
|
import "core:os/os2"
|
||||||
|
|
||||||
|
compile :: proc(llvm_path, output_path: string) -> bool {
|
||||||
|
pid := os2.get_pid()
|
||||||
|
temporary_output := fmt.tprintf("%s.brolang-tmp-%d", output_path, pid)
|
||||||
|
defer _ = os.remove(temporary_output)
|
||||||
|
|
||||||
|
command := []string{
|
||||||
|
"/usr/bin/env",
|
||||||
|
"ZIG_LOCAL_CACHE_DIR=/tmp/brolang-zig-cache",
|
||||||
|
"ZIG_GLOBAL_CACHE_DIR=/tmp/brolang-zig-global-cache",
|
||||||
|
"zig",
|
||||||
|
"cc",
|
||||||
|
"-Wno-override-module",
|
||||||
|
llvm_path,
|
||||||
|
"-o",
|
||||||
|
temporary_output,
|
||||||
|
}
|
||||||
|
state, stdout, stderr, err := os2.process_exec(
|
||||||
|
os2.Process_Desc{command=command},
|
||||||
|
context.allocator,
|
||||||
|
)
|
||||||
|
defer delete(stdout)
|
||||||
|
defer delete(stderr)
|
||||||
|
if len(stdout) > 0 {
|
||||||
|
fmt.print(string(stdout))
|
||||||
|
}
|
||||||
|
if len(stderr) > 0 {
|
||||||
|
fmt.eprint(string(stderr))
|
||||||
|
}
|
||||||
|
if err != nil || state.exit_code != 0 {
|
||||||
|
if err != nil {
|
||||||
|
fmt.eprintln("failed to execute zig cc:", err)
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if rename_err := os2.rename(temporary_output, output_path); rename_err != nil {
|
||||||
|
fmt.eprintln("failed to atomically replace output:", rename_err)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,80 @@
|
|||||||
|
package compiler
|
||||||
|
|
||||||
|
import "./backend"
|
||||||
|
import "./checker"
|
||||||
|
import "./lexer"
|
||||||
|
import "./llvm"
|
||||||
|
import "./lower"
|
||||||
|
import "./opt"
|
||||||
|
import "./parser"
|
||||||
|
import "./source"
|
||||||
|
import "core:fmt"
|
||||||
|
import vmem "core:mem/virtual"
|
||||||
|
import "core:os"
|
||||||
|
import "core:os/os2"
|
||||||
|
|
||||||
|
compile_file :: proc(input_path, output_path: string) -> int {
|
||||||
|
source_bytes, ok := os.read_entire_file(input_path)
|
||||||
|
if !ok {
|
||||||
|
fmt.eprintln("failed to read input:", input_path)
|
||||||
|
return 2
|
||||||
|
}
|
||||||
|
defer delete(source_bytes)
|
||||||
|
|
||||||
|
source_file := source.Source{path=input_path, text=string(source_bytes)}
|
||||||
|
diagnostics := source.init_diagnostics(&source_file)
|
||||||
|
defer source.destroy_diagnostics(&diagnostics)
|
||||||
|
|
||||||
|
lexer_arena: vmem.Arena
|
||||||
|
if err := vmem.arena_init_growing(&lexer_arena); err != nil {
|
||||||
|
fmt.eprintln("failed to initialize lexer arena:", err)
|
||||||
|
return 2
|
||||||
|
}
|
||||||
|
defer vmem.arena_destroy(&lexer_arena)
|
||||||
|
parser_arena: vmem.Arena
|
||||||
|
if err := vmem.arena_init_growing(&parser_arena); err != nil {
|
||||||
|
fmt.eprintln("failed to initialize parser arena:", err)
|
||||||
|
return 2
|
||||||
|
}
|
||||||
|
defer vmem.arena_destroy(&parser_arena)
|
||||||
|
checker_arena: vmem.Arena
|
||||||
|
if err := vmem.arena_init_growing(&checker_arena); err != nil {
|
||||||
|
fmt.eprintln("failed to initialize checker arena:", err)
|
||||||
|
return 2
|
||||||
|
}
|
||||||
|
defer vmem.arena_destroy(&checker_arena)
|
||||||
|
lower_arena: vmem.Arena
|
||||||
|
if err := vmem.arena_init_growing(&lower_arena); err != nil {
|
||||||
|
fmt.eprintln("failed to initialize lowering arena:", err)
|
||||||
|
return 2
|
||||||
|
}
|
||||||
|
defer vmem.arena_destroy(&lower_arena)
|
||||||
|
|
||||||
|
tokens := lexer.lex(&source_file, &diagnostics, vmem.arena_allocator(&lexer_arena))
|
||||||
|
ast_module := parser.parse(&tokens, &diagnostics, vmem.arena_allocator(&parser_arena))
|
||||||
|
vmem.arena_free_all(&lexer_arena)
|
||||||
|
hir_module := checker.check(&ast_module, &diagnostics, vmem.arena_allocator(&checker_arena))
|
||||||
|
vmem.arena_free_all(&parser_arena)
|
||||||
|
ir_module := lower.lower(&hir_module, vmem.arena_allocator(&lower_arena))
|
||||||
|
vmem.arena_free_all(&checker_arena)
|
||||||
|
opt.run(&ir_module)
|
||||||
|
|
||||||
|
llvm_text := llvm.emit(&ir_module, &diagnostics)
|
||||||
|
defer delete(llvm_text)
|
||||||
|
vmem.arena_free_all(&lower_arena)
|
||||||
|
llvm_path := fmt.tprintf("%s.brolang-%d.ll", output_path, os2.get_pid())
|
||||||
|
defer _ = os.remove(llvm_path)
|
||||||
|
if err := os.write_entire_file_or_err(llvm_path, transmute([]byte)llvm_text); err != nil {
|
||||||
|
fmt.eprintln("failed to write temporary LLVM IR:", err)
|
||||||
|
return 2
|
||||||
|
}
|
||||||
|
|
||||||
|
source.print_all(&diagnostics)
|
||||||
|
if !backend.compile(llvm_path, output_path) {
|
||||||
|
return 2
|
||||||
|
}
|
||||||
|
if len(diagnostics.items) > 0 {
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
return 0
|
||||||
|
}
|
||||||
@@ -0,0 +1,121 @@
|
|||||||
|
package hir
|
||||||
|
|
||||||
|
import "../source"
|
||||||
|
import "../types"
|
||||||
|
import "core:mem"
|
||||||
|
|
||||||
|
INVALID_ID :: -1
|
||||||
|
|
||||||
|
Expr_Kind :: enum {
|
||||||
|
Invalid,
|
||||||
|
Integer,
|
||||||
|
Local,
|
||||||
|
Global,
|
||||||
|
Widen,
|
||||||
|
Add,
|
||||||
|
Call,
|
||||||
|
}
|
||||||
|
|
||||||
|
Expr :: struct {
|
||||||
|
kind: Expr_Kind,
|
||||||
|
span: source.Span,
|
||||||
|
type: types.Type,
|
||||||
|
integer: i64,
|
||||||
|
target: int,
|
||||||
|
left: int,
|
||||||
|
right: int,
|
||||||
|
args: []int,
|
||||||
|
diagnostic: int,
|
||||||
|
}
|
||||||
|
|
||||||
|
Local :: struct {
|
||||||
|
name: string,
|
||||||
|
type: types.Type,
|
||||||
|
mutable: bool,
|
||||||
|
parameter: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
Stmt_Kind :: enum {
|
||||||
|
Declaration,
|
||||||
|
Assignment,
|
||||||
|
Return,
|
||||||
|
Expression,
|
||||||
|
Sink,
|
||||||
|
Trap,
|
||||||
|
}
|
||||||
|
|
||||||
|
Stmt :: struct {
|
||||||
|
kind: Stmt_Kind,
|
||||||
|
span: source.Span,
|
||||||
|
local: int,
|
||||||
|
expr: int,
|
||||||
|
diagnostic: int,
|
||||||
|
}
|
||||||
|
|
||||||
|
Function :: struct {
|
||||||
|
name: string,
|
||||||
|
link_name: string,
|
||||||
|
c_abi: bool,
|
||||||
|
is_main: bool,
|
||||||
|
params: []int,
|
||||||
|
result: types.Type,
|
||||||
|
locals: []Local,
|
||||||
|
body: []int,
|
||||||
|
direct_global_reads: []int,
|
||||||
|
calls: []int,
|
||||||
|
problematic: bool,
|
||||||
|
diagnostic: int,
|
||||||
|
}
|
||||||
|
|
||||||
|
Global :: struct {
|
||||||
|
name: string,
|
||||||
|
type: types.Type,
|
||||||
|
expr: int,
|
||||||
|
static_value: i64,
|
||||||
|
is_static: bool,
|
||||||
|
dependencies: []int,
|
||||||
|
calls: []int,
|
||||||
|
direct_problem: bool,
|
||||||
|
problematic: bool,
|
||||||
|
diagnostic: int,
|
||||||
|
}
|
||||||
|
|
||||||
|
Module :: struct {
|
||||||
|
exprs: [dynamic]Expr,
|
||||||
|
statements: [dynamic]Stmt,
|
||||||
|
functions: [dynamic]Function,
|
||||||
|
globals: [dynamic]Global,
|
||||||
|
allocator: mem.Allocator,
|
||||||
|
}
|
||||||
|
|
||||||
|
init_module :: proc(allocator := context.allocator) -> Module {
|
||||||
|
module: Module
|
||||||
|
module.allocator = allocator
|
||||||
|
module.exprs.allocator = allocator
|
||||||
|
module.statements.allocator = allocator
|
||||||
|
module.functions.allocator = allocator
|
||||||
|
module.globals.allocator = allocator
|
||||||
|
return module
|
||||||
|
}
|
||||||
|
|
||||||
|
destroy_module :: proc(module: ^Module) {
|
||||||
|
for expr in module.exprs {
|
||||||
|
delete(expr.args, module.allocator)
|
||||||
|
}
|
||||||
|
for function in module.functions {
|
||||||
|
delete(function.link_name, module.allocator)
|
||||||
|
delete(function.params, module.allocator)
|
||||||
|
delete(function.locals, module.allocator)
|
||||||
|
delete(function.body, module.allocator)
|
||||||
|
delete(function.direct_global_reads, module.allocator)
|
||||||
|
delete(function.calls, module.allocator)
|
||||||
|
}
|
||||||
|
for global in module.globals {
|
||||||
|
delete(global.dependencies, module.allocator)
|
||||||
|
delete(global.calls, module.allocator)
|
||||||
|
}
|
||||||
|
delete(module.exprs)
|
||||||
|
delete(module.statements)
|
||||||
|
delete(module.functions)
|
||||||
|
delete(module.globals)
|
||||||
|
}
|
||||||
@@ -0,0 +1,91 @@
|
|||||||
|
package ir
|
||||||
|
|
||||||
|
import "../source"
|
||||||
|
import "../types"
|
||||||
|
import "core:mem"
|
||||||
|
|
||||||
|
INVALID_ID :: -1
|
||||||
|
|
||||||
|
Opcode :: enum {
|
||||||
|
Param,
|
||||||
|
Const,
|
||||||
|
Load_Global,
|
||||||
|
Alloca,
|
||||||
|
Load,
|
||||||
|
Store,
|
||||||
|
Widen,
|
||||||
|
Add_Checked,
|
||||||
|
Call,
|
||||||
|
Trap,
|
||||||
|
Return,
|
||||||
|
Return_Void,
|
||||||
|
}
|
||||||
|
|
||||||
|
Instruction :: struct {
|
||||||
|
op: Opcode,
|
||||||
|
span: source.Span,
|
||||||
|
type: types.Type,
|
||||||
|
integer: i64,
|
||||||
|
target: int,
|
||||||
|
a: int,
|
||||||
|
b: int,
|
||||||
|
args: []int,
|
||||||
|
diagnostic: int,
|
||||||
|
}
|
||||||
|
|
||||||
|
Function :: struct {
|
||||||
|
name: string,
|
||||||
|
link_name: string,
|
||||||
|
c_abi: bool,
|
||||||
|
is_main: bool,
|
||||||
|
param_types: []types.Type,
|
||||||
|
result: types.Type,
|
||||||
|
instructions: []Instruction,
|
||||||
|
problematic: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
Global :: struct {
|
||||||
|
name: string,
|
||||||
|
type: types.Type,
|
||||||
|
is_static: bool,
|
||||||
|
static_value: i64,
|
||||||
|
initializer: []Instruction,
|
||||||
|
problematic: bool,
|
||||||
|
diagnostic: int,
|
||||||
|
}
|
||||||
|
|
||||||
|
Module :: struct {
|
||||||
|
functions: [dynamic]Function,
|
||||||
|
globals: [dynamic]Global,
|
||||||
|
allocator: mem.Allocator,
|
||||||
|
}
|
||||||
|
|
||||||
|
init_module :: proc(allocator := context.allocator) -> Module {
|
||||||
|
module: Module
|
||||||
|
module.functions.allocator = allocator
|
||||||
|
module.globals.allocator = allocator
|
||||||
|
module.allocator = allocator
|
||||||
|
return module
|
||||||
|
}
|
||||||
|
|
||||||
|
destroy_instructions :: proc(instructions: []Instruction, allocator: mem.Allocator) {
|
||||||
|
for instruction in instructions {
|
||||||
|
delete(instruction.args, allocator)
|
||||||
|
}
|
||||||
|
delete(instructions, allocator)
|
||||||
|
}
|
||||||
|
|
||||||
|
destroy_module :: proc(module: ^Module) {
|
||||||
|
for function in module.functions {
|
||||||
|
delete(function.name, module.allocator)
|
||||||
|
delete(function.link_name, module.allocator)
|
||||||
|
delete(function.param_types, module.allocator)
|
||||||
|
destroy_instructions(function.instructions, module.allocator)
|
||||||
|
}
|
||||||
|
for global in module.globals {
|
||||||
|
delete(global.name, module.allocator)
|
||||||
|
destroy_instructions(global.initializer, module.allocator)
|
||||||
|
}
|
||||||
|
delete(module.functions)
|
||||||
|
delete(module.globals)
|
||||||
|
}
|
||||||
@@ -0,0 +1,135 @@
|
|||||||
|
package lexer
|
||||||
|
|
||||||
|
import "../source"
|
||||||
|
import "../token"
|
||||||
|
|
||||||
|
is_identifier_start :: proc(value: byte) -> bool {
|
||||||
|
return value == '_' || value >= 'a' && value <= 'z' || value >= 'A' && value <= 'Z'
|
||||||
|
}
|
||||||
|
|
||||||
|
is_identifier_continue :: proc(value: byte) -> bool {
|
||||||
|
return is_identifier_start(value) || value >= '0' && value <= '9'
|
||||||
|
}
|
||||||
|
|
||||||
|
keyword_kind :: proc(text: string) -> token.Kind {
|
||||||
|
switch text {
|
||||||
|
case "c": return .Keyword_C
|
||||||
|
case "func": return .Keyword_Func
|
||||||
|
case "return": return .Keyword_Return
|
||||||
|
case "void": return .Keyword_Void
|
||||||
|
case "int": return .Keyword_Int
|
||||||
|
case "i8": return .Keyword_I8
|
||||||
|
case "i16": return .Keyword_I16
|
||||||
|
case "i32": return .Keyword_I32
|
||||||
|
case "i64": return .Keyword_I64
|
||||||
|
case "_": return .Underscore
|
||||||
|
}
|
||||||
|
return .Identifier
|
||||||
|
}
|
||||||
|
|
||||||
|
append_token :: proc(
|
||||||
|
stream: ^token.Stream,
|
||||||
|
source_file: ^source.Source,
|
||||||
|
kind: token.Kind,
|
||||||
|
start, end: int,
|
||||||
|
diagnostic := -1,
|
||||||
|
) {
|
||||||
|
append(&stream.items, token.Token{
|
||||||
|
kind=kind,
|
||||||
|
span=source.Span{start=start, end=end},
|
||||||
|
text=source_file.text[start:end],
|
||||||
|
diagnostic=diagnostic,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
lex :: proc(
|
||||||
|
source_file: ^source.Source,
|
||||||
|
diagnostics: ^source.Diagnostics,
|
||||||
|
allocator := context.allocator,
|
||||||
|
) -> token.Stream {
|
||||||
|
stream: token.Stream
|
||||||
|
stream.items.allocator = allocator
|
||||||
|
bytes := transmute([]byte)source_file.text
|
||||||
|
cursor := 0
|
||||||
|
|
||||||
|
for cursor < len(bytes) {
|
||||||
|
value := bytes[cursor]
|
||||||
|
switch value {
|
||||||
|
case ' ', '\t', '\r':
|
||||||
|
cursor += 1
|
||||||
|
case '\n':
|
||||||
|
append_token(&stream, source_file, .Newline, cursor, cursor+1)
|
||||||
|
cursor += 1
|
||||||
|
case '#':
|
||||||
|
for cursor < len(bytes) && bytes[cursor] != '\n' {
|
||||||
|
cursor += 1
|
||||||
|
}
|
||||||
|
case ':':
|
||||||
|
start := cursor
|
||||||
|
cursor += 1
|
||||||
|
if cursor < len(bytes) && bytes[cursor] == ':' {
|
||||||
|
cursor += 1
|
||||||
|
append_token(&stream, source_file, .Colon_Colon, start, cursor)
|
||||||
|
} else {
|
||||||
|
id := source.add(diagnostics, source.Span{start=start, end=cursor}, "expected a second ':'")
|
||||||
|
append_token(&stream, source_file, .Invalid, start, cursor, id)
|
||||||
|
}
|
||||||
|
case '=':
|
||||||
|
append_token(&stream, source_file, .Equal, cursor, cursor+1)
|
||||||
|
cursor += 1
|
||||||
|
case '+':
|
||||||
|
append_token(&stream, source_file, .Plus, cursor, cursor+1)
|
||||||
|
cursor += 1
|
||||||
|
case '(':
|
||||||
|
append_token(&stream, source_file, .Left_Paren, cursor, cursor+1)
|
||||||
|
cursor += 1
|
||||||
|
case ')':
|
||||||
|
append_token(&stream, source_file, .Right_Paren, cursor, cursor+1)
|
||||||
|
cursor += 1
|
||||||
|
case '{':
|
||||||
|
append_token(&stream, source_file, .Left_Brace, cursor, cursor+1)
|
||||||
|
cursor += 1
|
||||||
|
case '}':
|
||||||
|
append_token(&stream, source_file, .Right_Brace, cursor, cursor+1)
|
||||||
|
cursor += 1
|
||||||
|
case ',':
|
||||||
|
append_token(&stream, source_file, .Comma, cursor, cursor+1)
|
||||||
|
cursor += 1
|
||||||
|
case ';':
|
||||||
|
id := source.add(
|
||||||
|
diagnostics,
|
||||||
|
source.Span{start=cursor, end=cursor+1},
|
||||||
|
"semicolons are invalid; terminate statements with a newline",
|
||||||
|
)
|
||||||
|
append_token(&stream, source_file, .Invalid, cursor, cursor+1, id)
|
||||||
|
cursor += 1
|
||||||
|
case:
|
||||||
|
if value >= '0' && value <= '9' {
|
||||||
|
start := cursor
|
||||||
|
for cursor < len(bytes) && bytes[cursor] >= '0' && bytes[cursor] <= '9' {
|
||||||
|
cursor += 1
|
||||||
|
}
|
||||||
|
append_token(&stream, source_file, .Integer, start, cursor)
|
||||||
|
} else if is_identifier_start(value) {
|
||||||
|
start := cursor
|
||||||
|
for cursor < len(bytes) && is_identifier_continue(bytes[cursor]) {
|
||||||
|
cursor += 1
|
||||||
|
}
|
||||||
|
text := source_file.text[start:cursor]
|
||||||
|
append_token(&stream, source_file, keyword_kind(text), start, cursor)
|
||||||
|
} else {
|
||||||
|
id := source.addf(
|
||||||
|
diagnostics,
|
||||||
|
source.Span{start=cursor, end=cursor+1},
|
||||||
|
"invalid source byte 0x%02x",
|
||||||
|
value,
|
||||||
|
)
|
||||||
|
append_token(&stream, source_file, .Invalid, cursor, cursor+1, id)
|
||||||
|
cursor += 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
append_token(&stream, source_file, .Eof, len(bytes), len(bytes))
|
||||||
|
return stream
|
||||||
|
}
|
||||||
@@ -0,0 +1,389 @@
|
|||||||
|
package llvm
|
||||||
|
|
||||||
|
import "../ir"
|
||||||
|
import "../source"
|
||||||
|
import "../types"
|
||||||
|
import "core:fmt"
|
||||||
|
import "core:mem"
|
||||||
|
import "core:strings"
|
||||||
|
|
||||||
|
Trap_Message :: struct {
|
||||||
|
text: string,
|
||||||
|
}
|
||||||
|
|
||||||
|
Emitter :: struct {
|
||||||
|
module: ^ir.Module,
|
||||||
|
diagnostics: ^source.Diagnostics,
|
||||||
|
builder: strings.Builder,
|
||||||
|
messages: [dynamic]Trap_Message,
|
||||||
|
allocator: mem.Allocator,
|
||||||
|
}
|
||||||
|
|
||||||
|
llvm_type :: proc(value: types.Type) -> string {
|
||||||
|
if value.kind == .Void {
|
||||||
|
return "void"
|
||||||
|
}
|
||||||
|
switch value.bits {
|
||||||
|
case 8: return "i8"
|
||||||
|
case 16: return "i16"
|
||||||
|
case 32: return "i32"
|
||||||
|
case: return "i64"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function_result_type :: proc(function: ir.Function) -> string {
|
||||||
|
if function.is_main {
|
||||||
|
return "i32"
|
||||||
|
}
|
||||||
|
return llvm_type(function.result)
|
||||||
|
}
|
||||||
|
|
||||||
|
write_operand :: proc(builder: ^strings.Builder, instructions: []ir.Instruction, value_id: int) {
|
||||||
|
if value_id < 0 || value_id >= len(instructions) {
|
||||||
|
fmt.sbprintf(builder, "-6148914691236517206")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
value := instructions[value_id]
|
||||||
|
if value.op == .Const {
|
||||||
|
fmt.sbprintf(builder, "%d", value.integer)
|
||||||
|
} else {
|
||||||
|
fmt.sbprintf(builder, "%%v%d", value_id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
register_message :: proc(emitter: ^Emitter, text: string) -> int {
|
||||||
|
id := len(emitter.messages)
|
||||||
|
cloned := fmt.aprintf("%s\n", text, allocator=emitter.allocator)
|
||||||
|
append(&emitter.messages, Trap_Message{text=cloned})
|
||||||
|
return id
|
||||||
|
}
|
||||||
|
|
||||||
|
diagnostic_message :: proc(emitter: ^Emitter, diagnostic: int, span: source.Span, fallback: string) -> int {
|
||||||
|
if diagnostic >= 0 && diagnostic < len(emitter.diagnostics.items) {
|
||||||
|
message := source.format(emitter.diagnostics, diagnostic, emitter.allocator)
|
||||||
|
id := register_message(emitter, message)
|
||||||
|
delete(message, emitter.allocator)
|
||||||
|
return id
|
||||||
|
}
|
||||||
|
line, column := source.line_and_column(emitter.diagnostics.source, span.start)
|
||||||
|
message := fmt.aprintf(
|
||||||
|
"%s:%d:%d: runtime trap: %s",
|
||||||
|
emitter.diagnostics.source.path,
|
||||||
|
line,
|
||||||
|
column,
|
||||||
|
fallback,
|
||||||
|
allocator=emitter.allocator,
|
||||||
|
)
|
||||||
|
id := register_message(emitter, message)
|
||||||
|
delete(message, emitter.allocator)
|
||||||
|
return id
|
||||||
|
}
|
||||||
|
|
||||||
|
emit_trap_call :: proc(emitter: ^Emitter, message_id: int) {
|
||||||
|
message := emitter.messages[message_id]
|
||||||
|
fmt.sbprintf(
|
||||||
|
&emitter.builder,
|
||||||
|
" call void @bro.trap(ptr @bro.msg.%d, i64 %d)\n",
|
||||||
|
message_id,
|
||||||
|
len(message.text),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
emit_call_args :: proc(builder: ^strings.Builder, instructions: []ir.Instruction, args: []int) {
|
||||||
|
for arg, index in args {
|
||||||
|
if index > 0 {
|
||||||
|
strings.write_string(builder, ", ")
|
||||||
|
}
|
||||||
|
fmt.sbprintf(builder, "%s ", llvm_type(instructions[arg].type))
|
||||||
|
write_operand(builder, instructions, arg)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
emit_instruction_stream :: proc(
|
||||||
|
emitter: ^Emitter,
|
||||||
|
instructions: []ir.Instruction,
|
||||||
|
function: ir.Function,
|
||||||
|
global_initializer := false,
|
||||||
|
) -> int {
|
||||||
|
return_value := -1
|
||||||
|
after_return := false
|
||||||
|
for instruction, instruction_id in instructions {
|
||||||
|
if after_return {
|
||||||
|
fmt.sbprintf(&emitter.builder, "recover_after_return_%d:\n", instruction_id)
|
||||||
|
after_return = false
|
||||||
|
}
|
||||||
|
switch instruction.op {
|
||||||
|
case .Param, .Const:
|
||||||
|
case .Load_Global:
|
||||||
|
if instruction.target < 0 || instruction.target >= len(emitter.module.globals) {
|
||||||
|
message := diagnostic_message(emitter, -1, instruction.span, "invalid global reference")
|
||||||
|
emit_trap_call(emitter, message)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
global := emitter.module.globals[instruction.target]
|
||||||
|
if global.is_static {
|
||||||
|
fmt.sbprintf(
|
||||||
|
&emitter.builder,
|
||||||
|
" %%v%d = load %s, ptr @bro.g.%d\n",
|
||||||
|
instruction_id,
|
||||||
|
llvm_type(global.type),
|
||||||
|
instruction.target,
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
fmt.sbprintf(
|
||||||
|
&emitter.builder,
|
||||||
|
" %%v%d = call %s @bro.get.%d()\n",
|
||||||
|
instruction_id,
|
||||||
|
llvm_type(global.type),
|
||||||
|
instruction.target,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
case .Alloca:
|
||||||
|
fmt.sbprintf(&emitter.builder, " %%v%d = alloca %s\n", instruction_id, llvm_type(instruction.type))
|
||||||
|
case .Load:
|
||||||
|
fmt.sbprintf(
|
||||||
|
&emitter.builder,
|
||||||
|
" %%v%d = load %s, ptr %%v%d\n",
|
||||||
|
instruction_id,
|
||||||
|
llvm_type(instruction.type),
|
||||||
|
instruction.a,
|
||||||
|
)
|
||||||
|
case .Store:
|
||||||
|
fmt.sbprintf(&emitter.builder, " store %s ", llvm_type(instruction.type))
|
||||||
|
write_operand(&emitter.builder, instructions, instruction.b)
|
||||||
|
fmt.sbprintf(&emitter.builder, ", ptr %%v%d\n", instruction.a)
|
||||||
|
case .Widen:
|
||||||
|
from_type := instructions[instruction.a].type
|
||||||
|
fmt.sbprintf(&emitter.builder, " %%v%d = sext %s ", instruction_id, llvm_type(from_type))
|
||||||
|
write_operand(&emitter.builder, instructions, instruction.a)
|
||||||
|
fmt.sbprintf(&emitter.builder, " to %s\n", llvm_type(instruction.type))
|
||||||
|
case .Add_Checked:
|
||||||
|
type_name := llvm_type(instruction.type)
|
||||||
|
fmt.sbprintf(&emitter.builder, " %%pair%d = call ", instruction_id)
|
||||||
|
strings.write_string(&emitter.builder, "{ ")
|
||||||
|
fmt.sbprintf(&emitter.builder, "%s, i1 } @llvm.sadd.with.overflow.%s(%s ", type_name, type_name, type_name)
|
||||||
|
write_operand(&emitter.builder, instructions, instruction.a)
|
||||||
|
fmt.sbprintf(&emitter.builder, ", %s ", type_name)
|
||||||
|
write_operand(&emitter.builder, instructions, instruction.b)
|
||||||
|
fmt.sbprintf(&emitter.builder, ")\n")
|
||||||
|
fmt.sbprintf(&emitter.builder, " %%v%d = extractvalue ", instruction_id)
|
||||||
|
strings.write_string(&emitter.builder, "{ ")
|
||||||
|
fmt.sbprintf(&emitter.builder, "%s, i1 } %%pair%d, 0\n", type_name, instruction_id)
|
||||||
|
fmt.sbprintf(&emitter.builder, " %%overflow%d = extractvalue ", instruction_id)
|
||||||
|
strings.write_string(&emitter.builder, "{ ")
|
||||||
|
fmt.sbprintf(&emitter.builder, "%s, i1 } %%pair%d, 1\n", type_name, instruction_id)
|
||||||
|
fmt.sbprintf(
|
||||||
|
&emitter.builder,
|
||||||
|
" br i1 %%overflow%d, label %%overflow_trap%d, label %%overflow_continue%d\n",
|
||||||
|
instruction_id,
|
||||||
|
instruction_id,
|
||||||
|
instruction_id,
|
||||||
|
)
|
||||||
|
fmt.sbprintf(&emitter.builder, "overflow_trap%d:\n", instruction_id)
|
||||||
|
message := diagnostic_message(emitter, -1, instruction.span, "signed integer addition overflow")
|
||||||
|
emit_trap_call(emitter, message)
|
||||||
|
fmt.sbprintf(&emitter.builder, " unreachable\noverflow_continue%d:\n", instruction_id)
|
||||||
|
case .Call:
|
||||||
|
if instruction.target < 0 || instruction.target >= len(emitter.module.functions) {
|
||||||
|
message := diagnostic_message(emitter, -1, instruction.span, "invalid function specialization")
|
||||||
|
emit_trap_call(emitter, message)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
target := emitter.module.functions[instruction.target]
|
||||||
|
if instruction.type.kind != .Void {
|
||||||
|
fmt.sbprintf(&emitter.builder, " %%v%d = ", instruction_id)
|
||||||
|
} else {
|
||||||
|
strings.write_string(&emitter.builder, " ")
|
||||||
|
}
|
||||||
|
strings.write_string(&emitter.builder, "call ")
|
||||||
|
if !target.c_abi {
|
||||||
|
strings.write_string(&emitter.builder, "fastcc ")
|
||||||
|
}
|
||||||
|
fmt.sbprintf(&emitter.builder, "%s @%s(", function_result_type(target), target.link_name)
|
||||||
|
emit_call_args(&emitter.builder, instructions, instruction.args)
|
||||||
|
strings.write_string(&emitter.builder, ")\n")
|
||||||
|
case .Trap:
|
||||||
|
message := diagnostic_message(emitter, instruction.diagnostic, instruction.span, "invalid recovered source")
|
||||||
|
emit_trap_call(emitter, message)
|
||||||
|
case .Return:
|
||||||
|
if global_initializer {
|
||||||
|
return_value = instruction.a
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
fmt.sbprintf(&emitter.builder, " ret %s ", function_result_type(function))
|
||||||
|
write_operand(&emitter.builder, instructions, instruction.a)
|
||||||
|
strings.write_string(&emitter.builder, "\n")
|
||||||
|
after_return = true
|
||||||
|
case .Return_Void:
|
||||||
|
if global_initializer {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if function.is_main {
|
||||||
|
strings.write_string(&emitter.builder, " ret i32 0\n")
|
||||||
|
} else {
|
||||||
|
strings.write_string(&emitter.builder, " ret void\n")
|
||||||
|
}
|
||||||
|
after_return = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return return_value
|
||||||
|
}
|
||||||
|
|
||||||
|
emit_globals :: proc(emitter: ^Emitter) {
|
||||||
|
for global, global_id in emitter.module.globals {
|
||||||
|
if global.is_static {
|
||||||
|
fmt.sbprintf(
|
||||||
|
&emitter.builder,
|
||||||
|
"@bro.g.%d = internal constant %s %d\n",
|
||||||
|
global_id,
|
||||||
|
llvm_type(global.type),
|
||||||
|
global.static_value,
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
fmt.sbprintf(
|
||||||
|
&emitter.builder,
|
||||||
|
"@bro.g.%d = internal global %s 0\n@bro.gstate.%d = internal global i8 0\n",
|
||||||
|
global_id,
|
||||||
|
llvm_type(global.type),
|
||||||
|
global_id,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
strings.write_string(&emitter.builder, "\n")
|
||||||
|
}
|
||||||
|
|
||||||
|
emit_global_accessors :: proc(emitter: ^Emitter) {
|
||||||
|
placeholder_function := ir.Function{result=types.I64}
|
||||||
|
for global, global_id in emitter.module.globals {
|
||||||
|
if global.is_static {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
type_name := llvm_type(global.type)
|
||||||
|
fmt.sbprintf(&emitter.builder, "define internal %s @bro.get.%d() ", type_name, global_id)
|
||||||
|
strings.write_string(&emitter.builder, "{\nentry:\n")
|
||||||
|
fmt.sbprintf(
|
||||||
|
&emitter.builder,
|
||||||
|
" %%state = load i8, ptr @bro.gstate.%d\n %%done = icmp eq i8 %%state, 2\n br i1 %%done, label %%ready, label %%check\n",
|
||||||
|
global_id,
|
||||||
|
)
|
||||||
|
strings.write_string(&emitter.builder, "check:\n %visiting = icmp eq i8 %state, 1\n br i1 %visiting, label %cycle, label %initialize\ncycle:\n")
|
||||||
|
message_text := fmt.aprintf("runtime trap: global initialization cycle involving '%s'", global.name, allocator=emitter.allocator)
|
||||||
|
message := register_message(emitter, message_text)
|
||||||
|
delete(message_text, emitter.allocator)
|
||||||
|
emit_trap_call(emitter, message)
|
||||||
|
strings.write_string(&emitter.builder, " unreachable\ninitialize:\n")
|
||||||
|
fmt.sbprintf(&emitter.builder, " store i8 1, ptr @bro.gstate.%d\n", global_id)
|
||||||
|
placeholder_function.result = global.type
|
||||||
|
value := emit_instruction_stream(emitter, global.initializer, placeholder_function, true)
|
||||||
|
fmt.sbprintf(&emitter.builder, " store %s ", type_name)
|
||||||
|
write_operand(&emitter.builder, global.initializer, value)
|
||||||
|
fmt.sbprintf(&emitter.builder, ", ptr @bro.g.%d\n", global_id)
|
||||||
|
fmt.sbprintf(&emitter.builder, " store i8 2, ptr @bro.gstate.%d\n", global_id)
|
||||||
|
fmt.sbprintf(&emitter.builder, " ret %s ", type_name)
|
||||||
|
write_operand(&emitter.builder, global.initializer, value)
|
||||||
|
strings.write_string(&emitter.builder, "\nready:\n")
|
||||||
|
fmt.sbprintf(&emitter.builder, " %%value = load %s, ptr @bro.g.%d\n ret %s %%value\n}\n\n", type_name, global_id, type_name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
emit_constructor :: proc(emitter: ^Emitter) {
|
||||||
|
count := 0
|
||||||
|
for global in emitter.module.globals {
|
||||||
|
if !global.is_static && !global.problematic {
|
||||||
|
count += 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if count == 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
strings.write_string(
|
||||||
|
&emitter.builder,
|
||||||
|
"@llvm.global_ctors = appending global [1 x { i32, ptr, ptr }] [{ i32, ptr, ptr } { i32 65535, ptr @bro.init, ptr null }]\n\n",
|
||||||
|
)
|
||||||
|
strings.write_string(&emitter.builder, "define internal void @bro.init() {\nentry:\n")
|
||||||
|
for global, global_id in emitter.module.globals {
|
||||||
|
if !global.is_static && !global.problematic {
|
||||||
|
fmt.sbprintf(&emitter.builder, " %%g%d = call %s @bro.get.%d()\n", global_id, llvm_type(global.type), global_id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
strings.write_string(&emitter.builder, " ret void\n}\n\n")
|
||||||
|
}
|
||||||
|
|
||||||
|
emit_functions :: proc(emitter: ^Emitter) {
|
||||||
|
for function in emitter.module.functions {
|
||||||
|
strings.write_string(&emitter.builder, "define ")
|
||||||
|
if !function.c_abi {
|
||||||
|
strings.write_string(&emitter.builder, "internal fastcc ")
|
||||||
|
}
|
||||||
|
fmt.sbprintf(&emitter.builder, "%s @%s(", function_result_type(function), function.link_name)
|
||||||
|
for param_type, index in function.param_types {
|
||||||
|
if index > 0 {
|
||||||
|
strings.write_string(&emitter.builder, ", ")
|
||||||
|
}
|
||||||
|
fmt.sbprintf(&emitter.builder, "%s %%v%d", llvm_type(param_type), index)
|
||||||
|
}
|
||||||
|
strings.write_string(&emitter.builder, ") {\nentry:\n")
|
||||||
|
_ = emit_instruction_stream(emitter, function.instructions, function)
|
||||||
|
strings.write_string(&emitter.builder, "}\n\n")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
emit_escaped_bytes :: proc(builder: ^strings.Builder, text: string) {
|
||||||
|
for value in transmute([]byte)text {
|
||||||
|
if value >= 32 && value <= 126 && value != '\\' && value != '"' {
|
||||||
|
strings.write_byte(builder, value)
|
||||||
|
} else {
|
||||||
|
fmt.sbprintf(builder, "\\%02X", value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
emit_messages :: proc(emitter: ^Emitter) {
|
||||||
|
for message, message_id in emitter.messages {
|
||||||
|
fmt.sbprintf(&emitter.builder, "@bro.msg.%d = private unnamed_addr constant [%d x i8] c\"", message_id, len(message.text))
|
||||||
|
emit_escaped_bytes(&emitter.builder, message.text)
|
||||||
|
strings.write_string(&emitter.builder, "\"\n")
|
||||||
|
}
|
||||||
|
strings.write_string(&emitter.builder, "\n")
|
||||||
|
}
|
||||||
|
|
||||||
|
emit_declarations :: proc(emitter: ^Emitter) {
|
||||||
|
strings.write_string(&emitter.builder, "declare i64 @write(i32, ptr, i64)\ndeclare void @llvm.trap()\n")
|
||||||
|
widths := [?]int{8, 16, 32, 64}
|
||||||
|
for bits in widths {
|
||||||
|
strings.write_string(&emitter.builder, "declare { i")
|
||||||
|
fmt.sbprintf(&emitter.builder, "%d", bits)
|
||||||
|
strings.write_string(&emitter.builder, ", i1 } @llvm.sadd.with.overflow.i")
|
||||||
|
fmt.sbprintf(&emitter.builder, "%d(i%d, i%d)\n", bits, bits, bits)
|
||||||
|
}
|
||||||
|
strings.write_string(
|
||||||
|
&emitter.builder,
|
||||||
|
"\ndefine internal void @bro.trap(ptr %message, i64 %length) {\nentry:\n %written = call i64 @write(i32 2, ptr %message, i64 %length)\n call void @llvm.trap()\n unreachable\n}\n\n",
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
emit :: proc(module: ^ir.Module, diagnostics: ^source.Diagnostics, allocator := context.allocator) -> string {
|
||||||
|
emitter := Emitter{
|
||||||
|
module=module,
|
||||||
|
diagnostics=diagnostics,
|
||||||
|
builder=strings.builder_make(allocator),
|
||||||
|
allocator=allocator,
|
||||||
|
}
|
||||||
|
emitter.messages.allocator = allocator
|
||||||
|
defer {
|
||||||
|
for message in emitter.messages {
|
||||||
|
delete(message.text, allocator)
|
||||||
|
}
|
||||||
|
delete(emitter.messages)
|
||||||
|
strings.builder_destroy(&emitter.builder)
|
||||||
|
}
|
||||||
|
|
||||||
|
strings.write_string(&emitter.builder, "; generated by brolang\n\n")
|
||||||
|
emit_globals(&emitter)
|
||||||
|
emit_constructor(&emitter)
|
||||||
|
emit_global_accessors(&emitter)
|
||||||
|
emit_functions(&emitter)
|
||||||
|
emit_messages(&emitter)
|
||||||
|
emit_declarations(&emitter)
|
||||||
|
return fmt.aprintf("%s", strings.to_string(emitter.builder), allocator=allocator)
|
||||||
|
}
|
||||||
@@ -0,0 +1,356 @@
|
|||||||
|
package lower
|
||||||
|
|
||||||
|
import "../hir"
|
||||||
|
import "../ir"
|
||||||
|
import "../types"
|
||||||
|
import "core:fmt"
|
||||||
|
import "core:mem"
|
||||||
|
|
||||||
|
State :: struct {
|
||||||
|
hir_module: ^hir.Module,
|
||||||
|
instructions: [dynamic]ir.Instruction,
|
||||||
|
local_values: []int,
|
||||||
|
local_slots: []int,
|
||||||
|
allocator: mem.Allocator,
|
||||||
|
}
|
||||||
|
|
||||||
|
append_instruction :: proc(state: ^State, instruction: ir.Instruction) -> int {
|
||||||
|
id := len(state.instructions)
|
||||||
|
append(&state.instructions, instruction)
|
||||||
|
return id
|
||||||
|
}
|
||||||
|
|
||||||
|
clone_args :: proc(values: []int, allocator: mem.Allocator) -> []int {
|
||||||
|
result := make([]int, len(values), allocator)
|
||||||
|
copy(result, values)
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
sentinel :: proc(value_type: types.Type) -> i64 {
|
||||||
|
switch value_type.bits {
|
||||||
|
case 8: return -86
|
||||||
|
case 16: return -21846
|
||||||
|
case 32: return -1431655766
|
||||||
|
case: return -6148914691236517206
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
lower_expr :: proc(state: ^State, expr_id: int) -> int {
|
||||||
|
if expr_id < 0 || expr_id >= len(state.hir_module.exprs) {
|
||||||
|
trap := append_instruction(state, ir.Instruction{
|
||||||
|
op=.Trap,
|
||||||
|
type=types.VOID,
|
||||||
|
target=-1,
|
||||||
|
a=-1,
|
||||||
|
b=-1,
|
||||||
|
diagnostic=-1,
|
||||||
|
})
|
||||||
|
_ = trap
|
||||||
|
return append_instruction(state, ir.Instruction{
|
||||||
|
op=.Const,
|
||||||
|
type=types.I64,
|
||||||
|
integer=sentinel(types.I64),
|
||||||
|
target=-1,
|
||||||
|
a=-1,
|
||||||
|
b=-1,
|
||||||
|
diagnostic=-1,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
expr := state.hir_module.exprs[expr_id]
|
||||||
|
switch expr.kind {
|
||||||
|
case .Invalid:
|
||||||
|
append_instruction(state, ir.Instruction{
|
||||||
|
op=.Trap,
|
||||||
|
span=expr.span,
|
||||||
|
type=types.VOID,
|
||||||
|
target=-1,
|
||||||
|
a=-1,
|
||||||
|
b=-1,
|
||||||
|
diagnostic=expr.diagnostic,
|
||||||
|
})
|
||||||
|
fallback := expr.type
|
||||||
|
if !types.is_concrete_integer(fallback) {
|
||||||
|
fallback = types.I64
|
||||||
|
}
|
||||||
|
return append_instruction(state, ir.Instruction{
|
||||||
|
op=.Const,
|
||||||
|
span=expr.span,
|
||||||
|
type=fallback,
|
||||||
|
integer=sentinel(fallback),
|
||||||
|
target=-1,
|
||||||
|
a=-1,
|
||||||
|
b=-1,
|
||||||
|
diagnostic=-1,
|
||||||
|
})
|
||||||
|
case .Integer:
|
||||||
|
return append_instruction(state, ir.Instruction{
|
||||||
|
op=.Const,
|
||||||
|
span=expr.span,
|
||||||
|
type=expr.type,
|
||||||
|
integer=expr.integer,
|
||||||
|
target=-1,
|
||||||
|
a=-1,
|
||||||
|
b=-1,
|
||||||
|
diagnostic=-1,
|
||||||
|
})
|
||||||
|
case .Local:
|
||||||
|
if expr.target >= 0 && expr.target < len(state.local_slots) && state.local_slots[expr.target] >= 0 {
|
||||||
|
return append_instruction(state, ir.Instruction{
|
||||||
|
op=.Load,
|
||||||
|
span=expr.span,
|
||||||
|
type=expr.type,
|
||||||
|
target=-1,
|
||||||
|
a=state.local_slots[expr.target],
|
||||||
|
b=-1,
|
||||||
|
diagnostic=-1,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
if expr.target >= 0 && expr.target < len(state.local_values) {
|
||||||
|
return state.local_values[expr.target]
|
||||||
|
}
|
||||||
|
case .Global:
|
||||||
|
return append_instruction(state, ir.Instruction{
|
||||||
|
op=.Load_Global,
|
||||||
|
span=expr.span,
|
||||||
|
type=expr.type,
|
||||||
|
target=expr.target,
|
||||||
|
a=-1,
|
||||||
|
b=-1,
|
||||||
|
diagnostic=-1,
|
||||||
|
})
|
||||||
|
case .Widen:
|
||||||
|
value := lower_expr(state, expr.left)
|
||||||
|
return append_instruction(state, ir.Instruction{
|
||||||
|
op=.Widen,
|
||||||
|
span=expr.span,
|
||||||
|
type=expr.type,
|
||||||
|
target=-1,
|
||||||
|
a=value,
|
||||||
|
b=-1,
|
||||||
|
diagnostic=-1,
|
||||||
|
})
|
||||||
|
case .Add:
|
||||||
|
left := lower_expr(state, expr.left)
|
||||||
|
right := lower_expr(state, expr.right)
|
||||||
|
return append_instruction(state, ir.Instruction{
|
||||||
|
op=.Add_Checked,
|
||||||
|
span=expr.span,
|
||||||
|
type=expr.type,
|
||||||
|
target=-1,
|
||||||
|
a=left,
|
||||||
|
b=right,
|
||||||
|
diagnostic=-1,
|
||||||
|
})
|
||||||
|
case .Call:
|
||||||
|
args := make([]int, len(expr.args), state.allocator)
|
||||||
|
for arg, index in expr.args {
|
||||||
|
args[index] = lower_expr(state, arg)
|
||||||
|
}
|
||||||
|
return append_instruction(state, ir.Instruction{
|
||||||
|
op=.Call,
|
||||||
|
span=expr.span,
|
||||||
|
type=expr.type,
|
||||||
|
target=expr.target,
|
||||||
|
a=-1,
|
||||||
|
b=-1,
|
||||||
|
args=args,
|
||||||
|
diagnostic=-1,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
append_instruction(state, ir.Instruction{
|
||||||
|
op=.Trap,
|
||||||
|
span=expr.span,
|
||||||
|
type=types.VOID,
|
||||||
|
target=-1,
|
||||||
|
a=-1,
|
||||||
|
b=-1,
|
||||||
|
diagnostic=expr.diagnostic,
|
||||||
|
})
|
||||||
|
return append_instruction(state, ir.Instruction{
|
||||||
|
op=.Const,
|
||||||
|
span=expr.span,
|
||||||
|
type=types.I64,
|
||||||
|
integer=sentinel(types.I64),
|
||||||
|
target=-1,
|
||||||
|
a=-1,
|
||||||
|
b=-1,
|
||||||
|
diagnostic=-1,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
lower_body :: proc(hir_module: ^hir.Module, function: hir.Function, allocator: mem.Allocator) -> []ir.Instruction {
|
||||||
|
state := State{
|
||||||
|
hir_module=hir_module,
|
||||||
|
allocator=allocator,
|
||||||
|
local_values=make([]int, len(function.locals), allocator),
|
||||||
|
local_slots=make([]int, len(function.locals), allocator),
|
||||||
|
}
|
||||||
|
state.instructions.allocator = allocator
|
||||||
|
defer {
|
||||||
|
delete(state.local_values, allocator)
|
||||||
|
delete(state.local_slots, allocator)
|
||||||
|
}
|
||||||
|
for _, index in state.local_values {
|
||||||
|
state.local_values[index] = -1
|
||||||
|
state.local_slots[index] = -1
|
||||||
|
}
|
||||||
|
for local_id in function.params {
|
||||||
|
param := append_instruction(&state, ir.Instruction{
|
||||||
|
op=.Param,
|
||||||
|
type=function.locals[local_id].type,
|
||||||
|
target=local_id,
|
||||||
|
a=-1,
|
||||||
|
b=-1,
|
||||||
|
diagnostic=-1,
|
||||||
|
})
|
||||||
|
state.local_values[local_id] = param
|
||||||
|
}
|
||||||
|
|
||||||
|
for statement_id in function.body {
|
||||||
|
statement := hir_module.statements[statement_id]
|
||||||
|
switch statement.kind {
|
||||||
|
case .Declaration:
|
||||||
|
value := lower_expr(&state, statement.expr)
|
||||||
|
local := function.locals[statement.local]
|
||||||
|
if local.mutable {
|
||||||
|
slot := append_instruction(&state, ir.Instruction{
|
||||||
|
op=.Alloca,
|
||||||
|
span=statement.span,
|
||||||
|
type=local.type,
|
||||||
|
target=statement.local,
|
||||||
|
a=-1,
|
||||||
|
b=-1,
|
||||||
|
diagnostic=-1,
|
||||||
|
})
|
||||||
|
state.local_slots[statement.local] = slot
|
||||||
|
append_instruction(&state, ir.Instruction{
|
||||||
|
op=.Store,
|
||||||
|
span=statement.span,
|
||||||
|
type=local.type,
|
||||||
|
target=-1,
|
||||||
|
a=slot,
|
||||||
|
b=value,
|
||||||
|
diagnostic=-1,
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
state.local_values[statement.local] = value
|
||||||
|
}
|
||||||
|
case .Assignment:
|
||||||
|
value := lower_expr(&state, statement.expr)
|
||||||
|
slot := -1
|
||||||
|
if statement.local >= 0 && statement.local < len(state.local_slots) {
|
||||||
|
slot = state.local_slots[statement.local]
|
||||||
|
}
|
||||||
|
append_instruction(&state, ir.Instruction{
|
||||||
|
op=.Store,
|
||||||
|
span=statement.span,
|
||||||
|
type=function.locals[statement.local].type,
|
||||||
|
target=-1,
|
||||||
|
a=slot,
|
||||||
|
b=value,
|
||||||
|
diagnostic=-1,
|
||||||
|
})
|
||||||
|
case .Return:
|
||||||
|
if statement.expr < 0 {
|
||||||
|
append_instruction(&state, ir.Instruction{
|
||||||
|
op=.Return_Void,
|
||||||
|
span=statement.span,
|
||||||
|
type=types.VOID,
|
||||||
|
target=-1,
|
||||||
|
a=-1,
|
||||||
|
b=-1,
|
||||||
|
diagnostic=-1,
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
value := lower_expr(&state, statement.expr)
|
||||||
|
append_instruction(&state, ir.Instruction{
|
||||||
|
op=.Return,
|
||||||
|
span=statement.span,
|
||||||
|
type=function.result,
|
||||||
|
target=-1,
|
||||||
|
a=value,
|
||||||
|
b=-1,
|
||||||
|
diagnostic=-1,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
case .Expression, .Sink:
|
||||||
|
_ = lower_expr(&state, statement.expr)
|
||||||
|
case .Trap:
|
||||||
|
append_instruction(&state, ir.Instruction{
|
||||||
|
op=.Trap,
|
||||||
|
span=statement.span,
|
||||||
|
type=types.VOID,
|
||||||
|
target=-1,
|
||||||
|
a=-1,
|
||||||
|
b=-1,
|
||||||
|
diagnostic=statement.diagnostic,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if len(state.instructions) == 0 ||
|
||||||
|
(state.instructions[len(state.instructions)-1].op != .Return &&
|
||||||
|
state.instructions[len(state.instructions)-1].op != .Return_Void) {
|
||||||
|
if function.result.kind == .Void {
|
||||||
|
append_instruction(&state, ir.Instruction{op=.Return_Void, type=types.VOID, target=-1, a=-1, b=-1, diagnostic=-1})
|
||||||
|
} else {
|
||||||
|
value := append_instruction(&state, ir.Instruction{
|
||||||
|
op=.Const,
|
||||||
|
type=function.result,
|
||||||
|
integer=sentinel(function.result),
|
||||||
|
target=-1,
|
||||||
|
a=-1,
|
||||||
|
b=-1,
|
||||||
|
diagnostic=-1,
|
||||||
|
})
|
||||||
|
append_instruction(&state, ir.Instruction{op=.Return, type=function.result, target=-1, a=value, b=-1, diagnostic=-1})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return state.instructions[:]
|
||||||
|
}
|
||||||
|
|
||||||
|
lower_global_initializer :: proc(hir_module: ^hir.Module, global: hir.Global, allocator: mem.Allocator) -> []ir.Instruction {
|
||||||
|
state := State{hir_module=hir_module, allocator=allocator}
|
||||||
|
state.instructions.allocator = allocator
|
||||||
|
value := lower_expr(&state, global.expr)
|
||||||
|
append_instruction(&state, ir.Instruction{
|
||||||
|
op=.Return,
|
||||||
|
type=global.type,
|
||||||
|
target=-1,
|
||||||
|
a=value,
|
||||||
|
b=-1,
|
||||||
|
diagnostic=-1,
|
||||||
|
})
|
||||||
|
return state.instructions[:]
|
||||||
|
}
|
||||||
|
|
||||||
|
lower :: proc(hir_module: ^hir.Module, allocator := context.allocator) -> ir.Module {
|
||||||
|
module := ir.init_module(allocator)
|
||||||
|
for global in hir_module.globals {
|
||||||
|
append(&module.globals, ir.Global{
|
||||||
|
name=fmt.aprintf("%s", global.name, allocator=allocator),
|
||||||
|
type=global.type,
|
||||||
|
is_static=global.is_static,
|
||||||
|
static_value=global.static_value,
|
||||||
|
initializer=nil if global.is_static else lower_global_initializer(hir_module, global, allocator),
|
||||||
|
problematic=global.problematic,
|
||||||
|
diagnostic=global.diagnostic,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
for function in hir_module.functions {
|
||||||
|
param_types := make([]types.Type, len(function.params), allocator)
|
||||||
|
for local_id, index in function.params {
|
||||||
|
param_types[index] = function.locals[local_id].type
|
||||||
|
}
|
||||||
|
append(&module.functions, ir.Function{
|
||||||
|
name=fmt.aprintf("%s", function.name, allocator=allocator),
|
||||||
|
link_name=fmt.aprintf("%s", function.link_name, allocator=allocator),
|
||||||
|
c_abi=function.c_abi,
|
||||||
|
is_main=function.is_main,
|
||||||
|
param_types=param_types,
|
||||||
|
result=function.result,
|
||||||
|
instructions=lower_body(hir_module, function, allocator),
|
||||||
|
problematic=function.problematic,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return module
|
||||||
|
}
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
package opt
|
||||||
|
|
||||||
|
import "../ir"
|
||||||
|
|
||||||
|
Pass :: enum {
|
||||||
|
// Intentionally empty in v1. This enum is the stable optimization boundary.
|
||||||
|
}
|
||||||
|
|
||||||
|
run :: proc(module: ^ir.Module, passes: []Pass = nil) {
|
||||||
|
_ = module
|
||||||
|
_ = passes
|
||||||
|
}
|
||||||
@@ -0,0 +1,459 @@
|
|||||||
|
package parser
|
||||||
|
|
||||||
|
import "../ast"
|
||||||
|
import "../source"
|
||||||
|
import "../token"
|
||||||
|
import "core:strconv"
|
||||||
|
|
||||||
|
Parser :: struct {
|
||||||
|
tokens: ^token.Stream,
|
||||||
|
diagnostics: ^source.Diagnostics,
|
||||||
|
module: ast.Module,
|
||||||
|
cursor: int,
|
||||||
|
delimiter_depth: int,
|
||||||
|
}
|
||||||
|
|
||||||
|
current :: proc(parser: ^Parser) -> token.Token {
|
||||||
|
return parser.tokens.items[min(parser.cursor, len(parser.tokens.items)-1)]
|
||||||
|
}
|
||||||
|
|
||||||
|
previous :: proc(parser: ^Parser) -> token.Token {
|
||||||
|
return parser.tokens.items[max(parser.cursor-1, 0)]
|
||||||
|
}
|
||||||
|
|
||||||
|
advance :: proc(parser: ^Parser) -> token.Token {
|
||||||
|
result := current(parser)
|
||||||
|
if result.kind != .Eof {
|
||||||
|
parser.cursor += 1
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
allow :: proc(parser: ^Parser, kind: token.Kind) -> (token.Token, bool) {
|
||||||
|
if current(parser).kind == kind {
|
||||||
|
return advance(parser), true
|
||||||
|
}
|
||||||
|
return current(parser), false
|
||||||
|
}
|
||||||
|
|
||||||
|
skip_newlines :: proc(parser: ^Parser) {
|
||||||
|
for current(parser).kind == .Newline {
|
||||||
|
advance(parser)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
add_expr :: proc(parser: ^Parser, expr: ast.Expr) -> int {
|
||||||
|
id := len(parser.module.exprs)
|
||||||
|
append(&parser.module.exprs, expr)
|
||||||
|
return id
|
||||||
|
}
|
||||||
|
|
||||||
|
invalid_expr :: proc(parser: ^Parser, span: source.Span, message: string) -> int {
|
||||||
|
id := source.add(parser.diagnostics, span, message)
|
||||||
|
return add_expr(parser, ast.Expr{
|
||||||
|
kind=.Invalid,
|
||||||
|
span=span,
|
||||||
|
left=ast.INVALID_ID,
|
||||||
|
right=ast.INVALID_ID,
|
||||||
|
diagnostic=id,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
is_type_token :: proc(kind: token.Kind) -> bool {
|
||||||
|
#partial switch kind {
|
||||||
|
case .Keyword_Int, .Keyword_I8, .Keyword_I16, .Keyword_I32, .Keyword_I64, .Keyword_Void:
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
parse_type :: proc(parser: ^Parser) -> ast.Type_Syntax {
|
||||||
|
tok := current(parser)
|
||||||
|
#partial switch tok.kind {
|
||||||
|
case .Keyword_Int:
|
||||||
|
advance(parser)
|
||||||
|
return .Int
|
||||||
|
case .Keyword_I8:
|
||||||
|
advance(parser)
|
||||||
|
return .I8
|
||||||
|
case .Keyword_I16:
|
||||||
|
advance(parser)
|
||||||
|
return .I16
|
||||||
|
case .Keyword_I32:
|
||||||
|
advance(parser)
|
||||||
|
return .I32
|
||||||
|
case .Keyword_I64:
|
||||||
|
advance(parser)
|
||||||
|
return .I64
|
||||||
|
case .Keyword_Void:
|
||||||
|
advance(parser)
|
||||||
|
return .Void
|
||||||
|
}
|
||||||
|
source.add(parser.diagnostics, tok.span, "expected a type")
|
||||||
|
return .Invalid
|
||||||
|
}
|
||||||
|
|
||||||
|
parse_call :: proc(parser: ^Parser, name: token.Token) -> int {
|
||||||
|
left_paren := advance(parser)
|
||||||
|
parser.delimiter_depth += 1
|
||||||
|
defer parser.delimiter_depth -= 1
|
||||||
|
args: [dynamic]int
|
||||||
|
args.allocator = parser.module.allocator
|
||||||
|
skip_newlines(parser)
|
||||||
|
for current(parser).kind != .Right_Paren && current(parser).kind != .Eof {
|
||||||
|
append(&args, parse_expression(parser))
|
||||||
|
skip_newlines(parser)
|
||||||
|
if _, ok := allow(parser, .Comma); ok {
|
||||||
|
skip_newlines(parser)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
break
|
||||||
|
}
|
||||||
|
right_paren, ok := allow(parser, .Right_Paren)
|
||||||
|
if !ok {
|
||||||
|
source.add(parser.diagnostics, current(parser).span, "expected ')' after call arguments")
|
||||||
|
right_paren = left_paren
|
||||||
|
}
|
||||||
|
return add_expr(parser, ast.Expr{
|
||||||
|
kind=.Call,
|
||||||
|
span=source.Span{start=name.span.start, end=right_paren.span.end},
|
||||||
|
text=name.text,
|
||||||
|
args=args[:],
|
||||||
|
left=ast.INVALID_ID,
|
||||||
|
right=ast.INVALID_ID,
|
||||||
|
diagnostic=-1,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
parse_primary :: proc(parser: ^Parser) -> int {
|
||||||
|
tok := current(parser)
|
||||||
|
#partial switch tok.kind {
|
||||||
|
case .Integer:
|
||||||
|
advance(parser)
|
||||||
|
value, ok := strconv.parse_i64(tok.text)
|
||||||
|
if !ok {
|
||||||
|
return invalid_expr(parser, tok.span, "integer literal does not fit in i64")
|
||||||
|
}
|
||||||
|
return add_expr(parser, ast.Expr{
|
||||||
|
kind=.Integer,
|
||||||
|
span=tok.span,
|
||||||
|
integer=value,
|
||||||
|
left=ast.INVALID_ID,
|
||||||
|
right=ast.INVALID_ID,
|
||||||
|
diagnostic=-1,
|
||||||
|
})
|
||||||
|
case .Identifier:
|
||||||
|
advance(parser)
|
||||||
|
if current(parser).kind == .Left_Paren {
|
||||||
|
return parse_call(parser, tok)
|
||||||
|
}
|
||||||
|
return add_expr(parser, ast.Expr{
|
||||||
|
kind=.Name,
|
||||||
|
span=tok.span,
|
||||||
|
text=tok.text,
|
||||||
|
left=ast.INVALID_ID,
|
||||||
|
right=ast.INVALID_ID,
|
||||||
|
diagnostic=-1,
|
||||||
|
})
|
||||||
|
case .Underscore:
|
||||||
|
advance(parser)
|
||||||
|
return invalid_expr(parser, tok.span, "'_' is a write-only sink and cannot be read")
|
||||||
|
case .Left_Paren:
|
||||||
|
advance(parser)
|
||||||
|
parser.delimiter_depth += 1
|
||||||
|
defer parser.delimiter_depth -= 1
|
||||||
|
skip_newlines(parser)
|
||||||
|
expr := parse_expression(parser)
|
||||||
|
skip_newlines(parser)
|
||||||
|
if _, ok := allow(parser, .Right_Paren); !ok {
|
||||||
|
source.add(parser.diagnostics, current(parser).span, "expected ')'")
|
||||||
|
}
|
||||||
|
return expr
|
||||||
|
case .Invalid:
|
||||||
|
advance(parser)
|
||||||
|
return add_expr(parser, ast.Expr{
|
||||||
|
kind=.Invalid,
|
||||||
|
span=tok.span,
|
||||||
|
left=ast.INVALID_ID,
|
||||||
|
right=ast.INVALID_ID,
|
||||||
|
diagnostic=tok.diagnostic,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
if tok.kind != .Newline && tok.kind != .Right_Brace && tok.kind != .Eof {
|
||||||
|
advance(parser)
|
||||||
|
}
|
||||||
|
return invalid_expr(parser, tok.span, "expected an expression")
|
||||||
|
}
|
||||||
|
|
||||||
|
parse_expression :: proc(parser: ^Parser) -> int {
|
||||||
|
left := parse_primary(parser)
|
||||||
|
if parser.delimiter_depth > 0 {
|
||||||
|
skip_newlines(parser)
|
||||||
|
}
|
||||||
|
for current(parser).kind == .Plus {
|
||||||
|
advance(parser)
|
||||||
|
skip_newlines(parser)
|
||||||
|
right := parse_primary(parser)
|
||||||
|
left_expr := parser.module.exprs[left]
|
||||||
|
right_expr := parser.module.exprs[right]
|
||||||
|
left = add_expr(parser, ast.Expr{
|
||||||
|
kind=.Add,
|
||||||
|
span=source.Span{start=left_expr.span.start, end=right_expr.span.end},
|
||||||
|
left=left,
|
||||||
|
right=right,
|
||||||
|
diagnostic=-1,
|
||||||
|
})
|
||||||
|
if parser.delimiter_depth > 0 {
|
||||||
|
skip_newlines(parser)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return left
|
||||||
|
}
|
||||||
|
|
||||||
|
finish_statement :: proc(parser: ^Parser) -> int {
|
||||||
|
if current(parser).kind == .Newline {
|
||||||
|
skip_newlines(parser)
|
||||||
|
return -1
|
||||||
|
}
|
||||||
|
if current(parser).kind == .Eof {
|
||||||
|
return -1
|
||||||
|
}
|
||||||
|
diagnostic := source.add(
|
||||||
|
parser.diagnostics,
|
||||||
|
current(parser).span,
|
||||||
|
"completed statements must be followed by a newline",
|
||||||
|
)
|
||||||
|
for current(parser).kind != .Newline &&
|
||||||
|
current(parser).kind != .Right_Brace &&
|
||||||
|
current(parser).kind != .Eof {
|
||||||
|
advance(parser)
|
||||||
|
}
|
||||||
|
skip_newlines(parser)
|
||||||
|
return diagnostic
|
||||||
|
}
|
||||||
|
|
||||||
|
parse_return :: proc(parser: ^Parser) -> int {
|
||||||
|
start := advance(parser)
|
||||||
|
skip_newlines(parser)
|
||||||
|
if current(parser).kind == .Underscore {
|
||||||
|
end := advance(parser)
|
||||||
|
id := len(parser.module.statements)
|
||||||
|
append(&parser.module.statements, ast.Stmt{
|
||||||
|
kind=.Return,
|
||||||
|
span=source.Span{start=start.span.start, end=end.span.end},
|
||||||
|
name="_",
|
||||||
|
expr=ast.INVALID_ID,
|
||||||
|
diagnostic=-1,
|
||||||
|
})
|
||||||
|
return id
|
||||||
|
}
|
||||||
|
expr := parse_expression(parser)
|
||||||
|
id := len(parser.module.statements)
|
||||||
|
append(&parser.module.statements, ast.Stmt{
|
||||||
|
kind=.Return,
|
||||||
|
span=source.Span{start=start.span.start, end=parser.module.exprs[expr].span.end},
|
||||||
|
expr=expr,
|
||||||
|
diagnostic=-1,
|
||||||
|
})
|
||||||
|
return id
|
||||||
|
}
|
||||||
|
|
||||||
|
parse_statement :: proc(parser: ^Parser) -> int {
|
||||||
|
if current(parser).kind == .Keyword_Return {
|
||||||
|
return parse_return(parser)
|
||||||
|
}
|
||||||
|
|
||||||
|
if current(parser).kind == .Identifier || current(parser).kind == .Underscore {
|
||||||
|
start_cursor := parser.cursor
|
||||||
|
name := advance(parser)
|
||||||
|
type_syntax := ast.Type_Syntax.Invalid
|
||||||
|
had_type := false
|
||||||
|
if is_type_token(current(parser).kind) {
|
||||||
|
type_syntax = parse_type(parser)
|
||||||
|
had_type = true
|
||||||
|
}
|
||||||
|
operator := current(parser)
|
||||||
|
if operator.kind == .Colon_Colon || operator.kind == .Equal {
|
||||||
|
advance(parser)
|
||||||
|
skip_newlines(parser)
|
||||||
|
expr := parse_expression(parser)
|
||||||
|
kind := ast.Stmt_Kind.Assignment
|
||||||
|
immutable := false
|
||||||
|
if operator.kind == .Colon_Colon || had_type {
|
||||||
|
kind = .Declaration
|
||||||
|
immutable = operator.kind == .Colon_Colon
|
||||||
|
}
|
||||||
|
id := len(parser.module.statements)
|
||||||
|
append(&parser.module.statements, ast.Stmt{
|
||||||
|
kind=kind,
|
||||||
|
span=source.Span{start=name.span.start, end=parser.module.exprs[expr].span.end},
|
||||||
|
name=name.text,
|
||||||
|
type=type_syntax,
|
||||||
|
immutable=immutable,
|
||||||
|
expr=expr,
|
||||||
|
diagnostic=-1,
|
||||||
|
})
|
||||||
|
return id
|
||||||
|
}
|
||||||
|
parser.cursor = start_cursor
|
||||||
|
}
|
||||||
|
|
||||||
|
expr := parse_expression(parser)
|
||||||
|
id := len(parser.module.statements)
|
||||||
|
append(&parser.module.statements, ast.Stmt{
|
||||||
|
kind=.Expression,
|
||||||
|
span=parser.module.exprs[expr].span,
|
||||||
|
expr=expr,
|
||||||
|
diagnostic=-1,
|
||||||
|
})
|
||||||
|
return id
|
||||||
|
}
|
||||||
|
|
||||||
|
parse_params :: proc(parser: ^Parser) -> []ast.Param {
|
||||||
|
params: [dynamic]ast.Param
|
||||||
|
params.allocator = parser.module.allocator
|
||||||
|
skip_newlines(parser)
|
||||||
|
for current(parser).kind != .Right_Paren && current(parser).kind != .Eof {
|
||||||
|
names: [dynamic]token.Token
|
||||||
|
names.allocator = parser.module.allocator
|
||||||
|
for {
|
||||||
|
if current(parser).kind != .Identifier {
|
||||||
|
source.add(parser.diagnostics, current(parser).span, "expected parameter name")
|
||||||
|
break
|
||||||
|
}
|
||||||
|
append(&names, advance(parser))
|
||||||
|
if is_type_token(current(parser).kind) {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if _, ok := allow(parser, .Comma); !ok {
|
||||||
|
source.add(parser.diagnostics, current(parser).span, "expected ',' or parameter type")
|
||||||
|
break
|
||||||
|
}
|
||||||
|
skip_newlines(parser)
|
||||||
|
}
|
||||||
|
type_syntax := parse_type(parser)
|
||||||
|
for name in names {
|
||||||
|
append(¶ms, ast.Param{name=name.text, span=name.span, type=type_syntax})
|
||||||
|
}
|
||||||
|
delete(names)
|
||||||
|
skip_newlines(parser)
|
||||||
|
if _, ok := allow(parser, .Comma); ok {
|
||||||
|
skip_newlines(parser)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
break
|
||||||
|
}
|
||||||
|
return params[:]
|
||||||
|
}
|
||||||
|
|
||||||
|
parse_function :: proc(parser: ^Parser, name: token.Token, c_abi: bool) {
|
||||||
|
func_token := advance(parser)
|
||||||
|
if _, ok := allow(parser, .Left_Paren); !ok {
|
||||||
|
source.add(parser.diagnostics, current(parser).span, "expected '(' after 'func'")
|
||||||
|
}
|
||||||
|
params := parse_params(parser)
|
||||||
|
if _, ok := allow(parser, .Right_Paren); !ok {
|
||||||
|
source.add(parser.diagnostics, current(parser).span, "expected ')' after parameters")
|
||||||
|
}
|
||||||
|
skip_newlines(parser)
|
||||||
|
result := parse_type(parser)
|
||||||
|
skip_newlines(parser)
|
||||||
|
if _, ok := allow(parser, .Left_Brace); !ok {
|
||||||
|
source.add(parser.diagnostics, current(parser).span, "expected '{' before function body")
|
||||||
|
}
|
||||||
|
|
||||||
|
body: [dynamic]int
|
||||||
|
body.allocator = parser.module.allocator
|
||||||
|
skip_newlines(parser)
|
||||||
|
for current(parser).kind != .Right_Brace && current(parser).kind != .Eof {
|
||||||
|
append(&body, parse_statement(parser))
|
||||||
|
if diagnostic := finish_statement(parser); diagnostic >= 0 {
|
||||||
|
statement_id := len(parser.module.statements)
|
||||||
|
append(&parser.module.statements, ast.Stmt{
|
||||||
|
kind=.Invalid,
|
||||||
|
span=current(parser).span,
|
||||||
|
expr=ast.INVALID_ID,
|
||||||
|
diagnostic=diagnostic,
|
||||||
|
})
|
||||||
|
append(&body, statement_id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
end := current(parser)
|
||||||
|
if _, ok := allow(parser, .Right_Brace); !ok {
|
||||||
|
source.add(parser.diagnostics, current(parser).span, "expected '}' after function body")
|
||||||
|
end = func_token
|
||||||
|
}
|
||||||
|
append(&parser.module.functions, ast.Function{
|
||||||
|
span=source.Span{start=name.span.start, end=end.span.end},
|
||||||
|
name=name.text,
|
||||||
|
c_abi=c_abi,
|
||||||
|
params=params,
|
||||||
|
result=result,
|
||||||
|
body=body[:],
|
||||||
|
diagnostic=-1,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
parse_top_level :: proc(parser: ^Parser) {
|
||||||
|
if current(parser).kind != .Identifier {
|
||||||
|
source.add(parser.diagnostics, current(parser).span, "expected a top-level declaration")
|
||||||
|
for current(parser).kind != .Newline && current(parser).kind != .Eof {
|
||||||
|
advance(parser)
|
||||||
|
}
|
||||||
|
_ = finish_statement(parser)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
name := advance(parser)
|
||||||
|
type_syntax := ast.Type_Syntax.Invalid
|
||||||
|
if is_type_token(current(parser).kind) {
|
||||||
|
type_syntax = parse_type(parser)
|
||||||
|
}
|
||||||
|
operator := current(parser)
|
||||||
|
if operator.kind != .Colon_Colon && operator.kind != .Equal {
|
||||||
|
source.add(parser.diagnostics, operator.span, "expected '::' or '=' after top-level name")
|
||||||
|
_ = finish_statement(parser)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
advance(parser)
|
||||||
|
skip_newlines(parser)
|
||||||
|
|
||||||
|
c_abi := false
|
||||||
|
if operator.kind == .Colon_Colon && current(parser).kind == .Keyword_C {
|
||||||
|
c_abi = true
|
||||||
|
advance(parser)
|
||||||
|
skip_newlines(parser)
|
||||||
|
}
|
||||||
|
if operator.kind == .Colon_Colon && current(parser).kind == .Keyword_Func {
|
||||||
|
parse_function(parser, name, c_abi)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
expr := parse_expression(parser)
|
||||||
|
append(&parser.module.globals, ast.Global{
|
||||||
|
span=source.Span{start=name.span.start, end=parser.module.exprs[expr].span.end},
|
||||||
|
name=name.text,
|
||||||
|
type=type_syntax,
|
||||||
|
immutable=operator.kind == .Colon_Colon,
|
||||||
|
expr=expr,
|
||||||
|
diagnostic=-1,
|
||||||
|
})
|
||||||
|
_ = finish_statement(parser)
|
||||||
|
}
|
||||||
|
|
||||||
|
parse :: proc(
|
||||||
|
stream: ^token.Stream,
|
||||||
|
diagnostics: ^source.Diagnostics,
|
||||||
|
allocator := context.allocator,
|
||||||
|
) -> ast.Module {
|
||||||
|
parser := Parser{
|
||||||
|
tokens=stream,
|
||||||
|
diagnostics=diagnostics,
|
||||||
|
module=ast.init_module(allocator),
|
||||||
|
}
|
||||||
|
skip_newlines(&parser)
|
||||||
|
for current(&parser).kind != .Eof {
|
||||||
|
parse_top_level(&parser)
|
||||||
|
skip_newlines(&parser)
|
||||||
|
}
|
||||||
|
return parser.module
|
||||||
|
}
|
||||||
@@ -0,0 +1,104 @@
|
|||||||
|
package source
|
||||||
|
|
||||||
|
import "core:fmt"
|
||||||
|
import "core:mem"
|
||||||
|
|
||||||
|
Span :: struct {
|
||||||
|
start: int,
|
||||||
|
end: int,
|
||||||
|
}
|
||||||
|
|
||||||
|
Source :: struct {
|
||||||
|
path: string,
|
||||||
|
text: string,
|
||||||
|
}
|
||||||
|
|
||||||
|
Diagnostic :: struct {
|
||||||
|
span: Span,
|
||||||
|
message: string,
|
||||||
|
}
|
||||||
|
|
||||||
|
Diagnostics :: struct {
|
||||||
|
source: ^Source,
|
||||||
|
items: [dynamic]Diagnostic,
|
||||||
|
allocator: mem.Allocator,
|
||||||
|
}
|
||||||
|
|
||||||
|
init_diagnostics :: proc(source_file: ^Source, allocator := context.allocator) -> Diagnostics {
|
||||||
|
result: Diagnostics
|
||||||
|
result.source = source_file
|
||||||
|
result.allocator = allocator
|
||||||
|
result.items.allocator = allocator
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
destroy_diagnostics :: proc(diagnostics: ^Diagnostics) {
|
||||||
|
for diagnostic in diagnostics.items {
|
||||||
|
delete(diagnostic.message, diagnostics.allocator)
|
||||||
|
}
|
||||||
|
delete(diagnostics.items)
|
||||||
|
}
|
||||||
|
|
||||||
|
add :: proc(diagnostics: ^Diagnostics, span: Span, message: string) -> int {
|
||||||
|
for diagnostic, id in diagnostics.items {
|
||||||
|
if diagnostic.span == span && diagnostic.message == message {
|
||||||
|
return id
|
||||||
|
}
|
||||||
|
}
|
||||||
|
id := len(diagnostics.items)
|
||||||
|
cloned := fmt.aprintf("%s", message, allocator=diagnostics.allocator)
|
||||||
|
append(&diagnostics.items, Diagnostic{span=span, message=cloned})
|
||||||
|
return id
|
||||||
|
}
|
||||||
|
|
||||||
|
addf :: proc(diagnostics: ^Diagnostics, span: Span, format: string, args: ..any) -> int {
|
||||||
|
message := fmt.aprintf(format, ..args, allocator=diagnostics.allocator)
|
||||||
|
for diagnostic, id in diagnostics.items {
|
||||||
|
if diagnostic.span == span && diagnostic.message == message {
|
||||||
|
delete(message, diagnostics.allocator)
|
||||||
|
return id
|
||||||
|
}
|
||||||
|
}
|
||||||
|
id := len(diagnostics.items)
|
||||||
|
append(&diagnostics.items, Diagnostic{span=span, message=message})
|
||||||
|
return id
|
||||||
|
}
|
||||||
|
|
||||||
|
line_and_column :: proc(source_file: ^Source, offset: int) -> (line, column: int) {
|
||||||
|
line = 1
|
||||||
|
column = 1
|
||||||
|
limit := min(offset, len(source_file.text))
|
||||||
|
for byte_value in transmute([]byte)source_file.text[:limit] {
|
||||||
|
if byte_value == '\n' {
|
||||||
|
line += 1
|
||||||
|
column = 1
|
||||||
|
} else {
|
||||||
|
column += 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
format :: proc(diagnostics: ^Diagnostics, id: int, allocator := context.allocator) -> string {
|
||||||
|
if id < 0 || id >= len(diagnostics.items) {
|
||||||
|
return fmt.aprintf("%s: compiler recovery error", diagnostics.source.path, allocator=allocator)
|
||||||
|
}
|
||||||
|
diagnostic := diagnostics.items[id]
|
||||||
|
line, column := line_and_column(diagnostics.source, diagnostic.span.start)
|
||||||
|
return fmt.aprintf(
|
||||||
|
"%s:%d:%d: error: %s",
|
||||||
|
diagnostics.source.path,
|
||||||
|
line,
|
||||||
|
column,
|
||||||
|
diagnostic.message,
|
||||||
|
allocator=allocator,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
print_all :: proc(diagnostics: ^Diagnostics) {
|
||||||
|
for _, id in diagnostics.items {
|
||||||
|
message := format(diagnostics, id)
|
||||||
|
fmt.eprintln(message)
|
||||||
|
delete(message)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,40 @@
|
|||||||
|
package token
|
||||||
|
|
||||||
|
import "../source"
|
||||||
|
|
||||||
|
Kind :: enum {
|
||||||
|
Invalid,
|
||||||
|
Eof,
|
||||||
|
Newline,
|
||||||
|
Identifier,
|
||||||
|
Integer,
|
||||||
|
Underscore,
|
||||||
|
Colon_Colon,
|
||||||
|
Equal,
|
||||||
|
Plus,
|
||||||
|
Left_Paren,
|
||||||
|
Right_Paren,
|
||||||
|
Left_Brace,
|
||||||
|
Right_Brace,
|
||||||
|
Comma,
|
||||||
|
Keyword_C,
|
||||||
|
Keyword_Func,
|
||||||
|
Keyword_Return,
|
||||||
|
Keyword_Void,
|
||||||
|
Keyword_Int,
|
||||||
|
Keyword_I8,
|
||||||
|
Keyword_I16,
|
||||||
|
Keyword_I32,
|
||||||
|
Keyword_I64,
|
||||||
|
}
|
||||||
|
|
||||||
|
Token :: struct {
|
||||||
|
kind: Kind,
|
||||||
|
span: source.Span,
|
||||||
|
text: string,
|
||||||
|
diagnostic: int,
|
||||||
|
}
|
||||||
|
|
||||||
|
Stream :: struct {
|
||||||
|
items: [dynamic]Token,
|
||||||
|
}
|
||||||
@@ -0,0 +1,139 @@
|
|||||||
|
package types
|
||||||
|
|
||||||
|
import "core:fmt"
|
||||||
|
|
||||||
|
Numeric_Category :: enum {
|
||||||
|
None,
|
||||||
|
Signed_Integer,
|
||||||
|
Unsigned_Integer,
|
||||||
|
Float,
|
||||||
|
}
|
||||||
|
|
||||||
|
Kind :: enum {
|
||||||
|
Invalid,
|
||||||
|
Void,
|
||||||
|
Int_Constraint,
|
||||||
|
Concrete,
|
||||||
|
}
|
||||||
|
|
||||||
|
Type :: struct {
|
||||||
|
kind: Kind,
|
||||||
|
category: Numeric_Category,
|
||||||
|
bits: int,
|
||||||
|
}
|
||||||
|
|
||||||
|
INVALID :: Type {
|
||||||
|
kind = .Invalid,
|
||||||
|
}
|
||||||
|
VOID :: Type {
|
||||||
|
kind = .Void,
|
||||||
|
}
|
||||||
|
INT :: Type {
|
||||||
|
kind = .Int_Constraint,
|
||||||
|
category = .Signed_Integer,
|
||||||
|
}
|
||||||
|
I8 :: Type {
|
||||||
|
kind = .Concrete,
|
||||||
|
category = .Signed_Integer,
|
||||||
|
bits = 8,
|
||||||
|
}
|
||||||
|
I16 :: Type {
|
||||||
|
kind = .Concrete,
|
||||||
|
category = .Signed_Integer,
|
||||||
|
bits = 16,
|
||||||
|
}
|
||||||
|
I32 :: Type {
|
||||||
|
kind = .Concrete,
|
||||||
|
category = .Signed_Integer,
|
||||||
|
bits = 32,
|
||||||
|
}
|
||||||
|
I64 :: Type {
|
||||||
|
kind = .Concrete,
|
||||||
|
category = .Signed_Integer,
|
||||||
|
bits = 64,
|
||||||
|
}
|
||||||
|
|
||||||
|
is_valid :: proc(value: Type) -> bool {
|
||||||
|
return value.kind != .Invalid
|
||||||
|
}
|
||||||
|
|
||||||
|
is_concrete_integer :: proc(value: Type) -> bool {
|
||||||
|
return(
|
||||||
|
value.kind == .Concrete &&
|
||||||
|
(value.category == .Signed_Integer || value.category == .Unsigned_Integer) \
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
is_signed :: proc(value: Type) -> bool {
|
||||||
|
return value.kind == .Concrete && value.category == .Signed_Integer
|
||||||
|
}
|
||||||
|
|
||||||
|
equal :: proc(a, b: Type) -> bool {
|
||||||
|
return a.kind == b.kind && a.category == b.category && a.bits == b.bits
|
||||||
|
}
|
||||||
|
|
||||||
|
can_widen :: proc(from, to: Type) -> bool {
|
||||||
|
if equal(from, to) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return(
|
||||||
|
from.kind == .Concrete &&
|
||||||
|
to.kind == .Concrete &&
|
||||||
|
from.category == to.category &&
|
||||||
|
from.bits < to.bits \
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
widest :: proc(a, b: Type) -> Type {
|
||||||
|
if a.kind != .Concrete || b.kind != .Concrete || a.category != b.category {
|
||||||
|
return INVALID
|
||||||
|
}
|
||||||
|
if a.bits >= b.bits {
|
||||||
|
return a
|
||||||
|
}
|
||||||
|
return b
|
||||||
|
}
|
||||||
|
|
||||||
|
smallest_signed_for_literal :: proc(value: i64) -> Type {
|
||||||
|
if value >= -128 && value <= 127 {
|
||||||
|
return I8
|
||||||
|
}
|
||||||
|
if value >= -32768 && value <= 32767 {
|
||||||
|
return I16
|
||||||
|
}
|
||||||
|
if value >= -2147483648 && value <= 2147483647 {
|
||||||
|
return I32
|
||||||
|
}
|
||||||
|
return I64
|
||||||
|
}
|
||||||
|
|
||||||
|
name :: proc(value: Type) -> string {
|
||||||
|
switch value.kind {
|
||||||
|
case .Invalid:
|
||||||
|
return "<invalid>"
|
||||||
|
case .Void:
|
||||||
|
return "void"
|
||||||
|
case .Int_Constraint:
|
||||||
|
return "int"
|
||||||
|
case .Concrete:
|
||||||
|
switch value.category {
|
||||||
|
case .Signed_Integer:
|
||||||
|
switch value.bits {
|
||||||
|
case 8:
|
||||||
|
return "i8"
|
||||||
|
case 16:
|
||||||
|
return "i16"
|
||||||
|
case 32:
|
||||||
|
return "i32"
|
||||||
|
case 64:
|
||||||
|
return "i64"
|
||||||
|
}
|
||||||
|
case .Unsigned_Integer:
|
||||||
|
return fmt.tprintf("u%d", value.bits)
|
||||||
|
case .Float:
|
||||||
|
return fmt.tprintf("f%d", value.bits)
|
||||||
|
case .None:
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return "<invalid>"
|
||||||
|
}
|
||||||
@@ -0,0 +1,509 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import compiler_core "./compiler"
|
||||||
|
import "./compiler/ast"
|
||||||
|
import "./compiler/backend"
|
||||||
|
import "./compiler/checker"
|
||||||
|
import "./compiler/hir"
|
||||||
|
import "./compiler/ir"
|
||||||
|
import "./compiler/lexer"
|
||||||
|
import "./compiler/llvm"
|
||||||
|
import "./compiler/lower"
|
||||||
|
import "./compiler/parser"
|
||||||
|
import "./compiler/source"
|
||||||
|
import "./compiler/token"
|
||||||
|
import "./compiler/types"
|
||||||
|
import "core:os"
|
||||||
|
import "core:os/os2"
|
||||||
|
import "core:strings"
|
||||||
|
import "core:testing"
|
||||||
|
|
||||||
|
@(test)
|
||||||
|
lexer_preserves_newlines_and_skips_comments :: proc(t: ^testing.T) {
|
||||||
|
source_file := source.Source{path="test.bro", text="# comment\nmain :: func() void {}\n"}
|
||||||
|
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), 0)
|
||||||
|
testing.expect_value(t, stream.items[0].kind, token.Kind.Newline)
|
||||||
|
testing.expect_value(t, stream.items[1].kind, token.Kind.Identifier)
|
||||||
|
}
|
||||||
|
|
||||||
|
@(test)
|
||||||
|
parser_accepts_grouped_params_and_multiline_statements :: proc(t: ^testing.T) {
|
||||||
|
text := `sum :: func(a,
|
||||||
|
b int) int {
|
||||||
|
return (a
|
||||||
|
+ b)
|
||||||
|
}
|
||||||
|
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.functions), 2)
|
||||||
|
testing.expect_value(t, len(module.functions[0].params), 2)
|
||||||
|
}
|
||||||
|
|
||||||
|
@(test)
|
||||||
|
pipeline_emits_specialized_calling_conventions_and_checked_add :: proc(t: ^testing.T) {
|
||||||
|
text := `sum_c :: c func(a, b int) int {
|
||||||
|
return a + b
|
||||||
|
}
|
||||||
|
sum_bro :: func(a, b int) int {
|
||||||
|
return a + b
|
||||||
|
}
|
||||||
|
main :: func() void {
|
||||||
|
_ = sum_c(1, 2)
|
||||||
|
_ = sum_bro(1, 2)
|
||||||
|
}
|
||||||
|
`
|
||||||
|
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)
|
||||||
|
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_value(t, len(diagnostics.items), 0)
|
||||||
|
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 internal fastcc i8 @bro__sum_bro__i8__i8"))
|
||||||
|
testing.expect(t, strings.contains(llvm_text, "@llvm.sadd.with.overflow.i8"))
|
||||||
|
}
|
||||||
|
|
||||||
|
@(test)
|
||||||
|
literal_addition_trees_fold_with_contextual_types :: proc(t: ^testing.T) {
|
||||||
|
text := `return_i16 :: func() i16 {
|
||||||
|
return 1 + 2
|
||||||
|
}
|
||||||
|
take_i16 :: func(value i16) i16 {
|
||||||
|
return value
|
||||||
|
}
|
||||||
|
take_int :: func(value int) int {
|
||||||
|
return value
|
||||||
|
}
|
||||||
|
main :: func() void {
|
||||||
|
local i16 :: 1 + 2
|
||||||
|
_ = 100 + (20 + 8)
|
||||||
|
_ = return_i16()
|
||||||
|
_ = take_i16(1 + 2)
|
||||||
|
_ = take_int(127 + 1)
|
||||||
|
}
|
||||||
|
`
|
||||||
|
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)
|
||||||
|
ir_module := lower.lower(&hir_module)
|
||||||
|
defer ir.destroy_module(&ir_module)
|
||||||
|
llvm_text := llvm.emit(&ir_module, &diagnostics)
|
||||||
|
defer delete(llvm_text)
|
||||||
|
|
||||||
|
testing.expect_value(t, len(diagnostics.items), 0)
|
||||||
|
testing.expect(t, strings.contains(llvm_text, "@bro__take_int__i16"))
|
||||||
|
for function in ir_module.functions {
|
||||||
|
for instruction in function.instructions {
|
||||||
|
testing.expect(t, instruction.op != ir.Opcode.Add_Checked)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for function in hir_module.functions {
|
||||||
|
for statement_id in function.body {
|
||||||
|
statement := hir_module.statements[statement_id]
|
||||||
|
if statement.expr < 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
expr := hir_module.exprs[statement.expr]
|
||||||
|
if expr.kind == .Integer {
|
||||||
|
testing.expect(t, types.equal(expr.type, types.I16))
|
||||||
|
}
|
||||||
|
if expr.kind == .Call && function.name == "main" && len(expr.args) > 0 {
|
||||||
|
arg := hir_module.exprs[expr.args[0]]
|
||||||
|
testing.expect_value(t, arg.kind, hir.Expr_Kind.Integer)
|
||||||
|
testing.expect(t, types.equal(arg.type, types.I16))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@(test)
|
||||||
|
runtime_arithmetic_does_not_inherit_result_context :: proc(t: ^testing.T) {
|
||||||
|
text := `widen_after_add :: func(value i8) i16 {
|
||||||
|
return value + 1
|
||||||
|
}
|
||||||
|
main :: func() void {
|
||||||
|
_ = widen_after_add(1)
|
||||||
|
}
|
||||||
|
`
|
||||||
|
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)
|
||||||
|
found := false
|
||||||
|
for function in hir_module.functions {
|
||||||
|
if function.name != "widen_after_add" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
found = true
|
||||||
|
statement := hir_module.statements[function.body[0]]
|
||||||
|
widen := hir_module.exprs[statement.expr]
|
||||||
|
add := hir_module.exprs[widen.left]
|
||||||
|
testing.expect_value(t, widen.kind, hir.Expr_Kind.Widen)
|
||||||
|
testing.expect(t, types.equal(widen.type, types.I16))
|
||||||
|
testing.expect_value(t, add.kind, hir.Expr_Kind.Add)
|
||||||
|
testing.expect(t, types.equal(add.type, types.I8))
|
||||||
|
}
|
||||||
|
testing.expect(t, found)
|
||||||
|
}
|
||||||
|
|
||||||
|
@(test)
|
||||||
|
parser_recovers_after_invalid_tokens :: proc(t: ^testing.T) {
|
||||||
|
text := `broken @ declaration
|
||||||
|
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(t, len(diagnostics.items) > 0)
|
||||||
|
testing.expect_value(t, len(module.functions), 1)
|
||||||
|
testing.expect_value(t, module.functions[0].name, "main")
|
||||||
|
}
|
||||||
|
|
||||||
|
@(test)
|
||||||
|
return_sink_and_unconsumed_values_have_distinct_hir :: proc(t: ^testing.T) {
|
||||||
|
text := `give :: func() i8 {
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
done :: func() void {
|
||||||
|
return _
|
||||||
|
}
|
||||||
|
main :: func() void {
|
||||||
|
done()
|
||||||
|
_ = give()
|
||||||
|
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)
|
||||||
|
|
||||||
|
done_id, main_id := -1, -1
|
||||||
|
for function, id in hir_module.functions {
|
||||||
|
if function.name == "done" {
|
||||||
|
done_id = id
|
||||||
|
} else if function.name == "main" {
|
||||||
|
main_id = id
|
||||||
|
}
|
||||||
|
}
|
||||||
|
testing.expect(t, done_id >= 0)
|
||||||
|
testing.expect(t, main_id >= 0)
|
||||||
|
testing.expect_value(t, hir_module.statements[hir_module.functions[done_id].body[0]].kind, hir.Stmt_Kind.Return)
|
||||||
|
testing.expect_value(t, hir_module.statements[hir_module.functions[done_id].body[0]].expr, -1)
|
||||||
|
main := hir_module.functions[main_id]
|
||||||
|
testing.expect_value(t, hir_module.statements[main.body[0]].kind, hir.Stmt_Kind.Expression)
|
||||||
|
testing.expect_value(t, hir_module.statements[main.body[1]].kind, hir.Stmt_Kind.Sink)
|
||||||
|
testing.expect_value(t, hir_module.statements[main.body[2]].kind, hir.Stmt_Kind.Trap)
|
||||||
|
}
|
||||||
|
|
||||||
|
@(test)
|
||||||
|
recursive_specialization_reaches_a_fixed_point :: proc(t: ^testing.T) {
|
||||||
|
text := `a :: func(value int) i32 {
|
||||||
|
return b(value)
|
||||||
|
}
|
||||||
|
b :: func(value int) i32 {
|
||||||
|
return a(value)
|
||||||
|
}
|
||||||
|
main :: func() void {
|
||||||
|
_ = a(1)
|
||||||
|
}
|
||||||
|
`
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
|
||||||
|
run_executable :: proc(path: string) -> os2.Process_State {
|
||||||
|
state, stdout, stderr, _ := os2.process_exec(
|
||||||
|
os2.Process_Desc{command=[]string{path}},
|
||||||
|
context.allocator,
|
||||||
|
)
|
||||||
|
delete(stdout)
|
||||||
|
delete(stderr)
|
||||||
|
return state
|
||||||
|
}
|
||||||
|
|
||||||
|
@(test)
|
||||||
|
valid_program_compiles_and_runs :: proc(t: ^testing.T) {
|
||||||
|
output := "/tmp/brolang-test-valid"
|
||||||
|
defer _ = os.remove(output)
|
||||||
|
status := compiler_core.compile_file("examples/prototype.bro", output)
|
||||||
|
testing.expect_value(t, status, 0)
|
||||||
|
state := run_executable(output)
|
||||||
|
testing.expect_value(t, state.exit_code, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
@(test)
|
||||||
|
folded_constant_addition_compiles_and_runs :: proc(t: ^testing.T) {
|
||||||
|
output := "/tmp/brolang-test-constant-fold"
|
||||||
|
defer _ = os.remove(output)
|
||||||
|
status := compiler_core.compile_file("examples/constant_fold.bro", output)
|
||||||
|
testing.expect_value(t, status, 0)
|
||||||
|
state := run_executable(output)
|
||||||
|
testing.expect_value(t, state.exit_code, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
@(test)
|
||||||
|
unused_invalid_global_does_not_trap :: proc(t: ^testing.T) {
|
||||||
|
output := "/tmp/brolang-test-invalid-unused"
|
||||||
|
defer _ = os.remove(output)
|
||||||
|
status := compiler_core.compile_file("examples/invalid_unused_global.bro", output)
|
||||||
|
testing.expect_value(t, status, 1)
|
||||||
|
state := run_executable(output)
|
||||||
|
testing.expect_value(t, state.exit_code, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
@(test)
|
||||||
|
used_invalid_global_traps :: proc(t: ^testing.T) {
|
||||||
|
output := "/tmp/brolang-test-invalid-used"
|
||||||
|
defer _ = os.remove(output)
|
||||||
|
status := compiler_core.compile_file("examples/invalid_used_global.bro", output)
|
||||||
|
testing.expect_value(t, status, 1)
|
||||||
|
state := run_executable(output)
|
||||||
|
testing.expect(t, !state.success)
|
||||||
|
}
|
||||||
|
|
||||||
|
@(test)
|
||||||
|
main_int_is_constrained_to_i32 :: proc(t: ^testing.T) {
|
||||||
|
output := "/tmp/brolang-test-main-int"
|
||||||
|
defer _ = os.remove(output)
|
||||||
|
status := compiler_core.compile_file("examples/main_int.bro", output)
|
||||||
|
testing.expect_value(t, status, 0)
|
||||||
|
state := run_executable(output)
|
||||||
|
testing.expect_value(t, state.exit_code, 3)
|
||||||
|
}
|
||||||
|
|
||||||
|
@(test)
|
||||||
|
main_i32_returns_directly :: proc(t: ^testing.T) {
|
||||||
|
output := "/tmp/brolang-test-main-i32"
|
||||||
|
defer _ = os.remove(output)
|
||||||
|
status := compiler_core.compile_file("examples/main_i32.bro", output)
|
||||||
|
testing.expect_value(t, status, 0)
|
||||||
|
state := run_executable(output)
|
||||||
|
testing.expect_value(t, state.exit_code, 4)
|
||||||
|
}
|
||||||
|
|
||||||
|
@(test)
|
||||||
|
transitive_problematic_global_is_deferred :: proc(t: ^testing.T) {
|
||||||
|
output := "/tmp/brolang-test-transitive-unused"
|
||||||
|
defer _ = os.remove(output)
|
||||||
|
status := compiler_core.compile_file("examples/invalid_transitive_unused_global.bro", output)
|
||||||
|
testing.expect_value(t, status, 1)
|
||||||
|
state := run_executable(output)
|
||||||
|
testing.expect_value(t, state.exit_code, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
@(test)
|
||||||
|
checked_addition_traps_on_overflow :: proc(t: ^testing.T) {
|
||||||
|
output := "/tmp/brolang-test-overflow"
|
||||||
|
defer _ = os.remove(output)
|
||||||
|
status := compiler_core.compile_file("examples/overflow.bro", output)
|
||||||
|
testing.expect_value(t, status, 0)
|
||||||
|
state := run_executable(output)
|
||||||
|
testing.expect(t, !state.success)
|
||||||
|
}
|
||||||
|
|
||||||
|
@(test)
|
||||||
|
constant_that_does_not_fit_context_produces_trap_executable :: proc(t: ^testing.T) {
|
||||||
|
output := "/tmp/brolang-test-constant-context-error"
|
||||||
|
defer _ = os.remove(output)
|
||||||
|
status := compiler_core.compile_file("examples/constant_context_error.bro", output)
|
||||||
|
testing.expect_value(t, status, 1)
|
||||||
|
state := run_executable(output)
|
||||||
|
testing.expect(t, !state.success)
|
||||||
|
}
|
||||||
|
|
||||||
|
@(test)
|
||||||
|
constant_beyond_i64_produces_trap_executable :: proc(t: ^testing.T) {
|
||||||
|
output := "/tmp/brolang-test-constant-i64-overflow"
|
||||||
|
defer _ = os.remove(output)
|
||||||
|
status := compiler_core.compile_file("examples/constant_i64_overflow.bro", output)
|
||||||
|
testing.expect_value(t, status, 1)
|
||||||
|
state := run_executable(output)
|
||||||
|
testing.expect(t, !state.success)
|
||||||
|
}
|
||||||
|
|
||||||
|
@(test)
|
||||||
|
same_line_statements_are_diagnosed :: proc(t: ^testing.T) {
|
||||||
|
text := "main :: func() void { _ = 1 _ = 2\n}\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)
|
||||||
|
module := parser.parse(&stream, &diagnostics)
|
||||||
|
defer ast.destroy_module(&module)
|
||||||
|
testing.expect(t, len(diagnostics.items) > 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
@(test)
|
||||||
|
missing_main_produces_trap_executable :: proc(t: ^testing.T) {
|
||||||
|
output := "/tmp/brolang-test-missing-main"
|
||||||
|
defer _ = os.remove(output)
|
||||||
|
status := compiler_core.compile_file("examples/missing_main.bro", output)
|
||||||
|
testing.expect_value(t, status, 1)
|
||||||
|
state := run_executable(output)
|
||||||
|
testing.expect(t, !state.success)
|
||||||
|
}
|
||||||
|
|
||||||
|
@(test)
|
||||||
|
backend_failure_preserves_existing_output :: proc(t: ^testing.T) {
|
||||||
|
output := "/tmp/brolang-test-preserved-output"
|
||||||
|
defer _ = os.remove(output)
|
||||||
|
previous := "previous artifact"
|
||||||
|
testing.expect(t, os.write_entire_file(output, transmute([]byte)previous))
|
||||||
|
testing.expect(t, !backend.compile("/definitely/not/llvm.ll", output))
|
||||||
|
data, ok := os.read_entire_file(output)
|
||||||
|
defer delete(data)
|
||||||
|
testing.expect(t, ok)
|
||||||
|
testing.expect_value(t, string(data), previous)
|
||||||
|
}
|
||||||
|
|
||||||
|
@(test)
|
||||||
|
mutable_local_reassignment_uses_runtime_storage :: proc(t: ^testing.T) {
|
||||||
|
output := "/tmp/brolang-test-mutable"
|
||||||
|
defer _ = os.remove(output)
|
||||||
|
status := compiler_core.compile_file("examples/mutable_local.bro", output)
|
||||||
|
testing.expect_value(t, status, 0)
|
||||||
|
state := run_executable(output)
|
||||||
|
testing.expect_value(t, state.exit_code, 3)
|
||||||
|
}
|
||||||
|
|
||||||
|
@(test)
|
||||||
|
implicit_narrowing_produces_trap_executable :: proc(t: ^testing.T) {
|
||||||
|
output := "/tmp/brolang-test-narrowing"
|
||||||
|
defer _ = os.remove(output)
|
||||||
|
status := compiler_core.compile_file("examples/narrowing_error.bro", output)
|
||||||
|
testing.expect_value(t, status, 1)
|
||||||
|
state := run_executable(output)
|
||||||
|
testing.expect(t, !state.success)
|
||||||
|
}
|
||||||
|
|
||||||
|
@(test)
|
||||||
|
valid_runtime_global_initializes_before_main :: proc(t: ^testing.T) {
|
||||||
|
output := "/tmp/brolang-test-runtime-global"
|
||||||
|
defer _ = os.remove(output)
|
||||||
|
status := compiler_core.compile_file("examples/runtime_global.bro", output)
|
||||||
|
testing.expect_value(t, status, 0)
|
||||||
|
state := run_executable(output)
|
||||||
|
testing.expect_value(t, state.exit_code, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
@(test)
|
||||||
|
unused_global_cycle_is_deferred :: proc(t: ^testing.T) {
|
||||||
|
output := "/tmp/brolang-test-cycle-unused"
|
||||||
|
defer _ = os.remove(output)
|
||||||
|
status := compiler_core.compile_file("examples/cycle_unused.bro", output)
|
||||||
|
testing.expect_value(t, status, 1)
|
||||||
|
state := run_executable(output)
|
||||||
|
testing.expect_value(t, state.exit_code, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
@(test)
|
||||||
|
used_global_cycle_traps :: proc(t: ^testing.T) {
|
||||||
|
output := "/tmp/brolang-test-cycle-used"
|
||||||
|
defer _ = os.remove(output)
|
||||||
|
status := compiler_core.compile_file("examples/cycle_used.bro", output)
|
||||||
|
testing.expect_value(t, status, 1)
|
||||||
|
state := run_executable(output)
|
||||||
|
testing.expect(t, !state.success)
|
||||||
|
}
|
||||||
|
|
||||||
|
@(test)
|
||||||
|
malformed_typed_values_still_produce_executable :: proc(t: ^testing.T) {
|
||||||
|
output := "/tmp/brolang-test-malformed-typed"
|
||||||
|
defer _ = os.remove(output)
|
||||||
|
status := compiler_core.compile_file("examples/malformed_typed_recovery.bro", output)
|
||||||
|
testing.expect_value(t, status, 1)
|
||||||
|
state := run_executable(output)
|
||||||
|
testing.expect(t, !state.success)
|
||||||
|
}
|
||||||
|
|
||||||
|
@(test)
|
||||||
|
unused_invalid_function_is_diagnosed_but_not_reached :: proc(t: ^testing.T) {
|
||||||
|
output := "/tmp/brolang-test-invalid-unused-function"
|
||||||
|
defer _ = os.remove(output)
|
||||||
|
status := compiler_core.compile_file("examples/invalid_unused_function.bro", output)
|
||||||
|
testing.expect_value(t, status, 1)
|
||||||
|
state := run_executable(output)
|
||||||
|
testing.expect_value(t, state.exit_code, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
@(test)
|
||||||
|
function_mediated_problematic_global_is_deferred :: proc(t: ^testing.T) {
|
||||||
|
output := "/tmp/brolang-test-function-global-unused"
|
||||||
|
defer _ = os.remove(output)
|
||||||
|
status := compiler_core.compile_file("examples/function_global_unused.bro", output)
|
||||||
|
testing.expect_value(t, status, 1)
|
||||||
|
state := run_executable(output)
|
||||||
|
testing.expect_value(t, state.exit_code, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
@(test)
|
||||||
|
function_mediated_problematic_global_traps_when_used :: proc(t: ^testing.T) {
|
||||||
|
output := "/tmp/brolang-test-function-global-used"
|
||||||
|
defer _ = os.remove(output)
|
||||||
|
status := compiler_core.compile_file("examples/function_global_used.bro", output)
|
||||||
|
testing.expect_value(t, status, 1)
|
||||||
|
state := run_executable(output)
|
||||||
|
testing.expect(t, !state.success)
|
||||||
|
}
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
main :: func() void {
|
||||||
|
value i8 :: 127 + 1
|
||||||
|
_ = value
|
||||||
|
}
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
main :: func() void {
|
||||||
|
_ = 127 + 1
|
||||||
|
}
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
main :: func() void {
|
||||||
|
_ = 9223372036854775807 + 1
|
||||||
|
}
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
a int :: b
|
||||||
|
b int :: a
|
||||||
|
|
||||||
|
main :: func() void {
|
||||||
|
_ = 1
|
||||||
|
}
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
a int :: b
|
||||||
|
b int :: a
|
||||||
|
|
||||||
|
main :: func() void {
|
||||||
|
_ = a
|
||||||
|
}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
bad int = 1
|
||||||
|
|
||||||
|
read_bad :: func() int {
|
||||||
|
return bad
|
||||||
|
}
|
||||||
|
|
||||||
|
derived :: read_bad()
|
||||||
|
|
||||||
|
main :: func() void {}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
bad int = 1
|
||||||
|
|
||||||
|
read_bad :: func() int {
|
||||||
|
return bad
|
||||||
|
}
|
||||||
|
|
||||||
|
derived :: read_bad()
|
||||||
|
|
||||||
|
main :: func() void {
|
||||||
|
_ = derived
|
||||||
|
}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
bad int = 4
|
||||||
|
|
||||||
|
read_bad :: func() int {
|
||||||
|
return bad
|
||||||
|
}
|
||||||
|
|
||||||
|
derived int :: read_bad()
|
||||||
|
|
||||||
|
main :: func() void {
|
||||||
|
_ = 1
|
||||||
|
}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
bad int = 4
|
||||||
|
|
||||||
|
read_bad :: func() int {
|
||||||
|
return bad
|
||||||
|
}
|
||||||
|
|
||||||
|
derived int :: read_bad()
|
||||||
|
|
||||||
|
main :: func() void {
|
||||||
|
_ = derived
|
||||||
|
}
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
broken :: func(value int) int {
|
||||||
|
return missing + value
|
||||||
|
}
|
||||||
|
|
||||||
|
main :: func() void {
|
||||||
|
_ = 1
|
||||||
|
}
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
bad int = 4
|
||||||
|
|
||||||
|
main :: func() void {
|
||||||
|
_ = 1
|
||||||
|
}
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
bad int = 4
|
||||||
|
|
||||||
|
main :: func() void {
|
||||||
|
_ = bad
|
||||||
|
}
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
main :: func() i32 {
|
||||||
|
return 4
|
||||||
|
}
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
main :: func() int {
|
||||||
|
return 3
|
||||||
|
}
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
take :: func(value i8) i8 {
|
||||||
|
return value
|
||||||
|
}
|
||||||
|
|
||||||
|
bad_return :: func() i8 {
|
||||||
|
return missing
|
||||||
|
}
|
||||||
|
|
||||||
|
main :: func() void {
|
||||||
|
_ = take(missing)
|
||||||
|
_ = bad_return()
|
||||||
|
}
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
x int :: 2
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
main :: func() i32 {
|
||||||
|
value i32 = 1
|
||||||
|
value = value + 2
|
||||||
|
return value
|
||||||
|
}
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
take :: func(value i8) i8 {
|
||||||
|
return value
|
||||||
|
}
|
||||||
|
|
||||||
|
main :: func() void {
|
||||||
|
_ = take(128)
|
||||||
|
}
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
main :: func() void {
|
||||||
|
value i8 = 127
|
||||||
|
_ = value + 1
|
||||||
|
}
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
# this is a comment
|
||||||
|
|
||||||
|
x int :: 2
|
||||||
|
|
||||||
|
sum_c :: c func(a, b int) int {
|
||||||
|
return a + b
|
||||||
|
}
|
||||||
|
|
||||||
|
sum_brolang :: func(a, b int) int {
|
||||||
|
return a + b
|
||||||
|
}
|
||||||
|
|
||||||
|
main :: func() void {
|
||||||
|
y int = 4
|
||||||
|
a_add_b_c :: sum_c(1, 2)
|
||||||
|
a_add_b_brolang :: sum_brolang(1, 2)
|
||||||
|
_ = y
|
||||||
|
_ = a_add_b_c
|
||||||
|
_ = a_add_b_brolang
|
||||||
|
}
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
make_value :: func() int {
|
||||||
|
return 40 + 2
|
||||||
|
}
|
||||||
|
|
||||||
|
answer int :: make_value()
|
||||||
|
|
||||||
|
main :: func() i32 {
|
||||||
|
_ = answer
|
||||||
|
return 0
|
||||||
|
}
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import "./compiler"
|
||||||
|
import "core:fmt"
|
||||||
|
import "core:os/os2"
|
||||||
|
|
||||||
|
main :: proc() {
|
||||||
|
if len(os2.args) != 4 || os2.args[2] != "-o" {
|
||||||
|
fmt.eprintln("usage: brolang <input.bro> -o <executable>")
|
||||||
|
os2.exit(2)
|
||||||
|
}
|
||||||
|
status := compiler.compile_file(os2.args[1], os2.args[3])
|
||||||
|
if status != 0 {
|
||||||
|
os2.exit(status)
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user