redscript-mc 1.2.9 → 1.2.10

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 (210) hide show
  1. package/README.md +35 -33
  2. package/README.zh.md +35 -33
  3. package/demo.gif +0 -0
  4. package/dist/ast/types.d.ts +6 -0
  5. package/dist/index.d.ts +1 -1
  6. package/dist/index.js +1 -1
  7. package/dist/lexer/index.d.ts +1 -1
  8. package/dist/lexer/index.js +2 -0
  9. package/dist/lowering/index.d.ts +3 -0
  10. package/dist/lowering/index.js +60 -7
  11. package/dist/optimizer/dce.js +6 -0
  12. package/dist/parser/index.js +12 -0
  13. package/package.json +1 -1
  14. package/src/ast/types.ts +2 -0
  15. package/src/index.ts +1 -1
  16. package/src/lexer/index.ts +3 -1
  17. package/src/lowering/index.ts +64 -7
  18. package/src/optimizer/dce.ts +6 -0
  19. package/src/parser/index.ts +14 -0
  20. package/test-datapacks/README.md +67 -0
  21. package/test-datapacks/test_control_flow/data/minecraft/tags/function/load.json +5 -0
  22. package/test-datapacks/test_control_flow/data/test_control_flow/function/__load.mcfunction +41 -0
  23. package/test-datapacks/test_control_flow/data/test_control_flow/function/run_control_flow_tests.mcfunction +21 -0
  24. package/test-datapacks/test_control_flow/data/test_control_flow/function/test_break/else_7.mcfunction +3 -0
  25. package/test-datapacks/test_control_flow/data/test_control_flow/function/test_break/else_8.mcfunction +3 -0
  26. package/test-datapacks/test_control_flow/data/test_control_flow/function/test_break/for_body_1.mcfunction +5 -0
  27. package/test-datapacks/test_control_flow/data/test_control_flow/function/test_break/for_check_0.mcfunction +5 -0
  28. package/test-datapacks/test_control_flow/data/test_control_flow/function/test_break/for_continue_2.mcfunction +5 -0
  29. package/test-datapacks/test_control_flow/data/test_control_flow/function/test_break/for_exit_2.mcfunction +5 -0
  30. package/test-datapacks/test_control_flow/data/test_control_flow/function/test_break/for_exit_3.mcfunction +5 -0
  31. package/test-datapacks/test_control_flow/data/test_control_flow/function/test_break/merge_5.mcfunction +5 -0
  32. package/test-datapacks/test_control_flow/data/test_control_flow/function/test_break/merge_6.mcfunction +2 -0
  33. package/test-datapacks/test_control_flow/data/test_control_flow/function/test_break/merge_8.mcfunction +1 -0
  34. package/test-datapacks/test_control_flow/data/test_control_flow/function/test_break/merge_9.mcfunction +1 -0
  35. package/test-datapacks/test_control_flow/data/test_control_flow/function/test_break/then_3.mcfunction +3 -0
  36. package/test-datapacks/test_control_flow/data/test_control_flow/function/test_break/then_4.mcfunction +3 -0
  37. package/test-datapacks/test_control_flow/data/test_control_flow/function/test_break/then_6.mcfunction +3 -0
  38. package/test-datapacks/test_control_flow/data/test_control_flow/function/test_break/then_7.mcfunction +3 -0
  39. package/test-datapacks/test_control_flow/data/test_control_flow/function/test_break.mcfunction +4 -0
  40. package/test-datapacks/test_control_flow/data/test_control_flow/function/test_complex_conditions/else_1.mcfunction +3 -0
  41. package/test-datapacks/test_control_flow/data/test_control_flow/function/test_complex_conditions/else_10.mcfunction +3 -0
  42. package/test-datapacks/test_control_flow/data/test_control_flow/function/test_complex_conditions/else_4.mcfunction +3 -0
  43. package/test-datapacks/test_control_flow/data/test_control_flow/function/test_complex_conditions/else_7.mcfunction +3 -0
  44. package/test-datapacks/test_control_flow/data/test_control_flow/function/test_complex_conditions/merge_11.mcfunction +1 -0
  45. package/test-datapacks/test_control_flow/data/test_control_flow/function/test_complex_conditions/merge_2.mcfunction +9 -0
  46. package/test-datapacks/test_control_flow/data/test_control_flow/function/test_complex_conditions/merge_5.mcfunction +7 -0
  47. package/test-datapacks/test_control_flow/data/test_control_flow/function/test_complex_conditions/merge_8.mcfunction +13 -0
  48. package/test-datapacks/test_control_flow/data/test_control_flow/function/test_complex_conditions/then_0.mcfunction +3 -0
  49. package/test-datapacks/test_control_flow/data/test_control_flow/function/test_complex_conditions/then_3.mcfunction +3 -0
  50. package/test-datapacks/test_control_flow/data/test_control_flow/function/test_complex_conditions/then_6.mcfunction +3 -0
  51. package/test-datapacks/test_control_flow/data/test_control_flow/function/test_complex_conditions/then_9.mcfunction +3 -0
  52. package/test-datapacks/test_control_flow/data/test_control_flow/function/test_complex_conditions.mcfunction +12 -0
  53. package/test-datapacks/test_control_flow/data/test_control_flow/function/test_continue/else_7.mcfunction +3 -0
  54. package/test-datapacks/test_control_flow/data/test_control_flow/function/test_continue/else_8.mcfunction +3 -0
  55. package/test-datapacks/test_control_flow/data/test_control_flow/function/test_continue/for_body_1.mcfunction +7 -0
  56. package/test-datapacks/test_control_flow/data/test_control_flow/function/test_continue/for_check_0.mcfunction +5 -0
  57. package/test-datapacks/test_control_flow/data/test_control_flow/function/test_continue/for_continue_2.mcfunction +5 -0
  58. package/test-datapacks/test_control_flow/data/test_control_flow/function/test_continue/for_exit_2.mcfunction +5 -0
  59. package/test-datapacks/test_control_flow/data/test_control_flow/function/test_continue/for_exit_3.mcfunction +5 -0
  60. package/test-datapacks/test_control_flow/data/test_control_flow/function/test_continue/merge_5.mcfunction +8 -0
  61. package/test-datapacks/test_control_flow/data/test_control_flow/function/test_continue/merge_6.mcfunction +5 -0
  62. package/test-datapacks/test_control_flow/data/test_control_flow/function/test_continue/merge_8.mcfunction +1 -0
  63. package/test-datapacks/test_control_flow/data/test_control_flow/function/test_continue/merge_9.mcfunction +1 -0
  64. package/test-datapacks/test_control_flow/data/test_control_flow/function/test_continue/then_3.mcfunction +2 -0
  65. package/test-datapacks/test_control_flow/data/test_control_flow/function/test_continue/then_4.mcfunction +2 -0
  66. package/test-datapacks/test_control_flow/data/test_control_flow/function/test_continue/then_6.mcfunction +3 -0
  67. package/test-datapacks/test_control_flow/data/test_control_flow/function/test_continue/then_7.mcfunction +3 -0
  68. package/test-datapacks/test_control_flow/data/test_control_flow/function/test_continue.mcfunction +4 -0
  69. package/test-datapacks/test_control_flow/data/test_control_flow/function/test_for_loop/else_13.mcfunction +3 -0
  70. package/test-datapacks/test_control_flow/data/test_control_flow/function/test_for_loop/else_16.mcfunction +3 -0
  71. package/test-datapacks/test_control_flow/data/test_control_flow/function/test_for_loop/else_4.mcfunction +3 -0
  72. package/test-datapacks/test_control_flow/data/test_control_flow/function/test_for_loop/else_5.mcfunction +3 -0
  73. package/test-datapacks/test_control_flow/data/test_control_flow/function/test_for_loop/for_body_1.mcfunction +5 -0
  74. package/test-datapacks/test_control_flow/data/test_control_flow/function/test_for_loop/for_body_10.mcfunction +8 -0
  75. package/test-datapacks/test_control_flow/data/test_control_flow/function/test_for_loop/for_body_12.mcfunction +5 -0
  76. package/test-datapacks/test_control_flow/data/test_control_flow/function/test_for_loop/for_body_7.mcfunction +3 -0
  77. package/test-datapacks/test_control_flow/data/test_control_flow/function/test_for_loop/for_body_8.mcfunction +3 -0
  78. package/test-datapacks/test_control_flow/data/test_control_flow/function/test_for_loop/for_check_0.mcfunction +5 -0
  79. package/test-datapacks/test_control_flow/data/test_control_flow/function/test_for_loop/for_check_11.mcfunction +5 -0
  80. package/test-datapacks/test_control_flow/data/test_control_flow/function/test_for_loop/for_check_6.mcfunction +5 -0
  81. package/test-datapacks/test_control_flow/data/test_control_flow/function/test_for_loop/for_check_7.mcfunction +5 -0
  82. package/test-datapacks/test_control_flow/data/test_control_flow/function/test_for_loop/for_check_9.mcfunction +5 -0
  83. package/test-datapacks/test_control_flow/data/test_control_flow/function/test_for_loop/for_continue_13.mcfunction +5 -0
  84. package/test-datapacks/test_control_flow/data/test_control_flow/function/test_for_loop/for_continue_2.mcfunction +5 -0
  85. package/test-datapacks/test_control_flow/data/test_control_flow/function/test_for_loop/for_continue_9.mcfunction +5 -0
  86. package/test-datapacks/test_control_flow/data/test_control_flow/function/test_for_loop/for_exit_10.mcfunction +5 -0
  87. package/test-datapacks/test_control_flow/data/test_control_flow/function/test_for_loop/for_exit_11.mcfunction +5 -0
  88. package/test-datapacks/test_control_flow/data/test_control_flow/function/test_for_loop/for_exit_14.mcfunction +2 -0
  89. package/test-datapacks/test_control_flow/data/test_control_flow/function/test_for_loop/for_exit_2.mcfunction +5 -0
  90. package/test-datapacks/test_control_flow/data/test_control_flow/function/test_for_loop/for_exit_3.mcfunction +5 -0
  91. package/test-datapacks/test_control_flow/data/test_control_flow/function/test_for_loop/for_exit_8.mcfunction +5 -0
  92. package/test-datapacks/test_control_flow/data/test_control_flow/function/test_for_loop/merge_14.mcfunction +1 -0
  93. package/test-datapacks/test_control_flow/data/test_control_flow/function/test_for_loop/merge_17.mcfunction +1 -0
  94. package/test-datapacks/test_control_flow/data/test_control_flow/function/test_for_loop/merge_5.mcfunction +4 -0
  95. package/test-datapacks/test_control_flow/data/test_control_flow/function/test_for_loop/merge_6.mcfunction +4 -0
  96. package/test-datapacks/test_control_flow/data/test_control_flow/function/test_for_loop/then_12.mcfunction +3 -0
  97. package/test-datapacks/test_control_flow/data/test_control_flow/function/test_for_loop/then_15.mcfunction +3 -0
  98. package/test-datapacks/test_control_flow/data/test_control_flow/function/test_for_loop/then_3.mcfunction +3 -0
  99. package/test-datapacks/test_control_flow/data/test_control_flow/function/test_for_loop/then_4.mcfunction +3 -0
  100. package/test-datapacks/test_control_flow/data/test_control_flow/function/test_for_loop.mcfunction +4 -0
  101. package/test-datapacks/test_control_flow/data/test_control_flow/function/test_foreach_at/foreach_0.mcfunction +2 -0
  102. package/test-datapacks/test_control_flow/data/test_control_flow/function/test_foreach_at.mcfunction +5 -0
  103. package/test-datapacks/test_control_flow/data/test_control_flow/function/test_foreach_selector/else_1.mcfunction +3 -0
  104. package/test-datapacks/test_control_flow/data/test_control_flow/function/test_foreach_selector/foreach_0.mcfunction +4 -0
  105. package/test-datapacks/test_control_flow/data/test_control_flow/function/test_foreach_selector/merge_2.mcfunction +2 -0
  106. package/test-datapacks/test_control_flow/data/test_control_flow/function/test_foreach_selector/then_0.mcfunction +3 -0
  107. package/test-datapacks/test_control_flow/data/test_control_flow/function/test_foreach_selector.mcfunction +11 -0
  108. package/test-datapacks/test_control_flow/data/test_control_flow/function/test_if_else/else_1.mcfunction +3 -0
  109. package/test-datapacks/test_control_flow/data/test_control_flow/function/test_if_else/else_13.mcfunction +5 -0
  110. package/test-datapacks/test_control_flow/data/test_control_flow/function/test_if_else/else_16.mcfunction +3 -0
  111. package/test-datapacks/test_control_flow/data/test_control_flow/function/test_if_else/else_4.mcfunction +3 -0
  112. package/test-datapacks/test_control_flow/data/test_control_flow/function/test_if_else/merge_11.mcfunction +2 -0
  113. package/test-datapacks/test_control_flow/data/test_control_flow/function/test_if_else/merge_14.mcfunction +1 -0
  114. package/test-datapacks/test_control_flow/data/test_control_flow/function/test_if_else/merge_17.mcfunction +2 -0
  115. package/test-datapacks/test_control_flow/data/test_control_flow/function/test_if_else/merge_2.mcfunction +5 -0
  116. package/test-datapacks/test_control_flow/data/test_control_flow/function/test_if_else/merge_5.mcfunction +5 -0
  117. package/test-datapacks/test_control_flow/data/test_control_flow/function/test_if_else/merge_8.mcfunction +6 -0
  118. package/test-datapacks/test_control_flow/data/test_control_flow/function/test_if_else/then_0.mcfunction +3 -0
  119. package/test-datapacks/test_control_flow/data/test_control_flow/function/test_if_else/then_12.mcfunction +3 -0
  120. package/test-datapacks/test_control_flow/data/test_control_flow/function/test_if_else/then_15.mcfunction +3 -0
  121. package/test-datapacks/test_control_flow/data/test_control_flow/function/test_if_else/then_3.mcfunction +3 -0
  122. package/test-datapacks/test_control_flow/data/test_control_flow/function/test_if_else/then_6.mcfunction +5 -0
  123. package/test-datapacks/test_control_flow/data/test_control_flow/function/test_if_else/then_9.mcfunction +3 -0
  124. package/test-datapacks/test_control_flow/data/test_control_flow/function/test_if_else.mcfunction +6 -0
  125. package/test-datapacks/test_control_flow/data/test_control_flow/function/test_match/match_0.mcfunction +3 -0
  126. package/test-datapacks/test_control_flow/data/test_control_flow/function/test_match/match_1.mcfunction +3 -0
  127. package/test-datapacks/test_control_flow/data/test_control_flow/function/test_match/match_2.mcfunction +3 -0
  128. package/test-datapacks/test_control_flow/data/test_control_flow/function/test_match/match_3.mcfunction +2 -0
  129. package/test-datapacks/test_control_flow/data/test_control_flow/function/test_match/match_4.mcfunction +3 -0
  130. package/test-datapacks/test_control_flow/data/test_control_flow/function/test_match/match_5.mcfunction +3 -0
  131. package/test-datapacks/test_control_flow/data/test_control_flow/function/test_match/match_6.mcfunction +3 -0
  132. package/test-datapacks/test_control_flow/data/test_control_flow/function/test_match/match_7.mcfunction +3 -0
  133. package/test-datapacks/test_control_flow/data/test_control_flow/function/test_match/match_8.mcfunction +3 -0
  134. package/test-datapacks/test_control_flow/data/test_control_flow/function/test_match/match_9.mcfunction +2 -0
  135. package/test-datapacks/test_control_flow/data/test_control_flow/function/test_match.mcfunction +15 -0
  136. package/test-datapacks/test_control_flow/data/test_control_flow/function/test_while_loop/else_13.mcfunction +3 -0
  137. package/test-datapacks/test_control_flow/data/test_control_flow/function/test_while_loop/else_4.mcfunction +3 -0
  138. package/test-datapacks/test_control_flow/data/test_control_flow/function/test_while_loop/loop_body_1.mcfunction +5 -0
  139. package/test-datapacks/test_control_flow/data/test_control_flow/function/test_while_loop/loop_body_7.mcfunction +8 -0
  140. package/test-datapacks/test_control_flow/data/test_control_flow/function/test_while_loop/loop_check_0.mcfunction +5 -0
  141. package/test-datapacks/test_control_flow/data/test_control_flow/function/test_while_loop/loop_check_6.mcfunction +4 -0
  142. package/test-datapacks/test_control_flow/data/test_control_flow/function/test_while_loop/loop_exit_2.mcfunction +5 -0
  143. package/test-datapacks/test_control_flow/data/test_control_flow/function/test_while_loop/loop_exit_8.mcfunction +5 -0
  144. package/test-datapacks/test_control_flow/data/test_control_flow/function/test_while_loop/merge_11.mcfunction +2 -0
  145. package/test-datapacks/test_control_flow/data/test_control_flow/function/test_while_loop/merge_14.mcfunction +1 -0
  146. package/test-datapacks/test_control_flow/data/test_control_flow/function/test_while_loop/merge_5.mcfunction +3 -0
  147. package/test-datapacks/test_control_flow/data/test_control_flow/function/test_while_loop/then_12.mcfunction +3 -0
  148. package/test-datapacks/test_control_flow/data/test_control_flow/function/test_while_loop/then_3.mcfunction +3 -0
  149. package/test-datapacks/test_control_flow/data/test_control_flow/function/test_while_loop/then_9.mcfunction +2 -0
  150. package/test-datapacks/test_control_flow/data/test_control_flow/function/test_while_loop.mcfunction +3 -0
  151. package/test-datapacks/test_control_flow/pack.mcmeta +6 -0
  152. package/test-datapacks/test_control_flow.mcrs +242 -0
  153. package/test-datapacks/test_decorators/data/minecraft/tags/function/load.json +5 -0
  154. package/test-datapacks/test_decorators/data/minecraft/tags/function/tick.json +5 -0
  155. package/test-datapacks/test_decorators/data/test_decorators/function/__load.mcfunction +20 -0
  156. package/test-datapacks/test_decorators/data/test_decorators/function/__tick.mcfunction +3 -0
  157. package/test-datapacks/test_decorators/data/test_decorators/function/every_second/merge_2.mcfunction +9 -0
  158. package/test-datapacks/test_decorators/data/test_decorators/function/every_second/merge_5.mcfunction +1 -0
  159. package/test-datapacks/test_decorators/data/test_decorators/function/every_second/then_0.mcfunction +1 -0
  160. package/test-datapacks/test_decorators/data/test_decorators/function/every_second/then_3.mcfunction +4 -0
  161. package/test-datapacks/test_decorators/data/test_decorators/function/every_second/tick_body.mcfunction +6 -0
  162. package/test-datapacks/test_decorators/data/test_decorators/function/every_second/tick_skip.mcfunction +1 -0
  163. package/test-datapacks/test_decorators/data/test_decorators/function/every_second.mcfunction +5 -0
  164. package/test-datapacks/test_decorators/data/test_decorators/function/on_load.mcfunction +2 -0
  165. package/test-datapacks/test_decorators/data/test_decorators/function/on_test_trigger.mcfunction +2 -0
  166. package/test-datapacks/test_decorators/data/test_decorators/function/on_tick/merge_2.mcfunction +10 -0
  167. package/test-datapacks/test_decorators/data/test_decorators/function/on_tick/merge_5.mcfunction +5 -0
  168. package/test-datapacks/test_decorators/data/test_decorators/function/on_tick/merge_8.mcfunction +1 -0
  169. package/test-datapacks/test_decorators/data/test_decorators/function/on_tick/then_0.mcfunction +1 -0
  170. package/test-datapacks/test_decorators/data/test_decorators/function/on_tick/then_3.mcfunction +3 -0
  171. package/test-datapacks/test_decorators/data/test_decorators/function/on_tick/then_6.mcfunction +4 -0
  172. package/test-datapacks/test_decorators/data/test_decorators/function/on_tick.mcfunction +5 -0
  173. package/test-datapacks/test_decorators/data/test_decorators/function/run_decorator_tests.mcfunction +7 -0
  174. package/test-datapacks/test_decorators/data/test_decorators/function/setup_trigger_test.mcfunction +4 -0
  175. package/test-datapacks/test_decorators/data/test_decorators/function/start_slow_tick_test.mcfunction +4 -0
  176. package/test-datapacks/test_decorators/data/test_decorators/function/start_tick_test.mcfunction +4 -0
  177. package/test-datapacks/test_decorators/data/test_decorators/function/stop_tick_test.mcfunction +3 -0
  178. package/test-datapacks/test_decorators/pack.mcmeta +6 -0
  179. package/test-datapacks/test_decorators.mcrs +81 -0
  180. package/test-datapacks/test_events/data/minecraft/tags/function/load.json +5 -0
  181. package/test-datapacks/test_events/data/test_events/advancements/on_death_on_player_death.json +10 -0
  182. package/test-datapacks/test_events/data/test_events/function/__load.mcfunction +3 -0
  183. package/test-datapacks/test_events/data/test_events/function/on_player_death.mcfunction +3 -0
  184. package/test-datapacks/test_events/data/test_events/function/run_event_tests.mcfunction +5 -0
  185. package/test-datapacks/test_events/data/test_events/function/setup_events.mcfunction +3 -0
  186. package/test-datapacks/test_events/data/test_events/function/spawn_test_entity.mcfunction +3 -0
  187. package/test-datapacks/test_events/data/test_events/function/test_advancement_event.mcfunction +4 -0
  188. package/test-datapacks/test_events/pack.mcmeta +6 -0
  189. package/test-datapacks/test_events.mcrs +37 -0
  190. package/test-datapacks/test_fstrings/data/minecraft/tags/function/load.json +5 -0
  191. package/test-datapacks/test_fstrings/data/test_fstrings/function/__load.mcfunction +10 -0
  192. package/test-datapacks/test_fstrings/data/test_fstrings/function/run_fstring_tests.mcfunction +15 -0
  193. package/test-datapacks/test_fstrings/data/test_fstrings/function/test_actionbar_fstring.mcfunction +4 -0
  194. package/test-datapacks/test_fstrings/data/test_fstrings/function/test_announce.mcfunction +3 -0
  195. package/test-datapacks/test_fstrings/data/test_fstrings/function/test_multi_var_fstring.mcfunction +9 -0
  196. package/test-datapacks/test_fstrings/data/test_fstrings/function/test_say_fstring.mcfunction +4 -0
  197. package/test-datapacks/test_fstrings/data/test_fstrings/function/test_tell.mcfunction +3 -0
  198. package/test-datapacks/test_fstrings/data/test_fstrings/function/test_title_fstring.mcfunction +4 -0
  199. package/test-datapacks/test_fstrings/pack.mcmeta +6 -0
  200. package/test-datapacks/test_fstrings.mcrs +58 -0
  201. package/test-datapacks/test_timers/data/minecraft/tags/function/load.json +5 -0
  202. package/test-datapacks/test_timers/data/test_timers/function/__interval_0.mcfunction +3 -0
  203. package/test-datapacks/test_timers/data/test_timers/function/__interval_body_0.mcfunction +5 -0
  204. package/test-datapacks/test_timers/data/test_timers/function/__load.mcfunction +5 -0
  205. package/test-datapacks/test_timers/data/test_timers/function/__timeout_0.mcfunction +2 -0
  206. package/test-datapacks/test_timers/data/test_timers/function/run_timer_tests.mcfunction +5 -0
  207. package/test-datapacks/test_timers/data/test_timers/function/test_set_interval.mcfunction +5 -0
  208. package/test-datapacks/test_timers/data/test_timers/function/test_set_timeout.mcfunction +3 -0
  209. package/test-datapacks/test_timers/pack.mcmeta +6 -0
  210. package/test-datapacks/test_timers.mcrs +35 -0
package/README.md CHANGED
@@ -20,55 +20,56 @@ Write clean game logic. RedScript handles the scoreboard spaghetti.
20
20
 
21
21
  ### 🚀 [Try it online — no install needed!](https://redscript-ide.pages.dev)
22
22
 
23
+ <img src="./demo.gif" alt="RedScript Demo" width="400" />
24
+
25
+ *↑ Particles spawning at player position every tick — 100% vanilla, no mods! Just 30 lines of RedScript with full control flow: `if`, `foreach`, `@tick`, f-strings, and more.*
26
+
23
27
  </div>
24
28
 
25
29
  ---
26
30
 
27
31
  ### What is RedScript?
28
32
 
29
- You want to make a Minecraft mini-game. You need a countdown timer, kill counter, respawn logic, and a scoreboard display. In vanilla MC, that's 40+ `.mcfunction` files, hundreds of `execute if score` commands, and a weekend of debugging.
33
+ RedScript is a typed scripting language that compiles to vanilla Minecraft datapacks. Write clean code with variables, functions, loops, and events RedScript handles the scoreboard commands and `.mcfunction` files for you.
30
34
 
31
- With RedScript, it's this:
35
+ **The demo above?** Just 30 lines:
32
36
 
33
37
  ```rs
34
- // pvp_game.mcrs
35
- import "stdlib/player.mcrs"
36
-
37
- const GAME_TIME: int = 300;
38
-
39
- @tick(rate=20)
40
- fn every_second() {
41
- let time: int = scoreboard_get(#game, #timer);
42
-
43
- if (time <= 0) {
44
- end_game();
45
- return;
38
+ let counter: int = 0;
39
+ let running: bool = false;
40
+
41
+ @tick fn demo_tick() {
42
+ if (!running) { return; }
43
+ counter = counter + 1;
44
+
45
+ foreach (p in @a) at @s {
46
+ particle("minecraft:end_rod", ~0, ~1, ~0, 0.5, 0.5, 0.5, 0.1, 5);
47
+ }
48
+
49
+ if (counter % 20 == 0) {
50
+ say(f"Running for {counter} ticks");
46
51
  }
47
-
48
- scoreboard_set(#game, #timer, time - 1);
49
- actionbar(@a, "⏱ ${time}s remaining");
50
- }
51
-
52
- fn start_game() {
53
- scoreboard_set(#game, #timer, GAME_TIME);
54
- scoreboard_set(#game, #running, 1);
55
- title(@a, "Fight!", "Game started");
56
- tp(@a, (0, 64, 0));
57
52
  }
58
53
 
59
- fn end_game() {
60
- scoreboard_set(#game, #running, 0);
61
- title(@a, "Game Over!");
62
- announce("Thanks for playing!");
54
+ @keep fn start() {
55
+ running = true;
56
+ counter = 0;
57
+ say(f"Demo started!");
63
58
  }
64
59
 
65
- @on_death
66
- fn on_kill() {
67
- scoreboard_add(@s, #kills, 1);
60
+ @keep fn stop() {
61
+ running = false;
62
+ say(f"Demo stopped at {counter} ticks.");
68
63
  }
69
64
  ```
70
65
 
71
- One file. Compiles to a ready-to-use datapack in seconds.
66
+ **What you get:**
67
+ - ✅ `let` / `const` variables (no more `scoreboard players set`)
68
+ - ✅ `if` / `else` / `for` / `foreach` control flow
69
+ - ✅ `@tick` / `@load` / `@on(Event)` decorators
70
+ - ✅ `foreach (p in @a) at @s` — iterate entities with execute context
71
+ - ✅ f-strings like `f"Score: {points}"` for dynamic output
72
+ - ✅ One file → ready-to-use datapack
72
73
 
73
74
  ---
74
75
 
@@ -199,7 +200,8 @@ if (hp <= 0) {
199
200
  }
200
201
 
201
202
  for (let i: int = 0; i < 10; i = i + 1) {
202
- summon("minecraft:zombie", (i, 64, 0));
203
+ say(f"Spawning wave {i}");
204
+ summon("minecraft:zombie", ~0, ~0, ~0);
203
205
  }
204
206
 
205
207
  foreach (player in @a) {
package/README.zh.md CHANGED
@@ -20,55 +20,56 @@
20
20
 
21
21
  ### 🚀 [在线试用 — 无需安装!](https://redscript-ide.pages.dev)
22
22
 
23
+ <img src="./demo.gif" alt="RedScript Demo" width="400" />
24
+
25
+ *↑ 每 tick 在玩家位置生成粒子 — 纯原版,无需 MOD!仅 30 行 RedScript,包含完整控制流:`if`、`foreach`、`@tick`、f-strings 等。*
26
+
23
27
  </div>
24
28
 
25
29
  ---
26
30
 
27
31
  ### RedScript 是什么?
28
32
 
29
- 你想做一个 Minecraft 小游戏——倒计时、击杀计数、复活逻辑、记分板显示。用原版 MC 的话,这意味着 40+ 个 `.mcfunction` 文件、几百条 `execute if score` 命令,还要花一个周末调试。
33
+ RedScript 是一门编译到原版 Minecraft 数据包的脚本语言。用变量、函数、循环、事件写代码,RedScript 帮你生成记分板命令和 `.mcfunction` 文件。
30
34
 
31
- RedScript,就是这样:
35
+ **上面的演示?** 只有 30 行:
32
36
 
33
37
  ```rs
34
- // pvp_game.mcrs
35
- import "stdlib/player.mcrs"
36
-
37
- const GAME_TIME: int = 300;
38
-
39
- @tick(rate=20)
40
- fn every_second() {
41
- let time: int = scoreboard_get(#game, #timer);
42
-
43
- if (time <= 0) {
44
- end_game();
45
- return;
38
+ let counter: int = 0;
39
+ let running: bool = false;
40
+
41
+ @tick fn demo_tick() {
42
+ if (!running) { return; }
43
+ counter = counter + 1;
44
+
45
+ foreach (p in @a) at @s {
46
+ particle("minecraft:end_rod", ~0, ~1, ~0, 0.5, 0.5, 0.5, 0.1, 5);
47
+ }
48
+
49
+ if (counter % 20 == 0) {
50
+ say(f"已运行 {counter} ticks");
46
51
  }
47
-
48
- scoreboard_set(#game, #timer, time - 1);
49
- actionbar(@a, "⏱ 剩余 ${time} 秒");
50
- }
51
-
52
- fn start_game() {
53
- scoreboard_set(#game, #timer, GAME_TIME);
54
- scoreboard_set(#game, #running, 1);
55
- title(@a, "开始战斗!", "游戏已开始");
56
- tp(@a, (0, 64, 0));
57
52
  }
58
53
 
59
- fn end_game() {
60
- scoreboard_set(#game, #running, 0);
61
- title(@a, "游戏结束!");
62
- announce("感谢游玩!");
54
+ @keep fn start() {
55
+ running = true;
56
+ counter = 0;
57
+ say(f"Demo 已启动!");
63
58
  }
64
59
 
65
- @on_death
66
- fn on_kill() {
67
- scoreboard_add(@s, #kills, 1);
60
+ @keep fn stop() {
61
+ running = false;
62
+ say(f"Demo 已停止,共运行 {counter} ticks。");
68
63
  }
69
64
  ```
70
65
 
71
- 一个文件,几秒钟编译出可以直接用的 datapack。
66
+ **你得到:**
67
+ - ✅ `let` / `const` 变量(告别 `scoreboard players set`)
68
+ - ✅ `if` / `else` / `for` / `foreach` 完整控制流
69
+ - ✅ `@tick` / `@load` / `@on(Event)` 装饰器
70
+ - ✅ `foreach (p in @a) at @s` — 遍历实体并设置执行上下文
71
+ - ✅ f-strings 如 `f"分数: {points}"` 动态输出
72
+ - ✅ 一个文件 → 可直接使用的 datapack
72
73
 
73
74
  ---
74
75
 
@@ -199,7 +200,8 @@ if (hp <= 0) {
199
200
  }
200
201
 
201
202
  for (let i: int = 0; i < 10; i = i + 1) {
202
- summon("minecraft:zombie", (i, 64, 0));
203
+ say(f"生成第 {i} 波");
204
+ summon("minecraft:zombie", ~0, ~0, ~0);
203
205
  }
204
206
 
205
207
  foreach (player in @a) {
package/demo.gif ADDED
Binary file
@@ -275,6 +275,12 @@ export type Stmt = {
275
275
  kind: 'return';
276
276
  value?: Expr;
277
277
  span?: Span;
278
+ } | {
279
+ kind: 'break';
280
+ span?: Span;
281
+ } | {
282
+ kind: 'continue';
283
+ span?: Span;
278
284
  } | {
279
285
  kind: 'if';
280
286
  cond: Expr;
package/dist/index.d.ts CHANGED
@@ -3,7 +3,7 @@
3
3
  *
4
4
  * Main entry point for programmatic usage.
5
5
  */
6
- export declare const version = "1.2.9";
6
+ export declare const version = "1.2.10";
7
7
  import type { Warning } from './lowering';
8
8
  import { DatapackFile } from './codegen/mcfunction';
9
9
  import type { IRModule } from './ir/types';
package/dist/index.js CHANGED
@@ -9,7 +9,7 @@ exports.MCCommandValidator = exports.generateDatapack = exports.optimize = expor
9
9
  exports.compile = compile;
10
10
  exports.check = check;
11
11
  // eslint-disable-next-line @typescript-eslint/no-var-requires
12
- exports.version = '1.2.9';
12
+ exports.version = '1.2.10';
13
13
  const lexer_1 = require("./lexer");
14
14
  const parser_1 = require("./parser");
15
15
  const typechecker_1 = require("./typechecker");
@@ -5,7 +5,7 @@
5
5
  * Handles special cases like entity selectors vs decorators,
6
6
  * range literals, and raw commands.
7
7
  */
8
- export type TokenKind = 'fn' | 'let' | 'const' | 'if' | 'else' | 'while' | 'for' | 'foreach' | 'match' | 'return' | 'as' | 'at' | 'in' | 'is' | 'struct' | 'impl' | 'enum' | 'trigger' | 'namespace' | 'execute' | 'run' | 'unless' | 'int' | 'bool' | 'float' | 'string' | 'void' | 'BlockPos' | 'true' | 'false' | 'selector' | 'decorator' | 'int_lit' | 'float_lit' | 'byte_lit' | 'short_lit' | 'long_lit' | 'double_lit' | 'string_lit' | 'f_string' | 'range_lit' | 'rel_coord' | 'local_coord' | '+' | '-' | '*' | '/' | '%' | '~' | '^' | '==' | '!=' | '<' | '<=' | '>' | '>=' | '&&' | '||' | '!' | '=' | '+=' | '-=' | '*=' | '/=' | '%=' | '{' | '}' | '(' | ')' | '[' | ']' | ',' | ';' | ':' | '::' | '->' | '=>' | '.' | 'ident' | 'mc_name' | 'raw_cmd' | 'eof';
8
+ export type TokenKind = 'fn' | 'let' | 'const' | 'if' | 'else' | 'while' | 'for' | 'foreach' | 'match' | 'return' | 'break' | 'continue' | 'as' | 'at' | 'in' | 'is' | 'struct' | 'impl' | 'enum' | 'trigger' | 'namespace' | 'execute' | 'run' | 'unless' | 'int' | 'bool' | 'float' | 'string' | 'void' | 'BlockPos' | 'true' | 'false' | 'selector' | 'decorator' | 'int_lit' | 'float_lit' | 'byte_lit' | 'short_lit' | 'long_lit' | 'double_lit' | 'string_lit' | 'f_string' | 'range_lit' | 'rel_coord' | 'local_coord' | '+' | '-' | '*' | '/' | '%' | '~' | '^' | '==' | '!=' | '<' | '<=' | '>' | '>=' | '&&' | '||' | '!' | '=' | '+=' | '-=' | '*=' | '/=' | '%=' | '{' | '}' | '(' | ')' | '[' | ']' | ',' | ';' | ':' | '::' | '->' | '=>' | '.' | 'ident' | 'mc_name' | 'raw_cmd' | 'eof';
9
9
  export interface Token {
10
10
  kind: TokenKind;
11
11
  value: string;
@@ -23,6 +23,8 @@ const KEYWORDS = {
23
23
  foreach: 'foreach',
24
24
  match: 'match',
25
25
  return: 'return',
26
+ break: 'break',
27
+ continue: 'continue',
26
28
  as: 'as',
27
29
  at: 'at',
28
30
  in: 'in',
@@ -45,6 +45,7 @@ export declare class Lowering {
45
45
  private varTypes;
46
46
  private floatVars;
47
47
  private worldObjCounter;
48
+ private loopStack;
48
49
  constructor(namespace: string, sourceRanges?: SourceRange[]);
49
50
  lower(program: Program): IRModule;
50
51
  private lowerFn;
@@ -54,6 +55,8 @@ export declare class Lowering {
54
55
  private lowerStmt;
55
56
  private lowerLetStmt;
56
57
  private lowerReturnStmt;
58
+ private lowerBreakStmt;
59
+ private lowerContinueStmt;
57
60
  private lowerIfStmt;
58
61
  private lowerIsCheckIfStmt;
59
62
  private lowerWhileStmt;
@@ -209,6 +209,8 @@ class Lowering {
209
209
  this.floatVars = new Set();
210
210
  // World object counter for unique tags
211
211
  this.worldObjCounter = 0;
212
+ // Loop context stack for break/continue
213
+ this.loopStack = [];
212
214
  this.namespace = namespace;
213
215
  this.sourceRanges = sourceRanges;
214
216
  LoweringBuilder.resetTempCounter();
@@ -462,6 +464,12 @@ class Lowering {
462
464
  case 'return':
463
465
  this.lowerReturnStmt(stmt);
464
466
  break;
467
+ case 'break':
468
+ this.lowerBreakStmt();
469
+ break;
470
+ case 'continue':
471
+ this.lowerContinueStmt();
472
+ break;
465
473
  case 'if':
466
474
  this.lowerIfStmt(stmt);
467
475
  break;
@@ -599,6 +607,20 @@ class Lowering {
599
607
  this.builder.emitReturn();
600
608
  }
601
609
  }
610
+ lowerBreakStmt() {
611
+ if (this.loopStack.length === 0) {
612
+ throw new diagnostics_1.DiagnosticError('LoweringError', 'break statement outside of loop', { line: 1, col: 1 });
613
+ }
614
+ const loop = this.loopStack[this.loopStack.length - 1];
615
+ this.builder.emitJump(loop.breakLabel);
616
+ }
617
+ lowerContinueStmt() {
618
+ if (this.loopStack.length === 0) {
619
+ throw new diagnostics_1.DiagnosticError('LoweringError', 'continue statement outside of loop', { line: 1, col: 1 });
620
+ }
621
+ const loop = this.loopStack[this.loopStack.length - 1];
622
+ this.builder.emitJump(loop.continueLabel);
623
+ }
602
624
  lowerIfStmt(stmt) {
603
625
  if (stmt.cond.kind === 'is_check') {
604
626
  this.lowerIsCheckIfStmt(stmt);
@@ -671,12 +693,16 @@ class Lowering {
671
693
  const condVar = this.lowerExpr(stmt.cond);
672
694
  const condName = this.operandToVar(condVar);
673
695
  this.builder.emitJumpIf(condName, bodyLabel, exitLabel);
696
+ // Push loop context for break/continue (while has no step, so continue goes to check)
697
+ this.loopStack.push({ breakLabel: exitLabel, continueLabel: checkLabel });
674
698
  // Body block
675
699
  this.builder.startBlock(bodyLabel);
676
700
  this.lowerBlock(stmt.body);
677
701
  if (!this.builder.isBlockSealed()) {
678
702
  this.builder.emitJump(checkLabel);
679
703
  }
704
+ // Pop loop context
705
+ this.loopStack.pop();
680
706
  // Exit block
681
707
  this.builder.startBlock(exitLabel);
682
708
  }
@@ -688,6 +714,7 @@ class Lowering {
688
714
  }
689
715
  const checkLabel = this.builder.freshLabel('for_check');
690
716
  const bodyLabel = this.builder.freshLabel('for_body');
717
+ const continueLabel = this.builder.freshLabel('for_continue');
691
718
  const exitLabel = this.builder.freshLabel('for_exit');
692
719
  this.builder.emitJump(checkLabel);
693
720
  // Check block
@@ -695,14 +722,20 @@ class Lowering {
695
722
  const condVar = this.lowerExpr(stmt.cond);
696
723
  const condName = this.operandToVar(condVar);
697
724
  this.builder.emitJumpIf(condName, bodyLabel, exitLabel);
725
+ // Push loop context for break/continue
726
+ this.loopStack.push({ breakLabel: exitLabel, continueLabel });
698
727
  // Body block
699
728
  this.builder.startBlock(bodyLabel);
700
729
  this.lowerBlock(stmt.body);
701
- // Step expression
702
- this.lowerExpr(stmt.step);
703
730
  if (!this.builder.isBlockSealed()) {
704
- this.builder.emitJump(checkLabel);
731
+ this.builder.emitJump(continueLabel);
705
732
  }
733
+ // Continue block (step + loop back)
734
+ this.builder.startBlock(continueLabel);
735
+ this.lowerExpr(stmt.step);
736
+ this.builder.emitJump(checkLabel);
737
+ // Pop loop context
738
+ this.loopStack.pop();
706
739
  // Exit block
707
740
  this.builder.startBlock(exitLabel);
708
741
  }
@@ -794,12 +827,32 @@ class Lowering {
794
827
  defaultArm = arm;
795
828
  continue;
796
829
  }
797
- const patternValue = this.lowerExpr(arm.pattern);
798
- if (patternValue.kind !== 'const') {
799
- throw new Error('Match patterns must lower to compile-time constants');
830
+ // Handle range patterns specially
831
+ let matchCondition;
832
+ if (arm.pattern.kind === 'range_lit') {
833
+ const range = arm.pattern.range;
834
+ if (range.min !== undefined && range.max !== undefined) {
835
+ matchCondition = `${range.min}..${range.max}`;
836
+ }
837
+ else if (range.min !== undefined) {
838
+ matchCondition = `${range.min}..`;
839
+ }
840
+ else if (range.max !== undefined) {
841
+ matchCondition = `..${range.max}`;
842
+ }
843
+ else {
844
+ matchCondition = '0..'; // Match any
845
+ }
846
+ }
847
+ else {
848
+ const patternValue = this.lowerExpr(arm.pattern);
849
+ if (patternValue.kind !== 'const') {
850
+ throw new Error('Match patterns must lower to compile-time constants');
851
+ }
852
+ matchCondition = String(patternValue.value);
800
853
  }
801
854
  const subFnName = `${this.currentFn}/match_${this.foreachCounter++}`;
802
- this.builder.emitRaw(`execute if score ${matchedVar} rs matches ..0 if score ${subject} rs matches ${patternValue.value} run function ${this.namespace}:${subFnName}`);
855
+ this.builder.emitRaw(`execute if score ${matchedVar} rs matches ..0 if score ${subject} rs matches ${matchCondition} run function ${this.namespace}:${subFnName}`);
803
856
  this.emitMatchArmSubFunction(subFnName, matchedVar, arm.body, true);
804
857
  }
805
858
  if (defaultArm) {
@@ -241,6 +241,8 @@ class DeadCodeEliminator {
241
241
  this.collectNestedStmtRefs(stmt, scope);
242
242
  break;
243
243
  case 'raw':
244
+ case 'break':
245
+ case 'continue':
244
246
  break;
245
247
  }
246
248
  }
@@ -508,6 +510,10 @@ class DeadCodeEliminator {
508
510
  return [copySpan({ ...stmt, body: this.transformBlock(stmt.body, scope) }, stmt)];
509
511
  case 'raw':
510
512
  return [stmt];
513
+ case 'break':
514
+ return [stmt];
515
+ case 'continue':
516
+ return [stmt];
511
517
  }
512
518
  }
513
519
  transformExpr(expr, scope) {
@@ -397,6 +397,18 @@ class Parser {
397
397
  if (this.check('return')) {
398
398
  return this.parseReturnStmt();
399
399
  }
400
+ // Break statement
401
+ if (this.check('break')) {
402
+ const token = this.advance();
403
+ this.match(';');
404
+ return this.withLoc({ kind: 'break' }, token);
405
+ }
406
+ // Continue statement
407
+ if (this.check('continue')) {
408
+ const token = this.advance();
409
+ this.match(';');
410
+ return this.withLoc({ kind: 'continue' }, token);
411
+ }
400
412
  // If statement
401
413
  if (this.check('if')) {
402
414
  return this.parseIfStmt();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "redscript-mc",
3
- "version": "1.2.9",
3
+ "version": "1.2.10",
4
4
  "description": "A high-level programming language that compiles to Minecraft datapacks",
5
5
  "main": "dist/index.js",
6
6
  "bin": {
package/src/ast/types.ts CHANGED
@@ -189,6 +189,8 @@ export type Stmt =
189
189
  | { kind: 'let'; name: string; type?: TypeNode; init: Expr; span?: Span }
190
190
  | { kind: 'expr'; expr: Expr; span?: Span }
191
191
  | { kind: 'return'; value?: Expr; span?: Span }
192
+ | { kind: 'break'; span?: Span }
193
+ | { kind: 'continue'; span?: Span }
192
194
  | { kind: 'if'; cond: Expr; then: Block; else_?: Block; span?: Span }
193
195
  | { kind: 'while'; cond: Expr; body: Block; span?: Span }
194
196
  | { 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.10'
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
 
@@ -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()