cluaupp 1.5.2 → 2.0.0-beta.1

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 (195) hide show
  1. package/CHANGELOG.md +3 -0
  2. package/README.md +27 -40
  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/syntaxes/clpp.tmLanguage.json +211 -0
  12. package/examples/README.md +9 -9
  13. package/examples/game/cluaupp.config.json +2 -2
  14. package/examples/game/default.project.json +4 -15
  15. package/examples/game/out/ReplicatedStorage/Shared/GameInfo.luau +8 -0
  16. package/examples/game/out/ReplicatedStorage/Shared/GameInfo.luau.map.json +13 -0
  17. package/examples/game/out/ServerScriptService/Leaderstats.server.luau +26 -0
  18. package/examples/game/out/ServerScriptService/Leaderstats.server.luau.map.json +13 -0
  19. package/examples/game/out/ServerScriptService/Main.server.luau +15 -0
  20. package/examples/game/out/ServerScriptService/Main.server.luau.map.json +13 -0
  21. package/examples/game/out/StarterPlayer/StarterPlayerScripts/Main.client.luau +10 -0
  22. package/examples/game/out/StarterPlayer/StarterPlayerScripts/Main.client.luau.map.json +13 -0
  23. package/examples/game/src/ReplicatedStorage/Shared/GameInfo.clp +1 -0
  24. package/examples/game/src/ServerScriptService/Leaderstats.server.clp +18 -0
  25. package/examples/game/src/ServerScriptService/Main.server.clp +9 -0
  26. package/examples/game/src/StarterPlayer/StarterPlayerScripts/Main.client.clp +4 -0
  27. package/generated/api/build-profile.js +1 -1
  28. package/generated/api/loader.js +2 -2
  29. package/generated/api/surface/build.js +328 -0
  30. package/generated/api/surface/luau-defs.js +595 -0
  31. package/generated/api/surface/model.js +8 -0
  32. package/generated/clpp/contract.js +6 -3
  33. package/generated/clpp/handshake.js +36 -0
  34. package/generated/clpp/modules.js +20 -9
  35. package/generated/clpp/runner.js +62 -31
  36. package/generated/compile.js +18 -1
  37. package/generated/compiler/ast.js +3 -0
  38. package/generated/compiler/checker/calls.js +999 -0
  39. package/generated/compiler/checker/checker.js +1215 -0
  40. package/generated/compiler/checker/exprs.js +791 -0
  41. package/generated/compiler/checker/flow.js +168 -0
  42. package/generated/compiler/checker/members.js +763 -0
  43. package/generated/compiler/checker/statements.js +792 -0
  44. package/generated/compiler/checker/symbols.js +90 -0
  45. package/generated/compiler/checker/typeops.js +348 -0
  46. package/generated/compiler/checker/yields.js +115 -0
  47. package/generated/compiler/diagnostics.js +138 -0
  48. package/generated/compiler/emit/emitter.js +2356 -0
  49. package/generated/compiler/emit/luau-types.js +137 -0
  50. package/generated/compiler/emit/names.js +130 -0
  51. package/generated/compiler/emit/runtime.js +432 -0
  52. package/generated/compiler/lexer.js +403 -0
  53. package/generated/compiler/parser.js +1881 -0
  54. package/generated/compiler/project.js +332 -0
  55. package/generated/compiler/roblox/api.js +388 -0
  56. package/generated/compiler/roblox/context.js +100 -0
  57. package/generated/compiler/source.js +59 -0
  58. package/generated/compiler/stdlib.js +232 -0
  59. package/generated/compiler/types.js +250 -0
  60. package/generated/target/build-cache.js +4 -4
  61. package/generated/target/gen-rojo-tree.js +15 -0
  62. package/generated/target/source-map.js +61 -8
  63. package/generated/target/studio-bridge.js +1 -0
  64. package/generated/utils/project.js +83 -25
  65. package/native/bin/.gitkeep +1 -0
  66. package/package.json +17 -6
  67. package/plugins/CluauppNav/Plugin.luau +28 -4
  68. package/rust/cluaupp/Cargo.lock +795 -0
  69. package/rust/cluaupp/Cargo.toml +17 -0
  70. package/rust/cluaupp/src/main.rs +717 -0
  71. package/scripts/postinstall-native.js +56 -0
  72. package/skills/README.md +3 -3
  73. package/skills/clpp-hive-flare/SKILL.md +30 -26
  74. package/skills/clpp-language/SKILL.md +67 -74
  75. package/skills/clpp-language/compiler.md +24 -36
  76. package/skills/clpp-style/SKILL.md +31 -44
  77. package/src/api/build-profile.ts +1 -1
  78. package/src/api/loader.ts +2 -2
  79. package/src/api/surface/build.ts +374 -0
  80. package/src/api/surface/luau-defs.ts +635 -0
  81. package/src/api/surface/model.ts +96 -0
  82. package/src/clpp/contract.ts +20 -3
  83. package/src/clpp/handshake.ts +25 -0
  84. package/src/clpp/modules.ts +17 -10
  85. package/src/clpp/runner.ts +52 -34
  86. package/src/compile.ts +20 -2
  87. package/src/compiler/ast.ts +336 -0
  88. package/src/compiler/checker/calls.ts +1025 -0
  89. package/src/compiler/checker/checker.ts +1352 -0
  90. package/src/compiler/checker/exprs.ts +805 -0
  91. package/src/compiler/checker/flow.ts +174 -0
  92. package/src/compiler/checker/members.ts +793 -0
  93. package/src/compiler/checker/statements.ts +802 -0
  94. package/src/compiler/checker/symbols.ts +219 -0
  95. package/src/compiler/checker/typeops.ts +355 -0
  96. package/src/compiler/checker/yields.ts +120 -0
  97. package/src/compiler/diagnostics.ts +163 -0
  98. package/src/compiler/emit/emitter.ts +2486 -0
  99. package/src/compiler/emit/luau-types.ts +143 -0
  100. package/src/compiler/emit/names.ts +130 -0
  101. package/src/compiler/emit/runtime.ts +438 -0
  102. package/src/compiler/lexer.ts +444 -0
  103. package/src/compiler/parser.ts +1943 -0
  104. package/src/compiler/project.ts +354 -0
  105. package/src/compiler/roblox/api.ts +408 -0
  106. package/src/compiler/roblox/context.ts +108 -0
  107. package/src/compiler/source.ts +67 -0
  108. package/src/compiler/stdlib.ts +256 -0
  109. package/src/compiler/types.ts +313 -0
  110. package/src/target/build-cache.ts +5 -8
  111. package/src/target/gen-rojo-tree.ts +15 -0
  112. package/src/target/source-map.ts +65 -27
  113. package/src/target/studio-bridge.ts +3 -0
  114. package/src/types.ts +3 -0
  115. package/src/utils/project.ts +79 -31
  116. package/templates/README.md +7 -0
  117. package/templates/{game → game-modern}/cluaupp.config.json +2 -2
  118. package/templates/{game → game-modern}/package.json +1 -2
  119. package/templates/game-modern/rokit.toml +2 -0
  120. package/templates/game-modern/src/ReplicatedStorage/Shared/GameInfo.clp +1 -0
  121. package/templates/game-modern/src/ServerScriptService/Handlers/Guard.server.clp +4 -0
  122. package/templates/game-modern/src/ServerScriptService/Main.server.clp +8 -0
  123. package/templates/game-modern/src/StarterPlayer/StarterPlayerScripts/Main.client.clp +4 -0
  124. package/examples/game/out/ReplicatedStorage/Shared/Constants/Datas/TemplateData.luau +0 -29
  125. package/examples/game/out/ReplicatedStorage/Shared/Constants/Datas/TemplateData.luau.map.json +0 -13
  126. package/examples/game/out/ReplicatedStorage/Shared/Net/Net.luau +0 -83
  127. package/examples/game/out/ReplicatedStorage/Shared/Net/Net.luau.map.json +0 -13
  128. package/examples/game/out/ServerScriptService/Boot/DataBoot.server.luau +0 -18
  129. package/examples/game/out/ServerScriptService/Boot/DataBoot.server.luau.map.json +0 -13
  130. package/examples/game/out/ServerScriptService/Configuration/ServerConfig.luau +0 -14
  131. package/examples/game/out/ServerScriptService/Configuration/ServerConfig.luau.map.json +0 -13
  132. package/examples/game/out/ServerScriptService/Handlers/Guard.server.luau +0 -15
  133. package/examples/game/out/ServerScriptService/Handlers/Guard.server.luau.map.json +0 -13
  134. package/examples/game/out/ServerScriptService/Handlers/PlayerHandler.server.luau +0 -50
  135. package/examples/game/out/ServerScriptService/Handlers/PlayerHandler.server.luau.map.json +0 -13
  136. package/examples/game/out/StarterPlayer/StarterCharacterScripts/Character/CharacterReady.client.luau +0 -12
  137. package/examples/game/out/StarterPlayer/StarterCharacterScripts/Character/CharacterReady.client.luau.map.json +0 -13
  138. package/examples/game/out/StarterPlayer/StarterPlayerScripts/Controllers/DataController.client.luau +0 -16
  139. package/examples/game/out/StarterPlayer/StarterPlayerScripts/Controllers/DataController.client.luau.map.json +0 -13
  140. package/examples/game/src/Client/Controllers/DataController.client.clpp +0 -7
  141. package/examples/game/src/Client/Main.client.clpp +0 -4
  142. package/examples/game/src/Declarations/Net.flare +0 -5
  143. package/examples/game/src/Include/GameTypes.clh +0 -40
  144. package/examples/game/src/Modules/Core/Guard.server.clpp +0 -7
  145. package/examples/game/src/Modules/Core/PlayerHandler.server.clpp +0 -23
  146. package/examples/game/src/Server/Boot/DataBoot.server.clpp +0 -13
  147. package/examples/game/src/Server/Main.server.clpp +0 -4
  148. package/templates/game/.clangd +0 -25
  149. package/templates/game/.vscode/extensions.json +0 -8
  150. package/templates/game/.vscode/settings.json +0 -17
  151. package/templates/game/Packages/.gitkeep +0 -0
  152. package/templates/game/compile_flags.txt +0 -6
  153. package/templates/game/default.project.json +0 -41
  154. package/templates/game/include/clpp/datatypes.clh +0 -575
  155. package/templates/game/include/clpp/generated/enums.clh +0 -4920
  156. package/templates/game/include/clpp/generated/instances.clh +0 -87182
  157. package/templates/game/include/clpp/libs/arrayindexer.clh +0 -12
  158. package/templates/game/include/clpp/libs/axiom.clh +0 -154
  159. package/templates/game/include/clpp/libs/bloom.clh +0 -58
  160. package/templates/game/include/clpp/libs/coil.clh +0 -37
  161. package/templates/game/include/clpp/libs/crest.clh +0 -22
  162. package/templates/game/include/clpp/libs/echo.clh +0 -52
  163. package/templates/game/include/clpp/libs/ember.clh +0 -17
  164. package/templates/game/include/clpp/libs/flare.clh +0 -65
  165. package/templates/game/include/clpp/libs/gleam.clh +0 -111
  166. package/templates/game/include/clpp/libs/guide.clh +0 -42
  167. package/templates/game/include/clpp/libs/helm.clh +0 -31
  168. package/templates/game/include/clpp/libs/hive.clh +0 -20
  169. package/templates/game/include/clpp/libs/keep.clh +0 -158
  170. package/templates/game/include/clpp/libs/lens.clh +0 -24
  171. package/templates/game/include/clpp/libs/mint.clh +0 -28
  172. package/templates/game/include/clpp/libs/net.clh +0 -31
  173. package/templates/game/include/clpp/libs/occlude.clh +0 -15
  174. package/templates/game/include/clpp/libs/pin.clh +0 -28
  175. package/templates/game/include/clpp/libs/promise.clh +0 -24
  176. package/templates/game/include/clpp/libs/roster.clh +0 -123
  177. package/templates/game/include/clpp/libs/shift.clh +0 -22
  178. package/templates/game/include/clpp/libs/spark.clh +0 -28
  179. package/templates/game/include/clpp/libs/stage.clh +0 -23
  180. package/templates/game/include/clpp/libs/sweep.clh +0 -23
  181. package/templates/game/include/clpp/libs/trace.clh +0 -18
  182. package/templates/game/include/clpp/libs/ward.clh +0 -19
  183. package/templates/game/include/clpp/libs.clh +0 -31
  184. package/templates/game/include/clpp/roblox.clh +0 -41
  185. package/templates/game/rokit.toml +0 -5
  186. package/templates/game/src/Client/Controllers/DataController.client.clpp +0 -7
  187. package/templates/game/src/Client/Main.client.clpp +0 -4
  188. package/templates/game/src/Declarations/Net.flare +0 -5
  189. package/templates/game/src/Include/GameTypes.clh +0 -40
  190. package/templates/game/src/Modules/Core/Guard.server.clpp +0 -7
  191. package/templates/game/src/Modules/Core/PlayerHandler.server.clpp +0 -24
  192. package/templates/game/src/Server/Boot/DataBoot.server.clpp +0 -13
  193. package/templates/game/src/Server/Main.server.clpp +0 -4
  194. package/templates/game/stylua.toml +0 -9
  195. package/templates/game/wally.toml +0 -12
@@ -0,0 +1,143 @@
1
+ /** CL++ types → Luau type annotations. `null` means "omit the annotation" (let Luau infer). */
2
+
3
+ import { prune, type Type } from "../types.js";
4
+
5
+ export interface TypeNaming {
6
+ /** Luau name for a struct type (`Fighter`, `Combat.Fighter`), or null when it cannot be named here. */
7
+ struct(id: string): string | null;
8
+ /** Luau type of a payload enum, or null for plain enums (numbers). */
9
+ enumType(id: string): string | null;
10
+ }
11
+
12
+ export function luauType(t: Type, names: TypeNaming, depth = 0): string | null {
13
+ const p = prune(t);
14
+ if (depth > 8) {
15
+ return null;
16
+ }
17
+ const sub = (x: Type) => luauType(x, names, depth + 1);
18
+ switch (p.k) {
19
+ case "int":
20
+ case "float":
21
+ return "number";
22
+ case "bool":
23
+ return "boolean";
24
+ case "string":
25
+ return "string";
26
+ case "lit":
27
+ return typeof p.value === "string" ? JSON.stringify(p.value) : typeof p.value === "boolean" ? String(p.value) : "number";
28
+ case "null":
29
+ return "nil";
30
+ case "void":
31
+ return "()";
32
+ case "any":
33
+ return "any";
34
+ case "buffer":
35
+ return "buffer";
36
+ case "thread":
37
+ return "thread";
38
+ case "list": {
39
+ const e = sub(p.elem);
40
+ return e ? `{ ${e} }` : null;
41
+ }
42
+ case "dict": {
43
+ const k = sub(p.key);
44
+ const v = sub(p.value);
45
+ return k && v ? `{ [${k}]: ${v} }` : null;
46
+ }
47
+ case "table": {
48
+ const parts: string[] = [];
49
+ for (const [k, v] of Object.entries(p.fields)) {
50
+ const vt = sub(v);
51
+ if (!vt) {
52
+ return null;
53
+ }
54
+ parts.push(`${/^[A-Za-z_][A-Za-z0-9_]*$/.test(k) ? k : `[${JSON.stringify(k)}]`}: ${vt}`);
55
+ }
56
+ return `{ ${parts.join(", ")} }`;
57
+ }
58
+ case "tuple": {
59
+ const parts = p.elems.map(sub);
60
+ return parts.every(Boolean) ? `(${parts.join(", ")})` : null;
61
+ }
62
+ case "struct": {
63
+ const base = names.struct(p.id);
64
+ if (!base) {
65
+ return null;
66
+ }
67
+ if (p.args.length) {
68
+ const args = p.args.map(sub);
69
+ return args.every(Boolean) ? `${base}<${args.join(", ")}>` : null;
70
+ }
71
+ return base;
72
+ }
73
+ case "enum":
74
+ return names.enumType(p.id) ?? "number";
75
+ case "variant": {
76
+ const parts = p.alternatives.map(sub);
77
+ return parts.every(Boolean) && parts.length ? [...new Set(parts)].join(" | ") : null;
78
+ }
79
+ case "option": {
80
+ const inner = sub(p.inner);
81
+ if (!inner) {
82
+ return null;
83
+ }
84
+ if (inner === "any") {
85
+ return "any";
86
+ }
87
+ return /->|\|/.test(inner) ? `(${inner})?` : `${inner}?`;
88
+ }
89
+ case "result": {
90
+ const ok = sub(p.ok) ?? "any";
91
+ const err = sub(p.err) ?? "any";
92
+ const opt = (x: string) => (x === "any" ? x : /->|\|/.test(x) ? `(${x})?` : `${x}?`);
93
+ return `{ ok: boolean, value: ${opt(ok)}, error: ${opt(err)} }`;
94
+ }
95
+ case "task": {
96
+ const r = sub(p.result);
97
+ return `ClTask<${r && r !== "()" ? r : "any"}>`;
98
+ }
99
+ case "func": {
100
+ const params = p.params.map((x) => {
101
+ const pt = sub(x.type);
102
+ return pt ? (x.optional && !pt.endsWith("?") ? (/->|\|/.test(pt) ? `(${pt})?` : `${pt}?`) : pt) : null;
103
+ });
104
+ if (!params.every(Boolean)) {
105
+ return null;
106
+ }
107
+ if (p.variadic) {
108
+ const v = sub(p.variadic);
109
+ params.push(`...${v ?? "any"}`);
110
+ }
111
+ const ret = prune(p.ret).k === "void" ? "()" : sub(p.ret);
112
+ if (!ret) {
113
+ return null;
114
+ }
115
+ return `(${params.join(", ")}) -> ${ret}`;
116
+ }
117
+ case "union": {
118
+ const parts = p.members.map(sub);
119
+ return parts.every(Boolean) ? [...new Set(parts)].join(" | ") : null;
120
+ }
121
+ case "param":
122
+ return p.name;
123
+ case "instance":
124
+ return p.cls === "Object" ? "Instance" : p.cls;
125
+ case "datatype":
126
+ if (p.name === "__Mutex") {
127
+ return "ClMutex";
128
+ }
129
+ return p.name;
130
+ case "rbxEnumItem":
131
+ return p.enumName === "*" ? "EnumItem" : `Enum.${p.enumName}`;
132
+ case "rbxEnum":
133
+ return "Enum";
134
+ case "rbxEnums":
135
+ return "Enums";
136
+ case "rbxSignal":
137
+ return "RBXScriptSignal";
138
+ case "clSignal":
139
+ return null;
140
+ default:
141
+ return null;
142
+ }
143
+ }
@@ -0,0 +1,130 @@
1
+ /** Luau-safe identifiers: keywords, globals the output relies on, and non-ASCII names. */
2
+
3
+ import type { RobloxApi } from "../roblox/api.js";
4
+
5
+ export const LUAU_KEYWORDS = new Set([
6
+ "and", "break", "do", "else", "elseif", "end", "false", "for", "function", "if", "in", "local", "nil", "not", "or",
7
+ "repeat", "return", "then", "true", "until", "while",
8
+ ]);
9
+
10
+ /** Names emitted code may reference as globals; user symbols are renamed away from them. */
11
+ export function reservedGlobals(api: RobloxApi): Set<string> {
12
+ const out = new Set<string>([
13
+ "self", "continue", "type", "typeof", "export",
14
+ "game", "workspace", "script", "plugin", "shared", "Enum", "Instance", "Vector3", "Vector2",
15
+ "print", "warn", "error", "assert", "tostring", "tonumber", "select", "pairs", "ipairs", "next", "pcall",
16
+ "xpcall", "require", "setmetatable", "getmetatable", "rawget", "rawset", "rawequal", "rawlen", "unpack",
17
+ "tick", "time", "wait", "delay", "spawn",
18
+ ]);
19
+ for (const name of Object.keys(api.libraries)) {
20
+ out.add(name);
21
+ }
22
+ for (const name of Object.keys(api.datatypes)) {
23
+ out.add(name);
24
+ }
25
+ return out;
26
+ }
27
+
28
+ const LUAU_BUILTIN_TYPES = new Set(["number", "string", "boolean", "any", "unknown", "never", "nil", "thread", "buffer", "userdata", "vector", "Enum", "EnumItem", "Instance"]);
29
+
30
+ /** Names that may not be used for an emitted type alias. */
31
+ export function isReservedTypeName(api: RobloxApi, name: string): boolean {
32
+ return LUAU_BUILTIN_TYPES.has(name) || api.hasClass(name) || api.hasDatatype(name) || name === "RBXScriptSignal" || name === "RBXScriptConnection" || name.startsWith("Cl");
33
+ }
34
+
35
+ export function sanitizeIdentifier(name: string): string {
36
+ if (/^[A-Za-z_][A-Za-z0-9_]*$/.test(name)) {
37
+ return name;
38
+ }
39
+ let out = "";
40
+ for (const ch of name) {
41
+ if (/[A-Za-z0-9_]/.test(ch)) {
42
+ out += ch;
43
+ } else {
44
+ out += `_${ch.codePointAt(0)!.toString(16)}`;
45
+ }
46
+ }
47
+ return /^[0-9]/.test(out) ? `_${out}` : out;
48
+ }
49
+
50
+ export function isIdentifier(name: string): boolean {
51
+ return /^[A-Za-z_][A-Za-z0-9_]*$/.test(name) && !LUAU_KEYWORDS.has(name);
52
+ }
53
+
54
+ /** Luau string literal with the shortest correct escaping. */
55
+ export function luauString(value: string): string {
56
+ let out = '"';
57
+ for (const ch of value) {
58
+ const code = ch.codePointAt(0)!;
59
+ switch (ch) {
60
+ case '"':
61
+ out += '\\"';
62
+ break;
63
+ case "\\":
64
+ out += "\\\\";
65
+ break;
66
+ case "\n":
67
+ out += "\\n";
68
+ break;
69
+ case "\r":
70
+ out += "\\r";
71
+ break;
72
+ case "\t":
73
+ out += "\\t";
74
+ break;
75
+ case "\0":
76
+ out += "\\0";
77
+ break;
78
+ default:
79
+ out += code < 32 || code === 127 ? `\\${code}` : ch;
80
+ }
81
+ }
82
+ return `${out}"`;
83
+ }
84
+
85
+ /** Escape literal text inside a Luau interpolated string. */
86
+ export function interpolationText(value: string): string {
87
+ let out = "";
88
+ for (const ch of value) {
89
+ switch (ch) {
90
+ case "`":
91
+ out += "\\`";
92
+ break;
93
+ case "{":
94
+ out += "\\{";
95
+ break;
96
+ case "}":
97
+ out += "\\}";
98
+ break;
99
+ case "\\":
100
+ out += "\\\\";
101
+ break;
102
+ case "\n":
103
+ out += "\\n";
104
+ break;
105
+ case "\r":
106
+ out += "\\r";
107
+ break;
108
+ case "\t":
109
+ out += "\\t";
110
+ break;
111
+ default:
112
+ out += ch;
113
+ }
114
+ }
115
+ return out;
116
+ }
117
+
118
+ /** Number literal: integers stay integral, floats use the shortest round-trip form. */
119
+ export function luauNumber(value: number): string {
120
+ if (Number.isNaN(value)) {
121
+ return "(0 / 0)";
122
+ }
123
+ if (!Number.isFinite(value)) {
124
+ return value > 0 ? "math.huge" : "-math.huge";
125
+ }
126
+ if (Number.isInteger(value) && Math.abs(value) < 2 ** 53) {
127
+ return String(value);
128
+ }
129
+ return String(value);
130
+ }
@@ -0,0 +1,438 @@
1
+ /**
2
+ * File-local runtime helpers. Only the helpers a file uses are emitted (with their dependencies),
3
+ * as `local function`s so `--!optimize 2` can inline them. No shared runtime module, no require.
4
+ */
5
+
6
+ export interface Helper {
7
+ /** Luau source (one or more top-level statements). */
8
+ code: string;
9
+ deps?: string[];
10
+ /** Type aliases the helper declares. */
11
+ types?: string;
12
+ }
13
+
14
+ export const HELPERS: Record<string, Helper> = {
15
+ __cl_idiv: {
16
+ code: `local function __cl_idiv(a: number, b: number): number
17
+ local q = a / b
18
+ return if q < 0 then math.ceil(q) else math.floor(q)
19
+ end`,
20
+ },
21
+ __cl_trunc: {
22
+ code: `local function __cl_trunc(x: number): number
23
+ return if x < 0 then math.ceil(x) else math.floor(x)
24
+ end`,
25
+ },
26
+ __cl_toint: {
27
+ deps: ["__cl_tonumber", "__cl_trunc"],
28
+ code: `local function __cl_toint(text: string): number
29
+ return __cl_trunc(__cl_tonumber(text))
30
+ end`,
31
+ },
32
+ __cl_tonumber: {
33
+ code: `local function __cl_tonumber(text: string): number
34
+ local n = tonumber(text)
35
+ if n == nil then
36
+ error(\`not a number: "{text}"\`, 2)
37
+ end
38
+ return n
39
+ end`,
40
+ },
41
+ __cl_strlen: {
42
+ code: `local function __cl_strlen(s: string): number
43
+ return utf8.len(s) or #s
44
+ end`,
45
+ },
46
+ __cl_char: {
47
+ code: `local function __cl_char(s: string, i: number): string
48
+ local from = utf8.offset(s, i + 1)
49
+ if from == nil or from > #s then
50
+ error(\`index out of range: {i} (size {utf8.len(s) or #s})\`, 2)
51
+ end
52
+ local to = (utf8.offset(s, i + 2) or #s + 1) - 1
53
+ return string.sub(s, from, to)
54
+ end`,
55
+ },
56
+ __cl_slice: {
57
+ code: `local function __cl_slice<T>(x: T, a: number, b: number): T
58
+ if type(x) == "string" then
59
+ local s = x :: any
60
+ local from = utf8.offset(s, a + 1) or #s + 1
61
+ local to = (utf8.offset(s, b + 1) or #s + 1) - 1
62
+ return string.sub(s, from, to) :: any
63
+ end
64
+ return table.move(x :: any, a + 1, b, 1, {}) :: any
65
+ end`,
66
+ },
67
+ __cl_count: {
68
+ code: `local function __cl_count(t: { [any]: any }): number
69
+ local n = 0
70
+ for _ in t do
71
+ n += 1
72
+ end
73
+ return n
74
+ end`,
75
+ },
76
+ __cl_take: {
77
+ code: `local function __cl_take<K, V>(t: { [K]: V }, key: K): V
78
+ local value = t[key]
79
+ t[key] = nil
80
+ return value
81
+ end`,
82
+ },
83
+ __cl_sorted: {
84
+ code: `local function __cl_sorted<T>(list: { T }): { T }
85
+ local out = table.clone(list)
86
+ table.sort(out :: any)
87
+ return out
88
+ end`,
89
+ },
90
+ __cl_str: {
91
+ code: `local function __cl_str(value: any): string
92
+ local kind = typeof(value)
93
+ if kind == "string" then
94
+ return value
95
+ elseif kind == "boolean" or kind == "number" or kind == "nil" then
96
+ return if value == nil then "null" else tostring(value)
97
+ elseif kind == "Vector3" then
98
+ return \`({value.X}, {value.Y}, {value.Z})\`
99
+ elseif kind == "Vector2" then
100
+ return \`({value.X}, {value.Y})\`
101
+ elseif kind ~= "table" then
102
+ return tostring(value)
103
+ end
104
+ local meta = getmetatable(value)
105
+ local fields = if type(meta) == "table" then rawget(meta, "__fields") else nil
106
+ local parts = {}
107
+ local function item(v: any): string
108
+ return if type(v) == "string" then \`"{v}"\` else __cl_str(v)
109
+ end
110
+ if fields then
111
+ for i, name in fields do
112
+ parts[i] = __cl_str(value[name])
113
+ end
114
+ return "(" .. table.concat(parts, ", ") .. ")"
115
+ end
116
+ if rawget(value, "tag") ~= nil and type(rawget(value, "tag")) == "number" then
117
+ return if value.value ~= nil then __cl_str(value.value) else tostring(value.tag)
118
+ end
119
+ if #value > 0 or next(value) == nil then
120
+ for i, v in value do
121
+ parts[i] = item(v)
122
+ end
123
+ return "[" .. table.concat(parts, ", ") .. "]"
124
+ end
125
+ for k, v in value do
126
+ table.insert(parts, \`{k}: {__cl_str(v)}\`)
127
+ end
128
+ return "{" .. table.concat(parts, ", ") .. "}"
129
+ end`,
130
+ },
131
+ __cl_copy: {
132
+ code: `local function __cl_copy<T>(value: T): T
133
+ if type(value) ~= "table" then
134
+ return value
135
+ end
136
+ local source = value :: any
137
+ local meta = getmetatable(source)
138
+ if meta ~= nil and (type(meta) ~= "table" or rawget(meta, "__fields") == nil) then
139
+ return value -- foreign object: shared by reference
140
+ end
141
+ local out = table.clone(source)
142
+ for k, v in out do
143
+ if type(v) == "table" then
144
+ out[k] = __cl_copy(v)
145
+ end
146
+ end
147
+ if meta ~= nil then
148
+ setmetatable(out, meta)
149
+ end
150
+ return out :: any
151
+ end`,
152
+ },
153
+ __cl_eq: {
154
+ code: `local function __cl_eq(a: any, b: any): boolean
155
+ if a == b then
156
+ return true
157
+ end
158
+ if type(a) ~= "table" or type(b) ~= "table" or getmetatable(a) ~= getmetatable(b) then
159
+ return false
160
+ end
161
+ for k, v in a do
162
+ if not __cl_eq(v, b[k]) then
163
+ return false
164
+ end
165
+ end
166
+ for k in b do
167
+ if a[k] == nil then
168
+ return false
169
+ end
170
+ end
171
+ return true
172
+ end`,
173
+ },
174
+ __cl_findeq: {
175
+ deps: ["__cl_eq"],
176
+ code: `local function __cl_findeq(list: { any }, value: any): number
177
+ for i, v in list do
178
+ if __cl_eq(v, value) then
179
+ return i - 1
180
+ end
181
+ end
182
+ return -1
183
+ end`,
184
+ },
185
+ __cl_task: {
186
+ types: `type ClTask<T> = { done: boolean, ok: boolean, value: T?, err: any, waiting: { thread } }`,
187
+ code: `local function __cl_spawn<T>(work: () -> T): ClTask<T>
188
+ local job: ClTask<T> = { done = false, ok = true, value = nil, err = nil, waiting = {} }
189
+ task.spawn(function()
190
+ local ok, result = pcall(work)
191
+ job.ok = ok
192
+ if ok then
193
+ job.value = result
194
+ else
195
+ job.err = result
196
+ end
197
+ job.done = true
198
+ for _, waiter in job.waiting do
199
+ task.spawn(waiter)
200
+ end
201
+ end)
202
+ return job
203
+ end
204
+
205
+ local function __cl_await<T>(job: ClTask<T>): T
206
+ if not job.done then
207
+ table.insert(job.waiting, coroutine.running())
208
+ coroutine.yield()
209
+ end
210
+ if not job.ok then
211
+ error(job.err, 0)
212
+ end
213
+ return job.value :: T
214
+ end`,
215
+ },
216
+ __cl_parallel: {
217
+ deps: ["__cl_task"],
218
+ code: `local function __cl_parallel<T>(jobs: { ClTask<T> }): { T }
219
+ local results = table.create(#jobs)
220
+ for i, job in jobs do
221
+ results[i] = __cl_await(job)
222
+ end
223
+ return results
224
+ end`,
225
+ },
226
+ __cl_mutex: {
227
+ types: `type ClMutex = { locked: boolean, queue: { thread } }`,
228
+ code: `local function __cl_mutex(): ClMutex
229
+ return { locked = false, queue = {} }
230
+ end
231
+
232
+ local function __cl_lock(m: ClMutex)
233
+ if m.locked then
234
+ table.insert(m.queue, coroutine.running())
235
+ coroutine.yield()
236
+ end
237
+ m.locked = true
238
+ end
239
+
240
+ local function __cl_unlock(m: ClMutex)
241
+ local nextThread = table.remove(m.queue, 1)
242
+ if nextThread then
243
+ task.spawn(nextThread)
244
+ else
245
+ m.locked = false
246
+ end
247
+ end`,
248
+ },
249
+ __cl_errmsg: {
250
+ code: `local function __cl_errmsg(err: any): string
251
+ if type(err) == "string" then
252
+ return (string.gsub(err, "^[^:]+:%d+: ", ""))
253
+ end
254
+ return tostring(err)
255
+ end`,
256
+ },
257
+ trim: {
258
+ code: `local function __cl_trim(text: string): string
259
+ return (string.match(text, "^%s*(.-)%s*$")) or ""
260
+ end`,
261
+ },
262
+ replace: {
263
+ code: `local function __cl_replace(text: string, target: string, replacement: string): string
264
+ if target == "" then
265
+ return text
266
+ end
267
+ local out = {}
268
+ local start = 1
269
+ while true do
270
+ local i, j = string.find(text, target, start, true)
271
+ if i == nil or j == nil then
272
+ break
273
+ end
274
+ table.insert(out, string.sub(text, start, i - 1))
275
+ table.insert(out, replacement)
276
+ start = j + 1
277
+ end
278
+ table.insert(out, string.sub(text, start))
279
+ return table.concat(out)
280
+ end`,
281
+ },
282
+ endsWith: {
283
+ code: `local function __cl_endsWith(text: string, part: string): boolean
284
+ return part == "" or string.sub(text, -#part) == part
285
+ end`,
286
+ },
287
+ toNumber: {
288
+ deps: ["__cl_tonumber"],
289
+ code: "",
290
+ },
291
+ indexOf: {
292
+ code: `local function __cl_indexOf(text: string, part: string): number
293
+ local at = string.find(text, part, 1, true)
294
+ if at == nil then
295
+ return -1
296
+ end
297
+ return (utf8.len(string.sub(text, 1, at - 1)) or (at - 1))
298
+ end`,
299
+ },
300
+ joinAll: {
301
+ deps: ["__cl_str"],
302
+ code: `local function __cl_joinAll(parts: { any }, separator: string): string
303
+ local out = table.create(#parts)
304
+ for i, part in parts do
305
+ out[i] = __cl_str(part)
306
+ end
307
+ return table.concat(out, separator)
308
+ end`,
309
+ },
310
+ jsonParse: {
311
+ code: `local function __cl_jsonParse(text: string): any
312
+ return game:GetService("HttpService"):JSONDecode(text)
313
+ end`,
314
+ },
315
+ jsonValid: {
316
+ code: `local function __cl_jsonValid(text: string): boolean
317
+ return (pcall(function()
318
+ return game:GetService("HttpService"):JSONDecode(text)
319
+ end))
320
+ end`,
321
+ },
322
+ jsonStringify: {
323
+ code: `local function __cl_jsonStringify(value: any): string
324
+ return game:GetService("HttpService"):JSONEncode(value)
325
+ end`,
326
+ },
327
+ jsonPretty: {
328
+ deps: ["jsonStringify"],
329
+ code: `local function __cl_jsonPretty(value: any): string
330
+ local text = __cl_jsonStringify(value)
331
+ local out, depth, inString = {}, 0, false
332
+ for i = 1, #text do
333
+ local ch = string.sub(text, i, i)
334
+ if ch == '"' and string.sub(text, i - 1, i - 1) ~= "\\\\" then
335
+ inString = not inString
336
+ end
337
+ if inString then
338
+ table.insert(out, ch)
339
+ elseif ch == "{" or ch == "[" then
340
+ depth += 1
341
+ table.insert(out, ch .. "\\n" .. string.rep(" ", depth))
342
+ elseif ch == "}" or ch == "]" then
343
+ depth -= 1
344
+ table.insert(out, "\\n" .. string.rep(" ", depth) .. ch)
345
+ elseif ch == "," then
346
+ table.insert(out, ",\\n" .. string.rep(" ", depth))
347
+ elseif ch == ":" then
348
+ table.insert(out, ": ")
349
+ else
350
+ table.insert(out, ch)
351
+ end
352
+ end
353
+ return table.concat(out)
354
+ end`,
355
+ },
356
+ jsonGet: {
357
+ code: `local function __cl_jsonGet(value: any, path: string): any
358
+ local node = value
359
+ for key in string.gmatch(path, "[^%.]+") do
360
+ if type(node) ~= "table" then
361
+ return nil
362
+ end
363
+ local index = tonumber(key)
364
+ node = if index ~= nil and node[index + 1] ~= nil then node[index + 1] else node[key]
365
+ end
366
+ return node
367
+ end`,
368
+ },
369
+ jsonHas: {
370
+ deps: ["jsonGet"],
371
+ code: `local function __cl_jsonHas(value: any, path: string): boolean
372
+ return __cl_jsonGet(value, path) ~= nil
373
+ end`,
374
+ },
375
+ jsonSet: {
376
+ code: `local function __cl_jsonSet(target: any, key: string, value: any): any
377
+ local copy = if type(target) == "table" then table.clone(target) else {}
378
+ local index = tonumber(key)
379
+ if index ~= nil and #copy > 0 then
380
+ copy[index + 1] = value
381
+ else
382
+ copy[key] = value
383
+ end
384
+ return copy
385
+ end`,
386
+ },
387
+ jsonKeys: {
388
+ code: `local function __cl_jsonKeys(object: any): { string }
389
+ local keys = {}
390
+ if type(object) == "table" then
391
+ for k in object do
392
+ table.insert(keys, tostring(k))
393
+ end
394
+ end
395
+ return keys
396
+ end`,
397
+ },
398
+ urlHost: {
399
+ code: `local function __cl_urlHost(url: string): string
400
+ local rest = string.match(url, "^%a[%w+.-]*://(.*)$") or url
401
+ local host = string.match(rest, "^[^/?#]*") or ""
402
+ host = string.match(host, "^[^@]*@(.*)$") or host
403
+ return (string.match(host, "^([^:]*)")) or host
404
+ end`,
405
+ },
406
+ };
407
+
408
+ /** Luau name a std helper is called by. */
409
+ export function helperFunctionName(helper: string): string {
410
+ if (helper === "toNumber") {
411
+ return "__cl_tonumber";
412
+ }
413
+ return helper.startsWith("__cl_") ? helper : `__cl_${helper}`;
414
+ }
415
+
416
+ /** Ordered, de-duplicated helper set including dependencies. */
417
+ export function resolveHelpers(used: Iterable<string>): string[] {
418
+ const out: string[] = [];
419
+ const seen = new Set<string>();
420
+ const visit = (name: string) => {
421
+ if (seen.has(name)) {
422
+ return;
423
+ }
424
+ seen.add(name);
425
+ const h = HELPERS[name];
426
+ if (!h) {
427
+ return;
428
+ }
429
+ for (const d of h.deps || []) {
430
+ visit(d);
431
+ }
432
+ out.push(name);
433
+ };
434
+ for (const u of used) {
435
+ visit(u);
436
+ }
437
+ return out;
438
+ }