| 1 | // Copyright (c) 2026 Alexander Medvednikov. All rights reserved. |
| 2 | // Use of this source code is governed by an MIT license |
| 3 | // that can be found in the LICENSE file. |
| 4 | |
| 5 | module cleanc |
| 6 | |
| 7 | import strings |
| 8 | import v2.ast |
| 9 | |
| 10 | fn (mut g Gen) gen_for_stmt(node ast.ForStmt) { |
| 11 | if node.init is ast.ForInStmt { |
| 12 | if g.gen_map_for_in_stmt(node, node.init) { |
| 13 | return |
| 14 | } |
| 15 | if g.gen_array_for_in_stmt(node, node.init) { |
| 16 | return |
| 17 | } |
| 18 | } |
| 19 | if g.gen_transformed_untyped_map_for_in_stmt(node) { |
| 20 | return |
| 21 | } |
| 22 | g.write_indent() |
| 23 | has_init := !is_empty_stmt(node.init) |
| 24 | has_cond := !is_empty_expr(node.cond) |
| 25 | has_post := !is_empty_stmt(node.post) |
| 26 | |
| 27 | if has_init || has_post { |
| 28 | // C-style for loop: for (init; cond; post) |
| 29 | g.sb.write_string('for (') |
| 30 | if has_init { |
| 31 | g.gen_stmt_inline(node.init) |
| 32 | } |
| 33 | g.sb.write_string('; ') |
| 34 | if has_cond { |
| 35 | g.expr(node.cond) |
| 36 | } |
| 37 | g.sb.write_string('; ') |
| 38 | if has_post { |
| 39 | g.gen_stmt_inline(node.post) |
| 40 | } |
| 41 | g.sb.writeln(') {') |
| 42 | } else if has_cond { |
| 43 | // while-style: for cond { |
| 44 | g.sb.write_string('while (') |
| 45 | g.expr(node.cond) |
| 46 | g.sb.writeln(') {') |
| 47 | } else { |
| 48 | // Infinite loop: for { |
| 49 | g.sb.writeln('for (;;) {') |
| 50 | } |
| 51 | |
| 52 | g.indent++ |
| 53 | // Save runtime_local_types: variables declared in the loop body |
| 54 | // (e.g. _filter_it in nested map/filter) can shadow outer variables. |
| 55 | // Without save/restore, the inner type overwrites the outer in the flat map. |
| 56 | saved_local_types := g.runtime_local_types.clone() |
| 57 | saved_decl_types := g.runtime_decl_types.clone() |
| 58 | g.gen_stmts(node.stmts) |
| 59 | g.runtime_local_types = saved_local_types.clone() |
| 60 | g.runtime_decl_types = saved_decl_types.clone() |
| 61 | g.not_local_var_cache.clear() |
| 62 | g.indent-- |
| 63 | g.write_indent() |
| 64 | g.sb.writeln('}') |
| 65 | } |
| 66 | |
| 67 | fn (mut g Gen) gen_map_for_in_stmt(node ast.ForStmt, for_in ast.ForInStmt) bool { |
| 68 | mut map_type := g.get_expr_type(for_in.expr).trim_space().trim_right('*') |
| 69 | if (map_type == '' || map_type == 'int') && for_in.expr is ast.Ident { |
| 70 | map_type = (g.get_local_var_c_type(for_in.expr.name) or { '' }).trim_space().trim_right('*') |
| 71 | } |
| 72 | if !map_type.starts_with('Map_') { |
| 73 | return false |
| 74 | } |
| 75 | key_type, value_type := g.parse_map_kv_types(map_type['Map_'.len..]) |
| 76 | if key_type == '' || value_type == '' { |
| 77 | return false |
| 78 | } |
| 79 | id := g.tmp_counter |
| 80 | g.tmp_counter++ |
| 81 | map_tmp := '_map_iter_${id}' |
| 82 | len_tmp := '_map_len_${id}' |
| 83 | idx_tmp := '_map_idx_${id}' |
| 84 | delta_tmp := '_map_delta_${id}' |
| 85 | mut expr_sb := strings.new_builder(64) |
| 86 | saved_sb := g.sb |
| 87 | g.sb = expr_sb |
| 88 | g.expr(for_in.expr) |
| 89 | map_expr := g.sb.str() |
| 90 | g.sb = saved_sb |
| 91 | g.write_indent() |
| 92 | g.sb.writeln('${map_type} ${map_tmp} = ${map_expr};') |
| 93 | g.write_indent() |
| 94 | g.sb.writeln('int ${len_tmp} = ${map_tmp}.key_values.len;') |
| 95 | g.write_indent() |
| 96 | g.sb.writeln('for (int ${idx_tmp} = 0; (${idx_tmp} < ${len_tmp}); ${idx_tmp} = (${idx_tmp} + 1)) {') |
| 97 | g.indent++ |
| 98 | g.write_indent() |
| 99 | g.sb.writeln('int ${delta_tmp} = (${map_tmp}.key_values.len - ${len_tmp});') |
| 100 | g.write_indent() |
| 101 | g.sb.writeln('${len_tmp} = ${map_tmp}.key_values.len;') |
| 102 | g.write_indent() |
| 103 | g.sb.writeln('if ((${delta_tmp} < 0)) {') |
| 104 | g.indent++ |
| 105 | g.write_indent() |
| 106 | g.sb.writeln('${idx_tmp} = -1;') |
| 107 | g.write_indent() |
| 108 | g.sb.writeln('continue;') |
| 109 | g.indent-- |
| 110 | g.write_indent() |
| 111 | g.sb.writeln('}') |
| 112 | g.write_indent() |
| 113 | g.sb.writeln('if (!DenseArray__has_index(&${map_tmp}.key_values, ${idx_tmp})) {') |
| 114 | g.indent++ |
| 115 | g.write_indent() |
| 116 | g.sb.writeln('continue;') |
| 117 | g.indent-- |
| 118 | g.write_indent() |
| 119 | g.sb.writeln('}') |
| 120 | key_name := if for_in.key is ast.Ident { for_in.key.name } else { '' } |
| 121 | value_name := if for_in.value is ast.Ident { for_in.value.name } else { '' } |
| 122 | saved_local_types := g.runtime_local_types.clone() |
| 123 | saved_decl_types := g.runtime_decl_types.clone() |
| 124 | if key_name != '' && key_name != '_' { |
| 125 | g.write_indent() |
| 126 | g.sb.writeln('${key_type} ${key_name} = *((${key_type}*)DenseArray__key(&${map_tmp}.key_values, ${idx_tmp}));') |
| 127 | if key_type == 'string' { |
| 128 | g.write_indent() |
| 129 | g.sb.writeln('${key_name} = string__clone(${key_name});') |
| 130 | } |
| 131 | g.remember_runtime_local_type(key_name, key_type) |
| 132 | } |
| 133 | if value_name != '' && value_name != '_' { |
| 134 | g.write_indent() |
| 135 | g.sb.writeln('${value_type} ${value_name} = *((${value_type}*)DenseArray__value(&${map_tmp}.key_values, ${idx_tmp}));') |
| 136 | if value_type == 'string' { |
| 137 | g.write_indent() |
| 138 | g.sb.writeln('${value_name} = string__clone(${value_name});') |
| 139 | } |
| 140 | g.remember_runtime_local_type(value_name, value_type) |
| 141 | } |
| 142 | g.gen_stmts(node.stmts) |
| 143 | g.runtime_local_types = saved_local_types.clone() |
| 144 | g.runtime_decl_types = saved_decl_types.clone() |
| 145 | g.not_local_var_cache.clear() |
| 146 | g.indent-- |
| 147 | g.write_indent() |
| 148 | g.sb.writeln('}') |
| 149 | return true |
| 150 | } |
| 151 | |
| 152 | fn cleanc_for_in_ident_name(expr ast.Expr) (string, bool) { |
| 153 | if expr is ast.Ident { |
| 154 | return expr.name, false |
| 155 | } |
| 156 | if expr is ast.ModifierExpr { |
| 157 | if expr.expr is ast.Ident { |
| 158 | return expr.expr.name, expr.kind == .key_mut |
| 159 | } |
| 160 | } |
| 161 | return '', false |
| 162 | } |
| 163 | |
| 164 | fn (mut g Gen) gen_array_for_in_stmt(node ast.ForStmt, for_in ast.ForInStmt) bool { |
| 165 | mut array_type := g.get_expr_type(for_in.expr).trim_space().trim_right('*') |
| 166 | if (array_type == '' || array_type == 'int') && for_in.expr is ast.Ident { |
| 167 | array_type = |
| 168 | (g.get_local_var_c_type(for_in.expr.name) or { '' }).trim_space().trim_right('*') |
| 169 | } |
| 170 | if array_type == '' { |
| 171 | return false |
| 172 | } |
| 173 | mut array_decl_type := array_type |
| 174 | if !c_type_is_array_value(array_decl_type) { |
| 175 | alias_base := g.array_alias_base_type(array_decl_type) |
| 176 | if alias_base != '' { |
| 177 | array_decl_type = alias_base |
| 178 | } |
| 179 | } |
| 180 | if !c_type_is_array_value(array_decl_type) { |
| 181 | return false |
| 182 | } |
| 183 | elem_type := g.array_alias_elem_type_from_c_type(array_decl_type) |
| 184 | if elem_type == '' { |
| 185 | return false |
| 186 | } |
| 187 | id := g.tmp_counter |
| 188 | g.tmp_counter++ |
| 189 | array_tmp := '_arr_iter_${id}' |
| 190 | idx_tmp := '_arr_idx_${id}' |
| 191 | mut expr_sb := strings.new_builder(64) |
| 192 | saved_sb := g.sb |
| 193 | g.sb = expr_sb |
| 194 | g.expr(for_in.expr) |
| 195 | array_expr := g.sb.str() |
| 196 | g.sb = saved_sb |
| 197 | g.write_indent() |
| 198 | g.sb.writeln('${array_decl_type} ${array_tmp} = ${array_expr};') |
| 199 | g.write_indent() |
| 200 | g.sb.writeln('for (int ${idx_tmp} = 0; (${idx_tmp} < ${array_tmp}.len); ${idx_tmp} = (${idx_tmp} + 1)) {') |
| 201 | g.indent++ |
| 202 | key_name, _ := cleanc_for_in_ident_name(for_in.key) |
| 203 | value_name, value_is_mut := cleanc_for_in_ident_name(for_in.value) |
| 204 | saved_local_types := g.runtime_local_types.clone() |
| 205 | saved_decl_types := g.runtime_decl_types.clone() |
| 206 | if key_name != '' && key_name != '_' { |
| 207 | g.write_indent() |
| 208 | g.sb.writeln('int ${key_name} = ${idx_tmp};') |
| 209 | g.remember_runtime_local_type(key_name, 'int') |
| 210 | } |
| 211 | if value_name != '' && value_name != '_' { |
| 212 | g.write_indent() |
| 213 | if value_is_mut { |
| 214 | g.sb.writeln('${elem_type}* ${value_name} = &(((${elem_type}*)${array_tmp}.data)[${idx_tmp}]);') |
| 215 | g.remember_runtime_local_type(value_name, '${elem_type}*') |
| 216 | } else { |
| 217 | g.sb.writeln('${elem_type} ${value_name} = ((${elem_type}*)${array_tmp}.data)[${idx_tmp}];') |
| 218 | if elem_type == 'string' { |
| 219 | g.write_indent() |
| 220 | g.sb.writeln('${value_name} = string__clone(${value_name});') |
| 221 | } |
| 222 | g.remember_runtime_local_type(value_name, elem_type) |
| 223 | } |
| 224 | } |
| 225 | g.gen_stmts(node.stmts) |
| 226 | g.runtime_local_types = saved_local_types.clone() |
| 227 | g.runtime_decl_types = saved_decl_types.clone() |
| 228 | g.not_local_var_cache.clear() |
| 229 | g.indent-- |
| 230 | g.write_indent() |
| 231 | g.sb.writeln('}') |
| 232 | return true |
| 233 | } |
| 234 | |
| 235 | fn (mut g Gen) gen_transformed_untyped_map_for_in_stmt(node ast.ForStmt) bool { |
| 236 | if node.init !is ast.AssignStmt || node.stmts.len == 0 { |
| 237 | return false |
| 238 | } |
| 239 | init := node.init as ast.AssignStmt |
| 240 | if init.lhs.len == 0 || init.lhs[0] !is ast.Ident { |
| 241 | return false |
| 242 | } |
| 243 | key_name := (init.lhs[0] as ast.Ident).name |
| 244 | first_stmt := node.stmts[0] |
| 245 | if first_stmt !is ast.AssignStmt { |
| 246 | return false |
| 247 | } |
| 248 | value_assign := first_stmt as ast.AssignStmt |
| 249 | if value_assign.lhs.len == 0 || value_assign.rhs.len == 0 || value_assign.lhs[0] !is ast.Ident |
| 250 | || value_assign.rhs[0] !is ast.IndexExpr { |
| 251 | return false |
| 252 | } |
| 253 | value_name := (value_assign.lhs[0] as ast.Ident).name |
| 254 | index_expr := value_assign.rhs[0] as ast.IndexExpr |
| 255 | mut map_type := g.get_expr_type(index_expr.lhs).trim_space().trim_right('*') |
| 256 | if (map_type == '' || map_type == 'int') && index_expr.lhs is ast.Ident { |
| 257 | map_type = |
| 258 | (g.get_local_var_c_type(index_expr.lhs.name) or { '' }).trim_space().trim_right('*') |
| 259 | } |
| 260 | if !map_type.starts_with('Map_') { |
| 261 | return false |
| 262 | } |
| 263 | map_node := ast.ForStmt{ |
| 264 | stmts: node.stmts[1..] |
| 265 | } |
| 266 | return g.gen_map_for_in_stmt(map_node, ast.ForInStmt{ |
| 267 | key: ast.Expr(ast.Ident{ |
| 268 | name: key_name |
| 269 | }) |
| 270 | value: ast.Expr(ast.Ident{ |
| 271 | name: value_name |
| 272 | }) |
| 273 | expr: index_expr.lhs |
| 274 | }) |
| 275 | } |
| 276 | |
| 277 | fn (mut g Gen) gen_stmt_inline(node ast.Stmt) { |
| 278 | if !stmt_has_valid_data(node) { |
| 279 | return |
| 280 | } |
| 281 | match node { |
| 282 | ast.AssignStmt { |
| 283 | lhs := node.lhs[0] |
| 284 | rhs := node.rhs[0] |
| 285 | if node.op == .decl_assign { |
| 286 | mut name := '' |
| 287 | if lhs is ast.Ident { |
| 288 | name = lhs.name |
| 289 | } |
| 290 | typ := g.get_expr_type(rhs) |
| 291 | g.sb.write_string('${typ} ${name} = ') |
| 292 | g.expr(rhs) |
| 293 | // Register the for-loop init variable so assignments inside |
| 294 | // the loop body don't re-declare it. |
| 295 | if name != '' && typ != '' { |
| 296 | g.remember_runtime_local_type(name, typ) |
| 297 | } |
| 298 | } else { |
| 299 | g.expr(lhs) |
| 300 | op_str := match node.op { |
| 301 | .assign { '=' } |
| 302 | .plus_assign { '+=' } |
| 303 | .minus_assign { '-=' } |
| 304 | .mul_assign { '*=' } |
| 305 | .div_assign { '/=' } |
| 306 | .mod_assign { '%=' } |
| 307 | .and_assign { '&=' } |
| 308 | .or_assign { '|=' } |
| 309 | .xor_assign { '^=' } |
| 310 | .left_shift_assign { '<<=' } |
| 311 | .right_shift_assign { '>>=' } |
| 312 | else { '=' } |
| 313 | } |
| 314 | |
| 315 | g.sb.write_string(' ${op_str} ') |
| 316 | g.expr(rhs) |
| 317 | } |
| 318 | } |
| 319 | ast.ExprStmt { |
| 320 | g.expr(node.expr) |
| 321 | } |
| 322 | else {} |
| 323 | } |
| 324 | } |
| 325 | |