@ebowwa/sandbox 0.1.1
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/dist/compilers/index.d.ts +24 -0
- package/dist/compilers/index.d.ts.map +1 -0
- package/dist/compilers/index.js +42 -0
- package/dist/compilers/index.js.map +1 -0
- package/dist/compilers/javascript.d.ts +117 -0
- package/dist/compilers/javascript.d.ts.map +1 -0
- package/dist/compilers/javascript.js +462 -0
- package/dist/compilers/javascript.js.map +1 -0
- package/dist/compilers/python.d.ts +140 -0
- package/dist/compilers/python.d.ts.map +1 -0
- package/dist/compilers/python.js +650 -0
- package/dist/compilers/python.js.map +1 -0
- package/dist/compilers/typescript.d.ts +99 -0
- package/dist/compilers/typescript.d.ts.map +1 -0
- package/dist/compilers/typescript.js +323 -0
- package/dist/compilers/typescript.js.map +1 -0
- package/dist/core/cell.d.ts +160 -0
- package/dist/core/cell.d.ts.map +1 -0
- package/dist/core/cell.js +319 -0
- package/dist/core/cell.js.map +1 -0
- package/dist/core/compiler.d.ts +126 -0
- package/dist/core/compiler.d.ts.map +1 -0
- package/dist/core/compiler.js +123 -0
- package/dist/core/compiler.js.map +1 -0
- package/dist/core/index.d.ts +19 -0
- package/dist/core/index.d.ts.map +1 -0
- package/dist/core/index.js +14 -0
- package/dist/core/index.js.map +1 -0
- package/dist/core/limits.d.ts +173 -0
- package/dist/core/limits.d.ts.map +1 -0
- package/dist/core/limits.js +440 -0
- package/dist/core/limits.js.map +1 -0
- package/dist/core/permissions.d.ts +103 -0
- package/dist/core/permissions.d.ts.map +1 -0
- package/dist/core/permissions.js +341 -0
- package/dist/core/permissions.js.map +1 -0
- package/dist/core/runtime.d.ts +127 -0
- package/dist/core/runtime.d.ts.map +1 -0
- package/dist/core/runtime.js +325 -0
- package/dist/core/runtime.js.map +1 -0
- package/dist/core/types.d.ts +380 -0
- package/dist/core/types.d.ts.map +1 -0
- package/dist/core/types.js +67 -0
- package/dist/core/types.js.map +1 -0
- package/dist/index.d.ts +145 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +279 -0
- package/dist/index.js.map +1 -0
- package/dist/multi/index.d.ts +9 -0
- package/dist/multi/index.d.ts.map +1 -0
- package/dist/multi/index.js +7 -0
- package/dist/multi/index.js.map +1 -0
- package/dist/multi/polyglot.d.ts +179 -0
- package/dist/multi/polyglot.d.ts.map +1 -0
- package/dist/multi/polyglot.js +319 -0
- package/dist/multi/polyglot.js.map +1 -0
- package/dist/runtimes/docker.d.ts +97 -0
- package/dist/runtimes/docker.d.ts.map +1 -0
- package/dist/runtimes/docker.js +368 -0
- package/dist/runtimes/docker.js.map +1 -0
- package/dist/runtimes/index.d.ts +11 -0
- package/dist/runtimes/index.d.ts.map +1 -0
- package/dist/runtimes/index.js +9 -0
- package/dist/runtimes/index.js.map +1 -0
- package/dist/runtimes/process.d.ts +47 -0
- package/dist/runtimes/process.d.ts.map +1 -0
- package/dist/runtimes/process.js +230 -0
- package/dist/runtimes/process.js.map +1 -0
- package/dist/session/index.d.ts +12 -0
- package/dist/session/index.d.ts.map +1 -0
- package/dist/session/index.js +9 -0
- package/dist/session/index.js.map +1 -0
- package/dist/session/kernel.d.ts +199 -0
- package/dist/session/kernel.d.ts.map +1 -0
- package/dist/session/kernel.js +400 -0
- package/dist/session/kernel.js.map +1 -0
- package/dist/session/notebook.d.ts +168 -0
- package/dist/session/notebook.d.ts.map +1 -0
- package/dist/session/notebook.js +499 -0
- package/dist/session/notebook.js.map +1 -0
- package/dist/session/repl.d.ts +159 -0
- package/dist/session/repl.d.ts.map +1 -0
- package/dist/session/repl.js +409 -0
- package/dist/session/repl.js.map +1 -0
- package/package.json +142 -0
- package/src/compilers/index.ts +80 -0
- package/src/compilers/javascript.ts +571 -0
- package/src/compilers/python.ts +785 -0
- package/src/compilers/typescript.ts +442 -0
- package/src/core/cell.ts +439 -0
- package/src/core/compiler.ts +250 -0
- package/src/core/index.ts +123 -0
- package/src/core/limits.ts +508 -0
- package/src/core/permissions.ts +409 -0
- package/src/core/runtime.ts +499 -0
- package/src/core/types.ts +528 -0
- package/src/global.d.ts +59 -0
- package/src/index.ts +515 -0
- package/src/multi/index.ts +22 -0
- package/src/multi/polyglot.ts +461 -0
- package/src/runtimes/docker.ts +501 -0
- package/src/runtimes/index.ts +21 -0
- package/src/runtimes/process.ts +316 -0
- package/src/session/index.ts +41 -0
- package/src/session/kernel.ts +553 -0
- package/src/session/notebook.ts +635 -0
- package/src/session/repl.ts +521 -0
- package/src/wasm2wasm.d.ts +35 -0
package/src/core/cell.ts
ADDED
|
@@ -0,0 +1,439 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Cell Primitive
|
|
3
|
+
*
|
|
4
|
+
* Represents a single unit of executable code.
|
|
5
|
+
* Cells can be combined into notebooks for sequential execution.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import type {
|
|
9
|
+
Cell,
|
|
10
|
+
Language,
|
|
11
|
+
ExecutionResult,
|
|
12
|
+
ExecutionOptions,
|
|
13
|
+
ExecutionContext,
|
|
14
|
+
Permissions,
|
|
15
|
+
Limits,
|
|
16
|
+
DisplayOutput,
|
|
17
|
+
} from "./types.js";
|
|
18
|
+
import type { CompileResult } from "./compiler.js";
|
|
19
|
+
import { v4 as uuid } from "uuid";
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Cell metadata
|
|
23
|
+
*/
|
|
24
|
+
export interface CellMetadata {
|
|
25
|
+
/** Cell display order */
|
|
26
|
+
index?: number;
|
|
27
|
+
/** Cell is collapsed */
|
|
28
|
+
collapsed?: boolean;
|
|
29
|
+
/** Cell execution count */
|
|
30
|
+
executionCount?: number;
|
|
31
|
+
/** Tags for organization */
|
|
32
|
+
tags?: string[];
|
|
33
|
+
/** Custom metadata */
|
|
34
|
+
[key: string]: unknown;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Cell execution state
|
|
39
|
+
*/
|
|
40
|
+
export type CellState =
|
|
41
|
+
| "idle"
|
|
42
|
+
| "queued"
|
|
43
|
+
| "running"
|
|
44
|
+
| "success"
|
|
45
|
+
| "error"
|
|
46
|
+
| "cancelled";
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Extended cell with execution tracking
|
|
50
|
+
*/
|
|
51
|
+
export interface ExecutableCell extends Cell {
|
|
52
|
+
/** Current state */
|
|
53
|
+
state: CellState;
|
|
54
|
+
/** Last execution result */
|
|
55
|
+
result?: ExecutionResult;
|
|
56
|
+
/** Execution count (notebook-style) */
|
|
57
|
+
executionCount?: number;
|
|
58
|
+
/** Last execution timestamp */
|
|
59
|
+
lastRun?: Date;
|
|
60
|
+
/** Dependencies on other cells */
|
|
61
|
+
dependencies?: string[];
|
|
62
|
+
/** Exports from this cell */
|
|
63
|
+
exports?: string[];
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Cell Builder
|
|
68
|
+
*
|
|
69
|
+
* Fluent API for creating cells.
|
|
70
|
+
*/
|
|
71
|
+
export class CellBuilder {
|
|
72
|
+
private id = uuid();
|
|
73
|
+
private language: Language = "javascript";
|
|
74
|
+
private source = "";
|
|
75
|
+
private metadata: CellMetadata = {};
|
|
76
|
+
|
|
77
|
+
setId(id: string): this {
|
|
78
|
+
this.id = id;
|
|
79
|
+
return this;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
setLanguage(lang: Language): this {
|
|
83
|
+
this.language = lang;
|
|
84
|
+
return this;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
setSource(code: string): this {
|
|
88
|
+
this.source = code;
|
|
89
|
+
return this;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
addMetadata(key: string, value: unknown): this {
|
|
93
|
+
this.metadata[key] = value;
|
|
94
|
+
return this;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
setIndex(index: number): this {
|
|
98
|
+
this.metadata.index = index;
|
|
99
|
+
return this;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
addTag(tag: string): this {
|
|
103
|
+
this.metadata.tags = [...(this.metadata.tags ?? []), tag];
|
|
104
|
+
return this;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
build(): Cell {
|
|
108
|
+
return {
|
|
109
|
+
id: this.id,
|
|
110
|
+
language: this.language,
|
|
111
|
+
source: this.source,
|
|
112
|
+
metadata: Object.keys(this.metadata).length > 0 ? this.metadata : undefined,
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
buildExecutable(): ExecutableCell {
|
|
117
|
+
return {
|
|
118
|
+
...this.build(),
|
|
119
|
+
state: "idle",
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* Cell Executor
|
|
126
|
+
*
|
|
127
|
+
* Handles execution of individual cells.
|
|
128
|
+
*/
|
|
129
|
+
export class CellExecutor {
|
|
130
|
+
constructor(
|
|
131
|
+
private compileFn: (cell: Cell) => Promise<CompileResult>,
|
|
132
|
+
private executeFn: (
|
|
133
|
+
compiled: CompileResult,
|
|
134
|
+
context: ExecutionContext
|
|
135
|
+
) => Promise<ExecutionResult>
|
|
136
|
+
) {}
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* Execute a cell
|
|
140
|
+
*/
|
|
141
|
+
async execute(
|
|
142
|
+
cell: ExecutableCell,
|
|
143
|
+
context: ExecutionContext
|
|
144
|
+
): Promise<ExecutionResult> {
|
|
145
|
+
cell.state = "running";
|
|
146
|
+
cell.lastRun = new Date();
|
|
147
|
+
|
|
148
|
+
try {
|
|
149
|
+
// Compile the cell
|
|
150
|
+
const compiled = await this.compileFn(cell);
|
|
151
|
+
|
|
152
|
+
// Execute
|
|
153
|
+
const result = await this.executeFn(compiled, context);
|
|
154
|
+
|
|
155
|
+
// Update cell state
|
|
156
|
+
cell.state = result.success ? "success" : "error";
|
|
157
|
+
cell.result = result;
|
|
158
|
+
cell.executionCount = (cell.executionCount ?? 0) + 1;
|
|
159
|
+
|
|
160
|
+
return result;
|
|
161
|
+
} catch (error) {
|
|
162
|
+
const result: ExecutionResult = {
|
|
163
|
+
success: false,
|
|
164
|
+
error: {
|
|
165
|
+
message: error instanceof Error ? error.message : String(error),
|
|
166
|
+
type: "runtime",
|
|
167
|
+
},
|
|
168
|
+
metrics: {
|
|
169
|
+
duration: 0,
|
|
170
|
+
memoryUsed: 0,
|
|
171
|
+
},
|
|
172
|
+
output: { stdout: [], stderr: [], displays: [] },
|
|
173
|
+
};
|
|
174
|
+
|
|
175
|
+
cell.state = "error";
|
|
176
|
+
cell.result = result;
|
|
177
|
+
|
|
178
|
+
return result;
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
/**
|
|
184
|
+
* Cell Registry
|
|
185
|
+
*
|
|
186
|
+
* Tracks cells and their relationships.
|
|
187
|
+
*/
|
|
188
|
+
export class CellRegistry {
|
|
189
|
+
private cells = new Map<string, ExecutableCell>();
|
|
190
|
+
private dependencies = new Map<string, Set<string>>();
|
|
191
|
+
private dependents = new Map<string, Set<string>>();
|
|
192
|
+
|
|
193
|
+
/**
|
|
194
|
+
* Add a cell
|
|
195
|
+
*/
|
|
196
|
+
add(cell: ExecutableCell): void {
|
|
197
|
+
this.cells.set(cell.id, cell);
|
|
198
|
+
|
|
199
|
+
// Track dependencies
|
|
200
|
+
if (cell.dependencies) {
|
|
201
|
+
for (const depId of cell.dependencies) {
|
|
202
|
+
this.addDependency(cell.id, depId);
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
/**
|
|
208
|
+
* Get a cell by ID
|
|
209
|
+
*/
|
|
210
|
+
get(id: string): ExecutableCell | undefined {
|
|
211
|
+
return this.cells.get(id);
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
/**
|
|
215
|
+
* Remove a cell
|
|
216
|
+
*/
|
|
217
|
+
remove(id: string): void {
|
|
218
|
+
const cell = this.cells.get(id);
|
|
219
|
+
if (!cell) return;
|
|
220
|
+
|
|
221
|
+
// Clean up dependencies
|
|
222
|
+
const deps = this.dependencies.get(id);
|
|
223
|
+
if (deps) {
|
|
224
|
+
for (const depId of deps) {
|
|
225
|
+
this.dependents.get(depId)?.delete(id);
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
// Clean up dependents
|
|
230
|
+
const dependents = this.dependents.get(id);
|
|
231
|
+
if (dependents) {
|
|
232
|
+
for (const dependentId of dependents) {
|
|
233
|
+
this.dependencies.get(dependentId)?.delete(id);
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
this.cells.delete(id);
|
|
238
|
+
this.dependencies.delete(id);
|
|
239
|
+
this.dependents.delete(id);
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
/**
|
|
243
|
+
* Get all cells
|
|
244
|
+
*/
|
|
245
|
+
getAll(): ExecutableCell[] {
|
|
246
|
+
return Array.from(this.cells.values());
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
/**
|
|
250
|
+
* Get cells by language
|
|
251
|
+
*/
|
|
252
|
+
getByLanguage(language: Language): ExecutableCell[] {
|
|
253
|
+
return this.getAll().filter(c => c.language === language);
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
/**
|
|
257
|
+
* Get cells by state
|
|
258
|
+
*/
|
|
259
|
+
getByState(state: CellState): ExecutableCell[] {
|
|
260
|
+
return this.getAll().filter(c => c.state === state);
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
/**
|
|
264
|
+
* Add dependency between cells
|
|
265
|
+
*/
|
|
266
|
+
addDependency(cellId: string, dependsOn: string): void {
|
|
267
|
+
if (!this.dependencies.has(cellId)) {
|
|
268
|
+
this.dependencies.set(cellId, new Set());
|
|
269
|
+
}
|
|
270
|
+
this.dependencies.get(cellId)!.add(dependsOn);
|
|
271
|
+
|
|
272
|
+
if (!this.dependents.has(dependsOn)) {
|
|
273
|
+
this.dependents.set(dependsOn, new Set());
|
|
274
|
+
}
|
|
275
|
+
this.dependents.get(dependsOn)!.add(cellId);
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
/**
|
|
279
|
+
* Get dependencies of a cell
|
|
280
|
+
*/
|
|
281
|
+
getDependencies(cellId: string): string[] {
|
|
282
|
+
return Array.from(this.dependencies.get(cellId) ?? []);
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
/**
|
|
286
|
+
* Get dependents of a cell (cells that depend on this one)
|
|
287
|
+
*/
|
|
288
|
+
getDependents(cellId: string): string[] {
|
|
289
|
+
return Array.from(this.dependents.get(cellId) ?? []);
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
/**
|
|
293
|
+
* Get execution order (topological sort)
|
|
294
|
+
*/
|
|
295
|
+
getExecutionOrder(): string[] {
|
|
296
|
+
const order: string[] = [];
|
|
297
|
+
const visited = new Set<string>();
|
|
298
|
+
const visiting = new Set<string>();
|
|
299
|
+
|
|
300
|
+
const visit = (id: string) => {
|
|
301
|
+
if (visited.has(id)) return;
|
|
302
|
+
if (visiting.has(id)) {
|
|
303
|
+
throw new Error(`Circular dependency detected involving cell: ${id}`);
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
visiting.add(id);
|
|
307
|
+
|
|
308
|
+
const deps = this.dependencies.get(id);
|
|
309
|
+
if (deps) {
|
|
310
|
+
for (const dep of deps) {
|
|
311
|
+
visit(dep);
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
visiting.delete(id);
|
|
316
|
+
visited.add(id);
|
|
317
|
+
order.push(id);
|
|
318
|
+
};
|
|
319
|
+
|
|
320
|
+
for (const id of this.cells.keys()) {
|
|
321
|
+
visit(id);
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
return order;
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
/**
|
|
328
|
+
* Check if cell has circular dependencies
|
|
329
|
+
*/
|
|
330
|
+
hasCircularDependency(cellId: string): boolean {
|
|
331
|
+
try {
|
|
332
|
+
this.getExecutionOrder();
|
|
333
|
+
return false;
|
|
334
|
+
} catch {
|
|
335
|
+
return true;
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
/**
|
|
340
|
+
* Get cells that need re-execution after a cell changes
|
|
341
|
+
*/
|
|
342
|
+
getCellsToReexecute(cellId: string): string[] {
|
|
343
|
+
const toReexecute: string[] = [];
|
|
344
|
+
const visited = new Set<string>();
|
|
345
|
+
|
|
346
|
+
const collect = (id: string) => {
|
|
347
|
+
if (visited.has(id)) return;
|
|
348
|
+
visited.add(id);
|
|
349
|
+
toReexecute.push(id);
|
|
350
|
+
|
|
351
|
+
const dependents = this.dependents.get(id);
|
|
352
|
+
if (dependents) {
|
|
353
|
+
for (const dependent of dependents) {
|
|
354
|
+
collect(dependent);
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
};
|
|
358
|
+
|
|
359
|
+
collect(cellId);
|
|
360
|
+
return toReexecute.slice(1); // Exclude the original cell
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
/**
|
|
364
|
+
* Clear all cells
|
|
365
|
+
*/
|
|
366
|
+
clear(): void {
|
|
367
|
+
this.cells.clear();
|
|
368
|
+
this.dependencies.clear();
|
|
369
|
+
this.dependents.clear();
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
/**
|
|
373
|
+
* Get count
|
|
374
|
+
*/
|
|
375
|
+
get count(): number {
|
|
376
|
+
return this.cells.size;
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
/**
|
|
381
|
+
* Create a simple cell
|
|
382
|
+
*/
|
|
383
|
+
export function createCell(
|
|
384
|
+
language: Language,
|
|
385
|
+
source: string,
|
|
386
|
+
options?: {
|
|
387
|
+
id?: string;
|
|
388
|
+
metadata?: CellMetadata;
|
|
389
|
+
dependencies?: string[];
|
|
390
|
+
}
|
|
391
|
+
): ExecutableCell {
|
|
392
|
+
return {
|
|
393
|
+
id: options?.id ?? uuid(),
|
|
394
|
+
language,
|
|
395
|
+
source,
|
|
396
|
+
metadata: options?.metadata,
|
|
397
|
+
dependencies: options?.dependencies,
|
|
398
|
+
state: "idle",
|
|
399
|
+
};
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
/**
|
|
403
|
+
* Create cells from code blocks
|
|
404
|
+
*/
|
|
405
|
+
export function createCellsFromMarkdown(
|
|
406
|
+
markdown: string,
|
|
407
|
+
defaultLanguage: Language = "javascript"
|
|
408
|
+
): ExecutableCell[] {
|
|
409
|
+
const cells: ExecutableCell[] = [];
|
|
410
|
+
const codeBlockRegex = /```(\w+)?\n([\s\S]*?)```/g;
|
|
411
|
+
let match;
|
|
412
|
+
let index = 0;
|
|
413
|
+
|
|
414
|
+
while ((match = codeBlockRegex.exec(markdown)) !== null) {
|
|
415
|
+
const lang = (match[1] as Language) ?? defaultLanguage;
|
|
416
|
+
const source = match[2].trim();
|
|
417
|
+
|
|
418
|
+
cells.push(
|
|
419
|
+
createCell(lang, source, {
|
|
420
|
+
metadata: { index: index++ },
|
|
421
|
+
})
|
|
422
|
+
);
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
return cells;
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
/**
|
|
429
|
+
* Convert cells to markdown
|
|
430
|
+
*/
|
|
431
|
+
export function cellsToMarkdown(cells: ExecutableCell[]): string {
|
|
432
|
+
return cells
|
|
433
|
+
.sort((a, b) => ((a.metadata?.index as number) ?? 0) - ((b.metadata?.index as number) ?? 0))
|
|
434
|
+
.map(cell => {
|
|
435
|
+
const lang = cell.language === "javascript" ? "js" : cell.language;
|
|
436
|
+
return `\`\`\`${lang}\n${cell.source}\n\`\`\``;
|
|
437
|
+
})
|
|
438
|
+
.join("\n\n");
|
|
439
|
+
}
|
|
@@ -0,0 +1,250 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Compiler Primitive
|
|
3
|
+
*
|
|
4
|
+
* Transforms source code to executable format (typically WASM).
|
|
5
|
+
* Composable: mix and match compilers for different languages.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import type { Language, Permissions, Limits } from "./types.js";
|
|
9
|
+
|
|
10
|
+
/** Compiler result */
|
|
11
|
+
export interface CompileResult {
|
|
12
|
+
/** Compiled WASM bytes */
|
|
13
|
+
wasmBytes: Uint8Array;
|
|
14
|
+
/** Original source code (for interpreted languages) */
|
|
15
|
+
source?: string;
|
|
16
|
+
/** Source map for debugging */
|
|
17
|
+
sourceMap?: string;
|
|
18
|
+
/** Exported functions */
|
|
19
|
+
exports: string[];
|
|
20
|
+
/** Imported functions needed */
|
|
21
|
+
imports: ImportDefinition[];
|
|
22
|
+
/** Memory requirements */
|
|
23
|
+
memoryRequirements?: {
|
|
24
|
+
initial: number; // Pages
|
|
25
|
+
maximum?: number; // Pages
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/** Import definition */
|
|
30
|
+
export interface ImportDefinition {
|
|
31
|
+
module: string;
|
|
32
|
+
name: string;
|
|
33
|
+
type: "function" | "memory" | "global" | "table";
|
|
34
|
+
signature?: string;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/** Compiler options */
|
|
38
|
+
export interface CompilerOptions {
|
|
39
|
+
/** Optimize output */
|
|
40
|
+
optimize?: boolean;
|
|
41
|
+
/** Debug info */
|
|
42
|
+
debug?: boolean;
|
|
43
|
+
/** Source map */
|
|
44
|
+
sourceMap?: boolean;
|
|
45
|
+
/** Target WASM features */
|
|
46
|
+
features?: {
|
|
47
|
+
simd?: boolean;
|
|
48
|
+
threads?: boolean;
|
|
49
|
+
referenceTypes?: boolean;
|
|
50
|
+
bulkMemory?: boolean;
|
|
51
|
+
};
|
|
52
|
+
/** Additional compiler-specific options */
|
|
53
|
+
extra?: Record<string, unknown>;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/** Base compiler interface */
|
|
57
|
+
export interface ICompiler {
|
|
58
|
+
/** Language this compiler handles */
|
|
59
|
+
readonly language: Language;
|
|
60
|
+
|
|
61
|
+
/** Check if this compiler is available */
|
|
62
|
+
isAvailable(): Promise<boolean>;
|
|
63
|
+
|
|
64
|
+
/** Check if code syntax is valid */
|
|
65
|
+
validate(source: string): Promise<{ valid: boolean; error?: string }>;
|
|
66
|
+
|
|
67
|
+
/** Compile source to WASM */
|
|
68
|
+
compile(
|
|
69
|
+
source: string,
|
|
70
|
+
permissions: Permissions,
|
|
71
|
+
limits: Limits,
|
|
72
|
+
options?: CompilerOptions
|
|
73
|
+
): Promise<CompileResult>;
|
|
74
|
+
|
|
75
|
+
/** Get language capabilities */
|
|
76
|
+
getCapabilities(): LanguageCapabilities;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/** Language capabilities */
|
|
80
|
+
export interface LanguageCapabilities {
|
|
81
|
+
/** Supports REPL-style execution */
|
|
82
|
+
repl: boolean;
|
|
83
|
+
/** Can maintain state between executions */
|
|
84
|
+
stateful: boolean;
|
|
85
|
+
/** Requires compilation step */
|
|
86
|
+
compiled: boolean;
|
|
87
|
+
/** Has garbage collection */
|
|
88
|
+
gc: boolean;
|
|
89
|
+
/** Can import external modules */
|
|
90
|
+
imports: boolean;
|
|
91
|
+
/** Supports async/await */
|
|
92
|
+
async: boolean;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Base Compiler class
|
|
97
|
+
*
|
|
98
|
+
* Extend this to create language-specific compilers.
|
|
99
|
+
*/
|
|
100
|
+
export abstract class BaseCompiler implements ICompiler {
|
|
101
|
+
abstract readonly language: Language;
|
|
102
|
+
|
|
103
|
+
async isAvailable(): Promise<boolean> {
|
|
104
|
+
return true; // Override if compiler needs external tools
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
async validate(_source: string): Promise<{ valid: boolean; error?: string }> {
|
|
108
|
+
return { valid: true }; // Override for language-specific validation
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
abstract compile(
|
|
112
|
+
source: string,
|
|
113
|
+
permissions: Permissions,
|
|
114
|
+
limits: Limits,
|
|
115
|
+
options?: CompilerOptions
|
|
116
|
+
): Promise<CompileResult>;
|
|
117
|
+
|
|
118
|
+
getCapabilities(): LanguageCapabilities {
|
|
119
|
+
return {
|
|
120
|
+
repl: false,
|
|
121
|
+
stateful: false,
|
|
122
|
+
compiled: true,
|
|
123
|
+
gc: false,
|
|
124
|
+
imports: false,
|
|
125
|
+
async: false,
|
|
126
|
+
};
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* WASM Passthrough Compiler
|
|
132
|
+
*
|
|
133
|
+
* Treats already-compiled WASM as "compiled".
|
|
134
|
+
* Used when source is already WASM bytes.
|
|
135
|
+
*/
|
|
136
|
+
export class WasmCompiler extends BaseCompiler {
|
|
137
|
+
readonly language = "wasm" as const;
|
|
138
|
+
|
|
139
|
+
async validate(source: string | Uint8Array): Promise<{ valid: boolean; error?: string }> {
|
|
140
|
+
try {
|
|
141
|
+
const bytes = typeof source === "string"
|
|
142
|
+
? new TextEncoder().encode(source)
|
|
143
|
+
: source;
|
|
144
|
+
|
|
145
|
+
// Check WASM magic number
|
|
146
|
+
if (bytes[0] !== 0x00 || bytes[1] !== 0x61 || bytes[2] !== 0x73 || bytes[3] !== 0x6D) {
|
|
147
|
+
return { valid: false, error: "Invalid WASM magic number" };
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
// Use WebAssembly.validate
|
|
151
|
+
const valid = WebAssembly.validate(bytes);
|
|
152
|
+
return { valid };
|
|
153
|
+
} catch (error) {
|
|
154
|
+
return { valid: false, error: String(error) };
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
async compile(
|
|
159
|
+
source: string | Uint8Array,
|
|
160
|
+
_permissions: Permissions,
|
|
161
|
+
_limits: Limits,
|
|
162
|
+
_options?: CompilerOptions
|
|
163
|
+
): Promise<CompileResult> {
|
|
164
|
+
const wasmBytes = typeof source === "string"
|
|
165
|
+
? new TextEncoder().encode(source)
|
|
166
|
+
: source;
|
|
167
|
+
|
|
168
|
+
// Extract exports from module
|
|
169
|
+
const module = await WebAssembly.compile(wasmBytes);
|
|
170
|
+
const exports = WebAssembly.Module.exports(module).map(e => e.name);
|
|
171
|
+
const imports = WebAssembly.Module.imports(module).map(i => ({
|
|
172
|
+
module: i.module,
|
|
173
|
+
name: i.name,
|
|
174
|
+
type: i.kind as "function" | "memory" | "global" | "table",
|
|
175
|
+
}));
|
|
176
|
+
|
|
177
|
+
return {
|
|
178
|
+
wasmBytes,
|
|
179
|
+
exports,
|
|
180
|
+
imports,
|
|
181
|
+
};
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
getCapabilities(): LanguageCapabilities {
|
|
185
|
+
return {
|
|
186
|
+
repl: false,
|
|
187
|
+
stateful: true,
|
|
188
|
+
compiled: true,
|
|
189
|
+
gc: false,
|
|
190
|
+
imports: true,
|
|
191
|
+
async: true,
|
|
192
|
+
};
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
/**
|
|
197
|
+
* Compiler Registry
|
|
198
|
+
*
|
|
199
|
+
* Manages available compilers and routes code to the right one.
|
|
200
|
+
*/
|
|
201
|
+
export class CompilerRegistry {
|
|
202
|
+
private compilers = new Map<Language, ICompiler>();
|
|
203
|
+
|
|
204
|
+
register(compiler: ICompiler): void {
|
|
205
|
+
this.compilers.set(compiler.language, compiler);
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
get(language: Language): ICompiler | undefined {
|
|
209
|
+
return this.compilers.get(language);
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
has(language: Language): boolean {
|
|
213
|
+
return this.compilers.has(language);
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
async getAvailable(): Promise<Language[]> {
|
|
217
|
+
const available: Language[] = [];
|
|
218
|
+
for (const [lang, compiler] of this.compilers) {
|
|
219
|
+
if (await compiler.isAvailable()) {
|
|
220
|
+
available.push(lang);
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
return available;
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
async compile(
|
|
227
|
+
source: string | Uint8Array,
|
|
228
|
+
language: Language,
|
|
229
|
+
permissions: Permissions,
|
|
230
|
+
limits: Limits,
|
|
231
|
+
options?: CompilerOptions
|
|
232
|
+
): Promise<CompileResult> {
|
|
233
|
+
const compiler = this.compilers.get(language);
|
|
234
|
+
if (!compiler) {
|
|
235
|
+
throw new Error(`No compiler registered for language: ${language}`);
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
if (!(await compiler.isAvailable())) {
|
|
239
|
+
throw new Error(`Compiler for ${language} is not available`);
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
return (compiler as unknown as {
|
|
243
|
+
compile: (src: string | Uint8Array, perms: Permissions, lim: Limits, opts?: CompilerOptions) => Promise<CompileResult>;
|
|
244
|
+
}).compile(source, permissions, limits, options);
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
// Default registry with built-in compilers
|
|
249
|
+
export const defaultCompilerRegistry = new CompilerRegistry();
|
|
250
|
+
defaultCompilerRegistry.register(new WasmCompiler());
|