rooklang 0.3.1 → 0.3.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +17 -4
- package/README.ru.md +17 -4
- package/dist/ast/expressions.d.ts +16 -1
- package/dist/ast/markup.d.ts +6 -1
- package/dist/browser.d.ts +5 -0
- package/dist/browser.js +1 -0
- package/dist/checker/assignments.d.ts +5 -0
- package/dist/checker/assignments.js +34 -8
- package/dist/checker/base.d.ts +2 -1
- package/dist/checker/base.js +4 -10
- package/dist/checker/builtins.js +4 -35
- package/dist/checker/bytes.d.ts +2 -2
- package/dist/checker/bytes.js +2 -2
- package/dist/checker/channels.d.ts +3 -0
- package/dist/checker/channels.js +39 -1
- package/dist/checker/context.d.ts +6 -0
- package/dist/checker/control-flow.d.ts +1 -0
- package/dist/checker/control-flow.js +17 -0
- package/dist/checker/error-results.d.ts +6 -0
- package/dist/checker/error-results.js +31 -0
- package/dist/checker/expressions.d.ts +2 -0
- package/dist/checker/expressions.js +4 -0
- package/dist/checker/helpers.js +2 -0
- package/dist/checker/queues.d.ts +6 -0
- package/dist/checker/queues.js +24 -0
- package/dist/checker/resolution.js +6 -2
- package/dist/checker/statements.d.ts +5 -0
- package/dist/checker/statements.js +19 -0
- package/dist/checker/transfer.js +4 -1
- package/dist/checker/types/printing.js +7 -0
- package/dist/checker/types/queue.d.ts +6 -0
- package/dist/checker/types/queue.js +42 -0
- package/dist/checker/types/relations.js +12 -1
- package/dist/checker/types.d.ts +1 -0
- package/dist/checker/types.js +1 -0
- package/dist/checker/untyped-globals.d.ts +1 -0
- package/dist/checker/untyped-globals.js +47 -0
- package/dist/codegen/attributes.js +0 -1
- package/dist/codegen/calls.d.ts +9 -0
- package/dist/codegen/calls.js +41 -1
- package/dist/codegen/component-values.js +0 -1
- package/dist/codegen/components.js +0 -1
- package/dist/codegen/control-flow.js +0 -3
- package/dist/codegen/control-statements.d.ts +9 -1
- package/dist/codegen/control-statements.js +51 -7
- package/dist/codegen/creation.d.ts +6 -8
- package/dist/codegen/creation.js +1 -10
- package/dist/codegen/early-returns.js +0 -1
- package/dist/codegen/elements.js +0 -3
- package/dist/codegen/emitter.d.ts +3 -5
- package/dist/codegen/emitter.js +0 -2
- package/dist/codegen/expressions.d.ts +4 -1
- package/dist/codegen/expressions.js +9 -13
- package/dist/codegen/functions.js +0 -2
- package/dist/codegen/js.d.ts +0 -7
- package/dist/codegen/js.js +33 -59
- package/dist/codegen/parallel.js +2 -4
- package/dist/codegen/refs.js +0 -1
- package/dist/codegen/runtime.d.ts +13 -0
- package/dist/codegen/runtime.js +93 -0
- package/dist/codegen/simple-statements.js +3 -1
- package/dist/codegen/styles.d.ts +1 -1
- package/dist/codegen/styles.js +2 -2
- package/dist/codegen/web-components.js +0 -1
- package/dist/compile.d.ts +7 -0
- package/dist/compile.js +1 -0
- package/dist/completion.js +2 -1
- package/dist/index.d.ts +2 -0
- package/dist/index.js +1 -0
- package/dist/loader.js +19 -6
- package/dist/parser/base.d.ts +3 -1
- package/dist/parser/expressions.js +9 -2
- package/dist/parser/literals.d.ts +7 -0
- package/dist/parser/literals.js +45 -1
- package/dist/parser/simple-statements.js +3 -0
- package/dist/parser/types.d.ts +1 -1
- package/dist/parser/types.js +10 -3
- package/dist/runtime/sources.d.ts +2 -0
- package/dist/runtime/sources.js +14 -0
- package/dist/runtime-module.d.ts +1 -0
- package/dist/runtime-module.js +3 -0
- package/dist/ts-types.d.ts +4 -0
- package/dist/ts-types.js +25 -3
- package/dist/vite.js +7 -1
- package/dist/walk-types.js +1 -0
- package/dist/walk.js +9 -0
- package/package.json +6 -4
- package/src/runtime/chan.js +277 -0
- package/src/runtime/core.js +23 -0
- package/src/runtime/go.js +340 -0
- package/src/runtime/index.js +13 -0
- package/src/runtime/markup.js +336 -0
- package/src/runtime/queue.js +118 -0
- package/src/runtime/select.js +170 -0
- package/src/runtime/streams.js +61 -0
- package/src/runtime/threads.js +240 -0
- package/src/runtime/timers.js +27 -0
- package/dist/codegen/chan-helpers.d.ts +0 -2
- package/dist/codegen/chan-helpers.js +0 -253
- package/dist/codegen/go-helpers.d.ts +0 -9
- package/dist/codegen/go-helpers.js +0 -307
- package/dist/codegen/helpers.d.ts +0 -10
- package/dist/codegen/helpers.js +0 -356
- package/dist/codegen/select-helpers.d.ts +0 -2
- package/dist/codegen/select-helpers.js +0 -135
- package/dist/codegen/thread-helpers.d.ts +0 -2
- 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
|
|
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
|
|
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.
|
|
112
|
-
|
|
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
|
|
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
|
|
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
|
-
языкового сервера с компилятором.
|
|
113
|
-
|
|
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.
|
package/dist/ast/markup.d.ts
CHANGED
|
@@ -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) => {
|
|
@@ -11,4 +11,9 @@ export declare abstract class AssignmentChecker extends InitializerChecker {
|
|
|
11
11
|
/** The type a value assigned to `target` must have. */
|
|
12
12
|
protected checkTarget(target: ast.Expression): Type;
|
|
13
13
|
protected checkReturn(node: ast.ReturnStatement): void;
|
|
14
|
+
/**
|
|
15
|
+
* `return null, err` in a function of `(User, error)`: null takes the place of a value only
|
|
16
|
+
* together with an error that is not null. Returns whether the null is checked here.
|
|
17
|
+
*/
|
|
18
|
+
private nullWithError;
|
|
14
19
|
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
|
+
import { guardedByError, unpackedResults } from "./error-results.js";
|
|
1
2
|
import { isUntyped, countValues, callName, isCall } from "./helpers.js";
|
|
2
3
|
import { InitializerChecker } from "./initializers.js";
|
|
3
|
-
import { BOOL, ANY, isAssignable, isNullable, nonNull, typeToString, UNKNOWN, } from "./types.js";
|
|
4
|
+
import { BOOL, ANY, isAssignable, isNullable, nonNull, nullable, typeToString, UNKNOWN, } from "./types.js";
|
|
4
5
|
// Where values go: assignments to variables, fields and elements, and `return`. Both unpack a call
|
|
5
6
|
// that returns several values.
|
|
6
7
|
export class AssignmentChecker extends InitializerChecker {
|
|
@@ -26,12 +27,15 @@ export class AssignmentChecker extends InitializerChecker {
|
|
|
26
27
|
this.error(`${prefix} ${callName(value)} returns ${countValues(returned)}`, value);
|
|
27
28
|
return Array(count).fill(UNKNOWN);
|
|
28
29
|
}
|
|
30
|
+
// `return f()` passes the results on as they are; variables get the values before an error
|
|
31
|
+
// as nullable (error-results.ts).
|
|
32
|
+
const types = context === 'return' ? type.types : unpackedResults(type.types);
|
|
29
33
|
if (declared) {
|
|
30
|
-
for (const element of
|
|
34
|
+
for (const element of types)
|
|
31
35
|
this.expectAssignable(element, declared, value);
|
|
32
|
-
return
|
|
36
|
+
return types.map(() => declared);
|
|
33
37
|
}
|
|
34
|
-
return
|
|
38
|
+
return types;
|
|
35
39
|
}
|
|
36
40
|
checkAssignment(node) {
|
|
37
41
|
const { targets, values, operator } = node;
|
|
@@ -187,11 +191,33 @@ export class AssignmentChecker extends InitializerChecker {
|
|
|
187
191
|
if (values.length !== expected.length) {
|
|
188
192
|
this.error(`wrong number of return values: expected ${expected.length}, got ${values.length}`, node);
|
|
189
193
|
}
|
|
190
|
-
values.
|
|
194
|
+
const types = values.map((value, i) => this.checkValue(value, expected[i] ?? null));
|
|
195
|
+
types.forEach((type, i) => {
|
|
191
196
|
const target = expected[i];
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
197
|
+
if (!target)
|
|
198
|
+
return;
|
|
199
|
+
if (type.kind === 'null' && this.nullWithError(expected, types, values, i))
|
|
200
|
+
return;
|
|
201
|
+
this.expectAssignable(type, target, values[i], ' in return');
|
|
195
202
|
});
|
|
196
203
|
}
|
|
204
|
+
/**
|
|
205
|
+
* `return null, err` in a function of `(User, error)`: null takes the place of a value only
|
|
206
|
+
* together with an error that is not null. Returns whether the null is checked here.
|
|
207
|
+
*/
|
|
208
|
+
nullWithError(expected, types, values, i) {
|
|
209
|
+
const last = expected.length - 1;
|
|
210
|
+
if (!guardedByError(expected).includes(i) || types.length !== expected.length)
|
|
211
|
+
return false;
|
|
212
|
+
const error = types[last];
|
|
213
|
+
const target = typeToString(expected[i]);
|
|
214
|
+
if (error.kind === 'null') {
|
|
215
|
+
this.error(`cannot return null with no error: make the result ${typeToString(nullable(expected[i]))} if the value may be null without an error`, values[i]);
|
|
216
|
+
}
|
|
217
|
+
else if (isNullable(error)) {
|
|
218
|
+
const name = values[last].kind === 'Identifier' ? values[last].name : 'err';
|
|
219
|
+
this.error(`null in place of ${target} needs an error that is not null: check it first, e.g. if ${name} != null { return null, ${name} }`, values[i]);
|
|
220
|
+
}
|
|
221
|
+
return true;
|
|
222
|
+
}
|
|
197
223
|
}
|
package/dist/checker/base.d.ts
CHANGED
|
@@ -18,6 +18,8 @@ export declare abstract class CheckerBase {
|
|
|
18
18
|
protected abstract checkTry(node: ast.TryStatement): void;
|
|
19
19
|
protected abstract forget(target: ast.Expression): void;
|
|
20
20
|
protected abstract narrowableBinding(node: ast.Expression): Binding | undefined;
|
|
21
|
+
/** The error for a value that may be null where it must not be. */
|
|
22
|
+
protected abstract nullError(node: ast.Expression): void;
|
|
21
23
|
protected abstract checkFunction(params: readonly ast.Parameter[], type: FunctionType, results: Type[] | null, body: ast.BlockStatement | ast.Expression, options?: {
|
|
22
24
|
isConstructor?: boolean;
|
|
23
25
|
isComponent?: boolean;
|
|
@@ -94,7 +96,6 @@ export declare abstract class CheckerBase {
|
|
|
94
96
|
constructor(program: ast.Program, options: CheckOptions);
|
|
95
97
|
protected error(message: string, node: ast.NodeBase): void;
|
|
96
98
|
protected expectAssignable(source: Type, target: Type, node: ast.NodeBase, context?: string): void;
|
|
97
|
-
protected nullError(node: ast.Expression): void;
|
|
98
99
|
protected withScope<T>(check: () => T): T;
|
|
99
100
|
protected withClass<T>(context: ClassContext | null, check: () => T): T;
|
|
100
101
|
protected lookupValue(name: string): Binding | undefined;
|
package/dist/checker/base.js
CHANGED
|
@@ -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 →
|
|
11
|
-
// Checker (checker.ts). Statements, expressions and markup check each other
|
|
12
|
-
// methods of later layers that earlier ones call are declared here as abstract.
|
|
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;
|
|
@@ -66,12 +66,6 @@ export class CheckerBase {
|
|
|
66
66
|
}
|
|
67
67
|
this.error(message, node);
|
|
68
68
|
}
|
|
69
|
-
nullError(node) {
|
|
70
|
-
const name = node.kind === 'Identifier' ? node.name : null;
|
|
71
|
-
this.error(name !== null
|
|
72
|
-
? `"${name}" may be null: check it with "if ${name} != null" or use "?."`
|
|
73
|
-
: 'this value may be null: check it for null or use "?."', node);
|
|
74
|
-
}
|
|
75
69
|
withScope(check) {
|
|
76
70
|
const saved = this.scope;
|
|
77
71
|
this.scope = new Scope(saved);
|
package/dist/checker/builtins.js
CHANGED
|
@@ -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
|
-
/**
|
|
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]),
|
package/dist/checker/bytes.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type * as ast from '../ast.ts';
|
|
2
|
-
import {
|
|
2
|
+
import { QueueChecker } from './queues.ts';
|
|
3
3
|
import { type Type } from './types.ts';
|
|
4
|
-
export declare abstract class BytesChecker extends
|
|
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. */
|
package/dist/checker/bytes.js
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
import {
|
|
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
|
|
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
|
}
|
package/dist/checker/channels.js
CHANGED
|
@@ -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;
|
|
@@ -87,6 +87,12 @@ export interface Binding {
|
|
|
87
87
|
type: Type | null;
|
|
88
88
|
/** For components, which exist only at compile time and are used as tags. */
|
|
89
89
|
component?: ComponentInfo;
|
|
90
|
+
/**
|
|
91
|
+
* `const user, err = loadUser(id)` of `(User, error)`: the error has the values that are not null
|
|
92
|
+
* once it is null, and each of them has the error (error-results.ts).
|
|
93
|
+
*/
|
|
94
|
+
guards?: Binding[];
|
|
95
|
+
guardedBy?: Binding;
|
|
90
96
|
}
|
|
91
97
|
export interface ComponentInfo {
|
|
92
98
|
node: ast.ComponentDeclaration;
|
|
@@ -17,6 +17,7 @@ export declare abstract class ControlFlowChecker extends StatementChecker {
|
|
|
17
17
|
protected narrow(node: ast.Expression, assumeTrue: boolean): Narrowing;
|
|
18
18
|
/** `x instanceof Date`: an instance of the class when true, the rest of a union when false. */
|
|
19
19
|
protected narrowInstanceof(left: ast.Expression, right: ast.Expression, assumeTrue: boolean): Narrowing;
|
|
20
|
+
protected nullError(node: ast.Expression): void;
|
|
20
21
|
protected narrowableBinding(node: ast.Expression): Binding | undefined;
|
|
21
22
|
/** An assignment to a variable ends its narrowing. */
|
|
22
23
|
protected forget(target: ast.Expression): void;
|
|
@@ -170,6 +170,14 @@ export class ControlFlowChecker extends StatementChecker {
|
|
|
170
170
|
if (binding?.type)
|
|
171
171
|
return [[binding, nonNull(this.flow.get(binding) ?? binding.type)]];
|
|
172
172
|
}
|
|
173
|
+
// `err == null` when true, `err != null` when false: the values returned with `err` are not
|
|
174
|
+
// null (error-results.ts).
|
|
175
|
+
if ((operator === '==' && assumeTrue) || (operator === '!=' && !assumeTrue)) {
|
|
176
|
+
const target = right.kind === 'NullLiteral' ? left : left.kind === 'NullLiteral' ? right : null;
|
|
177
|
+
const guards = target ? this.narrowableBinding(target)?.guards : undefined;
|
|
178
|
+
if (guards)
|
|
179
|
+
return guards.map((value) => [value, nonNull(this.flow.get(value) ?? value.type)]);
|
|
180
|
+
}
|
|
173
181
|
if (operator === 'instanceof')
|
|
174
182
|
return this.narrowInstanceof(left, right, assumeTrue);
|
|
175
183
|
if (operator !== '==' && operator !== '!=')
|
|
@@ -219,6 +227,15 @@ export class ControlFlowChecker extends StatementChecker {
|
|
|
219
227
|
: !isAssignable(member, instance));
|
|
220
228
|
return [[binding, union(isNullable(current) ? [...kept, NULL] : kept)]];
|
|
221
229
|
}
|
|
230
|
+
nullError(node) {
|
|
231
|
+
const name = node.kind === 'Identifier' ? node.name : null;
|
|
232
|
+
const error = name !== null ? this.lookupValue(name)?.guardedBy?.name : undefined;
|
|
233
|
+
this.error(error !== undefined
|
|
234
|
+
? `"${name}" may be null until "${error}" is checked: e.g. if ${error} != null { return }`
|
|
235
|
+
: name !== null
|
|
236
|
+
? `"${name}" may be null: check it with "if ${name} != null" or use "?."`
|
|
237
|
+
: 'this value may be null: check it for null or use "?."', node);
|
|
238
|
+
}
|
|
222
239
|
narrowableBinding(node) {
|
|
223
240
|
if (node.kind !== 'Identifier')
|
|
224
241
|
return undefined;
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { type Type } from './types.ts';
|
|
2
|
+
export declare function isErrorType(type: Type): boolean;
|
|
3
|
+
/** Positions of the results that may be null only together with an error: all before it. */
|
|
4
|
+
export declare function guardedByError(results: readonly Type[]): number[];
|
|
5
|
+
/** The types of the unpacked results: those before the error may be null. */
|
|
6
|
+
export declare function unpackedResults(results: readonly Type[]): Type[];
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { ERROR, isNullable, nullable, typesEqual } from "./types.js";
|
|
2
|
+
// Values returned with an error: in `(User, error)` the function may return `null, err` instead of
|
|
3
|
+
// a User, as long as the error is not null. So where the results are unpacked, the values before
|
|
4
|
+
// the error may be null, and they are not null once the error is found to be null. A result that
|
|
5
|
+
// is nullable itself, as in `(?User, error)`, may be null without an error too.
|
|
6
|
+
export function isErrorType(type) {
|
|
7
|
+
return typesEqual(type, ERROR);
|
|
8
|
+
}
|
|
9
|
+
/** Positions of the results that may be null only together with an error: all before it. */
|
|
10
|
+
export function guardedByError(results) {
|
|
11
|
+
if (results.length < 2 || !isErrorType(results.at(-1)))
|
|
12
|
+
return [];
|
|
13
|
+
return results.flatMap((type, i) => (i < results.length - 1 && canBeGuarded(type) ? [i] : []));
|
|
14
|
+
}
|
|
15
|
+
/** The types of the unpacked results: those before the error may be null. */
|
|
16
|
+
export function unpackedResults(results) {
|
|
17
|
+
const guarded = guardedByError(results);
|
|
18
|
+
return results.map((type, i) => (guarded.includes(i) ? nullable(type) : type));
|
|
19
|
+
}
|
|
20
|
+
function canBeGuarded(type) {
|
|
21
|
+
switch (type.kind) {
|
|
22
|
+
case 'any':
|
|
23
|
+
case 'unknown':
|
|
24
|
+
case 'void':
|
|
25
|
+
case 'never':
|
|
26
|
+
case 'null':
|
|
27
|
+
return false;
|
|
28
|
+
default:
|
|
29
|
+
return !isNullable(type);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
@@ -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':
|
package/dist/checker/helpers.js
CHANGED
|
@@ -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;
|