rooklang 0.3.4 → 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 +2 -0
- package/README.ru.md +2 -0
- package/dist/ast/declarations.d.ts +19 -3
- package/dist/ast/expressions.d.ts +4 -8
- package/dist/ast/statements.d.ts +3 -1
- package/dist/checker/assignments.d.ts +2 -0
- package/dist/checker/assignments.js +9 -3
- package/dist/checker/base.d.ts +8 -2
- package/dist/checker/base.js +3 -3
- package/dist/checker/builtins.js +7 -3
- package/dist/checker/channels.js +1 -2
- package/dist/checker/classes.js +7 -1
- package/dist/checker/closing.js +1 -1
- package/dist/checker/completion.d.ts +1 -1
- package/dist/checker/completion.js +2 -1
- 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 +28 -5
- package/dist/checker/context.js +23 -0
- package/dist/checker/control-flow.d.ts +6 -5
- package/dist/checker/control-flow.js +48 -40
- package/dist/checker/declarations.d.ts +2 -2
- package/dist/checker/declarations.js +3 -2
- 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 +1 -1
- package/dist/checker/expressions.js +7 -6
- package/dist/checker/flow.d.ts +25 -3
- package/dist/checker/flow.js +48 -8
- package/dist/checker/functions.d.ts +2 -2
- package/dist/checker/functions.js +9 -5
- package/dist/checker/helpers.d.ts +4 -1
- package/dist/checker/helpers.js +7 -2
- package/dist/checker/imports.js +5 -2
- package/dist/checker/loops.d.ts +2 -0
- package/dist/checker/loops.js +27 -2
- package/dist/checker/markup.js +14 -2
- package/dist/checker/members.js +3 -1
- package/dist/checker/shared-state.d.ts +11 -0
- package/dist/checker/shared-state.js +37 -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/untyped-globals.d.ts +1 -0
- package/dist/checker/untyped-globals.js +3 -1
- package/dist/codegen/attributes.d.ts +4 -0
- package/dist/codegen/attributes.js +24 -2
- 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/creation.js +5 -2
- package/dist/codegen/early-returns.d.ts +4 -3
- package/dist/codegen/early-returns.js +17 -5
- package/dist/codegen/emitter.d.ts +3 -0
- package/dist/codegen/emitter.js +18 -4
- package/dist/codegen/expressions.js +0 -2
- package/dist/codegen/functions.d.ts +11 -1
- package/dist/codegen/functions.js +31 -3
- 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 +2 -0
- package/dist/codegen/reactive.d.ts +4 -59
- package/dist/codegen/reactive.js +34 -72
- package/dist/codegen/runtime.js +6 -1
- package/dist/codegen/shared.d.ts +22 -0
- package/dist/codegen/shared.js +93 -0
- package/dist/codegen/simple-statements.d.ts +1 -1
- package/dist/codegen/statements.d.ts +5 -0
- package/dist/codegen/statements.js +35 -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.js +6 -2
- package/dist/decorator-copy.d.ts +1 -1
- package/dist/decorator-copy.js +1 -0
- 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/inline-packages.d.ts +2 -1
- package/dist/inline-packages.js +9 -4
- package/dist/names.d.ts +4 -0
- package/dist/names.js +29 -0
- package/dist/parser/base.d.ts +1 -0
- package/dist/parser/base.js +1 -1
- package/dist/parser/control-flow.d.ts +5 -0
- package/dist/parser/control-flow.js +28 -2
- package/dist/parser/declarations.d.ts +5 -15
- package/dist/parser/declarations.js +15 -119
- package/dist/parser/decorators.d.ts +6 -2
- package/dist/parser/decorators.js +43 -19
- package/dist/parser/params.d.ts +21 -0
- package/dist/parser/params.js +143 -0
- package/dist/parser/simple-statements.d.ts +1 -0
- package/dist/parser/simple-statements.js +22 -6
- package/dist/parser/statements.d.ts +5 -2
- package/dist/parser/statements.js +18 -11
- package/dist/runtime/sources.js +3 -2
- package/dist/walk.js +3 -3
- package/package.json +2 -2
- package/src/runtime/decode.js +41 -10
- package/src/runtime/index.js +1 -0
- package/src/runtime/markup.js +27 -17
- package/src/runtime/shared.js +17 -0
|
@@ -1,59 +1,8 @@
|
|
|
1
1
|
import type * as ast from '../ast.ts';
|
|
2
2
|
import type { Type } from '../checker/types.ts';
|
|
3
|
+
import type { Live, Source } from './live.ts';
|
|
3
4
|
import { readNames } from './mutations.ts';
|
|
4
|
-
|
|
5
|
-
export interface Source {
|
|
6
|
-
/** Unique in the module; marks the places that change the source in the generated code. */
|
|
7
|
-
readonly id: number;
|
|
8
|
-
readonly name: string;
|
|
9
|
-
readonly kind: 'state' | 'prop';
|
|
10
|
-
/** The function that brings dependent nodes up to date, e.g. `$$updateCount3`. */
|
|
11
|
-
readonly update: string;
|
|
12
|
-
/**
|
|
13
|
-
* Statements that compute again the `derived` values that read it: `double = count * 2`. They
|
|
14
|
-
* run first, also before the markup exists, so that the values are right whenever they are read.
|
|
15
|
-
*/
|
|
16
|
-
readonly computations: string[];
|
|
17
|
-
/** Statements that bring the nodes depending on it up to date. */
|
|
18
|
-
readonly dependents: string[];
|
|
19
|
-
/** Places that change it. */
|
|
20
|
-
readonly writes: Write[];
|
|
21
|
-
}
|
|
22
|
-
export interface Write {
|
|
23
|
-
/** Whether it may run before the markup exists: in a function of the body, not in a handler. */
|
|
24
|
-
early: boolean;
|
|
25
|
-
/** A dependent it does not need: `bind:` does not update its own element. */
|
|
26
|
-
skip: number | null;
|
|
27
|
-
}
|
|
28
|
-
/** Everything a change of the source runs: the computations, then the updates of the nodes. */
|
|
29
|
-
export declare function updatesOf(source: Source): string[];
|
|
30
|
-
/**
|
|
31
|
-
* Where the updates of live markup go: the update functions of a component's state, or the update
|
|
32
|
-
* of a block of `{if ...}` / `{for ...}`.
|
|
33
|
-
*/
|
|
34
|
-
export interface Live {
|
|
35
|
-
/** Sources whose values an expression reads. */
|
|
36
|
-
dependencies(node: ast.Node): Set<Source>;
|
|
37
|
-
/** Adds a statement that brings a part of the markup up to date when the sources change. */
|
|
38
|
-
depend(sources: ReadonlySet<Source>, statement: string): void;
|
|
39
|
-
/** The index that the next statement for a source gets, for `bind:` to skip its own update. */
|
|
40
|
-
ownIndex(source: Source): number | null;
|
|
41
|
-
}
|
|
42
|
-
/**
|
|
43
|
-
* A block of markup control flow: a branch of `{if ...}` or the content for one item of
|
|
44
|
-
* `{for ...}`. It is created again when needed, so its updates go into a function of its own,
|
|
45
|
-
* which runs when any source that the block reads changes.
|
|
46
|
-
*/
|
|
47
|
-
export declare class Block implements Live {
|
|
48
|
-
readonly statements: string[];
|
|
49
|
-
/** Sources that any statement depends on. */
|
|
50
|
-
readonly sources: Set<Source>;
|
|
51
|
-
private readonly parent;
|
|
52
|
-
constructor(parent: Live);
|
|
53
|
-
dependencies(node: ast.Node): Set<Source>;
|
|
54
|
-
depend(sources: ReadonlySet<Source>, statement: string): void;
|
|
55
|
-
ownIndex(): null;
|
|
56
|
-
}
|
|
5
|
+
export { Block, type Live, type Source, type Write, updatesOf } from './live.ts';
|
|
57
6
|
/**
|
|
58
7
|
* The reactivity of one inlined component. The analysis goes by names: a local variable that
|
|
59
8
|
* has the name of a state variable can only cause extra updates, never a missed one.
|
|
@@ -70,7 +19,7 @@ export declare class Reactive implements Live {
|
|
|
70
19
|
private readonly functions;
|
|
71
20
|
/** Local functions that the live markup calls: they run while it is created and updated. */
|
|
72
21
|
private readonly renderFunctions;
|
|
73
|
-
constructor(component: ast.ComponentDeclaration, setup: unknown, props: Iterable<string>, createSource: (name: string, kind: Source['kind']) => Source);
|
|
22
|
+
constructor(component: ast.ComponentDeclaration, setup: unknown, props: Iterable<string>, createSource: (name: string, kind: Source['kind']) => Source, shared?: ReadonlySet<string>);
|
|
74
23
|
/** Whether a statement of the body declares a function that the live markup calls. */
|
|
75
24
|
declaresRenderFunction(node: ast.Statement): boolean;
|
|
76
25
|
/** Adds a statement that computes a `derived` value again when the sources change. */
|
|
@@ -82,11 +31,7 @@ export declare class Reactive implements Live {
|
|
|
82
31
|
* the names that the node itself reads.
|
|
83
32
|
*/
|
|
84
33
|
dependencies(node: ast.Node, read?: typeof readNames): Set<Source>;
|
|
85
|
-
/**
|
|
86
|
-
* `state` variables that a statement (or an expression body) changes: by assignment, `++`,
|
|
87
|
-
* or a method that may change the value, such as `push`. Nested blocks and functions are not
|
|
88
|
-
* included: their statements are handled on their own.
|
|
89
|
-
*/
|
|
34
|
+
/** `state` variables that a statement changes, by assignment, `++` or a method like `push`. */
|
|
90
35
|
writtenBy(node: ast.Node, typeOf: (node: ast.Expression) => Type | undefined): Set<Source>;
|
|
91
36
|
/** Sources whose value `node` refers into: `todos`, `todos[i].tags`, `todos.find(...)`. */
|
|
92
37
|
rootSources(node: ast.Expression): ReadonlySet<Source>;
|
package/dist/codegen/reactive.js
CHANGED
|
@@ -1,38 +1,11 @@
|
|
|
1
|
+
import { ownStatements } from "../checker/helpers.js";
|
|
1
2
|
import { wrappedMarkup } from "../decorators.js";
|
|
2
3
|
import { containsReturn, returnedMarkup } from "../recursion.js";
|
|
3
4
|
import { forEachChild } from "../walk.js";
|
|
4
|
-
import {
|
|
5
|
-
|
|
5
|
+
import { readNames, rootName, writeTargets } from "./mutations.js";
|
|
6
|
+
// Reactivity of components: which markup depends on which state, and which code changes it.
|
|
7
|
+
export { Block, updatesOf } from "./live.js";
|
|
6
8
|
const EMPTY = new Set();
|
|
7
|
-
/** Everything a change of the source runs: the computations, then the updates of the nodes. */
|
|
8
|
-
export function updatesOf(source) {
|
|
9
|
-
return [...source.computations, ...source.dependents];
|
|
10
|
-
}
|
|
11
|
-
/**
|
|
12
|
-
* A block of markup control flow: a branch of `{if ...}` or the content for one item of
|
|
13
|
-
* `{for ...}`. It is created again when needed, so its updates go into a function of its own,
|
|
14
|
-
* which runs when any source that the block reads changes.
|
|
15
|
-
*/
|
|
16
|
-
export class Block {
|
|
17
|
-
statements = [];
|
|
18
|
-
/** Sources that any statement depends on. */
|
|
19
|
-
sources = new Set();
|
|
20
|
-
parent;
|
|
21
|
-
constructor(parent) {
|
|
22
|
-
this.parent = parent;
|
|
23
|
-
}
|
|
24
|
-
dependencies(node) {
|
|
25
|
-
return this.parent.dependencies(node);
|
|
26
|
-
}
|
|
27
|
-
depend(sources, statement) {
|
|
28
|
-
this.statements.push(statement);
|
|
29
|
-
for (const source of sources)
|
|
30
|
-
this.sources.add(source);
|
|
31
|
-
}
|
|
32
|
-
ownIndex() {
|
|
33
|
-
return null;
|
|
34
|
-
}
|
|
35
|
-
}
|
|
36
9
|
/**
|
|
37
10
|
* The reactivity of one inlined component. The analysis goes by names: a local variable that
|
|
38
11
|
* has the name of a state variable can only cause extra updates, never a missed one.
|
|
@@ -49,10 +22,22 @@ export class Reactive {
|
|
|
49
22
|
functions = new Map();
|
|
50
23
|
/** Local functions that the live markup calls: they run while it is created and updated. */
|
|
51
24
|
renderFunctions = new Set();
|
|
52
|
-
constructor(component, setup, props, createSource) {
|
|
25
|
+
constructor(component, setup, props, createSource, shared = new Set()) {
|
|
53
26
|
this.setup = setup;
|
|
27
|
+
// State of the module that the component reads; its own names, set after, hide it.
|
|
28
|
+
const read = new Set();
|
|
29
|
+
readNames(component, read);
|
|
30
|
+
for (const name of read) {
|
|
31
|
+
if (shared.has(name))
|
|
32
|
+
this.sources.set(name, createSource(name, 'shared'));
|
|
33
|
+
}
|
|
54
34
|
for (const name of props)
|
|
55
35
|
this.sources.set(name, createSource(name, 'prop'));
|
|
36
|
+
// A bound property changes as state does, whether the parent sets it or not.
|
|
37
|
+
for (const param of component.params) {
|
|
38
|
+
if (param.bind)
|
|
39
|
+
this.sources.set(param.name.name, createSource(param.name.name, 'state'));
|
|
40
|
+
}
|
|
56
41
|
// Markup declared after an early return may never be created, so it is not updated.
|
|
57
42
|
let returned = false;
|
|
58
43
|
for (const statement of component.body.body) {
|
|
@@ -146,52 +131,15 @@ export class Reactive {
|
|
|
146
131
|
visit(node, read);
|
|
147
132
|
return found;
|
|
148
133
|
}
|
|
149
|
-
/**
|
|
150
|
-
* `state` variables that a statement (or an expression body) changes: by assignment, `++`,
|
|
151
|
-
* or a method that may change the value, such as `push`. Nested blocks and functions are not
|
|
152
|
-
* included: their statements are handled on their own.
|
|
153
|
-
*/
|
|
134
|
+
/** `state` variables that a statement changes, by assignment, `++` or a method like `push`. */
|
|
154
135
|
writtenBy(node, typeOf) {
|
|
155
136
|
const found = new Set();
|
|
156
|
-
const
|
|
137
|
+
for (const target of writeTargets(node, typeOf)) {
|
|
157
138
|
for (const source of this.rootSources(target)) {
|
|
158
139
|
if (source.kind === 'state')
|
|
159
140
|
found.add(source);
|
|
160
141
|
}
|
|
161
|
-
}
|
|
162
|
-
const visit = (node) => {
|
|
163
|
-
switch (node.kind) {
|
|
164
|
-
case 'BlockStatement':
|
|
165
|
-
case 'EventHandler':
|
|
166
|
-
case 'FuncDeclaration':
|
|
167
|
-
case 'FuncExpression':
|
|
168
|
-
case 'ArrowFunction':
|
|
169
|
-
case 'ClassDeclaration':
|
|
170
|
-
return;
|
|
171
|
-
case 'SwitchCase':
|
|
172
|
-
node.tests.forEach(visit);
|
|
173
|
-
return;
|
|
174
|
-
case 'AssignmentStatement':
|
|
175
|
-
node.targets.forEach(write);
|
|
176
|
-
break;
|
|
177
|
-
case 'IncDecStatement':
|
|
178
|
-
case 'DeleteStatement':
|
|
179
|
-
write(node.target);
|
|
180
|
-
break;
|
|
181
|
-
case 'CallExpression': {
|
|
182
|
-
const { callee } = node;
|
|
183
|
-
if (callee.kind === 'MemberExpression' &&
|
|
184
|
-
mayChange(typeOf(callee.object), callee.property.name)) {
|
|
185
|
-
write(callee.object);
|
|
186
|
-
}
|
|
187
|
-
break;
|
|
188
|
-
}
|
|
189
|
-
default:
|
|
190
|
-
break;
|
|
191
|
-
}
|
|
192
|
-
forEachChild(node, visit);
|
|
193
|
-
};
|
|
194
|
-
visit(node);
|
|
142
|
+
}
|
|
195
143
|
return found;
|
|
196
144
|
}
|
|
197
145
|
/** Sources whose value `node` refers into: `todos`, `todos[i].tags`, `todos.find(...)`. */
|
|
@@ -241,8 +189,22 @@ export class Reactive {
|
|
|
241
189
|
add(node.value.name, this.rootSources(node.iterable));
|
|
242
190
|
if (node.key && inMarkup)
|
|
243
191
|
add(node.key.name, this.dependencies(node.iterable));
|
|
192
|
+
// With a key, the block of an item may get another object with the same key.
|
|
193
|
+
if (node.withKey && inMarkup)
|
|
194
|
+
add(node.value.name, this.dependencies(node.iterable));
|
|
244
195
|
break;
|
|
245
196
|
case 'VariableDeclaration':
|
|
197
|
+
// `derived first { return todos[0] }` points into what it returns.
|
|
198
|
+
if (node.body) {
|
|
199
|
+
const name = node.names[0].name;
|
|
200
|
+
add(name, this.dependencies(node.body));
|
|
201
|
+
for (const statement of ownStatements(node.body.body)) {
|
|
202
|
+
if (statement.kind !== 'ReturnStatement')
|
|
203
|
+
continue;
|
|
204
|
+
for (const value of statement.values)
|
|
205
|
+
add(name, this.rootSources(value));
|
|
206
|
+
}
|
|
207
|
+
}
|
|
246
208
|
node.names.forEach((name, i) => {
|
|
247
209
|
const value = node.values.length === node.names.length ? node.values[i] : node.values[0];
|
|
248
210
|
if (!value)
|
package/dist/codegen/runtime.js
CHANGED
|
@@ -59,9 +59,14 @@ export function runtimeCode(names, mode) {
|
|
|
59
59
|
add(owners.get(name));
|
|
60
60
|
// The files only declare, but for a few that register what a thread shares, so their order does
|
|
61
61
|
// not matter: the one of RUNTIME_FILES.
|
|
62
|
-
|
|
62
|
+
const code = [...files]
|
|
63
63
|
.filter(([file]) => needed.has(file))
|
|
64
64
|
.flatMap(([, { code }], i) => [...(i > 0 ? [''] : []), ...code.split('\n')]);
|
|
65
|
+
const list = names.join(', ');
|
|
66
|
+
// A function of its own keeps the names of the runtime that are not `$$`, such as check() of
|
|
67
|
+
// decode, apart from those of the program, which may declare a check() too: only the names that
|
|
68
|
+
// the code uses come out of it.
|
|
69
|
+
return [`const { ${list} } = (() => {`, ...code, '', `return { ${list} };`, '})();'];
|
|
65
70
|
}
|
|
66
71
|
/**
|
|
67
72
|
* The `go` of a module: in a module, a call in a worker that starts with its worker module; in the
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import type * as ast from '../ast.ts';
|
|
2
|
+
import type { Type } from '../checker/types.ts';
|
|
3
|
+
export interface SharedState {
|
|
4
|
+
/** The sets of updates by the name of the state here: its own, and those it imports. */
|
|
5
|
+
readonly listeners: ReadonlyMap<string, string>;
|
|
6
|
+
/** The states of this module, which only it changes. */
|
|
7
|
+
readonly own: ReadonlySet<string>;
|
|
8
|
+
/** Names that refer into the value of a state of this module: `for item in items`. */
|
|
9
|
+
readonly aliases: ReadonlyMap<string, ReadonlySet<string>>;
|
|
10
|
+
}
|
|
11
|
+
/** The set of updates of a state: `$$listeners$items`, or that of its local name for an import. */
|
|
12
|
+
export declare function listenersName(name: string): string;
|
|
13
|
+
/** The states of the program and those it imports, `local → name in its module`. */
|
|
14
|
+
export declare function sharedState(program: ast.Program, imported: ReadonlyMap<string, string> | undefined): SharedState;
|
|
15
|
+
/**
|
|
16
|
+
* The states of this module that a statement changes, through them or through names that refer
|
|
17
|
+
* into them. The analysis goes by names, as that of components: a local variable with the name of
|
|
18
|
+
* a state only causes an extra run of the updates.
|
|
19
|
+
*/
|
|
20
|
+
export declare function sharedWrites(node: ast.Node, shared: SharedState, typeOf: (node: ast.Expression) => Type | undefined): string[];
|
|
21
|
+
/** The states of this module whose value `node` refers into: `items`, `items[0]`, `item.tags`. */
|
|
22
|
+
export declare function rootsOf(node: ast.Expression, shared: SharedState): ReadonlySet<string>;
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import { forEachChild } from "../walk.js";
|
|
2
|
+
import { rootName, writeTargets } from "./mutations.js";
|
|
3
|
+
/** The set of updates of a state: `$$listeners$items`, or that of its local name for an import. */
|
|
4
|
+
export function listenersName(name) {
|
|
5
|
+
return `$$listeners$${name}`;
|
|
6
|
+
}
|
|
7
|
+
/** The states of the program and those it imports, `local → name in its module`. */
|
|
8
|
+
export function sharedState(program, imported) {
|
|
9
|
+
const own = new Set();
|
|
10
|
+
for (const statement of program.body) {
|
|
11
|
+
if (statement.kind !== 'VariableDeclaration' || statement.keyword !== 'state')
|
|
12
|
+
continue;
|
|
13
|
+
for (const name of statement.names)
|
|
14
|
+
if (name.name !== '_')
|
|
15
|
+
own.add(name.name);
|
|
16
|
+
}
|
|
17
|
+
const listeners = new Map();
|
|
18
|
+
for (const name of [...own, ...(imported?.keys() ?? [])])
|
|
19
|
+
listeners.set(name, listenersName(name));
|
|
20
|
+
return { listeners, own, aliases: own.size > 0 ? aliasesOf(program, own) : new Map() };
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* The states of this module that a statement changes, through them or through names that refer
|
|
24
|
+
* into them. The analysis goes by names, as that of components: a local variable with the name of
|
|
25
|
+
* a state only causes an extra run of the updates.
|
|
26
|
+
*/
|
|
27
|
+
export function sharedWrites(node, shared, typeOf) {
|
|
28
|
+
if (shared.own.size === 0)
|
|
29
|
+
return [];
|
|
30
|
+
const found = new Set();
|
|
31
|
+
for (const target of writeTargets(node, typeOf)) {
|
|
32
|
+
for (const name of rootsOf(target, shared))
|
|
33
|
+
found.add(name);
|
|
34
|
+
}
|
|
35
|
+
return [...found];
|
|
36
|
+
}
|
|
37
|
+
/** The states of this module whose value `node` refers into: `items`, `items[0]`, `item.tags`. */
|
|
38
|
+
export function rootsOf(node, shared) {
|
|
39
|
+
const name = rootName(node);
|
|
40
|
+
if (name === null)
|
|
41
|
+
return new Set();
|
|
42
|
+
if (shared.own.has(name))
|
|
43
|
+
return new Set([name]);
|
|
44
|
+
return shared.aliases.get(name) ?? new Set();
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Names that refer into the states anywhere in the module: the items of `for item in items`, a
|
|
48
|
+
* variable given `items[0]`, the parameters of a callback of `items.find(item => ...)`. Repeats
|
|
49
|
+
* until nothing changes, since an alias of an alias is one too.
|
|
50
|
+
*/
|
|
51
|
+
function aliasesOf(program, own) {
|
|
52
|
+
const aliases = new Map();
|
|
53
|
+
const roots = (node) => rootsOf(node, { listeners: new Map(), own, aliases });
|
|
54
|
+
let changed = true;
|
|
55
|
+
const add = (name, states) => {
|
|
56
|
+
if (name === '_' || own.has(name))
|
|
57
|
+
return;
|
|
58
|
+
for (const state of states) {
|
|
59
|
+
const set = aliases.get(name) ?? new Set();
|
|
60
|
+
if (set.has(state))
|
|
61
|
+
continue;
|
|
62
|
+
set.add(state);
|
|
63
|
+
aliases.set(name, set);
|
|
64
|
+
changed = true;
|
|
65
|
+
}
|
|
66
|
+
};
|
|
67
|
+
const visit = (node) => {
|
|
68
|
+
if (node.kind === 'ForInStatement')
|
|
69
|
+
add(node.value.name, roots(node.iterable));
|
|
70
|
+
if (node.kind === 'VariableDeclaration' && node.keyword !== 'state') {
|
|
71
|
+
node.names.forEach((name, i) => {
|
|
72
|
+
const value = node.values.length === node.names.length ? node.values[i] : node.values[0];
|
|
73
|
+
if (value)
|
|
74
|
+
add(name.name, roots(value));
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
if (node.kind === 'CallExpression' && node.callee.kind === 'MemberExpression') {
|
|
78
|
+
const states = roots(node.callee.object);
|
|
79
|
+
for (const arg of node.arguments) {
|
|
80
|
+
if (arg.kind !== 'ArrowFunction' && arg.kind !== 'FuncExpression')
|
|
81
|
+
continue;
|
|
82
|
+
for (const param of arg.params)
|
|
83
|
+
add(param.name.name, states);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
forEachChild(node, visit);
|
|
87
|
+
};
|
|
88
|
+
while (changed) {
|
|
89
|
+
changed = false;
|
|
90
|
+
visit(program);
|
|
91
|
+
}
|
|
92
|
+
return aliases;
|
|
93
|
+
}
|
|
@@ -14,7 +14,7 @@ export declare abstract class SimpleStatementEmitter extends TryEmitter {
|
|
|
14
14
|
protected labels: number;
|
|
15
15
|
/** A `defer` inside an if/loop/switch; implemented by FunctionEmitter. */
|
|
16
16
|
protected abstract deferStatement(node: ast.DeferStatement): void;
|
|
17
|
-
/** `mount
|
|
17
|
+
/** `mount { ... }` of a component (see FunctionEmitter). */
|
|
18
18
|
protected abstract mountStatement(node: ast.MountStatement): void;
|
|
19
19
|
/** `effect { ... }` of a component (see FunctionEmitter). */
|
|
20
20
|
protected abstract effectStatement(node: ast.EffectStatement): void;
|
|
@@ -9,6 +9,11 @@ import { ParallelLoopEmitter } from './parallel.ts';
|
|
|
9
9
|
export declare abstract class StatementEmitter extends ParallelLoopEmitter {
|
|
10
10
|
/** A CSS module: its object and CSS go with the styles (StyleEmitter), not to an import. */
|
|
11
11
|
protected abstract styleModuleImport(node: ast.ImportDeclaration): boolean;
|
|
12
|
+
private sharedReads;
|
|
13
|
+
/** State of the module: the updates of the components that read it are kept beside it. */
|
|
14
|
+
protected emitStatement(node: ast.Statement): void;
|
|
15
|
+
/** Whether the components or decorators of the module read `name`, and so listen to it. */
|
|
16
|
+
protected readsShared(name: string): boolean;
|
|
12
17
|
protected importDeclaration(node: ast.ImportDeclaration): void;
|
|
13
18
|
/**
|
|
14
19
|
* `export { log as $$visible$log }`: the names of the module that the code of exported
|
|
@@ -3,7 +3,9 @@ import { isIterator } from "../iterators.js";
|
|
|
3
3
|
import { decoratedName, moduleNamesOf } from "../decorators.js";
|
|
4
4
|
import { inlinePackages } from "../inline-packages.js";
|
|
5
5
|
import { packageExports } from "./imported.js";
|
|
6
|
+
import { readNames } from "./mutations.js";
|
|
6
7
|
import { ParallelLoopEmitter } from "./parallel.js";
|
|
8
|
+
import { listenersName } from "./shared.js";
|
|
7
9
|
import { ARROW, POSTFIX } from "./syntax.js";
|
|
8
10
|
/**
|
|
9
11
|
* Imports and classes; with the layers below, all statements and declarations. A decorator of
|
|
@@ -12,6 +14,34 @@ import { ARROW, POSTFIX } from "./syntax.js";
|
|
|
12
14
|
* So does an inlined component of another module; a called one brings its function, `$$Card`.
|
|
13
15
|
*/
|
|
14
16
|
export class StatementEmitter extends ParallelLoopEmitter {
|
|
17
|
+
sharedReads = null;
|
|
18
|
+
/** State of the module: the updates of the components that read it are kept beside it. */
|
|
19
|
+
emitStatement(node) {
|
|
20
|
+
super.emitStatement(node);
|
|
21
|
+
if (node.kind !== 'VariableDeclaration' || node.keyword !== 'state')
|
|
22
|
+
return;
|
|
23
|
+
if (!this.program.body.includes(node))
|
|
24
|
+
return;
|
|
25
|
+
const exported = node.exported ? 'export ' : '';
|
|
26
|
+
for (const name of node.names) {
|
|
27
|
+
const listeners = this.shared.listeners.get(name.name);
|
|
28
|
+
if (listeners)
|
|
29
|
+
this.line(`${exported}const ${listeners} = new Set();`);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
/** Whether the components or decorators of the module read `name`, and so listen to it. */
|
|
33
|
+
readsShared(name) {
|
|
34
|
+
if (!this.sharedReads) {
|
|
35
|
+
this.sharedReads = new Set();
|
|
36
|
+
for (const statement of this.program.body) {
|
|
37
|
+
if (statement.kind === 'ComponentDeclaration' ||
|
|
38
|
+
statement.kind === 'DecoratorDeclaration') {
|
|
39
|
+
readNames(statement, this.sharedReads);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
return this.sharedReads.has(name);
|
|
44
|
+
}
|
|
15
45
|
importDeclaration(node) {
|
|
16
46
|
if (this.styleModuleImport(node))
|
|
17
47
|
return;
|
|
@@ -36,6 +66,11 @@ export class StatementEmitter extends ParallelLoopEmitter {
|
|
|
36
66
|
const hidden = (exported, local) => named.push(exported === local ? exported : `${exported} as ${local}`);
|
|
37
67
|
for (const specifier of node.namedImports) {
|
|
38
68
|
const component = this.options.components?.get(specifier.local.name);
|
|
69
|
+
const state = this.options.shared?.get(specifier.local.name);
|
|
70
|
+
// State of that module: the components here that read it listen to its changes.
|
|
71
|
+
if (state !== undefined && this.readsShared(specifier.local.name)) {
|
|
72
|
+
hidden(listenersName(state), this.shared.listeners.get(specifier.local.name));
|
|
73
|
+
}
|
|
39
74
|
const inlined = this.inlinedImports.get(specifier.local.name);
|
|
40
75
|
if (inlined) {
|
|
41
76
|
for (const [exported, local] of inlined.imports)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type * as ast from '../ast.ts';
|
|
2
2
|
import { type Type } from '../checker/types.ts';
|
|
3
|
-
import type
|
|
3
|
+
import { type Reactive, type Source, type Write } from './reactive.ts';
|
|
4
4
|
import { StatementEmitter } from './statements.ts';
|
|
5
5
|
/**
|
|
6
6
|
* Updates after changes of component state. Each state variable gets an update function; every
|
|
@@ -19,8 +19,13 @@ export declare abstract class UpdateEmitter extends StatementEmitter {
|
|
|
19
19
|
* the markup, which would update it again; code that runs later (handlers) is not included.
|
|
20
20
|
*/
|
|
21
21
|
protected rendering: number;
|
|
22
|
-
/**
|
|
22
|
+
/**
|
|
23
|
+
* After a statement that changes state of the module, the updates of the components that read
|
|
24
|
+
* it; code that renders markup changes none, as with the state of a component.
|
|
25
|
+
*/
|
|
23
26
|
protected statement(node: ast.Statement): void;
|
|
27
|
+
/** After a statement that changes state, the update of what depends on it. */
|
|
28
|
+
private statementWithUpdates;
|
|
24
29
|
protected changesState(body: ast.Expression): boolean;
|
|
25
30
|
protected withRendering<T>(rendering: number, emit: () => T): T;
|
|
26
31
|
/** Records a place that changes a source and returns its marker; see resolveMarkers. */
|
|
@@ -45,7 +50,10 @@ export declare abstract class UpdateEmitter extends StatementEmitter {
|
|
|
45
50
|
* update statement, or by nothing when no markup depends on the source.
|
|
46
51
|
*/
|
|
47
52
|
export declare function resolveMarkers(block: string, reactive: Reactive): string;
|
|
48
|
-
/**
|
|
53
|
+
/**
|
|
54
|
+
* The update statements that a place changing the source needs: the computations, the call that
|
|
55
|
+
* gives a bound property to the parent, then the updates of the nodes.
|
|
56
|
+
*/
|
|
49
57
|
export declare function needed(source: Source, write: Write): string[];
|
|
50
58
|
/** One place changes the source, after the markup exists, and needs one update statement. */
|
|
51
59
|
export declare function inlinesUpdate(source: Source): boolean;
|
package/dist/codegen/updates.js
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { widenLiterals } from "../checker/types.js";
|
|
2
|
+
import { updatesOf } from "./reactive.js";
|
|
2
3
|
import { StatementEmitter } from "./statements.js";
|
|
3
4
|
import { startsWithObjectLiteral } from "./analysis.js";
|
|
5
|
+
import { sharedWrites } from "./shared.js";
|
|
4
6
|
import { ARROW } from "./syntax.js";
|
|
5
7
|
/**
|
|
6
8
|
* Updates after changes of component state. Each state variable gets an update function; every
|
|
@@ -19,8 +21,37 @@ export class UpdateEmitter extends StatementEmitter {
|
|
|
19
21
|
* the markup, which would update it again; code that runs later (handlers) is not included.
|
|
20
22
|
*/
|
|
21
23
|
rendering = 0;
|
|
22
|
-
/**
|
|
24
|
+
/**
|
|
25
|
+
* After a statement that changes state of the module, the updates of the components that read
|
|
26
|
+
* it; code that renders markup changes none, as with the state of a component.
|
|
27
|
+
*/
|
|
23
28
|
statement(node) {
|
|
29
|
+
const shared = this.rendering > 0 ? [] : sharedWrites(node, this.shared, (each) => this.typeOf(each));
|
|
30
|
+
if (shared.length === 0) {
|
|
31
|
+
this.statementWithUpdates(node);
|
|
32
|
+
return;
|
|
33
|
+
}
|
|
34
|
+
const notify = () => {
|
|
35
|
+
for (const name of shared)
|
|
36
|
+
this.line(`$$changed(${this.shared.listeners.get(name)});`);
|
|
37
|
+
};
|
|
38
|
+
if (node.kind !== 'ReturnStatement' && node.kind !== 'ThrowStatement') {
|
|
39
|
+
this.statementWithUpdates(node);
|
|
40
|
+
notify();
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
// The value is computed first, then the updates run, then the function leaves.
|
|
44
|
+
const values = node.kind === 'ThrowStatement' ? [node.argument] : node.values;
|
|
45
|
+
const temp = this.temp();
|
|
46
|
+
this.withHoisting(true, () => {
|
|
47
|
+
const rendered = values.map((value) => this.expression(value, ARROW));
|
|
48
|
+
this.line(`const ${temp} = ${rendered.length === 1 ? rendered[0] : `[${rendered.join(', ')}]`};`);
|
|
49
|
+
});
|
|
50
|
+
notify();
|
|
51
|
+
this.line(`${node.kind === 'ThrowStatement' ? 'throw' : 'return'} ${temp};`);
|
|
52
|
+
}
|
|
53
|
+
/** After a statement that changes state, the update of what depends on it. */
|
|
54
|
+
statementWithUpdates(node) {
|
|
24
55
|
const { reactive } = this;
|
|
25
56
|
if (reactive && this.fn === reactive.setup && reactive.declaresRenderFunction(node)) {
|
|
26
57
|
this.withRendering(this.rendering + 1, () => super.statement(node));
|
|
@@ -33,6 +64,9 @@ export class UpdateEmitter extends StatementEmitter {
|
|
|
33
64
|
for (const source of changed) {
|
|
34
65
|
for (const computation of source.computations)
|
|
35
66
|
this.line(computation);
|
|
67
|
+
// The parent of a bound property gets the value the body gave it.
|
|
68
|
+
if (source.bound)
|
|
69
|
+
this.line(source.bound);
|
|
36
70
|
}
|
|
37
71
|
return;
|
|
38
72
|
}
|
|
@@ -137,7 +171,14 @@ export function resolveMarkers(block, reactive) {
|
|
|
137
171
|
const sources = new Map();
|
|
138
172
|
for (const source of reactive.sources.values())
|
|
139
173
|
sources.set(source.id, source);
|
|
140
|
-
|
|
174
|
+
// `$$watch(...)` of state of the module stays if something depends on it.
|
|
175
|
+
const listened = block.replace(/^([ \t]*)\uE004(\d+)\uE004(.*)\n/gm, (line, indent, id, text) => {
|
|
176
|
+
const source = sources.get(Number(id));
|
|
177
|
+
if (!source)
|
|
178
|
+
return line;
|
|
179
|
+
return updatesOf(source).length > 0 ? `${indent}${text}\n` : '';
|
|
180
|
+
});
|
|
181
|
+
return listened.replace(/^([ \t]*)\uE000(\d+)(?::(\d+))?\uE001;\n/gm, (line, indent, id, skip) => {
|
|
141
182
|
const source = sources.get(Number(id));
|
|
142
183
|
if (!source)
|
|
143
184
|
return line;
|
|
@@ -148,9 +189,16 @@ export function resolveMarkers(block, reactive) {
|
|
|
148
189
|
return `${indent}${text}\n`;
|
|
149
190
|
});
|
|
150
191
|
}
|
|
151
|
-
/**
|
|
192
|
+
/**
|
|
193
|
+
* The update statements that a place changing the source needs: the computations, the call that
|
|
194
|
+
* gives a bound property to the parent, then the updates of the nodes.
|
|
195
|
+
*/
|
|
152
196
|
export function needed(source, write) {
|
|
153
|
-
return [
|
|
197
|
+
return [
|
|
198
|
+
...source.computations,
|
|
199
|
+
...(source.bound ? [source.bound] : []),
|
|
200
|
+
...source.dependents.filter((_, i) => i !== write.skip),
|
|
201
|
+
];
|
|
154
202
|
}
|
|
155
203
|
/** One place changes the source, after the markup exists, and needs one update statement. */
|
|
156
204
|
export function inlinesUpdate(source) {
|
|
@@ -6,8 +6,8 @@ import { ComponentEmitter } from './components.ts';
|
|
|
6
6
|
* have their values.
|
|
7
7
|
*
|
|
8
8
|
* The element renders the component into its shadow root when it is connected. When it is
|
|
9
|
-
* disconnected and not put back in the same task, the component is removed:
|
|
10
|
-
*
|
|
9
|
+
* disconnected and not put back in the same task, the component is removed: what its
|
|
10
|
+
* `mount` deferred runs. Moving the element keeps its markup.
|
|
11
11
|
*
|
|
12
12
|
* Every property except `children` is a JS property of the element; its setter updates the markup.
|
|
13
13
|
* Strings, numbers and bools also have attributes: `item-count="3"` sets `itemCount` to 3, and a
|
|
@@ -6,8 +6,8 @@ import { ComponentEmitter } from "./components.js";
|
|
|
6
6
|
* have their values.
|
|
7
7
|
*
|
|
8
8
|
* The element renders the component into its shadow root when it is connected. When it is
|
|
9
|
-
* disconnected and not put back in the same task, the component is removed:
|
|
10
|
-
*
|
|
9
|
+
* disconnected and not put back in the same task, the component is removed: what its
|
|
10
|
+
* `mount` deferred runs. Moving the element keeps its markup.
|
|
11
11
|
*
|
|
12
12
|
* Every property except `children` is a JS property of the element; its setter updates the markup.
|
|
13
13
|
* Strings, numbers and bools also have attributes: `item-count="3"` sets `itemCount` to 3, and a
|
package/dist/compile.js
CHANGED
|
@@ -102,11 +102,12 @@ export function prepareCheck(program, options) {
|
|
|
102
102
|
}
|
|
103
103
|
/**
|
|
104
104
|
* What the module imports from other `.rk` modules for the generator: decorators, whose code is
|
|
105
|
-
* inlined,
|
|
105
|
+
* inlined, components, which are called or inlined, and state, whose changes it listens to.
|
|
106
106
|
*/
|
|
107
107
|
function importedCode(program, importModule) {
|
|
108
108
|
const found = new Map();
|
|
109
109
|
const components = new Map();
|
|
110
|
+
const shared = new Map();
|
|
110
111
|
for (const statement of program.body) {
|
|
111
112
|
if (statement.kind !== 'ImportDeclaration' || !statement.source.value.endsWith('.rk')) {
|
|
112
113
|
continue;
|
|
@@ -116,6 +117,9 @@ function importedCode(program, importModule) {
|
|
|
116
117
|
const decorators = exports?.decorators;
|
|
117
118
|
for (const specifier of statement.namedImports) {
|
|
118
119
|
const component = exports?.components?.get(specifier.imported.name);
|
|
120
|
+
if (exports?.shared?.has(specifier.imported.name)) {
|
|
121
|
+
shared.set(specifier.local.name, specifier.imported.name);
|
|
122
|
+
}
|
|
119
123
|
if (component) {
|
|
120
124
|
components.set(specifier.local.name, {
|
|
121
125
|
node: component.node,
|
|
@@ -135,7 +139,7 @@ function importedCode(program, importModule) {
|
|
|
135
139
|
});
|
|
136
140
|
}
|
|
137
141
|
}
|
|
138
|
-
return { decorators: found, components };
|
|
142
|
+
return { decorators: found, components, shared };
|
|
139
143
|
}
|
|
140
144
|
/** The CSS modules that the program imports, read and parsed, or what is wrong with them. */
|
|
141
145
|
function readStyleModules(program, read) {
|
package/dist/decorator-copy.d.ts
CHANGED
|
@@ -5,7 +5,7 @@ export type CopyListener = (copy: ast.Node, original: ast.Node) => void;
|
|
|
5
5
|
export declare function originOf(node: ast.Node): ast.Node;
|
|
6
6
|
/**
|
|
7
7
|
* Renames of the names that are not values: tags of components, `<Icon />`, and decorators,
|
|
8
|
-
*
|
|
8
|
+
* `@visible.shown`. Code of another module refers to those of its own module.
|
|
9
9
|
*/
|
|
10
10
|
export interface OtherNames {
|
|
11
11
|
tags?: ReadonlyMap<string, string>;
|
package/dist/decorator-copy.js
CHANGED
|
@@ -143,6 +143,7 @@ export function renamer(renames, copied, other = {}) {
|
|
|
143
143
|
return done({
|
|
144
144
|
...node,
|
|
145
145
|
iterable: copy(node.iterable, hidden),
|
|
146
|
+
...(node.withKey ? { withKey: copy(node.withKey, scoped(names, hidden)) } : {}),
|
|
146
147
|
body: copy(node.body, scoped(names, hidden)),
|
|
147
148
|
}, node);
|
|
148
149
|
}
|