rooklang 0.0.0-stage → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (314) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +177 -2
  3. package/README.ru.md +178 -0
  4. package/dist/ast/declarations.d.ts +185 -0
  5. package/dist/ast/declarations.js +1 -0
  6. package/dist/ast/expressions.d.ts +201 -0
  7. package/dist/ast/expressions.js +1 -0
  8. package/dist/ast/markup.d.ts +141 -0
  9. package/dist/ast/markup.js +1 -0
  10. package/dist/ast/statements.d.ts +173 -0
  11. package/dist/ast/statements.js +1 -0
  12. package/dist/ast.d.ts +18 -0
  13. package/dist/ast.js +18 -0
  14. package/dist/attrs.d.ts +8 -0
  15. package/dist/attrs.js +23 -0
  16. package/dist/browser.d.ts +30 -0
  17. package/dist/browser.js +36 -0
  18. package/dist/build.d.ts +48 -0
  19. package/dist/build.js +112 -0
  20. package/dist/checker/assignments.d.ts +14 -0
  21. package/dist/checker/assignments.js +197 -0
  22. package/dist/checker/base.d.ts +124 -0
  23. package/dist/checker/base.js +180 -0
  24. package/dist/checker/bigint.d.ts +15 -0
  25. package/dist/checker/bigint.js +85 -0
  26. package/dist/checker/builtins.d.ts +9 -0
  27. package/dist/checker/builtins.js +307 -0
  28. package/dist/checker/bytes.d.ts +11 -0
  29. package/dist/checker/bytes.js +72 -0
  30. package/dist/checker/calls.d.ts +23 -0
  31. package/dist/checker/calls.js +210 -0
  32. package/dist/checker/channel.d.ts +14 -0
  33. package/dist/checker/channel.js +44 -0
  34. package/dist/checker/channels.d.ts +9 -0
  35. package/dist/checker/channels.js +95 -0
  36. package/dist/checker/checker.d.ts +4 -0
  37. package/dist/checker/checker.js +22 -0
  38. package/dist/checker/classes.d.ts +24 -0
  39. package/dist/checker/classes.js +223 -0
  40. package/dist/checker/completion-names.d.ts +11 -0
  41. package/dist/checker/completion-names.js +100 -0
  42. package/dist/checker/completion.d.ts +25 -0
  43. package/dist/checker/completion.js +222 -0
  44. package/dist/checker/component-values.d.ts +11 -0
  45. package/dist/checker/component-values.js +88 -0
  46. package/dist/checker/components.d.ts +37 -0
  47. package/dist/checker/components.js +287 -0
  48. package/dist/checker/context.d.ts +196 -0
  49. package/dist/checker/context.js +35 -0
  50. package/dist/checker/control-flow.d.ts +24 -0
  51. package/dist/checker/control-flow.js +237 -0
  52. package/dist/checker/declarations.d.ts +26 -0
  53. package/dist/checker/declarations.js +259 -0
  54. package/dist/checker/decorator-exports.d.ts +5 -0
  55. package/dist/checker/decorator-exports.js +40 -0
  56. package/dist/checker/decorator-members.d.ts +28 -0
  57. package/dist/checker/decorator-members.js +140 -0
  58. package/dist/checker/decorator-wrappers.d.ts +20 -0
  59. package/dist/checker/decorator-wrappers.js +132 -0
  60. package/dist/checker/decorators.d.ts +28 -0
  61. package/dist/checker/decorators.js +265 -0
  62. package/dist/checker/dom-svg.d.ts +7 -0
  63. package/dist/checker/dom-svg.js +19 -0
  64. package/dist/checker/dom.d.ts +22 -0
  65. package/dist/checker/dom.js +361 -0
  66. package/dist/checker/expressions.d.ts +32 -0
  67. package/dist/checker/expressions.js +244 -0
  68. package/dist/checker/flow.d.ts +18 -0
  69. package/dist/checker/flow.js +134 -0
  70. package/dist/checker/forwarding.d.ts +15 -0
  71. package/dist/checker/forwarding.js +78 -0
  72. package/dist/checker/functions.d.ts +24 -0
  73. package/dist/checker/functions.js +154 -0
  74. package/dist/checker/go-for.d.ts +12 -0
  75. package/dist/checker/go-for.js +93 -0
  76. package/dist/checker/goroutines.d.ts +19 -0
  77. package/dist/checker/goroutines.js +110 -0
  78. package/dist/checker/helpers.d.ts +35 -0
  79. package/dist/checker/helpers.js +162 -0
  80. package/dist/checker/imports.d.ts +7 -0
  81. package/dist/checker/imports.js +83 -0
  82. package/dist/checker/initializers.d.ts +22 -0
  83. package/dist/checker/initializers.js +104 -0
  84. package/dist/checker/iterables.d.ts +5 -0
  85. package/dist/checker/iterables.js +17 -0
  86. package/dist/checker/iterators.d.ts +13 -0
  87. package/dist/checker/iterators.js +95 -0
  88. package/dist/checker/loops.d.ts +10 -0
  89. package/dist/checker/loops.js +82 -0
  90. package/dist/checker/markup.d.ts +28 -0
  91. package/dist/checker/markup.js +220 -0
  92. package/dist/checker/members.d.ts +29 -0
  93. package/dist/checker/members.js +233 -0
  94. package/dist/checker/operators.d.ts +13 -0
  95. package/dist/checker/operators.js +177 -0
  96. package/dist/checker/promise.d.ts +17 -0
  97. package/dist/checker/promise.js +49 -0
  98. package/dist/checker/refs.d.ts +26 -0
  99. package/dist/checker/refs.js +136 -0
  100. package/dist/checker/resolution.d.ts +23 -0
  101. package/dist/checker/resolution.js +173 -0
  102. package/dist/checker/statements.d.ts +28 -0
  103. package/dist/checker/statements.js +212 -0
  104. package/dist/checker/styles.d.ts +14 -0
  105. package/dist/checker/styles.js +49 -0
  106. package/dist/checker/transfer.d.ts +5 -0
  107. package/dist/checker/transfer.js +83 -0
  108. package/dist/checker/types/bytes.d.ts +4 -0
  109. package/dist/checker/types/bytes.js +51 -0
  110. package/dist/checker/types/classes.d.ts +22 -0
  111. package/dist/checker/types/classes.js +93 -0
  112. package/dist/checker/types/constructors.d.ts +27 -0
  113. package/dist/checker/types/constructors.js +129 -0
  114. package/dist/checker/types/generics.d.ts +20 -0
  115. package/dist/checker/types/generics.js +75 -0
  116. package/dist/checker/types/lazy.d.ts +30 -0
  117. package/dist/checker/types/lazy.js +92 -0
  118. package/dist/checker/types/model.d.ts +149 -0
  119. package/dist/checker/types/model.js +15 -0
  120. package/dist/checker/types/params.d.ts +7 -0
  121. package/dist/checker/types/params.js +220 -0
  122. package/dist/checker/types/printing.d.ts +6 -0
  123. package/dist/checker/types/printing.js +109 -0
  124. package/dist/checker/types/relations.d.ts +16 -0
  125. package/dist/checker/types/relations.js +294 -0
  126. package/dist/checker/types.d.ts +9 -0
  127. package/dist/checker/types.js +10 -0
  128. package/dist/cli.d.ts +2 -0
  129. package/dist/cli.js +193 -0
  130. package/dist/codegen/analysis.d.ts +18 -0
  131. package/dist/codegen/analysis.js +144 -0
  132. package/dist/codegen/attributes.d.ts +40 -0
  133. package/dist/codegen/attributes.js +227 -0
  134. package/dist/codegen/calls.d.ts +22 -0
  135. package/dist/codegen/calls.js +106 -0
  136. package/dist/codegen/chan-helpers.d.ts +2 -0
  137. package/dist/codegen/chan-helpers.js +253 -0
  138. package/dist/codegen/component-helpers.d.ts +33 -0
  139. package/dist/codegen/component-helpers.js +18 -0
  140. package/dist/codegen/component-props.d.ts +42 -0
  141. package/dist/codegen/component-props.js +173 -0
  142. package/dist/codegen/component-values.d.ts +27 -0
  143. package/dist/codegen/component-values.js +146 -0
  144. package/dist/codegen/components.d.ts +53 -0
  145. package/dist/codegen/components.js +268 -0
  146. package/dist/codegen/control-flow.d.ts +25 -0
  147. package/dist/codegen/control-flow.js +152 -0
  148. package/dist/codegen/control-statements.d.ts +28 -0
  149. package/dist/codegen/control-statements.js +226 -0
  150. package/dist/codegen/creation.d.ts +31 -0
  151. package/dist/codegen/creation.js +113 -0
  152. package/dist/codegen/early-returns.d.ts +59 -0
  153. package/dist/codegen/early-returns.js +244 -0
  154. package/dist/codegen/element-helpers.d.ts +12 -0
  155. package/dist/codegen/element-helpers.js +54 -0
  156. package/dist/codegen/elements.d.ts +71 -0
  157. package/dist/codegen/elements.js +293 -0
  158. package/dist/codegen/emitter.d.ts +135 -0
  159. package/dist/codegen/emitter.js +296 -0
  160. package/dist/codegen/expressions.d.ts +49 -0
  161. package/dist/codegen/expressions.js +262 -0
  162. package/dist/codegen/functions.d.ts +28 -0
  163. package/dist/codegen/functions.js +170 -0
  164. package/dist/codegen/go-helpers.d.ts +9 -0
  165. package/dist/codegen/go-helpers.js +307 -0
  166. package/dist/codegen/helpers.d.ts +10 -0
  167. package/dist/codegen/helpers.js +356 -0
  168. package/dist/codegen/imported.d.ts +38 -0
  169. package/dist/codegen/imported.js +71 -0
  170. package/dist/codegen/js.d.ts +20 -0
  171. package/dist/codegen/js.js +113 -0
  172. package/dist/codegen/mutations.d.ts +10 -0
  173. package/dist/codegen/mutations.js +102 -0
  174. package/dist/codegen/parallel.d.ts +7 -0
  175. package/dist/codegen/parallel.js +63 -0
  176. package/dist/codegen/reactive.d.ts +95 -0
  177. package/dist/codegen/reactive.js +280 -0
  178. package/dist/codegen/refs.d.ts +62 -0
  179. package/dist/codegen/refs.js +160 -0
  180. package/dist/codegen/select-helpers.d.ts +2 -0
  181. package/dist/codegen/select-helpers.js +135 -0
  182. package/dist/codegen/simple-statements.d.ts +42 -0
  183. package/dist/codegen/simple-statements.js +253 -0
  184. package/dist/codegen/statements.d.ts +22 -0
  185. package/dist/codegen/statements.js +142 -0
  186. package/dist/codegen/styles.d.ts +54 -0
  187. package/dist/codegen/styles.js +157 -0
  188. package/dist/codegen/syntax.d.ts +18 -0
  189. package/dist/codegen/syntax.js +70 -0
  190. package/dist/codegen/thread-helpers.d.ts +2 -0
  191. package/dist/codegen/thread-helpers.js +198 -0
  192. package/dist/codegen/updates.d.ts +51 -0
  193. package/dist/codegen/updates.js +201 -0
  194. package/dist/codegen/web-components.d.ts +20 -0
  195. package/dist/codegen/web-components.js +149 -0
  196. package/dist/compile-file.d.ts +11 -0
  197. package/dist/compile-file.js +24 -0
  198. package/dist/compile.d.ts +53 -0
  199. package/dist/compile.js +187 -0
  200. package/dist/completion.d.ts +10 -0
  201. package/dist/completion.js +134 -0
  202. package/dist/config.d.ts +2 -0
  203. package/dist/config.js +44 -0
  204. package/dist/css/hash.d.ts +1 -0
  205. package/dist/css/hash.js +10 -0
  206. package/dist/css/parse.d.ts +42 -0
  207. package/dist/css/parse.js +138 -0
  208. package/dist/css/print.d.ts +9 -0
  209. package/dist/css/print.js +35 -0
  210. package/dist/css/selectors.d.ts +29 -0
  211. package/dist/css/selectors.js +168 -0
  212. package/dist/declarations.d.ts +3 -0
  213. package/dist/declarations.js +207 -0
  214. package/dist/decorator-copy.d.ts +18 -0
  215. package/dist/decorator-copy.js +158 -0
  216. package/dist/decorators.d.ts +61 -0
  217. package/dist/decorators.js +241 -0
  218. package/dist/diagnostics.d.ts +17 -0
  219. package/dist/diagnostics.js +22 -0
  220. package/dist/go-for.d.ts +26 -0
  221. package/dist/go-for.js +155 -0
  222. package/dist/goroutines.d.ts +37 -0
  223. package/dist/goroutines.js +253 -0
  224. package/dist/html-tag.d.ts +21 -0
  225. package/dist/html-tag.js +103 -0
  226. package/dist/index.d.ts +33 -0
  227. package/dist/index.js +55 -0
  228. package/dist/inline-packages.d.ts +32 -0
  229. package/dist/inline-packages.js +129 -0
  230. package/dist/iterators.d.ts +10 -0
  231. package/dist/iterators.js +33 -0
  232. package/dist/lexer/base.d.ts +24 -0
  233. package/dist/lexer/base.js +87 -0
  234. package/dist/lexer/lexer.d.ts +8 -0
  235. package/dist/lexer/lexer.js +134 -0
  236. package/dist/lexer/literals.d.ts +18 -0
  237. package/dist/lexer/literals.js +203 -0
  238. package/dist/lexer/markup.d.ts +10 -0
  239. package/dist/lexer/markup.js +93 -0
  240. package/dist/lexer/style.d.ts +17 -0
  241. package/dist/lexer/style.js +97 -0
  242. package/dist/lexer/tables.d.ts +53 -0
  243. package/dist/lexer/tables.js +101 -0
  244. package/dist/lexer/token.d.ts +42 -0
  245. package/dist/lexer/token.js +108 -0
  246. package/dist/loader.d.ts +1 -0
  247. package/dist/loader.js +50 -0
  248. package/dist/methods.d.ts +5 -0
  249. package/dist/methods.js +81 -0
  250. package/dist/modules.d.ts +3 -0
  251. package/dist/modules.js +49 -0
  252. package/dist/names.d.ts +23 -0
  253. package/dist/names.js +267 -0
  254. package/dist/parser/base.d.ts +96 -0
  255. package/dist/parser/base.js +202 -0
  256. package/dist/parser/classes.d.ts +8 -0
  257. package/dist/parser/classes.js +164 -0
  258. package/dist/parser/control-flow.d.ts +29 -0
  259. package/dist/parser/control-flow.js +232 -0
  260. package/dist/parser/declarations.d.ts +27 -0
  261. package/dist/parser/declarations.js +238 -0
  262. package/dist/parser/decorators.d.ts +30 -0
  263. package/dist/parser/decorators.js +176 -0
  264. package/dist/parser/expressions.d.ts +15 -0
  265. package/dist/parser/expressions.js +255 -0
  266. package/dist/parser/literals.d.ts +27 -0
  267. package/dist/parser/literals.js +206 -0
  268. package/dist/parser/markup.d.ts +22 -0
  269. package/dist/parser/markup.js +193 -0
  270. package/dist/parser/parser.d.ts +14 -0
  271. package/dist/parser/parser.js +22 -0
  272. package/dist/parser/simple-statements.d.ts +22 -0
  273. package/dist/parser/simple-statements.js +156 -0
  274. package/dist/parser/statements.d.ts +37 -0
  275. package/dist/parser/statements.js +267 -0
  276. package/dist/parser/syntax.d.ts +51 -0
  277. package/dist/parser/syntax.js +173 -0
  278. package/dist/parser/types.d.ts +23 -0
  279. package/dist/parser/types.js +218 -0
  280. package/dist/recursion.d.ts +25 -0
  281. package/dist/recursion.js +170 -0
  282. package/dist/refs.d.ts +49 -0
  283. package/dist/refs.js +210 -0
  284. package/dist/run.d.ts +1 -0
  285. package/dist/run.js +21 -0
  286. package/dist/source.d.ts +15 -0
  287. package/dist/source.js +34 -0
  288. package/dist/style-modules.d.ts +17 -0
  289. package/dist/style-modules.js +49 -0
  290. package/dist/styles.d.ts +36 -0
  291. package/dist/styles.js +160 -0
  292. package/dist/svg.d.ts +8 -0
  293. package/dist/svg.js +77 -0
  294. package/dist/ts-types.d.ts +11 -0
  295. package/dist/ts-types.js +133 -0
  296. package/dist/typed-arrays.d.ts +8 -0
  297. package/dist/typed-arrays.js +43 -0
  298. package/dist/typescript/convert-base.d.ts +42 -0
  299. package/dist/typescript/convert-base.js +135 -0
  300. package/dist/typescript/convert-objects.d.ts +28 -0
  301. package/dist/typescript/convert-objects.js +245 -0
  302. package/dist/typescript/convert.d.ts +14 -0
  303. package/dist/typescript/convert.js +67 -0
  304. package/dist/typescript/importer.d.ts +37 -0
  305. package/dist/typescript/importer.js +152 -0
  306. package/dist/typescript/library.d.ts +23 -0
  307. package/dist/typescript/library.js +81 -0
  308. package/dist/vite.d.ts +11 -0
  309. package/dist/vite.js +86 -0
  310. package/dist/walk-types.d.ts +4 -0
  311. package/dist/walk-types.js +56 -0
  312. package/dist/walk.d.ts +5 -0
  313. package/dist/walk.js +315 -0
  314. package/package.json +104 -4
@@ -0,0 +1,170 @@
1
+ import { isIterator } from "../iterators.js";
2
+ import { declarationsOf, findDefers } from "./analysis.js";
3
+ import { WebComponentEmitter } from "./web-components.js";
4
+ import { ARROW, POSTFIX } from "./syntax.js";
5
+ /** Function bodies and `defer`. */
6
+ export class FunctionEmitter extends WebComponentEmitter {
7
+ /** Parameters and body of a function, method or func literal. */
8
+ func(params, body) {
9
+ const defers = findDefers(body);
10
+ const mode = defers.length === 0
11
+ ? 'none'
12
+ : defers.every((defer) => body.body.includes(defer))
13
+ ? 'try'
14
+ : 'stack';
15
+ return this.withFunction(mode, params, () => {
16
+ // An iterator is a generator: it takes no function, and `yield(v)` yields (calls.ts).
17
+ this.fn.iterator = isIterator(params);
18
+ const paramList = this.fn.iterator ? '' : this.params(params);
19
+ const text = this.block(() => {
20
+ this.withScope(declarationsOf(body.body), () => {
21
+ if (mode === 'try') {
22
+ this.statementsWithDefers(body.body);
23
+ }
24
+ else if (mode === 'stack') {
25
+ this.helpers.add('runDeferred');
26
+ this.line('const $$defer = [];');
27
+ const statements = this.block(() => this.statements(body.body));
28
+ const finalizer = this.block(() => this.line('$$runDeferred($$defer);'));
29
+ this.line(`try ${statements} finally ${finalizer}`);
30
+ }
31
+ else {
32
+ this.statements(body.body);
33
+ }
34
+ });
35
+ });
36
+ return [paramList, text];
37
+ });
38
+ }
39
+ /**
40
+ * `effect { ... }` becomes a function, `$$effect5`, which runs once the markup is created and
41
+ * again after the updates of what it reads (see EarlyReturnEmitter). What it defers is run before
42
+ * it runs again, and when `{if}`, `{switch}` or `{for}` removes the component.
43
+ */
44
+ effectStatement(node) {
45
+ const id = this.nextId();
46
+ const name = `$$effect${id}`;
47
+ const cleanups = findDefers(node.body).length > 0 ? `$$cleanups${id}` : null;
48
+ this.inHandler++;
49
+ try {
50
+ const body = this.withRendering(0, () => cleanups === null ? this.func([], node.body)[1] : this.effectBody(node.body, cleanups));
51
+ if (cleanups !== null)
52
+ this.line(`const ${cleanups} = [];`);
53
+ this.line(`const ${name} = () => ${body};`);
54
+ }
55
+ finally {
56
+ this.inHandler--;
57
+ }
58
+ this.addEffect({ name, node, cleanups });
59
+ }
60
+ effectBody(body, cleanups) {
61
+ return this.withFunction('effect', [], () => {
62
+ this.fn.deferList = cleanups;
63
+ this.helpers.add('runDeferred');
64
+ return this.block(() => this.withScope(declarationsOf(body.body), () => {
65
+ this.line(`$$runDeferred(${cleanups}.splice(0));`);
66
+ this.statements(body.body);
67
+ }));
68
+ });
69
+ }
70
+ /** A `defer` in 'stack' and 'effect' modes; in 'try' mode statementsWithDefers handles them. */
71
+ deferStatement(node) {
72
+ const { body } = node;
73
+ if (body.kind === 'BlockStatement') {
74
+ const block = this.block(() => this.blockStatements(body.body));
75
+ this.line(`${this.fn.deferList}.push(() => ${block});`);
76
+ return;
77
+ }
78
+ // Temporaries for the arguments go into a block so that each loop iteration gets its own.
79
+ const lines = this.capture(() => this.line(`${this.fn.deferList}.push(() => ${this.deferredCall(body)});`));
80
+ if (lines.length === 1)
81
+ this.line(lines[0].trimStart());
82
+ else
83
+ this.line(`{\n${lines.join('\n')}\n${this.indent()}}`);
84
+ }
85
+ /** Statements of a body whose `defer`s are all at its top level: one try/finally per `defer`. */
86
+ statementsWithDefers(list) {
87
+ const index = list.findIndex((statement) => statement.kind === 'DeferStatement');
88
+ if (index === -1) {
89
+ this.statements(list);
90
+ return;
91
+ }
92
+ this.statements(list.slice(0, index));
93
+ const runDeferred = this.prepareDeferred(list[index]);
94
+ const rest = list.slice(index + 1);
95
+ if (rest.length === 0) {
96
+ // Nothing can happen between the `defer` and the end of the function.
97
+ runDeferred();
98
+ return;
99
+ }
100
+ const body = this.block(() => this.statementsWithDefers(rest));
101
+ this.line(`try ${body} finally ${this.block(runDeferred)}`);
102
+ }
103
+ /**
104
+ * Go evaluates the function and arguments of a deferred call at the `defer` statement. Values
105
+ * that cannot change in between are used as they are; others are saved to temporaries here.
106
+ * Returns a function that writes the deferred code.
107
+ */
108
+ prepareDeferred(node) {
109
+ const { body } = node;
110
+ if (body.kind === 'BlockStatement')
111
+ return () => this.blockStatements(body.body);
112
+ const call = this.deferredCall(body);
113
+ return () => this.line(`${call};`);
114
+ }
115
+ deferredCall(call) {
116
+ const { callee } = call;
117
+ let target;
118
+ if (callee.kind === 'MemberExpression') {
119
+ const object = this.memberObject(callee.object, (o) => this.stable(o, POSTFIX));
120
+ target = `${object}${callee.optional ? '?.' : '.'}${callee.property.name}`;
121
+ }
122
+ else if (callee.kind === 'IndexExpression') {
123
+ const object = this.memberObject(callee.object, (o) => this.stable(o, POSTFIX));
124
+ target = `${object}${callee.optional ? '?.[' : '['}${this.stable(callee.index, 0)}]`;
125
+ }
126
+ else if (this.isBuiltinError(callee)) {
127
+ target = 'Error';
128
+ }
129
+ else {
130
+ target = this.stable(callee, POSTFIX);
131
+ }
132
+ const args = call.arguments.map((arg) => arg.kind === 'SpreadElement'
133
+ ? `...${this.stable(arg.argument, ARROW)}`
134
+ : this.stable(arg, ARROW));
135
+ return `${target}${call.optional ? '?.' : ''}(${args.join(', ')})`;
136
+ }
137
+ /** The expression itself if its value cannot change before the deferred call runs. */
138
+ stable(node, precedence) {
139
+ if (this.isStable(node))
140
+ return this.expression(node, precedence);
141
+ const temp = this.temp();
142
+ this.line(`const ${temp} = ${this.expression(node, ARROW)};`);
143
+ return temp;
144
+ }
145
+ isStable(node) {
146
+ switch (node.kind) {
147
+ case 'NumberLiteral':
148
+ case 'StringLiteral':
149
+ case 'BooleanLiteral':
150
+ case 'NullLiteral':
151
+ case 'ThisExpression':
152
+ case 'SuperExpression':
153
+ return true;
154
+ case 'TemplateLiteral':
155
+ return node.expressions.every((expression) => this.isStable(expression));
156
+ case 'Identifier': {
157
+ const kind = this.lookup(node.name);
158
+ // A derived value is computed again by the updates, not by the code.
159
+ if (kind === 'derived')
160
+ return false;
161
+ if (kind === 'let' || kind === 'state' || kind === 'param' || kind === 'catch') {
162
+ return !this.assigned.has(node.name);
163
+ }
164
+ return true;
165
+ }
166
+ default:
167
+ return false;
168
+ }
169
+ }
170
+ }
@@ -0,0 +1,9 @@
1
+ export type GoHelper = 'go' | 'goLocal' | 'goFor' | 'shared' | 'serve' | 'cancel';
2
+ export declare const GO_HELPERS: Readonly<Record<GoHelper, readonly string[]>>;
3
+ /**
4
+ * The start of a worker of a module: a Web Worker, or a worker thread of Node. Node takes only the
5
+ * path of a `file:` URL, without the query of `app.rk?goroutines`, so its worker starts with a
6
+ * module that imports the whole URL. An absolute URL, such as the Blob of the playground, is used
7
+ * as it is.
8
+ */
9
+ export declare function startWorker(url: string): string[];
@@ -0,0 +1,307 @@
1
+ // The helpers of `go`: the pool of workers of the program, and the side of the worker. They work
2
+ // with Web Workers and with the worker threads of Node, whose API differs: `on("message")` instead
3
+ // of `onmessage`, and `unref()`, so that idle workers do not keep the program running.
4
+ export const GO_HELPERS = {
5
+ /** `go` in a module: a call in a worker of the pool, started by $$startWorker of the module. */
6
+ go: [
7
+ 'function $$go(name, args) {',
8
+ ' return (globalThis[Symbol.for("rook.goroutines")] ??= $$pool()).call(',
9
+ ' $$startWorker,',
10
+ ' name,',
11
+ ' $$copy(args, true),',
12
+ ' );',
13
+ '}',
14
+ '',
15
+ '// The workers of the program, shared by its modules: up to one per processor. Each runs the worker',
16
+ '// module of one module, `start`. Calls wait in one queue and go to a worker of their module that is',
17
+ '// free: one whose thread is not running code, though its earlier calls may wait in `await`. A',
18
+ '// worker that is still busy keeps its calls for later; an idle worker of another module makes room',
19
+ '// for a new one, unless it keeps channels.',
20
+ 'function $$pool() {',
21
+ ' const size = globalThis.navigator?.hardwareConcurrency ?? 4;',
22
+ ' const registry = $$registry();',
23
+ ' const workers = [];',
24
+ ' const queue = [];',
25
+ ' const calls = new Map();',
26
+ ' let ids = 0;',
27
+ ' // A worker without calls lets the program end, unless something here waits for it.',
28
+ ' const unref = (worker) => {',
29
+ ' if (worker.calls === 0 && !(registry.pins.get(worker.key) > 0)) worker.thread.unref?.();',
30
+ ' };',
31
+ ' registry.idle = (key) => {',
32
+ ' const worker = workers.find((each) => each.key === key);',
33
+ ' if (worker) unref(worker);',
34
+ ' };',
35
+ ' const send = (worker, call) => {',
36
+ ' worker.busy = true;',
37
+ ' worker.calls++;',
38
+ ' worker.thread.ref?.();',
39
+ ' call.worker = worker;',
40
+ ' calls.set(call.id, call);',
41
+ ' $$post(worker.key, { call: call.id, name: call.name, args: call.args });',
42
+ ' call.args = null;',
43
+ ' };',
44
+ ' const dispatch = () => {',
45
+ ' const starting = new Map();',
46
+ ' for (const worker of workers) {',
47
+ ' if (!worker.key) starting.set(worker.start, (starting.get(worker.start) ?? 0) + 1);',
48
+ ' }',
49
+ ' for (let i = 0; i < queue.length; i++) {',
50
+ ' const call = queue[i];',
51
+ ' let free = null;',
52
+ ' for (const worker of workers) {',
53
+ ' if (worker.start !== call.start || !worker.key || worker.busy) continue;',
54
+ ' if (!free || worker.calls < free.calls) free = worker;',
55
+ ' }',
56
+ ' // A free worker without calls; else a new worker; else one whose calls wait in await.',
57
+ ' if (free?.calls === 0 || (free && workers.length >= size)) {',
58
+ ' queue.splice(i--, 1);',
59
+ ' send(free, call);',
60
+ ' } else if (starting.get(call.start) > 0) {',
61
+ ' starting.set(call.start, starting.get(call.start) - 1);',
62
+ ' } else if (workers.length < size || retire(call.start)) {',
63
+ ' add(call.start);',
64
+ ' } else {',
65
+ ' // It waits for a busy worker of its module, which may have let go of the program when',
66
+ ' // its last call returned: the program runs on until that worker is free and takes it.',
67
+ ' for (const worker of workers) if (worker.start === call.start && worker.key) worker.thread.ref?.();',
68
+ ' }',
69
+ ' }',
70
+ ' };',
71
+ ' const retire = (start) => {',
72
+ ' const idle = workers.find(',
73
+ ' (worker) => worker.key && worker.start !== start && !worker.busy && worker.calls === 0 && !worker.owns,',
74
+ ' );',
75
+ ' if (!idle) return false;',
76
+ ' idle.thread.terminate();',
77
+ ' remove(idle, null);',
78
+ ' return true;',
79
+ ' };',
80
+ ' // A worker that stopped: its calls fail, and the threads that talked to it forget it.',
81
+ ' const remove = (worker, error) => {',
82
+ ' workers.splice(workers.indexOf(worker), 1);',
83
+ ' for (const [id, call] of calls) {',
84
+ ' if (call.worker !== worker) continue;',
85
+ ' calls.delete(id);',
86
+ ' call.reject(error);',
87
+ ' }',
88
+ ' if (worker.key) {',
89
+ ' $$gone(worker.key);',
90
+ ' for (const other of workers) other.key && other.thread.postMessage({ gone: worker.key });',
91
+ ' }',
92
+ ' };',
93
+ ' const fail = (worker, error) => {',
94
+ ' remove(worker, error);',
95
+ ' dispatch();',
96
+ ' };',
97
+ ' const receive = (worker, data) => {',
98
+ ' if ("op" in data || "answer" in data) return $$incoming(worker.key, data);',
99
+ ' if ("hello" in data) {',
100
+ ' worker.key = data.hello;',
101
+ ' $$connect(worker.key, worker.thread, false);',
102
+ ' worker.thread.postMessage({ init: registry.key });',
103
+ ' // Workers talk to each other through ports of their own, also while this thread is busy.',
104
+ ' for (const other of workers) {',
105
+ ' if (other === worker || !other.key) continue;',
106
+ ' const { port1, port2 } = new MessageChannel();',
107
+ ' worker.thread.postMessage({ port: other.key, through: port1 }, [port1]);',
108
+ ' other.thread.postMessage({ port: worker.key, through: port2 }, [port2]);',
109
+ ' }',
110
+ ' worker.busy = false;',
111
+ ' unref(worker);',
112
+ ' } else if ("free" in data) {',
113
+ ' worker.busy = false;',
114
+ ' worker.owns = data.owns;',
115
+ ' } else if ("returned" in data) {',
116
+ ' $$adopt(data);',
117
+ ' const call = calls.get(data.returned);',
118
+ ' calls.delete(data.returned);',
119
+ ' worker.owns = data.owns;',
120
+ ' worker.calls--;',
121
+ ' unref(worker);',
122
+ ' if ("error" in data) call?.reject(data.error);',
123
+ ' else call?.resolve(data.value);',
124
+ ' }',
125
+ ' dispatch();',
126
+ ' // A worker that a waiting call held on to, now free and without a call, lets go again.',
127
+ ' if ("free" in data) unref(worker);',
128
+ ' };',
129
+ ' const add = (start) => {',
130
+ ' const worker = { start, key: null, thread: null, busy: true, calls: 0, owns: false };',
131
+ ' workers.push(worker);',
132
+ ' start().then(',
133
+ ' (thread) => {',
134
+ ' worker.thread = thread;',
135
+ ' if (thread.on) {',
136
+ ' thread.on("message", (data) => receive(worker, data));',
137
+ ' thread.on("error", (error) => fail(worker, error));',
138
+ ' } else {',
139
+ ' thread.onmessage = (event) => receive(worker, event.data);',
140
+ ' thread.onerror = (event) => fail(worker, new Error(event.message ?? "a worker failed"));',
141
+ ' }',
142
+ ' },',
143
+ ' (error) => {',
144
+ ' remove(worker, error);',
145
+ ' for (const call of queue.filter((each) => each.start === start)) {',
146
+ ' queue.splice(queue.indexOf(call), 1);',
147
+ ' call.reject(error);',
148
+ ' }',
149
+ ' },',
150
+ ' );',
151
+ ' };',
152
+ ' const tasks = new WeakMap();',
153
+ ' return {',
154
+ ' call: (start, name, args) => {',
155
+ ' const call = { id: ids++, start, name, args, resolve: null, reject: null, worker: null };',
156
+ ' const task = new Promise((resolve, reject) => Object.assign(call, { resolve, reject }));',
157
+ ' tasks.set(task, call);',
158
+ ' queue.push(call);',
159
+ ' dispatch();',
160
+ ' return task;',
161
+ ' },',
162
+ ' // A call that waits leaves the queue; a worker cannot stop a call, so the worker stops, and',
163
+ ' // the other calls in it fail too.',
164
+ ' cancel: (task) => {',
165
+ ' const call = tasks.get(task);',
166
+ ' tasks.delete(task);',
167
+ ' if (!call || (!queue.includes(call) && !calls.has(call.id))) return;',
168
+ ' task.catch(() => {});',
169
+ ' if (queue.includes(call)) {',
170
+ ' queue.splice(queue.indexOf(call), 1);',
171
+ ' return call.reject(new Error("goroutine cancelled"));',
172
+ ' }',
173
+ ' calls.delete(call.id);',
174
+ ' call.reject(new Error("goroutine cancelled"));',
175
+ ' call.worker.thread.terminate();',
176
+ ' remove(call.worker, new Error("the worker of the goroutine stopped: another goroutine in it was cancelled"));',
177
+ ' dispatch();',
178
+ ' },',
179
+ ' };',
180
+ '}',
181
+ ],
182
+ /** `go` inside a goroutine: the function runs in the same worker, with copies of the arguments. */
183
+ goLocal: [
184
+ 'function $$go(fn, args) {',
185
+ ' const copies = $$copy(args);',
186
+ ' const run = typeof fn === "function" ? fn : $$goroutines[fn];',
187
+ ' let cancel;',
188
+ ' const cancelled = new Promise((_, reject) => (cancel = reject));',
189
+ ' const task = Promise.race([cancelled, Promise.resolve().then(() => cancel && run(...copies))]);',
190
+ ' $$registry().cancels.set(task, () => {',
191
+ ' task.catch(() => {});',
192
+ ' cancel(new Error("goroutine cancelled"));',
193
+ ' cancel = null;',
194
+ ' });',
195
+ ' return task;',
196
+ '}',
197
+ ],
198
+ goFor: [
199
+ '// `go for`: the array is split into parts, one per processor, each part runs as a goroutine, and',
200
+ '// the loop waits for all of them. A typed array on a SharedArrayBuffer is shared with the workers,',
201
+ '// which write into it; other parts are copies, moved to the workers and back into the array.',
202
+ 'async function $$goFor(name, items, ...captured) {',
203
+ ' const count = Math.min(globalThis.navigator?.hardwareConcurrency ?? 4, items.length);',
204
+ ' const shared =',
205
+ ' typeof SharedArrayBuffer === "function" && items.buffer instanceof SharedArrayBuffer;',
206
+ ' const parts = [];',
207
+ ' for (let k = 0; k < count; k++) {',
208
+ ' const from = Math.floor((items.length * k) / count);',
209
+ ' const to = Math.floor((items.length * (k + 1)) / count);',
210
+ ' const part = shared ? items.subarray(from, to) : items.slice(from, to);',
211
+ ' if (ArrayBuffer.isView(part) && !shared) $$transfer(part);',
212
+ ' parts.push(',
213
+ ' $$go(name, [part, from, ...captured]).then((written) => {',
214
+ ' if (written === undefined || shared) return;',
215
+ ' if (ArrayBuffer.isView(items)) items.set(written, from);',
216
+ ' else for (let i = 0; i < written.length; i++) items[from + i] = written[i];',
217
+ ' }),',
218
+ ' );',
219
+ ' }',
220
+ ' await Promise.all(parts);',
221
+ '}',
222
+ ],
223
+ shared: [
224
+ '// `make([]T, n, shared)`: the memory of a typed array that the workers of `go for` write into,',
225
+ '// without copies. A page has SharedArrayBuffer only with cross-origin isolation.',
226
+ 'function $$shared(bytes) {',
227
+ ' if (typeof SharedArrayBuffer !== "function") {',
228
+ ' throw new Error(',
229
+ ' "make(..., shared) needs SharedArrayBuffer, which a page has only with cross-origin isolation: the headers Cross-Origin-Opener-Policy: same-origin and Cross-Origin-Embedder-Policy: require-corp",',
230
+ ' );',
231
+ ' }',
232
+ ' return new SharedArrayBuffer(bytes);',
233
+ '}',
234
+ ],
235
+ serve: [
236
+ '// The worker: runs the calls that the pool sends and sends back their results or errors. Once the',
237
+ '// thread is free again, it says so, and the pool may send another call.',
238
+ 'async function $$serve(functions) {',
239
+ ' const threads = "node:worker_threads";',
240
+ ' const port =',
241
+ ' typeof postMessage === "function"',
242
+ ' ? globalThis',
243
+ ' : (await import(/* @vite-ignore */ threads)).parentPort;',
244
+ ' const registry = $$registry();',
245
+ ' // The port to the main thread keeps the worker running.',
246
+ ' registry.idle = (key) => key !== registry.main && registry.ports.get(key)?.unref?.();',
247
+ ' port.postMessage({ hello: registry.key });',
248
+ ' const free = () => port.postMessage({ free: true, owns: registry.states.size > 0 });',
249
+ ' const receive = async (data) => {',
250
+ ' if ("op" in data || "answer" in data) return $$incoming(registry.main, data);',
251
+ ' if ("init" in data) {',
252
+ ' registry.main = data.init;',
253
+ ' return $$connect(data.init, port, false);',
254
+ ' }',
255
+ ' if ("port" in data) return $$connect(data.port, data.through, true);',
256
+ ' if ("gone" in data) return $$gone(data.gone);',
257
+ ' setTimeout(free);',
258
+ ' $$adopt(data.args);',
259
+ ' const { main } = registry;',
260
+ ' try {',
261
+ ' const value = await functions[data.name](...data.args);',
262
+ ' $$post(main, { returned: data.call, value, owns: registry.states.size > 0 });',
263
+ ' } catch (error) {',
264
+ ' try {',
265
+ ' $$post(main, { returned: data.call, error, owns: registry.states.size > 0 });',
266
+ ' } catch {',
267
+ ' $$post(main, { returned: data.call, error: new Error(String(error)), owns: true });',
268
+ ' }',
269
+ ' }',
270
+ ' };',
271
+ ' if (port.on) port.on("message", receive);',
272
+ ' else port.onmessage = (event) => receive(event.data);',
273
+ '}',
274
+ ],
275
+ cancel: [
276
+ '// `cancel(task)`: stops a goroutine of `go`, whose Promise fails with "goroutine cancelled". A call',
277
+ '// in a worker stops with its worker; inside a goroutine, `go` runs in the same worker, so its',
278
+ '// Promise fails but code that has started goes on.',
279
+ 'function $$cancel(task) {',
280
+ ' globalThis[Symbol.for("rook.goroutines")]?.cancel(task);',
281
+ ' $$registry().cancels.get(task)?.();',
282
+ '}',
283
+ ],
284
+ };
285
+ /**
286
+ * The start of a worker of a module: a Web Worker, or a worker thread of Node. Node takes only the
287
+ * path of a `file:` URL, without the query of `app.rk?goroutines`, so its worker starts with a
288
+ * module that imports the whole URL. An absolute URL, such as the Blob of the playground, is used
289
+ * as it is.
290
+ */
291
+ export function startWorker(url) {
292
+ const base = /^[a-z][a-z\d+.-]*:/i.test(url) ? '' : ', import.meta.url';
293
+ const workerUrl = `new URL(${JSON.stringify(url)}${base})`;
294
+ return [
295
+ 'async function $$startWorker() {',
296
+ ' if (typeof Worker === "function") {',
297
+ ` return new Worker(${workerUrl}, { type: "module" });`,
298
+ ' }',
299
+ ' const threads = "node:worker_threads";',
300
+ ' const { Worker: NodeWorker } = await import(/* @vite-ignore */ threads);',
301
+ // Vite bundles the Web Worker; this URL is only for Node.
302
+ ` const url = new URL(/* @vite-ignore */ ${JSON.stringify(url)}${base});`,
303
+ ' const code = `import ${JSON.stringify(url.href)};`;',
304
+ ' return new NodeWorker(new URL(`data:text/javascript,${encodeURIComponent(code)}`));',
305
+ '}',
306
+ ];
307
+ }
@@ -0,0 +1,10 @@
1
+ import { type ChanHelper } from './chan-helpers.ts';
2
+ import { type GoHelper } from './go-helpers.ts';
3
+ import { type SelectHelper } from './select-helpers.ts';
4
+ import { type ThreadHelper } from './thread-helpers.ts';
5
+ export type Helper = GoHelper | ChanHelper | SelectHelper | ThreadHelper | 'runDeferred' | 'values' | 'append' | 'attribute' | 'content' | 'branches' | 'dynamic' | 'list' | 'owner' | 'mount' | 'style' | 'element' | 'svgElement';
6
+ /** Predeclared functions of goroutines and channels, by the helpers that define them. */
7
+ export declare const PREDECLARED: Readonly<Record<string, Helper>>;
8
+ /** Helpers that use other helpers. */
9
+ export declare const HELPER_NEEDS: Readonly<Partial<Record<Helper, readonly Helper[]>>>;
10
+ export declare const HELPERS: Readonly<Record<Helper, readonly string[]>>;