rooklang 0.3.4 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -0
- package/README.ru.md +2 -0
- package/dist/ast/declarations.d.ts +19 -3
- package/dist/ast/expressions.d.ts +4 -8
- package/dist/ast/statements.d.ts +3 -1
- package/dist/checker/assignments.d.ts +2 -0
- package/dist/checker/assignments.js +9 -3
- package/dist/checker/base.d.ts +8 -2
- package/dist/checker/base.js +3 -3
- package/dist/checker/builtins.js +7 -3
- package/dist/checker/channels.js +1 -2
- package/dist/checker/classes.js +7 -1
- package/dist/checker/closing.js +1 -1
- package/dist/checker/completion.d.ts +1 -1
- package/dist/checker/completion.js +2 -1
- package/dist/checker/component-bindings.d.ts +7 -0
- package/dist/checker/component-bindings.js +45 -0
- package/dist/checker/component-values.js +10 -7
- package/dist/checker/components.d.ts +2 -2
- package/dist/checker/components.js +8 -3
- package/dist/checker/context.d.ts +28 -5
- package/dist/checker/context.js +23 -0
- package/dist/checker/control-flow.d.ts +6 -5
- package/dist/checker/control-flow.js +48 -40
- package/dist/checker/declarations.d.ts +2 -2
- package/dist/checker/declarations.js +3 -2
- package/dist/checker/decorator-exports.js +4 -0
- package/dist/checker/decorator-includes.d.ts +9 -0
- package/dist/checker/decorator-includes.js +71 -0
- package/dist/checker/decorator-members.d.ts +8 -10
- package/dist/checker/decorator-members.js +28 -31
- package/dist/checker/decorator-wrappers.d.ts +1 -1
- package/dist/checker/decorator-wrappers.js +3 -2
- package/dist/checker/decorators.d.ts +14 -5
- package/dist/checker/decorators.js +52 -25
- package/dist/checker/expressions.d.ts +1 -1
- package/dist/checker/expressions.js +7 -6
- package/dist/checker/flow.d.ts +25 -3
- package/dist/checker/flow.js +48 -8
- package/dist/checker/functions.d.ts +2 -2
- package/dist/checker/functions.js +9 -5
- package/dist/checker/helpers.d.ts +4 -1
- package/dist/checker/helpers.js +7 -2
- package/dist/checker/imports.js +5 -2
- package/dist/checker/loops.d.ts +2 -0
- package/dist/checker/loops.js +27 -2
- package/dist/checker/markup.js +14 -2
- package/dist/checker/members.js +3 -1
- package/dist/checker/shared-state.d.ts +11 -0
- package/dist/checker/shared-state.js +37 -0
- package/dist/checker/statements.d.ts +5 -2
- package/dist/checker/statements.js +51 -22
- package/dist/checker/tagged-unions.d.ts +37 -0
- package/dist/checker/tagged-unions.js +132 -0
- package/dist/checker/untyped-globals.d.ts +1 -0
- package/dist/checker/untyped-globals.js +3 -1
- package/dist/codegen/attributes.d.ts +4 -0
- package/dist/codegen/attributes.js +24 -2
- package/dist/codegen/component-helpers.d.ts +6 -1
- package/dist/codegen/component-helpers.js +6 -1
- package/dist/codegen/component-props.d.ts +11 -2
- package/dist/codegen/component-props.js +50 -5
- package/dist/codegen/components.d.ts +6 -5
- package/dist/codegen/components.js +44 -21
- package/dist/codegen/control-flow.d.ts +2 -0
- package/dist/codegen/control-flow.js +23 -9
- package/dist/codegen/creation.js +5 -2
- package/dist/codegen/early-returns.d.ts +4 -3
- package/dist/codegen/early-returns.js +17 -5
- package/dist/codegen/emitter.d.ts +3 -0
- package/dist/codegen/emitter.js +18 -4
- package/dist/codegen/expressions.js +0 -2
- package/dist/codegen/functions.d.ts +11 -1
- package/dist/codegen/functions.js +31 -3
- package/dist/codegen/live.d.ts +60 -0
- package/dist/codegen/live.js +29 -0
- package/dist/codegen/mutations.d.ts +7 -0
- package/dist/codegen/mutations.js +43 -2
- package/dist/codegen/options.d.ts +2 -0
- package/dist/codegen/reactive.d.ts +4 -59
- package/dist/codegen/reactive.js +34 -72
- package/dist/codegen/runtime.js +6 -1
- package/dist/codegen/shared.d.ts +22 -0
- package/dist/codegen/shared.js +93 -0
- package/dist/codegen/simple-statements.d.ts +1 -1
- package/dist/codegen/statements.d.ts +5 -0
- package/dist/codegen/statements.js +35 -0
- package/dist/codegen/updates.d.ts +11 -3
- package/dist/codegen/updates.js +52 -4
- package/dist/codegen/web-components.d.ts +2 -2
- package/dist/codegen/web-components.js +2 -2
- package/dist/compile.js +6 -2
- package/dist/decorator-copy.d.ts +1 -1
- package/dist/decorator-copy.js +1 -0
- package/dist/decorator-names.d.ts +14 -0
- package/dist/decorator-names.js +56 -0
- package/dist/decorators.d.ts +2 -3
- package/dist/decorators.js +36 -14
- package/dist/inline-packages.d.ts +2 -1
- package/dist/inline-packages.js +9 -4
- package/dist/names.d.ts +4 -0
- package/dist/names.js +29 -0
- package/dist/parser/base.d.ts +1 -0
- package/dist/parser/base.js +1 -1
- package/dist/parser/control-flow.d.ts +5 -0
- package/dist/parser/control-flow.js +28 -2
- package/dist/parser/declarations.d.ts +5 -15
- package/dist/parser/declarations.js +15 -119
- package/dist/parser/decorators.d.ts +6 -2
- package/dist/parser/decorators.js +43 -19
- package/dist/parser/params.d.ts +21 -0
- package/dist/parser/params.js +143 -0
- package/dist/parser/simple-statements.d.ts +1 -0
- package/dist/parser/simple-statements.js +22 -6
- package/dist/parser/statements.d.ts +5 -2
- package/dist/parser/statements.js +18 -11
- package/dist/runtime/sources.js +3 -2
- package/dist/walk.js +3 -3
- package/package.json +2 -2
- package/src/runtime/decode.js +41 -10
- package/src/runtime/index.js +1 -0
- package/src/runtime/markup.js +27 -17
- package/src/runtime/shared.js +17 -0
package/dist/walk.js
CHANGED
|
@@ -47,19 +47,18 @@ export function forEachChild(node, visit) {
|
|
|
47
47
|
// The names after `needs` are other decorators', not values in scope.
|
|
48
48
|
visit(node.name);
|
|
49
49
|
each(node.params);
|
|
50
|
+
each(node.includes);
|
|
50
51
|
visit(node.body);
|
|
51
52
|
break;
|
|
52
53
|
case 'DecoratorUse':
|
|
53
54
|
// The name is the decorator's, not a value in scope.
|
|
54
55
|
each(node.arguments);
|
|
55
56
|
break;
|
|
56
|
-
case 'DecoratorGet':
|
|
57
|
-
visit(node.target);
|
|
58
|
-
break;
|
|
59
57
|
case 'VariableDeclaration':
|
|
60
58
|
each(node.names);
|
|
61
59
|
optional(node.type);
|
|
62
60
|
each(node.values);
|
|
61
|
+
optional(node.body);
|
|
63
62
|
break;
|
|
64
63
|
case 'ClassDeclaration':
|
|
65
64
|
visit(node.name);
|
|
@@ -143,6 +142,7 @@ export function forEachChild(node, visit) {
|
|
|
143
142
|
optional(node.key);
|
|
144
143
|
visit(node.value);
|
|
145
144
|
visit(node.iterable);
|
|
145
|
+
optional(node.withKey ?? null);
|
|
146
146
|
visit(node.body);
|
|
147
147
|
break;
|
|
148
148
|
case 'SwitchStatement':
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "rooklang",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.0",
|
|
4
4
|
"description": "A language with JavaScript semantics, Go-style syntax, goroutines on real threads and static types that compiles to readable JavaScript",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"rooklang",
|
|
@@ -57,7 +57,7 @@
|
|
|
57
57
|
"scripts": {
|
|
58
58
|
"rook": "node src/cli.ts",
|
|
59
59
|
"site:dev": "vite",
|
|
60
|
-
"site:build": "vite build",
|
|
60
|
+
"site:build": "vite build && node scripts/prerender.ts",
|
|
61
61
|
"site": "npm run site:build && node src/cli.ts run site/server.rk",
|
|
62
62
|
"site:images": "node scripts/og-image.ts",
|
|
63
63
|
"vscode:build": "node scripts/build-vscode.ts",
|
package/src/runtime/decode.js
CHANGED
|
@@ -48,13 +48,22 @@ export function $$decode(value, type) {
|
|
|
48
48
|
|
|
49
49
|
const PRIMITIVES = { number: 'number', string: 'string', bool: 'boolean' };
|
|
50
50
|
|
|
51
|
-
/**
|
|
52
|
-
|
|
51
|
+
/**
|
|
52
|
+
* Where and how the value does not fit the type, or null if it does. For a member of a union,
|
|
53
|
+
* `tagsFirst` checks its fields of literal types first: a member whose tag does not fit reports
|
|
54
|
+
* that, not another field.
|
|
55
|
+
*/
|
|
56
|
+
function check(value, type, path, tagsFirst = false) {
|
|
53
57
|
if (typeof type === 'string') {
|
|
54
58
|
return type === 'any' || typeof value === PRIMITIVES[type] ? null : mismatch(value, type, path);
|
|
55
59
|
}
|
|
56
60
|
|
|
57
|
-
if ('literal' in type)
|
|
61
|
+
if ('literal' in type) {
|
|
62
|
+
// Remembered for unions: a tag that fits no member lists the tags of all of them.
|
|
63
|
+
return type.literal.includes(value)
|
|
64
|
+
? null
|
|
65
|
+
: { ...mismatch(value, type, path), literal: type.literal, value };
|
|
66
|
+
}
|
|
58
67
|
|
|
59
68
|
if ('nullable' in type) {
|
|
60
69
|
if (value == null) return null;
|
|
@@ -92,7 +101,11 @@ function check(value, type, path) {
|
|
|
92
101
|
return null;
|
|
93
102
|
}
|
|
94
103
|
|
|
95
|
-
|
|
104
|
+
const fields = Object.entries(type.fields);
|
|
105
|
+
|
|
106
|
+
if (tagsFirst) fields.sort(([, a], [, b]) => Number(isLiteral(b)) - Number(isLiteral(a)));
|
|
107
|
+
|
|
108
|
+
for (const [name, field] of fields) {
|
|
96
109
|
// Only own fields: a field named `toString` is not the method of every object.
|
|
97
110
|
if (!Object.hasOwn(value, name)) {
|
|
98
111
|
if (isOptional(field)) continue;
|
|
@@ -110,26 +123,44 @@ function check(value, type, path) {
|
|
|
110
123
|
|
|
111
124
|
/**
|
|
112
125
|
* The value fits one of the types. If none fits, the type whose check went deepest into the value
|
|
113
|
-
* is most likely the one meant, `{ kind: "circle", r: "2" }`
|
|
114
|
-
* is the one reported.
|
|
126
|
+
* is most likely the one meant, and of those the one whose tag fits: `{ kind: "circle", r: "2" }`
|
|
127
|
+
* of `Circle | Square` is a Circle, so its problem is the one reported. When no tag fits, the
|
|
128
|
+
* message lists them all: `kind: expected "circle" | "square", got "oval"`.
|
|
115
129
|
*/
|
|
116
130
|
function checkUnion(value, type, path) {
|
|
117
|
-
|
|
131
|
+
const problems = [];
|
|
118
132
|
|
|
119
133
|
for (const member of type.union) {
|
|
120
|
-
const problem = check(value, member, path);
|
|
134
|
+
const problem = check(value, member, path, true);
|
|
121
135
|
|
|
122
136
|
if (problem === null) return null;
|
|
123
|
-
|
|
137
|
+
problems.push(problem);
|
|
124
138
|
}
|
|
125
139
|
|
|
126
|
-
|
|
140
|
+
const depth = Math.max(...problems.map((problem) => problem.path.length));
|
|
141
|
+
|
|
142
|
+
if (depth === path.length) return mismatch(value, type, path);
|
|
143
|
+
|
|
144
|
+
const deepest = problems.filter((problem) => problem.path.length === depth);
|
|
145
|
+
const fitting = deepest.find((problem) => !problem.literal);
|
|
146
|
+
|
|
147
|
+
if (fitting) return fitting;
|
|
148
|
+
|
|
149
|
+
const [first] = deepest;
|
|
150
|
+
const same = deepest.filter((problem) => pathText(problem.path) === pathText(first.path));
|
|
151
|
+
const literal = [...new Set(same.flatMap((problem) => problem.literal))];
|
|
152
|
+
|
|
153
|
+
return { ...mismatch(first.value, { literal }, first.path), literal, value: first.value };
|
|
127
154
|
}
|
|
128
155
|
|
|
129
156
|
function isObject(value) {
|
|
130
157
|
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
131
158
|
}
|
|
132
159
|
|
|
160
|
+
function isLiteral(type) {
|
|
161
|
+
return typeof type === 'object' && 'literal' in type;
|
|
162
|
+
}
|
|
163
|
+
|
|
133
164
|
function isOptional(type) {
|
|
134
165
|
return type === 'any' || (typeof type === 'object' && 'nullable' in type);
|
|
135
166
|
}
|
package/src/runtime/index.js
CHANGED
package/src/runtime/markup.js
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
// Markup: the owners of the blocks that `{if}`, `{switch}` and `{for}` remove, the
|
|
2
|
-
// `mount
|
|
2
|
+
// `mount` of components, the blocks themselves, elements and the CSS of components.
|
|
3
3
|
|
|
4
4
|
import { $$runDeferred } from './core.js';
|
|
5
5
|
|
|
6
|
-
// Owners of markup that `{if}`, `{switch}` and `{for}` may remove: the
|
|
7
|
-
//
|
|
6
|
+
// Owners of markup that `{if}`, `{switch}` and `{for}` may remove: what the `mount` and `effect`
|
|
7
|
+
// of its components deferred, and the removal of the blocks inside it, run when it is removed.
|
|
8
8
|
// The current owner is shared by the modules of a page, since the blocks of one module may hold
|
|
9
9
|
// the components of another.
|
|
10
10
|
export const $$owner = Symbol.for('rook.owner');
|
|
@@ -36,15 +36,17 @@ export function $$onRemove(cleanup) {
|
|
|
36
36
|
globalThis[$$owner]?.cleanups.push(cleanup);
|
|
37
37
|
}
|
|
38
38
|
|
|
39
|
-
// `mount
|
|
39
|
+
// `mount { ... }` of a component: runs when its markup is in the document. That is checked after
|
|
40
40
|
// the code that creates the component; markup that is not in the document yet is waited for with a
|
|
41
41
|
// MutationObserver, which watches only while some markup waits. Markup that its owner removes
|
|
42
|
-
// before that never mounts.
|
|
42
|
+
// before that never mounts. What its `defer`s put into `cleanups` runs when the owner is removed;
|
|
43
|
+
// they are added to the owner once it mounts, so the cleanups of the decorators, which mount after
|
|
44
|
+
// the component, run before those of the component.
|
|
43
45
|
export const $$waiting = new Set();
|
|
44
46
|
export let $$observer = null;
|
|
45
47
|
|
|
46
|
-
export function $$mount(node, mount) {
|
|
47
|
-
const entry = { node, mount, owner: globalThis[$$owner] };
|
|
48
|
+
export function $$mount(node, mount, cleanups) {
|
|
49
|
+
const entry = { node, mount, cleanups, owner: globalThis[$$owner] };
|
|
48
50
|
|
|
49
51
|
entry.owner?.cleanups.push(() => $$waiting.delete(entry));
|
|
50
52
|
queueMicrotask(() => $$tryMount(entry));
|
|
@@ -65,9 +67,8 @@ export function $$tryMount(entry) {
|
|
|
65
67
|
return;
|
|
66
68
|
}
|
|
67
69
|
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
if (typeof cleanup === 'function') entry.owner?.cleanups.push(cleanup);
|
|
70
|
+
entry.mount();
|
|
71
|
+
if (entry.cleanups) entry.owner?.cleanups.push(() => $$runDeferred(entry.cleanups.splice(0)));
|
|
71
72
|
}
|
|
72
73
|
|
|
73
74
|
export function $$mountWaiting() {
|
|
@@ -216,8 +217,10 @@ export function $$dynamic(component, create) {
|
|
|
216
217
|
|
|
217
218
|
// `{for item in items { ... }}` in markup: a block for each item, between its own markers. On
|
|
218
219
|
// updates the blocks are found again by the item itself: blocks of items that stay are updated and
|
|
219
|
-
// moved into place, others are created or removed.
|
|
220
|
-
|
|
220
|
+
// moved into place, others are created or removed. With `key`, of `with key item.id`, they are
|
|
221
|
+
// found by the key instead, so an item replaced by another with the same key keeps its block,
|
|
222
|
+
// which gets the new item.
|
|
223
|
+
export function $$list(items, create, key = (item) => item) {
|
|
221
224
|
const end = document.createTextNode('');
|
|
222
225
|
const fragment = document.createDocumentFragment();
|
|
223
226
|
|
|
@@ -236,24 +239,25 @@ export function $$list(items, create) {
|
|
|
236
239
|
const old = new Map();
|
|
237
240
|
|
|
238
241
|
for (const block of blocks) {
|
|
239
|
-
const same = old.get(block.
|
|
242
|
+
const same = old.get(block.key);
|
|
240
243
|
|
|
241
244
|
if (same) same.push(block);
|
|
242
|
-
else old.set(block.
|
|
245
|
+
else old.set(block.key, [block]);
|
|
243
246
|
}
|
|
244
247
|
|
|
245
248
|
const next = [];
|
|
246
249
|
let index = 0;
|
|
247
250
|
|
|
248
251
|
for (const item of items()) {
|
|
249
|
-
|
|
252
|
+
const id = key(item, index);
|
|
253
|
+
let block = old.get(id)?.shift();
|
|
250
254
|
|
|
251
255
|
if (block) {
|
|
252
|
-
block.update?.(index);
|
|
256
|
+
block.update?.(index, item);
|
|
253
257
|
} else {
|
|
254
258
|
const [[content, blockUpdate], remove] = $$owned(() => create(item, index));
|
|
255
259
|
|
|
256
|
-
block = {
|
|
260
|
+
block = { key: id, start: document.createTextNode(''), end: document.createTextNode('') };
|
|
257
261
|
block.update = blockUpdate;
|
|
258
262
|
block.remove = remove;
|
|
259
263
|
block.content = document.createDocumentFragment();
|
|
@@ -315,6 +319,12 @@ export function $$svgElement(tag, attributes) {
|
|
|
315
319
|
return element;
|
|
316
320
|
}
|
|
317
321
|
|
|
322
|
+
// The class of `class={[styles.tab, active ? styles.active : null]}`: the classes that are not
|
|
323
|
+
// null or empty, separated by spaces.
|
|
324
|
+
export function $$classes(list) {
|
|
325
|
+
return list.filter((each) => each).join(' ');
|
|
326
|
+
}
|
|
327
|
+
|
|
318
328
|
// Sets an attribute whose value may be null: then the attribute is removed.
|
|
319
329
|
export function $$attribute(element, name, value) {
|
|
320
330
|
if (value == null) element.removeAttribute(name);
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
// State of a module, `state items []Item = []` at its top level: the set of the updates of the
|
|
2
|
+
// components that read it, which every change of it runs.
|
|
3
|
+
|
|
4
|
+
import { $$onRemove } from './markup.js';
|
|
5
|
+
|
|
6
|
+
// A component adds its update while it is on the page: `{if}`, `{switch}` or `{for}` that removes
|
|
7
|
+
// it takes the update away.
|
|
8
|
+
export function $$watch(listeners, update) {
|
|
9
|
+
listeners.add(update);
|
|
10
|
+
$$onRemove(() => listeners.delete(update));
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
// After a change of the state: the updates run on a copy, since one may remove a component, or
|
|
14
|
+
// add one, that listens too.
|
|
15
|
+
export function $$changed(listeners) {
|
|
16
|
+
for (const update of [...listeners]) update();
|
|
17
|
+
}
|