redscript-mc 1.0.0 → 1.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.github/ISSUE_TEMPLATE/bug_report.yml +72 -0
- package/.github/ISSUE_TEMPLATE/feature_request.yml +57 -0
- package/.github/PULL_REQUEST_TEMPLATE.md +17 -25
- package/CHANGELOG.md +112 -0
- package/CONTRIBUTING.md +140 -0
- package/README.md +28 -19
- package/README.zh.md +28 -19
- package/dist/__tests__/cli.test.js +148 -10
- package/dist/__tests__/codegen.test.js +26 -1
- package/dist/__tests__/diagnostics.test.js +5 -5
- package/dist/__tests__/e2e.test.js +336 -17
- package/dist/__tests__/formatter.test.d.ts +1 -0
- package/dist/__tests__/formatter.test.js +40 -0
- package/dist/__tests__/lexer.test.js +12 -2
- package/dist/__tests__/lowering.test.js +200 -12
- package/dist/__tests__/mc-integration.test.js +370 -31
- package/dist/__tests__/mc-syntax.test.js +3 -3
- package/dist/__tests__/nbt.test.js +2 -2
- package/dist/__tests__/optimizer-advanced.test.js +5 -5
- package/dist/__tests__/parser.test.js +80 -0
- package/dist/__tests__/runtime.test.js +9 -9
- package/dist/__tests__/typechecker.test.js +158 -0
- package/dist/ast/types.d.ts +40 -3
- package/dist/cli.js +25 -7
- package/dist/codegen/mcfunction/index.d.ts +1 -1
- package/dist/codegen/mcfunction/index.js +38 -3
- package/dist/codegen/structure/index.js +32 -1
- package/dist/compile.d.ts +10 -0
- package/dist/compile.js +36 -5
- package/dist/events/types.d.ts +35 -0
- package/dist/events/types.js +59 -0
- package/dist/formatter/index.d.ts +1 -0
- package/dist/formatter/index.js +26 -0
- package/dist/index.js +3 -2
- package/dist/ir/builder.d.ts +2 -1
- package/dist/ir/types.d.ts +11 -2
- package/dist/ir/types.js +1 -1
- package/dist/lexer/index.d.ts +1 -1
- package/dist/lexer/index.js +2 -0
- package/dist/lowering/index.d.ts +34 -1
- package/dist/lowering/index.js +622 -23
- package/dist/mc-test/runner.d.ts +2 -2
- package/dist/mc-test/runner.js +3 -3
- package/dist/mc-test/setup.js +2 -2
- package/dist/parser/index.d.ts +4 -0
- package/dist/parser/index.js +153 -16
- package/dist/typechecker/index.d.ts +17 -0
- package/dist/typechecker/index.js +343 -17
- package/docs/COMPILATION_STATS.md +24 -24
- package/docs/ENTITY_TYPE_SYSTEM.md +242 -0
- package/docs/IMPLEMENTATION_GUIDE.md +1 -1
- package/docs/STRUCTURE_TARGET.md +1 -1
- package/editors/vscode/.vscodeignore +1 -0
- package/editors/vscode/CHANGELOG.md +9 -0
- package/editors/vscode/icons/mcrs.svg +7 -0
- package/editors/vscode/icons/redscript-icons.json +10 -0
- package/editors/vscode/out/extension.js +1295 -80
- package/editors/vscode/package-lock.json +2 -2
- package/editors/vscode/package.json +10 -3
- package/editors/vscode/src/hover.ts +55 -2
- package/editors/vscode/src/symbols.ts +42 -0
- package/package.json +1 -1
- package/src/__tests__/cli.test.ts +176 -10
- package/src/__tests__/codegen.test.ts +28 -1
- package/src/__tests__/diagnostics.test.ts +5 -5
- package/src/__tests__/e2e.test.ts +335 -17
- package/src/__tests__/fixtures/event-test.mcrs +13 -0
- package/src/__tests__/fixtures/impl-test.mcrs +46 -0
- package/src/__tests__/fixtures/interval-test.mcrs +11 -0
- package/src/__tests__/fixtures/is-check-test.mcrs +20 -0
- package/src/__tests__/fixtures/timeout-test.mcrs +7 -0
- package/src/__tests__/lexer.test.ts +14 -2
- package/src/__tests__/lowering.test.ts +226 -12
- package/src/__tests__/mc-integration.test.ts +421 -31
- package/src/__tests__/mc-syntax.test.ts +3 -3
- package/src/__tests__/nbt.test.ts +2 -2
- package/src/__tests__/optimizer-advanced.test.ts +5 -5
- package/src/__tests__/parser.test.ts +91 -5
- package/src/__tests__/runtime.test.ts +9 -9
- package/src/__tests__/typechecker.test.ts +171 -0
- package/src/ast/types.ts +44 -3
- package/src/cli.ts +10 -10
- package/src/codegen/mcfunction/index.ts +40 -3
- package/src/codegen/structure/index.ts +35 -1
- package/src/compile.ts +54 -6
- package/src/events/types.ts +69 -0
- package/src/examples/capture_the_flag.mcrs +208 -0
- package/src/examples/{counter.rs → counter.mcrs} +1 -1
- package/src/examples/hunger_games.mcrs +301 -0
- package/src/examples/new_features_demo.mcrs +193 -0
- package/src/examples/parkour_race.mcrs +233 -0
- package/src/examples/rpg.mcrs +13 -0
- package/src/examples/{shop.rs → shop.mcrs} +1 -1
- package/src/examples/{showcase_game.rs → showcase_game.mcrs} +3 -3
- package/src/examples/{turret.rs → turret.mcrs} +1 -1
- package/src/examples/zombie_survival.mcrs +314 -0
- package/src/index.ts +4 -3
- package/src/ir/builder.ts +3 -1
- package/src/ir/types.ts +12 -2
- package/src/lexer/index.ts +3 -1
- package/src/lowering/index.ts +684 -24
- package/src/mc-test/runner.ts +3 -3
- package/src/mc-test/setup.ts +2 -2
- package/src/parser/index.ts +170 -19
- package/src/stdlib/README.md +178 -140
- package/src/stdlib/bossbar.mcrs +68 -0
- package/src/stdlib/{cooldown.rs → cooldown.mcrs} +1 -1
- package/src/stdlib/effects.mcrs +64 -0
- package/src/stdlib/interactions.mcrs +195 -0
- package/src/stdlib/inventory.mcrs +38 -0
- package/src/stdlib/mobs.mcrs +99 -0
- package/src/stdlib/particles.mcrs +52 -0
- package/src/stdlib/sets.mcrs +20 -0
- package/src/stdlib/spawn.mcrs +41 -0
- package/src/stdlib/tags.mcrs +951 -0
- package/src/stdlib/teams.mcrs +68 -0
- package/src/stdlib/timer.mcrs +72 -0
- package/src/stdlib/world.mcrs +92 -0
- package/src/typechecker/index.ts +404 -18
- package/src/examples/rpg.rs +0 -13
- package/src/stdlib/mobs.rs +0 -99
- package/src/stdlib/timer.rs +0 -51
- /package/src/examples/{arena.rs → arena.mcrs} +0 -0
- /package/src/examples/{pvp_arena.rs → pvp_arena.mcrs} +0 -0
- /package/src/examples/{quiz.rs → quiz.mcrs} +0 -0
- /package/src/examples/{stdlib_demo.rs → stdlib_demo.mcrs} +0 -0
- /package/src/examples/{world_manager.rs → world_manager.mcrs} +0 -0
- /package/src/stdlib/{combat.rs → combat.mcrs} +0 -0
- /package/src/stdlib/{math.rs → math.mcrs} +0 -0
- /package/src/stdlib/{player.rs → player.mcrs} +0 -0
- /package/src/stdlib/{strings.rs → strings.mcrs} +0 -0
- /package/src/templates/{combat.rs → combat.mcrs} +0 -0
- /package/src/templates/{economy.rs → economy.mcrs} +0 -0
- /package/src/templates/{mini-game-framework.rs → mini-game-framework.mcrs} +0 -0
- /package/src/templates/{quest.rs → quest.mcrs} +0 -0
- /package/src/test_programs/{zombie_game.rs → zombie_game.mcrs} +0 -0
package/src/mc-test/runner.ts
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* RedScript MC Integration Test Runner
|
|
3
3
|
*
|
|
4
|
-
* Compiles a .
|
|
4
|
+
* Compiles a .mcrs file, installs it to a running Paper server,
|
|
5
5
|
* runs test scenarios, and reports results.
|
|
6
6
|
*
|
|
7
7
|
* Usage:
|
|
8
|
-
* npx ts-node src/mc-test/runner.ts src/examples/counter.
|
|
8
|
+
* npx ts-node src/mc-test/runner.ts src/examples/counter.mcrs
|
|
9
9
|
*
|
|
10
10
|
* Requires:
|
|
11
11
|
* - Paper server running with TestHarnessPlugin
|
|
@@ -105,7 +105,7 @@ export async function runMCTests(
|
|
|
105
105
|
if (require.main === module) {
|
|
106
106
|
const sourceFile = process.argv[2]
|
|
107
107
|
if (!sourceFile) {
|
|
108
|
-
console.error('Usage: ts-node runner.ts <source.
|
|
108
|
+
console.error('Usage: ts-node runner.ts <source.mcrs>')
|
|
109
109
|
process.exit(1)
|
|
110
110
|
}
|
|
111
111
|
|
package/src/mc-test/setup.ts
CHANGED
|
@@ -36,11 +36,11 @@ function main() {
|
|
|
36
36
|
// Example files
|
|
37
37
|
const exampleNamespaces = ['counter', 'world_manager']
|
|
38
38
|
for (const ns of exampleNamespaces) {
|
|
39
|
-
const file = path.join(EXAMPLES_DIR, `${ns}.
|
|
39
|
+
const file = path.join(EXAMPLES_DIR, `${ns}.mcrs`)
|
|
40
40
|
if (fs.existsSync(file)) {
|
|
41
41
|
writeFixture(fs.readFileSync(file, 'utf-8'), ns)
|
|
42
42
|
} else {
|
|
43
|
-
console.log(` ⚠ ${ns}.
|
|
43
|
+
console.log(` ⚠ ${ns}.mcrs not found, skipping`)
|
|
44
44
|
}
|
|
45
45
|
}
|
|
46
46
|
|
package/src/parser/index.ts
CHANGED
|
@@ -7,10 +7,10 @@
|
|
|
7
7
|
|
|
8
8
|
import { Lexer, type Token, type TokenKind } from '../lexer'
|
|
9
9
|
import type {
|
|
10
|
-
Block, ConstDecl, Decorator, EntitySelector, Expr, FnDecl, LiteralExpr, Param,
|
|
10
|
+
Block, ConstDecl, Decorator, EntitySelector, Expr, FnDecl, GlobalDecl, LiteralExpr, Param,
|
|
11
11
|
Program, RangeExpr, SelectorFilter, SelectorKind, Span, Stmt, TypeNode, AssignOp,
|
|
12
|
-
StructDecl, StructField, ExecuteSubcommand, EnumDecl, EnumVariant, BlockPosExpr,
|
|
13
|
-
CoordComponent, LambdaParam
|
|
12
|
+
StructDecl, StructField, ExecuteSubcommand, EnumDecl, EnumVariant, BlockPosExpr, ImplBlock,
|
|
13
|
+
CoordComponent, LambdaParam, EntityTypeName
|
|
14
14
|
} from '../ast/types'
|
|
15
15
|
import type { BinOp, CmpOp } from '../ir/types'
|
|
16
16
|
import { DiagnosticError } from '../diagnostics'
|
|
@@ -23,12 +23,33 @@ const PRECEDENCE: Record<string, number> = {
|
|
|
23
23
|
'||': 1,
|
|
24
24
|
'&&': 2,
|
|
25
25
|
'==': 3, '!=': 3,
|
|
26
|
-
'<': 4, '<=': 4, '>': 4, '>=': 4,
|
|
26
|
+
'<': 4, '<=': 4, '>': 4, '>=': 4, 'is': 4,
|
|
27
27
|
'+': 5, '-': 5,
|
|
28
28
|
'*': 6, '/': 6, '%': 6,
|
|
29
29
|
}
|
|
30
30
|
|
|
31
|
-
const BINARY_OPS = new Set(['||', '&&', '==', '!=', '<', '<=', '>', '>=', '+', '-', '*', '/', '%'])
|
|
31
|
+
const BINARY_OPS = new Set(['||', '&&', '==', '!=', '<', '<=', '>', '>=', 'is', '+', '-', '*', '/', '%'])
|
|
32
|
+
|
|
33
|
+
const ENTITY_TYPE_NAMES = new Set<EntityTypeName>([
|
|
34
|
+
'entity',
|
|
35
|
+
'Player',
|
|
36
|
+
'Mob',
|
|
37
|
+
'HostileMob',
|
|
38
|
+
'PassiveMob',
|
|
39
|
+
'Zombie',
|
|
40
|
+
'Skeleton',
|
|
41
|
+
'Creeper',
|
|
42
|
+
'Spider',
|
|
43
|
+
'Enderman',
|
|
44
|
+
'Pig',
|
|
45
|
+
'Cow',
|
|
46
|
+
'Sheep',
|
|
47
|
+
'Chicken',
|
|
48
|
+
'Villager',
|
|
49
|
+
'ArmorStand',
|
|
50
|
+
'Item',
|
|
51
|
+
'Arrow',
|
|
52
|
+
])
|
|
32
53
|
|
|
33
54
|
function computeIsSingle(raw: string): boolean {
|
|
34
55
|
if (/^@[spr](\[|$)/.test(raw)) return true
|
|
@@ -132,8 +153,10 @@ export class Parser {
|
|
|
132
153
|
|
|
133
154
|
parse(defaultNamespace = 'redscript'): Program {
|
|
134
155
|
let namespace = defaultNamespace
|
|
156
|
+
const globals: GlobalDecl[] = []
|
|
135
157
|
const declarations: FnDecl[] = []
|
|
136
158
|
const structs: StructDecl[] = []
|
|
159
|
+
const implBlocks: ImplBlock[] = []
|
|
137
160
|
const enums: EnumDecl[] = []
|
|
138
161
|
const consts: ConstDecl[] = []
|
|
139
162
|
|
|
@@ -147,8 +170,12 @@ export class Parser {
|
|
|
147
170
|
|
|
148
171
|
// Parse struct and function declarations
|
|
149
172
|
while (!this.check('eof')) {
|
|
150
|
-
if (this.check('
|
|
173
|
+
if (this.check('let')) {
|
|
174
|
+
globals.push(this.parseGlobalDecl(true))
|
|
175
|
+
} else if (this.check('struct')) {
|
|
151
176
|
structs.push(this.parseStructDecl())
|
|
177
|
+
} else if (this.check('impl')) {
|
|
178
|
+
implBlocks.push(this.parseImplBlock())
|
|
152
179
|
} else if (this.check('enum')) {
|
|
153
180
|
enums.push(this.parseEnumDecl())
|
|
154
181
|
} else if (this.check('const')) {
|
|
@@ -158,7 +185,7 @@ export class Parser {
|
|
|
158
185
|
}
|
|
159
186
|
}
|
|
160
187
|
|
|
161
|
-
return { namespace, declarations, structs, enums, consts }
|
|
188
|
+
return { namespace, globals, declarations, structs, implBlocks, enums, consts }
|
|
162
189
|
}
|
|
163
190
|
|
|
164
191
|
// -------------------------------------------------------------------------
|
|
@@ -216,6 +243,20 @@ export class Parser {
|
|
|
216
243
|
return this.withLoc({ name, variants }, enumToken)
|
|
217
244
|
}
|
|
218
245
|
|
|
246
|
+
private parseImplBlock(): ImplBlock {
|
|
247
|
+
const implToken = this.expect('impl')
|
|
248
|
+
const typeName = this.expect('ident').value
|
|
249
|
+
this.expect('{')
|
|
250
|
+
|
|
251
|
+
const methods: FnDecl[] = []
|
|
252
|
+
while (!this.check('}') && !this.check('eof')) {
|
|
253
|
+
methods.push(this.parseFnDecl(typeName))
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
this.expect('}')
|
|
257
|
+
return this.withLoc({ kind: 'impl_block', typeName, methods }, implToken)
|
|
258
|
+
}
|
|
259
|
+
|
|
219
260
|
private parseConstDecl(): ConstDecl {
|
|
220
261
|
const constToken = this.expect('const')
|
|
221
262
|
const name = this.expect('ident').value
|
|
@@ -227,21 +268,32 @@ export class Parser {
|
|
|
227
268
|
return this.withLoc({ name, type, value }, constToken)
|
|
228
269
|
}
|
|
229
270
|
|
|
271
|
+
private parseGlobalDecl(mutable: boolean): GlobalDecl {
|
|
272
|
+
const token = this.advance() // consume 'let'
|
|
273
|
+
const name = this.expect('ident').value
|
|
274
|
+
this.expect(':')
|
|
275
|
+
const type = this.parseType()
|
|
276
|
+
this.expect('=')
|
|
277
|
+
const init = this.parseExpr()
|
|
278
|
+
this.expect(';')
|
|
279
|
+
return this.withLoc({ kind: 'global', name, type, init, mutable }, token)
|
|
280
|
+
}
|
|
281
|
+
|
|
230
282
|
// -------------------------------------------------------------------------
|
|
231
283
|
// Function Declaration
|
|
232
284
|
// -------------------------------------------------------------------------
|
|
233
285
|
|
|
234
|
-
private parseFnDecl(): FnDecl {
|
|
286
|
+
private parseFnDecl(implTypeName?: string): FnDecl {
|
|
235
287
|
const decorators = this.parseDecorators()
|
|
236
288
|
|
|
237
289
|
const fnToken = this.expect('fn')
|
|
238
290
|
const name = this.expect('ident').value
|
|
239
291
|
this.expect('(')
|
|
240
|
-
const params = this.parseParams()
|
|
292
|
+
const params = this.parseParams(implTypeName)
|
|
241
293
|
this.expect(')')
|
|
242
294
|
|
|
243
295
|
let returnType: TypeNode = { kind: 'named', name: 'void' }
|
|
244
|
-
if (this.match('->')) {
|
|
296
|
+
if (this.match('->') || this.match(':')) {
|
|
245
297
|
returnType = this.parseType()
|
|
246
298
|
}
|
|
247
299
|
|
|
@@ -263,7 +315,7 @@ export class Parser {
|
|
|
263
315
|
}
|
|
264
316
|
|
|
265
317
|
private parseDecoratorValue(value: string): Decorator {
|
|
266
|
-
// Parse @tick
|
|
318
|
+
// Parse @tick, @on(PlayerDeath), or @on_trigger("name")
|
|
267
319
|
const match = value.match(/^@(\w+)(?:\(([^)]*)\))?$/)
|
|
268
320
|
if (!match) {
|
|
269
321
|
this.error(`Invalid decorator: ${value}`)
|
|
@@ -278,6 +330,14 @@ export class Parser {
|
|
|
278
330
|
|
|
279
331
|
const args: Decorator['args'] = {}
|
|
280
332
|
|
|
333
|
+
if (name === 'on') {
|
|
334
|
+
const eventTypeMatch = argsStr.match(/^([A-Za-z_][A-Za-z0-9_]*)$/)
|
|
335
|
+
if (eventTypeMatch) {
|
|
336
|
+
args.eventType = eventTypeMatch[1]
|
|
337
|
+
return { name, args }
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
|
|
281
341
|
// Handle @on_trigger("name"), @on_advancement("id"), @on_craft("item"), @on_join_team("team")
|
|
282
342
|
if (name === 'on_trigger' || name === 'on_advancement' || name === 'on_craft' || name === 'on_join_team') {
|
|
283
343
|
const strMatch = argsStr.match(/^"([^"]*)"$/)
|
|
@@ -314,15 +374,20 @@ export class Parser {
|
|
|
314
374
|
return { name, args }
|
|
315
375
|
}
|
|
316
376
|
|
|
317
|
-
private parseParams(): Param[] {
|
|
377
|
+
private parseParams(implTypeName?: string): Param[] {
|
|
318
378
|
const params: Param[] = []
|
|
319
379
|
|
|
320
380
|
if (!this.check(')')) {
|
|
321
381
|
do {
|
|
322
382
|
const paramToken = this.expect('ident')
|
|
323
383
|
const name = paramToken.value
|
|
324
|
-
|
|
325
|
-
|
|
384
|
+
let type: TypeNode
|
|
385
|
+
if (implTypeName && params.length === 0 && name === 'self' && !this.check(':')) {
|
|
386
|
+
type = { kind: 'struct', name: implTypeName }
|
|
387
|
+
} else {
|
|
388
|
+
this.expect(':')
|
|
389
|
+
type = this.parseType()
|
|
390
|
+
}
|
|
326
391
|
let defaultValue: Expr | undefined
|
|
327
392
|
if (this.match('=')) {
|
|
328
393
|
defaultValue = this.parseExpr()
|
|
@@ -650,15 +715,15 @@ export class Parser {
|
|
|
650
715
|
if (this.peek().kind === 'ident' && this.peek().value === 'entity') {
|
|
651
716
|
this.advance() // consume 'entity'
|
|
652
717
|
}
|
|
653
|
-
const
|
|
654
|
-
subcommands.push({ kind: 'if_entity',
|
|
718
|
+
const selectorOrVar = this.parseSelectorOrVarSelector()
|
|
719
|
+
subcommands.push({ kind: 'if_entity', ...selectorOrVar })
|
|
655
720
|
} else if (this.match('unless')) {
|
|
656
721
|
// Expect 'entity' keyword (as ident) or just parse selector directly
|
|
657
722
|
if (this.peek().kind === 'ident' && this.peek().value === 'entity') {
|
|
658
723
|
this.advance() // consume 'entity'
|
|
659
724
|
}
|
|
660
|
-
const
|
|
661
|
-
subcommands.push({ kind: 'unless_entity',
|
|
725
|
+
const selectorOrVar = this.parseSelectorOrVarSelector()
|
|
726
|
+
subcommands.push({ kind: 'unless_entity', ...selectorOrVar })
|
|
662
727
|
} else if (this.match('in')) {
|
|
663
728
|
const dim = this.expect('ident').value
|
|
664
729
|
subcommands.push({ kind: 'in', dimension: dim })
|
|
@@ -726,6 +791,15 @@ export class Parser {
|
|
|
726
791
|
if (prec < minPrec) break
|
|
727
792
|
|
|
728
793
|
const opToken = this.advance()
|
|
794
|
+
if (op === 'is') {
|
|
795
|
+
const entityType = this.parseEntityTypeName()
|
|
796
|
+
left = this.withLoc(
|
|
797
|
+
{ kind: 'is_check', expr: left, entityType },
|
|
798
|
+
this.getLocToken(left) ?? opToken
|
|
799
|
+
)
|
|
800
|
+
continue
|
|
801
|
+
}
|
|
802
|
+
|
|
729
803
|
const right = this.parseBinaryExpr(prec + 1) // left associative
|
|
730
804
|
left = this.withLoc(
|
|
731
805
|
{ kind: 'binary', op: op as BinOp | CmpOp | '&&' | '||', left, right },
|
|
@@ -752,6 +826,14 @@ export class Parser {
|
|
|
752
826
|
return this.parsePostfixExpr()
|
|
753
827
|
}
|
|
754
828
|
|
|
829
|
+
private parseEntityTypeName(): EntityTypeName {
|
|
830
|
+
const token = this.expect('ident')
|
|
831
|
+
if (ENTITY_TYPE_NAMES.has(token.value as EntityTypeName)) {
|
|
832
|
+
return token.value as EntityTypeName
|
|
833
|
+
}
|
|
834
|
+
this.error(`Unknown entity type '${token.value}'`)
|
|
835
|
+
}
|
|
836
|
+
|
|
755
837
|
private isSubtraction(): boolean {
|
|
756
838
|
// Check if this minus is binary (subtraction) by looking at previous token
|
|
757
839
|
// If previous was a value (literal, ident, ), ]) it's subtraction
|
|
@@ -782,6 +864,10 @@ export class Parser {
|
|
|
782
864
|
'has_tag': '__entity_has_tag',
|
|
783
865
|
'push': '__array_push',
|
|
784
866
|
'pop': '__array_pop',
|
|
867
|
+
'add': 'set_add',
|
|
868
|
+
'contains': 'set_contains',
|
|
869
|
+
'remove': 'set_remove',
|
|
870
|
+
'clear': 'set_clear',
|
|
785
871
|
}
|
|
786
872
|
const internalFn = methodMap[expr.field]
|
|
787
873
|
if (internalFn) {
|
|
@@ -793,7 +879,14 @@ export class Parser {
|
|
|
793
879
|
)
|
|
794
880
|
continue
|
|
795
881
|
}
|
|
796
|
-
|
|
882
|
+
// Generic method sugar: obj.method(args) → method(obj, args)
|
|
883
|
+
const args = this.parseArgs()
|
|
884
|
+
this.expect(')')
|
|
885
|
+
expr = this.withLoc(
|
|
886
|
+
{ kind: 'call', fn: expr.field, args: [expr.obj, ...args] },
|
|
887
|
+
this.getLocToken(expr) ?? openParenToken
|
|
888
|
+
)
|
|
889
|
+
continue
|
|
797
890
|
}
|
|
798
891
|
const args = this.parseArgs()
|
|
799
892
|
this.expect(')')
|
|
@@ -846,6 +939,16 @@ export class Parser {
|
|
|
846
939
|
private parsePrimaryExpr(): Expr {
|
|
847
940
|
const token = this.peek()
|
|
848
941
|
|
|
942
|
+
if (token.kind === 'ident' && this.peek(1).kind === '::') {
|
|
943
|
+
const typeToken = this.advance()
|
|
944
|
+
this.expect('::')
|
|
945
|
+
const methodToken = this.expect('ident')
|
|
946
|
+
this.expect('(')
|
|
947
|
+
const args = this.parseArgs()
|
|
948
|
+
this.expect(')')
|
|
949
|
+
return this.withLoc({ kind: 'static_call', type: typeToken.value, method: methodToken.value, args }, typeToken)
|
|
950
|
+
}
|
|
951
|
+
|
|
849
952
|
if (token.kind === 'ident' && this.peek(1).kind === '=>') {
|
|
850
953
|
return this.parseSingleParamLambda()
|
|
851
954
|
}
|
|
@@ -1308,6 +1411,39 @@ export class Parser {
|
|
|
1308
1411
|
return this.parseSelectorValue(token.value)
|
|
1309
1412
|
}
|
|
1310
1413
|
|
|
1414
|
+
// Parse either a selector (@a[...]) or a variable with filters (p[...])
|
|
1415
|
+
// Returns { selector } for selectors or { varName, filters } for variables
|
|
1416
|
+
private parseSelectorOrVarSelector(): { selector?: EntitySelector, varName?: string, filters?: SelectorFilter } {
|
|
1417
|
+
if (this.check('selector')) {
|
|
1418
|
+
return { selector: this.parseSelector() }
|
|
1419
|
+
}
|
|
1420
|
+
|
|
1421
|
+
// Must be an identifier (variable) possibly with filters
|
|
1422
|
+
const varToken = this.expect('ident')
|
|
1423
|
+
const varName = varToken.value
|
|
1424
|
+
|
|
1425
|
+
// Check for optional filters [...]
|
|
1426
|
+
if (this.check('[')) {
|
|
1427
|
+
this.advance() // consume '['
|
|
1428
|
+
// Collect everything until ']'
|
|
1429
|
+
let filterStr = ''
|
|
1430
|
+
let depth = 1
|
|
1431
|
+
while (depth > 0 && !this.check('eof')) {
|
|
1432
|
+
if (this.check('[')) depth++
|
|
1433
|
+
else if (this.check(']')) depth--
|
|
1434
|
+
if (depth > 0) {
|
|
1435
|
+
filterStr += this.peek().value ?? this.peek().kind
|
|
1436
|
+
this.advance()
|
|
1437
|
+
}
|
|
1438
|
+
}
|
|
1439
|
+
this.expect(']')
|
|
1440
|
+
const filters = this.parseSelectorFilters(filterStr)
|
|
1441
|
+
return { varName, filters }
|
|
1442
|
+
}
|
|
1443
|
+
|
|
1444
|
+
return { varName }
|
|
1445
|
+
}
|
|
1446
|
+
|
|
1311
1447
|
private parseSelectorValue(value: string): EntitySelector {
|
|
1312
1448
|
// Parse @e[type=zombie, distance=..5]
|
|
1313
1449
|
const bracketIndex = value.indexOf('[')
|
|
@@ -1364,6 +1500,21 @@ export class Parser {
|
|
|
1364
1500
|
case 'scores':
|
|
1365
1501
|
filters.scores = this.parseScoresFilter(val)
|
|
1366
1502
|
break
|
|
1503
|
+
case 'x':
|
|
1504
|
+
filters.x = this.parseRangeValue(val)
|
|
1505
|
+
break
|
|
1506
|
+
case 'y':
|
|
1507
|
+
filters.y = this.parseRangeValue(val)
|
|
1508
|
+
break
|
|
1509
|
+
case 'z':
|
|
1510
|
+
filters.z = this.parseRangeValue(val)
|
|
1511
|
+
break
|
|
1512
|
+
case 'x_rotation':
|
|
1513
|
+
filters.x_rotation = this.parseRangeValue(val)
|
|
1514
|
+
break
|
|
1515
|
+
case 'y_rotation':
|
|
1516
|
+
filters.y_rotation = this.parseRangeValue(val)
|
|
1517
|
+
break
|
|
1367
1518
|
}
|
|
1368
1519
|
}
|
|
1369
1520
|
|