rooklang 0.3.2 → 0.3.4
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 +32 -7
- package/README.ru.md +32 -7
- package/dist/ast/declarations.d.ts +20 -1
- package/dist/ast/expressions.d.ts +31 -1
- package/dist/ast/markup.d.ts +6 -1
- package/dist/ast/statements.d.ts +25 -1
- package/dist/auto-import.d.ts +11 -0
- package/dist/auto-import.js +81 -0
- package/dist/breaks.d.ts +6 -0
- package/dist/breaks.js +36 -0
- package/dist/build.d.ts +7 -0
- package/dist/build.js +23 -2
- package/dist/checker/array-literals.d.ts +6 -0
- package/dist/checker/array-literals.js +59 -0
- package/dist/checker/assignments.d.ts +7 -0
- package/dist/checker/assignments.js +48 -8
- package/dist/checker/base.d.ts +2 -1
- package/dist/checker/base.js +6 -12
- package/dist/checker/builtins.js +15 -5
- package/dist/checker/bytes.d.ts +2 -2
- package/dist/checker/bytes.js +2 -2
- package/dist/checker/calls.js +3 -0
- package/dist/checker/channel-objects.d.ts +3 -0
- package/dist/checker/channel-objects.js +40 -0
- package/dist/checker/channels.d.ts +1 -0
- package/dist/checker/channels.js +16 -2
- package/dist/checker/checker.d.ts +1 -1
- package/dist/checker/checker.js +19 -0
- package/dist/checker/class-members.d.ts +22 -0
- package/dist/checker/class-members.js +105 -0
- package/dist/checker/classes.d.ts +5 -4
- package/dist/checker/classes.js +41 -12
- package/dist/checker/closing.d.ts +21 -0
- package/dist/checker/closing.js +164 -0
- package/dist/checker/completion.d.ts +2 -2
- package/dist/checker/completion.js +2 -2
- package/dist/checker/context.d.ts +19 -0
- package/dist/checker/control-flow.d.ts +6 -0
- package/dist/checker/control-flow.js +34 -0
- package/dist/checker/declarations.js +1 -0
- package/dist/checker/decode.d.ts +8 -0
- package/dist/checker/decode.js +32 -0
- package/dist/checker/error-results.d.ts +6 -0
- package/dist/checker/error-results.js +31 -0
- package/dist/checker/expressions.d.ts +5 -1
- package/dist/checker/expressions.js +13 -34
- package/dist/checker/flow.js +8 -4
- package/dist/checker/helpers.d.ts +1 -1
- package/dist/checker/helpers.js +9 -2
- package/dist/checker/iterables.d.ts +3 -1
- package/dist/checker/iterables.js +13 -0
- package/dist/checker/iterators.d.ts +4 -1
- package/dist/checker/iterators.js +12 -4
- package/dist/checker/json.d.ts +5 -0
- package/dist/checker/json.js +92 -0
- package/dist/checker/loops.js +23 -3
- package/dist/checker/members.js +7 -0
- package/dist/checker/operators.d.ts +2 -2
- package/dist/checker/operators.js +7 -5
- package/dist/checker/queues.d.ts +2 -2
- package/dist/checker/queues.js +2 -2
- package/dist/checker/resolution.js +5 -3
- package/dist/checker/signals.d.ts +8 -0
- package/dist/checker/signals.js +48 -0
- package/dist/checker/statements.d.ts +5 -0
- package/dist/checker/statements.js +19 -0
- package/dist/checker/topics.d.ts +6 -0
- package/dist/checker/topics.js +17 -0
- package/dist/checker/transfer.d.ts +3 -1
- package/dist/checker/transfer.js +25 -5
- package/dist/checker/try.d.ts +6 -0
- package/dist/checker/try.js +19 -0
- package/dist/checker/types/constructors.d.ts +1 -1
- package/dist/checker/types/constructors.js +2 -2
- package/dist/checker/types/generics.js +1 -1
- package/dist/checker/types/iterables.d.ts +13 -0
- package/dist/checker/types/iterables.js +52 -0
- package/dist/checker/types/model.d.ts +25 -0
- package/dist/checker/types/params.js +22 -3
- package/dist/checker/types/printing.js +4 -3
- package/dist/checker/types/relations.js +19 -25
- package/dist/checker/types/topic.d.ts +6 -0
- package/dist/checker/types/topic.js +23 -0
- package/dist/checker/types.d.ts +2 -0
- package/dist/checker/types.js +2 -0
- package/dist/cli.js +18 -36
- package/dist/codegen/calls.d.ts +23 -0
- package/dist/codegen/calls.js +88 -7
- package/dist/codegen/control-statements.d.ts +19 -3
- package/dist/codegen/control-statements.js +105 -20
- package/dist/codegen/decode.d.ts +14 -0
- package/dist/codegen/decode.js +93 -0
- package/dist/codegen/emitter.d.ts +10 -41
- package/dist/codegen/emitter.js +9 -4
- package/dist/codegen/expressions.d.ts +9 -0
- package/dist/codegen/expressions.js +15 -24
- package/dist/codegen/js.d.ts +2 -2
- package/dist/codegen/js.js +5 -2
- package/dist/codegen/options.d.ts +40 -0
- package/dist/codegen/options.js +1 -0
- package/dist/codegen/runtime.d.ts +7 -5
- package/dist/codegen/runtime.js +28 -20
- package/dist/codegen/simple-statements.d.ts +10 -4
- package/dist/codegen/simple-statements.js +40 -15
- package/dist/codegen/statements.js +64 -4
- package/dist/codegen/try.d.ts +5 -0
- package/dist/codegen/try.js +40 -0
- package/dist/compile.d.ts +9 -0
- package/dist/compile.js +19 -6
- package/dist/completion.d.ts +4 -2
- package/dist/completion.js +10 -5
- package/dist/declarations.js +32 -6
- package/dist/decorator-copy.js +5 -2
- package/dist/goroutines.d.ts +1 -1
- package/dist/goroutines.js +5 -4
- package/dist/index.d.ts +6 -2
- package/dist/index.js +14 -5
- package/dist/lexer/base.d.ts +2 -0
- package/dist/lexer/base.js +6 -1
- package/dist/lexer/lexer.js +3 -0
- package/dist/lexer/literals.d.ts +5 -0
- package/dist/lexer/literals.js +51 -1
- package/dist/lexer/markup.js +3 -4
- package/dist/lexer/tables.js +2 -0
- package/dist/lexer/token.d.ts +4 -3
- package/dist/lexer/token.js +0 -2
- package/dist/loader.js +40 -13
- package/dist/parser/base.d.ts +4 -0
- package/dist/parser/base.js +2 -1
- package/dist/parser/classes.d.ts +2 -0
- package/dist/parser/classes.js +88 -23
- package/dist/parser/control-flow.d.ts +3 -1
- package/dist/parser/control-flow.js +21 -24
- package/dist/parser/declarations.d.ts +7 -4
- package/dist/parser/declarations.js +45 -6
- package/dist/parser/expressions.js +27 -3
- package/dist/parser/jumps.d.ts +11 -0
- package/dist/parser/jumps.js +77 -0
- package/dist/parser/literals.d.ts +9 -0
- package/dist/parser/literals.js +45 -6
- package/dist/parser/markup.js +1 -1
- package/dist/parser/simple-statements.d.ts +1 -1
- package/dist/parser/simple-statements.js +16 -9
- package/dist/parser/statements.d.ts +0 -3
- package/dist/parser/statements.js +22 -25
- package/dist/parser/syntax.d.ts +11 -1
- package/dist/parser/syntax.js +16 -2
- package/dist/parser/types.js +8 -3
- package/dist/project-modules.d.ts +12 -0
- package/dist/project-modules.js +106 -0
- package/dist/run-file.d.ts +6 -0
- package/dist/run-file.js +111 -0
- package/dist/runtime/sources.js +11 -5
- package/dist/runtime-module.d.ts +1 -0
- package/dist/runtime-module.js +3 -0
- package/dist/source-map.d.ts +17 -0
- package/dist/source-map.js +75 -0
- package/dist/ts-types.d.ts +4 -0
- package/dist/ts-types.js +20 -3
- package/dist/typescript/convert-objects.d.ts +2 -1
- package/dist/typescript/convert-objects.js +18 -9
- package/dist/vite.js +13 -5
- package/dist/walk-types.js +1 -0
- package/dist/walk.d.ts +0 -2
- package/dist/walk.js +11 -28
- package/package.json +1 -1
- package/src/runtime/chan.js +15 -5
- package/src/runtime/channel.js +28 -0
- package/src/runtime/classes.js +32 -0
- package/src/runtime/core.js +10 -1
- package/src/runtime/decode.js +190 -0
- package/src/runtime/go.js +14 -5
- package/src/runtime/index.js +6 -0
- package/src/runtime/select.js +14 -9
- package/src/runtime/signals.js +59 -0
- package/src/runtime/threads.js +32 -5
- package/src/runtime/topic.js +118 -0
- package/src/runtime/try.js +45 -0
package/dist/walk.js
CHANGED
|
@@ -30,7 +30,7 @@ export function forEachChild(node, visit) {
|
|
|
30
30
|
visit(node.name);
|
|
31
31
|
each(node.params);
|
|
32
32
|
each(node.results);
|
|
33
|
-
|
|
33
|
+
optional(node.body);
|
|
34
34
|
break;
|
|
35
35
|
case 'Parameter':
|
|
36
36
|
visit(node.name);
|
|
@@ -111,11 +111,16 @@ export function forEachChild(node, visit) {
|
|
|
111
111
|
visit(node.element);
|
|
112
112
|
optional(node.items);
|
|
113
113
|
break;
|
|
114
|
+
case 'TopicExpression':
|
|
115
|
+
visit(node.element);
|
|
116
|
+
visit(node.name);
|
|
117
|
+
break;
|
|
114
118
|
case 'MakeExpression':
|
|
115
119
|
visit(node.type);
|
|
116
120
|
visit(node.length);
|
|
117
121
|
break;
|
|
118
122
|
case 'ConversionExpression':
|
|
123
|
+
case 'DecodeExpression':
|
|
119
124
|
visit(node.type);
|
|
120
125
|
visit(node.argument);
|
|
121
126
|
break;
|
|
@@ -123,6 +128,7 @@ export function forEachChild(node, visit) {
|
|
|
123
128
|
each(node.values);
|
|
124
129
|
break;
|
|
125
130
|
case 'IfStatement':
|
|
131
|
+
optional(node.init);
|
|
126
132
|
visit(node.condition);
|
|
127
133
|
visit(node.consequent);
|
|
128
134
|
optional(node.alternate);
|
|
@@ -140,6 +146,7 @@ export function forEachChild(node, visit) {
|
|
|
140
146
|
visit(node.body);
|
|
141
147
|
break;
|
|
142
148
|
case 'SwitchStatement':
|
|
149
|
+
optional(node.init);
|
|
143
150
|
optional(node.discriminant);
|
|
144
151
|
each(node.cases);
|
|
145
152
|
break;
|
|
@@ -204,6 +211,7 @@ export function forEachChild(node, visit) {
|
|
|
204
211
|
visit(node.body);
|
|
205
212
|
break;
|
|
206
213
|
case 'UnaryExpression':
|
|
214
|
+
case 'TryExpression':
|
|
207
215
|
visit(node.argument);
|
|
208
216
|
break;
|
|
209
217
|
case 'GoExpression':
|
|
@@ -265,6 +273,7 @@ export function forEachChild(node, visit) {
|
|
|
265
273
|
case 'ArrayType':
|
|
266
274
|
case 'ChanType':
|
|
267
275
|
case 'QueueType':
|
|
276
|
+
case 'TopicType':
|
|
268
277
|
case 'NullableType':
|
|
269
278
|
case 'FuncType':
|
|
270
279
|
case 'ComponentType':
|
|
@@ -287,6 +296,7 @@ export function forEachChild(node, visit) {
|
|
|
287
296
|
case 'NumberLiteral':
|
|
288
297
|
case 'BigIntLiteral':
|
|
289
298
|
case 'StringLiteral':
|
|
299
|
+
case 'RegExpLiteral':
|
|
290
300
|
case 'TemplateElement':
|
|
291
301
|
case 'BooleanLiteral':
|
|
292
302
|
case 'NullLiteral':
|
|
@@ -295,30 +305,3 @@ export function forEachChild(node, visit) {
|
|
|
295
305
|
break;
|
|
296
306
|
}
|
|
297
307
|
}
|
|
298
|
-
/** Whether a `break` in these statements leaves the enclosing loop or switch. */
|
|
299
|
-
export function containsBreak(statements) {
|
|
300
|
-
let found = false;
|
|
301
|
-
const visit = (node) => {
|
|
302
|
-
switch (node.kind) {
|
|
303
|
-
case 'BreakStatement':
|
|
304
|
-
found = true;
|
|
305
|
-
return;
|
|
306
|
-
// A `break` inside these belongs to them.
|
|
307
|
-
case 'ForStatement':
|
|
308
|
-
case 'ForInStatement':
|
|
309
|
-
case 'SwitchStatement':
|
|
310
|
-
case 'SelectStatement':
|
|
311
|
-
case 'FuncDeclaration':
|
|
312
|
-
case 'FuncExpression':
|
|
313
|
-
case 'ArrowFunction':
|
|
314
|
-
case 'ClassDeclaration':
|
|
315
|
-
case 'MountStatement':
|
|
316
|
-
case 'EffectStatement':
|
|
317
|
-
return;
|
|
318
|
-
default:
|
|
319
|
-
forEachChild(node, visit);
|
|
320
|
-
}
|
|
321
|
-
};
|
|
322
|
-
statements.forEach(visit);
|
|
323
|
-
return found;
|
|
324
|
-
}
|
package/package.json
CHANGED
package/src/runtime/chan.js
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
// Channels: sending, receiving and closing, here or in the thread that made the channel
|
|
2
2
|
// (threads.js), and the queues of a channel. Receiving from a closed channel gives
|
|
3
|
-
// the zero value of its type, as in Go.
|
|
3
|
+
// the zero value of its type, as in Go. Channel.EMPTY is closed and belongs to no thread: a send to
|
|
4
|
+
// it goes nowhere, and receiving gives the zero value that the code that receives passes.
|
|
4
5
|
|
|
5
6
|
import { $$Queue } from './queue.js';
|
|
6
7
|
import { $$commit, $$lock, $$select, $$try, $$unlock, $$unwait } from './select.js';
|
|
7
|
-
import { $$adopt, $$ask, $$ownerOf, $$post, $$registry, $$stopped } from './threads.js';
|
|
8
|
+
import { $$adopt, $$ask, $$isEmpty, $$ownerOf, $$post, $$registry, $$stopped } from './threads.js';
|
|
8
9
|
|
|
9
10
|
$$registry().handle ??= { message: $$message, pump: $$pump };
|
|
10
11
|
|
|
@@ -32,6 +33,9 @@ export function $$chan(size, zero) {
|
|
|
32
33
|
// that made the channel.
|
|
33
34
|
export function $$send(channel, value) {
|
|
34
35
|
const id = channel.$$chan;
|
|
36
|
+
|
|
37
|
+
if ($$isEmpty(id)) return Promise.resolve();
|
|
38
|
+
|
|
35
39
|
const owner = $$ownerOf(id);
|
|
36
40
|
|
|
37
41
|
if (owner !== $$registry().key) return $$ask(owner, { op: 'send', channel: id, value });
|
|
@@ -41,11 +45,12 @@ export function $$send(channel, value) {
|
|
|
41
45
|
);
|
|
42
46
|
}
|
|
43
47
|
|
|
44
|
-
export function $$receive(channel, withOk = false) {
|
|
48
|
+
export function $$receive(channel, withOk = false, zero = null) {
|
|
45
49
|
const id = channel.$$chan;
|
|
46
50
|
const owner = $$ownerOf(id);
|
|
47
|
-
const received =
|
|
48
|
-
|
|
51
|
+
const received = $$isEmpty(id)
|
|
52
|
+
? Promise.resolve([zero, false])
|
|
53
|
+
: owner !== $$registry().key
|
|
49
54
|
? $$ask(owner, { op: 'receive', channel: id })
|
|
50
55
|
: new Promise((resolve, reject) => $$wait(id, 'receivers', { done: resolve, fail: reject }));
|
|
51
56
|
|
|
@@ -55,6 +60,9 @@ export function $$receive(channel, withOk = false) {
|
|
|
55
60
|
// Closing a channel of another thread fails this one if it was closed already, as a panic would.
|
|
56
61
|
export function $$close(channel) {
|
|
57
62
|
const id = channel.$$chan;
|
|
63
|
+
|
|
64
|
+
if ($$isEmpty(id)) return;
|
|
65
|
+
|
|
58
66
|
const owner = $$ownerOf(id);
|
|
59
67
|
|
|
60
68
|
if (owner === $$registry().key) return $$closeHere(id);
|
|
@@ -70,6 +78,8 @@ export function $$closeHere(id) {
|
|
|
70
78
|
|
|
71
79
|
if (state.closed) throw new Error('close of a closed channel');
|
|
72
80
|
state.closed = true;
|
|
81
|
+
// What made the channel stops with it, as signal() does (signals.js).
|
|
82
|
+
state.onClose?.();
|
|
73
83
|
$$pump(id);
|
|
74
84
|
}
|
|
75
85
|
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
// `Channel`: what Rook code and the code compiled from it do with channels, by name. The functions
|
|
2
|
+
// themselves are in chan.js, select.js, signals.js, streams.js and timers.js.
|
|
3
|
+
|
|
4
|
+
import { $$chan, $$close, $$range, $$receive, $$send } from './chan.js';
|
|
5
|
+
import { $$select, $$try } from './select.js';
|
|
6
|
+
import { $$abortSignal, $$done, $$signal } from './signals.js';
|
|
7
|
+
import { $$fromStream, $$stream } from './streams.js';
|
|
8
|
+
import { $$after, $$tick } from './timers.js';
|
|
9
|
+
|
|
10
|
+
export const $$Channel = Object.freeze({
|
|
11
|
+
// The zero value of channel types: closed, and the same in every thread (threads.js).
|
|
12
|
+
EMPTY: Object.freeze({ $$chan: 'empty', size: 0, zero: null }),
|
|
13
|
+
make: $$chan,
|
|
14
|
+
send: $$send,
|
|
15
|
+
receive: $$receive,
|
|
16
|
+
close: $$close,
|
|
17
|
+
range: $$range,
|
|
18
|
+
select: $$select,
|
|
19
|
+
// [] for a send and [value, ok] for a receive that goes on at once, else null.
|
|
20
|
+
try: (channel, send, value) => $$try(channel.$$chan, send, value),
|
|
21
|
+
fromStream: $$fromStream,
|
|
22
|
+
stream: $$stream,
|
|
23
|
+
after: $$after,
|
|
24
|
+
tick: $$tick,
|
|
25
|
+
signal: $$signal,
|
|
26
|
+
done: $$done,
|
|
27
|
+
abortSignal: $$abortSignal,
|
|
28
|
+
});
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
// Data classes: `data class Point` registers its class with an id of its own, the name and a hash
|
|
2
|
+
// of its code, the same in a module and in its worker module, which has a copy of the class. A copy
|
|
3
|
+
// of a value that goes to another thread loses the class of its instances (structuredClone); the
|
|
4
|
+
// thread that sends lists them with their ids (threads.js), and the one that gets the copy gives
|
|
5
|
+
// them their class again.
|
|
6
|
+
|
|
7
|
+
function registry() {
|
|
8
|
+
return (globalThis[Symbol.for('rook.classes')] ??= { byId: new Map(), ids: new WeakMap() });
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export function $$dataClass(cls, id) {
|
|
12
|
+
const { byId, ids } = registry();
|
|
13
|
+
|
|
14
|
+
byId.set(id, cls.prototype);
|
|
15
|
+
ids.set(cls.prototype, id);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/** The id of the data class of an instance, or undefined. */
|
|
19
|
+
export function $$classOf(value) {
|
|
20
|
+
return registry().ids.get(Object.getPrototypeOf(value));
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/** Gives the copies of instances their classes back: `[object, id]` pairs of a copied message. */
|
|
24
|
+
export function $$reclass(instances) {
|
|
25
|
+
const { byId } = registry();
|
|
26
|
+
|
|
27
|
+
for (const [object, id] of instances) {
|
|
28
|
+
const prototype = byId.get(id);
|
|
29
|
+
|
|
30
|
+
if (prototype) Object.setPrototypeOf(object, prototype);
|
|
31
|
+
}
|
|
32
|
+
}
|
package/src/runtime/core.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
// What the code of any module may need: `for` over a value of unknown type, and
|
|
1
|
+
// What the code of any module may need: `for` over a value of unknown type, `defer`, and wrapped
|
|
2
|
+
// errors.
|
|
2
3
|
|
|
3
4
|
// `for v in x` over an untyped value: a Map gives its values, as a typed one does.
|
|
4
5
|
export function $$values(iterable) {
|
|
@@ -21,3 +22,11 @@ export function $$runDeferred(deferred) {
|
|
|
21
22
|
|
|
22
23
|
if (failure) throw failure.error;
|
|
23
24
|
}
|
|
25
|
+
|
|
26
|
+
// `error("load user", err)`: an error whose message ends with that of its cause, as `%w` of Go
|
|
27
|
+
// gives "load user: not found", and whose `cause` is the error itself, for code that looks at it.
|
|
28
|
+
export function $$wrap(message, cause) {
|
|
29
|
+
const reason = cause instanceof Error ? cause.message : String(cause);
|
|
30
|
+
|
|
31
|
+
return new Error(`${message}: ${reason}`, { cause });
|
|
32
|
+
}
|
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
// `decode<T>(x)`: JSON text, or a value from JS, checked against the type T, which the compiler
|
|
2
|
+
// writes as data: "number", "string", "bool", "any", { literal: [values] }, { nullable: T },
|
|
3
|
+
// { array: T }, { record: T }, { union: [types] } and $$type(name, fields) for an object. A value
|
|
4
|
+
// that fits is returned as it is, with the fields that T does not have; a nullable field may be
|
|
5
|
+
// missing. The error says where the value does not fit: `decode User: tags[1]: expected string,
|
|
6
|
+
// got 42`.
|
|
7
|
+
|
|
8
|
+
/** An object type. Its fields come from a function, so that types can refer to each other. */
|
|
9
|
+
export function $$type(name, fields) {
|
|
10
|
+
let resolved = null;
|
|
11
|
+
|
|
12
|
+
return {
|
|
13
|
+
name,
|
|
14
|
+
get fields() {
|
|
15
|
+
return (resolved ??= fields());
|
|
16
|
+
},
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/** `decode<T>(text)` of a string: the JSON text is parsed, then checked. */
|
|
21
|
+
export function $$decodeJson(text, type) {
|
|
22
|
+
let value;
|
|
23
|
+
|
|
24
|
+
try {
|
|
25
|
+
value = JSON.parse(text);
|
|
26
|
+
} catch (error) {
|
|
27
|
+
return [null, new Error(`decode ${describe(type)}: invalid JSON: ${error.message}`)];
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
return $$decode(value, type);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* `decode<T>(value)` of a value from JS. Without the types of the checker (`rook build --no-check`)
|
|
35
|
+
* the type is null: then a string is parsed as JSON text, and nothing is checked.
|
|
36
|
+
*/
|
|
37
|
+
export function $$decode(value, type) {
|
|
38
|
+
if (type === null) return typeof value === 'string' ? $$decodeJson(value, 'any') : [value, null];
|
|
39
|
+
|
|
40
|
+
const problem = check(value, type, []);
|
|
41
|
+
|
|
42
|
+
if (problem === null) return [value, null];
|
|
43
|
+
|
|
44
|
+
const where = problem.path.length > 0 ? `${pathText(problem.path)}: ` : '';
|
|
45
|
+
|
|
46
|
+
return [null, new Error(`decode ${describe(type)}: ${where}${problem.message}`)];
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const PRIMITIVES = { number: 'number', string: 'string', bool: 'boolean' };
|
|
50
|
+
|
|
51
|
+
/** Where and how the value does not fit the type, or null if it does. */
|
|
52
|
+
function check(value, type, path) {
|
|
53
|
+
if (typeof type === 'string') {
|
|
54
|
+
return type === 'any' || typeof value === PRIMITIVES[type] ? null : mismatch(value, type, path);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
if ('literal' in type) return type.literal.includes(value) ? null : mismatch(value, type, path);
|
|
58
|
+
|
|
59
|
+
if ('nullable' in type) {
|
|
60
|
+
if (value == null) return null;
|
|
61
|
+
|
|
62
|
+
const problem = check(value, type.nullable, path);
|
|
63
|
+
|
|
64
|
+
// The value itself is of another type: the message names ?T, not T.
|
|
65
|
+
return problem !== null && problem.path.length === path.length
|
|
66
|
+
? mismatch(value, type, path)
|
|
67
|
+
: problem;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
if ('array' in type) {
|
|
71
|
+
if (!Array.isArray(value)) return mismatch(value, type, path);
|
|
72
|
+
|
|
73
|
+
for (let i = 0; i < value.length; i++) {
|
|
74
|
+
const problem = check(value[i], type.array, [...path, i]);
|
|
75
|
+
|
|
76
|
+
if (problem !== null) return problem;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
return null;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
if ('union' in type) return checkUnion(value, type, path);
|
|
83
|
+
if (!isObject(value)) return mismatch(value, type, path);
|
|
84
|
+
|
|
85
|
+
if ('record' in type) {
|
|
86
|
+
for (const key of Object.keys(value)) {
|
|
87
|
+
const problem = check(value[key], type.record, [...path, key]);
|
|
88
|
+
|
|
89
|
+
if (problem !== null) return problem;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
return null;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
for (const [name, field] of Object.entries(type.fields)) {
|
|
96
|
+
// Only own fields: a field named `toString` is not the method of every object.
|
|
97
|
+
if (!Object.hasOwn(value, name)) {
|
|
98
|
+
if (isOptional(field)) continue;
|
|
99
|
+
|
|
100
|
+
return { path: [...path, name], message: 'missing' };
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
const problem = check(value[name], field, [...path, name]);
|
|
104
|
+
|
|
105
|
+
if (problem !== null) return problem;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
return null;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* The value fits one of the types. If none fits, the type whose check went deepest into the value
|
|
113
|
+
* is most likely the one meant, `{ kind: "circle", r: "2" }` of `Circle | Square`, so its problem
|
|
114
|
+
* is the one reported.
|
|
115
|
+
*/
|
|
116
|
+
function checkUnion(value, type, path) {
|
|
117
|
+
let deepest = null;
|
|
118
|
+
|
|
119
|
+
for (const member of type.union) {
|
|
120
|
+
const problem = check(value, member, path);
|
|
121
|
+
|
|
122
|
+
if (problem === null) return null;
|
|
123
|
+
if (problem.path.length > (deepest?.path.length ?? path.length)) deepest = problem;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
return deepest ?? mismatch(value, type, path);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
function isObject(value) {
|
|
130
|
+
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
function isOptional(type) {
|
|
134
|
+
return type === 'any' || (typeof type === 'object' && 'nullable' in type);
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
function mismatch(value, type, path) {
|
|
138
|
+
return { path, message: `expected ${describe(type)}, got ${shown(value)}` };
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/** The type as Rook writes it: `[]string`, `?User`, `"admin" | "user"`. */
|
|
142
|
+
function describe(type) {
|
|
143
|
+
if (typeof type === 'string') return type;
|
|
144
|
+
if ('literal' in type) return type.literal.map((value) => JSON.stringify(value)).join(' | ');
|
|
145
|
+
if ('nullable' in type) return `?${grouped(type.nullable)}`;
|
|
146
|
+
if ('array' in type) return `[]${grouped(type.array)}`;
|
|
147
|
+
if ('record' in type) return `{ [key string]: ${describe(type.record)} }`;
|
|
148
|
+
if ('union' in type) return type.union.map(describe).join(' | ');
|
|
149
|
+
|
|
150
|
+
return type.name;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
function grouped(type) {
|
|
154
|
+
const several = typeof type === 'object' && ('union' in type || type.literal?.length > 1);
|
|
155
|
+
|
|
156
|
+
return several ? `(${describe(type)})` : describe(type);
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
/** The value in a message: a primitive itself, a long string cut, and the kind of anything else. */
|
|
160
|
+
function shown(value) {
|
|
161
|
+
if (value === null) return 'null';
|
|
162
|
+
if (Array.isArray(value)) return 'an array';
|
|
163
|
+
|
|
164
|
+
switch (typeof value) {
|
|
165
|
+
case 'string':
|
|
166
|
+
return JSON.stringify(value.length > 40 ? `${value.slice(0, 40)}…` : value);
|
|
167
|
+
|
|
168
|
+
case 'number':
|
|
169
|
+
case 'boolean':
|
|
170
|
+
return String(value);
|
|
171
|
+
|
|
172
|
+
case 'object':
|
|
173
|
+
return 'an object';
|
|
174
|
+
|
|
175
|
+
default:
|
|
176
|
+
return typeof value;
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
/** `tags[1]`, `boss.name`, `["first name"]`. */
|
|
181
|
+
function pathText(path) {
|
|
182
|
+
return path
|
|
183
|
+
.map((key, i) => {
|
|
184
|
+
if (typeof key === 'number') return `[${key}]`;
|
|
185
|
+
if (/^[A-Za-z_$][\w$]*$/.test(key)) return i === 0 ? key : `.${key}`;
|
|
186
|
+
|
|
187
|
+
return `[${JSON.stringify(key)}]`;
|
|
188
|
+
})
|
|
189
|
+
.join('');
|
|
190
|
+
}
|
package/src/runtime/go.js
CHANGED
|
@@ -9,6 +9,7 @@ import {
|
|
|
9
9
|
$$gone,
|
|
10
10
|
$$incoming,
|
|
11
11
|
$$post,
|
|
12
|
+
$$reclassed,
|
|
12
13
|
$$registry,
|
|
13
14
|
$$transfer,
|
|
14
15
|
} from './threads.js';
|
|
@@ -142,6 +143,7 @@ export function $$pool() {
|
|
|
142
143
|
worker.owns = data.owns;
|
|
143
144
|
} else if ('returned' in data) {
|
|
144
145
|
$$adopt(data);
|
|
146
|
+
$$reclassed(data);
|
|
145
147
|
|
|
146
148
|
const call = calls.get(data.returned);
|
|
147
149
|
|
|
@@ -238,12 +240,18 @@ export function $$goHere(functions, fn, args) {
|
|
|
238
240
|
return task;
|
|
239
241
|
}
|
|
240
242
|
|
|
241
|
-
// `go for`: the array is split into parts,
|
|
242
|
-
// of the
|
|
243
|
-
//
|
|
244
|
-
//
|
|
243
|
+
// `go for`: the array is split into parts, each part runs as a goroutine of `go` of the module, and
|
|
244
|
+
// the loop waits for all of them. The pool gives a part to the first worker that is free, so with
|
|
245
|
+
// several parts per processor the workers end at about the same time, also when some elements take
|
|
246
|
+
// longer than others or some processors are slower. The names that the body uses are copied with
|
|
247
|
+
// each part: if one of them is an object, which may be large, there is one part per processor. A
|
|
248
|
+
// typed array on a SharedArrayBuffer is shared with the workers, which write into it; other parts
|
|
249
|
+
// are copies, moved to the workers and back into the array.
|
|
245
250
|
export async function $$goFor(go, name, items, ...captured) {
|
|
246
|
-
const
|
|
251
|
+
const partsPerProcessor = 4;
|
|
252
|
+
const processors = globalThis.navigator?.hardwareConcurrency ?? 4;
|
|
253
|
+
const small = captured.every((value) => typeof value !== 'object' || value === null);
|
|
254
|
+
const count = Math.min(small ? processors * partsPerProcessor : processors, items.length);
|
|
247
255
|
const shared =
|
|
248
256
|
typeof SharedArrayBuffer === 'function' && items.buffer instanceof SharedArrayBuffer;
|
|
249
257
|
const parts = [];
|
|
@@ -305,6 +313,7 @@ export async function $$serve(functions) {
|
|
|
305
313
|
if ('gone' in data) return $$gone(data.gone);
|
|
306
314
|
setTimeout(free);
|
|
307
315
|
$$adopt(data.args);
|
|
316
|
+
$$reclassed(data);
|
|
308
317
|
|
|
309
318
|
const { main } = registry;
|
|
310
319
|
|
package/src/runtime/index.js
CHANGED
|
@@ -3,11 +3,17 @@
|
|
|
3
3
|
// The compiler can also put the parts that a module needs into the module itself (runtime.ts).
|
|
4
4
|
|
|
5
5
|
export * from './chan.js';
|
|
6
|
+
export * from './channel.js';
|
|
7
|
+
export * from './classes.js';
|
|
6
8
|
export * from './core.js';
|
|
9
|
+
export * from './decode.js';
|
|
7
10
|
export * from './go.js';
|
|
8
11
|
export * from './markup.js';
|
|
9
12
|
export * from './queue.js';
|
|
10
13
|
export * from './select.js';
|
|
14
|
+
export * from './signals.js';
|
|
11
15
|
export * from './streams.js';
|
|
12
16
|
export * from './threads.js';
|
|
13
17
|
export * from './timers.js';
|
|
18
|
+
export * from './topic.js';
|
|
19
|
+
export * from './try.js';
|
package/src/runtime/select.js
CHANGED
|
@@ -3,12 +3,13 @@
|
|
|
3
3
|
// channels of a select.
|
|
4
4
|
|
|
5
5
|
import { $$finish, $$pump, $$zero } from './chan.js';
|
|
6
|
-
import { $$ask, $$ownerOf, $$pin, $$post, $$registry, $$stopped } from './threads.js';
|
|
6
|
+
import { $$ask, $$isEmpty, $$ownerOf, $$pin, $$post, $$registry, $$stopped } from './threads.js';
|
|
7
7
|
|
|
8
8
|
// `select`: the index of the case that went on, and what it received. A case is { channel } to
|
|
9
|
-
// receive or { channel, value } to send
|
|
10
|
-
//
|
|
11
|
-
//
|
|
9
|
+
// receive or { channel, value } to send, with the zero value that Channel.EMPTY gives, if it is
|
|
10
|
+
// not null; from another thread, { channel, send, value }. One that can go on at once wins, a
|
|
11
|
+
// random one as in Go; else `default`, -1, if there is one; else the first that can go on later.
|
|
12
|
+
// A case with a null channel never goes on, and one with Channel.EMPTY always can.
|
|
12
13
|
export async function $$select(cases, hasDefault) {
|
|
13
14
|
const registry = $$registry();
|
|
14
15
|
const all = cases.map((each, i) => ({
|
|
@@ -16,12 +17,13 @@ export async function $$select(cases, hasDefault) {
|
|
|
16
17
|
id: typeof each.channel === 'string' ? each.channel : (each.channel?.$$chan ?? null),
|
|
17
18
|
send: each.send ?? 'value' in each,
|
|
18
19
|
value: each.value,
|
|
20
|
+
zero: each.zero ?? null,
|
|
19
21
|
}));
|
|
20
22
|
const live = all.filter(({ id }) => id !== null);
|
|
21
|
-
const owners = new Set(live.map(({ id }) => $$ownerOf(id)));
|
|
23
|
+
const owners = new Set(live.filter(({ id }) => !$$isEmpty(id)).map(({ id }) => $$ownerOf(id)));
|
|
22
24
|
|
|
23
25
|
// The channels of one other thread: the select runs there.
|
|
24
|
-
if (owners.size === 1 && !owners.has(registry.key)) {
|
|
26
|
+
if (owners.size === 1 && !owners.has(registry.key) && live.every(({ id }) => !$$isEmpty(id))) {
|
|
25
27
|
const [owner] = owners;
|
|
26
28
|
|
|
27
29
|
return $$ask(owner, {
|
|
@@ -34,10 +36,13 @@ export async function $$select(cases, hasDefault) {
|
|
|
34
36
|
const order = live.map(({ i }) => i).sort(() => Math.random() - 0.5);
|
|
35
37
|
|
|
36
38
|
for (const i of order) {
|
|
37
|
-
const { id, send, value } = all[i];
|
|
39
|
+
const { id, send, value, zero } = all[i];
|
|
38
40
|
const owner = $$ownerOf(id);
|
|
39
|
-
const ready =
|
|
40
|
-
|
|
41
|
+
const ready = $$isEmpty(id)
|
|
42
|
+
? send
|
|
43
|
+
? []
|
|
44
|
+
: [zero, false]
|
|
45
|
+
: owner === registry.key
|
|
41
46
|
? $$try(id, send, value)
|
|
42
47
|
: await $$ask(owner, { op: 'try', channel: id, send, value });
|
|
43
48
|
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
// Signals of the process and AbortSignal as channels: `signal(...names)`, `done(abortSignal)` and
|
|
2
|
+
// `abortSignal(ch)`, as signal.Notify and the Done() of a context are in Go.
|
|
3
|
+
|
|
4
|
+
import { $$chan, $$closeHere, $$receive } from './chan.js';
|
|
5
|
+
import { $$try } from './select.js';
|
|
6
|
+
import { $$registry } from './threads.js';
|
|
7
|
+
|
|
8
|
+
// `signal("SIGINT", "SIGTERM")`: a channel that gets the name of each signal that comes. While it
|
|
9
|
+
// listens, the signals do not end the process, and it keeps the program running, as a tick does;
|
|
10
|
+
// `close(ch)` stops it, and the signals do what they did before. A signal that finds the last one
|
|
11
|
+
// not received yet is dropped. Only the main thread of Node gets the signals of the process.
|
|
12
|
+
export function $$signal(...names) {
|
|
13
|
+
// Node's own, which a browser does not have.
|
|
14
|
+
const { process } = globalThis;
|
|
15
|
+
|
|
16
|
+
if (process === undefined) throw new Error('signal() needs Node: a browser has no signals');
|
|
17
|
+
|
|
18
|
+
const channel = $$chan(1, '');
|
|
19
|
+
const listener = (name) => $$try(channel.$$chan, true, name);
|
|
20
|
+
// A listener of a signal does not keep the program running, and a timer that never fires does.
|
|
21
|
+
const timer = setInterval(() => {}, 2 ** 31 - 1);
|
|
22
|
+
|
|
23
|
+
for (const name of names) process.on(name, listener);
|
|
24
|
+
$$registry().states.get(channel.$$chan).onClose = () => {
|
|
25
|
+
clearInterval(timer);
|
|
26
|
+
for (const name of names) process.off(name, listener);
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
return channel;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// `done(signal)`: a channel that gets the reason of an AbortSignal when it aborts, and is closed
|
|
33
|
+
// then, so that `case <-done(request.signal):` goes on once the request is cancelled.
|
|
34
|
+
export function $$done(signal) {
|
|
35
|
+
const channel = $$chan(1, null);
|
|
36
|
+
const abort = () => {
|
|
37
|
+
$$try(channel.$$chan, true, signal.reason);
|
|
38
|
+
$$closeHere(channel.$$chan);
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
if (signal.aborted) abort();
|
|
42
|
+
else signal.addEventListener('abort', abort, { once: true });
|
|
43
|
+
|
|
44
|
+
return channel;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// `abortSignal(ch)`: an AbortSignal that aborts when the channel gets a value, with the value as
|
|
48
|
+
// its reason, or is closed, for `fetch(url, { signal: abortSignal(stop) })`. It receives that one
|
|
49
|
+
// value, as any receiver of the channel would.
|
|
50
|
+
export function $$abortSignal(channel) {
|
|
51
|
+
const controller = new AbortController();
|
|
52
|
+
|
|
53
|
+
$$receive(channel, true).then(
|
|
54
|
+
([value, ok]) => controller.abort(ok ? value : undefined),
|
|
55
|
+
(error) => controller.abort(error),
|
|
56
|
+
);
|
|
57
|
+
|
|
58
|
+
return controller.signal;
|
|
59
|
+
}
|