rooklang 0.3.1 → 0.3.3

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 (107) hide show
  1. package/README.md +17 -4
  2. package/README.ru.md +17 -4
  3. package/dist/ast/expressions.d.ts +16 -1
  4. package/dist/ast/markup.d.ts +6 -1
  5. package/dist/browser.d.ts +5 -0
  6. package/dist/browser.js +1 -0
  7. package/dist/checker/assignments.d.ts +5 -0
  8. package/dist/checker/assignments.js +34 -8
  9. package/dist/checker/base.d.ts +2 -1
  10. package/dist/checker/base.js +4 -10
  11. package/dist/checker/builtins.js +4 -35
  12. package/dist/checker/bytes.d.ts +2 -2
  13. package/dist/checker/bytes.js +2 -2
  14. package/dist/checker/channels.d.ts +3 -0
  15. package/dist/checker/channels.js +39 -1
  16. package/dist/checker/context.d.ts +6 -0
  17. package/dist/checker/control-flow.d.ts +1 -0
  18. package/dist/checker/control-flow.js +17 -0
  19. package/dist/checker/error-results.d.ts +6 -0
  20. package/dist/checker/error-results.js +31 -0
  21. package/dist/checker/expressions.d.ts +2 -0
  22. package/dist/checker/expressions.js +4 -0
  23. package/dist/checker/helpers.js +2 -0
  24. package/dist/checker/queues.d.ts +6 -0
  25. package/dist/checker/queues.js +24 -0
  26. package/dist/checker/resolution.js +6 -2
  27. package/dist/checker/statements.d.ts +5 -0
  28. package/dist/checker/statements.js +19 -0
  29. package/dist/checker/transfer.js +4 -1
  30. package/dist/checker/types/printing.js +7 -0
  31. package/dist/checker/types/queue.d.ts +6 -0
  32. package/dist/checker/types/queue.js +42 -0
  33. package/dist/checker/types/relations.js +12 -1
  34. package/dist/checker/types.d.ts +1 -0
  35. package/dist/checker/types.js +1 -0
  36. package/dist/checker/untyped-globals.d.ts +1 -0
  37. package/dist/checker/untyped-globals.js +47 -0
  38. package/dist/codegen/attributes.js +0 -1
  39. package/dist/codegen/calls.d.ts +9 -0
  40. package/dist/codegen/calls.js +41 -1
  41. package/dist/codegen/component-values.js +0 -1
  42. package/dist/codegen/components.js +0 -1
  43. package/dist/codegen/control-flow.js +0 -3
  44. package/dist/codegen/control-statements.d.ts +9 -1
  45. package/dist/codegen/control-statements.js +51 -7
  46. package/dist/codegen/creation.d.ts +6 -8
  47. package/dist/codegen/creation.js +1 -10
  48. package/dist/codegen/early-returns.js +0 -1
  49. package/dist/codegen/elements.js +0 -3
  50. package/dist/codegen/emitter.d.ts +3 -5
  51. package/dist/codegen/emitter.js +0 -2
  52. package/dist/codegen/expressions.d.ts +4 -1
  53. package/dist/codegen/expressions.js +9 -13
  54. package/dist/codegen/functions.js +0 -2
  55. package/dist/codegen/js.d.ts +0 -7
  56. package/dist/codegen/js.js +33 -59
  57. package/dist/codegen/parallel.js +2 -4
  58. package/dist/codegen/refs.js +0 -1
  59. package/dist/codegen/runtime.d.ts +13 -0
  60. package/dist/codegen/runtime.js +93 -0
  61. package/dist/codegen/simple-statements.js +3 -1
  62. package/dist/codegen/styles.d.ts +1 -1
  63. package/dist/codegen/styles.js +2 -2
  64. package/dist/codegen/web-components.js +0 -1
  65. package/dist/compile.d.ts +7 -0
  66. package/dist/compile.js +1 -0
  67. package/dist/completion.js +2 -1
  68. package/dist/index.d.ts +2 -0
  69. package/dist/index.js +1 -0
  70. package/dist/loader.js +19 -6
  71. package/dist/parser/base.d.ts +3 -1
  72. package/dist/parser/expressions.js +9 -2
  73. package/dist/parser/literals.d.ts +7 -0
  74. package/dist/parser/literals.js +45 -1
  75. package/dist/parser/simple-statements.js +3 -0
  76. package/dist/parser/types.d.ts +1 -1
  77. package/dist/parser/types.js +10 -3
  78. package/dist/runtime/sources.d.ts +2 -0
  79. package/dist/runtime/sources.js +14 -0
  80. package/dist/runtime-module.d.ts +1 -0
  81. package/dist/runtime-module.js +3 -0
  82. package/dist/ts-types.d.ts +4 -0
  83. package/dist/ts-types.js +25 -3
  84. package/dist/vite.js +7 -1
  85. package/dist/walk-types.js +1 -0
  86. package/dist/walk.js +9 -0
  87. package/package.json +6 -4
  88. package/src/runtime/chan.js +277 -0
  89. package/src/runtime/core.js +23 -0
  90. package/src/runtime/go.js +340 -0
  91. package/src/runtime/index.js +13 -0
  92. package/src/runtime/markup.js +336 -0
  93. package/src/runtime/queue.js +118 -0
  94. package/src/runtime/select.js +170 -0
  95. package/src/runtime/streams.js +61 -0
  96. package/src/runtime/threads.js +240 -0
  97. package/src/runtime/timers.js +27 -0
  98. package/dist/codegen/chan-helpers.d.ts +0 -2
  99. package/dist/codegen/chan-helpers.js +0 -253
  100. package/dist/codegen/go-helpers.d.ts +0 -9
  101. package/dist/codegen/go-helpers.js +0 -307
  102. package/dist/codegen/helpers.d.ts +0 -10
  103. package/dist/codegen/helpers.js +0 -356
  104. package/dist/codegen/select-helpers.d.ts +0 -2
  105. package/dist/codegen/select-helpers.js +0 -135
  106. package/dist/codegen/thread-helpers.d.ts +0 -2
  107. package/dist/codegen/thread-helpers.js +0 -198
@@ -6,7 +6,7 @@ import { type ImportedComponent, type LocalizedPackage } from './imported.ts';
6
6
  import type { Root } from '../refs.ts';
7
7
  import type { StyleModule } from '../style-modules.ts';
8
8
  import type { BindingKind } from './analysis.ts';
9
- import type { Helper } from './helpers.ts';
9
+ import type { RuntimeMode } from './runtime.ts';
10
10
  export interface JsOptions {
11
11
  /** The Rook source, used to keep the blank lines between statements. */
12
12
  source: string;
@@ -22,14 +22,14 @@ export interface JsOptions {
22
22
  functions?: ReadonlySet<string> | undefined;
23
23
  /** The CSS modules the module imports, by their paths as written. */
24
24
  styleModules?: ReadonlyMap<string, StyleModule> | undefined;
25
- /** Create elements with their attributes by a helper, $$element; see creation.ts. */
26
- elementHelper?: boolean | undefined;
27
25
  /** For `go`: where the worker module is (`./app.worker.js`), or in it, the functions it runs. */
28
26
  goroutines?: {
29
27
  workerUrl: string;
30
28
  } | {
31
29
  targets: readonly string[];
32
30
  } | undefined;
31
+ /** How the module gets the runtime it uses: imported, by default, or in it (runtime.ts). */
32
+ runtime?: RuntimeMode | undefined;
33
33
  }
34
34
  /** A decorator of another module, with the types of the expressions of that module. */
35
35
  export interface ImportedDecorator extends DecoratorSource {
@@ -67,8 +67,6 @@ export declare abstract class Emitter {
67
67
  private level;
68
68
  private readonly scopes;
69
69
  protected fn: FunctionContext;
70
- /** Helpers that the module must define, such as $$runDeferred. */
71
- protected readonly helpers: Set<Helper>;
72
70
  /**
73
71
  * Whether code for the current expression may be written before the statement that contains it.
74
72
  * Elements use this to be created in statements of their own; it is off where an expression is
@@ -27,8 +27,6 @@ export class Emitter {
27
27
  temps: 0,
28
28
  breakTargets: [],
29
29
  };
30
- /** Helpers that the module must define, such as $$runDeferred. */
31
- helpers = new Set();
32
30
  /**
33
31
  * Whether code for the current expression may be written before the statement that contains it.
34
32
  * Elements use this to be created in statements of their own; it is off where an expression is
@@ -18,11 +18,14 @@ export declare abstract class ExpressionEmitter extends Emitter {
18
18
  protected abstract arrayLiteral(node: ast.ArrayLiteral): string;
19
19
  protected abstract conversion(node: ast.ConversionExpression): string;
20
20
  protected abstract make(node: ast.MakeExpression): string;
21
+ protected abstract chan(node: ast.ChanExpression): string;
22
+ protected abstract streamChan(node: ast.StreamChanExpression): string;
23
+ protected abstract queue(node: ast.QueueExpression): string;
21
24
  /** `<-ch` that gives `[value, ok]`, in `v, ok = <-ch`. */
22
25
  protected readonly commaOk: WeakSet<ast.Expression>;
23
26
  /** `items[i]` in a part of `go for`, which is `items[$$i]` there (parallel.ts). */
24
27
  protected readonly partElements: Set<ast.Expression>;
25
- /** In the worker module, `go` runs in the same worker (go-helpers.ts). */
28
+ /** In the worker module, `go` runs in the same worker (runtime.ts). */
26
29
  protected get inWorker(): boolean;
27
30
  /** `v, ok = <-ch`: two variables from a single receive. */
28
31
  protected markCommaOk(count: number, values: readonly ast.Expression[]): void;
@@ -3,7 +3,7 @@ import { goCaptures, goParams, goTarget } from "../goroutines.js";
3
3
  import { isIterator, usesThis } from "../iterators.js";
4
4
  import { containsElement, startsWithObjectLiteral } from "./analysis.js";
5
5
  import { Emitter } from "./emitter.js";
6
- import { PREDECLARED } from "./helpers.js";
6
+ import { PREDECLARED } from "./runtime.js";
7
7
  import { ARROW, BINARY, CONDITIONAL, POSTFIX, PRIMARY, UNARY } from "./syntax.js";
8
8
  /** Expressions, with the parentheses that JS operator precedence requires. */
9
9
  export class ExpressionEmitter extends Emitter {
@@ -11,7 +11,7 @@ export class ExpressionEmitter extends Emitter {
11
11
  commaOk = new WeakSet();
12
12
  /** `items[i]` in a part of `go for`, which is `items[$$i]` there (parallel.ts). */
13
13
  partElements = new Set();
14
- /** In the worker module, `go` runs in the same worker (go-helpers.ts). */
14
+ /** In the worker module, `go` runs in the same worker (runtime.ts). */
15
15
  get inWorker() {
16
16
  return this.options.goroutines !== undefined && 'targets' in this.options.goroutines;
17
17
  }
@@ -59,9 +59,7 @@ export class ExpressionEmitter extends Emitter {
59
59
  return [this.functionNames.get(component) ?? `$$${component.name.name}`, PRIMARY];
60
60
  }
61
61
  // `close(ch)` and the like are predeclared; JS has a `close` of its own, the window's.
62
- const helper = Object.hasOwn(PREDECLARED, node.name) ? PREDECLARED[node.name] : undefined;
63
- if (helper && this.lookup(node.name) === undefined) {
64
- this.helpers.add(helper);
62
+ if (PREDECLARED.has(node.name) && this.lookup(node.name) === undefined) {
65
63
  return [`$$${node.name}`, PRIMARY];
66
64
  }
67
65
  // The predeclared `error` used as a value is JS's `Error`, which also works without `new`.
@@ -108,12 +106,12 @@ export class ExpressionEmitter extends Emitter {
108
106
  }
109
107
  case 'ArrowFunction':
110
108
  return [this.arrowFunction(node), ARROW];
111
- // `chan<number>(10)`: the channel keeps the zero value that a closed one gives.
112
- case 'ChanExpression': {
113
- const size = node.size ? this.expression(node.size, ARROW) : '0';
114
- this.helpers.add('chan');
115
- return [`$$chan(${size}, ${this.zeroValue(node.element) ?? 'null'})`, POSTFIX];
116
- }
109
+ case 'ChanExpression':
110
+ return [this.chan(node), POSTFIX];
111
+ case 'StreamChanExpression':
112
+ return [this.streamChan(node), POSTFIX];
113
+ case 'QueueExpression':
114
+ return [this.queue(node), POSTFIX];
117
115
  // The worker module has the function under the same name, a literal under the name given to
118
116
  // it, after the local names it uses; the arguments are copied there. In the worker module a
119
117
  // literal runs in the same worker, with copies of those names as its first parameters.
@@ -129,7 +127,6 @@ export class ExpressionEmitter extends Emitter {
129
127
  const fn = literal && this.inWorker
130
128
  ? this.expression({ ...literal, params: [...goParams(literal, captured), ...literal.params] }, ARROW)
131
129
  : JSON.stringify(goTarget(this.program, node));
132
- this.helpers.add(this.inWorker ? 'goLocal' : 'go');
133
130
  return [`$$go(${fn}, [${args.filter((arg) => arg !== '').join(', ')}])`, POSTFIX];
134
131
  }
135
132
  case 'UnaryExpression':
@@ -225,7 +222,6 @@ export class ExpressionEmitter extends Emitter {
225
222
  }
226
223
  unary(node) {
227
224
  if (node.operator === '<-') {
228
- this.helpers.add('chan');
229
225
  const channel = this.expression(node.argument, ARROW);
230
226
  return `await $$receive(${channel}${this.commaOk.has(node) ? ', true' : ''})`;
231
227
  }
@@ -22,7 +22,6 @@ export class FunctionEmitter extends WebComponentEmitter {
22
22
  this.statementsWithDefers(body.body);
23
23
  }
24
24
  else if (mode === 'stack') {
25
- this.helpers.add('runDeferred');
26
25
  this.line('const $$defer = [];');
27
26
  const statements = this.block(() => this.statements(body.body));
28
27
  const finalizer = this.block(() => this.line('$$runDeferred($$defer);'));
@@ -60,7 +59,6 @@ export class FunctionEmitter extends WebComponentEmitter {
60
59
  effectBody(body, cleanups) {
61
60
  return this.withFunction('effect', [], () => {
62
61
  this.fn.deferList = cleanups;
63
- this.helpers.add('runDeferred');
64
62
  return this.block(() => this.withScope(declarationsOf(body.body), () => {
65
63
  this.line(`$$runDeferred(${cleanups}.splice(0));`);
66
64
  this.statements(body.body);
@@ -3,18 +3,11 @@ import type { JsOptions } from './emitter.ts';
3
3
  export type { JsOptions } from './emitter.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
- /**
7
- * A module whose code creates more elements than this creates them with $$element, which takes
8
- * their attributes: its definition takes less than the statements it saves.
9
- */
10
- export declare const ELEMENT_HELPER_LIMIT = 20;
11
6
  /**
12
7
  * Generates a readable ES module from a program that has parsed without errors. A component is
13
8
  * inlined where it is used, unless that adds more than INLINE_LIMIT lines compared to a function
14
9
  * and its calls. The module is generated again with each such candidate as a function, and the one
15
10
  * that saves the most goes first: that changes the sizes of the components inside it. A component
16
11
  * of another module is a candidate too; its function is in that module, and counts as one copy.
17
- * Then, if the code creates more than ELEMENT_HELPER_LIMIT elements, it is generated again with
18
- * the helper that creates them.
19
12
  */
20
13
  export declare function generateJs(program: ast.Program, options: JsOptions): string;
@@ -1,31 +1,16 @@
1
1
  import { declarationsOf } from "./analysis.js";
2
2
  import { FunctionEmitter } from "./functions.js";
3
- import { startWorker } from "./go-helpers.js";
4
- import { HELPER_NEEDS, HELPERS } from "./helpers.js";
3
+ import { moduleGo, runtimeCode, runtimeNames } from "./runtime.js";
5
4
  /** A component whose inlined copies add more lines than this to its module becomes a function. */
6
5
  export const INLINE_LIMIT = 100;
7
- /**
8
- * A module whose code creates more elements than this creates them with $$element, which takes
9
- * their attributes: its definition takes less than the statements it saves.
10
- */
11
- export const ELEMENT_HELPER_LIMIT = 20;
12
6
  /**
13
7
  * Generates a readable ES module from a program that has parsed without errors. A component is
14
8
  * inlined where it is used, unless that adds more than INLINE_LIMIT lines compared to a function
15
9
  * and its calls. The module is generated again with each such candidate as a function, and the one
16
10
  * that saves the most goes first: that changes the sizes of the components inside it. A component
17
11
  * of another module is a candidate too; its function is in that module, and counts as one copy.
18
- * Then, if the code creates more than ELEMENT_HELPER_LIMIT elements, it is generated again with
19
- * the helper that creates them.
20
12
  */
21
13
  export function generateJs(program, options) {
22
- const { code, functions, elements } = chooseFunctions(program, options);
23
- if (elements <= ELEMENT_HELPER_LIMIT)
24
- return code;
25
- return new JsGenerator(program, { ...options, functions, elementHelper: true }).generate();
26
- }
27
- /** The code with the components that become functions; see generateJs. */
28
- function chooseFunctions(program, options) {
29
14
  const functions = new Set();
30
15
  const generate = (names) => {
31
16
  const generator = new JsGenerator(program, { ...options, functions: names });
@@ -47,14 +32,13 @@ function chooseFunctions(program, options) {
47
32
  if (saved > INLINE_LIMIT && (!best || saved > best.saved))
48
33
  best = { ...trial, name, saved };
49
34
  }
50
- if (!best) {
51
- return { code: current.code, functions, elements: current.generator.createdElements };
52
- }
35
+ if (!best)
36
+ return current.code;
53
37
  functions.add(best.name);
54
38
  current = best;
55
39
  }
56
40
  }
57
- /** The whole module: its statements, plus the helpers that its code needs. */
41
+ /** The whole module: its statements, plus the runtime that its code needs. */
58
42
  class JsGenerator extends FunctionEmitter {
59
43
  generate() {
60
44
  this.withScope(declarationsOf(this.program.body), () => {
@@ -63,51 +47,41 @@ class JsGenerator extends FunctionEmitter {
63
47
  this.hiddenExports();
64
48
  });
65
49
  const { goroutines } = this.options;
50
+ const inWorker = goroutines !== undefined && 'targets' in goroutines;
66
51
  // The worker module runs the functions that `go` calls (see goroutines.ts).
67
- if (goroutines && 'targets' in goroutines) {
52
+ if (inWorker) {
68
53
  const names = goroutines.targets.map((name) => this.name(name));
69
- this.helpers.add('serve');
70
54
  this.lines.push('', `const $$goroutines = { ${names.join(', ')} };`, '$$serve($$goroutines);');
71
55
  }
56
+ let imports = 0;
57
+ while (this.lines[imports]?.startsWith('import '))
58
+ imports++;
59
+ const rest = this.lines.slice(imports);
72
60
  // The CSS of the components goes before the code, so that the page has it before the markup.
73
61
  const styles = this.styleRegistrations();
74
- let lines = this.lines;
75
- if (styles.length > 0)
76
- this.helpers.add('style');
77
- if (this.helpers.size > 0) {
78
- const needed = new Set();
79
- const add = (helper) => {
80
- if (needed.has(helper))
81
- return;
82
- needed.add(helper);
83
- for (const other of HELPER_NEEDS[helper] ?? [])
84
- add(other);
85
- };
86
- for (const helper of this.helpers)
87
- add(helper);
88
- const workerUrl = goroutines && 'workerUrl' in goroutines ? goroutines.workerUrl : '';
89
- const helpers = [...needed]
90
- .sort()
91
- .flatMap((helper, i) => [
92
- ...(i > 0 ? [''] : []),
93
- ...HELPERS[helper],
94
- ...(helper === 'go' ? ['', ...startWorker(workerUrl)] : []),
95
- ]);
96
- let imports = 0;
97
- while (lines[imports]?.startsWith('import '))
98
- imports++;
99
- const rest = lines.slice(imports);
100
- while (rest[0] === '')
101
- rest.shift();
102
- lines = [
103
- ...lines.slice(0, imports),
104
- ...(imports > 0 ? [''] : []),
105
- ...helpers,
106
- ...(styles.length > 0 ? ['', ...styles] : []),
107
- ...(rest.length > 0 ? [''] : []),
108
- ...rest,
109
- ];
62
+ // What is the module's own: its `go`, and the CSS of its components for their shadow roots.
63
+ const own = [
64
+ ...(/(?<![.\w$])\$\$go(?![\w$])/.test(rest.join('\n'))
65
+ ? moduleGo(inWorker ? null : (goroutines?.workerUrl ?? ''))
66
+ : []),
67
+ ...(styles.length > 0 ? ['const $$styles = [];'] : []),
68
+ ];
69
+ const mode = this.options.runtime ?? 'import';
70
+ const runtime = runtimeCode(runtimeNames([...own, ...styles, ...rest].join('\n')), mode);
71
+ if (runtime.length === 0 && own.length === 0 && styles.length === 0) {
72
+ return this.lines.length === 0 ? '' : `${this.lines.join('\n')}\n`;
110
73
  }
111
- return lines.length === 0 ? '' : `${lines.join('\n')}\n`;
74
+ while (rest[0] === '')
75
+ rest.shift();
76
+ // The import of the runtime goes with the other imports; the runtime itself, after them.
77
+ const head = this.lines.slice(0, imports);
78
+ const sections = [
79
+ mode === 'import' ? [...head, ...runtime] : head,
80
+ mode === 'inline' ? runtime : [],
81
+ own,
82
+ styles,
83
+ rest,
84
+ ].filter((section) => section.length > 0);
85
+ return `${sections.flatMap((section, i) => [...(i > 0 ? [''] : []), ...section]).join('\n')}\n`;
112
86
  }
113
87
  }
@@ -4,7 +4,7 @@ import { declaredIn } from "../names.js";
4
4
  import { ControlStatementEmitter } from "./control-statements.js";
5
5
  import { ARROW } from "./syntax.js";
6
6
  // `go for i, v in items { ... }`: `await $$goFor("$$goroutine0", items, ...)` splits the array into
7
- // parts and runs each as a goroutine (go-helpers.ts). In the worker module, the function of a part
7
+ // parts and runs each as a goroutine (src/runtime/go.js). In the worker module, the function of a part
8
8
  // loops over it: `i` is the index in the whole array, from `$$from`, the index of the part's first
9
9
  // element, and `items[i]` is `items[$$i]`, the index in the part. The part goes back if the body
10
10
  // writes into it; a typed array is moved back, not copied.
@@ -24,9 +24,7 @@ export class ParallelLoopEmitter extends ControlStatementEmitter {
24
24
  this.expression(node.iterable, ARROW),
25
25
  ...goCaptures(this.program, node).map((name) => this.name(name)),
26
26
  ];
27
- this.helpers.add('goFor');
28
- this.helpers.add(this.inWorker ? 'goLocal' : 'go');
29
- this.line(`await $$goFor(${args.join(', ')});`);
27
+ this.line(`await $$goFor($$go, ${args.join(', ')});`);
30
28
  }
31
29
  /** The loop over a part, in the function of the worker module that runs it. */
32
30
  partLoop(node, loop) {
@@ -65,7 +65,6 @@ export class RefEmitter extends EarlyReturnEmitter {
65
65
  const scopeClass = scope && !hasClass(element) ? scope : null;
66
66
  let creation = { covered: new Set(), scoped: false };
67
67
  if (preset) {
68
- this.createdElements++;
69
68
  this.line(`const ${name} = ${preset.name} ?? ${this.createElement(tag, svg)};`);
70
69
  }
71
70
  else {
@@ -0,0 +1,13 @@
1
+ /** How a module gets the runtime: by an import, or with the code of the runtime in it. */
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>;
5
+ /** The names of the runtime that code uses, in order. */
6
+ export declare function runtimeNames(code: string): string[];
7
+ /** What gives a module these names of the runtime: an import, or the files that have them. */
8
+ export declare function runtimeCode(names: readonly string[], mode: RuntimeMode): string[];
9
+ /**
10
+ * The `go` of a module: in a module, a call in a worker that starts with its worker module; in the
11
+ * worker module, a call in the same worker, of a function of `$$goroutines`.
12
+ */
13
+ export declare function moduleGo(workerUrl: string | null): string[];
@@ -0,0 +1,93 @@
1
+ import { RUNTIME_MODULE } from "../runtime-module.js";
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',
11
+ ]);
12
+ /** A name of generated code, but not a member: `$$chan(...)`, and not `channel.$$chan`. */
13
+ const NAME = /(?<![.\w$])\$\$[A-Za-z_][\w$]*/g;
14
+ const files = new Map();
15
+ const owners = new Map();
16
+ for (const [file, source] of Object.entries(RUNTIME_FILES)) {
17
+ const needs = [...source.matchAll(/^import [^;]* from '\.\/(\w+\.js)';$/gm)].map((match) => match[1]);
18
+ for (const match of source.matchAll(/^export (?:async )?(?:function\*?|class|const|let) (\$\$\w+)/gm)) {
19
+ owners.set(match[1], file);
20
+ }
21
+ const code = source
22
+ .replace(/^import [^;]*;\n+/gm, '')
23
+ .replace(/^export /gm, '')
24
+ .trimEnd();
25
+ files.set(file, { code, needs });
26
+ }
27
+ /** The names of the runtime that code uses, in order. */
28
+ export function runtimeNames(code) {
29
+ return [...new Set(code.match(NAME))].filter((name) => owners.has(name)).sort();
30
+ }
31
+ /** What gives a module these names of the runtime: an import, or the files that have them. */
32
+ export function runtimeCode(names, mode) {
33
+ if (names.length === 0)
34
+ return [];
35
+ if (mode === 'import') {
36
+ const line = `import { ${names.join(', ')} } from "${RUNTIME_MODULE}";`;
37
+ return line.length <= 100
38
+ ? [line]
39
+ : ['import {', ...names.map((name) => ` ${name},`), `} from "${RUNTIME_MODULE}";`];
40
+ }
41
+ const needed = new Set();
42
+ const add = (file) => {
43
+ if (needed.has(file))
44
+ return;
45
+ needed.add(file);
46
+ for (const other of files.get(file).needs)
47
+ add(other);
48
+ };
49
+ for (const name of names)
50
+ add(owners.get(name));
51
+ // The files only declare, but for a few that register what a thread shares, so their order does
52
+ // not matter: the one of RUNTIME_FILES.
53
+ return [...files]
54
+ .filter(([file]) => needed.has(file))
55
+ .flatMap(([, { code }], i) => [...(i > 0 ? [''] : []), ...code.split('\n')]);
56
+ }
57
+ /**
58
+ * The `go` of a module: in a module, a call in a worker that starts with its worker module; in the
59
+ * worker module, a call in the same worker, of a function of `$$goroutines`.
60
+ */
61
+ export function moduleGo(workerUrl) {
62
+ if (workerUrl === null) {
63
+ return ['const $$go = (fn, args) => $$goHere($$goroutines, fn, args);'];
64
+ }
65
+ return [
66
+ ...startWorker(workerUrl),
67
+ '',
68
+ 'const $$go = (name, args) => $$goWorker($$startWorker, name, args);',
69
+ ];
70
+ }
71
+ /**
72
+ * The start of a worker of a module: a Web Worker, or a worker thread of Node. Node takes only the
73
+ * path of a `file:` URL, without the query of `app.rk?goroutines`, so its worker starts with a
74
+ * module that imports the whole URL. An absolute URL, such as the Blob of the playground, is used
75
+ * as it is.
76
+ */
77
+ function startWorker(url) {
78
+ const base = /^[a-z][a-z\d+.-]*:/i.test(url) ? '' : ', import.meta.url';
79
+ const workerUrl = `new URL(${JSON.stringify(url)}${base})`;
80
+ return [
81
+ 'async function $$startWorker() {',
82
+ ' if (typeof Worker === "function") {',
83
+ ` return new Worker(${workerUrl}, { type: "module" });`,
84
+ ' }',
85
+ ' const threads = "node:worker_threads";',
86
+ ' const { Worker: NodeWorker } = await import(/* @vite-ignore */ threads);',
87
+ // Vite bundles the Web Worker; this URL is only for Node.
88
+ ` const url = new URL(/* @vite-ignore */ ${JSON.stringify(url)}${base});`,
89
+ ' const code = `import ${JSON.stringify(url.href)};`;',
90
+ ' return new NodeWorker(new URL(`data:text/javascript,${encodeURIComponent(code)}`));',
91
+ '}',
92
+ ];
93
+ }
@@ -140,7 +140,6 @@ export class SimpleStatementEmitter extends CallEmitter {
140
140
  // Waits until the channel takes the value.
141
141
  case 'SendStatement': {
142
142
  const channel = this.expression(node.channel, ARROW);
143
- this.helpers.add('chan');
144
143
  this.line(`await $$send(${channel}, ${this.expression(node.value, ARROW)});`);
145
144
  break;
146
145
  }
@@ -190,6 +189,9 @@ export class SimpleStatementEmitter extends CallEmitter {
190
189
  : undefined;
191
190
  return typed === undefined ? '[]' : `new ${typed}()`;
192
191
  }
192
+ // An empty queue, as `[]` is an empty array.
193
+ case 'QueueType':
194
+ return 'new $$Queue()';
193
195
  case 'FuncType':
194
196
  case 'ComponentType':
195
197
  case 'ChanType':
@@ -33,7 +33,7 @@ export declare abstract class StyleEmitter extends RefEmitter {
33
33
  */
34
34
  protected styleModuleImport(node: ast.ImportDeclaration): boolean;
35
35
  /**
36
- * The objects of the CSS modules, and `$$style(id, css)` for them and for every style of a
36
+ * The objects of the CSS modules, and `$$style($$styles, id, css)` for them and for every style of a
37
37
  * component the module uses: modules first, so that components can override them.
38
38
  */
39
39
  protected styleRegistrations(): string[];
@@ -38,13 +38,13 @@ export class StyleEmitter extends RefEmitter {
38
38
  return true;
39
39
  }
40
40
  /**
41
- * The objects of the CSS modules, and `$$style(id, css)` for them and for every style of a
41
+ * The objects of the CSS modules, and `$$style($$styles, id, css)` for them and for every style of a
42
42
  * component the module uses: modules first, so that components can override them.
43
43
  */
44
44
  styleRegistrations() {
45
45
  const register = (id, css) => {
46
46
  const text = css.replace(/[`\\]|\$\{/g, (match) => `\\${match}`);
47
- return `$$style(${JSON.stringify(id)}, \`${text}\`);`;
47
+ return `$$style($$styles, ${JSON.stringify(id)}, \`${text}\`);`;
48
48
  };
49
49
  return [
50
50
  ...this.moduleObjects,
@@ -22,7 +22,6 @@ export class WebComponentEmitter extends ComponentEmitter {
22
22
  }
23
23
  }
24
24
  webComponentClass(component) {
25
- this.helpers.add('owner');
26
25
  const name = component.name.name;
27
26
  const className = `$$${name}Element`;
28
27
  const props = component.params.filter((param) => param.name.name !== 'children');
package/dist/compile.d.ts CHANGED
@@ -1,5 +1,6 @@
1
1
  import type * as ast from './ast.ts';
2
2
  import { type CheckOptions, type ImportResult, type Library } from './checker/checker.ts';
3
+ import type { RuntimeMode } from './codegen/runtime.ts';
3
4
  import type { Diagnostic } from './diagnostics.ts';
4
5
  import { type StyleModule } from './style-modules.ts';
5
6
  export interface CoreCompileOptions {
@@ -21,6 +22,12 @@ export interface CoreCompileOptions {
21
22
  readStyleModule?: ((specifier: string) => string | null) | undefined;
22
23
  /** Where the worker module of `go` is, relative to the module; see workerUrl(). */
23
24
  workerUrl?: string | undefined;
25
+ /**
26
+ * How the code gets the runtime of Rook: `import`, the default, imports it from
27
+ * "rooklang/runtime"; `inline` puts the parts it uses into the module, for code that runs
28
+ * without imports (src/codegen/runtime.ts).
29
+ */
30
+ runtime?: RuntimeMode | undefined;
24
31
  }
25
32
  /**
26
33
  * Where the worker module of a module is: a `.worker.js` next to its `.js`, or, where `.rk`
package/dist/compile.js CHANGED
@@ -41,6 +41,7 @@ export function compileModule(source, options = {}) {
41
41
  rewriteImports,
42
42
  types,
43
43
  styleModules,
44
+ runtime: options.runtime,
44
45
  ...importedCode(program, options.importModule),
45
46
  };
46
47
  const code = generateJs(program, {
@@ -17,7 +17,8 @@ const STRINGS = new Set([
17
17
  'CssText',
18
18
  ]);
19
19
  const VALUE_KEYWORDS = KEYWORDS.filter((keyword) => !RESERVED.has(keyword));
20
- const TYPE_KEYWORDS = ['func', 'comp', 'chan'];
20
+ // `queue` is a type of its own only in a type: elsewhere it is a name.
21
+ const TYPE_KEYWORDS = ['func', 'comp', 'chan', 'queue'];
21
22
  /** Keywords only at the start of a statement at the top level of a component or a decorator. */
22
23
  const COMPONENT_KEYWORDS = ['state', 'derived', 'ref', 'style', 'effect', 'mount'];
23
24
  export function completeModule(source, offset, options = {}) {
package/dist/index.d.ts CHANGED
@@ -21,6 +21,8 @@ export interface CompileOptions {
21
21
  workerUrl?: string;
22
22
  /** Markup and components; by default as the package.json of the file says (src/config.ts). */
23
23
  markup?: boolean;
24
+ /** Import the runtime from "rooklang/runtime", the default, or put it into the module. */
25
+ runtime?: 'import' | 'inline';
24
26
  }
25
27
  export declare function compile(source: string, options?: CompileOptions): CompileResult;
26
28
  /**
package/dist/index.js CHANGED
@@ -18,6 +18,7 @@ export function compile(source, options = {}) {
18
18
  workerUrl: options.workerUrl ?? workerUrl(filename ?? 'main.rk', rewriteImports),
19
19
  typeCheck: options.typeCheck ?? true,
20
20
  declarations: options.declarations ?? false,
21
+ runtime: options.runtime,
21
22
  });
22
23
  }
23
24
  /**
package/dist/loader.js CHANGED
@@ -1,18 +1,19 @@
1
1
  import { createHash } from 'node:crypto';
2
2
  import { existsSync, readFileSync, writeFileSync } from 'node:fs';
3
- import { registerHooks } from 'node:module';
4
- import { join } from 'node:path';
3
+ import { createRequire, registerHooks } from 'node:module';
4
+ import { extname, join } from 'node:path';
5
5
  import { fileURLToPath } from 'node:url';
6
6
  import { isMainThread } from 'node:worker_threads';
7
- import { displayPath } from "./build.js";
8
- import { compileFile } from "./compile-file.js";
7
+ import { RUNTIME_MODULE } from "./runtime-module.js";
9
8
  // `rook run` imports this module first (`node --import`): Node then imports `.rk` files,
10
9
  // compiled when they load, and `app.rk?goroutines` is the worker module of `go` in app.rk.
11
10
  // The worker threads of `go` import it too, since they get the `--import` of the process. The main
12
11
  // thread compiles a module before any worker needs it, so it leaves the code of each module and
13
12
  // of its worker module in ROOK_RUN_CACHE, a directory of the run, and the workers take it from
14
- // there: compiling again, with the types, would take each of them as long. ROOK_NO_CHECK turns
15
- // type checking off, and ROOK_MARKUP, 1 or 0, markup and components on or off for every file.
13
+ // there: compiling again, with the types, would take each of them as long. So a worker does not
14
+ // even load the compiler, which takes ~80 ms, unless a module is missing from the cache; the hooks
15
+ // are synchronous, so it is loaded with require(). ROOK_NO_CHECK turns type checking off, and
16
+ // ROOK_MARKUP, 1 or 0, markup and components on or off for every file.
16
17
  const typeCheck = process.env['ROOK_NO_CHECK'] === undefined;
17
18
  const markupSetting = process.env['ROOK_MARKUP'];
18
19
  const markup = markupSetting === undefined ? undefined : markupSetting === '1';
@@ -21,7 +22,17 @@ const cache = process.env['ROOK_RUN_CACHE'];
21
22
  function cached(url) {
22
23
  return cache ? join(cache, `${createHash('sha1').update(url).digest('hex')}.js`) : null;
23
24
  }
25
+ // The compiled code imports the runtime of this compiler, wherever the program is.
26
+ const runtime = import.meta.resolve(RUNTIME_MODULE);
27
+ const require = createRequire(import.meta.url);
28
+ // The files next to this one: .ts in src, .js in the dist of the package.
29
+ const extension = extname(fileURLToPath(import.meta.url));
24
30
  registerHooks({
31
+ resolve(specifier, context, nextResolve) {
32
+ if (specifier === RUNTIME_MODULE)
33
+ return { url: runtime, shortCircuit: true };
34
+ return nextResolve(specifier, context);
35
+ },
25
36
  load(url, context, nextLoad) {
26
37
  const { protocol, pathname, search } = new URL(url);
27
38
  if (protocol !== 'file:' || !pathname.endsWith('.rk'))
@@ -31,6 +42,8 @@ registerHooks({
31
42
  return { format: 'module', source: readFileSync(file, 'utf8'), shortCircuit: true };
32
43
  }
33
44
  const moduleUrl = new URL(pathname, url).href;
45
+ const { displayPath } = require(`./build${extension}`);
46
+ const { compileFile } = require(`./compile-file${extension}`);
34
47
  const path = displayPath(fileURLToPath(moduleUrl));
35
48
  const result = compileFile(path, {
36
49
  rewriteImports: false,
@@ -23,7 +23,7 @@ export declare abstract class ParserBase {
23
23
  protected abstract parseClass(start: number, exported: boolean): ast.ClassDeclaration;
24
24
  protected abstract parseInterface(start: number, exported: boolean): ast.InterfaceDeclaration;
25
25
  protected abstract parseTypeAlias(start: number, exported: boolean): ast.TypeAliasDeclaration;
26
- protected abstract canStartType(): boolean;
26
+ protected abstract canStartType(offset?: number): boolean;
27
27
  protected abstract parseType(): ast.TypeNode;
28
28
  protected abstract parseTypeArgs(): ast.TypeNode[];
29
29
  protected abstract parseTypeMembers(): ast.TypeMember[];
@@ -35,6 +35,8 @@ export declare abstract class ParserBase {
35
35
  protected abstract parseArrayLiteral(): ast.ArrayLiteral;
36
36
  protected abstract parseConversion(): ast.ConversionExpression;
37
37
  protected abstract parseMake(): ast.MakeExpression;
38
+ protected abstract parseStreamChan(start: number): ast.StreamChanExpression;
39
+ protected abstract parseQueue(): ast.QueueExpression | null;
38
40
  protected abstract parseArrowFunction(async?: boolean): ast.ArrowFunction;
39
41
  protected abstract parseFuncExpression(async?: boolean): ast.FuncExpression;
40
42
  protected abstract parseIdentifier(what?: string): ast.Identifier;