rooklang 0.3.1 → 0.3.3
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 +17 -4
- package/README.ru.md +17 -4
- package/dist/ast/expressions.d.ts +16 -1
- package/dist/ast/markup.d.ts +6 -1
- package/dist/browser.d.ts +5 -0
- package/dist/browser.js +1 -0
- package/dist/checker/assignments.d.ts +5 -0
- package/dist/checker/assignments.js +34 -8
- package/dist/checker/base.d.ts +2 -1
- package/dist/checker/base.js +4 -10
- package/dist/checker/builtins.js +4 -35
- package/dist/checker/bytes.d.ts +2 -2
- package/dist/checker/bytes.js +2 -2
- package/dist/checker/channels.d.ts +3 -0
- package/dist/checker/channels.js +39 -1
- package/dist/checker/context.d.ts +6 -0
- package/dist/checker/control-flow.d.ts +1 -0
- package/dist/checker/control-flow.js +17 -0
- package/dist/checker/error-results.d.ts +6 -0
- package/dist/checker/error-results.js +31 -0
- package/dist/checker/expressions.d.ts +2 -0
- package/dist/checker/expressions.js +4 -0
- package/dist/checker/helpers.js +2 -0
- package/dist/checker/queues.d.ts +6 -0
- package/dist/checker/queues.js +24 -0
- package/dist/checker/resolution.js +6 -2
- package/dist/checker/statements.d.ts +5 -0
- package/dist/checker/statements.js +19 -0
- package/dist/checker/transfer.js +4 -1
- package/dist/checker/types/printing.js +7 -0
- package/dist/checker/types/queue.d.ts +6 -0
- package/dist/checker/types/queue.js +42 -0
- package/dist/checker/types/relations.js +12 -1
- package/dist/checker/types.d.ts +1 -0
- package/dist/checker/types.js +1 -0
- package/dist/checker/untyped-globals.d.ts +1 -0
- package/dist/checker/untyped-globals.js +47 -0
- package/dist/codegen/attributes.js +0 -1
- package/dist/codegen/calls.d.ts +9 -0
- package/dist/codegen/calls.js +41 -1
- package/dist/codegen/component-values.js +0 -1
- package/dist/codegen/components.js +0 -1
- package/dist/codegen/control-flow.js +0 -3
- package/dist/codegen/control-statements.d.ts +9 -1
- package/dist/codegen/control-statements.js +51 -7
- package/dist/codegen/creation.d.ts +6 -8
- package/dist/codegen/creation.js +1 -10
- package/dist/codegen/early-returns.js +0 -1
- package/dist/codegen/elements.js +0 -3
- package/dist/codegen/emitter.d.ts +3 -5
- package/dist/codegen/emitter.js +0 -2
- package/dist/codegen/expressions.d.ts +4 -1
- package/dist/codegen/expressions.js +9 -13
- package/dist/codegen/functions.js +0 -2
- package/dist/codegen/js.d.ts +0 -7
- package/dist/codegen/js.js +33 -59
- package/dist/codegen/parallel.js +2 -4
- package/dist/codegen/refs.js +0 -1
- package/dist/codegen/runtime.d.ts +13 -0
- package/dist/codegen/runtime.js +93 -0
- package/dist/codegen/simple-statements.js +3 -1
- package/dist/codegen/styles.d.ts +1 -1
- package/dist/codegen/styles.js +2 -2
- package/dist/codegen/web-components.js +0 -1
- package/dist/compile.d.ts +7 -0
- package/dist/compile.js +1 -0
- package/dist/completion.js +2 -1
- package/dist/index.d.ts +2 -0
- package/dist/index.js +1 -0
- package/dist/loader.js +19 -6
- package/dist/parser/base.d.ts +3 -1
- package/dist/parser/expressions.js +9 -2
- package/dist/parser/literals.d.ts +7 -0
- package/dist/parser/literals.js +45 -1
- package/dist/parser/simple-statements.js +3 -0
- package/dist/parser/types.d.ts +1 -1
- package/dist/parser/types.js +10 -3
- package/dist/runtime/sources.d.ts +2 -0
- package/dist/runtime/sources.js +14 -0
- package/dist/runtime-module.d.ts +1 -0
- package/dist/runtime-module.js +3 -0
- package/dist/ts-types.d.ts +4 -0
- package/dist/ts-types.js +25 -3
- package/dist/vite.js +7 -1
- package/dist/walk-types.js +1 -0
- package/dist/walk.js +9 -0
- package/package.json +6 -4
- package/src/runtime/chan.js +277 -0
- package/src/runtime/core.js +23 -0
- package/src/runtime/go.js +340 -0
- package/src/runtime/index.js +13 -0
- package/src/runtime/markup.js +336 -0
- package/src/runtime/queue.js +118 -0
- package/src/runtime/select.js +170 -0
- package/src/runtime/streams.js +61 -0
- package/src/runtime/threads.js +240 -0
- package/src/runtime/timers.js +27 -0
- package/dist/codegen/chan-helpers.d.ts +0 -2
- package/dist/codegen/chan-helpers.js +0 -253
- package/dist/codegen/go-helpers.d.ts +0 -9
- package/dist/codegen/go-helpers.js +0 -307
- package/dist/codegen/helpers.d.ts +0 -10
- package/dist/codegen/helpers.js +0 -356
- package/dist/codegen/select-helpers.d.ts +0 -2
- package/dist/codegen/select-helpers.js +0 -135
- package/dist/codegen/thread-helpers.d.ts +0 -2
- package/dist/codegen/thread-helpers.js +0 -198
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { ChannelChecker } from "./channels.js";
|
|
2
|
+
import { isUntyped } from "./helpers.js";
|
|
3
|
+
import { arrayOf, queueOf, typeToString } from "./types.js";
|
|
4
|
+
// Queues: `queue<number>()` makes an empty one, `queue<number>(items)` one with the values of an
|
|
5
|
+
// array or of another iterable. The members of `queue T` are in types/queue.ts.
|
|
6
|
+
export class QueueChecker extends ChannelChecker {
|
|
7
|
+
checkQueue(node) {
|
|
8
|
+
const element = this.resolveType(node.element);
|
|
9
|
+
if (node.items) {
|
|
10
|
+
// An array literal gets the type of the values, so that `[]` and `[1, null]` fit.
|
|
11
|
+
const items = this.checkValue(node.items, arrayOf(element));
|
|
12
|
+
if (items.kind === 'object' && items.iterates) {
|
|
13
|
+
this.expectAssignable(items.iterates, element, node.items);
|
|
14
|
+
}
|
|
15
|
+
else if (items.kind === 'array' || isUntyped(items)) {
|
|
16
|
+
this.expectAssignable(items, arrayOf(element), node.items);
|
|
17
|
+
}
|
|
18
|
+
else {
|
|
19
|
+
this.error(`queue<T>(items) takes its first values as an array or another iterable, not ${typeToString(items)}, e.g. queue<number>([1, 2, 3])`, node.items);
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
return queueOf(element);
|
|
23
|
+
}
|
|
24
|
+
}
|
|
@@ -2,7 +2,7 @@ import { holdsBigInts, TYPED_ELEMENTS } from "../typed-arrays.js";
|
|
|
2
2
|
import { chanOf } from "./channel.js";
|
|
3
3
|
import { ClassChecker } from "./classes.js";
|
|
4
4
|
import { CONTENT } from "./dom.js";
|
|
5
|
-
import { ANY, arrayOf, BIGINT, bindParams, builtInTypedArray, classInstance, func, instantiate, isAssignable, isNullable, literal, nullable, NUMBER, substitute, typeToString, union, UNKNOWN, } from "./types.js";
|
|
5
|
+
import { ANY, arrayOf, BIGINT, bindParams, builtInTypedArray, classInstance, func, instantiate, isAssignable, isNullable, literal, nullable, NUMBER, queueOf, substitute, typeToString, union, UNKNOWN, } from "./types.js";
|
|
6
6
|
// Type annotations as types: names, aliases, generics with their arguments; and the type a variable gets from its value.
|
|
7
7
|
export class TypeResolver extends ClassChecker {
|
|
8
8
|
resolveType(node) {
|
|
@@ -15,6 +15,8 @@ export class TypeResolver extends ClassChecker {
|
|
|
15
15
|
}
|
|
16
16
|
case 'ChanType':
|
|
17
17
|
return chanOf(this.resolveType(node.element), node.direction);
|
|
18
|
+
case 'QueueType':
|
|
19
|
+
return queueOf(this.resolveType(node.element));
|
|
18
20
|
case 'NullableType':
|
|
19
21
|
return nullable(this.resolveType(node.type));
|
|
20
22
|
case 'FuncType':
|
|
@@ -79,7 +81,9 @@ export class TypeResolver extends ClassChecker {
|
|
|
79
81
|
if (entry === undefined) {
|
|
80
82
|
this.error(this.lookupValue(name.name)
|
|
81
83
|
? `"${name.name}" is a value, not a type`
|
|
82
|
-
:
|
|
84
|
+
: name.name === 'queue'
|
|
85
|
+
? 'a queue type is written with the type of its values after it, e.g. "queue number"'
|
|
86
|
+
: `unknown type "${name.name}"`, name);
|
|
83
87
|
return UNKNOWN;
|
|
84
88
|
}
|
|
85
89
|
const args = typeArgs.map((arg) => this.resolveType(arg));
|
|
@@ -23,6 +23,11 @@ export declare abstract class StatementChecker extends AssignmentChecker {
|
|
|
23
23
|
private checkDelete;
|
|
24
24
|
protected checkBlock(statements: readonly ast.Statement[]): void;
|
|
25
25
|
protected checkVariableDeclaration(node: ast.VariableDeclaration): void;
|
|
26
|
+
/**
|
|
27
|
+
* `const user, err = loadUser(id)` of `(User, error)`: `user` is not null once `err` is found to
|
|
28
|
+
* be null. Variables of `let` may be assigned apart, so only constants are linked.
|
|
29
|
+
*/
|
|
30
|
+
private linkError;
|
|
26
31
|
/** The types of the variables of a declaration: the written one, or those of their values. */
|
|
27
32
|
private initializerTypes;
|
|
28
33
|
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { guardedByError } from "./error-results.js";
|
|
1
2
|
import { AssignmentChecker } from "./assignments.js";
|
|
2
3
|
import { isUntyped, isNumeric, ownStatements } from "./helpers.js";
|
|
3
4
|
import { func, hasZeroValue, isNullable, nonNull, nullable, typeToString, UNKNOWN, } from "./types.js";
|
|
@@ -182,8 +183,26 @@ export class StatementChecker extends AssignmentChecker {
|
|
|
182
183
|
this.flow.set(binding, nonNull(type));
|
|
183
184
|
}
|
|
184
185
|
});
|
|
186
|
+
if (node.keyword === 'const' && !declared)
|
|
187
|
+
this.linkError(node, bindings);
|
|
185
188
|
});
|
|
186
189
|
}
|
|
190
|
+
/**
|
|
191
|
+
* `const user, err = loadUser(id)` of `(User, error)`: `user` is not null once `err` is found to
|
|
192
|
+
* be null. Variables of `let` may be assigned apart, so only constants are linked.
|
|
193
|
+
*/
|
|
194
|
+
linkError(node, bindings) {
|
|
195
|
+
const results = node.values.length === 1 ? this.typeOfChecked(node.values[0]) : null;
|
|
196
|
+
const error = bindings.at(-1);
|
|
197
|
+
if (results?.kind !== 'tuple' || results.types.length !== bindings.length || !error)
|
|
198
|
+
return;
|
|
199
|
+
const guarded = guardedByError(results.types).flatMap((i) => bindings[i] ?? []);
|
|
200
|
+
if (guarded.length === 0)
|
|
201
|
+
return;
|
|
202
|
+
error.guards = guarded;
|
|
203
|
+
for (const binding of guarded)
|
|
204
|
+
binding.guardedBy = error;
|
|
205
|
+
}
|
|
187
206
|
/** The types of the variables of a declaration: the written one, or those of their values. */
|
|
188
207
|
initializerTypes(node, declared) {
|
|
189
208
|
const { names, values } = node;
|
package/dist/checker/transfer.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { memberNames, typeToString } from "./types.js";
|
|
1
|
+
import { isQueue, memberNames, queueElement, typeToString } from "./types.js";
|
|
2
2
|
// What can go to a worker and back: a worker gets a copy of each value (the structured clone of
|
|
3
3
|
// postMessage), and a copy has the data of a value but not its methods.
|
|
4
4
|
/** Buffers and their views: copied whole, or moved by `transfer(x)` (channels.ts). */
|
|
@@ -66,6 +66,9 @@ export function notCopied(type, seen = new Set()) {
|
|
|
66
66
|
? null
|
|
67
67
|
: `a copy of ${typeToString(type)} would lose its class and methods`;
|
|
68
68
|
case 'object': {
|
|
69
|
+
// A queue goes as its values, and the other thread makes a queue of them (src/runtime/threads.js).
|
|
70
|
+
if (isQueue(type))
|
|
71
|
+
return inner([queueElement(type)]);
|
|
69
72
|
const name = type.instanceOf?.template.name ?? type.name;
|
|
70
73
|
if (name !== null && COPIED.has(name))
|
|
71
74
|
return inner(type.instanceOf?.args ?? []);
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { TYPED_ARRAYS, typedArrayName } from "../../typed-arrays.js";
|
|
2
2
|
import { ERROR_CLASS } from "./classes.js";
|
|
3
3
|
import { nullable } from "./constructors.js";
|
|
4
|
+
import { isQueue, queueElement } from "./queue.js";
|
|
4
5
|
// Types as text, for messages.
|
|
5
6
|
/** How deep object types without a name are printed with their members. */
|
|
6
7
|
const MAX_OBJECT_DEPTH = 3;
|
|
@@ -58,6 +59,12 @@ export function typeToString(type) {
|
|
|
58
59
|
(type.name === 'chan' || type.name === 'chan<-' || type.name === '<-chan')) {
|
|
59
60
|
return `${type.name} ${typeToString(type.instanceOf.args[0])}`;
|
|
60
61
|
}
|
|
62
|
+
// `queue (string | number)`: the parentheses that the type is written with.
|
|
63
|
+
if (isQueue(type)) {
|
|
64
|
+
const element = queueElement(type);
|
|
65
|
+
const text = typeToString(element);
|
|
66
|
+
return `queue ${element.kind === 'union' && !element.name ? `(${text})` : text}`;
|
|
67
|
+
}
|
|
61
68
|
if (type.instanceOf && type.name !== null) {
|
|
62
69
|
return `${type.name}<${type.instanceOf.args.map(typeToString).join(', ')}>`;
|
|
63
70
|
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { type ObjectType, type Type } from './model.ts';
|
|
2
|
+
export declare function queueOf(element: Type): ObjectType;
|
|
3
|
+
/** Whether a type is `queue T`. */
|
|
4
|
+
export declare function isQueue(type: Type): boolean;
|
|
5
|
+
/** The type of the values of a queue type. */
|
|
6
|
+
export declare function queueElement(queue: Type): Type;
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { func, method, nullable, property } from "./constructors.js";
|
|
2
|
+
import { instantiate } from "./generics.js";
|
|
3
|
+
import { NUMBER } from "./model.js";
|
|
4
|
+
// `queue T`, made by `queue<T>()`: values that go in and out at both ends without moving the
|
|
5
|
+
// others, as an array moves them on `shift()`. A queue type is an instance of a generic object
|
|
6
|
+
// with the members of an array that a queue keeps, and only a queue is assignable to it
|
|
7
|
+
// (relations.ts). The template is made on first use, since the modules of types import each other.
|
|
8
|
+
let template = null;
|
|
9
|
+
function queueTemplate() {
|
|
10
|
+
if (template)
|
|
11
|
+
return template;
|
|
12
|
+
const T = { kind: 'param', name: 'T' };
|
|
13
|
+
template = {
|
|
14
|
+
kind: 'object',
|
|
15
|
+
name: 'queue',
|
|
16
|
+
members: new Map([
|
|
17
|
+
['length', property(NUMBER)],
|
|
18
|
+
['push', method(func([], [NUMBER], { rest: T }))],
|
|
19
|
+
['unshift', method(func([], [NUMBER], { rest: T }))],
|
|
20
|
+
// An empty queue gives null, as `pop()` of an empty array gives undefined.
|
|
21
|
+
['shift', method(func([], [nullable(T)]))],
|
|
22
|
+
['pop', method(func([], [nullable(T)]))],
|
|
23
|
+
['at', method(func([NUMBER], [nullable(T)]))],
|
|
24
|
+
['clear', method(func([], []))],
|
|
25
|
+
]),
|
|
26
|
+
call: null,
|
|
27
|
+
iterates: T,
|
|
28
|
+
typeParams: [T],
|
|
29
|
+
};
|
|
30
|
+
return template;
|
|
31
|
+
}
|
|
32
|
+
export function queueOf(element) {
|
|
33
|
+
return instantiate(queueTemplate(), [element]);
|
|
34
|
+
}
|
|
35
|
+
/** Whether a type is `queue T`. */
|
|
36
|
+
export function isQueue(type) {
|
|
37
|
+
return type.kind === 'object' && template !== null && type.instanceOf?.template === template;
|
|
38
|
+
}
|
|
39
|
+
/** The type of the values of a queue type. */
|
|
40
|
+
export function queueElement(queue) {
|
|
41
|
+
return queue.instanceOf.args[0];
|
|
42
|
+
}
|
|
@@ -3,6 +3,7 @@ import { memberNames } from "./lazy.js";
|
|
|
3
3
|
import { isNullable, literalBase, nonNull, union } from "./constructors.js";
|
|
4
4
|
import { UNKNOWN, VOID, } from "./model.js";
|
|
5
5
|
import { typedArrayOf, typeToString } from "./printing.js";
|
|
6
|
+
import { isQueue, queueElement } from "./queue.js";
|
|
6
7
|
// Relations between types: assignability, comparison, equality, common types.
|
|
7
8
|
/** Whether a value of type `source` can be used where `target` is expected. */
|
|
8
9
|
export function isAssignable(source, target) {
|
|
@@ -75,6 +76,10 @@ export function assignable(source, target, assuming) {
|
|
|
75
76
|
return missingMember(source, members, nested) === null;
|
|
76
77
|
}
|
|
77
78
|
case 'object': {
|
|
79
|
+
// Only a queue is a queue, as only an array is an array, and its values are checked as theirs.
|
|
80
|
+
if (isQueue(target)) {
|
|
81
|
+
return isQueue(source) && assignable(queueElement(source), queueElement(target), nested);
|
|
82
|
+
}
|
|
78
83
|
// A channel both takes and gives values, so `chan number` is not a `chan ?number`, as in Go;
|
|
79
84
|
// `chan any` is any channel, for `close(ch)`. A `chan T` is also a channel of one
|
|
80
85
|
// direction, `chan<- T` or `<-chan T`, but not the other way round.
|
|
@@ -201,10 +206,13 @@ function missingMember(source, members, assuming) {
|
|
|
201
206
|
}
|
|
202
207
|
/** Why `source` is not assignable to an object or class `target`, e.g. `missing field "y"`. */
|
|
203
208
|
export function explainMismatch(source, target) {
|
|
204
|
-
// `Stack<number>` and `Stack<string>`: the type arguments already say what differs
|
|
209
|
+
// `Stack<number>` and `Stack<string>`: the type arguments already say what differs; only a queue
|
|
210
|
+
// is a queue.
|
|
205
211
|
if (source.kind === 'class' && target.kind === 'class' && source.info === target.info) {
|
|
206
212
|
return null;
|
|
207
213
|
}
|
|
214
|
+
if (isQueue(target))
|
|
215
|
+
return null;
|
|
208
216
|
const members = target.kind === 'object'
|
|
209
217
|
? target.members
|
|
210
218
|
: target.kind === 'class'
|
|
@@ -288,6 +296,9 @@ export function hasZeroValue(type) {
|
|
|
288
296
|
case 'any':
|
|
289
297
|
case 'unknown':
|
|
290
298
|
return true;
|
|
299
|
+
// An empty queue, as `[]` is the zero value of an array.
|
|
300
|
+
case 'object':
|
|
301
|
+
return isQueue(type);
|
|
291
302
|
default:
|
|
292
303
|
return false;
|
|
293
304
|
}
|
package/dist/checker/types.d.ts
CHANGED
package/dist/checker/types.js
CHANGED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const UNTYPED_GLOBALS: readonly string[];
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
// JS globals that the checker knows by name only, without types: everything about them is `any`.
|
|
2
|
+
// With TypeScript's lib files (in Node) the library types them; the browser compiler of the
|
|
3
|
+
// playground has only these names.
|
|
4
|
+
export const UNTYPED_GLOBALS = [
|
|
5
|
+
'Object',
|
|
6
|
+
'Array',
|
|
7
|
+
'Date',
|
|
8
|
+
'RegExp',
|
|
9
|
+
'Map',
|
|
10
|
+
'Set',
|
|
11
|
+
'WeakMap',
|
|
12
|
+
'WeakSet',
|
|
13
|
+
'Promise',
|
|
14
|
+
'Symbol',
|
|
15
|
+
'Reflect',
|
|
16
|
+
'Proxy',
|
|
17
|
+
'Intl',
|
|
18
|
+
'globalThis',
|
|
19
|
+
'process',
|
|
20
|
+
'Buffer',
|
|
21
|
+
'URL',
|
|
22
|
+
'URLSearchParams',
|
|
23
|
+
'TextEncoder',
|
|
24
|
+
'TextDecoder',
|
|
25
|
+
// Streams, and what makes them: chan(stream) and stream(ch) work with them.
|
|
26
|
+
'ReadableStream',
|
|
27
|
+
'WritableStream',
|
|
28
|
+
'TransformStream',
|
|
29
|
+
'TextEncoderStream',
|
|
30
|
+
'TextDecoderStream',
|
|
31
|
+
'Blob',
|
|
32
|
+
'Response',
|
|
33
|
+
'Request',
|
|
34
|
+
'Headers',
|
|
35
|
+
'AbortController',
|
|
36
|
+
'fetch',
|
|
37
|
+
'structuredClone',
|
|
38
|
+
'queueMicrotask',
|
|
39
|
+
'window',
|
|
40
|
+
'document',
|
|
41
|
+
'navigator',
|
|
42
|
+
'location',
|
|
43
|
+
'localStorage',
|
|
44
|
+
'sessionStorage',
|
|
45
|
+
'crypto',
|
|
46
|
+
'performance',
|
|
47
|
+
];
|
|
@@ -88,7 +88,6 @@ export class AttributeEmitter extends UpdateEmitter {
|
|
|
88
88
|
this.setLive(live, value, (text) => `${element}.setAttribute(${quoted}, ${text});`);
|
|
89
89
|
}
|
|
90
90
|
else if (live && live.dependencies(value).size > 0) {
|
|
91
|
-
this.helpers.add('attribute');
|
|
92
91
|
this.setLive(live, value, (text) => `$$attribute(${element}, ${quoted}, ${text});`);
|
|
93
92
|
}
|
|
94
93
|
else {
|
package/dist/codegen/calls.d.ts
CHANGED
|
@@ -19,4 +19,13 @@ export declare abstract class CallEmitter extends ExpressionEmitter {
|
|
|
19
19
|
*/
|
|
20
20
|
protected make(node: ast.MakeExpression): string;
|
|
21
21
|
protected conversion(node: ast.ConversionExpression): string;
|
|
22
|
+
/** `chan<number>(10)`: the channel keeps the zero value that a closed one gives. */
|
|
23
|
+
protected chan(node: ast.ChanExpression): string;
|
|
24
|
+
/** `queue<number>(items)`: the queue copies the values, as `[...items]` would. */
|
|
25
|
+
protected queue(node: ast.QueueExpression): string;
|
|
26
|
+
/**
|
|
27
|
+
* `chan(stream, size)`: the channel keeps the zero value of its values, as `chan<T>(size)` does,
|
|
28
|
+
* from the type of the stream that the checker found; without it, null.
|
|
29
|
+
*/
|
|
30
|
+
protected streamChan(node: ast.StreamChanExpression): string;
|
|
22
31
|
}
|
package/dist/codegen/calls.js
CHANGED
|
@@ -97,10 +97,50 @@ export class CallEmitter extends ExpressionEmitter {
|
|
|
97
97
|
const bytes = size === 1
|
|
98
98
|
? this.expression(node.length, ARROW)
|
|
99
99
|
: `${size} * ${this.expression(node.length, BINARY['*'] + 1)}`;
|
|
100
|
-
this.helpers.add('shared');
|
|
101
100
|
return `new ${typed}($$shared(${bytes}))`;
|
|
102
101
|
}
|
|
103
102
|
conversion(node) {
|
|
104
103
|
return `new TextEncoder().encode(${this.expression(node.argument, 0)})`;
|
|
105
104
|
}
|
|
105
|
+
/** `chan<number>(10)`: the channel keeps the zero value that a closed one gives. */
|
|
106
|
+
chan(node) {
|
|
107
|
+
const size = node.size ? this.expression(node.size, ARROW) : '0';
|
|
108
|
+
return `$$chan(${size}, ${this.zeroValue(node.element) ?? 'null'})`;
|
|
109
|
+
}
|
|
110
|
+
/** `queue<number>(items)`: the queue copies the values, as `[...items]` would. */
|
|
111
|
+
queue(node) {
|
|
112
|
+
return `new $$Queue(${node.items ? this.expression(node.items, ARROW) : ''})`;
|
|
113
|
+
}
|
|
114
|
+
/**
|
|
115
|
+
* `chan(stream, size)`: the channel keeps the zero value of its values, as `chan<T>(size)` does,
|
|
116
|
+
* from the type of the stream that the checker found; without it, null.
|
|
117
|
+
*/
|
|
118
|
+
streamChan(node) {
|
|
119
|
+
const stream = this.expression(node.stream, ARROW);
|
|
120
|
+
const size = node.size ? this.expression(node.size, ARROW) : '0';
|
|
121
|
+
const type = this.options.types?.get(node);
|
|
122
|
+
const channel = type?.kind === 'tuple' ? type.types[0] : undefined;
|
|
123
|
+
const element = channel?.kind === 'object' ? channel.instanceOf?.args[0] : undefined;
|
|
124
|
+
return `$$fromStream(${stream}, ${size}, ${element ? zeroOf(element) : 'null'})`;
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
/** The zero value of a type, as zeroValue gives for a type as written. */
|
|
128
|
+
function zeroOf(type) {
|
|
129
|
+
const typed = typedArrayOf(type);
|
|
130
|
+
if (typed !== null)
|
|
131
|
+
return `new ${typed}()`;
|
|
132
|
+
switch (type.kind) {
|
|
133
|
+
case 'number':
|
|
134
|
+
return '0';
|
|
135
|
+
case 'bigint':
|
|
136
|
+
return '0n';
|
|
137
|
+
case 'string':
|
|
138
|
+
return '""';
|
|
139
|
+
case 'bool':
|
|
140
|
+
return 'false';
|
|
141
|
+
case 'array':
|
|
142
|
+
return '[]';
|
|
143
|
+
default:
|
|
144
|
+
return 'null';
|
|
145
|
+
}
|
|
106
146
|
}
|
|
@@ -52,7 +52,6 @@ export class ComponentValueEmitter extends ComponentPropsEmitter {
|
|
|
52
52
|
finally {
|
|
53
53
|
this.fixedComponents.delete(node);
|
|
54
54
|
}
|
|
55
|
-
this.helpers.add('dynamic');
|
|
56
55
|
this.line(`const [${name}, ${update}] = $$dynamic(${this.liveFunction(value)}, ${create});`);
|
|
57
56
|
live.depend(live.dependencies(node), `${update}();`);
|
|
58
57
|
return name;
|
|
@@ -26,19 +26,16 @@ export class ControlFlowEmitter extends ElementEmitter {
|
|
|
26
26
|
const [choose, blocks] = statement.kind === 'IfStatement'
|
|
27
27
|
? this.ifBranches(statement, live)
|
|
28
28
|
: this.switchBranches(statement, live);
|
|
29
|
-
this.helpers.add('branches');
|
|
30
29
|
call = `$$branches(${choose}, [${blocks.join(', ')}])`;
|
|
31
30
|
break;
|
|
32
31
|
}
|
|
33
32
|
case 'ForInStatement': {
|
|
34
|
-
this.helpers.add('list');
|
|
35
33
|
const items = this.liveFunction(statement.iterable);
|
|
36
34
|
call = `$$list(${items}, ${this.blockCreator(statement.body.body, live, statement)})`;
|
|
37
35
|
break;
|
|
38
36
|
}
|
|
39
37
|
case 'ForStatement':
|
|
40
38
|
// Without items to find the blocks by, the loop runs again and creates new content.
|
|
41
|
-
this.helpers.add('content');
|
|
42
39
|
call = `$$content(${this.loopContent(statement)})`;
|
|
43
40
|
break;
|
|
44
41
|
}
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import type * as ast from '../ast.ts';
|
|
2
2
|
import { SimpleStatementEmitter } from './simple-statements.ts';
|
|
3
|
-
/** if, for, switch and try. */
|
|
4
3
|
export declare abstract class ControlStatementEmitter extends SimpleStatementEmitter {
|
|
5
4
|
protected ifStatement(node: ast.IfStatement): string;
|
|
6
5
|
protected loopBody(body: ast.BlockStatement): string;
|
|
@@ -12,6 +11,15 @@ export declare abstract class ControlStatementEmitter extends SimpleStatementEmi
|
|
|
12
11
|
* `for i, x in xs` iterates `xs.entries()`, which gives the keys and values of a Map too.
|
|
13
12
|
*/
|
|
14
13
|
protected forInStatement(node: ast.ForInStatement): void;
|
|
14
|
+
/** `[i, v]` of a loop over pairs; `_` leaves its place empty. */
|
|
15
|
+
private pair;
|
|
16
|
+
/**
|
|
17
|
+
* `for i, v in items` over an array or a typed array: a loop over the indexes, which runs faster
|
|
18
|
+
* than one over the pairs of `entries()`. The variables of the loop cannot be assigned, so `i` is
|
|
19
|
+
* the counter itself. The array is read once, as `entries()` reads it, into `$$items` unless it is
|
|
20
|
+
* a name that cannot change.
|
|
21
|
+
*/
|
|
22
|
+
private indexLoop;
|
|
15
23
|
/** Each `case` ends with an implicit `break`; a case with declarations gets its own block. */
|
|
16
24
|
protected switchStatement(node: ast.SwitchStatement, discriminant: ast.Expression): void;
|
|
17
25
|
/**
|
|
@@ -1,10 +1,14 @@
|
|
|
1
1
|
import { isChan } from "../checker/channel.js";
|
|
2
2
|
import { isMap } from "../checker/iterables.js";
|
|
3
|
+
import { typedArrayOf } from "../checker/types.js";
|
|
4
|
+
import { declaredIn } from "../names.js";
|
|
3
5
|
import { containsBreak } from "../walk.js";
|
|
4
6
|
import { declarationsOf, endsWithJump } from "./analysis.js";
|
|
5
7
|
import { SimpleStatementEmitter } from "./simple-statements.js";
|
|
6
8
|
import { ARROW, BINARY, POSTFIX } from "./syntax.js";
|
|
7
9
|
/** if, for, switch and try. */
|
|
10
|
+
/** Names that keep their value, so a loop over the indexes may read the array through them. */
|
|
11
|
+
const FIXED = new Set(['const', 'import', 'loop']);
|
|
8
12
|
export class ControlStatementEmitter extends SimpleStatementEmitter {
|
|
9
13
|
ifStatement(node) {
|
|
10
14
|
let text = `if (${this.expression(node.condition, 0)}) ${this.blockStatement(node.consequent)}`;
|
|
@@ -53,7 +57,6 @@ export class ControlStatementEmitter extends SimpleStatementEmitter {
|
|
|
53
57
|
case 'ExpressionStatement':
|
|
54
58
|
return this.expression(node.expression, 0);
|
|
55
59
|
case 'SendStatement':
|
|
56
|
-
this.helpers.add('chan');
|
|
57
60
|
return `await $$send(${this.expression(node.channel, ARROW)}, ${this.expression(node.value, ARROW)})`;
|
|
58
61
|
}
|
|
59
62
|
}
|
|
@@ -71,7 +74,6 @@ export class ControlStatementEmitter extends SimpleStatementEmitter {
|
|
|
71
74
|
let head;
|
|
72
75
|
// A channel gives its values as they come, until it is closed.
|
|
73
76
|
if (iterable && isChan(iterable)) {
|
|
74
|
-
this.helpers.add('chan');
|
|
75
77
|
this.line(`for await (const ${value} of $$range(${this.expression(node.iterable, ARROW)})) ${this.loopBody(node.body)}`);
|
|
76
78
|
return;
|
|
77
79
|
}
|
|
@@ -92,19 +94,62 @@ export class ControlStatementEmitter extends SimpleStatementEmitter {
|
|
|
92
94
|
if (iterable && isMap(iterable))
|
|
93
95
|
values += '.values()';
|
|
94
96
|
else if (untyped) {
|
|
95
|
-
this.helpers.add('values');
|
|
96
97
|
values = `$$values(${values})`;
|
|
97
98
|
}
|
|
98
99
|
head = `const ${value} of ${values}`;
|
|
99
100
|
}
|
|
101
|
+
else if (iterable &&
|
|
102
|
+
(iterable.kind === 'array' || iterable.kind === 'string' || typedArrayOf(iterable) !== null)) {
|
|
103
|
+
// Without the index, the loop goes over the values.
|
|
104
|
+
if (node.key.name === '_' && node.value.name !== '_') {
|
|
105
|
+
head = `const ${value} of ${this.expression(node.iterable, POSTFIX)}`;
|
|
106
|
+
}
|
|
107
|
+
else if (iterable.kind === 'string') {
|
|
108
|
+
// A string has no entries(): its characters, as `for...of` gives them, are numbered.
|
|
109
|
+
const pair = this.pair(node.key, node.value);
|
|
110
|
+
head = `const ${pair} of Array.from(${this.expression(node.iterable, ARROW)}).entries()`;
|
|
111
|
+
}
|
|
112
|
+
else {
|
|
113
|
+
this.indexLoop(node, node.key);
|
|
114
|
+
return;
|
|
115
|
+
}
|
|
116
|
+
}
|
|
100
117
|
else {
|
|
101
|
-
|
|
102
|
-
const pair = node.value.name === '_' ? `[${key}]` : `[${key}, ${value}]`;
|
|
103
|
-
head = `const ${pair} of ${this.expression(node.iterable, POSTFIX)}.entries()`;
|
|
118
|
+
head = `const ${this.pair(node.key, node.value)} of ${this.expression(node.iterable, POSTFIX)}.entries()`;
|
|
104
119
|
}
|
|
105
120
|
this.line(`for (${head}) ${this.loopBody(node.body)}`);
|
|
106
121
|
});
|
|
107
122
|
}
|
|
123
|
+
/** `[i, v]` of a loop over pairs; `_` leaves its place empty. */
|
|
124
|
+
pair(key, value) {
|
|
125
|
+
const name = key.name === '_' ? '' : this.name(key.name);
|
|
126
|
+
return value.name === '_' ? `[${name}]` : `[${name}, ${this.name(value.name)}]`;
|
|
127
|
+
}
|
|
128
|
+
/**
|
|
129
|
+
* `for i, v in items` over an array or a typed array: a loop over the indexes, which runs faster
|
|
130
|
+
* than one over the pairs of `entries()`. The variables of the loop cannot be assigned, so `i` is
|
|
131
|
+
* the counter itself. The array is read once, as `entries()` reads it, into `$$items` unless it is
|
|
132
|
+
* a name that cannot change.
|
|
133
|
+
*/
|
|
134
|
+
indexLoop(node, keyNode) {
|
|
135
|
+
const key = keyNode.name === '_' ? '$$i' : this.name(keyNode.name);
|
|
136
|
+
const value = node.value.name === '_' ? null : this.name(node.value.name);
|
|
137
|
+
const { iterable } = node;
|
|
138
|
+
const fixed = iterable.kind === 'Identifier' && FIXED.has(this.lookup(iterable.name) ?? 'let');
|
|
139
|
+
const items = fixed ? this.expression(iterable, POSTFIX) : '$$items';
|
|
140
|
+
const init = fixed ? '' : `, $$items = ${this.expression(iterable, ARROW)}`;
|
|
141
|
+
// A declaration of the same name in the body would clash with the element's in one block.
|
|
142
|
+
const hidden = declaredIn(node.body.body).some((name) => name === keyNode.name || name === node.value.name);
|
|
143
|
+
const body = this.withBreakTarget(null, () => this.block(() => {
|
|
144
|
+
if (value !== null)
|
|
145
|
+
this.line(`const ${value} = ${items}[${key}];`);
|
|
146
|
+
if (hidden)
|
|
147
|
+
this.line(this.blockStatement(node.body));
|
|
148
|
+
else
|
|
149
|
+
this.blockStatements(node.body.body);
|
|
150
|
+
}));
|
|
151
|
+
this.line(`for (let ${key} = 0${init}; ${key} < ${items}.length; ${key}++) ${body}`);
|
|
152
|
+
}
|
|
108
153
|
/** Each `case` ends with an implicit `break`; a case with declarations gets its own block. */
|
|
109
154
|
switchStatement(node, discriminant) {
|
|
110
155
|
const head = `switch (${this.expression(discriminant, 0)})`;
|
|
@@ -140,7 +185,6 @@ export class ControlStatementEmitter extends SimpleStatementEmitter {
|
|
|
140
185
|
* switch runs the case; a `break` inside it leaves the select, as in Go. `default` is -1.
|
|
141
186
|
*/
|
|
142
187
|
selectStatement(node) {
|
|
143
|
-
this.helpers.add('chan');
|
|
144
188
|
const operations = node.cases.flatMap(({ operation }) => {
|
|
145
189
|
if (operation === null)
|
|
146
190
|
return [];
|
|
@@ -2,11 +2,11 @@ import type * as ast from '../ast.ts';
|
|
|
2
2
|
import { AttributeEmitter } from './attributes.ts';
|
|
3
3
|
import type { Live } from './reactive.ts';
|
|
4
4
|
/**
|
|
5
|
-
* The creation of elements.
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
5
|
+
* The creation of elements. An element is created by a function of the runtime together with the
|
|
6
|
+
* attributes it has from the start: `$$element("a", { href: "/", className: "link" },
|
|
7
|
+
* { "aria-label": label })` takes the place of a `createElement` and a statement for each
|
|
8
|
+
* attribute. The first object holds properties, the second attributes set with setAttribute; an
|
|
9
|
+
* SVG element has only attributes.
|
|
10
10
|
*/
|
|
11
11
|
/** Attributes given to the helper that creates an element, which need no statements of their own. */
|
|
12
12
|
export interface Creation {
|
|
@@ -15,10 +15,8 @@ export interface Creation {
|
|
|
15
15
|
scoped: boolean;
|
|
16
16
|
}
|
|
17
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
18
|
/**
|
|
21
|
-
* Writes `declaration = <a new element>`.
|
|
19
|
+
* Writes `declaration = <a new element>`. It is given the attributes before the
|
|
22
20
|
* first spread, which may override them, except those that need more than a value: handlers,
|
|
23
21
|
* `bind:`, styles, properties of web components and attributes that may be null. `scopeClass`
|
|
24
22
|
* is the class of the style for an element without a class attribute.
|
package/dist/codegen/creation.js
CHANGED
|
@@ -2,21 +2,14 @@ import { domProperty } from "../checker/dom.js";
|
|
|
2
2
|
import { AttributeEmitter } from "./attributes.js";
|
|
3
3
|
import { ARROW } from "./syntax.js";
|
|
4
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
5
|
/**
|
|
8
|
-
* Writes `declaration = <a new element>`.
|
|
6
|
+
* Writes `declaration = <a new element>`. It is given the attributes before the
|
|
9
7
|
* first spread, which may override them, except those that need more than a value: handlers,
|
|
10
8
|
* `bind:`, styles, properties of web components and attributes that may be null. `scopeClass`
|
|
11
9
|
* is the class of the style for an element without a class attribute.
|
|
12
10
|
*/
|
|
13
11
|
createWith(declaration, element, tag, svg, attributes, live, scope, scopeClass) {
|
|
14
12
|
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
13
|
const entries = [];
|
|
21
14
|
for (const attribute of attributes) {
|
|
22
15
|
if (attribute.kind === 'JsxSpreadAttribute')
|
|
@@ -48,13 +41,11 @@ export class CreationEmitter extends AttributeEmitter {
|
|
|
48
41
|
const attributesObject = object(true);
|
|
49
42
|
const args = [JSON.stringify(tag)];
|
|
50
43
|
if (svg) {
|
|
51
|
-
this.helpers.add('svgElement');
|
|
52
44
|
if (attributesObject)
|
|
53
45
|
args.push(attributesObject);
|
|
54
46
|
this.line(`${declaration} = $$svgElement(${args.join(', ')});`);
|
|
55
47
|
}
|
|
56
48
|
else {
|
|
57
|
-
this.helpers.add('element');
|
|
58
49
|
if (properties || attributesObject)
|
|
59
50
|
args.push(properties ?? '{}');
|
|
60
51
|
if (attributesObject)
|
|
@@ -94,7 +94,6 @@ export class EarlyReturnEmitter extends ComponentValueEmitter {
|
|
|
94
94
|
this.line(`${effect.name}();`);
|
|
95
95
|
if (effect.cleanups === null)
|
|
96
96
|
continue;
|
|
97
|
-
this.helpers.add('owner');
|
|
98
97
|
this.line(`$$onRemove(() => $$runDeferred(${effect.cleanups}.splice(0)));`);
|
|
99
98
|
}
|
|
100
99
|
}
|
package/dist/codegen/elements.js
CHANGED
|
@@ -93,7 +93,6 @@ export class ElementEmitter extends CreationEmitter {
|
|
|
93
93
|
this.line(`${target} = document.createDocumentFragment();`);
|
|
94
94
|
}
|
|
95
95
|
else if (preset) {
|
|
96
|
-
this.createdElements++;
|
|
97
96
|
this.line(`${target} = ${preset.name} ?? ${this.createElement(tag, svg)};`);
|
|
98
97
|
}
|
|
99
98
|
else {
|
|
@@ -217,7 +216,6 @@ export class ElementEmitter extends CreationEmitter {
|
|
|
217
216
|
}
|
|
218
217
|
case 'unknown':
|
|
219
218
|
flush();
|
|
220
|
-
this.helpers.add('append');
|
|
221
219
|
this.line(`$$append(${element}, ${this.expression(expression, ARROW)});`);
|
|
222
220
|
break;
|
|
223
221
|
}
|
|
@@ -258,7 +256,6 @@ export class ElementEmitter extends CreationEmitter {
|
|
|
258
256
|
}
|
|
259
257
|
const name = `$$content${id}`;
|
|
260
258
|
const update = `$$updateContent${id}`;
|
|
261
|
-
this.helpers.add('content');
|
|
262
259
|
this.line(`const [${name}, ${update}] = $$content(${this.liveFunction(expression)});`);
|
|
263
260
|
live.depend(sources, `${update}();`);
|
|
264
261
|
return name;
|