rooklang 0.3.1 → 0.3.3
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/README.md +17 -4
- package/README.ru.md +17 -4
- package/dist/ast/expressions.d.ts +16 -1
- package/dist/ast/markup.d.ts +6 -1
- package/dist/browser.d.ts +5 -0
- package/dist/browser.js +1 -0
- package/dist/checker/assignments.d.ts +5 -0
- package/dist/checker/assignments.js +34 -8
- package/dist/checker/base.d.ts +2 -1
- package/dist/checker/base.js +4 -10
- package/dist/checker/builtins.js +4 -35
- package/dist/checker/bytes.d.ts +2 -2
- package/dist/checker/bytes.js +2 -2
- package/dist/checker/channels.d.ts +3 -0
- package/dist/checker/channels.js +39 -1
- package/dist/checker/context.d.ts +6 -0
- package/dist/checker/control-flow.d.ts +1 -0
- package/dist/checker/control-flow.js +17 -0
- package/dist/checker/error-results.d.ts +6 -0
- package/dist/checker/error-results.js +31 -0
- package/dist/checker/expressions.d.ts +2 -0
- package/dist/checker/expressions.js +4 -0
- package/dist/checker/helpers.js +2 -0
- package/dist/checker/queues.d.ts +6 -0
- package/dist/checker/queues.js +24 -0
- package/dist/checker/resolution.js +6 -2
- package/dist/checker/statements.d.ts +5 -0
- package/dist/checker/statements.js +19 -0
- package/dist/checker/transfer.js +4 -1
- package/dist/checker/types/printing.js +7 -0
- package/dist/checker/types/queue.d.ts +6 -0
- package/dist/checker/types/queue.js +42 -0
- package/dist/checker/types/relations.js +12 -1
- package/dist/checker/types.d.ts +1 -0
- package/dist/checker/types.js +1 -0
- package/dist/checker/untyped-globals.d.ts +1 -0
- package/dist/checker/untyped-globals.js +47 -0
- package/dist/codegen/attributes.js +0 -1
- package/dist/codegen/calls.d.ts +9 -0
- package/dist/codegen/calls.js +41 -1
- package/dist/codegen/component-values.js +0 -1
- package/dist/codegen/components.js +0 -1
- package/dist/codegen/control-flow.js +0 -3
- package/dist/codegen/control-statements.d.ts +9 -1
- package/dist/codegen/control-statements.js +51 -7
- package/dist/codegen/creation.d.ts +6 -8
- package/dist/codegen/creation.js +1 -10
- package/dist/codegen/early-returns.js +0 -1
- package/dist/codegen/elements.js +0 -3
- package/dist/codegen/emitter.d.ts +3 -5
- package/dist/codegen/emitter.js +0 -2
- package/dist/codegen/expressions.d.ts +4 -1
- package/dist/codegen/expressions.js +9 -13
- package/dist/codegen/functions.js +0 -2
- package/dist/codegen/js.d.ts +0 -7
- package/dist/codegen/js.js +33 -59
- package/dist/codegen/parallel.js +2 -4
- package/dist/codegen/refs.js +0 -1
- package/dist/codegen/runtime.d.ts +13 -0
- package/dist/codegen/runtime.js +93 -0
- package/dist/codegen/simple-statements.js +3 -1
- package/dist/codegen/styles.d.ts +1 -1
- package/dist/codegen/styles.js +2 -2
- package/dist/codegen/web-components.js +0 -1
- package/dist/compile.d.ts +7 -0
- package/dist/compile.js +1 -0
- package/dist/completion.js +2 -1
- package/dist/index.d.ts +2 -0
- package/dist/index.js +1 -0
- package/dist/loader.js +19 -6
- package/dist/parser/base.d.ts +3 -1
- package/dist/parser/expressions.js +9 -2
- package/dist/parser/literals.d.ts +7 -0
- package/dist/parser/literals.js +45 -1
- package/dist/parser/simple-statements.js +3 -0
- package/dist/parser/types.d.ts +1 -1
- package/dist/parser/types.js +10 -3
- package/dist/runtime/sources.d.ts +2 -0
- package/dist/runtime/sources.js +14 -0
- package/dist/runtime-module.d.ts +1 -0
- package/dist/runtime-module.js +3 -0
- package/dist/ts-types.d.ts +4 -0
- package/dist/ts-types.js +25 -3
- package/dist/vite.js +7 -1
- package/dist/walk-types.js +1 -0
- package/dist/walk.js +9 -0
- package/package.json +6 -4
- package/src/runtime/chan.js +277 -0
- package/src/runtime/core.js +23 -0
- package/src/runtime/go.js +340 -0
- package/src/runtime/index.js +13 -0
- package/src/runtime/markup.js +336 -0
- package/src/runtime/queue.js +118 -0
- package/src/runtime/select.js +170 -0
- package/src/runtime/streams.js +61 -0
- package/src/runtime/threads.js +240 -0
- package/src/runtime/timers.js +27 -0
- package/dist/codegen/chan-helpers.d.ts +0 -2
- package/dist/codegen/chan-helpers.js +0 -253
- package/dist/codegen/go-helpers.d.ts +0 -9
- package/dist/codegen/go-helpers.js +0 -307
- package/dist/codegen/helpers.d.ts +0 -10
- package/dist/codegen/helpers.js +0 -356
- package/dist/codegen/select-helpers.d.ts +0 -2
- package/dist/codegen/select-helpers.js +0 -135
- package/dist/codegen/thread-helpers.d.ts +0 -2
- package/dist/codegen/thread-helpers.js +0 -198
|
@@ -1,307 +0,0 @@
|
|
|
1
|
-
// The helpers of `go`: the pool of workers of the program, and the side of the worker. They work
|
|
2
|
-
// with Web Workers and with the worker threads of Node, whose API differs: `on("message")` instead
|
|
3
|
-
// of `onmessage`, and `unref()`, so that idle workers do not keep the program running.
|
|
4
|
-
export const GO_HELPERS = {
|
|
5
|
-
/** `go` in a module: a call in a worker of the pool, started by $$startWorker of the module. */
|
|
6
|
-
go: [
|
|
7
|
-
'function $$go(name, args) {',
|
|
8
|
-
' return (globalThis[Symbol.for("rook.goroutines")] ??= $$pool()).call(',
|
|
9
|
-
' $$startWorker,',
|
|
10
|
-
' name,',
|
|
11
|
-
' $$copy(args, true),',
|
|
12
|
-
' );',
|
|
13
|
-
'}',
|
|
14
|
-
'',
|
|
15
|
-
'// The workers of the program, shared by its modules: up to one per processor. Each runs the worker',
|
|
16
|
-
'// module of one module, `start`. Calls wait in one queue and go to a worker of their module that is',
|
|
17
|
-
'// free: one whose thread is not running code, though its earlier calls may wait in `await`. A',
|
|
18
|
-
'// worker that is still busy keeps its calls for later; an idle worker of another module makes room',
|
|
19
|
-
'// for a new one, unless it keeps channels.',
|
|
20
|
-
'function $$pool() {',
|
|
21
|
-
' const size = globalThis.navigator?.hardwareConcurrency ?? 4;',
|
|
22
|
-
' const registry = $$registry();',
|
|
23
|
-
' const workers = [];',
|
|
24
|
-
' const queue = [];',
|
|
25
|
-
' const calls = new Map();',
|
|
26
|
-
' let ids = 0;',
|
|
27
|
-
' // A worker without calls lets the program end, unless something here waits for it.',
|
|
28
|
-
' const unref = (worker) => {',
|
|
29
|
-
' if (worker.calls === 0 && !(registry.pins.get(worker.key) > 0)) worker.thread.unref?.();',
|
|
30
|
-
' };',
|
|
31
|
-
' registry.idle = (key) => {',
|
|
32
|
-
' const worker = workers.find((each) => each.key === key);',
|
|
33
|
-
' if (worker) unref(worker);',
|
|
34
|
-
' };',
|
|
35
|
-
' const send = (worker, call) => {',
|
|
36
|
-
' worker.busy = true;',
|
|
37
|
-
' worker.calls++;',
|
|
38
|
-
' worker.thread.ref?.();',
|
|
39
|
-
' call.worker = worker;',
|
|
40
|
-
' calls.set(call.id, call);',
|
|
41
|
-
' $$post(worker.key, { call: call.id, name: call.name, args: call.args });',
|
|
42
|
-
' call.args = null;',
|
|
43
|
-
' };',
|
|
44
|
-
' const dispatch = () => {',
|
|
45
|
-
' const starting = new Map();',
|
|
46
|
-
' for (const worker of workers) {',
|
|
47
|
-
' if (!worker.key) starting.set(worker.start, (starting.get(worker.start) ?? 0) + 1);',
|
|
48
|
-
' }',
|
|
49
|
-
' for (let i = 0; i < queue.length; i++) {',
|
|
50
|
-
' const call = queue[i];',
|
|
51
|
-
' let free = null;',
|
|
52
|
-
' for (const worker of workers) {',
|
|
53
|
-
' if (worker.start !== call.start || !worker.key || worker.busy) continue;',
|
|
54
|
-
' if (!free || worker.calls < free.calls) free = worker;',
|
|
55
|
-
' }',
|
|
56
|
-
' // A free worker without calls; else a new worker; else one whose calls wait in await.',
|
|
57
|
-
' if (free?.calls === 0 || (free && workers.length >= size)) {',
|
|
58
|
-
' queue.splice(i--, 1);',
|
|
59
|
-
' send(free, call);',
|
|
60
|
-
' } else if (starting.get(call.start) > 0) {',
|
|
61
|
-
' starting.set(call.start, starting.get(call.start) - 1);',
|
|
62
|
-
' } else if (workers.length < size || retire(call.start)) {',
|
|
63
|
-
' add(call.start);',
|
|
64
|
-
' } else {',
|
|
65
|
-
' // It waits for a busy worker of its module, which may have let go of the program when',
|
|
66
|
-
' // its last call returned: the program runs on until that worker is free and takes it.',
|
|
67
|
-
' for (const worker of workers) if (worker.start === call.start && worker.key) worker.thread.ref?.();',
|
|
68
|
-
' }',
|
|
69
|
-
' }',
|
|
70
|
-
' };',
|
|
71
|
-
' const retire = (start) => {',
|
|
72
|
-
' const idle = workers.find(',
|
|
73
|
-
' (worker) => worker.key && worker.start !== start && !worker.busy && worker.calls === 0 && !worker.owns,',
|
|
74
|
-
' );',
|
|
75
|
-
' if (!idle) return false;',
|
|
76
|
-
' idle.thread.terminate();',
|
|
77
|
-
' remove(idle, null);',
|
|
78
|
-
' return true;',
|
|
79
|
-
' };',
|
|
80
|
-
' // A worker that stopped: its calls fail, and the threads that talked to it forget it.',
|
|
81
|
-
' const remove = (worker, error) => {',
|
|
82
|
-
' workers.splice(workers.indexOf(worker), 1);',
|
|
83
|
-
' for (const [id, call] of calls) {',
|
|
84
|
-
' if (call.worker !== worker) continue;',
|
|
85
|
-
' calls.delete(id);',
|
|
86
|
-
' call.reject(error);',
|
|
87
|
-
' }',
|
|
88
|
-
' if (worker.key) {',
|
|
89
|
-
' $$gone(worker.key);',
|
|
90
|
-
' for (const other of workers) other.key && other.thread.postMessage({ gone: worker.key });',
|
|
91
|
-
' }',
|
|
92
|
-
' };',
|
|
93
|
-
' const fail = (worker, error) => {',
|
|
94
|
-
' remove(worker, error);',
|
|
95
|
-
' dispatch();',
|
|
96
|
-
' };',
|
|
97
|
-
' const receive = (worker, data) => {',
|
|
98
|
-
' if ("op" in data || "answer" in data) return $$incoming(worker.key, data);',
|
|
99
|
-
' if ("hello" in data) {',
|
|
100
|
-
' worker.key = data.hello;',
|
|
101
|
-
' $$connect(worker.key, worker.thread, false);',
|
|
102
|
-
' worker.thread.postMessage({ init: registry.key });',
|
|
103
|
-
' // Workers talk to each other through ports of their own, also while this thread is busy.',
|
|
104
|
-
' for (const other of workers) {',
|
|
105
|
-
' if (other === worker || !other.key) continue;',
|
|
106
|
-
' const { port1, port2 } = new MessageChannel();',
|
|
107
|
-
' worker.thread.postMessage({ port: other.key, through: port1 }, [port1]);',
|
|
108
|
-
' other.thread.postMessage({ port: worker.key, through: port2 }, [port2]);',
|
|
109
|
-
' }',
|
|
110
|
-
' worker.busy = false;',
|
|
111
|
-
' unref(worker);',
|
|
112
|
-
' } else if ("free" in data) {',
|
|
113
|
-
' worker.busy = false;',
|
|
114
|
-
' worker.owns = data.owns;',
|
|
115
|
-
' } else if ("returned" in data) {',
|
|
116
|
-
' $$adopt(data);',
|
|
117
|
-
' const call = calls.get(data.returned);',
|
|
118
|
-
' calls.delete(data.returned);',
|
|
119
|
-
' worker.owns = data.owns;',
|
|
120
|
-
' worker.calls--;',
|
|
121
|
-
' unref(worker);',
|
|
122
|
-
' if ("error" in data) call?.reject(data.error);',
|
|
123
|
-
' else call?.resolve(data.value);',
|
|
124
|
-
' }',
|
|
125
|
-
' dispatch();',
|
|
126
|
-
' // A worker that a waiting call held on to, now free and without a call, lets go again.',
|
|
127
|
-
' if ("free" in data) unref(worker);',
|
|
128
|
-
' };',
|
|
129
|
-
' const add = (start) => {',
|
|
130
|
-
' const worker = { start, key: null, thread: null, busy: true, calls: 0, owns: false };',
|
|
131
|
-
' workers.push(worker);',
|
|
132
|
-
' start().then(',
|
|
133
|
-
' (thread) => {',
|
|
134
|
-
' worker.thread = thread;',
|
|
135
|
-
' if (thread.on) {',
|
|
136
|
-
' thread.on("message", (data) => receive(worker, data));',
|
|
137
|
-
' thread.on("error", (error) => fail(worker, error));',
|
|
138
|
-
' } else {',
|
|
139
|
-
' thread.onmessage = (event) => receive(worker, event.data);',
|
|
140
|
-
' thread.onerror = (event) => fail(worker, new Error(event.message ?? "a worker failed"));',
|
|
141
|
-
' }',
|
|
142
|
-
' },',
|
|
143
|
-
' (error) => {',
|
|
144
|
-
' remove(worker, error);',
|
|
145
|
-
' for (const call of queue.filter((each) => each.start === start)) {',
|
|
146
|
-
' queue.splice(queue.indexOf(call), 1);',
|
|
147
|
-
' call.reject(error);',
|
|
148
|
-
' }',
|
|
149
|
-
' },',
|
|
150
|
-
' );',
|
|
151
|
-
' };',
|
|
152
|
-
' const tasks = new WeakMap();',
|
|
153
|
-
' return {',
|
|
154
|
-
' call: (start, name, args) => {',
|
|
155
|
-
' const call = { id: ids++, start, name, args, resolve: null, reject: null, worker: null };',
|
|
156
|
-
' const task = new Promise((resolve, reject) => Object.assign(call, { resolve, reject }));',
|
|
157
|
-
' tasks.set(task, call);',
|
|
158
|
-
' queue.push(call);',
|
|
159
|
-
' dispatch();',
|
|
160
|
-
' return task;',
|
|
161
|
-
' },',
|
|
162
|
-
' // A call that waits leaves the queue; a worker cannot stop a call, so the worker stops, and',
|
|
163
|
-
' // the other calls in it fail too.',
|
|
164
|
-
' cancel: (task) => {',
|
|
165
|
-
' const call = tasks.get(task);',
|
|
166
|
-
' tasks.delete(task);',
|
|
167
|
-
' if (!call || (!queue.includes(call) && !calls.has(call.id))) return;',
|
|
168
|
-
' task.catch(() => {});',
|
|
169
|
-
' if (queue.includes(call)) {',
|
|
170
|
-
' queue.splice(queue.indexOf(call), 1);',
|
|
171
|
-
' return call.reject(new Error("goroutine cancelled"));',
|
|
172
|
-
' }',
|
|
173
|
-
' calls.delete(call.id);',
|
|
174
|
-
' call.reject(new Error("goroutine cancelled"));',
|
|
175
|
-
' call.worker.thread.terminate();',
|
|
176
|
-
' remove(call.worker, new Error("the worker of the goroutine stopped: another goroutine in it was cancelled"));',
|
|
177
|
-
' dispatch();',
|
|
178
|
-
' },',
|
|
179
|
-
' };',
|
|
180
|
-
'}',
|
|
181
|
-
],
|
|
182
|
-
/** `go` inside a goroutine: the function runs in the same worker, with copies of the arguments. */
|
|
183
|
-
goLocal: [
|
|
184
|
-
'function $$go(fn, args) {',
|
|
185
|
-
' const copies = $$copy(args);',
|
|
186
|
-
' const run = typeof fn === "function" ? fn : $$goroutines[fn];',
|
|
187
|
-
' let cancel;',
|
|
188
|
-
' const cancelled = new Promise((_, reject) => (cancel = reject));',
|
|
189
|
-
' const task = Promise.race([cancelled, Promise.resolve().then(() => cancel && run(...copies))]);',
|
|
190
|
-
' $$registry().cancels.set(task, () => {',
|
|
191
|
-
' task.catch(() => {});',
|
|
192
|
-
' cancel(new Error("goroutine cancelled"));',
|
|
193
|
-
' cancel = null;',
|
|
194
|
-
' });',
|
|
195
|
-
' return task;',
|
|
196
|
-
'}',
|
|
197
|
-
],
|
|
198
|
-
goFor: [
|
|
199
|
-
'// `go for`: the array is split into parts, one per processor, each part runs as a goroutine, and',
|
|
200
|
-
'// the loop waits for all of them. A typed array on a SharedArrayBuffer is shared with the workers,',
|
|
201
|
-
'// which write into it; other parts are copies, moved to the workers and back into the array.',
|
|
202
|
-
'async function $$goFor(name, items, ...captured) {',
|
|
203
|
-
' const count = Math.min(globalThis.navigator?.hardwareConcurrency ?? 4, items.length);',
|
|
204
|
-
' const shared =',
|
|
205
|
-
' typeof SharedArrayBuffer === "function" && items.buffer instanceof SharedArrayBuffer;',
|
|
206
|
-
' const parts = [];',
|
|
207
|
-
' for (let k = 0; k < count; k++) {',
|
|
208
|
-
' const from = Math.floor((items.length * k) / count);',
|
|
209
|
-
' const to = Math.floor((items.length * (k + 1)) / count);',
|
|
210
|
-
' const part = shared ? items.subarray(from, to) : items.slice(from, to);',
|
|
211
|
-
' if (ArrayBuffer.isView(part) && !shared) $$transfer(part);',
|
|
212
|
-
' parts.push(',
|
|
213
|
-
' $$go(name, [part, from, ...captured]).then((written) => {',
|
|
214
|
-
' if (written === undefined || shared) return;',
|
|
215
|
-
' if (ArrayBuffer.isView(items)) items.set(written, from);',
|
|
216
|
-
' else for (let i = 0; i < written.length; i++) items[from + i] = written[i];',
|
|
217
|
-
' }),',
|
|
218
|
-
' );',
|
|
219
|
-
' }',
|
|
220
|
-
' await Promise.all(parts);',
|
|
221
|
-
'}',
|
|
222
|
-
],
|
|
223
|
-
shared: [
|
|
224
|
-
'// `make([]T, n, shared)`: the memory of a typed array that the workers of `go for` write into,',
|
|
225
|
-
'// without copies. A page has SharedArrayBuffer only with cross-origin isolation.',
|
|
226
|
-
'function $$shared(bytes) {',
|
|
227
|
-
' if (typeof SharedArrayBuffer !== "function") {',
|
|
228
|
-
' throw new Error(',
|
|
229
|
-
' "make(..., shared) needs SharedArrayBuffer, which a page has only with cross-origin isolation: the headers Cross-Origin-Opener-Policy: same-origin and Cross-Origin-Embedder-Policy: require-corp",',
|
|
230
|
-
' );',
|
|
231
|
-
' }',
|
|
232
|
-
' return new SharedArrayBuffer(bytes);',
|
|
233
|
-
'}',
|
|
234
|
-
],
|
|
235
|
-
serve: [
|
|
236
|
-
'// The worker: runs the calls that the pool sends and sends back their results or errors. Once the',
|
|
237
|
-
'// thread is free again, it says so, and the pool may send another call.',
|
|
238
|
-
'async function $$serve(functions) {',
|
|
239
|
-
' const threads = "node:worker_threads";',
|
|
240
|
-
' const port =',
|
|
241
|
-
' typeof postMessage === "function"',
|
|
242
|
-
' ? globalThis',
|
|
243
|
-
' : (await import(/* @vite-ignore */ threads)).parentPort;',
|
|
244
|
-
' const registry = $$registry();',
|
|
245
|
-
' // The port to the main thread keeps the worker running.',
|
|
246
|
-
' registry.idle = (key) => key !== registry.main && registry.ports.get(key)?.unref?.();',
|
|
247
|
-
' port.postMessage({ hello: registry.key });',
|
|
248
|
-
' const free = () => port.postMessage({ free: true, owns: registry.states.size > 0 });',
|
|
249
|
-
' const receive = async (data) => {',
|
|
250
|
-
' if ("op" in data || "answer" in data) return $$incoming(registry.main, data);',
|
|
251
|
-
' if ("init" in data) {',
|
|
252
|
-
' registry.main = data.init;',
|
|
253
|
-
' return $$connect(data.init, port, false);',
|
|
254
|
-
' }',
|
|
255
|
-
' if ("port" in data) return $$connect(data.port, data.through, true);',
|
|
256
|
-
' if ("gone" in data) return $$gone(data.gone);',
|
|
257
|
-
' setTimeout(free);',
|
|
258
|
-
' $$adopt(data.args);',
|
|
259
|
-
' const { main } = registry;',
|
|
260
|
-
' try {',
|
|
261
|
-
' const value = await functions[data.name](...data.args);',
|
|
262
|
-
' $$post(main, { returned: data.call, value, owns: registry.states.size > 0 });',
|
|
263
|
-
' } catch (error) {',
|
|
264
|
-
' try {',
|
|
265
|
-
' $$post(main, { returned: data.call, error, owns: registry.states.size > 0 });',
|
|
266
|
-
' } catch {',
|
|
267
|
-
' $$post(main, { returned: data.call, error: new Error(String(error)), owns: true });',
|
|
268
|
-
' }',
|
|
269
|
-
' }',
|
|
270
|
-
' };',
|
|
271
|
-
' if (port.on) port.on("message", receive);',
|
|
272
|
-
' else port.onmessage = (event) => receive(event.data);',
|
|
273
|
-
'}',
|
|
274
|
-
],
|
|
275
|
-
cancel: [
|
|
276
|
-
'// `cancel(task)`: stops a goroutine of `go`, whose Promise fails with "goroutine cancelled". A call',
|
|
277
|
-
'// in a worker stops with its worker; inside a goroutine, `go` runs in the same worker, so its',
|
|
278
|
-
'// Promise fails but code that has started goes on.',
|
|
279
|
-
'function $$cancel(task) {',
|
|
280
|
-
' globalThis[Symbol.for("rook.goroutines")]?.cancel(task);',
|
|
281
|
-
' $$registry().cancels.get(task)?.();',
|
|
282
|
-
'}',
|
|
283
|
-
],
|
|
284
|
-
};
|
|
285
|
-
/**
|
|
286
|
-
* The start of a worker of a module: a Web Worker, or a worker thread of Node. Node takes only the
|
|
287
|
-
* path of a `file:` URL, without the query of `app.rk?goroutines`, so its worker starts with a
|
|
288
|
-
* module that imports the whole URL. An absolute URL, such as the Blob of the playground, is used
|
|
289
|
-
* as it is.
|
|
290
|
-
*/
|
|
291
|
-
export function startWorker(url) {
|
|
292
|
-
const base = /^[a-z][a-z\d+.-]*:/i.test(url) ? '' : ', import.meta.url';
|
|
293
|
-
const workerUrl = `new URL(${JSON.stringify(url)}${base})`;
|
|
294
|
-
return [
|
|
295
|
-
'async function $$startWorker() {',
|
|
296
|
-
' if (typeof Worker === "function") {',
|
|
297
|
-
` return new Worker(${workerUrl}, { type: "module" });`,
|
|
298
|
-
' }',
|
|
299
|
-
' const threads = "node:worker_threads";',
|
|
300
|
-
' const { Worker: NodeWorker } = await import(/* @vite-ignore */ threads);',
|
|
301
|
-
// Vite bundles the Web Worker; this URL is only for Node.
|
|
302
|
-
` const url = new URL(/* @vite-ignore */ ${JSON.stringify(url)}${base});`,
|
|
303
|
-
' const code = `import ${JSON.stringify(url.href)};`;',
|
|
304
|
-
' return new NodeWorker(new URL(`data:text/javascript,${encodeURIComponent(code)}`));',
|
|
305
|
-
'}',
|
|
306
|
-
];
|
|
307
|
-
}
|
|
@@ -1,10 +0,0 @@
|
|
|
1
|
-
import { type ChanHelper } from './chan-helpers.ts';
|
|
2
|
-
import { type GoHelper } from './go-helpers.ts';
|
|
3
|
-
import { type SelectHelper } from './select-helpers.ts';
|
|
4
|
-
import { type ThreadHelper } from './thread-helpers.ts';
|
|
5
|
-
export type Helper = GoHelper | ChanHelper | SelectHelper | ThreadHelper | 'runDeferred' | 'values' | 'append' | 'attribute' | 'content' | 'branches' | 'dynamic' | 'list' | 'owner' | 'mount' | 'style' | 'element' | 'svgElement';
|
|
6
|
-
/** Predeclared functions of goroutines and channels, by the helpers that define them. */
|
|
7
|
-
export declare const PREDECLARED: Readonly<Record<string, Helper>>;
|
|
8
|
-
/** Helpers that use other helpers. */
|
|
9
|
-
export declare const HELPER_NEEDS: Readonly<Partial<Record<Helper, readonly Helper[]>>>;
|
|
10
|
-
export declare const HELPERS: Readonly<Record<Helper, readonly string[]>>;
|
package/dist/codegen/helpers.js
DELETED
|
@@ -1,356 +0,0 @@
|
|
|
1
|
-
import { CHAN_HELPERS } from "./chan-helpers.js";
|
|
2
|
-
import { GO_HELPERS } from "./go-helpers.js";
|
|
3
|
-
import { SELECT_HELPERS } from "./select-helpers.js";
|
|
4
|
-
import { THREAD_HELPERS } from "./thread-helpers.js";
|
|
5
|
-
/** Predeclared functions of goroutines and channels, by the helpers that define them. */
|
|
6
|
-
export const PREDECLARED = {
|
|
7
|
-
close: 'chan',
|
|
8
|
-
transfer: 'threads',
|
|
9
|
-
after: 'timers',
|
|
10
|
-
tick: 'timers',
|
|
11
|
-
cancel: 'cancel',
|
|
12
|
-
};
|
|
13
|
-
/** Helpers that use other helpers. */
|
|
14
|
-
export const HELPER_NEEDS = {
|
|
15
|
-
go: ['threads'],
|
|
16
|
-
goFor: ['threads'],
|
|
17
|
-
goLocal: ['threads'],
|
|
18
|
-
serve: ['threads'],
|
|
19
|
-
chan: ['threads', 'select'],
|
|
20
|
-
timers: ['chan'],
|
|
21
|
-
cancel: ['threads'],
|
|
22
|
-
select: ['chan'],
|
|
23
|
-
owner: ['runDeferred'],
|
|
24
|
-
mount: ['owner'],
|
|
25
|
-
content: ['owner'],
|
|
26
|
-
branches: ['owner'],
|
|
27
|
-
dynamic: ['owner'],
|
|
28
|
-
list: ['owner'],
|
|
29
|
-
};
|
|
30
|
-
export const HELPERS = {
|
|
31
|
-
...GO_HELPERS,
|
|
32
|
-
...CHAN_HELPERS,
|
|
33
|
-
...SELECT_HELPERS,
|
|
34
|
-
...THREAD_HELPERS,
|
|
35
|
-
/** `for v in x` over an untyped value: a Map gives its values, as a typed one does. */
|
|
36
|
-
values: [
|
|
37
|
-
'function $$values(iterable) {',
|
|
38
|
-
" if (typeof iterable === 'function') return iterable();",
|
|
39
|
-
' return iterable instanceof Map ? iterable.values() : iterable;',
|
|
40
|
-
'}',
|
|
41
|
-
],
|
|
42
|
-
/** Runs deferred calls in reverse order; every call runs even if an earlier one throws. */
|
|
43
|
-
runDeferred: [
|
|
44
|
-
'function $$runDeferred(deferred) {',
|
|
45
|
-
' let failure;',
|
|
46
|
-
' for (let i = deferred.length - 1; i >= 0; i--) {',
|
|
47
|
-
' try {',
|
|
48
|
-
' deferred[i]();',
|
|
49
|
-
' } catch (error) {',
|
|
50
|
-
' failure = { error };',
|
|
51
|
-
' }',
|
|
52
|
-
' }',
|
|
53
|
-
' if (failure) throw failure.error;',
|
|
54
|
-
'}',
|
|
55
|
-
],
|
|
56
|
-
/**
|
|
57
|
-
* Owners of markup that `{if}`, `{switch}` and `{for}` may remove: the functions returned by the
|
|
58
|
-
* `mount()` of its components, and the removal of the blocks inside it, run when it is removed.
|
|
59
|
-
* The current owner is shared by the modules of a page, since the blocks of one module may hold
|
|
60
|
-
* the components of another.
|
|
61
|
-
*/
|
|
62
|
-
owner: [
|
|
63
|
-
'const $$owner = Symbol.for("rook.owner");',
|
|
64
|
-
'',
|
|
65
|
-
'function $$owned(create) {',
|
|
66
|
-
' const owner = { removed: false, cleanups: [] };',
|
|
67
|
-
' const parent = globalThis[$$owner];',
|
|
68
|
-
' globalThis[$$owner] = owner;',
|
|
69
|
-
' let result;',
|
|
70
|
-
' try {',
|
|
71
|
-
' result = create();',
|
|
72
|
-
' } finally {',
|
|
73
|
-
' globalThis[$$owner] = parent;',
|
|
74
|
-
' }',
|
|
75
|
-
' const remove = () => {',
|
|
76
|
-
' if (owner.removed) return;',
|
|
77
|
-
' owner.removed = true;',
|
|
78
|
-
' $$runDeferred(owner.cleanups);',
|
|
79
|
-
' };',
|
|
80
|
-
' return [result, remove];',
|
|
81
|
-
'}',
|
|
82
|
-
'',
|
|
83
|
-
'function $$onRemove(cleanup) {',
|
|
84
|
-
' globalThis[$$owner]?.cleanups.push(cleanup);',
|
|
85
|
-
'}',
|
|
86
|
-
],
|
|
87
|
-
/**
|
|
88
|
-
* `mount() { ... }` of a component: runs when its markup is in the document. That is checked
|
|
89
|
-
* after the code that creates the component; markup that is not in the document yet is waited
|
|
90
|
-
* for with a MutationObserver, which watches only while some markup waits. Markup that its
|
|
91
|
-
* owner removes before that never mounts. The function `mount` returns runs when the owner is
|
|
92
|
-
* removed.
|
|
93
|
-
*/
|
|
94
|
-
mount: [
|
|
95
|
-
'const $$waiting = new Set();',
|
|
96
|
-
'let $$observer = null;',
|
|
97
|
-
'',
|
|
98
|
-
'function $$mount(node, mount) {',
|
|
99
|
-
' const entry = { node, mount, owner: globalThis[$$owner] };',
|
|
100
|
-
' entry.owner?.cleanups.push(() => $$waiting.delete(entry));',
|
|
101
|
-
' queueMicrotask(() => $$tryMount(entry));',
|
|
102
|
-
'}',
|
|
103
|
-
'',
|
|
104
|
-
'function $$tryMount(entry) {',
|
|
105
|
-
' if (entry.owner?.removed) return;',
|
|
106
|
-
' const node = entry.node();',
|
|
107
|
-
' if (node != null && !node.isConnected) {',
|
|
108
|
-
' $$waiting.add(entry);',
|
|
109
|
-
' if (!$$observer) {',
|
|
110
|
-
' $$observer = new MutationObserver($$mountWaiting);',
|
|
111
|
-
' $$observer.observe(node.ownerDocument ?? document, { childList: true, subtree: true });',
|
|
112
|
-
' }',
|
|
113
|
-
' return;',
|
|
114
|
-
' }',
|
|
115
|
-
' const cleanup = entry.mount();',
|
|
116
|
-
' if (typeof cleanup === "function") entry.owner?.cleanups.push(cleanup);',
|
|
117
|
-
'}',
|
|
118
|
-
'',
|
|
119
|
-
'function $$mountWaiting() {',
|
|
120
|
-
' for (const entry of $$waiting) {',
|
|
121
|
-
' if (!entry.node().isConnected) continue;',
|
|
122
|
-
' $$waiting.delete(entry);',
|
|
123
|
-
' $$tryMount(entry);',
|
|
124
|
-
' }',
|
|
125
|
-
' if ($$waiting.size === 0) {',
|
|
126
|
-
' $$observer.disconnect();',
|
|
127
|
-
' $$observer = null;',
|
|
128
|
-
' }',
|
|
129
|
-
'}',
|
|
130
|
-
],
|
|
131
|
-
/** Appends element content whose type is not known: null adds nothing, arrays add each item. */
|
|
132
|
-
append: [
|
|
133
|
-
'function $$append(parent, value) {',
|
|
134
|
-
' if (value == null || value === false) return;',
|
|
135
|
-
' if (Array.isArray(value)) {',
|
|
136
|
-
' for (const item of value) $$append(parent, item);',
|
|
137
|
-
' } else {',
|
|
138
|
-
' parent.append(value);',
|
|
139
|
-
' }',
|
|
140
|
-
'}',
|
|
141
|
-
],
|
|
142
|
-
/**
|
|
143
|
-
* Nodes of markup that change, `{list}` or `{ok ? <b /> : null}`: they are kept before an end
|
|
144
|
-
* marker. Nodes that stay are moved rather than created again. Returns the markers with the
|
|
145
|
-
* first nodes, to be inserted, and the update.
|
|
146
|
-
*/
|
|
147
|
-
content: [
|
|
148
|
-
'function $$content(value) {',
|
|
149
|
-
' const end = document.createTextNode("");',
|
|
150
|
-
' const fragment = document.createDocumentFragment();',
|
|
151
|
-
' fragment.append(end);',
|
|
152
|
-
' let nodes = [];',
|
|
153
|
-
' let remove;',
|
|
154
|
-
' const update = () => {',
|
|
155
|
-
' const next = [];',
|
|
156
|
-
' const add = (item) => {',
|
|
157
|
-
' if (item == null || item === false) return;',
|
|
158
|
-
' if (Array.isArray(item)) {',
|
|
159
|
-
' for (const each of item) add(each);',
|
|
160
|
-
' } else if (item.nodeType === 11) {',
|
|
161
|
-
' next.push(...item.childNodes);',
|
|
162
|
-
' } else {',
|
|
163
|
-
' next.push(typeof item === "object" ? item : document.createTextNode(String(item)));',
|
|
164
|
-
' }',
|
|
165
|
-
' };',
|
|
166
|
-
' const [created, removeCreated] = $$owned(value);',
|
|
167
|
-
' add(created);',
|
|
168
|
-
' remove?.();',
|
|
169
|
-
' remove = removeCreated;',
|
|
170
|
-
' const kept = new Set(next);',
|
|
171
|
-
' for (const node of nodes) if (!kept.has(node)) node.remove();',
|
|
172
|
-
' end.before(...next);',
|
|
173
|
-
' nodes = next;',
|
|
174
|
-
' };',
|
|
175
|
-
' update();',
|
|
176
|
-
' $$onRemove(() => remove?.());',
|
|
177
|
-
' return [fragment, update];',
|
|
178
|
-
'}',
|
|
179
|
-
],
|
|
180
|
-
/**
|
|
181
|
-
* `{if ...}` and `{switch ...}` in markup: shows the block that `choose` picks (-1 for none)
|
|
182
|
-
* between two markers. The block is created again only when the choice changes; otherwise it is
|
|
183
|
-
* updated.
|
|
184
|
-
*/
|
|
185
|
-
branches: [
|
|
186
|
-
'function $$branches(choose, blocks) {',
|
|
187
|
-
' const start = document.createTextNode("");',
|
|
188
|
-
' const end = document.createTextNode("");',
|
|
189
|
-
' const fragment = document.createDocumentFragment();',
|
|
190
|
-
' fragment.append(start, end);',
|
|
191
|
-
' let current = null;',
|
|
192
|
-
' let update;',
|
|
193
|
-
' let remove;',
|
|
194
|
-
' const render = () => {',
|
|
195
|
-
' const next = choose();',
|
|
196
|
-
' if (next === current) {',
|
|
197
|
-
' update?.();',
|
|
198
|
-
' return;',
|
|
199
|
-
' }',
|
|
200
|
-
' current = next;',
|
|
201
|
-
' remove?.();',
|
|
202
|
-
' while (start.nextSibling !== end) start.nextSibling.remove();',
|
|
203
|
-
' update = undefined;',
|
|
204
|
-
' remove = undefined;',
|
|
205
|
-
' if (next >= 0) {',
|
|
206
|
-
' const [[content, blockUpdate], removeBlock] = $$owned(blocks[next]);',
|
|
207
|
-
' end.before(content);',
|
|
208
|
-
' update = blockUpdate;',
|
|
209
|
-
' remove = removeBlock;',
|
|
210
|
-
' }',
|
|
211
|
-
' };',
|
|
212
|
-
' render();',
|
|
213
|
-
' $$onRemove(() => remove?.());',
|
|
214
|
-
' return [fragment, render];',
|
|
215
|
-
'}',
|
|
216
|
-
],
|
|
217
|
-
/**
|
|
218
|
-
* `<Page />` whose component is a value that changes: when it does, the block that `create`
|
|
219
|
-
* makes with it is created again between two markers; otherwise the block is updated.
|
|
220
|
-
*/
|
|
221
|
-
dynamic: [
|
|
222
|
-
'function $$dynamic(component, create) {',
|
|
223
|
-
' const start = document.createTextNode("");',
|
|
224
|
-
' const end = document.createTextNode("");',
|
|
225
|
-
' const fragment = document.createDocumentFragment();',
|
|
226
|
-
' fragment.append(start, end);',
|
|
227
|
-
' let current;',
|
|
228
|
-
' let update;',
|
|
229
|
-
' let remove;',
|
|
230
|
-
' const render = () => {',
|
|
231
|
-
' const next = component();',
|
|
232
|
-
' if (remove && next === current) {',
|
|
233
|
-
' update?.();',
|
|
234
|
-
' return;',
|
|
235
|
-
' }',
|
|
236
|
-
' current = next;',
|
|
237
|
-
' remove?.();',
|
|
238
|
-
' while (start.nextSibling !== end) start.nextSibling.remove();',
|
|
239
|
-
' const [[content, blockUpdate], removeBlock] = $$owned(() => create(next));',
|
|
240
|
-
' end.before(content);',
|
|
241
|
-
' update = blockUpdate;',
|
|
242
|
-
' remove = removeBlock;',
|
|
243
|
-
' };',
|
|
244
|
-
' render();',
|
|
245
|
-
' $$onRemove(() => remove?.());',
|
|
246
|
-
' return [fragment, render];',
|
|
247
|
-
'}',
|
|
248
|
-
],
|
|
249
|
-
/**
|
|
250
|
-
* `{for item in items { ... }}` in markup: a block for each item, between its own markers. On
|
|
251
|
-
* updates the blocks are found again by the item itself: blocks of items that stay are updated
|
|
252
|
-
* and moved into place, others are created or removed.
|
|
253
|
-
*/
|
|
254
|
-
list: [
|
|
255
|
-
'function $$list(items, create) {',
|
|
256
|
-
' const end = document.createTextNode("");',
|
|
257
|
-
' const fragment = document.createDocumentFragment();',
|
|
258
|
-
' fragment.append(end);',
|
|
259
|
-
' let blocks = [];',
|
|
260
|
-
' const nodesOf = (block) => {',
|
|
261
|
-
' const nodes = [];',
|
|
262
|
-
' for (let node = block.start; node !== block.end; node = node.nextSibling) nodes.push(node);',
|
|
263
|
-
' nodes.push(block.end);',
|
|
264
|
-
' return nodes;',
|
|
265
|
-
' };',
|
|
266
|
-
' const update = () => {',
|
|
267
|
-
' const old = new Map();',
|
|
268
|
-
' for (const block of blocks) {',
|
|
269
|
-
' const same = old.get(block.item);',
|
|
270
|
-
' if (same) same.push(block);',
|
|
271
|
-
' else old.set(block.item, [block]);',
|
|
272
|
-
' }',
|
|
273
|
-
' const next = [];',
|
|
274
|
-
' let index = 0;',
|
|
275
|
-
' for (const item of items()) {',
|
|
276
|
-
' let block = old.get(item)?.shift();',
|
|
277
|
-
' if (block) {',
|
|
278
|
-
' block.update?.(index);',
|
|
279
|
-
' } else {',
|
|
280
|
-
' const [[content, blockUpdate], remove] = $$owned(() => create(item, index));',
|
|
281
|
-
' block = { item, start: document.createTextNode(""), end: document.createTextNode("") };',
|
|
282
|
-
' block.update = blockUpdate;',
|
|
283
|
-
' block.remove = remove;',
|
|
284
|
-
' block.content = document.createDocumentFragment();',
|
|
285
|
-
' block.content.append(block.start, content, block.end);',
|
|
286
|
-
' }',
|
|
287
|
-
' next.push(block);',
|
|
288
|
-
' index++;',
|
|
289
|
-
' }',
|
|
290
|
-
' for (const same of old.values()) {',
|
|
291
|
-
' for (const block of same) {',
|
|
292
|
-
' block.remove();',
|
|
293
|
-
' for (const node of nodesOf(block)) node.remove();',
|
|
294
|
-
' }',
|
|
295
|
-
' }',
|
|
296
|
-
' let before = end;',
|
|
297
|
-
' for (let i = next.length - 1; i >= 0; i--) {',
|
|
298
|
-
' const block = next[i];',
|
|
299
|
-
' if (block.content) {',
|
|
300
|
-
' before.before(block.content);',
|
|
301
|
-
' block.content = null;',
|
|
302
|
-
' } else if (block.end.nextSibling !== before) {',
|
|
303
|
-
' before.before(...nodesOf(block));',
|
|
304
|
-
' }',
|
|
305
|
-
' before = block.start;',
|
|
306
|
-
' }',
|
|
307
|
-
' blocks = next;',
|
|
308
|
-
' };',
|
|
309
|
-
' update();',
|
|
310
|
-
' $$onRemove(() => {',
|
|
311
|
-
' for (const block of blocks) block.remove();',
|
|
312
|
-
' });',
|
|
313
|
-
' return [fragment, update];',
|
|
314
|
-
'}',
|
|
315
|
-
],
|
|
316
|
-
/** Creates an element with its properties and the attributes that are set with setAttribute. */
|
|
317
|
-
element: [
|
|
318
|
-
'function $$element(tag, properties, attributes) {',
|
|
319
|
-
' const element = Object.assign(document.createElement(tag), properties);',
|
|
320
|
-
' for (const name in attributes) element.setAttribute(name, attributes[name]);',
|
|
321
|
-
' return element;',
|
|
322
|
-
'}',
|
|
323
|
-
],
|
|
324
|
-
/** Creates an SVG element with its attributes. */
|
|
325
|
-
svgElement: [
|
|
326
|
-
'function $$svgElement(tag, attributes) {',
|
|
327
|
-
' const element = document.createElementNS("http://www.w3.org/2000/svg", tag);',
|
|
328
|
-
' for (const name in attributes) element.setAttribute(name, attributes[name]);',
|
|
329
|
-
' return element;',
|
|
330
|
-
'}',
|
|
331
|
-
],
|
|
332
|
-
/** Sets an attribute whose value may be null: then the attribute is removed. */
|
|
333
|
-
attribute: [
|
|
334
|
-
'function $$attribute(element, name, value) {',
|
|
335
|
-
' if (value == null) element.removeAttribute(name);',
|
|
336
|
-
' else element.setAttribute(name, value);',
|
|
337
|
-
'}',
|
|
338
|
-
],
|
|
339
|
-
/**
|
|
340
|
-
* Adds the CSS of a component to the page once: modules that inline the same component add it
|
|
341
|
-
* with the same id. `$$styles` keeps the CSS of the module for the shadow roots of its web
|
|
342
|
-
* components, which the page's styles do not reach.
|
|
343
|
-
*/
|
|
344
|
-
style: [
|
|
345
|
-
'const $$styles = [];',
|
|
346
|
-
'',
|
|
347
|
-
'function $$style(id, css) {',
|
|
348
|
-
' $$styles.push(css);',
|
|
349
|
-
' if (document.getElementById(id)) return;',
|
|
350
|
-
' const style = document.createElement("style");',
|
|
351
|
-
' style.id = id;',
|
|
352
|
-
' style.textContent = css;',
|
|
353
|
-
' document.head.append(style);',
|
|
354
|
-
'}',
|
|
355
|
-
],
|
|
356
|
-
};
|