Browse Source

Golang style enumerations with `iota`

Ginger Bill 8 years ago
parent
commit
ac1566762b
6 changed files with 150 additions and 96 deletions
  1. 24 1
      code/demo.odin
  2. 60 20
      src/checker/checker.c
  3. 10 4
      src/checker/decl.c
  4. 4 0
      src/checker/entity.c
  5. 32 56
      src/checker/expr.c
  6. 20 15
      src/parser.c

+ 24 - 1
code/demo.odin

@@ -8,6 +8,29 @@
 #import "sync.odin";
 #import "utf8.odin";
 
+type float32 f32;
+const (
+	X = iota;
+	Y;
+	Z;
+	A = iota+1;
+	B;
+	C;
+);
+
+type Byte_Size f64;
+const (
+	_            = iota; // ignore first value by assigning to blank identifier
+	KB Byte_Size = 1 << (10 * iota);
+	MB;
+	GB;
+	TB;
+	PB;
+	EB;
+);
+
 proc main() {
-	fmt.println("Here");
+	fmt.println(X, Y, Z);
+	fmt.println(A, B, C);
+	fmt.println(KB, MB, GB, TB, PB, EB);
 }

+ 60 - 20
src/checker/checker.c

@@ -196,6 +196,7 @@ typedef struct CheckerContext {
 	Scope *    scope;
 	DeclInfo * decl;
 	u32        stmt_state_flags;
+	ExactValue iota; // Value of `iota` in a constant declaration; Invalid otherwise
 } CheckerContext;
 
 #define MAP_TYPE TypeAndValue
@@ -534,8 +535,9 @@ void init_universal_scope(BuildContext *bc) {
 	}
 
 // Constants
-	add_global_constant(a, str_lit("true"),  t_untyped_bool, make_exact_value_bool(true));
-	add_global_constant(a, str_lit("false"), t_untyped_bool, make_exact_value_bool(false));
+	add_global_constant(a, str_lit("true"),  t_untyped_bool,    make_exact_value_bool(true));
+	add_global_constant(a, str_lit("false"), t_untyped_bool,    make_exact_value_bool(false));
+	add_global_constant(a, str_lit("iota"),  t_untyped_integer, make_exact_value_integer(0));
 
 	add_global_entity(make_entity_nil(a, str_lit("nil"), t_untyped_nil));
 
@@ -558,6 +560,7 @@ void init_universal_scope(BuildContext *bc) {
 
 	t_u8_ptr = make_type_pointer(a, t_u8);
 	t_int_ptr = make_type_pointer(a, t_int);
+	e_iota = scope_lookup_entity(universal_scope, str_lit("iota"));
 }
 
 
@@ -1054,14 +1057,42 @@ void init_preload(Checker *c) {
 	c->done_preload = true;
 }
 
-
-
-
+void check_arity_match(Checker *c, AstNodeValueSpec *s, AstNodeValueSpec *init);
 
 #include "expr.c"
 #include "decl.c"
 #include "stmt.c"
 
+void check_arity_match(Checker *c, AstNodeValueSpec *s, AstNodeValueSpec *init) {
+	isize lhs = s->names.count;
+	isize rhs = s->values.count;
+	if (init != NULL) {
+		rhs = init->values.count;
+	}
+
+	if (init == NULL && rhs == 0) {
+		if (s->type == NULL) {
+			error_node(s->names.e[0], "Missing type or initial expression");
+		}
+	} else if (lhs < rhs) {
+		if (lhs < s->values.count) {
+			AstNode *n = s->values.e[lhs];
+			gbString str = expr_to_string(n);
+			error_node(n, "Extra initial expression `%s`", str);
+			gb_string_free(str);
+		} else {
+			error_node(s->names.e[0], "Extra initial expression");
+		}
+	} else if (lhs > rhs && (init != NULL || rhs != 1)) {
+		AstNode *n = s->names.e[rhs];
+		gbString str = expr_to_string(n);
+		error_node(n, "Missing expression for `%s`", str);
+		gb_string_free(str);
+	}
+}
+
+
+
 void check_all_global_entities(Checker *c) {
 	Scope *prev_file = {0};
 
@@ -1112,8 +1143,11 @@ void check_global_collect_entities_from_file(Checker *c, Scope *parent_scope, As
 				continue;
 			}
 
-			for_array(spec_index, gd->specs) {
-				AstNode *spec = gd->specs.e[spec_index];
+			AstNodeValueSpec empty_spec_ = {0}, *empty_spec = &empty_spec_;
+			AstNodeValueSpec *last = NULL;
+
+			for_array(iota, gd->specs) {
+				AstNode *spec = gd->specs.e[iota];
 				switch (spec->kind) {
 				case_ast_node(vs, ValueSpec, spec);
 					switch (vs->keyword) {
@@ -1156,34 +1190,40 @@ void check_global_collect_entities_from_file(Checker *c, Scope *parent_scope, As
 
 							add_entity_and_decl_info(c, name, e, d);
 						}
+
+						check_arity_match(c, vs, NULL);
 					} break;
 
 					case Token_const: {
-						for_array(i, vs->values) {
+						if (vs->type != NULL || vs->values.count > 0) {
+							last = vs;
+						} else if (last == NULL) {
+							last = empty_spec;
+						}
+
+						for_array(i, vs->names) {
 							AstNode *name = vs->names.e[i];
-							AstNode *value = unparen_expr(vs->values.e[i]);
 							if (name->kind != AstNode_Ident) {
 								error_node(name, "A declaration's name must be an identifier, got %.*s", LIT(ast_node_strings[name->kind]));
 								continue;
 							}
 
-							ExactValue v = {ExactValue_Invalid};
+							ExactValue v = make_exact_value_integer(iota);
 							Entity *e = make_entity_constant(c->allocator, parent_scope, name->Ident, NULL, v);
 							e->identifier = name;
+
+							AstNode *init = NULL;
+							if (i < last->values.count) {
+								init = last->values.e[i];
+							}
+
 							DeclInfo *di = make_declaration_info(c->allocator, e->scope);
-							di->type_expr = vs->type;
-							di->init_expr = value;
+							di->type_expr = last->type;
+							di->init_expr = init;
 							add_entity_and_decl_info(c, name, e, di);
 						}
 
-						isize lhs_count = vs->names.count;
-						isize rhs_count = vs->values.count;
-
-						if (rhs_count == 0 && vs->type == NULL) {
-							error_node(decl, "Missing type or initial expression");
-						} else if (lhs_count < rhs_count) {
-							error_node(decl, "Extra initial expression");
-						}
+						check_arity_match(c, vs, last);
 					} break;
 					}
 				case_end;

+ 10 - 4
src/checker/decl.c

@@ -229,8 +229,9 @@ void check_type_decl(Checker *c, Entity *e, AstNode *type_expr, Type *def) {
 	}
 }
 
-void check_const_decl(Checker *c, Entity *e, AstNode *type_expr, AstNode *init_expr, Type *named_type) {
+void check_const_decl(Checker *c, Entity *e, AstNode *type_expr, AstNode *init, Type *named_type) {
 	GB_ASSERT(e->type == NULL);
+	GB_ASSERT(e->kind == Entity_Constant);
 
 	if (e->flags & EntityFlag_Visited) {
 		e->type = t_invalid;
@@ -238,6 +239,10 @@ void check_const_decl(Checker *c, Entity *e, AstNode *type_expr, AstNode *init_e
 	}
 	e->flags |= EntityFlag_Visited;
 
+	GB_ASSERT(c->context.iota.kind == ExactValue_Invalid);
+	c->context.iota = e->Constant.value;
+	e->Constant.value = (ExactValue){0};
+
 	if (type_expr) {
 		Type *t = check_type(c, type_expr);
 		if (!is_type_constant_type(t)) {
@@ -245,18 +250,19 @@ void check_const_decl(Checker *c, Entity *e, AstNode *type_expr, AstNode *init_e
 			error_node(type_expr, "Invalid constant type `%s`", str);
 			gb_string_free(str);
 			e->type = t_invalid;
+			c->context.iota = (ExactValue){0};
 			return;
 		}
 		e->type = t;
 	}
 
 	Operand operand = {0};
-	if (init_expr) {
-		// check_expr_or_type(c, &operand, init_expr);
-		check_expr(c, &operand, init_expr);
+	if (init != NULL) {
+		check_expr(c, &operand, init);
 	}
 
 	check_init_constant(c, e, &operand);
+	c->context.iota = (ExactValue){0};
 }
 
 

+ 4 - 0
src/checker/entity.c

@@ -83,6 +83,10 @@ struct Entity {
 	};
 };
 
+
+Entity *e_iota = NULL;
+
+
 Entity *alloc_entity(gbAllocator a, EntityKind kind, Scope *scope, Token token, Type *type) {
 	Entity *entity = gb_alloc_item(a, Entity);
 	entity->kind   = kind;

+ 32 - 56
src/checker/expr.c

@@ -81,8 +81,11 @@ void check_local_collect_entities(Checker *c, AstNodeArray nodes, DelayedEntitie
 			// Will be handled later
 		case_end;
 		case_ast_node(gd, GenericDecl, node);
-			for_array(spec_index, gd->specs) {
-				AstNode *spec = gd->specs.e[spec_index];
+			AstNodeValueSpec empty_spec_ = {0}, *empty_spec = &empty_spec_;
+			AstNodeValueSpec *last = NULL;
+
+			for_array(iota, gd->specs) {
+				AstNode *spec = gd->specs.e[iota];
 				switch (spec->kind) {
 				case_ast_node(vs, ValueSpec, spec);
 					switch (vs->keyword) {
@@ -90,73 +93,38 @@ void check_local_collect_entities(Checker *c, AstNodeArray nodes, DelayedEntitie
 						break;
 
 					case Token_const: {
-						gbTempArenaMemory tmp = gb_temp_arena_memory_begin(&c->tmp_arena);
-
-						isize entity_count = vs->names.count;
-						isize entity_index = 0;
-						Entity **entities = gb_alloc_array(c->tmp_allocator, Entity *, entity_count);
+						if (vs->type != NULL || vs->values.count > 0) {
+							last = vs;
+						} else if (last == NULL) {
+							last = empty_spec;
+						}
 
-						for_array(i, vs->values) {
+						for_array(i, vs->names) {
 							AstNode *name = vs->names.e[i];
-							AstNode *value = unparen_expr(vs->values.e[i]);
 							if (name->kind != AstNode_Ident) {
 								error_node(name, "A declaration's name must be an identifier, got %.*s", LIT(ast_node_strings[name->kind]));
-								entities[entity_index++] = NULL;
 								continue;
 							}
 
-							ExactValue v = {ExactValue_Invalid};
+							ExactValue v = make_exact_value_integer(iota);
 							Entity *e = make_entity_constant(c->allocator, c->context.scope, name->Ident, NULL, v);
 							e->identifier = name;
-							entities[entity_index++] = e;
-							DeclInfo *d = make_declaration_info(c->allocator, e->scope);
-							d->type_expr = vs->type;
-							d->init_expr = value;
-							add_entity_and_decl_info(c, name, e, d);
 
-							DelayedEntity delay = {name, e, d};
-							array_add(delayed_entities, delay);
-						}
-
-						isize lhs_count = vs->names.count;
-						isize rhs_count = vs->values.count;
+							AstNode *init = NULL;
+							if (i < last->values.count) {
+								init = last->values.e[i];
+							}
 
-						// TODO(bill): Better error messages or is this good enough?
-						if (rhs_count == 0 && vs->type == NULL) {
-							error_node(node, "Missing type or initial expression");
-						} else if (lhs_count < rhs_count) {
-							error_node(node, "Extra initial expression");
-						}
+							DeclInfo *di = make_declaration_info(c->allocator, e->scope);
+							di->type_expr = last->type;
+							di->init_expr = init;
+							add_entity_and_decl_info(c, name, e, di);
 
-						if (dof != NULL) {
-							// NOTE(bill): Within a record
-							for_array(i, vs->names) {
-								Entity *e = entities[i];
-								if (e == NULL) {
-									continue;
-								}
-								AstNode *name = vs->names.e[i];
-								if (name->kind != AstNode_Ident) {
-									continue;
-								}
-								Token name_token = name->Ident;
-								if (str_eq(name_token.string, str_lit("_"))) {
-									dof->other_fields[dof->other_field_index++] = e;
-								} else {
-									HashKey key = hash_string(name_token.string);
-									if (map_entity_get(dof->entity_map, key) != NULL) {
-										// TODO(bill): Scope checking already checks the declaration
-										error(name_token, "`%.*s` is already declared in this record", LIT(name_token.string));
-									} else {
-										map_entity_set(dof->entity_map, key, e);
-										dof->other_fields[dof->other_field_index++] = e;
-									}
-									add_entity(c, c->context.scope, name, e);
-								}
-							}
+							DelayedEntity delay = {name, e, di};
+							array_add(delayed_entities, delay);
 						}
 
-						gb_temp_arena_memory_end(tmp);
+						check_arity_match(c, vs, last);
 					} break;
 					}
 				case_end;
@@ -1097,7 +1065,15 @@ void check_identifier(Checker *c, Operand *o, AstNode *n, Type *named_type) {
 			o->type = t_invalid;
 			return;
 		}
-		o->value = e->Constant.value;
+		if (e == e_iota) {
+			if (c->context.iota.kind == ExactValue_Invalid) {
+				error(e->token, "Use of `iota` outside a constant declaration is not allowed");
+				return;
+			}
+			o->value = c->context.iota;
+		} else {
+			o->value = e->Constant.value;
+		}
 		if (o->value.kind == ExactValue_Invalid) {
 			return;
 		}

+ 20 - 15
src/parser.c

@@ -1921,7 +1921,7 @@ AstNode *parse_type(AstFile *f) {
 }
 
 
-#define PARSE_SPEC_PROC(name) AstNode *(name)(AstFile *f, TokenKind keyword)
+#define PARSE_SPEC_PROC(name) AstNode *(name)(AstFile *f, TokenKind keyword, isize index)
 typedef PARSE_SPEC_PROC(*ParserSpecProc);
 
 
@@ -1933,9 +1933,12 @@ AstNode *parse_generic_decl(AstFile *f, TokenKind keyword, ParserSpecProc spec_p
 		open = expect_token(f, Token_OpenParen);
 		array_init(&specs, heap_allocator());
 
-		while (f->curr_token.kind != Token_CloseParen &&
-		       f->curr_token.kind != Token_EOF) {
-			AstNode *spec = spec_proc(f, keyword);
+
+		for (isize index = 0;
+		     f->curr_token.kind != Token_CloseParen &&
+		     f->curr_token.kind != Token_EOF;
+		     index++) {
+			AstNode *spec = spec_proc(f, keyword, index);
 			array_add(&specs, spec);
 			expect_semicolon(f, spec);
 		}
@@ -1943,7 +1946,7 @@ AstNode *parse_generic_decl(AstFile *f, TokenKind keyword, ParserSpecProc spec_p
 		close = expect_token(f, Token_CloseParen);
 	} else {
 		array_init_reserve(&specs, heap_allocator(), 1);
-		array_add(&specs, spec_proc(f, keyword));
+		array_add(&specs, spec_proc(f, keyword, 0));
 	}
 
 	return make_generic_decl(f, token, open, close, specs, 0, false);
@@ -1963,17 +1966,19 @@ PARSE_SPEC_PROC(parse_value_spec) {
 		syntax_error(f->curr_token, "Too many values on the right hand side of the declaration");
 	}
 
-	if (keyword == Token_const) {
-		if (values.count < names.count) {
-			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");
+	switch (keyword) {
+	case Token_var:
+		if (type == NULL && values.count == 0 && names.count > 0) {
+			syntax_error(f->curr_token, "Missing type or initialization");
+			return make_bad_decl(f, f->curr_token, f->curr_token);
 		}
-	}
-
-	if (type == NULL && values.count == 0 && names.count > 0) {
-		syntax_error(f->curr_token, "Missing type or initialization");
-		return make_bad_decl(f, f->curr_token, f->curr_token);
+		break;
+	case Token_const:
+		if (values.count == 0 && (index == 0 || type != NULL)) {
+			syntax_error(f->curr_token, "Missing constant value");
+			return make_bad_decl(f, f->curr_token, f->curr_token);
+		}
+		break;
 	}
 
 	// TODO(bill): Fix this so it does not require it