rooklang 0.3.3 → 0.3.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (170) hide show
  1. package/README.md +32 -7
  2. package/README.ru.md +32 -7
  3. package/dist/ast/declarations.d.ts +20 -1
  4. package/dist/ast/expressions.d.ts +31 -1
  5. package/dist/ast/markup.d.ts +6 -1
  6. package/dist/ast/statements.d.ts +25 -1
  7. package/dist/auto-import.d.ts +11 -0
  8. package/dist/auto-import.js +81 -0
  9. package/dist/breaks.d.ts +6 -0
  10. package/dist/breaks.js +36 -0
  11. package/dist/build.d.ts +7 -0
  12. package/dist/build.js +23 -2
  13. package/dist/checker/array-literals.d.ts +6 -0
  14. package/dist/checker/array-literals.js +59 -0
  15. package/dist/checker/assignments.d.ts +2 -0
  16. package/dist/checker/assignments.js +14 -0
  17. package/dist/checker/base.js +6 -6
  18. package/dist/checker/builtins.js +15 -5
  19. package/dist/checker/bytes.d.ts +2 -2
  20. package/dist/checker/bytes.js +2 -2
  21. package/dist/checker/calls.js +3 -0
  22. package/dist/checker/channel-objects.d.ts +3 -0
  23. package/dist/checker/channel-objects.js +40 -0
  24. package/dist/checker/channels.d.ts +1 -0
  25. package/dist/checker/channels.js +16 -2
  26. package/dist/checker/checker.d.ts +1 -1
  27. package/dist/checker/checker.js +19 -0
  28. package/dist/checker/class-members.d.ts +22 -0
  29. package/dist/checker/class-members.js +105 -0
  30. package/dist/checker/classes.d.ts +5 -4
  31. package/dist/checker/classes.js +41 -12
  32. package/dist/checker/closing.d.ts +21 -0
  33. package/dist/checker/closing.js +164 -0
  34. package/dist/checker/completion.d.ts +2 -2
  35. package/dist/checker/completion.js +2 -2
  36. package/dist/checker/context.d.ts +13 -0
  37. package/dist/checker/control-flow.d.ts +5 -0
  38. package/dist/checker/control-flow.js +17 -0
  39. package/dist/checker/declarations.js +1 -0
  40. package/dist/checker/decode.d.ts +8 -0
  41. package/dist/checker/decode.js +32 -0
  42. package/dist/checker/expressions.d.ts +5 -1
  43. package/dist/checker/expressions.js +13 -34
  44. package/dist/checker/flow.js +8 -4
  45. package/dist/checker/helpers.d.ts +1 -1
  46. package/dist/checker/helpers.js +9 -2
  47. package/dist/checker/iterables.d.ts +3 -1
  48. package/dist/checker/iterables.js +13 -0
  49. package/dist/checker/iterators.d.ts +4 -1
  50. package/dist/checker/iterators.js +12 -4
  51. package/dist/checker/json.d.ts +5 -0
  52. package/dist/checker/json.js +92 -0
  53. package/dist/checker/loops.js +23 -3
  54. package/dist/checker/members.js +7 -0
  55. package/dist/checker/operators.d.ts +2 -2
  56. package/dist/checker/operators.js +7 -5
  57. package/dist/checker/queues.d.ts +2 -2
  58. package/dist/checker/queues.js +2 -2
  59. package/dist/checker/resolution.js +5 -3
  60. package/dist/checker/signals.d.ts +8 -0
  61. package/dist/checker/signals.js +48 -0
  62. package/dist/checker/topics.d.ts +6 -0
  63. package/dist/checker/topics.js +17 -0
  64. package/dist/checker/transfer.d.ts +3 -1
  65. package/dist/checker/transfer.js +25 -5
  66. package/dist/checker/try.d.ts +6 -0
  67. package/dist/checker/try.js +19 -0
  68. package/dist/checker/types/constructors.d.ts +1 -1
  69. package/dist/checker/types/constructors.js +2 -2
  70. package/dist/checker/types/generics.js +1 -1
  71. package/dist/checker/types/iterables.d.ts +13 -0
  72. package/dist/checker/types/iterables.js +52 -0
  73. package/dist/checker/types/model.d.ts +25 -0
  74. package/dist/checker/types/params.js +22 -3
  75. package/dist/checker/types/printing.js +4 -3
  76. package/dist/checker/types/relations.js +19 -25
  77. package/dist/checker/types/topic.d.ts +6 -0
  78. package/dist/checker/types/topic.js +23 -0
  79. package/dist/checker/types.d.ts +2 -0
  80. package/dist/checker/types.js +2 -0
  81. package/dist/cli.js +18 -36
  82. package/dist/codegen/calls.d.ts +23 -0
  83. package/dist/codegen/calls.js +88 -7
  84. package/dist/codegen/control-statements.d.ts +10 -2
  85. package/dist/codegen/control-statements.js +56 -19
  86. package/dist/codegen/decode.d.ts +14 -0
  87. package/dist/codegen/decode.js +93 -0
  88. package/dist/codegen/emitter.d.ts +10 -41
  89. package/dist/codegen/emitter.js +9 -4
  90. package/dist/codegen/expressions.d.ts +9 -0
  91. package/dist/codegen/expressions.js +15 -24
  92. package/dist/codegen/js.d.ts +2 -2
  93. package/dist/codegen/js.js +5 -2
  94. package/dist/codegen/options.d.ts +40 -0
  95. package/dist/codegen/options.js +1 -0
  96. package/dist/codegen/runtime.d.ts +7 -3
  97. package/dist/codegen/runtime.js +27 -13
  98. package/dist/codegen/simple-statements.d.ts +10 -4
  99. package/dist/codegen/simple-statements.js +40 -15
  100. package/dist/codegen/statements.js +64 -4
  101. package/dist/codegen/try.d.ts +5 -0
  102. package/dist/codegen/try.js +40 -0
  103. package/dist/compile.d.ts +9 -0
  104. package/dist/compile.js +19 -6
  105. package/dist/completion.d.ts +4 -2
  106. package/dist/completion.js +10 -5
  107. package/dist/declarations.js +32 -6
  108. package/dist/decorator-copy.js +5 -2
  109. package/dist/goroutines.d.ts +1 -1
  110. package/dist/goroutines.js +5 -4
  111. package/dist/index.d.ts +6 -2
  112. package/dist/index.js +14 -5
  113. package/dist/lexer/base.d.ts +2 -0
  114. package/dist/lexer/base.js +6 -1
  115. package/dist/lexer/lexer.js +3 -0
  116. package/dist/lexer/literals.d.ts +5 -0
  117. package/dist/lexer/literals.js +51 -1
  118. package/dist/lexer/markup.js +3 -4
  119. package/dist/lexer/tables.js +2 -0
  120. package/dist/lexer/token.d.ts +4 -3
  121. package/dist/lexer/token.js +0 -2
  122. package/dist/loader.js +30 -8
  123. package/dist/parser/base.d.ts +4 -0
  124. package/dist/parser/base.js +2 -1
  125. package/dist/parser/classes.d.ts +2 -0
  126. package/dist/parser/classes.js +88 -23
  127. package/dist/parser/control-flow.d.ts +3 -1
  128. package/dist/parser/control-flow.js +21 -24
  129. package/dist/parser/declarations.d.ts +7 -4
  130. package/dist/parser/declarations.js +45 -6
  131. package/dist/parser/expressions.js +27 -3
  132. package/dist/parser/jumps.d.ts +11 -0
  133. package/dist/parser/jumps.js +77 -0
  134. package/dist/parser/literals.d.ts +9 -0
  135. package/dist/parser/literals.js +45 -6
  136. package/dist/parser/markup.js +1 -1
  137. package/dist/parser/simple-statements.d.ts +1 -1
  138. package/dist/parser/simple-statements.js +16 -9
  139. package/dist/parser/statements.d.ts +0 -3
  140. package/dist/parser/statements.js +22 -25
  141. package/dist/parser/syntax.d.ts +11 -1
  142. package/dist/parser/syntax.js +16 -2
  143. package/dist/parser/types.js +8 -3
  144. package/dist/project-modules.d.ts +12 -0
  145. package/dist/project-modules.js +106 -0
  146. package/dist/run-file.d.ts +6 -0
  147. package/dist/run-file.js +111 -0
  148. package/dist/runtime/sources.js +11 -5
  149. package/dist/source-map.d.ts +17 -0
  150. package/dist/source-map.js +75 -0
  151. package/dist/ts-types.js +4 -1
  152. package/dist/typescript/convert-objects.d.ts +2 -1
  153. package/dist/typescript/convert-objects.js +18 -9
  154. package/dist/vite.js +12 -4
  155. package/dist/walk-types.js +1 -0
  156. package/dist/walk.d.ts +0 -2
  157. package/dist/walk.js +11 -28
  158. package/package.json +1 -1
  159. package/src/runtime/chan.js +15 -5
  160. package/src/runtime/channel.js +28 -0
  161. package/src/runtime/classes.js +32 -0
  162. package/src/runtime/core.js +10 -1
  163. package/src/runtime/decode.js +190 -0
  164. package/src/runtime/go.js +3 -0
  165. package/src/runtime/index.js +6 -0
  166. package/src/runtime/select.js +14 -9
  167. package/src/runtime/signals.js +59 -0
  168. package/src/runtime/threads.js +32 -5
  169. package/src/runtime/topic.js +118 -0
  170. package/src/runtime/try.js +45 -0
@@ -1,8 +1,8 @@
1
1
  import { isChan } from "../checker/channel.js";
2
- import { isMap } from "../checker/iterables.js";
3
- import { typedArrayOf } from "../checker/types.js";
2
+ import { asyncIterated, isMap } from "../checker/iterables.js";
3
+ import { isTopic, typedArrayOf } from "../checker/types.js";
4
4
  import { declaredIn } from "../names.js";
5
- import { containsBreak } from "../walk.js";
5
+ import { containsBreak } from "../breaks.js";
6
6
  import { declarationsOf, endsWithJump } from "./analysis.js";
7
7
  import { SimpleStatementEmitter } from "./simple-statements.js";
8
8
  import { ARROW, BINARY, POSTFIX } from "./syntax.js";
@@ -11,6 +11,9 @@ import { ARROW, BINARY, POSTFIX } from "./syntax.js";
11
11
  const FIXED = new Set(['const', 'import', 'loop']);
12
12
  export class ControlStatementEmitter extends SimpleStatementEmitter {
13
13
  ifStatement(node) {
14
+ const { init } = node;
15
+ if (init)
16
+ return this.withInit(init, () => this.line(this.ifStatement({ ...node, init: null })));
14
17
  let text = `if (${this.expression(node.condition, 0)}) ${this.blockStatement(node.consequent)}`;
15
18
  if (node.alternate?.kind === 'IfStatement') {
16
19
  // The condition of `else if` is evaluated only when the first one is false.
@@ -22,6 +25,20 @@ export class ControlStatementEmitter extends SimpleStatementEmitter {
22
25
  }
23
26
  return text;
24
27
  }
28
+ /**
29
+ * `if const v = f(); v > 0 { }` → `{ const v = f(); if (v > 0) { } }`: a block keeps the
30
+ * variables of the initializer to the statement, which `emit` writes.
31
+ */
32
+ withInit(init, emit) {
33
+ return this.block(() => this.withScope(declarationsOf([init]), () => {
34
+ this.statement(init);
35
+ this.withHoisting(true, emit);
36
+ }));
37
+ }
38
+ /** `outer: for (...)`: the label goes on the loop itself, after what is hoisted before it. */
39
+ labeled(node, text) {
40
+ return node.label ? `${this.label(node.label)}: ${text}` : text;
41
+ }
25
42
  loopBody(body) {
26
43
  return this.withBreakTarget(null, () => this.blockStatement(body));
27
44
  }
@@ -37,10 +54,10 @@ export class ControlStatementEmitter extends SimpleStatementEmitter {
37
54
  node.update ? this.simpleStatement(node.update) : '',
38
55
  ]);
39
56
  if (node.init === null && node.update === null) {
40
- this.line(`while (${condition || 'true'}) ${this.loopBody(node.body)}`);
57
+ this.line(this.labeled(node, `while (${condition || 'true'}) ${this.loopBody(node.body)}`));
41
58
  return;
42
59
  }
43
- this.line(`for (${init}; ${condition}; ${update}) ${this.loopBody(node.body)}`);
60
+ this.line(this.labeled(node, `for (${init}; ${condition}; ${update}) ${this.loopBody(node.body)}`));
44
61
  });
45
62
  }
46
63
  /** A statement inside a `for` header, without the `;`. */
@@ -57,7 +74,7 @@ export class ControlStatementEmitter extends SimpleStatementEmitter {
57
74
  case 'ExpressionStatement':
58
75
  return this.expression(node.expression, 0);
59
76
  case 'SendStatement':
60
- return `await $$send(${this.expression(node.channel, ARROW)}, ${this.expression(node.value, ARROW)})`;
77
+ return this.send(node);
61
78
  }
62
79
  }
63
80
  /**
@@ -72,9 +89,14 @@ export class ControlStatementEmitter extends SimpleStatementEmitter {
72
89
  const value = this.name(node.value.name);
73
90
  const iterable = this.options.types?.get(node.iterable);
74
91
  let head;
75
- // A channel gives its values as they come, until it is closed.
76
- if (iterable && isChan(iterable)) {
77
- this.line(`for await (const ${value} of $$range(${this.expression(node.iterable, ARROW)})) ${this.loopBody(node.body)}`);
92
+ // An async iterable, such as a stream of Node, gives its values as their Promises do.
93
+ if (iterable && asyncIterated(iterable) !== null) {
94
+ this.line(this.labeled(node, `for await (const ${value} of ${this.expression(node.iterable, POSTFIX)}) ${this.loopBody(node.body)}`));
95
+ return;
96
+ }
97
+ // A channel gives its values as they come, until it is closed, and so does a topic.
98
+ if (iterable && (isChan(iterable) || isTopic(iterable))) {
99
+ this.line(this.labeled(node, `for await (const ${value} of $$Channel.range(${this.receivedFrom(node.iterable)})) ${this.loopBody(node.body)}`));
78
100
  return;
79
101
  }
80
102
  // An iterator is a generator function: called, it gives a lazy JS iterator, of pairs if its
@@ -117,7 +139,7 @@ export class ControlStatementEmitter extends SimpleStatementEmitter {
117
139
  else {
118
140
  head = `const ${this.pair(node.key, node.value)} of ${this.expression(node.iterable, POSTFIX)}.entries()`;
119
141
  }
120
- this.line(`for (${head}) ${this.loopBody(node.body)}`);
142
+ this.line(this.labeled(node, `for (${head}) ${this.loopBody(node.body)}`));
121
143
  });
122
144
  }
123
145
  /** `[i, v]` of a loop over pairs; `_` leaves its place empty. */
@@ -148,10 +170,20 @@ export class ControlStatementEmitter extends SimpleStatementEmitter {
148
170
  else
149
171
  this.blockStatements(node.body.body);
150
172
  }));
151
- this.line(`for (let ${key} = 0${init}; ${key} < ${items}.length; ${key}++) ${body}`);
173
+ this.line(this.labeled(node, `for (let ${key} = 0${init}; ${key} < ${items}.length; ${key}++) ${body}`));
174
+ }
175
+ switchStatement(node) {
176
+ const { init, discriminant } = node;
177
+ if (init) {
178
+ this.line(this.withInit(init, () => this.switchStatement({ ...node, init: null })));
179
+ }
180
+ else if (discriminant === null)
181
+ this.taglessSwitch(node);
182
+ else
183
+ this.valueSwitch(node, discriminant);
152
184
  }
153
185
  /** Each `case` ends with an implicit `break`; a case with declarations gets its own block. */
154
- switchStatement(node, discriminant) {
186
+ valueSwitch(node, discriminant) {
155
187
  const head = `switch (${this.expression(discriminant, 0)})`;
156
188
  const body = this.withBreakTarget(null, () => this.block(() => {
157
189
  node.cases.forEach((switchCase, i) => {
@@ -178,7 +210,7 @@ export class ControlStatementEmitter extends SimpleStatementEmitter {
178
210
  }
179
211
  });
180
212
  }));
181
- this.line(`${head} ${body}`);
213
+ this.line(this.labeled(node, `${head} ${body}`));
182
214
  }
183
215
  /**
184
216
  * `select`: $$select gives the index of the case that went on and what it received, and a
@@ -189,7 +221,10 @@ export class ControlStatementEmitter extends SimpleStatementEmitter {
189
221
  if (operation === null)
190
222
  return [];
191
223
  if (operation.kind === 'UnaryExpression') {
192
- return [`{ channel: ${this.expression(operation.argument, ARROW)} }`];
224
+ const zero = this.receivedZero(operation.argument);
225
+ return [
226
+ `{ channel: ${this.receivedFrom(operation.argument)}${zero !== 'null' ? `, zero: ${zero}` : ''} }`,
227
+ ];
193
228
  }
194
229
  const channel = this.expression(operation.channel, ARROW);
195
230
  return [`{ channel: ${channel}, value: ${this.expression(operation.value, ARROW)} }`];
@@ -197,7 +232,7 @@ export class ControlStatementEmitter extends SimpleStatementEmitter {
197
232
  const hasDefault = node.cases.some((selectCase) => selectCase.operation === null);
198
233
  let index = 0;
199
234
  const select = this.block(() => {
200
- this.line(`const [$$case, $$received, $$ok] = await $$select([${operations.join(', ')}], ${hasDefault});`);
235
+ this.line(`const [$$case, $$received, $$ok] = await $$Channel.select([${operations.join(', ')}], ${hasDefault});`);
201
236
  const body = this.withBreakTarget(null, () => this.block(() => {
202
237
  for (const { names, operation, body: statements } of node.cases) {
203
238
  const label = operation === null ? 'default:' : `case ${index++}:`;
@@ -223,7 +258,7 @@ export class ControlStatementEmitter extends SimpleStatementEmitter {
223
258
  }));
224
259
  this.line(`switch ($$case) ${body}`);
225
260
  });
226
- this.line(select);
261
+ this.line(this.labeled(node, select));
227
262
  }
228
263
  /**
229
264
  * `switch { case cond: ... }` becomes an if/else chain. A `break` inside it must leave the
@@ -232,9 +267,11 @@ export class ControlStatementEmitter extends SimpleStatementEmitter {
232
267
  taglessSwitch(node) {
233
268
  const cases = node.cases.filter((switchCase) => switchCase.tests.length > 0);
234
269
  const fallback = node.cases.find((switchCase) => switchCase.tests.length === 0);
235
- const label = node.cases.some((switchCase) => containsBreak(switchCase.body))
236
- ? `$$switch${++this.labels}`
237
- : null;
270
+ const label = node.label
271
+ ? this.label(node.label)
272
+ : node.cases.some((switchCase) => containsBreak(switchCase.body))
273
+ ? `$$switch${++this.labels}`
274
+ : null;
238
275
  const text = this.withBreakTarget(label, () => {
239
276
  const caseBlock = (body) => this.block(() => this.blockStatements(body));
240
277
  const branches = cases.map((switchCase) => {
@@ -0,0 +1,14 @@
1
+ import type * as ast from '../ast.ts';
2
+ import { CallEmitter } from './calls.ts';
3
+ export declare abstract class DecodeEmitter extends CallEmitter {
4
+ /** The constants of the object types that decode checks against, by type, in order. */
5
+ private readonly decodeTypes;
6
+ /** `$$decodeJson(text, type)` for a string, `$$decode(value, type)` for a value from JS. */
7
+ protected decode(node: ast.DecodeExpression): string;
8
+ /** The constants of the object types, for the start of the module. */
9
+ protected decodeTypeLines(): string[];
10
+ /** A type as runtime/decode.js has it: `"string"`, `{ array: "number" }`, `$$typeUser`. */
11
+ private shape;
12
+ /** The constant of an object type, made the first time it is used. */
13
+ private objectType;
14
+ }
@@ -0,0 +1,93 @@
1
+ import { isText } from "../checker/json.js";
2
+ import { memberNames, typeToString } from "../checker/types.js";
3
+ import { CallEmitter } from "./calls.js";
4
+ import { ARROW } from "./syntax.js";
5
+ // `decode<User>(body)`: the type, which the checker found, becomes data that the runtime checks the
6
+ // value against (runtime/decode.js). An object type is a constant of the module, `$$typeUser`,
7
+ // written before its code (js.ts), so that types that refer to themselves or each other can do so,
8
+ // and the value of each call is only its name: `$$decodeJson(body, $$typeUser)`.
9
+ const IDENTIFIER = /^[A-Za-z_$][\w$]*$/;
10
+ export class DecodeEmitter extends CallEmitter {
11
+ /** The constants of the object types that decode checks against, by type, in order. */
12
+ decodeTypes = new Map();
13
+ /** `$$decodeJson(text, type)` for a string, `$$decode(value, type)` for a value from JS. */
14
+ decode(node) {
15
+ const argument = this.expression(node.argument, ARROW);
16
+ const result = this.options.types?.get(node);
17
+ const type = result?.kind === 'tuple' ? result.types[0] : undefined;
18
+ // Without the types of the checker nothing is checked (runtime/decode.js).
19
+ if (type === undefined)
20
+ return `$$decode(${argument}, null)`;
21
+ const given = this.options.types?.get(node.argument);
22
+ const decode = given !== undefined && isText(given) ? '$$decodeJson' : '$$decode';
23
+ return `${decode}(${argument}, ${this.shape(type)})`;
24
+ }
25
+ /** The constants of the object types, for the start of the module. */
26
+ decodeTypeLines() {
27
+ return [...this.decodeTypes.values()].flatMap(({ lines }) => lines);
28
+ }
29
+ /** A type as runtime/decode.js has it: `"string"`, `{ array: "number" }`, `$$typeUser`. */
30
+ shape(type) {
31
+ switch (type.kind) {
32
+ case 'number':
33
+ case 'string':
34
+ case 'bool':
35
+ case 'any':
36
+ return `"${type.kind}"`;
37
+ case 'null':
38
+ return '{ literal: [null] }';
39
+ case 'literal':
40
+ return `{ literal: [${JSON.stringify(type.value)}] }`;
41
+ case 'nullable':
42
+ return `{ nullable: ${this.shape(type.type)} }`;
43
+ case 'array':
44
+ return `{ array: ${this.shape(type.element)} }`;
45
+ case 'union': {
46
+ // The literals of a union are one list, as `"admin" | "user"` reads.
47
+ const literals = type.types.flatMap((each) => each.kind === 'literal' ? [each.value] : []);
48
+ const others = type.types.filter((each) => each.kind !== 'literal');
49
+ const shapes = [
50
+ ...(literals.length > 0 ? [`{ literal: [${literals.map(json).join(', ')}] }`] : []),
51
+ ...others.map((each) => this.shape(each)),
52
+ ];
53
+ return shapes.length === 1 ? shapes[0] : `{ union: [${shapes.join(', ')}] }`;
54
+ }
55
+ case 'object':
56
+ return type.index && [...memberNames(type.members)].length === 0
57
+ ? `{ record: ${this.shape(type.index)} }`
58
+ : this.objectType(type);
59
+ // The checker lets no other type through.
60
+ default:
61
+ return '"any"';
62
+ }
63
+ }
64
+ /** The constant of an object type, made the first time it is used. */
65
+ objectType(type) {
66
+ const known = this.decodeTypes.get(type);
67
+ if (known)
68
+ return known.name;
69
+ // `$$type` itself is the function of the runtime that makes them.
70
+ const written = type.instanceOf?.template.name ?? type.name ?? 'Object';
71
+ const base = `$$type${written.replace(/[^\w$]/g, '')}`;
72
+ const taken = new Set([...this.decodeTypes.values()].map(({ name }) => name));
73
+ let name = base;
74
+ for (let i = 2; taken.has(name); i++)
75
+ name = `${base}${i}`;
76
+ const entry = { name, lines: [] };
77
+ // Registered before its fields, which may refer to it.
78
+ this.decodeTypes.set(type, entry);
79
+ const fields = [...memberNames(type.members)].flatMap((field) => {
80
+ const member = type.members.get(field);
81
+ const key = IDENTIFIER.test(field) ? field : json(field);
82
+ return member ? [` ${key}: ${this.shape(member.type)},`] : [];
83
+ });
84
+ entry.lines =
85
+ fields.length === 0
86
+ ? [`const ${name} = $$type(${json(typeToString(type))}, () => ({}));`]
87
+ : [`const ${name} = $$type(${json(typeToString(type))}, () => ({`, ...fields, '}));'];
88
+ return name;
89
+ }
90
+ }
91
+ function json(value) {
92
+ return JSON.stringify(value);
93
+ }
@@ -1,42 +1,8 @@
1
1
  import type * as ast from '../ast.ts';
2
- import type { Type } from '../checker/types.ts';
3
- import { type DecoratorSource } from '../decorators.ts';
4
- import type { InlinePackage } from '../inline-packages.ts';
5
- import { type ImportedComponent, type LocalizedPackage } from './imported.ts';
2
+ import { type LocalizedPackage } from './imported.ts';
6
3
  import type { Root } from '../refs.ts';
7
- import type { StyleModule } from '../style-modules.ts';
8
4
  import type { BindingKind } from './analysis.ts';
9
- import type { RuntimeMode } from './runtime.ts';
10
- export interface JsOptions {
11
- /** The Rook source, used to keep the blank lines between statements. */
12
- source: string;
13
- /** Replace `.rk` with `.js` in relative import paths, for output written next to the sources. */
14
- rewriteImports: boolean;
15
- /** Types from the checker, when it ran; without them the generated code is more general. */
16
- types?: WeakMap<ast.Expression, Type> | undefined;
17
- /** Decorators imported from other modules, by name: their code is inlined here. */
18
- decorators?: ReadonlyMap<string, ImportedDecorator> | undefined;
19
- /** Components imported from other modules, by name: called, or inlined when they are small. */
20
- components?: ReadonlyMap<string, ImportedComponent> | undefined;
21
- /** Components of the module that become functions, since inlining them adds too many lines. */
22
- functions?: ReadonlySet<string> | undefined;
23
- /** The CSS modules the module imports, by their paths as written. */
24
- styleModules?: ReadonlyMap<string, StyleModule> | undefined;
25
- /** For `go`: where the worker module is (`./app.worker.js`), or in it, the functions it runs. */
26
- goroutines?: {
27
- workerUrl: string;
28
- } | {
29
- targets: readonly string[];
30
- } | undefined;
31
- /** How the module gets the runtime it uses: imported, by default, or in it (runtime.ts). */
32
- runtime?: RuntimeMode | undefined;
33
- }
34
- /** A decorator of another module, with the types of the expressions of that module. */
35
- export interface ImportedDecorator extends DecoratorSource {
36
- types: WeakMap<ast.Expression, Type> | null;
37
- /** The components of that module its code uses, which go with it into the components here. */
38
- inline: InlinePackage | null;
39
- }
5
+ import type { JsOptions } from './options.ts';
40
6
  /** How a function implements its `defer` statements. */
41
7
  export type DeferMode = 'none' | 'try' | 'stack' | 'effect';
42
8
  interface FunctionContext {
@@ -54,10 +20,11 @@ interface FunctionContext {
54
20
  * The base of the JS generator: the output buffer, scopes of declared names and the state of the
55
21
  * current function. The generator is split into layers, each adding to the previous one:
56
22
  *
57
- * Emitter → ExpressionEmitter → CallEmitter → SimpleStatementEmitter → ControlStatementEmitter
58
- * → ParallelLoopEmitter → StatementEmitter → UpdateEmitter → AttributeEmitter
59
- * → CreationEmitter → ElementEmitter → ControlFlowEmitter → ComponentPropsEmitter
60
- * → ComponentValueEmitter → EarlyReturnEmitter → RefEmitter
23
+ * Emitter → ExpressionEmitter → CallEmitter → DecodeEmitter → TryEmitter
24
+ * → SimpleStatementEmitter → ControlStatementEmitter → ParallelLoopEmitter
25
+ * → StatementEmitter → UpdateEmitter → AttributeEmitter → CreationEmitter → ElementEmitter
26
+ * → ControlFlowEmitter → ComponentPropsEmitter → ComponentValueEmitter
27
+ * → EarlyReturnEmitter → RefEmitter
61
28
  * → StyleEmitter → ComponentEmitter → WebComponentEmitter → FunctionEmitter → JsGenerator
62
29
  */
63
30
  export declare abstract class Emitter {
@@ -100,7 +67,7 @@ export declare abstract class Emitter {
100
67
  /** The decorators that components of the module apply, by name. */
101
68
  protected readonly appliedDecorators: Set<string>;
102
69
  /** Copies of the code of other modules: their positions are in the sources of those. */
103
- private readonly foreign;
70
+ protected readonly foreign: WeakSet<ast.Node>;
104
71
  /** Components of other modules inlined here, by name, with what their packages bring. */
105
72
  protected readonly inlinedImports: Map<string, LocalizedPackage>;
106
73
  /** The components that decorators of other modules bring, by the names of the decorators. */
@@ -123,6 +90,8 @@ export declare abstract class Emitter {
123
90
  protected lookup(name: string): BindingKind | undefined;
124
91
  /** The JS name for a Rook name. */
125
92
  protected name(name: string): string;
93
+ /** Labels are names of their own in JS, which cannot be reserved words either. */
94
+ protected label(name: ast.Identifier): string;
126
95
  protected params(params: ast.Parameter[]): string;
127
96
  protected withFunction<T>(mode: DeferMode, params: ast.Parameter[], emit: () => T): T;
128
97
  protected withHoisting<T>(hoist: boolean, emit: () => T): T;
@@ -9,10 +9,11 @@ import { JS_RESERVED, JS_UNDECLARABLE } from "./syntax.js";
9
9
  * The base of the JS generator: the output buffer, scopes of declared names and the state of the
10
10
  * current function. The generator is split into layers, each adding to the previous one:
11
11
  *
12
- * Emitter → ExpressionEmitter → CallEmitter → SimpleStatementEmitter → ControlStatementEmitter
13
- * → ParallelLoopEmitter → StatementEmitter → UpdateEmitter → AttributeEmitter
14
- * → CreationEmitter → ElementEmitter → ControlFlowEmitter → ComponentPropsEmitter
15
- * → ComponentValueEmitter → EarlyReturnEmitter → RefEmitter
12
+ * Emitter → ExpressionEmitter → CallEmitter → DecodeEmitter → TryEmitter
13
+ * → SimpleStatementEmitter → ControlStatementEmitter → ParallelLoopEmitter
14
+ * → StatementEmitter → UpdateEmitter → AttributeEmitter → CreationEmitter → ElementEmitter
15
+ * → ControlFlowEmitter → ComponentPropsEmitter → ComponentValueEmitter
16
+ * → EarlyReturnEmitter → RefEmitter
16
17
  * → StyleEmitter → ComponentEmitter → WebComponentEmitter → FunctionEmitter → JsGenerator
17
18
  */
18
19
  export class Emitter {
@@ -250,6 +251,10 @@ export class Emitter {
250
251
  const rename = JS_RESERVED.has(name) || (JS_UNDECLARABLE.has(name) && this.lookup(name) !== undefined);
251
252
  return rename ? `${name}$` : name;
252
253
  }
254
+ /** Labels are names of their own in JS, which cannot be reserved words either. */
255
+ label(name) {
256
+ return JS_RESERVED.has(name.name) ? `${name.name}$` : name.name;
257
+ }
253
258
  params(params) {
254
259
  // Several `_` parameters would be duplicate names in JS.
255
260
  const blanks = params.filter((param) => param.name.name === '_').length;
@@ -21,6 +21,15 @@ export declare abstract class ExpressionEmitter extends Emitter {
21
21
  protected abstract chan(node: ast.ChanExpression): string;
22
22
  protected abstract streamChan(node: ast.StreamChanExpression): string;
23
23
  protected abstract queue(node: ast.QueueExpression): string;
24
+ protected abstract topic(node: ast.TopicExpression): string;
25
+ /** `go f(x)`; implemented by CallEmitter. */
26
+ protected abstract go(node: ast.GoExpression): string;
27
+ /** `decode<T>(x)`; implemented by DecodeEmitter. */
28
+ protected abstract decode(node: ast.DecodeExpression): string;
29
+ /** `try x`, with the precedence of its code; implemented by TryEmitter. */
30
+ protected abstract tryValue(node: ast.TryExpression): [string, number];
31
+ /** `<-x` without its `await`; implemented by CallEmitter. */
32
+ protected abstract receive(node: ast.Expression, withOk: boolean): string;
24
33
  /** `<-ch` that gives `[value, ok]`, in `v, ok = <-ch`. */
25
34
  protected readonly commaOk: WeakSet<ast.Expression>;
26
35
  /** `items[i]` in a part of `go for`, which is `items[$$i]` there (parallel.ts). */
@@ -1,5 +1,4 @@
1
1
  import { decoratedName } from "../decorators.js";
2
- import { goCaptures, goParams, goTarget } from "../goroutines.js";
3
2
  import { isIterator, usesThis } from "../iterators.js";
4
3
  import { containsElement, startsWithObjectLiteral } from "./analysis.js";
5
4
  import { Emitter } from "./emitter.js";
@@ -59,8 +58,9 @@ export class ExpressionEmitter extends Emitter {
59
58
  return [this.functionNames.get(component) ?? `$$${component.name.name}`, PRIMARY];
60
59
  }
61
60
  // `close(ch)` and the like are predeclared; JS has a `close` of its own, the window's.
62
- if (PREDECLARED.has(node.name) && this.lookup(node.name) === undefined) {
63
- return [`$$${node.name}`, PRIMARY];
61
+ const predeclared = PREDECLARED.get(node.name);
62
+ if (predeclared !== undefined && this.lookup(node.name) === undefined) {
63
+ return [predeclared, PRIMARY];
64
64
  }
65
65
  // The predeclared `error` used as a value is JS's `Error`, which also works without `new`.
66
66
  return [this.isBuiltinError(node) ? 'Error' : this.name(node.name), PRIMARY];
@@ -69,6 +69,8 @@ export class ExpressionEmitter extends Emitter {
69
69
  case 'BigIntLiteral':
70
70
  case 'StringLiteral':
71
71
  return [node.raw, PRIMARY];
72
+ case 'RegExpLiteral':
73
+ return [`/${node.pattern}/${node.flags}`, PRIMARY];
72
74
  case 'TemplateLiteral':
73
75
  return [this.template(node), PRIMARY];
74
76
  case 'BooleanLiteral':
@@ -112,23 +114,14 @@ export class ExpressionEmitter extends Emitter {
112
114
  return [this.streamChan(node), POSTFIX];
113
115
  case 'QueueExpression':
114
116
  return [this.queue(node), POSTFIX];
115
- // The worker module has the function under the same name, a literal under the name given to
116
- // it, after the local names it uses; the arguments are copied there. In the worker module a
117
- // literal runs in the same worker, with copies of those names as its first parameters.
118
- case 'GoExpression': {
119
- const { callee } = node.call;
120
- const literal = callee.kind === 'FuncExpression' ? callee : null;
121
- const captured = literal ? goCaptures(this.program, node) : [];
122
- const args = [
123
- ...captured.map((name) => this.name(name)),
124
- this.elements(node.call.arguments),
125
- ];
126
- // Without type checking, a callee that is neither fails when the call runs.
127
- const fn = literal && this.inWorker
128
- ? this.expression({ ...literal, params: [...goParams(literal, captured), ...literal.params] }, ARROW)
129
- : JSON.stringify(goTarget(this.program, node));
130
- return [`$$go(${fn}, [${args.filter((arg) => arg !== '').join(', ')}])`, POSTFIX];
131
- }
117
+ case 'TopicExpression':
118
+ return [this.topic(node), POSTFIX];
119
+ case 'GoExpression':
120
+ return [this.go(node), POSTFIX];
121
+ case 'DecodeExpression':
122
+ return [this.decode(node), POSTFIX];
123
+ case 'TryExpression':
124
+ return this.tryValue(node);
132
125
  case 'UnaryExpression':
133
126
  return [this.unary(node), UNARY];
134
127
  case 'BinaryExpression':
@@ -221,10 +214,8 @@ export class ExpressionEmitter extends Emitter {
221
214
  });
222
215
  }
223
216
  unary(node) {
224
- if (node.operator === '<-') {
225
- const channel = this.expression(node.argument, ARROW);
226
- return `await $$receive(${channel}${this.commaOk.has(node) ? ', true' : ''})`;
227
- }
217
+ if (node.operator === '<-')
218
+ return `await ${this.receive(node.argument, this.commaOk.has(node))}`;
228
219
  const argument = this.expression(node.argument, UNARY);
229
220
  if (node.operator === 'typeof' || node.operator === 'await') {
230
221
  return `${node.operator} ${argument}`;
@@ -1,6 +1,6 @@
1
1
  import type * as ast from '../ast.ts';
2
- import type { JsOptions } from './emitter.ts';
3
- export type { JsOptions } from './emitter.ts';
2
+ import type { JsOptions } from './options.ts';
3
+ export type { JsOptions } from './options.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
6
  /**
@@ -62,12 +62,14 @@ class JsGenerator extends FunctionEmitter {
62
62
  // What is the module's own: its `go`, and the CSS of its components for their shadow roots.
63
63
  const own = [
64
64
  ...(/(?<![.\w$])\$\$go(?![\w$])/.test(rest.join('\n'))
65
- ? moduleGo(inWorker ? null : (goroutines?.workerUrl ?? ''))
65
+ ? moduleGo(inWorker ? null : (goroutines?.workerUrl ?? ''), goroutines && 'nodeLoader' in goroutines ? goroutines.nodeLoader : undefined)
66
66
  : []),
67
67
  ...(styles.length > 0 ? ['const $$styles = [];'] : []),
68
68
  ];
69
+ // The types that decode checks against, before the code that may decode at the top level.
70
+ const types = this.decodeTypeLines();
69
71
  const mode = this.options.runtime ?? 'import';
70
- const runtime = runtimeCode(runtimeNames([...own, ...styles, ...rest].join('\n')), mode);
72
+ const runtime = runtimeCode(runtimeNames([...own, ...types, ...styles, ...rest].join('\n')), mode);
71
73
  if (runtime.length === 0 && own.length === 0 && styles.length === 0) {
72
74
  return this.lines.length === 0 ? '' : `${this.lines.join('\n')}\n`;
73
75
  }
@@ -79,6 +81,7 @@ class JsGenerator extends FunctionEmitter {
79
81
  mode === 'import' ? [...head, ...runtime] : head,
80
82
  mode === 'inline' ? runtime : [],
81
83
  own,
84
+ types,
82
85
  styles,
83
86
  rest,
84
87
  ].filter((section) => section.length > 0);
@@ -0,0 +1,40 @@
1
+ import type * as ast from '../ast.ts';
2
+ import type { Type } from '../checker/types.ts';
3
+ import type { DecoratorSource } from '../decorators.ts';
4
+ import type { InlinePackage } from '../inline-packages.ts';
5
+ import type { StyleModule } from '../style-modules.ts';
6
+ import type { ImportedComponent } from './imported.ts';
7
+ import type { RuntimeMode } from './runtime.ts';
8
+ export interface JsOptions {
9
+ /** The Rook source, used to keep the blank lines between statements. */
10
+ source: string;
11
+ /** Replace `.rk` with `.js` in relative import paths, for output written next to the sources. */
12
+ rewriteImports: boolean;
13
+ /** Types from the checker, when it ran; without them the generated code is more general. */
14
+ types?: WeakMap<ast.Expression, Type> | undefined;
15
+ /** Decorators imported from other modules, by name: their code is inlined here. */
16
+ decorators?: ReadonlyMap<string, ImportedDecorator> | undefined;
17
+ /** Components imported from other modules, by name: called, or inlined when they are small. */
18
+ components?: ReadonlyMap<string, ImportedComponent> | undefined;
19
+ /** Components of the module that become functions, since inlining them adds too many lines. */
20
+ functions?: ReadonlySet<string> | undefined;
21
+ /** The CSS modules the module imports, by their paths as written. */
22
+ styleModules?: ReadonlyMap<string, StyleModule> | undefined;
23
+ /** For `go`: where the worker module is (`./app.worker.js`), or in it, the functions it runs. */
24
+ goroutines?: {
25
+ workerUrl: string;
26
+ nodeLoader?: string | undefined;
27
+ } | {
28
+ targets: readonly string[];
29
+ } | undefined;
30
+ /** How the module gets the runtime it uses: imported, by default, or in it (runtime.ts). */
31
+ runtime?: RuntimeMode | undefined;
32
+ /** The data classes of the module, whose imports stay even if only types use them. */
33
+ dataClasses?: ReadonlySet<string> | undefined;
34
+ }
35
+ /** A decorator of another module, with the types of the expressions of that module. */
36
+ export interface ImportedDecorator extends DecoratorSource {
37
+ types: WeakMap<ast.Expression, Type> | null;
38
+ /** The components of that module its code uses, which go with it into the components here. */
39
+ inline: InlinePackage | null;
40
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -1,7 +1,11 @@
1
1
  /** How a module gets the runtime: by an import, or with the code of the runtime in it. */
2
2
  export type RuntimeMode = 'import' | 'inline';
3
- /** Predeclared functions of goroutines and channels: `close(ch)` is `$$close(ch)`. */
4
- export declare const PREDECLARED: ReadonlySet<string>;
3
+ /**
4
+ * Predeclared names of goroutines and channels, and what they are in JS: `close(ch)` is
5
+ * `$$Channel.close(ch)`, and `Channel` itself is `$$Channel`, so that the names of a program stay
6
+ * its own.
7
+ */
8
+ export declare const PREDECLARED: ReadonlyMap<string, string>;
5
9
  /** The names of the runtime that code uses, in order. */
6
10
  export declare function runtimeNames(code: string): string[];
7
11
  /** What gives a module these names of the runtime: an import, or the files that have them. */
@@ -10,4 +14,4 @@ export declare function runtimeCode(names: readonly string[], mode: RuntimeMode)
10
14
  * The `go` of a module: in a module, a call in a worker that starts with its worker module; in the
11
15
  * worker module, a call in the same worker, of a function of `$$goroutines`.
12
16
  */
13
- export declare function moduleGo(workerUrl: string | null): string[];
17
+ export declare function moduleGo(workerUrl: string | null, nodeLoader?: string): string[];
@@ -1,13 +1,22 @@
1
1
  import { RUNTIME_MODULE } from "../runtime-module.js";
2
2
  import { RUNTIME_FILES } from "../runtime/sources.js";
3
- /** Predeclared functions of goroutines and channels: `close(ch)` is `$$close(ch)`. */
4
- export const PREDECLARED = new Set([
5
- 'after',
6
- 'cancel',
7
- 'close',
8
- 'stream',
9
- 'tick',
10
- 'transfer',
3
+ /**
4
+ * Predeclared names of goroutines and channels, and what they are in JS: `close(ch)` is
5
+ * `$$Channel.close(ch)`, and `Channel` itself is `$$Channel`, so that the names of a program stay
6
+ * its own.
7
+ */
8
+ export const PREDECLARED = new Map([
9
+ ['abortSignal', '$$Channel.abortSignal'],
10
+ ['after', '$$Channel.after'],
11
+ ['cancel', '$$cancel'],
12
+ ['Channel', '$$Channel'],
13
+ ['close', '$$Channel.close'],
14
+ ['done', '$$Channel.done'],
15
+ ['signal', '$$Channel.signal'],
16
+ ['stream', '$$Channel.stream'],
17
+ ['tick', '$$Channel.tick'],
18
+ ['Topic', '$$Topic'],
19
+ ['transfer', '$$transfer'],
11
20
  ]);
12
21
  /** A name of generated code, but not a member: `$$chan(...)`, and not `channel.$$chan`. */
13
22
  const NAME = /(?<![.\w$])\$\$[A-Za-z_][\w$]*/g;
@@ -58,12 +67,12 @@ export function runtimeCode(names, mode) {
58
67
  * The `go` of a module: in a module, a call in a worker that starts with its worker module; in the
59
68
  * worker module, a call in the same worker, of a function of `$$goroutines`.
60
69
  */
61
- export function moduleGo(workerUrl) {
70
+ export function moduleGo(workerUrl, nodeLoader) {
62
71
  if (workerUrl === null) {
63
72
  return ['const $$go = (fn, args) => $$goHere($$goroutines, fn, args);'];
64
73
  }
65
74
  return [
66
- ...startWorker(workerUrl),
75
+ ...startWorker(workerUrl, nodeLoader),
67
76
  '',
68
77
  'const $$go = (name, args) => $$goWorker($$startWorker, name, args);',
69
78
  ];
@@ -72,9 +81,14 @@ export function moduleGo(workerUrl) {
72
81
  * The start of a worker of a module: a Web Worker, or a worker thread of Node. Node takes only the
73
82
  * path of a `file:` URL, without the query of `app.rk?goroutines`, so its worker starts with a
74
83
  * module that imports the whole URL. An absolute URL, such as the Blob of the playground, is used
75
- * as it is.
84
+ * as it is. With `nodeLoader`, as under Vitest, the worker of Node imports the loader of `.rk`
85
+ * files first, since its thread has neither Vite nor the loader of `rook run`.
76
86
  */
77
- function startWorker(url) {
87
+ function startWorker(url, nodeLoader) {
88
+ // The loader registers its hooks when it runs, so the module comes after it, by a dynamic import.
89
+ const code = nodeLoader === undefined
90
+ ? '`import ${JSON.stringify(url.href)};`'
91
+ : `\`import ${JSON.stringify(nodeLoader)}; await import(\${JSON.stringify(url.href)});\``;
78
92
  const base = /^[a-z][a-z\d+.-]*:/i.test(url) ? '' : ', import.meta.url';
79
93
  const workerUrl = `new URL(${JSON.stringify(url)}${base})`;
80
94
  return [
@@ -86,7 +100,7 @@ function startWorker(url) {
86
100
  ' const { Worker: NodeWorker } = await import(/* @vite-ignore */ threads);',
87
101
  // Vite bundles the Web Worker; this URL is only for Node.
88
102
  ` const url = new URL(/* @vite-ignore */ ${JSON.stringify(url)}${base});`,
89
- ' const code = `import ${JSON.stringify(url.href)};`;',
103
+ ` const code = ${code};`,
90
104
  ' return new NodeWorker(new URL(`data:text/javascript,${encodeURIComponent(code)}`));',
91
105
  '}',
92
106
  ];