浏览代码

Initial Commit
Original Git Repo "went bad" so I had to restart.

gingerBill 9 年之前
当前提交
7430008fd7
共有 20 个文件被更改,包括 17332 次插入0 次删除
  1. 4 0
      .gitignore
  2. 70 0
      build.bat
  3. 25 0
      libs.txt
  4. 6 0
      src/checker.cpp
  5. 457 0
      src/checker/checker.cpp
  6. 66 0
      src/checker/entity.cpp
  7. 1709 0
      src/checker/expression.cpp
  8. 695 0
      src/checker/statements.cpp
  9. 576 0
      src/checker/type.cpp
  10. 365 0
      src/checker/value.cpp
  11. 221 0
      src/common.cpp
  12. 10331 0
      src/gb/gb.h
  13. 77 0
      src/generator.cpp
  14. 41 0
      src/main.cpp
  15. 1591 0
      src/parser.cpp
  16. 184 0
      src/printer.cpp
  17. 9 0
      src/test.odin
  18. 760 0
      src/tokenizer.cpp
  19. 45 0
      syntax.odin
  20. 100 0
      todo.md

+ 4 - 0
.gitignore

@@ -0,0 +1,4 @@
+bin/
+misc/
+*.sln
+*.suo

+ 70 - 0
build.bat

@@ -0,0 +1,70 @@
+@echo off
+
+set base_dir=W:\Odin
+:: Make sure this is a decent name and not generic
+set exe_name=odin.exe
+
+:: Debug = 0, Release = 1
+set release_mode=0
+
+set compiler_flags= -nologo -Oi -TP -W4 -fp:fast -fp:except- -Gm- -MP -FC -Z7 -GS- -EHsc- -GR-
+
+if %release_mode% EQU 0 ( rem Debug
+	set compiler_flags=%compiler_flags% -Od -MDd -Z7
+) else ( rem Release
+	set compiler_flags=%compiler_flags% -O2 -MT
+)
+
+set compiler_warnings= ^
+	-we4013 -we4706 ^
+	-wd4100 -wd4127 -wd4189 ^
+	-wd4201 -wd4204 -wd4244 ^
+	-wd4306 ^
+	-wd4480 ^
+	-wd4505 -wd4512 -wd4550 ^
+
+set compiler_includes= ^
+	rem -I"C:\Program Files\LLVM\include"
+
+set libs= kernel32.lib user32.lib gdi32.lib opengl32.lib ^
+
+	rem -libpath:"C:\Program Files\LLVM\lib" ^
+	rem LLVMCodeGen.lib ^
+	rem LLVMTarget.lib ^
+	rem LLVMBitWriter.lib ^
+	rem LLVMAnalysis.lib ^
+	rem LLVMObject.lib ^
+	rem LLVMMCParser.lib ^
+	rem LLVMBitReader.lib ^
+	rem LLVMMC.lib ^
+	rem LLVMCore.lib ^
+	rem LLVMSupport.lib
+
+
+
+
+set linker_flags= -incremental:no -opt:ref -subsystem:console
+
+rem Debug
+if %release_mode% EQU 0 (set linker_flags=%linker_flags% -debug)
+
+set compiler_settings=%compiler_flags% %compiler_warnings% %compiler_includes%
+set linker_settings=%libs% %linker_flags%
+
+set build_dir= "%base_dir%\bin\"
+if not exist %build_dir% mkdir %build_dir%
+pushd %build_dir%
+	del *.pdb > NUL 2> NUL
+	del *.ilk > NUL 2> NUL
+
+	del ..\misc\*.pdb > NUL 2> NUL
+	del ..\misc\*.ilk > NUL 2> NUL
+
+	cl %compiler_settings% "%base_dir%\src\main.cpp" ^
+		/link %linker_settings% -OUT:%exe_name% ^
+		&& call run.bat
+
+	:do_not_compile_exe
+popd
+:end_of_build
+

+ 25 - 0
libs.txt

@@ -0,0 +1,25 @@
+LLVMX86Disassembler.lib ^
+LLVMX86AsmParser.lib ^
+LLVMX86CodeGen.lib ^
+LLVMSelectionDAG.lib ^
+LLVMAsmPrinter.lib ^
+LLVMCodeGen.lib ^
+LLVMTarget.lib ^
+LLVMScalarOpts.lib ^
+LLVMInstCombine.lib ^
+LLVMInstrumentation.lib ^
+LLVMProfileData.lib ^
+LLVMTransformUtils.lib ^
+LLVMBitWriter.lib ^
+LLVMAnalysis.lib ^
+LLVMX86Desc.lib ^
+LLVMObject.lib ^
+LLVMMCParser.lib ^
+LLVMBitReader.lib ^
+LLVMMCDisassembler.lib ^
+LLVMX86Info.lib ^
+LLVMX86AsmPrinter.lib ^
+LLVMMC.lib ^
+LLVMX86Utils.lib ^
+LLVMCore.lib ^
+LLVMSupport.lib ^

+ 6 - 0
src/checker.cpp

@@ -0,0 +1,6 @@
+#include "checker/value.cpp"
+#include "checker/entity.cpp"
+#include "checker/type.cpp"
+#include "checker/checker.cpp"
+#include "checker/expression.cpp"
+#include "checker/statements.cpp"

+ 457 - 0
src/checker/checker.cpp

@@ -0,0 +1,457 @@
+enum AddressingMode {
+	Addressing_Invalid,
+
+	Addressing_NoValue,
+	Addressing_Value,
+	Addressing_Variable,
+	Addressing_Constant,
+	Addressing_Type,
+	Addressing_Builtin,
+
+	Addressing_Count,
+};
+
+struct Operand {
+	AddressingMode mode;
+	Type *type;
+	Value value;
+
+	AstNode *expression;
+	i32 builtin_id;
+};
+
+struct TypeAndValue {
+	AddressingMode mode;
+	Type *type;
+	Value value;
+};
+
+struct ExpressionInfo {
+	b32 is_lhs; // Debug info
+	AddressingMode mode;
+	Type *type; // Type_Basic
+	Value value;
+};
+
+ExpressionInfo make_expression_info(b32 is_lhs, AddressingMode mode, Type *type, Value value) {
+	ExpressionInfo ei = {};
+	ei.is_lhs = is_lhs;
+	ei.mode   = mode;
+	ei.type   = type;
+	ei.value  = value;
+	return ei;
+}
+
+struct Scope {
+	Scope *parent;
+	gbArray(Scope *) children; // TODO(bill): Remove and make into a linked list
+	Map<Entity *>    elements; // Key: String
+};
+
+enum ExpressionKind {
+	Expression_Expression,
+	Expression_Conversion,
+	Expression_Statement,
+};
+
+struct BuiltinProcedure {
+	String name;
+	isize arg_count;
+	b32 variadic;
+	ExpressionKind kind;
+};
+
+enum BuiltinProcedureId {
+	BuiltinProcedure_Invalid,
+
+	BuiltinProcedure_size_of,
+	BuiltinProcedure_size_of_val,
+	BuiltinProcedure_align_of,
+	BuiltinProcedure_align_of_val,
+	BuiltinProcedure_offset_of,
+	BuiltinProcedure_offset_of_val,
+	BuiltinProcedure_static_assert,
+	BuiltinProcedure_print,
+	BuiltinProcedure_println,
+
+	BuiltinProcedure_Count,
+};
+
+struct Checker {
+	Parser *            parser;
+	Map<TypeAndValue>   types;       // Key: AstNode * | Expression -> Type (and value)
+	Map<Entity *>       definitions; // Key: AstNode * | Identifier -> Entity
+	Map<Entity *>       uses;        // Key: AstNode * | Identifier -> Entity (Anonymous field)
+	Map<Scope *>        scopes;      // Key: AstNode * | Node       -> Scope
+	Map<ExpressionInfo> untyped;     // Key: AstNode * | Expression -> ExpressionInfo
+	BaseTypeSizes       sizes;
+	Scope *             file_scope;
+
+	gbArena entity_arena;
+
+	Scope *curr_scope;
+	gbArray(Type *) procedure_stack;
+	b32 in_defer;
+
+#define MAX_CHECKER_ERROR_COUNT 10
+	isize error_prev_line;
+	isize error_prev_column;
+	isize error_count;
+};
+
+
+gb_global Scope *global_scope = NULL;
+
+gb_global BuiltinProcedure builtin_procedures[BuiltinProcedure_Count] = {
+	{STR_LIT(""),                 0, false, Expression_Statement},
+	{STR_LIT("size_of"),          1, false, Expression_Expression},
+	{STR_LIT("size_of_val"),      1, false, Expression_Expression},
+	{STR_LIT("align_of"),         1, false, Expression_Expression},
+	{STR_LIT("align_of_val"),     1, false, Expression_Expression},
+	{STR_LIT("offset_of"),        2, false, Expression_Expression},
+	{STR_LIT("offset_of_val"),    1, false, Expression_Expression},
+	{STR_LIT("static_assert"),    1, false, Expression_Statement},
+	{STR_LIT("print"),            1, true,  Expression_Statement},
+	{STR_LIT("println"),          1, true,  Expression_Statement},
+};
+
+
+// TODO(bill): Arena allocation
+Scope *make_scope(Scope *parent) {
+	gbAllocator a = gb_heap_allocator();
+	Scope *s = gb_alloc_item(a, Scope);
+	s->parent = parent;
+	gb_array_init(s->children, a);
+	map_init(&s->elements, a);
+	if (parent != NULL && parent != global_scope)
+		gb_array_append(parent->children, s);
+	return s;
+}
+
+void destroy_scope(Scope *scope) {
+	for (isize i = 0; i < gb_array_count(scope->children); i++) {
+		destroy_scope(scope->children[i]);
+	}
+	map_destroy(&scope->elements);
+	gb_array_free(scope->children);
+	gb_free(gb_heap_allocator(), scope);
+}
+
+
+void scope_lookup_parent_entity(Scope *s, String name, Scope **scope, Entity **entity) {
+	u64 key = hash_string(name);
+	for (; s != NULL; s = s->parent) {
+		Entity **found = map_get(&s->elements, key);
+		if (found) {
+			if (entity) *entity = *found;
+			if (scope) *scope = s;
+			return;
+		}
+	}
+	if (entity) *entity = NULL;
+	if (scope) *scope = NULL;
+}
+
+Entity *scope_lookup_entity(Scope *s, String name) {
+	Entity *entity = NULL;
+	scope_lookup_parent_entity(s, name, NULL, &entity);
+	return entity;
+}
+
+Entity *scope_lookup_entity_current(Scope *s, String name) {
+	u64 key = hash_string(name);
+	Entity **found = map_get(&s->elements, key);
+	if (found)
+		return *found;
+	return NULL;
+}
+
+
+
+Entity *scope_insert_entity(Scope *s, Entity *entity) {
+	String name = entity->token.string;
+	u64 key = hash_string(name);
+	Entity **found = map_get(&s->elements, key);
+	if (found)
+		return *found;
+	map_set(&s->elements, key, entity);
+	if (entity->parent == NULL)
+		entity->parent = s;
+	return NULL;
+}
+
+
+
+
+
+void add_global_entity(Entity *entity) {
+	String name = entity->token.string;
+	if (gb_memchr(name.text, ' ', name.len)) {
+		return; // NOTE(bill): `untyped thing`
+	}
+	if (scope_insert_entity(global_scope, entity)) {
+		GB_PANIC("Internal type checking error: double declaration");
+	}
+}
+
+void init_global_scope(void) {
+	global_scope = make_scope(NULL);
+	gbAllocator a = gb_heap_allocator();
+
+// Types
+	for (isize i = 0; i < gb_count_of(basic_types); i++) {
+		Token token = {Token_Identifier};
+		token.string = basic_types[i].basic.name;
+		add_global_entity(alloc_entity(a, Entity_TypeName, NULL, token, &basic_types[i]));
+	}
+	for (isize i = 0; i < gb_count_of(basic_type_aliases); i++) {
+		Token token = {Token_Identifier};
+		token.string = basic_type_aliases[i].basic.name;
+		add_global_entity(alloc_entity(a, Entity_TypeName, NULL, token, &basic_type_aliases[i]));
+	}
+
+// Constants
+	Token true_token = {Token_Identifier};
+	true_token.string = make_string("true");
+	Entity *true_entity = alloc_entity(a, Entity_Constant, NULL, true_token, &basic_types[Basic_UntypedBool]);
+	true_entity->constant.value = make_value_bool(true);
+	add_global_entity(true_entity);
+
+	Token false_token = {Token_Identifier};
+	false_token.string = make_string("false");
+	Entity *false_entity = alloc_entity(a, Entity_Constant, NULL, false_token, &basic_types[Basic_UntypedBool]);
+	false_entity->constant.value = make_value_bool(false);
+	add_global_entity(false_entity);
+
+	Token null_token = {Token_Identifier};
+	null_token.string = make_string("null");
+	Entity *null_entity = alloc_entity(a, Entity_Constant, NULL, null_token, &basic_types[Basic_UntypedPointer]);
+	null_entity->constant.value = make_value_pointer(NULL);
+	add_global_entity(null_entity);
+
+// Builtin Procedures
+	for (isize i = 0; i < gb_count_of(builtin_procedures); i++) {
+		i32 id = cast(i32)i;
+		Token token = {Token_Identifier};
+		token.string = builtin_procedures[i].name;
+		Entity *entity = alloc_entity(a, Entity_Builtin, NULL, token, &basic_types[Basic_Invalid]);
+		entity->builtin.id = id;
+		add_global_entity(entity);
+	}
+}
+
+
+
+
+
+
+
+void init_checker(Checker *c, Parser *parser) {
+	gbAllocator a = gb_heap_allocator();
+
+	c->parser = parser;
+	map_init(&c->types,       gb_heap_allocator());
+	map_init(&c->definitions, gb_heap_allocator());
+	map_init(&c->uses,        gb_heap_allocator());
+	map_init(&c->scopes,      gb_heap_allocator());
+	c->sizes.word_size = 8;
+	c->sizes.max_align = 8;
+
+	map_init(&c->untyped, a);
+
+	c->file_scope = make_scope(global_scope);
+	c->curr_scope = c->file_scope;
+
+	gb_array_init(c->procedure_stack, a);
+
+	// NOTE(bill): Is this big enough or too small?
+	isize entity_arena_size = 2 * gb_size_of(Entity) * gb_array_count(c->parser->tokens);
+	gb_arena_init_from_allocator(&c->entity_arena, a, entity_arena_size);
+}
+
+void destroy_checker(Checker *c) {
+	map_destroy(&c->types);
+	map_destroy(&c->definitions);
+	map_destroy(&c->uses);
+	map_destroy(&c->scopes);
+	map_destroy(&c->untyped);
+	destroy_scope(c->file_scope);
+	gb_array_free(c->procedure_stack);
+	gb_arena_free(&c->entity_arena);
+}
+
+#define print_checker_error(p, token, fmt, ...) print_checker_error_(p, __FUNCTION__, token, fmt, ##__VA_ARGS__)
+void print_checker_error_(Checker *c, char *function, Token token, char *fmt, ...) {
+	va_list va;
+
+	// NOTE(bill): Duplicate error, skip it
+	if (c->error_prev_line == token.line && c->error_prev_column == token.column) {
+		goto error;
+	}
+	c->error_prev_line = token.line;
+	c->error_prev_column = token.column;
+
+#if 0
+	gb_printf_err("%s()\n", function);
+#endif
+	va_start(va, fmt);
+	gb_printf_err("%s(%td:%td) %s\n",
+	              c->parser->tokenizer.fullpath, token.line, token.column,
+	              gb_bprintf_va(fmt, va));
+	va_end(va);
+
+error:
+	c->error_count++;
+	// NOTE(bill): If there are too many errors, just quit
+	if (c->error_count > MAX_CHECKER_ERROR_COUNT) {
+		gb_exit(1);
+		return;
+	}
+}
+
+
+
+Entity *entity_of_identifier(Checker *c, AstNode *identifier) {
+	GB_ASSERT(identifier->kind == AstNode_Identifier);
+	Entity **found = map_get(&c->definitions, hash_pointer(identifier));
+	if (found)
+		return *found;
+
+	found = map_get(&c->uses, hash_pointer(identifier));
+	if (found)
+		return *found;
+	return NULL;
+}
+
+Type *type_of_expression(Checker *c, AstNode *expression) {
+	TypeAndValue *found = map_get(&c->types, hash_pointer(expression));
+	if (found)
+		return found->type;
+	if (expression->kind == AstNode_Identifier) {
+		Entity *entity = entity_of_identifier(c, expression);
+		if (entity)
+			return entity->type;
+	}
+
+	return NULL;
+}
+
+
+void add_untyped(Checker *c, AstNode *expression, b32 lhs, AddressingMode mode, Type *basic_type, Value value) {
+	map_set(&c->untyped, hash_pointer(expression), make_expression_info(lhs, mode, basic_type, value));
+}
+
+
+void add_type_and_value(Checker *c, AstNode *expression, AddressingMode mode, Type *type, Value value) {
+	GB_ASSERT(expression != NULL);
+	GB_ASSERT(type != NULL);
+	if (mode == Addressing_Invalid)
+		return;
+
+	if (mode == Addressing_Constant) {
+		GB_ASSERT(value.kind != Value_Invalid);
+		GB_ASSERT(type == &basic_types[Basic_Invalid] || is_type_constant_type(type));
+	}
+
+	TypeAndValue tv = {};
+	tv.type = type;
+	tv.value = value;
+	map_set(&c->types, hash_pointer(expression), tv);
+}
+
+void add_entity_definition(Checker *c, AstNode *identifier, Entity *entity) {
+	GB_ASSERT(identifier != NULL);
+	GB_ASSERT(identifier->kind == AstNode_Identifier);
+	u64 key = hash_pointer(identifier);
+	map_set(&c->definitions, key, entity);
+}
+
+void add_entity(Checker *c, Scope *scope, AstNode *identifier, Entity *entity) {
+	Entity *insert_entity = scope_insert_entity(scope, entity);
+	if (insert_entity) {
+		print_checker_error(c, entity->token, "Redeclared entity in this scope: %.*s", LIT(entity->token.string));
+		return;
+	}
+	if (identifier)
+		add_entity_definition(c, identifier, entity);
+}
+
+void add_entity_use(Checker *c, AstNode *identifier, Entity *entity) {
+	GB_ASSERT(identifier != NULL);
+	GB_ASSERT(identifier->kind == AstNode_Identifier);
+	u64 key = hash_pointer(identifier);
+	map_set(&c->uses, key, entity);
+}
+
+void add_scope(Checker *c, AstNode *node, Scope *scope) {
+	GB_ASSERT(node != NULL);
+	GB_ASSERT(scope != NULL);
+	map_set(&c->scopes, hash_pointer(node), scope);
+}
+
+
+void check_open_scope(Checker *c, AstNode *statement) {
+	Scope *scope = make_scope(c->curr_scope);
+	add_scope(c, statement, scope);
+	c->curr_scope = scope;
+}
+
+void check_close_scope(Checker *c) {
+	c->curr_scope = c->curr_scope->parent;
+}
+
+void push_procedure(Checker *c, Type *procedure_type) {
+	gb_array_append(c->procedure_stack, procedure_type);
+}
+
+void pop_procedure(Checker *c) {
+	gb_array_pop(c->procedure_stack);
+}
+
+
+
+
+Entity *make_entity_variable(Checker *c, Scope *parent, Token token, Type *type) {
+	Entity *entity = alloc_entity(gb_arena_allocator(&c->entity_arena), Entity_Variable, parent, token, type);
+	return entity;
+}
+
+Entity *make_entity_constant(Checker *c, Scope *parent, Token token, Type *type, Value value) {
+	Entity *entity = alloc_entity(gb_arena_allocator(&c->entity_arena), Entity_Constant, parent, token, type);
+	entity->constant.value = value;
+	return entity;
+}
+
+Entity *make_entity_type_name(Checker *c, Scope *parent, Token token, Type *type) {
+	Entity *entity = alloc_entity(gb_arena_allocator(&c->entity_arena), Entity_TypeName, parent, token, type);
+	return entity;
+}
+
+Entity *make_entity_param(Checker *c, Scope *parent, Token token, Type *type) {
+	Entity *entity = alloc_entity(gb_arena_allocator(&c->entity_arena), Entity_Variable, parent, token, type);
+	entity->variable.used = true;
+	return entity;
+}
+
+Entity *make_entity_field(Checker *c, Scope *parent, Token token, Type *type) {
+	Entity *entity = alloc_entity(gb_arena_allocator(&c->entity_arena), Entity_Variable, parent, token, type);
+	entity->variable.is_field  = true;
+	return entity;
+}
+
+Entity *make_entity_procedure(Checker *c, Scope *parent, Token token, Type *signature_type) {
+	Entity *entity = alloc_entity(gb_arena_allocator(&c->entity_arena), Entity_Procedure, parent, token, signature_type);
+	return entity;
+}
+
+Entity *make_entity_builtin(Checker *c, Scope *parent, Token token, Type *type, i32 id) {
+	Entity *entity = alloc_entity(gb_arena_allocator(&c->entity_arena), Entity_Builtin, parent, token, type);
+	entity->builtin.id = id;
+	return entity;
+}
+
+Entity *make_entity_dummy_variable(Checker *c, Token token) {
+	token.string = make_string("_");
+	return make_entity_variable(c, c->file_scope, token, NULL);
+}
+

+ 66 - 0
src/checker/entity.cpp

@@ -0,0 +1,66 @@
+struct Scope;
+struct Checker;
+
+enum EntityKind {
+	Entity_Invalid,
+
+	Entity_Constant,
+	Entity_Variable,
+	Entity_TypeName,
+	Entity_Procedure,
+	Entity_Builtin,
+
+	Entity_Count,
+};
+
+typedef i64 EntityGuid;
+
+struct Entity {
+	EntityKind kind;
+	EntityGuid guid;
+
+	Scope *parent;
+	Token token;
+	Type *type;
+
+	union {
+		struct { Value value; } constant;
+		struct {
+			b8 visited;
+			b8 is_field;
+			b8 used;
+		} variable;
+		struct {} type_name;
+		struct {} procedure;
+		struct { i32 id; } builtin;
+	};
+};
+
+gb_global gbAtomic64 entity_guid_counter = {0};
+
+EntityGuid next_entity_guid(void) {
+	return cast(EntityGuid)gb_atomic64_fetch_add(&entity_guid_counter, 1);
+}
+
+Entity *alloc_entity(gbAllocator a, EntityKind kind, Scope *parent, Token token, Type *type) {
+	Entity *entity = gb_alloc_item(a, Entity);
+	entity->kind   = kind;
+	entity->guid   = next_entity_guid();
+	entity->parent = parent;
+	entity->token  = token;
+	entity->type   = type;
+	return entity;
+}
+
+
+
+
+Entity *make_entity_variable (Checker *c, Scope *parent, Token token, Type *type);
+Entity *make_entity_constant (Checker *c, Scope *parent, Token token, Type *type, Value value);
+Entity *make_entity_type_name(Checker *c, Scope *parent, Token token, Type *type);
+Entity *make_entity_param    (Checker *c, Scope *parent, Token token, Type *type);
+Entity *make_entity_field    (Checker *c, Scope *parent, Token token, Type *type);
+Entity *make_entity_procedure(Checker *c, Scope *parent, Token token, Type *signature_type);
+Entity *make_entity_builtin  (Checker *c, Scope *parent, Token token, Type *type, i32 id);
+
+Entity *make_entity_dummy_variable(Checker *c, Token token);

+ 1709 - 0
src/checker/expression.cpp

@@ -0,0 +1,1709 @@
+void           check_assignment        (Checker *c, Operand *operand, Type *type, String context_name);
+void           check_expression        (Checker *c, Operand *operand, AstNode *expression);
+void           check_multi_expression  (Checker *c, Operand *operand, AstNode *expression);
+void           check_expression_or_type(Checker *c, Operand *operand, AstNode *expression);
+ExpressionKind check_expression_base   (Checker *c, Operand *operand, AstNode *expression, Type *type_hint = NULL);
+Type *         check_type              (Checker *c, AstNode *expression, Type *named_type = NULL);
+void           check_selector          (Checker *c, Operand *operand, AstNode *node);
+void           check_not_tuple         (Checker *c, Operand *operand);
+void           convert_to_typed        (Checker *c, Operand *operand, Type *target_type);
+gbString       expression_to_string    (AstNode *expression);
+
+
+void check_struct_type(Checker *c, Type *struct_type, AstNode *node) {
+	GB_ASSERT(node->kind == AstNode_StructType);
+	GB_ASSERT(struct_type->kind == Type_Structure);
+	auto *st = &node->struct_type;
+	if (st->field_count == 0) {
+		print_checker_error(c, ast_node_token(node), "Empty struct{} definition");
+		return;
+	}
+
+	Map<Entity *> entity_map = {};
+	map_init(&entity_map, gb_heap_allocator());
+	defer (map_destroy(&entity_map));
+
+	isize field_count = 0;
+	for (AstNode *field = st->field_list; field != NULL; field = field->next) {
+		for (AstNode *name = field->field.name_list; name != NULL; name = name->next) {
+			GB_ASSERT(name->kind == AstNode_Identifier);
+			field_count++;
+		}
+	}
+
+	Entity **fields = gb_alloc_array(gb_arena_allocator(&c->entity_arena),
+	                                 Entity *, st->field_count);
+	isize field_index = 0;
+	for (AstNode *field = st->field_list; field != NULL; field = field->next) {
+		Type *type = check_type(c, field->field.type_expression);
+		for (AstNode *name = field->field.name_list; name != NULL; name = name->next) {
+			GB_ASSERT(name->kind == AstNode_Identifier);
+			Token name_token = name->identifier.token;
+			// TODO(bill): is the curr_scope correct?
+			Entity *e = make_entity_field(c, c->curr_scope, name_token, type);
+			u64 key = hash_string(name_token.string);
+			if (map_get(&entity_map, key)) {
+				// TODO(bill): Scope checking already checks the declaration
+				print_checker_error(c, name_token, "`%.*s` is already declared in this structure", LIT(name_token.string));
+			} else {
+				map_set(&entity_map, key, e);
+				fields[field_index++] = e;
+			}
+			add_entity_use(c, name, e);
+		}
+	}
+	struct_type->structure.fields = fields;
+	struct_type->structure.field_count = field_count;
+}
+
+Type *check_get_params(Checker *c, Scope *scope, AstNode *field_list, isize field_count) {
+	if (field_list == NULL || field_count == 0)
+		return NULL;
+
+	Type *tuple = make_type_tuple();
+
+	Entity **variables = gb_alloc_array(gb_arena_allocator(&c->entity_arena),
+	                                    Entity *, field_count);
+	isize variable_index = 0;
+	for (AstNode *field = field_list; field != NULL; field = field->next) {
+		GB_ASSERT(field->kind == AstNode_Field);
+		AstNode *type_expression = field->field.type_expression;
+		if (type_expression) {
+			Type *type = check_type(c, type_expression);
+			for (AstNode *name = field->field.name_list; name != NULL; name = name->next) {
+				GB_ASSERT(name->kind == AstNode_Identifier);
+				Entity *param = make_entity_param(c, scope, name->identifier.token, type);
+				add_entity(c, scope, name, param);
+				variables[variable_index++] = param;
+			}
+		}
+	}
+	tuple->tuple.variables = variables;
+	tuple->tuple.variable_count = field_count;
+
+	return tuple;
+}
+
+Type *check_get_results(Checker *c, Scope *scope, AstNode *list, isize list_count) {
+	if (list == NULL)
+		return NULL;
+	Type *tuple = make_type_tuple();
+
+	Entity **variables = gb_alloc_array(gb_arena_allocator(&c->entity_arena),
+	                                    Entity *, list_count);
+	isize variable_index = 0;
+	for (AstNode *item = list; item != NULL; item = item->next) {
+		Type *type = check_type(c, item);
+		Token token = ast_node_token(item);
+		token.string = make_string(""); // NOTE(bill): results are not named
+		// TODO(bill): Should I have named results?
+		Entity *param = make_entity_param(c, scope, token, type);
+		// NOTE(bill): No need to record
+		variables[variable_index++] = param;
+	}
+	tuple->tuple.variables = variables;
+	tuple->tuple.variable_count = list_count;
+
+	return tuple;
+}
+
+
+void check_procedure_type(Checker *c, Type *type, AstNode *proc_type_node) {
+	isize param_count = 0;
+	isize result_count = 0;
+
+	// NOTE(bill): Each field can store multiple items
+	for (AstNode *field = proc_type_node->procedure_type.param_list;
+	     field != NULL;
+	     field = field->next) {
+		param_count += field->field.name_list_count;
+	}
+
+	for (AstNode *item = proc_type_node->procedure_type.results_list;
+	     item != NULL;
+	     item = item->next) {
+		result_count++;
+	}
+
+	Type *params  = check_get_params (c, c->curr_scope, proc_type_node->procedure_type.param_list, param_count);
+	Type *results = check_get_results(c, c->curr_scope, proc_type_node->procedure_type.results_list, result_count);
+
+	type->procedure.scope = c->curr_scope;
+	type->procedure.params = params;
+	type->procedure.params_count = proc_type_node->procedure_type.param_count;
+	type->procedure.results = results;
+	type->procedure.results_count = proc_type_node->procedure_type.result_count;
+}
+
+void check_identifier(Checker *c, Operand *operand, AstNode *n, Type *named_type) {
+	GB_ASSERT(n->kind == AstNode_Identifier);
+	operand->mode = Addressing_Invalid;
+	operand->expression = n;
+	Entity *e = NULL;
+	scope_lookup_parent_entity(c->curr_scope, n->identifier.token.string, NULL, &e);
+	if (e == NULL) {
+		print_checker_error(c, n->identifier.token,
+		                    "Undeclared type/identifier: %.*s", LIT(n->identifier.token.string));
+		return;
+	}
+	add_entity_use(c, n, e);
+
+	Type *type = e->type;
+	GB_ASSERT(type != NULL);
+
+	switch (e->kind) {
+	case Entity_Constant:
+		if (type == &basic_types[Basic_Invalid])
+			return;
+		operand->value = e->constant.value;
+		GB_ASSERT(operand->value.kind != Value_Invalid);
+		operand->mode = Addressing_Constant;
+		break;
+
+	case Entity_Variable:
+		e->variable.used = true;
+		if (type == &basic_types[Basic_Invalid])
+			return;
+		operand->mode = Addressing_Variable;
+		break;
+
+	case Entity_TypeName:
+		operand->mode = Addressing_Type;
+		break;
+
+	case Entity_Procedure:
+		operand->mode = Addressing_Value;
+		break;
+
+	case Entity_Builtin:
+		operand->builtin_id = e->builtin.id;
+		operand->mode = Addressing_Builtin;
+		break;
+
+	default:
+		GB_PANIC("Unknown EntityKind");
+		break;
+	}
+
+	operand->type = type;
+}
+
+i64 check_array_count(Checker *c, AstNode *expression) {
+	if (expression) {
+		Operand operand = {};
+		check_expression(c, &operand, expression);
+		if (operand.mode != Addressing_Constant) {
+			if (operand.mode != Addressing_Invalid) {
+				print_checker_error(c, ast_node_token(expression), "Array count must be a constant");
+			}
+			return 0;
+		}
+		if (is_type_untyped(operand.type) || is_type_integer(operand.type)) {
+			if (operand.value.kind == Value_Integer) {
+				i64 count = operand.value.value_integer;
+				if (count >= 0)
+					return count;
+				print_checker_error(c, ast_node_token(expression), "Invalid array count");
+				return 0;
+			}
+		}
+
+		print_checker_error(c, ast_node_token(expression), "Array count must be an integer");
+	}
+	return 0;
+}
+
+Type *check_type_expression_extra(Checker *c, AstNode *expression, Type *named_type) {
+	gbString err_str = NULL;
+	defer (gb_string_free(err_str));
+
+	switch (expression->kind) {
+	case AstNode_Identifier: {
+		Operand operand = {};
+		check_identifier(c, &operand, expression, named_type);
+		switch (operand.mode) {
+		case Addressing_Type: {
+			Type *t = operand.type;
+			set_base_type(named_type, t);
+			return t;
+		} break;
+
+		case Addressing_Invalid:
+			break;
+
+		case Addressing_NoValue:
+			err_str = expression_to_string(expression);
+			print_checker_error(c, ast_node_token(expression), "`%s` used as a type", err_str);
+			break;
+		default:
+			err_str = expression_to_string(expression);
+			print_checker_error(c, ast_node_token(expression), "`%s` used as a type when not a type", err_str);
+			break;
+		}
+	} break;
+
+	case AstNode_ParenExpression:
+		return check_type(c, expression->paren_expression.expression, named_type);
+
+	case AstNode_ArrayType:
+		if (expression->array_type.count != NULL) {
+			Type *t = make_type_array(check_type(c, expression->array_type.element),
+			                          check_array_count(c, expression->array_type.count));
+			set_base_type(named_type, t);
+			return t;
+		} else {
+			print_checker_error(c, ast_node_token(expression), "Empty array size");
+			return NULL;
+		}
+		break;
+
+	case AstNode_StructType: {
+		Type *t = make_type_structure();
+		set_base_type(named_type, t);
+		check_struct_type(c, t, expression);
+		return t;
+	} break;
+
+	case AstNode_PointerType: {
+		Type *t = make_type_pointer(check_type(c, expression->pointer_type.type_expression));
+		set_base_type(named_type, t);
+		return t;
+	} break;
+
+	case AstNode_ProcedureType: {
+		Type *t = alloc_type(Type_Procedure);
+		set_base_type(named_type, t);
+		check_open_scope(c, expression);
+		check_procedure_type(c, t, expression);
+		check_close_scope(c);
+		return t;
+	} break;
+
+	default:
+		err_str = expression_to_string(expression);
+		print_checker_error(c, ast_node_token(expression), "`%s` is not a type", err_str);
+		break;
+	}
+
+	Type *t = &basic_types[Basic_Invalid];
+	set_base_type(named_type, t);
+	return t;
+}
+
+
+Type *check_type(Checker *c, AstNode *expression, Type *named_type) {
+	Value null_value = {Value_Invalid};
+	Type *type = NULL;
+	gbString err_str = NULL;
+	defer (gb_string_free(err_str));
+
+	switch (expression->kind) {
+	case AstNode_Identifier: {
+		Operand operand = {};
+		check_identifier(c, &operand, expression, named_type);
+		switch (operand.mode) {
+		case Addressing_Type: {
+			type = operand.type;
+			set_base_type(named_type, type);
+			goto end;
+		} break;
+
+		case Addressing_Invalid:
+			break;
+
+		case Addressing_NoValue:
+			err_str = expression_to_string(expression);
+			print_checker_error(c, ast_node_token(expression), "`%s` used as a type", err_str);
+			break;
+		default:
+			err_str = expression_to_string(expression);
+			print_checker_error(c, ast_node_token(expression), "`%s` used as a type when not a type", err_str);
+			break;
+		}
+	} break;
+
+	case AstNode_SelectorExpression: {
+		Operand operand = {};
+		check_selector(c, &operand, expression);
+
+		if (operand.mode == Addressing_Type) {
+			set_base_type(type, operand.type);
+			return operand.type;
+		}
+	} break;
+
+	case AstNode_ParenExpression:
+		return check_type(c, expression->paren_expression.expression, named_type);
+
+	case AstNode_ArrayType:
+		type = make_type_array(check_type(c, expression->array_type.element),
+		                       check_array_count(c, expression->array_type.count));
+		set_base_type(named_type, type);
+		goto end;
+		break;
+
+	case AstNode_StructType: {
+		type = make_type_structure();
+		set_base_type(named_type, type);
+		check_struct_type(c, type, expression);
+		goto end;
+	} break;
+
+	case AstNode_PointerType: {
+		type = make_type_pointer(check_type(c, expression->pointer_type.type_expression));
+		set_base_type(named_type, type);
+		goto end;
+	} break;
+
+	case AstNode_ProcedureType: {
+		type = alloc_type(Type_Procedure);
+		set_base_type(named_type, type);
+		check_procedure_type(c, type, expression);
+		goto end;
+	} break;
+
+	default:
+		err_str = expression_to_string(expression);
+		print_checker_error(c, ast_node_token(expression), "`%s` is not a type", err_str);
+		break;
+	}
+
+	type = &basic_types[Basic_Invalid];
+	set_base_type(named_type, type);
+
+end:
+	GB_ASSERT(is_type_typed(type));
+	add_type_and_value(c, expression, Addressing_Type, type, null_value);
+	return type;
+}
+
+
+b32 check_unary_op(Checker *c, Operand *operand, Token op) {
+	// TODO(bill): Handle errors correctly
+	gbString str = NULL;
+	defer (gb_string_free(str));
+	switch (op.kind) {
+	case Token_Add:
+	case Token_Sub:
+		if (!is_type_numeric(operand->type)) {
+			str = expression_to_string(operand->expression);
+			print_checker_error(c, op, "Operator `%.*s` is not allowed with `%s`", LIT(op.string), str);
+		}
+		break;
+
+	case Token_Xor:
+		if (!is_type_integer(operand->type)) {
+			print_checker_error(c, op, "Operator `%.*s` is only allowed with integers", LIT(op.string));
+		}
+		break;
+
+	case Token_Not:
+		if (!is_type_boolean(operand->type)) {
+			str = expression_to_string(operand->expression);
+			print_checker_error(c, op, "Operator `%.*s` is only allowed on boolean expression", LIT(op.string));
+		}
+		break;
+
+	default:
+		print_checker_error(c, op, "Unknown operator `%.*s`", LIT(op.string));
+		return false;
+	}
+
+	return true;
+}
+
+b32 check_binary_op(Checker *c, Operand *operand, Token op) {
+	// TODO(bill): Handle errors correctly
+	switch (op.kind) {
+	case Token_Add:
+	case Token_Sub:
+	case Token_Mul:
+	case Token_Quo:
+		if (!is_type_numeric(operand->type)) {
+			print_checker_error(c, op, "Operator `%.*s` is only allowed with numeric expressions", LIT(op.string));
+		}
+		break;
+
+	case Token_Mod:
+	case Token_Or:
+	case Token_Xor:
+	case Token_AndNot:
+		if (!is_type_integer(operand->type)) {
+			print_checker_error(c, op, "Operand `%.*s` is only allowed with integers", LIT(op.string));
+		}
+		break;
+
+	case Token_CmpAnd:
+	case Token_CmpOr:
+		if (!is_type_boolean(operand->type)) {
+			print_checker_error(c, op, "Operator `%.*s` is only allowed with boolean expressions", LIT(op.string));
+		}
+		break;
+
+	case Token_AddEq:
+	case Token_SubEq:
+	case Token_MulEq:
+	case Token_QuoEq:
+	case Token_ModEq:
+	case Token_AndEq:
+	case Token_OrEq:
+	case Token_XorEq:
+	case Token_AndNotEq:
+	case Token_CmpAndEq:
+	case Token_CmpOrEq:
+		// TODO(bill): is this okay?
+		return true;
+
+
+	default:
+		print_checker_error(c, op, "Unknown operator `%.*s`", LIT(op.string));
+		return false;
+	}
+
+	return true;
+
+}
+b32 check_value_is_expressible(Checker *c, Value in_value, Type *type, Value *out_value) {
+	if (in_value.kind == Value_Invalid)
+		return true;
+
+	if (is_type_boolean(type)) {
+		return in_value.kind == Value_Bool;
+	} else if (is_type_string(type)) {
+		return in_value.kind == Value_String;
+	} else if (is_type_integer(type)) {
+		if (in_value.kind != Value_Integer)
+			return false;
+		if (out_value) *out_value = in_value;
+		i64 i = in_value.value_integer;
+		i64 s = 8*type_size_of(c->sizes, gb_arena_allocator(&c->entity_arena), type);
+		u64 umax = ~0ull;
+		if (s < 64)
+			umax = (1ull << s) - 1ull;
+		i64 imax = (1ll << (s-1ll));
+
+
+		switch (type->basic.kind) {
+		case Basic_i8:
+		case Basic_i16:
+		case Basic_i32:
+		case Basic_i64:
+		case Basic_int:
+			return gb_is_between(i, -imax, imax-1);
+
+		case Basic_u8:
+		case Basic_u16:
+		case Basic_u32:
+		case Basic_u64:
+		case Basic_uint:
+			return !(i < 0 || cast(u64)i > umax);
+
+		case Basic_UntypedInteger:
+			return true;
+
+		default: GB_PANIC("Unknown integer type!"); break;
+		}
+	} else if (is_type_float(type)) {
+		Value v = value_to_float(in_value);
+		if (v.kind != Value_Float)
+			return false;
+
+		switch (type->basic.kind) {
+		case Basic_f32:
+			if (out_value) *out_value = v;
+			return true;
+
+		case Basic_f64:
+			if (out_value) *out_value = v;
+			return true;
+
+		case Basic_UntypedFloat:
+			return true;
+		}
+	} else if (is_type_pointer(type)) {
+		if (in_value.kind == Value_Pointer)
+			return true;
+		if (in_value.kind == Value_Integer)
+			return true;
+		if (out_value) *out_value = in_value;
+	}
+
+	return false;
+}
+
+void check_is_expressible(Checker *c, Operand *operand, Type *type) {
+	GB_ASSERT(type->kind == Type_Basic);
+	GB_ASSERT(operand->mode == Addressing_Constant);
+	if (!check_value_is_expressible(c, operand->value, type, &operand->value)) {
+		gbString a = type_to_string(operand->type);
+		gbString b = type_to_string(type);
+		defer (gb_string_free(a));
+		defer (gb_string_free(b));
+		if (is_type_numeric(operand->type) && is_type_numeric(type)) {
+			if (!is_type_integer(operand->type) && is_type_integer(type)) {
+				print_checker_error(c, ast_node_token(operand->expression), "`%s` truncated to `%s`", a, b);
+			} else {
+				print_checker_error(c, ast_node_token(operand->expression), "`%s` overflows to `%s`", a, b);
+			}
+		} else {
+			print_checker_error(c, ast_node_token(operand->expression), "Cannot convert `%s` to `%s`", a, b);
+		}
+
+		operand->mode = Addressing_Invalid;
+	}
+}
+
+
+void check_unary_expression(Checker *c, Operand *operand, Token op, AstNode *node) {
+	if (op.kind == Token_Pointer) { // Pointer address
+		if (operand->mode != Addressing_Variable) {
+			gbString str = expression_to_string(node->unary_expression.operand);
+			defer (gb_string_free(str));
+			print_checker_error(c, op, "Cannot take the pointer address of `%s`", str);
+			operand->mode = Addressing_Invalid;
+			return;
+		}
+		operand->mode = Addressing_Value;
+		operand->type = make_type_pointer(operand->type);
+		return;
+	}
+
+	if (!check_unary_op(c, operand, op)) {
+		operand->mode = Addressing_Invalid;
+		return;
+	}
+
+	if (operand->mode == Addressing_Constant) {
+		Type *type = get_base_type(operand->type);
+		GB_ASSERT(type->kind == Type_Basic);
+		i32 precision = 0;
+		if (is_type_unsigned(type))
+			precision = cast(i32)(8 * type_size_of(c->sizes, gb_arena_allocator(&c->entity_arena), type));
+		operand->value = unary_operator_value(op, operand->value, precision);
+
+		if (is_type_typed(type)) {
+			if (node != NULL)
+				operand->expression = node;
+			check_is_expressible(c, operand, type);
+		}
+		return;
+	}
+
+	operand->mode = Addressing_Value;
+}
+
+b32 check_assignable_to(Checker *c, Operand *operand, Type *type);
+
+void check_comparison(Checker *c, Operand *x, Operand *y, Token op) {
+	gbString err_str = NULL;
+	defer (gb_string_free(err_str));
+
+	if (check_assignable_to(c, x, y->type) ||
+	    check_assignable_to(c, y, x->type)) {
+		b32 defined = false;
+		switch (op.kind) {
+		case Token_CmpEq:
+		case Token_NotEq:
+			defined = is_type_comparable(x->type);
+			break;
+		case Token_Lt:
+		case Token_Gt:
+		case Token_LtEq:
+		case Token_GtEq: {
+			defined = is_type_ordered(x->type);
+		} break;
+		}
+
+		if (!defined) {
+			gbString type_string = type_to_string(x->type);
+			err_str = gb_string_make(gb_heap_allocator(),
+			                         gb_bprintf("operator `%.*s` not defined for type `%s`", LIT(op.string), type_string));
+			gb_string_free(type_string);
+		}
+	} else {
+		gbString xt = type_to_string(x->type);
+		gbString yt = type_to_string(y->type);
+		defer(gb_string_free(xt));
+		defer(gb_string_free(yt));
+		err_str = gb_string_make(gb_heap_allocator(),
+		                         gb_bprintf("mismatched types `%s` and `%s`", xt, yt));
+	}
+
+	if (err_str) {
+		print_checker_error(c, op, "Cannot compare expression, %s", err_str);
+		return;
+	}
+
+	if (x->mode == Addressing_Constant &&
+	    y->mode == Addressing_Constant) {
+		x->value = make_value_bool(compare_values(op, x->value, y->value));
+	} else {
+		// TODO(bill): What should I do?
+	}
+
+	x->type = &basic_types[Basic_UntypedBool];
+}
+
+void check_binary_expression(Checker *c, Operand *x, AstNode *node) {
+	GB_ASSERT(node->kind == AstNode_BinaryExpression);
+	Operand y = {};
+	gbString err_str = NULL;
+	defer (gb_string_free(err_str));
+
+	check_expression(c, x, node->binary_expression.left);
+	check_expression(c, &y, node->binary_expression.right);
+	if (x->mode == Addressing_Invalid) return;
+	if (y.mode == Addressing_Invalid) {
+		x->mode = Addressing_Invalid;
+		x->expression = y.expression;
+		return;
+	}
+
+	convert_to_typed(c, x, y.type);
+	if (x->mode == Addressing_Invalid) return;
+	convert_to_typed(c, &y, x->type);
+	if (y.mode == Addressing_Invalid) {
+		x->mode = Addressing_Invalid;
+		return;
+	}
+
+	Token op = node->binary_expression.op;
+	if (token_is_comparison(op)) {
+		check_comparison(c, x, &y, op);
+		return;
+	}
+
+	if (!are_types_identical(x->type, y.type)) {
+		if (x->type != &basic_types[Basic_Invalid] &&
+		    y.type  != &basic_types[Basic_Invalid]) {
+			gbString xt = type_to_string(x->type);
+			gbString yt = type_to_string(y.type);
+			defer (gb_string_free(xt));
+			defer (gb_string_free(yt));
+			err_str = expression_to_string(x->expression);
+			print_checker_error(c, op, "Mismatched types in binary expression `%s` : `%s` vs `%s`", err_str, xt, yt);
+		}
+		x->mode = Addressing_Invalid;
+		return;
+	}
+
+	if (!check_binary_op(c, x, op)) {
+		x->mode = Addressing_Invalid;
+		return;
+	}
+
+	if ((op.kind == Token_Quo || op.kind == Token_Mod) &&
+	    (x->mode == Addressing_Constant || is_type_integer(x->type)) &&
+	    y.mode == Addressing_Constant) {
+		b32 fail = false;
+		switch (y.value.kind) {
+		case Value_Integer:
+			if (y.value.value_integer == 0)
+				fail = true;
+			break;
+		case Value_Float:
+			if (y.value.value_float == 0.0)
+				fail = true;
+			break;
+		}
+
+		if (fail) {
+			print_checker_error(c, ast_node_token(y.expression),
+			                    "Division by zero not allowed");
+			x->mode = Addressing_Invalid;
+			return;
+		}
+	}
+
+	if (x->mode == Addressing_Constant &&
+	    y.mode  == Addressing_Constant) {
+		Value a = x->value;
+		Value b = y.value;
+
+		Type *type = get_base_type(x->type);
+		GB_ASSERT(type->kind == Type_Basic);
+		if (op.kind == Token_Quo && is_type_integer(type)) {
+			op.kind = Token_QuoEq; // NOTE(bill): Hack to get division of integers
+		}
+		x->value = binary_operator_value(op, a, b);
+		if (is_type_typed(type)) {
+			if (node != NULL)
+				x->expression = node;
+			check_is_expressible(c, x, type);
+		}
+		return;
+	}
+
+	x->mode = Addressing_Value;
+}
+
+
+void update_expression_type(Checker *c, AstNode *expression, Type *type, b32 final) {
+	ExpressionInfo *found = map_get(&c->untyped, hash_pointer(expression));
+	if (!found)
+		return;
+
+	switch (expression->kind) {
+	case AstNode_UnaryExpression:
+		if (found->value.kind != Value_Invalid)
+			break;
+		update_expression_type(c, expression->unary_expression.operand, type, final);
+		break;
+
+	case AstNode_BinaryExpression:
+		if (found->value.kind != Value_Invalid)
+			break;
+		if (!token_is_comparison(expression->binary_expression.op)) {
+			update_expression_type(c, expression->binary_expression.left,  type, final);
+			update_expression_type(c, expression->binary_expression.right, type, final);
+		}
+	}
+
+	if (!final && is_type_untyped(type)) {
+		found->type = get_base_type(type);
+	} else {
+		found->type = type;
+	}
+}
+
+void update_expression_value(Checker *c, AstNode *expression, Value value) {
+	ExpressionInfo *found = map_get(&c->untyped, hash_pointer(expression));
+	if (found)
+		found->value = value;
+}
+
+void convert_untyped_error(Checker *c, Operand *operand, Type *target_type) {
+	gbString expr_str = expression_to_string(operand->expression);
+	gbString type_str = type_to_string(target_type);
+	char *extra_text = "";
+	defer (gb_string_free(expr_str));
+	defer (gb_string_free(type_str));
+
+	if (operand->mode == Addressing_Constant) {
+		if (operand->value.value_integer == 0) {
+			// NOTE(bill): Doesn't matter what the type is as it's still zero
+			extra_text = " - Did you want `null`?";
+		}
+	}
+	print_checker_error(c, ast_node_token(operand->expression), "Cannot convert `%s` to `%s`%s", expr_str, type_str, extra_text);
+
+	operand->mode = Addressing_Invalid;
+}
+
+void convert_to_typed(Checker *c, Operand *operand, Type *target_type) {
+	GB_ASSERT_NOT_NULL(target_type);
+	if (operand->mode == Addressing_Invalid ||
+	    is_type_typed(operand->type) ||
+	    target_type == &basic_types[Basic_Invalid]) {
+		return;
+	}
+
+	if (is_type_untyped(target_type)) {
+		Type *x = operand->type;
+		Type *y = target_type;
+		if (is_type_numeric(x) && is_type_numeric(y)) {
+			if (x < y) {
+				operand->type = target_type;
+				update_expression_type(c, operand->expression, target_type, false);
+			}
+		} else if (x != y) {
+			convert_untyped_error(c, operand, target_type);
+		}
+		return;
+	}
+
+	Type *t = get_base_type(target_type);
+	switch (t->kind) {
+	case Type_Basic:
+		if (operand->mode == Addressing_Constant) {
+			check_is_expressible(c, operand, t);
+			if (operand->mode == Addressing_Invalid) {
+				return;
+			}
+			update_expression_value(c, operand->expression, operand->value);
+		} else {
+			// TODO(bill): Is this really needed?
+			switch (operand->type->basic.kind) {
+			case Basic_UntypedBool:
+				if (!is_type_boolean(target_type)) {
+					convert_untyped_error(c, operand, target_type);
+					return;
+				}
+				break;
+			case Basic_UntypedInteger:
+			case Basic_UntypedFloat:
+			case Basic_UntypedRune:
+				if (!is_type_numeric(target_type)) {
+					convert_untyped_error(c, operand, target_type);
+					return;
+				}
+				break;
+			}
+		}
+		break;
+	case Type_Pointer:
+		switch (operand->type->basic.kind) {
+		case Basic_UntypedPointer:
+			target_type = &basic_types[Basic_UntypedPointer];
+			break;
+		default:
+			convert_untyped_error(c, operand, target_type);
+			return;
+		}
+
+		break;
+	default:
+		convert_untyped_error(c, operand, target_type);
+		return;
+	}
+
+	operand->type = target_type;
+}
+
+b32 check_index_value(Checker *c, AstNode *index_value, i64 max_count, b32 bound_checks) {
+	Operand operand = {Addressing_Invalid};
+	check_expression(c, &operand, index_value);
+	if (operand.mode == Addressing_Invalid)
+		return false;
+
+	convert_to_typed(c, &operand, &basic_types[Basic_int]);
+	if (operand.mode == Addressing_Invalid)
+		return false;
+
+	if (!is_type_integer(operand.type)) {
+		gbString expr_str = expression_to_string(operand.expression);
+		print_checker_error(c, ast_node_token(operand.expression),
+		                    "Index `%s` must be an integer", expr_str);
+		gb_string_free(expr_str);
+		return false;
+	}
+
+	if (operand.mode == Addressing_Constant) {
+		if (bound_checks && max_count > 0) { // NOTE(bill): Do array bound checking
+			i64 i = value_to_integer(operand.value).value_integer;
+			if (i < 0) {
+				gbString expr_str = expression_to_string(operand.expression);
+				print_checker_error(c, ast_node_token(operand.expression),
+				                    "Index `%s` cannot be a negative value", expr_str);
+				gb_string_free(expr_str);
+				return false;
+			}
+
+			if (i >= max_count) {
+				gbString expr_str = expression_to_string(operand.expression);
+				print_checker_error(c, ast_node_token(operand.expression),
+				                    "Index `%s` is out of bounds range [0, %lld)", expr_str, max_count);
+				gb_string_free(expr_str);
+				return false;
+			}
+		}
+	}
+
+	// NOTE(bill): It's alright :D
+	return true;
+}
+
+Entity *lookup_field(Type *type, AstNode *field_node, isize *index = NULL) {
+	GB_ASSERT(field_node->kind == AstNode_Identifier);
+	type = get_base_type(type);
+	if (type->kind == Type_Pointer)
+		type = get_base_type(type->pointer.element);
+
+	String field_str = field_node->identifier.token.string;
+	if (type->kind == Type_Structure) {
+		for (isize i = 0; i < type->structure.field_count; i++) {
+			Entity *f = type->structure.fields[i];
+			GB_ASSERT(f->kind == Entity_Variable && f->variable.is_field);
+			String str = f->token.string;
+			if (are_strings_equal(field_str, str)) {
+				if (index) *index = i;
+				return f;
+			}
+		}
+	} else {
+		// TODO(bill): Array.count
+		// TODO(bill): Array.elements
+		// TODO(bill): Or should these be functions?
+	}
+
+	return NULL;
+}
+
+void check_selector(Checker *c, Operand *operand, AstNode *node) {
+	GB_ASSERT(node->kind == AstNode_SelectorExpression);
+
+	AstNode *op_expr  = node->selector_expression.operand;
+	AstNode *selector = node->selector_expression.selector;
+	if (selector) {
+		Entity *entity = lookup_field(operand->type, selector);
+		if (entity == NULL) {
+			gbString op_str  = expression_to_string(op_expr);
+			gbString sel_str = expression_to_string(selector);
+			defer (gb_string_free(op_str));
+			defer (gb_string_free(sel_str));
+			print_checker_error(c, ast_node_token(op_expr), "`%s` has no field `%s`",
+			                    op_str, sel_str);
+			operand->mode = Addressing_Invalid;
+			operand->expression = node;
+			return;
+		}
+		add_entity_use(c, selector, entity);
+
+		operand->type = entity->type;
+		operand->expression = node;
+		if (operand->mode != Addressing_Variable)
+			operand->mode = Addressing_Value;
+	} else {
+		operand->mode = Addressing_Invalid;
+		operand->expression = node;
+	}
+
+}
+
+
+b32 check_builtin_procedure(Checker *c, Operand *operand, AstNode *call, i32 id) {
+	GB_ASSERT(call->kind == AstNode_CallExpression);
+	auto *ce = &call->call_expression;
+	BuiltinProcedure *bp = &builtin_procedures[id];
+	{
+		char *err = NULL;
+		if (ce->arg_list_count < bp->arg_count)
+			err = "Too few";
+		if (ce->arg_list_count > bp->arg_count && !bp->variadic)
+			err = "Too many";
+		if (err) {
+			gbString call_str = expression_to_string(call);
+			defer (gb_string_free(call_str));
+			print_checker_error(c, ce->close, "`%s` arguments for `%s`, expected %td, got %td",
+			                    err, call_str, bp->arg_count, ce->arg_list_count);
+			return false;
+		}
+	}
+
+	switch (id) {
+	case BuiltinProcedure_size_of:
+	case BuiltinProcedure_align_of:
+	case BuiltinProcedure_offset_of:
+		break;
+	default:
+		check_multi_expression(c, operand, ce->arg_list);
+	}
+
+	gbAllocator allocator = gb_arena_allocator(&c->entity_arena);
+
+	switch (id) {
+	case BuiltinProcedure_size_of: {
+		Type *type = check_type(c, ce->arg_list);
+		if (!type) {
+			print_checker_error(c, ast_node_token(ce->arg_list), "Expected a type for `size_of`");
+			return false;
+		}
+
+		operand->mode = Addressing_Constant;
+		operand->value = make_value_integer(type_size_of(c->sizes, allocator, type));
+		operand->type = &basic_types[Basic_int];
+
+	} break;
+
+	case BuiltinProcedure_size_of_val:
+		check_assignment(c, operand, NULL, make_string("argument of `size_of`"));
+		if (operand->mode == Addressing_Invalid)
+			return false;
+
+		operand->mode = Addressing_Constant;
+		operand->value = make_value_integer(type_size_of(c->sizes, allocator, operand->type));
+		operand->type = &basic_types[Basic_int];
+		break;
+
+	case BuiltinProcedure_align_of: {
+		Type *type = check_type(c, ce->arg_list);
+		if (!type) {
+			print_checker_error(c, ast_node_token(ce->arg_list), "Expected a type for `align_of`");
+			return false;
+		}
+		operand->mode = Addressing_Constant;
+		operand->value = make_value_integer(type_align_of(c->sizes, allocator, type));
+		operand->type = &basic_types[Basic_int];
+	} break;
+
+	case BuiltinProcedure_align_of_val:
+		check_assignment(c, operand, NULL, make_string("argument of `align_of`"));
+		if (operand->mode == Addressing_Invalid)
+			return false;
+
+		operand->mode = Addressing_Constant;
+		operand->value = make_value_integer(type_align_of(c->sizes, allocator, operand->type));
+		operand->type = &basic_types[Basic_int];
+		break;
+
+	case BuiltinProcedure_offset_of: {
+		Type *type = get_base_type(check_type(c, ce->arg_list));
+		AstNode *field_arg = unparen_expression(ce->arg_list->next);
+		if (type) {
+			if (type->kind != Type_Structure) {
+				print_checker_error(c, ast_node_token(ce->arg_list), "Expected a structure type for `offset_of`");
+				return false;
+			}
+			if (field_arg->kind != AstNode_Identifier) {
+				print_checker_error(c, ast_node_token(field_arg), "Expected an identifier for field argument");
+				return false;
+			}
+		}
+
+		isize index = 0;
+		Entity *entity = lookup_field(type, field_arg, &index);
+		if (entity == NULL) {
+			gbString type_str = type_to_string(type);
+			print_checker_error(c, ast_node_token(ce->arg_list),
+			                    "`%s` has no field named `%s`", type_str, LIT(field_arg->identifier.token.string));
+			return false;
+		}
+
+		operand->mode = Addressing_Constant;
+		operand->value = make_value_integer(type_offset_of(c->sizes, allocator, type, index));
+		operand->type  = &basic_types[Basic_int];
+	} break;
+
+	case BuiltinProcedure_offset_of_val: {
+		AstNode *arg = unparen_expression(ce->arg_list);
+		if (arg->kind != AstNode_SelectorExpression) {
+			gbString str = expression_to_string(arg);
+			print_checker_error(c, ast_node_token(arg), "`%s` is not a selector expression", str);
+			return false;
+		}
+		auto *s = &arg->selector_expression;
+
+		check_expression(c, operand, s->operand);
+		if (operand->mode == Addressing_Invalid)
+			return false;
+
+		Type *type = operand->type;
+		if (get_base_type(type)->kind == Type_Pointer) {
+			Type *p = get_base_type(type);
+			if (get_base_type(p)->kind == Type_Structure)
+				type = p->pointer.element;
+		}
+
+		isize index = 0;
+		Entity *entity = lookup_field(type, s->selector, &index);
+		if (entity == NULL) {
+			gbString type_str = type_to_string(type);
+			print_checker_error(c, ast_node_token(arg),
+			                    "`%s` has no field named `%s`", type_str, LIT(s->selector->identifier.token.string));
+			return false;
+		}
+
+		operand->mode = Addressing_Constant;
+		operand->value = make_value_integer(type_offset_of(c->sizes, allocator, type, index));
+		operand->type  = &basic_types[Basic_int];
+	} break;
+
+	case BuiltinProcedure_static_assert:
+		if (operand->mode != Addressing_Constant ||
+		    !is_type_boolean(operand->type)) {
+			gbString str = expression_to_string(ce->arg_list);
+			defer (gb_string_free(str));
+			print_checker_error(c, ast_node_token(call),
+			                    "`%s` is not a constant boolean", str);
+			return false;
+		}
+		if (!operand->value.value_bool) {
+			gbString str = expression_to_string(ce->arg_list);
+			defer (gb_string_free(str));
+			print_checker_error(c, ast_node_token(call),
+			                    "Static assertion: `%s`", str);
+			return true;
+		}
+		break;
+
+	case BuiltinProcedure_print:
+	case BuiltinProcedure_println: {
+		for (AstNode *arg = ce->arg_list; arg != NULL; arg = arg->next) {
+			check_assignment(c, operand, NULL, make_string("argument"));
+			if (operand->mode == Addressing_Invalid)
+				return false;
+		}
+	} break;
+	}
+
+	return true;
+}
+
+
+void check_call_arguments(Checker *c, Operand *operand, Type *proc_type, AstNode *call) {
+	GB_ASSERT(call->kind == AstNode_CallExpression);
+	GB_ASSERT(proc_type->kind == Type_Procedure);
+	auto *ce = &call->call_expression;
+	isize param_count = 0;
+	if (proc_type->procedure.params)
+		param_count = proc_type->procedure.params->tuple.variable_count;
+
+ 	if (ce->arg_list_count == 0 && param_count == 0)
+		return;
+
+	isize error_code = 0;
+
+	if (ce->arg_list_count > param_count) {
+		error_code = +1;
+	} else {
+		Entity **sig_params = proc_type->procedure.params->tuple.variables;
+		isize param_index = 0;
+		AstNode *call_arg = ce->arg_list;
+		for (;
+		     call_arg != NULL && param_index < param_count;
+		     call_arg = call_arg->next) {
+			check_multi_expression(c, operand, call_arg);
+			if (operand->mode == Addressing_Invalid)
+				continue;
+			if (operand->type->kind != Type_Tuple) {
+				check_not_tuple(c, operand);
+				check_assignment(c, operand, sig_params[param_index]->type, make_string("argument"));
+				param_index++;
+			} else {
+				auto *tuple = &operand->type->tuple;
+				isize i = 0;
+				for (;
+				     i < tuple->variable_count && param_index < param_count;
+				     i++, param_index++) {
+					Entity *e = tuple->variables[i];
+					operand->type = e->type;
+					operand->mode = Addressing_Value;
+					check_not_tuple(c, operand);
+					check_assignment(c, operand, sig_params[param_index]->type, make_string("argument"));
+
+				}
+
+				if (i < tuple->variable_count && param_index == param_count) {
+					error_code = +1;
+					break;
+				}
+			}
+
+			if (param_index < param_count)
+				break;
+		}
+
+		if (param_index < param_count) {
+			error_code = -1;
+		} else if (call_arg != NULL && call_arg->next != NULL) {
+			error_code = +1;
+		}
+	}
+
+	if (error_code != 0) {
+		char *err_fmt = "";
+		if (error_code < 0)
+			err_fmt = "Too few arguments for `%s`, expected %td arguments";
+		else
+			err_fmt = "Too many arguments for `%s`, expected %td arguments";
+
+		gbString proc_str = expression_to_string(ce->proc);
+		print_checker_error(c, ast_node_token(call), err_fmt, proc_str, param_count);
+		gb_string_free(proc_str);
+
+		operand->mode = Addressing_Invalid;
+	}
+}
+
+
+ExpressionKind check_call_expression(Checker *c, Operand *operand, AstNode *call) {
+	GB_ASSERT(call->kind == AstNode_CallExpression);
+	auto *ce = &call->call_expression;
+	check_expression_or_type(c, operand, ce->proc);
+
+	if (operand->mode == Addressing_Invalid) {
+		for (AstNode *arg = ce->arg_list; arg != NULL; arg = arg->next)
+			check_expression_base(c, operand, arg);
+		operand->mode = Addressing_Invalid;
+		operand->expression = call;
+		return Expression_Statement;
+	}
+
+	if (operand->mode == Addressing_Builtin) {
+		i32 id = operand->builtin_id;
+		if (!check_builtin_procedure(c, operand, call, id))
+			operand->mode = Addressing_Invalid;
+		operand->expression = call;
+		return builtin_procedures[id].kind;
+	}
+
+	Type *proc_type = get_base_type(operand->type);
+	if (proc_type == NULL || proc_type->kind != Type_Procedure) {
+		AstNode *e = operand->expression;
+		gbString str = expression_to_string(e);
+		defer (gb_string_free(str));
+		print_checker_error(c, ast_node_token(e),
+		                    "Cannot call a non-procedure: `%s`", str);
+
+		operand->mode = Addressing_Invalid;
+		operand->expression = call;
+
+		return Expression_Statement;
+	}
+
+	check_call_arguments(c, operand, proc_type, call);
+
+	auto *proc = &proc_type->procedure;
+	if (proc->results_count == 0) {
+		operand->mode = Addressing_NoValue;
+	} else if (proc->results_count == 1) {
+		operand->mode = Addressing_Value;
+		operand->type = proc->results->tuple.variables[0]->type;
+	} else {
+		operand->mode = Addressing_Value;
+		operand->type = proc->results;
+	}
+
+	operand->expression = call;
+	return Expression_Statement;
+}
+
+b32 check_castable_to(Checker *c, Operand *operand, Type *y) {
+	if (check_assignable_to(c, operand, y))
+		return true;
+
+	Type *x = operand->type;
+	Type *xb = get_base_type(x);
+	Type *yb = get_base_type(y);
+	if (are_types_identical(xb, yb))
+		return true;
+
+	// Cast between numbers
+	if (is_type_integer(x) || is_type_float(x)) {
+		if (is_type_integer(y) || is_type_float(y))
+			return true;
+	}
+
+	// Cast between pointers
+	if (is_type_pointer(x)) {
+		if (is_type_pointer(y))
+			return true;
+	}
+
+	// untyped integers -> pointers
+	if (is_type_untyped(xb) && is_type_integer(xb)) {
+		if (is_type_pointer(yb))
+			return true;
+	}
+
+	// (u)int <-> pointer
+	if (is_type_pointer(xb) || is_type_int_or_uint(xb)) {
+		if (is_type_pointer(yb))
+			return true;
+	}
+	if (is_type_pointer(xb)) {
+		if (is_type_pointer(yb) || is_type_int_or_uint(yb))
+			return true;
+	}
+
+	return false;
+}
+
+void check_cast_expression(Checker *c, Operand *operand, Type *type) {
+	b32 const_expr = operand->mode == Addressing_Constant;
+	b32 can_convert = false;
+
+	if (const_expr && is_type_constant_type(type)) {
+		Type *t = get_base_type(type);
+		if (t->kind == Type_Basic) {
+			if (check_value_is_expressible(c, operand->value, t, &operand->value)) {
+				can_convert = true;
+			}
+		}
+	} else if (check_castable_to(c, operand, type)) {
+		operand->mode = Addressing_Value;
+		can_convert = true;
+	}
+
+	if (!can_convert) {
+		gbString expr_str = expression_to_string(operand->expression);
+		gbString type_str = type_to_string(type);
+		defer (gb_string_free(expr_str));
+		defer (gb_string_free(type_str));
+		print_checker_error(c, ast_node_token(operand->expression),
+		                    "Cannot cast `%s` to `%s`", expr_str, type_str);
+
+		operand->mode = Addressing_Invalid;
+		return;
+	}
+
+	operand->type = type;
+}
+
+
+
+ExpressionKind check_expression_base(Checker *c, Operand *operand, AstNode *expression, Type *type_hint) {
+	ExpressionKind kind = Expression_Statement;
+
+	operand->mode = Addressing_Invalid;
+	operand->type = &basic_types[Basic_Invalid];
+
+	switch (expression->kind) {
+	case AstNode_BadExpression:
+		goto error;
+
+	case AstNode_Identifier:
+		check_identifier(c, operand, expression, type_hint);
+		break;
+	case AstNode_BasicLiteral: {
+		BasicKind kind = Basic_Invalid;
+		Token lit = expression->basic_literal;
+		switch (lit.kind) {
+		case Token_Integer: kind = Basic_UntypedInteger; break;
+		case Token_Float:   kind = Basic_UntypedFloat;   break;
+		case Token_String:  kind = Basic_UntypedString;  break;
+		case Token_Rune:    kind = Basic_UntypedRune;    break;
+		default:            GB_PANIC("Unknown literal"); break;
+		}
+		operand->mode  = Addressing_Constant;
+		operand->type  = &basic_types[kind];
+		operand->value = make_value_from_basic_literal(lit);
+	} break;
+
+	case AstNode_ParenExpression:
+		kind = check_expression_base(c, operand, expression->paren_expression.expression);
+		operand->expression = expression;
+		break;
+
+	case AstNode_UnaryExpression:
+		check_expression(c, operand, expression->unary_expression.operand);
+		if (operand->mode == Addressing_Invalid)
+			goto error;
+		check_unary_expression(c, operand, expression->unary_expression.op, expression);
+		if (operand->mode == Addressing_Invalid)
+			goto error;
+		break;
+
+	case AstNode_BinaryExpression:
+		check_binary_expression(c, operand, expression);
+		if (operand->mode == Addressing_Invalid)
+			goto error;
+		break;
+
+
+	case AstNode_SelectorExpression:
+		check_expression_base(c, operand, expression->selector_expression.operand);
+		check_selector(c, operand, expression);
+		break;
+
+	case AstNode_IndexExpression: {
+		check_expression(c, operand, expression->index_expression.expression);
+		if (operand->mode == Addressing_Invalid)
+			goto error;
+
+		b32 valid = false;
+		b32 bound_checks = false;
+		i64 max_count = 0;
+		Type *t = get_base_type(operand->type);
+		switch (t->kind) {
+		case Type_Basic:
+			if (is_type_string(t)) {
+				valid = true;
+				if (operand->mode == Addressing_Constant) {
+					max_count = operand->value.value_string.len;
+					bound_checks = true;
+				}
+				operand->mode = Addressing_Value;
+				operand->type = &basic_types[Basic_u8];
+			}
+			break;
+
+		case Type_Array:
+			valid = true;
+			max_count = t->array.count;
+			bound_checks = max_count > 0;
+			if (operand->mode != Addressing_Variable)
+				operand->mode = Addressing_Value;
+			operand->type = t->array.element;
+			break;
+
+		case Type_Pointer:
+			valid = true;
+			bound_checks = false;
+			max_count = 0;
+			operand->mode = Addressing_Variable;
+			operand->type = get_base_type(t->pointer.element);
+			break;
+		}
+
+		if (!valid) {
+			gbString str = expression_to_string(operand->expression);
+			print_checker_error(c, ast_node_token(operand->expression),
+			                    "Cannot index `%s`", str);
+			gb_string_free(str);
+			goto error;
+		}
+
+		if (expression->index_expression.value == NULL) {
+			gbString str = expression_to_string(operand->expression);
+			print_checker_error(c, ast_node_token(operand->expression),
+			                    "Missing index for `%s`", str);
+			gb_string_free(str);
+			goto error;
+		}
+
+		check_index_value(c, expression->index_expression.value, max_count, bound_checks);
+	} break;
+
+	case AstNode_CastExpression: {
+		Type *cast_type = check_type(c, expression->cast_expression.type_expression);
+		check_expression_or_type(c, operand, expression->cast_expression.operand);
+		if (operand->mode != Addressing_Invalid)
+			check_cast_expression(c, operand, cast_type);
+
+	} break;
+
+	case AstNode_CallExpression:
+		return check_call_expression(c, operand, expression);
+
+	case AstNode_DereferenceExpression:
+		check_expression_or_type(c, operand, expression->dereference_expression.operand);
+		if (operand->mode == Addressing_Invalid) {
+			goto error;
+		} else {
+			Type *t = get_base_type(operand->type);
+			if (t->kind == Type_Pointer) {
+				operand->mode = Addressing_Variable;
+				operand->type = t->pointer.element;
+ 			} else {
+ 				gbString str = expression_to_string(operand->expression);
+ 				print_checker_error(c, ast_node_token(operand->expression),
+ 				                    "Cannot dereference `%s`", str);
+ 				gb_string_free(str);
+ 				goto error;
+ 			}
+		}
+		break;
+
+	case AstNode_ProcedureType:
+	case AstNode_PointerType:
+	case AstNode_ArrayType:
+	case AstNode_StructType:
+		operand->mode = Addressing_Type;
+		operand->type = check_type(c, expression);
+		break;
+	}
+
+	kind = Expression_Expression;
+	operand->expression = expression;
+	goto after_error;
+
+error:
+	operand->mode = Addressing_Invalid;
+	operand->expression = expression;
+	goto after_error;
+
+after_error:
+	Type *type = NULL;
+	Value value = {Value_Invalid};
+	switch (operand->mode) {
+	case Addressing_Invalid:
+		type = &basic_types[Basic_Invalid];
+		break;
+	case Addressing_NoValue:
+		type = NULL;
+		break;
+	case Addressing_Constant:
+		type = operand->type;
+		value = operand->value;
+		break;
+	default:
+		type = operand->type;
+		break;
+	}
+
+	if (type) {
+		if (is_type_untyped(type)) {
+			add_untyped(c, expression, false, operand->mode, type, value);
+		} else {
+			add_type_and_value(c, expression, operand->mode, type, value);
+		}
+	}
+	return kind;
+}
+
+void check_multi_expression(Checker *c, Operand *operand, AstNode *expression) {
+	gbString err_str = NULL;
+	defer (gb_string_free(err_str));
+
+	check_expression_base(c, operand, expression);
+	switch (operand->mode) {
+	default:
+		return; // NOTE(bill): Valid
+
+	case Addressing_NoValue:
+		err_str = expression_to_string(expression);
+		print_checker_error(c, ast_node_token(expression), "`%s` used as value", err_str);
+		break;
+	case Addressing_Type:
+		err_str = expression_to_string(expression);
+		print_checker_error(c, ast_node_token(expression), "`%s` is not an expression", err_str);
+		break;
+	}
+	operand->mode = Addressing_Invalid;
+}
+
+// NOTE(bill): Just a santity checker
+// TODO(bill): Remove this entirely
+void check_not_tuple(Checker *c, Operand *operand) {
+	if (operand->mode == Addressing_Value) {
+		// NOTE(bill): Tuples are not first class thus never named
+		if (operand->type->kind == Type_Tuple) {
+			isize count = operand->type->tuple.variable_count;
+			GB_ASSERT(count != 1);
+			print_checker_error(c, ast_node_token(operand->expression),
+			                    gb_bprintf("%td-valued tuple found where single value expected", count));
+			operand->mode = Addressing_Invalid;
+		}
+	}
+}
+
+void check_expression(Checker *c, Operand *operand, AstNode *expression) {
+	check_multi_expression(c, operand, expression);
+	check_not_tuple(c, operand);
+}
+
+
+void check_expression_or_type(Checker *c, Operand *operand, AstNode *expression) {
+	check_expression_base(c, operand, expression);
+	check_not_tuple(c, operand);
+	if (operand->mode == Addressing_NoValue) {
+		AstNode *e = operand->expression;
+		gbString str = expression_to_string(e);
+		defer (gb_string_free(str));
+		print_checker_error(c, ast_node_token(e),
+		                    "`%s` used as value or type", str);
+		operand->mode = Addressing_Invalid;
+	}
+}
+
+
+gbString write_expression_to_string(gbString str, AstNode *node);
+
+gbString write_field_list_to_string(gbString str, AstNode *field_list, char *sep) {
+	isize i = 0;
+	for (AstNode *field = field_list; field != NULL; field = field->next) {
+		GB_ASSERT(field->kind == AstNode_Field);
+		if (i > 0)
+			str = gb_string_appendc(str, sep);
+
+		isize j = 0;
+		for (AstNode *name = field->field.name_list; name != NULL; name = name->next) {
+			if (j > 0)
+				str = gb_string_appendc(str, ", ");
+			str = write_expression_to_string(str, name);
+			j++;
+		}
+
+		str = gb_string_appendc(str, ": ");
+		str = write_expression_to_string(str, field->field.type_expression);
+
+		i++;
+	}
+	return str;
+}
+
+gbString string_append_token(gbString str, Token token) {
+	return gb_string_append_length(str, token.string.text, token.string.len);
+}
+
+
+gbString write_expression_to_string(gbString str, AstNode *node) {
+	if (node == NULL)
+		return str;
+
+	switch (node->kind) {
+	default:
+		str = gb_string_appendc(str, "(bad expression)");
+		break;
+
+	case AstNode_Identifier:
+		str = string_append_token(str, node->identifier.token);
+		break;
+
+	case AstNode_BasicLiteral:
+		str = string_append_token(str, node->basic_literal);
+		break;
+
+	case AstNode_UnaryExpression:
+		str = string_append_token(str, node->unary_expression.op);
+		str = write_expression_to_string(str, node->unary_expression.operand);
+		break;
+
+	case AstNode_BinaryExpression:
+		str = write_expression_to_string(str, node->binary_expression.left);
+		str = gb_string_appendc(str, " ");
+		str = string_append_token(str, node->binary_expression.op);
+		str = gb_string_appendc(str, " ");
+		str = write_expression_to_string(str, node->binary_expression.right);
+		break;
+
+	case AstNode_ParenExpression:
+		str = gb_string_appendc(str, "(");
+		str = write_expression_to_string(str, node->paren_expression.expression);
+		str = gb_string_appendc(str, ")");
+		break;
+
+	case AstNode_SelectorExpression:
+		str = write_expression_to_string(str, node->selector_expression.operand);
+		str = gb_string_appendc(str, ".");
+		str = write_expression_to_string(str, node->selector_expression.selector);
+		break;
+
+	case AstNode_IndexExpression:
+		str = write_expression_to_string(str, node->index_expression.expression);
+		str = gb_string_appendc(str, "[");
+		str = write_expression_to_string(str, node->index_expression.value);
+		str = gb_string_appendc(str, "]");
+		break;
+
+	case AstNode_CastExpression:
+		str = gb_string_appendc(str, "cast(");
+		str = write_expression_to_string(str, node->cast_expression.type_expression);
+		str = gb_string_appendc(str, ")");
+		str = write_expression_to_string(str, node->cast_expression.operand);
+		break;
+
+
+	case AstNode_PointerType:
+		str = gb_string_appendc(str, "*");
+		str = write_expression_to_string(str, node->pointer_type.type_expression);
+		break;
+	case AstNode_ArrayType:
+		str = gb_string_appendc(str, "[");
+		str = write_expression_to_string(str, node->array_type.count);
+		str = gb_string_appendc(str, "]");
+		str = write_expression_to_string(str, node->array_type.element);
+		break;
+
+	case AstNode_CallExpression: {
+		str = write_expression_to_string(str, node->call_expression.proc);
+		str = gb_string_appendc(str, "(");
+		isize i = 0;
+		for (AstNode *arg = node->call_expression.arg_list; arg != NULL; arg = arg->next) {
+			if (i > 0) gb_string_appendc(str, ", ");
+			str = write_expression_to_string(str, arg);
+			i++;
+		}
+		str = gb_string_appendc(str, ")");
+	} break;
+
+	case AstNode_ProcedureType:
+		str = gb_string_appendc(str, "proc(");
+		str = write_field_list_to_string(str, node->procedure_type.param_list, ", ");
+		str = gb_string_appendc(str, ")");
+
+		break;
+	case AstNode_StructType:
+		str = gb_string_appendc(str, "struct{");
+		str = gb_string_appendc(str, "}");
+		break;
+
+	}
+
+	return str;
+}
+
+gbString expression_to_string(AstNode *expression) {
+	return write_expression_to_string(gb_string_make(gb_heap_allocator(), ""), expression);
+}

+ 695 - 0
src/checker/statements.cpp

@@ -0,0 +1,695 @@
+//
+
+b32 check_assignable_to(Checker *c, Operand *operand, Type *type) {
+	if (operand->mode == Addressing_Invalid ||
+	    type == &basic_types[Basic_Invalid]) {
+		return true;
+	}
+
+	Type *s = operand->type;
+
+	if (are_types_identical(s, type))
+		return true;
+
+	Type *sb = get_base_type(s);
+	Type *tb = get_base_type(type);
+
+	if (is_type_untyped(sb)) {
+		switch (tb->kind) {
+		case Type_Basic:
+			if (operand->mode == Addressing_Constant)
+				return check_value_is_expressible(c, operand->value, tb, NULL);
+			if (sb->kind == Type_Basic)
+				return sb->basic.kind == Basic_UntypedBool && is_type_boolean(tb);
+			break;
+		case Type_Pointer:
+			return sb->basic.kind == Basic_UntypedPointer;
+		}
+	}
+
+	if (are_types_identical(sb, tb) && (!is_type_named(sb) || !is_type_named(tb)))
+		return true;
+
+	if (is_type_pointer(sb) && is_type_rawptr(tb))
+	    return true;
+
+	if (is_type_rawptr(sb) && is_type_pointer(tb))
+	    return true;
+
+	if (sb->kind == Type_Array && tb->kind == Type_Array) {
+		if (are_types_identical(sb->array.element, tb->array.element)) {
+			if (tb->array.count == 0) // NOTE(bill): Not static size
+				return true;
+		}
+	}
+
+	return false;
+}
+
+
+// NOTE(bill): `content_name` is for debugging
+void check_assignment(Checker *c, Operand *operand, Type *type, String context_name) {
+	check_not_tuple(c, operand);
+	if (operand->mode == Addressing_Invalid)
+		return;
+
+	if (is_type_untyped(operand->type)) {
+		Type *target_type = type;
+
+		if (type == NULL)
+			target_type = default_type(operand->type);
+		convert_to_typed(c, operand, target_type);
+		if (operand->mode == Addressing_Invalid)
+			return;
+	}
+
+	if (type != NULL) {
+		if (!check_assignable_to(c, operand, type)) {
+			gbString type_string = type_to_string(type);
+			gbString op_type_string = type_to_string(operand->type);
+			defer (gb_string_free(type_string));
+			defer (gb_string_free(op_type_string));
+
+			// TODO(bill): is this a good enough error message?
+			print_checker_error(c, ast_node_token(operand->expression),
+			                    "Cannot assign value `%.*s` of type `%s` to `%s` in %.*s",
+			                    LIT(ast_node_token(operand->expression).string),
+			                    op_type_string,
+			                    type_string,
+			                    LIT(context_name));
+
+			operand->mode = Addressing_Invalid;
+		}
+	}
+}
+
+
+Type *check_assign_variable(Checker *c, Operand *x, AstNode *lhs) {
+	if (x->mode == Addressing_Invalid ||
+	    x->type == &basic_types[Basic_Invalid]) {
+		return NULL;
+	}
+
+	AstNode *node = unparen_expression(lhs);
+
+	// NOTE(bill): Ignore assignments to `_`
+	if (node->kind == AstNode_Identifier &&
+	    are_strings_equal(node->identifier.token.string, make_string("_"))) {
+    	add_entity_definition(c, node, NULL);
+    	check_assignment(c, x, NULL, make_string("assignment to `_` identifier"));
+    	if (x->mode == Addressing_Invalid)
+    		return NULL;
+    	return x->type;
+    }
+
+	Entity *e = NULL;
+	b32 used = false;
+	if (node->kind == AstNode_Identifier) {
+		scope_lookup_parent_entity(c->curr_scope, node->identifier.token.string,
+		                           NULL, &e);
+		if (e != NULL && e->kind == Entity_Variable) {
+			used = e->variable.used; // TODO(bill): Make backup just in case
+		}
+	}
+
+
+	Operand y = {Addressing_Invalid};
+	check_expression(c, &y, lhs);
+	if (e) e->variable.used = used;
+
+	if (y.mode == Addressing_Invalid ||
+	    y.type == &basic_types[Basic_Invalid]) {
+		return NULL;
+	}
+
+	switch (y.mode) {
+	case Addressing_Variable:
+		break;
+	case Addressing_Invalid:
+		return NULL;
+	default: {
+		if (y.expression->kind == AstNode_SelectorExpression) {
+			// NOTE(bill): Extra error checks
+			Operand z = {Addressing_Invalid};
+			check_expression(c, &z, y.expression->selector_expression.operand);
+		}
+
+		gbString str = expression_to_string(y.expression);
+		defer (gb_string_free(str));
+		print_checker_error(c, ast_node_token(y.expression),
+		                    "Cannot assign to `%s`", str);
+	} break;
+	}
+
+	check_assignment(c, x, y.type, make_string("assignment"));
+	if (x->mode == Addressing_Invalid)
+		return NULL;
+
+	return x->type;
+}
+
+void check_assign_variables(Checker *c,
+                            AstNode *lhs_list, isize lhs_count,
+                            AstNode *rhs_list, isize rhs_count) {
+	Operand operand = {Addressing_Invalid};
+	AstNode *lhs = lhs_list, *rhs = rhs_list;
+	for (;
+	     lhs != NULL && rhs != NULL;
+	     lhs = lhs->next, rhs = rhs->next) {
+		check_multi_expression(c, &operand, rhs);
+		if (operand.type->kind != Type_Tuple) {
+			check_assign_variable(c, &operand, lhs);
+		} else {
+			auto *tuple = &operand.type->tuple;
+			for (isize i = 0;
+			     i < tuple->variable_count && lhs != NULL;
+			     i++, lhs = lhs->next) {
+				// TODO(bill): More error checking
+				operand.type = tuple->variables[i]->type;
+				check_assign_variable(c, &operand, lhs);
+			}
+			if (lhs == NULL)
+				break;
+		}
+	}
+}
+
+// NOTE(bill): `content_name` is for debugging
+Type *check_init_variable(Checker *c, Entity *e, Operand *operand, String context_name) {
+	if (operand->mode == Addressing_Invalid ||
+	    operand->type == &basic_types[Basic_Invalid] ||
+	    e->type == &basic_types[Basic_Invalid]) {
+		if (e->type == NULL)
+			e->type = &basic_types[Basic_Invalid];
+		return NULL;
+	}
+
+	if (e->type == NULL) {
+		// NOTE(bill): Use the type of the operand
+		Type *t = operand->type;
+		if (is_type_untyped(t)) {
+			if (t == &basic_types[Basic_Invalid]) {
+				print_checker_error(c, e->token, "Use of untyped thing in %.*s", LIT(context_name));
+				e->type = &basic_types[Basic_Invalid];
+				return NULL;
+			}
+			t = default_type(t);
+		}
+		e->type = t;
+	}
+
+	check_assignment(c, operand, e->type, context_name);
+	if (operand->mode == Addressing_Invalid)
+		return NULL;
+
+	return e->type;
+}
+
+void check_init_variables(Checker *c, Entity **lhs, isize lhs_count, AstNode *init_list, isize init_count, String context_name) {
+	if (lhs_count == 0 && init_count == 0)
+		return;
+
+	isize i = 0;
+	AstNode *rhs = init_list;
+	for (;
+	     i < lhs_count && rhs != NULL;
+	     i++, rhs = rhs->next) {
+		Operand operand = {};
+		check_multi_expression(c, &operand, rhs);
+		if (operand.type->kind != Type_Tuple) {
+			check_init_variable(c, lhs[i], &operand, context_name);
+		} else {
+			auto *tuple = &operand.type->tuple;
+			for (isize j = 0;
+			     j < tuple->variable_count && i < lhs_count;
+			     j++, i++) {
+				Type *type = tuple->variables[j]->type;
+				operand.type = type;
+				check_init_variable(c, lhs[i], &operand, context_name);
+			}
+		}
+	}
+
+	if (i < lhs_count) {
+		if (lhs[i]->type == NULL)
+			print_checker_error(c, lhs[i]->token, "Too few values on the right hand side of the declaration");
+	} else if (rhs != NULL) {	
+		print_checker_error(c, ast_node_token(rhs), "Too many values on the right hand side of the declaration");
+	}
+}
+
+void check_init_constant(Checker *c, Entity *e, Operand *operand) {
+	if (operand->mode == Addressing_Invalid ||
+	    operand->type == &basic_types[Basic_Invalid] ||
+	    e->type == &basic_types[Basic_Invalid]) {
+
+		if (e->type == NULL)
+			e->type = &basic_types[Basic_Invalid];
+		return;
+	}
+
+	if (operand->mode != Addressing_Constant) {
+		// TODO(bill): better error
+		print_checker_error(c, ast_node_token(operand->expression),
+		                    "`%.*s` is not a constant", LIT(ast_node_token(operand->expression).string));
+		if (e->type == NULL)
+			e->type = &basic_types[Basic_Invalid];
+		return;
+	}
+	if (!is_type_constant_type(operand->type)) {
+		// NOTE(bill): no need to free string as it's panicking
+		GB_PANIC("Type `%s` not constant!!!", type_to_string(operand->type));
+	}
+
+	if (e->type == NULL) // NOTE(bill): type inference
+		e->type = operand->type;
+
+	check_assignment(c, operand, e->type, make_string("constant declaration"));
+	if (operand->mode == Addressing_Invalid)
+		return;
+
+	e->constant.value = operand->value;
+}
+
+void check_constant_declaration(Checker *c, Entity *e, AstNode *type_expression, AstNode *init_expression) {
+	GB_ASSERT(e->type == NULL);
+
+	if (e->variable.visited) {
+		e->type = &basic_types[Basic_Invalid];
+		return;
+	}
+	e->variable.visited = true;
+
+	if (type_expression) {
+		Type *t = check_type(c, type_expression);
+		if (!is_type_constant_type(t)) {
+			gbString str = type_to_string(t);
+			defer (gb_string_free(str));
+			print_checker_error(c, ast_node_token(type_expression),
+			                    "Invalid constant type `%s`", str);
+			e->type = &basic_types[Basic_Invalid];
+			return;
+		}
+		e->type = t;
+	}
+
+	Operand operand = {Addressing_Invalid};
+	if (init_expression)
+		check_expression(c, &operand, init_expression);
+	check_init_constant(c, e, &operand);
+}
+
+void check_statement(Checker *c, AstNode *node);
+
+void check_statement_list(Checker *c, AstNode *node) {
+	for (; node != NULL; node = node->next)
+		check_statement(c, node);
+}
+
+b32 check_is_terminating(Checker *c, AstNode *node);
+
+b32 check_is_terminating_list(Checker *c, AstNode *node_list) {
+	AstNode *end_of_list = node_list;
+	for (; end_of_list != NULL; end_of_list = end_of_list->next) {
+		if (end_of_list->next == NULL)
+			break;
+	}
+
+	for (AstNode *node = end_of_list; node != NULL; node = node->prev) {
+		if (node->kind == AstNode_EmptyStatement)
+			continue;
+		return check_is_terminating(c, node);
+	}
+	return false;
+}
+
+b32 check_is_terminating(Checker *c, AstNode *node) {
+	switch (node->kind) {
+	case AstNode_BlockStatement:
+		return check_is_terminating_list(c, node->block_statement.list);
+
+	case AstNode_ExpressionStatement:
+		return check_is_terminating(c, node->expression_statement.expression);
+
+	case AstNode_ReturnStatement:
+		return true;
+
+	case AstNode_IfStatement:
+		if (node->if_statement.else_statement != NULL) {
+			if (check_is_terminating(c, node->if_statement.body) &&
+			    check_is_terminating(c, node->if_statement.else_statement))
+			    return true;
+		}
+		break;
+
+	case AstNode_ForStatement:
+		if (node->for_statement.cond == NULL) {
+			return true;
+		}
+		break;
+	}
+
+	return false;
+}
+
+void check_statement(Checker *c, AstNode *node) {
+	switch (node->kind) {
+	case AstNode_EmptyStatement: break;
+	case AstNode_BadStatement:   break;
+	case AstNode_BadDeclaration: break;
+
+	case AstNode_ExpressionStatement: {
+		Operand operand = {Addressing_Invalid};
+		ExpressionKind kind = check_expression_base(c, &operand, node->expression_statement.expression);
+		switch (operand.mode) {
+		case Addressing_Type:
+			print_checker_error(c, ast_node_token(node), "Is not an expression");
+			break;
+		default:
+			if (kind == Expression_Statement)
+				return;
+			print_checker_error(c, ast_node_token(node), "Expression is not used");
+			break;
+		}
+	} break;
+
+	case AstNode_IncDecStatement: {
+		Token op = {};
+		auto *s = &node->inc_dec_statement;
+		op = s->op;
+		switch (s->op.kind) {
+		case Token_Increment:
+			op.kind = Token_Add;
+			op.string.len = 1;
+			break;
+		case Token_Decrement:
+			op.kind = Token_Sub;
+			op.string.len = 1;
+			break;
+		default:
+			print_checker_error(c, s->op, "Unknown inc/dec operation %.*s", LIT(s->op.string));
+			return;
+		}
+
+		Operand operand = {Addressing_Invalid};
+		check_expression(c, &operand, s->expression);
+		if (operand.mode == Addressing_Invalid)
+			return;
+		if (!is_type_numeric(operand.type)) {
+			print_checker_error(c, s->op, "Non numeric type");
+			return;
+		}
+
+		AstNode basic_lit = {AstNode_BasicLiteral};
+		basic_lit.basic_literal = s->op;
+		basic_lit.basic_literal.kind = Token_Integer;
+		basic_lit.basic_literal.string = make_string("1");
+		AstNode be = {AstNode_BinaryExpression};
+		be.binary_expression.op = op;
+		be.binary_expression.left = s->expression;;
+		be.binary_expression.right = &basic_lit;
+		check_binary_expression(c, &operand, &be);
+
+	} break;
+
+	case AstNode_AssignStatement:
+		switch (node->assign_statement.op.kind) {
+		case Token_Eq:
+			if (node->assign_statement.lhs_count == 0) {
+				print_checker_error(c, node->assign_statement.op, "Missing lhs in assignment statement");
+				return;
+			}
+			check_assign_variables(c,
+			                       node->assign_statement.lhs_list, node->assign_statement.lhs_count,
+			                       node->assign_statement.rhs_list, node->assign_statement.rhs_count);
+			break;
+
+		default: {
+			Token op = node->assign_statement.op;
+			if (node->assign_statement.lhs_count != 1 ||
+			    node->assign_statement.rhs_count != 1) {
+				print_checker_error(c, op,
+				                    "assignment operation `%.*s` requires single-valued expressions", LIT(op.string));
+				return;
+			}
+			// TODO(bill): Check if valid assignment operator
+			Operand operand = {Addressing_Invalid};
+			AstNode be = {AstNode_BinaryExpression};
+			be.binary_expression.op    = op;
+			 // NOTE(bill): Only use the first one will be used
+			be.binary_expression.left  = node->assign_statement.lhs_list;
+			be.binary_expression.right = node->assign_statement.rhs_list;
+
+			check_binary_expression(c, &operand, &be);
+			if (operand.mode == Addressing_Invalid)
+				return;
+			// NOTE(bill): Only use the first one will be used
+			check_assign_variable(c, &operand, node->assign_statement.lhs_list);
+		} break;
+		}
+		break;
+
+	case AstNode_BlockStatement:
+		check_open_scope(c, node);
+		check_statement_list(c, node->block_statement.list);
+		check_close_scope(c);
+		break;
+
+	case AstNode_IfStatement: {
+		Operand operand = {Addressing_Invalid};
+		check_expression(c, &operand, node->if_statement.cond);
+		if (operand.mode != Addressing_Invalid &&
+		    !is_type_boolean(operand.type)) {
+			print_checker_error(c, ast_node_token(node->if_statement.cond),
+			                    "Non-boolean condition in `if` statement");
+		}
+		check_statement(c, node->if_statement.body);
+
+		if (node->if_statement.else_statement) {
+			switch (node->if_statement.else_statement->kind) {
+			case AstNode_IfStatement:
+			case AstNode_BlockStatement:
+				check_statement(c, node->if_statement.else_statement);
+				break;
+			default:
+				print_checker_error(c, ast_node_token(node->if_statement.else_statement),
+				                    "Invalid `else` statement in `if` statement");
+				break;
+			}
+		}
+	} break;
+
+	case AstNode_ReturnStatement: {
+		auto *rs = &node->return_statement;
+		GB_ASSERT(gb_array_count(c->procedure_stack) > 0);
+
+		if (c->in_defer) {
+			print_checker_error(c, rs->token, "You cannot `return` within a defer statement");
+			// TODO(bill): Should I break here?
+			break;
+		}
+
+		Type *proc_type = c->procedure_stack[gb_array_count(c->procedure_stack)-1];
+		isize result_count = 0;
+		if (proc_type->procedure.results)
+			result_count = proc_type->procedure.results->tuple.variable_count;
+		if (result_count != rs->result_count) {
+			print_checker_error(c, rs->token, "Expected %td return %s, got %td",
+			                    result_count,
+			                    (result_count != 1 ? "values" : "value"),
+			                    rs->result_count);
+		} else if (result_count > 0) {
+			auto *tuple = &proc_type->procedure.results->tuple;
+			check_init_variables(c, tuple->variables, tuple->variable_count,
+			                     rs->results, rs->result_count, make_string("return statement"));
+		}
+	} break;
+
+	case AstNode_ForStatement: {
+		check_open_scope(c, node);
+		defer (check_close_scope(c));
+
+		check_statement(c, node->for_statement.init);
+		if (node->for_statement.cond) {
+			Operand operand = {Addressing_Invalid};
+			check_expression(c, &operand, node->for_statement.cond);
+			if (operand.mode != Addressing_Invalid &&
+			    !is_type_boolean(operand.type)) {
+				print_checker_error(c, ast_node_token(node->for_statement.cond),
+				                    "Non-boolean condition in `for` statement");
+			}
+		}
+		check_statement(c, node->for_statement.end);
+		check_statement(c, node->for_statement.body);
+	} break;
+
+	case AstNode_DeferStatement: {
+		auto *ds = &node->defer_statement;
+		if (is_ast_node_declaration(ds->statement)) {
+			print_checker_error(c, ds->token, "You cannot defer a declaration");
+		} else {
+			b32 out_in_defer = c->in_defer;
+			c->in_defer = true;
+			check_statement(c, ds->statement);
+			c->in_defer = out_in_defer;
+		}
+	} break;
+
+
+// Declarations
+	case AstNode_VariableDeclaration: {
+		auto *vd = &node->variable_declaration;
+		gbAllocator allocator = gb_arena_allocator(&c->entity_arena);
+
+		isize entity_count = vd->name_list_count;
+		isize entity_index = 0;
+		Entity **entities = gb_alloc_array(allocator, Entity *, entity_count);
+		switch (vd->kind) {
+		case Declaration_Mutable: {
+			Entity **new_entities = gb_alloc_array(allocator, Entity *, entity_count);
+			isize new_entity_count = 0;
+
+			for (AstNode *name = vd->name_list; name != NULL; name = name->next) {
+				Entity *entity = NULL;
+				Token token = name->identifier.token;
+				if (name->kind == AstNode_Identifier) {
+					String str = token.string;
+					Entity *found = NULL;
+					// NOTE(bill): Ignore assignments to `_`
+					b32 can_be_ignored = are_strings_equal(str, make_string("_"));
+					if (!can_be_ignored) {
+						found = scope_lookup_entity_current(c->curr_scope, str);
+					}
+					if (found == NULL) {
+						entity = make_entity_variable(c, c->curr_scope, token, NULL);
+						if (!can_be_ignored) {
+							new_entities[new_entity_count++] = entity;
+						}
+						add_entity_definition(c, name, entity);
+					} else {
+						entity = found;
+					}
+				} else {
+					print_checker_error(c, token, "A variable declaration must be an identifier");
+				}
+				if (entity == NULL)
+					entity = make_entity_dummy_variable(c, token);
+				entities[entity_index++] = entity;
+			}
+
+			Type *init_type = NULL;
+			if (vd->type_expression) {
+				init_type = check_type(c, vd->type_expression, NULL);
+				if (init_type == NULL)
+					init_type = &basic_types[Basic_Invalid];
+			}
+
+			for (isize i = 0; i < entity_count; i++) {
+				Entity *e = entities[i];
+				GB_ASSERT(e != NULL);
+				if (e->variable.visited) {
+					e->type = &basic_types[Basic_Invalid];
+					continue;
+				}
+				e->variable.visited = true;
+
+				if (e->type == NULL)
+					e->type = init_type;
+			}
+
+
+			check_init_variables(c, entities, entity_count, vd->value_list, vd->value_list_count, make_string("variable declaration"));
+
+			AstNode *name = vd->name_list;
+			for (isize i = 0; i < new_entity_count; i++, name = name->next) {
+				add_entity(c, c->curr_scope, name, new_entities[i]);
+			}
+
+		} break;
+
+		case Declaration_Immutable: {
+			for (AstNode *name = vd->name_list, *value = vd->value_list;
+			     name != NULL && value != NULL;
+			     name = name->next, value = value->next) {
+				GB_ASSERT(name->kind == AstNode_Identifier);
+				Value v = {Value_Invalid};
+				Entity *e = make_entity_constant(c, c->curr_scope, name->identifier.token, NULL, v);
+				entities[entity_index++] = e;
+				check_constant_declaration(c, e, vd->type_expression, value);
+			}
+
+			isize lhs_count = vd->name_list_count;
+			isize rhs_count = vd->value_list_count;
+
+			// TODO(bill): Better error messages or is this good enough?
+			if (rhs_count == 0 && vd->type_expression == NULL) {
+				print_checker_error(c, ast_node_token(node), "Missing type or initial expression");
+			} else if (lhs_count < rhs_count) {
+				print_checker_error(c, ast_node_token(node), "Extra initial expression");
+			}
+
+			AstNode *name = vd->name_list;
+			for (isize i = 0; i < entity_count; i++, name = name->next) {
+				add_entity(c, c->curr_scope, name, entities[i]);
+			}
+		} break;
+
+		default:
+			print_checker_error(c, ast_node_token(node), "Unknown variable declaration kind. Probably an invalid AST.");
+			return;
+		}
+	} break;
+
+
+	case AstNode_ProcedureDeclaration: {
+		auto *pd = &node->procedure_declaration;
+		GB_ASSERT_MSG(pd->kind == Declaration_Immutable, "Mutable/temp procedures are not yet implemented");
+		// TODO(bill): Should this be the case? And are the scopes correct?
+		// TODO(bill): Should procedures just have global scope?
+		Entity *e = make_entity_procedure(c, c->curr_scope, pd->name->identifier.token, NULL);
+		add_entity(c, c->curr_scope, pd->name, e);
+
+		Type *proc_type = make_type_procedure(e->parent, NULL, 0, NULL, 0);
+		e->type = proc_type;
+
+		check_open_scope(c, pd->procedure_type);
+		{
+			check_procedure_type(c, proc_type, pd->procedure_type);
+			if (pd->body) {
+				GB_ASSERT(pd->body->kind == AstNode_BlockStatement);
+
+				push_procedure(c, proc_type);
+				check_statement_list(c, pd->body->block_statement.list);
+				if (pd->procedure_type->procedure_type.result_count > 0) {
+					if (!check_is_terminating(c, pd->body)) {
+						print_checker_error(c, pd->body->block_statement.close, "Missing return statement at the end of the procedure");
+					}
+				}
+				pop_procedure(c);
+			} else if (pd->tag) {
+				GB_ASSERT(pd->tag->kind == AstNode_TagExpression);
+
+				String tag_name = pd->tag->tag_expression.name.string;
+				if (gb_strncmp("foreign", cast(char *)tag_name.text, tag_name.len) == 0) {
+					// NOTE(bill): Foreign procedure (linking stage)
+				}
+			}
+		}
+		check_close_scope(c);
+
+	} break;
+
+	case AstNode_TypeDeclaration: {
+		auto *td = &node->type_declaration;
+		AstNode *name = td->name;
+		Entity *e = make_entity_type_name(c, c->curr_scope, name->identifier.token, NULL);
+		add_entity(c, c->curr_scope, name, e);
+
+		e->type = make_type_named(e->token.string, NULL, e);
+		check_type(c, td->type_expression, e->type);
+		// NOTE(bill): Prevent recursive definition
+		set_base_type(e->type, get_base_type(e->type));
+
+	} break;
+	}
+}

+ 576 - 0
src/checker/type.cpp

@@ -0,0 +1,576 @@
+struct Scope;
+
+enum BasicKind {
+	Basic_Invalid,
+	Basic_bool,
+	Basic_i8,
+	Basic_i16,
+	Basic_i32,
+	Basic_i64,
+	Basic_u8,
+	Basic_u16,
+	Basic_u32,
+	Basic_u64,
+	Basic_f32,
+	Basic_f64,
+	Basic_int,
+	Basic_uint,
+	Basic_rawptr,
+	Basic_string,
+	Basic_UntypedBool,
+	Basic_UntypedInteger,
+	Basic_UntypedFloat,
+	Basic_UntypedPointer,
+	Basic_UntypedString,
+	Basic_UntypedRune,
+
+	Basic_Count,
+
+	Basic_rune = Basic_i32,
+};
+
+enum BasicFlag : u32 {
+	BasicFlag_Boolean  = GB_BIT(0),
+	BasicFlag_Integer  = GB_BIT(1),
+	BasicFlag_Unsigned = GB_BIT(2),
+	BasicFlag_Float    = GB_BIT(3),
+	BasicFlag_Pointer  = GB_BIT(4),
+	BasicFlag_String   = GB_BIT(5),
+	BasicFlag_Untyped  = GB_BIT(6),
+
+	BasicFlag_Numeric      = BasicFlag_Integer | BasicFlag_Float,
+	BasicFlag_Ordered      = BasicFlag_Numeric | BasicFlag_String  | BasicFlag_Pointer,
+	BasicFlag_ConstantType = BasicFlag_Boolean | BasicFlag_Numeric | BasicFlag_String | BasicFlag_Pointer,
+};
+
+struct BasicType {
+	BasicKind kind;
+	u32       flags;
+	String    name;
+};
+
+
+enum TypeKind {
+	Type_Invalid,
+
+	Type_Basic,
+	Type_Array,
+	Type_Structure,
+	Type_Pointer,
+	Type_Named,
+	Type_Tuple,
+	Type_Procedure,
+
+	Type_Count,
+};
+struct Type {
+	TypeKind kind;
+	union {
+		BasicType basic;
+		struct {
+			Type *element;
+			i64 count;
+		} array;
+		struct {
+			// Theses are arrays
+			Entity **fields; // Entity_Variable
+			isize    field_count; // == offset_count
+			i64 *    offsets;
+			b32      offsets_set;
+		} structure;
+		struct { Type *element; } pointer;
+		struct {
+			String  name;
+			Type *  base;
+			Entity *type_name; // Entity_TypeName
+		} named;
+		struct {
+			Entity **variables; // Entity_Variable
+			isize    variable_count;
+		} tuple;
+		struct {
+			Scope *scope;
+			Type * params;  // Type_Tuple
+			Type * results; // Type_Tuple
+			isize  params_count;
+			isize  results_count;
+		} procedure;
+	};
+};
+
+Type *get_base_type(Type *t) {
+	while (t->kind == Type_Named)
+		t = t->named.base;
+	return t;
+}
+
+void set_base_type(Type *t, Type *base) {
+	if (t && t->kind == Type_Named) {
+		t->named.base = base;
+	}
+}
+
+
+Type *alloc_type(TypeKind kind) {
+	Type *t = gb_alloc_item(gb_heap_allocator(), Type);
+	t->kind = kind;
+	return t;
+}
+
+
+Type *make_type_basic(BasicType basic) {
+	Type *t = alloc_type(Type_Basic);
+	t->basic = basic;
+	return t;
+}
+Type *make_type_array(Type *element, i64 count) {
+	Type *t = alloc_type(Type_Array);
+	t->array.element = element;
+	t->array.count = count;
+	return t;
+}
+
+Type *make_type_structure(void) {
+	Type *t = alloc_type(Type_Structure);
+	return t;
+}
+
+Type *make_type_pointer(Type *element) {
+	Type *t = alloc_type(Type_Pointer);
+	t->pointer.element = element;
+	return t;
+}
+
+Type *make_type_named(String name, Type *base, Entity *type_name) {
+	Type *t = alloc_type(Type_Named);
+	t->named.name = name;
+	t->named.base = base;
+	t->named.type_name = type_name;
+	return t;
+}
+
+Type *make_type_tuple(void) {
+	Type *t = alloc_type(Type_Tuple);
+	return t;
+}
+
+Type *make_type_procedure(Scope *scope, Type *params, isize params_count, Type *results, isize results_count) {
+	Type *t = alloc_type(Type_Procedure);
+	t->procedure.scope = scope;
+	t->procedure.params = params;
+	t->procedure.params_count = params_count;
+	t->procedure.results = results;
+	t->procedure.results_count = results_count;
+	return t;
+}
+
+
+
+#define STR_LIT(x) {cast(u8 *)x, gb_size_of(x)-1}
+gb_global Type basic_types[] = {
+	{Type_Basic, {Basic_Invalid,        0,                                      STR_LIT("invalid type")}},
+	{Type_Basic, {Basic_bool,           BasicFlag_Boolean,                      STR_LIT("bool")}},
+	{Type_Basic, {Basic_i8,             BasicFlag_Integer,                      STR_LIT("i8")}},
+	{Type_Basic, {Basic_i16,            BasicFlag_Integer,                      STR_LIT("i16")}},
+	{Type_Basic, {Basic_i32,            BasicFlag_Integer,                      STR_LIT("i32")}},
+	{Type_Basic, {Basic_i64,            BasicFlag_Integer,                      STR_LIT("i64")}},
+	{Type_Basic, {Basic_u8,             BasicFlag_Integer | BasicFlag_Unsigned, STR_LIT("u8")}},
+	{Type_Basic, {Basic_u16,            BasicFlag_Integer | BasicFlag_Unsigned, STR_LIT("u16")}},
+	{Type_Basic, {Basic_u32,            BasicFlag_Integer | BasicFlag_Unsigned, STR_LIT("u32")}},
+	{Type_Basic, {Basic_u64,            BasicFlag_Integer | BasicFlag_Unsigned, STR_LIT("u64")}},
+	{Type_Basic, {Basic_f32,            BasicFlag_Float,                        STR_LIT("f32")}},
+	{Type_Basic, {Basic_f64,            BasicFlag_Float,                        STR_LIT("f64")}},
+	{Type_Basic, {Basic_int,            BasicFlag_Integer,                      STR_LIT("int")}},
+	{Type_Basic, {Basic_uint,           BasicFlag_Integer | BasicFlag_Unsigned, STR_LIT("uint")}},
+	{Type_Basic, {Basic_rawptr,         BasicFlag_Pointer,                      STR_LIT("rawptr")}},
+	{Type_Basic, {Basic_string,         BasicFlag_String,                       STR_LIT("string")}},
+	{Type_Basic, {Basic_UntypedBool,    BasicFlag_Boolean | BasicFlag_Untyped,  STR_LIT("untyped bool")}},
+	{Type_Basic, {Basic_UntypedInteger, BasicFlag_Integer | BasicFlag_Untyped,  STR_LIT("untyped integer")}},
+	{Type_Basic, {Basic_UntypedFloat,   BasicFlag_Float   | BasicFlag_Untyped,  STR_LIT("untyped float")}},
+	{Type_Basic, {Basic_UntypedPointer, BasicFlag_Pointer | BasicFlag_Untyped,  STR_LIT("untyped pointer")}},
+	{Type_Basic, {Basic_UntypedString,  BasicFlag_String  | BasicFlag_Untyped,  STR_LIT("untyped string")}},
+	{Type_Basic, {Basic_UntypedRune,    BasicFlag_Integer | BasicFlag_Untyped,  STR_LIT("untyped rune")}},
+};
+
+gb_global Type basic_type_aliases[] = {
+	{Type_Basic, {Basic_rune, BasicFlag_Integer,  STR_LIT("rune")}},
+};
+
+
+b32 is_type_named(Type *t) {
+	if (t->kind == Type_Basic)
+		return true;
+	return t->kind == Type_Named;
+}
+b32 is_type_boolean(Type *t) {
+	if (t->kind == Type_Basic)
+		return (t->basic.flags & BasicFlag_Boolean) != 0;
+	return false;
+}
+b32 is_type_integer(Type *t) {
+	if (t->kind == Type_Basic)
+		return (t->basic.flags & BasicFlag_Integer) != 0;
+	return false;
+}
+b32 is_type_unsigned(Type *t) {
+	if (t->kind == Type_Basic)
+		return (t->basic.flags & BasicFlag_Unsigned) != 0;
+	return false;
+}
+b32 is_type_numeric(Type *t) {
+	if (t->kind == Type_Basic)
+		return (t->basic.flags & BasicFlag_Numeric) != 0;
+	return false;
+}
+b32 is_type_string(Type *t) {
+	if (t->kind == Type_Basic)
+		return (t->basic.flags & BasicFlag_String) != 0;
+	return false;
+}
+b32 is_type_typed(Type *t) {
+	if (t->kind == Type_Basic)
+		return (t->basic.flags & BasicFlag_Untyped) == 0;
+	return true;
+}
+b32 is_type_untyped(Type *t) {
+	if (t->kind == Type_Basic)
+		return (t->basic.flags & BasicFlag_Untyped) != 0;
+	return false;
+}
+b32 is_type_ordered(Type *t) {
+	if (t->kind == Type_Basic)
+		return (t->basic.flags & BasicFlag_Ordered) != 0;
+	return false;
+}
+b32 is_type_constant_type(Type *t) {
+	if (t->kind == Type_Basic)
+		return (t->basic.flags & BasicFlag_ConstantType) != 0;
+	return false;
+}
+b32 is_type_float(Type *t) {
+	if (t->kind == Type_Basic)
+		return (t->basic.flags & BasicFlag_Float) != 0;
+	return false;
+}
+b32 is_type_pointer(Type *t) {
+	if (t->kind == Type_Basic)
+		return (t->basic.flags & BasicFlag_Pointer) != 0;
+	return t->kind == Type_Pointer;
+}
+
+b32 is_type_int_or_uint(Type *t) {
+	if (t->kind == Type_Basic)
+		return (t->basic.kind == Basic_int) || (t->basic.kind == Basic_uint);
+	return false;
+}
+
+b32 is_type_rawptr(Type *t) {
+	if (t->kind == Type_Basic)
+		return t->basic.kind == Basic_rawptr;
+	return false;
+}
+
+b32 is_type_comparable(Type *t) {
+	t = get_base_type(t);
+	switch (t->kind) {
+	case Type_Basic:
+		return true;
+	case Type_Pointer:
+		return true;
+	case Type_Structure: {
+		for (isize i = 0; i < t->structure.field_count; i++) {
+			if (!is_type_comparable(t->structure.fields[i]->type))
+				return false;
+		}
+		return true;
+	} break;
+	case Type_Array:
+		return is_type_comparable(t->array.element);
+	}
+	return false;
+}
+
+b32 are_types_identical(Type *x, Type *y) {
+	if (x == y)
+		return true;
+
+	switch (x->kind) {
+	case Type_Basic:
+		if (y->kind == Type_Basic)
+			return x->basic.kind == y->basic.kind;
+		break;
+
+	case Type_Array:
+		if (y->kind == Type_Array)
+			return (x->array.count == y->array.count) && are_types_identical(x->array.element, y->array.element);
+		break;
+
+	case Type_Structure:
+		if (y->kind == Type_Structure) {
+			if (x->structure.field_count == y->structure.field_count) {
+				for (isize i = 0; i < x->structure.field_count; i++) {
+					if (!are_types_identical(x->structure.fields[i]->type, y->structure.fields[i]->type)) {
+						return false;
+					}
+				}
+
+				return true;
+			}
+		}
+		break;
+
+	case Type_Pointer:
+		if (y->kind == Type_Pointer)
+			return are_types_identical(x->pointer.element, y->pointer.element);
+		break;
+
+	case Type_Named:
+		if (y->kind == Type_Named)
+			return x->named.base == y->named.base;
+		break;
+
+
+	case Type_Tuple:
+		if (y->kind == Type_Tuple) {
+			if (x->tuple.variable_count == y->tuple.variable_count) {
+				for (isize i = 0; i < x->tuple.variable_count; i++) {
+					if (!are_types_identical(x->tuple.variables[i]->type, y->tuple.variables[i]->type))
+						return false;
+				}
+				return true;
+			}
+		}
+		break;
+
+	case Type_Procedure:
+		if (y->kind == Type_Procedure) {
+			return are_types_identical(x->procedure.params, y->procedure.params) &&
+			       are_types_identical(x->procedure.results, y->procedure.results);
+		}
+		break;
+	}
+
+
+	return false;
+}
+
+
+Type *default_type(Type *type) {
+	if (type->kind == Type_Basic) {
+		switch (type->basic.kind) {
+		case Basic_UntypedBool:    return &basic_types[Basic_bool];
+		case Basic_UntypedInteger: return &basic_types[Basic_int];
+		case Basic_UntypedFloat:   return &basic_types[Basic_f64];
+		case Basic_UntypedString:  return &basic_types[Basic_string];
+		case Basic_UntypedRune:    return &basic_types[Basic_rune];
+		case Basic_UntypedPointer: return &basic_types[Basic_rawptr];
+		}
+	}
+	return type;
+}
+
+
+struct BaseTypeSizes {
+	i64 word_size;
+	i64 max_align;
+};
+
+// TODO(bill): Change
+gb_global i64 basic_type_sizes[] = {
+	0, // Basic_Invalid
+	1, // Basic_bool // TODO(bill): What size should this be? And should I have different booleans?
+	1, // Basic_i8
+	2, // Basic_i16
+	4, // Basic_i32
+	8, // Basic_i64
+	1, // Basic_u8
+	2, // Basic_u16
+	4, // Basic_u32
+	8, // Basic_u64
+	4, // Basic_f32
+	8, // Basic_f64
+};
+
+
+i64 type_size_of(BaseTypeSizes s, gbAllocator allocator, Type *t);
+i64 type_align_of(BaseTypeSizes s, gbAllocator allocator, Type *t);
+
+i64 align_formula(i64 size, i64 align) {
+	i64 result = size + align-1;
+	return result - result%align;
+}
+
+i64 type_align_of(BaseTypeSizes s, gbAllocator allocator, Type *t) {
+	t = get_base_type(t);
+
+	switch (t->kind) {
+	case Type_Array:
+		return type_align_of(s, allocator, t->array.element);
+	case Type_Structure: {
+		i64 max = 1;
+		for (isize i = 0; i < t->structure.field_count; i++) {
+			i64 align = type_align_of(s, allocator, t->structure.fields[i]->type);
+			if (max < align)
+				max = align;
+		}
+		return max;
+	} break;
+	}
+
+	return gb_clamp(type_size_of(s, allocator, t), 1, s.max_align);
+}
+
+i64 *type_set_offsets_of(BaseTypeSizes s, gbAllocator allocator, Entity **fields, isize field_count) {
+	// TODO(bill): use arena allocation
+	i64 *offsets = gb_alloc_array(allocator, i64, field_count);
+	i64 curr_offset = 0;
+	for (isize i = 0; i < field_count; i++) {
+		i64 align = type_align_of(s, allocator, fields[i]->type);
+		curr_offset = align_formula(curr_offset, align);
+		offsets[i] = curr_offset;
+		curr_offset += type_size_of(s, allocator, fields[i]->type);
+	}
+
+	return offsets;
+}
+
+b32 type_set_offsets(BaseTypeSizes s, gbAllocator allocator, Type *t) {
+	GB_ASSERT(t->kind == Type_Structure);
+	if (!t->structure.offsets_set) {
+		t->structure.offsets = type_set_offsets_of(s, allocator, t->structure.fields, t->structure.field_count);
+		t->structure.offsets_set = true;
+		return true;
+	}
+	return false;
+}
+
+i64 type_size_of(BaseTypeSizes s, gbAllocator allocator, Type *t) {
+	t = get_base_type(t);
+	switch (t->kind) {
+	case Type_Basic: {
+		GB_ASSERT(is_type_typed(t));
+		BasicKind kind = t->basic.kind;
+		if (kind < gb_count_of(basic_type_sizes)) {
+			i64 size = basic_type_sizes[kind];
+			if (size > 0)
+				return size;
+		}
+		if (kind == Basic_string)
+			return 2 * s.word_size;
+	} break;
+
+	case Type_Array: {
+		i64 count = t->array.count;
+		if (count == 0)
+			return 0;
+		i64 align = type_align_of(s, allocator, t->array.element);
+		i64 size  = type_size_of(s,  allocator, t->array.element);
+		i64 alignment = align_formula(size, align);
+		return alignment*(count-1) + size;
+	} break;
+
+	case Type_Structure: {
+		i64 count = t->structure.field_count;
+		if (count == 0)
+			return 0;
+		type_set_offsets(s, allocator, t);
+		return t->structure.offsets[count-1] + type_size_of(s, allocator, t->structure.fields[count-1]->type);
+	} break;
+	}
+
+	// Catch all
+	return s.word_size;
+}
+
+i64 type_offset_of(BaseTypeSizes s, gbAllocator allocator, Type *t, isize index) {
+	GB_ASSERT(t->kind == Type_Structure);
+	type_set_offsets(s, allocator, t);
+	if (gb_is_between(index, 0, t->structure.field_count-1)) {
+		return t->structure.offsets[index];
+	}
+	return 0;
+}
+
+
+gbString write_type_to_string(gbString str, Type *type) {
+	if (type == NULL) {
+		return gb_string_appendc(str, "<no type>");
+	}
+
+	switch (type->kind) {
+	case Type_Basic:
+		str = gb_string_append_length(str, type->basic.name.text, type->basic.name.len);
+		break;
+	case Type_Array:
+		if (type->array.count >= 0) {
+			str = gb_string_appendc(str, gb_bprintf("[%td]", type->array.count));
+		} else {
+			str = gb_string_appendc(str, "[]");
+		}
+		str = write_type_to_string(str, type->array.element);
+		break;
+	case Type_Structure: {
+		str = gb_string_appendc(str, "struct{");
+		for (isize i = 0; i < type->structure.field_count; i++) {
+			Entity *f = type->structure.fields[i];
+			GB_ASSERT(f->kind == Entity_Variable);
+			if (i < type->structure.field_count-1)
+				str = gb_string_appendc(str, "; ");
+			str = gb_string_append_length(str, f->token.string.text, f->token.string.len);
+			str = gb_string_appendc(str, ": ");
+			str = write_type_to_string(str, f->type);
+		}
+		str = gb_string_appendc(str, "}");
+	} break;
+
+	case Type_Pointer:
+		str = gb_string_appendc(str, "^");
+		str = write_type_to_string(str, type->pointer.element);
+		break;
+
+	case Type_Named:
+		if (type->named.type_name != NULL) {
+			str = gb_string_append_length(str, type->named.name.text, type->named.name.len);
+		} else {
+			// NOTE(bill): Just in case
+			str = gb_string_appendc(str, "<Type_Named>");
+		}
+		break;
+
+	case Type_Tuple:
+		if (type->tuple.variable_count > 0) {
+			for (isize i = 0; i < type->tuple.variable_count; i++) {
+				Entity *var = type->tuple.variables[i];
+				GB_ASSERT(var->kind == Entity_Variable);
+				if (i > 0)
+					str = gb_string_appendc(str, ", ");
+				if (var->token.string.len > 0) {
+					str = gb_string_append_length(str, var->token.string.text, var->token.string.len);
+					str = gb_string_appendc(str, ": ");
+				}
+				str = write_type_to_string(str, var->type);
+			}
+		}
+		break;
+
+	case Type_Procedure:
+		str = gb_string_appendc(str, "proc(");
+		if (type->procedure.params)
+			str = write_type_to_string(str, type->procedure.params);
+		str = gb_string_appendc(str, ")");
+		if (type->procedure.results) {
+			str = gb_string_appendc(str, " -> ");
+			str = write_type_to_string(str, type->procedure.results);
+		}
+		break;
+	}
+	return str;
+}
+
+
+gbString type_to_string(Type *type, gbAllocator a = gb_heap_allocator()) {
+	gbString str = gb_string_make(a, "");
+	return write_type_to_string(str, type);
+}
+
+

+ 365 - 0
src/checker/value.cpp

@@ -0,0 +1,365 @@
+#include <math.h>
+
+// TODO(bill): Big numbers
+// IMPORTANT TODO(bill): This needs to be completely fixed!!!!!!!!
+
+enum ValueKind {
+	Value_Invalid,
+
+	Value_Bool,
+	Value_String,
+	Value_Integer,
+	Value_Float,
+	Value_Pointer, // TODO(bill): Value_Pointer
+
+	Value_Count,
+};
+
+struct Value {
+	ValueKind kind;
+	union {
+		b32    value_bool;
+		String value_string;
+		i64    value_integer;
+		f64    value_float;
+		void * value_pointer;
+	};
+};
+
+Value make_value_bool(b32 b) {
+	Value result = {Value_Bool};
+	result.value_bool = (b != 0);
+	return result;
+}
+
+Value make_value_string(String string) {
+	// TODO(bill): Allow for numbers with underscores in them
+	Value result = {Value_String};
+	result.value_string = string;
+	return result;
+}
+
+Value make_value_integer(String string) {
+	// TODO(bill): Allow for numbers with underscores in them
+	Value result = {Value_Integer};
+	i32 base = 10;
+	if (string.text[0] == '0') {
+		switch (string.text[1]) {
+		case 'b': base = 2;  break;
+		case 'o': base = 8;  break;
+		case 'd': base = 10; break;
+		case 'x': base = 16; break;
+		}
+	}
+
+	result.value_integer = gb_str_to_i64(cast(char *)string.text, NULL, base);
+
+	return result;
+}
+
+Value make_value_integer(i64 i) {
+	Value result = {Value_Integer};
+	result.value_integer = i;
+	return result;
+}
+
+Value make_value_float(String string) {
+	// TODO(bill): Allow for numbers with underscores in them
+	Value result = {Value_Float};
+	result.value_float = gb_str_to_f64(cast(char *)string.text, NULL);
+	return result;
+}
+
+Value make_value_float(f64 f) {
+	Value result = {Value_Float};
+	result.value_float = f;
+	return result;
+}
+
+Value make_value_pointer(void *ptr) {
+	Value result = {Value_Pointer};
+	result.value_pointer = ptr;
+	return result;
+}
+
+Value make_value_from_basic_literal(Token token) {
+	switch (token.kind) {
+	case Token_String:  return make_value_string(token.string);
+	case Token_Integer: return make_value_integer(token.string);
+	case Token_Float:   return make_value_float(token.string);
+	case Token_Rune:    return make_value_integer(token.string);
+	default:
+		GB_PANIC("Invalid token for basic literal");
+		break;
+	}
+
+	Value result = {Value_Invalid};
+	return result;
+}
+
+Value value_to_integer(Value v) {
+	switch (v.kind) {
+	case Value_Integer:
+		return v;
+	case Value_Float:
+		return make_value_integer(cast(i64)v.value_float);
+	}
+	Value r = {Value_Invalid};
+	return r;
+}
+
+Value value_to_float(Value v) {
+	switch (v.kind) {
+	case Value_Integer:
+		return make_value_float(cast(i64)v.value_integer);
+	case Value_Float:
+		return v;
+	}
+	Value r = {Value_Invalid};
+	return r;
+}
+
+
+Value unary_operator_value(Token op, Value v, i32 precision) {
+	switch (op.kind) {
+	case Token_Add:	{
+		switch (v.kind) {
+		case Value_Invalid:
+		case Value_Integer:
+		case Value_Float:
+			return v;
+		}
+	} break;
+
+	case Token_Sub:	{
+		switch (v.kind) {
+		case Value_Invalid:
+			return v;
+		case Value_Integer: {
+			Value i = v;
+			i.value_integer = -i.value_integer;
+			return i;
+		}
+		case Value_Float: {
+			Value i = v;
+			i.value_float = -i.value_float;
+			return i;
+		}
+		}
+	} break;
+
+	case Token_Xor: {
+		i64 i = 0;
+		switch (v.kind) {
+		case Value_Invalid:
+			return v;
+		case Value_Integer:
+			i = ~i;
+			break;
+		default:
+			goto failure;
+		}
+
+		// NOTE(bill): unsigned integers will be negative and will need to be
+		// limited to the types precision
+		if (precision > 0)
+			i &= ~((-1)<<precision);
+
+		return make_value_integer(i);
+	} break;
+
+	case Token_Not: {
+		switch (v.kind) {
+		case Value_Invalid: return v;
+		case Value_Bool:
+			return make_value_bool(!v.value_bool);
+		}
+	} break;
+	}
+
+failure:
+	GB_PANIC("Invalid unary operation, %s", token_kind_to_string(op.kind));
+
+	Value error_value = {};
+	return error_value;
+}
+
+// NOTE(bill): Make sure things are evaluated in correct order
+i32 value_order(Value v) {
+	switch (v.kind) {
+	case Value_Invalid:
+		return 0;
+	case Value_Bool:
+	case Value_String:
+		return 1;
+	case Value_Integer:
+		return 2;
+	case Value_Float:
+		return 3;
+	case Value_Pointer:
+		return 4;
+
+	default:
+		GB_PANIC("How'd you get here? Invalid Value.kind");
+		return -1;
+	}
+}
+
+void match_values(Value *x, Value *y) {
+	if (value_order(*y) < value_order(*x)) {
+		match_values(y, x);
+		return;
+	}
+
+	switch (x->kind) {
+	case Value_Invalid:
+		*y = *x;
+		return;
+
+	case Value_Bool:
+	case Value_String:
+		return;
+
+	case Value_Integer: {
+		switch (y->kind) {
+		case Value_Integer:
+			return;
+		case Value_Float:
+			// TODO(bill): Is this good enough?
+			*x = make_value_float(cast(f64)x->value_integer);
+			return;
+		}
+	} break;
+
+	case Value_Float: {
+		if (y->kind == Value_Float)
+			return;
+	} break;
+	}
+
+	GB_PANIC("How'd you get here? Invalid Value.kind");
+}
+
+Value binary_operator_value(Token op, Value x, Value y) {
+	match_values(&x, &y);
+
+	switch (x.kind) {
+	case Value_Invalid:
+		return x;
+
+	case Value_Bool:
+		switch (op.kind) {
+		case Token_CmpAnd: return make_value_bool(x.value_bool && y.value_bool);
+		case Token_CmpOr:  return make_value_bool(x.value_bool || y.value_bool);
+		default: goto error;
+		}
+		break;
+
+	case Value_Integer: {
+		i64 a = x.value_integer;
+		i64 b = y.value_integer;
+		i64 c = 0;
+		switch (op.kind) {
+		case Token_Add:    c = a + b;  break;
+		case Token_Sub:    c = a - b;  break;
+		case Token_Mul:    c = a * b;  break;
+		case Token_Quo:    return make_value_float(fmod(cast(f64)a, cast(f64)b));
+		case Token_QuoEq:  c = a / b;  break; // NOTE(bill): Integer division
+		case Token_Mod:    c = a % b;  break;
+		case Token_And:    c = a & b;  break;
+		case Token_Or:     c = a | b;  break;
+		case Token_Xor:    c = a ^ b;  break;
+		case Token_AndNot: c = a&(~b); break;
+		default: goto error;
+		}
+		return make_value_integer(c);
+	} break;
+
+	case Value_Float: {
+		f64 a = x.value_float;
+		f64 b = y.value_float;
+		switch (op.kind) {
+		case Token_Add: return make_value_float(a + b);
+		case Token_Sub: return make_value_float(a - b);
+		case Token_Mul: return make_value_float(a * b);
+		case Token_Quo: return make_value_float(a / b);
+		default: goto error;
+		}
+	} break;
+	}
+
+error:
+	Value error_value = {};
+	GB_PANIC("Invalid binary operation: %s", token_kind_to_string(op.kind));
+	return error_value;
+}
+
+gb_inline Value value_add(Value x, Value y) { Token op = {Token_Add}; return binary_operator_value(op, x, y); }
+gb_inline Value value_sub(Value x, Value y) { Token op = {Token_Sub}; return binary_operator_value(op, x, y); }
+gb_inline Value value_mul(Value x, Value y) { Token op = {Token_Mul}; return binary_operator_value(op, x, y); }
+gb_inline Value value_quo(Value x, Value y) { Token op = {Token_Quo}; return binary_operator_value(op, x, y); }
+
+
+i32 cmp_f64(f64 a, f64 b) {
+	return (a > b) - (a < b);
+}
+
+b32 compare_values(Token op, Value x, Value y) {
+	match_values(&x, &y);
+
+	switch (x.kind) {
+	case Value_Invalid:
+		return false;
+
+	case Value_Bool:
+		switch (op.kind) {
+		case Token_CmpEq: return x.value_bool == y.value_bool;
+		case Token_NotEq: return x.value_bool != y.value_bool;
+		}
+		break;
+
+	case Value_Integer: {
+		i64 a = x.value_integer;
+		i64 b = y.value_integer;
+		switch (op.kind) {
+		case Token_CmpEq: return a == b;
+		case Token_NotEq: return a != b;
+		case Token_Lt:    return a <  b;
+		case Token_LtEq:  return a <= b;
+		case Token_Gt:    return a >  b;
+		case Token_GtEq:  return a >= b;
+		}
+	} break;
+
+	case Value_Float: {
+		f64 a = x.value_float;
+		f64 b = y.value_float;
+		switch (op.kind) {
+		case Token_CmpEq: return cmp_f64(a, b) == 0;
+		case Token_NotEq: return cmp_f64(a, b) != 0;
+		case Token_Lt:    return cmp_f64(a, b) <  0;
+		case Token_LtEq:  return cmp_f64(a, b) <= 0;
+		case Token_Gt:    return cmp_f64(a, b) >  0;
+		case Token_GtEq:  return cmp_f64(a, b) >= 0;
+		}
+	} break;
+
+	case Value_String: {
+		String a = x.value_string;
+		String b = y.value_string;
+		isize len = gb_min(a.len, b.len);
+		// TODO(bill): gb_memcompare is used because the strings are UTF-8
+		switch (op.kind) {
+		case Token_CmpEq: return gb_memcompare(a.text, b.text, len) == 0;
+		case Token_NotEq: return gb_memcompare(a.text, b.text, len) != 0;
+		case Token_Lt:    return gb_memcompare(a.text, b.text, len) <  0;
+		case Token_LtEq:  return gb_memcompare(a.text, b.text, len) <= 0;
+		case Token_Gt:    return gb_memcompare(a.text, b.text, len) >  0;
+		case Token_GtEq:  return gb_memcompare(a.text, b.text, len) >= 0;
+		}
+	} break;
+	}
+
+	GB_PANIC("Invalid comparison");
+	return false;
+}

+ 221 - 0
src/common.cpp

@@ -0,0 +1,221 @@
+#define GB_NO_WINDOWS_H
+#define GB_IMPLEMENTATION
+#include "gb/gb.h"
+
+// NOTE(bill): Used for UTF-8 strings
+typedef struct String {
+	u8 *text;
+	isize len;
+} String;
+// NOTE(bill): used for printf style arguments
+#define LIT(x) (x).len, (x).text
+
+
+gb_inline String make_string(u8 *text, isize len) {
+	String s;
+	s.text = text;
+	if (len < 0)
+		len = gb_strlen(cast(char *)text);
+	s.len = len;
+	return s;
+}
+
+gb_inline String make_string(char *text) {
+	return make_string(cast(u8 *)cast(void *)text, gb_strlen(text));
+}
+
+gb_inline b32 are_strings_equal(String a, String b) {
+	if (a.len == b.len) {
+		return gb_memcompare(a.text, b.text, a.len) == 0;
+	}
+	return false;
+}
+
+// Hasing
+
+gb_inline u64 hashing_proc(void const *data, isize len) {
+	return gb_murmur64(data, len);
+}
+
+gb_inline u64 hash_string(String s) {
+	return hashing_proc(s.text, s.len);
+}
+
+gb_inline u64 hash_pointer(void *ptr) {
+	u64 p = cast(u64)cast(uintptr)ptr;
+	return p;
+}
+
+////////////////////////////////////////////////////////////////
+//
+// Generic Data Structures
+//
+////////////////////////////////////////////////////////////////
+
+
+typedef struct MapFindResult {
+	isize hash_index;
+	isize entry_prev;
+	isize entry_index;
+} MapFindResult;
+
+template <typename T>
+struct MapEntry {
+	u64 key;
+	isize next;
+	T value;
+};
+
+template <typename T>
+struct Map {
+	gbArray(isize) hashes;
+	gbArray(MapEntry<T>) entries;
+};
+
+template <typename T> void map_init   (Map<T> *h, gbAllocator a);
+template <typename T> void map_destroy(Map<T> *h);
+template <typename T> T *  map_get    (Map<T> *h, u64 key);
+template <typename T> void map_set    (Map<T> *h, u64 key, T value);
+template <typename T> void map_remove (Map<T> *h, u64 key);
+template <typename T> void map_clear  (Map<T> *h);
+template <typename T> void map_grow   (Map<T> *h);
+template <typename T> void map_rehash (Map<T> *h, isize new_count);
+
+
+
+template <typename T>
+gb_inline void map_init(Map<T> *h, gbAllocator a) {
+	gb_array_init(h->hashes,  a);
+	gb_array_init(h->entries, a);
+}
+
+template <typename T>
+gb_inline void map_destroy(Map<T> *h) {
+	if (h->entries) gb_array_free(h->entries);
+	if (h->hashes)  gb_array_free(h->hashes);
+}
+
+template <typename T>
+gb_internal isize map__add_entry(Map<T> *h, u64 key) {
+	MapEntry<T> e = {0};
+	e.key = key;
+	e.next = -1;
+	gb_array_append(h->entries, e);
+	return gb_array_count(h->entries)-1;
+}
+
+template <typename T>
+gb_internal MapFindResult map__find(Map<T> *h, u64 key) {
+	MapFindResult fr = {-1, -1, -1};
+	if (gb_array_count(h->hashes) > 0) {
+		fr.hash_index  = key % gb_array_count(h->hashes);
+		fr.entry_index = h->hashes[fr.hash_index];
+		while (fr.entry_index >= 0) {
+			if (h->entries[fr.entry_index].key == key)
+				return fr;
+			fr.entry_prev = fr.entry_index;
+			fr.entry_index = h->entries[fr.entry_index].next;
+		}
+	}
+	return fr;
+}
+
+template <typename T>
+gb_internal b32 map__full(Map<T> *h) {
+	return 0.75f * gb_array_count(h->hashes) <= gb_array_count(h->entries);
+}
+
+template <typename T>
+gb_inline void map_grow(Map<T> *h) {
+	isize new_count = GB_ARRAY_GROW_FORMULA(gb_array_count(h->entries));
+	map_rehash(h, new_count);
+}
+
+template <typename T>
+void map_rehash(Map<T> *h, isize new_count) {
+	isize i, j;
+	Map<T> nh = {0};
+	map_init(&nh, gb_array_allocator(h->hashes));
+	gb_array_resize(nh.hashes, new_count);
+	gb_array_reserve(nh.entries, gb_array_count(h->entries));
+	for (i = 0; i < new_count; i++)
+		nh.hashes[i] = -1;
+	for (i = 0; i < gb_array_count(h->entries); i++) {
+		MapEntry<T> *e = &h->entries[i];
+		MapFindResult fr;
+		if (gb_array_count(nh.hashes) == 0)
+			map_grow(&nh);
+		fr = map__find(&nh, e->key);
+		j = map__add_entry(&nh, e->key);
+		if (fr.entry_prev < 0)
+			nh.hashes[fr.hash_index] = j;
+		else
+			nh.entries[fr.entry_prev].next = j;
+		nh.entries[j].next = fr.entry_index;
+		nh.entries[j].value = e->value;
+		if (map__full(&nh))
+			map_grow(&nh);
+	}
+	map_destroy(h);
+	*h = nh;
+}
+
+template <typename T>
+gb_inline T *map_get(Map<T> *h, u64 key) {
+	isize index = map__find(h, key).entry_index;
+	if (index >= 0)
+		return &h->entries[index].value;
+	return NULL;
+}
+
+template <typename T>
+void map_set(Map<T> *h, u64 key, T value) {
+	isize index;
+	MapFindResult fr;
+	if (gb_array_count(h->hashes) == 0)
+		map_grow(h);
+	fr = map__find(h, key);
+	if (fr.entry_index >= 0) {
+		index = fr.entry_index;
+	} else {
+		index = map__add_entry(h, key);
+		if (fr.entry_prev >= 0) {
+			h->entries[fr.entry_prev].next = index;
+		} else {
+			h->hashes[fr.hash_index] = index;
+		}
+	}
+	h->entries[index].value = value;
+
+	if (map__full(h))
+		map_grow(h);
+}
+
+template <typename T>
+void map_remove(Map<T> *h, u64 key) {
+	MapFindResult fr = map__find(h, key);
+	if (fr.entry_index >= 0) {
+		if (fr.entry_prev < 0) {
+			h->hashes[fr.hash_index] = h->entries[fr.entry_index].next;
+		} else {
+			h->entries[fr.entry_prev].next = h->entries[fr.entry_index].next;
+		}
+		if (fr.entry_index == gb_array_count(h->entries)-1) {
+			gb_array_pop(h->entries);
+			return;
+		}
+		h->entries[fr.entry_index] = h->entries[gb_array_count(h->entries)-1];
+		MapFindResult last = map__find(h, h->entries[fr.entry_index].key);
+		if (last.entry_prev >= 0) {
+			h->entries[last.entry_prev].next = fr.entry_index;
+		} else {
+			h->hashes[last.hash_index] = fr.entry_index;
+		}
+	}
+}
+
+template <typename T>
+gb_inline void map_clear(Map<T> *h) {
+	gb_array_clear(h->hashes);
+	gb_array_clear(h->entries);
+}

+ 10331 - 0
src/gb/gb.h

@@ -0,0 +1,10331 @@
+/* gb.h - v0.26b - Ginger Bill's C Helper Library - public domain
+                 - no warranty implied; use at your own risk
+
+	This is a single header file with a bunch of useful stuff
+	to replace the C/C++ standard library
+
+===========================================================================
+	YOU MUST
+
+		#define GB_IMPLEMENTATION
+
+	in EXACTLY _one_ C or C++ file that includes this header, BEFORE the
+	include like this:
+
+		#define GB_IMPLEMENTATION
+		#include "gb.h"
+
+	All other files should just #include "gb.h" without #define
+
+
+	If you want the platform layer, YOU MUST
+
+		#define GB_PLATFORM
+
+	BEFORE the include like this:
+
+		#define GB_PLATFORM
+		#include "gb.h"
+
+===========================================================================
+
+LICENSE
+	This software is dual-licensed to the public domain and under the following
+	license: you are granted a perpetual, irrevocable license to copy, modify,
+	publish, and distribute this file as you see fit.
+
+WARNING
+	- This library is _slightly_ experimental and features may not work as expected.
+	- This also means that many functions are not documented.
+
+CREDITS
+	Written by Ginger Bill
+
+TODOS
+	- Remove CRT dependency for people who want that
+		- But do I really?
+		- Or make it only depend on the really needed stuff?
+	- Older compiler support?
+		- How old do you wanna go?
+		- Only support C90+extension and C99 not pure C89.
+	- File handling
+		- All files to be UTF-8 (even on windows)
+	- Better Virtual Memory handling
+	- Generic Heap Allocator (tcmalloc/dlmalloc/?)
+	- Fixed Heap Allocator
+	- Better UTF support and conversion
+	- Free List, best fit rather than first fit
+	- More date & time functions
+
+VERSION HISTORY
+	0.26b - Minor fixes
+	0.26a - gbString Fix
+	0.26  - Default allocator flags and generic hash table
+	0.25a - Fix UTF-8 stuff
+	0.25  - OS X gbPlatform Support (missing some things)
+	0.24b - Compile on OSX (excluding platform part)
+	0.24a - Minor additions
+	0.24  - Enum convention change
+	0.23  - Optional Windows.h removal (because I'm crazy)
+	0.22a - Remove gbVideoMode from gb_platform_init_*
+	0.22  - gbAffinity - (Missing Linux version)
+	0.21  - Platform Layer Restructuring
+	0.20  - Improve file io
+	0.19  - Clipboard Text
+	0.18a - Controller vibration
+	0.18  - Raw keyboard and mouse input for WIN32
+	0.17d - Fixed printf bug for strings
+	0.17c - Compile as 32 bit
+	0.17b - Change formating style because why not?
+	0.17a - Dropped C90 Support (For numerous reasons)
+	0.17  - Instantiated Hash Table
+	0.16a - Minor code layout changes
+	0.16  - New file API and improved platform layer
+	0.15d - Linux Experimental Support (DON'T USE IT PLEASE)
+	0.15c - Linux Experimental Support (DON'T USE IT)
+	0.15b - C90 Support
+	0.15a - gb_atomic(32|64)_spin_(lock|unlock)
+	0.15  - Recursive "Mutex"; Key States; gbRandom
+	0.14  - Better File Handling and better printf (WIN32 Only)
+	0.13  - Highly experimental platform layer (WIN32 Only)
+	0.12b - Fix minor file bugs
+	0.12a - Compile as C++
+	0.12  - New File Handing System! No stdio or stdlib! (WIN32 Only)
+	0.11a - Add string precision and width (experimental)
+	0.11  - Started making stdio & stdlib optional (Not tested much)
+	0.10c - Fix gb_endian_swap32()
+	0.10b - Probable timing bug for gb_time_now()
+	0.10a - Work on multiple compilers
+	0.10  - Scratch Memory Allocator
+	0.09a - Faster Mutex and the Free List is slightly improved
+	0.09  - Basic Virtual Memory System and Dreadful Free List allocator
+	0.08a - Fix *_appendv bug
+	0.08  - Huge Overhaul!
+	0.07a - Fix alignment in gb_heap_allocator_proc
+	0.07  - Hash Table and Hashing Functions
+	0.06c - Better Documentation
+	0.06b - OS X Support
+	0.06a - Linux Support
+	0.06  - Windows GCC Support and MSVC x86 Support
+	0.05b - Formatting
+	0.05a - Minor function name changes
+	0.05  - Radix Sort for unsigned integers (TODO: Other primitives)
+	0.04  - Better UTF support and search/sort procs
+	0.03  - Completely change procedure naming convention
+	0.02a - Bug fixes
+	0.02  - Change naming convention and gbArray(Type)
+	0.01  - Initial Version
+*/
+
+
+#ifndef GB_INCLUDE_GB_H
+#define GB_INCLUDE_GB_H
+
+#if defined(__cplusplus)
+extern "C" {
+#endif
+
+#if defined(__cplusplus)
+	#define GB_EXTERN extern "C"
+#else
+	#define GB_EXTERN extern
+#endif
+
+#if defined(_WIN32)
+	#define GB_DLL_EXPORT GB_EXTERN __declspec(dllexport)
+	#define GB_DLL_IMPORT GB_EXTERN __declspec(dllimport)
+#else
+	#define GB_DLL_EXPORT GB_EXTERN __attribute__((visibility("default")))
+	#define GB_DLL_IMPORT GB_EXTERN
+#endif
+
+// NOTE(bill): Redefine for DLL, etc.
+#ifndef GB_DEF
+	#ifdef GB_STATIC
+		#define GB_DEF static
+	#else
+		#define GB_DEF extern
+	#endif
+#endif
+
+#if defined(_WIN64) || defined(__x86_64__) || defined(_M_X64) || defined(__64BIT__) || defined(__powerpc64__) || defined(__ppc64__)
+	#ifndef GB_ARCH_64_BIT
+	#define GB_ARCH_64_BIT 1
+	#endif
+#else
+	// NOTE(bill): I'm only supporting 32 bit and 64 bit systems
+	#ifndef GB_ARCH_32_BIT
+	#define GB_ARCH_32_BIT 1
+	#endif
+#endif
+
+
+#ifndef GB_EDIAN_ORDER
+#define GB_EDIAN_ORDER
+	// TODO(bill): Is the a good way or is it better to test for certain compilers and macros?
+	#define GB_IS_BIG_EDIAN    (!*(u8*)&(u16){1})
+	#define GB_IS_LITTLE_EDIAN (!GB_IS_BIG_EDIAN)
+#endif
+
+#if defined(_WIN32) || defined(_WIN64)
+	#ifndef GB_SYSTEM_WINDOWS
+	#define GB_SYSTEM_WINDOWS 1
+	#endif
+#elif defined(__APPLE__) && defined(__MACH__)
+	#ifndef GB_SYSTEM_OSX
+	#define GB_SYSTEM_OSX 1
+	#endif
+#elif defined(__unix__)
+	#ifndef GB_SYSTEM_UNIX
+	#define GB_SYSTEM_UNIX 1
+	#endif
+
+	#if defined(__linux__)
+		#ifndef GB_SYSTEM_LINUX
+		#define GB_SYSTEM_LINUX 1
+		#endif
+	#elif defined(__FreeBSD__) || defined(__FreeBSD_kernel__)
+		#ifndef GB_SYSTEM_FREEBSD
+		#define GB_SYSTEM_FREEBSD 1
+		#endif
+	#else
+		#error This UNIX operating system is not supported
+	#endif
+#else
+	#error This operating system is not supported
+#endif
+
+#if defined(_MSC_VER)
+	#define GB_COMPILER_MSVC 1
+#elif defined(__GNUC__)
+	#define GB_COMPILER_GCC 1
+#elif defined(__clang__)
+	#define GB_COMPILER_CLANG 1
+#else
+	#error Unknown compiler
+#endif
+
+#if defined(_M_IX86) || defined(_M_X64) || defined(__i386__) || defined(__x86_64__)
+	#ifndef GB_CPU_X86
+	#define GB_CPU_X86 1
+	#endif
+	#ifndef GB_CACHE_LINE_SIZE
+	#define GB_CACHE_LINE_SIZE 64
+	#endif
+
+#elif defined(_M_PPC) || defined(__powerpc__) || defined(__powerpc64__)
+	#ifndef GB_CPU_PPC
+	#define GB_CPU_PPC 1
+	#endif
+	#ifndef GB_CACHE_LINE_SIZE
+	#define GB_CACHE_LINE_SIZE 128
+	#endif
+
+#elif defined(__arm__)
+	#ifndef GB_CPU_ARM
+	#define GB_CPU_ARM 1
+	#endif
+	#ifndef GB_CACHE_LINE_SIZE
+	#define GB_CACHE_LINE_SIZE 64
+	#endif
+
+#elif defined(__MIPSEL__) || defined(__mips_isa_rev)
+	#ifndef GB_CPU_MIPS
+	#define GB_CPU_MIPS 1
+	#endif
+	#ifndef GB_CACHE_LINE_SIZE
+	#define GB_CACHE_LINE_SIZE 64
+	#endif
+
+#else
+	#error Unknown CPU Type
+#endif
+
+
+
+#ifndef GB_STATIC_ASSERT
+	#define GB_STATIC_ASSERT3(cond, msg) typedef char static_assertion_##msg[(!!(cond))*2-1]
+	// NOTE(bill): Token pasting madness!!
+	#define GB_STATIC_ASSERT2(cond, line) GB_STATIC_ASSERT3(cond, static_assertion_at_line_##line)
+	#define GB_STATIC_ASSERT1(cond, line) GB_STATIC_ASSERT2(cond, line)
+	#define GB_STATIC_ASSERT(cond)        GB_STATIC_ASSERT1(cond, __LINE__)
+#endif
+
+
+////////////////////////////////////////////////////////////////
+//
+// Headers
+//
+//
+
+#if defined(_WIN32) && !defined(__MINGW32__)
+	#ifndef _CRT_SECURE_NO_WARNINGS
+	#define _CRT_SECURE_NO_WARNINGS
+	#endif
+#endif
+
+#if defined(GB_SYSTEM_UNIX)
+	#define _GNU_SOURCE
+	#define _LARGEFILE64_SOURCE
+#endif
+
+
+// TODO(bill): How many of these headers do I really need?
+#include <stdarg.h>
+#include <stddef.h>
+
+#if defined(GB_SYSTEM_WINDOWS)
+	#if !defined(GB_NO_WINDOWS_H)
+		#define NOMINMAX            1
+		#define WIN32_LEAN_AND_MEAN 1
+		#define WIN32_MEAN_AND_LEAN 1
+		#define VC_EXTRALEAN        1
+		#include <windows.h>
+		#undef NOMINMAX
+		#undef WIN32_LEAN_AND_MEAN
+		#undef WIN32_MEAN_AND_LEAN
+		#undef VC_EXTRALEAN
+	#endif
+
+	#include <malloc.h> // NOTE(bill): _aligned_*()
+	#include <intrin.h>
+#else
+	#include <dlfcn.h>
+	#include <errno.h>
+	#include <fcntl.h>
+	#include <pthread.h>
+	#include <stdlib.h> // NOTE(bill): malloc on linux
+	#include <sys/mman.h>
+	#if !defined(GB_SYSTEM_OSX)
+		#include <sys/sendfile.h>
+	#endif
+	#include <sys/stat.h>
+	#include <sys/time.h>
+	#include <sys/types.h>
+	#include <time.h>
+	#include <unistd.h>
+#endif
+
+#if defined(GB_SYSTEM_OSX)
+#include <mach/mach.h>
+#include <mach/mach_init.h>
+#include <mach/mach_time.h>
+#include <mach/thread_act.h>
+#include <mach/thread_policy.h>
+#include <sys/sysctl.h>
+#include <copyfile.h>
+#include <mach/clock.h>
+#endif
+
+#if defined(GB_SYSTEM_UNIX)
+#include <semaphore.h>
+#endif
+
+
+////////////////////////////////////////////////////////////////
+//
+// Base Types
+//
+//
+
+#if defined(GB_COMPILER_MSVC)
+	#if _MSC_VER < 1300
+	typedef unsigned char     u8;
+	typedef   signed char     i8;
+	typedef unsigned short   u16;
+	typedef   signed short   i16;
+	typedef unsigned int     u32;
+	typedef   signed int     i32;
+	#else
+	typedef unsigned __int8   u8;
+	typedef   signed __int8   i8;
+	typedef unsigned __int16 u16;
+	typedef   signed __int16 i16;
+	typedef unsigned __int32 u32;
+	typedef   signed __int32 i32;
+	#endif
+	typedef unsigned __int64 u64;
+	typedef   signed __int64 i64;
+#else
+	#include <stdint.h>
+	typedef uint8_t   u8;
+	typedef  int8_t   i8;
+	typedef uint16_t u16;
+	typedef  int16_t i16;
+	typedef uint32_t u32;
+	typedef  int32_t i32;
+	typedef uint64_t u64;
+	typedef  int64_t i64;
+#endif
+
+GB_STATIC_ASSERT(sizeof(u8)  == sizeof(i8));
+GB_STATIC_ASSERT(sizeof(u16) == sizeof(i16));
+GB_STATIC_ASSERT(sizeof(u32) == sizeof(i32));
+GB_STATIC_ASSERT(sizeof(u64) == sizeof(i64));
+
+GB_STATIC_ASSERT(sizeof(u8)  == 1);
+GB_STATIC_ASSERT(sizeof(u16) == 2);
+GB_STATIC_ASSERT(sizeof(u32) == 4);
+GB_STATIC_ASSERT(sizeof(u64) == 8);
+
+typedef size_t    usize;
+typedef ptrdiff_t isize;
+
+GB_STATIC_ASSERT(sizeof(usize) == sizeof(isize));
+
+// NOTE(bill): (u)intptr is only here for semantic reasons really as this library will only support 32/64 bit OSes.
+// NOTE(bill): Are there any modern OSes (not 16 bit) where intptr != isize ?
+#if defined(_WIN64)
+	typedef signed   __int64  intptr;
+	typedef unsigned __int64 uintptr;
+#elif defined(_WIN32)
+	// NOTE(bill); To mark types changing their size, e.g. intptr
+	#ifndef _W64
+		#if !defined(__midl) && (defined(_X86_) || defined(_M_IX86)) && _MSC_VER >= 1300
+			#define _W64 __w64
+		#else
+			#define _W64
+		#endif
+	#endif
+
+	typedef _W64   signed int  intptr;
+	typedef _W64 unsigned int uintptr;
+#else
+	typedef uintptr_t uintptr;
+	typedef  intptr_t  intptr;
+#endif
+
+GB_STATIC_ASSERT(sizeof(uintptr) == sizeof(intptr));
+
+typedef float  f32;
+typedef double f64;
+
+GB_STATIC_ASSERT(sizeof(f32) == 4);
+GB_STATIC_ASSERT(sizeof(f64) == 8);
+
+typedef i32 Rune; // NOTE(bill): Unicode codepoint
+#define GB_RUNE_INVALID cast(Rune)(0xfffd)
+#define GB_RUNE_MAX     cast(Rune)(0x0010ffff)
+#define GB_RUNE_BOM     cast(Rune)(0xfeff)
+#define GB_RUNE_EOF     cast(Rune)(-1)
+
+
+// NOTE(bill): I think C99 and C++ `bool` is stupid for numerous reasons but there are too many
+// to write in this small comment.
+typedef i8  b8;
+typedef i16 b16;
+typedef i32 b32; // NOTE(bill): Prefer this!!!
+
+// NOTE(bill): Get true and false
+#if !defined(__cplusplus)
+	#if (defined(_MSC_VER) && _MSC_VER <= 1800) || !defined(__STDC_VERSION__)
+		#ifndef true
+		#define true  (0 == 0)
+		#endif
+		#ifndef false
+		#define false (0 != 0)
+		#endif
+	#else
+		#include <stdbool.h>
+	#endif
+#endif
+
+// NOTE(bill): These do are not prefixed with gb because the types are not.
+#ifndef U8_MIN
+#define U8_MIN 0u
+#define U8_MAX 0xffu
+#define I8_MIN (-0x7f - 1)
+#define I8_MAX 0x7f
+
+#define U16_MIN 0u
+#define U16_MAX 0xffffu
+#define I16_MIN (-0x7fff - 1)
+#define I16_MAX 0x7fff
+
+#define U32_MIN 0u
+#define U32_MAX 0xffffffffu
+#define I32_MIN (-0x7fffffff - 1)
+#define I32_MAX 0x7fffffff
+
+#define U64_MIN 0ull
+#define U64_MAX 0xffffffffffffffffull
+#define I64_MIN (-0x7fffffffffffffffll - 1)
+#define I64_MAX 0x7fffffffffffffffll
+
+#if defined(GB_ARCH_32_BIT)
+	#define USIZE_MIX U32_MIN
+	#define USIZE_MAX U32_MAX
+
+	#define ISIZE_MIX S32_MIN
+	#define ISIZE_MAX S32_MAX
+#elif defined(GB_ARCH_64_BIT)
+	#define USIZE_MIX U64_MIN
+	#define USIZE_MAX U64_MAX
+
+	#define ISIZE_MIX I64_MIN
+	#define ISIZE_MAX I64_MAX
+#else
+	#error Unknown architecture size. This library only supports 32 bit and 64 bit architectures.
+#endif
+
+#define F32_MIN 1.17549435e-38f
+#define F32_MAX 3.40282347e+38f
+
+#define F64_MIN 2.2250738585072014e-308
+#define F64_MAX 1.7976931348623157e+308
+
+#endif
+
+#ifndef NULL
+	#if defined(__cplusplus)
+		#if __cplusplus >= 201103L
+			#define NULL nullptr
+		#else
+			#define NULL 0
+		#endif
+	#else
+		#define NULL ((void *)0)
+	#endif
+#endif
+
+// TODO(bill): Is this enough to get inline working?
+#if !defined(__cplusplus)
+	#if defined(_MSC_VER) && _MSC_VER <= 1800
+	#define inline __inline
+	#elif !defined(__STDC_VERSION__)
+	#define inline __inline__
+	#else
+	#define inline
+	#endif
+#endif
+
+#if !defined(gb_restrict)
+	#if defined(_MSC_VER)
+		#define gb_restrict __restrict
+	#elif defined(__STDC_VERSION__)
+		#define gb_restrict restrict
+	#else
+		#define gb_restrict
+	#endif
+#endif
+
+// TODO(bill): Should force inline be a separate keyword and gb_inline be inline?
+#if !defined(gb_inline)
+	#if defined(_MSC_VER)
+		#if _MSC_VER < 1300
+		#define gb_inline
+		#else
+		#define gb_inline __forceinline
+		#endif
+	#else
+		#define gb_inline __attribute__ ((__always_inline__))
+	#endif
+#endif
+
+#if !defined(gb_no_inline)
+	#if defined(_MSC_VER)
+		#define gb_no_inline __declspec(noinline)
+	#else
+		#define gb_no_inline __attribute__ ((noinline))
+	#endif
+#endif
+
+
+#if !defined(gb_thread_local)
+	#if defined(_MSC_VER) && _MSC_VER >= 1300
+		#define gb_thread_local __declspec(thread)
+	#elif defined(__GNUC__)
+		#define gb_thread_local __thread
+	#else
+		#define gb_thread_local thread_local
+	#endif
+#endif
+
+
+// NOTE(bill): Easy to grep
+// NOTE(bill): Not needed in macros
+#ifndef cast
+#define cast(Type) (Type)
+#endif
+
+// NOTE(bill): Because a signed sizeof is more useful
+#ifndef gb_size_of
+#define gb_size_of(x) (isize)(sizeof(x))
+#endif
+
+#ifndef gb_count_of
+#define gb_count_of(x) ((gb_size_of(x)/gb_size_of(0[x])) / ((isize)(!(gb_size_of(x) % gb_size_of(0[x])))))
+#endif
+
+#ifndef gb_offset_of
+#define gb_offset_of(Type, element) ((isize)&(((Type *)0)->element))
+#endif
+
+#if defined(__cplusplus)
+#ifndef gb_align_of
+	#if __cplusplus >= 201103L
+		#define gb_align_of(Type) (isize)alignof(Type)
+	#else
+extern "C++" {
+		// NOTE(bill): Fucking Templates!
+		template <typename T> struct gbAlignment_Trick { char c; T member; };
+		#define gb_align_of(Type) gb_offset_of(gbAlignment_Trick<Type>, member)
+}
+	#endif
+#endif
+#else
+	#ifndef gb_align_of
+	#define gb_align_of(Type) gb_offset_of(struct { char c; Type member; }, member)
+	#endif
+#endif
+
+// NOTE(bill): I do wish I had a type_of that was portable
+#ifndef gb_swap
+#define gb_swap(Type, a, b) do { Type tmp = (a); (a) = (b); (b) = tmp; } while (0)
+#endif
+
+// NOTE(bill): Because static means 3/4 different things in C/C++. Great design (!)
+#ifndef gb_global
+#define gb_global        static // Global variables
+#define gb_internal      static // Internal linkage
+#define gb_local_persist static // Local Persisting variables
+#endif
+
+
+#ifndef gb_unused
+	#if defined(_MSC_VER)
+		#define gb_unused(x) (__pragma(warning(suppress:4100))(x))
+	#elif defined (__GCC__)
+		#define gb_unused(x) __attribute__((__unused__))(x)
+	#else
+		#define gb_unused(x) ((void)(gb_size_of(x)))
+	#endif
+#endif
+
+
+
+
+////////////////////////////////////////////////////////////////
+//
+// Defer statement
+// Akin to D's SCOPE_EXIT or
+// similar to Go's defer but scope-based
+//
+// NOTE: C++11 (and above) only!
+//
+#if defined(__cplusplus) && ((defined(_MSC_VER) && _MSC_VER >= 1400) || (__cplusplus >= 201103L))
+extern "C++" {
+	// NOTE(bill): Stupid fucking templates
+	template <typename T> struct gbRemoveReference       { typedef T Type; };
+	template <typename T> struct gbRemoveReference<T &>  { typedef T Type; };
+	template <typename T> struct gbRemoveReference<T &&> { typedef T Type; };
+
+	/// NOTE(bill): "Move" semantics - invented because the C++ committee are idiots (as a collective not as indiviuals (well a least some aren't))
+	template <typename T> inline T &&gb_forward(typename gbRemoveReference<T>::Type &t)  { return static_cast<T &&>(t); }
+	template <typename T> inline T &&gb_forward(typename gbRemoveReference<T>::Type &&t) { return static_cast<T &&>(t); }
+	template <typename T> inline T &&gb_move   (T &&t)                                   { return static_cast<typename gbRemoveReference<T>::Type &&>(t); }
+	template <typename F>
+	struct gbprivDefer {
+		F f;
+		gbprivDefer(F &&f) : f(gb_forward<F>(f)) {}
+		~gbprivDefer() { f(); }
+	};
+	template <typename F> gbprivDefer<F> gb__defer_func(F &&f) { return gbprivDefer<F>(gb_forward<F>(f)); }
+
+	#define GB_DEFER_1(x, y) x##y
+	#define GB_DEFER_2(x, y) GB_DEFER_1(x, y)
+	#define GB_DEFER_3(x)    GB_DEFER_2(x, __COUNTER__)
+	#define defer(code)      auto GB_DEFER_3(_defer_) = gb__defer_func([&]()->void{code;})
+}
+
+// Example
+#if 0
+	gbMutex m;
+	gb_mutex_init(&m);
+	{
+		gb_mutex_lock(&m);
+		defer (gb_mutex_unlock(&m));
+
+		...
+	}
+#endif
+
+#endif
+
+
+////////////////////////////////////////////////////////////////
+//
+// Macro Fun!
+//
+//
+
+#ifndef GB_JOIN_MACROS
+#define GB_JOIN_MACROS
+	#define GB_JOIN2_IND(a, b) a##b
+
+	#define GB_JOIN2(a, b)       GB_JOIN2_IND(a, b)
+	#define GB_JOIN3(a, b, c)    GB_JOIN2(GB_JOIN2(a, b), c)
+	#define GB_JOIN4(a, b, c, d) GB_JOIN2(GB_JOIN2(GB_JOIN2(a, b), c), d)
+#endif
+
+
+#ifndef GB_BIT
+#define GB_BIT(x) (1<<(x))
+#endif
+
+#ifndef gb_min
+#define gb_min(a, b) ((a) < (b) ? (a) : (b))
+#endif
+
+#ifndef gb_max
+#define gb_max(a, b) ((a) > (b) ? (a) : (b))
+#endif
+
+#ifndef gb_min3
+#define gb_min3(a, b, c) gb_min(gb_min(a, b), c)
+#endif
+
+#ifndef gb_max3
+#define gb_max3(a, b, c) gb_max(gb_max(a, b), c)
+#endif
+
+#ifndef gb_clamp
+#define gb_clamp(x, lower, upper) gb_min(gb_max((x), (lower)), (upper))
+#endif
+
+#ifndef gb_clamp01
+#define gb_clamp01(x) gb_clamp((x), 0, 1)
+#endif
+
+#ifndef gb_is_between
+#define gb_is_between(x, lower, upper) (((x) >= (lower)) && ((x) <= (upper)))
+#endif
+
+/* NOTE(bill): Very useful bit setting */
+#ifndef GB_MASK_SET
+#define GB_MASK_SET(var, set, mask) do { \
+	if (set) (var) |=  (mask); \
+	else     (var) &= ~(mask); \
+} while (0)
+#endif
+
+
+// NOTE(bill): Some compilers support applying printf-style warnings to user functions.
+#if defined(__clang__) || defined(__GNUC__)
+#define GB_PRINTF_ARGS(FMT) __attribute__((format(printf, FMT, (FMT+1))))
+#else
+#define GB_PRINTF_ARGS(FMT)
+#endif
+
+////////////////////////////////////////////////////////////////
+//
+// Debug
+//
+//
+
+
+#ifndef GB_DEBUG_TRAP
+	#if defined(_MSC_VER)
+	 	#if _MSC_VER < 1300
+		#define GB_DEBUG_TRAP() __asm int 3 /* Trap to debugger! */
+		#else
+		#define GB_DEBUG_TRAP() __debugbreak()
+		#endif
+	#else
+		#define GB_DEBUG_TRAP() __builtin_trap()
+	#endif
+#endif
+
+#ifndef GB_ASSERT_MSG
+#define GB_ASSERT_MSG(cond, msg, ...) do { \
+	if (!(cond)) { \
+		gb_assert_handler(#cond, __FILE__, cast(i64)__LINE__, msg, ##__VA_ARGS__); \
+		GB_DEBUG_TRAP(); \
+	} \
+} while (0)
+#endif
+
+#ifndef GB_ASSERT
+#define GB_ASSERT(cond) GB_ASSERT_MSG(cond, NULL)
+#endif
+
+#ifndef GB_ASSERT_NOT_NULL
+#define GB_ASSERT_NOT_NULL(ptr) GB_ASSERT_MSG((ptr) != NULL, #ptr " must not be NULL")
+#endif
+
+// NOTE(bill): Things that shouldn't happen with a message!
+#ifndef GB_PANIC
+#define GB_PANIC(msg, ...) GB_ASSERT_MSG(0, msg, ##__VA_ARGS__)
+#endif
+
+GB_DEF void gb_assert_handler(char const *condition, char const *file, i32 line, char const *msg, ...);
+
+
+
+////////////////////////////////////////////////////////////////
+//
+// Memory
+//
+//
+
+
+GB_DEF b32 gb_is_power_of_two(isize x);
+
+GB_DEF void *      gb_align_forward(void *ptr, isize alignment);
+
+GB_DEF void *      gb_pointer_add      (void *ptr, isize bytes);
+GB_DEF void *      gb_pointer_sub      (void *ptr, isize bytes);
+GB_DEF void const *gb_pointer_add_const(void const *ptr, isize bytes);
+GB_DEF void const *gb_pointer_sub_const(void const *ptr, isize bytes);
+GB_DEF isize       gb_pointer_diff     (void const *begin, void const *end);
+
+
+GB_DEF void gb_zero_size(void *ptr, isize size);
+#ifndef     gb_zero_item
+#define     gb_zero_item(t)         gb_zero_size((t), gb_size_of(*(t))) // NOTE(bill): Pass pointer of struct
+#define     gb_zero_array(a, count) gb_zero_size((a), gb_size_of(*(a))*count)
+#endif
+
+GB_DEF void *      gb_memcopy   (void *dest, void const *source, isize size);
+GB_DEF void *      gb_memmove   (void *dest, void const *source, isize size);
+GB_DEF void *      gb_memset    (void *data, u8 byte_value, isize size);
+GB_DEF i32         gb_memcompare(void const *s1, void const *s2, isize size);
+GB_DEF void        gb_memswap   (void *i, void *j, isize size);
+GB_DEF void const *gb_memchr    (void const *data, u8 byte_value, isize size);
+GB_DEF void const *gb_memrchr   (void const *data, u8 byte_value, isize size);
+
+
+// NOTE(bill): Very similar to doing `*cast(T *)(&u)`
+#ifndef GB_BIT_CAST
+#define GB_BIT_CAST(dest, source) do { \
+	GB_STATIC_ASSERT(gb_size_of(*(dest)) <= gb_size_of(source)); \
+	gb_memcopy((dest), &(source), gb_size_of(*dest)); \
+} while (0)
+#endif
+
+
+
+
+#ifndef gb_kilobytes
+#define gb_kilobytes(x) (            (x) * (i64)(1024))
+#define gb_megabytes(x) (gb_kilobytes(x) * (i64)(1024))
+#define gb_gigabytes(x) (gb_megabytes(x) * (i64)(1024))
+#define gb_terabytes(x) (gb_gigabytes(x) * (i64)(1024))
+#endif
+
+
+
+
+// Atomics
+
+// TODO(bill): Be specific with memory order?
+// e.g. relaxed, acquire, release, acquire_release
+
+#if defined(GB_COMPILER_MSVC)
+typedef struct gbAtomic32  { i32   volatile value; } gbAtomic32;
+typedef struct gbAtomic64  { i64   volatile value; } gbAtomic64;
+typedef struct gbAtomicPtr { void *volatile value; } gbAtomicPtr;
+#else
+	#if defined(GB_ARCH_32_BIT)
+	#define GB_ATOMIC_PTR_ALIGNMENT 4
+	#elif defined(GB_ARCH_64_BIT)
+	#define GB_ATOMIC_PTR_ALIGNMENT 8
+	#else
+	#error Unknown architecture
+	#endif
+
+typedef struct gbAtomic32  { i32   volatile value; } __attribute__ ((aligned(4))) gbAtomic32;
+typedef struct gbAtomic64  { i64   volatile value; } __attribute__ ((aligned(8))) gbAtomic64;
+typedef struct gbAtomicPtr { void *volatile value; } __attribute__ ((aligned(GB_ATOMIC_PTR_ALIGNMENT))) gbAtomicPtr;
+#endif
+
+GB_DEF i32  gb_atomic32_load            (gbAtomic32 const volatile *a);
+GB_DEF void gb_atomic32_store           (gbAtomic32 volatile *a, i32 value);
+GB_DEF i32  gb_atomic32_compare_exchange(gbAtomic32 volatile *a, i32 expected, i32 desired);
+GB_DEF i32  gb_atomic32_exchanged       (gbAtomic32 volatile *a, i32 desired);
+GB_DEF i32  gb_atomic32_fetch_add       (gbAtomic32 volatile *a, i32 operand);
+GB_DEF i32  gb_atomic32_fetch_and       (gbAtomic32 volatile *a, i32 operand);
+GB_DEF i32  gb_atomic32_fetch_or        (gbAtomic32 volatile *a, i32 operand);
+GB_DEF b32  gb_atomic32_spin_lock       (gbAtomic32 volatile *a, isize time_out); // NOTE(bill): time_out = -1 as default
+GB_DEF void gb_atomic32_spin_unlock     (gbAtomic32 volatile *a);
+GB_DEF b32  gb_atomic32_try_acquire_lock(gbAtomic32 volatile *a);
+
+
+GB_DEF i64  gb_atomic64_load            (gbAtomic64 const volatile *a);
+GB_DEF void gb_atomic64_store           (gbAtomic64 volatile *a, i64 value);
+GB_DEF i64  gb_atomic64_compare_exchange(gbAtomic64 volatile *a, i64 expected, i64 desired);
+GB_DEF i64  gb_atomic64_exchanged       (gbAtomic64 volatile *a, i64 desired);
+GB_DEF i64  gb_atomic64_fetch_add       (gbAtomic64 volatile *a, i64 operand);
+GB_DEF i64  gb_atomic64_fetch_and       (gbAtomic64 volatile *a, i64 operand);
+GB_DEF i64  gb_atomic64_fetch_or        (gbAtomic64 volatile *a, i64 operand);
+GB_DEF b32  gb_atomic64_spin_lock       (gbAtomic64 volatile *a, isize time_out); // NOTE(bill): time_out = -1 as default
+GB_DEF void gb_atomic64_spin_unlock     (gbAtomic64 volatile *a);
+GB_DEF b32  gb_atomic64_try_acquire_lock(gbAtomic64 volatile *a);
+
+
+GB_DEF void *gb_atomic_ptr_load            (gbAtomicPtr const volatile *a);
+GB_DEF void  gb_atomic_ptr_store           (gbAtomicPtr volatile *a, void *value);
+GB_DEF void *gb_atomic_ptr_compare_exchange(gbAtomicPtr volatile *a, void *expected, void *desired);
+GB_DEF void *gb_atomic_ptr_exchanged       (gbAtomicPtr volatile *a, void *desired);
+GB_DEF void *gb_atomic_ptr_fetch_add       (gbAtomicPtr volatile *a, void *operand);
+GB_DEF void *gb_atomic_ptr_fetch_and       (gbAtomicPtr volatile *a, void *operand);
+GB_DEF void *gb_atomic_ptr_fetch_or        (gbAtomicPtr volatile *a, void *operand);
+GB_DEF b32   gb_atomic_ptr_spin_lock       (gbAtomicPtr volatile *a, isize time_out); // NOTE(bill): time_out = -1 as default
+GB_DEF void  gb_atomic_ptr_spin_unlock     (gbAtomicPtr volatile *a);
+GB_DEF b32   gb_atomic_ptr_try_acquire_lock(gbAtomicPtr volatile *a);
+
+
+// Fences
+GB_DEF void gb_yield_thread(void);
+GB_DEF void gb_mfence      (void);
+GB_DEF void gb_sfence      (void);
+GB_DEF void gb_lfence      (void);
+
+
+#if defined(GB_SYSTEM_WINDOWS)
+typedef struct gbSemaphore { void *win32_handle; }     gbSemaphore;
+#elif defined(GB_SYSTEM_OSX)
+typedef struct gbSemaphore { semaphore_t osx_handle; } gbSemaphore;
+#elif defined(GB_SYSTEM_UNIX)
+typedef struct gbSemaphore { sem_t unix_handle; }      gbSemaphore;
+#else
+#error
+#endif
+
+GB_DEF void gb_semaphore_init   (gbSemaphore *s);
+GB_DEF void gb_semaphore_destroy(gbSemaphore *s);
+GB_DEF void gb_semaphore_post   (gbSemaphore *s, i32 count);
+GB_DEF void gb_semaphore_release(gbSemaphore *s); // NOTE(bill): gb_semaphore_post(s, 1)
+GB_DEF void gb_semaphore_wait   (gbSemaphore *s);
+
+
+// Mutex
+// TODO(bill): Should this be replaced with a CRITICAL_SECTION on win32 or is the better?
+typedef struct gbMutex {
+	gbSemaphore semaphore;
+	gbAtomic32  counter;
+	gbAtomic32  owner;
+	i32         recursion;
+} gbMutex;
+
+GB_DEF void gb_mutex_init    (gbMutex *m);
+GB_DEF void gb_mutex_destroy (gbMutex *m);
+GB_DEF void gb_mutex_lock    (gbMutex *m);
+GB_DEF b32  gb_mutex_try_lock(gbMutex *m);
+GB_DEF void gb_mutex_unlock  (gbMutex *m);
+
+// NOTE(bill): If you wanted a Scoped Mutex in C++, why not use the defer() construct?
+// No need for a silly wrapper class and it's clear!
+#if 0
+gbMutex m = {0};
+gb_mutex_init(&m);
+{
+	gb_mutex_lock(&m);
+	defer (gb_mutex_unlock(&m));
+
+	// Do whatever as the mutex is now scoped based!
+}
+#endif
+
+
+
+#define GB_THREAD_PROC(name) void name(void *data)
+typedef GB_THREAD_PROC(gbThreadProc);
+
+typedef struct gbThread {
+#if defined(GB_SYSTEM_WINDOWS)
+	void *        win32_handle;
+#else
+	pthread_t     posix_handle;
+#endif
+
+	gbThreadProc *proc;
+	void *        data;
+
+	gbSemaphore   semaphore;
+	isize         stack_size;
+	b32           is_running;
+} gbThread;
+
+GB_DEF void gb_thread_init            (gbThread *t);
+GB_DEF void gb_thread_destory         (gbThread *t);
+GB_DEF void gb_thread_start           (gbThread *t, gbThreadProc *proc, void *data);
+GB_DEF void gb_thread_start_with_stack(gbThread *t, gbThreadProc *proc, void *data, isize stack_size);
+GB_DEF void gb_thread_join            (gbThread *t);
+GB_DEF b32  gb_thread_is_running      (gbThread const *t);
+GB_DEF u32  gb_thread_current_id      (void);
+GB_DEF void gb_thread_set_name        (gbThread *t, char const *name);
+
+
+// NOTE(bill): Thread Merge Operation
+// Based on Sean Barrett's stb_sync
+typedef struct gbSync {
+	i32 target;  // Target Number of threads
+	i32 current; // Threads to hit
+	i32 waiting; // Threads waiting
+
+	gbMutex start;
+	gbMutex mutex;
+	gbSemaphore release;
+} gbSync;
+
+GB_DEF void gb_sync_init          (gbSync *s);
+GB_DEF void gb_sync_destroy       (gbSync *s);
+GB_DEF void gb_sync_set_target    (gbSync *s, i32 count);
+GB_DEF void gb_sync_release       (gbSync *s);
+GB_DEF i32  gb_sync_reach         (gbSync *s);
+GB_DEF void gb_sync_reach_and_wait(gbSync *s);
+
+
+
+#if defined(GB_SYSTEM_WINDOWS)
+
+typedef struct gbAffinity {
+	b32   is_accurate;
+	isize core_count;
+	isize thread_count;
+	#define GB_WIN32_MAX_THREADS (8 * gb_size_of(usize))
+	usize core_masks[GB_WIN32_MAX_THREADS];
+
+} gbAffinity;
+
+#elif defined(GB_SYSTEM_OSX)
+typedef struct gbAffinity {
+	b32   is_accurate;
+	isize core_count;
+	isize thread_count;
+	isize threads_per_core;
+} gbAffinity;
+
+#elif defined(GB_SYSTEM_LINUX)
+#error TODO(bill): Implement gbAffinity for linux
+#else
+#error TODO(bill): Unknown system
+#endif
+
+GB_DEF void  gb_affinity_init   (gbAffinity *a);
+GB_DEF void  gb_affinity_destroy(gbAffinity *a);
+GB_DEF b32   gb_affinity_set    (gbAffinity *a, isize core, isize thread);
+GB_DEF isize gb_affinity_thread_count_for_core(gbAffinity *a, isize core);
+
+
+
+
+////////////////////////////////////////////////////////////////
+//
+// Virtual Memory
+//
+//
+
+typedef struct gbVirtualMemory {
+	void *data;
+	isize size;
+} gbVirtualMemory;
+
+GB_DEF gbVirtualMemory gb_virtual_memory(void *data, isize size);
+GB_DEF gbVirtualMemory gb_vm_alloc      (void *addr, isize size);
+GB_DEF b32             gb_vm_free       (gbVirtualMemory vm);
+GB_DEF gbVirtualMemory gb_vm_trim       (gbVirtualMemory vm, isize lead_size, isize size);
+GB_DEF b32             gb_vm_purge      (gbVirtualMemory vm);
+GB_DEF isize gb_virtual_memory_page_size(isize *alignment_out);
+
+
+
+
+////////////////////////////////////////////////////////////////
+//
+// Custom Allocation
+//
+//
+
+typedef enum gbAllocationType {
+	gbAllocation_Alloc,
+	gbAllocation_Free,
+	gbAllocation_FreeAll,
+	gbAllocation_Resize,
+} gbAllocationType;
+
+// NOTE(bill): This is useful so you can define an allocator of the same type and parameters
+#define GB_ALLOCATOR_PROC(name)                         \
+void *name(void *allocator_data, gbAllocationType type, \
+           isize size, isize alignment,                 \
+           void *old_memory, isize old_size,            \
+           u64 flags)
+typedef GB_ALLOCATOR_PROC(gbAllocatorProc);
+
+typedef struct gbAllocator {
+	gbAllocatorProc *proc;
+	void *           data;
+} gbAllocator;
+
+typedef enum gbAllocatorFlag {
+	gbAllocatorFlag_ClearToZero = GB_BIT(0),
+} gbAllocatorFlag;
+
+// TODO(bill): Is this a decent default alignment?
+#ifndef GB_DEFAULT_MEMORY_ALIGNMENT
+#define GB_DEFAULT_MEMORY_ALIGNMENT (2 * gb_size_of(void *))
+#endif
+
+#ifndef GB_DEFAULT_ALLOCATOR_FLAGS
+#define GB_DEFAULT_ALLOCATOR_FLAGS (gbAllocatorFlag_ClearToZero)
+#endif
+
+GB_DEF void *gb_alloc_align (gbAllocator a, isize size, isize alignment);
+GB_DEF void *gb_alloc       (gbAllocator a, isize size);
+GB_DEF void  gb_free        (gbAllocator a, void *ptr);
+GB_DEF void  gb_free_all    (gbAllocator a);
+GB_DEF void *gb_resize      (gbAllocator a, void *ptr, isize old_size, isize new_size);
+GB_DEF void *gb_resize_align(gbAllocator a, void *ptr, isize old_size, isize new_size, isize alignment);
+// TODO(bill): For gb_resize, should the use need to pass the old_size or only the new_size?
+
+GB_DEF void *gb_alloc_copy      (gbAllocator a, void const *src, isize size);
+GB_DEF void *gb_alloc_copy_align(gbAllocator a, void const *src, isize size, isize alignment);
+GB_DEF char *gb_alloc_str       (gbAllocator a, char const *str);
+GB_DEF char *gb_alloc_str_len   (gbAllocator a, char const *str, isize len);
+
+
+// NOTE(bill): These are very useful and the type cast has saved me from numerous bugs
+#ifndef gb_alloc_item
+#define gb_alloc_item(allocator_, Type)         (Type *)gb_alloc(allocator_, gb_size_of(Type))
+#define gb_alloc_array(allocator_, Type, count) (Type *)gb_alloc(allocator_, gb_size_of(Type) * (count))
+#endif
+
+// NOTE(bill): Use this if you don't need a "fancy" resize allocation
+GB_DEF void *gb_default_resize_align(gbAllocator a, void *ptr, isize old_size, isize new_size, isize alignment);
+
+
+
+// TODO(bill): Probably use a custom heap allocator system that doesn't depend on malloc/free
+// Base it off TCMalloc or something else? Or something entirely custom?
+GB_DEF gbAllocator gb_heap_allocator(void);
+GB_DEF GB_ALLOCATOR_PROC(gb_heap_allocator_proc);
+
+// NOTE(bill): Yep, I use my own allocator system!
+#ifndef gb_malloc
+#define gb_malloc(sz) gb_alloc(gb_heap_allocator(), sz)
+#define gb_mfree(ptr) gb_free(gb_heap_allocator(), ptr)
+#endif
+
+
+
+//
+// Arena Allocator
+//
+typedef struct gbArena {
+	gbAllocator backing;
+	void *      physical_start;
+	isize       total_size;
+	isize       total_allocated;
+	isize       temp_count;
+} gbArena;
+
+GB_DEF void gb_arena_init_from_memory   (gbArena *arena, void *start, isize size);
+GB_DEF void gb_arena_init_from_allocator(gbArena *arena, gbAllocator backing, isize size);
+GB_DEF void gb_arena_init_sub           (gbArena *arena, gbArena *parent_arena, isize size);
+GB_DEF void gb_arena_free               (gbArena *arena);
+
+GB_DEF isize gb_arena_alignment_of  (gbArena *arena, isize alignment);
+GB_DEF isize gb_arena_size_remaining(gbArena *arena, isize alignment);
+GB_DEF void  gb_arena_check         (gbArena *arena);
+
+
+// Allocation Types: alloc, free_all, resize
+GB_DEF gbAllocator gb_arena_allocator(gbArena *arena);
+GB_DEF GB_ALLOCATOR_PROC(gb_arena_allocator_proc);
+
+
+
+typedef struct gbTempArenaMemory {
+	gbArena *arena;
+	isize    original_count;
+} gbTempArenaMemory;
+
+GB_DEF gbTempArenaMemory gb_temp_arena_memory_begin(gbArena *arena);
+GB_DEF void              gb_temp_arena_memory_end  (gbTempArenaMemory tmp_mem);
+
+
+
+
+
+
+
+//
+// Pool Allocator
+//
+
+
+typedef struct gbPool {
+	gbAllocator backing;
+	void *      physical_start;
+	void *      free_list;
+	isize       block_size;
+	isize       block_align;
+	isize       total_size;
+} gbPool;
+
+GB_DEF void gb_pool_init      (gbPool *pool, gbAllocator backing, isize num_blocks, isize block_size);
+GB_DEF void gb_pool_init_align(gbPool *pool, gbAllocator backing, isize num_blocks, isize block_size, isize block_align);
+GB_DEF void gb_pool_free      (gbPool *pool);
+
+// Allocation Types: alloc, free
+GB_DEF gbAllocator gb_pool_allocator(gbPool *pool);
+GB_DEF GB_ALLOCATOR_PROC(gb_pool_allocator_proc);
+
+
+
+// NOTE(bill): Used for allocators to keep track of sizes
+typedef struct gbAllocationHeader {
+	isize size;
+} gbAllocationHeader;
+
+GB_DEF gbAllocationHeader *gb_allocation_header     (void *data);
+GB_DEF void                gb_allocation_header_fill(gbAllocationHeader *header, void *data, isize size);
+
+// TODO(bill): Find better way of doing this without #if #elif etc.
+#if defined(GB_ARCH_32_BIT)
+#define GB_ISIZE_HIGH_BIT 0x80000000
+#elif defined(GB_ARCH_64_BIT)
+#define GB_ISIZE_HIGH_BIT 0x8000000000000000ll
+#else
+#error
+#endif
+
+//
+// Free List Allocator
+//
+
+// IMPORTANT TODO(bill): Thoroughly test the free list allocator!
+// NOTE(bill): This is a very shitty free list as it just picks the first free block not the best size
+// as I am just being lazy. Also, I will probably remove it later; it's only here because why not?!
+//
+// NOTE(bill): I may also complete remove this if I completely implement a fixed heap allocator
+
+typedef struct gbFreeListBlock gbFreeListBlock;
+struct gbFreeListBlock {
+	gbFreeListBlock *next;
+	isize            size;
+};
+
+typedef struct gbFreeList {
+	void *           physical_start;
+	isize            total_size;
+
+	gbFreeListBlock *curr_block;
+
+	isize            total_allocated;
+	isize            allocation_count;
+} gbFreeList;
+
+GB_DEF void gb_free_list_init               (gbFreeList *fl, void *start, isize size);
+GB_DEF void gb_free_list_init_from_allocator(gbFreeList *fl, gbAllocator backing, isize size);
+
+// Allocation Types: alloc, free, free_all, resize
+GB_DEF gbAllocator gb_free_list_allocator(gbFreeList *fl);
+GB_DEF GB_ALLOCATOR_PROC(gb_free_list_allocator_proc);
+
+
+
+//
+// Scratch Memory Allocator - Ring Buffer Based Arena
+//
+
+typedef struct gbScratchMemory {
+	void *physical_start;
+	isize total_size;
+	void *alloc_point;
+	void *free_point;
+} gbScratchMemory;
+
+GB_DEF void gb_scratch_memory_init     (gbScratchMemory *s, void *start, isize size);
+GB_DEF b32  gb_scratch_memory_is_in_use(gbScratchMemory *s, void *ptr);
+
+
+// Allocation Types: alloc, free, free_all, resize
+GB_DEF gbAllocator gb_scratch_allocator(gbScratchMemory *s);
+GB_DEF GB_ALLOCATOR_PROC(gb_scratch_allocator_proc);
+
+// TODO(bill): Stack allocator
+// TODO(bill): Fixed heap allocator
+// TODO(bill): General heap allocator. Maybe a TCMalloc like clone?
+
+
+////////////////////////////////////////////////////////////////
+//
+// Sort & Search
+//
+//
+
+#define GB_COMPARE_PROC(name) int name(void const *a, void const *b)
+typedef GB_COMPARE_PROC(gbCompareProc);
+
+#define GB_COMPARE_PROC_PTR(def) GB_COMPARE_PROC((*def))
+
+// Producure pointers
+// NOTE(bill): The offset parameter specifies the offset in the structure
+// e.g. gb_i32_cmp(gb_offset_of(Thing, value))
+// Use 0 if it's just the type instead.
+
+GB_DEF GB_COMPARE_PROC_PTR(gb_i16_cmp  (isize offset));
+GB_DEF GB_COMPARE_PROC_PTR(gb_i32_cmp  (isize offset));
+GB_DEF GB_COMPARE_PROC_PTR(gb_i64_cmp  (isize offset));
+GB_DEF GB_COMPARE_PROC_PTR(gb_isize_cmp(isize offset));
+GB_DEF GB_COMPARE_PROC_PTR(gb_str_cmp  (isize offset));
+GB_DEF GB_COMPARE_PROC_PTR(gb_f32_cmp  (isize offset));
+GB_DEF GB_COMPARE_PROC_PTR(gb_f64_cmp  (isize offset));
+GB_DEF GB_COMPARE_PROC_PTR(gb_char_cmp (isize offset));
+
+// TODO(bill): Better sorting algorithms
+// NOTE(bill): Uses quick sort for large arrays but insertion sort for small
+#define gb_sort_array(array, count, compare_proc) gb_sort(array, count, gb_size_of(*(array)), compare_proc)
+GB_DEF void gb_sort(void *base, isize count, isize size, gbCompareProc compare_proc);
+
+// NOTE(bill): the count of temp == count of items
+#define gb_radix_sort(Type) gb_radix_sort_##Type
+#define GB_RADIX_SORT_PROC(Type) void gb_radix_sort(Type)(Type *items, Type *temp, isize count)
+
+GB_DEF GB_RADIX_SORT_PROC(u8);
+GB_DEF GB_RADIX_SORT_PROC(u16);
+GB_DEF GB_RADIX_SORT_PROC(u32);
+GB_DEF GB_RADIX_SORT_PROC(u64);
+
+
+// NOTE(bill): Returns index or -1 if not found
+#define gb_binary_search_array(array, count, key, compare_proc) gb_binary_search(array, count, gb_size_of(*(array)), key, compare_proc)
+GB_DEF isize gb_binary_search(void const *base, isize count, isize size, void const *key, gbCompareProc compare_proc);
+
+#define gb_shuffle_array(array, count) gb_shuffle(array, count, gb_size_of(*(array)))
+GB_DEF void gb_shuffle(void *base, isize count, isize size);
+
+#define gb_reverse_array(array, count) gb_reverse(array, count, gb_size_of(*(array)))
+GB_DEF void gb_reverse(void *base, isize count, isize size);
+
+////////////////////////////////////////////////////////////////
+//
+// Char Functions
+//
+//
+
+GB_DEF char gb_char_to_lower       (char c);
+GB_DEF char gb_char_to_upper       (char c);
+GB_DEF b32  gb_char_is_space       (char c);
+GB_DEF b32  gb_char_is_digit       (char c);
+GB_DEF b32  gb_char_is_hex_digit   (char c);
+GB_DEF b32  gb_char_is_alpha       (char c);
+GB_DEF b32  gb_char_is_alphanumeric(char c);
+GB_DEF i32  gb_digit_to_int        (char c);
+GB_DEF i32  gb_hex_digit_to_int    (char c);
+
+// NOTE(bill): ASCII only
+GB_DEF void gb_str_to_lower(char *str);
+GB_DEF void gb_str_to_upper(char *str);
+
+GB_DEF isize gb_strlen (char const *str);
+GB_DEF isize gb_strnlen(char const *str, isize max_len);
+GB_DEF i32   gb_strcmp (char const *s1, char const *s2);
+GB_DEF i32   gb_strncmp(char const *s1, char const *s2, isize len);
+GB_DEF char *gb_strcpy (char *dest, char const *source);
+GB_DEF char *gb_strncpy(char *dest, char const *source, isize len);
+GB_DEF isize gb_strlcpy(char *dest, char const *source, isize len);
+GB_DEF char *gb_strrev (char *str); // NOTE(bill): ASCII only
+
+// NOTE(bill): A less fucking crazy strtok!
+GB_DEF char const *gb_strtok(char *output, char const *src, char const *delimit);
+
+GB_DEF b32 gb_str_has_prefix(char const *str, char const *prefix);
+GB_DEF b32 gb_str_has_suffix(char const *str, char const *suffix);
+
+GB_DEF char const *gb_char_first_occurence(char const *str, char c);
+GB_DEF char const *gb_char_last_occurence (char const *str, char c);
+
+GB_DEF void gb_str_concat(char *dest, isize dest_len,
+                          char const *src_a, isize src_a_len,
+                          char const *src_b, isize src_b_len);
+
+GB_DEF u64   gb_str_to_u64(char const *str, char **end_ptr, i32 base); // TODO(bill): Support more than just decimal and hexadecimal
+GB_DEF i64   gb_str_to_i64(char const *str, char **end_ptr, i32 base); // TODO(bill): Support more than just decimal and hexadecimal
+GB_DEF f32   gb_str_to_f32(char const *str, char **end_ptr);
+GB_DEF f64   gb_str_to_f64(char const *str, char **end_ptr);
+GB_DEF void  gb_i64_to_str(i64 value, char *string, i32 base);
+GB_DEF void  gb_u64_to_str(u64 value, char *string, i32 base);
+
+
+////////////////////////////////////////////////////////////////
+//
+// UTF-8 Handling
+//
+//
+
+// NOTE(bill): Does not check if utf-8 string is valid
+GB_DEF isize gb_utf8_strlen (u8 const *str);
+GB_DEF isize gb_utf8_strnlen(u8 const *str, isize max_len);
+
+// NOTE(bill): Windows doesn't handle 8 bit filenames well ('cause Micro$hit)
+GB_DEF u16 *gb_utf8_to_ucs2    (u16 *buffer, isize len, u8 const *str);
+GB_DEF u8 * gb_ucs2_to_utf8    (u8 *buffer, isize len, u16 const *str);
+GB_DEF u16 *gb_utf8_to_ucs2_buf(u8 const *str);   // NOTE(bill): Uses locally persisting buffer
+GB_DEF u8 * gb_ucs2_to_utf8_buf(u16 const *str); // NOTE(bill): Uses locally persisting buffer
+
+// NOTE(bill): Returns size of codepoint in bytes
+GB_DEF isize gb_utf8_decode        (u8 const *str, isize str_len, Rune *codepoint);
+GB_DEF isize gb_utf8_codepoint_size(u8 const *str, isize str_len);
+
+////////////////////////////////////////////////////////////////
+//
+// gbString - C Read-Only-Compatible
+//
+//
+/*
+Reasoning:
+
+	By default, strings in C are null terminated which means you have to count
+	the number of character up to the null character to calculate the length.
+	Many "better" C string libraries will create a struct for a string.
+	i.e.
+
+	    struct String {
+	    	Allocator allocator;
+	        size_t    length;
+	        size_t    capacity;
+	        char *    cstring;
+	    };
+
+	This library tries to augment normal C strings in a better way that is still
+	compatible with C-style strings.
+
+	+--------+-----------------------+-----------------+
+	| Header | Binary C-style String | Null Terminator |
+	+--------+-----------------------+-----------------+
+	         |
+	         +-> Pointer returned by functions
+
+	Due to the meta-data being stored before the string pointer and every gb string
+	having an implicit null terminator, gb strings are full compatible with c-style
+	strings and read-only functions.
+
+Advantages:
+
+    * gb strings can be passed to C-style string functions without accessing a struct
+      member of calling a function, i.e.
+
+          gb_printf("%s\n", gb_str);
+
+      Many other libraries do either of these:
+
+          gb_printf("%s\n", string->cstr);
+          gb_printf("%s\n", get_cstring(string));
+
+    * You can access each character just like a C-style string:
+
+          gb_printf("%c %c\n", str[0], str[13]);
+
+    * gb strings are singularly allocated. The meta-data is next to the character
+      array which is better for the cache.
+
+Disadvantages:
+
+    * In the C version of these functions, many return the new string. i.e.
+          str = gb_string_appendc(str, "another string");
+      This could be changed to gb_string_appendc(&str, "another string"); but I'm still not sure.
+
+	* This is incompatible with "gb_string.h" strings
+*/
+
+#if 0
+#define GB_IMPLEMENTATION
+#include "gb.h"
+int main(int argc, char **argv) {
+	gbString str = gb_string_make("Hello");
+	gbString other_str = gb_string_make_length(", ", 2);
+	str = gb_string_append(str, other_str);
+	str = gb_string_appendc(str, "world!");
+
+	gb_printf("%s\n", str); // Hello, world!
+
+	gb_printf("str length = %d\n", gb_string_length(str));
+
+	str = gb_string_set(str, "Potato soup");
+	gb_printf("%s\n", str); // Potato soup
+
+	str = gb_string_set(str, "Hello");
+	other_str = gb_string_set(other_str, "Pizza");
+	if (gb_strings_are_equal(str, other_str))
+		gb_printf("Not called\n");
+	else
+		gb_printf("Called\n");
+
+	str = gb_string_set(str, "Ab.;!...AHello World       ??");
+	str = gb_string_trim(str, "Ab.;!. ?");
+	gb_printf("%s\n", str); // "Hello World"
+
+	gb_string_free(str);
+	gb_string_free(other_str);
+
+	return 0;
+}
+#endif
+
+// TODO(bill): Should this be a wrapper to gbArray(char) or this extra type safety better?
+typedef char *gbString;
+
+// NOTE(bill): If you only need a small string, just use a standard c string or change the size from isize to u16, etc.
+typedef struct gbStringHeader {
+	gbAllocator allocator;
+	isize       length;
+	isize       capacity;
+} gbStringHeader;
+
+#define GB_STRING_HEADER(str) (cast(gbStringHeader *)(str) - 1)
+
+GB_DEF gbString gb_string_make           (gbAllocator a, char const *str);
+GB_DEF gbString gb_string_make_length    (gbAllocator a, void const *str, isize num_bytes);
+GB_DEF void     gb_string_free           (gbString str);
+GB_DEF gbString gb_string_duplicate      (gbAllocator a, gbString const str);
+GB_DEF isize    gb_string_length         (gbString const str);
+GB_DEF isize    gb_string_capacity       (gbString const str);
+GB_DEF isize    gb_string_available_space(gbString const str);
+GB_DEF void     gb_string_clear          (gbString str);
+GB_DEF gbString gb_string_append         (gbString str, gbString const other);
+GB_DEF gbString gb_string_append_length  (gbString str, void const *other, isize num_bytes);
+GB_DEF gbString gb_string_appendc        (gbString str, char const *other);
+GB_DEF gbString gb_string_set            (gbString str, char const *cstr);
+GB_DEF gbString gb_string_make_space_for (gbString str, isize add_len);
+GB_DEF isize    gb_string_allocation_size(gbString const str);
+GB_DEF b32      gb_string_are_equal      (gbString const lhs, gbString const rhs);
+GB_DEF gbString gb_string_trim           (gbString str, char const *cut_set);
+GB_DEF gbString gb_string_trim_space     (gbString str); // Whitespace ` \t\r\n\v\f`
+
+
+
+////////////////////////////////////////////////////////////////
+//
+// Fixed Capacity Buffer (POD Types)
+//
+//
+// gbBuffer(Type) works like gbString or gbArray where the actual type is just a pointer to the first
+// element.
+//
+
+typedef struct gbBufferHeader {
+	isize count;
+	isize capacity;
+} gbBufferHeader;
+
+#define gbBuffer(Type) Type *
+
+#define GB_BUFFER_HEADER(x)   (cast(gbBufferHeader *)(x) - 1)
+#define gb_buffer_count(x)    (GB_BUFFER_HEADER(x)->count)
+#define gb_buffer_capacity(x) (GB_BUFFER_HEADER(x)->capacity)
+
+#define gb_buffer_init(x, allocator, cap) do { \
+	void **nx = cast(void **)&(x); \
+	gbBufferHeader *gb__bh = cast(gbBufferHeader *)gb_alloc((allocator), (cap)*gb_size_of(*(x))); \
+	gb__bh->count = 0; \
+	gb__bh->capacity = cap; \
+	*nx = cast(void *)(gb__bh+1); \
+} while (0)
+
+
+#define gb_buffer_free(x, allocator) (gb_free(allocator, GB_BUFFER_HEADER(x)))
+
+#define gb_buffer_append(x, item) do { (x)[gb_buffer_count(x)++] = (item); } while (0)
+
+#define gb_buffer_appendv(x, items, item_count) do { \
+	GB_ASSERT(gb_size_of(*(items)) == gb_size_of(*(x))); \
+	GB_ASSERT(gb_buffer_count(x)+item_count <= gb_buffer_capacity(x)); \
+	gb_memcopy(&(x)[gb_buffer_count(x)], (items), gb_size_of(*(x))*(item_count)); \
+	gb_buffer_count(x) += (item_count); \
+} while (0)
+
+#define gb_buffer_pop(x)   do { GB_ASSERT(gb_buffer_count(x) > 0); gb_buffer_count(x)--; } while (0)
+#define gb_buffer_clear(x) do { gb_buffer_count(x) = 0; } while (0)
+
+
+
+////////////////////////////////////////////////////////////////
+//
+// Dynamic Array (POD Types)
+//
+// NOTE(bill): I know this is a macro hell but C is an old (and shit) language with no proper arrays
+// Also why the fuck not?! It fucking works! And it has custom allocation, which is already better than C++!
+//
+// gbArray(Type) works like gbString or gbBuffer where the actual type is just a pointer to the first
+// element.
+//
+
+
+
+// Available Procedures for gbArray(Type)
+// gb_array_init
+// gb_array_free
+// gb_array_set_capacity
+// gb_array_grow
+// gb_array_append
+// gb_array_appendv
+// gb_array_pop
+// gb_array_clear
+// gb_array_resize
+// gb_array_reserve
+//
+
+#if 0 // Example
+void foo(void) {
+	isize i;
+	int test_values[] = {4, 2, 1, 7};
+	gbAllocator a = gb_heap_allocator();
+	gbArray(int) items;
+
+	gb_array_init(items, a);
+
+	gb_array_append(items, 1);
+	gb_array_append(items, 4);
+	gb_array_append(items, 9);
+	gb_array_append(items, 16);
+
+	items[1] = 3; // Manually set value
+	              // NOTE: No array bounds checking
+
+	for (i = 0; i < items.count; i++)
+		gb_printf("%d\n", items[i]);
+	// 1
+	// 3
+	// 9
+	// 16
+
+	gb_array_clear(items);
+
+	gb_array_appendv(items, test_values, gb_count_of(test_values));
+	for (i = 0; i < items.count; i++)
+		gb_printf("%d\n", items[i]);
+	// 4
+	// 2
+	// 1
+	// 7
+
+	gb_array_free(items);
+}
+#endif
+
+typedef struct gbArrayHeader {
+	gbAllocator allocator;
+	isize       count;
+	isize       capacity;
+} gbArrayHeader;
+
+// NOTE(bill): This thing is magic!
+#define gbArray(Type) Type *
+
+#ifndef GB_ARRAY_GROW_FORMULA
+#define GB_ARRAY_GROW_FORMULA(x) (2*(x) + 8)
+#endif
+
+GB_STATIC_ASSERT(GB_ARRAY_GROW_FORMULA(0) > 0);
+
+#define GB_ARRAY_HEADER(x)    (cast(gbArrayHeader *)(x) - 1)
+#define gb_array_allocator(x) (GB_ARRAY_HEADER(x)->allocator)
+#define gb_array_count(x)     (GB_ARRAY_HEADER(x)->count)
+#define gb_array_capacity(x)  (GB_ARRAY_HEADER(x)->capacity)
+
+// TODO(bill): Have proper alignment!
+#define gb_array_init_reserve(x, allocator_, cap) do { \
+	void **gb__array_ = cast(void **)&(x); \
+	gbArrayHeader *gb__ah = cast(gbArrayHeader *)gb_alloc(allocator_, gb_size_of(gbArrayHeader)+gb_size_of(*(x))*(cap)); \
+	gb__ah->allocator = allocator_; \
+	gb__ah->count = gb__ah->capacity = 0; \
+	*gb__array_ = cast(void *)(gb__ah+1); \
+} while (0)
+
+// NOTE(bill): Give it an initial default capacity
+#define gb_array_init(x, allocator) gb_array_init_reserve(x, allocator, GB_ARRAY_GROW_FORMULA(0))
+
+#define gb_array_free(x) do { \
+	gbArrayHeader *gb__ah = GB_ARRAY_HEADER(x); \
+	gb_free(gb__ah->allocator, gb__ah); \
+} while (0)
+
+#define gb_array_set_capacity(x, capacity) do { \
+	if (x) { \
+		void **gb__array_ = cast(void **)&(x); \
+		*gb__array_ = gb__array_set_capacity((x), (capacity), gb_size_of(*(x))); \
+	} \
+} while (0)
+
+// NOTE(bill): Do not use the thing below directly, use the macro
+GB_DEF void *gb__array_set_capacity(void *array, isize capacity, isize element_size);
+
+
+// TODO(bill): Decide on a decent growing formula for gbArray
+#define gb_array_grow(x, min_capacity) do { \
+	isize new_capacity = GB_ARRAY_GROW_FORMULA(gb_array_capacity(x)); \
+	if (new_capacity < (min_capacity)) \
+		new_capacity = (min_capacity); \
+	gb_array_set_capacity(x, new_capacity); \
+} while (0)
+
+
+#define gb_array_append(x, item) do { \
+	if (gb_array_capacity(x) < gb_array_count(x)+1) \
+		gb_array_grow(x, 0); \
+	(x)[gb_array_count(x)++] = (item); \
+} while (0)
+
+#define gb_array_appendv(x, items, item_count) do { \
+	gbArrayHeader *gb__ah = GB_ARRAY_HEADER(x); \
+	GB_ASSERT(gb_size_of((items)[0]) == gb_size_of((x)[0])); \
+	if (gb__ah->capacity < gb__ah->count+(item_count)) \
+		gb_array_grow(x, gb__ah->count+(item_count)); \
+	gb_memcopy(&(x)[gb__ah->count], (items), gb_size_of((x)[0])*(item_count));\
+	gb__ah->count += (item_count); \
+} while (0)
+
+
+
+#define gb_array_pop(x)   do { GB_ASSERT(GB_ARRAY_HEADER(x)->count > 0); GB_ARRAY_HEADER(x)->count--; } while (0)
+#define gb_array_clear(x) do { GB_ARRAY_HEADER(x)->count = 0; } while (0)
+
+#define gb_array_resize(x, new_count) do { \
+	if (GB_ARRAY_HEADER(x)->capacity < (new_count)) \
+		gb_array_grow(x, (new_count)); \
+	GB_ARRAY_HEADER(x)->count = (new_count); \
+} while (0)
+
+
+#define gb_array_reserve(x, new_capacity) do { \
+	if (GB_ARRAY_HEADER(x)->capacity < (new_capacity)) \
+		gb_array_set_capacity(x, new_capacity); \
+} while (0)
+
+
+
+
+
+////////////////////////////////////////////////////////////////
+//
+// Hashing and Checksum Functions
+//
+//
+
+GB_EXTERN u32 gb_adler32(void const *data, isize len);
+
+GB_EXTERN u32 gb_crc32(void const *data, isize len);
+GB_EXTERN u64 gb_crc64(void const *data, isize len);
+
+GB_EXTERN u32 gb_fnv32 (void const *data, isize len);
+GB_EXTERN u64 gb_fnv64 (void const *data, isize len);
+GB_EXTERN u32 gb_fnv32a(void const *data, isize len);
+GB_EXTERN u64 gb_fnv64a(void const *data, isize len);
+
+// NOTE(bill): Default seed of 0x9747b28c
+// NOTE(bill): I prefer using murmur64 for most hashes
+GB_EXTERN u32 gb_murmur32(void const *data, isize len);
+GB_EXTERN u64 gb_murmur64(void const *data, isize len);
+
+GB_EXTERN u32 gb_murmur32_seed(void const *data, isize len, u32 seed);
+GB_EXTERN u64 gb_murmur64_seed(void const *data, isize len, u64 seed);
+
+
+////////////////////////////////////////////////////////////////
+//
+// Instantiated Hash Table
+//
+// This is an attempt to implement a templated hash table
+// NOTE(bill): The key is aways a u64 for simplicity and you will _probably_ _never_ need anything bigger.
+//
+// Hash table type and function declaration, call: GB_TABLE_DECLARE(PREFIX, NAME, N, VALUE)
+// Hash table function definitions, call: GB_TABLE_DEFINE(NAME, N, VALUE)
+//
+//     PREFIX  - a prefix for function prototypes e.g. extern, static, etc.
+//     NAME    - Name of the Hash Table
+//     FUNC    - the name will prefix function names
+//     VALUE   - the type of the value to be stored
+//
+// NOTE(bill): I really wish C had decent metaprogramming capabilities (and no I don't mean C++'s templates either)
+//
+
+typedef struct gbHashTableFindResult {
+	isize hash_index;
+	isize entry_prev;
+	isize entry_index;
+} gbHashTableFindResult;
+
+#define GB_TABLE(PREFIX, NAME, FUNC, VALUE) \
+	GB_TABLE_DECLARE(PREFIX, NAME, FUNC, VALUE); \
+	GB_TABLE_DEFINE(NAME, FUNC, VALUE);
+
+#define GB_TABLE_DECLARE(PREFIX, NAME, FUNC, VALUE) \
+typedef struct GB_JOIN2(NAME,Entry) { \
+	u64 key; \
+	isize next; \
+	VALUE value; \
+} GB_JOIN2(NAME,Entry); \
+\
+typedef struct NAME { \
+	gbArray(isize) hashes; \
+	gbArray(GB_JOIN2(NAME,Entry)) entries; \
+} NAME; \
+\
+PREFIX void                  GB_JOIN2(FUNC,init)       (NAME *h, gbAllocator a); \
+PREFIX void                  GB_JOIN2(FUNC,destroy)    (NAME *h); \
+PREFIX VALUE *               GB_JOIN2(FUNC,get)        (NAME *h, u64 key); \
+PREFIX void                  GB_JOIN2(FUNC,set)        (NAME *h, u64 key, VALUE value); \
+PREFIX void                  GB_JOIN2(FUNC,grow)       (NAME *h); \
+PREFIX void                  GB_JOIN2(FUNC,rehash)     (NAME *h, isize new_count); \
+
+
+
+
+
+#define GB_TABLE_DEFINE(NAME, FUNC, VALUE) \
+void GB_JOIN2(FUNC,init)(NAME *h, gbAllocator a) { \
+	gb_array_init(h->hashes,  a); \
+	gb_array_init(h->entries, a); \
+} \
+\
+void GB_JOIN2(FUNC,destroy)(NAME *h) { \
+	if (h->entries) gb_array_free(h->entries); \
+	if (h->hashes)  gb_array_free(h->hashes); \
+} \
+\
+gb_internal isize GB_JOIN2(FUNC,_add_entry)(NAME *h, u64 key) { \
+	isize index; \
+	GB_JOIN2(NAME,Entry) e = {0}; \
+	e.key = key; \
+	e.next = -1; \
+	index = gb_array_count(h->entries); \
+	gb_array_append(h->entries, e); \
+	return index; \
+} \
+\
+gb_internal gbHashTableFindResult GB_JOIN2(FUNC,_find)(NAME *h, u64 key) { \
+	gbHashTableFindResult r = {-1, -1, -1}; \
+	if (gb_array_count(h->hashes) > 0) { \
+		r.hash_index  = key % gb_array_count(h->hashes); \
+		r.entry_index = h->hashes[r.hash_index]; \
+		while (r.entry_index >= 0) { \
+			if (h->entries[r.entry_index].key == key) \
+				return r; \
+			r.entry_prev = r.entry_index; \
+			r.entry_index = h->entries[r.entry_index].next; \
+		} \
+	} \
+	return r; \
+} \
+\
+gb_internal b32 GB_JOIN2(FUNC,_full)(NAME *h) { \
+	return 0.75f * gb_array_count(h->hashes) < gb_array_count(h->entries); \
+} \
+\
+void GB_JOIN2(FUNC,grow)(NAME *h) { \
+	isize new_count = GB_ARRAY_GROW_FORMULA(gb_array_count(h->entries)); \
+	GB_JOIN2(FUNC,rehash)(h, new_count); \
+} \
+\
+void GB_JOIN2(FUNC,rehash)(NAME *h, isize new_count) { \
+	isize i, j; \
+	NAME nh = {0}; \
+	GB_JOIN2(FUNC,init)(&nh, gb_array_allocator(h->hashes)); \
+	gb_array_resize(nh.hashes, new_count); \
+	gb_array_reserve(nh.entries, gb_array_count(h->entries)); \
+	for (i = 0; i < new_count; i++) \
+		nh.hashes[i] = -1; \
+	for (i = 0; i < gb_array_count(h->entries); i++) { \
+		GB_JOIN2(NAME,Entry) *e; \
+		gbHashTableFindResult fr; \
+		if (gb_array_count(nh.hashes) == 0) \
+			GB_JOIN2(FUNC,grow)(&nh); \
+		e = &nh.entries[i]; \
+		fr = GB_JOIN2(FUNC,_find)(&nh, e->key); \
+		j = GB_JOIN2(FUNC,_add_entry)(&nh, e->key); \
+		if (fr.entry_prev < 0) \
+			nh.hashes[fr.hash_index] = j; \
+		else \
+			nh.entries[fr.entry_prev].next = j; \
+		nh.entries[j].next = fr.entry_index; \
+		nh.entries[j].value = e->value; \
+		if (GB_JOIN2(FUNC,_full)(&nh)) \
+			GB_JOIN2(FUNC,grow)(&nh); \
+	} \
+	GB_JOIN2(FUNC,destroy)(h); \
+	h->hashes  = nh.hashes; \
+	h->entries = nh.entries; \
+} \
+\
+VALUE *GB_JOIN2(FUNC,get)(NAME *h, u64 key) { \
+	isize index = GB_JOIN2(FUNC,_find)(h, key).entry_index; \
+	if (index >= 0) \
+		return &h->entries[index].value; \
+	return NULL; \
+} \
+\
+void GB_JOIN2(FUNC,set)(NAME *h, u64 key, VALUE value) { \
+	isize index; \
+	gbHashTableFindResult fr; \
+	if (gb_array_count(h->hashes) == 0) \
+		GB_JOIN2(FUNC,grow)(h); \
+	fr = GB_JOIN2(FUNC,_find)(h, key); \
+	if (fr.entry_index >= 0) { \
+		index = fr.entry_index; \
+	} else { \
+		index = GB_JOIN2(FUNC,_add_entry)(h, key); \
+		if (fr.entry_prev >= 0) { \
+			h->entries[fr.entry_prev].next = index; \
+		} else { \
+			h->hashes[fr.hash_index] = index; \
+		} \
+	} \
+	h->entries[index].value = value; \
+	if (GB_JOIN2(FUNC,_full)(h)) \
+		GB_JOIN2(FUNC,grow)(h); \
+} \
+
+
+
+
+////////////////////////////////////////////////////////////////
+//
+// File Handling
+//
+
+
+typedef u32 gbFileMode;
+typedef enum gbFileModeFlag {
+	gbFileMode_Read       = GB_BIT(0),
+	gbFileMode_Write      = GB_BIT(1),
+	gbFileMode_Append     = GB_BIT(2),
+	gbFileMode_Rw         = GB_BIT(3),
+
+	gbFileMode_Modes = gbFileMode_Read | gbFileMode_Write | gbFileMode_Append | gbFileMode_Rw,
+} gbFileModeFlag;
+
+// NOTE(bill): Only used internally and for the file operations
+typedef enum gbSeekWhenceType {
+	gbSeekWhence_Begin   = 0,
+	gbSeekWhence_Current = 1,
+	gbSeekWhence_End     = 2,
+} gbSeekWhenceType;
+
+typedef enum gbFileError {
+	gbFileError_None,
+	gbFileError_Invalid,
+	gbFileError_Exists,
+	gbFileError_NotExists,
+	gbFileError_Permission,
+	gbFileError_TruncationFailure,
+} gbFileError;
+
+typedef union gbFileDescriptor {
+	void *  p;
+	intptr  i;
+	uintptr u;
+} gbFileDescriptor;
+
+typedef struct gbFileOperations gbFileOperations;
+
+#define GB_FILE_OPEN_PROC(name)     gbFileError name(gbFileDescriptor *fd, gbFileOperations const **ops, gbFileMode mode, char const *filename)
+#define GB_FILE_READ_AT_PROC(name)  b32         name(gbFileDescriptor fd, void *buffer, isize size, i64 offset, isize *bytes_read)
+#define GB_FILE_WRITE_AT_PROC(name) b32         name(gbFileDescriptor fd, void const *buffer, isize size, i64 offset, isize *bytes_written)
+#define GB_FILE_SEEK_PROC(name)     b32         name(gbFileDescriptor fd, i64 offset, gbSeekWhenceType whence, i64 *new_offset)
+#define GB_FILE_CLOSE_PROC(name)    void        name(gbFileDescriptor fd)
+typedef GB_FILE_OPEN_PROC(gbFileOpenProc);
+typedef GB_FILE_READ_AT_PROC(gbFileReadProc);
+typedef GB_FILE_WRITE_AT_PROC(gbFileWriteProc);
+typedef GB_FILE_SEEK_PROC(gbFileSeekProc);
+typedef GB_FILE_CLOSE_PROC(gbFileCloseProc);
+
+struct gbFileOperations {
+	gbFileReadProc  *read_at;
+	gbFileWriteProc *write_at;
+	gbFileSeekProc  *seek;
+	gbFileCloseProc *close;
+};
+
+extern gbFileOperations const gbDefaultFileOperations;
+
+
+// typedef struct gbDirInfo {
+// 	u8 *buf;
+// 	isize buf_count;
+// 	isize buf_pos;
+// } gbDirInfo;
+
+typedef u64 gbFileTime;
+
+typedef struct gbFile {
+	gbFileOperations const *ops;
+	gbFileDescriptor        fd;
+	char const *            filename;
+	gbFileTime              last_write_time;
+	// gbDirInfo *          dir_info; // TODO(bill): Get directory info
+} gbFile;
+
+// TODO(bill): gbAsyncFile
+
+typedef enum gbFileStandardType {
+	gbFileStandard_Input,
+	gbFileStandard_Output,
+	gbFileStandard_Error,
+
+	gbFileStandard_Count,
+} gbFileStandardType;
+
+GB_DEF gbFile *const gb_file_get_standard(gbFileStandardType std);
+
+GB_DEF gbFileError gb_file_create        (gbFile *file, char const *filename);
+GB_DEF gbFileError gb_file_open          (gbFile *file, char const *filename);
+GB_DEF gbFileError gb_file_open_mode     (gbFile *file, gbFileMode mode, char const *filename);
+GB_DEF gbFileError gb_file_new           (gbFile *file, gbFileDescriptor fd, gbFileOperations const *ops, char const *filename);
+GB_DEF b32         gb_file_read_at_check (gbFile *file, void *buffer, isize size, i64 offset, isize *bytes_read);
+GB_DEF b32         gb_file_write_at_check(gbFile *file, void const *buffer, isize size, i64 offset, isize *bytes_written);
+GB_DEF b32         gb_file_read_at       (gbFile *file, void *buffer, isize size, i64 offset);
+GB_DEF b32         gb_file_write_at      (gbFile *file, void const *buffer, isize size, i64 offset);
+GB_DEF i64         gb_file_seek          (gbFile *file, i64 offset);
+GB_DEF i64         gb_file_seek_to_end   (gbFile *file);
+GB_DEF i64         gb_file_skip          (gbFile *file, i64 bytes); // NOTE(bill): Skips a certain amount of bytes
+GB_DEF i64         gb_file_tell          (gbFile *file);
+GB_DEF gbFileError gb_file_close         (gbFile *file);
+GB_DEF b32         gb_file_read          (gbFile *file, void *buffer, isize size);
+GB_DEF b32         gb_file_write         (gbFile *file, void const *buffer, isize size);
+GB_DEF i64         gb_file_size          (gbFile *file);
+GB_DEF char const *gb_file_name          (gbFile *file);
+GB_DEF gbFileError gb_file_truncate      (gbFile *file, i64 size);
+GB_DEF b32         gb_file_has_changed   (gbFile *file); // NOTE(bill): Changed since lasted checked
+// TODO(bill):
+// gbFileError gb_file_temp(gbFile *file);
+//
+
+typedef struct gbFileContents {
+	gbAllocator allocator;
+	void *      data;
+	isize       size;
+} gbFileContents;
+
+
+GB_DEF gbFileContents gb_file_read_contents(gbAllocator a, b32 zero_terminate, char const *filepath);
+GB_DEF void           gb_file_free_contents(gbFileContents *fc);
+
+
+// TODO(bill): Should these have different na,es as they do not take in a gbFile * ???
+GB_DEF b32        gb_file_exists         (char const *filepath);
+GB_DEF gbFileTime gb_file_last_write_time(char const *filepath);
+GB_DEF b32        gb_file_copy           (char const *existing_filename, char const *new_filename, b32 fail_if_exists);
+GB_DEF b32        gb_file_move           (char const *existing_filename, char const *new_filename);
+
+
+#ifndef GB_PATH_SEPARATOR
+	#if defined(GB_SYSTEM_WINDOWS)
+		#define GB_PATH_SEPARATOR '\\'
+	#else
+		#define GB_PATH_SEPARATOR '/'
+	#endif
+#endif
+
+GB_DEF b32         gb_path_is_absolute  (char const *path);
+GB_DEF b32         gb_path_is_relative  (char const *path);
+GB_DEF b32         gb_path_is_root      (char const *path);
+GB_DEF char const *gb_path_base_name    (char const *path);
+GB_DEF char const *gb_path_extension    (char const *path);
+GB_DEF char *      gb_path_get_full_name(gbAllocator a, char const *path);
+
+
+////////////////////////////////////////////////////////////////
+//
+// Printing
+//
+//
+
+GB_DEF isize gb_printf        (char const *fmt, ...) GB_PRINTF_ARGS(1);
+GB_DEF isize gb_printf_va     (char const *fmt, va_list va);
+GB_DEF isize gb_printf_err    (char const *fmt, ...) GB_PRINTF_ARGS(1);
+GB_DEF isize gb_printf_err_va (char const *fmt, va_list va);
+GB_DEF isize gb_fprintf       (gbFile *f, char const *fmt, ...) GB_PRINTF_ARGS(2);
+GB_DEF isize gb_fprintf_va    (gbFile *f, char const *fmt, va_list va);
+
+GB_DEF char *gb_bprintf    (char const *fmt, ...) GB_PRINTF_ARGS(1); // NOTE(bill): A locally persisting buffer is used internally
+GB_DEF char *gb_bprintf_va (char const *fmt, va_list va);            // NOTE(bill): A locally persisting buffer is used internally
+GB_DEF isize gb_snprintf   (char *str, isize n, char const *fmt, ...) GB_PRINTF_ARGS(3);
+GB_DEF isize gb_snprintf_va(char *str, isize n, char const *fmt, va_list va);
+
+////////////////////////////////////////////////////////////////
+//
+// DLL Handling
+//
+//
+
+typedef void *gbDllHandle;
+typedef void (*gbDllProc)(void);
+
+GB_DEF gbDllHandle gb_dll_load        (char const *filepath);
+GB_DEF void        gb_dll_unload      (gbDllHandle dll);
+GB_DEF gbDllProc   gb_dll_proc_address(gbDllHandle dll, char const *proc_name);
+
+
+////////////////////////////////////////////////////////////////
+//
+// Time
+//
+//
+
+GB_DEF u64  gb_rdtsc       (void);
+GB_DEF f64  gb_time_now    (void); // NOTE(bill): This is only for relative time e.g. game loops
+GB_DEF u64  gb_utc_time_now(void); // NOTE(bill): Number of microseconds since 1601-01-01 UTC
+GB_DEF void gb_sleep_ms    (u32 ms);
+
+
+////////////////////////////////////////////////////////////////
+//
+// Miscellany
+//
+//
+
+typedef struct gbRandom {
+	u32 offsets[8];
+	u32 value;
+} gbRandom;
+
+// NOTE(bill): Generates from numerous sources to produce a decent pseudo-random seed
+GB_DEF void  gb_random_init          (gbRandom *r);
+GB_DEF u32   gb_random_gen_u32       (gbRandom *r);
+GB_DEF u32   gb_random_gen_u32_unique(gbRandom *r);
+GB_DEF u64   gb_random_gen_u64       (gbRandom *r); // NOTE(bill): (gb_random_gen_u32() << 32) | gb_random_gen_u32()
+GB_DEF isize gb_random_gen_isize     (gbRandom *r);
+GB_DEF i64   gb_random_range_i64     (gbRandom *r, i64 lower_inc, i64 higher_inc);
+GB_DEF isize gb_random_range_isize   (gbRandom *r, isize lower_inc, isize higher_inc);
+GB_DEF f64   gb_random_range_f64     (gbRandom *r, f64 lower_inc, f64 higher_inc);
+
+
+
+
+GB_DEF void gb_exit     (u32 code);
+GB_DEF void gb_yield    (void);
+GB_DEF void gb_set_env  (char const *name, char const *value);
+GB_DEF void gb_unset_env(char const *name);
+
+GB_DEF u16 gb_endian_swap16(u16 i);
+GB_DEF u32 gb_endian_swap32(u32 i);
+GB_DEF u64 gb_endian_swap64(u64 i);
+
+GB_DEF isize gb_count_set_bits(u64 mask);
+
+////////////////////////////////////////////////////////////////
+//
+// Platform Stuff
+//
+//
+
+#if defined(GB_PLATFORM)
+
+// NOTE(bill):
+// Coordiate system - +ve x - left to right
+//                  - +ve y - bottom to top
+//                  - Relative to window
+
+// TODO(bill): Proper documentation for this with code examples
+
+// Window Support - Complete
+// OS X Support - Missing:
+//     * Sofware framebuffer
+//     * (show|hide) window
+//     * show_cursor
+//     * toggle (fullscreen|borderless)
+//     * set window position
+//     * Clipboard
+//     * GameControllers
+// Linux Support - None
+// Other OS Support - None
+
+#ifndef GB_MAX_GAME_CONTROLLER_COUNT
+#define GB_MAX_GAME_CONTROLLER_COUNT 4
+#endif
+
+typedef enum gbKeyType {
+	gbKey_Unknown = 0,  // Unhandled key
+
+	// NOTE(bill): Allow the basic printable keys to be aliased with their chars
+	gbKey_0 = '0',
+	gbKey_1,
+	gbKey_2,
+	gbKey_3,
+	gbKey_4,
+	gbKey_5,
+	gbKey_6,
+	gbKey_7,
+	gbKey_8,
+	gbKey_9,
+
+	gbKey_A = 'A',
+	gbKey_B,
+	gbKey_C,
+	gbKey_D,
+	gbKey_E,
+	gbKey_F,
+	gbKey_G,
+	gbKey_H,
+	gbKey_I,
+	gbKey_J,
+	gbKey_K,
+	gbKey_L,
+	gbKey_M,
+	gbKey_N,
+	gbKey_O,
+	gbKey_P,
+	gbKey_Q,
+	gbKey_R,
+	gbKey_S,
+	gbKey_T,
+	gbKey_U,
+	gbKey_V,
+	gbKey_W,
+	gbKey_X,
+	gbKey_Y,
+	gbKey_Z,
+
+	gbKey_Lbracket  = '[',
+	gbKey_Rbracket  = ']',
+	gbKey_Semicolon = ';',
+	gbKey_Comma     = ',',
+	gbKey_Period    = '.',
+	gbKey_Quote     = '\'',
+	gbKey_Slash     = '/',
+	gbKey_Backslash = '\\',
+	gbKey_Grave     = '`',
+	gbKey_Equals    = '=',
+	gbKey_Minus     = '-',
+	gbKey_Space     = ' ',
+
+	gbKey__Pad = 128,   // NOTE(bill): make sure ASCII is reserved
+
+	gbKey_Escape,       // Escape
+	gbKey_Lcontrol,     // Left Control
+	gbKey_Lshift,       // Left Shift
+	gbKey_Lalt,         // Left Alt
+	gbKey_Lsystem,      // Left OS specific: window (Windows and Linux), apple/cmd (MacOS X), ...
+	gbKey_Rcontrol,     // Right Control
+	gbKey_Rshift,       // Right Shift
+	gbKey_Ralt,         // Right Alt
+	gbKey_Rsystem,      // Right OS specific: window (Windows and Linux), apple/cmd (MacOS X), ...
+	gbKey_Menu,         // Menu
+	gbKey_Return,       // Return
+	gbKey_Backspace,    // Backspace
+	gbKey_Tab,          // Tabulation
+	gbKey_Pageup,       // Page up
+	gbKey_Pagedown,     // Page down
+	gbKey_End,          // End
+	gbKey_Home,         // Home
+	gbKey_Insert,       // Insert
+	gbKey_Delete,       // Delete
+	gbKey_Plus,         // +
+	gbKey_Subtract,     // -
+	gbKey_Multiply,     // *
+	gbKey_Divide,       // /
+	gbKey_Left,         // Left arrow
+	gbKey_Right,        // Right arrow
+	gbKey_Up,           // Up arrow
+	gbKey_Down,         // Down arrow
+	gbKey_Numpad0,      // Numpad 0
+	gbKey_Numpad1,      // Numpad 1
+	gbKey_Numpad2,      // Numpad 2
+	gbKey_Numpad3,      // Numpad 3
+	gbKey_Numpad4,      // Numpad 4
+	gbKey_Numpad5,      // Numpad 5
+	gbKey_Numpad6,      // Numpad 6
+	gbKey_Numpad7,      // Numpad 7
+	gbKey_Numpad8,      // Numpad 8
+	gbKey_Numpad9,      // Numpad 9
+	gbKey_NumpadDot,    // Numpad .
+	gbKey_NumpadEnter,  // Numpad Enter
+	gbKey_F1,           // F1
+	gbKey_F2,           // F2
+	gbKey_F3,           // F3
+	gbKey_F4,           // F4
+	gbKey_F5,           // F5
+	gbKey_F6,           // F6
+	gbKey_F7,           // F7
+	gbKey_F8,           // F8
+	gbKey_F9,           // F8
+	gbKey_F10,          // F10
+	gbKey_F11,          // F11
+	gbKey_F12,          // F12
+	gbKey_F13,          // F13
+	gbKey_F14,          // F14
+	gbKey_F15,          // F15
+	gbKey_Pause,        // Pause
+
+	gbKey_Count,
+} gbKeyType;
+
+/* TODO(bill): Change name? */
+typedef u8 gbKeyState;
+typedef enum gbKeyStateFlag {
+	gbKeyState_Down     = GB_BIT(0),
+	gbKeyState_Pressed  = GB_BIT(1),
+	gbKeyState_Released = GB_BIT(2)
+} gbKeyStateFlag;
+
+GB_DEF void gb_key_state_update(gbKeyState *s, b32 is_down);
+
+typedef enum gbMouseButtonType {
+	gbMouseButton_Left,
+	gbMouseButton_Middle,
+	gbMouseButton_Right,
+	gbMouseButton_X1,
+	gbMouseButton_X2,
+
+	gbMouseButton_Count
+} gbMouseButtonType;
+
+typedef enum gbControllerAxisType {
+	gbControllerAxis_LeftX,
+	gbControllerAxis_LeftY,
+	gbControllerAxis_RightX,
+	gbControllerAxis_RightY,
+	gbControllerAxis_LeftTrigger,
+	gbControllerAxis_RightTrigger,
+
+	gbControllerAxis_Count
+} gbControllerAxisType;
+
+typedef enum gbControllerButtonType {
+	gbControllerButton_Up,
+	gbControllerButton_Down,
+	gbControllerButton_Left,
+	gbControllerButton_Right,
+	gbControllerButton_A,
+	gbControllerButton_B,
+	gbControllerButton_X,
+	gbControllerButton_Y,
+	gbControllerButton_LeftShoulder,
+	gbControllerButton_RightShoulder,
+	gbControllerButton_Back,
+	gbControllerButton_Start,
+	gbControllerButton_LeftThumb,
+	gbControllerButton_RightThumb,
+
+	gbControllerButton_Count
+} gbControllerButtonType;
+
+typedef struct gbGameController {
+	b16 is_connected, is_analog;
+
+	f32        axes[gbControllerAxis_Count];
+	gbKeyState buttons[gbControllerButton_Count];
+} gbGameController;
+
+#if defined(GB_SYSTEM_WINDOWS)
+	typedef struct _XINPUT_GAMEPAD XINPUT_GAMEPAD;
+	typedef struct _XINPUT_STATE   XINPUT_STATE;
+	typedef struct _XINPUT_VIBRATION XINPUT_VIBRATION;
+
+	#define GB_XINPUT_GET_STATE(name) unsigned long __stdcall name(unsigned long dwUserIndex, XINPUT_STATE *pState)
+	typedef GB_XINPUT_GET_STATE(gbXInputGetStateProc);
+
+	#define GB_XINPUT_SET_STATE(name) unsigned long __stdcall name(unsigned long dwUserIndex, XINPUT_VIBRATION *pVibration)
+	typedef GB_XINPUT_SET_STATE(gbXInputSetStateProc);
+#endif
+
+
+typedef enum gbWindowFlag {
+	gbWindow_Fullscreen        = GB_BIT(0),
+	gbWindow_Hidden            = GB_BIT(1),
+	gbWindow_Borderless        = GB_BIT(2),
+	gbWindow_Resizable         = GB_BIT(3),
+	gbWindow_Minimized         = GB_BIT(4),
+	gbWindow_Maximized         = GB_BIT(5),
+	gbWindow_FullscreenDesktop = gbWindow_Fullscreen | gbWindow_Borderless,
+} gbWindowFlag;
+
+typedef enum gbRendererType {
+	gbRenderer_Opengl,
+	gbRenderer_Software,
+
+	gbRenderer_Count,
+} gbRendererType;
+
+
+
+#if defined(GB_SYSTEM_WINDOWS) && !defined(_WINDOWS_)
+typedef struct tagBITMAPINFOHEADER {
+	unsigned long biSize;
+	long          biWidth;
+	long          biHeight;
+	u16           biPlanes;
+	u16           biBitCount;
+	unsigned long biCompression;
+	unsigned long biSizeImage;
+	long          biXPelsPerMeter;
+	long          biYPelsPerMeter;
+	unsigned long biClrUsed;
+	unsigned long biClrImportant;
+} BITMAPINFOHEADER, *PBITMAPINFOHEADER;
+typedef struct tagRGBQUAD {
+	u8 rgbBlue;
+	u8 rgbGreen;
+	u8 rgbRed;
+	u8 rgbReserved;
+} RGBQUAD;
+typedef struct tagBITMAPINFO {
+	BITMAPINFOHEADER bmiHeader;
+	RGBQUAD          bmiColors[1];
+} BITMAPINFO, *PBITMAPINFO;
+#endif
+
+typedef struct gbPlatform {
+	b32 is_initialized;
+
+	void *window_handle;
+	i32   window_x, window_y;
+	i32   window_width, window_height;
+	u32   window_flags;
+	b16   window_is_closed, window_has_focus;
+
+#if defined(GB_SYSTEM_WINDOWS)
+	void *win32_dc;
+#elif defined(GB_SYSTEM_OSX)
+	void *osx_autorelease_pool; // TODO(bill): Is this really needed?
+#endif
+
+	gbRendererType renderer_type;
+	union {
+		struct {
+			void *      context;
+			i32         major;
+			i32         minor;
+			b16         core, compatible;
+			gbDllHandle dll_handle;
+		} opengl;
+
+		// NOTE(bill): Software rendering
+		struct {
+#if defined(GB_SYSTEM_WINDOWS)
+			BITMAPINFO win32_bmi;
+#endif
+			void *     memory;
+			isize      memory_size;
+			i32        pitch;
+			i32        bits_per_pixel;
+		} sw_framebuffer;
+	};
+
+	gbKeyState keys[gbKey_Count];
+	struct {
+		gbKeyState control;
+		gbKeyState alt;
+		gbKeyState shift;
+	} key_modifiers;
+
+	Rune  char_buffer[256];
+	isize char_buffer_count;
+
+	b32 mouse_clip;
+	i32 mouse_x, mouse_y;
+	i32 mouse_dx, mouse_dy; // NOTE(bill): Not raw mouse movement
+	i32 mouse_raw_dx, mouse_raw_dy; // NOTE(bill): Raw mouse movement
+	f32 mouse_wheel_delta;
+	gbKeyState mouse_buttons[gbMouseButton_Count];
+
+	gbGameController game_controllers[GB_MAX_GAME_CONTROLLER_COUNT];
+
+	f64              curr_time;
+	f64              dt_for_frame;
+	b32              quit_requested;
+
+#if defined(GB_SYSTEM_WINDOWS)
+	struct {
+		gbXInputGetStateProc *get_state;
+		gbXInputSetStateProc *set_state;
+	} xinput;
+#endif
+} gbPlatform;
+
+
+typedef struct gbVideoMode {
+	i32 width, height;
+	i32 bits_per_pixel;
+} gbVideoMode;
+
+GB_DEF gbVideoMode gb_video_mode                     (i32 width, i32 height, i32 bits_per_pixel);
+GB_DEF b32         gb_video_mode_is_valid            (gbVideoMode mode);
+GB_DEF gbVideoMode gb_video_mode_get_desktop         (void);
+GB_DEF isize       gb_video_mode_get_fullscreen_modes(gbVideoMode *modes, isize max_mode_count); // NOTE(bill): returns mode count
+GB_DEF GB_COMPARE_PROC(gb_video_mode_cmp);     // NOTE(bill): Sort smallest to largest (Ascending)
+GB_DEF GB_COMPARE_PROC(gb_video_mode_dsc_cmp); // NOTE(bill): Sort largest to smallest (Descending)
+
+
+// NOTE(bill): Software rendering
+GB_DEF b32   gb_platform_init_with_software         (gbPlatform *p, char const *window_title, i32 width, i32 height, u32 window_flags);
+// NOTE(bill): OpenGL Rendering
+GB_DEF b32   gb_platform_init_with_opengl           (gbPlatform *p, char const *window_title, i32 width, i32 height, u32 window_flags, i32 major, i32 minor, b32 core, b32 compatible);
+GB_DEF void  gb_platform_update                     (gbPlatform *p);
+GB_DEF void  gb_platform_display                    (gbPlatform *p);
+GB_DEF void  gb_platform_destroy                    (gbPlatform *p);
+GB_DEF void  gb_platform_show_cursor                (gbPlatform *p, b32 show);
+GB_DEF void  gb_platform_set_mouse_position         (gbPlatform *p, i32 x, i32 y);
+GB_DEF void  gb_platform_set_controller_vibration   (gbPlatform *p, isize index, f32 left_motor, f32 right_motor);
+GB_DEF b32   gb_platform_has_clipboard_text         (gbPlatform *p);
+GB_DEF void  gb_platform_set_clipboard_text         (gbPlatform *p, char const *str);
+GB_DEF char *gb_platform_get_clipboard_text         (gbPlatform *p, gbAllocator a);
+GB_DEF void  gb_platform_set_window_position        (gbPlatform *p, i32 x, i32 y);
+GB_DEF void  gb_platform_set_window_title           (gbPlatform *p, char const *title, ...) GB_PRINTF_ARGS(2);
+GB_DEF void  gb_platform_toggle_fullscreen          (gbPlatform *p, b32 fullscreen_desktop);
+GB_DEF void  gb_platform_toggle_borderless          (gbPlatform *p);
+GB_DEF void  gb_platform_make_opengl_context_current(gbPlatform *p);
+GB_DEF void  gb_platform_show_window                (gbPlatform *p);
+GB_DEF void  gb_platform_hide_window                (gbPlatform *p);
+
+
+#endif // GB_PLATFORM
+
+#if defined(__cplusplus)
+}
+#endif
+
+#endif // GB_INCLUDE_GB_H
+
+
+
+
+
+
+////////////////////////////////////////////////////////////////
+//
+//
+//
+//
+//
+//
+//
+//
+//
+//
+//
+//
+//
+//
+//
+//
+//
+//
+//
+//
+//
+//
+// Implementation
+//
+//
+//
+//
+//
+//
+//
+//
+//
+//
+//
+//
+//
+//
+//
+//
+//
+//
+//
+//
+//
+// It's turtles all the way down!
+////////////////////////////////////////////////////////////////
+#if defined(GB_IMPLEMENTATION) && !defined(GB_IMPLEMENTATION_DONE)
+#define GB_IMPLEMENTATION_DONE
+
+#if defined(__cplusplus)
+extern "C" {
+#endif
+
+
+#if defined(GB_COMPILER_MSVC) && !defined(_WINDOWS_)
+	////////////////////////////////////////////////////////////////
+	//
+	// Bill's Mini Windows.h
+	//
+	//
+
+	#define WINAPI   __stdcall
+	#define WINAPIV  __cdecl
+	#define CALLBACK __stdcall
+	#define MAX_PATH 260
+	#define CCHDEVICENAME 32
+	#define CCHFORMNAME   32
+
+	typedef unsigned long DWORD;
+	typedef int WINBOOL;
+	#ifndef XFree86Server
+		#ifndef __OBJC__
+		typedef WINBOOL BOOL;
+		#else
+		#define BOOL WINBOOL
+		#endif
+	typedef unsigned char BYTE;
+	#endif
+	typedef unsigned short WORD;
+	typedef float FLOAT;
+	typedef int INT;
+	typedef unsigned int UINT;
+	typedef short SHORT;
+	typedef long LONG;
+	typedef long long LONGLONG;
+	typedef unsigned short USHORT;
+	typedef unsigned long ULONG;
+	typedef unsigned long long ULONGLONG;
+
+	typedef UINT WPARAM;
+	typedef LONG LPARAM;
+	typedef LONG LRESULT;
+	#ifndef _HRESULT_DEFINED
+	typedef LONG HRESULT;
+	#define _HRESULT_DEFINED
+	#endif
+	#ifndef XFree86Server
+	typedef WORD ATOM;
+	#endif /* XFree86Server */
+	typedef void *HANDLE;
+	typedef HANDLE HGLOBAL;
+	typedef HANDLE HLOCAL;
+	typedef HANDLE GLOBALHANDLE;
+	typedef HANDLE LOCALHANDLE;
+	typedef void *HGDIOBJ;
+
+	#define DECLARE_HANDLE(name) typedef HANDLE name
+	DECLARE_HANDLE(HACCEL);
+	DECLARE_HANDLE(HBITMAP);
+	DECLARE_HANDLE(HBRUSH);
+	DECLARE_HANDLE(HCOLORSPACE);
+	DECLARE_HANDLE(HDC);
+	DECLARE_HANDLE(HGLRC);
+	DECLARE_HANDLE(HDESK);
+	DECLARE_HANDLE(HENHMETAFILE);
+	DECLARE_HANDLE(HFONT);
+	DECLARE_HANDLE(HICON);
+	DECLARE_HANDLE(HKEY);
+	typedef HKEY *PHKEY;
+	DECLARE_HANDLE(HMENU);
+	DECLARE_HANDLE(HMETAFILE);
+	DECLARE_HANDLE(HINSTANCE);
+	typedef HINSTANCE HMODULE;
+	DECLARE_HANDLE(HPALETTE);
+	DECLARE_HANDLE(HPEN);
+	DECLARE_HANDLE(HRGN);
+	DECLARE_HANDLE(HRSRC);
+	DECLARE_HANDLE(HSTR);
+	DECLARE_HANDLE(HTASK);
+	DECLARE_HANDLE(HWND);
+	DECLARE_HANDLE(HWINSTA);
+	DECLARE_HANDLE(HKL);
+	DECLARE_HANDLE(HRAWINPUT);
+	DECLARE_HANDLE(HMONITOR);
+	#undef DECLARE_HANDLE
+
+	typedef int HFILE;
+	typedef HICON HCURSOR;
+	typedef DWORD COLORREF;
+	typedef int (WINAPI *FARPROC)();
+	typedef int (WINAPI *NEARPROC)();
+	typedef int (WINAPI *PROC)();
+	typedef LRESULT (CALLBACK *WNDPROC)(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam);
+
+	#if defined(_WIN64)
+	typedef unsigned __int64 ULONG_PTR;
+	typedef signed __int64 LONG_PTR;
+	#else
+	typedef unsigned long ULONG_PTR;
+	typedef signed long LONG_PTR;
+	#endif
+	typedef ULONG_PTR DWORD_PTR;
+
+	typedef struct tagRECT {
+		LONG left;
+		LONG top;
+		LONG right;
+		LONG bottom;
+	} RECT;
+	typedef struct tagRECTL {
+		LONG left;
+		LONG top;
+		LONG right;
+		LONG bottom;
+	} RECTL;
+	typedef struct tagPOINT {
+		LONG x;
+		LONG y;
+	} POINT;
+	typedef struct tagSIZE {
+		LONG cx;
+		LONG cy;
+	} SIZE;
+	typedef struct tagPOINTS {
+		SHORT x;
+		SHORT y;
+	} POINTS;
+	typedef struct _SECURITY_ATTRIBUTES {
+		DWORD  nLength;
+		HANDLE lpSecurityDescriptor;
+		BOOL   bInheritHandle;
+	} SECURITY_ATTRIBUTES;
+	typedef enum _LOGICAL_PROCESSOR_RELATIONSHIP {
+		RelationProcessorCore,
+		RelationNumaNode,
+		RelationCache,
+		RelationProcessorPackage,
+		RelationGroup,
+		RelationAll               = 0xffff
+	} LOGICAL_PROCESSOR_RELATIONSHIP;
+	typedef enum _PROCESSOR_CACHE_TYPE {
+		CacheUnified,
+		CacheInstruction,
+		CacheData,
+		CacheTrace
+	} PROCESSOR_CACHE_TYPE;
+	typedef struct _CACHE_DESCRIPTOR {
+		BYTE                 Level;
+		BYTE                 Associativity;
+		WORD                 LineSize;
+		DWORD                Size;
+		PROCESSOR_CACHE_TYPE Type;
+	} CACHE_DESCRIPTOR;
+	typedef struct _SYSTEM_LOGICAL_PROCESSOR_INFORMATION {
+		ULONG_PTR                       ProcessorMask;
+		LOGICAL_PROCESSOR_RELATIONSHIP Relationship;
+		union {
+			struct {
+				BYTE Flags;
+			} ProcessorCore;
+			struct {
+				DWORD NodeNumber;
+			} NumaNode;
+			CACHE_DESCRIPTOR Cache;
+			ULONGLONG        Reserved[2];
+		};
+	} SYSTEM_LOGICAL_PROCESSOR_INFORMATION;
+	typedef struct _MEMORY_BASIC_INFORMATION {
+		void *BaseAddress;
+		void *AllocationBase;
+		DWORD AllocationProtect;
+		usize RegionSize;
+		DWORD State;
+		DWORD Protect;
+		DWORD Type;
+	} MEMORY_BASIC_INFORMATION;
+	typedef struct _SYSTEM_INFO {
+		union {
+			DWORD   dwOemId;
+			struct {
+				WORD wProcessorArchitecture;
+				WORD wReserved;
+			};
+		};
+		DWORD     dwPageSize;
+		void *    lpMinimumApplicationAddress;
+		void *    lpMaximumApplicationAddress;
+		DWORD_PTR dwActiveProcessorMask;
+		DWORD     dwNumberOfProcessors;
+		DWORD     dwProcessorType;
+		DWORD     dwAllocationGranularity;
+		WORD      wProcessorLevel;
+		WORD      wProcessorRevision;
+	} SYSTEM_INFO;
+	typedef union _LARGE_INTEGER {
+		struct {
+			DWORD LowPart;
+			LONG  HighPart;
+		};
+		struct {
+			DWORD LowPart;
+			LONG  HighPart;
+		} u;
+		LONGLONG QuadPart;
+	} LARGE_INTEGER;
+	typedef union _ULARGE_INTEGER {
+		struct {
+			DWORD LowPart;
+			DWORD HighPart;
+		};
+		struct {
+			DWORD LowPart;
+			DWORD HighPart;
+		} u;
+		ULONGLONG QuadPart;
+	} ULARGE_INTEGER;
+
+	typedef struct _OVERLAPPED {
+		ULONG_PTR Internal;
+		ULONG_PTR InternalHigh;
+		union {
+			struct {
+				DWORD Offset;
+				DWORD OffsetHigh;
+			};
+			void *Pointer;
+		};
+		HANDLE hEvent;
+	} OVERLAPPED;
+	typedef struct _FILETIME {
+		DWORD dwLowDateTime;
+		DWORD dwHighDateTime;
+	} FILETIME;
+	typedef struct _WIN32_FIND_DATAW {
+		DWORD    dwFileAttributes;
+		FILETIME ftCreationTime;
+		FILETIME ftLastAccessTime;
+		FILETIME ftLastWriteTime;
+		DWORD    nFileSizeHigh;
+		DWORD    nFileSizeLow;
+		DWORD    dwReserved0;
+		DWORD    dwReserved1;
+		wchar_t  cFileName[MAX_PATH];
+		wchar_t  cAlternateFileName[14];
+	} WIN32_FIND_DATAW;
+	typedef struct _WIN32_FILE_ATTRIBUTE_DATA {
+		DWORD    dwFileAttributes;
+		FILETIME ftCreationTime;
+		FILETIME ftLastAccessTime;
+		FILETIME ftLastWriteTime;
+		DWORD    nFileSizeHigh;
+		DWORD    nFileSizeLow;
+	} WIN32_FILE_ATTRIBUTE_DATA;
+	typedef enum _GET_FILEEX_INFO_LEVELS {
+		GetFileExInfoStandard,
+		GetFileExMaxInfoLevel
+	} GET_FILEEX_INFO_LEVELS;
+	typedef struct tagRAWINPUTHEADER {
+		DWORD  dwType;
+		DWORD  dwSize;
+		HANDLE hDevice;
+		WPARAM wParam;
+	} RAWINPUTHEADER;
+	typedef struct tagRAWINPUTDEVICE {
+		USHORT usUsagePage;
+		USHORT usUsage;
+		DWORD  dwFlags;
+		HWND   hwndTarget;
+	} RAWINPUTDEVICE;
+	typedef struct tagRAWMOUSE {
+		WORD usFlags;
+		union {
+			ULONG ulButtons;
+			struct {
+				WORD usButtonFlags;
+				WORD usButtonData;
+			};
+		};
+		ULONG ulRawButtons;
+		LONG  lLastX;
+		LONG  lLastY;
+		ULONG ulExtraInformation;
+	} RAWMOUSE;
+	typedef struct tagRAWKEYBOARD {
+		WORD  MakeCode;
+		WORD  Flags;
+		WORD  Reserved;
+		WORD  VKey;
+		UINT  Message;
+		ULONG ExtraInformation;
+	} RAWKEYBOARD;
+	typedef struct tagRAWHID {
+		DWORD dwSizeHid;
+		DWORD dwCount;
+		BYTE  bRawData[1];
+	} RAWHID;
+	typedef struct tagRAWINPUT {
+		RAWINPUTHEADER header;
+		union {
+			RAWMOUSE    mouse;
+			RAWKEYBOARD keyboard;
+			RAWHID      hid;
+		} data;
+	} RAWINPUT;
+	typedef struct tagWNDCLASSEXW {
+		UINT           cbSize;
+		UINT           style;
+		WNDPROC        lpfnWndProc;
+		INT            cbClsExtra;
+		INT            cbWndExtra;
+		HINSTANCE      hInstance;
+		HICON          hIcon;
+		HCURSOR        hCursor;
+		HANDLE         hbrBackground;
+		wchar_t const *lpszMenuName;
+		wchar_t const *lpszClassName;
+		HICON          hIconSm;
+	} WNDCLASSEXW;
+	typedef struct _POINTL {
+		LONG x;
+		LONG y;
+	} POINTL;
+	typedef struct _devicemodew {
+		wchar_t dmDeviceName[CCHDEVICENAME];
+		WORD    dmSpecVersion;
+		WORD    dmDriverVersion;
+		WORD    dmSize;
+		WORD    dmDriverExtra;
+		DWORD   dmFields;
+		union {
+			struct {
+				short dmOrientation;
+				short dmPaperSize;
+				short dmPaperLength;
+				short dmPaperWidth;
+				short dmScale;
+				short dmCopies;
+				short dmDefaultSource;
+				short dmPrintQuality;
+			};
+			struct {
+				POINTL dmPosition;
+				DWORD  dmDisplayOrientation;
+				DWORD  dmDisplayFixedOutput;
+			};
+		};
+		short   dmColor;
+		short   dmDuplex;
+		short   dmYResolution;
+		short   dmTTOption;
+		short   dmCollate;
+		wchar_t dmFormName[CCHFORMNAME];
+		WORD    dmLogPixels;
+		DWORD   dmBitsPerPel;
+		DWORD   dmPelsWidth;
+		DWORD   dmPelsHeight;
+		union {
+			DWORD dmDisplayFlags;
+			DWORD dmNup;
+		};
+		DWORD dmDisplayFrequency;
+	#if (WINVER >= 0x0400)
+		DWORD dmICMMethod;
+		DWORD dmICMIntent;
+		DWORD dmMediaType;
+		DWORD dmDitherType;
+		DWORD dmReserved1;
+		DWORD dmReserved2;
+	#if (WINVER >= 0x0500) || (_WIN32_WINNT >= 0x0400)
+		DWORD dmPanningWidth;
+		DWORD dmPanningHeight;
+	#endif
+	#endif
+	} DEVMODEW;
+	typedef struct tagPIXELFORMATDESCRIPTOR {
+		WORD  nSize;
+		WORD  nVersion;
+		DWORD dwFlags;
+		BYTE  iPixelType;
+		BYTE  cColorBits;
+		BYTE  cRedBits;
+		BYTE  cRedShift;
+		BYTE  cGreenBits;
+		BYTE  cGreenShift;
+		BYTE  cBlueBits;
+		BYTE  cBlueShift;
+		BYTE  cAlphaBits;
+		BYTE  cAlphaShift;
+		BYTE  cAccumBits;
+		BYTE  cAccumRedBits;
+		BYTE  cAccumGreenBits;
+		BYTE  cAccumBlueBits;
+		BYTE  cAccumAlphaBits;
+		BYTE  cDepthBits;
+		BYTE  cStencilBits;
+		BYTE  cAuxBuffers;
+		BYTE  iLayerType;
+		BYTE  bReserved;
+		DWORD dwLayerMask;
+		DWORD dwVisibleMask;
+		DWORD dwDamageMask;
+	} PIXELFORMATDESCRIPTOR;
+	typedef struct tagMSG {     // msg
+		HWND   hwnd;
+		UINT   message;
+		WPARAM wParam;
+		LPARAM lParam;
+		DWORD time;
+		POINT pt;
+	} MSG;
+	typedef struct tagWINDOWPLACEMENT {
+		UINT length;
+		UINT flags;
+		UINT showCmd;
+		POINT ptMinPosition;
+		POINT ptMaxPosition;
+		RECT rcNormalPosition;
+	} WINDOWPLACEMENT;
+	typedef struct tagMONITORINFO {
+		DWORD cbSize;
+		RECT  rcMonitor;
+		RECT  rcWork;
+		DWORD dwFlags;
+	} MONITORINFO;
+
+
+
+	#define INFINITE 0xffffffffl
+	#define INVALID_HANDLE_VALUE ((void *)(intptr)(-1))
+
+
+	typedef DWORD WINAPI THREAD_START_ROUTINE(void *parameter);
+
+	GB_DLL_IMPORT DWORD   WINAPI GetLastError       (void);
+	GB_DLL_IMPORT BOOL    WINAPI CloseHandle        (HANDLE object);
+	GB_DLL_IMPORT HANDLE  WINAPI CreateSemaphoreA   (SECURITY_ATTRIBUTES *semaphore_attributes, LONG initial_count,
+	                                                 LONG maximum_count, char const *name);
+	GB_DLL_IMPORT BOOL    WINAPI ReleaseSemaphore   (HANDLE semaphore, LONG release_count, LONG *previous_count);
+	GB_DLL_IMPORT DWORD   WINAPI WaitForSingleObject(HANDLE handle, DWORD milliseconds);
+	GB_DLL_IMPORT HANDLE  WINAPI CreateThread       (SECURITY_ATTRIBUTES *semaphore_attributes, usize stack_size,
+	                                                 THREAD_START_ROUTINE *start_address, void *parameter,
+	                                                 DWORD creation_flags, DWORD *thread_id);
+	GB_DLL_IMPORT DWORD   WINAPI GetThreadId        (HANDLE handle);
+	GB_DLL_IMPORT void    WINAPI RaiseException     (DWORD, DWORD, DWORD, ULONG_PTR const *);
+
+
+
+	GB_DLL_IMPORT BOOL      WINAPI GetLogicalProcessorInformation(SYSTEM_LOGICAL_PROCESSOR_INFORMATION *buffer, DWORD *return_length);
+	GB_DLL_IMPORT DWORD_PTR WINAPI SetThreadAffinityMask(HANDLE thread, DWORD_PTR check_mask);
+	GB_DLL_IMPORT HANDLE    WINAPI GetCurrentThread(void);
+
+	#define PAGE_NOACCESS          0x01
+	#define PAGE_READONLY          0x02
+	#define PAGE_READWRITE         0x04
+	#define PAGE_WRITECOPY         0x08
+	#define PAGE_EXECUTE           0x10
+	#define PAGE_EXECUTE_READ      0x20
+	#define PAGE_EXECUTE_READWRITE 0x40
+	#define PAGE_EXECUTE_WRITECOPY 0x80
+	#define PAGE_GUARD            0x100
+	#define PAGE_NOCACHE          0x200
+	#define PAGE_WRITECOMBINE     0x400
+
+	#define MEM_COMMIT           0x1000
+	#define MEM_RESERVE          0x2000
+	#define MEM_DECOMMIT         0x4000
+	#define MEM_RELEASE          0x8000
+	#define MEM_FREE            0x10000
+	#define MEM_PRIVATE         0x20000
+	#define MEM_MAPPED          0x40000
+	#define MEM_RESET           0x80000
+	#define MEM_TOP_DOWN       0x100000
+	#define MEM_LARGE_PAGES  0x20000000
+	#define MEM_4MB_PAGES    0x80000000
+
+
+
+
+	GB_DLL_IMPORT void * WINAPI VirtualAlloc (void *addr, usize size, DWORD allocation_type, DWORD protect);
+	GB_DLL_IMPORT usize  WINAPI VirtualQuery (void const *address, MEMORY_BASIC_INFORMATION *buffer, usize length);
+	GB_DLL_IMPORT BOOL   WINAPI VirtualFree  (void *address, usize size, DWORD free_type);
+	GB_DLL_IMPORT void   WINAPI GetSystemInfo(SYSTEM_INFO *system_info);
+
+
+	#ifndef VK_UNKNOWN
+	#define VK_UNKNOWN 0
+	#define VK_LBUTTON  0x01
+	#define VK_RBUTTON  0x02
+	#define VK_CANCEL   0x03
+	#define VK_MBUTTON  0x04
+	#define VK_XBUTTON1 0x05
+	#define VK_XBUTTON2 0x06
+	#define VK_BACK 0x08
+	#define VK_TAB 0x09
+	#define VK_CLEAR 0x0C
+	#define VK_RETURN 0x0D
+	#define VK_SHIFT 0x10
+	#define VK_CONTROL 0x11 // CTRL key
+	#define VK_MENU 0x12 // ALT key
+	#define VK_PAUSE 0x13 // PAUSE key
+	#define VK_CAPITAL 0x14 // CAPS LOCK key
+	#define VK_KANA 0x15 // Input Method Editor (IME) Kana mode
+	#define VK_HANGUL 0x15 // IME Hangul mode
+	#define VK_JUNJA 0x17 // IME Junja mode
+	#define VK_FINAL 0x18 // IME final mode
+	#define VK_HANJA 0x19 // IME Hanja mode
+	#define VK_KANJI 0x19 // IME Kanji mode
+	#define VK_ESCAPE 0x1B // ESC key
+	#define VK_CONVERT 0x1C // IME convert
+	#define VK_NONCONVERT 0x1D // IME nonconvert
+	#define VK_ACCEPT 0x1E // IME accept
+	#define VK_MODECHANGE 0x1F // IME mode change request
+	#define VK_SPACE 0x20 // SPACE key
+	#define VK_PRIOR 0x21 // PAGE UP key
+	#define VK_NEXT 0x22 // PAGE DOWN key
+	#define VK_END 0x23 // END key
+	#define VK_HOME 0x24 // HOME key
+	#define VK_LEFT 0x25 // LEFT ARROW key
+	#define VK_UP 0x26 // UP ARROW key
+	#define VK_RIGHT 0x27 // RIGHT ARROW key
+	#define VK_DOWN 0x28 // DOWN ARROW key
+	#define VK_SELECT 0x29 // SELECT key
+	#define VK_PRINT 0x2A // PRINT key
+	#define VK_EXECUTE 0x2B // EXECUTE key
+	#define VK_SNAPSHOT 0x2C // PRINT SCREEN key
+	#define VK_INSERT 0x2D // INS key
+	#define VK_DELETE 0x2E // DEL key
+	#define VK_HELP 0x2F // HELP key
+	#define VK_0 0x30
+	#define VK_1 0x31
+	#define VK_2 0x32
+	#define VK_3 0x33
+	#define VK_4 0x34
+	#define VK_5 0x35
+	#define VK_6 0x36
+	#define VK_7 0x37
+	#define VK_8 0x38
+	#define VK_9 0x39
+	#define VK_A 0x41
+	#define VK_B 0x42
+	#define VK_C 0x43
+	#define VK_D 0x44
+	#define VK_E 0x45
+	#define VK_F 0x46
+	#define VK_G 0x47
+	#define VK_H 0x48
+	#define VK_I 0x49
+	#define VK_J 0x4A
+	#define VK_K 0x4B
+	#define VK_L 0x4C
+	#define VK_M 0x4D
+	#define VK_N 0x4E
+	#define VK_O 0x4F
+	#define VK_P 0x50
+	#define VK_Q 0x51
+	#define VK_R 0x52
+	#define VK_S 0x53
+	#define VK_T 0x54
+	#define VK_U 0x55
+	#define VK_V 0x56
+	#define VK_W 0x57
+	#define VK_X 0x58
+	#define VK_Y 0x59
+	#define VK_Z 0x5A
+	#define VK_LWIN 0x5B // Left Windows key (Microsoft Natural keyboard)
+	#define VK_RWIN 0x5C // Right Windows key (Natural keyboard)
+	#define VK_APPS 0x5D // Applications key (Natural keyboard)
+	#define VK_SLEEP 0x5F // Computer Sleep key
+	// Num pad keys
+	#define VK_NUMPAD0 0x60
+	#define VK_NUMPAD1 0x61
+	#define VK_NUMPAD2 0x62
+	#define VK_NUMPAD3 0x63
+	#define VK_NUMPAD4 0x64
+	#define VK_NUMPAD5 0x65
+	#define VK_NUMPAD6 0x66
+	#define VK_NUMPAD7 0x67
+	#define VK_NUMPAD8 0x68
+	#define VK_NUMPAD9 0x69
+	#define VK_MULTIPLY 0x6A
+	#define VK_ADD 0x6B
+	#define VK_SEPARATOR 0x6C
+	#define VK_SUBTRACT 0x6D
+	#define VK_DECIMAL 0x6E
+	#define VK_DIVIDE 0x6F
+	#define VK_F1 0x70
+	#define VK_F2 0x71
+	#define VK_F3 0x72
+	#define VK_F4 0x73
+	#define VK_F5 0x74
+	#define VK_F6 0x75
+	#define VK_F7 0x76
+	#define VK_F8 0x77
+	#define VK_F9 0x78
+	#define VK_F10 0x79
+	#define VK_F11 0x7A
+	#define VK_F12 0x7B
+	#define VK_F13 0x7C
+	#define VK_F14 0x7D
+	#define VK_F15 0x7E
+	#define VK_F16 0x7F
+	#define VK_F17 0x80
+	#define VK_F18 0x81
+	#define VK_F19 0x82
+	#define VK_F20 0x83
+	#define VK_F21 0x84
+	#define VK_F22 0x85
+	#define VK_F23 0x86
+	#define VK_F24 0x87
+	#define VK_NUMLOCK 0x90
+	#define VK_SCROLL 0x91
+	#define VK_LSHIFT 0xA0
+	#define VK_RSHIFT 0xA1
+	#define VK_LCONTROL 0xA2
+	#define VK_RCONTROL 0xA3
+	#define VK_LMENU 0xA4
+	#define VK_RMENU 0xA5
+	#define VK_BROWSER_BACK 0xA6 // Windows 2000/XP: Browser Back key
+	#define VK_BROWSER_FORWARD 0xA7 // Windows 2000/XP: Browser Forward key
+	#define VK_BROWSER_REFRESH 0xA8 // Windows 2000/XP: Browser Refresh key
+	#define VK_BROWSER_STOP 0xA9 // Windows 2000/XP: Browser Stop key
+	#define VK_BROWSER_SEARCH 0xAA // Windows 2000/XP: Browser Search key
+	#define VK_BROWSER_FAVORITES 0xAB // Windows 2000/XP: Browser Favorites key
+	#define VK_BROWSER_HOME 0xAC // Windows 2000/XP: Browser Start and Home key
+	#define VK_VOLUME_MUTE 0xAD // Windows 2000/XP: Volume Mute key
+	#define VK_VOLUME_DOWN 0xAE // Windows 2000/XP: Volume Down key
+	#define VK_VOLUME_UP 0xAF // Windows 2000/XP: Volume Up key
+	#define VK_MEDIA_NEXT_TRACK 0xB0 // Windows 2000/XP: Next Track key
+	#define VK_MEDIA_PREV_TRACK 0xB1 // Windows 2000/XP: Previous Track key
+	#define VK_MEDIA_STOP 0xB2 // Windows 2000/XP: Stop Media key
+	#define VK_MEDIA_PLAY_PAUSE 0xB3 // Windows 2000/XP: Play/Pause Media key
+	#define VK_MEDIA_LAUNCH_MAIL 0xB4 // Windows 2000/XP: Start Mail key
+	#define VK_MEDIA_LAUNCH_MEDIA_SELECT 0xB5 // Windows 2000/XP: Select Media key
+	#define VK_MEDIA_LAUNCH_APP1 0xB6 // VK_LAUNCH_APP1 (B6) Windows 2000/XP: Start Application 1 key
+	#define VK_MEDIA_LAUNCH_APP2 0xB7 // VK_LAUNCH_APP2 (B7) Windows 2000/XP: Start Application 2 key
+	#define VK_OEM_1 0xBA
+	#define VK_OEM_PLUS 0xBB
+	#define VK_OEM_COMMA 0xBC
+	#define VK_OEM_MINUS 0xBD
+	#define VK_OEM_PERIOD 0xBE
+	#define VK_OEM_2 0xBF
+	#define VK_OEM_3 0xC0
+	#define VK_OEM_4 0xDB
+	#define VK_OEM_5 0xDC
+	#define VK_OEM_6 0xDD
+	#define VK_OEM_7 0xDE
+	#define VK_OEM_8 0xDF
+	#define VK_OEM_102 0xE2
+	#define VK_PROCESSKEY 0xE5
+	#define VK_PACKET 0xE7
+	#define VK_ATTN 0xF6 // Attn key
+	#define VK_CRSEL 0xF7 // CrSel key
+	#define VK_EXSEL 0xF8 // ExSel key
+	#define VK_EREOF 0xF9 // Erase EOF key
+	#define VK_PLAY 0xFA // Play key
+	#define VK_ZOOM 0xFB // Zoom key
+	#define VK_NONAME 0xFC // Reserved for future use
+	#define VK_PA1 0xFD // VK_PA1 (FD) PA1 key
+	#define VK_OEM_CLEAR 0xFE // Clear key
+	#endif // VK_UNKNOWN
+
+
+
+	#define GENERIC_READ             0x80000000
+	#define GENERIC_WRITE            0x40000000
+	#define GENERIC_EXECUTE          0x20000000
+	#define GENERIC_ALL              0x10000000
+	#define FILE_SHARE_READ          0x00000001
+	#define FILE_SHARE_WRITE         0x00000002
+	#define FILE_SHARE_DELETE        0x00000004
+	#define CREATE_NEW               1
+	#define CREATE_ALWAYS            2
+	#define OPEN_EXISTING            3
+	#define OPEN_ALWAYS              4
+	#define TRUNCATE_EXISTING        5
+	#define FILE_ATTRIBUTE_READONLY  0x00000001
+	#define FILE_ATTRIBUTE_NORMAL    0x00000080
+	#define FILE_ATTRIBUTE_TEMPORARY 0x00000100
+	#define ERROR_FILE_NOT_FOUND     2l
+	#define ERROR_ACCESS_DENIED      5L
+	#define ERROR_NO_MORE_FILES      18l
+	#define ERROR_FILE_EXISTS        80l
+	#define ERROR_ALREADY_EXISTS     183l
+	#define STD_INPUT_HANDLE         ((DWORD)-10)
+	#define STD_OUTPUT_HANDLE        ((DWORD)-11)
+	#define STD_ERROR_HANDLE         ((DWORD)-12)
+
+	GB_DLL_IMPORT BOOL   WINAPI SetFilePointerEx(HANDLE file, LARGE_INTEGER distance_to_move,
+	                                             LARGE_INTEGER *new_file_pointer, DWORD move_method);
+	GB_DLL_IMPORT BOOL   WINAPI ReadFile        (HANDLE file, void *buffer, DWORD bytes_to_read, DWORD *bytes_read, OVERLAPPED *overlapped);
+	GB_DLL_IMPORT BOOL   WINAPI WriteFile       (HANDLE file, void const *buffer, DWORD bytes_to_write, DWORD *bytes_written, OVERLAPPED *overlapped);
+	GB_DLL_IMPORT HANDLE WINAPI CreateFileW     (wchar_t const *path, DWORD desired_access, DWORD share_mode,
+	                                             SECURITY_ATTRIBUTES *, DWORD creation_disposition,
+	                                             DWORD flags_and_attributes, HANDLE template_file);
+	GB_DLL_IMPORT HANDLE WINAPI GetStdHandle    (DWORD std_handle);
+	GB_DLL_IMPORT BOOL   WINAPI GetFileSizeEx   (HANDLE file, LARGE_INTEGER *size);
+	GB_DLL_IMPORT BOOL   WINAPI SetEndOfFile    (HANDLE file);
+	GB_DLL_IMPORT HANDLE WINAPI FindFirstFileW  (wchar_t const *path, WIN32_FIND_DATAW *data);
+	GB_DLL_IMPORT BOOL   WINAPI FindClose       (HANDLE find_file);
+	GB_DLL_IMPORT BOOL   WINAPI GetFileAttributesExW(wchar_t const *path, GET_FILEEX_INFO_LEVELS info_level_id, WIN32_FILE_ATTRIBUTE_DATA *data);
+	GB_DLL_IMPORT BOOL   WINAPI CopyFileW(wchar_t const *old_f, wchar_t const *new_f, BOOL fail_if_exists);
+	GB_DLL_IMPORT BOOL   WINAPI MoveFileW(wchar_t const *old_f, wchar_t const *new_f);
+
+	GB_DLL_IMPORT HMODULE WINAPI LoadLibraryA  (char const *filename);
+	GB_DLL_IMPORT BOOL    WINAPI FreeLibrary   (HMODULE module);
+	GB_DLL_IMPORT FARPROC WINAPI GetProcAddress(HMODULE module, char const *name);
+
+	GB_DLL_IMPORT BOOL WINAPI QueryPerformanceFrequency(LARGE_INTEGER *frequency);
+	GB_DLL_IMPORT BOOL WINAPI QueryPerformanceCounter  (LARGE_INTEGER *counter);
+	GB_DLL_IMPORT void WINAPI GetSystemTimeAsFileTime  (FILETIME *system_time_as_file_time);
+	GB_DLL_IMPORT void WINAPI Sleep(DWORD milliseconds);
+	GB_DLL_IMPORT void WINAPI ExitProcess(UINT exit_code);
+
+	GB_DLL_IMPORT BOOL WINAPI SetEnvironmentVariableA(char const *name, char const *value);
+
+
+	#define WM_NULL                   0x0000
+	#define WM_CREATE                 0x0001
+	#define WM_DESTROY                0x0002
+	#define WM_MOVE                   0x0003
+	#define WM_SIZE                   0x0005
+	#define WM_ACTIVATE               0x0006
+	#define WM_SETFOCUS               0x0007
+	#define WM_KILLFOCUS              0x0008
+	#define WM_ENABLE                 0x000A
+	#define WM_SETREDRAW              0x000B
+	#define WM_SETTEXT                0x000C
+	#define WM_GETTEXT                0x000D
+	#define WM_GETTEXTLENGTH          0x000E
+	#define WM_PAINT                  0x000F
+	#define WM_CLOSE                  0x0010
+	#define WM_QUERYENDSESSION        0x0011
+	#define WM_QUERYOPEN              0x0013
+	#define WM_ENDSESSION             0x0016
+	#define WM_QUIT                   0x0012
+	#define WM_ERASEBKGND             0x0014
+	#define WM_SYSCOLORCHANGE         0x0015
+	#define WM_SHOWWINDOW             0x0018
+	#define WM_WININICHANGE           0x001A
+	#define WM_SETTINGCHANGE          WM_WININICHANGE
+	#define WM_DEVMODECHANGE          0x001B
+	#define WM_ACTIVATEAPP            0x001C
+	#define WM_FONTCHANGE             0x001D
+	#define WM_TIMECHANGE             0x001E
+	#define WM_CANCELMODE             0x001F
+	#define WM_SETCURSOR              0x0020
+	#define WM_MOUSEACTIVATE          0x0021
+	#define WM_CHILDACTIVATE          0x0022
+	#define WM_QUEUESYNC              0x0023
+	#define WM_GETMINMAXINFO          0x0024
+	#define WM_PAINTICON              0x0026
+	#define WM_ICONERASEBKGND         0x0027
+	#define WM_NEXTDLGCTL             0x0028
+	#define WM_SPOOLERSTATUS          0x002A
+	#define WM_DRAWITEM               0x002B
+	#define WM_MEASUREITEM            0x002C
+	#define WM_DELETEITEM             0x002D
+	#define WM_VKEYTOITEM             0x002E
+	#define WM_CHARTOITEM             0x002F
+	#define WM_SETFONT                0x0030
+	#define WM_GETFONT                0x0031
+	#define WM_SETHOTKEY              0x0032
+	#define WM_GETHOTKEY              0x0033
+	#define WM_QUERYDRAGICON          0x0037
+	#define WM_COMPAREITEM            0x0039
+	#define WM_GETOBJECT              0x003D
+	#define WM_COMPACTING             0x0041
+	#define WM_COMMNOTIFY             0x0044  /* no longer suported */
+	#define WM_WINDOWPOSCHANGING      0x0046
+	#define WM_WINDOWPOSCHANGED       0x0047
+	#define WM_POWER                  0x0048
+	#define WM_COPYDATA               0x004A
+	#define WM_CANCELJOURNAL          0x004B
+	#define WM_NOTIFY                 0x004E
+	#define WM_INPUTLANGCHANGEREQUEST 0x0050
+	#define WM_INPUTLANGCHANGE        0x0051
+	#define WM_TCARD                  0x0052
+	#define WM_HELP                   0x0053
+	#define WM_USERCHANGED            0x0054
+	#define WM_NOTIFYFORMAT           0x0055
+	#define WM_CONTEXTMENU            0x007B
+	#define WM_STYLECHANGING          0x007C
+	#define WM_STYLECHANGED           0x007D
+	#define WM_DISPLAYCHANGE          0x007E
+	#define WM_GETICON                0x007F
+	#define WM_SETICON                0x0080
+	#define WM_INPUT                  0x00FF
+	#define WM_KEYFIRST               0x0100
+	#define WM_KEYDOWN                0x0100
+	#define WM_KEYUP                  0x0101
+	#define WM_CHAR                   0x0102
+	#define WM_DEADCHAR               0x0103
+	#define WM_SYSKEYDOWN             0x0104
+	#define WM_SYSKEYUP               0x0105
+	#define WM_SYSCHAR                0x0106
+	#define WM_SYSDEADCHAR            0x0107
+	#define WM_UNICHAR                0x0109
+	#define WM_KEYLAST                0x0109
+	#define WM_APP                    0x8000
+
+
+	#define RID_INPUT 0x10000003
+
+	#define RIM_TYPEMOUSE    0x00000000
+	#define RIM_TYPEKEYBOARD 0x00000001
+	#define RIM_TYPEHID      0x00000002
+
+	#define RI_KEY_MAKE    0x0000
+	#define RI_KEY_BREAK   0x0001
+	#define RI_KEY_E0      0x0002
+	#define RI_KEY_E1      0x0004
+	#define RI_MOUSE_WHEEL 0x0400
+
+	#define RIDEV_NOLEGACY 0x00000030
+
+	#define MAPVK_VK_TO_VSC    0
+	#define MAPVK_VSC_TO_VK    1
+	#define MAPVK_VK_TO_CHAR   2
+	#define MAPVK_VSC_TO_VK_EX 3
+
+	GB_DLL_IMPORT BOOL WINAPI RegisterRawInputDevices(RAWINPUTDEVICE const *raw_input_devices, UINT num_devices, UINT size);
+	GB_DLL_IMPORT UINT WINAPI GetRawInputData(HRAWINPUT raw_input, UINT ui_command, void *data, UINT *size, UINT size_header);
+	GB_DLL_IMPORT UINT WINAPI MapVirtualKeyW(UINT code, UINT map_type);
+
+
+	#define CS_DBLCLKS 		0x0008
+	#define CS_VREDRAW 		0x0001
+	#define CS_HREDRAW 		0x0002
+
+	#define MB_OK              0x0000l
+	#define MB_ICONSTOP        0x0010l
+	#define MB_YESNO           0x0004l
+	#define MB_HELP            0x4000l
+	#define MB_ICONEXCLAMATION 0x0030l
+
+	GB_DLL_IMPORT LRESULT WINAPI DefWindowProcW(HWND wnd, UINT msg, WPARAM wParam, LPARAM lParam);
+	GB_DLL_IMPORT HGDIOBJ WINAPI GetStockObject(int object);
+	GB_DLL_IMPORT HMODULE WINAPI GetModuleHandleW(wchar_t const *);
+	GB_DLL_IMPORT ATOM    WINAPI RegisterClassExW(WNDCLASSEXW const *wcx); // u16 == ATOM
+	GB_DLL_IMPORT int     WINAPI MessageBoxW(void *wnd, wchar_t const *text, wchar_t const *caption, unsigned int type);
+
+
+	#define DM_BITSPERPEL 0x00040000l
+	#define DM_PELSWIDTH  0x00080000l
+	#define DM_PELSHEIGHT 0x00100000l
+
+	#define CDS_FULLSCREEN 0x4
+	#define DISP_CHANGE_SUCCESSFUL 0
+	#define IDYES 6
+
+	#define WS_VISIBLE          0x10000000
+	#define WS_THICKFRAME       0x00040000
+	#define WS_MAXIMIZE         0x01000000
+	#define WS_MAXIMIZEBOX      0x00010000
+	#define WS_MINIMIZE         0x20000000
+	#define WS_MINIMIZEBOX      0x00020000
+	#define WS_POPUP            0x80000000
+	#define WS_OVERLAPPED	    0
+	#define WS_OVERLAPPEDWINDOW	0xcf0000
+	#define CW_USEDEFAULT       0x80000000
+	#define WS_BORDER           0x800000
+	#define WS_CAPTION          0xc00000
+	#define WS_SYSMENU          0x80000
+
+	#define HWND_NOTOPMOST (HWND)(-2)
+	#define HWND_TOPMOST   (HWND)(-1)
+	#define HWND_TOP       (HWND)(+0)
+	#define HWND_BOTTOM    (HWND)(+1)
+	#define SWP_NOSIZE          0x0001
+	#define SWP_NOMOVE          0x0002
+	#define SWP_NOZORDER        0x0004
+	#define SWP_NOREDRAW        0x0008
+	#define SWP_NOACTIVATE      0x0010
+	#define SWP_FRAMECHANGED    0x0020
+	#define SWP_SHOWWINDOW      0x0040
+	#define SWP_HIDEWINDOW      0x0080
+	#define SWP_NOCOPYBITS      0x0100
+	#define SWP_NOOWNERZORDER   0x0200
+	#define SWP_NOSENDCHANGING  0x0400
+
+	#define SW_HIDE             0
+	#define SW_SHOWNORMAL       1
+	#define SW_NORMAL           1
+	#define SW_SHOWMINIMIZED    2
+	#define SW_SHOWMAXIMIZED    3
+	#define SW_MAXIMIZE         3
+	#define SW_SHOWNOACTIVATE   4
+	#define SW_SHOW             5
+	#define SW_MINIMIZE         6
+	#define SW_SHOWMINNOACTIVE  7
+	#define SW_SHOWNA           8
+	#define SW_RESTORE          9
+	#define SW_SHOWDEFAULT      10
+	#define SW_FORCEMINIMIZE    11
+	#define SW_MAX              11
+
+	#define ENUM_CURRENT_SETTINGS  cast(DWORD)-1
+	#define ENUM_REGISTRY_SETTINGS cast(DWORD)-2
+
+	GB_DLL_IMPORT LONG    WINAPI ChangeDisplaySettingsW(DEVMODEW *dev_mode, DWORD flags);
+	GB_DLL_IMPORT BOOL    WINAPI AdjustWindowRect(RECT *rect, DWORD style, BOOL enu);
+	GB_DLL_IMPORT HWND    WINAPI CreateWindowExW(DWORD ex_style, wchar_t const *class_name, wchar_t const *window_name,
+	                                             DWORD style, int x, int y, int width, int height, HWND wnd_parent,
+	                                             HMENU menu, HINSTANCE instance, void *param);
+	GB_DLL_IMPORT HMODULE  WINAPI GetModuleHandleW(wchar_t const *);
+	GB_DLL_IMPORT HDC             GetDC(HANDLE);
+	GB_DLL_IMPORT BOOL     WINAPI GetWindowPlacement(HWND hWnd, WINDOWPLACEMENT *lpwndpl);
+	GB_DLL_IMPORT BOOL            GetMonitorInfoW(HMONITOR hMonitor, MONITORINFO *lpmi);
+	GB_DLL_IMPORT HMONITOR        MonitorFromWindow(HWND hwnd, DWORD dwFlags);
+	GB_DLL_IMPORT LONG     WINAPI SetWindowLongW(HWND hWnd, int nIndex, LONG dwNewLong);
+	GB_DLL_IMPORT BOOL     WINAPI SetWindowPos(HWND hWnd, HWND hWndInsertAfter, int X, int Y, int cx, int cy, UINT uFlags);
+	GB_DLL_IMPORT BOOL     WINAPI SetWindowPlacement(HWND hWnd, WINDOWPLACEMENT const *lpwndpl);
+	GB_DLL_IMPORT BOOL     WINAPI ShowWindow(HWND hWnd, int nCmdShow);
+	GB_DLL_IMPORT LONG_PTR WINAPI GetWindowLongPtrW(HWND wnd, int index);
+
+	GB_DLL_IMPORT BOOL           EnumDisplaySettingsW(wchar_t const *lpszDeviceName, DWORD iModeNum, DEVMODEW *lpDevMode);
+	GB_DLL_IMPORT void *  WINAPI GlobalLock(HGLOBAL hMem);
+	GB_DLL_IMPORT BOOL    WINAPI GlobalUnlock(HGLOBAL hMem);
+	GB_DLL_IMPORT HGLOBAL WINAPI GlobalAlloc(UINT uFlags, usize dwBytes);
+	GB_DLL_IMPORT HANDLE  WINAPI GetClipboardData(UINT uFormat);
+	GB_DLL_IMPORT BOOL    WINAPI IsClipboardFormatAvailable(UINT format);
+	GB_DLL_IMPORT BOOL    WINAPI OpenClipboard(HWND hWndNewOwner);
+	GB_DLL_IMPORT BOOL    WINAPI EmptyClipboard(void);
+	GB_DLL_IMPORT BOOL    WINAPI CloseClipboard(void);
+	GB_DLL_IMPORT HANDLE  WINAPI SetClipboardData(UINT uFormat, HANDLE hMem);
+
+	#define PFD_TYPE_RGBA             0
+	#define PFD_TYPE_COLORINDEX       1
+	#define PFD_MAIN_PLANE            0
+	#define PFD_OVERLAY_PLANE         1
+	#define PFD_UNDERLAY_PLANE        (-1)
+	#define PFD_DOUBLEBUFFER          1
+	#define PFD_STEREO                2
+	#define PFD_DRAW_TO_WINDOW        4
+	#define PFD_DRAW_TO_BITMAP        8
+	#define PFD_SUPPORT_GDI           16
+	#define PFD_SUPPORT_OPENGL        32
+	#define PFD_GENERIC_FORMAT        64
+	#define PFD_NEED_PALETTE          128
+	#define PFD_NEED_SYSTEM_PALETTE   0x00000100
+	#define PFD_SWAP_EXCHANGE         0x00000200
+	#define PFD_SWAP_COPY             0x00000400
+	#define PFD_SWAP_LAYER_BUFFERS    0x00000800
+	#define PFD_GENERIC_ACCELERATED   0x00001000
+	#define PFD_DEPTH_DONTCARE        0x20000000
+	#define PFD_DOUBLEBUFFER_DONTCARE 0x40000000
+	#define PFD_STEREO_DONTCARE       0x80000000
+
+	#define GWLP_USERDATA -21
+
+	#define GWL_ID    -12
+	#define GWL_STYLE -16
+
+	GB_DLL_IMPORT BOOL  WINAPI SetPixelFormat   (HDC hdc, int pixel_format, PIXELFORMATDESCRIPTOR const *pfd);
+	GB_DLL_IMPORT int   WINAPI ChoosePixelFormat(HDC hdc, PIXELFORMATDESCRIPTOR const *pfd);
+	GB_DLL_IMPORT HGLRC WINAPI wglCreateContext (HDC hdc);
+	GB_DLL_IMPORT BOOL  WINAPI wglMakeCurrent   (HDC hdc, HGLRC hglrc);
+	GB_DLL_IMPORT PROC  WINAPI wglGetProcAddress(char const *str);
+	GB_DLL_IMPORT BOOL  WINAPI wglDeleteContext (HGLRC hglrc);
+
+	GB_DLL_IMPORT BOOL     WINAPI SetForegroundWindow(HWND hWnd);
+	GB_DLL_IMPORT HWND     WINAPI SetFocus(HWND hWnd);
+	GB_DLL_IMPORT LONG_PTR WINAPI SetWindowLongPtrW(HWND hWnd, int nIndex, LONG_PTR dwNewLong);
+	GB_DLL_IMPORT BOOL     WINAPI GetClientRect(HWND hWnd, RECT *lpRect);
+	GB_DLL_IMPORT BOOL     WINAPI IsIconic(HWND hWnd);
+	GB_DLL_IMPORT HWND     WINAPI GetFocus(void);
+	GB_DLL_IMPORT int      WINAPI ShowCursor(BOOL bShow);
+	GB_DLL_IMPORT SHORT    WINAPI GetAsyncKeyState(int key);
+	GB_DLL_IMPORT BOOL     WINAPI GetCursorPos(POINT *lpPoint);
+	GB_DLL_IMPORT BOOL     WINAPI SetCursorPos(int x, int y);
+	GB_DLL_IMPORT BOOL            ScreenToClient(HWND hWnd, POINT *lpPoint);
+	GB_DLL_IMPORT BOOL            ClientToScreen(HWND hWnd, POINT *lpPoint);
+	GB_DLL_IMPORT BOOL     WINAPI MoveWindow(HWND hWnd, int X, int Y, int nWidth, int nHeight, BOOL bRepaint);
+	GB_DLL_IMPORT BOOL     WINAPI SetWindowTextW(HWND hWnd, wchar_t const *lpString);
+	GB_DLL_IMPORT DWORD    WINAPI GetWindowLongW(HWND hWnd, int nIndex);
+
+
+
+
+	#define PM_NOREMOVE 0
+	#define PM_REMOVE   1
+
+	GB_DLL_IMPORT BOOL    WINAPI PeekMessageW(MSG *lpMsg, HWND hWnd, UINT wMsgFilterMin, UINT wMsgFilterMax, UINT wRemoveMsg);
+	GB_DLL_IMPORT BOOL    WINAPI TranslateMessage(MSG const *lpMsg);
+	GB_DLL_IMPORT LRESULT WINAPI DispatchMessageW(MSG const *lpMsg);
+
+	typedef  enum
+	{
+		DIB_RGB_COLORS  = 0x00,
+		DIB_PAL_COLORS  = 0x01,
+		DIB_PAL_INDICES = 0x02
+	} DIBColors;
+
+	#define SRCCOPY     (u32)0x00CC0020
+	#define SRCPAINT    (u32)0x00EE0086
+	#define SRCAND      (u32)0x008800C6
+	#define SRCINVERT   (u32)0x00660046
+	#define SRCERASE    (u32)0x00440328
+	#define NOTSRCCOPY  (u32)0x00330008
+	#define NOTSRCERASE (u32)0x001100A6
+	#define MERGECOPY   (u32)0x00C000CA
+	#define MERGEPAINT  (u32)0x00BB0226
+	#define PATCOPY     (u32)0x00F00021
+	#define PATPAINT    (u32)0x00FB0A09
+	#define PATINVERT   (u32)0x005A0049
+	#define DSTINVERT   (u32)0x00550009
+	#define BLACKNESS   (u32)0x00000042
+	#define WHITENESS   (u32)0x00FF0062
+
+	GB_DLL_IMPORT BOOL WINAPI SwapBuffers(HDC hdc);
+	GB_DLL_IMPORT BOOL WINAPI DestroyWindow(HWND hWnd);
+	GB_DLL_IMPORT int         StretchDIBits(HDC hdc, int XDest, int YDest, int nDestWidth, int nDestHeight,
+	                                        int XSrc, int YSrc, int nSrcWidth, int nSrcHeight,
+	                                        void const *lpBits, /*BITMAPINFO*/void const *lpBitsInfo, UINT iUsage, DWORD dwRop);
+	                                        // IMPORTANT TODO(bill): FIX THIS!!!!
+#endif // Bill's Mini Windows.h
+
+
+
+#if defined(__GCC__) || defined(__GNUC__)
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Wattributes"
+#pragma GCC diagnostic ignored "-Wmissing-braces"
+#endif
+
+#if defined(_MSC_VER)
+#pragma warning(push)
+#pragma warning(disable:4201)
+#pragma warning(disable:4127) // Conditional expression is constant
+#endif
+
+void gb_assert_handler(char const *condition, char const *file, i32 line, char const *msg, ...) {
+	gb_printf_err("%s:%d: Assert Failure: ", file, line);
+	if (condition)
+		gb_printf_err( "`%s` ", condition);
+	if (msg) {
+		va_list va;
+		va_start(va, msg);
+		gb_printf_err_va(msg, va);
+		va_end(va);
+	}
+	gb_printf_err("\n");
+}
+
+b32 gb_is_power_of_two(isize x) {
+	if (x <= 0)
+		return false;
+	return !(x & (x-1));
+}
+
+gb_inline void *gb_align_forward(void *ptr, isize alignment) {
+	uintptr p;
+	isize modulo;
+
+	GB_ASSERT(gb_is_power_of_two(alignment));
+
+	p = cast(uintptr)ptr;
+	modulo = p & (alignment-1);
+	if (modulo) p += (alignment - modulo);
+	return cast(void *)p;
+}
+
+
+
+gb_inline void *      gb_pointer_add      (void *ptr, isize bytes)             { return cast(void *)(cast(u8 *)ptr + bytes); }
+gb_inline void *      gb_pointer_sub      (void *ptr, isize bytes)             { return cast(void *)(cast(u8 *)ptr - bytes); }
+gb_inline void const *gb_pointer_add_const(void const *ptr, isize bytes)       { return cast(void const *)(cast(u8 const *)ptr + bytes); }
+gb_inline void const *gb_pointer_sub_const(void const *ptr, isize bytes)       { return cast(void const *)(cast(u8 const *)ptr - bytes); }
+gb_inline isize       gb_pointer_diff     (void const *begin, void const *end) { return cast(isize)(cast(u8 const *)end - cast(u8 const *)begin); }
+
+gb_inline void gb_zero_size(void *ptr, isize size) { gb_memset(ptr, 0, size); }
+
+
+#if defined(_MSC_VER)
+#pragma intrinsic(__movsb)
+#endif
+
+gb_inline void *gb_memcopy(void *dest, void const *source, isize n) {
+#if defined(_MSC_VER)
+	// TODO(bill): Is this good enough?
+	__movsb(cast(u8 *gb_restrict)dest, cast(u8 *gb_restrict)source, n);
+#elif defined(GB_CPU_X86)
+	__asm__ __volatile__("rep movsb" : "+D"(cast(u8 *gb_restrict)dest), "+S"(cast(u8 *gb_restrict)source), "+c"(n) : : "memory");
+#else
+	u8 *d = cast(u8 *)dest;
+	u8 const *s = cast(u8 const *)source;
+	u32 w, x;
+
+	for (; cast(uintptr)s % 4 && n; n--)
+		*d++ = *s++;
+
+	if (cast(uintptr)d % 4 == 0) {
+		for (; n >= 16;
+		     s += 16, d += 16, n -= 16) {
+			*cast(u32 *)(d+ 0) = *cast(u32 *)(s+ 0);
+			*cast(u32 *)(d+ 4) = *cast(u32 *)(s+ 4);
+			*cast(u32 *)(d+ 8) = *cast(u32 *)(s+ 8);
+			*cast(u32 *)(d+12) = *cast(u32 *)(s+12);
+		}
+		if (n & 8) {
+			*cast(u32 *)(d+0) = *cast(u32 *)(s+0);
+			*cast(u32 *)(d+4) = *cast(u32 *)(s+4);
+			d += 8;
+			s += 8;
+		}
+		if (n&4) {
+			*cast(u32 *)(d+0) = *cast(u32 *)(s+0);
+			d += 4;
+			s += 4;
+		}
+		if (n&2) {
+			*d++ = *s++; *d++ = *s++;
+		}
+		if (n&1) {
+			*d = *s;
+		}
+		return dest;
+	}
+
+	if (n >= 32) {
+	#if __BYTE_ORDER == __BIG_ENDIAN
+	#define LS <<
+	#define RS >>
+	#else
+	#define LS >>
+	#define RS <<
+	#endif
+		switch (cast(uintptr)d % 4) {
+		case 1: {
+			w = *cast(u32 *)s;
+			*d++ = *s++;
+			*d++ = *s++;
+			*d++ = *s++;
+			n -= 3;
+			while (n > 16) {
+				x = *cast(u32 *)(s+1);
+				*cast(u32 *)(d+0)  = (w LS 24) | (x RS 8);
+				w = *cast(u32 *)(s+5);
+				*cast(u32 *)(d+4)  = (x LS 24) | (w RS 8);
+				x = *cast(u32 *)(s+9);
+				*cast(u32 *)(d+8)  = (w LS 24) | (x RS 8);
+				w = *cast(u32 *)(s+13);
+				*cast(u32 *)(d+12) = (x LS 24) | (w RS 8);
+
+				s += 16;
+				d += 16;
+				n -= 16;
+			}
+		} break;
+		case 2: {
+			w = *cast(u32 *)s;
+			*d++ = *s++;
+			*d++ = *s++;
+			n -= 2;
+			while (n > 17) {
+				x = *cast(u32 *)(s+2);
+				*cast(u32 *)(d+0)  = (w LS 16) | (x RS 16);
+				w = *cast(u32 *)(s+6);
+				*cast(u32 *)(d+4)  = (x LS 16) | (w RS 16);
+				x = *cast(u32 *)(s+10);
+				*cast(u32 *)(d+8)  = (w LS 16) | (x RS 16);
+				w = *cast(u32 *)(s+14);
+				*cast(u32 *)(d+12) = (x LS 16) | (w RS 16);
+
+				s += 16;
+				d += 16;
+				n -= 16;
+			}
+		} break;
+		case 3: {
+			w = *cast(u32 *)s;
+			*d++ = *s++;
+			n -= 1;
+			while (n > 18) {
+				x = *cast(u32 *)(s+3);
+				*cast(u32 *)(d+0)  = (w LS 8) | (x RS 24);
+				w = *cast(u32 *)(s+7);
+				*cast(u32 *)(d+4)  = (x LS 8) | (w RS 24);
+				x = *cast(u32 *)(s+11);
+				*cast(u32 *)(d+8)  = (w LS 8) | (x RS 24);
+				w = *cast(u32 *)(s+15);
+				*cast(u32 *)(d+12) = (x LS 8) | (w RS 24);
+
+				s += 16;
+				d += 16;
+				n -= 16;
+			}
+		} break;
+		default: break; // NOTE(bill): Do nowt!
+		}
+	#undef LS
+	#undef RS
+		if (n & 16) {
+			*d++ = *s++; *d++ = *s++; *d++ = *s++; *d++ = *s++;
+			*d++ = *s++; *d++ = *s++; *d++ = *s++; *d++ = *s++;
+			*d++ = *s++; *d++ = *s++; *d++ = *s++; *d++ = *s++;
+			*d++ = *s++; *d++ = *s++; *d++ = *s++; *d++ = *s++;
+		}
+		if (n & 8) {
+			*d++ = *s++; *d++ = *s++; *d++ = *s++; *d++ = *s++;
+			*d++ = *s++; *d++ = *s++; *d++ = *s++; *d++ = *s++;
+		}
+		if (n & 4)
+			*d++ = *s++; *d++ = *s++; *d++ = *s++; *d++ = *s++;
+		if (n & 2)
+			*d++ = *s++; *d++ = *s++;
+		if (n & 1)
+			*d = *s;
+	}
+
+#endif
+	return dest;
+}
+
+gb_inline void *gb_memmove(void *dest, void const *source, isize n) {
+	u8 *d = cast(u8 *)dest;
+	u8 const *s = cast(u8 const *)source;
+
+	if (d == s)
+		return d;
+	if (s+n <= d || d+n <= s) // NOTE(bill): Non-overlapping
+		return gb_memcopy(d, s, n);
+
+	if (d < s) {
+		if (cast(uintptr)s % gb_size_of(isize) == cast(uintptr)d % gb_size_of(isize)) {
+			while (cast(uintptr)d % gb_size_of(isize)) {
+				if (!n--) return dest;
+				*d++ = *s++;
+			}
+			while (n>=gb_size_of(isize)) {
+				*cast(isize *)d = *cast(isize *)s;
+				n -= gb_size_of(isize);
+				d += gb_size_of(isize);
+				s += gb_size_of(isize);
+			}
+		}
+		for (; n; n--) *d++ = *s++;
+	} else {
+		if ((cast(uintptr)s % gb_size_of(isize)) == (cast(uintptr)d % gb_size_of(isize))) {
+			while (cast(uintptr)(d+n) % gb_size_of(isize)) {
+				if (!n--)
+					return dest;
+				d[n] = s[n];
+			}
+			while (n >= gb_size_of(isize)) {
+				n -= gb_size_of(isize);
+				*cast(isize *)(d+n) = *cast(isize *)(s+n);
+			}
+		}
+		while (n) n--, d[n] = s[n];
+	}
+
+	return dest;
+}
+
+gb_inline void *gb_memset(void *dest, u8 c, isize n) {
+	u8 *s = cast(u8 *)dest;
+	isize k;
+	u32 c32 = ((u32)-1)/255 * c;
+
+	if (n == 0)
+		return dest;
+	s[0] = s[n-1] = c;
+	if (n < 3)
+		return dest;
+	s[1] = s[n-2] = c;
+	s[2] = s[n-3] = c;
+	if (n < 7)
+		return dest;
+	s[3] = s[n-4] = c;
+	if (n < 9)
+		return dest;
+
+	k = -cast(intptr)s & 3;
+	s += k;
+	n -= k;
+	n &= -4;
+
+	*cast(u32 *)(s+0) = c32;
+	*cast(u32 *)(s+n-4) = c32;
+	if (n < 9)
+		return dest;
+	*cast(u32 *)(s +  4)    = c32;
+	*cast(u32 *)(s +  8)    = c32;
+	*cast(u32 *)(s+n-12) = c32;
+	*cast(u32 *)(s+n- 8) = c32;
+	if (n < 25)
+		return dest;
+	*cast(u32 *)(s + 12) = c32;
+	*cast(u32 *)(s + 16) = c32;
+	*cast(u32 *)(s + 20) = c32;
+	*cast(u32 *)(s + 24) = c32;
+	*cast(u32 *)(s+n-28) = c32;
+	*cast(u32 *)(s+n-24) = c32;
+	*cast(u32 *)(s+n-20) = c32;
+	*cast(u32 *)(s+n-16) = c32;
+
+	k = 24 + (cast(uintptr)s & 4);
+	s += k;
+	n -= k;
+
+
+	{
+		u64 c64 = (cast(u64)c32 << 32) | c32;
+		while (n > 31) {
+			*cast(u64 *)(s+0) = c64;
+			*cast(u64 *)(s+8) = c64;
+			*cast(u64 *)(s+16) = c64;
+			*cast(u64 *)(s+24) = c64;
+
+			n -= 32;
+			s += 32;
+		}
+	}
+
+	return dest;
+}
+
+gb_inline i32 gb_memcompare(void const *s1, void const *s2, isize size) {
+	// TODO(bill): Heavily optimize
+
+	u8 const *s1p8 = cast(u8 const *)s1;
+	u8 const *s2p8 = cast(u8 const *)s2;
+	while (size--) {
+		if (*s1p8 != *s2p8)
+			return (*s1p8 - *s2p8);
+		s1p8++, s2p8++;
+	}
+	return 0;
+}
+
+void gb_memswap(void *i, void *j, isize size) {
+	if (i == j) return;
+
+	if (size == 4) {
+		gb_swap(u32, *cast(u32 *)i, *cast(u32 *)j);
+	} else if (size == 8) {
+		gb_swap(u64, *cast(u64 *)i, *cast(u64 *)j);
+	} else if (size < 8) {
+		u8 *a = cast(u8 *)i;
+		u8 *b = cast(u8 *)j;
+		if (a != b) {
+			while (size--) {
+				gb_swap(u8, *a, *b);
+				a++, b++;
+			}
+		}
+	} else {
+		char buffer[256];
+
+		// TODO(bill): Is the recursion ever a problem?
+		while (size > gb_size_of(buffer)) {
+			gb_memswap(i, j, gb_size_of(buffer));
+			i = gb_pointer_add(i, gb_size_of(buffer));
+			j = gb_pointer_add(j, gb_size_of(buffer));
+			size -= gb_size_of(buffer);
+		}
+
+		gb_memcopy(buffer, i,      size);
+		gb_memcopy(i,      j,      size);
+		gb_memcopy(j,      buffer, size);
+	}
+}
+
+#define GB__ONES        (cast(usize)-1/U8_MAX)
+#define GB__HIGHS       (GB__ONES * (U8_MAX/2+1))
+#define GB__HAS_ZERO(x) ((x)-GB__ONES & ~(x) & GB__HIGHS)
+
+
+void const *gb_memchr(void const *data, u8 c, isize n) {
+	u8 const *s = cast(u8 const *)data;
+	while ((cast(uintptr)s & (sizeof(usize)-1)) &&
+	       n && *s != c) {
+		s++;
+		n--;
+	}
+	if (n && *s != c) {
+		isize const *w;
+		isize k = GB__ONES * c;
+		w = cast(isize const *)s;
+		while (n >= gb_size_of(isize) && !GB__HAS_ZERO(*w ^ k)) {
+			w++;
+			n -= gb_size_of(isize);
+		}
+		s = cast(u8 const *)w;
+		while (n && *s != c) {
+			s++;
+			n--;
+		}
+	}
+
+	return n ? cast(void const *)s : NULL;
+}
+
+
+void const *gb_memrchr(void const *data, u8 c, isize n) {
+	u8 const *s = cast(u8 const *)data;
+	while (n--) {
+		if (s[n] == c)
+			return cast(void const *)(s + n);
+	}
+	return NULL;
+}
+
+
+
+gb_inline void *gb_alloc_align (gbAllocator a, isize size, isize alignment)                                { return a.proc(a.data, gbAllocation_Alloc, size, alignment, NULL, 0, GB_DEFAULT_ALLOCATOR_FLAGS); }
+gb_inline void *gb_alloc       (gbAllocator a, isize size)                                                 { return gb_alloc_align(a, size, GB_DEFAULT_MEMORY_ALIGNMENT); }
+gb_inline void  gb_free        (gbAllocator a, void *ptr)                                                  { a.proc(a.data, gbAllocation_Free, 0, 0, ptr, 0, GB_DEFAULT_ALLOCATOR_FLAGS); }
+gb_inline void  gb_free_all    (gbAllocator a)                                                             { a.proc(a.data, gbAllocation_FreeAll, 0, 0, NULL, 0, GB_DEFAULT_ALLOCATOR_FLAGS); }
+gb_inline void *gb_resize      (gbAllocator a, void *ptr, isize old_size, isize new_size)                  { return gb_resize_align(a, ptr, old_size, new_size, GB_DEFAULT_MEMORY_ALIGNMENT); }
+gb_inline void *gb_resize_align(gbAllocator a, void *ptr, isize old_size, isize new_size, isize alignment) { return a.proc(a.data, gbAllocation_Resize, new_size, alignment, ptr, old_size, GB_DEFAULT_ALLOCATOR_FLAGS); }
+
+gb_inline void *gb_alloc_copy      (gbAllocator a, void const *src, isize size)                  { return gb_memcopy(gb_alloc(a, size), src, size); }
+gb_inline void *gb_alloc_copy_align(gbAllocator a, void const *src, isize size, isize alignment) { return gb_memcopy(gb_alloc_align(a, size, alignment), src, size); }
+
+gb_inline char *gb_alloc_str(gbAllocator a, char const *str) {
+	return gb_alloc_str_len(a, str, gb_strlen(str));
+}
+
+gb_inline char *gb_alloc_str_len(gbAllocator a, char const *str, isize len) {
+	char *result;
+	result = cast(char *)gb_alloc_copy(a, str, len+1);
+	result[len] = '\0';
+	return result;
+}
+
+
+gb_inline void *gb_default_resize_align(gbAllocator a, void *old_memory, isize old_size, isize new_size, isize alignment) {
+	if (!old_memory) return gb_alloc_align(a, new_size, alignment);
+
+	if (new_size == 0) {
+		gb_free(a, old_memory);
+		return NULL;
+	}
+
+	if (new_size < old_size)
+		new_size = old_size;
+
+	if (old_size == new_size) {
+		return old_memory;
+	} else {
+		void *new_memory = gb_alloc_align(a, new_size, alignment);
+		if (!new_memory) return NULL;
+		gb_memmove(new_memory, old_memory, gb_min(new_size, old_size));
+		gb_free(a, old_memory);
+		return new_memory;
+	}
+}
+
+
+
+
+////////////////////////////////////////////////////////////////
+//
+// Concurrency
+//
+//
+// IMPORTANT TODO(bill): Use compiler intrinsics for the atomics
+
+#if defined(GB_COMPILER_MSVC) && !defined(GB_COMPILER_CLANG)
+gb_inline i32  gb_atomic32_load (gbAtomic32 const volatile *a)      { return a->value;  }
+gb_inline void gb_atomic32_store(gbAtomic32 volatile *a, i32 value) { a->value = value; }
+
+gb_inline i32 gb_atomic32_compare_exchange(gbAtomic32 volatile *a, i32 expected, i32 desired) {
+	return _InterlockedCompareExchange(cast(long volatile *)a, desired, expected);
+}
+gb_inline i32 gb_atomic32_exchanged(gbAtomic32 volatile *a, i32 desired) {
+	return _InterlockedExchange(cast(long volatile *)a, desired);
+}
+gb_inline i32 gb_atomic32_fetch_add(gbAtomic32 volatile *a, i32 operand) {
+	return _InterlockedExchangeAdd(cast(long volatile *)a, operand);
+}
+gb_inline i32 gb_atomic32_fetch_and(gbAtomic32 volatile *a, i32 operand) {
+	return _InterlockedAnd(cast(long volatile *)a, operand);
+}
+gb_inline i32 gb_atomic32_fetch_or(gbAtomic32 volatile *a, i32 operand) {
+	return _InterlockedOr(cast(long volatile *)a, operand);
+}
+
+gb_inline i64 gb_atomic64_load(gbAtomic64 const volatile *a) {
+#if defined(GB_ARCH_64_BIT)
+	return a->value;
+#elif GB_CPU_X86
+	// NOTE(bill): The most compatible way to get an atomic 64-bit load on x86 is with cmpxchg8b
+	i64 result;
+	__asm {
+		mov esi, a;
+		mov ebx, eax;
+		mov ecx, edx;
+		lock cmpxchg8b [esi];
+		mov dword ptr result, eax;
+		mov dword ptr result[4], edx;
+	}
+	return result;
+#else
+#error TODO(bill): atomics for this CPU
+#endif
+}
+
+gb_inline void gb_atomic64_store(gbAtomic64 volatile *a, i64 value) {
+#if defined(GB_ARCH_64_BIT)
+	a->value = value;
+#elif GB_CPU_X86
+	// NOTE(bill): The most compatible way to get an atomic 64-bit store on x86 is with cmpxchg8b
+	__asm {
+		mov esi, a;
+		mov ebx, dword ptr value;
+		mov ecx, dword ptr value[4];
+	retry:
+		cmpxchg8b [esi];
+		jne retry;
+	}
+#else
+#error TODO(bill): atomics for this CPU
+#endif
+}
+
+gb_inline i64 gb_atomic64_compare_exchange(gbAtomic64 volatile *a, i64 expected, i64 desired) {
+	return _InterlockedCompareExchange64(cast(i64 volatile *)a, desired, expected);
+}
+
+gb_inline i64 gb_atomic64_exchanged(gbAtomic64 volatile *a, i64 desired) {
+#if defined(GB_ARCH_64_BIT)
+	return _InterlockedExchange64(cast(i64 volatile *)a, desired);
+#elif GB_CPU_X86
+	i64 expected = a->value;
+	for (;;) {
+		i64 original = _InterlockedCompareExchange64(cast(i64 volatile *)a, desired, expected);
+		if (original == expected)
+			return original;
+		expected = original;
+	}
+#else
+#error TODO(bill): atomics for this CPU
+#endif
+}
+
+gb_inline i64 gb_atomic64_fetch_add(gbAtomic64 volatile *a, i64 operand) {
+#if defined(GB_ARCH_64_BIT)
+	return _InterlockedExchangeAdd64(cast(i64 volatile *)a, operand);
+#elif GB_CPU_X86
+	i64 expected = a->value;
+	for (;;) {
+		i64 original = _InterlockedCompareExchange64(cast(i64 volatile *)a, expected + operand, expected);
+		if (original == expected)
+			return original;
+		expected = original;
+	}
+#else
+#error TODO(bill): atomics for this CPU
+#endif
+}
+
+gb_inline i64 gb_atomic64_fetch_and(gbAtomic64 volatile *a, i64 operand) {
+#if defined(GB_ARCH_64_BIT)
+	return _InterlockedAnd64(cast(i64 volatile *)a, operand);
+#elif GB_CPU_X86
+	i64 expected = a->value;
+	for (;;) {
+		i64 original = _InterlockedCompareExchange64(cast(i64 volatile *)a, expected & operand, expected);
+		if (original == expected)
+			return original;
+		expected = original;
+	}
+#else
+#error TODO(bill): atomics for this CPU
+#endif
+}
+
+gb_inline i64 gb_atomic64_fetch_or(gbAtomic64 volatile *a, i64 operand) {
+#if defined(GB_ARCH_64_BIT)
+	return _InterlockedAnd64(cast(i64 volatile *)a, operand);
+#elif GB_CPU_X86
+	i64 expected = a->value;
+	for (;;) {
+		i64 original = _InterlockedCompareExchange64(cast(i64 volatile *)a, expected | operand, expected);
+		if (original == expected)
+			return original;
+		expected = original;
+	}
+#else
+#error TODO(bill): atomics for this CPU
+#endif
+}
+
+
+
+#elif defined(GB_CPU_X86)
+
+gb_inline i32  gb_atomic32_load (gbAtomic32 const volatile *a)      { return a->value;  }
+gb_inline void gb_atomic32_store(gbAtomic32 volatile *a, i32 value) { a->value = value; }
+
+gb_inline i32 gb_atomic32_compare_exchange(gbAtomic32 volatile *a, i32 expected, i32 desired) {
+	i32 original;
+	__asm__ volatile(
+		"lock; cmpxchgl %2, %1"
+		: "=a"(original), "+m"(a->value)
+		: "q"(desired), "0"(expected)
+	);
+	return original;
+}
+
+gb_inline i32 gb_atomic32_exchanged(gbAtomic32 volatile *a, i32 desired) {
+	// NOTE(bill): No lock prefix is necessary for xchgl
+	i32 original;
+	__asm__ volatile(
+		"xchgl %0, %1"
+		: "=r"(original), "+m"(a->value)
+		: "0"(desired)
+	);
+	return original;
+}
+
+gb_inline i32 gb_atomic32_fetch_add(gbAtomic32 volatile *a, i32 operand) {
+	i32 original;
+	__asm__ volatile(
+		"lock; xaddl %0, %1"
+		: "=r"(original), "+m"(a->value)
+		: "0"(operand)
+	);
+	return original;
+}
+
+gb_inline i32 gb_atomic32_fetch_and(gbAtomic32 volatile *a, i32 operand) {
+	i32 original;
+	i32 tmp;
+	__asm__ volatile(
+		"1:     movl    %1, %0\n"
+		"       movl    %0, %2\n"
+		"       andl    %3, %2\n"
+		"       lock; cmpxchgl %2, %1\n"
+		"       jne     1b"
+		: "=&a"(original), "+m"(a->value), "=&r"(tmp)
+		: "r"(operand)
+	);
+	return original;
+}
+
+gb_inline i32 gb_atomic32_fetch_or(gbAtomic32 volatile *a, i32 operand) {
+	i32 original;
+	i32 temp;
+	__asm__ volatile(
+		"1:     movl    %1, %0\n"
+		"       movl    %0, %2\n"
+		"       orl     %3, %2\n"
+		"       lock; cmpxchgl %2, %1\n"
+		"       jne     1b"
+		: "=&a"(original), "+m"(a->value), "=&r"(temp)
+		: "r"(operand)
+	);
+	return original;
+}
+
+
+gb_inline i64 gb_atomic64_load(gbAtomic64 const volatile *a) {
+#if defined(GB_ARCH_64_BIT)
+	return a->value;
+#else
+	i64 original;
+	__asm__ volatile(
+		"movl %%ebx, %%eax\n"
+		"movl %%ecx, %%edx\n"
+		"lock; cmpxchg8b %1"
+		: "=&A"(original)
+		: "m"(a->value)
+	);
+	return original;
+#endif
+}
+
+gb_inline void gb_atomic64_store(gbAtomic64 volatile *a, i64 value) {
+#if defined(GB_ARCH_64_BIT)
+	a->value = value;
+#else
+	i64 expected = a->value;
+	__asm__ volatile(
+		"1:    cmpxchg8b %0\n"
+		"      jne 1b"
+		: "=m"(a->value)
+		: "b"((i32)value), "c"((i32)(value >> 32)), "A"(expected)
+	);
+#endif
+}
+
+gb_inline i64 gb_atomic64_compare_exchange(gbAtomic64 volatile *a, i64 expected, i64 desired) {
+#if defined(GB_ARCH_64_BIT)
+	i64 original;
+	__asm__ volatile(
+		"lock; cmpxchgq %2, %1"
+		: "=a"(original), "+m"(a->value)
+		: "q"(desired), "0"(expected)
+	);
+	return original;
+#else
+	i64 original;
+	__asm__ volatile(
+		"lock; cmpxchg8b %1"
+		: "=A"(original), "+m"(a->value)
+		: "b"((i32)desired), "c"((i32)(desired >> 32)), "0"(expected)
+	);
+	return original;
+#endif
+}
+
+gb_inline i64 gb_atomic64_exchanged(gbAtomic64 volatile *a, i64 desired) {
+#if defined(GB_ARCH_64_BIT)
+	i64 original;
+	__asm__ volatile(
+		"xchgq %0, %1"
+		: "=r"(original), "+m"(a->value)
+		: "0"(desired)
+	);
+	return original;
+#else
+	i64 original = a->value;
+	for (;;) {
+		i64 previous = gb_atomic64_compare_exchange(a, original, desired);
+		if (original == previous)
+			return original;
+		original = previous;
+	}
+#endif
+}
+
+gb_inline i64 gb_atomic64_fetch_add(gbAtomic64 volatile *a, i64 operand) {
+#if defined(GB_ARCH_64_BIT)
+	i64 original;
+	__asm__ volatile(
+		"lock; xaddq %0, %1"
+		: "=r"(original), "+m"(a->value)
+		: "0"(operand)
+	);
+	return original;
+#else
+	for (;;) {
+		i64 original = a->value;
+		if (gb_atomic64_compare_exchange(a, original, original + operand) == original)
+			return original;
+	}
+#endif
+}
+
+gb_inline i64 gb_atomic64_fetch_and(gbAtomic64 volatile *a, i64 operand) {
+#if defined(GB_ARCH_64_BIT)
+	i64 original;
+	i64 tmp;
+	__asm__ volatile(
+		"1:     movq    %1, %0\n"
+		"       movq    %0, %2\n"
+		"       andq    %3, %2\n"
+		"       lock; cmpxchgq %2, %1\n"
+		"       jne     1b"
+		: "=&a"(original), "+m"(a->value), "=&r"(tmp)
+		: "r"(operand)
+	);
+	return original;
+#else
+	for (;;) {
+		i64 original = a->value;
+		if (gb_atomic64_compare_exchange(a, original, original & operand) == original)
+			return original;
+	}
+#endif
+}
+
+gb_inline i64 gb_atomic64_fetch_or(gbAtomic64 volatile *a, i64 operand) {
+#if defined(GB_ARCH_64_BIT)
+	i64 original;
+	i64 temp;
+	__asm__ volatile(
+		"1:     movq    %1, %0\n"
+		"       movq    %0, %2\n"
+		"       orq     %3, %2\n"
+		"       lock; cmpxchgq %2, %1\n"
+		"       jne     1b"
+		: "=&a"(original), "+m"(a->value), "=&r"(temp)
+		: "r"(operand)
+	);
+	return original;
+#else
+	for (;;) {
+		i64 original = a->value;
+		if (gb_atomic64_compare_exchange(a, original, original | operand) == original)
+			return original;
+	}
+#endif
+}
+
+#else
+#error TODO(bill): Implement Atomics for this CPU
+#endif
+
+gb_inline b32 gb_atomic32_spin_lock(gbAtomic32 volatile *a, isize time_out) {
+	i32 old_value = gb_atomic32_compare_exchange(a, 1, 0);
+	i32 counter = 0;
+	while (old_value != 0 && (time_out < 0 || counter++ < time_out)) {
+		gb_yield_thread();
+		old_value = gb_atomic32_compare_exchange(a, 1, 0);
+		gb_mfence();
+	}
+	return old_value == 0;
+}
+gb_inline void gb_atomic32_spin_unlock(gbAtomic32 volatile *a) {
+	gb_atomic32_store(a, 0);
+	gb_mfence();
+}
+
+gb_inline b32 gb_atomic64_spin_lock(gbAtomic64 volatile *a, isize time_out) {
+	i64 old_value = gb_atomic64_compare_exchange(a, 1, 0);
+	i64 counter = 0;
+	while (old_value != 0 && (time_out < 0 || counter++ < time_out)) {
+		gb_yield_thread();
+		old_value = gb_atomic64_compare_exchange(a, 1, 0);
+		gb_mfence();
+	}
+	return old_value == 0;
+}
+
+gb_inline void gb_atomic64_spin_unlock(gbAtomic64 volatile *a) {
+	gb_atomic64_store(a, 0);
+	gb_mfence();
+}
+
+gb_inline b32 gb_atomic32_try_acquire_lock(gbAtomic32 volatile *a) {
+	i32 old_value;
+	gb_yield_thread();
+	old_value = gb_atomic32_compare_exchange(a, 1, 0);
+	gb_mfence();
+	return old_value == 0;
+}
+
+gb_inline b32 gb_atomic64_try_acquire_lock(gbAtomic64 volatile *a) {
+	i64 old_value;
+	gb_yield_thread();
+	old_value = gb_atomic64_compare_exchange(a, 1, 0);
+	gb_mfence();
+	return old_value == 0;
+}
+
+
+
+#if defined(GB_ARCH_32_BIT)
+
+gb_inline void *gb_atomic_ptr_load(gbAtomicPtr const volatile *a) {
+	return cast(void *)cast(intptr)gb_atomic32_load(cast(gbAtomic32 const volatile *)a);
+}
+gb_inline void gb_atomic_ptr_store(gbAtomicPtr volatile *a, void *value) {
+	gb_atomic32_store(cast(gbAtomic32 volatile *)a, cast(i32)cast(intptr)value);
+}
+gb_inline void *gb_atomic_ptr_compare_exchange(gbAtomicPtr volatile *a, void *expected, void *desired) {
+	return cast(void *)cast(intptr)gb_atomic32_compare_exchange(cast(gbAtomic32 volatile *)a, cast(i32)cast(intptr)expected, cast(i32)cast(intptr)desired);
+}
+gb_inline void *gb_atomic_ptr_exchanged(gbAtomicPtr volatile *a, void *desired) {
+	return cast(void *)cast(intptr)gb_atomic32_exchanged(cast(gbAtomic32 volatile *)a, cast(i32)cast(intptr)desired);
+}
+gb_inline void *gb_atomic_ptr_fetch_add(gbAtomicPtr volatile *a, void *operand) {
+	return cast(void *)cast(intptr)gb_atomic32_fetch_add(cast(gbAtomic32 volatile *)a, cast(i32)cast(intptr)operand);
+}
+gb_inline void *gb_atomic_ptr_fetch_and(gbAtomicPtr volatile *a, void *operand) {
+	return cast(void *)cast(intptr)gb_atomic32_fetch_and(cast(gbAtomic32 volatile *)a, cast(i32)cast(intptr)operand);
+}
+gb_inline void *gb_atomic_ptr_fetch_or(gbAtomicPtr volatile *a, void *operand) {
+	return cast(void *)cast(intptr)gb_atomic32_fetch_or(cast(gbAtomic32 volatile *)a, cast(i32)cast(intptr)operand);
+}
+gb_inline b32 gb_atomic_ptr_spin_lock(gbAtomicPtr volatile *a, isize time_out) {
+	return gb_atomic32_spin_lock(cast(gbAtomic32 volatile *)a, time_out);
+}
+gb_inline void gb_atomic_ptr_spin_unlock(gbAtomicPtr volatile *a) {
+	gb_atomic32_spin_unlock(cast(gbAtomic32 volatile *)a);
+}
+gb_inline b32 gb_atomic_ptr_try_acquire_lock(gbAtomicPtr volatile *a) {
+	return gb_atomic32_try_acquire_lock(cast(gbAtomic32 volatile *)a);
+}
+
+#elif defined(GB_ARCH_64_BIT)
+
+gb_inline void *gb_atomic_ptr_load(gbAtomicPtr const volatile *a) {
+	return cast(void *)cast(intptr)gb_atomic64_load(cast(gbAtomic64 const volatile *)a);
+}
+gb_inline void gb_atomic_ptr_store(gbAtomicPtr volatile *a, void *value) {
+	gb_atomic64_store(cast(gbAtomic64 volatile *)a, cast(i64)cast(intptr)value);
+}
+gb_inline void *gb_atomic_ptr_compare_exchange(gbAtomicPtr volatile *a, void *expected, void *desired) {
+	return cast(void *)cast(intptr)gb_atomic64_compare_exchange(cast(gbAtomic64 volatile *)a, cast(i64)cast(intptr)expected, cast(i64)cast(intptr)desired);
+}
+gb_inline void *gb_atomic_ptr_exchanged(gbAtomicPtr volatile *a, void *desired) {
+	return cast(void *)cast(intptr)gb_atomic64_exchanged(cast(gbAtomic64 volatile *)a, cast(i64)cast(intptr)desired);
+}
+gb_inline void *gb_atomic_ptr_fetch_add(gbAtomicPtr volatile *a, void *operand) {
+	return cast(void *)cast(intptr)gb_atomic64_fetch_add(cast(gbAtomic64 volatile *)a, cast(i64)cast(intptr)operand);
+}
+gb_inline void *gb_atomic_ptr_fetch_and(gbAtomicPtr volatile *a, void *operand) {
+	return cast(void *)cast(intptr)gb_atomic64_fetch_and(cast(gbAtomic64 volatile *)a, cast(i64)cast(intptr)operand);
+}
+gb_inline void *gb_atomic_ptr_fetch_or(gbAtomicPtr volatile *a, void *operand) {
+	return cast(void *)cast(intptr)gb_atomic64_fetch_or(cast(gbAtomic64 volatile *)a, cast(i64)cast(intptr)operand);
+}
+gb_inline b32 gb_atomic_ptr_spin_lock(gbAtomicPtr volatile *a, isize time_out) {
+	return gb_atomic64_spin_lock(cast(gbAtomic64 volatile *)a, time_out);
+}
+gb_inline void gb_atomic_ptr_spin_unlock(gbAtomicPtr volatile *a) {
+	gb_atomic64_spin_unlock(cast(gbAtomic64 volatile *)a);
+}
+gb_inline b32 gb_atomic_ptr_try_acquire_lock(gbAtomicPtr volatile *a) {
+	return gb_atomic64_try_acquire_lock(cast(gbAtomic64 volatile *)a);
+}
+#endif
+
+
+gb_inline void gb_yield_thread(void) {
+#if defined(GB_SYSTEM_WINDOWS)
+	_mm_pause();
+#elif defined(GB_SYSTEM_OSX)
+	__asm__ volatile ("" : : : "memory");
+#elif defined(GB_CPU_X86)
+	_mm_pause();
+#else
+#error Unknown architecture
+#endif
+}
+
+gb_inline void gb_mfence(void) {
+#if defined(GB_SYSTEM_WINDOWS)
+	_ReadWriteBarrier();
+#elif defined(GB_SYSTEM_OSX)
+	__sync_synchronize();
+#elif defined(GB_CPU_X86)
+	_mm_mfence();
+#else
+#error Unknown architecture
+#endif
+}
+
+gb_inline void gb_sfence(void) {
+#if defined(GB_SYSTEM_WINDOWS)
+	_WriteBarrier();
+#elif defined(GB_SYSTEM_OSX)
+	__asm__ volatile ("" : : : "memory");
+#elif defined(GB_CPU_X86)
+	_mm_sfence();
+#else
+#error Unknown architecture
+#endif
+}
+
+gb_inline void gb_lfence(void) {
+#if defined(GB_SYSTEM_WINDOWS)
+	_ReadBarrier();
+#elif defined(GB_SYSTEM_OSX)
+	__asm__ volatile ("" : : : "memory");
+#elif defined(GB_CPU_X86)
+	_mm_lfence();
+#else
+#error Unknown architecture
+#endif
+}
+
+
+gb_inline void gb_semaphore_release(gbSemaphore *s) { gb_semaphore_post(s, 1); }
+
+#if defined(GB_SYSTEM_WINDOWS)
+	gb_inline void gb_semaphore_init   (gbSemaphore *s)            { s->win32_handle = CreateSemaphoreA(NULL, 0, I32_MAX, NULL); }
+	gb_inline void gb_semaphore_destroy(gbSemaphore *s)            { CloseHandle(s->win32_handle); }
+	gb_inline void gb_semaphore_post   (gbSemaphore *s, i32 count) { ReleaseSemaphore(s->win32_handle, count, NULL); }
+	gb_inline void gb_semaphore_wait   (gbSemaphore *s)            { WaitForSingleObject(s->win32_handle, INFINITE); }
+
+#elif defined(GB_SYSTEM_OSX)
+	gb_inline void gb_semaphore_init   (gbSemaphore *s)            { semaphore_create(mach_task_self(), &s->osx_handle, SYNC_POLICY_FIFO, 0); }
+	gb_inline void gb_semaphore_destroy(gbSemaphore *s)            { semaphore_destroy(mach_task_self(), s->osx_handle); }
+	gb_inline void gb_semaphore_post   (gbSemaphore *s, i32 count) { while (count --> 0) semaphore_signal(s->osx_handle); }
+	gb_inline void gb_semaphore_wait   (gbSemaphore *s)            { semaphore_wait(s->osx_handle); }
+
+#elif defined(GB_SYSTEM_UNIX)
+	gb_inline void gb_semaphore_init   (gbSemaphore *s)            { sem_init(&s->unix_handle, 0, 0); }
+	gb_inline void gb_semaphore_destroy(gbSemaphore *s)            { sem_destroy(&s->unix_handle); }
+	gb_inline void gb_semaphore_post   (gbSemaphore *s, i32 count) { while (count --> 0) sem_post(&s->unix_handle); }
+	gb_inline void gb_semaphore_wait   (gbSemaphore *s)            { int i; do { i = sem_wait(&s->unix_handle); } while (i == -1 && errno == EINTR); }
+
+#else
+#error
+#endif
+
+// NOTE(bill): THIS IS FUCKING AWESOME THAT THIS "MUTEX" IS FAST AND RECURSIVE TOO!
+// NOTE(bill): WHO THE FUCK NEEDS A NORMAL MUTEX NOW?!?!?!?!
+gb_inline void gb_mutex_init(gbMutex *m) {
+	gb_atomic32_store(&m->counter, 0);
+	gb_atomic32_store(&m->owner, gb_thread_current_id());
+	gb_semaphore_init(&m->semaphore);
+	m->recursion = 0;
+}
+
+gb_inline void gb_mutex_destroy(gbMutex *m) { gb_semaphore_destroy(&m->semaphore); }
+
+gb_inline void gb_mutex_lock(gbMutex *m) {
+	i32 thread_id = cast(i32)gb_thread_current_id();
+	if (gb_atomic32_fetch_add(&m->counter, 1) > 0) {
+		if (thread_id != gb_atomic32_load(&m->owner))
+			gb_semaphore_wait(&m->semaphore);
+	}
+
+	gb_atomic32_store(&m->owner, thread_id);
+	m->recursion++;
+}
+
+gb_inline b32 gb_mutex_try_lock(gbMutex *m) {
+	i32 thread_id = cast(i32)gb_thread_current_id();
+	if (gb_atomic32_load(&m->owner) == thread_id) {
+		gb_atomic32_fetch_add(&m->counter, 1);
+	} else {
+		i32 expected = 0;
+		if (gb_atomic32_load(&m->counter) != 0)
+			return false;
+		if (!gb_atomic32_compare_exchange(&m->counter, expected, 1))
+			return false;
+		gb_atomic32_store(&m->owner, thread_id);
+	}
+
+	m->recursion++;
+	return true;
+}
+
+gb_inline void gb_mutex_unlock(gbMutex *m) {
+	i32 recursion;
+	i32 thread_id = cast(i32)gb_thread_current_id();
+
+	GB_ASSERT(thread_id == gb_atomic32_load(&m->owner));
+
+	recursion = --m->recursion;
+	if (recursion == 0)
+		gb_atomic32_store(&m->owner, thread_id);
+
+	if (gb_atomic32_fetch_add(&m->counter, -1) > 1) {
+		if (recursion == 0)
+			gb_semaphore_release(&m->semaphore);
+	}
+}
+
+
+
+
+
+
+
+void gb_thread_init(gbThread *t) {
+	gb_zero_item(t);
+#if defined(GB_SYSTEM_WINDOWS)
+	t->win32_handle = INVALID_HANDLE_VALUE;
+#else
+	t->posix_handle = 0;
+#endif
+	gb_semaphore_init(&t->semaphore);
+}
+
+void gb_thread_destory(gbThread *t) {
+	if (t->is_running) gb_thread_join(t);
+	gb_semaphore_destroy(&t->semaphore);
+}
+
+
+gb_inline void gb__thread_run(gbThread *t) {
+	gb_semaphore_release(&t->semaphore);
+	t->proc(t->data);
+}
+
+#if defined(GB_SYSTEM_WINDOWS)
+	gb_inline DWORD __stdcall gb__thread_proc(void *arg) { gb__thread_run(cast(gbThread *)arg); return 0; }
+#else
+	gb_inline void *          gb__thread_proc(void *arg) { gb__thread_run(cast(gbThread *)arg); return NULL; }
+#endif
+
+gb_inline void gb_thread_start(gbThread *t, gbThreadProc *proc, void *data) { gb_thread_start_with_stack(t, proc, data, 0); }
+
+gb_inline void gb_thread_start_with_stack(gbThread *t, gbThreadProc *proc, void *data, isize stack_size) {
+	GB_ASSERT(!t->is_running);
+	GB_ASSERT(proc != NULL);
+	t->proc = proc;
+	t->data = data;
+	t->stack_size = stack_size;
+
+#if defined(GB_SYSTEM_WINDOWS)
+	t->win32_handle = CreateThread(NULL, stack_size, gb__thread_proc, t, 0, NULL);
+	GB_ASSERT_MSG(t->win32_handle != NULL, "CreateThread: GetLastError");
+#else
+	{
+		pthread_attr_t attr;
+		pthread_attr_init(&attr);
+		pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_JOINABLE);
+		if (stack_size != 0)
+			pthread_attr_setstacksize(&attr, stack_size);
+		pthread_create(&t->posix_handle, &attr, gb__thread_proc, t);
+		pthread_attr_destroy(&attr);
+	}
+#endif
+
+	t->is_running = true;
+	gb_semaphore_wait(&t->semaphore);
+}
+
+gb_inline void gb_thread_join(gbThread *t) {
+	if (!t->is_running) return;
+
+#if defined(GB_SYSTEM_WINDOWS)
+	WaitForSingleObject(t->win32_handle, INFINITE);
+	CloseHandle(t->win32_handle);
+	t->win32_handle = INVALID_HANDLE_VALUE;
+#else
+	pthread_join(t->posix_handle, NULL);
+	t->posix_handle = 0;
+#endif
+	t->is_running = false;
+}
+
+gb_inline b32 gb_thread_is_running(gbThread const *t) { return t->is_running != 0; }
+
+gb_inline u32 gb_thread_current_id(void) {
+	u32 thread_id;
+#if defined(GB_SYSTEM_WINDOWS)
+	#if defined(GB_ARCH_32_BIT) && defined(GB_CPU_X86)
+		thread_id = (cast(u32 *)__readfsdword(24))[9];
+	#elif defined(GB_ARCH_64_BIT) && defined(GB_CPU_X86)
+		thread_id = (cast(u32 *)__readgsqword(48))[18];
+	#else
+		thread_id = GetCurrentThreadId();
+	#endif
+
+#elif defined(GB_SYSTEM_OSX) && defined(GB_ARCH_64_BIT)
+	thread_id = pthread_mach_thread_np(pthread_self());
+#elif defined(GB_ARCH_32_BIT) && defined(GB_CPU_X86)
+	__asm__("mov %%gs:0x08,%0" : "=r"(thread_id));
+#elif defined(GB_ARCH_64_BIT) && defined(GB_CPU_X86)
+	__asm__("mov %%gs:0x10,%0" : "=r"(thread_id));
+#else
+	#error Unsupported architecture for gb_thread_current_id()
+#endif
+
+	return thread_id;
+}
+
+
+
+void gb_thread_set_name(gbThread *t, char const *name) {
+#if defined(GB_COMPILER_MSVC)
+	#pragma pack(push, 8)
+		typedef struct {
+			DWORD       type;
+			char const *name;
+			DWORD       id;
+			DWORD       flags;
+		} gbprivThreadName;
+	#pragma pack(pop)
+		gbprivThreadName tn;
+		tn.type  = 0x1000;
+		tn.name  = name;
+		tn.id    = GetThreadId(cast(HANDLE)t->win32_handle);
+		tn.flags = 0;
+
+		__try {
+			RaiseException(0x406d1388, 0, gb_size_of(tn)/4, cast(usize *)&tn);
+		} __except(1 /*EXCEPTION_EXECUTE_HANDLER*/) {
+		}
+
+#elif defined(GB_SYSTEM_WINDOWS) && !defined(GB_COMPILER_MSVC)
+	// IMPORTANT TODO(bill): Set thread name for GCC/Clang on windows
+	return;
+#elif defined(GB_SYSTEM_OSX)
+	// TODO(bill): Test if this works
+	pthread_setname_np(name);
+#else
+	// TODO(bill): Test if this works
+	pthread_setname_np(t->posix_handle, name);
+#endif
+}
+
+
+
+
+void gb_sync_init(gbSync *s) {
+	gb_zero_item(s);
+	gb_mutex_init(&s->mutex);
+	gb_mutex_init(&s->start);
+	gb_semaphore_init(&s->release);
+}
+
+void gb_sync_destroy(gbSync *s) {
+	if (s->waiting)
+		GB_PANIC("Cannot destroy while threads are waiting!");
+
+	gb_mutex_destroy(&s->mutex);
+	gb_mutex_destroy(&s->start);
+	gb_semaphore_destroy(&s->release);
+}
+
+void gb_sync_set_target(gbSync *s, i32 count) {
+	gb_mutex_lock(&s->start);
+
+	gb_mutex_lock(&s->mutex);
+	GB_ASSERT(s->target == 0);
+	s->target = count;
+	s->current = 0;
+	s->waiting = 0;
+	gb_mutex_unlock(&s->mutex);
+}
+
+void gb_sync_release(gbSync *s) {
+	if (s->waiting) {
+		gb_semaphore_release(&s->release);
+	} else {
+		s->target = 0;
+		gb_mutex_unlock(&s->start);
+	}
+}
+
+i32 gb_sync_reach(gbSync *s) {
+	i32 n;
+	gb_mutex_lock(&s->mutex);
+	GB_ASSERT(s->current < s->target);
+	n = ++s->current; // NOTE(bill): Record this value to avoid possible race if `return s->current` was done
+	if (s->current == s->target)
+		gb_sync_release(s);
+	gb_mutex_unlock(&s->mutex);
+	return n;
+}
+
+void gb_sync_reach_and_wait(gbSync *s) {
+	gb_mutex_lock(&s->mutex);
+	GB_ASSERT(s->current < s->target);
+	s->current++;
+	if (s->current == s->target) {
+		gb_sync_release(s);
+		gb_mutex_unlock(&s->mutex);
+	} else {
+		s->waiting++;                   // NOTE(bill): Waiting, so one more waiter
+		gb_mutex_unlock(&s->mutex);     // NOTE(bill): Release the mutex to other threads
+
+		gb_semaphore_wait(&s->release); // NOTE(bill): Wait for merge completion
+
+		gb_mutex_lock(&s->mutex);       // NOTE(bill): On merge completion, lock mutex
+		s->waiting--;                   // NOTE(bill): Done waiting
+		gb_sync_release(s);             // NOTE(bill): Restart the next waiter
+		gb_mutex_unlock(&s->mutex);
+	}
+}
+
+
+
+
+
+
+
+
+gb_inline gbAllocator gb_heap_allocator(void) {
+	gbAllocator a;
+	a.proc = gb_heap_allocator_proc;
+	a.data = NULL;
+	return a;
+}
+
+GB_ALLOCATOR_PROC(gb_heap_allocator_proc) {
+	void *ptr = NULL;
+	gb_unused(allocator_data);
+	gb_unused(old_size);
+// TODO(bill): Throughly test!
+	switch (type) {
+#if defined(GB_COMPILER_MSVC)
+	case gbAllocation_Alloc:
+		ptr = _aligned_malloc(size, alignment);
+		if (flags & gbAllocatorFlag_ClearToZero)
+			gb_zero_size(ptr, size);
+		break;
+	case gbAllocation_Free:
+		_aligned_free(old_memory);
+		break;
+	case gbAllocation_Resize:
+		ptr = _aligned_realloc(old_memory, size, alignment);
+		break;
+#else
+	// TODO(bill): *nix version that's decent
+	case gbAllocation_Alloc: {
+		gbAllocationHeader *header;
+		isize total_size = size + alignment + gb_size_of(gbAllocationHeader);
+		ptr = malloc(total_size);
+		header = cast(gbAllocationHeader *)ptr;
+		ptr = gb_align_forward(header+1, alignment);
+		gb_allocation_header_fill(header, ptr, size);
+		if (flags & gbAllocatorFlag_ClearToZero)
+			gb_zero_size(ptr, size);
+	} break;
+
+	case gbAllocation_Free: {
+		free(gb_allocation_header(old_memory));
+	} break;
+
+	case gbAllocation_Resize: {
+		gbAllocator a = gb_heap_allocator();
+		ptr = gb_default_resize_align(a, old_memory, old_size, size, alignment);
+	} break;
+#endif
+
+	case gbAllocation_FreeAll:
+		break;
+	}
+
+	return ptr;
+}
+
+
+#if defined(GB_SYSTEM_WINDOWS)
+void gb_affinity_init(gbAffinity *a) {
+	SYSTEM_LOGICAL_PROCESSOR_INFORMATION *start_processor_info = NULL;
+	DWORD length = 0;
+	b32 result  = GetLogicalProcessorInformation(NULL, &length);
+
+	gb_zero_item(a);
+
+	if (!result && GetLastError() == 122l /*ERROR_INSUFFICIENT_BUFFER*/ && length > 0) {
+		start_processor_info = cast(SYSTEM_LOGICAL_PROCESSOR_INFORMATION *)gb_alloc(gb_heap_allocator(), length);
+		result = GetLogicalProcessorInformation(start_processor_info, &length);
+		if (result) {
+			SYSTEM_LOGICAL_PROCESSOR_INFORMATION *end_processor_info, *processor_info;
+
+			a->is_accurate  = true;
+			a->core_count   = 0;
+			a->thread_count = 0;
+			end_processor_info = cast(SYSTEM_LOGICAL_PROCESSOR_INFORMATION *)gb_pointer_add(start_processor_info, length);
+
+			for (processor_info = start_processor_info;
+			     processor_info < end_processor_info;
+			     processor_info++) {
+				if (processor_info->Relationship == RelationProcessorCore) {
+					isize thread = gb_count_set_bits(processor_info->ProcessorMask);
+					if (thread == 0) {
+						a->is_accurate = false;
+					} else if (a->thread_count + thread > GB_WIN32_MAX_THREADS) {
+						a->is_accurate = false;
+					} else {
+						GB_ASSERT(a->core_count <= a->thread_count &&
+						          a->thread_count < GB_WIN32_MAX_THREADS);
+						a->core_masks[a->core_count++] = processor_info->ProcessorMask;
+						a->thread_count += thread;
+					}
+				}
+			}
+		}
+
+		gb_free(gb_heap_allocator(), start_processor_info);
+	}
+
+	GB_ASSERT(a->core_count <= a->thread_count);
+	if (a->thread_count == 0) {
+		a->is_accurate   = false;
+		a->core_count    = 1;
+		a->thread_count  = 1;
+		a->core_masks[0] = 1;
+	}
+
+}
+void gb_affinity_destroy(gbAffinity *a) {
+	gb_unused(a);
+}
+
+
+b32 gb_affinity_set(gbAffinity *a, isize core, isize thread) {
+	usize available_mask, check_mask = 1;
+	GB_ASSERT(thread < gb_affinity_thread_count_for_core(a, core));
+
+	available_mask = a->core_masks[core];
+	for (;;) {
+		if ((available_mask & check_mask) != 0) {
+			if (thread-- == 0) {
+				usize result = SetThreadAffinityMask(GetCurrentThread(), check_mask);
+				return result != 0;
+			}
+		}
+		check_mask <<= 1; // NOTE(bill): Onto the next bit
+	}
+}
+
+isize gb_affinity_thread_count_for_core(gbAffinity *a, isize core) {
+	GB_ASSERT(core >= 0 && core < a->core_count);
+	return gb_count_set_bits(a->core_masks[core]);
+}
+
+#elif defined(GB_SYSTEM_OSX)
+void gb_affinity_init(gbAffinity *a) {
+	usize count, count_size = gb_size_of(count);
+
+	a->is_accurate               = false;
+	a->thread_count     = 1;
+	a->core_count       = 1;
+	a->threads_per_core = 1;
+
+	if (sysctlbyname("hw.logicalcpu", &count, &count_size, NULL, 0) == 0) {
+		if (count > 0) {
+			a->thread_count = count;
+			// Get # of physical cores
+			if (sysctlbyname("hw.physicalcpu", &count, &count_size, NULL, 0) == 0) {
+				if (count > 0) {
+					a->core_count = count;
+					a->threads_per_core = a->thread_count / count;
+					if (a->threads_per_core < 1)
+						a->threads_per_core = 1;
+					else
+						a->is_accurate = true;
+				}
+			}
+		}
+	}
+
+}
+
+void gb_affinity_destroy(gbAffinity *a) {
+	gb_unused(a);
+}
+
+b32 gb_affinity_set(gbAffinity *a, isize core, isize thread_index) {
+	isize index;
+	thread_t thread;
+	thread_affinity_policy_data_t info;
+	kern_return_t result;
+
+	GB_ASSERT(core < a->core_count);
+	GB_ASSERT(thread_index < a->threads_per_core);
+
+	index = core * a->threads_per_core + thread_index;
+	thread = mach_thread_self();
+	info.affinity_tag = cast(integer_t)index;
+	result = thread_policy_set(thread, THREAD_AFFINITY_POLICY, cast(thread_policy_t)&info, THREAD_AFFINITY_POLICY_COUNT);
+	return result == KERN_SUCCESS;
+}
+
+isize gb_affinity_thread_count_for_core(gbAffinity *a, isize core) {
+	GB_ASSERT(core >= 0 && core < a->core_count);
+	return a->threads_per_core;
+}
+
+#elif defined(GB_SYSTEM_LINUX)
+#error TODO(bill): Implement gbAffinity for linux
+#else
+#error TODO(bill): Unknown system
+#endif
+
+
+
+
+
+
+
+
+
+////////////////////////////////////////////////////////////////
+//
+// Virtual Memory
+//
+//
+
+gbVirtualMemory gb_virtual_memory(void *data, isize size) {
+	gbVirtualMemory vm;
+	vm.data = data;
+	vm.size = size;
+	return vm;
+}
+
+
+#if defined(GB_SYSTEM_WINDOWS)
+gb_inline gbVirtualMemory gb_vm_alloc(void *addr, isize size) {
+	gbVirtualMemory vm;
+	GB_ASSERT(size > 0);
+	vm.data = VirtualAlloc(addr, size, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE);
+	vm.size = size;
+	return vm;
+}
+
+gb_inline b32 gb_vm_free(gbVirtualMemory vm) {
+	MEMORY_BASIC_INFORMATION info;
+	while (vm.size > 0) {
+		if (VirtualQuery(vm.data, &info, gb_size_of(info)) == 0)
+			return false;
+		if (info.BaseAddress != vm.data ||
+		    info.AllocationBase != vm.data ||
+		    info.State != MEM_COMMIT || info.RegionSize > cast(usize)vm.size) {
+			return false;
+		}
+		if (VirtualFree(vm.data, 0, MEM_RELEASE) == 0)
+			return false;
+		vm.data = gb_pointer_add(vm.data, info.RegionSize);
+		vm.size -= info.RegionSize;
+	}
+	return true;
+}
+
+gb_inline gbVirtualMemory gb_vm_trim(gbVirtualMemory vm, isize lead_size, isize size) {
+	gbVirtualMemory new_vm = {0};
+	void *ptr;
+	GB_ASSERT(vm.size >= lead_size + size);
+
+	ptr = gb_pointer_add(vm.data, lead_size);
+
+	gb_vm_free(vm);
+	new_vm = gb_vm_alloc(ptr, size);
+	if (new_vm.data == ptr)
+		return new_vm;
+	if (new_vm.data)
+		gb_vm_free(new_vm);
+	return new_vm;
+}
+
+gb_inline b32 gb_vm_purge(gbVirtualMemory vm) {
+	VirtualAlloc(vm.data, vm.size, MEM_RESET, PAGE_READWRITE);
+	// NOTE(bill): Can this really fail?
+	return true;
+}
+
+isize gb_virtual_memory_page_size(isize *alignment_out) {
+	SYSTEM_INFO info;
+	GetSystemInfo(&info);
+	if (alignment_out) *alignment_out = info.dwAllocationGranularity;
+	return info.dwPageSize;
+}
+
+#else
+
+#ifndef MAP_ANONYMOUS
+#define MAP_ANONYMOUS MAP_ANON
+#endif
+
+gb_inline gbVirtualMemory gb_vm_alloc(void *addr, isize size) {
+	gbVirtualMemory vm;
+	GB_ASSERT(size > 0);
+	vm.data = mmap(addr, size, PROT_READ | PROT_WRITE, MAP_ANONYMOUS | MAP_PRIVATE, -1, 0);
+	vm.size = size;
+	return vm;
+}
+
+gb_inline b32 gb_vm_free(gbVirtualMemory vm) {
+	munmap(vm.data, vm.size);
+	return true;
+}
+
+gb_inline gbVirtualMemory gb_vm_trim(gbVirtualMemory vm, isize lead_size, isize size) {
+	void *ptr;
+	isize trail_size;
+	GB_ASSERT(vm.size >= lead_size + size);
+
+	ptr = gb_pointer_add(vm.data, lead_size);
+	trail_size = vm.size - lead_size - size;
+
+	if (lead_size != 0)
+		gb_vm_free(gb_virtual_memory(vm.data, lead_size));
+	if (trail_size != 0)
+		gb_vm_free(gb_virtual_memory(ptr, trail_size));
+	return gb_virtual_memory(ptr, size);
+
+}
+
+gb_inline b32 gb_vm_purge(gbVirtualMemory vm) {
+	int err = madvise(vm.data, vm.size, MADV_DONTNEED);
+	return err != 0;
+}
+
+isize gb_virtual_memory_page_size(isize *alignment_out) {
+	// TODO(bill): Is this always true?
+	isize result = cast(isize)sysconf(_SC_PAGE_SIZE);
+	if (alignment_out) *alignment_out = result;
+	return result;
+}
+
+#endif
+
+
+
+
+////////////////////////////////////////////////////////////////
+//
+// Custom Allocation
+//
+//
+
+
+//
+// Arena Allocator
+//
+
+gb_inline void gb_arena_init_from_memory(gbArena *arena, void *start, isize size) {
+	arena->backing.proc    = NULL;
+	arena->backing.data    = NULL;
+	arena->physical_start  = start;
+	arena->total_size      = size;
+	arena->total_allocated = 0;
+	arena->temp_count      = 0;
+}
+
+gb_inline void gb_arena_init_from_allocator(gbArena *arena, gbAllocator backing, isize size) {
+	arena->backing         = backing;
+	arena->physical_start  = gb_alloc(backing, size); // NOTE(bill): Uses default alignment
+	arena->total_size      = size;
+	arena->total_allocated = 0;
+	arena->temp_count      = 0;
+}
+
+gb_inline void gb_arena_init_sub(gbArena *arena, gbArena *parent_arena, isize size) { gb_arena_init_from_allocator(arena, gb_arena_allocator(parent_arena), size); }
+
+
+gb_inline void gb_arena_free(gbArena *arena) {
+	if (arena->backing.proc) {
+		gb_free(arena->backing, arena->physical_start);
+		arena->physical_start = NULL;
+	}
+}
+
+
+gb_inline isize gb_arena_alignment_of(gbArena *arena, isize alignment) {
+	isize alignment_offset, result_pointer, mask;
+	GB_ASSERT(gb_is_power_of_two(alignment));
+
+	alignment_offset = 0;
+	result_pointer = cast(isize)arena->physical_start + arena->total_allocated;
+	mask = alignment - 1;
+	if (result_pointer & mask)
+		alignment_offset = alignment - (result_pointer & mask);
+
+	return alignment_offset;
+}
+
+gb_inline isize gb_arena_size_remaining(gbArena *arena, isize alignment) {
+	isize result = arena->total_size - (arena->total_allocated + gb_arena_alignment_of(arena, alignment));
+	return result;
+}
+
+gb_inline void gb_arena_check(gbArena *arena) { GB_ASSERT(arena->temp_count == 0); }
+
+
+
+
+
+
+gb_inline gbAllocator gb_arena_allocator(gbArena *arena) {
+	gbAllocator allocator;
+	allocator.proc = gb_arena_allocator_proc;
+	allocator.data = arena;
+	return allocator;
+}
+
+GB_ALLOCATOR_PROC(gb_arena_allocator_proc) {
+	gbArena *arena = cast(gbArena *)allocator_data;
+	void *ptr = NULL;
+
+	gb_unused(old_size);
+
+	switch (type) {
+	case gbAllocation_Alloc: {
+		void *end = gb_pointer_add(arena->physical_start, arena->total_allocated);
+		isize total_size = size + alignment;
+
+		// NOTE(bill): Out of memory
+		if (arena->total_allocated + total_size > cast(isize)arena->total_size) {
+			gb_printf_err("Arena out of memory\n");
+			return NULL;
+		}
+
+		ptr = gb_align_forward(end, alignment);
+		arena->total_allocated += total_size;
+		if (flags & gbAllocatorFlag_ClearToZero)
+			gb_zero_size(ptr, size);
+	} break;
+
+	case gbAllocation_Free:
+		// NOTE(bill): Free all at once
+		// Use Temp_Arena_Memory if you want to free a block
+		break;
+
+	case gbAllocation_FreeAll:
+		arena->total_allocated = 0;
+		break;
+
+	case gbAllocation_Resize: {
+		// TODO(bill): Check if ptr is on top of stack and just extend
+		gbAllocator a = gb_arena_allocator(arena);
+		ptr = gb_default_resize_align(a, old_memory, old_size, size, alignment);
+	} break;
+	}
+	return ptr;
+}
+
+
+gb_inline gbTempArenaMemory gb_temp_arena_memory_begin(gbArena *arena) {
+	gbTempArenaMemory tmp;
+	tmp.arena = arena;
+	tmp.original_count = arena->total_allocated;
+	arena->temp_count++;
+	return tmp;
+}
+
+gb_inline void gb_temp_arena_memory_end(gbTempArenaMemory tmp) {
+	GB_ASSERT(tmp.arena->total_allocated >= tmp.original_count);
+	GB_ASSERT(tmp.arena->temp_count > 0);
+	tmp.arena->total_allocated = tmp.original_count;
+	tmp.arena->temp_count--;
+}
+
+
+
+
+//
+// Pool Allocator
+//
+
+
+gb_inline void gb_pool_init(gbPool *pool, gbAllocator backing, isize num_blocks, isize block_size) {
+	gb_pool_init_align(pool, backing, num_blocks, block_size, GB_DEFAULT_MEMORY_ALIGNMENT);
+}
+
+void gb_pool_init_align(gbPool *pool, gbAllocator backing, isize num_blocks, isize block_size, isize block_align) {
+	isize actual_block_size, pool_size, block_index;
+	void *data, *curr;
+	uintptr *end;
+
+	gb_zero_item(pool);
+
+	pool->backing = backing;
+	pool->block_size = block_size;
+	pool->block_align = block_align;
+
+	actual_block_size = block_size + block_align;
+	pool_size = num_blocks * actual_block_size;
+
+	data = gb_alloc_align(backing, pool_size, block_align);
+
+	// NOTE(bill): Init intrusive freelist
+	curr = data;
+	for (block_index = 0; block_index < num_blocks-1; block_index++) {
+		uintptr *next = cast(uintptr *)curr;
+		*next = cast(uintptr)curr + actual_block_size;
+		curr = gb_pointer_add(curr, actual_block_size);
+	}
+
+	end  = cast(uintptr *)curr;
+	*end = cast(uintptr)NULL;
+
+	pool->physical_start = data;
+	pool->free_list      = data;
+}
+
+gb_inline void gb_pool_free(gbPool *pool) {
+	if (pool->backing.proc) {
+		gb_free(pool->backing, pool->physical_start);
+	}
+}
+
+
+gb_inline gbAllocator gb_pool_allocator(gbPool *pool) {
+	gbAllocator allocator;
+	allocator.proc = gb_pool_allocator_proc;
+	allocator.data = pool;
+	return allocator;
+}
+GB_ALLOCATOR_PROC(gb_pool_allocator_proc) {
+	gbPool *pool = cast(gbPool *)allocator_data;
+	void *ptr = NULL;
+
+	gb_unused(old_size);
+
+	switch (type) {
+	case gbAllocation_Alloc: {
+		uintptr next_free;
+		GB_ASSERT(size      == pool->block_size);
+		GB_ASSERT(alignment == pool->block_align);
+		GB_ASSERT(pool->free_list != NULL);
+
+		next_free = *cast(uintptr *)pool->free_list;
+		ptr = pool->free_list;
+		pool->free_list = cast(void *)next_free;
+		pool->total_size += pool->block_size;
+		if (flags & gbAllocatorFlag_ClearToZero)
+			gb_zero_size(ptr, size);
+	} break;
+
+	case gbAllocation_Free: {
+		uintptr *next;
+		if (old_memory == NULL) return NULL;
+
+		next = cast(uintptr *)old_memory;
+		*next = cast(uintptr)pool->free_list;
+		pool->free_list = old_memory;
+		pool->total_size -= pool->block_size;
+	} break;
+
+	case gbAllocation_FreeAll:
+		// TODO(bill):
+		break;
+
+	case gbAllocation_Resize:
+		// NOTE(bill): Cannot resize
+		GB_PANIC("You cannot resize something allocated by with a pool.");
+		break;
+	}
+
+	return ptr;
+}
+
+
+
+
+
+gb_inline gbAllocationHeader *gb_allocation_header(void *data) {
+	isize *p = cast(isize *)data;
+	while (p[-1] == cast(isize)(-1))
+		p--;
+	return cast(gbAllocationHeader *)p - 1;
+}
+
+gb_inline void gb_allocation_header_fill(gbAllocationHeader *header, void *data, isize size) {
+	isize *ptr;
+	header->size = size;
+	ptr = cast(isize *)(header + 1);
+	while (cast(void *)ptr < data)
+		*ptr++ = cast(isize)(-1);
+}
+
+
+
+//
+// Free List Allocator
+//
+
+gb_inline void gb_free_list_init(gbFreeList *fl, void *start, isize size) {
+	GB_ASSERT(size > gb_size_of(gbFreeListBlock));
+
+	fl->physical_start   = start;
+	fl->total_size       = size;
+	fl->curr_block       = cast(gbFreeListBlock *)start;
+	fl->curr_block->size = size;
+	fl->curr_block->next = NULL;
+}
+
+
+gb_inline void gb_free_list_init_from_allocator(gbFreeList *fl, gbAllocator backing, isize size) {
+	void *start = gb_alloc(backing, size);
+	gb_free_list_init(fl, start, size);
+}
+
+
+
+gb_inline gbAllocator gb_free_list_allocator(gbFreeList *fl) {
+	gbAllocator a;
+	a.proc = gb_free_list_allocator_proc;
+	a.data = fl;
+	return a;
+}
+
+GB_ALLOCATOR_PROC(gb_free_list_allocator_proc) {
+	gbFreeList *fl = cast(gbFreeList *)allocator_data;
+	void *ptr = NULL;
+
+	GB_ASSERT_NOT_NULL(fl);
+
+	switch (type) {
+	case gbAllocation_Alloc: {
+		gbFreeListBlock *prev_block = NULL;
+		gbFreeListBlock *curr_block = fl->curr_block;
+
+		while (curr_block) {
+			isize total_size;
+			gbAllocationHeader *header;
+
+			total_size = size + alignment + gb_size_of(gbAllocationHeader);
+
+			if (curr_block->size < total_size) {
+				prev_block = curr_block;
+				curr_block = curr_block->next;
+				continue;
+			}
+
+			if (curr_block->size - total_size <= gb_size_of(gbAllocationHeader)) {
+				total_size = curr_block->size;
+
+				if (prev_block)
+					prev_block->next = curr_block->next;
+				else
+					fl->curr_block = curr_block->next;
+			} else {
+				// NOTE(bill): Create a new block for the remaining memory
+				gbFreeListBlock *next_block;
+				next_block = cast(gbFreeListBlock *)gb_pointer_add(curr_block, total_size);
+
+				GB_ASSERT(cast(void *)next_block < gb_pointer_add(fl->physical_start, fl->total_size));
+
+				next_block->size = curr_block->size - total_size;
+				next_block->next = curr_block->next;
+
+				if (prev_block)
+					prev_block->next = next_block;
+				else
+					fl->curr_block = next_block;
+			}
+
+
+			// TODO(bill): Set Header Info
+			header = cast(gbAllocationHeader *)curr_block;
+			ptr = gb_align_forward(header+1, alignment);
+			gb_allocation_header_fill(header, ptr, size);
+
+			fl->total_allocated += total_size;
+			fl->allocation_count++;
+
+
+			if (flags & gbAllocatorFlag_ClearToZero)
+				gb_zero_size(ptr, size);
+			return ptr;
+		}
+		// NOTE(bill): if ptr == NULL, ran out of free list memory! FUCK!
+		return NULL;
+	} break;
+
+	case gbAllocation_Free: {
+		gbAllocationHeader *header = gb_allocation_header(old_memory);
+		isize block_size = header->size;
+		uintptr block_start, block_end;
+		gbFreeListBlock *prev_block = NULL;
+		gbFreeListBlock *curr_block = fl->curr_block;
+
+		block_start = cast(uintptr)header;
+		block_end   = cast(uintptr)block_start + block_size;
+
+		while (curr_block) {
+			if (cast(uintptr)curr_block >= block_end)
+				break;
+			prev_block = curr_block;
+			curr_block = curr_block->next;
+		}
+
+		if (prev_block == NULL) {
+			prev_block = cast(gbFreeListBlock *)block_start;
+			prev_block->size = block_size;
+			prev_block->next = fl->curr_block;
+
+			fl->curr_block = prev_block;
+		} else if ((cast(uintptr)prev_block + prev_block->size) == block_start) {
+			prev_block->size += block_size;
+		} else {
+			gbFreeListBlock *tmp = cast(gbFreeListBlock *)block_start;
+			tmp->size = block_size;
+			tmp->next = prev_block->next;
+			prev_block->next = tmp;
+
+			prev_block = tmp;
+		}
+
+		if (curr_block && (cast(uintptr)curr_block == block_end)) {
+			prev_block->size += curr_block->size;
+			prev_block->next = curr_block->next;
+		}
+
+		fl->allocation_count--;
+		fl->total_allocated -= block_size;
+	} break;
+
+	case gbAllocation_FreeAll:
+		gb_free_list_init(fl, fl->physical_start, fl->total_size);
+		break;
+
+	case gbAllocation_Resize:
+		ptr = gb_default_resize_align(gb_free_list_allocator(fl), old_memory, old_size, size, alignment);
+		break;
+	}
+
+	return ptr;
+}
+
+
+
+void gb_scratch_memory_init(gbScratchMemory *s, void *start, isize size) {
+	s->physical_start = start;
+	s->total_size     = size;
+	s->alloc_point    = start;
+	s->free_point     = start;
+}
+
+
+b32 gb_scratch_memory_is_in_use(gbScratchMemory *s, void *ptr) {
+	if (s->free_point == s->alloc_point) return false;
+	if (s->alloc_point > s->free_point)
+		return ptr >= s->free_point && ptr < s->alloc_point;
+	return ptr >= s->free_point || ptr < s->alloc_point;
+}
+
+
+gbAllocator gb_scratch_allocator(gbScratchMemory *s) {
+	gbAllocator a;
+	a.proc = gb_scratch_allocator_proc;
+	a.data = s;
+	return a;
+}
+
+GB_ALLOCATOR_PROC(gb_scratch_allocator_proc) {
+	gbScratchMemory *s = cast(gbScratchMemory *)allocator_data;
+	void *ptr = NULL;
+	GB_ASSERT_NOT_NULL(s);
+
+	switch (type) {
+	case gbAllocation_Alloc: {
+		void *pt = s->alloc_point;
+		gbAllocationHeader *header = cast(gbAllocationHeader *)pt;
+		void *data = gb_align_forward(header+1, alignment);
+		void *end = gb_pointer_add(s->physical_start, s->total_size);
+
+		GB_ASSERT(alignment % 4 == 0);
+		size = ((size + 3)/4)*4;
+		pt = gb_pointer_add(pt, size);
+
+		// NOTE(bill): Wrap around
+		if (pt > end) {
+			header->size = gb_pointer_diff(header, end) | GB_ISIZE_HIGH_BIT;
+			pt = s->physical_start;
+			header = cast(gbAllocationHeader *)pt;
+			data = gb_align_forward(header+1, alignment);
+			pt = gb_pointer_add(pt, size);
+		}
+
+		if (!gb_scratch_memory_is_in_use(s, pt)) {
+			gb_allocation_header_fill(header, pt, gb_pointer_diff(header, pt));
+			s->alloc_point = cast(u8 *)pt;
+			ptr = data;
+		}
+
+		if (flags & gbAllocatorFlag_ClearToZero)
+			gb_zero_size(ptr, size);
+	} break;
+
+	case gbAllocation_Free: {
+		if (old_memory) {
+			void *end = gb_pointer_add(s->physical_start, s->total_size);
+			if (old_memory < s->physical_start || old_memory >= end) {
+				GB_ASSERT(false);
+			} else {
+				// NOTE(bill): Mark as free
+				gbAllocationHeader *h = gb_allocation_header(old_memory);
+				GB_ASSERT((h->size & GB_ISIZE_HIGH_BIT) == 0);
+				h->size = h->size | GB_ISIZE_HIGH_BIT;
+
+				while (s->free_point != s->alloc_point) {
+					gbAllocationHeader *header = cast(gbAllocationHeader *)s->free_point;
+					if ((header->size & GB_ISIZE_HIGH_BIT) == 0)
+						break;
+
+					s->free_point = gb_pointer_add(s->free_point, h->size & (~GB_ISIZE_HIGH_BIT));
+					if (s->free_point == end)
+						s->free_point = s->physical_start;
+				}
+			}
+		}
+	} break;
+
+	case gbAllocation_FreeAll:
+		s->alloc_point = s->physical_start;
+		s->free_point  = s->physical_start;
+		break;
+
+	case gbAllocation_Resize:
+		ptr = gb_default_resize_align(gb_scratch_allocator(s), old_memory, old_size, size, alignment);
+		break;
+	}
+
+	return ptr;
+}
+
+
+
+
+
+
+////////////////////////////////////////////////////////////////
+//
+// Sorting
+//
+//
+
+// TODO(bill): Should I make all the macros local?
+
+#define GB__COMPARE_PROC(Type) \
+gb_global isize gb__##Type##_cmp_offset; GB_COMPARE_PROC(gb__##Type##_cmp) { \
+	Type const p = *cast(Type const *)gb_pointer_add_const(a, gb__##Type##_cmp_offset); \
+	Type const q = *cast(Type const *)gb_pointer_add_const(b, gb__##Type##_cmp_offset); \
+	return p < q ? -1 : p > q; \
+} \
+GB_COMPARE_PROC_PTR(gb_##Type##_cmp(isize offset)) { \
+	gb__##Type##_cmp_offset = offset; \
+	return &gb__##Type##_cmp; \
+}
+
+
+GB__COMPARE_PROC(i16);
+GB__COMPARE_PROC(i32);
+GB__COMPARE_PROC(i64);
+GB__COMPARE_PROC(isize);
+GB__COMPARE_PROC(f32);
+GB__COMPARE_PROC(f64);
+GB__COMPARE_PROC(char);
+
+// NOTE(bill): str_cmp is special as it requires a funny type and funny comparison
+gb_global isize gb__str_cmp_offset; GB_COMPARE_PROC(gb__str_cmp) {
+	char const *p = *cast(char const **)gb_pointer_add_const(a, gb__str_cmp_offset);
+	char const *q = *cast(char const **)gb_pointer_add_const(b, gb__str_cmp_offset);
+	return gb_strcmp(p, q);
+}
+GB_COMPARE_PROC_PTR(gb_str_cmp(isize offset)) {
+	gb__str_cmp_offset = offset;
+	return &gb__str_cmp;
+}
+
+#undef GB__COMPARE_PROC
+
+
+
+
+// TODO(bill): Make user definable?
+#define GB__SORT_STACK_SIZE            64
+#define GB__SORT_INSERT_SORT_THRESHOLD  8
+
+#define GB__SORT_PUSH(_base, _limit) do { \
+	stack_ptr[0] = (_base); \
+	stack_ptr[1] = (_limit); \
+	stack_ptr += 2; \
+} while (0)
+
+
+#define GB__SORT_POP(_base, _limit) do { \
+	stack_ptr -= 2; \
+	(_base)  = stack_ptr[0]; \
+	(_limit) = stack_ptr[1]; \
+} while (0)
+
+
+
+void gb_sort(void *base_, isize count, isize size, gbCompareProc cmp) {
+	u8 *i, *j;
+	u8 *base = cast(u8 *)base_;
+	u8 *limit = base + count*size;
+	isize threshold = GB__SORT_INSERT_SORT_THRESHOLD * size;
+
+	// NOTE(bill): Prepare the stack
+	u8 *stack[GB__SORT_STACK_SIZE] = {0};
+	u8 **stack_ptr = stack;
+
+	for (;;) {
+		if ((limit-base) > threshold) {
+			// NOTE(bill): Quick sort
+			i = base + size;
+			j = limit - size;
+
+			gb_memswap(((limit-base)/size/2) * size + base, base, size);
+			if (cmp(i, j) > 0)    gb_memswap(i, j, size);
+			if (cmp(base, j) > 0) gb_memswap(base, j, size);
+			if (cmp(i, base) > 0) gb_memswap(i, base, size);
+
+			for (;;) {
+				do i += size; while (cmp(i, base) < 0);
+				do j -= size; while (cmp(j, base) > 0);
+				if (i > j) break;
+				gb_memswap(i, j, size);
+			}
+
+			gb_memswap(base, j, size);
+
+			if (j - base > limit - i) {
+				GB__SORT_PUSH(base, j);
+				base = i;
+			} else {
+				GB__SORT_PUSH(i, limit);
+				limit = j;
+			}
+		} else {
+			// NOTE(bill): Insertion sort
+			for (j = base, i = j+size;
+			     i < limit;
+			     j = i, i += size) {
+				for (; cmp(j, j+size) > 0; j -= size) {
+					gb_memswap(j, j+size, size);
+					if (j == base) break;
+				}
+			}
+
+			if (stack_ptr == stack) break; // NOTE(bill): Sorting is done!
+			GB__SORT_POP(base, limit);
+		}
+	}
+}
+
+#undef GB__SORT_PUSH
+#undef GB__SORT_POP
+
+
+#define GB_RADIX_SORT_PROC_GEN(Type) GB_RADIX_SORT_PROC(Type) { \
+	Type *source = items; \
+	Type *dest   = temp; \
+	isize byte_index, i, byte_max = 8*gb_size_of(Type); \
+	for (byte_index = 0; byte_index < byte_max; byte_index += 8) { \
+		isize offsets[256] = {0}; \
+		isize total = 0; \
+		/* NOTE(bill): First pass - count how many of each key */ \
+		for (i = 0; i < count; i++) { \
+			Type radix_value = source[i]; \
+			Type radix_piece = (radix_value >> byte_index) & 0xff; \
+			offsets[radix_piece]++; \
+		} \
+		/* NOTE(bill): Change counts to offsets */ \
+		for (i = 0; i < gb_count_of(offsets); i++) { \
+			isize skcount = offsets[i]; \
+			offsets[i] = total; \
+			total += skcount; \
+		} \
+		/* NOTE(bill): Second pass - place elements into the right location */ \
+		for (i = 0; i < count; i++) { \
+			Type radix_value = source[i]; \
+			Type radix_piece = (radix_value >> byte_index) & 0xff; \
+			dest[offsets[radix_piece]++] = source[i]; \
+		} \
+		gb_swap(Type *gb_restrict, source, dest); \
+	} \
+}
+
+GB_RADIX_SORT_PROC_GEN(u8);
+GB_RADIX_SORT_PROC_GEN(u16);
+GB_RADIX_SORT_PROC_GEN(u32);
+GB_RADIX_SORT_PROC_GEN(u64);
+
+gb_inline isize gb_binary_search(void const *base, isize count, isize size, void const *key, gbCompareProc compare_proc) {
+	isize start = 0;
+	isize end = count;
+
+	while (start < end) {
+		isize mid = start + (end-start)/2;
+		isize result = compare_proc(key, cast(u8 *)base + mid*size);
+		if (result < 0)
+			end = mid;
+		else if (result > 0)
+			start = mid+1;
+		else
+			return mid;
+	}
+
+	return -1;
+}
+
+void gb_shuffle(void *base, isize count, isize size) {
+	u8 *a;
+	isize i, j;
+	gbRandom random; gb_random_init(&random);
+
+	a = cast(u8 *)base + (count-1) * size;
+	for (i = count; i > 1; i--) {
+		j = gb_random_gen_isize(&random) % i;
+		gb_memswap(a, cast(u8 *)base + j*size, size);
+		a -= size;
+	}
+}
+
+void gb_reverse(void *base, isize count, isize size) {
+	isize i, j = count-1;
+	for (i = 0; i < j; i++, j++)
+		gb_memswap(cast(u8 *)base + i*size, cast(u8 *)base + j*size, size);
+}
+
+
+
+////////////////////////////////////////////////////////////////
+//
+// Char things
+//
+//
+
+
+
+
+gb_inline char gb_char_to_lower(char c) {
+	if (c >= 'A' && c <= 'Z')
+		return 'a' + (c - 'A');
+	return c;
+}
+
+gb_inline char gb_char_to_upper(char c) {
+	if (c >= 'a' && c <= 'z')
+		return 'A' + (c - 'a');
+	return c;
+}
+
+gb_inline b32 gb_char_is_space(char c) {
+	if (c == ' '  ||
+	    c == '\t' ||
+	    c == '\n' ||
+	    c == '\r' ||
+	    c == '\f' ||
+	    c == '\v')
+	    return true;
+	return false;
+}
+
+gb_inline b32 gb_char_is_digit(char c) {
+	if (c >= '0' && c <= '9')
+		return true;
+	return false;
+}
+
+gb_inline b32 gb_char_is_hex_digit(char c) {
+	if (gb_char_is_digit(c) ||
+	    (c >= 'a' && c <= 'f') ||
+	    (c >= 'A' && c <= 'F'))
+	    return true;
+	return false;
+}
+
+gb_inline b32 gb_char_is_alpha(char c) {
+	if ((c >= 'A' && c <= 'Z') ||
+	    (c >= 'a' && c <= 'z'))
+	    return true;
+	return false;
+}
+
+gb_inline b32 gb_char_is_alphanumeric(char c) {
+	return gb_char_is_alpha(c) || gb_char_is_digit(c);
+}
+
+gb_inline i32 gb_digit_to_int(char c) {
+	return gb_char_is_digit(c) ? c - '0' : c - 'W';
+}
+
+gb_inline i32 gb_hex_digit_to_int(char c) {
+	if (gb_char_is_digit(c))
+		return gb_digit_to_int(c);
+	else if (gb_is_between(c, 'a', 'f'))
+		return c - 'a' + 10;
+	else if (gb_is_between(c, 'A', 'F'))
+		return c - 'A' + 10;
+	return 0;
+}
+
+
+
+
+gb_inline void gb_str_to_lower(char *str) {
+	if (!str) return;
+	while (*str) {
+		*str = gb_char_to_lower(*str);
+		str++;
+	}
+}
+
+gb_inline void gb_str_to_upper(char *str) {
+	if (!str) return;
+	while (*str) {
+		*str = gb_char_to_upper(*str);
+		str++;
+	}
+}
+
+
+gb_inline isize gb_strlen(char const *str) {
+	char const *begin = str;
+	isize const *w;
+	while (cast(uintptr)str % sizeof(usize)) {
+		if (!*str)
+			return str - begin;
+		str++;
+	}
+	w = cast(isize const *)str;
+	while (!GB__HAS_ZERO(*w))
+		w++;
+	str = cast(char const *)w;
+	while (*str)
+		str++;
+	return str - begin;
+}
+
+gb_inline isize gb_strnlen(char const *str, isize max_len) {
+	char const *end = cast(char const *)gb_memchr(str, 0, max_len);
+	if (end)
+		return end - str;
+	return max_len;
+}
+
+gb_inline isize gb_utf8_strlen(u8 const *str) {
+	isize result = 0;
+	for (; *str; str++) {
+		if ((*str & 0xc0) != 0x80)
+			result++;
+	}
+	return result;
+}
+
+gb_inline isize gb_utf8_strnlen(u8 const *str, isize max_len) {
+	isize result = 0;
+	for (; *str && result < max_len; str++) {
+		if ((*str & 0xc0) != 0x80)
+			result++;
+	}
+	return result;
+}
+
+
+gb_inline i32 gb_strcmp(char const *s1, char const *s2) {
+	while (*s1 && (*s1 == *s2)) {
+		s1++, s2++;
+	}
+	return *(u8 *)s1 - *(u8 *)s2;
+}
+
+gb_inline char *gb_strcpy(char *dest, char const *source) {
+	GB_ASSERT_NOT_NULL(dest);
+	if (source) {
+		char *str = dest;
+		while (*source) *str++ = *source++;
+	}
+	return dest;
+}
+
+
+gb_inline char *gb_strncpy(char *dest, char const *source, isize len) {
+	GB_ASSERT_NOT_NULL(dest);
+	if (source) {
+		char *str = dest;
+		while (len > 0 && *source) {
+			*str++ = *source++;
+			len--;
+		}
+		while (len > 0) {
+			*str++ = '\0';
+			len--;
+		}
+	}
+	return dest;
+}
+
+gb_inline isize gb_strlcpy(char *dest, char const *source, isize len) {
+	isize result = 0;
+	GB_ASSERT_NOT_NULL(dest);
+	if (source) {
+		char const *source_start = source;
+		char *str = dest;
+		while (len > 0 && *source) {
+			*str++ = *source++;
+			len--;
+		}
+		while (len > 0) {
+			*str++ = '\0';
+			len--;
+		}
+
+		result = source - source_start;
+	}
+	return result;
+}
+
+gb_inline char *gb_strrev(char *str) {
+	isize len = gb_strlen(str);
+	char *a = str + 0;
+	char *b = str + len-1;
+	len /= 2;
+	while (len--) {
+		gb_swap(char, *a, *b);
+		a++, b--;
+	}
+	return str;
+}
+
+
+
+
+gb_inline i32 gb_strncmp(char const *s1, char const *s2, isize len) {
+	for (; len > 0;
+	     s1++, s2++, len--) {
+		if (*s1 != *s2)
+			return ((s1 < s2) ? -1 : +1);
+		else if (*s1 == '\0')
+			return 0;
+	}
+	return 0;
+}
+
+
+gb_inline char const *gb_strtok(char *output, char const *src, char const *delimit) {
+	while (*src && gb_char_first_occurence(delimit, *src) != NULL)
+		*output++ = *src++;
+
+	*output = 0;
+	return *src ? src+1 : src;
+}
+
+gb_inline b32 gb_str_has_prefix(char const *str, char const *prefix) {
+	while (*prefix) {
+		if (*str++ != *prefix++)
+			return false;
+	}
+	return true;
+}
+
+gb_inline b32 gb_str_has_suffix(char const *str, char const *suffix) {
+	isize i = gb_strlen(str);
+	isize j = gb_strlen(suffix);
+	if (j <= i)
+		return gb_strcmp(str+i-j, suffix) == 0;
+	return false;
+}
+
+
+
+
+gb_inline char const *gb_char_first_occurence(char const *s, char c) {
+	char ch = c;
+	for (; *s != ch; s++) {
+		if (*s == '\0')
+			return NULL;
+	}
+	return s;
+}
+
+
+gb_inline char const *gb_char_last_occurence(char const *s, char c) {
+	char const *result = NULL;
+	do {
+		if (*s == c)
+			result = s;
+	} while (*s++);
+
+	return result;
+}
+
+
+
+gb_inline void gb_str_concat(char *dest, isize dest_len,
+                             char const *src_a, isize src_a_len,
+                             char const *src_b, isize src_b_len) {
+	GB_ASSERT(dest_len >= src_a_len+src_b_len+1);
+	if (dest) {
+		gb_memcopy(dest, src_a, src_a_len);
+		gb_memcopy(dest+src_a_len, src_b, src_b_len);
+		dest[src_a_len+src_b_len] = '\0';
+	}
+}
+
+
+gb_internal isize gb__scan_i64(char const *text, i32 base, i64 *value) {
+	char const *text_begin = text;
+	i64 result = 0;
+	b32 negative = false;
+
+	if (*text == '-') {
+		negative = true;
+		text++;
+	}
+
+	if (base == 16 && gb_strncmp(text, "0x", 2) == 0)
+		text += 2;
+
+	for (;;) {
+		i64 v;
+		if (gb_char_is_digit(*text))
+			v = *text - '0';
+		else if (base == 16 && gb_char_is_hex_digit(*text))
+			v = gb_hex_digit_to_int(*text);
+		else
+			break;
+
+		result *= base;
+		result += v;
+		text++;
+	}
+
+	if (value) {
+		if (negative) result = -result;
+		*value = result;
+	}
+
+	return (text - text_begin);
+}
+
+gb_internal isize gb__scan_u64(char const *text, i32 base, u64 *value) {
+	char const *text_begin = text;
+	u64 result = 0;
+
+	if (base == 16 && gb_strncmp(text, "0x", 2) == 0)
+		text += 2;
+
+	for (;;) {
+		u64 v;
+		if (gb_char_is_digit(*text))
+			v = *text - '0';
+		else if (base == 16 && gb_char_is_hex_digit(*text))
+			v = gb_hex_digit_to_int(*text);
+		else {
+			break;
+		}
+
+		result *= base;
+		result += v;
+		text++;
+	}
+
+	if (value)
+		*value = result;
+
+	return (text - text_begin);
+}
+
+
+// TODO(bill): Make better
+u64 gb_str_to_u64(char const *str, char **end_ptr, i32 base) {
+	isize len;
+	u64 value = 0;
+
+	if (!base) {
+		if ((gb_strlen(str) > 2) && (gb_strncmp(str, "0x", 2) == 0))
+			base = 16;
+		else
+			base = 10;
+	}
+
+	len = gb__scan_u64(str, base, &value);
+	if (end_ptr)
+		*end_ptr = (char *)str + len;
+	return value;
+}
+
+i64 gb_str_to_i64(char const *str, char **end_ptr, i32 base) {
+	isize len;
+	i64 value;
+
+	if (!base) {
+		if ((gb_strlen(str) > 2) && (gb_strncmp(str, "0x", 2) == 0))
+			base = 16;
+		else
+			base = 10;
+	}
+
+	len = gb__scan_i64(str, base, &value);
+	if (end_ptr)
+		*end_ptr = (char *)str + len;
+	return value;
+}
+
+// TODO(bill): Are these good enough for characters?
+gb_global char const gb__num_to_char_table[] =
+	"0123456789"
+	"ABCDEFGHIJKLMNOPQRSTUVWXYZ"
+	"abcdefghijklmnopqrstuvwxyz"
+	"@$";
+
+gb_inline void gb_i64_to_str(i64 value, char *string, i32 base) {
+	char *buf = string;
+	b32 negative = false;
+	if (value < 0) {
+		negative = true;
+		value = -value;
+	}
+	if (value) {
+		while (value > 0) {
+			*buf++ = gb__num_to_char_table[value % base];
+			value /= base;
+		}
+	} else {
+		*buf++ = '0';
+	}
+	if (negative)
+		*buf++ = '-';
+	*buf = '\0';
+	gb_strrev(string);
+}
+
+
+
+gb_inline void gb_u64_to_str(u64 value, char *string, i32 base) {
+	char *buf = string;
+
+	if (value) {
+		while (value > 0) {
+			*buf++ = gb__num_to_char_table[value % base];
+			value /= base;
+		}
+	} else {
+		*buf++ = '0';
+	}
+	*buf = '\0';
+
+	gb_strrev(string);
+}
+
+gb_inline f32 gb_str_to_f32(char const *str, char **end_ptr) {
+	f32 result = 0.0f;
+	isize signed_exponent = 0;
+	char c;
+	for (c = *str; c != '\0' && gb_char_is_digit(c); c = *str++)
+		result = result*10.0f + (c-'0');
+
+	if (c == '.') {
+		for (c = *str++; c != '\0' && gb_char_is_digit(c); c = *str++) {
+			result = result*1.0f + (c-'0');
+			signed_exponent--;
+		}
+	}
+
+	if (c == 'e' || c == 'E') {
+		i64 sign = +1, i = 0;
+		c = *str++;
+		if (c == '+') {
+			c = *str++;
+		} else if (c == '-') {
+			c = *str++;
+			sign = -1;
+		}
+		while (gb_char_is_digit(c)) {
+			i = i*10 + (c-'0');
+			c = *str++;
+		}
+		signed_exponent += i*sign;
+	}
+
+	while (signed_exponent > 0) {
+		result *= 10.0f;
+		signed_exponent--;
+	}
+	while (signed_exponent < 0) {
+		result *= 0.1f;
+		signed_exponent++;
+	}
+
+	if (end_ptr) *end_ptr = cast(char *)str;
+
+	return result;
+}
+
+
+
+gb_inline f64 gb_str_to_f64(char const *str, char **end_ptr) {
+	f64 result = 0.0;
+	isize signed_exponent = 0;
+	char c;
+	for (c = *str; c != '\0' && gb_char_is_digit(c); c = *str++)
+		result = result*10.0 + (c-'0');
+
+
+	if (c == '.') {
+		for (c = *str; c != '\0' && gb_char_is_digit(c); c = *str++) {
+			result = result*1.0 + (c-'0');
+			signed_exponent--;
+		}
+	}
+
+	if (c == 'e' || c == 'E') {
+		i64 sign = +1, i = 0;
+		c = *str++;
+		if (c == '+') {
+			c = *str++;
+		} else if (c == '-') {
+			c = *str++;
+			sign = -1;
+		}
+		while (gb_char_is_digit(c)) {
+			i = i*10 + (c-'0');
+			c = *str++;
+		}
+		signed_exponent += i*sign;
+	}
+
+	while (signed_exponent > 0) {
+		result *= 10.0;
+		signed_exponent--;
+	}
+	while (signed_exponent < 0) {
+		result *= 0.1;
+		signed_exponent++;
+	}
+
+	if (end_ptr) *end_ptr = cast(char *)str;
+
+	return result;
+}
+
+
+
+
+
+
+
+gb_inline void gb__set_string_length  (gbString str, isize len) { GB_STRING_HEADER(str)->length = len; }
+gb_inline void gb__set_string_capacity(gbString str, isize cap) { GB_STRING_HEADER(str)->capacity = cap; }
+
+
+gb_inline gbString gb_string_make(gbAllocator a, char const *str) {
+	isize len = str ? gb_strlen(str) : 0;
+	return gb_string_make_length(a, str, len);
+}
+
+gbString gb_string_make_length(gbAllocator a, void const *init_str, isize num_bytes) {
+	isize header_size = gb_size_of(gbStringHeader);
+	void *ptr = gb_alloc(a, header_size + num_bytes + 1);
+
+	gbString str;
+	gbStringHeader *header;
+
+	if (!init_str) gb_zero_size(ptr, header_size + num_bytes + 1);
+	if (ptr == NULL) return NULL;
+
+	str = cast(char *)ptr + header_size;
+	header = GB_STRING_HEADER(str);
+	header->allocator = a;
+	header->length    = num_bytes;
+	header->capacity  = num_bytes;
+	if (num_bytes && init_str)
+		gb_memcopy(str, init_str, num_bytes);
+	str[num_bytes] = '\0';
+
+	return str;
+}
+
+gb_inline void gb_string_free(gbString str) {
+	if (str) {
+		gbStringHeader *header = GB_STRING_HEADER(str);
+		gb_free(header->allocator, header);
+	}
+
+}
+
+gb_inline gbString gb_string_duplicate(gbAllocator a, gbString const str) { return gb_string_make_length(a, str, gb_string_length(str)); }
+
+gb_inline isize gb_string_length  (gbString const str) { return GB_STRING_HEADER(str)->length; }
+gb_inline isize gb_string_capacity(gbString const str) { return GB_STRING_HEADER(str)->capacity; }
+
+gb_inline isize gb_string_available_space(gbString const str) {
+	gbStringHeader *h = GB_STRING_HEADER(str);
+	if (h->capacity > h->length)
+		return h->capacity - h->length;
+	return 0;
+}
+
+
+gb_inline void gb_string_clear(gbString str) { gb__set_string_length(str, 0); str[0] = '\0'; }
+
+gb_inline gbString gb_string_append(gbString str, gbString const other) { return gb_string_append_length(str, other, gb_string_length(other)); }
+
+gbString gb_string_append_length(gbString str, void const *other, isize other_len) {
+	isize curr_len = gb_string_length(str);
+
+	str = gb_string_make_space_for(str, other_len);
+	if (str == NULL)
+		return NULL;
+
+	gb_memcopy(str + curr_len, other, other_len);
+	str[curr_len + other_len] = '\0';
+	gb__set_string_length(str, curr_len + other_len);
+
+	return str;
+}
+
+gb_inline gbString gb_string_appendc(gbString str, char const *other) {
+	return gb_string_append_length(str, other, gb_strlen(other));
+}
+
+
+gbString gb_string_set(gbString str, char const *cstr) {
+	isize len = gb_strlen(cstr);
+	if (gb_string_capacity(str) < len) {
+		str = gb_string_make_space_for(str, len - gb_string_length(str));
+		if (str == NULL)
+			return NULL;
+	}
+
+	gb_memcopy(str, cstr, len);
+	str[len] = '\0';
+	gb__set_string_length(str, len);
+
+	return str;
+}
+
+
+
+gbString gb_string_make_space_for(gbString str, isize add_len) {
+	isize available = gb_string_available_space(str);
+
+	// NOTE(bill): Return if there is enough space left
+	if (available >= add_len) {
+		return str;
+	} else {
+		isize new_len, old_size, new_size;
+		void *ptr, *new_ptr;
+		gbAllocator a = GB_STRING_HEADER(str)->allocator;
+		gbStringHeader *header;
+
+		new_len = gb_string_length(str) + add_len;
+		ptr = GB_STRING_HEADER(str);
+		old_size = gb_size_of(gbStringHeader) + gb_string_length(str) + 1;
+		new_size = gb_size_of(gbStringHeader) + new_len + 1;
+
+		new_ptr = gb_resize(a, ptr, old_size, new_size);
+		if (new_ptr == NULL) return NULL;
+
+		header = cast(gbStringHeader *)new_ptr;
+		header->allocator = a;
+
+		str = cast(gbString)(header+1);
+		gb__set_string_capacity(str, new_len);
+
+		return str;
+	}
+}
+
+gb_inline isize gb_string_allocation_size(gbString const str) {
+	isize cap = gb_string_capacity(str);
+	return gb_size_of(gbStringHeader) + cap;
+}
+
+
+gb_inline b32 gb_string_are_equal(gbString const lhs, gbString const rhs) {
+	isize lhs_len, rhs_len, i;
+	lhs_len = gb_string_length(lhs);
+	rhs_len = gb_string_length(rhs);
+	if (lhs_len != rhs_len)
+		return false;
+
+	for (i = 0; i < lhs_len; i++) {
+		if (lhs[i] != rhs[i])
+			return false;
+	}
+
+	return true;
+}
+
+
+gbString gb_string_trim(gbString str, char const *cut_set) {
+	char *start, *end, *start_pos, *end_pos;
+	isize len;
+
+	start_pos = start = str;
+	end_pos   = end   = str + gb_string_length(str) - 1;
+
+	while (start_pos <= end && gb_char_first_occurence(cut_set, *start_pos))
+		start_pos++;
+	while (end_pos > start_pos && gb_char_first_occurence(cut_set, *end_pos))
+		end_pos--;
+
+	len = cast(isize)((start_pos > end_pos) ? 0 : ((end_pos - start_pos)+1));
+
+	if (str != start_pos)
+		gb_memmove(str, start_pos, len);
+	str[len] = '\0';
+
+	gb__set_string_length(str, len);
+
+	return str;
+}
+
+gb_inline gbString gb_string_trim_space(gbString str) { return gb_string_trim(str, " \t\r\n\v\f"); }
+
+
+
+
+////////////////////////////////////////////////////////////////
+//
+// Windows UTF-8 Handling
+//
+//
+
+
+u16 *gb_utf8_to_ucs2(u16 *buffer, isize len, u8 const *str) {
+	Rune c;
+	isize i = 0;
+	len--;
+	while (*str) {
+		if (i >= len)
+			return NULL;
+		if (!(*str & 0x80)) {
+			buffer[i++] = *str++;
+		} else if ((*str & 0xe0) == 0xc0) {
+			if (*str < 0xc2)
+				return NULL;
+			c = (*str++ & 0x1f) << 6;
+			if ((*str & 0xc0) != 0x80)
+				return NULL;
+			buffer[i++] = cast(u16)(c + (*str++ & 0x3f));
+		} else if ((*str & 0xf0) == 0xe0) {
+			if (*str == 0xe0 &&
+			    (str[1] < 0xa0 || str[1] > 0xbf))
+				return NULL;
+			if (*str == 0xed && str[1] > 0x9f) // str[1] < 0x80 is checked below
+				return NULL;
+			c = (*str++ & 0x0f) << 12;
+			if ((*str & 0xc0) != 0x80)
+				return NULL;
+			c += (*str++ & 0x3f) << 6;
+			if ((*str & 0xc0) != 0x80)
+				return NULL;
+			buffer[i++] = cast(u16)(c + (*str++ & 0x3f));
+		} else if ((*str & 0xf8) == 0xf0) {
+			if (*str > 0xf4)
+				return NULL;
+			if (*str == 0xf0 && (str[1] < 0x90 || str[1] > 0xbf))
+				return NULL;
+			if (*str == 0xf4 && str[1] > 0x8f) // str[1] < 0x80 is checked below
+				return NULL;
+			c = (*str++ & 0x07) << 18;
+			if ((*str & 0xc0) != 0x80)
+				return NULL;
+			c += (*str++ & 0x3f) << 12;
+			if ((*str & 0xc0) != 0x80)
+				return NULL;
+			c += (*str++ & 0x3f) << 6;
+			if ((*str & 0xc0) != 0x80)
+				return NULL;
+			c += (*str++ & 0x3f);
+			// UTF-8 encodings of values used in surrogate pairs are invalid
+			if ((c & 0xfffff800) == 0xd800)
+				return NULL;
+			if (c >= 0x10000) {
+				c -= 0x10000;
+				if (i+2 > len)
+					return NULL;
+				buffer[i++] = 0xd800 | (0x3ff & (c>>10));
+				buffer[i++] = 0xdc00 | (0x3ff & (c    ));
+			}
+		} else {
+			return NULL;
+		}
+	}
+	buffer[i] = 0;
+	return buffer;
+}
+
+u8 *gb_ucs2_to_utf8(u8 *buffer, isize len, u16 const *str) {
+	isize i = 0;
+	len--;
+	while (*str) {
+		if (*str < 0x80) {
+			if (i+1 > len)
+				return NULL;
+			buffer[i++] = (char) *str++;
+		} else if (*str < 0x800) {
+			if (i+2 > len)
+				return NULL;
+			buffer[i++] = cast(char)(0xc0 + (*str >> 6));
+			buffer[i++] = cast(char)(0x80 + (*str & 0x3f));
+			str += 1;
+		} else if (*str >= 0xd800 && *str < 0xdc00) {
+			Rune c;
+			if (i+4 > len)
+				return NULL;
+			c = ((str[0] - 0xd800) << 10) + ((str[1]) - 0xdc00) + 0x10000;
+			buffer[i++] = cast(char)(0xf0 +  (c >> 18));
+			buffer[i++] = cast(char)(0x80 + ((c >> 12) & 0x3f));
+			buffer[i++] = cast(char)(0x80 + ((c >>  6) & 0x3f));
+			buffer[i++] = cast(char)(0x80 + ((c      ) & 0x3f));
+			str += 2;
+		} else if (*str >= 0xdc00 && *str < 0xe000) {
+			return NULL;
+		} else {
+			if (i+3 > len)
+				return NULL;
+			buffer[i++] = 0xe0 +  (*str >> 12);
+			buffer[i++] = 0x80 + ((*str >>  6) & 0x3f);
+			buffer[i++] = 0x80 + ((*str      ) & 0x3f);
+			str += 1;
+		}
+	}
+	buffer[i] = 0;
+	return buffer;
+}
+
+u16 *gb_utf8_to_ucs2_buf(u8 const *str) { // NOTE(bill): Uses locally persisting buffer
+	gb_local_persist u16 buf[4096];
+	return gb_utf8_to_ucs2(buf, gb_count_of(buf), str);
+}
+
+u8 *gb_ucs2_to_utf8_buf(u16 const *str) { // NOTE(bill): Uses locally persisting buffer
+	gb_local_persist u8 buf[4096];
+	return gb_ucs2_to_utf8(buf, gb_count_of(buf), str);
+}
+
+
+
+gb_global u8 const gb__utf8_first[256] = {
+	0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, // 0x00-0x0F
+	0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, // 0x10-0x1F
+	0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, // 0x20-0x2F
+	0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, // 0x30-0x3F
+	0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, // 0x40-0x4F
+	0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, // 0x50-0x5F
+	0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, // 0x60-0x6F
+	0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, // 0x70-0x7F
+	0xf1, 0xf1, 0xf1, 0xf1, 0xf1, 0xf1, 0xf1, 0xf1, 0xf1, 0xf1, 0xf1, 0xf1, 0xf1, 0xf1, 0xf1, 0xf1, // 0x80-0x8F
+	0xf1, 0xf1, 0xf1, 0xf1, 0xf1, 0xf1, 0xf1, 0xf1, 0xf1, 0xf1, 0xf1, 0xf1, 0xf1, 0xf1, 0xf1, 0xf1, // 0x90-0x9F
+	0xf1, 0xf1, 0xf1, 0xf1, 0xf1, 0xf1, 0xf1, 0xf1, 0xf1, 0xf1, 0xf1, 0xf1, 0xf1, 0xf1, 0xf1, 0xf1, // 0xA0-0xAF
+	0xf1, 0xf1, 0xf1, 0xf1, 0xf1, 0xf1, 0xf1, 0xf1, 0xf1, 0xf1, 0xf1, 0xf1, 0xf1, 0xf1, 0xf1, 0xf1, // 0xB0-0xBF
+	0xf1, 0xf1, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, // 0xC0-0xCF
+	0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, // 0xD0-0xDF
+	0x13, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x23, 0x03, 0x03, // 0xE0-0xEF
+	0x34, 0x04, 0x04, 0x04, 0x44, 0xf1, 0xf1, 0xf1, 0xf1, 0xf1, 0xf1, 0xf1, 0xf1, 0xf1, 0xf1, 0xf1, // 0xF0-0xFF
+};
+
+
+typedef struct gbUtf8AcceptRange {
+	u8 lo, hi;
+} gbUtf8AcceptRange;
+
+gb_global gbUtf8AcceptRange const gb__utf8_accept_ranges[] = {
+	{0x80, 0xbf},
+	{0xa0, 0xbf},
+	{0x80, 0x9f},
+	{0x90, 0xbf},
+	{0x80, 0x8f},
+};
+
+
+isize gb_utf8_decode(u8 const *str, isize str_len, Rune *codepoint_out) {
+	isize width = 0;
+	Rune codepoint = GB_RUNE_INVALID;
+
+	if (str_len > 0) {
+		u8 s0 = str[0];
+		u8 x = gb__utf8_first[s0], sz;
+		u8 b1, b2, b3;
+		gbUtf8AcceptRange accept;
+		if (x > 0xf0) {
+			Rune mask = (cast(Rune)x >> 31) << 31;
+			codepoint = (cast(Rune)s0 & (~mask)) | (GB_RUNE_INVALID & mask);
+			width = 1;
+			goto end;
+		}
+
+		sz = x&7;
+		accept = gb__utf8_accept_ranges[x>>4];
+		if (str_len < gb_size_of(sz))
+			goto invalid_codepoint;
+
+		b1 = str[1];
+		if (b1 < accept.lo || accept.hi < b1)
+			goto invalid_codepoint;
+
+		if (sz == 2) {
+			codepoint = (cast(Rune)s0&0x1f)<<6 | (cast(Rune)b1&0x3f);
+			width = 2;
+			goto end;
+		}
+
+		b2 = str[2];
+		if (!gb_is_between(b2, 0x80, 0xbf))
+			goto invalid_codepoint;
+
+		if (sz == 3) {
+			codepoint = (cast(Rune)s0&0x1f)<<12 | (cast(Rune)b1&0x3f)<<6 | (cast(Rune)b2&0x3f);
+			width = 3;
+			goto end;
+		}
+
+		b3 = str[3];
+		if (!gb_is_between(b3, 0x80, 0xbf))
+			goto invalid_codepoint;
+
+		codepoint = (cast(Rune)s0&0x07)<<18 | (cast(Rune)b1&0x3f)<<12 | (cast(Rune)b2&0x3f)<<6 | (cast(Rune)b3&0x3f);
+		width = 4;
+		goto end;
+
+	invalid_codepoint:
+		codepoint = GB_RUNE_INVALID;
+		width = 1;
+	}
+
+end:
+	if (codepoint_out) *codepoint_out = codepoint;
+	return width;
+}
+
+isize gb_utf8_codepoint_size(u8 const *str, isize str_len) {
+	isize i = 0;
+	for (; i < str_len && str[i]; i++) {
+		if ((str[i] & 0xc0) != 0x80)
+			break;
+	}
+	return i+1;
+}
+
+
+
+
+
+////////////////////////////////////////////////////////////////
+//
+// gbArray
+//
+//
+
+
+gb_no_inline void *gb__array_set_capacity(void *array, isize capacity, isize element_size) {
+	gbArrayHeader *h = GB_ARRAY_HEADER(array);
+
+	GB_ASSERT(element_size > 0);
+
+	if (capacity == h->capacity)
+		return array;
+
+	if (capacity < h->count) {
+		if (h->capacity < capacity) {
+			isize new_capacity = GB_ARRAY_GROW_FORMULA(h->capacity);
+			if (new_capacity < capacity)
+				new_capacity = capacity;
+			gb__array_set_capacity(array, new_capacity, element_size);
+		}
+		h->count = capacity;
+	}
+
+	{
+		isize size = gb_size_of(gbArrayHeader) + element_size*capacity;
+		gbArrayHeader *nh = cast(gbArrayHeader *)gb_alloc(h->allocator, size);
+		gb_memmove(nh, h, gb_size_of(gbArrayHeader) + element_size*h->count);
+		nh->allocator = h->allocator;
+		nh->count     = h->count;
+		nh->capacity  = capacity;
+		gb_free(h->allocator, h);
+		return nh+1;
+	}
+}
+
+
+////////////////////////////////////////////////////////////////
+//
+// Hashing functions
+//
+//
+
+u32 gb_adler32(void const *data, isize len) {
+	u32 const MOD_ALDER = 65521;
+	u32 a = 1, b = 0;
+	isize i, block_len;
+	u8 const *bytes = cast(u8 const *)data;
+
+	block_len = len % 5552;
+
+	while (len) {
+		for (i = 0; i+7 < block_len; i += 8) {
+			a += bytes[0], b += a;
+			a += bytes[1], b += a;
+			a += bytes[2], b += a;
+			a += bytes[3], b += a;
+			a += bytes[4], b += a;
+			a += bytes[5], b += a;
+			a += bytes[6], b += a;
+			a += bytes[7], b += a;
+
+			bytes += 8;
+		}
+		for (; i < block_len; i++)
+			a += *bytes++, b += a;
+
+		a %= MOD_ALDER, b %= MOD_ALDER;
+		len -= block_len;
+		block_len = 5552;
+	}
+
+	return (b << 16) | a;
+}
+
+
+gb_global u32 const GB__CRC32_TABLE[256] = {
+	0x00000000, 0x77073096, 0xee0e612c, 0x990951ba,
+	0x076dc419, 0x706af48f, 0xe963a535, 0x9e6495a3,
+	0x0edb8832, 0x79dcb8a4, 0xe0d5e91e, 0x97d2d988,
+	0x09b64c2b, 0x7eb17cbd, 0xe7b82d07, 0x90bf1d91,
+	0x1db71064, 0x6ab020f2, 0xf3b97148, 0x84be41de,
+	0x1adad47d, 0x6ddde4eb, 0xf4d4b551, 0x83d385c7,
+	0x136c9856, 0x646ba8c0, 0xfd62f97a, 0x8a65c9ec,
+	0x14015c4f, 0x63066cd9, 0xfa0f3d63, 0x8d080df5,
+	0x3b6e20c8, 0x4c69105e, 0xd56041e4, 0xa2677172,
+	0x3c03e4d1, 0x4b04d447, 0xd20d85fd, 0xa50ab56b,
+	0x35b5a8fa, 0x42b2986c, 0xdbbbc9d6, 0xacbcf940,
+	0x32d86ce3, 0x45df5c75, 0xdcd60dcf, 0xabd13d59,
+	0x26d930ac, 0x51de003a, 0xc8d75180, 0xbfd06116,
+	0x21b4f4b5, 0x56b3c423, 0xcfba9599, 0xb8bda50f,
+	0x2802b89e, 0x5f058808, 0xc60cd9b2, 0xb10be924,
+	0x2f6f7c87, 0x58684c11, 0xc1611dab, 0xb6662d3d,
+	0x76dc4190, 0x01db7106, 0x98d220bc, 0xefd5102a,
+	0x71b18589, 0x06b6b51f, 0x9fbfe4a5, 0xe8b8d433,
+	0x7807c9a2, 0x0f00f934, 0x9609a88e, 0xe10e9818,
+	0x7f6a0dbb, 0x086d3d2d, 0x91646c97, 0xe6635c01,
+	0x6b6b51f4, 0x1c6c6162, 0x856530d8, 0xf262004e,
+	0x6c0695ed, 0x1b01a57b, 0x8208f4c1, 0xf50fc457,
+	0x65b0d9c6, 0x12b7e950, 0x8bbeb8ea, 0xfcb9887c,
+	0x62dd1ddf, 0x15da2d49, 0x8cd37cf3, 0xfbd44c65,
+	0x4db26158, 0x3ab551ce, 0xa3bc0074, 0xd4bb30e2,
+	0x4adfa541, 0x3dd895d7, 0xa4d1c46d, 0xd3d6f4fb,
+	0x4369e96a, 0x346ed9fc, 0xad678846, 0xda60b8d0,
+	0x44042d73, 0x33031de5, 0xaa0a4c5f, 0xdd0d7cc9,
+	0x5005713c, 0x270241aa, 0xbe0b1010, 0xc90c2086,
+	0x5768b525, 0x206f85b3, 0xb966d409, 0xce61e49f,
+	0x5edef90e, 0x29d9c998, 0xb0d09822, 0xc7d7a8b4,
+	0x59b33d17, 0x2eb40d81, 0xb7bd5c3b, 0xc0ba6cad,
+	0xedb88320, 0x9abfb3b6, 0x03b6e20c, 0x74b1d29a,
+	0xead54739, 0x9dd277af, 0x04db2615, 0x73dc1683,
+	0xe3630b12, 0x94643b84, 0x0d6d6a3e, 0x7a6a5aa8,
+	0xe40ecf0b, 0x9309ff9d, 0x0a00ae27, 0x7d079eb1,
+	0xf00f9344, 0x8708a3d2, 0x1e01f268, 0x6906c2fe,
+	0xf762575d, 0x806567cb, 0x196c3671, 0x6e6b06e7,
+	0xfed41b76, 0x89d32be0, 0x10da7a5a, 0x67dd4acc,
+	0xf9b9df6f, 0x8ebeeff9, 0x17b7be43, 0x60b08ed5,
+	0xd6d6a3e8, 0xa1d1937e, 0x38d8c2c4, 0x4fdff252,
+	0xd1bb67f1, 0xa6bc5767, 0x3fb506dd, 0x48b2364b,
+	0xd80d2bda, 0xaf0a1b4c, 0x36034af6, 0x41047a60,
+	0xdf60efc3, 0xa867df55, 0x316e8eef, 0x4669be79,
+	0xcb61b38c, 0xbc66831a, 0x256fd2a0, 0x5268e236,
+	0xcc0c7795, 0xbb0b4703, 0x220216b9, 0x5505262f,
+	0xc5ba3bbe, 0xb2bd0b28, 0x2bb45a92, 0x5cb36a04,
+	0xc2d7ffa7, 0xb5d0cf31, 0x2cd99e8b, 0x5bdeae1d,
+	0x9b64c2b0, 0xec63f226, 0x756aa39c, 0x026d930a,
+	0x9c0906a9, 0xeb0e363f, 0x72076785, 0x05005713,
+	0x95bf4a82, 0xe2b87a14, 0x7bb12bae, 0x0cb61b38,
+	0x92d28e9b, 0xe5d5be0d, 0x7cdcefb7, 0x0bdbdf21,
+	0x86d3d2d4, 0xf1d4e242, 0x68ddb3f8, 0x1fda836e,
+	0x81be16cd, 0xf6b9265b, 0x6fb077e1, 0x18b74777,
+	0x88085ae6, 0xff0f6a70, 0x66063bca, 0x11010b5c,
+	0x8f659eff, 0xf862ae69, 0x616bffd3, 0x166ccf45,
+	0xa00ae278, 0xd70dd2ee, 0x4e048354, 0x3903b3c2,
+	0xa7672661, 0xd06016f7, 0x4969474d, 0x3e6e77db,
+	0xaed16a4a, 0xd9d65adc, 0x40df0b66, 0x37d83bf0,
+	0xa9bcae53, 0xdebb9ec5, 0x47b2cf7f, 0x30b5ffe9,
+	0xbdbdf21c, 0xcabac28a, 0x53b39330, 0x24b4a3a6,
+	0xbad03605, 0xcdd70693, 0x54de5729, 0x23d967bf,
+	0xb3667a2e, 0xc4614ab8, 0x5d681b02, 0x2a6f2b94,
+	0xb40bbe37, 0xc30c8ea1, 0x5a05df1b, 0x2d02ef8d,
+};
+
+gb_global u64 const GB__CRC64_TABLE[256] = {
+	0x0000000000000000ull, 0x42f0e1eba9ea3693ull, 0x85e1c3d753d46d26ull, 0xc711223cfa3e5bb5ull,
+	0x493366450e42ecdfull, 0x0bc387aea7a8da4cull, 0xccd2a5925d9681f9ull, 0x8e224479f47cb76aull,
+	0x9266cc8a1c85d9beull, 0xd0962d61b56fef2dull, 0x17870f5d4f51b498ull, 0x5577eeb6e6bb820bull,
+	0xdb55aacf12c73561ull, 0x99a54b24bb2d03f2ull, 0x5eb4691841135847ull, 0x1c4488f3e8f96ed4ull,
+	0x663d78ff90e185efull, 0x24cd9914390bb37cull, 0xe3dcbb28c335e8c9ull, 0xa12c5ac36adfde5aull,
+	0x2f0e1eba9ea36930ull, 0x6dfeff5137495fa3ull, 0xaaefdd6dcd770416ull, 0xe81f3c86649d3285ull,
+	0xf45bb4758c645c51ull, 0xb6ab559e258e6ac2ull, 0x71ba77a2dfb03177ull, 0x334a9649765a07e4ull,
+	0xbd68d2308226b08eull, 0xff9833db2bcc861dull, 0x388911e7d1f2dda8ull, 0x7a79f00c7818eb3bull,
+	0xcc7af1ff21c30bdeull, 0x8e8a101488293d4dull, 0x499b3228721766f8ull, 0x0b6bd3c3dbfd506bull,
+	0x854997ba2f81e701ull, 0xc7b97651866bd192ull, 0x00a8546d7c558a27ull, 0x4258b586d5bfbcb4ull,
+	0x5e1c3d753d46d260ull, 0x1cecdc9e94ace4f3ull, 0xdbfdfea26e92bf46ull, 0x990d1f49c77889d5ull,
+	0x172f5b3033043ebfull, 0x55dfbadb9aee082cull, 0x92ce98e760d05399ull, 0xd03e790cc93a650aull,
+	0xaa478900b1228e31ull, 0xe8b768eb18c8b8a2ull, 0x2fa64ad7e2f6e317ull, 0x6d56ab3c4b1cd584ull,
+	0xe374ef45bf6062eeull, 0xa1840eae168a547dull, 0x66952c92ecb40fc8ull, 0x2465cd79455e395bull,
+	0x3821458aada7578full, 0x7ad1a461044d611cull, 0xbdc0865dfe733aa9ull, 0xff3067b657990c3aull,
+	0x711223cfa3e5bb50ull, 0x33e2c2240a0f8dc3ull, 0xf4f3e018f031d676ull, 0xb60301f359dbe0e5ull,
+	0xda050215ea6c212full, 0x98f5e3fe438617bcull, 0x5fe4c1c2b9b84c09ull, 0x1d14202910527a9aull,
+	0x93366450e42ecdf0ull, 0xd1c685bb4dc4fb63ull, 0x16d7a787b7faa0d6ull, 0x5427466c1e109645ull,
+	0x4863ce9ff6e9f891ull, 0x0a932f745f03ce02ull, 0xcd820d48a53d95b7ull, 0x8f72eca30cd7a324ull,
+	0x0150a8daf8ab144eull, 0x43a04931514122ddull, 0x84b16b0dab7f7968ull, 0xc6418ae602954ffbull,
+	0xbc387aea7a8da4c0ull, 0xfec89b01d3679253ull, 0x39d9b93d2959c9e6ull, 0x7b2958d680b3ff75ull,
+	0xf50b1caf74cf481full, 0xb7fbfd44dd257e8cull, 0x70eadf78271b2539ull, 0x321a3e938ef113aaull,
+	0x2e5eb66066087d7eull, 0x6cae578bcfe24bedull, 0xabbf75b735dc1058ull, 0xe94f945c9c3626cbull,
+	0x676dd025684a91a1ull, 0x259d31cec1a0a732ull, 0xe28c13f23b9efc87ull, 0xa07cf2199274ca14ull,
+	0x167ff3eacbaf2af1ull, 0x548f120162451c62ull, 0x939e303d987b47d7ull, 0xd16ed1d631917144ull,
+	0x5f4c95afc5edc62eull, 0x1dbc74446c07f0bdull, 0xdaad56789639ab08ull, 0x985db7933fd39d9bull,
+	0x84193f60d72af34full, 0xc6e9de8b7ec0c5dcull, 0x01f8fcb784fe9e69ull, 0x43081d5c2d14a8faull,
+	0xcd2a5925d9681f90ull, 0x8fdab8ce70822903ull, 0x48cb9af28abc72b6ull, 0x0a3b7b1923564425ull,
+	0x70428b155b4eaf1eull, 0x32b26afef2a4998dull, 0xf5a348c2089ac238ull, 0xb753a929a170f4abull,
+	0x3971ed50550c43c1ull, 0x7b810cbbfce67552ull, 0xbc902e8706d82ee7ull, 0xfe60cf6caf321874ull,
+	0xe224479f47cb76a0ull, 0xa0d4a674ee214033ull, 0x67c58448141f1b86ull, 0x253565a3bdf52d15ull,
+	0xab1721da49899a7full, 0xe9e7c031e063acecull, 0x2ef6e20d1a5df759ull, 0x6c0603e6b3b7c1caull,
+	0xf6fae5c07d3274cdull, 0xb40a042bd4d8425eull, 0x731b26172ee619ebull, 0x31ebc7fc870c2f78ull,
+	0xbfc9838573709812ull, 0xfd39626eda9aae81ull, 0x3a28405220a4f534ull, 0x78d8a1b9894ec3a7ull,
+	0x649c294a61b7ad73ull, 0x266cc8a1c85d9be0ull, 0xe17dea9d3263c055ull, 0xa38d0b769b89f6c6ull,
+	0x2daf4f0f6ff541acull, 0x6f5faee4c61f773full, 0xa84e8cd83c212c8aull, 0xeabe6d3395cb1a19ull,
+	0x90c79d3fedd3f122ull, 0xd2377cd44439c7b1ull, 0x15265ee8be079c04ull, 0x57d6bf0317edaa97ull,
+	0xd9f4fb7ae3911dfdull, 0x9b041a914a7b2b6eull, 0x5c1538adb04570dbull, 0x1ee5d94619af4648ull,
+	0x02a151b5f156289cull, 0x4051b05e58bc1e0full, 0x87409262a28245baull, 0xc5b073890b687329ull,
+	0x4b9237f0ff14c443ull, 0x0962d61b56fef2d0ull, 0xce73f427acc0a965ull, 0x8c8315cc052a9ff6ull,
+	0x3a80143f5cf17f13ull, 0x7870f5d4f51b4980ull, 0xbf61d7e80f251235ull, 0xfd913603a6cf24a6ull,
+	0x73b3727a52b393ccull, 0x31439391fb59a55full, 0xf652b1ad0167feeaull, 0xb4a25046a88dc879ull,
+	0xa8e6d8b54074a6adull, 0xea16395ee99e903eull, 0x2d071b6213a0cb8bull, 0x6ff7fa89ba4afd18ull,
+	0xe1d5bef04e364a72ull, 0xa3255f1be7dc7ce1ull, 0x64347d271de22754ull, 0x26c49cccb40811c7ull,
+	0x5cbd6cc0cc10fafcull, 0x1e4d8d2b65facc6full, 0xd95caf179fc497daull, 0x9bac4efc362ea149ull,
+	0x158e0a85c2521623ull, 0x577eeb6e6bb820b0ull, 0x906fc95291867b05ull, 0xd29f28b9386c4d96ull,
+	0xcedba04ad0952342ull, 0x8c2b41a1797f15d1ull, 0x4b3a639d83414e64ull, 0x09ca82762aab78f7ull,
+	0x87e8c60fded7cf9dull, 0xc51827e4773df90eull, 0x020905d88d03a2bbull, 0x40f9e43324e99428ull,
+	0x2cffe7d5975e55e2ull, 0x6e0f063e3eb46371ull, 0xa91e2402c48a38c4ull, 0xebeec5e96d600e57ull,
+	0x65cc8190991cb93dull, 0x273c607b30f68faeull, 0xe02d4247cac8d41bull, 0xa2dda3ac6322e288ull,
+	0xbe992b5f8bdb8c5cull, 0xfc69cab42231bacfull, 0x3b78e888d80fe17aull, 0x7988096371e5d7e9ull,
+	0xf7aa4d1a85996083ull, 0xb55aacf12c735610ull, 0x724b8ecdd64d0da5ull, 0x30bb6f267fa73b36ull,
+	0x4ac29f2a07bfd00dull, 0x08327ec1ae55e69eull, 0xcf235cfd546bbd2bull, 0x8dd3bd16fd818bb8ull,
+	0x03f1f96f09fd3cd2ull, 0x41011884a0170a41ull, 0x86103ab85a2951f4ull, 0xc4e0db53f3c36767ull,
+	0xd8a453a01b3a09b3ull, 0x9a54b24bb2d03f20ull, 0x5d45907748ee6495ull, 0x1fb5719ce1045206ull,
+	0x919735e51578e56cull, 0xd367d40ebc92d3ffull, 0x1476f63246ac884aull, 0x568617d9ef46bed9ull,
+	0xe085162ab69d5e3cull, 0xa275f7c11f7768afull, 0x6564d5fde549331aull, 0x279434164ca30589ull,
+	0xa9b6706fb8dfb2e3ull, 0xeb46918411358470ull, 0x2c57b3b8eb0bdfc5ull, 0x6ea7525342e1e956ull,
+	0x72e3daa0aa188782ull, 0x30133b4b03f2b111ull, 0xf7021977f9cceaa4ull, 0xb5f2f89c5026dc37ull,
+	0x3bd0bce5a45a6b5dull, 0x79205d0e0db05dceull, 0xbe317f32f78e067bull, 0xfcc19ed95e6430e8ull,
+	0x86b86ed5267cdbd3ull, 0xc4488f3e8f96ed40ull, 0x0359ad0275a8b6f5ull, 0x41a94ce9dc428066ull,
+	0xcf8b0890283e370cull, 0x8d7be97b81d4019full, 0x4a6acb477bea5a2aull, 0x089a2aacd2006cb9ull,
+	0x14dea25f3af9026dull, 0x562e43b4931334feull, 0x913f6188692d6f4bull, 0xd3cf8063c0c759d8ull,
+	0x5dedc41a34bbeeb2ull, 0x1f1d25f19d51d821ull, 0xd80c07cd676f8394ull, 0x9afce626ce85b507ull,
+};
+
+u32 gb_crc32(void const *data, isize len) {
+	isize remaining;
+	u32 result = ~(cast(u32)0);
+	u8 const *c = cast(u8 const *)data;
+	for (remaining = len; remaining--; c++)
+		result = (result >> 8) ^ (GB__CRC32_TABLE[(result ^ *c) & 0xff]);
+	return ~result;
+}
+
+u64 gb_crc64(void const *data, isize len) {
+	isize remaining;
+	u64 result = ~(cast(u64)0);
+	u8 const *c = cast(u8 const *)data;
+	for (remaining = len; remaining--; c++)
+		result = (result >> 8) ^ (GB__CRC64_TABLE[(result ^ *c) & 0xff]);
+	return ~result;
+}
+
+u32 gb_fnv32(void const *data, isize len) {
+	isize i;
+	u32 h = 0x811c9dc5;
+	u8 const *c = cast(u8 const *)data;
+
+	for (i = 0; i < len; i++)
+		h = (h * 0x01000193) ^ c[i];
+
+	return h;
+}
+
+u64 gb_fnv64(void const *data, isize len) {
+	isize i;
+	u64 h = 0xcbf29ce484222325ull;
+	u8 const *c = cast(u8 const *)data;
+
+	for (i = 0; i < len; i++)
+		h = (h * 0x100000001b3ll) ^ c[i];
+
+	return h;
+}
+
+u32 gb_fnv32a(void const *data, isize len) {
+	isize i;
+	u32 h = 0x811c9dc5;
+	u8 const *c = cast(u8 const *)data;
+
+	for (i = 0; i < len; i++)
+		h = (h ^ c[i]) * 0x01000193;
+
+	return h;
+}
+
+u64 gb_fnv64a(void const *data, isize len) {
+	isize i;
+	u64 h = 0xcbf29ce484222325ull;
+	u8 const *c = cast(u8 const *)data;
+
+	for (i = 0; i < len; i++)
+		h = (h ^ c[i]) * 0x100000001b3ll;
+
+	return h;
+}
+
+gb_inline u32 gb_murmur32(void const *data, isize len) { return gb_murmur32_seed(data, len, 0x9747b28c); }
+gb_inline u64 gb_murmur64(void const *data, isize len) { return gb_murmur64_seed(data, len, 0x9747b28c); }
+
+u32 gb_murmur32_seed(void const *data, isize len, u32 seed) {
+	u32 const c1 = 0xcc9e2d51;
+	u32 const c2 = 0x1b873593;
+	u32 const r1 = 15;
+	u32 const r2 = 13;
+	u32 const m  = 5;
+	u32 const n  = 0xe6546b64;
+
+	isize i, nblocks = len / 4;
+	u32 hash = seed, k1 = 0;
+	u32 const *blocks = cast(u32 const*)data;
+	u8 const *tail = cast(u8 const *)(data) + nblocks*4;
+
+	for (i = 0; i < nblocks; i++) {
+		u32 k = blocks[i];
+		k *= c1;
+		k = (k << r1) | (k >> (32 - r1));
+		k *= c2;
+
+		hash ^= k;
+		hash = ((hash << r2) | (hash >> (32 - r2))) * m + n;
+	}
+
+	switch (len & 3) {
+	case 3:
+		k1 ^= tail[2] << 16;
+	case 2:
+		k1 ^= tail[1] << 8;
+	case 1:
+		k1 ^= tail[0];
+
+		k1 *= c1;
+		k1 = (k1 << r1) | (k1 >> (32 - r1));
+		k1 *= c2;
+		hash ^= k1;
+	}
+
+	hash ^= len;
+	hash ^= (hash >> 16);
+	hash *= 0x85ebca6b;
+	hash ^= (hash >> 13);
+	hash *= 0xc2b2ae35;
+	hash ^= (hash >> 16);
+
+	return hash;
+}
+
+u64 gb_murmur64_seed(void const *data_, isize len, u64 seed) {
+#if defined(GB_ARCH_64_BIT)
+	u64 const m = 0xc6a4a7935bd1e995ULL;
+	i32 const r = 47;
+
+	u64 h = seed ^ (len * m);
+
+	u64 const *data = cast(u64 const *)data_;
+	u8  const *data2 = cast(u8 const *)data_;
+	u64 const* end = data + (len / 8);
+
+	while (data != end) {
+		u64 k = *data++;
+
+		k *= m;
+		k ^= k >> r;
+		k *= m;
+
+		h ^= k;
+		h *= m;
+	}
+
+	switch (len & 7) {
+	case 7: h ^= cast(u64)(data2[6]) << 48;
+	case 6: h ^= cast(u64)(data2[5]) << 40;
+	case 5: h ^= cast(u64)(data2[4]) << 32;
+	case 4: h ^= cast(u64)(data2[3]) << 24;
+	case 3: h ^= cast(u64)(data2[2]) << 16;
+	case 2: h ^= cast(u64)(data2[1]) << 8;
+	case 1: h ^= cast(u64)(data2[0]);
+		h *= m;
+	};
+
+	h ^= h >> r;
+	h *= m;
+	h ^= h >> r;
+
+	return h;
+#else
+	u64 h;
+	u32 const m = 0x5bd1e995;
+	i32 const r = 24;
+
+	u32 h1 = cast(u32)(seed) ^ cast(u32)(len);
+	u32 h2 = cast(u32)(seed >> 32);
+
+	u32 const *data = cast(u32 const *)data_;
+
+	while (len >= 8) {
+		u32 k1, k2;
+		k1 = *data++;
+		k1 *= m;
+		k1 ^= k1 >> r;
+		k1 *= m;
+		h1 *= m;
+		h1 ^= k1;
+		len -= 4;
+
+		k2 = *data++;
+		k2 *= m;
+		k2 ^= k2 >> r;
+		k2 *= m;
+		h2 *= m;
+		h2 ^= k2;
+		len -= 4;
+	}
+
+	if (len >= 4) {
+		u32 k1 = *data++;
+		k1 *= m;
+		k1 ^= k1 >> r;
+		k1 *= m;
+		h1 *= m;
+		h1 ^= k1;
+		len -= 4;
+	}
+
+	switch (len) {
+	case 3: h2 ^= (cast(u8 const *)data)[2] << 16;
+	case 2: h2 ^= (cast(u8 const *)data)[1] <<  8;
+	case 1: h2 ^= (cast(u8 const *)data)[0] <<  0;
+		h2 *= m;
+	};
+
+	h1 ^= h2 >> 18;
+	h1 *= m;
+	h2 ^= h1 >> 22;
+	h2 *= m;
+	h1 ^= h2 >> 17;
+	h1 *= m;
+	h2 ^= h1 >> 19;
+	h2 *= m;
+
+	h = h1;
+	h = (h << 32) | h2;
+
+	return h;
+#endif
+}
+
+
+
+
+
+
+
+////////////////////////////////////////////////////////////////
+//
+// File Handling
+//
+//
+
+#if defined(GB_SYSTEM_WINDOWS)
+	gb_internal GB_FILE_SEEK_PROC(gb__win32_file_seek) {
+		LARGE_INTEGER li_offset;
+		li_offset.QuadPart = offset;
+		if (!SetFilePointerEx(fd.p, li_offset, &li_offset, whence)) {
+			return false;
+		}
+
+		if (new_offset) *new_offset = li_offset.QuadPart;
+		return true;
+	}
+
+	gb_internal GB_FILE_READ_AT_PROC(gb__win32_file_read) {
+		b32 result = false;
+		DWORD size_ = cast(DWORD)(size > I32_MAX ? I32_MAX : size);
+		DWORD bytes_read_;
+		gb__win32_file_seek(fd, offset, gbSeekWhence_Begin, NULL);
+		if (ReadFile(fd.p, buffer, size_, &bytes_read_, NULL)) {
+			if (bytes_read) *bytes_read = bytes_read_;
+			result = true;
+		}
+
+		return result;
+	}
+
+	gb_internal GB_FILE_WRITE_AT_PROC(gb__win32_file_write) {
+		DWORD size_ = cast(DWORD)(size > I32_MAX ? I32_MAX : size);
+		DWORD bytes_written_;
+		gb__win32_file_seek(fd, offset, gbSeekWhence_Begin, NULL);
+		if (WriteFile(fd.p, buffer, size_, &bytes_written_, NULL)) {
+			if (bytes_written) *bytes_written = bytes_written_;
+			return true;
+		}
+		return false;
+	}
+
+	gb_internal GB_FILE_CLOSE_PROC(gb__win32_file_close) {
+		CloseHandle(fd.p);
+	}
+
+	gbFileOperations const gbDefaultFileOperations = {
+		gb__win32_file_read,
+		gb__win32_file_write,
+		gb__win32_file_seek,
+		gb__win32_file_close
+	};
+
+	gb_no_inline GB_FILE_OPEN_PROC(gb__win32_file_open) {
+		DWORD desired_access;
+		DWORD creation_disposition;
+		void *handle;
+		u16 path[1024] = {0}; // TODO(bill): Is this really enough or should I heap allocate this if it's too large?
+
+		switch (mode & gbFileMode_Modes) {
+		case gbFileMode_Read:
+			desired_access = GENERIC_READ;
+			creation_disposition = OPEN_EXISTING;
+			break;
+		case gbFileMode_Write:
+			desired_access = GENERIC_WRITE;
+			creation_disposition = CREATE_ALWAYS;
+			break;
+		case gbFileMode_Append:
+			desired_access = GENERIC_WRITE;
+			creation_disposition = OPEN_ALWAYS;
+			break;
+		case gbFileMode_Read | gbFileMode_Rw:
+			desired_access = GENERIC_READ | GENERIC_WRITE;
+			creation_disposition = OPEN_EXISTING;
+			break;
+		case gbFileMode_Write | gbFileMode_Rw:
+			desired_access = GENERIC_READ | GENERIC_WRITE;
+			creation_disposition = CREATE_ALWAYS;
+			break;
+		case gbFileMode_Append | gbFileMode_Rw:
+			desired_access = GENERIC_READ | GENERIC_WRITE;
+			creation_disposition = OPEN_ALWAYS;
+			break;
+		default:
+			GB_PANIC("Invalid file mode");
+			return gbFileError_Invalid;
+		}
+
+		handle = CreateFileW(cast(wchar_t const *)gb_utf8_to_ucs2(path, gb_count_of(path), cast(u8 *)filename),
+		                     desired_access,
+		                     FILE_SHARE_READ|FILE_SHARE_DELETE, NULL,
+		                     creation_disposition, FILE_ATTRIBUTE_NORMAL, NULL);
+
+		if (handle == INVALID_HANDLE_VALUE) {
+			DWORD err = GetLastError();
+			switch (err) {
+			case ERROR_FILE_NOT_FOUND: return gbFileError_NotExists;
+			case ERROR_FILE_EXISTS:    return gbFileError_Exists;
+			case ERROR_ALREADY_EXISTS: return gbFileError_Exists;
+			case ERROR_ACCESS_DENIED:  return gbFileError_Permission;
+			}
+			return gbFileError_Invalid;
+		}
+
+		if (mode & gbFileMode_Append) {
+			LARGE_INTEGER offset = {0};
+			if (!SetFilePointerEx(handle, offset, NULL, gbSeekWhence_End)) {
+				CloseHandle(handle);
+				return gbFileError_Invalid;
+			}
+		}
+
+		fd->p = handle;
+		*ops = &gbDefaultFileOperations;
+		return gbFileError_None;
+	}
+
+#else // POSIX
+	gb_internal GB_FILE_SEEK_PROC(gb__posix_file_seek) {
+		#if defined(GB_SYSTEM_OSX)
+		i64 res = lseek(fd.i, offset, whence);
+		#else
+		i64 res = lseek64(fd.i, offset, whence);
+		#endif
+		if (res < 0) return false;
+		if (new_offset) *new_offset = res;
+		return true;
+	}
+
+	gb_internal GB_FILE_READ_AT_PROC(gb__posix_file_read) {
+		isize res = pread(fd.i, buffer, size, offset);
+		if (res < 0) return false;
+		if (bytes_read) *bytes_read = res;
+		return true;
+	}
+
+	gb_internal GB_FILE_WRITE_AT_PROC(gb__posix_file_write) {
+		isize res;
+		i64 curr_offset = 0;
+		gb__posix_file_seek(fd, 0, gbSeekWhence_Current, &curr_offset);
+		if (curr_offset == offset) {
+			// NOTE(bill): Writing to stdout et al. doesn't like pwrite for numerous reasons
+			res = write(cast(int)fd.i, buffer, size);
+		} else {
+			res = pwrite(cast(int)fd.i, buffer, size, offset);
+		}
+		if (res < 0) return false;
+		if (bytes_written) *bytes_written = res;
+		return true;
+	}
+
+
+	gb_internal GB_FILE_CLOSE_PROC(gb__posix_file_close) {
+		close(fd.i);
+	}
+
+	gbFileOperations const gbDefaultFileOperations = {
+		gb__posix_file_read,
+		gb__posix_file_write,
+		gb__posix_file_seek,
+		gb__posix_file_close
+	};
+
+	gb_no_inline GB_FILE_OPEN_PROC(gb__posix_file_open) {
+		i32 os_mode;
+		switch (mode & gbFileMode_Modes) {
+		case gbFileMode_Read:
+			os_mode = O_RDONLY;
+			break;
+		case gbFileMode_Write:
+			os_mode = O_WRONLY | O_CREAT | O_TRUNC;
+			break;
+		case gbFileMode_Append:
+			os_mode = O_WRONLY | O_APPEND | O_CREAT;
+			break;
+		case gbFileMode_Read | gbFileMode_Rw:
+			os_mode = O_RDWR;
+			break;
+		case gbFileMode_Write | gbFileMode_Rw:
+			os_mode = O_RDWR | O_CREAT | O_TRUNC;
+			break;
+		case gbFileMode_Append | gbFileMode_Rw:
+			os_mode = O_RDWR | O_APPEND | O_CREAT;
+			break;
+		default:
+			GB_PANIC("Invalid file mode");
+			return gbFileError_Invalid;
+		}
+
+		fd->i = open(filename, os_mode, S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH);
+		if (fd->i < 0) {
+			// TODO(bill): More file errors
+			return gbFileError_Invalid;
+		}
+
+		*ops = &gbDefaultFileOperations;
+		return gbFileError_None;
+	}
+
+#endif
+
+
+
+gbFileError gb_file_new(gbFile *f, gbFileDescriptor fd, gbFileOperations const *ops, char const *filename) {
+	gbFileError err = gbFileError_None;
+
+	f->ops = ops;
+	f->fd = fd;
+	f->filename = gb_alloc_str(gb_heap_allocator(), filename);
+	f->last_write_time = gb_file_last_write_time(f->filename);
+
+	return err;
+}
+
+
+
+gbFileError gb_file_open_mode(gbFile *f, gbFileMode mode, char const *filename) {
+	gbFileError err;
+#if defined(GB_SYSTEM_WINDOWS)
+	err = gb__win32_file_open(&f->fd, &f->ops, mode, filename);
+#else
+	err = gb__posix_file_open(&f->fd, &f->ops, mode, filename);
+#endif
+	if (err == gbFileError_None)
+		return gb_file_new(f, f->fd, f->ops, filename);
+	return err;
+}
+
+gbFileError gb_file_close(gbFile *f) {
+	if (!f)
+		return gbFileError_Invalid;
+
+	if (f->filename) gb_free(gb_heap_allocator(), cast(char *)f->filename);
+
+#if defined(GB_SYSTEM_WINDOWS)
+	if (f->fd.p == INVALID_HANDLE_VALUE)
+		return gbFileError_Invalid;
+#else
+	if (f->fd.i < 0)
+		return gbFileError_Invalid;
+#endif
+
+	if (!f->ops) f->ops = &gbDefaultFileOperations;
+	f->ops->close(f->fd);
+
+	return gbFileError_None;
+}
+
+gb_inline b32 gb_file_read_at_check(gbFile *f, void *buffer, isize size, i64 offset, isize *bytes_read) {
+	if (!f->ops) f->ops = &gbDefaultFileOperations;
+	return f->ops->read_at(f->fd, buffer, size, offset, bytes_read);
+}
+
+gb_inline b32 gb_file_write_at_check(gbFile *f, void const *buffer, isize size, i64 offset, isize *bytes_written) {
+	if (!f->ops) f->ops = &gbDefaultFileOperations;
+	return f->ops->write_at(f->fd, buffer, size, offset, bytes_written);
+}
+
+
+gb_inline b32 gb_file_read_at(gbFile *f, void *buffer, isize size, i64 offset) {
+	return gb_file_read_at_check(f, buffer, size, offset, NULL);
+}
+
+gb_inline b32 gb_file_write_at(gbFile *f, void const *buffer, isize size, i64 offset) {
+	return gb_file_write_at_check(f, buffer, size, offset, NULL);
+}
+
+gb_inline i64 gb_file_seek(gbFile *f, i64 offset) {
+	i64 new_offset = 0;
+	if (!f->ops) f->ops = &gbDefaultFileOperations;
+	f->ops->seek(f->fd, offset, gbSeekWhence_Begin, &new_offset);
+	return new_offset;
+}
+
+gb_inline i64 gb_file_seek_to_end(gbFile *f) {
+	i64 new_offset = 0;
+	if (!f->ops) f->ops = &gbDefaultFileOperations;
+	f->ops->seek(f->fd, 0, gbSeekWhence_End, &new_offset);
+	return new_offset;
+}
+
+// NOTE(bill): Skips a certain amount of bytes
+gb_inline i64 gb_file_skip(gbFile *f, i64 bytes) {
+	i64 new_offset = 0;
+	if (!f->ops) f->ops = &gbDefaultFileOperations;
+	f->ops->seek(f->fd, bytes, gbSeekWhence_Current, &new_offset);
+	return new_offset;
+}
+
+gb_inline i64 gb_file_tell(gbFile *f) {
+	i64 new_offset = 0;
+	if (!f->ops) f->ops = &gbDefaultFileOperations;
+	f->ops->seek(f->fd, 0, gbSeekWhence_Current, &new_offset);
+	return new_offset;
+}
+gb_inline b32 gb_file_read (gbFile *f, void *buffer, isize size)       { return gb_file_read_at(f, buffer, size, gb_file_tell(f)); }
+gb_inline b32 gb_file_write(gbFile *f, void const *buffer, isize size) { return gb_file_write_at(f, buffer, size, gb_file_tell(f)); }
+
+
+gbFileError gb_file_create(gbFile *f, char const *filename) {
+	return gb_file_open_mode(f, gbFileMode_Write|gbFileMode_Rw, filename);
+}
+
+
+gbFileError gb_file_open(gbFile *f, char const *filename) {
+	return gb_file_open_mode(f, gbFileMode_Read, filename);
+}
+
+
+char const *gb_file_name(gbFile *f) { return f->filename ? f->filename : ""; }
+
+gb_inline b32 gb_file_has_changed(gbFile *f) {
+	b32 result = false;
+	gbFileTime last_write_time = gb_file_last_write_time(f->filename);
+	if (f->last_write_time != last_write_time) {
+		result = true;
+		f->last_write_time = last_write_time;
+	}
+	return result;
+}
+
+// TODO(bill): Is this a bad idea?
+gb_global b32    gb__std_file_set = false;
+gb_global gbFile gb__std_files[gbFileStandard_Count] = {{0}};
+
+
+#if defined(GB_SYSTEM_WINDOWS)
+
+gb_inline gbFile *const gb_file_get_standard(gbFileStandardType std) {
+	if (!gb__std_file_set) {
+	#define GB__SET_STD_FILE(type, v) gb__std_files[type].fd.p = v; gb__std_files[type].ops = &gbDefaultFileOperations
+		GB__SET_STD_FILE(gbFileStandard_Input,  GetStdHandle(STD_INPUT_HANDLE));
+		GB__SET_STD_FILE(gbFileStandard_Output, GetStdHandle(STD_OUTPUT_HANDLE));
+		GB__SET_STD_FILE(gbFileStandard_Error,  GetStdHandle(STD_ERROR_HANDLE));
+	#undef GB__SET_STD_FILE
+		gb__std_file_set = true;
+	}
+	return &gb__std_files[std];
+}
+
+gb_inline i64 gb_file_size(gbFile *f) {
+	LARGE_INTEGER size;
+	GetFileSizeEx(f->fd.p, &size);
+	return size.QuadPart;
+}
+
+gbFileError gb_file_truncate(gbFile *f, i64 size) {
+	gbFileError err = gbFileError_None;
+	i64 prev_offset = gb_file_tell(f);
+	gb_file_seek(f, size);
+	if (!SetEndOfFile(f))
+		err = gbFileError_TruncationFailure;
+	gb_file_seek(f, prev_offset);
+	return err;
+}
+
+
+b32 gb_file_exists(char const *name) {
+	WIN32_FIND_DATAW data;
+	void *handle = FindFirstFileW(cast(wchar_t const *)gb_utf8_to_ucs2_buf(cast(u8 *)name), &data);
+	b32 found = handle != INVALID_HANDLE_VALUE;
+	if (found) FindClose(handle);
+	return found;
+}
+
+#else // POSIX
+
+gb_inline gbFile *const gb_file_get_standard(gbFileStandardType std) {
+	if (!gb__std_file_set) {
+	#define GB__SET_STD_FILE(type, v) gb__std_files[type].fd.i = v; gb__std_files[type].ops = &gbDefaultFileOperations
+		GB__SET_STD_FILE(gbFileStandard_Input,  0);
+		GB__SET_STD_FILE(gbFileStandard_Output, 1);
+		GB__SET_STD_FILE(gbFileStandard_Error,  2);
+	#undef GB__SET_STD_FILE
+		gb__std_file_set = true;
+	}
+	return &gb__std_files[std];
+}
+
+gb_inline i64 gb_file_size(gbFile *f) {
+	i64 size = 0;
+	i64 prev_offset = gb_file_tell(f);
+	gb_file_seek_to_end(f);
+	size = gb_file_tell(f);
+	gb_file_seek(f, prev_offset);
+	return size;
+}
+
+gb_inline gbFileError gb_file_truncate(gbFile *f, i64 size) {
+	gbFileError err = gbFileError_None;
+	int i = ftruncate(f->fd.i, size);
+	if (i != 0) err = gbFileError_TruncationFailure;
+	return err;
+}
+
+gb_inline b32 gb_file_exists(char const *name) {
+	return access(name, F_OK) != -1;
+}
+#endif
+
+
+
+#if defined(GB_SYSTEM_WINDOWS)
+gbFileTime gb_file_last_write_time(char const *filepath) {
+	u16 path[1024] = {0};
+	ULARGE_INTEGER li = {0};
+	FILETIME last_write_time = {0};
+	WIN32_FILE_ATTRIBUTE_DATA data = {0};
+
+	if (GetFileAttributesExW(cast(wchar_t const *)gb_utf8_to_ucs2(path, gb_count_of(path), cast(u8 *)filepath),
+	                         GetFileExInfoStandard, &data))
+		last_write_time = data.ftLastWriteTime;
+
+	li.LowPart = last_write_time.dwLowDateTime;
+	li.HighPart = last_write_time.dwHighDateTime;
+	return cast(gbFileTime)li.QuadPart;
+}
+
+
+gb_inline b32 gb_file_copy(char const *existing_filename, char const *new_filename, b32 fail_if_exists) {
+	u16 old_f[300] = {0};
+	u16 new_f[300] = {0};
+
+	return CopyFileW(cast(wchar_t const *)gb_utf8_to_ucs2(old_f, gb_count_of(old_f), cast(u8 *)existing_filename),
+	                 cast(wchar_t const *)gb_utf8_to_ucs2(new_f, gb_count_of(new_f), cast(u8 *)new_filename),
+	                 fail_if_exists);
+}
+
+gb_inline b32 gb_file_move(char const *existing_filename, char const *new_filename) {
+	u16 old_f[300] = {0};
+	u16 new_f[300] = {0};
+
+	return MoveFileW(cast(wchar_t const *)gb_utf8_to_ucs2(old_f, gb_count_of(old_f), cast(u8 *)existing_filename),
+	                 cast(wchar_t const *)gb_utf8_to_ucs2(new_f, gb_count_of(new_f), cast(u8 *)new_filename));
+}
+
+
+
+#else
+
+gbFileTime gb_file_last_write_time(char const *filepath) {
+	time_t result = 0;
+	struct stat file_stat;
+
+	if (stat(filepath, &file_stat))
+		result = file_stat.st_mtime;
+
+	return cast(gbFileTime)result;
+}
+
+
+gb_inline b32 gb_file_copy(char const *existing_filename, char const *new_filename, b32 fail_if_exists) {
+#if defined(GB_SYSTEM_OSX)
+	return copyfile(existing_filename, new_filename, NULL, COPYFILE_DATA) == 0;
+#else
+	isize size;
+	int existing_fd = open(existing_filename, O_RDONLY, 0);
+	int new_fd      = open(new_filename, O_WRONLY|O_CREAT, 0666);
+
+	struct stat stat_existing;
+	fstat(existing_fd, &stat_existing);
+
+	size = sendfile(new_fd, existing_fd, 0, stat_existing.st_size);
+
+	close(new_fd);
+	close(existing_fd);
+
+	return size == stat_existing.st_size;
+#endif
+}
+
+gb_inline b32 gb_file_move(char const *existing_filename, char const *new_filename) {
+	if (link(existing_filename, new_filename) == 0) {
+		if (unlink(existing_filename) != -1)
+			return true;
+	}
+	return false;
+}
+
+#endif
+
+
+
+
+
+gbFileContents gb_file_read_contents(gbAllocator a, b32 zero_terminate, char const *filepath) {
+	gbFileContents result = {0};
+	gbFile file = {0};
+
+	result.allocator = a;
+
+	if (gb_file_open(&file, filepath) == gbFileError_None) {
+		isize file_size = cast(isize)gb_file_size(&file);
+		if (file_size > 0) {
+			result.data = gb_alloc(a, zero_terminate ? file_size+1 : file_size);
+			result.size = file_size;
+			gb_file_read_at(&file, result.data, result.size, 0);
+			if (zero_terminate) {
+				u8 *str = cast(u8 *)result.data;
+				str[file_size] = '\0';
+			}
+		}
+		gb_file_close(&file);
+	}
+
+	return result;
+}
+
+void gb_file_free_contents(gbFileContents *fc) {
+	GB_ASSERT_NOT_NULL(fc->data);
+	gb_free(fc->allocator, fc->data);
+	fc->data = NULL;
+	fc->size = 0;
+}
+
+
+
+
+
+gb_inline b32 gb_path_is_absolute(char const *path) {
+	b32 result = false;
+	GB_ASSERT_NOT_NULL(path);
+#if defined(GB_SYSTEM_WINDOWS)
+	result == (gb_strlen(path) > 2) &&
+	          gb_char_is_alpha(path[0]) &&
+	          (path[1] == ':' && path[2] == GB_PATH_SEPARATOR);
+#else
+	result = (gb_strlen(path) > 0 && path[0] == GB_PATH_SEPARATOR);
+#endif
+	return result;
+}
+
+gb_inline b32 gb_path_is_relative(char const *path) { return !gb_path_is_absolute(path); }
+
+gb_inline b32 gb_path_is_root(char const *path) {
+	b32 result = false;
+	GB_ASSERT_NOT_NULL(path);
+#if defined(GB_SYSTEM_WINDOWS)
+	result = gb_path_is_absolute(path) && (gb_strlen(path) == 3);
+#else
+	result = gb_path_is_absolute(path) && (gb_strlen(path) == 1);
+#endif
+	return result;
+}
+
+gb_inline char const *gb_path_base_name(char const *path) {
+	char const *ls;
+	GB_ASSERT_NOT_NULL(path);
+	ls = gb_char_last_occurence(path, '/');
+	return (ls == NULL) ? path : ls+1;
+}
+
+gb_inline char const *gb_path_extension(char const *path) {
+	char const *ld;
+	GB_ASSERT_NOT_NULL(path);
+	ld = gb_char_last_occurence(path, '.');
+	return (ld == NULL) ? NULL : ld+1;
+}
+
+
+#if !defined(_WINDOWS_) && defined(GB_SYSTEM_WINDOWS)
+GB_DLL_IMPORT DWORD WINAPI GetFullPathNameA(char const *lpFileName, DWORD nBufferLength, char *lpBuffer, char **lpFilePart);
+#endif
+
+char *gb_path_get_full_name(gbAllocator a, char const *path) {
+#if defined(GB_SYSTEM_WINDOWS)
+// TODO(bill): Make UTF-8
+	char buf[300];
+	isize len = GetFullPathNameA(path, gb_count_of(buf), buf, NULL);
+	return gb_alloc_str_len(a, buf, len+1);
+#else
+// TODO(bill): Make work on *nix, etc.
+	return gb_alloc_str_len(a, path, gb_strlen(path));
+#endif
+}
+
+
+
+
+
+////////////////////////////////////////////////////////////////
+//
+// Printing
+//
+//
+
+
+isize gb_printf(char const *fmt, ...) {
+	isize res;
+	va_list va;
+	va_start(va, fmt);
+	res = gb_printf_va(fmt, va);
+	va_end(va);
+	return res;
+}
+
+
+isize gb_printf_err(char const *fmt, ...) {
+	isize res;
+	va_list va;
+	va_start(va, fmt);
+	res = gb_printf_err_va(fmt, va);
+	va_end(va);
+	return res;
+}
+
+isize gb_fprintf(struct gbFile *f, char const *fmt, ...) {
+	isize res;
+	va_list va;
+	va_start(va, fmt);
+	res = gb_fprintf_va(f, fmt, va);
+	va_end(va);
+	return res;
+}
+
+char *gb_bprintf(char const *fmt, ...) {
+	va_list va;
+	char *str;
+	va_start(va, fmt);
+	str = gb_bprintf_va(fmt, va);
+	va_end(va);
+	return str;
+}
+
+isize gb_snprintf(char *str, isize n, char const *fmt, ...) {
+	isize res;
+	va_list va;
+	va_start(va, fmt);
+	res = gb_snprintf_va(str, n, fmt, va);
+	va_end(va);
+	return res;
+}
+
+
+
+gb_inline isize gb_printf_va(char const *fmt, va_list va) {
+	return gb_fprintf_va(gb_file_get_standard(gbFileStandard_Output), fmt, va);
+}
+
+gb_inline isize gb_printf_err_va(char const *fmt, va_list va) {
+	return gb_fprintf_va(gb_file_get_standard(gbFileStandard_Error), fmt, va);
+}
+
+gb_inline isize gb_fprintf_va(struct gbFile *f, char const *fmt, va_list va) {
+	gb_local_persist char buf[4096];
+	isize len = gb_snprintf_va(buf, gb_size_of(buf), fmt, va);
+	gb_file_write(f, buf, len-1); // NOTE(bill): prevent extra whitespace
+	return len;
+}
+
+
+gb_inline char *gb_bprintf_va(char const *fmt, va_list va) {
+	gb_local_persist char buffer[4096];
+	gb_snprintf_va(buffer, gb_size_of(buffer), fmt, va);
+	return buffer;
+}
+
+
+enum {
+	gbFmt_Minus     = GB_BIT(0),
+	gbFmt_Plus      = GB_BIT(1),
+	gbFmt_Alt       = GB_BIT(2),
+	gbFmt_Space     = GB_BIT(3),
+	gbFmt_Zero      = GB_BIT(4),
+
+	gbFmt_Char      = GB_BIT(5),
+	gbFmt_Short     = GB_BIT(6),
+	gbFmt_Int       = GB_BIT(7),
+	gbFmt_Long      = GB_BIT(8),
+	gbFmt_Llong     = GB_BIT(9),
+	gbFmt_Size      = GB_BIT(10),
+	gbFmt_Intptr    = GB_BIT(11),
+
+	gbFmt_Unsigned  = GB_BIT(12),
+	gbFmt_Lower     = GB_BIT(13),
+	gbFmt_Upper     = GB_BIT(14),
+
+
+	gbFmt_Done      = GB_BIT(30),
+
+	gbFmt_Ints = gbFmt_Char|gbFmt_Short|gbFmt_Int|gbFmt_Long|gbFmt_Llong|gbFmt_Size|gbFmt_Intptr
+};
+
+typedef struct {
+	i32 base;
+	i32 flags;
+	i32 width;
+	i32 precision;
+} gbprivFmtInfo;
+
+
+gb_internal isize gb__print_string(char *text, isize max_len, gbprivFmtInfo *info, char const *str) {
+	// TODO(bill): Get precision and width to work correctly. How does it actually work?!
+	// TODO(bill): This looks very buggy indeed.
+	isize res = 0, len;
+	isize remaining = max_len;
+
+	if (info && info->precision >= 0)
+		len = gb_strnlen(str, info->precision);
+	else
+		len = gb_strlen(str);
+
+	if (info && (info->width == 0 || info->flags & gbFmt_Minus)) {
+		if (info->precision > 0)
+			len = info->precision < len ? info->precision : len;
+
+		res += gb_strlcpy(text, str, len);
+
+		if (info->width > res) {
+			isize padding = info->width - len;
+			char pad = (info->flags & gbFmt_Zero) ? '0' : ' ';
+			while (padding --> 0 && remaining --> 0)
+				*text++ = pad, res++;
+		}
+	} else {
+		if (info && (info->width > res)) {
+			isize padding = info->width - len;
+			char pad = (info->flags & gbFmt_Zero) ? '0' : ' ';
+			while (padding --> 0 && remaining --> 0)
+				*text++ = pad, res++;
+		}
+
+		res += gb_strlcpy(text, str, len);
+	}
+
+
+	if (info) {
+		if (info->flags & gbFmt_Upper)
+			gb_str_to_upper(text);
+		else if (info->flags & gbFmt_Lower)
+			gb_str_to_lower(text);
+	}
+
+	return res;
+}
+
+gb_internal isize gb__print_char(char *text, isize max_len, gbprivFmtInfo *info, char arg) {
+	char str[2] = "";
+	str[0] = arg;
+	return gb__print_string(text, max_len, info, str);
+}
+
+
+gb_internal isize gb__print_i64(char *text, isize max_len, gbprivFmtInfo *info, i64 value) {
+	char num[130];
+	gb_i64_to_str(value, num, info ? info->base : 10);
+	return gb__print_string(text, max_len, info, num);
+}
+
+gb_internal isize gb__print_u64(char *text, isize max_len, gbprivFmtInfo *info, u64 value) {
+	char num[130];
+	gb_u64_to_str(value, num, info ? info->base : 10);
+	return gb__print_string(text, max_len, info, num);
+}
+
+
+gb_internal isize gb__print_f64(char *text, isize max_len, gbprivFmtInfo *info, f64 arg) {
+	// TODO(bill): Handle exponent notation
+	isize width, len, remaining = max_len;
+	char *text_begin = text;
+
+	if (arg) {
+		u64 value;
+		if (arg < 0) {
+			if (remaining > 1)
+				*text = '-', remaining--;
+			text++;
+			arg = -arg;
+		} else if (info->flags & gbFmt_Minus) {
+			if (remaining > 1)
+				*text = '+', remaining--;
+			text++;
+		}
+
+		value = cast(u64)arg;
+		len = gb__print_u64(text, remaining, NULL, value);
+		text += len;
+
+		if (len >= remaining)
+			remaining = gb_min(remaining, 1);
+		else
+			remaining -= len;
+		arg -= value;
+
+		if (info->precision < 0)
+			info->precision = 6;
+
+		if ((info->flags & gbFmt_Alt) || info->precision > 0) {
+			i64 mult = 10;
+			if (remaining > 1)
+				*text = '.', remaining--;
+			text++;
+			while (info->precision-- > 0) {
+				value = cast(u64)(arg * mult);
+				len = gb__print_u64(text, remaining, NULL, value);
+				text += len;
+				if (len >= remaining)
+					remaining = gb_min(remaining, 1);
+				else
+					remaining -= len;
+				arg -= cast(f64)value / mult;
+				mult *= 10;
+			}
+		}
+	} else {
+		if (remaining > 1)
+			*text = '0', remaining--;
+		text++;
+		if (info->flags & gbFmt_Alt) {
+			if (remaining > 1)
+				*text = '.', remaining--;
+			text++;
+		}
+	}
+
+	width = info->width - (text - text_begin);
+	if (width > 0) {
+		char fill = (info->flags & gbFmt_Zero) ? '0' : ' ';
+		char *end = text+remaining-1;
+		len = (text - text_begin);
+
+		for (len = (text - text_begin); len--; ) {
+			if ((text_begin+len+width) < end)
+				*(text_begin+len+width) = *(text_begin+len);
+		}
+
+		len = width;
+		text += len;
+		if (len >= remaining)
+			remaining = gb_min(remaining, 1);
+		else
+			remaining -= len;
+
+		while (len--) {
+			if (text_begin+len < end)
+				text_begin[len] = fill;
+		}
+	}
+
+	return (text - text_begin);
+}
+
+
+
+gb_no_inline isize gb_snprintf_va(char *text, isize max_len, char const *fmt, va_list va) {
+	char const *text_begin = text;
+	isize remaining = max_len, res;
+
+	while (*fmt) {
+		gbprivFmtInfo info = {0};
+		isize len = 0;
+		info.precision = -1;
+
+		while (*fmt && *fmt != '%' && remaining)
+			*text++ = *fmt++;
+
+		if (*fmt == '%') {
+			do {
+				switch (*fmt++) {
+				case '-': info.flags |= gbFmt_Minus; fmt++; break;
+				case '+': info.flags |= gbFmt_Plus;  fmt++; break;
+				case '#': info.flags |= gbFmt_Alt;   fmt++; break;
+				case ' ': info.flags |= gbFmt_Space; fmt++; break;
+				case '0': info.flags |= gbFmt_Zero;  fmt++; break;
+				default:  info.flags |= gbFmt_Done;         break;
+				}
+			} while (!(info.flags & gbFmt_Done));
+		}
+
+		// NOTE(bill): Optional Width
+		if (*fmt == '*') {
+			int width = va_arg(va, int);
+			if (width < 0) {
+				info.flags |= gbFmt_Minus;
+				info.width = -info.width;
+			} else {
+				info.width = -info.width;
+			}
+			fmt++;
+		} else {
+			info.width = cast(i32)gb_str_to_i64(fmt, cast(char **)&fmt, 10);
+		}
+
+		// NOTE(bill): Optional Precision
+		if (*fmt == '.') {
+			fmt++;
+			if (*fmt == '*') {
+				info.precision = va_arg(va, int);
+				fmt++;
+			} else {
+				info.precision = cast(i32)gb_str_to_i64(fmt, cast(char **)&fmt, 10);
+			}
+			info.flags &= ~gbFmt_Zero;
+		}
+
+		switch (*fmt++) {
+		case 'h':
+			if (*fmt == 'h') { // hh => char
+				info.flags |= gbFmt_Char;
+				fmt++;
+			} else { // h => short
+				info.flags |= gbFmt_Short;
+			}
+			break;
+
+		case 'l':
+			if (*fmt == 'l') { // ll => long long
+				info.flags |= gbFmt_Llong;
+				fmt++;
+			} else { // l => long
+				info.flags |= gbFmt_Long;
+			}
+			break;
+
+			break;
+
+		case 'z': // NOTE(bill): usize
+			info.flags |= gbFmt_Unsigned;
+			// fallthrough
+		case 't': // NOTE(bill): isize
+			info.flags |= gbFmt_Size;
+			break;
+
+		default: fmt--; break;
+		}
+
+
+		switch (*fmt) {
+		case 'u':
+			info.flags |= gbFmt_Unsigned;
+			// fallthrough
+		case 'd':
+		case 'i':
+			info.base = 10;
+			break;
+
+		case 'o':
+			info.base = 8;
+			break;
+
+		case 'x':
+			info.base = 16;
+			info.flags |= (gbFmt_Unsigned | gbFmt_Lower);
+			break;
+
+		case 'X':
+			info.base = 16;
+			info.flags |= (gbFmt_Unsigned | gbFmt_Upper);
+			break;
+
+		case 'f':
+		case 'F':
+		case 'g':
+		case 'G':
+			len = gb__print_f64(text, remaining, &info, va_arg(va, f64));
+			break;
+
+		case 'a':
+		case 'A':
+			// TODO(bill):
+			break;
+
+		case 'c':
+			len = gb__print_char(text, remaining, &info, cast(char)va_arg(va, int));
+			break;
+
+		case 's':
+			len = gb__print_string(text, remaining, &info, va_arg(va, char *));
+			break;
+
+		case 'p':
+			info.base = 16;
+			info.flags |= (gbFmt_Lower|gbFmt_Unsigned|gbFmt_Alt|gbFmt_Intptr);
+			break;
+
+		default: fmt--; break;
+		}
+
+		fmt++;
+
+		if (info.base != 0) {
+			if (info.flags & gbFmt_Unsigned) {
+				u64 value = 0;
+				switch (info.flags & gbFmt_Ints) {
+				case gbFmt_Char:   value = cast(u64)cast(u8) va_arg(va, int);       break;
+				case gbFmt_Short:  value = cast(u64)cast(u16)va_arg(va, int);       break;
+				case gbFmt_Long:   value = cast(u64)va_arg(va, unsigned long);      break;
+				case gbFmt_Llong:  value = cast(u64)va_arg(va, unsigned long long); break;
+				case gbFmt_Size:   value = cast(u64)va_arg(va, usize);              break;
+				case gbFmt_Intptr: value = cast(u64)va_arg(va, uintptr);            break;
+				default:             value = cast(u64)va_arg(va, unsigned int);       break;
+				}
+
+				len = gb__print_u64(text, remaining, &info, value);
+
+			} else {
+				i64 value = 0;
+				switch (info.flags & gbFmt_Ints) {
+				case gbFmt_Char:   value = cast(i64)cast(i8) va_arg(va, int); break;
+				case gbFmt_Short:  value = cast(i64)cast(i16)va_arg(va, int); break;
+				case gbFmt_Long:   value = cast(i64)va_arg(va, long);         break;
+				case gbFmt_Llong:  value = cast(i64)va_arg(va, long long);    break;
+				case gbFmt_Size:   value = cast(i64)va_arg(va, usize);        break;
+				case gbFmt_Intptr: value = cast(i64)va_arg(va, uintptr);      break;
+				default:             value = cast(i64)va_arg(va, int);          break;
+				}
+
+				len = gb__print_i64(text, remaining, &info, value);
+			}
+		}
+
+
+		text += len;
+		if (len >= remaining)
+			remaining = gb_min(remaining, 1);
+		else
+			remaining -= len;
+	}
+
+	*text++ = '\0';
+	res = (text - text_begin);
+	return (res >= max_len || res < 0) ? -1 : res;
+}
+
+
+////////////////////////////////////////////////////////////////
+//
+// DLL Handling
+//
+//
+
+#if defined(GB_SYSTEM_WINDOWS)
+
+gbDllHandle gb_dll_load(char const *filepath) {
+	return cast(gbDllHandle)LoadLibraryA(filepath);
+}
+gb_inline void      gb_dll_unload      (gbDllHandle dll)                        { FreeLibrary(cast(HMODULE)dll); }
+gb_inline gbDllProc gb_dll_proc_address(gbDllHandle dll, char const *proc_name) { return cast(gbDllProc)GetProcAddress(cast(HMODULE)dll, proc_name); }
+
+#else // POSIX
+
+gbDllHandle gb_dll_load(char const *filepath) {
+	// TODO(bill): Should this be RTLD_LOCAL?
+	return cast(gbDllHandle)dlopen(filepath, RTLD_LAZY|RTLD_GLOBAL);
+}
+
+gb_inline void      gb_dll_unload      (gbDllHandle dll)                        { dlclose(dll); }
+gb_inline gbDllProc gb_dll_proc_address(gbDllHandle dll, char const *proc_name) { return cast(gbDllProc)dlsym(dll, proc_name); }
+
+#endif
+
+
+////////////////////////////////////////////////////////////////
+//
+// Time
+//
+//
+
+#if defined(GB_COMPILER_MSVC) && !defined(__clang__)
+	gb_inline u64 gb_rdtsc(void) { return __rdtsc(); }
+#elif defined(__i386__)
+	gb_inline u64 gb_rdtsc(void) {
+		u64 x;
+		__asm__ volatile (".byte 0x0f, 0x31" : "=A" (x));
+		return x;
+	}
+#elif defined(__x86_64__)
+	gb_inline u64 gb_rdtsc(void) {
+		u32 hi, lo;
+		__asm__ __volatile__ ("rdtsc" : "=a"(lo), "=d"(hi));
+		return (cast(u64)lo) | ((cast(u64)hi)<<32);
+	}
+#elif defined(__powerpc__)
+	gb_inline u64 gb_rdtsc(void) {
+		u64 result = 0;
+		u32 upper, lower,tmp;
+		__asm__ volatile(
+			"0:                   \n"
+			"\tmftbu   %0         \n"
+			"\tmftb    %1         \n"
+			"\tmftbu   %2         \n"
+			"\tcmpw    %2,%0      \n"
+			"\tbne     0b         \n"
+			: "=r"(upper),"=r"(lower),"=r"(tmp)
+		);
+		result = upper;
+		result = result<<32;
+		result = result|lower;
+
+		return result;
+	}
+#endif
+
+#if defined(GB_SYSTEM_WINDOWS)
+
+	gb_inline f64 gb_time_now(void) {
+		gb_local_persist LARGE_INTEGER win32_perf_count_freq = {0};
+		f64 result;
+		LARGE_INTEGER counter;
+		if (!win32_perf_count_freq.QuadPart) {
+			QueryPerformanceFrequency(&win32_perf_count_freq);
+			GB_ASSERT(win32_perf_count_freq.QuadPart != 0);
+		}
+
+		QueryPerformanceCounter(&counter);
+
+		result = counter.QuadPart / cast(f64)(win32_perf_count_freq.QuadPart);
+		return result;
+	}
+
+	gb_inline u64 gb_utc_time_now(void) {
+		FILETIME ft;
+		ULARGE_INTEGER li;
+
+		GetSystemTimeAsFileTime(&ft);
+		li.LowPart = ft.dwLowDateTime;
+		li.HighPart = ft.dwHighDateTime;
+
+		return li.QuadPart/10;
+	}
+
+	gb_inline void gb_sleep_ms(u32 ms) { Sleep(ms); }
+
+#else
+
+	gb_global f64 gb__timebase  = 0.0;
+	gb_global u64 gb__timestart = 0;
+
+	gb_inline f64 gb_time_now(void) {
+#if defined(GB_SYSTEM_OSX)
+		f64 result;
+
+		if (!gb__timestart) {
+			mach_timebase_info_data_t tb = {0};
+			mach_timebase_info(&tb);
+			gb__timebase = tb.numer;
+			gb__timebase /= tb.denom;
+			gb__timestart = mach_absolute_time();
+		}
+
+		// NOTE(bill): mach_absolute_time() returns things in nanoseconds
+		result = 1.0e-9 * (mach_absolute_time() - gb__timestart) * gb__timebase;
+		return result;
+#else
+		struct timespec t;
+		f64 result;
+
+		// IMPORTANT TODO(bill): THIS IS A HACK
+		clock_gettime(1 /*CLOCK_MONOTONIC*/, &t);
+		result = t.tv_sec + 1.0e-9 * t.tv_nsec;
+		return result;
+#endif
+	}
+
+	gb_inline u64 gb_utc_time_now(void) {
+		struct timespec t;
+#if defined(GB_SYSTEM_OSX)
+		clock_serv_t cclock;
+		mach_timespec_t mts;
+		host_get_clock_service(mach_host_self(), CALENDAR_CLOCK, &cclock);
+		clock_get_time(cclock, &mts);
+		mach_port_deallocate(mach_task_self(), cclock);
+		t.tv_sec = mts.tv_sec;
+		t.tv_nsec = mts.tv_nsec;
+#else
+		// IMPORTANT TODO(bill): THIS IS A HACK
+		clock_gettime(0 /*CLOCK_REALTIME*/, &t);
+#endif
+		return cast(u64)t.tv_sec * 1000000ull + t.tv_nsec/1000 + 11644473600000000ull;
+	}
+
+	gb_inline void gb_sleep_ms(u32 ms) {
+		struct timespec req = {cast(time_t)ms/1000, cast(long)((ms%1000)*1000000)};
+		struct timespec rem = {0, 0};
+		nanosleep(&req, &rem);
+	}
+
+#endif
+
+
+
+////////////////////////////////////////////////////////////////
+//
+// Miscellany
+//
+//
+
+gb_global gbAtomic32 gb__random_shared_counter = {0};
+
+gb_internal u32 gb__get_noise_from_time(void) {
+	u32 accum = 0;
+	f64 start, remaining, end, curr = 0;
+	u64 interval = 100000ll;
+
+	start     = gb_time_now();
+	remaining = (interval - cast(u64)(interval*start)%interval) / cast(f64)interval;
+	end       = start + remaining;
+
+	do {
+		curr = gb_time_now();
+		accum += cast(u32)curr;
+	} while (curr >= end);
+	return accum;
+}
+
+// NOTE(bill): Partly from http://preshing.com/20121224/how-to-generate-a-sequence-of-unique-random-integers/
+// But the generation is even more random-er-est
+
+gb_internal gb_inline u32 gb__permute_qpr(u32 x) {
+	gb_local_persist u32 const prime = 4294967291; // 2^32 - 5
+	if (x >= prime) {
+		return x;
+	} else {
+		u32 residue = cast(u32)(cast(u64) x * x) % prime;
+		if (x <= prime / 2)
+			return residue;
+		else
+			return prime - residue;
+	}
+}
+
+gb_internal gb_inline u32 gb__permute_with_offset(u32 x, u32 offset) {
+	return (gb__permute_qpr(x) + offset) ^ 0x5bf03635;
+}
+
+
+void gb_random_init(gbRandom *r) {
+	u64 time, tick;
+	isize i, j;
+	u32 x = 0;
+	r->value = 0;
+
+	r->offsets[0] = gb__get_noise_from_time();
+	r->offsets[1] = gb_atomic32_fetch_add(&gb__random_shared_counter, 1);
+	r->offsets[2] = gb_thread_current_id();
+	r->offsets[3] = gb_thread_current_id() * 3 + 1;
+	time = gb_utc_time_now();
+	r->offsets[4] = cast(u32)(time >> 32);
+	r->offsets[5] = cast(u32)time;
+	r->offsets[6] = gb__get_noise_from_time();
+	tick = gb_rdtsc();
+	r->offsets[7] = cast(u32)(tick ^ (tick >> 32));
+
+	for (j = 0; j < 4; j++) {
+		for (i = 0; i < gb_count_of(r->offsets); i++) {
+			r->offsets[i] = x = gb__permute_with_offset(x, r->offsets[i]);
+		}
+	}
+}
+
+u32 gb_random_gen_u32(gbRandom *r) {
+	u32 x = r->value;
+	u32 carry = 1;
+	isize i;
+	for (i = 0; i < gb_count_of(r->offsets); i++) {
+		x = gb__permute_with_offset(x, r->offsets[i]);
+		if (carry > 0) {
+			carry = ++r->offsets[i] ? 0 : 1;
+		}
+	}
+
+	r->value = x;
+	return x;
+}
+
+u32 gb_random_gen_u32_unique(gbRandom *r) {
+	u32 x = r->value;
+	isize i;
+	r->value++;
+	for (i = 0; i < gb_count_of(r->offsets); i++) {
+		x = gb__permute_with_offset(x, r->offsets[i]);
+	}
+
+	return x;
+}
+
+u64 gb_random_gen_u64(gbRandom *r) {
+	return ((cast(u64)gb_random_gen_u32(r)) << 32) | gb_random_gen_u32(r);
+}
+
+
+isize gb_random_gen_isize(gbRandom *r) {
+	u64 u = gb_random_gen_u64(r);
+	return *cast(isize *)&u;
+}
+
+
+
+
+i64 gb_random_range_i64(gbRandom *r, i64 lower_inc, i64 higher_inc) {
+	u64 u = gb_random_gen_u64(r);
+	i64 i = *cast(i64 *)&u;
+	i64 diff = higher_inc-lower_inc+1;
+	i %= diff;
+	i += lower_inc;
+	return i;
+}
+
+isize gb_random_range_isize(gbRandom *r, isize lower_inc, isize higher_inc) {
+	u64 u = gb_random_gen_u64(r);
+	isize i = *cast(isize *)&u;
+	isize diff = higher_inc-lower_inc+1;
+	i %= diff;
+	i += lower_inc;
+	return i;
+}
+
+// NOTE(bill): Semi-cc'ed from gb_math to remove need for fmod and math.h
+f64 gb__copy_sign64(f64 x, f64 y) {
+	i64 ix, iy;
+	ix = *(i64 *)&x;
+	iy = *(i64 *)&y;
+
+	ix &= 0x7fffffffffffffff;
+	ix |= iy & 0x8000000000000000;
+	return *cast(f64 *)&ix;
+}
+
+f64 gb__floor64    (f64 x)        { return cast(f64)((x >= 0.0) ? cast(i64)x : cast(i64)(x-0.9999999999999999)); }
+f64 gb__ceil64     (f64 x)        { return cast(f64)((x < 0) ? cast(i64)x : (cast(i64)x)+1); }
+f64 gb__round64    (f64 x)        { return cast(f64)((x >= 0.0) ? gb__floor64(x + 0.5) : gb__ceil64(x - 0.5)); }
+f64 gb__remainder64(f64 x, f64 y) { return x - (gb__round64(x/y)*y); }
+f64 gb__abs64      (f64 x)        { return x < 0 ? -x : x; }
+f64 gb__sign64     (f64 x)        { return x < 0 ? -1.0 : +1.0; }
+
+f64 gb__mod64(f64 x, f64 y) {
+	f64 result;
+	y = gb__abs64(y);
+	result = gb__remainder64(gb__abs64(x), y);
+	if (gb__sign64(result)) result += y;
+	return gb__copy_sign64(result, x);
+}
+
+
+f64 gb_random_range_f64(gbRandom *r, f64 lower_inc, f64 higher_inc) {
+	u64 u = gb_random_gen_u64(r);
+	f64 f = *cast(f64 *)&u;
+	f64 diff = higher_inc-lower_inc+1.0;
+	f = gb__mod64(f, diff);
+	f += lower_inc;
+	return f;
+}
+
+
+
+#if defined(GB_SYSTEM_WINDOWS)
+gb_inline void gb_exit(u32 code) { ExitProcess(code); }
+#else
+gb_inline void gb_exit(u32 code) { exit(code); }
+#endif
+
+gb_inline void gb_yield(void) {
+#if defined(GB_SYSTEM_WINDOWS)
+	Sleep(0);
+#else
+	sched_yield();
+#endif
+}
+
+gb_inline void gb_set_env(char const *name, char const *value) {
+#if defined(GB_SYSTEM_WINDOWS)
+	// TODO(bill): Should this be a Wide version?
+	SetEnvironmentVariableA(name, value);
+#else
+	setenv(name, value, 1);
+#endif
+}
+
+gb_inline void gb_unset_env(char const *name) {
+#if defined(GB_SYSTEM_WINDOWS)
+	// TODO(bill): Should this be a Wide version?
+	SetEnvironmentVariableA(name, NULL);
+#else
+	unsetenv(name);
+#endif
+}
+
+
+gb_inline u16 gb_endian_swap16(u16 i) {
+	return (i>>8) | (i<<8);
+}
+
+gb_inline u32 gb_endian_swap32(u32 i) {
+	return (i>>24) |(i<<24) |
+	       ((i&0x00ff0000u)>>8)  | ((i&0x0000ff00u)<<8);
+}
+
+gb_inline u64 gb_endian_swap64(u64 i) {
+	return (i>>56) | (i<<56) |
+	       ((i&0x00ff000000000000ull)>>40) | ((i&0x000000000000ff00ull)<<40) |
+	       ((i&0x0000ff0000000000ull)>>24) | ((i&0x0000000000ff0000ull)<<24) |
+	       ((i&0x000000ff00000000ull)>>8)  | ((i&0x00000000ff000000ull)<<8);
+}
+
+
+gb_inline isize gb_count_set_bits(u64 mask) {
+	isize count = 0;
+	while (mask) {
+		count += (mask & 1);
+		mask >>= 1;
+	}
+	return count;
+}
+
+
+
+
+
+
+////////////////////////////////////////////////////////////////
+//
+// Platform
+//
+//
+
+#if defined(GB_PLATFORM)
+
+gb_inline void gb_key_state_update(gbKeyState *s, b32 is_down) {
+	b32 was_down = (*s & gbKeyState_Down) != 0;
+	is_down = is_down != 0; // NOTE(bill): Make sure it's a boolean
+	GB_MASK_SET(*s, is_down,               gbKeyState_Down);
+	GB_MASK_SET(*s, !was_down &&  is_down, gbKeyState_Pressed);
+	GB_MASK_SET(*s,  was_down && !is_down, gbKeyState_Released);
+}
+
+#if defined(GB_SYSTEM_WINDOWS)
+
+#ifndef ERROR_DEVICE_NOT_CONNECTED
+#define ERROR_DEVICE_NOT_CONNECTED 1167
+#endif
+
+GB_XINPUT_GET_STATE(gbXInputGetState_Stub) {
+	gb_unused(dwUserIndex); gb_unused(pState);
+	return ERROR_DEVICE_NOT_CONNECTED;
+}
+GB_XINPUT_SET_STATE(gbXInputSetState_Stub) {
+	gb_unused(dwUserIndex); gb_unused(pVibration);
+	return ERROR_DEVICE_NOT_CONNECTED;
+}
+
+
+gb_internal gb_inline f32 gb__process_xinput_stick_value(i16 value, i16 dead_zone_threshold) {
+	f32 result = 0;
+
+	if (value < -dead_zone_threshold)
+		result = cast(f32) (value + dead_zone_threshold) / (32768.0f - dead_zone_threshold);
+	else if (value > dead_zone_threshold)
+		result = cast(f32) (value - dead_zone_threshold) / (32767.0f - dead_zone_threshold);
+
+	return result;
+}
+
+gb_internal void gb__platform_resize_dib_section(gbPlatform *p, i32 width, i32 height) {
+	if ((p->renderer_type == gbRenderer_Software) &&
+	    !(p->window_width == width && p->window_height == height)) {
+		BITMAPINFO bmi = {0};
+
+		if (width == 0 || height == 0)
+			return;
+
+		p->window_width  = width;
+		p->window_height = height;
+
+		// TODO(bill): Is this slow to get the desktop mode everytime?
+		p->sw_framebuffer.bits_per_pixel = gb_video_mode_get_desktop().bits_per_pixel;
+		p->sw_framebuffer.pitch = (p->sw_framebuffer.bits_per_pixel * width / 8);
+
+		bmi.bmiHeader.biSize = gb_size_of(bmi.bmiHeader);
+		bmi.bmiHeader.biWidth       = width;
+		bmi.bmiHeader.biHeight      = height; // NOTE(bill): -ve is top-down, +ve is bottom-up
+		bmi.bmiHeader.biPlanes      = 1;
+		bmi.bmiHeader.biBitCount    = cast(u16)p->sw_framebuffer.bits_per_pixel;
+		bmi.bmiHeader.biCompression = 0 /*BI_RGB*/;
+
+		p->sw_framebuffer.win32_bmi = bmi;
+
+
+		if (p->sw_framebuffer.memory)
+			gb_vm_free(gb_virtual_memory(p->sw_framebuffer.memory, p->sw_framebuffer.memory_size));
+
+		{
+			isize memory_size = p->sw_framebuffer.pitch * height;
+			gbVirtualMemory vm = gb_vm_alloc(0, memory_size);
+			p->sw_framebuffer.memory      = vm.data;
+			p->sw_framebuffer.memory_size = vm.size;
+		}
+	}
+}
+
+
+gb_internal gbKeyType gb__win32_from_vk(unsigned int key) {
+	// NOTE(bill): Letters and numbers are defined the same for VK_* and GB_*
+	if (key >= 'A' && key < 'Z') return cast(gbKeyType)key;
+	if (key >= '0' && key < '9') return cast(gbKeyType)key;
+	switch (key) {
+	case VK_ESCAPE: return gbKey_Escape;
+
+	case VK_LCONTROL: return gbKey_Lcontrol;
+	case VK_LSHIFT:   return gbKey_Lshift;
+	case VK_LMENU:    return gbKey_Lalt;
+	case VK_LWIN:     return gbKey_Lsystem;
+	case VK_RCONTROL: return gbKey_Rcontrol;
+	case VK_RSHIFT:   return gbKey_Rshift;
+	case VK_RMENU:    return gbKey_Ralt;
+	case VK_RWIN:     return gbKey_Rsystem;
+	case VK_MENU:     return gbKey_Menu;
+
+	case VK_OEM_4:      return gbKey_Lbracket;
+	case VK_OEM_6:      return gbKey_Rbracket;
+	case VK_OEM_1:      return gbKey_Semicolon;
+	case VK_OEM_COMMA:  return gbKey_Comma;
+	case VK_OEM_PERIOD: return gbKey_Period;
+	case VK_OEM_7:      return gbKey_Quote;
+	case VK_OEM_2:      return gbKey_Slash;
+	case VK_OEM_5:      return gbKey_Backslash;
+	case VK_OEM_3:      return gbKey_Grave;
+	case VK_OEM_PLUS:   return gbKey_Equals;
+	case VK_OEM_MINUS:  return gbKey_Minus;
+
+	case VK_SPACE:  return gbKey_Space;
+	case VK_RETURN: return gbKey_Return;
+	case VK_BACK:   return gbKey_Backspace;
+	case VK_TAB:    return gbKey_Tab;
+
+	case VK_PRIOR:  return gbKey_Pageup;
+	case VK_NEXT:   return gbKey_Pagedown;
+	case VK_END:    return gbKey_End;
+	case VK_HOME:   return gbKey_Home;
+	case VK_INSERT: return gbKey_Insert;
+	case VK_DELETE: return gbKey_Delete;
+
+	case VK_ADD:      return gbKey_Plus;
+	case VK_SUBTRACT: return gbKey_Subtract;
+	case VK_MULTIPLY: return gbKey_Multiply;
+	case VK_DIVIDE:   return gbKey_Divide;
+
+	case VK_LEFT:  return gbKey_Left;
+	case VK_RIGHT: return gbKey_Right;
+	case VK_UP:    return gbKey_Up;
+	case VK_DOWN:  return gbKey_Down;
+
+	case VK_NUMPAD0:   return gbKey_Numpad0;
+	case VK_NUMPAD1:   return gbKey_Numpad1;
+	case VK_NUMPAD2:   return gbKey_Numpad2;
+	case VK_NUMPAD3:   return gbKey_Numpad3;
+	case VK_NUMPAD4:   return gbKey_Numpad4;
+	case VK_NUMPAD5:   return gbKey_Numpad5;
+	case VK_NUMPAD6:   return gbKey_Numpad6;
+	case VK_NUMPAD7:   return gbKey_Numpad7;
+	case VK_NUMPAD8:   return gbKey_Numpad8;
+	case VK_NUMPAD9:   return gbKey_Numpad9;
+	case VK_SEPARATOR: return gbKey_NumpadEnter;
+	case VK_DECIMAL:   return gbKey_NumpadDot;
+
+	case VK_F1:  return gbKey_F1;
+	case VK_F2:  return gbKey_F2;
+	case VK_F3:  return gbKey_F3;
+	case VK_F4:  return gbKey_F4;
+	case VK_F5:  return gbKey_F5;
+	case VK_F6:  return gbKey_F6;
+	case VK_F7:  return gbKey_F7;
+	case VK_F8:  return gbKey_F8;
+	case VK_F9:  return gbKey_F9;
+	case VK_F10: return gbKey_F10;
+	case VK_F11: return gbKey_F11;
+	case VK_F12: return gbKey_F12;
+	case VK_F13: return gbKey_F13;
+	case VK_F14: return gbKey_F14;
+	case VK_F15: return gbKey_F15;
+
+	case VK_PAUSE: return gbKey_Pause;
+	}
+	return gbKey_Unknown;
+}
+LRESULT CALLBACK gb__win32_window_callback(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam) {
+	// NOTE(bill): Silly callbacks
+	gbPlatform *platform = cast(gbPlatform *)GetWindowLongPtrW(hWnd, GWLP_USERDATA);
+	b32 window_has_focus = (platform != NULL) && platform->window_has_focus;
+
+	if (msg == WM_CREATE) { // NOTE(bill): Doesn't need the platform
+		// NOTE(bill): https://msdn.microsoft.com/en-us/library/windows/desktop/ms645536(v=vs.85).aspx
+		RAWINPUTDEVICE rid[2] = {0};
+
+		// NOTE(bill): Keyboard
+		rid[0].usUsagePage = 0x01;
+		rid[0].usUsage     = 0x06;
+		rid[0].dwFlags     = 0x00000030/*RIDEV_NOLEGACY*/; // NOTE(bill): Do not generate legacy messages such as WM_KEYDOWN
+		rid[0].hwndTarget  = hWnd;
+
+		// NOTE(bill): Mouse
+		rid[1].usUsagePage = 0x01;
+		rid[1].usUsage     = 0x02;
+		rid[1].dwFlags     = 0; // NOTE(bill): adds HID mouse and also allows legacy mouse messages to allow for window movement etc.
+		rid[1].hwndTarget  = hWnd;
+
+		if (RegisterRawInputDevices(rid, gb_count_of(rid), gb_size_of(rid[0])) == false) {
+			DWORD err = GetLastError();
+			GB_PANIC("Failed to initialize raw input device for win32."
+			         "Err: %u", err);
+		}
+	}
+
+	if (!platform)
+		return DefWindowProcW(hWnd, msg, wParam, lParam);
+
+	switch (msg) {
+	case WM_CLOSE:
+	case WM_DESTROY:
+		platform->window_is_closed = true;
+		return 0;
+
+	case WM_QUIT: {
+		platform->quit_requested = true;
+	} break;
+
+	case WM_UNICHAR: {
+		if (window_has_focus) {
+			if (wParam == '\r')
+				wParam = '\n';
+			// TODO(bill): Does this need to be thread-safe?
+			platform->char_buffer[platform->char_buffer_count++] = cast(Rune)wParam;
+		}
+	} break;
+
+
+	case WM_INPUT: {
+		RAWINPUT raw = {0};
+		unsigned int size = gb_size_of(RAWINPUT);
+
+		if (!GetRawInputData(cast(HRAWINPUT)lParam, RID_INPUT, &raw, &size, gb_size_of(RAWINPUTHEADER))) {
+			return 0;
+		}
+		switch (raw.header.dwType) {
+		case RIM_TYPEKEYBOARD: {
+			// NOTE(bill): Many thanks to https://blog.molecular-matters.com/2011/09/05/properly-handling-keyboard-input/
+			// for the
+			RAWKEYBOARD *raw_kb = &raw.data.keyboard;
+			unsigned int vk = raw_kb->VKey;
+			unsigned int scan_code = raw_kb->MakeCode;
+			unsigned int flags = raw_kb->Flags;
+			// NOTE(bill): e0 and e1 are escape sequences used for certain special keys, such as PRINT and PAUSE/BREAK.
+			// NOTE(bill): http://www.win.tue.nl/~aeb/linux/kbd/scancodes-1.html
+			b32 is_e0   = (flags & RI_KEY_E0) != 0;
+			b32 is_e1   = (flags & RI_KEY_E1) != 0;
+			b32 is_up   = (flags & RI_KEY_BREAK) != 0;
+			b32 is_down = !is_up;
+
+			// TODO(bill): Should I handle scan codes?
+
+			if (vk == 255) {
+				// NOTE(bill): Discard "fake keys"
+				return 0;
+			} else if (vk == VK_SHIFT) {
+				// NOTE(bill): Correct left/right shift
+				vk = MapVirtualKeyW(scan_code, MAPVK_VSC_TO_VK_EX);
+			} else if (vk == VK_NUMLOCK) {
+				// NOTE(bill): Correct PAUSE/BREAK and NUM LOCK and set the extended bit
+				scan_code = MapVirtualKeyW(vk, MAPVK_VK_TO_VSC) | 0x100;
+			}
+
+			if (is_e1) {
+				// NOTE(bill): Escaped sequences, turn vk into the correct scan code
+				// except for VK_PAUSE (it's a bug)
+				if (vk == VK_PAUSE)
+					scan_code = 0x45;
+				else
+					scan_code = MapVirtualKeyW(vk, MAPVK_VK_TO_VSC);
+			}
+
+			switch (vk) {
+			case VK_CONTROL: vk = (is_e0) ? VK_RCONTROL : VK_LCONTROL; break;
+			case VK_MENU:    vk = (is_e0) ? VK_RMENU    : VK_LMENU;   break;
+
+			case VK_RETURN: if (is_e0)  vk = VK_SEPARATOR; break; // NOTE(bill): Numpad return
+			case VK_DELETE: if (!is_e0) vk = VK_DECIMAL;   break; // NOTE(bill): Numpad dot
+			case VK_INSERT: if (!is_e0) vk = VK_NUMPAD0;   break;
+			case VK_HOME:   if (!is_e0) vk = VK_NUMPAD7;   break;
+			case VK_END:    if (!is_e0) vk = VK_NUMPAD1;   break;
+			case VK_PRIOR:  if (!is_e0) vk = VK_NUMPAD9;   break;
+			case VK_NEXT:   if (!is_e0) vk = VK_NUMPAD3;   break;
+
+			// NOTE(bill): The standard arrow keys will always have their e0 bit set, but the
+			// corresponding keys on the NUMPAD will not.
+			case VK_LEFT:  if (!is_e0) vk = VK_NUMPAD4; break;
+			case VK_RIGHT: if (!is_e0) vk = VK_NUMPAD6; break;
+			case VK_UP:    if (!is_e0) vk = VK_NUMPAD8; break;
+			case VK_DOWN:  if (!is_e0) vk = VK_NUMPAD2; break;
+
+			// NUMPAD 5 doesn't have its e0 bit set
+			case VK_CLEAR: if (!is_e0) vk = VK_NUMPAD5; break;
+			}
+
+			// NOTE(bill): Set appropriate key state flags
+			gb_key_state_update(&platform->keys[gb__win32_from_vk(vk)], is_down);
+
+		} break;
+		case RIM_TYPEMOUSE: {
+			RAWMOUSE *raw_mouse = &raw.data.mouse;
+			u16 flags = raw_mouse->usButtonFlags;
+			long dx = +raw_mouse->lLastX;
+			long dy = -raw_mouse->lLastY;
+
+			if (flags & RI_MOUSE_WHEEL)
+				platform->mouse_wheel_delta = cast(i16)raw_mouse->usButtonData;
+
+			platform->mouse_raw_dx = dx;
+			platform->mouse_raw_dy = dy;
+		} break;
+		}
+	} break;
+
+	default: break;
+	}
+
+	return DefWindowProcW(hWnd, msg, wParam, lParam);
+}
+
+
+typedef void *wglCreateContextAttribsARB_Proc(void *hDC, void *hshareContext, int const *attribList);
+
+
+b32 gb__platform_init(gbPlatform *p, char const *window_title, gbVideoMode mode, gbRendererType type, u32 window_flags) {
+	WNDCLASSEXW wc = {gb_size_of(WNDCLASSEXW)};
+	DWORD ex_style = 0, style = 0;
+	RECT wr;
+	u16 title_buffer[256] = {0}; // TODO(bill): gb_local_persist this?
+
+	wc.style = CS_HREDRAW | CS_VREDRAW; // | CS_OWNDC
+	wc.lpfnWndProc   = gb__win32_window_callback;
+	wc.hbrBackground = cast(HBRUSH)GetStockObject(0/*WHITE_BRUSH*/);
+	wc.lpszMenuName  = NULL;
+	wc.lpszClassName = L"gb-win32-wndclass"; // TODO(bill): Is this enough?
+	wc.hInstance     = GetModuleHandleW(NULL);
+
+	if (RegisterClassExW(&wc) == 0) {
+		MessageBoxW(NULL, L"Failed to register the window class", L"ERROR", MB_OK | MB_ICONEXCLAMATION);
+		return false;
+	}
+
+	if ((window_flags & gbWindow_Fullscreen) && !(window_flags & gbWindow_Borderless)) {
+		DEVMODEW screen_settings = {gb_size_of(DEVMODEW)};
+		screen_settings.dmPelsWidth	 = mode.width;
+		screen_settings.dmPelsHeight = mode.height;
+		screen_settings.dmBitsPerPel = mode.bits_per_pixel;
+		screen_settings.dmFields     = DM_BITSPERPEL|DM_PELSWIDTH|DM_PELSHEIGHT;
+
+		if (ChangeDisplaySettingsW(&screen_settings, CDS_FULLSCREEN) != DISP_CHANGE_SUCCESSFUL) {
+			if (MessageBoxW(NULL, L"The requested fullscreen mode is not supported by\n"
+			                L"your video card. Use windowed mode instead?",
+			                L"",
+			                MB_YESNO|MB_ICONEXCLAMATION) == IDYES) {
+				window_flags &= ~gbWindow_Fullscreen;
+			} else {
+				mode = gb_video_mode_get_desktop();
+				screen_settings.dmPelsWidth	 = mode.width;
+				screen_settings.dmPelsHeight = mode.height;
+				screen_settings.dmBitsPerPel = mode.bits_per_pixel;
+				ChangeDisplaySettingsW(&screen_settings, CDS_FULLSCREEN);
+			}
+		}
+	}
+
+
+	// ex_style = WS_EX_APPWINDOW | WS_EX_WINDOWEDGE;
+	// style = WS_CLIPSIBLINGS | WS_CLIPCHILDREN | WS_VISIBLE | WS_THICKFRAME | WS_SYSMENU | WS_MAXIMIZEBOX | WS_MINIMIZEBOX;
+
+	style |= WS_VISIBLE;
+
+	if (window_flags & gbWindow_Hidden)       style &= ~WS_VISIBLE;
+	if (window_flags & gbWindow_Resizable)    style |= WS_THICKFRAME | WS_MAXIMIZEBOX;
+	if (window_flags & gbWindow_Maximized)    style |=  WS_MAXIMIZE;
+	if (window_flags & gbWindow_Minimized)    style |=  WS_MINIMIZE;
+
+	// NOTE(bill): Completely ignore the given mode and just change it
+	if (window_flags & gbWindow_FullscreenDesktop) {
+		mode = gb_video_mode_get_desktop();
+	}
+
+	if ((window_flags & gbWindow_Fullscreen) || (window_flags & gbWindow_Borderless)) {
+		style |= WS_POPUP;
+	} else {
+		style |= WS_OVERLAPPED | WS_CAPTION | WS_SYSMENU | WS_MINIMIZEBOX;
+	}
+
+
+	wr.left   = 0;
+	wr.top    = 0;
+	wr.right  = mode.width;
+	wr.bottom = mode.height;
+	AdjustWindowRect(&wr, style, false);
+
+	p->window_flags  = window_flags;
+	p->window_handle = CreateWindowExW(ex_style,
+	                                   wc.lpszClassName,
+	                                   cast(wchar_t const *)gb_utf8_to_ucs2(title_buffer, gb_size_of(title_buffer), window_title),
+	                                   style,
+	                                   CW_USEDEFAULT, CW_USEDEFAULT,
+	                                   wr.right - wr.left, wr.bottom - wr.top,
+	                                   0, 0,
+	                                   GetModuleHandleW(NULL),
+	                                   NULL);
+
+	if (!p->window_handle) {
+		MessageBoxW(NULL, L"Window creation failed", L"Error", MB_OK|MB_ICONEXCLAMATION);
+		return false;
+	}
+
+	p->win32_dc = GetDC(cast(HWND)p->window_handle);
+
+	p->renderer_type = type;
+	switch (p->renderer_type) {
+	case gbRenderer_Opengl: {
+		wglCreateContextAttribsARB_Proc *wglCreateContextAttribsARB;
+		i32 attribs[8] = {0};
+		isize c = 0;
+
+		PIXELFORMATDESCRIPTOR pfd = {gb_size_of(PIXELFORMATDESCRIPTOR)};
+		pfd.nVersion     = 1;
+		pfd.dwFlags      = PFD_DRAW_TO_WINDOW | PFD_SUPPORT_OPENGL | PFD_DOUBLEBUFFER;
+		pfd.iPixelType   = PFD_TYPE_RGBA;
+		pfd.cColorBits   = 32;
+		pfd.cAlphaBits   = 8;
+		pfd.cDepthBits   = 24;
+		pfd.cStencilBits = 8;
+		pfd.iLayerType   = PFD_MAIN_PLANE;
+
+		SetPixelFormat(cast(HDC)p->win32_dc, ChoosePixelFormat(cast(HDC)p->win32_dc, &pfd), NULL);
+		p->opengl.context = cast(void *)wglCreateContext(cast(HDC)p->win32_dc);
+		wglMakeCurrent(cast(HDC)p->win32_dc, cast(HGLRC)p->opengl.context);
+
+		if (p->opengl.major > 0) {
+			attribs[c++] = 0x2091; // WGL_CONTEXT_MAJOR_VERSION_ARB
+			attribs[c++] = gb_max(p->opengl.major, 1);
+		}
+		if (p->opengl.major > 0 && p->opengl.minor >= 0) {
+			attribs[c++] = 0x2092; // WGL_CONTEXT_MINOR_VERSION_ARB
+			attribs[c++] = gb_max(p->opengl.minor, 0);
+		}
+
+		if (p->opengl.core) {
+			attribs[c++] = 0x9126; // WGL_CONTEXT_PROFILE_MASK_ARB
+			attribs[c++] = 0x0001; // WGL_CONTEXT_CORE_PROFILE_BIT_ARB
+		} else if (p->opengl.compatible) {
+			attribs[c++] = 0x9126; // WGL_CONTEXT_PROFILE_MASK_ARB
+			attribs[c++] = 0x0002; // WGL_CONTEXT_COMPATIBILITY_PROFILE_BIT_ARB
+		}
+		attribs[c++] = 0; // NOTE(bill): tells the proc that this is the end of attribs
+
+		wglCreateContextAttribsARB = cast(wglCreateContextAttribsARB_Proc *)wglGetProcAddress("wglCreateContextAttribsARB");
+		if (wglCreateContextAttribsARB) {
+			HGLRC rc = cast(HGLRC)wglCreateContextAttribsARB(p->win32_dc, 0, attribs);
+			if (rc && wglMakeCurrent(cast(HDC)p->win32_dc, rc)) {
+				p->opengl.context = rc;
+			} else {
+				// TODO(bill): Handle errors from GetLastError
+				// ERROR_INVALID_VERSION_ARB 0x2095
+				// ERROR_INVALID_PROFILE_ARB 0x2096
+			}
+		}
+
+	} break;
+
+	case gbRenderer_Software:
+		gb__platform_resize_dib_section(p, mode.width, mode.height);
+		break;
+
+	default:
+		GB_PANIC("Unknown window type");
+		break;
+	}
+
+	SetForegroundWindow(cast(HWND)p->window_handle);
+	SetFocus(cast(HWND)p->window_handle);
+	SetWindowLongPtrW(cast(HWND)p->window_handle, GWLP_USERDATA, cast(LONG_PTR)p);
+
+	p->window_width  = mode.width;
+	p->window_height = mode.height;
+
+	if (p->renderer_type == gbRenderer_Opengl) {
+		p->opengl.dll_handle = gb_dll_load("opengl32.dll");
+	}
+
+	{ // Load XInput
+		// TODO(bill): What other dlls should I look for?
+		gbDllHandle xinput_library = gb_dll_load("xinput1_4.dll");
+		p->xinput.get_state = gbXInputGetState_Stub;
+		p->xinput.set_state = gbXInputSetState_Stub;
+
+		if (!xinput_library) xinput_library = gb_dll_load("xinput9_1_0.dll");
+		if (!xinput_library) xinput_library = gb_dll_load("xinput1_3.dll");
+		if (!xinput_library) {
+			// TODO(bill): Proper Diagnostic
+			gb_printf_err("XInput could not be loaded. Controllers will not work!\n");
+		} else {
+			p->xinput.get_state = cast(gbXInputGetStateProc *)gb_dll_proc_address(xinput_library, "XInputGetState");
+			p->xinput.set_state = cast(gbXInputSetStateProc *)gb_dll_proc_address(xinput_library, "XInputSetState");
+		}
+	}
+
+	// Init keys
+	gb_zero_array(p->keys, gb_count_of(p->keys));
+
+	p->is_initialized = true;
+	return true;
+}
+
+gb_inline b32 gb_platform_init_with_software(gbPlatform *p, char const *window_title,
+                                             i32 width, i32 height, u32 window_flags) {
+	gbVideoMode mode;
+	mode.width          = width;
+	mode.height         = height;
+	mode.bits_per_pixel = 32;
+	return gb__platform_init(p, window_title, mode, gbRenderer_Software, window_flags);
+}
+
+gb_inline b32 gb_platform_init_with_opengl(gbPlatform *p, char const *window_title,
+                                           i32 width, i32 height, u32 window_flags, i32 major, i32 minor, b32 core, b32 compatible) {
+	gbVideoMode mode;
+	mode.width          = width;
+	mode.height         = height;
+	mode.bits_per_pixel = 32;
+	p->opengl.major      = major;
+	p->opengl.minor      = minor;
+	p->opengl.core       = cast(b16)core;
+	p->opengl.compatible = cast(b16)compatible;
+	return gb__platform_init(p, window_title, mode, gbRenderer_Opengl, window_flags);
+}
+
+#ifndef _XINPUT_H_
+typedef struct _XINPUT_GAMEPAD {
+	u16 wButtons;
+	u8  bLeftTrigger;
+	u8  bRightTrigger;
+	u16 sThumbLX;
+	u16 sThumbLY;
+	u16 sThumbRX;
+	u16 sThumbRY;
+} XINPUT_GAMEPAD;
+
+typedef struct _XINPUT_STATE {
+	DWORD          dwPacketNumber;
+	XINPUT_GAMEPAD Gamepad;
+} XINPUT_STATE;
+
+typedef struct _XINPUT_VIBRATION {
+	u16 wLeftMotorSpeed;
+	u16 wRightMotorSpeed;
+} XINPUT_VIBRATION;
+
+#define XINPUT_GAMEPAD_DPAD_UP              0x00000001
+#define XINPUT_GAMEPAD_DPAD_DOWN            0x00000002
+#define XINPUT_GAMEPAD_DPAD_LEFT            0x00000004
+#define XINPUT_GAMEPAD_DPAD_RIGHT           0x00000008
+#define XINPUT_GAMEPAD_START                0x00000010
+#define XINPUT_GAMEPAD_BACK                 0x00000020
+#define XINPUT_GAMEPAD_LEFT_THUMB           0x00000040
+#define XINPUT_GAMEPAD_RIGHT_THUMB          0x00000080
+#define XINPUT_GAMEPAD_LEFT_SHOULDER        0x0100
+#define XINPUT_GAMEPAD_RIGHT_SHOULDER       0x0200
+#define XINPUT_GAMEPAD_A                    0x1000
+#define XINPUT_GAMEPAD_B                    0x2000
+#define XINPUT_GAMEPAD_X                    0x4000
+#define XINPUT_GAMEPAD_Y                    0x8000
+#define XINPUT_GAMEPAD_LEFT_THUMB_DEADZONE  7849
+#define XINPUT_GAMEPAD_RIGHT_THUMB_DEADZONE 8689
+#define XINPUT_GAMEPAD_TRIGGER_THRESHOLD    30
+#endif
+
+#ifndef XUSER_MAX_COUNT
+#define XUSER_MAX_COUNT 4
+#endif
+
+void gb_platform_update(gbPlatform *p) {
+	isize i;
+
+	{ // NOTE(bill): Set window state
+		// TODO(bill): Should this be moved to gb__win32_window_callback ?
+		RECT window_rect;
+		i32 x, y, w, h;
+
+		GetClientRect(cast(HWND)p->window_handle, &window_rect);
+		x = window_rect.left;
+		y = window_rect.top;
+		w = window_rect.right - window_rect.left;
+		h = window_rect.bottom - window_rect.top;
+
+		if ((p->window_width != w) || (p->window_height != h)) {
+			if (p->renderer_type == gbRenderer_Software)
+				gb__platform_resize_dib_section(p, w, h);
+		}
+
+
+		p->window_x = x;
+		p->window_y = y;
+		p->window_width = w;
+		p->window_height = h;
+		GB_MASK_SET(p->window_flags, IsIconic(cast(HWND)p->window_handle) != 0, gbWindow_Minimized);
+
+		p->window_has_focus = GetFocus() == cast(HWND)p->window_handle;
+	}
+
+	{ // NOTE(bill): Set mouse position
+		POINT mouse_pos;
+		DWORD win_button_id[gbMouseButton_Count] = {
+			VK_LBUTTON,
+			VK_MBUTTON,
+			VK_RBUTTON,
+			VK_XBUTTON1,
+			VK_XBUTTON2,
+		};
+
+		// NOTE(bill): This needs to be GetAsyncKeyState as RAWMOUSE doesn't aways work for some odd reason
+		// TODO(bill): Try and get RAWMOUSE to work for key presses
+		for (i = 0; i < gbMouseButton_Count; i++)
+			gb_key_state_update(p->mouse_buttons+i, GetAsyncKeyState(win_button_id[i]) < 0);
+
+		GetCursorPos(&mouse_pos);
+		ScreenToClient(cast(HWND)p->window_handle, &mouse_pos);
+		{
+			i32 x = mouse_pos.x;
+			i32 y = p->window_height-1 - mouse_pos.y;
+			p->mouse_dx = x - p->mouse_x;
+			p->mouse_dy = y - p->mouse_y;
+			p->mouse_x = x;
+			p->mouse_y = y;
+		}
+
+		if (p->mouse_clip) {
+			b32 update = false;
+			i32 x = p->mouse_x;
+			i32 y = p->mouse_y;
+			if (p->mouse_x < 0) {
+				x = 0;
+				update = true;
+			} else if (p->mouse_y > p->window_height-1) {
+				y = p->window_height-1;
+				update = true;
+			}
+
+			if (p->mouse_y < 0) {
+				y = 0;
+				update = true;
+			} else if (p->mouse_x > p->window_width-1) {
+				x = p->window_width-1;
+				update = true;
+			}
+
+			if (update)
+				gb_platform_set_mouse_position(p, x, y);
+		}
+
+
+	}
+
+
+	// NOTE(bill): Set Key/Button states
+	if (p->window_has_focus) {
+		p->char_buffer_count = 0; // TODO(bill): Reset buffer count here or else where?
+
+		// NOTE(bill): Need to update as the keys only get updates on events
+		for (i = 0; i < gbKey_Count; i++) {
+			b32 is_down = (p->keys[i] & gbKeyState_Down) != 0;
+			gb_key_state_update(&p->keys[i], is_down);
+		}
+
+		p->key_modifiers.control = p->keys[gbKey_Lcontrol] | p->keys[gbKey_Rcontrol];
+		p->key_modifiers.alt     = p->keys[gbKey_Lalt]     | p->keys[gbKey_Ralt];
+		p->key_modifiers.shift   = p->keys[gbKey_Lshift]   | p->keys[gbKey_Rshift];
+
+	}
+
+	{ // NOTE(bill): Set Controller states
+		isize max_controller_count = XUSER_MAX_COUNT;
+		if (max_controller_count > gb_count_of(p->game_controllers))
+			max_controller_count = gb_count_of(p->game_controllers);
+
+		for (i = 0; i < max_controller_count; i++) {
+			gbGameController *controller = &p->game_controllers[i];
+			XINPUT_STATE controller_state = {0};
+			if (p->xinput.get_state(cast(DWORD)i, &controller_state) != 0) {
+				// NOTE(bill): The controller is not available
+				controller->is_connected = false;
+			} else {
+				// NOTE(bill): This controller is plugged in
+				// TODO(bill): See if ControllerState.dwPacketNumber increments too rapidly
+				XINPUT_GAMEPAD *pad = &controller_state.Gamepad;
+
+				controller->is_connected = true;
+
+				// TODO(bill): This is a square deadzone, check XInput to verify that the deadzone is "round" and do round deadzone processing.
+				controller->axes[gbControllerAxis_LeftX]  = gb__process_xinput_stick_value(pad->sThumbLX, XINPUT_GAMEPAD_LEFT_THUMB_DEADZONE);
+				controller->axes[gbControllerAxis_LeftY]  = gb__process_xinput_stick_value(pad->sThumbLY, XINPUT_GAMEPAD_LEFT_THUMB_DEADZONE);
+				controller->axes[gbControllerAxis_RightX] = gb__process_xinput_stick_value(pad->sThumbRX, XINPUT_GAMEPAD_RIGHT_THUMB_DEADZONE);
+				controller->axes[gbControllerAxis_RightY] = gb__process_xinput_stick_value(pad->sThumbRY, XINPUT_GAMEPAD_RIGHT_THUMB_DEADZONE);
+
+				controller->axes[gbControllerAxis_LeftTrigger]  = cast(f32)pad->bLeftTrigger / 255.0f;
+				controller->axes[gbControllerAxis_RightTrigger] = cast(f32)pad->bRightTrigger / 255.0f;
+
+
+				if ((controller->axes[gbControllerAxis_LeftX] != 0.0f) ||
+					(controller->axes[gbControllerAxis_LeftY] != 0.0f)) {
+					controller->is_analog = true;
+				}
+
+			#define GB__PROCESS_DIGITAL_BUTTON(button_type, xinput_button) \
+				gb_key_state_update(&controller->buttons[button_type], (pad->wButtons & xinput_button) == xinput_button)
+
+				GB__PROCESS_DIGITAL_BUTTON(gbControllerButton_A,              XINPUT_GAMEPAD_A);
+				GB__PROCESS_DIGITAL_BUTTON(gbControllerButton_B,              XINPUT_GAMEPAD_B);
+				GB__PROCESS_DIGITAL_BUTTON(gbControllerButton_X,              XINPUT_GAMEPAD_X);
+				GB__PROCESS_DIGITAL_BUTTON(gbControllerButton_Y,              XINPUT_GAMEPAD_Y);
+				GB__PROCESS_DIGITAL_BUTTON(gbControllerButton_LeftShoulder,  XINPUT_GAMEPAD_LEFT_SHOULDER);
+				GB__PROCESS_DIGITAL_BUTTON(gbControllerButton_RightShoulder, XINPUT_GAMEPAD_RIGHT_SHOULDER);
+				GB__PROCESS_DIGITAL_BUTTON(gbControllerButton_Start,          XINPUT_GAMEPAD_START);
+				GB__PROCESS_DIGITAL_BUTTON(gbControllerButton_Back,           XINPUT_GAMEPAD_BACK);
+				GB__PROCESS_DIGITAL_BUTTON(gbControllerButton_Left,           XINPUT_GAMEPAD_DPAD_LEFT);
+				GB__PROCESS_DIGITAL_BUTTON(gbControllerButton_Right,          XINPUT_GAMEPAD_DPAD_RIGHT);
+				GB__PROCESS_DIGITAL_BUTTON(gbControllerButton_Down,           XINPUT_GAMEPAD_DPAD_DOWN);
+				GB__PROCESS_DIGITAL_BUTTON(gbControllerButton_Up,             XINPUT_GAMEPAD_DPAD_UP);
+				GB__PROCESS_DIGITAL_BUTTON(gbControllerButton_LeftThumb,     XINPUT_GAMEPAD_LEFT_THUMB);
+				GB__PROCESS_DIGITAL_BUTTON(gbControllerButton_RightThumb,    XINPUT_GAMEPAD_RIGHT_THUMB);
+			#undef GB__PROCESS_DIGITAL_BUTTON
+			}
+		}
+	}
+
+	{ // NOTE(bill): Process pending messages
+		MSG message;
+		for (;;) {
+			BOOL is_okay = PeekMessageW(&message, 0, 0, 0, PM_REMOVE);
+			if (!is_okay) break;
+
+			switch (message.message) {
+			case WM_QUIT:
+				p->quit_requested = true;
+				break;
+
+			default:
+				TranslateMessage(&message);
+				DispatchMessageW(&message);
+				break;
+			}
+		}
+	}
+}
+
+void gb_platform_display(gbPlatform *p) {
+	if (p->renderer_type == gbRenderer_Opengl) {
+		SwapBuffers(cast(HDC)p->win32_dc);
+	} else if (p->renderer_type == gbRenderer_Software) {
+		StretchDIBits(cast(HDC)p->win32_dc,
+		              0, 0, p->window_width, p->window_height,
+		              0, 0, p->window_width, p->window_height,
+		              p->sw_framebuffer.memory,
+		              &p->sw_framebuffer.win32_bmi,
+		              DIB_RGB_COLORS, SRCCOPY);
+	} else {
+		GB_PANIC("Invalid window rendering type");
+	}
+
+	{
+		f64 prev_time = p->curr_time;
+		f64 curr_time = gb_time_now();
+		p->dt_for_frame = curr_time - prev_time;
+		p->curr_time = curr_time;
+	}
+}
+
+
+void gb_platform_destroy(gbPlatform *p) {
+	if (p->renderer_type == gbRenderer_Opengl)
+		wglDeleteContext(cast(HGLRC)p->opengl.context);
+	else if (p->renderer_type == gbRenderer_Software)
+		gb_vm_free(gb_virtual_memory(p->sw_framebuffer.memory, p->sw_framebuffer.memory_size));
+
+	DestroyWindow(cast(HWND)p->window_handle);
+}
+
+void gb_platform_show_cursor(gbPlatform *p, b32 show) {
+	gb_unused(p);
+	ShowCursor(show);
+}
+
+void gb_platform_set_mouse_position(gbPlatform *p, i32 x, i32 y) {
+	POINT point;
+	point.x = cast(LONG)x;
+	point.y = cast(LONG)(p->window_height-1 - y);
+	ClientToScreen(cast(HWND)p->window_handle, &point);
+	SetCursorPos(point.x, point.y);
+
+	p->mouse_x = point.x;
+	p->mouse_y = p->window_height-1 - point.y;
+}
+
+
+
+void gb_platform_set_controller_vibration(gbPlatform *p, isize index, f32 left_motor, f32 right_motor) {
+	if (gb_is_between(index, 0, GB_MAX_GAME_CONTROLLER_COUNT-1)) {
+		XINPUT_VIBRATION vibration = {0};
+		left_motor  = gb_clamp01(left_motor);
+		right_motor = gb_clamp01(right_motor);
+		vibration.wLeftMotorSpeed  = cast(WORD)(65535 * left_motor);
+		vibration.wRightMotorSpeed = cast(WORD)(65535 * right_motor);
+
+		p->xinput.set_state(cast(DWORD)index, &vibration);
+	}
+}
+
+
+void gb_platform_set_window_position(gbPlatform *p, i32 x, i32 y) {
+	RECT rect;
+	i32 width, height;
+
+	GetClientRect(cast(HWND)p->window_handle, &rect);
+	width  = rect.right - rect.left;
+	height = rect.bottom - rect.top;
+	MoveWindow(cast(HWND)p->window_handle, x, y, width, height, false);
+}
+
+void gb_platform_set_window_title(gbPlatform *p, char const *title, ...) {
+	u16 buffer[256] = {0};
+	char str[512] = {0};
+	va_list va;
+	va_start(va, title);
+	gb_snprintf_va(str, gb_size_of(str), title, va);
+	va_end(va);
+
+	if (str[0] != '\0')
+		SetWindowTextW(cast(HWND)p->window_handle, cast(wchar_t const *)gb_utf8_to_ucs2(buffer, gb_size_of(buffer), str));
+}
+
+void gb_platform_toggle_fullscreen(gbPlatform *p, b32 fullscreen_desktop) {
+	// NOTE(bill): From the man himself, Raymond Chen! (Modified for my need.)
+	HWND handle = cast(HWND)p->window_handle;
+	DWORD style = cast(DWORD)GetWindowLongW(handle, GWL_STYLE);
+	WINDOWPLACEMENT placement;
+
+	if (style & WS_OVERLAPPEDWINDOW) {
+		MONITORINFO monitor_info = {gb_size_of(monitor_info)};
+		if (GetWindowPlacement(handle, &placement) &&
+		    GetMonitorInfoW(MonitorFromWindow(handle, 1), &monitor_info)) {
+			style &= ~WS_OVERLAPPEDWINDOW;
+			if (fullscreen_desktop) {
+				style &= ~WS_CAPTION;
+				style |= WS_POPUP;
+			}
+			SetWindowLongW(handle, GWL_STYLE, style);
+			SetWindowPos(handle, HWND_TOP,
+			             monitor_info.rcMonitor.left, monitor_info.rcMonitor.top,
+			             monitor_info.rcMonitor.right - monitor_info.rcMonitor.left,
+			             monitor_info.rcMonitor.bottom - monitor_info.rcMonitor.top,
+			             SWP_NOOWNERZORDER | SWP_FRAMECHANGED);
+
+			if (fullscreen_desktop)
+				p->window_flags |= gbWindow_FullscreenDesktop;
+			else
+				p->window_flags |= gbWindow_Fullscreen;
+		}
+	} else {
+		style &= ~WS_POPUP;
+		style |= WS_OVERLAPPEDWINDOW | WS_CAPTION;
+		SetWindowLongW(handle, GWL_STYLE, style);
+		SetWindowPlacement(handle, &placement);
+		SetWindowPos(handle, 0, 0, 0, 0, 0,
+		             SWP_NOMOVE | SWP_NOSIZE | SWP_NOZORDER |
+		             SWP_NOOWNERZORDER | SWP_FRAMECHANGED);
+
+		p->window_flags &= ~gbWindow_Fullscreen;
+	}
+}
+
+void gb_platform_toggle_borderless(gbPlatform *p) {
+	HWND handle = cast(HWND)p->window_handle;
+	DWORD style = GetWindowLongW(handle, GWL_STYLE);
+	b32 is_borderless = (style & WS_POPUP) != 0;
+
+	GB_MASK_SET(style, is_borderless,  WS_OVERLAPPEDWINDOW | WS_CAPTION);
+	GB_MASK_SET(style, !is_borderless, WS_POPUP);
+
+	SetWindowLongW(handle, GWL_STYLE, style);
+
+	GB_MASK_SET(p->window_flags, !is_borderless, gbWindow_Borderless);
+}
+
+
+
+gb_inline void gb_platform_make_opengl_context_current(gbPlatform *p) {
+	if (p->renderer_type == gbRenderer_Opengl) {
+		wglMakeCurrent(cast(HDC)p->win32_dc, cast(HGLRC)p->opengl.context);
+	}
+}
+
+gb_inline void gb_platform_show_window(gbPlatform *p) {
+	ShowWindow(cast(HWND)p->window_handle, SW_SHOW);
+	p->window_flags &= ~gbWindow_Hidden;
+}
+
+gb_inline void gb_platform_hide_window(gbPlatform *p) {
+	ShowWindow(cast(HWND)p->window_handle, SW_HIDE);
+	p->window_flags |= gbWindow_Hidden;
+}
+
+gb_inline gbVideoMode gb_video_mode_get_desktop(void) {
+	DEVMODEW win32_mode = {gb_size_of(win32_mode)};
+	EnumDisplaySettingsW(NULL, ENUM_CURRENT_SETTINGS, &win32_mode);
+	return gb_video_mode(win32_mode.dmPelsWidth, win32_mode.dmPelsHeight, win32_mode.dmBitsPerPel);
+}
+
+isize gb_video_mode_get_fullscreen_modes(gbVideoMode *modes, isize max_mode_count) {
+	DEVMODEW win32_mode = {gb_size_of(win32_mode)};
+	i32 count;
+	for (count = 0;
+	     count < max_mode_count && EnumDisplaySettingsW(NULL, count, &win32_mode);
+	     count++) {
+		modes[count] = gb_video_mode(win32_mode.dmPelsWidth, win32_mode.dmPelsHeight, win32_mode.dmBitsPerPel);
+	}
+
+	gb_sort_array(modes, count, gb_video_mode_dsc_cmp);
+	return count;
+}
+
+
+
+b32 gb_platform_has_clipboard_text(gbPlatform *p) {
+	b32 result = false;
+
+	if (IsClipboardFormatAvailable(1/*CF_TEXT*/) &&
+	    OpenClipboard(cast(HWND)p->window_handle)) {
+		HANDLE mem = GetClipboardData(1/*CF_TEXT*/);
+		if (mem) {
+			char *str = cast(char *)GlobalLock(mem);
+			if (str && str[0] != '\0')
+				result = true;
+			GlobalUnlock(mem);
+		} else {
+			return false;
+		}
+
+		CloseClipboard();
+	}
+
+	return result;
+}
+
+// TODO(bill): Handle UTF-8
+void gb_platform_set_clipboard_text(gbPlatform *p, char const *str) {
+	if (OpenClipboard(cast(HWND)p->window_handle)) {
+		isize i, len = gb_strlen(str)+1;
+
+		HANDLE mem = cast(HANDLE)GlobalAlloc(0x0002/*GMEM_MOVEABLE*/, len);
+		if (mem) {
+			char *dst = cast(char *)GlobalLock(mem);
+			if (dst) {
+				for (i = 0; str[i]; i++) {
+					// TODO(bill): Does this cause a buffer overflow?
+					// NOTE(bill): Change \n to \r\n 'cause windows
+					if (str[i] == '\n' && (i == 0 || str[i-1] != '\r')) {
+						*dst++ = '\r';
+					}
+					*dst++ = str[i];
+				}
+				*dst = 0;
+			}
+			GlobalUnlock(mem);
+		}
+
+		EmptyClipboard();
+		if (!SetClipboardData(1/*CF_TEXT*/, mem))
+			return;
+		CloseClipboard();
+	}
+}
+
+// TODO(bill): Handle UTF-8
+char *gb_platform_get_clipboard_text(gbPlatform *p, gbAllocator a) {
+	char *text = NULL;
+
+	if (IsClipboardFormatAvailable(1/*CF_TEXT*/) &&
+	    OpenClipboard(cast(HWND)p->window_handle)) {
+		HANDLE mem = GetClipboardData(1/*CF_TEXT*/);
+		if (mem) {
+			char *str = cast(char *)GlobalLock(mem);
+			text = gb_alloc_str(a, str);
+			GlobalUnlock(mem);
+		} else {
+			return NULL;
+		}
+
+		CloseClipboard();
+	}
+
+	return text;
+}
+
+#elif defined(GB_SYSTEM_OSX)
+
+#include <CoreGraphics/CoreGraphics.h>
+#include <objc/objc.h>
+#include <objc/message.h>
+#include <objc/NSObjCRuntime.h>
+
+#if __LP64__ || (TARGET_OS_EMBEDDED && !TARGET_OS_IPHONE) || TARGET_OS_WIN32 || NS_BUILD_32_LIKE_64
+	#define NSIntegerEncoding  "q"
+	#define NSUIntegerEncoding "L"
+#else
+	#define NSIntegerEncoding  "i"
+	#define NSUIntegerEncoding "I"
+#endif
+
+#ifdef __OBJC__
+	#import <Cocoa/Cocoa.h>
+#else
+	typedef CGPoint NSPoint;
+	typedef CGSize  NSSize;
+	typedef CGRect  NSRect;
+
+	extern id NSApp;
+	extern id const NSDefaultRunLoopMode;
+#endif
+
+#if defined(__OBJC__) && __has_feature(objc_arc)
+#error TODO(bill): Cannot compile as objective-c code just yet!
+#endif
+
+// ABI is a bit different between platforms
+#ifdef __arm64__
+#define abi_objc_msgSend_stret objc_msgSend
+#else
+#define abi_objc_msgSend_stret objc_msgSend_stret
+#endif
+#ifdef __i386__
+#define abi_objc_msgSend_fpret objc_msgSend_fpret
+#else
+#define abi_objc_msgSend_fpret objc_msgSend
+#endif
+
+#define objc_msgSend_id				((id (*)(id, SEL))objc_msgSend)
+#define objc_msgSend_void			((void (*)(id, SEL))objc_msgSend)
+#define objc_msgSend_void_id		((void (*)(id, SEL, id))objc_msgSend)
+#define objc_msgSend_void_bool		((void (*)(id, SEL, BOOL))objc_msgSend)
+#define objc_msgSend_id_char_const	((id (*)(id, SEL, char const *))objc_msgSend)
+
+gb_internal NSUInteger gb__osx_application_should_terminate(id self, SEL _sel, id sender) {
+	// NOTE(bill): Do nothing
+	return 0;
+}
+
+gb_internal void gb__osx_window_will_close(id self, SEL _sel, id notification) {
+	NSUInteger value = true;
+	object_setInstanceVariable(self, "closed", cast(void *)value);
+}
+
+gb_internal void gb__osx_window_did_become_key(id self, SEL _sel, id notification) {
+	gbPlatform *p = NULL;
+	object_getInstanceVariable(self, "gbPlatform", cast(void **)&p);
+	if (p) {
+		// TODO(bill):
+	}
+}
+
+b32 gb__platform_init(gbPlatform *p, char const *window_title, gbVideoMode mode, gbRendererType type, u32 window_flags) {
+	if (p->is_initialized)
+		return true;
+	// Init Platform
+	{ // Initial OSX State
+		Class appDelegateClass;
+		b32 resultAddProtoc, resultAddMethod;
+		id dgAlloc, dg, menubarAlloc, menubar;
+		id appMenuItemAlloc, appMenuItem;
+		id appMenuAlloc, appMenu;
+
+		#if defined(ARC_AVAILABLE)
+		#error TODO(bill): This code should be compiled as C for now
+		#else
+		id poolAlloc = objc_msgSend_id(cast(id)objc_getClass("NSAutoreleasePool"), sel_registerName("alloc"));
+		p->osx_autorelease_pool = objc_msgSend_id(poolAlloc, sel_registerName("init"));
+		#endif
+
+		objc_msgSend_id(cast(id)objc_getClass("NSApplication"), sel_registerName("sharedApplication"));
+		((void (*)(id, SEL, NSInteger))objc_msgSend)(NSApp, sel_registerName("setActivationPolicy:"), 0);
+
+		appDelegateClass = objc_allocateClassPair((Class)objc_getClass("NSObject"), "AppDelegate", 0);
+		resultAddProtoc = class_addProtocol(appDelegateClass, objc_getProtocol("NSApplicationDelegate"));
+		assert(resultAddProtoc);
+		resultAddMethod = class_addMethod(appDelegateClass, sel_registerName("applicationShouldTerminate:"), cast(IMP)gb__osx_application_should_terminate, NSUIntegerEncoding "@:@");
+		assert(resultAddMethod);
+		dgAlloc = objc_msgSend_id(cast(id)appDelegateClass, sel_registerName("alloc"));
+		dg = objc_msgSend_id(dgAlloc, sel_registerName("init"));
+		#ifndef ARC_AVAILABLE
+		objc_msgSend_void(dg, sel_registerName("autorelease"));
+		#endif
+
+		objc_msgSend_void_id(NSApp, sel_registerName("setDelegate:"), dg);
+		objc_msgSend_void(NSApp, sel_registerName("finishLaunching"));
+
+		menubarAlloc = objc_msgSend_id(cast(id)objc_getClass("NSMenu"), sel_registerName("alloc"));
+		menubar = objc_msgSend_id(menubarAlloc, sel_registerName("init"));
+		#ifndef ARC_AVAILABLE
+		objc_msgSend_void(menubar, sel_registerName("autorelease"));
+		#endif
+
+		appMenuItemAlloc = objc_msgSend_id(cast(id)objc_getClass("NSMenuItem"), sel_registerName("alloc"));
+		appMenuItem = objc_msgSend_id(appMenuItemAlloc, sel_registerName("init"));
+		#ifndef ARC_AVAILABLE
+		objc_msgSend_void(appMenuItem, sel_registerName("autorelease"));
+		#endif
+
+		objc_msgSend_void_id(menubar, sel_registerName("addItem:"), appMenuItem);
+		((id (*)(id, SEL, id))objc_msgSend)(NSApp, sel_registerName("setMainMenu:"), menubar);
+
+		appMenuAlloc = objc_msgSend_id(cast(id)objc_getClass("NSMenu"), sel_registerName("alloc"));
+		appMenu = objc_msgSend_id(appMenuAlloc, sel_registerName("init"));
+		#ifndef ARC_AVAILABLE
+		objc_msgSend_void(appMenu, sel_registerName("autorelease"));
+		#endif
+
+		{
+			id processInfo = objc_msgSend_id(cast(id)objc_getClass("NSProcessInfo"), sel_registerName("processInfo"));
+			id appName = objc_msgSend_id(processInfo, sel_registerName("processName"));
+
+			id quitTitlePrefixString = objc_msgSend_id_char_const(cast(id)objc_getClass("NSString"), sel_registerName("stringWithUTF8String:"), "Quit ");
+			id quitTitle = ((id (*)(id, SEL, id))objc_msgSend)(quitTitlePrefixString, sel_registerName("stringByAppendingString:"), appName);
+
+			id quitMenuItemKey = objc_msgSend_id_char_const(cast(id)objc_getClass("NSString"), sel_registerName("stringWithUTF8String:"), "q");
+			id quitMenuItemAlloc = objc_msgSend_id(cast(id)objc_getClass("NSMenuItem"), sel_registerName("alloc"));
+			id quitMenuItem = ((id (*)(id, SEL, id, SEL, id))objc_msgSend)(quitMenuItemAlloc, sel_registerName("initWithTitle:action:keyEquivalent:"), quitTitle, sel_registerName("terminate:"), quitMenuItemKey);
+			#ifndef ARC_AVAILABLE
+			objc_msgSend_void(quitMenuItem, sel_registerName("autorelease"));
+			#endif
+
+			objc_msgSend_void_id(appMenu, sel_registerName("addItem:"), quitMenuItem);
+			objc_msgSend_void_id(appMenuItem, sel_registerName("setSubmenu:"), appMenu);
+		}
+	}
+
+	{ // Init Window
+		NSRect rect = {{0, 0}, {cast(CGFloat)mode.width, cast(CGFloat)mode.height}};
+		id windowAlloc, window, wdgAlloc, wdg, contentView, titleString;
+		Class WindowDelegateClass;
+		b32 resultAddProtoc, resultAddIvar, resultAddMethod;
+
+		windowAlloc = objc_msgSend_id(cast(id)objc_getClass("NSWindow"), sel_registerName("alloc"));
+		window = ((id (*)(id, SEL, NSRect, NSUInteger, NSUInteger, BOOL))objc_msgSend)(windowAlloc, sel_registerName("initWithContentRect:styleMask:backing:defer:"), rect, 15, 2, NO);
+		#ifndef ARC_AVAILABLE
+		objc_msgSend_void(window, sel_registerName("autorelease"));
+		#endif
+
+		// when we are not using ARC, than window will be added to autorelease pool
+		// so if we close it by hand (pressing red button), we don't want it to be released for us
+		// so it will be released by autorelease pool later
+		objc_msgSend_void_bool(window, sel_registerName("setReleasedWhenClosed:"), NO);
+
+		WindowDelegateClass = objc_allocateClassPair((Class)objc_getClass("NSObject"), "WindowDelegate", 0);
+		resultAddProtoc = class_addProtocol(WindowDelegateClass, objc_getProtocol("NSWindowDelegate"));
+		GB_ASSERT(resultAddProtoc);
+		resultAddIvar = class_addIvar(WindowDelegateClass, "closed", gb_size_of(NSUInteger), rint(log2(gb_size_of(NSUInteger))), NSUIntegerEncoding);
+		GB_ASSERT(resultAddIvar);
+		resultAddIvar = class_addIvar(WindowDelegateClass, "gbPlatform", gb_size_of(void *), rint(log2(gb_size_of(void *))), "ˆv");
+		GB_ASSERT(resultAddIvar);
+		resultAddMethod = class_addMethod(WindowDelegateClass, sel_registerName("windowWillClose:"), cast(IMP)gb__osx_window_will_close,  "v@:@");
+		GB_ASSERT(resultAddMethod);
+		resultAddMethod = class_addMethod(WindowDelegateClass, sel_registerName("windowDidBecomeKey:"), cast(IMP)gb__osx_window_did_become_key,  "v@:@");
+		GB_ASSERT(resultAddMethod);
+		wdgAlloc = objc_msgSend_id(cast(id)WindowDelegateClass, sel_registerName("alloc"));
+		wdg = objc_msgSend_id(wdgAlloc, sel_registerName("init"));
+		#ifndef ARC_AVAILABLE
+		objc_msgSend_void(wdg, sel_registerName("autorelease"));
+		#endif
+
+		objc_msgSend_void_id(window, sel_registerName("setDelegate:"), wdg);
+
+		contentView = objc_msgSend_id(window, sel_registerName("contentView"));
+
+		{
+			NSPoint point = {20, 20};
+			((void (*)(id, SEL, NSPoint))objc_msgSend)(window, sel_registerName("cascadeTopLeftFromPoint:"), point);
+		}
+
+		titleString = objc_msgSend_id_char_const(cast(id)objc_getClass("NSString"), sel_registerName("stringWithUTF8String:"), window_title);
+		objc_msgSend_void_id(window, sel_registerName("setTitle:"), titleString);
+
+		if (type == gbRenderer_Opengl) {
+			// TODO(bill): Make sure this works correctly
+			u32 opengl_hex_version = (p->opengl.major << 12) | (p->opengl.minor << 8);
+			u32 gl_attribs[] = {
+				8, 24,                  // NSOpenGLPFAColorSize, 24,
+				11, 8,                  // NSOpenGLPFAAlphaSize, 8,
+				5,                      // NSOpenGLPFADoubleBuffer,
+				73,                     // NSOpenGLPFAAccelerated,
+				//72,                   // NSOpenGLPFANoRecovery,
+				//55, 1,                // NSOpenGLPFASampleBuffers, 1,
+				//56, 4,                // NSOpenGLPFASamples, 4,
+				99, opengl_hex_version, // NSOpenGLPFAOpenGLProfile, NSOpenGLProfileVersion3_2Core,
+				0
+			};
+
+			id pixel_format_alloc, pixel_format;
+			id opengl_context_alloc, opengl_context;
+
+			pixel_format_alloc = objc_msgSend_id(cast(id)objc_getClass("NSOpenGLPixelFormat"), sel_registerName("alloc"));
+			pixel_format = ((id (*)(id, SEL, const uint32_t*))objc_msgSend)(pixel_format_alloc, sel_registerName("initWithAttributes:"), gl_attribs);
+			#ifndef ARC_AVAILABLE
+			objc_msgSend_void(pixel_format, sel_registerName("autorelease"));
+			#endif
+
+			opengl_context_alloc = objc_msgSend_id(cast(id)objc_getClass("NSOpenGLContext"), sel_registerName("alloc"));
+			opengl_context = ((id (*)(id, SEL, id, id))objc_msgSend)(opengl_context_alloc, sel_registerName("initWithFormat:shareContext:"), pixel_format, nil);
+			#ifndef ARC_AVAILABLE
+			objc_msgSend_void(opengl_context, sel_registerName("autorelease"));
+			#endif
+
+			objc_msgSend_void_id(opengl_context, sel_registerName("setView:"), contentView);
+			objc_msgSend_void_id(window, sel_registerName("makeKeyAndOrderFront:"), window);
+			objc_msgSend_void_bool(window, sel_registerName("setAcceptsMouseMovedEvents:"), YES);
+
+
+			p->window_handle = cast(void *)window;
+			p->opengl.context = cast(void *)opengl_context;
+		} else {
+			GB_PANIC("TODO(bill): Software rendering");
+		}
+
+		{
+			id blackColor = objc_msgSend_id(cast(id)objc_getClass("NSColor"), sel_registerName("blackColor"));
+			objc_msgSend_void_id(window, sel_registerName("setBackgroundColor:"), blackColor);
+			objc_msgSend_void_bool(NSApp, sel_registerName("activateIgnoringOtherApps:"), YES);
+		}
+		object_setInstanceVariable(wdg, "gbPlatform", cast(void *)p);
+
+		p->is_initialized = true;
+	}
+
+	return true;
+}
+
+// NOTE(bill): Software rendering
+b32 gb_platform_init_with_software(gbPlatform *p, char const *window_title, i32 width, i32 height, u32 window_flags) {
+	GB_PANIC("TODO(bill): Software rendering in not yet implemented on OS X\n");
+	return gb__platform_init(p, window_title, gb_video_mode(width, height, 32), gbRenderer_Software, window_flags);
+}
+// NOTE(bill): OpenGL Rendering
+b32 gb_platform_init_with_opengl(gbPlatform *p, char const *window_title, i32 width, i32 height, u32 window_flags,
+                                 i32 major, i32 minor, b32 core, b32 compatible) {
+
+	p->opengl.major = major;
+	p->opengl.minor = minor;
+	p->opengl.core  = core;
+	p->opengl.compatible = compatible;
+	return gb__platform_init(p, window_title, gb_video_mode(width, height, 32), gbRenderer_Opengl, window_flags);
+}
+
+// NOTE(bill): Reverse engineering can be fun!!!
+gb_internal gbKeyType gb__osx_from_key_code(u16 key_code) {
+	switch (key_code) {
+	default: return gbKey_Unknown;
+	// NOTE(bill): WHO THE FUCK DESIGNED THIS VIRTUAL KEY CODE SYSTEM?!
+	// THEY ARE FUCKING IDIOTS!
+	case 0x1d: return gbKey_0;
+	case 0x12: return gbKey_1;
+	case 0x13: return gbKey_2;
+	case 0x14: return gbKey_3;
+	case 0x15: return gbKey_4;
+	case 0x17: return gbKey_5;
+	case 0x16: return gbKey_6;
+	case 0x1a: return gbKey_7;
+	case 0x1c: return gbKey_8;
+	case 0x19: return gbKey_9;
+
+	case 0x00: return gbKey_A;
+	case 0x0b: return gbKey_B;
+	case 0x08: return gbKey_C;
+	case 0x02: return gbKey_D;
+	case 0x0e: return gbKey_E;
+	case 0x03: return gbKey_F;
+	case 0x05: return gbKey_G;
+	case 0x04: return gbKey_H;
+	case 0x22: return gbKey_I;
+	case 0x26: return gbKey_J;
+	case 0x28: return gbKey_K;
+	case 0x25: return gbKey_L;
+	case 0x2e: return gbKey_M;
+	case 0x2d: return gbKey_N;
+	case 0x1f: return gbKey_O;
+	case 0x23: return gbKey_P;
+	case 0x0c: return gbKey_Q;
+	case 0x0f: return gbKey_R;
+	case 0x01: return gbKey_S;
+	case 0x11: return gbKey_T;
+	case 0x20: return gbKey_U;
+	case 0x09: return gbKey_V;
+	case 0x0d: return gbKey_W;
+	case 0x07: return gbKey_X;
+	case 0x10: return gbKey_Y;
+	case 0x06: return gbKey_Z;
+
+	case 0x21: return gbKey_Lbracket;
+	case 0x1e: return gbKey_Rbracket;
+	case 0x29: return gbKey_Semicolon;
+	case 0x2b: return gbKey_Comma;
+	case 0x2f: return gbKey_Period;
+	case 0x27: return gbKey_Quote;
+	case 0x2c: return gbKey_Slash;
+	case 0x2a: return gbKey_Backslash;
+	case 0x32: return gbKey_Grave;
+	case 0x18: return gbKey_Equals;
+	case 0x1b: return gbKey_Minus;
+	case 0x31: return gbKey_Space;
+
+	case 0x35: return gbKey_Escape;       // Escape
+	case 0x3b: return gbKey_Lcontrol;     // Left Control
+	case 0x38: return gbKey_Lshift;       // Left Shift
+	case 0x3a: return gbKey_Lalt;         // Left Alt
+	case 0x37: return gbKey_Lsystem;      // Left OS specific: window (Windows and Linux), apple/cmd (MacOS X), ...
+	case 0x3e: return gbKey_Rcontrol;     // Right Control
+	case 0x3c: return gbKey_Rshift;       // Right Shift
+	case 0x3d: return gbKey_Ralt;         // Right Alt
+	// case 0x37: return gbKey_Rsystem;      // Right OS specific: window (Windows and Linux), apple/cmd (MacOS X), ...
+	case 0x6e: return gbKey_Menu;         // Menu
+	case 0x24: return gbKey_Return;       // Return
+	case 0x33: return gbKey_Backspace;    // Backspace
+	case 0x30: return gbKey_Tab;          // Tabulation
+	case 0x74: return gbKey_Pageup;       // Page up
+	case 0x79: return gbKey_Pagedown;     // Page down
+	case 0x77: return gbKey_End;          // End
+	case 0x73: return gbKey_Home;         // Home
+	case 0x72: return gbKey_Insert;       // Insert
+	case 0x75: return gbKey_Delete;       // Delete
+	case 0x45: return gbKey_Plus;         // +
+	case 0x4e: return gbKey_Subtract;     // -
+	case 0x43: return gbKey_Multiply;     // *
+	case 0x4b: return gbKey_Divide;       // /
+	case 0x7b: return gbKey_Left;         // Left arrow
+	case 0x7c: return gbKey_Right;        // Right arrow
+	case 0x7e: return gbKey_Up;           // Up arrow
+	case 0x7d: return gbKey_Down;         // Down arrow
+	case 0x52: return gbKey_Numpad0;      // Numpad 0
+	case 0x53: return gbKey_Numpad1;      // Numpad 1
+	case 0x54: return gbKey_Numpad2;      // Numpad 2
+	case 0x55: return gbKey_Numpad3;      // Numpad 3
+	case 0x56: return gbKey_Numpad4;      // Numpad 4
+	case 0x57: return gbKey_Numpad5;      // Numpad 5
+	case 0x58: return gbKey_Numpad6;      // Numpad 6
+	case 0x59: return gbKey_Numpad7;      // Numpad 7
+	case 0x5b: return gbKey_Numpad8;      // Numpad 8
+	case 0x5c: return gbKey_Numpad9;      // Numpad 9
+	case 0x41: return gbKey_NumpadDot;    // Numpad .
+	case 0x4c: return gbKey_NumpadEnter;  // Numpad Enter
+	case 0x7a: return gbKey_F1;           // F1
+	case 0x78: return gbKey_F2;           // F2
+	case 0x63: return gbKey_F3;           // F3
+	case 0x76: return gbKey_F4;           // F4
+	case 0x60: return gbKey_F5;           // F5
+	case 0x61: return gbKey_F6;           // F6
+	case 0x62: return gbKey_F7;           // F7
+	case 0x64: return gbKey_F8;           // F8
+	case 0x65: return gbKey_F9;           // F8
+	case 0x6d: return gbKey_F10;          // F10
+	case 0x67: return gbKey_F11;          // F11
+	case 0x6f: return gbKey_F12;          // F12
+	case 0x69: return gbKey_F13;          // F13
+	case 0x6b: return gbKey_F14;          // F14
+	case 0x71: return gbKey_F15;          // F15
+	// case : return gbKey_Pause;        // Pause // NOTE(bill): Not possible on OS X
+	}
+}
+
+gb_internal void gb__osx_on_cocoa_event(gbPlatform *p, id event, id window) {
+	if (!event) {
+		return;
+	} else if (objc_msgSend_id(window, sel_registerName("delegate"))) {
+		NSUInteger event_type = ((NSUInteger (*)(id, SEL))objc_msgSend)(event, sel_registerName("type"));
+		switch (event_type) {
+		case 1: gb_key_state_update(&p->mouse_buttons[gbMouseButton_Left],  true);  break; // NSLeftMouseDown
+		case 2: gb_key_state_update(&p->mouse_buttons[gbMouseButton_Left],  false); break; // NSLeftMouseUp
+		case 3: gb_key_state_update(&p->mouse_buttons[gbMouseButton_Right], true);  break; // NSRightMouseDown
+		case 4: gb_key_state_update(&p->mouse_buttons[gbMouseButton_Right], false); break; // NSRightMouseUp
+		case 25: { // NSOtherMouseDown
+			// TODO(bill): Test thoroughly
+			NSInteger number = ((NSInteger (*)(id, SEL))objc_msgSend)(event, sel_registerName("buttonNumber"));
+			if (number == 2) gb_key_state_update(&p->mouse_buttons[gbMouseButton_Middle], true);
+			if (number == 3) gb_key_state_update(&p->mouse_buttons[gbMouseButton_X1],     true);
+			if (number == 4) gb_key_state_update(&p->mouse_buttons[gbMouseButton_X2],     true);
+		} break;
+		case 26: { // NSOtherMouseUp
+			NSInteger number = ((NSInteger (*)(id, SEL))objc_msgSend)(event, sel_registerName("buttonNumber"));
+			if (number == 2) gb_key_state_update(&p->mouse_buttons[gbMouseButton_Middle], false);
+			if (number == 3) gb_key_state_update(&p->mouse_buttons[gbMouseButton_X1],     false);
+			if (number == 4) gb_key_state_update(&p->mouse_buttons[gbMouseButton_X2],     false);
+
+		} break;
+
+		// TODO(bill): Scroll wheel
+		case 22: { // NSScrollWheel
+			CGFloat dx = ((CGFloat (*)(id, SEL))abi_objc_msgSend_fpret)(event, sel_registerName("scrollingDeltaX"));
+			CGFloat dy = ((CGFloat (*)(id, SEL))abi_objc_msgSend_fpret)(event, sel_registerName("scrollingDeltaY"));
+			BOOL precision_scrolling = ((BOOL (*)(id, SEL))objc_msgSend)(event, sel_registerName("hasPreciseScrollingDeltas"));
+			if (precision_scrolling) {
+				dx *= 0.1f;
+				dy *= 0.1f;
+			}
+			// TODO(bill): Handle sideways
+			p->mouse_wheel_delta = dy;
+			// p->mouse_wheel_dy = dy;
+			// gb_printf("%f %f\n", dx, dy);
+		} break;
+
+		case 12: { // NSFlagsChanged
+		#if 0
+			// TODO(bill): Reverse engineer this properly
+			NSUInteger modifiers = ((NSUInteger (*)(id, SEL))objc_msgSend)(event, sel_registerName("modifierFlags"));
+			u32 upper_mask = (modifiers & 0xffff0000ul) >> 16;
+			b32 shift   = (upper_mask & 0x02) != 0;
+			b32 control = (upper_mask & 0x04) != 0;
+			b32 alt     = (upper_mask & 0x08) != 0;
+			b32 command = (upper_mask & 0x10) != 0;
+		#endif
+
+			// gb_printf("%u\n", keys.mask);
+			// gb_printf("%x\n", cast(u32)modifiers);
+		} break;
+
+		case 10: { // NSKeyDown
+			u16 key_code;
+
+			id input_text = objc_msgSend_id(event, sel_registerName("characters"));
+			char const *input_text_utf8 = ((char const *(*)(id, SEL))objc_msgSend)(input_text, sel_registerName("UTF8String"));
+			p->char_buffer_count = gb_strnlen(input_text_utf8, gb_size_of(p->char_buffer));
+			gb_memcopy(p->char_buffer, input_text_utf8, p->char_buffer_count);
+
+			key_code = ((unsigned short (*)(id, SEL))objc_msgSend)(event, sel_registerName("keyCode"));
+			gb_key_state_update(&p->keys[gb__osx_from_key_code(key_code)], true);
+		} break;
+
+		case 11: { // NSKeyUp
+			u16 key_code = ((unsigned short (*)(id, SEL))objc_msgSend)(event, sel_registerName("keyCode"));
+			gb_key_state_update(&p->keys[gb__osx_from_key_code(key_code)], false);
+		} break;
+
+		default: break;
+		}
+
+		objc_msgSend_void_id(NSApp, sel_registerName("sendEvent:"), event);
+	}
+}
+
+
+void gb_platform_update(gbPlatform *p) {
+	id window, key_window, content_view;
+	NSRect original_frame;
+
+	window = cast(id)p->window_handle;
+	key_window = objc_msgSend_id(NSApp, sel_registerName("keyWindow"));
+	p->window_has_focus = key_window == window; // TODO(bill): Is this right
+
+
+	if (p->window_has_focus) {
+		isize i;
+		p->char_buffer_count = 0; // TODO(bill): Reset buffer count here or else where?
+
+		// NOTE(bill): Need to update as the keys only get updates on events
+		for (i = 0; i < gbKey_Count; i++) {
+			b32 is_down = (p->keys[i] & gbKeyState_Down) != 0;
+			gb_key_state_update(&p->keys[i], is_down);
+		}
+
+		for (i = 0; i < gbMouseButton_Count; i++) {
+			b32 is_down = (p->mouse_buttons[i] & gbKeyState_Down) != 0;
+			gb_key_state_update(&p->mouse_buttons[i], is_down);
+		}
+
+	}
+
+	{ // Handle Events
+		id distant_past = objc_msgSend_id(cast(id)objc_getClass("NSDate"), sel_registerName("distantPast"));
+		id event = ((id (*)(id, SEL, NSUInteger, id, id, BOOL))objc_msgSend)(NSApp, sel_registerName("nextEventMatchingMask:untilDate:inMode:dequeue:"), NSUIntegerMax, distant_past, NSDefaultRunLoopMode, YES);
+		gb__osx_on_cocoa_event(p, event, window);
+	}
+
+	if (p->window_has_focus) {
+		p->key_modifiers.control = p->keys[gbKey_Lcontrol] | p->keys[gbKey_Rcontrol];
+		p->key_modifiers.alt     = p->keys[gbKey_Lalt]     | p->keys[gbKey_Ralt];
+		p->key_modifiers.shift   = p->keys[gbKey_Lshift]   | p->keys[gbKey_Rshift];
+	}
+
+	{ // Check if window is closed
+		id wdg = objc_msgSend_id(window, sel_registerName("delegate"));
+		if (!wdg) {
+			p->window_is_closed = false;
+		} else {
+			NSUInteger value = 0;
+			object_getInstanceVariable(wdg, "closed", cast(void **)&value);
+			p->window_is_closed = (value != 0);
+		}
+	}
+
+
+
+	content_view = objc_msgSend_id(window, sel_registerName("contentView"));
+	original_frame = ((NSRect (*)(id, SEL))abi_objc_msgSend_stret)(content_view, sel_registerName("frame"));
+
+	{ // Window
+		NSRect frame = original_frame;
+		frame = ((NSRect (*)(id, SEL, NSRect))abi_objc_msgSend_stret)(content_view, sel_registerName("convertRectToBacking:"), frame);
+		p->window_width  = frame.size.width;
+		p->window_height = frame.size.height;
+		frame = ((NSRect (*)(id, SEL, NSRect))abi_objc_msgSend_stret)(window, sel_registerName("convertRectToScreen:"), frame);
+		p->window_x = frame.origin.x;
+		p->window_y = frame.origin.y;
+	}
+
+	{ // Mouse
+		NSRect frame = original_frame;
+		NSPoint mouse_pos = ((NSPoint (*)(id, SEL))objc_msgSend)(window, sel_registerName("mouseLocationOutsideOfEventStream"));
+		mouse_pos.x = gb_clamp(mouse_pos.x, 0, frame.size.width-1);
+		mouse_pos.y = gb_clamp(mouse_pos.y, 0, frame.size.height-1);
+
+		{
+			i32 x = mouse_pos.x;
+			i32 y = mouse_pos.y;
+			p->mouse_dx = x - p->mouse_x;
+			p->mouse_dy = y - p->mouse_y;
+			p->mouse_x = x;
+			p->mouse_y = y;
+		}
+
+		if (p->mouse_clip) {
+			b32 update = false;
+			i32 x = p->mouse_x;
+			i32 y = p->mouse_y;
+			if (p->mouse_x < 0) {
+				x = 0;
+				update = true;
+			} else if (p->mouse_y > p->window_height-1) {
+				y = p->window_height-1;
+				update = true;
+			}
+
+			if (p->mouse_y < 0) {
+				y = 0;
+				update = true;
+			} else if (p->mouse_x > p->window_width-1) {
+				x = p->window_width-1;
+				update = true;
+			}
+
+			if (update)
+				gb_platform_set_mouse_position(p, x, y);
+		}
+	}
+
+	{ // TODO(bill): Controllers
+
+	}
+
+	// TODO(bill): Is this in the correct place?
+	objc_msgSend_void(NSApp, sel_registerName("updateWindows"));
+	if (p->renderer_type == gbRenderer_Opengl) {
+		objc_msgSend_void(cast(id)p->opengl.context, sel_registerName("update"));
+		gb_platform_make_opengl_context_current(p);
+	}
+}
+
+void gb_platform_display(gbPlatform *p) {
+	// TODO(bill): Do more
+	if (p->renderer_type == gbRenderer_Opengl) {
+		gb_platform_make_opengl_context_current(p);
+		objc_msgSend_void(cast(id)p->opengl.context, sel_registerName("flushBuffer"));
+	} else if (p->renderer_type == gbRenderer_Software) {
+		// TODO(bill):
+	} else {
+		GB_PANIC("Invalid window rendering type");
+	}
+
+	{
+		f64 prev_time = p->curr_time;
+		f64 curr_time = gb_time_now();
+		p->dt_for_frame = curr_time - prev_time;
+		p->curr_time = curr_time;
+	}
+}
+
+void gb_platform_destroy(gbPlatform *p) {
+	gb_platform_make_opengl_context_current(p);
+
+	objc_msgSend_void(cast(id)p->window_handle, sel_registerName("close"));
+
+	#if defined(ARC_AVAILABLE)
+	// TODO(bill): autorelease pool
+	#else
+	objc_msgSend_void(cast(id)p->osx_autorelease_pool, sel_registerName("drain"));
+	#endif
+}
+
+void gb_platform_show_cursor(gbPlatform *p, b32 show) {
+	if (show ) {
+		// objc_msgSend_void(class_registerName("NSCursor"), sel_registerName("unhide"));
+	} else {
+		// objc_msgSend_void(class_registerName("NSCursor"), sel_registerName("hide"));
+	}
+}
+
+void gb_platform_set_mouse_position(gbPlatform *p, i32 x, i32 y) {
+	// TODO(bill):
+	CGPoint pos = {cast(CGFloat)x, cast(CGFloat)y};
+	pos.x += p->window_x;
+	pos.y += p->window_y;
+	CGWarpMouseCursorPosition(pos);
+}
+
+void gb_platform_set_controller_vibration(gbPlatform *p, isize index, f32 left_motor, f32 right_motor) {
+	// TODO(bill):
+}
+
+b32 gb_platform_has_clipboard_text(gbPlatform *p) {
+	// TODO(bill):
+	return false;
+}
+
+void gb_platform_set_clipboard_text(gbPlatform *p, char const *str) {
+	// TODO(bill):
+}
+
+char *gb_platform_get_clipboard_text(gbPlatform *p, gbAllocator a) {
+	// TODO(bill):
+	return NULL;
+}
+
+void gb_platform_set_window_position(gbPlatform *p, i32 x, i32 y) {
+	// TODO(bill):
+}
+
+void gb_platform_set_window_title(gbPlatform *p, char const *title, ...) {
+	id title_string;
+	char buf[256] = {0};
+	va_list va;
+	va_start(va, title);
+	gb_snprintf_va(buf, gb_count_of(buf), title, va);
+	va_end(va);
+
+	title_string = objc_msgSend_id_char_const(cast(id)objc_getClass("NSString"), sel_registerName("stringWithUTF8String:"), buf);
+	objc_msgSend_void_id(cast(id)p->window_handle, sel_registerName("setTitle:"), title_string);
+}
+
+void gb_platform_toggle_fullscreen(gbPlatform *p, b32 fullscreen_desktop) {
+	// TODO(bill):
+}
+
+void gb_platform_toggle_borderless(gbPlatform *p) {
+	// TODO(bill):
+}
+
+void gb_platform_make_opengl_context_current(gbPlatform *p) {
+	objc_msgSend_void(cast(id)p->opengl.context, sel_registerName("makeCurrentContext"));
+}
+
+void gb_platform_show_window(gbPlatform *p) {
+	// TODO(bill):
+}
+
+void gb_platform_hide_window(gbPlatform *p) {
+	// TODO(bill):
+}
+
+i32 gb__osx_mode_bits_per_pixel(CGDisplayModeRef mode) {
+	i32 bits_per_pixel = 0;
+	CFStringRef pixel_encoding = CGDisplayModeCopyPixelEncoding(mode);
+	if(CFStringCompare(pixel_encoding, CFSTR(IO32BitDirectPixels), kCFCompareCaseInsensitive) == kCFCompareEqualTo) {
+		bits_per_pixel = 32;
+	} else if(CFStringCompare(pixel_encoding, CFSTR(IO16BitDirectPixels), kCFCompareCaseInsensitive) == kCFCompareEqualTo) {
+		bits_per_pixel = 16;
+	} else if(CFStringCompare(pixel_encoding, CFSTR(IO8BitIndexedPixels), kCFCompareCaseInsensitive) == kCFCompareEqualTo) {
+		bits_per_pixel = 8;
+	}
+    CFRelease(pixel_encoding);
+
+	return bits_per_pixel;
+}
+
+i32 gb__osx_display_bits_per_pixel(CGDirectDisplayID display) {
+	CGDisplayModeRef mode = CGDisplayCopyDisplayMode(display);
+	i32 bits_per_pixel = gb__osx_mode_bits_per_pixel(mode);
+	CGDisplayModeRelease(mode);
+	return bits_per_pixel;
+}
+
+gbVideoMode gb_video_mode_get_desktop(void) {
+	CGDirectDisplayID display = CGMainDisplayID();
+	return gb_video_mode(CGDisplayPixelsWide(display),
+	                     CGDisplayPixelsHigh(display),
+	                     gb__osx_display_bits_per_pixel(display));
+}
+
+
+isize gb_video_mode_get_fullscreen_modes(gbVideoMode *modes, isize max_mode_count) {
+	CFArrayRef cg_modes = CGDisplayCopyAllDisplayModes(CGMainDisplayID(), NULL);
+	CFIndex i, count;
+	if (cg_modes == NULL) {
+		return 0;
+	}
+
+	count = gb_min(CFArrayGetCount(cg_modes), max_mode_count);
+	for (i = 0; i < count; i++) {
+		CGDisplayModeRef cg_mode = cast(CGDisplayModeRef)CFArrayGetValueAtIndex(cg_modes, i);
+		modes[i] = gb_video_mode(CGDisplayModeGetWidth(cg_mode),
+		                         CGDisplayModeGetHeight(cg_mode),
+		                         gb__osx_mode_bits_per_pixel(cg_mode));
+	}
+
+	CFRelease(cg_modes);
+
+	gb_sort_array(modes, count, gb_video_mode_dsc_cmp);
+	return cast(isize)count;
+}
+
+#endif
+
+
+// TODO(bill): OSX Platform Layer
+// NOTE(bill): Use this as a guide so there is no need for Obj-C https://github.com/jimon/osx_app_in_plain_c
+
+gb_inline gbVideoMode gb_video_mode(i32 width, i32 height, i32 bits_per_pixel) {
+	gbVideoMode m;
+	m.width = width;
+	m.height = height;
+	m.bits_per_pixel = bits_per_pixel;
+	return m;
+}
+
+gb_inline b32 gb_video_mode_is_valid(gbVideoMode mode) {
+	gb_local_persist gbVideoMode modes[256] = {0};
+	gb_local_persist isize mode_count = 0;
+	gb_local_persist b32 is_set = false;
+	isize i;
+
+	if (!is_set) {
+		mode_count = gb_video_mode_get_fullscreen_modes(modes, gb_count_of(modes));
+		is_set = true;
+	}
+
+	for (i = 0; i < mode_count; i++) {
+		gb_printf("%d %d\n", modes[i].width, modes[i].height);
+	}
+
+	return gb_binary_search_array(modes, mode_count, &mode, gb_video_mode_cmp) >= 0;
+}
+
+GB_COMPARE_PROC(gb_video_mode_cmp) {
+	gbVideoMode const *x = cast(gbVideoMode const *)a;
+	gbVideoMode const *y = cast(gbVideoMode const *)b;
+
+	if (x->bits_per_pixel == y->bits_per_pixel) {
+		if (x->width == y->width)
+			return x->height < y->height ? -1 : x->height > y->height;
+		return x->width < y->width ? -1 : x->width > y->width;
+	}
+	return x->bits_per_pixel < y->bits_per_pixel ? -1 : +1;
+}
+
+GB_COMPARE_PROC(gb_video_mode_dsc_cmp) {
+	return gb_video_mode_cmp(b, a);
+}
+
+#endif // defined(GB_PLATFORM)
+
+
+
+
+#if defined(GB_COMPILER_MSVC)
+#pragma warning(pop)
+#endif
+
+#if defined(__GCC__) || defined(__GNUC__)
+#pragma GCC diagnostic pop
+#endif
+
+
+#if defined(__cplusplus)
+}
+#endif
+
+#endif // GB_IMPLEMENTATION
+

+ 77 - 0
src/generator.cpp

@@ -0,0 +1,77 @@
+// #include <llvm-c/llvm>
+
+struct Generator {
+	Checker *checker;
+	String output_fullpath;
+	gbFile output;
+
+#define MAX_GENERATOR_ERROR_COUNT 10
+	isize error_prev_line;
+	isize error_prev_column;
+	isize error_count;
+};
+
+#define print_generator_error(p, token, fmt, ...) print_generator_error_(p, __FUNCTION__, token, fmt, ##__VA_ARGS__)
+void print_generator_error_(Generator *g, char *function, Token token, char *fmt, ...) {
+	va_list va;
+
+	// NOTE(bill): Duplicate error, skip it
+	if (g->error_prev_line == token.line && g->error_prev_column == token.column) {
+		goto error;
+	}
+	g->error_prev_line = token.line;
+	g->error_prev_column = token.column;
+
+#if 0
+	gb_printf_err("%s()\n", function);
+#endif
+	va_start(va, fmt);
+	gb_printf_err("%s(%td:%td) %s\n",
+	              g->checker->parser->tokenizer.fullpath, token.line, token.column,
+	              gb_bprintf_va(fmt, va));
+	va_end(va);
+
+error:
+	g->error_count++;
+	// NOTE(bill): If there are too many errors, just quit
+	if (g->error_count > MAX_GENERATOR_ERROR_COUNT) {
+		gb_exit(1);
+		return;
+	}
+}
+
+
+b32 init_generator(Generator *g, Checker *checker) {
+	if (checker->error_count > 0)
+		return false;
+	gb_zero_item(g);
+	g->checker = checker;
+
+	char *fullpath = checker->parser->tokenizer.fullpath;
+	char const *ext = gb_path_extension(fullpath);
+	isize base_len = ext-fullpath;
+	isize ext_len = gb_strlen("cpp");
+	isize len = base_len + ext_len + 1;
+	u8 *text = gb_alloc_array(gb_heap_allocator(), u8, len);
+	gb_memcopy(text, fullpath, base_len);
+	gb_memcopy(text+base_len, "cpp", ext_len);
+	g->output_fullpath = make_string(text, len);
+
+
+	return true;
+}
+
+void destroy_generator(Generator *g) {
+	if (g->error_count > 0) {
+
+	}
+
+	if (g->output_fullpath.text)
+		gb_free(gb_heap_allocator(), g->output_fullpath.text);
+}
+
+
+
+void generate_code(Generator *g, AstNode *root_node) {
+
+}

+ 41 - 0
src/main.cpp

@@ -0,0 +1,41 @@
+#include "common.cpp"
+#include "tokenizer.cpp"
+#include "parser.cpp"
+#include "printer.cpp"
+#include "checker.cpp"
+#include "generator.cpp"
+
+int main(int argc, char **argv) {
+	if (argc < 2) {
+		gb_printf_err("Please specify a .odin file\n");
+		return 1;
+	}
+
+	init_global_scope();
+
+	for (int arg_index = 1; arg_index < argc; arg_index++) {
+		char *arg = argv[arg_index];
+		char *filename = arg;
+		Parser parser = {0};
+
+		if (init_parser(&parser, filename)) {
+			defer (destroy_parser(&parser));
+			AstNode *root_node = parse_statement_list(&parser, NULL);
+			// print_ast(root_node, 0);
+
+			Checker checker = {};
+			init_checker(&checker, &parser);
+			defer (destroy_checker(&checker));
+
+			check_statement_list(&checker, root_node);
+
+			Generator generator = {};
+			if (init_generator(&generator, &checker)) {
+				defer (destroy_generator(&generator));
+				generate_code(&generator, root_node);
+			}
+		}
+	}
+
+	return 0;
+}

+ 1591 - 0
src/parser.cpp

@@ -0,0 +1,1591 @@
+struct AstNode;
+struct Type;
+struct AstScope;
+
+// NOTE(bill): Just used to quickly check if there is double declaration in the same scope
+// No type checking actually happens
+struct AstEntity {
+	Token    token;
+	AstScope *parent;
+	AstNode *declaration;
+};
+
+struct AstScope {
+	AstScope *parent;
+	Map<AstEntity> entities; // Key: Token.string
+};
+
+
+struct Parser {
+	gbArena        arena;
+	Tokenizer      tokenizer;
+	gbArray(Token) tokens;
+	Token *        cursor; // NOTE(bill): Current token, easy to peek forward and backwards if needed
+
+	AstScope *file_scope;
+	AstScope *curr_scope;
+	isize scope_level;
+
+#define MAX_PARSER_ERROR_COUNT 10
+	isize error_count;
+	isize error_prev_line;
+	isize error_prev_column;
+};
+
+enum AstNodeKind {
+	AstNode_Invalid,
+
+	AstNode_BasicLiteral,
+	AstNode_Identifier,
+
+AstNode__ExpressionBegin,
+	AstNode_BadExpression, // NOTE(bill): Naughty expression
+	AstNode_TagExpression,
+	AstNode_UnaryExpression,
+	AstNode_BinaryExpression,
+	AstNode_ParenExpression,
+	AstNode_CallExpression,
+	AstNode_SelectorExpression,
+	AstNode_IndexExpression,
+	AstNode_CastExpression,
+	AstNode_DereferenceExpression,
+AstNode__ExpressionEnd,
+
+AstNode__StatementBegin,
+	AstNode_BadStatement,
+	AstNode_EmptyStatement,
+	AstNode_ExpressionStatement,
+	AstNode_IncDecStatement,
+	AstNode_AssignStatement,
+
+AstNode__ComplexStatementBegin,
+	AstNode_BlockStatement,
+	AstNode_IfStatement,
+	AstNode_ReturnStatement,
+	AstNode_ForStatement,
+	AstNode_DeferStatement,
+AstNode__ComplexStatementEnd,
+
+AstNode__StatementEnd,
+
+AstNode__DeclarationBegin,
+	AstNode_BadDeclaration,
+	AstNode_VariableDeclaration,
+	AstNode_ProcedureDeclaration,
+	AstNode_TypeDeclaration,
+AstNode__DeclarationEnd,
+
+AstNode__TypeBegin,
+	AstNode_Field,
+	AstNode_ProcedureType,
+	AstNode_PointerType,
+	AstNode_ArrayType,
+	AstNode_StructType,
+AstNode__TypeEnd,
+
+	AstNode_Count,
+};
+
+enum DeclarationKind {
+	Declaration_Invalid,
+
+	Declaration_Mutable,
+	Declaration_Immutable,
+
+	Declaration_Count,
+};
+
+
+struct AstNode {
+	AstNodeKind kind;
+	AstNode *prev, *next; // NOTE(bill): allow for Linked list
+	Type *type;
+	union {
+		// NOTE(bill): open/close for debugging/errors
+		Token basic_literal;
+		struct {
+			Token token;
+			AstEntity *entity;
+		} identifier;
+		struct {
+			Token token;
+			Token name;
+		} tag_expression;
+
+		struct { Token begin, end; }                                bad_expression;
+		struct { Token op; AstNode *operand; }                      unary_expression;
+		struct { Token op; AstNode *left, *right; }                 binary_expression;
+		struct { AstNode *expression; Token open, close; }          paren_expression;
+		struct { Token token; AstNode *operand, *selector; }        selector_expression;
+	struct { AstNode *expression, *value; Token open, close; }  index_expression;
+		struct { Token token; AstNode *type_expression, *operand; } cast_expression;
+		struct {
+			AstNode *proc, *arg_list;
+			isize arg_list_count;
+			Token open, close;
+		} call_expression;
+		struct { Token op; AstNode *operand; } dereference_expression;
+
+		struct { Token begin, end; }              bad_statement;
+		struct { Token token; }                   empty_statement;
+		struct { AstNode *expression; }           expression_statement;
+		struct { Token op; AstNode *expression; } inc_dec_statement;
+		struct {
+			Token op;
+			AstNode *lhs_list, *rhs_list;
+			isize lhs_count, rhs_count;
+		} assign_statement;
+		struct {
+			AstNode *list;
+			isize list_count;
+			Token open, close;
+		} block_statement;
+		struct {
+			Token token;
+			AstNode *cond, *body, *else_statement;
+		} if_statement;
+		struct {
+			Token token;
+			AstNode *results; // NOTE(bill): Return values
+			isize result_count;
+		} return_statement;
+		struct {
+			Token token;
+			AstNode *init, *cond, *end;
+			AstNode *body;
+		} for_statement;
+		struct {
+			Token token;
+			AstNode *statement;
+		} defer_statement;
+
+		struct { Token begin, end; } bad_declaration;
+		struct {
+			DeclarationKind kind;
+			AstNode *name_list;
+			AstNode *type_expression;
+			AstNode *value_list;
+			isize name_list_count, value_list_count;
+		} variable_declaration;
+
+		struct {
+			AstNode *name_list;
+			isize name_list_count;
+			AstNode *type_expression;
+		} field;
+		struct {
+			Token token;
+			AstNode *param_list; // AstNode_Field
+			isize param_count;
+			AstNode *results_list; // type expression list
+			isize result_count;
+		} procedure_type;
+		struct {
+			DeclarationKind kind;
+			AstNode *name;           // AstNode_Identifier
+			AstNode *procedure_type; // AstNode_ProcedureType
+			AstNode *body;           // AstNode_BlockStatement
+			AstNode *tag;            // AstNode_TagExpression;
+		} procedure_declaration;
+		struct {
+			Token token;
+			AstNode *name;
+			AstNode *type_expression;
+		} type_declaration;
+
+
+		struct {
+			Token token;
+			AstNode *type_expression;
+		} pointer_type;
+		struct {
+			Token token;
+			AstNode *count;
+			AstNode *element;
+		} array_type;
+		struct {
+			Token token;
+			AstNode *field_list; // AstNode_Field
+			isize field_count;
+		} struct_type;
+	};
+};
+
+
+#define DLIST_SET(curr_element, next_element)  do { \
+	(curr_element)->next = (next_element);             \
+	(curr_element)->next->prev = (curr_element);       \
+	(curr_element) = (curr_element)->next;             \
+} while (0)
+
+#define DLIST_APPEND(root_element, curr_element, next_element) do { \
+	if ((root_element) == NULL) \
+		(root_element) = (curr_element) = (next_element); \
+	else \
+		DLIST_SET(curr_element, next_element); \
+} while (0)
+
+
+gb_inline AstScope *make_ast_scope(Parser *p, AstScope *parent) {
+	AstScope *scope = gb_alloc_item(gb_arena_allocator(&p->arena), AstScope);
+	map_init(&scope->entities, gb_heap_allocator());
+	scope->parent = parent;
+	return scope;
+}
+
+
+gb_inline b32 is_ast_node_expression(AstNode *node) {
+	return gb_is_between(node->kind, AstNode__ExpressionBegin+1, AstNode__ExpressionEnd-1);
+}
+gb_inline b32 is_ast_node_statement(AstNode *node) {
+	return gb_is_between(node->kind, AstNode__StatementBegin+1, AstNode__StatementEnd-1);
+}
+gb_inline b32 is_ast_node_complex_statement(AstNode *node) {
+	return gb_is_between(node->kind, AstNode__ComplexStatementBegin+1, AstNode__ComplexStatementEnd-1);
+}
+gb_inline b32 is_ast_node_declaration(AstNode *node) {
+	return gb_is_between(node->kind, AstNode__DeclarationBegin+1, AstNode__DeclarationEnd-1);
+}
+gb_inline b32 is_ast_node_type(AstNode *node) {
+	return gb_is_between(node->kind, AstNode__TypeBegin+1, AstNode__TypeEnd-1);
+}
+
+
+Token ast_node_token(AstNode *node) {
+	switch (node->kind) {
+	case AstNode_BasicLiteral:
+		return node->basic_literal;
+	case AstNode_Identifier:
+		return node->identifier.token;
+	case AstNode_BadExpression:
+		return node->bad_expression.begin;
+	case AstNode_UnaryExpression:
+		return node->unary_expression.op;
+	case AstNode_BinaryExpression:
+		return ast_node_token(node->binary_expression.left);
+	case AstNode_ParenExpression:
+		return node->paren_expression.open;
+	case AstNode_CallExpression:
+		return ast_node_token(node->call_expression.proc);
+	case AstNode_SelectorExpression:
+		return ast_node_token(node->selector_expression.selector);
+	case AstNode_IndexExpression:
+		return node->index_expression.open;
+	case AstNode_CastExpression:
+		return node->cast_expression.token;
+	case AstNode_DereferenceExpression:
+		return node->dereference_expression.op;
+	case AstNode_BadStatement:
+		return node->bad_statement.begin;
+	case AstNode_EmptyStatement:
+		return node->empty_statement.token;
+	case AstNode_ExpressionStatement:
+		return ast_node_token(node->expression_statement.expression);
+	case AstNode_IncDecStatement:
+		return node->inc_dec_statement.op;
+	case AstNode_AssignStatement:
+		return node->assign_statement.op;
+	case AstNode_BlockStatement:
+		return node->block_statement.open;
+	case AstNode_IfStatement:
+		return node->if_statement.token;
+	case AstNode_ReturnStatement:
+		return node->return_statement.token;
+	case AstNode_ForStatement:
+		return node->for_statement.token;
+	case AstNode_DeferStatement:
+		return node->defer_statement.token;
+	case AstNode_BadDeclaration:
+		return node->bad_declaration.begin;
+	case AstNode_VariableDeclaration:
+		return ast_node_token(node->variable_declaration.name_list);
+	case AstNode_ProcedureDeclaration:
+		return node->procedure_declaration.name->identifier.token;
+	case AstNode_TypeDeclaration:
+		return node->type_declaration.token;
+	case AstNode_Field:
+		return ast_node_token(node->field.name_list);
+	case AstNode_ProcedureType:
+		return node->procedure_type.token;
+	case AstNode_PointerType:
+		return node->pointer_type.token;
+	case AstNode_ArrayType:
+		return node->array_type.token;
+	case AstNode_StructType:
+		return node->struct_type.token;
+	}
+
+	Token null_token = {};
+	return null_token;
+;}
+
+gb_inline void destroy_ast_scope(AstScope *scope) {
+	// NOTE(bill): No need to free the actual pointer to the AstScope
+	// as there should be enough room in the arena
+	map_destroy(&scope->entities);
+}
+
+gb_inline AstScope *open_ast_scope(Parser *p) {
+	AstScope *scope = make_ast_scope(p, p->curr_scope);
+	p->curr_scope = scope;
+	p->scope_level++;
+	return p->curr_scope;
+}
+
+gb_inline void close_ast_scope(Parser *p) {
+	GB_ASSERT_NOT_NULL(p->curr_scope);
+	GB_ASSERT(p->scope_level > 0);
+	{
+		AstScope *parent = p->curr_scope->parent;
+		if (p->curr_scope) {
+			destroy_ast_scope(p->curr_scope);
+		}
+		p->curr_scope = parent;
+		p->scope_level--;
+	}
+}
+
+AstEntity *make_ast_entity(Parser *p, Token token, AstNode *declaration, AstScope *parent) {
+	AstEntity *entity = gb_alloc_item(gb_arena_allocator(&p->arena), AstEntity);
+	entity->token = token;
+	entity->declaration = declaration;
+	entity->parent = parent;
+	return entity;
+}
+
+u64 hash_token(Token t) {
+	return hash_string(t.string);
+}
+
+AstEntity *ast_scope_lookup(AstScope *scope, Token token) {
+	return map_get(&scope->entities, hash_token(token));
+}
+
+AstEntity *ast_scope_insert(AstScope *scope, AstEntity entity) {
+	AstEntity *prev = ast_scope_lookup(scope, entity.token);
+	if (prev == NULL) {
+		map_set(&scope->entities, hash_token(entity.token), entity);
+	}
+	return prev;
+}
+
+// NOTE(bill): And this below is why is need a new language! Discriminated unions are a pain in C/C++
+gb_inline AstNode *make_node(Parser *p, AstNodeKind kind) {
+	AstNode *node = gb_alloc_item(gb_arena_allocator(&p->arena), AstNode);
+	node->kind = kind;
+	return node;
+}
+
+gb_inline AstNode *make_bad_expression(Parser *p, Token begin, Token end) {
+	AstNode *result = make_node(p, AstNode_BadExpression);
+	result->bad_expression.begin = begin;
+	result->bad_expression.end   = end;
+	return result;
+}
+
+gb_inline AstNode *make_tag_expression(Parser *p, Token token, Token name) {
+	AstNode *result = make_node(p, AstNode_TagExpression);
+	result->tag_expression.token = token;
+	result->tag_expression.name = name;
+	return result;
+}
+
+gb_inline AstNode *make_unary_expression(Parser *p, Token op, AstNode *operand) {
+	AstNode *result = make_node(p, AstNode_UnaryExpression);
+	result->unary_expression.op = op;
+	result->unary_expression.operand = operand;
+	return result;
+}
+
+gb_inline AstNode *make_binary_expression(Parser *p, Token op, AstNode *left, AstNode *right) {
+	AstNode *result = make_node(p, AstNode_BinaryExpression);
+	result->binary_expression.op = op;
+	result->binary_expression.left = left;
+	result->binary_expression.right = right;
+	return result;
+}
+
+gb_inline AstNode *make_paren_expression(Parser *p, AstNode *expression, Token open, Token close) {
+	AstNode *result = make_node(p, AstNode_ParenExpression);
+	result->paren_expression.expression = expression;
+	result->paren_expression.open = open;
+	result->paren_expression.close = close;
+	return result;
+}
+
+gb_inline AstNode *make_call_expression(Parser *p, AstNode *proc, AstNode *arg_list, isize arg_list_count, Token open, Token close) {
+	AstNode *result = make_node(p, AstNode_CallExpression);
+	result->call_expression.proc = proc;
+	result->call_expression.arg_list = arg_list;
+	result->call_expression.arg_list_count = arg_list_count;
+	result->call_expression.open  = open;
+	result->call_expression.close = close;
+	return result;
+}
+
+gb_inline AstNode *make_selector_expression(Parser *p, Token token, AstNode *operand, AstNode *selector) {
+	AstNode *result = make_node(p, AstNode_SelectorExpression);
+	result->selector_expression.operand = operand;
+	result->selector_expression.selector = selector;
+	return result;
+}
+
+gb_inline AstNode *make_index_expression(Parser *p, AstNode *expression, AstNode *value, Token open, Token close) {
+	AstNode *result = make_node(p, AstNode_IndexExpression);
+	result->index_expression.expression = expression;
+	result->index_expression.value = value;
+	result->index_expression.open = open;
+	result->index_expression.close = close;
+	return result;
+}
+
+gb_inline AstNode *make_cast_expression(Parser *p, Token token, AstNode *type_expression, AstNode *operand) {
+	AstNode *result = make_node(p, AstNode_CastExpression);
+	result->cast_expression.token = token;
+	result->cast_expression.type_expression = type_expression;
+	result->cast_expression.operand = operand;
+	return result;
+}
+
+
+gb_inline AstNode *make_dereference_expression(Parser *p, AstNode *operand, Token op) {
+	AstNode *result = make_node(p, AstNode_DereferenceExpression);
+	result->dereference_expression.operand = operand;
+	result->dereference_expression.op = op;
+	return result;
+}
+
+
+gb_inline AstNode *make_basic_literal(Parser *p, Token basic_literal) {
+	AstNode *result = make_node(p, AstNode_BasicLiteral);
+	result->basic_literal = basic_literal;
+	return result;
+}
+
+gb_inline AstNode *make_identifier(Parser *p, Token token, AstEntity *entity = NULL) {
+	AstNode *result = make_node(p, AstNode_Identifier);
+	result->identifier.token = token;
+	result->identifier.entity   = entity;
+	return result;
+}
+
+gb_inline AstNode *make_bad_statement(Parser *p, Token begin, Token end) {
+	AstNode *result = make_node(p, AstNode_BadStatement);
+	result->bad_statement.begin = begin;
+	result->bad_statement.end   = end;
+	return result;
+}
+
+gb_inline AstNode *make_empty_statement(Parser *p, Token token) {
+	AstNode *result = make_node(p, AstNode_EmptyStatement);
+	result->empty_statement.token = token;
+	return result;
+}
+
+gb_inline AstNode *make_expression_statement(Parser *p, AstNode *expression) {
+	AstNode *result = make_node(p, AstNode_ExpressionStatement);
+	result->expression_statement.expression = expression;
+	return result;
+}
+
+gb_inline AstNode *make_inc_dec_statement(Parser *p, Token op, AstNode *expression) {
+	AstNode *result = make_node(p, AstNode_IncDecStatement);
+	result->inc_dec_statement.op = op;
+	result->inc_dec_statement.expression = expression;
+	return result;
+}
+
+gb_inline AstNode *make_assign_statement(Parser *p, Token op, AstNode *lhs_list, isize lhs_count, AstNode *rhs_list, isize rhs_count) {
+	AstNode *result = make_node(p, AstNode_AssignStatement);
+	result->assign_statement.op = op;
+	result->assign_statement.lhs_list = lhs_list;
+	result->assign_statement.lhs_count = lhs_count;
+	result->assign_statement.rhs_list = rhs_list;
+	result->assign_statement.rhs_count = rhs_count;
+	return result;
+}
+
+gb_inline AstNode *make_block_statement(Parser *p, AstNode *list, isize list_count, Token open, Token close) {
+	AstNode *result = make_node(p, AstNode_BlockStatement);
+	result->block_statement.list = list;
+	result->block_statement.list_count = list_count;
+	result->block_statement.open = open;
+	result->block_statement.close = close;
+	return result;
+}
+
+gb_inline AstNode *make_if_statement(Parser *p, Token token, AstNode *cond, AstNode *body, AstNode *else_statement) {
+	AstNode *result = make_node(p, AstNode_IfStatement);
+	result->if_statement.token = token;
+	result->if_statement.cond = cond;
+	result->if_statement.body = body;
+	result->if_statement.else_statement = else_statement;
+	return result;
+}
+
+gb_inline AstNode *make_return_statement(Parser *p, Token token, AstNode *results, isize result_count) {
+	AstNode *result = make_node(p, AstNode_ReturnStatement);
+	result->return_statement.token = token;
+	result->return_statement.results = results;
+	result->return_statement.result_count = result_count;
+	return result;
+}
+
+gb_inline AstNode *make_for_statement(Parser *p, Token token, AstNode *init, AstNode *cond, AstNode *end, AstNode *body) {
+	AstNode *result = make_node(p, AstNode_ForStatement);
+	result->for_statement.token = token;
+	result->for_statement.init = init;
+	result->for_statement.cond = cond;
+	result->for_statement.end = end;
+	result->for_statement.body = body;
+	return result;
+}
+gb_inline AstNode *make_defer_statement(Parser *p, Token token, AstNode *statement) {
+	AstNode *result = make_node(p, AstNode_DeferStatement);
+	result->defer_statement.token = token;
+	result->defer_statement.statement = statement;
+	return result;
+}
+
+gb_inline AstNode *make_bad_declaration(Parser *p, Token begin, Token end) {
+	AstNode *result = make_node(p, AstNode_BadDeclaration);
+	result->bad_declaration.begin = begin;
+	result->bad_declaration.end = end;
+	return result;
+}
+
+gb_inline AstNode *make_variable_declaration(Parser *p, DeclarationKind kind, AstNode *name_list, isize name_list_count, AstNode *type_expression, AstNode *value_list, isize value_list_count) {
+	AstNode *result = make_node(p, AstNode_VariableDeclaration);
+	result->variable_declaration.kind = kind;
+	result->variable_declaration.name_list = name_list;
+	result->variable_declaration.name_list_count = name_list_count;
+	result->variable_declaration.type_expression = type_expression;
+	result->variable_declaration.value_list = value_list;
+	result->variable_declaration.value_list_count = value_list_count;
+	return result;
+}
+
+gb_inline AstNode *make_field(Parser *p, AstNode *name_list, isize name_list_count, AstNode *type_expression) {
+	AstNode *result = make_node(p, AstNode_Field);
+	result->field.name_list = name_list;
+	result->field.name_list_count = name_list_count;
+	result->field.type_expression = type_expression;
+	return result;
+}
+
+gb_inline AstNode *make_procedure_type(Parser *p, Token token, AstNode *param_list, isize param_count, AstNode *results_list, isize result_count) {
+	AstNode *result = make_node(p, AstNode_ProcedureType);
+	result->procedure_type.token = token;
+	result->procedure_type.param_list = param_list;
+	result->procedure_type.param_count = param_count;
+	result->procedure_type.results_list = results_list;
+	result->procedure_type.result_count = result_count;
+	return result;
+}
+
+gb_inline AstNode *make_procedure_declaration(Parser *p, DeclarationKind kind, AstNode *name, AstNode *procedure_type, AstNode *body, AstNode *tag) {
+	AstNode *result = make_node(p, AstNode_ProcedureDeclaration);
+	result->procedure_declaration.kind = kind;
+	result->procedure_declaration.name = name;
+	result->procedure_declaration.procedure_type = procedure_type;
+	result->procedure_declaration.body = body;
+	result->procedure_declaration.tag = tag;
+	return result;
+}
+
+gb_inline AstNode *make_pointer_type(Parser *p, Token token, AstNode *type_expression) {
+	AstNode *result = make_node(p, AstNode_PointerType);
+	result->pointer_type.token = token;
+	result->pointer_type.type_expression = type_expression;
+	return result;
+}
+
+gb_inline AstNode *make_array_type(Parser *p, Token token, AstNode *count, AstNode *element) {
+	AstNode *result = make_node(p, AstNode_ArrayType);
+	result->array_type.token = token;
+	result->array_type.count = count;
+	result->array_type.element = element;
+	return result;
+}
+
+gb_inline AstNode *make_struct_type(Parser *p, Token token, AstNode *field_list, isize field_count) {
+	AstNode *result = make_node(p, AstNode_StructType);
+	result->struct_type.token = token;
+	result->struct_type.field_list = field_list;
+	result->struct_type.field_count = field_count;
+	return result;
+}
+
+gb_inline AstNode *make_type_declaration(Parser *p, Token token, AstNode *name, AstNode *type_expression) {
+	AstNode *result = make_node(p, AstNode_TypeDeclaration);
+	result->type_declaration.token = token;
+	result->type_declaration.name = name;
+	result->type_declaration.type_expression = type_expression;
+	return result;
+}
+
+
+#define print_parse_error(p, token, fmt, ...) print_parse_error_(p, __FUNCTION__, token, fmt, ##__VA_ARGS__)
+void print_parse_error_(Parser *p, char *function, Token token, char *fmt, ...) {
+	va_list va;
+
+	// NOTE(bill): Duplicate error, skip it
+	if (p->error_prev_line == token.line && p->error_prev_column == token.column) {
+		goto error;
+	}
+	p->error_prev_line = token.line;
+	p->error_prev_column = token.column;
+
+#if 1
+	gb_printf_err("%s()\n", function);
+#endif
+	va_start(va, fmt);
+	gb_printf_err("%s(%td:%td) %s\n",
+	              p->tokenizer.fullpath, token.line, token.column,
+	              gb_bprintf_va(fmt, va));
+	va_end(va);
+
+error:
+	p->error_count++;
+	// NOTE(bill): If there are too many errors, just quit
+	if (p->error_count > MAX_PARSER_ERROR_COUNT) {
+		gb_exit(1);
+		return;
+	}
+}
+
+
+gb_inline b32 next_token(Parser *p) {
+	if (p->cursor+1 < p->tokens + gb_array_count(p->tokens)) {
+		p->cursor++;
+		return true;
+	} else {
+		print_parse_error(p, p->cursor[0], "Token is EOF");
+		return false;
+	}
+}
+
+gb_inline Token expect_token(Parser *p, TokenKind kind) {
+	Token prev = p->cursor[0];
+	if (prev.kind != kind)
+		print_parse_error(p, p->cursor[0], "Expected `%s`, got `%s`",
+		                  token_kind_to_string(kind),
+		                  token_kind_to_string(prev.kind));
+	next_token(p);
+	return prev;
+}
+
+gb_inline Token expect_operator(Parser *p) {
+	Token prev = p->cursor[0];
+	if (!gb_is_between(prev.kind, Token__OperatorBegin+1, Token__OperatorEnd-1))
+		print_parse_error(p, p->cursor[0], "Expected an operator, got `%s`",
+		                  token_kind_to_string(prev.kind));
+	next_token(p);
+	return prev;
+}
+
+gb_inline Token expect_keyword(Parser *p) {
+	Token prev = p->cursor[0];
+	if (!gb_is_between(prev.kind, Token__KeywordBegin+1, Token__KeywordEnd-1))
+		print_parse_error(p, p->cursor[0], "Expected a keyword, got `%s`",
+		                  token_kind_to_string(prev.kind));
+	next_token(p);
+	return prev;
+}
+
+gb_inline b32 allow_token(Parser *p, TokenKind kind) {
+	Token prev = p->cursor[0];
+	if (prev.kind == kind) {
+		next_token(p);
+		return true;
+	}
+	return false;
+}
+
+
+
+b32 init_parser(Parser *p, char *filename) {
+	if (init_tokenizer(&p->tokenizer, filename)) {
+		gb_array_init(p->tokens, gb_heap_allocator());
+		for (;;) {
+			Token token = tokenizer_get_token(&p->tokenizer);
+			if (token.kind == Token_Invalid)
+				return false;
+			gb_array_append(p->tokens, token);
+
+			if (token.kind == Token_EOF)
+				break;
+		}
+
+		p->cursor = &p->tokens[0];
+
+		// NOTE(bill): Is this big enough or too small?
+		isize arena_size = gb_max(gb_size_of(AstNode), gb_size_of(AstScope));
+		arena_size *= 1.25*gb_array_count(p->tokens);
+		gb_arena_init_from_allocator(&p->arena, gb_heap_allocator(), arena_size);
+
+		open_ast_scope(p);
+		p->file_scope = p->curr_scope;
+
+		return true;
+	}
+	return false;
+}
+
+void destroy_parser(Parser *p) {
+	close_ast_scope(p);
+	gb_arena_free(&p->arena);
+	gb_array_free(p->tokens);
+	destroy_tokenizer(&p->tokenizer);
+}
+
+
+
+gb_internal void add_ast_entity(Parser *p, AstScope *scope, AstNode *declaration, AstNode *name_list) {
+	for (AstNode *n = name_list; n != NULL; n = n->next) {
+		GB_ASSERT_MSG(n->kind == AstNode_Identifier, "Identifier is already declared or resolved");
+
+		AstEntity *entity = make_ast_entity(p, n->identifier.token, declaration, scope);
+		n->identifier.entity = entity;
+
+		AstEntity *insert_entity = ast_scope_insert(scope, *entity);
+		if (insert_entity != NULL &&
+		    !are_strings_equal(insert_entity->token.string, make_string("_"))) {
+			print_parse_error(p, entity->token,
+			                  "There is already a previous declaration of `%.*s` in the current scope at (%td:%td)",
+			                  LIT(insert_entity->token.string),
+			                  insert_entity->token.line, insert_entity->token.column);
+		}
+	}
+}
+
+AstNode *parse_expression(Parser *p, b32 lhs);
+
+AstNode *parse_identifier(Parser *p) {
+	Token identifier = expect_token(p, Token_Identifier);
+	return make_identifier(p, identifier);
+}
+
+AstNode *parse_rhs(Parser *p) {
+	return parse_expression(p, false);
+}
+
+AstNode *unparen_expression(AstNode *node) {
+	for (;;) {
+		if (node->kind != AstNode_ParenExpression)
+			return node;
+		node = node->paren_expression.expression;
+	}
+}
+
+AstNode *parse_atom_expression(Parser *p, b32 lhs) {
+	AstNode *operand = NULL; // Operand
+	switch (p->cursor[0].kind) {
+	case Token_Identifier:
+		operand = parse_identifier(p);
+		if (!lhs) {
+			// TODO(bill): Handle?
+		}
+		break;
+
+	case Token_Integer:
+	case Token_Float:
+	case Token_String:
+	case Token_Rune:
+		operand = make_basic_literal(p, p->cursor[0]);
+		next_token(p);
+		break;
+
+	case Token_OpenParen: {
+		Token open, close;
+		// NOTE(bill): Skip the Paren Expression
+		open = expect_token(p, Token_OpenParen);
+		operand = parse_rhs(p);
+		close = expect_token(p, Token_CloseParen);
+		operand = make_paren_expression(p, operand, open, close);
+	} break;
+	}
+
+	b32 loop = true;
+
+	while (loop) {
+		switch (p->cursor[0].kind) {
+		case Token_OpenParen: {
+			if (lhs) {
+				// TODO(bill): Handle this shit! Is this even allowed in this language?!
+			}
+			AstNode *arg_list = NULL;
+			AstNode *arg_list_curr = NULL;
+			isize arg_list_count = 0;
+			Token open_paren, close_paren;
+
+			open_paren = expect_token(p, Token_OpenParen);
+
+			while (p->cursor[0].kind != Token_CloseParen &&
+			       p->cursor[0].kind != Token_EOF) {
+				if (p->cursor[0].kind == Token_Comma)
+					print_parse_error(p, p->cursor[0], "Expected an expression not a ,");
+
+				DLIST_APPEND(arg_list, arg_list_curr, parse_rhs(p));
+				arg_list_count++;
+
+				if (p->cursor[0].kind != Token_Comma) {
+					if (p->cursor[0].kind == Token_CloseParen)
+						break;
+				}
+
+				next_token(p);
+			}
+
+			close_paren = expect_token(p, Token_CloseParen);
+
+			operand = make_call_expression(p, operand, arg_list, arg_list_count, open_paren, close_paren);
+		} break;
+
+		case Token_Period: {
+			Token token = p->cursor[0];
+			next_token(p);
+			if (lhs) {
+				// TODO(bill): handle this
+			}
+			switch (p->cursor[0].kind) {
+			case Token_Identifier:
+				operand = make_selector_expression(p, token, operand, parse_identifier(p));
+				break;
+			default: {
+				Token token = p->cursor[0];
+				print_parse_error(p, token, "Expected a selector");
+				next_token(p);
+				operand = make_selector_expression(p, token, operand, NULL);
+			} break;
+			}
+		} break;
+
+		case Token_OpenBracket: {
+			if (lhs) {
+				// TODO(bill): Handle this
+			}
+			AstNode *value;
+			Token open, close;
+
+			open = expect_token(p, Token_OpenBracket);
+			value = parse_expression(p, false);
+			close = expect_token(p, Token_CloseBracket);
+
+			operand = make_index_expression(p, operand, value, open, close);
+		} break;
+
+		case Token_Pointer: // Deference
+			operand = make_dereference_expression(p, operand, expect_token(p, Token_Pointer));
+			break;
+
+		default:
+			loop = false;
+			break;
+		}
+
+		lhs = false; // NOTE(bill): 'tis not lhs anymore
+	}
+
+	return operand;
+}
+
+AstNode *parse_type(Parser *p);
+
+AstNode *parse_unary_expression(Parser *p, b32 lhs) {
+	switch (p->cursor[0].kind) {
+	case Token_Pointer:
+	case Token_Add:
+	case Token_Sub:
+	case Token_Not:
+	case Token_Xor: {
+		AstNode *operand;
+		Token op = p->cursor[0];
+		next_token(p);
+		operand = parse_unary_expression(p, false);
+		return make_unary_expression(p, op, operand);
+	} break;
+
+	case Token_cast: {
+		AstNode *type_expression, *operand;
+		Token token = p->cursor[0];
+		next_token(p);
+		expect_token(p, Token_OpenParen);
+		type_expression = parse_type(p);
+		expect_token(p, Token_CloseParen);
+		operand = parse_unary_expression(p, false);
+		return make_cast_expression(p, token, type_expression, operand);
+	} break;
+	}
+
+	return parse_atom_expression(p, lhs);
+}
+
+AstNode *parse_binary_expression(Parser *p, b32 lhs, i32 prec_in) {
+	AstNode *expression = parse_unary_expression(p, lhs);
+	for (i32 prec = token_precedence(p->cursor[0]); prec >= prec_in; prec--) {
+		for (;;) {
+			AstNode *right;
+			Token op = p->cursor[0];
+			i32 op_prec = token_precedence(op);
+			if (op_prec != prec)
+				break;
+			expect_operator(p); // NOTE(bill): error checks too
+			if (lhs) {
+				// TODO(bill): error checking
+				lhs = false;
+			}
+			right = parse_binary_expression(p, false, prec+1);
+			if (!right)
+				print_parse_error(p, op, "Expected expression on the right hand side of the binary operator");
+			expression = make_binary_expression(p, op, expression, right);
+		}
+	}
+	return expression;
+}
+
+AstNode *parse_expression(Parser *p, b32 lhs) {
+	return parse_binary_expression(p, lhs, 0+1);
+}
+
+
+AstNode *parse_expression_list(Parser *p, b32 lhs, isize *list_count_) {
+	AstNode *list_root = NULL;
+	AstNode *list_curr = NULL;
+	isize list_count = 0;
+
+	do {
+		DLIST_APPEND(list_root, list_curr, parse_expression(p, lhs));
+		list_count++;
+		if (p->cursor[0].kind != Token_Comma ||
+		    p->cursor[0].kind == Token_EOF)
+		    break;
+		next_token(p);
+	} while (true);
+
+	if (list_count_) *list_count_ = list_count;
+
+	return list_root;
+}
+
+AstNode *parse_lhs_expression_list(Parser *p, isize *list_count) {
+	return parse_expression_list(p, true, list_count);
+}
+
+AstNode *parse_rhs_expression_list(Parser *p, isize *list_count) {
+	return parse_expression_list(p, false, list_count);
+}
+
+AstNode *parse_declaration(Parser *p, AstNode *name_list, isize name_list_count);
+
+AstNode *parse_simple_statement(Parser *p) {
+	isize lhs_count = 0, rhs_count = 0;
+	AstNode *lhs_expression_list = parse_lhs_expression_list(p, &lhs_count);
+
+	AstNode *statement = NULL;
+	Token token = p->cursor[0];
+	switch (token.kind) {
+	case Token_Eq:
+	case Token_AddEq:
+	case Token_SubEq:
+	case Token_MulEq:
+	case Token_QuoEq:
+	case Token_ModEq:
+	case Token_AndEq:
+	case Token_OrEq:
+	case Token_XorEq:
+	case Token_AndNotEq:
+	case Token_CmpAndEq:
+	case Token_CmpOrEq:
+	{
+		if (p->curr_scope == p->file_scope) {
+			print_parse_error(p, p->cursor[0], "You cannot use a simple statement in the file scope");
+			return make_bad_statement(p, p->cursor[0], p->cursor[0]);
+		}
+		next_token(p);
+		AstNode *rhs_expression_list = parse_rhs_expression_list(p, &rhs_count);
+		if (rhs_expression_list == NULL) {
+			print_parse_error(p, token, "No right-hand side in assignment statement.");
+			return make_bad_statement(p, token, p->cursor[0]);
+		}
+		return make_assign_statement(p, token,
+		                             lhs_expression_list, lhs_count,
+		                             rhs_expression_list, rhs_count);
+	} break;
+
+	case Token_Colon: // Declare
+		return parse_declaration(p, lhs_expression_list, lhs_count);
+	}
+
+	if (lhs_count > 1) {
+		print_parse_error(p, token, "Expected 1 expression");
+		return make_bad_statement(p, token, p->cursor[0]);
+	}
+
+	token = p->cursor[0];
+	switch (token.kind) {
+	case Token_Increment:
+	case Token_Decrement:
+		if (p->curr_scope == p->file_scope) {
+			print_parse_error(p, p->cursor[0], "You cannot use a simple statement in the file scope");
+			return make_bad_statement(p, p->cursor[0], p->cursor[0]);
+		}
+		statement = make_inc_dec_statement(p, token, lhs_expression_list);
+		next_token(p);
+		return statement;
+	}
+
+	return make_expression_statement(p, lhs_expression_list);
+}
+
+AstNode *parse_statement_list(Parser *p, isize *list_count_);
+AstNode *parse_statement(Parser *p);
+AstNode *parse_body(Parser *p, AstScope *scope);
+
+AstNode *parse_block_statement(Parser *p) {
+	if (p->curr_scope == p->file_scope) {
+		print_parse_error(p, p->cursor[0], "You cannot use a block statement in the file scope");
+		return make_bad_statement(p, p->cursor[0], p->cursor[0]);
+	}
+	AstNode *block_statement;
+
+	open_ast_scope(p);
+	block_statement = parse_body(p, p->curr_scope);
+	close_ast_scope(p);
+	return block_statement;
+}
+
+AstNode *convert_statement_to_expression(Parser *p, AstNode *statement, char *kind) {
+	if (statement == NULL)
+		return NULL;
+
+	if (statement->kind == AstNode_ExpressionStatement)
+		return statement->expression_statement.expression;
+
+	print_parse_error(p, p->cursor[0], "Expected `%s`, found a simple statement.", kind);
+	return make_bad_expression(p, p->cursor[0], p->cursor[1]);
+}
+
+AstNode *parse_identfier_list(Parser *p, isize *list_count_) {
+	AstNode *list_root = NULL;
+	AstNode *list_curr = NULL;
+	isize list_count = 0;
+
+	do {
+		DLIST_APPEND(list_root, list_curr, parse_identifier(p));
+		list_count++;
+		if (p->cursor[0].kind != Token_Comma ||
+		    p->cursor[0].kind == Token_EOF)
+		    break;
+		next_token(p);
+	} while (true);
+
+	if (list_count_) *list_count_ = list_count;
+
+	return list_root;
+}
+
+
+AstNode *parse_identifier_or_type(Parser *p);
+
+AstNode *parse_type_attempt(Parser *p) {
+	AstNode *type = parse_identifier_or_type(p);
+	if (type != NULL) {
+		// TODO(bill): Handle?
+	}
+	return type;
+}
+
+AstNode *parse_type(Parser *p) {
+	AstNode *type = parse_type_attempt(p);
+	if (type == NULL) {
+		Token token = p->cursor[0];
+		print_parse_error(p, token, "Expected a type");
+		next_token(p);
+		return make_bad_expression(p, token, p->cursor[0]);
+	}
+	return type;
+}
+
+AstNode *parse_field_declaration(Parser *p, AstScope *scope) {
+	AstNode *name_list = NULL;
+	isize name_list_count = 0;
+	name_list = parse_lhs_expression_list(p, &name_list_count);
+	if (name_list_count == 0)
+		print_parse_error(p, p->cursor[0], "Empty field declaration");
+
+	expect_token(p, Token_Colon);
+
+	AstNode *type_expression = parse_type_attempt(p);
+	if (type_expression == NULL)
+		print_parse_error(p, p->cursor[0], "Expected a type for this field declaration");
+
+	AstNode *field = make_field(p, name_list, name_list_count, type_expression);
+	add_ast_entity(p, scope, field, name_list);
+	return field;
+}
+
+AstNode *parse_procedure_type(Parser *p, AstScope **scope_) {
+	Token token = expect_token(p, Token_proc);
+	AstScope *scope = make_ast_scope(p, p->file_scope); // Procedure's scope
+	AstNode *params = NULL;
+	AstNode *results = NULL;
+	isize param_count = 0;
+	isize result_count = 0;
+
+	expect_token(p, Token_OpenParen);
+	if (p->cursor[0].kind != Token_CloseParen) {
+		// IMPORTANT TODO(bill): Allow for lhs-expression list style types
+		// proc(x, y: int, a, b: f32);
+		AstNode *params_curr = NULL;
+		do {
+			AstNode *type_node = parse_type(p);
+			DLIST_APPEND(params, params_curr, type_node);
+			param_count++;
+			if (p->cursor[0].kind != Token_Comma ||
+			    p->cursor[0].kind == Token_EOF)
+			    break;
+			next_token(p);
+		} while (true);
+	}
+	expect_token(p, Token_CloseParen);
+
+	// NOTE(bill): Has results
+	if (allow_token(p, Token_ArrowRight)) {
+		if (p->cursor[0].kind != Token_Semicolon) {
+			AstNode *results_curr = NULL;
+			do {
+				DLIST_APPEND(results, results_curr, parse_type(p));
+				result_count++;
+				if (p->cursor[0].kind != Token_Comma ||
+				    p->cursor[0].kind == Token_EOF)
+				    break;
+				next_token(p);
+			} while (true);
+		} else {
+			print_parse_error(p, p->cursor[0], "Expected at least one type after the `->`");
+		}
+	}
+
+	if (scope_) *scope_ = scope;
+	return make_procedure_type(p, token, params, param_count, results, result_count);
+}
+
+
+AstNode *parse_identifier_or_type(Parser *p) {
+	switch (p->cursor[0].kind) {
+	case Token_Identifier:
+		return parse_identifier(p);
+
+	case Token_Pointer:
+		return make_pointer_type(p,  expect_token(p, Token_Pointer), parse_type(p));
+
+	case Token_OpenBracket: {
+		Token token = expect_token(p, Token_OpenBracket);
+		AstNode *count_expression = NULL;
+
+		if (p->cursor[0].kind != Token_CloseBracket)
+			count_expression = parse_rhs(p);
+		expect_token(p, Token_CloseBracket);
+		return make_array_type(p, token, count_expression, parse_type(p));
+	}
+
+	case Token_struct: {
+		Token token = expect_token(p, Token_struct);
+		Token open, close;
+		AstNode *field_list = NULL;
+		AstNode *field_list_curr = NULL;
+		isize field_list_count = 0;
+
+		open = expect_token(p, Token_OpenBrace);
+
+		AstScope *scope = make_ast_scope(p, NULL); // NOTE(bill): The struct needs its own scope with NO parent
+		while (p->cursor[0].kind == Token_Identifier ||
+		       p->cursor[0].kind == Token_Mul) {
+			DLIST_APPEND(field_list, field_list_curr, parse_field_declaration(p, scope));
+			expect_token(p, Token_Semicolon);
+			field_list_count++;
+		}
+		destroy_ast_scope(scope);
+
+		close = expect_token(p, Token_CloseBrace);
+
+		return make_struct_type(p, token, field_list, field_list_count);
+	}
+
+	case Token_proc:
+		return parse_procedure_type(p, NULL);
+
+
+	case Token_OpenParen: {
+		// NOTE(bill): Skip the paren expression
+		AstNode *type_expression;
+		Token open, close;
+		open = expect_token(p, Token_OpenParen);
+		type_expression = parse_type(p);
+		close = expect_token(p, Token_CloseParen);
+		return make_paren_expression(p, type_expression, open, close);
+	}
+
+	case Token_Colon:
+	case Token_Eq:
+		break;
+
+	default:
+		print_parse_error(p, p->cursor[0], "Expected type after type separator `:`");
+		break;
+	}
+
+	return NULL;
+}
+
+AstNode *parse_parameters(Parser *p, AstScope *scope, isize *param_count_, b32 use_parens) {
+	AstNode *param_list = NULL;
+	AstNode *param_list_curr = NULL;
+	isize param_count = 0;
+	if (use_parens) expect_token(p, Token_OpenParen);
+	while (p->cursor[0].kind != Token_CloseParen) {
+		DLIST_APPEND(param_list, param_list_curr, parse_field_declaration(p, scope));
+		param_count++;
+		if (p->cursor[0].kind != Token_Comma)
+			break;
+		next_token(p);
+	}
+	if (use_parens) expect_token(p, Token_CloseParen);
+
+	if (param_count_) *param_count_ = param_count;
+	return param_list;
+}
+
+AstNode *parse_results(Parser *p, AstScope *scope, isize *result_count_) {
+	AstNode *result_list = NULL;
+	AstNode *result_list_curr = NULL;
+	isize result_count = 0;
+
+	if (allow_token(p, Token_ArrowRight)) {
+		while (p->cursor[0].kind != Token_OpenBrace &&
+		       p->cursor[0].kind != Token_Semicolon) {
+			DLIST_APPEND(result_list, result_list_curr, parse_type(p));
+			result_count++;
+			if (p->cursor[0].kind != Token_Comma)
+				break;
+			next_token(p);
+		}
+
+		if (result_count == 0)
+			print_parse_error(p, p->cursor[0], "Expected return types after `->`");
+	}
+
+	if (result_count_) *result_count_ = result_count;
+	return result_list;
+}
+
+void parse_procedure_signature(Parser *p, AstScope *scope,
+                               AstNode **param_list, isize *param_count,
+                               AstNode **result_list, isize *result_count) {
+	*param_list  = parse_parameters(p, scope, param_count, true);
+	*result_list = parse_results(p, scope, result_count);
+}
+
+AstNode *parse_body(Parser *p, AstScope *scope) {
+	AstNode *statement_list = NULL;
+	isize statement_list_count = 0;
+	Token open, close;
+	open = expect_token(p, Token_OpenBrace);
+	statement_list = parse_statement_list(p, &statement_list_count);
+	close = expect_token(p, Token_CloseBrace);
+
+	return make_block_statement(p, statement_list, statement_list_count, open, close);
+}
+
+
+AstNode *parse_tag_expression(Parser *p) {
+	Token token = expect_token(p, Token_Hash);
+	Token name  = expect_token(p, Token_Identifier);
+	return make_tag_expression(p, token, name);
+}
+
+AstNode *parse_procedure_declaration(Parser *p, Token proc_token, AstNode *name, DeclarationKind kind) {
+	AstNode *param_list = NULL;
+	AstNode *result_list = NULL;
+	isize param_count = 0;
+	isize result_count = 0;
+
+	AstScope *scope = open_ast_scope(p);
+
+	parse_procedure_signature(p, scope, &param_list, &param_count, &result_list, &result_count);
+
+	AstNode *body = NULL, *tag = NULL;
+	if (p->cursor[0].kind == Token_OpenBrace) {
+		body = parse_body(p, scope);
+	} else if (p->cursor[0].kind == Token_Hash) {
+		tag = parse_tag_expression(p);
+	}
+
+	close_ast_scope(p);
+
+	AstNode *proc_type = make_procedure_type(p, proc_token, param_list, param_count, result_list, result_count);
+	return make_procedure_declaration(p, kind, name, proc_type, body, tag);
+}
+
+AstNode *parse_declaration(Parser *p, AstNode *name_list, isize name_list_count) {
+	AstNode *value_list = NULL;
+	AstNode *type_expression = NULL;
+	isize value_list_count = 0;
+	if (allow_token(p, Token_Colon)) {
+		type_expression = parse_identifier_or_type(p);
+	} else if (p->cursor[0].kind != Token_Eq && p->cursor[0].kind != Token_Semicolon) {
+		print_parse_error(p, p->cursor[0], "Expected type separator `:` or `=`");
+	}
+
+	DeclarationKind declaration_kind = Declaration_Mutable;
+
+	if (p->cursor[0].kind == Token_Eq ||
+	    p->cursor[0].kind == Token_Colon) {
+		if (p->cursor[0].kind == Token_Colon)
+			declaration_kind = Declaration_Immutable;
+		next_token(p);
+
+		if (p->cursor[0].kind == Token_proc) { // NOTE(bill): Procedure declarations
+			Token proc_token = p->cursor[0];
+			AstNode *name = name_list;
+			if (name_list_count != 1) {
+				print_parse_error(p, p->cursor[0], "You can only declare one procedure at a time (at the moment)");
+				return make_bad_declaration(p, name->identifier.token, p->cursor[0]);
+			}
+
+			// TODO(bill): Allow for mutable procedures
+			if (declaration_kind != Declaration_Immutable) {
+				print_parse_error(p, p->cursor[0], "Only immutable procedures are supported (at the moment)");
+				return make_bad_declaration(p, name->identifier.token, p->cursor[0]);
+			}
+			next_token(p); // Skip `proc` token
+
+			AstNode *procedure_declaration = parse_procedure_declaration(p, proc_token, name, declaration_kind);
+			add_ast_entity(p, p->curr_scope, procedure_declaration, name_list);
+			return procedure_declaration;
+
+		} else {
+			value_list = parse_rhs_expression_list(p, &value_list_count);
+			if (value_list_count > name_list_count) {
+				print_parse_error(p, p->cursor[0], "Too many values on the right hand side of the declaration");
+			} else if (value_list_count < name_list_count &&
+			           declaration_kind == Declaration_Immutable) {
+				print_parse_error(p, p->cursor[0], "All constant declarations must be defined");
+			} else if (value_list == NULL) {
+				print_parse_error(p, p->cursor[0], "Expected an expression for this declaration");
+			}
+		}
+	}
+
+	if (declaration_kind == Declaration_Mutable) {
+		if (type_expression == NULL && value_list == NULL) {
+			print_parse_error(p, p->cursor[0], "Missing variable type or initialization");
+			return make_bad_declaration(p, p->cursor[0], p->cursor[0]);
+		}
+	} else if (declaration_kind == Declaration_Immutable) {
+		if (type_expression == NULL && value_list == NULL && name_list_count > 0) {
+			print_parse_error(p, p->cursor[0], "Missing constant value");
+			return make_bad_declaration(p, p->cursor[0], p->cursor[0]);
+		}
+	} else {
+		print_parse_error(p, p->cursor[0], "Unknown type of variable declaration");
+		return make_bad_declaration(p, p->cursor[0], p->cursor[0]);
+	}
+
+	AstNode *variable_declaration = make_variable_declaration(p, declaration_kind, name_list, name_list_count, type_expression, value_list, value_list_count);
+	add_ast_entity(p, p->curr_scope, variable_declaration, name_list);
+	return variable_declaration;
+}
+
+
+AstNode *parse_if_statement(Parser *p) {
+	if (p->curr_scope == p->file_scope) {
+		print_parse_error(p, p->cursor[0], "You cannot use an if statement in the file scope");
+		return make_bad_statement(p, p->cursor[0], p->cursor[0]);
+	}
+
+	Token token = expect_token(p, Token_if);
+	AstNode *cond, *body, *else_statement;
+
+	open_ast_scope(p);
+
+	cond = convert_statement_to_expression(p, parse_simple_statement(p), "boolean expression");
+
+	if (cond == NULL) {
+		print_parse_error(p, p->cursor[0], "Expected condition for if statement");
+	}
+
+	body = parse_block_statement(p);
+	else_statement = NULL;
+	if (allow_token(p, Token_else)) {
+		switch (p->cursor[0].kind) {
+		case Token_if:
+			else_statement = parse_if_statement(p);
+			break;
+		case Token_OpenBrace:
+			else_statement = parse_block_statement(p);
+			break;
+		default:
+			print_parse_error(p, p->cursor[0], "Expected if statement block statement");
+			else_statement = make_bad_statement(p, p->cursor[0], p->cursor[1]);
+			break;
+		}
+	}
+
+	close_ast_scope(p);
+	return make_if_statement(p, token, cond, body, else_statement);
+}
+
+AstNode *parse_return_statement(Parser *p) {
+	if (p->curr_scope == p->file_scope) {
+		print_parse_error(p, p->cursor[0], "You cannot use a return statement in the file scope");
+		return make_bad_statement(p, p->cursor[0], p->cursor[0]);
+	}
+
+	Token token = expect_token(p, Token_return);
+	AstNode *result = NULL;
+	isize result_count = 0;
+	if (p->cursor[0].kind != Token_Semicolon)
+		result = parse_rhs_expression_list(p, &result_count);
+	expect_token(p, Token_Semicolon);
+
+	return make_return_statement(p, token, result, result_count);
+}
+
+AstNode *parse_for_statement(Parser *p) {
+	if (p->curr_scope == p->file_scope) {
+		print_parse_error(p, p->cursor[0], "You cannot use a for statement in the file scope");
+		return make_bad_statement(p, p->cursor[0], p->cursor[0]);
+	}
+
+	Token token = expect_token(p, Token_for);
+	open_ast_scope(p);
+	AstNode *init_statement = NULL, *cond = NULL, *end_statement = NULL, *body = NULL;
+
+	if (p->cursor[0].kind != Token_OpenBrace) {
+		cond = parse_simple_statement(p);
+		if (is_ast_node_complex_statement(cond)) {
+			print_parse_error(p, p->cursor[0],
+			                  "You are not allowed that type of statement in a for statement, it's too complex!");
+		}
+
+		if (allow_token(p, Token_Semicolon)) {
+			init_statement = cond;
+			cond = NULL;
+			if (p->cursor[0].kind != Token_Semicolon) {
+				cond = parse_simple_statement(p);
+			}
+			expect_token(p, Token_Semicolon);
+			if (p->cursor[0].kind != Token_OpenBrace) {
+				end_statement = parse_simple_statement(p);
+			}
+		}
+	}
+	body = parse_block_statement(p);
+
+	close_ast_scope(p);
+
+	return make_for_statement(p, token, init_statement, cond, end_statement, body);
+}
+
+AstNode *parse_defer_statement(Parser *p) {
+	if (p->curr_scope == p->file_scope) {
+		print_parse_error(p, p->cursor[0], "You cannot use a defer statement in the file scope");
+		return make_bad_statement(p, p->cursor[0], p->cursor[0]);
+	}
+
+	Token token = expect_token(p, Token_defer);
+	AstNode *statement = parse_statement(p);
+	switch (statement->kind) {
+	case AstNode_EmptyStatement:
+		print_parse_error(p, token, "Empty statement after defer (e.g. `;`)");
+		break;
+	case AstNode_DeferStatement:
+		print_parse_error(p, token, "You cannot defer a defer statement");
+		break;
+	case AstNode_ReturnStatement:
+		print_parse_error(p, token, "You cannot a return statement");
+		break;
+	}
+
+	return make_defer_statement(p, token, statement);
+}
+
+AstNode *parse_type_declaration(Parser *p) {
+	Token   token = expect_token(p, Token_type);
+	AstNode *name = parse_identifier(p);
+	expect_token(p, Token_Colon);
+	AstNode *type_expression = parse_type(p);
+
+	AstNode *type_declaration = make_type_declaration(p, token, name, type_expression);
+
+	if (type_expression->kind != AstNode_StructType)
+		expect_token(p, Token_Semicolon);
+
+	return type_declaration;
+}
+
+AstNode *parse_statement(Parser *p) {
+	AstNode *s = NULL;
+	Token token = p->cursor[0];
+	switch (token.kind) {
+	case Token_type:
+		return parse_type_declaration(p);
+
+	// Operands
+	case Token_Identifier:
+	case Token_Integer:
+	case Token_Float:
+	case Token_Rune:
+	case Token_String:
+	case Token_OpenParen:
+	// Unary Operators
+	case Token_Add:
+	case Token_Sub:
+	case Token_Xor:
+	case Token_Not:
+		s = parse_simple_statement(p);
+		if (s->kind != AstNode_ProcedureDeclaration && !allow_token(p, Token_Semicolon)) {
+			print_parse_error(p, p->cursor[0], "Expected `;` after statement, got `%s`", token_kind_to_string(p->cursor[0].kind));
+		}
+		return s;
+
+	// TODO(bill): other keywords
+	case Token_if:     return parse_if_statement(p);
+	case Token_return: return parse_return_statement(p);
+	case Token_for:    return parse_for_statement(p);
+	case Token_defer:  return parse_defer_statement(p);
+	// case Token_match:
+	// case Token_case:
+
+	case Token_OpenBrace: return parse_block_statement(p);
+	// case Token_CloseBrace: s = make_empty_statement(p, token); break;
+
+	case Token_Semicolon:
+		s = make_empty_statement(p, token);
+		next_token(p);
+		return s;
+	}
+
+	print_parse_error(p, token, "Expected a statement, got `%s`", token_kind_to_string(token.kind));
+	return make_bad_statement(p, token, p->cursor[0]);
+}
+
+AstNode *parse_statement_list(Parser *p, isize *list_count_) {
+	AstNode *list_root = NULL;
+	AstNode *list_curr = NULL;
+	isize list_count = 0;
+
+	while (p->cursor[0].kind != Token_case &&
+	       p->cursor[0].kind != Token_default &&
+	       p->cursor[0].kind != Token_CloseBrace &&
+	       p->cursor[0].kind != Token_EOF) {
+		DLIST_APPEND(list_root, list_curr, parse_statement(p));
+		list_count++;
+	}
+
+	if (list_count_) *list_count_ = list_count;
+
+	return list_root;
+}

+ 184 - 0
src/printer.cpp

@@ -0,0 +1,184 @@
+
+
+gb_inline void print_indent(isize indent) {
+	while (indent --> 0)
+		gb_printf("  ");
+}
+
+void print_ast(AstNode *node, isize indent) {
+	if (node == NULL)
+		return;
+
+	switch (node->kind) {
+	case AstNode_BasicLiteral:
+		print_indent(indent);
+		print_token(node->basic_literal);
+		break;
+	case AstNode_Identifier:
+		print_indent(indent);
+		print_token(node->identifier.token);
+		break;
+
+	case AstNode_TagExpression:
+		print_indent(indent);
+		gb_printf("(tag)\n");
+		print_indent(indent+1);
+		print_token(node->tag_expression.name);
+		break;
+
+	case AstNode_UnaryExpression:
+		print_indent(indent);
+		print_token(node->unary_expression.op);
+		print_ast(node->unary_expression.operand, indent+1);
+		break;
+	case AstNode_BinaryExpression:
+		print_indent(indent);
+		print_token(node->binary_expression.op);
+		print_ast(node->binary_expression.left, indent+1);
+		print_ast(node->binary_expression.right, indent+1);
+		break;
+	case AstNode_CallExpression:
+		print_indent(indent);
+		gb_printf("(call)\n");
+		print_ast(node->call_expression.proc, indent+1);
+		print_ast(node->call_expression.arg_list, indent+1);
+		break;
+	case AstNode_SelectorExpression:
+		print_indent(indent);
+		gb_printf(".\n");
+		print_ast(node->selector_expression.operand,  indent+1);
+		print_ast(node->selector_expression.selector, indent+1);
+		break;
+	case AstNode_IndexExpression:
+		print_indent(indent);
+		gb_printf("([])\n");
+		print_ast(node->index_expression.expression, indent+1);
+		print_ast(node->index_expression.value, indent+1);
+		break;
+	case AstNode_CastExpression:
+		print_indent(indent);
+		gb_printf("(cast)\n");
+		print_ast(node->cast_expression.type_expression, indent+1);
+		print_ast(node->cast_expression.operand, indent+1);
+		break;
+	case AstNode_DereferenceExpression:
+		print_indent(indent);
+		gb_printf("(dereference)\n");
+		print_ast(node->dereference_expression.operand, indent+1);
+		break;
+
+
+	case AstNode_ExpressionStatement:
+		print_ast(node->expression_statement.expression, indent);
+		break;
+	case AstNode_IncDecStatement:
+		print_indent(indent);
+		print_token(node->inc_dec_statement.op);
+		print_ast(node->inc_dec_statement.expression, indent+1);
+		break;
+	case AstNode_AssignStatement:
+		print_indent(indent);
+		print_token(node->assign_statement.op);
+		print_ast(node->assign_statement.lhs_list, indent+1);
+		print_ast(node->assign_statement.rhs_list, indent+1);
+		break;
+	case AstNode_BlockStatement:
+		print_indent(indent);
+		gb_printf("(block)\n");
+		print_ast(node->block_statement.list, indent+1);
+		break;
+
+	case AstNode_IfStatement:
+		print_indent(indent);
+		gb_printf("(if)\n");
+		print_ast(node->if_statement.cond, indent+1);
+		print_ast(node->if_statement.body, indent+1);
+		if (node->if_statement.else_statement) {
+			print_indent(indent);
+			gb_printf("(else)\n");
+			print_ast(node->if_statement.else_statement, indent+1);
+		}
+		break;
+	case AstNode_ReturnStatement:
+		print_indent(indent);
+		gb_printf("(return)\n");
+		print_ast(node->return_statement.results, indent+1);
+		break;
+	case AstNode_ForStatement:
+		print_indent(indent);
+		gb_printf("(for)\n");
+		print_ast(node->for_statement.init, indent+1);
+		print_ast(node->for_statement.cond, indent+1);
+		print_ast(node->for_statement.end, indent+1);
+		print_ast(node->for_statement.body, indent+1);
+		break;
+	case AstNode_DeferStatement:
+		print_indent(indent);
+		gb_printf("(defer)\n");
+		print_ast(node->defer_statement.statement, indent+1);
+		break;
+
+
+	case AstNode_VariableDeclaration:
+		print_indent(indent);
+		if (node->variable_declaration.kind == Declaration_Mutable)
+			gb_printf("(decl:var,mutable)\n");
+		else if (node->variable_declaration.kind == Declaration_Immutable)
+			gb_printf("(decl:var,immutable)\n");
+		print_ast(node->variable_declaration.name_list, indent+1);
+		print_ast(node->variable_declaration.type_expression, indent+1);
+		print_ast(node->variable_declaration.value_list, indent+1);
+		break;
+	case AstNode_ProcedureDeclaration:
+		print_indent(indent);
+		if (node->procedure_declaration.kind == Declaration_Mutable)
+			gb_printf("(decl:proc,mutable)\n");
+		else if (node->procedure_declaration.kind == Declaration_Immutable)
+			gb_printf("(decl:proc,immutable)\n");
+		print_ast(node->procedure_declaration.procedure_type, indent+1);
+		print_ast(node->procedure_declaration.body, indent+1);
+		print_ast(node->procedure_declaration.tag, indent+1);
+		break;
+
+	case AstNode_TypeDeclaration:
+		print_indent(indent);
+		gb_printf("(type)\n");
+		print_ast(node->type_declaration.name, indent+1);
+		print_ast(node->type_declaration.type_expression, indent+1);
+		break;
+
+	case AstNode_ProcedureType:
+		print_indent(indent);
+		gb_printf("(type:proc)(%td -> %td)\n", node->procedure_type.param_count, node->procedure_type.result_count);
+		print_ast(node->procedure_type.param_list, indent+1);
+		if (node->procedure_type.results_list) {
+			print_indent(indent+1);
+			gb_printf("->\n");
+			print_ast(node->procedure_type.results_list, indent+1);
+		}
+		break;
+	case AstNode_Field:
+		print_ast(node->field.name_list, indent);
+		print_ast(node->field.type_expression, indent);
+		break;
+	case AstNode_PointerType:
+		print_indent(indent);
+		print_token(node->pointer_type.token);
+		print_ast(node->pointer_type.type_expression, indent+1);
+		break;
+	case AstNode_ArrayType:
+		print_indent(indent);
+		gb_printf("[]\n");
+		print_ast(node->array_type.count, indent+1);
+		print_ast(node->array_type.element, indent+1);
+		break;
+	case AstNode_StructType:
+		print_indent(indent);
+		gb_printf("(struct)\n");
+		print_ast(node->struct_type.field_list, indent+1);
+		break;
+	}
+
+	if (node->next)
+		print_ast(node->next, indent);
+}

+ 9 - 0
src/test.odin

@@ -0,0 +1,9 @@
+type float: f32;
+
+main :: proc() {
+	thing :: proc(n: int) -> int, f32 {
+		return n*n, 13.37;
+	}
+
+	_, _ := 1, 2;
+}

+ 760 - 0
src/tokenizer.cpp

@@ -0,0 +1,760 @@
+// TODO(bill): Unicode support
+b32 rune_is_letter(Rune r) {
+	if (r < 0x80 && gb_char_is_alpha(cast(char)r) || r == '_') {
+		return true;
+	}
+	return false;
+}
+
+b32 rune_is_digit(Rune r) {
+	if (r < 0x80 && gb_is_between(r, '0', '9'))
+		return true;
+	return false;
+}
+
+b32 rune_is_whitespace(Rune r) {
+	switch (r) {
+	case ' ':
+	case '\t':
+	case '\n':
+	case '\r':
+	case '\f':
+	case '\v':
+		return true;
+	}
+	return false;
+}
+
+typedef enum TokenKind TokenKind;
+enum TokenKind {
+	Token_Invalid,
+	Token_EOF,
+
+Token__LiteralBegin,
+	Token_Identifier,
+	Token_Integer,
+	Token_Float,
+	Token_Rune,
+	Token_String,
+Token__LiteralEnd,
+
+Token__OperatorBegin,
+	Token_Eq, // =
+
+	Token_Not,     // ! (Unary Boolean)
+	Token_Hash,    // #
+	Token_At,      // @ // TODO(bill): Remove
+	Token_Pointer, // ^
+
+	Token_Add, // +
+	Token_Sub, // -
+	Token_Mul, // *
+	Token_Quo, // /
+	Token_Mod, // %
+
+	Token_AddEq, // +=
+	Token_SubEq, // -=
+	Token_MulEq, // *=
+	Token_QuoEq, // /=
+	Token_ModEq, // %=
+
+	Token_And,        // &
+	Token_Or,         // |
+	Token_Xor,        // ~
+	Token_AndNot,     // &~
+
+	Token_AndEq,    // &=
+	Token_OrEq,     // |=
+	Token_XorEq,    // ~=
+	Token_AndNotEq, // &~=
+
+	Token_Increment,  // ++
+	Token_Decrement,  // --
+	Token_ArrowRight, // ->
+	Token_ArrowLeft,  // <-
+
+	Token_CmpAnd,   // &&
+	Token_CmpOr,    // ||
+Token__ComparisonBegin,
+	Token_CmpEq,    // ==
+	Token_Lt,       // <
+	Token_Gt,       // >
+	Token_NotEq,    // !=
+	Token_LtEq,     // <=
+	Token_GtEq,     // >=
+Token__ComparisonEnd,
+	Token_CmpAndEq, // &&=
+	Token_CmpOrEq,  // ||=
+
+	Token_OpenParen,    // (
+	Token_CloseParen,   // )
+	Token_OpenBracket,  // [
+	Token_CloseBracket, // ]
+	Token_OpenBrace,    // {
+	Token_CloseBrace,   // }
+
+	Token_Colon,      // :
+	Token_Semicolon,  // ;
+	Token_Period,     // .
+	Token_Comma,      // ,
+	Token_Ellipsis,   // ...
+Token__OperatorEnd,
+
+Token__KeywordBegin,
+	Token_type,
+	Token_proc,
+
+	Token_match, // TODO(bill): switch vs match?
+	Token_break,
+	Token_continue,
+	Token_fallthrough,
+	Token_case,
+	Token_default,
+
+	Token_if,
+	Token_else,
+	Token_for,
+	Token_defer,
+	Token_return,
+	Token_import,
+	Token_cast,
+
+	Token_struct,
+	Token_union,
+	Token_enum,
+
+	Token_inline,
+	Token_no_inline,
+Token__KeywordEnd,
+
+	Token_Count,
+};
+
+char const *TOKEN_STRINGS[] = {
+	"Invalid",
+	"EOF",
+"_LiteralBegin",
+	"Identifier",
+	"Integer",
+	"Float",
+	"Rune",
+	"String",
+"_LiteralEnd",
+"_OperatorBegin",
+	"=",
+	"!",
+	"#",
+	"@",
+	"^",
+	"+",
+	"-",
+	"*",
+	"/",
+	"%",
+	"+=",
+	"-=",
+	"*=",
+	"/=",
+	"%=",
+	"&",
+	"|",
+	"~",
+	"&~",
+	"&=",
+	"|=",
+	"~=",
+	"&~=",
+	"++",
+	"--",
+	"->",
+	"<-",
+	"&&",
+	"||",
+"_ComparisonBegin",
+	"==",
+	"<",
+	">",
+	"!=",
+	"<=",
+	">=",
+"_ComparisonEnd",
+	"&&=",
+	"||=",
+	"(",
+	")",
+	"[",
+	"]",
+	"{",
+	"}",
+	":",
+	";",
+	".",
+	",",
+	"...",
+"_OperatorEnd",
+"_KeywordBegin",
+	"type",
+	"proc",
+	"switch",
+	"break",
+	"continue",
+	"fallthrough",
+	"case",
+	"default",
+	"if",
+	"else",
+	"for",
+	"defer",
+	"return",
+	"import",
+	"cast",
+	"struct",
+	"union",
+	"enum",
+	"inline",
+	"no_inline",
+	"import",
+"_KeywordEnd",
+};
+
+
+// NOTE(bill): Text is UTF-8, thus why u8 and not char
+typedef struct Token Token;
+struct Token {
+	TokenKind kind;
+	String string;
+	isize line, column;
+};
+
+
+
+char const *token_kind_to_string(TokenKind kind) {
+	return TOKEN_STRINGS[kind];
+}
+
+i32 token_precedence(Token t) {
+	switch (t.kind) {
+	case Token_CmpOr:  return 1;
+	case Token_CmpAnd: return 2;
+
+	case Token_CmpEq:
+	case Token_NotEq:
+	case Token_Lt:
+	case Token_Gt:
+	case Token_LtEq:
+	case Token_GtEq:
+		return 3;
+
+	case Token_Add:
+	case Token_Sub:
+	case Token_Or:
+	case Token_Xor:
+		return 4;
+
+	case Token_Mul:
+	case Token_Quo:
+	case Token_Mod:
+	case Token_And:
+	case Token_AndNot:
+		return 5;
+	}
+
+	return 0;
+}
+
+
+gb_inline b32 token_is_literal(Token t) {
+	return gb_is_between(t.kind, Token__LiteralBegin+1, Token__LiteralEnd-1);
+}
+gb_inline b32 token_is_operator(Token t) {
+	return gb_is_between(t.kind, Token__OperatorBegin+1, Token__OperatorEnd-1);
+}
+gb_inline b32 token_is_keyword(Token t) {
+	return gb_is_between(t.kind, Token__KeywordBegin+1, Token__KeywordEnd-1);
+}
+gb_inline b32 token_is_comparison(Token t) {
+	return gb_is_between(t.kind, Token__ComparisonBegin+1, Token__ComparisonEnd-1);
+}
+
+gb_inline void print_token(Token t) { gb_printf("%.*s\n", LIT(t.string)); }
+
+typedef struct Tokenizer Tokenizer;
+struct Tokenizer {
+	char *fullpath;
+	u8 *start;
+	u8 *end;
+
+	Rune  curr_rune;   // current character
+	u8 *  curr;        // character pos
+	u8 *  read_curr;   // pos from start
+	u8 *  line;        // current line pos
+	isize line_count;
+};
+
+
+#define tokenizer_error(t, msg, ...) tokenizer_error_(t, __FUNCTION__, msg, ##__VA_ARGS__)
+void tokenizer_error_(Tokenizer *t, char *function, char *msg, ...) {
+	va_list va;
+	isize column = t->read_curr - t->line+1;
+	if (column < 1)
+		column = 1;
+
+	gb_printf_err("%s()\n", function);
+	gb_printf_err("%s(%td:%td) ", t->fullpath, t->line_count, column);
+
+	va_start(va, msg);
+	gb_printf_err_va(msg, va);
+	va_end(va);
+
+	gb_printf_err("\n");
+
+	gb_exit(1);
+}
+
+void advance_to_next_rune(Tokenizer *t) {
+	if (t->read_curr < t->end) {
+		Rune rune;
+		isize width = 1;
+
+		t->curr = t->read_curr;
+		if (t->curr_rune == '\n') {
+			t->line = t->curr;
+			t->line_count++;
+		}
+		rune = *t->read_curr;
+		if (rune == 0) {
+			tokenizer_error(t, "Illegal character NUL");
+		} else if (rune >= 0x80) { // not ASCII
+			width = gb_utf8_decode(t->read_curr, t->end-t->read_curr, &rune);
+			if (rune == GB_RUNE_INVALID && width == 1)
+				tokenizer_error(t, "Illegal UTF-8 encoding");
+			else if (rune == GB_RUNE_BOM && t->curr-t->start > 0)
+				tokenizer_error(t, "Illegal byte order mark");
+		}
+		t->read_curr += width;
+		t->curr_rune = rune;
+	} else {
+		t->curr = t->end;
+		if (t->curr_rune == '\n') {
+			t->line = t->curr;
+			t->line_count++;
+		}
+		t->curr_rune = GB_RUNE_EOF;
+	}
+}
+
+b32 init_tokenizer(Tokenizer *t, char *filename) {
+	gbFileContents fc = gb_file_read_contents(gb_heap_allocator(), true, filename);
+	gb_zero_item(t);
+	if (fc.data) {
+		t->start = cast(u8 *)fc.data;
+		t->line = t->read_curr = t->curr = t->start;
+		t->end = t->start + fc.size;
+
+		t->fullpath = gb_path_get_full_name(gb_heap_allocator(), filename);
+
+		t->line_count = 1;
+
+		advance_to_next_rune(t);
+		if (t->curr_rune == GB_RUNE_BOM)
+			advance_to_next_rune(t); // Ignore BOM at file beginning
+		return true;
+	}
+	return false;
+}
+
+gb_inline void destroy_tokenizer(Tokenizer *t) {
+	gb_free(gb_heap_allocator(), t->start);
+}
+
+void tokenizer_skip_whitespace(Tokenizer *t) {
+	for (;;) {
+		if (rune_is_whitespace(t->curr_rune)) {
+			advance_to_next_rune(t);
+		} else if (t->curr_rune == '/') {
+			if (t->read_curr[0] == '/') { // Line comment //
+				while (t->curr_rune != '\n')
+					advance_to_next_rune(t);
+			} else if (t->read_curr[0] == '*') { // (Nested) Block comment /**/
+				isize comment_scope = 1;
+				for (;;) {
+					advance_to_next_rune(t);
+					if (t->curr_rune == '/') {
+						advance_to_next_rune(t);
+						if (t->curr_rune == '*') {
+							advance_to_next_rune(t);
+							comment_scope++;
+						}
+					}
+					if (t->curr_rune == '*') {
+						advance_to_next_rune(t);
+						if (t->curr_rune == '/') {
+							advance_to_next_rune(t);
+							comment_scope--;
+						}
+					}
+					if (comment_scope == 0)
+						break;
+				}
+			} else {
+				break;
+			}
+		} else {
+			break;
+		}
+	}
+}
+
+gb_inline i32 digit_value(Rune r) {
+	if (gb_char_is_digit(cast(char)r))
+		return r - '0';
+	if (gb_is_between(cast(char)r, 'a', 'f'))
+		return r - 'a' + 10;
+	if (gb_is_between(cast(char)r, 'A', 'F'))
+		return r - 'A' + 10;
+	return 16; // NOTE(bill): Larger than highest possible
+}
+
+gb_inline void scan_mantissa(Tokenizer *t, i32 base) {
+	// TODO(bill): Allow for underscores in numbers as a number separator
+	// TODO(bill): Is this a good idea?
+	// while (digit_value(t->curr_rune) < base || t->curr_rune == '_')
+	while (digit_value(t->curr_rune) < base)
+		advance_to_next_rune(t);
+}
+
+
+Token scan_number_to_token(Tokenizer *t, b32 seen_decimal_point) {
+	Token token = {};
+	u8 *start_curr = t->curr;
+	token.kind = Token_Integer;
+	token.string = make_string(start_curr, 1);
+	token.line = t->line_count;
+	token.column = t->curr-t->line+1;
+
+	if (seen_decimal_point) {
+		start_curr--;
+		token.kind = Token_Float;
+		scan_mantissa(t, 10);
+		goto exponent;
+	}
+
+	if (t->curr_rune == '0') {
+		u8 *prev = t->curr;
+		advance_to_next_rune(t);
+		if (t->curr_rune == 'b') { // Binary
+			advance_to_next_rune(t);
+			scan_mantissa(t, 2);
+			if (t->curr - prev <= 2)
+				token.kind = Token_Invalid;
+		} else if (t->curr_rune == 'o') { // Octal
+			advance_to_next_rune(t);
+			scan_mantissa(t, 8);
+			if (t->curr - prev <= 2)
+				token.kind = Token_Invalid;
+		} else if (t->curr_rune == 'd') { // Decimal
+			advance_to_next_rune(t);
+			scan_mantissa(t, 10);
+			if (t->curr - prev <= 2)
+				token.kind = Token_Invalid;
+		} else if (t->curr_rune == 'x') { // Hexadecimal
+			advance_to_next_rune(t);
+			scan_mantissa(t, 16);
+			if (t->curr - prev <= 2)
+				token.kind = Token_Invalid;
+		} else {
+			seen_decimal_point = false;
+			scan_mantissa(t, 10);
+
+			if (t->curr_rune == '.' || t->curr_rune == 'e' || t->curr_rune == 'E') {
+				seen_decimal_point = true;
+				goto fraction;
+			}
+		}
+		goto end;
+	}
+
+	scan_mantissa(t, 10);
+
+fraction:
+	if (t->curr_rune == '.') {
+		token.kind = Token_Float;
+		advance_to_next_rune(t);
+		scan_mantissa(t, 10);
+	}
+
+exponent:
+	if (t->curr_rune == 'e' || t->curr_rune == 'E') {
+		token.kind = Token_Float;
+		advance_to_next_rune(t);
+		if (t->curr_rune == '-' || t->curr_rune == '+')
+			advance_to_next_rune(t);
+		scan_mantissa(t, 10);
+	}
+
+end:
+	token.string.len = t->curr - token.string.text;
+	return token;
+}
+
+// Quote == " for string and ' for char
+b32 scan_escape(Tokenizer *t, Rune quote) {
+	isize len = 0;
+	u32 base = 0, max = 0, x = 0;
+
+	Rune r = t->curr_rune;
+	if (r == 'a'  ||
+	    r == 'b'  ||
+	    r == 'f'  ||
+	    r == 'n'  ||
+	    r == 'r'  ||
+	    r == 't'  ||
+	    r == 'v'  ||
+	    r == '\\' ||
+	    r == quote) {
+		advance_to_next_rune(t);
+		return true;
+	} else if (gb_is_between(r, '0', '7')) {
+		len = 3; base = 8; max = 255;
+	} else if (r == 'x') {
+		advance_to_next_rune(t);
+		len = 2; base = 16; max = 255;
+	} else if (r == 'u') {
+		advance_to_next_rune(t);
+		len = 4; base = 16; max = GB_RUNE_MAX;
+	} else if (r == 'U') {
+		advance_to_next_rune(t);
+		len = 8; base = 16; max = GB_RUNE_MAX;
+	} else {
+		if (t->curr_rune < 0)
+			tokenizer_error(t, "Escape sequence was not terminated");
+		else
+			tokenizer_error(t, "Unknown escape sequence");
+		return false;
+	}
+
+	while (len --> 0) {
+		u32 d = cast(u32)digit_value(t->curr_rune);
+		if (d >= base) {
+			if (t->curr_rune < 0)
+				tokenizer_error(t, "Escape sequence was not terminated");
+			else
+				tokenizer_error(t, "Illegal character %d in escape sequence", t->curr_rune);
+			return false;
+		}
+
+		x = x*base + d;
+		advance_to_next_rune(t);
+	}
+
+	return true;
+}
+
+gb_inline TokenKind token_type_variant2(Tokenizer *t, TokenKind a, TokenKind b) {
+	if (t->curr_rune == '=') {
+		advance_to_next_rune(t);
+		return b;
+	}
+	return a;
+}
+
+
+gb_inline TokenKind token_type_variant3(Tokenizer *t, TokenKind a, TokenKind b, Rune ch_c, TokenKind c) {
+	if (t->curr_rune == '=') {
+		advance_to_next_rune(t);
+		return b;
+	}
+	if (t->curr_rune == ch_c) {
+		advance_to_next_rune(t);
+		return c;
+	}
+	return a;
+}
+
+gb_inline TokenKind token_type_variant4(Tokenizer *t, TokenKind a, TokenKind b, Rune ch_c, TokenKind c, Rune ch_d, TokenKind d) {
+	if (t->curr_rune == '=') {
+		advance_to_next_rune(t);
+		return b;
+	} else if (t->curr_rune == ch_c) {
+		advance_to_next_rune(t);
+		return c;
+	} else if (t->curr_rune == ch_d) {
+		advance_to_next_rune(t);
+		return d;
+	}
+	return a;
+}
+
+Token tokenizer_get_token(Tokenizer *t) {
+	Token token = {};
+	Rune curr_rune;
+
+	tokenizer_skip_whitespace(t);
+	token.string = make_string(t->curr, 1);
+	token.line = t->line_count;
+	token.column = t->curr - t->line + 1;
+
+	curr_rune = t->curr_rune;
+	if (rune_is_letter(curr_rune)) {
+		token.kind = Token_Identifier;
+		while (rune_is_letter(t->curr_rune) || rune_is_digit(t->curr_rune))
+			advance_to_next_rune(t);
+
+		token.string.len = t->curr - token.string.text;
+
+		// NOTE(bill): ALL identifiers are > 1
+		if (token.string.len > 1) {
+		#define KWB if (0) {}
+		#define KWT(keyword, token_type) else if ((gb_size_of(keyword)-1) == token.string.len && gb_strncmp((char *)token.string.text, keyword, token.string.len) == 0) token.kind = token_type
+		#define KWE else {}
+
+			KWB
+			KWT("type",        Token_type);
+			KWT("proc",        Token_proc);
+			KWT("match",       Token_match);
+			KWT("break",       Token_break);
+			KWT("continue",    Token_continue);
+			KWT("fallthrough", Token_fallthrough);
+			KWT("case",        Token_case);
+			KWT("default",     Token_default);
+			KWT("if",          Token_if);
+			KWT("else",        Token_else);
+			KWT("for",         Token_for);
+			KWT("defer",       Token_defer);
+			KWT("return",      Token_return);
+			KWT("import",      Token_import);
+			KWT("cast",        Token_cast);
+			KWT("struct",      Token_struct);
+			KWT("union",       Token_union);
+			KWT("enum",        Token_enum);
+			KWT("inline",      Token_inline);
+			KWT("no_inline",   Token_no_inline);
+			KWE
+
+		#undef KWB
+		#undef KWT
+		#undef KWE
+		}
+
+	} else if (gb_is_between(curr_rune, '0', '9')) {
+		token = scan_number_to_token(t, false);
+	} else {
+		advance_to_next_rune(t);
+		switch (curr_rune) {
+		case GB_RUNE_EOF:
+			token.kind = Token_EOF;
+			break;
+		case '"': // String Literal
+			token.kind = Token_String;
+			for (;;) {
+				Rune r = t->curr_rune;
+				if (r == '\n' || r < 0) {
+					tokenizer_error(t, "String literal not terminated");
+					break;
+				}
+				advance_to_next_rune(t);
+				if (r == '"')
+					break;
+				if (r == '\\')
+					scan_escape(t, '"');
+			}
+			break;
+
+		case '\'': { // Rune Literal
+			b32 valid = true;
+			isize len = 0;
+			token.kind = Token_Rune;
+			for (;;) {
+				Rune r = t->curr_rune;
+				if (r == '\n' || r < 0) {
+					if (valid)
+						tokenizer_error(t, "Rune literal not terminated");
+					break;
+				}
+				advance_to_next_rune(t);
+				if (r == '\'')
+					break;
+				len++;
+				if (r == '\\') {
+					if (!scan_escape(t, '\''))
+						valid = false;
+				}
+			}
+
+			if (valid && len != 1)
+				tokenizer_error(t, "Illegal rune literal");
+		} break;
+
+		case '.':
+			token.kind = Token_Period; // Default
+			if (gb_is_between(t->curr_rune, '0', '9')) { // Might be a number
+				token = scan_number_to_token(t, true);
+			} else if (t->curr_rune == '.') { // Could be an ellipsis
+				advance_to_next_rune(t);
+				if (t->curr_rune == '.') {
+					advance_to_next_rune(t);
+					token.kind = Token_Ellipsis;
+				}
+			}
+			break;
+
+		case '#': token.kind = Token_Hash;         break;
+		case '@': token.kind = Token_At;           break;
+		case '^': token.kind = Token_Pointer;      break;
+		case ';': token.kind = Token_Semicolon;    break;
+		case ',': token.kind = Token_Comma;        break;
+		case '(': token.kind = Token_OpenParen;    break;
+		case ')': token.kind = Token_CloseParen;   break;
+		case '[': token.kind = Token_OpenBracket;  break;
+		case ']': token.kind = Token_CloseBracket; break;
+		case '{': token.kind = Token_OpenBrace;    break;
+		case '}': token.kind = Token_CloseBrace;   break;
+		case ':': token.kind = Token_Colon;        break;
+
+		case '*': token.kind = token_type_variant2(t, Token_Mul,   Token_MulEq);     break;
+		case '/': token.kind = token_type_variant2(t, Token_Quo,   Token_QuoEq);     break;
+		case '%': token.kind = token_type_variant2(t, Token_Mod,   Token_ModEq);     break;
+		case '=': token.kind = token_type_variant2(t, Token_Eq,    Token_CmpEq);     break;
+		case '~': token.kind = token_type_variant2(t, Token_Xor,   Token_XorEq);     break;
+		case '!': token.kind = token_type_variant2(t, Token_Not,   Token_NotEq);     break;
+		case '>': token.kind = token_type_variant2(t, Token_Gt,    Token_GtEq);      break;
+		case '<': token.kind = token_type_variant3(t, Token_Lt,    Token_LtEq,  '-', Token_ArrowLeft); break;
+		case '+': token.kind = token_type_variant3(t, Token_Add,   Token_AddEq, '+', Token_Increment); break;
+		case '-': token.kind = token_type_variant4(t, Token_Sub,   Token_SubEq, '-', Token_Decrement, '>', Token_ArrowRight); break;
+
+		case '&':
+			token.kind = Token_And;
+			if (t->curr_rune == '~') {
+				advance_to_next_rune(t);
+				token.kind = token_type_variant2(t, Token_AndNot, Token_AndNotEq);
+			} else {
+				advance_to_next_rune(t);
+				token.kind = token_type_variant3(t, Token_And, Token_AndEq, '&', Token_CmpAnd);
+				if (t->curr_rune == '=') {
+					token.kind = Token_CmpAndEq;
+					advance_to_next_rune(t);
+				}
+			}
+			break;
+
+		case '|':
+			token.kind = Token_Or;
+			advance_to_next_rune(t);
+			token.kind = token_type_variant3(t, Token_Or, Token_OrEq, '|', Token_CmpOr);
+			if (t->curr_rune == '=')  {
+				token.kind = Token_CmpOrEq;
+				advance_to_next_rune(t);
+			}
+			break;
+
+		default:
+			if (curr_rune != GB_RUNE_BOM)
+				tokenizer_error(t, "Illegal character: %c (%d) ", cast(char)curr_rune, curr_rune);
+			token.kind = Token_Invalid;
+			break;
+		}
+	}
+
+	token.string.len = t->curr - token.string.text;
+	return token;
+}

+ 45 - 0
syntax.odin

@@ -0,0 +1,45 @@
+main :: proc(args: []string) -> i32 {
+	if args.count < 2 {
+		io.println("Please specify a .odin file");
+		return 1;
+	}
+
+	for arg_index := 1; arg_index < args.count; arg_index++ {
+		arg := args[arg_index];
+		filename := arg;
+		ext := filepath.path_extension(filename);
+		if (ext != "odin") {
+			io.println("File is not a .odin file");
+			return 1;
+		}
+		output_name := filepath.change_extension(filename, "c");
+
+		parser: Parser;
+		err: Error;
+		parser, err = make_parser(filename);
+		if err {
+			handle_error();
+		}
+		defer destroy_parser(*parser);
+
+		root_node := parse_statement_list(*parser, null);
+
+		code_generator: CodeGenerator;
+		code_generator, err = make_code_generator(*parser, root);
+		if err {
+			handle_error();
+		}
+		defer destroy_code_generator(*code_generator);
+
+		output: File;
+		output, err = file_create(output_nameu);
+		if err {
+			handle_error();
+		}
+		defer file_close(*output);
+
+		convert_to_c_code(*code_generator, root, *output);
+	}
+
+	return 0;
+};

+ 100 - 0
todo.md

@@ -0,0 +1,100 @@
+# Odin Language Features
+
+* variables
+* constants (compile-time)
+* procedures
+	- overloading
+	- polymorphic (poly prockets)
+	- multiple return values
+		- Optional forced checking
+	- inline and outline actually meant it!
+	- local scoped procedures
+		* Maybe closures & lambdas?
+	- named parameters
+	- optional parameters
+* struct
+* enum
+* raw union
+* tagged union or variants or both?
+* pointers
+* pointer arithmetic
+* defer statement
+* death to headers
+	- no pre-declaration
+* maybe both inline assembly and intrinsics
+* `using`
+* metaprogramming
+	- Compile execution
+	- Introspection
+	- Any type
+		* type_of?
+
+
+## Basic Types
+bool - true|false
+	 - register size or variable size?
+
+u8   - Unsigned integer
+u16
+u32
+u64
+uint - Register size unsigned integer
+uintptr - integer big enough to store a pointer
+
+i8  - Signed integer
+i16
+i32
+i64
+int - Register size signed integer
+intptr - integer big enough to store a pointer
+
+
+f32 - Floating Point 32 bit
+f64 - Floating Point 64 bit
+
+byte - alias for u8
+rune - alias for i32
+
+string   - Immutable: once created, it is impossible to change the contents of a string
+arrays   -    ArrayType = [count]ElementType
+                  count = Expression
+            ElementType = Type
+	Allow for dynamic arrays? Allow use slices?
+pointers - PointerType = *BaseType
+              BaseType = Type
+
+struct
+enum
+raw_union
+tagged_union
+procedure
+
+
+
+
+
+
+## Basic program
+
+import "io"
+
+type Vec2: struct {
+	x, y: f32;
+}
+
+test_proc :: (x, y: f32) -> f32 {
+	result := x * y;
+	result += 2;
+	result /= x;
+	return result;
+}
+
+main :: () {
+	x : i32 = 123;
+	y := 1337;  // Type inference (will be `int`)
+	z :: x + y; // Defined and made constant
+
+	io.print("Hellope, World! z = {}", z);
+}
+
+