redscript-mc 1.2.9 → 1.2.11

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 (219) hide show
  1. package/README.md +35 -33
  2. package/README.zh.md +35 -33
  3. package/demo.gif +0 -0
  4. package/dist/__tests__/mc-integration.test.js +85 -0
  5. package/dist/ast/types.d.ts +84 -2
  6. package/dist/index.d.ts +1 -1
  7. package/dist/index.js +1 -1
  8. package/dist/lexer/index.d.ts +1 -1
  9. package/dist/lexer/index.js +2 -0
  10. package/dist/lowering/index.d.ts +3 -0
  11. package/dist/lowering/index.js +119 -11
  12. package/dist/optimizer/dce.js +6 -0
  13. package/dist/parser/index.d.ts +4 -0
  14. package/dist/parser/index.js +171 -18
  15. package/package.json +1 -1
  16. package/src/__tests__/fixtures/array-test.mcrs +30 -0
  17. package/src/__tests__/fixtures/break-continue-test.mcrs +46 -0
  18. package/src/__tests__/fixtures/enum-test.mcrs +37 -0
  19. package/src/__tests__/fixtures/foreach-at-test.mcrs +33 -0
  20. package/src/__tests__/fixtures/match-range-test.mcrs +45 -0
  21. package/src/__tests__/fixtures/struct-test.mcrs +34 -0
  22. package/src/__tests__/mc-integration.test.ts +97 -0
  23. package/src/ast/types.ts +24 -1
  24. package/src/index.ts +1 -1
  25. package/src/lexer/index.ts +3 -1
  26. package/src/lowering/index.ts +123 -11
  27. package/src/optimizer/dce.ts +6 -0
  28. package/src/parser/index.ts +159 -18
  29. package/test-datapacks/README.md +67 -0
  30. package/test-datapacks/test_control_flow/data/minecraft/tags/function/load.json +5 -0
  31. package/test-datapacks/test_control_flow/data/test_control_flow/function/__load.mcfunction +41 -0
  32. package/test-datapacks/test_control_flow/data/test_control_flow/function/run_control_flow_tests.mcfunction +21 -0
  33. package/test-datapacks/test_control_flow/data/test_control_flow/function/test_break/else_7.mcfunction +3 -0
  34. package/test-datapacks/test_control_flow/data/test_control_flow/function/test_break/else_8.mcfunction +3 -0
  35. package/test-datapacks/test_control_flow/data/test_control_flow/function/test_break/for_body_1.mcfunction +5 -0
  36. package/test-datapacks/test_control_flow/data/test_control_flow/function/test_break/for_check_0.mcfunction +5 -0
  37. package/test-datapacks/test_control_flow/data/test_control_flow/function/test_break/for_continue_2.mcfunction +5 -0
  38. package/test-datapacks/test_control_flow/data/test_control_flow/function/test_break/for_exit_2.mcfunction +5 -0
  39. package/test-datapacks/test_control_flow/data/test_control_flow/function/test_break/for_exit_3.mcfunction +5 -0
  40. package/test-datapacks/test_control_flow/data/test_control_flow/function/test_break/merge_5.mcfunction +5 -0
  41. package/test-datapacks/test_control_flow/data/test_control_flow/function/test_break/merge_6.mcfunction +2 -0
  42. package/test-datapacks/test_control_flow/data/test_control_flow/function/test_break/merge_8.mcfunction +1 -0
  43. package/test-datapacks/test_control_flow/data/test_control_flow/function/test_break/merge_9.mcfunction +1 -0
  44. package/test-datapacks/test_control_flow/data/test_control_flow/function/test_break/then_3.mcfunction +3 -0
  45. package/test-datapacks/test_control_flow/data/test_control_flow/function/test_break/then_4.mcfunction +3 -0
  46. package/test-datapacks/test_control_flow/data/test_control_flow/function/test_break/then_6.mcfunction +3 -0
  47. package/test-datapacks/test_control_flow/data/test_control_flow/function/test_break/then_7.mcfunction +3 -0
  48. package/test-datapacks/test_control_flow/data/test_control_flow/function/test_break.mcfunction +4 -0
  49. package/test-datapacks/test_control_flow/data/test_control_flow/function/test_complex_conditions/else_1.mcfunction +3 -0
  50. package/test-datapacks/test_control_flow/data/test_control_flow/function/test_complex_conditions/else_10.mcfunction +3 -0
  51. package/test-datapacks/test_control_flow/data/test_control_flow/function/test_complex_conditions/else_4.mcfunction +3 -0
  52. package/test-datapacks/test_control_flow/data/test_control_flow/function/test_complex_conditions/else_7.mcfunction +3 -0
  53. package/test-datapacks/test_control_flow/data/test_control_flow/function/test_complex_conditions/merge_11.mcfunction +1 -0
  54. package/test-datapacks/test_control_flow/data/test_control_flow/function/test_complex_conditions/merge_2.mcfunction +9 -0
  55. package/test-datapacks/test_control_flow/data/test_control_flow/function/test_complex_conditions/merge_5.mcfunction +7 -0
  56. package/test-datapacks/test_control_flow/data/test_control_flow/function/test_complex_conditions/merge_8.mcfunction +13 -0
  57. package/test-datapacks/test_control_flow/data/test_control_flow/function/test_complex_conditions/then_0.mcfunction +3 -0
  58. package/test-datapacks/test_control_flow/data/test_control_flow/function/test_complex_conditions/then_3.mcfunction +3 -0
  59. package/test-datapacks/test_control_flow/data/test_control_flow/function/test_complex_conditions/then_6.mcfunction +3 -0
  60. package/test-datapacks/test_control_flow/data/test_control_flow/function/test_complex_conditions/then_9.mcfunction +3 -0
  61. package/test-datapacks/test_control_flow/data/test_control_flow/function/test_complex_conditions.mcfunction +12 -0
  62. package/test-datapacks/test_control_flow/data/test_control_flow/function/test_continue/else_7.mcfunction +3 -0
  63. package/test-datapacks/test_control_flow/data/test_control_flow/function/test_continue/else_8.mcfunction +3 -0
  64. package/test-datapacks/test_control_flow/data/test_control_flow/function/test_continue/for_body_1.mcfunction +7 -0
  65. package/test-datapacks/test_control_flow/data/test_control_flow/function/test_continue/for_check_0.mcfunction +5 -0
  66. package/test-datapacks/test_control_flow/data/test_control_flow/function/test_continue/for_continue_2.mcfunction +5 -0
  67. package/test-datapacks/test_control_flow/data/test_control_flow/function/test_continue/for_exit_2.mcfunction +5 -0
  68. package/test-datapacks/test_control_flow/data/test_control_flow/function/test_continue/for_exit_3.mcfunction +5 -0
  69. package/test-datapacks/test_control_flow/data/test_control_flow/function/test_continue/merge_5.mcfunction +8 -0
  70. package/test-datapacks/test_control_flow/data/test_control_flow/function/test_continue/merge_6.mcfunction +5 -0
  71. package/test-datapacks/test_control_flow/data/test_control_flow/function/test_continue/merge_8.mcfunction +1 -0
  72. package/test-datapacks/test_control_flow/data/test_control_flow/function/test_continue/merge_9.mcfunction +1 -0
  73. package/test-datapacks/test_control_flow/data/test_control_flow/function/test_continue/then_3.mcfunction +2 -0
  74. package/test-datapacks/test_control_flow/data/test_control_flow/function/test_continue/then_4.mcfunction +2 -0
  75. package/test-datapacks/test_control_flow/data/test_control_flow/function/test_continue/then_6.mcfunction +3 -0
  76. package/test-datapacks/test_control_flow/data/test_control_flow/function/test_continue/then_7.mcfunction +3 -0
  77. package/test-datapacks/test_control_flow/data/test_control_flow/function/test_continue.mcfunction +4 -0
  78. package/test-datapacks/test_control_flow/data/test_control_flow/function/test_for_loop/else_13.mcfunction +3 -0
  79. package/test-datapacks/test_control_flow/data/test_control_flow/function/test_for_loop/else_16.mcfunction +3 -0
  80. package/test-datapacks/test_control_flow/data/test_control_flow/function/test_for_loop/else_4.mcfunction +3 -0
  81. package/test-datapacks/test_control_flow/data/test_control_flow/function/test_for_loop/else_5.mcfunction +3 -0
  82. package/test-datapacks/test_control_flow/data/test_control_flow/function/test_for_loop/for_body_1.mcfunction +5 -0
  83. package/test-datapacks/test_control_flow/data/test_control_flow/function/test_for_loop/for_body_10.mcfunction +8 -0
  84. package/test-datapacks/test_control_flow/data/test_control_flow/function/test_for_loop/for_body_12.mcfunction +5 -0
  85. package/test-datapacks/test_control_flow/data/test_control_flow/function/test_for_loop/for_body_7.mcfunction +3 -0
  86. package/test-datapacks/test_control_flow/data/test_control_flow/function/test_for_loop/for_body_8.mcfunction +3 -0
  87. package/test-datapacks/test_control_flow/data/test_control_flow/function/test_for_loop/for_check_0.mcfunction +5 -0
  88. package/test-datapacks/test_control_flow/data/test_control_flow/function/test_for_loop/for_check_11.mcfunction +5 -0
  89. package/test-datapacks/test_control_flow/data/test_control_flow/function/test_for_loop/for_check_6.mcfunction +5 -0
  90. package/test-datapacks/test_control_flow/data/test_control_flow/function/test_for_loop/for_check_7.mcfunction +5 -0
  91. package/test-datapacks/test_control_flow/data/test_control_flow/function/test_for_loop/for_check_9.mcfunction +5 -0
  92. package/test-datapacks/test_control_flow/data/test_control_flow/function/test_for_loop/for_continue_13.mcfunction +5 -0
  93. package/test-datapacks/test_control_flow/data/test_control_flow/function/test_for_loop/for_continue_2.mcfunction +5 -0
  94. package/test-datapacks/test_control_flow/data/test_control_flow/function/test_for_loop/for_continue_9.mcfunction +5 -0
  95. package/test-datapacks/test_control_flow/data/test_control_flow/function/test_for_loop/for_exit_10.mcfunction +5 -0
  96. package/test-datapacks/test_control_flow/data/test_control_flow/function/test_for_loop/for_exit_11.mcfunction +5 -0
  97. package/test-datapacks/test_control_flow/data/test_control_flow/function/test_for_loop/for_exit_14.mcfunction +2 -0
  98. package/test-datapacks/test_control_flow/data/test_control_flow/function/test_for_loop/for_exit_2.mcfunction +5 -0
  99. package/test-datapacks/test_control_flow/data/test_control_flow/function/test_for_loop/for_exit_3.mcfunction +5 -0
  100. package/test-datapacks/test_control_flow/data/test_control_flow/function/test_for_loop/for_exit_8.mcfunction +5 -0
  101. package/test-datapacks/test_control_flow/data/test_control_flow/function/test_for_loop/merge_14.mcfunction +1 -0
  102. package/test-datapacks/test_control_flow/data/test_control_flow/function/test_for_loop/merge_17.mcfunction +1 -0
  103. package/test-datapacks/test_control_flow/data/test_control_flow/function/test_for_loop/merge_5.mcfunction +4 -0
  104. package/test-datapacks/test_control_flow/data/test_control_flow/function/test_for_loop/merge_6.mcfunction +4 -0
  105. package/test-datapacks/test_control_flow/data/test_control_flow/function/test_for_loop/then_12.mcfunction +3 -0
  106. package/test-datapacks/test_control_flow/data/test_control_flow/function/test_for_loop/then_15.mcfunction +3 -0
  107. package/test-datapacks/test_control_flow/data/test_control_flow/function/test_for_loop/then_3.mcfunction +3 -0
  108. package/test-datapacks/test_control_flow/data/test_control_flow/function/test_for_loop/then_4.mcfunction +3 -0
  109. package/test-datapacks/test_control_flow/data/test_control_flow/function/test_for_loop.mcfunction +4 -0
  110. package/test-datapacks/test_control_flow/data/test_control_flow/function/test_foreach_at/foreach_0.mcfunction +2 -0
  111. package/test-datapacks/test_control_flow/data/test_control_flow/function/test_foreach_at.mcfunction +5 -0
  112. package/test-datapacks/test_control_flow/data/test_control_flow/function/test_foreach_selector/else_1.mcfunction +3 -0
  113. package/test-datapacks/test_control_flow/data/test_control_flow/function/test_foreach_selector/foreach_0.mcfunction +4 -0
  114. package/test-datapacks/test_control_flow/data/test_control_flow/function/test_foreach_selector/merge_2.mcfunction +2 -0
  115. package/test-datapacks/test_control_flow/data/test_control_flow/function/test_foreach_selector/then_0.mcfunction +3 -0
  116. package/test-datapacks/test_control_flow/data/test_control_flow/function/test_foreach_selector.mcfunction +11 -0
  117. package/test-datapacks/test_control_flow/data/test_control_flow/function/test_if_else/else_1.mcfunction +3 -0
  118. package/test-datapacks/test_control_flow/data/test_control_flow/function/test_if_else/else_13.mcfunction +5 -0
  119. package/test-datapacks/test_control_flow/data/test_control_flow/function/test_if_else/else_16.mcfunction +3 -0
  120. package/test-datapacks/test_control_flow/data/test_control_flow/function/test_if_else/else_4.mcfunction +3 -0
  121. package/test-datapacks/test_control_flow/data/test_control_flow/function/test_if_else/merge_11.mcfunction +2 -0
  122. package/test-datapacks/test_control_flow/data/test_control_flow/function/test_if_else/merge_14.mcfunction +1 -0
  123. package/test-datapacks/test_control_flow/data/test_control_flow/function/test_if_else/merge_17.mcfunction +2 -0
  124. package/test-datapacks/test_control_flow/data/test_control_flow/function/test_if_else/merge_2.mcfunction +5 -0
  125. package/test-datapacks/test_control_flow/data/test_control_flow/function/test_if_else/merge_5.mcfunction +5 -0
  126. package/test-datapacks/test_control_flow/data/test_control_flow/function/test_if_else/merge_8.mcfunction +6 -0
  127. package/test-datapacks/test_control_flow/data/test_control_flow/function/test_if_else/then_0.mcfunction +3 -0
  128. package/test-datapacks/test_control_flow/data/test_control_flow/function/test_if_else/then_12.mcfunction +3 -0
  129. package/test-datapacks/test_control_flow/data/test_control_flow/function/test_if_else/then_15.mcfunction +3 -0
  130. package/test-datapacks/test_control_flow/data/test_control_flow/function/test_if_else/then_3.mcfunction +3 -0
  131. package/test-datapacks/test_control_flow/data/test_control_flow/function/test_if_else/then_6.mcfunction +5 -0
  132. package/test-datapacks/test_control_flow/data/test_control_flow/function/test_if_else/then_9.mcfunction +3 -0
  133. package/test-datapacks/test_control_flow/data/test_control_flow/function/test_if_else.mcfunction +6 -0
  134. package/test-datapacks/test_control_flow/data/test_control_flow/function/test_match/match_0.mcfunction +3 -0
  135. package/test-datapacks/test_control_flow/data/test_control_flow/function/test_match/match_1.mcfunction +3 -0
  136. package/test-datapacks/test_control_flow/data/test_control_flow/function/test_match/match_2.mcfunction +3 -0
  137. package/test-datapacks/test_control_flow/data/test_control_flow/function/test_match/match_3.mcfunction +2 -0
  138. package/test-datapacks/test_control_flow/data/test_control_flow/function/test_match/match_4.mcfunction +3 -0
  139. package/test-datapacks/test_control_flow/data/test_control_flow/function/test_match/match_5.mcfunction +3 -0
  140. package/test-datapacks/test_control_flow/data/test_control_flow/function/test_match/match_6.mcfunction +3 -0
  141. package/test-datapacks/test_control_flow/data/test_control_flow/function/test_match/match_7.mcfunction +3 -0
  142. package/test-datapacks/test_control_flow/data/test_control_flow/function/test_match/match_8.mcfunction +3 -0
  143. package/test-datapacks/test_control_flow/data/test_control_flow/function/test_match/match_9.mcfunction +2 -0
  144. package/test-datapacks/test_control_flow/data/test_control_flow/function/test_match.mcfunction +15 -0
  145. package/test-datapacks/test_control_flow/data/test_control_flow/function/test_while_loop/else_13.mcfunction +3 -0
  146. package/test-datapacks/test_control_flow/data/test_control_flow/function/test_while_loop/else_4.mcfunction +3 -0
  147. package/test-datapacks/test_control_flow/data/test_control_flow/function/test_while_loop/loop_body_1.mcfunction +5 -0
  148. package/test-datapacks/test_control_flow/data/test_control_flow/function/test_while_loop/loop_body_7.mcfunction +8 -0
  149. package/test-datapacks/test_control_flow/data/test_control_flow/function/test_while_loop/loop_check_0.mcfunction +5 -0
  150. package/test-datapacks/test_control_flow/data/test_control_flow/function/test_while_loop/loop_check_6.mcfunction +4 -0
  151. package/test-datapacks/test_control_flow/data/test_control_flow/function/test_while_loop/loop_exit_2.mcfunction +5 -0
  152. package/test-datapacks/test_control_flow/data/test_control_flow/function/test_while_loop/loop_exit_8.mcfunction +5 -0
  153. package/test-datapacks/test_control_flow/data/test_control_flow/function/test_while_loop/merge_11.mcfunction +2 -0
  154. package/test-datapacks/test_control_flow/data/test_control_flow/function/test_while_loop/merge_14.mcfunction +1 -0
  155. package/test-datapacks/test_control_flow/data/test_control_flow/function/test_while_loop/merge_5.mcfunction +3 -0
  156. package/test-datapacks/test_control_flow/data/test_control_flow/function/test_while_loop/then_12.mcfunction +3 -0
  157. package/test-datapacks/test_control_flow/data/test_control_flow/function/test_while_loop/then_3.mcfunction +3 -0
  158. package/test-datapacks/test_control_flow/data/test_control_flow/function/test_while_loop/then_9.mcfunction +2 -0
  159. package/test-datapacks/test_control_flow/data/test_control_flow/function/test_while_loop.mcfunction +3 -0
  160. package/test-datapacks/test_control_flow/pack.mcmeta +6 -0
  161. package/test-datapacks/test_control_flow.mcrs +242 -0
  162. package/test-datapacks/test_decorators/data/minecraft/tags/function/load.json +5 -0
  163. package/test-datapacks/test_decorators/data/minecraft/tags/function/tick.json +5 -0
  164. package/test-datapacks/test_decorators/data/test_decorators/function/__load.mcfunction +20 -0
  165. package/test-datapacks/test_decorators/data/test_decorators/function/__tick.mcfunction +3 -0
  166. package/test-datapacks/test_decorators/data/test_decorators/function/every_second/merge_2.mcfunction +9 -0
  167. package/test-datapacks/test_decorators/data/test_decorators/function/every_second/merge_5.mcfunction +1 -0
  168. package/test-datapacks/test_decorators/data/test_decorators/function/every_second/then_0.mcfunction +1 -0
  169. package/test-datapacks/test_decorators/data/test_decorators/function/every_second/then_3.mcfunction +4 -0
  170. package/test-datapacks/test_decorators/data/test_decorators/function/every_second/tick_body.mcfunction +6 -0
  171. package/test-datapacks/test_decorators/data/test_decorators/function/every_second/tick_skip.mcfunction +1 -0
  172. package/test-datapacks/test_decorators/data/test_decorators/function/every_second.mcfunction +5 -0
  173. package/test-datapacks/test_decorators/data/test_decorators/function/on_load.mcfunction +2 -0
  174. package/test-datapacks/test_decorators/data/test_decorators/function/on_test_trigger.mcfunction +2 -0
  175. package/test-datapacks/test_decorators/data/test_decorators/function/on_tick/merge_2.mcfunction +10 -0
  176. package/test-datapacks/test_decorators/data/test_decorators/function/on_tick/merge_5.mcfunction +5 -0
  177. package/test-datapacks/test_decorators/data/test_decorators/function/on_tick/merge_8.mcfunction +1 -0
  178. package/test-datapacks/test_decorators/data/test_decorators/function/on_tick/then_0.mcfunction +1 -0
  179. package/test-datapacks/test_decorators/data/test_decorators/function/on_tick/then_3.mcfunction +3 -0
  180. package/test-datapacks/test_decorators/data/test_decorators/function/on_tick/then_6.mcfunction +4 -0
  181. package/test-datapacks/test_decorators/data/test_decorators/function/on_tick.mcfunction +5 -0
  182. package/test-datapacks/test_decorators/data/test_decorators/function/run_decorator_tests.mcfunction +7 -0
  183. package/test-datapacks/test_decorators/data/test_decorators/function/setup_trigger_test.mcfunction +4 -0
  184. package/test-datapacks/test_decorators/data/test_decorators/function/start_slow_tick_test.mcfunction +4 -0
  185. package/test-datapacks/test_decorators/data/test_decorators/function/start_tick_test.mcfunction +4 -0
  186. package/test-datapacks/test_decorators/data/test_decorators/function/stop_tick_test.mcfunction +3 -0
  187. package/test-datapacks/test_decorators/pack.mcmeta +6 -0
  188. package/test-datapacks/test_decorators.mcrs +81 -0
  189. package/test-datapacks/test_events/data/minecraft/tags/function/load.json +5 -0
  190. package/test-datapacks/test_events/data/test_events/advancements/on_death_on_player_death.json +10 -0
  191. package/test-datapacks/test_events/data/test_events/function/__load.mcfunction +3 -0
  192. package/test-datapacks/test_events/data/test_events/function/on_player_death.mcfunction +3 -0
  193. package/test-datapacks/test_events/data/test_events/function/run_event_tests.mcfunction +5 -0
  194. package/test-datapacks/test_events/data/test_events/function/setup_events.mcfunction +3 -0
  195. package/test-datapacks/test_events/data/test_events/function/spawn_test_entity.mcfunction +3 -0
  196. package/test-datapacks/test_events/data/test_events/function/test_advancement_event.mcfunction +4 -0
  197. package/test-datapacks/test_events/pack.mcmeta +6 -0
  198. package/test-datapacks/test_events.mcrs +37 -0
  199. package/test-datapacks/test_fstrings/data/minecraft/tags/function/load.json +5 -0
  200. package/test-datapacks/test_fstrings/data/test_fstrings/function/__load.mcfunction +10 -0
  201. package/test-datapacks/test_fstrings/data/test_fstrings/function/run_fstring_tests.mcfunction +15 -0
  202. package/test-datapacks/test_fstrings/data/test_fstrings/function/test_actionbar_fstring.mcfunction +4 -0
  203. package/test-datapacks/test_fstrings/data/test_fstrings/function/test_announce.mcfunction +3 -0
  204. package/test-datapacks/test_fstrings/data/test_fstrings/function/test_multi_var_fstring.mcfunction +9 -0
  205. package/test-datapacks/test_fstrings/data/test_fstrings/function/test_say_fstring.mcfunction +4 -0
  206. package/test-datapacks/test_fstrings/data/test_fstrings/function/test_tell.mcfunction +3 -0
  207. package/test-datapacks/test_fstrings/data/test_fstrings/function/test_title_fstring.mcfunction +4 -0
  208. package/test-datapacks/test_fstrings/pack.mcmeta +6 -0
  209. package/test-datapacks/test_fstrings.mcrs +58 -0
  210. package/test-datapacks/test_timers/data/minecraft/tags/function/load.json +5 -0
  211. package/test-datapacks/test_timers/data/test_timers/function/__interval_0.mcfunction +3 -0
  212. package/test-datapacks/test_timers/data/test_timers/function/__interval_body_0.mcfunction +5 -0
  213. package/test-datapacks/test_timers/data/test_timers/function/__load.mcfunction +5 -0
  214. package/test-datapacks/test_timers/data/test_timers/function/__timeout_0.mcfunction +2 -0
  215. package/test-datapacks/test_timers/data/test_timers/function/run_timer_tests.mcfunction +5 -0
  216. package/test-datapacks/test_timers/data/test_timers/function/test_set_interval.mcfunction +5 -0
  217. package/test-datapacks/test_timers/data/test_timers/function/test_set_timeout.mcfunction +3 -0
  218. package/test-datapacks/test_timers/pack.mcmeta +6 -0
  219. package/test-datapacks/test_timers.mcrs +35 -0
@@ -0,0 +1,34 @@
1
+ // Struct instantiation and field access test
2
+
3
+ struct Point {
4
+ x: int,
5
+ y: int,
6
+ z: int
7
+ }
8
+
9
+ struct Player {
10
+ score: int,
11
+ alive: bool
12
+ }
13
+
14
+ @keep fn test_struct() {
15
+ // Create struct instance
16
+ let p: Point = { x: 10, y: 64, z: -5 };
17
+
18
+ // Access fields
19
+ scoreboard_set("#struct_x", #rs, p.x);
20
+ scoreboard_set("#struct_y", #rs, p.y);
21
+ scoreboard_set("#struct_z", #rs, p.z);
22
+
23
+ // Modify via new instance
24
+ let p2: Point = { x: p.x + 5, y: p.y, z: p.z * 2 };
25
+ scoreboard_set("#struct_x2", #rs, p2.x);
26
+ scoreboard_set("#struct_z2", #rs, p2.z);
27
+
28
+ // Bool field
29
+ let player: Player = { score: 100, alive: true };
30
+ if (player.alive) {
31
+ scoreboard_set("#struct_alive", #rs, 1);
32
+ }
33
+ scoreboard_set("#struct_score", #rs, player.score);
34
+ }
@@ -797,3 +797,100 @@ describe('MC Integration - New Features', () => {
797
797
  expect(tickResult.ok).toBe(true)
798
798
  })
799
799
  })
800
+
801
+ describe('MC Integration - Extended Coverage', () => {
802
+ test('struct-test.mcrs: struct instantiation and field access', async () => {
803
+ if (!serverOnline) return
804
+
805
+ writeFixtureFile('struct-test.mcrs', 'struct_test')
806
+ await mc.reload()
807
+ await mc.command('/function struct_test:__load').catch(() => {})
808
+ await mc.command('/function struct_test:test_struct')
809
+ await mc.ticks(5)
810
+
811
+ expect(await mc.scoreboard('#struct_x', 'rs')).toBe(10)
812
+ expect(await mc.scoreboard('#struct_y', 'rs')).toBe(64)
813
+ expect(await mc.scoreboard('#struct_z', 'rs')).toBe(-5)
814
+ expect(await mc.scoreboard('#struct_x2', 'rs')).toBe(15) // 10+5
815
+ expect(await mc.scoreboard('#struct_z2', 'rs')).toBe(-10) // -5*2
816
+ expect(await mc.scoreboard('#struct_alive', 'rs')).toBe(1)
817
+ expect(await mc.scoreboard('#struct_score', 'rs')).toBe(100)
818
+ })
819
+
820
+ test('enum-test.mcrs: enum values and match', async () => {
821
+ if (!serverOnline) return
822
+
823
+ writeFixtureFile('enum-test.mcrs', 'enum_test')
824
+ await mc.reload()
825
+ await mc.command('/function enum_test:__load').catch(() => {})
826
+ await mc.command('/function enum_test:test_enum')
827
+ await mc.ticks(5)
828
+
829
+ expect(await mc.scoreboard('#enum_phase', 'rs')).toBe(2) // Playing=2
830
+ expect(await mc.scoreboard('#enum_match', 'rs')).toBe(2) // matched Playing
831
+ expect(await mc.scoreboard('#enum_rank', 'rs')).toBe(10) // Diamond=10
832
+ expect(await mc.scoreboard('#enum_high', 'rs')).toBe(1) // Diamond > Gold
833
+ })
834
+
835
+ test('array-test.mcrs: array operations', async () => {
836
+ if (!serverOnline) return
837
+
838
+ writeFixtureFile('array-test.mcrs', 'array_test')
839
+ await mc.reload()
840
+ await mc.command('/function array_test:__load').catch(() => {})
841
+ await mc.command('/function array_test:test_array')
842
+ await mc.ticks(5)
843
+
844
+ expect(await mc.scoreboard('#arr_0', 'rs')).toBe(10)
845
+ expect(await mc.scoreboard('#arr_2', 'rs')).toBe(30)
846
+ expect(await mc.scoreboard('#arr_4', 'rs')).toBe(50)
847
+ expect(await mc.scoreboard('#arr_len', 'rs')).toBe(5)
848
+ expect(await mc.scoreboard('#arr_sum', 'rs')).toBe(150) // 10+20+30+40+50
849
+ expect(await mc.scoreboard('#arr_push', 'rs')).toBe(4) // [1,2,3,4].len
850
+ expect(await mc.scoreboard('#arr_pop', 'rs')).toBe(4) // popped value
851
+ })
852
+
853
+ test('break-continue-test.mcrs: break and continue statements', async () => {
854
+ if (!serverOnline) return
855
+
856
+ writeFixtureFile('break-continue-test.mcrs', 'break_continue_test')
857
+ await mc.reload()
858
+ await mc.command('/function break_continue_test:__load').catch(() => {})
859
+ await mc.command('/function break_continue_test:test_break_continue')
860
+ await mc.ticks(10)
861
+
862
+ expect(await mc.scoreboard('#break_at', 'rs')).toBe(5)
863
+ expect(await mc.scoreboard('#sum_evens', 'rs')).toBe(20) // 0+2+4+6+8
864
+ expect(await mc.scoreboard('#while_break', 'rs')).toBe(7)
865
+ expect(await mc.scoreboard('#nested_break', 'rs')).toBe(3) // outer completes 3 times
866
+ })
867
+
868
+ test('match-range-test.mcrs: match with range patterns', async () => {
869
+ if (!serverOnline) return
870
+
871
+ writeFixtureFile('match-range-test.mcrs', 'match_range_test')
872
+ await mc.reload()
873
+ await mc.command('/function match_range_test:__load').catch(() => {})
874
+ await mc.command('/function match_range_test:test_match_range')
875
+ await mc.ticks(5)
876
+
877
+ expect(await mc.scoreboard('#grade', 'rs')).toBe(4) // score=85 → B
878
+ expect(await mc.scoreboard('#boundary_59', 'rs')).toBe(1) // 59 matches 0..59
879
+ expect(await mc.scoreboard('#boundary_60', 'rs')).toBe(2) // 60 matches 60..100
880
+ expect(await mc.scoreboard('#neg_range', 'rs')).toBe(1) // -5 matches ..0
881
+ })
882
+
883
+ test('foreach-at-test.mcrs: foreach with at @s context', async () => {
884
+ if (!serverOnline) return
885
+
886
+ writeFixtureFile('foreach-at-test.mcrs', 'foreach_at_test')
887
+ await mc.reload()
888
+ await mc.fullReset({ clearArea: false, killEntities: true, resetScoreboards: false })
889
+ await mc.command('/function foreach_at_test:setup').catch(() => {})
890
+ await mc.command('/function foreach_at_test:test_foreach_at')
891
+ await mc.ticks(10)
892
+
893
+ expect(await mc.scoreboard('#foreach_count', 'rs')).toBe(3)
894
+ expect(await mc.scoreboard('#foreach_at_count', 'rs')).toBe(3)
895
+ })
896
+ })
package/src/ast/types.ts CHANGED
@@ -179,16 +179,39 @@ export type LiteralExpr =
179
179
  // ---------------------------------------------------------------------------
180
180
 
181
181
  export type ExecuteSubcommand =
182
+ // Context modifiers
182
183
  | { kind: 'as'; selector: EntitySelector }
183
184
  | { kind: 'at'; selector: EntitySelector }
185
+ | { kind: 'positioned'; x: string; y: string; z: string }
186
+ | { kind: 'positioned_as'; selector: EntitySelector }
187
+ | { kind: 'rotated'; yaw: string; pitch: string }
188
+ | { kind: 'rotated_as'; selector: EntitySelector }
189
+ | { kind: 'facing'; x: string; y: string; z: string }
190
+ | { kind: 'facing_entity'; selector: EntitySelector; anchor: 'eyes' | 'feet' }
191
+ | { kind: 'anchored'; anchor: 'eyes' | 'feet' }
192
+ | { kind: 'align'; axes: string }
193
+ | { kind: 'in'; dimension: string }
194
+ | { kind: 'on'; relation: string }
195
+ | { kind: 'summon'; entity: string }
196
+ // Conditions
184
197
  | { kind: 'if_entity'; selector?: EntitySelector; varName?: string; filters?: SelectorFilter }
185
198
  | { kind: 'unless_entity'; selector?: EntitySelector; varName?: string; filters?: SelectorFilter }
186
- | { kind: 'in'; dimension: string }
199
+ | { kind: 'if_block'; pos: [string, string, string]; block: string }
200
+ | { kind: 'unless_block'; pos: [string, string, string]; block: string }
201
+ | { kind: 'if_score'; target: string; targetObj: string; op: string; source: string; sourceObj: string }
202
+ | { kind: 'unless_score'; target: string; targetObj: string; op: string; source: string; sourceObj: string }
203
+ | { kind: 'if_score_range'; target: string; targetObj: string; range: string }
204
+ | { kind: 'unless_score_range'; target: string; targetObj: string; range: string }
205
+ // Store
206
+ | { kind: 'store_result'; target: string; targetObj: string }
207
+ | { kind: 'store_success'; target: string; targetObj: string }
187
208
 
188
209
  export type Stmt =
189
210
  | { kind: 'let'; name: string; type?: TypeNode; init: Expr; span?: Span }
190
211
  | { kind: 'expr'; expr: Expr; span?: Span }
191
212
  | { kind: 'return'; value?: Expr; span?: Span }
213
+ | { kind: 'break'; span?: Span }
214
+ | { kind: 'continue'; span?: Span }
192
215
  | { kind: 'if'; cond: Expr; then: Block; else_?: Block; span?: Span }
193
216
  | { kind: 'while'; cond: Expr; body: Block; span?: Span }
194
217
  | { kind: 'for'; init?: Stmt; cond: Expr; step: Expr; body: Block; span?: Span }
package/src/index.ts CHANGED
@@ -5,7 +5,7 @@
5
5
  */
6
6
 
7
7
  // eslint-disable-next-line @typescript-eslint/no-var-requires
8
- export const version = '1.2.9'
8
+ export const version = '1.2.11'
9
9
 
10
10
  import { Lexer } from './lexer'
11
11
  import { Parser } from './parser'
@@ -15,7 +15,7 @@ import { DiagnosticError } from '../diagnostics'
15
15
  export type TokenKind =
16
16
  // Keywords
17
17
  | 'fn' | 'let' | 'const' | 'if' | 'else' | 'while' | 'for' | 'foreach' | 'match'
18
- | 'return' | 'as' | 'at' | 'in' | 'is' | 'struct' | 'impl' | 'enum' | 'trigger' | 'namespace'
18
+ | 'return' | 'break' | 'continue' | 'as' | 'at' | 'in' | 'is' | 'struct' | 'impl' | 'enum' | 'trigger' | 'namespace'
19
19
  | 'execute' | 'run' | 'unless'
20
20
  // Types
21
21
  | 'int' | 'bool' | 'float' | 'string' | 'void'
@@ -75,6 +75,8 @@ const KEYWORDS: Record<string, TokenKind> = {
75
75
  foreach: 'foreach',
76
76
  match: 'match',
77
77
  return: 'return',
78
+ break: 'break',
79
+ continue: 'continue',
78
80
  as: 'as',
79
81
  at: 'at',
80
82
  in: 'in',
@@ -224,6 +224,9 @@ export class Lowering {
224
224
  // World object counter for unique tags
225
225
  private worldObjCounter: number = 0
226
226
 
227
+ // Loop context stack for break/continue
228
+ private loopStack: Array<{ breakLabel: string; continueLabel: string; stepFn?: () => void }> = []
229
+
227
230
  constructor(namespace: string, sourceRanges: SourceRange[] = []) {
228
231
  this.namespace = namespace
229
232
  this.sourceRanges = sourceRanges
@@ -530,6 +533,12 @@ export class Lowering {
530
533
  case 'return':
531
534
  this.lowerReturnStmt(stmt)
532
535
  break
536
+ case 'break':
537
+ this.lowerBreakStmt()
538
+ break
539
+ case 'continue':
540
+ this.lowerContinueStmt()
541
+ break
533
542
  case 'if':
534
543
  this.lowerIfStmt(stmt)
535
544
  break
@@ -682,6 +691,22 @@ export class Lowering {
682
691
  }
683
692
  }
684
693
 
694
+ private lowerBreakStmt(): void {
695
+ if (this.loopStack.length === 0) {
696
+ throw new DiagnosticError('LoweringError', 'break statement outside of loop', { line: 1, col: 1 })
697
+ }
698
+ const loop = this.loopStack[this.loopStack.length - 1]
699
+ this.builder.emitJump(loop.breakLabel)
700
+ }
701
+
702
+ private lowerContinueStmt(): void {
703
+ if (this.loopStack.length === 0) {
704
+ throw new DiagnosticError('LoweringError', 'continue statement outside of loop', { line: 1, col: 1 })
705
+ }
706
+ const loop = this.loopStack[this.loopStack.length - 1]
707
+ this.builder.emitJump(loop.continueLabel)
708
+ }
709
+
685
710
  private lowerIfStmt(stmt: Extract<Stmt, { kind: 'if' }>): void {
686
711
  if (stmt.cond.kind === 'is_check') {
687
712
  this.lowerIsCheckIfStmt(stmt)
@@ -790,6 +815,9 @@ export class Lowering {
790
815
  const condName = this.operandToVar(condVar)
791
816
  this.builder.emitJumpIf(condName, bodyLabel, exitLabel)
792
817
 
818
+ // Push loop context for break/continue (while has no step, so continue goes to check)
819
+ this.loopStack.push({ breakLabel: exitLabel, continueLabel: checkLabel })
820
+
793
821
  // Body block
794
822
  this.builder.startBlock(bodyLabel)
795
823
  this.lowerBlock(stmt.body)
@@ -797,6 +825,9 @@ export class Lowering {
797
825
  this.builder.emitJump(checkLabel)
798
826
  }
799
827
 
828
+ // Pop loop context
829
+ this.loopStack.pop()
830
+
800
831
  // Exit block
801
832
  this.builder.startBlock(exitLabel)
802
833
  }
@@ -811,6 +842,7 @@ export class Lowering {
811
842
 
812
843
  const checkLabel = this.builder.freshLabel('for_check')
813
844
  const bodyLabel = this.builder.freshLabel('for_body')
845
+ const continueLabel = this.builder.freshLabel('for_continue')
814
846
  const exitLabel = this.builder.freshLabel('for_exit')
815
847
 
816
848
  this.builder.emitJump(checkLabel)
@@ -821,15 +853,24 @@ export class Lowering {
821
853
  const condName = this.operandToVar(condVar)
822
854
  this.builder.emitJumpIf(condName, bodyLabel, exitLabel)
823
855
 
856
+ // Push loop context for break/continue
857
+ this.loopStack.push({ breakLabel: exitLabel, continueLabel })
858
+
824
859
  // Body block
825
860
  this.builder.startBlock(bodyLabel)
826
861
  this.lowerBlock(stmt.body)
827
- // Step expression
828
- this.lowerExpr(stmt.step)
829
862
  if (!this.builder.isBlockSealed()) {
830
- this.builder.emitJump(checkLabel)
863
+ this.builder.emitJump(continueLabel)
831
864
  }
832
865
 
866
+ // Continue block (step + loop back)
867
+ this.builder.startBlock(continueLabel)
868
+ this.lowerExpr(stmt.step)
869
+ this.builder.emitJump(checkLabel)
870
+
871
+ // Pop loop context
872
+ this.loopStack.pop()
873
+
833
874
  // Exit block
834
875
  this.builder.startBlock(exitLabel)
835
876
  }
@@ -945,13 +986,29 @@ export class Lowering {
945
986
  continue
946
987
  }
947
988
 
948
- const patternValue = this.lowerExpr(arm.pattern)
949
- if (patternValue.kind !== 'const') {
950
- throw new Error('Match patterns must lower to compile-time constants')
989
+ // Handle range patterns specially
990
+ let matchCondition: string
991
+ if (arm.pattern.kind === 'range_lit') {
992
+ const range = arm.pattern.range
993
+ if (range.min !== undefined && range.max !== undefined) {
994
+ matchCondition = `${range.min}..${range.max}`
995
+ } else if (range.min !== undefined) {
996
+ matchCondition = `${range.min}..`
997
+ } else if (range.max !== undefined) {
998
+ matchCondition = `..${range.max}`
999
+ } else {
1000
+ matchCondition = '0..' // Match any
1001
+ }
1002
+ } else {
1003
+ const patternValue = this.lowerExpr(arm.pattern)
1004
+ if (patternValue.kind !== 'const') {
1005
+ throw new Error('Match patterns must lower to compile-time constants')
1006
+ }
1007
+ matchCondition = String(patternValue.value)
951
1008
  }
952
1009
 
953
1010
  const subFnName = `${this.currentFn}/match_${this.foreachCounter++}`
954
- this.builder.emitRaw(`execute if score ${matchedVar} rs matches ..0 if score ${subject} rs matches ${patternValue.value} run function ${this.namespace}:${subFnName}`)
1011
+ this.builder.emitRaw(`execute if score ${matchedVar} rs matches ..0 if score ${subject} rs matches ${matchCondition} run function ${this.namespace}:${subFnName}`)
955
1012
  this.emitMatchArmSubFunction(subFnName, matchedVar, arm.body, true)
956
1013
  }
957
1014
 
@@ -1143,17 +1200,51 @@ export class Lowering {
1143
1200
  const parts: string[] = ['execute']
1144
1201
  for (const sub of stmt.subcommands) {
1145
1202
  switch (sub.kind) {
1203
+ // Context modifiers
1146
1204
  case 'as':
1147
1205
  parts.push(`as ${this.selectorToString(sub.selector)}`)
1148
1206
  break
1149
1207
  case 'at':
1150
1208
  parts.push(`at ${this.selectorToString(sub.selector)}`)
1151
1209
  break
1210
+ case 'positioned':
1211
+ parts.push(`positioned ${sub.x} ${sub.y} ${sub.z}`)
1212
+ break
1213
+ case 'positioned_as':
1214
+ parts.push(`positioned as ${this.selectorToString(sub.selector)}`)
1215
+ break
1216
+ case 'rotated':
1217
+ parts.push(`rotated ${sub.yaw} ${sub.pitch}`)
1218
+ break
1219
+ case 'rotated_as':
1220
+ parts.push(`rotated as ${this.selectorToString(sub.selector)}`)
1221
+ break
1222
+ case 'facing':
1223
+ parts.push(`facing ${sub.x} ${sub.y} ${sub.z}`)
1224
+ break
1225
+ case 'facing_entity':
1226
+ parts.push(`facing entity ${this.selectorToString(sub.selector)} ${sub.anchor}`)
1227
+ break
1228
+ case 'anchored':
1229
+ parts.push(`anchored ${sub.anchor}`)
1230
+ break
1231
+ case 'align':
1232
+ parts.push(`align ${sub.axes}`)
1233
+ break
1234
+ case 'in':
1235
+ parts.push(`in ${sub.dimension}`)
1236
+ break
1237
+ case 'on':
1238
+ parts.push(`on ${sub.relation}`)
1239
+ break
1240
+ case 'summon':
1241
+ parts.push(`summon ${sub.entity}`)
1242
+ break
1243
+ // Conditions
1152
1244
  case 'if_entity':
1153
1245
  if (sub.selector) {
1154
1246
  parts.push(`if entity ${this.selectorToString(sub.selector)}`)
1155
1247
  } else if (sub.varName) {
1156
- // Variable with filters - substitute with @s and apply filters
1157
1248
  const sel: EntitySelector = { kind: '@s', filters: sub.filters }
1158
1249
  parts.push(`if entity ${this.selectorToString(sel)}`)
1159
1250
  }
@@ -1162,13 +1253,34 @@ export class Lowering {
1162
1253
  if (sub.selector) {
1163
1254
  parts.push(`unless entity ${this.selectorToString(sub.selector)}`)
1164
1255
  } else if (sub.varName) {
1165
- // Variable with filters - substitute with @s and apply filters
1166
1256
  const sel: EntitySelector = { kind: '@s', filters: sub.filters }
1167
1257
  parts.push(`unless entity ${this.selectorToString(sel)}`)
1168
1258
  }
1169
1259
  break
1170
- case 'in':
1171
- parts.push(`in ${sub.dimension}`)
1260
+ case 'if_block':
1261
+ parts.push(`if block ${sub.pos[0]} ${sub.pos[1]} ${sub.pos[2]} ${sub.block}`)
1262
+ break
1263
+ case 'unless_block':
1264
+ parts.push(`unless block ${sub.pos[0]} ${sub.pos[1]} ${sub.pos[2]} ${sub.block}`)
1265
+ break
1266
+ case 'if_score':
1267
+ parts.push(`if score ${sub.target} ${sub.targetObj} ${sub.op} ${sub.source} ${sub.sourceObj}`)
1268
+ break
1269
+ case 'unless_score':
1270
+ parts.push(`unless score ${sub.target} ${sub.targetObj} ${sub.op} ${sub.source} ${sub.sourceObj}`)
1271
+ break
1272
+ case 'if_score_range':
1273
+ parts.push(`if score ${sub.target} ${sub.targetObj} matches ${sub.range}`)
1274
+ break
1275
+ case 'unless_score_range':
1276
+ parts.push(`unless score ${sub.target} ${sub.targetObj} matches ${sub.range}`)
1277
+ break
1278
+ // Store
1279
+ case 'store_result':
1280
+ parts.push(`store result score ${sub.target} ${sub.targetObj}`)
1281
+ break
1282
+ case 'store_success':
1283
+ parts.push(`store success score ${sub.target} ${sub.targetObj}`)
1172
1284
  break
1173
1285
  }
1174
1286
  }
@@ -267,6 +267,8 @@ export class DeadCodeEliminator {
267
267
  this.collectNestedStmtRefs(stmt, scope)
268
268
  break
269
269
  case 'raw':
270
+ case 'break':
271
+ case 'continue':
270
272
  break
271
273
  }
272
274
  }
@@ -542,6 +544,10 @@ export class DeadCodeEliminator {
542
544
  return [copySpan({ ...stmt, body: this.transformBlock(stmt.body, scope) }, stmt)]
543
545
  case 'raw':
544
546
  return [stmt]
547
+ case 'break':
548
+ return [stmt]
549
+ case 'continue':
550
+ return [stmt]
545
551
  }
546
552
  }
547
553
 
@@ -470,6 +470,20 @@ export class Parser {
470
470
  return this.parseReturnStmt()
471
471
  }
472
472
 
473
+ // Break statement
474
+ if (this.check('break')) {
475
+ const token = this.advance()
476
+ this.match(';')
477
+ return this.withLoc({ kind: 'break' }, token)
478
+ }
479
+
480
+ // Continue statement
481
+ if (this.check('continue')) {
482
+ const token = this.advance()
483
+ this.match(';')
484
+ return this.withLoc({ kind: 'continue' }, token)
485
+ }
486
+
473
487
  // If statement
474
488
  if (this.check('if')) {
475
489
  return this.parseIfStmt()
@@ -646,11 +660,11 @@ export class Parser {
646
660
  const iterable = this.parseExpr()
647
661
  this.expect(')')
648
662
 
649
- // Parse optional execute context modifiers (at, positioned, rotated, facing, etc.)
663
+ // Parse optional execute context modifiers (as, at, positioned, rotated, facing, etc.)
650
664
  let executeContext: string | undefined
651
- // Check for 'at' keyword or identifiers like 'positioned', 'rotated', 'facing', 'anchored', 'align'
652
- const execIdentKeywords = ['positioned', 'rotated', 'facing', 'anchored', 'align']
653
- if (this.check('at') || this.check('in') || (this.check('ident') && execIdentKeywords.includes(this.peek().value))) {
665
+ // Check for execute subcommand keywords
666
+ const execIdentKeywords = ['positioned', 'rotated', 'facing', 'anchored', 'align', 'on', 'summon']
667
+ if (this.check('as') || this.check('at') || this.check('in') || (this.check('ident') && execIdentKeywords.includes(this.peek().value))) {
654
668
  // Collect everything until we hit '{'
655
669
  let context = ''
656
670
  while (!this.check('{') && !this.check('eof')) {
@@ -724,25 +738,84 @@ export class Parser {
724
738
  } else if (this.match('at')) {
725
739
  const selector = this.parseSelector()
726
740
  subcommands.push({ kind: 'at', selector })
727
- } else if (this.match('if')) {
728
- // Expect 'entity' keyword (as ident) or just parse selector directly
729
- if (this.peek().kind === 'ident' && this.peek().value === 'entity') {
730
- this.advance() // consume 'entity'
741
+ } else if (this.checkIdent('positioned')) {
742
+ this.advance()
743
+ if (this.match('as')) {
744
+ const selector = this.parseSelector()
745
+ subcommands.push({ kind: 'positioned_as', selector })
746
+ } else {
747
+ const x = this.parseCoordToken()
748
+ const y = this.parseCoordToken()
749
+ const z = this.parseCoordToken()
750
+ subcommands.push({ kind: 'positioned', x, y, z })
731
751
  }
732
- const selectorOrVar = this.parseSelectorOrVarSelector()
733
- subcommands.push({ kind: 'if_entity', ...selectorOrVar })
734
- } else if (this.match('unless')) {
735
- // Expect 'entity' keyword (as ident) or just parse selector directly
736
- if (this.peek().kind === 'ident' && this.peek().value === 'entity') {
737
- this.advance() // consume 'entity'
752
+ } else if (this.checkIdent('rotated')) {
753
+ this.advance()
754
+ if (this.match('as')) {
755
+ const selector = this.parseSelector()
756
+ subcommands.push({ kind: 'rotated_as', selector })
757
+ } else {
758
+ const yaw = this.parseCoordToken()
759
+ const pitch = this.parseCoordToken()
760
+ subcommands.push({ kind: 'rotated', yaw, pitch })
761
+ }
762
+ } else if (this.checkIdent('facing')) {
763
+ this.advance()
764
+ if (this.checkIdent('entity')) {
765
+ this.advance()
766
+ const selector = this.parseSelector()
767
+ const anchor = this.checkIdent('eyes') || this.checkIdent('feet') ? this.advance().value as 'eyes' | 'feet' : 'feet'
768
+ subcommands.push({ kind: 'facing_entity', selector, anchor })
769
+ } else {
770
+ const x = this.parseCoordToken()
771
+ const y = this.parseCoordToken()
772
+ const z = this.parseCoordToken()
773
+ subcommands.push({ kind: 'facing', x, y, z })
774
+ }
775
+ } else if (this.checkIdent('anchored')) {
776
+ this.advance()
777
+ const anchor = this.advance().value as 'eyes' | 'feet'
778
+ subcommands.push({ kind: 'anchored', anchor })
779
+ } else if (this.checkIdent('align')) {
780
+ this.advance()
781
+ const axes = this.advance().value
782
+ subcommands.push({ kind: 'align', axes })
783
+ } else if (this.checkIdent('on')) {
784
+ this.advance()
785
+ const relation = this.advance().value
786
+ subcommands.push({ kind: 'on', relation })
787
+ } else if (this.checkIdent('summon')) {
788
+ this.advance()
789
+ const entity = this.advance().value
790
+ subcommands.push({ kind: 'summon', entity })
791
+ } else if (this.checkIdent('store')) {
792
+ this.advance()
793
+ const storeType = this.advance().value // 'result' or 'success'
794
+ if (this.checkIdent('score')) {
795
+ this.advance()
796
+ const target = this.advance().value
797
+ const targetObj = this.advance().value
798
+ if (storeType === 'result') {
799
+ subcommands.push({ kind: 'store_result', target, targetObj })
800
+ } else {
801
+ subcommands.push({ kind: 'store_success', target, targetObj })
802
+ }
803
+ } else {
804
+ this.error('store currently only supports score target')
738
805
  }
739
- const selectorOrVar = this.parseSelectorOrVarSelector()
740
- subcommands.push({ kind: 'unless_entity', ...selectorOrVar })
806
+ } else if (this.match('if')) {
807
+ this.parseExecuteCondition(subcommands, 'if')
808
+ } else if (this.match('unless')) {
809
+ this.parseExecuteCondition(subcommands, 'unless')
741
810
  } else if (this.match('in')) {
742
- const dim = this.expect('ident').value
811
+ // Dimension can be namespaced: minecraft:the_nether
812
+ let dim = this.advance().value
813
+ if (this.match(':')) {
814
+ dim += ':' + this.advance().value
815
+ }
743
816
  subcommands.push({ kind: 'in', dimension: dim })
744
817
  } else {
745
- this.error(`Unexpected token in execute statement: ${this.peek().kind}`)
818
+ this.error(`Unexpected token in execute statement: ${this.peek().kind} (${this.peek().value})`)
746
819
  }
747
820
  }
748
821
 
@@ -752,6 +825,74 @@ export class Parser {
752
825
  return this.withLoc({ kind: 'execute', subcommands, body }, executeToken)
753
826
  }
754
827
 
828
+ private parseExecuteCondition(subcommands: ExecuteSubcommand[], type: 'if' | 'unless'): void {
829
+ if (this.checkIdent('entity') || this.check('selector')) {
830
+ if (this.checkIdent('entity')) this.advance()
831
+ const selectorOrVar = this.parseSelectorOrVarSelector()
832
+ subcommands.push({ kind: type === 'if' ? 'if_entity' : 'unless_entity', ...selectorOrVar })
833
+ } else if (this.checkIdent('block')) {
834
+ this.advance()
835
+ const x = this.parseCoordToken()
836
+ const y = this.parseCoordToken()
837
+ const z = this.parseCoordToken()
838
+ const block = this.parseBlockId()
839
+ subcommands.push({ kind: type === 'if' ? 'if_block' : 'unless_block', pos: [x, y, z], block })
840
+ } else if (this.checkIdent('score')) {
841
+ this.advance()
842
+ const target = this.advance().value
843
+ const targetObj = this.advance().value
844
+ // Check for range or comparison
845
+ if (this.checkIdent('matches')) {
846
+ this.advance()
847
+ const range = this.advance().value
848
+ subcommands.push({ kind: type === 'if' ? 'if_score_range' : 'unless_score_range', target, targetObj, range })
849
+ } else {
850
+ const op = this.advance().value // <, <=, =, >=, >
851
+ const source = this.advance().value
852
+ const sourceObj = this.advance().value
853
+ subcommands.push({
854
+ kind: type === 'if' ? 'if_score' : 'unless_score',
855
+ target, targetObj, op, source, sourceObj
856
+ })
857
+ }
858
+ } else {
859
+ this.error(`Unknown condition type after ${type}`)
860
+ }
861
+ }
862
+
863
+ private parseCoordToken(): string {
864
+ // Handle ~, ^, numbers, relative coords like ~5, ^-3
865
+ const token = this.peek()
866
+ if (token.kind === 'rel_coord' || token.kind === 'local_coord' ||
867
+ token.kind === 'int_lit' || token.kind === 'float_lit' ||
868
+ token.kind === '-' || token.kind === 'ident') {
869
+ return this.advance().value
870
+ }
871
+ this.error(`Expected coordinate, got ${token.kind}`)
872
+ return '~'
873
+ }
874
+
875
+ private parseBlockId(): string {
876
+ // Parse block ID like minecraft:stone or stone
877
+ let id = this.advance().value
878
+ if (this.match(':')) {
879
+ id += ':' + this.advance().value
880
+ }
881
+ // Handle block states [facing=north]
882
+ if (this.check('[')) {
883
+ id += this.advance().value // [
884
+ while (!this.check(']') && !this.check('eof')) {
885
+ id += this.advance().value
886
+ }
887
+ id += this.advance().value // ]
888
+ }
889
+ return id
890
+ }
891
+
892
+ private checkIdent(value: string): boolean {
893
+ return this.check('ident') && this.peek().value === value
894
+ }
895
+
755
896
  private parseExprStmt(): Stmt {
756
897
  const expr = this.parseExpr()
757
898
  this.expect(';')