rooklang 0.3.2 → 0.3.4
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 +32 -7
- package/README.ru.md +32 -7
- package/dist/ast/declarations.d.ts +20 -1
- package/dist/ast/expressions.d.ts +31 -1
- package/dist/ast/markup.d.ts +6 -1
- package/dist/ast/statements.d.ts +25 -1
- 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 +7 -0
- package/dist/checker/assignments.js +48 -8
- package/dist/checker/base.d.ts +2 -1
- package/dist/checker/base.js +6 -12
- package/dist/checker/builtins.js +15 -5
- 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 +16 -2
- 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 +41 -12
- package/dist/checker/closing.d.ts +21 -0
- package/dist/checker/closing.js +164 -0
- package/dist/checker/completion.d.ts +2 -2
- package/dist/checker/completion.js +2 -2
- package/dist/checker/context.d.ts +19 -0
- package/dist/checker/control-flow.d.ts +6 -0
- package/dist/checker/control-flow.js +34 -0
- package/dist/checker/declarations.js +1 -0
- package/dist/checker/decode.d.ts +8 -0
- package/dist/checker/decode.js +32 -0
- package/dist/checker/error-results.d.ts +6 -0
- package/dist/checker/error-results.js +31 -0
- package/dist/checker/expressions.d.ts +5 -1
- package/dist/checker/expressions.js +13 -34
- package/dist/checker/flow.js +8 -4
- package/dist/checker/helpers.d.ts +1 -1
- package/dist/checker/helpers.js +9 -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.js +23 -3
- package/dist/checker/members.js +7 -0
- 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/signals.d.ts +8 -0
- package/dist/checker/signals.js +48 -0
- package/dist/checker/statements.d.ts +5 -0
- package/dist/checker/statements.js +19 -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/cli.js +18 -36
- package/dist/codegen/calls.d.ts +23 -0
- package/dist/codegen/calls.js +88 -7
- package/dist/codegen/control-statements.d.ts +19 -3
- package/dist/codegen/control-statements.js +105 -20
- package/dist/codegen/decode.d.ts +14 -0
- package/dist/codegen/decode.js +93 -0
- package/dist/codegen/emitter.d.ts +10 -41
- package/dist/codegen/emitter.js +9 -4
- package/dist/codegen/expressions.d.ts +9 -0
- package/dist/codegen/expressions.js +15 -24
- package/dist/codegen/js.d.ts +2 -2
- package/dist/codegen/js.js +5 -2
- package/dist/codegen/options.d.ts +40 -0
- package/dist/codegen/options.js +1 -0
- package/dist/codegen/runtime.d.ts +7 -5
- package/dist/codegen/runtime.js +28 -20
- package/dist/codegen/simple-statements.d.ts +10 -4
- package/dist/codegen/simple-statements.js +40 -15
- package/dist/codegen/statements.js +64 -4
- package/dist/codegen/try.d.ts +5 -0
- package/dist/codegen/try.js +40 -0
- package/dist/compile.d.ts +9 -0
- package/dist/compile.js +19 -6
- package/dist/completion.d.ts +4 -2
- package/dist/completion.js +10 -5
- package/dist/declarations.js +32 -6
- package/dist/decorator-copy.js +5 -2
- 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/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 +40 -13
- package/dist/parser/base.d.ts +4 -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 +3 -1
- package/dist/parser/control-flow.js +21 -24
- package/dist/parser/declarations.d.ts +7 -4
- package/dist/parser/declarations.js +45 -6
- 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/simple-statements.d.ts +1 -1
- package/dist/parser/simple-statements.js +16 -9
- package/dist/parser/statements.d.ts +0 -3
- package/dist/parser/statements.js +22 -25
- 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 +11 -5
- package/dist/runtime-module.d.ts +1 -0
- package/dist/runtime-module.js +3 -0
- package/dist/source-map.d.ts +17 -0
- package/dist/source-map.js +75 -0
- package/dist/ts-types.d.ts +4 -0
- package/dist/ts-types.js +20 -3
- package/dist/typescript/convert-objects.d.ts +2 -1
- package/dist/typescript/convert-objects.js +18 -9
- package/dist/vite.js +13 -5
- package/dist/walk-types.js +1 -0
- package/dist/walk.d.ts +0 -2
- package/dist/walk.js +11 -28
- package/package.json +1 -1
- 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 +190 -0
- package/src/runtime/go.js +14 -5
- package/src/runtime/index.js +6 -0
- package/src/runtime/select.js +14 -9
- 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
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
|
}
|
|
@@ -1,8 +1,14 @@
|
|
|
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;
|
|
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;
|
|
6
12
|
protected loopBody(body: ast.BlockStatement): string;
|
|
7
13
|
protected forStatement(node: ast.ForStatement): void;
|
|
8
14
|
/** A statement inside a `for` header, without the `;`. */
|
|
@@ -12,8 +18,18 @@ export declare abstract class ControlStatementEmitter extends SimpleStatementEmi
|
|
|
12
18
|
* `for i, x in xs` iterates `xs.entries()`, which gives the keys and values of a Map too.
|
|
13
19
|
*/
|
|
14
20
|
protected forInStatement(node: ast.ForInStatement): void;
|
|
21
|
+
/** `[i, v]` of a loop over pairs; `_` leaves its place empty. */
|
|
22
|
+
private pair;
|
|
23
|
+
/**
|
|
24
|
+
* `for i, v in items` over an array or a typed array: a loop over the indexes, which runs faster
|
|
25
|
+
* than one over the pairs of `entries()`. The variables of the loop cannot be assigned, so `i` is
|
|
26
|
+
* the counter itself. The array is read once, as `entries()` reads it, into `$$items` unless it is
|
|
27
|
+
* a name that cannot change.
|
|
28
|
+
*/
|
|
29
|
+
private indexLoop;
|
|
30
|
+
protected switchStatement(node: ast.SwitchStatement): void;
|
|
15
31
|
/** Each `case` ends with an implicit `break`; a case with declarations gets its own block. */
|
|
16
|
-
|
|
32
|
+
private valueSwitch;
|
|
17
33
|
/**
|
|
18
34
|
* `select`: $$select gives the index of the case that went on and what it received, and a
|
|
19
35
|
* switch runs the case; a `break` inside it leaves the select, as in Go. `default` is -1.
|
|
@@ -23,6 +39,6 @@ export declare abstract class ControlStatementEmitter extends SimpleStatementEmi
|
|
|
23
39
|
* `switch { case cond: ... }` becomes an if/else chain. A `break` inside it must leave the
|
|
24
40
|
* switch, not an enclosing loop, so then the chain gets a label to break to.
|
|
25
41
|
*/
|
|
26
|
-
|
|
42
|
+
private taglessSwitch;
|
|
27
43
|
protected tryStatement(node: ast.TryStatement): string;
|
|
28
44
|
}
|
|
@@ -1,12 +1,19 @@
|
|
|
1
1
|
import { isChan } from "../checker/channel.js";
|
|
2
|
-
import { isMap } from "../checker/iterables.js";
|
|
3
|
-
import {
|
|
2
|
+
import { asyncIterated, isMap } from "../checker/iterables.js";
|
|
3
|
+
import { isTopic, typedArrayOf } from "../checker/types.js";
|
|
4
|
+
import { declaredIn } from "../names.js";
|
|
5
|
+
import { containsBreak } from "../breaks.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) {
|
|
14
|
+
const { init } = node;
|
|
15
|
+
if (init)
|
|
16
|
+
return this.withInit(init, () => this.line(this.ifStatement({ ...node, init: null })));
|
|
10
17
|
let text = `if (${this.expression(node.condition, 0)}) ${this.blockStatement(node.consequent)}`;
|
|
11
18
|
if (node.alternate?.kind === 'IfStatement') {
|
|
12
19
|
// The condition of `else if` is evaluated only when the first one is false.
|
|
@@ -18,6 +25,20 @@ export class ControlStatementEmitter extends SimpleStatementEmitter {
|
|
|
18
25
|
}
|
|
19
26
|
return text;
|
|
20
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
|
+
}
|
|
21
42
|
loopBody(body) {
|
|
22
43
|
return this.withBreakTarget(null, () => this.blockStatement(body));
|
|
23
44
|
}
|
|
@@ -33,10 +54,10 @@ export class ControlStatementEmitter extends SimpleStatementEmitter {
|
|
|
33
54
|
node.update ? this.simpleStatement(node.update) : '',
|
|
34
55
|
]);
|
|
35
56
|
if (node.init === null && node.update === null) {
|
|
36
|
-
this.line(`while (${condition || 'true'}) ${this.loopBody(node.body)}`);
|
|
57
|
+
this.line(this.labeled(node, `while (${condition || 'true'}) ${this.loopBody(node.body)}`));
|
|
37
58
|
return;
|
|
38
59
|
}
|
|
39
|
-
this.line(`for (${init}; ${condition}; ${update}) ${this.loopBody(node.body)}`);
|
|
60
|
+
this.line(this.labeled(node, `for (${init}; ${condition}; ${update}) ${this.loopBody(node.body)}`));
|
|
40
61
|
});
|
|
41
62
|
}
|
|
42
63
|
/** A statement inside a `for` header, without the `;`. */
|
|
@@ -53,7 +74,7 @@ export class ControlStatementEmitter extends SimpleStatementEmitter {
|
|
|
53
74
|
case 'ExpressionStatement':
|
|
54
75
|
return this.expression(node.expression, 0);
|
|
55
76
|
case 'SendStatement':
|
|
56
|
-
return
|
|
77
|
+
return this.send(node);
|
|
57
78
|
}
|
|
58
79
|
}
|
|
59
80
|
/**
|
|
@@ -68,9 +89,14 @@ export class ControlStatementEmitter extends SimpleStatementEmitter {
|
|
|
68
89
|
const value = this.name(node.value.name);
|
|
69
90
|
const iterable = this.options.types?.get(node.iterable);
|
|
70
91
|
let head;
|
|
71
|
-
//
|
|
72
|
-
if (iterable &&
|
|
73
|
-
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)}`));
|
|
74
100
|
return;
|
|
75
101
|
}
|
|
76
102
|
// An iterator is a generator function: called, it gives a lazy JS iterator, of pairs if its
|
|
@@ -94,16 +120,70 @@ export class ControlStatementEmitter extends SimpleStatementEmitter {
|
|
|
94
120
|
}
|
|
95
121
|
head = `const ${value} of ${values}`;
|
|
96
122
|
}
|
|
123
|
+
else if (iterable &&
|
|
124
|
+
(iterable.kind === 'array' || iterable.kind === 'string' || typedArrayOf(iterable) !== null)) {
|
|
125
|
+
// Without the index, the loop goes over the values.
|
|
126
|
+
if (node.key.name === '_' && node.value.name !== '_') {
|
|
127
|
+
head = `const ${value} of ${this.expression(node.iterable, POSTFIX)}`;
|
|
128
|
+
}
|
|
129
|
+
else if (iterable.kind === 'string') {
|
|
130
|
+
// A string has no entries(): its characters, as `for...of` gives them, are numbered.
|
|
131
|
+
const pair = this.pair(node.key, node.value);
|
|
132
|
+
head = `const ${pair} of Array.from(${this.expression(node.iterable, ARROW)}).entries()`;
|
|
133
|
+
}
|
|
134
|
+
else {
|
|
135
|
+
this.indexLoop(node, node.key);
|
|
136
|
+
return;
|
|
137
|
+
}
|
|
138
|
+
}
|
|
97
139
|
else {
|
|
98
|
-
|
|
99
|
-
const pair = node.value.name === '_' ? `[${key}]` : `[${key}, ${value}]`;
|
|
100
|
-
head = `const ${pair} of ${this.expression(node.iterable, POSTFIX)}.entries()`;
|
|
140
|
+
head = `const ${this.pair(node.key, node.value)} of ${this.expression(node.iterable, POSTFIX)}.entries()`;
|
|
101
141
|
}
|
|
102
|
-
this.line(`for (${head}) ${this.loopBody(node.body)}`);
|
|
142
|
+
this.line(this.labeled(node, `for (${head}) ${this.loopBody(node.body)}`));
|
|
103
143
|
});
|
|
104
144
|
}
|
|
145
|
+
/** `[i, v]` of a loop over pairs; `_` leaves its place empty. */
|
|
146
|
+
pair(key, value) {
|
|
147
|
+
const name = key.name === '_' ? '' : this.name(key.name);
|
|
148
|
+
return value.name === '_' ? `[${name}]` : `[${name}, ${this.name(value.name)}]`;
|
|
149
|
+
}
|
|
150
|
+
/**
|
|
151
|
+
* `for i, v in items` over an array or a typed array: a loop over the indexes, which runs faster
|
|
152
|
+
* than one over the pairs of `entries()`. The variables of the loop cannot be assigned, so `i` is
|
|
153
|
+
* the counter itself. The array is read once, as `entries()` reads it, into `$$items` unless it is
|
|
154
|
+
* a name that cannot change.
|
|
155
|
+
*/
|
|
156
|
+
indexLoop(node, keyNode) {
|
|
157
|
+
const key = keyNode.name === '_' ? '$$i' : this.name(keyNode.name);
|
|
158
|
+
const value = node.value.name === '_' ? null : this.name(node.value.name);
|
|
159
|
+
const { iterable } = node;
|
|
160
|
+
const fixed = iterable.kind === 'Identifier' && FIXED.has(this.lookup(iterable.name) ?? 'let');
|
|
161
|
+
const items = fixed ? this.expression(iterable, POSTFIX) : '$$items';
|
|
162
|
+
const init = fixed ? '' : `, $$items = ${this.expression(iterable, ARROW)}`;
|
|
163
|
+
// A declaration of the same name in the body would clash with the element's in one block.
|
|
164
|
+
const hidden = declaredIn(node.body.body).some((name) => name === keyNode.name || name === node.value.name);
|
|
165
|
+
const body = this.withBreakTarget(null, () => this.block(() => {
|
|
166
|
+
if (value !== null)
|
|
167
|
+
this.line(`const ${value} = ${items}[${key}];`);
|
|
168
|
+
if (hidden)
|
|
169
|
+
this.line(this.blockStatement(node.body));
|
|
170
|
+
else
|
|
171
|
+
this.blockStatements(node.body.body);
|
|
172
|
+
}));
|
|
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);
|
|
184
|
+
}
|
|
105
185
|
/** Each `case` ends with an implicit `break`; a case with declarations gets its own block. */
|
|
106
|
-
|
|
186
|
+
valueSwitch(node, discriminant) {
|
|
107
187
|
const head = `switch (${this.expression(discriminant, 0)})`;
|
|
108
188
|
const body = this.withBreakTarget(null, () => this.block(() => {
|
|
109
189
|
node.cases.forEach((switchCase, i) => {
|
|
@@ -130,7 +210,7 @@ export class ControlStatementEmitter extends SimpleStatementEmitter {
|
|
|
130
210
|
}
|
|
131
211
|
});
|
|
132
212
|
}));
|
|
133
|
-
this.line(`${head} ${body}`);
|
|
213
|
+
this.line(this.labeled(node, `${head} ${body}`));
|
|
134
214
|
}
|
|
135
215
|
/**
|
|
136
216
|
* `select`: $$select gives the index of the case that went on and what it received, and a
|
|
@@ -141,7 +221,10 @@ export class ControlStatementEmitter extends SimpleStatementEmitter {
|
|
|
141
221
|
if (operation === null)
|
|
142
222
|
return [];
|
|
143
223
|
if (operation.kind === 'UnaryExpression') {
|
|
144
|
-
|
|
224
|
+
const zero = this.receivedZero(operation.argument);
|
|
225
|
+
return [
|
|
226
|
+
`{ channel: ${this.receivedFrom(operation.argument)}${zero !== 'null' ? `, zero: ${zero}` : ''} }`,
|
|
227
|
+
];
|
|
145
228
|
}
|
|
146
229
|
const channel = this.expression(operation.channel, ARROW);
|
|
147
230
|
return [`{ channel: ${channel}, value: ${this.expression(operation.value, ARROW)} }`];
|
|
@@ -149,7 +232,7 @@ export class ControlStatementEmitter extends SimpleStatementEmitter {
|
|
|
149
232
|
const hasDefault = node.cases.some((selectCase) => selectCase.operation === null);
|
|
150
233
|
let index = 0;
|
|
151
234
|
const select = this.block(() => {
|
|
152
|
-
this.line(`const [$$case, $$received, $$ok] = await $$select([${operations.join(', ')}], ${hasDefault});`);
|
|
235
|
+
this.line(`const [$$case, $$received, $$ok] = await $$Channel.select([${operations.join(', ')}], ${hasDefault});`);
|
|
153
236
|
const body = this.withBreakTarget(null, () => this.block(() => {
|
|
154
237
|
for (const { names, operation, body: statements } of node.cases) {
|
|
155
238
|
const label = operation === null ? 'default:' : `case ${index++}:`;
|
|
@@ -175,7 +258,7 @@ export class ControlStatementEmitter extends SimpleStatementEmitter {
|
|
|
175
258
|
}));
|
|
176
259
|
this.line(`switch ($$case) ${body}`);
|
|
177
260
|
});
|
|
178
|
-
this.line(select);
|
|
261
|
+
this.line(this.labeled(node, select));
|
|
179
262
|
}
|
|
180
263
|
/**
|
|
181
264
|
* `switch { case cond: ... }` becomes an if/else chain. A `break` inside it must leave the
|
|
@@ -184,9 +267,11 @@ export class ControlStatementEmitter extends SimpleStatementEmitter {
|
|
|
184
267
|
taglessSwitch(node) {
|
|
185
268
|
const cases = node.cases.filter((switchCase) => switchCase.tests.length > 0);
|
|
186
269
|
const fallback = node.cases.find((switchCase) => switchCase.tests.length === 0);
|
|
187
|
-
const label = node.
|
|
188
|
-
?
|
|
189
|
-
:
|
|
270
|
+
const label = node.label
|
|
271
|
+
? this.label(node.label)
|
|
272
|
+
: node.cases.some((switchCase) => containsBreak(switchCase.body))
|
|
273
|
+
? `$$switch${++this.labels}`
|
|
274
|
+
: null;
|
|
190
275
|
const text = this.withBreakTarget(label, () => {
|
|
191
276
|
const caseBlock = (body) => this.block(() => this.blockStatements(body));
|
|
192
277
|
const branches = cases.map((switchCase) => {
|
|
@@ -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
|
+
}
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import { isText } from "../checker/json.js";
|
|
2
|
+
import { memberNames, typeToString } from "../checker/types.js";
|
|
3
|
+
import { CallEmitter } from "./calls.js";
|
|
4
|
+
import { ARROW } from "./syntax.js";
|
|
5
|
+
// `decode<User>(body)`: the type, which the checker found, becomes data that the runtime checks the
|
|
6
|
+
// value against (runtime/decode.js). An object type is a constant of the module, `$$typeUser`,
|
|
7
|
+
// written before its code (js.ts), so that types that refer to themselves or each other can do so,
|
|
8
|
+
// and the value of each call is only its name: `$$decodeJson(body, $$typeUser)`.
|
|
9
|
+
const IDENTIFIER = /^[A-Za-z_$][\w$]*$/;
|
|
10
|
+
export class DecodeEmitter extends CallEmitter {
|
|
11
|
+
/** The constants of the object types that decode checks against, by type, in order. */
|
|
12
|
+
decodeTypes = new Map();
|
|
13
|
+
/** `$$decodeJson(text, type)` for a string, `$$decode(value, type)` for a value from JS. */
|
|
14
|
+
decode(node) {
|
|
15
|
+
const argument = this.expression(node.argument, ARROW);
|
|
16
|
+
const result = this.options.types?.get(node);
|
|
17
|
+
const type = result?.kind === 'tuple' ? result.types[0] : undefined;
|
|
18
|
+
// Without the types of the checker nothing is checked (runtime/decode.js).
|
|
19
|
+
if (type === undefined)
|
|
20
|
+
return `$$decode(${argument}, null)`;
|
|
21
|
+
const given = this.options.types?.get(node.argument);
|
|
22
|
+
const decode = given !== undefined && isText(given) ? '$$decodeJson' : '$$decode';
|
|
23
|
+
return `${decode}(${argument}, ${this.shape(type)})`;
|
|
24
|
+
}
|
|
25
|
+
/** The constants of the object types, for the start of the module. */
|
|
26
|
+
decodeTypeLines() {
|
|
27
|
+
return [...this.decodeTypes.values()].flatMap(({ lines }) => lines);
|
|
28
|
+
}
|
|
29
|
+
/** A type as runtime/decode.js has it: `"string"`, `{ array: "number" }`, `$$typeUser`. */
|
|
30
|
+
shape(type) {
|
|
31
|
+
switch (type.kind) {
|
|
32
|
+
case 'number':
|
|
33
|
+
case 'string':
|
|
34
|
+
case 'bool':
|
|
35
|
+
case 'any':
|
|
36
|
+
return `"${type.kind}"`;
|
|
37
|
+
case 'null':
|
|
38
|
+
return '{ literal: [null] }';
|
|
39
|
+
case 'literal':
|
|
40
|
+
return `{ literal: [${JSON.stringify(type.value)}] }`;
|
|
41
|
+
case 'nullable':
|
|
42
|
+
return `{ nullable: ${this.shape(type.type)} }`;
|
|
43
|
+
case 'array':
|
|
44
|
+
return `{ array: ${this.shape(type.element)} }`;
|
|
45
|
+
case 'union': {
|
|
46
|
+
// The literals of a union are one list, as `"admin" | "user"` reads.
|
|
47
|
+
const literals = type.types.flatMap((each) => each.kind === 'literal' ? [each.value] : []);
|
|
48
|
+
const others = type.types.filter((each) => each.kind !== 'literal');
|
|
49
|
+
const shapes = [
|
|
50
|
+
...(literals.length > 0 ? [`{ literal: [${literals.map(json).join(', ')}] }`] : []),
|
|
51
|
+
...others.map((each) => this.shape(each)),
|
|
52
|
+
];
|
|
53
|
+
return shapes.length === 1 ? shapes[0] : `{ union: [${shapes.join(', ')}] }`;
|
|
54
|
+
}
|
|
55
|
+
case 'object':
|
|
56
|
+
return type.index && [...memberNames(type.members)].length === 0
|
|
57
|
+
? `{ record: ${this.shape(type.index)} }`
|
|
58
|
+
: this.objectType(type);
|
|
59
|
+
// The checker lets no other type through.
|
|
60
|
+
default:
|
|
61
|
+
return '"any"';
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
/** The constant of an object type, made the first time it is used. */
|
|
65
|
+
objectType(type) {
|
|
66
|
+
const known = this.decodeTypes.get(type);
|
|
67
|
+
if (known)
|
|
68
|
+
return known.name;
|
|
69
|
+
// `$$type` itself is the function of the runtime that makes them.
|
|
70
|
+
const written = type.instanceOf?.template.name ?? type.name ?? 'Object';
|
|
71
|
+
const base = `$$type${written.replace(/[^\w$]/g, '')}`;
|
|
72
|
+
const taken = new Set([...this.decodeTypes.values()].map(({ name }) => name));
|
|
73
|
+
let name = base;
|
|
74
|
+
for (let i = 2; taken.has(name); i++)
|
|
75
|
+
name = `${base}${i}`;
|
|
76
|
+
const entry = { name, lines: [] };
|
|
77
|
+
// Registered before its fields, which may refer to it.
|
|
78
|
+
this.decodeTypes.set(type, entry);
|
|
79
|
+
const fields = [...memberNames(type.members)].flatMap((field) => {
|
|
80
|
+
const member = type.members.get(field);
|
|
81
|
+
const key = IDENTIFIER.test(field) ? field : json(field);
|
|
82
|
+
return member ? [` ${key}: ${this.shape(member.type)},`] : [];
|
|
83
|
+
});
|
|
84
|
+
entry.lines =
|
|
85
|
+
fields.length === 0
|
|
86
|
+
? [`const ${name} = $$type(${json(typeToString(type))}, () => ({}));`]
|
|
87
|
+
: [`const ${name} = $$type(${json(typeToString(type))}, () => ({`, ...fields, '}));'];
|
|
88
|
+
return name;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
function json(value) {
|
|
92
|
+
return JSON.stringify(value);
|
|
93
|
+
}
|
|
@@ -1,42 +1,8 @@
|
|
|
1
1
|
import type * as ast from '../ast.ts';
|
|
2
|
-
import type
|
|
3
|
-
import { type DecoratorSource } from '../decorators.ts';
|
|
4
|
-
import type { InlinePackage } from '../inline-packages.ts';
|
|
5
|
-
import { type ImportedComponent, type LocalizedPackage } from './imported.ts';
|
|
2
|
+
import { type LocalizedPackage } from './imported.ts';
|
|
6
3
|
import type { Root } from '../refs.ts';
|
|
7
|
-
import type { StyleModule } from '../style-modules.ts';
|
|
8
4
|
import type { BindingKind } from './analysis.ts';
|
|
9
|
-
import type {
|
|
10
|
-
export interface JsOptions {
|
|
11
|
-
/** The Rook source, used to keep the blank lines between statements. */
|
|
12
|
-
source: string;
|
|
13
|
-
/** Replace `.rk` with `.js` in relative import paths, for output written next to the sources. */
|
|
14
|
-
rewriteImports: boolean;
|
|
15
|
-
/** Types from the checker, when it ran; without them the generated code is more general. */
|
|
16
|
-
types?: WeakMap<ast.Expression, Type> | undefined;
|
|
17
|
-
/** Decorators imported from other modules, by name: their code is inlined here. */
|
|
18
|
-
decorators?: ReadonlyMap<string, ImportedDecorator> | undefined;
|
|
19
|
-
/** Components imported from other modules, by name: called, or inlined when they are small. */
|
|
20
|
-
components?: ReadonlyMap<string, ImportedComponent> | undefined;
|
|
21
|
-
/** Components of the module that become functions, since inlining them adds too many lines. */
|
|
22
|
-
functions?: ReadonlySet<string> | undefined;
|
|
23
|
-
/** The CSS modules the module imports, by their paths as written. */
|
|
24
|
-
styleModules?: ReadonlyMap<string, StyleModule> | undefined;
|
|
25
|
-
/** For `go`: where the worker module is (`./app.worker.js`), or in it, the functions it runs. */
|
|
26
|
-
goroutines?: {
|
|
27
|
-
workerUrl: string;
|
|
28
|
-
} | {
|
|
29
|
-
targets: readonly string[];
|
|
30
|
-
} | undefined;
|
|
31
|
-
/** How the module gets the runtime it uses: imported, by default, or in it (runtime.ts). */
|
|
32
|
-
runtime?: RuntimeMode | undefined;
|
|
33
|
-
}
|
|
34
|
-
/** A decorator of another module, with the types of the expressions of that module. */
|
|
35
|
-
export interface ImportedDecorator extends DecoratorSource {
|
|
36
|
-
types: WeakMap<ast.Expression, Type> | null;
|
|
37
|
-
/** The components of that module its code uses, which go with it into the components here. */
|
|
38
|
-
inline: InlinePackage | null;
|
|
39
|
-
}
|
|
5
|
+
import type { JsOptions } from './options.ts';
|
|
40
6
|
/** How a function implements its `defer` statements. */
|
|
41
7
|
export type DeferMode = 'none' | 'try' | 'stack' | 'effect';
|
|
42
8
|
interface FunctionContext {
|
|
@@ -54,10 +20,11 @@ interface FunctionContext {
|
|
|
54
20
|
* The base of the JS generator: the output buffer, scopes of declared names and the state of the
|
|
55
21
|
* current function. The generator is split into layers, each adding to the previous one:
|
|
56
22
|
*
|
|
57
|
-
* Emitter → ExpressionEmitter → CallEmitter →
|
|
58
|
-
* →
|
|
59
|
-
* →
|
|
60
|
-
* →
|
|
23
|
+
* Emitter → ExpressionEmitter → CallEmitter → DecodeEmitter → TryEmitter
|
|
24
|
+
* → SimpleStatementEmitter → ControlStatementEmitter → ParallelLoopEmitter
|
|
25
|
+
* → StatementEmitter → UpdateEmitter → AttributeEmitter → CreationEmitter → ElementEmitter
|
|
26
|
+
* → ControlFlowEmitter → ComponentPropsEmitter → ComponentValueEmitter
|
|
27
|
+
* → EarlyReturnEmitter → RefEmitter
|
|
61
28
|
* → StyleEmitter → ComponentEmitter → WebComponentEmitter → FunctionEmitter → JsGenerator
|
|
62
29
|
*/
|
|
63
30
|
export declare abstract class Emitter {
|
|
@@ -100,7 +67,7 @@ export declare abstract class Emitter {
|
|
|
100
67
|
/** The decorators that components of the module apply, by name. */
|
|
101
68
|
protected readonly appliedDecorators: Set<string>;
|
|
102
69
|
/** Copies of the code of other modules: their positions are in the sources of those. */
|
|
103
|
-
|
|
70
|
+
protected readonly foreign: WeakSet<ast.Node>;
|
|
104
71
|
/** Components of other modules inlined here, by name, with what their packages bring. */
|
|
105
72
|
protected readonly inlinedImports: Map<string, LocalizedPackage>;
|
|
106
73
|
/** The components that decorators of other modules bring, by the names of the decorators. */
|
|
@@ -123,6 +90,8 @@ export declare abstract class Emitter {
|
|
|
123
90
|
protected lookup(name: string): BindingKind | undefined;
|
|
124
91
|
/** The JS name for a Rook name. */
|
|
125
92
|
protected name(name: string): string;
|
|
93
|
+
/** Labels are names of their own in JS, which cannot be reserved words either. */
|
|
94
|
+
protected label(name: ast.Identifier): string;
|
|
126
95
|
protected params(params: ast.Parameter[]): string;
|
|
127
96
|
protected withFunction<T>(mode: DeferMode, params: ast.Parameter[], emit: () => T): T;
|
|
128
97
|
protected withHoisting<T>(hoist: boolean, emit: () => T): T;
|
package/dist/codegen/emitter.js
CHANGED
|
@@ -9,10 +9,11 @@ import { JS_RESERVED, JS_UNDECLARABLE } from "./syntax.js";
|
|
|
9
9
|
* The base of the JS generator: the output buffer, scopes of declared names and the state of the
|
|
10
10
|
* current function. The generator is split into layers, each adding to the previous one:
|
|
11
11
|
*
|
|
12
|
-
* Emitter → ExpressionEmitter → CallEmitter →
|
|
13
|
-
* →
|
|
14
|
-
* →
|
|
15
|
-
* →
|
|
12
|
+
* Emitter → ExpressionEmitter → CallEmitter → DecodeEmitter → TryEmitter
|
|
13
|
+
* → SimpleStatementEmitter → ControlStatementEmitter → ParallelLoopEmitter
|
|
14
|
+
* → StatementEmitter → UpdateEmitter → AttributeEmitter → CreationEmitter → ElementEmitter
|
|
15
|
+
* → ControlFlowEmitter → ComponentPropsEmitter → ComponentValueEmitter
|
|
16
|
+
* → EarlyReturnEmitter → RefEmitter
|
|
16
17
|
* → StyleEmitter → ComponentEmitter → WebComponentEmitter → FunctionEmitter → JsGenerator
|
|
17
18
|
*/
|
|
18
19
|
export class Emitter {
|
|
@@ -250,6 +251,10 @@ export class Emitter {
|
|
|
250
251
|
const rename = JS_RESERVED.has(name) || (JS_UNDECLARABLE.has(name) && this.lookup(name) !== undefined);
|
|
251
252
|
return rename ? `${name}$` : name;
|
|
252
253
|
}
|
|
254
|
+
/** Labels are names of their own in JS, which cannot be reserved words either. */
|
|
255
|
+
label(name) {
|
|
256
|
+
return JS_RESERVED.has(name.name) ? `${name.name}$` : name.name;
|
|
257
|
+
}
|
|
253
258
|
params(params) {
|
|
254
259
|
// Several `_` parameters would be duplicate names in JS.
|
|
255
260
|
const blanks = params.filter((param) => param.name.name === '_').length;
|
|
@@ -21,6 +21,15 @@ export declare abstract class ExpressionEmitter extends Emitter {
|
|
|
21
21
|
protected abstract chan(node: ast.ChanExpression): string;
|
|
22
22
|
protected abstract streamChan(node: ast.StreamChanExpression): string;
|
|
23
23
|
protected abstract queue(node: ast.QueueExpression): string;
|
|
24
|
+
protected abstract topic(node: ast.TopicExpression): string;
|
|
25
|
+
/** `go f(x)`; implemented by CallEmitter. */
|
|
26
|
+
protected abstract go(node: ast.GoExpression): string;
|
|
27
|
+
/** `decode<T>(x)`; implemented by DecodeEmitter. */
|
|
28
|
+
protected abstract decode(node: ast.DecodeExpression): string;
|
|
29
|
+
/** `try x`, with the precedence of its code; implemented by TryEmitter. */
|
|
30
|
+
protected abstract tryValue(node: ast.TryExpression): [string, number];
|
|
31
|
+
/** `<-x` without its `await`; implemented by CallEmitter. */
|
|
32
|
+
protected abstract receive(node: ast.Expression, withOk: boolean): string;
|
|
24
33
|
/** `<-ch` that gives `[value, ok]`, in `v, ok = <-ch`. */
|
|
25
34
|
protected readonly commaOk: WeakSet<ast.Expression>;
|
|
26
35
|
/** `items[i]` in a part of `go for`, which is `items[$$i]` there (parallel.ts). */
|