redscript-mc 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.github/ISSUE_TEMPLATE/bug_report.md +40 -0
- package/.github/ISSUE_TEMPLATE/feature_request.md +31 -0
- package/.github/ISSUE_TEMPLATE/wrong_output.md +33 -0
- package/.github/PULL_REQUEST_TEMPLATE.md +34 -0
- package/.github/workflows/ci.yml +29 -0
- package/.github/workflows/publish-extension.yml +35 -0
- package/LICENSE +21 -0
- package/README.md +261 -0
- package/README.zh.md +261 -0
- package/dist/__tests__/cli.test.d.ts +1 -0
- package/dist/__tests__/cli.test.js +140 -0
- package/dist/__tests__/codegen.test.d.ts +1 -0
- package/dist/__tests__/codegen.test.js +121 -0
- package/dist/__tests__/diagnostics.test.d.ts +4 -0
- package/dist/__tests__/diagnostics.test.js +149 -0
- package/dist/__tests__/e2e.test.d.ts +6 -0
- package/dist/__tests__/e2e.test.js +1528 -0
- package/dist/__tests__/lexer.test.d.ts +1 -0
- package/dist/__tests__/lexer.test.js +316 -0
- package/dist/__tests__/lowering.test.d.ts +1 -0
- package/dist/__tests__/lowering.test.js +819 -0
- package/dist/__tests__/mc-integration.test.d.ts +12 -0
- package/dist/__tests__/mc-integration.test.js +395 -0
- package/dist/__tests__/mc-syntax.test.d.ts +1 -0
- package/dist/__tests__/mc-syntax.test.js +112 -0
- package/dist/__tests__/nbt.test.d.ts +1 -0
- package/dist/__tests__/nbt.test.js +82 -0
- package/dist/__tests__/optimizer-advanced.test.d.ts +1 -0
- package/dist/__tests__/optimizer-advanced.test.js +124 -0
- package/dist/__tests__/optimizer.test.d.ts +1 -0
- package/dist/__tests__/optimizer.test.js +118 -0
- package/dist/__tests__/parser.test.d.ts +1 -0
- package/dist/__tests__/parser.test.js +717 -0
- package/dist/__tests__/repl.test.d.ts +1 -0
- package/dist/__tests__/repl.test.js +27 -0
- package/dist/__tests__/runtime.test.d.ts +1 -0
- package/dist/__tests__/runtime.test.js +276 -0
- package/dist/__tests__/structure-optimizer.test.d.ts +1 -0
- package/dist/__tests__/structure-optimizer.test.js +33 -0
- package/dist/__tests__/typechecker.test.d.ts +1 -0
- package/dist/__tests__/typechecker.test.js +364 -0
- package/dist/ast/types.d.ts +357 -0
- package/dist/ast/types.js +9 -0
- package/dist/cli.d.ts +11 -0
- package/dist/cli.js +407 -0
- package/dist/codegen/cmdblock/index.d.ts +26 -0
- package/dist/codegen/cmdblock/index.js +45 -0
- package/dist/codegen/mcfunction/index.d.ts +34 -0
- package/dist/codegen/mcfunction/index.js +413 -0
- package/dist/codegen/structure/index.d.ts +18 -0
- package/dist/codegen/structure/index.js +249 -0
- package/dist/compile.d.ts +30 -0
- package/dist/compile.js +152 -0
- package/dist/data/arena/function/__load.mcfunction +6 -0
- package/dist/data/arena/function/__tick.mcfunction +2 -0
- package/dist/data/arena/function/announce_leaders/else_1.mcfunction +3 -0
- package/dist/data/arena/function/announce_leaders/foreach_0/merge_2.mcfunction +1 -0
- package/dist/data/arena/function/announce_leaders/foreach_0/then_0.mcfunction +3 -0
- package/dist/data/arena/function/announce_leaders/foreach_0.mcfunction +7 -0
- package/dist/data/arena/function/announce_leaders/foreach_1/merge_2.mcfunction +1 -0
- package/dist/data/arena/function/announce_leaders/foreach_1/then_0.mcfunction +4 -0
- package/dist/data/arena/function/announce_leaders/foreach_1.mcfunction +6 -0
- package/dist/data/arena/function/announce_leaders/merge_2.mcfunction +1 -0
- package/dist/data/arena/function/announce_leaders/then_0.mcfunction +4 -0
- package/dist/data/arena/function/announce_leaders.mcfunction +6 -0
- package/dist/data/arena/function/arena_tick/merge_2.mcfunction +1 -0
- package/dist/data/arena/function/arena_tick/then_0.mcfunction +4 -0
- package/dist/data/arena/function/arena_tick.mcfunction +11 -0
- package/dist/data/counter/function/__load.mcfunction +5 -0
- package/dist/data/counter/function/__tick.mcfunction +2 -0
- package/dist/data/counter/function/counter_tick/merge_2.mcfunction +1 -0
- package/dist/data/counter/function/counter_tick/then_0.mcfunction +3 -0
- package/dist/data/counter/function/counter_tick.mcfunction +11 -0
- package/dist/data/minecraft/tags/function/load.json +5 -0
- package/dist/data/minecraft/tags/function/tick.json +5 -0
- package/dist/data/quiz/function/__load.mcfunction +16 -0
- package/dist/data/quiz/function/__tick.mcfunction +6 -0
- package/dist/data/quiz/function/__trigger_quiz_a_dispatch.mcfunction +4 -0
- package/dist/data/quiz/function/__trigger_quiz_b_dispatch.mcfunction +4 -0
- package/dist/data/quiz/function/__trigger_quiz_c_dispatch.mcfunction +4 -0
- package/dist/data/quiz/function/__trigger_quiz_start_dispatch.mcfunction +4 -0
- package/dist/data/quiz/function/answer_a.mcfunction +4 -0
- package/dist/data/quiz/function/answer_b.mcfunction +4 -0
- package/dist/data/quiz/function/answer_c.mcfunction +4 -0
- package/dist/data/quiz/function/ask_question/else_1.mcfunction +5 -0
- package/dist/data/quiz/function/ask_question/else_4.mcfunction +5 -0
- package/dist/data/quiz/function/ask_question/else_7.mcfunction +4 -0
- package/dist/data/quiz/function/ask_question/merge_2.mcfunction +1 -0
- package/dist/data/quiz/function/ask_question/merge_5.mcfunction +2 -0
- package/dist/data/quiz/function/ask_question/merge_8.mcfunction +2 -0
- package/dist/data/quiz/function/ask_question/then_0.mcfunction +4 -0
- package/dist/data/quiz/function/ask_question/then_3.mcfunction +4 -0
- package/dist/data/quiz/function/ask_question/then_6.mcfunction +4 -0
- package/dist/data/quiz/function/ask_question.mcfunction +7 -0
- package/dist/data/quiz/function/finish_quiz.mcfunction +6 -0
- package/dist/data/quiz/function/handle_answer/else_1.mcfunction +5 -0
- package/dist/data/quiz/function/handle_answer/else_10.mcfunction +3 -0
- package/dist/data/quiz/function/handle_answer/else_16.mcfunction +3 -0
- package/dist/data/quiz/function/handle_answer/else_4.mcfunction +3 -0
- package/dist/data/quiz/function/handle_answer/else_7.mcfunction +5 -0
- package/dist/data/quiz/function/handle_answer/merge_11.mcfunction +2 -0
- package/dist/data/quiz/function/handle_answer/merge_14.mcfunction +2 -0
- package/dist/data/quiz/function/handle_answer/merge_17.mcfunction +2 -0
- package/dist/data/quiz/function/handle_answer/merge_2.mcfunction +8 -0
- package/dist/data/quiz/function/handle_answer/merge_5.mcfunction +2 -0
- package/dist/data/quiz/function/handle_answer/merge_8.mcfunction +2 -0
- package/dist/data/quiz/function/handle_answer/then_0.mcfunction +5 -0
- package/dist/data/quiz/function/handle_answer/then_12.mcfunction +5 -0
- package/dist/data/quiz/function/handle_answer/then_15.mcfunction +6 -0
- package/dist/data/quiz/function/handle_answer/then_3.mcfunction +6 -0
- package/dist/data/quiz/function/handle_answer/then_6.mcfunction +5 -0
- package/dist/data/quiz/function/handle_answer/then_9.mcfunction +6 -0
- package/dist/data/quiz/function/handle_answer.mcfunction +11 -0
- package/dist/data/quiz/function/start_quiz.mcfunction +5 -0
- package/dist/data/shop/function/__load.mcfunction +7 -0
- package/dist/data/shop/function/__tick.mcfunction +3 -0
- package/dist/data/shop/function/__trigger_shop_buy_dispatch.mcfunction +4 -0
- package/dist/data/shop/function/complete_purchase/else_1.mcfunction +5 -0
- package/dist/data/shop/function/complete_purchase/else_4.mcfunction +5 -0
- package/dist/data/shop/function/complete_purchase/else_7.mcfunction +3 -0
- package/dist/data/shop/function/complete_purchase/merge_2.mcfunction +2 -0
- package/dist/data/shop/function/complete_purchase/merge_5.mcfunction +2 -0
- package/dist/data/shop/function/complete_purchase/merge_8.mcfunction +2 -0
- package/dist/data/shop/function/complete_purchase/then_0.mcfunction +4 -0
- package/dist/data/shop/function/complete_purchase/then_3.mcfunction +4 -0
- package/dist/data/shop/function/complete_purchase/then_6.mcfunction +4 -0
- package/dist/data/shop/function/complete_purchase.mcfunction +7 -0
- package/dist/data/shop/function/handle_shop_trigger.mcfunction +3 -0
- package/dist/data/turret/function/__load.mcfunction +5 -0
- package/dist/data/turret/function/__tick.mcfunction +4 -0
- package/dist/data/turret/function/__trigger_deploy_turret_dispatch.mcfunction +4 -0
- package/dist/data/turret/function/deploy_turret.mcfunction +8 -0
- package/dist/data/turret/function/turret_tick/at_1.mcfunction +2 -0
- package/dist/data/turret/function/turret_tick/foreach_0.mcfunction +2 -0
- package/dist/data/turret/function/turret_tick/foreach_2.mcfunction +2 -0
- package/dist/data/turret/function/turret_tick/tick_body.mcfunction +3 -0
- package/dist/data/turret/function/turret_tick/tick_skip.mcfunction +1 -0
- package/dist/data/turret/function/turret_tick.mcfunction +5 -0
- package/dist/diagnostics/index.d.ts +44 -0
- package/dist/diagnostics/index.js +140 -0
- package/dist/index.d.ts +53 -0
- package/dist/index.js +126 -0
- package/dist/ir/builder.d.ts +32 -0
- package/dist/ir/builder.js +99 -0
- package/dist/ir/types.d.ts +117 -0
- package/dist/ir/types.js +15 -0
- package/dist/lexer/index.d.ts +36 -0
- package/dist/lexer/index.js +458 -0
- package/dist/lowering/index.d.ts +106 -0
- package/dist/lowering/index.js +2041 -0
- package/dist/mc-test/client.d.ts +128 -0
- package/dist/mc-test/client.js +174 -0
- package/dist/mc-test/runner.d.ts +28 -0
- package/dist/mc-test/runner.js +150 -0
- package/dist/mc-test/setup.d.ts +11 -0
- package/dist/mc-test/setup.js +98 -0
- package/dist/mc-validator/index.d.ts +17 -0
- package/dist/mc-validator/index.js +322 -0
- package/dist/nbt/index.d.ts +86 -0
- package/dist/nbt/index.js +250 -0
- package/dist/optimizer/commands.d.ts +36 -0
- package/dist/optimizer/commands.js +349 -0
- package/dist/optimizer/passes.d.ts +34 -0
- package/dist/optimizer/passes.js +227 -0
- package/dist/optimizer/structure.d.ts +8 -0
- package/dist/optimizer/structure.js +344 -0
- package/dist/pack.mcmeta +6 -0
- package/dist/parser/index.d.ts +76 -0
- package/dist/parser/index.js +1193 -0
- package/dist/repl.d.ts +16 -0
- package/dist/repl.js +165 -0
- package/dist/runtime/index.d.ts +101 -0
- package/dist/runtime/index.js +1288 -0
- package/dist/typechecker/index.d.ts +42 -0
- package/dist/typechecker/index.js +629 -0
- package/docs/COMPILATION_STATS.md +142 -0
- package/docs/IMPLEMENTATION_GUIDE.md +512 -0
- package/docs/LANGUAGE_REFERENCE.md +415 -0
- package/docs/MC_MAPPING.md +280 -0
- package/docs/STRUCTURE_TARGET.md +80 -0
- package/docs/mc-reference/commands.md +259 -0
- package/editors/vscode/.vscodeignore +10 -0
- package/editors/vscode/LICENSE +21 -0
- package/editors/vscode/README.md +78 -0
- package/editors/vscode/build.mjs +28 -0
- package/editors/vscode/icon.png +0 -0
- package/editors/vscode/mcfunction-language-configuration.json +28 -0
- package/editors/vscode/out/extension.js +7236 -0
- package/editors/vscode/package-lock.json +566 -0
- package/editors/vscode/package.json +137 -0
- package/editors/vscode/redscript-language-configuration.json +28 -0
- package/editors/vscode/snippets/redscript.json +114 -0
- package/editors/vscode/src/codeactions.ts +89 -0
- package/editors/vscode/src/completion.ts +130 -0
- package/editors/vscode/src/extension.ts +239 -0
- package/editors/vscode/src/hover.ts +1120 -0
- package/editors/vscode/src/symbols.ts +207 -0
- package/editors/vscode/syntaxes/mcfunction.tmLanguage.json +740 -0
- package/editors/vscode/syntaxes/redscript.tmLanguage.json +357 -0
- package/editors/vscode/tsconfig.json +13 -0
- package/jest.config.js +5 -0
- package/package.json +38 -0
- package/src/__tests__/cli.test.ts +130 -0
- package/src/__tests__/codegen.test.ts +128 -0
- package/src/__tests__/diagnostics.test.ts +195 -0
- package/src/__tests__/e2e.test.ts +1721 -0
- package/src/__tests__/fixtures/mc-commands-1.21.4.json +18734 -0
- package/src/__tests__/formatter.test.ts +46 -0
- package/src/__tests__/lexer.test.ts +356 -0
- package/src/__tests__/lowering.test.ts +962 -0
- package/src/__tests__/mc-integration.test.ts +409 -0
- package/src/__tests__/mc-syntax.test.ts +96 -0
- package/src/__tests__/nbt.test.ts +58 -0
- package/src/__tests__/optimizer-advanced.test.ts +144 -0
- package/src/__tests__/optimizer.test.ts +129 -0
- package/src/__tests__/parser.test.ts +800 -0
- package/src/__tests__/repl.test.ts +33 -0
- package/src/__tests__/runtime.test.ts +289 -0
- package/src/__tests__/structure-optimizer.test.ts +38 -0
- package/src/__tests__/typechecker.test.ts +395 -0
- package/src/ast/types.ts +248 -0
- package/src/cli.ts +445 -0
- package/src/codegen/cmdblock/index.ts +63 -0
- package/src/codegen/mcfunction/index.ts +471 -0
- package/src/codegen/structure/index.ts +305 -0
- package/src/compile.ts +188 -0
- package/src/diagnostics/index.ts +186 -0
- package/src/examples/README.md +77 -0
- package/src/examples/SHOWCASE_GAME.md +43 -0
- package/src/examples/arena.rs +44 -0
- package/src/examples/counter.rs +12 -0
- package/src/examples/pvp_arena.rs +131 -0
- package/src/examples/quiz.rs +90 -0
- package/src/examples/rpg.rs +13 -0
- package/src/examples/shop.rs +30 -0
- package/src/examples/showcase_game.rs +552 -0
- package/src/examples/stdlib_demo.rs +181 -0
- package/src/examples/turret.rs +27 -0
- package/src/examples/world_manager.rs +23 -0
- package/src/formatter/index.ts +22 -0
- package/src/index.ts +161 -0
- package/src/ir/builder.ts +114 -0
- package/src/ir/types.ts +119 -0
- package/src/lexer/index.ts +555 -0
- package/src/lowering/index.ts +2406 -0
- package/src/mc-test/client.ts +259 -0
- package/src/mc-test/runner.ts +140 -0
- package/src/mc-test/setup.ts +70 -0
- package/src/mc-validator/index.ts +367 -0
- package/src/nbt/index.ts +321 -0
- package/src/optimizer/commands.ts +416 -0
- package/src/optimizer/passes.ts +233 -0
- package/src/optimizer/structure.ts +441 -0
- package/src/parser/index.ts +1437 -0
- package/src/repl.ts +165 -0
- package/src/runtime/index.ts +1403 -0
- package/src/stdlib/README.md +156 -0
- package/src/stdlib/combat.rs +20 -0
- package/src/stdlib/cooldown.rs +45 -0
- package/src/stdlib/math.rs +49 -0
- package/src/stdlib/mobs.rs +99 -0
- package/src/stdlib/player.rs +29 -0
- package/src/stdlib/strings.rs +7 -0
- package/src/stdlib/timer.rs +51 -0
- package/src/templates/README.md +126 -0
- package/src/templates/combat.rs +96 -0
- package/src/templates/economy.rs +40 -0
- package/src/templates/mini-game-framework.rs +117 -0
- package/src/templates/quest.rs +78 -0
- package/src/test_programs/zombie_game.rs +25 -0
- package/src/typechecker/index.ts +737 -0
- package/tsconfig.json +16 -0
package/README.zh.md
ADDED
|
@@ -0,0 +1,261 @@
|
|
|
1
|
+
<div align="center">
|
|
2
|
+
|
|
3
|
+
<img src="https://img.shields.io/badge/RedScript-1.0-red?style=for-the-badge&logo=minecraft&logoColor=white" alt="RedScript" />
|
|
4
|
+
|
|
5
|
+
**一个编译到 Minecraft Datapack 的类型化脚本语言。**
|
|
6
|
+
|
|
7
|
+
写干净的游戏逻辑,把记分板的面条代码交给 RedScript 处理。
|
|
8
|
+
|
|
9
|
+
[](https://github.com/bkmashiro/redscript/actions/workflows/ci.yml)
|
|
10
|
+
[](https://www.npmjs.com/package/redscript-mc)
|
|
11
|
+
[](./LICENSE)
|
|
12
|
+
[](./src/__tests__)
|
|
13
|
+
|
|
14
|
+
[English](./README.md) · [Wiki](https://github.com/bkmashiro/redscript/wiki) · [快速开始](#快速开始)
|
|
15
|
+
|
|
16
|
+
</div>
|
|
17
|
+
|
|
18
|
+
---
|
|
19
|
+
|
|
20
|
+
### RedScript 是什么?
|
|
21
|
+
|
|
22
|
+
你想做一个 Minecraft 小游戏——倒计时、击杀计数、复活逻辑、记分板显示。用原版 MC 的话,这意味着 40+ 个 `.mcfunction` 文件、几百条 `execute if score` 命令,还要花一个周末调试。
|
|
23
|
+
|
|
24
|
+
用 RedScript,就是这样:
|
|
25
|
+
|
|
26
|
+
```rs
|
|
27
|
+
// pvp_game.rs
|
|
28
|
+
import "stdlib/player.rs"
|
|
29
|
+
|
|
30
|
+
const GAME_TIME: int = 300;
|
|
31
|
+
|
|
32
|
+
@tick(rate=20)
|
|
33
|
+
fn every_second() {
|
|
34
|
+
let time: int = scoreboard_get(#game, #timer);
|
|
35
|
+
|
|
36
|
+
if (time <= 0) {
|
|
37
|
+
end_game();
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
scoreboard_set(#game, #timer, time - 1);
|
|
42
|
+
actionbar(@a, "⏱ 剩余 ${time} 秒");
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
fn start_game() {
|
|
46
|
+
scoreboard_set(#game, #timer, GAME_TIME);
|
|
47
|
+
scoreboard_set(#game, #running, 1);
|
|
48
|
+
title(@a, "开始战斗!", "游戏已开始");
|
|
49
|
+
tp(@a, (0, 64, 0));
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
fn end_game() {
|
|
53
|
+
scoreboard_set(#game, #running, 0);
|
|
54
|
+
title(@a, "游戏结束!");
|
|
55
|
+
announce("感谢游玩!");
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
@on_death
|
|
59
|
+
fn on_kill() {
|
|
60
|
+
scoreboard_add(@s, #kills, 1);
|
|
61
|
+
}
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
一个文件,几秒钟编译出可以直接用的 datapack。
|
|
65
|
+
|
|
66
|
+
---
|
|
67
|
+
|
|
68
|
+
### 快速开始
|
|
69
|
+
|
|
70
|
+
#### 安装
|
|
71
|
+
|
|
72
|
+
```bash
|
|
73
|
+
npm install -g redscript
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
#### 编译
|
|
77
|
+
|
|
78
|
+
```bash
|
|
79
|
+
redscript compile pvp_game.rs -o ./my-datapack
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
```
|
|
83
|
+
✓ 已编译 pvp_game.rs
|
|
84
|
+
命名空间 : pvp_game
|
|
85
|
+
函数数量 : 7
|
|
86
|
+
命令数量 : 34 → 28 (优化器节省了 18%)
|
|
87
|
+
输出目录 : ./my-datapack/
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
#### 部署
|
|
91
|
+
|
|
92
|
+
把输出文件夹丢进存档的 `datapacks/` 目录,游戏内跑 `/reload`,完成。
|
|
93
|
+
|
|
94
|
+
---
|
|
95
|
+
|
|
96
|
+
### 语言特性
|
|
97
|
+
|
|
98
|
+
#### 变量与类型
|
|
99
|
+
|
|
100
|
+
```rs
|
|
101
|
+
let x: int = 42;
|
|
102
|
+
let name: string = "Steve";
|
|
103
|
+
let spawn: BlockPos = (0, 64, 0);
|
|
104
|
+
let nearby: BlockPos = (~5, ~0, ~5); // 相对坐标
|
|
105
|
+
const MAX: int = 100; // 编译期常量
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
#### MC 名称(Objective / Tag / Team)
|
|
109
|
+
|
|
110
|
+
用 `#name` 表示 Minecraft 标识符,不需要引号:
|
|
111
|
+
|
|
112
|
+
```rs
|
|
113
|
+
// Objective、fake player、tag、team 名都不用引号
|
|
114
|
+
let hp: int = scoreboard_get(@s, #health);
|
|
115
|
+
scoreboard_set(#game, #timer, 300); // fake player #game,objective timer
|
|
116
|
+
tag_add(@s, #hasKey);
|
|
117
|
+
team_join(#red, @s);
|
|
118
|
+
gamerule(#keepInventory, true);
|
|
119
|
+
|
|
120
|
+
// 字符串仍然兼容(向后兼容)
|
|
121
|
+
scoreboard_get(@s, "health") // 和 #health 编译结果相同
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
#### 函数与默认参数
|
|
125
|
+
|
|
126
|
+
```rs
|
|
127
|
+
fn greet(player: selector, msg: string = "欢迎!") {
|
|
128
|
+
tell(player, msg);
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
greet(@s); // 使用默认消息
|
|
132
|
+
greet(@a, "你好!"); // 覆盖默认值
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
#### 装饰器
|
|
136
|
+
|
|
137
|
+
```rs
|
|
138
|
+
@tick // 每 tick 执行
|
|
139
|
+
fn heartbeat() { ... }
|
|
140
|
+
|
|
141
|
+
@tick(rate=20) // 每秒执行一次
|
|
142
|
+
fn every_second() { ... }
|
|
143
|
+
|
|
144
|
+
@on_advancement("story/mine_diamond")
|
|
145
|
+
fn on_diamond() {
|
|
146
|
+
give(@s, "minecraft:diamond", 5);
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
@on_death
|
|
150
|
+
fn on_death() {
|
|
151
|
+
scoreboard_add(@s, #deaths, 1);
|
|
152
|
+
}
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
#### 控制流
|
|
156
|
+
|
|
157
|
+
```rs
|
|
158
|
+
if (hp <= 0) {
|
|
159
|
+
respawn();
|
|
160
|
+
} else if (hp < 5) {
|
|
161
|
+
warn_player();
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
for (let i: int = 0; i < 10; i = i + 1) {
|
|
165
|
+
summon("minecraft:zombie", (i, 64, 0));
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
foreach (player in @a) {
|
|
169
|
+
heal(player, 2);
|
|
170
|
+
}
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
#### 结构体与枚举
|
|
174
|
+
|
|
175
|
+
```rs
|
|
176
|
+
enum Phase { Lobby, Playing, Ended }
|
|
177
|
+
|
|
178
|
+
struct Player {
|
|
179
|
+
score: int,
|
|
180
|
+
alive: bool,
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
match (phase) {
|
|
184
|
+
Phase::Lobby => { announce("等待玩家..."); }
|
|
185
|
+
Phase::Playing => { every_second(); }
|
|
186
|
+
Phase::Ended => { show_scoreboard(); }
|
|
187
|
+
}
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
#### Lambda
|
|
191
|
+
|
|
192
|
+
```rs
|
|
193
|
+
fn apply(f: (int) -> int, x: int) -> int {
|
|
194
|
+
return f(x);
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
let double = (x: int) -> int { return x * 2; };
|
|
198
|
+
apply(double, 5); // 10
|
|
199
|
+
```
|
|
200
|
+
|
|
201
|
+
#### 数组
|
|
202
|
+
|
|
203
|
+
```rs
|
|
204
|
+
let scores: int[] = [];
|
|
205
|
+
push(scores, 42);
|
|
206
|
+
|
|
207
|
+
foreach (s in scores) {
|
|
208
|
+
announce("得分:${s}");
|
|
209
|
+
}
|
|
210
|
+
```
|
|
211
|
+
|
|
212
|
+
---
|
|
213
|
+
|
|
214
|
+
### CLI 参考
|
|
215
|
+
|
|
216
|
+
```
|
|
217
|
+
redscript compile <file> 编译为 datapack(默认)或 structure
|
|
218
|
+
-o, --output <dir> 输出目录 [默认: ./out]
|
|
219
|
+
--target datapack|structure 输出格式 [默认: datapack]
|
|
220
|
+
--namespace <ns> Datapack 命名空间 [默认: 文件名]
|
|
221
|
+
--no-optimize 禁用优化器
|
|
222
|
+
--stats 输出优化器统计信息
|
|
223
|
+
|
|
224
|
+
redscript repl 启动交互式 REPL
|
|
225
|
+
redscript validate <file> 验证 MC 命令语法
|
|
226
|
+
```
|
|
227
|
+
|
|
228
|
+
---
|
|
229
|
+
|
|
230
|
+
### 标准库
|
|
231
|
+
|
|
232
|
+
```rs
|
|
233
|
+
import "stdlib/math.rs" // abs, min, max, clamp
|
|
234
|
+
import "stdlib/player.rs" // is_alive, in_range, get_health
|
|
235
|
+
import "stdlib/timer.rs" // start_timer, tick_timer, has_elapsed
|
|
236
|
+
import "stdlib/cooldown.rs" // set_cooldown, check_cooldown
|
|
237
|
+
import "stdlib/mobs.rs" // ZOMBIE, SKELETON, CREEPER ... (60+ 实体常量)
|
|
238
|
+
```
|
|
239
|
+
|
|
240
|
+
---
|
|
241
|
+
|
|
242
|
+
### 更多文档
|
|
243
|
+
|
|
244
|
+
| | |
|
|
245
|
+
|---|---|
|
|
246
|
+
| 📖 [语言参考](docs/LANGUAGE_REFERENCE.md) | 完整语法与类型系统 |
|
|
247
|
+
| 🔧 [内置函数](https://github.com/bkmashiro/redscript/wiki/Builtins) | 所有 34+ MC 内置函数 |
|
|
248
|
+
| ⚡ [优化器](https://github.com/bkmashiro/redscript/wiki/Optimizer) | 各优化 Pass 说明 |
|
|
249
|
+
| 🧱 [结构体目标](docs/STRUCTURE_TARGET.md) | 编译到 NBT 命令方块结构体 |
|
|
250
|
+
| 🧪 [集成测试](https://github.com/bkmashiro/redscript/wiki/Integration-Testing) | 在真实 Paper 服务器上测试 |
|
|
251
|
+
| 🏗 [实现指南](docs/IMPLEMENTATION_GUIDE.md) | 编译器内部原理 |
|
|
252
|
+
|
|
253
|
+
---
|
|
254
|
+
|
|
255
|
+
<div align="center">
|
|
256
|
+
|
|
257
|
+
MIT License · Copyright © 2026 [bkmashiro](https://github.com/bkmashiro)
|
|
258
|
+
|
|
259
|
+
*少写一点,多做一些,更快交付。*
|
|
260
|
+
|
|
261
|
+
</div>
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
const index_1 = require("../index");
|
|
37
|
+
const fs = __importStar(require("fs"));
|
|
38
|
+
const os = __importStar(require("os"));
|
|
39
|
+
const path = __importStar(require("path"));
|
|
40
|
+
const child_process_1 = require("child_process");
|
|
41
|
+
// Note: watch command is tested manually as it's an interactive long-running process
|
|
42
|
+
describe('CLI API', () => {
|
|
43
|
+
describe('imports', () => {
|
|
44
|
+
it('compiles a file with imported helpers', () => {
|
|
45
|
+
const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'redscript-imports-'));
|
|
46
|
+
const libPath = path.join(tempDir, 'lib.rs');
|
|
47
|
+
const mainPath = path.join(tempDir, 'main.rs');
|
|
48
|
+
fs.writeFileSync(libPath, 'fn double(x: int) -> int { return x + x; }\n');
|
|
49
|
+
fs.writeFileSync(mainPath, 'import "./lib.rs"\n\nfn main() { let value: int = double(2); }\n');
|
|
50
|
+
const source = fs.readFileSync(mainPath, 'utf-8');
|
|
51
|
+
const result = (0, index_1.compile)(source, { namespace: 'imports', filePath: mainPath });
|
|
52
|
+
expect(result.files.length).toBeGreaterThan(0);
|
|
53
|
+
expect(result.ir.functions.some(fn => fn.name === 'double')).toBe(true);
|
|
54
|
+
expect(result.ir.functions.some(fn => fn.name === 'main')).toBe(true);
|
|
55
|
+
});
|
|
56
|
+
it('deduplicates circular imports', () => {
|
|
57
|
+
const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'redscript-circular-'));
|
|
58
|
+
const aPath = path.join(tempDir, 'a.rs');
|
|
59
|
+
const bPath = path.join(tempDir, 'b.rs');
|
|
60
|
+
const mainPath = path.join(tempDir, 'main.rs');
|
|
61
|
+
fs.writeFileSync(aPath, 'import "./b.rs"\n\nfn from_a() -> int { return 1; }\n');
|
|
62
|
+
fs.writeFileSync(bPath, 'import "./a.rs"\n\nfn from_b() -> int { return from_a(); }\n');
|
|
63
|
+
fs.writeFileSync(mainPath, 'import "./a.rs"\n\nfn main() { let value: int = from_b(); }\n');
|
|
64
|
+
const source = fs.readFileSync(mainPath, 'utf-8');
|
|
65
|
+
const result = (0, index_1.compile)(source, { namespace: 'circular', filePath: mainPath });
|
|
66
|
+
expect(result.ir.functions.filter(fn => fn.name === 'from_a')).toHaveLength(1);
|
|
67
|
+
expect(result.ir.functions.filter(fn => fn.name === 'from_b')).toHaveLength(1);
|
|
68
|
+
});
|
|
69
|
+
});
|
|
70
|
+
describe('compile()', () => {
|
|
71
|
+
it('compiles simple source', () => {
|
|
72
|
+
const source = 'fn test() { say("hello"); }';
|
|
73
|
+
const result = (0, index_1.compile)(source, { namespace: 'mypack' });
|
|
74
|
+
expect(result.files.length).toBeGreaterThan(0);
|
|
75
|
+
expect(result.ast.namespace).toBe('mypack');
|
|
76
|
+
expect(result.ir.functions.length).toBe(1);
|
|
77
|
+
});
|
|
78
|
+
it('uses default namespace', () => {
|
|
79
|
+
const source = 'fn test() {}';
|
|
80
|
+
const result = (0, index_1.compile)(source);
|
|
81
|
+
expect(result.ast.namespace).toBe('redscript');
|
|
82
|
+
});
|
|
83
|
+
it('generates correct file structure', () => {
|
|
84
|
+
const source = 'fn test() { say("hello"); }';
|
|
85
|
+
const result = (0, index_1.compile)(source, { namespace: 'game' });
|
|
86
|
+
const paths = result.files.map(f => f.path);
|
|
87
|
+
expect(paths).toContain('pack.mcmeta');
|
|
88
|
+
expect(paths).toContain('data/game/function/__load.mcfunction');
|
|
89
|
+
expect(paths.some(p => p.includes('test.mcfunction'))).toBe(true);
|
|
90
|
+
});
|
|
91
|
+
it('collects optimizer stats', () => {
|
|
92
|
+
const source = `
|
|
93
|
+
fn build() {
|
|
94
|
+
foreach (turret in @e[tag=turret]) {
|
|
95
|
+
let range: int = scoreboard_get("config", "turret_range");
|
|
96
|
+
if (range > 0) {
|
|
97
|
+
if (range > -1) {
|
|
98
|
+
say("ready");
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
`;
|
|
104
|
+
const result = (0, index_1.compile)(source, { namespace: 'stats' });
|
|
105
|
+
expect(result.stats?.licmHoists).toBeGreaterThan(0);
|
|
106
|
+
expect(result.stats?.totalCommandsBefore).toBeGreaterThan(result.stats?.totalCommandsAfter ?? 0);
|
|
107
|
+
expect(result.stats?.deadCodeRemoved).toBeGreaterThanOrEqual(0);
|
|
108
|
+
});
|
|
109
|
+
});
|
|
110
|
+
describe('--stats flag', () => {
|
|
111
|
+
it('prints optimizer statistics', () => {
|
|
112
|
+
const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'redscript-stats-'));
|
|
113
|
+
const inputPath = path.join(tempDir, 'input.rs');
|
|
114
|
+
const outputDir = path.join(tempDir, 'out');
|
|
115
|
+
fs.writeFileSync(inputPath, 'fn build() { setblock((0, 64, 0), "minecraft:stone"); setblock((1, 64, 0), "minecraft:stone"); }');
|
|
116
|
+
const stdout = (0, child_process_1.execFileSync)(process.execPath, ['-r', 'ts-node/register', path.join(process.cwd(), 'src/cli.ts'), 'compile', inputPath, '-o', outputDir, '--stats'], { cwd: process.cwd(), encoding: 'utf-8' });
|
|
117
|
+
expect(stdout).toContain('Optimizations applied:');
|
|
118
|
+
expect(stdout).toContain('setblock batching:');
|
|
119
|
+
expect(stdout).toContain('Total mcfunction commands:');
|
|
120
|
+
});
|
|
121
|
+
});
|
|
122
|
+
describe('check()', () => {
|
|
123
|
+
it('returns null for valid source', () => {
|
|
124
|
+
const source = 'fn test() { say("hello"); }';
|
|
125
|
+
const error = (0, index_1.check)(source);
|
|
126
|
+
expect(error).toBeNull();
|
|
127
|
+
});
|
|
128
|
+
it('returns error for invalid source', () => {
|
|
129
|
+
const source = 'fn test( { say("hello"); }'; // Missing )
|
|
130
|
+
const error = (0, index_1.check)(source);
|
|
131
|
+
expect(error).toBeInstanceOf(Error);
|
|
132
|
+
});
|
|
133
|
+
it('returns error for syntax errors', () => {
|
|
134
|
+
const source = 'fn test() { let x = ; }'; // Missing value
|
|
135
|
+
const error = (0, index_1.check)(source);
|
|
136
|
+
expect(error).toBeInstanceOf(Error);
|
|
137
|
+
});
|
|
138
|
+
});
|
|
139
|
+
});
|
|
140
|
+
//# sourceMappingURL=cli.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const mcfunction_1 = require("../codegen/mcfunction");
|
|
4
|
+
describe('generateDatapack', () => {
|
|
5
|
+
it('generates pack.mcmeta', () => {
|
|
6
|
+
const mod = { namespace: 'test', functions: [], globals: [] };
|
|
7
|
+
const files = (0, mcfunction_1.generateDatapack)(mod);
|
|
8
|
+
const meta = files.find(f => f.path === 'pack.mcmeta');
|
|
9
|
+
expect(meta).toBeDefined();
|
|
10
|
+
expect(JSON.parse(meta.content).pack.pack_format).toBe(26);
|
|
11
|
+
});
|
|
12
|
+
it('generates __load.mcfunction with objective setup', () => {
|
|
13
|
+
const mod = { namespace: 'mypack', functions: [], globals: ['counter'] };
|
|
14
|
+
const files = (0, mcfunction_1.generateDatapack)(mod);
|
|
15
|
+
const load = files.find(f => f.path.includes('__load.mcfunction'));
|
|
16
|
+
expect(load?.content).toContain('scoreboard objectives add rs dummy');
|
|
17
|
+
expect(load?.content).toContain('scoreboard players set $counter rs 0');
|
|
18
|
+
});
|
|
19
|
+
it('generates function file for simple add(a, b)', () => {
|
|
20
|
+
const mod = {
|
|
21
|
+
namespace: 'mypack',
|
|
22
|
+
globals: [],
|
|
23
|
+
functions: [{
|
|
24
|
+
name: 'add',
|
|
25
|
+
params: ['a', 'b'],
|
|
26
|
+
locals: ['a', 'b', 'result'],
|
|
27
|
+
blocks: [{
|
|
28
|
+
label: 'entry',
|
|
29
|
+
instrs: [
|
|
30
|
+
{ op: 'binop', dst: 'result', lhs: { kind: 'var', name: 'a' }, bop: '+', rhs: { kind: 'var', name: 'b' } },
|
|
31
|
+
],
|
|
32
|
+
term: { op: 'return', value: { kind: 'var', name: 'result' } },
|
|
33
|
+
}],
|
|
34
|
+
}],
|
|
35
|
+
};
|
|
36
|
+
const files = (0, mcfunction_1.generateDatapack)(mod);
|
|
37
|
+
const fn = files.find(f => f.path.includes('add.mcfunction'));
|
|
38
|
+
expect(fn).toBeDefined();
|
|
39
|
+
// Should have param setup
|
|
40
|
+
expect(fn.content).toContain('scoreboard players operation $a rs = $p0 rs');
|
|
41
|
+
expect(fn.content).toContain('scoreboard players operation $b rs = $p1 rs');
|
|
42
|
+
// Should have add operation
|
|
43
|
+
expect(fn.content).toContain('+=');
|
|
44
|
+
});
|
|
45
|
+
it('generates tick tag for tick loop function', () => {
|
|
46
|
+
const mod = {
|
|
47
|
+
namespace: 'mypack',
|
|
48
|
+
globals: [],
|
|
49
|
+
functions: [{
|
|
50
|
+
name: 'game_loop',
|
|
51
|
+
params: [],
|
|
52
|
+
locals: [],
|
|
53
|
+
blocks: [{ label: 'entry', instrs: [], term: { op: 'return' } }],
|
|
54
|
+
isTickLoop: true,
|
|
55
|
+
}],
|
|
56
|
+
};
|
|
57
|
+
const files = (0, mcfunction_1.generateDatapack)(mod);
|
|
58
|
+
// tick.json should point to __tick
|
|
59
|
+
const tickTag = files.find(f => f.path.includes('tick.json'));
|
|
60
|
+
expect(tickTag).toBeDefined();
|
|
61
|
+
expect(JSON.parse(tickTag.content).values).toContain('mypack:__tick');
|
|
62
|
+
// __tick.mcfunction should call the game_loop function
|
|
63
|
+
const tickFn = files.find(f => f.path.includes('__tick.mcfunction'));
|
|
64
|
+
expect(tickFn).toBeDefined();
|
|
65
|
+
expect(tickFn.content).toContain('function mypack:game_loop');
|
|
66
|
+
});
|
|
67
|
+
it('generates conditional branches with execute if/unless', () => {
|
|
68
|
+
const mod = {
|
|
69
|
+
namespace: 'mypack',
|
|
70
|
+
globals: [],
|
|
71
|
+
functions: [{
|
|
72
|
+
name: 'check',
|
|
73
|
+
params: [],
|
|
74
|
+
locals: ['cond'],
|
|
75
|
+
blocks: [
|
|
76
|
+
{
|
|
77
|
+
label: 'entry',
|
|
78
|
+
instrs: [
|
|
79
|
+
{ op: 'assign', dst: 'cond', src: { kind: 'const', value: 1 } },
|
|
80
|
+
],
|
|
81
|
+
term: { op: 'jump_if', cond: 'cond', then: 'then_block', else_: 'else_block' },
|
|
82
|
+
},
|
|
83
|
+
{
|
|
84
|
+
label: 'then_block',
|
|
85
|
+
instrs: [{ op: 'raw', cmd: 'say hello' }],
|
|
86
|
+
term: { op: 'return' },
|
|
87
|
+
},
|
|
88
|
+
{
|
|
89
|
+
label: 'else_block',
|
|
90
|
+
instrs: [{ op: 'raw', cmd: 'say goodbye' }],
|
|
91
|
+
term: { op: 'return' },
|
|
92
|
+
},
|
|
93
|
+
],
|
|
94
|
+
}],
|
|
95
|
+
};
|
|
96
|
+
const files = (0, mcfunction_1.generateDatapack)(mod);
|
|
97
|
+
const entry = files.find(f => f.path.endsWith('check.mcfunction'));
|
|
98
|
+
expect(entry?.content).toContain('execute if score $cond rs matches 1..');
|
|
99
|
+
expect(entry?.content).toContain('execute if score $cond rs matches ..0');
|
|
100
|
+
});
|
|
101
|
+
it('generates advancement json for event decorators', () => {
|
|
102
|
+
const mod = {
|
|
103
|
+
namespace: 'mypack',
|
|
104
|
+
globals: [],
|
|
105
|
+
functions: [{
|
|
106
|
+
name: 'on_mine_diamond',
|
|
107
|
+
params: [],
|
|
108
|
+
locals: [],
|
|
109
|
+
blocks: [{ label: 'entry', instrs: [], term: { op: 'return' } }],
|
|
110
|
+
eventTrigger: { kind: 'advancement', value: 'story/mine_diamond' },
|
|
111
|
+
}],
|
|
112
|
+
};
|
|
113
|
+
const result = (0, mcfunction_1.generateDatapackWithStats)(mod);
|
|
114
|
+
const advancement = result.advancements.find(f => f.path === 'data/mypack/advancements/on_advancement_on_mine_diamond.json');
|
|
115
|
+
expect(advancement).toBeDefined();
|
|
116
|
+
const json = JSON.parse(advancement.content);
|
|
117
|
+
expect(json.criteria.trigger.trigger).toBe('minecraft:story/mine_diamond');
|
|
118
|
+
expect(json.rewards.function).toBe('mypack:on_mine_diamond');
|
|
119
|
+
});
|
|
120
|
+
});
|
|
121
|
+
//# sourceMappingURL=codegen.test.js.map
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Diagnostics Tests
|
|
4
|
+
*/
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
const diagnostics_1 = require("../diagnostics");
|
|
7
|
+
const compile_1 = require("../compile");
|
|
8
|
+
describe('DiagnosticError', () => {
|
|
9
|
+
describe('formatError', () => {
|
|
10
|
+
it('formats source context with a caret pointer', () => {
|
|
11
|
+
const source = [
|
|
12
|
+
'fn main() {',
|
|
13
|
+
' let x = foo(',
|
|
14
|
+
'}',
|
|
15
|
+
].join('\n');
|
|
16
|
+
const error = new diagnostics_1.DiagnosticError('TypeError', 'Unknown function: foo', { line: 2, col: 11 }, source.split('\n'));
|
|
17
|
+
expect((0, diagnostics_1.formatError)(error, source)).toBe([
|
|
18
|
+
'Error at line 2, col 11:',
|
|
19
|
+
' let x = foo(',
|
|
20
|
+
' ^',
|
|
21
|
+
'Unknown function: foo',
|
|
22
|
+
].join('\n'));
|
|
23
|
+
});
|
|
24
|
+
it('includes file path when available', () => {
|
|
25
|
+
const source = 'let x = foo();';
|
|
26
|
+
const error = new diagnostics_1.DiagnosticError('TypeError', 'Unknown function: foo', { file: 'test.rs', line: 1, col: 9 }, source.split('\n'));
|
|
27
|
+
expect((0, diagnostics_1.formatError)(error, source)).toContain('Error in test.rs at line 1, col 9:');
|
|
28
|
+
});
|
|
29
|
+
});
|
|
30
|
+
describe('format', () => {
|
|
31
|
+
it('formats error with source line and pointer', () => {
|
|
32
|
+
const sourceLines = [
|
|
33
|
+
'fn main() {',
|
|
34
|
+
' let x = 42',
|
|
35
|
+
'}',
|
|
36
|
+
];
|
|
37
|
+
const error = new diagnostics_1.DiagnosticError('ParseError', "Expected ';' after statement", { line: 2, col: 14 }, sourceLines);
|
|
38
|
+
const formatted = error.format();
|
|
39
|
+
expect(formatted).toContain('[ParseError]');
|
|
40
|
+
expect(formatted).toContain('line 2');
|
|
41
|
+
expect(formatted).toContain('col 14');
|
|
42
|
+
expect(formatted).toContain('let x = 42');
|
|
43
|
+
expect(formatted).toContain('^');
|
|
44
|
+
});
|
|
45
|
+
it('formats error with file path', () => {
|
|
46
|
+
const error = new diagnostics_1.DiagnosticError('LexError', 'Unexpected character', { file: 'test.rs', line: 1, col: 1 }, ['@@@']);
|
|
47
|
+
const formatted = error.format();
|
|
48
|
+
expect(formatted).toContain('test.rs:');
|
|
49
|
+
expect(formatted).toContain('[LexError]');
|
|
50
|
+
});
|
|
51
|
+
it('handles missing source lines gracefully', () => {
|
|
52
|
+
const error = new diagnostics_1.DiagnosticError('ParseError', 'Syntax error', { line: 10, col: 5 });
|
|
53
|
+
const formatted = error.format();
|
|
54
|
+
expect(formatted).toContain('[ParseError]');
|
|
55
|
+
expect(formatted).toContain('line 10');
|
|
56
|
+
});
|
|
57
|
+
});
|
|
58
|
+
});
|
|
59
|
+
describe('DiagnosticCollector', () => {
|
|
60
|
+
it('collects multiple errors', () => {
|
|
61
|
+
const collector = new diagnostics_1.DiagnosticCollector('line1\nline2\nline3');
|
|
62
|
+
collector.error('ParseError', 'First error', 1, 1);
|
|
63
|
+
collector.error('ParseError', 'Second error', 2, 1);
|
|
64
|
+
expect(collector.hasErrors()).toBe(true);
|
|
65
|
+
expect(collector.getErrors()).toHaveLength(2);
|
|
66
|
+
});
|
|
67
|
+
it('formats all errors', () => {
|
|
68
|
+
const collector = new diagnostics_1.DiagnosticCollector('let x');
|
|
69
|
+
collector.error('ParseError', 'Missing semicolon', 1, 6);
|
|
70
|
+
const formatted = collector.formatAll();
|
|
71
|
+
expect(formatted).toContain('Missing semicolon');
|
|
72
|
+
expect(formatted).toContain('let x');
|
|
73
|
+
});
|
|
74
|
+
});
|
|
75
|
+
describe('parseErrorMessage', () => {
|
|
76
|
+
it('extracts line and col from error message', () => {
|
|
77
|
+
const err = (0, diagnostics_1.parseErrorMessage)('ParseError', "Expected ';' at line 5, col 12", ['', '', '', '', 'let x = 42']);
|
|
78
|
+
expect(err.location.line).toBe(5);
|
|
79
|
+
expect(err.location.col).toBe(12);
|
|
80
|
+
expect(err.message).toBe("Expected ';'");
|
|
81
|
+
});
|
|
82
|
+
it('defaults to line 1, col 1 if no position in message', () => {
|
|
83
|
+
const err = (0, diagnostics_1.parseErrorMessage)('LexError', 'Unknown error');
|
|
84
|
+
expect(err.location.line).toBe(1);
|
|
85
|
+
expect(err.location.col).toBe(1);
|
|
86
|
+
});
|
|
87
|
+
});
|
|
88
|
+
describe('compile function', () => {
|
|
89
|
+
it('returns success for valid code', () => {
|
|
90
|
+
const result = (0, compile_1.compile)('fn main() { let x = 1; }');
|
|
91
|
+
expect(result.success).toBe(true);
|
|
92
|
+
expect(result.files).toBeDefined();
|
|
93
|
+
});
|
|
94
|
+
it('returns DiagnosticError for lex errors', () => {
|
|
95
|
+
const result = (0, compile_1.compile)('fn main() { let x = $ }');
|
|
96
|
+
expect(result.success).toBe(false);
|
|
97
|
+
expect(result.error).toBeInstanceOf(diagnostics_1.DiagnosticError);
|
|
98
|
+
expect(result.error?.kind).toBe('LexError');
|
|
99
|
+
});
|
|
100
|
+
it('returns DiagnosticError for parse errors', () => {
|
|
101
|
+
const result = (0, compile_1.compile)('fn main() { let x = }');
|
|
102
|
+
expect(result.success).toBe(false);
|
|
103
|
+
expect(result.error).toBeInstanceOf(diagnostics_1.DiagnosticError);
|
|
104
|
+
expect(result.error?.kind).toBe('ParseError');
|
|
105
|
+
});
|
|
106
|
+
it('returns DiagnosticError for missing semicolon', () => {
|
|
107
|
+
const result = (0, compile_1.compile)('fn main() { let x = 42 }');
|
|
108
|
+
expect(result.success).toBe(false);
|
|
109
|
+
expect(result.error?.kind).toBe('ParseError');
|
|
110
|
+
expect(result.error?.message).toContain("Expected ';'");
|
|
111
|
+
});
|
|
112
|
+
it('includes file path in error', () => {
|
|
113
|
+
const result = (0, compile_1.compile)('fn main() { }', { filePath: 'test.rs' });
|
|
114
|
+
// This is valid, but test that filePath is passed through
|
|
115
|
+
expect(result.success).toBe(true);
|
|
116
|
+
});
|
|
117
|
+
it('formats error nicely', () => {
|
|
118
|
+
const result = (0, compile_1.compile)('fn main() {\n let x = 42\n}');
|
|
119
|
+
expect(result.success).toBe(false);
|
|
120
|
+
const formatted = (0, compile_1.formatCompileError)(result);
|
|
121
|
+
expect(formatted).toContain('Error at line');
|
|
122
|
+
expect(formatted).toContain('^');
|
|
123
|
+
// Error points to } on line 3, which is where semicolon was expected
|
|
124
|
+
expect(formatted).toContain('}');
|
|
125
|
+
});
|
|
126
|
+
});
|
|
127
|
+
describe('Lexer DiagnosticError', () => {
|
|
128
|
+
it('throws DiagnosticError for unexpected character', () => {
|
|
129
|
+
const result = (0, compile_1.compile)('fn main() { let x = $ }');
|
|
130
|
+
expect(result.success).toBe(false);
|
|
131
|
+
expect(result.error?.kind).toBe('LexError');
|
|
132
|
+
expect(result.error?.message).toContain('Unexpected character');
|
|
133
|
+
});
|
|
134
|
+
it('throws DiagnosticError for unterminated string', () => {
|
|
135
|
+
const result = (0, compile_1.compile)('fn main() { let x = "hello }');
|
|
136
|
+
expect(result.success).toBe(false);
|
|
137
|
+
expect(result.error?.kind).toBe('LexError');
|
|
138
|
+
expect(result.error?.message).toContain('Unterminated string');
|
|
139
|
+
});
|
|
140
|
+
});
|
|
141
|
+
describe('Parser DiagnosticError', () => {
|
|
142
|
+
it('includes line and column info', () => {
|
|
143
|
+
const result = (0, compile_1.compile)('fn main() { return }');
|
|
144
|
+
expect(result.success).toBe(false);
|
|
145
|
+
expect(result.error?.location.line).toBeGreaterThan(0);
|
|
146
|
+
expect(result.error?.location.col).toBeGreaterThan(0);
|
|
147
|
+
});
|
|
148
|
+
});
|
|
149
|
+
//# sourceMappingURL=diagnostics.test.js.map
|