rooklang 0.3.0 → 0.3.2

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 (94) 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/base.js +4 -4
  8. package/dist/checker/builtins.js +4 -35
  9. package/dist/checker/bytes.d.ts +2 -2
  10. package/dist/checker/bytes.js +2 -2
  11. package/dist/checker/channels.d.ts +3 -0
  12. package/dist/checker/channels.js +39 -1
  13. package/dist/checker/expressions.d.ts +2 -0
  14. package/dist/checker/expressions.js +4 -0
  15. package/dist/checker/helpers.js +2 -0
  16. package/dist/checker/queues.d.ts +6 -0
  17. package/dist/checker/queues.js +24 -0
  18. package/dist/checker/resolution.js +6 -2
  19. package/dist/checker/transfer.js +4 -1
  20. package/dist/checker/types/printing.js +7 -0
  21. package/dist/checker/types/queue.d.ts +6 -0
  22. package/dist/checker/types/queue.js +42 -0
  23. package/dist/checker/types/relations.js +12 -1
  24. package/dist/checker/types.d.ts +1 -0
  25. package/dist/checker/types.js +1 -0
  26. package/dist/checker/untyped-globals.d.ts +1 -0
  27. package/dist/checker/untyped-globals.js +47 -0
  28. package/dist/cli.js +4 -2
  29. package/dist/codegen/attributes.js +0 -1
  30. package/dist/codegen/calls.d.ts +9 -0
  31. package/dist/codegen/calls.js +41 -1
  32. package/dist/codegen/component-values.js +0 -1
  33. package/dist/codegen/components.js +0 -1
  34. package/dist/codegen/control-flow.js +0 -3
  35. package/dist/codegen/control-statements.js +0 -4
  36. package/dist/codegen/creation.d.ts +6 -8
  37. package/dist/codegen/creation.js +1 -10
  38. package/dist/codegen/early-returns.js +0 -1
  39. package/dist/codegen/elements.js +0 -3
  40. package/dist/codegen/emitter.d.ts +3 -5
  41. package/dist/codegen/emitter.js +0 -2
  42. package/dist/codegen/expressions.d.ts +4 -1
  43. package/dist/codegen/expressions.js +9 -13
  44. package/dist/codegen/functions.js +0 -2
  45. package/dist/codegen/js.d.ts +0 -7
  46. package/dist/codegen/js.js +33 -59
  47. package/dist/codegen/parallel.js +2 -4
  48. package/dist/codegen/refs.js +0 -1
  49. package/dist/codegen/runtime.d.ts +15 -0
  50. package/dist/codegen/runtime.js +99 -0
  51. package/dist/codegen/simple-statements.js +3 -1
  52. package/dist/codegen/styles.d.ts +1 -1
  53. package/dist/codegen/styles.js +2 -2
  54. package/dist/codegen/web-components.js +0 -1
  55. package/dist/compile.d.ts +7 -0
  56. package/dist/compile.js +1 -0
  57. package/dist/completion.js +2 -1
  58. package/dist/index.d.ts +2 -0
  59. package/dist/index.js +1 -0
  60. package/dist/loader.js +8 -0
  61. package/dist/parser/base.d.ts +3 -1
  62. package/dist/parser/expressions.js +9 -2
  63. package/dist/parser/literals.d.ts +7 -0
  64. package/dist/parser/literals.js +45 -1
  65. package/dist/parser/simple-statements.js +3 -0
  66. package/dist/parser/types.d.ts +1 -1
  67. package/dist/parser/types.js +10 -3
  68. package/dist/runtime/sources.d.ts +2 -0
  69. package/dist/runtime/sources.js +14 -0
  70. package/dist/ts-types.js +9 -1
  71. package/dist/vite.js +7 -1
  72. package/dist/walk-types.js +1 -0
  73. package/dist/walk.js +9 -0
  74. package/package.json +7 -4
  75. package/src/runtime/chan.js +277 -0
  76. package/src/runtime/core.js +23 -0
  77. package/src/runtime/go.js +334 -0
  78. package/src/runtime/index.js +13 -0
  79. package/src/runtime/markup.js +336 -0
  80. package/src/runtime/queue.js +118 -0
  81. package/src/runtime/select.js +170 -0
  82. package/src/runtime/streams.js +61 -0
  83. package/src/runtime/threads.js +240 -0
  84. package/src/runtime/timers.js +27 -0
  85. package/dist/codegen/chan-helpers.d.ts +0 -2
  86. package/dist/codegen/chan-helpers.js +0 -253
  87. package/dist/codegen/go-helpers.d.ts +0 -9
  88. package/dist/codegen/go-helpers.js +0 -307
  89. package/dist/codegen/helpers.d.ts +0 -10
  90. package/dist/codegen/helpers.js +0 -356
  91. package/dist/codegen/select-helpers.d.ts +0 -2
  92. package/dist/codegen/select-helpers.js +0 -135
  93. package/dist/codegen/thread-helpers.d.ts +0 -2
  94. package/dist/codegen/thread-helpers.js +0 -198
package/README.md CHANGED
@@ -3,7 +3,8 @@
3
3
  **English** · [Русский](README.ru.md)
4
4
 
5
5
  A language with the semantics of JavaScript, Go-style syntax, goroutines on real threads and static
6
- types. It compiles to readable JavaScript without a runtime (WebAssembly is planned).
6
+ types. It compiles to readable JavaScript with a small runtime library for channels, goroutines
7
+ and markup (WebAssembly is planned).
7
8
 
8
9
  ```go
9
10
  async func square(jobs <-chan number, results chan<- number) {
@@ -55,12 +56,15 @@ Specification: [docs/spec.md](docs/spec.md), design of components:
55
56
  Rook needs Node.js ≥ 24.
56
57
 
57
58
  ```sh
58
- npm install --save-dev rooklang
59
+ npm install rooklang
59
60
  npx rook build app.rk # app.js next to the source
60
61
  npx rook build src --out-dir dist # every module of a folder
61
62
  npx rook run app.rk # compile and run
62
63
  ```
63
64
 
65
+ The compiled code imports what channels, goroutines and markup need from `rooklang/runtime`, so
66
+ the package is a dependency of the project, not only of its build.
67
+
64
68
  From code, the compiler is `import { compile } from 'rooklang'`. The package also has the
65
69
  [Vite](#vite) plugin, `rooklang/vite`, and `rooklang/browser`, the compiler without the
66
70
  TypeScript compiler for running in a browser, as in the site's playground: there imports are not
@@ -90,6 +94,7 @@ staged version is formatted, so edits that are not staged stay out of the commit
90
94
  | `npm run check` | All at once: types, lint, format, tests |
91
95
  | `npm run build` | Build the npm package to `dist/` |
92
96
  | `npm run vscode:install` | Build and install the VS Code extension |
97
+ | `npm run vscode:package` | Pack the VS Code extension into `rooklang.vsix` |
93
98
 
94
99
  Options of `build`: `--out-dir dir` writes the `.js` files to a separate folder, `--stdout` prints
95
100
  the JS of one file, `--no-check` skips type checking. `--markup` and `--no-markup` turn markup and
@@ -108,8 +113,16 @@ version yet, runs `npm run check`, builds `dist/`, publishes the package to npm
108
113
 
109
114
  [editors/vscode/](editors/vscode/) has an extension for `.rk` files: highlighting of code, types
110
115
  and markup, errors as you type and completion of names, members, types, tags and attributes, from a
111
- language server with the compiler. `npm run vscode:install` builds it and installs it with `code`;
112
- see [editors/vscode/README.md](editors/vscode/README.md).
116
+ language server with the compiler. It is "Rook Language" in the
117
+ [Visual Studio Marketplace](https://marketplace.visualstudio.com/items?itemName=Qwern9876r.rooklang) and on [Open VSX](https://open-vsx.org/extension/Qwern9876r/rooklang).
118
+ `npm run vscode:install` builds it from the source and installs it with `code`; how to work on it
119
+ is in [editors/vscode/DEVELOPMENT.md](editors/vscode/DEVELOPMENT.md).
120
+
121
+ A release of the extension is a new `version` in `editors/vscode/package.json` on `main`: the
122
+ workflow [.github/workflows/vscode.yml](.github/workflows/vscode.yml) sees that a store does not
123
+ have this version yet, runs `npm run check`, publishes the extension to the Marketplace and Open VSX
124
+ and tags the commit `vscode-v0.1.1`. The setup of the stores is in
125
+ [editors/vscode/DEVELOPMENT.md](editors/vscode/DEVELOPMENT.md#publishing).
113
126
 
114
127
  ## Documentation site
115
128
 
package/README.ru.md CHANGED
@@ -3,7 +3,8 @@
3
3
  [English](README.md) · **Русский**
4
4
 
5
5
  Язык с семантикой JavaScript, синтаксисом в духе Go, горутинами на настоящих потоках и статической
6
- типизацией. Компилируется в читаемый JavaScript без рантайма (в планах — WebAssembly).
6
+ типизацией. Компилируется в читаемый JavaScript с маленькой библиотекой рантайма для каналов,
7
+ горутин и разметки (в планах — WebAssembly).
7
8
 
8
9
  ```go
9
10
  async func square(jobs <-chan number, results chan<- number) {
@@ -55,12 +56,15 @@ for let i = 0; i < 4; i++ {
55
56
  Нужен Node.js ≥ 24.
56
57
 
57
58
  ```sh
58
- npm install --save-dev rooklang
59
+ npm install rooklang
59
60
  npx rook build app.rk # app.js рядом с исходником
60
61
  npx rook build src --out-dir dist # все модули папки
61
62
  npx rook run app.rk # скомпилировать и запустить
62
63
  ```
63
64
 
65
+ Скомпилированный код импортирует то, что нужно каналам, горутинам и разметке, из
66
+ `rooklang/runtime`, поэтому пакет — зависимость проекта, а не только его сборки.
67
+
64
68
  Из кода компилятор подключается как `import { compile } from 'rooklang'`. Ещё в пакете есть
65
69
  плагин для [Vite](#vite) — `rooklang/vite` — и `rooklang/browser`: компилятор без
66
70
  компилятора TypeScript, для работы в браузере, как в песочнице сайта; импорты там не проверяются
@@ -91,6 +95,7 @@ Node.js ≥ 24 (`nvm use`). Компилятор написан на TypeScript,
91
95
  | `npm run check` | Всё сразу — типы, линт, формат, тесты |
92
96
  | `npm run build` | Собрать npm-пакет в `dist/` |
93
97
  | `npm run vscode:install` | Собрать и установить расширение для VS Code |
98
+ | `npm run vscode:package` | Упаковать расширение для VS Code в `rooklang.vsix` |
94
99
 
95
100
  Опции `build`: `--out-dir dir` — писать `.js` в отдельную папку, `--stdout` — вывести JS одного
96
101
  файла в консоль, `--no-check` — без проверки типов. `--markup` и `--no-markup` включают или
@@ -109,8 +114,16 @@ Node.js ≥ 24 (`nvm use`). Компилятор написан на TypeScript,
109
114
 
110
115
  В [editors/vscode/](editors/vscode/) лежит расширение для файлов `.rk`: подсветка кода, типов и
111
116
  разметки, ошибки прямо при наборе и автодополнение имён, членов, типов, тегов и атрибутов — от
112
- языкового сервера с компилятором. `npm run vscode:install` собирает его и устанавливает через
113
- `code`; подробности — в [editors/vscode/README.ru.md](editors/vscode/README.ru.md).
117
+ языкового сервера с компилятором. Оно называется «Rook Language» и есть в
118
+ [Visual Studio Marketplace](https://marketplace.visualstudio.com/items?itemName=Qwern9876r.rooklang) и в [Open VSX](https://open-vsx.org/extension/Qwern9876r/rooklang).
119
+ `npm run vscode:install` собирает его из исходников и устанавливает через `code`; как с ним
120
+ работать — в [editors/vscode/DEVELOPMENT.ru.md](editors/vscode/DEVELOPMENT.ru.md).
121
+
122
+ Релиз расширения — это новая `version` в `editors/vscode/package.json` в `main`: workflow
123
+ [.github/workflows/vscode.yml](.github/workflows/vscode.yml) видит, что в магазине такой версии ещё
124
+ нет, запускает `npm run check`, публикует расширение в Marketplace и Open VSX и ставит на коммит тег
125
+ `vscode-v0.1.1`. Как настроены магазины — в
126
+ [editors/vscode/DEVELOPMENT.ru.md](editors/vscode/DEVELOPMENT.ru.md#публикация).
114
127
 
115
128
  ## Сайт с документацией
116
129
 
@@ -1,7 +1,7 @@
1
1
  import type { NodeBase, Parameter } from './declarations.ts';
2
2
  import type { ElementExpression, TypeNode } from './markup.ts';
3
3
  import type { BlockStatement } from './statements.ts';
4
- export type Expression = Identifier | NumberLiteral | BigIntLiteral | StringLiteral | TemplateLiteral | BooleanLiteral | NullLiteral | ThisExpression | SuperExpression | ArrayLiteral | ObjectLiteral | FuncExpression | ArrowFunction | UnaryExpression | BinaryExpression | ConditionalExpression | CallExpression | NewExpression | MemberExpression | IndexExpression | ElementExpression | DecoratorMember | DecoratorGet | AsExpression | GoExpression | ChanExpression | ConversionExpression | MakeExpression;
4
+ export type Expression = Identifier | NumberLiteral | BigIntLiteral | StringLiteral | TemplateLiteral | BooleanLiteral | NullLiteral | ThisExpression | SuperExpression | ArrayLiteral | ObjectLiteral | FuncExpression | ArrowFunction | UnaryExpression | BinaryExpression | ConditionalExpression | CallExpression | NewExpression | MemberExpression | IndexExpression | ElementExpression | DecoratorMember | DecoratorGet | AsExpression | GoExpression | ChanExpression | StreamChanExpression | QueueExpression | ConversionExpression | MakeExpression;
5
5
  /**
6
6
  * `[]byte("rook")`: the UTF-8 bytes of a string. `string(data)`, the way back, is a call, since
7
7
  * `string` may be the name of a value.
@@ -27,6 +27,21 @@ export interface ChanExpression extends NodeBase {
27
27
  element: TypeNode;
28
28
  size: Expression | null;
29
29
  }
30
+ /** `queue<number>()`: a new, empty queue; `queue<number>([1, 2])` starts with these values. */
31
+ export interface QueueExpression extends NodeBase {
32
+ kind: 'QueueExpression';
33
+ element: TypeNode;
34
+ items: Expression | null;
35
+ }
36
+ /**
37
+ * `chan(res.body, 4)`: a channel that receives the chunks of a ReadableStream, with room for 4,
38
+ * and a channel that gets the error of the stream, or null at its end.
39
+ */
40
+ export interface StreamChanExpression extends NodeBase {
41
+ kind: 'StreamChanExpression';
42
+ stream: Expression;
43
+ size: Expression | null;
44
+ }
30
45
  /**
31
46
  * `go work(x)`: calls a function of the module in a worker, with copies of the arguments. As an
32
47
  * expression it is a Promise of what the function returns; as a statement nothing waits for it.
@@ -60,7 +60,7 @@ export interface EventHandler extends NodeBase {
60
60
  kind: 'EventHandler';
61
61
  body: SimpleStatement[];
62
62
  }
63
- export type TypeNode = TypeReference | ArrayType | NullableType | FuncType | ComponentType | ObjectType | UnionType | LiteralType | ChanType;
63
+ export type TypeNode = TypeReference | ArrayType | NullableType | FuncType | ComponentType | ObjectType | UnionType | LiteralType | ChanType | QueueType;
64
64
  /**
65
65
  * `chan number`: a channel of numbers, made by `chan<number>(size)`; `chan<- number` only sends
66
66
  * and `<-chan number` only receives.
@@ -70,6 +70,11 @@ export interface ChanType extends NodeBase {
70
70
  element: TypeNode;
71
71
  direction: 'both' | 'send' | 'receive';
72
72
  }
73
+ /** `queue number`: a queue of numbers, made by `queue<number>()`. */
74
+ export interface QueueType extends NodeBase {
75
+ kind: 'QueueType';
76
+ element: TypeNode;
77
+ }
73
78
  /** A named type: `number`, `User`, `Point`, `Box<number>`. */
74
79
  export interface TypeReference extends NodeBase {
75
80
  kind: 'TypeReference';
package/dist/browser.d.ts CHANGED
@@ -23,6 +23,11 @@ export interface BrowserCompileResult {
23
23
  export interface BrowserOptions {
24
24
  /** Markup and components; off by default, as in a package without them. */
25
25
  markup?: boolean;
26
+ /**
27
+ * Import the runtime from "rooklang/runtime", the default, or put it into the module: code that
28
+ * runs without imports, as in the playground, needs it there.
29
+ */
30
+ runtime?: 'import' | 'inline';
26
31
  }
27
32
  /** Compiles a program; `workerUrl` is where its worker module of `go` will be, if not next to it. */
28
33
  export declare function compile(source: string, workerUrl?: string | null, options?: BrowserOptions): BrowserCompileResult;
package/dist/browser.js CHANGED
@@ -12,6 +12,7 @@ export function compile(source, workerUrl, options = {}) {
12
12
  rewriteImports: false,
13
13
  workerUrl: workerUrl ?? undefined,
14
14
  markup: options.markup ?? false,
15
+ runtime: options.runtime,
15
16
  });
16
17
  const file = new SourceFile('', source);
17
18
  const located = (diagnostic) => {
@@ -7,10 +7,10 @@ import { explainMismatch, isAssignable, LazyMap, memberNames, nonNull, property,
7
7
  // declarations → classes → resolution → initializers → assignments → statements → control flow →
8
8
  // functions → expressions → operators → markup → forwarding → components → component values →
9
9
  // refs → styles → decorator wrappers → decorator exports → decorators → decorator members →
10
- // members → calls → goroutines → channels → bytes → loops → go for → iterators → completion →
11
- // Checker (checker.ts). Statements, expressions and markup check each other recursively, so
12
- // methods of later layers that earlier ones call are declared here as abstract. This layer has the
13
- // state of a check, diagnostics, scopes and names, and the DOM types.
10
+ // members → calls → goroutines → channels → queues → bytes → loops → go for → iterators →
11
+ // completion → Checker (checker.ts). Statements, expressions and markup check each other
12
+ // recursively, so methods of later layers that earlier ones call are declared here as abstract.
13
+ // This layer has the state of a check, diagnostics, scopes and names, and the DOM types.
14
14
  export class CheckerBase {
15
15
  program;
16
16
  options;
@@ -5,6 +5,7 @@ import { SVG_TYPES } from "./dom-svg.js";
5
5
  import { chanOf } from "./channel.js";
6
6
  import { PROMISE } from "./promise.js";
7
7
  import { BUFFERS } from "./transfer.js";
8
+ import { UNTYPED_GLOBALS } from "./untyped-globals.js";
8
9
  import { ANY, arrayOf, BIGINT, BOOL, ERROR, ERROR_CLASS, func, method, NUMBER, nullable, property, STRING, } from "./types.js";
9
10
  // Types of the JS standard library that Rook programs use most. Anything not listed here
10
11
  // can still be reached through the untyped globals below.
@@ -203,42 +204,8 @@ const numberObject = object('Number', {
203
204
  const stringObject = object('String', { fromCharCode: method(func([], [STRING], { rest: NUMBER })) }, func([ANY], [STRING]));
204
205
  const booleanObject = object('Boolean', {}, func([ANY], [BOOL]));
205
206
  const timer = func([func([], []), NUMBER], [ANY], { required: 1, rest: ANY });
206
- /** JS globals that are available without types: everything about them is `any`. */
207
+ /** The type parameter of `transfer`: it gives back what it takes. */
207
208
  const MOVED = { kind: 'param', name: 'T' };
208
- const UNTYPED_GLOBALS = [
209
- 'Object',
210
- 'Array',
211
- 'Date',
212
- 'RegExp',
213
- 'Map',
214
- 'Set',
215
- 'WeakMap',
216
- 'WeakSet',
217
- 'Promise',
218
- 'Symbol',
219
- 'Reflect',
220
- 'Proxy',
221
- 'Intl',
222
- 'globalThis',
223
- 'process',
224
- 'Buffer',
225
- 'URL',
226
- 'URLSearchParams',
227
- 'TextEncoder',
228
- 'TextDecoder',
229
- 'AbortController',
230
- 'fetch',
231
- 'structuredClone',
232
- 'queueMicrotask',
233
- 'window',
234
- 'document',
235
- 'navigator',
236
- 'location',
237
- 'localStorage',
238
- 'sessionStorage',
239
- 'crypto',
240
- 'performance',
241
- ];
242
209
  export const GLOBAL_VALUES = new Map([
243
210
  ['console', console],
244
211
  ['Math', math],
@@ -268,6 +235,8 @@ export const GLOBAL_VALUES = new Map([
268
235
  // Channels of time, as in Go: the time once after `ms`, or every `ms` until `close(ticks)`.
269
236
  ['after', func([NUMBER], [chanOf(NUMBER, 'receive')])],
270
237
  ['tick', func([NUMBER], [chanOf(NUMBER)])],
238
+ // `stream(ch)`: a ReadableStream of the values of a channel (its type: channels.ts).
239
+ ['stream', func([chanOf(ANY, 'receive')], [ANY])],
271
240
  // `cancel(task)` stops a goroutine: it takes the Promise of `go` (channels.ts).
272
241
  ['cancel', func([ANY], [])],
273
242
  ...[...UNTYPED_GLOBALS, ...BUFFERS].map((name) => [name, ANY]),
@@ -1,7 +1,7 @@
1
1
  import type * as ast from '../ast.ts';
2
- import { ChannelChecker } from './channels.ts';
2
+ import { QueueChecker } from './queues.ts';
3
3
  import { type Type } from './types.ts';
4
- export declare abstract class BytesChecker extends ChannelChecker {
4
+ export declare abstract class BytesChecker extends QueueChecker {
5
5
  protected checkArrayLiteral(node: ast.ArrayLiteral, expected: Type | null): Type;
6
6
  private isElements;
7
7
  /** `make([]float64, n)`: a typed array of `n` zeros. */
@@ -1,11 +1,11 @@
1
- import { ChannelChecker } from "./channels.js";
1
+ import { QueueChecker } from "./queues.js";
2
2
  import { isUntyped } from "./helpers.js";
3
3
  import { holdsBigInts } from "../typed-arrays.js";
4
4
  import { BIGINT, isBytes, nonNull, NUMBER, STRING, typedArrayOf, typeToString, UNKNOWN, } from "./types.js";
5
5
  // `[]byte`, a Uint8Array, and the other typed arrays, `[]float64`: an array literal where one is
6
6
  // expected, `let data []byte = [1, 2]`; and the conversions of Go between text and its UTF-8
7
7
  // bytes, `[]byte("rook")` and `string(data)`.
8
- export class BytesChecker extends ChannelChecker {
8
+ export class BytesChecker extends QueueChecker {
9
9
  checkArrayLiteral(node, expected) {
10
10
  const typed = expected && nonNull(expected);
11
11
  const name = typed && typedArrayOf(typed);
@@ -3,7 +3,10 @@ import { GoroutineChecker } from './goroutines.ts';
3
3
  import { type Type } from './types.ts';
4
4
  export declare abstract class ChannelChecker extends GoroutineChecker {
5
5
  protected checkChan(node: ast.ChanExpression): Type;
6
+ protected checkStreamChan(node: ast.StreamChanExpression): Type;
6
7
  protected checkSend(node: ast.SendStatement): void;
7
8
  protected checkCall(node: ast.CallExpression, callee: Type, expected?: Type | null): Type;
9
+ /** `stream(ch)`: a ReadableStream of the values of the channel, from the library if it has one. */
10
+ private streamOf;
8
11
  protected checkSelect(node: ast.SelectStatement): void;
9
12
  }
@@ -4,10 +4,11 @@ import { GoroutineChecker } from "./goroutines.js";
4
4
  import { isUntyped } from "./helpers.js";
5
5
  import { isPromise } from "./promise.js";
6
6
  import { BUFFERS } from "./transfer.js";
7
- import { BOOL, isNullable, nonNull, NUMBER, typeToString } from "./types.js";
7
+ import { ANY, BOOL, ERROR, instantiate, isNullable, nonNull, NUMBER, typeToString, typedArrayOf, UNKNOWN, } from "./types.js";
8
8
  // Channels: `chan<number>(10)` makes one, `ch <- v` sends a value, `<-ch` receives one (in
9
9
  // operators.ts), `for v in ch` receives them until it is closed (in control-flow.ts), and
10
10
  // `select` waits for the first of several. `transfer(x)` moves a buffer to another thread.
11
+ // `chan(stream)` receives the chunks of a ReadableStream, and `stream(ch)` reads a channel.
11
12
  /** What `transfer(x)` moves: a buffer, or a view whose buffer moves. */
12
13
  const MOVABLE = new Set(BUFFERS);
13
14
  function typeName(type) {
@@ -24,6 +25,31 @@ export class ChannelChecker extends GoroutineChecker {
24
25
  this.expectAssignable(this.checkValue(node.size, NUMBER), NUMBER, node.size);
25
26
  return chanOf(element);
26
27
  }
28
+ // `chan(res.body, 4)`: the channel of the chunks and the one of the error of the stream. A null
29
+ // stream, such as the body of a response without one, gives a closed channel.
30
+ checkStreamChan(node) {
31
+ const type = this.checkValue(node.stream);
32
+ const stream = isNullable(type) ? nonNull(type) : type;
33
+ let element = ANY;
34
+ if (stream.kind === 'object' && typeName(stream) === 'ReadableStream') {
35
+ element = stream.instanceOf?.args[0] ?? ANY;
36
+ // The bytes of a body are a Uint8Array<ArrayBuffer>, and a channel takes only its own type:
37
+ // its values are `[]byte`.
38
+ const typed = typedArrayOf(element);
39
+ if (typed !== null)
40
+ element = this.typedArray(typed);
41
+ }
42
+ else if (!isUntyped(stream)) {
43
+ this.error(`chan(...) takes a ReadableStream, not ${typeToString(type)}; a channel of values is made as chan<T>(size), e.g. chan<number>(10)`, node.stream);
44
+ if (node.size)
45
+ this.checkValue(node.size);
46
+ // `ch = chan(10)` meant `chan<number>(10)`: one error is enough.
47
+ return UNKNOWN;
48
+ }
49
+ if (node.size)
50
+ this.expectAssignable(this.checkValue(node.size, NUMBER), NUMBER, node.size);
51
+ return { kind: 'tuple', types: [chanOf(element), chanOf(ERROR, 'receive')] };
52
+ }
27
53
  checkSend(node) {
28
54
  const nullChannel = this.inSelectCase;
29
55
  this.inSelectCase = false;
@@ -58,6 +84,8 @@ export class ChannelChecker extends GoroutineChecker {
58
84
  if (builtin === 'close' && isChan(type) && chanDirection(type) === 'receive') {
59
85
  this.error(`cannot close ${typeToString(type)}: it only receives, and a channel is closed by its sender`, argument);
60
86
  }
87
+ if (builtin === 'stream' && isChan(type))
88
+ return this.streamOf(type, argument);
61
89
  if (builtin === 'cancel' && !isUntyped(type) && !isPromise(type)) {
62
90
  this.error(`cancel() stops a goroutine: give it the Promise of "go", not ${typeToString(type)}, e.g. "const task = go work(x)" and "cancel(task)"`, argument);
63
91
  }
@@ -66,6 +94,16 @@ export class ChannelChecker extends GoroutineChecker {
66
94
  }
67
95
  return result;
68
96
  }
97
+ /** `stream(ch)`: a ReadableStream of the values of the channel, from the library if it has one. */
98
+ streamOf(channel, argument) {
99
+ if (chanDirection(channel) === 'send') {
100
+ this.error(`stream() reads a channel, and ${typeToString(channel)} only sends; give it a "chan T" or a "<-chan T"`, argument);
101
+ }
102
+ const template = this.options.library?.type('ReadableStream');
103
+ return template?.kind === 'object' && template.typeParams?.length === 1
104
+ ? instantiate(template, [elementOf(channel)])
105
+ : ANY;
106
+ }
69
107
  checkSelect(node) {
70
108
  this.dropNarrowing(assignedNames(node));
71
109
  const entry = this.flow;
@@ -9,6 +9,8 @@ export declare abstract class ExpressionChecker extends FunctionChecker {
9
9
  protected abstract checkGo(node: ast.GoExpression): Type;
10
10
  /** `chan<number>(10)`; implemented by ChannelChecker. */
11
11
  protected abstract checkChan(node: ast.ChanExpression): Type;
12
+ protected abstract checkStreamChan(node: ast.StreamChanExpression): Type;
13
+ protected abstract checkQueue(node: ast.QueueExpression): Type;
12
14
  /** `[]byte("text")`; implemented by BytesChecker. */
13
15
  protected abstract checkConversion(node: ast.ConversionExpression): Type;
14
16
  protected abstract checkMake(node: ast.MakeExpression): Type;
@@ -67,6 +67,10 @@ export class ExpressionChecker extends FunctionChecker {
67
67
  return this.checkGo(node);
68
68
  case 'ChanExpression':
69
69
  return this.checkChan(node);
70
+ case 'StreamChanExpression':
71
+ return this.checkStreamChan(node);
72
+ case 'QueueExpression':
73
+ return this.checkQueue(node);
70
74
  case 'ConversionExpression':
71
75
  return this.checkConversion(node);
72
76
  case 'MakeExpression':
@@ -137,6 +137,8 @@ export function callName(node) {
137
137
  }
138
138
  if (node.kind === 'GoExpression')
139
139
  return callName(node.call);
140
+ if (node.kind === 'StreamChanExpression')
141
+ return 'chan(stream)';
140
142
  if (node.kind !== 'CallExpression')
141
143
  return 'this expression';
142
144
  const { callee } = node;
@@ -0,0 +1,6 @@
1
+ import type * as ast from '../ast.ts';
2
+ import { ChannelChecker } from './channels.ts';
3
+ import { type Type } from './types.ts';
4
+ export declare abstract class QueueChecker extends ChannelChecker {
5
+ protected checkQueue(node: ast.QueueExpression): Type;
6
+ }
@@ -0,0 +1,24 @@
1
+ import { ChannelChecker } from "./channels.js";
2
+ import { isUntyped } from "./helpers.js";
3
+ import { arrayOf, queueOf, typeToString } from "./types.js";
4
+ // Queues: `queue<number>()` makes an empty one, `queue<number>(items)` one with the values of an
5
+ // array or of another iterable. The members of `queue T` are in types/queue.ts.
6
+ export class QueueChecker extends ChannelChecker {
7
+ checkQueue(node) {
8
+ const element = this.resolveType(node.element);
9
+ if (node.items) {
10
+ // An array literal gets the type of the values, so that `[]` and `[1, null]` fit.
11
+ const items = this.checkValue(node.items, arrayOf(element));
12
+ if (items.kind === 'object' && items.iterates) {
13
+ this.expectAssignable(items.iterates, element, node.items);
14
+ }
15
+ else if (items.kind === 'array' || isUntyped(items)) {
16
+ this.expectAssignable(items, arrayOf(element), node.items);
17
+ }
18
+ else {
19
+ this.error(`queue<T>(items) takes its first values as an array or another iterable, not ${typeToString(items)}, e.g. queue<number>([1, 2, 3])`, node.items);
20
+ }
21
+ }
22
+ return queueOf(element);
23
+ }
24
+ }
@@ -2,7 +2,7 @@ import { holdsBigInts, TYPED_ELEMENTS } from "../typed-arrays.js";
2
2
  import { chanOf } from "./channel.js";
3
3
  import { ClassChecker } from "./classes.js";
4
4
  import { CONTENT } from "./dom.js";
5
- import { ANY, arrayOf, BIGINT, bindParams, builtInTypedArray, classInstance, func, instantiate, isAssignable, isNullable, literal, nullable, NUMBER, substitute, typeToString, union, UNKNOWN, } from "./types.js";
5
+ import { ANY, arrayOf, BIGINT, bindParams, builtInTypedArray, classInstance, func, instantiate, isAssignable, isNullable, literal, nullable, NUMBER, queueOf, substitute, typeToString, union, UNKNOWN, } from "./types.js";
6
6
  // Type annotations as types: names, aliases, generics with their arguments; and the type a variable gets from its value.
7
7
  export class TypeResolver extends ClassChecker {
8
8
  resolveType(node) {
@@ -15,6 +15,8 @@ export class TypeResolver extends ClassChecker {
15
15
  }
16
16
  case 'ChanType':
17
17
  return chanOf(this.resolveType(node.element), node.direction);
18
+ case 'QueueType':
19
+ return queueOf(this.resolveType(node.element));
18
20
  case 'NullableType':
19
21
  return nullable(this.resolveType(node.type));
20
22
  case 'FuncType':
@@ -79,7 +81,9 @@ export class TypeResolver extends ClassChecker {
79
81
  if (entry === undefined) {
80
82
  this.error(this.lookupValue(name.name)
81
83
  ? `"${name.name}" is a value, not a type`
82
- : `unknown type "${name.name}"`, name);
84
+ : name.name === 'queue'
85
+ ? 'a queue type is written with the type of its values after it, e.g. "queue number"'
86
+ : `unknown type "${name.name}"`, name);
83
87
  return UNKNOWN;
84
88
  }
85
89
  const args = typeArgs.map((arg) => this.resolveType(arg));
@@ -1,4 +1,4 @@
1
- import { memberNames, typeToString } from "./types.js";
1
+ import { isQueue, memberNames, queueElement, typeToString } from "./types.js";
2
2
  // What can go to a worker and back: a worker gets a copy of each value (the structured clone of
3
3
  // postMessage), and a copy has the data of a value but not its methods.
4
4
  /** Buffers and their views: copied whole, or moved by `transfer(x)` (channels.ts). */
@@ -66,6 +66,9 @@ export function notCopied(type, seen = new Set()) {
66
66
  ? null
67
67
  : `a copy of ${typeToString(type)} would lose its class and methods`;
68
68
  case 'object': {
69
+ // A queue goes as its values, and the other thread makes a queue of them (src/runtime/threads.js).
70
+ if (isQueue(type))
71
+ return inner([queueElement(type)]);
69
72
  const name = type.instanceOf?.template.name ?? type.name;
70
73
  if (name !== null && COPIED.has(name))
71
74
  return inner(type.instanceOf?.args ?? []);
@@ -1,6 +1,7 @@
1
1
  import { TYPED_ARRAYS, typedArrayName } from "../../typed-arrays.js";
2
2
  import { ERROR_CLASS } from "./classes.js";
3
3
  import { nullable } from "./constructors.js";
4
+ import { isQueue, queueElement } from "./queue.js";
4
5
  // Types as text, for messages.
5
6
  /** How deep object types without a name are printed with their members. */
6
7
  const MAX_OBJECT_DEPTH = 3;
@@ -58,6 +59,12 @@ export function typeToString(type) {
58
59
  (type.name === 'chan' || type.name === 'chan<-' || type.name === '<-chan')) {
59
60
  return `${type.name} ${typeToString(type.instanceOf.args[0])}`;
60
61
  }
62
+ // `queue (string | number)`: the parentheses that the type is written with.
63
+ if (isQueue(type)) {
64
+ const element = queueElement(type);
65
+ const text = typeToString(element);
66
+ return `queue ${element.kind === 'union' && !element.name ? `(${text})` : text}`;
67
+ }
61
68
  if (type.instanceOf && type.name !== null) {
62
69
  return `${type.name}<${type.instanceOf.args.map(typeToString).join(', ')}>`;
63
70
  }
@@ -0,0 +1,6 @@
1
+ import { type ObjectType, type Type } from './model.ts';
2
+ export declare function queueOf(element: Type): ObjectType;
3
+ /** Whether a type is `queue T`. */
4
+ export declare function isQueue(type: Type): boolean;
5
+ /** The type of the values of a queue type. */
6
+ export declare function queueElement(queue: Type): Type;
@@ -0,0 +1,42 @@
1
+ import { func, method, nullable, property } from "./constructors.js";
2
+ import { instantiate } from "./generics.js";
3
+ import { NUMBER } from "./model.js";
4
+ // `queue T`, made by `queue<T>()`: values that go in and out at both ends without moving the
5
+ // others, as an array moves them on `shift()`. A queue type is an instance of a generic object
6
+ // with the members of an array that a queue keeps, and only a queue is assignable to it
7
+ // (relations.ts). The template is made on first use, since the modules of types import each other.
8
+ let template = null;
9
+ function queueTemplate() {
10
+ if (template)
11
+ return template;
12
+ const T = { kind: 'param', name: 'T' };
13
+ template = {
14
+ kind: 'object',
15
+ name: 'queue',
16
+ members: new Map([
17
+ ['length', property(NUMBER)],
18
+ ['push', method(func([], [NUMBER], { rest: T }))],
19
+ ['unshift', method(func([], [NUMBER], { rest: T }))],
20
+ // An empty queue gives null, as `pop()` of an empty array gives undefined.
21
+ ['shift', method(func([], [nullable(T)]))],
22
+ ['pop', method(func([], [nullable(T)]))],
23
+ ['at', method(func([NUMBER], [nullable(T)]))],
24
+ ['clear', method(func([], []))],
25
+ ]),
26
+ call: null,
27
+ iterates: T,
28
+ typeParams: [T],
29
+ };
30
+ return template;
31
+ }
32
+ export function queueOf(element) {
33
+ return instantiate(queueTemplate(), [element]);
34
+ }
35
+ /** Whether a type is `queue T`. */
36
+ export function isQueue(type) {
37
+ return type.kind === 'object' && template !== null && type.instanceOf?.template === template;
38
+ }
39
+ /** The type of the values of a queue type. */
40
+ export function queueElement(queue) {
41
+ return queue.instanceOf.args[0];
42
+ }
@@ -3,6 +3,7 @@ import { memberNames } from "./lazy.js";
3
3
  import { isNullable, literalBase, nonNull, union } from "./constructors.js";
4
4
  import { UNKNOWN, VOID, } from "./model.js";
5
5
  import { typedArrayOf, typeToString } from "./printing.js";
6
+ import { isQueue, queueElement } from "./queue.js";
6
7
  // Relations between types: assignability, comparison, equality, common types.
7
8
  /** Whether a value of type `source` can be used where `target` is expected. */
8
9
  export function isAssignable(source, target) {
@@ -75,6 +76,10 @@ export function assignable(source, target, assuming) {
75
76
  return missingMember(source, members, nested) === null;
76
77
  }
77
78
  case 'object': {
79
+ // Only a queue is a queue, as only an array is an array, and its values are checked as theirs.
80
+ if (isQueue(target)) {
81
+ return isQueue(source) && assignable(queueElement(source), queueElement(target), nested);
82
+ }
78
83
  // A channel both takes and gives values, so `chan number` is not a `chan ?number`, as in Go;
79
84
  // `chan any` is any channel, for `close(ch)`. A `chan T` is also a channel of one
80
85
  // direction, `chan<- T` or `<-chan T`, but not the other way round.
@@ -201,10 +206,13 @@ function missingMember(source, members, assuming) {
201
206
  }
202
207
  /** Why `source` is not assignable to an object or class `target`, e.g. `missing field "y"`. */
203
208
  export function explainMismatch(source, target) {
204
- // `Stack<number>` and `Stack<string>`: the type arguments already say what differs.
209
+ // `Stack<number>` and `Stack<string>`: the type arguments already say what differs; only a queue
210
+ // is a queue.
205
211
  if (source.kind === 'class' && target.kind === 'class' && source.info === target.info) {
206
212
  return null;
207
213
  }
214
+ if (isQueue(target))
215
+ return null;
208
216
  const members = target.kind === 'object'
209
217
  ? target.members
210
218
  : target.kind === 'class'
@@ -288,6 +296,9 @@ export function hasZeroValue(type) {
288
296
  case 'any':
289
297
  case 'unknown':
290
298
  return true;
299
+ // An empty queue, as `[]` is the zero value of an array.
300
+ case 'object':
301
+ return isQueue(type);
291
302
  default:
292
303
  return false;
293
304
  }
@@ -7,3 +7,4 @@ export * from './types/relations.ts';
7
7
  export * from './types/params.ts';
8
8
  export * from './types/printing.ts';
9
9
  export * from './types/bytes.ts';
10
+ export * from './types/queue.ts';
@@ -8,3 +8,4 @@ export * from "./types/relations.js";
8
8
  export * from "./types/params.js";
9
9
  export * from "./types/printing.js";
10
10
  export * from "./types/bytes.js";
11
+ export * from "./types/queue.js";
@@ -0,0 +1 @@
1
+ export declare const UNTYPED_GLOBALS: readonly string[];