| 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.parser |
| 9 | import v2.pref as vpref |
| 10 | import v2.token |
| 11 | import v2.types |
| 12 | import os |
| 13 | |
| 14 | const preamble_includes_freestanding = r'// Generated by V Clean C Backend |
| 15 | #include <stdbool.h> |
| 16 | #include <stdint.h> |
| 17 | #ifndef __TINYC__ |
| 18 | #include <stdatomic.h> |
| 19 | #endif |
| 20 | #include <stddef.h> |
| 21 | #include <string.h> |
| 22 | ' |
| 23 | |
| 24 | const preamble_includes_minimal = r'// Generated by V Clean C Backend |
| 25 | #include <stdio.h> |
| 26 | #include <stdlib.h> |
| 27 | #include <stdbool.h> |
| 28 | #include <stdint.h> |
| 29 | #ifndef __TINYC__ |
| 30 | #include <stdatomic.h> |
| 31 | #endif |
| 32 | #include <stddef.h> |
| 33 | #include <string.h> |
| 34 | #include <dirent.h> |
| 35 | #if defined(__APPLE__) && defined(__MACH__) && defined(__ENVIRONMENT_MAC_OS_X_VERSION_MIN_REQUIRED__) |
| 36 | #include <mach/mach.h> |
| 37 | #include <mach/task.h> |
| 38 | #include <mach/mach_time.h> |
| 39 | #endif |
| 40 | #include <pthread.h> |
| 41 | ' |
| 42 | |
| 43 | const preamble_includes_minimal_windows = r'// Generated by V Clean C Backend |
| 44 | #include <stdio.h> |
| 45 | #include <stdlib.h> |
| 46 | #include <stdbool.h> |
| 47 | #include <stdint.h> |
| 48 | #ifndef __TINYC__ |
| 49 | #include <stdatomic.h> |
| 50 | #endif |
| 51 | #include <stddef.h> |
| 52 | #include <string.h> |
| 53 | #include <windows.h> |
| 54 | ' |
| 55 | |
| 56 | const preamble_includes_full = r'// Generated by V Clean C Backend |
| 57 | #include <stdio.h> |
| 58 | #include <stdlib.h> |
| 59 | #include <stdbool.h> |
| 60 | #include <stdint.h> |
| 61 | #ifndef __TINYC__ |
| 62 | #include <stdatomic.h> |
| 63 | #endif |
| 64 | #include <stddef.h> |
| 65 | #include <string.h> |
| 66 | #include <float.h> |
| 67 | #include <math.h> |
| 68 | #include <unistd.h> |
| 69 | #include <fcntl.h> |
| 70 | #include <sys/stat.h> |
| 71 | #include <errno.h> |
| 72 | #include <signal.h> |
| 73 | #include <dirent.h> |
| 74 | #include <sys/types.h> |
| 75 | #include <sys/select.h> |
| 76 | #include <sys/wait.h> |
| 77 | #if defined(__APPLE__) && defined(__MACH__) && defined(__ENVIRONMENT_MAC_OS_X_VERSION_MIN_REQUIRED__) |
| 78 | #include <sys/syslimits.h> |
| 79 | #include <mach/mach.h> |
| 80 | #include <mach/task.h> |
| 81 | #include <mach/mach_time.h> |
| 82 | #include <execinfo.h> |
| 83 | #include <mach-o/dyld.h> |
| 84 | #endif |
| 85 | #include <termios.h> |
| 86 | #include <sys/ioctl.h> |
| 87 | #include <pthread.h> |
| 88 | #ifndef PTHREAD_RWLOCK_PREFER_WRITER_NONRECURSIVE_NP |
| 89 | #define PTHREAD_RWLOCK_PREFER_WRITER_NONRECURSIVE_NP 0 |
| 90 | #define pthread_rwlockattr_setkind_np(attr, kind) 0 |
| 91 | #endif |
| 92 | #include <time.h> |
| 93 | #include <sys/time.h> |
| 94 | #include <sys/statvfs.h> |
| 95 | #include <utime.h> |
| 96 | #include <sys/utsname.h> |
| 97 | #if defined(__APPLE__) && defined(__MACH__) && defined(__ENVIRONMENT_MAC_OS_X_VERSION_MIN_REQUIRED__) |
| 98 | #include <sys/ptrace.h> |
| 99 | #include <libproc.h> |
| 100 | #endif |
| 101 | extern char** environ; |
| 102 | #define signal(v_sig, v_handler) signal((v_sig), ((void (*)(int))(v_handler))) |
| 103 | #define v_signal_with_handler_cast(sig, handler) signal((sig), ((void (*)(int))(handler))) |
| 104 | ' |
| 105 | |
| 106 | const preamble_includes_full_windows = r'// Generated by V Clean C Backend |
| 107 | #include <stdio.h> |
| 108 | #include <stdlib.h> |
| 109 | #include <stdbool.h> |
| 110 | #include <stdint.h> |
| 111 | #ifndef __TINYC__ |
| 112 | #include <stdatomic.h> |
| 113 | #endif |
| 114 | #include <stddef.h> |
| 115 | #include <string.h> |
| 116 | #include <float.h> |
| 117 | #include <math.h> |
| 118 | #include <errno.h> |
| 119 | #include <signal.h> |
| 120 | #include <time.h> |
| 121 | #include <windows.h> |
| 122 | #define signal(v_sig, v_handler) signal((v_sig), ((void (*)(int))(v_handler))) |
| 123 | #define v_signal_with_handler_cast(sig, handler) signal((sig), ((void (*)(int))(handler))) |
| 124 | ' |
| 125 | |
| 126 | const preamble_includes_minimal_cross = r'// Generated by V Clean C Backend |
| 127 | #include <stdio.h> |
| 128 | #include <stdlib.h> |
| 129 | #include <stdbool.h> |
| 130 | #include <stdint.h> |
| 131 | #ifndef __TINYC__ |
| 132 | #include <stdatomic.h> |
| 133 | #endif |
| 134 | #include <stddef.h> |
| 135 | #include <string.h> |
| 136 | #if defined(_WIN32) |
| 137 | #include <windows.h> |
| 138 | #else |
| 139 | #include <dirent.h> |
| 140 | #include <pthread.h> |
| 141 | #endif |
| 142 | #if defined(__APPLE__) && defined(__MACH__) && defined(__ENVIRONMENT_MAC_OS_X_VERSION_MIN_REQUIRED__) |
| 143 | #include <mach/mach.h> |
| 144 | #include <mach/task.h> |
| 145 | #include <mach/mach_time.h> |
| 146 | #endif |
| 147 | ' |
| 148 | |
| 149 | const preamble_includes_full_cross = r'// Generated by V Clean C Backend |
| 150 | #include <stdio.h> |
| 151 | #include <stdlib.h> |
| 152 | #include <stdbool.h> |
| 153 | #include <stdint.h> |
| 154 | #ifndef __TINYC__ |
| 155 | #include <stdatomic.h> |
| 156 | #endif |
| 157 | #include <stddef.h> |
| 158 | #include <string.h> |
| 159 | #include <float.h> |
| 160 | #include <math.h> |
| 161 | #include <errno.h> |
| 162 | #include <signal.h> |
| 163 | #include <time.h> |
| 164 | #if defined(_WIN32) |
| 165 | #include <windows.h> |
| 166 | #else |
| 167 | #include <unistd.h> |
| 168 | #include <fcntl.h> |
| 169 | #include <sys/stat.h> |
| 170 | #include <dirent.h> |
| 171 | #include <sys/types.h> |
| 172 | #include <sys/select.h> |
| 173 | #include <sys/wait.h> |
| 174 | #include <termios.h> |
| 175 | #include <sys/ioctl.h> |
| 176 | #include <pthread.h> |
| 177 | #include <sys/time.h> |
| 178 | #include <sys/statvfs.h> |
| 179 | #include <utime.h> |
| 180 | #include <sys/utsname.h> |
| 181 | #ifndef PTHREAD_RWLOCK_PREFER_WRITER_NONRECURSIVE_NP |
| 182 | #define PTHREAD_RWLOCK_PREFER_WRITER_NONRECURSIVE_NP 0 |
| 183 | #define pthread_rwlockattr_setkind_np(attr, kind) 0 |
| 184 | #endif |
| 185 | extern char** environ; |
| 186 | #endif |
| 187 | #if defined(__APPLE__) && defined(__MACH__) && defined(__ENVIRONMENT_MAC_OS_X_VERSION_MIN_REQUIRED__) |
| 188 | #include <sys/syslimits.h> |
| 189 | #include <mach/mach.h> |
| 190 | #include <mach/task.h> |
| 191 | #include <mach/mach_time.h> |
| 192 | #include <execinfo.h> |
| 193 | #include <mach-o/dyld.h> |
| 194 | #include <sys/ptrace.h> |
| 195 | #include <libproc.h> |
| 196 | #endif |
| 197 | #define signal(v_sig, v_handler) signal((v_sig), ((void (*)(int))(v_handler))) |
| 198 | #define v_signal_with_handler_cast(sig, handler) signal((sig), ((void (*)(int))(handler))) |
| 199 | ' |
| 200 | |
| 201 | const apple_minimal_includes_guarded = r'#if defined(__APPLE__) && defined(__MACH__) && defined(__ENVIRONMENT_MAC_OS_X_VERSION_MIN_REQUIRED__) |
| 202 | #include <mach/mach.h> |
| 203 | #include <mach/task.h> |
| 204 | #include <mach/mach_time.h> |
| 205 | #endif |
| 206 | ' |
| 207 | |
| 208 | const apple_minimal_includes_plain = r'#include <mach/mach.h> |
| 209 | #include <mach/task.h> |
| 210 | #include <mach/mach_time.h> |
| 211 | ' |
| 212 | |
| 213 | const apple_full_includes_guarded = r'#if defined(__APPLE__) && defined(__MACH__) && defined(__ENVIRONMENT_MAC_OS_X_VERSION_MIN_REQUIRED__) |
| 214 | #include <sys/syslimits.h> |
| 215 | #include <mach/mach.h> |
| 216 | #include <mach/task.h> |
| 217 | #include <mach/mach_time.h> |
| 218 | #include <execinfo.h> |
| 219 | #include <mach-o/dyld.h> |
| 220 | #endif |
| 221 | ' |
| 222 | |
| 223 | const apple_full_includes_plain = r'#include <sys/syslimits.h> |
| 224 | #include <mach/mach.h> |
| 225 | #include <mach/task.h> |
| 226 | #include <mach/mach_time.h> |
| 227 | #include <execinfo.h> |
| 228 | #include <mach-o/dyld.h> |
| 229 | ' |
| 230 | |
| 231 | const apple_late_full_includes_guarded = r'#if defined(__APPLE__) && defined(__MACH__) && defined(__ENVIRONMENT_MAC_OS_X_VERSION_MIN_REQUIRED__) |
| 232 | #include <sys/ptrace.h> |
| 233 | #include <libproc.h> |
| 234 | #endif |
| 235 | ' |
| 236 | |
| 237 | const apple_late_full_includes_plain = r'#include <sys/ptrace.h> |
| 238 | #include <libproc.h> |
| 239 | ' |
| 240 | |
| 241 | const linux_gettid_feature_define = r'#ifndef _GNU_SOURCE |
| 242 | #define _GNU_SOURCE |
| 243 | #endif |
| 244 | ' |
| 245 | |
| 246 | const linux_gettid_helper = r'#include <sys/syscall.h> |
| 247 | static inline uint32_t v_cleanc_gettid(void) { |
| 248 | return (uint32_t)syscall(SYS_gettid); |
| 249 | } |
| 250 | ' |
| 251 | |
| 252 | const apple_macos_cross_guard = 'defined(__APPLE__) && defined(__MACH__) && defined(__ENVIRONMENT_MAC_OS_X_VERSION_MIN_REQUIRED__)' |
| 253 | const apple_ios_cross_guard = 'defined(__APPLE__) && defined(__MACH__) && defined(__ENVIRONMENT_IPHONE_OS_VERSION_MIN_REQUIRED__)' |
| 254 | const freestanding_missing_alloc_hook_message = 'v2: freestanding target requires freestanding_alloc hook for heap allocation' |
| 255 | const freestanding_missing_format_hook_message = 'v2: freestanding target cannot print non-string values without formatting support' |
| 256 | const freestanding_missing_heap_runtime_message = 'v2: freestanding target cannot use V runtime heap helpers with --skip-builtin' |
| 257 | const freestanding_missing_output_hook_message = 'v2: freestanding target requires freestanding_output hook for output' |
| 258 | const freestanding_missing_panic_hook_message = 'v2: freestanding target requires freestanding_panic hook for panic' |
| 259 | |
| 260 | fn (g &Gen) target_os_name() string { |
| 261 | return normalize_c_target_os_name(g.pref.target_os_or_host()) |
| 262 | } |
| 263 | |
| 264 | fn normalize_c_target_os_name(target_os string) string { |
| 265 | return match target_os.to_lower() { |
| 266 | 'darwin', 'mac' { 'macos' } |
| 267 | else { target_os.to_lower() } |
| 268 | } |
| 269 | } |
| 270 | |
| 271 | fn (g &Gen) has_target_define(name string) bool { |
| 272 | if g.pref == unsafe { nil } { |
| 273 | return false |
| 274 | } |
| 275 | return vpref.comptime_optional_define_value(name, g.pref.user_defines, |
| 276 | g.pref.explicit_user_defines) |
| 277 | } |
| 278 | |
| 279 | fn (g &Gen) is_freestanding_target() bool { |
| 280 | return g.pref != unsafe { nil } && g.pref.is_freestanding() |
| 281 | } |
| 282 | |
| 283 | fn (g &Gen) has_freestanding_hook_capability(capability string) bool { |
| 284 | if !g.is_freestanding_target() || g.pref == unsafe { nil } { |
| 285 | return false |
| 286 | } |
| 287 | return g.pref.has_freestanding_hook(capability) |
| 288 | } |
| 289 | |
| 290 | fn (g &Gen) has_freestanding_hooks() bool { |
| 291 | return g.has_freestanding_hook_capability('output') |
| 292 | || g.has_freestanding_hook_capability('panic') |
| 293 | || g.has_freestanding_hook_capability('alloc') |
| 294 | } |
| 295 | |
| 296 | fn (g &Gen) c_heap_malloc_call(size_expr string) string { |
| 297 | if g.has_freestanding_hook_capability('alloc') { |
| 298 | return 'v_platform_malloc(${size_expr})' |
| 299 | } |
| 300 | if g.is_freestanding_target() { |
| 301 | return g.c_freestanding_missing_alloc_ptr_expr() |
| 302 | } |
| 303 | return 'malloc(${size_expr})' |
| 304 | } |
| 305 | |
| 306 | fn (g &Gen) c_heap_realloc_call(ptr_expr string, size_expr string) string { |
| 307 | if g.has_freestanding_hook_capability('alloc') { |
| 308 | return 'v_platform_realloc(${ptr_expr}, ${size_expr})' |
| 309 | } |
| 310 | if g.is_freestanding_target() { |
| 311 | return g.c_freestanding_missing_alloc_ptr_expr() |
| 312 | } |
| 313 | return 'realloc(${ptr_expr}, ${size_expr})' |
| 314 | } |
| 315 | |
| 316 | fn (g &Gen) c_heap_free_call(ptr_expr string) string { |
| 317 | if g.has_freestanding_hook_capability('alloc') { |
| 318 | return 'v_platform_free(${ptr_expr})' |
| 319 | } |
| 320 | if g.is_freestanding_target() { |
| 321 | return g.c_freestanding_missing_alloc_void_expr() |
| 322 | } |
| 323 | return 'free(${ptr_expr})' |
| 324 | } |
| 325 | |
| 326 | fn (g &Gen) c_freestanding_missing_alloc_ptr_expr() string { |
| 327 | return '({ _Static_assert(0, "${freestanding_missing_alloc_hook_message}"); (void*)0; })' |
| 328 | } |
| 329 | |
| 330 | fn (g &Gen) c_freestanding_missing_alloc_void_expr() string { |
| 331 | return '({ _Static_assert(0, "${freestanding_missing_alloc_hook_message}"); (void)0; })' |
| 332 | } |
| 333 | |
| 334 | fn (g &Gen) c_freestanding_missing_format_string_expr() string { |
| 335 | return '({ _Static_assert(0, "${freestanding_missing_format_hook_message}"); (string){0}; })' |
| 336 | } |
| 337 | |
| 338 | fn is_target_os_ct_flag(name string) bool { |
| 339 | return name in [ |
| 340 | 'darwin', |
| 341 | 'macos', |
| 342 | 'mac', |
| 343 | 'linux', |
| 344 | 'windows', |
| 345 | 'bsd', |
| 346 | 'freebsd', |
| 347 | 'openbsd', |
| 348 | 'netbsd', |
| 349 | 'dragonfly', |
| 350 | 'android', |
| 351 | 'termux', |
| 352 | 'ios', |
| 353 | 'solaris', |
| 354 | 'qnx', |
| 355 | 'serenity', |
| 356 | 'plan9', |
| 357 | 'vinix', |
| 358 | ] |
| 359 | } |
| 360 | |
| 361 | fn c_preprocessor_flag_expr_for_ct_flag(name string) ?string { |
| 362 | return match name.to_lower() { |
| 363 | 'linux' { |
| 364 | 'defined(__linux__)' |
| 365 | } |
| 366 | 'windows' { |
| 367 | 'defined(_WIN32)' |
| 368 | } |
| 369 | 'darwin', 'macos', 'mac' { |
| 370 | apple_macos_cross_guard |
| 371 | } |
| 372 | 'bsd' { |
| 373 | '(${apple_macos_cross_guard}) || defined(__FreeBSD__) || defined(__OpenBSD__) || defined(__NetBSD__) || defined(__DragonFly__)' |
| 374 | } |
| 375 | 'freebsd' { |
| 376 | 'defined(__FreeBSD__)' |
| 377 | } |
| 378 | 'openbsd' { |
| 379 | 'defined(__OpenBSD__)' |
| 380 | } |
| 381 | 'netbsd' { |
| 382 | 'defined(__NetBSD__)' |
| 383 | } |
| 384 | 'dragonfly' { |
| 385 | 'defined(__DragonFly__)' |
| 386 | } |
| 387 | 'android' { |
| 388 | 'defined(__ANDROID__)' |
| 389 | } |
| 390 | 'termux' { |
| 391 | 'defined(__TERMUX__)' |
| 392 | } |
| 393 | 'ios' { |
| 394 | apple_ios_cross_guard |
| 395 | } |
| 396 | 'solaris' { |
| 397 | 'defined(__sun)' |
| 398 | } |
| 399 | 'qnx' { |
| 400 | 'defined(__QNX__)' |
| 401 | } |
| 402 | 'serenity' { |
| 403 | 'defined(__serenity__)' |
| 404 | } |
| 405 | 'plan9' { |
| 406 | 'defined(__plan9__)' |
| 407 | } |
| 408 | 'vinix' { |
| 409 | 'defined(__vinix__)' |
| 410 | } |
| 411 | else { |
| 412 | none |
| 413 | } |
| 414 | } |
| 415 | } |
| 416 | |
| 417 | fn optional_user_ct_flag_name(cond string) ?string { |
| 418 | trimmed := cond.trim_space() |
| 419 | if !trimmed.ends_with('?') { |
| 420 | return none |
| 421 | } |
| 422 | name := trimmed[..trimmed.len - 1].trim_space() |
| 423 | if name == '' { |
| 424 | return none |
| 425 | } |
| 426 | return name |
| 427 | } |
| 428 | |
| 429 | fn (g &Gen) c_preprocessor_expr_for_ct_cond(cond string) ?string { |
| 430 | trimmed := cond.trim_space() |
| 431 | if trimmed == '' { |
| 432 | return none |
| 433 | } |
| 434 | if or_idx := top_level_bool_op_index(trimmed, '||') { |
| 435 | left := g.c_preprocessor_expr_for_ct_cond(trimmed[..or_idx].trim_space()) or { return none } |
| 436 | right := g.c_preprocessor_expr_for_ct_cond(trimmed[or_idx + 2..].trim_space()) or { |
| 437 | return none |
| 438 | } |
| 439 | return combine_c_preprocessor_or_expr(left, right) |
| 440 | } |
| 441 | if and_idx := top_level_bool_op_index(trimmed, '&&') { |
| 442 | left := g.c_preprocessor_expr_for_ct_cond(trimmed[..and_idx].trim_space()) or { |
| 443 | return none |
| 444 | } |
| 445 | right := g.c_preprocessor_expr_for_ct_cond(trimmed[and_idx + 2..].trim_space()) or { |
| 446 | return none |
| 447 | } |
| 448 | return combine_c_preprocessor_and_expr(left, right) |
| 449 | } |
| 450 | if trimmed.starts_with('!') { |
| 451 | inner := g.c_preprocessor_expr_for_ct_cond(trimmed[1..]) or { return none } |
| 452 | return negate_c_preprocessor_expr(inner) |
| 453 | } |
| 454 | if stripped := strip_outer_bool_parens(trimmed) { |
| 455 | return g.c_preprocessor_expr_for_ct_cond(stripped) |
| 456 | } |
| 457 | if optional_name := optional_user_ct_flag_name(trimmed) { |
| 458 | return if g.has_target_define(optional_name) { '' } else { '0' } |
| 459 | } |
| 460 | lower_trimmed := trimmed.to_lower() |
| 461 | if os_guard := c_preprocessor_flag_expr_for_ct_flag(lower_trimmed) { |
| 462 | return os_guard |
| 463 | } |
| 464 | if g.directive_ct_flag_matches(lower_trimmed) { |
| 465 | return '' |
| 466 | } |
| 467 | return '0' |
| 468 | } |
| 469 | |
| 470 | fn combine_c_preprocessor_and_expr(left string, right string) string { |
| 471 | if left == '0' || right == '0' { |
| 472 | return '0' |
| 473 | } |
| 474 | if left == '' { |
| 475 | return right |
| 476 | } |
| 477 | if right == '' { |
| 478 | return left |
| 479 | } |
| 480 | return '(${left}) && (${right})' |
| 481 | } |
| 482 | |
| 483 | fn combine_c_preprocessor_or_expr(left string, right string) string { |
| 484 | if left == '' || right == '' { |
| 485 | return '' |
| 486 | } |
| 487 | if left == '0' { |
| 488 | return right |
| 489 | } |
| 490 | if right == '0' { |
| 491 | return left |
| 492 | } |
| 493 | return '(${left}) || (${right})' |
| 494 | } |
| 495 | |
| 496 | fn negate_c_preprocessor_expr(expr string) string { |
| 497 | if expr == '' { |
| 498 | return '0' |
| 499 | } |
| 500 | if expr == '0' { |
| 501 | return '' |
| 502 | } |
| 503 | return '!(${expr})' |
| 504 | } |
| 505 | |
| 506 | fn (g &Gen) preamble_includes(minimal_preamble bool) string { |
| 507 | if g.is_freestanding_target() { |
| 508 | return preamble_includes_freestanding |
| 509 | } |
| 510 | target_os := g.target_os_name() |
| 511 | if target_os == 'cross' { |
| 512 | return if minimal_preamble { |
| 513 | preamble_includes_minimal_cross |
| 514 | } else { |
| 515 | preamble_includes_full_cross |
| 516 | } |
| 517 | } |
| 518 | if target_os == 'windows' { |
| 519 | return if minimal_preamble { |
| 520 | preamble_includes_minimal_windows |
| 521 | } else { |
| 522 | preamble_includes_full_windows |
| 523 | } |
| 524 | } |
| 525 | raw := if minimal_preamble { |
| 526 | preamble_includes_minimal |
| 527 | } else { |
| 528 | preamble_includes_full |
| 529 | } |
| 530 | if target_os == '' { |
| 531 | return raw |
| 532 | } |
| 533 | mut out := raw |
| 534 | if g.should_emit_linux_gettid_compat() { |
| 535 | out = out.replace('// Generated by V Clean C Backend\n', |
| 536 | '// Generated by V Clean C Backend\n${linux_gettid_feature_define}') |
| 537 | } |
| 538 | if target_os == 'macos' { |
| 539 | out = out.replace(apple_minimal_includes_guarded, apple_minimal_includes_plain) |
| 540 | out = out.replace(apple_full_includes_guarded, apple_full_includes_plain) |
| 541 | out = out.replace(apple_late_full_includes_guarded, apple_late_full_includes_plain) |
| 542 | } else { |
| 543 | out = out.replace(apple_minimal_includes_guarded, '') |
| 544 | out = out.replace(apple_full_includes_guarded, '') |
| 545 | out = out.replace(apple_late_full_includes_guarded, '') |
| 546 | } |
| 547 | return out |
| 548 | } |
| 549 | |
| 550 | fn (g &Gen) should_emit_linux_gettid_compat() bool { |
| 551 | return !g.is_freestanding_target() && g.target_os_name() == 'linux' |
| 552 | && !g.has_target_define('no_gettid') && !g.has_target_define('musl') |
| 553 | } |
| 554 | |
| 555 | fn (mut g Gen) emit_linux_gettid_compat(minimal_preamble bool) { |
| 556 | if !g.should_emit_linux_gettid_compat() { |
| 557 | return |
| 558 | } |
| 559 | if minimal_preamble { |
| 560 | g.sb.writeln('#include <unistd.h>') |
| 561 | } |
| 562 | g.sb.write_string(linux_gettid_helper) |
| 563 | } |
| 564 | |
| 565 | fn (g &Gen) directive_ct_flag_matches(name string) bool { |
| 566 | lower_name := name.to_lower() |
| 567 | if g.target_os_name() == 'cross' && is_target_os_ct_flag(lower_name) { |
| 568 | return true |
| 569 | } |
| 570 | if vpref.comptime_flag_value(g.pref, lower_name) { |
| 571 | return true |
| 572 | } |
| 573 | target_os := g.target_os_name() |
| 574 | return match lower_name { |
| 575 | 'mac' { target_os == 'macos' } |
| 576 | 'android' { target_os == 'android' } |
| 577 | 'ios' { target_os == 'ios' } |
| 578 | 'cross' { target_os == 'cross' } |
| 579 | 'freestanding', 'bare' { g.is_freestanding_target() } |
| 580 | else { false } |
| 581 | } |
| 582 | } |
| 583 | |
| 584 | fn (g &Gen) directive_ct_cond_matches(cond string) bool { |
| 585 | if cond == '' { |
| 586 | return true |
| 587 | } |
| 588 | trimmed := cond.trim_space() |
| 589 | if g.target_os_name() == 'cross' && g.c_preprocessor_expr_for_ct_cond(trimmed) != none { |
| 590 | return true |
| 591 | } |
| 592 | if or_idx := top_level_bool_op_index(trimmed, '||') { |
| 593 | left := trimmed[..or_idx].trim_space() |
| 594 | right := trimmed[or_idx + 2..].trim_space() |
| 595 | return g.directive_ct_cond_matches(left) || g.directive_ct_cond_matches(right) |
| 596 | } |
| 597 | if and_idx := top_level_bool_op_index(trimmed, '&&') { |
| 598 | left := trimmed[..and_idx].trim_space() |
| 599 | right := trimmed[and_idx + 2..].trim_space() |
| 600 | return g.directive_ct_cond_matches(left) && g.directive_ct_cond_matches(right) |
| 601 | } |
| 602 | if trimmed.starts_with('!') { |
| 603 | return !g.directive_ct_cond_matches(trimmed[1..]) |
| 604 | } |
| 605 | if stripped := strip_outer_bool_parens(trimmed) { |
| 606 | return g.directive_ct_cond_matches(stripped) |
| 607 | } |
| 608 | if optional_name := optional_user_ct_flag_name(trimmed) { |
| 609 | return g.has_target_define(optional_name) |
| 610 | } |
| 611 | return g.directive_ct_flag_matches(trimmed) |
| 612 | } |
| 613 | |
| 614 | fn top_level_bool_op_index(expr string, op string) ?int { |
| 615 | mut depth := 0 |
| 616 | mut i := 0 |
| 617 | for i < expr.len { |
| 618 | ch := expr[i] |
| 619 | if ch == `(` { |
| 620 | depth++ |
| 621 | } else if ch == `)` { |
| 622 | if depth > 0 { |
| 623 | depth-- |
| 624 | } |
| 625 | } else if depth == 0 && i + op.len <= expr.len && expr[i..i + op.len] == op { |
| 626 | return i |
| 627 | } |
| 628 | i++ |
| 629 | } |
| 630 | return none |
| 631 | } |
| 632 | |
| 633 | fn strip_outer_bool_parens(expr string) ?string { |
| 634 | if expr.len < 2 || expr[0] != `(` || expr[expr.len - 1] != `)` { |
| 635 | return none |
| 636 | } |
| 637 | mut depth := 0 |
| 638 | for i, ch in expr { |
| 639 | if ch == `(` { |
| 640 | depth++ |
| 641 | } else if ch == `)` { |
| 642 | depth-- |
| 643 | if depth == 0 && i < expr.len - 1 { |
| 644 | return none |
| 645 | } |
| 646 | } |
| 647 | } |
| 648 | if depth == 0 { |
| 649 | return expr[1..expr.len - 1].trim_space() |
| 650 | } |
| 651 | return none |
| 652 | } |
| 653 | |
| 654 | fn combine_ct_conditions(outer string, inner string) string { |
| 655 | if outer == '' { |
| 656 | return inner |
| 657 | } |
| 658 | if inner == '' { |
| 659 | return outer |
| 660 | } |
| 661 | return '(${outer}) && (${inner})' |
| 662 | } |
| 663 | |
| 664 | fn ast_ct_cond_string(expr ast.Expr) ?string { |
| 665 | return match expr { |
| 666 | ast.Ident { |
| 667 | expr.name |
| 668 | } |
| 669 | ast.PrefixExpr { |
| 670 | if expr.op == .not { |
| 671 | inner := ast_ct_cond_string(expr.expr) or { return none } |
| 672 | '!(${inner})' |
| 673 | } else { |
| 674 | none |
| 675 | } |
| 676 | } |
| 677 | ast.InfixExpr { |
| 678 | left := ast_ct_cond_string(expr.lhs) or { return none } |
| 679 | right := ast_ct_cond_string(expr.rhs) or { return none } |
| 680 | if expr.op == .and { |
| 681 | '(${left}) && (${right})' |
| 682 | } else if expr.op == .logical_or { |
| 683 | '(${left}) || (${right})' |
| 684 | } else { |
| 685 | none |
| 686 | } |
| 687 | } |
| 688 | ast.PostfixExpr { |
| 689 | if expr.op == .question && expr.expr is ast.Ident { |
| 690 | '${expr.expr.name}?' |
| 691 | } else { |
| 692 | none |
| 693 | } |
| 694 | } |
| 695 | ast.ParenExpr { |
| 696 | inner := ast_ct_cond_string(expr.expr) or { return none } |
| 697 | '(${inner})' |
| 698 | } |
| 699 | else { |
| 700 | none |
| 701 | } |
| 702 | } |
| 703 | } |
| 704 | |
| 705 | fn ast_ct_cond_string_cursor(expr ast.Cursor) ?string { |
| 706 | if !expr.is_valid() { |
| 707 | return none |
| 708 | } |
| 709 | return match expr.kind() { |
| 710 | .expr_ident { |
| 711 | expr.name() |
| 712 | } |
| 713 | .expr_comptime { |
| 714 | ast_ct_cond_string_cursor(expr.edge(0)) or { return none } |
| 715 | } |
| 716 | .expr_prefix { |
| 717 | if unsafe { token.Token(int(expr.aux())) } == .not { |
| 718 | inner := ast_ct_cond_string_cursor(expr.edge(0)) or { return none } |
| 719 | '!(${inner})' |
| 720 | } else { |
| 721 | none |
| 722 | } |
| 723 | } |
| 724 | .expr_infix { |
| 725 | left := ast_ct_cond_string_cursor(expr.edge(0)) or { return none } |
| 726 | right := ast_ct_cond_string_cursor(expr.edge(1)) or { return none } |
| 727 | op := unsafe { token.Token(int(expr.aux())) } |
| 728 | if op == .and { |
| 729 | '(${left}) && (${right})' |
| 730 | } else if op == .logical_or { |
| 731 | '(${left}) || (${right})' |
| 732 | } else { |
| 733 | none |
| 734 | } |
| 735 | } |
| 736 | .expr_postfix { |
| 737 | if unsafe { token.Token(int(expr.aux())) } == .question |
| 738 | && expr.edge(0).kind() == .expr_ident { |
| 739 | '${expr.edge(0).name()}?' |
| 740 | } else { |
| 741 | none |
| 742 | } |
| 743 | } |
| 744 | .expr_paren { |
| 745 | inner := ast_ct_cond_string_cursor(expr.edge(0)) or { return none } |
| 746 | '(${inner})' |
| 747 | } |
| 748 | else { |
| 749 | none |
| 750 | } |
| 751 | } |
| 752 | } |
| 753 | |
| 754 | fn (g &Gen) cross_directive_guard(cond string) ?string { |
| 755 | if g.target_os_name() != 'cross' { |
| 756 | return none |
| 757 | } |
| 758 | return g.c_preprocessor_expr_for_ct_cond(cond) |
| 759 | } |
| 760 | |
| 761 | fn find_vmod_root_for_file(file_path string) string { |
| 762 | mut dir := os.dir(file_path) |
| 763 | for _ in 0 .. 12 { |
| 764 | if os.exists(os.join_path(dir, 'v.mod')) { |
| 765 | return dir |
| 766 | } |
| 767 | parent := os.dir(dir) |
| 768 | if parent == dir || parent == '' { |
| 769 | break |
| 770 | } |
| 771 | dir = parent |
| 772 | } |
| 773 | return os.dir(file_path) |
| 774 | } |
| 775 | |
| 776 | fn normalize_c_directive_value(name string, raw string, file_name string, vroot string) string { |
| 777 | mut value := raw.trim_space() |
| 778 | if value.ends_with(';') { |
| 779 | value = value[..value.len - 1].trim_space() |
| 780 | } |
| 781 | if value.contains('@VMODROOT') { |
| 782 | value = value.replace('@VMODROOT', find_vmod_root_for_file(file_name)) |
| 783 | } |
| 784 | if value.contains('@VEXEROOT') { |
| 785 | value = value.replace('@VEXEROOT', vroot) |
| 786 | } |
| 787 | if name == 'include' { |
| 788 | if value == '' { |
| 789 | return value |
| 790 | } |
| 791 | mut had_trailing_angle := false |
| 792 | if value.ends_with('>') && !value.starts_with('<') && !value.starts_with('"') { |
| 793 | value = value[..value.len - 1].trim_space() |
| 794 | had_trailing_angle = true |
| 795 | } |
| 796 | if !value.starts_with('<') && !value.starts_with('"') { |
| 797 | if value.starts_with('/') || value.starts_with('./') || value.starts_with('../') { |
| 798 | value = '"${value}"' |
| 799 | } else if had_trailing_angle { |
| 800 | value = '<${value}>' |
| 801 | } else if value.contains('/') { |
| 802 | value = '<${value}>' |
| 803 | } else if value.ends_with('.h') || value.ends_with('.hh') || value.ends_with('.hpp') { |
| 804 | value = '"${value}"' |
| 805 | } else if value.ends_with('>') { |
| 806 | value = '<${value}' |
| 807 | } else { |
| 808 | value = '<${value}>' |
| 809 | } |
| 810 | } |
| 811 | } |
| 812 | return value |
| 813 | } |
| 814 | |
| 815 | fn (mut g Gen) emit_collected_c_directives() { |
| 816 | mut seen := map[string]bool{} |
| 817 | mut cross_source_paths := map[string]bool{} |
| 818 | if g.target_os_name() == 'cross' { |
| 819 | cross_source_paths = g.collect_cross_directives_from_original_sources(mut seen) |
| 820 | } |
| 821 | if g.has_flat() { |
| 822 | for i in 0 .. g.flat.files.len { |
| 823 | fc := g.flat.file_cursor(i) |
| 824 | file_name := fc.name() |
| 825 | if file_name in cross_source_paths { |
| 826 | continue |
| 827 | } |
| 828 | g.set_file_cursor_module(fc) |
| 829 | emit_implementation_directives := g.should_emit_current_file() |
| 830 | g.collect_directives_from_cursor_stmts(fc.stmts(), file_name, |
| 831 | emit_implementation_directives, mut seen) |
| 832 | } |
| 833 | return |
| 834 | } |
| 835 | for file in g.files { |
| 836 | if file.name in cross_source_paths { |
| 837 | continue |
| 838 | } |
| 839 | g.set_file_module(file) |
| 840 | emit_implementation_directives := g.should_emit_current_file() |
| 841 | g.collect_directives_from_stmts(file.stmts, file.name, emit_implementation_directives, mut |
| 842 | seen) |
| 843 | } |
| 844 | } |
| 845 | |
| 846 | fn (mut g Gen) collect_cross_directives_from_original_sources(mut seen map[string]bool) map[string]bool { |
| 847 | // Cross mode reparses only the files that survived normal source filtering. |
| 848 | // This recovers C directives from $if OS branches before transform strips |
| 849 | // them, but it does not provide a second OS-specific function-body pipeline. |
| 850 | mut parsed_paths := map[string]bool{} |
| 851 | mut file_set := token.FileSet.new() |
| 852 | mut par := parser.Parser.new(g.pref) |
| 853 | if g.has_flat() { |
| 854 | for i in 0 .. g.flat.files.len { |
| 855 | fc := g.flat.file_cursor(i) |
| 856 | source_path := fc.name() |
| 857 | if source_path == '' || source_path in parsed_paths || !os.exists(source_path) { |
| 858 | continue |
| 859 | } |
| 860 | parsed_paths[source_path] = true |
| 861 | source_files := par.parse_files([source_path], mut file_set) |
| 862 | for source_file in source_files { |
| 863 | g.set_file_module(source_file) |
| 864 | emit_implementation_directives := g.should_emit_current_file() |
| 865 | g.collect_directives_from_stmts(source_file.stmts, source_file.name, |
| 866 | emit_implementation_directives, mut seen) |
| 867 | } |
| 868 | } |
| 869 | return parsed_paths |
| 870 | } |
| 871 | for file in g.files { |
| 872 | source_path := file.name |
| 873 | if source_path == '' || source_path in parsed_paths || !os.exists(source_path) { |
| 874 | continue |
| 875 | } |
| 876 | parsed_paths[source_path] = true |
| 877 | source_files := par.parse_files([source_path], mut file_set) |
| 878 | for source_file in source_files { |
| 879 | g.set_file_module(source_file) |
| 880 | emit_implementation_directives := g.should_emit_current_file() |
| 881 | g.collect_directives_from_stmts(source_file.stmts, source_file.name, |
| 882 | emit_implementation_directives, mut seen) |
| 883 | } |
| 884 | } |
| 885 | return parsed_paths |
| 886 | } |
| 887 | |
| 888 | fn (mut g Gen) collect_directives_from_stmts(stmts []ast.Stmt, file_name string, emit_implementation_directives bool, mut seen map[string]bool) { |
| 889 | g.collect_directives_from_stmts_with_ct_cond(stmts, file_name, emit_implementation_directives, |
| 890 | '', mut seen) |
| 891 | } |
| 892 | |
| 893 | fn (mut g Gen) collect_directives_from_cursor_stmts(stmts ast.CursorList, file_name string, emit_implementation_directives bool, mut seen map[string]bool) { |
| 894 | g.collect_directives_from_cursor_stmts_with_ct_cond(stmts, file_name, |
| 895 | emit_implementation_directives, '', mut seen) |
| 896 | } |
| 897 | |
| 898 | fn (mut g Gen) collect_directives_from_cursor_stmts_with_ct_cond(stmts ast.CursorList, file_name string, emit_implementation_directives bool, outer_ct_cond string, mut seen map[string]bool) { |
| 899 | g.collect_directives_from_cursor_stmts_with_ct_cond_and_seen_guard(stmts, file_name, |
| 900 | emit_implementation_directives, outer_ct_cond, '', mut seen) |
| 901 | } |
| 902 | |
| 903 | fn (mut g Gen) collect_directives_from_cursor_stmts_with_ct_cond_and_seen_guard(stmts ast.CursorList, file_name string, emit_implementation_directives bool, outer_ct_cond string, active_seen_guard string, mut seen map[string]bool) { |
| 904 | if outer_ct_cond != '' && g.target_os_name() == 'cross' { |
| 905 | if guard := g.cross_directive_guard(outer_ct_cond) { |
| 906 | if guard == '0' { |
| 907 | return |
| 908 | } |
| 909 | if guard != '' { |
| 910 | g.sb.writeln('#if ${guard}') |
| 911 | g.collect_directives_from_cursor_stmts_with_ct_cond_and_seen_guard(stmts, |
| 912 | file_name, emit_implementation_directives, '', combine_ct_conditions(active_seen_guard, |
| 913 | guard), mut seen) |
| 914 | g.sb.writeln('#endif') |
| 915 | return |
| 916 | } |
| 917 | } |
| 918 | } |
| 919 | for i in 0 .. stmts.len() { |
| 920 | stmt := stmts.at(i) |
| 921 | match stmt.kind() { |
| 922 | .stmt_directive { |
| 923 | g.emit_directive_cursor_with_outer_ct_cond_and_seen_guard(stmt, file_name, |
| 924 | emit_implementation_directives, outer_ct_cond, active_seen_guard, mut seen) |
| 925 | } |
| 926 | .stmt_expr { |
| 927 | g.collect_directives_from_expr_cursor_with_ct_cond_and_seen_guard(stmt.edge(0), |
| 928 | file_name, emit_implementation_directives, outer_ct_cond, active_seen_guard, mut |
| 929 | seen) |
| 930 | } |
| 931 | else {} |
| 932 | } |
| 933 | } |
| 934 | } |
| 935 | |
| 936 | fn (mut g Gen) collect_directives_from_stmts_with_ct_cond(stmts []ast.Stmt, file_name string, emit_implementation_directives bool, outer_ct_cond string, mut seen map[string]bool) { |
| 937 | g.collect_directives_from_stmts_with_ct_cond_and_seen_guard(stmts, file_name, |
| 938 | emit_implementation_directives, outer_ct_cond, '', mut seen) |
| 939 | } |
| 940 | |
| 941 | fn (mut g Gen) collect_directives_from_stmts_with_ct_cond_and_seen_guard(stmts []ast.Stmt, file_name string, emit_implementation_directives bool, outer_ct_cond string, active_seen_guard string, mut seen map[string]bool) { |
| 942 | if outer_ct_cond != '' && g.target_os_name() == 'cross' { |
| 943 | if guard := g.cross_directive_guard(outer_ct_cond) { |
| 944 | if guard == '0' { |
| 945 | return |
| 946 | } |
| 947 | if guard != '' { |
| 948 | g.sb.writeln('#if ${guard}') |
| 949 | g.collect_directives_from_stmts_with_ct_cond_and_seen_guard(stmts, file_name, |
| 950 | emit_implementation_directives, '', combine_ct_conditions(active_seen_guard, |
| 951 | guard), mut seen) |
| 952 | g.sb.writeln('#endif') |
| 953 | return |
| 954 | } |
| 955 | } |
| 956 | } |
| 957 | for stmt in stmts { |
| 958 | if stmt is ast.Directive { |
| 959 | g.emit_directive_with_outer_ct_cond_and_seen_guard(stmt, file_name, |
| 960 | emit_implementation_directives, outer_ct_cond, active_seen_guard, mut seen) |
| 961 | } else if stmt is ast.ExprStmt { |
| 962 | g.collect_directives_from_expr_with_ct_cond_and_seen_guard(stmt.expr, file_name, |
| 963 | emit_implementation_directives, outer_ct_cond, active_seen_guard, mut seen) |
| 964 | } |
| 965 | } |
| 966 | } |
| 967 | |
| 968 | fn (mut g Gen) collect_directives_from_expr(expr ast.Expr, file_name string, emit_implementation_directives bool, mut seen map[string]bool) { |
| 969 | g.collect_directives_from_expr_with_ct_cond(expr, file_name, emit_implementation_directives, |
| 970 | '', mut seen) |
| 971 | } |
| 972 | |
| 973 | fn (mut g Gen) collect_directives_from_expr_with_ct_cond(expr ast.Expr, file_name string, emit_implementation_directives bool, outer_ct_cond string, mut seen map[string]bool) { |
| 974 | g.collect_directives_from_expr_with_ct_cond_and_seen_guard(expr, file_name, |
| 975 | emit_implementation_directives, outer_ct_cond, '', mut seen) |
| 976 | } |
| 977 | |
| 978 | fn (mut g Gen) collect_directives_from_expr_with_ct_cond_and_seen_guard(expr ast.Expr, file_name string, emit_implementation_directives bool, outer_ct_cond string, active_seen_guard string, mut seen map[string]bool) { |
| 979 | if expr is ast.ComptimeExpr { |
| 980 | if expr.expr is ast.IfExpr { |
| 981 | g.collect_directives_from_active_comptime_if(expr.expr, file_name, |
| 982 | emit_implementation_directives, outer_ct_cond, active_seen_guard, mut seen) |
| 983 | } else { |
| 984 | g.collect_directives_from_expr_with_ct_cond_and_seen_guard(expr.expr, file_name, |
| 985 | emit_implementation_directives, outer_ct_cond, active_seen_guard, mut seen) |
| 986 | } |
| 987 | } else if expr is ast.IfExpr { |
| 988 | g.collect_directives_from_stmts_with_ct_cond_and_seen_guard(expr.stmts, file_name, |
| 989 | emit_implementation_directives, outer_ct_cond, active_seen_guard, mut seen) |
| 990 | if expr.else_expr !is ast.EmptyExpr { |
| 991 | g.collect_directives_from_expr_with_ct_cond_and_seen_guard(expr.else_expr, file_name, |
| 992 | emit_implementation_directives, outer_ct_cond, active_seen_guard, mut seen) |
| 993 | } |
| 994 | } |
| 995 | } |
| 996 | |
| 997 | fn (mut g Gen) collect_directives_from_expr_cursor_with_ct_cond_and_seen_guard(expr ast.Cursor, file_name string, emit_implementation_directives bool, outer_ct_cond string, active_seen_guard string, mut seen map[string]bool) { |
| 998 | if !expr.is_valid() { |
| 999 | return |
| 1000 | } |
| 1001 | if expr.kind() == .expr_comptime { |
| 1002 | inner := expr.edge(0) |
| 1003 | if inner.kind() == .expr_if { |
| 1004 | g.collect_directives_from_active_comptime_if_cursor(inner, file_name, |
| 1005 | emit_implementation_directives, outer_ct_cond, active_seen_guard, mut seen) |
| 1006 | } else { |
| 1007 | g.collect_directives_from_expr_cursor_with_ct_cond_and_seen_guard(inner, file_name, |
| 1008 | emit_implementation_directives, outer_ct_cond, active_seen_guard, mut seen) |
| 1009 | } |
| 1010 | return |
| 1011 | } |
| 1012 | if expr.kind() == .expr_if { |
| 1013 | g.collect_directives_from_cursor_stmts_with_ct_cond_and_seen_guard(if_expr_body_cursor_list(expr), |
| 1014 | file_name, emit_implementation_directives, outer_ct_cond, active_seen_guard, mut seen) |
| 1015 | else_expr := expr.edge(1) |
| 1016 | if else_expr.is_valid() && else_expr.kind() != .expr_empty { |
| 1017 | g.collect_directives_from_expr_cursor_with_ct_cond_and_seen_guard(else_expr, file_name, |
| 1018 | emit_implementation_directives, outer_ct_cond, active_seen_guard, mut seen) |
| 1019 | } |
| 1020 | } |
| 1021 | } |
| 1022 | |
| 1023 | fn (mut g Gen) emit_directive_with_outer_ct_cond(stmt ast.Directive, file_name string, emit_implementation_directives bool, outer_ct_cond string, mut seen map[string]bool) { |
| 1024 | g.emit_directive_with_outer_ct_cond_and_seen_guard(stmt, file_name, |
| 1025 | emit_implementation_directives, outer_ct_cond, '', mut seen) |
| 1026 | } |
| 1027 | |
| 1028 | fn (mut g Gen) emit_directive_with_outer_ct_cond_and_seen_guard(stmt ast.Directive, file_name string, emit_implementation_directives bool, outer_ct_cond string, active_seen_guard string, mut seen map[string]bool) { |
| 1029 | if outer_ct_cond == '' { |
| 1030 | g.emit_directive_with_seen_guard(stmt, file_name, emit_implementation_directives, |
| 1031 | active_seen_guard, mut seen) |
| 1032 | return |
| 1033 | } |
| 1034 | g.emit_directive_fields_with_seen_guard(stmt.name, stmt.value, combine_ct_conditions(outer_ct_cond, |
| 1035 | stmt.ct_cond), file_name, emit_implementation_directives, active_seen_guard, mut seen) |
| 1036 | } |
| 1037 | |
| 1038 | fn (mut g Gen) emit_directive_cursor_with_outer_ct_cond_and_seen_guard(stmt ast.Cursor, file_name string, emit_implementation_directives bool, outer_ct_cond string, active_seen_guard string, mut seen map[string]bool) { |
| 1039 | if !stmt.is_valid() || stmt.kind() != .stmt_directive { |
| 1040 | return |
| 1041 | } |
| 1042 | ct_cond := if stmt.edge_count() > 0 { stmt.edge(0).name() } else { '' } |
| 1043 | resolved_ct_cond := if outer_ct_cond == '' { |
| 1044 | ct_cond |
| 1045 | } else { |
| 1046 | combine_ct_conditions(outer_ct_cond, ct_cond) |
| 1047 | } |
| 1048 | g.emit_directive_fields_with_seen_guard(stmt.name(), stmt.extra_str(), resolved_ct_cond, |
| 1049 | file_name, emit_implementation_directives, active_seen_guard, mut seen) |
| 1050 | } |
| 1051 | |
| 1052 | fn (mut g Gen) collect_directives_from_active_comptime_if(expr ast.IfExpr, file_name string, emit_implementation_directives bool, outer_ct_cond string, active_seen_guard string, mut seen map[string]bool) { |
| 1053 | if g.target_os_name() == 'cross' { |
| 1054 | if cond := ast_ct_cond_string(expr.cond) { |
| 1055 | if g.c_preprocessor_expr_for_ct_cond(cond) != none { |
| 1056 | g.collect_directives_from_stmts_with_ct_cond_and_seen_guard(expr.stmts, file_name, |
| 1057 | emit_implementation_directives, combine_ct_conditions(outer_ct_cond, cond), |
| 1058 | active_seen_guard, mut seen) |
| 1059 | next_outer_cond := combine_ct_conditions(outer_ct_cond, '!(${cond})') |
| 1060 | if expr.else_expr is ast.IfExpr { |
| 1061 | if expr.else_expr.cond is ast.EmptyExpr { |
| 1062 | g.collect_directives_from_stmts_with_ct_cond_and_seen_guard(expr.else_expr.stmts, |
| 1063 | file_name, emit_implementation_directives, next_outer_cond, |
| 1064 | active_seen_guard, mut seen) |
| 1065 | } else { |
| 1066 | g.collect_directives_from_active_comptime_if(expr.else_expr, file_name, |
| 1067 | emit_implementation_directives, next_outer_cond, active_seen_guard, mut |
| 1068 | seen) |
| 1069 | } |
| 1070 | } else if expr.else_expr !is ast.EmptyExpr { |
| 1071 | g.collect_directives_from_expr_with_ct_cond_and_seen_guard(expr.else_expr, |
| 1072 | file_name, emit_implementation_directives, next_outer_cond, |
| 1073 | active_seen_guard, mut seen) |
| 1074 | } |
| 1075 | return |
| 1076 | } |
| 1077 | } |
| 1078 | } |
| 1079 | if g.ast_comptime_simple_cond_matches(expr.cond) { |
| 1080 | g.collect_directives_from_stmts_with_ct_cond_and_seen_guard(expr.stmts, file_name, |
| 1081 | emit_implementation_directives, outer_ct_cond, active_seen_guard, mut seen) |
| 1082 | return |
| 1083 | } |
| 1084 | if expr.else_expr is ast.IfExpr { |
| 1085 | if expr.else_expr.cond is ast.EmptyExpr { |
| 1086 | g.collect_directives_from_stmts_with_ct_cond_and_seen_guard(expr.else_expr.stmts, |
| 1087 | file_name, emit_implementation_directives, outer_ct_cond, active_seen_guard, mut |
| 1088 | seen) |
| 1089 | } else { |
| 1090 | g.collect_directives_from_active_comptime_if(expr.else_expr, file_name, |
| 1091 | emit_implementation_directives, outer_ct_cond, active_seen_guard, mut seen) |
| 1092 | } |
| 1093 | } else if expr.else_expr !is ast.EmptyExpr { |
| 1094 | g.collect_directives_from_expr_with_ct_cond_and_seen_guard(expr.else_expr, file_name, |
| 1095 | emit_implementation_directives, outer_ct_cond, active_seen_guard, mut seen) |
| 1096 | } |
| 1097 | } |
| 1098 | |
| 1099 | fn (mut g Gen) collect_directives_from_active_comptime_if_cursor(expr ast.Cursor, file_name string, emit_implementation_directives bool, outer_ct_cond string, active_seen_guard string, mut seen map[string]bool) { |
| 1100 | if !expr.is_valid() || expr.kind() != .expr_if { |
| 1101 | return |
| 1102 | } |
| 1103 | cond_expr := expr.edge(0) |
| 1104 | if g.target_os_name() == 'cross' { |
| 1105 | if cond := ast_ct_cond_string_cursor(cond_expr) { |
| 1106 | if g.c_preprocessor_expr_for_ct_cond(cond) != none { |
| 1107 | g.collect_directives_from_cursor_stmts_with_ct_cond_and_seen_guard(if_expr_body_cursor_list(expr), |
| 1108 | file_name, emit_implementation_directives, combine_ct_conditions(outer_ct_cond, |
| 1109 | cond), active_seen_guard, mut seen) |
| 1110 | next_outer_cond := combine_ct_conditions(outer_ct_cond, '!(${cond})') |
| 1111 | else_expr := expr.edge(1) |
| 1112 | if else_expr.kind() == .expr_if { |
| 1113 | if else_expr.edge(0).kind() == .expr_empty { |
| 1114 | g.collect_directives_from_cursor_stmts_with_ct_cond_and_seen_guard(if_expr_body_cursor_list(else_expr), |
| 1115 | file_name, emit_implementation_directives, next_outer_cond, |
| 1116 | active_seen_guard, mut seen) |
| 1117 | } else { |
| 1118 | g.collect_directives_from_active_comptime_if_cursor(else_expr, file_name, |
| 1119 | emit_implementation_directives, next_outer_cond, active_seen_guard, mut |
| 1120 | seen) |
| 1121 | } |
| 1122 | } else if else_expr.is_valid() && else_expr.kind() != .expr_empty { |
| 1123 | g.collect_directives_from_expr_cursor_with_ct_cond_and_seen_guard(else_expr, |
| 1124 | file_name, emit_implementation_directives, next_outer_cond, |
| 1125 | active_seen_guard, mut seen) |
| 1126 | } |
| 1127 | return |
| 1128 | } |
| 1129 | } |
| 1130 | } |
| 1131 | if g.ast_comptime_simple_cond_matches_cursor(cond_expr) { |
| 1132 | g.collect_directives_from_cursor_stmts_with_ct_cond_and_seen_guard(if_expr_body_cursor_list(expr), |
| 1133 | file_name, emit_implementation_directives, outer_ct_cond, active_seen_guard, mut seen) |
| 1134 | return |
| 1135 | } |
| 1136 | else_expr := expr.edge(1) |
| 1137 | if else_expr.kind() == .expr_if { |
| 1138 | if else_expr.edge(0).kind() == .expr_empty { |
| 1139 | g.collect_directives_from_cursor_stmts_with_ct_cond_and_seen_guard(if_expr_body_cursor_list(else_expr), |
| 1140 | file_name, emit_implementation_directives, outer_ct_cond, active_seen_guard, mut |
| 1141 | seen) |
| 1142 | } else { |
| 1143 | g.collect_directives_from_active_comptime_if_cursor(else_expr, file_name, |
| 1144 | emit_implementation_directives, outer_ct_cond, active_seen_guard, mut seen) |
| 1145 | } |
| 1146 | } else if else_expr.is_valid() && else_expr.kind() != .expr_empty { |
| 1147 | g.collect_directives_from_expr_cursor_with_ct_cond_and_seen_guard(else_expr, file_name, |
| 1148 | emit_implementation_directives, outer_ct_cond, active_seen_guard, mut seen) |
| 1149 | } |
| 1150 | } |
| 1151 | |
| 1152 | fn if_expr_body_cursor_list(expr ast.Cursor) ast.CursorList { |
| 1153 | return ast.CursorList{ |
| 1154 | flat: expr.flat |
| 1155 | parent_id: expr.id |
| 1156 | offset: 2 |
| 1157 | } |
| 1158 | } |
| 1159 | |
| 1160 | fn (g &Gen) ast_comptime_simple_cond_matches(expr ast.Expr) bool { |
| 1161 | if expr is ast.Ident { |
| 1162 | return g.ast_comptime_flag_matches(expr.name) |
| 1163 | } |
| 1164 | if expr is ast.ComptimeExpr { |
| 1165 | return g.ast_comptime_simple_cond_matches(expr.expr) |
| 1166 | } |
| 1167 | if expr is ast.CallExpr { |
| 1168 | if pkg_name := cleanc_pkgconfig_call_name(expr) { |
| 1169 | return vpref.comptime_pkgconfig_value(pkg_name) |
| 1170 | } |
| 1171 | } |
| 1172 | if expr is ast.CallOrCastExpr { |
| 1173 | if pkg_name := cleanc_pkgconfig_call_name(expr) { |
| 1174 | return vpref.comptime_pkgconfig_value(pkg_name) |
| 1175 | } |
| 1176 | } |
| 1177 | if expr is ast.PrefixExpr { |
| 1178 | if expr.op == .not { |
| 1179 | return !g.ast_comptime_simple_cond_matches(expr.expr) |
| 1180 | } |
| 1181 | return false |
| 1182 | } |
| 1183 | if expr is ast.InfixExpr { |
| 1184 | if expr.op == .and { |
| 1185 | return g.ast_comptime_simple_cond_matches(expr.lhs) |
| 1186 | && g.ast_comptime_simple_cond_matches(expr.rhs) |
| 1187 | } |
| 1188 | if expr.op == .logical_or { |
| 1189 | return g.ast_comptime_simple_cond_matches(expr.lhs) |
| 1190 | || g.ast_comptime_simple_cond_matches(expr.rhs) |
| 1191 | } |
| 1192 | return false |
| 1193 | } |
| 1194 | if expr is ast.PostfixExpr { |
| 1195 | if expr.op == .question && expr.expr is ast.Ident { |
| 1196 | return g.has_target_define(expr.expr.name) |
| 1197 | } |
| 1198 | return false |
| 1199 | } |
| 1200 | if expr is ast.ParenExpr { |
| 1201 | return g.ast_comptime_simple_cond_matches(expr.expr) |
| 1202 | } |
| 1203 | return false |
| 1204 | } |
| 1205 | |
| 1206 | fn (g &Gen) ast_comptime_simple_cond_matches_cursor(expr ast.Cursor) bool { |
| 1207 | if !expr.is_valid() { |
| 1208 | return false |
| 1209 | } |
| 1210 | match expr.kind() { |
| 1211 | .expr_ident { |
| 1212 | return g.ast_comptime_flag_matches(expr.name()) |
| 1213 | } |
| 1214 | .expr_comptime, .expr_paren { |
| 1215 | return g.ast_comptime_simple_cond_matches_cursor(expr.edge(0)) |
| 1216 | } |
| 1217 | .expr_call, .expr_call_or_cast { |
| 1218 | if pkg_name := cleanc_pkgconfig_call_name_cursor(expr) { |
| 1219 | return vpref.comptime_pkgconfig_value(pkg_name) |
| 1220 | } |
| 1221 | } |
| 1222 | .expr_prefix { |
| 1223 | if unsafe { token.Token(int(expr.aux())) } == .not { |
| 1224 | return !g.ast_comptime_simple_cond_matches_cursor(expr.edge(0)) |
| 1225 | } |
| 1226 | } |
| 1227 | .expr_infix { |
| 1228 | op := unsafe { token.Token(int(expr.aux())) } |
| 1229 | if op == .and { |
| 1230 | return g.ast_comptime_simple_cond_matches_cursor(expr.edge(0)) |
| 1231 | && g.ast_comptime_simple_cond_matches_cursor(expr.edge(1)) |
| 1232 | } |
| 1233 | if op == .logical_or { |
| 1234 | return g.ast_comptime_simple_cond_matches_cursor(expr.edge(0)) |
| 1235 | || g.ast_comptime_simple_cond_matches_cursor(expr.edge(1)) |
| 1236 | } |
| 1237 | } |
| 1238 | .expr_postfix { |
| 1239 | if unsafe { token.Token(int(expr.aux())) } == .question |
| 1240 | && expr.edge(0).kind() == .expr_ident { |
| 1241 | return g.has_target_define(expr.edge(0).name()) |
| 1242 | } |
| 1243 | } |
| 1244 | else {} |
| 1245 | } |
| 1246 | |
| 1247 | return false |
| 1248 | } |
| 1249 | |
| 1250 | fn (g &Gen) ast_comptime_flag_matches(name string) bool { |
| 1251 | lower_name := name.to_lower() |
| 1252 | if g.pref == unsafe { nil } { |
| 1253 | return false |
| 1254 | } |
| 1255 | match lower_name { |
| 1256 | 'macos', 'darwin', 'mac', 'linux', 'windows', 'bsd', 'freebsd', 'openbsd', 'netbsd', |
| 1257 | 'dragonfly', 'android', 'termux', 'ios', 'solaris', 'qnx', 'serenity', 'plan9', 'vinix', |
| 1258 | 'none' { |
| 1259 | return vpref.comptime_flag_value(g.pref, lower_name) |
| 1260 | } |
| 1261 | 'cross', 'freestanding', 'bare' { |
| 1262 | return vpref.comptime_flag_value(g.pref, lower_name) |
| 1263 | || vpref.define_list_contains(g.pref.user_defines, lower_name) |
| 1264 | } |
| 1265 | else {} |
| 1266 | } |
| 1267 | |
| 1268 | return vpref.define_list_contains(g.pref.user_defines, lower_name) |
| 1269 | } |
| 1270 | |
| 1271 | fn cleanc_pkgconfig_call_name(expr ast.Expr) ?string { |
| 1272 | match expr { |
| 1273 | ast.CallExpr { |
| 1274 | if expr.lhs is ast.Ident && expr.lhs.name == 'pkgconfig' && expr.args.len == 1 { |
| 1275 | return cleanc_string_literal_value(expr.args[0]) |
| 1276 | } |
| 1277 | } |
| 1278 | ast.CallOrCastExpr { |
| 1279 | if expr.lhs is ast.Ident && expr.lhs.name == 'pkgconfig' { |
| 1280 | return cleanc_string_literal_value(expr.expr) |
| 1281 | } |
| 1282 | } |
| 1283 | else {} |
| 1284 | } |
| 1285 | |
| 1286 | return none |
| 1287 | } |
| 1288 | |
| 1289 | fn cleanc_pkgconfig_call_name_cursor(expr ast.Cursor) ?string { |
| 1290 | if !expr.is_valid() { |
| 1291 | return none |
| 1292 | } |
| 1293 | if expr.kind() == .expr_call { |
| 1294 | lhs := expr.edge(0) |
| 1295 | if lhs.kind() == .expr_ident && lhs.name() == 'pkgconfig' && expr.edge_count() == 2 { |
| 1296 | return cleanc_string_literal_value_cursor(expr.edge(1)) |
| 1297 | } |
| 1298 | } else if expr.kind() == .expr_call_or_cast { |
| 1299 | lhs := expr.edge(0) |
| 1300 | if lhs.kind() == .expr_ident && lhs.name() == 'pkgconfig' { |
| 1301 | return cleanc_string_literal_value_cursor(expr.edge(1)) |
| 1302 | } |
| 1303 | } |
| 1304 | return none |
| 1305 | } |
| 1306 | |
| 1307 | fn cleanc_string_literal_value(expr ast.Expr) ?string { |
| 1308 | if expr is ast.StringLiteral { |
| 1309 | return cleanc_unquote_string_literal_value(expr.value) |
| 1310 | } |
| 1311 | return none |
| 1312 | } |
| 1313 | |
| 1314 | fn cleanc_string_literal_value_cursor(expr ast.Cursor) ?string { |
| 1315 | if !expr.is_valid() || (expr.kind() != .expr_string && expr.kind() != .expr_basic_literal) { |
| 1316 | return none |
| 1317 | } |
| 1318 | return cleanc_unquote_string_literal_value(expr.name()) |
| 1319 | } |
| 1320 | |
| 1321 | fn cleanc_unquote_string_literal_value(value string) string { |
| 1322 | if value.len >= 2 && ((value[0] == `"` && value[value.len - 1] == `"`) |
| 1323 | || (value[0] == `'` && value[value.len - 1] == `'`)) { |
| 1324 | return value[1..value.len - 1] |
| 1325 | } |
| 1326 | return value |
| 1327 | } |
| 1328 | |
| 1329 | fn c_directive_emits_linked_symbols(value string) bool { |
| 1330 | lower_value := value.trim_space().to_lower() |
| 1331 | for marker in ['.c"', ".c'", '.c>', '.m"', ".m'", '.m>'] { |
| 1332 | if lower_value.contains(marker) { |
| 1333 | return true |
| 1334 | } |
| 1335 | } |
| 1336 | return lower_value.contains('"miniz.h"') || lower_value.contains('<miniz.h>') |
| 1337 | || lower_value.contains('/miniz.h"') || lower_value.contains('/miniz.h>') |
| 1338 | } |
| 1339 | |
| 1340 | fn c_define_enables_linked_symbols(value string) bool { |
| 1341 | upper_value := value.trim_space().to_upper() |
| 1342 | return upper_value.contains('IMPLEMENTATION') |
| 1343 | } |
| 1344 | |
| 1345 | fn (mut g Gen) emit_directive(stmt ast.Directive, file_name string, emit_implementation_directives bool, mut seen map[string]bool) { |
| 1346 | g.emit_directive_with_seen_guard(stmt, file_name, emit_implementation_directives, '', mut seen) |
| 1347 | } |
| 1348 | |
| 1349 | fn (mut g Gen) emit_directive_with_seen_guard(stmt ast.Directive, file_name string, emit_implementation_directives bool, active_seen_guard string, mut seen map[string]bool) { |
| 1350 | g.emit_directive_fields_with_seen_guard(stmt.name, stmt.value, stmt.ct_cond, file_name, |
| 1351 | emit_implementation_directives, active_seen_guard, mut seen) |
| 1352 | } |
| 1353 | |
| 1354 | fn (mut g Gen) emit_directive_fields_with_seen_guard(raw_name string, raw_value string, ct_cond string, file_name string, emit_implementation_directives bool, active_seen_guard string, mut seen map[string]bool) { |
| 1355 | name := raw_name.trim_space() |
| 1356 | if name == '' || name == 'flag' { |
| 1357 | return |
| 1358 | } |
| 1359 | if !g.directive_ct_cond_matches(ct_cond) { |
| 1360 | return |
| 1361 | } |
| 1362 | if name !in ['include', 'define', 'undef', 'ifdef', 'ifndef', 'if', 'elif', 'else', 'endif', |
| 1363 | 'pragma', 'insert'] { |
| 1364 | return |
| 1365 | } |
| 1366 | // #insert is V's directive to inline a file; for C backend, emit as #include. |
| 1367 | emit_name := if name == 'insert' { 'include' } else { name } |
| 1368 | // Skip includes with corrupt paths (ARM64 backend may corrupt string data) |
| 1369 | if emit_name == 'include' { |
| 1370 | v := raw_value.trim_space() |
| 1371 | if v.len > 0 && !v.contains('/') && !v.contains('.') && !v.starts_with('"') |
| 1372 | && !v.starts_with('<') { |
| 1373 | return |
| 1374 | } |
| 1375 | } |
| 1376 | vroot := if g.pref != unsafe { nil } { g.pref.vroot } else { '' } |
| 1377 | value := normalize_c_directive_value(emit_name, raw_value, file_name, vroot) |
| 1378 | if !emit_implementation_directives && emit_name == 'include' |
| 1379 | && c_directive_emits_linked_symbols(value) { |
| 1380 | return |
| 1381 | } |
| 1382 | if !emit_implementation_directives && emit_name == 'define' |
| 1383 | && c_define_enables_linked_symbols(value) { |
| 1384 | return |
| 1385 | } |
| 1386 | line := if value == '' { '#${emit_name}' } else { '#${emit_name} ${value}' } |
| 1387 | guard := g.cross_directive_guard(ct_cond) or { '' } |
| 1388 | if guard == '0' { |
| 1389 | return |
| 1390 | } |
| 1391 | mut seen_key := if guard == '' { line } else { '#if ${guard}\n${line}' } |
| 1392 | if active_seen_guard != '' { |
| 1393 | seen_key = '#if ${active_seen_guard}\n${seen_key}' |
| 1394 | } |
| 1395 | deduplicate := emit_name !in ['if', 'ifdef', 'ifndef', 'elif', 'else', 'endif'] |
| 1396 | if deduplicate { |
| 1397 | if seen_key in seen { |
| 1398 | return |
| 1399 | } |
| 1400 | seen[seen_key] = true |
| 1401 | } |
| 1402 | if emit_name == 'include' && value.contains('/thirdparty/stdatomic/nix/atomic.h') { |
| 1403 | if guard != '' { |
| 1404 | g.sb.writeln('#if ${guard}') |
| 1405 | } |
| 1406 | g.sb.writeln('#if defined(__TINYC__) && defined(__APPLE__) && defined(__aarch64__)') |
| 1407 | g.sb.writeln('#define extern static') |
| 1408 | g.sb.writeln('#endif') |
| 1409 | g.sb.writeln(line) |
| 1410 | g.sb.writeln('#if defined(__TINYC__) && defined(__APPLE__) && defined(__aarch64__)') |
| 1411 | g.sb.writeln('#undef extern') |
| 1412 | g.sb.writeln('#endif') |
| 1413 | if guard != '' { |
| 1414 | g.sb.writeln('#endif') |
| 1415 | } |
| 1416 | return |
| 1417 | } |
| 1418 | // Defer .m (Objective-C) includes until after type definitions, |
| 1419 | // because they reference V-generated C types like gg__Color, string, etc. |
| 1420 | if emit_name == 'include' && (value.contains('.m"') || value.contains(".m'")) { |
| 1421 | deferred_guard := combine_ct_conditions(active_seen_guard, guard) |
| 1422 | g.deferred_m_includes << if deferred_guard == '' { |
| 1423 | line |
| 1424 | } else { |
| 1425 | '#if ${deferred_guard}\n${line}\n#endif' |
| 1426 | } |
| 1427 | return |
| 1428 | } |
| 1429 | if guard != '' { |
| 1430 | g.sb.writeln('#if ${guard}') |
| 1431 | g.sb.writeln(line) |
| 1432 | g.sb.writeln('#endif') |
| 1433 | return |
| 1434 | } |
| 1435 | g.sb.writeln(line) |
| 1436 | } |
| 1437 | |
| 1438 | fn (mut g Gen) emit_deferred_m_includes() { |
| 1439 | // Skip .m includes when compiling as shared library (avoids duplicate ObjC classes) |
| 1440 | if g.pref != unsafe { nil } && g.pref.is_shared_lib { |
| 1441 | return |
| 1442 | } |
| 1443 | for line in g.deferred_m_includes { |
| 1444 | g.sb.writeln(line) |
| 1445 | } |
| 1446 | } |
| 1447 | |
| 1448 | fn (mut g Gen) emit_vec_simd_fallback_aliases() { |
| 1449 | g.sb.writeln('#if defined(__clang__)') |
| 1450 | g.sb.writeln('typedef float vec__SimdFloat2 __attribute__((ext_vector_type(2)));') |
| 1451 | g.sb.writeln('typedef float vec__SimdFloat4 __attribute__((ext_vector_type(4)));') |
| 1452 | g.sb.writeln('typedef int vec__SimdInt4 __attribute__((ext_vector_type(4)));') |
| 1453 | g.sb.writeln('typedef unsigned int vec__SimdU32_4 __attribute__((ext_vector_type(4)));') |
| 1454 | g.sb.writeln('#elif defined(__GNUC__)') |
| 1455 | g.sb.writeln('typedef float vec__SimdFloat2 __attribute__((vector_size(8)));') |
| 1456 | g.sb.writeln('typedef float vec__SimdFloat4 __attribute__((vector_size(16)));') |
| 1457 | g.sb.writeln('typedef int vec__SimdInt4 __attribute__((vector_size(16)));') |
| 1458 | g.sb.writeln('typedef unsigned int vec__SimdU32_4 __attribute__((vector_size(16)));') |
| 1459 | g.sb.writeln('#else') |
| 1460 | g.sb.writeln('typedef struct vec__SimdFloat2 { f32 x; f32 y; } vec__SimdFloat2;') |
| 1461 | g.sb.writeln('typedef struct vec__SimdFloat4 { f32 x; f32 y; f32 z; f32 w; } vec__SimdFloat4;') |
| 1462 | g.sb.writeln('typedef struct vec__SimdInt4 { i32 x; i32 y; i32 z; i32 w; } vec__SimdInt4;') |
| 1463 | g.sb.writeln('typedef struct vec__SimdU32_4 { u32 x; u32 y; u32 z; u32 w; } vec__SimdU32_4;') |
| 1464 | g.sb.writeln('#endif') |
| 1465 | } |
| 1466 | |
| 1467 | fn (mut g Gen) emit_tinyc_arm_cpu_relax_fallback() { |
| 1468 | g.sb.writeln('#if defined(__TINYC__) && (defined(__aarch64__) || defined(_M_ARM64) || defined(__arm__) || defined(_M_ARM))') |
| 1469 | g.sb.writeln('#ifdef cpu_relax') |
| 1470 | g.sb.writeln('#undef cpu_relax') |
| 1471 | g.sb.writeln('#endif') |
| 1472 | g.sb.writeln('#define cpu_relax() ((void)0)') |
| 1473 | g.sb.writeln('#endif') |
| 1474 | } |
| 1475 | |
| 1476 | fn (mut g Gen) emit_stdatomic_compat_include() { |
| 1477 | if g.pref == unsafe { nil } || g.pref.vroot.len == 0 { |
| 1478 | return |
| 1479 | } |
| 1480 | dir_name := if g.target_os_name() == 'windows' { 'win' } else { 'nix' } |
| 1481 | header_path := os.join_path(g.pref.vroot, 'thirdparty', 'stdatomic', dir_name, 'atomic.h') |
| 1482 | if !os.exists(header_path) { |
| 1483 | return |
| 1484 | } |
| 1485 | include_path := header_path.replace('\\', '/') |
| 1486 | // The `extern -> static` hack only matters for tcc on Apple arm64; keep |
| 1487 | // the __APPLE__ guard out of non-macos target preambles (they are |
| 1488 | // asserted apple-free by target_codegen_test). |
| 1489 | apple_target := g.target_os_name() == 'macos' |
| 1490 | if apple_target { |
| 1491 | g.sb.writeln('#if defined(__TINYC__) && defined(__APPLE__) && defined(__aarch64__)') |
| 1492 | g.sb.writeln('#define extern static') |
| 1493 | g.sb.writeln('#endif') |
| 1494 | } |
| 1495 | g.sb.writeln('#include "${include_path}"') |
| 1496 | if apple_target { |
| 1497 | g.sb.writeln('#if defined(__TINYC__) && defined(__APPLE__) && defined(__aarch64__)') |
| 1498 | g.sb.writeln('#undef extern') |
| 1499 | g.sb.writeln('#endif') |
| 1500 | } |
| 1501 | } |
| 1502 | |
| 1503 | fn (mut g Gen) emit_v_architecture_macros() { |
| 1504 | g.sb.writeln('#if defined(__x86_64__) || defined(_M_AMD64)') |
| 1505 | g.sb.writeln('#define __V_amd64 1') |
| 1506 | g.sb.writeln('#define __V_architecture 1') |
| 1507 | g.sb.writeln('#elif defined(__aarch64__) || defined(__arm64__) || defined(_M_ARM64)') |
| 1508 | g.sb.writeln('#define __V_arm64 1') |
| 1509 | g.sb.writeln('#define __V_architecture 2') |
| 1510 | g.sb.writeln('#elif defined(__arm__) || defined(_M_ARM)') |
| 1511 | g.sb.writeln('#define __V_arm32 1') |
| 1512 | g.sb.writeln('#define __V_architecture 3') |
| 1513 | g.sb.writeln('#elif defined(__riscv) && __riscv_xlen == 64') |
| 1514 | g.sb.writeln('#define __V_rv64 1') |
| 1515 | g.sb.writeln('#define __V_architecture 4') |
| 1516 | g.sb.writeln('#elif defined(__riscv) && __riscv_xlen == 32') |
| 1517 | g.sb.writeln('#define __V_rv32 1') |
| 1518 | g.sb.writeln('#define __V_architecture 5') |
| 1519 | g.sb.writeln('#elif defined(__i386__) || defined(_M_IX86)') |
| 1520 | g.sb.writeln('#define __V_x86 1') |
| 1521 | g.sb.writeln('#define __V_architecture 6') |
| 1522 | g.sb.writeln('#elif defined(__s390x__)') |
| 1523 | g.sb.writeln('#define __V_s390x 1') |
| 1524 | g.sb.writeln('#define __V_architecture 7') |
| 1525 | g.sb.writeln('#elif defined(__powerpc64__) && defined(__LITTLE_ENDIAN__)') |
| 1526 | g.sb.writeln('#define __V_ppc64le 1') |
| 1527 | g.sb.writeln('#define __V_architecture 8') |
| 1528 | g.sb.writeln('#elif defined(__loongarch64)') |
| 1529 | g.sb.writeln('#define __V_loongarch64 1') |
| 1530 | g.sb.writeln('#define __V_architecture 9') |
| 1531 | g.sb.writeln('#elif defined(__sparc__)') |
| 1532 | g.sb.writeln('#define __V_sparc64 1') |
| 1533 | g.sb.writeln('#define __V_architecture 10') |
| 1534 | g.sb.writeln('#elif defined(__powerpc64__) && defined(__BIG_ENDIAN__)') |
| 1535 | g.sb.writeln('#define __V_ppc64 1') |
| 1536 | g.sb.writeln('#define __V_architecture 11') |
| 1537 | g.sb.writeln('#elif (defined(__powerpc__) || defined(__powerpc) || defined(__POWERPC__) || defined(__ppc__) || defined(__ppc) || defined(__PPC__)) && !defined(__powerpc64__) && !defined(__ppc64__) && !defined(__PPC64__)') |
| 1538 | g.sb.writeln('#define __V_ppc 1') |
| 1539 | g.sb.writeln('#define __V_architecture 12') |
| 1540 | g.sb.writeln('#else') |
| 1541 | g.sb.writeln('#define __V_architecture 0') |
| 1542 | g.sb.writeln('#endif') |
| 1543 | } |
| 1544 | |
| 1545 | fn (mut g Gen) emit_v_commit_hash_fallback() { |
| 1546 | g.sb.writeln('#ifndef V_COMMIT_HASH') |
| 1547 | g.sb.writeln('#define V_COMMIT_HASH "@@@"') |
| 1548 | g.sb.writeln('#endif') |
| 1549 | } |
| 1550 | |
| 1551 | fn (mut g Gen) emit_stub_rwmutex() { |
| 1552 | g.sb.writeln('typedef struct sync__RwMutex { u32 inited; } sync__RwMutex;') |
| 1553 | g.sb.writeln('static inline void sync__RwMutex_rlock(sync__RwMutex* m) { (void)m; }') |
| 1554 | g.sb.writeln('static inline void sync__RwMutex_runlock(sync__RwMutex* m) { (void)m; }') |
| 1555 | g.sb.writeln('static inline void sync__RwMutex_lock(sync__RwMutex* m) { (void)m; }') |
| 1556 | g.sb.writeln('static inline void sync__RwMutex_unlock(sync__RwMutex* m) { (void)m; }') |
| 1557 | g.emitted_types['body_sync__RwMutex'] = true |
| 1558 | } |
| 1559 | |
| 1560 | fn (mut g Gen) emit_pthread_rwmutex() { |
| 1561 | g.sb.writeln('typedef struct sync__RwMutex { pthread_rwlock_t mutex; u32 inited; } sync__RwMutex;') |
| 1562 | g.sb.writeln('static inline void sync__RwMutex_rlock(sync__RwMutex* m) { pthread_rwlock_rdlock(&m->mutex); }') |
| 1563 | g.sb.writeln('static inline void sync__RwMutex_runlock(sync__RwMutex* m) { pthread_rwlock_unlock(&m->mutex); }') |
| 1564 | g.sb.writeln('static inline void sync__RwMutex_lock(sync__RwMutex* m) { pthread_rwlock_wrlock(&m->mutex); }') |
| 1565 | g.sb.writeln('static inline void sync__RwMutex_unlock(sync__RwMutex* m) { pthread_rwlock_unlock(&m->mutex); }') |
| 1566 | g.emitted_types['body_sync__RwMutex'] = true |
| 1567 | } |
| 1568 | |
| 1569 | fn (mut g Gen) emit_windows_rwmutex() { |
| 1570 | g.sb.writeln('typedef struct sync__RwMutex { SRWLOCK mutex; u32 inited; } sync__RwMutex;') |
| 1571 | g.sb.writeln('static inline void sync__RwMutex_rlock(sync__RwMutex* m) { AcquireSRWLockShared(&m->mutex); }') |
| 1572 | g.sb.writeln('static inline void sync__RwMutex_runlock(sync__RwMutex* m) { ReleaseSRWLockShared(&m->mutex); }') |
| 1573 | g.sb.writeln('static inline void sync__RwMutex_lock(sync__RwMutex* m) { AcquireSRWLockExclusive(&m->mutex); }') |
| 1574 | g.sb.writeln('static inline void sync__RwMutex_unlock(sync__RwMutex* m) { ReleaseSRWLockExclusive(&m->mutex); }') |
| 1575 | g.emitted_types['body_sync__RwMutex'] = true |
| 1576 | } |
| 1577 | |
| 1578 | fn (mut g Gen) emit_cross_rwmutex() { |
| 1579 | g.sb.writeln('#if defined(_WIN32)') |
| 1580 | g.emit_windows_rwmutex() |
| 1581 | g.sb.writeln('#else') |
| 1582 | g.emit_pthread_rwmutex() |
| 1583 | g.sb.writeln('#endif') |
| 1584 | g.emitted_types['body_sync__RwMutex'] = true |
| 1585 | } |
| 1586 | |
| 1587 | fn (mut g Gen) emit_target_rwmutex() { |
| 1588 | target_os := g.target_os_name() |
| 1589 | if g.is_freestanding_target() { |
| 1590 | g.emit_stub_rwmutex() |
| 1591 | return |
| 1592 | } |
| 1593 | if target_os == 'windows' { |
| 1594 | g.emit_windows_rwmutex() |
| 1595 | return |
| 1596 | } |
| 1597 | if target_os == 'cross' { |
| 1598 | g.emit_cross_rwmutex() |
| 1599 | return |
| 1600 | } |
| 1601 | g.emit_pthread_rwmutex() |
| 1602 | } |
| 1603 | |
| 1604 | fn (mut g Gen) write_preamble() { |
| 1605 | minimal_preamble := g.use_minimal_preamble() |
| 1606 | g.sb.write_string(g.preamble_includes(minimal_preamble)) |
| 1607 | g.emit_linux_gettid_compat(minimal_preamble) |
| 1608 | g.emit_stdatomic_compat_include() |
| 1609 | g.emit_v_architecture_macros() |
| 1610 | g.emit_v_commit_hash_fallback() |
| 1611 | g.emit_collected_c_directives() |
| 1612 | g.emit_tinyc_arm_cpu_relax_fallback() |
| 1613 | if g.pref != unsafe { nil } && g.pref.prealloc && !g.is_freestanding_target() { |
| 1614 | g.sb.writeln('#define _VPREALLOC (1)') |
| 1615 | // Save the real free() before redefining it as a no-op. |
| 1616 | // prealloc_vcleanup needs the real free to release arena chunks. |
| 1617 | g.sb.writeln('static inline void _v_cfree(void *p) { free(p); }') |
| 1618 | g.sb.writeln('#define free(p) ((void)(p), (void)0)') |
| 1619 | } |
| 1620 | g.sb.writeln('') |
| 1621 | |
| 1622 | // V primitive type aliases |
| 1623 | g.sb.writeln('// V primitive types') |
| 1624 | g.sb.writeln('typedef int8_t i8;') |
| 1625 | g.sb.writeln('typedef int16_t i16;') |
| 1626 | g.sb.writeln('typedef int32_t i32;') |
| 1627 | g.sb.writeln('typedef int64_t i64;') |
| 1628 | g.sb.writeln('typedef uint8_t u8;') |
| 1629 | g.sb.writeln('typedef uint16_t u16;') |
| 1630 | g.sb.writeln('typedef uint32_t u32;') |
| 1631 | g.sb.writeln('typedef uint64_t u64;') |
| 1632 | g.sb.writeln('typedef float f32;') |
| 1633 | g.sb.writeln('typedef double f64;') |
| 1634 | g.sb.writeln('typedef u8 byte;') |
| 1635 | g.sb.writeln('typedef size_t usize;') |
| 1636 | g.sb.writeln('typedef ptrdiff_t isize;') |
| 1637 | g.sb.writeln('typedef u32 rune;') |
| 1638 | g.sb.writeln('typedef char* byteptr;') |
| 1639 | g.sb.writeln('typedef char* charptr;') |
| 1640 | g.sb.writeln('typedef void* voidptr;') |
| 1641 | g.sb.writeln('typedef struct sync__Channel* chan;') |
| 1642 | g.sb.writeln('typedef double float_literal;') |
| 1643 | g.sb.writeln('typedef int64_t int_literal;') |
| 1644 | g.sb.writeln('extern int g_main_argc;') |
| 1645 | g.sb.writeln('extern void* g_main_argv;') |
| 1646 | g.emit_freestanding_platform_hook_decls() |
| 1647 | g.emit_vec_simd_fallback_aliases() |
| 1648 | if minimal_preamble { |
| 1649 | g.emit_target_rwmutex() |
| 1650 | g.sb.writeln('') |
| 1651 | return |
| 1652 | } |
| 1653 | // wyhash implementation used by builtin/map and hash modules. |
| 1654 | g.sb.writeln('#ifndef wyhash_final_version_4_2') |
| 1655 | g.sb.writeln('#define wyhash_final_version_4_2') |
| 1656 | g.sb.writeln('#define WYHASH_CONDOM 1') |
| 1657 | g.sb.writeln('#define WYHASH_32BIT_MUM 0') |
| 1658 | g.sb.writeln('#if defined(__GNUC__) || defined(__INTEL_COMPILER) || defined(__clang__)') |
| 1659 | g.sb.writeln(' #define _likely_(x) __builtin_expect(x,1)') |
| 1660 | g.sb.writeln(' #define _unlikely_(x) __builtin_expect(x,0)') |
| 1661 | g.sb.writeln('#else') |
| 1662 | g.sb.writeln(' #define _likely_(x) (x)') |
| 1663 | g.sb.writeln(' #define _unlikely_(x) (x)') |
| 1664 | g.sb.writeln('#endif') |
| 1665 | g.sb.writeln('static inline uint64_t _wyrot(uint64_t x) { return (x>>32)|(x<<32); }') |
| 1666 | g.sb.writeln('static inline void _wymum(uint64_t *A, uint64_t *B){') |
| 1667 | g.sb.writeln('#if defined(__SIZEOF_INT128__)') |
| 1668 | g.sb.writeln(' __uint128_t r=*A; r*=*B; *A=(uint64_t)r; *B=(uint64_t)(r>>64);') |
| 1669 | g.sb.writeln('#elif defined(_MSC_VER) && defined(_M_X64)') |
| 1670 | g.sb.writeln(' *A=_umul128(*A,*B,B);') |
| 1671 | g.sb.writeln('#else') |
| 1672 | g.sb.writeln(' uint64_t ha=*A>>32, hb=*B>>32, la=(uint32_t)*A, lb=(uint32_t)*B, hi, lo;') |
| 1673 | g.sb.writeln(' uint64_t rh=ha*hb, rm0=ha*lb, rm1=hb*la, rl=la*lb, t=rl+(rm0<<32), c=t<rl;') |
| 1674 | g.sb.writeln(' lo=t+(rm1<<32); c+=lo<t; hi=rh+(rm0>>32)+(rm1>>32)+c;') |
| 1675 | g.sb.writeln(' *A=lo; *B=hi;') |
| 1676 | g.sb.writeln('#endif') |
| 1677 | g.sb.writeln('}') |
| 1678 | g.sb.writeln('static inline uint64_t _wymix(uint64_t A, uint64_t B){ _wymum(&A,&B); return A^B; }') |
| 1679 | g.sb.writeln('#ifndef WYHASH_LITTLE_ENDIAN') |
| 1680 | g.sb.writeln(' #if defined(__LITTLE_ENDIAN__) || defined(__aarch64__) || defined(__x86_64__) || defined(_M_X64) || defined(__i386__) || defined(_M_IX86) || defined(TARGET_ORDER_IS_LITTLE)') |
| 1681 | g.sb.writeln(' #define WYHASH_LITTLE_ENDIAN 1') |
| 1682 | g.sb.writeln(' #else') |
| 1683 | g.sb.writeln(' #define WYHASH_LITTLE_ENDIAN 0') |
| 1684 | g.sb.writeln(' #endif') |
| 1685 | g.sb.writeln('#endif') |
| 1686 | g.sb.writeln('#if (WYHASH_LITTLE_ENDIAN)') |
| 1687 | g.sb.writeln(' static inline uint64_t _wyr8(const uint8_t *p) { uint64_t v; memcpy(&v, p, 8); return v;}') |
| 1688 | g.sb.writeln(' static inline uint64_t _wyr4(const uint8_t *p) { uint32_t v; memcpy(&v, p, 4); return v;}') |
| 1689 | g.sb.writeln('#elif defined(__GNUC__) || defined(__INTEL_COMPILER) || defined(__clang__)') |
| 1690 | g.sb.writeln(' static inline uint64_t _wyr8(const uint8_t *p) { uint64_t v; memcpy(&v, p, 8); return __builtin_bswap64(v);}') |
| 1691 | g.sb.writeln(' static inline uint64_t _wyr4(const uint8_t *p) { uint32_t v; memcpy(&v, p, 4); return __builtin_bswap32(v);}') |
| 1692 | g.sb.writeln('#else') |
| 1693 | g.sb.writeln(' static inline uint64_t _wyr8(const uint8_t *p) {') |
| 1694 | g.sb.writeln(' uint64_t v; memcpy(&v, p, 8);') |
| 1695 | g.sb.writeln(' return (((v >> 56) & 0xff)| ((v >> 40) & 0xff00)| ((v >> 24) & 0xff0000)| ((v >> 8) & 0xff000000)| ((v << 8) & 0xff00000000)| ((v << 24) & 0xff0000000000)| ((v << 40) & 0xff000000000000)| ((v << 56) & 0xff00000000000000));') |
| 1696 | g.sb.writeln(' }') |
| 1697 | g.sb.writeln(' static inline uint64_t _wyr4(const uint8_t *p) {') |
| 1698 | g.sb.writeln(' uint32_t v; memcpy(&v, p, 4);') |
| 1699 | g.sb.writeln(' return (((v >> 24) & 0xff)| ((v >> 8) & 0xff00)| ((v << 8) & 0xff0000)| ((v << 24) & 0xff000000));') |
| 1700 | g.sb.writeln(' }') |
| 1701 | g.sb.writeln('#endif') |
| 1702 | g.sb.writeln('static inline uint64_t _wyr3(const uint8_t *p, size_t k) { return (((uint64_t)p[0])<<16)|(((uint64_t)p[k>>1])<<8)|p[k-1];}') |
| 1703 | g.sb.writeln('static inline uint64_t wyhash(const void *key, size_t len, uint64_t seed, const uint64_t *secret){') |
| 1704 | g.sb.writeln(' const uint8_t *p=(const uint8_t *)key; seed^=_wymix(seed^secret[0]^len,secret[1]); uint64_t a, b;') |
| 1705 | g.sb.writeln(' if(_likely_(len<=16)){') |
| 1706 | g.sb.writeln(' if(_likely_(len>=4)){ a=(_wyr4(p)<<32)|_wyr4(p+((len>>3)<<2)); b=(_wyr4(p+len-4)<<32)|_wyr4(p+len-4-((len>>3)<<2)); }') |
| 1707 | g.sb.writeln(' else if(_likely_(len>0)){ a=_wyr3(p,len); b=0; }') |
| 1708 | g.sb.writeln(' else a=b=0;') |
| 1709 | g.sb.writeln(' } else {') |
| 1710 | g.sb.writeln(' size_t i=len;') |
| 1711 | g.sb.writeln(' if(_unlikely_(i>=48)){') |
| 1712 | g.sb.writeln(' uint64_t see1=seed, see2=seed;') |
| 1713 | g.sb.writeln(' do{') |
| 1714 | g.sb.writeln(' seed=_wymix(_wyr8(p)^secret[1],_wyr8(p+8)^seed);') |
| 1715 | g.sb.writeln(' see1=_wymix(_wyr8(p+16)^secret[2],_wyr8(p+24)^see1);') |
| 1716 | g.sb.writeln(' see2=_wymix(_wyr8(p+32)^secret[3],_wyr8(p+40)^see2);') |
| 1717 | g.sb.writeln(' p+=48; i-=48;') |
| 1718 | g.sb.writeln(' }while(_likely_(i>=48));') |
| 1719 | g.sb.writeln(' seed^=see1^see2;') |
| 1720 | g.sb.writeln(' }') |
| 1721 | g.sb.writeln(' while(_unlikely_(i>16)){ seed=_wymix(_wyr8(p)^secret[1],_wyr8(p+8)^seed); i-=16; p+=16; }') |
| 1722 | g.sb.writeln(' a=_wyr8(p+i-16); b=_wyr8(p+i-8);') |
| 1723 | g.sb.writeln(' }') |
| 1724 | g.sb.writeln(' a^=secret[1]; b^=seed; _wymum(&a,&b);') |
| 1725 | g.sb.writeln(' return _wymix(a^secret[0]^len,b^secret[1]);') |
| 1726 | g.sb.writeln('}') |
| 1727 | g.sb.writeln('static const uint64_t _wyp[4] = {0x2d358dccaa6c78a5ull, 0x8bb84b93962eacc9ull, 0x4b33a62ed433d4a3ull, 0x4d5a2da51de1aa47ull};') |
| 1728 | g.sb.writeln('static inline uint64_t wyhash64(uint64_t A, uint64_t B){ A^=0x2d358dccaa6c78a5ull; B^=0x8bb84b93962eacc9ull; _wymum(&A,&B); return _wymix(A^0x2d358dccaa6c78a5ull,B^0x8bb84b93962eacc9ull);}') |
| 1729 | g.sb.writeln('#endif') |
| 1730 | g.sb.writeln('#define _MOV') |
| 1731 | if g.target_os_name() != 'windows' && !g.is_freestanding_target() { |
| 1732 | g.sb.writeln('typedef u8 termios__Cc;') |
| 1733 | } |
| 1734 | // sync__RwMutex for shared variables |
| 1735 | g.emit_target_rwmutex() |
| 1736 | g.sb.writeln('') |
| 1737 | g.sb.writeln('') |
| 1738 | } |
| 1739 | |
| 1740 | fn (mut g Gen) emit_freestanding_platform_hook_decls() { |
| 1741 | if !g.has_freestanding_hooks() { |
| 1742 | return |
| 1743 | } |
| 1744 | g.sb.writeln('') |
| 1745 | if g.has_freestanding_hook_capability('output') { |
| 1746 | g.sb.writeln('isize v_platform_write(int stream, const u8* buf, isize len);') |
| 1747 | } |
| 1748 | if g.has_freestanding_hook_capability('panic') { |
| 1749 | g.sb.writeln('void v_platform_panic(const u8* msg, isize len);') |
| 1750 | } |
| 1751 | if g.has_freestanding_hook_capability('alloc') { |
| 1752 | g.sb.writeln('void* v_platform_malloc(isize n);') |
| 1753 | g.sb.writeln('void* v_platform_realloc(void* ptr, isize n);') |
| 1754 | g.sb.writeln('void v_platform_free(void* ptr);') |
| 1755 | } |
| 1756 | } |
| 1757 | |
| 1758 | fn (g &Gen) use_minimal_preamble() bool { |
| 1759 | return g.emit_modules.len == 1 && 'main' in g.emit_modules |
| 1760 | } |
| 1761 | |
| 1762 | fn (mut g Gen) emit_runtime_aliases() { |
| 1763 | mut array_names := g.array_aliases.keys() |
| 1764 | array_names.sort() |
| 1765 | if 'Array_math__T' !in g.array_aliases { |
| 1766 | g.sb.writeln('typedef array Array_math__T;') |
| 1767 | } |
| 1768 | // Emit dynamic array aliases (skip fixed arrays) |
| 1769 | for name in array_names { |
| 1770 | if name.starts_with('Array_fixed_') { |
| 1771 | continue |
| 1772 | } |
| 1773 | g.emit_array_alias_decl(name) |
| 1774 | } |
| 1775 | // Pointer element typedefs (e.g. 'typedef Coord* Coordptr;') are deferred |
| 1776 | // to emit_pointer_typedefs() which runs after pass 2 (enum/alias definitions). |
| 1777 | // Emit primitive fixed array typedefs (non-primitive ones deferred until after struct defs) |
| 1778 | for name in array_names { |
| 1779 | if !name.starts_with('Array_fixed_') { |
| 1780 | continue |
| 1781 | } |
| 1782 | if info := g.collected_fixed_array_types[name] { |
| 1783 | if info.elem_type in primitive_types |
| 1784 | || info.elem_type in ['char', 'voidptr', 'charptr', 'byteptr', 'void*', 'char*'] { |
| 1785 | g.sb.writeln('typedef ${info.elem_type} ${name} [${info.size}];') |
| 1786 | alias_key := 'alias_${name}' |
| 1787 | body_key := 'body_${name}' |
| 1788 | g.emitted_types[alias_key] = true |
| 1789 | g.emitted_types[body_key] = true |
| 1790 | // Emit fallback str macros for fixed array types (only if no real function was generated) |
| 1791 | str_fn := '${name}_str' |
| 1792 | if str_fn !in g.fn_return_types { |
| 1793 | g.sb.writeln('#define ${name}_str(a) ((string){.str = "${name}", .len = ${name.len}, .is_lit = 1})') |
| 1794 | g.sb.writeln('#define ${name}__str(a) ${name}_str(a)') |
| 1795 | } |
| 1796 | } |
| 1797 | } |
| 1798 | } |
| 1799 | g.sb.writeln('typedef array VArg_string;') |
| 1800 | g.sb.writeln('bool map_map_eq(map a, map b);') |
| 1801 | mut map_names := g.map_aliases.keys() |
| 1802 | map_names.sort() |
| 1803 | for name in map_names { |
| 1804 | g.emit_map_alias_decl(name) |
| 1805 | } |
| 1806 | // Option/Result forward declarations (struct definitions emitted later |
| 1807 | // after IError is defined, via emit_option_result_structs) |
| 1808 | mut option_names := g.option_aliases.keys() |
| 1809 | option_names.sort() |
| 1810 | for name in option_names { |
| 1811 | if g.should_skip_shadowed_option_result_alias(name) { |
| 1812 | continue |
| 1813 | } |
| 1814 | val_type := option_value_type(name) |
| 1815 | g.emit_option_result_forward_decl(name, val_type) |
| 1816 | } |
| 1817 | if '_option_string' in g.option_aliases { |
| 1818 | g.sb.writeln('static _option_string builtin__Option_string__clone(_option_string s);') |
| 1819 | } |
| 1820 | mut result_names := g.result_aliases.keys() |
| 1821 | result_names.sort() |
| 1822 | for name in result_names { |
| 1823 | if g.should_skip_shadowed_option_result_alias(name) { |
| 1824 | continue |
| 1825 | } |
| 1826 | val_type := g.result_value_type(name) |
| 1827 | g.emit_option_result_forward_decl(name, val_type) |
| 1828 | } |
| 1829 | } |
| 1830 | |
| 1831 | fn (mut g Gen) emit_option_result_forward_decl(name string, val_type string) { |
| 1832 | if g.option_result_payload_invalid(val_type) { |
| 1833 | return |
| 1834 | } |
| 1835 | key := 'option_result_forward_${name}' |
| 1836 | if key in g.emitted_types { |
| 1837 | return |
| 1838 | } |
| 1839 | g.emitted_types[key] = true |
| 1840 | if val_type != '' && val_type != 'void' { |
| 1841 | g.sb.writeln('typedef struct ${name} ${name};') |
| 1842 | } else if name.starts_with('_option_') { |
| 1843 | g.sb.writeln('typedef _option ${name};') |
| 1844 | } else { |
| 1845 | g.sb.writeln('typedef _result ${name};') |
| 1846 | } |
| 1847 | } |
| 1848 | |
| 1849 | // emit_pointer_typedefs emits pointer element typedefs needed by array helper functions. |
| 1850 | // e.g. Array_Coordptr needs 'typedef Coord* Coordptr;'. |
| 1851 | // Must run AFTER pass 2 so that enum and type alias definitions are available. |
| 1852 | fn (mut g Gen) emit_pointer_typedefs() { |
| 1853 | mut array_names := g.array_aliases.keys() |
| 1854 | array_names.sort() |
| 1855 | for name in array_names { |
| 1856 | if name.starts_with('Array_fixed_') { |
| 1857 | continue |
| 1858 | } |
| 1859 | elem := name['Array_'.len..] |
| 1860 | if elem.len > 3 && elem.ends_with('ptr') { |
| 1861 | base := elem[..elem.len - 3] |
| 1862 | if base !in ['void', 'char', 'byte'] && !elem.starts_with('Array_') |
| 1863 | && !elem.starts_with('Map_') { |
| 1864 | g.sb.writeln('typedef ${base}* ${elem};') |
| 1865 | } |
| 1866 | } |
| 1867 | } |
| 1868 | } |
| 1869 | |
| 1870 | fn (mut g Gen) get_enum_name(node ast.EnumDecl) string { |
| 1871 | if g.cur_module != '' && g.cur_module != 'main' && g.cur_module != 'builtin' { |
| 1872 | return '${g.cur_module}__${node.name}' |
| 1873 | } |
| 1874 | return node.name |
| 1875 | } |
| 1876 | |
| 1877 | fn (mut g Gen) emit_array_alias_decl(name string) { |
| 1878 | array_name := name.trim_right('*') |
| 1879 | if !array_name.starts_with('Array_') || array_name.starts_with('Array_fixed_') { |
| 1880 | return |
| 1881 | } |
| 1882 | alias_key := 'alias_${array_name}' |
| 1883 | if alias_key in g.emitted_types { |
| 1884 | return |
| 1885 | } |
| 1886 | g.emitted_types[alias_key] = true |
| 1887 | g.array_aliases[array_name] = true |
| 1888 | g.sb.writeln('typedef array ${array_name};') |
| 1889 | } |
| 1890 | |
| 1891 | fn (mut g Gen) emit_map_alias_decl(name string) { |
| 1892 | map_name := name.trim_right('*') |
| 1893 | if !map_name.starts_with('Map_') { |
| 1894 | return |
| 1895 | } |
| 1896 | alias_key := 'alias_${map_name}' |
| 1897 | if alias_key in g.emitted_types { |
| 1898 | return |
| 1899 | } |
| 1900 | g.emitted_types[alias_key] = true |
| 1901 | g.map_aliases[map_name] = true |
| 1902 | g.sb.writeln('typedef map ${map_name};') |
| 1903 | // Forward-declare map helper functions (bodies generated later). |
| 1904 | map_str_fn := '${map_name}_str' |
| 1905 | if map_str_fn !in g.fn_return_types { |
| 1906 | g.sb.writeln('string ${map_name}_str(${map_name} m);') |
| 1907 | } |
| 1908 | g.sb.writeln('bool ${map_name}_map_eq(${map_name} a, ${map_name} b);') |
| 1909 | } |
| 1910 | |
| 1911 | fn (mut g Gen) gen_enum_decl(node ast.EnumDecl) { |
| 1912 | name := g.get_enum_name(node) |
| 1913 | enum_key := 'enum_${name}' |
| 1914 | if enum_key in g.emitted_types { |
| 1915 | return |
| 1916 | } |
| 1917 | g.emitted_types[enum_key] = true |
| 1918 | mut is_flag := false |
| 1919 | for attribute in node.attributes { |
| 1920 | if attribute.name == 'flag' { |
| 1921 | is_flag = true |
| 1922 | break |
| 1923 | } |
| 1924 | if attribute.name == '' && attribute.value is ast.Ident && attribute.value.name == 'flag' { |
| 1925 | is_flag = true |
| 1926 | break |
| 1927 | } |
| 1928 | } |
| 1929 | |
| 1930 | g.sb.writeln('typedef enum {') |
| 1931 | for i, field in node.fields { |
| 1932 | g.sb.write_string('\t${g.enum_member_c_name(name, field.name)}') |
| 1933 | if field.value !is ast.EmptyExpr { |
| 1934 | g.sb.write_string(' = ') |
| 1935 | if enum_value := g.enum_field_value_int_expr(field.value) { |
| 1936 | g.sb.write_string(enum_value) |
| 1937 | } else { |
| 1938 | g.expr(field.value) |
| 1939 | } |
| 1940 | } else if is_flag { |
| 1941 | g.sb.write_string(' = ${u64(1) << i}U') |
| 1942 | } |
| 1943 | if i < node.fields.len - 1 { |
| 1944 | g.sb.writeln(',') |
| 1945 | } else { |
| 1946 | g.sb.writeln('') |
| 1947 | } |
| 1948 | } |
| 1949 | g.sb.writeln('} ${name};') |
| 1950 | g.sb.writeln('') |
| 1951 | enum_str_fn := '${name}__str' |
| 1952 | has_explicit_str := g.has_explicit_str_method_for_c_type(name) |
| 1953 | if !has_explicit_str && ((g.cache_bundle_name == 'virtuals' && g.should_emit_current_file()) |
| 1954 | || enum_str_fn in g.force_emit_fn_names) { |
| 1955 | mut enum_str_expr := c_static_v_string_expr('unknown enum value') |
| 1956 | for i := node.fields.len - 1; i >= 0; i-- { |
| 1957 | field := node.fields[i] |
| 1958 | enum_str_expr = '((v == ${g.enum_member_c_name(name, field.name)}) ? ${c_static_v_string_expr(field.name)} : ${enum_str_expr})' |
| 1959 | } |
| 1960 | g.late_struct_defs << 'string ${name}__str(${name} v) { return ${enum_str_expr}; }\n' |
| 1961 | g.fn_return_types[enum_str_fn] = 'string' |
| 1962 | } else if enum_str_fn !in g.fn_return_types { |
| 1963 | // Emit a macro so the expression is type-checked only at use sites, |
| 1964 | // where `string` is fully defined. |
| 1965 | mut enum_str_expr := c_static_v_string_expr('unknown enum value') |
| 1966 | for i := node.fields.len - 1; i >= 0; i-- { |
| 1967 | field := node.fields[i] |
| 1968 | enum_str_expr = '((_e == ${g.enum_member_c_name(name, field.name)}) ? ${c_static_v_string_expr(field.name)} : ${enum_str_expr})' |
| 1969 | } |
| 1970 | g.sb.writeln('#define ${name}__str(v) ({ ${name} _e = (v); ${enum_str_expr}; })') |
| 1971 | } |
| 1972 | enum_short_str_fn := '${name}_str' |
| 1973 | if enum_short_str_fn !in g.fn_return_types { |
| 1974 | g.sb.writeln('#define ${name}_str(v) ${name}__str(v)') |
| 1975 | } |
| 1976 | // Runtime enum values array for lowered `EnumType.values` usages. Keep the |
| 1977 | // helper name distinct from enum fields; enums can have a field named `values`. |
| 1978 | values_name := '${name}__values_array' |
| 1979 | values_data_name := '__enum_values_data_${name}' |
| 1980 | if node.fields.len > 0 { |
| 1981 | g.sb.write_string('static ${name} ${values_data_name}[${node.fields.len}] = {') |
| 1982 | for i, field in node.fields { |
| 1983 | if i > 0 { |
| 1984 | g.sb.write_string(', ') |
| 1985 | } |
| 1986 | g.sb.write_string(g.enum_member_c_name(name, field.name)) |
| 1987 | } |
| 1988 | g.sb.writeln('};') |
| 1989 | g.sb.writeln('#define ${values_name} ((array){ .data = ${values_data_name}, .offset = 0, .len = ${node.fields.len}, .cap = ${node.fields.len}, .flags = 0, .element_size = sizeof(${name}) })') |
| 1990 | } else { |
| 1991 | g.sb.writeln('#define ${values_name} ((array){ .data = 0, .offset = 0, .len = 0, .cap = 0, .flags = 0, .element_size = sizeof(${name}) })') |
| 1992 | } |
| 1993 | g.sb.writeln('') |
| 1994 | } |
| 1995 | |
| 1996 | fn (mut g Gen) gen_enum_from_string_helper(node ast.EnumDecl) { |
| 1997 | name := g.get_enum_name(node) |
| 1998 | opt_name := '_option_' + mangle_alias_component(name) |
| 1999 | if opt_name !in g.option_aliases || !g.should_emit_current_file() { |
| 2000 | return |
| 2001 | } |
| 2002 | helper_name := '${name}__from_string' |
| 2003 | helper_key := 'enum_from_string_${helper_name}' |
| 2004 | if helper_key in g.emitted_types { |
| 2005 | return |
| 2006 | } |
| 2007 | g.emitted_types[helper_key] = true |
| 2008 | g.declared_fn_names[helper_name] = true |
| 2009 | g.sb.writeln('${opt_name} ${helper_name}(string s);') |
| 2010 | g.sb.writeln('${opt_name} ${helper_name}(string s) {') |
| 2011 | for field in node.fields { |
| 2012 | field_lit := c_string_literal_content_to_c(field.name) |
| 2013 | g.sb.writeln('\tif (s.len == ${field.name.len} && memcmp(s.str, ${field_lit}, ${field.name.len}) == 0) {') |
| 2014 | g.sb.writeln('\t\t${name} _val = ${g.enum_member_c_name(name, field.name)};') |
| 2015 | g.sb.writeln('\t\t${opt_name} _opt = (${opt_name}){ .state = 2 };') |
| 2016 | g.sb.writeln('\t\t_option_ok(&_val, (_option*)&_opt, sizeof(_val));') |
| 2017 | g.sb.writeln('\t\treturn _opt;') |
| 2018 | g.sb.writeln('\t}') |
| 2019 | } |
| 2020 | g.sb.writeln('\treturn (${opt_name}){ .state = 2 };') |
| 2021 | g.sb.writeln('}') |
| 2022 | g.sb.writeln('') |
| 2023 | } |
| 2024 | |
| 2025 | fn (mut g Gen) emit_enum_from_string_helpers() { |
| 2026 | if g.has_flat() { |
| 2027 | for i in 0 .. g.flat.files.len { |
| 2028 | fc := g.flat.file_cursor(i) |
| 2029 | g.set_file_cursor_module(fc) |
| 2030 | stmts := fc.stmts() |
| 2031 | for j in 0 .. stmts.len() { |
| 2032 | stmt := stmts.at(j) |
| 2033 | if stmt.kind() == .stmt_enum_decl { |
| 2034 | g.gen_enum_from_string_helper(stmt.enum_decl(true)) |
| 2035 | } |
| 2036 | } |
| 2037 | } |
| 2038 | return |
| 2039 | } |
| 2040 | for file in g.files { |
| 2041 | g.set_file_module(file) |
| 2042 | for stmt in file.stmts { |
| 2043 | if !stmt_has_valid_data(stmt) { |
| 2044 | continue |
| 2045 | } |
| 2046 | if stmt is ast.EnumDecl { |
| 2047 | g.gen_enum_from_string_helper(stmt) |
| 2048 | } |
| 2049 | } |
| 2050 | } |
| 2051 | } |
| 2052 | |
| 2053 | fn (mut g Gen) enum_field_value_int_expr(expr ast.Expr) ?string { |
| 2054 | match expr { |
| 2055 | ast.BasicLiteral { |
| 2056 | if expr.kind == .number { |
| 2057 | return sanitize_c_number_literal(expr.value) |
| 2058 | } |
| 2059 | } |
| 2060 | ast.CallOrCastExpr { |
| 2061 | return g.enum_field_value_int_expr(expr.expr) |
| 2062 | } |
| 2063 | ast.CastExpr { |
| 2064 | return g.enum_field_value_int_expr(expr.expr) |
| 2065 | } |
| 2066 | ast.ParenExpr { |
| 2067 | return g.enum_field_value_int_expr(expr.expr) |
| 2068 | } |
| 2069 | ast.PrefixExpr { |
| 2070 | if expr.op == .minus { |
| 2071 | if value := g.enum_field_value_int_expr(expr.expr) { |
| 2072 | return '-${value}' |
| 2073 | } |
| 2074 | } |
| 2075 | } |
| 2076 | ast.SelectorExpr { |
| 2077 | return g.enum_selector_int_value(expr) |
| 2078 | } |
| 2079 | else {} |
| 2080 | } |
| 2081 | |
| 2082 | return none |
| 2083 | } |
| 2084 | |
| 2085 | fn (mut g Gen) enum_selector_int_value(sel ast.SelectorExpr) ?string { |
| 2086 | rhs_name := sel.rhs.name |
| 2087 | if sel.lhs is ast.SelectorExpr { |
| 2088 | lhs_sel := sel.lhs as ast.SelectorExpr |
| 2089 | if lhs_sel.lhs is ast.Ident { |
| 2090 | lhs_mod := lhs_sel.lhs as ast.Ident |
| 2091 | mut module_names := [lhs_mod.name] |
| 2092 | resolved_mod_name := g.resolve_module_name(lhs_mod.name) |
| 2093 | if resolved_mod_name !in module_names { |
| 2094 | module_names << resolved_mod_name |
| 2095 | } |
| 2096 | for mod_name in module_names { |
| 2097 | enum_name := '${mod_name}__${lhs_sel.rhs.name}' |
| 2098 | if value := g.enum_decl_field_int_value(enum_name, rhs_name) { |
| 2099 | return value |
| 2100 | } |
| 2101 | } |
| 2102 | } |
| 2103 | } |
| 2104 | if sel.lhs is ast.Ident { |
| 2105 | lhs_name := sel.lhs.name |
| 2106 | mut enum_names := [lhs_name] |
| 2107 | if g.cur_module != '' && g.cur_module != 'main' && g.cur_module != 'builtin' { |
| 2108 | enum_names << '${g.cur_module}__${lhs_name}' |
| 2109 | } |
| 2110 | for enum_name in enum_names { |
| 2111 | if value := g.enum_decl_field_int_value(enum_name, rhs_name) { |
| 2112 | return value |
| 2113 | } |
| 2114 | } |
| 2115 | } |
| 2116 | return none |
| 2117 | } |
| 2118 | |
| 2119 | fn (mut g Gen) enum_decl_field_int_value(enum_name string, field_name string) ?string { |
| 2120 | short_name := enum_name.all_after_last('__') |
| 2121 | module_name := if enum_name.contains('__') { |
| 2122 | enum_name.all_before_last('__') |
| 2123 | } else { |
| 2124 | g.cur_module |
| 2125 | } |
| 2126 | if g.has_flat() { |
| 2127 | for i in 0 .. g.flat.files.len { |
| 2128 | fc := g.flat.file_cursor(i) |
| 2129 | if module_name != '' && flat_file_module_name(fc) != module_name { |
| 2130 | continue |
| 2131 | } |
| 2132 | stmts := fc.stmts() |
| 2133 | for j in 0 .. stmts.len() { |
| 2134 | stmt := stmts.at(j) |
| 2135 | if stmt.kind() != .stmt_enum_decl || stmt.name() != short_name { |
| 2136 | continue |
| 2137 | } |
| 2138 | decl := stmt.enum_decl(true) |
| 2139 | mut next_value := 0 |
| 2140 | for field in decl.fields { |
| 2141 | if field.value !is ast.EmptyExpr { |
| 2142 | if value := g.enum_field_value_int_expr(field.value) { |
| 2143 | next_value = value.int() |
| 2144 | } |
| 2145 | } |
| 2146 | if field.name == field_name { |
| 2147 | return '${next_value}' |
| 2148 | } |
| 2149 | next_value++ |
| 2150 | } |
| 2151 | } |
| 2152 | } |
| 2153 | return none |
| 2154 | } |
| 2155 | for file in g.files { |
| 2156 | if module_name != '' && file.mod != module_name { |
| 2157 | continue |
| 2158 | } |
| 2159 | for stmt in file.stmts { |
| 2160 | if stmt is ast.EnumDecl && stmt.name == short_name { |
| 2161 | mut next_value := 0 |
| 2162 | for field in stmt.fields { |
| 2163 | if field.value !is ast.EmptyExpr { |
| 2164 | if value := g.enum_field_value_int_expr(field.value) { |
| 2165 | next_value = value.int() |
| 2166 | } |
| 2167 | } |
| 2168 | if field.name == field_name { |
| 2169 | return '${next_value}' |
| 2170 | } |
| 2171 | next_value++ |
| 2172 | } |
| 2173 | } |
| 2174 | } |
| 2175 | } |
| 2176 | return none |
| 2177 | } |
| 2178 | |
| 2179 | // collect_force_emit_str_fns scans map_aliases and array_aliases to find which |
| 2180 | // str functions will be called from generated map/array str code, and adds them |
| 2181 | // to force_emit_fn_names so they get emitted even if mark_used didn't trace them. |
| 2182 | fn (mut g Gen) collect_force_emit_str_fns() { |
| 2183 | if g.used_fn_keys.len == 0 { |
| 2184 | return |
| 2185 | } |
| 2186 | for name, _ in g.map_aliases { |
| 2187 | if '${name}_str' in g.fn_return_types { |
| 2188 | continue // user-defined map str function |
| 2189 | } |
| 2190 | without_prefix := name.all_after('Map_') |
| 2191 | _, value_type := g.parse_map_kv_types(without_prefix) |
| 2192 | if value_type == '' { |
| 2193 | continue |
| 2194 | } |
| 2195 | g.add_force_emit_str_fn(value_type) |
| 2196 | } |
| 2197 | // Force-emit all Array_*_str functions that have registered signatures. |
| 2198 | // These are generated by the transformer for string interpolation but the |
| 2199 | // calls to them are emitted directly by cleanc (in write_sprintf_arg), |
| 2200 | // so markused may not trace them. |
| 2201 | for fn_name, _ in g.fn_return_types { |
| 2202 | if fn_name.starts_with('Array_') && fn_name.ends_with('_str') { |
| 2203 | g.force_emit_fn_names[fn_name] = true |
| 2204 | } |
| 2205 | } |
| 2206 | } |
| 2207 | |
| 2208 | fn (mut g Gen) add_force_emit_str_fn(type_name string) { |
| 2209 | // Primitive types don't need force-emitting (already in builtin) |
| 2210 | if type_name in ['string', 'int', 'i8', 'i16', 'i32', 'i64', 'u8', 'u16', 'u32', 'u64', 'f32', |
| 2211 | 'f64', 'bool', 'rune', 'voidptr'] { |
| 2212 | return |
| 2213 | } |
| 2214 | if str_fn := g.get_str_fn_for_type(type_name) { |
| 2215 | g.force_emit_fn_names[str_fn] = true |
| 2216 | } |
| 2217 | } |
| 2218 | |
| 2219 | // emit_map_str_functions generates Map_K_V_str functions for all map types. |
| 2220 | fn (mut g Gen) emit_map_str_functions() { |
| 2221 | mut map_names := g.map_aliases.keys() |
| 2222 | map_names.sort() |
| 2223 | for name in map_names { |
| 2224 | // Skip if the user already defined this function |
| 2225 | if '${name}_str' in g.fn_return_types { |
| 2226 | continue |
| 2227 | } |
| 2228 | // Skip map aliases whose typedef wasn't emitted (late-registered aliases |
| 2229 | // that reference unresolved short-name types). Without a typedef, the |
| 2230 | // generated helper would reference an undeclared type. |
| 2231 | if 'alias_${name}' !in g.emitted_types { |
| 2232 | continue |
| 2233 | } |
| 2234 | // Parse key and value types from Map_K_V name |
| 2235 | key_type, value_type := g.map_alias_key_value_types(name) |
| 2236 | if key_type == '' || value_type == '' { |
| 2237 | continue |
| 2238 | } |
| 2239 | // Skip generic placeholder types (single uppercase letters like K, V, T) |
| 2240 | if key_type.len == 1 && key_type[0] >= `A` && key_type[0] <= `Z` { |
| 2241 | continue |
| 2242 | } |
| 2243 | // Skip types that can't be safely used as local variables (fixed arrays, etc.) |
| 2244 | if !g.can_emit_map_str_for_type(key_type) || !g.can_emit_map_str_for_type(value_type) { |
| 2245 | // Emit a fallback that returns the type name |
| 2246 | g.sb.writeln('') |
| 2247 | g.sb.writeln('string ${name}_str(${name} m) {') |
| 2248 | g.sb.writeln('\treturn (string){.str = "${name}", .len = ${name.len}, .is_lit = 1};') |
| 2249 | g.sb.writeln('}') |
| 2250 | continue |
| 2251 | } |
| 2252 | g.sb.writeln('') |
| 2253 | g.sb.writeln('string ${name}_str(${name} m) {') |
| 2254 | g.sb.writeln('\tstrings__Builder sb = strings__new_builder(2 + m.key_values.len * 10);') |
| 2255 | g.sb.writeln('\tstrings__Builder__write_string(&sb, (string){.str = "{", .len = 1, .is_lit = 1});') |
| 2256 | g.sb.writeln('\tbool is_first = true;') |
| 2257 | g.sb.writeln('\tfor (int i = 0; i < m.key_values.len; ++i) {') |
| 2258 | g.sb.writeln('\t\tif (!DenseArray__has_index(&m.key_values, i)) continue;') |
| 2259 | g.sb.writeln('\t\tif (!is_first) strings__Builder__write_string(&sb, (string){.str = ", ", .len = 2, .is_lit = 1});') |
| 2260 | // Key |
| 2261 | if key_type.starts_with('Array_fixed_') { |
| 2262 | elem_type, arr_len := g.parse_fixed_array_type(key_type) |
| 2263 | g.sb.writeln('\t\t${elem_type}* key_ptr = (${elem_type}*)DenseArray__key(&m.key_values, i);') |
| 2264 | g.emit_fixed_array_str_write('key_ptr', elem_type, arr_len) |
| 2265 | } else { |
| 2266 | g.sb.writeln('\t\t${key_type} key = *(${key_type}*)DenseArray__key(&m.key_values, i);') |
| 2267 | g.emit_map_str_write_val('key', key_type) |
| 2268 | } |
| 2269 | // Separator |
| 2270 | g.sb.writeln('\t\tstrings__Builder__write_string(&sb, (string){.str = ": ", .len = 2, .is_lit = 1});') |
| 2271 | // Value |
| 2272 | if value_type.starts_with('Array_fixed_') { |
| 2273 | // Fixed arrays can't be assigned to local variables; use pointer |
| 2274 | elem_type, arr_len := g.parse_fixed_array_type(value_type) |
| 2275 | g.sb.writeln('\t\t${elem_type}* val_ptr = (${elem_type}*)DenseArray__value(&m.key_values, i);') |
| 2276 | g.emit_fixed_array_str_write('val_ptr', elem_type, arr_len) |
| 2277 | } else { |
| 2278 | g.sb.writeln('\t\t${value_type} val = *(${value_type}*)DenseArray__value(&m.key_values, i);') |
| 2279 | g.emit_map_str_write_val('val', value_type) |
| 2280 | } |
| 2281 | g.sb.writeln('\t\tis_first = false;') |
| 2282 | g.sb.writeln('\t}') |
| 2283 | g.sb.writeln('\tstrings__Builder__write_string(&sb, (string){.str = "}", .len = 1, .is_lit = 1});') |
| 2284 | g.sb.writeln('\treturn strings__Builder__str(&sb);') |
| 2285 | g.sb.writeln('}') |
| 2286 | } |
| 2287 | } |
| 2288 | |
| 2289 | fn (g &Gen) can_emit_map_str_for_type(type_name string) bool { |
| 2290 | // Primitive types are always OK |
| 2291 | if type_name in ['string', 'int', 'i8', 'i16', 'i32', 'i64', 'u8', 'u16', 'u32', 'u64', 'f32', |
| 2292 | 'f64', 'bool', 'rune', 'voidptr'] { |
| 2293 | return true |
| 2294 | } |
| 2295 | // Fixed arrays — need special handling but can be emitted |
| 2296 | if type_name.starts_with('Array_fixed_') { |
| 2297 | return true |
| 2298 | } |
| 2299 | // Pointer-like types that may not be valid C type names |
| 2300 | if type_name in ['intptr', 'charptr', 'byteptr'] || type_name.ends_with('ptr') { |
| 2301 | return false |
| 2302 | } |
| 2303 | // Check if a str function exists for this type |
| 2304 | if '${type_name}_str' in g.fn_return_types || '${type_name}__str' in g.fn_return_types { |
| 2305 | return true |
| 2306 | } |
| 2307 | // Map types can call their own str function (recursive) |
| 2308 | if type_name.starts_with('Map_') { |
| 2309 | return true |
| 2310 | } |
| 2311 | // Array types — check if Array_T_str exists |
| 2312 | if type_name.starts_with('Array_') { |
| 2313 | return '${type_name}_str' in g.fn_return_types |
| 2314 | } |
| 2315 | // For other types (structs, enums, etc.), check if str function exists |
| 2316 | return '${type_name}_str' in g.fn_return_types || '${type_name}__str' in g.fn_return_types |
| 2317 | } |
| 2318 | |
| 2319 | fn (mut g Gen) emit_map_str_write_val(var_name string, type_name string) { |
| 2320 | if type_name == 'string' { |
| 2321 | g.sb.writeln('\t\tstrings__Builder__write_string(&sb, (string){.str = "\'", .len = 1, .is_lit = 1});') |
| 2322 | g.sb.writeln('\t\tstrings__Builder__write_string(&sb, ${var_name});') |
| 2323 | g.sb.writeln('\t\tstrings__Builder__write_string(&sb, (string){.str = "\'", .len = 1, .is_lit = 1});') |
| 2324 | } else if type_name == 'rune' { |
| 2325 | g.sb.writeln('\t\tstrings__Builder__write_string(&sb, (string){.str = "`", .len = 1, .is_lit = 1});') |
| 2326 | g.sb.writeln('\t\tstrings__Builder__write_string(&sb, rune__str(${var_name}));') |
| 2327 | g.sb.writeln('\t\tstrings__Builder__write_string(&sb, (string){.str = "`", .len = 1, .is_lit = 1});') |
| 2328 | } else if type_name in ['int', 'i8', 'i16', 'i32', 'i64'] { |
| 2329 | g.sb.writeln('\t\tstrings__Builder__write_string(&sb, int__str((int)(${var_name})));') |
| 2330 | } else if type_name in ['u8', 'u16', 'u32', 'u64'] { |
| 2331 | g.sb.writeln('\t\tstrings__Builder__write_string(&sb, u64__str((u64)(${var_name})));') |
| 2332 | } else if type_name == 'f32' { |
| 2333 | g.sb.writeln('\t\tstrings__Builder__write_string(&sb, f32__str(${var_name}));') |
| 2334 | } else if type_name == 'f64' { |
| 2335 | g.sb.writeln('\t\tstrings__Builder__write_string(&sb, f64__str(${var_name}));') |
| 2336 | } else if type_name == 'bool' { |
| 2337 | g.sb.writeln('\t\tstrings__Builder__write_string(&sb, ${var_name} ? (string){.str = "true", .len = 4, .is_lit = 1} : (string){.str = "false", .len = 5, .is_lit = 1});') |
| 2338 | } else if type_name == 'voidptr' { |
| 2339 | g.sb.writeln('\t\t{ uintptr_t _addr = (uintptr_t)(${var_name}); char _buf[2 + sizeof(uintptr_t) * 2]; _buf[0] = \'0\'; _buf[1] = \'x\'; for (int _i = 0; _i < (int)(sizeof(uintptr_t) * 2); ++_i) { int _shift = (int)((sizeof(uintptr_t) * 2 - 1 - _i) * 4); _buf[2 + _i] = "0123456789abcdef"[(_addr >> _shift) & 0xf]; } strings__Builder__write_string(&sb, (string){.str = (u8*)_buf, .len = (int)sizeof(_buf), .is_lit = 0}); }') |
| 2340 | } else { |
| 2341 | // For custom types, pick whichever str symbol exists (`Type_str` or `Type__str`). |
| 2342 | if str_fn := g.get_str_fn_for_type(type_name) { |
| 2343 | // Check if the str function expects a pointer receiver |
| 2344 | ptr_params := g.fn_param_is_ptr[str_fn] or { []bool{} } |
| 2345 | if ptr_params.len > 0 && ptr_params[0] { |
| 2346 | g.sb.writeln('\t\tstrings__Builder__write_string(&sb, ${str_fn}(&${var_name}));') |
| 2347 | } else { |
| 2348 | g.sb.writeln('\t\tstrings__Builder__write_string(&sb, ${str_fn}(${var_name}));') |
| 2349 | } |
| 2350 | } else { |
| 2351 | // Keep the legacy fallback so missing str support still fails loudly in C. |
| 2352 | g.sb.writeln('\t\tstrings__Builder__write_string(&sb, ${type_name}_str(${var_name}));') |
| 2353 | } |
| 2354 | } |
| 2355 | } |
| 2356 | |
| 2357 | // parse_fixed_array_type parses "Array_fixed_f64_2" into ("f64", 2). |
| 2358 | fn (g &Gen) parse_fixed_array_type(type_name string) (string, int) { |
| 2359 | // Format: Array_fixed_<elem_type>_<len> |
| 2360 | without_prefix := type_name.all_after('Array_fixed_') |
| 2361 | // The last _N is the length |
| 2362 | last_underscore := without_prefix.last_index('_') or { return '', 0 } |
| 2363 | elem_type := without_prefix[..last_underscore] |
| 2364 | arr_len := without_prefix[last_underscore + 1..].int() |
| 2365 | return elem_type, arr_len |
| 2366 | } |
| 2367 | |
| 2368 | // emit_fixed_array_str_write generates code to format a fixed array as "[e1, e2, ...]". |
| 2369 | fn (mut g Gen) emit_fixed_array_str_write(ptr_var string, elem_type string, arr_len int) { |
| 2370 | g.sb.writeln('\t\tstrings__Builder__write_string(&sb, (string){.str = "[", .len = 1, .is_lit = 1});') |
| 2371 | for j in 0 .. arr_len { |
| 2372 | if j > 0 { |
| 2373 | g.sb.writeln('\t\tstrings__Builder__write_string(&sb, (string){.str = ", ", .len = 2, .is_lit = 1});') |
| 2374 | } |
| 2375 | g.emit_map_str_write_val('${ptr_var}[${j}]', elem_type) |
| 2376 | } |
| 2377 | g.sb.writeln('\t\tstrings__Builder__write_string(&sb, (string){.str = "]", .len = 1, .is_lit = 1});') |
| 2378 | } |
| 2379 | |
| 2380 | // emit_map_eq_functions generates Map_K_V_map_eq functions for all map types. |
| 2381 | fn (mut g Gen) emit_map_eq_functions() { |
| 2382 | // Generic fallback for when the specific map type is not known. |
| 2383 | // Only emit if not already provided via the V source (builtin/map.v) |
| 2384 | // or via the cache (builtin.o). |
| 2385 | if 'map_map_eq' !in g.fn_return_types && g.cached_init_calls.len == 0 { |
| 2386 | g.sb.writeln('') |
| 2387 | g.sb.writeln('bool map_map_eq(map a, map b) {') |
| 2388 | g.sb.writeln('\tif (a.len != b.len) return false;') |
| 2389 | g.sb.writeln('\tfor (int i = 0; i < a.key_values.len; ++i) {') |
| 2390 | g.sb.writeln('\t\tif (!DenseArray__has_index(&a.key_values, i)) continue;') |
| 2391 | g.sb.writeln('\t\tvoid* k = DenseArray__key(&a.key_values, i);') |
| 2392 | g.sb.writeln('\t\tif (!map__exists(&b, k)) return false;') |
| 2393 | g.sb.writeln('\t\tvoid* va = DenseArray__value(&a.key_values, i);') |
| 2394 | g.sb.writeln('\t\tvoid* vb_p = map__get(&b, k, va);') |
| 2395 | g.sb.writeln('\t\tif (memcmp(va, vb_p, a.value_bytes) != 0) return false;') |
| 2396 | g.sb.writeln('\t}') |
| 2397 | g.sb.writeln('\treturn true;') |
| 2398 | g.sb.writeln('}') |
| 2399 | } |
| 2400 | |
| 2401 | mut map_names := g.map_aliases.keys() |
| 2402 | map_names.sort() |
| 2403 | for name in map_names { |
| 2404 | // Skip late-registered map aliases without a typedef (see emit_map_str_functions). |
| 2405 | if 'alias_${name}' !in g.emitted_types { |
| 2406 | continue |
| 2407 | } |
| 2408 | key_type, value_type := g.map_alias_key_value_types(name) |
| 2409 | if key_type == '' || value_type == '' { |
| 2410 | g.sb.writeln('') |
| 2411 | g.sb.writeln('bool ${name}_map_eq(${name} a, ${name} b) { return map_map_eq(a, b); }') |
| 2412 | continue |
| 2413 | } |
| 2414 | // Skip generic placeholder types |
| 2415 | if value_type.len == 1 && value_type[0] >= `A` && value_type[0] <= `Z` { |
| 2416 | g.sb.writeln('') |
| 2417 | g.sb.writeln('bool ${name}_map_eq(${name} a, ${name} b) { return map_map_eq(a, b); }') |
| 2418 | continue |
| 2419 | } |
| 2420 | g.sb.writeln('') |
| 2421 | g.sb.writeln('bool ${name}_map_eq(${name} a, ${name} b) {') |
| 2422 | g.sb.writeln('\tif (a.len != b.len) return false;') |
| 2423 | g.sb.writeln('\tfor (int i = 0; i < a.key_values.len; ++i) {') |
| 2424 | g.sb.writeln('\t\tif (!DenseArray__has_index(&a.key_values, i)) continue;') |
| 2425 | g.sb.writeln('\t\tvoid* k = DenseArray__key(&a.key_values, i);') |
| 2426 | g.sb.writeln('\t\tif (!map__exists(&b, k)) return false;') |
| 2427 | // Compare values based on value type |
| 2428 | if value_type == 'string' { |
| 2429 | g.sb.writeln('\t\tstring va = *(string*)map__get(&a, k, &(string){0});') |
| 2430 | g.sb.writeln('\t\tstring vb = *(string*)map__get(&b, k, &(string){0});') |
| 2431 | g.sb.writeln('\t\tif (!string__eq(va, vb)) return false;') |
| 2432 | } else if value_type in ['int', 'i8', 'i16', 'i32', 'i64', 'u8', 'u16', 'u32', 'u64', 'f32', |
| 2433 | 'f64', 'bool', 'rune', 'voidptr'] { |
| 2434 | g.sb.writeln('\t\t${value_type} va = *(${value_type}*)map__get(&a, k, &(${value_type}){0});') |
| 2435 | g.sb.writeln('\t\t${value_type} vb = *(${value_type}*)map__get(&b, k, &(${value_type}){0});') |
| 2436 | g.sb.writeln('\t\tif (va != vb) return false;') |
| 2437 | } else if value_type in ['intptr', 'u8ptr', 'charptr', 'byteptr'] |
| 2438 | || value_type.ends_with('ptr') { |
| 2439 | // Pointer-like types (V aliases and module-qualified pointer types): compare as void* |
| 2440 | g.sb.writeln('\t\tvoid* va = *(void**)map__get(&a, k, &(void*){0});') |
| 2441 | g.sb.writeln('\t\tvoid* vb = *(void**)map__get(&b, k, &(void*){0});') |
| 2442 | g.sb.writeln('\t\tif (va != vb) return false;') |
| 2443 | } else if value_type.starts_with('Array_fixed_') { |
| 2444 | // Fixed arrays: use memcmp (they are plain C arrays) |
| 2445 | g.sb.writeln('\t\tvoid* va = map__get(&a, k, &(${value_type}){0});') |
| 2446 | g.sb.writeln('\t\tvoid* vb = map__get(&b, k, &(${value_type}){0});') |
| 2447 | g.sb.writeln('\t\tif (memcmp(va, vb, sizeof(${value_type})) != 0) return false;') |
| 2448 | } else if value_type == 'array' || value_type.starts_with('Array_') { |
| 2449 | // Array values: use __v2_array_eq for deep comparison |
| 2450 | g.sb.writeln('\t\tarray va = *(array*)map__get(&a, k, &(${value_type}){0});') |
| 2451 | g.sb.writeln('\t\tarray vb = *(array*)map__get(&b, k, &(${value_type}){0});') |
| 2452 | g.sb.writeln('\t\tif (!__v2_array_eq(va, vb)) return false;') |
| 2453 | } else if value_type.starts_with('Map_') { |
| 2454 | // Map values: use the map's own eq function for deep comparison |
| 2455 | g.sb.writeln('\t\t${value_type} va = *(${value_type}*)map__get(&a, k, &(${value_type}){0});') |
| 2456 | g.sb.writeln('\t\t${value_type} vb = *(${value_type}*)map__get(&b, k, &(${value_type}){0});') |
| 2457 | g.sb.writeln('\t\tif (!${value_type}_map_eq(va, vb)) return false;') |
| 2458 | } else { |
| 2459 | // Try to look up struct fields for deep comparison |
| 2460 | struct_type := g.lookup_struct_type_by_c_name(value_type) |
| 2461 | if struct_type.fields.len > 0 { |
| 2462 | g.sb.writeln('\t\t${value_type} va = *(${value_type}*)map__get(&a, k, &(${value_type}){0});') |
| 2463 | g.sb.writeln('\t\t${value_type} vb = *(${value_type}*)map__get(&b, k, &(${value_type}){0});') |
| 2464 | g.emit_struct_field_eq(struct_type, 'va', 'vb') |
| 2465 | } else { |
| 2466 | // Fallback: use memcmp on the value |
| 2467 | g.sb.writeln('\t\tvoid* va = map__get(&a, k, &(${value_type}){0});') |
| 2468 | g.sb.writeln('\t\tvoid* vb = map__get(&b, k, &(${value_type}){0});') |
| 2469 | g.sb.writeln('\t\tif (memcmp(va, vb, sizeof(${value_type})) != 0) return false;') |
| 2470 | } |
| 2471 | } |
| 2472 | g.sb.writeln('\t}') |
| 2473 | g.sb.writeln('\treturn true;') |
| 2474 | g.sb.writeln('}') |
| 2475 | } |
| 2476 | } |
| 2477 | |
| 2478 | // lookup_struct_type_by_c_name resolves a C type name to a types.Struct by searching |
| 2479 | // all known module scopes. C names may be like "MValue" (current module) or "os__File". |
| 2480 | // Returns empty Struct for sum types/aliases (their merged fields aren't safe for field access). |
| 2481 | fn (mut g Gen) lookup_struct_type_by_c_name(c_name string) types.Struct { |
| 2482 | if g.env == unsafe { nil } { |
| 2483 | return types.Struct{} |
| 2484 | } |
| 2485 | cache_key := '${g.cur_module}|${c_name}' |
| 2486 | if cached := g.struct_type_lookup_cache[cache_key] { |
| 2487 | return cached |
| 2488 | } |
| 2489 | if cache_key in g.struct_type_lookup_miss { |
| 2490 | return types.Struct{} |
| 2491 | } |
| 2492 | // Try extracting module from mangled name (e.g. "os__File" -> module "os", name "File") |
| 2493 | mut mod_name := '' |
| 2494 | mut struct_name := c_name |
| 2495 | if idx := c_name.index('__') { |
| 2496 | mod_name = c_name[..idx] |
| 2497 | struct_name = c_name[idx + 2..] |
| 2498 | } |
| 2499 | if mod_name != '' { |
| 2500 | if mut scope := g.env_scope(mod_name) { |
| 2501 | if obj := scope.lookup_parent(struct_name, 0) { |
| 2502 | typ := obj.typ() |
| 2503 | // Skip sum types - their merged fields aren't safe for direct access |
| 2504 | if typ is types.Alias || typ is types.SumType { |
| 2505 | g.struct_type_lookup_miss[cache_key] = true |
| 2506 | return types.Struct{} |
| 2507 | } |
| 2508 | if typ is types.Struct { |
| 2509 | g.struct_type_lookup_cache[cache_key] = typ |
| 2510 | return typ |
| 2511 | } |
| 2512 | } |
| 2513 | } |
| 2514 | } |
| 2515 | // Bare C type names belong to main/builtin. Non-main module types are |
| 2516 | // emitted with a module prefix, so do not let the current module shadow them. |
| 2517 | mut tried := map[string]bool{} |
| 2518 | cur_mod := if g.cur_module != '' { g.cur_module } else { 'main' } |
| 2519 | mut modules_to_try := []string{} |
| 2520 | if mod_name == '' && cur_mod != 'main' && cur_mod != 'builtin' { |
| 2521 | modules_to_try << 'main' |
| 2522 | modules_to_try << 'builtin' |
| 2523 | } |
| 2524 | modules_to_try << cur_mod |
| 2525 | if 'main' !in modules_to_try { |
| 2526 | modules_to_try << 'main' |
| 2527 | } |
| 2528 | if 'builtin' !in modules_to_try { |
| 2529 | modules_to_try << 'builtin' |
| 2530 | } |
| 2531 | for try_mod in modules_to_try { |
| 2532 | if tried[try_mod] { |
| 2533 | continue |
| 2534 | } |
| 2535 | tried[try_mod] = true |
| 2536 | if mut scope := g.env_scope(try_mod) { |
| 2537 | if obj := scope.lookup_parent(struct_name, 0) { |
| 2538 | typ := obj.typ() |
| 2539 | if typ is types.Alias { |
| 2540 | g.struct_type_lookup_miss[cache_key] = true |
| 2541 | return types.Struct{} |
| 2542 | } |
| 2543 | if typ is types.Struct { |
| 2544 | g.struct_type_lookup_cache[cache_key] = typ |
| 2545 | return typ |
| 2546 | } |
| 2547 | } |
| 2548 | } |
| 2549 | } |
| 2550 | // Try all module scopes from files |
| 2551 | if g.has_flat() { |
| 2552 | for i in 0 .. g.flat.files.len { |
| 2553 | file_mod := flat_file_module_name(g.flat.file_cursor(i)) |
| 2554 | if tried[file_mod] { |
| 2555 | continue |
| 2556 | } |
| 2557 | tried[file_mod] = true |
| 2558 | if mut scope := g.env_scope(file_mod) { |
| 2559 | if obj := scope.lookup_parent(struct_name, 0) { |
| 2560 | typ := obj.typ() |
| 2561 | if typ is types.Alias { |
| 2562 | g.struct_type_lookup_miss[cache_key] = true |
| 2563 | return types.Struct{} |
| 2564 | } |
| 2565 | if typ is types.Struct { |
| 2566 | g.struct_type_lookup_cache[cache_key] = typ |
| 2567 | return typ |
| 2568 | } |
| 2569 | } |
| 2570 | } |
| 2571 | } |
| 2572 | } else { |
| 2573 | for file in g.files { |
| 2574 | file_mod := file.mod |
| 2575 | if tried[file_mod] { |
| 2576 | continue |
| 2577 | } |
| 2578 | tried[file_mod] = true |
| 2579 | if mut scope := g.env_scope(file_mod) { |
| 2580 | if obj := scope.lookup_parent(struct_name, 0) { |
| 2581 | typ := obj.typ() |
| 2582 | if typ is types.Alias { |
| 2583 | g.struct_type_lookup_miss[cache_key] = true |
| 2584 | return types.Struct{} |
| 2585 | } |
| 2586 | if typ is types.Struct { |
| 2587 | g.struct_type_lookup_cache[cache_key] = typ |
| 2588 | return typ |
| 2589 | } |
| 2590 | } |
| 2591 | } |
| 2592 | } |
| 2593 | } |
| 2594 | g.struct_type_lookup_miss[cache_key] = true |
| 2595 | return types.Struct{} |
| 2596 | } |
| 2597 | |
| 2598 | // emit_struct_field_eq generates field-by-field comparison code for a struct type. |
| 2599 | fn (mut g Gen) emit_struct_field_eq(s types.Struct, va string, vb string) { |
| 2600 | for field in s.fields { |
| 2601 | fname := escape_c_keyword(field.name) |
| 2602 | ftype := field.typ |
| 2603 | match ftype { |
| 2604 | types.String { |
| 2605 | g.sb.writeln('\t\tif (!string__eq(${va}.${fname}, ${vb}.${fname})) return false;') |
| 2606 | } |
| 2607 | types.Map { |
| 2608 | c_type := g.types_type_to_c(ftype) |
| 2609 | if c_type.starts_with('Map_') { |
| 2610 | g.sb.writeln('\t\tif (!${c_type}_map_eq(${va}.${fname}, ${vb}.${fname})) return false;') |
| 2611 | } else { |
| 2612 | g.sb.writeln('\t\tif (!map_map_eq(${va}.${fname}, ${vb}.${fname})) return false;') |
| 2613 | } |
| 2614 | } |
| 2615 | types.Array { |
| 2616 | g.sb.writeln('\t\tif (!__v2_array_eq(${va}.${fname}, ${vb}.${fname})) return false;') |
| 2617 | } |
| 2618 | types.Primitive { |
| 2619 | g.sb.writeln('\t\tif (${va}.${fname} != ${vb}.${fname}) return false;') |
| 2620 | } |
| 2621 | types.Pointer { |
| 2622 | g.sb.writeln('\t\tif (${va}.${fname} != ${vb}.${fname}) return false;') |
| 2623 | } |
| 2624 | types.Rune { |
| 2625 | g.sb.writeln('\t\tif (${va}.${fname} != ${vb}.${fname}) return false;') |
| 2626 | } |
| 2627 | types.Enum { |
| 2628 | g.sb.writeln('\t\tif (${va}.${fname} != ${vb}.${fname}) return false;') |
| 2629 | } |
| 2630 | types.Struct { |
| 2631 | c_type := g.types_type_to_c(ftype) |
| 2632 | if g.struct_has_ref_fields(ftype) { |
| 2633 | g.emit_struct_field_eq(ftype, '${va}.${fname}', '${vb}.${fname}') |
| 2634 | } else { |
| 2635 | g.sb.writeln('\t\tif (memcmp(&${va}.${fname}, &${vb}.${fname}, sizeof(${c_type})) != 0) return false;') |
| 2636 | } |
| 2637 | } |
| 2638 | else { |
| 2639 | c_type := g.types_type_to_c(ftype) |
| 2640 | g.sb.writeln('\t\tif (memcmp(&${va}.${fname}, &${vb}.${fname}, sizeof(${c_type})) != 0) return false;') |
| 2641 | } |
| 2642 | } |
| 2643 | } |
| 2644 | } |
| 2645 | |
| 2646 | fn (mut g Gen) map_alias_key_value_types(name string) (string, string) { |
| 2647 | if info := g.ensure_map_type_info(name) { |
| 2648 | if info.key_c_type != '' && info.value_c_type != '' { |
| 2649 | return info.key_c_type, info.value_c_type |
| 2650 | } |
| 2651 | } |
| 2652 | without_prefix := name.all_after('Map_') |
| 2653 | return g.parse_map_kv_types(without_prefix) |
| 2654 | } |
| 2655 | |
| 2656 | // parse_map_kv_types parses key and value types from a "key_value" string. |
| 2657 | fn (g &Gen) parse_map_kv_types(kv_str string) (string, string) { |
| 2658 | // Try simple primitive types first (they're unambiguous since they don't |
| 2659 | // contain underscores — except for the separator underscore) |
| 2660 | simple_types := ['string', 'int', 'i8', 'i16', 'i32', 'i64', 'u8', 'u16', 'u32', 'u64', 'f32', |
| 2661 | 'f64', 'bool', 'rune', 'voidptr', 'charptr', 'byteptr'] |
| 2662 | for st in simple_types { |
| 2663 | if kv_str.starts_with('${st}_') { |
| 2664 | return st, kv_str.all_after('${st}_') |
| 2665 | } |
| 2666 | } |
| 2667 | // For compound key types: try each underscore position and check if the |
| 2668 | // key type is a known type alias/struct. Keep the longest match: generic |
| 2669 | // struct names can have their base type emitted too, e.g. |
| 2670 | // `ApiResponse_T_Array_FileInfo_Array_int`. |
| 2671 | mut best_key := '' |
| 2672 | mut best_value := '' |
| 2673 | for i := 1; i < kv_str.len; i++ { |
| 2674 | if kv_str[i] == `_` { |
| 2675 | key := kv_str[..i] |
| 2676 | value := kv_str[i + 1..] |
| 2677 | if value == '' { |
| 2678 | continue |
| 2679 | } |
| 2680 | // Verify key is a known type |
| 2681 | if g.is_known_map_key_type(key, simple_types) { |
| 2682 | best_key = key |
| 2683 | best_value = value |
| 2684 | } |
| 2685 | } |
| 2686 | } |
| 2687 | if best_key != '' { |
| 2688 | return best_key, best_value |
| 2689 | } |
| 2690 | // Last resort: split on last underscore |
| 2691 | last_idx := kv_str.last_index('_') or { return '', '' } |
| 2692 | if last_idx > 0 && last_idx < kv_str.len - 1 { |
| 2693 | return kv_str[..last_idx], kv_str[last_idx + 1..] |
| 2694 | } |
| 2695 | return '', '' |
| 2696 | } |
| 2697 | |
| 2698 | fn (g &Gen) is_known_map_key_type(key string, simple_types []string) bool { |
| 2699 | if key.starts_with('Array_fixed_') { |
| 2700 | _, arr_len := g.parse_fixed_array_type(key) |
| 2701 | return arr_len > 0 |
| 2702 | } |
| 2703 | return key in g.map_aliases || key in g.array_aliases || key in g.emitted_types |
| 2704 | || 'body_${key}' in g.emitted_types || 'alias_${key}' in g.emitted_types |
| 2705 | || 'enum_${key}' in g.emitted_types || key in simple_types |
| 2706 | } |
| 2707 | |