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/src/cli.ts
ADDED
|
@@ -0,0 +1,445 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* RedScript CLI
|
|
4
|
+
*
|
|
5
|
+
* Usage:
|
|
6
|
+
* redscript compile <file> [-o <out>] [--output-nbt <file>] [--namespace <ns>]
|
|
7
|
+
* redscript check <file>
|
|
8
|
+
* redscript repl
|
|
9
|
+
* redscript version
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import { compile, check } from './index'
|
|
13
|
+
import { generateCommandBlocks } from './codegen/cmdblock'
|
|
14
|
+
import { compileToStructure } from './codegen/structure'
|
|
15
|
+
import { formatError } from './diagnostics'
|
|
16
|
+
import { startRepl } from './repl'
|
|
17
|
+
import type { OptimizationStats } from './optimizer/commands'
|
|
18
|
+
import * as fs from 'fs'
|
|
19
|
+
import * as path from 'path'
|
|
20
|
+
|
|
21
|
+
// Parse command line arguments
|
|
22
|
+
const args = process.argv.slice(2)
|
|
23
|
+
|
|
24
|
+
function printUsage(): void {
|
|
25
|
+
console.log(`
|
|
26
|
+
RedScript Compiler
|
|
27
|
+
|
|
28
|
+
Usage:
|
|
29
|
+
redscript compile <file> [-o <out>] [--output-nbt <file>] [--namespace <ns>] [--target <target>]
|
|
30
|
+
redscript watch <dir> [-o <outdir>] [--namespace <ns>] [--hot-reload <url>]
|
|
31
|
+
redscript check <file>
|
|
32
|
+
redscript fmt <file.rs> [file2.rs ...]
|
|
33
|
+
redscript repl
|
|
34
|
+
redscript version
|
|
35
|
+
|
|
36
|
+
Commands:
|
|
37
|
+
compile Compile a RedScript file to a Minecraft datapack
|
|
38
|
+
watch Watch a directory for .rs file changes, recompile, and hot reload
|
|
39
|
+
check Check a RedScript file for errors without generating output
|
|
40
|
+
fmt Auto-format RedScript source files
|
|
41
|
+
repl Start an interactive RedScript REPL
|
|
42
|
+
version Print the RedScript version
|
|
43
|
+
|
|
44
|
+
Options:
|
|
45
|
+
-o, --output <path> Output directory or file path, depending on target
|
|
46
|
+
--output-nbt <file> Output .nbt file path for structure target
|
|
47
|
+
--namespace <ns> Datapack namespace (default: derived from filename)
|
|
48
|
+
--target <target> Output target: datapack (default), cmdblock, or structure
|
|
49
|
+
--stats Print optimizer statistics
|
|
50
|
+
--hot-reload <url> After each successful compile, POST to <url>/reload
|
|
51
|
+
(use with redscript-testharness; e.g. http://localhost:25561)
|
|
52
|
+
-h, --help Show this help message
|
|
53
|
+
|
|
54
|
+
Targets:
|
|
55
|
+
datapack Generate a full Minecraft datapack (default)
|
|
56
|
+
cmdblock Generate JSON structure for command block placement
|
|
57
|
+
structure Generate a Minecraft structure .nbt file with command blocks
|
|
58
|
+
`)
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function printVersion(): void {
|
|
62
|
+
const packagePath = path.join(__dirname, '..', 'package.json')
|
|
63
|
+
try {
|
|
64
|
+
const pkg = JSON.parse(fs.readFileSync(packagePath, 'utf-8'))
|
|
65
|
+
console.log(`RedScript v${pkg.version}`)
|
|
66
|
+
} catch {
|
|
67
|
+
console.log('RedScript v0.1.0')
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function parseArgs(args: string[]): {
|
|
72
|
+
command?: string
|
|
73
|
+
file?: string
|
|
74
|
+
output?: string
|
|
75
|
+
outputNbt?: string
|
|
76
|
+
namespace?: string
|
|
77
|
+
target?: string
|
|
78
|
+
stats?: boolean
|
|
79
|
+
help?: boolean
|
|
80
|
+
hotReload?: string
|
|
81
|
+
} {
|
|
82
|
+
const result: ReturnType<typeof parseArgs> = {}
|
|
83
|
+
let i = 0
|
|
84
|
+
|
|
85
|
+
while (i < args.length) {
|
|
86
|
+
const arg = args[i]
|
|
87
|
+
|
|
88
|
+
if (arg === '-h' || arg === '--help') {
|
|
89
|
+
result.help = true
|
|
90
|
+
i++
|
|
91
|
+
} else if (arg === '-o' || arg === '--output') {
|
|
92
|
+
result.output = args[++i]
|
|
93
|
+
i++
|
|
94
|
+
} else if (arg === '--output-nbt') {
|
|
95
|
+
result.outputNbt = args[++i]
|
|
96
|
+
i++
|
|
97
|
+
} else if (arg === '--namespace') {
|
|
98
|
+
result.namespace = args[++i]
|
|
99
|
+
i++
|
|
100
|
+
} else if (arg === '--target') {
|
|
101
|
+
result.target = args[++i]
|
|
102
|
+
i++
|
|
103
|
+
} else if (arg === '--stats') {
|
|
104
|
+
result.stats = true
|
|
105
|
+
i++
|
|
106
|
+
} else if (arg === '--hot-reload') {
|
|
107
|
+
result.hotReload = args[++i]
|
|
108
|
+
i++
|
|
109
|
+
} else if (!result.command) {
|
|
110
|
+
result.command = arg
|
|
111
|
+
i++
|
|
112
|
+
} else if (!result.file) {
|
|
113
|
+
result.file = arg
|
|
114
|
+
i++
|
|
115
|
+
} else {
|
|
116
|
+
i++
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
return result
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
function deriveNamespace(filePath: string): string {
|
|
124
|
+
const basename = path.basename(filePath, path.extname(filePath))
|
|
125
|
+
// Convert to valid identifier: lowercase, replace non-alphanumeric with underscore
|
|
126
|
+
return basename.toLowerCase().replace(/[^a-z0-9]/g, '_')
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
function printWarnings(warnings: Array<{ code: string; message: string }> | undefined): void {
|
|
130
|
+
if (!warnings || warnings.length === 0) {
|
|
131
|
+
return
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
for (const warning of warnings) {
|
|
135
|
+
console.error(`Warning [${warning.code}]: ${warning.message}`)
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
function formatReduction(before: number, after: number): string {
|
|
140
|
+
if (before === 0) return '0%'
|
|
141
|
+
return `${Math.round(((before - after) / before) * 100)}%`
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
function printOptimizationStats(stats: OptimizationStats | undefined): void {
|
|
145
|
+
if (!stats) return
|
|
146
|
+
|
|
147
|
+
console.log('Optimizations applied:')
|
|
148
|
+
console.log(` LICM: ${stats.licmHoists} reads hoisted from ${stats.licmLoopBodies} loop bodies`)
|
|
149
|
+
console.log(` CSE: ${stats.cseRedundantReads + stats.cseArithmetic} expressions eliminated`)
|
|
150
|
+
console.log(` setblock batching: ${stats.setblockMergedCommands} setblocks -> ${stats.setblockFillCommands} fills (saved ${stats.setblockSavedCommands} commands)`)
|
|
151
|
+
console.log(` dead code: ${stats.deadCodeRemoved} commands removed`)
|
|
152
|
+
console.log(` constant folding: ${stats.constantFolds} constants folded`)
|
|
153
|
+
console.log(` Total mcfunction commands: ${stats.totalCommandsBefore} -> ${stats.totalCommandsAfter} (${formatReduction(stats.totalCommandsBefore, stats.totalCommandsAfter)} reduction)`)
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
function compileCommand(file: string, output: string, namespace: string, target: string = 'datapack', showStats = false): void {
|
|
157
|
+
// Read source file
|
|
158
|
+
if (!fs.existsSync(file)) {
|
|
159
|
+
console.error(`Error: File not found: ${file}`)
|
|
160
|
+
process.exit(1)
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
const source = fs.readFileSync(file, 'utf-8')
|
|
164
|
+
|
|
165
|
+
try {
|
|
166
|
+
if (target === 'cmdblock') {
|
|
167
|
+
const result = compile(source, { namespace, filePath: file })
|
|
168
|
+
printWarnings(result.warnings)
|
|
169
|
+
|
|
170
|
+
// Generate command block JSON
|
|
171
|
+
const hasTick = result.files.some(f => f.path.includes('__tick.mcfunction'))
|
|
172
|
+
const hasLoad = result.files.some(f => f.path.includes('__load.mcfunction'))
|
|
173
|
+
const cmdBlocks = generateCommandBlocks(namespace, hasTick, hasLoad)
|
|
174
|
+
|
|
175
|
+
// Write command block JSON
|
|
176
|
+
fs.mkdirSync(output, { recursive: true })
|
|
177
|
+
const outputFile = path.join(output, `${namespace}_cmdblocks.json`)
|
|
178
|
+
fs.writeFileSync(outputFile, JSON.stringify(cmdBlocks, null, 2))
|
|
179
|
+
|
|
180
|
+
console.log(`✓ Generated command blocks for ${file}`)
|
|
181
|
+
console.log(` Output: ${outputFile}`)
|
|
182
|
+
console.log(` Blocks: ${cmdBlocks.blocks.length}`)
|
|
183
|
+
if (showStats) {
|
|
184
|
+
printOptimizationStats(result.stats)
|
|
185
|
+
}
|
|
186
|
+
} else if (target === 'structure') {
|
|
187
|
+
const structure = compileToStructure(source, namespace, file)
|
|
188
|
+
fs.mkdirSync(path.dirname(output), { recursive: true })
|
|
189
|
+
fs.writeFileSync(output, structure.buffer)
|
|
190
|
+
|
|
191
|
+
console.log(`✓ Generated structure for ${file}`)
|
|
192
|
+
console.log(` Output: ${output}`)
|
|
193
|
+
console.log(` Blocks: ${structure.blockCount}`)
|
|
194
|
+
if (showStats) {
|
|
195
|
+
printOptimizationStats(structure.stats)
|
|
196
|
+
}
|
|
197
|
+
} else {
|
|
198
|
+
const result = compile(source, { namespace, filePath: file })
|
|
199
|
+
printWarnings(result.warnings)
|
|
200
|
+
|
|
201
|
+
// Default: generate datapack
|
|
202
|
+
// Create output directory
|
|
203
|
+
fs.mkdirSync(output, { recursive: true })
|
|
204
|
+
|
|
205
|
+
// Write all files
|
|
206
|
+
for (const dataFile of result.files) {
|
|
207
|
+
const filePath = path.join(output, dataFile.path)
|
|
208
|
+
const dir = path.dirname(filePath)
|
|
209
|
+
fs.mkdirSync(dir, { recursive: true })
|
|
210
|
+
fs.writeFileSync(filePath, dataFile.content)
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
console.log(`✓ Compiled ${file} to ${output}/`)
|
|
214
|
+
console.log(` Namespace: ${namespace}`)
|
|
215
|
+
console.log(` Functions: ${result.ir.functions.length}`)
|
|
216
|
+
console.log(` Files: ${result.files.length}`)
|
|
217
|
+
if (showStats) {
|
|
218
|
+
printOptimizationStats(result.stats)
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
} catch (err) {
|
|
222
|
+
console.error(formatError(err as Error, source))
|
|
223
|
+
process.exit(1)
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
function checkCommand(file: string): void {
|
|
228
|
+
// Read source file
|
|
229
|
+
if (!fs.existsSync(file)) {
|
|
230
|
+
console.error(`Error: File not found: ${file}`)
|
|
231
|
+
process.exit(1)
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
const source = fs.readFileSync(file, 'utf-8')
|
|
235
|
+
|
|
236
|
+
const error = check(source, 'redscript', file)
|
|
237
|
+
if (error) {
|
|
238
|
+
console.error(formatError(error, source))
|
|
239
|
+
process.exit(1)
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
console.log(`✓ ${file} is valid`)
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
async function hotReload(url: string): Promise<void> {
|
|
246
|
+
try {
|
|
247
|
+
const res = await fetch(`${url}/reload`, { method: 'POST' })
|
|
248
|
+
if (res.ok) {
|
|
249
|
+
console.log(`🔄 Hot reload sent → ${url}`)
|
|
250
|
+
} else {
|
|
251
|
+
console.warn(`⚠ Hot reload failed: HTTP ${res.status}`)
|
|
252
|
+
}
|
|
253
|
+
} catch (e) {
|
|
254
|
+
console.warn(`⚠ Hot reload failed (is the server running?): ${(e as Error).message}`)
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
function watchCommand(dir: string, output: string, namespace?: string, hotReloadUrl?: string): void {
|
|
259
|
+
// Check if directory exists
|
|
260
|
+
if (!fs.existsSync(dir)) {
|
|
261
|
+
console.error(`Error: Directory not found: ${dir}`)
|
|
262
|
+
process.exit(1)
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
const stat = fs.statSync(dir)
|
|
266
|
+
if (!stat.isDirectory()) {
|
|
267
|
+
console.error(`Error: ${dir} is not a directory`)
|
|
268
|
+
process.exit(1)
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
console.log(`👁 Watching ${dir} for .rs file changes...`)
|
|
272
|
+
console.log(` Output: ${output}`)
|
|
273
|
+
if (hotReloadUrl) console.log(` Hot reload: ${hotReloadUrl}`)
|
|
274
|
+
console.log(` Press Ctrl+C to stop\n`)
|
|
275
|
+
|
|
276
|
+
// Debounce timer
|
|
277
|
+
let debounceTimer: NodeJS.Timeout | null = null
|
|
278
|
+
|
|
279
|
+
// Compile all .rs files in directory
|
|
280
|
+
async function compileAll(): Promise<void> {
|
|
281
|
+
const files = findRsFiles(dir)
|
|
282
|
+
if (files.length === 0) {
|
|
283
|
+
console.log(`⚠ No .rs files found in ${dir}`)
|
|
284
|
+
return
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
let hasErrors = false
|
|
288
|
+
for (const file of files) {
|
|
289
|
+
let source = ''
|
|
290
|
+
try {
|
|
291
|
+
source = fs.readFileSync(file, 'utf-8')
|
|
292
|
+
const ns = namespace ?? deriveNamespace(file)
|
|
293
|
+
const result = compile(source, { namespace: ns, filePath: file })
|
|
294
|
+
printWarnings(result.warnings)
|
|
295
|
+
|
|
296
|
+
// Create output directory
|
|
297
|
+
fs.mkdirSync(output, { recursive: true })
|
|
298
|
+
|
|
299
|
+
// Write all files
|
|
300
|
+
for (const dataFile of result.files) {
|
|
301
|
+
const filePath = path.join(output, dataFile.path)
|
|
302
|
+
const fileDir = path.dirname(filePath)
|
|
303
|
+
fs.mkdirSync(fileDir, { recursive: true })
|
|
304
|
+
fs.writeFileSync(filePath, dataFile.content)
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
const timestamp = new Date().toLocaleTimeString()
|
|
308
|
+
console.log(`✓ [${timestamp}] Compiled ${file} (${result.files.length} files)`)
|
|
309
|
+
} catch (err) {
|
|
310
|
+
hasErrors = true
|
|
311
|
+
const timestamp = new Date().toLocaleTimeString()
|
|
312
|
+
console.error(`✗ [${timestamp}] ${formatError(err as Error, source)}`)
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
if (!hasErrors) {
|
|
317
|
+
if (hotReloadUrl) await hotReload(hotReloadUrl)
|
|
318
|
+
console.log('')
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
// Find all .rs files recursively
|
|
323
|
+
function findRsFiles(directory: string): string[] {
|
|
324
|
+
const results: string[] = []
|
|
325
|
+
const entries = fs.readdirSync(directory, { withFileTypes: true })
|
|
326
|
+
|
|
327
|
+
for (const entry of entries) {
|
|
328
|
+
const fullPath = path.join(directory, entry.name)
|
|
329
|
+
if (entry.isDirectory()) {
|
|
330
|
+
results.push(...findRsFiles(fullPath))
|
|
331
|
+
} else if (entry.isFile() && entry.name.endsWith('.rs')) {
|
|
332
|
+
results.push(fullPath)
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
return results
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
// Initial compile
|
|
340
|
+
void compileAll()
|
|
341
|
+
|
|
342
|
+
// Watch for changes
|
|
343
|
+
fs.watch(dir, { recursive: true }, (eventType, filename) => {
|
|
344
|
+
if (filename && filename.endsWith('.rs')) {
|
|
345
|
+
// Debounce rapid changes
|
|
346
|
+
if (debounceTimer) {
|
|
347
|
+
clearTimeout(debounceTimer)
|
|
348
|
+
}
|
|
349
|
+
debounceTimer = setTimeout(() => {
|
|
350
|
+
console.log(`📝 Change detected: ${filename}`)
|
|
351
|
+
void compileAll()
|
|
352
|
+
}, 100)
|
|
353
|
+
}
|
|
354
|
+
})
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
// Main
|
|
358
|
+
const parsed = parseArgs(args)
|
|
359
|
+
|
|
360
|
+
async function main(): Promise<void> {
|
|
361
|
+
if (parsed.help || !parsed.command) {
|
|
362
|
+
printUsage()
|
|
363
|
+
process.exit(parsed.help ? 0 : 1)
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
switch (parsed.command) {
|
|
367
|
+
case 'compile':
|
|
368
|
+
if (!parsed.file) {
|
|
369
|
+
console.error('Error: No input file specified')
|
|
370
|
+
printUsage()
|
|
371
|
+
process.exit(1)
|
|
372
|
+
}
|
|
373
|
+
{
|
|
374
|
+
const namespace = parsed.namespace ?? deriveNamespace(parsed.file)
|
|
375
|
+
const target = parsed.target ?? 'datapack'
|
|
376
|
+
const output = target === 'structure'
|
|
377
|
+
? (parsed.outputNbt ?? parsed.output ?? `./${namespace}.nbt`)
|
|
378
|
+
: (parsed.output ?? './dist')
|
|
379
|
+
|
|
380
|
+
compileCommand(
|
|
381
|
+
parsed.file,
|
|
382
|
+
output,
|
|
383
|
+
namespace,
|
|
384
|
+
target,
|
|
385
|
+
parsed.stats
|
|
386
|
+
)
|
|
387
|
+
}
|
|
388
|
+
break
|
|
389
|
+
|
|
390
|
+
case 'watch':
|
|
391
|
+
if (!parsed.file) {
|
|
392
|
+
console.error('Error: No directory specified')
|
|
393
|
+
printUsage()
|
|
394
|
+
process.exit(1)
|
|
395
|
+
}
|
|
396
|
+
watchCommand(
|
|
397
|
+
parsed.file,
|
|
398
|
+
parsed.output ?? './dist',
|
|
399
|
+
parsed.namespace,
|
|
400
|
+
parsed.hotReload
|
|
401
|
+
)
|
|
402
|
+
break
|
|
403
|
+
|
|
404
|
+
case 'check':
|
|
405
|
+
if (!parsed.file) {
|
|
406
|
+
console.error('Error: No input file specified')
|
|
407
|
+
printUsage()
|
|
408
|
+
process.exit(1)
|
|
409
|
+
}
|
|
410
|
+
checkCommand(parsed.file)
|
|
411
|
+
break
|
|
412
|
+
|
|
413
|
+
case 'fmt':
|
|
414
|
+
case 'format': {
|
|
415
|
+
const files = args.filter(a => a.endsWith('.rs'))
|
|
416
|
+
if (files.length === 0) {
|
|
417
|
+
console.error('Usage: redscript fmt <file.rs> [file2.rs ...]')
|
|
418
|
+
process.exit(1)
|
|
419
|
+
}
|
|
420
|
+
const { format } = require('./formatter')
|
|
421
|
+
for (const file of files) {
|
|
422
|
+
const content = fs.readFileSync(file, 'utf8')
|
|
423
|
+
const formatted = format(content)
|
|
424
|
+
fs.writeFileSync(file, formatted)
|
|
425
|
+
console.log(`Formatted: ${file}`)
|
|
426
|
+
}
|
|
427
|
+
break
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
case 'repl':
|
|
431
|
+
await startRepl(parsed.namespace ?? 'repl')
|
|
432
|
+
break
|
|
433
|
+
|
|
434
|
+
case 'version':
|
|
435
|
+
printVersion()
|
|
436
|
+
break
|
|
437
|
+
|
|
438
|
+
default:
|
|
439
|
+
console.error(`Error: Unknown command '${parsed.command}'`)
|
|
440
|
+
printUsage()
|
|
441
|
+
process.exit(1)
|
|
442
|
+
}
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
void main()
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Command Block Target
|
|
3
|
+
*
|
|
4
|
+
* Generates a JSON structure representing command blocks that can be
|
|
5
|
+
* placed in Minecraft to run the compiled datapack.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
export interface CommandBlock {
|
|
9
|
+
type: 'repeat' | 'impulse' | 'chain'
|
|
10
|
+
command: string
|
|
11
|
+
pos: [number, number, number]
|
|
12
|
+
auto?: boolean
|
|
13
|
+
conditional?: boolean
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export interface CommandBlockStructure {
|
|
17
|
+
format: 'redscript-cmdblock-v1'
|
|
18
|
+
namespace: string
|
|
19
|
+
blocks: CommandBlock[]
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Generate a command block structure JSON for a given namespace.
|
|
24
|
+
*
|
|
25
|
+
* Creates:
|
|
26
|
+
* - 1 × Repeat block: function <namespace>:__tick
|
|
27
|
+
* - 1 × Impulse block (auto): function <namespace>:__load
|
|
28
|
+
*/
|
|
29
|
+
export function generateCommandBlocks(
|
|
30
|
+
namespace: string,
|
|
31
|
+
hasTick: boolean,
|
|
32
|
+
hasLoad: boolean
|
|
33
|
+
): CommandBlockStructure {
|
|
34
|
+
const blocks: CommandBlock[] = []
|
|
35
|
+
let x = 0
|
|
36
|
+
|
|
37
|
+
// Load block - impulse with auto (runs once when placed)
|
|
38
|
+
if (hasLoad) {
|
|
39
|
+
blocks.push({
|
|
40
|
+
type: 'impulse',
|
|
41
|
+
command: `function ${namespace}:__load`,
|
|
42
|
+
pos: [x, 0, 0],
|
|
43
|
+
auto: true,
|
|
44
|
+
})
|
|
45
|
+
x++
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// Tick block - repeat (runs every tick)
|
|
49
|
+
if (hasTick) {
|
|
50
|
+
blocks.push({
|
|
51
|
+
type: 'repeat',
|
|
52
|
+
command: `function ${namespace}:__tick`,
|
|
53
|
+
pos: [x, 0, 0],
|
|
54
|
+
})
|
|
55
|
+
x++
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
return {
|
|
59
|
+
format: 'redscript-cmdblock-v1',
|
|
60
|
+
namespace,
|
|
61
|
+
blocks,
|
|
62
|
+
}
|
|
63
|
+
}
|