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
|
@@ -1,6 +1,11 @@
|
|
|
1
1
|
import { containsReturn } from "../recursion.js";
|
|
2
2
|
/** Marks where the setters of an inlined component with early returns go: before any return. */
|
|
3
3
|
export const SETTERS = String.fromCharCode(0xe002);
|
|
4
|
+
/**
|
|
5
|
+
* Marks the `$$watch(...)` of a component that reads state of the module, around the id of the
|
|
6
|
+
* source: it stays only if something depends on the state (resolveMarkers).
|
|
7
|
+
*/
|
|
8
|
+
export const LISTEN = String.fromCharCode(0xe004);
|
|
4
9
|
/** `f(a, undefined, undefined)` → `f(a)`: missing arguments are undefined anyway. */
|
|
5
10
|
export function withoutTrailingUndefined(args) {
|
|
6
11
|
let end = args.length;
|
|
@@ -8,7 +13,7 @@ export function withoutTrailingUndefined(args) {
|
|
|
8
13
|
end--;
|
|
9
14
|
return args.slice(0, end);
|
|
10
15
|
}
|
|
11
|
-
/** Whether a component has `mount
|
|
16
|
+
/** Whether a component has `mount { ... }`. */
|
|
12
17
|
export function hasMount(component) {
|
|
13
18
|
return component.body.body.some((statement) => statement.kind === 'MountStatement');
|
|
14
19
|
}
|
|
@@ -9,7 +9,7 @@ export declare abstract class ComponentPropsEmitter extends ControlFlowEmitter {
|
|
|
9
9
|
/** Setters waiting for their place at the top of an inlined component, by its result. */
|
|
10
10
|
protected readonly pendingSetters: Map<string, string[]>;
|
|
11
11
|
/**
|
|
12
|
-
* In a component with `mount
|
|
12
|
+
* In a component with `mount`: the variable that gets its first node once the markup is
|
|
13
13
|
* created, so that `$$mount` can wait until that node is in the document.
|
|
14
14
|
*/
|
|
15
15
|
protected mountNode: string | null;
|
|
@@ -17,7 +17,14 @@ export declare abstract class ComponentPropsEmitter extends ControlFlowEmitter {
|
|
|
17
17
|
* A use of a recursive component: a call of its function. The parent's updates call the
|
|
18
18
|
* returned function with the new values of the properties that depend on its state.
|
|
19
19
|
*/
|
|
20
|
-
protected callComponent(component: ast.ComponentDeclaration, values: ReadonlyMap<string, string>, reactiveProps: ReadonlyMap<string, [string, Set<Source>]>, live: Live | null, root?: string): string;
|
|
20
|
+
protected callComponent(component: ast.ComponentDeclaration, values: ReadonlyMap<string, string>, reactiveProps: ReadonlyMap<string, [string, Set<Source>]>, live: Live | null, root?: string, binders?: ReadonlyMap<string, string>): string;
|
|
21
|
+
/**
|
|
22
|
+
* `bind:active={tab}`: the function that the component calls with the new value of `active`. It
|
|
23
|
+
* is written in the caller, so it writes the caller's `tab` and updates what reads it there.
|
|
24
|
+
*/
|
|
25
|
+
protected binder(target: ast.Expression): string;
|
|
26
|
+
/** `$$bindActive?.(active);` in a function component, which gets the binders as arguments. */
|
|
27
|
+
protected bindersOf(component: ast.ComponentDeclaration, reactive: Reactive): string[];
|
|
21
28
|
protected reactiveOf(component: ast.ComponentDeclaration, props: Iterable<string>): Reactive;
|
|
22
29
|
/**
|
|
23
30
|
* `title, kind = "info"`: parameters with their default values, each name with a prefix. A tag
|
|
@@ -40,3 +47,5 @@ export declare abstract class ComponentPropsEmitter extends ControlFlowEmitter {
|
|
|
40
47
|
*/
|
|
41
48
|
protected spreadProps(argument: ast.Expression, component: ast.ComponentDeclaration, live: Live | null, values: Map<string, string>, reactiveProps: Map<string, [string, Set<Source>]>): void;
|
|
42
49
|
}
|
|
50
|
+
/** The bound properties of a component, `bind active string`, in their order. */
|
|
51
|
+
export declare function boundParams(component: ast.ComponentDeclaration): ast.Parameter[];
|
|
@@ -3,6 +3,7 @@ import { declaredNames } from "../names.js";
|
|
|
3
3
|
import { isConstant, isSimple, rootName } from "./analysis.js";
|
|
4
4
|
import { ControlFlowEmitter } from "./control-flow.js";
|
|
5
5
|
import { Reactive, updatesOf } from "./reactive.js";
|
|
6
|
+
import { rootsOf } from "./shared.js";
|
|
6
7
|
import { ARROW, indentMore, lowerFirst, POSTFIX, upperFirst } from "./syntax.js";
|
|
7
8
|
import { withoutTrailingUndefined } from "./component-helpers.js";
|
|
8
9
|
/** The properties of components: their values, spread objects, and calls of function components. */
|
|
@@ -12,7 +13,7 @@ export class ComponentPropsEmitter extends ControlFlowEmitter {
|
|
|
12
13
|
/** Setters waiting for their place at the top of an inlined component, by its result. */
|
|
13
14
|
pendingSetters = new Map();
|
|
14
15
|
/**
|
|
15
|
-
* In a component with `mount
|
|
16
|
+
* In a component with `mount`: the variable that gets its first node once the markup is
|
|
16
17
|
* created, so that `$$mount` can wait until that node is in the document.
|
|
17
18
|
*/
|
|
18
19
|
mountNode = null;
|
|
@@ -20,15 +21,17 @@ export class ComponentPropsEmitter extends ControlFlowEmitter {
|
|
|
20
21
|
* A use of a recursive component: a call of its function. The parent's updates call the
|
|
21
22
|
* returned function with the new values of the properties that depend on its state.
|
|
22
23
|
*/
|
|
23
|
-
callComponent(component, values, reactiveProps, live, root) {
|
|
24
|
+
callComponent(component, values, reactiveProps, live, root, binders = new Map()) {
|
|
24
25
|
const name = component.name.name;
|
|
25
26
|
const id = this.nextId();
|
|
26
27
|
const result = `$$${lowerFirst(name)}${id}`;
|
|
27
28
|
// A missing property with a default value is left undefined, so that the default applies.
|
|
28
29
|
const argument = (param) => values.get(param.name.name) ?? (param.defaultValue ? 'undefined' : 'null');
|
|
29
|
-
// The
|
|
30
|
+
// The functions that write bound properties into the caller come next, then the element a ref
|
|
31
|
+
// created for it, as `$$root`.
|
|
30
32
|
const args = withoutTrailingUndefined([
|
|
31
33
|
...component.params.map(argument),
|
|
34
|
+
...boundParams(component).map((param) => binders.get(param.name.name) ?? 'undefined'),
|
|
32
35
|
...(root === undefined ? [] : [root]),
|
|
33
36
|
]);
|
|
34
37
|
const fn = this.functionNames.get(component) ?? `$$${name}`;
|
|
@@ -49,13 +52,51 @@ export class ComponentPropsEmitter extends ControlFlowEmitter {
|
|
|
49
52
|
live.depend(sources, `${setter}(${setterArgs.join(', ')});`);
|
|
50
53
|
return result;
|
|
51
54
|
}
|
|
55
|
+
/**
|
|
56
|
+
* `bind:active={tab}`: the function that the component calls with the new value of `active`. It
|
|
57
|
+
* is written in the caller, so it writes the caller's `tab` and updates what reads it there.
|
|
58
|
+
*/
|
|
59
|
+
binder(target) {
|
|
60
|
+
const name = `$$bind${this.nextId()}`;
|
|
61
|
+
const text = this.liveExpression(target);
|
|
62
|
+
// `todos` of `bind:todos={todos}`, or the state that `bind:open={menu.open}` writes into.
|
|
63
|
+
const written = [...(this.reactive?.rootSources(target) ?? [])].filter((source) => source.kind === 'state');
|
|
64
|
+
const body = this.block(() => {
|
|
65
|
+
this.line(`${text} = $$value;`);
|
|
66
|
+
for (const name of rootsOf(target, this.shared)) {
|
|
67
|
+
this.line(`$$changed(${this.shared.listeners.get(name)});`);
|
|
68
|
+
}
|
|
69
|
+
for (const source of written)
|
|
70
|
+
this.line(`${this.write(source)};`);
|
|
71
|
+
});
|
|
72
|
+
this.line(`const ${name} = ($$value) => ${body};`);
|
|
73
|
+
return name;
|
|
74
|
+
}
|
|
75
|
+
/** `$$bindActive?.(active);` in a function component, which gets the binders as arguments. */
|
|
76
|
+
bindersOf(component, reactive) {
|
|
77
|
+
return boundParams(component).map((param) => {
|
|
78
|
+
const binder = `$$bind${upperFirst(param.name.name)}`;
|
|
79
|
+
reactive.sources.get(param.name.name).bound = `${binder}?.(${this.name(param.name.name)});`;
|
|
80
|
+
return binder;
|
|
81
|
+
});
|
|
82
|
+
}
|
|
52
83
|
reactiveOf(component, props) {
|
|
84
|
+
const shared = new Set(this.shared.listeners.keys());
|
|
53
85
|
return new Reactive(component, this.fn, props, (sourceName, kind) => {
|
|
54
86
|
const id = this.nextId();
|
|
55
87
|
// `$$card$price`, an argument of a decorator, is updated by `$$updateCard$price3`.
|
|
56
88
|
const update = `$$update${upperFirst(sourceName.replace(/^\$\$/, ''))}${id}`;
|
|
57
|
-
return {
|
|
58
|
-
|
|
89
|
+
return {
|
|
90
|
+
id,
|
|
91
|
+
name: sourceName,
|
|
92
|
+
kind,
|
|
93
|
+
update,
|
|
94
|
+
computations: [],
|
|
95
|
+
dependents: [],
|
|
96
|
+
writes: [],
|
|
97
|
+
bound: null,
|
|
98
|
+
};
|
|
99
|
+
}, shared);
|
|
59
100
|
}
|
|
60
101
|
/**
|
|
61
102
|
* `title, kind = "info"`: parameters with their default values, each name with a prefix. A tag
|
|
@@ -171,3 +212,7 @@ export class ComponentPropsEmitter extends ControlFlowEmitter {
|
|
|
171
212
|
}
|
|
172
213
|
}
|
|
173
214
|
}
|
|
215
|
+
/** The bound properties of a component, `bind active string`, in their order. */
|
|
216
|
+
export function boundParams(component) {
|
|
217
|
+
return component.params.filter((param) => param.bind);
|
|
218
|
+
}
|
|
@@ -19,16 +19,17 @@ export declare abstract class ComponentEmitter extends StyleEmitter {
|
|
|
19
19
|
* reads; one that reads no state is a constant.
|
|
20
20
|
*/
|
|
21
21
|
private derivedValue;
|
|
22
|
+
/**
|
|
23
|
+
* `derived summary { ... }`: the block becomes a function that computes the value, called first
|
|
24
|
+
* and in the updates of what it reads; one that reads no state runs once. The block computes
|
|
25
|
+
* while the markup is created or updated, so it calls no updates, as the expressions there.
|
|
26
|
+
*/
|
|
27
|
+
private derivedBlock;
|
|
22
28
|
/**
|
|
23
29
|
* An argument of a decorator that reads properties of the component: when they change, it is
|
|
24
30
|
* computed again, and what reads it is updated.
|
|
25
31
|
*/
|
|
26
32
|
private decoratorArgument;
|
|
27
|
-
/**
|
|
28
|
-
* `mount() { ... }` becomes `$$mount(() => { ... })`. Its body runs later, like a handler: what
|
|
29
|
-
* it changes updates the markup.
|
|
30
|
-
*/
|
|
31
|
-
protected mountStatement(node: ast.MountStatement): void;
|
|
32
33
|
/**
|
|
33
34
|
* Inlines a component: its code goes into a block where its properties are constants. Attribute
|
|
34
35
|
* values are computed before the block, so that the component's own names cannot hide the
|
|
@@ -50,6 +50,10 @@ export class ComponentEmitter extends StyleEmitter {
|
|
|
50
50
|
const { reactive } = this;
|
|
51
51
|
const [name] = node.names;
|
|
52
52
|
const [value] = node.values;
|
|
53
|
+
if (node.body && name) {
|
|
54
|
+
this.derivedBlock(name, node.body.body);
|
|
55
|
+
return;
|
|
56
|
+
}
|
|
53
57
|
const sources = reactive && value ? reactive.dependencies(value) : new Set();
|
|
54
58
|
super.statement(sources.size > 0 ? node : { ...node, keyword: 'const' });
|
|
55
59
|
if (!reactive || !name || !value || sources.size === 0)
|
|
@@ -57,6 +61,25 @@ export class ComponentEmitter extends StyleEmitter {
|
|
|
57
61
|
const expression = this.withHoisting(false, () => this.expression(value, ARROW));
|
|
58
62
|
reactive.compute(sources, `${this.name(name.name)} = ${expression};`);
|
|
59
63
|
}
|
|
64
|
+
/**
|
|
65
|
+
* `derived summary { ... }`: the block becomes a function that computes the value, called first
|
|
66
|
+
* and in the updates of what it reads; one that reads no state runs once. The block computes
|
|
67
|
+
* while the markup is created or updated, so it calls no updates, as the expressions there.
|
|
68
|
+
*/
|
|
69
|
+
derivedBlock(name, block) {
|
|
70
|
+
const { reactive } = this;
|
|
71
|
+
const sources = reactive ? reactive.dependencies(block) : new Set();
|
|
72
|
+
const [, body] = this.withRendering(this.rendering + 1, () => this.func([], block));
|
|
73
|
+
const variable = this.name(name.name);
|
|
74
|
+
if (!reactive || sources.size === 0) {
|
|
75
|
+
this.line(`const ${variable} = (() => ${body})();`);
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
78
|
+
const compute = `$$compute${upperFirst(name.name)}${this.nextId()}`;
|
|
79
|
+
this.line(`const ${compute} = () => ${body};`);
|
|
80
|
+
this.line(`let ${variable} = ${compute}();`);
|
|
81
|
+
reactive.compute(sources, `${variable} = ${compute}();`);
|
|
82
|
+
}
|
|
60
83
|
/**
|
|
61
84
|
* An argument of a decorator that reads properties of the component: when they change, it is
|
|
62
85
|
* computed again, and what reads it is updated.
|
|
@@ -67,26 +90,13 @@ export class ComponentEmitter extends StyleEmitter {
|
|
|
67
90
|
const source = reactive?.sources.get(node.names[0].name);
|
|
68
91
|
if (!reactive || !value || !source)
|
|
69
92
|
return;
|
|
70
|
-
|
|
93
|
+
// Properties, or an argument of a decorator that includes this one, which changes with them.
|
|
94
|
+
const props = new Set([...reactive.dependencies(value)].filter((each) => each !== source));
|
|
71
95
|
if (props.size === 0)
|
|
72
96
|
return;
|
|
73
97
|
const assignment = `${this.name(source.name)} = ${this.expression(value, ARROW)};`;
|
|
74
98
|
reactive.depend(props, `${assignment}\n${this.indent()}${this.write(source)};`);
|
|
75
99
|
}
|
|
76
|
-
/**
|
|
77
|
-
* `mount() { ... }` becomes `$$mount(() => { ... })`. Its body runs later, like a handler: what
|
|
78
|
-
* it changes updates the markup.
|
|
79
|
-
*/
|
|
80
|
-
mountStatement(node) {
|
|
81
|
-
this.inHandler++;
|
|
82
|
-
try {
|
|
83
|
-
const [, body] = this.withRendering(0, () => this.func([], node.body));
|
|
84
|
-
this.line(`$$mount(() => ${this.mountNode ?? 'null'}, () => ${body});`);
|
|
85
|
-
}
|
|
86
|
-
finally {
|
|
87
|
-
this.inHandler--;
|
|
88
|
-
}
|
|
89
|
-
}
|
|
90
100
|
/**
|
|
91
101
|
* Inlines a component: its code goes into a block where its properties are constants. Attribute
|
|
92
102
|
* values are computed before the block, so that the component's own names cannot hide the
|
|
@@ -101,6 +111,7 @@ export class ComponentEmitter extends StyleEmitter {
|
|
|
101
111
|
this.line(`// <${name}>`);
|
|
102
112
|
const values = new Map();
|
|
103
113
|
const reactiveProps = new Map();
|
|
114
|
+
const binders = new Map();
|
|
104
115
|
const rest = attrsParam(component);
|
|
105
116
|
const forwarded = [];
|
|
106
117
|
for (const attribute of node.attributes) {
|
|
@@ -108,9 +119,12 @@ export class ComponentEmitter extends StyleEmitter {
|
|
|
108
119
|
this.spreadProps(attribute.argument, component, live, values, reactiveProps);
|
|
109
120
|
continue;
|
|
110
121
|
}
|
|
111
|
-
const
|
|
122
|
+
const bound = attribute.name.name.startsWith('bind:');
|
|
123
|
+
const prop = bound ? attribute.name.name.slice('bind:'.length) : attribute.name.name;
|
|
112
124
|
if (prop === 'ref')
|
|
113
125
|
continue;
|
|
126
|
+
if (bound)
|
|
127
|
+
binders.set(prop, this.binder(attribute.value));
|
|
114
128
|
if (rest && !component.params.some((param) => param.name.name === prop)) {
|
|
115
129
|
forwarded.push(attribute);
|
|
116
130
|
continue;
|
|
@@ -133,8 +147,9 @@ export class ComponentEmitter extends StyleEmitter {
|
|
|
133
147
|
if (rest)
|
|
134
148
|
values.set(rest.name.name, this.attrsApplier(component, forwarded, live));
|
|
135
149
|
const root = this.rootFor(node);
|
|
136
|
-
if (isFunction)
|
|
137
|
-
return this.callComponent(component, values, reactiveProps, live, root?.name);
|
|
150
|
+
if (isFunction) {
|
|
151
|
+
return this.callComponent(component, values, reactiveProps, live, root?.name, binders);
|
|
152
|
+
}
|
|
138
153
|
const result = `$$${lowerFirst(name)}${this.nextId()}`;
|
|
139
154
|
const label = hasEarlyReturn(component) ? `$$body${this.nextId()}` : null;
|
|
140
155
|
const mountNode = hasMount(component) ? `$$mountNode${this.nextId()}` : null;
|
|
@@ -153,8 +168,12 @@ export class ComponentEmitter extends StyleEmitter {
|
|
|
153
168
|
for (const param of component.params) {
|
|
154
169
|
const value = values.get(param.name.name) ??
|
|
155
170
|
(param.defaultValue ? this.expression(param.defaultValue, ARROW) : 'null');
|
|
156
|
-
const
|
|
157
|
-
|
|
171
|
+
const name = this.name(param.name.name);
|
|
172
|
+
const binder = binders.get(param.name.name);
|
|
173
|
+
const keyword = setters.has(param.name.name) || param.bind ? 'let' : 'const';
|
|
174
|
+
this.line(`${keyword} ${name} = ${value};`);
|
|
175
|
+
if (binder)
|
|
176
|
+
reactive.sources.get(param.name.name).bound = `${binder}(${name});`;
|
|
158
177
|
}
|
|
159
178
|
this.body(component, result, reactive, { kind: 'inline', setters }, label);
|
|
160
179
|
})))));
|
|
@@ -182,7 +201,11 @@ export class ComponentEmitter extends StyleEmitter {
|
|
|
182
201
|
// A ref to the component created the element at its top: `$$root`, undefined without one.
|
|
183
202
|
const own = this.takesRoot(component) ? ownRoot(component) : null;
|
|
184
203
|
const preset = own && typeof own !== 'string' ? { node: own, name: '$$root', created: false } : null;
|
|
185
|
-
const params = [
|
|
204
|
+
const params = [
|
|
205
|
+
this.parameters(component.params, ''),
|
|
206
|
+
...this.bindersOf(component, reactive),
|
|
207
|
+
...(preset ? ['$$root'] : []),
|
|
208
|
+
]
|
|
186
209
|
.filter((each) => each !== '')
|
|
187
210
|
.join(', ');
|
|
188
211
|
const body = this.withRefs(component, preset, () => this.withStyles(component, result, () => this.inComponent(reactive, mountNode, () => this.block(() => {
|
|
@@ -20,6 +20,8 @@ export declare abstract class ControlFlowEmitter extends ElementEmitter {
|
|
|
20
20
|
* state go into its update. The block of `<Page />` gets the component, `($$component3) => ...`.
|
|
21
21
|
*/
|
|
22
22
|
protected blockCreator(statements: readonly ast.Statement[], parent: Live, loop?: ast.ForInStatement, component?: ast.Identifier): string;
|
|
23
|
+
/** The key of a list, `with key todo.id`: `(todo) => todo.id`, which its blocks are found by. */
|
|
24
|
+
private listKey;
|
|
23
25
|
/** A classic `for` loop in live markup: a function that runs it and returns the content. */
|
|
24
26
|
private loopContent;
|
|
25
27
|
}
|
|
@@ -31,7 +31,9 @@ export class ControlFlowEmitter extends ElementEmitter {
|
|
|
31
31
|
}
|
|
32
32
|
case 'ForInStatement': {
|
|
33
33
|
const items = this.liveFunction(statement.iterable);
|
|
34
|
-
|
|
34
|
+
const create = this.blockCreator(statement.body.body, live, statement);
|
|
35
|
+
const key = statement.withKey ? `, ${this.listKey(statement, statement.withKey)}` : '';
|
|
36
|
+
call = `$$list(${items}, ${create}${key})`;
|
|
35
37
|
break;
|
|
36
38
|
}
|
|
37
39
|
case 'ForStatement':
|
|
@@ -85,20 +87,22 @@ export class ControlFlowEmitter extends ElementEmitter {
|
|
|
85
87
|
*/
|
|
86
88
|
blockCreator(statements, parent, loop, component) {
|
|
87
89
|
const block = new Block(parent);
|
|
88
|
-
const params = loop
|
|
89
|
-
? [loop.value, ...(loop.key ? [loop.key] : [])].map(parameter)
|
|
90
|
-
: component
|
|
91
|
-
? [parameter(component)]
|
|
92
|
-
: [];
|
|
90
|
+
const params = loop ? loopParameters(loop) : component ? [parameter(component)] : [];
|
|
93
91
|
return this.withFunction('none', params, () => this.withRendering(this.rendering + 1, () => {
|
|
94
92
|
const paramList = this.params(params);
|
|
95
93
|
const fragment = `$$block${this.nextId()}`;
|
|
96
94
|
const body = this.block(() => this.withScope(declarationsOf(statements), () => {
|
|
97
95
|
this.line(`const ${fragment} = document.createDocumentFragment();`);
|
|
98
96
|
this.children(fragment, statements, block);
|
|
99
|
-
// The block of an item that stays gets the item's new position
|
|
97
|
+
// The block of an item that stays gets the item's new position, and with a key the
|
|
98
|
+
// item itself, which may be another object with the same key.
|
|
100
99
|
const key = loop?.key && loop.key.name !== '_' ? this.name(loop.key.name) : null;
|
|
101
|
-
const
|
|
100
|
+
const item = loop?.withKey && loop.value.name !== '_' ? this.name(loop.value.name) : null;
|
|
101
|
+
const updates = [
|
|
102
|
+
...(key ? [`${key} = $$index;`] : []),
|
|
103
|
+
...(item ? [`${item} = $$item;`] : []),
|
|
104
|
+
...block.statements,
|
|
105
|
+
];
|
|
102
106
|
if (updates.length === 0) {
|
|
103
107
|
this.line(`return [${fragment}];`);
|
|
104
108
|
return;
|
|
@@ -107,11 +111,17 @@ export class ControlFlowEmitter extends ElementEmitter {
|
|
|
107
111
|
for (const statement of updates)
|
|
108
112
|
this.line(indentMore(statement));
|
|
109
113
|
});
|
|
110
|
-
|
|
114
|
+
const args = item ? '$$index, $$item' : key ? '$$index' : '';
|
|
115
|
+
this.line(`return [${fragment}, (${args}) => ${update}];`);
|
|
111
116
|
}));
|
|
112
117
|
return `(${paramList}) => ${body}`;
|
|
113
118
|
}));
|
|
114
119
|
}
|
|
120
|
+
/** The key of a list, `with key todo.id`: `(todo) => todo.id`, which its blocks are found by. */
|
|
121
|
+
listKey(loop, key) {
|
|
122
|
+
const params = loopParameters(loop);
|
|
123
|
+
return this.withFunction('none', params, () => `(${this.params(params)}) => ${this.expression(key, CONDITIONAL)}`);
|
|
124
|
+
}
|
|
115
125
|
/** A classic `for` loop in live markup: a function that runs it and returns the content. */
|
|
116
126
|
loopContent(statement) {
|
|
117
127
|
return this.withFunction('none', [], () => this.withRendering(this.rendering + 1, () => {
|
|
@@ -136,6 +146,10 @@ function returnNumber(value, at) {
|
|
|
136
146
|
};
|
|
137
147
|
return { kind: 'ReturnStatement', values: [number], start: at.start, end: at.start };
|
|
138
148
|
}
|
|
149
|
+
/** The variables of a loop, `(todo, i)`, as the parameters of its blocks and of its key. */
|
|
150
|
+
function loopParameters(loop) {
|
|
151
|
+
return [loop.value, ...(loop.key ? [loop.key] : [])].map(parameter);
|
|
152
|
+
}
|
|
139
153
|
/** A loop variable as a parameter of the function that creates the loop's blocks. */
|
|
140
154
|
function parameter(name) {
|
|
141
155
|
return {
|
|
@@ -2,6 +2,13 @@ import type * as ast from '../ast.ts';
|
|
|
2
2
|
import { SimpleStatementEmitter } from './simple-statements.ts';
|
|
3
3
|
export declare abstract class ControlStatementEmitter extends SimpleStatementEmitter {
|
|
4
4
|
protected ifStatement(node: ast.IfStatement): string;
|
|
5
|
+
/**
|
|
6
|
+
* `if const v = f(); v > 0 { }` → `{ const v = f(); if (v > 0) { } }`: a block keeps the
|
|
7
|
+
* variables of the initializer to the statement, which `emit` writes.
|
|
8
|
+
*/
|
|
9
|
+
private withInit;
|
|
10
|
+
/** `outer: for (...)`: the label goes on the loop itself, after what is hoisted before it. */
|
|
11
|
+
private labeled;
|
|
5
12
|
protected loopBody(body: ast.BlockStatement): string;
|
|
6
13
|
protected forStatement(node: ast.ForStatement): void;
|
|
7
14
|
/** A statement inside a `for` header, without the `;`. */
|
|
@@ -20,8 +27,9 @@ export declare abstract class ControlStatementEmitter extends SimpleStatementEmi
|
|
|
20
27
|
* a name that cannot change.
|
|
21
28
|
*/
|
|
22
29
|
private indexLoop;
|
|
30
|
+
protected switchStatement(node: ast.SwitchStatement): void;
|
|
23
31
|
/** Each `case` ends with an implicit `break`; a case with declarations gets its own block. */
|
|
24
|
-
|
|
32
|
+
private valueSwitch;
|
|
25
33
|
/**
|
|
26
34
|
* `select`: $$select gives the index of the case that went on and what it received, and a
|
|
27
35
|
* switch runs the case; a `break` inside it leaves the select, as in Go. `default` is -1.
|
|
@@ -31,6 +39,6 @@ export declare abstract class ControlStatementEmitter extends SimpleStatementEmi
|
|
|
31
39
|
* `switch { case cond: ... }` becomes an if/else chain. A `break` inside it must leave the
|
|
32
40
|
* switch, not an enclosing loop, so then the chain gets a label to break to.
|
|
33
41
|
*/
|
|
34
|
-
|
|
42
|
+
private taglessSwitch;
|
|
35
43
|
protected tryStatement(node: ast.TryStatement): string;
|
|
36
44
|
}
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { isChan } from "../checker/channel.js";
|
|
2
|
-
import { isMap } from "../checker/iterables.js";
|
|
3
|
-
import { typedArrayOf } from "../checker/types.js";
|
|
2
|
+
import { asyncIterated, isMap } from "../checker/iterables.js";
|
|
3
|
+
import { isTopic, typedArrayOf } from "../checker/types.js";
|
|
4
4
|
import { declaredIn } from "../names.js";
|
|
5
|
-
import { containsBreak } from "../
|
|
5
|
+
import { containsBreak } from "../breaks.js";
|
|
6
6
|
import { declarationsOf, endsWithJump } from "./analysis.js";
|
|
7
7
|
import { SimpleStatementEmitter } from "./simple-statements.js";
|
|
8
8
|
import { ARROW, BINARY, POSTFIX } from "./syntax.js";
|
|
@@ -11,6 +11,9 @@ import { ARROW, BINARY, POSTFIX } from "./syntax.js";
|
|
|
11
11
|
const FIXED = new Set(['const', 'import', 'loop']);
|
|
12
12
|
export class ControlStatementEmitter extends SimpleStatementEmitter {
|
|
13
13
|
ifStatement(node) {
|
|
14
|
+
const { init } = node;
|
|
15
|
+
if (init)
|
|
16
|
+
return this.withInit(init, () => this.line(this.ifStatement({ ...node, init: null })));
|
|
14
17
|
let text = `if (${this.expression(node.condition, 0)}) ${this.blockStatement(node.consequent)}`;
|
|
15
18
|
if (node.alternate?.kind === 'IfStatement') {
|
|
16
19
|
// The condition of `else if` is evaluated only when the first one is false.
|
|
@@ -22,6 +25,20 @@ export class ControlStatementEmitter extends SimpleStatementEmitter {
|
|
|
22
25
|
}
|
|
23
26
|
return text;
|
|
24
27
|
}
|
|
28
|
+
/**
|
|
29
|
+
* `if const v = f(); v > 0 { }` → `{ const v = f(); if (v > 0) { } }`: a block keeps the
|
|
30
|
+
* variables of the initializer to the statement, which `emit` writes.
|
|
31
|
+
*/
|
|
32
|
+
withInit(init, emit) {
|
|
33
|
+
return this.block(() => this.withScope(declarationsOf([init]), () => {
|
|
34
|
+
this.statement(init);
|
|
35
|
+
this.withHoisting(true, emit);
|
|
36
|
+
}));
|
|
37
|
+
}
|
|
38
|
+
/** `outer: for (...)`: the label goes on the loop itself, after what is hoisted before it. */
|
|
39
|
+
labeled(node, text) {
|
|
40
|
+
return node.label ? `${this.label(node.label)}: ${text}` : text;
|
|
41
|
+
}
|
|
25
42
|
loopBody(body) {
|
|
26
43
|
return this.withBreakTarget(null, () => this.blockStatement(body));
|
|
27
44
|
}
|
|
@@ -37,10 +54,10 @@ export class ControlStatementEmitter extends SimpleStatementEmitter {
|
|
|
37
54
|
node.update ? this.simpleStatement(node.update) : '',
|
|
38
55
|
]);
|
|
39
56
|
if (node.init === null && node.update === null) {
|
|
40
|
-
this.line(`while (${condition || 'true'}) ${this.loopBody(node.body)}`);
|
|
57
|
+
this.line(this.labeled(node, `while (${condition || 'true'}) ${this.loopBody(node.body)}`));
|
|
41
58
|
return;
|
|
42
59
|
}
|
|
43
|
-
this.line(`for (${init}; ${condition}; ${update}) ${this.loopBody(node.body)}`);
|
|
60
|
+
this.line(this.labeled(node, `for (${init}; ${condition}; ${update}) ${this.loopBody(node.body)}`));
|
|
44
61
|
});
|
|
45
62
|
}
|
|
46
63
|
/** A statement inside a `for` header, without the `;`. */
|
|
@@ -57,7 +74,7 @@ export class ControlStatementEmitter extends SimpleStatementEmitter {
|
|
|
57
74
|
case 'ExpressionStatement':
|
|
58
75
|
return this.expression(node.expression, 0);
|
|
59
76
|
case 'SendStatement':
|
|
60
|
-
return
|
|
77
|
+
return this.send(node);
|
|
61
78
|
}
|
|
62
79
|
}
|
|
63
80
|
/**
|
|
@@ -72,9 +89,14 @@ export class ControlStatementEmitter extends SimpleStatementEmitter {
|
|
|
72
89
|
const value = this.name(node.value.name);
|
|
73
90
|
const iterable = this.options.types?.get(node.iterable);
|
|
74
91
|
let head;
|
|
75
|
-
//
|
|
76
|
-
if (iterable &&
|
|
77
|
-
this.line(`for await (const ${value} of
|
|
92
|
+
// An async iterable, such as a stream of Node, gives its values as their Promises do.
|
|
93
|
+
if (iterable && asyncIterated(iterable) !== null) {
|
|
94
|
+
this.line(this.labeled(node, `for await (const ${value} of ${this.expression(node.iterable, POSTFIX)}) ${this.loopBody(node.body)}`));
|
|
95
|
+
return;
|
|
96
|
+
}
|
|
97
|
+
// A channel gives its values as they come, until it is closed, and so does a topic.
|
|
98
|
+
if (iterable && (isChan(iterable) || isTopic(iterable))) {
|
|
99
|
+
this.line(this.labeled(node, `for await (const ${value} of $$Channel.range(${this.receivedFrom(node.iterable)})) ${this.loopBody(node.body)}`));
|
|
78
100
|
return;
|
|
79
101
|
}
|
|
80
102
|
// An iterator is a generator function: called, it gives a lazy JS iterator, of pairs if its
|
|
@@ -117,7 +139,7 @@ export class ControlStatementEmitter extends SimpleStatementEmitter {
|
|
|
117
139
|
else {
|
|
118
140
|
head = `const ${this.pair(node.key, node.value)} of ${this.expression(node.iterable, POSTFIX)}.entries()`;
|
|
119
141
|
}
|
|
120
|
-
this.line(`for (${head}) ${this.loopBody(node.body)}`);
|
|
142
|
+
this.line(this.labeled(node, `for (${head}) ${this.loopBody(node.body)}`));
|
|
121
143
|
});
|
|
122
144
|
}
|
|
123
145
|
/** `[i, v]` of a loop over pairs; `_` leaves its place empty. */
|
|
@@ -148,10 +170,20 @@ export class ControlStatementEmitter extends SimpleStatementEmitter {
|
|
|
148
170
|
else
|
|
149
171
|
this.blockStatements(node.body.body);
|
|
150
172
|
}));
|
|
151
|
-
this.line(`for (let ${key} = 0${init}; ${key} < ${items}.length; ${key}++) ${body}`);
|
|
173
|
+
this.line(this.labeled(node, `for (let ${key} = 0${init}; ${key} < ${items}.length; ${key}++) ${body}`));
|
|
174
|
+
}
|
|
175
|
+
switchStatement(node) {
|
|
176
|
+
const { init, discriminant } = node;
|
|
177
|
+
if (init) {
|
|
178
|
+
this.line(this.withInit(init, () => this.switchStatement({ ...node, init: null })));
|
|
179
|
+
}
|
|
180
|
+
else if (discriminant === null)
|
|
181
|
+
this.taglessSwitch(node);
|
|
182
|
+
else
|
|
183
|
+
this.valueSwitch(node, discriminant);
|
|
152
184
|
}
|
|
153
185
|
/** Each `case` ends with an implicit `break`; a case with declarations gets its own block. */
|
|
154
|
-
|
|
186
|
+
valueSwitch(node, discriminant) {
|
|
155
187
|
const head = `switch (${this.expression(discriminant, 0)})`;
|
|
156
188
|
const body = this.withBreakTarget(null, () => this.block(() => {
|
|
157
189
|
node.cases.forEach((switchCase, i) => {
|
|
@@ -178,7 +210,7 @@ export class ControlStatementEmitter extends SimpleStatementEmitter {
|
|
|
178
210
|
}
|
|
179
211
|
});
|
|
180
212
|
}));
|
|
181
|
-
this.line(`${head} ${body}`);
|
|
213
|
+
this.line(this.labeled(node, `${head} ${body}`));
|
|
182
214
|
}
|
|
183
215
|
/**
|
|
184
216
|
* `select`: $$select gives the index of the case that went on and what it received, and a
|
|
@@ -189,7 +221,10 @@ export class ControlStatementEmitter extends SimpleStatementEmitter {
|
|
|
189
221
|
if (operation === null)
|
|
190
222
|
return [];
|
|
191
223
|
if (operation.kind === 'UnaryExpression') {
|
|
192
|
-
|
|
224
|
+
const zero = this.receivedZero(operation.argument);
|
|
225
|
+
return [
|
|
226
|
+
`{ channel: ${this.receivedFrom(operation.argument)}${zero !== 'null' ? `, zero: ${zero}` : ''} }`,
|
|
227
|
+
];
|
|
193
228
|
}
|
|
194
229
|
const channel = this.expression(operation.channel, ARROW);
|
|
195
230
|
return [`{ channel: ${channel}, value: ${this.expression(operation.value, ARROW)} }`];
|
|
@@ -197,7 +232,7 @@ export class ControlStatementEmitter extends SimpleStatementEmitter {
|
|
|
197
232
|
const hasDefault = node.cases.some((selectCase) => selectCase.operation === null);
|
|
198
233
|
let index = 0;
|
|
199
234
|
const select = this.block(() => {
|
|
200
|
-
this.line(`const [$$case, $$received, $$ok] = await $$select([${operations.join(', ')}], ${hasDefault});`);
|
|
235
|
+
this.line(`const [$$case, $$received, $$ok] = await $$Channel.select([${operations.join(', ')}], ${hasDefault});`);
|
|
201
236
|
const body = this.withBreakTarget(null, () => this.block(() => {
|
|
202
237
|
for (const { names, operation, body: statements } of node.cases) {
|
|
203
238
|
const label = operation === null ? 'default:' : `case ${index++}:`;
|
|
@@ -223,7 +258,7 @@ export class ControlStatementEmitter extends SimpleStatementEmitter {
|
|
|
223
258
|
}));
|
|
224
259
|
this.line(`switch ($$case) ${body}`);
|
|
225
260
|
});
|
|
226
|
-
this.line(select);
|
|
261
|
+
this.line(this.labeled(node, select));
|
|
227
262
|
}
|
|
228
263
|
/**
|
|
229
264
|
* `switch { case cond: ... }` becomes an if/else chain. A `break` inside it must leave the
|
|
@@ -232,9 +267,11 @@ export class ControlStatementEmitter extends SimpleStatementEmitter {
|
|
|
232
267
|
taglessSwitch(node) {
|
|
233
268
|
const cases = node.cases.filter((switchCase) => switchCase.tests.length > 0);
|
|
234
269
|
const fallback = node.cases.find((switchCase) => switchCase.tests.length === 0);
|
|
235
|
-
const label = node.
|
|
236
|
-
?
|
|
237
|
-
:
|
|
270
|
+
const label = node.label
|
|
271
|
+
? this.label(node.label)
|
|
272
|
+
: node.cases.some((switchCase) => containsBreak(switchCase.body))
|
|
273
|
+
? `$$switch${++this.labels}`
|
|
274
|
+
: null;
|
|
238
275
|
const text = this.withBreakTarget(label, () => {
|
|
239
276
|
const caseBlock = (body) => this.block(() => this.blockStatements(body));
|
|
240
277
|
const branches = cases.map((switchCase) => {
|
package/dist/codegen/creation.js
CHANGED
|
@@ -70,11 +70,14 @@ export class CreationEmitter extends AttributeEmitter {
|
|
|
70
70
|
const classes = `${value?.value ?? ''} ${scope}`.trim();
|
|
71
71
|
return { attribute: svg, key, value: JSON.stringify(classes) };
|
|
72
72
|
}
|
|
73
|
-
const nullable = this.typeOf(value)?.kind !== 'string';
|
|
74
73
|
return this.valueEntry(element, svg, key, value, live, (text) => {
|
|
75
|
-
return `\`\${${
|
|
74
|
+
return `\`\${${this.classText(value, text)}} ${scope}\``;
|
|
76
75
|
});
|
|
77
76
|
}
|
|
77
|
+
if (name === 'class' && value !== null && this.isClassList(value)) {
|
|
78
|
+
const key = svg ? 'class' : 'className';
|
|
79
|
+
return this.valueEntry(element, svg, key, value, live, (text) => `$$classes(${text})`);
|
|
80
|
+
}
|
|
78
81
|
// SVG attributes are set with setAttribute: their properties are objects.
|
|
79
82
|
const property = svg ? null : domProperty(tag, name);
|
|
80
83
|
if (value === null) {
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type * as ast from '../ast.ts';
|
|
2
|
+
import { CallEmitter } from './calls.ts';
|
|
3
|
+
export declare abstract class DecodeEmitter extends CallEmitter {
|
|
4
|
+
/** The constants of the object types that decode checks against, by type, in order. */
|
|
5
|
+
private readonly decodeTypes;
|
|
6
|
+
/** `$$decodeJson(text, type)` for a string, `$$decode(value, type)` for a value from JS. */
|
|
7
|
+
protected decode(node: ast.DecodeExpression): string;
|
|
8
|
+
/** The constants of the object types, for the start of the module. */
|
|
9
|
+
protected decodeTypeLines(): string[];
|
|
10
|
+
/** A type as runtime/decode.js has it: `"string"`, `{ array: "number" }`, `$$typeUser`. */
|
|
11
|
+
private shape;
|
|
12
|
+
/** The constant of an object type, made the first time it is used. */
|
|
13
|
+
private objectType;
|
|
14
|
+
}
|