mirror of
https://github.com/c3lang/c3c.git
synced 2026-02-27 12:01:16 +00:00
Add $foreach
This commit is contained in:
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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:
|
||||
|
||||
52
test/test_suite/compile_time/ct_foreach.c3t
Normal file
52
test/test_suite/compile_time/ct_foreach.c3t
Normal file
@@ -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
|
||||
}
|
||||
Reference in New Issue
Block a user