bun-assemblyscript 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.vscode/as.code-snippets +32 -0
- package/.vscode/extensions.json +5 -0
- package/.vscode/settings.json +5 -0
- package/README.md +34 -0
- package/app.ts +2 -0
- package/assemblyscript.d.ts +113 -0
- package/build-app.ts +10 -0
- package/bun.lock +54 -0
- package/bunfig.toml +9 -0
- package/debug_asc.ts +11 -0
- package/dist/app.js +1 -0
- package/index.ts +19 -0
- package/package.json +34 -0
- package/preload.ts +4 -0
- package/src/bin/watch.ts +14 -0
- package/src/cache/index.ts +135 -0
- package/src/compiler.ts +177 -0
- package/src/install/bunfig.ts +45 -0
- package/src/install/index.ts +142 -0
- package/src/install/tsconfig.ts +61 -0
- package/src/install/vscode.ts +80 -0
- package/src/instantiator.ts +90 -0
- package/src/plugin.ts +189 -0
- package/src/typegen/generator.ts +62 -0
- package/src/typegen/global.d.ts +26 -0
- package/src/typegen/parser.ts +118 -0
- package/src/typegen/typemap.ts +51 -0
- package/src/watcher/index.ts +117 -0
- package/src/watcher/logger.ts +26 -0
- package/test/fixtures/math.as +14 -0
- package/test/fixtures/math.as.d.ts +5 -0
- package/test/math.test.ts +22 -0
- package/tsconfig.json +33 -0
package/src/plugin.ts
ADDED
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
import type { BunPlugin } from "bun";
|
|
2
|
+
import { compile, type CompilerOptions } from "./compiler";
|
|
3
|
+
import { instantiate } from "./instantiator";
|
|
4
|
+
import { parseASExports, type ASExport } from "./typegen/parser";
|
|
5
|
+
import { generateDts } from "./typegen/generator";
|
|
6
|
+
import { join } from "path";
|
|
7
|
+
import { existsSync } from "fs";
|
|
8
|
+
|
|
9
|
+
export interface PluginOptions {
|
|
10
|
+
compilerOverrides?: CompilerOptions;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
function isBuildMode(build: { config?: { target?: string } }): boolean {
|
|
14
|
+
return (
|
|
15
|
+
build.config !== undefined &&
|
|
16
|
+
typeof build.config.target === "string" &&
|
|
17
|
+
build.config.target.length > 0 &&
|
|
18
|
+
process.env.NODE_ENV === "production"
|
|
19
|
+
);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function isComplexType(t: string): boolean {
|
|
23
|
+
const trim = t.trim();
|
|
24
|
+
return trim === "string" || trim === "String" || trim.includes("Array") || trim.includes("Map");
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function hasComplexExports(exports: ASExport[]): boolean {
|
|
28
|
+
for (const exp of exports) {
|
|
29
|
+
if (exp.kind === "class") return true;
|
|
30
|
+
if (exp.type && isComplexType(exp.type)) return true;
|
|
31
|
+
if (exp.params && exp.params.some((p) => isComplexType(p.type))) return true;
|
|
32
|
+
if (exp.returnType && isComplexType(exp.returnType)) return true;
|
|
33
|
+
if (exp.methods && hasComplexExports(exp.methods)) return true;
|
|
34
|
+
}
|
|
35
|
+
return false;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function generateInlineModule(exportNames: string[], wasmBase64: string): string {
|
|
39
|
+
const lines: string[] = [
|
|
40
|
+
`// Auto-generated by bun-plugin-assemblyscript (inline)`,
|
|
41
|
+
`const __b64 = ${JSON.stringify(wasmBase64)};`,
|
|
42
|
+
`const __bin = Uint8Array.from(atob(__b64), (c) => c.charCodeAt(0));`,
|
|
43
|
+
`const __memory = new WebAssembly.Memory({ initial: 1 });`,
|
|
44
|
+
`function __readStr(mem, ptr) {`,
|
|
45
|
+
` if (ptr === 0) return "";`,
|
|
46
|
+
` const dv = new DataView(mem.buffer);`,
|
|
47
|
+
` const len = dv.getInt32(ptr - 4, true) >>> 1;`,
|
|
48
|
+
` if (len <= 0) return "";`,
|
|
49
|
+
` return String.fromCharCode(...new Uint16Array(mem.buffer, ptr, len));`,
|
|
50
|
+
`}`,
|
|
51
|
+
`const __imports = {`,
|
|
52
|
+
` env: {`,
|
|
53
|
+
` memory: __memory,`,
|
|
54
|
+
` abort(msg, file, line, col) {`,
|
|
55
|
+
` const m = __readStr(__memory, msg);`,
|
|
56
|
+
` const f = __readStr(__memory, file);`,
|
|
57
|
+
` throw new Error(\`AbortError: \${m || "(no)"} — \${f}:\${line}:\${col}\`);`,
|
|
58
|
+
` }`,
|
|
59
|
+
` }`,
|
|
60
|
+
`};`,
|
|
61
|
+
`const { instance: __inst } = await WebAssembly.instantiate(__bin, __imports);`,
|
|
62
|
+
];
|
|
63
|
+
for (const name of exportNames) {
|
|
64
|
+
lines.push(`export const ${name} = __inst.exports.${name};`);
|
|
65
|
+
}
|
|
66
|
+
return lines.join("\n") + "\n";
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function generateFileModule(exportNames: string[], wasmUrl: string): string {
|
|
70
|
+
const lines: string[] = [
|
|
71
|
+
`// Auto-generated by bun-plugin-assemblyscript (file)`,
|
|
72
|
+
`const __url = ${JSON.stringify(wasmUrl)};`,
|
|
73
|
+
`const __res = await fetch(__url);`,
|
|
74
|
+
`const __bin = new Uint8Array(await __res.arrayBuffer());`,
|
|
75
|
+
`const __memory = new WebAssembly.Memory({ initial: 1 });`,
|
|
76
|
+
`function __readStr(mem, ptr) {`,
|
|
77
|
+
` if (ptr === 0) return "";`,
|
|
78
|
+
` const dv = new DataView(mem.buffer);`,
|
|
79
|
+
` const len = dv.getInt32(ptr - 4, true) >>> 1;`,
|
|
80
|
+
` if (len <= 0) return "";`,
|
|
81
|
+
` return String.fromCharCode(...new Uint16Array(mem.buffer, ptr, len));`,
|
|
82
|
+
`}`,
|
|
83
|
+
`const __imports = {`,
|
|
84
|
+
` env: {`,
|
|
85
|
+
` memory: __memory,`,
|
|
86
|
+
` abort(msg, file, line, col) {`,
|
|
87
|
+
` const m = __readStr(__memory, msg);`,
|
|
88
|
+
` const f = __readStr(__memory, file);`,
|
|
89
|
+
` throw new Error(\`AbortError: \${m || "(no)"} — \${f}:\${line}:\${col}\`);`,
|
|
90
|
+
` }`,
|
|
91
|
+
` }`,
|
|
92
|
+
`};`,
|
|
93
|
+
`const { instance: __inst } = await WebAssembly.instantiate(__bin, __imports);`,
|
|
94
|
+
];
|
|
95
|
+
for (const name of exportNames) {
|
|
96
|
+
lines.push(`export const ${name} = __inst.exports.${name};`);
|
|
97
|
+
}
|
|
98
|
+
return lines.join("\n") + "\n";
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
export function assemblyScriptPlugin(options: PluginOptions = {}): BunPlugin {
|
|
102
|
+
return {
|
|
103
|
+
name: "bun-plugin-assemblyscript",
|
|
104
|
+
async setup(build) {
|
|
105
|
+
let embedMode = "auto";
|
|
106
|
+
try {
|
|
107
|
+
if (existsSync("bunfig.toml")) {
|
|
108
|
+
const config = await Bun.file("bunfig.toml").text();
|
|
109
|
+
const match = config.match(/embedMode\s*=\s*(?:"|')([^"']+)(?:"|')/);
|
|
110
|
+
if (match) embedMode = match[1];
|
|
111
|
+
}
|
|
112
|
+
} catch (e) {}
|
|
113
|
+
|
|
114
|
+
// Build context
|
|
115
|
+
const isProd = isBuildMode(build) || process.env.NODE_ENV === "production" || build.config?.minify;
|
|
116
|
+
|
|
117
|
+
build.onLoad({ filter: /\.as$/ }, async (args) => {
|
|
118
|
+
let parsedExports: ASExport[] = [];
|
|
119
|
+
let hasComplex = false;
|
|
120
|
+
try {
|
|
121
|
+
const source = await Bun.file(args.path).text();
|
|
122
|
+
parsedExports = parseASExports(source);
|
|
123
|
+
await generateDts(parsedExports, args.path);
|
|
124
|
+
hasComplex = hasComplexExports(parsedExports);
|
|
125
|
+
} catch (e) {
|
|
126
|
+
console.warn("[bun-assemblyscript] Génération de types échouée :", e);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
const compilerOptions: CompilerOptions = {
|
|
130
|
+
optimizeLevel: isProd ? 3 : 0,
|
|
131
|
+
shrinkLevel: isProd ? 2 : 0,
|
|
132
|
+
runtime: hasComplex ? "incremental" : "stub",
|
|
133
|
+
sourceMap: !isProd,
|
|
134
|
+
debug: !isProd,
|
|
135
|
+
...options.compilerOverrides,
|
|
136
|
+
};
|
|
137
|
+
|
|
138
|
+
const result = await compile(args.path, compilerOptions);
|
|
139
|
+
|
|
140
|
+
if (!result.success || !result.wasmBytes) {
|
|
141
|
+
return {
|
|
142
|
+
contents: "",
|
|
143
|
+
errors: result.errors.map((text) => ({ text })),
|
|
144
|
+
};
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
// Ecrire le sourcemap dans le cache à côté du .wasm si non prod
|
|
148
|
+
if (!isProd && result.sourceMapBytes) {
|
|
149
|
+
const cacheDir = join(process.cwd(), ".cache", "bun-as", require("path").basename(args.path));
|
|
150
|
+
const currentHash = require("crypto").createHash("sha256").update(await Bun.file(args.path).text()).digest("hex");
|
|
151
|
+
const mapPath = join(cacheDir, currentHash + ".wasm.map");
|
|
152
|
+
try {
|
|
153
|
+
await require("fs/promises").mkdir(cacheDir, { recursive: true });
|
|
154
|
+
await Bun.write(mapPath, result.sourceMapBytes);
|
|
155
|
+
} catch (e) {}
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
// Stratégie d'embedding
|
|
159
|
+
let finalMode = embedMode;
|
|
160
|
+
if (finalMode === "auto") {
|
|
161
|
+
finalMode = result.wasmBytes.length < 100000 ? "inline" : "file";
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
const exportsMap = await instantiate(result.wasmBytes);
|
|
165
|
+
const exportNames = Object.keys(exportsMap).filter(
|
|
166
|
+
(k) => k !== "__data_end" && k !== "__heap_base"
|
|
167
|
+
);
|
|
168
|
+
|
|
169
|
+
if (finalMode === "inline") {
|
|
170
|
+
const b64 = Buffer.from(result.wasmBytes).toString("base64");
|
|
171
|
+
return { contents: generateInlineModule(exportNames, b64), loader: "js" };
|
|
172
|
+
} else {
|
|
173
|
+
// File mode
|
|
174
|
+
const outdir = build.config?.outdir || "./dist";
|
|
175
|
+
const fileName = "module-" + Bun.hash(result.wasmBytes) + ".wasm";
|
|
176
|
+
const outPath = join(process.cwd(), outdir, fileName);
|
|
177
|
+
|
|
178
|
+
try {
|
|
179
|
+
await require("fs/promises").mkdir(join(process.cwd(), outdir), { recursive: true });
|
|
180
|
+
await Bun.write(outPath, result.wasmBytes);
|
|
181
|
+
} catch(e) {}
|
|
182
|
+
|
|
183
|
+
const wasmUrl = "./" + fileName;
|
|
184
|
+
return { contents: generateFileModule(exportNames, wasmUrl), loader: "js" };
|
|
185
|
+
}
|
|
186
|
+
});
|
|
187
|
+
},
|
|
188
|
+
};
|
|
189
|
+
}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { mapType } from "./typemap";
|
|
2
|
+
import { type ASExport } from "./parser";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Génère le contenu d'un fichier .d.ts à partir d'un tableau d'exports AS.
|
|
6
|
+
* Si le contenu (hash) n'a pas changé, on n'écrase pas le fichier.
|
|
7
|
+
*
|
|
8
|
+
* @param exports Liste des exports parsés
|
|
9
|
+
* @param sourcePath Chemin absolu vers le fichier `.as` d'origine
|
|
10
|
+
*/
|
|
11
|
+
export async function generateDts(
|
|
12
|
+
exports: ASExport[],
|
|
13
|
+
sourcePath: string
|
|
14
|
+
): Promise<void> {
|
|
15
|
+
const lines: string[] = [
|
|
16
|
+
"// auto-generated by bun-plugin-assemblyscript — do not edit",
|
|
17
|
+
"",
|
|
18
|
+
];
|
|
19
|
+
|
|
20
|
+
for (const exp of exports) {
|
|
21
|
+
if (exp.kind === "const") {
|
|
22
|
+
lines.push(`export const ${exp.name}: ${mapType(exp.type!)};`);
|
|
23
|
+
} else if (exp.kind === "function") {
|
|
24
|
+
const params = exp
|
|
25
|
+
.params!.map((p) => `${p.name}: ${mapType(p.type)}`)
|
|
26
|
+
.join(", ");
|
|
27
|
+
lines.push(
|
|
28
|
+
`export function ${exp.name}(${params}): ${mapType(exp.returnType!)};`
|
|
29
|
+
);
|
|
30
|
+
} else if (exp.kind === "class") {
|
|
31
|
+
lines.push(`export class ${exp.name} {`);
|
|
32
|
+
if (exp.methods) {
|
|
33
|
+
for (const m of exp.methods) {
|
|
34
|
+
const params = m
|
|
35
|
+
.params!.map((p) => `${p.name}: ${mapType(p.type)}`)
|
|
36
|
+
.join(", ");
|
|
37
|
+
lines.push(
|
|
38
|
+
` ${m.name}(${params}): ${mapType(m.returnType!)};`
|
|
39
|
+
);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
lines.push(`}`);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const dtsContent = lines.join("\n") + "\n";
|
|
47
|
+
const dtsPath = sourcePath + ".d.ts";
|
|
48
|
+
const newHash = Bun.hash(dtsContent);
|
|
49
|
+
|
|
50
|
+
try {
|
|
51
|
+
const existingContent = await Bun.file(dtsPath).text();
|
|
52
|
+
const oldHash = Bun.hash(existingContent);
|
|
53
|
+
if (newHash === oldHash) {
|
|
54
|
+
// Pas de modification structurelle de la signature, on ne fait rien
|
|
55
|
+
return;
|
|
56
|
+
}
|
|
57
|
+
} catch (err) {
|
|
58
|
+
// Fichier inexistant, on procède à l'écriture
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
await Bun.write(dtsPath, dtsContent);
|
|
62
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Types globaux AssemblyScript (primitives)
|
|
3
|
+
* Cela permet à l'éditeur de reconnaître i32, f64, etc. dans les fichiers .as
|
|
4
|
+
* sans entrer en conflit avec les types TypeScript standards (String, Array).
|
|
5
|
+
*/
|
|
6
|
+
declare type i8 = number;
|
|
7
|
+
declare type i16 = number;
|
|
8
|
+
declare type i32 = number;
|
|
9
|
+
declare type i64 = bigint;
|
|
10
|
+
declare type isize = number;
|
|
11
|
+
declare type u8 = number;
|
|
12
|
+
declare type u16 = number;
|
|
13
|
+
declare type u32 = number;
|
|
14
|
+
declare type u64 = bigint;
|
|
15
|
+
declare type usize = number;
|
|
16
|
+
declare type f32 = number;
|
|
17
|
+
declare type f64 = number;
|
|
18
|
+
declare type bool = boolean;
|
|
19
|
+
declare type v128 = never;
|
|
20
|
+
declare type anyref = any;
|
|
21
|
+
declare type externref = any;
|
|
22
|
+
|
|
23
|
+
declare module "*.as" {
|
|
24
|
+
const exports: Record<string, unknown>;
|
|
25
|
+
export default exports;
|
|
26
|
+
}
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
export type ASExportKind = "function" | "const" | "class";
|
|
2
|
+
|
|
3
|
+
export interface ASParam {
|
|
4
|
+
name: string;
|
|
5
|
+
type: string;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export interface ASExport {
|
|
9
|
+
name: string;
|
|
10
|
+
kind: ASExportKind;
|
|
11
|
+
params?: ASParam[];
|
|
12
|
+
returnType?: string;
|
|
13
|
+
type?: string; // For constants
|
|
14
|
+
methods?: ASExport[]; // For class methods
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function extractClassBody(text: string, startIndex: number): string {
|
|
18
|
+
let depth = 0;
|
|
19
|
+
let inClass = false;
|
|
20
|
+
let bodyStart = -1;
|
|
21
|
+
const length = text.length;
|
|
22
|
+
|
|
23
|
+
for (let i = startIndex; i < length; i++) {
|
|
24
|
+
const char = text[i];
|
|
25
|
+
if (char === "{") {
|
|
26
|
+
if (depth === 0) {
|
|
27
|
+
inClass = true;
|
|
28
|
+
bodyStart = i + 1;
|
|
29
|
+
}
|
|
30
|
+
depth++;
|
|
31
|
+
} else if (char === "}") {
|
|
32
|
+
depth--;
|
|
33
|
+
if (depth === 0 && inClass) {
|
|
34
|
+
return text.substring(bodyStart, i);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
return "";
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Parse un fichier source AssemblyScript (.as) et extrait les signatures publiques.
|
|
43
|
+
* Gère "export function", "export const" et "export class".
|
|
44
|
+
*/
|
|
45
|
+
export function parseASExports(source: string): ASExport[] {
|
|
46
|
+
// Nettoyer les commentaires ( /* ... */ et // ... )
|
|
47
|
+
const text = source.replace(/\/\*[\s\S]*?\*\/|\/\/.*/g, "");
|
|
48
|
+
const exports: ASExport[] = [];
|
|
49
|
+
|
|
50
|
+
// Parsing des fonctions (hors des classes)
|
|
51
|
+
const fnRegex = /export\s+function\s+([a-zA-Z0-9_]+)\s*\(([^)]*)\)\s*:\s*([^;{]+)/g;
|
|
52
|
+
let match;
|
|
53
|
+
while ((match = fnRegex.exec(text)) !== null) {
|
|
54
|
+
const name = match[1];
|
|
55
|
+
const paramsStr = match[2];
|
|
56
|
+
const returnType = match[3].trim();
|
|
57
|
+
|
|
58
|
+
const params: ASParam[] = paramsStr
|
|
59
|
+
.split(",")
|
|
60
|
+
.filter((p) => p.trim())
|
|
61
|
+
.map((p) => {
|
|
62
|
+
const parts = p.split(":");
|
|
63
|
+
return {
|
|
64
|
+
name: parts[0].trim(),
|
|
65
|
+
type: parts.length > 1 ? parts[1].trim() : "unknown",
|
|
66
|
+
};
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
exports.push({ kind: "function", name, params, returnType });
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// Parsing des constantes (export const)
|
|
73
|
+
const constRegex = /export\s+const\s+([a-zA-Z0-9_]+)\s*:\s*([^=;]+)/g;
|
|
74
|
+
while ((match = constRegex.exec(text)) !== null) {
|
|
75
|
+
exports.push({ kind: "const", name: match[1], type: match[2].trim() });
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// Parsing des classes (export class)
|
|
79
|
+
const classRegex = /export\s+class\s+([a-zA-Z0-9_]+)/g;
|
|
80
|
+
while ((match = classRegex.exec(text)) !== null) {
|
|
81
|
+
const className = match[1];
|
|
82
|
+
const body = extractClassBody(text, match.index);
|
|
83
|
+
|
|
84
|
+
const methods: ASExport[] = [];
|
|
85
|
+
// Méthodes publiques de classe
|
|
86
|
+
const methodRegex = /(?:public\s+)?([a-zA-Z0-9_]+)\s*\(([^)]*)\)\s*:\s*([^;{]+)/g;
|
|
87
|
+
let mMatch;
|
|
88
|
+
while ((mMatch = methodRegex.exec(body)) !== null) {
|
|
89
|
+
const mName = mMatch[1];
|
|
90
|
+
if (mName === "constructor") continue;
|
|
91
|
+
|
|
92
|
+
const mParamsStr = mMatch[2];
|
|
93
|
+
const mReturnType = mMatch[3].trim();
|
|
94
|
+
|
|
95
|
+
const mParams: ASParam[] = mParamsStr
|
|
96
|
+
.split(",")
|
|
97
|
+
.filter((p) => p.trim())
|
|
98
|
+
.map((p) => {
|
|
99
|
+
const parts = p.split(":");
|
|
100
|
+
return {
|
|
101
|
+
name: parts[0].trim(),
|
|
102
|
+
type: parts.length > 1 ? parts[1].trim() : "unknown",
|
|
103
|
+
};
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
methods.push({
|
|
107
|
+
kind: "function",
|
|
108
|
+
name: mName,
|
|
109
|
+
params: mParams,
|
|
110
|
+
returnType: mReturnType,
|
|
111
|
+
});
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
exports.push({ kind: "class", name: className, methods });
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
return exports;
|
|
118
|
+
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Mappe un type AssemblyScript vers un type de définition TypeScript (.d.ts).
|
|
3
|
+
*/
|
|
4
|
+
export function mapType(asType: string): string {
|
|
5
|
+
const t = asType.trim();
|
|
6
|
+
|
|
7
|
+
// Primitives exactes
|
|
8
|
+
switch (t) {
|
|
9
|
+
case "i8":
|
|
10
|
+
case "i16":
|
|
11
|
+
case "i32":
|
|
12
|
+
case "i64":
|
|
13
|
+
case "u8":
|
|
14
|
+
case "u16":
|
|
15
|
+
case "u32":
|
|
16
|
+
case "u64":
|
|
17
|
+
case "f32":
|
|
18
|
+
case "f64":
|
|
19
|
+
case "isize":
|
|
20
|
+
case "usize":
|
|
21
|
+
return "number";
|
|
22
|
+
|
|
23
|
+
case "bool":
|
|
24
|
+
return "boolean";
|
|
25
|
+
|
|
26
|
+
case "string":
|
|
27
|
+
case "String":
|
|
28
|
+
return "string";
|
|
29
|
+
|
|
30
|
+
case "void":
|
|
31
|
+
return "void";
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// Match des tableaux
|
|
35
|
+
const arrayMatch = t.match(/^(?:Static)?Array<(.+)>$/);
|
|
36
|
+
if (arrayMatch) {
|
|
37
|
+
return `${mapType(arrayMatch[1])}[]`;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// Match des Maps
|
|
41
|
+
const mapMatch = t.match(/^Map<(.+?)\s*,\s*(.+)>$/);
|
|
42
|
+
if (mapMatch) {
|
|
43
|
+
return `Map<${mapType(mapMatch[1])}, ${mapType(mapMatch[2])}>`;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// Fallback si type inconnu
|
|
47
|
+
console.warn(
|
|
48
|
+
`[bun-assemblyscript] Warning : Type AssemblyScript non reconnu '${t}'. Fallback sur 'unknown'.`
|
|
49
|
+
);
|
|
50
|
+
return "unknown";
|
|
51
|
+
}
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
import { Glob } from "bun";
|
|
2
|
+
import { watch } from "fs";
|
|
3
|
+
import { join, basename } from "path";
|
|
4
|
+
import { compile } from "../compiler";
|
|
5
|
+
import { get, set, computeHash, invalidate } from "../cache";
|
|
6
|
+
import { parseASExports } from "../typegen/parser";
|
|
7
|
+
import { generateDts } from "../typegen/generator";
|
|
8
|
+
import { logSuccess, logError, logStart } from "./logger";
|
|
9
|
+
|
|
10
|
+
// Conserve les watchers et les hashes en mémoire vive
|
|
11
|
+
const hashes = new Map<string, string>();
|
|
12
|
+
const watchers = new Map<string, any>();
|
|
13
|
+
|
|
14
|
+
async function processFile(filePath: string, isInitial: boolean, isProd = false) {
|
|
15
|
+
try {
|
|
16
|
+
const text = await Bun.file(filePath).text();
|
|
17
|
+
const hash = await computeHash(text);
|
|
18
|
+
|
|
19
|
+
// Skip si strictement rien n'a changé en mémoire
|
|
20
|
+
if (hashes.get(filePath) === hash) {
|
|
21
|
+
if (isInitial) {
|
|
22
|
+
// En initialisation, vérifier si le cache disque existe vraiment
|
|
23
|
+
const cached = await get(filePath, hash);
|
|
24
|
+
if (cached) return;
|
|
25
|
+
} else {
|
|
26
|
+
return;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
hashes.set(filePath, hash);
|
|
31
|
+
const startTime = performance.now();
|
|
32
|
+
|
|
33
|
+
// Check le cache persistant sur disque
|
|
34
|
+
const cached = await get(filePath, hash);
|
|
35
|
+
if (cached) {
|
|
36
|
+
if (!isInitial) {
|
|
37
|
+
logSuccess(basename(filePath), Math.round(performance.now() - startTime) + " (cached)");
|
|
38
|
+
}
|
|
39
|
+
return;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// Compilation complète par asc
|
|
43
|
+
const result = await compile(filePath, {
|
|
44
|
+
optimizeLevel: isProd ? 3 : 0,
|
|
45
|
+
runtime: isProd ? "minimal" : "stub",
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
if (!result.success || !result.wasmBytes) {
|
|
49
|
+
logError(basename(filePath), result.errors);
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// Régénération automatique des définitions TypeScript
|
|
54
|
+
const parsed = parseASExports(text);
|
|
55
|
+
await generateDts(parsed, filePath);
|
|
56
|
+
const dtsContent = await Bun.file(filePath + ".d.ts").text();
|
|
57
|
+
|
|
58
|
+
// Enregistrer dans le cache disque
|
|
59
|
+
await set(filePath, hash, result.wasmBytes, dtsContent);
|
|
60
|
+
|
|
61
|
+
logSuccess(basename(filePath), Math.round(performance.now() - startTime));
|
|
62
|
+
} catch (err) {
|
|
63
|
+
if ((err as NodeJS.ErrnoException).code === "ENOENT") {
|
|
64
|
+
// Fichier potentiellement supprimé
|
|
65
|
+
await invalidate(filePath);
|
|
66
|
+
hashes.delete(filePath);
|
|
67
|
+
} else {
|
|
68
|
+
logError(basename(filePath), [err instanceof Error ? err.message : String(err)]);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Lance le daemon de surveillance.
|
|
75
|
+
*/
|
|
76
|
+
export async function startWatcher() {
|
|
77
|
+
const glob = new Glob("**/*.as");
|
|
78
|
+
const files: string[] = [];
|
|
79
|
+
|
|
80
|
+
// Scan initial récursif avec Glob (ignore node_modules et caches)
|
|
81
|
+
for (const file of glob.scanSync(".")) {
|
|
82
|
+
if (file.includes("node_modules") || file.includes(".cache")) continue;
|
|
83
|
+
const absPath = join(process.cwd(), file);
|
|
84
|
+
files.push(absPath);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
logStart(files.length);
|
|
88
|
+
|
|
89
|
+
for (const file of files) {
|
|
90
|
+
await processFile(file, true);
|
|
91
|
+
|
|
92
|
+
const watcher = watch(file, async (event) => {
|
|
93
|
+
if (event === "change" || event === "rename") {
|
|
94
|
+
await processFile(file, false);
|
|
95
|
+
}
|
|
96
|
+
});
|
|
97
|
+
watchers.set(file, watcher);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Compile tout le projet `as` en mode production (optimisation max).
|
|
103
|
+
*/
|
|
104
|
+
export async function startBuild() {
|
|
105
|
+
const glob = new Glob("**/*.as");
|
|
106
|
+
const files: string[] = [];
|
|
107
|
+
|
|
108
|
+
for (const file of glob.scanSync(".")) {
|
|
109
|
+
if (file.includes("node_modules") || file.includes(".cache")) continue;
|
|
110
|
+
const absPath = join(process.cwd(), file);
|
|
111
|
+
files.push(absPath);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
for (const file of files) {
|
|
115
|
+
await processFile(file, true, true);
|
|
116
|
+
}
|
|
117
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Utilitaire de log léger en console sans aucune dépendance,
|
|
3
|
+
* s'appuyant uniquement sur les séquences d'échappement ANSI natives.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
export function logSuccess(file: string, ms: number | string) {
|
|
7
|
+
// \x1b[32m -> Vert, \x1b[0m -> Reset
|
|
8
|
+
console.log(`\x1b[32m[bun-as] ✓ ${file} compiled in ${ms}ms\x1b[0m`);
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export function logError(file: string, errors: string[]) {
|
|
12
|
+
// \x1b[31m -> Rouge
|
|
13
|
+
console.error(`\x1b[31m[bun-as] ✗ Error in ${file}\x1b[0m`);
|
|
14
|
+
for (const error of errors) {
|
|
15
|
+
console.error(`\x1b[31m ${error}\x1b[0m`);
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export function logStart(count: number) {
|
|
20
|
+
// \x1b[36m -> Cyan
|
|
21
|
+
console.log(
|
|
22
|
+
`\x1b[36m[bun-as] Watching ${count} AssemblyScript file${
|
|
23
|
+
count > 1 ? "s" : ""
|
|
24
|
+
}...\x1b[0m`
|
|
25
|
+
);
|
|
26
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/// <reference path="../../assemblyscript.d.ts" />
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
export function add(a: i32, b: i32): i32 {
|
|
5
|
+
return (a + b) as i32;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export function multiply(a: i32, b: i32): i32 {
|
|
9
|
+
return (a * b) as i32;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export function subtract(a: i32, b: i32): i32 {
|
|
13
|
+
return (a - b) as i32;
|
|
14
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { describe, expect, it } from "bun:test";
|
|
2
|
+
import { add, multiply, subtract } from "./fixtures/math.as";
|
|
3
|
+
|
|
4
|
+
describe("AssemblyScript Math Functions", () => {
|
|
5
|
+
it("should add two numbers correctly", () => {
|
|
6
|
+
expect(add(2, 3)).toBe(5);
|
|
7
|
+
expect(add(-1, 1)).toBe(0);
|
|
8
|
+
expect(add(0, 0)).toBe(0);
|
|
9
|
+
});
|
|
10
|
+
|
|
11
|
+
it("should multiply two numbers correctly", () => {
|
|
12
|
+
expect(multiply(2, 3)).toBe(6);
|
|
13
|
+
expect(multiply(-2, 3)).toBe(-6);
|
|
14
|
+
expect(multiply(0, 5)).toBe(0);
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
it("should subtract two numbers correctly", () => {
|
|
18
|
+
expect(subtract(5, 3)).toBe(2);
|
|
19
|
+
expect(subtract(3, 5)).toBe(-2);
|
|
20
|
+
expect(subtract(0, 0)).toBe(0);
|
|
21
|
+
});
|
|
22
|
+
});
|
package/tsconfig.json
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "ESNext",
|
|
4
|
+
"module": "ESNext",
|
|
5
|
+
"moduleResolution": "bundler",
|
|
6
|
+
"lib": [
|
|
7
|
+
"ESNext"
|
|
8
|
+
],
|
|
9
|
+
"types": [
|
|
10
|
+
"bun-types"
|
|
11
|
+
],
|
|
12
|
+
"strict": true,
|
|
13
|
+
"esModuleInterop": true,
|
|
14
|
+
"skipLibCheck": true,
|
|
15
|
+
"forceConsistentCasingInFileNames": true,
|
|
16
|
+
"resolveJsonModule": true,
|
|
17
|
+
"declaration": true,
|
|
18
|
+
"declarationMap": true,
|
|
19
|
+
"sourceMap": true,
|
|
20
|
+
"outDir": "./dist"
|
|
21
|
+
},
|
|
22
|
+
"include": [
|
|
23
|
+
"*.ts",
|
|
24
|
+
"src/**/*.ts",
|
|
25
|
+
"test/**/*.ts",
|
|
26
|
+
"assemblyscript.d.ts",
|
|
27
|
+
"**/*.as.d.ts"
|
|
28
|
+
],
|
|
29
|
+
"exclude": [
|
|
30
|
+
"node_modules",
|
|
31
|
+
"dist"
|
|
32
|
+
]
|
|
33
|
+
}
|