rooklang 0.3.2 → 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 (178) 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 +7 -0
  16. package/dist/checker/assignments.js +48 -8
  17. package/dist/checker/base.d.ts +2 -1
  18. package/dist/checker/base.js +6 -12
  19. package/dist/checker/builtins.js +15 -5
  20. package/dist/checker/bytes.d.ts +2 -2
  21. package/dist/checker/bytes.js +2 -2
  22. package/dist/checker/calls.js +3 -0
  23. package/dist/checker/channel-objects.d.ts +3 -0
  24. package/dist/checker/channel-objects.js +40 -0
  25. package/dist/checker/channels.d.ts +1 -0
  26. package/dist/checker/channels.js +16 -2
  27. package/dist/checker/checker.d.ts +1 -1
  28. package/dist/checker/checker.js +19 -0
  29. package/dist/checker/class-members.d.ts +22 -0
  30. package/dist/checker/class-members.js +105 -0
  31. package/dist/checker/classes.d.ts +5 -4
  32. package/dist/checker/classes.js +41 -12
  33. package/dist/checker/closing.d.ts +21 -0
  34. package/dist/checker/closing.js +164 -0
  35. package/dist/checker/completion.d.ts +2 -2
  36. package/dist/checker/completion.js +2 -2
  37. package/dist/checker/context.d.ts +19 -0
  38. package/dist/checker/control-flow.d.ts +6 -0
  39. package/dist/checker/control-flow.js +34 -0
  40. package/dist/checker/declarations.js +1 -0
  41. package/dist/checker/decode.d.ts +8 -0
  42. package/dist/checker/decode.js +32 -0
  43. package/dist/checker/error-results.d.ts +6 -0
  44. package/dist/checker/error-results.js +31 -0
  45. package/dist/checker/expressions.d.ts +5 -1
  46. package/dist/checker/expressions.js +13 -34
  47. package/dist/checker/flow.js +8 -4
  48. package/dist/checker/helpers.d.ts +1 -1
  49. package/dist/checker/helpers.js +9 -2
  50. package/dist/checker/iterables.d.ts +3 -1
  51. package/dist/checker/iterables.js +13 -0
  52. package/dist/checker/iterators.d.ts +4 -1
  53. package/dist/checker/iterators.js +12 -4
  54. package/dist/checker/json.d.ts +5 -0
  55. package/dist/checker/json.js +92 -0
  56. package/dist/checker/loops.js +23 -3
  57. package/dist/checker/members.js +7 -0
  58. package/dist/checker/operators.d.ts +2 -2
  59. package/dist/checker/operators.js +7 -5
  60. package/dist/checker/queues.d.ts +2 -2
  61. package/dist/checker/queues.js +2 -2
  62. package/dist/checker/resolution.js +5 -3
  63. package/dist/checker/signals.d.ts +8 -0
  64. package/dist/checker/signals.js +48 -0
  65. package/dist/checker/statements.d.ts +5 -0
  66. package/dist/checker/statements.js +19 -0
  67. package/dist/checker/topics.d.ts +6 -0
  68. package/dist/checker/topics.js +17 -0
  69. package/dist/checker/transfer.d.ts +3 -1
  70. package/dist/checker/transfer.js +25 -5
  71. package/dist/checker/try.d.ts +6 -0
  72. package/dist/checker/try.js +19 -0
  73. package/dist/checker/types/constructors.d.ts +1 -1
  74. package/dist/checker/types/constructors.js +2 -2
  75. package/dist/checker/types/generics.js +1 -1
  76. package/dist/checker/types/iterables.d.ts +13 -0
  77. package/dist/checker/types/iterables.js +52 -0
  78. package/dist/checker/types/model.d.ts +25 -0
  79. package/dist/checker/types/params.js +22 -3
  80. package/dist/checker/types/printing.js +4 -3
  81. package/dist/checker/types/relations.js +19 -25
  82. package/dist/checker/types/topic.d.ts +6 -0
  83. package/dist/checker/types/topic.js +23 -0
  84. package/dist/checker/types.d.ts +2 -0
  85. package/dist/checker/types.js +2 -0
  86. package/dist/cli.js +18 -36
  87. package/dist/codegen/calls.d.ts +23 -0
  88. package/dist/codegen/calls.js +88 -7
  89. package/dist/codegen/control-statements.d.ts +19 -3
  90. package/dist/codegen/control-statements.js +105 -20
  91. package/dist/codegen/decode.d.ts +14 -0
  92. package/dist/codegen/decode.js +93 -0
  93. package/dist/codegen/emitter.d.ts +10 -41
  94. package/dist/codegen/emitter.js +9 -4
  95. package/dist/codegen/expressions.d.ts +9 -0
  96. package/dist/codegen/expressions.js +15 -24
  97. package/dist/codegen/js.d.ts +2 -2
  98. package/dist/codegen/js.js +5 -2
  99. package/dist/codegen/options.d.ts +40 -0
  100. package/dist/codegen/options.js +1 -0
  101. package/dist/codegen/runtime.d.ts +7 -5
  102. package/dist/codegen/runtime.js +28 -20
  103. package/dist/codegen/simple-statements.d.ts +10 -4
  104. package/dist/codegen/simple-statements.js +40 -15
  105. package/dist/codegen/statements.js +64 -4
  106. package/dist/codegen/try.d.ts +5 -0
  107. package/dist/codegen/try.js +40 -0
  108. package/dist/compile.d.ts +9 -0
  109. package/dist/compile.js +19 -6
  110. package/dist/completion.d.ts +4 -2
  111. package/dist/completion.js +10 -5
  112. package/dist/declarations.js +32 -6
  113. package/dist/decorator-copy.js +5 -2
  114. package/dist/goroutines.d.ts +1 -1
  115. package/dist/goroutines.js +5 -4
  116. package/dist/index.d.ts +6 -2
  117. package/dist/index.js +14 -5
  118. package/dist/lexer/base.d.ts +2 -0
  119. package/dist/lexer/base.js +6 -1
  120. package/dist/lexer/lexer.js +3 -0
  121. package/dist/lexer/literals.d.ts +5 -0
  122. package/dist/lexer/literals.js +51 -1
  123. package/dist/lexer/markup.js +3 -4
  124. package/dist/lexer/tables.js +2 -0
  125. package/dist/lexer/token.d.ts +4 -3
  126. package/dist/lexer/token.js +0 -2
  127. package/dist/loader.js +40 -13
  128. package/dist/parser/base.d.ts +4 -0
  129. package/dist/parser/base.js +2 -1
  130. package/dist/parser/classes.d.ts +2 -0
  131. package/dist/parser/classes.js +88 -23
  132. package/dist/parser/control-flow.d.ts +3 -1
  133. package/dist/parser/control-flow.js +21 -24
  134. package/dist/parser/declarations.d.ts +7 -4
  135. package/dist/parser/declarations.js +45 -6
  136. package/dist/parser/expressions.js +27 -3
  137. package/dist/parser/jumps.d.ts +11 -0
  138. package/dist/parser/jumps.js +77 -0
  139. package/dist/parser/literals.d.ts +9 -0
  140. package/dist/parser/literals.js +45 -6
  141. package/dist/parser/markup.js +1 -1
  142. package/dist/parser/simple-statements.d.ts +1 -1
  143. package/dist/parser/simple-statements.js +16 -9
  144. package/dist/parser/statements.d.ts +0 -3
  145. package/dist/parser/statements.js +22 -25
  146. package/dist/parser/syntax.d.ts +11 -1
  147. package/dist/parser/syntax.js +16 -2
  148. package/dist/parser/types.js +8 -3
  149. package/dist/project-modules.d.ts +12 -0
  150. package/dist/project-modules.js +106 -0
  151. package/dist/run-file.d.ts +6 -0
  152. package/dist/run-file.js +111 -0
  153. package/dist/runtime/sources.js +11 -5
  154. package/dist/runtime-module.d.ts +1 -0
  155. package/dist/runtime-module.js +3 -0
  156. package/dist/source-map.d.ts +17 -0
  157. package/dist/source-map.js +75 -0
  158. package/dist/ts-types.d.ts +4 -0
  159. package/dist/ts-types.js +20 -3
  160. package/dist/typescript/convert-objects.d.ts +2 -1
  161. package/dist/typescript/convert-objects.js +18 -9
  162. package/dist/vite.js +13 -5
  163. package/dist/walk-types.js +1 -0
  164. package/dist/walk.d.ts +0 -2
  165. package/dist/walk.js +11 -28
  166. package/package.json +1 -1
  167. package/src/runtime/chan.js +15 -5
  168. package/src/runtime/channel.js +28 -0
  169. package/src/runtime/classes.js +32 -0
  170. package/src/runtime/core.js +10 -1
  171. package/src/runtime/decode.js +190 -0
  172. package/src/runtime/go.js +14 -5
  173. package/src/runtime/index.js +6 -0
  174. package/src/runtime/select.js +14 -9
  175. package/src/runtime/signals.js +59 -0
  176. package/src/runtime/threads.js +32 -5
  177. package/src/runtime/topic.js +118 -0
  178. package/src/runtime/try.js +45 -0
@@ -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,9 +1,11 @@
1
- /** Where compiled code imports the runtime from. */
2
- export declare const RUNTIME_MODULE = "rooklang/runtime";
3
1
  /** How a module gets the runtime: by an import, or with the code of the runtime in it. */
4
2
  export type RuntimeMode = 'import' | 'inline';
5
- /** Predeclared functions of goroutines and channels: `close(ch)` is `$$close(ch)`. */
6
- 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>;
7
9
  /** The names of the runtime that code uses, in order. */
8
10
  export declare function runtimeNames(code: string): string[];
9
11
  /** What gives a module these names of the runtime: an import, or the files that have them. */
@@ -12,4 +14,4 @@ export declare function runtimeCode(names: readonly string[], mode: RuntimeMode)
12
14
  * The `go` of a module: in a module, a call in a worker that starts with its worker module; in the
13
15
  * worker module, a call in the same worker, of a function of `$$goroutines`.
14
16
  */
15
- export declare function moduleGo(workerUrl: string | null): string[];
17
+ export declare function moduleGo(workerUrl: string | null, nodeLoader?: string): string[];
@@ -1,19 +1,22 @@
1
+ import { RUNTIME_MODULE } from "../runtime-module.js";
1
2
  import { RUNTIME_FILES } from "../runtime/sources.js";
2
- // The runtime of generated code, src/runtime. A module imports the names of it that its code uses
3
- // from "rooklang/runtime"; with `runtime: 'inline'` the files that have them are put into the
4
- // module instead, for code that runs without imports, such as that of the playground. A module
5
- // keeps what is its own: the start of its workers, whose URL a bundler must see in it, the `go`
6
- // that uses it, and the CSS of its components.
7
- /** Where compiled code imports the runtime from. */
8
- export const RUNTIME_MODULE = 'rooklang/runtime';
9
- /** Predeclared functions of goroutines and channels: `close(ch)` is `$$close(ch)`. */
10
- export const PREDECLARED = new Set([
11
- 'after',
12
- 'cancel',
13
- 'close',
14
- 'stream',
15
- 'tick',
16
- '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'],
17
20
  ]);
18
21
  /** A name of generated code, but not a member: `$$chan(...)`, and not `channel.$$chan`. */
19
22
  const NAME = /(?<![.\w$])\$\$[A-Za-z_][\w$]*/g;
@@ -64,12 +67,12 @@ export function runtimeCode(names, mode) {
64
67
  * The `go` of a module: in a module, a call in a worker that starts with its worker module; in the
65
68
  * worker module, a call in the same worker, of a function of `$$goroutines`.
66
69
  */
67
- export function moduleGo(workerUrl) {
70
+ export function moduleGo(workerUrl, nodeLoader) {
68
71
  if (workerUrl === null) {
69
72
  return ['const $$go = (fn, args) => $$goHere($$goroutines, fn, args);'];
70
73
  }
71
74
  return [
72
- ...startWorker(workerUrl),
75
+ ...startWorker(workerUrl, nodeLoader),
73
76
  '',
74
77
  'const $$go = (name, args) => $$goWorker($$startWorker, name, args);',
75
78
  ];
@@ -78,9 +81,14 @@ export function moduleGo(workerUrl) {
78
81
  * The start of a worker of a module: a Web Worker, or a worker thread of Node. Node takes only the
79
82
  * path of a `file:` URL, without the query of `app.rk?goroutines`, so its worker starts with a
80
83
  * module that imports the whole URL. An absolute URL, such as the Blob of the playground, is used
81
- * 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`.
82
86
  */
83
- 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)});\``;
84
92
  const base = /^[a-z][a-z\d+.-]*:/i.test(url) ? '' : ', import.meta.url';
85
93
  const workerUrl = `new URL(${JSON.stringify(url)}${base})`;
86
94
  return [
@@ -92,7 +100,7 @@ function startWorker(url) {
92
100
  ' const { Worker: NodeWorker } = await import(/* @vite-ignore */ threads);',
93
101
  // Vite bundles the Web Worker; this URL is only for Node.
94
102
  ` const url = new URL(/* @vite-ignore */ ${JSON.stringify(url)}${base});`,
95
- ' const code = `import ${JSON.stringify(url.href)};`;',
103
+ ` const code = ${code};`,
96
104
  ' return new NodeWorker(new URL(`data:text/javascript,${encodeURIComponent(code)}`));',
97
105
  '}',
98
106
  ];
@@ -1,13 +1,12 @@
1
1
  import type * as ast from '../ast.ts';
2
- import { CallEmitter } from './calls.ts';
2
+ import { TryEmitter } from './try.ts';
3
3
  /** Statements: lists and blocks of them, variables, assignments, `return`; the others are in later layers. */
4
- export declare abstract class SimpleStatementEmitter extends CallEmitter {
4
+ export declare abstract class SimpleStatementEmitter extends TryEmitter {
5
5
  protected abstract forInStatement(node: ast.ForInStatement): void;
6
6
  protected abstract selectStatement(node: ast.SelectStatement): void;
7
7
  protected abstract forStatement(node: ast.ForStatement): void;
8
8
  protected abstract ifStatement(node: ast.IfStatement): string;
9
- protected abstract switchStatement(node: ast.SwitchStatement, discriminant: ast.Expression): void;
10
- protected abstract taglessSwitch(node: ast.SwitchStatement): void;
9
+ protected abstract switchStatement(node: ast.SwitchStatement): void;
11
10
  protected abstract tryStatement(node: ast.TryStatement): string;
12
11
  protected abstract classDeclaration(node: ast.ClassDeclaration): void;
13
12
  protected abstract importDeclaration(node: ast.ImportDeclaration): void;
@@ -29,7 +28,14 @@ export declare abstract class SimpleStatementEmitter extends CallEmitter {
29
28
  /** A block's statements in a new scope, without the braces; `then` writes more code in it. */
30
29
  protected blockStatements(statements: readonly ast.Statement[], then?: () => void): void;
31
30
  protected blockStatement(node: ast.BlockStatement): string;
31
+ /**
32
+ * The start of the statement being written, which its first line gets as a mark for the source
33
+ * map (source-map.ts). Imports have none, since the import of the runtime goes among them, and
34
+ * neither does the code of other modules, whose places are in their own sources.
35
+ */
36
+ private sourceMark;
32
37
  protected statement(node: ast.Statement): void;
38
+ protected line(text: string): void;
33
39
  protected emitStatement(node: ast.Statement): void;
34
40
  /** `let a, b = 1, 2` → `let a = 1, b = 2`; `const q, err = f()` → `const [q, err] = f()`. */
35
41
  protected variableDeclaration(node: ast.VariableDeclaration): string;
@@ -1,10 +1,11 @@
1
1
  import { isIterator } from "../iterators.js";
2
+ import { sourceMark } from "../source-map.js";
2
3
  import { holdsBigInts, TYPED_ARRAYS, TYPED_ELEMENTS } from "../typed-arrays.js";
3
4
  import { declarationsOf } from "./analysis.js";
4
- import { CallEmitter } from "./calls.js";
5
+ import { TryEmitter } from "./try.js";
5
6
  import { ARROW, arrayPattern, POSTFIX } from "./syntax.js";
6
7
  /** Statements: lists and blocks of them, variables, assignments, `return`; the others are in later layers. */
7
- export class SimpleStatementEmitter extends CallEmitter {
8
+ export class SimpleStatementEmitter extends TryEmitter {
8
9
  /** Counter for the labels of switches compiled to if/else. */
9
10
  labels = 0;
10
11
  statements(list) {
@@ -34,8 +35,34 @@ export class SimpleStatementEmitter extends CallEmitter {
34
35
  blockStatement(node) {
35
36
  return this.block(() => this.blockStatements(node.body));
36
37
  }
38
+ /**
39
+ * The start of the statement being written, which its first line gets as a mark for the source
40
+ * map (source-map.ts). Imports have none, since the import of the runtime goes among them, and
41
+ * neither does the code of other modules, whose places are in their own sources.
42
+ */
43
+ sourceMark = null;
37
44
  statement(node) {
38
- this.withHoisting(true, () => this.emitStatement(node));
45
+ // A statement may write the statements inside it first, as a function writes its body before
46
+ // its first line: then the mark waits for that line.
47
+ const outer = this.sourceMark;
48
+ this.sourceMark =
49
+ node.kind === 'ImportDeclaration' || this.foreign.has(node) ? null : node.start;
50
+ try {
51
+ this.withHoisting(true, () => this.emitStatement(node));
52
+ }
53
+ finally {
54
+ this.sourceMark = outer;
55
+ }
56
+ }
57
+ // The marks of the updates of components stand alone in their lines (updates.ts).
58
+ line(text) {
59
+ if (this.sourceMark === null || text.startsWith('\uE000')) {
60
+ super.line(text);
61
+ return;
62
+ }
63
+ const mark = sourceMark(this.sourceMark);
64
+ this.sourceMark = null;
65
+ super.line(mark + text);
39
66
  }
40
67
  emitStatement(node) {
41
68
  switch (node.kind) {
@@ -115,18 +142,15 @@ export class SimpleStatementEmitter extends CallEmitter {
115
142
  this.selectStatement(node);
116
143
  break;
117
144
  case 'SwitchStatement':
118
- if (node.discriminant === null)
119
- this.taglessSwitch(node);
120
- else
121
- this.switchStatement(node, node.discriminant);
145
+ this.switchStatement(node);
122
146
  break;
123
147
  case 'BreakStatement': {
124
- const label = this.fn.breakTargets.at(-1);
148
+ const label = node.label ? this.label(node.label) : this.fn.breakTargets.at(-1);
125
149
  this.line(label ? `break ${label};` : 'break;');
126
150
  break;
127
151
  }
128
152
  case 'ContinueStatement':
129
- this.line('continue;');
153
+ this.line(node.label ? `continue ${this.label(node.label)};` : 'continue;');
130
154
  break;
131
155
  case 'ThrowStatement':
132
156
  this.line(`throw ${this.expression(node.argument, 0)};`);
@@ -137,12 +161,9 @@ export class SimpleStatementEmitter extends CallEmitter {
137
161
  case 'DeferStatement':
138
162
  this.deferStatement(node);
139
163
  break;
140
- // Waits until the channel takes the value.
141
- case 'SendStatement': {
142
- const channel = this.expression(node.channel, ARROW);
143
- this.line(`await $$send(${channel}, ${this.expression(node.value, ARROW)});`);
164
+ case 'SendStatement':
165
+ this.line(`${this.send(node)};`);
144
166
  break;
145
- }
146
167
  case 'MountStatement':
147
168
  this.mountStatement(node);
148
169
  break;
@@ -192,9 +213,13 @@ export class SimpleStatementEmitter extends CallEmitter {
192
213
  // An empty queue, as `[]` is an empty array.
193
214
  case 'QueueType':
194
215
  return 'new $$Queue()';
216
+ // Closed, the same for all types: what is sent to them goes nowhere.
217
+ case 'ChanType':
218
+ return '$$Channel.EMPTY';
219
+ case 'TopicType':
220
+ return '$$Topic.EMPTY';
195
221
  case 'FuncType':
196
222
  case 'ComponentType':
197
- case 'ChanType':
198
223
  case 'ObjectType':
199
224
  case 'UnionType':
200
225
  case 'LiteralType':
@@ -1,3 +1,4 @@
1
+ import { isSuperCall } from "../checker/helpers.js";
1
2
  import { isIterator } from "../iterators.js";
2
3
  import { decoratedName, moduleNamesOf } from "../decorators.js";
3
4
  import { inlinePackages } from "../inline-packages.js";
@@ -16,7 +17,9 @@ export class StatementEmitter extends ParallelLoopEmitter {
16
17
  return;
17
18
  // Imports used only as types (e.g. interfaces) do not exist at runtime, and neither do
18
19
  // components: a component used as a value is its function, imported below.
19
- const used = (name) => this.valueNames.has(name.name) && !this.options.components?.has(name.name);
20
+ // A data class, even used only as a type, registers itself where it is imported.
21
+ const used = (name) => (this.valueNames.has(name.name) || this.options.dataClasses?.has(name.name) === true) &&
22
+ !this.options.components?.has(name.name);
20
23
  const clauses = [];
21
24
  if (node.defaultImport && used(node.defaultImport)) {
22
25
  clauses.push(this.name(node.defaultImport.name));
@@ -100,7 +103,10 @@ export class StatementEmitter extends ParallelLoopEmitter {
100
103
  : '';
101
104
  const body = this.block(() => {
102
105
  let previous;
106
+ // An abstract method has no code: only the subclasses have it.
103
107
  for (const member of node.members) {
108
+ if (member.kind === 'MethodDeclaration' && member.abstract)
109
+ continue;
104
110
  const bothFields = previous?.kind === 'FieldDeclaration' && member.kind === 'FieldDeclaration';
105
111
  if (previous && (!bothFields || this.blankLineBetween(previous, member)))
106
112
  this.blankLine();
@@ -109,6 +115,12 @@ export class StatementEmitter extends ParallelLoopEmitter {
109
115
  }
110
116
  });
111
117
  this.line(`${exported}class ${this.name(node.name.name)}${superClass} ${body}`);
118
+ // A data class is registered, so that its instances get it back in another thread
119
+ // (runtime/classes.js); the id is the same in the worker module, which has a copy of it.
120
+ if (node.data) {
121
+ const id = `${node.name.name}:${hash(this.options.source.slice(node.start, node.end))}`;
122
+ this.line(`$$dataClass(${this.name(node.name.name)}, ${JSON.stringify(id)});`);
123
+ }
112
124
  }
113
125
  /** Visibility modifiers are checked by the compiler and do not exist in the output. */
114
126
  classMember(member) {
@@ -126,17 +138,65 @@ export class StatementEmitter extends ParallelLoopEmitter {
126
138
  break;
127
139
  }
128
140
  case 'ConstructorDeclaration': {
129
- const [params, body] = this.func(member.params, member.body);
141
+ const [params, body] = this.func(member.params, assignProperties(member));
130
142
  this.line(`constructor(${params}) ${body}`);
131
143
  break;
132
144
  }
133
145
  case 'MethodDeclaration': {
146
+ if (!member.body)
147
+ break;
134
148
  const [params, body] = this.func(member.params, member.body);
135
- const modifiers = `${member.isStatic ? 'static ' : ''}${member.async ? 'async ' : ''}`;
136
- const star = isIterator(member.params) ? '*' : '';
149
+ const modifiers = `${member.isStatic ? 'static ' : ''}${member.async ? 'async ' : ''}` +
150
+ (member.accessor ? `${member.accessor} ` : '');
151
+ const star = isIterator(member.params) && !member.accessor ? '*' : '';
137
152
  this.line(`${modifiers}${star}${member.name.name}(${params}) ${body}`);
138
153
  break;
139
154
  }
140
155
  }
141
156
  }
142
157
  }
158
+ /**
159
+ * The body of a constructor whose parameters are properties, `constructor(private name string)`:
160
+ * it assigns them first, `this.name = name`, or right after `super(...)` in a derived class.
161
+ */
162
+ function assignProperties(node) {
163
+ const assignments = node.params.flatMap((param) => {
164
+ if (!param.property)
165
+ return [];
166
+ const at = { start: param.start, end: param.end };
167
+ const self = { kind: 'ThisExpression', ...at };
168
+ const target = {
169
+ kind: 'MemberExpression',
170
+ object: self,
171
+ property: param.name,
172
+ optional: false,
173
+ ...at,
174
+ };
175
+ return [
176
+ {
177
+ kind: 'AssignmentStatement',
178
+ operator: '=',
179
+ targets: [target],
180
+ values: [param.name],
181
+ ...at,
182
+ },
183
+ ];
184
+ });
185
+ if (assignments.length === 0)
186
+ return node.body;
187
+ const statements = node.body.body;
188
+ const after = statements.findIndex(isSuperCall) + 1;
189
+ return {
190
+ ...node.body,
191
+ body: [...statements.slice(0, after), ...assignments, ...statements.slice(after)],
192
+ };
193
+ }
194
+ /** FNV-1a of a text, as 8 hex digits: an id that changes only with the text. */
195
+ function hash(text) {
196
+ let value = 0x811c9dc5;
197
+ for (let i = 0; i < text.length; i++) {
198
+ value ^= text.charCodeAt(i);
199
+ value = Math.imul(value, 0x01000193) >>> 0;
200
+ }
201
+ return value.toString(16).padStart(8, '0');
202
+ }
@@ -0,0 +1,5 @@
1
+ import type * as ast from '../ast.ts';
2
+ import { DecodeEmitter } from './decode.ts';
3
+ export declare abstract class TryEmitter extends DecodeEmitter {
4
+ protected tryValue(node: ast.TryExpression): [string, number];
5
+ }
@@ -0,0 +1,40 @@
1
+ import { isErrorType } from "../checker/error-results.js";
2
+ import { forEachChild } from "../walk.js";
3
+ import { startsWithObjectLiteral } from "./analysis.js";
4
+ import { DecodeEmitter } from "./decode.js";
5
+ import { ARROW, POSTFIX, UNARY } from "./syntax.js";
6
+ // `try expr`: the expression runs in a function that the runtime calls inside try/catch
7
+ // (runtime/try.js), `$$catch(() => JSON.parse(text))`. One that waits is an async function, whose
8
+ // rejected Promise is caught as well: `await $$catchAsync(async () => await fetch(url))`. The runtime
9
+ // is told how many values the expression gives, and whether the last of them is an error already.
10
+ export class TryEmitter extends DecodeEmitter {
11
+ tryValue(node) {
12
+ // The expression may not run to the end, so it creates no elements in advance.
13
+ const code = this.withHoisting(false, () => this.expression(node.argument, ARROW));
14
+ const body = startsWithObjectLiteral(node.argument) ? `(${code})` : code;
15
+ const type = this.options.types?.get(node.argument);
16
+ let results = '';
17
+ if (type?.kind === 'void' || (type !== undefined && isErrorType(type))) {
18
+ results = type.kind === 'void' ? ', 0' : ', 1, true';
19
+ }
20
+ else if (type?.kind === 'tuple') {
21
+ results = `, ${type.types.length}${isErrorType(type.types.at(-1)) ? ', true' : ''}`;
22
+ }
23
+ return waits(node.argument)
24
+ ? [`await $$catchAsync(async () => ${body}${results})`, UNARY]
25
+ : [`$$catch(() => ${body}${results})`, POSTFIX];
26
+ }
27
+ }
28
+ /** Whether the expression waits, with `await` or `<-`, outside the functions in it. */
29
+ function waits(node) {
30
+ if (node.kind === 'UnaryExpression' && (node.operator === 'await' || node.operator === '<-')) {
31
+ return true;
32
+ }
33
+ if (node.kind === 'FuncExpression' || node.kind === 'ArrowFunction')
34
+ return false;
35
+ let found = false;
36
+ forEachChild(node, (child) => {
37
+ found ||= waits(child);
38
+ });
39
+ return found;
40
+ }
package/dist/compile.d.ts CHANGED
@@ -2,6 +2,7 @@ import type * as ast from './ast.ts';
2
2
  import { type CheckOptions, type ImportResult, type Library } from './checker/checker.ts';
3
3
  import type { RuntimeMode } from './codegen/runtime.ts';
4
4
  import type { Diagnostic } from './diagnostics.ts';
5
+ import { type SourceMap } from './source-map.ts';
5
6
  import { type StyleModule } from './style-modules.ts';
6
7
  export interface CoreCompileOptions {
7
8
  /** Replace `.rk` with `.js` in relative import paths. Defaults to `true`. */
@@ -22,12 +23,16 @@ export interface CoreCompileOptions {
22
23
  readStyleModule?: ((specifier: string) => string | null) | undefined;
23
24
  /** Where the worker module of `go` is, relative to the module; see workerUrl(). */
24
25
  workerUrl?: string | undefined;
26
+ /** A module that a worker thread of Node imports first, to load `.rk` files (vite.ts). */
27
+ nodeLoader?: string | undefined;
25
28
  /**
26
29
  * How the code gets the runtime of Rook: `import`, the default, imports it from
27
30
  * "rooklang/runtime"; `inline` puts the parts it uses into the module, for code that runs
28
31
  * without imports (src/codegen/runtime.ts).
29
32
  */
30
33
  runtime?: RuntimeMode | undefined;
34
+ /** The name of the source in the source maps, `app.rk`. */
35
+ sourceName?: string | undefined;
31
36
  }
32
37
  /**
33
38
  * Where the worker module of a module is: a `.worker.js` next to its `.js`, or, where `.rk`
@@ -45,6 +50,10 @@ export interface CompileResult {
45
50
  dependencies: string[];
46
51
  /** The `.d.ts` for the module, with the `declarations` option. */
47
52
  declarations?: string;
53
+ /** Where the lines of `code` come from in the source, when there is code. */
54
+ map?: SourceMap;
55
+ /** That of the worker module. */
56
+ workerMap?: SourceMap;
48
57
  /** The worker module that runs the functions `go` calls, when the module has `go`. */
49
58
  worker?: string;
50
59
  }
package/dist/compile.js CHANGED
@@ -3,6 +3,7 @@ import { property, STRING } from "./checker/types.js";
3
3
  import { generateJs } from "./codegen/js.js";
4
4
  import { printDeclarations } from "./declarations.js";
5
5
  import { goroutines, goTarget, workerProgram } from "./goroutines.js";
6
+ import { withoutMarks } from "./source-map.js";
6
7
  import { parse } from "./parser/parser.js";
7
8
  import { isStyleModule, styleModule, unusedClasses } from "./style-modules.js";
8
9
  /**
@@ -24,6 +25,8 @@ export function compileModule(source, options = {}) {
24
25
  return { code: '', diagnostics: problems, warnings: [], dependencies };
25
26
  let types;
26
27
  let declarations;
28
+ // Without the checker, only the data classes that the module declares are known.
29
+ let dataClasses = new Set(program.body.flatMap((each) => each.kind === 'ClassDeclaration' && each.data ? [each.name.name] : []));
27
30
  let warnings = [];
28
31
  if (options.typeCheck ?? true) {
29
32
  const checked = check(program, checkOptions);
@@ -32,6 +35,7 @@ export function compileModule(source, options = {}) {
32
35
  return { code: '', diagnostics: checked.diagnostics, warnings, dependencies };
33
36
  }
34
37
  types = checked.types;
38
+ dataClasses = checked.dataClasses;
35
39
  if (options.declarations)
36
40
  declarations = printDeclarations(program, checked);
37
41
  }
@@ -42,25 +46,34 @@ export function compileModule(source, options = {}) {
42
46
  types,
43
47
  styleModules,
44
48
  runtime: options.runtime,
49
+ dataClasses,
45
50
  ...importedCode(program, options.importModule),
46
51
  };
47
- const code = generateJs(program, {
52
+ const sourceName = options.sourceName ?? 'main.rk';
53
+ const main = withoutMarks(generateJs(program, {
48
54
  ...jsOptions,
49
- goroutines: { workerUrl: options.workerUrl ?? workerUrl('main.rk', rewriteImports) },
50
- });
55
+ goroutines: {
56
+ workerUrl: options.workerUrl ?? workerUrl('main.rk', rewriteImports),
57
+ nodeLoader: options.nodeLoader,
58
+ },
59
+ }), source, sourceName);
51
60
  const targets = [
52
61
  ...new Set(goroutines(program).flatMap((node) => goTarget(program, node) ?? [])),
53
62
  ];
54
63
  const worker = targets.length > 0
55
- ? generateJs(workerProgram(program, targets), { ...jsOptions, goroutines: { targets } })
64
+ ? withoutMarks(generateJs(workerProgram(program, targets, dataClasses), {
65
+ ...jsOptions,
66
+ goroutines: { targets },
67
+ }), source, sourceName)
56
68
  : undefined;
57
69
  return {
58
- code,
70
+ code: main.code,
59
71
  diagnostics: [],
60
72
  warnings,
61
73
  dependencies,
62
74
  ...(declarations ? { declarations } : {}),
63
- ...(worker === undefined ? {} : { worker }),
75
+ map: main.map,
76
+ ...(worker === undefined ? {} : { worker: worker.code, workerMap: worker.map }),
64
77
  };
65
78
  }
66
79
  /**