|
1 |
| -import type { Notebook } from "@observablehq/notebook-kit"; |
2 |
| -import { define, type DefineState, type Definition } from "@observablehq/notebook-kit/runtime"; |
| 1 | +import { Identifier } from "acorn"; |
| 2 | +import { FileAttachments } from "@observablehq/stdlib"; |
| 3 | +import { type Notebook, type Cell, parseJavaScript, transpile, type TranspiledJavaScript } from "@observablehq/notebook-kit"; |
| 4 | +import { type DefineState, type Definition } from "@observablehq/notebook-kit/runtime"; |
3 | 5 | import type { Inspector } from "@observablehq/inspector";
|
4 | 6 | import type { Runtime, Module } from "@observablehq/runtime";
|
| 7 | +import { define } from "@observablehq/notebook-kit/runtime"; |
| 8 | +import { fixRelativeUrl } from "../util/paths.ts"; |
| 9 | +import { fetchEx } from "../util/comms.ts"; |
| 10 | +import { html2notebook } from "./util.ts"; |
5 | 11 |
|
6 | 12 | export type InspectorFactory = (name: string | undefined, id: string | number) => Inspector;
|
7 | 13 |
|
8 |
| -class RuntimeWrapper { |
9 |
| - protected stateById = new Map<string, DefineState>(); |
| 14 | +const FunctionConstructors = { |
| 15 | + regular: Object.getPrototypeOf(function () { }).constructor, |
| 16 | + async: Object.getPrototypeOf(async function () { }).constructor, |
| 17 | + generator: Object.getPrototypeOf(function* () { }).constructor, |
| 18 | + asyncGenerator: Object.getPrototypeOf(async function* () { }).constructor, |
| 19 | +} as const; |
10 | 20 |
|
11 |
| - constructor() { |
| 21 | +type FunctionConstructor = |
| 22 | + | typeof FunctionConstructors.regular |
| 23 | + | typeof FunctionConstructors.async |
| 24 | + | typeof FunctionConstructors.generator |
| 25 | + | typeof FunctionConstructors.asyncGenerator; |
12 | 26 |
|
| 27 | +type FunctionLikeExpression = { |
| 28 | + type: "FunctionExpression" | "ArrowFunctionExpression"; |
| 29 | + body: { type: "BlockStatement" | "Expression"; start: number; end: number }; |
| 30 | + async?: boolean; |
| 31 | + generator?: boolean; |
| 32 | + params?: Identifier[]; |
| 33 | +}; |
| 34 | + |
| 35 | +function constructFunction(input: string) { |
| 36 | + const { body } = parseJavaScript(input) as { body: FunctionLikeExpression }; |
| 37 | + if (body.type !== "FunctionExpression" && body.type !== "ArrowFunctionExpression") { |
| 38 | + throw new Error(`Unsupported function type: ${body.type}`); |
13 | 39 | }
|
14 | 40 |
|
15 |
| - add(vscodeCellID: string, definition: Definition, placeholderDiv: HTMLDivElement): void { |
16 |
| - let state = this.stateById.get(vscodeCellID); |
| 41 | + const Constructor = (body.async && body.generator) ? |
| 42 | + FunctionConstructors.asyncGenerator : |
| 43 | + body.async ? |
| 44 | + FunctionConstructors.async : |
| 45 | + body.generator ? |
| 46 | + FunctionConstructors.generator : |
| 47 | + FunctionConstructors.regular; |
| 48 | + |
| 49 | + const params = body.params?.map(param => input.substring(param.start, param.end)).join(", ") || ""; |
| 50 | + const bodyStr = body.body.type === "BlockStatement" ? input.substring(body.body.start, body.body.end).slice(1, -1).trim() : input.substring(body.body.start, body.body.end); |
| 51 | + return Constructor(params, bodyStr); |
| 52 | +} |
| 53 | +interface CellEx extends Cell { |
| 54 | + transpiled: TranspiledJavaScript; |
| 55 | +} |
| 56 | +function transpileCell(cell: Cell): CellEx { |
| 57 | + const retVal: CellEx = { |
| 58 | + ...cell, |
| 59 | + transpiled: transpile(cell.value, cell.mode) |
| 60 | + }; |
| 61 | + retVal.transpiled.body = constructFunction(retVal.transpiled.body); |
| 62 | + return retVal; |
| 63 | +} |
| 64 | +export interface CompileOptions { |
| 65 | + baseUrl?: string; |
| 66 | + importMode?: "recursive" | "precompiled"; |
| 67 | +} |
| 68 | +export function notebook(_cells: CellEx[] = [], { baseUrl = ".", importMode = "precompiled" }: CompileOptions = {}) { |
| 69 | + const stateById = new Map<number, DefineState>(); |
| 70 | + |
| 71 | + function add(vscodeCellID: number, definition: Definition, inspector: Inspector): void { |
| 72 | + let state = stateById.get(vscodeCellID); |
17 | 73 | if (state) {
|
18 | 74 | state.variables.forEach((v) => v.delete());
|
19 | 75 | state.variables = [];
|
20 | 76 | } else {
|
21 |
| - state = { root: placeholderDiv, expanded: [], variables: [] }; |
22 |
| - this.stateById.set(vscodeCellID, state); |
| 77 | + state = { root: inspector._node, expanded: [], variables: [] }; |
| 78 | + stateById.set(vscodeCellID, state); |
23 | 79 | }
|
24 | 80 | define(state, definition);
|
25 | 81 | }
|
26 | 82 |
|
27 |
| - remove(vscodeCellID: string): void { |
28 |
| - const state = this.stateById.get(vscodeCellID)!; |
| 83 | + function remove(vscodeCellID: number): void { |
| 84 | + const state = stateById.get(vscodeCellID)!; |
29 | 85 | state.root.remove();
|
30 | 86 | state.variables.forEach((v) => v.delete());
|
31 |
| - this.stateById.delete(vscodeCellID); |
| 87 | + stateById.delete(vscodeCellID); |
32 | 88 | }
|
33 | 89 |
|
34 |
| - removeAll(): void { |
35 |
| - const keys = Array.from(this.stateById.keys()); |
| 90 | + function removeAll(): void { |
| 91 | + const keys = Array.from(stateById.keys()); |
36 | 92 | for (const key of keys) {
|
37 |
| - this.remove(key); |
| 93 | + remove(key); |
38 | 94 | }
|
39 | 95 | }
|
40 | 96 |
|
41 |
| -} |
42 |
| - |
43 |
| -// File --- |
44 |
| -export function compile(notebook: Notebook) { |
45 |
| - const retVal = (runtime: Runtime, inspector?: InspectorFactory): Module => { |
46 |
| - const main: Module = runtime.module(); |
| 97 | + const files: any[] = []; |
| 98 | + const fileAttachmentsMap = new Map<string, any>(files); |
| 99 | + const cells = new Map<number, CellEx>(_cells.map(c => [c.id, c])); |
47 | 100 |
|
48 |
| - notebook.cells.forEach(cell => { |
49 |
| - main.define(cell.id, cell.value); |
| 101 | + const retVal = (runtime: Runtime, inspectorFactory: InspectorFactory): Module => { |
| 102 | + runtime.main.builtin("fetchEx", fetchEx); |
| 103 | + cells.forEach(cell => { |
| 104 | + const inspector = inspectorFactory(`cell-${cell.id}`, cell.id); |
| 105 | + add(cell.id, { id: cell.id, ...cell.transpiled }, inspector); |
50 | 106 | });
|
51 |
| - return main; |
| 107 | + return runtime.main; |
52 | 108 | };
|
| 109 | + retVal.fileAttachments = fileAttachmentsMap; |
| 110 | + retVal.cells = cells; |
| 111 | + // retVal.set = async (n: Cell): Promise<JavaScriptCell> => { |
| 112 | + // const cell = await transpileCell(n, { baseUrl, importMode }); |
| 113 | + // retVal.delete(cell.id); |
| 114 | + // cells.set(cell.id, cell); |
| 115 | + // return cell; |
| 116 | + // }; |
| 117 | + // retVal.get = (id: string | number): JavaScriptCell | undefined => { |
| 118 | + // return cells.get(id); |
| 119 | + // }; |
| 120 | + // retVal.delete = (id: string | number): boolean => { |
| 121 | + // const cell = cells.get(id); |
| 122 | + // if (cell) { |
| 123 | + // cell.delete(); |
| 124 | + // return cells.delete(id); |
| 125 | + // } |
| 126 | + // return false; |
| 127 | + // }; |
| 128 | + // retVal.clear = () => { |
| 129 | + // cells.forEach(cell => cell.delete()); |
| 130 | + // cells.clear(); |
| 131 | + // }; |
| 132 | + // retVal.write = (w: Writer) => { |
| 133 | + // w.files(_files); |
| 134 | + // cells.forEach(cell => cell.write(w)); |
| 135 | + // }; |
| 136 | + // retVal.toString = (w = new Writer()) => { |
| 137 | + // retVal.write(w); |
| 138 | + // return w.toString().trim(); |
| 139 | + // }; |
53 | 140 | return retVal;
|
54 | 141 | }
|
| 142 | + |
| 143 | +export function compile(notebookOrOjs: Notebook | string, { baseUrl = ".", importMode = "precompiled" }: CompileOptions = {}) { |
| 144 | + const htmlNotebook = typeof notebookOrOjs === "string" ? html2notebook(notebookOrOjs) : notebookOrOjs; |
| 145 | + const _cells: CellEx[] = htmlNotebook.cells.map(n => transpileCell(n)); |
| 146 | + return notebook(_cells, { baseUrl, importMode }); |
| 147 | +} |
| 148 | +export type compileFunc = Awaited<ReturnType<typeof compile>>; |
0 commit comments