bloat-buster/src/compiler.bbb
David Gonzalez Martin c94d12bfe8
Some checks failed
CI / ci (MinSizeRel, ubuntu-latest) (push) Failing after 1m6s
CI / ci (RelWithDebInfo, ubuntu-latest) (push) Failing after 1m5s
CI / ci (Release, ubuntu-latest) (push) Failing after 1m3s
CI / ci (Debug, ubuntu-latest) (push) Failing after 2m27s
Forward declared type
2025-05-27 20:22:21 -06:00

2178 lines
48 KiB
Plaintext

mode_t = typealias u64;
int = typealias s32;
usize = typealias u64;
ssize = typealias s64;
File = typealias s32;
uid_t = typealias u32;
gid_t = typealias u32;
off_t = typealias s64;
ino_t = typealias u64;
dev_t = typealias u64;
timespec = struct
{
seconds: ssize,
nanoseconds: ssize,
};
Stat = struct
{
dev: dev_t,
ino: ino_t,
nlink: usize,
mode: u32,
uid: uid_t,
gid: gid_t,
_: u32,
rdev: dev_t,
size: off_t,
blksize: ssize,
blocks: s64,
atim: timespec,
mtime: timespec,
ctim: timespec,
_: [3]ssize,
}
OAccessMode = enum u2
{
read_only = 0,
write_only = 1,
read_write = 2,
}
O = bits u32
{
access_mode: OAccessMode,
_: u4,
creat: u1,
excl: u1,
noctty: u1,
trunc: u1,
append: u1,
nonblock: u1,
dsync: u1,
async: u1,
direct: u1,
_: u1,
directory: u1,
nofollow: u1,
noatime: u1,
cloexec: u1,
sync: u1,
path: u1,
tmpfile: u1,
_: u9,
}
[extern] memcmp = fn [cc(c)] (a: &u8, b: &u8, byte_count: usize) int;
[extern] memcpy = fn [cc(c)] (destination: &u8, source: &u8, byte_count: u64) &u8;
[extern] exit = fn [cc(c)] (exit_code: int) noreturn;
[extern] realpath = fn [cc(c)] (source_path: &u8, resolved_path: &u8) &u8;
[extern] mkdir = fn [cc(c)] (path: &u8, mode: mode_t) int;
[extern] open = fn [cc(c)] (path: &u8, o: O, ...) int;
[extern] close = fn [cc(c)] (fd: File) int;
[extern] fstat = fn [cc(c)] (fd: File, s: &Stat) int;
[extern] write = fn [cc(c)] (fd: File, pointer: &u8, byte_count: u64) ssize;
[extern] read = fn [cc(c)] (fd: File, pointer: &u8, byte_count: u64) ssize;
assert = macro (ok: u1) void
{
if (!ok)
{
unreachable;
}
}
align_forward = fn (value: u64, alignment: u64) u64
{
assert(alignment != 0);
>mask = alignment - 1;
>result = (value + mask) & ~mask;
return result;
}
string_no_match = #integer_max(u64);
c_string_length = fn (c_string: &u8) u64
{
>it = c_string;
while (it.&)
{
it = it + 1;
}
return #int_from_pointer(it) - #int_from_pointer(c_string);
}
c_string_to_slice = fn (c_string: &u8) []u8
{
>length = c_string_length(c_string);
return c_string[0..length];
}
string_equal = fn(a: []u8, b: []u8) u1
{
>result: #ReturnType = 0;
if (a.length == b.length)
{
result = memcmp(a.pointer, b.pointer, a.length) == 0;
}
return result;
}
string_last_character = fn(string: []u8, character: u8) u64
{
>i = string.length;
while (i > 0)
{
i -= 1;
if (string[i] == character)
{
return i;
}
}
return string_no_match;
}
OS_Linux_PROT = bits u32
{
read: u1,
write: u1,
execute: u1,
sem: u1,
_: u28,
}
OS_Linux_MAP_Type = enum u4
{
shared = 0x1,
private = 0x2,
shared_validate = 0x3,
}
OS_Linux_MAP = bits u32
{
type: OS_Linux_MAP_Type,
fixed: u1,
anonymous: u1,
bit_32: u1,
_: u1,
grows_down: u1,
_: u2,
deny_write: u1,
executable: u1,
locked: u1,
no_reserve: u1,
populate: u1,
non_block: u1,
stack: u1,
huge_tlb: u1,
sync: u1,
fixed_noreplace: u1,
_: u5,
uninitialized: u1,
_: u5,
}
[extern] mmap = fn [cc(c)] (address: u64, size: u64, protection: OS_Linux_PROT, map: OS_Linux_MAP, file_descriptor: s32, offset: s64) &u8;
[extern] mprotect = fn [cc(c)] (address: u64, size: u64, protection: OS_Linux_PROT) s32;
OS_ProtectionFlags = bits
{
read: u1,
write: u1,
execute: u1,
}
OS_MapFlags = bits
{
private: u1,
anonymous: u1,
no_reserve: u1,
populate: u1,
}
os_linux_protection_flags = fn(map_flags: OS_ProtectionFlags) OS_Linux_PROT
{
return {
.read = map_flags.read,
.write = map_flags.write,
.execute = map_flags.execute,
zero,
};
}
os_linux_map_flags = fn(map_flags: OS_MapFlags) OS_Linux_MAP
{
return {
.type = #select(map_flags.private, .private, .shared),
.anonymous = map_flags.anonymous,
.no_reserve = map_flags.no_reserve,
.populate = map_flags.populate,
zero,
};
}
os_reserve = fn (base: u64, size: u64, protection: OS_ProtectionFlags, map: OS_MapFlags) &u8
{
>protection_flags = os_linux_protection_flags(protection);
>map_flags = os_linux_map_flags(map);
>address = mmap(base, size, protection_flags, map_flags, -1, 0);
if (#int_from_pointer(address) == #integer_max(u64))
{
unreachable;
}
return address;
}
os_commit = fn (address: u64, size: u64, protection: OS_ProtectionFlags) void
{
>protection_flags = os_linux_protection_flags(protection);
>result = mprotect(address, size, protection_flags);
if (result != 0)
{
unreachable;
}
}
os_make_directory = fn (path: &u8) void
{
>result = mkdir(path, 0o755);
}
OpenFlags = bits
{
truncate: u1,
execute: u1,
write: u1,
read: u1,
create: u1,
directory: u1,
}
OpenPermissions = bits
{
read: u1,
write: u1,
execute: u1,
}
os_file_open = fn (path: &u8, flags: OpenFlags, permissions: OpenPermissions) File
{
>access_mode: OAccessMode = undefined;
if (flags.read and flags.write)
{
access_mode = .read_write;
}
else if (flags.read)
{
access_mode = .read_only;
}
else if (flags.write)
{
access_mode = .write_only;
}
else
{
unreachable;
}
>o: O = {
.access_mode = access_mode,
.trunc = flags.truncate,
.creat = flags.create,
.directory = flags.directory,
zero,
};
>mode: mode_t = #select(permissions.execute, 0o755, 0o644);
>fd = open(path, o, mode);
return fd;
}
os_file_close = fn (fd: File) void
{
>result = close(fd);
assert(result == 0);
}
os_file_is_valid = fn (fd: File) u1
{
return fd >= 0;
}
os_file_get_size = fn (fd: File) u64
{
>stat: Stat = undefined;
>result = fstat(fd, &stat);
assert(result == 0);
return #extend(stat.size);
}
os_file_read_partially = fn (fd: File, pointer: &u8, length: u64) u64
{
>result = read(fd, pointer, length);
assert(result > 0);
return #extend(result);
}
os_file_read = fn (fd: File, buffer: []u8, byte_count: u64) void
{
assert(byte_count <= buffer.length);
>total_read_byte_count: u64 = 0;
while (total_read_byte_count < byte_count)
{
>read_byte_count = os_file_read_partially(fd, buffer.pointer + total_read_byte_count, byte_count - total_read_byte_count);
total_read_byte_count += read_byte_count;
}
}
os_path_absolute_stack = fn (buffer: []u8, relative_file_path: &u8) []u8
{
>syscall_result = realpath(relative_file_path, buffer.pointer);
>result: []u8 = zero;
if (syscall_result)
{
result = c_string_to_slice(syscall_result);
assert(result.length < buffer.length);
}
return result;
}
Arena = struct
{
reserved_size: u64,
position: u64,
os_position: u64,
granularity: u64,
reserved: [32]u8,
}
minimum_position: u64 = #byte_size(Arena);
ArenaInitialization = struct
{
reserved_size: u64,
granularity: u64,
initial_size: u64,
}
arena_initialize = fn (initialization: ArenaInitialization) &Arena
{
>protection_flags: OS_ProtectionFlags = {
.read = 1,
.write = 1,
zero,
};
>map_flags: OS_MapFlags = {
.private = 1,
.anonymous = 1,
.no_reserve = 1,
.populate = 0,
};
>arena: &Arena = #pointer_cast(os_reserve(0, initialization.reserved_size, protection_flags, map_flags));
os_commit(#int_from_pointer(arena), initialization.initial_size, {
.read = 1,
.write = 1,
zero,
});
arena.& = {
.reserved_size = initialization.reserved_size,
.position = minimum_position,
.os_position = initialization.initial_size,
.granularity = initialization.granularity,
zero,
};
return arena;
}
arena_initialize_default = fn (initial_size: u64) &Arena
{
return arena_initialize({
.reserved_size = 4 * 1024 * 1024 * 1024,
.granularity = 4 * 1024,
.initial_size = initial_size,
});
}
arena_allocate_bytes = fn (arena: &Arena, size: u64, alignment: u64) &u8
{
>aligned_offset = align_forward(arena.position, alignment);
>aligned_size_after = aligned_offset + size;
>arena_byte_pointer: &u8 = #pointer_cast(arena);
if (aligned_size_after > arena.os_position)
{
>target_committed_size = align_forward(aligned_size_after, arena.granularity);
>size_to_commit = target_committed_size - arena.os_position;
>commit_pointer = arena_byte_pointer + arena.os_position;
os_commit(#int_from_pointer(commit_pointer), size_to_commit, {
.read = 1,
.write = 1,
zero,
});
arena.os_position = target_committed_size;
}
>result = arena_byte_pointer + aligned_offset;
arena.position = aligned_size_after;
assert(arena.position <= arena.os_position);
return result;
}
arena_allocate = macro [T] (arena: &Arena, count: u64) &T
{
return #pointer_cast(arena_allocate_bytes(arena, #byte_size(T) * count, #align_of(T)));
}
arena_allocate_slice = macro [T] (arena: &Arena, count: u64) []T
{
>pointer: &T = #pointer_cast(arena_allocate_bytes(arena, #byte_size(T) * count, #align_of(T)));
return pointer[..count];
}
arena_duplicate_string = fn (arena: &Arena, string: []u8) []u8
{
>result = arena_allocate_bytes(arena, string.length + 1, 1);
memcpy(result, string.pointer, string.length);
return result[..string.length];
}
arena_join_string = fn (arena: &Arena, pieces: [][]u8) []u8
{
>size: u64 = 0;
for (p: pieces)
{
size += p.length;
}
>pointer = arena_allocate_bytes(arena, size + 1, 1);
>i: u64 = 0;
for (p: pieces)
{
memcpy(pointer + i, p.pointer, p.length);
i += p.length;
}
assert(i == size);
pointer[i] = 0;
return pointer[..size];
}
file_read = fn (arena: &Arena, path: []u8) []u8
{
>fd = os_file_open(path.pointer, { .read = 1, zero }, { .read = 1, zero });
>result: []u8 = zero;
if (os_file_is_valid(fd))
{
>file_size = os_file_get_size(fd);
>file_buffer = arena_allocate_bytes(arena, file_size, 1);
result = file_buffer[..file_size];
os_file_read(fd, result, file_size);
os_file_close(fd);
}
return result;
}
path_absolute = fn (arena: &Arena, relative_file_path: &u8) []u8
{
>buffer: [4096]u8 = undefined;
>stack_slice = os_path_absolute_stack(buffer[..], relative_file_path);
>result = arena_duplicate_string(arena, stack_slice);
return result;
}
GlobalState = struct
{
arena: &Arena,
}
global_state: GlobalState = undefined;
global_state_initialize = fn () void
{
global_state = {
.arena = arena_initialize_default(2 * 1024 * 1024),
};
}
fail = fn () noreturn
{
exit(1);
}
CompilerCommand = enum
{
compile,
test,
}
BuildMode = enum
{
debug_none,
debug,
soft_optimize,
optimize_for_speed,
optimize_for_size,
aggressively_optimize_for_speed,
aggressively_optimize_for_size,
}
CompileFile = struct
{
relative_file_path: []u8,
build_mode: BuildMode,
has_debug_info: u1,
silent: u1,
}
base_cache_dir = "bb-cache";
CPUArchitecture = enum
{
x86_64,
}
OperatingSystem = enum
{
linux,
}
Target = struct
{
cpu: CPUArchitecture,
os: OperatingSystem,
}
target_get_native = fn () Target
{
return { .cpu = .x86_64, .os = .linux };
}
CompileOptions = struct
{
content: []u8,
path: []u8,
executable: []u8,
name: []u8,
objects: [][]u8,
libraries: [][]u8,
target: Target,
build_mode: BuildMode,
has_debug_info: u1,
silent: u1,
}
CallingConvention = enum
{
c,
}
InlineBehavior = enum
{
default = 0,
always_inline = 1,
no_inline = 2,
inline_hint = 3,
}
FunctionAttributes = struct
{
inline_behavior: InlineBehavior,
naked: u1,
}
Type = struct;
Value = struct;
Local = struct;
Block = struct;
ScopeKind = enum
{
global,
function,
local,
for_each,
macro_declaration,
macro_instantiation,
}
TypeList = struct
{
first: &Type,
last: &Type,
}
Scope = struct
{
types: TypeList,
parent: &Scope,
line: u32,
column: u32,
kind: ScopeKind,
}
Variable = struct
{
storage: &Value,
type: &Type,
scope: &Scope,
name: []u8,
line: u32,
column: u32,
}
Linkage = enum
{
internal,
external,
}
Global = struct
{
variable: Variable,
initial_value: &Value,
next: &Global,
linkage: Linkage,
emitted: u1,
}
Local = struct
{
variable: Variable,
initial_value: &Value,
next: &Local,
}
Argument = struct
{
variable: Variable,
index: u32,
}
TypeId = enum
{
void,
noreturn,
forward_declaration,
integer,
function,
pointer,
array,
enum,
struct,
bits,
alias,
union,
unresolved,
vector,
floating_point,
enum_array,
opaque,
}
TypeInteger = struct
{
bit_count: u32,
signed: u1,
}
TypePointer = struct
{
element_type: &Type,
next: &Type,
}
AbiRegisterCountSystemV = struct
{
gpr: u32,
sse: u32,
};
AbiRegisterCount = union
{
system_v: AbiRegisterCountSystemV,
};
AbiInformation = struct
{
foo: u32,
}
TypeFunction = struct
{
semantic_return_type: &Type,
semantic_argument_types: []&Type,
calling_convention: CallingConvention,
is_variable_arguments: u1,
abi_argument_types: []&Type,
abi_return_type: &Type,
available_registers: AbiRegisterCount,
argument_abis: []AbiInformation,
return_abi: AbiInformation,
}
TypeContent = union
{
integer: TypeInteger,
function: TypeFunction,
pointer: TypePointer,
}
Type = struct
{
content: TypeContent,
id: TypeId,
name: []u8,
next: &Type,
scope: &Scope,
}
ValueId = enum
{
infer_or_ignore,
external_function,
function,
}
ValueConstantInteger = struct
{
value: u64,
is_signed: u1,
}
ValueFunction = struct
{
arguments: []Argument,
scope: Scope,
block: &Block,
attributes: FunctionAttributes,
}
ValueContent = union
{
constant_integer: ValueConstantInteger,
function: ValueFunction,
}
ValueKind = enum
{
right,
left,
}
Value = struct
{
content: ValueContent,
type: &Type,
id: ValueId,
kind: ValueKind,
}
i128_offset: u64 = 64 * 2;
void_offset: u64 = i128_offset + 2;
MacroDeclaration = struct
{
foo: u32,
}
MacroInstantiation = struct
{
foo: u32,
}
LLVMContext = opaque;
LLVMModule = opaque;
LLVMBuilder = opaque;
LLVMDIBuilder = opaque;
LLVMValue = opaque;
LLVMType = opaque;
LLVMMetadata = opaque;
LLVMBasicBlock = opaque;
LLVMIntrinsicId = typealias u32;
LLVMIntrinsicIndex = enum
{
trap,
va_start,
va_end,
va_copy,
}
ModuleLLVM = struct
{
context: &LLVMContext,
module: &LLVMModule,
builder: &LLVMBuilder,
di_builder: &LLVMDIBuilder,
file: &LLVMMetadata,
compile_unit: &LLVMMetadata,
pointer_type: &LLVMType,
void_type: &LLVMType,
intrinsic_table: enum_array[LLVMIntrinsicIndex](LLVMIntrinsicId),
memcmp: &LLVMValue,
inlined_at: &LLVMMetadata,
continue_block: &LLVMBasicBlock,
exit_block: &LLVMBasicBlock,
debug_tag: u32,
}
Module = struct
{
arena: &Arena,
content: []u8,
offset: u64,
line_offset: u64,
line_character_offset: u64,
first_pointer_type: &Type,
first_slice_type: &Type,
first_pair_struct_type: &Type,
first_array_type: &Type,
va_list_type: &Type,
void_value: &Value,
first_global: &Global,
last_global: &Global,
first_macro_declaration: &MacroDeclaration,
last_macro_declaration: &MacroDeclaration,
current_function: &Global,
current_macro_declaration: &MacroDeclaration,
current_macro_instantiation: &MacroInstantiation,
llvm: ModuleLLVM,
scope: Scope,
name: []u8,
path: []u8,
executable: []u8,
objects: [][]u8,
libraries: [][]u8,
target: Target,
build_mode: BuildMode,
has_debug_info: u1,
silent: u1,
}
Statement = struct;
StatementId = enum
{
local,
expression,
return,
assignment,
if,
block,
while,
switch,
for,
break,
continue,
}
StatementAssignmentId = enum
{
assign,
assign_add,
assign_sub,
assign_mul,
assign_div,
assign_rem,
assign_shift_left,
assign_shift_right,
assign_and,
assign_or,
assign_xor,
}
StatementAssignment = struct
{
left: &Value,
right: &Value,
id: StatementAssignmentId,
}
StatementIf = struct
{
condition: &Value,
if: &Statement,
else: &Statement,
}
Block = struct
{
first_local: &Local,
last_local: &Local,
first_statement: &Statement,
scope: Scope,
}
StatementWhile = struct
{
condition: &Value,
block: &Block,
}
StatementSwitchClause = struct
{
values: []&Value,
block: &Block,
}
StatementSwitch = struct
{
discriminant: &Value,
clauses: []StatementSwitchClause,
}
StatementForKind = enum
{
slice,
range,
}
StatementFor = struct
{
first_local: &Local,
last_local: &Local,
kinds: ValueKind,
iteratables: &Value,
predicate: &Statement,
scope: Scope,
kind: StatementForKind,
}
StatementContent = union
{
local: &Local,
expression: &Value,
return: &Value,
assignment: StatementAssignment,
if: StatementIf,
block: &Block,
while: StatementWhile,
switch: StatementSwitch,
for: StatementFor,
}
Statement = struct
{
content: StatementContent,
next: &Statement,
line: u32,
column: u32,
id: StatementId,
}
new_global = fn (module: &Module) &Global
{
>result = arena_allocate[Global](module.arena, 1);
if (module.last_global)
{
assert(module.first_global != zero);
module.last_global.next = result;
module.last_global = result;
}
else
{
assert(module.first_global == zero);
module.first_global = result;
module.last_global = result;
}
return result;
}
new_type = fn (module: &Module, type: Type) &Type
{
>result = arena_allocate[Type](module.arena, 1);
result.& = type;
>scope = type.scope;
assert(scope != zero);
if (scope.types.last)
{
assert(scope.types.first != zero);
scope.types.last.next = result;
scope.types.last = result;
}
else
{
assert(scope.types.first == zero);
scope.types.first = result;
scope.types.last = result;
}
return result;
}
integer_type = fn (module: &Module, integer: TypeInteger) &Type
{
assert(integer.bit_count != 0);
assert(integer.bit_count <= 64);
>index = #select(integer.bit_count == 128, i128_offset + #extend(integer.signed), #extend(integer.bit_count) - 1 + 64 * #extend(integer.signed));
>result = module.scope.types.first + index;
assert(result.id == .integer);
assert(result.content.integer.bit_count == integer.bit_count);
assert(result.content.integer.signed == integer.signed);
return result;
}
void_type = fn (module: &Module) &Type
{
return module.scope.types.first + void_offset;
}
noreturn_type = fn (module: &Module) &Type
{
return void_type(module) + 1;
}
get_pointer_type = fn (module: &Module, element_type: &Type) &Type
{
>last_pointer_type = module.first_pointer_type;
while (last_pointer_type)
{
assert(last_pointer_type.id == .pointer);
if (last_pointer_type.content.pointer.element_type == element_type)
{
return last_pointer_type;
}
if (!last_pointer_type.content.pointer.next)
{
break;
}
last_pointer_type = last_pointer_type.content.pointer.next;
}
>result = new_type(module, {
.content = {
.pointer = {
.element_type = element_type,
zero,
},
},
.id = .pointer,
.name = arena_join_string(module.arena, [ "&", element_type.name ][..]),
.scope = element_type.scope,
zero,
});
if (last_pointer_type)
{
assert(module.first_pointer_type != zero);
last_pointer_type.content.pointer.next = result;
}
else
{
assert(!module.first_pointer_type);
module.first_pointer_type = result;
}
return result;
}
is_space = fn (ch: u8) u1
{
return ch == ' ' or ch == '\n' or ch == '\t' or ch == '\r';
}
is_decimal = fn (ch: u8) u1
{
return ch >= '0' and ch <= '9';
}
is_octal = fn (ch: u8) u1
{
return ch >= '0' and ch <= '7';
}
is_binary = fn (ch: u8) u1
{
return ch == '0' or ch == '1';
}
is_hex_alpha_lower = fn (ch: u8) u1
{
return ch >= 'a' and ch <= 'f';
}
is_hex_alpha_upper = fn (ch: u8) u1
{
return ch >= 'A' and ch <= 'F';
}
is_hex_alpha = fn (ch: u8) u1
{
return is_hex_alpha_lower(ch) or is_hex_alpha_upper(ch);
}
is_hex = fn (ch: u8) u1
{
return is_decimal(ch) or is_hex_alpha(ch);
}
is_identifier_start = fn (ch: u8) u1
{
return (ch >= 'a' and ch <= 'z') or (ch >= 'A' and ch <= 'Z') or ch == '_';
}
is_identifier = fn (ch: u8) u1
{
return is_identifier_start(ch) or is_decimal(ch);
}
report_error = fn () noreturn
{
#trap();
}
get_line = fn (module: &Module) u32
{
>line = module.line_offset + 1;
assert(line <= #integer_max(u32));
return #truncate(line);
}
get_column = fn (module: &Module) u32
{
>column = module.offset - module.line_character_offset + 1;
assert(column <= #integer_max(u32));
return #truncate(column);
}
Checkpoint = struct
{
offset: u64,
line_offset: u64,
line_character_offset: u64,
}
get_checkpoint = fn (module: &Module) Checkpoint
{
return {
.offset = module.offset,
.line_offset = module.line_offset,
.line_character_offset = module.line_character_offset,
};
}
set_checkpoint = fn (module: &Module, checkpoint: Checkpoint) void
{
module.offset = checkpoint.offset;
module.line_offset = checkpoint.line_offset;
module.line_character_offset = checkpoint.line_character_offset;
}
skip_space = fn (module: &Module) void
{
while (1)
{
>iteration_offset = module.offset;
while (module.offset < module.content.length and? is_space(module.content[module.offset]))
{
module.line_offset += #extend(module.content[module.offset] == '\n');
module.line_character_offset = #select(module.content[module.offset] == '\n', module.offset, module.line_character_offset);
module.offset += 1;
}
if (module.offset + 1 < module.content.length)
{
>i = module.offset;
>is_comment = module.content[i] == '/' and module.content[i + 1] == '/';
if (is_comment)
{
while (module.offset < module.content.length and? module.content[module.offset] != '\n')
{
module.offset += 1;
}
if (module.offset < module.content.length)
{
module.line_offset += 1;
module.line_character_offset = module.offset;
module.offset += 1;
}
}
}
if (module.offset - iteration_offset == 0)
{
break;
}
}
}
consume_character_if_match = fn (module: &Module, expected_character: u8) u1
{
>is_ch: u1 = 0;
if (module.offset < module.content.length)
{
>ch = module.content[module.offset];
is_ch = expected_character == ch;
module.offset += #extend(is_ch);
}
return is_ch;
}
expect_character = fn (module: &Module, expected_character: u8) void
{
if (!consume_character_if_match(module, expected_character))
{
report_error();
}
}
parse_identifier = fn (module: &Module) []u8
{
>start = module.offset;
if (is_identifier_start(module.content[start]))
{
module.offset += 1;
while (module.offset < module.content.length)
{
if (is_identifier(module.content[module.offset]))
{
module.offset += 1;
}
else
{
break;
}
}
}
if (module.offset - start == 0)
{
report_error();
}
>result = module.content[start..module.offset];
return result;
}
new_value = fn (module: &Module) &Value
{
>value = arena_allocate[Value](module.arena, 1);
return value;
}
GlobalAttributeKeyword = enum
{
export,
extern,
}
GlobalKeyword = enum
{
bits,
enum,
fn,
macro,
opaque,
struct,
typealias,
union,
}
FunctionTypeAttribute = enum
{
cc,
}
left_bracket: u8 = '[';
right_bracket: u8 = ']';
left_parenthesis: u8 = '(';
right_parenthesis: u8 = ')';
left_brace: u8 = '{';
right_brace: u8 = '}';
accumulate_decimal = fn(accumulator: u64, ch: u8) u64
{
assert(is_decimal(ch));
return (accumulator * 10) + #extend(ch - '0');
}
parse_integer_decimal_assume_valid = fn (string: []u8) u64
{
>value: u64 = 0;
for (ch: string)
{
value = accumulate_decimal(value, ch);
}
return value;
}
TypeKeyword = enum
{
void,
noreturn,
enum_array,
}
parse_type = fn (module: &Module, scope: &Scope) &Type
{
>start_character = module.content[module.offset];
if (is_identifier_start(start_character))
{
>identifier = parse_identifier(module);
>type_keyword_s2e = #string_to_enum(TypeKeyword, identifier);
if (type_keyword_s2e.is_valid)
{
>type_keyword = type_keyword_s2e.enum_value;
switch (type_keyword)
{
.void =>
{
#trap();
},
.noreturn =>
{
#trap();
},
.enum_array =>
{
#trap();
},
}
}
else
{
>is_integer_type = identifier.length > 1 and (identifier[0] == 's' or identifier[0] == 'u');
if (is_integer_type)
{
for (ch: identifier[1..])
{
is_integer_type = is_integer_type and is_decimal(ch);
}
}
if (is_integer_type)
{
>is_signed: u1 = undefined;
switch (identifier[0])
{
's' =>
{
is_signed = 1;
},
'u' =>
{
is_signed = 0;
},
else =>
{
unreachable;
},
}
>bit_count = parse_integer_decimal_assume_valid(identifier[1..]);
if (bit_count == 0)
{
report_error();
}
if (bit_count > 64)
{
if (bit_count != 128)
{
report_error();
}
}
>result = integer_type(module, { .bit_count = #truncate(bit_count), .signed = is_signed });
return result;
}
else
{
//>type = module.first_type;
//while (type)
//{
// if (string_equal(identifier, type.name))
// {
// return type;
// }
// type = type.next;
//}
report_error();
}
}
}
else if (start_character == '&')
{
#trap();
}
else if (start_character == left_bracket)
{
#trap();
}
else if (start_character == '#')
{
#trap();
}
else
{
report_error();
}
}
parse_statement = fn (module: &Module, scope: &Scope) &Statement
{
#trap();
}
parse_block = fn (module: &Module, parent_scope: &Scope) &Block
{
>block = arena_allocate[Block](module.arena, 1);
block.& = {
.scope = {
.parent = parent_scope,
.line = get_line(module),
.column = get_column(module),
.kind = .local,
zero,
},
zero,
};
>scope = &block.scope;
expect_character(module, left_brace);
>current_statement: &Statement = zero;
while (1)
{
skip_space(module);
if (module.offset == module.content.length)
{
break;
}
if (consume_character_if_match(module, right_brace))
{
break;
}
>statement = parse_statement(module, scope);
if (!block.first_statement)
{
block.first_statement = statement;
}
if (current_statement)
{
current_statement.next = statement;
}
assert(!statement.next);
current_statement = statement;
}
return block;
}
parse = fn (module: &Module) void
{
>scope = &module.scope;
while (1)
{
skip_space(module);
if (module.offset == module.content.length)
{
break;
}
>is_export: u1 = 0;
>is_extern: u1 = 0;
>global_line = get_line(module);
>global_column = get_line(module);
if (consume_character_if_match(module, left_bracket))
{
while (module.offset < module.content.length)
{
>global_attribute_keyword_string = parse_identifier(module);
>global_attribute_keyword_s2e = #string_to_enum(GlobalAttributeKeyword, global_attribute_keyword_string);
if (!global_attribute_keyword_s2e.is_valid)
{
report_error();
}
>global_attribute_keyword = global_attribute_keyword_s2e.enum_value;
switch (global_attribute_keyword)
{
.export =>
{
is_export = 1;
},
.extern =>
{
is_extern = 1;
},
}
if (consume_character_if_match(module, right_bracket))
{
break;
}
else
{
report_error();
}
}
skip_space(module);
}
>global_name = parse_identifier(module);
>last_global = module.first_global;
while (last_global)
{
if (string_equal(global_name, last_global.variable.name))
{
report_error();
}
if (!last_global.next)
{
break;
}
last_global = last_global.next;
}
>type_it = module.scope.types.first;
>forward_declaration: &Type = zero;
while (type_it)
{
if (string_equal(global_name, type_it.name))
{
if (type_it.id == .forward_declaration)
{
forward_declaration = type_it;
break;
}
else
{
report_error();
}
}
if (!type_it.next)
{
break;
}
type_it = type_it.next;
}
skip_space(module);
>global_type: &Type = zero;
if (consume_character_if_match(module, ':'))
{
skip_space(module);
global_type = parse_type(module, scope);
skip_space(module);
}
expect_character(module, '=');
skip_space(module);
>is_global_keyword: u1 = 0;
if (is_identifier_start(module.content[module.offset]))
{
>checkpoint = get_checkpoint(module);
>global_keyword_string = parse_identifier(module);
skip_space(module);
>global_keyword_s2e = #string_to_enum(GlobalKeyword, global_keyword_string);
is_global_keyword = global_keyword_s2e.is_valid;
if (is_global_keyword)
{
>global_keyword = global_keyword_s2e.enum_value;
switch (global_keyword)
{
.bits =>
{
#trap();
},
.enum =>
{
#trap();
},
.fn =>
{
>calling_convention: CallingConvention = .c;
>function_attributes: FunctionAttributes = zero;
>is_variable_arguments: u1 = 0;
if (consume_character_if_match(module, left_bracket))
{
while (module.offset < module.content.length)
{
>function_identifier = parse_identifier(module);
>function_type_attribute_s2e = #string_to_enum(FunctionTypeAttribute, function_identifier);
if (!function_type_attribute_s2e.is_valid)
{
report_error();
}
>function_type_attribute = function_type_attribute_s2e.enum_value;
switch (function_type_attribute)
{
.cc =>
{
expect_character(module, left_parenthesis);
skip_space(module);
>calling_convention_string = parse_identifier(module);
>calling_convention_s2e = #string_to_enum(CallingConvention, calling_convention_string);
if (!calling_convention_s2e.is_valid)
{
report_error();
}
>candidate_calling_convention = calling_convention_s2e.enum_value;
calling_convention = candidate_calling_convention;
skip_space(module);
expect_character(module, right_parenthesis);
},
}
skip_space(module);
if (consume_character_if_match(module, right_bracket))
{
break;
}
else
{
report_error();
}
}
}
skip_space(module);
expect_character(module, left_parenthesis);
>semantic_argument_type_buffer: [64]&Type = undefined;
>semantic_argument_name_buffer: [64][]u8 = undefined;
>argument_line_buffer: [64]u32 = undefined;
>semantic_argument_count: u64 = 0;
while (module.offset < module.content.length)
{
skip_space(module);
if (consume_character_if_match(module, '.'))
{
#trap();
}
if (consume_character_if_match(module, right_parenthesis))
{
break;
}
>line = get_line(module);
argument_line_buffer[semantic_argument_count] = line;
>argument_name = parse_identifier(module);
semantic_argument_name_buffer[semantic_argument_count] = argument_name;
skip_space(module);
expect_character(module, ':');
skip_space(module);
>argument_type = parse_type(module, scope);
semantic_argument_type_buffer[semantic_argument_count] = argument_type;
skip_space(module);
consume_character_if_match(module, '.');
semantic_argument_count += 1;
}
skip_space(module);
>return_type = parse_type(module, scope);
skip_space(module);
>argument_types: []&Type = zero;
if (semantic_argument_count != 0)
{
#trap();
}
>is_declaration = consume_character_if_match(module, ';');
>function_type = new_type(module, {
.content = {
.function = {
.semantic_return_type = return_type,
.semantic_argument_types = argument_types,
.calling_convention = calling_convention,
.is_variable_arguments = is_variable_arguments,
zero,
},
},
.id = .function,
.name = "",
.scope = &module.scope,
zero,
});
>storage = new_value(module);
storage.& = {
.id = .external_function,
.type = get_pointer_type(module, function_type),
zero,
// TODO? kind = .left,
};
>global = new_global(module);
global.& = {
.variable = {
.storage = storage,
.type = function_type,
.scope = scope,
.name = global_name,
.line = global_line,
.column = global_column,
},
.linkage = #select(is_export or is_extern, .external, .internal),
zero,
};
if (!is_declaration)
{
module.current_function = global;
>arguments = arena_allocate_slice[Argument](module.arena, semantic_argument_count);
for (i: 0..semantic_argument_count)
{
>argument = &arguments[i];
>name = semantic_argument_name_buffer[i];
>type = semantic_argument_type_buffer[i];
>line = argument_line_buffer[i];
#trap();
}
storage.content.function = {
.arguments = arguments,
.scope = {
.parent = scope,
.line = global_line,
.column = global_column,
.kind = .function,
zero,
},
.attributes = function_attributes,
zero,
};
storage.id = .function;
storage.content.function.block = parse_block(module, &storage.content.function.scope);
module.current_function = zero;
}
},
.macro =>
{
#trap();
},
.opaque =>
{
#trap();
},
.struct =>
{
#trap();
},
.typealias =>
{
#trap();
},
.union =>
{
#trap();
},
}
}
else
{
set_checkpoint(module, checkpoint);
}
}
if (!is_global_keyword)
{
#trap();
}
}
}
emit = fn (module: &Module) void
{
}
compile = fn (arena: &Arena, options: CompileOptions) void
{
>signs: [2]u1 = [0, 1];
>base_type_allocation = arena_allocate[Type](arena,
i128_offset // Basic integer types
+ 2 // u128, s128
+ 2 // void, noreturn
);
>type_it = base_type_allocation;
>previous: &Type = zero;
for (sign: signs)
{
for (b: 0..64)
{
>bit_count = b + 1;
>first_digit: u8 = #truncate(#select(bit_count < 10, bit_count % 10 + '0', bit_count / 10 + '0'));
>second_digit: u8 = #truncate(#select(bit_count > 9, bit_count % 10 + '0', 0));
>name_buffer: [3]u8 = [ #select(sign, 's', 'u'), first_digit, second_digit ];
>name_length: u64 = 2 + #extend(bit_count > 9);
>name = arena_duplicate_string(arena, name_buffer[..name_length]);
type_it.& = {
.content = {
.integer = {
.bit_count = #truncate(bit_count),
.signed = sign,
},
},
.id = .integer,
.name = name,
zero,
};
if (previous)
{
previous.next = type_it;
}
previous = type_it;
type_it += 1;
}
}
for (sign: signs)
{
>name = #select(sign, "s128", "u128");
type_it.& = {
.content = {
.integer = {
.bit_count = 128,
.signed = sign,
},
},
.id = .integer,
.name = name,
zero,
};
previous.next = type_it;
previous = type_it;
type_it += 1;
}
>void_type = type_it;
type_it += 1;
>noreturn_type = type_it;
type_it += 1;
previous.next = void_type;
void_type.& = {
.id = .void,
.name = "void",
.next = noreturn_type,
zero,
};
noreturn_type.& = {
.id = .noreturn,
.name = "noreturn",
zero,
};
>void_value = arena_allocate[Value](arena, 1);
void_value.& = {
.id = .infer_or_ignore,
.type = void_type,
zero,
};
>module: Module = {
.arena = arena,
.content = options.content,
.void_value = void_value,
.name = options.name,
.path = options.path,
.executable = options.executable,
.objects = options.objects,
.libraries = options.libraries,
.target = options.target,
.build_mode = options.build_mode,
.has_debug_info = options.has_debug_info,
.silent = options.silent,
zero,
};
module.scope.types.first = base_type_allocation;
module.scope.types.last = noreturn_type;
parse(&module);
emit(&module);
}
compile_file = fn (arena: &Arena, compile_options: CompileFile) void
{
>relative_file_path = compile_options.relative_file_path;
if (relative_file_path.length < 5)
{
fail();
}
>extension_start = string_last_character(relative_file_path, '.');
if (extension_start == string_no_match)
{
fail();
}
if (!string_equal(relative_file_path[extension_start..], ".bbb"))
{
fail();
}
>separator_index = string_last_character(relative_file_path, '/');
if (separator_index == string_no_match)
{
separator_index = 0;
}
>base_start = separator_index + #extend(separator_index != 0 or relative_file_path[separator_index] == '/');
>base_name = relative_file_path[base_start..extension_start];
>is_compiler = string_equal(relative_file_path, "src/compiler.bbb");
>outputh_path_dir = arena_join_string(arena, [
base_cache_dir,
#select(is_compiler, "/compiler/", "/"),
#enum_name(compile_options.build_mode),
"_",
#select(compile_options.has_debug_info, "di", "nodi"),
][..]);
os_make_directory(base_cache_dir.pointer);
if (is_compiler)
{
>compiler_dir = arena_join_string(arena, [ base_cache_dir, "/compiler" ][..]);
os_make_directory(compiler_dir.pointer);
}
os_make_directory(outputh_path_dir.pointer);
>outputh_path_base = arena_join_string(arena, [ outputh_path_dir, "/", base_name ][..]);
>output_object_path = arena_join_string(arena, [ outputh_path_base, ".o" ][..]);
>output_executable_path = outputh_path_base;
>file_content = file_read(arena, relative_file_path);
>file_path = path_absolute(arena, relative_file_path.pointer);
>c_abi_object_path = ""; // TODO
>objects: [][]u8 = undefined;
objects = [ output_object_path ][..];
>options: CompileOptions = {
.executable = output_executable_path,
.objects = objects,
.libraries = zero,
.name = base_name,
.build_mode = compile_options.build_mode,
.content = file_content,
.path = file_path,
.has_debug_info = compile_options.has_debug_info,
.target = target_get_native(),
.silent = compile_options.silent,
};
compile(arena, options);
}
[export] main = fn [cc(c)] (argument_count: u32, argv: &&u8) s32
{
global_state_initialize();
>arena = global_state.arena;
if (argument_count < 2)
{
return 1;
}
>command_string = c_string_to_slice(argv[1]);
>command_string_to_enum = #string_to_enum(CompilerCommand, command_string);
if (!command_string_to_enum.is_valid)
{
return 1;
}
>command = command_string_to_enum.enum_value;
switch (command)
{
.compile =>
{
if (argument_count < 3)
{
return 1;
}
>build_mode: BuildMode = .debug_none;
>has_debug_info: u1 = 1;
if (argument_count >= 4)
{
>build_mode_string_to_enum = #string_to_enum(BuildMode, c_string_to_slice(argv[3]));
if (!build_mode_string_to_enum.is_valid)
{
return 1;
}
build_mode = build_mode_string_to_enum.enum_value;
}
if (argument_count >= 5)
{
>has_debug_info_string = c_string_to_slice(argv[4]);
if (string_equal(has_debug_info_string, "true"))
{
has_debug_info = 1;
}
else if (string_equal(has_debug_info_string, "false"))
{
has_debug_info = 0;
}
else
{
return 1;
}
}
>relative_file_path_pointer = argv[2];
if (!relative_file_path_pointer)
{
return 1;
}
>relative_file_path = c_string_to_slice(relative_file_path_pointer);
compile_file(arena, {
.relative_file_path = relative_file_path,
.build_mode = build_mode,
.has_debug_info = has_debug_info,
.silent = 0,
});
},
.test =>
{
// TODO
#trap();
},
}
return 0;
}