From 5576ffd68f665ccd278d7ad5d61786f55af1a093 Mon Sep 17 00:00:00 2001 From: SheatNoisette <45624871+SheatNoisette@users.noreply.github.com> Date: Thu, 8 Jan 2026 11:21:25 +0100 Subject: [PATCH] wasm: handle `for` in strings and FixedArrays (#26290) --- vlib/builtin/wasm/string.v | 12 ++ vlib/v/gen/wasm/gen.v | 155 ++++++++++++++++++++++ vlib/v/gen/wasm/tests/for_in_range.vv | 56 ++++++++ vlib/v/gen/wasm/tests/for_in_range.vv.out | 38 +++++- 4 files changed, 260 insertions(+), 1 deletion(-) diff --git a/vlib/builtin/wasm/string.v b/vlib/builtin/wasm/string.v index e0986d5ce..a38022e98 100644 --- a/vlib/builtin/wasm/string.v +++ b/vlib/builtin/wasm/string.v @@ -63,3 +63,15 @@ pub fn (s string) < (other string) bool { } return false } + +// at returns the byte at index `idx`. +// Example: assert 'ABC'.at(1) == u8(`B`) +// NOTE: Adapted from C built-in, modified for WASM +pub fn (s string) at(idx int) u8 { + $if !no_bounds_checking { + if idx < 0 || idx >= s.len { + panic('string index out of range') + } + } + return unsafe { s.str[idx] } +} diff --git a/vlib/v/gen/wasm/gen.v b/vlib/v/gen/wasm/gen.v index ca3d0c8d0..9467190b5 100644 --- a/vlib/v/gen/wasm/gen.v +++ b/vlib/v/gen/wasm/gen.v @@ -1123,6 +1123,7 @@ pub fn (mut g Gen) expr(node ast.Expr, expected ast.Type) { ast.Nil { g.func.i32_const(0) } + ast.EmptyExpr {} ast.IfExpr { g.if_expr(node, expected, []) } @@ -1156,6 +1157,27 @@ pub fn (mut g Gen) expr(node ast.Expr, expected ast.Type) { } pub fn (mut g Gen) for_in_stmt(node ast.ForInStmt) { + if node.is_range { + g.for_in_range(node) + return + } + + cond_sym := g.table.sym(node.cond_type) + + match cond_sym.kind { + .array_fixed { + g.for_in_array_fixed(node, cond_sym) + } + .string { + g.for_in_string(node) + } + else { + g.w_error('unsupported iter type: ${cond_sym.kind}') + } + } +} + +fn (mut g Gen) for_in_range(node ast.ForInStmt) { loop_var_type := unpack_literal_int(node.val_type) block := g.func.c_block([], []) { @@ -1198,6 +1220,139 @@ pub fn (mut g Gen) for_in_stmt(node ast.ForInStmt) { g.func.c_end(block) } +fn (mut g Gen) for_in_array_fixed(node ast.ForInStmt, cond_sym &ast.TypeSymbol) { + info := cond_sym.info as ast.ArrayFixed + array_size := info.size + + block := g.func.c_block([], []) + { + idx_var := g.new_local('__idx', ast.int_type) + g.literalint(0, ast.int_type) + g.set(idx_var) + + array_base := g.new_local('__array_base', node.cond_type) + g.expr(node.cond, node.cond_type) + g.set(array_base) + + loop := g.func.c_loop([], []) + { + g.loop_breakpoint_stack << LoopBreakpoint{ + c_continue: loop + c_break: block + name: node.label + } + + // if index >= array_size + g.get(idx_var) + g.literalint(array_size, ast.int_type) + g.func.ge(.i32_t, false) + g.func.c_br_if(block) + + // _ -> No variable in the loop + if node.val_var != '_' { + element_var := g.new_local(node.val_var, node.val_type) + + // array_base + idx * element_size + g.get(array_base) + g.get(idx_var) + + elem_size, _ := g.pool.type_size(node.val_type) + if elem_size > 1 { + g.literalint(elem_size, ast.int_type) + g.func.mul(.i32_t) + } + g.func.add(.i32_t) + + if g.is_pure_type(node.val_type) { + g.load(node.val_type, 0) + } + + g.set(element_var) + } + + // Inside loop + g.expr_stmts(node.stmts, ast.void_type) + + // idx++ + g.set_prepare(idx_var) + { + g.get(idx_var) + g.literalint(1, ast.int_type) + g.func.add(.i32_t) + } + g.set(idx_var) + + g.func.c_br(loop) + g.loop_breakpoint_stack.pop() + } + g.func.c_end(loop) + } + g.func.c_end(block) +} + +fn (mut g Gen) for_in_string(node ast.ForInStmt) { + block := g.func.c_block([], []) + { + idx_var := g.new_local('__idx', ast.int_type) + g.literalint(0, ast.int_type) + g.set(idx_var) + + // String ptr + string_var := g.new_local('__string', ast.string_type) + g.expr(node.cond, ast.string_type) + g.set(string_var) + + len_var := g.new_local('__len', ast.int_type) + g.get(string_var) + g.load_field(ast.string_type, ast.int_type, 'len') + g.set(len_var) + + loop := g.func.c_loop([], []) + { + g.loop_breakpoint_stack << LoopBreakpoint{ + c_continue: loop + c_break: block + name: node.label + } + + // if index >= length + g.get(idx_var) + g.get(len_var) + g.func.ge(.i32_t, false) + g.func.c_br_if(block) + + // _ -> No variable in the loop + if node.val_var != '_' { + char_var := g.new_local(node.val_var, node.val_type) + + // Use string.at(idx) method to get the byte, don't reinvent the wheel + g.get(string_var) + g.get(idx_var) + g.func.call('string.at') + + g.set(char_var) + } + + // Inside loop + g.expr_stmts(node.stmts, ast.void_type) + + // idx++ + g.set_prepare(idx_var) + { + g.get(idx_var) + g.literalint(1, ast.int_type) + g.func.add(.i32_t) + } + g.set(idx_var) + + g.func.c_br(loop) + g.loop_breakpoint_stack.pop() + } + g.func.c_end(loop) + } + g.func.c_end(block) +} + pub fn (g &Gen) file_pos(pos token.Pos) string { return '${os.to_slash(g.file_path)}:${pos.line_nr + 1}:${pos.col + 1}' } diff --git a/vlib/v/gen/wasm/tests/for_in_range.vv b/vlib/v/gen/wasm/tests/for_in_range.vv index a56985522..fc39ce52b 100644 --- a/vlib/v/gen/wasm/tests/for_in_range.vv +++ b/vlib/v/gen/wasm/tests/for_in_range.vv @@ -54,6 +54,59 @@ fn range_dual() int { return sum } +fn for_in_item() { + a := ['a', 'b', 'c']! + b := 'This a string' + c := [1, 2, 3, 5]! + d := 42 + hard := [[[[1, 2, 3, 4]!]!]!]! + hard_2 := [[[[1, d, d, 4]!]!]!]! + + println('a ----') + for element in a { + println(element) + } + + println('b ----') + for character in b { + println(character) + } + + println('hard ----') + for i in hard { + for j in i { + for k in j { + for element in k { + println(element) + } + } + } + } + + println('hard_2 ----') + for i in hard_2 { + for j in i { + for k in j { + for element in k { + println(element) + } + } + } + } + + println('c ----') + for element in c { + println(element) + } + + println('counter ----') + mut i := 0 + for _ in c { + i++ + } + println(i) +} + fn main() { println('--- inverted_range()') println(inverted_range()) @@ -73,5 +126,8 @@ fn main() { println('--- range_dual()') println(range_dual()) + println('--- for_in_item()') + for_in_item() + // Currently, the backend doesn't support maps, list etc. iteration is not possible } diff --git a/vlib/v/gen/wasm/tests/for_in_range.vv.out b/vlib/v/gen/wasm/tests/for_in_range.vv.out index be41570e1..8388cf305 100644 --- a/vlib/v/gen/wasm/tests/for_in_range.vv.out +++ b/vlib/v/gen/wasm/tests/for_in_range.vv.out @@ -9,4 +9,40 @@ --- range_with_expr() 20 --- range_dual() -1021632 \ No newline at end of file +1021632 +--- for_in_item() +a ---- +a +b +c +b ---- +84 +104 +105 +115 +32 +97 +32 +115 +116 +114 +105 +110 +103 +hard ---- +1 +2 +3 +4 +hard_2 ---- +1 +42 +42 +4 +c ---- +1 +2 +3 +5 +counter ---- +4 \ No newline at end of file -- 2.39.5