redscript-mc 1.0.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 (272) hide show
  1. package/.github/ISSUE_TEMPLATE/bug_report.md +40 -0
  2. package/.github/ISSUE_TEMPLATE/feature_request.md +31 -0
  3. package/.github/ISSUE_TEMPLATE/wrong_output.md +33 -0
  4. package/.github/PULL_REQUEST_TEMPLATE.md +34 -0
  5. package/.github/workflows/ci.yml +29 -0
  6. package/.github/workflows/publish-extension.yml +35 -0
  7. package/LICENSE +21 -0
  8. package/README.md +261 -0
  9. package/README.zh.md +261 -0
  10. package/dist/__tests__/cli.test.d.ts +1 -0
  11. package/dist/__tests__/cli.test.js +140 -0
  12. package/dist/__tests__/codegen.test.d.ts +1 -0
  13. package/dist/__tests__/codegen.test.js +121 -0
  14. package/dist/__tests__/diagnostics.test.d.ts +4 -0
  15. package/dist/__tests__/diagnostics.test.js +149 -0
  16. package/dist/__tests__/e2e.test.d.ts +6 -0
  17. package/dist/__tests__/e2e.test.js +1528 -0
  18. package/dist/__tests__/lexer.test.d.ts +1 -0
  19. package/dist/__tests__/lexer.test.js +316 -0
  20. package/dist/__tests__/lowering.test.d.ts +1 -0
  21. package/dist/__tests__/lowering.test.js +819 -0
  22. package/dist/__tests__/mc-integration.test.d.ts +12 -0
  23. package/dist/__tests__/mc-integration.test.js +395 -0
  24. package/dist/__tests__/mc-syntax.test.d.ts +1 -0
  25. package/dist/__tests__/mc-syntax.test.js +112 -0
  26. package/dist/__tests__/nbt.test.d.ts +1 -0
  27. package/dist/__tests__/nbt.test.js +82 -0
  28. package/dist/__tests__/optimizer-advanced.test.d.ts +1 -0
  29. package/dist/__tests__/optimizer-advanced.test.js +124 -0
  30. package/dist/__tests__/optimizer.test.d.ts +1 -0
  31. package/dist/__tests__/optimizer.test.js +118 -0
  32. package/dist/__tests__/parser.test.d.ts +1 -0
  33. package/dist/__tests__/parser.test.js +717 -0
  34. package/dist/__tests__/repl.test.d.ts +1 -0
  35. package/dist/__tests__/repl.test.js +27 -0
  36. package/dist/__tests__/runtime.test.d.ts +1 -0
  37. package/dist/__tests__/runtime.test.js +276 -0
  38. package/dist/__tests__/structure-optimizer.test.d.ts +1 -0
  39. package/dist/__tests__/structure-optimizer.test.js +33 -0
  40. package/dist/__tests__/typechecker.test.d.ts +1 -0
  41. package/dist/__tests__/typechecker.test.js +364 -0
  42. package/dist/ast/types.d.ts +357 -0
  43. package/dist/ast/types.js +9 -0
  44. package/dist/cli.d.ts +11 -0
  45. package/dist/cli.js +407 -0
  46. package/dist/codegen/cmdblock/index.d.ts +26 -0
  47. package/dist/codegen/cmdblock/index.js +45 -0
  48. package/dist/codegen/mcfunction/index.d.ts +34 -0
  49. package/dist/codegen/mcfunction/index.js +413 -0
  50. package/dist/codegen/structure/index.d.ts +18 -0
  51. package/dist/codegen/structure/index.js +249 -0
  52. package/dist/compile.d.ts +30 -0
  53. package/dist/compile.js +152 -0
  54. package/dist/data/arena/function/__load.mcfunction +6 -0
  55. package/dist/data/arena/function/__tick.mcfunction +2 -0
  56. package/dist/data/arena/function/announce_leaders/else_1.mcfunction +3 -0
  57. package/dist/data/arena/function/announce_leaders/foreach_0/merge_2.mcfunction +1 -0
  58. package/dist/data/arena/function/announce_leaders/foreach_0/then_0.mcfunction +3 -0
  59. package/dist/data/arena/function/announce_leaders/foreach_0.mcfunction +7 -0
  60. package/dist/data/arena/function/announce_leaders/foreach_1/merge_2.mcfunction +1 -0
  61. package/dist/data/arena/function/announce_leaders/foreach_1/then_0.mcfunction +4 -0
  62. package/dist/data/arena/function/announce_leaders/foreach_1.mcfunction +6 -0
  63. package/dist/data/arena/function/announce_leaders/merge_2.mcfunction +1 -0
  64. package/dist/data/arena/function/announce_leaders/then_0.mcfunction +4 -0
  65. package/dist/data/arena/function/announce_leaders.mcfunction +6 -0
  66. package/dist/data/arena/function/arena_tick/merge_2.mcfunction +1 -0
  67. package/dist/data/arena/function/arena_tick/then_0.mcfunction +4 -0
  68. package/dist/data/arena/function/arena_tick.mcfunction +11 -0
  69. package/dist/data/counter/function/__load.mcfunction +5 -0
  70. package/dist/data/counter/function/__tick.mcfunction +2 -0
  71. package/dist/data/counter/function/counter_tick/merge_2.mcfunction +1 -0
  72. package/dist/data/counter/function/counter_tick/then_0.mcfunction +3 -0
  73. package/dist/data/counter/function/counter_tick.mcfunction +11 -0
  74. package/dist/data/minecraft/tags/function/load.json +5 -0
  75. package/dist/data/minecraft/tags/function/tick.json +5 -0
  76. package/dist/data/quiz/function/__load.mcfunction +16 -0
  77. package/dist/data/quiz/function/__tick.mcfunction +6 -0
  78. package/dist/data/quiz/function/__trigger_quiz_a_dispatch.mcfunction +4 -0
  79. package/dist/data/quiz/function/__trigger_quiz_b_dispatch.mcfunction +4 -0
  80. package/dist/data/quiz/function/__trigger_quiz_c_dispatch.mcfunction +4 -0
  81. package/dist/data/quiz/function/__trigger_quiz_start_dispatch.mcfunction +4 -0
  82. package/dist/data/quiz/function/answer_a.mcfunction +4 -0
  83. package/dist/data/quiz/function/answer_b.mcfunction +4 -0
  84. package/dist/data/quiz/function/answer_c.mcfunction +4 -0
  85. package/dist/data/quiz/function/ask_question/else_1.mcfunction +5 -0
  86. package/dist/data/quiz/function/ask_question/else_4.mcfunction +5 -0
  87. package/dist/data/quiz/function/ask_question/else_7.mcfunction +4 -0
  88. package/dist/data/quiz/function/ask_question/merge_2.mcfunction +1 -0
  89. package/dist/data/quiz/function/ask_question/merge_5.mcfunction +2 -0
  90. package/dist/data/quiz/function/ask_question/merge_8.mcfunction +2 -0
  91. package/dist/data/quiz/function/ask_question/then_0.mcfunction +4 -0
  92. package/dist/data/quiz/function/ask_question/then_3.mcfunction +4 -0
  93. package/dist/data/quiz/function/ask_question/then_6.mcfunction +4 -0
  94. package/dist/data/quiz/function/ask_question.mcfunction +7 -0
  95. package/dist/data/quiz/function/finish_quiz.mcfunction +6 -0
  96. package/dist/data/quiz/function/handle_answer/else_1.mcfunction +5 -0
  97. package/dist/data/quiz/function/handle_answer/else_10.mcfunction +3 -0
  98. package/dist/data/quiz/function/handle_answer/else_16.mcfunction +3 -0
  99. package/dist/data/quiz/function/handle_answer/else_4.mcfunction +3 -0
  100. package/dist/data/quiz/function/handle_answer/else_7.mcfunction +5 -0
  101. package/dist/data/quiz/function/handle_answer/merge_11.mcfunction +2 -0
  102. package/dist/data/quiz/function/handle_answer/merge_14.mcfunction +2 -0
  103. package/dist/data/quiz/function/handle_answer/merge_17.mcfunction +2 -0
  104. package/dist/data/quiz/function/handle_answer/merge_2.mcfunction +8 -0
  105. package/dist/data/quiz/function/handle_answer/merge_5.mcfunction +2 -0
  106. package/dist/data/quiz/function/handle_answer/merge_8.mcfunction +2 -0
  107. package/dist/data/quiz/function/handle_answer/then_0.mcfunction +5 -0
  108. package/dist/data/quiz/function/handle_answer/then_12.mcfunction +5 -0
  109. package/dist/data/quiz/function/handle_answer/then_15.mcfunction +6 -0
  110. package/dist/data/quiz/function/handle_answer/then_3.mcfunction +6 -0
  111. package/dist/data/quiz/function/handle_answer/then_6.mcfunction +5 -0
  112. package/dist/data/quiz/function/handle_answer/then_9.mcfunction +6 -0
  113. package/dist/data/quiz/function/handle_answer.mcfunction +11 -0
  114. package/dist/data/quiz/function/start_quiz.mcfunction +5 -0
  115. package/dist/data/shop/function/__load.mcfunction +7 -0
  116. package/dist/data/shop/function/__tick.mcfunction +3 -0
  117. package/dist/data/shop/function/__trigger_shop_buy_dispatch.mcfunction +4 -0
  118. package/dist/data/shop/function/complete_purchase/else_1.mcfunction +5 -0
  119. package/dist/data/shop/function/complete_purchase/else_4.mcfunction +5 -0
  120. package/dist/data/shop/function/complete_purchase/else_7.mcfunction +3 -0
  121. package/dist/data/shop/function/complete_purchase/merge_2.mcfunction +2 -0
  122. package/dist/data/shop/function/complete_purchase/merge_5.mcfunction +2 -0
  123. package/dist/data/shop/function/complete_purchase/merge_8.mcfunction +2 -0
  124. package/dist/data/shop/function/complete_purchase/then_0.mcfunction +4 -0
  125. package/dist/data/shop/function/complete_purchase/then_3.mcfunction +4 -0
  126. package/dist/data/shop/function/complete_purchase/then_6.mcfunction +4 -0
  127. package/dist/data/shop/function/complete_purchase.mcfunction +7 -0
  128. package/dist/data/shop/function/handle_shop_trigger.mcfunction +3 -0
  129. package/dist/data/turret/function/__load.mcfunction +5 -0
  130. package/dist/data/turret/function/__tick.mcfunction +4 -0
  131. package/dist/data/turret/function/__trigger_deploy_turret_dispatch.mcfunction +4 -0
  132. package/dist/data/turret/function/deploy_turret.mcfunction +8 -0
  133. package/dist/data/turret/function/turret_tick/at_1.mcfunction +2 -0
  134. package/dist/data/turret/function/turret_tick/foreach_0.mcfunction +2 -0
  135. package/dist/data/turret/function/turret_tick/foreach_2.mcfunction +2 -0
  136. package/dist/data/turret/function/turret_tick/tick_body.mcfunction +3 -0
  137. package/dist/data/turret/function/turret_tick/tick_skip.mcfunction +1 -0
  138. package/dist/data/turret/function/turret_tick.mcfunction +5 -0
  139. package/dist/diagnostics/index.d.ts +44 -0
  140. package/dist/diagnostics/index.js +140 -0
  141. package/dist/index.d.ts +53 -0
  142. package/dist/index.js +126 -0
  143. package/dist/ir/builder.d.ts +32 -0
  144. package/dist/ir/builder.js +99 -0
  145. package/dist/ir/types.d.ts +117 -0
  146. package/dist/ir/types.js +15 -0
  147. package/dist/lexer/index.d.ts +36 -0
  148. package/dist/lexer/index.js +458 -0
  149. package/dist/lowering/index.d.ts +106 -0
  150. package/dist/lowering/index.js +2041 -0
  151. package/dist/mc-test/client.d.ts +128 -0
  152. package/dist/mc-test/client.js +174 -0
  153. package/dist/mc-test/runner.d.ts +28 -0
  154. package/dist/mc-test/runner.js +150 -0
  155. package/dist/mc-test/setup.d.ts +11 -0
  156. package/dist/mc-test/setup.js +98 -0
  157. package/dist/mc-validator/index.d.ts +17 -0
  158. package/dist/mc-validator/index.js +322 -0
  159. package/dist/nbt/index.d.ts +86 -0
  160. package/dist/nbt/index.js +250 -0
  161. package/dist/optimizer/commands.d.ts +36 -0
  162. package/dist/optimizer/commands.js +349 -0
  163. package/dist/optimizer/passes.d.ts +34 -0
  164. package/dist/optimizer/passes.js +227 -0
  165. package/dist/optimizer/structure.d.ts +8 -0
  166. package/dist/optimizer/structure.js +344 -0
  167. package/dist/pack.mcmeta +6 -0
  168. package/dist/parser/index.d.ts +76 -0
  169. package/dist/parser/index.js +1193 -0
  170. package/dist/repl.d.ts +16 -0
  171. package/dist/repl.js +165 -0
  172. package/dist/runtime/index.d.ts +101 -0
  173. package/dist/runtime/index.js +1288 -0
  174. package/dist/typechecker/index.d.ts +42 -0
  175. package/dist/typechecker/index.js +629 -0
  176. package/docs/COMPILATION_STATS.md +142 -0
  177. package/docs/IMPLEMENTATION_GUIDE.md +512 -0
  178. package/docs/LANGUAGE_REFERENCE.md +415 -0
  179. package/docs/MC_MAPPING.md +280 -0
  180. package/docs/STRUCTURE_TARGET.md +80 -0
  181. package/docs/mc-reference/commands.md +259 -0
  182. package/editors/vscode/.vscodeignore +10 -0
  183. package/editors/vscode/LICENSE +21 -0
  184. package/editors/vscode/README.md +78 -0
  185. package/editors/vscode/build.mjs +28 -0
  186. package/editors/vscode/icon.png +0 -0
  187. package/editors/vscode/mcfunction-language-configuration.json +28 -0
  188. package/editors/vscode/out/extension.js +7236 -0
  189. package/editors/vscode/package-lock.json +566 -0
  190. package/editors/vscode/package.json +137 -0
  191. package/editors/vscode/redscript-language-configuration.json +28 -0
  192. package/editors/vscode/snippets/redscript.json +114 -0
  193. package/editors/vscode/src/codeactions.ts +89 -0
  194. package/editors/vscode/src/completion.ts +130 -0
  195. package/editors/vscode/src/extension.ts +239 -0
  196. package/editors/vscode/src/hover.ts +1120 -0
  197. package/editors/vscode/src/symbols.ts +207 -0
  198. package/editors/vscode/syntaxes/mcfunction.tmLanguage.json +740 -0
  199. package/editors/vscode/syntaxes/redscript.tmLanguage.json +357 -0
  200. package/editors/vscode/tsconfig.json +13 -0
  201. package/jest.config.js +5 -0
  202. package/package.json +38 -0
  203. package/src/__tests__/cli.test.ts +130 -0
  204. package/src/__tests__/codegen.test.ts +128 -0
  205. package/src/__tests__/diagnostics.test.ts +195 -0
  206. package/src/__tests__/e2e.test.ts +1721 -0
  207. package/src/__tests__/fixtures/mc-commands-1.21.4.json +18734 -0
  208. package/src/__tests__/formatter.test.ts +46 -0
  209. package/src/__tests__/lexer.test.ts +356 -0
  210. package/src/__tests__/lowering.test.ts +962 -0
  211. package/src/__tests__/mc-integration.test.ts +409 -0
  212. package/src/__tests__/mc-syntax.test.ts +96 -0
  213. package/src/__tests__/nbt.test.ts +58 -0
  214. package/src/__tests__/optimizer-advanced.test.ts +144 -0
  215. package/src/__tests__/optimizer.test.ts +129 -0
  216. package/src/__tests__/parser.test.ts +800 -0
  217. package/src/__tests__/repl.test.ts +33 -0
  218. package/src/__tests__/runtime.test.ts +289 -0
  219. package/src/__tests__/structure-optimizer.test.ts +38 -0
  220. package/src/__tests__/typechecker.test.ts +395 -0
  221. package/src/ast/types.ts +248 -0
  222. package/src/cli.ts +445 -0
  223. package/src/codegen/cmdblock/index.ts +63 -0
  224. package/src/codegen/mcfunction/index.ts +471 -0
  225. package/src/codegen/structure/index.ts +305 -0
  226. package/src/compile.ts +188 -0
  227. package/src/diagnostics/index.ts +186 -0
  228. package/src/examples/README.md +77 -0
  229. package/src/examples/SHOWCASE_GAME.md +43 -0
  230. package/src/examples/arena.rs +44 -0
  231. package/src/examples/counter.rs +12 -0
  232. package/src/examples/pvp_arena.rs +131 -0
  233. package/src/examples/quiz.rs +90 -0
  234. package/src/examples/rpg.rs +13 -0
  235. package/src/examples/shop.rs +30 -0
  236. package/src/examples/showcase_game.rs +552 -0
  237. package/src/examples/stdlib_demo.rs +181 -0
  238. package/src/examples/turret.rs +27 -0
  239. package/src/examples/world_manager.rs +23 -0
  240. package/src/formatter/index.ts +22 -0
  241. package/src/index.ts +161 -0
  242. package/src/ir/builder.ts +114 -0
  243. package/src/ir/types.ts +119 -0
  244. package/src/lexer/index.ts +555 -0
  245. package/src/lowering/index.ts +2406 -0
  246. package/src/mc-test/client.ts +259 -0
  247. package/src/mc-test/runner.ts +140 -0
  248. package/src/mc-test/setup.ts +70 -0
  249. package/src/mc-validator/index.ts +367 -0
  250. package/src/nbt/index.ts +321 -0
  251. package/src/optimizer/commands.ts +416 -0
  252. package/src/optimizer/passes.ts +233 -0
  253. package/src/optimizer/structure.ts +441 -0
  254. package/src/parser/index.ts +1437 -0
  255. package/src/repl.ts +165 -0
  256. package/src/runtime/index.ts +1403 -0
  257. package/src/stdlib/README.md +156 -0
  258. package/src/stdlib/combat.rs +20 -0
  259. package/src/stdlib/cooldown.rs +45 -0
  260. package/src/stdlib/math.rs +49 -0
  261. package/src/stdlib/mobs.rs +99 -0
  262. package/src/stdlib/player.rs +29 -0
  263. package/src/stdlib/strings.rs +7 -0
  264. package/src/stdlib/timer.rs +51 -0
  265. package/src/templates/README.md +126 -0
  266. package/src/templates/combat.rs +96 -0
  267. package/src/templates/economy.rs +40 -0
  268. package/src/templates/mini-game-framework.rs +117 -0
  269. package/src/templates/quest.rs +78 -0
  270. package/src/test_programs/zombie_game.rs +25 -0
  271. package/src/typechecker/index.ts +737 -0
  272. package/tsconfig.json +16 -0
@@ -0,0 +1,46 @@
1
+ import { format } from '../formatter'
2
+
3
+ describe('formatter', () => {
4
+ it('normalizes indentation to 4 spaces', () => {
5
+ const input = 'fn main() {\n let x: int = 1;\n}'
6
+ const result = format(input)
7
+ expect(result).toBe('fn main() {\n let x: int = 1;\n}\n')
8
+ })
9
+
10
+ it('handles nested blocks', () => {
11
+ const input = 'fn main() {\nif true {\nlet x: int = 1;\n}\n}'
12
+ const result = format(input)
13
+ expect(result).toBe(
14
+ 'fn main() {\n if true {\n let x: int = 1;\n }\n}\n'
15
+ )
16
+ })
17
+
18
+ it('trims trailing whitespace', () => {
19
+ const input = 'fn main() { \n let x: int = 1; \n} '
20
+ const result = format(input)
21
+ expect(result).toBe('fn main() {\n let x: int = 1;\n}\n')
22
+ })
23
+
24
+ it('ensures single newline at end of file', () => {
25
+ const input = 'fn main() {\n}\n\n\n'
26
+ const result = format(input)
27
+ expect(result).toBe('fn main() {\n}\n')
28
+ })
29
+
30
+ it('preserves blank lines', () => {
31
+ const input = 'fn a() {\n}\n\nfn b() {\n}'
32
+ const result = format(input)
33
+ expect(result).toBe('fn a() {\n}\n\nfn b() {\n}\n')
34
+ })
35
+
36
+ it('handles already formatted code', () => {
37
+ const input = 'fn main() {\n let x: int = 1;\n}\n'
38
+ const result = format(input)
39
+ expect(result).toBe(input)
40
+ })
41
+
42
+ it('handles empty input', () => {
43
+ expect(format('')).toBe('\n')
44
+ expect(format('\n\n')).toBe('\n')
45
+ })
46
+ })
@@ -0,0 +1,356 @@
1
+ import { Lexer, Token, TokenKind } from '../lexer'
2
+
3
+ function tokenize(source: string): Token[] {
4
+ return new Lexer(source).tokenize()
5
+ }
6
+
7
+ function kinds(tokens: Token[]): TokenKind[] {
8
+ return tokens.map(t => t.kind)
9
+ }
10
+
11
+ describe('Lexer', () => {
12
+ describe('keywords', () => {
13
+ it('recognizes all keywords', () => {
14
+ const tokens = tokenize('fn let const if else while for foreach match return as at in struct enum trigger namespace')
15
+ expect(kinds(tokens)).toEqual([
16
+ 'fn', 'let', 'const', 'if', 'else', 'while', 'for', 'foreach', 'match',
17
+ 'return', 'as', 'at', 'in', 'struct', 'enum', 'trigger', 'namespace', 'eof'
18
+ ])
19
+ })
20
+
21
+ it('recognizes type keywords', () => {
22
+ const tokens = tokenize('int bool float string void BlockPos')
23
+ expect(kinds(tokens)).toEqual(['int', 'bool', 'float', 'string', 'void', 'BlockPos', 'eof'])
24
+ })
25
+
26
+ it('recognizes boolean literals', () => {
27
+ const tokens = tokenize('true false')
28
+ expect(kinds(tokens)).toEqual(['true', 'false', 'eof'])
29
+ })
30
+ })
31
+
32
+ describe('identifiers', () => {
33
+ it('tokenizes simple identifiers', () => {
34
+ const tokens = tokenize('foo bar_baz _private x1')
35
+ expect(tokens.map(t => [t.kind, t.value])).toEqual([
36
+ ['ident', 'foo'],
37
+ ['ident', 'bar_baz'],
38
+ ['ident', '_private'],
39
+ ['ident', 'x1'],
40
+ ['eof', ''],
41
+ ])
42
+ })
43
+ })
44
+
45
+ describe('literals', () => {
46
+ it('tokenizes integer literals', () => {
47
+ const tokens = tokenize('42 0 123')
48
+ expect(tokens.map(t => [t.kind, t.value])).toEqual([
49
+ ['int_lit', '42'],
50
+ ['int_lit', '0'],
51
+ ['int_lit', '123'],
52
+ ['eof', ''],
53
+ ])
54
+ })
55
+
56
+ it('tokenizes float literals', () => {
57
+ const tokens = tokenize('3.14 0.5 10.0')
58
+ expect(tokens.map(t => [t.kind, t.value])).toEqual([
59
+ ['float_lit', '3.14'],
60
+ ['float_lit', '0.5'],
61
+ ['float_lit', '10.0'],
62
+ ['eof', ''],
63
+ ])
64
+ })
65
+
66
+ it('tokenizes string literals', () => {
67
+ const tokens = tokenize('"hello" "world" "with \\"quotes\\""')
68
+ expect(tokens.map(t => [t.kind, t.value])).toEqual([
69
+ ['string_lit', 'hello'],
70
+ ['string_lit', 'world'],
71
+ ['string_lit', 'with "quotes"'],
72
+ ['eof', ''],
73
+ ])
74
+ })
75
+
76
+ it('tokenizes interpolated strings as a single string token', () => {
77
+ const tokens = tokenize('"hello ${name + 1}"')
78
+ expect(tokens.map(t => [t.kind, t.value])).toEqual([
79
+ ['string_lit', 'hello ${name + 1}'],
80
+ ['eof', ''],
81
+ ])
82
+ })
83
+
84
+ it('tokenizes byte literals (b suffix)', () => {
85
+ const tokens = tokenize('20b 0B 127b')
86
+ expect(tokens.map(t => [t.kind, t.value])).toEqual([
87
+ ['byte_lit', '20b'],
88
+ ['byte_lit', '0B'],
89
+ ['byte_lit', '127b'],
90
+ ['eof', ''],
91
+ ])
92
+ })
93
+
94
+ it('tokenizes short literals (s suffix)', () => {
95
+ const tokens = tokenize('100s 0S 32767s')
96
+ expect(tokens.map(t => [t.kind, t.value])).toEqual([
97
+ ['short_lit', '100s'],
98
+ ['short_lit', '0S'],
99
+ ['short_lit', '32767s'],
100
+ ['eof', ''],
101
+ ])
102
+ })
103
+
104
+ it('tokenizes long literals (L suffix)', () => {
105
+ const tokens = tokenize('1000L 0l 999999L')
106
+ expect(tokens.map(t => [t.kind, t.value])).toEqual([
107
+ ['long_lit', '1000L'],
108
+ ['long_lit', '0l'],
109
+ ['long_lit', '999999L'],
110
+ ['eof', ''],
111
+ ])
112
+ })
113
+
114
+ it('tokenizes float literals with f suffix', () => {
115
+ const tokens = tokenize('3.14f 0.5F 10.0f')
116
+ expect(tokens.map(t => [t.kind, t.value])).toEqual([
117
+ ['float_lit', '3.14f'],
118
+ ['float_lit', '0.5F'],
119
+ ['float_lit', '10.0f'],
120
+ ['eof', ''],
121
+ ])
122
+ })
123
+
124
+ it('tokenizes double literals (d suffix)', () => {
125
+ const tokens = tokenize('3.14d 0.5D 10.0d')
126
+ expect(tokens.map(t => [t.kind, t.value])).toEqual([
127
+ ['double_lit', '3.14d'],
128
+ ['double_lit', '0.5D'],
129
+ ['double_lit', '10.0d'],
130
+ ['eof', ''],
131
+ ])
132
+ })
133
+
134
+ it('tokenizes integer with f/d suffix as float/double', () => {
135
+ const tokens = tokenize('5f 10d')
136
+ expect(tokens.map(t => [t.kind, t.value])).toEqual([
137
+ ['float_lit', '5f'],
138
+ ['double_lit', '10d'],
139
+ ['eof', ''],
140
+ ])
141
+ })
142
+
143
+ it('does not treat suffix-like letters in identifiers as NBT suffixes', () => {
144
+ // 1b2 should not be byte_lit — the 'b' is followed by a digit
145
+ const tokens = tokenize('1b2')
146
+ expect(tokens.map(t => [t.kind, t.value])).toEqual([
147
+ ['int_lit', '1'],
148
+ ['ident', 'b2'],
149
+ ['eof', ''],
150
+ ])
151
+ })
152
+
153
+ it('tokenizes range literals', () => {
154
+ const tokens = tokenize('..5 1.. 1..10')
155
+ expect(tokens.map(t => [t.kind, t.value])).toEqual([
156
+ ['range_lit', '..5'],
157
+ ['range_lit', '1..'],
158
+ ['range_lit', '1..10'],
159
+ ['eof', ''],
160
+ ])
161
+ })
162
+ })
163
+
164
+ describe('operators', () => {
165
+ it('tokenizes arithmetic operators', () => {
166
+ const tokens = tokenize('+ - * / % ~ ^')
167
+ expect(kinds(tokens)).toEqual(['+', '-', '*', '/', '%', '~', '^', 'eof'])
168
+ })
169
+
170
+ it('tokenizes comparison operators', () => {
171
+ const tokens = tokenize('== != < <= > >=')
172
+ expect(kinds(tokens)).toEqual(['==', '!=', '<', '<=', '>', '>=', 'eof'])
173
+ })
174
+
175
+ it('tokenizes logical operators', () => {
176
+ const tokens = tokenize('&& || !')
177
+ expect(kinds(tokens)).toEqual(['&&', '||', '!', 'eof'])
178
+ })
179
+
180
+ it('tokenizes assignment operators', () => {
181
+ const tokens = tokenize('= += -= *= /= %=')
182
+ expect(kinds(tokens)).toEqual(['=', '+=', '-=', '*=', '/=', '%=', 'eof'])
183
+ })
184
+
185
+ it('tokenizes arrow operator', () => {
186
+ const tokens = tokenize('->')
187
+ expect(kinds(tokens)).toEqual(['->', 'eof'])
188
+ })
189
+
190
+ it('tokenizes fat arrow operator', () => {
191
+ const tokens = tokenize('=>')
192
+ expect(kinds(tokens)).toEqual(['=>', 'eof'])
193
+ })
194
+ })
195
+
196
+ describe('delimiters', () => {
197
+ it('tokenizes all delimiters', () => {
198
+ const tokens = tokenize('{ } ( ) [ ] , ; : .')
199
+ expect(kinds(tokens)).toEqual(['{', '}', '(', ')', '[', ']', ',', ';', ':', '.', 'eof'])
200
+ })
201
+ })
202
+
203
+ describe('selectors', () => {
204
+ it('tokenizes simple selectors', () => {
205
+ const tokens = tokenize('@a @e @s @p @r @n')
206
+ expect(tokens.filter(t => t.kind !== 'eof').map(t => [t.kind, t.value])).toEqual([
207
+ ['selector', '@a'],
208
+ ['selector', '@e'],
209
+ ['selector', '@s'],
210
+ ['selector', '@p'],
211
+ ['selector', '@r'],
212
+ ['selector', '@n'],
213
+ ])
214
+ })
215
+
216
+ it('tokenizes selectors with parameters', () => {
217
+ const tokens = tokenize('@e[type=zombie] @a[distance=..5]')
218
+ expect(tokens.filter(t => t.kind !== 'eof').map(t => [t.kind, t.value])).toEqual([
219
+ ['selector', '@e[type=zombie]'],
220
+ ['selector', '@a[distance=..5]'],
221
+ ])
222
+ })
223
+
224
+ it('tokenizes selectors with complex NBT', () => {
225
+ const tokens = tokenize('@e[type=zombie, nbt={NoAI:1b}]')
226
+ expect(tokens.filter(t => t.kind !== 'eof').map(t => [t.kind, t.value])).toEqual([
227
+ ['selector', '@e[type=zombie, nbt={NoAI:1b}]'],
228
+ ])
229
+ })
230
+
231
+ it('handles nested braces in selector NBT', () => {
232
+ const tokens = tokenize('@e[nbt={Items:[{id:"stone"}]}]')
233
+ expect(tokens.filter(t => t.kind !== 'eof').map(t => [t.kind, t.value])).toEqual([
234
+ ['selector', '@e[nbt={Items:[{id:"stone"}]}]'],
235
+ ])
236
+ })
237
+ })
238
+
239
+ describe('decorators', () => {
240
+ it('tokenizes simple decorators', () => {
241
+ const tokens = tokenize('@tick @on_trigger')
242
+ expect(tokens.filter(t => t.kind !== 'eof').map(t => [t.kind, t.value])).toEqual([
243
+ ['decorator', '@tick'],
244
+ ['decorator', '@on_trigger'],
245
+ ])
246
+ })
247
+
248
+ it('tokenizes decorators with arguments', () => {
249
+ const tokens = tokenize('@tick(rate=20)')
250
+ expect(tokens.filter(t => t.kind !== 'eof').map(t => [t.kind, t.value])).toEqual([
251
+ ['decorator', '@tick(rate=20)'],
252
+ ])
253
+ })
254
+ })
255
+
256
+ describe('raw commands', () => {
257
+ it('tokenizes raw command', () => {
258
+ const tokens = tokenize('raw("say hello")')
259
+ expect(tokens.filter(t => t.kind !== 'eof').map(t => [t.kind, t.value])).toEqual([
260
+ ['raw_cmd', 'say hello'],
261
+ ])
262
+ })
263
+ })
264
+
265
+ describe('comments', () => {
266
+ it('skips line comments', () => {
267
+ const tokens = tokenize('let x = 5 // this is a comment\nlet y = 10')
268
+ expect(kinds(tokens)).toEqual(['let', 'ident', '=', 'int_lit', 'let', 'ident', '=', 'int_lit', 'eof'])
269
+ })
270
+ })
271
+
272
+ describe('complex expressions', () => {
273
+ it('tokenizes function declaration', () => {
274
+ const source = 'fn add(a: int, b: int) -> int { return a + b; }'
275
+ const tokens = tokenize(source)
276
+ expect(kinds(tokens)).toEqual([
277
+ 'fn', 'ident', '(', 'ident', ':', 'int', ',', 'ident', ':', 'int', ')',
278
+ '->', 'int', '{', 'return', 'ident', '+', 'ident', ';', '}', 'eof'
279
+ ])
280
+ })
281
+
282
+ it('tokenizes foreach statement', () => {
283
+ const source = 'foreach (z in @e[type=zombie]) { kill(z); }'
284
+ const tokens = tokenize(source)
285
+ expect(kinds(tokens)).toEqual([
286
+ 'foreach', '(', 'ident', 'in', 'selector', ')', '{', 'ident', '(', 'ident', ')', ';', '}', 'eof'
287
+ ])
288
+ })
289
+
290
+ it('tokenizes decorated function', () => {
291
+ const source = '@tick(rate=20)\nfn heartbeat() { say("alive"); }'
292
+ const tokens = tokenize(source)
293
+ expect(kinds(tokens)).toEqual([
294
+ 'decorator', 'fn', 'ident', '(', ')', '{', 'ident', '(', 'string_lit', ')', ';', '}', 'eof'
295
+ ])
296
+ })
297
+ })
298
+
299
+ describe('line/column tracking', () => {
300
+ it('tracks line and column correctly', () => {
301
+ const source = 'let x\nlet y'
302
+ const tokens = tokenize(source)
303
+ expect(tokens[0]).toMatchObject({ kind: 'let', line: 1, col: 1 })
304
+ expect(tokens[1]).toMatchObject({ kind: 'ident', value: 'x', line: 1, col: 5 })
305
+ expect(tokens[2]).toMatchObject({ kind: 'let', line: 2, col: 1 })
306
+ expect(tokens[3]).toMatchObject({ kind: 'ident', value: 'y', line: 2, col: 5 })
307
+ })
308
+ })
309
+
310
+ describe('edge cases', () => {
311
+ it('handles empty input', () => {
312
+ const tokens = tokenize('')
313
+ expect(kinds(tokens)).toEqual(['eof'])
314
+ })
315
+
316
+ it('handles whitespace only', () => {
317
+ const tokens = tokenize(' \n\t ')
318
+ expect(kinds(tokens)).toEqual(['eof'])
319
+ })
320
+
321
+ it('distinguishes selector from decorator', () => {
322
+ // @a is selector (single char followed by non-letter)
323
+ // @aa would be decorator
324
+ const tokens = tokenize('@a @aa')
325
+ expect(tokens.filter(t => t.kind !== 'eof').map(t => [t.kind, t.value])).toEqual([
326
+ ['selector', '@a'],
327
+ ['decorator', '@aa'],
328
+ ])
329
+ })
330
+ })
331
+ })
332
+
333
+ describe('Block comments', () => {
334
+ it('skips single-line block comment', () => {
335
+ const src = `/* comment */ fn test() {}`
336
+ const tokens = tokenize(src)
337
+ expect(tokens.map(t => t.kind)).not.toContain('/')
338
+ expect(tokens.find(t => t.kind === 'fn')).toBeDefined()
339
+ })
340
+
341
+ it('skips multi-line block comment', () => {
342
+ const src = `/**
343
+ * JSDoc comment
344
+ */
345
+ fn test() {}`
346
+ const tokens = tokenize(src)
347
+ expect(tokens.map(t => t.kind)).not.toContain('/')
348
+ expect(tokens.find(t => t.kind === 'fn')).toBeDefined()
349
+ })
350
+
351
+ it('handles block comment with asterisks', () => {
352
+ const src = `/*** stars ***/fn x(){}`
353
+ const tokens = tokenize(src)
354
+ expect(tokens.find(t => t.kind === 'fn')).toBeDefined()
355
+ })
356
+ })