rooklang 0.4.1 → 0.5.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/CHANGELOG.md +235 -0
- package/CHANGELOG.ru.md +235 -0
- package/dist/aliases.d.ts +2 -0
- package/dist/aliases.js +83 -0
- package/dist/ast/declarations.d.ts +18 -0
- package/dist/ast/expressions.d.ts +14 -1
- package/dist/ast/markup.d.ts +2 -0
- package/dist/ast/statements.d.ts +2 -2
- package/dist/checker/assignments.js +2 -1
- package/dist/checker/base.d.ts +3 -0
- package/dist/checker/base.js +1 -1
- package/dist/checker/builtins.js +5 -1
- package/dist/checker/calls.d.ts +2 -0
- package/dist/checker/calls.js +18 -3
- package/dist/checker/classes.js +8 -0
- package/dist/checker/component-bindings.d.ts +16 -0
- package/dist/checker/component-bindings.js +65 -2
- package/dist/checker/context.d.ts +12 -0
- package/dist/checker/declarations.d.ts +2 -0
- package/dist/checker/declarations.js +38 -3
- package/dist/checker/expressions.d.ts +1 -0
- package/dist/checker/expressions.js +17 -1
- package/dist/checker/flow.d.ts +1 -1
- package/dist/checker/flow.js +10 -0
- package/dist/checker/functions.d.ts +2 -0
- package/dist/checker/functions.js +33 -7
- package/dist/checker/goroutines.d.ts +2 -2
- package/dist/checker/goroutines.js +2 -2
- package/dist/checker/imports.d.ts +10 -0
- package/dist/checker/imports.js +73 -6
- package/dist/checker/initializers.d.ts +12 -0
- package/dist/checker/initializers.js +80 -0
- package/dist/checker/loops.js +3 -1
- package/dist/checker/members.js +1 -0
- package/dist/checker/resolution.d.ts +2 -0
- package/dist/checker/resolution.js +13 -2
- package/dist/checker/statements.d.ts +5 -0
- package/dist/checker/statements.js +26 -3
- package/dist/checker/tagged-unions.d.ts +6 -0
- package/dist/checker/tagged-unions.js +31 -1
- package/dist/checker/tags.d.ts +6 -0
- package/dist/checker/tags.js +44 -0
- package/dist/checker/types/constructors.js +10 -1
- package/dist/checker/types/params.js +33 -7
- package/dist/checker/types/printing.js +5 -1
- package/dist/checker/types/relations.js +2 -1
- package/dist/codegen/analysis.d.ts +6 -0
- package/dist/codegen/analysis.js +24 -0
- package/dist/codegen/calls.d.ts +2 -0
- package/dist/codegen/calls.js +23 -1
- package/dist/codegen/component-props.js +8 -1
- package/dist/codegen/control-flow.d.ts +3 -0
- package/dist/codegen/control-flow.js +17 -0
- package/dist/codegen/expressions.d.ts +4 -1
- package/dist/codegen/expressions.js +4 -15
- package/dist/codegen/functions.d.ts +2 -0
- package/dist/codegen/functions.js +13 -0
- package/dist/codegen/mutations.d.ts +6 -0
- package/dist/codegen/mutations.js +36 -0
- package/dist/codegen/options.d.ts +2 -0
- package/dist/codegen/reactive.js +9 -5
- package/dist/codegen/simple-statements.js +3 -1
- package/dist/codegen/statements.d.ts +4 -0
- package/dist/codegen/statements.js +37 -1
- package/dist/codegen/updates.d.ts +6 -1
- package/dist/codegen/updates.js +13 -2
- package/dist/compile.js +2 -0
- package/dist/completion.js +2 -1
- package/dist/declarations.js +18 -0
- package/dist/lexer/base.d.ts +3 -2
- package/dist/lexer/base.js +14 -5
- package/dist/lexer/lexer.js +3 -0
- package/dist/lexer/token.d.ts +6 -1
- package/dist/lexer/token.js +0 -1
- package/dist/modules.js +4 -3
- package/dist/parser/base.d.ts +5 -0
- package/dist/parser/base.js +9 -1
- package/dist/parser/classes.js +4 -3
- package/dist/parser/declarations.d.ts +2 -0
- package/dist/parser/declarations.js +25 -2
- package/dist/parser/decorators.js +1 -1
- package/dist/parser/expressions.js +6 -0
- package/dist/parser/literals.d.ts +2 -4
- package/dist/parser/literals.js +12 -24
- package/dist/parser/objects.d.ts +8 -0
- package/dist/parser/objects.js +51 -0
- package/dist/parser/params.d.ts +37 -4
- package/dist/parser/params.js +143 -9
- package/dist/parser/postfix.d.ts +5 -0
- package/dist/parser/postfix.js +36 -0
- package/dist/parser/simple-statements.d.ts +2 -0
- package/dist/parser/simple-statements.js +50 -0
- package/dist/parser/statements.js +8 -6
- package/dist/parser/syntax.js +0 -1
- package/dist/parser/types.d.ts +0 -4
- package/dist/parser/types.js +17 -32
- package/dist/project-modules.js +21 -0
- package/dist/reexports.d.ts +4 -0
- package/dist/reexports.js +56 -0
- package/dist/typescript/importer.d.ts +4 -0
- package/dist/typescript/importer.js +27 -3
- package/dist/typescript/library.js +5 -2
- package/dist/walk.js +12 -0
- package/package.json +4 -2
package/dist/codegen/calls.js
CHANGED
|
@@ -5,7 +5,7 @@ import { isYieldCall } from "../iterators.js";
|
|
|
5
5
|
import { TYPED_ARRAYS, TYPED_ELEMENTS } from "../typed-arrays.js";
|
|
6
6
|
import { hasOptionalLink } from "./analysis.js";
|
|
7
7
|
import { ExpressionEmitter } from "./expressions.js";
|
|
8
|
-
import { ARROW, BINARY, POSTFIX } from "./syntax.js";
|
|
8
|
+
import { ARROW, BINARY, POSTFIX, PRIMARY } from "./syntax.js";
|
|
9
9
|
// Calls, `new`, array literals and conversions. A `[]byte` is a Uint8Array: a literal where the
|
|
10
10
|
// checker found one expected becomes `new Uint8Array([...])`, and the conversions of Go between
|
|
11
11
|
// text and its UTF-8 bytes use TextEncoder and TextDecoder, `[]byte(s)` and `string(data)`. In an
|
|
@@ -43,6 +43,28 @@ export class CallEmitter extends ExpressionEmitter {
|
|
|
43
43
|
* given to it, after the local names it uses; the arguments are copied there. In the worker
|
|
44
44
|
* module a literal runs in the same worker, with copies of those names as its first parameters.
|
|
45
45
|
*/
|
|
46
|
+
objectLiteral(node) {
|
|
47
|
+
if (node.properties.length === 0)
|
|
48
|
+
return '{}';
|
|
49
|
+
const properties = node.properties.map((property) => {
|
|
50
|
+
if (property.kind === 'SpreadElement') {
|
|
51
|
+
return `...${this.expression(property.argument, ARROW)}`;
|
|
52
|
+
}
|
|
53
|
+
const key = property.key.kind === 'StringLiteral' ? property.key.raw : property.key.name;
|
|
54
|
+
// `fetch(req) { ... }`: a method of JS, whose `this` is the object.
|
|
55
|
+
if (property.method && property.value.kind === 'FuncExpression') {
|
|
56
|
+
const [params, body] = this.func(property.value.params, property.value.body);
|
|
57
|
+
return `${property.value.async ? 'async ' : ''}${key}(${params}) ${body}`;
|
|
58
|
+
}
|
|
59
|
+
const value = this.expression(property.value, ARROW);
|
|
60
|
+
// A renamed shorthand (`{ delete }` → `delete$`) must keep its key.
|
|
61
|
+
return property.shorthand && value === key ? key : `${key}: ${value}`;
|
|
62
|
+
});
|
|
63
|
+
return `{ ${properties.join(', ')} }`;
|
|
64
|
+
}
|
|
65
|
+
taggedTemplate(node) {
|
|
66
|
+
return `${this.expression(node.tag, POSTFIX)}${this.expression(node.quasi, PRIMARY)}`;
|
|
67
|
+
}
|
|
46
68
|
go(node) {
|
|
47
69
|
const { callee } = node.call;
|
|
48
70
|
const literal = callee.kind === 'FuncExpression' ? callee : null;
|
|
@@ -58,10 +58,17 @@ export class ComponentPropsEmitter extends ControlFlowEmitter {
|
|
|
58
58
|
*/
|
|
59
59
|
binder(target) {
|
|
60
60
|
const name = `$$bind${this.nextId()}`;
|
|
61
|
+
const list = target.kind === 'Identifier' ? this.loopLists.get(target.name) : undefined;
|
|
61
62
|
const text = this.liveExpression(target);
|
|
62
63
|
// `todos` of `bind:todos={todos}`, or the state that `bind:open={menu.open}` writes into.
|
|
63
|
-
const written = [...(this.reactive?.rootSources(target) ?? [])].filter((source) => source.kind === 'state');
|
|
64
|
+
const written = [...(this.reactive?.rootSources(list ?? target) ?? [])].filter((source) => source.kind === 'state');
|
|
64
65
|
const body = this.block(() => {
|
|
66
|
+
// The item of `{for row in rows}`: a new one replaces it in the list, wherever it is now.
|
|
67
|
+
if (list) {
|
|
68
|
+
const items = this.liveExpression(list, POSTFIX);
|
|
69
|
+
this.line(`const $$at = ${items}.indexOf(${text});`);
|
|
70
|
+
this.line(`if ($$at !== -1) ${items}[$$at] = $$value;`);
|
|
71
|
+
}
|
|
65
72
|
this.line(`${text} = $$value;`);
|
|
66
73
|
for (const name of rootsOf(target, this.shared)) {
|
|
67
74
|
this.line(`$$changed(${this.shared.listeners.get(name)});`);
|
|
@@ -8,6 +8,8 @@ import { type Live, type Source } from './reactive.ts';
|
|
|
8
8
|
* returns its update.
|
|
9
9
|
*/
|
|
10
10
|
export declare abstract class ControlFlowEmitter extends ElementEmitter {
|
|
11
|
+
/** The lists of the `{for}` loops around the code, by the names of their items. */
|
|
12
|
+
protected readonly loopLists: Map<string, ast.Expression>;
|
|
11
13
|
/** Writes the code that creates the content and returns what to append; see the class. */
|
|
12
14
|
protected liveControl(statement: ControlStatement, live: Live, sources: ReadonlySet<Source>): string;
|
|
13
15
|
/** The choice of an if/else chain, `() => a ? 0 : b ? 1 : -1`, and its blocks. */
|
|
@@ -20,6 +22,7 @@ export declare abstract class ControlFlowEmitter extends ElementEmitter {
|
|
|
20
22
|
* state go into its update. The block of `<Page />` gets the component, `($$component3) => ...`.
|
|
21
23
|
*/
|
|
22
24
|
protected blockCreator(statements: readonly ast.Statement[], parent: Live, loop?: ast.ForInStatement, component?: ast.Identifier): string;
|
|
25
|
+
private blockFunction;
|
|
23
26
|
/** The key of a list, `with key todo.id`: `(todo) => todo.id`, which its blocks are found by. */
|
|
24
27
|
private listKey;
|
|
25
28
|
/** A classic `for` loop in live markup: a function that runs it and returns the content. */
|
|
@@ -9,6 +9,8 @@ import { CONDITIONAL, indentMore, upperFirst } from "./syntax.js";
|
|
|
9
9
|
* returns its update.
|
|
10
10
|
*/
|
|
11
11
|
export class ControlFlowEmitter extends ElementEmitter {
|
|
12
|
+
/** The lists of the `{for}` loops around the code, by the names of their items. */
|
|
13
|
+
loopLists = new Map();
|
|
12
14
|
/** Writes the code that creates the content and returns what to append; see the class. */
|
|
13
15
|
liveControl(statement, live, sources) {
|
|
14
16
|
const id = this.nextId();
|
|
@@ -88,6 +90,21 @@ export class ControlFlowEmitter extends ElementEmitter {
|
|
|
88
90
|
blockCreator(statements, parent, loop, component) {
|
|
89
91
|
const block = new Block(parent);
|
|
90
92
|
const params = loop ? loopParameters(loop) : component ? [parameter(component)] : [];
|
|
93
|
+
const saved = loop && this.loopLists.get(loop.value.name);
|
|
94
|
+
// `bind:row={row}` in the block writes into the list.
|
|
95
|
+
if (loop)
|
|
96
|
+
this.loopLists.set(loop.value.name, loop.iterable);
|
|
97
|
+
try {
|
|
98
|
+
return this.blockFunction(block, statements, params, loop);
|
|
99
|
+
}
|
|
100
|
+
finally {
|
|
101
|
+
if (loop && saved)
|
|
102
|
+
this.loopLists.set(loop.value.name, saved);
|
|
103
|
+
else if (loop)
|
|
104
|
+
this.loopLists.delete(loop.value.name);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
blockFunction(block, statements, params, loop) {
|
|
91
108
|
return this.withFunction('none', params, () => this.withRendering(this.rendering + 1, () => {
|
|
92
109
|
const paramList = this.params(params);
|
|
93
110
|
const fragment = `$$block${this.nextId()}`;
|
|
@@ -22,6 +22,10 @@ export declare abstract class ExpressionEmitter extends Emitter {
|
|
|
22
22
|
protected abstract streamChan(node: ast.StreamChanExpression): string;
|
|
23
23
|
protected abstract queue(node: ast.QueueExpression): string;
|
|
24
24
|
protected abstract topic(node: ast.TopicExpression): string;
|
|
25
|
+
/** `{ a: 1, ...b, f() { ... } }`; implemented by CallEmitter. */
|
|
26
|
+
protected abstract objectLiteral(node: ast.ObjectLiteral): string;
|
|
27
|
+
/** sql`select ... ${id}`; implemented by CallEmitter. */
|
|
28
|
+
protected abstract taggedTemplate(node: ast.TaggedTemplateExpression): string;
|
|
25
29
|
/** `go f(x)`; implemented by CallEmitter. */
|
|
26
30
|
protected abstract go(node: ast.GoExpression): string;
|
|
27
31
|
/** `decode<T>(x)`; implemented by DecodeEmitter. */
|
|
@@ -54,7 +58,6 @@ export declare abstract class ExpressionEmitter extends Emitter {
|
|
|
54
58
|
private expressionWithPrecedence;
|
|
55
59
|
protected elements(elements: readonly (ast.Expression | ast.SpreadElement)[]): string;
|
|
56
60
|
private template;
|
|
57
|
-
private objectLiteral;
|
|
58
61
|
private arrowFunction;
|
|
59
62
|
private unary;
|
|
60
63
|
private binary;
|
|
@@ -82,9 +82,12 @@ export class ExpressionEmitter extends Emitter {
|
|
|
82
82
|
return [decoratedName(node.decorator.name, node.member.name), PRIMARY];
|
|
83
83
|
case 'ThisExpression':
|
|
84
84
|
return ['this', PRIMARY];
|
|
85
|
-
// `as`
|
|
85
|
+
// `as` and `user!` are only for the checker.
|
|
86
86
|
case 'AsExpression':
|
|
87
|
+
case 'NonNullExpression':
|
|
87
88
|
return this.expressionWithPrecedence(node.expression);
|
|
89
|
+
case 'TaggedTemplateExpression':
|
|
90
|
+
return [this.taggedTemplate(node), POSTFIX];
|
|
88
91
|
case 'SuperExpression':
|
|
89
92
|
return ['super', PRIMARY];
|
|
90
93
|
case 'ArrayLiteral': {
|
|
@@ -171,20 +174,6 @@ export class ExpressionEmitter extends Emitter {
|
|
|
171
174
|
});
|
|
172
175
|
return `${text}\``;
|
|
173
176
|
}
|
|
174
|
-
objectLiteral(node) {
|
|
175
|
-
if (node.properties.length === 0)
|
|
176
|
-
return '{}';
|
|
177
|
-
const properties = node.properties.map((property) => {
|
|
178
|
-
if (property.kind === 'SpreadElement') {
|
|
179
|
-
return `...${this.expression(property.argument, ARROW)}`;
|
|
180
|
-
}
|
|
181
|
-
const key = property.key.kind === 'StringLiteral' ? property.key.raw : property.key.name;
|
|
182
|
-
const value = this.expression(property.value, ARROW);
|
|
183
|
-
// A renamed shorthand (`{ delete }` → `delete$`) must keep its key.
|
|
184
|
-
return property.shorthand && value === key ? key : `${key}: ${value}`;
|
|
185
|
-
});
|
|
186
|
-
return `{ ${properties.join(', ')} }`;
|
|
187
|
-
}
|
|
188
177
|
arrowFunction(node) {
|
|
189
178
|
const { body } = node;
|
|
190
179
|
const async = node.async ? 'async ' : '';
|
|
@@ -2,6 +2,8 @@ import type * as ast from '../ast.ts';
|
|
|
2
2
|
import { WebComponentEmitter } from './web-components.ts';
|
|
3
3
|
/** Function bodies and `defer`. */
|
|
4
4
|
export declare abstract class FunctionEmitter extends WebComponentEmitter {
|
|
5
|
+
/** `a, times = 1, ...parts`: default values and the rest parameter are those of JS. */
|
|
6
|
+
protected params(params: ast.Parameter[]): string;
|
|
5
7
|
/** Parameters and body of a function, method or func literal. */
|
|
6
8
|
protected func(params: ast.Parameter[], body: ast.BlockStatement): [string, string];
|
|
7
9
|
/**
|
|
@@ -4,6 +4,19 @@ import { WebComponentEmitter } from "./web-components.js";
|
|
|
4
4
|
import { ARROW, POSTFIX } from "./syntax.js";
|
|
5
5
|
/** Function bodies and `defer`. */
|
|
6
6
|
export class FunctionEmitter extends WebComponentEmitter {
|
|
7
|
+
/** `a, times = 1, ...parts`: default values and the rest parameter are those of JS. */
|
|
8
|
+
params(params) {
|
|
9
|
+
const names = super.params(params).split(', ');
|
|
10
|
+
return params
|
|
11
|
+
.map((param, i) => {
|
|
12
|
+
if (param.rest)
|
|
13
|
+
return `...${names[i]}`;
|
|
14
|
+
return param.defaultValue
|
|
15
|
+
? `${names[i]} = ${this.expression(param.defaultValue, ARROW)}`
|
|
16
|
+
: names[i];
|
|
17
|
+
})
|
|
18
|
+
.join(', ');
|
|
19
|
+
}
|
|
7
20
|
/** Parameters and body of a function, method or func literal. */
|
|
8
21
|
func(params, body) {
|
|
9
22
|
const defers = findDefers(body);
|
|
@@ -4,6 +4,12 @@ import type { Type } from '../checker/types.ts';
|
|
|
4
4
|
export declare function rootName(node: ast.Expression): string | null;
|
|
5
5
|
/** Names whose values a node reads, not counting code that runs on events. */
|
|
6
6
|
export declare function readNames(node: ast.Node, names: Set<string>): void;
|
|
7
|
+
/**
|
|
8
|
+
* Names of the functions that code may run while it runs: those it calls and those it gives to a
|
|
9
|
+
* call, `items.map(format)`. A function it only gives as a value, `act={copy}` or
|
|
10
|
+
* `act={() => show("x")}`, runs later, when whoever gets it calls it.
|
|
11
|
+
*/
|
|
12
|
+
export declare function calledNames(node: ast.Node, names: Set<string>): void;
|
|
7
13
|
/**
|
|
8
14
|
* Names an effect reads while it runs: not in the functions it creates, which run later, nor in
|
|
9
15
|
* its `defer`, nor the variables it writes, or `count++` would run it again.
|
|
@@ -57,6 +57,42 @@ export function readNames(node, names) {
|
|
|
57
57
|
forEachChild(node, (child) => readNames(child, names));
|
|
58
58
|
}
|
|
59
59
|
}
|
|
60
|
+
/**
|
|
61
|
+
* Names of the functions that code may run while it runs: those it calls and those it gives to a
|
|
62
|
+
* call, `items.map(format)`. A function it only gives as a value, `act={copy}` or
|
|
63
|
+
* `act={() => show("x")}`, runs later, when whoever gets it calls it.
|
|
64
|
+
*/
|
|
65
|
+
export function calledNames(node, names) {
|
|
66
|
+
switch (node.kind) {
|
|
67
|
+
case 'CallExpression':
|
|
68
|
+
if (node.callee.kind === 'Identifier')
|
|
69
|
+
names.add(node.callee.name);
|
|
70
|
+
else
|
|
71
|
+
calledNames(node.callee, names);
|
|
72
|
+
for (const arg of node.arguments) {
|
|
73
|
+
if (arg.kind === 'Identifier')
|
|
74
|
+
names.add(arg.name);
|
|
75
|
+
else if (arg.kind === 'ArrowFunction' || arg.kind === 'FuncExpression') {
|
|
76
|
+
forEachChild(arg, (child) => calledNames(child, names));
|
|
77
|
+
}
|
|
78
|
+
else
|
|
79
|
+
calledNames(arg, names);
|
|
80
|
+
}
|
|
81
|
+
return;
|
|
82
|
+
case 'ArrowFunction':
|
|
83
|
+
case 'FuncExpression':
|
|
84
|
+
case 'EventHandler':
|
|
85
|
+
case 'MountStatement':
|
|
86
|
+
case 'EffectStatement':
|
|
87
|
+
return;
|
|
88
|
+
case 'JsxAttribute':
|
|
89
|
+
if (node.value !== null && !/^on[A-Z]/.test(node.name.name))
|
|
90
|
+
calledNames(node.value, names);
|
|
91
|
+
return;
|
|
92
|
+
default:
|
|
93
|
+
forEachChild(node, (child) => calledNames(child, names));
|
|
94
|
+
}
|
|
95
|
+
}
|
|
60
96
|
/**
|
|
61
97
|
* Names an effect reads while it runs: not in the functions it creates, which run later, nor in
|
|
62
98
|
* its `defer`, nor the variables it writes, or `count++` would run it again.
|
|
@@ -33,6 +33,8 @@ export interface JsOptions {
|
|
|
33
33
|
dataClasses?: ReadonlySet<string> | undefined;
|
|
34
34
|
/** State of other modules that this one imports, `local → name there`: it only reads it. */
|
|
35
35
|
shared?: ReadonlyMap<string, string> | undefined;
|
|
36
|
+
/** What each `export { ... } from` passes on in JS, with hidden names (reexports.ts). */
|
|
37
|
+
reexports?: ReadonlyMap<ast.ExportFromDeclaration, readonly string[]> | undefined;
|
|
36
38
|
}
|
|
37
39
|
/** A decorator of another module, with the types of the expressions of that module. */
|
|
38
40
|
export interface ImportedDecorator extends DecoratorSource {
|
package/dist/codegen/reactive.js
CHANGED
|
@@ -2,7 +2,7 @@ import { ownStatements } from "../checker/helpers.js";
|
|
|
2
2
|
import { wrappedMarkup } from "../decorators.js";
|
|
3
3
|
import { containsReturn, returnedMarkup } from "../recursion.js";
|
|
4
4
|
import { forEachChild } from "../walk.js";
|
|
5
|
-
import { readNames, rootName, writeTargets } from "./mutations.js";
|
|
5
|
+
import { calledNames, readNames, rootName, writeTargets } from "./mutations.js";
|
|
6
6
|
// Reactivity of components: which markup depends on which state, and which code changes it.
|
|
7
7
|
export { Block, updatesOf } from "./live.js";
|
|
8
8
|
const EMPTY = new Set();
|
|
@@ -69,19 +69,23 @@ export class Reactive {
|
|
|
69
69
|
...wrappedMarkup(component),
|
|
70
70
|
];
|
|
71
71
|
const seen = new Set();
|
|
72
|
-
|
|
72
|
+
// A function the markup only gives away, `act={copy}`, runs later and keeps its updates.
|
|
73
|
+
const visit = (node, body = false) => {
|
|
73
74
|
const names = new Set();
|
|
74
|
-
|
|
75
|
+
if (body)
|
|
76
|
+
forEachChild(node, (child) => calledNames(child, names));
|
|
77
|
+
else
|
|
78
|
+
calledNames(node, names);
|
|
75
79
|
for (const name of names) {
|
|
76
80
|
const fn = this.functions.get(name);
|
|
77
81
|
if (!fn || seen.has(name))
|
|
78
82
|
continue;
|
|
79
83
|
seen.add(name);
|
|
80
84
|
this.renderFunctions.add(fn);
|
|
81
|
-
visit(fn);
|
|
85
|
+
visit(fn, true);
|
|
82
86
|
}
|
|
83
87
|
};
|
|
84
|
-
markup.forEach(visit);
|
|
88
|
+
markup.forEach((node) => visit(node));
|
|
85
89
|
}
|
|
86
90
|
/** Whether a statement of the body declares a function that the live markup calls. */
|
|
87
91
|
declaresRenderFunction(node) {
|
|
@@ -170,8 +170,10 @@ export class SimpleStatementEmitter extends TryEmitter {
|
|
|
170
170
|
case 'EffectStatement':
|
|
171
171
|
this.effectStatement(node);
|
|
172
172
|
break;
|
|
173
|
+
// Components write their styles themselves (ComponentEmitter), and StatementEmitter the
|
|
174
|
+
// exports of other modules.
|
|
173
175
|
case 'StyleStatement':
|
|
174
|
-
|
|
176
|
+
case 'ExportFromDeclaration':
|
|
175
177
|
break;
|
|
176
178
|
}
|
|
177
179
|
}
|
|
@@ -20,6 +20,10 @@ export declare abstract class StatementEmitter extends ParallelLoopEmitter {
|
|
|
20
20
|
* decorators uses, and that of exported components when other modules inline them.
|
|
21
21
|
*/
|
|
22
22
|
protected hiddenExports(): void;
|
|
23
|
+
/** `const { name, size: bytes, ...rest } = file`, as in JS. */
|
|
24
|
+
protected variableDeclaration(node: ast.VariableDeclaration): string;
|
|
25
|
+
/** `export { Rows } from "./rows.js"` with the hidden names that go with them (reexports.ts). */
|
|
26
|
+
private exportFrom;
|
|
23
27
|
protected importPath(source: ast.StringLiteral): string;
|
|
24
28
|
protected classDeclaration(node: ast.ClassDeclaration): void;
|
|
25
29
|
/** Visibility modifiers are checked by the compiler and do not exist in the output. */
|
|
@@ -17,6 +17,10 @@ export class StatementEmitter extends ParallelLoopEmitter {
|
|
|
17
17
|
sharedReads = null;
|
|
18
18
|
/** State of the module: the updates of the components that read it are kept beside it. */
|
|
19
19
|
emitStatement(node) {
|
|
20
|
+
if (node.kind === 'ExportFromDeclaration') {
|
|
21
|
+
this.exportFrom(node);
|
|
22
|
+
return;
|
|
23
|
+
}
|
|
20
24
|
super.emitStatement(node);
|
|
21
25
|
if (node.kind !== 'VariableDeclaration' || node.keyword !== 'state')
|
|
22
26
|
return;
|
|
@@ -124,9 +128,34 @@ export class StatementEmitter extends ParallelLoopEmitter {
|
|
|
124
128
|
this.blankLine();
|
|
125
129
|
this.line(`export { ${names.join(', ')} };`);
|
|
126
130
|
}
|
|
131
|
+
/** `const { name, size: bytes, ...rest } = file`, as in JS. */
|
|
132
|
+
variableDeclaration(node) {
|
|
133
|
+
if (!node.pattern)
|
|
134
|
+
return super.variableDeclaration(node);
|
|
135
|
+
const { keys, rest } = node.pattern;
|
|
136
|
+
const fields = keys.map((key, i) => {
|
|
137
|
+
const name = this.name(node.names[i].name);
|
|
138
|
+
const field = key.kind === 'StringLiteral' ? key.raw : key.name;
|
|
139
|
+
return field === name ? name : `${field}: ${name}`;
|
|
140
|
+
});
|
|
141
|
+
if (rest)
|
|
142
|
+
fields.push(`...${this.name(node.names.at(-1).name)}`);
|
|
143
|
+
return `${node.keyword} { ${fields.join(', ')} } = ${this.expression(node.values[0], ARROW)}`;
|
|
144
|
+
}
|
|
145
|
+
/** `export { Rows } from "./rows.js"` with the hidden names that go with them (reexports.ts). */
|
|
146
|
+
exportFrom(node) {
|
|
147
|
+
const source = this.importPath(node.source);
|
|
148
|
+
const names = node.specifiers ? (this.options.reexports?.get(node) ?? []) : null;
|
|
149
|
+
if (names === null)
|
|
150
|
+
this.line(`export * from ${source};`);
|
|
151
|
+
else if (names.length > 0)
|
|
152
|
+
this.line(`export { ${names.join(', ')} } from ${source};`);
|
|
153
|
+
}
|
|
127
154
|
importPath(source) {
|
|
128
155
|
const path = source.value;
|
|
129
|
-
|
|
156
|
+
// `./button.rk` and the aliases `#ui/button.rk` and `@/ui/button.rk` (aliases.ts): the built
|
|
157
|
+
// module is next to its source.
|
|
158
|
+
if (this.options.rewriteImports && !isBare(path) && path.endsWith('.rk')) {
|
|
130
159
|
return JSON.stringify(`${path.slice(0, -'.rk'.length)}.js`);
|
|
131
160
|
}
|
|
132
161
|
return source.raw;
|
|
@@ -235,3 +264,10 @@ function hash(text) {
|
|
|
235
264
|
}
|
|
236
265
|
return value.toString(16).padStart(8, '0');
|
|
237
266
|
}
|
|
267
|
+
/** `pkg/file.rk` of another package, which is not built with this one. */
|
|
268
|
+
function isBare(path) {
|
|
269
|
+
return (!/^\.{1,2}\//.test(path) &&
|
|
270
|
+
!path.startsWith('#') &&
|
|
271
|
+
!path.startsWith('@/') &&
|
|
272
|
+
!path.startsWith('~/'));
|
|
273
|
+
}
|
|
@@ -19,6 +19,8 @@ export declare abstract class UpdateEmitter extends StatementEmitter {
|
|
|
19
19
|
* the markup, which would update it again; code that runs later (handlers) is not included.
|
|
20
20
|
*/
|
|
21
21
|
protected rendering: number;
|
|
22
|
+
/** Functions that live markup gives as values: they run later, not while it is rendered. */
|
|
23
|
+
private readonly later;
|
|
22
24
|
/**
|
|
23
25
|
* After a statement that changes state of the module, the updates of the components that read
|
|
24
26
|
* it; code that renders markup changes none, as with the state of a component.
|
|
@@ -41,9 +43,12 @@ export declare abstract class UpdateEmitter extends StatementEmitter {
|
|
|
41
43
|
protected liveFunction(node: ast.Expression): string;
|
|
42
44
|
/**
|
|
43
45
|
* An expression that is evaluated again on updates: elements inside it are created in place,
|
|
44
|
-
* so that each evaluation creates new ones.
|
|
46
|
+
* so that each evaluation creates new ones. A function that it gives as a value, as to a property
|
|
47
|
+
* `onAdd={() => count++}`, runs later, so what it changes updates the markup as it does in the
|
|
48
|
+
* value the markup was created with.
|
|
45
49
|
*/
|
|
46
50
|
protected liveExpression(node: ast.Expression, precedence?: number): string;
|
|
51
|
+
protected expression(node: ast.Expression, minPrecedence: number, forceParens?: boolean): string;
|
|
47
52
|
}
|
|
48
53
|
/**
|
|
49
54
|
* Replaces the markers of a component's sources: by the call of the update function, by the only
|
package/dist/codegen/updates.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { widenLiterals } from "../checker/types.js";
|
|
2
2
|
import { updatesOf } from "./reactive.js";
|
|
3
3
|
import { StatementEmitter } from "./statements.js";
|
|
4
|
-
import { startsWithObjectLiteral } from "./analysis.js";
|
|
4
|
+
import { functionValues, startsWithObjectLiteral } from "./analysis.js";
|
|
5
5
|
import { sharedWrites } from "./shared.js";
|
|
6
6
|
import { ARROW } from "./syntax.js";
|
|
7
7
|
/**
|
|
@@ -21,6 +21,8 @@ export class UpdateEmitter extends StatementEmitter {
|
|
|
21
21
|
* the markup, which would update it again; code that runs later (handlers) is not included.
|
|
22
22
|
*/
|
|
23
23
|
rendering = 0;
|
|
24
|
+
/** Functions that live markup gives as values: they run later, not while it is rendered. */
|
|
25
|
+
later = new WeakSet();
|
|
24
26
|
/**
|
|
25
27
|
* After a statement that changes state of the module, the updates of the components that read
|
|
26
28
|
* it; code that renders markup changes none, as with the state of a component.
|
|
@@ -157,11 +159,20 @@ export class UpdateEmitter extends StatementEmitter {
|
|
|
157
159
|
}
|
|
158
160
|
/**
|
|
159
161
|
* An expression that is evaluated again on updates: elements inside it are created in place,
|
|
160
|
-
* so that each evaluation creates new ones.
|
|
162
|
+
* so that each evaluation creates new ones. A function that it gives as a value, as to a property
|
|
163
|
+
* `onAdd={() => count++}`, runs later, so what it changes updates the markup as it does in the
|
|
164
|
+
* value the markup was created with.
|
|
161
165
|
*/
|
|
162
166
|
liveExpression(node, precedence = ARROW) {
|
|
167
|
+
for (const value of functionValues(node))
|
|
168
|
+
this.later.add(value);
|
|
163
169
|
return this.withRendering(this.rendering + 1, () => this.withHoisting(false, () => this.expression(node, precedence)));
|
|
164
170
|
}
|
|
171
|
+
expression(node, minPrecedence, forceParens = false) {
|
|
172
|
+
return this.later.has(node)
|
|
173
|
+
? this.withRendering(0, () => super.expression(node, minPrecedence, forceParens))
|
|
174
|
+
: super.expression(node, minPrecedence, forceParens);
|
|
175
|
+
}
|
|
165
176
|
}
|
|
166
177
|
/**
|
|
167
178
|
* Replaces the markers of a component's sources: by the call of the update function, by the only
|
package/dist/compile.js
CHANGED
|
@@ -6,6 +6,7 @@ import { goroutines, goTarget, workerProgram } from "./goroutines.js";
|
|
|
6
6
|
import { withoutMarks } from "./source-map.js";
|
|
7
7
|
import { parse } from "./parser/parser.js";
|
|
8
8
|
import { isStyleModule, styleModule, unusedClasses } from "./style-modules.js";
|
|
9
|
+
import { reexportedNames } from "./reexports.js";
|
|
9
10
|
/**
|
|
10
11
|
* Where the worker module of a module is: a `.worker.js` next to its `.js`, or, where `.rk`
|
|
11
12
|
* files are imported as they are (`rook run`), the module itself with `?goroutines`. Vite drops
|
|
@@ -48,6 +49,7 @@ export function compileModule(source, options = {}) {
|
|
|
48
49
|
runtime: options.runtime,
|
|
49
50
|
dataClasses,
|
|
50
51
|
...importedCode(program, options.importModule),
|
|
52
|
+
reexports: reexportedNames(program, options.importModule),
|
|
51
53
|
};
|
|
52
54
|
const sourceName = options.sourceName ?? 'main.rk';
|
|
53
55
|
const main = withoutMarks(generateJs(program, {
|
package/dist/completion.js
CHANGED
|
@@ -16,7 +16,8 @@ const STRINGS = new Set([
|
|
|
16
16
|
'CssText',
|
|
17
17
|
'RegExp',
|
|
18
18
|
]);
|
|
19
|
-
|
|
19
|
+
// `type` is a keyword only at the start of a statement, where it is offered too.
|
|
20
|
+
const VALUE_KEYWORDS = [...KEYWORDS, 'type'];
|
|
20
21
|
// `queue` is a type of its own only in a type: elsewhere it is a name.
|
|
21
22
|
const TYPE_KEYWORDS = ['func', 'comp', 'chan', 'queue', 'topic'];
|
|
22
23
|
/** Keywords only at the start of a statement at the top level of a component or a decorator. */
|
package/dist/declarations.js
CHANGED
|
@@ -29,6 +29,10 @@ class DeclarationPrinter {
|
|
|
29
29
|
case 'ImportDeclaration':
|
|
30
30
|
this.importDeclaration(statement);
|
|
31
31
|
break;
|
|
32
|
+
case 'ExportFromDeclaration':
|
|
33
|
+
exports = true;
|
|
34
|
+
this.exportFrom(statement);
|
|
35
|
+
break;
|
|
32
36
|
case 'FuncDeclaration':
|
|
33
37
|
if (statement.exported)
|
|
34
38
|
this.functionDeclaration(statement);
|
|
@@ -73,6 +77,20 @@ class DeclarationPrinter {
|
|
|
73
77
|
const source = node.source.value.replace(/\.rk$/, '.js');
|
|
74
78
|
this.lines.push(`import ${parts.join(', ')} from ${JSON.stringify(source)};`);
|
|
75
79
|
}
|
|
80
|
+
/** `export { Row } from "./rows.js"`: the values and types it passes on; components have none. */
|
|
81
|
+
exportFrom(node) {
|
|
82
|
+
const source = JSON.stringify(node.source.value.replace(/\.rk$/, '.js'));
|
|
83
|
+
if (!node.specifiers) {
|
|
84
|
+
this.lines.push(`export * from ${source};`);
|
|
85
|
+
return;
|
|
86
|
+
}
|
|
87
|
+
const { exports } = this.checked;
|
|
88
|
+
const names = node.specifiers
|
|
89
|
+
.filter(({ local }) => exports.values.has(local.name) || exports.types.has(local.name))
|
|
90
|
+
.map(({ imported, local }) => imported.name === local.name ? local.name : `${imported.name} as ${local.name}`);
|
|
91
|
+
if (names.length > 0)
|
|
92
|
+
this.lines.push(`export { ${names.join(', ')} } from ${source};`);
|
|
93
|
+
}
|
|
76
94
|
functionDeclaration(node) {
|
|
77
95
|
const type = this.checked.declarations.values.get(node.name.name);
|
|
78
96
|
if (type?.kind !== 'function')
|
package/dist/lexer/base.d.ts
CHANGED
|
@@ -14,8 +14,9 @@ export declare abstract class LexerBase {
|
|
|
14
14
|
protected lastEndsValue(): boolean;
|
|
15
15
|
protected lastEndsStatement(): boolean;
|
|
16
16
|
/**
|
|
17
|
-
* Unlike Go, a line starting with `.` / `?.` continues a method chain,
|
|
18
|
-
* a
|
|
17
|
+
* Unlike Go, a line starting with `.` / `?.` continues a method chain, a line starting with `?`
|
|
18
|
+
* or `:` continues a conditional expression, and a line starting with a closing bracket ends a
|
|
19
|
+
* list, so no semicolon is needed before it.
|
|
19
20
|
*/
|
|
20
21
|
protected nextLineContinues(): boolean;
|
|
21
22
|
protected insertSemicolon(): void;
|
package/dist/lexer/base.js
CHANGED
|
@@ -55,21 +55,30 @@ export class LexerBase {
|
|
|
55
55
|
}
|
|
56
56
|
lastEndsStatement() {
|
|
57
57
|
const last = this.tokens.at(-1);
|
|
58
|
-
|
|
58
|
+
if (last === undefined)
|
|
59
|
+
return false;
|
|
60
|
+
// `env.PORT!` at the end of a line: the `!` right after a value, which takes it not to be null.
|
|
61
|
+
if (last.kind === '!') {
|
|
62
|
+
const before = this.tokens.at(-2);
|
|
63
|
+
return before !== undefined && before.end === last.start && ENDS_VALUE.has(before.kind);
|
|
64
|
+
}
|
|
65
|
+
return ENDS_STATEMENT.has(last.kind);
|
|
59
66
|
}
|
|
60
67
|
/**
|
|
61
|
-
* Unlike Go, a line starting with `.` / `?.` continues a method chain,
|
|
62
|
-
* a
|
|
68
|
+
* Unlike Go, a line starting with `.` / `?.` continues a method chain, a line starting with `?`
|
|
69
|
+
* or `:` continues a conditional expression, and a line starting with a closing bracket ends a
|
|
70
|
+
* list, so no semicolon is needed before it.
|
|
63
71
|
*/
|
|
64
72
|
nextLineContinues() {
|
|
65
73
|
const t = this.text;
|
|
66
74
|
const c = t[this.pos];
|
|
67
75
|
const next = t[this.pos + 1];
|
|
68
|
-
if (c === ')' || c === ']' || c === '}')
|
|
76
|
+
if (c === ')' || c === ']' || c === '}' || c === ':')
|
|
69
77
|
return true;
|
|
70
78
|
if (c === '.')
|
|
71
79
|
return next !== '.' && !isDigit(next);
|
|
72
|
-
|
|
80
|
+
// `? "yes"` and `?.name`, but not `??`, which stays at the end of the line as other operators.
|
|
81
|
+
return c === '?' && next !== '?';
|
|
73
82
|
}
|
|
74
83
|
insertSemicolon() {
|
|
75
84
|
const end = this.tokens.at(-1)?.end ?? 0;
|
package/dist/lexer/lexer.js
CHANGED
|
@@ -30,7 +30,10 @@ class Lexer extends StyleLexer {
|
|
|
30
30
|
}
|
|
31
31
|
if (this.pos >= this.text.length)
|
|
32
32
|
break;
|
|
33
|
+
const count = this.tokens.length;
|
|
33
34
|
this.scanToken();
|
|
35
|
+
if (sawNewline && this.tokens.length > count)
|
|
36
|
+
this.tokens[count].newlineBefore = true;
|
|
34
37
|
}
|
|
35
38
|
if (this.lastEndsStatement())
|
|
36
39
|
this.insertSemicolon();
|
package/dist/lexer/token.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export declare const KEYWORDS: readonly ["func", "comp", "go", "chan", "return", "let", "const", "
|
|
1
|
+
export declare const KEYWORDS: readonly ["func", "comp", "go", "chan", "return", "let", "const", "interface", "class", "extends", "new", "this", "super", "if", "else", "for", "in", "break", "continue", "switch", "case", "default", "defer", "try", "catch", "finally", "throw", "import", "export", "null", "true", "false", "typeof", "instanceof", "async", "await"];
|
|
2
2
|
export declare const PUNCTUATORS: readonly ["{", "}", "(", ")", "[", "]", ";", ",", ":", ".", "...", "?", "?.", "??", "=>", "=", "+=", "-=", "*=", "/=", "%=", "**=", "&=", "|=", "^=", "<<=", ">>=", ">>>=", "&&=", "||=", "??=", "==", "!=", "<", "<-", ">", "<=", ">=", "+", "-", "*", "/", "%", "**", "++", "--", "&", "|", "^", "~", "<<", ">>", ">>>", "&&", "||", "!", "@"];
|
|
3
3
|
export type Keyword = (typeof KEYWORDS)[number];
|
|
4
4
|
export type Punctuator = (typeof PUNCTUATORS)[number];
|
|
@@ -24,6 +24,11 @@ interface TokenBase {
|
|
|
24
24
|
text: string;
|
|
25
25
|
start: number;
|
|
26
26
|
end: number;
|
|
27
|
+
/**
|
|
28
|
+
* A line break comes before the token. No semicolon is inserted after `>`, which may be an
|
|
29
|
+
* operator; the parser ends a declaration after `Map<string, number>` by it.
|
|
30
|
+
*/
|
|
31
|
+
newlineBefore?: boolean;
|
|
27
32
|
}
|
|
28
33
|
export interface NumberToken extends TokenBase {
|
|
29
34
|
kind: 'Number';
|
package/dist/lexer/token.js
CHANGED
package/dist/modules.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { readFileSync, statSync } from 'node:fs';
|
|
2
|
-
import {
|
|
2
|
+
import { resolveRookModule } from "./aliases.js";
|
|
3
3
|
import { check } from "./checker/checker.js";
|
|
4
4
|
import { markupFor } from "./config.js";
|
|
5
5
|
import { parse } from "./parser/parser.js";
|
|
@@ -9,9 +9,10 @@ const cache = new Map();
|
|
|
9
9
|
const loading = new Set();
|
|
10
10
|
/** Finds the types exported by a `.rk` module imported from the file `fromFile`. */
|
|
11
11
|
export function loadModuleExports(fromFile, specifier, markup) {
|
|
12
|
-
|
|
12
|
+
// A relative path, or an alias of package.json or tsconfig.json (aliases.ts).
|
|
13
|
+
const path = specifier.endsWith('.rk') ? resolveRookModule(fromFile, specifier) : null;
|
|
14
|
+
if (path === null)
|
|
13
15
|
return undefined;
|
|
14
|
-
const path = resolve(dirname(fromFile), specifier);
|
|
15
16
|
let version;
|
|
16
17
|
try {
|
|
17
18
|
const stats = statSync(path);
|
package/dist/parser/base.d.ts
CHANGED
|
@@ -66,6 +66,11 @@ export declare abstract class ParserBase {
|
|
|
66
66
|
protected requireMarkup(what: string): void;
|
|
67
67
|
protected peek(offset?: number): Token;
|
|
68
68
|
protected check(kind: TokenKind): boolean;
|
|
69
|
+
/**
|
|
70
|
+
* The line ended with the `>` of type arguments, `votes Map<string, number>`: the lexer inserts
|
|
71
|
+
* no semicolon after `>`, which may be an operator, so the line break ends the declaration here.
|
|
72
|
+
*/
|
|
73
|
+
protected afterTypeArguments(): boolean;
|
|
69
74
|
/** Whether the current token is the word `word`, e.g. the contextual keyword `from`. */
|
|
70
75
|
protected checkWord(word: string): boolean;
|
|
71
76
|
protected next(): Token;
|