rooklang 0.3.3 → 0.3.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (170) hide show
  1. package/README.md +32 -7
  2. package/README.ru.md +32 -7
  3. package/dist/ast/declarations.d.ts +20 -1
  4. package/dist/ast/expressions.d.ts +31 -1
  5. package/dist/ast/markup.d.ts +6 -1
  6. package/dist/ast/statements.d.ts +25 -1
  7. package/dist/auto-import.d.ts +11 -0
  8. package/dist/auto-import.js +81 -0
  9. package/dist/breaks.d.ts +6 -0
  10. package/dist/breaks.js +36 -0
  11. package/dist/build.d.ts +7 -0
  12. package/dist/build.js +23 -2
  13. package/dist/checker/array-literals.d.ts +6 -0
  14. package/dist/checker/array-literals.js +59 -0
  15. package/dist/checker/assignments.d.ts +2 -0
  16. package/dist/checker/assignments.js +14 -0
  17. package/dist/checker/base.js +6 -6
  18. package/dist/checker/builtins.js +15 -5
  19. package/dist/checker/bytes.d.ts +2 -2
  20. package/dist/checker/bytes.js +2 -2
  21. package/dist/checker/calls.js +3 -0
  22. package/dist/checker/channel-objects.d.ts +3 -0
  23. package/dist/checker/channel-objects.js +40 -0
  24. package/dist/checker/channels.d.ts +1 -0
  25. package/dist/checker/channels.js +16 -2
  26. package/dist/checker/checker.d.ts +1 -1
  27. package/dist/checker/checker.js +19 -0
  28. package/dist/checker/class-members.d.ts +22 -0
  29. package/dist/checker/class-members.js +105 -0
  30. package/dist/checker/classes.d.ts +5 -4
  31. package/dist/checker/classes.js +41 -12
  32. package/dist/checker/closing.d.ts +21 -0
  33. package/dist/checker/closing.js +164 -0
  34. package/dist/checker/completion.d.ts +2 -2
  35. package/dist/checker/completion.js +2 -2
  36. package/dist/checker/context.d.ts +13 -0
  37. package/dist/checker/control-flow.d.ts +5 -0
  38. package/dist/checker/control-flow.js +17 -0
  39. package/dist/checker/declarations.js +1 -0
  40. package/dist/checker/decode.d.ts +8 -0
  41. package/dist/checker/decode.js +32 -0
  42. package/dist/checker/expressions.d.ts +5 -1
  43. package/dist/checker/expressions.js +13 -34
  44. package/dist/checker/flow.js +8 -4
  45. package/dist/checker/helpers.d.ts +1 -1
  46. package/dist/checker/helpers.js +9 -2
  47. package/dist/checker/iterables.d.ts +3 -1
  48. package/dist/checker/iterables.js +13 -0
  49. package/dist/checker/iterators.d.ts +4 -1
  50. package/dist/checker/iterators.js +12 -4
  51. package/dist/checker/json.d.ts +5 -0
  52. package/dist/checker/json.js +92 -0
  53. package/dist/checker/loops.js +23 -3
  54. package/dist/checker/members.js +7 -0
  55. package/dist/checker/operators.d.ts +2 -2
  56. package/dist/checker/operators.js +7 -5
  57. package/dist/checker/queues.d.ts +2 -2
  58. package/dist/checker/queues.js +2 -2
  59. package/dist/checker/resolution.js +5 -3
  60. package/dist/checker/signals.d.ts +8 -0
  61. package/dist/checker/signals.js +48 -0
  62. package/dist/checker/topics.d.ts +6 -0
  63. package/dist/checker/topics.js +17 -0
  64. package/dist/checker/transfer.d.ts +3 -1
  65. package/dist/checker/transfer.js +25 -5
  66. package/dist/checker/try.d.ts +6 -0
  67. package/dist/checker/try.js +19 -0
  68. package/dist/checker/types/constructors.d.ts +1 -1
  69. package/dist/checker/types/constructors.js +2 -2
  70. package/dist/checker/types/generics.js +1 -1
  71. package/dist/checker/types/iterables.d.ts +13 -0
  72. package/dist/checker/types/iterables.js +52 -0
  73. package/dist/checker/types/model.d.ts +25 -0
  74. package/dist/checker/types/params.js +22 -3
  75. package/dist/checker/types/printing.js +4 -3
  76. package/dist/checker/types/relations.js +19 -25
  77. package/dist/checker/types/topic.d.ts +6 -0
  78. package/dist/checker/types/topic.js +23 -0
  79. package/dist/checker/types.d.ts +2 -0
  80. package/dist/checker/types.js +2 -0
  81. package/dist/cli.js +18 -36
  82. package/dist/codegen/calls.d.ts +23 -0
  83. package/dist/codegen/calls.js +88 -7
  84. package/dist/codegen/control-statements.d.ts +10 -2
  85. package/dist/codegen/control-statements.js +56 -19
  86. package/dist/codegen/decode.d.ts +14 -0
  87. package/dist/codegen/decode.js +93 -0
  88. package/dist/codegen/emitter.d.ts +10 -41
  89. package/dist/codegen/emitter.js +9 -4
  90. package/dist/codegen/expressions.d.ts +9 -0
  91. package/dist/codegen/expressions.js +15 -24
  92. package/dist/codegen/js.d.ts +2 -2
  93. package/dist/codegen/js.js +5 -2
  94. package/dist/codegen/options.d.ts +40 -0
  95. package/dist/codegen/options.js +1 -0
  96. package/dist/codegen/runtime.d.ts +7 -3
  97. package/dist/codegen/runtime.js +27 -13
  98. package/dist/codegen/simple-statements.d.ts +10 -4
  99. package/dist/codegen/simple-statements.js +40 -15
  100. package/dist/codegen/statements.js +64 -4
  101. package/dist/codegen/try.d.ts +5 -0
  102. package/dist/codegen/try.js +40 -0
  103. package/dist/compile.d.ts +9 -0
  104. package/dist/compile.js +19 -6
  105. package/dist/completion.d.ts +4 -2
  106. package/dist/completion.js +10 -5
  107. package/dist/declarations.js +32 -6
  108. package/dist/decorator-copy.js +5 -2
  109. package/dist/goroutines.d.ts +1 -1
  110. package/dist/goroutines.js +5 -4
  111. package/dist/index.d.ts +6 -2
  112. package/dist/index.js +14 -5
  113. package/dist/lexer/base.d.ts +2 -0
  114. package/dist/lexer/base.js +6 -1
  115. package/dist/lexer/lexer.js +3 -0
  116. package/dist/lexer/literals.d.ts +5 -0
  117. package/dist/lexer/literals.js +51 -1
  118. package/dist/lexer/markup.js +3 -4
  119. package/dist/lexer/tables.js +2 -0
  120. package/dist/lexer/token.d.ts +4 -3
  121. package/dist/lexer/token.js +0 -2
  122. package/dist/loader.js +30 -8
  123. package/dist/parser/base.d.ts +4 -0
  124. package/dist/parser/base.js +2 -1
  125. package/dist/parser/classes.d.ts +2 -0
  126. package/dist/parser/classes.js +88 -23
  127. package/dist/parser/control-flow.d.ts +3 -1
  128. package/dist/parser/control-flow.js +21 -24
  129. package/dist/parser/declarations.d.ts +7 -4
  130. package/dist/parser/declarations.js +45 -6
  131. package/dist/parser/expressions.js +27 -3
  132. package/dist/parser/jumps.d.ts +11 -0
  133. package/dist/parser/jumps.js +77 -0
  134. package/dist/parser/literals.d.ts +9 -0
  135. package/dist/parser/literals.js +45 -6
  136. package/dist/parser/markup.js +1 -1
  137. package/dist/parser/simple-statements.d.ts +1 -1
  138. package/dist/parser/simple-statements.js +16 -9
  139. package/dist/parser/statements.d.ts +0 -3
  140. package/dist/parser/statements.js +22 -25
  141. package/dist/parser/syntax.d.ts +11 -1
  142. package/dist/parser/syntax.js +16 -2
  143. package/dist/parser/types.js +8 -3
  144. package/dist/project-modules.d.ts +12 -0
  145. package/dist/project-modules.js +106 -0
  146. package/dist/run-file.d.ts +6 -0
  147. package/dist/run-file.js +111 -0
  148. package/dist/runtime/sources.js +11 -5
  149. package/dist/source-map.d.ts +17 -0
  150. package/dist/source-map.js +75 -0
  151. package/dist/ts-types.js +4 -1
  152. package/dist/typescript/convert-objects.d.ts +2 -1
  153. package/dist/typescript/convert-objects.js +18 -9
  154. package/dist/vite.js +12 -4
  155. package/dist/walk-types.js +1 -0
  156. package/dist/walk.d.ts +0 -2
  157. package/dist/walk.js +11 -28
  158. package/package.json +1 -1
  159. package/src/runtime/chan.js +15 -5
  160. package/src/runtime/channel.js +28 -0
  161. package/src/runtime/classes.js +32 -0
  162. package/src/runtime/core.js +10 -1
  163. package/src/runtime/decode.js +190 -0
  164. package/src/runtime/go.js +3 -0
  165. package/src/runtime/index.js +6 -0
  166. package/src/runtime/select.js +14 -9
  167. package/src/runtime/signals.js +59 -0
  168. package/src/runtime/threads.js +32 -5
  169. package/src/runtime/topic.js +118 -0
  170. package/src/runtime/try.js +45 -0
@@ -10,6 +10,8 @@ export declare abstract class LexerBase {
10
10
  constructor(text: string);
11
11
  /** Skips whitespace and comments; returns whether a line break was skipped. */
12
12
  protected skipTrivia(): boolean;
13
+ /** After a value `/` divides; elsewhere it starts a regular expression, as in JS. */
14
+ protected lastEndsValue(): boolean;
13
15
  protected lastEndsStatement(): boolean;
14
16
  /**
15
17
  * Unlike Go, a line starting with `.` / `?.` continues a method chain, and a line starting with
@@ -1,4 +1,4 @@
1
- import { ENDS_STATEMENT, isDigit } from "./tables.js";
1
+ import { ENDS_STATEMENT, ENDS_VALUE, isDigit } from "./tables.js";
2
2
  // The lexer is a chain of layers: base → literals → markup → style → Lexer (lexer.ts). This layer
3
3
  // keeps the position and the modes (code, markup tags, markup content, CSS), skips spaces and
4
4
  // comments, and inserts semicolons at the ends of lines.
@@ -48,6 +48,11 @@ export class LexerBase {
48
48
  }
49
49
  return sawNewline;
50
50
  }
51
+ /** After a value `/` divides; elsewhere it starts a regular expression, as in JS. */
52
+ lastEndsValue() {
53
+ const last = this.tokens.at(-1);
54
+ return last !== undefined && ENDS_VALUE.has(last.kind);
55
+ }
51
56
  lastEndsStatement() {
52
57
  const last = this.tokens.at(-1);
53
58
  return last !== undefined && ENDS_STATEMENT.has(last.kind);
@@ -71,6 +71,9 @@ class Lexer extends StyleLexer {
71
71
  this.push('}', start, start + 1);
72
72
  this.modes.pop();
73
73
  }
74
+ else if (c === '/' && !this.lastEndsValue()) {
75
+ this.scanRegExp(start);
76
+ }
74
77
  else if (c === '<' && this.startsElement()) {
75
78
  this.push('JsxTagOpen', start, start + 1);
76
79
  this.modes.push({ kind: 'tag', start });
@@ -1,6 +1,11 @@
1
1
  import { LexerBase } from './base.ts';
2
2
  import { type CharTest } from './tables.ts';
3
3
  export declare abstract class LiteralLexer extends LexerBase {
4
+ /**
5
+ * `/ab+c/gi`: the pattern goes up to a `/` that is not escaped or inside `[...]`, on one line,
6
+ * as in JS. JS checks the pattern when it loads the module, so an invalid one is an error here.
7
+ */
8
+ protected scanRegExp(start: number): void;
4
9
  protected scanNumber(start: number): void;
5
10
  /** Consumes digits and `_` separators starting at `pos`; returns the end offset. */
6
11
  protected scanDigits(pos: number, isValid: CharTest): number;
@@ -1,7 +1,57 @@
1
1
  import { LexerBase } from "./base.js";
2
2
  import { IDENTIFIER, SIMPLE_ESCAPES, isDigit, isHexDigit, isOctalDigit, isBinaryDigit, } from "./tables.js";
3
- // Numbers, strings, template strings and escapes.
3
+ // Numbers, strings, template strings, regular expressions and escapes.
4
+ /** The flags of a regular expression, each at most once. */
5
+ const FLAGS = /^(?!.*(.).*\1)[dgimsuvy]*$/;
4
6
  export class LiteralLexer extends LexerBase {
7
+ /**
8
+ * `/ab+c/gi`: the pattern goes up to a `/` that is not escaped or inside `[...]`, on one line,
9
+ * as in JS. JS checks the pattern when it loads the module, so an invalid one is an error here.
10
+ */
11
+ scanRegExp(start) {
12
+ const t = this.text;
13
+ let pos = start + 1;
14
+ let inClass = false;
15
+ while (pos < t.length && t[pos] !== '\n' && (inClass || t[pos] !== '/')) {
16
+ if (t[pos] === '\\' && t[pos + 1] !== '\n')
17
+ pos++;
18
+ else if (t[pos] === '[')
19
+ inClass = true;
20
+ else if (t[pos] === ']')
21
+ inClass = false;
22
+ pos++;
23
+ }
24
+ const pattern = t.slice(start + 1, pos);
25
+ if (t[pos] !== '/') {
26
+ this.error('unterminated regular expression: end it with "/"', start, pos);
27
+ this.tokens.push({
28
+ kind: 'RegExp',
29
+ text: t.slice(start, pos),
30
+ value: pattern,
31
+ start,
32
+ end: pos,
33
+ });
34
+ this.pos = pos;
35
+ return;
36
+ }
37
+ IDENTIFIER.lastIndex = pos + 1;
38
+ const flags = IDENTIFIER.exec(t)?.[0] ?? '';
39
+ const end = pos + 1 + flags.length;
40
+ this.tokens.push({ kind: 'RegExp', text: t.slice(start, end), value: pattern, start, end });
41
+ this.pos = end;
42
+ if (!FLAGS.test(flags) || (flags.includes('u') && flags.includes('v'))) {
43
+ this.error(`invalid flags "${flags}" of a regular expression: each of d, g, i, m, s, u, v and y goes once, and not u with v`, pos + 1, end);
44
+ return;
45
+ }
46
+ try {
47
+ new RegExp(pattern, flags);
48
+ }
49
+ catch (error) {
50
+ // "Invalid regular expression: /(/: Unterminated group" → "unterminated group".
51
+ const reason = error.message.replace(/^.*: /, '');
52
+ this.error(`invalid regular expression: ${reason.charAt(0).toLowerCase()}${reason.slice(1)}`, start, end);
53
+ }
54
+ }
5
55
  scanNumber(start) {
6
56
  const t = this.text;
7
57
  let pos = start;
@@ -1,14 +1,13 @@
1
1
  import { LiteralLexer } from "./literals.js";
2
- import { ENDS_VALUE, JSX_NAME, decodeEntities } from "./tables.js";
2
+ import { JSX_NAME, decodeEntities } from "./tables.js";
3
3
  // Markup: where an element starts, tags with their attributes, and text content.
4
4
  export class MarkupLexer extends LiteralLexer {
5
5
  /** `<` starts markup where a value is expected and is "less than" after a value. */
6
6
  startsElement() {
7
- const last = this.tokens.at(-1);
8
- if (last && ENDS_VALUE.has(last.kind))
7
+ if (this.lastEndsValue())
9
8
  return false;
10
9
  // `chan<number>(10)` makes a channel: its `<` opens the type of the values.
11
- if (last?.kind === 'chan')
10
+ if (this.tokens.at(-1)?.kind === 'chan')
12
11
  return false;
13
12
  const next = this.text[this.pos + 1];
14
13
  return next === '>' || (next !== undefined && /^[\p{ID_Start}$_]$/u.test(next));
@@ -8,6 +8,7 @@ export const ENDS_STATEMENT = new Set([
8
8
  'Number',
9
9
  'BigInt',
10
10
  'String',
11
+ 'RegExp',
11
12
  'Template',
12
13
  'TemplateTail',
13
14
  'true',
@@ -34,6 +35,7 @@ export const ENDS_VALUE = new Set([
34
35
  'Number',
35
36
  'BigInt',
36
37
  'String',
38
+ 'RegExp',
37
39
  'Template',
38
40
  'TemplateTail',
39
41
  'true',
@@ -1,4 +1,4 @@
1
- export declare const KEYWORDS: readonly ["func", "comp", "go", "chan", "return", "let", "const", "type", "interface", "class", "extends", "new", "this", "super", "if", "else", "for", "in", "break", "continue", "switch", "case", "default", "defer", "try", "catch", "finally", "throw", "import", "export", "null", "true", "false", "typeof", "instanceof", "async", "await", "map"];
1
+ export declare const KEYWORDS: readonly ["func", "comp", "go", "chan", "return", "let", "const", "type", "interface", "class", "extends", "new", "this", "super", "if", "else", "for", "in", "break", "continue", "switch", "case", "default", "defer", "try", "catch", "finally", "throw", "import", "export", "null", "true", "false", "typeof", "instanceof", "async", "await"];
2
2
  export declare const PUNCTUATORS: readonly ["{", "}", "(", ")", "[", "]", ";", ",", ":", ".", "...", "?", "?.", "??", "=>", "=", "+=", "-=", "*=", "/=", "%=", "**=", "&=", "|=", "^=", "<<=", ">>=", ">>>=", "&&=", "||=", "??=", "==", "!=", "<", "<-", ">", "<=", ">=", "+", "-", "*", "/", "%", "**", "++", "--", "&", "|", "^", "~", "<<", ">>", ">>>", "&&", "||", "!", "@"];
3
3
  export type Keyword = (typeof KEYWORDS)[number];
4
4
  export type Punctuator = (typeof PUNCTUATORS)[number];
@@ -7,9 +7,10 @@ export type Punctuator = (typeof PUNCTUATORS)[number];
7
7
  * substitutions: `a${x}b${y}c` becomes TemplateHead("a") x TemplateMiddle("b") y TemplateTail("c"),
8
8
  * and a template without substitutions is a single Template token. In markup, JsxText is the text
9
9
  * between tags and JsxString an attribute value in quotes; both have HTML entities decoded. CssText is
10
- * the raw text of a `style { ... }` block between the values from code.
10
+ * the raw text of a `style { ... }` block between the values from code. RegExp is `/ab+c/g`, whose
11
+ * value is the pattern as it is written, without the slashes and the flags.
11
12
  */
12
- export type StringKind = 'String' | 'Template' | 'TemplateHead' | 'TemplateMiddle' | 'TemplateTail' | 'JsxText' | 'JsxString' | 'CssText';
13
+ export type StringKind = 'String' | 'Template' | 'TemplateHead' | 'TemplateMiddle' | 'TemplateTail' | 'JsxText' | 'JsxString' | 'CssText' | 'RegExp';
13
14
  /**
14
15
  * Tokens of markup such as `<a href="/">text</a>`: JsxTagOpen `<`, JsxCloseTagOpen `</`, JsxName
15
16
  * (tag and attribute names, which may contain `-` and `:`), JsxTagEnd `>` and JsxSelfClose `/>`.
@@ -36,8 +36,6 @@ export const KEYWORDS = [
36
36
  'instanceof',
37
37
  'async',
38
38
  'await',
39
- // Reserved for future use.
40
- 'map',
41
39
  ];
42
40
  export const PUNCTUATORS = [
43
41
  '{',
package/dist/loader.js CHANGED
@@ -1,10 +1,11 @@
1
1
  import { createHash } from 'node:crypto';
2
- import { existsSync, readFileSync, writeFileSync } from 'node:fs';
2
+ import { appendFileSync, existsSync, readFileSync, statSync, writeFileSync } from 'node:fs';
3
3
  import { createRequire, registerHooks } from 'node:module';
4
- import { extname, join } from 'node:path';
4
+ import { dirname, extname, join, resolve } from 'node:path';
5
5
  import { fileURLToPath } from 'node:url';
6
6
  import { isMainThread } from 'node:worker_threads';
7
7
  import { RUNTIME_MODULE } from "./runtime-module.js";
8
+ import { sourceMapComment } from "./source-map.js";
8
9
  // `rook run` imports this module first (`node --import`): Node then imports `.rk` files,
9
10
  // compiled when they load, and `app.rk?goroutines` is the worker module of `go` in app.rk.
10
11
  // The worker threads of `go` import it too, since they get the `--import` of the process. The main
@@ -13,7 +14,10 @@ import { RUNTIME_MODULE } from "./runtime-module.js";
13
14
  // there: compiling again, with the types, would take each of them as long. So a worker does not
14
15
  // even load the compiler, which takes ~80 ms, unless a module is missing from the cache; the hooks
15
16
  // are synchronous, so it is loaded with require(). ROOK_NO_CHECK turns type checking off, and
16
- // ROOK_MARKUP, 1 or 0, markup and components on or off for every file.
17
+ // ROOK_MARKUP, 1 or 0, markup and components on or off for every file. The code has its source map
18
+ // in it, and the process runs with --enable-source-maps, so stack traces show the lines of .rk.
19
+ // The main thread lists the files it compiles, and those they read, in `files` of the directory of
20
+ // the run, with the time each was changed, for `rook run --watch` (run-file.ts).
17
21
  const typeCheck = process.env['ROOK_NO_CHECK'] === undefined;
18
22
  const markupSetting = process.env['ROOK_MARKUP'];
19
23
  const markup = markupSetting === undefined ? undefined : markupSetting === '1';
@@ -45,19 +49,37 @@ registerHooks({
45
49
  const { displayPath } = require(`./build${extension}`);
46
50
  const { compileFile } = require(`./compile-file${extension}`);
47
51
  const path = displayPath(fileURLToPath(moduleUrl));
52
+ if (isMainThread && cache)
53
+ list([resolve(path)]);
48
54
  const result = compileFile(path, {
49
55
  rewriteImports: false,
50
56
  typeCheck,
51
57
  markup,
52
58
  quiet: !isMainThread,
53
59
  });
60
+ const code = withMap(result.code, result.map);
61
+ const worker = result.worker === undefined ? undefined : withMap(result.worker, result.workerMap);
54
62
  if (isMainThread && cache) {
55
- writeFileSync(cached(moduleUrl), result.code);
56
- if (result.worker !== undefined) {
57
- writeFileSync(cached(`${moduleUrl}?goroutines`), result.worker);
58
- }
63
+ // The CSS modules it imports; the .rk modules are listed when they are loaded.
64
+ const read = result.dependencies.map((each) => resolve(dirname(path), each));
65
+ list(read.filter((each) => existsSync(each)));
66
+ writeFileSync(cached(moduleUrl), code);
67
+ if (worker !== undefined)
68
+ writeFileSync(cached(`${moduleUrl}?goroutines`), worker);
59
69
  }
60
- const source = search === '?goroutines' ? (result.worker ?? '') : result.code;
70
+ const source = search === '?goroutines' ? (worker ?? '') : code;
61
71
  return { format: 'module', source, shortCircuit: true };
62
72
  },
63
73
  });
74
+ /** The code with its source map in it. */
75
+ function withMap(code, map) {
76
+ if (map === undefined || code === '')
77
+ return code;
78
+ const data = Buffer.from(JSON.stringify(map)).toString('base64');
79
+ return `${code}${sourceMapComment(`data:application/json;base64,${data}`)}`;
80
+ }
81
+ /** Adds files to the list of the run: a line of the path and the time it was changed. */
82
+ function list(paths) {
83
+ const lines = paths.map((path) => `${path}\t${statSync(path).mtimeMs}\n`);
84
+ appendFileSync(join(cache, 'files'), lines.join(''));
85
+ }
@@ -4,6 +4,8 @@ import type { StringToken, Token, TokenKind } from '../lexer/token.ts';
4
4
  import { type Context } from './syntax.ts';
5
5
  export declare abstract class ParserBase {
6
6
  protected abstract parseBreakOrContinue(): ast.BreakStatement | ast.ContinueStatement;
7
+ protected abstract parseLabeled(): ast.Statement;
8
+ protected abstract parseReturn(): ast.ReturnStatement;
7
9
  protected abstract parseDefer(): ast.DeferStatement;
8
10
  protected abstract parseFor(): ast.ForStatement | ast.ForInStatement;
9
11
  protected abstract parseGoFor(): ast.ForStatement | ast.ForInStatement;
@@ -37,6 +39,8 @@ export declare abstract class ParserBase {
37
39
  protected abstract parseMake(): ast.MakeExpression;
38
40
  protected abstract parseStreamChan(start: number): ast.StreamChanExpression;
39
41
  protected abstract parseQueue(): ast.QueueExpression | null;
42
+ protected abstract parseTopic(): ast.TopicExpression | null;
43
+ protected abstract parseDecode(): ast.DecodeExpression | null;
40
44
  protected abstract parseArrowFunction(async?: boolean): ast.ArrowFunction;
41
45
  protected abstract parseFuncExpression(async?: boolean): ast.FuncExpression;
42
46
  protected abstract parseIdentifier(what?: string): ast.Identifier;
@@ -1,6 +1,6 @@
1
1
  import { STATEMENT_KEYWORDS, BRACKETS, ParseError, describe } from "./syntax.js";
2
2
  // The parser is a chain of layers, one per area: base → simple statements → statements → control
3
- // flow → declarations → decorators → classes → types → expressions → literals → markup → Parser
3
+ // flow → jumps → declarations → decorators → classes → types → expressions → literals → markup → Parser
4
4
  // (parser.ts). Statements, expressions and markup contain each other, so methods of later layers
5
5
  // that earlier ones call are declared here as abstract.
6
6
  //
@@ -21,6 +21,7 @@ export class ParserBase {
21
21
  noObjectLiteral: false,
22
22
  inComponent: false,
23
23
  inMarkup: false,
24
+ labels: [],
24
25
  };
25
26
  /** Expressions written in parentheses, which the tree itself does not record. */
26
27
  parenthesized = new WeakSet();
@@ -3,6 +3,8 @@ import { DecoratorParser } from './decorators.ts';
3
3
  export declare abstract class ClassParser extends DecoratorParser {
4
4
  protected parseClass(start: number, exported: boolean): ast.ClassDeclaration;
5
5
  protected parseClassMember(): ast.ClassMember[];
6
+ /** A method, a getter or a setter, after its name; an abstract one has no body. */
7
+ private parseMethod;
6
8
  protected parseInterface(start: number, exported: boolean): ast.InterfaceDeclaration;
7
9
  protected parseTypeAlias(start: number, exported: boolean): ast.TypeAliasDeclaration;
8
10
  }
@@ -1,9 +1,11 @@
1
1
  import { isKeyword } from "../lexer/token.js";
2
2
  import { DecoratorParser } from "./decorators.js";
3
- import { MODIFIERS } from "./syntax.js";
3
+ import { MODIFIERS, VISIBILITIES } from "./syntax.js";
4
4
  // Classes and their members, interfaces and `type` aliases.
5
5
  export class ClassParser extends DecoratorParser {
6
6
  parseClass(start, exported) {
7
+ const abstract = this.acceptWord('abstract');
8
+ const data = this.acceptWord('data');
7
9
  this.expect('class');
8
10
  const name = this.parseIdentifier('class name');
9
11
  const typeParams = this.parseTypeParams();
@@ -34,6 +36,8 @@ export class ClassParser extends DecoratorParser {
34
36
  return {
35
37
  kind: 'ClassDeclaration',
36
38
  exported,
39
+ data,
40
+ ...(abstract ? { abstract } : {}),
37
41
  name,
38
42
  typeParams,
39
43
  superClass,
@@ -45,24 +49,26 @@ export class ClassParser extends DecoratorParser {
45
49
  parseClassMember() {
46
50
  const start = this.peek().start;
47
51
  let visibility = null;
48
- let isStatic = false;
52
+ const modifiers = new Map();
49
53
  // Modifiers are contextual: `static` is a modifier only when a member name follows it.
50
54
  while (this.peek().kind === 'Identifier' && MODIFIERS.has(this.peek().text)) {
51
55
  const following = this.peek(1);
52
56
  if (following.kind !== 'Identifier' && !isKeyword(following.kind))
53
57
  break;
54
58
  const modifier = this.next();
55
- if (modifier.text === 'static') {
56
- if (isStatic)
57
- this.error('duplicate "static" modifier', modifier.start, modifier.end);
58
- isStatic = true;
59
- }
60
- else {
59
+ if (VISIBILITIES.has(modifier.text)) {
61
60
  if (visibility)
62
61
  this.error('duplicate visibility modifier', modifier.start, modifier.end);
63
62
  visibility = modifier.text;
64
63
  }
64
+ else {
65
+ if (modifiers.has(modifier.text)) {
66
+ this.error(`duplicate "${modifier.text}" modifier`, modifier.start, modifier.end);
67
+ }
68
+ modifiers.set(modifier.text, modifier);
69
+ }
65
70
  }
71
+ const isStatic = modifiers.has('static');
66
72
  // `async load()`; a method named `async` is `async()`.
67
73
  const asyncToken = this.check('async') && this.peek(1).kind !== '(' ? this.next() : null;
68
74
  const async = asyncToken !== null;
@@ -72,7 +78,12 @@ export class ClassParser extends DecoratorParser {
72
78
  this.error('constructors cannot be static', start, this.span(start).end);
73
79
  if (async)
74
80
  this.error('a constructor cannot be async', start, this.span(start).end);
75
- const params = this.parseParams(false);
81
+ for (const word of ['readonly', 'abstract']) {
82
+ const modifier = modifiers.get(word);
83
+ if (modifier)
84
+ this.error(`a constructor cannot be ${word}`, modifier.start, modifier.end);
85
+ }
86
+ const params = this.parseParams(false, false, false, true);
76
87
  const resultsStart = this.peek().start;
77
88
  if (this.parseResults().length > 0) {
78
89
  this.error('constructors cannot have result types', resultsStart, this.span(start).end);
@@ -88,27 +99,24 @@ export class ClassParser extends DecoratorParser {
88
99
  },
89
100
  ];
90
101
  }
102
+ // `get area()`; a method named `get` is `get()`, a field `get string`.
103
+ const accessorToken = (this.checkWord('get') || this.checkWord('set')) &&
104
+ isName(this.peek(1)) &&
105
+ this.peek(2).kind === '('
106
+ ? this.next()
107
+ : null;
91
108
  const name = this.parsePropertyName('member name');
92
109
  if (this.check('(')) {
93
- const params = this.parseParams(false);
94
- const results = this.parseResults();
95
- const body = this.parseFunctionBody(async);
96
110
  return [
97
- {
98
- kind: 'MethodDeclaration',
99
- visibility: visibility ?? 'public',
100
- isStatic,
101
- ...(async ? { async } : {}),
102
- name,
103
- params,
104
- results,
105
- body,
106
- ...this.span(start),
107
- },
111
+ this.parseMethod(start, name, visibility ?? 'public', modifiers, asyncToken, accessorToken),
108
112
  ];
109
113
  }
110
114
  if (asyncToken)
111
115
  this.error('only methods can be async', asyncToken.start, asyncToken.end);
116
+ const abstract = modifiers.get('abstract');
117
+ if (abstract) {
118
+ this.error('only methods can be abstract: for a field, declare an abstract getter, e.g. abstract get name() string', abstract.start, abstract.end);
119
+ }
112
120
  // Fields: `name string`, `count = 0`, `x, y number`.
113
121
  const names = [name];
114
122
  while (this.accept(','))
@@ -127,12 +135,65 @@ export class ClassParser extends DecoratorParser {
127
135
  kind: 'FieldDeclaration',
128
136
  visibility: visibility ?? 'public',
129
137
  isStatic,
138
+ ...(modifiers.has('readonly') ? { readonly: true } : {}),
130
139
  name: fieldName,
131
140
  type,
132
141
  value,
133
142
  ...span,
134
143
  }));
135
144
  }
145
+ /** A method, a getter or a setter, after its name; an abstract one has no body. */
146
+ parseMethod(start, name, visibility, modifiers, asyncToken, accessorToken) {
147
+ const isStatic = modifiers.has('static');
148
+ const readonly = modifiers.get('readonly');
149
+ const abstract = modifiers.get('abstract');
150
+ const accessor = accessorToken?.text;
151
+ const async = asyncToken !== null;
152
+ if (readonly) {
153
+ this.error('only fields can be readonly: a method cannot be assigned anyway', readonly.start, readonly.end);
154
+ }
155
+ if (abstract && isStatic) {
156
+ this.error('a static method cannot be abstract', abstract.start, abstract.end);
157
+ }
158
+ else if (abstract && visibility === 'private') {
159
+ this.error('an abstract method cannot be private: the subclasses that implement it could not see it', abstract.start, abstract.end);
160
+ }
161
+ if (accessor && asyncToken) {
162
+ this.error(`a ${accessor}ter cannot be async`, asyncToken.start, asyncToken.end);
163
+ }
164
+ const params = this.parseParams(false);
165
+ const results = this.parseResults();
166
+ const header = this.span(start);
167
+ if (accessor === 'get' && (params.length > 0 || results.length !== 1)) {
168
+ this.error('a getter takes nothing and returns one value, e.g. get area() number', header.start, header.end);
169
+ }
170
+ else if (accessor === 'set' && (params.length !== 1 || results.length > 0)) {
171
+ this.error('a setter takes one value and returns nothing, e.g. set area(v number)', header.start, header.end);
172
+ }
173
+ let body = null;
174
+ if (!abstract)
175
+ body = this.parseFunctionBody(async);
176
+ else if (this.check('{')) {
177
+ const block = this.parseFunctionBody(async);
178
+ this.error('an abstract method has no body: the subclasses implement it', block.start, block.end);
179
+ }
180
+ else {
181
+ this.endStatement();
182
+ }
183
+ return {
184
+ kind: 'MethodDeclaration',
185
+ visibility,
186
+ isStatic,
187
+ ...(async ? { async } : {}),
188
+ ...(accessor ? { accessor } : {}),
189
+ ...(abstract ? { abstract: true } : {}),
190
+ name,
191
+ params,
192
+ results,
193
+ body,
194
+ ...this.span(start),
195
+ };
196
+ }
136
197
  parseInterface(start, exported) {
137
198
  this.expect('interface');
138
199
  const name = this.parseIdentifier('interface name');
@@ -162,3 +223,7 @@ export class ClassParser extends DecoratorParser {
162
223
  };
163
224
  }
164
225
  }
226
+ /** A member name: keywords can be names after `.` and in classes. */
227
+ function isName(token) {
228
+ return token.kind === 'Identifier' || isKeyword(token.kind);
229
+ }
@@ -2,6 +2,9 @@ import type * as ast from '../ast.ts';
2
2
  import { StatementParser } from './statements.ts';
3
3
  export declare abstract class ControlFlowParser extends StatementParser {
4
4
  protected parseIf(): ast.IfStatement;
5
+ protected parseHeaderExpression(): ast.Expression;
6
+ /** `const v, err = f();` before the condition of `if` or the value of `switch`. */
7
+ private parseHeaderInit;
5
8
  protected parseFor(): ast.ForStatement | ast.ForInStatement;
6
9
  /** The rest of a `for` statement that starts at `start`, after the keyword. */
7
10
  private parseForAfter;
@@ -22,7 +25,6 @@ export declare abstract class ControlFlowParser extends StatementParser {
22
25
  protected parseSelect(): ast.SelectStatement;
23
26
  /** `v = <-ch`, `v, ok = <-ch`, `<-ch` or `ch <- v`; the names are new variables of the case. */
24
27
  private parseSelectOperation;
25
- protected parseBreakOrContinue(): ast.BreakStatement | ast.ContinueStatement;
26
28
  protected parseThrow(): ast.ThrowStatement;
27
29
  protected parseTry(): ast.TryStatement;
28
30
  protected parseDefer(): ast.DeferStatement;
@@ -1,8 +1,9 @@
1
1
  import { StatementParser } from "./statements.js";
2
- // if, for, switch, break and continue, throw, try and defer.
2
+ // if, for, switch, select, throw, try and defer.
3
3
  export class ControlFlowParser extends StatementParser {
4
4
  parseIf() {
5
5
  const start = this.next().start;
6
+ const init = this.parseHeaderInit('if');
6
7
  const condition = this.parseHeaderExpression();
7
8
  const consequent = this.parseBlock();
8
9
  let alternate = null;
@@ -10,7 +11,21 @@ export class ControlFlowParser extends StatementParser {
10
11
  if (this.accept('else')) {
11
12
  alternate = this.check('if') ? this.parseIf() : this.parseBlock();
12
13
  }
13
- return { kind: 'IfStatement', condition, consequent, alternate, ...this.span(start) };
14
+ return { kind: 'IfStatement', init, condition, consequent, alternate, ...this.span(start) };
15
+ }
16
+ parseHeaderExpression() {
17
+ return this.withContext({ noObjectLiteral: true }, () => this.parseExpression());
18
+ }
19
+ /** `const v, err = f();` before the condition of `if` or the value of `switch`. */
20
+ parseHeaderInit(keyword) {
21
+ if (!this.check('const') && !this.check('let'))
22
+ return null;
23
+ const init = this.withContext({ noObjectLiteral: true }, () => this.parseVariableDeclaration(this.peek().start, false));
24
+ if (this.context.inMarkup) {
25
+ this.error(`the ${keyword} of markup has no initializer: declare the variable before the markup`, init.start, init.end);
26
+ }
27
+ this.expect(';', `";" after the initializer of ${keyword}`);
28
+ return init;
14
29
  }
15
30
  parseFor() {
16
31
  return this.parseForAfter(this.next().start);
@@ -59,7 +74,7 @@ export class ControlFlowParser extends StatementParser {
59
74
  return { kind: 'ForInStatement', key, value, iterable, body, ...this.span(start) };
60
75
  }
61
76
  // The parts run at once in workers: nothing can leave the loop but `continue`.
62
- const body = this.withContext({ inLoop: true, inBreakable: false, inGoFor: true }, () => this.parseBlock());
77
+ const body = this.withContext({ inLoop: true, inBreakable: false, inGoFor: true, labels: [] }, () => this.parseBlock());
63
78
  return { kind: 'ForInStatement', key, value, iterable, body, go, ...this.span(start) };
64
79
  }
65
80
  /** `go for i, v in items { }`: the parts of the array run as goroutines, and the loop waits. */
@@ -77,6 +92,7 @@ export class ControlFlowParser extends StatementParser {
77
92
  }
78
93
  parseSwitch() {
79
94
  const start = this.next().start;
95
+ const init = this.parseHeaderInit('switch');
80
96
  const discriminant = this.check('{') ? null : this.parseHeaderExpression();
81
97
  this.expect('{');
82
98
  const cases = [];
@@ -101,7 +117,7 @@ export class ControlFlowParser extends StatementParser {
101
117
  cases.push({ kind: 'SwitchCase', tests, body, ...this.span(caseStart) });
102
118
  }
103
119
  this.expect('}');
104
- return { kind: 'SwitchStatement', discriminant, cases, ...this.span(start) };
120
+ return { kind: 'SwitchStatement', init, discriminant, cases, ...this.span(start) };
105
121
  }
106
122
  /** `select { case v = <-ch: ... case ch <- v: ... default: ... }`; it waits, as `await` does. */
107
123
  parseSelect() {
@@ -157,25 +173,6 @@ export class ControlFlowParser extends StatementParser {
157
173
  }
158
174
  return [names, first];
159
175
  }
160
- parseBreakOrContinue() {
161
- const token = this.next();
162
- let statement;
163
- if (token.kind === 'break') {
164
- if (!this.context.inBreakable) {
165
- this.error(this.context.inGoFor
166
- ? '"break" cannot stop "go for": its parts run at once in workers; skip an element with "continue"'
167
- : '"break" outside of a loop or switch', token.start, token.end);
168
- }
169
- statement = { kind: 'BreakStatement', start: token.start, end: token.end };
170
- }
171
- else {
172
- if (!this.context.inLoop)
173
- this.error('"continue" outside of a loop', token.start, token.end);
174
- statement = { kind: 'ContinueStatement', start: token.start, end: token.end };
175
- }
176
- this.endStatement();
177
- return statement;
178
- }
179
176
  parseThrow() {
180
177
  const start = this.next().start;
181
178
  const argument = this.parseExpression();
@@ -214,7 +211,7 @@ export class ControlFlowParser extends StatementParser {
214
211
  this.error('"defer" cannot be used in the body of "go for": it runs in workers, apart from the function; defer it before the loop', start, this.span(start).end);
215
212
  }
216
213
  if (this.check('{')) {
217
- const body = this.withContext({ inDefer: true, inAsync: false, inLoop: false, inBreakable: false }, () => this.parseBlock());
214
+ const body = this.withContext({ inDefer: true, inAsync: false, inLoop: false, inBreakable: false, labels: [] }, () => this.parseBlock());
218
215
  return { kind: 'DeferStatement', body, ...this.span(start) };
219
216
  }
220
217
  const call = this.withContext({ inDefer: true, inAsync: false }, () => this.parseExpression());
@@ -1,6 +1,6 @@
1
1
  import type * as ast from '../ast.ts';
2
- import { ControlFlowParser } from './control-flow.ts';
3
- export declare abstract class DeclarationParser extends ControlFlowParser {
2
+ import { JumpParser } from './jumps.ts';
3
+ export declare abstract class DeclarationParser extends JumpParser {
4
4
  protected parseImport(): ast.ImportDeclaration;
5
5
  protected parseImportSpecifier(): ast.ImportSpecifier;
6
6
  protected parseExport(): ast.Statement;
@@ -11,9 +11,12 @@ export declare abstract class DeclarationParser extends ControlFlowParser {
11
11
  protected parseTypeParams(): ast.TypeParameter[];
12
12
  /**
13
13
  * `(a, b number, c string)`. Names without a type take the type of the next typed name, as in
14
- * Go; only arrow functions may leave types out entirely.
14
+ * Go; only arrow functions may leave types out entirely. The parameters of a constructor can be
15
+ * properties: `(private name string)`.
15
16
  */
16
- protected parseParams(typesOptional: boolean, allowDefaults?: boolean, allowRest?: boolean): ast.Parameter[];
17
+ protected parseParams(typesOptional: boolean, allowDefaults?: boolean, allowRest?: boolean, allowProperties?: boolean): ast.Parameter[];
18
+ /** `private`, `readonly` or both before a parameter that is a property of its class. */
19
+ private parsePropertyModifiers;
17
20
  /** `...attrs`: the attributes that a component passes on to one of its elements. */
18
21
  private parseRestParam;
19
22
  /** Result types after a parameter list: nothing, `T`, or `(T1, T2)`. */