From 1e7ad2e241bada9e6fe2266638af23748a0524fa Mon Sep 17 00:00:00 2001 From: Christoffer Lerno Date: Mon, 24 Jan 2022 18:53:44 +0100 Subject: [PATCH] Add $foreach --- src/compiler/compiler_internal.h | 7 +- src/compiler/copying.c | 9 ++- src/compiler/enums.h | 4 + src/compiler/llvm_codegen_stmt.c | 3 +- src/compiler/parse_stmt.c | 35 ++++++--- src/compiler/sema_expr.c | 8 +- src/compiler/sema_stmts.c | 81 ++++++++++++++++++--- src/compiler/tokens.c | 6 ++ test/test_suite/compile_time/ct_foreach.c3t | 52 +++++++++++++ 9 files changed, 169 insertions(+), 36 deletions(-) create mode 100644 test/test_suite/compile_time/ct_foreach.c3t diff --git a/src/compiler/compiler_internal.h b/src/compiler/compiler_internal.h index 36700f53d..fc4a71288 100644 --- a/src/compiler/compiler_internal.h +++ b/src/compiler/compiler_internal.h @@ -1164,8 +1164,9 @@ typedef struct TokenId index; TokenId value; Expr *expr; - Ast *body; -} AstCtForStmt; + AstId body; +} AstCtForeachStmt; + typedef struct { @@ -1290,7 +1291,7 @@ typedef struct Ast_ AstCtIfStmt ct_if_stmt; // 24 AstCtIfStmt ct_elif_stmt; // 24 Ast *ct_else_stmt; // 8 - AstCtForStmt ct_for_stmt; // 64 + AstCtForeachStmt ct_foreach_stmt; // 64 AstScopedStmt scoped_stmt; // 16 AstScopingStmt scoping_stmt; AstAssertStmt ct_assert_stmt; diff --git a/src/compiler/copying.c b/src/compiler/copying.c index 986c046a1..3f8900e5e 100644 --- a/src/compiler/copying.c +++ b/src/compiler/copying.c @@ -286,13 +286,16 @@ Ast *ast_copy_deep(Ast *source) MACRO_COPY_AST(ast->ct_elif_stmt.then); MACRO_COPY_AST(ast->ct_elif_stmt.elif); return ast; + case AST_CT_ELSE_STMT: MACRO_COPY_AST(ast->ct_else_stmt); return ast; - case AST_CT_FOR_STMT: - MACRO_COPY_AST(ast->ct_for_stmt.body); - MACRO_COPY_EXPR(ast->ct_for_stmt.expr); + case AST_CT_FOREACH_STMT: + ast->ct_foreach_stmt.body = astid_copy_deep(ast->ct_foreach_stmt.body); + MACRO_COPY_EXPR(ast->ct_foreach_stmt.expr); return ast; + case AST_CT_FOR_STMT: + TODO case AST_CT_SWITCH_STMT: MACRO_COPY_EXPR(ast->ct_switch_stmt.cond); MACRO_COPY_AST_LIST(ast->ct_switch_stmt.body); diff --git a/src/compiler/enums.h b/src/compiler/enums.h index 4be8ae96c..ff47fe0b7 100644 --- a/src/compiler/enums.h +++ b/src/compiler/enums.h @@ -57,6 +57,7 @@ typedef enum AST_CT_ELIF_STMT, AST_CT_ELSE_STMT, AST_CT_FOR_STMT, + AST_CT_FOREACH_STMT, AST_CT_SWITCH_STMT, AST_DECLARE_STMT, AST_DEFAULT_STMT, @@ -474,10 +475,13 @@ typedef enum TOKEN_CT_DEFAULT, // $default TOKEN_CT_DEFINED, // $defined TOKEN_CT_FOR, // $for + TOKEN_CT_FOREACH, // $foreach TOKEN_CT_ELIF, // $elif TOKEN_CT_ELSE, // $else TOKEN_CT_ENDIF, // $endif TOKEN_CT_ENDSWITCH, // $endswitch + TOKEN_CT_ENDFOR, // $endfor + TOKEN_CT_ENDFOREACH, // $endforeach TOKEN_CT_EXTNAMEOF, // $extnameof TOKEN_CT_IF, // $if TOKEN_CT_NAMEOF, // $nameof diff --git a/src/compiler/llvm_codegen_stmt.c b/src/compiler/llvm_codegen_stmt.c index cdb329374..b3d2ae4e4 100644 --- a/src/compiler/llvm_codegen_stmt.c +++ b/src/compiler/llvm_codegen_stmt.c @@ -1289,7 +1289,7 @@ void llvm_emit_stmt(GenContext *c, Ast *ast) break; case AST_ASSERT_STMT: llvm_emit_assert_stmt(c, ast); - break;; + break; case AST_CT_ASSERT: case AST_CT_IF_STMT: case AST_CT_ELIF_STMT: @@ -1298,6 +1298,7 @@ void llvm_emit_stmt(GenContext *c, Ast *ast) case AST_CT_SWITCH_STMT: case AST_CASE_STMT: case AST_DEFAULT_STMT: + case AST_CT_FOREACH_STMT: UNREACHABLE case AST_SWITCH_STMT: gencontext_emit_switch(c, ast); diff --git a/src/compiler/parse_stmt.c b/src/compiler/parse_stmt.c index e7c455b47..feac52eb8 100644 --- a/src/compiler/parse_stmt.c +++ b/src/compiler/parse_stmt.c @@ -666,31 +666,38 @@ static inline Ast *parse_return(ParseContext *context) /** - * ct_for_stmt - * : CT_FOR '(' CT_IDENT IN expression ')' statement - * | CT_FOR '(' CT_IDENT, CT_IDENT IN expression ')' statement + * ct_foreach_stmt + * | CT_FOREACH '(' CT_IDENT (',' CT_IDENT)? ':' expr ')' statement * ; * * @return */ -static inline Ast* parse_ct_for_stmt(ParseContext *context) +static inline Ast* parse_ct_foreach_stmt(ParseContext *context) { - Ast *ast = AST_NEW_TOKEN(AST_CT_FOR_STMT, context->tok); - advance_and_verify(context, TOKEN_CT_FOR); + Ast *ast = AST_NEW_TOKEN(AST_CT_FOREACH_STMT, context->tok); + advance_and_verify(context, TOKEN_CT_FOREACH); CONSUME_OR(TOKEN_LPAREN, poisoned_ast); if (context->next_tok.type == TOKEN_COMMA) { - ast->ct_for_stmt.index = context->tok.id; + ast->ct_foreach_stmt.index = context->tok.id; TRY_CONSUME_OR(TOKEN_CT_IDENT, "Expected a compile time index variable", poisoned_ast); advance_and_verify(context, TOKEN_COMMA); } - ast->ct_for_stmt.value = context->tok.id; + ast->ct_foreach_stmt.value = context->tok.id; TRY_CONSUME_OR(TOKEN_CT_IDENT, "Expected a compile time variable", poisoned_ast); TRY_CONSUME_OR(TOKEN_COLON, "Expected ':'.", poisoned_ast); - ASSIGN_EXPR_ELSE(ast->ct_for_stmt.expr, parse_expr(context), poisoned_ast); + ASSIGN_EXPR_ELSE(ast->ct_foreach_stmt.expr, parse_expr(context), poisoned_ast); CONSUME_OR(TOKEN_RPAREN, poisoned_ast); - ASSIGN_AST_ELSE(ast->ct_for_stmt.body, parse_stmt(context), poisoned_ast); - + CONSUME_OR(TOKEN_COLON, poisoned_ast); + Ast *body = new_ast(AST_COMPOUND_STMT, ast->span); + ast->ct_foreach_stmt.body = astid(body); + AstId *current = &body->compound_stmt.first_stmt; + while (!try_consume(context, TOKEN_CT_ENDFOREACH)) + { + ASSIGN_AST_ELSE(Ast *stmt, parse_stmt(context), poisoned_ast); + *current = astid(stmt); + current = &stmt->next; + } return ast; } @@ -856,8 +863,10 @@ Ast *parse_stmt(ParseContext *context) return parse_ct_if_stmt(context); case TOKEN_CT_SWITCH: return parse_ct_switch_stmt(context); + case TOKEN_CT_FOREACH: + return parse_ct_foreach_stmt(context); case TOKEN_CT_FOR: - return parse_ct_for_stmt(context); + TODO case TOKEN_CT_UNREACHABLE: return parse_unreachable_stmt(context); case TOKEN_STAR: @@ -972,6 +981,8 @@ Ast *parse_stmt(ParseContext *context) case TOKEN_BITSTRUCT: case TOKEN_LVEC: case TOKEN_RVEC: + case TOKEN_CT_ENDFOR: + case TOKEN_CT_ENDFOREACH: SEMA_TOKEN_ERROR(context->tok, "Unexpected '%s' found when expecting a statement.", token_type_to_string(context->tok.type)); advance(context); return poisoned_ast; diff --git a/src/compiler/sema_expr.c b/src/compiler/sema_expr.c index e1a642e3b..0a233075d 100644 --- a/src/compiler/sema_expr.c +++ b/src/compiler/sema_expr.c @@ -3643,6 +3643,7 @@ static inline void sema_update_const_initializer_with_designator( } + /** * Create a const initializer. */ @@ -4029,14 +4030,11 @@ static inline bool sema_expr_analyse_initializer(SemaContext *context, Type *ext static inline bool sema_expr_analyse_initializer_list(SemaContext *context, Type *to, Expr *expr) { - if (!to) - { - return sema_expr_analyse_initializer(context, type_complist, type_complist, expr); - } assert(to); Type *assigned = type_flatten(to); switch (assigned->type_kind) { + case TYPE_UNTYPED_LIST: case TYPE_STRUCT: case TYPE_UNION: case TYPE_ARRAY: @@ -6967,7 +6965,7 @@ static inline bool sema_analyse_expr_dispatch(SemaContext *context, Expr *expr) return sema_expr_analyse_access(context, expr); case EXPR_INITIALIZER_LIST: case EXPR_DESIGNATED_INITIALIZER_LIST: - return sema_expr_analyse_initializer_list(context, NULL, expr); + return sema_expr_analyse_initializer_list(context, type_complist, expr); case EXPR_CAST: return sema_expr_analyse_cast(context, expr); case EXPR_EXPRESSION_LIST: diff --git a/src/compiler/sema_stmts.c b/src/compiler/sema_stmts.c index bcc7fcaeb..596e42bea 100644 --- a/src/compiler/sema_stmts.c +++ b/src/compiler/sema_stmts.c @@ -773,6 +773,7 @@ static inline bool sema_analyse_var_stmt(SemaContext *context, Ast *statement) // 2. Convert it to a NOP early statement->ast_kind = AST_NOP_STMT; + Expr *init; assert(decl->decl_kind == DECL_VAR); switch (decl->var.kind) { @@ -783,8 +784,7 @@ static inline bool sema_analyse_var_stmt(SemaContext *context, Ast *statement) SEMA_ERROR(decl->var.type_info, "Compile time type variables may not have a type."); return false; } - Expr *init = decl->var.init_expr; - if (init) + if ((init = decl->var.init_expr)) { if (!sema_analyse_expr_lvalue(context, init)) return false; if (init->expr_kind != EXPR_TYPEINFO) @@ -804,12 +804,12 @@ static inline bool sema_analyse_var_stmt(SemaContext *context, Ast *statement) SEMA_ERROR(decl->var.type_info, "Compile time variables may only be built-in types."); return false; } - if (decl->var.init_expr) + if ((init = decl->var.init_expr)) { - if (!sema_analyse_expr_rhs(context, decl->type, decl->var.init_expr, false)) return false; - if (!expr_is_constant_eval(decl->var.init_expr, CONSTANT_EVAL_ANY)) + if (!sema_analyse_expr_rhs(context, decl->type, init, false)) return false; + if (!expr_is_constant_eval(init, CONSTANT_EVAL_ANY)) { - SEMA_ERROR(decl->var.init_expr, "Expected a constant expression assigned to %s.", decl->name); + SEMA_ERROR(init, "Expected a constant expression assigned to %s.", decl->name); return false; } } @@ -821,16 +821,15 @@ static inline bool sema_analyse_var_stmt(SemaContext *context, Ast *statement) } else { - Expr *decl_init = decl->var.init_expr; - if (decl_init) + if ((init = decl->var.init_expr)) { - if (!sema_analyse_expr(context, decl_init)) return false; - if (!expr_is_constant_eval(decl_init, CONSTANT_EVAL_ANY)) + if (!sema_analyse_expr(context, init)) return false; + if (!expr_is_constant_eval(init, CONSTANT_EVAL_ANY)) { - SEMA_ERROR(decl->var.init_expr, "Expected a constant expression assigned to %s.", decl->name); + SEMA_ERROR(init, "Expected a constant expression assigned to %s.", decl->name); return false; } - decl->type = decl->var.init_expr->type; + decl->type = init->type; } else { @@ -2183,6 +2182,62 @@ static bool sema_analyse_ct_switch_stmt(SemaContext *context, Ast *statement) return sema_analyse_ct_switch_body(context, statement); } +static bool sema_analyse_ct_foreach_stmt(SemaContext *context, Ast *statement) +{ + Expr *collection = statement->ct_foreach_stmt.expr; + if (!sema_analyse_ct_expr(context, collection)) return false; + if (collection->expr_kind != EXPR_INITIALIZER_LIST) + { + SEMA_ERROR(collection, "Expected a list to iterate over"); + return false; + } + if (!expr_is_constant_eval(collection, CONSTANT_EVAL_ANY)) + { + SEMA_ERROR(collection, "A compile time $foreach must be over a constant value."); + return false; + } + Expr **expression = collection->initializer_list; + Decl *index = NULL; + TokenId index_token = statement->ct_foreach_stmt.index; + + AstId start = 0; + SCOPE_START; + + if (index_token.index) + { + index = decl_new_var(index_token, NULL, VARDECL_LOCAL_CT, VISIBLE_LOCAL); + index->type = type_int; + if (!sema_add_local(context, index)) return SCOPE_POP_ERROR(); + } + Decl *value = decl_new_var(statement->ct_foreach_stmt.value, NULL, VARDECL_LOCAL_CT, VISIBLE_LOCAL); + if (!sema_add_local(context, value)) return SCOPE_POP_ERROR(); + // Get the body + Ast *body = astptr(statement->ct_foreach_stmt.body); + AstId *current = &start; + VECEACH(expression, i) + { + Ast *compound_stmt = ast_copy_deep(body); + value->var.init_expr = expression[i]; + if (index) + { + Expr *expr = expr_new(EXPR_CONST, index->span); + expr_const_set_int(&expr->const_expr, i, TYPE_I32); + expr->const_expr.narrowable = true; + expr->type = type_int; + index->var.init_expr = expr; + index->type = type_int; + } + if (!sema_analyse_compound_stmt(context, compound_stmt)) return SCOPE_POP_ERROR(); + *current = astid(compound_stmt); + current = &compound_stmt->next; + } + SCOPE_END; + statement->ast_kind = AST_COMPOUND_STMT; + statement->compound_stmt.first_stmt = start; + statement->compound_stmt.defer_list = (DeferList) { 0, 0 }; + return true; +} + static bool sema_analyse_switch_stmt(SemaContext *context, Ast *statement) { statement->switch_stmt.scope_defer = context->active_scope.in_defer; @@ -2465,6 +2520,8 @@ static inline bool sema_analyse_statement_inner(SemaContext *context, Ast *state case AST_CT_ELIF_STMT: case AST_CT_ELSE_STMT: UNREACHABLE + case AST_CT_FOREACH_STMT: + return sema_analyse_ct_foreach_stmt(context, statement); case AST_CT_FOR_STMT: TODO } diff --git a/src/compiler/tokens.c b/src/compiler/tokens.c index 5481fae0d..9aa867a4b 100644 --- a/src/compiler/tokens.c +++ b/src/compiler/tokens.c @@ -351,6 +351,8 @@ const char *token_type_to_string(TokenType type) return "$defined"; case TOKEN_CT_FOR: return "$for"; + case TOKEN_CT_FOREACH: + return "$foreach"; case TOKEN_CT_ELSE: return "$else"; case TOKEN_CT_ELIF: @@ -359,6 +361,10 @@ const char *token_type_to_string(TokenType type) return "$endif"; case TOKEN_CT_ENDSWITCH: return "$endswitch"; + case TOKEN_CT_ENDFOR: + return "$endfor"; + case TOKEN_CT_ENDFOREACH: + return "$endforeach"; case TOKEN_CT_EXTNAMEOF: return "$extnameof"; case TOKEN_CT_IF: diff --git a/test/test_suite/compile_time/ct_foreach.c3t b/test/test_suite/compile_time/ct_foreach.c3t new file mode 100644 index 000000000..bbc2e3850 --- /dev/null +++ b/test/test_suite/compile_time/ct_foreach.c3t @@ -0,0 +1,52 @@ +// #target: x64-darwin +module test; + +extern fn void printf(char*, ...); + +fn void main() +{ + + var $foo = { 1, 10, 34 }; + $foreach ($i : $foo): + printf("Foo %d\n", $i); + $endforeach; + + $foreach ($i, $j : $foo): + printf("Bar %d %d\n", $i, $j); + $endforeach; + + $foreach ($x : { 123, "abc", 1177, "hello" }): + $typeof($x) z = $x; + $switch ($typeof($x)): + $case int: + printf("Bar %d\n", $x); + $default: + printf("Bar %s\n", $x); + $endswitch; + $endforeach; + +} + +/* #expect: test.ll +define void @test.main() #0 { +entry: + %z = alloca i32, align 4 + %z1 = alloca [3 x i8]*, align 8 + %z2 = alloca i32, align 4 + %z3 = alloca [5 x i8]*, align 8 + call void (i8*, ...) @printf(i8* getelementptr inbounds ([8 x i8], [8 x i8]* @.str, i32 0, i32 0), i32 1) + call void (i8*, ...) @printf(i8* getelementptr inbounds ([8 x i8], [8 x i8]* @.str.1, i32 0, i32 0), i32 10) + call void (i8*, ...) @printf(i8* getelementptr inbounds ([8 x i8], [8 x i8]* @.str.2, i32 0, i32 0), i32 34) + call void (i8*, ...) @printf(i8* getelementptr inbounds ([11 x i8], [11 x i8]* @.str.3, i32 0, i32 0), i32 0, i32 1) + call void (i8*, ...) @printf(i8* getelementptr inbounds ([11 x i8], [11 x i8]* @.str.4, i32 0, i32 0), i32 1, i32 10) + call void (i8*, ...) @printf(i8* getelementptr inbounds ([11 x i8], [11 x i8]* @.str.5, i32 0, i32 0), i32 2, i32 34) + store i32 123, i32* %z, align 4 + call void (i8*, ...) @printf(i8* getelementptr inbounds ([8 x i8], [8 x i8]* @.str.6, i32 0, i32 0), i32 123) + store [3 x i8]* bitcast ([4 x i8]* @.str.7 to [3 x i8]*), [3 x i8]** %z1, align 8 + call void (i8*, ...) @printf(i8* getelementptr inbounds ([8 x i8], [8 x i8]* @.str.8, i32 0, i32 0), [3 x i8]* bitcast ([4 x i8]* @.str.9 to [3 x i8]*)) + store i32 1177, i32* %z2, align 4 + call void (i8*, ...) @printf(i8* getelementptr inbounds ([8 x i8], [8 x i8]* @.str.10, i32 0, i32 0), i32 1177) + store [5 x i8]* bitcast ([6 x i8]* @.str.11 to [5 x i8]*), [5 x i8]** %z3, align 8 + call void (i8*, ...) @printf(i8* getelementptr inbounds ([8 x i8], [8 x i8]* @.str.12, i32 0, i32 0), [5 x i8]* bitcast ([6 x i8]* @.str.13 to [5 x i8]*)) + ret void +}