redscript-mc 1.2.29 → 2.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/.claude/commands/build-test.md +10 -0
- package/.claude/commands/deploy-demo.md +12 -0
- package/.claude/commands/stage-status.md +13 -0
- package/.claude/settings.json +12 -0
- package/.github/workflows/ci.yml +1 -0
- package/CLAUDE.md +231 -0
- package/README.md +29 -28
- package/README.zh.md +28 -28
- 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/lexer/index.js +9 -1
- package/dist/lowering/index.js +22 -5
- package/dist/src/__tests__/cli.test.d.ts +1 -0
- package/dist/src/__tests__/cli.test.js +104 -0
- package/dist/src/__tests__/codegen.test.d.ts +1 -0
- package/dist/src/__tests__/codegen.test.js +152 -0
- package/dist/src/__tests__/compile-all.test.d.ts +10 -0
- package/dist/src/__tests__/compile-all.test.js +108 -0
- package/dist/src/__tests__/dce.test.d.ts +1 -0
- package/dist/src/__tests__/dce.test.js +102 -0
- package/dist/src/__tests__/diagnostics.test.d.ts +4 -0
- package/dist/src/__tests__/diagnostics.test.js +177 -0
- package/dist/src/__tests__/e2e.test.d.ts +6 -0
- package/dist/src/__tests__/e2e.test.js +1789 -0
- package/dist/src/__tests__/entity-types.test.d.ts +1 -0
- package/dist/src/__tests__/entity-types.test.js +203 -0
- package/dist/src/__tests__/formatter.test.d.ts +1 -0
- package/dist/src/__tests__/formatter.test.js +40 -0
- package/dist/src/__tests__/lexer.test.d.ts +1 -0
- package/dist/src/__tests__/lexer.test.js +343 -0
- package/dist/src/__tests__/lowering.test.d.ts +1 -0
- package/dist/src/__tests__/lowering.test.js +1015 -0
- package/dist/src/__tests__/macro.test.d.ts +8 -0
- package/dist/src/__tests__/macro.test.js +306 -0
- package/dist/src/__tests__/mc-integration.test.d.ts +12 -0
- package/dist/src/__tests__/mc-integration.test.js +817 -0
- package/dist/src/__tests__/mc-syntax.test.d.ts +1 -0
- package/dist/src/__tests__/mc-syntax.test.js +124 -0
- package/dist/src/__tests__/nbt.test.d.ts +1 -0
- package/dist/src/__tests__/nbt.test.js +82 -0
- package/dist/src/__tests__/optimizer-advanced.test.d.ts +1 -0
- package/dist/src/__tests__/optimizer-advanced.test.js +124 -0
- package/dist/src/__tests__/optimizer.test.d.ts +1 -0
- package/dist/src/__tests__/optimizer.test.js +149 -0
- package/dist/src/__tests__/parser.test.d.ts +1 -0
- package/dist/src/__tests__/parser.test.js +807 -0
- package/dist/src/__tests__/repl.test.d.ts +1 -0
- package/dist/src/__tests__/repl.test.js +27 -0
- package/dist/src/__tests__/runtime.test.d.ts +1 -0
- package/dist/src/__tests__/runtime.test.js +289 -0
- package/dist/src/__tests__/stdlib-advanced.test.d.ts +4 -0
- package/dist/src/__tests__/stdlib-advanced.test.js +374 -0
- package/dist/src/__tests__/stdlib-bigint.test.d.ts +7 -0
- package/dist/src/__tests__/stdlib-bigint.test.js +426 -0
- package/dist/src/__tests__/stdlib-math.test.d.ts +7 -0
- package/dist/src/__tests__/stdlib-math.test.js +351 -0
- package/dist/src/__tests__/stdlib-vec.test.d.ts +4 -0
- package/dist/src/__tests__/stdlib-vec.test.js +263 -0
- package/dist/src/__tests__/structure-optimizer.test.d.ts +1 -0
- package/dist/src/__tests__/structure-optimizer.test.js +33 -0
- package/dist/src/__tests__/typechecker.test.d.ts +1 -0
- package/dist/src/__tests__/typechecker.test.js +552 -0
- package/dist/src/__tests__/var-allocator.test.d.ts +1 -0
- package/dist/src/__tests__/var-allocator.test.js +69 -0
- package/dist/src/ast/types.d.ts +515 -0
- package/dist/src/ast/types.js +9 -0
- package/dist/src/builtins/metadata.d.ts +36 -0
- package/dist/src/builtins/metadata.js +1014 -0
- package/dist/src/cli.d.ts +11 -0
- package/dist/src/cli.js +443 -0
- package/dist/src/codegen/cmdblock/index.d.ts +26 -0
- package/dist/src/codegen/cmdblock/index.js +45 -0
- package/dist/src/codegen/mcfunction/index.d.ts +40 -0
- package/dist/src/codegen/mcfunction/index.js +606 -0
- package/dist/src/codegen/structure/index.d.ts +24 -0
- package/dist/src/codegen/structure/index.js +279 -0
- package/dist/src/codegen/var-allocator.d.ts +45 -0
- package/dist/src/codegen/var-allocator.js +104 -0
- package/dist/src/compile.d.ts +37 -0
- package/dist/src/compile.js +165 -0
- package/dist/src/diagnostics/index.d.ts +44 -0
- package/dist/src/diagnostics/index.js +140 -0
- package/dist/src/events/types.d.ts +35 -0
- package/dist/src/events/types.js +59 -0
- package/dist/src/formatter/index.d.ts +1 -0
- package/dist/src/formatter/index.js +26 -0
- package/dist/src/index.d.ts +22 -0
- package/dist/src/index.js +45 -0
- package/dist/src/ir/builder.d.ts +33 -0
- package/dist/src/ir/builder.js +99 -0
- package/dist/src/ir/types.d.ts +132 -0
- package/dist/src/ir/types.js +15 -0
- package/dist/src/lexer/index.d.ts +37 -0
- package/dist/src/lexer/index.js +569 -0
- package/dist/src/lowering/index.d.ts +188 -0
- package/dist/src/lowering/index.js +3405 -0
- package/dist/src/mc-test/client.d.ts +128 -0
- package/dist/src/mc-test/client.js +174 -0
- package/dist/src/mc-test/runner.d.ts +28 -0
- package/dist/src/mc-test/runner.js +151 -0
- package/dist/src/mc-test/setup.d.ts +11 -0
- package/dist/src/mc-test/setup.js +98 -0
- package/dist/src/mc-validator/index.d.ts +17 -0
- package/dist/src/mc-validator/index.js +322 -0
- package/dist/src/nbt/index.d.ts +86 -0
- package/dist/src/nbt/index.js +250 -0
- package/dist/src/optimizer/commands.d.ts +38 -0
- package/dist/src/optimizer/commands.js +451 -0
- package/dist/src/optimizer/dce.d.ts +34 -0
- package/dist/src/optimizer/dce.js +639 -0
- package/dist/src/optimizer/passes.d.ts +34 -0
- package/dist/src/optimizer/passes.js +243 -0
- package/dist/src/optimizer/structure.d.ts +9 -0
- package/dist/src/optimizer/structure.js +356 -0
- package/dist/src/parser/index.d.ts +93 -0
- package/dist/src/parser/index.js +1687 -0
- package/dist/src/repl.d.ts +16 -0
- package/dist/src/repl.js +165 -0
- package/dist/src/runtime/index.d.ts +107 -0
- package/dist/src/runtime/index.js +1409 -0
- package/dist/src/typechecker/index.d.ts +61 -0
- package/dist/src/typechecker/index.js +1034 -0
- package/dist/src/types/entity-hierarchy.d.ts +29 -0
- package/dist/src/types/entity-hierarchy.js +107 -0
- package/dist/src2/__tests__/e2e/basic.test.d.ts +8 -0
- package/dist/src2/__tests__/e2e/basic.test.js +140 -0
- package/dist/src2/__tests__/e2e/macros.test.d.ts +9 -0
- package/dist/src2/__tests__/e2e/macros.test.js +182 -0
- package/dist/src2/__tests__/e2e/migrate.test.d.ts +13 -0
- package/dist/src2/__tests__/e2e/migrate.test.js +2739 -0
- package/dist/src2/__tests__/hir/desugar.test.d.ts +1 -0
- package/dist/src2/__tests__/hir/desugar.test.js +234 -0
- package/dist/src2/__tests__/lir/lower.test.d.ts +1 -0
- package/dist/src2/__tests__/lir/lower.test.js +559 -0
- package/dist/src2/__tests__/lir/types.test.d.ts +1 -0
- package/dist/src2/__tests__/lir/types.test.js +185 -0
- package/dist/src2/__tests__/lir/verify.test.d.ts +1 -0
- package/dist/src2/__tests__/lir/verify.test.js +221 -0
- package/dist/src2/__tests__/mir/arithmetic.test.d.ts +1 -0
- package/dist/src2/__tests__/mir/arithmetic.test.js +130 -0
- package/dist/src2/__tests__/mir/control-flow.test.d.ts +1 -0
- package/dist/src2/__tests__/mir/control-flow.test.js +205 -0
- package/dist/src2/__tests__/mir/verify.test.d.ts +1 -0
- package/dist/src2/__tests__/mir/verify.test.js +223 -0
- package/dist/src2/__tests__/optimizer/block_merge.test.d.ts +1 -0
- package/dist/src2/__tests__/optimizer/block_merge.test.js +78 -0
- package/dist/src2/__tests__/optimizer/branch_simplify.test.d.ts +1 -0
- package/dist/src2/__tests__/optimizer/branch_simplify.test.js +58 -0
- package/dist/src2/__tests__/optimizer/constant_fold.test.d.ts +1 -0
- package/dist/src2/__tests__/optimizer/constant_fold.test.js +131 -0
- package/dist/src2/__tests__/optimizer/copy_prop.test.d.ts +1 -0
- package/dist/src2/__tests__/optimizer/copy_prop.test.js +91 -0
- package/dist/src2/__tests__/optimizer/dce.test.d.ts +1 -0
- package/dist/src2/__tests__/optimizer/dce.test.js +76 -0
- package/dist/src2/__tests__/optimizer/pipeline.test.d.ts +1 -0
- package/dist/src2/__tests__/optimizer/pipeline.test.js +102 -0
- package/dist/src2/emit/compile.d.ts +19 -0
- package/dist/src2/emit/compile.js +80 -0
- package/dist/src2/emit/index.d.ts +17 -0
- package/dist/src2/emit/index.js +172 -0
- package/dist/src2/hir/lower.d.ts +15 -0
- package/dist/src2/hir/lower.js +378 -0
- package/dist/src2/hir/types.d.ts +373 -0
- package/dist/src2/hir/types.js +16 -0
- package/dist/src2/lir/lower.d.ts +15 -0
- package/dist/src2/lir/lower.js +453 -0
- package/dist/src2/lir/types.d.ts +136 -0
- package/dist/src2/lir/types.js +11 -0
- package/dist/src2/lir/verify.d.ts +14 -0
- package/dist/src2/lir/verify.js +113 -0
- package/dist/src2/mir/lower.d.ts +9 -0
- package/dist/src2/mir/lower.js +1030 -0
- package/dist/src2/mir/macro.d.ts +22 -0
- package/dist/src2/mir/macro.js +168 -0
- package/dist/src2/mir/types.d.ts +183 -0
- package/dist/src2/mir/types.js +11 -0
- package/dist/src2/mir/verify.d.ts +16 -0
- package/dist/src2/mir/verify.js +216 -0
- package/dist/src2/optimizer/block_merge.d.ts +12 -0
- package/dist/src2/optimizer/block_merge.js +84 -0
- package/dist/src2/optimizer/branch_simplify.d.ts +9 -0
- package/dist/src2/optimizer/branch_simplify.js +28 -0
- package/dist/src2/optimizer/constant_fold.d.ts +10 -0
- package/dist/src2/optimizer/constant_fold.js +85 -0
- package/dist/src2/optimizer/copy_prop.d.ts +9 -0
- package/dist/src2/optimizer/copy_prop.js +113 -0
- package/dist/src2/optimizer/dce.d.ts +8 -0
- package/dist/src2/optimizer/dce.js +155 -0
- package/dist/src2/optimizer/pipeline.d.ts +10 -0
- package/dist/src2/optimizer/pipeline.js +42 -0
- package/dist/tsconfig.tsbuildinfo +1 -0
- package/docs/compiler-pipeline-redesign.md +2243 -0
- package/docs/optimization-ideas.md +1076 -0
- package/editors/vscode/package-lock.json +3 -3
- package/editors/vscode/package.json +1 -1
- package/examples/readme-demo.mcrs +44 -66
- package/jest.config.js +1 -1
- package/package.json +6 -5
- package/scripts/postbuild.js +15 -0
- package/src/__tests__/cli.test.ts +8 -220
- package/src/__tests__/dce.test.ts +11 -56
- package/src/__tests__/diagnostics.test.ts +59 -38
- package/src/__tests__/mc-integration.test.ts +1 -2
- package/src/ast/types.ts +6 -1
- package/src/cli.ts +29 -156
- package/src/compile.ts +6 -162
- package/src/index.ts +14 -178
- package/src/lexer/index.ts +9 -1
- package/src/mc-test/runner.ts +4 -3
- package/src/parser/index.ts +1 -1
- package/src/repl.ts +1 -1
- package/src/runtime/index.ts +1 -1
- package/src2/__tests__/e2e/basic.test.ts +154 -0
- package/src2/__tests__/e2e/macros.test.ts +199 -0
- package/src2/__tests__/e2e/migrate.test.ts +3008 -0
- package/src2/__tests__/hir/desugar.test.ts +263 -0
- package/src2/__tests__/lir/lower.test.ts +619 -0
- package/src2/__tests__/lir/types.test.ts +207 -0
- package/src2/__tests__/lir/verify.test.ts +249 -0
- package/src2/__tests__/mir/arithmetic.test.ts +156 -0
- package/src2/__tests__/mir/control-flow.test.ts +242 -0
- package/src2/__tests__/mir/verify.test.ts +254 -0
- package/src2/__tests__/optimizer/block_merge.test.ts +84 -0
- package/src2/__tests__/optimizer/branch_simplify.test.ts +64 -0
- package/src2/__tests__/optimizer/constant_fold.test.ts +145 -0
- package/src2/__tests__/optimizer/copy_prop.test.ts +99 -0
- package/src2/__tests__/optimizer/dce.test.ts +83 -0
- package/src2/__tests__/optimizer/pipeline.test.ts +116 -0
- package/src2/emit/compile.ts +99 -0
- package/src2/emit/index.ts +222 -0
- package/src2/hir/lower.ts +428 -0
- package/src2/hir/types.ts +216 -0
- package/src2/lir/lower.ts +556 -0
- package/src2/lir/types.ts +109 -0
- package/src2/lir/verify.ts +129 -0
- package/src2/mir/lower.ts +1160 -0
- package/src2/mir/macro.ts +167 -0
- package/src2/mir/types.ts +106 -0
- package/src2/mir/verify.ts +218 -0
- package/src2/optimizer/block_merge.ts +93 -0
- package/src2/optimizer/branch_simplify.ts +27 -0
- package/src2/optimizer/constant_fold.ts +88 -0
- package/src2/optimizer/copy_prop.ts +106 -0
- package/src2/optimizer/dce.ts +133 -0
- package/src2/optimizer/pipeline.ts +44 -0
- package/tsconfig.json +2 -2
- 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 -3860
- package/src/optimizer/commands.ts +0 -534
- package/src/optimizer/dce.ts +0 -679
- package/src/optimizer/passes.ts +0 -250
- package/src/optimizer/structure.ts +0 -450
package/src/index.ts
CHANGED
|
@@ -1,181 +1,30 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* RedScript Compiler
|
|
3
|
-
*
|
|
3
|
+
*
|
|
4
4
|
* Main entry point for programmatic usage.
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
|
-
|
|
8
|
-
export const version = '1.2.11'
|
|
7
|
+
export const version = '2.0.0'
|
|
9
8
|
|
|
10
9
|
import { Lexer } from './lexer'
|
|
11
10
|
import { Parser } from './parser'
|
|
12
|
-
import {
|
|
13
|
-
import { Lowering, setScoreboardObjective } from './lowering'
|
|
14
|
-
import type { Warning } from './lowering'
|
|
15
|
-
import {
|
|
16
|
-
constantFoldingWithStats,
|
|
17
|
-
copyPropagation,
|
|
18
|
-
deadCodeEliminationWithStats,
|
|
19
|
-
} from './optimizer/passes'
|
|
20
|
-
import { eliminateDeadCode } from './optimizer/dce'
|
|
21
|
-
import {
|
|
22
|
-
countMcfunctionCommands,
|
|
23
|
-
generateDatapackWithStats,
|
|
24
|
-
DatapackFile,
|
|
25
|
-
} from './codegen/mcfunction'
|
|
26
|
-
import { preprocessSource, preprocessSourceWithMetadata } from './compile'
|
|
27
|
-
import type { IRModule } from './ir/types'
|
|
28
|
-
import type { Program } from './ast/types'
|
|
29
|
-
import type { DiagnosticError } from './diagnostics'
|
|
30
|
-
import { createEmptyOptimizationStats, type OptimizationStats } from './optimizer/commands'
|
|
11
|
+
import { preprocessSource } from './compile'
|
|
31
12
|
|
|
32
|
-
export
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
typeCheck?: boolean
|
|
36
|
-
filePath?: string
|
|
37
|
-
dce?: boolean
|
|
38
|
-
mangle?: boolean
|
|
39
|
-
/** Scoreboard objective used for all variable slots.
|
|
40
|
-
* Defaults to '__<namespace>' (e.g. '__mathshow') to avoid collisions when
|
|
41
|
-
* multiple RedScript datapacks are loaded simultaneously, without occupying
|
|
42
|
-
* the user's own namespace. Override only if you need a specific name. */
|
|
43
|
-
scoreboardObjective?: string
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
export interface CompileResult {
|
|
47
|
-
files: DatapackFile[]
|
|
48
|
-
advancements: DatapackFile[]
|
|
49
|
-
ast: Program
|
|
50
|
-
ir: IRModule
|
|
51
|
-
typeErrors?: DiagnosticError[]
|
|
52
|
-
warnings?: Warning[]
|
|
53
|
-
stats?: OptimizationStats
|
|
54
|
-
sourceMap?: Record<string, string>
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
/**
|
|
58
|
-
* Compile RedScript source code to a Minecraft datapack.
|
|
59
|
-
*
|
|
60
|
-
* @param source - The RedScript source code
|
|
61
|
-
* @param options - Compilation options
|
|
62
|
-
* @returns Compiled datapack files
|
|
63
|
-
*/
|
|
64
|
-
export function compile(source: string, options: CompileOptions = {}): CompileResult {
|
|
65
|
-
const namespace = options.namespace ?? 'redscript'
|
|
66
|
-
const shouldOptimize = options.optimize ?? true
|
|
67
|
-
const shouldTypeCheck = options.typeCheck ?? true
|
|
68
|
-
const shouldRunDce = options.dce ?? shouldOptimize
|
|
69
|
-
const mangle = options.mangle ?? false
|
|
70
|
-
const filePath = options.filePath
|
|
71
|
-
const preprocessed = preprocessSourceWithMetadata(source, { filePath })
|
|
72
|
-
const preprocessedSource = preprocessed.source
|
|
73
|
-
|
|
74
|
-
// Lexing
|
|
75
|
-
const tokens = new Lexer(preprocessedSource, filePath).tokenize()
|
|
76
|
-
|
|
77
|
-
// Parsing — user source
|
|
78
|
-
const parsedAst = new Parser(tokens, preprocessedSource, filePath).parse(namespace)
|
|
79
|
-
|
|
80
|
-
// Library imports: files that declared `module library;` are parsed independently
|
|
81
|
-
// (fresh Parser per file) so their functions are DCE-eligible but never bleed into user code.
|
|
82
|
-
const allLibrarySources: Array<{ src: string; fp?: string }> = []
|
|
83
|
-
for (const li of preprocessed.libraryImports ?? []) {
|
|
84
|
-
allLibrarySources.push({ src: li.source, fp: li.filePath })
|
|
85
|
-
}
|
|
86
|
-
for (const { src, fp } of allLibrarySources) {
|
|
87
|
-
const libPreprocessed = preprocessSourceWithMetadata(src, fp ? { filePath: fp } : {})
|
|
88
|
-
const libTokens = new Lexer(libPreprocessed.source, fp).tokenize()
|
|
89
|
-
const libAst = new Parser(libTokens, libPreprocessed.source, fp).parse(namespace)
|
|
90
|
-
for (const fn of libAst.declarations) fn.isLibraryFn = true
|
|
91
|
-
parsedAst.declarations.push(...libAst.declarations)
|
|
92
|
-
parsedAst.structs.push(...libAst.structs)
|
|
93
|
-
parsedAst.implBlocks.push(...libAst.implBlocks)
|
|
94
|
-
parsedAst.enums.push(...libAst.enums)
|
|
95
|
-
parsedAst.consts.push(...libAst.consts)
|
|
96
|
-
parsedAst.globals.push(...libAst.globals)
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
const dceResult = shouldRunDce ? eliminateDeadCode(parsedAst, preprocessed.ranges) : { program: parsedAst, warnings: [] }
|
|
100
|
-
const ast = dceResult.program
|
|
101
|
-
|
|
102
|
-
// Type checking (warn mode - collect errors but don't block)
|
|
103
|
-
let typeErrors: DiagnosticError[] | undefined
|
|
104
|
-
if (shouldTypeCheck) {
|
|
105
|
-
const checker = new TypeChecker(preprocessedSource, filePath)
|
|
106
|
-
typeErrors = checker.check(ast)
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
// Configure scoreboard objective for this compilation.
|
|
110
|
-
// Default: use the datapack namespace so each datapack gets its own objective
|
|
111
|
-
// automatically, preventing variable collisions when multiple datapacks coexist.
|
|
112
|
-
const scoreboardObj = options.scoreboardObjective ?? `__${namespace}`
|
|
113
|
-
setScoreboardObjective(scoreboardObj)
|
|
114
|
-
|
|
115
|
-
// Lowering to IR
|
|
116
|
-
const lowering = new Lowering(namespace, preprocessed.ranges)
|
|
117
|
-
const ir = lowering.lower(ast)
|
|
118
|
-
|
|
119
|
-
let optimizedIR: IRModule = ir
|
|
120
|
-
let generated = generateDatapackWithStats(ir, { optimizeCommands: shouldOptimize, mangle, scoreboardObjective: scoreboardObj })
|
|
121
|
-
let optimizationStats: OptimizationStats | undefined
|
|
122
|
-
|
|
123
|
-
if (shouldOptimize) {
|
|
124
|
-
const stats = createEmptyOptimizationStats()
|
|
125
|
-
const copyPropagatedFunctions = []
|
|
126
|
-
const deadCodeEliminatedFunctions = []
|
|
127
|
-
|
|
128
|
-
for (const fn of ir.functions) {
|
|
129
|
-
const folded = constantFoldingWithStats(fn)
|
|
130
|
-
stats.constantFolds += folded.stats.constantFolds ?? 0
|
|
13
|
+
// Re-export v2 compile API
|
|
14
|
+
export { compile, CompileOptions, CompileResult } from '../src2/emit/compile'
|
|
15
|
+
export type { DatapackFile } from '../src2/emit/index'
|
|
131
16
|
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
const copyPropagatedIR: IRModule = { ...ir, functions: copyPropagatedFunctions }
|
|
140
|
-
optimizedIR = { ...ir, functions: deadCodeEliminatedFunctions }
|
|
141
|
-
|
|
142
|
-
const baselineGenerated = generateDatapackWithStats(ir, { optimizeCommands: false, mangle, scoreboardObjective: scoreboardObj })
|
|
143
|
-
const beforeDceGenerated = generateDatapackWithStats(copyPropagatedIR, { optimizeCommands: false, mangle, scoreboardObjective: scoreboardObj })
|
|
144
|
-
const afterDceGenerated = generateDatapackWithStats(optimizedIR, { optimizeCommands: false, mangle, scoreboardObjective: scoreboardObj })
|
|
145
|
-
generated = generateDatapackWithStats(optimizedIR, { optimizeCommands: true, mangle, scoreboardObjective: scoreboardObj })
|
|
146
|
-
|
|
147
|
-
stats.deadCodeRemoved =
|
|
148
|
-
countMcfunctionCommands(beforeDceGenerated.files) - countMcfunctionCommands(afterDceGenerated.files)
|
|
149
|
-
stats.licmHoists = generated.stats.licmHoists
|
|
150
|
-
stats.licmLoopBodies = generated.stats.licmLoopBodies
|
|
151
|
-
stats.cseRedundantReads = generated.stats.cseRedundantReads
|
|
152
|
-
stats.cseArithmetic = generated.stats.cseArithmetic
|
|
153
|
-
stats.setblockMergedCommands = generated.stats.setblockMergedCommands
|
|
154
|
-
stats.setblockFillCommands = generated.stats.setblockFillCommands
|
|
155
|
-
stats.setblockSavedCommands = generated.stats.setblockSavedCommands
|
|
156
|
-
stats.totalCommandsBefore = countMcfunctionCommands(baselineGenerated.files)
|
|
157
|
-
stats.totalCommandsAfter = countMcfunctionCommands(generated.files)
|
|
158
|
-
optimizationStats = stats
|
|
159
|
-
} else {
|
|
160
|
-
optimizedIR = ir
|
|
161
|
-
generated = generateDatapackWithStats(ir, { optimizeCommands: false, mangle, scoreboardObjective: scoreboardObj })
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
return {
|
|
165
|
-
files: [...generated.files, ...generated.advancements],
|
|
166
|
-
advancements: generated.advancements,
|
|
167
|
-
ast,
|
|
168
|
-
ir: optimizedIR,
|
|
169
|
-
typeErrors,
|
|
170
|
-
warnings: [...dceResult.warnings, ...lowering.warnings],
|
|
171
|
-
stats: optimizationStats,
|
|
172
|
-
sourceMap: generated.sourceMap,
|
|
173
|
-
}
|
|
174
|
-
}
|
|
17
|
+
// Re-export utilities
|
|
18
|
+
export { Lexer } from './lexer'
|
|
19
|
+
export { Parser } from './parser'
|
|
20
|
+
export { preprocessSource, preprocessSourceWithMetadata } from './compile'
|
|
21
|
+
export { MCCommandValidator } from './mc-validator'
|
|
22
|
+
export type { Program, FnDecl, Expr, Stmt, Span } from './ast/types'
|
|
23
|
+
export type { DiagnosticError } from './diagnostics'
|
|
175
24
|
|
|
176
25
|
/**
|
|
177
26
|
* Check RedScript source code for errors without generating output.
|
|
178
|
-
*
|
|
27
|
+
*
|
|
179
28
|
* @param source - The RedScript source code
|
|
180
29
|
* @param namespace - Optional namespace
|
|
181
30
|
* @returns null if no errors, or an error object
|
|
@@ -190,16 +39,3 @@ export function check(source: string, namespace = 'redscript', filePath?: string
|
|
|
190
39
|
return err as Error
|
|
191
40
|
}
|
|
192
41
|
}
|
|
193
|
-
|
|
194
|
-
// Re-export types and classes for advanced usage
|
|
195
|
-
export { Lexer } from './lexer'
|
|
196
|
-
export { Parser } from './parser'
|
|
197
|
-
export { TypeChecker } from './typechecker'
|
|
198
|
-
export { Lowering } from './lowering'
|
|
199
|
-
export { optimize } from './optimizer/passes'
|
|
200
|
-
export { generateDatapack } from './codegen/mcfunction'
|
|
201
|
-
export { MCCommandValidator } from './mc-validator'
|
|
202
|
-
export type { DatapackFile } from './codegen/mcfunction'
|
|
203
|
-
export type { IRModule, IRFunction } from './ir/types'
|
|
204
|
-
export type { Program, FnDecl, Expr, Stmt, Span } from './ast/types'
|
|
205
|
-
export type { DiagnosticError } from './diagnostics'
|
package/src/lexer/index.ts
CHANGED
|
@@ -313,9 +313,17 @@ export class Lexer {
|
|
|
313
313
|
return
|
|
314
314
|
}
|
|
315
315
|
|
|
316
|
-
// Local coordinate: ^ or ^5 or ^-3 or ^0.5
|
|
316
|
+
// Local coordinate: ^ or ^5 or ^-3 or ^0.5 or ^varname (macro variable)
|
|
317
317
|
if (char === '^') {
|
|
318
318
|
let value = '^'
|
|
319
|
+
// Check for identifier (variable name for macro substitution, e.g. ^px, ^height)
|
|
320
|
+
if (/[a-zA-Z_]/.test(this.peek())) {
|
|
321
|
+
while (/[a-zA-Z0-9_]/.test(this.peek())) {
|
|
322
|
+
value += this.advance()
|
|
323
|
+
}
|
|
324
|
+
this.addToken('local_coord', value, startLine, startCol)
|
|
325
|
+
return
|
|
326
|
+
}
|
|
319
327
|
// Check for optional sign
|
|
320
328
|
if (this.peek() === '-' || this.peek() === '+') {
|
|
321
329
|
value += this.advance()
|
package/src/mc-test/runner.ts
CHANGED
|
@@ -54,9 +54,10 @@ export async function runMCTests(
|
|
|
54
54
|
// Compile and install datapack
|
|
55
55
|
const outDir = path.join(MC_SERVER_DIR, 'world', 'datapacks', 'redscript-test')
|
|
56
56
|
console.log(`Compiling ${sourceFile}...`)
|
|
57
|
-
const
|
|
58
|
-
|
|
59
|
-
|
|
57
|
+
const ns = path.basename(sourceFile, '.mcrs')
|
|
58
|
+
const result = compile(fs.readFileSync(sourceFile, 'utf-8'), { namespace: ns, filePath: sourceFile })
|
|
59
|
+
if (!result.files) {
|
|
60
|
+
throw new Error('Compilation failed')
|
|
60
61
|
}
|
|
61
62
|
// Write files
|
|
62
63
|
fs.mkdirSync(outDir, { recursive: true })
|
package/src/parser/index.ts
CHANGED
|
@@ -12,7 +12,7 @@ import type {
|
|
|
12
12
|
StructDecl, StructField, ExecuteSubcommand, EnumDecl, EnumVariant, BlockPosExpr, ImplBlock,
|
|
13
13
|
CoordComponent, LambdaParam, EntityTypeName
|
|
14
14
|
} from '../ast/types'
|
|
15
|
-
import type { BinOp, CmpOp } from '../
|
|
15
|
+
import type { BinOp, CmpOp } from '../ast/types'
|
|
16
16
|
import { DiagnosticError } from '../diagnostics'
|
|
17
17
|
|
|
18
18
|
// ---------------------------------------------------------------------------
|
package/src/repl.ts
CHANGED
package/src/runtime/index.ts
CHANGED
|
@@ -1496,7 +1496,7 @@ export class MCRuntime {
|
|
|
1496
1496
|
|
|
1497
1497
|
compileAndLoad(source: string): void {
|
|
1498
1498
|
const result = rsCompile(source, { namespace: this.namespace })
|
|
1499
|
-
if (!result.
|
|
1499
|
+
if (!result.files) {
|
|
1500
1500
|
throw new Error('Compilation failed')
|
|
1501
1501
|
}
|
|
1502
1502
|
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* End-to-end tests for the v2 compiler pipeline.
|
|
3
|
+
*
|
|
4
|
+
* These tests compile RedScript source through the full pipeline
|
|
5
|
+
* (Lexer → Parser → HIR → MIR → optimize → LIR → emit) and verify
|
|
6
|
+
* the generated .mcfunction output contains expected MC commands.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import { compile } from '../../emit/compile'
|
|
10
|
+
|
|
11
|
+
function getFile(files: { path: string; content: string }[], pathSubstr: string): string | undefined {
|
|
12
|
+
const f = files.find(f => f.path.includes(pathSubstr))
|
|
13
|
+
return f?.content
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
describe('e2e: basic compilation', () => {
|
|
17
|
+
test('simple arithmetic function produces scoreboard commands', () => {
|
|
18
|
+
const source = `
|
|
19
|
+
fn add(a: int, b: int): int {
|
|
20
|
+
return a + b;
|
|
21
|
+
}
|
|
22
|
+
`
|
|
23
|
+
const result = compile(source, { namespace: 'test' })
|
|
24
|
+
expect(result.files.length).toBeGreaterThan(0)
|
|
25
|
+
|
|
26
|
+
const addFn = getFile(result.files, 'add.mcfunction')
|
|
27
|
+
expect(addFn).toBeDefined()
|
|
28
|
+
expect(addFn).toContain('scoreboard players operation')
|
|
29
|
+
expect(addFn).toContain('__test')
|
|
30
|
+
})
|
|
31
|
+
|
|
32
|
+
test('pack.mcmeta is generated with pack_format 26', () => {
|
|
33
|
+
const source = `fn noop(): void {}`
|
|
34
|
+
const result = compile(source, { namespace: 'demo' })
|
|
35
|
+
const meta = getFile(result.files, 'pack.mcmeta')
|
|
36
|
+
expect(meta).toBeDefined()
|
|
37
|
+
const parsed = JSON.parse(meta!)
|
|
38
|
+
expect(parsed.pack.pack_format).toBe(26)
|
|
39
|
+
})
|
|
40
|
+
|
|
41
|
+
test('load.mcfunction creates scoreboard objective', () => {
|
|
42
|
+
const source = `fn noop(): void {}`
|
|
43
|
+
const result = compile(source, { namespace: 'mypack' })
|
|
44
|
+
const load = getFile(result.files, 'load.mcfunction')
|
|
45
|
+
expect(load).toBeDefined()
|
|
46
|
+
expect(load).toContain('scoreboard objectives add __mypack dummy')
|
|
47
|
+
})
|
|
48
|
+
|
|
49
|
+
test('@tick function appears in tick.json', () => {
|
|
50
|
+
const source = `
|
|
51
|
+
@tick fn game_tick(): void {
|
|
52
|
+
let x: int = 1;
|
|
53
|
+
}
|
|
54
|
+
`
|
|
55
|
+
const result = compile(source, { namespace: 'ticktest' })
|
|
56
|
+
const tickJson = getFile(result.files, 'tick.json')
|
|
57
|
+
expect(tickJson).toBeDefined()
|
|
58
|
+
const parsed = JSON.parse(tickJson!)
|
|
59
|
+
expect(parsed.values).toContain('ticktest:game_tick')
|
|
60
|
+
})
|
|
61
|
+
|
|
62
|
+
test('@load function appears in load.json', () => {
|
|
63
|
+
const source = `
|
|
64
|
+
@load fn setup(): void {
|
|
65
|
+
let x: int = 42;
|
|
66
|
+
}
|
|
67
|
+
`
|
|
68
|
+
const result = compile(source, { namespace: 'loadtest' })
|
|
69
|
+
const loadJson = getFile(result.files, 'load.json')
|
|
70
|
+
expect(loadJson).toBeDefined()
|
|
71
|
+
const parsed = JSON.parse(loadJson!)
|
|
72
|
+
expect(parsed.values).toContain('loadtest:setup')
|
|
73
|
+
// load.json should also reference the objective-init load function
|
|
74
|
+
expect(parsed.values).toContain('loadtest:load')
|
|
75
|
+
})
|
|
76
|
+
|
|
77
|
+
test('if/else produces conditional call pattern', () => {
|
|
78
|
+
const source = `
|
|
79
|
+
fn check(x: int): int {
|
|
80
|
+
if (x > 0) {
|
|
81
|
+
return 1;
|
|
82
|
+
} else {
|
|
83
|
+
return 0;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
`
|
|
87
|
+
const result = compile(source, { namespace: 'cond' })
|
|
88
|
+
|
|
89
|
+
// The main function should contain call_if_matches / call_unless_matches
|
|
90
|
+
const checkFn = getFile(result.files, 'check.mcfunction')
|
|
91
|
+
expect(checkFn).toBeDefined()
|
|
92
|
+
expect(checkFn).toContain('execute if score')
|
|
93
|
+
expect(checkFn).toContain('matches')
|
|
94
|
+
expect(checkFn).toContain('run function')
|
|
95
|
+
})
|
|
96
|
+
|
|
97
|
+
test('while loop produces loop structure with recursive call', () => {
|
|
98
|
+
const source = `
|
|
99
|
+
fn count(): void {
|
|
100
|
+
let i: int = 0;
|
|
101
|
+
while (i < 10) {
|
|
102
|
+
i = i + 1;
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
`
|
|
106
|
+
const result = compile(source, { namespace: 'loop' })
|
|
107
|
+
|
|
108
|
+
// There should be a loop body function that calls itself (or a header)
|
|
109
|
+
const fnFiles = result.files.filter(f => f.path.endsWith('.mcfunction'))
|
|
110
|
+
expect(fnFiles.length).toBeGreaterThan(1) // main + at least one extracted block
|
|
111
|
+
|
|
112
|
+
// At least one file should have a conditional call pattern for the loop
|
|
113
|
+
const allContent = fnFiles.map(f => f.content).join('\n')
|
|
114
|
+
expect(allContent).toContain('execute if score')
|
|
115
|
+
expect(allContent).toContain('run function')
|
|
116
|
+
})
|
|
117
|
+
|
|
118
|
+
test('function names are lowercased in output paths', () => {
|
|
119
|
+
const source = `fn MyFunc(): void {}`
|
|
120
|
+
const result = compile(source, { namespace: 'ns' })
|
|
121
|
+
const fn = result.files.find(f => f.path.includes('myfunc.mcfunction'))
|
|
122
|
+
expect(fn).toBeDefined()
|
|
123
|
+
})
|
|
124
|
+
|
|
125
|
+
test('constant assignment produces score_set', () => {
|
|
126
|
+
const source = `
|
|
127
|
+
fn init(): int {
|
|
128
|
+
let x: int = 42;
|
|
129
|
+
return x;
|
|
130
|
+
}
|
|
131
|
+
`
|
|
132
|
+
const result = compile(source, { namespace: 'cst' })
|
|
133
|
+
const fn = getFile(result.files, 'init.mcfunction')
|
|
134
|
+
expect(fn).toBeDefined()
|
|
135
|
+
expect(fn).toContain('scoreboard players set')
|
|
136
|
+
expect(fn).toContain('42')
|
|
137
|
+
})
|
|
138
|
+
|
|
139
|
+
test('load.json always includes namespace:load', () => {
|
|
140
|
+
const source = `fn noop(): void {}`
|
|
141
|
+
const result = compile(source, { namespace: 'abc' })
|
|
142
|
+
const loadJson = getFile(result.files, 'load.json')
|
|
143
|
+
expect(loadJson).toBeDefined()
|
|
144
|
+
const parsed = JSON.parse(loadJson!)
|
|
145
|
+
expect(parsed.values).toContain('abc:load')
|
|
146
|
+
})
|
|
147
|
+
|
|
148
|
+
test('no tick.json when no @tick functions', () => {
|
|
149
|
+
const source = `fn noop(): void {}`
|
|
150
|
+
const result = compile(source, { namespace: 'notick' })
|
|
151
|
+
const tickJson = getFile(result.files, 'tick.json')
|
|
152
|
+
expect(tickJson).toBeUndefined()
|
|
153
|
+
})
|
|
154
|
+
})
|
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* End-to-end tests for MC 1.20.2+ macro function support in the v2 pipeline.
|
|
3
|
+
*
|
|
4
|
+
* When a function uses runtime parameters in positions that require literal
|
|
5
|
+
* values in MC commands (coordinates, entity types, etc.), the compiler should
|
|
6
|
+
* automatically compile it as a macro function using $-prefixed syntax and
|
|
7
|
+
* call it via `function ns:fn with storage rs:macro_args`.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { compile } from '../../emit/compile'
|
|
11
|
+
|
|
12
|
+
function getFile(files: { path: string; content: string }[], pathSubstr: string): string | undefined {
|
|
13
|
+
const f = files.find(f => f.path.includes(pathSubstr))
|
|
14
|
+
return f?.content
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
// ---------------------------------------------------------------------------
|
|
18
|
+
// Macro function detection
|
|
19
|
+
// ---------------------------------------------------------------------------
|
|
20
|
+
|
|
21
|
+
describe('e2e: macro function detection', () => {
|
|
22
|
+
test('function with int params in summon coords emits $-prefixed command', () => {
|
|
23
|
+
const source = `
|
|
24
|
+
fn spawn_zombie(x: int, y: int, z: int) {
|
|
25
|
+
summon("minecraft:zombie", x, y, z);
|
|
26
|
+
}
|
|
27
|
+
`
|
|
28
|
+
const result = compile(source, { namespace: 'test' })
|
|
29
|
+
const fn = getFile(result.files, 'spawn_zombie.mcfunction')
|
|
30
|
+
expect(fn).toBeDefined()
|
|
31
|
+
// The function body should have a $summon macro line
|
|
32
|
+
expect(fn).toContain('$summon minecraft:zombie $(x) $(y) $(z)')
|
|
33
|
+
})
|
|
34
|
+
|
|
35
|
+
test('function with all constant args does NOT produce macro line', () => {
|
|
36
|
+
const source = `
|
|
37
|
+
fn spawn_fixed() {
|
|
38
|
+
summon("minecraft:zombie", 100, 64, 200);
|
|
39
|
+
}
|
|
40
|
+
`
|
|
41
|
+
const result = compile(source, { namespace: 'test' })
|
|
42
|
+
const fn = getFile(result.files, 'spawn_fixed.mcfunction')
|
|
43
|
+
expect(fn).toBeDefined()
|
|
44
|
+
expect(fn).toContain('summon minecraft:zombie 100 64 200')
|
|
45
|
+
// No $ prefix
|
|
46
|
+
expect(fn).not.toMatch(/^\$summon/m)
|
|
47
|
+
})
|
|
48
|
+
|
|
49
|
+
test('function with int params in particle coords emits $-prefixed command', () => {
|
|
50
|
+
const source = `
|
|
51
|
+
fn show_particle(x: int, y: int, z: int) {
|
|
52
|
+
particle("minecraft:flame", x, y, z);
|
|
53
|
+
}
|
|
54
|
+
`
|
|
55
|
+
const result = compile(source, { namespace: 'test' })
|
|
56
|
+
const fn = getFile(result.files, 'show_particle.mcfunction')
|
|
57
|
+
expect(fn).toBeDefined()
|
|
58
|
+
expect(fn).toContain('$particle minecraft:flame $(x) $(y) $(z)')
|
|
59
|
+
})
|
|
60
|
+
|
|
61
|
+
test('function with int params in setblock coords emits $-prefixed command', () => {
|
|
62
|
+
const source = `
|
|
63
|
+
fn place_block(x: int, y: int, z: int) {
|
|
64
|
+
setblock(x, y, z, "minecraft:stone");
|
|
65
|
+
}
|
|
66
|
+
`
|
|
67
|
+
const result = compile(source, { namespace: 'test' })
|
|
68
|
+
const fn = getFile(result.files, 'place_block.mcfunction')
|
|
69
|
+
expect(fn).toBeDefined()
|
|
70
|
+
expect(fn).toContain('$setblock $(x) $(y) $(z) minecraft:stone')
|
|
71
|
+
})
|
|
72
|
+
|
|
73
|
+
test('mixed literal and variable args: only variable args get $()', () => {
|
|
74
|
+
const source = `
|
|
75
|
+
fn teleport_y(y: int) {
|
|
76
|
+
summon("minecraft:zombie", 100, y, 200);
|
|
77
|
+
}
|
|
78
|
+
`
|
|
79
|
+
const result = compile(source, { namespace: 'test' })
|
|
80
|
+
const fn = getFile(result.files, 'teleport_y.mcfunction')
|
|
81
|
+
expect(fn).toBeDefined()
|
|
82
|
+
expect(fn).toContain('$summon minecraft:zombie 100 $(y) 200')
|
|
83
|
+
})
|
|
84
|
+
})
|
|
85
|
+
|
|
86
|
+
// ---------------------------------------------------------------------------
|
|
87
|
+
// Macro call site generation
|
|
88
|
+
// ---------------------------------------------------------------------------
|
|
89
|
+
|
|
90
|
+
describe('e2e: macro call site generation', () => {
|
|
91
|
+
test('call site emits store_score_to_nbt + function with storage', () => {
|
|
92
|
+
const source = `
|
|
93
|
+
fn spawn_zombie(x: int, y: int, z: int) {
|
|
94
|
+
summon("minecraft:zombie", x, y, z);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
fn caller(px: int, pz: int) {
|
|
98
|
+
spawn_zombie(px, 64, pz);
|
|
99
|
+
}
|
|
100
|
+
`
|
|
101
|
+
const result = compile(source, { namespace: 'test' })
|
|
102
|
+
const callerFn = getFile(result.files, 'caller.mcfunction')
|
|
103
|
+
expect(callerFn).toBeDefined()
|
|
104
|
+
|
|
105
|
+
// Should have 'function test:spawn_zombie with storage rs:macro_args'
|
|
106
|
+
expect(callerFn).toContain('with storage rs:macro_args')
|
|
107
|
+
expect(callerFn).toContain('spawn_zombie')
|
|
108
|
+
})
|
|
109
|
+
|
|
110
|
+
test('call site stores args to rs:macro_args NBT', () => {
|
|
111
|
+
const source = `
|
|
112
|
+
fn spawn_zombie(x: int, y: int, z: int) {
|
|
113
|
+
summon("minecraft:zombie", x, y, z);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
fn caller(my_x: int) {
|
|
117
|
+
spawn_zombie(my_x, 64, 0);
|
|
118
|
+
}
|
|
119
|
+
`
|
|
120
|
+
const result = compile(source, { namespace: 'test' })
|
|
121
|
+
const callerFn = getFile(result.files, 'caller.mcfunction')
|
|
122
|
+
expect(callerFn).toBeDefined()
|
|
123
|
+
|
|
124
|
+
// Should have NBT storage setup for macro args
|
|
125
|
+
expect(callerFn).toContain('rs:macro_args')
|
|
126
|
+
expect(callerFn).toContain('with storage')
|
|
127
|
+
})
|
|
128
|
+
})
|
|
129
|
+
|
|
130
|
+
// ---------------------------------------------------------------------------
|
|
131
|
+
// Float macro params (local coords)
|
|
132
|
+
// ---------------------------------------------------------------------------
|
|
133
|
+
|
|
134
|
+
describe('e2e: float macro params with local coords', () => {
|
|
135
|
+
test('float params in ^coord positions produce macro function', () => {
|
|
136
|
+
const source = `
|
|
137
|
+
fn draw_pt(px: float, py: float) {
|
|
138
|
+
particle("minecraft:end_rod", ^px, ^py, ^5, 0.02, 0.02, 0.02, 0.0, 10);
|
|
139
|
+
}
|
|
140
|
+
`
|
|
141
|
+
const result = compile(source, { namespace: 'test' })
|
|
142
|
+
const fn = getFile(result.files, 'draw_pt.mcfunction')
|
|
143
|
+
expect(fn).toBeDefined()
|
|
144
|
+
// Should have $particle with ^$(px) and ^$(py)
|
|
145
|
+
expect(fn).toContain('$particle minecraft:end_rod ^$(px) ^$(py) ^5')
|
|
146
|
+
})
|
|
147
|
+
|
|
148
|
+
test('float macro call site uses double 0.01 scale for NBT storage', () => {
|
|
149
|
+
const source = `
|
|
150
|
+
fn draw_pt(px: float, py: float) {
|
|
151
|
+
particle("minecraft:end_rod", ^px, ^py, ^5, 0.02, 0.02, 0.02, 0.0, 10);
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
fn caller() {
|
|
155
|
+
draw_pt(100, 200);
|
|
156
|
+
}
|
|
157
|
+
`
|
|
158
|
+
const result = compile(source, { namespace: 'test' })
|
|
159
|
+
const callerFn = getFile(result.files, 'caller.mcfunction')
|
|
160
|
+
expect(callerFn).toBeDefined()
|
|
161
|
+
|
|
162
|
+
// Should store to NBT with double type and 0.01 scale
|
|
163
|
+
expect(callerFn).toContain('rs:macro_args')
|
|
164
|
+
expect(callerFn).toContain('double 0.01')
|
|
165
|
+
expect(callerFn).toContain('with storage rs:macro_args')
|
|
166
|
+
})
|
|
167
|
+
})
|
|
168
|
+
|
|
169
|
+
// ---------------------------------------------------------------------------
|
|
170
|
+
// Non-macro functions still work
|
|
171
|
+
// ---------------------------------------------------------------------------
|
|
172
|
+
|
|
173
|
+
describe('e2e: non-macro functions', () => {
|
|
174
|
+
test('say builtin emits normal (non-macro) command', () => {
|
|
175
|
+
const source = `
|
|
176
|
+
fn greet() {
|
|
177
|
+
say("hello world");
|
|
178
|
+
}
|
|
179
|
+
`
|
|
180
|
+
const result = compile(source, { namespace: 'test' })
|
|
181
|
+
const fn = getFile(result.files, 'greet.mcfunction')
|
|
182
|
+
expect(fn).toBeDefined()
|
|
183
|
+
expect(fn).toContain('say hello world')
|
|
184
|
+
// No $ prefix
|
|
185
|
+
expect(fn).not.toMatch(/^\$/m)
|
|
186
|
+
})
|
|
187
|
+
|
|
188
|
+
test('kill builtin emits normal command', () => {
|
|
189
|
+
const source = `
|
|
190
|
+
fn cleanup() {
|
|
191
|
+
kill(@e[tag=temp]);
|
|
192
|
+
}
|
|
193
|
+
`
|
|
194
|
+
const result = compile(source, { namespace: 'test' })
|
|
195
|
+
const fn = getFile(result.files, 'cleanup.mcfunction')
|
|
196
|
+
expect(fn).toBeDefined()
|
|
197
|
+
expect(fn).toContain('kill @e[tag=temp]')
|
|
198
|
+
})
|
|
199
|
+
})
|