diff --git a/build.bat b/build.bat index 015aa6026..c5e38d20c 100644 --- a/build.bat +++ b/build.bat @@ -21,23 +21,36 @@ set compiler_warnings= ^ -wd4201 -wd4204 -wd4244 ^ -wd4306 ^ -wd4480 ^ - -wd4505 -wd4512 -wd4550 ^ + -wd4505 -wd4512 -wd4550 -set compiler_includes= ^ - - rem -I"C:\Program Files\LLVM\include" +set compiler_includes=-I"C:\Program Files\LLVM\include" set libs= kernel32.lib user32.lib gdi32.lib opengl32.lib ^ + -libpath:"C:\Program Files\LLVM\lib" - rem -libpath:"C:\Program Files\LLVM\lib" ^ + rem LLVMX86Disassembler.lib ^ + rem LLVMX86AsmParser.lib ^ + rem LLVMX86CodeGen.lib ^ + rem LLVMSelectionDAG.lib ^ + rem LLVMAsmPrinter.lib ^ rem LLVMCodeGen.lib ^ rem LLVMTarget.lib ^ + rem LLVMScalarOpts.lib ^ + rem LLVMInstCombine.lib ^ + rem LLVMInstrumentation.lib ^ + rem LLVMProfileData.lib ^ + rem LLVMTransformUtils.lib ^ rem LLVMBitWriter.lib ^ rem LLVMAnalysis.lib ^ + rem LLVMX86Desc.lib ^ rem LLVMObject.lib ^ rem LLVMMCParser.lib ^ rem LLVMBitReader.lib ^ + rem LLVMMCDisassembler.lib ^ + rem LLVMX86Info.lib ^ + rem LLVMX86AsmPrinter.lib ^ rem LLVMMC.lib ^ + rem LLVMX86Utils.lib ^ rem LLVMCore.lib ^ rem LLVMSupport.lib @@ -48,9 +61,10 @@ 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 compiler_settings=%compiler_includes% %compiler_flags% %compiler_warnings% set linker_settings=%libs% %linker_flags% + set build_dir= "%base_dir%\bin\" if not exist %build_dir% mkdir %build_dir% pushd %build_dir% diff --git a/src/checker.cpp b/src/checker.cpp deleted file mode 100644 index 97d1d9f87..000000000 --- a/src/checker.cpp +++ /dev/null @@ -1,6 +0,0 @@ -#include "checker/value.cpp" -#include "checker/entity.cpp" -#include "checker/type.cpp" -#include "checker/checker.cpp" -#include "checker/expression.cpp" -#include "checker/statements.cpp" diff --git a/src/checker/checker.cpp b/src/checker/checker.cpp index 87b370c4f..44ec1d6a5 100644 --- a/src/checker/checker.cpp +++ b/src/checker/checker.cpp @@ -1,3 +1,7 @@ +#include "value.cpp" +#include "entity.cpp" +#include "type.cpp" + enum AddressingMode { Addressing_Invalid, @@ -26,6 +30,43 @@ struct TypeAndValue { Value value; }; +struct DeclarationInfo { + Scope *scope; + + Entity **entities; + isize entity_count; + + AstNode *type_expr; + AstNode *init_expr; + AstNode *proc_decl; // AstNode_ProcedureDeclaration + + Map deps; // Key: Entity * + i32 mark; +}; + +DeclarationInfo *make_declaration_info(gbAllocator a, Scope *scope) { + DeclarationInfo *d = gb_alloc_item(a, DeclarationInfo); + d->scope = scope; + map_init(&d->deps, gb_heap_allocator()); + return d; +} + +void destroy_declaration_info(DeclarationInfo *d) { + map_destroy(&d->deps); +} + +b32 has_init(DeclarationInfo *d) { + if (d->init_expr != NULL) + return true; + if (d->proc_decl != NULL) { + if (d->proc_decl->procedure_declaration.body != NULL) + return true; + } + + return false; +} + + struct ExpressionInfo { b32 is_lhs; // Debug info AddressingMode mode; @@ -42,6 +83,13 @@ ExpressionInfo make_expression_info(b32 is_lhs, AddressingMode mode, Type *type, return ei; } +struct ProcedureInfo { + Token token; + DeclarationInfo *decl; + Type * type; // Type_Procedure + AstNode * body; // AstNode_BlockStatement +}; + struct Scope { Scope *parent; Scope *prev, *next; @@ -98,21 +146,25 @@ gb_global BuiltinProcedure builtin_procedures[BuiltinProcedure_Count] = { }; - struct Checker { - Parser * parser; - Map types; // Key: AstNode * | Expression -> Type (and value) - Map definitions; // Key: AstNode * | Identifier -> Entity - Map uses; // Key: AstNode * | Identifier -> Entity - Map scopes; // Key: AstNode * | Node -> Scope - Map untyped; // Key: AstNode * | Expression -> ExpressionInfo - BaseTypeSizes sizes; - Scope * file_scope; + Parser * parser; + Map types; // Key: AstNode * | Expression -> Type (and value) + Map definitions; // Key: AstNode * | Identifier -> Entity + Map uses; // Key: AstNode * | Identifier -> Entity + Map scopes; // Key: AstNode * | Node -> Scope + Map untyped; // Key: AstNode * | Expression -> ExpressionInfo + Map entities; // Key: Entity * + + BaseTypeSizes sizes; + Scope * file_scope; + gbArray(ProcedureInfo) procedures; // NOTE(bill): Procedures to check gbArena arena; gbAllocator allocator; - Scope *curr_scope; + Scope * curr_scope; + DeclarationInfo * decl; + gbArray(Type *) procedure_stack; b32 in_defer; // TODO(bill): Actually handle correctly @@ -187,8 +239,18 @@ Entity *scope_insert_entity(Scope *s, Entity *entity) { return NULL; } +void add_dependency(DeclarationInfo *d, Entity *e) { + map_set(&d->deps, hash_pointer(e), cast(b32)true); +} - +void add_declaration_dependency(Checker *c, Entity *e) { + if (c->decl) { + auto found = map_get(&c->entities, hash_pointer(e)); + if (found) { + add_dependency(c->decl, e); + } + } +} void add_global_entity(Entity *entity) { @@ -197,7 +259,7 @@ void add_global_entity(Entity *entity) { return; // NOTE(bill): `untyped thing` } if (scope_insert_entity(global_scope, entity)) { - GB_PANIC("Internal type checking error: double declaration"); + GB_PANIC("Compiler error: double declaration"); } } @@ -256,12 +318,14 @@ void init_checker(Checker *c, Parser *parser) { map_init(&c->definitions, gb_heap_allocator()); map_init(&c->uses, gb_heap_allocator()); map_init(&c->scopes, gb_heap_allocator()); + map_init(&c->entities, gb_heap_allocator()); c->sizes.word_size = 8; c->sizes.max_align = 8; map_init(&c->untyped, a); gb_array_init(c->procedure_stack, a); + gb_array_init(c->procedures, a); // NOTE(bill): Is this big enough or too small? isize item_size = gb_max(gb_max(gb_size_of(Entity), gb_size_of(Type)), gb_size_of(Scope)); @@ -279,11 +343,16 @@ void destroy_checker(Checker *c) { map_destroy(&c->uses); map_destroy(&c->scopes); map_destroy(&c->untyped); + map_destroy(&c->entities); destroy_scope(c->file_scope); gb_array_free(c->procedure_stack); + gb_array_free(c->procedures); + gb_arena_free(&c->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, ...) { @@ -371,12 +440,14 @@ void add_entity_definition(Checker *c, AstNode *identifier, Entity *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 (!are_strings_equal(entity->token.string, make_string("_"))) { + 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) + if (identifier != NULL) add_entity_definition(c, identifier, entity); } @@ -387,6 +458,28 @@ void add_entity_use(Checker *c, AstNode *identifier, Entity *entity) { map_set(&c->uses, key, entity); } + +void add_file_entity(Checker *c, AstNode *identifier, Entity *e, DeclarationInfo *d) { + GB_ASSERT(are_strings_equal(identifier->identifier.token.string, e->token.string)); + + + add_entity(c, c->file_scope, identifier, e); + map_set(&c->entities, hash_pointer(e), d); + e->order = gb_array_count(c->entities.entries); +} + + +void check_procedure_later(Checker *c, Token token, DeclarationInfo *decl, Type *type, AstNode *body) { + ProcedureInfo info = {}; + info.token = token; + info.decl = decl; + info.type = type; + info.body = body; + gb_array_append(c->procedures, info); +} + + + void add_scope(Checker *c, AstNode *node, Scope *scope) { GB_ASSERT(node != NULL); GB_ASSERT(scope != NULL); @@ -415,47 +508,201 @@ void pop_procedure(Checker *c) { -Entity *make_entity_variable(Checker *c, Scope *parent, Token token, Type *type) { - Entity *entity = alloc_entity(c->allocator, Entity_Variable, parent, token, type); - return entity; + + +#include "expression.cpp" +#include "statements.cpp" + + + + + +GB_COMPARE_PROC(entity_order_cmp) { + Entity const *p = cast(Entity const *)a; + Entity const *q = cast(Entity const *)b; + return p->order < q->order ? -1 : p->order > q->order; } -Entity *make_entity_constant(Checker *c, Scope *parent, Token token, Type *type, Value value) { - Entity *entity = alloc_entity(c->allocator, Entity_Constant, parent, token, type); - entity->constant.value = value; - return entity; +void check_file(Checker *c, AstNode *file_node) { + +// Collect Entities + for (AstNode *decl = file_node; decl != NULL; decl = decl->next) { + if (!is_ast_node_declaration(decl)) + continue; + + switch (decl->kind) { + case AstNode_BadDeclaration: + break; + + case AstNode_VariableDeclaration: { + auto *vd = &decl->variable_declaration; + + switch (vd->kind) { + 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->allocator, c->curr_scope, name->identifier.token, NULL, v); + DeclarationInfo *di = make_declaration_info(c->allocator, c->file_scope); + di->type_expr = vd->type_expression; + di->init_expr = value; + add_file_entity(c, name, e, di); + } + + isize lhs_count = vd->name_list_count; + isize rhs_count = vd->value_list_count; + + if (rhs_count == 0 && vd->type_expression == NULL) { + print_checker_error(c, ast_node_token(decl), "Missing type or initial expression"); + } else if (lhs_count < rhs_count) { + print_checker_error(c, ast_node_token(decl), "Extra initial expression"); + } + } break; + + case Declaration_Mutable: { + isize entity_count = vd->name_list_count; + isize entity_index = 0; + Entity **entities = gb_alloc_array(c->allocator, Entity *, entity_count); + DeclarationInfo *di = NULL; + if (vd->value_list_count == 1) { + di = make_declaration_info(gb_heap_allocator(), c->file_scope); + di->entities = entities; + di->entity_count = entity_count; + di->type_expr = vd->type_expression; + di->init_expr = vd->value_list; + } + + AstNode *value = vd->value_list; + for (AstNode *name = vd->name_list; name != NULL; name = name->next) { + Entity *e = make_entity_variable(c->allocator, c->file_scope, name->identifier.token, NULL); + entities[entity_index++] = e; + + DeclarationInfo *d = di; + if (d == NULL) { + AstNode *init_expr = value; + d = make_declaration_info(gb_heap_allocator(), c->file_scope); + d->type_expr = vd->type_expression; + d->init_expr = init_expr; + } + + add_file_entity(c, name, e, d); + + if (value != NULL) + value = value->next; + } + +#if 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 = current_scope_lookup_entity(c->file_scope, str); + } + if (found == NULL) { + entity = make_entity_variable(c->allocator, c->file_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")); +#endif + } break; + } + + + } break; + + case AstNode_TypeDeclaration: { + AstNode *identifier = decl->type_declaration.name; + Entity *e = make_entity_type_name(c->allocator, c->file_scope, identifier->identifier.token, NULL); + DeclarationInfo *d = make_declaration_info(c->allocator, e->parent); + d->type_expr = decl->type_declaration.type_expression; + add_file_entity(c, identifier, e, d); + } break; + + case AstNode_ProcedureDeclaration: { + AstNode *identifier = decl->procedure_declaration.name; + Token token = identifier->identifier.token; + Entity *e = make_entity_procedure(c->allocator, c->file_scope, token, NULL); + add_entity(c, c->file_scope, identifier, e); + DeclarationInfo *d = make_declaration_info(c->allocator, e->parent); + d->proc_decl = decl; + map_set(&c->entities, hash_pointer(e), d); + e->order = gb_array_count(c->entities.entries); + + } break; + + default: + print_checker_error(c, ast_node_token(decl), "Only declarations are allowed at file scope"); + break; + } + } + +// Order entities + { + gbArray(Entity *) entities; + isize count = gb_array_count(c->entities.entries); + gb_array_init_reserve(entities, gb_heap_allocator(), count); + defer (gb_array_free(entities)); + + for (isize i = 0; i < count; i++) { + u64 key = c->entities.entries[i].key; + Entity *e = cast(Entity *)cast(uintptr)key; + gb_array_append(entities, e); + } + + gb_sort_array(entities, count, entity_order_cmp); + + for (isize i = 0; i < count; i++) { + check_entity_declaration(c, entities[i], NULL); + } + } + + + for (isize i = 0; i < gb_array_count(c->procedures); i++) { + ProcedureInfo *pi = &c->procedures[i]; + check_procedure_body(c, pi->token, pi->decl, pi->type, pi->body); + } } -Entity *make_entity_type_name(Checker *c, Scope *parent, Token token, Type *type) { - Entity *entity = alloc_entity(c->allocator, Entity_TypeName, parent, token, type); - return entity; -} -Entity *make_entity_param(Checker *c, Scope *parent, Token token, Type *type) { - Entity *entity = alloc_entity(c->allocator, 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(c->allocator, 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(c->allocator, Entity_Procedure, parent, token, signature_type); - return entity; -} - -Entity *make_entity_builtin(Checker *c, Scope *parent, Token token, Type *type, BuiltinProcedureId id) { - Entity *entity = alloc_entity(c->allocator, 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); -} diff --git a/src/checker/entity.cpp b/src/checker/entity.cpp index e93a11822..d90d08cfd 100644 --- a/src/checker/entity.cpp +++ b/src/checker/entity.cpp @@ -23,6 +23,7 @@ struct Entity { Scope *parent; Token token; Type *type; + isize order; union { struct { Value value; } constant; @@ -53,14 +54,47 @@ Entity *alloc_entity(gbAllocator a, EntityKind kind, Scope *parent, Token token, return entity; } +Entity *make_entity_variable(gbAllocator a, Scope *parent, Token token, Type *type) { + Entity *entity = alloc_entity(a, Entity_Variable, parent, token, type); + return entity; +} +Entity *make_entity_constant(gbAllocator a, Scope *parent, Token token, Type *type, Value value) { + Entity *entity = alloc_entity(a, Entity_Constant, parent, token, type); + entity->constant.value = value; + return entity; +} + +Entity *make_entity_type_name(gbAllocator a, Scope *parent, Token token, Type *type) { + Entity *entity = alloc_entity(a, Entity_TypeName, parent, token, type); + return entity; +} + +Entity *make_entity_param(gbAllocator a, Scope *parent, Token token, Type *type) { + Entity *entity = alloc_entity(a, Entity_Variable, parent, token, type); + entity->variable.used = true; + return entity; +} + +Entity *make_entity_field(gbAllocator a, Scope *parent, Token token, Type *type) { + Entity *entity = alloc_entity(a, Entity_Variable, parent, token, type); + entity->variable.is_field = true; + return entity; +} + +Entity *make_entity_procedure(gbAllocator a, Scope *parent, Token token, Type *signature_type) { + Entity *entity = alloc_entity(a, Entity_Procedure, parent, token, signature_type); + return entity; +} + +Entity *make_entity_builtin(gbAllocator a, Scope *parent, Token token, Type *type, BuiltinProcedureId id) { + Entity *entity = alloc_entity(a, Entity_Builtin, parent, token, type); + entity->builtin.id = id; + return entity; +} + +Entity *make_entity_dummy_variable(gbAllocator a, Scope *file_scope, Token token) { + token.string = make_string("_"); + return make_entity_variable(a, file_scope, token, NULL); +} -// NOTE(bill): Defined in "checker.cpp" -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); diff --git a/src/checker/expression.cpp b/src/checker/expression.cpp index 072cec859..a96ae74d6 100644 --- a/src/checker/expression.cpp +++ b/src/checker/expression.cpp @@ -9,6 +9,7 @@ void check_selector (Checker *c, Operand *operand, AstNode *n 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_entity_declaration(Checker *c, Entity *e, Type *named_type); void check_struct_type(Checker *c, Type *struct_type, AstNode *node) { @@ -40,7 +41,7 @@ void check_struct_type(Checker *c, Type *struct_type, AstNode *node) { 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); + Entity *e = make_entity_field(c->allocator, 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 @@ -71,7 +72,7 @@ Type *check_get_params(Checker *c, Scope *scope, AstNode *field_list, isize fiel Type *type = check_type(c, type_expression); for (AstNode *name = field->field.name_list; name != NULL; name = name->next) { if (name->kind == AstNode_Identifier) { - Entity *param = make_entity_param(c, scope, name->identifier.token, type); + Entity *param = make_entity_param(c->allocator, scope, name->identifier.token, type); add_entity(c, scope, name, param); variables[variable_index++] = param; } else { @@ -99,11 +100,12 @@ Type *check_get_results(Checker *c, Scope *scope, AstNode *list, isize list_coun 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); + Entity *param = make_entity_param(c->allocator, scope, token, type); // NOTE(bill): No need to record variables[variable_index++] = param; if (get_base_type(type)->kind == Type_Array) { + // TODO(bill): Should I allow array's to returned? print_checker_error(c, token, "You cannot return an array from a procedure"); } } @@ -123,13 +125,14 @@ void check_procedure_type(Checker *c, Type *type, AstNode *proc_type_node) { 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.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; @@ -138,17 +141,22 @@ void check_identifier(Checker *c, Operand *operand, AstNode *n, Type *named_type 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)); + "Undeclared type or identifier `%.*s`", LIT(n->identifier.token.string)); return; } add_entity_use(c, n, e); - Type *type = e->type; - GB_ASSERT(type != NULL); + check_entity_declaration(c, e, named_type); + + if (e->type == NULL) { + GB_PANIC("Compiler error: How did this happen? type: %s; identifier: %.*s\n", type_to_string(e->type), LIT(n->identifier.token.string)); + return; + } switch (e->kind) { case Entity_Constant: - if (type == &basic_types[Basic_Invalid]) + add_declaration_dependency(c, e); + if (e->type == &basic_types[Basic_Invalid]) return; operand->value = e->constant.value; GB_ASSERT(operand->value.kind != Value_Invalid); @@ -156,8 +164,9 @@ void check_identifier(Checker *c, Operand *operand, AstNode *n, Type *named_type break; case Entity_Variable: + add_declaration_dependency(c, e); e->variable.used = true; - if (type == &basic_types[Basic_Invalid]) + if (e->type == &basic_types[Basic_Invalid]) return; operand->mode = Addressing_Variable; break; @@ -167,6 +176,7 @@ void check_identifier(Checker *c, Operand *operand, AstNode *n, Type *named_type break; case Entity_Procedure: + add_declaration_dependency(c, e); operand->mode = Addressing_Value; break; @@ -176,11 +186,11 @@ void check_identifier(Checker *c, Operand *operand, AstNode *n, Type *named_type break; default: - GB_PANIC("Unknown EntityKind"); + GB_PANIC("Compiler error: Unknown EntityKind"); break; } - operand->type = type; + operand->type = e->type; } i64 check_array_count(Checker *c, AstNode *expression) { @@ -504,7 +514,7 @@ b32 check_value_is_expressible(Checker *c, Value in_value, Type *type, Value *ou case Basic_UntypedInteger: return true; - default: GB_PANIC("Unknown integer type!"); break; + default: GB_PANIC("Compiler error: Unknown integer type!"); break; } } else if (is_type_float(type)) { Value v = value_to_float(in_value); @@ -1291,24 +1301,22 @@ void check_call_arguments(Checker *c, Operand *operand, Type *proc_type, AstNode GB_ASSERT(call->kind == AstNode_CallExpression); GB_ASSERT(proc_type->kind == Type_Procedure); auto *ce = &call->call_expression; + isize error_code = 0; + isize param_index = 0; 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) { + for (; call_arg != NULL; call_arg = call_arg->next) { check_multi_expression(c, operand, call_arg); if (operand->mode == Addressing_Invalid) continue; @@ -1339,6 +1347,7 @@ void check_call_arguments(Checker *c, Operand *operand, Type *proc_type, AstNode break; } + if (param_index < param_count) { error_code = -1; } else if (call_arg != NULL && call_arg->next != NULL) { @@ -1348,10 +1357,11 @@ void check_call_arguments(Checker *c, Operand *operand, Type *proc_type, AstNode if (error_code != 0) { char *err_fmt = ""; - if (error_code < 0) + if (error_code < 0) { err_fmt = "Too few arguments for `%s`, expected %td arguments"; - else + } 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); @@ -1666,14 +1676,13 @@ ExpressionKind check_expression_base(Checker *c, Operand *operand, AstNode *node i64 indices[3] = {}; AstNode *nodes[3] = {se->low, se->high, se->max}; for (isize i = 0; i < gb_count_of(nodes); i++) { - AstNode *node = nodes[i]; i64 index = max_count; - if (node != NULL) { + if (nodes[i] != NULL) { i64 capacity = -1; if (max_count >= 0) capacity = max_count; i64 j = 0; - if (check_index_value(c, node, capacity, &j)) { + if (check_index_value(c, nodes[i], capacity, &j)) { index = j; } } else if (i == 0) { diff --git a/src/checker/statements.cpp b/src/checker/statements.cpp index a967ff742..435f7ef4f 100644 --- a/src/checker/statements.cpp +++ b/src/checker/statements.cpp @@ -1,5 +1,63 @@ // Statements and Declarations +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); +} + + +// NOTE(bill): The last expression has to be a `return` statement +// TODO(bill): This is a mild hack and should be probably handled +// TODO(bill): Warn/err against code after `return` that it won't be executed +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; +} + + b32 check_is_assignable_to(Checker *c, Operand *operand, Type *type) { if (operand->mode == Addressing_Invalid || type == &basic_types[Basic_Invalid]) { @@ -42,9 +100,8 @@ b32 check_is_assignable_to(Checker *c, Operand *operand, Type *type) { } } - if ((sb->kind == Type_Array || sb->kind == Type_Slice) && - tb->kind == Type_Slice) { - if (are_types_identical(sb->array.element, tb->slice.element)) { + if (sb->kind == Type_Slice && tb->kind == Type_Slice) { + if (are_types_identical(sb->slice.element, tb->slice.element)) { return true; } } @@ -214,13 +271,13 @@ Type *check_init_variable(Checker *c, Entity *e, Operand *operand, String contex } 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) + if ((lhs == NULL || lhs_count == 0) && init_count == 0) return; isize i = 0; AstNode *rhs = init_list; for (; - i < lhs_count && rhs != NULL; + i < lhs_count && i < init_count && rhs != NULL; i++, rhs = rhs->next) { Operand operand = {}; check_multi_expression(c, &operand, rhs); @@ -229,7 +286,7 @@ void check_init_variables(Checker *c, Entity **lhs, isize lhs_count, AstNode *in } else { auto *tuple = &operand.type->tuple; for (isize j = 0; - j < tuple->variable_count && i < lhs_count; + j < tuple->variable_count && i < lhs_count && i < init_count; j++, i++) { Type *type = tuple->variables[j]->type; operand.type = type; @@ -238,7 +295,7 @@ void check_init_variables(Checker *c, Entity **lhs, isize lhs_count, AstNode *in } } - if (i < lhs_count) { + if (i < lhs_count && i < init_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) { @@ -250,7 +307,6 @@ 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; @@ -266,7 +322,7 @@ void check_init_constant(Checker *c, Entity *e, Operand *operand) { } 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)); + GB_PANIC("Compiler error: Type `%s` not constant!!!", type_to_string(operand->type)); } if (e->type == NULL) // NOTE(bill): type inference @@ -279,7 +335,8 @@ void check_init_constant(Checker *c, Entity *e, Operand *operand) { e->constant.value = operand->value; } -void check_constant_declaration(Checker *c, Entity *e, AstNode *type_expression, AstNode *init_expression) { + +void check_constant_declaration(Checker *c, Entity *e, AstNode *type_expr, AstNode *init_expr) { GB_ASSERT(e->type == NULL); if (e->variable.visited) { @@ -288,12 +345,12 @@ void check_constant_declaration(Checker *c, Entity *e, AstNode *type_expression, } e->variable.visited = true; - if (type_expression) { - Type *t = check_type(c, type_expression); + if (type_expr) { + Type *t = check_type(c, type_expr); 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), + print_checker_error(c, ast_node_token(type_expr), "Invalid constant type `%s`", str); e->type = &basic_types[Basic_Invalid]; return; @@ -302,68 +359,169 @@ void check_constant_declaration(Checker *c, Entity *e, AstNode *type_expression, } Operand operand = {Addressing_Invalid}; - if (init_expression) - check_expression(c, &operand, init_expression); + if (init_expr) + check_expression(c, &operand, init_expr); check_init_constant(c, e, &operand); } -void check_statement(Checker *c, AstNode *node); +void check_type_declaration(Checker *c, Entity *e, AstNode *type_expr, Type *named_type) { + GB_ASSERT(e->type == NULL); + Type *named = make_type_named(c->allocator, e->token.string, NULL, e); + named->named.type_name = e; + set_base_type(named_type, named); + e->type = named; -void check_statement_list(Checker *c, AstNode *node) { - for (; node != NULL; node = node->next) - check_statement(c, node); + check_type(c, type_expr, named); + + set_base_type(named, get_base_type(get_base_type(named))); } -// NOTE(bill): The last expression has to be a `return` statement -// TODO(bill): This is a mild hack and should be probably handled -// TODO(bill): Warn/err against code after `return` that it won't be executed -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; +void check_procedure_body(Checker *c, Token token, DeclarationInfo *decl, Type *type, AstNode *body) { + GB_ASSERT(body->kind == AstNode_BlockStatement); + Scope *origin_curr_scope = c->curr_scope; + c->curr_scope = decl->scope; + push_procedure(c, type); + check_statement_list(c, body->block_statement.list); + if (decl->type_expr != NULL && + decl->type_expr->procedure_type.result_count > 0) { + if (!check_is_terminating(c, body)) { + print_checker_error(c, body->block_statement.close, "Missing return statement at the end of the procedure"); } + } + pop_procedure(c); + + c->curr_scope = origin_curr_scope; +} + +void check_procedure_declaration(Checker *c, Entity *e, DeclarationInfo *d, b32 check_body_later) { + GB_ASSERT(e->type == NULL); + + Type *proc_type = make_type_procedure(c->allocator, e->parent, NULL, 0, NULL, 0); + e->type = proc_type; + auto *pd = &d->proc_decl->procedure_declaration; + +#if 1 + Scope *origin_curr_scope = c->curr_scope; + c->curr_scope = c->file_scope; + check_open_scope(c, pd->procedure_type); +#endif + check_procedure_type(c, proc_type, pd->procedure_type); + b32 is_foreign = false; + b32 is_inline = false; + b32 is_no_inline = false; + for (AstNode *tag = pd->tag_list; tag != NULL; tag = tag->next) { + GB_ASSERT(tag->kind == AstNode_TagExpression); + + String tag_name = tag->tag_expression.name.string; + if (are_strings_equal(tag_name, make_string("foreign"))) { + is_foreign = true; + } else if (are_strings_equal(tag_name, make_string("inline"))) { + is_inline = true; + } else if (are_strings_equal(tag_name, make_string("no_inline"))) { + is_no_inline = true; + } else { + print_checker_error(c, ast_node_token(tag), "Unknown procedure tag"); + } + // TODO(bill): Other tags + } + + if (is_inline && is_no_inline) { + print_checker_error(c, ast_node_token(pd->tag_list), + "You cannot apply both `inline` and `no_inline` to a procedure"); + } + + if (pd->body != NULL) { + if (is_foreign) { + print_checker_error(c, ast_node_token(pd->body), + "A procedure tagged as `#foreign` cannot have a body"); + } + + d->scope = c->curr_scope; + + GB_ASSERT(pd->body->kind == AstNode_BlockStatement); + if (check_body_later) { + check_procedure_later(c, e->token, d, proc_type, pd->body); + } else { + check_procedure_body(c, e->token, d, proc_type, pd->body); + } + } + +#if 1 + check_close_scope(c); + c->curr_scope = origin_curr_scope; +#endif + +} + +void check_variable_declaration(Checker *c, Entity *e, Entity **entities, isize entity_count, AstNode *type_expr, AstNode *init_expr) { + GB_ASSERT(e->type == NULL); + GB_ASSERT(e->kind == Entity_Variable); + + if (e->variable.visited) { + e->type = &basic_types[Basic_Invalid]; + return; + } + e->variable.visited = true; + + if (type_expr != NULL) + e->type = check_type(c, type_expr, NULL); + + if (init_expr == NULL) { + if (type_expr == NULL) + e->type = &basic_types[Basic_Invalid]; + return; + } + + if (entities == NULL || entity_count == 1) { + GB_ASSERT(entities == NULL || entities[0] == e); + Operand operand = {}; + check_expression(c, &operand, init_expr); + check_init_variable(c, e, &operand, make_string("variable declaration")); + } + + if (type_expr != NULL) { + for (isize i = 0; i < entity_count; i++) + entities[i]->type = e->type; + } + + check_init_variables(c, entities, entity_count, init_expr, 1, make_string("variable declaration")); +} + + + +void check_entity_declaration(Checker *c, Entity *e, Type *named_type) { + if (e->type != NULL) + return; + + DeclarationInfo **found = map_get(&c->entities, hash_pointer(e)); + if (found == NULL) { + GB_PANIC("Compiler error: This entity should be declared!"); + } + DeclarationInfo *d = *found; + + switch (e->kind) { + case Entity_Constant: + c->decl = d; + check_constant_declaration(c, e, d->type_expr, d->init_expr); break; - - case AstNode_ForStatement: - if (node->for_statement.cond == NULL) { - return true; - } + case Entity_Variable: + c->decl = d; + check_variable_declaration(c, e, d->entities, d->entity_count, d->type_expr, d->init_expr); + break; + case Entity_TypeName: + check_type_declaration(c, e, d->type_expr, named_type); + break; + case Entity_Procedure: + check_procedure_declaration(c, e, d, true); break; } - return false; } + + + void check_statement(Checker *c, AstNode *node) { switch (node->kind) { case AstNode_EmptyStatement: break; @@ -577,7 +735,7 @@ void check_statement(Checker *c, AstNode *node) { found = current_scope_lookup_entity(c->curr_scope, str); } if (found == NULL) { - entity = make_entity_variable(c, c->curr_scope, token, NULL); + entity = make_entity_variable(c->allocator, c->curr_scope, token, NULL); if (!can_be_ignored) { new_entities[new_entity_count++] = entity; } @@ -589,7 +747,7 @@ void check_statement(Checker *c, AstNode *node) { print_checker_error(c, token, "A variable declaration must be an identifier"); } if (entity == NULL) - entity = make_entity_dummy_variable(c, token); + entity = make_entity_dummy_variable(c->allocator, c->file_scope, token); entities[entity_index++] = entity; } @@ -629,7 +787,7 @@ void check_statement(Checker *c, AstNode *node) { 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); + Entity *e = make_entity_constant(c->allocator, c->curr_scope, name->identifier.token, NULL, v); entities[entity_index++] = e; check_constant_declaration(c, e, vd->type_expression, value); } @@ -656,81 +814,23 @@ void check_statement(Checker *c, AstNode *node) { } } break; - case AstNode_ProcedureDeclaration: { auto *pd = &node->procedure_declaration; - GB_ASSERT_MSG(pd->kind == Declaration_Immutable, "Mutable/temp/anonymous procedures are not yet implemented"); - Entity *e = make_entity_procedure(c, c->curr_scope, pd->name->identifier.token, NULL); + Entity *e = make_entity_procedure(c->allocator, c->curr_scope, pd->name->identifier.token, NULL); add_entity(c, c->curr_scope, pd->name, e); - Type *proc_type = make_type_procedure(c->allocator, e->parent, NULL, 0, NULL, 0); - e->type = proc_type; - - // NOTE(bill): Procedures are just in file scope only. - // This is because closures/lambdas are not supported yet (or maybe never) - Scope *origin_curr_scope = c->curr_scope; - c->curr_scope = c->file_scope; - check_open_scope(c, pd->procedure_type); - { - check_procedure_type(c, proc_type, pd->procedure_type); - b32 is_foreign = false; - b32 is_inline = false; - b32 is_no_inline = false; - for (AstNode *tag = pd->tag_list; tag != NULL; tag = tag->next) { - GB_ASSERT(tag->kind == AstNode_TagExpression); - - String tag_name = tag->tag_expression.name.string; - if (are_strings_equal(tag_name, make_string("foreign"))) { - is_foreign = true; - } else if (are_strings_equal(tag_name, make_string("inline"))) { - is_inline = true; - } else if (are_strings_equal(tag_name, make_string("no_inline"))) { - is_no_inline = true; - } else { - print_checker_error(c, ast_node_token(tag), "Unknown procedure tag"); - } - // TODO(bill): Other tags - } - - if (is_inline && is_no_inline) { - print_checker_error(c, ast_node_token(pd->tag_list), - "You cannot apply both `inline` and `no_inline` to a procedure"); - } - - if (pd->body) { - GB_ASSERT(pd->body->kind == AstNode_BlockStatement); - - if (is_foreign) { - print_checker_error(c, ast_node_token(pd->body), - "A procedure tagged as `#foreign` cannot have a body"); - } - - 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); - } - } - check_close_scope(c); - c->curr_scope = origin_curr_scope; - + DeclarationInfo *decl = make_declaration_info(gb_heap_allocator(), e->parent); + decl->proc_decl = node; + check_procedure_declaration(c, e, decl, false); + destroy_declaration_info(decl); } 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); + Entity *e = make_entity_type_name(c->allocator, c->curr_scope, name->identifier.token, NULL); add_entity(c, c->curr_scope, name, e); - - e->type = make_type_named(c->allocator, 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)); - + check_type_declaration(c, e, td->type_expression, NULL); } break; } } diff --git a/src/generator.cpp b/src/generator.cpp index d85453a75..4c4afe207 100644 --- a/src/generator.cpp +++ b/src/generator.cpp @@ -1,9 +1,9 @@ -// #include +#include +#include struct Generator { Checker *checker; - String output_fullpath; - gbFile output; + String output_path; #define MAX_GENERATOR_ERROR_COUNT 10 isize error_prev_line; @@ -43,14 +43,10 @@ b32 init_generator(Generator *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; + isize len = ext-fullpath; 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); - + gb_memcopy(text, fullpath, len); + g->output_path = make_string(text, len); return true; } @@ -60,12 +56,27 @@ void destroy_generator(Generator *g) { } - if (g->output_fullpath.text) - gb_free(gb_heap_allocator(), g->output_fullpath.text); + if (g->output_path.text) + gb_free(gb_heap_allocator(), g->output_path.text); } +void emit_var_decl(Generator *g, String name, Type *type) { + // gb_printf("%.*s: %s;\n", LIT(name), type_to_string(type)); +} -void generate_code(Generator *g, AstNode *root_node) { + +void generate_code(Generator *g, AstNode *file_node) { + // if (file_node->kind == AstNode_VariableDeclaration) { + // auto *vd = &file_node->variable_declaration; + // if (vd->kind == Declaration_Mutable) { + // for (AstNode *name_item = vd->name_list; name_item != NULL; name_item = name_item->next) { + // String name = name_item->identifier.token.string; + // Entity *entity = entity_of_identifier(g->checker, name_item); + // Type *type = entity->type; + // emit_var_decl(g, name, type); + // } + // } + // } } diff --git a/src/main.cpp b/src/main.cpp index 66e93fdfd..68b7b7ee8 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -2,7 +2,7 @@ #include "tokenizer.cpp" #include "parser.cpp" #include "printer.cpp" -#include "checker.cpp" +#include "checker/checker.cpp" #include "generator.cpp" @@ -21,20 +21,20 @@ int main(int argc, char **argv) { if (init_parser(&parser, filename)) { defer (destroy_parser(&parser)); - AstNode *root_node = parse_statement_list(&parser, NULL); - // print_ast(root_node, 0); + AstNode *file_node = parse_statement_list(&parser, NULL); + // print_ast(file_node, 0); Checker checker = {}; init_checker(&checker, &parser); defer (destroy_checker(&checker)); - check_statement_list(&checker, root_node); + check_file(&checker, file_node); -#if 0 +#if 1 Generator generator = {}; if (init_generator(&generator, &checker)) { defer (destroy_generator(&generator)); - generate_code(&generator, root_node); + generate_code(&generator, file_node); } #endif } diff --git a/src/parser.cpp b/src/parser.cpp index d9f4928f1..050a69894 100644 --- a/src/parser.cpp +++ b/src/parser.cpp @@ -310,8 +310,12 @@ Token ast_node_token(AstNode *node) { 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_Field: { + if (node->field.name_list) + return ast_node_token(node->field.name_list); + else + return ast_node_token(node->field.type_expression); + } case AstNode_ProcedureType: return node->procedure_type.token; case AstNode_PointerType: @@ -1330,9 +1334,9 @@ AstNode *parse_results(Parser *p, AstScope *scope, isize *result_count) { return list; } - AstNode *field = make_field(p, NULL, 0, parse_type(p)); + AstNode *result = parse_type(p); if (result_count) *result_count = 1; - return field; + return result; } if (result_count) *result_count = 0; return NULL; diff --git a/src/test.odin b/src/test.odin index 089da3f1e..0ea81d86c 100644 --- a/src/test.odin +++ b/src/test.odin @@ -1,12 +1,26 @@ main :: proc() { - x : u8 = 0; + x := "Yep"; thing :: proc(n: int) -> (int, f32) { return n*n, 13.37; } - thang :: proc(a: int, b: f32, s: string) { - } + thang(thing(1), x); - thang(thing(1), "Yep"); + v: Vec2; } + +thang :: proc(a: int, b: f32, s: string) { + a = 1; + b = 2; + s = "Hello"; +} + +z := y; +y := x; +x := 1; + +type Vec2: struct { + x, y: f32; +} + diff --git a/src/tokenizer.cpp b/src/tokenizer.cpp index c3e952814..f077f687f 100644 --- a/src/tokenizer.cpp +++ b/src/tokenizer.cpp @@ -219,6 +219,9 @@ struct Token { }; +Token empty_token = {Token_Invalid}; + + char const *token_kind_to_string(TokenKind kind) { return TOKEN_STRINGS[kind];