sandstone 1.0.0-beta.2 → 1.0.0-beta.3
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/CLAUDE.md +315 -16
- package/bun.lock +18 -22
- package/dist/arguments/index.js +1093 -367
- package/dist/arguments/selector.d.ts +6 -6
- package/dist/commands/implementations/block/setblock.d.ts +6 -9
- package/dist/commands/implementations/entity/execute.d.ts +36 -21
- package/dist/commands/implementations/server/return.d.ts +14 -7
- package/dist/commands/implementations/world/data.d.ts +29 -32
- package/dist/commands/index.js +1102 -364
- package/dist/core/index.js +2207 -1481
- package/dist/core/resources/datapack/mcfunction.d.ts +1 -1
- package/dist/core/resources/datapack/variant.d.ts +1 -1
- package/dist/core/resources/resource.d.ts +4 -4
- package/dist/flow/Flow.d.ts +21 -22
- package/dist/flow/if_else.d.ts +8 -3
- package/dist/flow/index.d.ts +1 -0
- package/dist/flow/index.js +2196 -1451
- package/dist/flow/loop.d.ts +5 -4
- package/dist/flow/loops/for_i.d.ts +1 -1
- package/dist/flow/loops/for_of.d.ts +4 -3
- package/dist/flow/loops/staticAnalysis.d.ts +26 -0
- package/dist/flow/loops/while.d.ts +1 -1
- package/dist/flow/switch_case.d.ts +94 -12
- package/dist/index.d.ts +6 -6
- package/dist/index.js +1093 -367
- package/dist/pack/index.js +1103 -364
- package/dist/pack/pack.d.ts +12 -12
- package/dist/pack/visitors/index.d.ts +1 -0
- package/dist/pack/visitors/loopTransformationVisitor.d.ts +13 -6
- package/dist/pack/visitors/simplifyExecuteFunction.d.ts +3 -2
- package/dist/pack/visitors/simplifyReturnRunFunction.d.ts +2 -1
- package/dist/pack/visitors/switchTransformationVisitor.d.ts +31 -0
- package/dist/pack/visitors/unifyChainedExecutes.d.ts +3 -2
- package/dist/pack/visitors/visitor.d.ts +5 -1
- package/dist/utils.d.ts +34 -3
- package/dist/variables/Data.d.ts +7 -5
- package/dist/variables/DataSets.d.ts +17 -14
- package/dist/variables/Label.d.ts +1 -0
- package/dist/variables/Loop.d.ts +10 -2
- package/dist/variables/ResolveNBT.d.ts +4 -3
- package/dist/variables/Score.d.ts +49 -3
- package/dist/variables/index.js +1066 -328
- package/package.json +1 -1
- package/src/arguments/selector.ts +12 -0
- package/src/commands/implementations/block/setblock.ts +8 -12
- package/src/commands/implementations/entity/damage.ts +2 -2
- package/src/commands/implementations/entity/execute.ts +103 -34
- package/src/commands/implementations/server/return.ts +15 -7
- package/src/commands/implementations/world/data.ts +96 -48
- package/src/core/Macro.ts +18 -1
- package/src/core/resources/datapack/advancement.ts +1 -1
- package/src/core/resources/datapack/bannerPattern.ts +1 -1
- package/src/core/resources/datapack/chatType.ts +1 -1
- package/src/core/resources/datapack/damageType.ts +1 -1
- package/src/core/resources/datapack/dialog.ts +1 -1
- package/src/core/resources/datapack/enchantment.ts +2 -2
- package/src/core/resources/datapack/gametest.ts +2 -2
- package/src/core/resources/datapack/instrument.ts +1 -1
- package/src/core/resources/datapack/itemModifier.ts +1 -1
- package/src/core/resources/datapack/jukeboxSong.ts +1 -1
- package/src/core/resources/datapack/lootTable.ts +1 -1
- package/src/core/resources/datapack/mcfunction.ts +36 -17
- package/src/core/resources/datapack/predicate.ts +1 -1
- package/src/core/resources/datapack/recipe.ts +1 -1
- package/src/core/resources/datapack/structure.ts +1 -1
- package/src/core/resources/datapack/tag.ts +4 -4
- package/src/core/resources/datapack/timeline.ts +2 -2
- package/src/core/resources/datapack/trialSpawner.ts +1 -1
- package/src/core/resources/datapack/trimMaterial.ts +1 -1
- package/src/core/resources/datapack/trimPattern.ts +1 -1
- package/src/core/resources/datapack/variant.ts +2 -2
- package/src/core/resources/datapack/villagerTrade.ts +2 -2
- package/src/core/resources/resource.ts +22 -11
- package/src/core/resources/resourcepack/atlas.ts +1 -1
- package/src/core/resources/resourcepack/blockstate.ts +1 -1
- package/src/core/resources/resourcepack/equipment.ts +1 -1
- package/src/core/resources/resourcepack/font.ts +1 -1
- package/src/core/resources/resourcepack/itemDefinition.ts +1 -1
- package/src/core/resources/resourcepack/language.ts +1 -1
- package/src/core/resources/resourcepack/model.ts +1 -1
- package/src/core/resources/resourcepack/particle.ts +1 -1
- package/src/core/resources/resourcepack/postEffect.ts +1 -1
- package/src/core/resources/resourcepack/shader.ts +1 -1
- package/src/core/resources/resourcepack/sound.ts +1 -1
- package/src/core/resources/resourcepack/waypointStyle.ts +1 -1
- package/src/core/sandstoneCore.ts +9 -1
- package/src/flow/Flow.ts +58 -94
- package/src/flow/conditions/command.ts +1 -1
- package/src/flow/conditions/variables/dataPoint.ts +2 -2
- package/src/flow/if_else.ts +14 -3
- package/src/flow/index.ts +1 -0
- package/src/flow/loop.ts +10 -6
- package/src/flow/loops/for_i.ts +21 -17
- package/src/flow/loops/for_of.ts +43 -36
- package/src/flow/loops/staticAnalysis.ts +114 -0
- package/src/flow/loops/while.ts +8 -5
- package/src/flow/switch_case.ts +214 -13
- package/src/index.ts +6 -1
- package/src/pack/pack.ts +24 -19
- package/src/pack/visitors/containerCommandsToMCFunction.ts +12 -5
- package/src/pack/visitors/ifElseTransformationVisitor.ts +44 -16
- package/src/pack/visitors/index.ts +1 -0
- package/src/pack/visitors/initConstantsVisitor.ts +13 -11
- package/src/pack/visitors/loopTransformationVisitor.ts +30 -7
- package/src/pack/visitors/simplifyExecuteFunction.ts +43 -6
- package/src/pack/visitors/simplifyReturnRunFunction.ts +40 -17
- package/src/pack/visitors/switchTransformationVisitor.ts +452 -0
- package/src/pack/visitors/unifyChainedExecutes.ts +9 -1
- package/src/pack/visitors/visitor.ts +9 -1
- package/src/utils.ts +53 -12
- package/src/variables/Data.ts +20 -4
- package/src/variables/DataSets.ts +57 -23
- package/src/variables/JSONTextComponentClass.ts +6 -1
- package/src/variables/Label.ts +0 -3
- package/src/variables/Loop.ts +30 -1
- package/src/variables/Objective.ts +0 -1
- package/src/variables/ResolveNBT.ts +5 -3
- package/src/variables/Score.ts +238 -8
- package/src/variables/Sleep.ts +2 -2
- package/src/variables/UUID.ts +8 -6
- package/src/variables/nbt/NBTs.ts +14 -0
package/CLAUDE.md
CHANGED
|
@@ -101,12 +101,146 @@ Central engine managing:
|
|
|
101
101
|
- **ResourcePack resources**: Texture, BlockState, Model, Font, SoundEvent, Atlas
|
|
102
102
|
|
|
103
103
|
#### Visitor Pattern (`src/pack/visitors/`)
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
104
|
+
|
|
105
|
+
Visitors transform the AST before serialization. They run in sequence during `core.save()`.
|
|
106
|
+
|
|
107
|
+
**Visitor Base Class:**
|
|
108
|
+
```typescript
|
|
109
|
+
class GenericSandstoneVisitor {
|
|
110
|
+
// Override to transform specific node types
|
|
111
|
+
visitExecuteCommandNode(node: ExecuteCommandNode) {
|
|
112
|
+
// Transform node, return replacement (or same node)
|
|
113
|
+
return this.genericVisit(node) // Recursively visit children
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
**Execute-Related Visitors:**
|
|
119
|
+
|
|
120
|
+
| Visitor | Purpose |
|
|
121
|
+
|---------|---------|
|
|
122
|
+
| `UnifyChainedExecutesVisitor` | Merges `execute run execute run...` into single execute; unwraps empty executes (`execute run say hi` → `say hi`) |
|
|
123
|
+
| `SimplifyExecuteFunctionVisitor` | Inlines single-command MCFunctions called from execute |
|
|
124
|
+
| `ContainerCommandsToMCFunctionVisitor` | Extracts multi-command execute bodies into child MCFunctions |
|
|
125
|
+
|
|
126
|
+
**UnifyChainedExecutesVisitor Details:**
|
|
127
|
+
```typescript
|
|
128
|
+
visitExecuteCommandNode = (node) => {
|
|
129
|
+
// Merge nested execute: execute.run.execute.run.say → execute.run.say
|
|
130
|
+
if (node.body[0] instanceof ExecuteCommandNode) {
|
|
131
|
+
node.body = chainedExecute.body
|
|
132
|
+
node.args.push(...chainedExecute.args)
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
// Unwrap empty execute: execute run say hi → say hi
|
|
136
|
+
const flattenedArgs = node.args.flat(1).filter(a => a != null)
|
|
137
|
+
if (flattenedArgs.length === 0 && node.body.length === 1) {
|
|
138
|
+
return this.genericVisit(node.body[0]) // Return child directly
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
**Other Key Visitors:**
|
|
144
|
+
|
|
145
|
+
| Visitor | Purpose |
|
|
146
|
+
|---------|---------|
|
|
147
|
+
| `IfElseTransformationVisitor` | Transforms `_.if()` flow control into execute if/unless chains |
|
|
148
|
+
| `LoopTransformationVisitor` | Transforms `_.while()`, `_.forScore()` into recursive function calls; sets `loopExecute` on `LoopArgument` nodes |
|
|
149
|
+
| `SwitchTransformationVisitor` | Transforms `_.switch()` into MCFunctions with O(1) dispatch via macros |
|
|
150
|
+
| `InitConstantsVisitor` | Generates scoreboard objective creation commands |
|
|
151
|
+
| `InlineFunctionCallVisitor` | Inlines tiny MCFunctions to reduce function call overhead |
|
|
152
|
+
|
|
153
|
+
**Visitor Execution Order:**
|
|
154
|
+
Visitors run in a specific order defined in `pack.ts`. Order matters because:
|
|
155
|
+
- Flow transformations must run before container extraction
|
|
156
|
+
- Simplification visitors run after structural transformations
|
|
157
|
+
- Constant initialization runs early to set up scoreboards
|
|
158
|
+
- **SwitchTransformationVisitor runs before IfElseTransformationVisitor** since it creates IfNodes that need to be processed
|
|
159
|
+
|
|
160
|
+
#### Switch Transformation Flow
|
|
161
|
+
|
|
162
|
+
The `SwitchTransformationVisitor` transforms `_.switch()` into efficient MCFunction dispatch:
|
|
163
|
+
|
|
164
|
+
**Score-based switches** (switching on a `Score`):
|
|
165
|
+
- Case functions are named by their value: `case_0`, `case_5`, `case_100`
|
|
166
|
+
- Score is copied to NBT storage, then used as macro: `function case_$(value)`
|
|
167
|
+
- Uses `execute store success` to detect if the macro function exists
|
|
168
|
+
- Falls through to condition cases / default if no match
|
|
169
|
+
- Can be inlined when switch is last node in parent MCFunction
|
|
170
|
+
|
|
171
|
+
**Data-based switches** (switching on NBT data):
|
|
172
|
+
- Uses O(1) NBT storage lookup to map values to sequential indices
|
|
173
|
+
- Case functions are named by index: `case_0`, `case_1`, `case_2`
|
|
174
|
+
- Lookup table initialized in `initMCFunction`
|
|
175
|
+
- Falls through to condition cases / default if no match
|
|
176
|
+
|
|
177
|
+
**Key implementation details:**
|
|
178
|
+
|
|
179
|
+
1. **enterContext with addNode=false**: When populating the IfNode body for data switches, pass `false` to `enterContext` to avoid auto-adding the node:
|
|
180
|
+
```typescript
|
|
181
|
+
// Pass false - we manually push ifNode to body later
|
|
182
|
+
switchMCFunction.node.enterContext(ifNode, false)
|
|
183
|
+
functionCmd(innerMCFunction.name, 'with', switchMCFunction.macroPoint!)
|
|
184
|
+
switchMCFunction.node.exitContext()
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
2. **Push IfNode before buildFallback**: The IfNode must be added to the body BEFORE calling `buildFallback`, because `buildFallback` may create an `ElseNode` whose constructor auto-adds it to the body. This ensures correct order `[IfNode, ElseNode]` for `IfElseTransformationVisitor`.
|
|
188
|
+
|
|
189
|
+
3. **Score switch inlining**: For Score-based switches, the macro call is isolated in a child function (`try_case`), so the main switch function body doesn't contain macros and can be inlined.
|
|
190
|
+
|
|
191
|
+
#### Loop Transformation Flow
|
|
192
|
+
|
|
193
|
+
Loops (`_.while()`, `_.for()`, etc.) undergo a multi-stage transformation:
|
|
194
|
+
|
|
195
|
+
**1. User Code Creates LoopNode:**
|
|
196
|
+
```typescript
|
|
197
|
+
_.while(foo.matches([0,10]), () => {
|
|
198
|
+
say("foo")
|
|
199
|
+
foo['+='](1)
|
|
200
|
+
})
|
|
201
|
+
```
|
|
202
|
+
Creates a `WhileNode` (extends `LoopNode`) with:
|
|
203
|
+
- `executeArgs`: The condition as execute subcommands
|
|
204
|
+
- `callback`: User's loop body
|
|
205
|
+
- `loopback`: Creates `IfStatement` containing `LoopArgument` for recursion
|
|
206
|
+
|
|
207
|
+
**2. LoopTransformationVisitor:**
|
|
208
|
+
- Transforms `LoopNode` → `ExecuteCommandNode` with `callbackName: 'loop'`
|
|
209
|
+
- Tracks `currentLoopExecute` while visiting children
|
|
210
|
+
- Sets `loopArgument.loopExecute = currentLoopExecute` on any `LoopArgument` found
|
|
211
|
+
- This reference is critical for later resolving the recursive call target
|
|
212
|
+
|
|
213
|
+
**3. IfElseTransformationVisitor:**
|
|
214
|
+
- Transforms the loopback's `IfNode` → `ExecuteCommandNode` with `callbackName: 'if'`
|
|
215
|
+
- Creates nested execute structure for the condition check
|
|
216
|
+
|
|
217
|
+
**4. ContainerCommandsToMCFunctionVisitor:**
|
|
218
|
+
- Extracts multi-command execute bodies into child MCFunctions
|
|
219
|
+
- For the loop execute: creates `parent/loop` MCFunction
|
|
220
|
+
- For the if execute: creates `parent/loop/if` (or `if2` on conflict) MCFunction
|
|
221
|
+
- **Critical**: Sets `executeNode.createdMCFunction` when creating the child function
|
|
222
|
+
- This allows `LoopArgument` to later resolve the correct function name
|
|
223
|
+
|
|
224
|
+
**5. SimplifyExecuteFunctionVisitor:**
|
|
225
|
+
- Finds execute nodes whose body is a single function call
|
|
226
|
+
- If that function contains only a `LoopArgument`:
|
|
227
|
+
- Gets `loopArgument.loopExecute.createdMCFunction.name` (the loop function)
|
|
228
|
+
- Replaces execute body with direct call to loop function
|
|
229
|
+
- Deletes the intermediate if function
|
|
230
|
+
- **Important**: Must check `instanceof LoopArgument` BEFORE `instanceof CommandNode` since `LoopArgument` extends `Node`, not `CommandNode`
|
|
231
|
+
|
|
232
|
+
**Final Output:**
|
|
233
|
+
```mcfunction
|
|
234
|
+
# parent/loop.mcfunction
|
|
235
|
+
say foo
|
|
236
|
+
scoreboard players add counter __sandstone 1
|
|
237
|
+
execute if score counter __sandstone matches 0..10 run function namespace:parent/loop
|
|
238
|
+
```
|
|
239
|
+
|
|
240
|
+
**Key Fields for Loop Resolution:**
|
|
241
|
+
- `ExecuteCommandNode.createdMCFunction`: Set by `createMCFunction()`, stores the MCFunction created from this execute
|
|
242
|
+
- `LoopArgument.loopExecute`: Set by `LoopTransformationVisitor`, references the loop's execute node
|
|
243
|
+
- These together allow `LoopArgument` to produce `function <correct-loop-name>` at serialization or simplification time
|
|
110
244
|
|
|
111
245
|
**Visitor files** follow the naming convention `<transformationName>.ts` (e.g., `containerCommandsToMCFunction.ts`, `ifElseTransformationVisitor.ts`).
|
|
112
246
|
|
|
@@ -187,19 +321,156 @@ mcfunctionNode.exitContext()
|
|
|
187
321
|
2. Gets the top of that function's `contextStack`
|
|
188
322
|
3. Pushes the command node into that context's `body`
|
|
189
323
|
|
|
190
|
-
####
|
|
191
|
-
|
|
324
|
+
#### enterContext Pitfalls
|
|
325
|
+
|
|
326
|
+
The `enterContext` method has an `addNode` parameter that defaults to `true`:
|
|
327
|
+
|
|
192
328
|
```typescript
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
// -
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
// - Both commands go into execute's body
|
|
200
|
-
// - Visitor later extracts to child MCFunction if needed
|
|
329
|
+
enterContext = (node: ContainerNode | ContainerCommandNode, addNode: boolean = true) => {
|
|
330
|
+
if (addNode) {
|
|
331
|
+
this.currentContext.append(node) // Auto-adds to current context's body!
|
|
332
|
+
}
|
|
333
|
+
this.contextStack.push(node)
|
|
334
|
+
}
|
|
201
335
|
```
|
|
202
336
|
|
|
337
|
+
**Pitfall 1: Duplicate nodes when manually pushing**
|
|
338
|
+
|
|
339
|
+
If you call `enterContext(node)` and later manually push the same node to the body, you'll get duplicates:
|
|
340
|
+
|
|
341
|
+
```typescript
|
|
342
|
+
// WRONG - node gets added twice!
|
|
343
|
+
mcfunction.enterContext(ifNode) // Auto-adds ifNode to body
|
|
344
|
+
// ... populate ifNode.body ...
|
|
345
|
+
mcfunction.exitContext()
|
|
346
|
+
mcfunction.body.push(ifNode) // Duplicate!
|
|
347
|
+
|
|
348
|
+
// CORRECT - pass false to prevent auto-add
|
|
349
|
+
mcfunction.enterContext(ifNode, false) // Does NOT add to body
|
|
350
|
+
// ... populate ifNode.body ...
|
|
351
|
+
mcfunction.exitContext()
|
|
352
|
+
mcfunction.body.push(ifNode) // Only addition
|
|
353
|
+
```
|
|
354
|
+
|
|
355
|
+
**Pitfall 2: Node constructors that call enterContext**
|
|
356
|
+
|
|
357
|
+
Some node constructors (like `ElseNode`) automatically call `enterContext(this)`:
|
|
358
|
+
|
|
359
|
+
```typescript
|
|
360
|
+
export class ElseNode extends ContainerNode {
|
|
361
|
+
constructor(sandstoneCore: SandstoneCore, callback: () => void) {
|
|
362
|
+
super(sandstoneCore)
|
|
363
|
+
// This auto-adds the ElseNode to the current MCFunction's body!
|
|
364
|
+
this.sandstoneCore.getCurrentMCFunctionOrThrow().enterContext(this)
|
|
365
|
+
callback()
|
|
366
|
+
this.sandstoneCore.currentMCFunction?.exitContext()
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
```
|
|
370
|
+
|
|
371
|
+
When creating such nodes in visitors, be aware they'll be auto-added to the body. If you only want to link them (e.g., via `nextFlowNode`), you may need to account for their position in the body.
|
|
372
|
+
|
|
373
|
+
**Pitfall 3: Order matters for if/else chains**
|
|
374
|
+
|
|
375
|
+
For `IfElseTransformationVisitor` to work correctly, nodes must be in the body in the correct order:
|
|
376
|
+
|
|
377
|
+
```typescript
|
|
378
|
+
// WRONG order - ElseNode added before IfNode
|
|
379
|
+
this.buildFallback(ifNode, ...) // Creates ElseNode, auto-adds to body
|
|
380
|
+
mcfunction.body.push(ifNode) // IfNode comes after ElseNode
|
|
381
|
+
|
|
382
|
+
// CORRECT order - IfNode first, then ElseNode
|
|
383
|
+
mcfunction.body.push(ifNode) // IfNode added first
|
|
384
|
+
this.buildFallback(ifNode, ...) // ElseNode auto-added after
|
|
385
|
+
// Result: [IfNode, ElseNode] - correct order for visitor
|
|
386
|
+
```
|
|
387
|
+
|
|
388
|
+
#### Execute Command Internals
|
|
389
|
+
|
|
390
|
+
`ExecuteCommandNode` extends `ContainerCommandNode` and has special context management for the fluent `.run` API.
|
|
391
|
+
|
|
392
|
+
**Key Properties:**
|
|
393
|
+
- `isSingleExecute`: `true` for `.run.command`, `false` for `.run(() => {...})` callbacks
|
|
394
|
+
- `pendingCommit`: Set when `.run` is accessed on an uncommitted execute; triggers commit after body receives command
|
|
395
|
+
- `args`: Array of subcommands like `[['as', '@a'], ['at', '@s'], ['positioned', '0 0 0']]`
|
|
396
|
+
- `body`: Child command(s) that run after the execute modifiers
|
|
397
|
+
- `createdMCFunction`: Set by `createMCFunction()` when body is extracted to a child MCFunction; used by `LoopArgument` to resolve recursive call targets
|
|
398
|
+
- `macroStorage`: If set, the child function is called with `function <name> with storage <macroStorage> {}`
|
|
399
|
+
|
|
400
|
+
**The `.run` Getter Flow:**
|
|
401
|
+
|
|
402
|
+
```typescript
|
|
403
|
+
// execute.positioned(...).run.say('hello')
|
|
404
|
+
|
|
405
|
+
get run() {
|
|
406
|
+
const node = this.getNode() // Get or create ExecuteCommandNode
|
|
407
|
+
|
|
408
|
+
return new Proxy(commands, {
|
|
409
|
+
get: (_, prop) => {
|
|
410
|
+
// Mark for deferred commit if not already committed
|
|
411
|
+
if (!node.commited) {
|
|
412
|
+
node.pendingCommit = true
|
|
413
|
+
}
|
|
414
|
+
// Enter execute's context (commands go to execute.body)
|
|
415
|
+
mcfunction.enterContext(node, false) // false = don't add node yet
|
|
416
|
+
return commands[prop]
|
|
417
|
+
}
|
|
418
|
+
})
|
|
419
|
+
}
|
|
420
|
+
```
|
|
421
|
+
|
|
422
|
+
**Context and Commit Lifecycle:**
|
|
423
|
+
|
|
424
|
+
1. **Subcommand commits execute**: `.positioned(...)` calls `subCommand([...], ExecuteCommand, executable=true)` which commits the execute to MCFunction
|
|
425
|
+
2. **`.run` enters context**: Pushes execute onto `contextStack`, subsequent commands go to `execute.body`
|
|
426
|
+
3. **Child command commits**: `say('hello')` creates node, commits to current context (the execute)
|
|
427
|
+
4. **`append()` handles cleanup**: Exits context AND commits execute if `pendingCommit` was set
|
|
428
|
+
|
|
429
|
+
```typescript
|
|
430
|
+
// In ExecuteCommandNode
|
|
431
|
+
append = (...nodes: Node[]) => {
|
|
432
|
+
for (const node of nodes) {
|
|
433
|
+
this.body.push(node)
|
|
434
|
+
if (this.isSingleExecute) {
|
|
435
|
+
// Exit context first (parent becomes current)
|
|
436
|
+
mcfunction.exitContext()
|
|
437
|
+
// Then commit this execute to parent if pending
|
|
438
|
+
if (this.pendingCommit && !this.commited) {
|
|
439
|
+
this.commit() // Adds execute to parent context
|
|
440
|
+
}
|
|
441
|
+
}
|
|
442
|
+
}
|
|
443
|
+
}
|
|
444
|
+
```
|
|
445
|
+
|
|
446
|
+
**Nested Execute Chains:**
|
|
447
|
+
|
|
448
|
+
For `execute.positioned(...).run.execute.run.say('hi')`:
|
|
449
|
+
1. Outer execute committed via `.positioned`
|
|
450
|
+
2. `.run.execute` enters outer's context, returns NEW ExecuteCommand (inner)
|
|
451
|
+
3. `.run.say` on inner enters inner's context
|
|
452
|
+
4. `say('hi')` commits to inner's body
|
|
453
|
+
5. Inner's `append()` exits inner context, commits inner to outer's body (current context)
|
|
454
|
+
6. Outer's `append()` exits outer context (outer already committed)
|
|
455
|
+
|
|
456
|
+
Result: `execute positioned 0 0 0 run execute run say hi` (visitor simplifies later)
|
|
457
|
+
|
|
458
|
+
**Single vs Multiple Command Mode:**
|
|
459
|
+
|
|
460
|
+
```typescript
|
|
461
|
+
// Single command: isSingleExecute=true (default)
|
|
462
|
+
execute.as('@a').run.say('hi')
|
|
463
|
+
// - One command in body, stays as single execute node
|
|
464
|
+
|
|
465
|
+
// Callback mode: isSingleExecute=false
|
|
466
|
+
execute.as('@a').run(() => { say('a'); say('b') })
|
|
467
|
+
// - Multiple commands in body
|
|
468
|
+
// - ContainerCommandsToMCFunctionVisitor extracts to child MCFunction
|
|
469
|
+
```
|
|
470
|
+
|
|
471
|
+
**IMPORTANT - isSingleExecute in Visitors:**
|
|
472
|
+
When creating `ExecuteCommandNode` during visitor transformation, always use `isSingleExecute: false` even for single-command bodies. The `append()` method has special behavior when `isSingleExecute: true` that calls `getCurrentMCFunctionOrThrow().exitContext()`, which fails during visitor passes (no active MCFunction context). Simplification of single-command executes should happen in later visitors like `SimplifyExecuteFunctionVisitor`, not by setting `isSingleExecute: true`.
|
|
473
|
+
|
|
203
474
|
#### Deferred Execution with autoCommit
|
|
204
475
|
Commands normally commit immediately when created. Use `autoCommit=false` to defer:
|
|
205
476
|
```typescript
|
|
@@ -257,6 +528,34 @@ return createDeferredMacroExecute(pack, deferred, {
|
|
|
257
528
|
})
|
|
258
529
|
```
|
|
259
530
|
|
|
531
|
+
### Debugging Context Issues
|
|
532
|
+
|
|
533
|
+
Context bugs manifest as commands appearing in wrong functions or being orphaned. Common symptoms:
|
|
534
|
+
- Commands from later in the code appear inside earlier execute blocks
|
|
535
|
+
- Commands disappear entirely (orphaned nodes)
|
|
536
|
+
- "Execute nodes can only have one child" errors during save
|
|
537
|
+
|
|
538
|
+
**Debugging Steps:**
|
|
539
|
+
|
|
540
|
+
1. **Check context stack**: Add logging in `enterContext`/`exitContext` to trace stack state
|
|
541
|
+
2. **Verify commit timing**: Ensure execute nodes are committed before their context is needed
|
|
542
|
+
3. **Check `pendingCommit`**: For `.run.command` patterns, execute must commit after body receives command
|
|
543
|
+
4. **Trace nested executes**: Each `.run` access enters context; ensure matching exits
|
|
544
|
+
|
|
545
|
+
**Common Pitfalls:**
|
|
546
|
+
|
|
547
|
+
| Issue | Cause | Fix |
|
|
548
|
+
|-------|-------|-----|
|
|
549
|
+
| Commands go to wrong execute | Context not exited after previous execute | Ensure `append()` calls `exitContext()` |
|
|
550
|
+
| Execute is orphaned | Never committed to parent | Use `pendingCommit` or ensure subcommand commits |
|
|
551
|
+
| Nested execute loses inner | Inner execute not added to outer's body | Inner must commit while outer's context is current |
|
|
552
|
+
| Double commands | Node committed multiple times | Check `commited` flag before `commit()` |
|
|
553
|
+
|
|
554
|
+
**Key Invariants:**
|
|
555
|
+
- `contextStack[0]` is always the MCFunction itself
|
|
556
|
+
- After a single execute completes, `contextStack.length` should return to previous value
|
|
557
|
+
- Every `enterContext` needs a matching `exitContext`
|
|
558
|
+
|
|
260
559
|
### Key Design Patterns
|
|
261
560
|
|
|
262
561
|
- **Visitor Pattern**: AST transformations in `src/pack/visitors/`
|
package/bun.lock
CHANGED
|
@@ -151,25 +151,25 @@
|
|
|
151
151
|
|
|
152
152
|
"@types/semver": ["@types/semver@7.5.4", "", {}, "sha512-MMzuxN3GdFwskAnb6fz0orFvhfqi752yjaXylr0Rp4oDg5H0Zn1IuyRhDVvYOwAXoJirx2xuS16I3WjxnAIHiQ=="],
|
|
153
153
|
|
|
154
|
-
"@typescript-eslint/eslint-plugin": ["@typescript-eslint/eslint-plugin@8.56.
|
|
154
|
+
"@typescript-eslint/eslint-plugin": ["@typescript-eslint/eslint-plugin@8.56.1", "", { "dependencies": { "@eslint-community/regexpp": "^4.12.2", "@typescript-eslint/scope-manager": "8.56.1", "@typescript-eslint/type-utils": "8.56.1", "@typescript-eslint/utils": "8.56.1", "@typescript-eslint/visitor-keys": "8.56.1", "ignore": "^7.0.5", "natural-compare": "^1.4.0", "ts-api-utils": "^2.4.0" }, "peerDependencies": { "@typescript-eslint/parser": "^8.56.1", "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "typescript": ">=4.8.4 <6.0.0" } }, "sha512-Jz9ZztpB37dNC+HU2HI28Bs9QXpzCz+y/twHOwhyrIRdbuVDxSytJNDl6z/aAKlaRIwC7y8wJdkBv7FxYGgi0A=="],
|
|
155
155
|
|
|
156
|
-
"@typescript-eslint/parser": ["@typescript-eslint/parser@8.56.
|
|
156
|
+
"@typescript-eslint/parser": ["@typescript-eslint/parser@8.56.1", "", { "dependencies": { "@typescript-eslint/scope-manager": "8.56.1", "@typescript-eslint/types": "8.56.1", "@typescript-eslint/typescript-estree": "8.56.1", "@typescript-eslint/visitor-keys": "8.56.1", "debug": "^4.4.3" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "typescript": ">=4.8.4 <6.0.0" } }, "sha512-klQbnPAAiGYFyI02+znpBRLyjL4/BrBd0nyWkdC0s/6xFLkXYQ8OoRrSkqacS1ddVxf/LDyODIKbQ5TgKAf/Fg=="],
|
|
157
157
|
|
|
158
|
-
"@typescript-eslint/project-service": ["@typescript-eslint/project-service@8.56.
|
|
158
|
+
"@typescript-eslint/project-service": ["@typescript-eslint/project-service@8.56.1", "", { "dependencies": { "@typescript-eslint/tsconfig-utils": "^8.56.1", "@typescript-eslint/types": "^8.56.1", "debug": "^4.4.3" }, "peerDependencies": { "typescript": ">=4.8.4 <6.0.0" } }, "sha512-TAdqQTzHNNvlVFfR+hu2PDJrURiwKsUvxFn1M0h95BB8ah5jejas08jUWG4dBA68jDMI988IvtfdAI53JzEHOQ=="],
|
|
159
159
|
|
|
160
|
-
"@typescript-eslint/scope-manager": ["@typescript-eslint/scope-manager@8.56.
|
|
160
|
+
"@typescript-eslint/scope-manager": ["@typescript-eslint/scope-manager@8.56.1", "", { "dependencies": { "@typescript-eslint/types": "8.56.1", "@typescript-eslint/visitor-keys": "8.56.1" } }, "sha512-YAi4VDKcIZp0O4tz/haYKhmIDZFEUPOreKbfdAN3SzUDMcPhJ8QI99xQXqX+HoUVq8cs85eRKnD+rne2UAnj2w=="],
|
|
161
161
|
|
|
162
|
-
"@typescript-eslint/tsconfig-utils": ["@typescript-eslint/tsconfig-utils@8.56.
|
|
162
|
+
"@typescript-eslint/tsconfig-utils": ["@typescript-eslint/tsconfig-utils@8.56.1", "", { "peerDependencies": { "typescript": ">=4.8.4 <6.0.0" } }, "sha512-qOtCYzKEeyr3aR9f28mPJqBty7+DBqsdd63eO0yyDwc6vgThj2UjWfJIcsFeSucYydqcuudMOprZ+x1SpF3ZuQ=="],
|
|
163
163
|
|
|
164
|
-
"@typescript-eslint/type-utils": ["@typescript-eslint/type-utils@8.56.
|
|
164
|
+
"@typescript-eslint/type-utils": ["@typescript-eslint/type-utils@8.56.1", "", { "dependencies": { "@typescript-eslint/types": "8.56.1", "@typescript-eslint/typescript-estree": "8.56.1", "@typescript-eslint/utils": "8.56.1", "debug": "^4.4.3", "ts-api-utils": "^2.4.0" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "typescript": ">=4.8.4 <6.0.0" } }, "sha512-yB/7dxi7MgTtGhZdaHCemf7PuwrHMenHjmzgUW1aJpO+bBU43OycnM3Wn+DdvDO/8zzA9HlhaJ0AUGuvri4oGg=="],
|
|
165
165
|
|
|
166
166
|
"@typescript-eslint/types": ["@typescript-eslint/types@8.53.1", "", {}, "sha512-jr/swrr2aRmUAUjW5/zQHbMaui//vQlsZcJKijZf3M26bnmLj8LyZUpj8/Rd6uzaek06OWsqdofN/Thenm5O8A=="],
|
|
167
167
|
|
|
168
|
-
"@typescript-eslint/typescript-estree": ["@typescript-eslint/typescript-estree@8.56.
|
|
168
|
+
"@typescript-eslint/typescript-estree": ["@typescript-eslint/typescript-estree@8.56.1", "", { "dependencies": { "@typescript-eslint/project-service": "8.56.1", "@typescript-eslint/tsconfig-utils": "8.56.1", "@typescript-eslint/types": "8.56.1", "@typescript-eslint/visitor-keys": "8.56.1", "debug": "^4.4.3", "minimatch": "^10.2.2", "semver": "^7.7.3", "tinyglobby": "^0.2.15", "ts-api-utils": "^2.4.0" }, "peerDependencies": { "typescript": ">=4.8.4 <6.0.0" } }, "sha512-qzUL1qgalIvKWAf9C1HpvBjif+Vm6rcT5wZd4VoMb9+Km3iS3Cv9DY6dMRMDtPnwRAFyAi7YXJpTIEXLvdfPxg=="],
|
|
169
169
|
|
|
170
|
-
"@typescript-eslint/utils": ["@typescript-eslint/utils@8.56.
|
|
170
|
+
"@typescript-eslint/utils": ["@typescript-eslint/utils@8.56.1", "", { "dependencies": { "@eslint-community/eslint-utils": "^4.9.1", "@typescript-eslint/scope-manager": "8.56.1", "@typescript-eslint/types": "8.56.1", "@typescript-eslint/typescript-estree": "8.56.1" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "typescript": ">=4.8.4 <6.0.0" } }, "sha512-HPAVNIME3tABJ61siYlHzSWCGtOoeP2RTIaHXFMPqjrQKCGB9OgUVdiNgH7TJS2JNIQ5qQ4RsAUDuGaGme/KOA=="],
|
|
171
171
|
|
|
172
|
-
"@typescript-eslint/visitor-keys": ["@typescript-eslint/visitor-keys@8.56.
|
|
172
|
+
"@typescript-eslint/visitor-keys": ["@typescript-eslint/visitor-keys@8.56.1", "", { "dependencies": { "@typescript-eslint/types": "8.56.1", "eslint-visitor-keys": "^5.0.0" } }, "sha512-KiROIzYdEV85YygXw6BI/Dx4fnBlFQu6Mq4QE4MOH9fFnhohw6wX/OAvDY2/C+ut0I3RSPKenvZJIVYqJNkhEw=="],
|
|
173
173
|
|
|
174
174
|
"@typescript/analyze-trace": ["@typescript/analyze-trace@0.4.0", "", { "dependencies": { "chalk": "^4.1.2", "jsonstream-next": "^3.0.0", "p-limit": "^3.1.0", "split2": "^3.2.2", "treeify": "^1.1.0", "yargs": "^16.2.0" }, "bin": { "analyze-trace": "bin/analyze-trace", "print-trace-types": "bin/print-trace-types", "simplify-trace-types": "bin/simplify-trace-types" } }, "sha512-i7QVVGNNr20P5w3jMuGmozS07mCe8+XaAszFqeR+YpDj3sEbdNK0gRxKnwUbxAz3Auuc4skfSHlE0PaW+MXd1Q=="],
|
|
175
175
|
|
|
@@ -535,7 +535,7 @@
|
|
|
535
535
|
|
|
536
536
|
"typescript": ["typescript@5.9.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw=="],
|
|
537
537
|
|
|
538
|
-
"typescript-eslint": ["typescript-eslint@8.56.
|
|
538
|
+
"typescript-eslint": ["typescript-eslint@8.56.1", "", { "dependencies": { "@typescript-eslint/eslint-plugin": "8.56.1", "@typescript-eslint/parser": "8.56.1", "@typescript-eslint/typescript-estree": "8.56.1", "@typescript-eslint/utils": "8.56.1" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "typescript": ">=4.8.4 <6.0.0" } }, "sha512-U4lM6pjmBX7J5wk4szltF7I1cGBHXZopnAXCMXb3+fZ3B/0Z3hq3wS/CCUB2NZBNAExK92mCU2tEohWuwVMsDQ=="],
|
|
539
539
|
|
|
540
540
|
"unbzip2-stream": ["unbzip2-stream@1.4.3", "", { "dependencies": { "buffer": "^5.2.1", "through": "^2.3.8" } }, "sha512-mlExGW4w71ebDJviH16lQLtZS32VKqsSfk80GCfUlwT/4/hNRFsoscrF/c++9xinkMzECL1uL9DDwXqFWkruPg=="],
|
|
541
541
|
|
|
@@ -589,23 +589,23 @@
|
|
|
589
589
|
|
|
590
590
|
"@types/jsonfile/@types/node": ["@types/node@17.0.45", "", {}, "sha512-w+tIMs3rq2afQdsPJlODhoUEKzFP1ayaoyl1CcnwtIlsVe7K7bA1NGm4s3PraqTLlXnbIN84zuBlxBWo1u9BLw=="],
|
|
591
591
|
|
|
592
|
-
"@typescript-eslint/parser/@typescript-eslint/types": ["@typescript-eslint/types@8.56.
|
|
592
|
+
"@typescript-eslint/parser/@typescript-eslint/types": ["@typescript-eslint/types@8.56.1", "", {}, "sha512-dbMkdIUkIkchgGDIv7KLUpa0Mda4IYjo4IAMJUZ+3xNoUXxMsk9YtKpTHSChRS85o+H9ftm51gsK1dZReY9CVw=="],
|
|
593
593
|
|
|
594
|
-
"@typescript-eslint/project-service/@typescript-eslint/types": ["@typescript-eslint/types@8.56.
|
|
594
|
+
"@typescript-eslint/project-service/@typescript-eslint/types": ["@typescript-eslint/types@8.56.1", "", {}, "sha512-dbMkdIUkIkchgGDIv7KLUpa0Mda4IYjo4IAMJUZ+3xNoUXxMsk9YtKpTHSChRS85o+H9ftm51gsK1dZReY9CVw=="],
|
|
595
595
|
|
|
596
|
-
"@typescript-eslint/scope-manager/@typescript-eslint/types": ["@typescript-eslint/types@8.56.
|
|
596
|
+
"@typescript-eslint/scope-manager/@typescript-eslint/types": ["@typescript-eslint/types@8.56.1", "", {}, "sha512-dbMkdIUkIkchgGDIv7KLUpa0Mda4IYjo4IAMJUZ+3xNoUXxMsk9YtKpTHSChRS85o+H9ftm51gsK1dZReY9CVw=="],
|
|
597
597
|
|
|
598
|
-
"@typescript-eslint/type-utils/@typescript-eslint/types": ["@typescript-eslint/types@8.56.
|
|
598
|
+
"@typescript-eslint/type-utils/@typescript-eslint/types": ["@typescript-eslint/types@8.56.1", "", {}, "sha512-dbMkdIUkIkchgGDIv7KLUpa0Mda4IYjo4IAMJUZ+3xNoUXxMsk9YtKpTHSChRS85o+H9ftm51gsK1dZReY9CVw=="],
|
|
599
599
|
|
|
600
|
-
"@typescript-eslint/typescript-estree/@typescript-eslint/types": ["@typescript-eslint/types@8.56.
|
|
600
|
+
"@typescript-eslint/typescript-estree/@typescript-eslint/types": ["@typescript-eslint/types@8.56.1", "", {}, "sha512-dbMkdIUkIkchgGDIv7KLUpa0Mda4IYjo4IAMJUZ+3xNoUXxMsk9YtKpTHSChRS85o+H9ftm51gsK1dZReY9CVw=="],
|
|
601
601
|
|
|
602
|
-
"@typescript-eslint/typescript-estree/minimatch": ["minimatch@
|
|
602
|
+
"@typescript-eslint/typescript-estree/minimatch": ["minimatch@10.2.4", "", { "dependencies": { "brace-expansion": "^5.0.2" } }, "sha512-oRjTw/97aTBN0RHbYCdtF1MQfvusSIBQM0IZEgzl6426+8jSC0nF1a/GmnVLpfB9yyr6g6FTqWqiZVbxrtaCIg=="],
|
|
603
603
|
|
|
604
604
|
"@typescript-eslint/typescript-estree/semver": ["semver@7.7.4", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA=="],
|
|
605
605
|
|
|
606
|
-
"@typescript-eslint/utils/@typescript-eslint/types": ["@typescript-eslint/types@8.56.
|
|
606
|
+
"@typescript-eslint/utils/@typescript-eslint/types": ["@typescript-eslint/types@8.56.1", "", {}, "sha512-dbMkdIUkIkchgGDIv7KLUpa0Mda4IYjo4IAMJUZ+3xNoUXxMsk9YtKpTHSChRS85o+H9ftm51gsK1dZReY9CVw=="],
|
|
607
607
|
|
|
608
|
-
"@typescript-eslint/visitor-keys/@typescript-eslint/types": ["@typescript-eslint/types@8.56.
|
|
608
|
+
"@typescript-eslint/visitor-keys/@typescript-eslint/types": ["@typescript-eslint/types@8.56.1", "", {}, "sha512-dbMkdIUkIkchgGDIv7KLUpa0Mda4IYjo4IAMJUZ+3xNoUXxMsk9YtKpTHSChRS85o+H9ftm51gsK1dZReY9CVw=="],
|
|
609
609
|
|
|
610
610
|
"@typescript-eslint/visitor-keys/eslint-visitor-keys": ["eslint-visitor-keys@5.0.0", "", {}, "sha512-A0XeIi7CXU7nPlfHS9loMYEKxUaONu/hTEzHTGba9Huu94Cq1hPivf+DE5erJozZOky0LfvXAyrV/tcswpLI0Q=="],
|
|
611
611
|
|
|
@@ -629,8 +629,6 @@
|
|
|
629
629
|
|
|
630
630
|
"uri-js/punycode": ["punycode@2.3.0", "", {}, "sha512-rRV+zQD8tVFys26lAGR9WUuS4iUAngJScM+ZRSKtvl5tKeZ2t5bvdNFdNHBW9FWR4guGHlgmsZ1G7BSm2wTbuA=="],
|
|
631
631
|
|
|
632
|
-
"@typescript-eslint/typescript-estree/minimatch/brace-expansion": ["brace-expansion@2.0.2", "", { "dependencies": { "balanced-match": "^1.0.0" } }, "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ=="],
|
|
633
|
-
|
|
634
632
|
"bl/readable-stream/safe-buffer": ["safe-buffer@5.1.2", "", {}, "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g=="],
|
|
635
633
|
|
|
636
634
|
"bl/readable-stream/string_decoder": ["string_decoder@1.1.1", "", { "dependencies": { "safe-buffer": "~5.1.0" } }, "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg=="],
|
|
@@ -638,7 +636,5 @@
|
|
|
638
636
|
"tar-stream/readable-stream/safe-buffer": ["safe-buffer@5.1.2", "", {}, "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g=="],
|
|
639
637
|
|
|
640
638
|
"tar-stream/readable-stream/string_decoder": ["string_decoder@1.1.1", "", { "dependencies": { "safe-buffer": "~5.1.0" } }, "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg=="],
|
|
641
|
-
|
|
642
|
-
"@typescript-eslint/typescript-estree/minimatch/brace-expansion/balanced-match": ["balanced-match@1.0.2", "", {}, "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="],
|
|
643
639
|
}
|
|
644
640
|
}
|