redscript-mc 1.0.0 → 1.2.0
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/.github/ISSUE_TEMPLATE/bug_report.yml +72 -0
- package/.github/ISSUE_TEMPLATE/feature_request.yml +57 -0
- package/.github/PULL_REQUEST_TEMPLATE.md +17 -25
- package/CHANGELOG.md +112 -0
- package/CONTRIBUTING.md +140 -0
- package/README.md +28 -19
- package/README.zh.md +28 -19
- package/dist/__tests__/cli.test.js +148 -10
- package/dist/__tests__/codegen.test.js +26 -1
- package/dist/__tests__/diagnostics.test.js +5 -5
- package/dist/__tests__/e2e.test.js +336 -17
- package/dist/__tests__/formatter.test.d.ts +1 -0
- package/dist/__tests__/formatter.test.js +40 -0
- package/dist/__tests__/lexer.test.js +12 -2
- package/dist/__tests__/lowering.test.js +200 -12
- package/dist/__tests__/mc-integration.test.js +370 -31
- package/dist/__tests__/mc-syntax.test.js +3 -3
- package/dist/__tests__/nbt.test.js +2 -2
- package/dist/__tests__/optimizer-advanced.test.js +5 -5
- package/dist/__tests__/parser.test.js +80 -0
- package/dist/__tests__/runtime.test.js +9 -9
- package/dist/__tests__/typechecker.test.js +158 -0
- package/dist/ast/types.d.ts +40 -3
- package/dist/cli.js +25 -7
- package/dist/codegen/mcfunction/index.d.ts +1 -1
- package/dist/codegen/mcfunction/index.js +38 -3
- package/dist/codegen/structure/index.js +32 -1
- package/dist/compile.d.ts +10 -0
- package/dist/compile.js +36 -5
- package/dist/events/types.d.ts +35 -0
- package/dist/events/types.js +59 -0
- package/dist/formatter/index.d.ts +1 -0
- package/dist/formatter/index.js +26 -0
- package/dist/index.js +3 -2
- package/dist/ir/builder.d.ts +2 -1
- package/dist/ir/types.d.ts +11 -2
- package/dist/ir/types.js +1 -1
- package/dist/lexer/index.d.ts +1 -1
- package/dist/lexer/index.js +2 -0
- package/dist/lowering/index.d.ts +34 -1
- package/dist/lowering/index.js +622 -23
- package/dist/mc-test/runner.d.ts +2 -2
- package/dist/mc-test/runner.js +3 -3
- package/dist/mc-test/setup.js +2 -2
- package/dist/parser/index.d.ts +4 -0
- package/dist/parser/index.js +153 -16
- package/dist/typechecker/index.d.ts +17 -0
- package/dist/typechecker/index.js +343 -17
- package/docs/COMPILATION_STATS.md +24 -24
- package/docs/ENTITY_TYPE_SYSTEM.md +242 -0
- package/docs/IMPLEMENTATION_GUIDE.md +1 -1
- package/docs/STRUCTURE_TARGET.md +1 -1
- package/editors/vscode/.vscodeignore +1 -0
- package/editors/vscode/CHANGELOG.md +9 -0
- package/editors/vscode/icons/mcrs.svg +7 -0
- package/editors/vscode/icons/redscript-icons.json +10 -0
- package/editors/vscode/out/extension.js +1295 -80
- package/editors/vscode/package-lock.json +2 -2
- package/editors/vscode/package.json +10 -3
- package/editors/vscode/src/hover.ts +55 -2
- package/editors/vscode/src/symbols.ts +42 -0
- package/package.json +1 -1
- package/src/__tests__/cli.test.ts +176 -10
- package/src/__tests__/codegen.test.ts +28 -1
- package/src/__tests__/diagnostics.test.ts +5 -5
- package/src/__tests__/e2e.test.ts +335 -17
- package/src/__tests__/fixtures/event-test.mcrs +13 -0
- package/src/__tests__/fixtures/impl-test.mcrs +46 -0
- package/src/__tests__/fixtures/interval-test.mcrs +11 -0
- package/src/__tests__/fixtures/is-check-test.mcrs +20 -0
- package/src/__tests__/fixtures/timeout-test.mcrs +7 -0
- package/src/__tests__/lexer.test.ts +14 -2
- package/src/__tests__/lowering.test.ts +226 -12
- package/src/__tests__/mc-integration.test.ts +421 -31
- package/src/__tests__/mc-syntax.test.ts +3 -3
- package/src/__tests__/nbt.test.ts +2 -2
- package/src/__tests__/optimizer-advanced.test.ts +5 -5
- package/src/__tests__/parser.test.ts +91 -5
- package/src/__tests__/runtime.test.ts +9 -9
- package/src/__tests__/typechecker.test.ts +171 -0
- package/src/ast/types.ts +44 -3
- package/src/cli.ts +10 -10
- package/src/codegen/mcfunction/index.ts +40 -3
- package/src/codegen/structure/index.ts +35 -1
- package/src/compile.ts +54 -6
- package/src/events/types.ts +69 -0
- package/src/examples/capture_the_flag.mcrs +208 -0
- package/src/examples/{counter.rs → counter.mcrs} +1 -1
- package/src/examples/hunger_games.mcrs +301 -0
- package/src/examples/new_features_demo.mcrs +193 -0
- package/src/examples/parkour_race.mcrs +233 -0
- package/src/examples/rpg.mcrs +13 -0
- package/src/examples/{shop.rs → shop.mcrs} +1 -1
- package/src/examples/{showcase_game.rs → showcase_game.mcrs} +3 -3
- package/src/examples/{turret.rs → turret.mcrs} +1 -1
- package/src/examples/zombie_survival.mcrs +314 -0
- package/src/index.ts +4 -3
- package/src/ir/builder.ts +3 -1
- package/src/ir/types.ts +12 -2
- package/src/lexer/index.ts +3 -1
- package/src/lowering/index.ts +684 -24
- package/src/mc-test/runner.ts +3 -3
- package/src/mc-test/setup.ts +2 -2
- package/src/parser/index.ts +170 -19
- package/src/stdlib/README.md +178 -140
- package/src/stdlib/bossbar.mcrs +68 -0
- package/src/stdlib/{cooldown.rs → cooldown.mcrs} +1 -1
- package/src/stdlib/effects.mcrs +64 -0
- package/src/stdlib/interactions.mcrs +195 -0
- package/src/stdlib/inventory.mcrs +38 -0
- package/src/stdlib/mobs.mcrs +99 -0
- package/src/stdlib/particles.mcrs +52 -0
- package/src/stdlib/sets.mcrs +20 -0
- package/src/stdlib/spawn.mcrs +41 -0
- package/src/stdlib/tags.mcrs +951 -0
- package/src/stdlib/teams.mcrs +68 -0
- package/src/stdlib/timer.mcrs +72 -0
- package/src/stdlib/world.mcrs +92 -0
- package/src/typechecker/index.ts +404 -18
- package/src/examples/rpg.rs +0 -13
- package/src/stdlib/mobs.rs +0 -99
- package/src/stdlib/timer.rs +0 -51
- /package/src/examples/{arena.rs → arena.mcrs} +0 -0
- /package/src/examples/{pvp_arena.rs → pvp_arena.mcrs} +0 -0
- /package/src/examples/{quiz.rs → quiz.mcrs} +0 -0
- /package/src/examples/{stdlib_demo.rs → stdlib_demo.mcrs} +0 -0
- /package/src/examples/{world_manager.rs → world_manager.mcrs} +0 -0
- /package/src/stdlib/{combat.rs → combat.mcrs} +0 -0
- /package/src/stdlib/{math.rs → math.mcrs} +0 -0
- /package/src/stdlib/{player.rs → player.mcrs} +0 -0
- /package/src/stdlib/{strings.rs → strings.mcrs} +0 -0
- /package/src/templates/{combat.rs → combat.mcrs} +0 -0
- /package/src/templates/{economy.rs → economy.mcrs} +0 -0
- /package/src/templates/{mini-game-framework.rs → mini-game-framework.mcrs} +0 -0
- /package/src/templates/{quest.rs → quest.mcrs} +0 -0
- /package/src/test_programs/{zombie_game.rs → zombie_game.mcrs} +0 -0
|
@@ -146,6 +146,195 @@ fn main() {
|
|
|
146
146
|
})
|
|
147
147
|
})
|
|
148
148
|
|
|
149
|
+
describe('timer builtins', () => {
|
|
150
|
+
it('generates scheduled timer helper functions', () => {
|
|
151
|
+
const files = compile(`
|
|
152
|
+
fn main() {
|
|
153
|
+
let intervalId: int = setInterval(20, () => {
|
|
154
|
+
say("tick");
|
|
155
|
+
});
|
|
156
|
+
setTimeout(100, () => {
|
|
157
|
+
say("later");
|
|
158
|
+
});
|
|
159
|
+
clearInterval(intervalId);
|
|
160
|
+
}
|
|
161
|
+
`)
|
|
162
|
+
const mainFn = getFunction(files, 'main')
|
|
163
|
+
const intervalFn = getFunction(files, '__interval_0')
|
|
164
|
+
const intervalBodyFn = getFunction(files, '__interval_body_0')
|
|
165
|
+
const timeoutFn = getFunction(files, '__timeout_0')
|
|
166
|
+
expect(mainFn).toBeDefined()
|
|
167
|
+
expect(mainFn).toContain('schedule function test:__interval_0 20t')
|
|
168
|
+
expect(mainFn).toContain('schedule function test:__timeout_0 100t')
|
|
169
|
+
expect(mainFn).toContain('schedule clear test:__interval_0')
|
|
170
|
+
expect(intervalFn).toContain('function test:__interval_body_0')
|
|
171
|
+
expect(intervalFn).toContain('schedule function test:__interval_0 20t')
|
|
172
|
+
expect(intervalBodyFn).toContain('say tick')
|
|
173
|
+
expect(timeoutFn).toContain('say later')
|
|
174
|
+
})
|
|
175
|
+
})
|
|
176
|
+
|
|
177
|
+
describe('is type narrowing', () => {
|
|
178
|
+
it('type checks and compiles entity narrowing inside foreach blocks', () => {
|
|
179
|
+
const source = `
|
|
180
|
+
fn main() {
|
|
181
|
+
foreach (e in @e) {
|
|
182
|
+
if (e is Player) {
|
|
183
|
+
kill(e);
|
|
184
|
+
}
|
|
185
|
+
if (e is Zombie) {
|
|
186
|
+
kill(e);
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
`
|
|
191
|
+
expect(typeCheck(source)).toEqual([])
|
|
192
|
+
|
|
193
|
+
const files = compile(source)
|
|
194
|
+
const mainFn = getFunction(files, 'main')
|
|
195
|
+
const foreachFn = getSubFunction(files, 'main', 'foreach_0')
|
|
196
|
+
const thenFiles = files.filter(file => file.path.includes('/main/then_') && file.content.includes('kill @s'))
|
|
197
|
+
|
|
198
|
+
expect(mainFn).toContain('execute as @e run function test:main/foreach_0')
|
|
199
|
+
expect(foreachFn).toContain('execute if entity @s[type=player] run function test:main/then_')
|
|
200
|
+
expect(foreachFn).toContain('execute if entity @s[type=zombie] run function test:main/then_')
|
|
201
|
+
expect(thenFiles).toHaveLength(2)
|
|
202
|
+
})
|
|
203
|
+
})
|
|
204
|
+
|
|
205
|
+
describe('impl blocks', () => {
|
|
206
|
+
it('compiles static and instance impl methods end to end', () => {
|
|
207
|
+
const source = `
|
|
208
|
+
struct Point { x: int, y: int }
|
|
209
|
+
|
|
210
|
+
impl Point {
|
|
211
|
+
fn new(x: int, y: int) -> Point {
|
|
212
|
+
return { x: x, y: y };
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
fn distance(self) -> int {
|
|
216
|
+
return self.x + self.y;
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
fn main() {
|
|
221
|
+
let p: Point = Point::new(1, 2);
|
|
222
|
+
let d: int = p.distance();
|
|
223
|
+
say("\${d}");
|
|
224
|
+
}
|
|
225
|
+
`
|
|
226
|
+
expect(typeCheck(source)).toEqual([])
|
|
227
|
+
|
|
228
|
+
const files = compile(source)
|
|
229
|
+
const mainFn = getFunction(files, 'main')
|
|
230
|
+
const staticFn = getFunction(files, 'Point_new')
|
|
231
|
+
const instanceFn = getFunction(files, 'Point_distance')
|
|
232
|
+
|
|
233
|
+
expect(mainFn).toContain('function test:Point_new')
|
|
234
|
+
expect(mainFn).toContain('function test:Point_distance')
|
|
235
|
+
expect(staticFn).toBeDefined()
|
|
236
|
+
expect(instanceFn).toBeDefined()
|
|
237
|
+
})
|
|
238
|
+
})
|
|
239
|
+
|
|
240
|
+
describe('namespace prefixing', () => {
|
|
241
|
+
it('prefixes user objectives but preserves mc_name and qualified objectives', () => {
|
|
242
|
+
const files = compile(`
|
|
243
|
+
fn main() {
|
|
244
|
+
scoreboard_set("timer", #rs, 100);
|
|
245
|
+
scoreboard_set(@s, "timer", 100);
|
|
246
|
+
scoreboard_set(@s, #health, 20);
|
|
247
|
+
scoreboard_set(@s, "custom.timer", 1);
|
|
248
|
+
}
|
|
249
|
+
`, 'pack')
|
|
250
|
+
const mainFn = getFunction(files, 'main')
|
|
251
|
+
expect(mainFn).toContain('scoreboard players set timer rs 100')
|
|
252
|
+
expect(mainFn).toContain('scoreboard players set @s pack.timer 100')
|
|
253
|
+
expect(mainFn).toContain('scoreboard players set @s health 20')
|
|
254
|
+
expect(mainFn).toContain('scoreboard players set @s custom.timer 1')
|
|
255
|
+
})
|
|
256
|
+
})
|
|
257
|
+
|
|
258
|
+
describe('Timer OOP API', () => {
|
|
259
|
+
it('compiles the Timer impl API end to end', () => {
|
|
260
|
+
const source = `
|
|
261
|
+
struct Timer {
|
|
262
|
+
_id: int,
|
|
263
|
+
_duration: int
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
impl Timer {
|
|
267
|
+
fn new(duration: int) -> Timer {
|
|
268
|
+
scoreboard_set("timer_ticks", #rs, 0);
|
|
269
|
+
scoreboard_set("timer_active", #rs, 0);
|
|
270
|
+
return { _id: 0, _duration: duration };
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
fn start(self) {
|
|
274
|
+
scoreboard_set("timer_active", #rs, 1);
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
fn pause(self) {
|
|
278
|
+
scoreboard_set("timer_active", #rs, 0);
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
fn reset(self) {
|
|
282
|
+
scoreboard_set("timer_ticks", #rs, 0);
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
fn done(self) -> bool {
|
|
286
|
+
let ticks: int = scoreboard_get("timer_ticks", #rs);
|
|
287
|
+
return ticks >= self._duration;
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
fn tick(self) {
|
|
291
|
+
let active: int = scoreboard_get("timer_active", #rs);
|
|
292
|
+
let ticks: int = scoreboard_get("timer_ticks", #rs);
|
|
293
|
+
|
|
294
|
+
if (active == 1) {
|
|
295
|
+
if (ticks < self._duration) {
|
|
296
|
+
scoreboard_set("timer_ticks", #rs, ticks + 1);
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
fn main() {
|
|
303
|
+
let t: Timer = Timer::new(100);
|
|
304
|
+
t.start();
|
|
305
|
+
t.tick();
|
|
306
|
+
let finished: bool = t.done();
|
|
307
|
+
if (finished) {
|
|
308
|
+
say("done");
|
|
309
|
+
}
|
|
310
|
+
t.pause();
|
|
311
|
+
t.reset();
|
|
312
|
+
}
|
|
313
|
+
`
|
|
314
|
+
expect(typeCheck(source)).toEqual([])
|
|
315
|
+
|
|
316
|
+
const files = compile(source, 'timerapi')
|
|
317
|
+
const mainFn = getFunction(files, 'main')
|
|
318
|
+
const newFn = getFunction(files, 'Timer_new')
|
|
319
|
+
const startFn = getFunction(files, 'Timer_start')
|
|
320
|
+
const tickFn = getFunction(files, 'Timer_tick')
|
|
321
|
+
const doneFn = getFunction(files, 'Timer_done')
|
|
322
|
+
const pauseFn = getFunction(files, 'Timer_pause')
|
|
323
|
+
const resetFn = getFunction(files, 'Timer_reset')
|
|
324
|
+
|
|
325
|
+
expect(mainFn).toContain('function timerapi:Timer_new')
|
|
326
|
+
expect(mainFn).toContain('function timerapi:Timer_start')
|
|
327
|
+
expect(mainFn).toContain('function timerapi:Timer_tick')
|
|
328
|
+
expect(mainFn).toContain('function timerapi:Timer_done')
|
|
329
|
+
expect(newFn).toContain('scoreboard players set timer_ticks rs 0')
|
|
330
|
+
expect(startFn).toContain('scoreboard players set timer_active rs 1')
|
|
331
|
+
expect(tickFn).toContain('scoreboard players get timer_active rs')
|
|
332
|
+
expect(doneFn).toContain('scoreboard players get timer_ticks rs')
|
|
333
|
+
expect(pauseFn).toContain('scoreboard players set timer_active rs 0')
|
|
334
|
+
expect(resetFn).toContain('scoreboard players set timer_ticks rs 0')
|
|
335
|
+
})
|
|
336
|
+
})
|
|
337
|
+
|
|
149
338
|
describe('advancement event decorators', () => {
|
|
150
339
|
it('generates advancement json with reward function path', () => {
|
|
151
340
|
const source = `
|
|
@@ -309,12 +498,12 @@ fn test() {
|
|
|
309
498
|
}
|
|
310
499
|
`
|
|
311
500
|
const fn = getFunction(compile(source), 'test')!
|
|
312
|
-
expect(fn).toContain('scoreboard objectives setdisplay sidebar kills')
|
|
313
|
-
expect(fn).toContain('scoreboard objectives setdisplay list coins')
|
|
314
|
-
expect(fn).toContain('scoreboard objectives setdisplay belowName hp')
|
|
501
|
+
expect(fn).toContain('scoreboard objectives setdisplay sidebar test.kills')
|
|
502
|
+
expect(fn).toContain('scoreboard objectives setdisplay list test.coins')
|
|
503
|
+
expect(fn).toContain('scoreboard objectives setdisplay belowName test.hp')
|
|
315
504
|
expect(fn).toContain('scoreboard objectives setdisplay sidebar')
|
|
316
|
-
expect(fn).toContain('scoreboard objectives add kills playerKillCount "Kill Count"')
|
|
317
|
-
expect(fn).toContain('scoreboard objectives remove kills')
|
|
505
|
+
expect(fn).toContain('scoreboard objectives add test.kills playerKillCount "Kill Count"')
|
|
506
|
+
expect(fn).toContain('scoreboard objectives remove test.kills')
|
|
318
507
|
})
|
|
319
508
|
|
|
320
509
|
it('compiles bossbar builtins', () => {
|
|
@@ -340,7 +529,7 @@ fn test() {
|
|
|
340
529
|
expect(fn).toContain('bossbar set ns:health visible true')
|
|
341
530
|
expect(fn).toContain('bossbar set ns:health players @a')
|
|
342
531
|
expect(fn).toContain('bossbar remove ns:health')
|
|
343
|
-
expect(fn).toMatch(/execute store result score \$
|
|
532
|
+
expect(fn).toMatch(/execute store result score \$_\d+ rs run bossbar get ns:health value/)
|
|
344
533
|
})
|
|
345
534
|
|
|
346
535
|
it('compiles team builtins', () => {
|
|
@@ -536,7 +725,7 @@ fn test() -> int {
|
|
|
536
725
|
const fn = getFunction(files, 'test')
|
|
537
726
|
expect(fn).toBeDefined()
|
|
538
727
|
expect(fn).toContain('execute store result score')
|
|
539
|
-
expect(fn).toContain('scoreboard players get PlayerName kill_count')
|
|
728
|
+
expect(fn).toContain('scoreboard players get PlayerName test.kill_count')
|
|
540
729
|
})
|
|
541
730
|
|
|
542
731
|
it('compiles scoreboard_get with @s selector', () => {
|
|
@@ -549,7 +738,7 @@ fn test() -> int {
|
|
|
549
738
|
const files = compile(source)
|
|
550
739
|
const fn = getFunction(files, 'test')
|
|
551
740
|
expect(fn).toBeDefined()
|
|
552
|
-
expect(fn).toContain('scoreboard players get @s kill_count')
|
|
741
|
+
expect(fn).toContain('scoreboard players get @s test.kill_count')
|
|
553
742
|
})
|
|
554
743
|
|
|
555
744
|
it('compiles scoreboard_set with constant value', () => {
|
|
@@ -561,7 +750,7 @@ fn test() {
|
|
|
561
750
|
const files = compile(source)
|
|
562
751
|
const fn = getFunction(files, 'test')
|
|
563
752
|
expect(fn).toBeDefined()
|
|
564
|
-
expect(fn).toContain('scoreboard players set PlayerName kill_count 100')
|
|
753
|
+
expect(fn).toContain('scoreboard players set PlayerName test.kill_count 100')
|
|
565
754
|
})
|
|
566
755
|
|
|
567
756
|
it('compiles scoreboard_set with variable value', () => {
|
|
@@ -576,7 +765,7 @@ fn test() {
|
|
|
576
765
|
.filter(f => f.path.includes('test'))
|
|
577
766
|
.map(f => f.content)
|
|
578
767
|
.join('\n')
|
|
579
|
-
expect(allContent).toContain('execute store result score @s score')
|
|
768
|
+
expect(allContent).toContain('execute store result score @s test.score')
|
|
580
769
|
})
|
|
581
770
|
|
|
582
771
|
it('compiles score() as expression', () => {
|
|
@@ -604,7 +793,7 @@ fn double_score() -> int {
|
|
|
604
793
|
const files = compile(source)
|
|
605
794
|
const fn = getFunction(files, 'double_score')
|
|
606
795
|
expect(fn).toBeDefined()
|
|
607
|
-
expect(fn).toContain('scoreboard players get @s points')
|
|
796
|
+
expect(fn).toContain('scoreboard players get @s test.points')
|
|
608
797
|
})
|
|
609
798
|
})
|
|
610
799
|
|
|
@@ -651,21 +840,21 @@ fn double_score() -> int {
|
|
|
651
840
|
const source = 'fn test() { let x: int = random(1, 10); }'
|
|
652
841
|
const files = compile(source)
|
|
653
842
|
const fn = getFunction(files, 'test')
|
|
654
|
-
expect(fn).toContain('scoreboard players random $
|
|
843
|
+
expect(fn).toContain('scoreboard players random $_0 rs 1 10')
|
|
655
844
|
})
|
|
656
845
|
|
|
657
846
|
it('compiles random_native()', () => {
|
|
658
847
|
const source = 'fn test() { let x: int = random_native(1, 6); }'
|
|
659
848
|
const files = compile(source)
|
|
660
849
|
const fn = getFunction(files, 'test')
|
|
661
|
-
expect(fn).toContain('execute store result score $
|
|
850
|
+
expect(fn).toContain('execute store result score $_0 rs run random value 1 6')
|
|
662
851
|
})
|
|
663
852
|
|
|
664
853
|
it('compiles random_native() with zero min', () => {
|
|
665
854
|
const source = 'fn test() { let x: int = random_native(0, 100); }'
|
|
666
855
|
const files = compile(source)
|
|
667
856
|
const fn = getFunction(files, 'test')
|
|
668
|
-
expect(fn).toContain('execute store result score $
|
|
857
|
+
expect(fn).toContain('execute store result score $_0 rs run random value 0 100')
|
|
669
858
|
})
|
|
670
859
|
|
|
671
860
|
it('compiles random_sequence()', () => {
|
|
@@ -1188,7 +1377,7 @@ fn handle_claim() {
|
|
|
1188
1377
|
})
|
|
1189
1378
|
})
|
|
1190
1379
|
|
|
1191
|
-
describe('Real program: zombie_game.
|
|
1380
|
+
describe('Real program: zombie_game.mcrs', () => {
|
|
1192
1381
|
const source = `
|
|
1193
1382
|
// A zombie survival game logic
|
|
1194
1383
|
// Kills nearby zombies and tracks score
|
|
@@ -1481,10 +1670,10 @@ fn heal(amount: int) {
|
|
|
1481
1670
|
|
|
1482
1671
|
describe('backward compat: string objective still works', () => {
|
|
1483
1672
|
const source = `fn test() { let x: int = scoreboard_get(@s, "kills"); }`
|
|
1484
|
-
it('
|
|
1673
|
+
it('prefixes plain string objectives with the active namespace', () => {
|
|
1485
1674
|
const files = compile(source, 'compat')
|
|
1486
1675
|
const fn = getFunction(files, 'test')
|
|
1487
|
-
expect(fn).toContain('scoreboard players get @s kills')
|
|
1676
|
+
expect(fn).toContain('scoreboard players get @s compat.kills')
|
|
1488
1677
|
})
|
|
1489
1678
|
})
|
|
1490
1679
|
|
|
@@ -1719,3 +1908,132 @@ describe('NBT parameters', () => {
|
|
|
1719
1908
|
expect(fn).toContain('{Unbreakable:1b}')
|
|
1720
1909
|
})
|
|
1721
1910
|
})
|
|
1911
|
+
|
|
1912
|
+
// ---------------------------------------------------------------------------
|
|
1913
|
+
// Set Operations
|
|
1914
|
+
// ---------------------------------------------------------------------------
|
|
1915
|
+
|
|
1916
|
+
describe('Set operations', () => {
|
|
1917
|
+
it('creates a new set', () => {
|
|
1918
|
+
const src = `fn test() { let s = set_new(); }`
|
|
1919
|
+
const files = compile(src, 'settest')
|
|
1920
|
+
const fn = getFunction(files, 'test')
|
|
1921
|
+
expect(fn).toContain('data modify storage rs:sets __set_0 set value []')
|
|
1922
|
+
})
|
|
1923
|
+
|
|
1924
|
+
it('adds to a set with uniqueness check', () => {
|
|
1925
|
+
const src = `fn test() { let s = set_new(); set_add(s, "apple"); }`
|
|
1926
|
+
const files = compile(src, 'setadd')
|
|
1927
|
+
const fn = getFunction(files, 'test')
|
|
1928
|
+
expect(fn).toContain('execute unless data storage rs:sets __set_0[{value:apple}] run data modify storage rs:sets __set_0 append value {value:apple}')
|
|
1929
|
+
})
|
|
1930
|
+
|
|
1931
|
+
it('checks set membership', () => {
|
|
1932
|
+
const src = `fn test() { let s = set_new(); set_add(s, "x"); let has = set_contains(s, "x"); }`
|
|
1933
|
+
const files = compile(src, 'setcontains')
|
|
1934
|
+
const fn = getFunction(files, 'test')
|
|
1935
|
+
expect(fn).toContain('if data storage rs:sets __set_0[{value:x}]')
|
|
1936
|
+
})
|
|
1937
|
+
|
|
1938
|
+
it('removes from a set', () => {
|
|
1939
|
+
const src = `fn test() { let s = set_new(); set_add(s, "y"); set_remove(s, "y"); }`
|
|
1940
|
+
const files = compile(src, 'setremove')
|
|
1941
|
+
const fn = getFunction(files, 'test')
|
|
1942
|
+
expect(fn).toContain('data remove storage rs:sets __set_0[{value:y}]')
|
|
1943
|
+
})
|
|
1944
|
+
|
|
1945
|
+
it('clears a set', () => {
|
|
1946
|
+
const src = `fn test() { let s = set_new(); set_clear(s); }`
|
|
1947
|
+
const files = compile(src, 'setclear')
|
|
1948
|
+
const fn = getFunction(files, 'test')
|
|
1949
|
+
expect(fn).toContain('data modify storage rs:sets __set_0 set value []')
|
|
1950
|
+
})
|
|
1951
|
+
})
|
|
1952
|
+
|
|
1953
|
+
// ---------------------------------------------------------------------------
|
|
1954
|
+
// Method Syntax Sugar
|
|
1955
|
+
// ---------------------------------------------------------------------------
|
|
1956
|
+
|
|
1957
|
+
describe('Method syntax sugar', () => {
|
|
1958
|
+
it('transforms obj.method() to method(obj)', () => {
|
|
1959
|
+
const src = `fn test() { let s = set_new(); s.clear(); }`
|
|
1960
|
+
const files = compile(src, 'method1')
|
|
1961
|
+
const fn = getFunction(files, 'test')
|
|
1962
|
+
expect(fn).toContain('data modify storage rs:sets __set_0 set value []')
|
|
1963
|
+
})
|
|
1964
|
+
|
|
1965
|
+
it('transforms obj.method(arg) to method(obj, arg)', () => {
|
|
1966
|
+
const src = `fn test() { let s = set_new(); s.add("apple"); }`
|
|
1967
|
+
const files = compile(src, 'method2')
|
|
1968
|
+
const fn = getFunction(files, 'test')
|
|
1969
|
+
expect(fn).toContain('data modify storage rs:sets __set_0 append value {value:apple}')
|
|
1970
|
+
})
|
|
1971
|
+
|
|
1972
|
+
it('transforms obj.method(arg) with contains', () => {
|
|
1973
|
+
const src = `fn test() { let s = set_new(); s.add("x"); let r = s.contains("x"); }`
|
|
1974
|
+
const files = compile(src, 'method3')
|
|
1975
|
+
const fn = getFunction(files, 'test')
|
|
1976
|
+
expect(fn).toBeDefined()
|
|
1977
|
+
})
|
|
1978
|
+
|
|
1979
|
+
it('works with multiple args', () => {
|
|
1980
|
+
const src = `fn test() { let s = set_new(); s.add("a"); s.add("b"); s.remove("a"); }`
|
|
1981
|
+
const files = compile(src, 'method4')
|
|
1982
|
+
const fn = getFunction(files, 'test')
|
|
1983
|
+
expect(fn).toContain('data remove storage rs:sets __set_0[{value:a}]')
|
|
1984
|
+
})
|
|
1985
|
+
})
|
|
1986
|
+
|
|
1987
|
+
describe('Global variables', () => {
|
|
1988
|
+
it('initializes global in __load', () => {
|
|
1989
|
+
const src = `let x: int = 42;\nfn test() { say("hi"); }`
|
|
1990
|
+
const files = compile(src, 'globaltest')
|
|
1991
|
+
const load = getFunction(files, '__load')
|
|
1992
|
+
expect(load).toContain('scoreboard players set $x rs 42')
|
|
1993
|
+
})
|
|
1994
|
+
|
|
1995
|
+
it('reads and writes global in function', () => {
|
|
1996
|
+
const src = `let count: int = 0;\nfn inc() { count = count + 1; }`
|
|
1997
|
+
const files = compile(src, 'globalrw')
|
|
1998
|
+
const fn = getFunction(files, 'inc')
|
|
1999
|
+
expect(fn).toBeDefined()
|
|
2000
|
+
// Global should be initialized in __load
|
|
2001
|
+
const load = getFunction(files, '__load')
|
|
2002
|
+
expect(load).toContain('scoreboard players set $count rs 0')
|
|
2003
|
+
})
|
|
2004
|
+
|
|
2005
|
+
it('const cannot be reassigned', () => {
|
|
2006
|
+
const src = `const X: int = 5;\nfn bad() { X = 10; }`
|
|
2007
|
+
expect(() => compile(src, 'constbad')).toThrow()
|
|
2008
|
+
})
|
|
2009
|
+
})
|
|
2010
|
+
|
|
2011
|
+
describe('@load decorator', () => {
|
|
2012
|
+
it('calls @load function from __load.mcfunction', () => {
|
|
2013
|
+
const src = `@load fn init() { say("Datapack loaded!"); }`
|
|
2014
|
+
const files = compile(src, 'loadtest')
|
|
2015
|
+
const load = getFunction(files, '__load')
|
|
2016
|
+
expect(load).toContain('function loadtest:init')
|
|
2017
|
+
})
|
|
2018
|
+
|
|
2019
|
+
it('calls multiple @load functions in order', () => {
|
|
2020
|
+
const src = `
|
|
2021
|
+
@load fn setup() { say("setup"); }
|
|
2022
|
+
@load fn init() { say("init"); }
|
|
2023
|
+
`
|
|
2024
|
+
const files = compile(src, 'loadtest')
|
|
2025
|
+
const load = getFunction(files, '__load')!
|
|
2026
|
+
const setupIdx = load.indexOf('function loadtest:setup')
|
|
2027
|
+
const initIdx = load.indexOf('function loadtest:init')
|
|
2028
|
+
expect(setupIdx).toBeGreaterThan(-1)
|
|
2029
|
+
expect(initIdx).toBeGreaterThan(-1)
|
|
2030
|
+
expect(setupIdx).toBeLessThan(initIdx)
|
|
2031
|
+
})
|
|
2032
|
+
|
|
2033
|
+
it('generates the @load function body normally', () => {
|
|
2034
|
+
const src = `@load fn init() { say("hi"); }`
|
|
2035
|
+
const files = compile(src, 'loadtest')
|
|
2036
|
+
const fn = getFunction(files, 'init')
|
|
2037
|
+
expect(fn).toContain('say hi')
|
|
2038
|
+
})
|
|
2039
|
+
})
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
// Test @on(PlayerDeath) event system
|
|
2
|
+
// Since we can't easily kill players in test, we manually set the tag
|
|
3
|
+
|
|
4
|
+
@on(PlayerDeath)
|
|
5
|
+
fn handle_death(player: Player) {
|
|
6
|
+
say("Player died event triggered!");
|
|
7
|
+
scoreboard_set("#event_test", "death_count", scoreboard_get("#event_test", "death_count") + 1);
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
fn trigger_fake_death() {
|
|
11
|
+
// Manually add the death tag to simulate event
|
|
12
|
+
tag_add(@a, "rs.just_died");
|
|
13
|
+
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
struct timer {
|
|
2
|
+
_id: int,
|
|
3
|
+
_duration: int
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
impl timer {
|
|
7
|
+
fn new(duration: int) -> timer {
|
|
8
|
+
scoreboard_set("timer_ticks", #rs, 0);
|
|
9
|
+
scoreboard_set("timer_active", #rs, 0);
|
|
10
|
+
return { _id: 0, _duration: duration };
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
fn start(self) {
|
|
14
|
+
scoreboard_set("timer_active", #rs, 1);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
fn tick(self) {
|
|
18
|
+
let active: int = scoreboard_get("timer_active", #rs);
|
|
19
|
+
let ticks: int = scoreboard_get("timer_ticks", #rs);
|
|
20
|
+
|
|
21
|
+
if (active == 1) {
|
|
22
|
+
if (ticks < self._duration) {
|
|
23
|
+
scoreboard_set("timer_ticks", #rs, ticks + 1);
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
fn done(self) -> bool {
|
|
29
|
+
let ticks: int = scoreboard_get("timer_ticks", #rs);
|
|
30
|
+
return ticks >= 3;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
fn test() {
|
|
35
|
+
let timer_value: timer = timer::new(3);
|
|
36
|
+
timer_value.start();
|
|
37
|
+
timer_value.tick();
|
|
38
|
+
timer_value.tick();
|
|
39
|
+
timer_value.tick();
|
|
40
|
+
let finished: bool = timer_value.done();
|
|
41
|
+
if (finished) {
|
|
42
|
+
scoreboard_set("#impl", #done, 1);
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
scoreboard_set("#impl", #done, 1);
|
|
46
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
fn check_types() {
|
|
2
|
+
scoreboard_set("#is_check", #players, 0);
|
|
3
|
+
scoreboard_set("#is_check", #zombies, 0);
|
|
4
|
+
|
|
5
|
+
foreach (e in @e[type=zombie,tag=is_check_target]) {
|
|
6
|
+
if (e is Player) {
|
|
7
|
+
let players: int = scoreboard_get("#is_check", #players);
|
|
8
|
+
scoreboard_set("#is_check", #players, players + 1);
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
if (e is Zombie) {
|
|
12
|
+
let players: int = scoreboard_get("#is_check", #players);
|
|
13
|
+
scoreboard_set("#is_check", #players, players);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
let zombies: int = scoreboard_get("#is_check", #zombies);
|
|
17
|
+
scoreboard_set("#is_check", #zombies, zombies + 1);
|
|
18
|
+
kill(e);
|
|
19
|
+
}
|
|
20
|
+
}
|
|
@@ -11,10 +11,17 @@ function kinds(tokens: Token[]): TokenKind[] {
|
|
|
11
11
|
describe('Lexer', () => {
|
|
12
12
|
describe('keywords', () => {
|
|
13
13
|
it('recognizes all keywords', () => {
|
|
14
|
-
const tokens = tokenize('fn let const if else while for foreach match return as at in struct enum trigger namespace')
|
|
14
|
+
const tokens = tokenize('fn let const if else while for foreach match return as at in is struct impl enum trigger namespace')
|
|
15
15
|
expect(kinds(tokens)).toEqual([
|
|
16
16
|
'fn', 'let', 'const', 'if', 'else', 'while', 'for', 'foreach', 'match',
|
|
17
|
-
'return', 'as', 'at', 'in', 'struct', 'enum', 'trigger', 'namespace', 'eof'
|
|
17
|
+
'return', 'as', 'at', 'in', 'is', 'struct', 'impl', 'enum', 'trigger', 'namespace', 'eof'
|
|
18
|
+
])
|
|
19
|
+
})
|
|
20
|
+
|
|
21
|
+
it('tokenizes is-check and impl syntax with their dedicated keywords', () => {
|
|
22
|
+
const tokens = tokenize('if (e is Player) { } impl Point { }')
|
|
23
|
+
expect(kinds(tokens)).toEqual([
|
|
24
|
+
'if', '(', 'ident', 'is', 'ident', ')', '{', '}', 'impl', 'ident', '{', '}', 'eof',
|
|
18
25
|
])
|
|
19
26
|
})
|
|
20
27
|
|
|
@@ -191,6 +198,11 @@ describe('Lexer', () => {
|
|
|
191
198
|
const tokens = tokenize('=>')
|
|
192
199
|
expect(kinds(tokens)).toEqual(['=>', 'eof'])
|
|
193
200
|
})
|
|
201
|
+
|
|
202
|
+
it('tokenizes static method separators for impl methods', () => {
|
|
203
|
+
const tokens = tokenize('Point::new()')
|
|
204
|
+
expect(kinds(tokens)).toEqual(['ident', '::', 'ident', '(', ')', 'eof'])
|
|
205
|
+
})
|
|
194
206
|
})
|
|
195
207
|
|
|
196
208
|
describe('delimiters', () => {
|