cluaupp 0.1.0 → 0.1.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.
- package/CHANGELOG.md +6 -0
- package/docs/cli.md +3 -1
- package/docs/libraries.md +4 -1
- package/docs/syntax.md +19 -0
- package/include/cluaupp/libs/dataservice.hpp +5 -1
- package/package.json +2 -2
- package/src/cli.js +182 -27
- package/src/emit.js +7 -0
- package/src/parse.js +29 -0
- package/templates/game/default.project.json +10 -5
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.1.1
|
|
4
|
+
|
|
5
|
+
- DataService: `DataServiceOptions<T>.Template` is the required player-data table (`T` matches the save struct). Also documents `Exclude`.
|
|
6
|
+
- Compiler: C++ designated initializers (`Type { .Field = value }`) emit Luau tables, so `DataService.Server:Init({ Template = ... })` type-checks and compiles.
|
|
7
|
+
- Watch/build: delete orphaned `out/` files when a source is removed. Never wipe `out/` or `libs/` as a whole — Rojo 7 crashes if `libs/ArrayIndexer` disappears mid-serve. Vendor/header sync only writes missing/changed files (no `rmSync` of `libs/`). Watch rebuilds skip recopying `libs/`. `default.project.json` marks `out/*`, `libs`, and `Packages` as `$optional`.
|
|
8
|
+
|
|
3
9
|
## 0.1.0
|
|
4
10
|
|
|
5
11
|
- Product name: **Cluaupp** (C++ × Luau)
|
package/docs/cli.md
CHANGED
|
@@ -31,6 +31,8 @@ Transpiles `src/**/*.{cpp,h,hpp}` into `out/`. Script files become PascalCase **
|
|
|
31
31
|
- `.h` / `.hpp` with a sibling `.cpp` are not emitted twice
|
|
32
32
|
- `new Folder(parent)` in a stats system becomes `CacheController.Ensure`
|
|
33
33
|
- A parse error exits with code 1 and `file:line:column`
|
|
34
|
+
- After emit, **orphans in `out/` are removed** (source deleted → matching Luau deleted). `out/` itself is never wiped, so a running Rojo serve keeps the live tree.
|
|
35
|
+
- `libs/` and `include/cluaupp` are synced by writing missing/changed files only. They are **never** deleted as a folder — Rojo 7 unwrap-crashes if `libs/ArrayIndexer` vanishes while serving.
|
|
34
36
|
|
|
35
37
|
```bash
|
|
36
38
|
cluaupp build
|
|
@@ -39,7 +41,7 @@ cluaupp build ./my-game
|
|
|
39
41
|
|
|
40
42
|
## `cluaupp watch [folder]`
|
|
41
43
|
|
|
42
|
-
Runs a `build` and rebuilds when
|
|
44
|
+
Runs a `build` and rebuilds when anything under `src/` changes, including deletes. Parse errors are printed; the watcher stays alive. Rebuilds are debounced and do **not** recopy `libs/` (Rojo is likely already serving those files). Deleted `.cpp` files prune their `out/` artifacts on the next tick.
|
|
43
45
|
|
|
44
46
|
## `cluaupp --version` / `cluaupp -v`
|
|
45
47
|
|
package/docs/libraries.md
CHANGED
|
@@ -67,7 +67,10 @@ void init() {
|
|
|
67
67
|
auto* coins = Net::Event("Coins");
|
|
68
68
|
coins->On(OnCoins);
|
|
69
69
|
print(FormatNumber::Abbreviate(1500));
|
|
70
|
-
DataService::
|
|
70
|
+
DataService::Server.Init(DataServiceOptions {
|
|
71
|
+
.Template = { .Coins = 0 },
|
|
72
|
+
.StoreName = "PlayerData",
|
|
73
|
+
});
|
|
71
74
|
janitor->Add(coins);
|
|
72
75
|
}
|
|
73
76
|
```
|
package/docs/syntax.md
CHANGED
|
@@ -108,6 +108,25 @@ The only `for` accepted today is **range-for**: `for (auto* x : list)`. C-style
|
|
|
108
108
|
| `obj->Method(a)` | `obj:Method(a)` if the method is a Roblox API |
|
|
109
109
|
| `fn(a)` | `fn(a)` |
|
|
110
110
|
| `signal.Connect(fn)` | `signal:Connect(fn)` |
|
|
111
|
+
| `Type { .Field = value }` | `{ Field = value }` |
|
|
112
|
+
|
|
113
|
+
Designated initializers become Luau tables. Nested braces work the same way:
|
|
114
|
+
|
|
115
|
+
```cpp
|
|
116
|
+
DataService::Server.Init(DataServiceOptions {
|
|
117
|
+
.Template = playerData,
|
|
118
|
+
.StoreName = "PlayerData",
|
|
119
|
+
.UseMock = true,
|
|
120
|
+
});
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
```luau
|
|
124
|
+
DataService.Server:Init({
|
|
125
|
+
Template = playerData,
|
|
126
|
+
StoreName = "PlayerData",
|
|
127
|
+
UseMock = true,
|
|
128
|
+
})
|
|
129
|
+
```
|
|
111
130
|
|
|
112
131
|
## `new` and services
|
|
113
132
|
|
|
@@ -34,10 +34,13 @@ struct DataServiceEnum {
|
|
|
34
34
|
DataOrderList OrderList;
|
|
35
35
|
};
|
|
36
36
|
|
|
37
|
+
template <typename T>
|
|
37
38
|
struct DataServiceOptions {
|
|
39
|
+
T Template;
|
|
38
40
|
string StoreName;
|
|
39
41
|
bool UseMock;
|
|
40
42
|
string KeyPrefix;
|
|
43
|
+
LuaArray<DataPath> Exclude;
|
|
41
44
|
bool StrictPaths;
|
|
42
45
|
bool AutoCreateMissingTables;
|
|
43
46
|
};
|
|
@@ -112,7 +115,8 @@ public:
|
|
|
112
115
|
class DataServiceServer {
|
|
113
116
|
public:
|
|
114
117
|
DataPath Paths;
|
|
115
|
-
|
|
118
|
+
template <typename T>
|
|
119
|
+
DataServiceServer* Init(DataServiceOptions<T> options);
|
|
116
120
|
Data* WaitFor(Player* player);
|
|
117
121
|
Data* Get(Player* player);
|
|
118
122
|
bool HasData(Player* player);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "cluaupp",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.1",
|
|
4
4
|
"description": "Cluaupp — the definitive merge of C++ and modern Luau. Source-to-source transpiler with first-class Roblox APIs.",
|
|
5
5
|
"author": "KartzDev",
|
|
6
6
|
"license": "MIT",
|
|
@@ -29,7 +29,7 @@
|
|
|
29
29
|
"cluau": "node bin/cluaupp.js",
|
|
30
30
|
"build": "node bin/cluaupp.js build",
|
|
31
31
|
"watch": "node bin/cluaupp.js watch",
|
|
32
|
-
"test": "node test/leaderstats.test.js && node test/understand.test.js && node test/datatypes.test.js && node test/headers.test.js && node test/libs.test.js && node test/runtime-libs.test.js && node test/intellisense.test.js",
|
|
32
|
+
"test": "node test/leaderstats.test.js && node test/understand.test.js && node test/datatypes.test.js && node test/headers.test.js && node test/libs.test.js && node test/runtime-libs.test.js && node test/intellisense.test.js && node test/dataservice.test.js && node test/prune.test.js",
|
|
33
33
|
"generate-api": "node scripts/generate-api.js",
|
|
34
34
|
"check-highlight": "node scripts/check-highlight.js",
|
|
35
35
|
"vendor-libs": "node scripts/vendor-libs.js",
|
package/src/cli.js
CHANGED
|
@@ -34,27 +34,69 @@ function copyDir(from, to) {
|
|
|
34
34
|
}
|
|
35
35
|
}
|
|
36
36
|
|
|
37
|
+
function tryRm(target) {
|
|
38
|
+
try {
|
|
39
|
+
fs.rmSync(target, { recursive: true, force: true });
|
|
40
|
+
return true;
|
|
41
|
+
} catch (err) {
|
|
42
|
+
if (err.code === "EPERM" || err.code === "EBUSY" || err.code === "ENOTEMPTY") {
|
|
43
|
+
console.error("cluaupp: skip remove", target, `(${err.code})`);
|
|
44
|
+
return false;
|
|
45
|
+
}
|
|
46
|
+
throw err;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function copyFileIfChanged(src, dest) {
|
|
51
|
+
if (fs.existsSync(dest)) {
|
|
52
|
+
const from = fs.statSync(src);
|
|
53
|
+
const to = fs.statSync(dest);
|
|
54
|
+
if (from.size === to.size && from.mtimeMs <= to.mtimeMs) {
|
|
55
|
+
return;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
fs.mkdirSync(path.dirname(dest), { recursive: true });
|
|
59
|
+
try {
|
|
60
|
+
fs.copyFileSync(src, dest);
|
|
61
|
+
} catch (err) {
|
|
62
|
+
if (err.code === "EPERM" || err.code === "EBUSY") {
|
|
63
|
+
console.error("cluaupp: skip copy", dest, `(${err.code})`);
|
|
64
|
+
return;
|
|
65
|
+
}
|
|
66
|
+
throw err;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function syncDir(from, to) {
|
|
71
|
+
if (!fs.existsSync(from)) {
|
|
72
|
+
return;
|
|
73
|
+
}
|
|
74
|
+
fs.mkdirSync(to, { recursive: true });
|
|
75
|
+
for (const entry of fs.readdirSync(from, { withFileTypes: true })) {
|
|
76
|
+
const src = path.join(from, entry.name);
|
|
77
|
+
const dest = path.join(to, entry.name);
|
|
78
|
+
if (entry.isDirectory()) {
|
|
79
|
+
syncDir(src, dest);
|
|
80
|
+
} else {
|
|
81
|
+
copyFileIfChanged(src, dest);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
37
86
|
function copyRuntime(dest) {
|
|
38
87
|
const runtime = path.join(__dirname, "..", "runtime");
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
fs.rmSync(libs, { recursive: true, force: true });
|
|
42
|
-
}
|
|
43
|
-
if (fs.existsSync(runtime)) {
|
|
44
|
-
copyDir(runtime, libs);
|
|
88
|
+
if (!fs.existsSync(runtime)) {
|
|
89
|
+
return;
|
|
45
90
|
}
|
|
91
|
+
syncDir(runtime, path.join(dest, "libs"));
|
|
46
92
|
}
|
|
47
93
|
|
|
48
94
|
function copyHeaders(dest) {
|
|
49
95
|
const from = path.join(__dirname, "..", "include", "cluaupp");
|
|
50
|
-
const to = path.join(dest, "include", "cluaupp");
|
|
51
96
|
if (!fs.existsSync(from)) {
|
|
52
97
|
return;
|
|
53
98
|
}
|
|
54
|
-
|
|
55
|
-
fs.rmSync(to, { recursive: true, force: true });
|
|
56
|
-
}
|
|
57
|
-
copyDir(from, to);
|
|
99
|
+
syncDir(from, path.join(dest, "include", "cluaupp"));
|
|
58
100
|
}
|
|
59
101
|
|
|
60
102
|
function loadConfig(root) {
|
|
@@ -83,6 +125,21 @@ function collectCpp(dir, files = []) {
|
|
|
83
125
|
return files;
|
|
84
126
|
}
|
|
85
127
|
|
|
128
|
+
function collectFiles(dir, files = []) {
|
|
129
|
+
if (!fs.existsSync(dir)) {
|
|
130
|
+
return files;
|
|
131
|
+
}
|
|
132
|
+
for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
|
|
133
|
+
const full = path.join(dir, entry.name);
|
|
134
|
+
if (entry.isDirectory()) {
|
|
135
|
+
collectFiles(full, files);
|
|
136
|
+
} else {
|
|
137
|
+
files.push(full);
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
return files;
|
|
141
|
+
}
|
|
142
|
+
|
|
86
143
|
function siblingCpp(file) {
|
|
87
144
|
return file.replace(/\.(h|hpp|hh)$/i, ".cpp");
|
|
88
145
|
}
|
|
@@ -92,29 +149,100 @@ function resolveKey(file) {
|
|
|
92
149
|
return process.platform === "win32" ? resolved.toLowerCase() : resolved;
|
|
93
150
|
}
|
|
94
151
|
|
|
152
|
+
function posixRel(from, file) {
|
|
153
|
+
return path.relative(from, file).replace(/\\/g, "/");
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
function sourcePrefixes(rel) {
|
|
157
|
+
const noExt = rel.replace(/\.(cpp|cc|cxx|c|h|hpp|hh)$/i, "");
|
|
158
|
+
const noTag = noExt.replace(/\.(server|client)$/i, "");
|
|
159
|
+
return [...new Set([noExt, noTag, toLuauPath(rel).replace(/\\/g, "/")])];
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
function matchesPrefix(relOut, prefixes) {
|
|
163
|
+
const n = relOut.replace(/\\/g, "/").toLowerCase();
|
|
164
|
+
return prefixes.some((prefix) => {
|
|
165
|
+
const k = prefix.replace(/\\/g, "/").toLowerCase();
|
|
166
|
+
return n === k || n === `${k}.luau` || n.startsWith(`${k}/`) || n.startsWith(`${k}.`);
|
|
167
|
+
});
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
function removeEmptyOutDirs(outDir, projectRoot) {
|
|
171
|
+
if (!fs.existsSync(outDir)) {
|
|
172
|
+
return;
|
|
173
|
+
}
|
|
174
|
+
const walk = (dir) => {
|
|
175
|
+
if (resolveKey(dir) === resolveKey(outDir)) {
|
|
176
|
+
for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
|
|
177
|
+
if (entry.isDirectory()) {
|
|
178
|
+
walk(path.join(dir, entry.name));
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
return;
|
|
182
|
+
}
|
|
183
|
+
for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
|
|
184
|
+
if (entry.isDirectory()) {
|
|
185
|
+
walk(path.join(dir, entry.name));
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
if (fs.existsSync(dir) && fs.readdirSync(dir).length === 0) {
|
|
189
|
+
if (tryRm(dir)) {
|
|
190
|
+
console.log("cluaupp: removed", path.relative(projectRoot, dir));
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
};
|
|
194
|
+
walk(outDir);
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
function pruneOut(root, config, written, failedPrefixes) {
|
|
198
|
+
const outDir = path.join(root, config.outDir);
|
|
199
|
+
if (!fs.existsSync(outDir)) {
|
|
200
|
+
return;
|
|
201
|
+
}
|
|
202
|
+
for (const file of collectFiles(outDir)) {
|
|
203
|
+
if (written.has(resolveKey(file))) {
|
|
204
|
+
continue;
|
|
205
|
+
}
|
|
206
|
+
const rel = posixRel(outDir, file);
|
|
207
|
+
if (matchesPrefix(rel, failedPrefixes)) {
|
|
208
|
+
continue;
|
|
209
|
+
}
|
|
210
|
+
if (tryRm(file)) {
|
|
211
|
+
console.log("cluaupp: removed", path.relative(root, file));
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
removeEmptyOutDirs(outDir, root);
|
|
215
|
+
}
|
|
216
|
+
|
|
95
217
|
function build(root, options = {}) {
|
|
96
218
|
const exitOnError = options.exitOnError !== false;
|
|
219
|
+
const syncVendor = options.syncVendor !== false;
|
|
97
220
|
const config = loadConfig(root);
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
221
|
+
if (syncVendor) {
|
|
222
|
+
copyRuntime(root);
|
|
223
|
+
copyHeaders(root);
|
|
224
|
+
}
|
|
225
|
+
const srcDir = path.join(root, config.rootDir);
|
|
226
|
+
const files = collectCpp(srcDir);
|
|
101
227
|
if (files.length === 0) {
|
|
102
228
|
console.error("no .cpp/.h/.hpp files in", config.rootDir);
|
|
229
|
+
pruneOut(root, config, new Set(), []);
|
|
103
230
|
if (exitOnError) {
|
|
104
231
|
process.exit(1);
|
|
105
232
|
}
|
|
106
|
-
return;
|
|
233
|
+
return { failed: 0, written: new Set() };
|
|
107
234
|
}
|
|
108
235
|
|
|
109
236
|
const fileSet = new Set(files.map((file) => path.resolve(file)));
|
|
110
237
|
let failed = 0;
|
|
111
238
|
const written = new Set();
|
|
239
|
+
const failedPrefixes = [];
|
|
112
240
|
for (const file of files) {
|
|
113
241
|
if (isHeaderFile(file) && fileSet.has(path.resolve(siblingCpp(file)))) {
|
|
114
242
|
continue;
|
|
115
243
|
}
|
|
116
244
|
const source = fs.readFileSync(file, "utf8");
|
|
117
|
-
const rel =
|
|
245
|
+
const rel = posixRel(srcDir, file);
|
|
118
246
|
try {
|
|
119
247
|
const result = compileService(source, rel, {
|
|
120
248
|
...config,
|
|
@@ -122,7 +250,7 @@ function build(root, options = {}) {
|
|
|
122
250
|
relativeName: rel,
|
|
123
251
|
outName: toLuauPath(rel),
|
|
124
252
|
architecture: config.architecture !== false,
|
|
125
|
-
includeDirs: [path.dirname(file),
|
|
253
|
+
includeDirs: [path.dirname(file), srcDir, path.join(root, "include")],
|
|
126
254
|
});
|
|
127
255
|
for (const artifact of result.files) {
|
|
128
256
|
const dest = path.join(root, config.outDir, artifact.name);
|
|
@@ -134,18 +262,22 @@ function build(root, options = {}) {
|
|
|
134
262
|
for (const stale of result.stale || []) {
|
|
135
263
|
const dest = path.join(root, config.outDir, stale);
|
|
136
264
|
if (fs.existsSync(dest) && !written.has(resolveKey(dest))) {
|
|
137
|
-
|
|
138
|
-
|
|
265
|
+
if (tryRm(dest)) {
|
|
266
|
+
console.log("cluaupp: removed", path.relative(root, dest));
|
|
267
|
+
}
|
|
139
268
|
}
|
|
140
269
|
}
|
|
141
270
|
} catch (err) {
|
|
142
271
|
failed += 1;
|
|
272
|
+
failedPrefixes.push(...sourcePrefixes(rel));
|
|
143
273
|
console.error(err.message);
|
|
144
274
|
}
|
|
145
275
|
}
|
|
276
|
+
pruneOut(root, config, written, failedPrefixes);
|
|
146
277
|
if (failed > 0 && exitOnError) {
|
|
147
278
|
process.exit(1);
|
|
148
279
|
}
|
|
280
|
+
return { failed, written };
|
|
149
281
|
}
|
|
150
282
|
|
|
151
283
|
function init(dest) {
|
|
@@ -160,18 +292,39 @@ function init(dest) {
|
|
|
160
292
|
}
|
|
161
293
|
|
|
162
294
|
function watch(root) {
|
|
163
|
-
build(root, { exitOnError: false });
|
|
295
|
+
build(root, { exitOnError: false, syncVendor: true });
|
|
164
296
|
const config = loadConfig(root);
|
|
165
297
|
const dir = path.join(root, config.rootDir);
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
298
|
+
let timer = null;
|
|
299
|
+
let running = false;
|
|
300
|
+
let queued = false;
|
|
301
|
+
|
|
302
|
+
const run = () => {
|
|
303
|
+
if (running) {
|
|
304
|
+
queued = true;
|
|
305
|
+
return;
|
|
306
|
+
}
|
|
307
|
+
running = true;
|
|
308
|
+
try {
|
|
309
|
+
build(root, { exitOnError: false, syncVendor: false });
|
|
310
|
+
} catch (err) {
|
|
311
|
+
console.error(err.message);
|
|
312
|
+
} finally {
|
|
313
|
+
running = false;
|
|
314
|
+
if (queued) {
|
|
315
|
+
queued = false;
|
|
316
|
+
run();
|
|
173
317
|
}
|
|
174
318
|
}
|
|
319
|
+
};
|
|
320
|
+
|
|
321
|
+
console.log("watching", dir);
|
|
322
|
+
if (!fs.existsSync(dir)) {
|
|
323
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
324
|
+
}
|
|
325
|
+
fs.watch(dir, { recursive: true }, () => {
|
|
326
|
+
clearTimeout(timer);
|
|
327
|
+
timer = setTimeout(run, 200);
|
|
175
328
|
});
|
|
176
329
|
}
|
|
177
330
|
|
|
@@ -190,3 +343,5 @@ if (cmd === "init") {
|
|
|
190
343
|
} else {
|
|
191
344
|
printHelp();
|
|
192
345
|
}
|
|
346
|
+
|
|
347
|
+
module.exports = { build, watch };
|
package/src/emit.js
CHANGED
|
@@ -31,6 +31,13 @@ function emit(ast, options = {}) {
|
|
|
31
31
|
return `"${node.value}"`;
|
|
32
32
|
case "ident":
|
|
33
33
|
return node.name;
|
|
34
|
+
case "initlist": {
|
|
35
|
+
const entries = (node.fields || []).map((field) => `${field.name} = ${emitExpr(field.value)}`);
|
|
36
|
+
if (entries.length === 0) {
|
|
37
|
+
return "{}";
|
|
38
|
+
}
|
|
39
|
+
return `{ ${entries.join(", ")} }`;
|
|
40
|
+
}
|
|
34
41
|
case "unary": {
|
|
35
42
|
const inner = emitExpr(node.argument);
|
|
36
43
|
if (node.op === "!") {
|
package/src/parse.js
CHANGED
|
@@ -84,6 +84,28 @@ function parse(source, fileName) {
|
|
|
84
84
|
return args;
|
|
85
85
|
}
|
|
86
86
|
|
|
87
|
+
function parseInitList() {
|
|
88
|
+
eat("op", "{");
|
|
89
|
+
const fields = [];
|
|
90
|
+
while (!at("op", "}") && !at("eof")) {
|
|
91
|
+
if (at("op", ".")) {
|
|
92
|
+
i += 1;
|
|
93
|
+
const name = eat("ident").value;
|
|
94
|
+
eat("op", "=");
|
|
95
|
+
fields.push({ name, value: parseExpr() });
|
|
96
|
+
} else {
|
|
97
|
+
throw error("expected designated initializer .Field = value");
|
|
98
|
+
}
|
|
99
|
+
if (at("op", ",")) {
|
|
100
|
+
i += 1;
|
|
101
|
+
} else {
|
|
102
|
+
break;
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
eat("op", "}");
|
|
106
|
+
return { type: "initlist", fields };
|
|
107
|
+
}
|
|
108
|
+
|
|
87
109
|
function parsePrimary() {
|
|
88
110
|
if (at("kw", "true") || at("kw", "false")) {
|
|
89
111
|
return { type: "bool", value: eat("kw").value === "true" };
|
|
@@ -120,6 +142,9 @@ function parse(source, fileName) {
|
|
|
120
142
|
eat("op", ")");
|
|
121
143
|
return expr;
|
|
122
144
|
}
|
|
145
|
+
if (at("op", "{")) {
|
|
146
|
+
return parseInitList();
|
|
147
|
+
}
|
|
123
148
|
throw error("invalid expression");
|
|
124
149
|
}
|
|
125
150
|
|
|
@@ -155,6 +180,10 @@ function parse(source, fileName) {
|
|
|
155
180
|
node = { type: "call", object: null, name: node.name, args: parseArgs(), access: "." };
|
|
156
181
|
continue;
|
|
157
182
|
}
|
|
183
|
+
if (at("op", "{") && (node.type === "ident" || node.type === "member")) {
|
|
184
|
+
node = parseInitList();
|
|
185
|
+
continue;
|
|
186
|
+
}
|
|
158
187
|
break;
|
|
159
188
|
}
|
|
160
189
|
return node;
|
|
@@ -5,19 +5,23 @@
|
|
|
5
5
|
"ServerScriptService": {
|
|
6
6
|
"$className": "ServerScriptService",
|
|
7
7
|
"Cluaupp": {
|
|
8
|
-
"$path": "out/server"
|
|
8
|
+
"$path": "out/server",
|
|
9
|
+
"$optional": true
|
|
9
10
|
}
|
|
10
11
|
},
|
|
11
12
|
"ReplicatedStorage": {
|
|
12
13
|
"$className": "ReplicatedStorage",
|
|
13
14
|
"Cluaupp": {
|
|
14
|
-
"$path": "out/shared"
|
|
15
|
+
"$path": "out/shared",
|
|
16
|
+
"$optional": true
|
|
15
17
|
},
|
|
16
18
|
"CluauppLibs": {
|
|
17
|
-
"$path": "libs"
|
|
19
|
+
"$path": "libs",
|
|
20
|
+
"$optional": true
|
|
18
21
|
},
|
|
19
22
|
"Packages": {
|
|
20
|
-
"$path": "Packages"
|
|
23
|
+
"$path": "Packages",
|
|
24
|
+
"$optional": true
|
|
21
25
|
}
|
|
22
26
|
},
|
|
23
27
|
"StarterPlayer": {
|
|
@@ -25,7 +29,8 @@
|
|
|
25
29
|
"StarterPlayerScripts": {
|
|
26
30
|
"$className": "StarterPlayerScripts",
|
|
27
31
|
"Cluaupp": {
|
|
28
|
-
"$path": "out/client"
|
|
32
|
+
"$path": "out/client",
|
|
33
|
+
"$optional": true
|
|
29
34
|
}
|
|
30
35
|
}
|
|
31
36
|
},
|