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.
Files changed (123) hide show
  1. package/README.md +2 -0
  2. package/README.ru.md +2 -0
  3. package/dist/ast/declarations.d.ts +19 -3
  4. package/dist/ast/expressions.d.ts +4 -8
  5. package/dist/ast/statements.d.ts +3 -1
  6. package/dist/checker/assignments.d.ts +2 -0
  7. package/dist/checker/assignments.js +9 -3
  8. package/dist/checker/base.d.ts +8 -2
  9. package/dist/checker/base.js +3 -3
  10. package/dist/checker/builtins.js +7 -3
  11. package/dist/checker/channels.js +1 -2
  12. package/dist/checker/classes.js +7 -1
  13. package/dist/checker/closing.js +1 -1
  14. package/dist/checker/completion.d.ts +1 -1
  15. package/dist/checker/completion.js +2 -1
  16. package/dist/checker/component-bindings.d.ts +7 -0
  17. package/dist/checker/component-bindings.js +45 -0
  18. package/dist/checker/component-values.js +10 -7
  19. package/dist/checker/components.d.ts +2 -2
  20. package/dist/checker/components.js +8 -3
  21. package/dist/checker/context.d.ts +28 -5
  22. package/dist/checker/context.js +23 -0
  23. package/dist/checker/control-flow.d.ts +6 -5
  24. package/dist/checker/control-flow.js +48 -40
  25. package/dist/checker/declarations.d.ts +2 -2
  26. package/dist/checker/declarations.js +3 -2
  27. package/dist/checker/decorator-exports.js +4 -0
  28. package/dist/checker/decorator-includes.d.ts +9 -0
  29. package/dist/checker/decorator-includes.js +71 -0
  30. package/dist/checker/decorator-members.d.ts +8 -10
  31. package/dist/checker/decorator-members.js +28 -31
  32. package/dist/checker/decorator-wrappers.d.ts +1 -1
  33. package/dist/checker/decorator-wrappers.js +3 -2
  34. package/dist/checker/decorators.d.ts +14 -5
  35. package/dist/checker/decorators.js +52 -25
  36. package/dist/checker/expressions.d.ts +1 -1
  37. package/dist/checker/expressions.js +7 -6
  38. package/dist/checker/flow.d.ts +25 -3
  39. package/dist/checker/flow.js +48 -8
  40. package/dist/checker/functions.d.ts +2 -2
  41. package/dist/checker/functions.js +9 -5
  42. package/dist/checker/helpers.d.ts +4 -1
  43. package/dist/checker/helpers.js +7 -2
  44. package/dist/checker/imports.js +5 -2
  45. package/dist/checker/loops.d.ts +2 -0
  46. package/dist/checker/loops.js +27 -2
  47. package/dist/checker/markup.js +14 -2
  48. package/dist/checker/members.js +3 -1
  49. package/dist/checker/shared-state.d.ts +11 -0
  50. package/dist/checker/shared-state.js +37 -0
  51. package/dist/checker/statements.d.ts +5 -2
  52. package/dist/checker/statements.js +51 -22
  53. package/dist/checker/tagged-unions.d.ts +37 -0
  54. package/dist/checker/tagged-unions.js +132 -0
  55. package/dist/checker/untyped-globals.d.ts +1 -0
  56. package/dist/checker/untyped-globals.js +3 -1
  57. package/dist/codegen/attributes.d.ts +4 -0
  58. package/dist/codegen/attributes.js +24 -2
  59. package/dist/codegen/component-helpers.d.ts +6 -1
  60. package/dist/codegen/component-helpers.js +6 -1
  61. package/dist/codegen/component-props.d.ts +11 -2
  62. package/dist/codegen/component-props.js +50 -5
  63. package/dist/codegen/components.d.ts +6 -5
  64. package/dist/codegen/components.js +44 -21
  65. package/dist/codegen/control-flow.d.ts +2 -0
  66. package/dist/codegen/control-flow.js +23 -9
  67. package/dist/codegen/creation.js +5 -2
  68. package/dist/codegen/early-returns.d.ts +4 -3
  69. package/dist/codegen/early-returns.js +17 -5
  70. package/dist/codegen/emitter.d.ts +3 -0
  71. package/dist/codegen/emitter.js +18 -4
  72. package/dist/codegen/expressions.js +0 -2
  73. package/dist/codegen/functions.d.ts +11 -1
  74. package/dist/codegen/functions.js +31 -3
  75. package/dist/codegen/live.d.ts +60 -0
  76. package/dist/codegen/live.js +29 -0
  77. package/dist/codegen/mutations.d.ts +7 -0
  78. package/dist/codegen/mutations.js +43 -2
  79. package/dist/codegen/options.d.ts +2 -0
  80. package/dist/codegen/reactive.d.ts +4 -59
  81. package/dist/codegen/reactive.js +34 -72
  82. package/dist/codegen/runtime.js +6 -1
  83. package/dist/codegen/shared.d.ts +22 -0
  84. package/dist/codegen/shared.js +93 -0
  85. package/dist/codegen/simple-statements.d.ts +1 -1
  86. package/dist/codegen/statements.d.ts +5 -0
  87. package/dist/codegen/statements.js +35 -0
  88. package/dist/codegen/updates.d.ts +11 -3
  89. package/dist/codegen/updates.js +52 -4
  90. package/dist/codegen/web-components.d.ts +2 -2
  91. package/dist/codegen/web-components.js +2 -2
  92. package/dist/compile.js +6 -2
  93. package/dist/decorator-copy.d.ts +1 -1
  94. package/dist/decorator-copy.js +1 -0
  95. package/dist/decorator-names.d.ts +14 -0
  96. package/dist/decorator-names.js +56 -0
  97. package/dist/decorators.d.ts +2 -3
  98. package/dist/decorators.js +36 -14
  99. package/dist/inline-packages.d.ts +2 -1
  100. package/dist/inline-packages.js +9 -4
  101. package/dist/names.d.ts +4 -0
  102. package/dist/names.js +29 -0
  103. package/dist/parser/base.d.ts +1 -0
  104. package/dist/parser/base.js +1 -1
  105. package/dist/parser/control-flow.d.ts +5 -0
  106. package/dist/parser/control-flow.js +28 -2
  107. package/dist/parser/declarations.d.ts +5 -15
  108. package/dist/parser/declarations.js +15 -119
  109. package/dist/parser/decorators.d.ts +6 -2
  110. package/dist/parser/decorators.js +43 -19
  111. package/dist/parser/params.d.ts +21 -0
  112. package/dist/parser/params.js +143 -0
  113. package/dist/parser/simple-statements.d.ts +1 -0
  114. package/dist/parser/simple-statements.js +22 -6
  115. package/dist/parser/statements.d.ts +5 -2
  116. package/dist/parser/statements.js +18 -11
  117. package/dist/runtime/sources.js +3 -2
  118. package/dist/walk.js +3 -3
  119. package/package.json +2 -2
  120. package/src/runtime/decode.js +41 -10
  121. package/src/runtime/index.js +1 -0
  122. package/src/runtime/markup.js +27 -17
  123. 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.4",
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",
@@ -48,13 +48,22 @@ export function $$decode(value, type) {
48
48
 
49
49
  const PRIMITIVES = { number: 'number', string: 'string', bool: 'boolean' };
50
50
 
51
- /** Where and how the value does not fit the type, or null if it does. */
52
- function check(value, type, path) {
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) return type.literal.includes(value) ? null : mismatch(value, type, path);
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
- for (const [name, field] of Object.entries(type.fields)) {
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" }` of `Circle | Square`, so its problem
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
- let deepest = null;
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
- if (problem.path.length > (deepest?.path.length ?? path.length)) deepest = problem;
137
+ problems.push(problem);
124
138
  }
125
139
 
126
- return deepest ?? mismatch(value, type, path);
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
  }
@@ -11,6 +11,7 @@ export * from './go.js';
11
11
  export * from './markup.js';
12
12
  export * from './queue.js';
13
13
  export * from './select.js';
14
+ export * from './shared.js';
14
15
  export * from './signals.js';
15
16
  export * from './streams.js';
16
17
  export * from './threads.js';
@@ -1,10 +1,10 @@
1
1
  // Markup: the owners of the blocks that `{if}`, `{switch}` and `{for}` remove, the
2
- // `mount()` of components, the blocks themselves, elements and the CSS of components.
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 functions returned by the
7
- // `mount()` of its components, and the removal of the blocks inside it, run when it is removed.
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() { ... }` of a component: runs when its markup is in the document. That is checked after
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. The function `mount` returns runs when the owner is removed.
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
- const cleanup = entry.mount();
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
- export function $$list(items, create) {
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.item);
242
+ const same = old.get(block.key);
240
243
 
241
244
  if (same) same.push(block);
242
- else old.set(block.item, [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
- let block = old.get(item)?.shift();
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 = { item, start: document.createTextNode(''), end: document.createTextNode('') };
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
+ }