rooklang 0.3.0 → 0.3.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/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/base.js +4 -4
- 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/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/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/cli.js +4 -2
- 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.js +0 -4
- 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 +15 -0
- package/dist/codegen/runtime.js +99 -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 +8 -0
- 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/ts-types.js +9 -1
- package/dist/vite.js +7 -1
- package/dist/walk-types.js +1 -0
- package/dist/walk.js +9 -0
- package/package.json +7 -4
- package/src/runtime/chan.js +277 -0
- package/src/runtime/core.js +23 -0
- package/src/runtime/go.js +334 -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
|
@@ -0,0 +1,240 @@
|
|
|
1
|
+
// What threads share: a channel is data, `{ $$chan: id, size, zero }`, copied to other threads like
|
|
2
|
+
// any value, and what it holds lives in the thread that made it, whose key starts its id. Each
|
|
3
|
+
// thread has a registry of its channels and of its ports to the other threads, shared by its
|
|
4
|
+
// modules; the operations of a thread on the channels of another go straight to that thread
|
|
5
|
+
// through a MessagePort, which the main thread sets up on the first need (go.js).
|
|
6
|
+
|
|
7
|
+
import { $$Queue } from './queue.js';
|
|
8
|
+
|
|
9
|
+
// The state of this thread that its modules share: the channels it made, its ports to the other
|
|
10
|
+
// threads by their keys, and the requests that wait for an answer.
|
|
11
|
+
export function $$registry() {
|
|
12
|
+
return (globalThis[Symbol.for('rook.threads')] ??= {
|
|
13
|
+
key: Math.random().toString(36).slice(2),
|
|
14
|
+
channels: 0,
|
|
15
|
+
states: new Map(),
|
|
16
|
+
held: new FinalizationRegistry((id) => $$hold(id, -1)),
|
|
17
|
+
ports: new Map(),
|
|
18
|
+
pins: new Map(),
|
|
19
|
+
idle: null,
|
|
20
|
+
waiting: new Map(),
|
|
21
|
+
gone: new Set(),
|
|
22
|
+
main: null,
|
|
23
|
+
requests: new Map(),
|
|
24
|
+
asks: 0,
|
|
25
|
+
selects: new Map(),
|
|
26
|
+
moved: new WeakSet(),
|
|
27
|
+
cancels: new WeakMap(),
|
|
28
|
+
handle: null,
|
|
29
|
+
});
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// The key of the thread that made a channel, which keeps what it holds.
|
|
33
|
+
export function $$ownerOf(id) {
|
|
34
|
+
return id.slice(0, id.lastIndexOf(':'));
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// Sends a message to a thread. The channels in it are counted first, as the thread gets copies,
|
|
38
|
+
// and the buffers of transfer() move there. A message waits for the port of a thread that has just
|
|
39
|
+
// started.
|
|
40
|
+
export function $$post(key, message) {
|
|
41
|
+
const registry = $$registry();
|
|
42
|
+
|
|
43
|
+
if (registry.gone.has(key)) return;
|
|
44
|
+
|
|
45
|
+
const { channels, moved } = $$walk(message);
|
|
46
|
+
|
|
47
|
+
for (const channel of channels) $$hold(channel.$$chan, 1);
|
|
48
|
+
|
|
49
|
+
const port = registry.ports.get(key);
|
|
50
|
+
|
|
51
|
+
if (port) return port.postMessage(message, moved);
|
|
52
|
+
if (!registry.waiting.has(key)) registry.waiting.set(key, []);
|
|
53
|
+
registry.waiting.get(key).push([message, moved]);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// A request to a thread, answered by a message with its number.
|
|
57
|
+
export function $$ask(key, message) {
|
|
58
|
+
const registry = $$registry();
|
|
59
|
+
|
|
60
|
+
if (registry.gone.has(key)) return Promise.reject($$stopped());
|
|
61
|
+
$$pin(key, 1);
|
|
62
|
+
|
|
63
|
+
return new Promise((resolve, reject) => {
|
|
64
|
+
const ask = registry.asks++;
|
|
65
|
+
|
|
66
|
+
registry.requests.set(ask, { resolve, reject, key });
|
|
67
|
+
$$post(key, { ...message, ask });
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// A thread that this one waits for keeps the program running: in Node, its worker or port is
|
|
72
|
+
// referenced while some request or select waits for it.
|
|
73
|
+
export function $$pin(key, count) {
|
|
74
|
+
const registry = $$registry();
|
|
75
|
+
const pins = (registry.pins.get(key) ?? 0) + count;
|
|
76
|
+
|
|
77
|
+
registry.pins.set(key, pins);
|
|
78
|
+
|
|
79
|
+
const port = registry.ports.get(key);
|
|
80
|
+
|
|
81
|
+
if (pins > 0) port?.ref?.();
|
|
82
|
+
else if (registry.idle) registry.idle(key);
|
|
83
|
+
else port?.unref?.();
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
export function $$stopped() {
|
|
87
|
+
return new Error('the goroutine that made the channel has stopped');
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// A port to a thread: messages that waited for it go now.
|
|
91
|
+
export function $$connect(key, port, listen) {
|
|
92
|
+
const registry = $$registry();
|
|
93
|
+
|
|
94
|
+
registry.ports.set(key, port);
|
|
95
|
+
if (registry.pins.get(key) > 0) port.ref?.();
|
|
96
|
+
if (listen) {
|
|
97
|
+
const receive = (data) => $$incoming(key, data);
|
|
98
|
+
|
|
99
|
+
if (port.on) {
|
|
100
|
+
port.on('message', receive);
|
|
101
|
+
if (!(registry.pins.get(key) > 0)) port.unref?.();
|
|
102
|
+
} else {
|
|
103
|
+
port.onmessage = (event) => receive(event.data);
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
for (const [message, moved] of registry.waiting.get(key) ?? []) port.postMessage(message, moved);
|
|
108
|
+
registry.waiting.delete(key);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// A thread has stopped: what waits for it fails, and what it waited for is dropped.
|
|
112
|
+
export function $$gone(key) {
|
|
113
|
+
const registry = $$registry();
|
|
114
|
+
|
|
115
|
+
registry.gone.add(key);
|
|
116
|
+
registry.ports.get(key)?.close?.();
|
|
117
|
+
registry.ports.delete(key);
|
|
118
|
+
registry.waiting.delete(key);
|
|
119
|
+
for (const [ask, request] of registry.requests) {
|
|
120
|
+
if (request.key !== key) continue;
|
|
121
|
+
registry.requests.delete(ask);
|
|
122
|
+
request.reject($$stopped());
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
for (const [id, state] of registry.states) {
|
|
126
|
+
const alive = (entry) => (entry.select?.key ?? entry.from) !== key;
|
|
127
|
+
|
|
128
|
+
state.receivers.$$keep(alive);
|
|
129
|
+
state.senders.$$keep(alive);
|
|
130
|
+
registry.handle?.pump(id);
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
// A message from another thread: an answer to a request, or an operation on a channel of this one.
|
|
135
|
+
export function $$incoming(key, data) {
|
|
136
|
+
const registry = $$registry();
|
|
137
|
+
|
|
138
|
+
$$adopt(data);
|
|
139
|
+
if ('answer' in data) {
|
|
140
|
+
const request = registry.requests.get(data.answer);
|
|
141
|
+
|
|
142
|
+
registry.requests.delete(data.answer);
|
|
143
|
+
if (request) $$pin(request.key, -1);
|
|
144
|
+
if ('error' in data) request?.reject(data.error);
|
|
145
|
+
else request?.resolve(data.value);
|
|
146
|
+
} else if (data.op === 'hold') {
|
|
147
|
+
$$hold(data.channel, data.count);
|
|
148
|
+
} else if ('op' in data) {
|
|
149
|
+
registry.handle.message(key, data);
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
// Each value of a channel, in any thread, holds it: the thread that made it forgets it when none
|
|
154
|
+
// is left. A thread counts the channels of a value before a copy of it goes to another thread, and
|
|
155
|
+
// a copy lets go of them when it is collected.
|
|
156
|
+
export function $$hold(id, count) {
|
|
157
|
+
const registry = $$registry();
|
|
158
|
+
const owner = $$ownerOf(id);
|
|
159
|
+
|
|
160
|
+
if (owner !== registry.key) return $$post(owner, { op: 'hold', channel: id, count });
|
|
161
|
+
|
|
162
|
+
const state = registry.states.get(id);
|
|
163
|
+
|
|
164
|
+
if (!state) return;
|
|
165
|
+
state.holders += count;
|
|
166
|
+
if (state.holders <= 0) registry.states.delete(id);
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
export function $$adopt(value) {
|
|
170
|
+
const { held } = $$registry();
|
|
171
|
+
|
|
172
|
+
for (const channel of $$walk(value).channels) held.register(channel, channel.$$chan);
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
// A copy in this thread. One that `own` the runtime, such as the arguments of `go` that wait for
|
|
176
|
+
// a worker, moves its buffers on.
|
|
177
|
+
export function $$copy(value, own = false) {
|
|
178
|
+
const { channels, moved } = $$walk(value);
|
|
179
|
+
|
|
180
|
+
for (const channel of channels) $$hold(channel.$$chan, 1);
|
|
181
|
+
|
|
182
|
+
const copy = structuredClone(value, { transfer: moved });
|
|
183
|
+
const { held } = $$registry();
|
|
184
|
+
|
|
185
|
+
for (const channel of $$walk(copy, own).channels) held.register(channel, channel.$$chan);
|
|
186
|
+
|
|
187
|
+
return copy;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
// `transfer(x)`: the buffer of x moves to the thread that a value with it goes to, instead of a
|
|
191
|
+
// copy; here it is left empty.
|
|
192
|
+
export function $$transfer(value) {
|
|
193
|
+
$$registry().moved.add(ArrayBuffer.isView(value) ? value.buffer : value);
|
|
194
|
+
|
|
195
|
+
return value;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
// The channels in a value, each of which becomes a copy in the thread that gets the value, and the
|
|
199
|
+
// buffers that move there. A copy of a queue is its fields: here it gets its class again.
|
|
200
|
+
export function $$walk(
|
|
201
|
+
value,
|
|
202
|
+
own = false,
|
|
203
|
+
found = { channels: new Set(), moved: [] },
|
|
204
|
+
seen = new Set(),
|
|
205
|
+
) {
|
|
206
|
+
if (typeof value !== 'object' || value === null || seen.has(value)) return found;
|
|
207
|
+
seen.add(value);
|
|
208
|
+
|
|
209
|
+
const buffer =
|
|
210
|
+
value instanceof ArrayBuffer ? value : ArrayBuffer.isView(value) ? value.buffer : null;
|
|
211
|
+
|
|
212
|
+
// A SharedArrayBuffer is shared with the other thread, not moved.
|
|
213
|
+
if (buffer && !(buffer instanceof ArrayBuffer)) return found;
|
|
214
|
+
if (buffer) {
|
|
215
|
+
const { moved } = $$registry();
|
|
216
|
+
|
|
217
|
+
if (own) moved.add(buffer);
|
|
218
|
+
if (moved.has(buffer) && !found.moved.includes(buffer)) found.moved.push(buffer);
|
|
219
|
+
|
|
220
|
+
return found;
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
if (Array.isArray(value.$$queue)) Object.setPrototypeOf(value, $$Queue.prototype);
|
|
224
|
+
if (typeof value.$$chan === 'string') {
|
|
225
|
+
found.channels.add(value);
|
|
226
|
+
|
|
227
|
+
return found;
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
const items =
|
|
231
|
+
value instanceof Map
|
|
232
|
+
? [...value.keys(), ...value.values()]
|
|
233
|
+
: value instanceof Set || Array.isArray(value)
|
|
234
|
+
? value
|
|
235
|
+
: Object.values(value);
|
|
236
|
+
|
|
237
|
+
for (const item of items) $$walk(item, own, found, seen);
|
|
238
|
+
|
|
239
|
+
return found;
|
|
240
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
// Channels of time, as in Go: `after(ms)` and `tick(ms)`.
|
|
2
|
+
|
|
3
|
+
import { $$chan, $$send } from './chan.js';
|
|
4
|
+
import { $$try } from './select.js';
|
|
5
|
+
import { $$registry } from './threads.js';
|
|
6
|
+
|
|
7
|
+
// `after(ms)`: a channel that gets the time once, after `ms`. `tick(ms)`: one that gets it every
|
|
8
|
+
// `ms` until it is closed; a tick that finds the last one still there is dropped, as in Go.
|
|
9
|
+
export function $$after(ms) {
|
|
10
|
+
const channel = $$chan(1, 0);
|
|
11
|
+
|
|
12
|
+
setTimeout(() => $$send(channel, Date.now()), ms);
|
|
13
|
+
|
|
14
|
+
return channel;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export function $$tick(ms) {
|
|
18
|
+
const channel = $$chan(1, 0);
|
|
19
|
+
const timer = setInterval(() => {
|
|
20
|
+
const state = $$registry().states.get(channel.$$chan);
|
|
21
|
+
|
|
22
|
+
if (!state || state.closed) clearInterval(timer);
|
|
23
|
+
else $$try(channel.$$chan, true, Date.now());
|
|
24
|
+
}, ms);
|
|
25
|
+
|
|
26
|
+
return channel;
|
|
27
|
+
}
|
|
@@ -1,253 +0,0 @@
|
|
|
1
|
-
// The helpers of channels: sending, receiving and closing, here or in the thread that made the
|
|
2
|
-
// channel (thread-helpers.ts), and the queues of a channel. Receiving from a closed channel gives
|
|
3
|
-
// the zero value of its type, as in Go.
|
|
4
|
-
export const CHAN_HELPERS = {
|
|
5
|
-
chan: [
|
|
6
|
-
'$$registry().handle ??= { message: $$message, pump: $$pump };',
|
|
7
|
-
'',
|
|
8
|
-
'function $$chan(size, zero) {',
|
|
9
|
-
' const registry = $$registry();',
|
|
10
|
-
' const id = `${registry.key}:${registry.channels++}`;',
|
|
11
|
-
' const channel = { $$chan: id, size, zero };',
|
|
12
|
-
' registry.states.set(id, {',
|
|
13
|
-
' size,',
|
|
14
|
-
' zero,',
|
|
15
|
-
' buffer: [],',
|
|
16
|
-
' receivers: [],',
|
|
17
|
-
' senders: [],',
|
|
18
|
-
' closed: false,',
|
|
19
|
-
' holders: 1,',
|
|
20
|
-
' busy: false,',
|
|
21
|
-
' });',
|
|
22
|
-
' $$adopt(channel);',
|
|
23
|
-
' return channel;',
|
|
24
|
-
'}',
|
|
25
|
-
'',
|
|
26
|
-
'// A local operation waits in the queue of its channel; one of another thread asks the thread',
|
|
27
|
-
'// that made the channel.',
|
|
28
|
-
'function $$send(channel, value) {',
|
|
29
|
-
' const id = channel.$$chan;',
|
|
30
|
-
' const owner = $$ownerOf(id);',
|
|
31
|
-
' if (owner !== $$registry().key) return $$ask(owner, { op: "send", channel: id, value });',
|
|
32
|
-
' return new Promise((resolve, reject) => $$wait(id, "senders", { value, done: resolve, fail: reject }));',
|
|
33
|
-
'}',
|
|
34
|
-
'',
|
|
35
|
-
'function $$receive(channel, withOk = false) {',
|
|
36
|
-
' const id = channel.$$chan;',
|
|
37
|
-
' const owner = $$ownerOf(id);',
|
|
38
|
-
' const received =',
|
|
39
|
-
' owner !== $$registry().key',
|
|
40
|
-
' ? $$ask(owner, { op: "receive", channel: id })',
|
|
41
|
-
' : new Promise((resolve, reject) => $$wait(id, "receivers", { done: resolve, fail: reject }));',
|
|
42
|
-
' return withOk ? received : received.then(([value]) => value);',
|
|
43
|
-
'}',
|
|
44
|
-
'',
|
|
45
|
-
'// Closing a channel of another thread fails this one if it was closed already, as a panic would.',
|
|
46
|
-
'function $$close(channel) {',
|
|
47
|
-
' const id = channel.$$chan;',
|
|
48
|
-
' const owner = $$ownerOf(id);',
|
|
49
|
-
' if (owner === $$registry().key) return $$closeHere(id);',
|
|
50
|
-
' $$ask(owner, { op: "close", channel: id }).catch((error) => {',
|
|
51
|
-
' setTimeout(() => {',
|
|
52
|
-
' throw error;',
|
|
53
|
-
' });',
|
|
54
|
-
' });',
|
|
55
|
-
'}',
|
|
56
|
-
'',
|
|
57
|
-
'function $$closeHere(id) {',
|
|
58
|
-
' const state = $$registry().states.get(id);',
|
|
59
|
-
' if (state.closed) throw new Error("close of a closed channel");',
|
|
60
|
-
' state.closed = true;',
|
|
61
|
-
' $$pump(id);',
|
|
62
|
-
'}',
|
|
63
|
-
'',
|
|
64
|
-
'async function* $$range(channel) {',
|
|
65
|
-
' while (true) {',
|
|
66
|
-
' const [value, ok] = await $$receive(channel, true);',
|
|
67
|
-
' if (!ok) return;',
|
|
68
|
-
' yield value;',
|
|
69
|
-
' }',
|
|
70
|
-
'}',
|
|
71
|
-
'',
|
|
72
|
-
'function $$wait(id, queue, entry) {',
|
|
73
|
-
' const state = $$registry().states.get(id);',
|
|
74
|
-
' if (!state) return entry.fail?.($$stopped());',
|
|
75
|
-
' state[queue].push(entry);',
|
|
76
|
-
' $$pump(id);',
|
|
77
|
-
'}',
|
|
78
|
-
'',
|
|
79
|
-
'// The operations of other threads on the channels of this one.',
|
|
80
|
-
'function $$message(from, data) {',
|
|
81
|
-
' const answer = (value) => $$post(from, { answer: data.ask, value });',
|
|
82
|
-
' const fail = (error) => $$post(from, { answer: data.ask, error });',
|
|
83
|
-
' const select = { key: from, sid: data.sid };',
|
|
84
|
-
' switch (data.op) {',
|
|
85
|
-
' case "send":',
|
|
86
|
-
' return $$wait(data.channel, "senders", { from, value: data.value, done: answer, fail });',
|
|
87
|
-
' case "receive":',
|
|
88
|
-
' return $$wait(data.channel, "receivers", { from, done: answer, fail });',
|
|
89
|
-
' case "close":',
|
|
90
|
-
' try {',
|
|
91
|
-
' $$closeHere(data.channel);',
|
|
92
|
-
' return answer();',
|
|
93
|
-
' } catch (error) {',
|
|
94
|
-
' return fail(error);',
|
|
95
|
-
' }',
|
|
96
|
-
' case "try":',
|
|
97
|
-
' try {',
|
|
98
|
-
' return answer($$try(data.channel, data.send, data.value));',
|
|
99
|
-
' } catch (error) {',
|
|
100
|
-
' return fail(error);',
|
|
101
|
-
' }',
|
|
102
|
-
' case "select":',
|
|
103
|
-
' return $$select(data.cases, data.hasDefault).then(answer, fail);',
|
|
104
|
-
' case "wait":',
|
|
105
|
-
' return $$wait(data.channel, data.send ? "senders" : "receivers", {',
|
|
106
|
-
' select,',
|
|
107
|
-
' case: data.case,',
|
|
108
|
-
' value: data.value,',
|
|
109
|
-
' });',
|
|
110
|
-
' case "cancel":',
|
|
111
|
-
' return $$unwait(select, data.channels);',
|
|
112
|
-
' case "lock":',
|
|
113
|
-
' return answer($$lock(data.sid));',
|
|
114
|
-
' case "unlock":',
|
|
115
|
-
' return $$unlock(data.sid);',
|
|
116
|
-
' case "commit":',
|
|
117
|
-
' return $$commit(data.sid, data.case, data.result, data.error);',
|
|
118
|
-
' }',
|
|
119
|
-
'}',
|
|
120
|
-
'',
|
|
121
|
-
'// Matches what waits in a channel: a receiver with a value of the buffer, a sender or the zero value',
|
|
122
|
-
'// after close; a sender with room in the buffer. A case of a select goes on only if its select',
|
|
123
|
-
'// has not gone on with another case, which the thread of the select tells.',
|
|
124
|
-
'function $$pump(id) {',
|
|
125
|
-
' const state = $$registry().states.get(id);',
|
|
126
|
-
' while (state && !state.busy) {',
|
|
127
|
-
' const [receiver] = state.receivers;',
|
|
128
|
-
' const [sender] = state.senders;',
|
|
129
|
-
' if (receiver && (state.buffer.length > 0 || state.closed)) {',
|
|
130
|
-
' if (!$$pair(id, state, receiver, null)) return;',
|
|
131
|
-
' } else if (receiver && sender) {',
|
|
132
|
-
' if (!$$pair(id, state, receiver, sender)) return;',
|
|
133
|
-
' } else if (sender && (state.closed || state.buffer.length < state.size)) {',
|
|
134
|
-
' if (!$$pair(id, state, null, sender)) return;',
|
|
135
|
-
' } else {',
|
|
136
|
-
' return;',
|
|
137
|
-
' }',
|
|
138
|
-
' }',
|
|
139
|
-
'}',
|
|
140
|
-
'',
|
|
141
|
-
'// Whether the pump can go on at once: a pair whose selects are all in this thread is decided here.',
|
|
142
|
-
'function $$pair(id, state, receiver, sender) {',
|
|
143
|
-
' const entries = [receiver, sender].filter((entry) => entry !== null);',
|
|
144
|
-
' const key = $$registry().key;',
|
|
145
|
-
' if (entries.some((entry) => entry.select && entry.select.key !== key)) {',
|
|
146
|
-
' $$pairLater(id, state, receiver, sender);',
|
|
147
|
-
' return false;',
|
|
148
|
-
' }',
|
|
149
|
-
' const locks = entries.map((entry) => (entry.select ? $$lock(entry.select.sid) : "locked"));',
|
|
150
|
-
' if (locks.every((lock) => lock === "locked")) {',
|
|
151
|
-
' $$complete(state, receiver, sender);',
|
|
152
|
-
' return true;',
|
|
153
|
-
' }',
|
|
154
|
-
' entries.forEach((entry, i) => locks[i] === "locked" && $$unlockAt(entry.select));',
|
|
155
|
-
' return $$settle(id, state, entries, locks);',
|
|
156
|
-
'}',
|
|
157
|
-
'',
|
|
158
|
-
'// A pair with a select of another thread: the channel waits for the answers of that thread.',
|
|
159
|
-
'async function $$pairLater(id, state, receiver, sender) {',
|
|
160
|
-
' state.busy = true;',
|
|
161
|
-
' const entries = [receiver, sender].filter((entry) => entry !== null);',
|
|
162
|
-
' const locks = [];',
|
|
163
|
-
' for (const { select } of entries) {',
|
|
164
|
-
' const lock = !select',
|
|
165
|
-
' ? "locked"',
|
|
166
|
-
' : select.key === $$registry().key',
|
|
167
|
-
' ? $$lock(select.sid)',
|
|
168
|
-
' : await $$ask(select.key, { op: "lock", sid: select.sid }).catch(() => "done");',
|
|
169
|
-
' locks.push(lock);',
|
|
170
|
-
' if (lock !== "locked") break;',
|
|
171
|
-
' }',
|
|
172
|
-
' state.busy = false;',
|
|
173
|
-
' if (locks.length === entries.length && locks.every((lock) => lock === "locked")) {',
|
|
174
|
-
' $$complete(state, receiver, sender);',
|
|
175
|
-
' } else {',
|
|
176
|
-
' entries.forEach((entry, i) => locks[i] === "locked" && $$unlockAt(entry.select));',
|
|
177
|
-
' if (!$$settle(id, state, entries, locks)) return;',
|
|
178
|
-
' }',
|
|
179
|
-
' $$pump(id);',
|
|
180
|
-
'}',
|
|
181
|
-
'',
|
|
182
|
-
'// After a pair that could not go on: a select that has gone on leaves the queue; one that another',
|
|
183
|
-
'// channel is deciding is tried again a little later.',
|
|
184
|
-
'function $$settle(id, state, entries, locks) {',
|
|
185
|
-
' entries.forEach((entry, i) => {',
|
|
186
|
-
' if (locks[i] === "done") $$remove(state, entry);',
|
|
187
|
-
' });',
|
|
188
|
-
' if (locks.includes("done")) return true;',
|
|
189
|
-
' setTimeout(() => $$pump(id), 1 + Math.random() * 4);',
|
|
190
|
-
' return false;',
|
|
191
|
-
'}',
|
|
192
|
-
'',
|
|
193
|
-
'function $$remove(state, entry) {',
|
|
194
|
-
' for (const queue of [state.receivers, state.senders]) {',
|
|
195
|
-
' const at = queue.indexOf(entry);',
|
|
196
|
-
' if (at >= 0) queue.splice(at, 1);',
|
|
197
|
-
' }',
|
|
198
|
-
'}',
|
|
199
|
-
'',
|
|
200
|
-
'// Takes the pair off the queues and passes the value.',
|
|
201
|
-
'function $$complete(state, receiver, sender) {',
|
|
202
|
-
' if (receiver) $$remove(state, receiver);',
|
|
203
|
-
' if (sender) $$remove(state, sender);',
|
|
204
|
-
' if (receiver) {',
|
|
205
|
-
' const result =',
|
|
206
|
-
' state.buffer.length > 0',
|
|
207
|
-
' ? [state.buffer.shift(), true]',
|
|
208
|
-
' : sender',
|
|
209
|
-
' ? [sender.value, true]',
|
|
210
|
-
' : [Array.isArray(state.zero) ? [] : state.zero, false];',
|
|
211
|
-
' $$finish(receiver, result);',
|
|
212
|
-
' if (sender) $$finish(sender);',
|
|
213
|
-
' } else if (state.closed) {',
|
|
214
|
-
' $$finish(sender, undefined, new Error("send on a closed channel"));',
|
|
215
|
-
' } else {',
|
|
216
|
-
' state.buffer.push(sender.value);',
|
|
217
|
-
' $$finish(sender);',
|
|
218
|
-
' }',
|
|
219
|
-
'}',
|
|
220
|
-
'',
|
|
221
|
-
'function $$finish(entry, result, error) {',
|
|
222
|
-
' const { select } = entry;',
|
|
223
|
-
' if (!select) return error ? entry.fail(error) : entry.done(result);',
|
|
224
|
-
' if (select.key === $$registry().key) return $$commit(select.sid, entry.case, result, error);',
|
|
225
|
-
' $$post(select.key, { op: "commit", sid: select.sid, case: entry.case, result, error });',
|
|
226
|
-
'}',
|
|
227
|
-
'',
|
|
228
|
-
'function $$unlockAt(select) {',
|
|
229
|
-
' if (!select) return;',
|
|
230
|
-
' if (select.key === $$registry().key) $$unlock(select.sid);',
|
|
231
|
-
' else $$post(select.key, { op: "unlock", sid: select.sid });',
|
|
232
|
-
'}',
|
|
233
|
-
],
|
|
234
|
-
timers: [
|
|
235
|
-
'// `after(ms)`: a channel that gets the time once, after `ms`. `tick(ms)`: one that gets it every',
|
|
236
|
-
'// `ms` until it is closed; a tick that finds the last one still there is dropped, as in Go.',
|
|
237
|
-
'function $$after(ms) {',
|
|
238
|
-
' const channel = $$chan(1, 0);',
|
|
239
|
-
' setTimeout(() => $$send(channel, Date.now()), ms);',
|
|
240
|
-
' return channel;',
|
|
241
|
-
'}',
|
|
242
|
-
'',
|
|
243
|
-
'function $$tick(ms) {',
|
|
244
|
-
' const channel = $$chan(1, 0);',
|
|
245
|
-
' const timer = setInterval(() => {',
|
|
246
|
-
' const state = $$registry().states.get(channel.$$chan);',
|
|
247
|
-
' if (!state || state.closed) clearInterval(timer);',
|
|
248
|
-
' else $$try(channel.$$chan, true, Date.now());',
|
|
249
|
-
' }, ms);',
|
|
250
|
-
' return channel;',
|
|
251
|
-
'}',
|
|
252
|
-
],
|
|
253
|
-
};
|
|
@@ -1,9 +0,0 @@
|
|
|
1
|
-
export type GoHelper = 'go' | 'goLocal' | 'goFor' | 'shared' | 'serve' | 'cancel';
|
|
2
|
-
export declare const GO_HELPERS: Readonly<Record<GoHelper, readonly string[]>>;
|
|
3
|
-
/**
|
|
4
|
-
* The start of a worker of a module: a Web Worker, or a worker thread of Node. Node takes only the
|
|
5
|
-
* path of a `file:` URL, without the query of `app.rk?goroutines`, so its worker starts with a
|
|
6
|
-
* module that imports the whole URL. An absolute URL, such as the Blob of the playground, is used
|
|
7
|
-
* as it is.
|
|
8
|
-
*/
|
|
9
|
-
export declare function startWorker(url: string): string[];
|