From 9ca2246bac823022f39d495398456bcb142d50b9 Mon Sep 17 00:00:00 2001 From: Ginger Bill Date: Wed, 28 Jun 2017 22:38:04 +0100 Subject: [PATCH] Basic allowance for := and :: --- code/demo.odin | 52 ++++++++-------- src/check_stmt.cpp | 147 ++++++++++++++++++++++++++++++++++++++++++++ src/checker.cpp | 150 +++++++++++++++++++++++++++++++++++++++++++++ src/ir.cpp | 77 +++++++++++++++++++++++ src/parser.cpp | 77 ++++++++++++++++++++++- 5 files changed, 477 insertions(+), 26 deletions(-) diff --git a/code/demo.odin b/code/demo.odin index a7a72f4a8..0970d5626 100644 --- a/code/demo.odin +++ b/code/demo.odin @@ -2,12 +2,12 @@ import "fmt.odin"; proc general_stuff() { // Complex numbers - var a = 3 + 4i; - var b: complex64 = 3 + 4i; - var c: complex128 = 3 + 4i; - var d = complex(2, 3); + a := 3 + 4i; + b: complex64 = 3 + 4i; + c: complex128 = 3 + 4i; + d := complex(2, 3); - var e = a / conj(a); + e := a / conj(a); fmt.println("(3+4i)/(3-4i) =", e); fmt.println(real(e), "+", imag(e), "i"); @@ -15,7 +15,8 @@ proc general_stuff() { // C-style variadic procedures foreign __llvm_core { // The variadic part allows for extra type checking too which C does not provide - proc c_printf(fmt: ^u8, #c_vararg args: ..any) -> i32 #link_name "printf"; + proc c_printf(fmt: ^u8, #c_vararg args: ..any) -> i32 + #link_name "printf"; } @@ -27,6 +28,9 @@ proc general_stuff() { var foo = Foo{123, 0.513, "A string"}; var x, y, z = expand_to_tuple(foo); fmt.println(x, y, z); + compile_assert(type_of(x) == int); + compile_assert(type_of(y) == f32); + compile_assert(type_of(z) == string); // By default, all variables are zeroed @@ -51,7 +55,6 @@ proc foreign_blocks() { // See sys/windows.odin } - proc default_arguments() { proc hello(a: int = 9, b: int = 9) { fmt.printf("a is %d; b is %d\n", a, b); @@ -73,9 +76,9 @@ proc named_arguments() { }; using Colour; - proc make_character(name, catch_phrase: string, favorite_color, least_favorite_color: Colour) { + proc make_character(name, catch_phrase: string, favourite_colour, least_favourite_colour: Colour) { fmt.println(); - fmt.printf("My name is %v and I like %v. %v\n", name, favorite_color, catch_phrase); + fmt.printf("My name is %v and I like %v. %v\n", name, favourite_colour, catch_phrase); } make_character("Frank", "¡Ay, caramba!", Blue, Green); @@ -88,24 +91,24 @@ proc named_arguments() { // Named arguments help to disambiguate this problem make_character(catch_phrase = "¡Ay, caramba!", name = "Frank", - least_favorite_color = Green, favorite_color = Blue); + least_favourite_colour = Green, favourite_colour = Blue); // The named arguments can be specifed in any order. - make_character(favorite_color = Octarine, catch_phrase = "U wot m8!", - least_favorite_color = Green, name = "Dennis"); + make_character(favourite_colour = Octarine, catch_phrase = "U wot m8!", + least_favourite_colour = Green, name = "Dennis"); // NOTE: You cannot mix named arguments with normal values /* make_character("Dennis", - favorite_color = Octarine, catch_phrase = "U wot m8!", - least_favorite_color = Green); + favourite_colour = Octarine, catch_phrase = "U wot m8!", + least_favourite_colour = Green); */ // Named arguments can also aid with default arguments - proc numerous_things(s : string, a = 1, b = 2, c = 3.14, + proc numerous_things(s: string, a = 1, b = 2, c = 3.14, d = "The Best String!", e = false, f = 10.3/3.1, g = false) { var g_str = g ? "true" : "false"; fmt.printf("How many?! %s: %v\n", s, g_str); @@ -340,21 +343,22 @@ proc explicit_parametric_polymorphic_procedures() { // An alternative to `union`s for entity in entities { match e in entity.derived { - case Rock: fmt.println("Rock", e.portable_id); - case Door: fmt.println("Door", e.portable_id); - case Monster: fmt.println("Monster", e.portable_id); + case Rock: fmt.println("Rock", e.portable_id, e.x, e.y); + case Door: fmt.println("Door", e.portable_id, e.x, e.y); + case Monster: fmt.println("Monster", e.portable_id, e.x, e.y); } } } + proc main() { general_stuff(); - foreign_blocks(); - default_arguments(); - named_arguments(); - default_return_values(); - call_location(); - explicit_parametric_polymorphic_procedures(); + // foreign_blocks(); + // default_arguments(); + // named_arguments(); + // default_return_values(); + // call_location(); + // explicit_parametric_polymorphic_procedures(); // Command line argument(s)! // -opt=0,1,2,3 diff --git a/src/check_stmt.cpp b/src/check_stmt.cpp index 0eac4936b..21e960a06 100644 --- a/src/check_stmt.cpp +++ b/src/check_stmt.cpp @@ -1666,6 +1666,153 @@ void check_stmt_internal(Checker *c, AstNode *node, u32 flags) { c->context = prev_context; case_end; + case_ast_node(vd, ValueDecl, node); + if (vd->is_mutable) { + Entity **entities = gb_alloc_array(c->allocator, Entity *, vd->names.count); + isize entity_count = 0; + + if (vd->flags & VarDeclFlag_thread_local) { + vd->flags &= ~VarDeclFlag_thread_local; + error(node, "`thread_local` may only be applied to a variable declaration"); + } + + for_array(i, vd->names) { + AstNode *name = vd->names[i]; + Entity *entity = NULL; + if (name->kind != AstNode_Ident) { + error(name, "A variable declaration must be an identifier"); + } else { + Token token = name->Ident; + String str = token.string; + Entity *found = NULL; + // NOTE(bill): Ignore assignments to `_` + if (str != "_") { + found = current_scope_lookup_entity(c->context.scope, str); + } + if (found == NULL) { + entity = make_entity_variable(c->allocator, c->context.scope, token, NULL, false); + entity->identifier = name; + + AstNode *fl = c->context.curr_foreign_library; + if (fl != NULL) { + GB_ASSERT(fl->kind == AstNode_Ident); + entity->Variable.is_foreign = true; + entity->Variable.foreign_library_ident = fl; + } + } else { + TokenPos pos = found->token.pos; + error(token, + "Redeclaration of `%.*s` in this scope\n" + "\tat %.*s(%td:%td)", + LIT(str), LIT(pos.file), pos.line, pos.column); + entity = found; + } + } + if (entity == NULL) { + entity = make_entity_dummy_variable(c->allocator, c->global_scope, ast_node_token(name)); + } + entity->parent_proc_decl = c->context.curr_proc_decl; + entities[entity_count++] = entity; + } + + Type *init_type = NULL; + if (vd->type) { + init_type = check_type(c, vd->type, NULL); + if (init_type == NULL) { + init_type = t_invalid; + } + } + + for (isize i = 0; i < entity_count; i++) { + Entity *e = entities[i]; + GB_ASSERT(e != NULL); + if (e->flags & EntityFlag_Visited) { + e->type = t_invalid; + continue; + } + e->flags |= EntityFlag_Visited; + + if (e->type == NULL) { + e->type = init_type; + } + } + + check_arity_match(c, vd); + check_init_variables(c, entities, entity_count, vd->values, str_lit("variable declaration")); + + for (isize i = 0; i < entity_count; i++) { + Entity *e = entities[i]; + if (e->Variable.is_foreign) { + if (vd->values.count > 0) { + error(e->token, "A foreign variable declaration cannot have a default value"); + } + init_entity_foreign_library(c, e); + + String name = e->token.string; + auto *fp = &c->info.foreigns; + HashKey key = hash_string(name); + Entity **found = map_get(fp, key); + if (found) { + Entity *f = *found; + TokenPos pos = f->token.pos; + Type *this_type = base_type(e->type); + Type *other_type = base_type(f->type); + if (!are_types_identical(this_type, other_type)) { + error(e->token, + "Foreign entity `%.*s` previously declared elsewhere with a different type\n" + "\tat %.*s(%td:%td)", + LIT(name), LIT(pos.file), pos.line, pos.column); + } + } else { + map_set(fp, key, e); + } + } + add_entity(c, c->context.scope, e->identifier, e); + } + + if ((vd->flags & VarDeclFlag_using) != 0) { + Token token = ast_node_token(node); + if (vd->type != NULL && entity_count > 1) { + error(token, "`using` can only be applied to one variable of the same type"); + // TODO(bill): Should a `continue` happen here? + } + + for (isize entity_index = 0; entity_index < entity_count; entity_index++) { + Entity *e = entities[entity_index]; + if (e == NULL) { + continue; + } + if (e->kind != Entity_Variable) { + continue; + } + bool is_immutable = e->Variable.is_immutable; + String name = e->token.string; + Type *t = base_type(type_deref(e->type)); + + if (is_type_struct(t) || is_type_raw_union(t)) { + Scope *scope = scope_of_node(&c->info, t->Record.node); + for_array(i, scope->elements.entries) { + Entity *f = scope->elements.entries[i].value; + if (f->kind == Entity_Variable) { + Entity *uvar = make_entity_using_variable(c->allocator, e, f->token, f->type); + uvar->Variable.is_immutable = is_immutable; + Entity *prev = scope_insert_entity(c->context.scope, uvar); + if (prev != NULL) { + error(token, "Namespace collision while `using` `%.*s` of: %.*s", LIT(name), LIT(prev->token.string)); + return; + } + } + } + } else { + // NOTE(bill): skip the rest to remove extra errors + error(token, "`using` can only be applied to variables of type struct or raw_union"); + return; + } + } + } + } + case_end; + case_ast_node(gd, GenDecl, node); GB_ASSERT(!c->context.scope->is_file); diff --git a/src/checker.cpp b/src/checker.cpp index f490a2a23..238482060 100644 --- a/src/checker.cpp +++ b/src/checker.cpp @@ -1387,6 +1387,7 @@ void init_preload(Checker *c) { bool check_arity_match(Checker *c, AstNodeValueSpec *s); +bool check_arity_match(Checker *c, AstNodeValueDecl *vd); void check_collect_entities(Checker *c, Array nodes, bool is_file_scope); void check_collect_entities_from_when_stmt(Checker *c, AstNodeWhenStmt *ws, bool is_file_scope); @@ -1537,6 +1538,37 @@ bool check_arity_match(Checker *c, AstNodeValueSpec *spec) { return true; } + +bool check_arity_match(Checker *c, AstNodeValueDecl *vd) { + isize lhs = vd->names.count; + isize rhs = vd->values.count; + + if (rhs == 0) { + if (vd->type == NULL) { + error(vd->names[0], "Missing type or initial expression"); + return false; + } + } else if (lhs < rhs) { + if (lhs < vd->values.count) { + AstNode *n = vd->values[lhs]; + gbString str = expr_to_string(n); + error(n, "Extra initial expression `%s`", str); + gb_string_free(str); + } else { + error(vd->names[0], "Extra initial expression"); + } + return false; + } else if (lhs > rhs && rhs != 1) { + AstNode *n = vd->names[rhs]; + gbString str = expr_to_string(n); + error(n, "Missing expression for `%s`", str); + gb_string_free(str); + return false; + } + + return true; +} + void check_collect_entities_from_when_stmt(Checker *c, AstNodeWhenStmt *ws, bool is_file_scope) { Operand operand = {Addressing_Invalid}; check_expr(c, &operand, ws->cond); @@ -1595,6 +1627,124 @@ void check_collect_entities(Checker *c, Array nodes, bool is_file_sco } case_end; + case_ast_node(vd, ValueDecl, decl); + if (vd->is_mutable) { + if (!c->context.scope->is_file) { + // NOTE(bill): local scope -> handle later and in order + break; + } + + // NOTE(bill): You need to store the entity information here unline a constant declaration + isize entity_cap = vd->names.count; + isize entity_count = 0; + Entity **entities = gb_alloc_array(c->allocator, Entity *, entity_cap); + DeclInfo *di = NULL; + if (vd->values.count > 0) { + di = make_declaration_info(heap_allocator(), c->context.scope, c->context.decl); + di->entities = entities; + di->type_expr = vd->type; + di->init_expr = vd->values[0]; + + + if (vd->flags & VarDeclFlag_thread_local) { + error(decl, "#thread_local variable declarations cannot have initialization values"); + } + } + + + for_array(i, vd->names) { + AstNode *name = vd->names[i]; + AstNode *value = NULL; + if (i < vd->values.count) { + value = vd->values[i]; + } + if (name->kind != AstNode_Ident) { + error(name, "A declaration's name must be an identifier, got %.*s", LIT(ast_node_strings[name->kind])); + continue; + } + Entity *e = make_entity_variable(c->allocator, c->context.scope, name->Ident, NULL, false); + e->Variable.is_thread_local = (vd->flags & VarDeclFlag_thread_local) != 0; + e->identifier = name; + + if (vd->flags & VarDeclFlag_using) { + vd->flags &= ~VarDeclFlag_using; // NOTE(bill): This error will be only caught once + error(name, "`using` is not allowed at the file scope"); + } + + AstNode *fl = c->context.curr_foreign_library; + if (fl != NULL) { + GB_ASSERT(fl->kind == AstNode_Ident); + e->Variable.is_foreign = true; + e->Variable.foreign_library_ident = fl; + } + + entities[entity_count++] = e; + + DeclInfo *d = di; + if (d == NULL) { + AstNode *init_expr = value; + d = make_declaration_info(heap_allocator(), e->scope, c->context.decl); + d->type_expr = vd->type; + d->init_expr = init_expr; + } + + add_entity_and_decl_info(c, name, e, d); + } + + if (di != NULL) { + di->entity_count = entity_count; + } + + check_arity_match(c, vd); + } else { + for_array(i, vd->names) { + AstNode *name = vd->names[i]; + if (name->kind != AstNode_Ident) { + error(name, "A declaration's name must be an identifier, got %.*s", LIT(ast_node_strings[name->kind])); + continue; + } + + AstNode *init = unparen_expr(vd->values[i]); + + AstNode *fl = c->context.curr_foreign_library; + DeclInfo *d = make_declaration_info(c->allocator, c->context.scope, c->context.decl); + Entity *e = NULL; + + if (is_ast_node_type(init)) { + e = make_entity_type_name(c->allocator, d->scope, name->Ident, NULL); + d->type_expr = init; + d->init_expr = init; + } else if (init->kind == AstNode_ProcLit) { + ast_node(pl, ProcLit, init); + e = make_entity_procedure(c->allocator, d->scope, name->Ident, NULL, pl->tags); + if (fl != NULL) { + GB_ASSERT(fl->kind == AstNode_Ident); + e->Procedure.foreign_library_ident = fl; + pl->tags |= ProcTag_foreign; + } + GB_PANIC("TODO(bill): Constant procedure literals"); + d->proc_decl = init; + d->type_expr = pl->type; + } else { + e = make_entity_constant(c->allocator, d->scope, name->Ident, NULL, empty_exact_value); + d->type_expr = vd->type; + d->init_expr = init; + } + e->identifier = name; + + if (fl != NULL && e->kind != Entity_Procedure) { + error(name, "Only procedures and variables are allowed to be in a foreign block"); + // continue; + } + + + add_entity_and_decl_info(c, name, e, d); + } + + check_arity_match(c, vd); + } + case_end; + case_ast_node(gd, GenDecl, decl); AstNodeValueSpec empty_spec = {}; AstNodeValueSpec *last_spec = NULL; diff --git a/src/ir.cpp b/src/ir.cpp index 69b4c75f6..685730980 100644 --- a/src/ir.cpp +++ b/src/ir.cpp @@ -5949,6 +5949,83 @@ void ir_build_stmt_internal(irProcedure *proc, AstNode *node) { ir_build_assign_op(proc, addr, v_one, op); case_end; + case_ast_node(vd, ValueDecl, node); + if (vd->is_mutable) { + irModule *m = proc->module; + gbTempArenaMemory tmp = gb_temp_arena_memory_begin(&m->tmp_arena); + + if (vd->values.count == 0) { // declared and zero-initialized + for_array(i, vd->names) { + AstNode *name = vd->names[i]; + if (!ir_is_blank_ident(name)) { + ir_add_local_for_identifier(proc, name, true); + } + } + } else { // Tuple(s) + Array lvals = {}; + Array inits = {}; + array_init(&lvals, m->tmp_allocator, vd->names.count); + array_init(&inits, m->tmp_allocator, vd->names.count); + + for_array(i, vd->names) { + AstNode *name = vd->names[i]; + irAddr lval = ir_addr(NULL); + if (!ir_is_blank_ident(name)) { + ir_add_local_for_identifier(proc, name, false); + lval = ir_build_addr(proc, name); + } + + array_add(&lvals, lval); + } + + for_array(i, vd->values) { + irValue *init = ir_build_expr(proc, vd->values[i]); + Type *t = ir_type(init); + if (t->kind == Type_Tuple) { + for (isize i = 0; i < t->Tuple.variable_count; i++) { + Entity *e = t->Tuple.variables[i]; + irValue *v = ir_emit_struct_ev(proc, init, i); + array_add(&inits, v); + } + } else { + array_add(&inits, init); + } + } + + + for_array(i, inits) { + ir_addr_store(proc, lvals[i], inits[i]); + } + } + + gb_temp_arena_memory_end(tmp); + } else { + for_array(i, vd->names) { + AstNode *ident = vd->names[i]; + GB_ASSERT(ident->kind == AstNode_Ident); + Entity *e = entity_of_ident(proc->module->info, ident); + GB_ASSERT(e != NULL); + if (e->kind == Entity_TypeName) { + // NOTE(bill): Generate a new name + // parent_proc.name-guid + String ts_name = e->token.string; + isize name_len = proc->name.len + 1 + ts_name.len + 1 + 10 + 1; + u8 *name_text = gb_alloc_array(proc->module->allocator, u8, name_len); + i32 guid = cast(i32)proc->module->members.entries.count; + name_len = gb_snprintf(cast(char *)name_text, name_len, "%.*s.%.*s-%d", LIT(proc->name), LIT(ts_name), guid); + String name = make_string(name_text, name_len-1); + + irValue *value = ir_value_type_name(proc->module->allocator, + name, e->type); + map_set(&proc->module->entity_names, hash_entity(e), name); + ir_gen_global_type_name(proc->module, e, name); + } else if (e->kind == Entity_Procedure) { + GB_PANIC("TODO(bill): Procedure values"); + } + } + } + case_end; + case_ast_node(gd, GenDecl, node); for_array(i, gd->specs) { AstNode *spec = gd->specs[i]; diff --git a/src/parser.cpp b/src/parser.cpp index f395b382a..2d12c3a6b 100644 --- a/src/parser.cpp +++ b/src/parser.cpp @@ -337,6 +337,15 @@ AST_NODE_KIND(_DeclBegin, "", i32) \ u64 flags; \ CommentGroup docs; \ }) \ + AST_NODE_KIND(ValueDecl, "value declaration", struct { \ + Array names; \ + AstNode * type; \ + Array values; \ + u64 flags; \ + bool is_mutable; \ + CommentGroup docs; \ + CommentGroup comment; \ + }) \ AST_NODE_KIND(ValueSpec, "value specification", struct { \ Array names; \ AstNode * type; \ @@ -577,6 +586,7 @@ Token ast_node_token(AstNode *node) { case AstNode_Label: return node->Label.token; case AstNode_GenDecl: return node->GenDecl.token; + case AstNode_ValueDecl: return ast_node_token(node->ValueDecl.names[0]); case AstNode_ValueSpec: return ast_node_token(node->ValueSpec.names[0]); case AstNode_ImportSpec: return node->ImportSpec.import_name; case AstNode_TypeSpec: return ast_node_token(node->TypeSpec.name); @@ -817,6 +827,11 @@ AstNode *clone_ast_node(gbAllocator a, AstNode *node) { case AstNode_GenDecl: n->GenDecl.specs = clone_ast_node_array(a, n->GenDecl.specs); break; + case AstNode_ValueDecl: + n->ValueDecl.names = clone_ast_node_array(a, n->ValueDecl.names); + n->ValueDecl.type = clone_ast_node(a, n->ValueDecl.type); + n->ValueDecl.values = clone_ast_node_array(a, n->ValueDecl.values); + break; case AstNode_ValueSpec: n->ValueSpec.names = clone_ast_node_array(a, n->ValueSpec.names); n->ValueSpec.type = clone_ast_node(a, n->ValueSpec.type); @@ -1531,6 +1546,18 @@ AstNode *ast_gen_decl(AstFile *f, Token token, Token open, Token close, Array names, AstNode *type, Array values, bool is_mutable, + CommentGroup docs, CommentGroup comment) { + AstNode *result = make_ast_node(f, AstNode_ValueDecl); + result->ValueDecl.names = names; + result->ValueDecl.type = type; + result->ValueDecl.values = values; + result->ValueDecl.is_mutable = is_mutable; + result->ValueDecl.docs = docs; + result->ValueDecl.comment = comment; + return result; +} + AstNode *ast_value_spec(AstFile *f, Array names, AstNode *type, Array values, CommentGroup docs, CommentGroup comment) { AstNode *result = make_ast_node(f, AstNode_ValueSpec); @@ -3081,7 +3108,52 @@ AstNode *parse_decl(AstFile *f) { return parse_gen_decl(f, token, func); } +AstNode *parse_value_decl(AstFile *f, Array names, CommentGroup docs) { + bool is_mutable = true; + AstNode *type = NULL; + Array values = {}; + + Token colon = expect_token_after(f, Token_Colon, "identifier list"); + type = parse_type_attempt(f); + + if (f->curr_token.kind == Token_Eq || + f->curr_token.kind == Token_Colon) { + Token sep = f->curr_token; next_token(f); + is_mutable = sep.kind != Token_Colon; + values = parse_rhs_expr_list(f); + if (values.count > names.count) { + syntax_error(f->curr_token, "Too many values on the right hand side of the declaration"); + } else if (values.count < names.count && !is_mutable) { + syntax_error(f->curr_token, "All constant declarations must be defined"); + } else if (values.count == 0) { + syntax_error(f->curr_token, "Expected an expression for this declaration"); + } + } + + + if (is_mutable) { + if (type == NULL && values.count == 0) { + syntax_error(f->curr_token, "Missing variable type or initialization"); + return ast_bad_decl(f, f->curr_token, f->curr_token); + } + } else { + if (type == NULL && values.count == 0 && names.count > 0) { + syntax_error(f->curr_token, "Missing constant value"); + return ast_bad_decl(f, f->curr_token, f->curr_token); + } + } + + if (values.data == NULL) { + values = make_ast_node_array(f); + } + + if (f->expr_level >= 0) { + expect_semicolon(f, NULL); + } + + return ast_value_decl(f, names, type, values, is_mutable, docs, f->line_comment); +} AstNode *parse_simple_stmt(AstFile *f, StmtAllowFlag flags) { Token token = f->curr_token; @@ -3091,6 +3163,8 @@ AstNode *parse_simple_stmt(AstFile *f, StmtAllowFlag flags) { return parse_decl(f); } + CommentGroup docs = f->lead_comment; + Array lhs = parse_lhs_expr_list(f); token = f->curr_token; switch (token.kind) { @@ -3163,8 +3237,7 @@ AstNode *parse_simple_stmt(AstFile *f, StmtAllowFlag flags) { } break; } } - - // return parse_value_decl(f, lhs); + return parse_value_decl(f, lhs, docs); } if (lhs.count > 1) {