| 1 | module c |
| 2 | |
| 3 | import strings |
| 4 | |
| 5 | // c_reserved_words is a set (not a list) so `name in c_reserved_words` is an O(1) hash |
| 6 | // lookup. c_name() runs on every emitted identifier, so a linear scan here is costly. |
| 7 | const c_reserved_words = { |
| 8 | 'auto': true |
| 9 | 'break': true |
| 10 | 'case': true |
| 11 | 'char': true |
| 12 | 'const': true |
| 13 | 'continue': true |
| 14 | 'copy': true |
| 15 | 'default': true |
| 16 | 'do': true |
| 17 | 'double': true |
| 18 | 'else': true |
| 19 | 'enum': true |
| 20 | 'extern': true |
| 21 | 'float': true |
| 22 | 'for': true |
| 23 | 'goto': true |
| 24 | 'if': true |
| 25 | 'inline': true |
| 26 | 'int': true |
| 27 | 'long': true |
| 28 | 'register': true |
| 29 | 'restrict': true |
| 30 | 'return': true |
| 31 | 'short': true |
| 32 | 'signed': true |
| 33 | 'sizeof': true |
| 34 | 'static': true |
| 35 | 'struct': true |
| 36 | 'switch': true |
| 37 | 'typedef': true |
| 38 | 'union': true |
| 39 | 'unsigned': true |
| 40 | 'void': true |
| 41 | 'volatile': true |
| 42 | 'while': true |
| 43 | 'unix': true |
| 44 | } |
| 45 | |
| 46 | // c_libc_collisions are libc function names that are not C keywords but clash at |
| 47 | // link/declaration time when a user defines a plain (module `main`) function with |
| 48 | // the same name (e.g. `fn rint(...)` vs libc's `double rint(double)`). They are |
| 49 | // mangled to `v_<name>` consistently at definition and call sites. `C.<name>` |
| 50 | // calls are unaffected (the `C.` prefix is stripped before this check). |
| 51 | const c_libc_collisions = { |
| 52 | 'rint': true |
| 53 | 'y0': true |
| 54 | 'y1': true |
| 55 | 'yn': true |
| 56 | 'j0': true |
| 57 | 'j1': true |
| 58 | 'jn': true |
| 59 | 'drem': true |
| 60 | 'scalb': true |
| 61 | } |
| 62 | |
| 63 | // c_name converts c name data for c. |
| 64 | fn c_name(name string) string { |
| 65 | if name.starts_with('C.') { |
| 66 | return name[2..] |
| 67 | } |
| 68 | if name == 'malloc' { |
| 69 | return 'v_malloc' |
| 70 | } |
| 71 | if name == 'int_str' { |
| 72 | return 'int__str' |
| 73 | } |
| 74 | // The V builtin `exit` wraps `C.exit`; both lower to the C symbol `exit`. |
| 75 | // Rename the V function (and its call sites) to `v_exit` so its body's |
| 76 | // `C.exit(code)` call resolves to libc `exit` instead of recursing forever. |
| 77 | // `C.exit` itself is handled by the `C.` strip above, so it stays `exit`. |
| 78 | if name == 'exit' { |
| 79 | return 'v_exit' |
| 80 | } |
| 81 | if c_name_is_plain(name) { |
| 82 | if name in c_reserved_words || name in c_libc_collisions { |
| 83 | return 'v_${name}' |
| 84 | } |
| 85 | return name |
| 86 | } |
| 87 | n := c_name_sanitize(name) |
| 88 | if n in c_reserved_words || n in c_libc_collisions { |
| 89 | return 'v_${n}' |
| 90 | } |
| 91 | return n |
| 92 | } |
| 93 | |
| 94 | // c_name_sanitize converts c name sanitize data for c. |
| 95 | fn c_name_sanitize(name string) string { |
| 96 | mut b := strings.new_builder(name.len + 8) |
| 97 | mut i := 0 |
| 98 | for i < name.len { |
| 99 | c := name[i] |
| 100 | if c == `[` { |
| 101 | if i + 1 < name.len && name[i + 1] == `]` { |
| 102 | b.write_string('Array_') |
| 103 | i += 2 |
| 104 | continue |
| 105 | } |
| 106 | b.write_u8(`_`) |
| 107 | } else if c == `]` { |
| 108 | i++ |
| 109 | continue |
| 110 | } else if c == `.` { |
| 111 | if i + 1 < name.len { |
| 112 | next := name[i + 1] |
| 113 | if next == `-` { |
| 114 | b.write_string('__minus') |
| 115 | i += 2 |
| 116 | continue |
| 117 | } |
| 118 | if next == `+` { |
| 119 | b.write_string('__plus') |
| 120 | i += 2 |
| 121 | continue |
| 122 | } |
| 123 | if next == `*` { |
| 124 | b.write_string('__mul') |
| 125 | i += 2 |
| 126 | continue |
| 127 | } |
| 128 | if next == `/` { |
| 129 | b.write_string('__div') |
| 130 | i += 2 |
| 131 | continue |
| 132 | } |
| 133 | if next == `%` { |
| 134 | b.write_string('__mod') |
| 135 | i += 2 |
| 136 | continue |
| 137 | } |
| 138 | if next == `&` { |
| 139 | b.write_string('__and') |
| 140 | i += 2 |
| 141 | continue |
| 142 | } |
| 143 | if next == `|` { |
| 144 | b.write_string('__or') |
| 145 | i += 2 |
| 146 | continue |
| 147 | } |
| 148 | if next == `^` { |
| 149 | b.write_string('__xor') |
| 150 | i += 2 |
| 151 | continue |
| 152 | } |
| 153 | if i + 2 < name.len { |
| 154 | op := name[i + 2] |
| 155 | if next == `=` && op == `=` { |
| 156 | b.write_string('__eq') |
| 157 | i += 3 |
| 158 | continue |
| 159 | } |
| 160 | if next == `!` && op == `=` { |
| 161 | b.write_string('__ne') |
| 162 | i += 3 |
| 163 | continue |
| 164 | } |
| 165 | if next == `<` && op == `=` { |
| 166 | b.write_string('__le') |
| 167 | i += 3 |
| 168 | continue |
| 169 | } |
| 170 | if next == `>` && op == `=` { |
| 171 | b.write_string('__ge') |
| 172 | i += 3 |
| 173 | continue |
| 174 | } |
| 175 | if next == `<` && op == `<` { |
| 176 | b.write_string('__left_shift') |
| 177 | i += 3 |
| 178 | continue |
| 179 | } |
| 180 | if next == `>` && op == `>` { |
| 181 | b.write_string('__right_shift') |
| 182 | i += 3 |
| 183 | continue |
| 184 | } |
| 185 | } |
| 186 | if next == `<` { |
| 187 | b.write_string('__lt') |
| 188 | i += 2 |
| 189 | continue |
| 190 | } |
| 191 | if next == `>` { |
| 192 | b.write_string('__gt') |
| 193 | i += 2 |
| 194 | continue |
| 195 | } |
| 196 | } |
| 197 | b.write_string('__') |
| 198 | } else if c == `&` { |
| 199 | b.write_string('ptr') |
| 200 | } else if c == `@` { |
| 201 | i++ |
| 202 | continue |
| 203 | } else if c == `,` || c == ` ` { |
| 204 | b.write_u8(`_`) |
| 205 | } else { |
| 206 | b.write_u8(c) |
| 207 | } |
| 208 | i++ |
| 209 | } |
| 210 | return b.str() |
| 211 | } |
| 212 | |
| 213 | // c_name_is_plain converts c name is plain data for c. |
| 214 | fn c_name_is_plain(name string) bool { |
| 215 | for i in 0 .. name.len { |
| 216 | c := name[i] |
| 217 | if (c >= `a` && c <= `z`) || (c >= `A` && c <= `Z`) || (c >= `0` && c <= `9`) || c == `_` { |
| 218 | continue |
| 219 | } |
| 220 | return false |
| 221 | } |
| 222 | return true |
| 223 | } |
| 224 | |
| 225 | fn c_local_name(name string) string { |
| 226 | local_name := if name.contains('.') { name.all_after_last('.') } else { name } |
| 227 | return c_name(local_name) |
| 228 | } |
| 229 | |
| 230 | // c_escape supports c escape handling for c. |
| 231 | fn c_escape(s string) string { |
| 232 | return s.replace('\\', '\\\\').replace('"', '\\"').replace('\n', '\\n').replace('\t', '\\t').replace('\r', |
| 233 | '\\r') |
| 234 | } |
| 235 | |