redscript-mc 2.1.1 → 2.2.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 +11 -0
- package/README.md +50 -21
- package/README.zh.md +61 -61
- package/dist/src/__tests__/e2e/basic.test.js +25 -0
- package/dist/src/__tests__/e2e/coroutine.test.js +22 -0
- package/dist/src/__tests__/mc-integration.test.js +25 -13
- package/dist/src/__tests__/schedule.test.js +105 -0
- package/dist/src/__tests__/typechecker.test.js +63 -0
- package/dist/src/emit/compile.js +1 -0
- package/dist/src/emit/index.js +3 -1
- package/dist/src/lir/lower.js +26 -0
- package/dist/src/mir/lower.js +341 -12
- package/dist/src/mir/types.d.ts +10 -0
- package/dist/src/optimizer/copy_prop.js +4 -0
- package/dist/src/optimizer/coroutine.d.ts +2 -0
- package/dist/src/optimizer/coroutine.js +33 -1
- package/dist/src/optimizer/dce.js +7 -1
- package/dist/src/optimizer/lir/const_imm.js +1 -1
- package/dist/src/optimizer/lir/dead_slot.js +1 -1
- package/dist/src/typechecker/index.d.ts +2 -0
- package/dist/src/typechecker/index.js +29 -0
- package/docs/ROADMAP.md +35 -0
- package/editors/vscode/package-lock.json +3 -3
- package/editors/vscode/package.json +1 -1
- package/examples/coroutine-demo.mcrs +11 -10
- package/jest.config.js +19 -0
- package/package.json +1 -1
- package/src/__tests__/e2e/basic.test.ts +27 -0
- package/src/__tests__/e2e/coroutine.test.ts +23 -0
- package/src/__tests__/fixtures/array-test.mcrs +21 -22
- package/src/__tests__/fixtures/counter.mcrs +17 -0
- package/src/__tests__/fixtures/foreach-at-test.mcrs +9 -10
- package/src/__tests__/mc-integration.test.ts +25 -13
- package/src/__tests__/schedule.test.ts +112 -0
- package/src/__tests__/typechecker.test.ts +68 -0
- package/src/emit/compile.ts +1 -0
- package/src/emit/index.ts +3 -1
- package/src/lir/lower.ts +27 -0
- package/src/mir/lower.ts +355 -9
- package/src/mir/types.ts +4 -0
- package/src/optimizer/copy_prop.ts +4 -0
- package/src/optimizer/coroutine.ts +37 -1
- package/src/optimizer/dce.ts +6 -1
- package/src/optimizer/lir/const_imm.ts +1 -1
- package/src/optimizer/lir/dead_slot.ts +1 -1
- package/src/stdlib/timer.mcrs +10 -5
- package/src/typechecker/index.ts +39 -0
package/src/stdlib/timer.mcrs
CHANGED
|
@@ -1,11 +1,16 @@
|
|
|
1
1
|
// Timer utilities with an OOP-style API.
|
|
2
2
|
//
|
|
3
|
-
// Timer
|
|
4
|
-
//
|
|
5
|
-
//
|
|
3
|
+
// Each Timer::new() call is statically allocated a unique compile-time ID
|
|
4
|
+
// (0, 1, 2, ...). The compiler intercepts Timer method calls and inlines
|
|
5
|
+
// them as direct scoreboard operations on per-instance slots:
|
|
6
|
+
// __timer_N_ticks, __timer_N_active (stored in the namespace objective)
|
|
6
7
|
//
|
|
7
|
-
//
|
|
8
|
-
//
|
|
8
|
+
// Restriction: Timer::new() must be called at the top level of a function,
|
|
9
|
+
// not inside loops or if/else bodies (compile error enforced by TypeChecker).
|
|
10
|
+
//
|
|
11
|
+
// This file provides the struct definition and method stubs. The actual
|
|
12
|
+
// scoreboard operations are generated by the MIR lowering pass and never
|
|
13
|
+
// call these method bodies directly when the _id is statically known.
|
|
9
14
|
|
|
10
15
|
struct Timer {
|
|
11
16
|
_id: int,
|
package/src/typechecker/index.ts
CHANGED
|
@@ -138,6 +138,9 @@ export class TypeChecker {
|
|
|
138
138
|
private scope: Map<string, ScopeSymbol> = new Map()
|
|
139
139
|
// Stack for tracking @s type in different contexts
|
|
140
140
|
private selfTypeStack: EntityTypeName[] = ['entity']
|
|
141
|
+
// Depth of loop/conditional nesting (for static-allocation enforcement)
|
|
142
|
+
private loopDepth = 0
|
|
143
|
+
private condDepth = 0
|
|
141
144
|
|
|
142
145
|
private readonly richTextBuiltins = new Map<string, { messageIndex: number }>([
|
|
143
146
|
['say', { messageIndex: 0 }],
|
|
@@ -352,17 +355,23 @@ export class TypeChecker {
|
|
|
352
355
|
break
|
|
353
356
|
case 'if':
|
|
354
357
|
this.checkExpr(stmt.cond)
|
|
358
|
+
this.condDepth++
|
|
355
359
|
this.checkIfBranches(stmt)
|
|
360
|
+
this.condDepth--
|
|
356
361
|
break
|
|
357
362
|
case 'while':
|
|
358
363
|
this.checkExpr(stmt.cond)
|
|
364
|
+
this.loopDepth++
|
|
359
365
|
this.checkBlock(stmt.body)
|
|
366
|
+
this.loopDepth--
|
|
360
367
|
break
|
|
361
368
|
case 'for':
|
|
362
369
|
if (stmt.init) this.checkStmt(stmt.init)
|
|
363
370
|
this.checkExpr(stmt.cond)
|
|
364
371
|
this.checkExpr(stmt.step)
|
|
372
|
+
this.loopDepth++
|
|
365
373
|
this.checkBlock(stmt.body)
|
|
374
|
+
this.loopDepth--
|
|
366
375
|
break
|
|
367
376
|
case 'foreach':
|
|
368
377
|
this.checkExpr(stmt.iterable)
|
|
@@ -375,7 +384,9 @@ export class TypeChecker {
|
|
|
375
384
|
})
|
|
376
385
|
// Push self type context for @s inside the loop
|
|
377
386
|
this.pushSelfType(entityType)
|
|
387
|
+
this.loopDepth++
|
|
378
388
|
this.checkBlock(stmt.body)
|
|
389
|
+
this.loopDepth--
|
|
379
390
|
this.popSelfType()
|
|
380
391
|
} else {
|
|
381
392
|
const iterableType = this.inferType(stmt.iterable)
|
|
@@ -384,7 +395,9 @@ export class TypeChecker {
|
|
|
384
395
|
} else {
|
|
385
396
|
this.scope.set(stmt.binding, { type: { kind: 'named', name: 'void' }, mutable: true })
|
|
386
397
|
}
|
|
398
|
+
this.loopDepth++
|
|
387
399
|
this.checkBlock(stmt.body)
|
|
400
|
+
this.loopDepth--
|
|
388
401
|
}
|
|
389
402
|
break
|
|
390
403
|
case 'match':
|
|
@@ -712,6 +725,19 @@ export class TypeChecker {
|
|
|
712
725
|
|
|
713
726
|
const builtin = BUILTIN_SIGNATURES[expr.fn]
|
|
714
727
|
if (builtin) {
|
|
728
|
+
if (expr.fn === 'setTimeout' || expr.fn === 'setInterval') {
|
|
729
|
+
if (this.loopDepth > 0) {
|
|
730
|
+
this.report(
|
|
731
|
+
`${expr.fn}() cannot be called inside a loop. Declare timers at the top level.`,
|
|
732
|
+
expr
|
|
733
|
+
)
|
|
734
|
+
} else if (this.condDepth > 0) {
|
|
735
|
+
this.report(
|
|
736
|
+
`${expr.fn}() cannot be called inside an if/else body. Declare timers at the top level.`,
|
|
737
|
+
expr
|
|
738
|
+
)
|
|
739
|
+
}
|
|
740
|
+
}
|
|
715
741
|
this.checkFunctionCallArgs(expr.args, builtin.params, expr.fn, expr)
|
|
716
742
|
return
|
|
717
743
|
}
|
|
@@ -907,6 +933,19 @@ export class TypeChecker {
|
|
|
907
933
|
}
|
|
908
934
|
|
|
909
935
|
private checkStaticCallExpr(expr: Extract<Expr, { kind: 'static_call' }>): void {
|
|
936
|
+
if (expr.type === 'Timer' && expr.method === 'new') {
|
|
937
|
+
if (this.loopDepth > 0) {
|
|
938
|
+
this.report(
|
|
939
|
+
`Timer::new() cannot be called inside a loop. Declare timers at the top level.`,
|
|
940
|
+
expr
|
|
941
|
+
)
|
|
942
|
+
} else if (this.condDepth > 0) {
|
|
943
|
+
this.report(
|
|
944
|
+
`Timer::new() cannot be called inside an if/else body. Declare timers at the top level.`,
|
|
945
|
+
expr
|
|
946
|
+
)
|
|
947
|
+
}
|
|
948
|
+
}
|
|
910
949
|
const method = this.implMethods.get(expr.type)?.get(expr.method)
|
|
911
950
|
if (!method) {
|
|
912
951
|
this.report(`Type '${expr.type}' has no static method '${expr.method}'`, expr)
|