From ef95c1a6309b9358ac22e82a31929256203eb40a Mon Sep 17 00:00:00 2001 From: Christoffer Lerno Date: Mon, 24 Jan 2022 00:01:54 +0100 Subject: [PATCH] Fix $switch. And make top level $switch work. --- resources/lib/std/enumset.c3 | 24 ++-- src/compiler/compiler_internal.h | 3 +- src/compiler/copying.c | 7 ++ src/compiler/parse_global.c | 4 +- src/compiler/parse_stmt.c | 2 +- src/compiler/sema_expr.c | 14 +++ src/compiler/sema_internal.h | 1 + src/compiler/sema_passes.c | 113 +++++++++++++++++- src/compiler/sema_stmts.c | 2 +- .../compile_time/ct_switch_top_level.c3t | 54 +++++++++ 10 files changed, 207 insertions(+), 17 deletions(-) create mode 100644 test/test_suite/compile_time/ct_switch_top_level.c3t diff --git a/resources/lib/std/enumset.c3 b/resources/lib/std/enumset.c3 index c6beb1695..82077f3b8 100644 --- a/resources/lib/std/enumset.c3 +++ b/resources/lib/std/enumset.c3 @@ -5,28 +5,30 @@ $assert(Enum.min < Enum.max, "Only strictly increasing enums may be used with en $assert(Enum.max < 64, "Maximum value of an enum used as enum set is 63"); $assert(Enum.min >= 0, "Minimum value of an enum used as enum set is 0"); -$if $$C_INT_SIZE == 64: +$switch $$C_INT_SIZE: + +$case 64: private define EnumSetType = ulong; -$elif $$C_INT_SIZE == 32: +$case 32: -$if Enum.max < 32: + $if Enum.max < 32: private define EnumSetType = uint; -$else: + $else: private define EnumSetType = ulong; -$endif; + $endif; -$else: +$default: -$if Enum.max < 16: + $if Enum.max < 16: private define EnumSetType = ushort; -$elif Enum.max < 31: + $elif Enum.max < 31: private define EnumSetType = uint; -$else: + $else: private define EnumSetType = ulong; -$endif; + $endif; -$endif; +$endswitch; define EnumSet = distinct EnumSetType; diff --git a/src/compiler/compiler_internal.h b/src/compiler/compiler_internal.h index ce0b43641..36700f53d 100644 --- a/src/compiler/compiler_internal.h +++ b/src/compiler/compiler_internal.h @@ -411,7 +411,8 @@ typedef struct typedef struct { - TypeInfo *type; + Expr *expr; + Expr *to_expr; Decl **body; } CtCaseDecl; diff --git a/src/compiler/copying.c b/src/compiler/copying.c index c76fdce43..986c046a1 100644 --- a/src/compiler/copying.c +++ b/src/compiler/copying.c @@ -564,7 +564,14 @@ Decl *copy_decl(Decl *decl) MACRO_COPY_TYPE(decl->macro_decl.rtype); break; case DECL_CT_SWITCH: + MACRO_COPY_DECL_LIST(decl->ct_switch_decl.cases); + MACRO_COPY_EXPR(decl->ct_switch_decl.expr); + break; case DECL_CT_CASE: + MACRO_COPY_EXPR(decl->ct_case_decl.expr); + MACRO_COPY_EXPR(decl->ct_case_decl.to_expr); + MACRO_COPY_DECL_LIST(decl->ct_case_decl.body); + break; case DECL_ATTRIBUTE: TODO case DECL_DEFINE: diff --git a/src/compiler/parse_global.c b/src/compiler/parse_global.c index a6b8a9556..c02250834 100644 --- a/src/compiler/parse_global.c +++ b/src/compiler/parse_global.c @@ -188,7 +188,7 @@ static inline Decl *parse_ct_case(ParseContext *context) case TOKEN_CT_CASE: decl = decl_new_ct(DECL_CT_CASE, context->tok.id); advance(context); - ASSIGN_TYPE_ELSE(decl->ct_case_decl.type, parse_type(context), poisoned_decl); + ASSIGN_EXPR_ELSE(decl->ct_case_decl.expr, parse_constant_expr(context), poisoned_decl); break; default: SEMA_TOKEN_ERROR(context->tok, "Expected a $case or $default statement here."); @@ -214,7 +214,7 @@ static inline Decl *parse_ct_switch_top_level(ParseContext *context) { advance_and_verify(context, TOKEN_CT_SWITCH); Decl *ct = decl_new_ct(DECL_CT_SWITCH, context->prev_tok); - ASSIGN_EXPR_ELSE(ct->ct_switch_decl.expr, parse_const_paren_expr(context), poisoned_decl); + ASSIGN_EXPR_ELSE(ct->ct_switch_decl.expr, parse_constant_expr(context), poisoned_decl); CONSUME_OR(TOKEN_COLON, poisoned_decl); while (!try_consume(context, TOKEN_CT_ENDSWITCH)) diff --git a/src/compiler/parse_stmt.c b/src/compiler/parse_stmt.c index e7c455b47..f8b35e44a 100644 --- a/src/compiler/parse_stmt.c +++ b/src/compiler/parse_stmt.c @@ -713,7 +713,7 @@ static inline Ast* parse_ct_switch_stmt(ParseContext *context) { Ast *ast = AST_NEW_TOKEN(AST_CT_SWITCH_STMT, context->tok); advance_and_verify(context, TOKEN_CT_SWITCH); - ASSIGN_EXPR_ELSE(ast->ct_switch_stmt.cond, parse_const_paren_expr(context), poisoned_ast); + ASSIGN_EXPR_ELSE(ast->ct_switch_stmt.cond, parse_constant_expr(context), poisoned_ast); TRY_CONSUME(TOKEN_COLON, "Expected ':' after $switch expression, did you forget it?"); Ast **cases = NULL; while (!try_consume(context, TOKEN_CT_ENDSWITCH)) diff --git a/src/compiler/sema_expr.c b/src/compiler/sema_expr.c index 2997aa747..e1a642e3b 100644 --- a/src/compiler/sema_expr.c +++ b/src/compiler/sema_expr.c @@ -7069,6 +7069,20 @@ static inline bool sema_cast_rvalue(SemaContext *context, Expr *expr) return true; } +bool sema_analyse_ct_expr(SemaContext *context, Expr *expr) +{ + if (!sema_analyse_expr_lvalue(context, expr)) return false; + if (expr->expr_kind == EXPR_TYPEINFO) + { + Type *cond_val = expr->type_expr->type; + expr->expr_kind = EXPR_CONST; + expr->const_expr.const_kind = CONST_TYPEID; + expr->const_expr.typeid = cond_val->canonical; + expr->type = type_typeid; + } + return sema_cast_rvalue(context, expr); +} + bool sema_analyse_expr_lvalue(SemaContext *context, Expr *expr) { switch (expr->resolve_status) diff --git a/src/compiler/sema_internal.h b/src/compiler/sema_internal.h index 1dce279a7..68da352dc 100644 --- a/src/compiler/sema_internal.h +++ b/src/compiler/sema_internal.h @@ -68,6 +68,7 @@ void sema_analysis_pass_functions(Module *module); void sema_analyze_stage(Module *module, AnalysisStage stage); Decl *sema_find_operator(SemaContext *context, Expr *expr, const char *kw); bool sema_analyse_expr_lvalue(SemaContext *context, Expr *expr); +bool sema_analyse_ct_expr(SemaContext *context, Expr *expr); bool sema_expr_analyse_macro_call(SemaContext *context, Expr *call_expr, Expr *struct_var, Decl *decl, bool failable); void expr_rewrite_to_int_const(Expr *expr_to_rewrite, Type *type, uint64_t value, bool narrowable); diff --git a/src/compiler/sema_passes.c b/src/compiler/sema_passes.c index 522d09a47..439c1da24 100644 --- a/src/compiler/sema_passes.c +++ b/src/compiler/sema_passes.c @@ -139,6 +139,106 @@ static inline bool sema_analyse_top_level_if(SemaContext *context, Decl *ct_if) return true; } +static inline bool sema_analyse_top_level_switch(SemaContext *context, Decl *ct_switch) +{ + Expr *cond = ct_switch->ct_switch_decl.expr; + if (!sema_analyse_ct_expr(context, cond)) return false; + Type *type = cond->type; + bool is_type = type == type_typeid; + ExprConst *switch_expr_const = &cond->const_expr; + Decl **cases = ct_switch->ct_switch_decl.cases; + + unsigned case_count = vec_size(cases); + int matched_case = (int)case_count; + int default_case = (int)case_count; + for (unsigned i = 0; i < case_count; i++) + { + Decl *kase = cases[i]; + Expr *expr = kase->ct_case_decl.expr; + Expr *to_expr = kase->ct_case_decl.to_expr; + if (expr) + { + if (is_type) + { + if (!sema_analyse_ct_expr(context, expr)) return false; + if (expr->type != type_typeid) + { + SEMA_ERROR(expr, "A type was expected here not %s.", type_quoted_error_string(expr->type)); + return false; + } + } + else + { + if (!sema_analyse_expr_rhs(context, type, expr, false)) return false; + if (to_expr && !sema_analyse_expr_rhs(context, type, to_expr, false)) return false; + } + if (expr->expr_kind != EXPR_CONST) + { + SEMA_ERROR(expr, "The $case must have a constant expression."); + return false; + } + if (to_expr && to_expr->expr_kind != EXPR_CONST) + { + SEMA_ERROR(to_expr, "The $case must have a constant expression."); + return false; + } + ExprConst *const_expr = &expr->const_expr; + ExprConst *const_to_expr = to_expr ? &to_expr->const_expr : const_expr; + if (to_expr && expr_const_compare(const_expr, const_to_expr, BINARYOP_GT)) + { + SEMA_ERROR(to_expr, "The end of a range must be less or equal to the beginning."); + return false; + } + // Check that it is unique. + for (unsigned j = 0; j < i; j++) + { + Decl *other_case = cases[j]; + + // Default. + if (!other_case->ct_case_decl.expr) continue; + ExprConst *other_const = &other_case->ct_case_decl.expr->const_expr; + ExprConst *other_const_to = other_case->ct_case_decl.to_expr + ? &other_case->ct_case_decl.to_expr->const_expr : other_const; + if (expr_const_compare(const_expr, other_const_to, BINARYOP_LE) && + expr_const_compare(const_to_expr, other_const, BINARYOP_GE)) + { + SEMA_ERROR(kase, "'%s' appears more than once.", expr_const_to_error_string(const_expr)); + SEMA_PREV(cases[j]->ct_case_decl.expr, "The previous $case was here."); + return false; + } + } + if (expr_const_compare(switch_expr_const, const_expr, BINARYOP_GE) && + expr_const_compare(switch_expr_const, const_to_expr, BINARYOP_LE)) + { + matched_case = (int)i; + } + } + else + { + if (default_case < case_count) + { + SEMA_ERROR(kase, "More than one $default is not allowed."); + SEMA_PREV(cases[default_case], "The previous $default was here."); + return false; + } + default_case = (int)i; + continue; + } + } + + if (matched_case == case_count) matched_case = default_case; + + for (int i = matched_case; i < case_count; i++) + { + Decl **body = cases[i]->ct_case_decl.body; + if (body) + { + sema_append_decls(context->unit, body); + break; + } + } + return true; +} void sema_analysis_pass_conditional_compilation(Module *module) { @@ -152,7 +252,18 @@ void sema_analysis_pass_conditional_compilation(Module *module) // Also handle switch! SemaContext context; sema_context_init(&context, unit); - sema_analyse_top_level_if(&context, unit->ct_ifs[i]); + Decl *decl = unit->ct_ifs[i]; + switch (decl->decl_kind) + { + case DECL_CT_IF: + sema_analyse_top_level_if(&context, decl); + break; + case DECL_CT_SWITCH: + sema_analyse_top_level_switch(&context, decl); + break; + default: + UNREACHABLE + } sema_context_destroy(&context); } } diff --git a/src/compiler/sema_stmts.c b/src/compiler/sema_stmts.c index 082483fa6..bcc7fcaeb 100644 --- a/src/compiler/sema_stmts.c +++ b/src/compiler/sema_stmts.c @@ -2174,7 +2174,7 @@ static bool sema_analyse_ct_switch_body(SemaContext *context, Ast *statement) static bool sema_analyse_ct_switch_stmt(SemaContext *context, Ast *statement) { Expr *cond = statement->ct_switch_stmt.cond; - if (!sema_analyse_expr(context, cond)) return false; + if (!sema_analyse_ct_expr(context, cond)) return false; if (cond->expr_kind != EXPR_CONST) { SEMA_ERROR(cond, "A compile time $switch must be over a constant value."); diff --git a/test/test_suite/compile_time/ct_switch_top_level.c3t b/test/test_suite/compile_time/ct_switch_top_level.c3t new file mode 100644 index 000000000..71e1a7f29 --- /dev/null +++ b/test/test_suite/compile_time/ct_switch_top_level.c3t @@ -0,0 +1,54 @@ +// #target: x64-darwin +module test; + +extern fn void printf(char*, ...); + +macro tester() +{ + $Type = int; + $switch $Type: + $case int: + printf("Hello\n"); + int z = 0; + $default: + int j = 213; + $endswitch; + +} +$switch bool.typeid: +$case int: +int oefke = 23; +$default: +int oeoekgokege = 343432; +$endswitch; + +fn int main() +{ + @tester(); + @tester(); + int i = 1; + return 1; +} + +/* #expect: test.ll + +source_filename = "test" +target datalayout = "e-m:o-p270:32:32-p271:32:32-p272:64:64-i64:64-f80:128-n8:16:32:64-S128" +target triple = "x86_64-apple-darwin-1" + +@test.oeoekgokege = local_unnamed_addr global i32 343432, align 4 +@.str = private unnamed_addr constant [7 x i8] c"Hello\0A\00", align 1 +@.str.1 = private unnamed_addr constant [7 x i8] c"Hello\0A\00", align 1 + +define i32 @main() #0 { +entry: + %z = alloca i32, align 4 + %z1 = alloca i32, align 4 + %i = alloca i32, align 4 + call void (i8*, ...) @printf(i8* getelementptr inbounds ([7 x i8], [7 x i8]* @.str, i32 0, i32 0)) + store i32 0, i32* %z, align 4 + call void (i8*, ...) @printf(i8* getelementptr inbounds ([7 x i8], [7 x i8]* @.str.1, i32 0, i32 0)) + store i32 0, i32* %z1, align 4 + store i32 1, i32* %i, align 4 + ret i32 1 +}