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.
- package/README.md +32 -7
- package/README.ru.md +32 -7
- package/dist/ast/declarations.d.ts +20 -1
- package/dist/ast/expressions.d.ts +31 -1
- package/dist/ast/markup.d.ts +6 -1
- package/dist/ast/statements.d.ts +25 -1
- package/dist/auto-import.d.ts +11 -0
- package/dist/auto-import.js +81 -0
- package/dist/breaks.d.ts +6 -0
- package/dist/breaks.js +36 -0
- package/dist/build.d.ts +7 -0
- package/dist/build.js +23 -2
- package/dist/checker/array-literals.d.ts +6 -0
- package/dist/checker/array-literals.js +59 -0
- package/dist/checker/assignments.d.ts +2 -0
- package/dist/checker/assignments.js +14 -0
- package/dist/checker/base.js +6 -6
- package/dist/checker/builtins.js +15 -5
- package/dist/checker/bytes.d.ts +2 -2
- package/dist/checker/bytes.js +2 -2
- package/dist/checker/calls.js +3 -0
- package/dist/checker/channel-objects.d.ts +3 -0
- package/dist/checker/channel-objects.js +40 -0
- package/dist/checker/channels.d.ts +1 -0
- package/dist/checker/channels.js +16 -2
- package/dist/checker/checker.d.ts +1 -1
- package/dist/checker/checker.js +19 -0
- package/dist/checker/class-members.d.ts +22 -0
- package/dist/checker/class-members.js +105 -0
- package/dist/checker/classes.d.ts +5 -4
- package/dist/checker/classes.js +41 -12
- package/dist/checker/closing.d.ts +21 -0
- package/dist/checker/closing.js +164 -0
- package/dist/checker/completion.d.ts +2 -2
- package/dist/checker/completion.js +2 -2
- package/dist/checker/context.d.ts +13 -0
- package/dist/checker/control-flow.d.ts +5 -0
- package/dist/checker/control-flow.js +17 -0
- package/dist/checker/declarations.js +1 -0
- package/dist/checker/decode.d.ts +8 -0
- package/dist/checker/decode.js +32 -0
- package/dist/checker/expressions.d.ts +5 -1
- package/dist/checker/expressions.js +13 -34
- package/dist/checker/flow.js +8 -4
- package/dist/checker/helpers.d.ts +1 -1
- package/dist/checker/helpers.js +9 -2
- package/dist/checker/iterables.d.ts +3 -1
- package/dist/checker/iterables.js +13 -0
- package/dist/checker/iterators.d.ts +4 -1
- package/dist/checker/iterators.js +12 -4
- package/dist/checker/json.d.ts +5 -0
- package/dist/checker/json.js +92 -0
- package/dist/checker/loops.js +23 -3
- package/dist/checker/members.js +7 -0
- package/dist/checker/operators.d.ts +2 -2
- package/dist/checker/operators.js +7 -5
- package/dist/checker/queues.d.ts +2 -2
- package/dist/checker/queues.js +2 -2
- package/dist/checker/resolution.js +5 -3
- package/dist/checker/signals.d.ts +8 -0
- package/dist/checker/signals.js +48 -0
- package/dist/checker/topics.d.ts +6 -0
- package/dist/checker/topics.js +17 -0
- package/dist/checker/transfer.d.ts +3 -1
- package/dist/checker/transfer.js +25 -5
- package/dist/checker/try.d.ts +6 -0
- package/dist/checker/try.js +19 -0
- package/dist/checker/types/constructors.d.ts +1 -1
- package/dist/checker/types/constructors.js +2 -2
- package/dist/checker/types/generics.js +1 -1
- package/dist/checker/types/iterables.d.ts +13 -0
- package/dist/checker/types/iterables.js +52 -0
- package/dist/checker/types/model.d.ts +25 -0
- package/dist/checker/types/params.js +22 -3
- package/dist/checker/types/printing.js +4 -3
- package/dist/checker/types/relations.js +19 -25
- package/dist/checker/types/topic.d.ts +6 -0
- package/dist/checker/types/topic.js +23 -0
- package/dist/checker/types.d.ts +2 -0
- package/dist/checker/types.js +2 -0
- package/dist/cli.js +18 -36
- package/dist/codegen/calls.d.ts +23 -0
- package/dist/codegen/calls.js +88 -7
- package/dist/codegen/control-statements.d.ts +10 -2
- package/dist/codegen/control-statements.js +56 -19
- package/dist/codegen/decode.d.ts +14 -0
- package/dist/codegen/decode.js +93 -0
- package/dist/codegen/emitter.d.ts +10 -41
- package/dist/codegen/emitter.js +9 -4
- package/dist/codegen/expressions.d.ts +9 -0
- package/dist/codegen/expressions.js +15 -24
- package/dist/codegen/js.d.ts +2 -2
- package/dist/codegen/js.js +5 -2
- package/dist/codegen/options.d.ts +40 -0
- package/dist/codegen/options.js +1 -0
- package/dist/codegen/runtime.d.ts +7 -3
- package/dist/codegen/runtime.js +27 -13
- package/dist/codegen/simple-statements.d.ts +10 -4
- package/dist/codegen/simple-statements.js +40 -15
- package/dist/codegen/statements.js +64 -4
- package/dist/codegen/try.d.ts +5 -0
- package/dist/codegen/try.js +40 -0
- package/dist/compile.d.ts +9 -0
- package/dist/compile.js +19 -6
- package/dist/completion.d.ts +4 -2
- package/dist/completion.js +10 -5
- package/dist/declarations.js +32 -6
- package/dist/decorator-copy.js +5 -2
- package/dist/goroutines.d.ts +1 -1
- package/dist/goroutines.js +5 -4
- package/dist/index.d.ts +6 -2
- package/dist/index.js +14 -5
- package/dist/lexer/base.d.ts +2 -0
- package/dist/lexer/base.js +6 -1
- package/dist/lexer/lexer.js +3 -0
- package/dist/lexer/literals.d.ts +5 -0
- package/dist/lexer/literals.js +51 -1
- package/dist/lexer/markup.js +3 -4
- package/dist/lexer/tables.js +2 -0
- package/dist/lexer/token.d.ts +4 -3
- package/dist/lexer/token.js +0 -2
- package/dist/loader.js +30 -8
- package/dist/parser/base.d.ts +4 -0
- package/dist/parser/base.js +2 -1
- package/dist/parser/classes.d.ts +2 -0
- package/dist/parser/classes.js +88 -23
- package/dist/parser/control-flow.d.ts +3 -1
- package/dist/parser/control-flow.js +21 -24
- package/dist/parser/declarations.d.ts +7 -4
- package/dist/parser/declarations.js +45 -6
- package/dist/parser/expressions.js +27 -3
- package/dist/parser/jumps.d.ts +11 -0
- package/dist/parser/jumps.js +77 -0
- package/dist/parser/literals.d.ts +9 -0
- package/dist/parser/literals.js +45 -6
- package/dist/parser/markup.js +1 -1
- package/dist/parser/simple-statements.d.ts +1 -1
- package/dist/parser/simple-statements.js +16 -9
- package/dist/parser/statements.d.ts +0 -3
- package/dist/parser/statements.js +22 -25
- package/dist/parser/syntax.d.ts +11 -1
- package/dist/parser/syntax.js +16 -2
- package/dist/parser/types.js +8 -3
- package/dist/project-modules.d.ts +12 -0
- package/dist/project-modules.js +106 -0
- package/dist/run-file.d.ts +6 -0
- package/dist/run-file.js +111 -0
- package/dist/runtime/sources.js +11 -5
- package/dist/source-map.d.ts +17 -0
- package/dist/source-map.js +75 -0
- package/dist/ts-types.js +4 -1
- package/dist/typescript/convert-objects.d.ts +2 -1
- package/dist/typescript/convert-objects.js +18 -9
- package/dist/vite.js +12 -4
- package/dist/walk-types.js +1 -0
- package/dist/walk.d.ts +0 -2
- package/dist/walk.js +11 -28
- package/package.json +1 -1
- package/src/runtime/chan.js +15 -5
- package/src/runtime/channel.js +28 -0
- package/src/runtime/classes.js +32 -0
- package/src/runtime/core.js +10 -1
- package/src/runtime/decode.js +190 -0
- package/src/runtime/go.js +3 -0
- package/src/runtime/index.js +6 -0
- package/src/runtime/select.js +14 -9
- package/src/runtime/signals.js +59 -0
- package/src/runtime/threads.js +32 -5
- package/src/runtime/topic.js +118 -0
- package/src/runtime/try.js +45 -0
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { describe, FUNCTION_BODY } from "./syntax.js";
|
|
1
|
+
import { JumpParser } from "./jumps.js";
|
|
2
|
+
import { describe, FUNCTION_BODY, VISIBILITIES } from "./syntax.js";
|
|
3
3
|
// Imports and exports, functions and components with their parameters and results.
|
|
4
|
-
export class DeclarationParser extends
|
|
4
|
+
export class DeclarationParser extends JumpParser {
|
|
5
5
|
parseImport() {
|
|
6
6
|
const start = this.next().start;
|
|
7
7
|
if (!this.context.topLevel) {
|
|
@@ -111,9 +111,10 @@ export class DeclarationParser extends ControlFlowParser {
|
|
|
111
111
|
}
|
|
112
112
|
/**
|
|
113
113
|
* `(a, b number, c string)`. Names without a type take the type of the next typed name, as in
|
|
114
|
-
* Go; only arrow functions may leave types out entirely.
|
|
114
|
+
* Go; only arrow functions may leave types out entirely. The parameters of a constructor can be
|
|
115
|
+
* properties: `(private name string)`.
|
|
115
116
|
*/
|
|
116
|
-
parseParams(typesOptional, allowDefaults = false, allowRest = false) {
|
|
117
|
+
parseParams(typesOptional, allowDefaults = false, allowRest = false, allowProperties = false) {
|
|
117
118
|
this.expect('(');
|
|
118
119
|
const params = [];
|
|
119
120
|
let untyped = 0;
|
|
@@ -122,10 +123,19 @@ export class DeclarationParser extends ControlFlowParser {
|
|
|
122
123
|
params.push(this.parseRestParam(allowRest));
|
|
123
124
|
break;
|
|
124
125
|
}
|
|
126
|
+
const paramStart = this.peek().start;
|
|
127
|
+
const property = this.parsePropertyModifiers(allowProperties);
|
|
125
128
|
const name = this.parseIdentifier('parameter name');
|
|
126
129
|
const type = this.canStartType() ? this.parseType() : null;
|
|
127
130
|
const defaultValue = allowDefaults && this.accept('=') ? this.parseExpression() : null;
|
|
128
|
-
params.push({
|
|
131
|
+
params.push({
|
|
132
|
+
kind: 'Parameter',
|
|
133
|
+
name,
|
|
134
|
+
type,
|
|
135
|
+
defaultValue,
|
|
136
|
+
...(property ? { property } : {}),
|
|
137
|
+
...this.span(paramStart),
|
|
138
|
+
});
|
|
129
139
|
if (type === null) {
|
|
130
140
|
untyped++;
|
|
131
141
|
}
|
|
@@ -144,6 +154,35 @@ export class DeclarationParser extends ControlFlowParser {
|
|
|
144
154
|
}
|
|
145
155
|
return params;
|
|
146
156
|
}
|
|
157
|
+
/** `private`, `readonly` or both before a parameter that is a property of its class. */
|
|
158
|
+
parsePropertyModifiers(allowed) {
|
|
159
|
+
let visibility = null;
|
|
160
|
+
let readonly = false;
|
|
161
|
+
const start = this.peek().start;
|
|
162
|
+
// Elsewhere `readonly number` is a parameter named readonly, and `private x number` an error.
|
|
163
|
+
while (this.check('Identifier') &&
|
|
164
|
+
(VISIBILITIES.has(this.peek().text) || this.peek().text === 'readonly') &&
|
|
165
|
+
this.peek(1).kind === 'Identifier' &&
|
|
166
|
+
(allowed || this.peek(2).kind === 'Identifier')) {
|
|
167
|
+
const modifier = this.next();
|
|
168
|
+
if (modifier.text === 'readonly') {
|
|
169
|
+
if (readonly)
|
|
170
|
+
this.error('duplicate "readonly" modifier', modifier.start, modifier.end);
|
|
171
|
+
readonly = true;
|
|
172
|
+
}
|
|
173
|
+
else {
|
|
174
|
+
if (visibility)
|
|
175
|
+
this.error('duplicate visibility modifier', modifier.start, modifier.end);
|
|
176
|
+
visibility = modifier.text;
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
if (visibility === null && !readonly)
|
|
180
|
+
return null;
|
|
181
|
+
if (!allowed) {
|
|
182
|
+
this.error('only the parameters of a constructor can be properties, e.g. constructor(private name string)', start, this.span(start).end);
|
|
183
|
+
}
|
|
184
|
+
return { visibility: visibility ?? 'public', ...(readonly ? { readonly: true } : {}) };
|
|
185
|
+
}
|
|
147
186
|
/** `...attrs`: the attributes that a component passes on to one of its elements. */
|
|
148
187
|
parseRestParam(allowed) {
|
|
149
188
|
const start = this.next().start;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { BINARY_PRECEDENCE, isBinaryOperator, isUnaryOperator,
|
|
1
|
+
import { BINARY_PRECEDENCE, isBinaryOperator, isUnaryOperator, describe } from "./syntax.js";
|
|
2
2
|
import { TypeParser } from "./types.js";
|
|
3
3
|
// Expressions: operators with their precedence, member access, calls and indexing.
|
|
4
4
|
export class ExpressionParser extends TypeParser {
|
|
@@ -71,6 +71,12 @@ export class ExpressionParser extends TypeParser {
|
|
|
71
71
|
if (token.kind === '++' || token.kind === '--') {
|
|
72
72
|
this.fail(`"${token.kind}" must follow the variable: "i${token.kind}"`);
|
|
73
73
|
}
|
|
74
|
+
// `try` takes the whole expression after it, as `try { }` takes its block.
|
|
75
|
+
if (token.kind === 'try') {
|
|
76
|
+
this.next();
|
|
77
|
+
const argument = this.parseExpression();
|
|
78
|
+
return { kind: 'TryExpression', argument, start: token.start, end: argument.end };
|
|
79
|
+
}
|
|
74
80
|
if (token.kind === 'go') {
|
|
75
81
|
this.next();
|
|
76
82
|
if (this.check('for')) {
|
|
@@ -181,6 +187,16 @@ export class ExpressionParser extends TypeParser {
|
|
|
181
187
|
if (queue)
|
|
182
188
|
return queue;
|
|
183
189
|
}
|
|
190
|
+
if (this.checkWord('topic') && this.peek(1).kind === '<') {
|
|
191
|
+
const topic = this.parseTopic();
|
|
192
|
+
if (topic)
|
|
193
|
+
return topic;
|
|
194
|
+
}
|
|
195
|
+
if (this.checkWord('decode') && this.peek(1).kind === '<') {
|
|
196
|
+
const decode = this.parseDecode();
|
|
197
|
+
if (decode)
|
|
198
|
+
return decode;
|
|
199
|
+
}
|
|
184
200
|
this.next();
|
|
185
201
|
return { kind: 'Identifier', name: token.text, start, end };
|
|
186
202
|
case 'Number':
|
|
@@ -191,6 +207,16 @@ export class ExpressionParser extends TypeParser {
|
|
|
191
207
|
return { kind: 'BigIntLiteral', raw: token.text, start, end };
|
|
192
208
|
case 'String':
|
|
193
209
|
return this.parseStringLiteral();
|
|
210
|
+
case 'RegExp': {
|
|
211
|
+
const { value } = this.next();
|
|
212
|
+
return {
|
|
213
|
+
kind: 'RegExpLiteral',
|
|
214
|
+
pattern: value,
|
|
215
|
+
flags: token.text.slice(value.length + 2),
|
|
216
|
+
start,
|
|
217
|
+
end,
|
|
218
|
+
};
|
|
219
|
+
}
|
|
194
220
|
case 'Template':
|
|
195
221
|
case 'TemplateHead':
|
|
196
222
|
return this.parseTemplate(token);
|
|
@@ -254,8 +280,6 @@ export class ExpressionParser extends TypeParser {
|
|
|
254
280
|
case '@':
|
|
255
281
|
return this.parseDecoratorMember();
|
|
256
282
|
default:
|
|
257
|
-
if (RESERVED.has(token.kind))
|
|
258
|
-
this.fail(`"${token.text}" is reserved for future use`);
|
|
259
283
|
return this.fail(`expected expression, found ${describe(token)}`);
|
|
260
284
|
}
|
|
261
285
|
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type * as ast from '../ast.ts';
|
|
2
|
+
import { ControlFlowParser } from './control-flow.ts';
|
|
3
|
+
export declare abstract class JumpParser extends ControlFlowParser {
|
|
4
|
+
/** `outer: for`, `outer: switch` or `outer: select`: the statements that break can leave. */
|
|
5
|
+
protected parseLabeled(): ast.Statement;
|
|
6
|
+
protected parseReturn(): ast.ReturnStatement;
|
|
7
|
+
/** `}` + newline + `else`: the newline ends the `if`, so explain the error instead of failing. */
|
|
8
|
+
protected parseBreakOrContinue(): ast.BreakStatement | ast.ContinueStatement;
|
|
9
|
+
/** `break outer` leaves an enclosing statement with that label; `continue outer`, a loop. */
|
|
10
|
+
private checkLabel;
|
|
11
|
+
}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import { ControlFlowParser } from "./control-flow.js";
|
|
2
|
+
// The statements that jump, `return`, `break` and `continue`, and the labels that `break` and
|
|
3
|
+
// `continue` name: `outer: for ... { for ... { continue outer } }`.
|
|
4
|
+
export class JumpParser extends ControlFlowParser {
|
|
5
|
+
/** `outer: for`, `outer: switch` or `outer: select`: the statements that break can leave. */
|
|
6
|
+
parseLabeled() {
|
|
7
|
+
const label = this.parseIdentifier('label');
|
|
8
|
+
this.expect(':');
|
|
9
|
+
const goFor = this.check('go') && this.peek(1).kind === 'for';
|
|
10
|
+
const loop = this.check('for');
|
|
11
|
+
if (goFor || (!loop && !this.check('switch') && !this.checkWord('select'))) {
|
|
12
|
+
this.error(goFor
|
|
13
|
+
? '"go for" cannot have a label: its parts run at once in workers, which break and continue cannot leave'
|
|
14
|
+
: 'a label goes before for, switch or select: only they can be left with break, e.g. outer: for ...', label.start, label.end);
|
|
15
|
+
return this.parseStatement();
|
|
16
|
+
}
|
|
17
|
+
if (this.context.labels.some((each) => each.name === label.name)) {
|
|
18
|
+
this.error(`label "${label.name}" is already used by an enclosing statement`, label.start, label.end);
|
|
19
|
+
}
|
|
20
|
+
const entry = { name: label.name, loop, used: false };
|
|
21
|
+
const statement = this.withContext({ labels: [...this.context.labels, entry] }, () => this.parseStatement());
|
|
22
|
+
if (!entry.used) {
|
|
23
|
+
this.error(`label "${label.name}" is not used: leave the statement with "break ${label.name}" or remove the label`, label.start, label.end);
|
|
24
|
+
}
|
|
25
|
+
return { ...statement, label, start: label.start };
|
|
26
|
+
}
|
|
27
|
+
parseReturn() {
|
|
28
|
+
const start = this.next().start;
|
|
29
|
+
if (this.context.inGoFor) {
|
|
30
|
+
this.error('"return" cannot leave "go for": its body runs in workers; skip an element with "continue"', start, this.span(start).end);
|
|
31
|
+
}
|
|
32
|
+
else if (!this.context.inFunction) {
|
|
33
|
+
this.error('"return" outside of a function', start, this.span(start).end);
|
|
34
|
+
}
|
|
35
|
+
else if (this.context.inDefer) {
|
|
36
|
+
this.error('"return" is not allowed inside "defer"', start, this.span(start).end);
|
|
37
|
+
}
|
|
38
|
+
const values = this.check(';') || this.check('}') || this.check('EOF') ? [] : this.parseExpressionList();
|
|
39
|
+
const statement = { kind: 'ReturnStatement', values, ...this.span(start) };
|
|
40
|
+
this.endStatement();
|
|
41
|
+
return statement;
|
|
42
|
+
}
|
|
43
|
+
/** `}` + newline + `else`: the newline ends the `if`, so explain the error instead of failing. */
|
|
44
|
+
parseBreakOrContinue() {
|
|
45
|
+
const token = this.next();
|
|
46
|
+
const word = token.kind === 'break' ? 'break' : 'continue';
|
|
47
|
+
const label = this.check('Identifier') ? this.parseIdentifier('label') : null;
|
|
48
|
+
const span = { start: token.start, end: label?.end ?? token.end };
|
|
49
|
+
if (label)
|
|
50
|
+
this.checkLabel(word, label);
|
|
51
|
+
else if (word === 'continue' && !this.context.inLoop) {
|
|
52
|
+
this.error('"continue" outside of a loop', token.start, token.end);
|
|
53
|
+
}
|
|
54
|
+
else if (word === 'break' && !this.context.inBreakable) {
|
|
55
|
+
this.error(this.context.inGoFor
|
|
56
|
+
? '"break" cannot stop "go for": its parts run at once in workers; skip an element with "continue"'
|
|
57
|
+
: '"break" outside of a loop or switch', token.start, token.end);
|
|
58
|
+
}
|
|
59
|
+
this.endStatement();
|
|
60
|
+
const kind = word === 'break' ? 'BreakStatement' : 'ContinueStatement';
|
|
61
|
+
return { kind, ...(label ? { label } : {}), ...span };
|
|
62
|
+
}
|
|
63
|
+
/** `break outer` leaves an enclosing statement with that label; `continue outer`, a loop. */
|
|
64
|
+
checkLabel(word, label) {
|
|
65
|
+
const entry = this.context.labels.findLast((each) => each.name === label.name);
|
|
66
|
+
if (!entry) {
|
|
67
|
+
this.error(this.context.inGoFor
|
|
68
|
+
? `"${word} ${label.name}" cannot leave "go for": its parts run at once in workers`
|
|
69
|
+
: `label "${label.name}" is not defined: put it before an enclosing loop, e.g. ${label.name}: for ...`, label.start, label.end);
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
72
|
+
entry.used = true;
|
|
73
|
+
if (word === 'continue' && !entry.loop) {
|
|
74
|
+
this.error(`"continue ${label.name}" needs a loop, and ${label.name} labels a switch or select: leave it with "break ${label.name}"`, label.start, label.end);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
}
|
|
@@ -19,6 +19,15 @@ export declare abstract class LiteralParser extends ExpressionParser {
|
|
|
19
19
|
* that has no type and `(` after it is a comparison: then this gives null and reads nothing.
|
|
20
20
|
*/
|
|
21
21
|
protected parseQueue(): ast.QueueExpression | null;
|
|
22
|
+
/** `topic<CartEvent>("cart")`; `topic < n` is a comparison, as `queue < n` is. */
|
|
23
|
+
protected parseTopic(): ast.TopicExpression | null;
|
|
24
|
+
/** `decode<User>(body)`; `decode < n` is a comparison, as `topic < n` is. */
|
|
25
|
+
protected parseDecode(): ast.DecodeExpression | null;
|
|
26
|
+
/**
|
|
27
|
+
* `<T>(` after `queue`, `topic` or `decode`: the type, with `(` read. Without `(` after the
|
|
28
|
+
* type, gives null and reads nothing, so that `queue < n` stays a comparison.
|
|
29
|
+
*/
|
|
30
|
+
private parseMadeType;
|
|
22
31
|
/** `chan(stream)` or `chan(stream, size)`, after `chan`. */
|
|
23
32
|
protected parseStreamChan(start: number): ast.StreamChanExpression;
|
|
24
33
|
/** `[]byte("text")`: a type, then its value in parentheses. */
|
package/dist/parser/literals.js
CHANGED
|
@@ -133,18 +133,60 @@ export class LiteralParser extends ExpressionParser {
|
|
|
133
133
|
* that has no type and `(` after it is a comparison: then this gives null and reads nothing.
|
|
134
134
|
*/
|
|
135
135
|
parseQueue() {
|
|
136
|
+
const start = this.peek().start;
|
|
137
|
+
const element = this.parseMadeType();
|
|
138
|
+
if (!element)
|
|
139
|
+
return null;
|
|
140
|
+
const items = this.check(')') ? null : this.nested(() => this.parseExpression());
|
|
141
|
+
this.accept(',');
|
|
142
|
+
this.expect(')', '")": a queue takes its first values as one array, e.g. queue<number>([1, 2])');
|
|
143
|
+
return { kind: 'QueueExpression', element, items, ...this.span(start) };
|
|
144
|
+
}
|
|
145
|
+
/** `topic<CartEvent>("cart")`; `topic < n` is a comparison, as `queue < n` is. */
|
|
146
|
+
parseTopic() {
|
|
147
|
+
const start = this.peek().start;
|
|
148
|
+
const element = this.parseMadeType();
|
|
149
|
+
if (!element)
|
|
150
|
+
return null;
|
|
151
|
+
if (this.check(')')) {
|
|
152
|
+
this.fail('a topic takes its name, the same in every tab, e.g. topic<CartEvent>("cart")');
|
|
153
|
+
}
|
|
154
|
+
const name = this.nested(() => this.parseExpression());
|
|
155
|
+
this.accept(',');
|
|
156
|
+
this.expect(')', '")": a topic takes only its name, e.g. topic<CartEvent>("cart")');
|
|
157
|
+
return { kind: 'TopicExpression', element, name, ...this.span(start) };
|
|
158
|
+
}
|
|
159
|
+
/** `decode<User>(body)`; `decode < n` is a comparison, as `topic < n` is. */
|
|
160
|
+
parseDecode() {
|
|
161
|
+
const start = this.peek().start;
|
|
162
|
+
const type = this.parseMadeType();
|
|
163
|
+
if (!type)
|
|
164
|
+
return null;
|
|
165
|
+
if (this.check(')')) {
|
|
166
|
+
this.fail('decode takes JSON text or a value from JS, e.g. decode<User>(body)');
|
|
167
|
+
}
|
|
168
|
+
const argument = this.nested(() => this.parseExpression());
|
|
169
|
+
this.accept(',');
|
|
170
|
+
this.expect(')', '")": decode takes one argument, e.g. decode<User>(body)');
|
|
171
|
+
return { kind: 'DecodeExpression', type, argument, ...this.span(start) };
|
|
172
|
+
}
|
|
173
|
+
/**
|
|
174
|
+
* `<T>(` after `queue`, `topic` or `decode`: the type, with `(` read. Without `(` after the
|
|
175
|
+
* type, gives null and reads nothing, so that `queue < n` stays a comparison.
|
|
176
|
+
*/
|
|
177
|
+
parseMadeType() {
|
|
136
178
|
const { pos } = this;
|
|
137
179
|
// Closing the type may split a `>>` token, and errors are reported as the type is read.
|
|
138
180
|
const tokens = this.tokens.slice(pos);
|
|
139
181
|
const reported = this.diagnostics.length;
|
|
140
|
-
const start = this.next().start;
|
|
141
182
|
let element;
|
|
183
|
+
this.next();
|
|
142
184
|
try {
|
|
143
185
|
this.expect('<');
|
|
144
186
|
element = this.parseType();
|
|
145
187
|
this.expectTypeClose('">" after the type of the values');
|
|
146
188
|
if (!this.check('('))
|
|
147
|
-
throw new ParseError('not a
|
|
189
|
+
throw new ParseError('not a call');
|
|
148
190
|
}
|
|
149
191
|
catch (error) {
|
|
150
192
|
if (!(error instanceof ParseError))
|
|
@@ -155,10 +197,7 @@ export class LiteralParser extends ExpressionParser {
|
|
|
155
197
|
return null;
|
|
156
198
|
}
|
|
157
199
|
this.next();
|
|
158
|
-
|
|
159
|
-
this.accept(',');
|
|
160
|
-
this.expect(')', '")": a queue takes its first values as one array, e.g. queue<number>([1, 2])');
|
|
161
|
-
return { kind: 'QueueExpression', element, items, ...this.span(start) };
|
|
200
|
+
return element;
|
|
162
201
|
}
|
|
163
202
|
/** `chan(stream)` or `chan(stream, size)`, after `chan`. */
|
|
164
203
|
parseStreamChan(start) {
|
package/dist/parser/markup.js
CHANGED
|
@@ -136,7 +136,7 @@ export class MarkupParser extends LiteralParser {
|
|
|
136
136
|
parseEventHandler() {
|
|
137
137
|
const start = this.peek().start;
|
|
138
138
|
const body = [];
|
|
139
|
-
this.withContext({ noObjectLiteral: false, inFunction: true, inLoop: false, inBreakable: false }, () => {
|
|
139
|
+
this.withContext({ noObjectLiteral: false, inFunction: true, inLoop: false, inBreakable: false, labels: [] }, () => {
|
|
140
140
|
do {
|
|
141
141
|
if (this.check('}'))
|
|
142
142
|
break;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type * as ast from '../ast.ts';
|
|
2
2
|
import { ParserBase } from './base.ts';
|
|
3
3
|
export declare abstract class SimpleStatementParser extends ParserBase {
|
|
4
|
-
/** `await`, `<-ch` and `
|
|
4
|
+
/** `await`, `<-ch` and `select` wait: only in async functions and at the top level. */
|
|
5
5
|
protected requireAsync(what: string, start: number, end: number): void;
|
|
6
6
|
/** `x := 1` is a common habit from Go. */
|
|
7
7
|
protected rejectColonEquals(): void;
|
|
@@ -1,15 +1,13 @@
|
|
|
1
1
|
import { ParserBase } from "./base.js";
|
|
2
|
-
import { isAssignmentOperator, describe, plural } from "./syntax.js";
|
|
2
|
+
import { cannotWait, isAssignmentOperator, describe, plural } from "./syntax.js";
|
|
3
3
|
// Simple statements, which `for` headers allow too: declarations of variables, assignments, `i++`
|
|
4
4
|
// and calls.
|
|
5
5
|
export class SimpleStatementParser extends ParserBase {
|
|
6
|
-
/** `await`, `<-ch` and `
|
|
6
|
+
/** `await`, `<-ch` and `select` wait: only in async functions and at the top level. */
|
|
7
7
|
requireAsync(what, start, end) {
|
|
8
8
|
if (this.context.inAsync)
|
|
9
9
|
return;
|
|
10
|
-
this.error(this.context.inDefer
|
|
11
|
-
? `"${what}" is not allowed inside "defer": deferred code runs when the function ends`
|
|
12
|
-
: `"${what}" is allowed only in async functions and at the top level of a module; mark the function, e.g. "async func load() { ... }"`, start, end);
|
|
10
|
+
this.error(cannotWait(what, this.context.inDefer), start, end);
|
|
13
11
|
}
|
|
14
12
|
/** `x := 1` is a common habit from Go. */
|
|
15
13
|
rejectColonEquals() {
|
|
@@ -49,12 +47,15 @@ export class SimpleStatementParser extends ParserBase {
|
|
|
49
47
|
this.checkArity(targets.length, values, start);
|
|
50
48
|
return { kind: 'AssignmentStatement', operator, targets, values, ...this.span(start) };
|
|
51
49
|
}
|
|
52
|
-
// `ch <- value`: the value goes into the channel.
|
|
50
|
+
// `ch <- value`: the value goes into the channel. A send to a channel waits, and one to a
|
|
51
|
+
// topic does not, so whether it may be here is for the checker, which knows the type.
|
|
53
52
|
if (this.check('<-')) {
|
|
54
|
-
const
|
|
55
|
-
this.requireAsync('<-', arrow.start, arrow.end);
|
|
53
|
+
const { start: from, end } = this.next();
|
|
56
54
|
const value = this.parseExpression();
|
|
57
|
-
|
|
55
|
+
const cannotWait = this.context.inAsync
|
|
56
|
+
? {}
|
|
57
|
+
: { cannotWait: { inDefer: this.context.inDefer, start: from, end } };
|
|
58
|
+
return { kind: 'SendStatement', channel: first, value, ...cannotWait, ...this.span(start) };
|
|
58
59
|
}
|
|
59
60
|
if (this.check('++') || this.check('--')) {
|
|
60
61
|
const operator = this.next().kind === '++' ? '++' : '--';
|
|
@@ -90,6 +91,12 @@ export class SimpleStatementParser extends ParserBase {
|
|
|
90
91
|
// `chunks, failed = chan(res.body)`: the channel of a stream and that of its error.
|
|
91
92
|
if (values.length === 1 && value?.kind === 'StreamChanExpression')
|
|
92
93
|
return;
|
|
94
|
+
// `user, err = decode<User>(body)`: the value and the error.
|
|
95
|
+
if (values.length === 1 && value?.kind === 'DecodeExpression')
|
|
96
|
+
return;
|
|
97
|
+
// `data, err = try JSON.parse(text)`: the value and the error, as from a call.
|
|
98
|
+
if (values.length === 1 && value?.kind === 'TryExpression')
|
|
99
|
+
return;
|
|
93
100
|
this.error(`assignment mismatch: ${plural(targets, 'variable')} but ${plural(values.length, 'value')}`, start, this.span(start).end);
|
|
94
101
|
}
|
|
95
102
|
/** `delete scores[name]`: removes a field, so the target is one. */
|
|
@@ -30,8 +30,5 @@ export declare abstract class StatementParser extends SimpleStatementParser {
|
|
|
30
30
|
protected parseBlock(): ast.BlockStatement;
|
|
31
31
|
protected parseFunctionBody(async?: boolean): ast.BlockStatement;
|
|
32
32
|
/** An if/for/switch header, where `{` starts the block rather than an object literal. */
|
|
33
|
-
protected parseHeaderExpression(): ast.Expression;
|
|
34
|
-
protected parseReturn(): ast.ReturnStatement;
|
|
35
|
-
/** `}` + newline + `else`: the newline ends the `if`, so explain the error instead of failing. */
|
|
36
33
|
protected checkSameLine(keyword: 'else' | 'catch' | 'finally'): void;
|
|
37
34
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { SimpleStatementParser } from "./simple-statements.js";
|
|
2
2
|
import { FOREIGN_KEYWORDS, FUNCTION_BODY, describe } from "./syntax.js";
|
|
3
|
-
// Statements: blocks, `
|
|
3
|
+
// Statements: blocks, `mount()`, and which statement a token starts.
|
|
4
4
|
export class StatementParser extends SimpleStatementParser {
|
|
5
5
|
parseStatements(isEnd) {
|
|
6
6
|
const body = [];
|
|
@@ -62,13 +62,20 @@ export class StatementParser extends SimpleStatementParser {
|
|
|
62
62
|
return this.parseBreakOrContinue();
|
|
63
63
|
case 'throw':
|
|
64
64
|
return this.parseThrow();
|
|
65
|
+
// `try f()` without a block is an expression.
|
|
65
66
|
case 'try':
|
|
66
|
-
|
|
67
|
+
if (this.peek(1).kind === '{')
|
|
68
|
+
return this.parseTry();
|
|
69
|
+
break;
|
|
67
70
|
case 'defer':
|
|
68
71
|
return this.parseDefer();
|
|
69
72
|
default:
|
|
70
73
|
break;
|
|
71
74
|
}
|
|
75
|
+
// `outer: for`; `x := 1` is reported as Go's declaration.
|
|
76
|
+
if (token.kind === 'Identifier' && this.peek(1).kind === ':' && this.peek(2).kind !== '=') {
|
|
77
|
+
return this.parseLabeled();
|
|
78
|
+
}
|
|
72
79
|
const hint = token.kind === 'Identifier' ? FOREIGN_KEYWORDS.get(token.text) : undefined;
|
|
73
80
|
if (hint !== undefined && this.peek(1).kind === 'Identifier') {
|
|
74
81
|
this.fail(`"${token.text}" is not a Rook keyword: ${hint}`);
|
|
@@ -106,7 +113,10 @@ export class StatementParser extends SimpleStatementParser {
|
|
|
106
113
|
const call = expression.kind === 'CallExpression' ||
|
|
107
114
|
expression.kind === 'NewExpression' ||
|
|
108
115
|
expression.kind === 'GoExpression';
|
|
109
|
-
if (
|
|
116
|
+
if (expression.kind === 'TryExpression') {
|
|
117
|
+
this.error('try gives the error, which is dropped here: keep it, e.g. "const err = try save()", or catch it with try { } catch e { }', expression.start, expression.end);
|
|
118
|
+
}
|
|
119
|
+
else if (!call && !awaited) {
|
|
110
120
|
this.error('this expression does nothing: only function calls can be used as statements', expression.start, expression.end);
|
|
111
121
|
}
|
|
112
122
|
}
|
|
@@ -151,10 +161,17 @@ export class StatementParser extends SimpleStatementParser {
|
|
|
151
161
|
this.endStatement();
|
|
152
162
|
return declaration;
|
|
153
163
|
}
|
|
154
|
-
default:
|
|
164
|
+
default: {
|
|
155
165
|
if (this.isDecoratorDeclaration())
|
|
156
166
|
return this.parseDecoratorDeclaration(start, exported);
|
|
157
|
-
|
|
167
|
+
// `data` and `abstract` are keywords only before `class`: elsewhere they are names.
|
|
168
|
+
let words = this.checkWord('abstract') ? 1 : 0;
|
|
169
|
+
if (this.peek(words).text === 'data')
|
|
170
|
+
words++;
|
|
171
|
+
return words > 0 && this.peek(words).kind === 'class'
|
|
172
|
+
? this.parseClass(start, exported)
|
|
173
|
+
: undefined;
|
|
174
|
+
}
|
|
158
175
|
}
|
|
159
176
|
}
|
|
160
177
|
/** A statement ends with a newline or `;`, or right before the `}` that closes its block. */
|
|
@@ -237,26 +254,6 @@ export class StatementParser extends SimpleStatementParser {
|
|
|
237
254
|
return this.withContext({ ...FUNCTION_BODY, inAsync: async }, () => this.parseBlock());
|
|
238
255
|
}
|
|
239
256
|
/** An if/for/switch header, where `{` starts the block rather than an object literal. */
|
|
240
|
-
parseHeaderExpression() {
|
|
241
|
-
return this.withContext({ noObjectLiteral: true }, () => this.parseExpression());
|
|
242
|
-
}
|
|
243
|
-
parseReturn() {
|
|
244
|
-
const start = this.next().start;
|
|
245
|
-
if (this.context.inGoFor) {
|
|
246
|
-
this.error('"return" cannot leave "go for": its body runs in workers; skip an element with "continue"', start, this.span(start).end);
|
|
247
|
-
}
|
|
248
|
-
else if (!this.context.inFunction) {
|
|
249
|
-
this.error('"return" outside of a function', start, this.span(start).end);
|
|
250
|
-
}
|
|
251
|
-
else if (this.context.inDefer) {
|
|
252
|
-
this.error('"return" is not allowed inside "defer"', start, this.span(start).end);
|
|
253
|
-
}
|
|
254
|
-
const values = this.check(';') || this.check('}') || this.check('EOF') ? [] : this.parseExpressionList();
|
|
255
|
-
const statement = { kind: 'ReturnStatement', values, ...this.span(start) };
|
|
256
|
-
this.endStatement();
|
|
257
|
-
return statement;
|
|
258
|
-
}
|
|
259
|
-
/** `}` + newline + `else`: the newline ends the `if`, so explain the error instead of failing. */
|
|
260
257
|
checkSameLine(keyword) {
|
|
261
258
|
if (this.isImplicitSemicolon() && this.peek(1).kind === keyword) {
|
|
262
259
|
const token = this.peek(1);
|
package/dist/parser/syntax.d.ts
CHANGED
|
@@ -7,7 +7,7 @@ export declare const ASSIGNMENT_OPERATORS: ReadonlySet<string>;
|
|
|
7
7
|
export declare function isBinaryOperator(kind: TokenKind): kind is ast.BinaryOperator;
|
|
8
8
|
export declare function isUnaryOperator(kind: TokenKind): kind is ast.UnaryOperator;
|
|
9
9
|
export declare function isAssignmentOperator(kind: TokenKind): kind is ast.AssignmentOperator;
|
|
10
|
-
export declare const
|
|
10
|
+
export declare const VISIBILITIES: ReadonlySet<string>;
|
|
11
11
|
export declare const MODIFIERS: ReadonlySet<string>;
|
|
12
12
|
/** Words from JS that Rook spells differently. */
|
|
13
13
|
export declare const FOREIGN_KEYWORDS: ReadonlyMap<string, string>;
|
|
@@ -33,6 +33,14 @@ export interface Context {
|
|
|
33
33
|
inComponent: boolean;
|
|
34
34
|
/** In the blocks of `{if ...}` and `{for ...}` inside markup, where elements are statements. */
|
|
35
35
|
inMarkup: boolean;
|
|
36
|
+
/** The labels of the enclosing statements, innermost last, which `break` and `continue` name. */
|
|
37
|
+
labels: readonly Label[];
|
|
38
|
+
}
|
|
39
|
+
/** `outer: for`: `loop` tells `continue outer` from a label of a switch or select. */
|
|
40
|
+
export interface Label {
|
|
41
|
+
name: string;
|
|
42
|
+
loop: boolean;
|
|
43
|
+
used: boolean;
|
|
36
44
|
}
|
|
37
45
|
export declare const FUNCTION_BODY: Partial<Context>;
|
|
38
46
|
/** Thrown to abandon the current statement after its syntax error has been reported. */
|
|
@@ -49,3 +57,5 @@ export declare function cleanJsxText(text: string): string;
|
|
|
49
57
|
/** In `on*` attributes, these are the handler itself rather than code to run. */
|
|
50
58
|
export declare function isHandlerReference(node: ast.Expression): boolean;
|
|
51
59
|
export declare function templateElement(token: StringToken): ast.TemplateElement;
|
|
60
|
+
/** Why `what`, which waits, is not allowed where it is: not in an async function or in `defer`. */
|
|
61
|
+
export declare function cannotWait(what: string, inDefer: boolean): string;
|
package/dist/parser/syntax.js
CHANGED
|
@@ -60,8 +60,13 @@ export function isUnaryOperator(kind) {
|
|
|
60
60
|
export function isAssignmentOperator(kind) {
|
|
61
61
|
return ASSIGNMENT_OPERATORS.has(kind);
|
|
62
62
|
}
|
|
63
|
-
export const
|
|
64
|
-
export const MODIFIERS = new Set([
|
|
63
|
+
export const VISIBILITIES = new Set(['public', 'protected', 'private']);
|
|
64
|
+
export const MODIFIERS = new Set([
|
|
65
|
+
...VISIBILITIES,
|
|
66
|
+
'static',
|
|
67
|
+
'readonly',
|
|
68
|
+
'abstract',
|
|
69
|
+
]);
|
|
65
70
|
/** Words from JS that Rook spells differently. */
|
|
66
71
|
export const FOREIGN_KEYWORDS = new Map([
|
|
67
72
|
['function', 'declare functions with "func"'],
|
|
@@ -106,6 +111,7 @@ export const FUNCTION_BODY = {
|
|
|
106
111
|
inAsync: false,
|
|
107
112
|
noObjectLiteral: false,
|
|
108
113
|
inMarkup: false,
|
|
114
|
+
labels: [],
|
|
109
115
|
};
|
|
110
116
|
/** Thrown to abandon the current statement after its syntax error has been reported. */
|
|
111
117
|
export class ParseError extends Error {
|
|
@@ -118,6 +124,8 @@ export function describe(token) {
|
|
|
118
124
|
return token.text === '' ? 'newline' : '";"';
|
|
119
125
|
case 'String':
|
|
120
126
|
return 'string';
|
|
127
|
+
case 'RegExp':
|
|
128
|
+
return 'regular expression';
|
|
121
129
|
case 'Number':
|
|
122
130
|
return `number ${token.text}`;
|
|
123
131
|
case 'BigInt':
|
|
@@ -171,3 +179,9 @@ export function templateElement(token) {
|
|
|
171
179
|
end: token.end,
|
|
172
180
|
};
|
|
173
181
|
}
|
|
182
|
+
/** Why `what`, which waits, is not allowed where it is: not in an async function or in `defer`. */
|
|
183
|
+
export function cannotWait(what, inDefer) {
|
|
184
|
+
return inDefer
|
|
185
|
+
? `"${what}" is not allowed inside "defer": deferred code runs when the function ends`
|
|
186
|
+
: `"${what}" is allowed only in async functions and at the top level of a module; mark the function, e.g. "async func load() { ... }"`;
|
|
187
|
+
}
|
package/dist/parser/types.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { ClassParser } from "./classes.js";
|
|
2
2
|
import { describe } from "./syntax.js";
|
|
3
|
-
// Types: `?T`, `[]T`, `queue T`, unions, literal types, function, component and object types, type arguments.
|
|
3
|
+
// Types: `?T`, `[]T`, `queue T`, `topic T`, unions, literal types, function, component and object types, type arguments.
|
|
4
4
|
/** `iterator: true` for the parameters of an iterator: one, named `yield`. */
|
|
5
5
|
function iterator(names) {
|
|
6
6
|
return names.length === 1 && names[0] === 'yield' ? { iterator: true } : {};
|
|
@@ -106,14 +106,19 @@ export class TypeParser extends ClassParser {
|
|
|
106
106
|
const members = this.parseTypeMembers();
|
|
107
107
|
return { kind: 'ObjectType', members, ...this.span(start) };
|
|
108
108
|
}
|
|
109
|
-
// `queue number`: `queue`
|
|
110
|
-
//
|
|
109
|
+
// `queue number` and `topic number`: `queue` and `topic` are types of their own only before
|
|
110
|
+
// the type of their values, so that they stay names elsewhere.
|
|
111
111
|
default: {
|
|
112
112
|
if (this.checkWord('queue') && this.canStartType(1)) {
|
|
113
113
|
this.next();
|
|
114
114
|
const element = this.parsePrimaryType();
|
|
115
115
|
return { kind: 'QueueType', element, ...this.span(start) };
|
|
116
116
|
}
|
|
117
|
+
if (this.checkWord('topic') && this.canStartType(1)) {
|
|
118
|
+
this.next();
|
|
119
|
+
const element = this.parsePrimaryType();
|
|
120
|
+
return { kind: 'TopicType', element, ...this.span(start) };
|
|
121
|
+
}
|
|
117
122
|
const name = this.parseIdentifier('type');
|
|
118
123
|
const typeArgs = this.parseTypeArgs();
|
|
119
124
|
return { kind: 'TypeReference', name, typeArgs, ...this.span(start) };
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { Completion } from './checker/checker.ts';
|
|
2
|
+
/** What a name is used as, and so where it is offered: decorators have a namespace of their own. */
|
|
3
|
+
export type ExportSpace = 'value' | 'type' | 'tag' | 'decorator';
|
|
4
|
+
export interface ExportedName {
|
|
5
|
+
name: string;
|
|
6
|
+
kind: Completion['kind'];
|
|
7
|
+
spaces: ExportSpace[];
|
|
8
|
+
/** The absolute path of the module. */
|
|
9
|
+
path: string;
|
|
10
|
+
}
|
|
11
|
+
/** The names exported by the other `.rk` modules of the package of `filename`. */
|
|
12
|
+
export declare function projectExports(filename: string): ExportedName[];
|