rooklang 0.3.1 → 0.3.2
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/base.js +4 -4
- 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/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/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.js +0 -4
- 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 +15 -0
- package/dist/codegen/runtime.js +99 -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 +8 -0
- 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/ts-types.js +9 -1
- 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 +334 -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,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
|
}
|
|
@@ -53,7 +53,6 @@ export class ControlStatementEmitter extends SimpleStatementEmitter {
|
|
|
53
53
|
case 'ExpressionStatement':
|
|
54
54
|
return this.expression(node.expression, 0);
|
|
55
55
|
case 'SendStatement':
|
|
56
|
-
this.helpers.add('chan');
|
|
57
56
|
return `await $$send(${this.expression(node.channel, ARROW)}, ${this.expression(node.value, ARROW)})`;
|
|
58
57
|
}
|
|
59
58
|
}
|
|
@@ -71,7 +70,6 @@ export class ControlStatementEmitter extends SimpleStatementEmitter {
|
|
|
71
70
|
let head;
|
|
72
71
|
// A channel gives its values as they come, until it is closed.
|
|
73
72
|
if (iterable && isChan(iterable)) {
|
|
74
|
-
this.helpers.add('chan');
|
|
75
73
|
this.line(`for await (const ${value} of $$range(${this.expression(node.iterable, ARROW)})) ${this.loopBody(node.body)}`);
|
|
76
74
|
return;
|
|
77
75
|
}
|
|
@@ -92,7 +90,6 @@ export class ControlStatementEmitter extends SimpleStatementEmitter {
|
|
|
92
90
|
if (iterable && isMap(iterable))
|
|
93
91
|
values += '.values()';
|
|
94
92
|
else if (untyped) {
|
|
95
|
-
this.helpers.add('values');
|
|
96
93
|
values = `$$values(${values})`;
|
|
97
94
|
}
|
|
98
95
|
head = `const ${value} of ${values}`;
|
|
@@ -140,7 +137,6 @@ export class ControlStatementEmitter extends SimpleStatementEmitter {
|
|
|
140
137
|
* switch runs the case; a `break` inside it leaves the select, as in Go. `default` is -1.
|
|
141
138
|
*/
|
|
142
139
|
selectStatement(node) {
|
|
143
|
-
this.helpers.add('chan');
|
|
144
140
|
const operations = node.cases.flatMap(({ operation }) => {
|
|
145
141
|
if (operation === null)
|
|
146
142
|
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;
|
|
@@ -6,7 +6,7 @@ import { type ImportedComponent, type LocalizedPackage } from './imported.ts';
|
|
|
6
6
|
import type { Root } from '../refs.ts';
|
|
7
7
|
import type { StyleModule } from '../style-modules.ts';
|
|
8
8
|
import type { BindingKind } from './analysis.ts';
|
|
9
|
-
import type {
|
|
9
|
+
import type { RuntimeMode } from './runtime.ts';
|
|
10
10
|
export interface JsOptions {
|
|
11
11
|
/** The Rook source, used to keep the blank lines between statements. */
|
|
12
12
|
source: string;
|
|
@@ -22,14 +22,14 @@ export interface JsOptions {
|
|
|
22
22
|
functions?: ReadonlySet<string> | undefined;
|
|
23
23
|
/** The CSS modules the module imports, by their paths as written. */
|
|
24
24
|
styleModules?: ReadonlyMap<string, StyleModule> | undefined;
|
|
25
|
-
/** Create elements with their attributes by a helper, $$element; see creation.ts. */
|
|
26
|
-
elementHelper?: boolean | undefined;
|
|
27
25
|
/** For `go`: where the worker module is (`./app.worker.js`), or in it, the functions it runs. */
|
|
28
26
|
goroutines?: {
|
|
29
27
|
workerUrl: string;
|
|
30
28
|
} | {
|
|
31
29
|
targets: readonly string[];
|
|
32
30
|
} | undefined;
|
|
31
|
+
/** How the module gets the runtime it uses: imported, by default, or in it (runtime.ts). */
|
|
32
|
+
runtime?: RuntimeMode | undefined;
|
|
33
33
|
}
|
|
34
34
|
/** A decorator of another module, with the types of the expressions of that module. */
|
|
35
35
|
export interface ImportedDecorator extends DecoratorSource {
|
|
@@ -67,8 +67,6 @@ export declare abstract class Emitter {
|
|
|
67
67
|
private level;
|
|
68
68
|
private readonly scopes;
|
|
69
69
|
protected fn: FunctionContext;
|
|
70
|
-
/** Helpers that the module must define, such as $$runDeferred. */
|
|
71
|
-
protected readonly helpers: Set<Helper>;
|
|
72
70
|
/**
|
|
73
71
|
* Whether code for the current expression may be written before the statement that contains it.
|
|
74
72
|
* Elements use this to be created in statements of their own; it is off where an expression is
|
package/dist/codegen/emitter.js
CHANGED
|
@@ -27,8 +27,6 @@ export class Emitter {
|
|
|
27
27
|
temps: 0,
|
|
28
28
|
breakTargets: [],
|
|
29
29
|
};
|
|
30
|
-
/** Helpers that the module must define, such as $$runDeferred. */
|
|
31
|
-
helpers = new Set();
|
|
32
30
|
/**
|
|
33
31
|
* Whether code for the current expression may be written before the statement that contains it.
|
|
34
32
|
* Elements use this to be created in statements of their own; it is off where an expression is
|
|
@@ -18,11 +18,14 @@ export declare abstract class ExpressionEmitter extends Emitter {
|
|
|
18
18
|
protected abstract arrayLiteral(node: ast.ArrayLiteral): string;
|
|
19
19
|
protected abstract conversion(node: ast.ConversionExpression): string;
|
|
20
20
|
protected abstract make(node: ast.MakeExpression): string;
|
|
21
|
+
protected abstract chan(node: ast.ChanExpression): string;
|
|
22
|
+
protected abstract streamChan(node: ast.StreamChanExpression): string;
|
|
23
|
+
protected abstract queue(node: ast.QueueExpression): string;
|
|
21
24
|
/** `<-ch` that gives `[value, ok]`, in `v, ok = <-ch`. */
|
|
22
25
|
protected readonly commaOk: WeakSet<ast.Expression>;
|
|
23
26
|
/** `items[i]` in a part of `go for`, which is `items[$$i]` there (parallel.ts). */
|
|
24
27
|
protected readonly partElements: Set<ast.Expression>;
|
|
25
|
-
/** In the worker module, `go` runs in the same worker (
|
|
28
|
+
/** In the worker module, `go` runs in the same worker (runtime.ts). */
|
|
26
29
|
protected get inWorker(): boolean;
|
|
27
30
|
/** `v, ok = <-ch`: two variables from a single receive. */
|
|
28
31
|
protected markCommaOk(count: number, values: readonly ast.Expression[]): void;
|
|
@@ -3,7 +3,7 @@ import { goCaptures, goParams, goTarget } from "../goroutines.js";
|
|
|
3
3
|
import { isIterator, usesThis } from "../iterators.js";
|
|
4
4
|
import { containsElement, startsWithObjectLiteral } from "./analysis.js";
|
|
5
5
|
import { Emitter } from "./emitter.js";
|
|
6
|
-
import { PREDECLARED } from "./
|
|
6
|
+
import { PREDECLARED } from "./runtime.js";
|
|
7
7
|
import { ARROW, BINARY, CONDITIONAL, POSTFIX, PRIMARY, UNARY } from "./syntax.js";
|
|
8
8
|
/** Expressions, with the parentheses that JS operator precedence requires. */
|
|
9
9
|
export class ExpressionEmitter extends Emitter {
|
|
@@ -11,7 +11,7 @@ export class ExpressionEmitter extends Emitter {
|
|
|
11
11
|
commaOk = new WeakSet();
|
|
12
12
|
/** `items[i]` in a part of `go for`, which is `items[$$i]` there (parallel.ts). */
|
|
13
13
|
partElements = new Set();
|
|
14
|
-
/** In the worker module, `go` runs in the same worker (
|
|
14
|
+
/** In the worker module, `go` runs in the same worker (runtime.ts). */
|
|
15
15
|
get inWorker() {
|
|
16
16
|
return this.options.goroutines !== undefined && 'targets' in this.options.goroutines;
|
|
17
17
|
}
|
|
@@ -59,9 +59,7 @@ export class ExpressionEmitter extends Emitter {
|
|
|
59
59
|
return [this.functionNames.get(component) ?? `$$${component.name.name}`, PRIMARY];
|
|
60
60
|
}
|
|
61
61
|
// `close(ch)` and the like are predeclared; JS has a `close` of its own, the window's.
|
|
62
|
-
|
|
63
|
-
if (helper && this.lookup(node.name) === undefined) {
|
|
64
|
-
this.helpers.add(helper);
|
|
62
|
+
if (PREDECLARED.has(node.name) && this.lookup(node.name) === undefined) {
|
|
65
63
|
return [`$$${node.name}`, PRIMARY];
|
|
66
64
|
}
|
|
67
65
|
// The predeclared `error` used as a value is JS's `Error`, which also works without `new`.
|
|
@@ -108,12 +106,12 @@ export class ExpressionEmitter extends Emitter {
|
|
|
108
106
|
}
|
|
109
107
|
case 'ArrowFunction':
|
|
110
108
|
return [this.arrowFunction(node), ARROW];
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
this.
|
|
115
|
-
|
|
116
|
-
|
|
109
|
+
case 'ChanExpression':
|
|
110
|
+
return [this.chan(node), POSTFIX];
|
|
111
|
+
case 'StreamChanExpression':
|
|
112
|
+
return [this.streamChan(node), POSTFIX];
|
|
113
|
+
case 'QueueExpression':
|
|
114
|
+
return [this.queue(node), POSTFIX];
|
|
117
115
|
// The worker module has the function under the same name, a literal under the name given to
|
|
118
116
|
// it, after the local names it uses; the arguments are copied there. In the worker module a
|
|
119
117
|
// literal runs in the same worker, with copies of those names as its first parameters.
|
|
@@ -129,7 +127,6 @@ export class ExpressionEmitter extends Emitter {
|
|
|
129
127
|
const fn = literal && this.inWorker
|
|
130
128
|
? this.expression({ ...literal, params: [...goParams(literal, captured), ...literal.params] }, ARROW)
|
|
131
129
|
: JSON.stringify(goTarget(this.program, node));
|
|
132
|
-
this.helpers.add(this.inWorker ? 'goLocal' : 'go');
|
|
133
130
|
return [`$$go(${fn}, [${args.filter((arg) => arg !== '').join(', ')}])`, POSTFIX];
|
|
134
131
|
}
|
|
135
132
|
case 'UnaryExpression':
|
|
@@ -225,7 +222,6 @@ export class ExpressionEmitter extends Emitter {
|
|
|
225
222
|
}
|
|
226
223
|
unary(node) {
|
|
227
224
|
if (node.operator === '<-') {
|
|
228
|
-
this.helpers.add('chan');
|
|
229
225
|
const channel = this.expression(node.argument, ARROW);
|
|
230
226
|
return `await $$receive(${channel}${this.commaOk.has(node) ? ', true' : ''})`;
|
|
231
227
|
}
|
|
@@ -22,7 +22,6 @@ export class FunctionEmitter extends WebComponentEmitter {
|
|
|
22
22
|
this.statementsWithDefers(body.body);
|
|
23
23
|
}
|
|
24
24
|
else if (mode === 'stack') {
|
|
25
|
-
this.helpers.add('runDeferred');
|
|
26
25
|
this.line('const $$defer = [];');
|
|
27
26
|
const statements = this.block(() => this.statements(body.body));
|
|
28
27
|
const finalizer = this.block(() => this.line('$$runDeferred($$defer);'));
|
|
@@ -60,7 +59,6 @@ export class FunctionEmitter extends WebComponentEmitter {
|
|
|
60
59
|
effectBody(body, cleanups) {
|
|
61
60
|
return this.withFunction('effect', [], () => {
|
|
62
61
|
this.fn.deferList = cleanups;
|
|
63
|
-
this.helpers.add('runDeferred');
|
|
64
62
|
return this.block(() => this.withScope(declarationsOf(body.body), () => {
|
|
65
63
|
this.line(`$$runDeferred(${cleanups}.splice(0));`);
|
|
66
64
|
this.statements(body.body);
|
package/dist/codegen/js.d.ts
CHANGED
|
@@ -3,18 +3,11 @@ import type { JsOptions } from './emitter.ts';
|
|
|
3
3
|
export type { JsOptions } from './emitter.ts';
|
|
4
4
|
/** A component whose inlined copies add more lines than this to its module becomes a function. */
|
|
5
5
|
export declare const INLINE_LIMIT = 100;
|
|
6
|
-
/**
|
|
7
|
-
* A module whose code creates more elements than this creates them with $$element, which takes
|
|
8
|
-
* their attributes: its definition takes less than the statements it saves.
|
|
9
|
-
*/
|
|
10
|
-
export declare const ELEMENT_HELPER_LIMIT = 20;
|
|
11
6
|
/**
|
|
12
7
|
* Generates a readable ES module from a program that has parsed without errors. A component is
|
|
13
8
|
* inlined where it is used, unless that adds more than INLINE_LIMIT lines compared to a function
|
|
14
9
|
* and its calls. The module is generated again with each such candidate as a function, and the one
|
|
15
10
|
* that saves the most goes first: that changes the sizes of the components inside it. A component
|
|
16
11
|
* of another module is a candidate too; its function is in that module, and counts as one copy.
|
|
17
|
-
* Then, if the code creates more than ELEMENT_HELPER_LIMIT elements, it is generated again with
|
|
18
|
-
* the helper that creates them.
|
|
19
12
|
*/
|
|
20
13
|
export declare function generateJs(program: ast.Program, options: JsOptions): string;
|
package/dist/codegen/js.js
CHANGED
|
@@ -1,31 +1,16 @@
|
|
|
1
1
|
import { declarationsOf } from "./analysis.js";
|
|
2
2
|
import { FunctionEmitter } from "./functions.js";
|
|
3
|
-
import {
|
|
4
|
-
import { HELPER_NEEDS, HELPERS } from "./helpers.js";
|
|
3
|
+
import { moduleGo, runtimeCode, runtimeNames } from "./runtime.js";
|
|
5
4
|
/** A component whose inlined copies add more lines than this to its module becomes a function. */
|
|
6
5
|
export const INLINE_LIMIT = 100;
|
|
7
|
-
/**
|
|
8
|
-
* A module whose code creates more elements than this creates them with $$element, which takes
|
|
9
|
-
* their attributes: its definition takes less than the statements it saves.
|
|
10
|
-
*/
|
|
11
|
-
export const ELEMENT_HELPER_LIMIT = 20;
|
|
12
6
|
/**
|
|
13
7
|
* Generates a readable ES module from a program that has parsed without errors. A component is
|
|
14
8
|
* inlined where it is used, unless that adds more than INLINE_LIMIT lines compared to a function
|
|
15
9
|
* and its calls. The module is generated again with each such candidate as a function, and the one
|
|
16
10
|
* that saves the most goes first: that changes the sizes of the components inside it. A component
|
|
17
11
|
* of another module is a candidate too; its function is in that module, and counts as one copy.
|
|
18
|
-
* Then, if the code creates more than ELEMENT_HELPER_LIMIT elements, it is generated again with
|
|
19
|
-
* the helper that creates them.
|
|
20
12
|
*/
|
|
21
13
|
export function generateJs(program, options) {
|
|
22
|
-
const { code, functions, elements } = chooseFunctions(program, options);
|
|
23
|
-
if (elements <= ELEMENT_HELPER_LIMIT)
|
|
24
|
-
return code;
|
|
25
|
-
return new JsGenerator(program, { ...options, functions, elementHelper: true }).generate();
|
|
26
|
-
}
|
|
27
|
-
/** The code with the components that become functions; see generateJs. */
|
|
28
|
-
function chooseFunctions(program, options) {
|
|
29
14
|
const functions = new Set();
|
|
30
15
|
const generate = (names) => {
|
|
31
16
|
const generator = new JsGenerator(program, { ...options, functions: names });
|
|
@@ -47,14 +32,13 @@ function chooseFunctions(program, options) {
|
|
|
47
32
|
if (saved > INLINE_LIMIT && (!best || saved > best.saved))
|
|
48
33
|
best = { ...trial, name, saved };
|
|
49
34
|
}
|
|
50
|
-
if (!best)
|
|
51
|
-
return
|
|
52
|
-
}
|
|
35
|
+
if (!best)
|
|
36
|
+
return current.code;
|
|
53
37
|
functions.add(best.name);
|
|
54
38
|
current = best;
|
|
55
39
|
}
|
|
56
40
|
}
|
|
57
|
-
/** The whole module: its statements, plus the
|
|
41
|
+
/** The whole module: its statements, plus the runtime that its code needs. */
|
|
58
42
|
class JsGenerator extends FunctionEmitter {
|
|
59
43
|
generate() {
|
|
60
44
|
this.withScope(declarationsOf(this.program.body), () => {
|
|
@@ -63,51 +47,41 @@ class JsGenerator extends FunctionEmitter {
|
|
|
63
47
|
this.hiddenExports();
|
|
64
48
|
});
|
|
65
49
|
const { goroutines } = this.options;
|
|
50
|
+
const inWorker = goroutines !== undefined && 'targets' in goroutines;
|
|
66
51
|
// The worker module runs the functions that `go` calls (see goroutines.ts).
|
|
67
|
-
if (
|
|
52
|
+
if (inWorker) {
|
|
68
53
|
const names = goroutines.targets.map((name) => this.name(name));
|
|
69
|
-
this.helpers.add('serve');
|
|
70
54
|
this.lines.push('', `const $$goroutines = { ${names.join(', ')} };`, '$$serve($$goroutines);');
|
|
71
55
|
}
|
|
56
|
+
let imports = 0;
|
|
57
|
+
while (this.lines[imports]?.startsWith('import '))
|
|
58
|
+
imports++;
|
|
59
|
+
const rest = this.lines.slice(imports);
|
|
72
60
|
// The CSS of the components goes before the code, so that the page has it before the markup.
|
|
73
61
|
const styles = this.styleRegistrations();
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
const
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
};
|
|
86
|
-
for (const helper of this.helpers)
|
|
87
|
-
add(helper);
|
|
88
|
-
const workerUrl = goroutines && 'workerUrl' in goroutines ? goroutines.workerUrl : '';
|
|
89
|
-
const helpers = [...needed]
|
|
90
|
-
.sort()
|
|
91
|
-
.flatMap((helper, i) => [
|
|
92
|
-
...(i > 0 ? [''] : []),
|
|
93
|
-
...HELPERS[helper],
|
|
94
|
-
...(helper === 'go' ? ['', ...startWorker(workerUrl)] : []),
|
|
95
|
-
]);
|
|
96
|
-
let imports = 0;
|
|
97
|
-
while (lines[imports]?.startsWith('import '))
|
|
98
|
-
imports++;
|
|
99
|
-
const rest = lines.slice(imports);
|
|
100
|
-
while (rest[0] === '')
|
|
101
|
-
rest.shift();
|
|
102
|
-
lines = [
|
|
103
|
-
...lines.slice(0, imports),
|
|
104
|
-
...(imports > 0 ? [''] : []),
|
|
105
|
-
...helpers,
|
|
106
|
-
...(styles.length > 0 ? ['', ...styles] : []),
|
|
107
|
-
...(rest.length > 0 ? [''] : []),
|
|
108
|
-
...rest,
|
|
109
|
-
];
|
|
62
|
+
// What is the module's own: its `go`, and the CSS of its components for their shadow roots.
|
|
63
|
+
const own = [
|
|
64
|
+
...(/(?<![.\w$])\$\$go(?![\w$])/.test(rest.join('\n'))
|
|
65
|
+
? moduleGo(inWorker ? null : (goroutines?.workerUrl ?? ''))
|
|
66
|
+
: []),
|
|
67
|
+
...(styles.length > 0 ? ['const $$styles = [];'] : []),
|
|
68
|
+
];
|
|
69
|
+
const mode = this.options.runtime ?? 'import';
|
|
70
|
+
const runtime = runtimeCode(runtimeNames([...own, ...styles, ...rest].join('\n')), mode);
|
|
71
|
+
if (runtime.length === 0 && own.length === 0 && styles.length === 0) {
|
|
72
|
+
return this.lines.length === 0 ? '' : `${this.lines.join('\n')}\n`;
|
|
110
73
|
}
|
|
111
|
-
|
|
74
|
+
while (rest[0] === '')
|
|
75
|
+
rest.shift();
|
|
76
|
+
// The import of the runtime goes with the other imports; the runtime itself, after them.
|
|
77
|
+
const head = this.lines.slice(0, imports);
|
|
78
|
+
const sections = [
|
|
79
|
+
mode === 'import' ? [...head, ...runtime] : head,
|
|
80
|
+
mode === 'inline' ? runtime : [],
|
|
81
|
+
own,
|
|
82
|
+
styles,
|
|
83
|
+
rest,
|
|
84
|
+
].filter((section) => section.length > 0);
|
|
85
|
+
return `${sections.flatMap((section, i) => [...(i > 0 ? [''] : []), ...section]).join('\n')}\n`;
|
|
112
86
|
}
|
|
113
87
|
}
|
package/dist/codegen/parallel.js
CHANGED
|
@@ -4,7 +4,7 @@ import { declaredIn } from "../names.js";
|
|
|
4
4
|
import { ControlStatementEmitter } from "./control-statements.js";
|
|
5
5
|
import { ARROW } from "./syntax.js";
|
|
6
6
|
// `go for i, v in items { ... }`: `await $$goFor("$$goroutine0", items, ...)` splits the array into
|
|
7
|
-
// parts and runs each as a goroutine (go
|
|
7
|
+
// parts and runs each as a goroutine (src/runtime/go.js). In the worker module, the function of a part
|
|
8
8
|
// loops over it: `i` is the index in the whole array, from `$$from`, the index of the part's first
|
|
9
9
|
// element, and `items[i]` is `items[$$i]`, the index in the part. The part goes back if the body
|
|
10
10
|
// writes into it; a typed array is moved back, not copied.
|
|
@@ -24,9 +24,7 @@ export class ParallelLoopEmitter extends ControlStatementEmitter {
|
|
|
24
24
|
this.expression(node.iterable, ARROW),
|
|
25
25
|
...goCaptures(this.program, node).map((name) => this.name(name)),
|
|
26
26
|
];
|
|
27
|
-
this.
|
|
28
|
-
this.helpers.add(this.inWorker ? 'goLocal' : 'go');
|
|
29
|
-
this.line(`await $$goFor(${args.join(', ')});`);
|
|
27
|
+
this.line(`await $$goFor($$go, ${args.join(', ')});`);
|
|
30
28
|
}
|
|
31
29
|
/** The loop over a part, in the function of the worker module that runs it. */
|
|
32
30
|
partLoop(node, loop) {
|
package/dist/codegen/refs.js
CHANGED
|
@@ -65,7 +65,6 @@ export class RefEmitter extends EarlyReturnEmitter {
|
|
|
65
65
|
const scopeClass = scope && !hasClass(element) ? scope : null;
|
|
66
66
|
let creation = { covered: new Set(), scoped: false };
|
|
67
67
|
if (preset) {
|
|
68
|
-
this.createdElements++;
|
|
69
68
|
this.line(`const ${name} = ${preset.name} ?? ${this.createElement(tag, svg)};`);
|
|
70
69
|
}
|
|
71
70
|
else {
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/** Where compiled code imports the runtime from. */
|
|
2
|
+
export declare const RUNTIME_MODULE = "rooklang/runtime";
|
|
3
|
+
/** How a module gets the runtime: by an import, or with the code of the runtime in it. */
|
|
4
|
+
export type RuntimeMode = 'import' | 'inline';
|
|
5
|
+
/** Predeclared functions of goroutines and channels: `close(ch)` is `$$close(ch)`. */
|
|
6
|
+
export declare const PREDECLARED: ReadonlySet<string>;
|
|
7
|
+
/** The names of the runtime that code uses, in order. */
|
|
8
|
+
export declare function runtimeNames(code: string): string[];
|
|
9
|
+
/** What gives a module these names of the runtime: an import, or the files that have them. */
|
|
10
|
+
export declare function runtimeCode(names: readonly string[], mode: RuntimeMode): string[];
|
|
11
|
+
/**
|
|
12
|
+
* The `go` of a module: in a module, a call in a worker that starts with its worker module; in the
|
|
13
|
+
* worker module, a call in the same worker, of a function of `$$goroutines`.
|
|
14
|
+
*/
|
|
15
|
+
export declare function moduleGo(workerUrl: string | null): string[];
|