mirror of
https://github.com/odin-lang/Odin.git
synced 2026-04-20 05:20:28 +00:00
Initial Commit
Original Git Repo "went bad" so I had to restart.
This commit is contained in:
4
.gitignore
vendored
Normal file
4
.gitignore
vendored
Normal file
@@ -0,0 +1,4 @@
|
||||
bin/
|
||||
misc/
|
||||
*.sln
|
||||
*.suo
|
||||
70
build.bat
Normal file
70
build.bat
Normal file
@@ -0,0 +1,70 @@
|
||||
@echo off
|
||||
|
||||
set base_dir=W:\Odin
|
||||
:: Make sure this is a decent name and not generic
|
||||
set exe_name=odin.exe
|
||||
|
||||
:: Debug = 0, Release = 1
|
||||
set release_mode=0
|
||||
|
||||
set compiler_flags= -nologo -Oi -TP -W4 -fp:fast -fp:except- -Gm- -MP -FC -Z7 -GS- -EHsc- -GR-
|
||||
|
||||
if %release_mode% EQU 0 ( rem Debug
|
||||
set compiler_flags=%compiler_flags% -Od -MDd -Z7
|
||||
) else ( rem Release
|
||||
set compiler_flags=%compiler_flags% -O2 -MT
|
||||
)
|
||||
|
||||
set compiler_warnings= ^
|
||||
-we4013 -we4706 ^
|
||||
-wd4100 -wd4127 -wd4189 ^
|
||||
-wd4201 -wd4204 -wd4244 ^
|
||||
-wd4306 ^
|
||||
-wd4480 ^
|
||||
-wd4505 -wd4512 -wd4550 ^
|
||||
|
||||
set compiler_includes= ^
|
||||
rem -I"C:\Program Files\LLVM\include"
|
||||
|
||||
set libs= kernel32.lib user32.lib gdi32.lib opengl32.lib ^
|
||||
|
||||
rem -libpath:"C:\Program Files\LLVM\lib" ^
|
||||
rem LLVMCodeGen.lib ^
|
||||
rem LLVMTarget.lib ^
|
||||
rem LLVMBitWriter.lib ^
|
||||
rem LLVMAnalysis.lib ^
|
||||
rem LLVMObject.lib ^
|
||||
rem LLVMMCParser.lib ^
|
||||
rem LLVMBitReader.lib ^
|
||||
rem LLVMMC.lib ^
|
||||
rem LLVMCore.lib ^
|
||||
rem LLVMSupport.lib
|
||||
|
||||
|
||||
|
||||
|
||||
set linker_flags= -incremental:no -opt:ref -subsystem:console
|
||||
|
||||
rem Debug
|
||||
if %release_mode% EQU 0 (set linker_flags=%linker_flags% -debug)
|
||||
|
||||
set compiler_settings=%compiler_flags% %compiler_warnings% %compiler_includes%
|
||||
set linker_settings=%libs% %linker_flags%
|
||||
|
||||
set build_dir= "%base_dir%\bin\"
|
||||
if not exist %build_dir% mkdir %build_dir%
|
||||
pushd %build_dir%
|
||||
del *.pdb > NUL 2> NUL
|
||||
del *.ilk > NUL 2> NUL
|
||||
|
||||
del ..\misc\*.pdb > NUL 2> NUL
|
||||
del ..\misc\*.ilk > NUL 2> NUL
|
||||
|
||||
cl %compiler_settings% "%base_dir%\src\main.cpp" ^
|
||||
/link %linker_settings% -OUT:%exe_name% ^
|
||||
&& call run.bat
|
||||
|
||||
:do_not_compile_exe
|
||||
popd
|
||||
:end_of_build
|
||||
|
||||
25
libs.txt
Normal file
25
libs.txt
Normal file
@@ -0,0 +1,25 @@
|
||||
LLVMX86Disassembler.lib ^
|
||||
LLVMX86AsmParser.lib ^
|
||||
LLVMX86CodeGen.lib ^
|
||||
LLVMSelectionDAG.lib ^
|
||||
LLVMAsmPrinter.lib ^
|
||||
LLVMCodeGen.lib ^
|
||||
LLVMTarget.lib ^
|
||||
LLVMScalarOpts.lib ^
|
||||
LLVMInstCombine.lib ^
|
||||
LLVMInstrumentation.lib ^
|
||||
LLVMProfileData.lib ^
|
||||
LLVMTransformUtils.lib ^
|
||||
LLVMBitWriter.lib ^
|
||||
LLVMAnalysis.lib ^
|
||||
LLVMX86Desc.lib ^
|
||||
LLVMObject.lib ^
|
||||
LLVMMCParser.lib ^
|
||||
LLVMBitReader.lib ^
|
||||
LLVMMCDisassembler.lib ^
|
||||
LLVMX86Info.lib ^
|
||||
LLVMX86AsmPrinter.lib ^
|
||||
LLVMMC.lib ^
|
||||
LLVMX86Utils.lib ^
|
||||
LLVMCore.lib ^
|
||||
LLVMSupport.lib ^
|
||||
6
src/checker.cpp
Normal file
6
src/checker.cpp
Normal file
@@ -0,0 +1,6 @@
|
||||
#include "checker/value.cpp"
|
||||
#include "checker/entity.cpp"
|
||||
#include "checker/type.cpp"
|
||||
#include "checker/checker.cpp"
|
||||
#include "checker/expression.cpp"
|
||||
#include "checker/statements.cpp"
|
||||
457
src/checker/checker.cpp
Normal file
457
src/checker/checker.cpp
Normal file
@@ -0,0 +1,457 @@
|
||||
enum AddressingMode {
|
||||
Addressing_Invalid,
|
||||
|
||||
Addressing_NoValue,
|
||||
Addressing_Value,
|
||||
Addressing_Variable,
|
||||
Addressing_Constant,
|
||||
Addressing_Type,
|
||||
Addressing_Builtin,
|
||||
|
||||
Addressing_Count,
|
||||
};
|
||||
|
||||
struct Operand {
|
||||
AddressingMode mode;
|
||||
Type *type;
|
||||
Value value;
|
||||
|
||||
AstNode *expression;
|
||||
i32 builtin_id;
|
||||
};
|
||||
|
||||
struct TypeAndValue {
|
||||
AddressingMode mode;
|
||||
Type *type;
|
||||
Value value;
|
||||
};
|
||||
|
||||
struct ExpressionInfo {
|
||||
b32 is_lhs; // Debug info
|
||||
AddressingMode mode;
|
||||
Type *type; // Type_Basic
|
||||
Value value;
|
||||
};
|
||||
|
||||
ExpressionInfo make_expression_info(b32 is_lhs, AddressingMode mode, Type *type, Value value) {
|
||||
ExpressionInfo ei = {};
|
||||
ei.is_lhs = is_lhs;
|
||||
ei.mode = mode;
|
||||
ei.type = type;
|
||||
ei.value = value;
|
||||
return ei;
|
||||
}
|
||||
|
||||
struct Scope {
|
||||
Scope *parent;
|
||||
gbArray(Scope *) children; // TODO(bill): Remove and make into a linked list
|
||||
Map<Entity *> elements; // Key: String
|
||||
};
|
||||
|
||||
enum ExpressionKind {
|
||||
Expression_Expression,
|
||||
Expression_Conversion,
|
||||
Expression_Statement,
|
||||
};
|
||||
|
||||
struct BuiltinProcedure {
|
||||
String name;
|
||||
isize arg_count;
|
||||
b32 variadic;
|
||||
ExpressionKind kind;
|
||||
};
|
||||
|
||||
enum BuiltinProcedureId {
|
||||
BuiltinProcedure_Invalid,
|
||||
|
||||
BuiltinProcedure_size_of,
|
||||
BuiltinProcedure_size_of_val,
|
||||
BuiltinProcedure_align_of,
|
||||
BuiltinProcedure_align_of_val,
|
||||
BuiltinProcedure_offset_of,
|
||||
BuiltinProcedure_offset_of_val,
|
||||
BuiltinProcedure_static_assert,
|
||||
BuiltinProcedure_print,
|
||||
BuiltinProcedure_println,
|
||||
|
||||
BuiltinProcedure_Count,
|
||||
};
|
||||
|
||||
struct Checker {
|
||||
Parser * parser;
|
||||
Map<TypeAndValue> types; // Key: AstNode * | Expression -> Type (and value)
|
||||
Map<Entity *> definitions; // Key: AstNode * | Identifier -> Entity
|
||||
Map<Entity *> uses; // Key: AstNode * | Identifier -> Entity (Anonymous field)
|
||||
Map<Scope *> scopes; // Key: AstNode * | Node -> Scope
|
||||
Map<ExpressionInfo> untyped; // Key: AstNode * | Expression -> ExpressionInfo
|
||||
BaseTypeSizes sizes;
|
||||
Scope * file_scope;
|
||||
|
||||
gbArena entity_arena;
|
||||
|
||||
Scope *curr_scope;
|
||||
gbArray(Type *) procedure_stack;
|
||||
b32 in_defer;
|
||||
|
||||
#define MAX_CHECKER_ERROR_COUNT 10
|
||||
isize error_prev_line;
|
||||
isize error_prev_column;
|
||||
isize error_count;
|
||||
};
|
||||
|
||||
|
||||
gb_global Scope *global_scope = NULL;
|
||||
|
||||
gb_global BuiltinProcedure builtin_procedures[BuiltinProcedure_Count] = {
|
||||
{STR_LIT(""), 0, false, Expression_Statement},
|
||||
{STR_LIT("size_of"), 1, false, Expression_Expression},
|
||||
{STR_LIT("size_of_val"), 1, false, Expression_Expression},
|
||||
{STR_LIT("align_of"), 1, false, Expression_Expression},
|
||||
{STR_LIT("align_of_val"), 1, false, Expression_Expression},
|
||||
{STR_LIT("offset_of"), 2, false, Expression_Expression},
|
||||
{STR_LIT("offset_of_val"), 1, false, Expression_Expression},
|
||||
{STR_LIT("static_assert"), 1, false, Expression_Statement},
|
||||
{STR_LIT("print"), 1, true, Expression_Statement},
|
||||
{STR_LIT("println"), 1, true, Expression_Statement},
|
||||
};
|
||||
|
||||
|
||||
// TODO(bill): Arena allocation
|
||||
Scope *make_scope(Scope *parent) {
|
||||
gbAllocator a = gb_heap_allocator();
|
||||
Scope *s = gb_alloc_item(a, Scope);
|
||||
s->parent = parent;
|
||||
gb_array_init(s->children, a);
|
||||
map_init(&s->elements, a);
|
||||
if (parent != NULL && parent != global_scope)
|
||||
gb_array_append(parent->children, s);
|
||||
return s;
|
||||
}
|
||||
|
||||
void destroy_scope(Scope *scope) {
|
||||
for (isize i = 0; i < gb_array_count(scope->children); i++) {
|
||||
destroy_scope(scope->children[i]);
|
||||
}
|
||||
map_destroy(&scope->elements);
|
||||
gb_array_free(scope->children);
|
||||
gb_free(gb_heap_allocator(), scope);
|
||||
}
|
||||
|
||||
|
||||
void scope_lookup_parent_entity(Scope *s, String name, Scope **scope, Entity **entity) {
|
||||
u64 key = hash_string(name);
|
||||
for (; s != NULL; s = s->parent) {
|
||||
Entity **found = map_get(&s->elements, key);
|
||||
if (found) {
|
||||
if (entity) *entity = *found;
|
||||
if (scope) *scope = s;
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (entity) *entity = NULL;
|
||||
if (scope) *scope = NULL;
|
||||
}
|
||||
|
||||
Entity *scope_lookup_entity(Scope *s, String name) {
|
||||
Entity *entity = NULL;
|
||||
scope_lookup_parent_entity(s, name, NULL, &entity);
|
||||
return entity;
|
||||
}
|
||||
|
||||
Entity *scope_lookup_entity_current(Scope *s, String name) {
|
||||
u64 key = hash_string(name);
|
||||
Entity **found = map_get(&s->elements, key);
|
||||
if (found)
|
||||
return *found;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
|
||||
|
||||
Entity *scope_insert_entity(Scope *s, Entity *entity) {
|
||||
String name = entity->token.string;
|
||||
u64 key = hash_string(name);
|
||||
Entity **found = map_get(&s->elements, key);
|
||||
if (found)
|
||||
return *found;
|
||||
map_set(&s->elements, key, entity);
|
||||
if (entity->parent == NULL)
|
||||
entity->parent = s;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
void add_global_entity(Entity *entity) {
|
||||
String name = entity->token.string;
|
||||
if (gb_memchr(name.text, ' ', name.len)) {
|
||||
return; // NOTE(bill): `untyped thing`
|
||||
}
|
||||
if (scope_insert_entity(global_scope, entity)) {
|
||||
GB_PANIC("Internal type checking error: double declaration");
|
||||
}
|
||||
}
|
||||
|
||||
void init_global_scope(void) {
|
||||
global_scope = make_scope(NULL);
|
||||
gbAllocator a = gb_heap_allocator();
|
||||
|
||||
// Types
|
||||
for (isize i = 0; i < gb_count_of(basic_types); i++) {
|
||||
Token token = {Token_Identifier};
|
||||
token.string = basic_types[i].basic.name;
|
||||
add_global_entity(alloc_entity(a, Entity_TypeName, NULL, token, &basic_types[i]));
|
||||
}
|
||||
for (isize i = 0; i < gb_count_of(basic_type_aliases); i++) {
|
||||
Token token = {Token_Identifier};
|
||||
token.string = basic_type_aliases[i].basic.name;
|
||||
add_global_entity(alloc_entity(a, Entity_TypeName, NULL, token, &basic_type_aliases[i]));
|
||||
}
|
||||
|
||||
// Constants
|
||||
Token true_token = {Token_Identifier};
|
||||
true_token.string = make_string("true");
|
||||
Entity *true_entity = alloc_entity(a, Entity_Constant, NULL, true_token, &basic_types[Basic_UntypedBool]);
|
||||
true_entity->constant.value = make_value_bool(true);
|
||||
add_global_entity(true_entity);
|
||||
|
||||
Token false_token = {Token_Identifier};
|
||||
false_token.string = make_string("false");
|
||||
Entity *false_entity = alloc_entity(a, Entity_Constant, NULL, false_token, &basic_types[Basic_UntypedBool]);
|
||||
false_entity->constant.value = make_value_bool(false);
|
||||
add_global_entity(false_entity);
|
||||
|
||||
Token null_token = {Token_Identifier};
|
||||
null_token.string = make_string("null");
|
||||
Entity *null_entity = alloc_entity(a, Entity_Constant, NULL, null_token, &basic_types[Basic_UntypedPointer]);
|
||||
null_entity->constant.value = make_value_pointer(NULL);
|
||||
add_global_entity(null_entity);
|
||||
|
||||
// Builtin Procedures
|
||||
for (isize i = 0; i < gb_count_of(builtin_procedures); i++) {
|
||||
i32 id = cast(i32)i;
|
||||
Token token = {Token_Identifier};
|
||||
token.string = builtin_procedures[i].name;
|
||||
Entity *entity = alloc_entity(a, Entity_Builtin, NULL, token, &basic_types[Basic_Invalid]);
|
||||
entity->builtin.id = id;
|
||||
add_global_entity(entity);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
void init_checker(Checker *c, Parser *parser) {
|
||||
gbAllocator a = gb_heap_allocator();
|
||||
|
||||
c->parser = parser;
|
||||
map_init(&c->types, gb_heap_allocator());
|
||||
map_init(&c->definitions, gb_heap_allocator());
|
||||
map_init(&c->uses, gb_heap_allocator());
|
||||
map_init(&c->scopes, gb_heap_allocator());
|
||||
c->sizes.word_size = 8;
|
||||
c->sizes.max_align = 8;
|
||||
|
||||
map_init(&c->untyped, a);
|
||||
|
||||
c->file_scope = make_scope(global_scope);
|
||||
c->curr_scope = c->file_scope;
|
||||
|
||||
gb_array_init(c->procedure_stack, a);
|
||||
|
||||
// NOTE(bill): Is this big enough or too small?
|
||||
isize entity_arena_size = 2 * gb_size_of(Entity) * gb_array_count(c->parser->tokens);
|
||||
gb_arena_init_from_allocator(&c->entity_arena, a, entity_arena_size);
|
||||
}
|
||||
|
||||
void destroy_checker(Checker *c) {
|
||||
map_destroy(&c->types);
|
||||
map_destroy(&c->definitions);
|
||||
map_destroy(&c->uses);
|
||||
map_destroy(&c->scopes);
|
||||
map_destroy(&c->untyped);
|
||||
destroy_scope(c->file_scope);
|
||||
gb_array_free(c->procedure_stack);
|
||||
gb_arena_free(&c->entity_arena);
|
||||
}
|
||||
|
||||
#define print_checker_error(p, token, fmt, ...) print_checker_error_(p, __FUNCTION__, token, fmt, ##__VA_ARGS__)
|
||||
void print_checker_error_(Checker *c, char *function, Token token, char *fmt, ...) {
|
||||
va_list va;
|
||||
|
||||
// NOTE(bill): Duplicate error, skip it
|
||||
if (c->error_prev_line == token.line && c->error_prev_column == token.column) {
|
||||
goto error;
|
||||
}
|
||||
c->error_prev_line = token.line;
|
||||
c->error_prev_column = token.column;
|
||||
|
||||
#if 0
|
||||
gb_printf_err("%s()\n", function);
|
||||
#endif
|
||||
va_start(va, fmt);
|
||||
gb_printf_err("%s(%td:%td) %s\n",
|
||||
c->parser->tokenizer.fullpath, token.line, token.column,
|
||||
gb_bprintf_va(fmt, va));
|
||||
va_end(va);
|
||||
|
||||
error:
|
||||
c->error_count++;
|
||||
// NOTE(bill): If there are too many errors, just quit
|
||||
if (c->error_count > MAX_CHECKER_ERROR_COUNT) {
|
||||
gb_exit(1);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
Entity *entity_of_identifier(Checker *c, AstNode *identifier) {
|
||||
GB_ASSERT(identifier->kind == AstNode_Identifier);
|
||||
Entity **found = map_get(&c->definitions, hash_pointer(identifier));
|
||||
if (found)
|
||||
return *found;
|
||||
|
||||
found = map_get(&c->uses, hash_pointer(identifier));
|
||||
if (found)
|
||||
return *found;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
Type *type_of_expression(Checker *c, AstNode *expression) {
|
||||
TypeAndValue *found = map_get(&c->types, hash_pointer(expression));
|
||||
if (found)
|
||||
return found->type;
|
||||
if (expression->kind == AstNode_Identifier) {
|
||||
Entity *entity = entity_of_identifier(c, expression);
|
||||
if (entity)
|
||||
return entity->type;
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
|
||||
void add_untyped(Checker *c, AstNode *expression, b32 lhs, AddressingMode mode, Type *basic_type, Value value) {
|
||||
map_set(&c->untyped, hash_pointer(expression), make_expression_info(lhs, mode, basic_type, value));
|
||||
}
|
||||
|
||||
|
||||
void add_type_and_value(Checker *c, AstNode *expression, AddressingMode mode, Type *type, Value value) {
|
||||
GB_ASSERT(expression != NULL);
|
||||
GB_ASSERT(type != NULL);
|
||||
if (mode == Addressing_Invalid)
|
||||
return;
|
||||
|
||||
if (mode == Addressing_Constant) {
|
||||
GB_ASSERT(value.kind != Value_Invalid);
|
||||
GB_ASSERT(type == &basic_types[Basic_Invalid] || is_type_constant_type(type));
|
||||
}
|
||||
|
||||
TypeAndValue tv = {};
|
||||
tv.type = type;
|
||||
tv.value = value;
|
||||
map_set(&c->types, hash_pointer(expression), tv);
|
||||
}
|
||||
|
||||
void add_entity_definition(Checker *c, AstNode *identifier, Entity *entity) {
|
||||
GB_ASSERT(identifier != NULL);
|
||||
GB_ASSERT(identifier->kind == AstNode_Identifier);
|
||||
u64 key = hash_pointer(identifier);
|
||||
map_set(&c->definitions, key, entity);
|
||||
}
|
||||
|
||||
void add_entity(Checker *c, Scope *scope, AstNode *identifier, Entity *entity) {
|
||||
Entity *insert_entity = scope_insert_entity(scope, entity);
|
||||
if (insert_entity) {
|
||||
print_checker_error(c, entity->token, "Redeclared entity in this scope: %.*s", LIT(entity->token.string));
|
||||
return;
|
||||
}
|
||||
if (identifier)
|
||||
add_entity_definition(c, identifier, entity);
|
||||
}
|
||||
|
||||
void add_entity_use(Checker *c, AstNode *identifier, Entity *entity) {
|
||||
GB_ASSERT(identifier != NULL);
|
||||
GB_ASSERT(identifier->kind == AstNode_Identifier);
|
||||
u64 key = hash_pointer(identifier);
|
||||
map_set(&c->uses, key, entity);
|
||||
}
|
||||
|
||||
void add_scope(Checker *c, AstNode *node, Scope *scope) {
|
||||
GB_ASSERT(node != NULL);
|
||||
GB_ASSERT(scope != NULL);
|
||||
map_set(&c->scopes, hash_pointer(node), scope);
|
||||
}
|
||||
|
||||
|
||||
void check_open_scope(Checker *c, AstNode *statement) {
|
||||
Scope *scope = make_scope(c->curr_scope);
|
||||
add_scope(c, statement, scope);
|
||||
c->curr_scope = scope;
|
||||
}
|
||||
|
||||
void check_close_scope(Checker *c) {
|
||||
c->curr_scope = c->curr_scope->parent;
|
||||
}
|
||||
|
||||
void push_procedure(Checker *c, Type *procedure_type) {
|
||||
gb_array_append(c->procedure_stack, procedure_type);
|
||||
}
|
||||
|
||||
void pop_procedure(Checker *c) {
|
||||
gb_array_pop(c->procedure_stack);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
Entity *make_entity_variable(Checker *c, Scope *parent, Token token, Type *type) {
|
||||
Entity *entity = alloc_entity(gb_arena_allocator(&c->entity_arena), Entity_Variable, parent, token, type);
|
||||
return entity;
|
||||
}
|
||||
|
||||
Entity *make_entity_constant(Checker *c, Scope *parent, Token token, Type *type, Value value) {
|
||||
Entity *entity = alloc_entity(gb_arena_allocator(&c->entity_arena), Entity_Constant, parent, token, type);
|
||||
entity->constant.value = value;
|
||||
return entity;
|
||||
}
|
||||
|
||||
Entity *make_entity_type_name(Checker *c, Scope *parent, Token token, Type *type) {
|
||||
Entity *entity = alloc_entity(gb_arena_allocator(&c->entity_arena), Entity_TypeName, parent, token, type);
|
||||
return entity;
|
||||
}
|
||||
|
||||
Entity *make_entity_param(Checker *c, Scope *parent, Token token, Type *type) {
|
||||
Entity *entity = alloc_entity(gb_arena_allocator(&c->entity_arena), Entity_Variable, parent, token, type);
|
||||
entity->variable.used = true;
|
||||
return entity;
|
||||
}
|
||||
|
||||
Entity *make_entity_field(Checker *c, Scope *parent, Token token, Type *type) {
|
||||
Entity *entity = alloc_entity(gb_arena_allocator(&c->entity_arena), Entity_Variable, parent, token, type);
|
||||
entity->variable.is_field = true;
|
||||
return entity;
|
||||
}
|
||||
|
||||
Entity *make_entity_procedure(Checker *c, Scope *parent, Token token, Type *signature_type) {
|
||||
Entity *entity = alloc_entity(gb_arena_allocator(&c->entity_arena), Entity_Procedure, parent, token, signature_type);
|
||||
return entity;
|
||||
}
|
||||
|
||||
Entity *make_entity_builtin(Checker *c, Scope *parent, Token token, Type *type, i32 id) {
|
||||
Entity *entity = alloc_entity(gb_arena_allocator(&c->entity_arena), Entity_Builtin, parent, token, type);
|
||||
entity->builtin.id = id;
|
||||
return entity;
|
||||
}
|
||||
|
||||
Entity *make_entity_dummy_variable(Checker *c, Token token) {
|
||||
token.string = make_string("_");
|
||||
return make_entity_variable(c, c->file_scope, token, NULL);
|
||||
}
|
||||
|
||||
66
src/checker/entity.cpp
Normal file
66
src/checker/entity.cpp
Normal file
@@ -0,0 +1,66 @@
|
||||
struct Scope;
|
||||
struct Checker;
|
||||
|
||||
enum EntityKind {
|
||||
Entity_Invalid,
|
||||
|
||||
Entity_Constant,
|
||||
Entity_Variable,
|
||||
Entity_TypeName,
|
||||
Entity_Procedure,
|
||||
Entity_Builtin,
|
||||
|
||||
Entity_Count,
|
||||
};
|
||||
|
||||
typedef i64 EntityGuid;
|
||||
|
||||
struct Entity {
|
||||
EntityKind kind;
|
||||
EntityGuid guid;
|
||||
|
||||
Scope *parent;
|
||||
Token token;
|
||||
Type *type;
|
||||
|
||||
union {
|
||||
struct { Value value; } constant;
|
||||
struct {
|
||||
b8 visited;
|
||||
b8 is_field;
|
||||
b8 used;
|
||||
} variable;
|
||||
struct {} type_name;
|
||||
struct {} procedure;
|
||||
struct { i32 id; } builtin;
|
||||
};
|
||||
};
|
||||
|
||||
gb_global gbAtomic64 entity_guid_counter = {0};
|
||||
|
||||
EntityGuid next_entity_guid(void) {
|
||||
return cast(EntityGuid)gb_atomic64_fetch_add(&entity_guid_counter, 1);
|
||||
}
|
||||
|
||||
Entity *alloc_entity(gbAllocator a, EntityKind kind, Scope *parent, Token token, Type *type) {
|
||||
Entity *entity = gb_alloc_item(a, Entity);
|
||||
entity->kind = kind;
|
||||
entity->guid = next_entity_guid();
|
||||
entity->parent = parent;
|
||||
entity->token = token;
|
||||
entity->type = type;
|
||||
return entity;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
Entity *make_entity_variable (Checker *c, Scope *parent, Token token, Type *type);
|
||||
Entity *make_entity_constant (Checker *c, Scope *parent, Token token, Type *type, Value value);
|
||||
Entity *make_entity_type_name(Checker *c, Scope *parent, Token token, Type *type);
|
||||
Entity *make_entity_param (Checker *c, Scope *parent, Token token, Type *type);
|
||||
Entity *make_entity_field (Checker *c, Scope *parent, Token token, Type *type);
|
||||
Entity *make_entity_procedure(Checker *c, Scope *parent, Token token, Type *signature_type);
|
||||
Entity *make_entity_builtin (Checker *c, Scope *parent, Token token, Type *type, i32 id);
|
||||
|
||||
Entity *make_entity_dummy_variable(Checker *c, Token token);
|
||||
1709
src/checker/expression.cpp
Normal file
1709
src/checker/expression.cpp
Normal file
File diff suppressed because it is too large
Load Diff
695
src/checker/statements.cpp
Normal file
695
src/checker/statements.cpp
Normal file
@@ -0,0 +1,695 @@
|
||||
//
|
||||
|
||||
b32 check_assignable_to(Checker *c, Operand *operand, Type *type) {
|
||||
if (operand->mode == Addressing_Invalid ||
|
||||
type == &basic_types[Basic_Invalid]) {
|
||||
return true;
|
||||
}
|
||||
|
||||
Type *s = operand->type;
|
||||
|
||||
if (are_types_identical(s, type))
|
||||
return true;
|
||||
|
||||
Type *sb = get_base_type(s);
|
||||
Type *tb = get_base_type(type);
|
||||
|
||||
if (is_type_untyped(sb)) {
|
||||
switch (tb->kind) {
|
||||
case Type_Basic:
|
||||
if (operand->mode == Addressing_Constant)
|
||||
return check_value_is_expressible(c, operand->value, tb, NULL);
|
||||
if (sb->kind == Type_Basic)
|
||||
return sb->basic.kind == Basic_UntypedBool && is_type_boolean(tb);
|
||||
break;
|
||||
case Type_Pointer:
|
||||
return sb->basic.kind == Basic_UntypedPointer;
|
||||
}
|
||||
}
|
||||
|
||||
if (are_types_identical(sb, tb) && (!is_type_named(sb) || !is_type_named(tb)))
|
||||
return true;
|
||||
|
||||
if (is_type_pointer(sb) && is_type_rawptr(tb))
|
||||
return true;
|
||||
|
||||
if (is_type_rawptr(sb) && is_type_pointer(tb))
|
||||
return true;
|
||||
|
||||
if (sb->kind == Type_Array && tb->kind == Type_Array) {
|
||||
if (are_types_identical(sb->array.element, tb->array.element)) {
|
||||
if (tb->array.count == 0) // NOTE(bill): Not static size
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
// NOTE(bill): `content_name` is for debugging
|
||||
void check_assignment(Checker *c, Operand *operand, Type *type, String context_name) {
|
||||
check_not_tuple(c, operand);
|
||||
if (operand->mode == Addressing_Invalid)
|
||||
return;
|
||||
|
||||
if (is_type_untyped(operand->type)) {
|
||||
Type *target_type = type;
|
||||
|
||||
if (type == NULL)
|
||||
target_type = default_type(operand->type);
|
||||
convert_to_typed(c, operand, target_type);
|
||||
if (operand->mode == Addressing_Invalid)
|
||||
return;
|
||||
}
|
||||
|
||||
if (type != NULL) {
|
||||
if (!check_assignable_to(c, operand, type)) {
|
||||
gbString type_string = type_to_string(type);
|
||||
gbString op_type_string = type_to_string(operand->type);
|
||||
defer (gb_string_free(type_string));
|
||||
defer (gb_string_free(op_type_string));
|
||||
|
||||
// TODO(bill): is this a good enough error message?
|
||||
print_checker_error(c, ast_node_token(operand->expression),
|
||||
"Cannot assign value `%.*s` of type `%s` to `%s` in %.*s",
|
||||
LIT(ast_node_token(operand->expression).string),
|
||||
op_type_string,
|
||||
type_string,
|
||||
LIT(context_name));
|
||||
|
||||
operand->mode = Addressing_Invalid;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Type *check_assign_variable(Checker *c, Operand *x, AstNode *lhs) {
|
||||
if (x->mode == Addressing_Invalid ||
|
||||
x->type == &basic_types[Basic_Invalid]) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
AstNode *node = unparen_expression(lhs);
|
||||
|
||||
// NOTE(bill): Ignore assignments to `_`
|
||||
if (node->kind == AstNode_Identifier &&
|
||||
are_strings_equal(node->identifier.token.string, make_string("_"))) {
|
||||
add_entity_definition(c, node, NULL);
|
||||
check_assignment(c, x, NULL, make_string("assignment to `_` identifier"));
|
||||
if (x->mode == Addressing_Invalid)
|
||||
return NULL;
|
||||
return x->type;
|
||||
}
|
||||
|
||||
Entity *e = NULL;
|
||||
b32 used = false;
|
||||
if (node->kind == AstNode_Identifier) {
|
||||
scope_lookup_parent_entity(c->curr_scope, node->identifier.token.string,
|
||||
NULL, &e);
|
||||
if (e != NULL && e->kind == Entity_Variable) {
|
||||
used = e->variable.used; // TODO(bill): Make backup just in case
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Operand y = {Addressing_Invalid};
|
||||
check_expression(c, &y, lhs);
|
||||
if (e) e->variable.used = used;
|
||||
|
||||
if (y.mode == Addressing_Invalid ||
|
||||
y.type == &basic_types[Basic_Invalid]) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
switch (y.mode) {
|
||||
case Addressing_Variable:
|
||||
break;
|
||||
case Addressing_Invalid:
|
||||
return NULL;
|
||||
default: {
|
||||
if (y.expression->kind == AstNode_SelectorExpression) {
|
||||
// NOTE(bill): Extra error checks
|
||||
Operand z = {Addressing_Invalid};
|
||||
check_expression(c, &z, y.expression->selector_expression.operand);
|
||||
}
|
||||
|
||||
gbString str = expression_to_string(y.expression);
|
||||
defer (gb_string_free(str));
|
||||
print_checker_error(c, ast_node_token(y.expression),
|
||||
"Cannot assign to `%s`", str);
|
||||
} break;
|
||||
}
|
||||
|
||||
check_assignment(c, x, y.type, make_string("assignment"));
|
||||
if (x->mode == Addressing_Invalid)
|
||||
return NULL;
|
||||
|
||||
return x->type;
|
||||
}
|
||||
|
||||
void check_assign_variables(Checker *c,
|
||||
AstNode *lhs_list, isize lhs_count,
|
||||
AstNode *rhs_list, isize rhs_count) {
|
||||
Operand operand = {Addressing_Invalid};
|
||||
AstNode *lhs = lhs_list, *rhs = rhs_list;
|
||||
for (;
|
||||
lhs != NULL && rhs != NULL;
|
||||
lhs = lhs->next, rhs = rhs->next) {
|
||||
check_multi_expression(c, &operand, rhs);
|
||||
if (operand.type->kind != Type_Tuple) {
|
||||
check_assign_variable(c, &operand, lhs);
|
||||
} else {
|
||||
auto *tuple = &operand.type->tuple;
|
||||
for (isize i = 0;
|
||||
i < tuple->variable_count && lhs != NULL;
|
||||
i++, lhs = lhs->next) {
|
||||
// TODO(bill): More error checking
|
||||
operand.type = tuple->variables[i]->type;
|
||||
check_assign_variable(c, &operand, lhs);
|
||||
}
|
||||
if (lhs == NULL)
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// NOTE(bill): `content_name` is for debugging
|
||||
Type *check_init_variable(Checker *c, Entity *e, Operand *operand, String context_name) {
|
||||
if (operand->mode == Addressing_Invalid ||
|
||||
operand->type == &basic_types[Basic_Invalid] ||
|
||||
e->type == &basic_types[Basic_Invalid]) {
|
||||
if (e->type == NULL)
|
||||
e->type = &basic_types[Basic_Invalid];
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (e->type == NULL) {
|
||||
// NOTE(bill): Use the type of the operand
|
||||
Type *t = operand->type;
|
||||
if (is_type_untyped(t)) {
|
||||
if (t == &basic_types[Basic_Invalid]) {
|
||||
print_checker_error(c, e->token, "Use of untyped thing in %.*s", LIT(context_name));
|
||||
e->type = &basic_types[Basic_Invalid];
|
||||
return NULL;
|
||||
}
|
||||
t = default_type(t);
|
||||
}
|
||||
e->type = t;
|
||||
}
|
||||
|
||||
check_assignment(c, operand, e->type, context_name);
|
||||
if (operand->mode == Addressing_Invalid)
|
||||
return NULL;
|
||||
|
||||
return e->type;
|
||||
}
|
||||
|
||||
void check_init_variables(Checker *c, Entity **lhs, isize lhs_count, AstNode *init_list, isize init_count, String context_name) {
|
||||
if (lhs_count == 0 && init_count == 0)
|
||||
return;
|
||||
|
||||
isize i = 0;
|
||||
AstNode *rhs = init_list;
|
||||
for (;
|
||||
i < lhs_count && rhs != NULL;
|
||||
i++, rhs = rhs->next) {
|
||||
Operand operand = {};
|
||||
check_multi_expression(c, &operand, rhs);
|
||||
if (operand.type->kind != Type_Tuple) {
|
||||
check_init_variable(c, lhs[i], &operand, context_name);
|
||||
} else {
|
||||
auto *tuple = &operand.type->tuple;
|
||||
for (isize j = 0;
|
||||
j < tuple->variable_count && i < lhs_count;
|
||||
j++, i++) {
|
||||
Type *type = tuple->variables[j]->type;
|
||||
operand.type = type;
|
||||
check_init_variable(c, lhs[i], &operand, context_name);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (i < lhs_count) {
|
||||
if (lhs[i]->type == NULL)
|
||||
print_checker_error(c, lhs[i]->token, "Too few values on the right hand side of the declaration");
|
||||
} else if (rhs != NULL) {
|
||||
print_checker_error(c, ast_node_token(rhs), "Too many values on the right hand side of the declaration");
|
||||
}
|
||||
}
|
||||
|
||||
void check_init_constant(Checker *c, Entity *e, Operand *operand) {
|
||||
if (operand->mode == Addressing_Invalid ||
|
||||
operand->type == &basic_types[Basic_Invalid] ||
|
||||
e->type == &basic_types[Basic_Invalid]) {
|
||||
|
||||
if (e->type == NULL)
|
||||
e->type = &basic_types[Basic_Invalid];
|
||||
return;
|
||||
}
|
||||
|
||||
if (operand->mode != Addressing_Constant) {
|
||||
// TODO(bill): better error
|
||||
print_checker_error(c, ast_node_token(operand->expression),
|
||||
"`%.*s` is not a constant", LIT(ast_node_token(operand->expression).string));
|
||||
if (e->type == NULL)
|
||||
e->type = &basic_types[Basic_Invalid];
|
||||
return;
|
||||
}
|
||||
if (!is_type_constant_type(operand->type)) {
|
||||
// NOTE(bill): no need to free string as it's panicking
|
||||
GB_PANIC("Type `%s` not constant!!!", type_to_string(operand->type));
|
||||
}
|
||||
|
||||
if (e->type == NULL) // NOTE(bill): type inference
|
||||
e->type = operand->type;
|
||||
|
||||
check_assignment(c, operand, e->type, make_string("constant declaration"));
|
||||
if (operand->mode == Addressing_Invalid)
|
||||
return;
|
||||
|
||||
e->constant.value = operand->value;
|
||||
}
|
||||
|
||||
void check_constant_declaration(Checker *c, Entity *e, AstNode *type_expression, AstNode *init_expression) {
|
||||
GB_ASSERT(e->type == NULL);
|
||||
|
||||
if (e->variable.visited) {
|
||||
e->type = &basic_types[Basic_Invalid];
|
||||
return;
|
||||
}
|
||||
e->variable.visited = true;
|
||||
|
||||
if (type_expression) {
|
||||
Type *t = check_type(c, type_expression);
|
||||
if (!is_type_constant_type(t)) {
|
||||
gbString str = type_to_string(t);
|
||||
defer (gb_string_free(str));
|
||||
print_checker_error(c, ast_node_token(type_expression),
|
||||
"Invalid constant type `%s`", str);
|
||||
e->type = &basic_types[Basic_Invalid];
|
||||
return;
|
||||
}
|
||||
e->type = t;
|
||||
}
|
||||
|
||||
Operand operand = {Addressing_Invalid};
|
||||
if (init_expression)
|
||||
check_expression(c, &operand, init_expression);
|
||||
check_init_constant(c, e, &operand);
|
||||
}
|
||||
|
||||
void check_statement(Checker *c, AstNode *node);
|
||||
|
||||
void check_statement_list(Checker *c, AstNode *node) {
|
||||
for (; node != NULL; node = node->next)
|
||||
check_statement(c, node);
|
||||
}
|
||||
|
||||
b32 check_is_terminating(Checker *c, AstNode *node);
|
||||
|
||||
b32 check_is_terminating_list(Checker *c, AstNode *node_list) {
|
||||
AstNode *end_of_list = node_list;
|
||||
for (; end_of_list != NULL; end_of_list = end_of_list->next) {
|
||||
if (end_of_list->next == NULL)
|
||||
break;
|
||||
}
|
||||
|
||||
for (AstNode *node = end_of_list; node != NULL; node = node->prev) {
|
||||
if (node->kind == AstNode_EmptyStatement)
|
||||
continue;
|
||||
return check_is_terminating(c, node);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
b32 check_is_terminating(Checker *c, AstNode *node) {
|
||||
switch (node->kind) {
|
||||
case AstNode_BlockStatement:
|
||||
return check_is_terminating_list(c, node->block_statement.list);
|
||||
|
||||
case AstNode_ExpressionStatement:
|
||||
return check_is_terminating(c, node->expression_statement.expression);
|
||||
|
||||
case AstNode_ReturnStatement:
|
||||
return true;
|
||||
|
||||
case AstNode_IfStatement:
|
||||
if (node->if_statement.else_statement != NULL) {
|
||||
if (check_is_terminating(c, node->if_statement.body) &&
|
||||
check_is_terminating(c, node->if_statement.else_statement))
|
||||
return true;
|
||||
}
|
||||
break;
|
||||
|
||||
case AstNode_ForStatement:
|
||||
if (node->for_statement.cond == NULL) {
|
||||
return true;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void check_statement(Checker *c, AstNode *node) {
|
||||
switch (node->kind) {
|
||||
case AstNode_EmptyStatement: break;
|
||||
case AstNode_BadStatement: break;
|
||||
case AstNode_BadDeclaration: break;
|
||||
|
||||
case AstNode_ExpressionStatement: {
|
||||
Operand operand = {Addressing_Invalid};
|
||||
ExpressionKind kind = check_expression_base(c, &operand, node->expression_statement.expression);
|
||||
switch (operand.mode) {
|
||||
case Addressing_Type:
|
||||
print_checker_error(c, ast_node_token(node), "Is not an expression");
|
||||
break;
|
||||
default:
|
||||
if (kind == Expression_Statement)
|
||||
return;
|
||||
print_checker_error(c, ast_node_token(node), "Expression is not used");
|
||||
break;
|
||||
}
|
||||
} break;
|
||||
|
||||
case AstNode_IncDecStatement: {
|
||||
Token op = {};
|
||||
auto *s = &node->inc_dec_statement;
|
||||
op = s->op;
|
||||
switch (s->op.kind) {
|
||||
case Token_Increment:
|
||||
op.kind = Token_Add;
|
||||
op.string.len = 1;
|
||||
break;
|
||||
case Token_Decrement:
|
||||
op.kind = Token_Sub;
|
||||
op.string.len = 1;
|
||||
break;
|
||||
default:
|
||||
print_checker_error(c, s->op, "Unknown inc/dec operation %.*s", LIT(s->op.string));
|
||||
return;
|
||||
}
|
||||
|
||||
Operand operand = {Addressing_Invalid};
|
||||
check_expression(c, &operand, s->expression);
|
||||
if (operand.mode == Addressing_Invalid)
|
||||
return;
|
||||
if (!is_type_numeric(operand.type)) {
|
||||
print_checker_error(c, s->op, "Non numeric type");
|
||||
return;
|
||||
}
|
||||
|
||||
AstNode basic_lit = {AstNode_BasicLiteral};
|
||||
basic_lit.basic_literal = s->op;
|
||||
basic_lit.basic_literal.kind = Token_Integer;
|
||||
basic_lit.basic_literal.string = make_string("1");
|
||||
AstNode be = {AstNode_BinaryExpression};
|
||||
be.binary_expression.op = op;
|
||||
be.binary_expression.left = s->expression;;
|
||||
be.binary_expression.right = &basic_lit;
|
||||
check_binary_expression(c, &operand, &be);
|
||||
|
||||
} break;
|
||||
|
||||
case AstNode_AssignStatement:
|
||||
switch (node->assign_statement.op.kind) {
|
||||
case Token_Eq:
|
||||
if (node->assign_statement.lhs_count == 0) {
|
||||
print_checker_error(c, node->assign_statement.op, "Missing lhs in assignment statement");
|
||||
return;
|
||||
}
|
||||
check_assign_variables(c,
|
||||
node->assign_statement.lhs_list, node->assign_statement.lhs_count,
|
||||
node->assign_statement.rhs_list, node->assign_statement.rhs_count);
|
||||
break;
|
||||
|
||||
default: {
|
||||
Token op = node->assign_statement.op;
|
||||
if (node->assign_statement.lhs_count != 1 ||
|
||||
node->assign_statement.rhs_count != 1) {
|
||||
print_checker_error(c, op,
|
||||
"assignment operation `%.*s` requires single-valued expressions", LIT(op.string));
|
||||
return;
|
||||
}
|
||||
// TODO(bill): Check if valid assignment operator
|
||||
Operand operand = {Addressing_Invalid};
|
||||
AstNode be = {AstNode_BinaryExpression};
|
||||
be.binary_expression.op = op;
|
||||
// NOTE(bill): Only use the first one will be used
|
||||
be.binary_expression.left = node->assign_statement.lhs_list;
|
||||
be.binary_expression.right = node->assign_statement.rhs_list;
|
||||
|
||||
check_binary_expression(c, &operand, &be);
|
||||
if (operand.mode == Addressing_Invalid)
|
||||
return;
|
||||
// NOTE(bill): Only use the first one will be used
|
||||
check_assign_variable(c, &operand, node->assign_statement.lhs_list);
|
||||
} break;
|
||||
}
|
||||
break;
|
||||
|
||||
case AstNode_BlockStatement:
|
||||
check_open_scope(c, node);
|
||||
check_statement_list(c, node->block_statement.list);
|
||||
check_close_scope(c);
|
||||
break;
|
||||
|
||||
case AstNode_IfStatement: {
|
||||
Operand operand = {Addressing_Invalid};
|
||||
check_expression(c, &operand, node->if_statement.cond);
|
||||
if (operand.mode != Addressing_Invalid &&
|
||||
!is_type_boolean(operand.type)) {
|
||||
print_checker_error(c, ast_node_token(node->if_statement.cond),
|
||||
"Non-boolean condition in `if` statement");
|
||||
}
|
||||
check_statement(c, node->if_statement.body);
|
||||
|
||||
if (node->if_statement.else_statement) {
|
||||
switch (node->if_statement.else_statement->kind) {
|
||||
case AstNode_IfStatement:
|
||||
case AstNode_BlockStatement:
|
||||
check_statement(c, node->if_statement.else_statement);
|
||||
break;
|
||||
default:
|
||||
print_checker_error(c, ast_node_token(node->if_statement.else_statement),
|
||||
"Invalid `else` statement in `if` statement");
|
||||
break;
|
||||
}
|
||||
}
|
||||
} break;
|
||||
|
||||
case AstNode_ReturnStatement: {
|
||||
auto *rs = &node->return_statement;
|
||||
GB_ASSERT(gb_array_count(c->procedure_stack) > 0);
|
||||
|
||||
if (c->in_defer) {
|
||||
print_checker_error(c, rs->token, "You cannot `return` within a defer statement");
|
||||
// TODO(bill): Should I break here?
|
||||
break;
|
||||
}
|
||||
|
||||
Type *proc_type = c->procedure_stack[gb_array_count(c->procedure_stack)-1];
|
||||
isize result_count = 0;
|
||||
if (proc_type->procedure.results)
|
||||
result_count = proc_type->procedure.results->tuple.variable_count;
|
||||
if (result_count != rs->result_count) {
|
||||
print_checker_error(c, rs->token, "Expected %td return %s, got %td",
|
||||
result_count,
|
||||
(result_count != 1 ? "values" : "value"),
|
||||
rs->result_count);
|
||||
} else if (result_count > 0) {
|
||||
auto *tuple = &proc_type->procedure.results->tuple;
|
||||
check_init_variables(c, tuple->variables, tuple->variable_count,
|
||||
rs->results, rs->result_count, make_string("return statement"));
|
||||
}
|
||||
} break;
|
||||
|
||||
case AstNode_ForStatement: {
|
||||
check_open_scope(c, node);
|
||||
defer (check_close_scope(c));
|
||||
|
||||
check_statement(c, node->for_statement.init);
|
||||
if (node->for_statement.cond) {
|
||||
Operand operand = {Addressing_Invalid};
|
||||
check_expression(c, &operand, node->for_statement.cond);
|
||||
if (operand.mode != Addressing_Invalid &&
|
||||
!is_type_boolean(operand.type)) {
|
||||
print_checker_error(c, ast_node_token(node->for_statement.cond),
|
||||
"Non-boolean condition in `for` statement");
|
||||
}
|
||||
}
|
||||
check_statement(c, node->for_statement.end);
|
||||
check_statement(c, node->for_statement.body);
|
||||
} break;
|
||||
|
||||
case AstNode_DeferStatement: {
|
||||
auto *ds = &node->defer_statement;
|
||||
if (is_ast_node_declaration(ds->statement)) {
|
||||
print_checker_error(c, ds->token, "You cannot defer a declaration");
|
||||
} else {
|
||||
b32 out_in_defer = c->in_defer;
|
||||
c->in_defer = true;
|
||||
check_statement(c, ds->statement);
|
||||
c->in_defer = out_in_defer;
|
||||
}
|
||||
} break;
|
||||
|
||||
|
||||
// Declarations
|
||||
case AstNode_VariableDeclaration: {
|
||||
auto *vd = &node->variable_declaration;
|
||||
gbAllocator allocator = gb_arena_allocator(&c->entity_arena);
|
||||
|
||||
isize entity_count = vd->name_list_count;
|
||||
isize entity_index = 0;
|
||||
Entity **entities = gb_alloc_array(allocator, Entity *, entity_count);
|
||||
switch (vd->kind) {
|
||||
case Declaration_Mutable: {
|
||||
Entity **new_entities = gb_alloc_array(allocator, Entity *, entity_count);
|
||||
isize new_entity_count = 0;
|
||||
|
||||
for (AstNode *name = vd->name_list; name != NULL; name = name->next) {
|
||||
Entity *entity = NULL;
|
||||
Token token = name->identifier.token;
|
||||
if (name->kind == AstNode_Identifier) {
|
||||
String str = token.string;
|
||||
Entity *found = NULL;
|
||||
// NOTE(bill): Ignore assignments to `_`
|
||||
b32 can_be_ignored = are_strings_equal(str, make_string("_"));
|
||||
if (!can_be_ignored) {
|
||||
found = scope_lookup_entity_current(c->curr_scope, str);
|
||||
}
|
||||
if (found == NULL) {
|
||||
entity = make_entity_variable(c, c->curr_scope, token, NULL);
|
||||
if (!can_be_ignored) {
|
||||
new_entities[new_entity_count++] = entity;
|
||||
}
|
||||
add_entity_definition(c, name, entity);
|
||||
} else {
|
||||
entity = found;
|
||||
}
|
||||
} else {
|
||||
print_checker_error(c, token, "A variable declaration must be an identifier");
|
||||
}
|
||||
if (entity == NULL)
|
||||
entity = make_entity_dummy_variable(c, token);
|
||||
entities[entity_index++] = entity;
|
||||
}
|
||||
|
||||
Type *init_type = NULL;
|
||||
if (vd->type_expression) {
|
||||
init_type = check_type(c, vd->type_expression, NULL);
|
||||
if (init_type == NULL)
|
||||
init_type = &basic_types[Basic_Invalid];
|
||||
}
|
||||
|
||||
for (isize i = 0; i < entity_count; i++) {
|
||||
Entity *e = entities[i];
|
||||
GB_ASSERT(e != NULL);
|
||||
if (e->variable.visited) {
|
||||
e->type = &basic_types[Basic_Invalid];
|
||||
continue;
|
||||
}
|
||||
e->variable.visited = true;
|
||||
|
||||
if (e->type == NULL)
|
||||
e->type = init_type;
|
||||
}
|
||||
|
||||
|
||||
check_init_variables(c, entities, entity_count, vd->value_list, vd->value_list_count, make_string("variable declaration"));
|
||||
|
||||
AstNode *name = vd->name_list;
|
||||
for (isize i = 0; i < new_entity_count; i++, name = name->next) {
|
||||
add_entity(c, c->curr_scope, name, new_entities[i]);
|
||||
}
|
||||
|
||||
} break;
|
||||
|
||||
case Declaration_Immutable: {
|
||||
for (AstNode *name = vd->name_list, *value = vd->value_list;
|
||||
name != NULL && value != NULL;
|
||||
name = name->next, value = value->next) {
|
||||
GB_ASSERT(name->kind == AstNode_Identifier);
|
||||
Value v = {Value_Invalid};
|
||||
Entity *e = make_entity_constant(c, c->curr_scope, name->identifier.token, NULL, v);
|
||||
entities[entity_index++] = e;
|
||||
check_constant_declaration(c, e, vd->type_expression, value);
|
||||
}
|
||||
|
||||
isize lhs_count = vd->name_list_count;
|
||||
isize rhs_count = vd->value_list_count;
|
||||
|
||||
// TODO(bill): Better error messages or is this good enough?
|
||||
if (rhs_count == 0 && vd->type_expression == NULL) {
|
||||
print_checker_error(c, ast_node_token(node), "Missing type or initial expression");
|
||||
} else if (lhs_count < rhs_count) {
|
||||
print_checker_error(c, ast_node_token(node), "Extra initial expression");
|
||||
}
|
||||
|
||||
AstNode *name = vd->name_list;
|
||||
for (isize i = 0; i < entity_count; i++, name = name->next) {
|
||||
add_entity(c, c->curr_scope, name, entities[i]);
|
||||
}
|
||||
} break;
|
||||
|
||||
default:
|
||||
print_checker_error(c, ast_node_token(node), "Unknown variable declaration kind. Probably an invalid AST.");
|
||||
return;
|
||||
}
|
||||
} break;
|
||||
|
||||
|
||||
case AstNode_ProcedureDeclaration: {
|
||||
auto *pd = &node->procedure_declaration;
|
||||
GB_ASSERT_MSG(pd->kind == Declaration_Immutable, "Mutable/temp procedures are not yet implemented");
|
||||
// TODO(bill): Should this be the case? And are the scopes correct?
|
||||
// TODO(bill): Should procedures just have global scope?
|
||||
Entity *e = make_entity_procedure(c, c->curr_scope, pd->name->identifier.token, NULL);
|
||||
add_entity(c, c->curr_scope, pd->name, e);
|
||||
|
||||
Type *proc_type = make_type_procedure(e->parent, NULL, 0, NULL, 0);
|
||||
e->type = proc_type;
|
||||
|
||||
check_open_scope(c, pd->procedure_type);
|
||||
{
|
||||
check_procedure_type(c, proc_type, pd->procedure_type);
|
||||
if (pd->body) {
|
||||
GB_ASSERT(pd->body->kind == AstNode_BlockStatement);
|
||||
|
||||
push_procedure(c, proc_type);
|
||||
check_statement_list(c, pd->body->block_statement.list);
|
||||
if (pd->procedure_type->procedure_type.result_count > 0) {
|
||||
if (!check_is_terminating(c, pd->body)) {
|
||||
print_checker_error(c, pd->body->block_statement.close, "Missing return statement at the end of the procedure");
|
||||
}
|
||||
}
|
||||
pop_procedure(c);
|
||||
} else if (pd->tag) {
|
||||
GB_ASSERT(pd->tag->kind == AstNode_TagExpression);
|
||||
|
||||
String tag_name = pd->tag->tag_expression.name.string;
|
||||
if (gb_strncmp("foreign", cast(char *)tag_name.text, tag_name.len) == 0) {
|
||||
// NOTE(bill): Foreign procedure (linking stage)
|
||||
}
|
||||
}
|
||||
}
|
||||
check_close_scope(c);
|
||||
|
||||
} break;
|
||||
|
||||
case AstNode_TypeDeclaration: {
|
||||
auto *td = &node->type_declaration;
|
||||
AstNode *name = td->name;
|
||||
Entity *e = make_entity_type_name(c, c->curr_scope, name->identifier.token, NULL);
|
||||
add_entity(c, c->curr_scope, name, e);
|
||||
|
||||
e->type = make_type_named(e->token.string, NULL, e);
|
||||
check_type(c, td->type_expression, e->type);
|
||||
// NOTE(bill): Prevent recursive definition
|
||||
set_base_type(e->type, get_base_type(e->type));
|
||||
|
||||
} break;
|
||||
}
|
||||
}
|
||||
576
src/checker/type.cpp
Normal file
576
src/checker/type.cpp
Normal file
@@ -0,0 +1,576 @@
|
||||
struct Scope;
|
||||
|
||||
enum BasicKind {
|
||||
Basic_Invalid,
|
||||
Basic_bool,
|
||||
Basic_i8,
|
||||
Basic_i16,
|
||||
Basic_i32,
|
||||
Basic_i64,
|
||||
Basic_u8,
|
||||
Basic_u16,
|
||||
Basic_u32,
|
||||
Basic_u64,
|
||||
Basic_f32,
|
||||
Basic_f64,
|
||||
Basic_int,
|
||||
Basic_uint,
|
||||
Basic_rawptr,
|
||||
Basic_string,
|
||||
Basic_UntypedBool,
|
||||
Basic_UntypedInteger,
|
||||
Basic_UntypedFloat,
|
||||
Basic_UntypedPointer,
|
||||
Basic_UntypedString,
|
||||
Basic_UntypedRune,
|
||||
|
||||
Basic_Count,
|
||||
|
||||
Basic_rune = Basic_i32,
|
||||
};
|
||||
|
||||
enum BasicFlag : u32 {
|
||||
BasicFlag_Boolean = GB_BIT(0),
|
||||
BasicFlag_Integer = GB_BIT(1),
|
||||
BasicFlag_Unsigned = GB_BIT(2),
|
||||
BasicFlag_Float = GB_BIT(3),
|
||||
BasicFlag_Pointer = GB_BIT(4),
|
||||
BasicFlag_String = GB_BIT(5),
|
||||
BasicFlag_Untyped = GB_BIT(6),
|
||||
|
||||
BasicFlag_Numeric = BasicFlag_Integer | BasicFlag_Float,
|
||||
BasicFlag_Ordered = BasicFlag_Numeric | BasicFlag_String | BasicFlag_Pointer,
|
||||
BasicFlag_ConstantType = BasicFlag_Boolean | BasicFlag_Numeric | BasicFlag_String | BasicFlag_Pointer,
|
||||
};
|
||||
|
||||
struct BasicType {
|
||||
BasicKind kind;
|
||||
u32 flags;
|
||||
String name;
|
||||
};
|
||||
|
||||
|
||||
enum TypeKind {
|
||||
Type_Invalid,
|
||||
|
||||
Type_Basic,
|
||||
Type_Array,
|
||||
Type_Structure,
|
||||
Type_Pointer,
|
||||
Type_Named,
|
||||
Type_Tuple,
|
||||
Type_Procedure,
|
||||
|
||||
Type_Count,
|
||||
};
|
||||
struct Type {
|
||||
TypeKind kind;
|
||||
union {
|
||||
BasicType basic;
|
||||
struct {
|
||||
Type *element;
|
||||
i64 count;
|
||||
} array;
|
||||
struct {
|
||||
// Theses are arrays
|
||||
Entity **fields; // Entity_Variable
|
||||
isize field_count; // == offset_count
|
||||
i64 * offsets;
|
||||
b32 offsets_set;
|
||||
} structure;
|
||||
struct { Type *element; } pointer;
|
||||
struct {
|
||||
String name;
|
||||
Type * base;
|
||||
Entity *type_name; // Entity_TypeName
|
||||
} named;
|
||||
struct {
|
||||
Entity **variables; // Entity_Variable
|
||||
isize variable_count;
|
||||
} tuple;
|
||||
struct {
|
||||
Scope *scope;
|
||||
Type * params; // Type_Tuple
|
||||
Type * results; // Type_Tuple
|
||||
isize params_count;
|
||||
isize results_count;
|
||||
} procedure;
|
||||
};
|
||||
};
|
||||
|
||||
Type *get_base_type(Type *t) {
|
||||
while (t->kind == Type_Named)
|
||||
t = t->named.base;
|
||||
return t;
|
||||
}
|
||||
|
||||
void set_base_type(Type *t, Type *base) {
|
||||
if (t && t->kind == Type_Named) {
|
||||
t->named.base = base;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Type *alloc_type(TypeKind kind) {
|
||||
Type *t = gb_alloc_item(gb_heap_allocator(), Type);
|
||||
t->kind = kind;
|
||||
return t;
|
||||
}
|
||||
|
||||
|
||||
Type *make_type_basic(BasicType basic) {
|
||||
Type *t = alloc_type(Type_Basic);
|
||||
t->basic = basic;
|
||||
return t;
|
||||
}
|
||||
Type *make_type_array(Type *element, i64 count) {
|
||||
Type *t = alloc_type(Type_Array);
|
||||
t->array.element = element;
|
||||
t->array.count = count;
|
||||
return t;
|
||||
}
|
||||
|
||||
Type *make_type_structure(void) {
|
||||
Type *t = alloc_type(Type_Structure);
|
||||
return t;
|
||||
}
|
||||
|
||||
Type *make_type_pointer(Type *element) {
|
||||
Type *t = alloc_type(Type_Pointer);
|
||||
t->pointer.element = element;
|
||||
return t;
|
||||
}
|
||||
|
||||
Type *make_type_named(String name, Type *base, Entity *type_name) {
|
||||
Type *t = alloc_type(Type_Named);
|
||||
t->named.name = name;
|
||||
t->named.base = base;
|
||||
t->named.type_name = type_name;
|
||||
return t;
|
||||
}
|
||||
|
||||
Type *make_type_tuple(void) {
|
||||
Type *t = alloc_type(Type_Tuple);
|
||||
return t;
|
||||
}
|
||||
|
||||
Type *make_type_procedure(Scope *scope, Type *params, isize params_count, Type *results, isize results_count) {
|
||||
Type *t = alloc_type(Type_Procedure);
|
||||
t->procedure.scope = scope;
|
||||
t->procedure.params = params;
|
||||
t->procedure.params_count = params_count;
|
||||
t->procedure.results = results;
|
||||
t->procedure.results_count = results_count;
|
||||
return t;
|
||||
}
|
||||
|
||||
|
||||
|
||||
#define STR_LIT(x) {cast(u8 *)x, gb_size_of(x)-1}
|
||||
gb_global Type basic_types[] = {
|
||||
{Type_Basic, {Basic_Invalid, 0, STR_LIT("invalid type")}},
|
||||
{Type_Basic, {Basic_bool, BasicFlag_Boolean, STR_LIT("bool")}},
|
||||
{Type_Basic, {Basic_i8, BasicFlag_Integer, STR_LIT("i8")}},
|
||||
{Type_Basic, {Basic_i16, BasicFlag_Integer, STR_LIT("i16")}},
|
||||
{Type_Basic, {Basic_i32, BasicFlag_Integer, STR_LIT("i32")}},
|
||||
{Type_Basic, {Basic_i64, BasicFlag_Integer, STR_LIT("i64")}},
|
||||
{Type_Basic, {Basic_u8, BasicFlag_Integer | BasicFlag_Unsigned, STR_LIT("u8")}},
|
||||
{Type_Basic, {Basic_u16, BasicFlag_Integer | BasicFlag_Unsigned, STR_LIT("u16")}},
|
||||
{Type_Basic, {Basic_u32, BasicFlag_Integer | BasicFlag_Unsigned, STR_LIT("u32")}},
|
||||
{Type_Basic, {Basic_u64, BasicFlag_Integer | BasicFlag_Unsigned, STR_LIT("u64")}},
|
||||
{Type_Basic, {Basic_f32, BasicFlag_Float, STR_LIT("f32")}},
|
||||
{Type_Basic, {Basic_f64, BasicFlag_Float, STR_LIT("f64")}},
|
||||
{Type_Basic, {Basic_int, BasicFlag_Integer, STR_LIT("int")}},
|
||||
{Type_Basic, {Basic_uint, BasicFlag_Integer | BasicFlag_Unsigned, STR_LIT("uint")}},
|
||||
{Type_Basic, {Basic_rawptr, BasicFlag_Pointer, STR_LIT("rawptr")}},
|
||||
{Type_Basic, {Basic_string, BasicFlag_String, STR_LIT("string")}},
|
||||
{Type_Basic, {Basic_UntypedBool, BasicFlag_Boolean | BasicFlag_Untyped, STR_LIT("untyped bool")}},
|
||||
{Type_Basic, {Basic_UntypedInteger, BasicFlag_Integer | BasicFlag_Untyped, STR_LIT("untyped integer")}},
|
||||
{Type_Basic, {Basic_UntypedFloat, BasicFlag_Float | BasicFlag_Untyped, STR_LIT("untyped float")}},
|
||||
{Type_Basic, {Basic_UntypedPointer, BasicFlag_Pointer | BasicFlag_Untyped, STR_LIT("untyped pointer")}},
|
||||
{Type_Basic, {Basic_UntypedString, BasicFlag_String | BasicFlag_Untyped, STR_LIT("untyped string")}},
|
||||
{Type_Basic, {Basic_UntypedRune, BasicFlag_Integer | BasicFlag_Untyped, STR_LIT("untyped rune")}},
|
||||
};
|
||||
|
||||
gb_global Type basic_type_aliases[] = {
|
||||
{Type_Basic, {Basic_rune, BasicFlag_Integer, STR_LIT("rune")}},
|
||||
};
|
||||
|
||||
|
||||
b32 is_type_named(Type *t) {
|
||||
if (t->kind == Type_Basic)
|
||||
return true;
|
||||
return t->kind == Type_Named;
|
||||
}
|
||||
b32 is_type_boolean(Type *t) {
|
||||
if (t->kind == Type_Basic)
|
||||
return (t->basic.flags & BasicFlag_Boolean) != 0;
|
||||
return false;
|
||||
}
|
||||
b32 is_type_integer(Type *t) {
|
||||
if (t->kind == Type_Basic)
|
||||
return (t->basic.flags & BasicFlag_Integer) != 0;
|
||||
return false;
|
||||
}
|
||||
b32 is_type_unsigned(Type *t) {
|
||||
if (t->kind == Type_Basic)
|
||||
return (t->basic.flags & BasicFlag_Unsigned) != 0;
|
||||
return false;
|
||||
}
|
||||
b32 is_type_numeric(Type *t) {
|
||||
if (t->kind == Type_Basic)
|
||||
return (t->basic.flags & BasicFlag_Numeric) != 0;
|
||||
return false;
|
||||
}
|
||||
b32 is_type_string(Type *t) {
|
||||
if (t->kind == Type_Basic)
|
||||
return (t->basic.flags & BasicFlag_String) != 0;
|
||||
return false;
|
||||
}
|
||||
b32 is_type_typed(Type *t) {
|
||||
if (t->kind == Type_Basic)
|
||||
return (t->basic.flags & BasicFlag_Untyped) == 0;
|
||||
return true;
|
||||
}
|
||||
b32 is_type_untyped(Type *t) {
|
||||
if (t->kind == Type_Basic)
|
||||
return (t->basic.flags & BasicFlag_Untyped) != 0;
|
||||
return false;
|
||||
}
|
||||
b32 is_type_ordered(Type *t) {
|
||||
if (t->kind == Type_Basic)
|
||||
return (t->basic.flags & BasicFlag_Ordered) != 0;
|
||||
return false;
|
||||
}
|
||||
b32 is_type_constant_type(Type *t) {
|
||||
if (t->kind == Type_Basic)
|
||||
return (t->basic.flags & BasicFlag_ConstantType) != 0;
|
||||
return false;
|
||||
}
|
||||
b32 is_type_float(Type *t) {
|
||||
if (t->kind == Type_Basic)
|
||||
return (t->basic.flags & BasicFlag_Float) != 0;
|
||||
return false;
|
||||
}
|
||||
b32 is_type_pointer(Type *t) {
|
||||
if (t->kind == Type_Basic)
|
||||
return (t->basic.flags & BasicFlag_Pointer) != 0;
|
||||
return t->kind == Type_Pointer;
|
||||
}
|
||||
|
||||
b32 is_type_int_or_uint(Type *t) {
|
||||
if (t->kind == Type_Basic)
|
||||
return (t->basic.kind == Basic_int) || (t->basic.kind == Basic_uint);
|
||||
return false;
|
||||
}
|
||||
|
||||
b32 is_type_rawptr(Type *t) {
|
||||
if (t->kind == Type_Basic)
|
||||
return t->basic.kind == Basic_rawptr;
|
||||
return false;
|
||||
}
|
||||
|
||||
b32 is_type_comparable(Type *t) {
|
||||
t = get_base_type(t);
|
||||
switch (t->kind) {
|
||||
case Type_Basic:
|
||||
return true;
|
||||
case Type_Pointer:
|
||||
return true;
|
||||
case Type_Structure: {
|
||||
for (isize i = 0; i < t->structure.field_count; i++) {
|
||||
if (!is_type_comparable(t->structure.fields[i]->type))
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
} break;
|
||||
case Type_Array:
|
||||
return is_type_comparable(t->array.element);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
b32 are_types_identical(Type *x, Type *y) {
|
||||
if (x == y)
|
||||
return true;
|
||||
|
||||
switch (x->kind) {
|
||||
case Type_Basic:
|
||||
if (y->kind == Type_Basic)
|
||||
return x->basic.kind == y->basic.kind;
|
||||
break;
|
||||
|
||||
case Type_Array:
|
||||
if (y->kind == Type_Array)
|
||||
return (x->array.count == y->array.count) && are_types_identical(x->array.element, y->array.element);
|
||||
break;
|
||||
|
||||
case Type_Structure:
|
||||
if (y->kind == Type_Structure) {
|
||||
if (x->structure.field_count == y->structure.field_count) {
|
||||
for (isize i = 0; i < x->structure.field_count; i++) {
|
||||
if (!are_types_identical(x->structure.fields[i]->type, y->structure.fields[i]->type)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case Type_Pointer:
|
||||
if (y->kind == Type_Pointer)
|
||||
return are_types_identical(x->pointer.element, y->pointer.element);
|
||||
break;
|
||||
|
||||
case Type_Named:
|
||||
if (y->kind == Type_Named)
|
||||
return x->named.base == y->named.base;
|
||||
break;
|
||||
|
||||
|
||||
case Type_Tuple:
|
||||
if (y->kind == Type_Tuple) {
|
||||
if (x->tuple.variable_count == y->tuple.variable_count) {
|
||||
for (isize i = 0; i < x->tuple.variable_count; i++) {
|
||||
if (!are_types_identical(x->tuple.variables[i]->type, y->tuple.variables[i]->type))
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case Type_Procedure:
|
||||
if (y->kind == Type_Procedure) {
|
||||
return are_types_identical(x->procedure.params, y->procedure.params) &&
|
||||
are_types_identical(x->procedure.results, y->procedure.results);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
Type *default_type(Type *type) {
|
||||
if (type->kind == Type_Basic) {
|
||||
switch (type->basic.kind) {
|
||||
case Basic_UntypedBool: return &basic_types[Basic_bool];
|
||||
case Basic_UntypedInteger: return &basic_types[Basic_int];
|
||||
case Basic_UntypedFloat: return &basic_types[Basic_f64];
|
||||
case Basic_UntypedString: return &basic_types[Basic_string];
|
||||
case Basic_UntypedRune: return &basic_types[Basic_rune];
|
||||
case Basic_UntypedPointer: return &basic_types[Basic_rawptr];
|
||||
}
|
||||
}
|
||||
return type;
|
||||
}
|
||||
|
||||
|
||||
struct BaseTypeSizes {
|
||||
i64 word_size;
|
||||
i64 max_align;
|
||||
};
|
||||
|
||||
// TODO(bill): Change
|
||||
gb_global i64 basic_type_sizes[] = {
|
||||
0, // Basic_Invalid
|
||||
1, // Basic_bool // TODO(bill): What size should this be? And should I have different booleans?
|
||||
1, // Basic_i8
|
||||
2, // Basic_i16
|
||||
4, // Basic_i32
|
||||
8, // Basic_i64
|
||||
1, // Basic_u8
|
||||
2, // Basic_u16
|
||||
4, // Basic_u32
|
||||
8, // Basic_u64
|
||||
4, // Basic_f32
|
||||
8, // Basic_f64
|
||||
};
|
||||
|
||||
|
||||
i64 type_size_of(BaseTypeSizes s, gbAllocator allocator, Type *t);
|
||||
i64 type_align_of(BaseTypeSizes s, gbAllocator allocator, Type *t);
|
||||
|
||||
i64 align_formula(i64 size, i64 align) {
|
||||
i64 result = size + align-1;
|
||||
return result - result%align;
|
||||
}
|
||||
|
||||
i64 type_align_of(BaseTypeSizes s, gbAllocator allocator, Type *t) {
|
||||
t = get_base_type(t);
|
||||
|
||||
switch (t->kind) {
|
||||
case Type_Array:
|
||||
return type_align_of(s, allocator, t->array.element);
|
||||
case Type_Structure: {
|
||||
i64 max = 1;
|
||||
for (isize i = 0; i < t->structure.field_count; i++) {
|
||||
i64 align = type_align_of(s, allocator, t->structure.fields[i]->type);
|
||||
if (max < align)
|
||||
max = align;
|
||||
}
|
||||
return max;
|
||||
} break;
|
||||
}
|
||||
|
||||
return gb_clamp(type_size_of(s, allocator, t), 1, s.max_align);
|
||||
}
|
||||
|
||||
i64 *type_set_offsets_of(BaseTypeSizes s, gbAllocator allocator, Entity **fields, isize field_count) {
|
||||
// TODO(bill): use arena allocation
|
||||
i64 *offsets = gb_alloc_array(allocator, i64, field_count);
|
||||
i64 curr_offset = 0;
|
||||
for (isize i = 0; i < field_count; i++) {
|
||||
i64 align = type_align_of(s, allocator, fields[i]->type);
|
||||
curr_offset = align_formula(curr_offset, align);
|
||||
offsets[i] = curr_offset;
|
||||
curr_offset += type_size_of(s, allocator, fields[i]->type);
|
||||
}
|
||||
|
||||
return offsets;
|
||||
}
|
||||
|
||||
b32 type_set_offsets(BaseTypeSizes s, gbAllocator allocator, Type *t) {
|
||||
GB_ASSERT(t->kind == Type_Structure);
|
||||
if (!t->structure.offsets_set) {
|
||||
t->structure.offsets = type_set_offsets_of(s, allocator, t->structure.fields, t->structure.field_count);
|
||||
t->structure.offsets_set = true;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
i64 type_size_of(BaseTypeSizes s, gbAllocator allocator, Type *t) {
|
||||
t = get_base_type(t);
|
||||
switch (t->kind) {
|
||||
case Type_Basic: {
|
||||
GB_ASSERT(is_type_typed(t));
|
||||
BasicKind kind = t->basic.kind;
|
||||
if (kind < gb_count_of(basic_type_sizes)) {
|
||||
i64 size = basic_type_sizes[kind];
|
||||
if (size > 0)
|
||||
return size;
|
||||
}
|
||||
if (kind == Basic_string)
|
||||
return 2 * s.word_size;
|
||||
} break;
|
||||
|
||||
case Type_Array: {
|
||||
i64 count = t->array.count;
|
||||
if (count == 0)
|
||||
return 0;
|
||||
i64 align = type_align_of(s, allocator, t->array.element);
|
||||
i64 size = type_size_of(s, allocator, t->array.element);
|
||||
i64 alignment = align_formula(size, align);
|
||||
return alignment*(count-1) + size;
|
||||
} break;
|
||||
|
||||
case Type_Structure: {
|
||||
i64 count = t->structure.field_count;
|
||||
if (count == 0)
|
||||
return 0;
|
||||
type_set_offsets(s, allocator, t);
|
||||
return t->structure.offsets[count-1] + type_size_of(s, allocator, t->structure.fields[count-1]->type);
|
||||
} break;
|
||||
}
|
||||
|
||||
// Catch all
|
||||
return s.word_size;
|
||||
}
|
||||
|
||||
i64 type_offset_of(BaseTypeSizes s, gbAllocator allocator, Type *t, isize index) {
|
||||
GB_ASSERT(t->kind == Type_Structure);
|
||||
type_set_offsets(s, allocator, t);
|
||||
if (gb_is_between(index, 0, t->structure.field_count-1)) {
|
||||
return t->structure.offsets[index];
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
gbString write_type_to_string(gbString str, Type *type) {
|
||||
if (type == NULL) {
|
||||
return gb_string_appendc(str, "<no type>");
|
||||
}
|
||||
|
||||
switch (type->kind) {
|
||||
case Type_Basic:
|
||||
str = gb_string_append_length(str, type->basic.name.text, type->basic.name.len);
|
||||
break;
|
||||
case Type_Array:
|
||||
if (type->array.count >= 0) {
|
||||
str = gb_string_appendc(str, gb_bprintf("[%td]", type->array.count));
|
||||
} else {
|
||||
str = gb_string_appendc(str, "[]");
|
||||
}
|
||||
str = write_type_to_string(str, type->array.element);
|
||||
break;
|
||||
case Type_Structure: {
|
||||
str = gb_string_appendc(str, "struct{");
|
||||
for (isize i = 0; i < type->structure.field_count; i++) {
|
||||
Entity *f = type->structure.fields[i];
|
||||
GB_ASSERT(f->kind == Entity_Variable);
|
||||
if (i < type->structure.field_count-1)
|
||||
str = gb_string_appendc(str, "; ");
|
||||
str = gb_string_append_length(str, f->token.string.text, f->token.string.len);
|
||||
str = gb_string_appendc(str, ": ");
|
||||
str = write_type_to_string(str, f->type);
|
||||
}
|
||||
str = gb_string_appendc(str, "}");
|
||||
} break;
|
||||
|
||||
case Type_Pointer:
|
||||
str = gb_string_appendc(str, "^");
|
||||
str = write_type_to_string(str, type->pointer.element);
|
||||
break;
|
||||
|
||||
case Type_Named:
|
||||
if (type->named.type_name != NULL) {
|
||||
str = gb_string_append_length(str, type->named.name.text, type->named.name.len);
|
||||
} else {
|
||||
// NOTE(bill): Just in case
|
||||
str = gb_string_appendc(str, "<Type_Named>");
|
||||
}
|
||||
break;
|
||||
|
||||
case Type_Tuple:
|
||||
if (type->tuple.variable_count > 0) {
|
||||
for (isize i = 0; i < type->tuple.variable_count; i++) {
|
||||
Entity *var = type->tuple.variables[i];
|
||||
GB_ASSERT(var->kind == Entity_Variable);
|
||||
if (i > 0)
|
||||
str = gb_string_appendc(str, ", ");
|
||||
if (var->token.string.len > 0) {
|
||||
str = gb_string_append_length(str, var->token.string.text, var->token.string.len);
|
||||
str = gb_string_appendc(str, ": ");
|
||||
}
|
||||
str = write_type_to_string(str, var->type);
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case Type_Procedure:
|
||||
str = gb_string_appendc(str, "proc(");
|
||||
if (type->procedure.params)
|
||||
str = write_type_to_string(str, type->procedure.params);
|
||||
str = gb_string_appendc(str, ")");
|
||||
if (type->procedure.results) {
|
||||
str = gb_string_appendc(str, " -> ");
|
||||
str = write_type_to_string(str, type->procedure.results);
|
||||
}
|
||||
break;
|
||||
}
|
||||
return str;
|
||||
}
|
||||
|
||||
|
||||
gbString type_to_string(Type *type, gbAllocator a = gb_heap_allocator()) {
|
||||
gbString str = gb_string_make(a, "");
|
||||
return write_type_to_string(str, type);
|
||||
}
|
||||
|
||||
|
||||
365
src/checker/value.cpp
Normal file
365
src/checker/value.cpp
Normal file
@@ -0,0 +1,365 @@
|
||||
#include <math.h>
|
||||
|
||||
// TODO(bill): Big numbers
|
||||
// IMPORTANT TODO(bill): This needs to be completely fixed!!!!!!!!
|
||||
|
||||
enum ValueKind {
|
||||
Value_Invalid,
|
||||
|
||||
Value_Bool,
|
||||
Value_String,
|
||||
Value_Integer,
|
||||
Value_Float,
|
||||
Value_Pointer, // TODO(bill): Value_Pointer
|
||||
|
||||
Value_Count,
|
||||
};
|
||||
|
||||
struct Value {
|
||||
ValueKind kind;
|
||||
union {
|
||||
b32 value_bool;
|
||||
String value_string;
|
||||
i64 value_integer;
|
||||
f64 value_float;
|
||||
void * value_pointer;
|
||||
};
|
||||
};
|
||||
|
||||
Value make_value_bool(b32 b) {
|
||||
Value result = {Value_Bool};
|
||||
result.value_bool = (b != 0);
|
||||
return result;
|
||||
}
|
||||
|
||||
Value make_value_string(String string) {
|
||||
// TODO(bill): Allow for numbers with underscores in them
|
||||
Value result = {Value_String};
|
||||
result.value_string = string;
|
||||
return result;
|
||||
}
|
||||
|
||||
Value make_value_integer(String string) {
|
||||
// TODO(bill): Allow for numbers with underscores in them
|
||||
Value result = {Value_Integer};
|
||||
i32 base = 10;
|
||||
if (string.text[0] == '0') {
|
||||
switch (string.text[1]) {
|
||||
case 'b': base = 2; break;
|
||||
case 'o': base = 8; break;
|
||||
case 'd': base = 10; break;
|
||||
case 'x': base = 16; break;
|
||||
}
|
||||
}
|
||||
|
||||
result.value_integer = gb_str_to_i64(cast(char *)string.text, NULL, base);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
Value make_value_integer(i64 i) {
|
||||
Value result = {Value_Integer};
|
||||
result.value_integer = i;
|
||||
return result;
|
||||
}
|
||||
|
||||
Value make_value_float(String string) {
|
||||
// TODO(bill): Allow for numbers with underscores in them
|
||||
Value result = {Value_Float};
|
||||
result.value_float = gb_str_to_f64(cast(char *)string.text, NULL);
|
||||
return result;
|
||||
}
|
||||
|
||||
Value make_value_float(f64 f) {
|
||||
Value result = {Value_Float};
|
||||
result.value_float = f;
|
||||
return result;
|
||||
}
|
||||
|
||||
Value make_value_pointer(void *ptr) {
|
||||
Value result = {Value_Pointer};
|
||||
result.value_pointer = ptr;
|
||||
return result;
|
||||
}
|
||||
|
||||
Value make_value_from_basic_literal(Token token) {
|
||||
switch (token.kind) {
|
||||
case Token_String: return make_value_string(token.string);
|
||||
case Token_Integer: return make_value_integer(token.string);
|
||||
case Token_Float: return make_value_float(token.string);
|
||||
case Token_Rune: return make_value_integer(token.string);
|
||||
default:
|
||||
GB_PANIC("Invalid token for basic literal");
|
||||
break;
|
||||
}
|
||||
|
||||
Value result = {Value_Invalid};
|
||||
return result;
|
||||
}
|
||||
|
||||
Value value_to_integer(Value v) {
|
||||
switch (v.kind) {
|
||||
case Value_Integer:
|
||||
return v;
|
||||
case Value_Float:
|
||||
return make_value_integer(cast(i64)v.value_float);
|
||||
}
|
||||
Value r = {Value_Invalid};
|
||||
return r;
|
||||
}
|
||||
|
||||
Value value_to_float(Value v) {
|
||||
switch (v.kind) {
|
||||
case Value_Integer:
|
||||
return make_value_float(cast(i64)v.value_integer);
|
||||
case Value_Float:
|
||||
return v;
|
||||
}
|
||||
Value r = {Value_Invalid};
|
||||
return r;
|
||||
}
|
||||
|
||||
|
||||
Value unary_operator_value(Token op, Value v, i32 precision) {
|
||||
switch (op.kind) {
|
||||
case Token_Add: {
|
||||
switch (v.kind) {
|
||||
case Value_Invalid:
|
||||
case Value_Integer:
|
||||
case Value_Float:
|
||||
return v;
|
||||
}
|
||||
} break;
|
||||
|
||||
case Token_Sub: {
|
||||
switch (v.kind) {
|
||||
case Value_Invalid:
|
||||
return v;
|
||||
case Value_Integer: {
|
||||
Value i = v;
|
||||
i.value_integer = -i.value_integer;
|
||||
return i;
|
||||
}
|
||||
case Value_Float: {
|
||||
Value i = v;
|
||||
i.value_float = -i.value_float;
|
||||
return i;
|
||||
}
|
||||
}
|
||||
} break;
|
||||
|
||||
case Token_Xor: {
|
||||
i64 i = 0;
|
||||
switch (v.kind) {
|
||||
case Value_Invalid:
|
||||
return v;
|
||||
case Value_Integer:
|
||||
i = ~i;
|
||||
break;
|
||||
default:
|
||||
goto failure;
|
||||
}
|
||||
|
||||
// NOTE(bill): unsigned integers will be negative and will need to be
|
||||
// limited to the types precision
|
||||
if (precision > 0)
|
||||
i &= ~((-1)<<precision);
|
||||
|
||||
return make_value_integer(i);
|
||||
} break;
|
||||
|
||||
case Token_Not: {
|
||||
switch (v.kind) {
|
||||
case Value_Invalid: return v;
|
||||
case Value_Bool:
|
||||
return make_value_bool(!v.value_bool);
|
||||
}
|
||||
} break;
|
||||
}
|
||||
|
||||
failure:
|
||||
GB_PANIC("Invalid unary operation, %s", token_kind_to_string(op.kind));
|
||||
|
||||
Value error_value = {};
|
||||
return error_value;
|
||||
}
|
||||
|
||||
// NOTE(bill): Make sure things are evaluated in correct order
|
||||
i32 value_order(Value v) {
|
||||
switch (v.kind) {
|
||||
case Value_Invalid:
|
||||
return 0;
|
||||
case Value_Bool:
|
||||
case Value_String:
|
||||
return 1;
|
||||
case Value_Integer:
|
||||
return 2;
|
||||
case Value_Float:
|
||||
return 3;
|
||||
case Value_Pointer:
|
||||
return 4;
|
||||
|
||||
default:
|
||||
GB_PANIC("How'd you get here? Invalid Value.kind");
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
void match_values(Value *x, Value *y) {
|
||||
if (value_order(*y) < value_order(*x)) {
|
||||
match_values(y, x);
|
||||
return;
|
||||
}
|
||||
|
||||
switch (x->kind) {
|
||||
case Value_Invalid:
|
||||
*y = *x;
|
||||
return;
|
||||
|
||||
case Value_Bool:
|
||||
case Value_String:
|
||||
return;
|
||||
|
||||
case Value_Integer: {
|
||||
switch (y->kind) {
|
||||
case Value_Integer:
|
||||
return;
|
||||
case Value_Float:
|
||||
// TODO(bill): Is this good enough?
|
||||
*x = make_value_float(cast(f64)x->value_integer);
|
||||
return;
|
||||
}
|
||||
} break;
|
||||
|
||||
case Value_Float: {
|
||||
if (y->kind == Value_Float)
|
||||
return;
|
||||
} break;
|
||||
}
|
||||
|
||||
GB_PANIC("How'd you get here? Invalid Value.kind");
|
||||
}
|
||||
|
||||
Value binary_operator_value(Token op, Value x, Value y) {
|
||||
match_values(&x, &y);
|
||||
|
||||
switch (x.kind) {
|
||||
case Value_Invalid:
|
||||
return x;
|
||||
|
||||
case Value_Bool:
|
||||
switch (op.kind) {
|
||||
case Token_CmpAnd: return make_value_bool(x.value_bool && y.value_bool);
|
||||
case Token_CmpOr: return make_value_bool(x.value_bool || y.value_bool);
|
||||
default: goto error;
|
||||
}
|
||||
break;
|
||||
|
||||
case Value_Integer: {
|
||||
i64 a = x.value_integer;
|
||||
i64 b = y.value_integer;
|
||||
i64 c = 0;
|
||||
switch (op.kind) {
|
||||
case Token_Add: c = a + b; break;
|
||||
case Token_Sub: c = a - b; break;
|
||||
case Token_Mul: c = a * b; break;
|
||||
case Token_Quo: return make_value_float(fmod(cast(f64)a, cast(f64)b));
|
||||
case Token_QuoEq: c = a / b; break; // NOTE(bill): Integer division
|
||||
case Token_Mod: c = a % b; break;
|
||||
case Token_And: c = a & b; break;
|
||||
case Token_Or: c = a | b; break;
|
||||
case Token_Xor: c = a ^ b; break;
|
||||
case Token_AndNot: c = a&(~b); break;
|
||||
default: goto error;
|
||||
}
|
||||
return make_value_integer(c);
|
||||
} break;
|
||||
|
||||
case Value_Float: {
|
||||
f64 a = x.value_float;
|
||||
f64 b = y.value_float;
|
||||
switch (op.kind) {
|
||||
case Token_Add: return make_value_float(a + b);
|
||||
case Token_Sub: return make_value_float(a - b);
|
||||
case Token_Mul: return make_value_float(a * b);
|
||||
case Token_Quo: return make_value_float(a / b);
|
||||
default: goto error;
|
||||
}
|
||||
} break;
|
||||
}
|
||||
|
||||
error:
|
||||
Value error_value = {};
|
||||
GB_PANIC("Invalid binary operation: %s", token_kind_to_string(op.kind));
|
||||
return error_value;
|
||||
}
|
||||
|
||||
gb_inline Value value_add(Value x, Value y) { Token op = {Token_Add}; return binary_operator_value(op, x, y); }
|
||||
gb_inline Value value_sub(Value x, Value y) { Token op = {Token_Sub}; return binary_operator_value(op, x, y); }
|
||||
gb_inline Value value_mul(Value x, Value y) { Token op = {Token_Mul}; return binary_operator_value(op, x, y); }
|
||||
gb_inline Value value_quo(Value x, Value y) { Token op = {Token_Quo}; return binary_operator_value(op, x, y); }
|
||||
|
||||
|
||||
i32 cmp_f64(f64 a, f64 b) {
|
||||
return (a > b) - (a < b);
|
||||
}
|
||||
|
||||
b32 compare_values(Token op, Value x, Value y) {
|
||||
match_values(&x, &y);
|
||||
|
||||
switch (x.kind) {
|
||||
case Value_Invalid:
|
||||
return false;
|
||||
|
||||
case Value_Bool:
|
||||
switch (op.kind) {
|
||||
case Token_CmpEq: return x.value_bool == y.value_bool;
|
||||
case Token_NotEq: return x.value_bool != y.value_bool;
|
||||
}
|
||||
break;
|
||||
|
||||
case Value_Integer: {
|
||||
i64 a = x.value_integer;
|
||||
i64 b = y.value_integer;
|
||||
switch (op.kind) {
|
||||
case Token_CmpEq: return a == b;
|
||||
case Token_NotEq: return a != b;
|
||||
case Token_Lt: return a < b;
|
||||
case Token_LtEq: return a <= b;
|
||||
case Token_Gt: return a > b;
|
||||
case Token_GtEq: return a >= b;
|
||||
}
|
||||
} break;
|
||||
|
||||
case Value_Float: {
|
||||
f64 a = x.value_float;
|
||||
f64 b = y.value_float;
|
||||
switch (op.kind) {
|
||||
case Token_CmpEq: return cmp_f64(a, b) == 0;
|
||||
case Token_NotEq: return cmp_f64(a, b) != 0;
|
||||
case Token_Lt: return cmp_f64(a, b) < 0;
|
||||
case Token_LtEq: return cmp_f64(a, b) <= 0;
|
||||
case Token_Gt: return cmp_f64(a, b) > 0;
|
||||
case Token_GtEq: return cmp_f64(a, b) >= 0;
|
||||
}
|
||||
} break;
|
||||
|
||||
case Value_String: {
|
||||
String a = x.value_string;
|
||||
String b = y.value_string;
|
||||
isize len = gb_min(a.len, b.len);
|
||||
// TODO(bill): gb_memcompare is used because the strings are UTF-8
|
||||
switch (op.kind) {
|
||||
case Token_CmpEq: return gb_memcompare(a.text, b.text, len) == 0;
|
||||
case Token_NotEq: return gb_memcompare(a.text, b.text, len) != 0;
|
||||
case Token_Lt: return gb_memcompare(a.text, b.text, len) < 0;
|
||||
case Token_LtEq: return gb_memcompare(a.text, b.text, len) <= 0;
|
||||
case Token_Gt: return gb_memcompare(a.text, b.text, len) > 0;
|
||||
case Token_GtEq: return gb_memcompare(a.text, b.text, len) >= 0;
|
||||
}
|
||||
} break;
|
||||
}
|
||||
|
||||
GB_PANIC("Invalid comparison");
|
||||
return false;
|
||||
}
|
||||
221
src/common.cpp
Normal file
221
src/common.cpp
Normal file
@@ -0,0 +1,221 @@
|
||||
#define GB_NO_WINDOWS_H
|
||||
#define GB_IMPLEMENTATION
|
||||
#include "gb/gb.h"
|
||||
|
||||
// NOTE(bill): Used for UTF-8 strings
|
||||
typedef struct String {
|
||||
u8 *text;
|
||||
isize len;
|
||||
} String;
|
||||
// NOTE(bill): used for printf style arguments
|
||||
#define LIT(x) (x).len, (x).text
|
||||
|
||||
|
||||
gb_inline String make_string(u8 *text, isize len) {
|
||||
String s;
|
||||
s.text = text;
|
||||
if (len < 0)
|
||||
len = gb_strlen(cast(char *)text);
|
||||
s.len = len;
|
||||
return s;
|
||||
}
|
||||
|
||||
gb_inline String make_string(char *text) {
|
||||
return make_string(cast(u8 *)cast(void *)text, gb_strlen(text));
|
||||
}
|
||||
|
||||
gb_inline b32 are_strings_equal(String a, String b) {
|
||||
if (a.len == b.len) {
|
||||
return gb_memcompare(a.text, b.text, a.len) == 0;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// Hasing
|
||||
|
||||
gb_inline u64 hashing_proc(void const *data, isize len) {
|
||||
return gb_murmur64(data, len);
|
||||
}
|
||||
|
||||
gb_inline u64 hash_string(String s) {
|
||||
return hashing_proc(s.text, s.len);
|
||||
}
|
||||
|
||||
gb_inline u64 hash_pointer(void *ptr) {
|
||||
u64 p = cast(u64)cast(uintptr)ptr;
|
||||
return p;
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// Generic Data Structures
|
||||
//
|
||||
////////////////////////////////////////////////////////////////
|
||||
|
||||
|
||||
typedef struct MapFindResult {
|
||||
isize hash_index;
|
||||
isize entry_prev;
|
||||
isize entry_index;
|
||||
} MapFindResult;
|
||||
|
||||
template <typename T>
|
||||
struct MapEntry {
|
||||
u64 key;
|
||||
isize next;
|
||||
T value;
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
struct Map {
|
||||
gbArray(isize) hashes;
|
||||
gbArray(MapEntry<T>) entries;
|
||||
};
|
||||
|
||||
template <typename T> void map_init (Map<T> *h, gbAllocator a);
|
||||
template <typename T> void map_destroy(Map<T> *h);
|
||||
template <typename T> T * map_get (Map<T> *h, u64 key);
|
||||
template <typename T> void map_set (Map<T> *h, u64 key, T value);
|
||||
template <typename T> void map_remove (Map<T> *h, u64 key);
|
||||
template <typename T> void map_clear (Map<T> *h);
|
||||
template <typename T> void map_grow (Map<T> *h);
|
||||
template <typename T> void map_rehash (Map<T> *h, isize new_count);
|
||||
|
||||
|
||||
|
||||
template <typename T>
|
||||
gb_inline void map_init(Map<T> *h, gbAllocator a) {
|
||||
gb_array_init(h->hashes, a);
|
||||
gb_array_init(h->entries, a);
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
gb_inline void map_destroy(Map<T> *h) {
|
||||
if (h->entries) gb_array_free(h->entries);
|
||||
if (h->hashes) gb_array_free(h->hashes);
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
gb_internal isize map__add_entry(Map<T> *h, u64 key) {
|
||||
MapEntry<T> e = {0};
|
||||
e.key = key;
|
||||
e.next = -1;
|
||||
gb_array_append(h->entries, e);
|
||||
return gb_array_count(h->entries)-1;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
gb_internal MapFindResult map__find(Map<T> *h, u64 key) {
|
||||
MapFindResult fr = {-1, -1, -1};
|
||||
if (gb_array_count(h->hashes) > 0) {
|
||||
fr.hash_index = key % gb_array_count(h->hashes);
|
||||
fr.entry_index = h->hashes[fr.hash_index];
|
||||
while (fr.entry_index >= 0) {
|
||||
if (h->entries[fr.entry_index].key == key)
|
||||
return fr;
|
||||
fr.entry_prev = fr.entry_index;
|
||||
fr.entry_index = h->entries[fr.entry_index].next;
|
||||
}
|
||||
}
|
||||
return fr;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
gb_internal b32 map__full(Map<T> *h) {
|
||||
return 0.75f * gb_array_count(h->hashes) <= gb_array_count(h->entries);
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
gb_inline void map_grow(Map<T> *h) {
|
||||
isize new_count = GB_ARRAY_GROW_FORMULA(gb_array_count(h->entries));
|
||||
map_rehash(h, new_count);
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
void map_rehash(Map<T> *h, isize new_count) {
|
||||
isize i, j;
|
||||
Map<T> nh = {0};
|
||||
map_init(&nh, gb_array_allocator(h->hashes));
|
||||
gb_array_resize(nh.hashes, new_count);
|
||||
gb_array_reserve(nh.entries, gb_array_count(h->entries));
|
||||
for (i = 0; i < new_count; i++)
|
||||
nh.hashes[i] = -1;
|
||||
for (i = 0; i < gb_array_count(h->entries); i++) {
|
||||
MapEntry<T> *e = &h->entries[i];
|
||||
MapFindResult fr;
|
||||
if (gb_array_count(nh.hashes) == 0)
|
||||
map_grow(&nh);
|
||||
fr = map__find(&nh, e->key);
|
||||
j = map__add_entry(&nh, e->key);
|
||||
if (fr.entry_prev < 0)
|
||||
nh.hashes[fr.hash_index] = j;
|
||||
else
|
||||
nh.entries[fr.entry_prev].next = j;
|
||||
nh.entries[j].next = fr.entry_index;
|
||||
nh.entries[j].value = e->value;
|
||||
if (map__full(&nh))
|
||||
map_grow(&nh);
|
||||
}
|
||||
map_destroy(h);
|
||||
*h = nh;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
gb_inline T *map_get(Map<T> *h, u64 key) {
|
||||
isize index = map__find(h, key).entry_index;
|
||||
if (index >= 0)
|
||||
return &h->entries[index].value;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
void map_set(Map<T> *h, u64 key, T value) {
|
||||
isize index;
|
||||
MapFindResult fr;
|
||||
if (gb_array_count(h->hashes) == 0)
|
||||
map_grow(h);
|
||||
fr = map__find(h, key);
|
||||
if (fr.entry_index >= 0) {
|
||||
index = fr.entry_index;
|
||||
} else {
|
||||
index = map__add_entry(h, key);
|
||||
if (fr.entry_prev >= 0) {
|
||||
h->entries[fr.entry_prev].next = index;
|
||||
} else {
|
||||
h->hashes[fr.hash_index] = index;
|
||||
}
|
||||
}
|
||||
h->entries[index].value = value;
|
||||
|
||||
if (map__full(h))
|
||||
map_grow(h);
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
void map_remove(Map<T> *h, u64 key) {
|
||||
MapFindResult fr = map__find(h, key);
|
||||
if (fr.entry_index >= 0) {
|
||||
if (fr.entry_prev < 0) {
|
||||
h->hashes[fr.hash_index] = h->entries[fr.entry_index].next;
|
||||
} else {
|
||||
h->entries[fr.entry_prev].next = h->entries[fr.entry_index].next;
|
||||
}
|
||||
if (fr.entry_index == gb_array_count(h->entries)-1) {
|
||||
gb_array_pop(h->entries);
|
||||
return;
|
||||
}
|
||||
h->entries[fr.entry_index] = h->entries[gb_array_count(h->entries)-1];
|
||||
MapFindResult last = map__find(h, h->entries[fr.entry_index].key);
|
||||
if (last.entry_prev >= 0) {
|
||||
h->entries[last.entry_prev].next = fr.entry_index;
|
||||
} else {
|
||||
h->hashes[last.hash_index] = fr.entry_index;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
gb_inline void map_clear(Map<T> *h) {
|
||||
gb_array_clear(h->hashes);
|
||||
gb_array_clear(h->entries);
|
||||
}
|
||||
10331
src/gb/gb.h
Normal file
10331
src/gb/gb.h
Normal file
File diff suppressed because it is too large
Load Diff
77
src/generator.cpp
Normal file
77
src/generator.cpp
Normal file
@@ -0,0 +1,77 @@
|
||||
// #include <llvm-c/llvm>
|
||||
|
||||
struct Generator {
|
||||
Checker *checker;
|
||||
String output_fullpath;
|
||||
gbFile output;
|
||||
|
||||
#define MAX_GENERATOR_ERROR_COUNT 10
|
||||
isize error_prev_line;
|
||||
isize error_prev_column;
|
||||
isize error_count;
|
||||
};
|
||||
|
||||
#define print_generator_error(p, token, fmt, ...) print_generator_error_(p, __FUNCTION__, token, fmt, ##__VA_ARGS__)
|
||||
void print_generator_error_(Generator *g, char *function, Token token, char *fmt, ...) {
|
||||
va_list va;
|
||||
|
||||
// NOTE(bill): Duplicate error, skip it
|
||||
if (g->error_prev_line == token.line && g->error_prev_column == token.column) {
|
||||
goto error;
|
||||
}
|
||||
g->error_prev_line = token.line;
|
||||
g->error_prev_column = token.column;
|
||||
|
||||
#if 0
|
||||
gb_printf_err("%s()\n", function);
|
||||
#endif
|
||||
va_start(va, fmt);
|
||||
gb_printf_err("%s(%td:%td) %s\n",
|
||||
g->checker->parser->tokenizer.fullpath, token.line, token.column,
|
||||
gb_bprintf_va(fmt, va));
|
||||
va_end(va);
|
||||
|
||||
error:
|
||||
g->error_count++;
|
||||
// NOTE(bill): If there are too many errors, just quit
|
||||
if (g->error_count > MAX_GENERATOR_ERROR_COUNT) {
|
||||
gb_exit(1);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
b32 init_generator(Generator *g, Checker *checker) {
|
||||
if (checker->error_count > 0)
|
||||
return false;
|
||||
gb_zero_item(g);
|
||||
g->checker = checker;
|
||||
|
||||
char *fullpath = checker->parser->tokenizer.fullpath;
|
||||
char const *ext = gb_path_extension(fullpath);
|
||||
isize base_len = ext-fullpath;
|
||||
isize ext_len = gb_strlen("cpp");
|
||||
isize len = base_len + ext_len + 1;
|
||||
u8 *text = gb_alloc_array(gb_heap_allocator(), u8, len);
|
||||
gb_memcopy(text, fullpath, base_len);
|
||||
gb_memcopy(text+base_len, "cpp", ext_len);
|
||||
g->output_fullpath = make_string(text, len);
|
||||
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void destroy_generator(Generator *g) {
|
||||
if (g->error_count > 0) {
|
||||
|
||||
}
|
||||
|
||||
if (g->output_fullpath.text)
|
||||
gb_free(gb_heap_allocator(), g->output_fullpath.text);
|
||||
}
|
||||
|
||||
|
||||
|
||||
void generate_code(Generator *g, AstNode *root_node) {
|
||||
|
||||
}
|
||||
41
src/main.cpp
Normal file
41
src/main.cpp
Normal file
@@ -0,0 +1,41 @@
|
||||
#include "common.cpp"
|
||||
#include "tokenizer.cpp"
|
||||
#include "parser.cpp"
|
||||
#include "printer.cpp"
|
||||
#include "checker.cpp"
|
||||
#include "generator.cpp"
|
||||
|
||||
int main(int argc, char **argv) {
|
||||
if (argc < 2) {
|
||||
gb_printf_err("Please specify a .odin file\n");
|
||||
return 1;
|
||||
}
|
||||
|
||||
init_global_scope();
|
||||
|
||||
for (int arg_index = 1; arg_index < argc; arg_index++) {
|
||||
char *arg = argv[arg_index];
|
||||
char *filename = arg;
|
||||
Parser parser = {0};
|
||||
|
||||
if (init_parser(&parser, filename)) {
|
||||
defer (destroy_parser(&parser));
|
||||
AstNode *root_node = parse_statement_list(&parser, NULL);
|
||||
// print_ast(root_node, 0);
|
||||
|
||||
Checker checker = {};
|
||||
init_checker(&checker, &parser);
|
||||
defer (destroy_checker(&checker));
|
||||
|
||||
check_statement_list(&checker, root_node);
|
||||
|
||||
Generator generator = {};
|
||||
if (init_generator(&generator, &checker)) {
|
||||
defer (destroy_generator(&generator));
|
||||
generate_code(&generator, root_node);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
1591
src/parser.cpp
Normal file
1591
src/parser.cpp
Normal file
File diff suppressed because it is too large
Load Diff
184
src/printer.cpp
Normal file
184
src/printer.cpp
Normal file
@@ -0,0 +1,184 @@
|
||||
|
||||
|
||||
gb_inline void print_indent(isize indent) {
|
||||
while (indent --> 0)
|
||||
gb_printf(" ");
|
||||
}
|
||||
|
||||
void print_ast(AstNode *node, isize indent) {
|
||||
if (node == NULL)
|
||||
return;
|
||||
|
||||
switch (node->kind) {
|
||||
case AstNode_BasicLiteral:
|
||||
print_indent(indent);
|
||||
print_token(node->basic_literal);
|
||||
break;
|
||||
case AstNode_Identifier:
|
||||
print_indent(indent);
|
||||
print_token(node->identifier.token);
|
||||
break;
|
||||
|
||||
case AstNode_TagExpression:
|
||||
print_indent(indent);
|
||||
gb_printf("(tag)\n");
|
||||
print_indent(indent+1);
|
||||
print_token(node->tag_expression.name);
|
||||
break;
|
||||
|
||||
case AstNode_UnaryExpression:
|
||||
print_indent(indent);
|
||||
print_token(node->unary_expression.op);
|
||||
print_ast(node->unary_expression.operand, indent+1);
|
||||
break;
|
||||
case AstNode_BinaryExpression:
|
||||
print_indent(indent);
|
||||
print_token(node->binary_expression.op);
|
||||
print_ast(node->binary_expression.left, indent+1);
|
||||
print_ast(node->binary_expression.right, indent+1);
|
||||
break;
|
||||
case AstNode_CallExpression:
|
||||
print_indent(indent);
|
||||
gb_printf("(call)\n");
|
||||
print_ast(node->call_expression.proc, indent+1);
|
||||
print_ast(node->call_expression.arg_list, indent+1);
|
||||
break;
|
||||
case AstNode_SelectorExpression:
|
||||
print_indent(indent);
|
||||
gb_printf(".\n");
|
||||
print_ast(node->selector_expression.operand, indent+1);
|
||||
print_ast(node->selector_expression.selector, indent+1);
|
||||
break;
|
||||
case AstNode_IndexExpression:
|
||||
print_indent(indent);
|
||||
gb_printf("([])\n");
|
||||
print_ast(node->index_expression.expression, indent+1);
|
||||
print_ast(node->index_expression.value, indent+1);
|
||||
break;
|
||||
case AstNode_CastExpression:
|
||||
print_indent(indent);
|
||||
gb_printf("(cast)\n");
|
||||
print_ast(node->cast_expression.type_expression, indent+1);
|
||||
print_ast(node->cast_expression.operand, indent+1);
|
||||
break;
|
||||
case AstNode_DereferenceExpression:
|
||||
print_indent(indent);
|
||||
gb_printf("(dereference)\n");
|
||||
print_ast(node->dereference_expression.operand, indent+1);
|
||||
break;
|
||||
|
||||
|
||||
case AstNode_ExpressionStatement:
|
||||
print_ast(node->expression_statement.expression, indent);
|
||||
break;
|
||||
case AstNode_IncDecStatement:
|
||||
print_indent(indent);
|
||||
print_token(node->inc_dec_statement.op);
|
||||
print_ast(node->inc_dec_statement.expression, indent+1);
|
||||
break;
|
||||
case AstNode_AssignStatement:
|
||||
print_indent(indent);
|
||||
print_token(node->assign_statement.op);
|
||||
print_ast(node->assign_statement.lhs_list, indent+1);
|
||||
print_ast(node->assign_statement.rhs_list, indent+1);
|
||||
break;
|
||||
case AstNode_BlockStatement:
|
||||
print_indent(indent);
|
||||
gb_printf("(block)\n");
|
||||
print_ast(node->block_statement.list, indent+1);
|
||||
break;
|
||||
|
||||
case AstNode_IfStatement:
|
||||
print_indent(indent);
|
||||
gb_printf("(if)\n");
|
||||
print_ast(node->if_statement.cond, indent+1);
|
||||
print_ast(node->if_statement.body, indent+1);
|
||||
if (node->if_statement.else_statement) {
|
||||
print_indent(indent);
|
||||
gb_printf("(else)\n");
|
||||
print_ast(node->if_statement.else_statement, indent+1);
|
||||
}
|
||||
break;
|
||||
case AstNode_ReturnStatement:
|
||||
print_indent(indent);
|
||||
gb_printf("(return)\n");
|
||||
print_ast(node->return_statement.results, indent+1);
|
||||
break;
|
||||
case AstNode_ForStatement:
|
||||
print_indent(indent);
|
||||
gb_printf("(for)\n");
|
||||
print_ast(node->for_statement.init, indent+1);
|
||||
print_ast(node->for_statement.cond, indent+1);
|
||||
print_ast(node->for_statement.end, indent+1);
|
||||
print_ast(node->for_statement.body, indent+1);
|
||||
break;
|
||||
case AstNode_DeferStatement:
|
||||
print_indent(indent);
|
||||
gb_printf("(defer)\n");
|
||||
print_ast(node->defer_statement.statement, indent+1);
|
||||
break;
|
||||
|
||||
|
||||
case AstNode_VariableDeclaration:
|
||||
print_indent(indent);
|
||||
if (node->variable_declaration.kind == Declaration_Mutable)
|
||||
gb_printf("(decl:var,mutable)\n");
|
||||
else if (node->variable_declaration.kind == Declaration_Immutable)
|
||||
gb_printf("(decl:var,immutable)\n");
|
||||
print_ast(node->variable_declaration.name_list, indent+1);
|
||||
print_ast(node->variable_declaration.type_expression, indent+1);
|
||||
print_ast(node->variable_declaration.value_list, indent+1);
|
||||
break;
|
||||
case AstNode_ProcedureDeclaration:
|
||||
print_indent(indent);
|
||||
if (node->procedure_declaration.kind == Declaration_Mutable)
|
||||
gb_printf("(decl:proc,mutable)\n");
|
||||
else if (node->procedure_declaration.kind == Declaration_Immutable)
|
||||
gb_printf("(decl:proc,immutable)\n");
|
||||
print_ast(node->procedure_declaration.procedure_type, indent+1);
|
||||
print_ast(node->procedure_declaration.body, indent+1);
|
||||
print_ast(node->procedure_declaration.tag, indent+1);
|
||||
break;
|
||||
|
||||
case AstNode_TypeDeclaration:
|
||||
print_indent(indent);
|
||||
gb_printf("(type)\n");
|
||||
print_ast(node->type_declaration.name, indent+1);
|
||||
print_ast(node->type_declaration.type_expression, indent+1);
|
||||
break;
|
||||
|
||||
case AstNode_ProcedureType:
|
||||
print_indent(indent);
|
||||
gb_printf("(type:proc)(%td -> %td)\n", node->procedure_type.param_count, node->procedure_type.result_count);
|
||||
print_ast(node->procedure_type.param_list, indent+1);
|
||||
if (node->procedure_type.results_list) {
|
||||
print_indent(indent+1);
|
||||
gb_printf("->\n");
|
||||
print_ast(node->procedure_type.results_list, indent+1);
|
||||
}
|
||||
break;
|
||||
case AstNode_Field:
|
||||
print_ast(node->field.name_list, indent);
|
||||
print_ast(node->field.type_expression, indent);
|
||||
break;
|
||||
case AstNode_PointerType:
|
||||
print_indent(indent);
|
||||
print_token(node->pointer_type.token);
|
||||
print_ast(node->pointer_type.type_expression, indent+1);
|
||||
break;
|
||||
case AstNode_ArrayType:
|
||||
print_indent(indent);
|
||||
gb_printf("[]\n");
|
||||
print_ast(node->array_type.count, indent+1);
|
||||
print_ast(node->array_type.element, indent+1);
|
||||
break;
|
||||
case AstNode_StructType:
|
||||
print_indent(indent);
|
||||
gb_printf("(struct)\n");
|
||||
print_ast(node->struct_type.field_list, indent+1);
|
||||
break;
|
||||
}
|
||||
|
||||
if (node->next)
|
||||
print_ast(node->next, indent);
|
||||
}
|
||||
9
src/test.odin
Normal file
9
src/test.odin
Normal file
@@ -0,0 +1,9 @@
|
||||
type float: f32;
|
||||
|
||||
main :: proc() {
|
||||
thing :: proc(n: int) -> int, f32 {
|
||||
return n*n, 13.37;
|
||||
}
|
||||
|
||||
_, _ := 1, 2;
|
||||
}
|
||||
760
src/tokenizer.cpp
Normal file
760
src/tokenizer.cpp
Normal file
@@ -0,0 +1,760 @@
|
||||
// TODO(bill): Unicode support
|
||||
b32 rune_is_letter(Rune r) {
|
||||
if (r < 0x80 && gb_char_is_alpha(cast(char)r) || r == '_') {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
b32 rune_is_digit(Rune r) {
|
||||
if (r < 0x80 && gb_is_between(r, '0', '9'))
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
b32 rune_is_whitespace(Rune r) {
|
||||
switch (r) {
|
||||
case ' ':
|
||||
case '\t':
|
||||
case '\n':
|
||||
case '\r':
|
||||
case '\f':
|
||||
case '\v':
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
typedef enum TokenKind TokenKind;
|
||||
enum TokenKind {
|
||||
Token_Invalid,
|
||||
Token_EOF,
|
||||
|
||||
Token__LiteralBegin,
|
||||
Token_Identifier,
|
||||
Token_Integer,
|
||||
Token_Float,
|
||||
Token_Rune,
|
||||
Token_String,
|
||||
Token__LiteralEnd,
|
||||
|
||||
Token__OperatorBegin,
|
||||
Token_Eq, // =
|
||||
|
||||
Token_Not, // ! (Unary Boolean)
|
||||
Token_Hash, // #
|
||||
Token_At, // @ // TODO(bill): Remove
|
||||
Token_Pointer, // ^
|
||||
|
||||
Token_Add, // +
|
||||
Token_Sub, // -
|
||||
Token_Mul, // *
|
||||
Token_Quo, // /
|
||||
Token_Mod, // %
|
||||
|
||||
Token_AddEq, // +=
|
||||
Token_SubEq, // -=
|
||||
Token_MulEq, // *=
|
||||
Token_QuoEq, // /=
|
||||
Token_ModEq, // %=
|
||||
|
||||
Token_And, // &
|
||||
Token_Or, // |
|
||||
Token_Xor, // ~
|
||||
Token_AndNot, // &~
|
||||
|
||||
Token_AndEq, // &=
|
||||
Token_OrEq, // |=
|
||||
Token_XorEq, // ~=
|
||||
Token_AndNotEq, // &~=
|
||||
|
||||
Token_Increment, // ++
|
||||
Token_Decrement, // --
|
||||
Token_ArrowRight, // ->
|
||||
Token_ArrowLeft, // <-
|
||||
|
||||
Token_CmpAnd, // &&
|
||||
Token_CmpOr, // ||
|
||||
Token__ComparisonBegin,
|
||||
Token_CmpEq, // ==
|
||||
Token_Lt, // <
|
||||
Token_Gt, // >
|
||||
Token_NotEq, // !=
|
||||
Token_LtEq, // <=
|
||||
Token_GtEq, // >=
|
||||
Token__ComparisonEnd,
|
||||
Token_CmpAndEq, // &&=
|
||||
Token_CmpOrEq, // ||=
|
||||
|
||||
Token_OpenParen, // (
|
||||
Token_CloseParen, // )
|
||||
Token_OpenBracket, // [
|
||||
Token_CloseBracket, // ]
|
||||
Token_OpenBrace, // {
|
||||
Token_CloseBrace, // }
|
||||
|
||||
Token_Colon, // :
|
||||
Token_Semicolon, // ;
|
||||
Token_Period, // .
|
||||
Token_Comma, // ,
|
||||
Token_Ellipsis, // ...
|
||||
Token__OperatorEnd,
|
||||
|
||||
Token__KeywordBegin,
|
||||
Token_type,
|
||||
Token_proc,
|
||||
|
||||
Token_match, // TODO(bill): switch vs match?
|
||||
Token_break,
|
||||
Token_continue,
|
||||
Token_fallthrough,
|
||||
Token_case,
|
||||
Token_default,
|
||||
|
||||
Token_if,
|
||||
Token_else,
|
||||
Token_for,
|
||||
Token_defer,
|
||||
Token_return,
|
||||
Token_import,
|
||||
Token_cast,
|
||||
|
||||
Token_struct,
|
||||
Token_union,
|
||||
Token_enum,
|
||||
|
||||
Token_inline,
|
||||
Token_no_inline,
|
||||
Token__KeywordEnd,
|
||||
|
||||
Token_Count,
|
||||
};
|
||||
|
||||
char const *TOKEN_STRINGS[] = {
|
||||
"Invalid",
|
||||
"EOF",
|
||||
"_LiteralBegin",
|
||||
"Identifier",
|
||||
"Integer",
|
||||
"Float",
|
||||
"Rune",
|
||||
"String",
|
||||
"_LiteralEnd",
|
||||
"_OperatorBegin",
|
||||
"=",
|
||||
"!",
|
||||
"#",
|
||||
"@",
|
||||
"^",
|
||||
"+",
|
||||
"-",
|
||||
"*",
|
||||
"/",
|
||||
"%",
|
||||
"+=",
|
||||
"-=",
|
||||
"*=",
|
||||
"/=",
|
||||
"%=",
|
||||
"&",
|
||||
"|",
|
||||
"~",
|
||||
"&~",
|
||||
"&=",
|
||||
"|=",
|
||||
"~=",
|
||||
"&~=",
|
||||
"++",
|
||||
"--",
|
||||
"->",
|
||||
"<-",
|
||||
"&&",
|
||||
"||",
|
||||
"_ComparisonBegin",
|
||||
"==",
|
||||
"<",
|
||||
">",
|
||||
"!=",
|
||||
"<=",
|
||||
">=",
|
||||
"_ComparisonEnd",
|
||||
"&&=",
|
||||
"||=",
|
||||
"(",
|
||||
")",
|
||||
"[",
|
||||
"]",
|
||||
"{",
|
||||
"}",
|
||||
":",
|
||||
";",
|
||||
".",
|
||||
",",
|
||||
"...",
|
||||
"_OperatorEnd",
|
||||
"_KeywordBegin",
|
||||
"type",
|
||||
"proc",
|
||||
"switch",
|
||||
"break",
|
||||
"continue",
|
||||
"fallthrough",
|
||||
"case",
|
||||
"default",
|
||||
"if",
|
||||
"else",
|
||||
"for",
|
||||
"defer",
|
||||
"return",
|
||||
"import",
|
||||
"cast",
|
||||
"struct",
|
||||
"union",
|
||||
"enum",
|
||||
"inline",
|
||||
"no_inline",
|
||||
"import",
|
||||
"_KeywordEnd",
|
||||
};
|
||||
|
||||
|
||||
// NOTE(bill): Text is UTF-8, thus why u8 and not char
|
||||
typedef struct Token Token;
|
||||
struct Token {
|
||||
TokenKind kind;
|
||||
String string;
|
||||
isize line, column;
|
||||
};
|
||||
|
||||
|
||||
|
||||
char const *token_kind_to_string(TokenKind kind) {
|
||||
return TOKEN_STRINGS[kind];
|
||||
}
|
||||
|
||||
i32 token_precedence(Token t) {
|
||||
switch (t.kind) {
|
||||
case Token_CmpOr: return 1;
|
||||
case Token_CmpAnd: return 2;
|
||||
|
||||
case Token_CmpEq:
|
||||
case Token_NotEq:
|
||||
case Token_Lt:
|
||||
case Token_Gt:
|
||||
case Token_LtEq:
|
||||
case Token_GtEq:
|
||||
return 3;
|
||||
|
||||
case Token_Add:
|
||||
case Token_Sub:
|
||||
case Token_Or:
|
||||
case Token_Xor:
|
||||
return 4;
|
||||
|
||||
case Token_Mul:
|
||||
case Token_Quo:
|
||||
case Token_Mod:
|
||||
case Token_And:
|
||||
case Token_AndNot:
|
||||
return 5;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
gb_inline b32 token_is_literal(Token t) {
|
||||
return gb_is_between(t.kind, Token__LiteralBegin+1, Token__LiteralEnd-1);
|
||||
}
|
||||
gb_inline b32 token_is_operator(Token t) {
|
||||
return gb_is_between(t.kind, Token__OperatorBegin+1, Token__OperatorEnd-1);
|
||||
}
|
||||
gb_inline b32 token_is_keyword(Token t) {
|
||||
return gb_is_between(t.kind, Token__KeywordBegin+1, Token__KeywordEnd-1);
|
||||
}
|
||||
gb_inline b32 token_is_comparison(Token t) {
|
||||
return gb_is_between(t.kind, Token__ComparisonBegin+1, Token__ComparisonEnd-1);
|
||||
}
|
||||
|
||||
gb_inline void print_token(Token t) { gb_printf("%.*s\n", LIT(t.string)); }
|
||||
|
||||
typedef struct Tokenizer Tokenizer;
|
||||
struct Tokenizer {
|
||||
char *fullpath;
|
||||
u8 *start;
|
||||
u8 *end;
|
||||
|
||||
Rune curr_rune; // current character
|
||||
u8 * curr; // character pos
|
||||
u8 * read_curr; // pos from start
|
||||
u8 * line; // current line pos
|
||||
isize line_count;
|
||||
};
|
||||
|
||||
|
||||
#define tokenizer_error(t, msg, ...) tokenizer_error_(t, __FUNCTION__, msg, ##__VA_ARGS__)
|
||||
void tokenizer_error_(Tokenizer *t, char *function, char *msg, ...) {
|
||||
va_list va;
|
||||
isize column = t->read_curr - t->line+1;
|
||||
if (column < 1)
|
||||
column = 1;
|
||||
|
||||
gb_printf_err("%s()\n", function);
|
||||
gb_printf_err("%s(%td:%td) ", t->fullpath, t->line_count, column);
|
||||
|
||||
va_start(va, msg);
|
||||
gb_printf_err_va(msg, va);
|
||||
va_end(va);
|
||||
|
||||
gb_printf_err("\n");
|
||||
|
||||
gb_exit(1);
|
||||
}
|
||||
|
||||
void advance_to_next_rune(Tokenizer *t) {
|
||||
if (t->read_curr < t->end) {
|
||||
Rune rune;
|
||||
isize width = 1;
|
||||
|
||||
t->curr = t->read_curr;
|
||||
if (t->curr_rune == '\n') {
|
||||
t->line = t->curr;
|
||||
t->line_count++;
|
||||
}
|
||||
rune = *t->read_curr;
|
||||
if (rune == 0) {
|
||||
tokenizer_error(t, "Illegal character NUL");
|
||||
} else if (rune >= 0x80) { // not ASCII
|
||||
width = gb_utf8_decode(t->read_curr, t->end-t->read_curr, &rune);
|
||||
if (rune == GB_RUNE_INVALID && width == 1)
|
||||
tokenizer_error(t, "Illegal UTF-8 encoding");
|
||||
else if (rune == GB_RUNE_BOM && t->curr-t->start > 0)
|
||||
tokenizer_error(t, "Illegal byte order mark");
|
||||
}
|
||||
t->read_curr += width;
|
||||
t->curr_rune = rune;
|
||||
} else {
|
||||
t->curr = t->end;
|
||||
if (t->curr_rune == '\n') {
|
||||
t->line = t->curr;
|
||||
t->line_count++;
|
||||
}
|
||||
t->curr_rune = GB_RUNE_EOF;
|
||||
}
|
||||
}
|
||||
|
||||
b32 init_tokenizer(Tokenizer *t, char *filename) {
|
||||
gbFileContents fc = gb_file_read_contents(gb_heap_allocator(), true, filename);
|
||||
gb_zero_item(t);
|
||||
if (fc.data) {
|
||||
t->start = cast(u8 *)fc.data;
|
||||
t->line = t->read_curr = t->curr = t->start;
|
||||
t->end = t->start + fc.size;
|
||||
|
||||
t->fullpath = gb_path_get_full_name(gb_heap_allocator(), filename);
|
||||
|
||||
t->line_count = 1;
|
||||
|
||||
advance_to_next_rune(t);
|
||||
if (t->curr_rune == GB_RUNE_BOM)
|
||||
advance_to_next_rune(t); // Ignore BOM at file beginning
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
gb_inline void destroy_tokenizer(Tokenizer *t) {
|
||||
gb_free(gb_heap_allocator(), t->start);
|
||||
}
|
||||
|
||||
void tokenizer_skip_whitespace(Tokenizer *t) {
|
||||
for (;;) {
|
||||
if (rune_is_whitespace(t->curr_rune)) {
|
||||
advance_to_next_rune(t);
|
||||
} else if (t->curr_rune == '/') {
|
||||
if (t->read_curr[0] == '/') { // Line comment //
|
||||
while (t->curr_rune != '\n')
|
||||
advance_to_next_rune(t);
|
||||
} else if (t->read_curr[0] == '*') { // (Nested) Block comment /**/
|
||||
isize comment_scope = 1;
|
||||
for (;;) {
|
||||
advance_to_next_rune(t);
|
||||
if (t->curr_rune == '/') {
|
||||
advance_to_next_rune(t);
|
||||
if (t->curr_rune == '*') {
|
||||
advance_to_next_rune(t);
|
||||
comment_scope++;
|
||||
}
|
||||
}
|
||||
if (t->curr_rune == '*') {
|
||||
advance_to_next_rune(t);
|
||||
if (t->curr_rune == '/') {
|
||||
advance_to_next_rune(t);
|
||||
comment_scope--;
|
||||
}
|
||||
}
|
||||
if (comment_scope == 0)
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
gb_inline i32 digit_value(Rune r) {
|
||||
if (gb_char_is_digit(cast(char)r))
|
||||
return r - '0';
|
||||
if (gb_is_between(cast(char)r, 'a', 'f'))
|
||||
return r - 'a' + 10;
|
||||
if (gb_is_between(cast(char)r, 'A', 'F'))
|
||||
return r - 'A' + 10;
|
||||
return 16; // NOTE(bill): Larger than highest possible
|
||||
}
|
||||
|
||||
gb_inline void scan_mantissa(Tokenizer *t, i32 base) {
|
||||
// TODO(bill): Allow for underscores in numbers as a number separator
|
||||
// TODO(bill): Is this a good idea?
|
||||
// while (digit_value(t->curr_rune) < base || t->curr_rune == '_')
|
||||
while (digit_value(t->curr_rune) < base)
|
||||
advance_to_next_rune(t);
|
||||
}
|
||||
|
||||
|
||||
Token scan_number_to_token(Tokenizer *t, b32 seen_decimal_point) {
|
||||
Token token = {};
|
||||
u8 *start_curr = t->curr;
|
||||
token.kind = Token_Integer;
|
||||
token.string = make_string(start_curr, 1);
|
||||
token.line = t->line_count;
|
||||
token.column = t->curr-t->line+1;
|
||||
|
||||
if (seen_decimal_point) {
|
||||
start_curr--;
|
||||
token.kind = Token_Float;
|
||||
scan_mantissa(t, 10);
|
||||
goto exponent;
|
||||
}
|
||||
|
||||
if (t->curr_rune == '0') {
|
||||
u8 *prev = t->curr;
|
||||
advance_to_next_rune(t);
|
||||
if (t->curr_rune == 'b') { // Binary
|
||||
advance_to_next_rune(t);
|
||||
scan_mantissa(t, 2);
|
||||
if (t->curr - prev <= 2)
|
||||
token.kind = Token_Invalid;
|
||||
} else if (t->curr_rune == 'o') { // Octal
|
||||
advance_to_next_rune(t);
|
||||
scan_mantissa(t, 8);
|
||||
if (t->curr - prev <= 2)
|
||||
token.kind = Token_Invalid;
|
||||
} else if (t->curr_rune == 'd') { // Decimal
|
||||
advance_to_next_rune(t);
|
||||
scan_mantissa(t, 10);
|
||||
if (t->curr - prev <= 2)
|
||||
token.kind = Token_Invalid;
|
||||
} else if (t->curr_rune == 'x') { // Hexadecimal
|
||||
advance_to_next_rune(t);
|
||||
scan_mantissa(t, 16);
|
||||
if (t->curr - prev <= 2)
|
||||
token.kind = Token_Invalid;
|
||||
} else {
|
||||
seen_decimal_point = false;
|
||||
scan_mantissa(t, 10);
|
||||
|
||||
if (t->curr_rune == '.' || t->curr_rune == 'e' || t->curr_rune == 'E') {
|
||||
seen_decimal_point = true;
|
||||
goto fraction;
|
||||
}
|
||||
}
|
||||
goto end;
|
||||
}
|
||||
|
||||
scan_mantissa(t, 10);
|
||||
|
||||
fraction:
|
||||
if (t->curr_rune == '.') {
|
||||
token.kind = Token_Float;
|
||||
advance_to_next_rune(t);
|
||||
scan_mantissa(t, 10);
|
||||
}
|
||||
|
||||
exponent:
|
||||
if (t->curr_rune == 'e' || t->curr_rune == 'E') {
|
||||
token.kind = Token_Float;
|
||||
advance_to_next_rune(t);
|
||||
if (t->curr_rune == '-' || t->curr_rune == '+')
|
||||
advance_to_next_rune(t);
|
||||
scan_mantissa(t, 10);
|
||||
}
|
||||
|
||||
end:
|
||||
token.string.len = t->curr - token.string.text;
|
||||
return token;
|
||||
}
|
||||
|
||||
// Quote == " for string and ' for char
|
||||
b32 scan_escape(Tokenizer *t, Rune quote) {
|
||||
isize len = 0;
|
||||
u32 base = 0, max = 0, x = 0;
|
||||
|
||||
Rune r = t->curr_rune;
|
||||
if (r == 'a' ||
|
||||
r == 'b' ||
|
||||
r == 'f' ||
|
||||
r == 'n' ||
|
||||
r == 'r' ||
|
||||
r == 't' ||
|
||||
r == 'v' ||
|
||||
r == '\\' ||
|
||||
r == quote) {
|
||||
advance_to_next_rune(t);
|
||||
return true;
|
||||
} else if (gb_is_between(r, '0', '7')) {
|
||||
len = 3; base = 8; max = 255;
|
||||
} else if (r == 'x') {
|
||||
advance_to_next_rune(t);
|
||||
len = 2; base = 16; max = 255;
|
||||
} else if (r == 'u') {
|
||||
advance_to_next_rune(t);
|
||||
len = 4; base = 16; max = GB_RUNE_MAX;
|
||||
} else if (r == 'U') {
|
||||
advance_to_next_rune(t);
|
||||
len = 8; base = 16; max = GB_RUNE_MAX;
|
||||
} else {
|
||||
if (t->curr_rune < 0)
|
||||
tokenizer_error(t, "Escape sequence was not terminated");
|
||||
else
|
||||
tokenizer_error(t, "Unknown escape sequence");
|
||||
return false;
|
||||
}
|
||||
|
||||
while (len --> 0) {
|
||||
u32 d = cast(u32)digit_value(t->curr_rune);
|
||||
if (d >= base) {
|
||||
if (t->curr_rune < 0)
|
||||
tokenizer_error(t, "Escape sequence was not terminated");
|
||||
else
|
||||
tokenizer_error(t, "Illegal character %d in escape sequence", t->curr_rune);
|
||||
return false;
|
||||
}
|
||||
|
||||
x = x*base + d;
|
||||
advance_to_next_rune(t);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
gb_inline TokenKind token_type_variant2(Tokenizer *t, TokenKind a, TokenKind b) {
|
||||
if (t->curr_rune == '=') {
|
||||
advance_to_next_rune(t);
|
||||
return b;
|
||||
}
|
||||
return a;
|
||||
}
|
||||
|
||||
|
||||
gb_inline TokenKind token_type_variant3(Tokenizer *t, TokenKind a, TokenKind b, Rune ch_c, TokenKind c) {
|
||||
if (t->curr_rune == '=') {
|
||||
advance_to_next_rune(t);
|
||||
return b;
|
||||
}
|
||||
if (t->curr_rune == ch_c) {
|
||||
advance_to_next_rune(t);
|
||||
return c;
|
||||
}
|
||||
return a;
|
||||
}
|
||||
|
||||
gb_inline TokenKind token_type_variant4(Tokenizer *t, TokenKind a, TokenKind b, Rune ch_c, TokenKind c, Rune ch_d, TokenKind d) {
|
||||
if (t->curr_rune == '=') {
|
||||
advance_to_next_rune(t);
|
||||
return b;
|
||||
} else if (t->curr_rune == ch_c) {
|
||||
advance_to_next_rune(t);
|
||||
return c;
|
||||
} else if (t->curr_rune == ch_d) {
|
||||
advance_to_next_rune(t);
|
||||
return d;
|
||||
}
|
||||
return a;
|
||||
}
|
||||
|
||||
Token tokenizer_get_token(Tokenizer *t) {
|
||||
Token token = {};
|
||||
Rune curr_rune;
|
||||
|
||||
tokenizer_skip_whitespace(t);
|
||||
token.string = make_string(t->curr, 1);
|
||||
token.line = t->line_count;
|
||||
token.column = t->curr - t->line + 1;
|
||||
|
||||
curr_rune = t->curr_rune;
|
||||
if (rune_is_letter(curr_rune)) {
|
||||
token.kind = Token_Identifier;
|
||||
while (rune_is_letter(t->curr_rune) || rune_is_digit(t->curr_rune))
|
||||
advance_to_next_rune(t);
|
||||
|
||||
token.string.len = t->curr - token.string.text;
|
||||
|
||||
// NOTE(bill): ALL identifiers are > 1
|
||||
if (token.string.len > 1) {
|
||||
#define KWB if (0) {}
|
||||
#define KWT(keyword, token_type) else if ((gb_size_of(keyword)-1) == token.string.len && gb_strncmp((char *)token.string.text, keyword, token.string.len) == 0) token.kind = token_type
|
||||
#define KWE else {}
|
||||
|
||||
KWB
|
||||
KWT("type", Token_type);
|
||||
KWT("proc", Token_proc);
|
||||
KWT("match", Token_match);
|
||||
KWT("break", Token_break);
|
||||
KWT("continue", Token_continue);
|
||||
KWT("fallthrough", Token_fallthrough);
|
||||
KWT("case", Token_case);
|
||||
KWT("default", Token_default);
|
||||
KWT("if", Token_if);
|
||||
KWT("else", Token_else);
|
||||
KWT("for", Token_for);
|
||||
KWT("defer", Token_defer);
|
||||
KWT("return", Token_return);
|
||||
KWT("import", Token_import);
|
||||
KWT("cast", Token_cast);
|
||||
KWT("struct", Token_struct);
|
||||
KWT("union", Token_union);
|
||||
KWT("enum", Token_enum);
|
||||
KWT("inline", Token_inline);
|
||||
KWT("no_inline", Token_no_inline);
|
||||
KWE
|
||||
|
||||
#undef KWB
|
||||
#undef KWT
|
||||
#undef KWE
|
||||
}
|
||||
|
||||
} else if (gb_is_between(curr_rune, '0', '9')) {
|
||||
token = scan_number_to_token(t, false);
|
||||
} else {
|
||||
advance_to_next_rune(t);
|
||||
switch (curr_rune) {
|
||||
case GB_RUNE_EOF:
|
||||
token.kind = Token_EOF;
|
||||
break;
|
||||
case '"': // String Literal
|
||||
token.kind = Token_String;
|
||||
for (;;) {
|
||||
Rune r = t->curr_rune;
|
||||
if (r == '\n' || r < 0) {
|
||||
tokenizer_error(t, "String literal not terminated");
|
||||
break;
|
||||
}
|
||||
advance_to_next_rune(t);
|
||||
if (r == '"')
|
||||
break;
|
||||
if (r == '\\')
|
||||
scan_escape(t, '"');
|
||||
}
|
||||
break;
|
||||
|
||||
case '\'': { // Rune Literal
|
||||
b32 valid = true;
|
||||
isize len = 0;
|
||||
token.kind = Token_Rune;
|
||||
for (;;) {
|
||||
Rune r = t->curr_rune;
|
||||
if (r == '\n' || r < 0) {
|
||||
if (valid)
|
||||
tokenizer_error(t, "Rune literal not terminated");
|
||||
break;
|
||||
}
|
||||
advance_to_next_rune(t);
|
||||
if (r == '\'')
|
||||
break;
|
||||
len++;
|
||||
if (r == '\\') {
|
||||
if (!scan_escape(t, '\''))
|
||||
valid = false;
|
||||
}
|
||||
}
|
||||
|
||||
if (valid && len != 1)
|
||||
tokenizer_error(t, "Illegal rune literal");
|
||||
} break;
|
||||
|
||||
case '.':
|
||||
token.kind = Token_Period; // Default
|
||||
if (gb_is_between(t->curr_rune, '0', '9')) { // Might be a number
|
||||
token = scan_number_to_token(t, true);
|
||||
} else if (t->curr_rune == '.') { // Could be an ellipsis
|
||||
advance_to_next_rune(t);
|
||||
if (t->curr_rune == '.') {
|
||||
advance_to_next_rune(t);
|
||||
token.kind = Token_Ellipsis;
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case '#': token.kind = Token_Hash; break;
|
||||
case '@': token.kind = Token_At; break;
|
||||
case '^': token.kind = Token_Pointer; break;
|
||||
case ';': token.kind = Token_Semicolon; break;
|
||||
case ',': token.kind = Token_Comma; break;
|
||||
case '(': token.kind = Token_OpenParen; break;
|
||||
case ')': token.kind = Token_CloseParen; break;
|
||||
case '[': token.kind = Token_OpenBracket; break;
|
||||
case ']': token.kind = Token_CloseBracket; break;
|
||||
case '{': token.kind = Token_OpenBrace; break;
|
||||
case '}': token.kind = Token_CloseBrace; break;
|
||||
case ':': token.kind = Token_Colon; break;
|
||||
|
||||
case '*': token.kind = token_type_variant2(t, Token_Mul, Token_MulEq); break;
|
||||
case '/': token.kind = token_type_variant2(t, Token_Quo, Token_QuoEq); break;
|
||||
case '%': token.kind = token_type_variant2(t, Token_Mod, Token_ModEq); break;
|
||||
case '=': token.kind = token_type_variant2(t, Token_Eq, Token_CmpEq); break;
|
||||
case '~': token.kind = token_type_variant2(t, Token_Xor, Token_XorEq); break;
|
||||
case '!': token.kind = token_type_variant2(t, Token_Not, Token_NotEq); break;
|
||||
case '>': token.kind = token_type_variant2(t, Token_Gt, Token_GtEq); break;
|
||||
case '<': token.kind = token_type_variant3(t, Token_Lt, Token_LtEq, '-', Token_ArrowLeft); break;
|
||||
case '+': token.kind = token_type_variant3(t, Token_Add, Token_AddEq, '+', Token_Increment); break;
|
||||
case '-': token.kind = token_type_variant4(t, Token_Sub, Token_SubEq, '-', Token_Decrement, '>', Token_ArrowRight); break;
|
||||
|
||||
case '&':
|
||||
token.kind = Token_And;
|
||||
if (t->curr_rune == '~') {
|
||||
advance_to_next_rune(t);
|
||||
token.kind = token_type_variant2(t, Token_AndNot, Token_AndNotEq);
|
||||
} else {
|
||||
advance_to_next_rune(t);
|
||||
token.kind = token_type_variant3(t, Token_And, Token_AndEq, '&', Token_CmpAnd);
|
||||
if (t->curr_rune == '=') {
|
||||
token.kind = Token_CmpAndEq;
|
||||
advance_to_next_rune(t);
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case '|':
|
||||
token.kind = Token_Or;
|
||||
advance_to_next_rune(t);
|
||||
token.kind = token_type_variant3(t, Token_Or, Token_OrEq, '|', Token_CmpOr);
|
||||
if (t->curr_rune == '=') {
|
||||
token.kind = Token_CmpOrEq;
|
||||
advance_to_next_rune(t);
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
if (curr_rune != GB_RUNE_BOM)
|
||||
tokenizer_error(t, "Illegal character: %c (%d) ", cast(char)curr_rune, curr_rune);
|
||||
token.kind = Token_Invalid;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
token.string.len = t->curr - token.string.text;
|
||||
return token;
|
||||
}
|
||||
45
syntax.odin
Normal file
45
syntax.odin
Normal file
@@ -0,0 +1,45 @@
|
||||
main :: proc(args: []string) -> i32 {
|
||||
if args.count < 2 {
|
||||
io.println("Please specify a .odin file");
|
||||
return 1;
|
||||
}
|
||||
|
||||
for arg_index := 1; arg_index < args.count; arg_index++ {
|
||||
arg := args[arg_index];
|
||||
filename := arg;
|
||||
ext := filepath.path_extension(filename);
|
||||
if (ext != "odin") {
|
||||
io.println("File is not a .odin file");
|
||||
return 1;
|
||||
}
|
||||
output_name := filepath.change_extension(filename, "c");
|
||||
|
||||
parser: Parser;
|
||||
err: Error;
|
||||
parser, err = make_parser(filename);
|
||||
if err {
|
||||
handle_error();
|
||||
}
|
||||
defer destroy_parser(*parser);
|
||||
|
||||
root_node := parse_statement_list(*parser, null);
|
||||
|
||||
code_generator: CodeGenerator;
|
||||
code_generator, err = make_code_generator(*parser, root);
|
||||
if err {
|
||||
handle_error();
|
||||
}
|
||||
defer destroy_code_generator(*code_generator);
|
||||
|
||||
output: File;
|
||||
output, err = file_create(output_nameu);
|
||||
if err {
|
||||
handle_error();
|
||||
}
|
||||
defer file_close(*output);
|
||||
|
||||
convert_to_c_code(*code_generator, root, *output);
|
||||
}
|
||||
|
||||
return 0;
|
||||
};
|
||||
100
todo.md
Normal file
100
todo.md
Normal file
@@ -0,0 +1,100 @@
|
||||
# Odin Language Features
|
||||
|
||||
* variables
|
||||
* constants (compile-time)
|
||||
* procedures
|
||||
- overloading
|
||||
- polymorphic (poly prockets)
|
||||
- multiple return values
|
||||
- Optional forced checking
|
||||
- inline and outline actually meant it!
|
||||
- local scoped procedures
|
||||
* Maybe closures & lambdas?
|
||||
- named parameters
|
||||
- optional parameters
|
||||
* struct
|
||||
* enum
|
||||
* raw union
|
||||
* tagged union or variants or both?
|
||||
* pointers
|
||||
* pointer arithmetic
|
||||
* defer statement
|
||||
* death to headers
|
||||
- no pre-declaration
|
||||
* maybe both inline assembly and intrinsics
|
||||
* `using`
|
||||
* metaprogramming
|
||||
- Compile execution
|
||||
- Introspection
|
||||
- Any type
|
||||
* type_of?
|
||||
|
||||
|
||||
## Basic Types
|
||||
bool - true|false
|
||||
- register size or variable size?
|
||||
|
||||
u8 - Unsigned integer
|
||||
u16
|
||||
u32
|
||||
u64
|
||||
uint - Register size unsigned integer
|
||||
uintptr - integer big enough to store a pointer
|
||||
|
||||
i8 - Signed integer
|
||||
i16
|
||||
i32
|
||||
i64
|
||||
int - Register size signed integer
|
||||
intptr - integer big enough to store a pointer
|
||||
|
||||
|
||||
f32 - Floating Point 32 bit
|
||||
f64 - Floating Point 64 bit
|
||||
|
||||
byte - alias for u8
|
||||
rune - alias for i32
|
||||
|
||||
string - Immutable: once created, it is impossible to change the contents of a string
|
||||
arrays - ArrayType = [count]ElementType
|
||||
count = Expression
|
||||
ElementType = Type
|
||||
Allow for dynamic arrays? Allow use slices?
|
||||
pointers - PointerType = *BaseType
|
||||
BaseType = Type
|
||||
|
||||
struct
|
||||
enum
|
||||
raw_union
|
||||
tagged_union
|
||||
procedure
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## Basic program
|
||||
|
||||
import "io"
|
||||
|
||||
type Vec2: struct {
|
||||
x, y: f32;
|
||||
}
|
||||
|
||||
test_proc :: (x, y: f32) -> f32 {
|
||||
result := x * y;
|
||||
result += 2;
|
||||
result /= x;
|
||||
return result;
|
||||
}
|
||||
|
||||
main :: () {
|
||||
x : i32 = 123;
|
||||
y := 1337; // Type inference (will be `int`)
|
||||
z :: x + y; // Defined and made constant
|
||||
|
||||
io.print("Hellope, World! z = {}", z);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user