redscript-mc 1.1.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 (63) hide show
  1. package/CHANGELOG.md +54 -0
  2. package/dist/__tests__/cli.test.js +138 -0
  3. package/dist/__tests__/codegen.test.js +25 -0
  4. package/dist/__tests__/e2e.test.js +190 -12
  5. package/dist/__tests__/lexer.test.js +12 -2
  6. package/dist/__tests__/lowering.test.js +164 -9
  7. package/dist/__tests__/mc-integration.test.js +145 -51
  8. package/dist/__tests__/optimizer-advanced.test.js +3 -3
  9. package/dist/__tests__/parser.test.js +80 -0
  10. package/dist/__tests__/runtime.test.js +8 -8
  11. package/dist/__tests__/typechecker.test.js +158 -0
  12. package/dist/ast/types.d.ts +20 -1
  13. package/dist/codegen/mcfunction/index.js +30 -1
  14. package/dist/codegen/structure/index.js +25 -0
  15. package/dist/compile.d.ts +10 -0
  16. package/dist/compile.js +36 -5
  17. package/dist/events/types.d.ts +35 -0
  18. package/dist/events/types.js +59 -0
  19. package/dist/index.js +3 -2
  20. package/dist/ir/types.d.ts +4 -0
  21. package/dist/lexer/index.d.ts +1 -1
  22. package/dist/lexer/index.js +2 -0
  23. package/dist/lowering/index.d.ts +32 -1
  24. package/dist/lowering/index.js +439 -15
  25. package/dist/parser/index.d.ts +2 -0
  26. package/dist/parser/index.js +79 -10
  27. package/dist/typechecker/index.d.ts +17 -0
  28. package/dist/typechecker/index.js +343 -17
  29. package/docs/ENTITY_TYPE_SYSTEM.md +242 -0
  30. package/editors/vscode/CHANGELOG.md +9 -0
  31. package/editors/vscode/out/extension.js +1144 -72
  32. package/editors/vscode/package-lock.json +2 -2
  33. package/editors/vscode/package.json +1 -1
  34. package/package.json +1 -1
  35. package/src/__tests__/cli.test.ts +166 -0
  36. package/src/__tests__/codegen.test.ts +27 -0
  37. package/src/__tests__/e2e.test.ts +201 -12
  38. package/src/__tests__/fixtures/event-test.mcrs +13 -0
  39. package/src/__tests__/fixtures/impl-test.mcrs +46 -0
  40. package/src/__tests__/fixtures/interval-test.mcrs +11 -0
  41. package/src/__tests__/fixtures/is-check-test.mcrs +20 -0
  42. package/src/__tests__/fixtures/timeout-test.mcrs +7 -0
  43. package/src/__tests__/lexer.test.ts +14 -2
  44. package/src/__tests__/lowering.test.ts +178 -9
  45. package/src/__tests__/mc-integration.test.ts +166 -51
  46. package/src/__tests__/optimizer-advanced.test.ts +3 -3
  47. package/src/__tests__/parser.test.ts +91 -5
  48. package/src/__tests__/runtime.test.ts +8 -8
  49. package/src/__tests__/typechecker.test.ts +171 -0
  50. package/src/ast/types.ts +25 -1
  51. package/src/codegen/mcfunction/index.ts +31 -1
  52. package/src/codegen/structure/index.ts +27 -0
  53. package/src/compile.ts +54 -6
  54. package/src/events/types.ts +69 -0
  55. package/src/index.ts +4 -3
  56. package/src/ir/types.ts +4 -0
  57. package/src/lexer/index.ts +3 -1
  58. package/src/lowering/index.ts +528 -16
  59. package/src/parser/index.ts +90 -12
  60. package/src/stdlib/README.md +34 -4
  61. package/src/stdlib/tags.mcrs +951 -0
  62. package/src/stdlib/timer.mcrs +54 -33
  63. package/src/typechecker/index.ts +404 -18
@@ -9,8 +9,8 @@ import { Lexer, type Token, type TokenKind } from '../lexer'
9
9
  import type {
10
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
@@ -135,6 +156,7 @@ export class Parser {
135
156
  const globals: GlobalDecl[] = []
136
157
  const declarations: FnDecl[] = []
137
158
  const structs: StructDecl[] = []
159
+ const implBlocks: ImplBlock[] = []
138
160
  const enums: EnumDecl[] = []
139
161
  const consts: ConstDecl[] = []
140
162
 
@@ -152,6 +174,8 @@ export class Parser {
152
174
  globals.push(this.parseGlobalDecl(true))
153
175
  } else if (this.check('struct')) {
154
176
  structs.push(this.parseStructDecl())
177
+ } else if (this.check('impl')) {
178
+ implBlocks.push(this.parseImplBlock())
155
179
  } else if (this.check('enum')) {
156
180
  enums.push(this.parseEnumDecl())
157
181
  } else if (this.check('const')) {
@@ -161,7 +185,7 @@ export class Parser {
161
185
  }
162
186
  }
163
187
 
164
- return { namespace, globals, declarations, structs, enums, consts }
188
+ return { namespace, globals, declarations, structs, implBlocks, enums, consts }
165
189
  }
166
190
 
167
191
  // -------------------------------------------------------------------------
@@ -219,6 +243,20 @@ export class Parser {
219
243
  return this.withLoc({ name, variants }, enumToken)
220
244
  }
221
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
+
222
260
  private parseConstDecl(): ConstDecl {
223
261
  const constToken = this.expect('const')
224
262
  const name = this.expect('ident').value
@@ -245,17 +283,17 @@ export class Parser {
245
283
  // Function Declaration
246
284
  // -------------------------------------------------------------------------
247
285
 
248
- private parseFnDecl(): FnDecl {
286
+ private parseFnDecl(implTypeName?: string): FnDecl {
249
287
  const decorators = this.parseDecorators()
250
288
 
251
289
  const fnToken = this.expect('fn')
252
290
  const name = this.expect('ident').value
253
291
  this.expect('(')
254
- const params = this.parseParams()
292
+ const params = this.parseParams(implTypeName)
255
293
  this.expect(')')
256
294
 
257
295
  let returnType: TypeNode = { kind: 'named', name: 'void' }
258
- if (this.match('->')) {
296
+ if (this.match('->') || this.match(':')) {
259
297
  returnType = this.parseType()
260
298
  }
261
299
 
@@ -277,7 +315,7 @@ export class Parser {
277
315
  }
278
316
 
279
317
  private parseDecoratorValue(value: string): Decorator {
280
- // Parse @tick or @on_trigger("name") or @on_advancement("story/mine_diamond")
318
+ // Parse @tick, @on(PlayerDeath), or @on_trigger("name")
281
319
  const match = value.match(/^@(\w+)(?:\(([^)]*)\))?$/)
282
320
  if (!match) {
283
321
  this.error(`Invalid decorator: ${value}`)
@@ -292,6 +330,14 @@ export class Parser {
292
330
 
293
331
  const args: Decorator['args'] = {}
294
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
+
295
341
  // Handle @on_trigger("name"), @on_advancement("id"), @on_craft("item"), @on_join_team("team")
296
342
  if (name === 'on_trigger' || name === 'on_advancement' || name === 'on_craft' || name === 'on_join_team') {
297
343
  const strMatch = argsStr.match(/^"([^"]*)"$/)
@@ -328,15 +374,20 @@ export class Parser {
328
374
  return { name, args }
329
375
  }
330
376
 
331
- private parseParams(): Param[] {
377
+ private parseParams(implTypeName?: string): Param[] {
332
378
  const params: Param[] = []
333
379
 
334
380
  if (!this.check(')')) {
335
381
  do {
336
382
  const paramToken = this.expect('ident')
337
383
  const name = paramToken.value
338
- this.expect(':')
339
- const type = this.parseType()
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
+ }
340
391
  let defaultValue: Expr | undefined
341
392
  if (this.match('=')) {
342
393
  defaultValue = this.parseExpr()
@@ -740,6 +791,15 @@ export class Parser {
740
791
  if (prec < minPrec) break
741
792
 
742
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
+
743
803
  const right = this.parseBinaryExpr(prec + 1) // left associative
744
804
  left = this.withLoc(
745
805
  { kind: 'binary', op: op as BinOp | CmpOp | '&&' | '||', left, right },
@@ -766,6 +826,14 @@ export class Parser {
766
826
  return this.parsePostfixExpr()
767
827
  }
768
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
+
769
837
  private isSubtraction(): boolean {
770
838
  // Check if this minus is binary (subtraction) by looking at previous token
771
839
  // If previous was a value (literal, ident, ), ]) it's subtraction
@@ -871,6 +939,16 @@ export class Parser {
871
939
  private parsePrimaryExpr(): Expr {
872
940
  const token = this.peek()
873
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
+
874
952
  if (token.kind === 'ident' && this.peek(1).kind === '=>') {
875
953
  return this.parseSingleParamLambda()
876
954
  }
@@ -70,10 +70,13 @@ Cooldown system using scoreboards.
70
70
  - `is_on_cooldown(target)` → int
71
71
 
72
72
  ### timer.mcrs
73
- Timer utilities.
74
- - `timer_start(name, ticks)`
75
- - `timer_tick()`
76
- - `timer_done(name)` → int
73
+ Timer utilities with an OOP API.
74
+ - `Timer::new(ticks)` → `Timer`
75
+ - `timer.start()`, `timer.pause()`, `timer.reset()`
76
+ - `timer.done()` → bool
77
+ - `timer.elapsed()` → int
78
+ - `timer.remaining()` → int
79
+ - `timer.tick()` — manual tick update; current runtime uses one shared timer slot
77
80
 
78
81
  ### combat.mcrs
79
82
  Combat helpers.
@@ -162,3 +165,30 @@ Player input detection (right click, sneak, look direction).
162
165
  // Position ranges
163
166
  @a[x=-5..5, y=62..68, z=-5..5] // In specific area
164
167
  ```
168
+
169
+ ### tags.mcrs
170
+ Minecraft Java Edition tag constants generated from the Minecraft Fandom tag list.
171
+
172
+ #### Coverage
173
+ - 171 `BLOCK_*` constants for Java block tags
174
+ - 14 `ENTITY_*` constants for Java entity type tags
175
+ - 99 `ITEM_*` constants for Java item tags
176
+ - 2 `FLUID_*` constants for Java fluid tags
177
+ - 27 `DAMAGE_*` constants for Java damage type tags
178
+
179
+ #### Naming
180
+ - Constants use `SCREAMING_SNAKE_CASE`
181
+ - Each constant is prefixed by category: `BLOCK_`, `ENTITY_`, `ITEM_`, `FLUID_`, `DAMAGE_`
182
+ - Each value is the full tag selector string, for example `#minecraft:mineable/axe`
183
+
184
+ #### Usage
185
+ ```mcrs
186
+ import "stdlib/tags.mcrs"
187
+
188
+ // Select skeleton variants
189
+ kill(@e[type=ENTITY_SKELETONS]);
190
+
191
+ // Use block and item tags in your own helpers
192
+ const LOGS: string = BLOCK_LOGS;
193
+ const SWORDS: string = ITEM_SWORDS;
194
+ ```