inline for loops (only for 'in' based for loops)

This commit is contained in:
gingerBill
2019-08-26 13:54:35 +01:00
parent 4908d1ebdd
commit 01c10aa944
7 changed files with 530 additions and 24 deletions

View File

@@ -1018,6 +1018,44 @@ quaternions :: proc() {
fmt.println(c);
fmt.println(q);
}
{ // Memory layout of Quaternions
q := 1 + 2i + 3j + 4k;
a := transmute([4]f64)q;
fmt.println("Quaternion memory layout: xyzw/(ijkr)");
fmt.println(q); // 1.000+2.000i+3.000j+4.000k
fmt.println(a); // [2.000, 3.000, 4.000, 1.000]
}
}
inline_for_statement :: proc() {
fmt.println("\n#inline for statements");
fmt.println("Ranges");
inline for x, i in 1..<4 {
fmt.println(x, i);
}
fmt.println("Strings");
inline for r, i in "Hello, 世界" {
fmt.println(r, i);
}
fmt.println("Arrays");
inline for elem, idx in ([4]int{1, 4, 9, 16}) {
fmt.println(elem, idx);
}
Foo_Enum :: enum {
A = 1,
B,
C = 6,
D,
}
fmt.println("Enum types");
inline for elem, idx in Foo_Enum {
fmt.println(elem, idx);
}
}
main :: proc() {
@@ -1040,5 +1078,6 @@ main :: proc() {
deferred_procedure_associations();
reflection();
quaternions();
inline_for_statement();
}
}

View File

@@ -132,6 +132,10 @@ bool check_is_terminating(Ast *node) {
}
case_end;
case_ast_node(rs, InlineRangeStmt, node);
return false;
case_end;
case_ast_node(rs, RangeStmt, node);
return false;
case_end;
@@ -587,6 +591,236 @@ void add_constant_switch_case(CheckerContext *ctx, Map<TypeAndToken> *seen, Oper
multi_map_insert(seen, key, tap);
}
void check_inline_range_stmt(CheckerContext *ctx, Ast *node, u32 mod_flags) {
ast_node(irs, InlineRangeStmt, node);
check_open_scope(ctx, node);
Type *val0 = nullptr;
Type *val1 = nullptr;
Entity *entities[2] = {};
isize entity_count = 0;
Ast *expr = unparen_expr(irs->expr);
ExactValue inline_for_depth = exact_value_i64(0);
if (is_ast_range(expr)) {
ast_node(ie, BinaryExpr, expr);
Operand x = {Addressing_Invalid};
Operand y = {Addressing_Invalid};
check_expr(ctx, &x, ie->left);
if (x.mode == Addressing_Invalid) {
goto skip_expr;
}
check_expr(ctx, &y, ie->right);
if (y.mode == Addressing_Invalid) {
goto skip_expr;
}
convert_to_typed(ctx, &x, y.type);
if (x.mode == Addressing_Invalid) {
goto skip_expr;
}
convert_to_typed(ctx, &y, x.type);
if (y.mode == Addressing_Invalid) {
goto skip_expr;
}
convert_to_typed(ctx, &x, default_type(y.type));
if (x.mode == Addressing_Invalid) {
goto skip_expr;
}
convert_to_typed(ctx, &y, default_type(x.type));
if (y.mode == Addressing_Invalid) {
goto skip_expr;
}
if (!are_types_identical(x.type, y.type)) {
if (x.type != t_invalid &&
y.type != t_invalid) {
gbString xt = type_to_string(x.type);
gbString yt = type_to_string(y.type);
gbString expr_str = expr_to_string(x.expr);
error(ie->op, "Mismatched types in interval expression '%s' : '%s' vs '%s'", expr_str, xt, yt);
gb_string_free(expr_str);
gb_string_free(yt);
gb_string_free(xt);
}
goto skip_expr;
}
Type *type = x.type;
if (!is_type_integer(type) && !is_type_float(type) && !is_type_pointer(type) && !is_type_enum(type)) {
error(ie->op, "Only numerical and pointer types are allowed within interval expressions");
goto skip_expr;
}
if (x.mode == Addressing_Constant &&
y.mode == Addressing_Constant) {
ExactValue a = x.value;
ExactValue b = y.value;
GB_ASSERT(are_types_identical(x.type, y.type));
TokenKind op = Token_Lt;
switch (ie->op.kind) {
case Token_Ellipsis: op = Token_LtEq; break;
case Token_RangeHalf: op = Token_Lt; break;
default: error(ie->op, "Invalid range operator"); break;
}
bool ok = compare_exact_values(op, a, b);
if (!ok) {
// TODO(bill): Better error message
error(ie->op, "Invalid interval range");
goto skip_expr;
}
inline_for_depth = exact_value_sub(b, a);
if (ie->op.kind == Token_Ellipsis) {
inline_for_depth = exact_value_increment_one(inline_for_depth);
}
} else {
error(ie->op, "Interval expressions must be constant");
goto skip_expr;
}
add_type_and_value(&ctx->checker->info, ie->left, x.mode, x.type, x.value);
add_type_and_value(&ctx->checker->info, ie->right, y.mode, y.type, y.value);
val0 = type;
val1 = t_int;
} else {
Operand operand = {Addressing_Invalid};
check_expr_or_type(ctx, &operand, irs->expr);
if (operand.mode == Addressing_Type) {
if (!is_type_enum(operand.type)) {
gbString t = type_to_string(operand.type);
error(operand.expr, "Cannot iterate over the type '%s'", t);
gb_string_free(t);
goto skip_expr;
} else {
val0 = operand.type;
val1 = t_int;
add_type_info_type(ctx, operand.type);
Type *bt = base_type(operand.type);
inline_for_depth = exact_value_i64(bt->Enum.fields.count);
goto skip_expr;
}
} else if (operand.mode != Addressing_Invalid) {
Type *t = base_type(operand.type);
switch (t->kind) {
case Type_Basic:
if (is_type_string(t) && t->Basic.kind != Basic_cstring) {
val0 = t_rune;
val1 = t_int;
inline_for_depth = exact_value_i64(operand.value.value_string.len);
}
break;
case Type_Array:
val0 = t->Array.elem;
val1 = t_int;
inline_for_depth = exact_value_i64(t->Array.count);
break;
}
}
if (val0 == nullptr) {
gbString s = expr_to_string(operand.expr);
gbString t = type_to_string(operand.type);
error(operand.expr, "Cannot iterate over '%s' of type '%s' in an 'inline for' statement", s, t);
gb_string_free(t);
gb_string_free(s);
} else if (operand.mode != Addressing_Constant) {
error(operand.expr, "An 'inline for' expression must be known at compile time");
}
}
skip_expr:; // NOTE(zhiayang): again, declaring a variable immediately after a label... weird.
Ast * lhs[2] = {irs->val0, irs->val1};
Type *rhs[2] = {val0, val1};
for (isize i = 0; i < 2; i++) {
if (lhs[i] == nullptr) {
continue;
}
Ast * name = lhs[i];
Type *type = rhs[i];
Entity *entity = nullptr;
if (name->kind == Ast_Ident) {
Token token = name->Ident.token;
String str = token.string;
Entity *found = nullptr;
if (!is_blank_ident(str)) {
found = scope_lookup_current(ctx->scope, str);
}
if (found == nullptr) {
bool is_immutable = true;
entity = alloc_entity_variable(ctx->scope, token, type, is_immutable, EntityState_Resolved);
entity->flags |= EntityFlag_Value;
add_entity_definition(&ctx->checker->info, name, entity);
} else {
TokenPos pos = found->token.pos;
error(token,
"Redeclaration of '%.*s' in this scope\n"
"\tat %.*s(%td:%td)",
LIT(str), LIT(pos.file), pos.line, pos.column);
entity = found;
}
} else {
error(name, "A variable declaration must be an identifier");
}
if (entity == nullptr) {
entity = alloc_entity_dummy_variable(builtin_pkg->scope, ast_token(name));
}
entities[entity_count++] = entity;
if (type == nullptr) {
entity->type = t_invalid;
entity->flags |= EntityFlag_Used;
}
}
for (isize i = 0; i < entity_count; i++) {
add_entity(ctx->checker, ctx->scope, entities[i]->identifier, entities[i]);
}
// NOTE(bill): Minimize the amount of nesting of an 'inline for'
i64 prev_inline_for_depth = ctx->inline_for_depth;
defer (ctx->inline_for_depth = prev_inline_for_depth);
{
i64 v = exact_value_to_i64(inline_for_depth);
if (v <= 0) {
// Do nothing
} else {
ctx->inline_for_depth = gb_max(ctx->inline_for_depth, 1) * v;
}
if (ctx->inline_for_depth >= MAX_INLINE_FOR_DEPTH && prev_inline_for_depth < MAX_INLINE_FOR_DEPTH) {
if (prev_inline_for_depth > 0) {
error(node, "Nested 'inline for' loop cannot be inlined as it exceeds the maximum inline for depth (%lld levels >= %lld maximum levels)", v, MAX_INLINE_FOR_DEPTH);
} else {
error(node, "'inline for' loop cannot be inlined as it exceeds the maximum inline for depth (%lld levels >= %lld maximum levels)", v, MAX_INLINE_FOR_DEPTH);
}
error_line("\tUse a normal 'for' loop instead by removing the 'inline' prefix\n");
ctx->inline_for_depth = MAX_INLINE_FOR_DEPTH;
}
}
check_stmt(ctx, irs->body, mod_flags);
check_close_scope(ctx);
}
void check_switch_stmt(CheckerContext *ctx, Ast *node, u32 mod_flags) {
ast_node(ss, SwitchStmt, node);
@@ -1298,6 +1532,7 @@ void check_stmt_internal(CheckerContext *ctx, Ast *node, u32 flags) {
check_close_scope(ctx);
case_end;
case_ast_node(rs, RangeStmt, node);
u32 new_flags = mod_flags | Stmt_BreakAllowed | Stmt_ContinueAllowed;
@@ -1320,29 +1555,29 @@ void check_stmt_internal(CheckerContext *ctx, Ast *node, u32 flags) {
check_expr(ctx, &x, ie->left);
if (x.mode == Addressing_Invalid) {
goto skip_expr;
goto skip_expr_range_stmt;
}
check_expr(ctx, &y, ie->right);
if (y.mode == Addressing_Invalid) {
goto skip_expr;
goto skip_expr_range_stmt;
}
convert_to_typed(ctx, &x, y.type);
if (x.mode == Addressing_Invalid) {
goto skip_expr;
goto skip_expr_range_stmt;
}
convert_to_typed(ctx, &y, x.type);
if (y.mode == Addressing_Invalid) {
goto skip_expr;
goto skip_expr_range_stmt;
}
convert_to_typed(ctx, &x, default_type(y.type));
if (x.mode == Addressing_Invalid) {
goto skip_expr;
goto skip_expr_range_stmt;
}
convert_to_typed(ctx, &y, default_type(x.type));
if (y.mode == Addressing_Invalid) {
goto skip_expr;
goto skip_expr_range_stmt;
}
if (!are_types_identical(x.type, y.type)) {
@@ -1356,13 +1591,13 @@ void check_stmt_internal(CheckerContext *ctx, Ast *node, u32 flags) {
gb_string_free(yt);
gb_string_free(xt);
}
goto skip_expr;
goto skip_expr_range_stmt;
}
Type *type = x.type;
if (!is_type_integer(type) && !is_type_float(type) && !is_type_pointer(type) && !is_type_enum(type)) {
error(ie->op, "Only numerical and pointer types are allowed within interval expressions");
goto skip_expr;
goto skip_expr_range_stmt;
}
if (x.mode == Addressing_Constant &&
@@ -1382,18 +1617,10 @@ void check_stmt_internal(CheckerContext *ctx, Ast *node, u32 flags) {
if (!ok) {
// TODO(bill): Better error message
error(ie->op, "Invalid interval range");
goto skip_expr;
goto skip_expr_range_stmt;
}
}
if (x.mode != Addressing_Constant) {
x.value = empty_exact_value;
}
if (y.mode != Addressing_Constant) {
y.value = empty_exact_value;
}
add_type_and_value(&ctx->checker->info, ie->left, x.mode, x.type, x.value);
add_type_and_value(&ctx->checker->info, ie->right, y.mode, y.type, y.value);
val0 = type;
@@ -1407,12 +1634,12 @@ void check_stmt_internal(CheckerContext *ctx, Ast *node, u32 flags) {
gbString t = type_to_string(operand.type);
error(operand.expr, "Cannot iterate over the type '%s'", t);
gb_string_free(t);
goto skip_expr;
goto skip_expr_range_stmt;
} else {
val0 = operand.type;
val1 = t_int;
add_type_info_type(ctx, operand.type);
goto skip_expr;
goto skip_expr_range_stmt;
}
} else if (operand.mode != Addressing_Invalid) {
bool is_ptr = is_type_pointer(operand.type);
@@ -1457,7 +1684,8 @@ void check_stmt_internal(CheckerContext *ctx, Ast *node, u32 flags) {
}
}
skip_expr:; // NOTE(zhiayang): again, declaring a variable immediately after a label... weird.
skip_expr_range_stmt:; // NOTE(zhiayang): again, declaring a variable immediately after a label... weird.
Ast * lhs[2] = {rs->val0, rs->val1};
Type *rhs[2] = {val0, val1};
@@ -1515,6 +1743,10 @@ void check_stmt_internal(CheckerContext *ctx, Ast *node, u32 flags) {
check_close_scope(ctx);
case_end;
case_ast_node(irs, InlineRangeStmt, node);
check_inline_range_stmt(ctx, node, mod_flags);
case_end;
case_ast_node(ss, SwitchStmt, node);
check_switch_stmt(ctx, node, mod_flags);
case_end;

View File

@@ -276,6 +276,9 @@ struct CheckerContext {
CheckerPolyPath *poly_path;
isize poly_level; // TODO(bill): Actually handle correctly
#define MAX_INLINE_FOR_DEPTH 1024ll
i64 inline_for_depth;
bool in_enum_type;
bool collect_delayed_decls;
bool allow_polymorphic_types;

View File

@@ -813,6 +813,10 @@ gb_inline ExactValue exact_value_shift(TokenKind op, ExactValue const &x, ExactV
return exact_binary_operator_value(op, x, y);
}
gb_inline ExactValue exact_value_increment_one(ExactValue const &x) {
return exact_binary_operator_value(Token_Add, x, exact_value_i64(1));
}
i32 cmp_f64(f64 a, f64 b) {
return (a > b) - (a < b);

View File

@@ -9061,6 +9061,150 @@ void ir_build_stmt_internal(irProcedure *proc, Ast *node) {
ir_start_block(proc, done);
case_end;
case_ast_node(rs, InlineRangeStmt, node);
ir_emit_comment(proc, str_lit("InlineRangeStmt"));
ir_open_scope(proc); // Open scope here
Type *val0_type = nullptr;
Type *val1_type = nullptr;
if (rs->val0 != nullptr && !is_blank_ident(rs->val0)) {
val0_type = type_of_expr(rs->val0);
}
if (rs->val1 != nullptr && !is_blank_ident(rs->val1)) {
val1_type = type_of_expr(rs->val1);
}
if (val0_type != nullptr) {
ir_add_local_for_identifier(proc, rs->val0, true);
}
if (val1_type != nullptr) {
ir_add_local_for_identifier(proc, rs->val1, true);
}
irValue *val = nullptr;
irValue *key = nullptr;
irBlock *loop = nullptr;
irBlock *done = nullptr;
Ast *expr = unparen_expr(rs->expr);
TypeAndValue tav = type_and_value_of_expr(expr);
if (is_ast_range(expr)) {
irAddr val0_addr = {};
irAddr val1_addr = {};
if (val0_type) val0_addr = ir_build_addr(proc, rs->val0);
if (val1_type) val1_addr = ir_build_addr(proc, rs->val1);
TokenKind op = expr->BinaryExpr.op.kind;
Ast *start_expr = expr->BinaryExpr.left;
Ast *end_expr = expr->BinaryExpr.right;
GB_ASSERT(start_expr->tav.mode == Addressing_Constant);
GB_ASSERT(end_expr->tav.mode == Addressing_Constant);
ExactValue start = start_expr->tav.value;
ExactValue end = end_expr->tav.value;
if (op == Token_Ellipsis) { // .. [start, end]
ExactValue index = exact_value_i64(0);
for (ExactValue val = start;
compare_exact_values(Token_LtEq, val, end);
val = exact_value_increment_one(val), index = exact_value_increment_one(index)) {
if (val0_type) ir_addr_store(proc, val0_addr, ir_value_constant(val0_type, val));
if (val1_type) ir_addr_store(proc, val1_addr, ir_value_constant(val1_type, index));
ir_build_stmt(proc, rs->body);
}
} else if (op == Token_RangeHalf) { // ..< [start, end)
ExactValue index = exact_value_i64(0);
for (ExactValue val = start;
compare_exact_values(Token_Lt, val, end);
val = exact_value_increment_one(val), index = exact_value_increment_one(index)) {
if (val0_type) ir_addr_store(proc, val0_addr, ir_value_constant(val0_type, val));
if (val1_type) ir_addr_store(proc, val1_addr, ir_value_constant(val1_type, index));
ir_build_stmt(proc, rs->body);
}
}
} else if (tav.mode == Addressing_Type) {
GB_ASSERT(is_type_enum(type_deref(tav.type)));
Type *et = type_deref(tav.type);
Type *bet = base_type(et);
irAddr val0_addr = {};
irAddr val1_addr = {};
if (val0_type) val0_addr = ir_build_addr(proc, rs->val0);
if (val1_type) val1_addr = ir_build_addr(proc, rs->val1);
for_array(i, bet->Enum.fields) {
Entity *field = bet->Enum.fields[i];
GB_ASSERT(field->kind == Entity_Constant);
if (val0_type) ir_addr_store(proc, val0_addr, ir_value_constant(val0_type, field->Constant.value));
if (val1_type) ir_addr_store(proc, val1_addr, ir_value_constant(val1_type, exact_value_i64(i)));
ir_build_stmt(proc, rs->body);
}
} else {
irAddr val0_addr = {};
irAddr val1_addr = {};
if (val0_type) val0_addr = ir_build_addr(proc, rs->val0);
if (val1_type) val1_addr = ir_build_addr(proc, rs->val1);
GB_ASSERT(expr->tav.mode == Addressing_Constant);
Type *t = base_type(expr->tav.type);
switch (t->kind) {
case Type_Basic:
GB_ASSERT(is_type_string(t));
{
ExactValue value = expr->tav.value;
GB_ASSERT(value.kind == ExactValue_String);
String str = value.value_string;
Rune codepoint = 0;
isize offset = 0;
do {
isize width = gb_utf8_decode(str.text+offset, str.len-offset, &codepoint);
if (val0_type) ir_addr_store(proc, val0_addr, ir_value_constant(val0_type, exact_value_i64(codepoint)));
if (val1_type) ir_addr_store(proc, val1_addr, ir_value_constant(val1_type, exact_value_i64(offset)));
ir_build_stmt(proc, rs->body);
offset += width;
} while (offset < str.len);
}
break;
case Type_Array:
if (t->Array.count > 0) {
irValue *val = ir_build_expr(proc, expr);
irValue *val_addr = ir_address_from_load_or_generate_local(proc, val);
for (i64 i = 0; i < t->Array.count; i++) {
if (val0_type) {
// NOTE(bill): Due to weird legacy issues in LLVM, this needs to be an i32
irValue *elem = ir_emit_array_epi(proc, val_addr, cast(i32)i);
ir_addr_store(proc, val0_addr, ir_emit_load(proc, elem));
}
if (val1_type) ir_addr_store(proc, val1_addr, ir_value_constant(val1_type, exact_value_i64(i)));
ir_build_stmt(proc, rs->body);
}
}
break;
default:
GB_PANIC("Invalid inline for type");
break;
}
}
ir_close_scope(proc, irDeferExit_Default, nullptr);
case_end;
case_ast_node(ss, SwitchStmt, node);
ir_emit_comment(proc, str_lit("SwitchStmt"));
if (ss->init != nullptr) {

View File

@@ -51,6 +51,7 @@ Token ast_token(Ast *node) {
case Ast_ReturnStmt: return node->ReturnStmt.token;
case Ast_ForStmt: return node->ForStmt.token;
case Ast_RangeStmt: return node->RangeStmt.token;
case Ast_InlineRangeStmt: return node->InlineRangeStmt.inline_token;
case Ast_CaseClause: return node->CaseClause.token;
case Ast_SwitchStmt: return node->SwitchStmt.token;
case Ast_TypeSwitchStmt: return node->TypeSwitchStmt.token;
@@ -257,6 +258,12 @@ Ast *clone_ast(Ast *node) {
n->RangeStmt.expr = clone_ast(n->RangeStmt.expr);
n->RangeStmt.body = clone_ast(n->RangeStmt.body);
break;
case Ast_InlineRangeStmt:
n->InlineRangeStmt.val0 = clone_ast(n->InlineRangeStmt.val0);
n->InlineRangeStmt.val1 = clone_ast(n->InlineRangeStmt.val1);
n->InlineRangeStmt.expr = clone_ast(n->InlineRangeStmt.expr);
n->InlineRangeStmt.body = clone_ast(n->InlineRangeStmt.body);
break;
case Ast_CaseClause:
n->CaseClause.list = clone_ast_array(n->CaseClause.list);
n->CaseClause.stmts = clone_ast_array(n->CaseClause.stmts);
@@ -748,6 +755,18 @@ Ast *ast_range_stmt(AstFile *f, Token token, Ast *val0, Ast *val1, Token in_toke
return result;
}
Ast *ast_inline_range_stmt(AstFile *f, Token inline_token, Token for_token, Ast *val0, Ast *val1, Token in_token, Ast *expr, Ast *body) {
Ast *result = alloc_ast_node(f, Ast_InlineRangeStmt);
result->InlineRangeStmt.inline_token = inline_token;
result->InlineRangeStmt.for_token = for_token;
result->InlineRangeStmt.val0 = val0;
result->InlineRangeStmt.val1 = val1;
result->InlineRangeStmt.in_token = in_token;
result->InlineRangeStmt.expr = expr;
result->InlineRangeStmt.body = body;
return result;
}
Ast *ast_switch_stmt(AstFile *f, Token token, Ast *init, Ast *tag, Ast *body) {
Ast *result = alloc_ast_node(f, Ast_SwitchStmt);
result->SwitchStmt.token = token;
@@ -1119,6 +1138,17 @@ Token advance_token(AstFile *f) {
return prev;
}
bool peek_token_kind(AstFile *f, TokenKind kind) {
for (isize i = f->curr_token_index+1; i < f->tokens.count; i++) {
Token tok = f->tokens[i];
if (kind != Token_Comment && tok.kind == Token_Comment) {
continue;
}
return tok.kind == kind;
}
return false;
}
Token expect_token(AstFile *f, TokenKind kind) {
Token prev = f->curr_token;
if (prev.kind != kind) {
@@ -2092,7 +2122,7 @@ Ast *parse_operand(AstFile *f, bool lhs) {
bool prev_allow_range = f->allow_range;
f->allow_range = true;
elem = parse_expr(f, false);
elem = parse_expr(f, true);
f->allow_range = prev_allow_range;
if (allow_token(f, Token_Semicolon)) {
underlying = parse_type(f);
@@ -2650,7 +2680,7 @@ Ast *parse_simple_stmt(AstFile *f, u32 flags) {
allow_token(f, Token_in);
bool prev_allow_range = f->allow_range;
f->allow_range = true;
Ast *expr = parse_expr(f, false);
Ast *expr = parse_expr(f, true);
f->allow_range = prev_allow_range;
auto rhs = array_make<Ast *>(heap_allocator(), 0, 1);
@@ -3779,10 +3809,55 @@ Ast *parse_stmt(AstFile *f) {
Token token = f->curr_token;
switch (token.kind) {
// Operands
case Token_inline:
if (peek_token_kind(f, Token_for)) {
Token inline_token = expect_token(f, Token_inline);
Token for_token = expect_token(f, Token_for);
Ast *val0 = nullptr;
Ast *val1 = nullptr;
Token in_token = {};
Ast *expr = nullptr;
Ast *body = nullptr;
bool bad_stmt = false;
if (f->curr_token.kind != Token_in) {
Array<Ast *> idents = parse_ident_list(f, false);
switch (idents.count) {
case 1:
val0 = idents[0];
break;
case 2:
val0 = idents[0];
val1 = idents[1];
break;
default:
syntax_error(for_token, "Expected either 1 or 2 identifiers");
bad_stmt = true;
break;
}
}
in_token = expect_token(f, Token_in);
bool prev_allow_range = f->allow_range;
f->allow_range = true;
expr = parse_expr(f, true);
f->allow_range = prev_allow_range;
if (allow_token(f, Token_do)) {
body = convert_stmt_to_body(f, parse_stmt(f));
} else {
body = parse_block_stmt(f, false);
}
if (bad_stmt) {
return ast_bad_stmt(f, inline_token, f->curr_token);
}
return ast_inline_range_stmt(f, inline_token, for_token, val0, val1, in_token, expr, body);
}
/* fallthrough */
case Token_no_inline:
case Token_context: // Also allows for `context =`
case Token_proc:
case Token_inline:
case Token_no_inline:
case Token_Ident:
case Token_Integer:
case Token_Float:

View File

@@ -326,6 +326,15 @@ AST_KIND(_ComplexStmtBegin, "", bool) \
Ast *expr; \
Ast *body; \
}) \
AST_KIND(InlineRangeStmt, "inline range statement", struct { \
Token inline_token; \
Token for_token; \
Ast *val0; \
Ast *val1; \
Token in_token; \
Ast *expr; \
Ast *body; \
}) \
AST_KIND(CaseClause, "case clause", struct { \
Token token; \
Array<Ast *> list; \