rooklang 0.3.3 → 0.4.0
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 +34 -7
- package/README.ru.md +34 -7
- package/dist/ast/declarations.d.ts +39 -4
- package/dist/ast/expressions.d.ts +34 -8
- package/dist/ast/markup.d.ts +6 -1
- package/dist/ast/statements.d.ts +28 -2
- 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 +4 -0
- package/dist/checker/assignments.js +23 -3
- package/dist/checker/base.d.ts +8 -2
- package/dist/checker/base.js +7 -7
- package/dist/checker/builtins.js +22 -8
- 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 +17 -4
- 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 +48 -13
- package/dist/checker/closing.d.ts +21 -0
- package/dist/checker/closing.js +164 -0
- package/dist/checker/completion.d.ts +3 -3
- package/dist/checker/completion.js +4 -3
- package/dist/checker/component-bindings.d.ts +7 -0
- package/dist/checker/component-bindings.js +45 -0
- package/dist/checker/component-values.js +10 -7
- package/dist/checker/components.d.ts +2 -2
- package/dist/checker/components.js +8 -3
- package/dist/checker/context.d.ts +41 -5
- package/dist/checker/context.js +23 -0
- package/dist/checker/control-flow.d.ts +11 -5
- package/dist/checker/control-flow.js +65 -40
- package/dist/checker/declarations.d.ts +2 -2
- package/dist/checker/declarations.js +4 -2
- package/dist/checker/decode.d.ts +8 -0
- package/dist/checker/decode.js +32 -0
- package/dist/checker/decorator-exports.js +4 -0
- package/dist/checker/decorator-includes.d.ts +9 -0
- package/dist/checker/decorator-includes.js +71 -0
- package/dist/checker/decorator-members.d.ts +8 -10
- package/dist/checker/decorator-members.js +28 -31
- package/dist/checker/decorator-wrappers.d.ts +1 -1
- package/dist/checker/decorator-wrappers.js +3 -2
- package/dist/checker/decorators.d.ts +14 -5
- package/dist/checker/decorators.js +52 -25
- package/dist/checker/expressions.d.ts +6 -2
- package/dist/checker/expressions.js +20 -40
- package/dist/checker/flow.d.ts +25 -3
- package/dist/checker/flow.js +56 -12
- package/dist/checker/functions.d.ts +2 -2
- package/dist/checker/functions.js +9 -5
- package/dist/checker/helpers.d.ts +5 -2
- package/dist/checker/helpers.js +16 -4
- package/dist/checker/imports.js +5 -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.d.ts +2 -0
- package/dist/checker/loops.js +50 -5
- package/dist/checker/markup.js +14 -2
- package/dist/checker/members.js +10 -1
- 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/shared-state.d.ts +11 -0
- package/dist/checker/shared-state.js +37 -0
- package/dist/checker/signals.d.ts +8 -0
- package/dist/checker/signals.js +48 -0
- package/dist/checker/statements.d.ts +5 -2
- package/dist/checker/statements.js +51 -22
- package/dist/checker/tagged-unions.d.ts +37 -0
- package/dist/checker/tagged-unions.js +132 -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/checker/untyped-globals.d.ts +1 -0
- package/dist/checker/untyped-globals.js +3 -1
- package/dist/cli.js +18 -36
- package/dist/codegen/attributes.d.ts +4 -0
- package/dist/codegen/attributes.js +24 -2
- package/dist/codegen/calls.d.ts +23 -0
- package/dist/codegen/calls.js +88 -7
- package/dist/codegen/component-helpers.d.ts +6 -1
- package/dist/codegen/component-helpers.js +6 -1
- package/dist/codegen/component-props.d.ts +11 -2
- package/dist/codegen/component-props.js +50 -5
- package/dist/codegen/components.d.ts +6 -5
- package/dist/codegen/components.js +44 -21
- package/dist/codegen/control-flow.d.ts +2 -0
- package/dist/codegen/control-flow.js +23 -9
- package/dist/codegen/control-statements.d.ts +10 -2
- package/dist/codegen/control-statements.js +56 -19
- package/dist/codegen/creation.js +5 -2
- package/dist/codegen/decode.d.ts +14 -0
- package/dist/codegen/decode.js +93 -0
- package/dist/codegen/early-returns.d.ts +4 -3
- package/dist/codegen/early-returns.js +17 -5
- package/dist/codegen/emitter.d.ts +13 -41
- package/dist/codegen/emitter.js +27 -8
- package/dist/codegen/expressions.d.ts +9 -0
- package/dist/codegen/expressions.js +15 -26
- package/dist/codegen/functions.d.ts +11 -1
- package/dist/codegen/functions.js +31 -3
- package/dist/codegen/js.d.ts +2 -2
- package/dist/codegen/js.js +5 -2
- package/dist/codegen/live.d.ts +60 -0
- package/dist/codegen/live.js +29 -0
- package/dist/codegen/mutations.d.ts +7 -0
- package/dist/codegen/mutations.js +43 -2
- package/dist/codegen/options.d.ts +42 -0
- package/dist/codegen/options.js +1 -0
- package/dist/codegen/reactive.d.ts +4 -59
- package/dist/codegen/reactive.js +34 -72
- package/dist/codegen/runtime.d.ts +7 -3
- package/dist/codegen/runtime.js +33 -14
- package/dist/codegen/shared.d.ts +22 -0
- package/dist/codegen/shared.js +93 -0
- package/dist/codegen/simple-statements.d.ts +11 -5
- package/dist/codegen/simple-statements.js +40 -15
- package/dist/codegen/statements.d.ts +5 -0
- package/dist/codegen/statements.js +99 -4
- package/dist/codegen/try.d.ts +5 -0
- package/dist/codegen/try.js +40 -0
- package/dist/codegen/updates.d.ts +11 -3
- package/dist/codegen/updates.js +52 -4
- package/dist/codegen/web-components.d.ts +2 -2
- package/dist/codegen/web-components.js +2 -2
- package/dist/compile.d.ts +9 -0
- package/dist/compile.js +25 -8
- package/dist/completion.d.ts +4 -2
- package/dist/completion.js +10 -5
- package/dist/declarations.js +32 -6
- package/dist/decorator-copy.d.ts +1 -1
- package/dist/decorator-copy.js +6 -2
- package/dist/decorator-names.d.ts +14 -0
- package/dist/decorator-names.js +56 -0
- package/dist/decorators.d.ts +2 -3
- package/dist/decorators.js +36 -14
- 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/inline-packages.d.ts +2 -1
- package/dist/inline-packages.js +9 -4
- 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 +30 -8
- package/dist/names.d.ts +4 -0
- package/dist/names.js +29 -0
- package/dist/parser/base.d.ts +5 -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 +8 -1
- package/dist/parser/control-flow.js +49 -26
- package/dist/parser/declarations.d.ts +5 -12
- package/dist/parser/declarations.js +14 -79
- package/dist/parser/decorators.d.ts +6 -2
- package/dist/parser/decorators.js +43 -19
- 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/params.d.ts +21 -0
- package/dist/parser/params.js +143 -0
- package/dist/parser/simple-statements.d.ts +2 -1
- package/dist/parser/simple-statements.js +38 -15
- package/dist/parser/statements.d.ts +5 -5
- package/dist/parser/statements.js +39 -35
- 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 +13 -6
- package/dist/source-map.d.ts +17 -0
- package/dist/source-map.js +75 -0
- package/dist/ts-types.js +4 -1
- package/dist/typescript/convert-objects.d.ts +2 -1
- package/dist/typescript/convert-objects.js +18 -9
- package/dist/vite.js +12 -4
- package/dist/walk-types.js +1 -0
- package/dist/walk.d.ts +0 -2
- package/dist/walk.js +14 -31
- package/package.json +2 -2
- 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 +221 -0
- package/src/runtime/go.js +3 -0
- package/src/runtime/index.js +7 -0
- package/src/runtime/markup.js +27 -17
- package/src/runtime/select.js +14 -9
- package/src/runtime/shared.js +17 -0
- 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
|
@@ -0,0 +1,221 @@
|
|
|
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
|
+
/**
|
|
52
|
+
* Where and how the value does not fit the type, or null if it does. For a member of a union,
|
|
53
|
+
* `tagsFirst` checks its fields of literal types first: a member whose tag does not fit reports
|
|
54
|
+
* that, not another field.
|
|
55
|
+
*/
|
|
56
|
+
function check(value, type, path, tagsFirst = false) {
|
|
57
|
+
if (typeof type === 'string') {
|
|
58
|
+
return type === 'any' || typeof value === PRIMITIVES[type] ? null : mismatch(value, type, path);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
if ('literal' in type) {
|
|
62
|
+
// Remembered for unions: a tag that fits no member lists the tags of all of them.
|
|
63
|
+
return type.literal.includes(value)
|
|
64
|
+
? null
|
|
65
|
+
: { ...mismatch(value, type, path), literal: type.literal, value };
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
if ('nullable' in type) {
|
|
69
|
+
if (value == null) return null;
|
|
70
|
+
|
|
71
|
+
const problem = check(value, type.nullable, path);
|
|
72
|
+
|
|
73
|
+
// The value itself is of another type: the message names ?T, not T.
|
|
74
|
+
return problem !== null && problem.path.length === path.length
|
|
75
|
+
? mismatch(value, type, path)
|
|
76
|
+
: problem;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
if ('array' in type) {
|
|
80
|
+
if (!Array.isArray(value)) return mismatch(value, type, path);
|
|
81
|
+
|
|
82
|
+
for (let i = 0; i < value.length; i++) {
|
|
83
|
+
const problem = check(value[i], type.array, [...path, i]);
|
|
84
|
+
|
|
85
|
+
if (problem !== null) return problem;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
return null;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
if ('union' in type) return checkUnion(value, type, path);
|
|
92
|
+
if (!isObject(value)) return mismatch(value, type, path);
|
|
93
|
+
|
|
94
|
+
if ('record' in type) {
|
|
95
|
+
for (const key of Object.keys(value)) {
|
|
96
|
+
const problem = check(value[key], type.record, [...path, key]);
|
|
97
|
+
|
|
98
|
+
if (problem !== null) return problem;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
return null;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
const fields = Object.entries(type.fields);
|
|
105
|
+
|
|
106
|
+
if (tagsFirst) fields.sort(([, a], [, b]) => Number(isLiteral(b)) - Number(isLiteral(a)));
|
|
107
|
+
|
|
108
|
+
for (const [name, field] of fields) {
|
|
109
|
+
// Only own fields: a field named `toString` is not the method of every object.
|
|
110
|
+
if (!Object.hasOwn(value, name)) {
|
|
111
|
+
if (isOptional(field)) continue;
|
|
112
|
+
|
|
113
|
+
return { path: [...path, name], message: 'missing' };
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
const problem = check(value[name], field, [...path, name]);
|
|
117
|
+
|
|
118
|
+
if (problem !== null) return problem;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
return null;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* The value fits one of the types. If none fits, the type whose check went deepest into the value
|
|
126
|
+
* is most likely the one meant, and of those the one whose tag fits: `{ kind: "circle", r: "2" }`
|
|
127
|
+
* of `Circle | Square` is a Circle, so its problem is the one reported. When no tag fits, the
|
|
128
|
+
* message lists them all: `kind: expected "circle" | "square", got "oval"`.
|
|
129
|
+
*/
|
|
130
|
+
function checkUnion(value, type, path) {
|
|
131
|
+
const problems = [];
|
|
132
|
+
|
|
133
|
+
for (const member of type.union) {
|
|
134
|
+
const problem = check(value, member, path, true);
|
|
135
|
+
|
|
136
|
+
if (problem === null) return null;
|
|
137
|
+
problems.push(problem);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
const depth = Math.max(...problems.map((problem) => problem.path.length));
|
|
141
|
+
|
|
142
|
+
if (depth === path.length) return mismatch(value, type, path);
|
|
143
|
+
|
|
144
|
+
const deepest = problems.filter((problem) => problem.path.length === depth);
|
|
145
|
+
const fitting = deepest.find((problem) => !problem.literal);
|
|
146
|
+
|
|
147
|
+
if (fitting) return fitting;
|
|
148
|
+
|
|
149
|
+
const [first] = deepest;
|
|
150
|
+
const same = deepest.filter((problem) => pathText(problem.path) === pathText(first.path));
|
|
151
|
+
const literal = [...new Set(same.flatMap((problem) => problem.literal))];
|
|
152
|
+
|
|
153
|
+
return { ...mismatch(first.value, { literal }, first.path), literal, value: first.value };
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
function isObject(value) {
|
|
157
|
+
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
function isLiteral(type) {
|
|
161
|
+
return typeof type === 'object' && 'literal' in type;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
function isOptional(type) {
|
|
165
|
+
return type === 'any' || (typeof type === 'object' && 'nullable' in type);
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
function mismatch(value, type, path) {
|
|
169
|
+
return { path, message: `expected ${describe(type)}, got ${shown(value)}` };
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
/** The type as Rook writes it: `[]string`, `?User`, `"admin" | "user"`. */
|
|
173
|
+
function describe(type) {
|
|
174
|
+
if (typeof type === 'string') return type;
|
|
175
|
+
if ('literal' in type) return type.literal.map((value) => JSON.stringify(value)).join(' | ');
|
|
176
|
+
if ('nullable' in type) return `?${grouped(type.nullable)}`;
|
|
177
|
+
if ('array' in type) return `[]${grouped(type.array)}`;
|
|
178
|
+
if ('record' in type) return `{ [key string]: ${describe(type.record)} }`;
|
|
179
|
+
if ('union' in type) return type.union.map(describe).join(' | ');
|
|
180
|
+
|
|
181
|
+
return type.name;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
function grouped(type) {
|
|
185
|
+
const several = typeof type === 'object' && ('union' in type || type.literal?.length > 1);
|
|
186
|
+
|
|
187
|
+
return several ? `(${describe(type)})` : describe(type);
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
/** The value in a message: a primitive itself, a long string cut, and the kind of anything else. */
|
|
191
|
+
function shown(value) {
|
|
192
|
+
if (value === null) return 'null';
|
|
193
|
+
if (Array.isArray(value)) return 'an array';
|
|
194
|
+
|
|
195
|
+
switch (typeof value) {
|
|
196
|
+
case 'string':
|
|
197
|
+
return JSON.stringify(value.length > 40 ? `${value.slice(0, 40)}…` : value);
|
|
198
|
+
|
|
199
|
+
case 'number':
|
|
200
|
+
case 'boolean':
|
|
201
|
+
return String(value);
|
|
202
|
+
|
|
203
|
+
case 'object':
|
|
204
|
+
return 'an object';
|
|
205
|
+
|
|
206
|
+
default:
|
|
207
|
+
return typeof value;
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
/** `tags[1]`, `boss.name`, `["first name"]`. */
|
|
212
|
+
function pathText(path) {
|
|
213
|
+
return path
|
|
214
|
+
.map((key, i) => {
|
|
215
|
+
if (typeof key === 'number') return `[${key}]`;
|
|
216
|
+
if (/^[A-Za-z_$][\w$]*$/.test(key)) return i === 0 ? key : `.${key}`;
|
|
217
|
+
|
|
218
|
+
return `[${JSON.stringify(key)}]`;
|
|
219
|
+
})
|
|
220
|
+
.join('');
|
|
221
|
+
}
|
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
|
|
|
@@ -311,6 +313,7 @@ export async function $$serve(functions) {
|
|
|
311
313
|
if ('gone' in data) return $$gone(data.gone);
|
|
312
314
|
setTimeout(free);
|
|
313
315
|
$$adopt(data.args);
|
|
316
|
+
$$reclassed(data);
|
|
314
317
|
|
|
315
318
|
const { main } = registry;
|
|
316
319
|
|
package/src/runtime/index.js
CHANGED
|
@@ -3,11 +3,18 @@
|
|
|
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 './shared.js';
|
|
15
|
+
export * from './signals.js';
|
|
11
16
|
export * from './streams.js';
|
|
12
17
|
export * from './threads.js';
|
|
13
18
|
export * from './timers.js';
|
|
19
|
+
export * from './topic.js';
|
|
20
|
+
export * from './try.js';
|
package/src/runtime/markup.js
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
// Markup: the owners of the blocks that `{if}`, `{switch}` and `{for}` remove, the
|
|
2
|
-
// `mount
|
|
2
|
+
// `mount` of components, the blocks themselves, elements and the CSS of components.
|
|
3
3
|
|
|
4
4
|
import { $$runDeferred } from './core.js';
|
|
5
5
|
|
|
6
|
-
// Owners of markup that `{if}`, `{switch}` and `{for}` may remove: the
|
|
7
|
-
//
|
|
6
|
+
// Owners of markup that `{if}`, `{switch}` and `{for}` may remove: what the `mount` and `effect`
|
|
7
|
+
// of its components deferred, and the removal of the blocks inside it, run when it is removed.
|
|
8
8
|
// The current owner is shared by the modules of a page, since the blocks of one module may hold
|
|
9
9
|
// the components of another.
|
|
10
10
|
export const $$owner = Symbol.for('rook.owner');
|
|
@@ -36,15 +36,17 @@ export function $$onRemove(cleanup) {
|
|
|
36
36
|
globalThis[$$owner]?.cleanups.push(cleanup);
|
|
37
37
|
}
|
|
38
38
|
|
|
39
|
-
// `mount
|
|
39
|
+
// `mount { ... }` of a component: runs when its markup is in the document. That is checked after
|
|
40
40
|
// the code that creates the component; markup that is not in the document yet is waited for with a
|
|
41
41
|
// MutationObserver, which watches only while some markup waits. Markup that its owner removes
|
|
42
|
-
// before that never mounts.
|
|
42
|
+
// before that never mounts. What its `defer`s put into `cleanups` runs when the owner is removed;
|
|
43
|
+
// they are added to the owner once it mounts, so the cleanups of the decorators, which mount after
|
|
44
|
+
// the component, run before those of the component.
|
|
43
45
|
export const $$waiting = new Set();
|
|
44
46
|
export let $$observer = null;
|
|
45
47
|
|
|
46
|
-
export function $$mount(node, mount) {
|
|
47
|
-
const entry = { node, mount, owner: globalThis[$$owner] };
|
|
48
|
+
export function $$mount(node, mount, cleanups) {
|
|
49
|
+
const entry = { node, mount, cleanups, owner: globalThis[$$owner] };
|
|
48
50
|
|
|
49
51
|
entry.owner?.cleanups.push(() => $$waiting.delete(entry));
|
|
50
52
|
queueMicrotask(() => $$tryMount(entry));
|
|
@@ -65,9 +67,8 @@ export function $$tryMount(entry) {
|
|
|
65
67
|
return;
|
|
66
68
|
}
|
|
67
69
|
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
if (typeof cleanup === 'function') entry.owner?.cleanups.push(cleanup);
|
|
70
|
+
entry.mount();
|
|
71
|
+
if (entry.cleanups) entry.owner?.cleanups.push(() => $$runDeferred(entry.cleanups.splice(0)));
|
|
71
72
|
}
|
|
72
73
|
|
|
73
74
|
export function $$mountWaiting() {
|
|
@@ -216,8 +217,10 @@ export function $$dynamic(component, create) {
|
|
|
216
217
|
|
|
217
218
|
// `{for item in items { ... }}` in markup: a block for each item, between its own markers. On
|
|
218
219
|
// updates the blocks are found again by the item itself: blocks of items that stay are updated and
|
|
219
|
-
// moved into place, others are created or removed.
|
|
220
|
-
|
|
220
|
+
// moved into place, others are created or removed. With `key`, of `with key item.id`, they are
|
|
221
|
+
// found by the key instead, so an item replaced by another with the same key keeps its block,
|
|
222
|
+
// which gets the new item.
|
|
223
|
+
export function $$list(items, create, key = (item) => item) {
|
|
221
224
|
const end = document.createTextNode('');
|
|
222
225
|
const fragment = document.createDocumentFragment();
|
|
223
226
|
|
|
@@ -236,24 +239,25 @@ export function $$list(items, create) {
|
|
|
236
239
|
const old = new Map();
|
|
237
240
|
|
|
238
241
|
for (const block of blocks) {
|
|
239
|
-
const same = old.get(block.
|
|
242
|
+
const same = old.get(block.key);
|
|
240
243
|
|
|
241
244
|
if (same) same.push(block);
|
|
242
|
-
else old.set(block.
|
|
245
|
+
else old.set(block.key, [block]);
|
|
243
246
|
}
|
|
244
247
|
|
|
245
248
|
const next = [];
|
|
246
249
|
let index = 0;
|
|
247
250
|
|
|
248
251
|
for (const item of items()) {
|
|
249
|
-
|
|
252
|
+
const id = key(item, index);
|
|
253
|
+
let block = old.get(id)?.shift();
|
|
250
254
|
|
|
251
255
|
if (block) {
|
|
252
|
-
block.update?.(index);
|
|
256
|
+
block.update?.(index, item);
|
|
253
257
|
} else {
|
|
254
258
|
const [[content, blockUpdate], remove] = $$owned(() => create(item, index));
|
|
255
259
|
|
|
256
|
-
block = {
|
|
260
|
+
block = { key: id, start: document.createTextNode(''), end: document.createTextNode('') };
|
|
257
261
|
block.update = blockUpdate;
|
|
258
262
|
block.remove = remove;
|
|
259
263
|
block.content = document.createDocumentFragment();
|
|
@@ -315,6 +319,12 @@ export function $$svgElement(tag, attributes) {
|
|
|
315
319
|
return element;
|
|
316
320
|
}
|
|
317
321
|
|
|
322
|
+
// The class of `class={[styles.tab, active ? styles.active : null]}`: the classes that are not
|
|
323
|
+
// null or empty, separated by spaces.
|
|
324
|
+
export function $$classes(list) {
|
|
325
|
+
return list.filter((each) => each).join(' ');
|
|
326
|
+
}
|
|
327
|
+
|
|
318
328
|
// Sets an attribute whose value may be null: then the attribute is removed.
|
|
319
329
|
export function $$attribute(element, name, value) {
|
|
320
330
|
if (value == null) element.removeAttribute(name);
|
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,17 @@
|
|
|
1
|
+
// State of a module, `state items []Item = []` at its top level: the set of the updates of the
|
|
2
|
+
// components that read it, which every change of it runs.
|
|
3
|
+
|
|
4
|
+
import { $$onRemove } from './markup.js';
|
|
5
|
+
|
|
6
|
+
// A component adds its update while it is on the page: `{if}`, `{switch}` or `{for}` that removes
|
|
7
|
+
// it takes the update away.
|
|
8
|
+
export function $$watch(listeners, update) {
|
|
9
|
+
listeners.add(update);
|
|
10
|
+
$$onRemove(() => listeners.delete(update));
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
// After a change of the state: the updates run on a copy, since one may remove a component, or
|
|
14
|
+
// add one, that listens too.
|
|
15
|
+
export function $$changed(listeners) {
|
|
16
|
+
for (const update of [...listeners]) update();
|
|
17
|
+
}
|
|
@@ -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
|
+
}
|
package/src/runtime/threads.js
CHANGED
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
// modules; the operations of a thread on the channels of another go straight to that thread
|
|
5
5
|
// through a MessagePort, which the main thread sets up on the first need (go.js).
|
|
6
6
|
|
|
7
|
+
import { $$classOf, $$reclass } from './classes.js';
|
|
7
8
|
import { $$Queue } from './queue.js';
|
|
8
9
|
|
|
9
10
|
// The state of this thread that its modules share: the channels it made, its ports to the other
|
|
@@ -29,6 +30,12 @@ export function $$registry() {
|
|
|
29
30
|
});
|
|
30
31
|
}
|
|
31
32
|
|
|
33
|
+
// Channel.EMPTY, the zero value of channel types, has this id in every thread: it is closed, and
|
|
34
|
+
// no thread keeps it (channel.js).
|
|
35
|
+
export function $$isEmpty(id) {
|
|
36
|
+
return id === 'empty';
|
|
37
|
+
}
|
|
38
|
+
|
|
32
39
|
// The key of the thread that made a channel, which keeps what it holds.
|
|
33
40
|
export function $$ownerOf(id) {
|
|
34
41
|
return id.slice(0, id.lastIndexOf(':'));
|
|
@@ -42,9 +49,11 @@ export function $$post(key, message) {
|
|
|
42
49
|
|
|
43
50
|
if (registry.gone.has(key)) return;
|
|
44
51
|
|
|
45
|
-
const { channels, moved } = $$walk(message);
|
|
52
|
+
const { channels, moved, instances } = $$walk(message);
|
|
46
53
|
|
|
47
54
|
for (const channel of channels) $$hold(channel.$$chan, 1);
|
|
55
|
+
// The copies of the instances of data classes are listed in the copy, by reference.
|
|
56
|
+
if (instances.length > 0) message = { ...message, $$instances: instances };
|
|
48
57
|
|
|
49
58
|
const port = registry.ports.get(key);
|
|
50
59
|
|
|
@@ -136,6 +145,7 @@ export function $$incoming(key, data) {
|
|
|
136
145
|
const registry = $$registry();
|
|
137
146
|
|
|
138
147
|
$$adopt(data);
|
|
148
|
+
$$reclassed(data);
|
|
139
149
|
if ('answer' in data) {
|
|
140
150
|
const request = registry.requests.get(data.answer);
|
|
141
151
|
|
|
@@ -166,6 +176,11 @@ export function $$hold(id, count) {
|
|
|
166
176
|
if (state.holders <= 0) registry.states.delete(id);
|
|
167
177
|
}
|
|
168
178
|
|
|
179
|
+
// A message with copies of the instances of data classes gives them their classes back.
|
|
180
|
+
export function $$reclassed(data) {
|
|
181
|
+
if (data.$$instances) $$reclass(data.$$instances);
|
|
182
|
+
}
|
|
183
|
+
|
|
169
184
|
export function $$adopt(value) {
|
|
170
185
|
const { held } = $$registry();
|
|
171
186
|
|
|
@@ -175,11 +190,19 @@ export function $$adopt(value) {
|
|
|
175
190
|
// A copy in this thread. One that `own` the runtime, such as the arguments of `go` that wait for
|
|
176
191
|
// a worker, moves its buffers on.
|
|
177
192
|
export function $$copy(value, own = false) {
|
|
178
|
-
const { channels, moved } = $$walk(value);
|
|
193
|
+
const { channels, moved, instances } = $$walk(value);
|
|
179
194
|
|
|
180
195
|
for (const channel of channels) $$hold(channel.$$chan, 1);
|
|
181
196
|
|
|
182
|
-
const copy = structuredClone(
|
|
197
|
+
const { copy, $$instances } = structuredClone(
|
|
198
|
+
{ copy: value, $$instances: instances },
|
|
199
|
+
{
|
|
200
|
+
transfer: moved,
|
|
201
|
+
},
|
|
202
|
+
);
|
|
203
|
+
|
|
204
|
+
$$reclass($$instances);
|
|
205
|
+
|
|
183
206
|
const { held } = $$registry();
|
|
184
207
|
|
|
185
208
|
for (const channel of $$walk(copy, own).channels) held.register(channel, channel.$$chan);
|
|
@@ -200,7 +223,7 @@ export function $$transfer(value) {
|
|
|
200
223
|
export function $$walk(
|
|
201
224
|
value,
|
|
202
225
|
own = false,
|
|
203
|
-
found = { channels: new Set(), moved: [] },
|
|
226
|
+
found = { channels: new Set(), moved: [], instances: [] },
|
|
204
227
|
seen = new Set(),
|
|
205
228
|
) {
|
|
206
229
|
if (typeof value !== 'object' || value === null || seen.has(value)) return found;
|
|
@@ -221,8 +244,12 @@ export function $$walk(
|
|
|
221
244
|
}
|
|
222
245
|
|
|
223
246
|
if (Array.isArray(value.$$queue)) Object.setPrototypeOf(value, $$Queue.prototype);
|
|
247
|
+
|
|
248
|
+
const id = $$classOf(value);
|
|
249
|
+
|
|
250
|
+
if (id !== undefined) found.instances.push([value, id]);
|
|
224
251
|
if (typeof value.$$chan === 'string') {
|
|
225
|
-
found.channels.add(value);
|
|
252
|
+
if (!$$isEmpty(value.$$chan)) found.channels.add(value);
|
|
226
253
|
|
|
227
254
|
return found;
|
|
228
255
|
}
|