redscript-mc 1.2.30 → 2.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claudeignore +21 -0
- package/.github/workflows/ci.yml +1 -0
- package/README.md +12 -16
- package/README.zh.md +2 -2
- package/demo.gif +0 -0
- package/dist/cli.js +2 -554
- package/dist/compile.js +2 -266
- package/dist/index.js +2 -159
- package/dist/src/__tests__/budget.test.js +261 -0
- package/dist/src/__tests__/cli.test.js +104 -0
- package/dist/{__tests__ → src/__tests__}/dce.test.js +11 -47
- package/dist/{__tests__ → src/__tests__}/diagnostics.test.js +67 -40
- package/dist/src/__tests__/e2e/basic.test.d.ts +8 -0
- package/dist/src/__tests__/e2e/basic.test.js +140 -0
- package/dist/src/__tests__/e2e/coroutine.test.d.ts +7 -0
- package/dist/src/__tests__/e2e/coroutine.test.js +132 -0
- package/dist/src/__tests__/e2e/macros.test.d.ts +9 -0
- package/dist/src/__tests__/e2e/macros.test.js +182 -0
- package/dist/src/__tests__/e2e/migrate.test.d.ts +13 -0
- package/dist/src/__tests__/e2e/migrate.test.js +2739 -0
- package/dist/src/__tests__/e2e/stdlib-e2e.test.d.ts +10 -0
- package/dist/src/__tests__/e2e/stdlib-e2e.test.js +324 -0
- package/dist/src/__tests__/enum.test.d.ts +10 -0
- package/dist/src/__tests__/enum.test.js +389 -0
- package/dist/src/__tests__/generics.test.d.ts +14 -0
- package/dist/src/__tests__/generics.test.js +367 -0
- package/dist/src/__tests__/hir/desugar.test.js +234 -0
- package/dist/src/__tests__/incremental.test.d.ts +5 -0
- package/dist/src/__tests__/incremental.test.js +308 -0
- package/dist/src/__tests__/lir/lower.test.js +559 -0
- package/dist/src/__tests__/lir/types.test.js +185 -0
- package/dist/src/__tests__/lir/verify.test.js +221 -0
- package/dist/src/__tests__/lsp.test.d.ts +7 -0
- package/dist/src/__tests__/lsp.test.js +245 -0
- package/dist/{__tests__ → src/__tests__}/mc-integration.test.js +1 -3
- package/dist/src/__tests__/mc-version.test.d.ts +10 -0
- package/dist/src/__tests__/mc-version.test.js +154 -0
- package/dist/src/__tests__/mir/arithmetic.test.js +130 -0
- package/dist/src/__tests__/mir/control-flow.test.js +205 -0
- package/dist/src/__tests__/mir/verify.test.js +223 -0
- package/dist/src/__tests__/modules.test.d.ts +7 -0
- package/dist/src/__tests__/modules.test.js +333 -0
- package/dist/src/__tests__/optimizer/block_merge.test.js +78 -0
- package/dist/src/__tests__/optimizer/branch_simplify.test.js +58 -0
- package/dist/src/__tests__/optimizer/constant_fold.test.js +131 -0
- package/dist/src/__tests__/optimizer/copy_prop.test.js +91 -0
- package/dist/src/__tests__/optimizer/coroutine.test.d.ts +12 -0
- package/dist/src/__tests__/optimizer/coroutine.test.js +251 -0
- package/dist/src/__tests__/optimizer/dce.test.d.ts +1 -0
- package/dist/src/__tests__/optimizer/dce.test.js +76 -0
- package/dist/src/__tests__/optimizer/interprocedural.test.d.ts +1 -0
- package/dist/src/__tests__/optimizer/interprocedural.test.js +145 -0
- package/dist/src/__tests__/optimizer/lir/const_imm.test.d.ts +1 -0
- package/dist/src/__tests__/optimizer/lir/const_imm.test.js +138 -0
- package/dist/src/__tests__/optimizer/lir/dead_slot.test.d.ts +1 -0
- package/dist/src/__tests__/optimizer/lir/dead_slot.test.js +141 -0
- package/dist/src/__tests__/optimizer/lir/peephole.test.d.ts +1 -0
- package/dist/src/__tests__/optimizer/lir/peephole.test.js +126 -0
- package/dist/src/__tests__/optimizer/lir/pipeline.test.d.ts +1 -0
- package/dist/src/__tests__/optimizer/lir/pipeline.test.js +84 -0
- package/dist/src/__tests__/optimizer/nbt-batch.test.d.ts +1 -0
- package/dist/src/__tests__/optimizer/nbt-batch.test.js +110 -0
- package/dist/src/__tests__/optimizer/pipeline.test.d.ts +1 -0
- package/dist/src/__tests__/optimizer/pipeline.test.js +102 -0
- package/dist/src/__tests__/optimizer/selector-cache.test.d.ts +1 -0
- package/dist/src/__tests__/optimizer/selector-cache.test.js +103 -0
- package/dist/src/__tests__/optimizer/unroll.test.d.ts +1 -0
- package/dist/src/__tests__/optimizer/unroll.test.js +206 -0
- package/dist/src/__tests__/option.test.d.ts +14 -0
- package/dist/src/__tests__/option.test.js +275 -0
- package/dist/src/__tests__/parser.test.d.ts +1 -0
- package/dist/src/__tests__/repl.test.d.ts +1 -0
- package/dist/src/__tests__/schedule.test.d.ts +7 -0
- package/dist/src/__tests__/schedule.test.js +98 -0
- package/dist/src/__tests__/sourcemap.test.d.ts +7 -0
- package/dist/src/__tests__/sourcemap.test.js +227 -0
- package/dist/src/__tests__/tuple.test.d.ts +11 -0
- package/dist/src/__tests__/tuple.test.js +202 -0
- package/dist/src/__tests__/typechecker-strict.test.d.ts +10 -0
- package/dist/src/__tests__/typechecker-strict.test.js +197 -0
- package/dist/src/__tests__/typechecker.test.d.ts +1 -0
- package/dist/{ast → src/ast}/types.d.ts +58 -3
- package/dist/src/cache/deps.d.ts +41 -0
- package/dist/src/cache/deps.js +158 -0
- package/dist/src/cache/incremental.d.ts +35 -0
- package/dist/src/cache/incremental.js +165 -0
- package/dist/src/cache/index.d.ts +37 -0
- package/dist/src/cache/index.js +152 -0
- package/dist/{cli.d.ts → src/cli.d.ts} +1 -1
- package/dist/src/cli.js +474 -0
- package/dist/src/compile.d.ts +37 -0
- package/dist/src/compile.js +165 -0
- package/dist/{diagnostics → src/diagnostics}/index.d.ts +1 -1
- package/dist/{diagnostics → src/diagnostics}/index.js +8 -11
- package/dist/src/emit/compile.d.ts +29 -0
- package/dist/src/emit/compile.js +143 -0
- package/dist/src/emit/index.d.ts +26 -0
- package/dist/src/emit/index.js +223 -0
- package/dist/src/emit/modules.d.ts +29 -0
- package/dist/src/emit/modules.js +492 -0
- package/dist/src/emit/sourcemap.d.ts +53 -0
- package/dist/src/emit/sourcemap.js +73 -0
- package/dist/src/hir/lower.d.ts +15 -0
- package/dist/src/hir/lower.js +399 -0
- package/dist/src/hir/monomorphize.d.ts +22 -0
- package/dist/src/hir/monomorphize.js +379 -0
- package/dist/src/hir/types.d.ts +406 -0
- package/dist/src/hir/types.js +16 -0
- package/dist/src/index.d.ts +39 -0
- package/dist/src/index.js +67 -0
- package/dist/{lexer → src/lexer}/index.d.ts +1 -1
- package/dist/{lexer → src/lexer}/index.js +1 -0
- package/dist/src/lir/budget.d.ts +37 -0
- package/dist/src/lir/budget.js +280 -0
- package/dist/src/lir/lower.d.ts +15 -0
- package/dist/src/lir/lower.js +472 -0
- package/dist/src/lir/types.d.ts +139 -0
- package/dist/src/lir/types.js +11 -0
- package/dist/src/lir/verify.d.ts +14 -0
- package/dist/src/lir/verify.js +113 -0
- package/dist/src/lsp/main.d.ts +8 -0
- package/dist/src/lsp/main.js +11 -0
- package/dist/src/lsp/server.d.ts +11 -0
- package/dist/src/lsp/server.js +352 -0
- package/dist/{mc-test → src/mc-test}/runner.js +4 -3
- package/dist/src/mir/lower.d.ts +9 -0
- package/dist/src/mir/lower.js +1264 -0
- package/dist/src/mir/macro.d.ts +22 -0
- package/dist/src/mir/macro.js +168 -0
- package/dist/src/mir/types.d.ts +191 -0
- package/dist/src/mir/types.js +11 -0
- package/dist/src/mir/verify.d.ts +16 -0
- package/dist/src/mir/verify.js +216 -0
- package/dist/src/optimizer/block_merge.d.ts +12 -0
- package/dist/src/optimizer/block_merge.js +84 -0
- package/dist/src/optimizer/branch_simplify.d.ts +9 -0
- package/dist/src/optimizer/branch_simplify.js +28 -0
- package/dist/src/optimizer/constant_fold.d.ts +10 -0
- package/dist/src/optimizer/constant_fold.js +85 -0
- package/dist/src/optimizer/copy_prop.d.ts +9 -0
- package/dist/src/optimizer/copy_prop.js +113 -0
- package/dist/src/optimizer/coroutine.d.ts +34 -0
- package/dist/src/optimizer/coroutine.js +789 -0
- package/dist/src/optimizer/dce.d.ts +8 -0
- package/dist/src/optimizer/dce.js +156 -0
- package/dist/src/optimizer/interprocedural.d.ts +14 -0
- package/dist/src/optimizer/interprocedural.js +186 -0
- package/dist/src/optimizer/lir/const_imm.d.ts +12 -0
- package/dist/src/optimizer/lir/const_imm.js +139 -0
- package/dist/src/optimizer/lir/dead_slot.d.ts +14 -0
- package/dist/src/optimizer/lir/dead_slot.js +130 -0
- package/dist/src/optimizer/lir/peephole.d.ts +21 -0
- package/dist/src/optimizer/lir/peephole.js +52 -0
- package/dist/src/optimizer/lir/pipeline.d.ts +10 -0
- package/dist/src/optimizer/lir/pipeline.js +34 -0
- package/dist/src/optimizer/nbt-batch.d.ts +11 -0
- package/dist/src/optimizer/nbt-batch.js +51 -0
- package/dist/src/optimizer/pipeline.d.ts +14 -0
- package/dist/src/optimizer/pipeline.js +58 -0
- package/dist/src/optimizer/selector-cache.d.ts +22 -0
- package/dist/src/optimizer/selector-cache.js +100 -0
- package/dist/src/optimizer/unroll.d.ts +32 -0
- package/dist/src/optimizer/unroll.js +348 -0
- package/dist/{parser → src/parser}/index.d.ts +8 -0
- package/dist/{parser → src/parser}/index.js +204 -14
- package/dist/{repl.d.ts → src/repl.d.ts} +1 -1
- package/dist/{runtime → src/runtime}/index.js +1 -1
- package/dist/{typechecker → src/typechecker}/index.d.ts +4 -0
- package/dist/{typechecker → src/typechecker}/index.js +198 -13
- package/dist/src/types/mc-version.d.ts +24 -0
- package/dist/src/types/mc-version.js +49 -0
- package/docs/ROADMAP.md +395 -0
- package/docs/compiler-pipeline-redesign.md +2260 -0
- package/docs/optimization-ideas.md +1076 -0
- package/editors/vscode/out/extension.js +25176 -8000
- package/editors/vscode/package-lock.json +90 -6
- package/editors/vscode/package.json +3 -2
- package/editors/vscode/src/extension.ts +97 -67
- package/examples/showcase.mcrs +3 -3
- package/package.json +13 -6
- package/scripts/postbuild.js +15 -0
- package/src/__tests__/budget.test.ts +297 -0
- package/src/__tests__/cli.test.ts +8 -220
- package/src/__tests__/dce.test.ts +11 -56
- package/src/__tests__/diagnostics.test.ts +61 -41
- package/src/__tests__/e2e/basic.test.ts +154 -0
- package/src/__tests__/e2e/coroutine.test.ts +142 -0
- package/src/__tests__/e2e/macros.test.ts +199 -0
- package/src/__tests__/e2e/migrate.test.ts +3008 -0
- package/src/__tests__/e2e/stdlib-e2e.test.ts +348 -0
- package/src/__tests__/enum.test.ts +425 -0
- package/src/__tests__/generics.test.ts +390 -0
- package/src/__tests__/hir/desugar.test.ts +263 -0
- package/src/__tests__/incremental.test.ts +337 -0
- package/src/__tests__/lir/lower.test.ts +619 -0
- package/src/__tests__/lir/types.test.ts +207 -0
- package/src/__tests__/lir/verify.test.ts +249 -0
- package/src/__tests__/lsp.test.ts +270 -0
- package/src/__tests__/mc-integration.test.ts +1 -2
- package/src/__tests__/mc-version.test.ts +178 -0
- package/src/__tests__/mir/arithmetic.test.ts +156 -0
- package/src/__tests__/mir/control-flow.test.ts +242 -0
- package/src/__tests__/mir/verify.test.ts +254 -0
- package/src/__tests__/modules.test.ts +365 -0
- package/src/__tests__/optimizer/block_merge.test.ts +84 -0
- package/src/__tests__/optimizer/branch_simplify.test.ts +64 -0
- package/src/__tests__/optimizer/constant_fold.test.ts +145 -0
- package/src/__tests__/optimizer/copy_prop.test.ts +99 -0
- package/src/__tests__/optimizer/coroutine.test.ts +312 -0
- package/src/__tests__/optimizer/dce.test.ts +83 -0
- package/src/__tests__/optimizer/interprocedural.test.ts +174 -0
- package/src/__tests__/optimizer/lir/const_imm.test.ts +151 -0
- package/src/__tests__/optimizer/lir/dead_slot.test.ts +156 -0
- package/src/__tests__/optimizer/lir/peephole.test.ts +136 -0
- package/src/__tests__/optimizer/lir/pipeline.test.ts +113 -0
- package/src/__tests__/optimizer/nbt-batch.test.ts +119 -0
- package/src/__tests__/optimizer/pipeline.test.ts +116 -0
- package/src/__tests__/optimizer/selector-cache.test.ts +112 -0
- package/src/__tests__/optimizer/unroll.test.ts +231 -0
- package/src/__tests__/option.test.ts +299 -0
- package/src/__tests__/schedule.test.ts +105 -0
- package/src/__tests__/sourcemap.test.ts +254 -0
- package/src/__tests__/tuple.test.ts +220 -0
- package/src/__tests__/typechecker-strict.test.ts +216 -0
- package/src/ast/types.ts +39 -3
- package/src/cache/deps.ts +132 -0
- package/src/cache/incremental.ts +173 -0
- package/src/cache/index.ts +135 -0
- package/src/cli.ts +111 -195
- package/src/compile.ts +6 -162
- package/src/diagnostics/index.ts +8 -11
- package/src/emit/compile.ts +177 -0
- package/src/emit/index.ts +286 -0
- package/src/emit/modules.ts +581 -0
- package/src/emit/sourcemap.ts +101 -0
- package/src/hir/lower.ts +455 -0
- package/src/hir/monomorphize.ts +416 -0
- package/src/hir/types.ts +228 -0
- package/src/index.ts +37 -182
- package/src/lexer/index.ts +2 -1
- package/src/lir/budget.ts +321 -0
- package/src/lir/lower.ts +587 -0
- package/src/lir/types.ts +113 -0
- package/src/lir/verify.ts +129 -0
- package/src/lsp/main.ts +9 -0
- package/src/lsp/server.ts +414 -0
- package/src/mc-test/runner.ts +4 -3
- package/src/mir/lower.ts +1403 -0
- package/src/mir/macro.ts +167 -0
- package/src/mir/types.ts +117 -0
- package/src/mir/verify.ts +218 -0
- package/src/optimizer/block_merge.ts +93 -0
- package/src/optimizer/branch_simplify.ts +27 -0
- package/src/optimizer/constant_fold.ts +88 -0
- package/src/optimizer/copy_prop.ts +106 -0
- package/src/optimizer/coroutine.ts +996 -0
- package/src/optimizer/dce.ts +108 -653
- package/src/optimizer/interprocedural.ts +177 -0
- package/src/optimizer/lir/const_imm.ts +143 -0
- package/src/optimizer/lir/dead_slot.ts +123 -0
- package/src/optimizer/lir/peephole.ts +57 -0
- package/src/optimizer/lir/pipeline.ts +37 -0
- package/src/optimizer/nbt-batch.ts +50 -0
- package/src/optimizer/pipeline.ts +59 -0
- package/src/optimizer/selector-cache.ts +103 -0
- package/src/optimizer/unroll.ts +386 -0
- package/src/parser/index.ts +213 -16
- package/src/repl.ts +1 -1
- package/src/runtime/index.ts +1 -1
- package/src/stdlib/math.mcrs +4 -4
- package/src/templates/quest.mcrs +4 -4
- package/src/typechecker/index.ts +215 -15
- package/src/types/mc-version.ts +46 -0
- package/tsconfig.json +1 -1
- package/dist/__tests__/cli.test.js +0 -278
- package/dist/__tests__/codegen.test.js +0 -152
- package/dist/__tests__/e2e.test.d.ts +0 -6
- package/dist/__tests__/e2e.test.js +0 -1847
- package/dist/__tests__/entity-types.test.js +0 -203
- package/dist/__tests__/lowering.test.js +0 -1015
- package/dist/__tests__/macro.test.d.ts +0 -8
- package/dist/__tests__/macro.test.js +0 -305
- package/dist/__tests__/nbt.test.js +0 -82
- package/dist/__tests__/optimizer-advanced.test.js +0 -124
- package/dist/__tests__/optimizer.test.js +0 -149
- package/dist/__tests__/runtime.test.js +0 -289
- package/dist/__tests__/stdlib-advanced.test.d.ts +0 -4
- package/dist/__tests__/stdlib-advanced.test.js +0 -378
- package/dist/__tests__/stdlib-bigint.test.d.ts +0 -7
- package/dist/__tests__/stdlib-bigint.test.js +0 -428
- package/dist/__tests__/stdlib-math.test.d.ts +0 -7
- package/dist/__tests__/stdlib-math.test.js +0 -352
- package/dist/__tests__/stdlib-vec.test.d.ts +0 -4
- package/dist/__tests__/stdlib-vec.test.js +0 -264
- package/dist/__tests__/structure-optimizer.test.js +0 -33
- package/dist/__tests__/var-allocator.test.js +0 -69
- package/dist/codegen/cmdblock/index.d.ts +0 -26
- package/dist/codegen/cmdblock/index.js +0 -45
- package/dist/codegen/mcfunction/index.d.ts +0 -40
- package/dist/codegen/mcfunction/index.js +0 -606
- package/dist/codegen/structure/index.d.ts +0 -24
- package/dist/codegen/structure/index.js +0 -279
- package/dist/codegen/var-allocator.d.ts +0 -45
- package/dist/codegen/var-allocator.js +0 -104
- package/dist/compile.d.ts +0 -68
- package/dist/data/arena/function/__load.mcfunction +0 -6
- package/dist/data/arena/function/__tick.mcfunction +0 -2
- package/dist/data/arena/function/announce_leaders/else_1.mcfunction +0 -3
- package/dist/data/arena/function/announce_leaders/foreach_0/merge_2.mcfunction +0 -1
- package/dist/data/arena/function/announce_leaders/foreach_0/then_0.mcfunction +0 -3
- package/dist/data/arena/function/announce_leaders/foreach_0.mcfunction +0 -7
- package/dist/data/arena/function/announce_leaders/foreach_1/merge_2.mcfunction +0 -1
- package/dist/data/arena/function/announce_leaders/foreach_1/then_0.mcfunction +0 -4
- package/dist/data/arena/function/announce_leaders/foreach_1.mcfunction +0 -6
- package/dist/data/arena/function/announce_leaders/merge_2.mcfunction +0 -1
- package/dist/data/arena/function/announce_leaders/then_0.mcfunction +0 -4
- package/dist/data/arena/function/announce_leaders.mcfunction +0 -6
- package/dist/data/arena/function/arena_tick/merge_2.mcfunction +0 -1
- package/dist/data/arena/function/arena_tick/then_0.mcfunction +0 -4
- package/dist/data/arena/function/arena_tick.mcfunction +0 -11
- package/dist/data/counter/function/__load.mcfunction +0 -5
- package/dist/data/counter/function/__tick.mcfunction +0 -2
- package/dist/data/counter/function/counter_tick/merge_2.mcfunction +0 -1
- package/dist/data/counter/function/counter_tick/then_0.mcfunction +0 -3
- package/dist/data/counter/function/counter_tick.mcfunction +0 -11
- package/dist/data/gcd2/function/__load.mcfunction +0 -3
- package/dist/data/gcd2/function/abs/merge_2.mcfunction +0 -3
- package/dist/data/gcd2/function/abs/then_0.mcfunction +0 -5
- package/dist/data/gcd2/function/abs.mcfunction +0 -7
- package/dist/data/gcd2/function/gcd/loop_body_1.mcfunction +0 -7
- package/dist/data/gcd2/function/gcd/loop_check_0.mcfunction +0 -5
- package/dist/data/gcd2/function/gcd/loop_exit_2.mcfunction +0 -3
- package/dist/data/gcd2/function/gcd.mcfunction +0 -14
- package/dist/data/gcd3/function/__load.mcfunction +0 -3
- package/dist/data/gcd3/function/abs/merge_2.mcfunction +0 -3
- package/dist/data/gcd3/function/abs/then_0.mcfunction +0 -5
- package/dist/data/gcd3/function/abs.mcfunction +0 -7
- package/dist/data/gcd3/function/gcd/loop_body_1.mcfunction +0 -7
- package/dist/data/gcd3/function/gcd/loop_check_0.mcfunction +0 -5
- package/dist/data/gcd3/function/gcd/loop_exit_2.mcfunction +0 -3
- package/dist/data/gcd3/function/gcd.mcfunction +0 -14
- package/dist/data/gcd3/function/test.mcfunction +0 -7
- package/dist/data/gcd3nm/function/__load.mcfunction +0 -3
- package/dist/data/gcd3nm/function/abs/merge_2.mcfunction +0 -3
- package/dist/data/gcd3nm/function/abs/then_0.mcfunction +0 -5
- package/dist/data/gcd3nm/function/abs.mcfunction +0 -7
- package/dist/data/gcd3nm/function/gcd/loop_body_1.mcfunction +0 -7
- package/dist/data/gcd3nm/function/gcd/loop_check_0.mcfunction +0 -5
- package/dist/data/gcd3nm/function/gcd/loop_exit_2.mcfunction +0 -3
- package/dist/data/gcd3nm/function/gcd.mcfunction +0 -14
- package/dist/data/gcd3nm/function/test.mcfunction +0 -7
- package/dist/data/gcd_test/function/__load.mcfunction +0 -3
- package/dist/data/gcd_test/function/abs/merge_2.mcfunction +0 -3
- package/dist/data/gcd_test/function/abs/then_0.mcfunction +0 -5
- package/dist/data/gcd_test/function/abs.mcfunction +0 -7
- package/dist/data/gcd_test/function/gcd/loop_body_1.mcfunction +0 -7
- package/dist/data/gcd_test/function/gcd/loop_check_0.mcfunction +0 -5
- package/dist/data/gcd_test/function/gcd/loop_exit_2.mcfunction +0 -3
- package/dist/data/gcd_test/function/gcd.mcfunction +0 -14
- package/dist/data/isqrttest/function/__load.mcfunction +0 -6
- package/dist/data/isqrttest/function/isqrt/loop_body_4.mcfunction +0 -12
- package/dist/data/isqrttest/function/isqrt/loop_check_3.mcfunction +0 -5
- package/dist/data/isqrttest/function/isqrt/loop_exit_5.mcfunction +0 -3
- package/dist/data/isqrttest/function/isqrt/merge_2.mcfunction +0 -4
- package/dist/data/isqrttest/function/isqrt/merge_8.mcfunction +0 -6
- package/dist/data/isqrttest/function/isqrt/then_0.mcfunction +0 -3
- package/dist/data/isqrttest/function/isqrt/then_6.mcfunction +0 -3
- package/dist/data/isqrttest/function/isqrt.mcfunction +0 -7
- package/dist/data/isqrttest/function/test.mcfunction +0 -6
- package/dist/data/mathtest/function/__load.mcfunction +0 -3
- package/dist/data/mathtest/function/abs/merge_2.mcfunction +0 -3
- package/dist/data/mathtest/function/abs/then_0.mcfunction +0 -5
- package/dist/data/mathtest/function/abs.mcfunction +0 -6
- package/dist/data/mathtest/function/test.mcfunction +0 -5
- package/dist/data/minecraft/tags/function/load.json +0 -5
- package/dist/data/minecraft/tags/function/tick.json +0 -5
- package/dist/data/mypack/function/__load.mcfunction +0 -13
- package/dist/data/mypack/function/_atan_init.mcfunction +0 -2
- package/dist/data/mypack/function/abs/merge_2.mcfunction +0 -3
- package/dist/data/mypack/function/abs/then_0.mcfunction +0 -5
- package/dist/data/mypack/function/abs.mcfunction +0 -6
- package/dist/data/mypack/function/atan2_fixed/__sgi_1.mcfunction +0 -2
- package/dist/data/mypack/function/atan2_fixed/else_34.mcfunction +0 -3
- package/dist/data/mypack/function/atan2_fixed/loop_body_31.mcfunction +0 -19
- package/dist/data/mypack/function/atan2_fixed/loop_check_30.mcfunction +0 -5
- package/dist/data/mypack/function/atan2_fixed/loop_exit_32.mcfunction +0 -6
- package/dist/data/mypack/function/atan2_fixed/merge_11.mcfunction +0 -6
- package/dist/data/mypack/function/atan2_fixed/merge_14.mcfunction +0 -3
- package/dist/data/mypack/function/atan2_fixed/merge_17.mcfunction +0 -6
- package/dist/data/mypack/function/atan2_fixed/merge_2.mcfunction +0 -5
- package/dist/data/mypack/function/atan2_fixed/merge_20.mcfunction +0 -5
- package/dist/data/mypack/function/atan2_fixed/merge_23.mcfunction +0 -5
- package/dist/data/mypack/function/atan2_fixed/merge_26.mcfunction +0 -6
- package/dist/data/mypack/function/atan2_fixed/merge_29.mcfunction +0 -4
- package/dist/data/mypack/function/atan2_fixed/merge_38.mcfunction +0 -5
- package/dist/data/mypack/function/atan2_fixed/merge_41.mcfunction +0 -5
- package/dist/data/mypack/function/atan2_fixed/merge_44.mcfunction +0 -5
- package/dist/data/mypack/function/atan2_fixed/merge_47.mcfunction +0 -5
- package/dist/data/mypack/function/atan2_fixed/merge_5.mcfunction +0 -5
- package/dist/data/mypack/function/atan2_fixed/merge_8.mcfunction +0 -3
- package/dist/data/mypack/function/atan2_fixed/then_0.mcfunction +0 -5
- package/dist/data/mypack/function/atan2_fixed/then_12.mcfunction +0 -3
- package/dist/data/mypack/function/atan2_fixed/then_15.mcfunction +0 -5
- package/dist/data/mypack/function/atan2_fixed/then_18.mcfunction +0 -5
- package/dist/data/mypack/function/atan2_fixed/then_21.mcfunction +0 -3
- package/dist/data/mypack/function/atan2_fixed/then_24.mcfunction +0 -3
- package/dist/data/mypack/function/atan2_fixed/then_27.mcfunction +0 -6
- package/dist/data/mypack/function/atan2_fixed/then_3.mcfunction +0 -3
- package/dist/data/mypack/function/atan2_fixed/then_33.mcfunction +0 -5
- package/dist/data/mypack/function/atan2_fixed/then_36.mcfunction +0 -5
- package/dist/data/mypack/function/atan2_fixed/then_39.mcfunction +0 -5
- package/dist/data/mypack/function/atan2_fixed/then_42.mcfunction +0 -3
- package/dist/data/mypack/function/atan2_fixed/then_45.mcfunction +0 -5
- package/dist/data/mypack/function/atan2_fixed/then_6.mcfunction +0 -3
- package/dist/data/mypack/function/atan2_fixed/then_9.mcfunction +0 -5
- package/dist/data/mypack/function/atan2_fixed.mcfunction +0 -7
- package/dist/data/mypack/function/my_game.mcfunction +0 -10
- package/dist/data/quiz/function/__load.mcfunction +0 -16
- package/dist/data/quiz/function/__tick.mcfunction +0 -6
- package/dist/data/quiz/function/__trigger_quiz_a_dispatch.mcfunction +0 -4
- package/dist/data/quiz/function/__trigger_quiz_b_dispatch.mcfunction +0 -4
- package/dist/data/quiz/function/__trigger_quiz_c_dispatch.mcfunction +0 -4
- package/dist/data/quiz/function/__trigger_quiz_start_dispatch.mcfunction +0 -4
- package/dist/data/quiz/function/answer_a.mcfunction +0 -4
- package/dist/data/quiz/function/answer_b.mcfunction +0 -4
- package/dist/data/quiz/function/answer_c.mcfunction +0 -4
- package/dist/data/quiz/function/ask_question/else_1.mcfunction +0 -5
- package/dist/data/quiz/function/ask_question/else_4.mcfunction +0 -5
- package/dist/data/quiz/function/ask_question/else_7.mcfunction +0 -4
- package/dist/data/quiz/function/ask_question/merge_2.mcfunction +0 -1
- package/dist/data/quiz/function/ask_question/merge_5.mcfunction +0 -2
- package/dist/data/quiz/function/ask_question/merge_8.mcfunction +0 -2
- package/dist/data/quiz/function/ask_question/then_0.mcfunction +0 -4
- package/dist/data/quiz/function/ask_question/then_3.mcfunction +0 -4
- package/dist/data/quiz/function/ask_question/then_6.mcfunction +0 -4
- package/dist/data/quiz/function/ask_question.mcfunction +0 -7
- package/dist/data/quiz/function/finish_quiz.mcfunction +0 -6
- package/dist/data/quiz/function/handle_answer/else_1.mcfunction +0 -5
- package/dist/data/quiz/function/handle_answer/else_10.mcfunction +0 -3
- package/dist/data/quiz/function/handle_answer/else_16.mcfunction +0 -3
- package/dist/data/quiz/function/handle_answer/else_4.mcfunction +0 -3
- package/dist/data/quiz/function/handle_answer/else_7.mcfunction +0 -5
- package/dist/data/quiz/function/handle_answer/merge_11.mcfunction +0 -2
- package/dist/data/quiz/function/handle_answer/merge_14.mcfunction +0 -2
- package/dist/data/quiz/function/handle_answer/merge_17.mcfunction +0 -2
- package/dist/data/quiz/function/handle_answer/merge_2.mcfunction +0 -8
- package/dist/data/quiz/function/handle_answer/merge_5.mcfunction +0 -2
- package/dist/data/quiz/function/handle_answer/merge_8.mcfunction +0 -2
- package/dist/data/quiz/function/handle_answer/then_0.mcfunction +0 -5
- package/dist/data/quiz/function/handle_answer/then_12.mcfunction +0 -5
- package/dist/data/quiz/function/handle_answer/then_15.mcfunction +0 -6
- package/dist/data/quiz/function/handle_answer/then_3.mcfunction +0 -6
- package/dist/data/quiz/function/handle_answer/then_6.mcfunction +0 -5
- package/dist/data/quiz/function/handle_answer/then_9.mcfunction +0 -6
- package/dist/data/quiz/function/handle_answer.mcfunction +0 -11
- package/dist/data/quiz/function/start_quiz.mcfunction +0 -5
- package/dist/data/reqtest/function/__load.mcfunction +0 -4
- package/dist/data/reqtest/function/_table_init.mcfunction +0 -2
- package/dist/data/reqtest/function/no_trig.mcfunction +0 -3
- package/dist/data/reqtest/function/use_table.mcfunction +0 -4
- package/dist/data/reqtest2/function/__load.mcfunction +0 -3
- package/dist/data/reqtest2/function/no_trig.mcfunction +0 -3
- package/dist/data/runtime/function/__load.mcfunction +0 -5
- package/dist/data/runtime/function/__tick.mcfunction +0 -2
- package/dist/data/runtime/function/counter_tick/then_0.mcfunction +0 -3
- package/dist/data/runtime/function/counter_tick.mcfunction +0 -13
- package/dist/data/shop/function/__load.mcfunction +0 -7
- package/dist/data/shop/function/__tick.mcfunction +0 -3
- package/dist/data/shop/function/__trigger_shop_buy_dispatch.mcfunction +0 -4
- package/dist/data/shop/function/complete_purchase/else_1.mcfunction +0 -5
- package/dist/data/shop/function/complete_purchase/else_4.mcfunction +0 -5
- package/dist/data/shop/function/complete_purchase/else_7.mcfunction +0 -3
- package/dist/data/shop/function/complete_purchase/merge_2.mcfunction +0 -2
- package/dist/data/shop/function/complete_purchase/merge_5.mcfunction +0 -2
- package/dist/data/shop/function/complete_purchase/merge_8.mcfunction +0 -2
- package/dist/data/shop/function/complete_purchase/then_0.mcfunction +0 -4
- package/dist/data/shop/function/complete_purchase/then_3.mcfunction +0 -4
- package/dist/data/shop/function/complete_purchase/then_6.mcfunction +0 -4
- package/dist/data/shop/function/complete_purchase.mcfunction +0 -7
- package/dist/data/shop/function/handle_shop_trigger.mcfunction +0 -3
- package/dist/data/swap_test/function/__load.mcfunction +0 -3
- package/dist/data/swap_test/function/gcd_old/loop_body_1.mcfunction +0 -7
- package/dist/data/swap_test/function/gcd_old/loop_check_0.mcfunction +0 -5
- package/dist/data/swap_test/function/gcd_old/loop_exit_2.mcfunction +0 -3
- package/dist/data/swap_test/function/gcd_old.mcfunction +0 -8
- package/dist/data/turret/function/__load.mcfunction +0 -5
- package/dist/data/turret/function/__tick.mcfunction +0 -4
- package/dist/data/turret/function/__trigger_deploy_turret_dispatch.mcfunction +0 -4
- package/dist/data/turret/function/deploy_turret.mcfunction +0 -8
- package/dist/data/turret/function/turret_tick/at_1.mcfunction +0 -2
- package/dist/data/turret/function/turret_tick/foreach_0.mcfunction +0 -2
- package/dist/data/turret/function/turret_tick/foreach_2.mcfunction +0 -2
- package/dist/data/turret/function/turret_tick/tick_body.mcfunction +0 -3
- package/dist/data/turret/function/turret_tick/tick_skip.mcfunction +0 -1
- package/dist/data/turret/function/turret_tick.mcfunction +0 -5
- package/dist/gcd2.map.json +0 -15
- package/dist/gcd3.map.json +0 -17
- package/dist/gcd_test.map.json +0 -15
- package/dist/index.d.ts +0 -62
- package/dist/ir/builder.d.ts +0 -33
- package/dist/ir/builder.js +0 -99
- package/dist/ir/types.d.ts +0 -132
- package/dist/ir/types.js +0 -15
- package/dist/isqrttest.map.json +0 -15
- package/dist/lowering/index.d.ts +0 -188
- package/dist/lowering/index.js +0 -3403
- package/dist/mathtest.map.json +0 -6
- package/dist/mypack.map.json +0 -27
- package/dist/optimizer/commands.d.ts +0 -38
- package/dist/optimizer/commands.js +0 -451
- package/dist/optimizer/dce.d.ts +0 -34
- package/dist/optimizer/dce.js +0 -639
- package/dist/optimizer/passes.d.ts +0 -34
- package/dist/optimizer/passes.js +0 -243
- package/dist/optimizer/structure.d.ts +0 -9
- package/dist/optimizer/structure.js +0 -356
- package/dist/pack.mcmeta +0 -6
- package/dist/reqtest.map.json +0 -4
- package/dist/reqtest2.map.json +0 -4
- package/dist/runtime.map.json +0 -7
- package/dist/swap_test.map.json +0 -14
- package/src/__tests__/codegen.test.ts +0 -161
- package/src/__tests__/e2e.test.ts +0 -2039
- package/src/__tests__/entity-types.test.ts +0 -236
- package/src/__tests__/lowering.test.ts +0 -1185
- package/src/__tests__/macro.test.ts +0 -343
- package/src/__tests__/nbt.test.ts +0 -58
- package/src/__tests__/optimizer-advanced.test.ts +0 -144
- package/src/__tests__/optimizer.test.ts +0 -162
- package/src/__tests__/runtime.test.ts +0 -305
- package/src/__tests__/stdlib-advanced.test.ts +0 -379
- package/src/__tests__/stdlib-bigint.test.ts +0 -427
- package/src/__tests__/stdlib-math.test.ts +0 -374
- package/src/__tests__/stdlib-vec.test.ts +0 -259
- package/src/__tests__/structure-optimizer.test.ts +0 -38
- package/src/__tests__/var-allocator.test.ts +0 -75
- package/src/codegen/cmdblock/index.ts +0 -63
- package/src/codegen/mcfunction/index.ts +0 -662
- package/src/codegen/structure/index.ts +0 -346
- package/src/codegen/var-allocator.ts +0 -104
- package/src/ir/builder.ts +0 -116
- package/src/ir/types.ts +0 -134
- package/src/lowering/index.ts +0 -3876
- package/src/optimizer/commands.ts +0 -534
- package/src/optimizer/passes.ts +0 -250
- package/src/optimizer/structure.ts +0 -450
- /package/dist/{__tests__/cli.test.d.ts → src/__tests__/budget.test.d.ts} +0 -0
- /package/dist/{__tests__/codegen.test.d.ts → src/__tests__/cli.test.d.ts} +0 -0
- /package/dist/{__tests__ → src/__tests__}/compile-all.test.d.ts +0 -0
- /package/dist/{__tests__ → src/__tests__}/compile-all.test.js +0 -0
- /package/dist/{__tests__ → src/__tests__}/dce.test.d.ts +0 -0
- /package/dist/{__tests__ → src/__tests__}/diagnostics.test.d.ts +0 -0
- /package/dist/{__tests__ → src/__tests__}/formatter.test.d.ts +0 -0
- /package/dist/{__tests__ → src/__tests__}/formatter.test.js +0 -0
- /package/dist/{__tests__/entity-types.test.d.ts → src/__tests__/hir/desugar.test.d.ts} +0 -0
- /package/dist/{__tests__ → src/__tests__}/lexer.test.d.ts +0 -0
- /package/dist/{__tests__ → src/__tests__}/lexer.test.js +0 -0
- /package/dist/{__tests__/lowering.test.d.ts → src/__tests__/lir/lower.test.d.ts} +0 -0
- /package/dist/{__tests__/mc-syntax.test.d.ts → src/__tests__/lir/types.test.d.ts} +0 -0
- /package/dist/{__tests__/nbt.test.d.ts → src/__tests__/lir/verify.test.d.ts} +0 -0
- /package/dist/{__tests__ → src/__tests__}/mc-integration.test.d.ts +0 -0
- /package/dist/{__tests__/optimizer-advanced.test.d.ts → src/__tests__/mc-syntax.test.d.ts} +0 -0
- /package/dist/{__tests__ → src/__tests__}/mc-syntax.test.js +0 -0
- /package/dist/{__tests__/optimizer.test.d.ts → src/__tests__/mir/arithmetic.test.d.ts} +0 -0
- /package/dist/{__tests__/parser.test.d.ts → src/__tests__/mir/control-flow.test.d.ts} +0 -0
- /package/dist/{__tests__/repl.test.d.ts → src/__tests__/mir/verify.test.d.ts} +0 -0
- /package/dist/{__tests__/runtime.test.d.ts → src/__tests__/optimizer/block_merge.test.d.ts} +0 -0
- /package/dist/{__tests__/structure-optimizer.test.d.ts → src/__tests__/optimizer/branch_simplify.test.d.ts} +0 -0
- /package/dist/{__tests__/typechecker.test.d.ts → src/__tests__/optimizer/constant_fold.test.d.ts} +0 -0
- /package/dist/{__tests__/var-allocator.test.d.ts → src/__tests__/optimizer/copy_prop.test.d.ts} +0 -0
- /package/dist/{__tests__ → src/__tests__}/parser.test.js +0 -0
- /package/dist/{__tests__ → src/__tests__}/repl.test.js +0 -0
- /package/dist/{__tests__ → src/__tests__}/typechecker.test.js +0 -0
- /package/dist/{ast → src/ast}/types.js +0 -0
- /package/dist/{builtins → src/builtins}/metadata.d.ts +0 -0
- /package/dist/{builtins → src/builtins}/metadata.js +0 -0
- /package/dist/{events → src/events}/types.d.ts +0 -0
- /package/dist/{events → src/events}/types.js +0 -0
- /package/dist/{formatter → src/formatter}/index.d.ts +0 -0
- /package/dist/{formatter → src/formatter}/index.js +0 -0
- /package/dist/{mc-test → src/mc-test}/client.d.ts +0 -0
- /package/dist/{mc-test → src/mc-test}/client.js +0 -0
- /package/dist/{mc-test → src/mc-test}/runner.d.ts +0 -0
- /package/dist/{mc-test → src/mc-test}/setup.d.ts +0 -0
- /package/dist/{mc-test → src/mc-test}/setup.js +0 -0
- /package/dist/{mc-validator → src/mc-validator}/index.d.ts +0 -0
- /package/dist/{mc-validator → src/mc-validator}/index.js +0 -0
- /package/dist/{nbt → src/nbt}/index.d.ts +0 -0
- /package/dist/{nbt → src/nbt}/index.js +0 -0
- /package/dist/{repl.js → src/repl.js} +0 -0
- /package/dist/{runtime → src/runtime}/index.d.ts +0 -0
- /package/dist/{types → src/types}/entity-hierarchy.d.ts +0 -0
- /package/dist/{types → src/types}/entity-hierarchy.js +0 -0
|
@@ -0,0 +1,220 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tests for Phase 2b: Multi-return values / Tuple types
|
|
3
|
+
*
|
|
4
|
+
* Covers:
|
|
5
|
+
* - Tuple type parsing: (int, int)
|
|
6
|
+
* - Tuple literal parsing: (a, b)
|
|
7
|
+
* - Destructuring let: let (q, r) = ...
|
|
8
|
+
* - Function return type: fn f(): (int, int)
|
|
9
|
+
* - Full compilation to scoreboard commands
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import { Lexer } from '../lexer'
|
|
13
|
+
import { Parser } from '../parser'
|
|
14
|
+
import { compile } from '../emit/compile'
|
|
15
|
+
import type { Program, Stmt, Expr } from '../ast/types'
|
|
16
|
+
|
|
17
|
+
function parse(source: string, namespace = 'test'): Program {
|
|
18
|
+
const tokens = new Lexer(source).tokenize()
|
|
19
|
+
return new Parser(tokens).parse(namespace)
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function parseStmt(source: string): Stmt {
|
|
23
|
+
const program = parse(`fn _test() { ${source} }`)
|
|
24
|
+
return program.declarations[0].body[0]
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function getFile(files: { path: string; content: string }[], pathSubstr: string): string | undefined {
|
|
28
|
+
const f = files.find(f => f.path.includes(pathSubstr))
|
|
29
|
+
return f?.content
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// ---------------------------------------------------------------------------
|
|
33
|
+
// Parser tests
|
|
34
|
+
// ---------------------------------------------------------------------------
|
|
35
|
+
|
|
36
|
+
describe('Tuple: Parser', () => {
|
|
37
|
+
it('parses tuple type (int, int) as return type', () => {
|
|
38
|
+
const program = parse('fn f(a: int, b: int): (int, int) {}')
|
|
39
|
+
const fn = program.declarations[0]
|
|
40
|
+
expect(fn.returnType).toEqual({ kind: 'tuple', elements: [
|
|
41
|
+
{ kind: 'named', name: 'int' },
|
|
42
|
+
{ kind: 'named', name: 'int' },
|
|
43
|
+
]})
|
|
44
|
+
})
|
|
45
|
+
|
|
46
|
+
it('parses tuple type with 3 elements', () => {
|
|
47
|
+
const program = parse('fn f(): (int, bool, int) {}')
|
|
48
|
+
expect(program.declarations[0].returnType).toEqual({ kind: 'tuple', elements: [
|
|
49
|
+
{ kind: 'named', name: 'int' },
|
|
50
|
+
{ kind: 'named', name: 'bool' },
|
|
51
|
+
{ kind: 'named', name: 'int' },
|
|
52
|
+
]})
|
|
53
|
+
})
|
|
54
|
+
|
|
55
|
+
it('parses tuple literal expression (a, b)', () => {
|
|
56
|
+
const program = parse(`fn _test(a: int, b: int): (int, int) { return (a, b); }`)
|
|
57
|
+
const ret = program.declarations[0].body[0]
|
|
58
|
+
expect(ret.kind).toBe('return')
|
|
59
|
+
if (ret.kind === 'return' && ret.value) {
|
|
60
|
+
expect(ret.value.kind).toBe('tuple_lit')
|
|
61
|
+
if (ret.value.kind === 'tuple_lit') {
|
|
62
|
+
expect(ret.value.elements).toHaveLength(2)
|
|
63
|
+
expect(ret.value.elements[0]).toMatchObject({ kind: 'ident', name: 'a' })
|
|
64
|
+
expect(ret.value.elements[1]).toMatchObject({ kind: 'ident', name: 'b' })
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
})
|
|
68
|
+
|
|
69
|
+
it('parses destructuring let statement', () => {
|
|
70
|
+
const stmt = parseStmt('let (q, r) = divmod(10, 3);')
|
|
71
|
+
expect(stmt.kind).toBe('let_destruct')
|
|
72
|
+
if (stmt.kind === 'let_destruct') {
|
|
73
|
+
expect(stmt.names).toEqual(['q', 'r'])
|
|
74
|
+
expect(stmt.init).toMatchObject({ kind: 'call', fn: 'divmod' })
|
|
75
|
+
}
|
|
76
|
+
})
|
|
77
|
+
|
|
78
|
+
it('parses destructuring with 3 bindings', () => {
|
|
79
|
+
const stmt = parseStmt('let (a, b, c) = get_triple(1);')
|
|
80
|
+
expect(stmt.kind).toBe('let_destruct')
|
|
81
|
+
if (stmt.kind === 'let_destruct') {
|
|
82
|
+
expect(stmt.names).toEqual(['a', 'b', 'c'])
|
|
83
|
+
}
|
|
84
|
+
})
|
|
85
|
+
|
|
86
|
+
it('does not confuse (expr) grouped expression with tuple literal', () => {
|
|
87
|
+
const program = parse(`fn _test(): int { return (1 + 2); }`)
|
|
88
|
+
const ret = program.declarations[0].body[0]
|
|
89
|
+
expect(ret.kind).toBe('return')
|
|
90
|
+
if (ret.kind === 'return' && ret.value) {
|
|
91
|
+
expect(ret.value.kind).toBe('binary')
|
|
92
|
+
}
|
|
93
|
+
})
|
|
94
|
+
|
|
95
|
+
it('still parses function type (int) -> int correctly', () => {
|
|
96
|
+
const program = parse('fn apply(f: (int) -> int, x: int): int {}')
|
|
97
|
+
const param = program.declarations[0].params[0]
|
|
98
|
+
expect(param.type).toEqual({ kind: 'function_type', params: [{ kind: 'named', name: 'int' }], return: { kind: 'named', name: 'int' } })
|
|
99
|
+
})
|
|
100
|
+
})
|
|
101
|
+
|
|
102
|
+
// ---------------------------------------------------------------------------
|
|
103
|
+
// E2E compilation tests
|
|
104
|
+
// ---------------------------------------------------------------------------
|
|
105
|
+
|
|
106
|
+
describe('Tuple: E2E compilation', () => {
|
|
107
|
+
test('basic divmod function compiles and writes $ret_0 and $ret_1', () => {
|
|
108
|
+
const source = `
|
|
109
|
+
fn divmod(a: int, b: int): (int, int) {
|
|
110
|
+
return (a / b, a % b);
|
|
111
|
+
}
|
|
112
|
+
`
|
|
113
|
+
const result = compile(source, { namespace: 'test' })
|
|
114
|
+
const fn = getFile(result.files, 'divmod.mcfunction')
|
|
115
|
+
expect(fn).toBeDefined()
|
|
116
|
+
// MIR __rf_0 → LIR $ret_0 (see lir/lower.ts slot() method)
|
|
117
|
+
expect(fn).toContain('$ret_0')
|
|
118
|
+
expect(fn).toContain('$ret_1')
|
|
119
|
+
})
|
|
120
|
+
|
|
121
|
+
test('destructuring let reads from $ret_ slots after call', () => {
|
|
122
|
+
const source = `
|
|
123
|
+
fn divmod(a: int, b: int): (int, int) {
|
|
124
|
+
return (a / b, a % b);
|
|
125
|
+
}
|
|
126
|
+
fn use_divmod(): int {
|
|
127
|
+
let (q, r) = divmod(10, 3);
|
|
128
|
+
return q + r;
|
|
129
|
+
}
|
|
130
|
+
`
|
|
131
|
+
const result = compile(source, { namespace: 'test' })
|
|
132
|
+
const fn = getFile(result.files, 'use_divmod.mcfunction')
|
|
133
|
+
expect(fn).toBeDefined()
|
|
134
|
+
// Should call divmod and then read from $ret_ slots
|
|
135
|
+
expect(fn).toContain('divmod')
|
|
136
|
+
expect(fn).toContain('$ret_0')
|
|
137
|
+
expect(fn).toContain('$ret_1')
|
|
138
|
+
})
|
|
139
|
+
|
|
140
|
+
test('tuple literal assigned directly via destructuring', () => {
|
|
141
|
+
const source = `
|
|
142
|
+
fn make_sum(a: int, b: int): int {
|
|
143
|
+
let (x, y) = (a + 1, b + 2);
|
|
144
|
+
return x + y;
|
|
145
|
+
}
|
|
146
|
+
`
|
|
147
|
+
const result = compile(source, { namespace: 'test' })
|
|
148
|
+
const fn = getFile(result.files, 'make_sum.mcfunction')
|
|
149
|
+
expect(fn).toBeDefined()
|
|
150
|
+
expect(fn).toContain('scoreboard players operation')
|
|
151
|
+
})
|
|
152
|
+
|
|
153
|
+
test('tuple values used in subsequent computation', () => {
|
|
154
|
+
const source = `
|
|
155
|
+
fn divmod(a: int, b: int): (int, int) {
|
|
156
|
+
return (a / b, a % b);
|
|
157
|
+
}
|
|
158
|
+
fn compute(): int {
|
|
159
|
+
let (q, r) = divmod(10, 3);
|
|
160
|
+
return q + r;
|
|
161
|
+
}
|
|
162
|
+
`
|
|
163
|
+
const result = compile(source, { namespace: 'test' })
|
|
164
|
+
const fn = getFile(result.files, 'compute.mcfunction')
|
|
165
|
+
expect(fn).toBeDefined()
|
|
166
|
+
expect(fn).toContain('scoreboard players operation')
|
|
167
|
+
})
|
|
168
|
+
|
|
169
|
+
test('3-tuple return and destructuring', () => {
|
|
170
|
+
const source = `
|
|
171
|
+
fn triple(a: int): (int, int, int) {
|
|
172
|
+
return (a, a + 1, a + 2);
|
|
173
|
+
}
|
|
174
|
+
fn use_triple(): int {
|
|
175
|
+
let (x, y, z) = triple(5);
|
|
176
|
+
return x + y + z;
|
|
177
|
+
}
|
|
178
|
+
`
|
|
179
|
+
const result = compile(source, { namespace: 'test' })
|
|
180
|
+
const tripleFn = getFile(result.files, 'triple.mcfunction')
|
|
181
|
+
expect(tripleFn).toBeDefined()
|
|
182
|
+
expect(tripleFn).toContain('$ret_0')
|
|
183
|
+
expect(tripleFn).toContain('$ret_1')
|
|
184
|
+
expect(tripleFn).toContain('$ret_2')
|
|
185
|
+
})
|
|
186
|
+
|
|
187
|
+
test('enum and tuple combined: return (int, Phase)', () => {
|
|
188
|
+
const source = `
|
|
189
|
+
enum Phase { Idle, Running, Done }
|
|
190
|
+
fn get_state(): (int, int) {
|
|
191
|
+
return (42, Phase::Running);
|
|
192
|
+
}
|
|
193
|
+
fn use_state(): int {
|
|
194
|
+
let (count, phase) = get_state();
|
|
195
|
+
return count + phase;
|
|
196
|
+
}
|
|
197
|
+
`
|
|
198
|
+
const result = compile(source, { namespace: 'test' })
|
|
199
|
+
const fn = getFile(result.files, 'get_state.mcfunction')
|
|
200
|
+
expect(fn).toBeDefined()
|
|
201
|
+
expect(fn).toContain('$ret_0')
|
|
202
|
+
expect(fn).toContain('$ret_1')
|
|
203
|
+
// Phase::Running = 1
|
|
204
|
+
expect(fn).toContain('1')
|
|
205
|
+
})
|
|
206
|
+
|
|
207
|
+
test('tuple return type appears in generated mcfunction with correct objective', () => {
|
|
208
|
+
const source = `
|
|
209
|
+
fn minmax(a: int, b: int): (int, int) {
|
|
210
|
+
return (a, b);
|
|
211
|
+
}
|
|
212
|
+
`
|
|
213
|
+
const result = compile(source, { namespace: 'ns' })
|
|
214
|
+
const fn = getFile(result.files, 'minmax.mcfunction')
|
|
215
|
+
expect(fn).toBeDefined()
|
|
216
|
+
// MIR __rf_0 → LIR $ret_0
|
|
217
|
+
expect(fn).toContain('$ret_0')
|
|
218
|
+
expect(fn).toContain('__ns')
|
|
219
|
+
})
|
|
220
|
+
})
|
|
@@ -0,0 +1,216 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Phase 6b: TypeChecker strict (error-mode) tests
|
|
3
|
+
*
|
|
4
|
+
* Covers:
|
|
5
|
+
* - Type errors now block compilation (error-mode default)
|
|
6
|
+
* - --lenient flag demotes type errors to warnings
|
|
7
|
+
* - int/float implicit conversion detection
|
|
8
|
+
* - redscript check command correctly reports type errors
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import { compile } from '../emit/compile'
|
|
12
|
+
import { Lexer } from '../lexer'
|
|
13
|
+
import { Parser } from '../parser'
|
|
14
|
+
import { TypeChecker } from '../typechecker'
|
|
15
|
+
import type { DiagnosticError } from '../diagnostics'
|
|
16
|
+
|
|
17
|
+
// Helper: run TypeChecker directly
|
|
18
|
+
function typeCheck(source: string): DiagnosticError[] {
|
|
19
|
+
const tokens = new Lexer(source).tokenize()
|
|
20
|
+
const ast = new Parser(tokens).parse('test')
|
|
21
|
+
const checker = new TypeChecker(source)
|
|
22
|
+
return checker.check(ast)
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
// Helper: compile in strict mode (default)
|
|
26
|
+
function compileStrict(source: string): void {
|
|
27
|
+
compile(source, { namespace: 'test' })
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
// Helper: compile in lenient mode
|
|
31
|
+
function compileLenient(source: string): { warnings: string[] } {
|
|
32
|
+
return compile(source, { namespace: 'test', lenient: true })
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
describe('TypeChecker error-mode (Phase 6b)', () => {
|
|
36
|
+
describe('type errors block compilation', () => {
|
|
37
|
+
it('throws on undeclared variable usage', () => {
|
|
38
|
+
expect(() => compileStrict(`
|
|
39
|
+
fn test() {
|
|
40
|
+
let x: int = undeclared;
|
|
41
|
+
}
|
|
42
|
+
`)).toThrow()
|
|
43
|
+
})
|
|
44
|
+
|
|
45
|
+
it('throws on return type mismatch', () => {
|
|
46
|
+
expect(() => compileStrict(`
|
|
47
|
+
fn get_bool() -> bool {
|
|
48
|
+
return 5;
|
|
49
|
+
}
|
|
50
|
+
`)).toThrow()
|
|
51
|
+
})
|
|
52
|
+
|
|
53
|
+
it('throws on wrong argument count', () => {
|
|
54
|
+
expect(() => compileStrict(`
|
|
55
|
+
fn add(a: int, b: int) -> int {
|
|
56
|
+
return a + b;
|
|
57
|
+
}
|
|
58
|
+
fn test() {
|
|
59
|
+
let x: int = add(1);
|
|
60
|
+
}
|
|
61
|
+
`)).toThrow()
|
|
62
|
+
})
|
|
63
|
+
|
|
64
|
+
it('does not throw for valid programs', () => {
|
|
65
|
+
expect(() => compileStrict(`
|
|
66
|
+
fn add(a: int, b: int) -> int {
|
|
67
|
+
return a + b;
|
|
68
|
+
}
|
|
69
|
+
fn test() {
|
|
70
|
+
let x: int = add(1, 2);
|
|
71
|
+
}
|
|
72
|
+
`)).not.toThrow()
|
|
73
|
+
})
|
|
74
|
+
})
|
|
75
|
+
|
|
76
|
+
describe('--lenient mode', () => {
|
|
77
|
+
it('does not throw on type errors in lenient mode', () => {
|
|
78
|
+
expect(() => compileLenient(`
|
|
79
|
+
fn test() {
|
|
80
|
+
let x: int = undeclared;
|
|
81
|
+
}
|
|
82
|
+
`)).not.toThrow()
|
|
83
|
+
})
|
|
84
|
+
|
|
85
|
+
it('returns type errors as warnings in lenient mode', () => {
|
|
86
|
+
const result = compileLenient(`
|
|
87
|
+
fn test() {
|
|
88
|
+
let x: int = undeclared;
|
|
89
|
+
}
|
|
90
|
+
`)
|
|
91
|
+
expect(result.warnings.length).toBeGreaterThan(0)
|
|
92
|
+
expect(result.warnings[0]).toContain('TypeError')
|
|
93
|
+
})
|
|
94
|
+
|
|
95
|
+
it('emits no warnings for valid programs in lenient mode', () => {
|
|
96
|
+
const result = compileLenient(`
|
|
97
|
+
fn test() {
|
|
98
|
+
let x: int = 5;
|
|
99
|
+
}
|
|
100
|
+
`)
|
|
101
|
+
expect(result.warnings).toHaveLength(0)
|
|
102
|
+
})
|
|
103
|
+
})
|
|
104
|
+
|
|
105
|
+
describe('int/float implicit conversion checks', () => {
|
|
106
|
+
it('detects int assigned to float variable', () => {
|
|
107
|
+
// int literal assigned to float — different types
|
|
108
|
+
const errors = typeCheck(`
|
|
109
|
+
fn test() {
|
|
110
|
+
let x: float = 5;
|
|
111
|
+
}
|
|
112
|
+
`)
|
|
113
|
+
expect(errors.length).toBeGreaterThan(0)
|
|
114
|
+
expect(errors[0].message).toContain('cannot implicitly convert int to float')
|
|
115
|
+
expect(errors[0].message).toContain('as float')
|
|
116
|
+
})
|
|
117
|
+
|
|
118
|
+
it('detects float assigned to int variable', () => {
|
|
119
|
+
const errors = typeCheck(`
|
|
120
|
+
fn get_f() -> float {
|
|
121
|
+
return 3.14;
|
|
122
|
+
}
|
|
123
|
+
fn test() {
|
|
124
|
+
let x: int = get_f();
|
|
125
|
+
}
|
|
126
|
+
`)
|
|
127
|
+
expect(errors.length).toBeGreaterThan(0)
|
|
128
|
+
expect(errors[0].message).toContain('cannot implicitly convert')
|
|
129
|
+
})
|
|
130
|
+
|
|
131
|
+
it('detects int/float return type mismatch', () => {
|
|
132
|
+
const errors = typeCheck(`
|
|
133
|
+
fn get_float() -> float {
|
|
134
|
+
return 5;
|
|
135
|
+
}
|
|
136
|
+
`)
|
|
137
|
+
expect(errors.length).toBeGreaterThan(0)
|
|
138
|
+
expect(errors[0].message).toContain('cannot implicitly convert int to float')
|
|
139
|
+
expect(errors[0].message).toContain('as float')
|
|
140
|
+
})
|
|
141
|
+
|
|
142
|
+
it('detects float/int return type mismatch', () => {
|
|
143
|
+
const errors = typeCheck(`
|
|
144
|
+
fn get_int() -> int {
|
|
145
|
+
return 3.14;
|
|
146
|
+
}
|
|
147
|
+
`)
|
|
148
|
+
expect(errors.length).toBeGreaterThan(0)
|
|
149
|
+
expect(errors[0].message).toContain('cannot implicitly convert float to int')
|
|
150
|
+
expect(errors[0].message).toContain('as int')
|
|
151
|
+
})
|
|
152
|
+
|
|
153
|
+
it('allows int assigned to int variable', () => {
|
|
154
|
+
const errors = typeCheck(`
|
|
155
|
+
fn test() {
|
|
156
|
+
let x: int = 5;
|
|
157
|
+
}
|
|
158
|
+
`)
|
|
159
|
+
expect(errors).toHaveLength(0)
|
|
160
|
+
})
|
|
161
|
+
|
|
162
|
+
it('allows float assigned to float variable', () => {
|
|
163
|
+
const errors = typeCheck(`
|
|
164
|
+
fn get_f() -> float {
|
|
165
|
+
return 3.14;
|
|
166
|
+
}
|
|
167
|
+
fn test() {
|
|
168
|
+
let x: float = get_f();
|
|
169
|
+
}
|
|
170
|
+
`)
|
|
171
|
+
expect(errors).toHaveLength(0)
|
|
172
|
+
})
|
|
173
|
+
|
|
174
|
+
it('blocks compilation on int→float mismatch', () => {
|
|
175
|
+
expect(() => compileStrict(`
|
|
176
|
+
fn get_float() -> float {
|
|
177
|
+
return 5;
|
|
178
|
+
}
|
|
179
|
+
`)).toThrow()
|
|
180
|
+
})
|
|
181
|
+
|
|
182
|
+
it('int→float mismatch demoted to warning in lenient mode', () => {
|
|
183
|
+
const result = compileLenient(`
|
|
184
|
+
fn get_float() -> float {
|
|
185
|
+
return 5;
|
|
186
|
+
}
|
|
187
|
+
`)
|
|
188
|
+
expect(result.warnings.length).toBeGreaterThan(0)
|
|
189
|
+
expect(result.warnings[0]).toContain('TypeError')
|
|
190
|
+
})
|
|
191
|
+
})
|
|
192
|
+
|
|
193
|
+
describe('redscript check command behavior', () => {
|
|
194
|
+
it('TypeChecker check() returns errors for type mismatches', () => {
|
|
195
|
+
const errors = typeCheck(`
|
|
196
|
+
fn test() {
|
|
197
|
+
let x: int = undeclared;
|
|
198
|
+
let y: bool = 1;
|
|
199
|
+
}
|
|
200
|
+
`)
|
|
201
|
+
expect(errors.length).toBeGreaterThanOrEqual(1)
|
|
202
|
+
})
|
|
203
|
+
|
|
204
|
+
it('TypeChecker check() returns empty array for valid programs', () => {
|
|
205
|
+
const errors = typeCheck(`
|
|
206
|
+
struct Point { x: int, y: int }
|
|
207
|
+
|
|
208
|
+
fn test() {
|
|
209
|
+
let p: Point = { x: 10, y: 20 };
|
|
210
|
+
let val: int = p.x;
|
|
211
|
+
}
|
|
212
|
+
`)
|
|
213
|
+
expect(errors).toHaveLength(0)
|
|
214
|
+
})
|
|
215
|
+
})
|
|
216
|
+
})
|
package/src/ast/types.ts
CHANGED
|
@@ -5,7 +5,12 @@
|
|
|
5
5
|
* The AST is produced by the parser and consumed by the lowering pass.
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
|
-
|
|
8
|
+
// ---------------------------------------------------------------------------
|
|
9
|
+
// Binary / comparison operators (shared across AST, HIR, MIR)
|
|
10
|
+
// ---------------------------------------------------------------------------
|
|
11
|
+
|
|
12
|
+
export type BinOp = '+' | '-' | '*' | '/' | '%'
|
|
13
|
+
export type CmpOp = '==' | '!=' | '<' | '<=' | '>' | '>='
|
|
9
14
|
|
|
10
15
|
// ---------------------------------------------------------------------------
|
|
11
16
|
// Source Span
|
|
@@ -48,6 +53,8 @@ export type TypeNode =
|
|
|
48
53
|
| { kind: 'function_type'; params: TypeNode[]; return: TypeNode }
|
|
49
54
|
| { kind: 'entity'; entityType: EntityTypeName } // Entity types
|
|
50
55
|
| { kind: 'selector'; entityType?: string } // Selector type, optionally parameterized: selector<Player>
|
|
56
|
+
| { kind: 'tuple'; elements: TypeNode[] } // Tuple type: (int, int, bool)
|
|
57
|
+
| { kind: 'option'; inner: TypeNode } // Option<T> — null safety wrapper
|
|
51
58
|
|
|
52
59
|
export interface LambdaParam {
|
|
53
60
|
name: string
|
|
@@ -158,7 +165,7 @@ export type Expr =
|
|
|
158
165
|
| { kind: 'is_check'; expr: Expr; entityType: EntityTypeName; span?: Span }
|
|
159
166
|
| { kind: 'unary'; op: '!' | '-'; operand: Expr; span?: Span }
|
|
160
167
|
| { kind: 'assign'; target: string; op: AssignOp; value: Expr; span?: Span }
|
|
161
|
-
| { kind: 'call'; fn: string; args: Expr[]; span?: Span }
|
|
168
|
+
| { kind: 'call'; fn: string; args: Expr[]; typeArgs?: TypeNode[]; span?: Span }
|
|
162
169
|
| { kind: 'invoke'; callee: Expr; args: Expr[]; span?: Span }
|
|
163
170
|
| { kind: 'member'; obj: Expr; field: string; span?: Span }
|
|
164
171
|
| { kind: 'struct_lit'; fields: { name: string; value: Expr }[]; span?: Span }
|
|
@@ -166,7 +173,11 @@ export type Expr =
|
|
|
166
173
|
| { kind: 'index'; obj: Expr; index: Expr; span?: Span }
|
|
167
174
|
| { kind: 'array_lit'; elements: Expr[]; span?: Span }
|
|
168
175
|
| { kind: 'static_call'; type: string; method: string; args: Expr[]; span?: Span }
|
|
176
|
+
| { kind: 'path_expr'; enumName: string; variant: string; span?: Span }
|
|
169
177
|
| (LambdaExpr & { span?: Span })
|
|
178
|
+
| { kind: 'tuple_lit'; elements: Expr[]; span?: Span }
|
|
179
|
+
| { kind: 'some_lit'; value: Expr; span?: Span } // Some(expr)
|
|
180
|
+
| { kind: 'none_lit'; span?: Span } // None
|
|
170
181
|
|
|
171
182
|
export type LiteralExpr =
|
|
172
183
|
| Extract<Expr, { kind: 'int_lit' }>
|
|
@@ -212,6 +223,7 @@ export type ExecuteSubcommand =
|
|
|
212
223
|
|
|
213
224
|
export type Stmt =
|
|
214
225
|
| { kind: 'let'; name: string; type?: TypeNode; init: Expr; span?: Span }
|
|
226
|
+
| { kind: 'let_destruct'; names: string[]; type?: TypeNode; init: Expr; span?: Span }
|
|
215
227
|
| { kind: 'expr'; expr: Expr; span?: Span }
|
|
216
228
|
| { kind: 'return'; value?: Expr; span?: Span }
|
|
217
229
|
| { kind: 'break'; span?: Span }
|
|
@@ -222,6 +234,7 @@ export type Stmt =
|
|
|
222
234
|
| { kind: 'foreach'; binding: string; iterable: Expr; body: Block; executeContext?: string; span?: Span }
|
|
223
235
|
| { kind: 'for_range'; varName: string; start: Expr; end: Expr; body: Block; span?: Span }
|
|
224
236
|
| { kind: 'match'; expr: Expr; arms: { pattern: Expr | null; body: Block }[]; span?: Span }
|
|
237
|
+
| { kind: 'if_let_some'; binding: string; init: Expr; then: Block; else_?: Block; span?: Span }
|
|
225
238
|
| { kind: 'as_block'; selector: EntitySelector; body: Block; span?: Span }
|
|
226
239
|
| { kind: 'at_block'; selector: EntitySelector; body: Block; span?: Span }
|
|
227
240
|
| { kind: 'as_at'; as_sel: EntitySelector; at_sel: EntitySelector; body: Block; span?: Span }
|
|
@@ -235,7 +248,7 @@ export type Block = Stmt[]
|
|
|
235
248
|
// ---------------------------------------------------------------------------
|
|
236
249
|
|
|
237
250
|
export interface Decorator {
|
|
238
|
-
name: 'tick' | 'load' | 'on' | 'on_trigger' | 'on_advancement' | 'on_craft' | 'on_death' | 'on_login' | 'on_join_team' | 'keep' | 'require_on_load'
|
|
251
|
+
name: 'tick' | 'load' | 'on' | 'on_trigger' | 'on_advancement' | 'on_craft' | 'on_death' | 'on_login' | 'on_join_team' | 'keep' | 'require_on_load' | 'coroutine' | 'schedule'
|
|
239
252
|
args?: {
|
|
240
253
|
rate?: number
|
|
241
254
|
eventType?: string
|
|
@@ -243,6 +256,9 @@ export interface Decorator {
|
|
|
243
256
|
advancement?: string
|
|
244
257
|
item?: string
|
|
245
258
|
team?: string
|
|
259
|
+
batch?: number
|
|
260
|
+
onDone?: string
|
|
261
|
+
ticks?: number
|
|
246
262
|
}
|
|
247
263
|
/** Raw positional arguments (used by @requires and future generic decorators). */
|
|
248
264
|
rawArgs?: Array<{ kind: 'string'; value: string } | { kind: 'number'; value: number }>
|
|
@@ -263,7 +279,11 @@ export interface FnDecl {
|
|
|
263
279
|
* Library functions are NOT MC entry points — DCE only keeps them if they
|
|
264
280
|
* are reachable from a non-library (user) entry point. */
|
|
265
281
|
isLibraryFn?: boolean
|
|
282
|
+
/** True when declared with `export fn` or `@keep fn` — survives DCE. */
|
|
283
|
+
isExported?: boolean
|
|
266
284
|
name: string
|
|
285
|
+
/** Generic type parameter names, e.g. ['T'] for fn foo<T>(...) */
|
|
286
|
+
typeParams?: string[]
|
|
267
287
|
params: Param[]
|
|
268
288
|
returnType: TypeNode
|
|
269
289
|
decorators: Decorator[]
|
|
@@ -320,18 +340,34 @@ export interface GlobalDecl {
|
|
|
320
340
|
span?: Span
|
|
321
341
|
}
|
|
322
342
|
|
|
343
|
+
// ---------------------------------------------------------------------------
|
|
344
|
+
// Import Declarations (Phase 5b module system)
|
|
345
|
+
// ---------------------------------------------------------------------------
|
|
346
|
+
|
|
347
|
+
export interface ImportDecl {
|
|
348
|
+
/** The module being imported from, e.g. "math" in `import math::sin` */
|
|
349
|
+
moduleName: string
|
|
350
|
+
/** The symbol being imported, or '*' for wildcard */
|
|
351
|
+
symbol: string // '*' | identifier
|
|
352
|
+
span?: Span
|
|
353
|
+
}
|
|
354
|
+
|
|
323
355
|
// ---------------------------------------------------------------------------
|
|
324
356
|
// Program (Top-Level)
|
|
325
357
|
// ---------------------------------------------------------------------------
|
|
326
358
|
|
|
327
359
|
export interface Program {
|
|
328
360
|
namespace: string // Inferred from filename or `namespace mypack;`
|
|
361
|
+
/** Module name declared with `module <name>;` (undefined if no module decl) */
|
|
362
|
+
moduleName?: string
|
|
329
363
|
globals: GlobalDecl[]
|
|
330
364
|
declarations: FnDecl[]
|
|
331
365
|
structs: StructDecl[]
|
|
332
366
|
implBlocks: ImplBlock[]
|
|
333
367
|
enums: EnumDecl[]
|
|
334
368
|
consts: ConstDecl[]
|
|
369
|
+
/** Phase 5b: module imports (`import math::sin;`) */
|
|
370
|
+
imports: ImportDecl[]
|
|
335
371
|
/** True when the source file declares `module library;`.
|
|
336
372
|
* Library-mode: all functions are DCE-eligible by default — none are treated
|
|
337
373
|
* as MC entry points unless they carry @tick / @load / @on / @keep etc. */
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* DependencyGraph — tracks import relationships between .mcrs files.
|
|
3
|
+
*
|
|
4
|
+
* Builds a forward dependency map (file → files it imports) and supports
|
|
5
|
+
* reverse lookups (file → files that depend on it) for change propagation.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import * as fs from 'fs'
|
|
9
|
+
import * as path from 'path'
|
|
10
|
+
|
|
11
|
+
const IMPORT_RE = /^\s*import\s+"([^"]+)"\s*;?\s*$/
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Parse import statements from a source file's content.
|
|
15
|
+
* Returns absolute paths of imported files.
|
|
16
|
+
*/
|
|
17
|
+
export function parseImports(filePath: string, source?: string): string[] {
|
|
18
|
+
const absPath = path.resolve(filePath)
|
|
19
|
+
const content = source ?? fs.readFileSync(absPath, 'utf-8')
|
|
20
|
+
const dir = path.dirname(absPath)
|
|
21
|
+
const imports: string[] = []
|
|
22
|
+
|
|
23
|
+
for (const line of content.split('\n')) {
|
|
24
|
+
const trimmed = line.trim()
|
|
25
|
+
// Stop at first non-comment, non-blank, non-import line
|
|
26
|
+
if (!trimmed || trimmed.startsWith('//')) continue
|
|
27
|
+
const match = trimmed.match(IMPORT_RE)
|
|
28
|
+
if (match) {
|
|
29
|
+
imports.push(path.resolve(dir, match[1]))
|
|
30
|
+
} else {
|
|
31
|
+
// Past the import header
|
|
32
|
+
break
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
return imports
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export class DependencyGraph {
|
|
40
|
+
/** Forward edges: file → set of files it imports */
|
|
41
|
+
private deps = new Map<string, Set<string>>()
|
|
42
|
+
|
|
43
|
+
/** Add a file and its direct imports to the graph. */
|
|
44
|
+
addFile(filePath: string, source?: string): void {
|
|
45
|
+
const absPath = path.resolve(filePath)
|
|
46
|
+
const imports = parseImports(absPath, source)
|
|
47
|
+
this.deps.set(absPath, new Set(imports))
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/** Remove a file from the graph. */
|
|
51
|
+
removeFile(filePath: string): void {
|
|
52
|
+
this.deps.delete(path.resolve(filePath))
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/** Get direct imports of a file. */
|
|
56
|
+
getDirectDeps(filePath: string): Set<string> {
|
|
57
|
+
return this.deps.get(path.resolve(filePath)) ?? new Set()
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Get transitive dependencies of a file (all files it depends on,
|
|
62
|
+
* directly or indirectly).
|
|
63
|
+
*/
|
|
64
|
+
getTransitiveDeps(filePath: string): Set<string> {
|
|
65
|
+
const absPath = path.resolve(filePath)
|
|
66
|
+
const result = new Set<string>()
|
|
67
|
+
const stack = [absPath]
|
|
68
|
+
|
|
69
|
+
while (stack.length > 0) {
|
|
70
|
+
const current = stack.pop()!
|
|
71
|
+
const directDeps = this.deps.get(current)
|
|
72
|
+
if (!directDeps) continue
|
|
73
|
+
for (const dep of directDeps) {
|
|
74
|
+
if (!result.has(dep)) {
|
|
75
|
+
result.add(dep)
|
|
76
|
+
stack.push(dep)
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
return result
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Get reverse dependents: all files that (transitively) depend on the
|
|
86
|
+
* given file. Used for change propagation — when `filePath` changes,
|
|
87
|
+
* all returned files need recompilation.
|
|
88
|
+
*/
|
|
89
|
+
getDependents(filePath: string): Set<string> {
|
|
90
|
+
const absPath = path.resolve(filePath)
|
|
91
|
+
const result = new Set<string>()
|
|
92
|
+
const stack = [absPath]
|
|
93
|
+
|
|
94
|
+
while (stack.length > 0) {
|
|
95
|
+
const current = stack.pop()!
|
|
96
|
+
for (const [file, deps] of this.deps) {
|
|
97
|
+
if (deps.has(current) && !result.has(file)) {
|
|
98
|
+
result.add(file)
|
|
99
|
+
stack.push(file)
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
return result
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* Given a set of changed files, compute the full set of dirty files
|
|
109
|
+
* (changed files + all their reverse dependents).
|
|
110
|
+
*/
|
|
111
|
+
computeDirtySet(changedFiles: Set<string>): Set<string> {
|
|
112
|
+
const dirty = new Set<string>()
|
|
113
|
+
for (const file of changedFiles) {
|
|
114
|
+
const absFile = path.resolve(file)
|
|
115
|
+
dirty.add(absFile)
|
|
116
|
+
for (const dep of this.getDependents(absFile)) {
|
|
117
|
+
dirty.add(dep)
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
return dirty
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/** Get all tracked files. */
|
|
124
|
+
getAllFiles(): string[] {
|
|
125
|
+
return [...this.deps.keys()]
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/** Clear the graph. */
|
|
129
|
+
clear(): void {
|
|
130
|
+
this.deps.clear()
|
|
131
|
+
}
|
|
132
|
+
}
|