cluaupp 1.5.2 → 2.0.0-beta.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (210) hide show
  1. package/CHANGELOG.md +8 -0
  2. package/README.md +29 -41
  3. package/api/generated/bench-latest.json +43 -21
  4. package/api/generated/lsp-index.json +1 -1
  5. package/api/manifests/roblox-target.manifest.json +5 -5
  6. package/api/normalized/roblox-target.profile.json +8729 -267
  7. package/api/roblox/luau-builtins.d.luau +185 -0
  8. package/api/roblox/surface.json +1 -0
  9. package/api/roblox-api.lock.json +11 -2
  10. package/bin/cluaupp.js +38 -0
  11. package/editors/vscode/README.md +1 -1
  12. package/editors/vscode/package.json +50 -15
  13. package/editors/vscode/snippets/flare.json +1 -1
  14. package/editors/vscode/snippets/hive.json +1 -1
  15. package/editors/vscode/syntaxes/clpp.tmLanguage.json +211 -0
  16. package/examples/README.md +9 -9
  17. package/examples/game/cluaupp.config.json +2 -2
  18. package/examples/game/default.project.json +4 -15
  19. package/examples/game/out/ReplicatedStorage/Shared/GameInfo.luau +8 -0
  20. package/examples/game/out/ReplicatedStorage/Shared/GameInfo.luau.map.json +13 -0
  21. package/examples/game/out/ServerScriptService/Leaderstats.server.luau +26 -0
  22. package/examples/game/out/ServerScriptService/Leaderstats.server.luau.map.json +13 -0
  23. package/examples/game/out/ServerScriptService/Main.server.luau +15 -0
  24. package/examples/game/out/ServerScriptService/Main.server.luau.map.json +13 -0
  25. package/examples/game/out/StarterPlayer/StarterPlayerScripts/Main.client.luau +10 -0
  26. package/examples/game/out/StarterPlayer/StarterPlayerScripts/Main.client.luau.map.json +13 -0
  27. package/examples/game/src/ReplicatedStorage/Shared/GameInfo.clp +1 -0
  28. package/examples/game/src/ServerScriptService/Leaderstats.server.clp +18 -0
  29. package/examples/game/src/ServerScriptService/Main.server.clp +9 -0
  30. package/examples/game/src/StarterPlayer/StarterPlayerScripts/Main.client.clp +4 -0
  31. package/generated/api/build-profile.js +1 -1
  32. package/generated/api/loader.js +2 -2
  33. package/generated/api/surface/build.js +328 -0
  34. package/generated/api/surface/luau-defs.js +595 -0
  35. package/generated/api/surface/model.js +8 -0
  36. package/generated/api-cli.js +2 -2
  37. package/generated/cli.js +18 -17
  38. package/generated/clpp/contract.js +6 -3
  39. package/generated/clpp/handshake.js +36 -0
  40. package/generated/clpp/modules.js +20 -9
  41. package/generated/clpp/runner.js +62 -31
  42. package/generated/compile.js +18 -1
  43. package/generated/compiler/ast.js +3 -0
  44. package/generated/compiler/checker/calls.js +999 -0
  45. package/generated/compiler/checker/checker.js +1215 -0
  46. package/generated/compiler/checker/exprs.js +791 -0
  47. package/generated/compiler/checker/flow.js +168 -0
  48. package/generated/compiler/checker/members.js +763 -0
  49. package/generated/compiler/checker/statements.js +792 -0
  50. package/generated/compiler/checker/symbols.js +90 -0
  51. package/generated/compiler/checker/typeops.js +348 -0
  52. package/generated/compiler/checker/yields.js +115 -0
  53. package/generated/compiler/diagnostics.js +138 -0
  54. package/generated/compiler/emit/emitter.js +2356 -0
  55. package/generated/compiler/emit/luau-types.js +137 -0
  56. package/generated/compiler/emit/names.js +130 -0
  57. package/generated/compiler/emit/runtime.js +432 -0
  58. package/generated/compiler/lexer.js +403 -0
  59. package/generated/compiler/parser.js +1881 -0
  60. package/generated/compiler/project.js +332 -0
  61. package/generated/compiler/roblox/api.js +388 -0
  62. package/generated/compiler/roblox/context.js +100 -0
  63. package/generated/compiler/source.js +59 -0
  64. package/generated/compiler/stdlib.js +232 -0
  65. package/generated/compiler/types.js +250 -0
  66. package/generated/intellisense.js +1 -15
  67. package/generated/lsp.js +1 -1
  68. package/generated/target/build-cache.js +4 -4
  69. package/generated/target/datamodel-generate.js +12 -0
  70. package/generated/target/gen-rojo-tree.js +15 -0
  71. package/generated/target/source-map.js +61 -8
  72. package/generated/target/studio-bridge.js +1 -0
  73. package/generated/utils/project.js +104 -31
  74. package/include/README.md +5 -0
  75. package/native/bin/.gitkeep +1 -0
  76. package/package.json +17 -6
  77. package/plugins/CluauppNav/Plugin.luau +28 -4
  78. package/rust/cluaupp/Cargo.lock +795 -0
  79. package/rust/cluaupp/Cargo.toml +17 -0
  80. package/rust/cluaupp/src/main.rs +717 -0
  81. package/scripts/postinstall-native.js +56 -0
  82. package/skills/README.md +3 -3
  83. package/skills/clpp-hive-flare/SKILL.md +30 -26
  84. package/skills/clpp-language/SKILL.md +67 -74
  85. package/skills/clpp-language/compiler.md +24 -36
  86. package/skills/clpp-style/SKILL.md +31 -44
  87. package/src/api/build-profile.ts +1 -1
  88. package/src/api/loader.ts +2 -2
  89. package/src/api/surface/build.ts +374 -0
  90. package/src/api/surface/luau-defs.ts +635 -0
  91. package/src/api/surface/model.ts +96 -0
  92. package/src/api-cli.ts +2 -2
  93. package/src/cli.ts +19 -18
  94. package/src/clpp/contract.ts +20 -3
  95. package/src/clpp/handshake.ts +25 -0
  96. package/src/clpp/modules.ts +17 -10
  97. package/src/clpp/runner.ts +52 -34
  98. package/src/compile.ts +20 -2
  99. package/src/compiler/ast.ts +336 -0
  100. package/src/compiler/checker/calls.ts +1025 -0
  101. package/src/compiler/checker/checker.ts +1352 -0
  102. package/src/compiler/checker/exprs.ts +805 -0
  103. package/src/compiler/checker/flow.ts +174 -0
  104. package/src/compiler/checker/members.ts +793 -0
  105. package/src/compiler/checker/statements.ts +802 -0
  106. package/src/compiler/checker/symbols.ts +219 -0
  107. package/src/compiler/checker/typeops.ts +355 -0
  108. package/src/compiler/checker/yields.ts +120 -0
  109. package/src/compiler/diagnostics.ts +163 -0
  110. package/src/compiler/emit/emitter.ts +2486 -0
  111. package/src/compiler/emit/luau-types.ts +143 -0
  112. package/src/compiler/emit/names.ts +130 -0
  113. package/src/compiler/emit/runtime.ts +438 -0
  114. package/src/compiler/lexer.ts +444 -0
  115. package/src/compiler/parser.ts +1943 -0
  116. package/src/compiler/project.ts +354 -0
  117. package/src/compiler/roblox/api.ts +408 -0
  118. package/src/compiler/roblox/context.ts +108 -0
  119. package/src/compiler/source.ts +67 -0
  120. package/src/compiler/stdlib.ts +256 -0
  121. package/src/compiler/types.ts +313 -0
  122. package/src/intellisense.ts +1 -13
  123. package/src/lsp.ts +1 -1
  124. package/src/target/build-cache.ts +5 -8
  125. package/src/target/datamodel-generate.ts +12 -1
  126. package/src/target/gen-rojo-tree.ts +15 -0
  127. package/src/target/source-map.ts +65 -27
  128. package/src/target/studio-bridge.ts +3 -0
  129. package/src/types.ts +3 -0
  130. package/src/utils/project.ts +99 -37
  131. package/templates/README.md +7 -0
  132. package/templates/{game → game-modern}/cluaupp.config.json +2 -2
  133. package/templates/{game → game-modern}/package.json +1 -2
  134. package/templates/game-modern/rokit.toml +2 -0
  135. package/templates/game-modern/src/ReplicatedStorage/Shared/GameInfo.clp +1 -0
  136. package/templates/game-modern/src/ServerScriptService/Handlers/Guard.server.clp +4 -0
  137. package/templates/game-modern/src/ServerScriptService/Main.server.clp +8 -0
  138. package/templates/game-modern/src/StarterPlayer/StarterPlayerScripts/Main.client.clp +4 -0
  139. package/examples/game/out/ReplicatedStorage/Shared/Constants/Datas/TemplateData.luau +0 -29
  140. package/examples/game/out/ReplicatedStorage/Shared/Constants/Datas/TemplateData.luau.map.json +0 -13
  141. package/examples/game/out/ReplicatedStorage/Shared/Net/Net.luau +0 -83
  142. package/examples/game/out/ReplicatedStorage/Shared/Net/Net.luau.map.json +0 -13
  143. package/examples/game/out/ServerScriptService/Boot/DataBoot.server.luau +0 -18
  144. package/examples/game/out/ServerScriptService/Boot/DataBoot.server.luau.map.json +0 -13
  145. package/examples/game/out/ServerScriptService/Configuration/ServerConfig.luau +0 -14
  146. package/examples/game/out/ServerScriptService/Configuration/ServerConfig.luau.map.json +0 -13
  147. package/examples/game/out/ServerScriptService/Handlers/Guard.server.luau +0 -15
  148. package/examples/game/out/ServerScriptService/Handlers/Guard.server.luau.map.json +0 -13
  149. package/examples/game/out/ServerScriptService/Handlers/PlayerHandler.server.luau +0 -50
  150. package/examples/game/out/ServerScriptService/Handlers/PlayerHandler.server.luau.map.json +0 -13
  151. package/examples/game/out/StarterPlayer/StarterCharacterScripts/Character/CharacterReady.client.luau +0 -12
  152. package/examples/game/out/StarterPlayer/StarterCharacterScripts/Character/CharacterReady.client.luau.map.json +0 -13
  153. package/examples/game/out/StarterPlayer/StarterPlayerScripts/Controllers/DataController.client.luau +0 -16
  154. package/examples/game/out/StarterPlayer/StarterPlayerScripts/Controllers/DataController.client.luau.map.json +0 -13
  155. package/examples/game/src/Client/Controllers/DataController.client.clpp +0 -7
  156. package/examples/game/src/Client/Main.client.clpp +0 -4
  157. package/examples/game/src/Declarations/Net.flare +0 -5
  158. package/examples/game/src/Include/GameTypes.clh +0 -40
  159. package/examples/game/src/Modules/Core/Guard.server.clpp +0 -7
  160. package/examples/game/src/Modules/Core/PlayerHandler.server.clpp +0 -23
  161. package/examples/game/src/Server/Boot/DataBoot.server.clpp +0 -13
  162. package/examples/game/src/Server/Main.server.clpp +0 -4
  163. package/templates/game/.clangd +0 -25
  164. package/templates/game/.vscode/extensions.json +0 -8
  165. package/templates/game/.vscode/settings.json +0 -17
  166. package/templates/game/Packages/.gitkeep +0 -0
  167. package/templates/game/compile_flags.txt +0 -6
  168. package/templates/game/default.project.json +0 -41
  169. package/templates/game/include/clpp/datatypes.clh +0 -575
  170. package/templates/game/include/clpp/generated/enums.clh +0 -4920
  171. package/templates/game/include/clpp/generated/instances.clh +0 -87182
  172. package/templates/game/include/clpp/libs/arrayindexer.clh +0 -12
  173. package/templates/game/include/clpp/libs/axiom.clh +0 -154
  174. package/templates/game/include/clpp/libs/bloom.clh +0 -58
  175. package/templates/game/include/clpp/libs/coil.clh +0 -37
  176. package/templates/game/include/clpp/libs/crest.clh +0 -22
  177. package/templates/game/include/clpp/libs/echo.clh +0 -52
  178. package/templates/game/include/clpp/libs/ember.clh +0 -17
  179. package/templates/game/include/clpp/libs/flare.clh +0 -65
  180. package/templates/game/include/clpp/libs/gleam.clh +0 -111
  181. package/templates/game/include/clpp/libs/guide.clh +0 -42
  182. package/templates/game/include/clpp/libs/helm.clh +0 -31
  183. package/templates/game/include/clpp/libs/hive.clh +0 -20
  184. package/templates/game/include/clpp/libs/keep.clh +0 -158
  185. package/templates/game/include/clpp/libs/lens.clh +0 -24
  186. package/templates/game/include/clpp/libs/mint.clh +0 -28
  187. package/templates/game/include/clpp/libs/net.clh +0 -31
  188. package/templates/game/include/clpp/libs/occlude.clh +0 -15
  189. package/templates/game/include/clpp/libs/pin.clh +0 -28
  190. package/templates/game/include/clpp/libs/promise.clh +0 -24
  191. package/templates/game/include/clpp/libs/roster.clh +0 -123
  192. package/templates/game/include/clpp/libs/shift.clh +0 -22
  193. package/templates/game/include/clpp/libs/spark.clh +0 -28
  194. package/templates/game/include/clpp/libs/stage.clh +0 -23
  195. package/templates/game/include/clpp/libs/sweep.clh +0 -23
  196. package/templates/game/include/clpp/libs/trace.clh +0 -18
  197. package/templates/game/include/clpp/libs/ward.clh +0 -19
  198. package/templates/game/include/clpp/libs.clh +0 -31
  199. package/templates/game/include/clpp/roblox.clh +0 -41
  200. package/templates/game/rokit.toml +0 -5
  201. package/templates/game/src/Client/Controllers/DataController.client.clpp +0 -7
  202. package/templates/game/src/Client/Main.client.clpp +0 -4
  203. package/templates/game/src/Declarations/Net.flare +0 -5
  204. package/templates/game/src/Include/GameTypes.clh +0 -40
  205. package/templates/game/src/Modules/Core/Guard.server.clpp +0 -7
  206. package/templates/game/src/Modules/Core/PlayerHandler.server.clpp +0 -24
  207. package/templates/game/src/Server/Boot/DataBoot.server.clpp +0 -13
  208. package/templates/game/src/Server/Main.server.clpp +0 -4
  209. package/templates/game/stylua.toml +0 -9
  210. package/templates/game/wally.toml +0 -12
@@ -0,0 +1,2356 @@
1
+ "use strict";
2
+ /**
3
+ * Luau emitter. Walks the checked tree and writes idiomatic, `--!strict`-typed Luau:
4
+ * services hoisted once, `:` method calls decided by type, record-typed classes with metatables,
5
+ * numeric `for` loops, `continue`, compound assignment, `if`-expressions, string interpolation,
6
+ * and only the runtime helpers the file actually uses.
7
+ */
8
+ Object.defineProperty(exports, "__esModule", { value: true });
9
+ exports.LuauEmitter = void 0;
10
+ exports.emitLuau = emitLuau;
11
+ const stdlib_js_1 = require("../stdlib.js");
12
+ const types_js_1 = require("../types.js");
13
+ const luau_types_js_1 = require("./luau-types.js");
14
+ const names_js_1 = require("./names.js");
15
+ const runtime_js_1 = require("./runtime.js");
16
+ const P = { IF: 0, OR: 1, AND: 2, CMP: 3, CONCAT: 4, ADD: 5, MUL: 6, UNARY: 7, POW: 8, ATOM: 10 };
17
+ const atom = (code) => ({ code, prec: P.ATOM });
18
+ class LuauEmitter {
19
+ program;
20
+ file;
21
+ api;
22
+ options;
23
+ lines = [];
24
+ sourceOffset = null;
25
+ indent = 0;
26
+ helpers = new Set();
27
+ services = new Set();
28
+ names = new Map();
29
+ reserved;
30
+ tmp = 0;
31
+ loops = [];
32
+ usedLinks = new Set();
33
+ axiomRuntime = false;
34
+ sharedTable = false;
35
+ substitutions = new Map();
36
+ staticSignals = new Set();
37
+ enumTablesUsed = new Set();
38
+ fnDepth = 0;
39
+ selfName = "self";
40
+ /** Function symbols referenced so far (for forward declarations). */
41
+ referencedFuncs = new Set();
42
+ forwardFuncs = new Set();
43
+ definedFuncs = new Set();
44
+ isModule;
45
+ constructor(program, file, api, options) {
46
+ this.program = program;
47
+ this.file = file;
48
+ this.api = api;
49
+ this.options = options;
50
+ this.reserved = (0, names_js_1.reservedGlobals)(api);
51
+ for (const s of file.services) {
52
+ this.reserved.add(s);
53
+ }
54
+ this.isModule = file.kind === "module";
55
+ this.assignNames();
56
+ this.findStaticSignals();
57
+ }
58
+ // ---------- naming ----------
59
+ assignNames() {
60
+ for (const s of this.file.structs) {
61
+ s.luauName = (0, names_js_1.isReservedTypeName)(this.api, s.name) || this.reserved.has(s.name) ? `${s.name}_` : (0, names_js_1.sanitizeIdentifier)(s.name);
62
+ }
63
+ for (const e of this.file.enums) {
64
+ e.luauName = this.reserved.has(e.name) || (0, names_js_1.isReservedTypeName)(this.api, e.name) ? `${e.name}_` : (0, names_js_1.sanitizeIdentifier)(e.name);
65
+ }
66
+ }
67
+ nameOf(sym) {
68
+ const sub = this.substitutions.get(sym.id);
69
+ if (sub) {
70
+ return sub;
71
+ }
72
+ const known = this.names.get(sym.id);
73
+ if (known) {
74
+ return known;
75
+ }
76
+ let base = (0, names_js_1.sanitizeIdentifier)(sym.name);
77
+ if (sym.kind === "struct" && sym.typeId) {
78
+ const info = this.file.structRegistry.get(sym.typeId);
79
+ if (info) {
80
+ base = info.luauName;
81
+ }
82
+ }
83
+ if (names_js_1.LUAU_KEYWORDS.has(base) || this.reserved.has(base) || base.startsWith("__cl")) {
84
+ base = `${base}_`;
85
+ }
86
+ if (sym.kind === "self") {
87
+ base = this.selfName;
88
+ }
89
+ this.names.set(sym.id, base);
90
+ return base;
91
+ }
92
+ temp(prefix = "t") {
93
+ this.tmp++;
94
+ return `__${prefix}${this.tmp}`;
95
+ }
96
+ struct(id) {
97
+ const info = this.file.structRegistry.get(id);
98
+ if (!info) {
99
+ return null;
100
+ }
101
+ if (info.moduleId === this.file.moduleId) {
102
+ return info.luauName;
103
+ }
104
+ const link = this.linkFor((exp) => [...exp.structs.values()].some((s) => s.id === id));
105
+ if (!link) {
106
+ return null;
107
+ }
108
+ return `${this.linkName(link)}.${info.luauName}`;
109
+ }
110
+ enumType(id) {
111
+ const info = this.file.enumRegistry.get(id);
112
+ if (!info || !info.hasPayload) {
113
+ return null;
114
+ }
115
+ if (info.moduleId === this.file.moduleId) {
116
+ return info.luauName;
117
+ }
118
+ const link = this.linkFor((exp) => [...exp.enums.values()].some((e) => e.id === id));
119
+ return link ? `${this.linkName(link)}.${info.luauName}` : null;
120
+ }
121
+ linkFor(test) {
122
+ for (const link of this.file.links) {
123
+ const exp = link.target.kind === "clp" ? link.target.exports : link.target.kind === "luau" ? link.target.exports : undefined;
124
+ if (exp && test(exp)) {
125
+ this.usedLinks.add(link);
126
+ return link;
127
+ }
128
+ }
129
+ return null;
130
+ }
131
+ linkName(link) {
132
+ return link.sym ? this.nameOf(link.sym) : (0, names_js_1.sanitizeIdentifier)(link.decl.alias);
133
+ }
134
+ structRef(id) {
135
+ return this.struct(id) ?? "nil";
136
+ }
137
+ typeOf(t) {
138
+ return t ? (0, luau_types_js_1.luauType)(t, this) : null;
139
+ }
140
+ use(helper) {
141
+ this.helpers.add(helper);
142
+ return (0, runtime_js_1.helperFunctionName)(helper);
143
+ }
144
+ service(name) {
145
+ if (name === "Workspace") {
146
+ return "workspace";
147
+ }
148
+ this.services.add(name);
149
+ return name;
150
+ }
151
+ // ---------- writer ----------
152
+ line(text) {
153
+ const marker = text && this.sourceOffset !== null && this.options.sourcePosition ? `\u0000SRC${this.sourceOffset}\u0000` : "";
154
+ this.lines.push(text === "" ? "" : `${"\t".repeat(this.indent)}${text}${marker}`);
155
+ }
156
+ block(fn) {
157
+ this.indent++;
158
+ fn();
159
+ this.indent--;
160
+ }
161
+ /** Run fn with a fresh line buffer; returns the lines it produced. */
162
+ capture(fn) {
163
+ const saved = this.lines;
164
+ this.lines = [];
165
+ fn();
166
+ const out = this.lines;
167
+ this.lines = saved;
168
+ return out;
169
+ }
170
+ // ---------- entry ----------
171
+ emit() {
172
+ const types = this.capture(() => this.emitTypes());
173
+ const classes = this.capture(() => this.emitClassTables());
174
+ const functions = this.capture(() => this.emitFunctions());
175
+ const captured = this.file.topLevel.filter((s) => s.kind === "var" && s.captured);
176
+ const body = this.capture(() => {
177
+ for (const stmt of this.program.body) {
178
+ if (stmt.kind !== "Decl") {
179
+ this.stmt(stmt, true);
180
+ }
181
+ }
182
+ });
183
+ const footer = this.capture(() => this.emitReturn());
184
+ const out = [];
185
+ out.push(this.file.mode === "nocheck" ? "--!nocheck" : "--!strict");
186
+ out.push("--!optimize 2");
187
+ if (this.file.directives.includes("native")) {
188
+ out.push("--!native");
189
+ }
190
+ out.push(`-- Generated by Cluaupp ${this.options.version} from ${this.options.sourceLabel}. Do not edit: changes are overwritten on build.`);
191
+ const requireLines = this.capture(() => this.emitRequires());
192
+ const serviceLines = [...this.services].sort().map((s) => `local ${s} = game:GetService(${(0, names_js_1.luauString)(s)})`);
193
+ if (serviceLines.length || requireLines.length) {
194
+ out.push("", ...serviceLines);
195
+ if (requireLines.length) {
196
+ if (serviceLines.length) {
197
+ out.push("");
198
+ }
199
+ out.push(...requireLines);
200
+ }
201
+ }
202
+ const helperNames = (0, runtime_js_1.resolveHelpers)(this.helpers);
203
+ const helperTypes = helperNames.map((h) => runtime_js_1.HELPERS[h].types).filter(Boolean);
204
+ const helperCode = helperNames.map((h) => runtime_js_1.HELPERS[h].code).filter(Boolean);
205
+ if (helperTypes.length || helperCode.length) {
206
+ out.push("", ...helperTypes, ...(helperTypes.length ? [""] : []), helperCode.join("\n\n"));
207
+ }
208
+ if (types.length) {
209
+ out.push("", ...types);
210
+ }
211
+ if (this.sharedTable) {
212
+ out.push("", "local __clShared: { [string]: any } = {}");
213
+ }
214
+ if (classes.length) {
215
+ out.push("", ...classes);
216
+ }
217
+ const forward = [];
218
+ for (const s of captured) {
219
+ const t = this.typeOf(s.type);
220
+ forward.push(`local ${this.nameOf(s)}${t ? `: ${t}` : ""}`);
221
+ }
222
+ const forwardFns = this.file.topLevel.filter((s) => s.kind === "func" && this.forwardFuncs.has(s.id)).map((s) => this.nameOf(s));
223
+ if (forwardFns.length) {
224
+ forward.push(`local ${forwardFns.join(", ")}`);
225
+ }
226
+ if (forward.length) {
227
+ out.push("", ...forward);
228
+ }
229
+ const fnText = functions.map((l) => l.replace(/\u0000DECL(\d+)\u0000/g, (_m, id) => (this.forwardFuncs.has(Number(id)) ? "function" : "local function")));
230
+ if (fnText.length) {
231
+ out.push("", ...fnText);
232
+ }
233
+ if (body.length) {
234
+ out.push("", ...body);
235
+ }
236
+ if (footer.length) {
237
+ out.push("", ...footer);
238
+ }
239
+ const text = collapseBlankLines(out).join("\n").replace(/\s+$/, "");
240
+ const mappings = [];
241
+ const clean = text.split("\n").map((line, index) => line.replace(/\u0000SRC(\d+)\u0000/g, (_m, offset) => {
242
+ const pos = this.options.sourcePosition(Number(offset));
243
+ mappings.push({ luauLine: index + 1, clppLine: pos.line, clppColumn: pos.column, file: this.options.sourceLabel });
244
+ return "";
245
+ })).join("\n");
246
+ this.options.onSourceMap?.(mappings);
247
+ return clean + "\n";
248
+ }
249
+ emitRequires() {
250
+ for (const link of this.file.links) {
251
+ const target = link.target;
252
+ if (target.kind === "roblox" || target.kind === "error") {
253
+ continue;
254
+ }
255
+ if (target.kind === "std") {
256
+ if (target.module === "axiom" && this.axiomRuntime) {
257
+ this.line(`local ${this.linkName(link)} = require(${this.options.libsRoot(this.service.bind(this))}:WaitForChild("Axiom"))`);
258
+ }
259
+ continue;
260
+ }
261
+ const used = this.usedLinks.has(link);
262
+ if (!used) {
263
+ continue;
264
+ }
265
+ const expr = this.options.requireTarget(link, (name) => this.service(name));
266
+ if (expr) {
267
+ this.line(`local ${this.linkName(link)} = require(${expr})`);
268
+ }
269
+ }
270
+ }
271
+ // ---------- declarations ----------
272
+ orderedStructs() {
273
+ const own = this.file.structs;
274
+ const out = [];
275
+ const seen = new Set();
276
+ const visit = (s) => {
277
+ if (seen.has(s.id)) {
278
+ return;
279
+ }
280
+ seen.add(s.id);
281
+ for (const b of s.bases) {
282
+ const base = own.find((x) => x.id === b);
283
+ if (base) {
284
+ visit(base);
285
+ }
286
+ }
287
+ out.push(s);
288
+ };
289
+ own.forEach(visit);
290
+ return out;
291
+ }
292
+ exportKw() {
293
+ return this.isModule ? "export type" : "type";
294
+ }
295
+ emitTypes() {
296
+ for (const e of this.file.enums) {
297
+ if (!e.hasPayload) {
298
+ continue;
299
+ }
300
+ const payloads = [...new Set(e.cases.map((k) => (k.payload ? this.typeOf(k.payload) ?? "any" : null)).filter(Boolean))];
301
+ const optional = e.cases.some((k) => !k.payload);
302
+ const value = payloads.length ? (optional ? `(${payloads.join(" | ")})?` : payloads.join(" | ")) : "nil";
303
+ this.line(`${this.exportKw()} ${e.luauName} = { tag: number, value: ${value} }`);
304
+ }
305
+ for (const s of this.orderedStructs()) {
306
+ const generics = s.typeParams.length ? `<${s.typeParams.join(", ")}>` : "";
307
+ this.line(`${this.exportKw()} ${s.luauName}${generics} = {`);
308
+ this.block(() => {
309
+ for (const f of s.fields) {
310
+ this.line(`${f.name}: ${this.typeOf(f.type) ?? "any"},`);
311
+ }
312
+ for (const m of s.methods.values()) {
313
+ if (m.isStatic) {
314
+ continue;
315
+ }
316
+ this.line(`${m.name}: ${this.methodType(m)},`);
317
+ }
318
+ });
319
+ this.line("}");
320
+ }
321
+ }
322
+ ownerType(m) {
323
+ for (const s of this.file.structRegistry.values()) {
324
+ if (s.name === m.owner && s.methods.get(m.name) === m) {
325
+ const ref = this.struct(s.id) ?? s.luauName;
326
+ return s.typeParams.length ? `${ref}<${s.typeParams.join(", ")}>` : ref;
327
+ }
328
+ }
329
+ return "any";
330
+ }
331
+ methodType(m) {
332
+ const params = [`self: ${this.ownerType(m)}`];
333
+ for (const p of m.type.params) {
334
+ const t = this.typeOf(p.type) ?? "any";
335
+ params.push(p.optional && !t.endsWith("?") ? optionalType(t) : t);
336
+ }
337
+ if (m.type.variadic) {
338
+ params.push(`...${this.typeOf(m.type.variadic) ?? "any"}`);
339
+ }
340
+ const ret = this.returnType(m.type.ret, m.decl.isAsync);
341
+ return `(${params.join(", ")}) -> ${ret ?? "()"}`;
342
+ }
343
+ returnType(ret, isAsync) {
344
+ let r = (0, types_js_1.prune)(ret);
345
+ if (isAsync && r.k === "task") {
346
+ r = (0, types_js_1.prune)(r.result);
347
+ }
348
+ if (r.k === "void") {
349
+ return "()";
350
+ }
351
+ if (r.k === "infer" || r.k === "unknown") {
352
+ return null;
353
+ }
354
+ return this.typeOf(r);
355
+ }
356
+ emitClassTables() {
357
+ for (const e of this.file.enums) {
358
+ if (this.isModule || this.enumTablesUsed.has(e.id)) {
359
+ const cases = e.cases.map((k, i) => `${k.name} = ${i}`).join(", ");
360
+ this.line(`local ${e.luauName} = table.freeze({ ${cases} })`);
361
+ }
362
+ }
363
+ for (const s of this.orderedStructs()) {
364
+ this.line(`local ${s.luauName} = {}`);
365
+ this.line(`${s.luauName}.__index = ${s.luauName}`);
366
+ this.line(`${s.luauName}.__fields = table.freeze({ ${s.fields.map((f) => (0, names_js_1.luauString)(f.name)).join(", ")} })`);
367
+ }
368
+ for (const stmt of this.program.body) {
369
+ if (stmt.kind === "Decl" && stmt.decl.kind === "Namespace") {
370
+ this.line(`local ${(0, names_js_1.sanitizeIdentifier)(stmt.decl.name)} = {}`);
371
+ }
372
+ }
373
+ }
374
+ emitFunctions() {
375
+ let first = true;
376
+ const sep = () => {
377
+ if (!first) {
378
+ this.line("");
379
+ }
380
+ first = false;
381
+ };
382
+ for (const s of this.orderedStructs()) {
383
+ if (!s.isAbstract) {
384
+ sep();
385
+ this.emitConstructor(s);
386
+ }
387
+ for (const m of s.decl.methods) {
388
+ if (!m.body) {
389
+ continue;
390
+ }
391
+ sep();
392
+ this.emitFunction(m, `${s.luauName}.${m.name}`, s);
393
+ }
394
+ for (const [name, m] of s.methods) {
395
+ if (m.owner !== s.name) {
396
+ const base = [...this.file.structRegistry.values()].find((x) => x.name === m.owner && x.methods.get(name) === m);
397
+ if (base && !m.isAbstract) {
398
+ this.line(`${s.luauName}.${name} = ${this.structRef(base.id)}.${name}`);
399
+ }
400
+ }
401
+ }
402
+ }
403
+ for (const stmt of this.program.body) {
404
+ if (stmt.kind !== "Decl") {
405
+ continue;
406
+ }
407
+ const decl = stmt.decl;
408
+ if (decl.kind === "Func" && decl.body) {
409
+ sep();
410
+ const sym = this.file.funcSyms.get(decl);
411
+ this.emitFunction(decl, sym ? this.nameOf(sym) : (0, names_js_1.sanitizeIdentifier)(decl.name), null, sym ?? null);
412
+ }
413
+ else if (decl.kind === "Namespace") {
414
+ for (const f of decl.funcs) {
415
+ if (f.body) {
416
+ sep();
417
+ this.emitFunction(f, `${(0, names_js_1.sanitizeIdentifier)(decl.name)}.${f.name}`, null);
418
+ }
419
+ }
420
+ }
421
+ }
422
+ }
423
+ emitConstructor(s) {
424
+ const generics = s.typeParams.length ? `<${s.typeParams.join(", ")}>` : "";
425
+ const selfType = `${s.luauName}${generics}`;
426
+ const params = s.fields.map((f) => {
427
+ const t = this.typeOf(f.type) ?? "any";
428
+ return `${safeName(f.name)}: ${f.defaultValue ? optionalType(t) : t}`;
429
+ });
430
+ this.line(`function ${s.luauName}.new${generics}(${params.join(", ")}): ${selfType}`);
431
+ this.block(() => {
432
+ const fields = s.fields.map((f) => {
433
+ const local = safeName(f.name);
434
+ if (f.defaultValue) {
435
+ const def = this.expr(f.defaultValue).code;
436
+ return `${fieldKey(f.name)} = if ${local} == nil then ${def} else ${local}`;
437
+ }
438
+ return `${fieldKey(f.name)} = ${local}`;
439
+ });
440
+ if (fields.length <= 3) {
441
+ this.line(`local self = { ${fields.join(", ")} } :: ${selfType}`);
442
+ }
443
+ else {
444
+ this.line("local self = {");
445
+ this.block(() => fields.forEach((f) => this.line(`${f},`)));
446
+ this.line(`} :: ${selfType}`);
447
+ }
448
+ // __index supplies the methods described by the public struct type.
449
+ this.line(`return setmetatable(self, ${s.luauName})`);
450
+ });
451
+ this.line("end");
452
+ }
453
+ paramList(params, fnType) {
454
+ const list = [];
455
+ const prologue = [];
456
+ for (const p of params) {
457
+ const sym = p.sym;
458
+ const name = sym ? this.nameOf(sym) : (0, names_js_1.sanitizeIdentifier)(p.name);
459
+ if (p.variadic) {
460
+ const elem = p.ty && (0, types_js_1.prune)(p.ty).k === "list" ? this.typeOf((0, types_js_1.prune)(p.ty).elem) : null;
461
+ list.push(elem ? `...: ${elem}` : "...");
462
+ prologue.push(() => this.line(`local ${name} = { ... }`));
463
+ continue;
464
+ }
465
+ const t = this.typeOf(p.ty);
466
+ list.push(t ? `${name}: ${p.defaultValue && !t.endsWith("?") ? optionalType(t) : t}` : name);
467
+ if (p.defaultValue) {
468
+ const dv = p.defaultValue;
469
+ prologue.push(() => {
470
+ const value = this.expr(dv).code;
471
+ this.line(`if ${name} == nil then`);
472
+ this.block(() => this.line(`${name} = ${value}`));
473
+ this.line("end");
474
+ });
475
+ }
476
+ }
477
+ void fnType;
478
+ return { list, prologue };
479
+ }
480
+ emitFunction(decl, name, owner, sym = null) {
481
+ const fnType = sym ? sym.type : this.file.funcSyms.get(decl)?.type ?? null;
482
+ const isMethod = owner !== null && !decl.isStatic;
483
+ const generics = [...(isMethod || decl.isStatic ? owner?.typeParams ?? [] : []), ...decl.typeParams];
484
+ const { list, prologue } = this.paramList(decl.params, fnType);
485
+ if (isMethod && owner) {
486
+ const ownerGenerics = owner.typeParams.length ? `<${owner.typeParams.join(", ")}>` : "";
487
+ list.unshift(`self: ${owner.luauName}${ownerGenerics}`);
488
+ }
489
+ const ret = fnType ? this.returnType(fnType.ret, decl.isAsync) : null;
490
+ const retText = ret && ret !== "()" ? `: ${ret}` : "";
491
+ if (decl.attributes.some((a) => a.name === "native")) {
492
+ this.line("@native");
493
+ }
494
+ if (decl.doc) {
495
+ for (const docLine of decl.doc.split(/\r?\n/)) {
496
+ this.line(`--- ${docLine.trim()}`);
497
+ }
498
+ }
499
+ const header = owner || name.includes(".") ? `function ${name}` : `\u0000DECL${sym?.id ?? 0}\u0000 ${name}`;
500
+ if (sym) {
501
+ if (this.referencedFuncs.has(sym.id)) {
502
+ this.forwardFuncs.add(sym.id);
503
+ }
504
+ this.definedFuncs.add(sym.id);
505
+ }
506
+ this.line(`${header}${generics.length ? `<${generics.join(", ")}>` : ""}(${list.join(", ")})${retText}`);
507
+ this.block(() => {
508
+ this.fnDepth++;
509
+ const saved = this.loops.splice(0);
510
+ for (const p of prologue) {
511
+ p();
512
+ }
513
+ this.stmts(decl.body || []);
514
+ this.loops.push(...saved);
515
+ this.fnDepth--;
516
+ });
517
+ this.line("end");
518
+ }
519
+ emitReturn() {
520
+ if (!this.isModule) {
521
+ return;
522
+ }
523
+ const entries = [];
524
+ const exp = this.file.exports;
525
+ for (const [name] of exp.funcs) {
526
+ const sym = this.file.topLevel.find((s) => s.name === name && s.kind === "func");
527
+ if (sym) {
528
+ entries.push(`${fieldKey(name)} = ${this.nameOf(sym)}`);
529
+ }
530
+ }
531
+ for (const [name, k] of exp.consts) {
532
+ entries.push(`${fieldKey(name)} = ${literal(k.value)}`);
533
+ }
534
+ for (const s of this.file.structs) {
535
+ entries.push(`${fieldKey(s.luauName)} = ${s.luauName}`);
536
+ }
537
+ for (const e of this.file.enums) {
538
+ entries.push(`${fieldKey(e.luauName)} = ${e.luauName}`);
539
+ }
540
+ for (const [name] of exp.namespaces) {
541
+ entries.push(`${fieldKey(name)} = ${(0, names_js_1.sanitizeIdentifier)(name)}`);
542
+ }
543
+ if (entries.length === 0) {
544
+ this.line("return {}");
545
+ return;
546
+ }
547
+ this.line("return table.freeze({");
548
+ this.block(() => entries.forEach((e) => this.line(`${e},`)));
549
+ this.line("})");
550
+ }
551
+ // ---------- signals ----------
552
+ /** CL++ signals connected only at the top level resolve at compile time: firing calls the handlers directly. */
553
+ findStaticSignals() {
554
+ const dynamic = new Set();
555
+ const all = new Set();
556
+ const scan = (stmts, depth) => {
557
+ for (const s of stmts) {
558
+ walkStmt(s, (inner, d) => {
559
+ if (inner.kind === "Connect" && inner.target === "cl" && inner.signal.kind === "Name" && inner.signal.res?.kind === "signal") {
560
+ const id = inner.signal.res.sym.id;
561
+ all.add(id);
562
+ if (d > 0 || inner.handler.kind === "Function") {
563
+ dynamic.add(id);
564
+ }
565
+ }
566
+ }, depth);
567
+ }
568
+ };
569
+ scan(this.program.body, 0);
570
+ for (const id of all) {
571
+ if (!dynamic.has(id)) {
572
+ this.staticSignals.add(id);
573
+ }
574
+ }
575
+ for (const s of this.file.topLevel) {
576
+ if (s.kind === "signal" && !all.has(s.id)) {
577
+ this.staticSignals.add(s.id);
578
+ }
579
+ }
580
+ }
581
+ // ---------- statements ----------
582
+ stmts(list) {
583
+ for (const s of list) {
584
+ this.stmt(s, false);
585
+ }
586
+ }
587
+ stmt(s, topLevel) {
588
+ const saved = this.sourceOffset;
589
+ this.sourceOffset = s.span.start;
590
+ try {
591
+ this.emitStmt(s, topLevel);
592
+ }
593
+ finally {
594
+ this.sourceOffset = saved;
595
+ }
596
+ }
597
+ emitStmt(s, topLevel) {
598
+ switch (s.kind) {
599
+ case "Var":
600
+ this.varStmt(s, topLevel);
601
+ return;
602
+ case "Assign":
603
+ this.assign(s);
604
+ return;
605
+ case "Expr":
606
+ this.exprStmt(s.expr);
607
+ return;
608
+ case "If":
609
+ this.ifStmt(s);
610
+ return;
611
+ case "While": {
612
+ const cond = this.cond(s.cond);
613
+ this.line(`while ${cond} do`);
614
+ this.loopBody(s.body, {});
615
+ this.line("end");
616
+ return;
617
+ }
618
+ case "For":
619
+ this.forStmt(s);
620
+ return;
621
+ case "ForIn":
622
+ this.forIn(s);
623
+ return;
624
+ case "Break":
625
+ this.breakStmt();
626
+ return;
627
+ case "Continue":
628
+ this.continueStmt();
629
+ return;
630
+ case "Return":
631
+ this.returnStmt(s);
632
+ return;
633
+ case "Switch":
634
+ this.switchStmt(s);
635
+ return;
636
+ case "Match":
637
+ this.matchStmt(s);
638
+ return;
639
+ case "Try":
640
+ this.tryStmt(s);
641
+ return;
642
+ case "Output":
643
+ this.output(s);
644
+ return;
645
+ case "Connect":
646
+ this.connect(s);
647
+ return;
648
+ case "Signal": {
649
+ const sym = s.name.sym;
650
+ if (sym && !this.staticSignals.has(sym.id)) {
651
+ this.line(`local ${this.nameOf(sym)}: { (...any) -> () } = {}`);
652
+ }
653
+ return;
654
+ }
655
+ case "Using": {
656
+ const sym = s.sym;
657
+ if (sym && sym.constValue === undefined && s.target) {
658
+ this.line(`local ${this.nameOf(sym)} = ${this.valueOf(s.target).code}`);
659
+ }
660
+ return;
661
+ }
662
+ case "Block":
663
+ this.line("do");
664
+ this.block(() => this.stmts(s.body));
665
+ this.line("end");
666
+ return;
667
+ case "Decl":
668
+ if (s.decl.kind === "Func" && this.fnDepth > 0) {
669
+ const sym = this.file.funcSyms.get(s.decl);
670
+ const name = sym ? this.nameOf(sym) : (0, names_js_1.sanitizeIdentifier)(s.decl.name);
671
+ const lines = this.capture(() => this.emitFunction(s.decl, name, null, null));
672
+ if (lines.length) {
673
+ lines[0] = lines[0].replace(`function ${name}`, `local function ${name}`).replace(/\u0000DECL\d+\u0000 /, "local function ");
674
+ }
675
+ this.lines.push(...lines);
676
+ }
677
+ return;
678
+ default:
679
+ return;
680
+ }
681
+ }
682
+ varStmt(s, topLevel) {
683
+ if (s.unpack) {
684
+ const names = s.names.map((n) => (n.sym ? this.nameOf(n.sym) : (0, names_js_1.sanitizeIdentifier)(n.name)));
685
+ const src = (0, types_js_1.prune)(s.init?.ty ?? types_js_1.T.any);
686
+ const init = s.init ? this.expr(s.init).code : "nil";
687
+ if (src.k === "tuple") {
688
+ this.line(`local ${names.join(", ")} = ${init}`);
689
+ }
690
+ else {
691
+ this.line(`local ${names.join(", ")} = table.unpack(${init})`);
692
+ }
693
+ return;
694
+ }
695
+ const n = s.names[0];
696
+ const sym = n.sym;
697
+ if (!sym) {
698
+ return;
699
+ }
700
+ if (sym.kind === "const" && sym.constValue !== undefined) {
701
+ return; // inlined at every use
702
+ }
703
+ const name = this.nameOf(sym);
704
+ const declared = topLevel && sym.captured ? "" : "local ";
705
+ const annotation = declared ? this.annotationFor(s, sym) : "";
706
+ if (s.init && s.init.res?.kind === "call" && s.init.res.call.form === "instance") {
707
+ this.instanceInto(s.init, `${declared}${name}${annotation}`, name);
708
+ }
709
+ else if (s.init && s.init.res?.kind === "call" && s.init.res.call.form === "datatype" && s.init.res.call.props.length) {
710
+ this.datatypeInto(s.init, `${declared}${name}${annotation}`, name);
711
+ }
712
+ else {
713
+ const value = s.init ? this.valueOf(s.init).code : "nil";
714
+ this.line(`${declared}${name}${annotation} = ${value}`);
715
+ }
716
+ if (sym.observable) {
717
+ const t = this.typeOf(sym.type) ?? "any";
718
+ this.line(`local ${name}_changed: { (${t}) -> () } = {}`);
719
+ }
720
+ }
721
+ /** Only annotate when Luau could not infer the same type from the initializer. */
722
+ annotationFor(s, sym) {
723
+ const t = (0, types_js_1.prune)(sym.type);
724
+ const init = s.init;
725
+ const needs = !init ||
726
+ init.kind === "Null" ||
727
+ s.declaredType !== null ||
728
+ ((t.k === "list" || t.k === "dict") && init.kind === "Call" && init.res?.kind === "call" && init.res.call.form === "builtin") ||
729
+ (t.k === "option" && !isInferredByLuau(init)) ||
730
+ t.k === "union" ||
731
+ t.k === "lit";
732
+ if (!needs) {
733
+ return "";
734
+ }
735
+ const lt = this.typeOf(sym.type);
736
+ return lt && lt !== "any" ? `: ${lt}` : "";
737
+ }
738
+ instanceInto(e, declaration, name) {
739
+ const res = e.res;
740
+ const call = res.call;
741
+ this.line(`${declaration} = Instance.new(${(0, names_js_1.luauString)(call.cls)})`);
742
+ for (const p of call.props) {
743
+ this.line(`${name}.${p.name} = ${this.valueOf(p.value).code}`);
744
+ }
745
+ if (call.parent) {
746
+ this.line(`${name}.Parent = ${this.expr(call.parent).code}`);
747
+ }
748
+ }
749
+ datatypeInto(e, declaration, name) {
750
+ const res = e.res;
751
+ const call = res.call;
752
+ this.line(`${declaration} = ${call.name}.new(${this.args(res.args, res.variadicArgs)})`);
753
+ for (const p of call.props) {
754
+ this.line(`${name}.${p.name} = ${this.valueOf(p.value).code}`);
755
+ }
756
+ }
757
+ assign(s) {
758
+ const target = s.target;
759
+ // Vector component: pos.y = 1 → pos = Vector3.new(pos.X, 1, pos.Z)
760
+ if (target.kind === "Member" && target.res?.kind === "vectorComponent") {
761
+ this.assignVectorComponent(target, s.op, s.value);
762
+ return;
763
+ }
764
+ // `x = f(x)`: the old x is dropped by this very statement, so passing it needs no copy.
765
+ if (s.op === "=" && target.kind === "Name" && target.res?.kind === "local" && s.value.kind === "Call" && s.value.res?.kind === "call") {
766
+ const id = target.res.sym.id;
767
+ for (const arg of s.value.res.args) {
768
+ if (arg && arg.copy && arg.kind === "Name" && arg.res?.kind === "local" && arg.res.sym.id === id) {
769
+ arg.copy = false;
770
+ }
771
+ }
772
+ }
773
+ const targetCode = this.place(target);
774
+ const t = (0, types_js_1.prune)(target.ty ?? types_js_1.T.any);
775
+ let value;
776
+ if (s.op === "=") {
777
+ value = this.valueOf(s.value).code;
778
+ this.line(`${targetCode} = ${value}`);
779
+ }
780
+ else {
781
+ const op = s.op[0];
782
+ const rhs = this.expr(s.value);
783
+ const intDiv = op === "/" && isIntPair(t, (0, types_js_1.prune)(s.value.ty ?? types_js_1.T.any));
784
+ if (intDiv) {
785
+ this.line(`${targetCode} = ${this.use("__cl_idiv")}(${targetCode}, ${rhs.code})`);
786
+ }
787
+ else if (op === "%") {
788
+ this.line(`${targetCode} = math.fmod(${targetCode}, ${rhs.code})`);
789
+ }
790
+ else {
791
+ this.line(`${targetCode} ${s.op} ${rhs.code}`);
792
+ }
793
+ }
794
+ this.notifyObservable(target);
795
+ }
796
+ notifyObservable(target) {
797
+ if (target.kind === "Name" && target.res?.kind === "local" && target.res.sym.observable) {
798
+ const name = this.nameOf(target.res.sym);
799
+ this.line(`for _, onChange in ${name}_changed do`);
800
+ this.block(() => this.line(`onChange(${name})`));
801
+ this.line("end");
802
+ }
803
+ }
804
+ assignVectorComponent(target, op, valueExpr) {
805
+ const vecType = (0, types_js_1.prune)(target.object.ty ?? types_js_1.T.any);
806
+ const dt = vecType.k === "datatype" ? vecType.name : "Vector3";
807
+ const comps = dt.startsWith("Vector2") ? ["X", "Y"] : ["X", "Y", "Z"];
808
+ const objectPlace = this.place(target.object);
809
+ const simple = /^[A-Za-z_][\w.]*$/.test(objectPlace);
810
+ let src = objectPlace;
811
+ if (!simple) {
812
+ src = this.temp("v");
813
+ this.line(`local ${src} = ${objectPlace}`);
814
+ }
815
+ const comp = target.res.component.toUpperCase();
816
+ const rhs = this.expr(valueExpr).code;
817
+ const newValue = op === "=" ? rhs : `${src}.${comp} ${op[0]} ${wrapCode(rhs, P.MUL)}`;
818
+ const args = comps.map((c) => (c === comp ? newValue : `${src}.${c}`));
819
+ this.line(`${objectPlace} = ${dt}.new(${args.join(", ")})`);
820
+ }
821
+ /** Assignable Luau place for a target expression. */
822
+ place(e) {
823
+ switch (e.kind) {
824
+ case "Name":
825
+ return e.res?.kind === "local" ? this.nameOf(e.res.sym) : (0, names_js_1.sanitizeIdentifier)(e.name);
826
+ case "SelfField":
827
+ return e.res?.kind === "sharedField" ? this.shared(e.name) : `${this.selfName}.${e.name}`;
828
+ case "Member": {
829
+ const obj = this.expr(e.object);
830
+ if (e.res?.kind === "vectorComponent") {
831
+ return `${wrap(obj, P.ATOM)}.${e.res.component}`;
832
+ }
833
+ return `${wrap(obj, P.ATOM)}${memberAccess(e.name)}`;
834
+ }
835
+ case "Index":
836
+ return this.index(e).code;
837
+ default:
838
+ return this.expr(e).code;
839
+ }
840
+ }
841
+ shared(name) {
842
+ this.sharedTable = true;
843
+ return `__clShared${memberAccess(name)}`;
844
+ }
845
+ exprStmt(e) {
846
+ if (e.kind === "Update") {
847
+ this.line(`${this.place(e.target)} ${e.delta > 0 ? "+=" : "-="} 1`);
848
+ this.notifyObservable(e.target);
849
+ return;
850
+ }
851
+ if (e.kind === "Call" && e.res?.kind === "call") {
852
+ const call = e.res.call;
853
+ if (call.form === "signalFire") {
854
+ this.fireSignal(e, call.sym, e.res.args);
855
+ return;
856
+ }
857
+ if (call.form === "instance") {
858
+ const name = this.temp("i");
859
+ this.instanceInto(e, `local ${name}`, name);
860
+ return;
861
+ }
862
+ }
863
+ const code = this.expr(e).code;
864
+ if (isCallCode(code)) {
865
+ this.line(code);
866
+ }
867
+ else {
868
+ this.line(`local _ = ${code}`);
869
+ }
870
+ }
871
+ fireSignal(e, sym, args) {
872
+ const values = args.map((a) => (a ? this.valueOf(a).code : "nil"));
873
+ if (this.staticSignals.has(sym.id)) {
874
+ const handlers = sym.handlers ?? [];
875
+ const needTemps = handlers.length > 1 && values.some((v) => !/^[\w.]+$|^".*"$|^-?\d/.test(v));
876
+ const passed = needTemps
877
+ ? values.map((v) => {
878
+ const t = this.temp("a");
879
+ this.line(`local ${t} = ${v}`);
880
+ return t;
881
+ })
882
+ : values;
883
+ for (const h of handlers) {
884
+ this.line(`${wrap(this.expr(h), P.ATOM)}(${passed.join(", ")})`);
885
+ }
886
+ return;
887
+ }
888
+ this.line(`for _, handler in ${this.nameOf(sym)} do`);
889
+ this.block(() => this.line(`handler(${values.join(", ")})`));
890
+ this.line("end");
891
+ }
892
+ connect(s) {
893
+ if (s.target === "cl") {
894
+ const sym = s.signal.kind === "Name" && s.signal.res?.kind === "signal" ? s.signal.res.sym : null;
895
+ if (sym && this.staticSignals.has(sym.id)) {
896
+ return;
897
+ }
898
+ this.line(`table.insert(${this.expr(s.signal).code}, ${this.expr(s.handler).code})`);
899
+ return;
900
+ }
901
+ this.line(`${wrap(this.expr(s.signal), P.ATOM)}:Connect(${this.expr(s.handler).code})`);
902
+ }
903
+ output(s) {
904
+ const value = this.stringy(s.value, s.channel === "report");
905
+ switch (s.channel) {
906
+ case "post":
907
+ case "cout":
908
+ this.line(`print(${value})`);
909
+ return;
910
+ case "warn":
911
+ this.line(`warn(${value})`);
912
+ return;
913
+ case "report":
914
+ this.line(`error(${value}, 0)`);
915
+ return;
916
+ }
917
+ }
918
+ /** Value printed the way CL++ prints it. */
919
+ stringy(e, forceString = false) {
920
+ const t = (0, types_js_1.prune)(e.ty ?? types_js_1.T.any);
921
+ const c = this.expr(e);
922
+ if (t.k === "string" || t.k === "lit" || ((t.k === "int" || t.k === "float" || t.k === "bool") && !forceString)) {
923
+ return c.code;
924
+ }
925
+ if (t.k === "int" || t.k === "float" || t.k === "bool" || t.k === "instance" || t.k === "rbxEnumItem" || (t.k === "datatype" && t.name !== "Vector3" && t.name !== "Vector2")) {
926
+ return `tostring(${c.code})`;
927
+ }
928
+ return `${this.use("__cl_str")}(${c.code})`;
929
+ }
930
+ ifStmt(s) {
931
+ if (s.letName) {
932
+ const sym = s.letName.sym;
933
+ const name = sym ? this.nameOf(sym) : (0, names_js_1.sanitizeIdentifier)(s.letName.name);
934
+ this.line("do");
935
+ this.block(() => {
936
+ this.line(`local ${name} = ${this.valueOf(s.cond).code}`);
937
+ this.line(`if ${this.truthy({ code: name, prec: P.ATOM }, (0, types_js_1.prune)(s.cond.ty ?? types_js_1.T.any))} then`);
938
+ this.block(() => this.stmts(s.then));
939
+ if (s.else) {
940
+ this.line("else");
941
+ this.block(() => this.stmts(s.else));
942
+ }
943
+ this.line("end");
944
+ });
945
+ this.line("end");
946
+ return;
947
+ }
948
+ this.line(`if ${this.cond(s.cond)} then`);
949
+ this.block(() => this.stmts(s.then));
950
+ let rest = s.else;
951
+ while (rest) {
952
+ if (rest.length === 1 && rest[0].kind === "If" && !rest[0].letName && !rest[0].attributes) {
953
+ const next = rest[0];
954
+ const hoisted = [];
955
+ let cond = "";
956
+ hoisted.push(...this.capture(() => {
957
+ cond = this.cond(next.cond);
958
+ }));
959
+ if (hoisted.length) {
960
+ this.line("else");
961
+ this.block(() => {
962
+ this.lines.push(...hoisted.map((l) => `\t${l}`));
963
+ this.line(`if ${cond} then`);
964
+ this.block(() => this.stmts(next.then));
965
+ if (next.else) {
966
+ this.line("else");
967
+ this.block(() => this.stmts(next.else));
968
+ }
969
+ this.line("end");
970
+ });
971
+ break;
972
+ }
973
+ this.line(`elseif ${cond} then`);
974
+ this.block(() => this.stmts(next.then));
975
+ rest = next.else;
976
+ continue;
977
+ }
978
+ this.line("else");
979
+ this.block(() => this.stmts(rest));
980
+ break;
981
+ }
982
+ this.line("end");
983
+ }
984
+ loopBody(body, frame) {
985
+ this.loops.push({ kind: "loop", ...frame });
986
+ this.block(() => this.stmts(body));
987
+ this.loops.pop();
988
+ }
989
+ forStmt(s) {
990
+ const numeric = this.numericFor(s);
991
+ if (numeric) {
992
+ this.line(`for ${numeric.name} = ${numeric.from}, ${numeric.to}${numeric.step !== "1" ? `, ${numeric.step}` : ""} do`);
993
+ this.loopBody(s.body, {});
994
+ this.line("end");
995
+ return;
996
+ }
997
+ this.line("do");
998
+ this.block(() => {
999
+ if (s.init) {
1000
+ this.stmt(s.init, false);
1001
+ }
1002
+ const cond = s.cond ? this.cond(s.cond) : "true";
1003
+ this.line(`while ${cond} do`);
1004
+ const step = s.step;
1005
+ this.loopBody(s.body, { step: step ? () => this.stmt(step, false) : undefined });
1006
+ if (step) {
1007
+ this.block(() => this.stmt(step, false));
1008
+ }
1009
+ this.line("end");
1010
+ });
1011
+ this.line("end");
1012
+ }
1013
+ /** `for (let mut i = a; i < b; i++)` with an invariant bound → `for i = a, b - 1 do`. */
1014
+ numericFor(s) {
1015
+ const init = s.init;
1016
+ if (!init || init.kind !== "Var" || init.names.length !== 1 || !init.init || !s.cond || !s.step) {
1017
+ return null;
1018
+ }
1019
+ const sym = init.names[0].sym;
1020
+ if (!sym || !isIntLikeType(sym.type) || sym.captured) {
1021
+ return null;
1022
+ }
1023
+ const cond = s.cond;
1024
+ if (cond.kind !== "Binary" || !["<", "<=", ">", ">="].includes(cond.op) || cond.left.kind !== "Name" || cond.left.res?.kind !== "local" || cond.left.res.sym.id !== sym.id) {
1025
+ return null;
1026
+ }
1027
+ if (!isInvariant(cond.right, s.body)) {
1028
+ return null;
1029
+ }
1030
+ let delta = null;
1031
+ const step = s.step;
1032
+ if (step.kind === "Expr" && step.expr.kind === "Update" && isSym(step.expr.target, sym)) {
1033
+ delta = step.expr.delta;
1034
+ }
1035
+ else if (step.kind === "Assign" && isSym(step.target, sym) && step.value.kind === "Number" && !step.value.isFloat && (step.op === "+=" || step.op === "-=")) {
1036
+ delta = step.op === "+=" ? step.value.value : -step.value.value;
1037
+ }
1038
+ else if (step.kind === "Assign" && step.op === "=" && isSym(step.target, sym) && step.value.kind === "Binary" && (step.value.op === "+" || step.value.op === "-") && isSym(step.value.left, sym) && step.value.right.kind === "Number" && !step.value.right.isFloat) {
1039
+ delta = step.value.op === "+" ? step.value.right.value : -step.value.right.value;
1040
+ }
1041
+ if (!delta || assigns(s.body, sym)) {
1042
+ return null;
1043
+ }
1044
+ const up = cond.op === "<" || cond.op === "<=";
1045
+ if ((up && delta < 0) || (!up && delta > 0)) {
1046
+ return null;
1047
+ }
1048
+ const bound = this.expr(cond.right);
1049
+ let to;
1050
+ if (cond.op === "<=" || cond.op === ">=") {
1051
+ to = bound.code;
1052
+ }
1053
+ else if (cond.right.kind === "Number") {
1054
+ to = (0, names_js_1.luauNumber)(cond.right.value + (up ? -1 : 1));
1055
+ }
1056
+ else {
1057
+ to = `${wrap(bound, P.ADD)} ${up ? "-" : "+"} 1`;
1058
+ }
1059
+ return { name: this.nameOf(sym), from: this.expr(init.init).code, to, step: String(delta) };
1060
+ }
1061
+ forIn(s) {
1062
+ const sym = s.name.sym;
1063
+ const name = sym ? this.nameOf(sym) : (0, names_js_1.sanitizeIdentifier)(s.name.name);
1064
+ const iterable = this.expr(s.iterable);
1065
+ switch (s.iterKind) {
1066
+ case "range": {
1067
+ const to = s.iterable.kind === "Number" ? (0, names_js_1.luauNumber)(s.iterable.value - 1) : `${wrap(iterable, P.ADD)} - 1`;
1068
+ this.line(`for ${name} = 0, ${to} do`);
1069
+ this.loopBody(s.body, {});
1070
+ this.line("end");
1071
+ return;
1072
+ }
1073
+ case "string": {
1074
+ const cp = this.temp("cp");
1075
+ this.line(`for _, ${cp} in utf8.codes(${iterable.code}) do`);
1076
+ this.block(() => this.line(`local ${name} = utf8.char(${cp})`));
1077
+ this.loopBody(s.body, {});
1078
+ this.line("end");
1079
+ return;
1080
+ }
1081
+ case "dict":
1082
+ this.line(`for ${name} in ${iterable.code} do`);
1083
+ this.loopBody(s.body, {});
1084
+ this.line("end");
1085
+ return;
1086
+ default: {
1087
+ if (s.downcast && s.declaredType) {
1088
+ const raw = this.temp("item");
1089
+ this.line(`for _, ${raw} in ${iterable.code} do`);
1090
+ const t = this.typeOf(sym?.type);
1091
+ this.block(() => this.line(`local ${name} = ${raw}${t ? ` :: ${t}` : ""}`));
1092
+ }
1093
+ else {
1094
+ this.line(`for _, ${name} in ${iterable.code} do`);
1095
+ }
1096
+ this.loopBody(s.body, {});
1097
+ this.line("end");
1098
+ }
1099
+ }
1100
+ }
1101
+ breakStmt() {
1102
+ for (let i = this.loops.length - 1; i >= 0; i--) {
1103
+ const f = this.loops[i];
1104
+ if (f.kind === "try") {
1105
+ f.usedBreak = true;
1106
+ this.line("return 2");
1107
+ return;
1108
+ }
1109
+ if (f.kind === "switch") {
1110
+ if (f.wrapped) {
1111
+ this.line("break");
1112
+ }
1113
+ return;
1114
+ }
1115
+ this.line("break");
1116
+ return;
1117
+ }
1118
+ this.line("break");
1119
+ }
1120
+ continueStmt() {
1121
+ for (let i = this.loops.length - 1; i >= 0; i--) {
1122
+ const f = this.loops[i];
1123
+ if (f.kind === "try") {
1124
+ f.usedContinue = true;
1125
+ this.line("return 3");
1126
+ return;
1127
+ }
1128
+ if (f.kind === "switch") {
1129
+ if (f.wrapped) {
1130
+ f.continueFlag ||= this.temp("continue");
1131
+ this.line(`${f.continueFlag} = true`);
1132
+ this.line("break");
1133
+ return;
1134
+ }
1135
+ continue;
1136
+ }
1137
+ if (f.step) {
1138
+ f.step();
1139
+ }
1140
+ this.line("continue");
1141
+ return;
1142
+ }
1143
+ this.line("continue");
1144
+ }
1145
+ returnStmt(s) {
1146
+ const value = s.value ? this.valueOf(s.value).code : null;
1147
+ const tryFrame = [...this.loops].reverse().find((f) => f.kind === "try");
1148
+ if (tryFrame && this.fnDepth > 0) {
1149
+ tryFrame.usedReturn = true;
1150
+ this.line(value ? `return 1, ${value}` : "return 1");
1151
+ return;
1152
+ }
1153
+ this.line(value ? `return ${value}` : "return");
1154
+ }
1155
+ subject(e) {
1156
+ if (e.kind === "Name" && e.res?.kind === "local") {
1157
+ return this.nameOf(e.res.sym);
1158
+ }
1159
+ const t = this.temp("m");
1160
+ this.line(`local ${t} = ${this.expr(e).code}`);
1161
+ return t;
1162
+ }
1163
+ switchStmt(s) {
1164
+ const needsWrap = s.cases.some((k) => hasInnerBreak(k.body));
1165
+ const frame = { kind: "switch", wrapped: needsWrap };
1166
+ const repeatAt = this.lines.length;
1167
+ if (needsWrap) {
1168
+ this.line("repeat");
1169
+ this.indent++;
1170
+ }
1171
+ const subj = this.subject(s.subject);
1172
+ const subjType = (0, types_js_1.prune)(s.subject.ty ?? types_js_1.T.any);
1173
+ const cases = s.cases.filter((k) => k.test !== null);
1174
+ const def = s.cases.find((k) => k.test === null);
1175
+ this.loops.push(frame);
1176
+ const body = (k) => this.block(() => this.stmts(trimTrailingBreak(k.body)));
1177
+ cases.forEach((k, i) => {
1178
+ const test = this.equality({ code: subj, prec: P.ATOM }, k.test, subjType);
1179
+ this.line(`${i === 0 ? "if" : "elseif"} ${test} then`);
1180
+ body(k);
1181
+ });
1182
+ if (def) {
1183
+ if (cases.length) {
1184
+ this.line("else");
1185
+ body(def);
1186
+ }
1187
+ else {
1188
+ this.line("do");
1189
+ body(def);
1190
+ }
1191
+ }
1192
+ if (cases.length || def) {
1193
+ this.line("end");
1194
+ }
1195
+ this.loops.pop();
1196
+ if (needsWrap) {
1197
+ this.indent--;
1198
+ this.line("until true");
1199
+ if (frame.continueFlag) {
1200
+ this.lines.splice(repeatAt, 0, `${"\t".repeat(this.indent)}local ${frame.continueFlag} = false`);
1201
+ this.line(`if ${frame.continueFlag} then`);
1202
+ this.block(() => this.continueStmt());
1203
+ this.line("end");
1204
+ }
1205
+ }
1206
+ }
1207
+ equality(left, right, t) {
1208
+ const r = this.expr(right);
1209
+ if ((0, types_js_1.isValueTable)(t)) {
1210
+ return `${this.use("__cl_eq")}(${left.code}, ${r.code})`;
1211
+ }
1212
+ return `${wrap(left, P.CONCAT)} == ${wrap(r, P.CONCAT)}`;
1213
+ }
1214
+ matchStmt(s) {
1215
+ const subj = this.subject(s.subject);
1216
+ const subjType = (0, types_js_1.prune)(s.subject.ty ?? types_js_1.T.any);
1217
+ let first = true;
1218
+ let opened = false;
1219
+ for (let index = 0; index < s.arms.length; index++) {
1220
+ const arm = s.arms[index];
1221
+ const lastExhaustive = s.exhaustive === true && index === s.arms.length - 1 && !first && !arm.guard;
1222
+ const computed = this.armTest(arm, subj, subjType);
1223
+ const test = lastExhaustive ? "true" : computed.test;
1224
+ const bind = computed.bind;
1225
+ if (test === "true" && first) {
1226
+ // a lone wildcard
1227
+ this.line("do");
1228
+ opened = true;
1229
+ this.block(() => {
1230
+ bind();
1231
+ this.stmts(arm.body);
1232
+ });
1233
+ break;
1234
+ }
1235
+ if (test === "true") {
1236
+ this.line("else");
1237
+ this.block(() => {
1238
+ bind();
1239
+ this.stmts(arm.body);
1240
+ });
1241
+ break;
1242
+ }
1243
+ this.line(`${first ? "if" : "elseif"} ${test} then`);
1244
+ opened = true;
1245
+ first = false;
1246
+ this.block(() => {
1247
+ bind();
1248
+ this.stmts(arm.body);
1249
+ });
1250
+ }
1251
+ if (opened) {
1252
+ this.line("end");
1253
+ }
1254
+ }
1255
+ armTest(arm, subj, subjType) {
1256
+ const pattern = arm.pattern;
1257
+ let test = "true";
1258
+ let bindName = null;
1259
+ let bindSource = null;
1260
+ const caseTest = (p) => {
1261
+ const res = p.res;
1262
+ if (res?.kind === "enumCase") {
1263
+ if (res.anyPayload) {
1264
+ test = `${subj}.tag == ${res.index}`;
1265
+ if (p.binding) {
1266
+ bindSource = `${subj}.value`;
1267
+ }
1268
+ }
1269
+ else {
1270
+ test = `${subj} == ${res.index}`;
1271
+ }
1272
+ }
1273
+ else if (res?.kind === "builtin") {
1274
+ switch (res.name) {
1275
+ case "Some":
1276
+ test = `${subj} ~= nil`;
1277
+ bindSource = subj;
1278
+ break;
1279
+ case "None":
1280
+ test = `${subj} == nil`;
1281
+ break;
1282
+ case "Ok":
1283
+ test = `${subj}.ok`;
1284
+ bindSource = `${subj}.value`;
1285
+ break;
1286
+ case "Err":
1287
+ test = `not ${subj}.ok`;
1288
+ bindSource = `${subj}.error`;
1289
+ break;
1290
+ default:
1291
+ break;
1292
+ }
1293
+ }
1294
+ else if (res?.kind === "rbxEnumItem") {
1295
+ test = `${subj} == Enum.${res.enumName}.${res.item}`;
1296
+ }
1297
+ else if (p.valueExpr) {
1298
+ test = this.equality({ code: subj, prec: P.ATOM }, p.valueExpr, subjType);
1299
+ }
1300
+ if (p.binding && bindSource) {
1301
+ const sym = p.binding.sym;
1302
+ bindName = sym ? this.nameOf(sym) : (0, names_js_1.sanitizeIdentifier)(p.binding.name);
1303
+ if (sym) {
1304
+ this.substitutions.set(sym.id, bindSource);
1305
+ }
1306
+ }
1307
+ };
1308
+ if (pattern.kind === "Value") {
1309
+ test = this.equality({ code: subj, prec: P.ATOM }, pattern.expr, subjType);
1310
+ }
1311
+ else if (pattern.kind === "Case") {
1312
+ caseTest(pattern);
1313
+ }
1314
+ if (arm.guard) {
1315
+ const guard = this.cond(arm.guard);
1316
+ test = test === "true" ? guard : `${test} and ${parenIfLow(guard)}`;
1317
+ }
1318
+ const sym = pattern.kind === "Case" ? pattern.binding?.sym : undefined;
1319
+ if (sym) {
1320
+ this.substitutions.delete(sym.id);
1321
+ }
1322
+ return {
1323
+ test,
1324
+ bind: () => {
1325
+ if (bindName && bindSource) {
1326
+ const t = sym ? this.typeOf(sym.type) : null;
1327
+ this.line(`local ${bindName}${t && t !== "any" ? `: ${t}` : ""} = ${bindSource}${t && t !== "any" && pattern.kind === "Case" && ((pattern.res?.kind === "builtin" && pattern.res.name !== "Some") || pattern.res?.kind === "enumCase") ? ` :: ${t}` : ""}`);
1328
+ }
1329
+ },
1330
+ };
1331
+ }
1332
+ tryStmt(s) {
1333
+ const frame = { kind: "try" };
1334
+ const ok = this.temp("ok");
1335
+ const err = this.temp("err");
1336
+ const val = this.temp("value");
1337
+ this.loops.push(frame);
1338
+ const body = this.capture(() => this.block(() => this.block(() => this.stmts(s.body))));
1339
+ this.loops.pop();
1340
+ const flow = frame.usedBreak || frame.usedContinue || frame.usedReturn;
1341
+ this.line(`local ${ok}, ${err}${flow ? `, ${val}` : ""} = pcall(function()`);
1342
+ this.lines.push(...body.map((l) => (l.startsWith("\t") ? l.slice(1) : l)));
1343
+ this.line("end)");
1344
+ this.line(`if not ${ok} then`);
1345
+ this.block(() => {
1346
+ if (s.catchName) {
1347
+ const sym = s.catchName.sym;
1348
+ const name = sym ? this.nameOf(sym) : (0, names_js_1.sanitizeIdentifier)(s.catchName.name);
1349
+ this.line(`local ${name} = ${this.use("__cl_errmsg")}(${err})`);
1350
+ }
1351
+ this.stmts(s.handler);
1352
+ });
1353
+ if (frame.usedReturn) {
1354
+ this.line(`elseif ${err} == 1 then`);
1355
+ this.block(() => this.line(`return ${val}`));
1356
+ }
1357
+ if (frame.usedBreak) {
1358
+ this.line(`elseif ${err} == 2 then`);
1359
+ this.block(() => this.breakStmt());
1360
+ }
1361
+ if (frame.usedContinue) {
1362
+ this.line(`elseif ${err} == 3 then`);
1363
+ this.block(() => this.continueStmt());
1364
+ }
1365
+ this.line("end");
1366
+ }
1367
+ // ---------- expressions ----------
1368
+ /** Expression whose value flows into a new owner (copy for value semantics). */
1369
+ valueOf(e) {
1370
+ const c = this.expr(e);
1371
+ if (e.copy) {
1372
+ return atom(`${this.use("__cl_copy")}(${c.code})`);
1373
+ }
1374
+ return c;
1375
+ }
1376
+ /** Condition with CL++ truthiness (0 and "" are false). */
1377
+ cond(e) {
1378
+ return this.truthy(this.expr(e), (0, types_js_1.prune)(e.ty ?? types_js_1.T.any));
1379
+ }
1380
+ truthy(c, t) {
1381
+ switch (t.k) {
1382
+ case "int":
1383
+ case "float":
1384
+ return `${wrap(c, P.CMP + 1)} ~= 0`;
1385
+ case "string":
1386
+ return `${wrap(c, P.CMP + 1)} ~= ""`;
1387
+ case "lit":
1388
+ return typeof t.value === "boolean" ? c.code : typeof t.value === "string" ? `${wrap(c, P.CMP + 1)} ~= ""` : `${wrap(c, P.CMP + 1)} ~= 0`;
1389
+ default:
1390
+ return c.code;
1391
+ }
1392
+ }
1393
+ truthyCode(e) {
1394
+ const t = (0, types_js_1.prune)(e.ty ?? types_js_1.T.any);
1395
+ const c = this.expr(e);
1396
+ if (t.k === "int" || t.k === "float" || t.k === "string" || (t.k === "lit" && typeof t.value !== "boolean")) {
1397
+ return { code: this.truthy(c, t), prec: P.CMP };
1398
+ }
1399
+ return c;
1400
+ }
1401
+ expr(e) {
1402
+ switch (e.kind) {
1403
+ case "Number":
1404
+ return { code: (0, names_js_1.luauNumber)(e.value), prec: e.value < 0 ? P.UNARY : P.ATOM };
1405
+ case "String":
1406
+ return atom((0, names_js_1.luauString)(e.value));
1407
+ case "Template":
1408
+ return this.template(e);
1409
+ case "Bool":
1410
+ return atom(e.value ? "true" : "false");
1411
+ case "Null":
1412
+ return atom("nil");
1413
+ case "Name":
1414
+ return this.name(e);
1415
+ case "SelfField":
1416
+ if (e.res?.kind === "sharedField") {
1417
+ return atom(this.shared(e.name));
1418
+ }
1419
+ if (e.res?.kind === "dataModel") {
1420
+ return atom("game");
1421
+ }
1422
+ if (e.res?.kind === "global") {
1423
+ return atom(e.res.luau);
1424
+ }
1425
+ return atom(`${this.selfName}.${e.name}`);
1426
+ case "Member":
1427
+ return this.member(e);
1428
+ case "Scope":
1429
+ return this.scopeAccess(e);
1430
+ case "Index":
1431
+ return this.index(e);
1432
+ case "Call":
1433
+ case "New":
1434
+ return this.call(e);
1435
+ case "Unary":
1436
+ return this.unary(e);
1437
+ case "Binary":
1438
+ return this.binary(e);
1439
+ case "Ternary": {
1440
+ const cond = this.cond(e.cond);
1441
+ const a = this.valueOf(e.then);
1442
+ const b = this.valueOf(e.else);
1443
+ return { code: `if ${cond} then ${a.code} else ${b.code}`, prec: P.IF };
1444
+ }
1445
+ case "Function":
1446
+ return this.functionExpr(e);
1447
+ case "Spawn":
1448
+ case "Thread":
1449
+ return this.spawn(e.kind === "Spawn" ? e.call : e.call);
1450
+ case "Await":
1451
+ return this.await(e);
1452
+ case "Join":
1453
+ this.use("__cl_task");
1454
+ return atom(`__cl_await(${this.expr(e.operand).code})`);
1455
+ case "Move":
1456
+ return this.move(e);
1457
+ case "PCall": {
1458
+ const inner = this.expr(e.operand).code;
1459
+ const body = (0, types_js_1.prune)(e.operand.ty ?? types_js_1.T.any).k === "void" ? inner : `return ${inner}`;
1460
+ return { code: `if pcall(function() ${body} end) then 1 else 0`, prec: P.IF };
1461
+ }
1462
+ case "Parallel": {
1463
+ this.use("__cl_parallel");
1464
+ const jobs = e.calls.map((c) => this.spawn(c).code);
1465
+ return atom(`__cl_parallel({ ${jobs.join(", ")} })`);
1466
+ }
1467
+ case "Update":
1468
+ return this.updateExpr(e);
1469
+ case "Cast": {
1470
+ const inner = this.expr(e.operand);
1471
+ const t = this.typeOf(e.ty);
1472
+ return t ? atom(`(${wrap(inner, P.UNARY).toString()} :: ${t})`) : inner;
1473
+ }
1474
+ default:
1475
+ return atom("nil");
1476
+ }
1477
+ }
1478
+ name(e) {
1479
+ const res = e.res;
1480
+ let code;
1481
+ switch (res?.kind) {
1482
+ case "local":
1483
+ code = this.nameOf(res.sym);
1484
+ break;
1485
+ case "func": {
1486
+ const sym = res.sym;
1487
+ code = this.nameOf(sym);
1488
+ if (sym.topLevel && !this.definedFuncs.has(sym.id) && this.fnDepth > 0) {
1489
+ this.referencedFuncs.add(sym.id);
1490
+ }
1491
+ if (sym.topLevel && !this.definedFuncs.has(sym.id)) {
1492
+ this.referencedFuncs.add(sym.id);
1493
+ }
1494
+ break;
1495
+ }
1496
+ case "const":
1497
+ return literalCode(res.value);
1498
+ case "struct":
1499
+ code = this.structRef(res.sym.typeId ?? "");
1500
+ break;
1501
+ case "enum": {
1502
+ const id = res.sym.typeId ?? "";
1503
+ this.enumTablesUsed.add(id);
1504
+ const info = this.file.enumRegistry.get(id);
1505
+ code = info ? info.luauName : (0, names_js_1.sanitizeIdentifier)(e.name);
1506
+ break;
1507
+ }
1508
+ case "module": {
1509
+ const link = this.file.links.find((l) => l.decl.alias === e.name);
1510
+ if (link) {
1511
+ this.usedLinks.add(link);
1512
+ code = this.linkName(link);
1513
+ }
1514
+ else {
1515
+ code = (0, names_js_1.sanitizeIdentifier)(e.name);
1516
+ }
1517
+ break;
1518
+ }
1519
+ case "signal":
1520
+ code = this.nameOf(res.sym);
1521
+ break;
1522
+ case "builtin":
1523
+ code = res.name === "None" ? "nil" : e.name;
1524
+ break;
1525
+ case "global":
1526
+ code = res.luau;
1527
+ break;
1528
+ case "service":
1529
+ code = this.service(res.service);
1530
+ break;
1531
+ case "dataModel":
1532
+ code = "game";
1533
+ break;
1534
+ default:
1535
+ code = (0, names_js_1.sanitizeIdentifier)(e.name);
1536
+ }
1537
+ return atom(code);
1538
+ }
1539
+ member(e) {
1540
+ const res = e.res;
1541
+ switch (res?.kind) {
1542
+ case "const":
1543
+ return literalCode(res.value);
1544
+ case "service":
1545
+ return atom(this.service(res.service));
1546
+ case "enumCase":
1547
+ if (res.anyPayload) {
1548
+ return atom(`{ tag = ${res.index} }`);
1549
+ }
1550
+ return atom(String(res.index));
1551
+ case "rbxEnum":
1552
+ return atom(`Enum.${res.enumName}`);
1553
+ case "rbxEnumItem":
1554
+ return atom(`Enum.${res.enumName}.${res.item}`);
1555
+ case "stdlib":
1556
+ return atom(this.stdValue(res.module, res.member));
1557
+ case "rbxChild": {
1558
+ const obj = this.expr(e.object);
1559
+ const t = this.typeOf(e.ty);
1560
+ if (res.safe) {
1561
+ return atom(`(${wrap(obj, P.ATOM)}:WaitForChild(${(0, names_js_1.luauString)(res.childName)})${t && t !== "Instance" ? ` :: ${t}` : ""})`);
1562
+ }
1563
+ return atom(`${wrap(obj, P.ATOM)}${memberAccess(e.name)}`);
1564
+ }
1565
+ case "moduleMember":
1566
+ if (res.moduleId === "namespace") {
1567
+ return atom(`${(0, names_js_1.sanitizeIdentifier)(res.alias)}${memberAccess(res.member)}`);
1568
+ }
1569
+ break;
1570
+ case "vectorComponent": {
1571
+ const obj = this.expr(e.object);
1572
+ return atom(`${wrap(obj, P.ATOM)}.${res.component}`);
1573
+ }
1574
+ default:
1575
+ break;
1576
+ }
1577
+ const obj = this.expr(e.object);
1578
+ return atom(`${wrap(obj, P.ATOM)}${memberAccess(e.name)}`);
1579
+ }
1580
+ stdValue(module, member) {
1581
+ const fn = (0, stdlib_js_1.stdModule)(module).get(member);
1582
+ if (!fn) {
1583
+ return "nil";
1584
+ }
1585
+ if (module === "axiom" && !fn.inline) {
1586
+ this.axiomRuntime = true;
1587
+ const link = this.file.links.find((l) => l.target.kind === "std" && l.target.module === "axiom");
1588
+ return `${link ? this.linkName(link) : "Axiom"}.${member}`;
1589
+ }
1590
+ if (fn.helper) {
1591
+ return this.use(fn.helper);
1592
+ }
1593
+ const params = fn.params.map((p) => p.name);
1594
+ const body = this.std(fn.inline, params);
1595
+ return `function(${params.join(", ")}) return ${body} end`;
1596
+ }
1597
+ scopeAccess(e) {
1598
+ const obj = this.expr(e.object);
1599
+ if (e.res?.kind === "luauMethod") {
1600
+ return atom(`${wrap(obj, P.ATOM)}:${e.name}`);
1601
+ }
1602
+ if (e.res?.kind === "stdlib") {
1603
+ return atom(this.stdValue(e.res.module, e.res.member));
1604
+ }
1605
+ if (e.res?.kind === "library") {
1606
+ return atom(`${e.res.lib}.${e.res.member}`);
1607
+ }
1608
+ return atom(`${wrap(obj, P.ATOM)}.${e.name}`);
1609
+ }
1610
+ index(e) {
1611
+ const objType = (0, types_js_1.prune)(e.object.ty ?? types_js_1.T.any);
1612
+ const obj = this.expr(e.object);
1613
+ if (e.index.kind === "Binary" && e.index.op === "..") {
1614
+ return atom(`${this.use("__cl_slice")}(${obj.code}, ${this.expr(e.index.left).code}, ${this.expr(e.index.right).code})`);
1615
+ }
1616
+ switch (objType.k) {
1617
+ case "list":
1618
+ return atom(`${wrap(obj, P.ATOM)}[${this.plusOne(e.index)}]`);
1619
+ case "string":
1620
+ case "lit":
1621
+ return atom(`${this.use("__cl_char")}(${obj.code}, ${this.expr(e.index).code})`);
1622
+ case "instance":
1623
+ return atom(`${wrap(obj, P.ATOM)}:FindFirstChild(${this.expr(e.index).code})`);
1624
+ case "tuple":
1625
+ return atom(`(select(${this.plusOne(e.index)}, ${obj.code}))`);
1626
+ default:
1627
+ return atom(`${wrap(obj, P.ATOM)}[${this.expr(e.index).code}]`);
1628
+ }
1629
+ }
1630
+ plusOne(e) {
1631
+ if (e.kind === "Number") {
1632
+ return (0, names_js_1.luauNumber)(e.value + 1);
1633
+ }
1634
+ return `${wrap(this.expr(e), P.ADD)} + 1`;
1635
+ }
1636
+ unary(e) {
1637
+ switch (e.op) {
1638
+ case "-": {
1639
+ if (e.operand.kind === "Number") {
1640
+ return { code: (0, names_js_1.luauNumber)(-e.operand.value), prec: P.UNARY };
1641
+ }
1642
+ return { code: `-${wrap(this.expr(e.operand), P.UNARY)}`, prec: P.UNARY };
1643
+ }
1644
+ case "not": {
1645
+ const t = (0, types_js_1.prune)(e.operand.ty ?? types_js_1.T.any);
1646
+ const inner = this.expr(e.operand);
1647
+ if (t.k === "int" || t.k === "float") {
1648
+ return { code: `${wrap(inner, P.CMP + 1)} == 0`, prec: P.CMP };
1649
+ }
1650
+ if (t.k === "string") {
1651
+ return { code: `${wrap(inner, P.CMP + 1)} == ""`, prec: P.CMP };
1652
+ }
1653
+ return { code: `not ${wrap(inner, P.UNARY)}`, prec: P.UNARY };
1654
+ }
1655
+ case "~":
1656
+ return atom(`bit32.bnot(${this.expr(e.operand).code})`);
1657
+ default:
1658
+ return atom("nil");
1659
+ }
1660
+ }
1661
+ binary(e) {
1662
+ const lt = (0, types_js_1.prune)(e.left.ty ?? types_js_1.T.any);
1663
+ const rt = (0, types_js_1.prune)(e.right.ty ?? types_js_1.T.any);
1664
+ switch (e.op) {
1665
+ case "and":
1666
+ case "or": {
1667
+ if (e.res?.kind === "builtin" && e.res.name === "valueOr") {
1668
+ return { code: `${wrap(this.expr(e.left), P.OR)} or ${wrap(this.expr(e.right), P.OR + 1)}`, prec: P.OR };
1669
+ }
1670
+ const prec = e.op === "and" ? P.AND : P.OR;
1671
+ const l = this.truthyCode(e.left);
1672
+ const r = this.truthyCode(e.right);
1673
+ return { code: `${wrap(l, prec)} ${e.op} ${wrap(r, prec + 1)}`, prec };
1674
+ }
1675
+ case ".:": {
1676
+ const l = this.concatPart(e.left);
1677
+ const r = this.concatPart(e.right);
1678
+ return { code: `${wrap(l, P.CONCAT)} .. ${wrap(r, P.CONCAT)}`, prec: P.CONCAT };
1679
+ }
1680
+ case "+":
1681
+ case "-":
1682
+ case "*": {
1683
+ if (e.res?.kind === "func") {
1684
+ return atom(`${this.nameOf(e.res.sym)}(${this.expr(e.left).code}, ${this.expr(e.right).code})`);
1685
+ }
1686
+ const prec = e.op === "*" ? P.MUL : P.ADD;
1687
+ return { code: `${wrap(this.expr(e.left), prec)} ${e.op} ${wrap(this.expr(e.right), prec + 1)}`, prec };
1688
+ }
1689
+ case "/":
1690
+ if (isIntPair(lt, rt)) {
1691
+ return atom(`${this.use("__cl_idiv")}(${this.expr(e.left).code}, ${this.expr(e.right).code})`);
1692
+ }
1693
+ return { code: `${wrap(this.expr(e.left), P.MUL)} / ${wrap(this.expr(e.right), P.MUL + 1)}`, prec: P.MUL };
1694
+ case "%":
1695
+ if (lt.k === "datatype") {
1696
+ return { code: `${wrap(this.expr(e.left), P.MUL)} % ${wrap(this.expr(e.right), P.MUL + 1)}`, prec: P.MUL };
1697
+ }
1698
+ return atom(`math.fmod(${this.expr(e.left).code}, ${this.expr(e.right).code})`);
1699
+ case "==":
1700
+ case "!=": {
1701
+ if (((0, types_js_1.isValueTable)(lt) || (0, types_js_1.isValueTable)(rt)) && e.left.kind !== "Null" && e.right.kind !== "Null") {
1702
+ const call = `${this.use("__cl_eq")}(${this.expr(e.left).code}, ${this.expr(e.right).code})`;
1703
+ return e.op === "==" ? atom(call) : { code: `not ${call}`, prec: P.UNARY };
1704
+ }
1705
+ return { code: `${wrap(this.expr(e.left), P.CMP + 1)} ${e.op === "==" ? "==" : "~="} ${wrap(this.expr(e.right), P.CMP + 1)}`, prec: P.CMP };
1706
+ }
1707
+ case "<":
1708
+ case "<=":
1709
+ case ">":
1710
+ case ">=": {
1711
+ // generic operands: Luau cannot prove T supports ordering, CL++ checked the call sites
1712
+ const side = (x) => ((0, types_js_1.prune)(x.ty ?? types_js_1.T.any).k === "param" ? `(${this.expr(x).code} :: any)` : wrap(this.expr(x), P.CMP + 1));
1713
+ return { code: `${side(e.left)} ${e.op} ${side(e.right)}`, prec: P.CMP };
1714
+ }
1715
+ case "&":
1716
+ return atom(`bit32.band(${this.expr(e.left).code}, ${this.expr(e.right).code})`);
1717
+ case "|":
1718
+ return atom(`bit32.bor(${this.expr(e.left).code}, ${this.expr(e.right).code})`);
1719
+ case "^":
1720
+ return atom(`bit32.bxor(${this.expr(e.left).code}, ${this.expr(e.right).code})`);
1721
+ case "shl":
1722
+ return atom(`bit32.lshift(${this.expr(e.left).code}, ${this.expr(e.right).code})`);
1723
+ case "shr":
1724
+ return atom(`bit32.arshift(${this.expr(e.left).code}, ${this.expr(e.right).code})`);
1725
+ default:
1726
+ return atom("nil");
1727
+ }
1728
+ }
1729
+ concatPart(e) {
1730
+ const t = (0, types_js_1.prune)(e.ty ?? types_js_1.T.any);
1731
+ const c = this.expr(e);
1732
+ if (t.k === "string" || t.k === "int" || t.k === "float" || (t.k === "lit" && typeof t.value !== "boolean")) {
1733
+ return c;
1734
+ }
1735
+ if (t.k === "bool" || t.k === "lit" || t.k === "instance" || t.k === "rbxEnumItem" || (t.k === "datatype" && t.name !== "Vector3" && t.name !== "Vector2")) {
1736
+ return atom(`tostring(${c.code})`);
1737
+ }
1738
+ return atom(`${this.use("__cl_str")}(${c.code})`);
1739
+ }
1740
+ template(e) {
1741
+ if (e.parts.every((p) => typeof p === "string")) {
1742
+ return atom((0, names_js_1.luauString)(e.parts.join("")));
1743
+ }
1744
+ let out = "`";
1745
+ for (const part of e.parts) {
1746
+ if (typeof part === "string") {
1747
+ out += (0, names_js_1.interpolationText)(part);
1748
+ continue;
1749
+ }
1750
+ const constant = part.res?.kind === "const" ? part.res.value : undefined;
1751
+ if (typeof constant === "string" || typeof constant === "number") {
1752
+ out += (0, names_js_1.interpolationText)(String(constant));
1753
+ continue;
1754
+ }
1755
+ const t = (0, types_js_1.prune)(part.ty ?? types_js_1.T.any);
1756
+ let code = this.expr(part).code;
1757
+ if ((0, types_js_1.isValueTable)(t) || (t.k === "datatype" && (t.name === "Vector3" || t.name === "Vector2")) || t.k === "null" || t.k === "option") {
1758
+ code = `${this.use("__cl_str")}(${code})`;
1759
+ }
1760
+ if (code.includes("{") || code.includes("`")) {
1761
+ const tmp = this.temp("s");
1762
+ this.line(`local ${tmp} = ${code}`);
1763
+ code = tmp;
1764
+ }
1765
+ out += `{${code}}`;
1766
+ }
1767
+ return atom(`${out}\``);
1768
+ }
1769
+ functionExpr(e) {
1770
+ const { list, prologue } = this.paramList(e.params, null);
1771
+ const ret = (0, types_js_1.prune)(e.ty ?? types_js_1.T.any);
1772
+ const retType = ret.k === "func" ? this.returnType(ret.ret, false) : null;
1773
+ const savedLoops = this.loops.splice(0);
1774
+ this.fnDepth++;
1775
+ const body = this.capture(() => this.block(() => {
1776
+ for (const p of prologue) {
1777
+ p();
1778
+ }
1779
+ if (Array.isArray(e.body)) {
1780
+ this.stmts(e.body);
1781
+ }
1782
+ else {
1783
+ const v = this.valueOf(e.body);
1784
+ if ((0, types_js_1.prune)(e.body.ty ?? types_js_1.T.any).k === "void" && isCallCode(v.code)) {
1785
+ this.line(v.code);
1786
+ }
1787
+ else {
1788
+ this.line(`return ${v.code}`);
1789
+ }
1790
+ }
1791
+ }));
1792
+ this.fnDepth--;
1793
+ this.loops.push(...savedLoops);
1794
+ const head = `function(${list.join(", ")})${retType && retType !== "()" ? `: ${retType}` : ""}`;
1795
+ if (body.length === 1 && !Array.isArray(e.body) && body[0].trim().length < 60) {
1796
+ return atom(`${head} ${body[0].trim()} end`);
1797
+ }
1798
+ return atom(`${head}\n${body.join("\n")}\n${"\t".repeat(this.indent)}end`);
1799
+ }
1800
+ spawn(call) {
1801
+ this.use("__cl_task");
1802
+ const t = (0, types_js_1.prune)(call.ty ?? types_js_1.T.any);
1803
+ const inner = this.callRaw(call);
1804
+ const body = t.k === "void" ? inner : `return ${inner}`;
1805
+ return atom(`__cl_spawn(function() ${body} end)`);
1806
+ }
1807
+ /** A call to an async function emitted without the implicit spawn. */
1808
+ callRaw(call) {
1809
+ if (call.kind === "Call") {
1810
+ const saved = call.__raw;
1811
+ call.__raw = true;
1812
+ const code = this.expr(call).code;
1813
+ call.__raw = saved;
1814
+ return code;
1815
+ }
1816
+ return this.expr(call).code;
1817
+ }
1818
+ await(e) {
1819
+ const t = (0, types_js_1.prune)(e.operand.ty ?? types_js_1.T.any);
1820
+ if (t.k === "rbxSignal") {
1821
+ return atom(`${wrap(this.expr(e.operand), P.ATOM)}:Wait()`);
1822
+ }
1823
+ if (e.operand.kind === "Call" && this.isAsyncCall(e.operand)) {
1824
+ return atom(this.callRaw(e.operand));
1825
+ }
1826
+ if (t.k === "task") {
1827
+ this.use("__cl_task");
1828
+ return atom(`__cl_await(${this.expr(e.operand).code})`);
1829
+ }
1830
+ return this.expr(e.operand);
1831
+ }
1832
+ isAsyncCall(e) {
1833
+ if (e.kind !== "Call") {
1834
+ return false;
1835
+ }
1836
+ const res = e.callee.res;
1837
+ if (res && (res.kind === "func" || res.kind === "local")) {
1838
+ return Boolean(res.sym.decl?.isAsync);
1839
+ }
1840
+ if (res && res.kind === "method") {
1841
+ return Boolean(this.file.structRegistry.get(res.struct)?.methods.get(res.name)?.decl.isAsync);
1842
+ }
1843
+ return false;
1844
+ }
1845
+ move(e) {
1846
+ const src = this.place(e.operand);
1847
+ const tmp = this.temp("moved");
1848
+ this.line(`local ${tmp} = ${src}`);
1849
+ const t = (0, types_js_1.prune)(e.operand.ty ?? types_js_1.T.any);
1850
+ this.line(`${src} = ${t.k === "string" ? '""' : t.k === "list" || t.k === "dict" ? "{}" : "nil"}`);
1851
+ return atom(tmp);
1852
+ }
1853
+ updateExpr(e) {
1854
+ const target = this.place(e.target);
1855
+ const op = e.delta > 0 ? "+=" : "-=";
1856
+ if (e.prefix) {
1857
+ this.line(`${target} ${op} 1`);
1858
+ return atom(target);
1859
+ }
1860
+ const tmp = this.temp("old");
1861
+ this.line(`local ${tmp} = ${target}`);
1862
+ this.line(`${target} ${op} 1`);
1863
+ return atom(tmp);
1864
+ }
1865
+ // ---------- calls ----------
1866
+ args(args, rest) {
1867
+ const out = args.map((a) => (a ? this.valueOf(a).code : "nil"));
1868
+ for (const r of rest || []) {
1869
+ out.push(this.valueOf(r).code);
1870
+ }
1871
+ return out.join(", ");
1872
+ }
1873
+ call(e) {
1874
+ const res = e.res;
1875
+ if (!res || res.kind !== "call") {
1876
+ if (e.kind === "Call") {
1877
+ return atom(`${wrap(this.expr(e.callee), P.ATOM)}(${e.args.map((a) => this.valueOf(a.value).code).join(", ")})`);
1878
+ }
1879
+ return atom("nil");
1880
+ }
1881
+ const call = res.call;
1882
+ const args = res.args;
1883
+ switch (call.form) {
1884
+ case "struct":
1885
+ return atom(`${this.structRef(call.id)}.new(${this.args(args)})`);
1886
+ case "instance": {
1887
+ if (call.props.length === 0 && !call.parent) {
1888
+ return atom(`Instance.new(${(0, names_js_1.luauString)(call.cls)})`);
1889
+ }
1890
+ const tmp = this.temp("i");
1891
+ this.instanceInto(e, `local ${tmp}`, tmp);
1892
+ return atom(tmp);
1893
+ }
1894
+ case "datatype": {
1895
+ if (call.props.length === 0) {
1896
+ return atom(`${call.name}.new(${this.args(args, res.variadicArgs)})`);
1897
+ }
1898
+ const tmp = this.temp("d");
1899
+ this.datatypeInto(e, `local ${tmp}`, tmp);
1900
+ return atom(tmp);
1901
+ }
1902
+ case "enumCase":
1903
+ return atom(`{ tag = ${call.index}, value = ${args[0] ? this.valueOf(args[0]).code : "nil"} }`);
1904
+ case "some":
1905
+ case "variant":
1906
+ return args[0] ? this.valueOf(args[0]) : atom("nil");
1907
+ case "ok":
1908
+ return atom(`{ ok = true, value = ${args[0] ? this.valueOf(args[0]).code : "nil"}, error = nil }`);
1909
+ case "err":
1910
+ return atom(`{ ok = false, value = nil, error = ${args[0] ? this.valueOf(args[0]).code : "nil"} }`);
1911
+ case "builtin":
1912
+ return this.builtin(call.name, args, call.elemType);
1913
+ case "std":
1914
+ return this.stdCall(call.module, call.fn, args);
1915
+ case "signalFire":
1916
+ this.fireSignal(e, call.sym, args);
1917
+ return atom("nil");
1918
+ case "classArg": {
1919
+ const callee = e.callee;
1920
+ const obj = this.expr(callee.object);
1921
+ const rest = this.args(args, res.variadicArgs);
1922
+ const code = `${wrap(obj, P.ATOM)}:${callee.name}(${(0, names_js_1.luauString)(call.cls)}${rest ? `, ${rest}` : ""})`;
1923
+ if (call.cast) {
1924
+ const t = this.typeOf(e.ty);
1925
+ return t ? atom(`(${code} :: ${t})`) : atom(code);
1926
+ }
1927
+ return atom(code);
1928
+ }
1929
+ case "cast": {
1930
+ const inner = this.plainCall(e, args, res.variadicArgs);
1931
+ const t = this.typeOf(e.ty);
1932
+ return t ? atom(`(${inner} :: ${t})`) : atom(inner);
1933
+ }
1934
+ case "service":
1935
+ return atom(this.service(call.service));
1936
+ case "convert":
1937
+ return this.convert(call.to, args[0]);
1938
+ case "stringMethod": {
1939
+ const callee = e.callee;
1940
+ return atom(`${wrap(this.expr(callee.object), P.ATOM)}:${call.name}(${this.args(args, res.variadicArgs)})`);
1941
+ }
1942
+ case "observe": {
1943
+ const sym = call.sym;
1944
+ return atom(`table.insert(${this.nameOf(sym)}_changed, ${args[0] ? this.expr(args[0]).code : "nil"})`);
1945
+ }
1946
+ case "table": {
1947
+ const parts = call.keys.map((k, i) => `${fieldKey(k)} = ${args[i] ? this.valueOf(args[i]).code : "nil"}`);
1948
+ return atom(parts.length ? `{ ${parts.join(", ")} }` : "{}");
1949
+ }
1950
+ case "luauMethod":
1951
+ case "call":
1952
+ default: {
1953
+ const ce = e;
1954
+ const code = this.plainCall(ce, args, res.variadicArgs);
1955
+ if (this.isAsyncCall(ce) && !ce.__raw) {
1956
+ this.use("__cl_task");
1957
+ return atom(`__cl_spawn(function() return ${code} end)`);
1958
+ }
1959
+ return atom(code);
1960
+ }
1961
+ }
1962
+ }
1963
+ plainCall(e, args, rest) {
1964
+ const callee = e.callee;
1965
+ const argText = this.args(args, rest);
1966
+ if (callee.kind === "Member") {
1967
+ const r = callee.res;
1968
+ const isMethod = (r?.kind === "rbxMethod") ||
1969
+ (r?.kind === "method" && !r.isStatic) ||
1970
+ (r?.kind === "datatypeMember" && r.isMethod) ||
1971
+ (r?.kind === "library" && r.stringMethod);
1972
+ if (isMethod) {
1973
+ return `${wrap(this.expr(callee.object), P.ATOM)}:${callee.name}(${argText})`;
1974
+ }
1975
+ if (r?.kind === "method" && r.isStatic) {
1976
+ return `${this.structRef(r.struct)}.${callee.name}(${argText})`;
1977
+ }
1978
+ }
1979
+ if (callee.kind === "SelfField" && callee.res?.kind === "method" && !callee.res.isStatic) {
1980
+ return `${this.selfName}:${callee.name}(${argText})`;
1981
+ }
1982
+ if (callee.kind === "Scope" && callee.res?.kind === "luauMethod") {
1983
+ return `${wrap(this.expr(callee.object), P.ATOM)}:${callee.name}(${argText})`;
1984
+ }
1985
+ return `${wrap(this.expr(callee), P.ATOM)}(${argText})`;
1986
+ }
1987
+ convert(to, arg) {
1988
+ if (!arg) {
1989
+ return atom("nil");
1990
+ }
1991
+ const t = (0, types_js_1.prune)(arg.ty ?? types_js_1.T.any);
1992
+ const c = this.expr(arg);
1993
+ switch (to) {
1994
+ case "int":
1995
+ if (t.k === "int" || t.k === "enum") {
1996
+ return c;
1997
+ }
1998
+ if (t.k === "rbxEnumItem") {
1999
+ return atom(`${wrap(c, P.ATOM)}.Value`);
2000
+ }
2001
+ if (t.k === "string" || t.k === "lit") {
2002
+ return atom(`${this.use("__cl_toint")}(${c.code})`);
2003
+ }
2004
+ return atom(`${this.use("__cl_trunc")}(${c.code})`);
2005
+ case "float":
2006
+ if (t.k === "string" || (t.k === "lit" && typeof t.value === "string")) {
2007
+ return atom(`${this.use("__cl_tonumber")}(${c.code})`);
2008
+ }
2009
+ return c;
2010
+ case "string":
2011
+ if (t.k === "string") {
2012
+ return c;
2013
+ }
2014
+ if (t.k === "int" || t.k === "float" || t.k === "bool") {
2015
+ return atom(`tostring(${c.code})`);
2016
+ }
2017
+ return atom(`${this.use("__cl_str")}(${c.code})`);
2018
+ case "bool":
2019
+ return { code: this.truthy(c, t), prec: P.CMP };
2020
+ default:
2021
+ return c;
2022
+ }
2023
+ }
2024
+ builtin(name, args, elemType) {
2025
+ const a = (i) => (args[i] ? this.expr(args[i]) : atom("nil"));
2026
+ const v = (i) => (args[i] ? this.valueOf(args[i]) : atom("nil"));
2027
+ switch (name) {
2028
+ case "len": {
2029
+ const t = (0, types_js_1.prune)(args[0]?.ty ?? types_js_1.T.any);
2030
+ if (t.k === "string" || t.k === "lit") {
2031
+ return atom(`${this.use("__cl_strlen")}(${a(0).code})`);
2032
+ }
2033
+ if (t.k === "dict" || t.k === "table") {
2034
+ return atom(`${this.use("__cl_count")}(${a(0).code})`);
2035
+ }
2036
+ if (t.k === "buffer") {
2037
+ return atom(`buffer.len(${a(0).code})`);
2038
+ }
2039
+ return { code: `#${wrap(a(0), P.UNARY)}`, prec: P.UNARY };
2040
+ }
2041
+ case "list":
2042
+ case "array": {
2043
+ const items = args.map((x) => (x ? this.valueOf(x).code : "nil"));
2044
+ return atom(items.length ? `{ ${items.join(", ")} }` : "{}");
2045
+ }
2046
+ case "dictionary": {
2047
+ const parts = [];
2048
+ for (let i = 0; i + 1 < args.length; i += 2) {
2049
+ const k = args[i];
2050
+ const key = k.kind === "String" && (0, names_js_1.isIdentifier)(k.value) ? k.value : `[${this.expr(k).code}]`;
2051
+ parts.push(`${key} = ${this.valueOf(args[i + 1]).code}`);
2052
+ }
2053
+ return atom(parts.length ? `{ ${parts.join(", ")} }` : "{}");
2054
+ }
2055
+ case "push":
2056
+ return atom(`table.insert(${a(0).code}, ${v(1).code})`);
2057
+ case "pop":
2058
+ return atom(`table.remove(${a(0).code})`);
2059
+ case "insert":
2060
+ return atom(`table.insert(${a(0).code}, ${args[1] ? this.plusOne(args[1]) : "1"}, ${v(2).code})`);
2061
+ case "remove": {
2062
+ const t = (0, types_js_1.prune)(args[0]?.ty ?? types_js_1.T.any);
2063
+ if (t.k === "dict" || t.k === "table") {
2064
+ return atom(`${this.use("__cl_take")}(${a(0).code}, ${a(1).code})`);
2065
+ }
2066
+ return atom(`table.remove(${a(0).code}, ${args[1] ? this.plusOne(args[1]) : "1"})`);
2067
+ }
2068
+ case "find": {
2069
+ if (elemType && (0, types_js_1.isValueTable)(elemType)) {
2070
+ return atom(`${this.use("__cl_findeq")}(${a(0).code}, ${a(1).code})`);
2071
+ }
2072
+ return { code: `(table.find(${a(0).code}, ${a(1).code}) or 0) - 1`, prec: P.ADD };
2073
+ }
2074
+ case "sort":
2075
+ return atom(`${this.use("__cl_sorted")}(${a(0).code})`);
2076
+ case "mutex":
2077
+ this.use("__cl_mutex");
2078
+ return atom("__cl_mutex()");
2079
+ case "lock":
2080
+ this.use("__cl_mutex");
2081
+ return atom(`__cl_lock(${a(0).code})`);
2082
+ case "unlock":
2083
+ this.use("__cl_mutex");
2084
+ return atom(`__cl_unlock(${a(0).code})`);
2085
+ case "fetch_add": {
2086
+ const target = this.place(args[0]);
2087
+ const tmp = this.temp("old");
2088
+ this.line(`local ${tmp} = ${target}`);
2089
+ this.line(`${target} += ${a(1).code}`);
2090
+ return atom(tmp);
2091
+ }
2092
+ case "atomic_load":
2093
+ return a(0);
2094
+ default:
2095
+ return atom("nil");
2096
+ }
2097
+ }
2098
+ stdCall(module, member, args) {
2099
+ const fn = (0, stdlib_js_1.stdModule)(module).get(member);
2100
+ if (!fn) {
2101
+ return atom("nil");
2102
+ }
2103
+ const codes = args.map((x) => (x ? this.valueOf(x) : atom("nil")));
2104
+ while (codes.length < fn.params.length && fn.params[codes.length]?.optional) {
2105
+ break;
2106
+ }
2107
+ if (fn.inline) {
2108
+ const multi = /@\d/.test(fn.inline);
2109
+ const simple = args.every((x) => !x || isSimple(x));
2110
+ if (!multi || simple) {
2111
+ return { code: this.std(fn.inline, codes.map((c) => c.code), codes), prec: inlinePrec(fn.inline) };
2112
+ }
2113
+ }
2114
+ if (fn.helper) {
2115
+ return atom(`${this.use(fn.helper)}(${codes.map((c) => c.code).join(", ")})`);
2116
+ }
2117
+ this.axiomRuntime = true;
2118
+ const link = this.file.links.find((l) => l.target.kind === "std" && l.target.module === module);
2119
+ const alias = link ? this.linkName(link) : "Axiom";
2120
+ return atom(`${alias}.${member}(${codes.map((c) => c.code).join(", ")})`);
2121
+ }
2122
+ std(template, args, codes) {
2123
+ return template.replace(/[$@](\d)/g, (_m, n) => {
2124
+ const i = Number(n) - 1;
2125
+ const code = codes?.[i];
2126
+ if (code && code.prec < P.ATOM) {
2127
+ return `(${code.code})`;
2128
+ }
2129
+ return args[i] ?? "nil";
2130
+ });
2131
+ }
2132
+ }
2133
+ exports.LuauEmitter = LuauEmitter;
2134
+ // ---------- helpers ----------
2135
+ /** Luau infers the type of calls and member reads on its own; annotations there are noise. */
2136
+ function isInferredByLuau(e) {
2137
+ return e.kind === "Call" || e.kind === "Member" || e.kind === "Index" || e.kind === "Await";
2138
+ }
2139
+ function wrap(c, min) {
2140
+ return c.prec < min ? `(${c.code})` : c.code;
2141
+ }
2142
+ function wrapCode(code, min) {
2143
+ return /^[\w.:"()[\]]+$/.test(code) || min <= P.ADD ? code : `(${code})`;
2144
+ }
2145
+ function parenIfLow(code) {
2146
+ return /\s(and|or)\s/.test(code) ? `(${code})` : code;
2147
+ }
2148
+ function optionalType(t) {
2149
+ if (t.endsWith("?") || t === "any") {
2150
+ return t;
2151
+ }
2152
+ return /->|\|/.test(t) ? `(${t})?` : `${t}?`;
2153
+ }
2154
+ function safeName(name) {
2155
+ const s = (0, names_js_1.sanitizeIdentifier)(name);
2156
+ return names_js_1.LUAU_KEYWORDS.has(s) ? `${s}_` : s;
2157
+ }
2158
+ function fieldKey(name) {
2159
+ return (0, names_js_1.isIdentifier)(name) ? name : `[${(0, names_js_1.luauString)(name)}]`;
2160
+ }
2161
+ function memberAccess(name) {
2162
+ return (0, names_js_1.isIdentifier)(name) ? `.${name}` : `[${(0, names_js_1.luauString)(name)}]`;
2163
+ }
2164
+ function literal(value) {
2165
+ if (value === null) {
2166
+ return "nil";
2167
+ }
2168
+ if (typeof value === "string") {
2169
+ return (0, names_js_1.luauString)(value);
2170
+ }
2171
+ if (typeof value === "number") {
2172
+ return (0, names_js_1.luauNumber)(value);
2173
+ }
2174
+ return value ? "true" : "false";
2175
+ }
2176
+ function literalCode(value) {
2177
+ return { code: literal(value), prec: typeof value === "number" && value < 0 ? P.UNARY : P.ATOM };
2178
+ }
2179
+ function inlinePrec(template) {
2180
+ if (/^\w[\w.]*\([^()]*(\([^()]*\))*[^()]*\)$/.test(template) || /^[\w.]+$/.test(template)) {
2181
+ return P.ATOM;
2182
+ }
2183
+ if (/ ~= | == | <= | >= | < | > /.test(template)) {
2184
+ return / and | or /.test(template) ? P.AND : P.CMP;
2185
+ }
2186
+ if (/ [+-] /.test(template)) {
2187
+ return P.ADD;
2188
+ }
2189
+ if (/ [*/%] /.test(template)) {
2190
+ return P.MUL;
2191
+ }
2192
+ if (/\^/.test(template)) {
2193
+ return P.POW;
2194
+ }
2195
+ return P.UNARY;
2196
+ }
2197
+ function isCallCode(code) {
2198
+ return /\)$/.test(code.trim()) && !/^\(/.test(code.trim()) && !/^if /.test(code.trim());
2199
+ }
2200
+ function isSimple(e) {
2201
+ return e.kind === "Name" || e.kind === "Number" || e.kind === "String" || e.kind === "Bool" || e.kind === "SelfField" || (e.kind === "Member" && isSimple(e.object));
2202
+ }
2203
+ function isIntLikeType(t) {
2204
+ const p = (0, types_js_1.prune)(t);
2205
+ return p.k === "int";
2206
+ }
2207
+ function isIntPair(a, b) {
2208
+ const isInt = (t) => t.k === "int" || (t.k === "lit" && typeof t.value === "number" && Number.isInteger(t.value));
2209
+ return isInt(a) && isInt(b);
2210
+ }
2211
+ function isSym(e, sym) {
2212
+ return e.kind === "Name" && e.res?.kind === "local" && e.res.sym.id === sym.id;
2213
+ }
2214
+ function assigns(body, sym) {
2215
+ let found = false;
2216
+ const visit = (n) => {
2217
+ if (found || !n || typeof n !== "object") {
2218
+ return;
2219
+ }
2220
+ if (Array.isArray(n)) {
2221
+ n.forEach(visit);
2222
+ return;
2223
+ }
2224
+ const rec = n;
2225
+ if ((rec.kind === "Assign" || rec.kind === "Update") && rec.target && isSym(rec.target, sym)) {
2226
+ found = true;
2227
+ return;
2228
+ }
2229
+ if (rec.kind === "Function") {
2230
+ found = found || JSON.stringify(Object.keys(rec)).length < 0;
2231
+ }
2232
+ for (const [k, v] of Object.entries(n)) {
2233
+ if (k !== "ty" && k !== "res" && k !== "span" && k !== "sym" && k !== "narrowedFrom") {
2234
+ visit(v);
2235
+ }
2236
+ }
2237
+ };
2238
+ visit(body);
2239
+ return found;
2240
+ }
2241
+ function isInvariant(e, body) {
2242
+ if (e.kind === "Number") {
2243
+ return true;
2244
+ }
2245
+ if (e.kind === "Name") {
2246
+ if (e.res?.kind === "const") {
2247
+ return true;
2248
+ }
2249
+ if (e.res?.kind === "local") {
2250
+ const sym = e.res.sym;
2251
+ return !sym.mutable || !assigns(body, sym);
2252
+ }
2253
+ }
2254
+ if (e.kind === "Binary" && (e.op === "+" || e.op === "-" || e.op === "*")) {
2255
+ return isInvariant(e.left, body) && isInvariant(e.right, body);
2256
+ }
2257
+ if (e.kind === "Member" && e.res?.kind === "const") {
2258
+ return true;
2259
+ }
2260
+ return false;
2261
+ }
2262
+ function trimTrailingBreak(body) {
2263
+ if (body.length && body[body.length - 1].kind === "Break") {
2264
+ return body.slice(0, -1);
2265
+ }
2266
+ return body;
2267
+ }
2268
+ /** A `break` before the end of a switch case (needs a `repeat … until true` wrapper). */
2269
+ function hasInnerBreak(body) {
2270
+ const trimmed = trimTrailingBreak(body);
2271
+ let found = false;
2272
+ const visit = (s) => {
2273
+ if (found) {
2274
+ return;
2275
+ }
2276
+ switch (s.kind) {
2277
+ case "Break":
2278
+ found = true;
2279
+ return;
2280
+ case "If":
2281
+ s.then.forEach(visit);
2282
+ s.else?.forEach(visit);
2283
+ return;
2284
+ case "Block":
2285
+ s.body.forEach(visit);
2286
+ return;
2287
+ case "Match":
2288
+ s.arms.forEach((a) => a.body.forEach(visit));
2289
+ return;
2290
+ case "Try":
2291
+ s.body.forEach(visit);
2292
+ s.handler.forEach(visit);
2293
+ return;
2294
+ default:
2295
+ return;
2296
+ }
2297
+ };
2298
+ trimmed.forEach(visit);
2299
+ return found;
2300
+ }
2301
+ function walkStmt(s, fn, depth) {
2302
+ fn(s, depth);
2303
+ const inner = (list, d = depth) => list?.forEach((x) => walkStmt(x, fn, d));
2304
+ switch (s.kind) {
2305
+ case "If":
2306
+ inner(s.then);
2307
+ inner(s.else);
2308
+ break;
2309
+ case "While":
2310
+ case "ForIn":
2311
+ inner(s.body);
2312
+ break;
2313
+ case "For":
2314
+ inner(s.body);
2315
+ break;
2316
+ case "Block":
2317
+ inner(s.body);
2318
+ break;
2319
+ case "Try":
2320
+ inner(s.body);
2321
+ inner(s.handler);
2322
+ break;
2323
+ case "Match":
2324
+ s.arms.forEach((a) => inner(a.body));
2325
+ break;
2326
+ case "Switch":
2327
+ s.cases.forEach((k) => inner(k.body));
2328
+ break;
2329
+ case "Decl":
2330
+ if (s.decl.kind === "Func") {
2331
+ inner(s.decl.body, depth + 1);
2332
+ }
2333
+ else if (s.decl.kind === "Struct") {
2334
+ s.decl.methods.forEach((m) => inner(m.body, depth + 1));
2335
+ }
2336
+ else if (s.decl.kind === "Namespace") {
2337
+ s.decl.funcs.forEach((f) => inner(f.body, depth + 1));
2338
+ }
2339
+ break;
2340
+ default:
2341
+ break;
2342
+ }
2343
+ }
2344
+ function collapseBlankLines(lines) {
2345
+ const out = [];
2346
+ for (const l of lines) {
2347
+ if (l.trim() === "" && (out.length === 0 || out[out.length - 1].trim() === "")) {
2348
+ continue;
2349
+ }
2350
+ out.push(l);
2351
+ }
2352
+ return out;
2353
+ }
2354
+ function emitLuau(program, file, api, options) {
2355
+ return new LuauEmitter(program, file, api, options).emit();
2356
+ }