rooklang 0.3.2 → 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 (178) 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 +7 -0
  16. package/dist/checker/assignments.js +48 -8
  17. package/dist/checker/base.d.ts +2 -1
  18. package/dist/checker/base.js +6 -12
  19. package/dist/checker/builtins.js +15 -5
  20. package/dist/checker/bytes.d.ts +2 -2
  21. package/dist/checker/bytes.js +2 -2
  22. package/dist/checker/calls.js +3 -0
  23. package/dist/checker/channel-objects.d.ts +3 -0
  24. package/dist/checker/channel-objects.js +40 -0
  25. package/dist/checker/channels.d.ts +1 -0
  26. package/dist/checker/channels.js +16 -2
  27. package/dist/checker/checker.d.ts +1 -1
  28. package/dist/checker/checker.js +19 -0
  29. package/dist/checker/class-members.d.ts +22 -0
  30. package/dist/checker/class-members.js +105 -0
  31. package/dist/checker/classes.d.ts +5 -4
  32. package/dist/checker/classes.js +41 -12
  33. package/dist/checker/closing.d.ts +21 -0
  34. package/dist/checker/closing.js +164 -0
  35. package/dist/checker/completion.d.ts +2 -2
  36. package/dist/checker/completion.js +2 -2
  37. package/dist/checker/context.d.ts +19 -0
  38. package/dist/checker/control-flow.d.ts +6 -0
  39. package/dist/checker/control-flow.js +34 -0
  40. package/dist/checker/declarations.js +1 -0
  41. package/dist/checker/decode.d.ts +8 -0
  42. package/dist/checker/decode.js +32 -0
  43. package/dist/checker/error-results.d.ts +6 -0
  44. package/dist/checker/error-results.js +31 -0
  45. package/dist/checker/expressions.d.ts +5 -1
  46. package/dist/checker/expressions.js +13 -34
  47. package/dist/checker/flow.js +8 -4
  48. package/dist/checker/helpers.d.ts +1 -1
  49. package/dist/checker/helpers.js +9 -2
  50. package/dist/checker/iterables.d.ts +3 -1
  51. package/dist/checker/iterables.js +13 -0
  52. package/dist/checker/iterators.d.ts +4 -1
  53. package/dist/checker/iterators.js +12 -4
  54. package/dist/checker/json.d.ts +5 -0
  55. package/dist/checker/json.js +92 -0
  56. package/dist/checker/loops.js +23 -3
  57. package/dist/checker/members.js +7 -0
  58. package/dist/checker/operators.d.ts +2 -2
  59. package/dist/checker/operators.js +7 -5
  60. package/dist/checker/queues.d.ts +2 -2
  61. package/dist/checker/queues.js +2 -2
  62. package/dist/checker/resolution.js +5 -3
  63. package/dist/checker/signals.d.ts +8 -0
  64. package/dist/checker/signals.js +48 -0
  65. package/dist/checker/statements.d.ts +5 -0
  66. package/dist/checker/statements.js +19 -0
  67. package/dist/checker/topics.d.ts +6 -0
  68. package/dist/checker/topics.js +17 -0
  69. package/dist/checker/transfer.d.ts +3 -1
  70. package/dist/checker/transfer.js +25 -5
  71. package/dist/checker/try.d.ts +6 -0
  72. package/dist/checker/try.js +19 -0
  73. package/dist/checker/types/constructors.d.ts +1 -1
  74. package/dist/checker/types/constructors.js +2 -2
  75. package/dist/checker/types/generics.js +1 -1
  76. package/dist/checker/types/iterables.d.ts +13 -0
  77. package/dist/checker/types/iterables.js +52 -0
  78. package/dist/checker/types/model.d.ts +25 -0
  79. package/dist/checker/types/params.js +22 -3
  80. package/dist/checker/types/printing.js +4 -3
  81. package/dist/checker/types/relations.js +19 -25
  82. package/dist/checker/types/topic.d.ts +6 -0
  83. package/dist/checker/types/topic.js +23 -0
  84. package/dist/checker/types.d.ts +2 -0
  85. package/dist/checker/types.js +2 -0
  86. package/dist/cli.js +18 -36
  87. package/dist/codegen/calls.d.ts +23 -0
  88. package/dist/codegen/calls.js +88 -7
  89. package/dist/codegen/control-statements.d.ts +19 -3
  90. package/dist/codegen/control-statements.js +105 -20
  91. package/dist/codegen/decode.d.ts +14 -0
  92. package/dist/codegen/decode.js +93 -0
  93. package/dist/codegen/emitter.d.ts +10 -41
  94. package/dist/codegen/emitter.js +9 -4
  95. package/dist/codegen/expressions.d.ts +9 -0
  96. package/dist/codegen/expressions.js +15 -24
  97. package/dist/codegen/js.d.ts +2 -2
  98. package/dist/codegen/js.js +5 -2
  99. package/dist/codegen/options.d.ts +40 -0
  100. package/dist/codegen/options.js +1 -0
  101. package/dist/codegen/runtime.d.ts +7 -5
  102. package/dist/codegen/runtime.js +28 -20
  103. package/dist/codegen/simple-statements.d.ts +10 -4
  104. package/dist/codegen/simple-statements.js +40 -15
  105. package/dist/codegen/statements.js +64 -4
  106. package/dist/codegen/try.d.ts +5 -0
  107. package/dist/codegen/try.js +40 -0
  108. package/dist/compile.d.ts +9 -0
  109. package/dist/compile.js +19 -6
  110. package/dist/completion.d.ts +4 -2
  111. package/dist/completion.js +10 -5
  112. package/dist/declarations.js +32 -6
  113. package/dist/decorator-copy.js +5 -2
  114. package/dist/goroutines.d.ts +1 -1
  115. package/dist/goroutines.js +5 -4
  116. package/dist/index.d.ts +6 -2
  117. package/dist/index.js +14 -5
  118. package/dist/lexer/base.d.ts +2 -0
  119. package/dist/lexer/base.js +6 -1
  120. package/dist/lexer/lexer.js +3 -0
  121. package/dist/lexer/literals.d.ts +5 -0
  122. package/dist/lexer/literals.js +51 -1
  123. package/dist/lexer/markup.js +3 -4
  124. package/dist/lexer/tables.js +2 -0
  125. package/dist/lexer/token.d.ts +4 -3
  126. package/dist/lexer/token.js +0 -2
  127. package/dist/loader.js +40 -13
  128. package/dist/parser/base.d.ts +4 -0
  129. package/dist/parser/base.js +2 -1
  130. package/dist/parser/classes.d.ts +2 -0
  131. package/dist/parser/classes.js +88 -23
  132. package/dist/parser/control-flow.d.ts +3 -1
  133. package/dist/parser/control-flow.js +21 -24
  134. package/dist/parser/declarations.d.ts +7 -4
  135. package/dist/parser/declarations.js +45 -6
  136. package/dist/parser/expressions.js +27 -3
  137. package/dist/parser/jumps.d.ts +11 -0
  138. package/dist/parser/jumps.js +77 -0
  139. package/dist/parser/literals.d.ts +9 -0
  140. package/dist/parser/literals.js +45 -6
  141. package/dist/parser/markup.js +1 -1
  142. package/dist/parser/simple-statements.d.ts +1 -1
  143. package/dist/parser/simple-statements.js +16 -9
  144. package/dist/parser/statements.d.ts +0 -3
  145. package/dist/parser/statements.js +22 -25
  146. package/dist/parser/syntax.d.ts +11 -1
  147. package/dist/parser/syntax.js +16 -2
  148. package/dist/parser/types.js +8 -3
  149. package/dist/project-modules.d.ts +12 -0
  150. package/dist/project-modules.js +106 -0
  151. package/dist/run-file.d.ts +6 -0
  152. package/dist/run-file.js +111 -0
  153. package/dist/runtime/sources.js +11 -5
  154. package/dist/runtime-module.d.ts +1 -0
  155. package/dist/runtime-module.js +3 -0
  156. package/dist/source-map.d.ts +17 -0
  157. package/dist/source-map.js +75 -0
  158. package/dist/ts-types.d.ts +4 -0
  159. package/dist/ts-types.js +20 -3
  160. package/dist/typescript/convert-objects.d.ts +2 -1
  161. package/dist/typescript/convert-objects.js +18 -9
  162. package/dist/vite.js +13 -5
  163. package/dist/walk-types.js +1 -0
  164. package/dist/walk.d.ts +0 -2
  165. package/dist/walk.js +11 -28
  166. package/package.json +1 -1
  167. package/src/runtime/chan.js +15 -5
  168. package/src/runtime/channel.js +28 -0
  169. package/src/runtime/classes.js +32 -0
  170. package/src/runtime/core.js +10 -1
  171. package/src/runtime/decode.js +190 -0
  172. package/src/runtime/go.js +14 -5
  173. package/src/runtime/index.js +6 -0
  174. package/src/runtime/select.js +14 -9
  175. package/src/runtime/signals.js +59 -0
  176. package/src/runtime/threads.js +32 -5
  177. package/src/runtime/topic.js +118 -0
  178. package/src/runtime/try.js +45 -0
@@ -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 `ch <- v` wait: only in async functions and at the top level. */
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 arrow = this.next();
55
- this.requireAsync('<-', arrow.start, arrow.end);
53
+ const { start: from, end } = this.next();
56
54
  const value = this.parseExpression();
57
- return { kind: 'SendStatement', channel: first, value, ...this.span(start) };
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, `return`, `mount()`, and which statement a token starts.
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
- return this.parseTry();
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 (!call && !awaited) {
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
- return undefined;
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);
@@ -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 RESERVED: ReadonlySet<string>;
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;
@@ -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 RESERVED = new Set(['map']);
64
- export const MODIFIERS = new Set(['public', 'protected', 'private', 'static']);
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
+ }
@@ -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` is a type of its own only before the type of its values, so that it
110
- // stays a name elsewhere.
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[];
@@ -0,0 +1,106 @@
1
+ import { readdirSync, readFileSync, statSync } from 'node:fs';
2
+ import { dirname, join, resolve } from 'node:path';
3
+ import { markupFor } from "./config.js";
4
+ import { parse } from "./parser/parser.js";
5
+ /** Directories that hold no sources of the package. */
6
+ const SKIPPED = new Set(['node_modules', 'dist', 'build', 'out', 'coverage']);
7
+ /** A bound on the files read, for a file opened in a directory as big as the home directory. */
8
+ const MAX_FILES = 5000;
9
+ const parsed = new Map();
10
+ /** The names exported by the other `.rk` modules of the package of `filename`. */
11
+ export function projectExports(filename) {
12
+ const self = resolve(filename);
13
+ return rookFiles(packageRoot(self))
14
+ .filter((path) => path !== self)
15
+ .flatMap(exportsOf);
16
+ }
17
+ /** The directory of the nearest package.json above the file, or that of the file without one. */
18
+ function packageRoot(filename) {
19
+ for (let dir = dirname(filename);; dir = dirname(dir)) {
20
+ if (exists(join(dir, 'package.json')))
21
+ return dir;
22
+ if (dirname(dir) === dir)
23
+ return dirname(filename);
24
+ }
25
+ }
26
+ function exists(path) {
27
+ try {
28
+ statSync(path);
29
+ return true;
30
+ }
31
+ catch {
32
+ return false;
33
+ }
34
+ }
35
+ function rookFiles(root) {
36
+ const files = [];
37
+ const visit = (dir) => {
38
+ let entries;
39
+ try {
40
+ entries = readdirSync(dir, { withFileTypes: true });
41
+ }
42
+ catch {
43
+ return;
44
+ }
45
+ for (const entry of entries) {
46
+ if (files.length >= MAX_FILES)
47
+ return;
48
+ if (entry.name.startsWith('.'))
49
+ continue;
50
+ const path = join(dir, entry.name);
51
+ if (entry.isDirectory() && !SKIPPED.has(entry.name))
52
+ visit(path);
53
+ else if (entry.isFile() && entry.name.endsWith('.rk'))
54
+ files.push(path);
55
+ }
56
+ };
57
+ visit(root);
58
+ return files;
59
+ }
60
+ function exportsOf(path) {
61
+ let version;
62
+ try {
63
+ const stats = statSync(path);
64
+ version = `${stats.mtimeMs}:${stats.size}`;
65
+ }
66
+ catch {
67
+ return [];
68
+ }
69
+ const cached = parsed.get(path);
70
+ if (cached?.version === version)
71
+ return cached.names;
72
+ let names;
73
+ try {
74
+ const { program } = parse(readFileSync(path, 'utf8'), { markup: markupFor(path) });
75
+ names = program.body.flatMap((statement) => exported(statement, path));
76
+ }
77
+ catch {
78
+ names = [];
79
+ }
80
+ parsed.set(path, { version, names });
81
+ return names;
82
+ }
83
+ function exported(statement, path) {
84
+ const name = (node, kind, spaces) => ({ name: node.name, kind, spaces, path });
85
+ if (!('exported' in statement) || !statement.exported)
86
+ return [];
87
+ switch (statement.kind) {
88
+ case 'FuncDeclaration':
89
+ return [name(statement.name, 'function', ['value'])];
90
+ case 'ClassDeclaration':
91
+ return [name(statement.name, 'class', ['value', 'type'])];
92
+ case 'ComponentDeclaration':
93
+ return [name(statement.name, 'component', ['value', 'tag'])];
94
+ case 'DecoratorDeclaration':
95
+ return [name(statement.name, 'function', ['decorator'])];
96
+ case 'InterfaceDeclaration':
97
+ case 'TypeAliasDeclaration':
98
+ return [name(statement.name, 'type', ['type'])];
99
+ case 'VariableDeclaration': {
100
+ const kind = statement.keyword === 'const' ? 'constant' : 'variable';
101
+ return statement.names.map((each) => name(each, kind, ['value']));
102
+ }
103
+ default:
104
+ return [];
105
+ }
106
+ }
@@ -0,0 +1,6 @@
1
+ export interface RunOptions {
2
+ typeCheck: boolean;
3
+ markup: boolean | undefined;
4
+ watch: boolean;
5
+ }
6
+ export declare function runFile(file: string, options: RunOptions): void;
@@ -0,0 +1,111 @@
1
+ import { spawn } from 'node:child_process';
2
+ import { existsSync, mkdtempSync, readFileSync, rmSync, statSync, unwatchFile, watchFile, } from 'node:fs';
3
+ import { tmpdir } from 'node:os';
4
+ import { join } from 'node:path';
5
+ import { fileURLToPath } from 'node:url';
6
+ import { displayPath } from "./build.js";
7
+ /** The signals that `rook run` gives to the program, or leaves to it, rather than ending at them. */
8
+ const SIGNALS = ['SIGINT', 'SIGTERM', 'SIGHUP'];
9
+ /** How often the files are looked at, and how long a program has to stop before it is killed. */
10
+ const POLL_MS = 200;
11
+ const KILL_MS = 2000;
12
+ export function runFile(file, options) {
13
+ // The build rewrites the extensions of imports, not of these URLs: in the package they are .js.
14
+ const extension = import.meta.url.endsWith('.ts') ? '.ts' : '.js';
15
+ const loader = new URL(`./loader${extension}`, import.meta.url).href;
16
+ const run = fileURLToPath(new URL(`./run${extension}`, import.meta.url));
17
+ const watched = new Set();
18
+ let child = null;
19
+ let cache = '';
20
+ let restart = null;
21
+ let stopping = false;
22
+ let killer;
23
+ // The files of the run and the time each was changed when it was compiled.
24
+ const files = () => existsSync(join(cache, 'files'))
25
+ ? readFileSync(join(cache, 'files'), 'utf8')
26
+ .split('\n')
27
+ .filter(Boolean)
28
+ .map((line) => line.split('\t'))
29
+ .map(([path, time]) => [path, Number(time)])
30
+ : [];
31
+ // A file is compared with what was compiled, as it may change before it is watched.
32
+ const watch = ([path, compiled]) => {
33
+ if (!watched.has(path)) {
34
+ watched.add(path);
35
+ watchFile(path, { interval: POLL_MS }, (now, before) => {
36
+ if (now.mtimeMs !== before.mtimeMs)
37
+ changed(path);
38
+ });
39
+ }
40
+ if (existsSync(path) && statSync(path).mtimeMs !== compiled)
41
+ changed(path);
42
+ };
43
+ const changed = (path) => {
44
+ if (stopping || restart !== null)
45
+ return;
46
+ restart = path;
47
+ if (child === null) {
48
+ start();
49
+ return;
50
+ }
51
+ child.kill('SIGTERM');
52
+ killer = setTimeout(() => child?.kill('SIGKILL'), KILL_MS);
53
+ };
54
+ const stop = () => {
55
+ stopping = true;
56
+ for (const path of watched)
57
+ unwatchFile(path);
58
+ unwatchFile(join(cache, 'files'));
59
+ };
60
+ const forward = (name) => {
61
+ // Without --watch Ctrl+C is the program's; with it, it ends the watching too.
62
+ if (name !== 'SIGINT')
63
+ child?.kill(name);
64
+ if (options.watch)
65
+ stop();
66
+ if (options.watch && child === null)
67
+ process.exitCode = 0;
68
+ };
69
+ const start = () => {
70
+ if (restart !== null)
71
+ console.error(`rook: ${displayPath(restart)} changed, running again`);
72
+ restart = null;
73
+ cache = mkdtempSync(join(tmpdir(), 'rook-run-'));
74
+ const args = ['--enable-source-maps', '--import', loader, run, file];
75
+ child = spawn(process.execPath, args, {
76
+ stdio: 'inherit',
77
+ env: {
78
+ ...process.env,
79
+ ROOK_RUN_CACHE: cache,
80
+ ...(options.typeCheck ? {} : { ROOK_NO_CHECK: '1' }),
81
+ ...(options.markup === undefined ? {} : { ROOK_MARKUP: options.markup ? '1' : '0' }),
82
+ },
83
+ });
84
+ // The files of the program are watched as it loads them.
85
+ if (options.watch) {
86
+ watchFile(join(cache, 'files'), { interval: POLL_MS }, () => files().forEach(watch));
87
+ }
88
+ child.on('close', (status, signal) => {
89
+ clearTimeout(killer);
90
+ if (options.watch)
91
+ files().forEach(watch);
92
+ unwatchFile(join(cache, 'files'));
93
+ rmSync(cache, { recursive: true, force: true });
94
+ child = null;
95
+ if (!options.watch || stopping) {
96
+ for (const name of SIGNALS)
97
+ process.off(name, forward);
98
+ process.exitCode = status ?? (signal ? 1 : 0);
99
+ }
100
+ else if (restart !== null) {
101
+ start();
102
+ }
103
+ else {
104
+ console.error('rook: waiting for a change to run again');
105
+ }
106
+ });
107
+ };
108
+ for (const name of SIGNALS)
109
+ process.on(name, forward);
110
+ start();
111
+ }