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
package/README.md CHANGED
@@ -25,14 +25,21 @@ close(jobs)
25
25
  for let i = 0; i < 4; i++ {
26
26
  console.log(<-results)
27
27
  }
28
+ close(results)
28
29
  ```
29
30
 
30
31
  - **Goroutines and channels.** `go f(x)` runs a function as a goroutine, on another processor, and
31
- channels, `select`, `after`, `tick` and `cancel` work as in Go; `go for i, v in pixels { ... }`
32
+ channels, `select`, `after`, `tick` and `cancel` work as in Go, and signals and `AbortSignal`
33
+ are channels too, `signal("SIGTERM")` and `done(request.signal)`; `go for i, v in pixels { ... }`
32
34
  splits an array among the processors. The compiler checks that what goes to another thread can be
33
- copied there.
34
- - **Go syntax.** `func`, the type after the name, several results, errors as a second value,
35
- `defer`, a single `for` loop:
35
+ copied there, and warns of a channel that is never closed; an instance of a `data class` goes
36
+ with its class and methods.
37
+ - **Topics between tabs.** `topic<CartEvent>("cart")` passes values between the tabs of a site
38
+ over a `BroadcastChannel`: `cart <- e` sends and waits for nobody, `for e in cart` receives what
39
+ the other tabs send. `Channel` and `Topic` have what channels and topics compile to, by name, and
40
+ their closed zero values, `Channel.EMPTY` and `Topic.EMPTY`.
41
+ - **Go syntax.** `func`, the type after the name, several results, errors as a second value
42
+ (`try JSON.parse(text)` makes what JS throws one), `defer`, a single `for` loop:
36
43
 
37
44
  ```go
38
45
  func divide(a, b number) (number, error) {
@@ -43,7 +50,8 @@ for let i = 0; i < 4; i++ {
43
50
  }
44
51
  ```
45
52
 
46
- - **Static types with null safety.** A `?User` cannot be used without a check.
53
+ - **Static types with null safety.** A `?User` cannot be used without a check, and
54
+ `decode<User>(body)` checks JSON from outside against the type when the program runs.
47
55
  - **JavaScript inside.** The same values, classes and modules, any JS library; markup creates real
48
56
  DOM elements, and components are compiled away.
49
57
 
@@ -60,6 +68,7 @@ npm install rooklang
60
68
  npx rook build app.rk # app.js next to the source
61
69
  npx rook build src --out-dir dist # every module of a folder
62
70
  npx rook run app.rk # compile and run
71
+ npx rook run --watch app.rk # and again when a file of it changes
63
72
  ```
64
73
 
65
74
  The compiled code imports what channels, goroutines and markup need from `rooklang/runtime`, so
@@ -97,9 +106,11 @@ staged version is formatted, so edits that are not staged stay out of the commit
97
106
  | `npm run vscode:package` | Pack the VS Code extension into `rooklang.vsix` |
98
107
 
99
108
  Options of `build`: `--out-dir dir` writes the `.js` files to a separate folder, `--stdout` prints
100
- the JS of one file, `--no-check` skips type checking. `--markup` and `--no-markup` turn markup and
109
+ the JS of one file, `--no-check` skips type checking, `--no-source-map` writes no source maps
110
+ (`app.js.map`, by default next to each `.js`). `--markup` and `--no-markup` turn markup and
101
111
  components on or off for all files (with `run` too); by default they are off, and a package turns
102
- them on in its `package.json`: `"rook": { "markup": true }`.
112
+ them on in its `package.json`: `"rook": { "markup": true }`. `run --watch` runs the program again
113
+ when a `.rk` file or a CSS module of it changes, after stopping it with `SIGTERM`.
103
114
 
104
115
  ## Releases
105
116
 
@@ -183,6 +194,20 @@ build, and in development they show up over the page. Markup and components are
183
194
  so `.rk` modules fit a React, Svelte or Vue app as they are; turn them on with
184
195
  `"rook": { "markup": true }` in `package.json`, or `rook({ markup: true })`.
185
196
 
197
+ ### Tests
198
+
199
+ Rook has no test runner of its own: Vitest, with the same plugin in `vitest.config.ts`, runs tests
200
+ of `.rk` modules, written in JS or TS. Goroutines work there too: a worker thread of Vitest loads
201
+ the `.rk` modules with the loader of `rook run`.
202
+
203
+ ```ts
204
+ // math.test.ts
205
+ import { expect, it } from 'vitest';
206
+ import { add } from './math.rk';
207
+
208
+ it('adds', () => expect(add(1, 2)).toBe(3));
209
+ ```
210
+
186
211
  ## Conventions
187
212
 
188
213
  - Imports are written with the `.ts` extension (`import { x } from './lexer.ts'`).
package/README.ru.md CHANGED
@@ -25,14 +25,21 @@ close(jobs)
25
25
  for let i = 0; i < 4; i++ {
26
26
  console.log(<-results)
27
27
  }
28
+ close(results)
28
29
  ```
29
30
 
30
31
  - **Горутины и каналы.** `go f(x)` запускает функцию горутиной, на другом процессоре, а каналы,
31
- `select`, `after`, `tick` и `cancel` работают как в Go; `go for i, v in pixels { ... }` делит
32
+ `select`, `after`, `tick` и `cancel` работают как в Go, а сигналы и `AbortSignal` тоже становятся
33
+ каналами, `signal("SIGTERM")` и `done(request.signal)`; `go for i, v in pixels { ... }` делит
32
34
  массив между процессорами. Компилятор проверяет, что уходящее в другой поток можно туда
33
- скопировать.
34
- - **Синтаксис Go.** `func`, тип после имени, несколько результатов, ошибки вторым значением,
35
- `defer`, один цикл `for`:
35
+ скопировать, и предупреждает о канале, который никогда не закрывают; экземпляр `data class`
36
+ уходит вместе с классом и методами.
37
+ - **Топики между вкладками.** `topic<CartEvent>("cart")` передаёт значения между вкладками сайта
38
+ поверх `BroadcastChannel`: `cart <- e` отправляет и никого не ждёт, `for e in cart` принимает то,
39
+ что шлют другие вкладки. В `Channel` и `Topic` лежит то, во что компилируются каналы и топики, по
40
+ именам, и их закрытые нулевые значения `Channel.EMPTY` и `Topic.EMPTY`.
41
+ - **Синтаксис Go.** `func`, тип после имени, несколько результатов, ошибки вторым значением
42
+ (`try JSON.parse(text)` делает им то, что бросил JS), `defer`, один цикл `for`:
36
43
 
37
44
  ```go
38
45
  func divide(a, b number) (number, error) {
@@ -43,7 +50,8 @@ for let i = 0; i < 4; i++ {
43
50
  }
44
51
  ```
45
52
 
46
- - **Статическая типизация с null-safety.** Значение типа `?User` нельзя использовать без проверки.
53
+ - **Статическая типизация с null-safety.** Значение типа `?User` нельзя использовать без проверки,
54
+ а `decode<User>(body)` во время работы сверяет с типом JSON, пришедший извне.
47
55
  - **Внутри — JavaScript.** Те же значения, классы и модули, любые JS-библиотеки; разметка создаёт
48
56
  настоящие DOM-элементы, а компоненты исчезают при компиляции.
49
57
 
@@ -60,6 +68,7 @@ npm install rooklang
60
68
  npx rook build app.rk # app.js рядом с исходником
61
69
  npx rook build src --out-dir dist # все модули папки
62
70
  npx rook run app.rk # скомпилировать и запустить
71
+ npx rook run --watch app.rk # и запускать снова, когда меняется его файл
63
72
  ```
64
73
 
65
74
  Скомпилированный код импортирует то, что нужно каналам, горутинам и разметке, из
@@ -98,9 +107,11 @@ Node.js ≥ 24 (`nvm use`). Компилятор написан на TypeScript,
98
107
  | `npm run vscode:package` | Упаковать расширение для VS Code в `rooklang.vsix` |
99
108
 
100
109
  Опции `build`: `--out-dir dir` — писать `.js` в отдельную папку, `--stdout` — вывести JS одного
101
- файла в консоль, `--no-check` — без проверки типов. `--markup` и `--no-markup` включают или
110
+ файла в консоль, `--no-check` — без проверки типов, `--no-source-map` — без source maps
111
+ (`app.js.map`, по умолчанию рядом с каждым `.js`). `--markup` и `--no-markup` включают или
102
112
  выключают разметку и компоненты для всех файлов (и в `run`); по умолчанию они выключены, а пакет
103
- включает их в своём `package.json`: `"rook": { "markup": true }`.
113
+ включает их в своём `package.json`: `"rook": { "markup": true }`. `run --watch` запускает программу
114
+ заново, когда меняется её `.rk`-файл или CSS-модуль, сначала остановив её через `SIGTERM`.
104
115
 
105
116
  ## Релизы
106
117
 
@@ -183,6 +194,20 @@ export default defineConfig({ plugins: [rook()] });
183
194
  есть; включить их можно через `"rook": { "markup": true }` в `package.json` или
184
195
  `rook({ markup: true })`.
185
196
 
197
+ ### Тесты
198
+
199
+ Своего запускателя тестов у Rook нет: Vitest с тем же плагином в `vitest.config.ts` запускает тесты
200
+ `.rk`-модулей, написанные на JS или TS. Горутины там тоже работают: поток воркера Vitest загружает
201
+ `.rk`-модули загрузчиком `rook run`.
202
+
203
+ ```ts
204
+ // math.test.ts
205
+ import { expect, it } from 'vitest';
206
+ import { add } from './math.rk';
207
+
208
+ it('adds', () => expect(add(1, 2)).toBe(3));
209
+ ```
210
+
186
211
  ## Соглашения
187
212
 
188
213
  - Импорты пишутся с расширением `.ts` (`import { x } from './lexer.ts'`).
@@ -57,6 +57,14 @@ export interface Parameter extends NodeBase {
57
57
  * properties, which `{...attrs}` puts on one of its elements.
58
58
  */
59
59
  rest?: true;
60
+ /**
61
+ * `constructor(private name string)`: the parameter is a property too, which the constructor
62
+ * assigns, as in TypeScript.
63
+ */
64
+ property?: {
65
+ visibility: Visibility;
66
+ readonly?: true;
67
+ };
60
68
  }
61
69
  /**
62
70
  * `comp Card(title string, children Content) { ...; return <section>...</section> }`. Components
@@ -123,6 +131,10 @@ export interface VariableDeclaration extends NodeBase {
123
131
  export interface ClassDeclaration extends NodeBase {
124
132
  kind: 'ClassDeclaration';
125
133
  exported: boolean;
134
+ /** `data class Point`: its fields are data, and its instances go to goroutines with the class. */
135
+ data: boolean;
136
+ /** `abstract class Shape`: it has no instances of its own, only those of its subclasses. */
137
+ abstract?: true;
126
138
  name: Identifier;
127
139
  typeParams: TypeParameter[];
128
140
  /** An expression, as in JS, so that classes from JS modules can be extended. */
@@ -138,6 +150,8 @@ export interface FieldDeclaration extends NodeBase {
138
150
  /** `public` when no modifier is written. */
139
151
  visibility: Visibility;
140
152
  isStatic: boolean;
153
+ /** `readonly id string`: assigned only by its value or in the constructor of its class. */
154
+ readonly?: true;
141
155
  name: Identifier;
142
156
  type: TypeNode | null;
143
157
  value: Expression | null;
@@ -156,10 +170,15 @@ export interface MethodDeclaration extends NodeBase {
156
170
  isStatic: boolean;
157
171
  /** `async load() User`, like `async func`. */
158
172
  async?: true;
173
+ /** `get area() number { ... }` and `set area(v number) { ... }`, as in JS. */
174
+ accessor?: 'get' | 'set';
175
+ /** `abstract area() number`: no body, each subclass that is not abstract implements it. */
176
+ abstract?: true;
159
177
  name: Identifier;
160
178
  params: Parameter[];
161
179
  results: TypeNode[];
162
- body: BlockStatement;
180
+ /** `null` for an abstract method. */
181
+ body: BlockStatement | null;
163
182
  }
164
183
  /** `interface Shape { name string; area() number }` */
165
184
  export interface InterfaceDeclaration extends NodeBase {
@@ -1,7 +1,7 @@
1
1
  import type { NodeBase, Parameter } from './declarations.ts';
2
2
  import type { ElementExpression, TypeNode } from './markup.ts';
3
3
  import type { BlockStatement } from './statements.ts';
4
- export type Expression = Identifier | NumberLiteral | BigIntLiteral | StringLiteral | TemplateLiteral | BooleanLiteral | NullLiteral | ThisExpression | SuperExpression | ArrayLiteral | ObjectLiteral | FuncExpression | ArrowFunction | UnaryExpression | BinaryExpression | ConditionalExpression | CallExpression | NewExpression | MemberExpression | IndexExpression | ElementExpression | DecoratorMember | DecoratorGet | AsExpression | GoExpression | ChanExpression | StreamChanExpression | QueueExpression | ConversionExpression | MakeExpression;
4
+ export type Expression = Identifier | NumberLiteral | BigIntLiteral | StringLiteral | RegExpLiteral | TemplateLiteral | BooleanLiteral | NullLiteral | ThisExpression | SuperExpression | ArrayLiteral | ObjectLiteral | FuncExpression | ArrowFunction | UnaryExpression | BinaryExpression | ConditionalExpression | CallExpression | NewExpression | MemberExpression | IndexExpression | ElementExpression | DecoratorMember | DecoratorGet | AsExpression | GoExpression | ChanExpression | StreamChanExpression | QueueExpression | TopicExpression | DecodeExpression | TryExpression | ConversionExpression | MakeExpression;
5
5
  /**
6
6
  * `[]byte("rook")`: the UTF-8 bytes of a string. `string(data)`, the way back, is a call, since
7
7
  * `string` may be the name of a value.
@@ -33,6 +33,29 @@ export interface QueueExpression extends NodeBase {
33
33
  element: TypeNode;
34
34
  items: Expression | null;
35
35
  }
36
+ /** `topic<CartEvent>("cart")`: values of the tabs of a site, over a BroadcastChannel of that name. */
37
+ export interface TopicExpression extends NodeBase {
38
+ kind: 'TopicExpression';
39
+ element: TypeNode;
40
+ name: Expression;
41
+ }
42
+ /**
43
+ * `decode<User>(body)`: JSON text, or a value from JS, checked against a type when the program
44
+ * runs; it gives `(User, error)`.
45
+ */
46
+ export interface DecodeExpression extends NodeBase {
47
+ kind: 'DecodeExpression';
48
+ type: TypeNode;
49
+ argument: Expression;
50
+ }
51
+ /**
52
+ * `try JSON.parse(text)`: an exception in the expression, or a rejected Promise that an `await` in
53
+ * it waits for, becomes the error of `(T, error)`.
54
+ */
55
+ export interface TryExpression extends NodeBase {
56
+ kind: 'TryExpression';
57
+ argument: Expression;
58
+ }
36
59
  /**
37
60
  * `chan(res.body, 4)`: a channel that receives the chunks of a ReadableStream, with room for 4,
38
61
  * and a channel that gets the error of the stream, or null at its end.
@@ -82,6 +105,13 @@ export interface StringLiteral extends NodeBase {
82
105
  /** Source text including quotes. */
83
106
  raw: string;
84
107
  }
108
+ /** `/ab+c/gi`, which JS writes the same way. */
109
+ export interface RegExpLiteral extends NodeBase {
110
+ kind: 'RegExpLiteral';
111
+ /** As it is written, between the slashes. */
112
+ pattern: string;
113
+ flags: string;
114
+ }
85
115
  /** `` `Hello, ${name}!` ``: `quasis` always has one element more than `expressions`. */
86
116
  export interface TemplateLiteral extends NodeBase {
87
117
  kind: 'TemplateLiteral';
@@ -60,7 +60,7 @@ export interface EventHandler extends NodeBase {
60
60
  kind: 'EventHandler';
61
61
  body: SimpleStatement[];
62
62
  }
63
- export type TypeNode = TypeReference | ArrayType | NullableType | FuncType | ComponentType | ObjectType | UnionType | LiteralType | ChanType | QueueType;
63
+ export type TypeNode = TypeReference | ArrayType | NullableType | FuncType | ComponentType | ObjectType | UnionType | LiteralType | ChanType | QueueType | TopicType;
64
64
  /**
65
65
  * `chan number`: a channel of numbers, made by `chan<number>(size)`; `chan<- number` only sends
66
66
  * and `<-chan number` only receives.
@@ -75,6 +75,11 @@ export interface QueueType extends NodeBase {
75
75
  kind: 'QueueType';
76
76
  element: TypeNode;
77
77
  }
78
+ /** `topic CartEvent`: values that go to the other tabs of a site, made by `topic<T>(name)`. */
79
+ export interface TopicType extends NodeBase {
80
+ kind: 'TopicType';
81
+ element: TypeNode;
82
+ }
78
83
  /** A named type: `number`, `User`, `Point`, `Box<number>`. */
79
84
  export interface TypeReference extends NodeBase {
80
85
  kind: 'TypeReference';
@@ -4,11 +4,20 @@ import type { JsxElementStatement } from './markup.ts';
4
4
  export type Statement = ImportDeclaration | FuncDeclaration | ComponentDeclaration | DecoratorDeclaration | VariableDeclaration | ClassDeclaration | InterfaceDeclaration | TypeAliasDeclaration | BlockStatement | ExpressionStatement | AssignmentStatement | IncDecStatement | ReturnStatement | IfStatement | ForStatement | ForInStatement | SwitchStatement | BreakStatement | ContinueStatement | ThrowStatement | TryStatement | DeferStatement | MountStatement | EffectStatement | StyleStatement | DeleteStatement | JsxElementStatement | SendStatement | SelectStatement;
5
5
  /** Statements allowed in the `init` and `update` parts of a `for` header. */
6
6
  export type SimpleStatement = VariableDeclaration | ExpressionStatement | AssignmentStatement | IncDecStatement | DeleteStatement | SendStatement;
7
- /** `ch <- value`: waits until the channel takes the value, so only in async functions. */
7
+ /**
8
+ * `ch <- value`: waits until the channel takes the value, so only in async functions. A send to a
9
+ * topic waits for nobody, so the parser, which does not know the type, leaves the error to the
10
+ * checker: `cannotWait` is the `<-` of a send where waiting is not allowed, and why.
11
+ */
8
12
  export interface SendStatement extends NodeBase {
9
13
  kind: 'SendStatement';
10
14
  channel: Expression;
11
15
  value: Expression;
16
+ cannotWait?: {
17
+ inDefer: boolean;
18
+ start: number;
19
+ end: number;
20
+ };
12
21
  }
13
22
  export interface BlockStatement extends NodeBase {
14
23
  kind: 'BlockStatement';
@@ -44,6 +53,8 @@ export interface ReturnStatement extends NodeBase {
44
53
  }
45
54
  export interface IfStatement extends NodeBase {
46
55
  kind: 'IfStatement';
56
+ /** `if const v, err = f(); err != null { }`: its variables are seen in every branch, as in Go. */
57
+ init: VariableDeclaration | null;
47
58
  condition: Expression;
48
59
  consequent: BlockStatement;
49
60
  /** `else { ... }` or `else if ...`. */
@@ -58,6 +69,8 @@ export interface IfStatement extends NodeBase {
58
69
  */
59
70
  export interface ForStatement extends NodeBase {
60
71
  kind: 'ForStatement';
72
+ /** `outer: for { ... }`, for `break outer` and `continue outer` in a nested loop. */
73
+ label?: Identifier;
61
74
  init: SimpleStatement | null;
62
75
  condition: Expression | null;
63
76
  update: SimpleStatement | null;
@@ -69,6 +82,7 @@ export interface ForStatement extends NodeBase {
69
82
  */
70
83
  export interface ForInStatement extends NodeBase {
71
84
  kind: 'ForInStatement';
85
+ label?: Identifier;
72
86
  key: Identifier | null;
73
87
  value: Identifier;
74
88
  iterable: Expression;
@@ -78,6 +92,9 @@ export interface ForInStatement extends NodeBase {
78
92
  /** `switch cmd { ... }`, or `switch { ... }` without a discriminant as an if/else chain. */
79
93
  export interface SwitchStatement extends NodeBase {
80
94
  kind: 'SwitchStatement';
95
+ label?: Identifier;
96
+ /** `switch const kind = node.kind; kind { }`, as the initializer of `if`. */
97
+ init: VariableDeclaration | null;
81
98
  discriminant: Expression | null;
82
99
  cases: SwitchCase[];
83
100
  }
@@ -94,6 +111,7 @@ export interface SwitchCase extends NodeBase {
94
111
  */
95
112
  export interface SelectStatement extends NodeBase {
96
113
  kind: 'SelectStatement';
114
+ label?: Identifier;
97
115
  cases: SelectCase[];
98
116
  }
99
117
  /** A case of `select`: a receive, `<-ch`, a send, `ch <- v`, or `default` when it is null. */
@@ -104,12 +122,18 @@ export interface SelectCase extends NodeBase {
104
122
  operation: UnaryExpression | SendStatement | null;
105
123
  body: Statement[];
106
124
  }
125
+ /** `break`, or `break outer`: the label of an enclosing loop, switch or select. */
107
126
  export interface BreakStatement extends NodeBase {
108
127
  kind: 'BreakStatement';
128
+ label?: Identifier;
109
129
  }
130
+ /** `continue`, or `continue outer`: the label of an enclosing loop. */
110
131
  export interface ContinueStatement extends NodeBase {
111
132
  kind: 'ContinueStatement';
133
+ label?: Identifier;
112
134
  }
135
+ /** The statements that can have a label. */
136
+ export type LabeledStatement = ForStatement | ForInStatement | SwitchStatement | SelectStatement;
113
137
  export interface ThrowStatement extends NodeBase {
114
138
  kind: 'ThrowStatement';
115
139
  argument: Expression;
@@ -0,0 +1,11 @@
1
+ import type { Completion, TextEdit } from './checker/checker.ts';
2
+ import type { CompletionList } from './completion.ts';
3
+ /** A fix of a diagnostic: the import of the name from one module. */
4
+ export interface ImportFix {
5
+ title: string;
6
+ edit: TextEdit;
7
+ }
8
+ /** The names of other modules that fit where the cursor is and that are not in scope there. */
9
+ export declare function importCompletions(source: string, filename: string, list: CompletionList): Completion[];
10
+ /** For an error about a name that is not defined, the imports of the modules that export it. */
11
+ export declare function importFixes(source: string, filename: string, message: string): ImportFix[];
@@ -0,0 +1,81 @@
1
+ import { dirname, relative, resolve, sep } from 'node:path';
2
+ import { markupFor } from "./config.js";
3
+ import { parse } from "./parser/parser.js";
4
+ import { projectExports } from "./project-modules.js";
5
+ const SPACES = {
6
+ value: 'value',
7
+ type: 'type',
8
+ tag: 'tag',
9
+ };
10
+ /** The names of other modules that fit where the cursor is and that are not in scope there. */
11
+ export function importCompletions(source, filename, list) {
12
+ const space = list.context && SPACES[list.context];
13
+ if (!space)
14
+ return [];
15
+ const known = new Set(list.items.map((item) => item.name));
16
+ const candidates = projectExports(filename).filter((each) => each.spaces.includes(space) && !known.has(each.name));
17
+ if (candidates.length === 0)
18
+ return [];
19
+ const imports = importsOf(source, filename);
20
+ return candidates.map((each) => {
21
+ const specifier = specifierOf(filename, each.path);
22
+ return {
23
+ name: each.name,
24
+ kind: each.kind,
25
+ import: { specifier, edit: importEdit(source, imports, each.name, specifier, each.path) },
26
+ };
27
+ });
28
+ }
29
+ const UNDEFINED = [
30
+ [/^"(.+)" is not defined$/, 'value'],
31
+ [/^unknown type "(.+)"$/, 'type'],
32
+ [/^unknown component <(.+)>$/, 'tag'],
33
+ [/^unknown decorator @(.+)$/, 'decorator'],
34
+ ];
35
+ /** For an error about a name that is not defined, the imports of the modules that export it. */
36
+ export function importFixes(source, filename, message) {
37
+ for (const [pattern, space] of UNDEFINED) {
38
+ const name = pattern.exec(message)?.[1];
39
+ if (name === undefined)
40
+ continue;
41
+ const imports = importsOf(source, filename);
42
+ return projectExports(filename)
43
+ .filter((each) => each.name === name && each.spaces.includes(space))
44
+ .map((each) => {
45
+ const specifier = specifierOf(filename, each.path);
46
+ return {
47
+ title: `Add import { ${name} } from "${specifier}"`,
48
+ edit: importEdit(source, imports, name, specifier, each.path),
49
+ };
50
+ });
51
+ }
52
+ return [];
53
+ }
54
+ /** The imports of the module, with the absolute paths of the modules they import. */
55
+ function importsOf(source, filename) {
56
+ const { program } = parse(source, { markup: markupFor(filename) });
57
+ return program.body.flatMap((statement) => statement.kind === 'ImportDeclaration'
58
+ ? [[statement, resolve(dirname(filename), statement.source.value)]]
59
+ : []);
60
+ }
61
+ /** The path of a module as an import of the file `filename` writes it: "./card.rk". */
62
+ function specifierOf(filename, path) {
63
+ const specifier = relative(dirname(resolve(filename)), path)
64
+ .split(sep)
65
+ .join('/');
66
+ return specifier.startsWith('../') ? specifier : `./${specifier}`;
67
+ }
68
+ function importEdit(source, imports, name, specifier, path) {
69
+ const same = imports.find(([node, from]) => from === path && node.namedImports.length > 0);
70
+ const last = same?.[0].namedImports.at(-1);
71
+ if (last)
72
+ return { start: last.end, end: last.end, text: `, ${name}` };
73
+ const line = `import { ${name} } from "${specifier}"`;
74
+ const after = imports.at(-1)?.[0];
75
+ if (!after)
76
+ return { start: 0, end: 0, text: `${line}\n\n` };
77
+ const end = source.indexOf('\n', after.end);
78
+ return end === -1
79
+ ? { start: source.length, end: source.length, text: `\n${line}` }
80
+ : { start: end + 1, end: end + 1, text: `${line}\n` };
81
+ }
@@ -0,0 +1,6 @@
1
+ import type * as ast from './ast.ts';
2
+ /**
3
+ * Whether a `break` in these statements leaves the enclosing loop or switch: an unlabeled one
4
+ * outside of nested loops and switches, or `break label` anywhere when `label` is the statement's.
5
+ */
6
+ export declare function containsBreak(statements: readonly ast.Statement[], label?: ast.Identifier | null): boolean;
package/dist/breaks.js ADDED
@@ -0,0 +1,36 @@
1
+ import { forEachChild } from "./walk.js";
2
+ /**
3
+ * Whether a `break` in these statements leaves the enclosing loop or switch: an unlabeled one
4
+ * outside of nested loops and switches, or `break label` anywhere when `label` is the statement's.
5
+ */
6
+ export function containsBreak(statements, label = null) {
7
+ let found = false;
8
+ const visit = (node, nested) => {
9
+ switch (node.kind) {
10
+ case 'BreakStatement':
11
+ if (node.label ? node.label.name === label?.name : !nested)
12
+ found = true;
13
+ return;
14
+ // An unlabeled `break` inside these belongs to them.
15
+ case 'ForStatement':
16
+ case 'ForInStatement':
17
+ case 'SwitchStatement':
18
+ case 'SelectStatement':
19
+ if (label !== null)
20
+ forEachChild(node, (child) => visit(child, true));
21
+ return;
22
+ case 'FuncDeclaration':
23
+ case 'FuncExpression':
24
+ case 'ArrowFunction':
25
+ case 'ClassDeclaration':
26
+ case 'MountStatement':
27
+ case 'EffectStatement':
28
+ return;
29
+ default:
30
+ forEachChild(node, (child) => visit(child, nested));
31
+ }
32
+ };
33
+ for (const statement of statements)
34
+ visit(statement, false);
35
+ return found;
36
+ }
package/dist/build.d.ts CHANGED
@@ -9,6 +9,8 @@ export interface BuildOptions {
9
9
  declarations?: boolean;
10
10
  /** Markup and components for every file; by default as its package.json says (config.ts). */
11
11
  markup?: boolean;
12
+ /** Write a source map next to each `.js`, `app.js.map`. Defaults to `true`. */
13
+ sourceMap?: boolean;
12
14
  }
13
15
  export interface BuildOutput {
14
16
  /** Absolute path of the `.rk` file. */
@@ -26,6 +28,11 @@ export interface BuildOutput {
26
28
  output: string;
27
29
  code: string;
28
30
  };
31
+ /** The source maps of the module and of its worker module, `.js.map`, unless turned off. */
32
+ maps?: {
33
+ output: string;
34
+ code: string;
35
+ }[];
29
36
  }
30
37
  /** The errors or the warnings of a file. */
31
38
  export interface BuildError {
package/dist/build.js CHANGED
@@ -1,6 +1,7 @@
1
1
  import { existsSync, readdirSync, readFileSync, statSync } from 'node:fs';
2
- import { dirname, join, relative, resolve, sep } from 'node:path';
2
+ import { basename, dirname, join, relative, resolve, sep } from 'node:path';
3
3
  import { compile } from "./index.js";
4
+ import { sourceMapComment } from "./source-map.js";
4
5
  import { SourceFile } from "./source.js";
5
6
  /**
6
7
  * Compiles `.rk` files and directories (recursively), together with every `.rk` file they
@@ -47,6 +48,8 @@ export function build(entries, options = {}) {
47
48
  code: result.code,
48
49
  declarations: result.declarations,
49
50
  worker: result.worker,
51
+ map: result.map,
52
+ workerMap: result.workerMap,
50
53
  });
51
54
  }
52
55
  for (const specifier of result.dependencies) {
@@ -62,15 +65,33 @@ export function build(entries, options = {}) {
62
65
  return { outputs: [], errors, warnings };
63
66
  // The output keeps the folder structure below the given directories and all the files.
64
67
  const root = commonDirectory(queue, directories);
65
- const outputs = compiled.map(({ source, code, declarations: types, worker }) => {
68
+ const outputs = compiled.map((each) => {
69
+ const { source, declarations: types } = each;
66
70
  const target = options.outDir ? join(resolve(options.outDir), relative(root, source)) : source;
67
71
  const base = target.replace(/\.rk$/, '');
72
+ const maps = [];
73
+ // The code ends with where its map is, and the map says where the source is, from there.
74
+ const mapped = (code, output, map) => {
75
+ if (options.sourceMap === false || map === undefined)
76
+ return code;
77
+ const sources = [relative(dirname(output), source).split(sep).join('/')];
78
+ maps.push({
79
+ output: `${output}.map`,
80
+ code: JSON.stringify({ ...map, file: basename(output), sources }),
81
+ });
82
+ return `${code}${sourceMapComment(`${basename(output)}.map`)}`;
83
+ };
84
+ const code = mapped(each.code, `${base}.js`, each.map);
85
+ const worker = each.worker === undefined
86
+ ? undefined
87
+ : mapped(each.worker, `${base}.worker.js`, each.workerMap);
68
88
  return {
69
89
  source,
70
90
  output: `${base}.js`,
71
91
  code,
72
92
  ...(types === undefined ? {} : { declarations: { output: `${base}.d.ts`, code: types } }),
73
93
  ...(worker === undefined ? {} : { worker: { output: `${base}.worker.js`, code: worker } }),
94
+ ...(maps.length > 0 ? { maps } : {}),
74
95
  };
75
96
  });
76
97
  return { outputs, errors, warnings };
@@ -0,0 +1,6 @@
1
+ import type * as ast from '../ast.ts';
2
+ import { ExpressionChecker } from './expressions.ts';
3
+ import { type Type } from './types.ts';
4
+ export declare abstract class ArrayLiteralChecker extends ExpressionChecker {
5
+ protected checkArrayLiteral(node: ast.ArrayLiteral, expected: Type | null): Type;
6
+ }
@@ -0,0 +1,59 @@
1
+ import { ExpressionChecker } from "./expressions.js";
2
+ import { ANY, arrayOf, commonType, NEVER, nonNull, typeToString, UNKNOWN, } from "./types.js";
3
+ // Array literals: the type of their elements is the expected one, or the common type of all of
4
+ // them, `[1, null]` is a `[]?number`. A literal without spreads also keeps the types of its
5
+ // elements by position, so that a call infers `K` and `V` of a tuple `[K, V]` of a `.d.ts` from
6
+ // `["a", 1]`: `new Map([["a", 1]])` is a `Map<string, number>` (types/params.ts).
7
+ export class ArrayLiteralChecker extends ExpressionChecker {
8
+ checkArrayLiteral(node, expected) {
9
+ const context = expected ? nonNull(expected) : null;
10
+ const expectedElement = context?.kind === 'array' ? context.element : context?.kind === 'any' ? ANY : null;
11
+ let element = expectedElement ?? NEVER;
12
+ const positions = [];
13
+ for (const item of node.elements) {
14
+ let type;
15
+ if (item.kind === 'SpreadElement') {
16
+ const spread = this.checkValue(item.argument, expectedElement && arrayOf(expectedElement));
17
+ if (spread.kind === 'array')
18
+ type = spread.element;
19
+ else if (spread.kind === 'any' || spread.kind === 'unknown')
20
+ type = spread;
21
+ // JS spreads any iterable: a Set, a typed array, a `[]byte`.
22
+ else if (spread.kind === 'object' && spread.iterates)
23
+ type = spread.iterates;
24
+ else {
25
+ this.error(`cannot spread ${typeToString(spread)}: it is not an array or an iterable`, item.argument);
26
+ type = UNKNOWN;
27
+ }
28
+ }
29
+ else {
30
+ type = this.checkValue(item, expectedElement);
31
+ positions.push(type);
32
+ }
33
+ if (expectedElement) {
34
+ this.expectAssignable(type, expectedElement, item.kind === 'SpreadElement' ? item.argument : item);
35
+ }
36
+ else {
37
+ element = commonElement(element, type);
38
+ }
39
+ }
40
+ return positions.length === node.elements.length && positions.length > 0
41
+ ? arrayOf(element, positions)
42
+ : arrayOf(element);
43
+ }
44
+ }
45
+ /**
46
+ * The common type of the elements so far and the next one; of arrays with positions, by position,
47
+ * so that `[["a", 1], ["b", "x"]]` gives a `V` of `number | string`.
48
+ */
49
+ function commonElement(element, next) {
50
+ const common = commonType(element, next);
51
+ if (common.kind !== 'array' ||
52
+ element.kind !== 'array' ||
53
+ next.kind !== 'array' ||
54
+ element.positions?.length !== next.positions?.length ||
55
+ !element.positions) {
56
+ return common;
57
+ }
58
+ return arrayOf(common.element, element.positions.map((each, i) => commonType(each, next.positions[i])));
59
+ }
@@ -10,6 +10,8 @@ export declare abstract class AssignmentChecker extends InitializerChecker {
10
10
  protected checkAssignment(node: ast.AssignmentStatement): void;
11
11
  /** The type a value assigned to `target` must have. */
12
12
  protected checkTarget(target: ast.Expression): Type;
13
+ /** `this.id = id` in the constructor of the class that has the readonly field `id`. */
14
+ private initializes;
13
15
  protected checkReturn(node: ast.ReturnStatement): void;
14
16
  /**
15
17
  * `return null, err` in a function of `(User, error)`: null takes the place of a value only