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,93 @@
|
|
|
1
|
+
import { isText } from "../checker/json.js";
|
|
2
|
+
import { memberNames, typeToString } from "../checker/types.js";
|
|
3
|
+
import { CallEmitter } from "./calls.js";
|
|
4
|
+
import { ARROW } from "./syntax.js";
|
|
5
|
+
// `decode<User>(body)`: the type, which the checker found, becomes data that the runtime checks the
|
|
6
|
+
// value against (runtime/decode.js). An object type is a constant of the module, `$$typeUser`,
|
|
7
|
+
// written before its code (js.ts), so that types that refer to themselves or each other can do so,
|
|
8
|
+
// and the value of each call is only its name: `$$decodeJson(body, $$typeUser)`.
|
|
9
|
+
const IDENTIFIER = /^[A-Za-z_$][\w$]*$/;
|
|
10
|
+
export class DecodeEmitter extends CallEmitter {
|
|
11
|
+
/** The constants of the object types that decode checks against, by type, in order. */
|
|
12
|
+
decodeTypes = new Map();
|
|
13
|
+
/** `$$decodeJson(text, type)` for a string, `$$decode(value, type)` for a value from JS. */
|
|
14
|
+
decode(node) {
|
|
15
|
+
const argument = this.expression(node.argument, ARROW);
|
|
16
|
+
const result = this.options.types?.get(node);
|
|
17
|
+
const type = result?.kind === 'tuple' ? result.types[0] : undefined;
|
|
18
|
+
// Without the types of the checker nothing is checked (runtime/decode.js).
|
|
19
|
+
if (type === undefined)
|
|
20
|
+
return `$$decode(${argument}, null)`;
|
|
21
|
+
const given = this.options.types?.get(node.argument);
|
|
22
|
+
const decode = given !== undefined && isText(given) ? '$$decodeJson' : '$$decode';
|
|
23
|
+
return `${decode}(${argument}, ${this.shape(type)})`;
|
|
24
|
+
}
|
|
25
|
+
/** The constants of the object types, for the start of the module. */
|
|
26
|
+
decodeTypeLines() {
|
|
27
|
+
return [...this.decodeTypes.values()].flatMap(({ lines }) => lines);
|
|
28
|
+
}
|
|
29
|
+
/** A type as runtime/decode.js has it: `"string"`, `{ array: "number" }`, `$$typeUser`. */
|
|
30
|
+
shape(type) {
|
|
31
|
+
switch (type.kind) {
|
|
32
|
+
case 'number':
|
|
33
|
+
case 'string':
|
|
34
|
+
case 'bool':
|
|
35
|
+
case 'any':
|
|
36
|
+
return `"${type.kind}"`;
|
|
37
|
+
case 'null':
|
|
38
|
+
return '{ literal: [null] }';
|
|
39
|
+
case 'literal':
|
|
40
|
+
return `{ literal: [${JSON.stringify(type.value)}] }`;
|
|
41
|
+
case 'nullable':
|
|
42
|
+
return `{ nullable: ${this.shape(type.type)} }`;
|
|
43
|
+
case 'array':
|
|
44
|
+
return `{ array: ${this.shape(type.element)} }`;
|
|
45
|
+
case 'union': {
|
|
46
|
+
// The literals of a union are one list, as `"admin" | "user"` reads.
|
|
47
|
+
const literals = type.types.flatMap((each) => each.kind === 'literal' ? [each.value] : []);
|
|
48
|
+
const others = type.types.filter((each) => each.kind !== 'literal');
|
|
49
|
+
const shapes = [
|
|
50
|
+
...(literals.length > 0 ? [`{ literal: [${literals.map(json).join(', ')}] }`] : []),
|
|
51
|
+
...others.map((each) => this.shape(each)),
|
|
52
|
+
];
|
|
53
|
+
return shapes.length === 1 ? shapes[0] : `{ union: [${shapes.join(', ')}] }`;
|
|
54
|
+
}
|
|
55
|
+
case 'object':
|
|
56
|
+
return type.index && [...memberNames(type.members)].length === 0
|
|
57
|
+
? `{ record: ${this.shape(type.index)} }`
|
|
58
|
+
: this.objectType(type);
|
|
59
|
+
// The checker lets no other type through.
|
|
60
|
+
default:
|
|
61
|
+
return '"any"';
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
/** The constant of an object type, made the first time it is used. */
|
|
65
|
+
objectType(type) {
|
|
66
|
+
const known = this.decodeTypes.get(type);
|
|
67
|
+
if (known)
|
|
68
|
+
return known.name;
|
|
69
|
+
// `$$type` itself is the function of the runtime that makes them.
|
|
70
|
+
const written = type.instanceOf?.template.name ?? type.name ?? 'Object';
|
|
71
|
+
const base = `$$type${written.replace(/[^\w$]/g, '')}`;
|
|
72
|
+
const taken = new Set([...this.decodeTypes.values()].map(({ name }) => name));
|
|
73
|
+
let name = base;
|
|
74
|
+
for (let i = 2; taken.has(name); i++)
|
|
75
|
+
name = `${base}${i}`;
|
|
76
|
+
const entry = { name, lines: [] };
|
|
77
|
+
// Registered before its fields, which may refer to it.
|
|
78
|
+
this.decodeTypes.set(type, entry);
|
|
79
|
+
const fields = [...memberNames(type.members)].flatMap((field) => {
|
|
80
|
+
const member = type.members.get(field);
|
|
81
|
+
const key = IDENTIFIER.test(field) ? field : json(field);
|
|
82
|
+
return member ? [` ${key}: ${this.shape(member.type)},`] : [];
|
|
83
|
+
});
|
|
84
|
+
entry.lines =
|
|
85
|
+
fields.length === 0
|
|
86
|
+
? [`const ${name} = $$type(${json(typeToString(type))}, () => ({}));`]
|
|
87
|
+
: [`const ${name} = $$type(${json(typeToString(type))}, () => ({`, ...fields, '}));'];
|
|
88
|
+
return name;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
function json(value) {
|
|
92
|
+
return JSON.stringify(value);
|
|
93
|
+
}
|
|
@@ -38,9 +38,10 @@ export declare abstract class EarlyReturnEmitter extends ComponentValueEmitter {
|
|
|
38
38
|
protected declaredLive(node: ast.ElementExpression): Live | null;
|
|
39
39
|
/**
|
|
40
40
|
* After the markup of a component is created, before anything inserts it: the first node for
|
|
41
|
-
* `mount
|
|
42
|
-
* variables of its style,
|
|
43
|
-
* component is removed.
|
|
41
|
+
* `mount` (a fragment is empty once inserted, so its first child is taken now), the CSS
|
|
42
|
+
* variables of its style, the first run of the effects, whose cleanups also run when the
|
|
43
|
+
* component is removed, and the updates that listen to the state of the module it reads. Those
|
|
44
|
+
* are kept only if something depends on the state, which is known at the end (resolveMarkers).
|
|
44
45
|
*/
|
|
45
46
|
protected markupCreated(result: string): void;
|
|
46
47
|
/** Declarations that every return needs, written before the statements of the body. */
|
|
@@ -4,7 +4,7 @@ import { effectReads } from "./mutations.js";
|
|
|
4
4
|
import { Block, updatesOf } from "./reactive.js";
|
|
5
5
|
import { ARROW, indentMore } from "./syntax.js";
|
|
6
6
|
import { inlinesUpdate, needed } from "./updates.js";
|
|
7
|
-
import { SETTERS } from "./component-helpers.js";
|
|
7
|
+
import { LISTEN, SETTERS } from "./component-helpers.js";
|
|
8
8
|
export class EarlyReturnEmitter extends ComponentValueEmitter {
|
|
9
9
|
/**
|
|
10
10
|
* The effects of the component being written. One after an early return may not be created, so
|
|
@@ -81,9 +81,10 @@ export class EarlyReturnEmitter extends ComponentValueEmitter {
|
|
|
81
81
|
}
|
|
82
82
|
/**
|
|
83
83
|
* After the markup of a component is created, before anything inserts it: the first node for
|
|
84
|
-
* `mount
|
|
85
|
-
* variables of its style,
|
|
86
|
-
* component is removed.
|
|
84
|
+
* `mount` (a fragment is empty once inserted, so its first child is taken now), the CSS
|
|
85
|
+
* variables of its style, the first run of the effects, whose cleanups also run when the
|
|
86
|
+
* component is removed, and the updates that listen to the state of the module it reads. Those
|
|
87
|
+
* are kept only if something depends on the state, which is known at the end (resolveMarkers).
|
|
87
88
|
*/
|
|
88
89
|
markupCreated(result) {
|
|
89
90
|
if (this.mountNode) {
|
|
@@ -96,6 +97,12 @@ export class EarlyReturnEmitter extends ComponentValueEmitter {
|
|
|
96
97
|
continue;
|
|
97
98
|
this.line(`$$onRemove(() => $$runDeferred(${effect.cleanups}.splice(0)));`);
|
|
98
99
|
}
|
|
100
|
+
for (const source of this.reactive?.sources.values() ?? []) {
|
|
101
|
+
if (source.kind !== 'shared')
|
|
102
|
+
continue;
|
|
103
|
+
const listeners = this.shared.listeners.get(source.name);
|
|
104
|
+
this.line(`${LISTEN}${source.id}${LISTEN}$$watch(${listeners}, ${source.update});`);
|
|
105
|
+
}
|
|
99
106
|
}
|
|
100
107
|
/** Declarations that every return needs, written before the statements of the body. */
|
|
101
108
|
prepareEarlyReturns(result, reactive, ending, label, component) {
|
|
@@ -182,13 +189,18 @@ export class EarlyReturnEmitter extends ComponentValueEmitter {
|
|
|
182
189
|
let functions = 0;
|
|
183
190
|
for (const source of reactive.sources.values()) {
|
|
184
191
|
const called = source.writes.some((write) => needed(source, write).length > 0);
|
|
185
|
-
|
|
192
|
+
// State of the module: the changes are elsewhere, and they run the update it listens with.
|
|
193
|
+
const listened = source.kind === 'shared' && updatesOf(source).length > 0;
|
|
194
|
+
if (!listened && (source.kind !== 'state' || !called || inlinesUpdate(source)))
|
|
186
195
|
continue;
|
|
187
196
|
const body = this.block(() => {
|
|
188
197
|
// Derived values are computed again even before the markup exists, which a function of
|
|
189
198
|
// the body may change the state before.
|
|
190
199
|
for (const computation of source.computations)
|
|
191
200
|
this.line(indentMore(computation));
|
|
201
|
+
// The parent gets the value of a bound property even before the markup exists.
|
|
202
|
+
if (source.bound)
|
|
203
|
+
this.line(source.bound);
|
|
192
204
|
if (source.writes.some((write) => write.early))
|
|
193
205
|
this.line(`if (!${result}) return;`);
|
|
194
206
|
for (const dependent of source.dependents)
|
|
@@ -1,42 +1,9 @@
|
|
|
1
1
|
import type * as ast from '../ast.ts';
|
|
2
|
-
import type
|
|
3
|
-
import { type DecoratorSource } from '../decorators.ts';
|
|
4
|
-
import type { InlinePackage } from '../inline-packages.ts';
|
|
5
|
-
import { type ImportedComponent, type LocalizedPackage } from './imported.ts';
|
|
2
|
+
import { type LocalizedPackage } from './imported.ts';
|
|
6
3
|
import type { Root } from '../refs.ts';
|
|
7
|
-
import type { StyleModule } from '../style-modules.ts';
|
|
8
4
|
import type { BindingKind } from './analysis.ts';
|
|
9
|
-
import type {
|
|
10
|
-
|
|
11
|
-
/** The Rook source, used to keep the blank lines between statements. */
|
|
12
|
-
source: string;
|
|
13
|
-
/** Replace `.rk` with `.js` in relative import paths, for output written next to the sources. */
|
|
14
|
-
rewriteImports: boolean;
|
|
15
|
-
/** Types from the checker, when it ran; without them the generated code is more general. */
|
|
16
|
-
types?: WeakMap<ast.Expression, Type> | undefined;
|
|
17
|
-
/** Decorators imported from other modules, by name: their code is inlined here. */
|
|
18
|
-
decorators?: ReadonlyMap<string, ImportedDecorator> | undefined;
|
|
19
|
-
/** Components imported from other modules, by name: called, or inlined when they are small. */
|
|
20
|
-
components?: ReadonlyMap<string, ImportedComponent> | undefined;
|
|
21
|
-
/** Components of the module that become functions, since inlining them adds too many lines. */
|
|
22
|
-
functions?: ReadonlySet<string> | undefined;
|
|
23
|
-
/** The CSS modules the module imports, by their paths as written. */
|
|
24
|
-
styleModules?: ReadonlyMap<string, StyleModule> | undefined;
|
|
25
|
-
/** For `go`: where the worker module is (`./app.worker.js`), or in it, the functions it runs. */
|
|
26
|
-
goroutines?: {
|
|
27
|
-
workerUrl: string;
|
|
28
|
-
} | {
|
|
29
|
-
targets: readonly string[];
|
|
30
|
-
} | undefined;
|
|
31
|
-
/** How the module gets the runtime it uses: imported, by default, or in it (runtime.ts). */
|
|
32
|
-
runtime?: RuntimeMode | undefined;
|
|
33
|
-
}
|
|
34
|
-
/** A decorator of another module, with the types of the expressions of that module. */
|
|
35
|
-
export interface ImportedDecorator extends DecoratorSource {
|
|
36
|
-
types: WeakMap<ast.Expression, Type> | null;
|
|
37
|
-
/** The components of that module its code uses, which go with it into the components here. */
|
|
38
|
-
inline: InlinePackage | null;
|
|
39
|
-
}
|
|
5
|
+
import type { JsOptions } from './options.ts';
|
|
6
|
+
import { type SharedState } from './shared.ts';
|
|
40
7
|
/** How a function implements its `defer` statements. */
|
|
41
8
|
export type DeferMode = 'none' | 'try' | 'stack' | 'effect';
|
|
42
9
|
interface FunctionContext {
|
|
@@ -54,15 +21,18 @@ interface FunctionContext {
|
|
|
54
21
|
* The base of the JS generator: the output buffer, scopes of declared names and the state of the
|
|
55
22
|
* current function. The generator is split into layers, each adding to the previous one:
|
|
56
23
|
*
|
|
57
|
-
* Emitter → ExpressionEmitter → CallEmitter →
|
|
58
|
-
* →
|
|
59
|
-
* →
|
|
60
|
-
* →
|
|
24
|
+
* Emitter → ExpressionEmitter → CallEmitter → DecodeEmitter → TryEmitter
|
|
25
|
+
* → SimpleStatementEmitter → ControlStatementEmitter → ParallelLoopEmitter
|
|
26
|
+
* → StatementEmitter → UpdateEmitter → AttributeEmitter → CreationEmitter → ElementEmitter
|
|
27
|
+
* → ControlFlowEmitter → ComponentPropsEmitter → ComponentValueEmitter
|
|
28
|
+
* → EarlyReturnEmitter → RefEmitter
|
|
61
29
|
* → StyleEmitter → ComponentEmitter → WebComponentEmitter → FunctionEmitter → JsGenerator
|
|
62
30
|
*/
|
|
63
31
|
export declare abstract class Emitter {
|
|
64
32
|
protected readonly program: ast.Program;
|
|
65
33
|
protected readonly options: JsOptions;
|
|
34
|
+
/** The state of the module that its components share, and that of other modules it reads. */
|
|
35
|
+
protected readonly shared: SharedState;
|
|
66
36
|
protected lines: string[];
|
|
67
37
|
private level;
|
|
68
38
|
private readonly scopes;
|
|
@@ -100,7 +70,7 @@ export declare abstract class Emitter {
|
|
|
100
70
|
/** The decorators that components of the module apply, by name. */
|
|
101
71
|
protected readonly appliedDecorators: Set<string>;
|
|
102
72
|
/** Copies of the code of other modules: their positions are in the sources of those. */
|
|
103
|
-
|
|
73
|
+
protected readonly foreign: WeakSet<ast.Node>;
|
|
104
74
|
/** Components of other modules inlined here, by name, with what their packages bring. */
|
|
105
75
|
protected readonly inlinedImports: Map<string, LocalizedPackage>;
|
|
106
76
|
/** The components that decorators of other modules bring, by the names of the decorators. */
|
|
@@ -123,6 +93,8 @@ export declare abstract class Emitter {
|
|
|
123
93
|
protected lookup(name: string): BindingKind | undefined;
|
|
124
94
|
/** The JS name for a Rook name. */
|
|
125
95
|
protected name(name: string): string;
|
|
96
|
+
/** Labels are names of their own in JS, which cannot be reserved words either. */
|
|
97
|
+
protected label(name: ast.Identifier): string;
|
|
126
98
|
protected params(params: ast.Parameter[]): string;
|
|
127
99
|
protected withFunction<T>(mode: DeferMode, params: ast.Parameter[], emit: () => T): T;
|
|
128
100
|
protected withHoisting<T>(hoist: boolean, emit: () => T): T;
|
package/dist/codegen/emitter.js
CHANGED
|
@@ -4,20 +4,24 @@ import { localize } from "./imported.js";
|
|
|
4
4
|
import { assignedNames, collectValueNames } from "../names.js";
|
|
5
5
|
import { recursiveComponents } from "../recursion.js";
|
|
6
6
|
import { forEachChild } from "../walk.js";
|
|
7
|
+
import { sharedState } from "./shared.js";
|
|
7
8
|
import { JS_RESERVED, JS_UNDECLARABLE } from "./syntax.js";
|
|
8
9
|
/**
|
|
9
10
|
* The base of the JS generator: the output buffer, scopes of declared names and the state of the
|
|
10
11
|
* current function. The generator is split into layers, each adding to the previous one:
|
|
11
12
|
*
|
|
12
|
-
* Emitter → ExpressionEmitter → CallEmitter →
|
|
13
|
-
* →
|
|
14
|
-
* →
|
|
15
|
-
* →
|
|
13
|
+
* Emitter → ExpressionEmitter → CallEmitter → DecodeEmitter → TryEmitter
|
|
14
|
+
* → SimpleStatementEmitter → ControlStatementEmitter → ParallelLoopEmitter
|
|
15
|
+
* → StatementEmitter → UpdateEmitter → AttributeEmitter → CreationEmitter → ElementEmitter
|
|
16
|
+
* → ControlFlowEmitter → ComponentPropsEmitter → ComponentValueEmitter
|
|
17
|
+
* → EarlyReturnEmitter → RefEmitter
|
|
16
18
|
* → StyleEmitter → ComponentEmitter → WebComponentEmitter → FunctionEmitter → JsGenerator
|
|
17
19
|
*/
|
|
18
20
|
export class Emitter {
|
|
19
21
|
program;
|
|
20
22
|
options;
|
|
23
|
+
/** The state of the module that its components share, and that of other modules it reads. */
|
|
24
|
+
shared;
|
|
21
25
|
lines = [];
|
|
22
26
|
level = 0;
|
|
23
27
|
scopes = [];
|
|
@@ -67,16 +71,27 @@ export class Emitter {
|
|
|
67
71
|
constructor(program, options) {
|
|
68
72
|
this.program = program;
|
|
69
73
|
this.options = options;
|
|
74
|
+
this.shared = sharedState(program, options.shared);
|
|
70
75
|
const decorators = new Map(options.decorators);
|
|
71
76
|
const imported = [...(options.decorators?.values() ?? [])];
|
|
72
77
|
for (const statement of program.body) {
|
|
73
78
|
if (statement.kind === 'DecoratorDeclaration') {
|
|
74
79
|
decorators.set(statement.name.name, { node: statement, moduleNames: [] });
|
|
75
80
|
}
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
81
|
+
}
|
|
82
|
+
// Those that the applied ones include are applied too.
|
|
83
|
+
const apply = (name) => {
|
|
84
|
+
if (this.appliedDecorators.has(name))
|
|
85
|
+
return;
|
|
86
|
+
this.appliedDecorators.add(name);
|
|
87
|
+
for (const use of decorators.get(name)?.node.includes ?? [])
|
|
88
|
+
apply(use.name.name);
|
|
89
|
+
};
|
|
90
|
+
for (const statement of program.body) {
|
|
91
|
+
if (statement.kind !== 'ComponentDeclaration')
|
|
92
|
+
continue;
|
|
93
|
+
for (const use of statement.decorators)
|
|
94
|
+
apply(use.name.name);
|
|
80
95
|
}
|
|
81
96
|
const originals = new WeakSet();
|
|
82
97
|
const collect = (node) => {
|
|
@@ -250,6 +265,10 @@ export class Emitter {
|
|
|
250
265
|
const rename = JS_RESERVED.has(name) || (JS_UNDECLARABLE.has(name) && this.lookup(name) !== undefined);
|
|
251
266
|
return rename ? `${name}$` : name;
|
|
252
267
|
}
|
|
268
|
+
/** Labels are names of their own in JS, which cannot be reserved words either. */
|
|
269
|
+
label(name) {
|
|
270
|
+
return JS_RESERVED.has(name.name) ? `${name.name}$` : name.name;
|
|
271
|
+
}
|
|
253
272
|
params(params) {
|
|
254
273
|
// Several `_` parameters would be duplicate names in JS.
|
|
255
274
|
const blanks = params.filter((param) => param.name.name === '_').length;
|
|
@@ -21,6 +21,15 @@ export declare abstract class ExpressionEmitter extends Emitter {
|
|
|
21
21
|
protected abstract chan(node: ast.ChanExpression): string;
|
|
22
22
|
protected abstract streamChan(node: ast.StreamChanExpression): string;
|
|
23
23
|
protected abstract queue(node: ast.QueueExpression): string;
|
|
24
|
+
protected abstract topic(node: ast.TopicExpression): string;
|
|
25
|
+
/** `go f(x)`; implemented by CallEmitter. */
|
|
26
|
+
protected abstract go(node: ast.GoExpression): string;
|
|
27
|
+
/** `decode<T>(x)`; implemented by DecodeEmitter. */
|
|
28
|
+
protected abstract decode(node: ast.DecodeExpression): string;
|
|
29
|
+
/** `try x`, with the precedence of its code; implemented by TryEmitter. */
|
|
30
|
+
protected abstract tryValue(node: ast.TryExpression): [string, number];
|
|
31
|
+
/** `<-x` without its `await`; implemented by CallEmitter. */
|
|
32
|
+
protected abstract receive(node: ast.Expression, withOk: boolean): string;
|
|
24
33
|
/** `<-ch` that gives `[value, ok]`, in `v, ok = <-ch`. */
|
|
25
34
|
protected readonly commaOk: WeakSet<ast.Expression>;
|
|
26
35
|
/** `items[i]` in a part of `go for`, which is `items[$$i]` there (parallel.ts). */
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import { decoratedName } from "../decorators.js";
|
|
2
|
-
import { goCaptures, goParams, goTarget } from "../goroutines.js";
|
|
3
2
|
import { isIterator, usesThis } from "../iterators.js";
|
|
4
3
|
import { containsElement, startsWithObjectLiteral } from "./analysis.js";
|
|
5
4
|
import { Emitter } from "./emitter.js";
|
|
@@ -59,8 +58,9 @@ export class ExpressionEmitter extends Emitter {
|
|
|
59
58
|
return [this.functionNames.get(component) ?? `$$${component.name.name}`, PRIMARY];
|
|
60
59
|
}
|
|
61
60
|
// `close(ch)` and the like are predeclared; JS has a `close` of its own, the window's.
|
|
62
|
-
|
|
63
|
-
|
|
61
|
+
const predeclared = PREDECLARED.get(node.name);
|
|
62
|
+
if (predeclared !== undefined && this.lookup(node.name) === undefined) {
|
|
63
|
+
return [predeclared, PRIMARY];
|
|
64
64
|
}
|
|
65
65
|
// The predeclared `error` used as a value is JS's `Error`, which also works without `new`.
|
|
66
66
|
return [this.isBuiltinError(node) ? 'Error' : this.name(node.name), PRIMARY];
|
|
@@ -69,6 +69,8 @@ export class ExpressionEmitter extends Emitter {
|
|
|
69
69
|
case 'BigIntLiteral':
|
|
70
70
|
case 'StringLiteral':
|
|
71
71
|
return [node.raw, PRIMARY];
|
|
72
|
+
case 'RegExpLiteral':
|
|
73
|
+
return [`/${node.pattern}/${node.flags}`, PRIMARY];
|
|
72
74
|
case 'TemplateLiteral':
|
|
73
75
|
return [this.template(node), PRIMARY];
|
|
74
76
|
case 'BooleanLiteral':
|
|
@@ -78,8 +80,6 @@ export class ExpressionEmitter extends Emitter {
|
|
|
78
80
|
// The member is a variable of the component's code: see decorate().
|
|
79
81
|
case 'DecoratorMember':
|
|
80
82
|
return [decoratedName(node.decorator.name, node.member.name), PRIMARY];
|
|
81
|
-
case 'DecoratorGet':
|
|
82
|
-
return [decoratedName(node.target.decorator.name, node.target.member.name), PRIMARY];
|
|
83
83
|
case 'ThisExpression':
|
|
84
84
|
return ['this', PRIMARY];
|
|
85
85
|
// `as` is only for the checker.
|
|
@@ -112,23 +112,14 @@ export class ExpressionEmitter extends Emitter {
|
|
|
112
112
|
return [this.streamChan(node), POSTFIX];
|
|
113
113
|
case 'QueueExpression':
|
|
114
114
|
return [this.queue(node), POSTFIX];
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
...captured.map((name) => this.name(name)),
|
|
124
|
-
this.elements(node.call.arguments),
|
|
125
|
-
];
|
|
126
|
-
// Without type checking, a callee that is neither fails when the call runs.
|
|
127
|
-
const fn = literal && this.inWorker
|
|
128
|
-
? this.expression({ ...literal, params: [...goParams(literal, captured), ...literal.params] }, ARROW)
|
|
129
|
-
: JSON.stringify(goTarget(this.program, node));
|
|
130
|
-
return [`$$go(${fn}, [${args.filter((arg) => arg !== '').join(', ')}])`, POSTFIX];
|
|
131
|
-
}
|
|
115
|
+
case 'TopicExpression':
|
|
116
|
+
return [this.topic(node), POSTFIX];
|
|
117
|
+
case 'GoExpression':
|
|
118
|
+
return [this.go(node), POSTFIX];
|
|
119
|
+
case 'DecodeExpression':
|
|
120
|
+
return [this.decode(node), POSTFIX];
|
|
121
|
+
case 'TryExpression':
|
|
122
|
+
return this.tryValue(node);
|
|
132
123
|
case 'UnaryExpression':
|
|
133
124
|
return [this.unary(node), UNARY];
|
|
134
125
|
case 'BinaryExpression':
|
|
@@ -221,10 +212,8 @@ export class ExpressionEmitter extends Emitter {
|
|
|
221
212
|
});
|
|
222
213
|
}
|
|
223
214
|
unary(node) {
|
|
224
|
-
if (node.operator === '<-')
|
|
225
|
-
|
|
226
|
-
return `await $$receive(${channel}${this.commaOk.has(node) ? ', true' : ''})`;
|
|
227
|
-
}
|
|
215
|
+
if (node.operator === '<-')
|
|
216
|
+
return `await ${this.receive(node.argument, this.commaOk.has(node))}`;
|
|
228
217
|
const argument = this.expression(node.argument, UNARY);
|
|
229
218
|
if (node.operator === 'typeof' || node.operator === 'await') {
|
|
230
219
|
return `${node.operator} ${argument}`;
|
|
@@ -10,7 +10,17 @@ export declare abstract class FunctionEmitter extends WebComponentEmitter {
|
|
|
10
10
|
* it runs again, and when `{if}`, `{switch}` or `{for}` removes the component.
|
|
11
11
|
*/
|
|
12
12
|
protected effectStatement(node: ast.EffectStatement): void;
|
|
13
|
-
|
|
13
|
+
/**
|
|
14
|
+
* `mount { ... }` becomes `$$mount(() => node, () => { ... })`. Its body runs later, like a
|
|
15
|
+
* handler: what it changes updates the markup. What it defers goes into a list that the runtime
|
|
16
|
+
* runs when `{if}`, `{switch}` or `{for}` removes the component.
|
|
17
|
+
*/
|
|
18
|
+
protected mountStatement(node: ast.MountStatement): void;
|
|
19
|
+
/**
|
|
20
|
+
* The body of an effect or of `mount`, whose `defer`s put their calls into `cleanups`. An effect
|
|
21
|
+
* that runs again first runs what it deferred before.
|
|
22
|
+
*/
|
|
23
|
+
private deferringBody;
|
|
14
24
|
/** A `defer` in 'stack' and 'effect' modes; in 'try' mode statementsWithDefers handles them. */
|
|
15
25
|
protected deferStatement(node: ast.DeferStatement): void;
|
|
16
26
|
/** Statements of a body whose `defer`s are all at its top level: one try/finally per `defer`. */
|
|
@@ -46,7 +46,7 @@ export class FunctionEmitter extends WebComponentEmitter {
|
|
|
46
46
|
const cleanups = findDefers(node.body).length > 0 ? `$$cleanups${id}` : null;
|
|
47
47
|
this.inHandler++;
|
|
48
48
|
try {
|
|
49
|
-
const body = this.withRendering(0, () => cleanups === null ? this.func([], node.body)[1] : this.
|
|
49
|
+
const body = this.withRendering(0, () => cleanups === null ? this.func([], node.body)[1] : this.deferringBody(node.body, cleanups));
|
|
50
50
|
if (cleanups !== null)
|
|
51
51
|
this.line(`const ${cleanups} = [];`);
|
|
52
52
|
this.line(`const ${name} = () => ${body};`);
|
|
@@ -56,11 +56,39 @@ export class FunctionEmitter extends WebComponentEmitter {
|
|
|
56
56
|
}
|
|
57
57
|
this.addEffect({ name, node, cleanups });
|
|
58
58
|
}
|
|
59
|
-
|
|
59
|
+
/**
|
|
60
|
+
* `mount { ... }` becomes `$$mount(() => node, () => { ... })`. Its body runs later, like a
|
|
61
|
+
* handler: what it changes updates the markup. What it defers goes into a list that the runtime
|
|
62
|
+
* runs when `{if}`, `{switch}` or `{for}` removes the component.
|
|
63
|
+
*/
|
|
64
|
+
mountStatement(node) {
|
|
65
|
+
const cleanups = findDefers(node.body).length > 0 ? `$$cleanups${this.nextId()}` : null;
|
|
66
|
+
this.inHandler++;
|
|
67
|
+
try {
|
|
68
|
+
const body = this.withRendering(0, () => cleanups === null
|
|
69
|
+
? this.func([], node.body)[1]
|
|
70
|
+
: this.deferringBody(node.body, cleanups, false));
|
|
71
|
+
const args = [`() => ${this.mountNode ?? 'null'}`, `() => ${body}`];
|
|
72
|
+
if (cleanups !== null) {
|
|
73
|
+
this.line(`const ${cleanups} = [];`);
|
|
74
|
+
args.push(cleanups);
|
|
75
|
+
}
|
|
76
|
+
this.line(`$$mount(${args.join(', ')});`);
|
|
77
|
+
}
|
|
78
|
+
finally {
|
|
79
|
+
this.inHandler--;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* The body of an effect or of `mount`, whose `defer`s put their calls into `cleanups`. An effect
|
|
84
|
+
* that runs again first runs what it deferred before.
|
|
85
|
+
*/
|
|
86
|
+
deferringBody(body, cleanups, again = true) {
|
|
60
87
|
return this.withFunction('effect', [], () => {
|
|
61
88
|
this.fn.deferList = cleanups;
|
|
62
89
|
return this.block(() => this.withScope(declarationsOf(body.body), () => {
|
|
63
|
-
|
|
90
|
+
if (again)
|
|
91
|
+
this.line(`$$runDeferred(${cleanups}.splice(0));`);
|
|
64
92
|
this.statements(body.body);
|
|
65
93
|
}));
|
|
66
94
|
});
|
package/dist/codegen/js.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type * as ast from '../ast.ts';
|
|
2
|
-
import type { JsOptions } from './
|
|
3
|
-
export type { JsOptions } from './
|
|
2
|
+
import type { JsOptions } from './options.ts';
|
|
3
|
+
export type { JsOptions } from './options.ts';
|
|
4
4
|
/** A component whose inlined copies add more lines than this to its module becomes a function. */
|
|
5
5
|
export declare const INLINE_LIMIT = 100;
|
|
6
6
|
/**
|
package/dist/codegen/js.js
CHANGED
|
@@ -62,12 +62,14 @@ class JsGenerator extends FunctionEmitter {
|
|
|
62
62
|
// What is the module's own: its `go`, and the CSS of its components for their shadow roots.
|
|
63
63
|
const own = [
|
|
64
64
|
...(/(?<![.\w$])\$\$go(?![\w$])/.test(rest.join('\n'))
|
|
65
|
-
? moduleGo(inWorker ? null : (goroutines?.workerUrl ?? ''))
|
|
65
|
+
? moduleGo(inWorker ? null : (goroutines?.workerUrl ?? ''), goroutines && 'nodeLoader' in goroutines ? goroutines.nodeLoader : undefined)
|
|
66
66
|
: []),
|
|
67
67
|
...(styles.length > 0 ? ['const $$styles = [];'] : []),
|
|
68
68
|
];
|
|
69
|
+
// The types that decode checks against, before the code that may decode at the top level.
|
|
70
|
+
const types = this.decodeTypeLines();
|
|
69
71
|
const mode = this.options.runtime ?? 'import';
|
|
70
|
-
const runtime = runtimeCode(runtimeNames([...own, ...styles, ...rest].join('\n')), mode);
|
|
72
|
+
const runtime = runtimeCode(runtimeNames([...own, ...types, ...styles, ...rest].join('\n')), mode);
|
|
71
73
|
if (runtime.length === 0 && own.length === 0 && styles.length === 0) {
|
|
72
74
|
return this.lines.length === 0 ? '' : `${this.lines.join('\n')}\n`;
|
|
73
75
|
}
|
|
@@ -79,6 +81,7 @@ class JsGenerator extends FunctionEmitter {
|
|
|
79
81
|
mode === 'import' ? [...head, ...runtime] : head,
|
|
80
82
|
mode === 'inline' ? runtime : [],
|
|
81
83
|
own,
|
|
84
|
+
types,
|
|
82
85
|
styles,
|
|
83
86
|
rest,
|
|
84
87
|
].filter((section) => section.length > 0);
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import type * as ast from '../ast.ts';
|
|
2
|
+
/** What markup can depend on: a `state` variable, or a property that gets the parent's state. */
|
|
3
|
+
export interface Source {
|
|
4
|
+
/** Unique in the module; marks the places that change the source in the generated code. */
|
|
5
|
+
readonly id: number;
|
|
6
|
+
readonly name: string;
|
|
7
|
+
/** `shared` is state of the module, which the component reads and listens to. */
|
|
8
|
+
readonly kind: 'state' | 'prop' | 'shared';
|
|
9
|
+
/** The function that brings dependent nodes up to date, e.g. `$$updateCount3`. */
|
|
10
|
+
readonly update: string;
|
|
11
|
+
/**
|
|
12
|
+
* Statements that compute again the `derived` values that read it: `double = count * 2`. They
|
|
13
|
+
* run first, also before the markup exists, so that the values are right whenever they are read.
|
|
14
|
+
*/
|
|
15
|
+
readonly computations: string[];
|
|
16
|
+
/** Statements that bring the nodes depending on it up to date. */
|
|
17
|
+
readonly dependents: string[];
|
|
18
|
+
/** Places that change it. */
|
|
19
|
+
readonly writes: Write[];
|
|
20
|
+
/**
|
|
21
|
+
* A bound property, `bind active string`: the call that gives the parent its new value,
|
|
22
|
+
* `$$bindActive?.(active);`. Places that change it make the call; the parent setting it does not.
|
|
23
|
+
*/
|
|
24
|
+
bound: string | null;
|
|
25
|
+
}
|
|
26
|
+
export interface Write {
|
|
27
|
+
/** Whether it may run before the markup exists: in a function of the body, not in a handler. */
|
|
28
|
+
early: boolean;
|
|
29
|
+
/** A dependent it does not need: `bind:` does not update its own element. */
|
|
30
|
+
skip: number | null;
|
|
31
|
+
}
|
|
32
|
+
/** Everything a change of the source runs: the computations, then the updates of the nodes. */
|
|
33
|
+
export declare function updatesOf(source: Source): string[];
|
|
34
|
+
/**
|
|
35
|
+
* Where the updates of live markup go: the update functions of a component's state, or the update
|
|
36
|
+
* of a block of `{if ...}` / `{for ...}`.
|
|
37
|
+
*/
|
|
38
|
+
export interface Live {
|
|
39
|
+
/** Sources whose values an expression reads. */
|
|
40
|
+
dependencies(node: ast.Node): Set<Source>;
|
|
41
|
+
/** Adds a statement that brings a part of the markup up to date when the sources change. */
|
|
42
|
+
depend(sources: ReadonlySet<Source>, statement: string): void;
|
|
43
|
+
/** The index that the next statement for a source gets, for `bind:` to skip its own update. */
|
|
44
|
+
ownIndex(source: Source): number | null;
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* A block of markup control flow: a branch of `{if ...}` or the content for one item of
|
|
48
|
+
* `{for ...}`. It is created again when needed, so its updates go into a function of its own,
|
|
49
|
+
* which runs when any source that the block reads changes.
|
|
50
|
+
*/
|
|
51
|
+
export declare class Block implements Live {
|
|
52
|
+
readonly statements: string[];
|
|
53
|
+
/** Sources that any statement depends on. */
|
|
54
|
+
readonly sources: Set<Source>;
|
|
55
|
+
private readonly parent;
|
|
56
|
+
constructor(parent: Live);
|
|
57
|
+
dependencies(node: ast.Node): Set<Source>;
|
|
58
|
+
depend(sources: ReadonlySet<Source>, statement: string): void;
|
|
59
|
+
ownIndex(): null;
|
|
60
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/** Everything a change of the source runs: the computations, then the updates of the nodes. */
|
|
2
|
+
export function updatesOf(source) {
|
|
3
|
+
return [...source.computations, ...source.dependents];
|
|
4
|
+
}
|
|
5
|
+
/**
|
|
6
|
+
* A block of markup control flow: a branch of `{if ...}` or the content for one item of
|
|
7
|
+
* `{for ...}`. It is created again when needed, so its updates go into a function of its own,
|
|
8
|
+
* which runs when any source that the block reads changes.
|
|
9
|
+
*/
|
|
10
|
+
export class Block {
|
|
11
|
+
statements = [];
|
|
12
|
+
/** Sources that any statement depends on. */
|
|
13
|
+
sources = new Set();
|
|
14
|
+
parent;
|
|
15
|
+
constructor(parent) {
|
|
16
|
+
this.parent = parent;
|
|
17
|
+
}
|
|
18
|
+
dependencies(node) {
|
|
19
|
+
return this.parent.dependencies(node);
|
|
20
|
+
}
|
|
21
|
+
depend(sources, statement) {
|
|
22
|
+
this.statements.push(statement);
|
|
23
|
+
for (const source of sources)
|
|
24
|
+
this.sources.add(source);
|
|
25
|
+
}
|
|
26
|
+
ownIndex() {
|
|
27
|
+
return null;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type * as ast from '../ast.ts';
|
|
2
|
+
import type { Type } from '../checker/types.ts';
|
|
2
3
|
/** The variable at the start of `a.b[i].c()`, or `null`. */
|
|
3
4
|
export declare function rootName(node: ast.Expression): string | null;
|
|
4
5
|
/** Names whose values a node reads, not counting code that runs on events. */
|
|
@@ -8,3 +9,9 @@ export declare function readNames(node: ast.Node, names: Set<string>): void;
|
|
|
8
9
|
* its `defer`, nor the variables it writes, or `count++` would run it again.
|
|
9
10
|
*/
|
|
10
11
|
export declare function effectReads(node: ast.Node, names: Set<string>): void;
|
|
12
|
+
/**
|
|
13
|
+
* What a statement (or an expression body) changes: the targets of assignments, `++` and
|
|
14
|
+
* `delete`, and the values that a method that may change them is called on, such as `push`.
|
|
15
|
+
* Nested blocks and functions are not included: their statements are handled on their own.
|
|
16
|
+
*/
|
|
17
|
+
export declare function writeTargets(node: ast.Node, typeOf: (node: ast.Expression) => Type | undefined): ast.Expression[];
|