| 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 v2.ast |
| 8 | import v2.types |
| 9 | |
| 10 | // set_export_const_symbols controls whether emitted module const macros |
| 11 | // also get exported as linkable global symbols. |
| 12 | pub fn (mut g Gen) set_export_const_symbols(enable bool) { |
| 13 | g.export_const_symbols = enable |
| 14 | } |
| 15 | |
| 16 | // set_cache_bundle_name sets the cache bundle label used for emitting a |
| 17 | // deterministic cache-init function (e.g. __v2_cached_init_builtin). |
| 18 | pub fn (mut g Gen) set_cache_bundle_name(name string) { |
| 19 | g.cache_bundle_name = name.trim_space() |
| 20 | } |
| 21 | |
| 22 | fn (mut g Gen) gen_file_extern_globals(file ast.File) { |
| 23 | g.set_file_module(file) |
| 24 | for stmt in file.stmts { |
| 25 | if !stmt_has_valid_data(stmt) { |
| 26 | continue |
| 27 | } |
| 28 | if stmt is ast.GlobalDecl { |
| 29 | g.gen_global_decl_extern(stmt) |
| 30 | } |
| 31 | } |
| 32 | } |
| 33 | |
| 34 | fn (mut g Gen) gen_file_extern_consts(file ast.File) { |
| 35 | g.set_file_module(file) |
| 36 | for stmt in file.stmts { |
| 37 | if !stmt_has_valid_data(stmt) { |
| 38 | continue |
| 39 | } |
| 40 | if stmt is ast.ConstDecl { |
| 41 | g.gen_const_decl_extern(stmt) |
| 42 | } |
| 43 | } |
| 44 | } |
| 45 | |
| 46 | fn runtime_const_target_key(mod string, name string) string { |
| 47 | return '${mod}::${name}' |
| 48 | } |
| 49 | |
| 50 | fn generated_decl_name_for_module(mod string, field_name string) ?string { |
| 51 | if field_name == '' || field_name.starts_with('C.') { |
| 52 | return none |
| 53 | } |
| 54 | if mod != '' && mod != 'main' && mod != 'builtin' { |
| 55 | return '${mod}__${field_name}' |
| 56 | } |
| 57 | return field_name |
| 58 | } |
| 59 | |
| 60 | fn (g &Gen) generated_decl_name(field_name string) ?string { |
| 61 | return generated_decl_name_for_module(g.cur_module, field_name) |
| 62 | } |
| 63 | |
| 64 | fn (mut g Gen) const_storage_name(name string) string { |
| 65 | if cname := g.const_c_names[name] { |
| 66 | return cname |
| 67 | } |
| 68 | if name in g.declared_fn_names { |
| 69 | cname := '__v_const_${name}' |
| 70 | g.const_c_names[name] = cname |
| 71 | return cname |
| 72 | } |
| 73 | return name |
| 74 | } |
| 75 | |
| 76 | fn (g &Gen) renamed_const_name_for_ident(name string) ?string { |
| 77 | if generated_name := generated_decl_name_for_module(g.cur_module, name) { |
| 78 | if cname := g.const_c_names[generated_name] { |
| 79 | return cname |
| 80 | } |
| 81 | } |
| 82 | if cname := g.const_c_names[name] { |
| 83 | return cname |
| 84 | } |
| 85 | return none |
| 86 | } |
| 87 | |
| 88 | fn (g &Gen) is_scalar_zero_init_type(typ string) bool { |
| 89 | if typ in primitive_types || typ.ends_with('*') || typ in ['byteptr', 'charptr', 'voidptr'] { |
| 90 | return true |
| 91 | } |
| 92 | return g.is_enum_type(typ) |
| 93 | } |
| 94 | |
| 95 | fn (g &Gen) global_initializer_needs_runtime(expr ast.Expr) bool { |
| 96 | match expr { |
| 97 | ast.EmptyExpr { |
| 98 | return false |
| 99 | } |
| 100 | ast.InitExpr, ast.MapInitExpr, ast.IfExpr, ast.CallOrCastExpr { |
| 101 | return true |
| 102 | } |
| 103 | ast.OrExpr, ast.StringInterLiteral, ast.UnsafeExpr, ast.LockExpr, ast.MatchExpr { |
| 104 | return true |
| 105 | } |
| 106 | ast.ComptimeExpr { |
| 107 | return g.global_initializer_needs_runtime(expr.expr) |
| 108 | } |
| 109 | ast.ArrayInitExpr { |
| 110 | if expr.typ is ast.Type && expr.typ is ast.ArrayFixedType { |
| 111 | return g.contains_call_expr(expr) |
| 112 | } |
| 113 | return true |
| 114 | } |
| 115 | ast.CastExpr { |
| 116 | return g.global_initializer_needs_runtime(expr.expr) |
| 117 | } |
| 118 | ast.ParenExpr { |
| 119 | return g.global_initializer_needs_runtime(expr.expr) |
| 120 | } |
| 121 | ast.PrefixExpr { |
| 122 | return g.global_initializer_needs_runtime(expr.expr) |
| 123 | } |
| 124 | ast.PostfixExpr { |
| 125 | return g.global_initializer_needs_runtime(expr.expr) |
| 126 | } |
| 127 | ast.ModifierExpr { |
| 128 | return g.global_initializer_needs_runtime(expr.expr) |
| 129 | } |
| 130 | ast.IndexExpr { |
| 131 | return g.global_initializer_needs_runtime(expr.lhs) |
| 132 | || g.global_initializer_needs_runtime(expr.expr) |
| 133 | } |
| 134 | ast.InfixExpr { |
| 135 | return g.global_initializer_needs_runtime(expr.lhs) |
| 136 | || g.global_initializer_needs_runtime(expr.rhs) |
| 137 | } |
| 138 | else { |
| 139 | return g.contains_call_expr(expr) |
| 140 | } |
| 141 | } |
| 142 | } |
| 143 | |
| 144 | fn (g &Gen) const_initializer_needs_runtime(expr ast.Expr) bool { |
| 145 | match expr { |
| 146 | ast.OrExpr, ast.StringInterLiteral, ast.UnsafeExpr, ast.IfExpr { |
| 147 | return true |
| 148 | } |
| 149 | ast.ComptimeExpr { |
| 150 | return g.const_initializer_needs_runtime(expr.expr) |
| 151 | } |
| 152 | ast.CastExpr { |
| 153 | return g.const_initializer_needs_runtime(expr.expr) |
| 154 | } |
| 155 | ast.ParenExpr { |
| 156 | return g.const_initializer_needs_runtime(expr.expr) |
| 157 | } |
| 158 | ast.PrefixExpr { |
| 159 | return g.const_initializer_needs_runtime(expr.expr) |
| 160 | } |
| 161 | ast.PostfixExpr { |
| 162 | return g.const_initializer_needs_runtime(expr.expr) |
| 163 | } |
| 164 | ast.ModifierExpr { |
| 165 | return g.const_initializer_needs_runtime(expr.expr) |
| 166 | } |
| 167 | ast.IndexExpr { |
| 168 | return g.const_initializer_needs_runtime(expr.lhs) |
| 169 | || g.const_initializer_needs_runtime(expr.expr) |
| 170 | } |
| 171 | ast.InfixExpr { |
| 172 | return g.const_initializer_needs_runtime(expr.lhs) |
| 173 | || g.const_initializer_needs_runtime(expr.rhs) |
| 174 | } |
| 175 | ast.ArrayInitExpr { |
| 176 | for elem in expr.exprs { |
| 177 | if g.const_initializer_needs_runtime(elem) { |
| 178 | return true |
| 179 | } |
| 180 | } |
| 181 | return (expr.init !is ast.EmptyExpr && g.const_initializer_needs_runtime(expr.init)) |
| 182 | || (expr.len !is ast.EmptyExpr && g.const_initializer_needs_runtime(expr.len)) |
| 183 | || (expr.cap !is ast.EmptyExpr && g.const_initializer_needs_runtime(expr.cap)) |
| 184 | } |
| 185 | else { |
| 186 | return g.contains_call_expr(expr) |
| 187 | } |
| 188 | } |
| 189 | } |
| 190 | |
| 191 | fn (mut g Gen) collect_runtime_const_targets() { |
| 192 | g.runtime_const_targets = map[string]bool{} |
| 193 | mut module_consts := map[string]map[string]bool{} |
| 194 | if g.has_flat() { |
| 195 | for i in 0 .. g.flat.files.len { |
| 196 | fc := g.flat.file_cursor(i) |
| 197 | module_name := flat_file_module_name(fc) |
| 198 | mut const_names := if module_name in module_consts { |
| 199 | module_consts[module_name].clone() |
| 200 | } else { |
| 201 | map[string]bool{} |
| 202 | } |
| 203 | stmts := fc.stmts() |
| 204 | for j in 0 .. stmts.len() { |
| 205 | stmt := stmts.at(j) |
| 206 | if stmt.kind() != .stmt_const_decl { |
| 207 | continue |
| 208 | } |
| 209 | const_decl := stmt.const_decl() |
| 210 | for field in const_decl.fields { |
| 211 | if field.name != '' && !field.name.starts_with('C.') { |
| 212 | const_names[field.name] = true |
| 213 | } |
| 214 | } |
| 215 | } |
| 216 | module_consts[module_name] = const_names.clone() |
| 217 | } |
| 218 | for i in 0 .. g.flat.files.len { |
| 219 | fc := g.flat.file_cursor(i) |
| 220 | g.set_file_cursor_module(fc) |
| 221 | const_names := if g.cur_module in module_consts { |
| 222 | module_consts[g.cur_module].clone() |
| 223 | } else { |
| 224 | map[string]bool{} |
| 225 | } |
| 226 | if const_names.len == 0 { |
| 227 | continue |
| 228 | } |
| 229 | stmts := fc.stmts() |
| 230 | for j in 0 .. stmts.len() { |
| 231 | stmt := stmts.at(j) |
| 232 | if stmt.kind() != .stmt_fn_decl || !stmt.name().starts_with('__v_init_consts_') { |
| 233 | continue |
| 234 | } |
| 235 | body := stmt.list_at(3) |
| 236 | for k in 0 .. body.len() { |
| 237 | body_stmt := body.at(k) |
| 238 | if body_stmt.kind() != .stmt_assign { |
| 239 | continue |
| 240 | } |
| 241 | if body_stmt.extra_int() != 1 { |
| 242 | continue |
| 243 | } |
| 244 | lhs := body_stmt.edge(0) |
| 245 | if lhs.kind() != .expr_ident { |
| 246 | continue |
| 247 | } |
| 248 | if lhs.name() !in const_names { |
| 249 | continue |
| 250 | } |
| 251 | g.runtime_const_targets[runtime_const_target_key(g.cur_module, lhs.name())] = true |
| 252 | } |
| 253 | } |
| 254 | } |
| 255 | } else { |
| 256 | for file in g.files { |
| 257 | mut const_names := if file.mod in module_consts { |
| 258 | module_consts[file.mod].clone() |
| 259 | } else { |
| 260 | map[string]bool{} |
| 261 | } |
| 262 | for stmt in file.stmts { |
| 263 | if stmt !is ast.ConstDecl { |
| 264 | continue |
| 265 | } |
| 266 | const_decl := stmt as ast.ConstDecl |
| 267 | for field in const_decl.fields { |
| 268 | if field.name != '' && !field.name.starts_with('C.') { |
| 269 | const_names[field.name] = true |
| 270 | } |
| 271 | } |
| 272 | } |
| 273 | module_consts[file.mod] = const_names.clone() |
| 274 | } |
| 275 | for file in g.files { |
| 276 | g.set_file_module(file) |
| 277 | const_names := if g.cur_module in module_consts { |
| 278 | module_consts[g.cur_module].clone() |
| 279 | } else { |
| 280 | map[string]bool{} |
| 281 | } |
| 282 | if const_names.len == 0 { |
| 283 | continue |
| 284 | } |
| 285 | for stmt in file.stmts { |
| 286 | if stmt !is ast.FnDecl { |
| 287 | continue |
| 288 | } |
| 289 | fn_decl := stmt as ast.FnDecl |
| 290 | if !fn_decl.name.starts_with('__v_init_consts_') { |
| 291 | continue |
| 292 | } |
| 293 | for body_stmt in fn_decl.stmts { |
| 294 | if body_stmt !is ast.AssignStmt { |
| 295 | continue |
| 296 | } |
| 297 | assign_stmt := body_stmt as ast.AssignStmt |
| 298 | if assign_stmt.lhs.len != 1 { |
| 299 | continue |
| 300 | } |
| 301 | lhs_expr := assign_stmt.lhs[0] |
| 302 | if lhs_expr !is ast.Ident { |
| 303 | continue |
| 304 | } |
| 305 | lhs_ident := lhs_expr as ast.Ident |
| 306 | if lhs_ident.name !in const_names { |
| 307 | continue |
| 308 | } |
| 309 | |
| 310 | g.runtime_const_targets[runtime_const_target_key(g.cur_module, lhs_ident.name)] = true |
| 311 | } |
| 312 | } |
| 313 | } |
| 314 | } |
| 315 | } |
| 316 | |
| 317 | fn (g &Gen) is_runtime_const_target(field_name string) bool { |
| 318 | return runtime_const_target_key(g.cur_module, field_name) in g.runtime_const_targets |
| 319 | } |
| 320 | |
| 321 | fn (mut g Gen) lookup_const_expr(mod string, name string) ?ast.Expr { |
| 322 | if g.has_flat() { |
| 323 | for i in 0 .. g.flat.files.len { |
| 324 | fc := g.flat.file_cursor(i) |
| 325 | if flat_file_module_name(fc) != mod { |
| 326 | continue |
| 327 | } |
| 328 | stmts := fc.stmts() |
| 329 | for j in 0 .. stmts.len() { |
| 330 | stmt := stmts.at(j) |
| 331 | if stmt.kind() != .stmt_const_decl { |
| 332 | continue |
| 333 | } |
| 334 | const_decl := stmt.const_decl() |
| 335 | for field in const_decl.fields { |
| 336 | if field.name == name { |
| 337 | return field.value |
| 338 | } |
| 339 | } |
| 340 | } |
| 341 | } |
| 342 | return none |
| 343 | } |
| 344 | for file in g.files { |
| 345 | if file.mod != mod { |
| 346 | continue |
| 347 | } |
| 348 | for stmt in file.stmts { |
| 349 | if stmt !is ast.ConstDecl { |
| 350 | continue |
| 351 | } |
| 352 | const_decl := stmt as ast.ConstDecl |
| 353 | for field in const_decl.fields { |
| 354 | if field.name == name { |
| 355 | return field.value |
| 356 | } |
| 357 | } |
| 358 | } |
| 359 | } |
| 360 | return none |
| 361 | } |
| 362 | |
| 363 | fn (mut g Gen) lookup_generated_const_expr(generated_name string) ?ast.Expr { |
| 364 | if g.has_flat() { |
| 365 | for i in 0 .. g.flat.files.len { |
| 366 | fc := g.flat.file_cursor(i) |
| 367 | module_name := flat_file_module_name(fc) |
| 368 | stmts := fc.stmts() |
| 369 | for j in 0 .. stmts.len() { |
| 370 | stmt := stmts.at(j) |
| 371 | if stmt.kind() != .stmt_const_decl { |
| 372 | continue |
| 373 | } |
| 374 | const_decl := stmt.const_decl() |
| 375 | for field in const_decl.fields { |
| 376 | name := generated_decl_name_for_module(module_name, field.name) or { continue } |
| 377 | if name == generated_name { |
| 378 | return field.value |
| 379 | } |
| 380 | } |
| 381 | } |
| 382 | } |
| 383 | return none |
| 384 | } |
| 385 | for file in g.files { |
| 386 | for stmt in file.stmts { |
| 387 | if stmt !is ast.ConstDecl { |
| 388 | continue |
| 389 | } |
| 390 | const_decl := stmt as ast.ConstDecl |
| 391 | for field in const_decl.fields { |
| 392 | name := generated_decl_name_for_module(file.mod, field.name) or { continue } |
| 393 | if name == generated_name { |
| 394 | return field.value |
| 395 | } |
| 396 | } |
| 397 | } |
| 398 | } |
| 399 | return none |
| 400 | } |
| 401 | |
| 402 | fn (mut g Gen) enum_const_selector_type(expr ast.Expr) string { |
| 403 | if expr !is ast.SelectorExpr { |
| 404 | return '' |
| 405 | } |
| 406 | sel := expr as ast.SelectorExpr |
| 407 | if raw_type := g.get_raw_type(sel) { |
| 408 | match raw_type { |
| 409 | types.Enum { |
| 410 | enum_type := g.types_type_to_c(raw_type) |
| 411 | if enum_type != '' { |
| 412 | return enum_type |
| 413 | } |
| 414 | } |
| 415 | types.Alias { |
| 416 | if raw_type.base_type is types.Enum { |
| 417 | enum_type := g.types_type_to_c(raw_type) |
| 418 | if enum_type != '' { |
| 419 | return enum_type |
| 420 | } |
| 421 | } |
| 422 | } |
| 423 | else {} |
| 424 | } |
| 425 | } |
| 426 | lhs_type := g.get_expr_type(sel.lhs) |
| 427 | if lhs_type != '' && g.is_enum_type(lhs_type) && g.enum_has_field(lhs_type, sel.rhs.name) { |
| 428 | return lhs_type |
| 429 | } |
| 430 | return '' |
| 431 | } |
| 432 | |
| 433 | fn (mut g Gen) const_decl_storage_type(expr ast.Expr) string { |
| 434 | if expr is ast.Ident { |
| 435 | if const_expr := g.lookup_const_expr(g.cur_module, expr.name) { |
| 436 | return g.const_decl_storage_type(const_expr) |
| 437 | } |
| 438 | if g.cur_module != 'builtin' { |
| 439 | if const_expr := g.lookup_const_expr('builtin', expr.name) { |
| 440 | return g.const_decl_storage_type(const_expr) |
| 441 | } |
| 442 | } |
| 443 | if const_expr := g.lookup_generated_const_expr(expr.name) { |
| 444 | return g.const_decl_storage_type(const_expr) |
| 445 | } |
| 446 | } |
| 447 | if expr is ast.SelectorExpr && expr.lhs is ast.Ident { |
| 448 | if const_expr := g.lookup_const_expr(expr.lhs.name, expr.rhs.name) { |
| 449 | return g.const_decl_storage_type(const_expr) |
| 450 | } |
| 451 | } |
| 452 | if expr is ast.CallExpr && expr.lhs is ast.Ident { |
| 453 | fn_name := (expr.lhs as ast.Ident).name |
| 454 | if fn_name in ['builtin__new_array_from_c_array_noscan', 'builtin__new_array_from_c_array', |
| 455 | 'new_array_from_c_array'] { |
| 456 | return 'array' |
| 457 | } |
| 458 | } |
| 459 | enum_type := g.enum_const_selector_type(expr) |
| 460 | if enum_type != '' { |
| 461 | return enum_type |
| 462 | } |
| 463 | if expr is ast.InfixExpr { |
| 464 | if expr.op in [.eq, .ne, .lt, .gt, .le, .ge, .and, .logical_or] { |
| 465 | return 'bool' |
| 466 | } |
| 467 | if expr.op in [.plus, .minus, .mul, .div, .mod] { |
| 468 | lhs_type := g.const_decl_storage_type(expr.lhs) |
| 469 | rhs_type := g.const_decl_storage_type(expr.rhs) |
| 470 | if expr.op == .plus && (lhs_type == 'string' || rhs_type == 'string') { |
| 471 | return 'string' |
| 472 | } |
| 473 | return promote_numeric_c_types(lhs_type, rhs_type) |
| 474 | } |
| 475 | } |
| 476 | if raw_type := g.get_raw_type(expr) { |
| 477 | raw_c_type := g.types_type_to_c(raw_type) |
| 478 | if raw_c_type != '' && raw_c_type != 'void' && !raw_c_type.contains('literal') { |
| 479 | return raw_c_type |
| 480 | } |
| 481 | } |
| 482 | mut typ := g.get_expr_type(expr) |
| 483 | if typ == '' || typ == 'void' || typ == 'int_literal' || typ == 'float_literal' { |
| 484 | if expr is ast.InfixExpr { |
| 485 | numeric_type := g.infer_numeric_expr_type(expr) |
| 486 | if numeric_type != '' && !numeric_type.contains('literal') { |
| 487 | return numeric_type |
| 488 | } |
| 489 | } |
| 490 | if expr is ast.BasicLiteral { |
| 491 | if expr.kind == .number { |
| 492 | if expr.value.contains('.') || expr.value.contains('e') || expr.value.contains('E') { |
| 493 | return 'f64' |
| 494 | } |
| 495 | return 'int' |
| 496 | } |
| 497 | } |
| 498 | if typ == 'float_literal' { |
| 499 | return 'f64' |
| 500 | } |
| 501 | return 'int' |
| 502 | } |
| 503 | if expr is ast.InfixExpr && typ == 'string' { |
| 504 | numeric_type := g.infer_numeric_expr_type(expr) |
| 505 | if numeric_type != '' && !numeric_type.contains('literal') && numeric_type != 'string' { |
| 506 | return numeric_type |
| 507 | } |
| 508 | } |
| 509 | return typ |
| 510 | } |
| 511 | |
| 512 | fn (mut g Gen) const_expr_c_value_for_header(expr ast.Expr) ?string { |
| 513 | return g.const_expr_c_value_for_header_in_module(expr, g.cur_module, 0) |
| 514 | } |
| 515 | |
| 516 | fn (mut g Gen) resolved_scalar_const_expr(expr ast.Expr, rendered string) string { |
| 517 | if expr_contains_ident(expr) { |
| 518 | if value := g.const_expr_c_value_for_header(expr) { |
| 519 | return value |
| 520 | } |
| 521 | return g.resolve_const_expr(rendered) |
| 522 | } |
| 523 | return rendered |
| 524 | } |
| 525 | |
| 526 | fn (mut g Gen) const_expr_c_value_for_header_in_module(expr ast.Expr, mod string, depth int) ?string { |
| 527 | if depth > 20 { |
| 528 | return none |
| 529 | } |
| 530 | match expr { |
| 531 | ast.BasicLiteral { |
| 532 | if expr.kind == .number { |
| 533 | return sanitize_c_number_literal(expr.value) |
| 534 | } |
| 535 | } |
| 536 | ast.Ident { |
| 537 | if const_expr := g.lookup_const_expr(mod, expr.name) { |
| 538 | return g.const_expr_c_value_for_header_in_module(const_expr, mod, depth + 1) |
| 539 | } |
| 540 | if mod != 'builtin' { |
| 541 | if const_expr := g.lookup_const_expr('builtin', expr.name) { |
| 542 | return g.const_expr_c_value_for_header_in_module(const_expr, 'builtin', depth + |
| 543 | 1) |
| 544 | } |
| 545 | } |
| 546 | } |
| 547 | ast.SelectorExpr { |
| 548 | if expr.lhs is ast.Ident { |
| 549 | lhs_ident := expr.lhs as ast.Ident |
| 550 | mut module_names := [lhs_ident.name] |
| 551 | resolved_mod_name := g.resolve_module_name(lhs_ident.name) |
| 552 | if resolved_mod_name !in module_names { |
| 553 | module_names << resolved_mod_name |
| 554 | } |
| 555 | for mod_name in module_names { |
| 556 | if const_expr := g.lookup_const_expr(mod_name, expr.rhs.name) { |
| 557 | return g.const_expr_c_value_for_header_in_module(const_expr, mod_name, |
| 558 | |
| 559 | depth + 1) |
| 560 | } |
| 561 | } |
| 562 | } |
| 563 | } |
| 564 | ast.InfixExpr { |
| 565 | lhs := g.const_expr_c_value_for_header_in_module(expr.lhs, mod, depth + 1) or { |
| 566 | return none |
| 567 | } |
| 568 | rhs := g.const_expr_c_value_for_header_in_module(expr.rhs, mod, depth + 1) or { |
| 569 | return none |
| 570 | } |
| 571 | op := match expr.op { |
| 572 | .plus { '+' } |
| 573 | .minus { '-' } |
| 574 | .mul { '*' } |
| 575 | .div { '/' } |
| 576 | .mod { '%' } |
| 577 | .amp { '&' } |
| 578 | .pipe { '|' } |
| 579 | .xor { '^' } |
| 580 | .left_shift { '<<' } |
| 581 | .right_shift { '>>' } |
| 582 | else { return none } |
| 583 | } |
| 584 | |
| 585 | return '(${lhs} ${op} ${rhs})' |
| 586 | } |
| 587 | ast.PrefixExpr { |
| 588 | value := g.const_expr_c_value_for_header_in_module(expr.expr, mod, depth + 1) or { |
| 589 | return none |
| 590 | } |
| 591 | op := match expr.op { |
| 592 | .minus { '-' } |
| 593 | .plus { '+' } |
| 594 | .bit_not { '~' } |
| 595 | else { return none } |
| 596 | } |
| 597 | |
| 598 | return '${op}${value}' |
| 599 | } |
| 600 | ast.ParenExpr { |
| 601 | value := g.const_expr_c_value_for_header_in_module(expr.expr, mod, depth + 1) or { |
| 602 | return none |
| 603 | } |
| 604 | return '(${value})' |
| 605 | } |
| 606 | ast.CastExpr { |
| 607 | value := g.const_expr_c_value_for_header_in_module(expr.expr, mod, depth + 1) or { |
| 608 | return none |
| 609 | } |
| 610 | typ := g.expr_type_to_c(expr.typ) |
| 611 | if typ == '' || typ == 'void' { |
| 612 | return value |
| 613 | } |
| 614 | return '((${typ})(${value}))' |
| 615 | } |
| 616 | ast.CallOrCastExpr { |
| 617 | value := g.const_expr_c_value_for_header_in_module(expr.expr, mod, depth + 1) or { |
| 618 | return none |
| 619 | } |
| 620 | if !g.call_or_cast_lhs_is_type(expr.lhs) { |
| 621 | return none |
| 622 | } |
| 623 | typ := g.expr_type_to_c(expr.lhs) |
| 624 | if typ == '' || typ == 'void' { |
| 625 | return value |
| 626 | } |
| 627 | return '((${typ})(${value}))' |
| 628 | } |
| 629 | ast.CallExpr { |
| 630 | if !g.call_or_cast_lhs_is_type(expr.lhs) || expr.args.len != 1 { |
| 631 | return none |
| 632 | } |
| 633 | value := g.const_expr_c_value_for_header_in_module(expr.args[0], mod, depth + 1) or { |
| 634 | return none |
| 635 | } |
| 636 | typ := g.expr_type_to_c(expr.lhs) |
| 637 | if typ == '' || typ == 'void' { |
| 638 | return value |
| 639 | } |
| 640 | return '((${typ})(${value}))' |
| 641 | } |
| 642 | else {} |
| 643 | } |
| 644 | |
| 645 | return none |
| 646 | } |
| 647 | |
| 648 | fn (mut g Gen) emit_runtime_const_storage_decl(name string, typ string) { |
| 649 | if typ == 'string' { |
| 650 | g.sb.writeln('string ${name} = {0};') |
| 651 | return |
| 652 | } |
| 653 | if g.is_scalar_zero_init_type(typ) { |
| 654 | g.sb.writeln('${typ} ${name} = 0;') |
| 655 | return |
| 656 | } |
| 657 | g.sb.writeln('${typ} ${name} = {0};') |
| 658 | } |
| 659 | |
| 660 | fn (g &Gen) is_generated_non_type_ident(expr ast.Expr) bool { |
| 661 | if expr !is ast.Ident { |
| 662 | return false |
| 663 | } |
| 664 | value_ident := expr as ast.Ident |
| 665 | return value_ident.name.contains('__') && !g.is_type_name(value_ident.name) |
| 666 | } |
| 667 | |
| 668 | fn (mut g Gen) is_type_only_const_expr(expr ast.Expr) bool { |
| 669 | if is_header_type_only_const_expr(expr) { |
| 670 | return true |
| 671 | } |
| 672 | if expr is ast.Ident { |
| 673 | if expr.name.contains('.') { |
| 674 | return g.is_type_name(g.expr_type_to_c(expr)) |
| 675 | } |
| 676 | return g.is_type_name(expr.name) |
| 677 | } |
| 678 | if expr is ast.SelectorExpr { |
| 679 | return g.is_type_name(g.expr_type_to_c(expr)) |
| 680 | } |
| 681 | return false |
| 682 | } |
| 683 | |
| 684 | struct FileScopeGenContext { |
| 685 | cur_fn_scope &types.Scope = unsafe { nil } |
| 686 | cur_fn_name string |
| 687 | cur_fn_c_name string |
| 688 | cur_fn_ret_type string |
| 689 | cur_fn_c_ret_type string |
| 690 | cur_fn_scope_miss_key string |
| 691 | runtime_local_types map[string]string |
| 692 | runtime_decl_types map[string]string |
| 693 | cur_fn_mut_params map[string]bool |
| 694 | cur_fn_returned_idents map[string]bool |
| 695 | not_local_var_cache map[string]bool |
| 696 | } |
| 697 | |
| 698 | fn (mut g Gen) enter_file_scope_context() FileScopeGenContext { |
| 699 | ctx := FileScopeGenContext{ |
| 700 | cur_fn_scope: g.cur_fn_scope |
| 701 | cur_fn_name: g.cur_fn_name |
| 702 | cur_fn_c_name: g.cur_fn_c_name |
| 703 | cur_fn_ret_type: g.cur_fn_ret_type |
| 704 | cur_fn_c_ret_type: g.cur_fn_c_ret_type |
| 705 | cur_fn_scope_miss_key: g.cur_fn_scope_miss_key |
| 706 | runtime_local_types: g.runtime_local_types.clone() |
| 707 | runtime_decl_types: g.runtime_decl_types.clone() |
| 708 | cur_fn_mut_params: g.cur_fn_mut_params.clone() |
| 709 | cur_fn_returned_idents: g.cur_fn_returned_idents.clone() |
| 710 | not_local_var_cache: g.not_local_var_cache.clone() |
| 711 | } |
| 712 | g.cur_fn_scope = unsafe { nil } |
| 713 | g.cur_fn_name = '' |
| 714 | g.cur_fn_c_name = '' |
| 715 | g.cur_fn_ret_type = '' |
| 716 | g.cur_fn_c_ret_type = '' |
| 717 | g.cur_fn_scope_miss_key = '' |
| 718 | g.runtime_local_types.clear() |
| 719 | g.runtime_decl_types.clear() |
| 720 | g.cur_fn_mut_params.clear() |
| 721 | g.cur_fn_returned_idents.clear() |
| 722 | g.not_local_var_cache.clear() |
| 723 | return ctx |
| 724 | } |
| 725 | |
| 726 | fn (mut g Gen) leave_file_scope_context(ctx FileScopeGenContext) { |
| 727 | g.cur_fn_scope = ctx.cur_fn_scope |
| 728 | g.cur_fn_name = ctx.cur_fn_name |
| 729 | g.cur_fn_c_name = ctx.cur_fn_c_name |
| 730 | g.cur_fn_ret_type = ctx.cur_fn_ret_type |
| 731 | g.cur_fn_c_ret_type = ctx.cur_fn_c_ret_type |
| 732 | g.cur_fn_scope_miss_key = ctx.cur_fn_scope_miss_key |
| 733 | g.runtime_local_types = ctx.runtime_local_types.clone() |
| 734 | g.runtime_decl_types = ctx.runtime_decl_types.clone() |
| 735 | g.cur_fn_mut_params = ctx.cur_fn_mut_params.clone() |
| 736 | g.cur_fn_returned_idents = ctx.cur_fn_returned_idents.clone() |
| 737 | g.not_local_var_cache = ctx.not_local_var_cache.clone() |
| 738 | } |
| 739 | |
| 740 | fn (mut g Gen) gen_const_decl_extern(node ast.ConstDecl) { |
| 741 | file_scope_ctx := g.enter_file_scope_context() |
| 742 | defer { |
| 743 | g.leave_file_scope_context(file_scope_ctx) |
| 744 | } |
| 745 | for field in node.fields { |
| 746 | name := g.generated_decl_name(field.name) or { continue } |
| 747 | c_name := g.const_storage_name(name) |
| 748 | // Skip constants already emitted as either extern or #define |
| 749 | // (can happen when both .vh and full source files are parsed). |
| 750 | extern_key := 'extern_const_${c_name}' |
| 751 | macro_key := 'extern_const_macro_${c_name}' |
| 752 | if extern_key in g.emitted_types || macro_key in g.emitted_types { |
| 753 | continue |
| 754 | } |
| 755 | mut is_type_only := g.is_type_only_const_expr(field.value) |
| 756 | if is_type_only && g.is_generated_non_type_ident(field.value) { |
| 757 | is_type_only = false |
| 758 | } |
| 759 | if g.is_runtime_const_target(field.name) { |
| 760 | typ := g.const_decl_storage_type(field.value) |
| 761 | if typ == '' || typ == 'void' { |
| 762 | continue |
| 763 | } |
| 764 | g.emitted_types[extern_key] = true |
| 765 | g.sb.writeln('extern ${typ} ${c_name};') |
| 766 | continue |
| 767 | } |
| 768 | if is_type_only { |
| 769 | key := extern_key |
| 770 | typ := g.expr_type_to_c(field.value) |
| 771 | if typ == '' || typ == 'void' { |
| 772 | continue |
| 773 | } |
| 774 | if typ.starts_with('Array_fixed_') || typ.contains(' ') || typ.contains('literal') |
| 775 | || typ == 'mach_timebase_info_data_t' { |
| 776 | continue |
| 777 | } |
| 778 | g.emitted_types[key] = true |
| 779 | g.sb.writeln('extern ${typ} ${c_name};') |
| 780 | continue |
| 781 | } |
| 782 | value_expr := g.expr_to_string(field.value) |
| 783 | if value_expr.len == 0 { |
| 784 | continue |
| 785 | } |
| 786 | // Array consts with static backing data or inline compound literals |
| 787 | // cannot be #defined across translation units. Emit extern declarations instead. |
| 788 | if value_expr.contains('__const_array_data_') |
| 789 | || value_expr.contains('new_array_from_c_array') { |
| 790 | g.emitted_types[macro_key] = true |
| 791 | g.sb.writeln('extern array ${c_name};') |
| 792 | continue |
| 793 | } |
| 794 | mut macro_expr := value_expr |
| 795 | if macro_expr.contains('\n') { |
| 796 | // Keep macro definitions valid when value expressions are rendered |
| 797 | // across multiple lines (e.g. large string literals). |
| 798 | macro_expr = macro_expr.replace('\n', ' \\\n') |
| 799 | } |
| 800 | g.emitted_types[macro_key] = true |
| 801 | // Unqualified numeric consts use #define with fully-resolved literal |
| 802 | // values and also store into const_exprs so that downstream consts |
| 803 | // that reference them can be resolved too. This avoids macro |
| 804 | // collisions with local variables of the same name (e.g. max_len |
| 805 | // from sorted_map.v vs max_len parameter in eprint_space_padding). |
| 806 | if !name.contains('__') && is_numeric_const_expr(field.value) { |
| 807 | typ := g.const_decl_storage_type(field.value) |
| 808 | if typ != '' && typ != 'void' { |
| 809 | resolved := g.resolved_scalar_const_expr(field.value, macro_expr) |
| 810 | g.const_exprs[name] = resolved |
| 811 | g.sb.writeln('static const ${typ} ${c_name} = ${resolved};') |
| 812 | continue |
| 813 | } |
| 814 | } |
| 815 | if !name.contains('__') { |
| 816 | typ := g.const_decl_storage_type(field.value) |
| 817 | if g.is_scalar_zero_init_type(typ) || typ == 'string' { |
| 818 | resolved := g.resolved_scalar_const_expr(field.value, macro_expr) |
| 819 | g.sb.writeln('static const ${typ} ${c_name} = ${resolved};') |
| 820 | continue |
| 821 | } |
| 822 | } |
| 823 | // C struct zero-init consts are emitted as global variables in |
| 824 | // gen_const_decl, so the extern declaration must match. |
| 825 | if is_c_struct_init(field.value) { |
| 826 | typ := g.const_decl_storage_type(field.value) |
| 827 | if typ != '' && typ != 'void' && typ != 'int' { |
| 828 | g.sb.writeln('extern ${typ} ${c_name};') |
| 829 | continue |
| 830 | } |
| 831 | } |
| 832 | g.sb.writeln('#define ${c_name} ${macro_expr}') |
| 833 | } |
| 834 | } |
| 835 | |
| 836 | fn module_storage_c_name(module_name string, name string) string { |
| 837 | if name == '' || name.starts_with('C.') { |
| 838 | return name |
| 839 | } |
| 840 | if module_name != '' && module_name != 'main' && module_name != 'builtin' { |
| 841 | return '${module_name}__${name}' |
| 842 | } |
| 843 | return name |
| 844 | } |
| 845 | |
| 846 | fn module_storage_field_is_c_extern(node ast.GlobalDecl, field ast.FieldDecl) bool { |
| 847 | return field.name.starts_with('C.') || node.attributes.has('c_extern') |
| 848 | || field.attributes.has('c_extern') |
| 849 | } |
| 850 | |
| 851 | fn module_storage_field_c_name(module_name string, node ast.GlobalDecl, field ast.FieldDecl) string { |
| 852 | if module_storage_field_is_c_extern(node, field) { |
| 853 | return if field.name.starts_with('C.') { field.name.all_after('C.') } else { field.name } |
| 854 | } |
| 855 | return module_storage_c_name(module_name, field.name) |
| 856 | } |
| 857 | |
| 858 | fn (mut g Gen) gen_global_decl(node ast.GlobalDecl) { |
| 859 | for field in node.fields { |
| 860 | // Skip C globals that are already provided by C headers or cheaders. |
| 861 | if module_storage_field_is_c_extern(node, field) { |
| 862 | continue |
| 863 | } |
| 864 | name := module_storage_field_c_name(g.cur_module, node, field) |
| 865 | key := 'global_${name}' |
| 866 | if key in g.emitted_types { |
| 867 | continue |
| 868 | } |
| 869 | g.emitted_types[key] = true |
| 870 | g.write_indent() |
| 871 | if field.typ is ast.Type && field.typ is ast.ArrayFixedType { |
| 872 | fixed_typ := field.typ as ast.ArrayFixedType |
| 873 | elem_type := g.expr_type_to_c(fixed_typ.elem_type) |
| 874 | g.fixed_array_globals[name] = true |
| 875 | g.sb.write_string('${elem_type} ${name}[') |
| 876 | if len_expr := g.const_expr_c_value_for_header(fixed_typ.len) { |
| 877 | g.sb.write_string(len_expr) |
| 878 | } else { |
| 879 | g.expr(fixed_typ.len) |
| 880 | } |
| 881 | g.sb.write_string(']') |
| 882 | if field.value !is ast.EmptyExpr { |
| 883 | g.sb.write_string(' = ') |
| 884 | g.expr(field.value) |
| 885 | } |
| 886 | g.sb.writeln(';') |
| 887 | continue |
| 888 | } |
| 889 | mut typ := '' |
| 890 | if field.typ !is ast.EmptyExpr { |
| 891 | typ = g.expr_type_to_c(field.typ) |
| 892 | } else if field.value !is ast.EmptyExpr { |
| 893 | typ = g.get_expr_type(field.value) |
| 894 | } |
| 895 | if typ == '' || typ == 'void' { |
| 896 | typ = 'int' |
| 897 | } |
| 898 | if typ.starts_with('Array_fixed_') { |
| 899 | g.fixed_array_globals[name] = true |
| 900 | g.global_var_types[name] = typ |
| 901 | g.sb.write_string('${typ} ${name}') |
| 902 | if field.value !is ast.EmptyExpr { |
| 903 | g.sb.write_string(' = ') |
| 904 | if field.value is ast.ArrayInitExpr { |
| 905 | array_init := field.value as ast.ArrayInitExpr |
| 906 | if array_init.exprs.len == 0 && array_init.init is ast.EmptyExpr { |
| 907 | g.sb.write_string('{0}') |
| 908 | } else { |
| 909 | g.expr(field.value) |
| 910 | } |
| 911 | } else { |
| 912 | g.expr(field.value) |
| 913 | } |
| 914 | } |
| 915 | g.sb.writeln(';') |
| 916 | continue |
| 917 | } |
| 918 | g.global_var_types[name] = typ |
| 919 | // With prealloc, g_memory_block must be thread-local so each thread |
| 920 | // gets its own arena and the bump allocator is safe without locks. |
| 921 | if name == 'g_memory_block' && g.pref != unsafe { nil } && g.pref.prealloc { |
| 922 | g.sb.write_string('_Thread_local ${typ} ${name}') |
| 923 | } else { |
| 924 | g.sb.write_string('${typ} ${name}') |
| 925 | } |
| 926 | if field.value !is ast.EmptyExpr { |
| 927 | // Function calls are not compile-time constants in C |
| 928 | if g.global_initializer_needs_runtime(field.value) { |
| 929 | g.sb.writeln(';') |
| 930 | } else { |
| 931 | g.sb.write_string(' = ') |
| 932 | g.expr(field.value) |
| 933 | g.sb.writeln(';') |
| 934 | } |
| 935 | } else { |
| 936 | g.sb.writeln(';') |
| 937 | } |
| 938 | } |
| 939 | } |
| 940 | |
| 941 | fn (mut g Gen) gen_global_decl_extern(node ast.GlobalDecl) { |
| 942 | for field in node.fields { |
| 943 | // C.foo selectors are provided by C headers/macros; raw @[c_extern] |
| 944 | // globals below need an extern declaration, but C.foo does not. |
| 945 | if field.name.starts_with('C.') { |
| 946 | continue |
| 947 | } |
| 948 | name := module_storage_field_c_name(g.cur_module, node, field) |
| 949 | key := 'extern_global_${name}' |
| 950 | if key in g.emitted_types { |
| 951 | continue |
| 952 | } |
| 953 | g.emitted_types[key] = true |
| 954 | g.write_indent() |
| 955 | if field.typ is ast.Type && field.typ is ast.ArrayFixedType { |
| 956 | fixed_typ := field.typ as ast.ArrayFixedType |
| 957 | elem_type := g.expr_type_to_c(fixed_typ.elem_type) |
| 958 | g.sb.write_string('extern ${elem_type} ${name}[') |
| 959 | // Extern declarations are emitted before module const definitions. |
| 960 | // Resolve const-backed fixed-array lengths now, so C does not see an |
| 961 | // undeclared bound like `extern int a[my_const];`. |
| 962 | if len_expr := g.const_expr_c_value_for_header(fixed_typ.len) { |
| 963 | g.sb.write_string(len_expr) |
| 964 | } else { |
| 965 | g.expr(fixed_typ.len) |
| 966 | } |
| 967 | g.sb.writeln('];') |
| 968 | continue |
| 969 | } |
| 970 | mut typ := '' |
| 971 | if field.typ !is ast.EmptyExpr { |
| 972 | typ = g.expr_type_to_c(field.typ) |
| 973 | } else if field.value !is ast.EmptyExpr { |
| 974 | typ = g.get_expr_type(field.value) |
| 975 | } |
| 976 | if typ == 'mach_timebase_info_data_t' { |
| 977 | continue |
| 978 | } |
| 979 | if typ == '' || typ == 'void' { |
| 980 | typ = 'int' |
| 981 | } |
| 982 | g.global_var_types[name] = typ |
| 983 | if name == 'g_memory_block' && g.pref != unsafe { nil } && g.pref.prealloc { |
| 984 | g.sb.writeln('extern _Thread_local ${typ} ${name};') |
| 985 | } else { |
| 986 | g.sb.writeln('extern ${typ} ${name};') |
| 987 | } |
| 988 | } |
| 989 | } |
| 990 | |
| 991 | fn (mut g Gen) queue_exported_const_symbol(name string, typ string, value string) { |
| 992 | if name in g.exported_const_seen { |
| 993 | return |
| 994 | } |
| 995 | g.exported_const_seen[name] = true |
| 996 | g.exported_const_symbols << ExportedConstSymbol{ |
| 997 | name: name |
| 998 | typ: typ |
| 999 | value: value |
| 1000 | } |
| 1001 | } |
| 1002 | |
| 1003 | fn (mut g Gen) emit_exported_const_symbols() { |
| 1004 | if !g.export_const_symbols || g.exported_const_symbols.len == 0 { |
| 1005 | return |
| 1006 | } |
| 1007 | g.sb.writeln('') |
| 1008 | for sym in g.exported_const_symbols { |
| 1009 | g.sb.writeln('#ifdef ${sym.name}') |
| 1010 | g.sb.writeln('#undef ${sym.name}') |
| 1011 | g.sb.writeln('#endif') |
| 1012 | // Resolve references to other constants so the initializer is a |
| 1013 | // compile-time constant expression (required by TCC and strict C). |
| 1014 | resolved := g.resolve_const_expr(sym.value) |
| 1015 | g.sb.writeln('const ${sym.typ} ${sym.name} = ${resolved};') |
| 1016 | } |
| 1017 | } |
| 1018 | |
| 1019 | fn (g &Gen) cached_init_function_name() string { |
| 1020 | if g.cache_bundle_name.len == 0 { |
| 1021 | return '' |
| 1022 | } |
| 1023 | return '__v2_cached_init_${g.cache_bundle_name}' |
| 1024 | } |
| 1025 | |
| 1026 | fn module_const_init_fn_name(module_name string) string { |
| 1027 | return if module_name == 'builtin' || module_name == 'main' { |
| 1028 | '__v_init_consts_${module_name}' |
| 1029 | } else { |
| 1030 | '${module_name}____v_init_consts_${module_name}' |
| 1031 | } |
| 1032 | } |
| 1033 | |
| 1034 | fn (mut g Gen) emit_cached_module_init_function() { |
| 1035 | if !g.export_const_symbols || g.emit_modules.len == 0 || g.cache_bundle_name.len == 0 { |
| 1036 | return |
| 1037 | } |
| 1038 | mut modules := g.emit_modules.keys() |
| 1039 | modules.sort() |
| 1040 | mut init_modules := []string{} |
| 1041 | for module_name in modules { |
| 1042 | if g.module_has_const_init_fn(module_name) { |
| 1043 | init_modules << module_name |
| 1044 | } |
| 1045 | } |
| 1046 | g.sb.writeln('') |
| 1047 | init_fn_name := g.cached_init_function_name() |
| 1048 | g.sb.writeln('void ${init_fn_name}(void) {') |
| 1049 | for module_name in init_modules { |
| 1050 | init_fn := module_const_init_fn_name(module_name) |
| 1051 | g.sb.writeln('\t${init_fn}();') |
| 1052 | } |
| 1053 | g.sb.writeln('}') |
| 1054 | } |
| 1055 | |
| 1056 | fn (g &Gen) module_has_const_init_fn(module_name string) bool { |
| 1057 | init_fn := module_const_init_fn_name(module_name) |
| 1058 | return init_fn in g.fn_return_types |
| 1059 | } |
| 1060 | |
| 1061 | fn (mut g Gen) gen_const_decl(node ast.ConstDecl) { |
| 1062 | file_scope_ctx := g.enter_file_scope_context() |
| 1063 | defer { |
| 1064 | g.leave_file_scope_context(file_scope_ctx) |
| 1065 | } |
| 1066 | for field in node.fields { |
| 1067 | name := g.generated_decl_name(field.name) or { continue } |
| 1068 | c_name := g.const_storage_name(name) |
| 1069 | const_key := 'const_${c_name}' |
| 1070 | if const_key in g.emitted_types { |
| 1071 | continue |
| 1072 | } |
| 1073 | g.emitted_types[const_key] = true |
| 1074 | mut is_type_only := g.is_type_only_const_expr(field.value) |
| 1075 | if is_type_only && g.is_generated_non_type_ident(field.value) { |
| 1076 | is_type_only = false |
| 1077 | } |
| 1078 | if !g.should_emit_module(g.cur_module) && is_type_only { |
| 1079 | continue |
| 1080 | } |
| 1081 | // Type-only consts from .vh headers have no value — emit as zero-initialized globals |
| 1082 | // for simple scalar types only. Complex types (fixed arrays, structs) are skipped |
| 1083 | // because they need typedefs/initializer-lists that aren't available from .vh data. |
| 1084 | if is_type_only { |
| 1085 | typ := g.expr_type_to_c(field.value) |
| 1086 | if typ == '' || typ == 'void' || typ.starts_with('Array_fixed_') || typ.contains(' ') |
| 1087 | || typ.contains('literal') { |
| 1088 | continue |
| 1089 | } |
| 1090 | if typ == 'string' { |
| 1091 | g.sb.writeln('string ${c_name} = {0};') |
| 1092 | } else { |
| 1093 | g.sb.writeln('${typ} ${c_name} = 0;') |
| 1094 | } |
| 1095 | continue |
| 1096 | } |
| 1097 | if g.is_runtime_const_target(field.name) { |
| 1098 | typ := g.const_decl_storage_type(field.value) |
| 1099 | if typ == '' || typ == 'void' { |
| 1100 | continue |
| 1101 | } |
| 1102 | g.emit_runtime_const_storage_decl(c_name, typ) |
| 1103 | continue |
| 1104 | } |
| 1105 | mut is_fixed_array_const := false |
| 1106 | mut fixed_array_elem := '' |
| 1107 | mut fixed_array_len := 0 |
| 1108 | if raw_type := g.get_raw_type(field.value) { |
| 1109 | if raw_type is types.ArrayFixed { |
| 1110 | is_fixed_array_const = true |
| 1111 | fixed_array_elem = g.types_type_to_c(raw_type.elem_type) |
| 1112 | fixed_array_len = raw_type.len |
| 1113 | } |
| 1114 | } |
| 1115 | if !is_fixed_array_const && field.value is ast.ArrayInitExpr { |
| 1116 | array_value := field.value as ast.ArrayInitExpr |
| 1117 | mut elem_type := g.extract_array_elem_type(array_value.typ) |
| 1118 | if elem_type == '' && array_value.exprs.len > 0 { |
| 1119 | elem_type = g.get_expr_type(array_value.exprs[0]) |
| 1120 | } |
| 1121 | if elem_type != '' && elem_type != 'array' { |
| 1122 | // Check that no element contains a function call (not valid in C static initializers) |
| 1123 | mut has_call := false |
| 1124 | for i in 0 .. array_value.exprs.len { |
| 1125 | elem := array_value.exprs[i] |
| 1126 | if g.contains_call_expr(elem) { |
| 1127 | has_call = true |
| 1128 | break |
| 1129 | } |
| 1130 | } |
| 1131 | if !has_call { |
| 1132 | is_fixed_array_const = true |
| 1133 | fixed_array_elem = elem_type |
| 1134 | fixed_array_len = array_value.exprs.len |
| 1135 | } |
| 1136 | } |
| 1137 | } |
| 1138 | if is_fixed_array_const && fixed_array_elem != '' { |
| 1139 | g.fixed_array_globals[name] = true |
| 1140 | g.fixed_array_globals[c_name] = true |
| 1141 | if fixed_array_len > 0 { |
| 1142 | g.sb.write_string('static const ${fixed_array_elem} ${c_name}[${fixed_array_len}] = ') |
| 1143 | } else { |
| 1144 | g.sb.write_string('static const ${fixed_array_elem} ${c_name}[] = ') |
| 1145 | } |
| 1146 | if field.value is ast.ArrayInitExpr { |
| 1147 | g.gen_fixed_array_initializer(field.value as ast.ArrayInitExpr) |
| 1148 | } else { |
| 1149 | g.expr(field.value) |
| 1150 | } |
| 1151 | g.sb.writeln(';') |
| 1152 | continue |
| 1153 | } |
| 1154 | if g.try_emit_const_dynamic_array_call(c_name, field.value) { |
| 1155 | continue |
| 1156 | } |
| 1157 | typ := g.const_decl_storage_type(field.value) |
| 1158 | // Function calls and statement-expression lowerings are not compile-time |
| 1159 | // constants in C; emit as zero-initialized globals. |
| 1160 | if g.const_initializer_needs_runtime(field.value) { |
| 1161 | g.emit_runtime_const_storage_decl(c_name, typ) |
| 1162 | continue |
| 1163 | } |
| 1164 | if typ == 'string' { |
| 1165 | // String constants need a global variable |
| 1166 | g.sb.write_string('string ${c_name} = ') |
| 1167 | g.expr(field.value) |
| 1168 | g.sb.writeln(';') |
| 1169 | } else if |
| 1170 | typ in ['bool', 'char', 'rune', 'int', 'i8', 'i16', 'i32', 'i64', 'u8', 'u16', 'u32', 'u64', 'usize', 'isize', 'f32', 'f64'] |
| 1171 | || g.is_enum_type(typ) { |
| 1172 | value_expr := g.expr_to_string(field.value) |
| 1173 | resolved_value_expr := g.resolved_scalar_const_expr(field.value, value_expr) |
| 1174 | g.const_exprs[name] = resolved_value_expr |
| 1175 | // Qualified const names are safe as macros and work in C constant-expression |
| 1176 | // contexts (array sizes, static initializers). |
| 1177 | if name.contains('__') { |
| 1178 | g.sb.writeln('#define ${c_name} ${resolved_value_expr}') |
| 1179 | if g.export_const_symbols { |
| 1180 | g.queue_exported_const_symbol(c_name, typ, resolved_value_expr) |
| 1181 | } |
| 1182 | } else { |
| 1183 | // Unqualified names use static const to avoid macro collisions. |
| 1184 | // If the expression references other constants, inline their values |
| 1185 | // so that tcc (and strict C) accepts the initializer. |
| 1186 | g.sb.writeln('static const ${typ} ${c_name} = ${resolved_value_expr};') |
| 1187 | } |
| 1188 | } else { |
| 1189 | // C struct zero-init consts must be real global variables, not |
| 1190 | // #define macros, because macros expand to temporaries — taking |
| 1191 | // their address (&) or mutating them in-place has no effect. |
| 1192 | if typ != '' && typ != 'void' && typ != 'int' && is_c_struct_init(field.value) { |
| 1193 | g.sb.writeln('${typ} ${c_name} = {0};') |
| 1194 | } else if typ != '' && typ != 'void' && typ != 'int' && !typ.starts_with('Array_') |
| 1195 | && !typ.starts_with('Map_') && !typ.contains('*') && !typ.contains('(') |
| 1196 | && field.value is ast.InitExpr && (field.value as ast.InitExpr).fields.len == 0 { |
| 1197 | // Zero-initialized struct const — emit as global variable. |
| 1198 | g.sb.writeln('${typ} ${c_name} = {0};') |
| 1199 | } else { |
| 1200 | // Fallback for aggregate literals and other complex consts. |
| 1201 | g.sb.write_string('#define ${c_name} ') |
| 1202 | g.expr(field.value) |
| 1203 | g.sb.writeln('') |
| 1204 | } |
| 1205 | if typ != '' && typ != 'int' { |
| 1206 | g.const_types[name] = typ |
| 1207 | g.const_types[c_name] = typ |
| 1208 | } |
| 1209 | } |
| 1210 | } |
| 1211 | } |
| 1212 | |
| 1213 | // is_c_struct_init returns true if the expression is a C struct zero-initialization |
| 1214 | // like `C.mbedtls_ctr_drbg_context{}`. These must be emitted as global variables, |
| 1215 | // not #define macros, because they need a stable memory address. |
| 1216 | fn is_c_struct_init(e ast.Expr) bool { |
| 1217 | if e is ast.InitExpr { |
| 1218 | init := e as ast.InitExpr |
| 1219 | if init.typ is ast.Ident { |
| 1220 | ident := init.typ as ast.Ident |
| 1221 | return ident.name.starts_with('C.') |
| 1222 | } |
| 1223 | } |
| 1224 | return false |
| 1225 | } |
| 1226 | |
| 1227 | fn is_numeric_const_expr(e ast.Expr) bool { |
| 1228 | if e is ast.BasicLiteral { |
| 1229 | return true |
| 1230 | } |
| 1231 | if e is ast.InfixExpr { |
| 1232 | return is_numeric_const_expr(e.lhs) && is_numeric_const_expr(e.rhs) |
| 1233 | } |
| 1234 | if e is ast.CastExpr { |
| 1235 | return is_numeric_const_expr(e.expr) |
| 1236 | } |
| 1237 | if e is ast.ParenExpr { |
| 1238 | return is_numeric_const_expr(e.expr) |
| 1239 | } |
| 1240 | if e is ast.PrefixExpr { |
| 1241 | return is_numeric_const_expr(e.expr) |
| 1242 | } |
| 1243 | if e is ast.Ident { |
| 1244 | return true // references to other consts are still numeric |
| 1245 | } |
| 1246 | if e is ast.CallExpr { |
| 1247 | if e.lhs is ast.Ident && e.lhs.name in primitive_types && e.args.len == 1 { |
| 1248 | return is_numeric_const_expr(e.args[0]) |
| 1249 | } |
| 1250 | // sizeof() is a compile-time numeric expression |
| 1251 | if e.lhs is ast.Ident { |
| 1252 | return e.lhs.name == 'sizeof' |
| 1253 | } |
| 1254 | } |
| 1255 | return false |
| 1256 | } |
| 1257 | |
| 1258 | fn expr_contains_ident(e ast.Expr) bool { |
| 1259 | if e is ast.Ident { |
| 1260 | return true |
| 1261 | } |
| 1262 | if e is ast.InfixExpr { |
| 1263 | return expr_contains_ident(e.lhs) || expr_contains_ident(e.rhs) |
| 1264 | } |
| 1265 | if e is ast.CastExpr { |
| 1266 | return expr_contains_ident(e.expr) |
| 1267 | } |
| 1268 | if e is ast.CallOrCastExpr { |
| 1269 | return expr_contains_ident(e.expr) |
| 1270 | } |
| 1271 | if e is ast.CallExpr { |
| 1272 | if expr_contains_ident(e.lhs) { |
| 1273 | return true |
| 1274 | } |
| 1275 | for arg in e.args { |
| 1276 | if expr_contains_ident(arg) { |
| 1277 | return true |
| 1278 | } |
| 1279 | } |
| 1280 | } |
| 1281 | if e is ast.SelectorExpr { |
| 1282 | return true |
| 1283 | } |
| 1284 | if e is ast.ParenExpr { |
| 1285 | return expr_contains_ident(e.expr) |
| 1286 | } |
| 1287 | if e is ast.PrefixExpr { |
| 1288 | return expr_contains_ident(e.expr) |
| 1289 | } |
| 1290 | return false |
| 1291 | } |
| 1292 | |
| 1293 | // resolve_const_expr replaces references to other constants in a C expression |
| 1294 | // string with their expanded values, so that static const initializers |
| 1295 | // contain only literal values (required by tcc and the C standard). |
| 1296 | fn (g &Gen) resolve_const_expr(expr string) string { |
| 1297 | mut result := expr |
| 1298 | for _ in 0 .. 10 { |
| 1299 | mut changed := false |
| 1300 | for cname, cval in g.const_exprs { |
| 1301 | new_result := replace_whole_word(result, cname, cval) |
| 1302 | if new_result != result { |
| 1303 | result = new_result |
| 1304 | changed = true |
| 1305 | } |
| 1306 | } |
| 1307 | if !changed { |
| 1308 | break |
| 1309 | } |
| 1310 | } |
| 1311 | return result |
| 1312 | } |
| 1313 | |
| 1314 | fn replace_whole_word(s string, word string, replacement string) string { |
| 1315 | mut result := s |
| 1316 | mut pos := 0 |
| 1317 | for { |
| 1318 | idx := result.index_after(word, pos) or { break } |
| 1319 | // Check word boundary before. |
| 1320 | if idx > 0 { |
| 1321 | c := result[idx - 1] |
| 1322 | if c == `_` || (c >= `a` && c <= `z`) || (c >= `A` && c <= `Z`) |
| 1323 | || (c >= `0` && c <= `9`) { |
| 1324 | pos = idx + word.len |
| 1325 | continue |
| 1326 | } |
| 1327 | } |
| 1328 | // Check word boundary after. |
| 1329 | end := idx + word.len |
| 1330 | if end < result.len { |
| 1331 | c := result[end] |
| 1332 | if c == `_` || (c >= `a` && c <= `z`) || (c >= `A` && c <= `Z`) |
| 1333 | || (c >= `0` && c <= `9`) { |
| 1334 | pos = idx + word.len |
| 1335 | continue |
| 1336 | } |
| 1337 | } |
| 1338 | result = result[..idx] + replacement + result[end..] |
| 1339 | pos = idx + replacement.len |
| 1340 | } |
| 1341 | return result |
| 1342 | } |
| 1343 | |