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,7 @@
|
|
|
1
1
|
import { instanceMembers } from "./classes.js";
|
|
2
2
|
import { arrayOf, func, nonNull, nullable, union } from "./constructors.js";
|
|
3
3
|
import { instantiate } from "./generics.js";
|
|
4
|
+
import { iteratedBy } from "./iterables.js";
|
|
4
5
|
import { memberNames } from "./lazy.js";
|
|
5
6
|
import { VOID, } from "./model.js";
|
|
6
7
|
import { typesEqual } from "./relations.js";
|
|
@@ -45,7 +46,7 @@ export function substitute(type, bindings, fallback) {
|
|
|
45
46
|
case 'param':
|
|
46
47
|
return bindings.get(type) ?? fallback ?? type;
|
|
47
48
|
case 'array':
|
|
48
|
-
return arrayOf(substitute(type.element, bindings, fallback));
|
|
49
|
+
return arrayOf(substitute(type.element, bindings, fallback), type.positions?.map((each) => substitute(each, bindings, fallback)));
|
|
49
50
|
case 'nullable':
|
|
50
51
|
return nullable(substitute(type.type, bindings, fallback));
|
|
51
52
|
case 'union':
|
|
@@ -127,6 +128,8 @@ function substituteObject(type, bindings, fallback) {
|
|
|
127
128
|
return { ...type, members, call: type.call && sub(type.call) };
|
|
128
129
|
}
|
|
129
130
|
const MAX_STRUCTURAL_DEPTH = 3;
|
|
131
|
+
/** The names of the templates of channel types (checker/channel.ts). */
|
|
132
|
+
const CHANNELS = new Set(['chan', 'chan<-', '<-chan']);
|
|
130
133
|
/** Infers type parameters in `param` from the argument type `arg`. */
|
|
131
134
|
export function inferTypeParams(param, arg, bindings, depth = 0) {
|
|
132
135
|
if (!containsTypeParam(param))
|
|
@@ -139,8 +142,15 @@ export function inferTypeParams(param, arg, bindings, depth = 0) {
|
|
|
139
142
|
}
|
|
140
143
|
break;
|
|
141
144
|
case 'array':
|
|
142
|
-
|
|
145
|
+
// `[K, V]` from `["a", 1]`: each by its position; else the union of K and V would take all.
|
|
146
|
+
if (arg.kind === 'array' &&
|
|
147
|
+
param.positions?.length === arg.positions?.length &&
|
|
148
|
+
arg.positions) {
|
|
149
|
+
param.positions.forEach((each, i) => infer(each, arg.positions[i]));
|
|
150
|
+
}
|
|
151
|
+
else if (arg.kind === 'array') {
|
|
143
152
|
infer(param.element, arg.element);
|
|
153
|
+
}
|
|
144
154
|
break;
|
|
145
155
|
case 'nullable':
|
|
146
156
|
infer(param.type, nonNull(arg));
|
|
@@ -171,12 +181,21 @@ export function inferTypeParams(param, arg, bindings, depth = 0) {
|
|
|
171
181
|
}
|
|
172
182
|
break;
|
|
173
183
|
case 'object': {
|
|
184
|
+
// A channel of one direction from any channel: `chan<- T` from a `chan number`.
|
|
174
185
|
if (param.instanceOf &&
|
|
175
186
|
arg.kind === 'object' &&
|
|
176
|
-
arg.instanceOf?.template === param.instanceOf.template
|
|
187
|
+
(arg.instanceOf?.template === param.instanceOf.template ||
|
|
188
|
+
(CHANNELS.has(param.instanceOf.template.name ?? '') &&
|
|
189
|
+
CHANNELS.has(arg.instanceOf?.template.name ?? '')))) {
|
|
177
190
|
param.instanceOf.args.forEach((each, i) => infer(each, arg.instanceOf.args[i]));
|
|
178
191
|
break;
|
|
179
192
|
}
|
|
193
|
+
// `Iterable<T>` from `[]number` or a `Set<number>`: T is what `for` gives of it.
|
|
194
|
+
const values = param.iterates && iteratedBy(arg);
|
|
195
|
+
if (values) {
|
|
196
|
+
infer(param.iterates, values);
|
|
197
|
+
break;
|
|
198
|
+
}
|
|
180
199
|
// Structurally: `{ value T }` from `{ value number }`. Types refer to themselves, and the
|
|
181
200
|
// methods of generic interfaces give ever new instances (`then` of a promise gives a
|
|
182
201
|
// promise), so only a few levels deep, as in TypeScript.
|
|
@@ -2,6 +2,7 @@ import { TYPED_ARRAYS, typedArrayName } from "../../typed-arrays.js";
|
|
|
2
2
|
import { ERROR_CLASS } from "./classes.js";
|
|
3
3
|
import { nullable } from "./constructors.js";
|
|
4
4
|
import { isQueue, queueElement } from "./queue.js";
|
|
5
|
+
import { isTopic, topicElement } from "./topic.js";
|
|
5
6
|
// Types as text, for messages.
|
|
6
7
|
/** How deep object types without a name are printed with their members. */
|
|
7
8
|
const MAX_OBJECT_DEPTH = 3;
|
|
@@ -60,10 +61,10 @@ export function typeToString(type) {
|
|
|
60
61
|
return `${type.name} ${typeToString(type.instanceOf.args[0])}`;
|
|
61
62
|
}
|
|
62
63
|
// `queue (string | number)`: the parentheses that the type is written with.
|
|
63
|
-
if (isQueue(type)) {
|
|
64
|
-
const element = queueElement(type);
|
|
64
|
+
if (isQueue(type) || isTopic(type)) {
|
|
65
|
+
const element = isQueue(type) ? queueElement(type) : topicElement(type);
|
|
65
66
|
const text = typeToString(element);
|
|
66
|
-
return
|
|
67
|
+
return `${type.name} ${element.kind === 'union' && !element.name ? `(${text})` : text}`;
|
|
67
68
|
}
|
|
68
69
|
if (type.instanceOf && type.name !== null) {
|
|
69
70
|
return `${type.name}<${type.instanceOf.args.map(typeToString).join(', ')}>`;
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import { instanceMembers, isSubclass, publicMembers } from "./classes.js";
|
|
2
|
-
import { memberNames } from "./lazy.js";
|
|
3
2
|
import { isNullable, literalBase, nonNull, union } from "./constructors.js";
|
|
4
3
|
import { UNKNOWN, VOID, } from "./model.js";
|
|
4
|
+
import { indexAssignable, iterableAssignable, numberIndexAssignable } from "./iterables.js";
|
|
5
5
|
import { typedArrayOf, typeToString } from "./printing.js";
|
|
6
6
|
import { isQueue, queueElement } from "./queue.js";
|
|
7
|
+
import { isTopic, topicElement } from "./topic.js";
|
|
7
8
|
// Relations between types: assignability, comparison, equality, common types.
|
|
8
9
|
/** Whether a value of type `source` can be used where `target` is expected. */
|
|
9
10
|
export function isAssignable(source, target) {
|
|
@@ -80,6 +81,15 @@ export function assignable(source, target, assuming) {
|
|
|
80
81
|
if (isQueue(target)) {
|
|
81
82
|
return isQueue(source) && assignable(queueElement(source), queueElement(target), nested);
|
|
82
83
|
}
|
|
84
|
+
// A topic both sends and receives, so its values are the same both ways, as a channel's;
|
|
85
|
+
// `topic any` is any topic, for `close(t)`.
|
|
86
|
+
if (isTopic(target)) {
|
|
87
|
+
const element = topicElement(target);
|
|
88
|
+
return (isTopic(source) &&
|
|
89
|
+
(element.kind === 'any' ||
|
|
90
|
+
(assignable(topicElement(source), element, nested) &&
|
|
91
|
+
assignable(element, topicElement(source), nested))));
|
|
92
|
+
}
|
|
83
93
|
// A channel both takes and gives values, so `chan number` is not a `chan ?number`, as in Go;
|
|
84
94
|
// `chan any` is any channel, for `close(ch)`. A `chan T` is also a channel of one
|
|
85
95
|
// direction, `chan<- T` or `<-chan T`, but not the other way round.
|
|
@@ -106,6 +116,10 @@ export function assignable(source, target, assuming) {
|
|
|
106
116
|
if (source.kind === 'function')
|
|
107
117
|
return target.members.size === 0;
|
|
108
118
|
}
|
|
119
|
+
// `Iterable<T>`: what `for` gives of the value fits.
|
|
120
|
+
const iterable = target.iterates && iterableAssignable(source, target, nested);
|
|
121
|
+
if (iterable !== undefined && iterable !== null)
|
|
122
|
+
return iterable;
|
|
109
123
|
if (target.index && !indexAssignable(source, target.index, nested))
|
|
110
124
|
return false;
|
|
111
125
|
if (target.numberIndex && !numberIndexAssignable(source, target.numberIndex, nested)) {
|
|
@@ -115,33 +129,12 @@ export function assignable(source, target, assuming) {
|
|
|
115
129
|
}
|
|
116
130
|
}
|
|
117
131
|
}
|
|
118
|
-
/** `{ [string]: T }` accepts an object whose own index and fields are all `T`. */
|
|
119
132
|
/** The templates of channel types by name (channel.ts): no other type can have these names. */
|
|
120
133
|
const CHAN_NAMES = new Map([
|
|
121
134
|
['chan', 'both'],
|
|
122
135
|
['chan<-', 'send'],
|
|
123
136
|
['<-chan', 'receive'],
|
|
124
137
|
]);
|
|
125
|
-
function indexAssignable(source, index, assuming) {
|
|
126
|
-
if (source.kind !== 'object')
|
|
127
|
-
return false;
|
|
128
|
-
if (source.index && !assignable(source.index, index, assuming))
|
|
129
|
-
return false;
|
|
130
|
-
return [...memberNames(source.members)].every((name) => {
|
|
131
|
-
const member = source.members.get(name);
|
|
132
|
-
return !member || member.method || assignable(member.type, index, assuming);
|
|
133
|
-
});
|
|
134
|
-
}
|
|
135
|
-
/** `ArrayLike<T>` from a `.d.ts` accepts an array, a string or a typed array of `T`. */
|
|
136
|
-
function numberIndexAssignable(source, index, assuming) {
|
|
137
|
-
if (source.kind === 'array')
|
|
138
|
-
return assignable(source.element, index, assuming);
|
|
139
|
-
if (source.kind === 'string')
|
|
140
|
-
return assignable(source, index, assuming);
|
|
141
|
-
return (source.kind === 'object' &&
|
|
142
|
-
source.numberIndex !== undefined &&
|
|
143
|
-
assignable(source.numberIndex, index, assuming));
|
|
144
|
-
}
|
|
145
138
|
function functionAssignable(source, target, assuming) {
|
|
146
139
|
// An iterator is a JS generator: it does not take the callback that a function of its shape does.
|
|
147
140
|
if (Boolean(source.iterator) !== Boolean(target.iterator))
|
|
@@ -211,7 +204,7 @@ export function explainMismatch(source, target) {
|
|
|
211
204
|
if (source.kind === 'class' && target.kind === 'class' && source.info === target.info) {
|
|
212
205
|
return null;
|
|
213
206
|
}
|
|
214
|
-
if (isQueue(target))
|
|
207
|
+
if (isQueue(target) || isTopic(target))
|
|
215
208
|
return null;
|
|
216
209
|
const members = target.kind === 'object'
|
|
217
210
|
? target.members
|
|
@@ -296,9 +289,10 @@ export function hasZeroValue(type) {
|
|
|
296
289
|
case 'any':
|
|
297
290
|
case 'unknown':
|
|
298
291
|
return true;
|
|
299
|
-
// An empty queue, as `[]` is the zero value of an array.
|
|
292
|
+
// An empty queue, as `[]` is the zero value of an array; Channel.EMPTY and Topic.EMPTY, which
|
|
293
|
+
// are closed.
|
|
300
294
|
case 'object':
|
|
301
|
-
return isQueue(type);
|
|
295
|
+
return isQueue(type) || isTopic(type) || CHAN_NAMES.has(type.instanceOf?.template.name ?? '');
|
|
302
296
|
default:
|
|
303
297
|
return false;
|
|
304
298
|
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { ObjectType, Type } from './model.ts';
|
|
2
|
+
export declare function topicOf(element: Type): ObjectType;
|
|
3
|
+
/** Whether a type is `topic T`. */
|
|
4
|
+
export declare function isTopic(type: Type): boolean;
|
|
5
|
+
/** The type of the values of a topic type. */
|
|
6
|
+
export declare function topicElement(topic: Type): Type;
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { instantiate } from "./generics.js";
|
|
2
|
+
// `topic T`, made by `topic<T>(name)`: values that go to the other tabs of a site over a
|
|
3
|
+
// BroadcastChannel. A topic type is an instance of a generic object without members, as a channel
|
|
4
|
+
// type is, written and printed by its own syntax; only a topic of the same values is assignable to
|
|
5
|
+
// it (relations.ts), since a topic both sends and receives them.
|
|
6
|
+
const TOPIC = {
|
|
7
|
+
kind: 'object',
|
|
8
|
+
name: 'topic',
|
|
9
|
+
members: new Map(),
|
|
10
|
+
call: null,
|
|
11
|
+
typeParams: [{ kind: 'param', name: 'T' }],
|
|
12
|
+
};
|
|
13
|
+
export function topicOf(element) {
|
|
14
|
+
return instantiate(TOPIC, [element]);
|
|
15
|
+
}
|
|
16
|
+
/** Whether a type is `topic T`. */
|
|
17
|
+
export function isTopic(type) {
|
|
18
|
+
return type.kind === 'object' && type.instanceOf?.template === TOPIC;
|
|
19
|
+
}
|
|
20
|
+
/** The type of the values of a topic type. */
|
|
21
|
+
export function topicElement(topic) {
|
|
22
|
+
return topic.instanceOf.args[0];
|
|
23
|
+
}
|
package/dist/checker/types.d.ts
CHANGED
|
@@ -4,7 +4,9 @@ export * from './types/lazy.ts';
|
|
|
4
4
|
export * from './types/generics.ts';
|
|
5
5
|
export * from './types/classes.ts';
|
|
6
6
|
export * from './types/relations.ts';
|
|
7
|
+
export * from './types/iterables.ts';
|
|
7
8
|
export * from './types/params.ts';
|
|
8
9
|
export * from './types/printing.ts';
|
|
9
10
|
export * from './types/bytes.ts';
|
|
10
11
|
export * from './types/queue.ts';
|
|
12
|
+
export * from './types/topic.ts';
|
package/dist/checker/types.js
CHANGED
|
@@ -5,7 +5,9 @@ export * from "./types/lazy.js";
|
|
|
5
5
|
export * from "./types/generics.js";
|
|
6
6
|
export * from "./types/classes.js";
|
|
7
7
|
export * from "./types/relations.js";
|
|
8
|
+
export * from "./types/iterables.js";
|
|
8
9
|
export * from "./types/params.js";
|
|
9
10
|
export * from "./types/printing.js";
|
|
10
11
|
export * from "./types/bytes.js";
|
|
11
12
|
export * from "./types/queue.js";
|
|
13
|
+
export * from "./types/topic.js";
|
|
@@ -32,7 +32,6 @@ export const UNTYPED_GLOBALS = [
|
|
|
32
32
|
'Response',
|
|
33
33
|
'Request',
|
|
34
34
|
'Headers',
|
|
35
|
-
'AbortController',
|
|
36
35
|
'fetch',
|
|
37
36
|
'structuredClone',
|
|
38
37
|
'queueMicrotask',
|
|
@@ -45,3 +44,6 @@ export const UNTYPED_GLOBALS = [
|
|
|
45
44
|
'crypto',
|
|
46
45
|
'performance',
|
|
47
46
|
];
|
|
47
|
+
// Classes that are names of types too, `any` in both: `new AbortController()` and
|
|
48
|
+
// `signal AbortSignal`. TypeScript's lib files replace them as they replace the DOM.
|
|
49
|
+
export const UNTYPED_CLASSES = ['AbortController', 'AbortSignal', 'ImageData'];
|
package/dist/cli.js
CHANGED
|
@@ -1,9 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { existsSync, mkdirSync,
|
|
3
|
-
import {
|
|
4
|
-
import { spawnSync } from 'node:child_process';
|
|
5
|
-
import { dirname, join } from 'node:path';
|
|
6
|
-
import { fileURLToPath } from 'node:url';
|
|
2
|
+
import { existsSync, mkdirSync, readFileSync, statSync, writeFileSync } from 'node:fs';
|
|
3
|
+
import { dirname } from 'node:path';
|
|
7
4
|
import { parseArgs } from 'node:util';
|
|
8
5
|
import { build, displayPath } from "./build.js";
|
|
9
6
|
import { CompileError, compileFile } from "./compile-file.js";
|
|
@@ -11,6 +8,7 @@ import { markupFor } from "./config.js";
|
|
|
11
8
|
import { formatDiagnostic } from "./diagnostics.js";
|
|
12
9
|
import { tokenize } from "./lexer/lexer.js";
|
|
13
10
|
import { parse } from "./parser/parser.js";
|
|
11
|
+
import { runFile } from "./run-file.js";
|
|
14
12
|
import { SourceFile } from "./source.js";
|
|
15
13
|
const { version } = JSON.parse(readFileSync(new URL('../package.json', import.meta.url), 'utf8'));
|
|
16
14
|
const HELP = `Rook ${version}
|
|
@@ -25,6 +23,8 @@ Options:
|
|
|
25
23
|
--out-dir <dir> build: write the .js files to <dir>, keeping the folder structure
|
|
26
24
|
(by default they go next to the .rk files)
|
|
27
25
|
--stdout build: print the JS of a single file instead of writing files
|
|
26
|
+
--no-source-map build: do not write the source maps, app.js.map
|
|
27
|
+
--watch run: run again when a file of the program changes
|
|
28
28
|
--no-check Skip type checking
|
|
29
29
|
--markup Turn markup and components on; by default they are on where the
|
|
30
30
|
package.json has "rook": { "markup": true }
|
|
@@ -41,6 +41,8 @@ function main() {
|
|
|
41
41
|
'out-dir': { type: 'string' },
|
|
42
42
|
stdout: { type: 'boolean' },
|
|
43
43
|
'no-check': { type: 'boolean' },
|
|
44
|
+
'no-source-map': { type: 'boolean' },
|
|
45
|
+
watch: { type: 'boolean' },
|
|
44
46
|
markup: { type: 'boolean' },
|
|
45
47
|
'no-markup': { type: 'boolean' },
|
|
46
48
|
help: { type: 'boolean', short: 'h' },
|
|
@@ -81,11 +83,16 @@ function main() {
|
|
|
81
83
|
process.stdout.write(compileFile(file, { rewriteImports: true, typeCheck, markup }).code);
|
|
82
84
|
}
|
|
83
85
|
else {
|
|
84
|
-
buildFiles(files, {
|
|
86
|
+
buildFiles(files, {
|
|
87
|
+
outDir: values['out-dir'],
|
|
88
|
+
typeCheck,
|
|
89
|
+
markup,
|
|
90
|
+
sourceMap: !values['no-source-map'],
|
|
91
|
+
});
|
|
85
92
|
}
|
|
86
93
|
break;
|
|
87
94
|
case 'run':
|
|
88
|
-
|
|
95
|
+
runFile(file, { typeCheck, markup, watch: values.watch === true });
|
|
89
96
|
break;
|
|
90
97
|
case 'tokens':
|
|
91
98
|
printTokens(new SourceFile(file, readFileSync(file, 'utf8')));
|
|
@@ -113,44 +120,19 @@ function buildFiles(entries, options) {
|
|
|
113
120
|
const messages = errors.flatMap(({ file, diagnostics }) => diagnostics.map((diagnostic) => formatDiagnostic(file, diagnostic)));
|
|
114
121
|
throw new CompileError(messages.join('\n\n'));
|
|
115
122
|
}
|
|
116
|
-
for (const { source, output, code, declarations, worker } of outputs) {
|
|
117
|
-
const extra = `${declarations ? ' + .d.ts' : ''}${worker ? ' + .worker.js' : ''}`;
|
|
123
|
+
for (const { source, output, code, declarations, worker, maps = [] } of outputs) {
|
|
124
|
+
const extra = `${declarations ? ' + .d.ts' : ''}${worker ? ' + .worker.js' : ''}${maps.length > 0 ? ' + .js.map' : ''}`;
|
|
118
125
|
mkdirSync(dirname(output), { recursive: true });
|
|
119
126
|
writeFileSync(output, code);
|
|
120
127
|
if (declarations)
|
|
121
128
|
writeFileSync(declarations.output, declarations.code);
|
|
122
129
|
if (worker)
|
|
123
130
|
writeFileSync(worker.output, worker.code);
|
|
131
|
+
for (const map of maps)
|
|
132
|
+
writeFileSync(map.output, map.code);
|
|
124
133
|
console.log(`${displayPath(source)} → ${displayPath(output)}${extra}`);
|
|
125
134
|
}
|
|
126
135
|
}
|
|
127
|
-
/**
|
|
128
|
-
* Runs a program in a process of its own that imports loader.ts first: so do the worker threads
|
|
129
|
-
* of its goroutines, which do not share the hooks of the thread that registers them. They take
|
|
130
|
-
* the compiled code from a directory of the run, removed after it.
|
|
131
|
-
*/
|
|
132
|
-
function runFile(file, typeCheck, markup) {
|
|
133
|
-
// The build rewrites the extensions of imports, not of these URLs: in the package they are .js.
|
|
134
|
-
const extension = import.meta.url.endsWith('.ts') ? '.ts' : '.js';
|
|
135
|
-
const loader = new URL(`./loader${extension}`, import.meta.url).href;
|
|
136
|
-
const run = fileURLToPath(new URL(`./run${extension}`, import.meta.url));
|
|
137
|
-
const cache = mkdtempSync(join(tmpdir(), 'rook-run-'));
|
|
138
|
-
try {
|
|
139
|
-
const { status, signal } = spawnSync(process.execPath, ['--import', loader, run, file], {
|
|
140
|
-
stdio: 'inherit',
|
|
141
|
-
env: {
|
|
142
|
-
...process.env,
|
|
143
|
-
ROOK_RUN_CACHE: cache,
|
|
144
|
-
...(typeCheck ? {} : { ROOK_NO_CHECK: '1' }),
|
|
145
|
-
...(markup === undefined ? {} : { ROOK_MARKUP: markup ? '1' : '0' }),
|
|
146
|
-
},
|
|
147
|
-
});
|
|
148
|
-
return status ?? (signal ? 1 : 0);
|
|
149
|
-
}
|
|
150
|
-
finally {
|
|
151
|
-
rmSync(cache, { recursive: true, force: true });
|
|
152
|
-
}
|
|
153
|
-
}
|
|
154
136
|
function printTokens(file) {
|
|
155
137
|
const { tokens, diagnostics } = tokenize(file.text);
|
|
156
138
|
for (const token of tokens) {
|
|
@@ -20,6 +20,10 @@ export declare abstract class AttributeEmitter extends UpdateEmitter {
|
|
|
20
20
|
protected attribute(element: string, tag: string | null, attribute: ast.JsxAttribute | ast.JsxSpreadAttribute, live: Live | null, svg?: boolean, scope?: string | null): void;
|
|
21
21
|
/** The class of an element with the class of its style: `"card rook-card-1x9k2az"`. */
|
|
22
22
|
private scopedClass;
|
|
23
|
+
/** Whether `class={...}` is a list of classes, `[styles.tab, active ? styles.active : null]`. */
|
|
24
|
+
protected isClassList(value: ast.Expression): boolean;
|
|
25
|
+
/** The classes of `class={...}` as a string, before the class of the style is added to them. */
|
|
26
|
+
protected classText(value: ast.Expression, text: string): string;
|
|
23
27
|
/** Sets the class of an element: `className`, or the attribute of an SVG element. */
|
|
24
28
|
protected scopeClass(element: string, classes: string, svg: boolean): void;
|
|
25
29
|
/** `<app-card item-count={1} />`: the property of a web component of this module, if it has one. */
|
|
@@ -3,6 +3,7 @@ import { attributeName } from "../html-tag.js";
|
|
|
3
3
|
import { SVG_NAMESPACE } from "../svg.js";
|
|
4
4
|
import { eventName, mentions } from "./element-helpers.js";
|
|
5
5
|
import { ARROW } from "./syntax.js";
|
|
6
|
+
import { rootsOf } from "./shared.js";
|
|
6
7
|
import { UpdateEmitter } from "./updates.js";
|
|
7
8
|
// Attributes of elements: properties, setAttribute, styles, `bind:` and event handlers.
|
|
8
9
|
export class AttributeEmitter extends UpdateEmitter {
|
|
@@ -46,6 +47,15 @@ export class AttributeEmitter extends UpdateEmitter {
|
|
|
46
47
|
this.scopedClass(element, value, live, svg, scope);
|
|
47
48
|
return;
|
|
48
49
|
}
|
|
50
|
+
if (name === 'class' &&
|
|
51
|
+
value !== null &&
|
|
52
|
+
value.kind !== 'EventHandler' &&
|
|
53
|
+
this.isClassList(value)) {
|
|
54
|
+
this.setLive(live, value, (text) => svg
|
|
55
|
+
? `${element}.setAttribute("class", $$classes(${text}));`
|
|
56
|
+
: `${element}.className = $$classes(${text});`);
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
49
59
|
const webProperty = tag === null || svg ? null : this.webComponentProperty(tag, name);
|
|
50
60
|
if (webProperty) {
|
|
51
61
|
this.webComponentAttribute(element, webProperty, value, live);
|
|
@@ -102,15 +112,24 @@ export class AttributeEmitter extends UpdateEmitter {
|
|
|
102
112
|
this.scopeClass(element, `${value?.value ?? ''} ${scope}`.trim(), svg);
|
|
103
113
|
}
|
|
104
114
|
else if (value.kind !== 'EventHandler') {
|
|
105
|
-
const nullable = this.typeOf(value)?.kind !== 'string';
|
|
106
115
|
this.setLive(live, value, (text) => {
|
|
107
|
-
const scoped = `\`\${${
|
|
116
|
+
const scoped = `\`\${${this.classText(value, text)}} ${scope}\``;
|
|
108
117
|
return svg
|
|
109
118
|
? `${element}.setAttribute("class", ${scoped});`
|
|
110
119
|
: `${element}.className = ${scoped};`;
|
|
111
120
|
});
|
|
112
121
|
}
|
|
113
122
|
}
|
|
123
|
+
/** Whether `class={...}` is a list of classes, `[styles.tab, active ? styles.active : null]`. */
|
|
124
|
+
isClassList(value) {
|
|
125
|
+
return value.kind === 'ArrayLiteral' || this.typeOf(value)?.kind === 'array';
|
|
126
|
+
}
|
|
127
|
+
/** The classes of `class={...}` as a string, before the class of the style is added to them. */
|
|
128
|
+
classText(value, text) {
|
|
129
|
+
if (this.isClassList(value))
|
|
130
|
+
return `$$classes(${text})`;
|
|
131
|
+
return this.typeOf(value)?.kind === 'string' ? text : `${text} ?? ""`;
|
|
132
|
+
}
|
|
114
133
|
/** Sets the class of an element: `className`, or the attribute of an SVG element. */
|
|
115
134
|
scopeClass(element, classes, svg) {
|
|
116
135
|
const text = JSON.stringify(classes);
|
|
@@ -178,6 +197,9 @@ export class AttributeEmitter extends UpdateEmitter {
|
|
|
178
197
|
try {
|
|
179
198
|
const body = this.block(() => {
|
|
180
199
|
this.line(`${target} = ${current};`);
|
|
200
|
+
for (const name of rootsOf(value, this.shared)) {
|
|
201
|
+
this.line(`$$changed(${this.shared.listeners.get(name)});`);
|
|
202
|
+
}
|
|
181
203
|
for (const source of written) {
|
|
182
204
|
// The element already shows what was entered: its own update is skipped.
|
|
183
205
|
const own = live && dependsOn.has(source) ? live.ownIndex(source) : null;
|
package/dist/codegen/calls.d.ts
CHANGED
|
@@ -2,6 +2,12 @@ import type * as ast from '../ast.ts';
|
|
|
2
2
|
import { ExpressionEmitter } from './expressions.ts';
|
|
3
3
|
export declare abstract class CallEmitter extends ExpressionEmitter {
|
|
4
4
|
protected call(node: ast.CallExpression): string;
|
|
5
|
+
/**
|
|
6
|
+
* `go f(x)`: the worker module has the function under the same name, a literal under the name
|
|
7
|
+
* given to it, after the local names it uses; the arguments are copied there. In the worker
|
|
8
|
+
* module a literal runs in the same worker, with copies of those names as its first parameters.
|
|
9
|
+
*/
|
|
10
|
+
protected go(node: ast.GoExpression): string;
|
|
5
11
|
/** `yield v` for `yield(v)` in an iterator, `yield [k, v]` for `yield(k, v)`; else `null`. */
|
|
6
12
|
private yielded;
|
|
7
13
|
protected expressionStatement(node: ast.Expression): string;
|
|
@@ -23,6 +29,23 @@ export declare abstract class CallEmitter extends ExpressionEmitter {
|
|
|
23
29
|
protected chan(node: ast.ChanExpression): string;
|
|
24
30
|
/** `queue<number>(items)`: the queue copies the values, as `[...items]` would. */
|
|
25
31
|
protected queue(node: ast.QueueExpression): string;
|
|
32
|
+
/** Whether the checker found a topic here, or a `?topic T` in a case of `select`. */
|
|
33
|
+
protected isTopicValue(node: ast.Expression): boolean;
|
|
34
|
+
/**
|
|
35
|
+
* The channel that `<-x`, `for v in x` and a case of `select` receive from: a topic listens from
|
|
36
|
+
* the first of them on, into a channel of its own (src/runtime/topic.js).
|
|
37
|
+
*/
|
|
38
|
+
protected receivedFrom(node: ast.Expression): string;
|
|
39
|
+
/**
|
|
40
|
+
* The zero value that `<-x` gives if `x` is `Channel.EMPTY` or `Topic.EMPTY`, which are the same
|
|
41
|
+
* for all types; any other channel keeps its own. `null` when it is null.
|
|
42
|
+
*/
|
|
43
|
+
protected receivedZero(node: ast.Expression): string;
|
|
44
|
+
protected receive(node: ast.Expression, withOk: boolean): string;
|
|
45
|
+
/** `ch <- v` waits until the channel takes the value; a topic sends and goes on. */
|
|
46
|
+
protected send(node: ast.SendStatement): string;
|
|
47
|
+
/** `topic<T>(name)`: a topic keeps the zero value that a closed one gives, as a channel does. */
|
|
48
|
+
protected topic(node: ast.TopicExpression): string;
|
|
26
49
|
/**
|
|
27
50
|
* `chan(stream, size)`: the channel keeps the zero value of its values, as `chan<T>(size)` does,
|
|
28
51
|
* from the type of the stream that the checker found; without it, null.
|
package/dist/codegen/calls.js
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { elementOf, isChan } from "../checker/channel.js";
|
|
2
|
+
import { isNullable, isQueue, isTopic, nonNull, topicElement, typedArrayOf, } from "../checker/types.js";
|
|
3
|
+
import { goCaptures, goParams, goTarget } from "../goroutines.js";
|
|
2
4
|
import { isYieldCall } from "../iterators.js";
|
|
3
5
|
import { TYPED_ARRAYS, TYPED_ELEMENTS } from "../typed-arrays.js";
|
|
4
6
|
import { hasOptionalLink } from "./analysis.js";
|
|
@@ -8,21 +10,50 @@ import { ARROW, BINARY, POSTFIX } from "./syntax.js";
|
|
|
8
10
|
// checker found one expected becomes `new Uint8Array([...])`, and the conversions of Go between
|
|
9
11
|
// text and its UTF-8 bytes use TextEncoder and TextDecoder, `[]byte(s)` and `string(data)`. In an
|
|
10
12
|
// iterator, a generator, `yield(v)` yields: its value is `true`, since a generator that the loop
|
|
11
|
-
// over it leaves is closed at that `yield` and goes no further, so `false` is never seen.
|
|
13
|
+
// over it leaves is closed at that `yield` and goes no further, so `false` is never seen. Making
|
|
14
|
+
// channels, queues and topics is here too, with what a topic needs where it is received or closed.
|
|
12
15
|
export class CallEmitter extends ExpressionEmitter {
|
|
13
16
|
call(node) {
|
|
14
17
|
// After `?.` the arguments may not be evaluated at all.
|
|
15
18
|
const conditional = node.optional || hasOptionalLink(node.callee);
|
|
16
19
|
const args = this.withHoisting(this.hoist && !conditional, () => this.elements(node.arguments));
|
|
17
|
-
|
|
18
|
-
|
|
20
|
+
// `error("load user", err)` wraps an error, with its message after this one (runtime/core.js).
|
|
21
|
+
if (this.isBuiltinError(node.callee) && !node.optional) {
|
|
22
|
+
return node.arguments.length > 1 ? `$$wrap(${args})` : `new Error(${args})`;
|
|
23
|
+
}
|
|
19
24
|
if (this.isStringConversion(node.callee))
|
|
20
25
|
return `new TextDecoder().decode(${args})`;
|
|
21
26
|
const yielded = this.yielded(node);
|
|
22
27
|
if (yielded !== null)
|
|
23
28
|
return `(${yielded}, true)`;
|
|
29
|
+
// `close(t)` of a topic: it stops listening and sending.
|
|
30
|
+
const [argument] = node.arguments;
|
|
31
|
+
if (node.callee.kind === 'Identifier' &&
|
|
32
|
+
node.callee.name === 'close' &&
|
|
33
|
+
this.lookup('close') === undefined &&
|
|
34
|
+
argument?.kind !== 'SpreadElement' &&
|
|
35
|
+
argument !== undefined &&
|
|
36
|
+
this.isTopicValue(argument)) {
|
|
37
|
+
return `$$Topic.close(${args})`;
|
|
38
|
+
}
|
|
24
39
|
return `${this.expression(node.callee, POSTFIX)}${node.optional ? '?.' : ''}(${args})`;
|
|
25
40
|
}
|
|
41
|
+
/**
|
|
42
|
+
* `go f(x)`: the worker module has the function under the same name, a literal under the name
|
|
43
|
+
* given to it, after the local names it uses; the arguments are copied there. In the worker
|
|
44
|
+
* module a literal runs in the same worker, with copies of those names as its first parameters.
|
|
45
|
+
*/
|
|
46
|
+
go(node) {
|
|
47
|
+
const { callee } = node.call;
|
|
48
|
+
const literal = callee.kind === 'FuncExpression' ? callee : null;
|
|
49
|
+
const captured = literal ? goCaptures(this.program, node) : [];
|
|
50
|
+
const args = [...captured.map((name) => this.name(name)), this.elements(node.call.arguments)];
|
|
51
|
+
// Without type checking, a callee that is neither fails when the call runs.
|
|
52
|
+
const fn = literal && this.inWorker
|
|
53
|
+
? this.expression({ ...literal, params: [...goParams(literal, captured), ...literal.params] }, ARROW)
|
|
54
|
+
: JSON.stringify(goTarget(this.program, node));
|
|
55
|
+
return `$$go(${fn}, [${args.filter((arg) => arg !== '').join(', ')}])`;
|
|
56
|
+
}
|
|
26
57
|
/** `yield v` for `yield(v)` in an iterator, `yield [k, v]` for `yield(k, v)`; else `null`. */
|
|
27
58
|
yielded(node) {
|
|
28
59
|
if (!this.fn.iterator || !isYieldCall(node) || this.lookup('yield') !== 'param')
|
|
@@ -37,7 +68,8 @@ export class CallEmitter extends ExpressionEmitter {
|
|
|
37
68
|
yieldStatement(node) {
|
|
38
69
|
const [only] = node.consequent.body;
|
|
39
70
|
const { condition } = node;
|
|
40
|
-
if (node.
|
|
71
|
+
if (node.init ||
|
|
72
|
+
node.alternate ||
|
|
41
73
|
node.consequent.body.length !== 1 ||
|
|
42
74
|
only?.kind !== 'ReturnStatement' ||
|
|
43
75
|
only.values.length > 0 ||
|
|
@@ -105,12 +137,55 @@ export class CallEmitter extends ExpressionEmitter {
|
|
|
105
137
|
/** `chan<number>(10)`: the channel keeps the zero value that a closed one gives. */
|
|
106
138
|
chan(node) {
|
|
107
139
|
const size = node.size ? this.expression(node.size, ARROW) : '0';
|
|
108
|
-
return `$$
|
|
140
|
+
return `$$Channel.make(${size}, ${this.zeroValue(node.element) ?? 'null'})`;
|
|
109
141
|
}
|
|
110
142
|
/** `queue<number>(items)`: the queue copies the values, as `[...items]` would. */
|
|
111
143
|
queue(node) {
|
|
112
144
|
return `new $$Queue(${node.items ? this.expression(node.items, ARROW) : ''})`;
|
|
113
145
|
}
|
|
146
|
+
/** Whether the checker found a topic here, or a `?topic T` in a case of `select`. */
|
|
147
|
+
isTopicValue(node) {
|
|
148
|
+
const type = this.options.types?.get(node);
|
|
149
|
+
return type !== undefined && isTopic(isNullable(type) ? nonNull(type) : type);
|
|
150
|
+
}
|
|
151
|
+
/**
|
|
152
|
+
* The channel that `<-x`, `for v in x` and a case of `select` receive from: a topic listens from
|
|
153
|
+
* the first of them on, into a channel of its own (src/runtime/topic.js).
|
|
154
|
+
*/
|
|
155
|
+
receivedFrom(node) {
|
|
156
|
+
const channel = this.expression(node, ARROW);
|
|
157
|
+
return this.isTopicValue(node) ? `$$Topic.listen(${channel})` : channel;
|
|
158
|
+
}
|
|
159
|
+
/**
|
|
160
|
+
* The zero value that `<-x` gives if `x` is `Channel.EMPTY` or `Topic.EMPTY`, which are the same
|
|
161
|
+
* for all types; any other channel keeps its own. `null` when it is null.
|
|
162
|
+
*/
|
|
163
|
+
receivedZero(node) {
|
|
164
|
+
const type = this.options.types?.get(node);
|
|
165
|
+
const channel = type && (isNullable(type) ? nonNull(type) : type);
|
|
166
|
+
if (channel && isChan(channel))
|
|
167
|
+
return zeroOf(elementOf(channel));
|
|
168
|
+
if (channel && isTopic(channel))
|
|
169
|
+
return zeroOf(topicElement(channel));
|
|
170
|
+
return 'null';
|
|
171
|
+
}
|
|
172
|
+
receive(node, withOk) {
|
|
173
|
+
const zero = this.receivedZero(node);
|
|
174
|
+
const args = zero !== 'null' ? `, ${withOk}, ${zero}` : withOk ? ', true' : '';
|
|
175
|
+
return `$$Channel.receive(${this.receivedFrom(node)}${args})`;
|
|
176
|
+
}
|
|
177
|
+
/** `ch <- v` waits until the channel takes the value; a topic sends and goes on. */
|
|
178
|
+
send(node) {
|
|
179
|
+
const channel = this.expression(node.channel, ARROW);
|
|
180
|
+
const value = this.expression(node.value, ARROW);
|
|
181
|
+
return this.isTopicValue(node.channel)
|
|
182
|
+
? `$$Topic.publish(${channel}, ${value})`
|
|
183
|
+
: `await $$Channel.send(${channel}, ${value})`;
|
|
184
|
+
}
|
|
185
|
+
/** `topic<T>(name)`: a topic keeps the zero value that a closed one gives, as a channel does. */
|
|
186
|
+
topic(node) {
|
|
187
|
+
return `$$Topic.make(${this.expression(node.name, ARROW)}, ${this.zeroValue(node.element) ?? 'null'})`;
|
|
188
|
+
}
|
|
114
189
|
/**
|
|
115
190
|
* `chan(stream, size)`: the channel keeps the zero value of its values, as `chan<T>(size)` does,
|
|
116
191
|
* from the type of the stream that the checker found; without it, null.
|
|
@@ -121,7 +196,7 @@ export class CallEmitter extends ExpressionEmitter {
|
|
|
121
196
|
const type = this.options.types?.get(node);
|
|
122
197
|
const channel = type?.kind === 'tuple' ? type.types[0] : undefined;
|
|
123
198
|
const element = channel?.kind === 'object' ? channel.instanceOf?.args[0] : undefined;
|
|
124
|
-
return `$$fromStream(${stream}, ${size}, ${element ? zeroOf(element) : 'null'})`;
|
|
199
|
+
return `$$Channel.fromStream(${stream}, ${size}, ${element ? zeroOf(element) : 'null'})`;
|
|
125
200
|
}
|
|
126
201
|
}
|
|
127
202
|
/** The zero value of a type, as zeroValue gives for a type as written. */
|
|
@@ -140,6 +215,12 @@ function zeroOf(type) {
|
|
|
140
215
|
return 'false';
|
|
141
216
|
case 'array':
|
|
142
217
|
return '[]';
|
|
218
|
+
case 'object':
|
|
219
|
+
if (isQueue(type))
|
|
220
|
+
return 'new $$Queue()';
|
|
221
|
+
if (isChan(type))
|
|
222
|
+
return '$$Channel.EMPTY';
|
|
223
|
+
return isTopic(type) ? '$$Topic.EMPTY' : 'null';
|
|
143
224
|
default:
|
|
144
225
|
return 'null';
|
|
145
226
|
}
|
|
@@ -25,9 +25,14 @@ export interface EarlyReturns {
|
|
|
25
25
|
}
|
|
26
26
|
/** Marks where the setters of an inlined component with early returns go: before any return. */
|
|
27
27
|
export declare const SETTERS: string;
|
|
28
|
+
/**
|
|
29
|
+
* Marks the `$$watch(...)` of a component that reads state of the module, around the id of the
|
|
30
|
+
* source: it stays only if something depends on the state (resolveMarkers).
|
|
31
|
+
*/
|
|
32
|
+
export declare const LISTEN: string;
|
|
28
33
|
/** `f(a, undefined, undefined)` → `f(a)`: missing arguments are undefined anyway. */
|
|
29
34
|
export declare function withoutTrailingUndefined(args: string[]): string[];
|
|
30
|
-
/** Whether a component has `mount
|
|
35
|
+
/** Whether a component has `mount { ... }`. */
|
|
31
36
|
export declare function hasMount(component: ast.ComponentDeclaration): boolean;
|
|
32
37
|
/** Whether a component returns before the end of its body. */
|
|
33
38
|
export declare function hasEarlyReturn(component: ast.ComponentDeclaration): boolean;
|