Browse Source

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

gingerBill 6 years ago
parent
commit
01c10aa944
7 changed files with 530 additions and 24 deletions
  1. 39 0
      examples/demo/demo.odin
  2. 252 20
      src/check_stmt.cpp
  3. 3 0
      src/checker.hpp
  4. 4 0
      src/exact_value.cpp
  5. 144 0
      src/ir.cpp
  6. 79 4
      src/parser.cpp
  7. 9 0
      src/parser.hpp

+ 39 - 0
examples/demo/demo.odin

@@ -1018,6 +1018,44 @@ quaternions :: proc() {
 		fmt.println(c);
 		fmt.println(c);
 		fmt.println(q);
 		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() {
 main :: proc() {
@@ -1040,5 +1078,6 @@ main :: proc() {
 		deferred_procedure_associations();
 		deferred_procedure_associations();
 		reflection();
 		reflection();
 		quaternions();
 		quaternions();
+		inline_for_statement();
 	}
 	}
 }
 }

+ 252 - 20
src/check_stmt.cpp

@@ -132,6 +132,10 @@ bool check_is_terminating(Ast *node) {
 		}
 		}
 	case_end;
 	case_end;
 
 
+	case_ast_node(rs, InlineRangeStmt, node);
+		return false;
+	case_end;
+
 	case_ast_node(rs, RangeStmt, node);
 	case_ast_node(rs, RangeStmt, node);
 		return false;
 		return false;
 	case_end;
 	case_end;
@@ -587,6 +591,236 @@ void add_constant_switch_case(CheckerContext *ctx, Map<TypeAndToken> *seen, Oper
 	multi_map_insert(seen, key, tap);
 	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) {
 void check_switch_stmt(CheckerContext *ctx, Ast *node, u32 mod_flags) {
 	ast_node(ss, SwitchStmt, node);
 	ast_node(ss, SwitchStmt, node);
 
 
@@ -1298,6 +1532,7 @@ void check_stmt_internal(CheckerContext *ctx, Ast *node, u32 flags) {
 		check_close_scope(ctx);
 		check_close_scope(ctx);
 	case_end;
 	case_end;
 
 
+
 	case_ast_node(rs, RangeStmt, node);
 	case_ast_node(rs, RangeStmt, node);
 		u32 new_flags = mod_flags | Stmt_BreakAllowed | Stmt_ContinueAllowed;
 		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);
 			check_expr(ctx, &x, ie->left);
 			if (x.mode == Addressing_Invalid) {
 			if (x.mode == Addressing_Invalid) {
-				goto skip_expr;
+				goto skip_expr_range_stmt;
 			}
 			}
 			check_expr(ctx, &y, ie->right);
 			check_expr(ctx, &y, ie->right);
 			if (y.mode == Addressing_Invalid) {
 			if (y.mode == Addressing_Invalid) {
-				goto skip_expr;
+				goto skip_expr_range_stmt;
 			}
 			}
 
 
 			convert_to_typed(ctx, &x, y.type);
 			convert_to_typed(ctx, &x, y.type);
 			if (x.mode == Addressing_Invalid) {
 			if (x.mode == Addressing_Invalid) {
-				goto skip_expr;
+				goto skip_expr_range_stmt;
 			}
 			}
 			convert_to_typed(ctx, &y, x.type);
 			convert_to_typed(ctx, &y, x.type);
 			if (y.mode == Addressing_Invalid) {
 			if (y.mode == Addressing_Invalid) {
-				goto skip_expr;
+				goto skip_expr_range_stmt;
 			}
 			}
 
 
 			convert_to_typed(ctx, &x, default_type(y.type));
 			convert_to_typed(ctx, &x, default_type(y.type));
 			if (x.mode == Addressing_Invalid) {
 			if (x.mode == Addressing_Invalid) {
-				goto skip_expr;
+				goto skip_expr_range_stmt;
 			}
 			}
 			convert_to_typed(ctx, &y, default_type(x.type));
 			convert_to_typed(ctx, &y, default_type(x.type));
 			if (y.mode == Addressing_Invalid) {
 			if (y.mode == Addressing_Invalid) {
-				goto skip_expr;
+				goto skip_expr_range_stmt;
 			}
 			}
 
 
 			if (!are_types_identical(x.type, y.type)) {
 			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(yt);
 					gb_string_free(xt);
 					gb_string_free(xt);
 				}
 				}
-				goto skip_expr;
+				goto skip_expr_range_stmt;
 			}
 			}
 
 
 			Type *type = x.type;
 			Type *type = x.type;
 			if (!is_type_integer(type) && !is_type_float(type) && !is_type_pointer(type) && !is_type_enum(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");
 				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 &&
 			if (x.mode == Addressing_Constant &&
@@ -1382,18 +1617,10 @@ void check_stmt_internal(CheckerContext *ctx, Ast *node, u32 flags) {
 				if (!ok) {
 				if (!ok) {
 					// TODO(bill): Better error message
 					// TODO(bill): Better error message
 					error(ie->op, "Invalid interval range");
 					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->left,  x.mode, x.type, x.value);
 			add_type_and_value(&ctx->checker->info, ie->right, y.mode, y.type, y.value);
 			add_type_and_value(&ctx->checker->info, ie->right, y.mode, y.type, y.value);
 			val0 = type;
 			val0 = type;
@@ -1407,12 +1634,12 @@ void check_stmt_internal(CheckerContext *ctx, Ast *node, u32 flags) {
 					gbString t = type_to_string(operand.type);
 					gbString t = type_to_string(operand.type);
 					error(operand.expr, "Cannot iterate over the type '%s'", t);
 					error(operand.expr, "Cannot iterate over the type '%s'", t);
 					gb_string_free(t);
 					gb_string_free(t);
-					goto skip_expr;
+					goto skip_expr_range_stmt;
 				} else {
 				} else {
 					val0 = operand.type;
 					val0 = operand.type;
 					val1 = t_int;
 					val1 = t_int;
 					add_type_info_type(ctx, operand.type);
 					add_type_info_type(ctx, operand.type);
-					goto skip_expr;
+					goto skip_expr_range_stmt;
 				}
 				}
 			} else if (operand.mode != Addressing_Invalid) {
 			} else if (operand.mode != Addressing_Invalid) {
 				bool is_ptr = is_type_pointer(operand.type);
 				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};
 		Ast * lhs[2] = {rs->val0, rs->val1};
 		Type *rhs[2] = {val0, val1};
 		Type *rhs[2] = {val0, val1};
 
 
@@ -1515,6 +1743,10 @@ void check_stmt_internal(CheckerContext *ctx, Ast *node, u32 flags) {
 		check_close_scope(ctx);
 		check_close_scope(ctx);
 	case_end;
 	case_end;
 
 
+	case_ast_node(irs, InlineRangeStmt, node);
+		check_inline_range_stmt(ctx, node, mod_flags);
+	case_end;
+
 	case_ast_node(ss, SwitchStmt, node);
 	case_ast_node(ss, SwitchStmt, node);
 		check_switch_stmt(ctx, node, mod_flags);
 		check_switch_stmt(ctx, node, mod_flags);
 	case_end;
 	case_end;

+ 3 - 0
src/checker.hpp

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

+ 4 - 0
src/exact_value.cpp

@@ -813,6 +813,10 @@ gb_inline ExactValue exact_value_shift(TokenKind op, ExactValue const &x, ExactV
 	return exact_binary_operator_value(op, x, y);
 	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) {
 i32 cmp_f64(f64 a, f64 b) {
 	return (a > b) - (a < b);
 	return (a > b) - (a < b);

+ 144 - 0
src/ir.cpp

@@ -9061,6 +9061,150 @@ void ir_build_stmt_internal(irProcedure *proc, Ast *node) {
 		ir_start_block(proc, done);
 		ir_start_block(proc, done);
 	case_end;
 	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);
 	case_ast_node(ss, SwitchStmt, node);
 		ir_emit_comment(proc, str_lit("SwitchStmt"));
 		ir_emit_comment(proc, str_lit("SwitchStmt"));
 		if (ss->init != nullptr) {
 		if (ss->init != nullptr) {

+ 79 - 4
src/parser.cpp

@@ -51,6 +51,7 @@ Token ast_token(Ast *node) {
 	case Ast_ReturnStmt:         return node->ReturnStmt.token;
 	case Ast_ReturnStmt:         return node->ReturnStmt.token;
 	case Ast_ForStmt:            return node->ForStmt.token;
 	case Ast_ForStmt:            return node->ForStmt.token;
 	case Ast_RangeStmt:          return node->RangeStmt.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_CaseClause:         return node->CaseClause.token;
 	case Ast_SwitchStmt:         return node->SwitchStmt.token;
 	case Ast_SwitchStmt:         return node->SwitchStmt.token;
 	case Ast_TypeSwitchStmt:     return node->TypeSwitchStmt.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.expr  = clone_ast(n->RangeStmt.expr);
 		n->RangeStmt.body  = clone_ast(n->RangeStmt.body);
 		n->RangeStmt.body  = clone_ast(n->RangeStmt.body);
 		break;
 		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:
 	case Ast_CaseClause:
 		n->CaseClause.list  = clone_ast_array(n->CaseClause.list);
 		n->CaseClause.list  = clone_ast_array(n->CaseClause.list);
 		n->CaseClause.stmts = clone_ast_array(n->CaseClause.stmts);
 		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;
 	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 *ast_switch_stmt(AstFile *f, Token token, Ast *init, Ast *tag, Ast *body) {
 	Ast *result = alloc_ast_node(f, Ast_SwitchStmt);
 	Ast *result = alloc_ast_node(f, Ast_SwitchStmt);
 	result->SwitchStmt.token = token;
 	result->SwitchStmt.token = token;
@@ -1119,6 +1138,17 @@ Token advance_token(AstFile *f) {
 	return prev;
 	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 expect_token(AstFile *f, TokenKind kind) {
 	Token prev = f->curr_token;
 	Token prev = f->curr_token;
 	if (prev.kind != kind) {
 	if (prev.kind != kind) {
@@ -2092,7 +2122,7 @@ Ast *parse_operand(AstFile *f, bool lhs) {
 
 
 		bool prev_allow_range = f->allow_range;
 		bool prev_allow_range = f->allow_range;
 		f->allow_range = true;
 		f->allow_range = true;
-		elem = parse_expr(f, false);
+		elem = parse_expr(f, true);
 		f->allow_range = prev_allow_range;
 		f->allow_range = prev_allow_range;
 		if (allow_token(f, Token_Semicolon)) {
 		if (allow_token(f, Token_Semicolon)) {
 			underlying = parse_type(f);
 			underlying = parse_type(f);
@@ -2650,7 +2680,7 @@ Ast *parse_simple_stmt(AstFile *f, u32 flags) {
 			allow_token(f, Token_in);
 			allow_token(f, Token_in);
 			bool prev_allow_range = f->allow_range;
 			bool prev_allow_range = f->allow_range;
 			f->allow_range = true;
 			f->allow_range = true;
-			Ast *expr = parse_expr(f, false);
+			Ast *expr = parse_expr(f, true);
 			f->allow_range = prev_allow_range;
 			f->allow_range = prev_allow_range;
 
 
 			auto rhs = array_make<Ast *>(heap_allocator(), 0, 1);
 			auto rhs = array_make<Ast *>(heap_allocator(), 0, 1);
@@ -3779,10 +3809,55 @@ Ast *parse_stmt(AstFile *f) {
 	Token token = f->curr_token;
 	Token token = f->curr_token;
 	switch (token.kind) {
 	switch (token.kind) {
 	// Operands
 	// Operands
-	case Token_context: // Also allows for `context =`
-	case Token_proc:
 	case Token_inline:
 	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_no_inline:
+	case Token_context: // Also allows for `context =`
+	case Token_proc:
 	case Token_Ident:
 	case Token_Ident:
 	case Token_Integer:
 	case Token_Integer:
 	case Token_Float:
 	case Token_Float:

+ 9 - 0
src/parser.hpp

@@ -326,6 +326,15 @@ AST_KIND(_ComplexStmtBegin, "", bool) \
 		Ast *expr; \
 		Ast *expr; \
 		Ast *body; \
 		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 { \
 	AST_KIND(CaseClause, "case clause", struct { \
 		Token token;             \
 		Token token;             \
 		Array<Ast *> list;   \
 		Array<Ast *> list;   \