rooklang 0.0.0-stage → 0.3.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/LICENSE +21 -0
- package/README.md +177 -2
- package/README.ru.md +178 -0
- package/dist/ast/declarations.d.ts +185 -0
- package/dist/ast/declarations.js +1 -0
- package/dist/ast/expressions.d.ts +201 -0
- package/dist/ast/expressions.js +1 -0
- package/dist/ast/markup.d.ts +141 -0
- package/dist/ast/markup.js +1 -0
- package/dist/ast/statements.d.ts +173 -0
- package/dist/ast/statements.js +1 -0
- package/dist/ast.d.ts +18 -0
- package/dist/ast.js +18 -0
- package/dist/attrs.d.ts +8 -0
- package/dist/attrs.js +23 -0
- package/dist/browser.d.ts +30 -0
- package/dist/browser.js +36 -0
- package/dist/build.d.ts +48 -0
- package/dist/build.js +112 -0
- package/dist/checker/assignments.d.ts +14 -0
- package/dist/checker/assignments.js +197 -0
- package/dist/checker/base.d.ts +124 -0
- package/dist/checker/base.js +180 -0
- package/dist/checker/bigint.d.ts +15 -0
- package/dist/checker/bigint.js +85 -0
- package/dist/checker/builtins.d.ts +9 -0
- package/dist/checker/builtins.js +307 -0
- package/dist/checker/bytes.d.ts +11 -0
- package/dist/checker/bytes.js +72 -0
- package/dist/checker/calls.d.ts +23 -0
- package/dist/checker/calls.js +210 -0
- package/dist/checker/channel.d.ts +14 -0
- package/dist/checker/channel.js +44 -0
- package/dist/checker/channels.d.ts +9 -0
- package/dist/checker/channels.js +95 -0
- package/dist/checker/checker.d.ts +4 -0
- package/dist/checker/checker.js +22 -0
- package/dist/checker/classes.d.ts +24 -0
- package/dist/checker/classes.js +223 -0
- package/dist/checker/completion-names.d.ts +11 -0
- package/dist/checker/completion-names.js +100 -0
- package/dist/checker/completion.d.ts +25 -0
- package/dist/checker/completion.js +222 -0
- package/dist/checker/component-values.d.ts +11 -0
- package/dist/checker/component-values.js +88 -0
- package/dist/checker/components.d.ts +37 -0
- package/dist/checker/components.js +287 -0
- package/dist/checker/context.d.ts +196 -0
- package/dist/checker/context.js +35 -0
- package/dist/checker/control-flow.d.ts +24 -0
- package/dist/checker/control-flow.js +237 -0
- package/dist/checker/declarations.d.ts +26 -0
- package/dist/checker/declarations.js +259 -0
- package/dist/checker/decorator-exports.d.ts +5 -0
- package/dist/checker/decorator-exports.js +40 -0
- package/dist/checker/decorator-members.d.ts +28 -0
- package/dist/checker/decorator-members.js +140 -0
- package/dist/checker/decorator-wrappers.d.ts +20 -0
- package/dist/checker/decorator-wrappers.js +132 -0
- package/dist/checker/decorators.d.ts +28 -0
- package/dist/checker/decorators.js +265 -0
- package/dist/checker/dom-svg.d.ts +7 -0
- package/dist/checker/dom-svg.js +19 -0
- package/dist/checker/dom.d.ts +22 -0
- package/dist/checker/dom.js +361 -0
- package/dist/checker/expressions.d.ts +32 -0
- package/dist/checker/expressions.js +244 -0
- package/dist/checker/flow.d.ts +18 -0
- package/dist/checker/flow.js +134 -0
- package/dist/checker/forwarding.d.ts +15 -0
- package/dist/checker/forwarding.js +78 -0
- package/dist/checker/functions.d.ts +24 -0
- package/dist/checker/functions.js +154 -0
- package/dist/checker/go-for.d.ts +12 -0
- package/dist/checker/go-for.js +93 -0
- package/dist/checker/goroutines.d.ts +19 -0
- package/dist/checker/goroutines.js +110 -0
- package/dist/checker/helpers.d.ts +35 -0
- package/dist/checker/helpers.js +162 -0
- package/dist/checker/imports.d.ts +7 -0
- package/dist/checker/imports.js +83 -0
- package/dist/checker/initializers.d.ts +22 -0
- package/dist/checker/initializers.js +104 -0
- package/dist/checker/iterables.d.ts +5 -0
- package/dist/checker/iterables.js +17 -0
- package/dist/checker/iterators.d.ts +13 -0
- package/dist/checker/iterators.js +95 -0
- package/dist/checker/loops.d.ts +10 -0
- package/dist/checker/loops.js +82 -0
- package/dist/checker/markup.d.ts +28 -0
- package/dist/checker/markup.js +220 -0
- package/dist/checker/members.d.ts +29 -0
- package/dist/checker/members.js +233 -0
- package/dist/checker/operators.d.ts +13 -0
- package/dist/checker/operators.js +177 -0
- package/dist/checker/promise.d.ts +17 -0
- package/dist/checker/promise.js +49 -0
- package/dist/checker/refs.d.ts +26 -0
- package/dist/checker/refs.js +136 -0
- package/dist/checker/resolution.d.ts +23 -0
- package/dist/checker/resolution.js +173 -0
- package/dist/checker/statements.d.ts +28 -0
- package/dist/checker/statements.js +212 -0
- package/dist/checker/styles.d.ts +14 -0
- package/dist/checker/styles.js +49 -0
- package/dist/checker/transfer.d.ts +5 -0
- package/dist/checker/transfer.js +83 -0
- package/dist/checker/types/bytes.d.ts +4 -0
- package/dist/checker/types/bytes.js +51 -0
- package/dist/checker/types/classes.d.ts +22 -0
- package/dist/checker/types/classes.js +93 -0
- package/dist/checker/types/constructors.d.ts +27 -0
- package/dist/checker/types/constructors.js +129 -0
- package/dist/checker/types/generics.d.ts +20 -0
- package/dist/checker/types/generics.js +75 -0
- package/dist/checker/types/lazy.d.ts +30 -0
- package/dist/checker/types/lazy.js +92 -0
- package/dist/checker/types/model.d.ts +149 -0
- package/dist/checker/types/model.js +15 -0
- package/dist/checker/types/params.d.ts +7 -0
- package/dist/checker/types/params.js +220 -0
- package/dist/checker/types/printing.d.ts +6 -0
- package/dist/checker/types/printing.js +109 -0
- package/dist/checker/types/relations.d.ts +16 -0
- package/dist/checker/types/relations.js +294 -0
- package/dist/checker/types.d.ts +9 -0
- package/dist/checker/types.js +10 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +193 -0
- package/dist/codegen/analysis.d.ts +18 -0
- package/dist/codegen/analysis.js +144 -0
- package/dist/codegen/attributes.d.ts +40 -0
- package/dist/codegen/attributes.js +227 -0
- package/dist/codegen/calls.d.ts +22 -0
- package/dist/codegen/calls.js +106 -0
- package/dist/codegen/chan-helpers.d.ts +2 -0
- package/dist/codegen/chan-helpers.js +253 -0
- package/dist/codegen/component-helpers.d.ts +33 -0
- package/dist/codegen/component-helpers.js +18 -0
- package/dist/codegen/component-props.d.ts +42 -0
- package/dist/codegen/component-props.js +173 -0
- package/dist/codegen/component-values.d.ts +27 -0
- package/dist/codegen/component-values.js +146 -0
- package/dist/codegen/components.d.ts +53 -0
- package/dist/codegen/components.js +268 -0
- package/dist/codegen/control-flow.d.ts +25 -0
- package/dist/codegen/control-flow.js +152 -0
- package/dist/codegen/control-statements.d.ts +28 -0
- package/dist/codegen/control-statements.js +226 -0
- package/dist/codegen/creation.d.ts +31 -0
- package/dist/codegen/creation.js +113 -0
- package/dist/codegen/early-returns.d.ts +59 -0
- package/dist/codegen/early-returns.js +244 -0
- package/dist/codegen/element-helpers.d.ts +12 -0
- package/dist/codegen/element-helpers.js +54 -0
- package/dist/codegen/elements.d.ts +71 -0
- package/dist/codegen/elements.js +293 -0
- package/dist/codegen/emitter.d.ts +135 -0
- package/dist/codegen/emitter.js +296 -0
- package/dist/codegen/expressions.d.ts +49 -0
- package/dist/codegen/expressions.js +262 -0
- package/dist/codegen/functions.d.ts +28 -0
- package/dist/codegen/functions.js +170 -0
- package/dist/codegen/go-helpers.d.ts +9 -0
- package/dist/codegen/go-helpers.js +307 -0
- package/dist/codegen/helpers.d.ts +10 -0
- package/dist/codegen/helpers.js +356 -0
- package/dist/codegen/imported.d.ts +38 -0
- package/dist/codegen/imported.js +71 -0
- package/dist/codegen/js.d.ts +20 -0
- package/dist/codegen/js.js +113 -0
- package/dist/codegen/mutations.d.ts +10 -0
- package/dist/codegen/mutations.js +102 -0
- package/dist/codegen/parallel.d.ts +7 -0
- package/dist/codegen/parallel.js +63 -0
- package/dist/codegen/reactive.d.ts +95 -0
- package/dist/codegen/reactive.js +280 -0
- package/dist/codegen/refs.d.ts +62 -0
- package/dist/codegen/refs.js +160 -0
- package/dist/codegen/select-helpers.d.ts +2 -0
- package/dist/codegen/select-helpers.js +135 -0
- package/dist/codegen/simple-statements.d.ts +42 -0
- package/dist/codegen/simple-statements.js +253 -0
- package/dist/codegen/statements.d.ts +22 -0
- package/dist/codegen/statements.js +142 -0
- package/dist/codegen/styles.d.ts +54 -0
- package/dist/codegen/styles.js +157 -0
- package/dist/codegen/syntax.d.ts +18 -0
- package/dist/codegen/syntax.js +70 -0
- package/dist/codegen/thread-helpers.d.ts +2 -0
- package/dist/codegen/thread-helpers.js +198 -0
- package/dist/codegen/updates.d.ts +51 -0
- package/dist/codegen/updates.js +201 -0
- package/dist/codegen/web-components.d.ts +20 -0
- package/dist/codegen/web-components.js +149 -0
- package/dist/compile-file.d.ts +11 -0
- package/dist/compile-file.js +24 -0
- package/dist/compile.d.ts +53 -0
- package/dist/compile.js +187 -0
- package/dist/completion.d.ts +10 -0
- package/dist/completion.js +134 -0
- package/dist/config.d.ts +2 -0
- package/dist/config.js +44 -0
- package/dist/css/hash.d.ts +1 -0
- package/dist/css/hash.js +10 -0
- package/dist/css/parse.d.ts +42 -0
- package/dist/css/parse.js +138 -0
- package/dist/css/print.d.ts +9 -0
- package/dist/css/print.js +35 -0
- package/dist/css/selectors.d.ts +29 -0
- package/dist/css/selectors.js +168 -0
- package/dist/declarations.d.ts +3 -0
- package/dist/declarations.js +207 -0
- package/dist/decorator-copy.d.ts +18 -0
- package/dist/decorator-copy.js +158 -0
- package/dist/decorators.d.ts +61 -0
- package/dist/decorators.js +241 -0
- package/dist/diagnostics.d.ts +17 -0
- package/dist/diagnostics.js +22 -0
- package/dist/go-for.d.ts +26 -0
- package/dist/go-for.js +155 -0
- package/dist/goroutines.d.ts +37 -0
- package/dist/goroutines.js +253 -0
- package/dist/html-tag.d.ts +21 -0
- package/dist/html-tag.js +103 -0
- package/dist/index.d.ts +33 -0
- package/dist/index.js +55 -0
- package/dist/inline-packages.d.ts +32 -0
- package/dist/inline-packages.js +129 -0
- package/dist/iterators.d.ts +10 -0
- package/dist/iterators.js +33 -0
- package/dist/lexer/base.d.ts +24 -0
- package/dist/lexer/base.js +87 -0
- package/dist/lexer/lexer.d.ts +8 -0
- package/dist/lexer/lexer.js +134 -0
- package/dist/lexer/literals.d.ts +18 -0
- package/dist/lexer/literals.js +203 -0
- package/dist/lexer/markup.d.ts +10 -0
- package/dist/lexer/markup.js +93 -0
- package/dist/lexer/style.d.ts +17 -0
- package/dist/lexer/style.js +97 -0
- package/dist/lexer/tables.d.ts +53 -0
- package/dist/lexer/tables.js +101 -0
- package/dist/lexer/token.d.ts +42 -0
- package/dist/lexer/token.js +108 -0
- package/dist/loader.d.ts +1 -0
- package/dist/loader.js +50 -0
- package/dist/methods.d.ts +5 -0
- package/dist/methods.js +81 -0
- package/dist/modules.d.ts +3 -0
- package/dist/modules.js +49 -0
- package/dist/names.d.ts +23 -0
- package/dist/names.js +267 -0
- package/dist/parser/base.d.ts +96 -0
- package/dist/parser/base.js +202 -0
- package/dist/parser/classes.d.ts +8 -0
- package/dist/parser/classes.js +164 -0
- package/dist/parser/control-flow.d.ts +29 -0
- package/dist/parser/control-flow.js +232 -0
- package/dist/parser/declarations.d.ts +27 -0
- package/dist/parser/declarations.js +238 -0
- package/dist/parser/decorators.d.ts +30 -0
- package/dist/parser/decorators.js +176 -0
- package/dist/parser/expressions.d.ts +15 -0
- package/dist/parser/expressions.js +255 -0
- package/dist/parser/literals.d.ts +27 -0
- package/dist/parser/literals.js +206 -0
- package/dist/parser/markup.d.ts +22 -0
- package/dist/parser/markup.js +193 -0
- package/dist/parser/parser.d.ts +14 -0
- package/dist/parser/parser.js +22 -0
- package/dist/parser/simple-statements.d.ts +22 -0
- package/dist/parser/simple-statements.js +156 -0
- package/dist/parser/statements.d.ts +37 -0
- package/dist/parser/statements.js +267 -0
- package/dist/parser/syntax.d.ts +51 -0
- package/dist/parser/syntax.js +173 -0
- package/dist/parser/types.d.ts +23 -0
- package/dist/parser/types.js +218 -0
- package/dist/recursion.d.ts +25 -0
- package/dist/recursion.js +170 -0
- package/dist/refs.d.ts +49 -0
- package/dist/refs.js +210 -0
- package/dist/run.d.ts +1 -0
- package/dist/run.js +21 -0
- package/dist/source.d.ts +15 -0
- package/dist/source.js +34 -0
- package/dist/style-modules.d.ts +17 -0
- package/dist/style-modules.js +49 -0
- package/dist/styles.d.ts +36 -0
- package/dist/styles.js +160 -0
- package/dist/svg.d.ts +8 -0
- package/dist/svg.js +77 -0
- package/dist/ts-types.d.ts +11 -0
- package/dist/ts-types.js +133 -0
- package/dist/typed-arrays.d.ts +8 -0
- package/dist/typed-arrays.js +43 -0
- package/dist/typescript/convert-base.d.ts +42 -0
- package/dist/typescript/convert-base.js +135 -0
- package/dist/typescript/convert-objects.d.ts +28 -0
- package/dist/typescript/convert-objects.js +245 -0
- package/dist/typescript/convert.d.ts +14 -0
- package/dist/typescript/convert.js +67 -0
- package/dist/typescript/importer.d.ts +37 -0
- package/dist/typescript/importer.js +152 -0
- package/dist/typescript/library.d.ts +23 -0
- package/dist/typescript/library.js +81 -0
- package/dist/vite.d.ts +11 -0
- package/dist/vite.js +86 -0
- package/dist/walk-types.d.ts +4 -0
- package/dist/walk-types.js +56 -0
- package/dist/walk.d.ts +5 -0
- package/dist/walk.js +315 -0
- package/package.json +104 -4
|
@@ -0,0 +1,226 @@
|
|
|
1
|
+
import { isChan } from "../checker/channel.js";
|
|
2
|
+
import { isMap } from "../checker/iterables.js";
|
|
3
|
+
import { containsBreak } from "../walk.js";
|
|
4
|
+
import { declarationsOf, endsWithJump } from "./analysis.js";
|
|
5
|
+
import { SimpleStatementEmitter } from "./simple-statements.js";
|
|
6
|
+
import { ARROW, BINARY, POSTFIX } from "./syntax.js";
|
|
7
|
+
/** if, for, switch and try. */
|
|
8
|
+
export class ControlStatementEmitter extends SimpleStatementEmitter {
|
|
9
|
+
ifStatement(node) {
|
|
10
|
+
let text = `if (${this.expression(node.condition, 0)}) ${this.blockStatement(node.consequent)}`;
|
|
11
|
+
if (node.alternate?.kind === 'IfStatement') {
|
|
12
|
+
// The condition of `else if` is evaluated only when the first one is false.
|
|
13
|
+
const alternate = node.alternate;
|
|
14
|
+
text += ` else ${this.withHoisting(false, () => this.ifStatement(alternate))}`;
|
|
15
|
+
}
|
|
16
|
+
else if (node.alternate) {
|
|
17
|
+
text += ` else ${this.blockStatement(node.alternate)}`;
|
|
18
|
+
}
|
|
19
|
+
return text;
|
|
20
|
+
}
|
|
21
|
+
loopBody(body) {
|
|
22
|
+
return this.withBreakTarget(null, () => this.blockStatement(body));
|
|
23
|
+
}
|
|
24
|
+
forStatement(node) {
|
|
25
|
+
const bindings = node.init?.kind === 'VariableDeclaration'
|
|
26
|
+
? node.init.names.map((name) => [name.name, 'let'])
|
|
27
|
+
: [];
|
|
28
|
+
this.withScope(bindings, () => {
|
|
29
|
+
const init = node.init ? this.simpleStatement(node.init) : '';
|
|
30
|
+
// The condition and the update run on every iteration.
|
|
31
|
+
const [condition, update] = this.withHoisting(false, () => [
|
|
32
|
+
node.condition ? this.expression(node.condition, 0) : '',
|
|
33
|
+
node.update ? this.simpleStatement(node.update) : '',
|
|
34
|
+
]);
|
|
35
|
+
if (node.init === null && node.update === null) {
|
|
36
|
+
this.line(`while (${condition || 'true'}) ${this.loopBody(node.body)}`);
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
39
|
+
this.line(`for (${init}; ${condition}; ${update}) ${this.loopBody(node.body)}`);
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
/** A statement inside a `for` header, without the `;`. */
|
|
43
|
+
simpleStatement(node) {
|
|
44
|
+
switch (node.kind) {
|
|
45
|
+
case 'VariableDeclaration':
|
|
46
|
+
return this.variableDeclaration(node);
|
|
47
|
+
case 'AssignmentStatement':
|
|
48
|
+
return this.assignment(node);
|
|
49
|
+
case 'IncDecStatement':
|
|
50
|
+
return `${this.expression(node.target, POSTFIX)}${node.operator}`;
|
|
51
|
+
case 'DeleteStatement':
|
|
52
|
+
return `delete ${this.expression(node.target, POSTFIX)}`;
|
|
53
|
+
case 'ExpressionStatement':
|
|
54
|
+
return this.expression(node.expression, 0);
|
|
55
|
+
case 'SendStatement':
|
|
56
|
+
this.helpers.add('chan');
|
|
57
|
+
return `await $$send(${this.expression(node.channel, ARROW)}, ${this.expression(node.value, ARROW)})`;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* `for x in xs` → `for (const x of xs)`, `m.values()` for a Map, `seq()` for an iterator;
|
|
62
|
+
* `for i, x in xs` iterates `xs.entries()`, which gives the keys and values of a Map too.
|
|
63
|
+
*/
|
|
64
|
+
forInStatement(node) {
|
|
65
|
+
const bindings = [[node.value.name, 'loop']];
|
|
66
|
+
if (node.key)
|
|
67
|
+
bindings.push([node.key.name, 'loop']);
|
|
68
|
+
this.withScope(bindings, () => {
|
|
69
|
+
const value = this.name(node.value.name);
|
|
70
|
+
const iterable = this.options.types?.get(node.iterable);
|
|
71
|
+
let head;
|
|
72
|
+
// A channel gives its values as they come, until it is closed.
|
|
73
|
+
if (iterable && isChan(iterable)) {
|
|
74
|
+
this.helpers.add('chan');
|
|
75
|
+
this.line(`for await (const ${value} of $$range(${this.expression(node.iterable, ARROW)})) ${this.loopBody(node.body)}`);
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
78
|
+
// An iterator is a generator function: called, it gives a lazy JS iterator, of pairs if its
|
|
79
|
+
// yield takes two values.
|
|
80
|
+
if (iterable?.kind === 'function' && iterable.iterator) {
|
|
81
|
+
const yieldType = iterable.params[0];
|
|
82
|
+
const pairs = yieldType?.kind === 'function' && yieldType.params.length === 2;
|
|
83
|
+
const key = node.key && node.key.name !== '_' ? this.name(node.key.name) : '';
|
|
84
|
+
const values = `${this.expression(node.iterable, POSTFIX)}()`;
|
|
85
|
+
head = pairs ? `const [${key}, ${value}] of ${values}` : `const ${value} of ${values}`;
|
|
86
|
+
}
|
|
87
|
+
else if (node.key === null) {
|
|
88
|
+
// A Map gives its values; an untyped value may be a Map, which only the runtime tells.
|
|
89
|
+
// Code that was not checked is taken as it is written.
|
|
90
|
+
const untyped = iterable?.kind === 'any' || iterable?.kind === 'unknown';
|
|
91
|
+
let values = this.expression(node.iterable, untyped ? ARROW : POSTFIX);
|
|
92
|
+
if (iterable && isMap(iterable))
|
|
93
|
+
values += '.values()';
|
|
94
|
+
else if (untyped) {
|
|
95
|
+
this.helpers.add('values');
|
|
96
|
+
values = `$$values(${values})`;
|
|
97
|
+
}
|
|
98
|
+
head = `const ${value} of ${values}`;
|
|
99
|
+
}
|
|
100
|
+
else {
|
|
101
|
+
const key = node.key.name === '_' ? '' : this.name(node.key.name);
|
|
102
|
+
const pair = node.value.name === '_' ? `[${key}]` : `[${key}, ${value}]`;
|
|
103
|
+
head = `const ${pair} of ${this.expression(node.iterable, POSTFIX)}.entries()`;
|
|
104
|
+
}
|
|
105
|
+
this.line(`for (${head}) ${this.loopBody(node.body)}`);
|
|
106
|
+
});
|
|
107
|
+
}
|
|
108
|
+
/** Each `case` ends with an implicit `break`; a case with declarations gets its own block. */
|
|
109
|
+
switchStatement(node, discriminant) {
|
|
110
|
+
const head = `switch (${this.expression(discriminant, 0)})`;
|
|
111
|
+
const body = this.withBreakTarget(null, () => this.block(() => {
|
|
112
|
+
node.cases.forEach((switchCase, i) => {
|
|
113
|
+
// Case values are evaluated only until one matches.
|
|
114
|
+
const labels = switchCase.tests.length === 0
|
|
115
|
+
? ['default:']
|
|
116
|
+
: this.withHoisting(false, () => switchCase.tests.map((test) => `case ${this.expression(test, 0)}:`));
|
|
117
|
+
for (const label of labels.slice(0, -1))
|
|
118
|
+
this.line(label);
|
|
119
|
+
const isLast = i === node.cases.length - 1;
|
|
120
|
+
const needsBreak = !isLast && !endsWithJump(switchCase.body);
|
|
121
|
+
const declarations = declarationsOf(switchCase.body);
|
|
122
|
+
const lines = this.capture(() => {
|
|
123
|
+
this.withScope(declarations, () => this.statements(switchCase.body));
|
|
124
|
+
if (needsBreak)
|
|
125
|
+
this.line('break;');
|
|
126
|
+
});
|
|
127
|
+
if (declarations.length > 0) {
|
|
128
|
+
this.line(`${labels.at(-1)} {\n${lines.join('\n')}\n${this.indent()}}`);
|
|
129
|
+
}
|
|
130
|
+
else {
|
|
131
|
+
this.line(labels.at(-1));
|
|
132
|
+
this.lines.push(...lines);
|
|
133
|
+
}
|
|
134
|
+
});
|
|
135
|
+
}));
|
|
136
|
+
this.line(`${head} ${body}`);
|
|
137
|
+
}
|
|
138
|
+
/**
|
|
139
|
+
* `select`: $$select gives the index of the case that went on and what it received, and a
|
|
140
|
+
* switch runs the case; a `break` inside it leaves the select, as in Go. `default` is -1.
|
|
141
|
+
*/
|
|
142
|
+
selectStatement(node) {
|
|
143
|
+
this.helpers.add('chan');
|
|
144
|
+
const operations = node.cases.flatMap(({ operation }) => {
|
|
145
|
+
if (operation === null)
|
|
146
|
+
return [];
|
|
147
|
+
if (operation.kind === 'UnaryExpression') {
|
|
148
|
+
return [`{ channel: ${this.expression(operation.argument, ARROW)} }`];
|
|
149
|
+
}
|
|
150
|
+
const channel = this.expression(operation.channel, ARROW);
|
|
151
|
+
return [`{ channel: ${channel}, value: ${this.expression(operation.value, ARROW)} }`];
|
|
152
|
+
});
|
|
153
|
+
const hasDefault = node.cases.some((selectCase) => selectCase.operation === null);
|
|
154
|
+
let index = 0;
|
|
155
|
+
const select = this.block(() => {
|
|
156
|
+
this.line(`const [$$case, $$received, $$ok] = await $$select([${operations.join(', ')}], ${hasDefault});`);
|
|
157
|
+
const body = this.withBreakTarget(null, () => this.block(() => {
|
|
158
|
+
for (const { names, operation, body: statements } of node.cases) {
|
|
159
|
+
const label = operation === null ? 'default:' : `case ${index++}:`;
|
|
160
|
+
const variables = names.filter((name) => name.name !== '_');
|
|
161
|
+
const bindings = [
|
|
162
|
+
...variables.map((name) => [name.name, 'const']),
|
|
163
|
+
...declarationsOf(statements),
|
|
164
|
+
];
|
|
165
|
+
const lines = this.capture(() => {
|
|
166
|
+
this.withScope(bindings, () => {
|
|
167
|
+
names.forEach((name, i) => {
|
|
168
|
+
if (name.name === '_')
|
|
169
|
+
return;
|
|
170
|
+
this.line(`const ${this.name(name.name)} = ${i === 0 ? '$$received' : '$$ok'};`);
|
|
171
|
+
});
|
|
172
|
+
this.statements(statements);
|
|
173
|
+
if (!endsWithJump(statements))
|
|
174
|
+
this.line('break;');
|
|
175
|
+
});
|
|
176
|
+
});
|
|
177
|
+
this.line(`${label} {\n${lines.join('\n')}\n${this.indent()}}`);
|
|
178
|
+
}
|
|
179
|
+
}));
|
|
180
|
+
this.line(`switch ($$case) ${body}`);
|
|
181
|
+
});
|
|
182
|
+
this.line(select);
|
|
183
|
+
}
|
|
184
|
+
/**
|
|
185
|
+
* `switch { case cond: ... }` becomes an if/else chain. A `break` inside it must leave the
|
|
186
|
+
* switch, not an enclosing loop, so then the chain gets a label to break to.
|
|
187
|
+
*/
|
|
188
|
+
taglessSwitch(node) {
|
|
189
|
+
const cases = node.cases.filter((switchCase) => switchCase.tests.length > 0);
|
|
190
|
+
const fallback = node.cases.find((switchCase) => switchCase.tests.length === 0);
|
|
191
|
+
const label = node.cases.some((switchCase) => containsBreak(switchCase.body))
|
|
192
|
+
? `$$switch${++this.labels}`
|
|
193
|
+
: null;
|
|
194
|
+
const text = this.withBreakTarget(label, () => {
|
|
195
|
+
const caseBlock = (body) => this.block(() => this.blockStatements(body));
|
|
196
|
+
const branches = cases.map((switchCase) => {
|
|
197
|
+
// Later conditions are evaluated only if the earlier ones are false.
|
|
198
|
+
const condition = this.withHoisting(false, () => switchCase.tests.length === 1
|
|
199
|
+
? this.expression(switchCase.tests[0], 0)
|
|
200
|
+
: switchCase.tests.map((test) => this.expression(test, BINARY['||'] + 1)).join(' || '));
|
|
201
|
+
return `if (${condition}) ${caseBlock(switchCase.body)}`;
|
|
202
|
+
});
|
|
203
|
+
if (fallback)
|
|
204
|
+
branches.push(caseBlock(fallback.body));
|
|
205
|
+
return branches.join(' else ');
|
|
206
|
+
});
|
|
207
|
+
if (text === '')
|
|
208
|
+
return;
|
|
209
|
+
this.line(label ? `${label}: ${text}` : text);
|
|
210
|
+
}
|
|
211
|
+
tryStatement(node) {
|
|
212
|
+
let text = `try ${this.blockStatement(node.block)}`;
|
|
213
|
+
const { handler } = node;
|
|
214
|
+
if (handler) {
|
|
215
|
+
const param = handler.param;
|
|
216
|
+
const bindings = param ? [[param.name, 'catch']] : [];
|
|
217
|
+
text += this.withScope(bindings, () => {
|
|
218
|
+
const head = param ? ` catch (${this.name(param.name)})` : ' catch';
|
|
219
|
+
return `${head} ${this.blockStatement(handler.body)}`;
|
|
220
|
+
});
|
|
221
|
+
}
|
|
222
|
+
if (node.finalizer)
|
|
223
|
+
text += ` finally ${this.blockStatement(node.finalizer)}`;
|
|
224
|
+
return text;
|
|
225
|
+
}
|
|
226
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import type * as ast from '../ast.ts';
|
|
2
|
+
import { AttributeEmitter } from './attributes.ts';
|
|
3
|
+
import type { Live } from './reactive.ts';
|
|
4
|
+
/**
|
|
5
|
+
* The creation of elements. In a module that creates many of them (see ELEMENT_HELPER_LIMIT in
|
|
6
|
+
* js.ts) an element is created by a helper together with the attributes it has from the start:
|
|
7
|
+
* `$$element("a", { href: "/", className: "link" }, { "aria-label": label })` takes the place of
|
|
8
|
+
* a `createElement` and a statement for each attribute. The first object holds properties, the
|
|
9
|
+
* second attributes set with setAttribute; an SVG element has only attributes.
|
|
10
|
+
*/
|
|
11
|
+
/** Attributes given to the helper that creates an element, which need no statements of their own. */
|
|
12
|
+
export interface Creation {
|
|
13
|
+
covered: Set<ast.JsxAttribute | ast.JsxSpreadAttribute>;
|
|
14
|
+
/** Whether the class of the style is given too. */
|
|
15
|
+
scoped: boolean;
|
|
16
|
+
}
|
|
17
|
+
export declare abstract class CreationEmitter extends AttributeEmitter {
|
|
18
|
+
/** The elements the code creates, counted where it creates them, not as often as it runs. */
|
|
19
|
+
createdElements: number;
|
|
20
|
+
/**
|
|
21
|
+
* Writes `declaration = <a new element>`. With the helper it is given the attributes before the
|
|
22
|
+
* first spread, which may override them, except those that need more than a value: handlers,
|
|
23
|
+
* `bind:`, styles, properties of web components and attributes that may be null. `scopeClass`
|
|
24
|
+
* is the class of the style for an element without a class attribute.
|
|
25
|
+
*/
|
|
26
|
+
protected createWith(declaration: string, element: string, tag: string, svg: boolean, attributes: readonly (ast.JsxAttribute | ast.JsxSpreadAttribute)[], live: Live | null, scope: string | null, scopeClass: string | null): Creation;
|
|
27
|
+
/** What `attribute()` would set, as an entry of the objects of the helper, if it is a value. */
|
|
28
|
+
private entry;
|
|
29
|
+
/** An entry whose value may depend on state: then it is also set again on updates. */
|
|
30
|
+
private valueEntry;
|
|
31
|
+
}
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
import { domProperty } from "../checker/dom.js";
|
|
2
|
+
import { AttributeEmitter } from "./attributes.js";
|
|
3
|
+
import { ARROW } from "./syntax.js";
|
|
4
|
+
export class CreationEmitter extends AttributeEmitter {
|
|
5
|
+
/** The elements the code creates, counted where it creates them, not as often as it runs. */
|
|
6
|
+
createdElements = 0;
|
|
7
|
+
/**
|
|
8
|
+
* Writes `declaration = <a new element>`. With the helper it is given the attributes before the
|
|
9
|
+
* first spread, which may override them, except those that need more than a value: handlers,
|
|
10
|
+
* `bind:`, styles, properties of web components and attributes that may be null. `scopeClass`
|
|
11
|
+
* is the class of the style for an element without a class attribute.
|
|
12
|
+
*/
|
|
13
|
+
createWith(declaration, element, tag, svg, attributes, live, scope, scopeClass) {
|
|
14
|
+
const covered = new Set();
|
|
15
|
+
this.createdElements++;
|
|
16
|
+
if (!this.options.elementHelper) {
|
|
17
|
+
this.line(`${declaration} = ${this.createElement(tag, svg)};`);
|
|
18
|
+
return { covered, scoped: false };
|
|
19
|
+
}
|
|
20
|
+
const entries = [];
|
|
21
|
+
for (const attribute of attributes) {
|
|
22
|
+
if (attribute.kind === 'JsxSpreadAttribute')
|
|
23
|
+
break;
|
|
24
|
+
const entry = this.entry(element, tag, attribute, live, svg, scope);
|
|
25
|
+
if (entry) {
|
|
26
|
+
entries.push(entry);
|
|
27
|
+
covered.add(attribute);
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
if (scopeClass !== null) {
|
|
31
|
+
entries.push({
|
|
32
|
+
attribute: svg,
|
|
33
|
+
key: svg ? 'class' : 'className',
|
|
34
|
+
value: JSON.stringify(scopeClass),
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
const object = (attribute) => {
|
|
38
|
+
const fields = entries
|
|
39
|
+
.filter((entry) => entry.attribute === attribute)
|
|
40
|
+
.map(({ key, value }) => {
|
|
41
|
+
if (!/^[A-Za-z_$][\w$]*$/.test(key))
|
|
42
|
+
return `${JSON.stringify(key)}: ${value}`;
|
|
43
|
+
return key === value ? key : `${key}: ${value}`;
|
|
44
|
+
});
|
|
45
|
+
return fields.length === 0 ? null : `{ ${fields.join(', ')} }`;
|
|
46
|
+
};
|
|
47
|
+
const properties = object(false);
|
|
48
|
+
const attributesObject = object(true);
|
|
49
|
+
const args = [JSON.stringify(tag)];
|
|
50
|
+
if (svg) {
|
|
51
|
+
this.helpers.add('svgElement');
|
|
52
|
+
if (attributesObject)
|
|
53
|
+
args.push(attributesObject);
|
|
54
|
+
this.line(`${declaration} = $$svgElement(${args.join(', ')});`);
|
|
55
|
+
}
|
|
56
|
+
else {
|
|
57
|
+
this.helpers.add('element');
|
|
58
|
+
if (properties || attributesObject)
|
|
59
|
+
args.push(properties ?? '{}');
|
|
60
|
+
if (attributesObject)
|
|
61
|
+
args.push(attributesObject);
|
|
62
|
+
this.line(`${declaration} = $$element(${args.join(', ')});`);
|
|
63
|
+
}
|
|
64
|
+
return { covered, scoped: scopeClass !== null };
|
|
65
|
+
}
|
|
66
|
+
/** What `attribute()` would set, as an entry of the objects of the helper, if it is a value. */
|
|
67
|
+
entry(element, tag, attribute, live, svg, scope) {
|
|
68
|
+
const name = attribute.name.name;
|
|
69
|
+
const { value } = attribute;
|
|
70
|
+
if (name === 'ref' || name === 'style' || name.startsWith('bind:') || /^on[A-Z]/.test(name)) {
|
|
71
|
+
return null;
|
|
72
|
+
}
|
|
73
|
+
if (value?.kind === 'EventHandler' || (!svg && this.webComponentProperty(tag, name))) {
|
|
74
|
+
return null;
|
|
75
|
+
}
|
|
76
|
+
if (name === 'class' && scope) {
|
|
77
|
+
const key = svg ? 'class' : 'className';
|
|
78
|
+
if (value === null || value.kind === 'StringLiteral') {
|
|
79
|
+
const classes = `${value?.value ?? ''} ${scope}`.trim();
|
|
80
|
+
return { attribute: svg, key, value: JSON.stringify(classes) };
|
|
81
|
+
}
|
|
82
|
+
const nullable = this.typeOf(value)?.kind !== 'string';
|
|
83
|
+
return this.valueEntry(element, svg, key, value, live, (text) => {
|
|
84
|
+
return `\`\${${nullable ? `${text} ?? ""` : text}} ${scope}\``;
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
// SVG attributes are set with setAttribute: their properties are objects.
|
|
88
|
+
const property = svg ? null : domProperty(tag, name);
|
|
89
|
+
if (value === null) {
|
|
90
|
+
return property?.type.kind === 'bool'
|
|
91
|
+
? { attribute: false, key: property.name, value: 'true' }
|
|
92
|
+
: { attribute: true, key: name, value: '""' };
|
|
93
|
+
}
|
|
94
|
+
if (property)
|
|
95
|
+
return this.valueEntry(element, false, property.name, value, live);
|
|
96
|
+
// A null value leaves the attribute out, which takes a statement.
|
|
97
|
+
if (this.typeOf(value)?.kind === 'nullable')
|
|
98
|
+
return null;
|
|
99
|
+
return this.valueEntry(element, true, name, value, live);
|
|
100
|
+
}
|
|
101
|
+
/** An entry whose value may depend on state: then it is also set again on updates. */
|
|
102
|
+
valueEntry(element, attribute, key, value, live, wrap = (text) => text) {
|
|
103
|
+
const sources = live && value.kind !== 'StringLiteral' ? live.dependencies(value) : null;
|
|
104
|
+
if (!live || !sources || sources.size === 0) {
|
|
105
|
+
return { attribute, key, value: wrap(this.expression(value, ARROW)) };
|
|
106
|
+
}
|
|
107
|
+
const text = wrap(this.liveExpression(value));
|
|
108
|
+
live.depend(sources, attribute
|
|
109
|
+
? `${element}.setAttribute(${JSON.stringify(key)}, ${text});`
|
|
110
|
+
: `${element}.${key} = ${text};`);
|
|
111
|
+
return { attribute, key, value: text };
|
|
112
|
+
}
|
|
113
|
+
}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import type * as ast from '../ast.ts';
|
|
2
|
+
import { ComponentValueEmitter } from './component-values.ts';
|
|
3
|
+
import type { Live, Reactive } from './reactive.ts';
|
|
4
|
+
import { type EarlyReturns, type Ending } from './component-helpers.ts';
|
|
5
|
+
/** The markup a component returns, with early returns, update functions and setters of properties. */
|
|
6
|
+
/** `effect { ... }` of the component being written: its function and the list of its cleanups. */
|
|
7
|
+
interface Effect {
|
|
8
|
+
name: string;
|
|
9
|
+
node: ast.EffectStatement;
|
|
10
|
+
cleanups: string | null;
|
|
11
|
+
}
|
|
12
|
+
export declare abstract class EarlyReturnEmitter extends ComponentValueEmitter {
|
|
13
|
+
/** The CSS variables of the component's style, once its markup exists; by StyleEmitter. */
|
|
14
|
+
protected abstract setStyleValues(result: string): void;
|
|
15
|
+
/** Their updates, with those of the effects. */
|
|
16
|
+
protected abstract registerStyleValues(): void;
|
|
17
|
+
/**
|
|
18
|
+
* The effects of the component being written. One after an early return may not be created, so
|
|
19
|
+
* it does not run again on changes, like the markup there.
|
|
20
|
+
*/
|
|
21
|
+
private effects;
|
|
22
|
+
/** Whether an early return of the component has been written. */
|
|
23
|
+
protected returnedEarly: boolean;
|
|
24
|
+
/**
|
|
25
|
+
* The updates of the markup a component gives to the wrappers of its decorators, after an early
|
|
26
|
+
* return: they go into the view of the `return` that follows, with the wrappers' markup.
|
|
27
|
+
*/
|
|
28
|
+
private wrapped;
|
|
29
|
+
/** Writes the code of a component with its own reactivity, apart from the enclosing one. */
|
|
30
|
+
protected inComponent<T>(reactive: Reactive, mountNode: string | null, emit: () => T): T;
|
|
31
|
+
protected addEffect(effect: Effect): void;
|
|
32
|
+
/**
|
|
33
|
+
* An effect runs again after the updates of what it reads, so it is added after the markup, when
|
|
34
|
+
* the update functions are not written yet.
|
|
35
|
+
*/
|
|
36
|
+
protected registerEffects(): void;
|
|
37
|
+
/** Markup for a wrapper is live like returned markup, also where the body may have returned. */
|
|
38
|
+
protected declaredLive(node: ast.ElementExpression): Live | null;
|
|
39
|
+
/**
|
|
40
|
+
* After the markup of a component is created, before anything inserts it: the first node for
|
|
41
|
+
* `mount()` (a fragment is empty once inserted, so its first child is taken now), the CSS
|
|
42
|
+
* variables of its style, and the first run of the effects, whose cleanups also run when the
|
|
43
|
+
* component is removed.
|
|
44
|
+
*/
|
|
45
|
+
protected markupCreated(result: string): void;
|
|
46
|
+
/** Declarations that every return needs, written before the statements of the body. */
|
|
47
|
+
protected prepareEarlyReturns(result: string, reactive: Reactive, ending: Ending, label: string | null, component: ast.ComponentDeclaration): EarlyReturns;
|
|
48
|
+
/** The markup of one `return`: a block with its own update, which `view` keeps. */
|
|
49
|
+
protected returnedMarkup(value: ast.Expression, early: EarlyReturns): void;
|
|
50
|
+
/** The last `return` of a component with early returns, then the updates and the setters. */
|
|
51
|
+
protected lastReturn(value: ast.Expression, ending: Ending, early: EarlyReturns): void;
|
|
52
|
+
/** Puts the setters of an inlined component with early returns at the top of its block. */
|
|
53
|
+
protected placeSetters(code: string, result: string): string;
|
|
54
|
+
/** Functions that update the markup after changes of state; returns how many were written. */
|
|
55
|
+
protected updateFunctions(result: string, reactive: Reactive): number;
|
|
56
|
+
/** The markup a component returns, then the functions that update it. */
|
|
57
|
+
protected markup(value: ast.Expression, result: string, reactive: Reactive, ending: Ending): void;
|
|
58
|
+
}
|
|
59
|
+
export {};
|
|
@@ -0,0 +1,244 @@
|
|
|
1
|
+
import { isWrappedContent } from "../decorators.js";
|
|
2
|
+
import { ComponentValueEmitter } from "./component-values.js";
|
|
3
|
+
import { effectReads } from "./mutations.js";
|
|
4
|
+
import { Block, updatesOf } from "./reactive.js";
|
|
5
|
+
import { ARROW, indentMore } from "./syntax.js";
|
|
6
|
+
import { inlinesUpdate, needed } from "./updates.js";
|
|
7
|
+
import { SETTERS } from "./component-helpers.js";
|
|
8
|
+
export class EarlyReturnEmitter extends ComponentValueEmitter {
|
|
9
|
+
/**
|
|
10
|
+
* The effects of the component being written. One after an early return may not be created, so
|
|
11
|
+
* it does not run again on changes, like the markup there.
|
|
12
|
+
*/
|
|
13
|
+
effects = [];
|
|
14
|
+
/** Whether an early return of the component has been written. */
|
|
15
|
+
returnedEarly = false;
|
|
16
|
+
/**
|
|
17
|
+
* The updates of the markup a component gives to the wrappers of its decorators, after an early
|
|
18
|
+
* return: they go into the view of the `return` that follows, with the wrappers' markup.
|
|
19
|
+
*/
|
|
20
|
+
wrapped = null;
|
|
21
|
+
/** Writes the code of a component with its own reactivity, apart from the enclosing one. */
|
|
22
|
+
inComponent(reactive, mountNode, emit) {
|
|
23
|
+
const saved = {
|
|
24
|
+
reactive: this.reactive,
|
|
25
|
+
inHandler: this.inHandler,
|
|
26
|
+
rendering: this.rendering,
|
|
27
|
+
earlyReturns: this.earlyReturns,
|
|
28
|
+
mountNode: this.mountNode,
|
|
29
|
+
wrapped: this.wrapped,
|
|
30
|
+
effects: this.effects,
|
|
31
|
+
returnedEarly: this.returnedEarly,
|
|
32
|
+
};
|
|
33
|
+
this.reactive = reactive.sources.size > 0 ? reactive : null;
|
|
34
|
+
this.inHandler = 0;
|
|
35
|
+
this.rendering = 0;
|
|
36
|
+
this.earlyReturns = null;
|
|
37
|
+
this.mountNode = mountNode;
|
|
38
|
+
this.wrapped = null;
|
|
39
|
+
this.effects = [];
|
|
40
|
+
this.returnedEarly = false;
|
|
41
|
+
try {
|
|
42
|
+
return emit();
|
|
43
|
+
}
|
|
44
|
+
finally {
|
|
45
|
+
this.reactive = saved.reactive;
|
|
46
|
+
this.inHandler = saved.inHandler;
|
|
47
|
+
this.rendering = saved.rendering;
|
|
48
|
+
this.earlyReturns = saved.earlyReturns;
|
|
49
|
+
this.mountNode = saved.mountNode;
|
|
50
|
+
this.wrapped = saved.wrapped;
|
|
51
|
+
this.effects = saved.effects;
|
|
52
|
+
this.returnedEarly = saved.returnedEarly;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
addEffect(effect) {
|
|
56
|
+
this.effects.push({ ...effect, live: !this.returnedEarly, registered: false });
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* An effect runs again after the updates of what it reads, so it is added after the markup, when
|
|
60
|
+
* the update functions are not written yet.
|
|
61
|
+
*/
|
|
62
|
+
registerEffects() {
|
|
63
|
+
const { reactive } = this;
|
|
64
|
+
if (!reactive)
|
|
65
|
+
return;
|
|
66
|
+
this.registerStyleValues();
|
|
67
|
+
for (const effect of this.effects) {
|
|
68
|
+
if (effect.registered || !effect.live)
|
|
69
|
+
continue;
|
|
70
|
+
effect.registered = true;
|
|
71
|
+
reactive.depend(reactive.dependencies(effect.node.body, effectReads), `${effect.name}();`);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
/** Markup for a wrapper is live like returned markup, also where the body may have returned. */
|
|
75
|
+
declaredLive(node) {
|
|
76
|
+
const early = this.earlyReturns;
|
|
77
|
+
if (!early || !this.reactive || !isWrappedContent(node) || this.reactive.topLevel.has(node)) {
|
|
78
|
+
return super.declaredLive(node);
|
|
79
|
+
}
|
|
80
|
+
return (this.wrapped ??= new Block(early.reactive));
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* After the markup of a component is created, before anything inserts it: the first node for
|
|
84
|
+
* `mount()` (a fragment is empty once inserted, so its first child is taken now), the CSS
|
|
85
|
+
* variables of its style, and the first run of the effects, whose cleanups also run when the
|
|
86
|
+
* component is removed.
|
|
87
|
+
*/
|
|
88
|
+
markupCreated(result) {
|
|
89
|
+
if (this.mountNode) {
|
|
90
|
+
this.line(`${this.mountNode} = ${result}?.nodeType === 11 ? ${result}.firstChild : ${result};`);
|
|
91
|
+
}
|
|
92
|
+
this.setStyleValues(result);
|
|
93
|
+
for (const effect of this.effects) {
|
|
94
|
+
this.line(`${effect.name}();`);
|
|
95
|
+
if (effect.cleanups === null)
|
|
96
|
+
continue;
|
|
97
|
+
this.helpers.add('owner');
|
|
98
|
+
this.line(`$$onRemove(() => $$runDeferred(${effect.cleanups}.splice(0)));`);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
/** Declarations that every return needs, written before the statements of the body. */
|
|
102
|
+
prepareEarlyReturns(result, reactive, ending, label, component) {
|
|
103
|
+
const id = this.nextId();
|
|
104
|
+
const view = `$$view${id}`;
|
|
105
|
+
let exit = `break ${label};`;
|
|
106
|
+
let setter = null;
|
|
107
|
+
if (ending.kind === 'function') {
|
|
108
|
+
setter = ending.props.length > 0 ? `$$set${component.name.name}${id}` : null;
|
|
109
|
+
exit = `return [${result}${setter ? `, ${setter}` : ''}];`;
|
|
110
|
+
}
|
|
111
|
+
else if (ending.setters.size > 0) {
|
|
112
|
+
this.line(`${SETTERS}${result}${SETTERS}`);
|
|
113
|
+
}
|
|
114
|
+
if (reactive.sources.size > 0)
|
|
115
|
+
this.line(`let ${view};`);
|
|
116
|
+
return { fn: this.fn, result, reactive, view, exit, setter };
|
|
117
|
+
}
|
|
118
|
+
/** The markup of one `return`: a block with its own update, which `view` keeps. */
|
|
119
|
+
returnedMarkup(value, early) {
|
|
120
|
+
const { result, reactive, view } = early;
|
|
121
|
+
if (reactive.sources.size === 0) {
|
|
122
|
+
if (value.kind === 'ElementExpression' && this.isElement(value)) {
|
|
123
|
+
this.build(value, result, null);
|
|
124
|
+
}
|
|
125
|
+
else {
|
|
126
|
+
this.line(`${result} = ${this.expression(value, ARROW)};`);
|
|
127
|
+
}
|
|
128
|
+
this.markupCreated(result);
|
|
129
|
+
return;
|
|
130
|
+
}
|
|
131
|
+
// A wrapper returns once, so what was given to it updates in this view.
|
|
132
|
+
const block = this.wrapped ?? new Block(reactive);
|
|
133
|
+
this.wrapped = null;
|
|
134
|
+
const root = value.kind === 'ElementExpression'
|
|
135
|
+
? this.create(value, block)
|
|
136
|
+
: this.expression(value, ARROW);
|
|
137
|
+
this.registerEffects();
|
|
138
|
+
if (block.statements.length > 0) {
|
|
139
|
+
const body = this.block(() => {
|
|
140
|
+
for (const statement of block.statements)
|
|
141
|
+
this.line(indentMore(statement));
|
|
142
|
+
});
|
|
143
|
+
this.line(`${view} = () => ${body};`);
|
|
144
|
+
reactive.depend(block.sources, `${view}?.();`);
|
|
145
|
+
}
|
|
146
|
+
this.line(`${result} = ${root};`);
|
|
147
|
+
this.markupCreated(result);
|
|
148
|
+
}
|
|
149
|
+
/** The last `return` of a component with early returns, then the updates and the setters. */
|
|
150
|
+
lastReturn(value, ending, early) {
|
|
151
|
+
const { result, reactive } = early;
|
|
152
|
+
this.returnedMarkup(value, early);
|
|
153
|
+
const functions = this.updateFunctions(result, reactive);
|
|
154
|
+
if (ending.kind === 'inline') {
|
|
155
|
+
// An early return leaves the block, so the setters are assigned at its top.
|
|
156
|
+
const texts = [...ending.setters].map(([prop, setter]) => `${setter} = ($$${prop}) => ${this.setterBody([prop], reactive)};`);
|
|
157
|
+
if (texts.length > 0)
|
|
158
|
+
this.pendingSetters.set(result, texts);
|
|
159
|
+
return;
|
|
160
|
+
}
|
|
161
|
+
if (early.setter) {
|
|
162
|
+
this.blankLine();
|
|
163
|
+
const params = this.parameters(ending.props, '$$');
|
|
164
|
+
const names = ending.props.map((param) => param.name.name);
|
|
165
|
+
this.line(`function ${early.setter}(${params}) ${this.setterBody(names, reactive)}`);
|
|
166
|
+
}
|
|
167
|
+
else if (functions > 0) {
|
|
168
|
+
this.blankLine();
|
|
169
|
+
}
|
|
170
|
+
this.line(early.exit);
|
|
171
|
+
}
|
|
172
|
+
/** Puts the setters of an inlined component with early returns at the top of its block. */
|
|
173
|
+
placeSetters(code, result) {
|
|
174
|
+
const texts = this.pendingSetters.get(result);
|
|
175
|
+
const marker = `${SETTERS}${result}${SETTERS}`;
|
|
176
|
+
if (!code.includes(marker))
|
|
177
|
+
return code;
|
|
178
|
+
this.pendingSetters.delete(result);
|
|
179
|
+
return code.replace(new RegExp(`^([ \\t]*)${marker.replace(/\$/g, '\\$')}\n`, 'm'), (_, indent) => (texts ?? []).map((text) => `${indent}${text}\n`).join(''));
|
|
180
|
+
}
|
|
181
|
+
/** Functions that update the markup after changes of state; returns how many were written. */
|
|
182
|
+
updateFunctions(result, reactive) {
|
|
183
|
+
let functions = 0;
|
|
184
|
+
for (const source of reactive.sources.values()) {
|
|
185
|
+
const called = source.writes.some((write) => needed(source, write).length > 0);
|
|
186
|
+
if (source.kind !== 'state' || !called || inlinesUpdate(source))
|
|
187
|
+
continue;
|
|
188
|
+
const body = this.block(() => {
|
|
189
|
+
// Derived values are computed again even before the markup exists, which a function of
|
|
190
|
+
// the body may change the state before.
|
|
191
|
+
for (const computation of source.computations)
|
|
192
|
+
this.line(indentMore(computation));
|
|
193
|
+
if (source.writes.some((write) => write.early))
|
|
194
|
+
this.line(`if (!${result}) return;`);
|
|
195
|
+
for (const dependent of source.dependents)
|
|
196
|
+
this.line(indentMore(dependent));
|
|
197
|
+
});
|
|
198
|
+
this.blankLine();
|
|
199
|
+
this.line(`function ${source.update}() ${body}`);
|
|
200
|
+
functions++;
|
|
201
|
+
}
|
|
202
|
+
return functions;
|
|
203
|
+
}
|
|
204
|
+
/** The markup a component returns, then the functions that update it. */
|
|
205
|
+
markup(value, result, reactive, ending) {
|
|
206
|
+
if (reactive.sources.size === 0) {
|
|
207
|
+
if (value.kind === 'ElementExpression' && this.isElement(value)) {
|
|
208
|
+
this.build(value, result, null);
|
|
209
|
+
}
|
|
210
|
+
else {
|
|
211
|
+
this.line(`${result} = ${this.expression(value, ARROW)};`);
|
|
212
|
+
}
|
|
213
|
+
this.markupCreated(result);
|
|
214
|
+
if (ending.kind === 'function')
|
|
215
|
+
this.line(`return [${result}];`);
|
|
216
|
+
return;
|
|
217
|
+
}
|
|
218
|
+
const root = value.kind === 'ElementExpression'
|
|
219
|
+
? this.create(value, reactive)
|
|
220
|
+
: this.expression(value, ARROW);
|
|
221
|
+
this.registerEffects();
|
|
222
|
+
let functions = 0;
|
|
223
|
+
const setters = ending.kind === 'inline' ? ending.setters : new Map();
|
|
224
|
+
for (const [prop, setter] of setters) {
|
|
225
|
+
const source = reactive.sources.get(prop);
|
|
226
|
+
const param = `$$${prop}`;
|
|
227
|
+
const body = this.block(() => {
|
|
228
|
+
this.line(`${this.name(prop)} = ${param};`);
|
|
229
|
+
for (const dependent of updatesOf(source))
|
|
230
|
+
this.line(indentMore(dependent));
|
|
231
|
+
});
|
|
232
|
+
this.blankLine();
|
|
233
|
+
this.line(`${setter} = (${param}) => ${body};`);
|
|
234
|
+
functions++;
|
|
235
|
+
}
|
|
236
|
+
functions += this.updateFunctions(result, reactive);
|
|
237
|
+
if (functions > 0)
|
|
238
|
+
this.blankLine();
|
|
239
|
+
this.line(`${result} = ${root};`);
|
|
240
|
+
this.markupCreated(result);
|
|
241
|
+
if (ending.kind === 'function')
|
|
242
|
+
this.functionResult(result, reactive, ending.props);
|
|
243
|
+
}
|
|
244
|
+
}
|