initial draft

This commit is contained in:
2026-06-09 17:28:28 +02:00
commit 087fdb45d5
40 changed files with 4371 additions and 0 deletions
+110
View File
@@ -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)
}
+46
View File
@@ -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
+80
View File
@@ -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
}
+121
View File
@@ -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)
}
+91
View File
@@ -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)
}
+135
View File
@@ -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
}
+389
View File
@@ -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)
}
+356
View File
@@ -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
}
+12
View File
@@ -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
}
+459
View File
@@ -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(&params, 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
}
+104
View File
@@ -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)
}
}
+40
View File
@@ -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,
}
+139
View File
@@ -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>"
}