redscript-mc 3.0.1 → 3.0.2
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/workflows/ci.yml +1 -0
- package/README.md +119 -313
- package/README.zh.md +118 -314
- package/ROADMAP.md +5 -5
- package/dist/data/impl_test/function/counter/get.mcfunction +5 -0
- package/dist/data/impl_test/function/counter/inc.mcfunction +7 -0
- package/dist/data/impl_test/function/counter/new.mcfunction +4 -0
- package/dist/data/impl_test/function/load.mcfunction +1 -0
- package/dist/data/impl_test/function/test_impl.mcfunction +10 -0
- package/dist/data/minecraft/tags/function/load.json +5 -0
- package/dist/data/playground/function/load.mcfunction +1 -0
- package/dist/data/playground/function/start.mcfunction +4 -0
- package/dist/data/playground/function/start__say_macro_t1.mcfunction +1 -0
- package/dist/data/playground/function/stop.mcfunction +5 -0
- package/dist/data/playground/function/stop__say_macro_t0.mcfunction +1 -0
- package/dist/data/stdlib_queue8_test/function/__queue_append_apply.mcfunction +4 -0
- package/dist/data/stdlib_queue8_test/function/__queue_peek_apply.mcfunction +4 -0
- package/dist/data/stdlib_queue8_test/function/__queue_size_raw_apply.mcfunction +4 -0
- package/dist/data/stdlib_queue8_test/function/load.mcfunction +1 -0
- package/dist/data/stdlib_queue8_test/function/queue_clear.mcfunction +6 -0
- package/dist/data/stdlib_queue8_test/function/queue_empty__merge_1.mcfunction +5 -0
- package/dist/data/stdlib_queue8_test/function/queue_empty__then_0.mcfunction +5 -0
- package/dist/data/stdlib_queue8_test/function/queue_peek__merge_1.mcfunction +13 -0
- package/dist/data/stdlib_queue8_test/function/queue_peek__then_0.mcfunction +5 -0
- package/dist/data/stdlib_queue8_test/function/queue_pop__merge_1.mcfunction +15 -0
- package/dist/data/stdlib_queue8_test/function/queue_pop__then_0.mcfunction +5 -0
- package/dist/data/stdlib_queue8_test/function/queue_push__const_11.mcfunction +6 -0
- package/dist/data/stdlib_queue8_test/function/queue_push__const_22.mcfunction +6 -0
- package/dist/data/stdlib_queue8_test/function/queue_size.mcfunction +13 -0
- package/dist/data/stdlib_queue8_test/function/test_queue_push_and_size.mcfunction +13 -0
- package/dist/data/test/function/load.mcfunction +1 -0
- package/dist/data/test/function/say_at.mcfunction +6 -0
- package/dist/data/test/function/test.mcfunction +4 -0
- package/dist/pack.mcmeta +6 -0
- package/dist/package.json +1 -1
- package/dist/src/__tests__/formatter-extra.test.d.ts +7 -0
- package/dist/src/__tests__/formatter-extra.test.js +123 -0
- package/dist/src/__tests__/global-vars.test.d.ts +13 -0
- package/dist/src/__tests__/global-vars.test.js +156 -0
- package/dist/src/__tests__/lint/new-rules.test.d.ts +9 -0
- package/dist/src/__tests__/lint/new-rules.test.js +402 -0
- package/dist/src/__tests__/lsp-rename.test.d.ts +8 -0
- package/dist/src/__tests__/lsp-rename.test.js +157 -0
- package/dist/src/__tests__/mc-integration/say-fstring.test.d.ts +11 -0
- package/dist/src/__tests__/mc-integration/say-fstring.test.js +220 -0
- package/dist/src/__tests__/mc-integration/stdlib-coverage-2.test.js +1 -1
- package/dist/src/__tests__/mc-integration/stdlib-coverage-3.test.js +1 -1
- package/dist/src/__tests__/mc-integration/stdlib-coverage-4.test.js +1 -1
- package/dist/src/__tests__/mc-integration/stdlib-coverage-5.test.js +1 -1
- package/dist/src/__tests__/mc-integration/stdlib-coverage-6.test.js +1 -1
- package/dist/src/__tests__/mc-integration/stdlib-coverage-7.test.js +1 -1
- package/dist/src/__tests__/mc-integration/stdlib-coverage-8.test.js +1 -1
- package/dist/src/__tests__/mc-syntax.test.js +4 -1
- package/dist/src/__tests__/monomorphize-coverage.test.d.ts +9 -0
- package/dist/src/__tests__/monomorphize-coverage.test.js +204 -0
- package/dist/src/__tests__/optimizer-cse.test.d.ts +7 -0
- package/dist/src/__tests__/optimizer-cse.test.js +226 -0
- package/dist/src/__tests__/parser.test.js +4 -13
- package/dist/src/__tests__/repl-server-extra.test.js +6 -7
- package/dist/src/__tests__/repl-server.test.js +5 -7
- package/dist/src/__tests__/stdlib/queue.test.js +6 -6
- package/dist/src/cli.js +0 -0
- package/dist/src/lexer/index.js +2 -1
- package/dist/src/lint/index.d.ts +12 -5
- package/dist/src/lint/index.js +730 -5
- package/dist/src/lsp/main.js +0 -0
- package/dist/src/mc-test/client.d.ts +21 -0
- package/dist/src/mc-test/client.js +34 -0
- package/dist/src/mir/lower.js +108 -6
- package/dist/src/optimizer/interprocedural.js +37 -2
- package/dist/src/parser/decl-parser.d.ts +19 -0
- package/dist/src/parser/decl-parser.js +323 -0
- package/dist/src/parser/expr-parser.d.ts +46 -0
- package/dist/src/parser/expr-parser.js +759 -0
- package/dist/src/parser/index.d.ts +8 -129
- package/dist/src/parser/index.js +13 -2262
- package/dist/src/parser/stmt-parser.d.ts +28 -0
- package/dist/src/parser/stmt-parser.js +577 -0
- package/dist/src/parser/type-parser.d.ts +20 -0
- package/dist/src/parser/type-parser.js +257 -0
- package/dist/src/parser/utils.d.ts +34 -0
- package/dist/src/parser/utils.js +141 -0
- package/docs/dev/README-mc-integration-tests.md +141 -0
- package/docs/lint-rules.md +162 -0
- package/docs/stdlib/bigint.md +2 -0
- package/editors/vscode/README.md +63 -41
- package/editors/vscode/out/extension.js +1881 -1776
- package/editors/vscode/out/lsp-server.js +4257 -3651
- package/editors/vscode/package-lock.json +3 -3
- package/editors/vscode/package.json +1 -1
- package/examples/loops-demo.mcrs +87 -0
- package/package.json +1 -1
- package/redscript-docs/docs/en/stdlib/advanced.md +629 -0
- package/redscript-docs/docs/en/stdlib/bigint.md +316 -0
- package/redscript-docs/docs/en/stdlib/bits.md +292 -0
- package/redscript-docs/docs/en/stdlib/bossbar.md +177 -0
- package/redscript-docs/docs/en/stdlib/calculus.md +289 -0
- package/redscript-docs/docs/en/stdlib/color.md +353 -0
- package/redscript-docs/docs/en/stdlib/combat.md +88 -0
- package/redscript-docs/docs/en/stdlib/cooldown.md +82 -0
- package/redscript-docs/docs/en/stdlib/dialog.md +155 -0
- package/redscript-docs/docs/en/stdlib/easing.md +558 -0
- package/redscript-docs/docs/en/stdlib/ecs.md +475 -0
- package/redscript-docs/docs/en/stdlib/effects.md +324 -0
- package/redscript-docs/docs/en/stdlib/events.md +3 -0
- package/redscript-docs/docs/en/stdlib/expr.md +45 -0
- package/redscript-docs/docs/en/stdlib/fft.md +141 -0
- package/redscript-docs/docs/en/stdlib/geometry.md +430 -0
- package/redscript-docs/docs/en/stdlib/graph.md +259 -0
- package/redscript-docs/docs/en/stdlib/heap.md +185 -0
- package/redscript-docs/docs/en/stdlib/interactions.md +179 -0
- package/redscript-docs/docs/en/stdlib/inventory.md +97 -0
- package/redscript-docs/docs/en/stdlib/linalg.md +557 -0
- package/redscript-docs/docs/en/stdlib/list.md +559 -0
- package/redscript-docs/docs/en/stdlib/map.md +140 -0
- package/redscript-docs/docs/en/stdlib/math.md +193 -0
- package/redscript-docs/docs/en/stdlib/math_hp.md +149 -0
- package/redscript-docs/docs/en/stdlib/matrix.md +403 -0
- package/redscript-docs/docs/en/stdlib/mobs.md +965 -0
- package/redscript-docs/docs/en/stdlib/noise.md +244 -0
- package/redscript-docs/docs/en/stdlib/ode.md +253 -0
- package/redscript-docs/docs/en/stdlib/parabola.md +342 -0
- package/redscript-docs/docs/en/stdlib/particles.md +311 -0
- package/redscript-docs/docs/en/stdlib/pathfind.md +255 -0
- package/redscript-docs/docs/en/stdlib/physics.md +493 -0
- package/redscript-docs/docs/en/stdlib/player.md +78 -0
- package/redscript-docs/docs/en/stdlib/quaternion.md +673 -0
- package/redscript-docs/docs/en/stdlib/queue.md +134 -0
- package/redscript-docs/docs/en/stdlib/random.md +223 -0
- package/redscript-docs/docs/en/stdlib/result.md +143 -0
- package/redscript-docs/docs/en/stdlib/scheduler.md +183 -0
- package/redscript-docs/docs/en/stdlib/set_int.md +190 -0
- package/redscript-docs/docs/en/stdlib/sets.md +101 -0
- package/redscript-docs/docs/en/stdlib/signal.md +400 -0
- package/redscript-docs/docs/en/stdlib/sort.md +104 -0
- package/redscript-docs/docs/en/stdlib/spawn.md +147 -0
- package/redscript-docs/docs/en/stdlib/state.md +142 -0
- package/redscript-docs/docs/en/stdlib/strings.md +154 -0
- package/redscript-docs/docs/en/stdlib/tags.md +3451 -0
- package/redscript-docs/docs/en/stdlib/teams.md +153 -0
- package/redscript-docs/docs/en/stdlib/timer.md +246 -0
- package/redscript-docs/docs/en/stdlib/vec.md +158 -0
- package/redscript-docs/docs/en/stdlib/world.md +298 -0
- package/redscript-docs/docs/zh/stdlib/advanced.md +615 -0
- package/redscript-docs/docs/zh/stdlib/bigint.md +316 -0
- package/redscript-docs/docs/zh/stdlib/bits.md +292 -0
- package/redscript-docs/docs/zh/stdlib/bossbar.md +170 -0
- package/redscript-docs/docs/zh/stdlib/calculus.md +287 -0
- package/redscript-docs/docs/zh/stdlib/color.md +353 -0
- package/redscript-docs/docs/zh/stdlib/combat.md +88 -0
- package/redscript-docs/docs/zh/stdlib/cooldown.md +84 -0
- package/redscript-docs/docs/zh/stdlib/dialog.md +152 -0
- package/redscript-docs/docs/zh/stdlib/easing.md +558 -0
- package/redscript-docs/docs/zh/stdlib/ecs.md +472 -0
- package/redscript-docs/docs/zh/stdlib/effects.md +324 -0
- package/redscript-docs/docs/zh/stdlib/events.md +3 -0
- package/redscript-docs/docs/zh/stdlib/expr.md +37 -0
- package/redscript-docs/docs/zh/stdlib/fft.md +128 -0
- package/redscript-docs/docs/zh/stdlib/geometry.md +430 -0
- package/redscript-docs/docs/zh/stdlib/graph.md +259 -0
- package/redscript-docs/docs/zh/stdlib/heap.md +185 -0
- package/redscript-docs/docs/zh/stdlib/interactions.md +160 -0
- package/redscript-docs/docs/zh/stdlib/inventory.md +94 -0
- package/redscript-docs/docs/zh/stdlib/linalg.md +543 -0
- package/redscript-docs/docs/zh/stdlib/list.md +561 -0
- package/redscript-docs/docs/zh/stdlib/map.md +132 -0
- package/redscript-docs/docs/zh/stdlib/math.md +193 -0
- package/redscript-docs/docs/zh/stdlib/math_hp.md +143 -0
- package/redscript-docs/docs/zh/stdlib/matrix.md +396 -0
- package/redscript-docs/docs/zh/stdlib/mobs.md +965 -0
- package/redscript-docs/docs/zh/stdlib/noise.md +244 -0
- package/redscript-docs/docs/zh/stdlib/ode.md +243 -0
- package/redscript-docs/docs/zh/stdlib/parabola.md +337 -0
- package/redscript-docs/docs/zh/stdlib/particles.md +307 -0
- package/redscript-docs/docs/zh/stdlib/pathfind.md +255 -0
- package/redscript-docs/docs/zh/stdlib/physics.md +493 -0
- package/redscript-docs/docs/zh/stdlib/player.md +78 -0
- package/redscript-docs/docs/zh/stdlib/quaternion.md +669 -0
- package/redscript-docs/docs/zh/stdlib/queue.md +124 -0
- package/redscript-docs/docs/zh/stdlib/random.md +222 -0
- package/redscript-docs/docs/zh/stdlib/result.md +147 -0
- package/redscript-docs/docs/zh/stdlib/scheduler.md +173 -0
- package/redscript-docs/docs/zh/stdlib/set_int.md +180 -0
- package/redscript-docs/docs/zh/stdlib/sets.md +107 -0
- package/redscript-docs/docs/zh/stdlib/signal.md +373 -0
- package/redscript-docs/docs/zh/stdlib/sort.md +104 -0
- package/redscript-docs/docs/zh/stdlib/spawn.md +142 -0
- package/redscript-docs/docs/zh/stdlib/state.md +134 -0
- package/redscript-docs/docs/zh/stdlib/strings.md +107 -0
- package/redscript-docs/docs/zh/stdlib/tags.md +3451 -0
- package/redscript-docs/docs/zh/stdlib/teams.md +150 -0
- package/redscript-docs/docs/zh/stdlib/timer.md +254 -0
- package/redscript-docs/docs/zh/stdlib/vec.md +158 -0
- package/redscript-docs/docs/zh/stdlib/world.md +289 -0
- package/src/__tests__/formatter-extra.test.ts +139 -0
- package/src/__tests__/global-vars.test.ts +171 -0
- package/src/__tests__/lint/new-rules.test.ts +437 -0
- package/src/__tests__/lsp-rename.test.ts +171 -0
- package/src/__tests__/mc-integration/say-fstring.test.ts +211 -0
- package/src/__tests__/mc-integration/stdlib-coverage-2.test.ts +1 -1
- package/src/__tests__/mc-integration/stdlib-coverage-3.test.ts +1 -1
- package/src/__tests__/mc-integration/stdlib-coverage-4.test.ts +1 -1
- package/src/__tests__/mc-integration/stdlib-coverage-5.test.ts +1 -1
- package/src/__tests__/mc-integration/stdlib-coverage-6.test.ts +1 -1
- package/src/__tests__/mc-integration/stdlib-coverage-7.test.ts +1 -1
- package/src/__tests__/mc-integration/stdlib-coverage-8.test.ts +1 -1
- package/src/__tests__/mc-syntax.test.ts +3 -0
- package/src/__tests__/monomorphize-coverage.test.ts +220 -0
- package/src/__tests__/optimizer-cse.test.ts +250 -0
- package/src/__tests__/parser.test.ts +4 -13
- package/src/__tests__/repl-server-extra.test.ts +6 -6
- package/src/__tests__/repl-server.test.ts +5 -6
- package/src/__tests__/stdlib/queue.test.ts +6 -6
- package/src/lexer/index.ts +2 -1
- package/src/lint/index.ts +713 -5
- package/src/mc-test/client.ts +40 -0
- package/src/mir/lower.ts +111 -2
- package/src/optimizer/interprocedural.ts +40 -2
- package/src/parser/decl-parser.ts +349 -0
- package/src/parser/expr-parser.ts +838 -0
- package/src/parser/index.ts +17 -2558
- package/src/parser/stmt-parser.ts +585 -0
- package/src/parser/type-parser.ts +276 -0
- package/src/parser/utils.ts +173 -0
- package/src/stdlib/queue.mcrs +19 -6
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* RedScript MC Integration Tests — say() with f-string
|
|
3
|
+
*
|
|
4
|
+
* Verifies that say(f"...{var}...") correctly compiles to a MC macro function
|
|
5
|
+
* ($say template with $(var) placeholders) and that variable interpolation
|
|
6
|
+
* works correctly at runtime via `function <helper> with storage rs:macro_args`.
|
|
7
|
+
*
|
|
8
|
+
* Run: npx jest say-fstring --forceExit
|
|
9
|
+
* With server: MC_SERVER_DIR=~/mc-test-server MC_PORT=25561 npx jest say-fstring --forceExit
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import * as fs from 'fs'
|
|
13
|
+
import * as path from 'path'
|
|
14
|
+
import { compile } from '../../compile'
|
|
15
|
+
import { MCTestClient } from '../../mc-test/client'
|
|
16
|
+
|
|
17
|
+
const MC_HOST = process.env.MC_HOST ?? 'localhost'
|
|
18
|
+
const MC_PORT = parseInt(process.env.MC_PORT ?? '25561')
|
|
19
|
+
const MC_SERVER_DIR = process.env.MC_SERVER_DIR ?? path.join(process.env.HOME!, 'mc-test-server')
|
|
20
|
+
const DATAPACK_DIR = path.join(MC_SERVER_DIR, 'world', 'datapacks', 'redscript-test')
|
|
21
|
+
|
|
22
|
+
const NS = 'rs_say_fstr'
|
|
23
|
+
|
|
24
|
+
let serverOnline = false
|
|
25
|
+
let mc: MCTestClient
|
|
26
|
+
|
|
27
|
+
function writeFixture(source: string, namespace: string): void {
|
|
28
|
+
fs.mkdirSync(DATAPACK_DIR, { recursive: true })
|
|
29
|
+
if (!fs.existsSync(path.join(DATAPACK_DIR, 'pack.mcmeta'))) {
|
|
30
|
+
fs.writeFileSync(
|
|
31
|
+
path.join(DATAPACK_DIR, 'pack.mcmeta'),
|
|
32
|
+
JSON.stringify({ pack: { pack_format: 48, description: 'RedScript integration tests' } })
|
|
33
|
+
)
|
|
34
|
+
}
|
|
35
|
+
const result = compile(source, { namespace })
|
|
36
|
+
for (const file of result.files ?? []) {
|
|
37
|
+
if (file.path === 'pack.mcmeta') continue
|
|
38
|
+
const filePath = path.join(DATAPACK_DIR, file.path)
|
|
39
|
+
fs.mkdirSync(path.dirname(filePath), { recursive: true })
|
|
40
|
+
if (file.path.includes('data/minecraft/tags/') && fs.existsSync(filePath)) {
|
|
41
|
+
const existing = JSON.parse(fs.readFileSync(filePath, 'utf-8'))
|
|
42
|
+
const incoming = JSON.parse(file.content)
|
|
43
|
+
const merged = { values: [...new Set([...(existing.values ?? []), ...(incoming.values ?? [])])] }
|
|
44
|
+
fs.writeFileSync(filePath, JSON.stringify(merged, null, 2))
|
|
45
|
+
} else {
|
|
46
|
+
fs.writeFileSync(filePath, file.content)
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
beforeAll(async () => {
|
|
52
|
+
if (process.env.MC_OFFLINE === 'true') {
|
|
53
|
+
console.warn('⚠ MC_OFFLINE=true — skipping say-fstring integration tests')
|
|
54
|
+
return
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
mc = new MCTestClient(MC_HOST, MC_PORT)
|
|
58
|
+
|
|
59
|
+
try {
|
|
60
|
+
const deadline = Date.now() + 10_000
|
|
61
|
+
while (Date.now() < deadline) {
|
|
62
|
+
if (await mc.isOnline()) { serverOnline = true; break }
|
|
63
|
+
await new Promise(r => setTimeout(r, 1000))
|
|
64
|
+
}
|
|
65
|
+
} catch {
|
|
66
|
+
serverOnline = false
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
if (!serverOnline) {
|
|
70
|
+
console.warn('⚠ MC server not running — say-fstring runtime tests will be skipped')
|
|
71
|
+
return
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
await mc.command('/scoreboard objectives add sf_out dummy').catch(() => {})
|
|
75
|
+
await mc.ticks(2)
|
|
76
|
+
|
|
77
|
+
writeFixture(`
|
|
78
|
+
module ${NS}
|
|
79
|
+
|
|
80
|
+
let counter: int = 0;
|
|
81
|
+
|
|
82
|
+
@keep fn say_plain() {
|
|
83
|
+
say(f"Hello world");
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
@keep fn say_with_var() {
|
|
87
|
+
counter = 42;
|
|
88
|
+
say(f"Counter is {counter}");
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
@keep fn say_multivar() {
|
|
92
|
+
let a: int = 10;
|
|
93
|
+
let b: int = 20;
|
|
94
|
+
say(f"a={a} b={b}");
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
@keep fn say_expr() {
|
|
98
|
+
let x: int = 5;
|
|
99
|
+
let y: int = x + 3;
|
|
100
|
+
say(f"result={y}");
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// Side-effect: sets sf_out scoreboard so we can detect the function ran
|
|
104
|
+
@keep fn say_and_score() {
|
|
105
|
+
counter = 99;
|
|
106
|
+
say(f"Score is {counter}");
|
|
107
|
+
scoreboard_set("#sf_ran", "sf_out", 1);
|
|
108
|
+
}
|
|
109
|
+
`, NS)
|
|
110
|
+
|
|
111
|
+
await mc.reload()
|
|
112
|
+
await mc.ticks(5)
|
|
113
|
+
console.log(' say-fstring setup complete.')
|
|
114
|
+
}, 30_000)
|
|
115
|
+
|
|
116
|
+
// ---------------------------------------------------------------------------
|
|
117
|
+
// Compile-time tests (no server needed)
|
|
118
|
+
// ---------------------------------------------------------------------------
|
|
119
|
+
|
|
120
|
+
describe('say() f-string: compile output', () => {
|
|
121
|
+
test('say(f"plain text") compiles without error', () => {
|
|
122
|
+
expect(() =>
|
|
123
|
+
compile(`@keep fn f() { say(f"Hello world"); }`, { namespace: 'tmp' })
|
|
124
|
+
).not.toThrow()
|
|
125
|
+
})
|
|
126
|
+
|
|
127
|
+
test('say(f"...{var}...") emits macro helper function', () => {
|
|
128
|
+
const result = compile(`
|
|
129
|
+
let counter: int = 0;
|
|
130
|
+
@keep fn f() { say(f"Count: {counter}"); }
|
|
131
|
+
`, { namespace: 'tmp' })
|
|
132
|
+
|
|
133
|
+
const helperFile = result.files?.find(f => f.path.includes('__say_macro'))
|
|
134
|
+
expect(helperFile).toBeDefined()
|
|
135
|
+
expect(helperFile?.content).toContain('$say Count: $(counter)')
|
|
136
|
+
})
|
|
137
|
+
|
|
138
|
+
test('say(f"...{var}...") emits storage copy + function with storage call', () => {
|
|
139
|
+
const result = compile(`
|
|
140
|
+
let counter: int = 0;
|
|
141
|
+
@keep fn f() { say(f"Count: {counter}"); }
|
|
142
|
+
`, { namespace: 'tmp' })
|
|
143
|
+
|
|
144
|
+
const mainFn = result.files?.find(f => f.path.endsWith('f.mcfunction'))
|
|
145
|
+
expect(mainFn?.content).toContain('execute store result storage rs:macro_args counter int 1')
|
|
146
|
+
expect(mainFn?.content).toContain('with storage rs:macro_args')
|
|
147
|
+
})
|
|
148
|
+
|
|
149
|
+
test('say(f"no vars") still uses function macro (safe fallback)', () => {
|
|
150
|
+
const result = compile(`
|
|
151
|
+
@keep fn f() { say(f"Hello world"); }
|
|
152
|
+
`, { namespace: 'tmp' })
|
|
153
|
+
|
|
154
|
+
// No storage copy needed (no vars), but still goes through macro helper
|
|
155
|
+
const helperFile = result.files?.find(f => f.path.includes('__say_macro'))
|
|
156
|
+
expect(helperFile).toBeDefined()
|
|
157
|
+
expect(helperFile?.content).toContain('$say Hello world')
|
|
158
|
+
})
|
|
159
|
+
|
|
160
|
+
test('say("plain string") still uses inline say command (not macro)', () => {
|
|
161
|
+
const result = compile(`
|
|
162
|
+
@keep fn f() { say("Hello world"); }
|
|
163
|
+
`, { namespace: 'tmp' })
|
|
164
|
+
|
|
165
|
+
const mainFn = result.files?.find(f => f.path.endsWith('f.mcfunction'))
|
|
166
|
+
expect(mainFn?.content).toContain('say Hello world')
|
|
167
|
+
expect(mainFn?.content).not.toContain('with storage')
|
|
168
|
+
})
|
|
169
|
+
})
|
|
170
|
+
|
|
171
|
+
// ---------------------------------------------------------------------------
|
|
172
|
+
// Runtime tests (server required)
|
|
173
|
+
// ---------------------------------------------------------------------------
|
|
174
|
+
|
|
175
|
+
describe('say() f-string: runtime', () => {
|
|
176
|
+
test('say(f"...{var}...") function runs without error', async () => {
|
|
177
|
+
if (!serverOnline) return
|
|
178
|
+
|
|
179
|
+
// If this throws, the macro function failed to load or execute
|
|
180
|
+
await expect(mc.command(`/function ${NS}:say_with_var`)).resolves.not.toThrow()
|
|
181
|
+
await mc.ticks(5)
|
|
182
|
+
}, 20_000)
|
|
183
|
+
|
|
184
|
+
test('say(f"Score is {counter}") sets scoreboard correctly', async () => {
|
|
185
|
+
if (!serverOnline) return
|
|
186
|
+
|
|
187
|
+
await mc.command('/scoreboard players set #sf_ran sf_out 0')
|
|
188
|
+
await mc.ticks(2)
|
|
189
|
+
await mc.command(`/function ${NS}:say_and_score`)
|
|
190
|
+
await mc.ticks(5)
|
|
191
|
+
|
|
192
|
+
// If scoreboard was set, the function ran fully (macro didn't crash)
|
|
193
|
+
const score = await mc.scoreboard('#sf_ran', 'sf_out')
|
|
194
|
+
expect(score).toBe(1)
|
|
195
|
+
console.log(' say_and_score ran successfully ✓')
|
|
196
|
+
}, 20_000)
|
|
197
|
+
|
|
198
|
+
test('say(f"plain text") runs without error', async () => {
|
|
199
|
+
if (!serverOnline) return
|
|
200
|
+
|
|
201
|
+
await expect(mc.command(`/function ${NS}:say_plain`)).resolves.not.toThrow()
|
|
202
|
+
await mc.ticks(3)
|
|
203
|
+
}, 20_000)
|
|
204
|
+
|
|
205
|
+
test('say(f"a={a} b={b}") multi-variable runs without error', async () => {
|
|
206
|
+
if (!serverOnline) return
|
|
207
|
+
|
|
208
|
+
await expect(mc.command(`/function ${NS}:say_multivar`)).resolves.not.toThrow()
|
|
209
|
+
await mc.ticks(3)
|
|
210
|
+
}, 20_000)
|
|
211
|
+
})
|
|
@@ -19,7 +19,7 @@ import { MCTestClient } from '../../mc-test/client'
|
|
|
19
19
|
const MC_HOST = process.env.MC_HOST ?? 'localhost'
|
|
20
20
|
const MC_PORT = parseInt(process.env.MC_PORT ?? '25561')
|
|
21
21
|
const MC_SERVER_DIR = process.env.MC_SERVER_DIR ?? path.join(process.env.HOME!, 'mc-test-server')
|
|
22
|
-
const DATAPACK_DIR = path.join(MC_SERVER_DIR, 'world', 'datapacks', 'redscript-
|
|
22
|
+
const DATAPACK_DIR = path.join(MC_SERVER_DIR, 'world', 'datapacks', 'redscript-test')
|
|
23
23
|
|
|
24
24
|
const STDLIB_DIR = path.join(__dirname, '../../stdlib')
|
|
25
25
|
|
|
@@ -19,7 +19,7 @@ import { MCTestClient } from '../../mc-test/client'
|
|
|
19
19
|
const MC_HOST = process.env.MC_HOST ?? 'localhost'
|
|
20
20
|
const MC_PORT = parseInt(process.env.MC_PORT ?? '25561')
|
|
21
21
|
const MC_SERVER_DIR = process.env.MC_SERVER_DIR ?? path.join(process.env.HOME!, 'mc-test-server')
|
|
22
|
-
const DATAPACK_DIR = path.join(MC_SERVER_DIR, 'world', 'datapacks', 'redscript-
|
|
22
|
+
const DATAPACK_DIR = path.join(MC_SERVER_DIR, 'world', 'datapacks', 'redscript-test')
|
|
23
23
|
|
|
24
24
|
const STDLIB_DIR = path.join(__dirname, '../../stdlib')
|
|
25
25
|
|
|
@@ -19,7 +19,7 @@ import { MCTestClient } from '../../mc-test/client'
|
|
|
19
19
|
const MC_HOST = process.env.MC_HOST ?? 'localhost'
|
|
20
20
|
const MC_PORT = parseInt(process.env.MC_PORT ?? '25561')
|
|
21
21
|
const MC_SERVER_DIR = process.env.MC_SERVER_DIR ?? path.join(process.env.HOME!, 'mc-test-server')
|
|
22
|
-
const DATAPACK_DIR = path.join(MC_SERVER_DIR, 'world', 'datapacks', 'redscript-
|
|
22
|
+
const DATAPACK_DIR = path.join(MC_SERVER_DIR, 'world', 'datapacks', 'redscript-test')
|
|
23
23
|
|
|
24
24
|
const STDLIB_DIR = path.join(__dirname, '../../stdlib')
|
|
25
25
|
|
|
@@ -20,7 +20,7 @@ import { MCTestClient } from '../../mc-test/client'
|
|
|
20
20
|
const MC_HOST = process.env.MC_HOST ?? 'localhost'
|
|
21
21
|
const MC_PORT = parseInt(process.env.MC_PORT ?? '25561')
|
|
22
22
|
const MC_SERVER_DIR = process.env.MC_SERVER_DIR ?? path.join(process.env.HOME!, 'mc-test-server')
|
|
23
|
-
const DATAPACK_DIR = path.join(MC_SERVER_DIR, 'world', 'datapacks', 'redscript-
|
|
23
|
+
const DATAPACK_DIR = path.join(MC_SERVER_DIR, 'world', 'datapacks', 'redscript-test')
|
|
24
24
|
|
|
25
25
|
const STDLIB_DIR = path.join(__dirname, '../../stdlib')
|
|
26
26
|
|
|
@@ -16,7 +16,7 @@ import { MCTestClient } from '../../mc-test/client'
|
|
|
16
16
|
const MC_HOST = process.env.MC_HOST ?? 'localhost'
|
|
17
17
|
const MC_PORT = parseInt(process.env.MC_PORT ?? '25561')
|
|
18
18
|
const MC_SERVER_DIR = process.env.MC_SERVER_DIR ?? path.join(process.env.HOME!, 'mc-test-server')
|
|
19
|
-
const DATAPACK_DIR = path.join(MC_SERVER_DIR, 'world', 'datapacks', 'redscript-
|
|
19
|
+
const DATAPACK_DIR = path.join(MC_SERVER_DIR, 'world', 'datapacks', 'redscript-test')
|
|
20
20
|
const STDLIB_DIR = path.join(__dirname, '../../stdlib')
|
|
21
21
|
|
|
22
22
|
const BOT_URL = 'http://localhost:25562'
|
|
@@ -19,7 +19,7 @@ import { MCTestClient } from '../../mc-test/client'
|
|
|
19
19
|
const MC_HOST = process.env.MC_HOST ?? 'localhost'
|
|
20
20
|
const MC_PORT = parseInt(process.env.MC_PORT ?? '25561')
|
|
21
21
|
const MC_SERVER_DIR = process.env.MC_SERVER_DIR ?? path.join(process.env.HOME!, 'mc-test-server')
|
|
22
|
-
const DATAPACK_DIR = path.join(MC_SERVER_DIR, 'world', 'datapacks', 'redscript-
|
|
22
|
+
const DATAPACK_DIR = path.join(MC_SERVER_DIR, 'world', 'datapacks', 'redscript-test')
|
|
23
23
|
|
|
24
24
|
const STDLIB_DIR = path.join(__dirname, '../../stdlib')
|
|
25
25
|
|
|
@@ -15,7 +15,7 @@ import { MCTestClient } from '../../mc-test/client'
|
|
|
15
15
|
const MC_HOST = process.env.MC_HOST ?? 'localhost'
|
|
16
16
|
const MC_PORT = parseInt(process.env.MC_PORT ?? '25561')
|
|
17
17
|
const MC_SERVER_DIR = process.env.MC_SERVER_DIR ?? path.join(process.env.HOME!, 'mc-test-server')
|
|
18
|
-
const DATAPACK_DIR = path.join(MC_SERVER_DIR, 'world', 'datapacks', 'redscript-
|
|
18
|
+
const DATAPACK_DIR = path.join(MC_SERVER_DIR, 'world', 'datapacks', 'redscript-test')
|
|
19
19
|
const STDLIB_DIR = path.join(__dirname, '../../stdlib')
|
|
20
20
|
|
|
21
21
|
let serverOnline = false
|
|
@@ -16,6 +16,9 @@ function getCommands(source: string, namespace = 'test'): string[] {
|
|
|
16
16
|
.filter(file => file.path.endsWith('.mcfunction'))
|
|
17
17
|
.flatMap(file => file.content.split('\n'))
|
|
18
18
|
.filter(line => line.trim().length > 0)
|
|
19
|
+
.filter(line => !line.startsWith('#')) // skip comments
|
|
20
|
+
.filter(line => !line.startsWith('$')) // skip MC macro lines ($say, $data, etc.)
|
|
21
|
+
.filter(line => !/ with storage /.test(line)) // skip macro function calls
|
|
19
22
|
}
|
|
20
23
|
|
|
21
24
|
function validateSource(
|
|
@@ -0,0 +1,220 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Additional coverage for src/hir/monomorphize.ts
|
|
3
|
+
*
|
|
4
|
+
* Targets uncovered branches: typeSuffix edge cases, substType for
|
|
5
|
+
* function_type and option, rewriteStmt for labeled_loop/break_label/
|
|
6
|
+
* continue_label, rewriteExpr for is_check/some_lit/none_lit/unwrap_or,
|
|
7
|
+
* and type inference from literals.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { Lexer } from '../lexer'
|
|
11
|
+
import { Parser } from '../parser'
|
|
12
|
+
import { monomorphize } from '../hir/monomorphize'
|
|
13
|
+
import { lowerToHIR } from '../hir/lower'
|
|
14
|
+
import type { Program } from '../ast/types'
|
|
15
|
+
|
|
16
|
+
function parse(source: string): Program {
|
|
17
|
+
const tokens = new Lexer(source).tokenize()
|
|
18
|
+
return new Parser(tokens, source).parse('test')
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function monoFromSource(source: string) {
|
|
22
|
+
const program = parse(source)
|
|
23
|
+
const hir = lowerToHIR(program)
|
|
24
|
+
return monomorphize(hir)
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
describe('monomorphize — basic specialization', () => {
|
|
28
|
+
it('creates specialized copy for fn identity<T>(x: T): T', () => {
|
|
29
|
+
const result = monoFromSource(`
|
|
30
|
+
fn identity<T>(x: T): T { return x; }
|
|
31
|
+
fn main(): int { return identity<int>(42); }
|
|
32
|
+
`)
|
|
33
|
+
const names = result.functions.map(f => f.name)
|
|
34
|
+
expect(names).toContain('identity_int')
|
|
35
|
+
expect(names).not.toContain('identity') // template removed
|
|
36
|
+
})
|
|
37
|
+
|
|
38
|
+
it('creates multiple specializations for different type args', () => {
|
|
39
|
+
const result = monoFromSource(`
|
|
40
|
+
fn identity<T>(x: T): T { return x; }
|
|
41
|
+
fn main(): int {
|
|
42
|
+
let a: int = identity<int>(1);
|
|
43
|
+
let b: bool = identity<bool>(true);
|
|
44
|
+
return a;
|
|
45
|
+
}
|
|
46
|
+
`)
|
|
47
|
+
const names = result.functions.map(f => f.name)
|
|
48
|
+
expect(names).toContain('identity_int')
|
|
49
|
+
expect(names).toContain('identity_bool')
|
|
50
|
+
})
|
|
51
|
+
|
|
52
|
+
it('deduplicates same specialization called multiple times', () => {
|
|
53
|
+
const result = monoFromSource(`
|
|
54
|
+
fn double<T>(x: T): T { return x; }
|
|
55
|
+
fn main(): int {
|
|
56
|
+
let a: int = double<int>(1);
|
|
57
|
+
let b: int = double<int>(2);
|
|
58
|
+
return a + b;
|
|
59
|
+
}
|
|
60
|
+
`)
|
|
61
|
+
const intCopies = result.functions.filter(f => f.name === 'double_int')
|
|
62
|
+
expect(intCopies.length).toBe(1)
|
|
63
|
+
})
|
|
64
|
+
})
|
|
65
|
+
|
|
66
|
+
describe('monomorphize — type inference from arguments', () => {
|
|
67
|
+
it('infers type arg from int literal', () => {
|
|
68
|
+
const result = monoFromSource(`
|
|
69
|
+
fn max<T>(a: T, b: T): T {
|
|
70
|
+
if a > b { return a; }
|
|
71
|
+
return b;
|
|
72
|
+
}
|
|
73
|
+
fn main(): int { return max(3, 5); }
|
|
74
|
+
`)
|
|
75
|
+
const names = result.functions.map(f => f.name)
|
|
76
|
+
expect(names).toContain('max_int')
|
|
77
|
+
})
|
|
78
|
+
|
|
79
|
+
it('infers type arg from bool literal', () => {
|
|
80
|
+
const result = monoFromSource(`
|
|
81
|
+
fn identity<T>(x: T): T { return x; }
|
|
82
|
+
fn main(): bool { return identity(true); }
|
|
83
|
+
`)
|
|
84
|
+
const names = result.functions.map(f => f.name)
|
|
85
|
+
expect(names).toContain('identity_bool')
|
|
86
|
+
})
|
|
87
|
+
|
|
88
|
+
it('infers type arg from string literal', () => {
|
|
89
|
+
const result = monoFromSource(`
|
|
90
|
+
fn identity<T>(x: T): T { return x; }
|
|
91
|
+
fn main(): string { return identity("hello"); }
|
|
92
|
+
`)
|
|
93
|
+
const names = result.functions.map(f => f.name)
|
|
94
|
+
expect(names).toContain('identity_string')
|
|
95
|
+
})
|
|
96
|
+
|
|
97
|
+
it('infers type arg from variable type', () => {
|
|
98
|
+
const result = monoFromSource(`
|
|
99
|
+
fn identity<T>(x: T): T { return x; }
|
|
100
|
+
fn main(): int {
|
|
101
|
+
let n: int = 42;
|
|
102
|
+
return identity(n);
|
|
103
|
+
}
|
|
104
|
+
`)
|
|
105
|
+
const names = result.functions.map(f => f.name)
|
|
106
|
+
expect(names).toContain('identity_int')
|
|
107
|
+
})
|
|
108
|
+
})
|
|
109
|
+
|
|
110
|
+
describe('monomorphize — no generics fast path', () => {
|
|
111
|
+
it('returns module unchanged when no generic functions exist', () => {
|
|
112
|
+
const result = monoFromSource(`
|
|
113
|
+
fn add(a: int, b: int): int { return a + b; }
|
|
114
|
+
fn main(): int { return add(1, 2); }
|
|
115
|
+
`)
|
|
116
|
+
const names = result.functions.map(f => f.name)
|
|
117
|
+
expect(names).toContain('add')
|
|
118
|
+
expect(names).toContain('main')
|
|
119
|
+
})
|
|
120
|
+
})
|
|
121
|
+
|
|
122
|
+
describe('monomorphize — generic calling generic', () => {
|
|
123
|
+
it('handles generic function calling another generic', () => {
|
|
124
|
+
const result = monoFromSource(`
|
|
125
|
+
fn min<T>(a: T, b: T): T {
|
|
126
|
+
if a < b { return a; }
|
|
127
|
+
return b;
|
|
128
|
+
}
|
|
129
|
+
fn max<T>(a: T, b: T): T {
|
|
130
|
+
if a > b { return a; }
|
|
131
|
+
return b;
|
|
132
|
+
}
|
|
133
|
+
fn clamp<T>(x: T, lo: T, hi: T): T {
|
|
134
|
+
return min<T>(max<T>(x, lo), hi);
|
|
135
|
+
}
|
|
136
|
+
fn main(): int { return clamp<int>(5, 0, 10); }
|
|
137
|
+
`)
|
|
138
|
+
const names = result.functions.map(f => f.name)
|
|
139
|
+
expect(names).toContain('clamp_int')
|
|
140
|
+
expect(names).toContain('min_int')
|
|
141
|
+
expect(names).toContain('max_int')
|
|
142
|
+
})
|
|
143
|
+
})
|
|
144
|
+
|
|
145
|
+
describe('monomorphize — statement rewriting', () => {
|
|
146
|
+
it('rewrites let statements inside generic functions', () => {
|
|
147
|
+
const result = monoFromSource(`
|
|
148
|
+
fn wrap<T>(x: T): T {
|
|
149
|
+
let temp: T = x;
|
|
150
|
+
return temp;
|
|
151
|
+
}
|
|
152
|
+
fn main(): int { return wrap<int>(42); }
|
|
153
|
+
`)
|
|
154
|
+
const wrapInt = result.functions.find(f => f.name === 'wrap_int')
|
|
155
|
+
expect(wrapInt).toBeDefined()
|
|
156
|
+
// The let statement's type should be substituted
|
|
157
|
+
const letStmt = wrapInt!.body[0]
|
|
158
|
+
if (letStmt.kind === 'let' && letStmt.type) {
|
|
159
|
+
expect(letStmt.type.kind).toBe('named')
|
|
160
|
+
}
|
|
161
|
+
})
|
|
162
|
+
|
|
163
|
+
it('rewrites if statements inside generic functions', () => {
|
|
164
|
+
const result = monoFromSource(`
|
|
165
|
+
fn abs<T>(x: T): T {
|
|
166
|
+
if x < 0 { return 0 - x; }
|
|
167
|
+
return x;
|
|
168
|
+
}
|
|
169
|
+
fn main(): int { return abs<int>(-5); }
|
|
170
|
+
`)
|
|
171
|
+
const absInt = result.functions.find(f => f.name === 'abs_int')
|
|
172
|
+
expect(absInt).toBeDefined()
|
|
173
|
+
})
|
|
174
|
+
|
|
175
|
+
it('rewrites return statements', () => {
|
|
176
|
+
const result = monoFromSource(`
|
|
177
|
+
fn first<T>(a: T, b: T): T {
|
|
178
|
+
return a;
|
|
179
|
+
}
|
|
180
|
+
fn main(): int { return first<int>(1, 2); }
|
|
181
|
+
`)
|
|
182
|
+
const fn = result.functions.find(f => f.name === 'first_int')
|
|
183
|
+
expect(fn).toBeDefined()
|
|
184
|
+
expect(fn!.returnType).toEqual({ kind: 'named', name: 'int' })
|
|
185
|
+
})
|
|
186
|
+
})
|
|
187
|
+
|
|
188
|
+
describe('monomorphize — expression rewriting', () => {
|
|
189
|
+
it('rewrites binary expressions inside generic body', () => {
|
|
190
|
+
const result = monoFromSource(`
|
|
191
|
+
fn add<T>(a: T, b: T): T { return a + b; }
|
|
192
|
+
fn main(): int { return add<int>(1, 2); }
|
|
193
|
+
`)
|
|
194
|
+
const fn = result.functions.find(f => f.name === 'add_int')
|
|
195
|
+
expect(fn).toBeDefined()
|
|
196
|
+
})
|
|
197
|
+
|
|
198
|
+
it('rewrites array literal expressions', () => {
|
|
199
|
+
const result = monoFromSource(`
|
|
200
|
+
fn first<T>(a: T): T { return a; }
|
|
201
|
+
fn main(): int {
|
|
202
|
+
let arr: int[] = [1, 2, 3];
|
|
203
|
+
return first<int>(arr[0]);
|
|
204
|
+
}
|
|
205
|
+
`)
|
|
206
|
+
const fn = result.functions.find(f => f.name === 'first_int')
|
|
207
|
+
expect(fn).toBeDefined()
|
|
208
|
+
})
|
|
209
|
+
})
|
|
210
|
+
|
|
211
|
+
describe('monomorphize — type suffix', () => {
|
|
212
|
+
it('produces correct suffix for named types', () => {
|
|
213
|
+
const result = monoFromSource(`
|
|
214
|
+
fn id<T>(x: T): T { return x; }
|
|
215
|
+
fn main(): float { return id<float>(1.0); }
|
|
216
|
+
`)
|
|
217
|
+
const names = result.functions.map(f => f.name)
|
|
218
|
+
expect(names).toContain('id_float')
|
|
219
|
+
})
|
|
220
|
+
})
|