rooklang 0.3.3 → 0.3.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (170) hide show
  1. package/README.md +32 -7
  2. package/README.ru.md +32 -7
  3. package/dist/ast/declarations.d.ts +20 -1
  4. package/dist/ast/expressions.d.ts +31 -1
  5. package/dist/ast/markup.d.ts +6 -1
  6. package/dist/ast/statements.d.ts +25 -1
  7. package/dist/auto-import.d.ts +11 -0
  8. package/dist/auto-import.js +81 -0
  9. package/dist/breaks.d.ts +6 -0
  10. package/dist/breaks.js +36 -0
  11. package/dist/build.d.ts +7 -0
  12. package/dist/build.js +23 -2
  13. package/dist/checker/array-literals.d.ts +6 -0
  14. package/dist/checker/array-literals.js +59 -0
  15. package/dist/checker/assignments.d.ts +2 -0
  16. package/dist/checker/assignments.js +14 -0
  17. package/dist/checker/base.js +6 -6
  18. package/dist/checker/builtins.js +15 -5
  19. package/dist/checker/bytes.d.ts +2 -2
  20. package/dist/checker/bytes.js +2 -2
  21. package/dist/checker/calls.js +3 -0
  22. package/dist/checker/channel-objects.d.ts +3 -0
  23. package/dist/checker/channel-objects.js +40 -0
  24. package/dist/checker/channels.d.ts +1 -0
  25. package/dist/checker/channels.js +16 -2
  26. package/dist/checker/checker.d.ts +1 -1
  27. package/dist/checker/checker.js +19 -0
  28. package/dist/checker/class-members.d.ts +22 -0
  29. package/dist/checker/class-members.js +105 -0
  30. package/dist/checker/classes.d.ts +5 -4
  31. package/dist/checker/classes.js +41 -12
  32. package/dist/checker/closing.d.ts +21 -0
  33. package/dist/checker/closing.js +164 -0
  34. package/dist/checker/completion.d.ts +2 -2
  35. package/dist/checker/completion.js +2 -2
  36. package/dist/checker/context.d.ts +13 -0
  37. package/dist/checker/control-flow.d.ts +5 -0
  38. package/dist/checker/control-flow.js +17 -0
  39. package/dist/checker/declarations.js +1 -0
  40. package/dist/checker/decode.d.ts +8 -0
  41. package/dist/checker/decode.js +32 -0
  42. package/dist/checker/expressions.d.ts +5 -1
  43. package/dist/checker/expressions.js +13 -34
  44. package/dist/checker/flow.js +8 -4
  45. package/dist/checker/helpers.d.ts +1 -1
  46. package/dist/checker/helpers.js +9 -2
  47. package/dist/checker/iterables.d.ts +3 -1
  48. package/dist/checker/iterables.js +13 -0
  49. package/dist/checker/iterators.d.ts +4 -1
  50. package/dist/checker/iterators.js +12 -4
  51. package/dist/checker/json.d.ts +5 -0
  52. package/dist/checker/json.js +92 -0
  53. package/dist/checker/loops.js +23 -3
  54. package/dist/checker/members.js +7 -0
  55. package/dist/checker/operators.d.ts +2 -2
  56. package/dist/checker/operators.js +7 -5
  57. package/dist/checker/queues.d.ts +2 -2
  58. package/dist/checker/queues.js +2 -2
  59. package/dist/checker/resolution.js +5 -3
  60. package/dist/checker/signals.d.ts +8 -0
  61. package/dist/checker/signals.js +48 -0
  62. package/dist/checker/topics.d.ts +6 -0
  63. package/dist/checker/topics.js +17 -0
  64. package/dist/checker/transfer.d.ts +3 -1
  65. package/dist/checker/transfer.js +25 -5
  66. package/dist/checker/try.d.ts +6 -0
  67. package/dist/checker/try.js +19 -0
  68. package/dist/checker/types/constructors.d.ts +1 -1
  69. package/dist/checker/types/constructors.js +2 -2
  70. package/dist/checker/types/generics.js +1 -1
  71. package/dist/checker/types/iterables.d.ts +13 -0
  72. package/dist/checker/types/iterables.js +52 -0
  73. package/dist/checker/types/model.d.ts +25 -0
  74. package/dist/checker/types/params.js +22 -3
  75. package/dist/checker/types/printing.js +4 -3
  76. package/dist/checker/types/relations.js +19 -25
  77. package/dist/checker/types/topic.d.ts +6 -0
  78. package/dist/checker/types/topic.js +23 -0
  79. package/dist/checker/types.d.ts +2 -0
  80. package/dist/checker/types.js +2 -0
  81. package/dist/cli.js +18 -36
  82. package/dist/codegen/calls.d.ts +23 -0
  83. package/dist/codegen/calls.js +88 -7
  84. package/dist/codegen/control-statements.d.ts +10 -2
  85. package/dist/codegen/control-statements.js +56 -19
  86. package/dist/codegen/decode.d.ts +14 -0
  87. package/dist/codegen/decode.js +93 -0
  88. package/dist/codegen/emitter.d.ts +10 -41
  89. package/dist/codegen/emitter.js +9 -4
  90. package/dist/codegen/expressions.d.ts +9 -0
  91. package/dist/codegen/expressions.js +15 -24
  92. package/dist/codegen/js.d.ts +2 -2
  93. package/dist/codegen/js.js +5 -2
  94. package/dist/codegen/options.d.ts +40 -0
  95. package/dist/codegen/options.js +1 -0
  96. package/dist/codegen/runtime.d.ts +7 -3
  97. package/dist/codegen/runtime.js +27 -13
  98. package/dist/codegen/simple-statements.d.ts +10 -4
  99. package/dist/codegen/simple-statements.js +40 -15
  100. package/dist/codegen/statements.js +64 -4
  101. package/dist/codegen/try.d.ts +5 -0
  102. package/dist/codegen/try.js +40 -0
  103. package/dist/compile.d.ts +9 -0
  104. package/dist/compile.js +19 -6
  105. package/dist/completion.d.ts +4 -2
  106. package/dist/completion.js +10 -5
  107. package/dist/declarations.js +32 -6
  108. package/dist/decorator-copy.js +5 -2
  109. package/dist/goroutines.d.ts +1 -1
  110. package/dist/goroutines.js +5 -4
  111. package/dist/index.d.ts +6 -2
  112. package/dist/index.js +14 -5
  113. package/dist/lexer/base.d.ts +2 -0
  114. package/dist/lexer/base.js +6 -1
  115. package/dist/lexer/lexer.js +3 -0
  116. package/dist/lexer/literals.d.ts +5 -0
  117. package/dist/lexer/literals.js +51 -1
  118. package/dist/lexer/markup.js +3 -4
  119. package/dist/lexer/tables.js +2 -0
  120. package/dist/lexer/token.d.ts +4 -3
  121. package/dist/lexer/token.js +0 -2
  122. package/dist/loader.js +30 -8
  123. package/dist/parser/base.d.ts +4 -0
  124. package/dist/parser/base.js +2 -1
  125. package/dist/parser/classes.d.ts +2 -0
  126. package/dist/parser/classes.js +88 -23
  127. package/dist/parser/control-flow.d.ts +3 -1
  128. package/dist/parser/control-flow.js +21 -24
  129. package/dist/parser/declarations.d.ts +7 -4
  130. package/dist/parser/declarations.js +45 -6
  131. package/dist/parser/expressions.js +27 -3
  132. package/dist/parser/jumps.d.ts +11 -0
  133. package/dist/parser/jumps.js +77 -0
  134. package/dist/parser/literals.d.ts +9 -0
  135. package/dist/parser/literals.js +45 -6
  136. package/dist/parser/markup.js +1 -1
  137. package/dist/parser/simple-statements.d.ts +1 -1
  138. package/dist/parser/simple-statements.js +16 -9
  139. package/dist/parser/statements.d.ts +0 -3
  140. package/dist/parser/statements.js +22 -25
  141. package/dist/parser/syntax.d.ts +11 -1
  142. package/dist/parser/syntax.js +16 -2
  143. package/dist/parser/types.js +8 -3
  144. package/dist/project-modules.d.ts +12 -0
  145. package/dist/project-modules.js +106 -0
  146. package/dist/run-file.d.ts +6 -0
  147. package/dist/run-file.js +111 -0
  148. package/dist/runtime/sources.js +11 -5
  149. package/dist/source-map.d.ts +17 -0
  150. package/dist/source-map.js +75 -0
  151. package/dist/ts-types.js +4 -1
  152. package/dist/typescript/convert-objects.d.ts +2 -1
  153. package/dist/typescript/convert-objects.js +18 -9
  154. package/dist/vite.js +12 -4
  155. package/dist/walk-types.js +1 -0
  156. package/dist/walk.d.ts +0 -2
  157. package/dist/walk.js +11 -28
  158. package/package.json +1 -1
  159. package/src/runtime/chan.js +15 -5
  160. package/src/runtime/channel.js +28 -0
  161. package/src/runtime/classes.js +32 -0
  162. package/src/runtime/core.js +10 -1
  163. package/src/runtime/decode.js +190 -0
  164. package/src/runtime/go.js +3 -0
  165. package/src/runtime/index.js +6 -0
  166. package/src/runtime/select.js +14 -9
  167. package/src/runtime/signals.js +59 -0
  168. package/src/runtime/threads.js +32 -5
  169. package/src/runtime/topic.js +118 -0
  170. package/src/runtime/try.js +45 -0
@@ -1,6 +1,7 @@
1
1
  import { instanceMembers } from "./classes.js";
2
2
  import { arrayOf, func, nonNull, nullable, union } from "./constructors.js";
3
3
  import { instantiate } from "./generics.js";
4
+ import { iteratedBy } from "./iterables.js";
4
5
  import { memberNames } from "./lazy.js";
5
6
  import { VOID, } from "./model.js";
6
7
  import { typesEqual } from "./relations.js";
@@ -45,7 +46,7 @@ export function substitute(type, bindings, fallback) {
45
46
  case 'param':
46
47
  return bindings.get(type) ?? fallback ?? type;
47
48
  case 'array':
48
- return arrayOf(substitute(type.element, bindings, fallback));
49
+ return arrayOf(substitute(type.element, bindings, fallback), type.positions?.map((each) => substitute(each, bindings, fallback)));
49
50
  case 'nullable':
50
51
  return nullable(substitute(type.type, bindings, fallback));
51
52
  case 'union':
@@ -127,6 +128,8 @@ function substituteObject(type, bindings, fallback) {
127
128
  return { ...type, members, call: type.call && sub(type.call) };
128
129
  }
129
130
  const MAX_STRUCTURAL_DEPTH = 3;
131
+ /** The names of the templates of channel types (checker/channel.ts). */
132
+ const CHANNELS = new Set(['chan', 'chan<-', '<-chan']);
130
133
  /** Infers type parameters in `param` from the argument type `arg`. */
131
134
  export function inferTypeParams(param, arg, bindings, depth = 0) {
132
135
  if (!containsTypeParam(param))
@@ -139,8 +142,15 @@ export function inferTypeParams(param, arg, bindings, depth = 0) {
139
142
  }
140
143
  break;
141
144
  case 'array':
142
- if (arg.kind === 'array')
145
+ // `[K, V]` from `["a", 1]`: each by its position; else the union of K and V would take all.
146
+ if (arg.kind === 'array' &&
147
+ param.positions?.length === arg.positions?.length &&
148
+ arg.positions) {
149
+ param.positions.forEach((each, i) => infer(each, arg.positions[i]));
150
+ }
151
+ else if (arg.kind === 'array') {
143
152
  infer(param.element, arg.element);
153
+ }
144
154
  break;
145
155
  case 'nullable':
146
156
  infer(param.type, nonNull(arg));
@@ -171,12 +181,21 @@ export function inferTypeParams(param, arg, bindings, depth = 0) {
171
181
  }
172
182
  break;
173
183
  case 'object': {
184
+ // A channel of one direction from any channel: `chan<- T` from a `chan number`.
174
185
  if (param.instanceOf &&
175
186
  arg.kind === 'object' &&
176
- arg.instanceOf?.template === param.instanceOf.template) {
187
+ (arg.instanceOf?.template === param.instanceOf.template ||
188
+ (CHANNELS.has(param.instanceOf.template.name ?? '') &&
189
+ CHANNELS.has(arg.instanceOf?.template.name ?? '')))) {
177
190
  param.instanceOf.args.forEach((each, i) => infer(each, arg.instanceOf.args[i]));
178
191
  break;
179
192
  }
193
+ // `Iterable<T>` from `[]number` or a `Set<number>`: T is what `for` gives of it.
194
+ const values = param.iterates && iteratedBy(arg);
195
+ if (values) {
196
+ infer(param.iterates, values);
197
+ break;
198
+ }
180
199
  // Structurally: `{ value T }` from `{ value number }`. Types refer to themselves, and the
181
200
  // methods of generic interfaces give ever new instances (`then` of a promise gives a
182
201
  // promise), so only a few levels deep, as in TypeScript.
@@ -2,6 +2,7 @@ import { TYPED_ARRAYS, typedArrayName } from "../../typed-arrays.js";
2
2
  import { ERROR_CLASS } from "./classes.js";
3
3
  import { nullable } from "./constructors.js";
4
4
  import { isQueue, queueElement } from "./queue.js";
5
+ import { isTopic, topicElement } from "./topic.js";
5
6
  // Types as text, for messages.
6
7
  /** How deep object types without a name are printed with their members. */
7
8
  const MAX_OBJECT_DEPTH = 3;
@@ -60,10 +61,10 @@ export function typeToString(type) {
60
61
  return `${type.name} ${typeToString(type.instanceOf.args[0])}`;
61
62
  }
62
63
  // `queue (string | number)`: the parentheses that the type is written with.
63
- if (isQueue(type)) {
64
- const element = queueElement(type);
64
+ if (isQueue(type) || isTopic(type)) {
65
+ const element = isQueue(type) ? queueElement(type) : topicElement(type);
65
66
  const text = typeToString(element);
66
- return `queue ${element.kind === 'union' && !element.name ? `(${text})` : text}`;
67
+ return `${type.name} ${element.kind === 'union' && !element.name ? `(${text})` : text}`;
67
68
  }
68
69
  if (type.instanceOf && type.name !== null) {
69
70
  return `${type.name}<${type.instanceOf.args.map(typeToString).join(', ')}>`;
@@ -1,9 +1,10 @@
1
1
  import { instanceMembers, isSubclass, publicMembers } from "./classes.js";
2
- import { memberNames } from "./lazy.js";
3
2
  import { isNullable, literalBase, nonNull, union } from "./constructors.js";
4
3
  import { UNKNOWN, VOID, } from "./model.js";
4
+ import { indexAssignable, iterableAssignable, numberIndexAssignable } from "./iterables.js";
5
5
  import { typedArrayOf, typeToString } from "./printing.js";
6
6
  import { isQueue, queueElement } from "./queue.js";
7
+ import { isTopic, topicElement } from "./topic.js";
7
8
  // Relations between types: assignability, comparison, equality, common types.
8
9
  /** Whether a value of type `source` can be used where `target` is expected. */
9
10
  export function isAssignable(source, target) {
@@ -80,6 +81,15 @@ export function assignable(source, target, assuming) {
80
81
  if (isQueue(target)) {
81
82
  return isQueue(source) && assignable(queueElement(source), queueElement(target), nested);
82
83
  }
84
+ // A topic both sends and receives, so its values are the same both ways, as a channel's;
85
+ // `topic any` is any topic, for `close(t)`.
86
+ if (isTopic(target)) {
87
+ const element = topicElement(target);
88
+ return (isTopic(source) &&
89
+ (element.kind === 'any' ||
90
+ (assignable(topicElement(source), element, nested) &&
91
+ assignable(element, topicElement(source), nested))));
92
+ }
83
93
  // A channel both takes and gives values, so `chan number` is not a `chan ?number`, as in Go;
84
94
  // `chan any` is any channel, for `close(ch)`. A `chan T` is also a channel of one
85
95
  // direction, `chan<- T` or `<-chan T`, but not the other way round.
@@ -106,6 +116,10 @@ export function assignable(source, target, assuming) {
106
116
  if (source.kind === 'function')
107
117
  return target.members.size === 0;
108
118
  }
119
+ // `Iterable<T>`: what `for` gives of the value fits.
120
+ const iterable = target.iterates && iterableAssignable(source, target, nested);
121
+ if (iterable !== undefined && iterable !== null)
122
+ return iterable;
109
123
  if (target.index && !indexAssignable(source, target.index, nested))
110
124
  return false;
111
125
  if (target.numberIndex && !numberIndexAssignable(source, target.numberIndex, nested)) {
@@ -115,33 +129,12 @@ export function assignable(source, target, assuming) {
115
129
  }
116
130
  }
117
131
  }
118
- /** `{ [string]: T }` accepts an object whose own index and fields are all `T`. */
119
132
  /** The templates of channel types by name (channel.ts): no other type can have these names. */
120
133
  const CHAN_NAMES = new Map([
121
134
  ['chan', 'both'],
122
135
  ['chan<-', 'send'],
123
136
  ['<-chan', 'receive'],
124
137
  ]);
125
- function indexAssignable(source, index, assuming) {
126
- if (source.kind !== 'object')
127
- return false;
128
- if (source.index && !assignable(source.index, index, assuming))
129
- return false;
130
- return [...memberNames(source.members)].every((name) => {
131
- const member = source.members.get(name);
132
- return !member || member.method || assignable(member.type, index, assuming);
133
- });
134
- }
135
- /** `ArrayLike<T>` from a `.d.ts` accepts an array, a string or a typed array of `T`. */
136
- function numberIndexAssignable(source, index, assuming) {
137
- if (source.kind === 'array')
138
- return assignable(source.element, index, assuming);
139
- if (source.kind === 'string')
140
- return assignable(source, index, assuming);
141
- return (source.kind === 'object' &&
142
- source.numberIndex !== undefined &&
143
- assignable(source.numberIndex, index, assuming));
144
- }
145
138
  function functionAssignable(source, target, assuming) {
146
139
  // An iterator is a JS generator: it does not take the callback that a function of its shape does.
147
140
  if (Boolean(source.iterator) !== Boolean(target.iterator))
@@ -211,7 +204,7 @@ export function explainMismatch(source, target) {
211
204
  if (source.kind === 'class' && target.kind === 'class' && source.info === target.info) {
212
205
  return null;
213
206
  }
214
- if (isQueue(target))
207
+ if (isQueue(target) || isTopic(target))
215
208
  return null;
216
209
  const members = target.kind === 'object'
217
210
  ? target.members
@@ -296,9 +289,10 @@ export function hasZeroValue(type) {
296
289
  case 'any':
297
290
  case 'unknown':
298
291
  return true;
299
- // An empty queue, as `[]` is the zero value of an array.
292
+ // An empty queue, as `[]` is the zero value of an array; Channel.EMPTY and Topic.EMPTY, which
293
+ // are closed.
300
294
  case 'object':
301
- return isQueue(type);
295
+ return isQueue(type) || isTopic(type) || CHAN_NAMES.has(type.instanceOf?.template.name ?? '');
302
296
  default:
303
297
  return false;
304
298
  }
@@ -0,0 +1,6 @@
1
+ import type { ObjectType, Type } from './model.ts';
2
+ export declare function topicOf(element: Type): ObjectType;
3
+ /** Whether a type is `topic T`. */
4
+ export declare function isTopic(type: Type): boolean;
5
+ /** The type of the values of a topic type. */
6
+ export declare function topicElement(topic: Type): Type;
@@ -0,0 +1,23 @@
1
+ import { instantiate } from "./generics.js";
2
+ // `topic T`, made by `topic<T>(name)`: values that go to the other tabs of a site over a
3
+ // BroadcastChannel. A topic type is an instance of a generic object without members, as a channel
4
+ // type is, written and printed by its own syntax; only a topic of the same values is assignable to
5
+ // it (relations.ts), since a topic both sends and receives them.
6
+ const TOPIC = {
7
+ kind: 'object',
8
+ name: 'topic',
9
+ members: new Map(),
10
+ call: null,
11
+ typeParams: [{ kind: 'param', name: 'T' }],
12
+ };
13
+ export function topicOf(element) {
14
+ return instantiate(TOPIC, [element]);
15
+ }
16
+ /** Whether a type is `topic T`. */
17
+ export function isTopic(type) {
18
+ return type.kind === 'object' && type.instanceOf?.template === TOPIC;
19
+ }
20
+ /** The type of the values of a topic type. */
21
+ export function topicElement(topic) {
22
+ return topic.instanceOf.args[0];
23
+ }
@@ -4,7 +4,9 @@ export * from './types/lazy.ts';
4
4
  export * from './types/generics.ts';
5
5
  export * from './types/classes.ts';
6
6
  export * from './types/relations.ts';
7
+ export * from './types/iterables.ts';
7
8
  export * from './types/params.ts';
8
9
  export * from './types/printing.ts';
9
10
  export * from './types/bytes.ts';
10
11
  export * from './types/queue.ts';
12
+ export * from './types/topic.ts';
@@ -5,7 +5,9 @@ export * from "./types/lazy.js";
5
5
  export * from "./types/generics.js";
6
6
  export * from "./types/classes.js";
7
7
  export * from "./types/relations.js";
8
+ export * from "./types/iterables.js";
8
9
  export * from "./types/params.js";
9
10
  export * from "./types/printing.js";
10
11
  export * from "./types/bytes.js";
11
12
  export * from "./types/queue.js";
13
+ export * from "./types/topic.js";
package/dist/cli.js CHANGED
@@ -1,9 +1,6 @@
1
1
  #!/usr/bin/env node
2
- import { existsSync, mkdirSync, mkdtempSync, readFileSync, rmSync, statSync, writeFileSync, } from 'node:fs';
3
- import { tmpdir } from 'node:os';
4
- import { spawnSync } from 'node:child_process';
5
- import { dirname, join } from 'node:path';
6
- import { fileURLToPath } from 'node:url';
2
+ import { existsSync, mkdirSync, readFileSync, statSync, writeFileSync } from 'node:fs';
3
+ import { dirname } from 'node:path';
7
4
  import { parseArgs } from 'node:util';
8
5
  import { build, displayPath } from "./build.js";
9
6
  import { CompileError, compileFile } from "./compile-file.js";
@@ -11,6 +8,7 @@ import { markupFor } from "./config.js";
11
8
  import { formatDiagnostic } from "./diagnostics.js";
12
9
  import { tokenize } from "./lexer/lexer.js";
13
10
  import { parse } from "./parser/parser.js";
11
+ import { runFile } from "./run-file.js";
14
12
  import { SourceFile } from "./source.js";
15
13
  const { version } = JSON.parse(readFileSync(new URL('../package.json', import.meta.url), 'utf8'));
16
14
  const HELP = `Rook ${version}
@@ -25,6 +23,8 @@ Options:
25
23
  --out-dir <dir> build: write the .js files to <dir>, keeping the folder structure
26
24
  (by default they go next to the .rk files)
27
25
  --stdout build: print the JS of a single file instead of writing files
26
+ --no-source-map build: do not write the source maps, app.js.map
27
+ --watch run: run again when a file of the program changes
28
28
  --no-check Skip type checking
29
29
  --markup Turn markup and components on; by default they are on where the
30
30
  package.json has "rook": { "markup": true }
@@ -41,6 +41,8 @@ function main() {
41
41
  'out-dir': { type: 'string' },
42
42
  stdout: { type: 'boolean' },
43
43
  'no-check': { type: 'boolean' },
44
+ 'no-source-map': { type: 'boolean' },
45
+ watch: { type: 'boolean' },
44
46
  markup: { type: 'boolean' },
45
47
  'no-markup': { type: 'boolean' },
46
48
  help: { type: 'boolean', short: 'h' },
@@ -81,11 +83,16 @@ function main() {
81
83
  process.stdout.write(compileFile(file, { rewriteImports: true, typeCheck, markup }).code);
82
84
  }
83
85
  else {
84
- buildFiles(files, { outDir: values['out-dir'], typeCheck, markup });
86
+ buildFiles(files, {
87
+ outDir: values['out-dir'],
88
+ typeCheck,
89
+ markup,
90
+ sourceMap: !values['no-source-map'],
91
+ });
85
92
  }
86
93
  break;
87
94
  case 'run':
88
- process.exitCode = runFile(file, typeCheck, markup);
95
+ runFile(file, { typeCheck, markup, watch: values.watch === true });
89
96
  break;
90
97
  case 'tokens':
91
98
  printTokens(new SourceFile(file, readFileSync(file, 'utf8')));
@@ -113,44 +120,19 @@ function buildFiles(entries, options) {
113
120
  const messages = errors.flatMap(({ file, diagnostics }) => diagnostics.map((diagnostic) => formatDiagnostic(file, diagnostic)));
114
121
  throw new CompileError(messages.join('\n\n'));
115
122
  }
116
- for (const { source, output, code, declarations, worker } of outputs) {
117
- const extra = `${declarations ? ' + .d.ts' : ''}${worker ? ' + .worker.js' : ''}`;
123
+ for (const { source, output, code, declarations, worker, maps = [] } of outputs) {
124
+ const extra = `${declarations ? ' + .d.ts' : ''}${worker ? ' + .worker.js' : ''}${maps.length > 0 ? ' + .js.map' : ''}`;
118
125
  mkdirSync(dirname(output), { recursive: true });
119
126
  writeFileSync(output, code);
120
127
  if (declarations)
121
128
  writeFileSync(declarations.output, declarations.code);
122
129
  if (worker)
123
130
  writeFileSync(worker.output, worker.code);
131
+ for (const map of maps)
132
+ writeFileSync(map.output, map.code);
124
133
  console.log(`${displayPath(source)} → ${displayPath(output)}${extra}`);
125
134
  }
126
135
  }
127
- /**
128
- * Runs a program in a process of its own that imports loader.ts first: so do the worker threads
129
- * of its goroutines, which do not share the hooks of the thread that registers them. They take
130
- * the compiled code from a directory of the run, removed after it.
131
- */
132
- function runFile(file, typeCheck, markup) {
133
- // The build rewrites the extensions of imports, not of these URLs: in the package they are .js.
134
- const extension = import.meta.url.endsWith('.ts') ? '.ts' : '.js';
135
- const loader = new URL(`./loader${extension}`, import.meta.url).href;
136
- const run = fileURLToPath(new URL(`./run${extension}`, import.meta.url));
137
- const cache = mkdtempSync(join(tmpdir(), 'rook-run-'));
138
- try {
139
- const { status, signal } = spawnSync(process.execPath, ['--import', loader, run, file], {
140
- stdio: 'inherit',
141
- env: {
142
- ...process.env,
143
- ROOK_RUN_CACHE: cache,
144
- ...(typeCheck ? {} : { ROOK_NO_CHECK: '1' }),
145
- ...(markup === undefined ? {} : { ROOK_MARKUP: markup ? '1' : '0' }),
146
- },
147
- });
148
- return status ?? (signal ? 1 : 0);
149
- }
150
- finally {
151
- rmSync(cache, { recursive: true, force: true });
152
- }
153
- }
154
136
  function printTokens(file) {
155
137
  const { tokens, diagnostics } = tokenize(file.text);
156
138
  for (const token of tokens) {
@@ -2,6 +2,12 @@ import type * as ast from '../ast.ts';
2
2
  import { ExpressionEmitter } from './expressions.ts';
3
3
  export declare abstract class CallEmitter extends ExpressionEmitter {
4
4
  protected call(node: ast.CallExpression): string;
5
+ /**
6
+ * `go f(x)`: the worker module has the function under the same name, a literal under the name
7
+ * given to it, after the local names it uses; the arguments are copied there. In the worker
8
+ * module a literal runs in the same worker, with copies of those names as its first parameters.
9
+ */
10
+ protected go(node: ast.GoExpression): string;
5
11
  /** `yield v` for `yield(v)` in an iterator, `yield [k, v]` for `yield(k, v)`; else `null`. */
6
12
  private yielded;
7
13
  protected expressionStatement(node: ast.Expression): string;
@@ -23,6 +29,23 @@ export declare abstract class CallEmitter extends ExpressionEmitter {
23
29
  protected chan(node: ast.ChanExpression): string;
24
30
  /** `queue<number>(items)`: the queue copies the values, as `[...items]` would. */
25
31
  protected queue(node: ast.QueueExpression): string;
32
+ /** Whether the checker found a topic here, or a `?topic T` in a case of `select`. */
33
+ protected isTopicValue(node: ast.Expression): boolean;
34
+ /**
35
+ * The channel that `<-x`, `for v in x` and a case of `select` receive from: a topic listens from
36
+ * the first of them on, into a channel of its own (src/runtime/topic.js).
37
+ */
38
+ protected receivedFrom(node: ast.Expression): string;
39
+ /**
40
+ * The zero value that `<-x` gives if `x` is `Channel.EMPTY` or `Topic.EMPTY`, which are the same
41
+ * for all types; any other channel keeps its own. `null` when it is null.
42
+ */
43
+ protected receivedZero(node: ast.Expression): string;
44
+ protected receive(node: ast.Expression, withOk: boolean): string;
45
+ /** `ch <- v` waits until the channel takes the value; a topic sends and goes on. */
46
+ protected send(node: ast.SendStatement): string;
47
+ /** `topic<T>(name)`: a topic keeps the zero value that a closed one gives, as a channel does. */
48
+ protected topic(node: ast.TopicExpression): string;
26
49
  /**
27
50
  * `chan(stream, size)`: the channel keeps the zero value of its values, as `chan<T>(size)` does,
28
51
  * from the type of the stream that the checker found; without it, null.
@@ -1,4 +1,6 @@
1
- import { typedArrayOf } from "../checker/types.js";
1
+ import { elementOf, isChan } from "../checker/channel.js";
2
+ import { isNullable, isQueue, isTopic, nonNull, topicElement, typedArrayOf, } from "../checker/types.js";
3
+ import { goCaptures, goParams, goTarget } from "../goroutines.js";
2
4
  import { isYieldCall } from "../iterators.js";
3
5
  import { TYPED_ARRAYS, TYPED_ELEMENTS } from "../typed-arrays.js";
4
6
  import { hasOptionalLink } from "./analysis.js";
@@ -8,21 +10,50 @@ import { ARROW, BINARY, POSTFIX } from "./syntax.js";
8
10
  // checker found one expected becomes `new Uint8Array([...])`, and the conversions of Go between
9
11
  // text and its UTF-8 bytes use TextEncoder and TextDecoder, `[]byte(s)` and `string(data)`. In an
10
12
  // iterator, a generator, `yield(v)` yields: its value is `true`, since a generator that the loop
11
- // over it leaves is closed at that `yield` and goes no further, so `false` is never seen.
13
+ // over it leaves is closed at that `yield` and goes no further, so `false` is never seen. Making
14
+ // channels, queues and topics is here too, with what a topic needs where it is received or closed.
12
15
  export class CallEmitter extends ExpressionEmitter {
13
16
  call(node) {
14
17
  // After `?.` the arguments may not be evaluated at all.
15
18
  const conditional = node.optional || hasOptionalLink(node.callee);
16
19
  const args = this.withHoisting(this.hoist && !conditional, () => this.elements(node.arguments));
17
- if (this.isBuiltinError(node.callee) && !node.optional)
18
- return `new Error(${args})`;
20
+ // `error("load user", err)` wraps an error, with its message after this one (runtime/core.js).
21
+ if (this.isBuiltinError(node.callee) && !node.optional) {
22
+ return node.arguments.length > 1 ? `$$wrap(${args})` : `new Error(${args})`;
23
+ }
19
24
  if (this.isStringConversion(node.callee))
20
25
  return `new TextDecoder().decode(${args})`;
21
26
  const yielded = this.yielded(node);
22
27
  if (yielded !== null)
23
28
  return `(${yielded}, true)`;
29
+ // `close(t)` of a topic: it stops listening and sending.
30
+ const [argument] = node.arguments;
31
+ if (node.callee.kind === 'Identifier' &&
32
+ node.callee.name === 'close' &&
33
+ this.lookup('close') === undefined &&
34
+ argument?.kind !== 'SpreadElement' &&
35
+ argument !== undefined &&
36
+ this.isTopicValue(argument)) {
37
+ return `$$Topic.close(${args})`;
38
+ }
24
39
  return `${this.expression(node.callee, POSTFIX)}${node.optional ? '?.' : ''}(${args})`;
25
40
  }
41
+ /**
42
+ * `go f(x)`: the worker module has the function under the same name, a literal under the name
43
+ * given to it, after the local names it uses; the arguments are copied there. In the worker
44
+ * module a literal runs in the same worker, with copies of those names as its first parameters.
45
+ */
46
+ go(node) {
47
+ const { callee } = node.call;
48
+ const literal = callee.kind === 'FuncExpression' ? callee : null;
49
+ const captured = literal ? goCaptures(this.program, node) : [];
50
+ const args = [...captured.map((name) => this.name(name)), this.elements(node.call.arguments)];
51
+ // Without type checking, a callee that is neither fails when the call runs.
52
+ const fn = literal && this.inWorker
53
+ ? this.expression({ ...literal, params: [...goParams(literal, captured), ...literal.params] }, ARROW)
54
+ : JSON.stringify(goTarget(this.program, node));
55
+ return `$$go(${fn}, [${args.filter((arg) => arg !== '').join(', ')}])`;
56
+ }
26
57
  /** `yield v` for `yield(v)` in an iterator, `yield [k, v]` for `yield(k, v)`; else `null`. */
27
58
  yielded(node) {
28
59
  if (!this.fn.iterator || !isYieldCall(node) || this.lookup('yield') !== 'param')
@@ -37,7 +68,8 @@ export class CallEmitter extends ExpressionEmitter {
37
68
  yieldStatement(node) {
38
69
  const [only] = node.consequent.body;
39
70
  const { condition } = node;
40
- if (node.alternate ||
71
+ if (node.init ||
72
+ node.alternate ||
41
73
  node.consequent.body.length !== 1 ||
42
74
  only?.kind !== 'ReturnStatement' ||
43
75
  only.values.length > 0 ||
@@ -105,12 +137,55 @@ export class CallEmitter extends ExpressionEmitter {
105
137
  /** `chan<number>(10)`: the channel keeps the zero value that a closed one gives. */
106
138
  chan(node) {
107
139
  const size = node.size ? this.expression(node.size, ARROW) : '0';
108
- return `$$chan(${size}, ${this.zeroValue(node.element) ?? 'null'})`;
140
+ return `$$Channel.make(${size}, ${this.zeroValue(node.element) ?? 'null'})`;
109
141
  }
110
142
  /** `queue<number>(items)`: the queue copies the values, as `[...items]` would. */
111
143
  queue(node) {
112
144
  return `new $$Queue(${node.items ? this.expression(node.items, ARROW) : ''})`;
113
145
  }
146
+ /** Whether the checker found a topic here, or a `?topic T` in a case of `select`. */
147
+ isTopicValue(node) {
148
+ const type = this.options.types?.get(node);
149
+ return type !== undefined && isTopic(isNullable(type) ? nonNull(type) : type);
150
+ }
151
+ /**
152
+ * The channel that `<-x`, `for v in x` and a case of `select` receive from: a topic listens from
153
+ * the first of them on, into a channel of its own (src/runtime/topic.js).
154
+ */
155
+ receivedFrom(node) {
156
+ const channel = this.expression(node, ARROW);
157
+ return this.isTopicValue(node) ? `$$Topic.listen(${channel})` : channel;
158
+ }
159
+ /**
160
+ * The zero value that `<-x` gives if `x` is `Channel.EMPTY` or `Topic.EMPTY`, which are the same
161
+ * for all types; any other channel keeps its own. `null` when it is null.
162
+ */
163
+ receivedZero(node) {
164
+ const type = this.options.types?.get(node);
165
+ const channel = type && (isNullable(type) ? nonNull(type) : type);
166
+ if (channel && isChan(channel))
167
+ return zeroOf(elementOf(channel));
168
+ if (channel && isTopic(channel))
169
+ return zeroOf(topicElement(channel));
170
+ return 'null';
171
+ }
172
+ receive(node, withOk) {
173
+ const zero = this.receivedZero(node);
174
+ const args = zero !== 'null' ? `, ${withOk}, ${zero}` : withOk ? ', true' : '';
175
+ return `$$Channel.receive(${this.receivedFrom(node)}${args})`;
176
+ }
177
+ /** `ch <- v` waits until the channel takes the value; a topic sends and goes on. */
178
+ send(node) {
179
+ const channel = this.expression(node.channel, ARROW);
180
+ const value = this.expression(node.value, ARROW);
181
+ return this.isTopicValue(node.channel)
182
+ ? `$$Topic.publish(${channel}, ${value})`
183
+ : `await $$Channel.send(${channel}, ${value})`;
184
+ }
185
+ /** `topic<T>(name)`: a topic keeps the zero value that a closed one gives, as a channel does. */
186
+ topic(node) {
187
+ return `$$Topic.make(${this.expression(node.name, ARROW)}, ${this.zeroValue(node.element) ?? 'null'})`;
188
+ }
114
189
  /**
115
190
  * `chan(stream, size)`: the channel keeps the zero value of its values, as `chan<T>(size)` does,
116
191
  * from the type of the stream that the checker found; without it, null.
@@ -121,7 +196,7 @@ export class CallEmitter extends ExpressionEmitter {
121
196
  const type = this.options.types?.get(node);
122
197
  const channel = type?.kind === 'tuple' ? type.types[0] : undefined;
123
198
  const element = channel?.kind === 'object' ? channel.instanceOf?.args[0] : undefined;
124
- return `$$fromStream(${stream}, ${size}, ${element ? zeroOf(element) : 'null'})`;
199
+ return `$$Channel.fromStream(${stream}, ${size}, ${element ? zeroOf(element) : 'null'})`;
125
200
  }
126
201
  }
127
202
  /** The zero value of a type, as zeroValue gives for a type as written. */
@@ -140,6 +215,12 @@ function zeroOf(type) {
140
215
  return 'false';
141
216
  case 'array':
142
217
  return '[]';
218
+ case 'object':
219
+ if (isQueue(type))
220
+ return 'new $$Queue()';
221
+ if (isChan(type))
222
+ return '$$Channel.EMPTY';
223
+ return isTopic(type) ? '$$Topic.EMPTY' : 'null';
143
224
  default:
144
225
  return 'null';
145
226
  }
@@ -2,6 +2,13 @@ import type * as ast from '../ast.ts';
2
2
  import { SimpleStatementEmitter } from './simple-statements.ts';
3
3
  export declare abstract class ControlStatementEmitter extends SimpleStatementEmitter {
4
4
  protected ifStatement(node: ast.IfStatement): string;
5
+ /**
6
+ * `if const v = f(); v > 0 { }` → `{ const v = f(); if (v > 0) { } }`: a block keeps the
7
+ * variables of the initializer to the statement, which `emit` writes.
8
+ */
9
+ private withInit;
10
+ /** `outer: for (...)`: the label goes on the loop itself, after what is hoisted before it. */
11
+ private labeled;
5
12
  protected loopBody(body: ast.BlockStatement): string;
6
13
  protected forStatement(node: ast.ForStatement): void;
7
14
  /** A statement inside a `for` header, without the `;`. */
@@ -20,8 +27,9 @@ export declare abstract class ControlStatementEmitter extends SimpleStatementEmi
20
27
  * a name that cannot change.
21
28
  */
22
29
  private indexLoop;
30
+ protected switchStatement(node: ast.SwitchStatement): void;
23
31
  /** Each `case` ends with an implicit `break`; a case with declarations gets its own block. */
24
- protected switchStatement(node: ast.SwitchStatement, discriminant: ast.Expression): void;
32
+ private valueSwitch;
25
33
  /**
26
34
  * `select`: $$select gives the index of the case that went on and what it received, and a
27
35
  * switch runs the case; a `break` inside it leaves the select, as in Go. `default` is -1.
@@ -31,6 +39,6 @@ export declare abstract class ControlStatementEmitter extends SimpleStatementEmi
31
39
  * `switch { case cond: ... }` becomes an if/else chain. A `break` inside it must leave the
32
40
  * switch, not an enclosing loop, so then the chain gets a label to break to.
33
41
  */
34
- protected taglessSwitch(node: ast.SwitchStatement): void;
42
+ private taglessSwitch;
35
43
  protected tryStatement(node: ast.TryStatement): string;
36
44
  }