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.
Files changed (136) hide show
  1. package/.github/ISSUE_TEMPLATE/bug_report.yml +72 -0
  2. package/.github/ISSUE_TEMPLATE/feature_request.yml +57 -0
  3. package/.github/PULL_REQUEST_TEMPLATE.md +17 -25
  4. package/CHANGELOG.md +112 -0
  5. package/CONTRIBUTING.md +140 -0
  6. package/README.md +28 -19
  7. package/README.zh.md +28 -19
  8. package/dist/__tests__/cli.test.js +148 -10
  9. package/dist/__tests__/codegen.test.js +26 -1
  10. package/dist/__tests__/diagnostics.test.js +5 -5
  11. package/dist/__tests__/e2e.test.js +336 -17
  12. package/dist/__tests__/formatter.test.d.ts +1 -0
  13. package/dist/__tests__/formatter.test.js +40 -0
  14. package/dist/__tests__/lexer.test.js +12 -2
  15. package/dist/__tests__/lowering.test.js +200 -12
  16. package/dist/__tests__/mc-integration.test.js +370 -31
  17. package/dist/__tests__/mc-syntax.test.js +3 -3
  18. package/dist/__tests__/nbt.test.js +2 -2
  19. package/dist/__tests__/optimizer-advanced.test.js +5 -5
  20. package/dist/__tests__/parser.test.js +80 -0
  21. package/dist/__tests__/runtime.test.js +9 -9
  22. package/dist/__tests__/typechecker.test.js +158 -0
  23. package/dist/ast/types.d.ts +40 -3
  24. package/dist/cli.js +25 -7
  25. package/dist/codegen/mcfunction/index.d.ts +1 -1
  26. package/dist/codegen/mcfunction/index.js +38 -3
  27. package/dist/codegen/structure/index.js +32 -1
  28. package/dist/compile.d.ts +10 -0
  29. package/dist/compile.js +36 -5
  30. package/dist/events/types.d.ts +35 -0
  31. package/dist/events/types.js +59 -0
  32. package/dist/formatter/index.d.ts +1 -0
  33. package/dist/formatter/index.js +26 -0
  34. package/dist/index.js +3 -2
  35. package/dist/ir/builder.d.ts +2 -1
  36. package/dist/ir/types.d.ts +11 -2
  37. package/dist/ir/types.js +1 -1
  38. package/dist/lexer/index.d.ts +1 -1
  39. package/dist/lexer/index.js +2 -0
  40. package/dist/lowering/index.d.ts +34 -1
  41. package/dist/lowering/index.js +622 -23
  42. package/dist/mc-test/runner.d.ts +2 -2
  43. package/dist/mc-test/runner.js +3 -3
  44. package/dist/mc-test/setup.js +2 -2
  45. package/dist/parser/index.d.ts +4 -0
  46. package/dist/parser/index.js +153 -16
  47. package/dist/typechecker/index.d.ts +17 -0
  48. package/dist/typechecker/index.js +343 -17
  49. package/docs/COMPILATION_STATS.md +24 -24
  50. package/docs/ENTITY_TYPE_SYSTEM.md +242 -0
  51. package/docs/IMPLEMENTATION_GUIDE.md +1 -1
  52. package/docs/STRUCTURE_TARGET.md +1 -1
  53. package/editors/vscode/.vscodeignore +1 -0
  54. package/editors/vscode/CHANGELOG.md +9 -0
  55. package/editors/vscode/icons/mcrs.svg +7 -0
  56. package/editors/vscode/icons/redscript-icons.json +10 -0
  57. package/editors/vscode/out/extension.js +1295 -80
  58. package/editors/vscode/package-lock.json +2 -2
  59. package/editors/vscode/package.json +10 -3
  60. package/editors/vscode/src/hover.ts +55 -2
  61. package/editors/vscode/src/symbols.ts +42 -0
  62. package/package.json +1 -1
  63. package/src/__tests__/cli.test.ts +176 -10
  64. package/src/__tests__/codegen.test.ts +28 -1
  65. package/src/__tests__/diagnostics.test.ts +5 -5
  66. package/src/__tests__/e2e.test.ts +335 -17
  67. package/src/__tests__/fixtures/event-test.mcrs +13 -0
  68. package/src/__tests__/fixtures/impl-test.mcrs +46 -0
  69. package/src/__tests__/fixtures/interval-test.mcrs +11 -0
  70. package/src/__tests__/fixtures/is-check-test.mcrs +20 -0
  71. package/src/__tests__/fixtures/timeout-test.mcrs +7 -0
  72. package/src/__tests__/lexer.test.ts +14 -2
  73. package/src/__tests__/lowering.test.ts +226 -12
  74. package/src/__tests__/mc-integration.test.ts +421 -31
  75. package/src/__tests__/mc-syntax.test.ts +3 -3
  76. package/src/__tests__/nbt.test.ts +2 -2
  77. package/src/__tests__/optimizer-advanced.test.ts +5 -5
  78. package/src/__tests__/parser.test.ts +91 -5
  79. package/src/__tests__/runtime.test.ts +9 -9
  80. package/src/__tests__/typechecker.test.ts +171 -0
  81. package/src/ast/types.ts +44 -3
  82. package/src/cli.ts +10 -10
  83. package/src/codegen/mcfunction/index.ts +40 -3
  84. package/src/codegen/structure/index.ts +35 -1
  85. package/src/compile.ts +54 -6
  86. package/src/events/types.ts +69 -0
  87. package/src/examples/capture_the_flag.mcrs +208 -0
  88. package/src/examples/{counter.rs → counter.mcrs} +1 -1
  89. package/src/examples/hunger_games.mcrs +301 -0
  90. package/src/examples/new_features_demo.mcrs +193 -0
  91. package/src/examples/parkour_race.mcrs +233 -0
  92. package/src/examples/rpg.mcrs +13 -0
  93. package/src/examples/{shop.rs → shop.mcrs} +1 -1
  94. package/src/examples/{showcase_game.rs → showcase_game.mcrs} +3 -3
  95. package/src/examples/{turret.rs → turret.mcrs} +1 -1
  96. package/src/examples/zombie_survival.mcrs +314 -0
  97. package/src/index.ts +4 -3
  98. package/src/ir/builder.ts +3 -1
  99. package/src/ir/types.ts +12 -2
  100. package/src/lexer/index.ts +3 -1
  101. package/src/lowering/index.ts +684 -24
  102. package/src/mc-test/runner.ts +3 -3
  103. package/src/mc-test/setup.ts +2 -2
  104. package/src/parser/index.ts +170 -19
  105. package/src/stdlib/README.md +178 -140
  106. package/src/stdlib/bossbar.mcrs +68 -0
  107. package/src/stdlib/{cooldown.rs → cooldown.mcrs} +1 -1
  108. package/src/stdlib/effects.mcrs +64 -0
  109. package/src/stdlib/interactions.mcrs +195 -0
  110. package/src/stdlib/inventory.mcrs +38 -0
  111. package/src/stdlib/mobs.mcrs +99 -0
  112. package/src/stdlib/particles.mcrs +52 -0
  113. package/src/stdlib/sets.mcrs +20 -0
  114. package/src/stdlib/spawn.mcrs +41 -0
  115. package/src/stdlib/tags.mcrs +951 -0
  116. package/src/stdlib/teams.mcrs +68 -0
  117. package/src/stdlib/timer.mcrs +72 -0
  118. package/src/stdlib/world.mcrs +92 -0
  119. package/src/typechecker/index.ts +404 -18
  120. package/src/examples/rpg.rs +0 -13
  121. package/src/stdlib/mobs.rs +0 -99
  122. package/src/stdlib/timer.rs +0 -51
  123. /package/src/examples/{arena.rs → arena.mcrs} +0 -0
  124. /package/src/examples/{pvp_arena.rs → pvp_arena.mcrs} +0 -0
  125. /package/src/examples/{quiz.rs → quiz.mcrs} +0 -0
  126. /package/src/examples/{stdlib_demo.rs → stdlib_demo.mcrs} +0 -0
  127. /package/src/examples/{world_manager.rs → world_manager.mcrs} +0 -0
  128. /package/src/stdlib/{combat.rs → combat.mcrs} +0 -0
  129. /package/src/stdlib/{math.rs → math.mcrs} +0 -0
  130. /package/src/stdlib/{player.rs → player.mcrs} +0 -0
  131. /package/src/stdlib/{strings.rs → strings.mcrs} +0 -0
  132. /package/src/templates/{combat.rs → combat.mcrs} +0 -0
  133. /package/src/templates/{economy.rs → economy.mcrs} +0 -0
  134. /package/src/templates/{mini-game-framework.rs → mini-game-framework.mcrs} +0 -0
  135. /package/src/templates/{quest.rs → quest.mcrs} +0 -0
  136. /package/src/test_programs/{zombie_game.rs → zombie_game.mcrs} +0 -0
@@ -5,10 +5,45 @@
5
5
  * Transforms AST into IR (Three-Address Code).
6
6
  * Handles control flow, function extraction for foreach, and builtin calls.
7
7
  */
8
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
9
+ if (k2 === undefined) k2 = k;
10
+ var desc = Object.getOwnPropertyDescriptor(m, k);
11
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
12
+ desc = { enumerable: true, get: function() { return m[k]; } };
13
+ }
14
+ Object.defineProperty(o, k2, desc);
15
+ }) : (function(o, m, k, k2) {
16
+ if (k2 === undefined) k2 = k;
17
+ o[k2] = m[k];
18
+ }));
19
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
20
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
21
+ }) : function(o, v) {
22
+ o["default"] = v;
23
+ });
24
+ var __importStar = (this && this.__importStar) || (function () {
25
+ var ownKeys = function(o) {
26
+ ownKeys = Object.getOwnPropertyNames || function (o) {
27
+ var ar = [];
28
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
29
+ return ar;
30
+ };
31
+ return ownKeys(o);
32
+ };
33
+ return function (mod) {
34
+ if (mod && mod.__esModule) return mod;
35
+ var result = {};
36
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
37
+ __setModuleDefault(result, mod);
38
+ return result;
39
+ };
40
+ })();
8
41
  Object.defineProperty(exports, "__esModule", { value: true });
9
42
  exports.Lowering = void 0;
10
43
  const builder_1 = require("../ir/builder");
11
44
  const diagnostics_1 = require("../diagnostics");
45
+ const path = __importStar(require("path"));
46
+ const types_1 = require("../events/types");
12
47
  // ---------------------------------------------------------------------------
13
48
  // Builtin Functions
14
49
  // ---------------------------------------------------------------------------
@@ -20,9 +55,10 @@ const BUILTINS = {
20
55
  subtitle: ([sel, msg]) => `title ${sel} subtitle {"text":"${msg}"}`,
21
56
  title_times: ([sel, fadeIn, stay, fadeOut]) => `title ${sel} times ${fadeIn} ${stay} ${fadeOut}`,
22
57
  announce: ([msg]) => `tellraw @a {"text":"${msg}"}`,
23
- give: ([sel, item, count]) => `give ${sel} ${item} ${count ?? '1'}`,
58
+ give: ([sel, item, count, nbt]) => nbt ? `give ${sel} ${item}${nbt} ${count ?? '1'}` : `give ${sel} ${item} ${count ?? '1'}`,
24
59
  kill: ([sel]) => `kill ${sel ?? '@s'}`,
25
60
  effect: ([sel, eff, dur, amp]) => `effect give ${sel} ${eff} ${dur ?? '30'} ${amp ?? '0'}`,
61
+ effect_clear: ([sel, eff]) => eff ? `effect clear ${sel} ${eff}` : `effect clear ${sel}`,
26
62
  summon: ([type, x, y, z, nbt]) => {
27
63
  const pos = [x ?? '~', y ?? '~', z ?? '~'].join(' ');
28
64
  return nbt ? `summon ${type} ${pos} ${nbt}` : `summon ${type} ${pos}`;
@@ -73,12 +109,37 @@ const BUILTINS = {
73
109
  team_leave: () => null, // Special handling
74
110
  team_option: () => null, // Special handling
75
111
  data_get: () => null, // Special handling (returns value from NBT)
112
+ data_merge: () => null, // Special handling (merge NBT)
113
+ set_new: () => null, // Special handling (returns set ID)
114
+ set_add: () => null, // Special handling
115
+ set_contains: () => null, // Special handling (returns 1/0)
116
+ set_remove: () => null, // Special handling
117
+ set_clear: () => null, // Special handling
118
+ setTimeout: () => null, // Special handling
119
+ setInterval: () => null, // Special handling
120
+ clearInterval: () => null, // Special handling
76
121
  };
77
122
  function getSpan(node) {
78
123
  return node?.span;
79
124
  }
80
125
  const NAMESPACED_ENTITY_TYPE_RE = /^[a-z0-9_.-]+:[a-z0-9_./-]+$/;
81
126
  const BARE_ENTITY_TYPE_RE = /^[a-z0-9_./-]+$/;
127
+ const ENTITY_TO_MC_TYPE = {
128
+ Player: 'player',
129
+ Zombie: 'zombie',
130
+ Skeleton: 'skeleton',
131
+ Creeper: 'creeper',
132
+ Spider: 'spider',
133
+ Enderman: 'enderman',
134
+ Pig: 'pig',
135
+ Cow: 'cow',
136
+ Sheep: 'sheep',
137
+ Chicken: 'chicken',
138
+ Villager: 'villager',
139
+ ArmorStand: 'armor_stand',
140
+ Item: 'item',
141
+ Arrow: 'arrow',
142
+ };
82
143
  function normalizeSelector(selector, warnings) {
83
144
  return selector.replace(/type=([^,\]]+)/g, (match, entityType) => {
84
145
  const trimmed = entityType.trim();
@@ -115,17 +176,23 @@ function emitBlockPos(pos) {
115
176
  // Lowering Class
116
177
  // ---------------------------------------------------------------------------
117
178
  class Lowering {
118
- constructor(namespace) {
179
+ constructor(namespace, sourceRanges = []) {
119
180
  this.functions = [];
120
181
  this.globals = [];
182
+ this.globalNames = new Map();
121
183
  this.fnDecls = new Map();
184
+ this.implMethods = new Map();
122
185
  this.specializedFunctions = new Map();
123
186
  this.currentFn = '';
124
187
  this.foreachCounter = 0;
125
188
  this.lambdaCounter = 0;
189
+ this.timeoutCounter = 0;
190
+ this.intervalCounter = 0;
126
191
  this.warnings = [];
127
192
  this.varMap = new Map();
128
193
  this.lambdaBindings = new Map();
194
+ this.intervalBindings = new Map();
195
+ this.intervalFunctions = new Map();
129
196
  this.currentCallbackBindings = new Map();
130
197
  this.currentContext = {};
131
198
  this.blockPosVars = new Map();
@@ -142,6 +209,8 @@ class Lowering {
142
209
  // World object counter for unique tags
143
210
  this.worldObjCounter = 0;
144
211
  this.namespace = namespace;
212
+ this.sourceRanges = sourceRanges;
213
+ LoweringBuilder.resetTempCounter();
145
214
  }
146
215
  lower(program) {
147
216
  this.namespace = program.namespace;
@@ -164,13 +233,38 @@ class Lowering {
164
233
  this.constValues.set(constDecl.name, constDecl.value);
165
234
  this.varTypes.set(constDecl.name, this.normalizeType(constDecl.type));
166
235
  }
236
+ // Process global variable declarations (top-level let)
237
+ for (const g of program.globals ?? []) {
238
+ this.globalNames.set(g.name, { mutable: g.mutable });
239
+ this.varTypes.set(g.name, this.normalizeType(g.type));
240
+ const initValue = g.init.kind === 'int_lit' ? g.init.value : 0;
241
+ this.globals.push({ name: `$${g.name}`, init: initValue });
242
+ }
167
243
  for (const fn of program.declarations) {
168
244
  this.fnDecls.set(fn.name, fn);
169
245
  this.functionDefaults.set(fn.name, fn.params.map(param => param.default));
170
246
  }
247
+ for (const implBlock of program.implBlocks ?? []) {
248
+ let methods = this.implMethods.get(implBlock.typeName);
249
+ if (!methods) {
250
+ methods = new Map();
251
+ this.implMethods.set(implBlock.typeName, methods);
252
+ }
253
+ for (const method of implBlock.methods) {
254
+ const loweredName = `${implBlock.typeName}_${method.name}`;
255
+ methods.set(method.name, { fn: method, loweredName });
256
+ this.fnDecls.set(loweredName, method);
257
+ this.functionDefaults.set(loweredName, method.params.map(param => param.default));
258
+ }
259
+ }
171
260
  for (const fn of program.declarations) {
172
261
  this.lowerFn(fn);
173
262
  }
263
+ for (const implBlock of program.implBlocks ?? []) {
264
+ for (const method of implBlock.methods) {
265
+ this.lowerFn(method, { name: `${implBlock.typeName}_${method.name}` });
266
+ }
267
+ }
174
268
  return (0, builder_1.buildModule)(this.namespace, this.functions, this.globals);
175
269
  }
176
270
  // -------------------------------------------------------------------------
@@ -179,21 +273,48 @@ class Lowering {
179
273
  lowerFn(fn, options = {}) {
180
274
  const loweredName = options.name ?? fn.name;
181
275
  const callbackBindings = options.callbackBindings ?? new Map();
182
- const runtimeParams = fn.params.filter(param => !callbackBindings.has(param.name));
276
+ const stdlibCallSite = options.stdlibCallSite;
277
+ const staticEventDec = fn.decorators.find(d => d.name === 'on');
278
+ const eventType = staticEventDec?.args?.eventType;
279
+ const eventParamSpecs = eventType && (0, types_1.isEventTypeName)(eventType) ? (0, types_1.getEventParamSpecs)(eventType) : [];
280
+ const runtimeParams = staticEventDec
281
+ ? []
282
+ : fn.params.filter(param => !callbackBindings.has(param.name));
183
283
  this.currentFn = loweredName;
284
+ this.currentStdlibCallSite = stdlibCallSite;
184
285
  this.foreachCounter = 0;
185
286
  this.varMap = new Map();
186
287
  this.lambdaBindings = new Map();
288
+ this.intervalBindings = new Map();
187
289
  this.currentCallbackBindings = new Map(callbackBindings);
188
290
  this.currentContext = {};
189
291
  this.blockPosVars = new Map();
190
292
  this.stringValues = new Map();
191
293
  this.builder = new LoweringBuilder();
192
294
  // Map parameters
193
- for (const param of runtimeParams) {
194
- const paramName = param.name;
195
- this.varMap.set(paramName, `$${paramName}`);
196
- this.varTypes.set(paramName, this.normalizeType(param.type));
295
+ if (staticEventDec) {
296
+ for (let i = 0; i < fn.params.length; i++) {
297
+ const param = fn.params[i];
298
+ const expected = eventParamSpecs[i];
299
+ const normalizedType = this.normalizeType(param.type);
300
+ this.varTypes.set(param.name, normalizedType);
301
+ if (expected?.type.kind === 'entity') {
302
+ this.varMap.set(param.name, '@s');
303
+ continue;
304
+ }
305
+ if (expected?.type.kind === 'named' && expected.type.name === 'string') {
306
+ this.stringValues.set(param.name, '');
307
+ continue;
308
+ }
309
+ this.varMap.set(param.name, `$${param.name}`);
310
+ }
311
+ }
312
+ else {
313
+ for (const param of runtimeParams) {
314
+ const paramName = param.name;
315
+ this.varMap.set(paramName, `$${paramName}`);
316
+ this.varTypes.set(paramName, this.normalizeType(param.type));
317
+ }
197
318
  }
198
319
  for (const param of fn.params) {
199
320
  if (callbackBindings.has(param.name)) {
@@ -208,6 +329,15 @@ class Lowering {
208
329
  const varName = `$${paramName}`;
209
330
  this.builder.emitAssign(varName, { kind: 'var', name: `$p${i}` });
210
331
  }
332
+ if (staticEventDec) {
333
+ for (let i = 0; i < fn.params.length; i++) {
334
+ const param = fn.params[i];
335
+ const expected = eventParamSpecs[i];
336
+ if (expected?.type.kind === 'named' && expected.type.name !== 'string') {
337
+ this.builder.emitAssign(`$${param.name}`, { kind: 'const', value: 0 });
338
+ }
339
+ }
340
+ }
211
341
  // Lower body
212
342
  this.lowerBlock(fn.body);
213
343
  // If no explicit return, add void return
@@ -251,6 +381,16 @@ class Lowering {
251
381
  break;
252
382
  }
253
383
  }
384
+ if (eventType && (0, types_1.isEventTypeName)(eventType)) {
385
+ irFn.eventHandler = {
386
+ eventType,
387
+ tag: types_1.EVENT_TYPES[eventType].tag,
388
+ };
389
+ }
390
+ // Check for @load decorator
391
+ if (fn.decorators.some(d => d.name === 'load')) {
392
+ irFn.isLoadInit = true;
393
+ }
254
394
  // Handle tick rate counter if needed
255
395
  if (tickRate && tickRate > 1) {
256
396
  this.wrapWithTickRate(irFn, tickRate);
@@ -264,7 +404,7 @@ class Lowering {
264
404
  wrapWithTickRate(fn, rate) {
265
405
  // Add tick counter logic to entry block
266
406
  const counterVar = `$__tick_${fn.name}`;
267
- this.globals.push(counterVar);
407
+ this.globals.push({ name: counterVar, init: 0 });
268
408
  // Prepend counter logic to entry block
269
409
  const entry = fn.blocks[0];
270
410
  const originalInstrs = [...entry.instrs];
@@ -357,6 +497,10 @@ class Lowering {
357
497
  }
358
498
  }
359
499
  lowerLetStmt(stmt) {
500
+ // Check for duplicate declaration of foreach binding
501
+ if (this.currentContext.binding === stmt.name) {
502
+ throw new diagnostics_1.DiagnosticError('LoweringError', `Cannot redeclare foreach binding '${stmt.name}'`, stmt.span ?? { line: 0, col: 0 });
503
+ }
360
504
  const varName = `$${stmt.name}`;
361
505
  this.varMap.set(stmt.name, varName);
362
506
  // Track variable type
@@ -373,6 +517,15 @@ class Lowering {
373
517
  this.lambdaBindings.set(stmt.name, lambdaName);
374
518
  return;
375
519
  }
520
+ if (stmt.init.kind === 'call' && stmt.init.fn === 'setInterval') {
521
+ const value = this.lowerExpr(stmt.init);
522
+ const intervalFn = this.intervalFunctions.get(value.kind === 'const' ? value.value : NaN);
523
+ if (intervalFn) {
524
+ this.intervalBindings.set(stmt.name, intervalFn);
525
+ }
526
+ this.builder.emitAssign(varName, value);
527
+ return;
528
+ }
376
529
  // Handle struct literal initialization
377
530
  if (stmt.init.kind === 'struct_lit' && stmt.type?.kind === 'struct') {
378
531
  const structName = stmt.type.name.toLowerCase();
@@ -406,6 +559,13 @@ class Lowering {
406
559
  }
407
560
  return;
408
561
  }
562
+ // Handle set_new returning a set ID string
563
+ if (stmt.init.kind === 'call' && stmt.init.fn === 'set_new') {
564
+ const setId = `__set_${this.foreachCounter++}`;
565
+ this.builder.emitRaw(`data modify storage rs:sets ${setId} set value []`);
566
+ this.stringValues.set(stmt.name, setId);
567
+ return;
568
+ }
409
569
  // Handle spawn_object returning entity handle
410
570
  if (stmt.init.kind === 'call' && stmt.init.fn === 'spawn_object') {
411
571
  const value = this.lowerExpr(stmt.init);
@@ -439,6 +599,10 @@ class Lowering {
439
599
  }
440
600
  }
441
601
  lowerIfStmt(stmt) {
602
+ if (stmt.cond.kind === 'is_check') {
603
+ this.lowerIsCheckIfStmt(stmt);
604
+ return;
605
+ }
442
606
  const condVar = this.lowerExpr(stmt.cond);
443
607
  const condName = this.operandToVar(condVar);
444
608
  const thenLabel = this.builder.freshLabel('then');
@@ -462,6 +626,40 @@ class Lowering {
462
626
  // Merge block
463
627
  this.builder.startBlock(mergeLabel);
464
628
  }
629
+ lowerIsCheckIfStmt(stmt) {
630
+ const cond = stmt.cond;
631
+ if (cond.kind !== 'is_check') {
632
+ throw new diagnostics_1.DiagnosticError('LoweringError', "Internal error: expected 'is' check condition", stmt.span ?? { line: 0, col: 0 });
633
+ }
634
+ if (stmt.else_) {
635
+ throw new diagnostics_1.DiagnosticError('LoweringError', "'is' checks with else branches are not yet supported", cond.span ?? stmt.span ?? { line: 0, col: 0 });
636
+ }
637
+ const selector = this.exprToEntitySelector(cond.expr);
638
+ if (!selector) {
639
+ throw new diagnostics_1.DiagnosticError('LoweringError', "'is' checks require an entity selector or entity binding", cond.span ?? stmt.span ?? { line: 0, col: 0 });
640
+ }
641
+ const mcType = ENTITY_TO_MC_TYPE[cond.entityType];
642
+ if (!mcType) {
643
+ throw new diagnostics_1.DiagnosticError('LoweringError', `Cannot lower entity type check for '${cond.entityType}'`, cond.span ?? stmt.span ?? { line: 0, col: 0 });
644
+ }
645
+ const thenFnName = `${this.currentFn}/then_${this.foreachCounter++}`;
646
+ this.builder.emitRaw(`execute if entity ${this.appendTypeFilter(selector, mcType)} run function ${this.namespace}:${thenFnName}`);
647
+ const savedBuilder = this.builder;
648
+ const savedVarMap = new Map(this.varMap);
649
+ const savedBlockPosVars = new Map(this.blockPosVars);
650
+ this.builder = new LoweringBuilder();
651
+ this.varMap = new Map(savedVarMap);
652
+ this.blockPosVars = new Map(savedBlockPosVars);
653
+ this.builder.startBlock('entry');
654
+ this.lowerBlock(stmt.then);
655
+ if (!this.builder.isBlockSealed()) {
656
+ this.builder.emitReturn();
657
+ }
658
+ this.functions.push(this.builder.build(thenFnName, [], false));
659
+ this.builder = savedBuilder;
660
+ this.varMap = savedVarMap;
661
+ this.blockPosVars = savedBlockPosVars;
662
+ }
465
663
  lowerWhileStmt(stmt) {
466
664
  const checkLabel = this.builder.freshLabel('loop_check');
467
665
  const bodyLabel = this.builder.freshLabel('loop_body');
@@ -760,10 +958,24 @@ class Lowering {
760
958
  parts.push(`at ${this.selectorToString(sub.selector)}`);
761
959
  break;
762
960
  case 'if_entity':
763
- parts.push(`if entity ${this.selectorToString(sub.selector)}`);
961
+ if (sub.selector) {
962
+ parts.push(`if entity ${this.selectorToString(sub.selector)}`);
963
+ }
964
+ else if (sub.varName) {
965
+ // Variable with filters - substitute with @s and apply filters
966
+ const sel = { kind: '@s', filters: sub.filters };
967
+ parts.push(`if entity ${this.selectorToString(sel)}`);
968
+ }
764
969
  break;
765
970
  case 'unless_entity':
766
- parts.push(`unless entity ${this.selectorToString(sub.selector)}`);
971
+ if (sub.selector) {
972
+ parts.push(`unless entity ${this.selectorToString(sub.selector)}`);
973
+ }
974
+ else if (sub.varName) {
975
+ // Variable with filters - substitute with @s and apply filters
976
+ const sel = { kind: '@s', filters: sub.filters };
977
+ parts.push(`unless entity ${this.selectorToString(sel)}`);
978
+ }
767
979
  break;
768
980
  case 'in':
769
981
  parts.push(`in ${sub.dimension}`);
@@ -854,12 +1066,16 @@ class Lowering {
854
1066
  return { kind: 'var', name: this.selectorToString(expr.sel) };
855
1067
  case 'binary':
856
1068
  return this.lowerBinaryExpr(expr);
1069
+ case 'is_check':
1070
+ throw new diagnostics_1.DiagnosticError('LoweringError', "'is' checks are only supported as if conditions", expr.span ?? { line: 0, col: 0 });
857
1071
  case 'unary':
858
1072
  return this.lowerUnaryExpr(expr);
859
1073
  case 'assign':
860
1074
  return this.lowerAssignExpr(expr);
861
1075
  case 'call':
862
1076
  return this.lowerCallExpr(expr);
1077
+ case 'static_call':
1078
+ return this.lowerStaticCallExpr(expr);
863
1079
  case 'invoke':
864
1080
  return this.lowerInvokeExpr(expr);
865
1081
  case 'member_assign':
@@ -1051,6 +1267,14 @@ class Lowering {
1051
1267
  return { kind: 'var', name: dst };
1052
1268
  }
1053
1269
  lowerAssignExpr(expr) {
1270
+ // Check for const reassignment (both compile-time consts and immutable globals)
1271
+ if (this.constValues.has(expr.target)) {
1272
+ throw new diagnostics_1.DiagnosticError('LoweringError', `Cannot assign to constant '${expr.target}'`, getSpan(expr) ?? { line: 1, col: 1 });
1273
+ }
1274
+ const globalInfo = this.globalNames.get(expr.target);
1275
+ if (globalInfo && !globalInfo.mutable) {
1276
+ throw new diagnostics_1.DiagnosticError('LoweringError', `Cannot assign to constant '${expr.target}'`, getSpan(expr) ?? { line: 1, col: 1 });
1277
+ }
1054
1278
  const blockPosValue = this.resolveBlockPosExpr(expr.value);
1055
1279
  if (blockPosValue) {
1056
1280
  this.blockPosVars.set(expr.target, blockPosValue);
@@ -1169,6 +1393,10 @@ class Lowering {
1169
1393
  if (callbackTarget) {
1170
1394
  return this.emitDirectFunctionCall(callbackTarget, expr.args);
1171
1395
  }
1396
+ const implMethod = this.resolveInstanceMethod(expr);
1397
+ if (implMethod) {
1398
+ return this.emitMethodCall(implMethod.loweredName, implMethod.fn, expr.args);
1399
+ }
1172
1400
  // Regular function call
1173
1401
  const fnDecl = this.fnDecls.get(expr.fn);
1174
1402
  const defaultArgs = this.functionDefaults.get(expr.fn) ?? [];
@@ -1195,13 +1423,19 @@ class Lowering {
1195
1423
  }
1196
1424
  runtimeArgs.push(fullArgs[i]);
1197
1425
  }
1198
- const targetFn = callbackBindings.size > 0
1199
- ? this.ensureSpecializedFunction(fnDecl, callbackBindings)
1426
+ const stdlibCallSite = this.getStdlibCallSiteContext(fnDecl, getSpan(expr));
1427
+ const targetFn = callbackBindings.size > 0 || stdlibCallSite
1428
+ ? this.ensureSpecializedFunctionWithContext(fnDecl, callbackBindings, stdlibCallSite)
1200
1429
  : expr.fn;
1201
1430
  return this.emitDirectFunctionCall(targetFn, runtimeArgs);
1202
1431
  }
1203
1432
  return this.emitDirectFunctionCall(expr.fn, fullArgs);
1204
1433
  }
1434
+ lowerStaticCallExpr(expr) {
1435
+ const method = this.implMethods.get(expr.type)?.get(expr.method);
1436
+ const targetFn = method?.loweredName ?? `${expr.type}_${expr.method}`;
1437
+ return this.emitMethodCall(targetFn, method?.fn, expr.args);
1438
+ }
1205
1439
  lowerInvokeExpr(expr) {
1206
1440
  if (expr.callee.kind === 'lambda') {
1207
1441
  if (!Array.isArray(expr.callee.body)) {
@@ -1246,6 +1480,18 @@ class Lowering {
1246
1480
  this.builder.emitCall(fn, loweredArgs, dst);
1247
1481
  return { kind: 'var', name: dst };
1248
1482
  }
1483
+ emitMethodCall(fn, fnDecl, args) {
1484
+ const defaultArgs = this.functionDefaults.get(fn) ?? fnDecl?.params.map(param => param.default) ?? [];
1485
+ const fullArgs = [...args];
1486
+ for (let i = fullArgs.length; i < defaultArgs.length; i++) {
1487
+ const defaultExpr = defaultArgs[i];
1488
+ if (!defaultExpr) {
1489
+ break;
1490
+ }
1491
+ fullArgs.push(defaultExpr);
1492
+ }
1493
+ return this.emitDirectFunctionCall(fn, fullArgs);
1494
+ }
1249
1495
  resolveFunctionRefExpr(expr) {
1250
1496
  if (expr.kind === 'lambda') {
1251
1497
  return this.lowerLambdaExpr(expr);
@@ -1259,9 +1505,16 @@ class Lowering {
1259
1505
  return this.lambdaBindings.get(name) ?? this.currentCallbackBindings.get(name) ?? null;
1260
1506
  }
1261
1507
  ensureSpecializedFunction(fn, callbackBindings) {
1508
+ return this.ensureSpecializedFunctionWithContext(fn, callbackBindings);
1509
+ }
1510
+ ensureSpecializedFunctionWithContext(fn, callbackBindings, stdlibCallSite) {
1262
1511
  const parts = [...callbackBindings.entries()]
1263
1512
  .sort(([left], [right]) => left.localeCompare(right))
1264
1513
  .map(([param, target]) => `${param}_${target.replace(/[^a-zA-Z0-9_]/g, '_')}`);
1514
+ const callSiteHash = stdlibCallSite ? this.shortHash(this.serializeCallSite(stdlibCallSite)) : null;
1515
+ if (callSiteHash) {
1516
+ parts.push(`callsite_${callSiteHash}`);
1517
+ }
1265
1518
  const key = `${fn.name}::${parts.join('::')}`;
1266
1519
  const cached = this.specializedFunctions.get(key);
1267
1520
  if (cached) {
@@ -1270,7 +1523,7 @@ class Lowering {
1270
1523
  const specializedName = `${fn.name}__${parts.join('__')}`;
1271
1524
  this.specializedFunctions.set(key, specializedName);
1272
1525
  this.withSavedFunctionState(() => {
1273
- this.lowerFn(fn, { name: specializedName, callbackBindings });
1526
+ this.lowerFn(fn, { name: specializedName, callbackBindings, stdlibCallSite });
1274
1527
  });
1275
1528
  return specializedName;
1276
1529
  }
@@ -1293,10 +1546,12 @@ class Lowering {
1293
1546
  }
1294
1547
  withSavedFunctionState(callback) {
1295
1548
  const savedCurrentFn = this.currentFn;
1549
+ const savedStdlibCallSite = this.currentStdlibCallSite;
1296
1550
  const savedForeachCounter = this.foreachCounter;
1297
1551
  const savedBuilder = this.builder;
1298
1552
  const savedVarMap = new Map(this.varMap);
1299
1553
  const savedLambdaBindings = new Map(this.lambdaBindings);
1554
+ const savedIntervalBindings = new Map(this.intervalBindings);
1300
1555
  const savedCallbackBindings = new Map(this.currentCallbackBindings);
1301
1556
  const savedContext = this.currentContext;
1302
1557
  const savedBlockPosVars = new Map(this.blockPosVars);
@@ -1307,10 +1562,12 @@ class Lowering {
1307
1562
  }
1308
1563
  finally {
1309
1564
  this.currentFn = savedCurrentFn;
1565
+ this.currentStdlibCallSite = savedStdlibCallSite;
1310
1566
  this.foreachCounter = savedForeachCounter;
1311
1567
  this.builder = savedBuilder;
1312
1568
  this.varMap = savedVarMap;
1313
1569
  this.lambdaBindings = savedLambdaBindings;
1570
+ this.intervalBindings = savedIntervalBindings;
1314
1571
  this.currentCallbackBindings = savedCallbackBindings;
1315
1572
  this.currentContext = savedContext;
1316
1573
  this.blockPosVars = savedBlockPosVars;
@@ -1324,6 +1581,15 @@ class Lowering {
1324
1581
  this.builder.emitRaw(richTextCommand);
1325
1582
  return { kind: 'const', value: 0 };
1326
1583
  }
1584
+ if (name === 'setTimeout') {
1585
+ return this.lowerSetTimeout(args);
1586
+ }
1587
+ if (name === 'setInterval') {
1588
+ return this.lowerSetInterval(args);
1589
+ }
1590
+ if (name === 'clearInterval') {
1591
+ return this.lowerClearInterval(args, callSpan);
1592
+ }
1327
1593
  // Special case: random - legacy scoreboard RNG for pre-1.20.3 compatibility
1328
1594
  if (name === 'random') {
1329
1595
  const dst = this.builder.freshTemp();
@@ -1351,14 +1617,14 @@ class Lowering {
1351
1617
  if (name === 'scoreboard_get' || name === 'score') {
1352
1618
  const dst = this.builder.freshTemp();
1353
1619
  const player = this.exprToTargetString(args[0]);
1354
- const objective = this.exprToString(args[1]);
1620
+ const objective = this.resolveScoreboardObjective(args[0], args[1], callSpan);
1355
1621
  this.builder.emitRaw(`execute store result score ${dst} rs run scoreboard players get ${player} ${objective}`);
1356
1622
  return { kind: 'var', name: dst };
1357
1623
  }
1358
1624
  // Special case: scoreboard_set — write to vanilla MC scoreboard
1359
1625
  if (name === 'scoreboard_set') {
1360
1626
  const player = this.exprToTargetString(args[0]);
1361
- const objective = this.exprToString(args[1]);
1627
+ const objective = this.resolveScoreboardObjective(args[0], args[1], callSpan);
1362
1628
  const value = this.lowerExpr(args[2]);
1363
1629
  if (value.kind === 'const') {
1364
1630
  this.builder.emitRaw(`scoreboard players set ${player} ${objective} ${value.value}`);
@@ -1372,7 +1638,7 @@ class Lowering {
1372
1638
  }
1373
1639
  if (name === 'scoreboard_display') {
1374
1640
  const slot = this.exprToString(args[0]);
1375
- const objective = this.exprToString(args[1]);
1641
+ const objective = this.resolveScoreboardObjective(undefined, args[1], callSpan);
1376
1642
  this.builder.emitRaw(`scoreboard objectives setdisplay ${slot} ${objective}`);
1377
1643
  return { kind: 'const', value: 0 };
1378
1644
  }
@@ -1382,14 +1648,14 @@ class Lowering {
1382
1648
  return { kind: 'const', value: 0 };
1383
1649
  }
1384
1650
  if (name === 'scoreboard_add_objective') {
1385
- const objective = this.exprToString(args[0]);
1651
+ const objective = this.resolveScoreboardObjective(undefined, args[0], callSpan);
1386
1652
  const criteria = this.exprToString(args[1]);
1387
1653
  const displayName = args[2] ? ` ${this.exprToQuotedString(args[2])}` : '';
1388
1654
  this.builder.emitRaw(`scoreboard objectives add ${objective} ${criteria}${displayName}`);
1389
1655
  return { kind: 'const', value: 0 };
1390
1656
  }
1391
1657
  if (name === 'scoreboard_remove_objective') {
1392
- const objective = this.exprToString(args[0]);
1658
+ const objective = this.resolveScoreboardObjective(undefined, args[0], callSpan);
1393
1659
  this.builder.emitRaw(`scoreboard objectives remove ${objective}`);
1394
1660
  return { kind: 'const', value: 0 };
1395
1661
  }
@@ -1473,6 +1739,62 @@ class Lowering {
1473
1739
  this.builder.emitRaw(`execute store result score ${dst} rs run data get ${targetType} ${target} ${path} ${scale}`);
1474
1740
  return { kind: 'var', name: dst };
1475
1741
  }
1742
+ // data_merge(target, nbt) — merge NBT into entity/block/storage
1743
+ // data_merge(@s, { Invisible: 1b, Silent: 1b })
1744
+ if (name === 'data_merge') {
1745
+ const target = args[0];
1746
+ const nbt = args[1];
1747
+ const nbtStr = this.exprToSnbt ? this.exprToSnbt(nbt) : this.exprToString(nbt);
1748
+ // Check if target is a selector (entity) or string (block/storage)
1749
+ if (target.kind === 'selector') {
1750
+ const sel = this.exprToTargetString(target);
1751
+ this.builder.emitRaw(`data merge entity ${sel} ${nbtStr}`);
1752
+ }
1753
+ else {
1754
+ // Assume block position or storage
1755
+ const targetStr = this.exprToString(target);
1756
+ // If it looks like coordinates, use block; otherwise storage
1757
+ if (targetStr.match(/^~|^\d|^\^/)) {
1758
+ this.builder.emitRaw(`data merge block ${targetStr} ${nbtStr}`);
1759
+ }
1760
+ else {
1761
+ this.builder.emitRaw(`data merge storage ${targetStr} ${nbtStr}`);
1762
+ }
1763
+ }
1764
+ return { kind: 'const', value: 0 };
1765
+ }
1766
+ // Set data structure operations — unique collections via NBT storage
1767
+ // set_new is primarily handled in lowerLetStmt for proper string tracking.
1768
+ // This fallback handles standalone set_new() calls without assignment.
1769
+ if (name === 'set_new') {
1770
+ const setId = `__set_${this.foreachCounter++}`;
1771
+ this.builder.emitRaw(`data modify storage rs:sets ${setId} set value []`);
1772
+ return { kind: 'const', value: 0 };
1773
+ }
1774
+ if (name === 'set_add') {
1775
+ const setId = this.exprToString(args[0]);
1776
+ const value = this.exprToString(args[1]);
1777
+ this.builder.emitRaw(`execute unless data storage rs:sets ${setId}[{value:${value}}] run data modify storage rs:sets ${setId} append value {value:${value}}`);
1778
+ return { kind: 'const', value: 0 };
1779
+ }
1780
+ if (name === 'set_contains') {
1781
+ const dst = this.builder.freshTemp();
1782
+ const setId = this.exprToString(args[0]);
1783
+ const value = this.exprToString(args[1]);
1784
+ this.builder.emitRaw(`execute store result score ${dst} rs if data storage rs:sets ${setId}[{value:${value}}]`);
1785
+ return { kind: 'var', name: dst };
1786
+ }
1787
+ if (name === 'set_remove') {
1788
+ const setId = this.exprToString(args[0]);
1789
+ const value = this.exprToString(args[1]);
1790
+ this.builder.emitRaw(`data remove storage rs:sets ${setId}[{value:${value}}]`);
1791
+ return { kind: 'const', value: 0 };
1792
+ }
1793
+ if (name === 'set_clear') {
1794
+ const setId = this.exprToString(args[0]);
1795
+ this.builder.emitRaw(`data modify storage rs:sets ${setId} set value []`);
1796
+ return { kind: 'const', value: 0 };
1797
+ }
1476
1798
  const coordCommand = this.lowerCoordinateBuiltin(name, args);
1477
1799
  if (coordCommand) {
1478
1800
  this.builder.emitRaw(coordCommand);
@@ -1497,14 +1819,100 @@ class Lowering {
1497
1819
  }
1498
1820
  return { kind: 'const', value: 0 };
1499
1821
  }
1500
- // Convert args to strings for builtin
1501
- const strArgs = args.map(arg => this.exprToString(arg));
1822
+ // Convert args to strings for builtin (use SNBT for struct/array literals)
1823
+ const strArgs = args.map(arg => arg.kind === 'struct_lit' || arg.kind === 'array_lit'
1824
+ ? this.exprToSnbt(arg)
1825
+ : this.exprToString(arg));
1502
1826
  const cmd = BUILTINS[name](strArgs);
1503
1827
  if (cmd) {
1504
1828
  this.builder.emitRaw(cmd);
1505
1829
  }
1506
1830
  return { kind: 'const', value: 0 };
1507
1831
  }
1832
+ lowerSetTimeout(args) {
1833
+ const delay = this.exprToLiteral(args[0]);
1834
+ const callback = args[1];
1835
+ if (!callback || callback.kind !== 'lambda') {
1836
+ throw new diagnostics_1.DiagnosticError('LoweringError', 'setTimeout requires a lambda callback', getSpan(callback) ?? { line: 1, col: 1 });
1837
+ }
1838
+ const fnName = `__timeout_${this.timeoutCounter++}`;
1839
+ this.lowerNamedLambdaFunction(fnName, callback);
1840
+ this.builder.emitRaw(`schedule function ${this.namespace}:${fnName} ${delay}t`);
1841
+ return { kind: 'const', value: 0 };
1842
+ }
1843
+ lowerSetInterval(args) {
1844
+ const delay = this.exprToLiteral(args[0]);
1845
+ const callback = args[1];
1846
+ if (!callback || callback.kind !== 'lambda') {
1847
+ throw new diagnostics_1.DiagnosticError('LoweringError', 'setInterval requires a lambda callback', getSpan(callback) ?? { line: 1, col: 1 });
1848
+ }
1849
+ const id = this.intervalCounter++;
1850
+ const bodyName = `__interval_body_${id}`;
1851
+ const fnName = `__interval_${id}`;
1852
+ this.lowerNamedLambdaFunction(bodyName, callback);
1853
+ this.lowerIntervalWrapperFunction(fnName, bodyName, delay);
1854
+ this.intervalFunctions.set(id, fnName);
1855
+ this.builder.emitRaw(`schedule function ${this.namespace}:${fnName} ${delay}t`);
1856
+ return { kind: 'const', value: id };
1857
+ }
1858
+ lowerClearInterval(args, callSpan) {
1859
+ const fnName = this.resolveIntervalFunctionName(args[0]);
1860
+ if (!fnName) {
1861
+ throw new diagnostics_1.DiagnosticError('LoweringError', 'clearInterval requires an interval ID returned from setInterval', callSpan ?? getSpan(args[0]) ?? { line: 1, col: 1 });
1862
+ }
1863
+ this.builder.emitRaw(`schedule clear ${this.namespace}:${fnName}`);
1864
+ return { kind: 'const', value: 0 };
1865
+ }
1866
+ lowerNamedLambdaFunction(name, expr) {
1867
+ const lambdaFn = {
1868
+ name,
1869
+ params: expr.params.map(param => ({
1870
+ name: param.name,
1871
+ type: param.type ?? { kind: 'named', name: 'int' },
1872
+ })),
1873
+ returnType: expr.returnType ?? this.inferLambdaReturnType(expr),
1874
+ decorators: [],
1875
+ body: Array.isArray(expr.body) ? expr.body : [{ kind: 'return', value: expr.body }],
1876
+ };
1877
+ this.withSavedFunctionState(() => {
1878
+ this.lowerFn(lambdaFn);
1879
+ });
1880
+ }
1881
+ lowerIntervalWrapperFunction(name, bodyName, delay) {
1882
+ const intervalFn = {
1883
+ name,
1884
+ params: [],
1885
+ returnType: { kind: 'named', name: 'void' },
1886
+ decorators: [],
1887
+ body: [
1888
+ { kind: 'raw', cmd: `function ${this.namespace}:${bodyName}` },
1889
+ { kind: 'raw', cmd: `schedule function ${this.namespace}:${name} ${delay}t` },
1890
+ ],
1891
+ };
1892
+ this.withSavedFunctionState(() => {
1893
+ this.lowerFn(intervalFn);
1894
+ });
1895
+ }
1896
+ resolveIntervalFunctionName(expr) {
1897
+ if (!expr) {
1898
+ return null;
1899
+ }
1900
+ if (expr.kind === 'ident') {
1901
+ const boundInterval = this.intervalBindings.get(expr.name);
1902
+ if (boundInterval) {
1903
+ return boundInterval;
1904
+ }
1905
+ const constValue = this.constValues.get(expr.name);
1906
+ if (constValue?.kind === 'int_lit') {
1907
+ return this.intervalFunctions.get(constValue.value) ?? null;
1908
+ }
1909
+ return null;
1910
+ }
1911
+ if (expr.kind === 'int_lit') {
1912
+ return this.intervalFunctions.get(expr.value) ?? null;
1913
+ }
1914
+ return null;
1915
+ }
1508
1916
  lowerRichTextBuiltin(name, args) {
1509
1917
  const messageArgIndex = this.getRichTextArgIndex(name);
1510
1918
  if (messageArgIndex === null) {
@@ -1647,12 +2055,75 @@ class Lowering {
1647
2055
  }
1648
2056
  case 'selector':
1649
2057
  return this.selectorToString(expr.sel);
2058
+ case 'unary':
2059
+ // Handle unary minus on literals directly
2060
+ if (expr.op === '-' && expr.operand.kind === 'int_lit') {
2061
+ return (-expr.operand.value).toString();
2062
+ }
2063
+ if (expr.op === '-' && expr.operand.kind === 'float_lit') {
2064
+ return Math.trunc(-expr.operand.value).toString();
2065
+ }
2066
+ // Fall through to default for complex cases
2067
+ const unaryOp = this.lowerExpr(expr);
2068
+ return this.operandToVar(unaryOp);
1650
2069
  default:
1651
2070
  // Complex expression - lower and return var name
1652
2071
  const op = this.lowerExpr(expr);
1653
2072
  return this.operandToVar(op);
1654
2073
  }
1655
2074
  }
2075
+ exprToEntitySelector(expr) {
2076
+ if (expr.kind === 'selector') {
2077
+ return this.selectorToString(expr.sel);
2078
+ }
2079
+ if (expr.kind === 'ident') {
2080
+ const constValue = this.constValues.get(expr.name);
2081
+ if (constValue) {
2082
+ return this.exprToEntitySelector(constValue);
2083
+ }
2084
+ const mapped = this.varMap.get(expr.name);
2085
+ if (mapped?.startsWith('@')) {
2086
+ return mapped;
2087
+ }
2088
+ }
2089
+ return null;
2090
+ }
2091
+ appendTypeFilter(selector, mcType) {
2092
+ if (selector.endsWith(']')) {
2093
+ return `${selector.slice(0, -1)},type=${mcType}]`;
2094
+ }
2095
+ return `${selector}[type=${mcType}]`;
2096
+ }
2097
+ exprToSnbt(expr) {
2098
+ switch (expr.kind) {
2099
+ case 'struct_lit': {
2100
+ const entries = expr.fields.map(f => `${f.name}:${this.exprToSnbt(f.value)}`);
2101
+ return `{${entries.join(',')}}`;
2102
+ }
2103
+ case 'array_lit': {
2104
+ const items = expr.elements.map(e => this.exprToSnbt(e));
2105
+ return `[${items.join(',')}]`;
2106
+ }
2107
+ case 'str_lit':
2108
+ return `"${expr.value}"`;
2109
+ case 'int_lit':
2110
+ return String(expr.value);
2111
+ case 'float_lit':
2112
+ return String(expr.value);
2113
+ case 'byte_lit':
2114
+ return `${expr.value}b`;
2115
+ case 'short_lit':
2116
+ return `${expr.value}s`;
2117
+ case 'long_lit':
2118
+ return `${expr.value}L`;
2119
+ case 'double_lit':
2120
+ return `${expr.value}d`;
2121
+ case 'bool_lit':
2122
+ return expr.value ? '1b' : '0b';
2123
+ default:
2124
+ return this.exprToString(expr);
2125
+ }
2126
+ }
1656
2127
  exprToTargetString(expr) {
1657
2128
  if (expr.kind === 'selector') {
1658
2129
  return this.selectorToString(expr.sel);
@@ -1690,6 +2161,92 @@ class Lowering {
1690
2161
  isTeamTextOption(option) {
1691
2162
  return option === 'displayName' || option === 'prefix' || option === 'suffix';
1692
2163
  }
2164
+ exprToScoreboardObjective(expr, span) {
2165
+ if (expr.kind === 'mc_name') {
2166
+ return expr.value;
2167
+ }
2168
+ const objective = this.exprToString(expr);
2169
+ if (objective.startsWith('#') || objective.includes('.')) {
2170
+ return objective.startsWith('#') ? objective.slice(1) : objective;
2171
+ }
2172
+ return `${this.getObjectiveNamespace(span)}.${objective}`;
2173
+ }
2174
+ resolveScoreboardObjective(playerExpr, objectiveExpr, span) {
2175
+ const stdlibInternalObjective = this.tryGetStdlibInternalObjective(playerExpr, objectiveExpr, span);
2176
+ if (stdlibInternalObjective) {
2177
+ return stdlibInternalObjective;
2178
+ }
2179
+ return this.exprToScoreboardObjective(objectiveExpr, span);
2180
+ }
2181
+ getObjectiveNamespace(span) {
2182
+ const filePath = this.filePathForSpan(span);
2183
+ if (!filePath) {
2184
+ return this.namespace;
2185
+ }
2186
+ return this.isStdlibFile(filePath) ? 'rs' : this.namespace;
2187
+ }
2188
+ tryGetStdlibInternalObjective(playerExpr, objectiveExpr, span) {
2189
+ if (!span || !this.currentStdlibCallSite || objectiveExpr.kind !== 'mc_name' || objectiveExpr.value !== 'rs') {
2190
+ return null;
2191
+ }
2192
+ const filePath = this.filePathForSpan(span);
2193
+ if (!filePath || !this.isStdlibFile(filePath)) {
2194
+ return null;
2195
+ }
2196
+ const resourceBase = this.getStdlibInternalResourceBase(playerExpr);
2197
+ if (!resourceBase) {
2198
+ return null;
2199
+ }
2200
+ const hash = this.shortHash(this.serializeCallSite(this.currentStdlibCallSite));
2201
+ return `rs._${resourceBase}_${hash}`;
2202
+ }
2203
+ getStdlibInternalResourceBase(playerExpr) {
2204
+ if (!playerExpr || playerExpr.kind !== 'str_lit') {
2205
+ return null;
2206
+ }
2207
+ const match = playerExpr.value.match(/^([a-z0-9]+)_/);
2208
+ return match?.[1] ?? null;
2209
+ }
2210
+ getStdlibCallSiteContext(fn, exprSpan) {
2211
+ const fnFilePath = this.filePathForSpan(getSpan(fn));
2212
+ if (!fnFilePath || !this.isStdlibFile(fnFilePath)) {
2213
+ return undefined;
2214
+ }
2215
+ if (this.currentStdlibCallSite) {
2216
+ return this.currentStdlibCallSite;
2217
+ }
2218
+ if (!exprSpan) {
2219
+ return undefined;
2220
+ }
2221
+ return {
2222
+ filePath: this.filePathForSpan(exprSpan),
2223
+ line: exprSpan.line,
2224
+ col: exprSpan.col,
2225
+ };
2226
+ }
2227
+ serializeCallSite(callSite) {
2228
+ return `${callSite.filePath ?? '<memory>'}:${callSite.line}:${callSite.col}`;
2229
+ }
2230
+ shortHash(input) {
2231
+ let hash = 2166136261;
2232
+ for (let i = 0; i < input.length; i++) {
2233
+ hash ^= input.charCodeAt(i);
2234
+ hash = Math.imul(hash, 16777619);
2235
+ }
2236
+ return (hash >>> 0).toString(16).padStart(8, '0').slice(0, 4);
2237
+ }
2238
+ isStdlibFile(filePath) {
2239
+ const normalized = path.normalize(filePath);
2240
+ const stdlibSegment = `${path.sep}src${path.sep}stdlib${path.sep}`;
2241
+ return normalized.includes(stdlibSegment);
2242
+ }
2243
+ filePathForSpan(span) {
2244
+ if (!span) {
2245
+ return undefined;
2246
+ }
2247
+ const line = span.line;
2248
+ return this.sourceRanges.find(range => line >= range.startLine && line <= range.endLine)?.filePath;
2249
+ }
1693
2250
  lowerCoordinateBuiltin(name, args) {
1694
2251
  const pos0 = args[0] ? this.resolveBlockPosExpr(args[0]) : null;
1695
2252
  const pos1 = args[1] ? this.resolveBlockPosExpr(args[1]) : null;
@@ -1712,6 +2269,13 @@ class Lowering {
1712
2269
  }
1713
2270
  return null;
1714
2271
  }
2272
+ if (name === 'summon') {
2273
+ if (args.length >= 2 && pos1) {
2274
+ const nbt = args[2] ? ` ${this.exprToString(args[2])}` : '';
2275
+ return `summon ${this.exprToString(args[0])} ${emitBlockPos(pos1)}${nbt}`;
2276
+ }
2277
+ return null;
2278
+ }
1715
2279
  return null;
1716
2280
  }
1717
2281
  lowerTpCommand(args) {
@@ -1790,7 +2354,11 @@ class Lowering {
1790
2354
  };
1791
2355
  }
1792
2356
  if (expr.kind === 'call') {
1793
- return this.fnDecls.get(this.resolveFunctionRefByName(expr.fn) ?? expr.fn)?.returnType;
2357
+ const resolved = this.resolveFunctionRefByName(expr.fn) ?? this.resolveInstanceMethod(expr)?.loweredName ?? expr.fn;
2358
+ return this.fnDecls.get(resolved)?.returnType;
2359
+ }
2360
+ if (expr.kind === 'static_call') {
2361
+ return this.implMethods.get(expr.type)?.get(expr.method)?.fn.returnType;
1794
2362
  }
1795
2363
  if (expr.kind === 'invoke') {
1796
2364
  const calleeType = this.inferExprType(expr.callee);
@@ -1818,6 +2386,21 @@ class Lowering {
1818
2386
  }
1819
2387
  return undefined;
1820
2388
  }
2389
+ resolveInstanceMethod(expr) {
2390
+ const receiver = expr.args[0];
2391
+ if (!receiver) {
2392
+ return null;
2393
+ }
2394
+ const receiverType = this.inferExprType(receiver);
2395
+ if (receiverType?.kind !== 'struct') {
2396
+ return null;
2397
+ }
2398
+ const method = this.implMethods.get(receiverType.name)?.get(expr.fn);
2399
+ if (!method || method.fn.params[0]?.name !== 'self') {
2400
+ return null;
2401
+ }
2402
+ return method;
2403
+ }
1821
2404
  normalizeType(type) {
1822
2405
  if (type.kind === 'array') {
1823
2406
  return { kind: 'array', elem: this.normalizeType(type.elem) };
@@ -1941,6 +2524,18 @@ class Lowering {
1941
2524
  parts.push(`nbt=${filters.nbt}`);
1942
2525
  if (filters.gamemode)
1943
2526
  parts.push(`gamemode=${filters.gamemode}`);
2527
+ // Position filters
2528
+ if (filters.x)
2529
+ parts.push(`x=${this.rangeToString(filters.x)}`);
2530
+ if (filters.y)
2531
+ parts.push(`y=${this.rangeToString(filters.y)}`);
2532
+ if (filters.z)
2533
+ parts.push(`z=${this.rangeToString(filters.z)}`);
2534
+ // Rotation filters
2535
+ if (filters.x_rotation)
2536
+ parts.push(`x_rotation=${this.rangeToString(filters.x_rotation)}`);
2537
+ if (filters.y_rotation)
2538
+ parts.push(`y_rotation=${this.rangeToString(filters.y_rotation)}`);
1944
2539
  return this.finalizeSelector(parts.length ? `${kind}[${parts.join(',')}]` : kind);
1945
2540
  }
1946
2541
  finalizeSelector(selector) {
@@ -1965,14 +2560,17 @@ exports.Lowering = Lowering;
1965
2560
  // ---------------------------------------------------------------------------
1966
2561
  class LoweringBuilder {
1967
2562
  constructor() {
1968
- this.tempCount = 0;
1969
2563
  this.labelCount = 0;
1970
2564
  this.blocks = [];
1971
2565
  this.currentBlock = null;
1972
2566
  this.locals = new Set();
1973
2567
  }
2568
+ /** Reset the global temp counter (call between compilations). */
2569
+ static resetTempCounter() {
2570
+ LoweringBuilder.globalTempId = 0;
2571
+ }
1974
2572
  freshTemp() {
1975
- const name = `$t${this.tempCount++}`;
2573
+ const name = `$_${LoweringBuilder.globalTempId++}`;
1976
2574
  this.locals.add(name);
1977
2575
  return name;
1978
2576
  }
@@ -2038,4 +2636,5 @@ class LoweringBuilder {
2038
2636
  };
2039
2637
  }
2040
2638
  }
2639
+ LoweringBuilder.globalTempId = 0;
2041
2640
  //# sourceMappingURL=index.js.map