rooklang 0.4.2 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +235 -0
- package/CHANGELOG.ru.md +235 -0
- package/dist/aliases.d.ts +2 -0
- package/dist/aliases.js +83 -0
- package/dist/ast/declarations.d.ts +18 -0
- package/dist/ast/expressions.d.ts +14 -1
- package/dist/ast/markup.d.ts +2 -0
- package/dist/ast/statements.d.ts +2 -2
- package/dist/checker/assignments.js +2 -1
- package/dist/checker/base.d.ts +3 -0
- package/dist/checker/base.js +1 -1
- package/dist/checker/builtins.js +5 -1
- package/dist/checker/calls.d.ts +2 -0
- package/dist/checker/calls.js +18 -3
- package/dist/checker/classes.js +8 -0
- package/dist/checker/component-bindings.d.ts +16 -0
- package/dist/checker/component-bindings.js +65 -2
- package/dist/checker/context.d.ts +12 -0
- package/dist/checker/declarations.d.ts +2 -0
- package/dist/checker/declarations.js +38 -3
- package/dist/checker/expressions.d.ts +1 -0
- package/dist/checker/expressions.js +17 -1
- package/dist/checker/flow.d.ts +1 -1
- package/dist/checker/flow.js +10 -0
- package/dist/checker/functions.d.ts +2 -0
- package/dist/checker/functions.js +33 -7
- package/dist/checker/goroutines.d.ts +2 -2
- package/dist/checker/goroutines.js +2 -2
- package/dist/checker/imports.d.ts +10 -0
- package/dist/checker/imports.js +73 -6
- package/dist/checker/initializers.d.ts +12 -0
- package/dist/checker/initializers.js +80 -0
- package/dist/checker/loops.js +3 -1
- package/dist/checker/members.js +1 -0
- package/dist/checker/resolution.d.ts +2 -0
- package/dist/checker/resolution.js +13 -2
- package/dist/checker/statements.d.ts +5 -0
- package/dist/checker/statements.js +26 -3
- package/dist/checker/tagged-unions.d.ts +6 -0
- package/dist/checker/tagged-unions.js +31 -1
- package/dist/checker/tags.d.ts +6 -0
- package/dist/checker/tags.js +44 -0
- package/dist/checker/types/constructors.js +10 -1
- package/dist/checker/types/params.js +33 -7
- package/dist/checker/types/printing.js +5 -1
- package/dist/checker/types/relations.js +2 -1
- package/dist/codegen/calls.d.ts +2 -0
- package/dist/codegen/calls.js +23 -1
- package/dist/codegen/component-props.js +8 -1
- package/dist/codegen/control-flow.d.ts +3 -0
- package/dist/codegen/control-flow.js +17 -0
- package/dist/codegen/expressions.d.ts +4 -1
- package/dist/codegen/expressions.js +4 -15
- package/dist/codegen/functions.d.ts +2 -0
- package/dist/codegen/functions.js +13 -0
- package/dist/codegen/mutations.d.ts +6 -0
- package/dist/codegen/mutations.js +36 -0
- package/dist/codegen/options.d.ts +2 -0
- package/dist/codegen/reactive.js +9 -5
- package/dist/codegen/simple-statements.js +3 -1
- package/dist/codegen/statements.d.ts +4 -0
- package/dist/codegen/statements.js +37 -1
- package/dist/compile.js +2 -0
- package/dist/completion.js +2 -1
- package/dist/declarations.js +18 -0
- package/dist/lexer/base.d.ts +3 -2
- package/dist/lexer/base.js +14 -5
- package/dist/lexer/lexer.js +3 -0
- package/dist/lexer/token.d.ts +6 -1
- package/dist/lexer/token.js +0 -1
- package/dist/modules.js +4 -3
- package/dist/parser/base.d.ts +5 -0
- package/dist/parser/base.js +9 -1
- package/dist/parser/classes.js +4 -3
- package/dist/parser/declarations.d.ts +2 -0
- package/dist/parser/declarations.js +25 -2
- package/dist/parser/decorators.js +1 -1
- package/dist/parser/expressions.js +6 -0
- package/dist/parser/literals.d.ts +2 -4
- package/dist/parser/literals.js +12 -24
- package/dist/parser/objects.d.ts +8 -0
- package/dist/parser/objects.js +51 -0
- package/dist/parser/params.d.ts +37 -4
- package/dist/parser/params.js +143 -9
- package/dist/parser/postfix.d.ts +5 -0
- package/dist/parser/postfix.js +36 -0
- package/dist/parser/simple-statements.d.ts +2 -0
- package/dist/parser/simple-statements.js +50 -0
- package/dist/parser/statements.js +8 -6
- package/dist/parser/syntax.js +0 -1
- package/dist/parser/types.d.ts +0 -4
- package/dist/parser/types.js +17 -32
- package/dist/project-modules.js +21 -0
- package/dist/reexports.d.ts +4 -0
- package/dist/reexports.js +56 -0
- package/dist/typescript/importer.d.ts +4 -0
- package/dist/typescript/importer.js +27 -3
- package/dist/typescript/library.js +5 -2
- package/dist/walk.js +12 -0
- package/package.json +4 -2
|
@@ -65,15 +65,38 @@ export class DeclarationParser extends ParamParser {
|
|
|
65
65
|
if (!this.context.topLevel) {
|
|
66
66
|
this.error('"export" is only allowed at the top level of a module', start, this.span(start).end);
|
|
67
67
|
}
|
|
68
|
+
if (this.check('{') || this.check('*'))
|
|
69
|
+
return this.parseExportFrom(start);
|
|
68
70
|
return (this.parseDeclaration(start, true) ??
|
|
69
71
|
this.fail(`expected a declaration after "export", found ${describe(this.peek())}`));
|
|
70
72
|
}
|
|
73
|
+
/** `export { Row, Rows as Table } from "./rows.rk"` or `export * from "./rows.rk"`. */
|
|
74
|
+
parseExportFrom(start) {
|
|
75
|
+
let specifiers = null;
|
|
76
|
+
if (!this.accept('*')) {
|
|
77
|
+
this.expect('{');
|
|
78
|
+
specifiers = this.parseList('}', () => this.parseImportSpecifier());
|
|
79
|
+
}
|
|
80
|
+
if (!this.checkWord('from')) {
|
|
81
|
+
this.fail('a module exports its own names where they are declared, e.g. export func f(); export { ... } needs from "./module.rk"');
|
|
82
|
+
}
|
|
83
|
+
this.next();
|
|
84
|
+
const source = this.parseStringLiteral('module path');
|
|
85
|
+
const declaration = {
|
|
86
|
+
kind: 'ExportFromDeclaration',
|
|
87
|
+
specifiers,
|
|
88
|
+
source,
|
|
89
|
+
...this.span(start),
|
|
90
|
+
};
|
|
91
|
+
this.endStatement();
|
|
92
|
+
return declaration;
|
|
93
|
+
}
|
|
71
94
|
parseFuncDeclaration(start, exported) {
|
|
72
95
|
const async = this.accept('async');
|
|
73
96
|
this.expect('func');
|
|
74
97
|
const name = this.parseIdentifier('function name');
|
|
75
98
|
const typeParams = this.parseTypeParams();
|
|
76
|
-
const params = this.parseParams(false);
|
|
99
|
+
const params = this.parseParams(false, { defaults: true, rest: 'typed' });
|
|
77
100
|
const results = this.parseResults();
|
|
78
101
|
const body = this.parseFunctionBody(async);
|
|
79
102
|
return {
|
|
@@ -131,7 +154,7 @@ export class DeclarationParser extends ParamParser {
|
|
|
131
154
|
if (!htmlTag && !/^[A-Z]/.test(name.name)) {
|
|
132
155
|
this.error(`component names start with a capital letter, as in <${name.name.charAt(0).toUpperCase()}${name.name.slice(1)} />`, name.start, name.end);
|
|
133
156
|
}
|
|
134
|
-
const params = this.parseParams(false, true,
|
|
157
|
+
const params = this.parseParams(false, { defaults: true, rest: 'attrs' });
|
|
135
158
|
const body = this.withContext({ ...FUNCTION_BODY, inComponent: true }, () => this.parseComponentBody());
|
|
136
159
|
return {
|
|
137
160
|
kind: 'ComponentDeclaration',
|
|
@@ -15,7 +15,7 @@ export class DecoratorParser extends DeclarationParser {
|
|
|
15
15
|
this.error('decorators can only be declared at the top level of a module', keyword.start, keyword.end);
|
|
16
16
|
}
|
|
17
17
|
const name = this.parseIdentifier('decorator name');
|
|
18
|
-
const params = this.parseParams(false, true);
|
|
18
|
+
const params = this.parseParams(false, { defaults: true });
|
|
19
19
|
const needs = this.parseNeeds();
|
|
20
20
|
const includes = this.parseIncludes();
|
|
21
21
|
const body = this.withContext({ ...FUNCTION_BODY, inComponent: true }, () => this.parseDecoratorBody());
|
|
@@ -35,6 +35,12 @@ export class ExpressionParser extends TypeParser {
|
|
|
35
35
|
const precedence = BINARY_PRECEDENCE[operator];
|
|
36
36
|
if (precedence < minPrecedence)
|
|
37
37
|
return left;
|
|
38
|
+
// `await load<number>(1)` reads `await load` first.
|
|
39
|
+
const callee = left.kind === 'UnaryExpression' && left.operator === 'await' ? left.argument : left;
|
|
40
|
+
if (operator === '<' &&
|
|
41
|
+
(callee.kind === 'Identifier' || callee.kind === 'MemberExpression')) {
|
|
42
|
+
this.rejectTypeArguments();
|
|
43
|
+
}
|
|
38
44
|
this.next();
|
|
39
45
|
// `**` is right-associative, all other binary operators are left-associative.
|
|
40
46
|
const right = this.parseBinary(operator === '**' ? precedence : precedence + 1);
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type * as ast from '../ast.ts';
|
|
2
2
|
import { type StringToken, type TokenKind } from '../lexer/token.ts';
|
|
3
|
-
import {
|
|
4
|
-
export declare abstract class LiteralParser extends
|
|
3
|
+
import { ObjectParser } from './objects.ts';
|
|
4
|
+
export declare abstract class LiteralParser extends ObjectParser {
|
|
5
5
|
/** Whether the `(` at the current position opens arrow function parameters: `(...) =>`. */
|
|
6
6
|
protected isArrowFunctionAhead(): boolean;
|
|
7
7
|
protected parseArrowFunction(async?: boolean): ast.ArrowFunction;
|
|
@@ -32,8 +32,6 @@ export declare abstract class LiteralParser extends ExpressionParser {
|
|
|
32
32
|
protected parseStreamChan(start: number): ast.StreamChanExpression;
|
|
33
33
|
/** `[]byte("text")`: a type, then its value in parentheses. */
|
|
34
34
|
protected parseConversion(): ast.ConversionExpression;
|
|
35
|
-
protected parseObjectLiteral(): ast.ObjectLiteral;
|
|
36
|
-
protected parseProperty(): ast.Property | ast.SpreadElement;
|
|
37
35
|
protected parseTemplate(first: StringToken): ast.TemplateLiteral;
|
|
38
36
|
/** Parses `item, item, ...` up to `close`, which is consumed. A trailing comma is allowed. */
|
|
39
37
|
protected parseList<T>(close: TokenKind, parseItem: () => T): T[];
|
package/dist/parser/literals.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { isKeyword } from "../lexer/token.js";
|
|
2
|
-
import {
|
|
2
|
+
import { ObjectParser } from "./objects.js";
|
|
3
3
|
import { FUNCTION_BODY, ParseError, describe, templateElement } from "./syntax.js";
|
|
4
4
|
// Functions, `new`, literals of arrays, objects and templates, names.
|
|
5
|
-
export class LiteralParser extends
|
|
5
|
+
export class LiteralParser extends ObjectParser {
|
|
6
6
|
/** Whether the `(` at the current position opens arrow function parameters: `(...) =>`. */
|
|
7
7
|
isArrowFunctionAhead() {
|
|
8
8
|
let depth = 0;
|
|
@@ -39,7 +39,7 @@ export class LiteralParser extends ExpressionParser {
|
|
|
39
39
|
];
|
|
40
40
|
}
|
|
41
41
|
else {
|
|
42
|
-
params = this.parseParams(true);
|
|
42
|
+
params = this.parseParams(true, { defaults: true, rest: 'typed' });
|
|
43
43
|
}
|
|
44
44
|
this.expect('=>');
|
|
45
45
|
const body = this.withContext({ ...FUNCTION_BODY, inAsync: async }, () => this.check('{') ? this.parseBlock() : this.parseExpression());
|
|
@@ -60,7 +60,7 @@ export class LiteralParser extends ExpressionParser {
|
|
|
60
60
|
}
|
|
61
61
|
parseFuncExpression(async = false) {
|
|
62
62
|
const start = this.expect('func').start;
|
|
63
|
-
const params = this.parseParams(false);
|
|
63
|
+
const params = this.parseParams(false, { defaults: true, rest: 'typed' });
|
|
64
64
|
const results = this.parseResults();
|
|
65
65
|
const body = this.parseFunctionBody(async);
|
|
66
66
|
return {
|
|
@@ -86,6 +86,9 @@ export class LiteralParser extends ExpressionParser {
|
|
|
86
86
|
...this.span(callee.start),
|
|
87
87
|
};
|
|
88
88
|
}
|
|
89
|
+
// `new Promise<void>(...)`: the type arguments are inferred, as in calls.
|
|
90
|
+
if (this.check('<'))
|
|
91
|
+
this.rejectTypeArguments();
|
|
89
92
|
const args = this.check('(') ? this.parseArguments() : [];
|
|
90
93
|
return { kind: 'NewExpression', callee, arguments: args, ...this.span(start) };
|
|
91
94
|
}
|
|
@@ -220,26 +223,6 @@ export class LiteralParser extends ExpressionParser {
|
|
|
220
223
|
this.expect(')');
|
|
221
224
|
return { kind: 'ConversionExpression', type, argument, ...this.span(start) };
|
|
222
225
|
}
|
|
223
|
-
parseObjectLiteral() {
|
|
224
|
-
const start = this.expect('{').start;
|
|
225
|
-
const properties = this.nested(() => this.parseList('}', () => this.parseProperty()));
|
|
226
|
-
return { kind: 'ObjectLiteral', properties, ...this.span(start) };
|
|
227
|
-
}
|
|
228
|
-
parseProperty() {
|
|
229
|
-
const token = this.peek();
|
|
230
|
-
if (token.kind === '...')
|
|
231
|
-
return this.parseElement();
|
|
232
|
-
const key = token.kind === 'String' ? this.parseStringLiteral() : this.parsePropertyName();
|
|
233
|
-
if (this.accept(':')) {
|
|
234
|
-
const value = this.parseExpression();
|
|
235
|
-
return { kind: 'Property', key, value, shorthand: false, ...this.span(token.start) };
|
|
236
|
-
}
|
|
237
|
-
// Shorthand `{ name }` means `{ name: name }`, so the key must be a plain name.
|
|
238
|
-
if (key.kind !== 'Identifier' || token.kind !== 'Identifier') {
|
|
239
|
-
this.fail(`expected ":" after property name, found ${describe(this.peek())}`);
|
|
240
|
-
}
|
|
241
|
-
return { kind: 'Property', key, value: { ...key }, shorthand: true, ...this.span(token.start) };
|
|
242
|
-
}
|
|
243
226
|
parseTemplate(first) {
|
|
244
227
|
this.next();
|
|
245
228
|
const quasis = [templateElement(first)];
|
|
@@ -248,6 +231,11 @@ export class LiteralParser extends ExpressionParser {
|
|
|
248
231
|
while (true) {
|
|
249
232
|
expressions.push(this.nested(() => this.parseExpression()));
|
|
250
233
|
const part = this.peek();
|
|
234
|
+
if ((part.kind === '++' || part.kind === '--') &&
|
|
235
|
+
expressions.at(-1)?.kind === 'Identifier') {
|
|
236
|
+
const name = expressions.at(-1).name;
|
|
237
|
+
this.fail(`"${part.kind}" is a statement and cannot be used inside a template: use ${name} here and write ${name}${part.kind} on a line of its own`);
|
|
238
|
+
}
|
|
251
239
|
if (part.kind !== 'TemplateMiddle' && part.kind !== 'TemplateTail') {
|
|
252
240
|
this.fail(`expected "}" to close the template substitution, found ${describe(part)}`);
|
|
253
241
|
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type * as ast from '../ast.ts';
|
|
2
|
+
import { PostfixParser } from './postfix.ts';
|
|
3
|
+
export declare abstract class ObjectParser extends PostfixParser {
|
|
4
|
+
protected parseObjectLiteral(): ast.ObjectLiteral;
|
|
5
|
+
protected parseProperty(): ast.Property | ast.SpreadElement;
|
|
6
|
+
/** `fetch(req Request) Response { ... }`: typed parameters, as those of a `func` literal. */
|
|
7
|
+
private parseObjectMethod;
|
|
8
|
+
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { PostfixParser } from "./postfix.js";
|
|
2
|
+
import { describe } from "./syntax.js";
|
|
3
|
+
// Object literals: `{ name: value }`, `{ name }`, `{ ...other }`, and methods written as in a
|
|
4
|
+
// class, `{ fetch(req Request) Response { ... } }`.
|
|
5
|
+
export class ObjectParser extends PostfixParser {
|
|
6
|
+
parseObjectLiteral() {
|
|
7
|
+
const start = this.expect('{').start;
|
|
8
|
+
const properties = this.nested(() => this.parseList('}', () => this.parseProperty()));
|
|
9
|
+
return { kind: 'ObjectLiteral', properties, ...this.span(start) };
|
|
10
|
+
}
|
|
11
|
+
parseProperty() {
|
|
12
|
+
const token = this.peek();
|
|
13
|
+
if (token.kind === '...') {
|
|
14
|
+
this.next();
|
|
15
|
+
const argument = this.parseExpression();
|
|
16
|
+
return { kind: 'SpreadElement', argument, ...this.span(token.start) };
|
|
17
|
+
}
|
|
18
|
+
// `async load() T { ... }`: `async` before the name of a method.
|
|
19
|
+
const async = token.kind === 'async' && this.peek(2).kind === '(';
|
|
20
|
+
if (async)
|
|
21
|
+
this.next();
|
|
22
|
+
const key = this.check('String') ? this.parseStringLiteral() : this.parsePropertyName();
|
|
23
|
+
if (this.check('('))
|
|
24
|
+
return this.parseObjectMethod(key, token.start, async);
|
|
25
|
+
if (this.accept(':')) {
|
|
26
|
+
const value = this.parseExpression();
|
|
27
|
+
return { kind: 'Property', key, value, shorthand: false, ...this.span(token.start) };
|
|
28
|
+
}
|
|
29
|
+
// Shorthand `{ name }` means `{ name: name }`, so the key must be a plain name.
|
|
30
|
+
if (key.kind !== 'Identifier' || token.kind !== 'Identifier') {
|
|
31
|
+
this.fail(`expected ":" after property name, found ${describe(this.peek())}`);
|
|
32
|
+
}
|
|
33
|
+
return { kind: 'Property', key, value: { ...key }, shorthand: true, ...this.span(token.start) };
|
|
34
|
+
}
|
|
35
|
+
/** `fetch(req Request) Response { ... }`: typed parameters, as those of a `func` literal. */
|
|
36
|
+
parseObjectMethod(key, start, async) {
|
|
37
|
+
const funcStart = this.peek().start;
|
|
38
|
+
const params = this.parseParams(false, { defaults: true, rest: 'typed' });
|
|
39
|
+
const results = this.parseResults();
|
|
40
|
+
const body = this.parseFunctionBody(async);
|
|
41
|
+
const value = {
|
|
42
|
+
kind: 'FuncExpression',
|
|
43
|
+
...(async ? { async } : {}),
|
|
44
|
+
params,
|
|
45
|
+
results,
|
|
46
|
+
body,
|
|
47
|
+
...this.span(funcStart),
|
|
48
|
+
};
|
|
49
|
+
return { kind: 'Property', key, value, shorthand: false, method: true, ...this.span(start) };
|
|
50
|
+
}
|
|
51
|
+
}
|
package/dist/parser/params.d.ts
CHANGED
|
@@ -4,9 +4,14 @@ export declare abstract class ParamParser extends JumpParser {
|
|
|
4
4
|
/**
|
|
5
5
|
* `(a, b number, c string)`. Names without a type take the type of the next typed name, as in
|
|
6
6
|
* Go; only arrow functions may leave types out entirely. The parameters of a constructor can be
|
|
7
|
-
* properties: `(private name string)`.
|
|
7
|
+
* properties: `(private name string)`. Functions take `...parts []any` last, and components
|
|
8
|
+
* `...attrs`, the attributes of an element; both may have default values, `times = 1`.
|
|
8
9
|
*/
|
|
9
|
-
protected parseParams(typesOptional: boolean,
|
|
10
|
+
protected parseParams(typesOptional: boolean, options?: {
|
|
11
|
+
defaults?: boolean;
|
|
12
|
+
rest?: 'typed' | 'attrs';
|
|
13
|
+
properties?: boolean;
|
|
14
|
+
}): ast.Parameter[];
|
|
10
15
|
/** `private`, `readonly` or both before a parameter that is a property of its class. */
|
|
11
16
|
private parsePropertyModifiers;
|
|
12
17
|
/**
|
|
@@ -14,8 +19,36 @@ export declare abstract class ParamParser extends JumpParser {
|
|
|
14
19
|
* not by `,` or `)`, so a parameter named bind, `(bind Mode)`, stays one.
|
|
15
20
|
*/
|
|
16
21
|
private parseBindModifier;
|
|
22
|
+
/** `...parts []any`: the arguments after the others, in an array, as in JS. */
|
|
23
|
+
private parseTypedRest;
|
|
17
24
|
/** `...attrs`: the attributes that a component passes on to one of its elements. */
|
|
18
25
|
private parseRestParam;
|
|
19
|
-
/**
|
|
20
|
-
|
|
26
|
+
/**
|
|
27
|
+
* Result types after a parameter list: nothing, `T`, or `(T1, T2)`. A `{` may start the body
|
|
28
|
+
* instead: it starts an object type when the body follows that type,
|
|
29
|
+
* `func names() { [string]: string } { ... }`, or a value does, `f func() { [string]: T } = g`.
|
|
30
|
+
* A method of an interface has no body, so there `{` is always a type.
|
|
31
|
+
*/
|
|
32
|
+
protected parseResults(method?: boolean): ast.TypeNode[];
|
|
33
|
+
/** Whether the `{ ... }` here is followed by a body, `{`, or by a value, `=`. */
|
|
34
|
+
private objectTypeBeforeBody;
|
|
35
|
+
/**
|
|
36
|
+
* The type of a parameter of a function type, after its name if it has one, kept in `names`;
|
|
37
|
+
* `...` before the rest parameter is kept there too, `...parts`.
|
|
38
|
+
*/
|
|
39
|
+
protected parseParamType(names: string[]): ast.TypeNode;
|
|
40
|
+
/** `func(string, ...[]any)`: the last parameter, taken out of `params`, if it is the rest. */
|
|
41
|
+
protected restOf(params: ast.TypeNode[], names: string[]): ast.TypeNode | null;
|
|
42
|
+
/**
|
|
43
|
+
* `func(a, b number)`: as in Go, if a parameter has a name, all have, so `a` alone is a name that
|
|
44
|
+
* shares the type of the next one. Without names, `func(A, B)` is a list of types.
|
|
45
|
+
*/
|
|
46
|
+
protected groupParams(params: ast.TypeNode[], names: string[]): ast.TypeNode[];
|
|
47
|
+
/** `paths []string` in `(paths []string) => Shape`: a name before the type of a parameter. */
|
|
48
|
+
private startsNamedParam;
|
|
49
|
+
/**
|
|
50
|
+
* `load<number>(1)` would be two comparisons, `(load < number) > (1)`: a call takes no type
|
|
51
|
+
* arguments, which are inferred, so the likely mistake is reported as one.
|
|
52
|
+
*/
|
|
53
|
+
protected rejectTypeArguments(): void;
|
|
21
54
|
}
|
package/dist/parser/params.js
CHANGED
|
@@ -6,15 +6,20 @@ export class ParamParser extends JumpParser {
|
|
|
6
6
|
/**
|
|
7
7
|
* `(a, b number, c string)`. Names without a type take the type of the next typed name, as in
|
|
8
8
|
* Go; only arrow functions may leave types out entirely. The parameters of a constructor can be
|
|
9
|
-
* properties: `(private name string)`.
|
|
9
|
+
* properties: `(private name string)`. Functions take `...parts []any` last, and components
|
|
10
|
+
* `...attrs`, the attributes of an element; both may have default values, `times = 1`.
|
|
10
11
|
*/
|
|
11
|
-
parseParams(typesOptional,
|
|
12
|
+
parseParams(typesOptional, options = {}) {
|
|
13
|
+
const { defaults: allowDefaults = false, properties: allowProperties = false } = options;
|
|
14
|
+
const allowRest = options.rest === 'attrs';
|
|
12
15
|
this.expect('(');
|
|
13
16
|
const params = [];
|
|
14
17
|
let untyped = 0;
|
|
15
18
|
while (!this.check(')')) {
|
|
16
19
|
if (this.check('...')) {
|
|
17
|
-
params.push(
|
|
20
|
+
params.push(options.rest === 'typed'
|
|
21
|
+
? this.parseTypedRest(typesOptional)
|
|
22
|
+
: this.parseRestParam(allowRest));
|
|
18
23
|
break;
|
|
19
24
|
}
|
|
20
25
|
const paramStart = this.peek().start;
|
|
@@ -35,7 +40,11 @@ export class ParamParser extends JumpParser {
|
|
|
35
40
|
...(bind ? { bind: true } : {}),
|
|
36
41
|
...this.span(paramStart),
|
|
37
42
|
});
|
|
38
|
-
|
|
43
|
+
// `times = 1` has the type of its value.
|
|
44
|
+
if (type === null && defaultValue !== null) {
|
|
45
|
+
untyped = 0;
|
|
46
|
+
}
|
|
47
|
+
else if (type === null) {
|
|
39
48
|
untyped++;
|
|
40
49
|
}
|
|
41
50
|
else {
|
|
@@ -100,12 +109,30 @@ export class ParamParser extends JumpParser {
|
|
|
100
109
|
}
|
|
101
110
|
return true;
|
|
102
111
|
}
|
|
112
|
+
/** `...parts []any`: the arguments after the others, in an array, as in JS. */
|
|
113
|
+
parseTypedRest(typesOptional) {
|
|
114
|
+
const start = this.next().start;
|
|
115
|
+
const name = this.parseIdentifier('parameter name');
|
|
116
|
+
const type = this.canStartType() ? this.parseType() : null;
|
|
117
|
+
if (type === null && !typesOptional) {
|
|
118
|
+
this.error(`...${name.name} needs the type of its array, e.g. ...${name.name} []any`, start, name.end);
|
|
119
|
+
}
|
|
120
|
+
// After the error, the rest of the list is skipped up to the `)`.
|
|
121
|
+
if (this.check(',')) {
|
|
122
|
+
this.error(`...${name.name} is the last parameter`, start, name.end);
|
|
123
|
+
while (!this.check(')') && !this.check('EOF'))
|
|
124
|
+
this.next();
|
|
125
|
+
}
|
|
126
|
+
if (this.check('='))
|
|
127
|
+
this.error(`...${name.name} has no default value: it is [] without arguments`, start, name.end);
|
|
128
|
+
return { kind: 'Parameter', name, type, defaultValue: null, rest: true, ...this.span(start) };
|
|
129
|
+
}
|
|
103
130
|
/** `...attrs`: the attributes that a component passes on to one of its elements. */
|
|
104
131
|
parseRestParam(allowed) {
|
|
105
132
|
const start = this.next().start;
|
|
106
133
|
const name = this.parseIdentifier('parameter name');
|
|
107
134
|
if (!allowed) {
|
|
108
|
-
this.error(`...${name.name}
|
|
135
|
+
this.error(`...${name.name} needs the type of its array, e.g. ...${name.name} []any`, start, name.end);
|
|
109
136
|
}
|
|
110
137
|
if (this.canStartType() || this.check('=')) {
|
|
111
138
|
const at = this.peek().start;
|
|
@@ -131,13 +158,120 @@ export class ParamParser extends JumpParser {
|
|
|
131
158
|
...this.span(start),
|
|
132
159
|
};
|
|
133
160
|
}
|
|
134
|
-
/**
|
|
135
|
-
|
|
161
|
+
/**
|
|
162
|
+
* Result types after a parameter list: nothing, `T`, or `(T1, T2)`. A `{` may start the body
|
|
163
|
+
* instead: it starts an object type when the body follows that type,
|
|
164
|
+
* `func names() { [string]: string } { ... }`, or a value does, `f func() { [string]: T } = g`.
|
|
165
|
+
* A method of an interface has no body, so there `{` is always a type.
|
|
166
|
+
*/
|
|
167
|
+
parseResults(method = false) {
|
|
136
168
|
if (this.accept('('))
|
|
137
169
|
return this.parseList(')', () => this.parseType());
|
|
138
|
-
|
|
139
|
-
|
|
170
|
+
if (this.check('{') && !method && !this.objectTypeBeforeBody())
|
|
171
|
+
return [];
|
|
172
|
+
if (this.canStartType())
|
|
140
173
|
return [this.parseType()];
|
|
141
174
|
return [];
|
|
142
175
|
}
|
|
176
|
+
/** Whether the `{ ... }` here is followed by a body, `{`, or by a value, `=`. */
|
|
177
|
+
objectTypeBeforeBody() {
|
|
178
|
+
let depth = 0;
|
|
179
|
+
for (let offset = 0;; offset++) {
|
|
180
|
+
const { kind } = this.peek(offset);
|
|
181
|
+
if (kind === 'EOF')
|
|
182
|
+
return false;
|
|
183
|
+
if (kind === '{')
|
|
184
|
+
depth++;
|
|
185
|
+
else if (kind === '}' && --depth === 0) {
|
|
186
|
+
const next = this.peek(offset + 1).kind;
|
|
187
|
+
return next === '{' || next === '=';
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
/**
|
|
192
|
+
* The type of a parameter of a function type, after its name if it has one, kept in `names`;
|
|
193
|
+
* `...` before the rest parameter is kept there too, `...parts`.
|
|
194
|
+
*/
|
|
195
|
+
parseParamType(names) {
|
|
196
|
+
const rest = this.accept('...') ? '...' : '';
|
|
197
|
+
names.push(rest + (this.check('Identifier') && this.startsNamedParam() ? this.next().text : ''));
|
|
198
|
+
return this.parseType();
|
|
199
|
+
}
|
|
200
|
+
/** `func(string, ...[]any)`: the last parameter, taken out of `params`, if it is the rest. */
|
|
201
|
+
restOf(params, names) {
|
|
202
|
+
const at = names.findIndex((name) => name.startsWith('...'));
|
|
203
|
+
if (at === -1)
|
|
204
|
+
return null;
|
|
205
|
+
if (at !== params.length - 1) {
|
|
206
|
+
this.error('the rest parameter is the last one, e.g. func(string, ...[]any)', params[at].start, params[at].end);
|
|
207
|
+
}
|
|
208
|
+
names.splice(at, 1);
|
|
209
|
+
return params.splice(at, 1)[0];
|
|
210
|
+
}
|
|
211
|
+
/**
|
|
212
|
+
* `func(a, b number)`: as in Go, if a parameter has a name, all have, so `a` alone is a name that
|
|
213
|
+
* shares the type of the next one. Without names, `func(A, B)` is a list of types.
|
|
214
|
+
*/
|
|
215
|
+
groupParams(params, names) {
|
|
216
|
+
// `...` alone, an unnamed rest parameter, gives no name.
|
|
217
|
+
const named = (name) => name.replace(/^\.\.\./, '') !== '';
|
|
218
|
+
if (!names.some(named))
|
|
219
|
+
return params;
|
|
220
|
+
return params.map((param, i) => {
|
|
221
|
+
if (names[i] !== '' || param.kind !== 'TypeReference' || param.typeArgs.length > 0) {
|
|
222
|
+
return param;
|
|
223
|
+
}
|
|
224
|
+
const shared = names.findIndex((name, j) => j > i && named(name));
|
|
225
|
+
if (shared === -1) {
|
|
226
|
+
this.error(`parameter "${param.name.name}" has no type: write it after the name, e.g. func(a, b number)`, param.start, param.end);
|
|
227
|
+
return param;
|
|
228
|
+
}
|
|
229
|
+
names[i] = param.name.name;
|
|
230
|
+
return params[shared];
|
|
231
|
+
});
|
|
232
|
+
}
|
|
233
|
+
/** `paths []string` in `(paths []string) => Shape`: a name before the type of a parameter. */
|
|
234
|
+
startsNamedParam() {
|
|
235
|
+
const next = this.peek(1);
|
|
236
|
+
switch (next.kind) {
|
|
237
|
+
case 'Identifier':
|
|
238
|
+
case '?':
|
|
239
|
+
case '{':
|
|
240
|
+
case '(':
|
|
241
|
+
case 'func':
|
|
242
|
+
case 'chan':
|
|
243
|
+
case '<-':
|
|
244
|
+
case 'String':
|
|
245
|
+
case 'Number':
|
|
246
|
+
return true;
|
|
247
|
+
// `x []number` is named.
|
|
248
|
+
case '[':
|
|
249
|
+
return this.peek(2).kind === ']';
|
|
250
|
+
default:
|
|
251
|
+
return false;
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
/**
|
|
255
|
+
* `load<number>(1)` would be two comparisons, `(load < number) > (1)`: a call takes no type
|
|
256
|
+
* arguments, which are inferred, so the likely mistake is reported as one.
|
|
257
|
+
*/
|
|
258
|
+
rejectTypeArguments() {
|
|
259
|
+
const TYPE_TOKENS = new Set(['Identifier', ',', '[', ']', '?', '.', '|', 'chan']);
|
|
260
|
+
let depth = 0;
|
|
261
|
+
for (let offset = 0;; offset++) {
|
|
262
|
+
const { kind } = this.peek(offset);
|
|
263
|
+
if (kind === '<')
|
|
264
|
+
depth++;
|
|
265
|
+
else if (kind === '>')
|
|
266
|
+
depth--;
|
|
267
|
+
else if (!TYPE_TOKENS.has(kind))
|
|
268
|
+
return;
|
|
269
|
+
if (depth === 0) {
|
|
270
|
+
if (this.peek(offset + 1).kind !== '(')
|
|
271
|
+
return;
|
|
272
|
+
break;
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
this.fail('a call takes no type arguments: they are inferred from the arguments and the expected type; give the variable a type instead, e.g. const done Promise<void> = new Promise(...)');
|
|
276
|
+
}
|
|
143
277
|
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { ExpressionParser } from "./expressions.js";
|
|
2
|
+
// What may follow a value besides members, indexes and calls: `user!`, which takes it not to be
|
|
3
|
+
// null, and a template right after it, sql`select ... ${id}`, which calls it with its parts.
|
|
4
|
+
export class PostfixParser extends ExpressionParser {
|
|
5
|
+
parsePostfix(expression) {
|
|
6
|
+
let result = super.parsePostfix(expression);
|
|
7
|
+
while (true) {
|
|
8
|
+
const token = this.peek();
|
|
9
|
+
// `user!.name`: the `!` right after the value, so that `!` before a value stays a negation.
|
|
10
|
+
if (token.kind === '!' && token.start === result.end) {
|
|
11
|
+
this.next();
|
|
12
|
+
result = {
|
|
13
|
+
kind: 'NonNullExpression',
|
|
14
|
+
expression: result,
|
|
15
|
+
start: result.start,
|
|
16
|
+
end: token.end,
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
else if ((token.kind === 'Template' || token.kind === 'TemplateHead') &&
|
|
20
|
+
!token.newlineBefore) {
|
|
21
|
+
const quasi = this.parseTemplate(token);
|
|
22
|
+
result = {
|
|
23
|
+
kind: 'TaggedTemplateExpression',
|
|
24
|
+
tag: result,
|
|
25
|
+
quasi,
|
|
26
|
+
start: result.start,
|
|
27
|
+
end: quasi.end,
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
else {
|
|
31
|
+
return result;
|
|
32
|
+
}
|
|
33
|
+
result = super.parsePostfix(result);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
}
|
|
@@ -19,5 +19,7 @@ export declare abstract class SimpleStatementParser extends ParserBase {
|
|
|
19
19
|
/** `ref input` is one name, and the markup gives its element. */
|
|
20
20
|
private checkRef;
|
|
21
21
|
private parseDerivedBlock;
|
|
22
|
+
/** `const { name, size: bytes, ...rest } = file`: fields of an object, one level deep. */
|
|
23
|
+
private parseDestructuring;
|
|
22
24
|
protected parseVariableDeclaration(start: number, exported: boolean): ast.VariableDeclaration;
|
|
23
25
|
}
|
|
@@ -96,6 +96,10 @@ export class SimpleStatementParser extends ParserBase {
|
|
|
96
96
|
// `data, err = try JSON.parse(text)`: the value and the error, as from a call.
|
|
97
97
|
if (values.length === 1 && value?.kind === 'TryExpression')
|
|
98
98
|
return;
|
|
99
|
+
if (values.length === 1 && value?.kind === 'ConditionalExpression') {
|
|
100
|
+
this.error('a conditional expression gives one value, not the results of a call: unpack them in the branches of if/else, e.g. if ok { a, err = two() } else { a, err = other() }', start, this.span(start).end);
|
|
101
|
+
return;
|
|
102
|
+
}
|
|
99
103
|
this.error(`assignment mismatch: ${plural(targets, 'variable')} but ${plural(values.length, 'value')}`, start, this.span(start).end);
|
|
100
104
|
}
|
|
101
105
|
/** `delete scores[name]`: removes a field, so the target is one. */
|
|
@@ -135,6 +139,49 @@ export class SimpleStatementParser extends ParserBase {
|
|
|
135
139
|
end: body.end,
|
|
136
140
|
};
|
|
137
141
|
}
|
|
142
|
+
/** `const { name, size: bytes, ...rest } = file`: fields of an object, one level deep. */
|
|
143
|
+
parseDestructuring(start, exported, keyword) {
|
|
144
|
+
const names = [];
|
|
145
|
+
const keys = [];
|
|
146
|
+
let rest = false;
|
|
147
|
+
this.expect('{');
|
|
148
|
+
this.parseList('}', () => {
|
|
149
|
+
if (rest)
|
|
150
|
+
this.fail('...rest is the last name: it gets the other fields');
|
|
151
|
+
if (this.accept('...')) {
|
|
152
|
+
rest = true;
|
|
153
|
+
names.push(this.parseIdentifier('variable name'));
|
|
154
|
+
return;
|
|
155
|
+
}
|
|
156
|
+
const token = this.peek();
|
|
157
|
+
const key = this.check('String') ? this.parseStringLiteral() : this.parsePropertyName();
|
|
158
|
+
if (this.accept(':')) {
|
|
159
|
+
names.push(this.parseIdentifier('variable name'));
|
|
160
|
+
}
|
|
161
|
+
else if (key.kind === 'Identifier' && token.kind === 'Identifier') {
|
|
162
|
+
names.push({ ...key });
|
|
163
|
+
}
|
|
164
|
+
else {
|
|
165
|
+
const shown = token.kind === 'String' ? token.text : `"${token.text}"`;
|
|
166
|
+
this.fail(`${shown} needs a name to take its value, e.g. { ${token.text}: name }`);
|
|
167
|
+
}
|
|
168
|
+
keys.push(key);
|
|
169
|
+
});
|
|
170
|
+
this.expect('=', '"=" and the object: a pattern takes its fields from a value');
|
|
171
|
+
const value = this.parseExpression();
|
|
172
|
+
return {
|
|
173
|
+
kind: 'VariableDeclaration',
|
|
174
|
+
exported,
|
|
175
|
+
keyword,
|
|
176
|
+
isPublic: false,
|
|
177
|
+
names,
|
|
178
|
+
type: null,
|
|
179
|
+
values: [value],
|
|
180
|
+
body: null,
|
|
181
|
+
pattern: { keys, rest },
|
|
182
|
+
...this.span(start),
|
|
183
|
+
};
|
|
184
|
+
}
|
|
138
185
|
parseVariableDeclaration(start, exported) {
|
|
139
186
|
const token = this.next();
|
|
140
187
|
const keyword = token.kind === 'const'
|
|
@@ -142,6 +189,9 @@ export class SimpleStatementParser extends ParserBase {
|
|
|
142
189
|
: token.text === 'state' || token.text === 'derived' || token.text === 'ref'
|
|
143
190
|
? token.text
|
|
144
191
|
: 'let';
|
|
192
|
+
if (this.check('{') && (keyword === 'const' || keyword === 'let')) {
|
|
193
|
+
return this.parseDestructuring(start, exported, keyword);
|
|
194
|
+
}
|
|
145
195
|
const names = [this.parseIdentifier('variable name')];
|
|
146
196
|
while (this.accept(','))
|
|
147
197
|
names.push(this.parseIdentifier('variable name'));
|
|
@@ -158,14 +158,15 @@ export class StatementParser extends SimpleStatementParser {
|
|
|
158
158
|
return this.parseDecorated(start, exported);
|
|
159
159
|
case 'interface':
|
|
160
160
|
return this.parseInterface(start, exported);
|
|
161
|
-
case 'type': {
|
|
162
|
-
const declaration = this.parseTypeAlias(start, exported);
|
|
163
|
-
this.endStatement();
|
|
164
|
-
return declaration;
|
|
165
|
-
}
|
|
166
161
|
default: {
|
|
167
162
|
if (this.isDecoratorDeclaration())
|
|
168
163
|
return this.parseDecoratorDeclaration(start, exported);
|
|
164
|
+
// `type` is a keyword only before the name of a type: `const type = blob.type` is a name.
|
|
165
|
+
if (this.checkWord('type') && this.peek(1).kind === 'Identifier') {
|
|
166
|
+
const declaration = this.parseTypeAlias(start, exported);
|
|
167
|
+
this.endStatement();
|
|
168
|
+
return declaration;
|
|
169
|
+
}
|
|
169
170
|
// `data` and `abstract` are keywords only before `class`: elsewhere they are names.
|
|
170
171
|
let words = this.checkWord('abstract') ? 1 : 0;
|
|
171
172
|
if (this.peek(words).text === 'data')
|
|
@@ -178,8 +179,9 @@ export class StatementParser extends SimpleStatementParser {
|
|
|
178
179
|
}
|
|
179
180
|
/** A statement ends with a newline or `;`, or right before the `}` that closes its block. */
|
|
180
181
|
endStatement() {
|
|
181
|
-
if (this.accept(';') || this.check('}') || this.check('EOF'))
|
|
182
|
+
if (this.accept(';') || this.check('}') || this.check('EOF') || this.afterTypeArguments()) {
|
|
182
183
|
return;
|
|
184
|
+
}
|
|
183
185
|
const token = this.peek();
|
|
184
186
|
if (token.kind === '++' || token.kind === '--') {
|
|
185
187
|
this.fail(`"${token.kind}" is a statement and cannot be used inside an expression`);
|
package/dist/parser/syntax.js
CHANGED
package/dist/parser/types.d.ts
CHANGED
|
@@ -13,10 +13,6 @@ export declare abstract class TypeParser extends ClassParser {
|
|
|
13
13
|
* names, which only document them.
|
|
14
14
|
*/
|
|
15
15
|
protected parseParenthesizedType(): ast.TypeNode;
|
|
16
|
-
/** The type of a parameter of a function type, after its name if it has one, kept in `names`. */
|
|
17
|
-
private parseParamType;
|
|
18
|
-
/** `paths []string` in `(paths []string) => Shape`: a name before the type of a parameter. */
|
|
19
|
-
private startsNamedParam;
|
|
20
16
|
/** `{ name string; x, y number; area() number }`, separated by newlines, `;` or `,`. */
|
|
21
17
|
protected parseTypeMembers(): ast.TypeMember[];
|
|
22
18
|
protected parseTypeMember(): ast.TypeMember[];
|