rooklang 0.3.3 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (244) hide show
  1. package/README.md +34 -7
  2. package/README.ru.md +34 -7
  3. package/dist/ast/declarations.d.ts +39 -4
  4. package/dist/ast/expressions.d.ts +34 -8
  5. package/dist/ast/markup.d.ts +6 -1
  6. package/dist/ast/statements.d.ts +28 -2
  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 +4 -0
  16. package/dist/checker/assignments.js +23 -3
  17. package/dist/checker/base.d.ts +8 -2
  18. package/dist/checker/base.js +7 -7
  19. package/dist/checker/builtins.js +22 -8
  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 +17 -4
  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 +48 -13
  33. package/dist/checker/closing.d.ts +21 -0
  34. package/dist/checker/closing.js +164 -0
  35. package/dist/checker/completion.d.ts +3 -3
  36. package/dist/checker/completion.js +4 -3
  37. package/dist/checker/component-bindings.d.ts +7 -0
  38. package/dist/checker/component-bindings.js +45 -0
  39. package/dist/checker/component-values.js +10 -7
  40. package/dist/checker/components.d.ts +2 -2
  41. package/dist/checker/components.js +8 -3
  42. package/dist/checker/context.d.ts +41 -5
  43. package/dist/checker/context.js +23 -0
  44. package/dist/checker/control-flow.d.ts +11 -5
  45. package/dist/checker/control-flow.js +65 -40
  46. package/dist/checker/declarations.d.ts +2 -2
  47. package/dist/checker/declarations.js +4 -2
  48. package/dist/checker/decode.d.ts +8 -0
  49. package/dist/checker/decode.js +32 -0
  50. package/dist/checker/decorator-exports.js +4 -0
  51. package/dist/checker/decorator-includes.d.ts +9 -0
  52. package/dist/checker/decorator-includes.js +71 -0
  53. package/dist/checker/decorator-members.d.ts +8 -10
  54. package/dist/checker/decorator-members.js +28 -31
  55. package/dist/checker/decorator-wrappers.d.ts +1 -1
  56. package/dist/checker/decorator-wrappers.js +3 -2
  57. package/dist/checker/decorators.d.ts +14 -5
  58. package/dist/checker/decorators.js +52 -25
  59. package/dist/checker/expressions.d.ts +6 -2
  60. package/dist/checker/expressions.js +20 -40
  61. package/dist/checker/flow.d.ts +25 -3
  62. package/dist/checker/flow.js +56 -12
  63. package/dist/checker/functions.d.ts +2 -2
  64. package/dist/checker/functions.js +9 -5
  65. package/dist/checker/helpers.d.ts +5 -2
  66. package/dist/checker/helpers.js +16 -4
  67. package/dist/checker/imports.js +5 -2
  68. package/dist/checker/iterables.d.ts +3 -1
  69. package/dist/checker/iterables.js +13 -0
  70. package/dist/checker/iterators.d.ts +4 -1
  71. package/dist/checker/iterators.js +12 -4
  72. package/dist/checker/json.d.ts +5 -0
  73. package/dist/checker/json.js +92 -0
  74. package/dist/checker/loops.d.ts +2 -0
  75. package/dist/checker/loops.js +50 -5
  76. package/dist/checker/markup.js +14 -2
  77. package/dist/checker/members.js +10 -1
  78. package/dist/checker/operators.d.ts +2 -2
  79. package/dist/checker/operators.js +7 -5
  80. package/dist/checker/queues.d.ts +2 -2
  81. package/dist/checker/queues.js +2 -2
  82. package/dist/checker/resolution.js +5 -3
  83. package/dist/checker/shared-state.d.ts +11 -0
  84. package/dist/checker/shared-state.js +37 -0
  85. package/dist/checker/signals.d.ts +8 -0
  86. package/dist/checker/signals.js +48 -0
  87. package/dist/checker/statements.d.ts +5 -2
  88. package/dist/checker/statements.js +51 -22
  89. package/dist/checker/tagged-unions.d.ts +37 -0
  90. package/dist/checker/tagged-unions.js +132 -0
  91. package/dist/checker/topics.d.ts +6 -0
  92. package/dist/checker/topics.js +17 -0
  93. package/dist/checker/transfer.d.ts +3 -1
  94. package/dist/checker/transfer.js +25 -5
  95. package/dist/checker/try.d.ts +6 -0
  96. package/dist/checker/try.js +19 -0
  97. package/dist/checker/types/constructors.d.ts +1 -1
  98. package/dist/checker/types/constructors.js +2 -2
  99. package/dist/checker/types/generics.js +1 -1
  100. package/dist/checker/types/iterables.d.ts +13 -0
  101. package/dist/checker/types/iterables.js +52 -0
  102. package/dist/checker/types/model.d.ts +25 -0
  103. package/dist/checker/types/params.js +22 -3
  104. package/dist/checker/types/printing.js +4 -3
  105. package/dist/checker/types/relations.js +19 -25
  106. package/dist/checker/types/topic.d.ts +6 -0
  107. package/dist/checker/types/topic.js +23 -0
  108. package/dist/checker/types.d.ts +2 -0
  109. package/dist/checker/types.js +2 -0
  110. package/dist/checker/untyped-globals.d.ts +1 -0
  111. package/dist/checker/untyped-globals.js +3 -1
  112. package/dist/cli.js +18 -36
  113. package/dist/codegen/attributes.d.ts +4 -0
  114. package/dist/codegen/attributes.js +24 -2
  115. package/dist/codegen/calls.d.ts +23 -0
  116. package/dist/codegen/calls.js +88 -7
  117. package/dist/codegen/component-helpers.d.ts +6 -1
  118. package/dist/codegen/component-helpers.js +6 -1
  119. package/dist/codegen/component-props.d.ts +11 -2
  120. package/dist/codegen/component-props.js +50 -5
  121. package/dist/codegen/components.d.ts +6 -5
  122. package/dist/codegen/components.js +44 -21
  123. package/dist/codegen/control-flow.d.ts +2 -0
  124. package/dist/codegen/control-flow.js +23 -9
  125. package/dist/codegen/control-statements.d.ts +10 -2
  126. package/dist/codegen/control-statements.js +56 -19
  127. package/dist/codegen/creation.js +5 -2
  128. package/dist/codegen/decode.d.ts +14 -0
  129. package/dist/codegen/decode.js +93 -0
  130. package/dist/codegen/early-returns.d.ts +4 -3
  131. package/dist/codegen/early-returns.js +17 -5
  132. package/dist/codegen/emitter.d.ts +13 -41
  133. package/dist/codegen/emitter.js +27 -8
  134. package/dist/codegen/expressions.d.ts +9 -0
  135. package/dist/codegen/expressions.js +15 -26
  136. package/dist/codegen/functions.d.ts +11 -1
  137. package/dist/codegen/functions.js +31 -3
  138. package/dist/codegen/js.d.ts +2 -2
  139. package/dist/codegen/js.js +5 -2
  140. package/dist/codegen/live.d.ts +60 -0
  141. package/dist/codegen/live.js +29 -0
  142. package/dist/codegen/mutations.d.ts +7 -0
  143. package/dist/codegen/mutations.js +43 -2
  144. package/dist/codegen/options.d.ts +42 -0
  145. package/dist/codegen/options.js +1 -0
  146. package/dist/codegen/reactive.d.ts +4 -59
  147. package/dist/codegen/reactive.js +34 -72
  148. package/dist/codegen/runtime.d.ts +7 -3
  149. package/dist/codegen/runtime.js +33 -14
  150. package/dist/codegen/shared.d.ts +22 -0
  151. package/dist/codegen/shared.js +93 -0
  152. package/dist/codegen/simple-statements.d.ts +11 -5
  153. package/dist/codegen/simple-statements.js +40 -15
  154. package/dist/codegen/statements.d.ts +5 -0
  155. package/dist/codegen/statements.js +99 -4
  156. package/dist/codegen/try.d.ts +5 -0
  157. package/dist/codegen/try.js +40 -0
  158. package/dist/codegen/updates.d.ts +11 -3
  159. package/dist/codegen/updates.js +52 -4
  160. package/dist/codegen/web-components.d.ts +2 -2
  161. package/dist/codegen/web-components.js +2 -2
  162. package/dist/compile.d.ts +9 -0
  163. package/dist/compile.js +25 -8
  164. package/dist/completion.d.ts +4 -2
  165. package/dist/completion.js +10 -5
  166. package/dist/declarations.js +32 -6
  167. package/dist/decorator-copy.d.ts +1 -1
  168. package/dist/decorator-copy.js +6 -2
  169. package/dist/decorator-names.d.ts +14 -0
  170. package/dist/decorator-names.js +56 -0
  171. package/dist/decorators.d.ts +2 -3
  172. package/dist/decorators.js +36 -14
  173. package/dist/goroutines.d.ts +1 -1
  174. package/dist/goroutines.js +5 -4
  175. package/dist/index.d.ts +6 -2
  176. package/dist/index.js +14 -5
  177. package/dist/inline-packages.d.ts +2 -1
  178. package/dist/inline-packages.js +9 -4
  179. package/dist/lexer/base.d.ts +2 -0
  180. package/dist/lexer/base.js +6 -1
  181. package/dist/lexer/lexer.js +3 -0
  182. package/dist/lexer/literals.d.ts +5 -0
  183. package/dist/lexer/literals.js +51 -1
  184. package/dist/lexer/markup.js +3 -4
  185. package/dist/lexer/tables.js +2 -0
  186. package/dist/lexer/token.d.ts +4 -3
  187. package/dist/lexer/token.js +0 -2
  188. package/dist/loader.js +30 -8
  189. package/dist/names.d.ts +4 -0
  190. package/dist/names.js +29 -0
  191. package/dist/parser/base.d.ts +5 -0
  192. package/dist/parser/base.js +2 -1
  193. package/dist/parser/classes.d.ts +2 -0
  194. package/dist/parser/classes.js +88 -23
  195. package/dist/parser/control-flow.d.ts +8 -1
  196. package/dist/parser/control-flow.js +49 -26
  197. package/dist/parser/declarations.d.ts +5 -12
  198. package/dist/parser/declarations.js +14 -79
  199. package/dist/parser/decorators.d.ts +6 -2
  200. package/dist/parser/decorators.js +43 -19
  201. package/dist/parser/expressions.js +27 -3
  202. package/dist/parser/jumps.d.ts +11 -0
  203. package/dist/parser/jumps.js +77 -0
  204. package/dist/parser/literals.d.ts +9 -0
  205. package/dist/parser/literals.js +45 -6
  206. package/dist/parser/markup.js +1 -1
  207. package/dist/parser/params.d.ts +21 -0
  208. package/dist/parser/params.js +143 -0
  209. package/dist/parser/simple-statements.d.ts +2 -1
  210. package/dist/parser/simple-statements.js +38 -15
  211. package/dist/parser/statements.d.ts +5 -5
  212. package/dist/parser/statements.js +39 -35
  213. package/dist/parser/syntax.d.ts +11 -1
  214. package/dist/parser/syntax.js +16 -2
  215. package/dist/parser/types.js +8 -3
  216. package/dist/project-modules.d.ts +12 -0
  217. package/dist/project-modules.js +106 -0
  218. package/dist/run-file.d.ts +6 -0
  219. package/dist/run-file.js +111 -0
  220. package/dist/runtime/sources.js +13 -6
  221. package/dist/source-map.d.ts +17 -0
  222. package/dist/source-map.js +75 -0
  223. package/dist/ts-types.js +4 -1
  224. package/dist/typescript/convert-objects.d.ts +2 -1
  225. package/dist/typescript/convert-objects.js +18 -9
  226. package/dist/vite.js +12 -4
  227. package/dist/walk-types.js +1 -0
  228. package/dist/walk.d.ts +0 -2
  229. package/dist/walk.js +14 -31
  230. package/package.json +2 -2
  231. package/src/runtime/chan.js +15 -5
  232. package/src/runtime/channel.js +28 -0
  233. package/src/runtime/classes.js +32 -0
  234. package/src/runtime/core.js +10 -1
  235. package/src/runtime/decode.js +221 -0
  236. package/src/runtime/go.js +3 -0
  237. package/src/runtime/index.js +7 -0
  238. package/src/runtime/markup.js +27 -17
  239. package/src/runtime/select.js +14 -9
  240. package/src/runtime/shared.js +17 -0
  241. package/src/runtime/signals.js +59 -0
  242. package/src/runtime/threads.js +32 -5
  243. package/src/runtime/topic.js +118 -0
  244. package/src/runtime/try.js +45 -0
@@ -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':
@@ -9,6 +9,11 @@ import { ParallelLoopEmitter } from './parallel.ts';
9
9
  export declare abstract class StatementEmitter extends ParallelLoopEmitter {
10
10
  /** A CSS module: its object and CSS go with the styles (StyleEmitter), not to an import. */
11
11
  protected abstract styleModuleImport(node: ast.ImportDeclaration): boolean;
12
+ private sharedReads;
13
+ /** State of the module: the updates of the components that read it are kept beside it. */
14
+ protected emitStatement(node: ast.Statement): void;
15
+ /** Whether the components or decorators of the module read `name`, and so listen to it. */
16
+ protected readsShared(name: string): boolean;
12
17
  protected importDeclaration(node: ast.ImportDeclaration): void;
13
18
  /**
14
19
  * `export { log as $$visible$log }`: the names of the module that the code of exported
@@ -1,8 +1,11 @@
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";
4
5
  import { packageExports } from "./imported.js";
6
+ import { readNames } from "./mutations.js";
5
7
  import { ParallelLoopEmitter } from "./parallel.js";
8
+ import { listenersName } from "./shared.js";
6
9
  import { ARROW, POSTFIX } from "./syntax.js";
7
10
  /**
8
11
  * Imports and classes; with the layers below, all statements and declarations. A decorator of
@@ -11,12 +14,42 @@ import { ARROW, POSTFIX } from "./syntax.js";
11
14
  * So does an inlined component of another module; a called one brings its function, `$$Card`.
12
15
  */
13
16
  export class StatementEmitter extends ParallelLoopEmitter {
17
+ sharedReads = null;
18
+ /** State of the module: the updates of the components that read it are kept beside it. */
19
+ emitStatement(node) {
20
+ super.emitStatement(node);
21
+ if (node.kind !== 'VariableDeclaration' || node.keyword !== 'state')
22
+ return;
23
+ if (!this.program.body.includes(node))
24
+ return;
25
+ const exported = node.exported ? 'export ' : '';
26
+ for (const name of node.names) {
27
+ const listeners = this.shared.listeners.get(name.name);
28
+ if (listeners)
29
+ this.line(`${exported}const ${listeners} = new Set();`);
30
+ }
31
+ }
32
+ /** Whether the components or decorators of the module read `name`, and so listen to it. */
33
+ readsShared(name) {
34
+ if (!this.sharedReads) {
35
+ this.sharedReads = new Set();
36
+ for (const statement of this.program.body) {
37
+ if (statement.kind === 'ComponentDeclaration' ||
38
+ statement.kind === 'DecoratorDeclaration') {
39
+ readNames(statement, this.sharedReads);
40
+ }
41
+ }
42
+ }
43
+ return this.sharedReads.has(name);
44
+ }
14
45
  importDeclaration(node) {
15
46
  if (this.styleModuleImport(node))
16
47
  return;
17
48
  // Imports used only as types (e.g. interfaces) do not exist at runtime, and neither do
18
49
  // 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);
50
+ // A data class, even used only as a type, registers itself where it is imported.
51
+ const used = (name) => (this.valueNames.has(name.name) || this.options.dataClasses?.has(name.name) === true) &&
52
+ !this.options.components?.has(name.name);
20
53
  const clauses = [];
21
54
  if (node.defaultImport && used(node.defaultImport)) {
22
55
  clauses.push(this.name(node.defaultImport.name));
@@ -33,6 +66,11 @@ export class StatementEmitter extends ParallelLoopEmitter {
33
66
  const hidden = (exported, local) => named.push(exported === local ? exported : `${exported} as ${local}`);
34
67
  for (const specifier of node.namedImports) {
35
68
  const component = this.options.components?.get(specifier.local.name);
69
+ const state = this.options.shared?.get(specifier.local.name);
70
+ // State of that module: the components here that read it listen to its changes.
71
+ if (state !== undefined && this.readsShared(specifier.local.name)) {
72
+ hidden(listenersName(state), this.shared.listeners.get(specifier.local.name));
73
+ }
36
74
  const inlined = this.inlinedImports.get(specifier.local.name);
37
75
  if (inlined) {
38
76
  for (const [exported, local] of inlined.imports)
@@ -100,7 +138,10 @@ export class StatementEmitter extends ParallelLoopEmitter {
100
138
  : '';
101
139
  const body = this.block(() => {
102
140
  let previous;
141
+ // An abstract method has no code: only the subclasses have it.
103
142
  for (const member of node.members) {
143
+ if (member.kind === 'MethodDeclaration' && member.abstract)
144
+ continue;
104
145
  const bothFields = previous?.kind === 'FieldDeclaration' && member.kind === 'FieldDeclaration';
105
146
  if (previous && (!bothFields || this.blankLineBetween(previous, member)))
106
147
  this.blankLine();
@@ -109,6 +150,12 @@ export class StatementEmitter extends ParallelLoopEmitter {
109
150
  }
110
151
  });
111
152
  this.line(`${exported}class ${this.name(node.name.name)}${superClass} ${body}`);
153
+ // A data class is registered, so that its instances get it back in another thread
154
+ // (runtime/classes.js); the id is the same in the worker module, which has a copy of it.
155
+ if (node.data) {
156
+ const id = `${node.name.name}:${hash(this.options.source.slice(node.start, node.end))}`;
157
+ this.line(`$$dataClass(${this.name(node.name.name)}, ${JSON.stringify(id)});`);
158
+ }
112
159
  }
113
160
  /** Visibility modifiers are checked by the compiler and do not exist in the output. */
114
161
  classMember(member) {
@@ -126,17 +173,65 @@ export class StatementEmitter extends ParallelLoopEmitter {
126
173
  break;
127
174
  }
128
175
  case 'ConstructorDeclaration': {
129
- const [params, body] = this.func(member.params, member.body);
176
+ const [params, body] = this.func(member.params, assignProperties(member));
130
177
  this.line(`constructor(${params}) ${body}`);
131
178
  break;
132
179
  }
133
180
  case 'MethodDeclaration': {
181
+ if (!member.body)
182
+ break;
134
183
  const [params, body] = this.func(member.params, member.body);
135
- const modifiers = `${member.isStatic ? 'static ' : ''}${member.async ? 'async ' : ''}`;
136
- const star = isIterator(member.params) ? '*' : '';
184
+ const modifiers = `${member.isStatic ? 'static ' : ''}${member.async ? 'async ' : ''}` +
185
+ (member.accessor ? `${member.accessor} ` : '');
186
+ const star = isIterator(member.params) && !member.accessor ? '*' : '';
137
187
  this.line(`${modifiers}${star}${member.name.name}(${params}) ${body}`);
138
188
  break;
139
189
  }
140
190
  }
141
191
  }
142
192
  }
193
+ /**
194
+ * The body of a constructor whose parameters are properties, `constructor(private name string)`:
195
+ * it assigns them first, `this.name = name`, or right after `super(...)` in a derived class.
196
+ */
197
+ function assignProperties(node) {
198
+ const assignments = node.params.flatMap((param) => {
199
+ if (!param.property)
200
+ return [];
201
+ const at = { start: param.start, end: param.end };
202
+ const self = { kind: 'ThisExpression', ...at };
203
+ const target = {
204
+ kind: 'MemberExpression',
205
+ object: self,
206
+ property: param.name,
207
+ optional: false,
208
+ ...at,
209
+ };
210
+ return [
211
+ {
212
+ kind: 'AssignmentStatement',
213
+ operator: '=',
214
+ targets: [target],
215
+ values: [param.name],
216
+ ...at,
217
+ },
218
+ ];
219
+ });
220
+ if (assignments.length === 0)
221
+ return node.body;
222
+ const statements = node.body.body;
223
+ const after = statements.findIndex(isSuperCall) + 1;
224
+ return {
225
+ ...node.body,
226
+ body: [...statements.slice(0, after), ...assignments, ...statements.slice(after)],
227
+ };
228
+ }
229
+ /** FNV-1a of a text, as 8 hex digits: an id that changes only with the text. */
230
+ function hash(text) {
231
+ let value = 0x811c9dc5;
232
+ for (let i = 0; i < text.length; i++) {
233
+ value ^= text.charCodeAt(i);
234
+ value = Math.imul(value, 0x01000193) >>> 0;
235
+ }
236
+ return value.toString(16).padStart(8, '0');
237
+ }
@@ -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
+ }
@@ -1,6 +1,6 @@
1
1
  import type * as ast from '../ast.ts';
2
2
  import { type Type } from '../checker/types.ts';
3
- import type { Reactive, Source, Write } from './reactive.ts';
3
+ import { type Reactive, type Source, type Write } from './reactive.ts';
4
4
  import { StatementEmitter } from './statements.ts';
5
5
  /**
6
6
  * Updates after changes of component state. Each state variable gets an update function; every
@@ -19,8 +19,13 @@ export declare abstract class UpdateEmitter extends StatementEmitter {
19
19
  * the markup, which would update it again; code that runs later (handlers) is not included.
20
20
  */
21
21
  protected rendering: number;
22
- /** After a statement that changes state, the update of what depends on it. */
22
+ /**
23
+ * After a statement that changes state of the module, the updates of the components that read
24
+ * it; code that renders markup changes none, as with the state of a component.
25
+ */
23
26
  protected statement(node: ast.Statement): void;
27
+ /** After a statement that changes state, the update of what depends on it. */
28
+ private statementWithUpdates;
24
29
  protected changesState(body: ast.Expression): boolean;
25
30
  protected withRendering<T>(rendering: number, emit: () => T): T;
26
31
  /** Records a place that changes a source and returns its marker; see resolveMarkers. */
@@ -45,7 +50,10 @@ export declare abstract class UpdateEmitter extends StatementEmitter {
45
50
  * update statement, or by nothing when no markup depends on the source.
46
51
  */
47
52
  export declare function resolveMarkers(block: string, reactive: Reactive): string;
48
- /** The update statements that a place changing the source needs, the computations first. */
53
+ /**
54
+ * The update statements that a place changing the source needs: the computations, the call that
55
+ * gives a bound property to the parent, then the updates of the nodes.
56
+ */
49
57
  export declare function needed(source: Source, write: Write): string[];
50
58
  /** One place changes the source, after the markup exists, and needs one update statement. */
51
59
  export declare function inlinesUpdate(source: Source): boolean;
@@ -1,6 +1,8 @@
1
1
  import { widenLiterals } from "../checker/types.js";
2
+ import { updatesOf } from "./reactive.js";
2
3
  import { StatementEmitter } from "./statements.js";
3
4
  import { startsWithObjectLiteral } from "./analysis.js";
5
+ import { sharedWrites } from "./shared.js";
4
6
  import { ARROW } from "./syntax.js";
5
7
  /**
6
8
  * Updates after changes of component state. Each state variable gets an update function; every
@@ -19,8 +21,37 @@ export class UpdateEmitter extends StatementEmitter {
19
21
  * the markup, which would update it again; code that runs later (handlers) is not included.
20
22
  */
21
23
  rendering = 0;
22
- /** After a statement that changes state, the update of what depends on it. */
24
+ /**
25
+ * After a statement that changes state of the module, the updates of the components that read
26
+ * it; code that renders markup changes none, as with the state of a component.
27
+ */
23
28
  statement(node) {
29
+ const shared = this.rendering > 0 ? [] : sharedWrites(node, this.shared, (each) => this.typeOf(each));
30
+ if (shared.length === 0) {
31
+ this.statementWithUpdates(node);
32
+ return;
33
+ }
34
+ const notify = () => {
35
+ for (const name of shared)
36
+ this.line(`$$changed(${this.shared.listeners.get(name)});`);
37
+ };
38
+ if (node.kind !== 'ReturnStatement' && node.kind !== 'ThrowStatement') {
39
+ this.statementWithUpdates(node);
40
+ notify();
41
+ return;
42
+ }
43
+ // The value is computed first, then the updates run, then the function leaves.
44
+ const values = node.kind === 'ThrowStatement' ? [node.argument] : node.values;
45
+ const temp = this.temp();
46
+ this.withHoisting(true, () => {
47
+ const rendered = values.map((value) => this.expression(value, ARROW));
48
+ this.line(`const ${temp} = ${rendered.length === 1 ? rendered[0] : `[${rendered.join(', ')}]`};`);
49
+ });
50
+ notify();
51
+ this.line(`${node.kind === 'ThrowStatement' ? 'throw' : 'return'} ${temp};`);
52
+ }
53
+ /** After a statement that changes state, the update of what depends on it. */
54
+ statementWithUpdates(node) {
24
55
  const { reactive } = this;
25
56
  if (reactive && this.fn === reactive.setup && reactive.declaresRenderFunction(node)) {
26
57
  this.withRendering(this.rendering + 1, () => super.statement(node));
@@ -33,6 +64,9 @@ export class UpdateEmitter extends StatementEmitter {
33
64
  for (const source of changed) {
34
65
  for (const computation of source.computations)
35
66
  this.line(computation);
67
+ // The parent of a bound property gets the value the body gave it.
68
+ if (source.bound)
69
+ this.line(source.bound);
36
70
  }
37
71
  return;
38
72
  }
@@ -137,7 +171,14 @@ export function resolveMarkers(block, reactive) {
137
171
  const sources = new Map();
138
172
  for (const source of reactive.sources.values())
139
173
  sources.set(source.id, source);
140
- return block.replace(/^([ \t]*)\uE000(\d+)(?::(\d+))?\uE001;\n/gm, (line, indent, id, skip) => {
174
+ // `$$watch(...)` of state of the module stays if something depends on it.
175
+ const listened = block.replace(/^([ \t]*)\uE004(\d+)\uE004(.*)\n/gm, (line, indent, id, text) => {
176
+ const source = sources.get(Number(id));
177
+ if (!source)
178
+ return line;
179
+ return updatesOf(source).length > 0 ? `${indent}${text}\n` : '';
180
+ });
181
+ return listened.replace(/^([ \t]*)\uE000(\d+)(?::(\d+))?\uE001;\n/gm, (line, indent, id, skip) => {
141
182
  const source = sources.get(Number(id));
142
183
  if (!source)
143
184
  return line;
@@ -148,9 +189,16 @@ export function resolveMarkers(block, reactive) {
148
189
  return `${indent}${text}\n`;
149
190
  });
150
191
  }
151
- /** The update statements that a place changing the source needs, the computations first. */
192
+ /**
193
+ * The update statements that a place changing the source needs: the computations, the call that
194
+ * gives a bound property to the parent, then the updates of the nodes.
195
+ */
152
196
  export function needed(source, write) {
153
- return [...source.computations, ...source.dependents.filter((_, i) => i !== write.skip)];
197
+ return [
198
+ ...source.computations,
199
+ ...(source.bound ? [source.bound] : []),
200
+ ...source.dependents.filter((_, i) => i !== write.skip),
201
+ ];
154
202
  }
155
203
  /** One place changes the source, after the markup exists, and needs one update statement. */
156
204
  export function inlinesUpdate(source) {
@@ -6,8 +6,8 @@ import { ComponentEmitter } from './components.ts';
6
6
  * have their values.
7
7
  *
8
8
  * The element renders the component into its shadow root when it is connected. When it is
9
- * disconnected and not put back in the same task, the component is removed: the functions
10
- * returned by `mount()` run. Moving the element keeps its markup.
9
+ * disconnected and not put back in the same task, the component is removed: what its
10
+ * `mount` deferred runs. Moving the element keeps its markup.
11
11
  *
12
12
  * Every property except `children` is a JS property of the element; its setter updates the markup.
13
13
  * Strings, numbers and bools also have attributes: `item-count="3"` sets `itemCount` to 3, and a
@@ -6,8 +6,8 @@ import { ComponentEmitter } from "./components.js";
6
6
  * have their values.
7
7
  *
8
8
  * The element renders the component into its shadow root when it is connected. When it is
9
- * disconnected and not put back in the same task, the component is removed: the functions
10
- * returned by `mount()` run. Moving the element keeps its markup.
9
+ * disconnected and not put back in the same task, the component is removed: what its
10
+ * `mount` deferred runs. Moving the element keeps its markup.
11
11
  *
12
12
  * Every property except `children` is a JS property of the element; its setter updates the markup.
13
13
  * Strings, numbers and bools also have attributes: `item-count="3"` sets `itemCount` to 3, and a
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
  /**
@@ -89,11 +102,12 @@ export function prepareCheck(program, options) {
89
102
  }
90
103
  /**
91
104
  * What the module imports from other `.rk` modules for the generator: decorators, whose code is
92
- * inlined, and components, which are called or inlined.
105
+ * inlined, components, which are called or inlined, and state, whose changes it listens to.
93
106
  */
94
107
  function importedCode(program, importModule) {
95
108
  const found = new Map();
96
109
  const components = new Map();
110
+ const shared = new Map();
97
111
  for (const statement of program.body) {
98
112
  if (statement.kind !== 'ImportDeclaration' || !statement.source.value.endsWith('.rk')) {
99
113
  continue;
@@ -103,6 +117,9 @@ function importedCode(program, importModule) {
103
117
  const decorators = exports?.decorators;
104
118
  for (const specifier of statement.namedImports) {
105
119
  const component = exports?.components?.get(specifier.imported.name);
120
+ if (exports?.shared?.has(specifier.imported.name)) {
121
+ shared.set(specifier.local.name, specifier.imported.name);
122
+ }
106
123
  if (component) {
107
124
  components.set(specifier.local.name, {
108
125
  node: component.node,
@@ -122,7 +139,7 @@ function importedCode(program, importModule) {
122
139
  });
123
140
  }
124
141
  }
125
- return { decorators: found, components };
142
+ return { decorators: found, components, shared };
126
143
  }
127
144
  /** The CSS modules that the program imports, read and parsed, or what is wrong with them. */
128
145
  function readStyleModules(program, read) {
@@ -1,10 +1,12 @@
1
- import { type Completion } from './checker/checker.ts';
1
+ import { type Completion, type CompletionContext } from './checker/checker.ts';
2
2
  import { type CoreCompileOptions } from './compile.ts';
3
- export type { Completion, CompletionContext } from './checker/checker.ts';
3
+ export type { Completion, CompletionContext, TextEdit } from './checker/checker.ts';
4
4
  export interface CompletionList {
5
5
  /** Offsets of what the chosen item replaces: the part of the name before the cursor. */
6
6
  start: number;
7
7
  end: number;
8
+ /** What the cursor is in; none where nothing is suggested or the check did not reach it. */
9
+ context?: CompletionContext;
8
10
  items: Completion[];
9
11
  }
10
12
  export declare function completeModule(source: string, offset: number, options?: CoreCompileOptions): CompletionList;
@@ -1,9 +1,8 @@
1
- import { check } from "./checker/checker.js";
1
+ import { check, } from "./checker/checker.js";
2
2
  import { prepareCheck } from "./compile.js";
3
3
  import { tokenize } from "./lexer/lexer.js";
4
4
  import { KEYWORDS } from "./lexer/token.js";
5
5
  import { parse } from "./parser/parser.js";
6
- import { RESERVED } from "./parser/syntax.js";
7
6
  import { forEachChild } from "./walk.js";
8
7
  const CURSOR = '$$cursor';
9
8
  const STRINGS = new Set([
@@ -15,10 +14,11 @@ const STRINGS = new Set([
15
14
  'JsxText',
16
15
  'JsxString',
17
16
  'CssText',
17
+ 'RegExp',
18
18
  ]);
19
- const VALUE_KEYWORDS = KEYWORDS.filter((keyword) => !RESERVED.has(keyword));
19
+ const VALUE_KEYWORDS = KEYWORDS;
20
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
+ const TYPE_KEYWORDS = ['func', 'comp', 'chan', 'queue', 'topic'];
22
22
  /** Keywords only at the start of a statement at the top level of a component or a decorator. */
23
23
  const COMPONENT_KEYWORDS = ['state', 'derived', 'ref', 'style', 'effect', 'mount'];
24
24
  export function completeModule(source, offset, options = {}) {
@@ -55,7 +55,12 @@ export function completeModule(source, offset, options = {}) {
55
55
  const { program } = parse(text, { markup: options.markup ?? true });
56
56
  const result = completeIn(program, options);
57
57
  if (result) {
58
- return { start, end: offset, items: withKeywords(result, statementKeywords(program)) };
58
+ return {
59
+ start,
60
+ end: offset,
61
+ context: result.context,
62
+ items: withKeywords(result, statementKeywords(program)),
63
+ };
59
64
  }
60
65
  // The cursor is where a name is declared, `let ti|`: nothing to suggest.
61
66
  if (pathTo(program, CURSOR).length > 0)