mirror of
https://github.com/odin-lang/Odin.git
synced 2026-06-08 19:44:20 +00:00
Random Order File Scope Declaration
This commit is contained in:
26
build.bat
26
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%
|
||||
|
||||
@@ -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"
|
||||
@@ -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<b32> 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<TypeAndValue> types; // Key: AstNode * | Expression -> Type (and value)
|
||||
Map<Entity *> definitions; // Key: AstNode * | Identifier -> Entity
|
||||
Map<Entity *> uses; // Key: AstNode * | Identifier -> Entity
|
||||
Map<Scope *> scopes; // Key: AstNode * | Node -> Scope
|
||||
Map<ExpressionInfo> untyped; // Key: AstNode * | Expression -> ExpressionInfo
|
||||
BaseTypeSizes sizes;
|
||||
Scope * file_scope;
|
||||
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
|
||||
Map<Scope *> scopes; // Key: AstNode * | Node -> Scope
|
||||
Map<ExpressionInfo> untyped; // Key: AstNode * | Expression -> ExpressionInfo
|
||||
Map<DeclarationInfo *> 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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
// #include <llvm-c/llvm>
|
||||
#include <llvm-c/Core.h>
|
||||
#include <llvm-c/BitWriter.h>
|
||||
|
||||
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);
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
}
|
||||
|
||||
12
src/main.cpp
12
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
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -219,6 +219,9 @@ struct Token {
|
||||
};
|
||||
|
||||
|
||||
Token empty_token = {Token_Invalid};
|
||||
|
||||
|
||||
|
||||
char const *token_kind_to_string(TokenKind kind) {
|
||||
return TOKEN_STRINGS[kind];
|
||||
|
||||
Reference in New Issue
Block a user