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.
Files changed (47) hide show
  1. package/CHANGELOG.md +11 -0
  2. package/README.md +50 -21
  3. package/README.zh.md +61 -61
  4. package/dist/src/__tests__/e2e/basic.test.js +25 -0
  5. package/dist/src/__tests__/e2e/coroutine.test.js +22 -0
  6. package/dist/src/__tests__/mc-integration.test.js +25 -13
  7. package/dist/src/__tests__/schedule.test.js +105 -0
  8. package/dist/src/__tests__/typechecker.test.js +63 -0
  9. package/dist/src/emit/compile.js +1 -0
  10. package/dist/src/emit/index.js +3 -1
  11. package/dist/src/lir/lower.js +26 -0
  12. package/dist/src/mir/lower.js +341 -12
  13. package/dist/src/mir/types.d.ts +10 -0
  14. package/dist/src/optimizer/copy_prop.js +4 -0
  15. package/dist/src/optimizer/coroutine.d.ts +2 -0
  16. package/dist/src/optimizer/coroutine.js +33 -1
  17. package/dist/src/optimizer/dce.js +7 -1
  18. package/dist/src/optimizer/lir/const_imm.js +1 -1
  19. package/dist/src/optimizer/lir/dead_slot.js +1 -1
  20. package/dist/src/typechecker/index.d.ts +2 -0
  21. package/dist/src/typechecker/index.js +29 -0
  22. package/docs/ROADMAP.md +35 -0
  23. package/editors/vscode/package-lock.json +3 -3
  24. package/editors/vscode/package.json +1 -1
  25. package/examples/coroutine-demo.mcrs +11 -10
  26. package/jest.config.js +19 -0
  27. package/package.json +1 -1
  28. package/src/__tests__/e2e/basic.test.ts +27 -0
  29. package/src/__tests__/e2e/coroutine.test.ts +23 -0
  30. package/src/__tests__/fixtures/array-test.mcrs +21 -22
  31. package/src/__tests__/fixtures/counter.mcrs +17 -0
  32. package/src/__tests__/fixtures/foreach-at-test.mcrs +9 -10
  33. package/src/__tests__/mc-integration.test.ts +25 -13
  34. package/src/__tests__/schedule.test.ts +112 -0
  35. package/src/__tests__/typechecker.test.ts +68 -0
  36. package/src/emit/compile.ts +1 -0
  37. package/src/emit/index.ts +3 -1
  38. package/src/lir/lower.ts +27 -0
  39. package/src/mir/lower.ts +355 -9
  40. package/src/mir/types.ts +4 -0
  41. package/src/optimizer/copy_prop.ts +4 -0
  42. package/src/optimizer/coroutine.ts +37 -1
  43. package/src/optimizer/dce.ts +6 -1
  44. package/src/optimizer/lir/const_imm.ts +1 -1
  45. package/src/optimizer/lir/dead_slot.ts +1 -1
  46. package/src/stdlib/timer.mcrs +10 -5
  47. package/src/typechecker/index.ts +39 -0
@@ -1,11 +1,16 @@
1
1
  // Timer utilities with an OOP-style API.
2
2
  //
3
- // Timer state is stored in scoreboard-backed runtime state. Because RedScript
4
- // does not yet support dynamic scoreboard player/objective names for impl
5
- // methods, this API currently uses a single shared runtime slot.
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
- // The `_id` field is reserved for a future runtime-backed instance identifier.
8
- // Today it remains `0`, while persistence is shared across Timer values.
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,
@@ -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)