v / vlib / v2 / gen / cleanc / cheaders.v
2706 lines · 2579 sloc · 91.66 KB · 3690f882f624c3d82b8c36d38434baf10f68ce4c
Raw
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
5module cleanc
6
7import v2.ast
8import v2.parser
9import v2.pref as vpref
10import v2.token
11import v2.types
12import os
13
14const 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
24const 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
43const 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
56const 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
101extern 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
106const 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
126const 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
149const 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
185extern 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
201const 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
208const apple_minimal_includes_plain = r'#include <mach/mach.h>
209#include <mach/task.h>
210#include <mach/mach_time.h>
211'
212
213const 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
223const 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
231const 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
237const apple_late_full_includes_plain = r'#include <sys/ptrace.h>
238#include <libproc.h>
239'
240
241const linux_gettid_feature_define = r'#ifndef _GNU_SOURCE
242#define _GNU_SOURCE
243#endif
244'
245
246const linux_gettid_helper = r'#include <sys/syscall.h>
247static inline uint32_t v_cleanc_gettid(void) {
248 return (uint32_t)syscall(SYS_gettid);
249}
250'
251
252const apple_macos_cross_guard = 'defined(__APPLE__) && defined(__MACH__) && defined(__ENVIRONMENT_MAC_OS_X_VERSION_MIN_REQUIRED__)'
253const apple_ios_cross_guard = 'defined(__APPLE__) && defined(__MACH__) && defined(__ENVIRONMENT_IPHONE_OS_VERSION_MIN_REQUIRED__)'
254const freestanding_missing_alloc_hook_message = 'v2: freestanding target requires freestanding_alloc hook for heap allocation'
255const freestanding_missing_format_hook_message = 'v2: freestanding target cannot print non-string values without formatting support'
256const freestanding_missing_heap_runtime_message = 'v2: freestanding target cannot use V runtime heap helpers with --skip-builtin'
257const freestanding_missing_output_hook_message = 'v2: freestanding target requires freestanding_output hook for output'
258const freestanding_missing_panic_hook_message = 'v2: freestanding target requires freestanding_panic hook for panic'
259
260fn (g &Gen) target_os_name() string {
261 return normalize_c_target_os_name(g.pref.target_os_or_host())
262}
263
264fn 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
271fn (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
279fn (g &Gen) is_freestanding_target() bool {
280 return g.pref != unsafe { nil } && g.pref.is_freestanding()
281}
282
283fn (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
290fn (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
296fn (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
306fn (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
316fn (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
326fn (g &Gen) c_freestanding_missing_alloc_ptr_expr() string {
327 return '({ _Static_assert(0, "${freestanding_missing_alloc_hook_message}"); (void*)0; })'
328}
329
330fn (g &Gen) c_freestanding_missing_alloc_void_expr() string {
331 return '({ _Static_assert(0, "${freestanding_missing_alloc_hook_message}"); (void)0; })'
332}
333
334fn (g &Gen) c_freestanding_missing_format_string_expr() string {
335 return '({ _Static_assert(0, "${freestanding_missing_format_hook_message}"); (string){0}; })'
336}
337
338fn 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
361fn 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
417fn 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
429fn (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
470fn 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
483fn 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
496fn 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
506fn (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
550fn (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
555fn (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
565fn (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
584fn (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
614fn 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
633fn 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
654fn 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
664fn 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
705fn 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
754fn (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
761fn 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
776fn 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
815fn (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
846fn (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
888fn (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
893fn (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
898fn (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
903fn (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
936fn (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
941fn (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
968fn (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
973fn (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
978fn (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
997fn (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
1023fn (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
1028fn (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
1038fn (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
1052fn (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
1099fn (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
1152fn 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
1160fn (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
1206fn (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
1250fn (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
1271fn 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
1289fn 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
1307fn 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
1314fn 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
1321fn 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
1329fn 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
1340fn c_define_enables_linked_symbols(value string) bool {
1341 upper_value := value.trim_space().to_upper()
1342 return upper_value.contains('IMPLEMENTATION')
1343}
1344
1345fn (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
1349fn (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
1354fn (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
1438fn (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
1448fn (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
1467fn (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
1476fn (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
1503fn (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
1545fn (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
1551fn (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
1560fn (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
1569fn (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
1578fn (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
1587fn (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
1604fn (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
1740fn (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
1758fn (g &Gen) use_minimal_preamble() bool {
1759 return g.emit_modules.len == 1 && 'main' in g.emit_modules
1760}
1761
1762fn (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
1831fn (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.
1852fn (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
1870fn (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
1877fn (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
1891fn (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
1911fn (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
1996fn (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
2025fn (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
2053fn (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
2085fn (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
2119fn (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.
2182fn (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
2208fn (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.
2220fn (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
2289fn (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
2319fn (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).
2358fn (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, ...]".
2369fn (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.
2381fn (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).
2481fn (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.
2599fn (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
2646fn (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.
2657fn (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
2698fn (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