diff --git a/src/build_cfg.c b/src/build_cfg.c index fbab1e1f..68b360a9 100644 --- a/src/build_cfg.c +++ b/src/build_cfg.c @@ -125,13 +125,32 @@ static const LocalVariable *build_cast( add_unary_op(st, location, CF_PTR_CAST, obj, result); return result; } + if (is_number_type(obj->type) && is_number_type(to)) { const LocalVariable *result = add_local_var(st, to); add_unary_op(st, location, CF_NUM_CAST, obj, result); return result; } + + if (is_integer_type(obj->type) || to->kind == TYPE_ENUM) { + const LocalVariable *i32var = add_local_var(st, intType); + const LocalVariable *result = add_local_var(st, to); + add_unary_op(st, location, CF_NUM_CAST, obj, i32var); + add_unary_op(st, location, CF_INT32_TO_ENUM, i32var, result); + return result; + } + + if (obj->type->kind == TYPE_ENUM && is_integer_type(to)) { + const LocalVariable *i32var = add_local_var(st, intType); + const LocalVariable *result = add_local_var(st, to); + add_unary_op(st, location, CF_ENUM_TO_INT32, obj, i32var); + add_unary_op(st, location, CF_NUM_CAST, i32var, result); + return result; + } + if (obj->type == boolType && is_integer_type(to)) return build_bool_to_int_conversion(st, obj, location, to); + assert(0); } @@ -343,17 +362,17 @@ static const LocalVariable *build_address_of_expression(struct State *st, const case AST_EXPR_DEREF_AND_GET_FIELD: { // &obj->field aka &(obj->field) - const LocalVariable *obj = build_expression(st, address_of_what->data.field.obj); + const LocalVariable *obj = build_expression(st, address_of_what->data.structfield.obj); assert(obj->type->kind == TYPE_POINTER); assert(obj->type->data.valuetype->kind == TYPE_STRUCT); - return build_struct_field_pointer(st, obj, address_of_what->data.field.fieldname, address_of_what->location); + return build_struct_field_pointer(st, obj, address_of_what->data.structfield.fieldname, address_of_what->location); } case AST_EXPR_GET_FIELD: { // &obj.field aka &(obj.field), evaluate as &(&obj)->field - const LocalVariable *obj = build_address_of_expression(st, address_of_what->data.field.obj); + const LocalVariable *obj = build_address_of_expression(st, address_of_what->data.structfield.obj); assert(obj->type->kind == TYPE_POINTER); - return build_struct_field_pointer(st, obj, address_of_what->data.field.fieldname, address_of_what->location); + return build_struct_field_pointer(st, obj, address_of_what->data.structfield.fieldname, address_of_what->location); } case AST_EXPR_INDEXING: { @@ -437,6 +456,14 @@ static const LocalVariable *build_struct_init(struct State *st, const Type *type return instance; } +static int find_enum_member(const Type *enumtype, const char *name) +{ + for (int i = 0; i < enumtype->data.enummembers.count; i++) + if (!strcmp(enumtype->data.enummembers.names[i], name)) + return i; + assert(0); +} + static const LocalVariable *build_expression(struct State *st, const AstExpression *expr) { const ExpressionTypes *types = get_expr_types(st, expr); @@ -453,8 +480,16 @@ static const LocalVariable *build_expression(struct State *st, const AstExpressi result = build_struct_init(st, types->type, &expr->data.call, expr->location); break; case AST_EXPR_GET_FIELD: - temp = build_expression(st, expr->data.field.obj); - result = build_struct_field(st, temp, expr->data.field.fieldname, expr->location); + temp = build_expression(st, expr->data.structfield.obj); + result = build_struct_field(st, temp, expr->data.structfield.fieldname, expr->location); + break; + case AST_EXPR_GET_ENUM_MEMBER: + result = add_local_var(st, types->type); + Constant c = { CONSTANT_ENUM_MEMBER, { + .enum_member.enumtype = types->type, + .enum_member.memberidx = find_enum_member(types->type, expr->data.enummember.membername), + }}; + add_constant(st, expr->location, c, result); break; case AST_EXPR_GET_VARIABLE: if ((temp = find_local_var(st, expr->data.varname))) { diff --git a/src/codegen.c b/src/codegen.c index 878bce0d..0136963a 100644 --- a/src/codegen.c +++ b/src/codegen.c @@ -41,6 +41,8 @@ static LLVMTypeRef codegen_type(const Type *type) free(elems); return result; } + case TYPE_ENUM: + return LLVMInt32Type(); } assert(0); } @@ -152,6 +154,8 @@ static LLVMValueRef codegen_constant(const struct State *st, const Constant *c) return LLVMConstNull(codegen_type(voidPtrType)); case CONSTANT_STRING: return make_a_string_constant(st, c->data.str); + case CONSTANT_ENUM_MEMBER: + return LLVMConstInt(LLVMInt32Type(), c->data.enum_member.memberidx, false); } assert(0); } @@ -302,7 +306,13 @@ static void codegen_instruction(const struct State *st, const CfInstruction *ins case CF_BOOL_NEGATE: setdest(LLVMBuildXor(st->builder, getop(0), LLVMConstInt(LLVMInt1Type(), 1, false), "bool_negate")); break; case CF_PTR_CAST: setdest(LLVMBuildBitCast(st->builder, getop(0), codegen_type(ins->destvar->type), "ptr_cast")); break; - case CF_VARCPY: setdest(getop(0)); break; + + // various no-ops + case CF_VARCPY: + case CF_INT32_TO_ENUM: + case CF_ENUM_TO_INT32: + setdest(getop(0)); + break; case CF_NUM_ADD: setdest(build_num_operation(st->builder, getop(0), getop(1), ins->operands[0]->type, LLVMBuildAdd, LLVMBuildAdd, LLVMBuildFAdd)); break; case CF_NUM_SUB: setdest(build_num_operation(st->builder, getop(0), getop(1), ins->operands[0]->type, LLVMBuildSub, LLVMBuildSub, LLVMBuildFSub)); break; diff --git a/src/free.c b/src/free.c index 487cead0..91487427 100644 --- a/src/free.c +++ b/src/free.c @@ -72,8 +72,8 @@ static void free_expression(const AstExpression *expr) break; case AST_EXPR_GET_FIELD: case AST_EXPR_DEREF_AND_GET_FIELD: - free_expression(expr->data.field.obj); - free(expr->data.field.obj); + free_expression(expr->data.structfield.obj); + free(expr->data.structfield.obj); break; case AST_EXPR_INDEXING: case AST_EXPR_ADD: @@ -114,6 +114,7 @@ static void free_expression(const AstExpression *expr) free_constant(&expr->data.constant); break; case AST_EXPR_GET_VARIABLE: + case AST_EXPR_GET_ENUM_MEMBER: break; } } @@ -193,6 +194,9 @@ void free_ast(AstToplevelNode *topnodelist) free_name_type_value(ntv); free(t->data.structdef.fields.ptr); break; + case AST_TOPLEVEL_DEFINE_ENUM: + free(t->data.enumdef.membernames); + break; case AST_TOPLEVEL_IMPORT: free(t->data.import.path); break; @@ -220,7 +224,7 @@ void free_type_context(const TypeContext *ctx) { for (GlobalVariable **g = ctx->globals.ptr; g < End(ctx->globals); g++) free(*g); - for (Type **t = ctx->structs.ptr; t < End(ctx->structs); t++) + for (Type **t = ctx->owned_types.ptr; t < End(ctx->owned_types); t++) free_type(*t); for (Signature *s = ctx->function_signatures.ptr; s < End(ctx->function_signatures); s++) free_signature(s); @@ -228,7 +232,7 @@ void free_type_context(const TypeContext *ctx) free(ctx->globals.ptr); free(ctx->locals.ptr); free(ctx->types.ptr); - free(ctx->structs.ptr); + free(ctx->owned_types.ptr); free(ctx->function_signatures.ptr); } diff --git a/src/jou_compiler.h b/src/jou_compiler.h index 812479a4..91251472 100644 --- a/src/jou_compiler.h +++ b/src/jou_compiler.h @@ -31,6 +31,7 @@ typedef struct AstStatement AstStatement; typedef struct AstToplevelNode AstToplevelNode; typedef struct AstFunctionDef AstFunctionDef; typedef struct AstStructDef AstStructDef; +typedef struct AstEnumDef AstEnumDef; typedef struct AstImport AstImport; typedef struct GlobalVariable GlobalVariable; @@ -95,6 +96,7 @@ struct Token { // Constants can appear in AST and also compilation steps after AST. struct Constant { enum ConstantKind { + CONSTANT_ENUM_MEMBER, CONSTANT_INTEGER, CONSTANT_FLOAT, CONSTANT_DOUBLE, @@ -107,6 +109,7 @@ struct Constant { char *str; char double_or_float_text[100]; // convenient because LLVM wants a string anyway bool boolean; + struct { const Type *enumtype; int memberidx; } enum_member; } data; }; #define copy_constant(c) ( (c)->kind==CONSTANT_STRING ? (Constant){ CONSTANT_STRING, {.str=strdup((c)->data.str)} } : *(c) ) @@ -162,6 +165,7 @@ struct AstExpression { enum AstExpressionKind { AST_EXPR_CONSTANT, + AST_EXPR_GET_ENUM_MEMBER, // Cannot be just a Constant because ast doesn't know about Types. AST_EXPR_FUNCTION_CALL, AST_EXPR_BRACE_INIT, AST_EXPR_GET_FIELD, // foo.bar @@ -198,7 +202,8 @@ struct AstExpression { Constant constant; // AST_EXPR_CONSTANT char varname[100]; // AST_EXPR_GET_VARIABLE AstCall call; // AST_EXPR_CALL, AST_EXPR_INSTANTIATE - struct { AstExpression *obj; char fieldname[100]; } field; // AST_EXPR_GET_FIELD, AST_EXPR_DEREF_AND_GET_FIELD + struct { AstExpression *obj; char fieldname[100]; } structfield; // AST_EXPR_GET_FIELD, AST_EXPR_DEREF_AND_GET_FIELD + struct { char enumname[100]; char membername[100]; } enummember; struct { AstExpression *obj; AstType type; } as; /* The "operands" pointer is an array of 1 to 2 expressions. @@ -289,6 +294,12 @@ struct AstStructDef { List(AstNameTypeValue) fields; }; +struct AstEnumDef { + char name[100]; + char (*membernames)[100]; + int nmembers; +}; + struct AstImport { char *path; // Relative to current working directory, so e.g. "blah/stdlib/io.jou" char symbolname[100]; @@ -305,12 +316,14 @@ struct AstToplevelNode { AST_TOPLEVEL_DEFINE_FUNCTION, AST_TOPLEVEL_DEFINE_GLOBAL_VARIABLE, AST_TOPLEVEL_DEFINE_STRUCT, + AST_TOPLEVEL_DEFINE_ENUM, AST_TOPLEVEL_IMPORT, } kind; union { AstNameTypeValue globalvar; // AST_TOPLEVEL_DECLARE_GLOBAL_VARIABLE AstFunctionDef funcdef; // AST_TOPLEVEL_DECLARE_FUNCTION, AST_TOPLEVEL_DEFINE_FUNCTION (body is empty for declaring) AstStructDef structdef; // AST_TOPLEVEL_DEFINE_STRUCT + AstEnumDef enumdef; // AST_TOPLEVEL_DEFINE_ENUM AstImport import; // AST_TOPLEVEL_IMPORT } data; }; @@ -328,12 +341,14 @@ struct Type { TYPE_ARRAY, TYPE_STRUCT, TYPE_OPAQUE_STRUCT, // struct with unknown members + TYPE_ENUM, } kind; union { int width_in_bits; // TYPE_SIGNED_INTEGER, TYPE_UNSIGNED_INTEGER, TYPE_FLOATING_POINT const Type *valuetype; // TYPE_POINTER struct { const Type *membertype; int len; } array; // TYPE_ARRAY struct { int count; char (*names)[100]; const Type **types; } structfields; // TYPE_STRUCT + struct { int count; char (*names)[100]; } enummembers; } data; }; @@ -364,6 +379,7 @@ const Type *get_pointer_type(const Type *t); // result lives as long as t const Type *get_array_type(const Type *t, int len); // result lives as long as t const Type *type_of_constant(const Constant *c); Type *create_opaque_struct(const char *name); +Type *create_enum(const char *name, int membercount, char (*membernames)[100]); void set_struct_fields( Type *structtype, // must be opaque struct, becomes non-opaque int fieldcount, @@ -423,7 +439,7 @@ struct TypeContext { List(ExpressionTypes *) expr_types; List(GlobalVariable *) globals; // TODO: probably doesn't need to has pointers List(LocalVariable *) locals; - List(Type *) structs; // These will be freed later + List(Type *) owned_types; // These will be freed later List(const Type *) types; List(Signature) function_signatures; }; @@ -431,7 +447,9 @@ struct TypeContext { /* Type checking is split into several stages: 1. Create types. After this, structs defined in Jou exist, but - they are opaque and contain no members. + they are opaque and contain no members. Enums exist and contain + their members (although it doesn't really matter whether enum + members are handled in step 1 or 2). 2. Check signatures, global variables and struct bodies. This step assumes that all types exist, but doesn't need to know what fields each struct has. @@ -489,6 +507,8 @@ struct CfInstruction { CF_NUM_EQ, CF_NUM_LT, CF_NUM_CAST, + CF_ENUM_TO_INT32, + CF_INT32_TO_ENUM, CF_BOOL_NEGATE, // TODO: get rid of this? CF_VARCPY, // similar to assignment statements: var1 = var2 } kind; diff --git a/src/main.c b/src/main.c index 2c25512b..2a75404a 100644 --- a/src/main.c +++ b/src/main.c @@ -264,6 +264,8 @@ static bool astnode_conflicts_with_an_import(const AstToplevelNode *astnode, con return import->kind == EXPSYM_GLOBAL_VAR && !strcmp(import->name, astnode->data.globalvar.name); case AST_TOPLEVEL_DEFINE_STRUCT: return import->kind == EXPSYM_TYPE && !strcmp(import->name, astnode->data.structdef.name); + case AST_TOPLEVEL_DEFINE_ENUM: + return import->kind == EXPSYM_TYPE && !strcmp(import->name, astnode->data.enumdef.name); case AST_TOPLEVEL_IMPORT: return false; case AST_TOPLEVEL_END_OF_FILE: diff --git a/src/parse.c b/src/parse.c index 4b905a12..a1b4d837 100644 --- a/src/parse.c +++ b/src/parse.c @@ -378,6 +378,11 @@ static AstExpression parse_elementary_expression(const Token **tokens) } else if (is_operator(&(*tokens)[1], "{")) { expr.kind = AST_EXPR_BRACE_INIT; expr.data.call = parse_call(tokens, '{', '}', true); + } else if (is_operator(&(*tokens)[1], "::") && (*tokens)[2].type == TOKEN_NAME) { + expr.kind = AST_EXPR_GET_ENUM_MEMBER; + safe_strcpy(expr.data.enummember.enumname, (*tokens)[0].data.name); + safe_strcpy(expr.data.enummember.membername, (*tokens)[2].data.name); + *tokens += 3; } else { expr.kind = AST_EXPR_GET_VARIABLE; safe_strcpy(expr.data.varname, (*tokens)->data.name); @@ -422,12 +427,12 @@ static AstExpression parse_expression_with_fields_and_indexing(const Token **tok .location = startop->location, .kind = (is_operator(startop, "->") ? AST_EXPR_DEREF_AND_GET_FIELD : AST_EXPR_GET_FIELD), }; - result2.data.field.obj = malloc(sizeof *result2.data.field.obj); - *result2.data.field.obj = result; + result2.data.structfield.obj = malloc(sizeof *result2.data.structfield.obj); + *result2.data.structfield.obj = result; if ((*tokens)->type != TOKEN_NAME) fail_with_parse_error(*tokens, "a field name"); - safe_strcpy(result2.data.field.fieldname, (*tokens)->data.name); + safe_strcpy(result2.data.structfield.fieldname, (*tokens)->data.name); ++*tokens; result = result2; @@ -767,6 +772,37 @@ static AstStructDef parse_structdef(const Token **tokens) return result; } +static AstEnumDef parse_enumdef(const Token **tokens) +{ + AstEnumDef result = {0}; + if ((*tokens)->type != TOKEN_NAME) + fail_with_parse_error(*tokens, "a name for the enum"); + safe_strcpy(result.name, (*tokens)->data.name); + ++*tokens; + + parse_start_of_body(tokens); + List(const char*) membernames = {0}; + + while ((*tokens)->type != TOKEN_DEDENT) { + for (const char **old = membernames.ptr; old < End(membernames); old++) + if (!strcmp(*old, (*tokens)->data.name)) + fail_with_error((*tokens)->location, "the enum has two members named '%s'", (*tokens)->data.name); + + Append(&membernames, (*tokens)->data.name); + ++*tokens; + eat_newline(tokens); + } + + result.nmembers = membernames.len; + result.membernames = malloc(sizeof(result.membernames[0]) * result.nmembers); + for (int i = 0; i < result.nmembers; i++) + strcpy(result.membernames[i], membernames.ptr[i]); + + free(membernames.ptr); + ++*tokens; + return result; +} + static char *get_actual_import_path(const Token *pathtoken, const char *stdlib_path) { if (pathtoken->type != TOKEN_STRING) @@ -890,6 +926,10 @@ static AstToplevelNode parse_toplevel_node(const Token **tokens) ++*tokens; result.kind = AST_TOPLEVEL_DEFINE_STRUCT; result.data.structdef = parse_structdef(tokens); + } else if (is_keyword(*tokens, "enum")) { + ++*tokens; + result.kind = AST_TOPLEVEL_DEFINE_ENUM; + result.data.enumdef = parse_enumdef(tokens); } else { fail_with_parse_error(*tokens, "a definition or declaration"); } diff --git a/src/print.c b/src/print.c index facbf068..215fca53 100644 --- a/src/print.c +++ b/src/print.c @@ -27,6 +27,9 @@ static void print_string(const char *s) static void print_constant(const Constant *c) { switch(c->kind) { + case CONSTANT_ENUM_MEMBER: + printf("enum member %d of %s", c->data.enum_member.memberidx, c->data.enum_member.enumtype->name); + break; case CONSTANT_BOOL: printf(c->data.boolean ? "True" : "False"); break; @@ -195,12 +198,16 @@ static void print_ast_expression(const AstExpression *expr, struct TreePrinter t printf("dereference and "); __attribute__((fallthrough)); case AST_EXPR_GET_FIELD: - printf("get field \"%s\"\n", expr->data.field.fieldname); - print_ast_expression(expr->data.field.obj, print_tree_prefix(tp, true)); + printf("get struct field \"%s\"\n", expr->data.structfield.fieldname); + print_ast_expression(expr->data.structfield.obj, print_tree_prefix(tp, true)); + break; + case AST_EXPR_GET_ENUM_MEMBER: + printf("get member \"%s\" from enum \"%s\"\n", + expr->data.enummember.membername, expr->data.enummember.enumname); break; case AST_EXPR_SIZEOF: printf("sizeof expression\n"); - print_ast_expression(expr->data.field.obj, print_tree_prefix(tp, true)); + print_ast_expression(expr->data.structfield.obj, print_tree_prefix(tp, true)); break; case AST_EXPR_GET_VARIABLE: printf("get variable \"%s\"\n", expr->data.varname); @@ -398,6 +405,12 @@ void print_ast(const AstToplevelNode *topnodelist) printf("\n"); } break; + case AST_TOPLEVEL_DEFINE_ENUM: + printf("Define enum \"%s\" with %d members:\n", + topnodelist->data.enumdef.name, topnodelist->data.enumdef.nmembers); + for (int i = 0; i < topnodelist->data.enumdef.nmembers; i++) + printf(" %s\n", topnodelist->data.enumdef.membernames[i]); + break; case AST_TOPLEVEL_END_OF_FILE: printf("End of file.\n"); break; @@ -471,6 +484,12 @@ static void print_cf_instruction(const CfInstruction *ins) ins->destvar->type->data.width_in_bits, very_short_number_type_description(ins->destvar->type)); break; + case CF_ENUM_TO_INT32: + printf("cast %s from enum to 32-bit signed int", varname(ins->operands[0])); + break; + case CF_INT32_TO_ENUM: + printf("cast %s from 32-bit signed int to enum", varname(ins->operands[0])); + break; case CF_CONSTANT: print_constant(&ins->data.constant); break; diff --git a/src/tokenize.c b/src/tokenize.c index da69e719..506aabb2 100644 --- a/src/tokenize.c +++ b/src/tokenize.c @@ -216,7 +216,7 @@ static bool is_keyword(const char *s) { const char *keywords[] = { "from", "import", - "def", "declare", "struct", "global", + "def", "declare", "struct", "enum", "global", "return", "if", "elif", "else", "while", "for", "break", "continue", "True", "False", "NULL", "and", "or", "not", "as", "sizeof", @@ -329,7 +329,7 @@ static const char *read_operator(struct State *st) const char *operators[] = { // Longer operators first, so that '==' does not parse as '=' '=' "...", "===", "!==", - "==", "!=", "->", "<=", ">=", "++", "--", "+=", "-=", "*=", "/=", "%=", + "==", "!=", "->", "<=", ">=", "++", "--", "+=", "-=", "*=", "/=", "%=", "::", ".", ",", ":", ";", "=", "(", ")", "{", "}", "[", "]", "&", "%", "*", "/", "+", "-", "<", ">", NULL, }; diff --git a/src/typecheck.c b/src/typecheck.c index 6cb15104..923bfe47 100644 --- a/src/typecheck.c +++ b/src/typecheck.c @@ -40,17 +40,27 @@ ExportSymbol *typecheck_step1_create_types(TypeContext *ctx, const AstToplevelNo List(ExportSymbol) exports = {0}; for (; ast->kind != AST_TOPLEVEL_END_OF_FILE; ast++) { - if (ast->kind != AST_TOPLEVEL_DEFINE_STRUCT) - continue; - + Type *t; char name[100]; - safe_strcpy(name, ast->data.structdef.name); + + switch(ast->kind) { + case AST_TOPLEVEL_DEFINE_STRUCT: + safe_strcpy(name, ast->data.structdef.name); + t = create_opaque_struct(name); + break; + case AST_TOPLEVEL_DEFINE_ENUM: + safe_strcpy(name, ast->data.enumdef.name); + t = create_enum(name, ast->data.enumdef.nmembers, ast->data.enumdef.membernames); + break; + default: + continue; + } if (find_type(ctx, name)) fail_with_error(ast->location, "a type named '%s' already exists", name); - Type *t = create_opaque_struct(name); - Append(&ctx->structs, t); + Append(&ctx->types, t); + Append(&ctx->owned_types, t); struct ExportSymbol es = { .kind = EXPSYM_TYPE, .data.type = t }; safe_strcpy(es.name, name); @@ -180,7 +190,7 @@ static void handle_struct_fields(TypeContext *ctx, const AstStructDef *structdef { // Previous type-checking pass created an opaque struct. Type *type = NULL; - for (Type **s = ctx->structs.ptr; s < End(ctx->structs); s++) + for (Type **s = ctx->owned_types.ptr; s < End(ctx->owned_types); s++) if (!strcmp((*s)->name, structdef->name)) { type = *s; break; @@ -199,7 +209,7 @@ static void handle_struct_fields(TypeContext *ctx, const AstStructDef *structdef fieldtypes[i] = type_from_ast(ctx, &structdef->fields.ptr[i].type); Type *structtype = NULL; - for (Type **t = ctx->structs.ptr; t < End(ctx->structs); t++) { + for (Type **t = ctx->owned_types.ptr; t < End(ctx->owned_types); t++) { if (!strcmp((*t)->name, structdef->name)) { structtype = *t; break; @@ -230,9 +240,12 @@ ExportSymbol *typecheck_step2_signatures_globals_structbodies(TypeContext *ctx, // took care of that. handle_struct_fields(ctx, &ast->data.structdef); break; - case AST_TOPLEVEL_END_OF_FILE: + case AST_TOPLEVEL_DEFINE_ENUM: case AST_TOPLEVEL_IMPORT: + // Everything done in previous type-checking steps. break; + case AST_TOPLEVEL_END_OF_FILE: + assert(0); } } @@ -325,6 +338,8 @@ static void check_explicit_cast(const Type *from, const Type *to, Location locat from != to // TODO: should probably be error if it's the same type. && !(is_pointer_type(from) && is_pointer_type(to)) && !(is_number_type(from) && is_number_type(to)) + && !(is_integer_type(from) && to->kind == TYPE_ENUM) + && !(from->kind == TYPE_ENUM && is_integer_type(to)) // TODO: pointer-to-int, int-to-pointer ) { @@ -385,6 +400,7 @@ static const Type *check_binop( bool got_integers = is_integer_type(lhstypes->type) && is_integer_type(rhstypes->type); bool got_numbers = is_number_type(lhstypes->type) && is_number_type(rhstypes->type); + bool got_enums = lhstypes->type->kind == TYPE_ENUM && rhstypes->type->kind == TYPE_ENUM; bool got_pointers = ( is_pointer_type(lhstypes->type) && is_pointer_type(rhstypes->type) @@ -396,23 +412,27 @@ static const Type *check_binop( ) ); - if (!got_integers && !got_numbers && !(got_pointers && (op == AST_EXPR_EQ || op == AST_EXPR_NE))) + if(!( + got_integers + || got_numbers + || ((got_enums || got_pointers) && (op == AST_EXPR_EQ || op == AST_EXPR_NE)) + )) fail_with_error(location, "wrong types: cannot %s %s and %s", do_what, lhstypes->type->name, rhstypes->type->name); - // TODO: is this a good idea? - const Type *cast_type; + const Type *cast_type = NULL; if (got_integers) { cast_type = get_integer_type( max(lhstypes->type->data.width_in_bits, rhstypes->type->data.width_in_bits), lhstypes->type->kind == TYPE_SIGNED_INTEGER || rhstypes->type->kind == TYPE_SIGNED_INTEGER ); } - if (got_pointers) { - cast_type = voidPtrType; - } - if (got_numbers && !got_integers) { + if (got_numbers && !got_integers) cast_type = (lhstypes->type == doubleType || rhstypes->type == doubleType) ? doubleType : floatType; - } + if (got_pointers) + cast_type = voidPtrType; + if (got_enums) + cast_type = intType; + assert(cast_type); do_implicit_cast(lhstypes, cast_type, (Location){0}, NULL); do_implicit_cast(rhstypes, cast_type, (Location){0}, NULL); @@ -444,6 +464,7 @@ static const char *short_expression_description(const AstExpression *expr) switch(expr->kind) { // Imagine "cannot assign to" in front of these, e.g. "cannot assign to a constant" case AST_EXPR_CONSTANT: return "a constant"; + case AST_EXPR_GET_ENUM_MEMBER: return "an enum member"; case AST_EXPR_SIZEOF: return "a sizeof expression"; case AST_EXPR_FUNCTION_CALL: return "a function call"; case AST_EXPR_BRACE_INIT: return "a newly created instance"; @@ -485,7 +506,7 @@ static const char *short_expression_description(const AstExpression *expr) case AST_EXPR_GET_FIELD: case AST_EXPR_DEREF_AND_GET_FIELD: - snprintf(result, sizeof result, "field '%s'", expr->data.field.fieldname); + snprintf(result, sizeof result, "field '%s'", expr->data.structfield.fieldname); break; } @@ -687,12 +708,56 @@ static const Type *typecheck_struct_init(TypeContext *ctx, const AstCall *call, return t; } +static const char *very_short_type_description(const Type *t) +{ + switch(t->kind) { + case TYPE_STRUCT: + case TYPE_OPAQUE_STRUCT: + return "a struct"; + case TYPE_ENUM: + return "an enum"; + case TYPE_VOID_POINTER: + case TYPE_POINTER: + return "a pointer type"; + case TYPE_SIGNED_INTEGER: + case TYPE_UNSIGNED_INTEGER: + case TYPE_FLOATING_POINT: + return "a number type"; + case TYPE_ARRAY: + return "an array type"; + case TYPE_BOOL: + return "the built-in boolean type"; + } +} + +static bool enum_member_exists(const Type *t, const char *name) +{ + assert(t->kind == TYPE_ENUM); + for (int i = 0; i < t->data.enummembers.count; i++) + if (!strcmp(t->data.enummembers.names[i], name)) + return true; + return false; +} + static ExpressionTypes *typecheck_expression(TypeContext *ctx, const AstExpression *expr) { const Type *temptype; const Type *result; switch(expr->kind) { + case AST_EXPR_GET_ENUM_MEMBER: + result = find_type(ctx, expr->data.enummember.enumname); + if (!result) + fail_with_error( + expr->location, "there is no type named '%s'", expr->data.enummember.enumname); + if (result->kind != TYPE_ENUM) + fail_with_error( + expr->location, "the '::' syntax is only for enums, but %s is %s", + expr->data.enummember.enumname, very_short_type_description(result)); + if (!enum_member_exists(result, expr->data.enummember.membername)) + fail_with_error(expr->location, "enum %s has no member named '%s'", + expr->data.enummember.enumname, expr->data.enummember.membername); + break; case AST_EXPR_FUNCTION_CALL: { const Type *ret = typecheck_function_call(ctx, &expr->data.call, expr->location); @@ -709,22 +774,22 @@ static ExpressionTypes *typecheck_expression(TypeContext *ctx, const AstExpressi result = typecheck_struct_init(ctx, &expr->data.call, expr->location); break; case AST_EXPR_GET_FIELD: - temptype = typecheck_expression_not_void(ctx, expr->data.field.obj)->type; + temptype = typecheck_expression_not_void(ctx, expr->data.structfield.obj)->type; if (temptype->kind != TYPE_STRUCT) fail_with_error( expr->location, "left side of the '.' operator must be a struct, not %s", temptype->name); - result = typecheck_struct_field(temptype, expr->data.field.fieldname, expr->location); + result = typecheck_struct_field(temptype, expr->data.structfield.fieldname, expr->location); break; case AST_EXPR_DEREF_AND_GET_FIELD: - temptype = typecheck_expression_not_void(ctx, expr->data.field.obj)->type; + temptype = typecheck_expression_not_void(ctx, expr->data.structfield.obj)->type; if (temptype->kind != TYPE_POINTER || temptype->data.valuetype->kind != TYPE_STRUCT) fail_with_error( expr->location, "left side of the '->' operator must be a pointer to a struct, not %s", temptype->name); - result = typecheck_struct_field(temptype->data.valuetype, expr->data.field.fieldname, expr->location); + result = typecheck_struct_field(temptype->data.valuetype, expr->data.structfield.fieldname, expr->location); break; case AST_EXPR_INDEXING: result = typecheck_indexing(ctx, &expr->data.operands[0], &expr->data.operands[1]); diff --git a/src/types.c b/src/types.c index 0b0f8bd8..b158c06d 100644 --- a/src/types.c +++ b/src/types.c @@ -145,6 +145,8 @@ bool is_pointer_type(const Type *t) const Type *type_of_constant(const Constant *c) { switch(c->kind) { + case CONSTANT_ENUM_MEMBER: + return c->data.enum_member.enumtype; case CONSTANT_NULL: return voidPtrType; case CONSTANT_DOUBLE: @@ -181,6 +183,20 @@ void set_struct_fields(Type *structtype, int count, char (*names)[100], const Ty structtype->data.structfields.types = types; } +Type *create_enum(const char *name, int membercount, char (*membernames)[100]) +{ + struct TypeInfo *result = calloc(1, sizeof *result); + result->type = (Type){ + .kind = TYPE_ENUM, + .data.enummembers = { .count=membercount, .names=membernames }, + }; + + assert(strlen(name) < sizeof result->type.name); + strcpy(result->type.name, name); + + return &result->type; +} + char *signature_to_string(const Signature *sig, bool include_return_type) { diff --git a/tests/404/enum.jou b/tests/404/enum.jou new file mode 100644 index 00000000..2dba4640 --- /dev/null +++ b/tests/404/enum.jou @@ -0,0 +1,2 @@ +def foo() -> void: + x = Foo::Bar # Error: there is no type named 'Foo' diff --git a/tests/404/enum_member.jou b/tests/404/enum_member.jou new file mode 100644 index 00000000..db524307 --- /dev/null +++ b/tests/404/enum_member.jou @@ -0,0 +1,6 @@ +enum FooBar: + Foo + Bar + +def foo() -> void: + x = FooBar::Asdf # Error: enum FooBar has no member named 'Asdf' diff --git a/tests/already_exists_error/struct_and_enum.jou b/tests/already_exists_error/struct_and_enum.jou new file mode 100644 index 00000000..6734a9d4 --- /dev/null +++ b/tests/already_exists_error/struct_and_enum.jou @@ -0,0 +1,5 @@ +struct Foo: + x: int + +enum Foo: # Error: a type named 'Foo' already exists + Blah diff --git a/tests/other_errors/duplicate_enum_member.jou b/tests/other_errors/duplicate_enum_member.jou new file mode 100644 index 00000000..6f073120 --- /dev/null +++ b/tests/other_errors/duplicate_enum_member.jou @@ -0,0 +1,3 @@ +enum Blah: + Foo + Foo # Error: the enum has two members named 'Foo' diff --git a/tests/should_succeed/enum.jou b/tests/should_succeed/enum.jou new file mode 100644 index 00000000..5bdb5ce9 --- /dev/null +++ b/tests/should_succeed/enum.jou @@ -0,0 +1,23 @@ +from "stdlib/io.jou" import printf + +enum FooBar: + Foo + Bar + +def main() -> int: + printf("%d\n", FooBar::Foo as int) # Output: 0 + printf("%d\n", FooBar::Bar as int) # Output: 1 + + printf("%d\n", FooBar::Foo as byte) # Output: 0 + printf("%d\n", FooBar::Bar as byte) # Output: 1 + + printf("%lld\n", FooBar::Foo as long) # Output: 0 + printf("%lld\n", FooBar::Bar as long) # Output: 1 + + x = FooBar::Foo + if x == FooBar::Foo: + printf("yass\n") # Output: yass + if x == FooBar::Bar: + printf("wut wut why dis\n") + + return 0 diff --git a/tests/should_succeed/imported/bar.jou b/tests/should_succeed/imported/bar.jou index 9bbdbbcd..6ce5693d 100644 --- a/tests/should_succeed/imported/bar.jou +++ b/tests/should_succeed/imported/bar.jou @@ -6,5 +6,9 @@ struct Point: x: int y: int +enum FooBar: + Foo + Bar + def bar(point: Point) -> void: printf("Bar Bar %d %d\n", point.x, point.y) diff --git a/tests/should_succeed/local_import.jou b/tests/should_succeed/local_import.jou index 7b9a7bb5..12e5a373 100644 --- a/tests/should_succeed/local_import.jou +++ b/tests/should_succeed/local_import.jou @@ -1,5 +1,13 @@ -from "./imported/bar.jou" import bar, Point +from "stdlib/io.jou" import printf +from "./imported/bar.jou" import bar, Point, FooBar def main() -> int: bar(Point{x=1, y=2}) # Output: Bar Bar 1 2 + + foo = FooBar::Foo + if foo == FooBar::Foo: + printf("Yay\n") # Output: Yay + if foo == FooBar::Bar: + printf("waaaat\n") + return 0 diff --git a/tests/wrong_type/enum_member_from_struct.jou b/tests/wrong_type/enum_member_from_struct.jou new file mode 100644 index 00000000..4e67a6f4 --- /dev/null +++ b/tests/wrong_type/enum_member_from_struct.jou @@ -0,0 +1,5 @@ +struct BlahBlah: + x: int + +def foo() -> int: + wat = BlahBlah::x # Error: the '::' syntax is only for enums, but BlahBlah is a struct diff --git a/tests/wrong_type/enum_to_int.jou b/tests/wrong_type/enum_to_int.jou new file mode 100644 index 00000000..09445e4e --- /dev/null +++ b/tests/wrong_type/enum_to_int.jou @@ -0,0 +1,6 @@ +enum FooBar: + Foo + Bar + +def main() -> int: + x: FooBar = 0 # Error: initial value for variable of type FooBar cannot be of type int diff --git a/tests/wrong_type/int_to_enum.jou b/tests/wrong_type/int_to_enum.jou new file mode 100644 index 00000000..54e0315d --- /dev/null +++ b/tests/wrong_type/int_to_enum.jou @@ -0,0 +1,6 @@ +enum FooBar: + Foo + Bar + +def main() -> int: + x: int = FooBar::Foo # Error: initial value for variable of type int cannot be of type FooBar