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
package/dist/cli.js ADDED
@@ -0,0 +1,193 @@
1
+ #!/usr/bin/env node
2
+ import { existsSync, mkdirSync, mkdtempSync, readFileSync, rmSync, statSync, writeFileSync, } from 'node:fs';
3
+ import { tmpdir } from 'node:os';
4
+ import { spawnSync } from 'node:child_process';
5
+ import { dirname, join } from 'node:path';
6
+ import { fileURLToPath } from 'node:url';
7
+ import { parseArgs } from 'node:util';
8
+ import { build, displayPath } from "./build.js";
9
+ import { CompileError, compileFile } from "./compile-file.js";
10
+ import { markupFor } from "./config.js";
11
+ import { formatDiagnostic } from "./diagnostics.js";
12
+ import { tokenize } from "./lexer/lexer.js";
13
+ import { parse } from "./parser/parser.js";
14
+ import { SourceFile } from "./source.js";
15
+ const { version } = JSON.parse(readFileSync(new URL('../package.json', import.meta.url), 'utf8'));
16
+ const HELP = `Rook ${version}
17
+
18
+ Usage:
19
+ rook build <file.rk | dir>... Compile to .js files, with the .rk files they import
20
+ rook run <file.rk> Compile and run, with the .rk files it imports
21
+ rook tokens <file.rk> Print lexer tokens (for debugging)
22
+ rook ast <file.rk> Print the syntax tree (for debugging)
23
+
24
+ Options:
25
+ --out-dir <dir> build: write the .js files to <dir>, keeping the folder structure
26
+ (by default they go next to the .rk files)
27
+ --stdout build: print the JS of a single file instead of writing files
28
+ --no-check Skip type checking
29
+ --markup Turn markup and components on; by default they are on where the
30
+ package.json has "rook": { "markup": true }
31
+ --no-markup Turn them off
32
+ -h, --help Show this help
33
+ -v, --version Show version`;
34
+ /** A mistake in how the CLI was called. */
35
+ class UsageError extends Error {
36
+ }
37
+ function main() {
38
+ const { values, positionals } = parseArgs({
39
+ allowPositionals: true,
40
+ options: {
41
+ 'out-dir': { type: 'string' },
42
+ stdout: { type: 'boolean' },
43
+ 'no-check': { type: 'boolean' },
44
+ markup: { type: 'boolean' },
45
+ 'no-markup': { type: 'boolean' },
46
+ help: { type: 'boolean', short: 'h' },
47
+ version: { type: 'boolean', short: 'v' },
48
+ },
49
+ });
50
+ if (values.version) {
51
+ console.log(version);
52
+ return;
53
+ }
54
+ const [command, ...files] = positionals;
55
+ if (values.help || !command) {
56
+ console.log(HELP);
57
+ return;
58
+ }
59
+ if (files.length === 0)
60
+ throw new UsageError(`missing input file for "${command}"`);
61
+ for (const path of files) {
62
+ if (!existsSync(path))
63
+ throw new UsageError(`file not found: ${path}`);
64
+ }
65
+ if (command !== 'build' && files.length > 1) {
66
+ throw new UsageError(`"${command}" takes a single file`);
67
+ }
68
+ const file = files[0];
69
+ const typeCheck = !values['no-check'];
70
+ if (values.markup && values['no-markup']) {
71
+ throw new UsageError('--markup and --no-markup cannot be used together');
72
+ }
73
+ // Unless one of them is given, each file takes the setting of its package.json (config.ts).
74
+ const markup = values.markup ? true : values['no-markup'] ? false : undefined;
75
+ switch (command) {
76
+ case 'build':
77
+ if (values.stdout) {
78
+ if (files.length > 1 || statSync(file).isDirectory()) {
79
+ throw new UsageError('--stdout needs a single .rk file');
80
+ }
81
+ process.stdout.write(compileFile(file, { rewriteImports: true, typeCheck, markup }).code);
82
+ }
83
+ else {
84
+ buildFiles(files, { outDir: values['out-dir'], typeCheck, markup });
85
+ }
86
+ break;
87
+ case 'run':
88
+ process.exitCode = runFile(file, typeCheck, markup);
89
+ break;
90
+ case 'tokens':
91
+ printTokens(new SourceFile(file, readFileSync(file, 'utf8')));
92
+ break;
93
+ case 'ast':
94
+ printAst(new SourceFile(file, readFileSync(file, 'utf8')), markup ?? markupFor(file));
95
+ break;
96
+ default:
97
+ throw new UsageError(`unknown command "${command}"`);
98
+ }
99
+ }
100
+ /** Builds the files and directories and writes the `.js` files, or none if there are errors. */
101
+ function buildFiles(entries, options) {
102
+ for (const entry of entries) {
103
+ if (!statSync(entry).isDirectory() && !entry.endsWith('.rk')) {
104
+ throw new UsageError(`not a .rk file: ${entry}`);
105
+ }
106
+ }
107
+ const { outputs, errors, warnings } = build(entries, options);
108
+ for (const { file, diagnostics } of warnings) {
109
+ for (const warning of diagnostics)
110
+ console.warn(`${formatDiagnostic(file, warning, 'warning')}\n`);
111
+ }
112
+ if (errors.length > 0) {
113
+ const messages = errors.flatMap(({ file, diagnostics }) => diagnostics.map((diagnostic) => formatDiagnostic(file, diagnostic)));
114
+ throw new CompileError(messages.join('\n\n'));
115
+ }
116
+ for (const { source, output, code, declarations, worker } of outputs) {
117
+ const extra = `${declarations ? ' + .d.ts' : ''}${worker ? ' + .worker.js' : ''}`;
118
+ mkdirSync(dirname(output), { recursive: true });
119
+ writeFileSync(output, code);
120
+ if (declarations)
121
+ writeFileSync(declarations.output, declarations.code);
122
+ if (worker)
123
+ writeFileSync(worker.output, worker.code);
124
+ console.log(`${displayPath(source)} → ${displayPath(output)}${extra}`);
125
+ }
126
+ }
127
+ /**
128
+ * Runs a program in a process of its own that imports loader.ts first: so do the worker threads
129
+ * of its goroutines, which do not share the hooks of the thread that registers them. They take
130
+ * the compiled code from a directory of the run, removed after it.
131
+ */
132
+ function runFile(file, typeCheck, markup) {
133
+ const loader = new URL('./loader.ts', import.meta.url).href;
134
+ const run = fileURLToPath(new URL('./run.ts', import.meta.url));
135
+ const cache = mkdtempSync(join(tmpdir(), 'rook-run-'));
136
+ try {
137
+ const { status, signal } = spawnSync(process.execPath, ['--import', loader, run, file], {
138
+ stdio: 'inherit',
139
+ env: {
140
+ ...process.env,
141
+ ROOK_RUN_CACHE: cache,
142
+ ...(typeCheck ? {} : { ROOK_NO_CHECK: '1' }),
143
+ ...(markup === undefined ? {} : { ROOK_MARKUP: markup ? '1' : '0' }),
144
+ },
145
+ });
146
+ return status ?? (signal ? 1 : 0);
147
+ }
148
+ finally {
149
+ rmSync(cache, { recursive: true, force: true });
150
+ }
151
+ }
152
+ function printTokens(file) {
153
+ const { tokens, diagnostics } = tokenize(file.text);
154
+ for (const token of tokens) {
155
+ const { line, column } = file.position(token.start);
156
+ const text = token.kind === ';' && token.text === '' ? '(newline)' : JSON.stringify(token.text);
157
+ console.log(`${line}:${column}`.padEnd(8) + token.kind.padEnd(16) + text);
158
+ }
159
+ reportDiagnostics(file, diagnostics);
160
+ }
161
+ function printAst(file, markup) {
162
+ const { program, diagnostics } = parse(file.text, { markup });
163
+ // Positions are left out and identifiers printed as plain names to keep the output readable.
164
+ const compact = (key, value) => {
165
+ if (key === 'start' || key === 'end' || key === 'raw')
166
+ return undefined;
167
+ if (typeof value === 'object' && value !== null && 'kind' in value && 'name' in value) {
168
+ if (value.kind === 'Identifier')
169
+ return value.name;
170
+ }
171
+ return value;
172
+ };
173
+ console.log(JSON.stringify(program, compact, 2));
174
+ reportDiagnostics(file, diagnostics);
175
+ }
176
+ function reportDiagnostics(file, diagnostics) {
177
+ for (const diagnostic of diagnostics)
178
+ console.error(`${formatDiagnostic(file, diagnostic)}\n`);
179
+ if (diagnostics.length > 0)
180
+ process.exitCode = 1;
181
+ }
182
+ try {
183
+ main();
184
+ }
185
+ catch (err) {
186
+ if (err instanceof CompileError)
187
+ console.error(err.message);
188
+ else if (err instanceof UsageError)
189
+ console.error(`rook: ${err.message}`);
190
+ else
191
+ console.error(err);
192
+ process.exitCode = 1;
193
+ }
@@ -0,0 +1,18 @@
1
+ import type * as ast from '../ast.ts';
2
+ export type BindingKind = 'let' | 'const' | 'state' | 'derived' | 'ref' | 'param' | 'function' | 'class' | 'import' | 'loop' | 'catch';
3
+ /** Names declared directly in a list of statements. */
4
+ export declare function declarationsOf(statements: readonly ast.Statement[]): [string, BindingKind][];
5
+ /** `defer` statements of a function body, excluding those of nested functions. */
6
+ export declare function findDefers(body: ast.BlockStatement): ast.DeferStatement[];
7
+ /** Whether markup appears in an expression, not counting nested functions (they handle theirs). */
8
+ export declare function containsElement(node: ast.Node): boolean;
9
+ /** Whether a member chain has `?.` somewhere: then the rest of it may not be evaluated. */
10
+ export declare function hasOptionalLink(node: ast.Expression): boolean;
11
+ export declare function endsWithJump(statements: readonly ast.Statement[]): boolean;
12
+ export declare function startsWithObjectLiteral(node: ast.Expression): boolean;
13
+ /** The variable at the start of `a.b.c`, or `null`. */
14
+ export declare function rootName(node: ast.Expression): string | null;
15
+ /** Literals, which can be put into inlined code as they are. */
16
+ export declare function isConstant(node: ast.Expression): boolean;
17
+ /** Expressions without side effects, which may be evaluated more than once. */
18
+ export declare function isSimple(node: ast.Expression): boolean;
@@ -0,0 +1,144 @@
1
+ import { forEachChild } from "../walk.js";
2
+ /** Names declared directly in a list of statements. */
3
+ export function declarationsOf(statements) {
4
+ const bindings = [];
5
+ for (const statement of statements) {
6
+ switch (statement.kind) {
7
+ case 'VariableDeclaration':
8
+ for (const name of statement.names)
9
+ bindings.push([name.name, statement.keyword]);
10
+ break;
11
+ case 'FuncDeclaration':
12
+ bindings.push([statement.name.name, 'function']);
13
+ break;
14
+ case 'ClassDeclaration':
15
+ bindings.push([statement.name.name, 'class']);
16
+ break;
17
+ case 'ImportDeclaration':
18
+ if (statement.defaultImport)
19
+ bindings.push([statement.defaultImport.name, 'import']);
20
+ if (statement.namespaceImport)
21
+ bindings.push([statement.namespaceImport.name, 'import']);
22
+ for (const specifier of statement.namedImports) {
23
+ bindings.push([specifier.local.name, 'import']);
24
+ }
25
+ break;
26
+ default:
27
+ break;
28
+ }
29
+ }
30
+ return bindings;
31
+ }
32
+ /** `defer` statements of a function body, excluding those of nested functions. */
33
+ export function findDefers(body) {
34
+ const defers = [];
35
+ const visit = (node) => {
36
+ switch (node.kind) {
37
+ case 'DeferStatement':
38
+ defers.push(node);
39
+ return;
40
+ case 'FuncDeclaration':
41
+ case 'FuncExpression':
42
+ case 'ArrowFunction':
43
+ case 'ClassDeclaration':
44
+ case 'MountStatement':
45
+ case 'EffectStatement':
46
+ return;
47
+ default:
48
+ forEachChild(node, visit);
49
+ }
50
+ };
51
+ forEachChild(body, visit);
52
+ return defers;
53
+ }
54
+ /** Whether markup appears in an expression, not counting nested functions (they handle theirs). */
55
+ export function containsElement(node) {
56
+ let found = false;
57
+ const visit = (child) => {
58
+ if (found)
59
+ return;
60
+ if (child.kind === 'ElementExpression') {
61
+ found = true;
62
+ return;
63
+ }
64
+ if (child.kind === 'FuncExpression' || child.kind === 'ArrowFunction')
65
+ return;
66
+ forEachChild(child, visit);
67
+ };
68
+ visit(node);
69
+ return found;
70
+ }
71
+ /** Whether a member chain has `?.` somewhere: then the rest of it may not be evaluated. */
72
+ export function hasOptionalLink(node) {
73
+ switch (node.kind) {
74
+ case 'MemberExpression':
75
+ case 'IndexExpression':
76
+ return node.optional || hasOptionalLink(node.object);
77
+ case 'CallExpression':
78
+ return node.optional || hasOptionalLink(node.callee);
79
+ default:
80
+ return false;
81
+ }
82
+ }
83
+ export function endsWithJump(statements) {
84
+ const kind = statements.at(-1)?.kind;
85
+ return (kind === 'ReturnStatement' ||
86
+ kind === 'ThrowStatement' ||
87
+ kind === 'BreakStatement' ||
88
+ kind === 'ContinueStatement');
89
+ }
90
+ export function startsWithObjectLiteral(node) {
91
+ switch (node.kind) {
92
+ case 'ObjectLiteral':
93
+ return true;
94
+ case 'CallExpression':
95
+ return startsWithObjectLiteral(node.callee);
96
+ case 'MemberExpression':
97
+ case 'IndexExpression':
98
+ return startsWithObjectLiteral(node.object);
99
+ case 'BinaryExpression':
100
+ return startsWithObjectLiteral(node.left);
101
+ case 'ConditionalExpression':
102
+ return startsWithObjectLiteral(node.test);
103
+ default:
104
+ return false;
105
+ }
106
+ }
107
+ /** The variable at the start of `a.b.c`, or `null`. */
108
+ export function rootName(node) {
109
+ if (node.kind === 'Identifier')
110
+ return node.name;
111
+ return node.kind === 'MemberExpression' ? rootName(node.object) : null;
112
+ }
113
+ /** Literals, which can be put into inlined code as they are. */
114
+ export function isConstant(node) {
115
+ switch (node.kind) {
116
+ case 'NumberLiteral':
117
+ case 'StringLiteral':
118
+ case 'BooleanLiteral':
119
+ case 'NullLiteral':
120
+ return true;
121
+ case 'TemplateLiteral':
122
+ return node.expressions.length === 0;
123
+ case 'UnaryExpression':
124
+ return node.operator === '-' && node.argument.kind === 'NumberLiteral';
125
+ default:
126
+ return false;
127
+ }
128
+ }
129
+ /** Expressions without side effects, which may be evaluated more than once. */
130
+ export function isSimple(node) {
131
+ switch (node.kind) {
132
+ case 'Identifier':
133
+ case 'NumberLiteral':
134
+ case 'StringLiteral':
135
+ case 'BooleanLiteral':
136
+ case 'NullLiteral':
137
+ case 'ThisExpression':
138
+ return true;
139
+ case 'MemberExpression':
140
+ return isSimple(node.object);
141
+ default:
142
+ return false;
143
+ }
144
+ }
@@ -0,0 +1,40 @@
1
+ import type * as ast from '../ast.ts';
2
+ import type { Live } from './reactive.ts';
3
+ import { UpdateEmitter } from './updates.ts';
4
+ export declare abstract class AttributeEmitter extends UpdateEmitter {
5
+ protected abstract nextId(): number;
6
+ protected abstract once(node: ast.Expression): string;
7
+ /** Counter for `$$li1`, `$$div2`, ... */
8
+ protected elementCount: number;
9
+ /** The element that elements of markup blocks are appended to, in plain control flow. */
10
+ protected contentTarget: string | null;
11
+ /** Whether the markup being written is inside `<svg>`: its elements are SVG then. */
12
+ protected inSvg: boolean;
13
+ /** `attrs` of `...attrs` in the component being written: `{...attrs}` applies them. */
14
+ protected attrsParam: string | null;
15
+ /** `document.createElement("div")`, or `createElementNS` for an SVG element. */
16
+ protected createElement(tag: string, svg: boolean): string;
17
+ /** `onClick={count++}` becomes `() => { count++; }`; a function is used as it is. */
18
+ protected handler(value: ast.JsxAttribute['value']): string;
19
+ protected elementName(tag: string | null): string;
20
+ protected attribute(element: string, tag: string | null, attribute: ast.JsxAttribute | ast.JsxSpreadAttribute, live: Live | null, svg?: boolean, scope?: string | null): void;
21
+ /** The class of an element with the class of its style: `"card rook-card-1x9k2az"`. */
22
+ private scopedClass;
23
+ /** Sets the class of an element: `className`, or the attribute of an SVG element. */
24
+ protected scopeClass(element: string, classes: string, svg: boolean): void;
25
+ /** `<app-card item-count={1} />`: the property of a web component of this module, if it has one. */
26
+ protected webComponentProperty(tag: string, name: string): ast.Parameter | null;
27
+ /** A property of a web component is set as a JS property, so it may be an object or a function. */
28
+ protected webComponentAttribute(element: string, param: ast.Parameter, value: ast.JsxAttribute['value'], live: Live | null): void;
29
+ /**
30
+ * Writes the statement that sets a value. In live markup, when the value depends on state, the
31
+ * same statement also goes into the updates of that state.
32
+ */
33
+ protected setLive(live: Live | null, value: ast.Expression, statement: (text: string) => string): void;
34
+ /**
35
+ * `bind:value={name}`: the element shows the variable, and what the user enters is written
36
+ * back to it. In live markup the element is also updated when the variable changes elsewhere.
37
+ */
38
+ protected binding(element: string, tag: string, attribute: ast.JsxAttribute, live: Live | null): void;
39
+ protected handlerFunction(value: ast.JsxAttribute['value']): string;
40
+ }
@@ -0,0 +1,227 @@
1
+ import { domProperty } from "../checker/dom.js";
2
+ import { attributeName } from "../html-tag.js";
3
+ import { SVG_NAMESPACE } from "../svg.js";
4
+ import { eventName, mentions } from "./element-helpers.js";
5
+ import { ARROW } from "./syntax.js";
6
+ import { UpdateEmitter } from "./updates.js";
7
+ // Attributes of elements: properties, setAttribute, styles, `bind:` and event handlers.
8
+ export class AttributeEmitter extends UpdateEmitter {
9
+ /** Counter for `$$li1`, `$$div2`, ... */
10
+ elementCount = 0;
11
+ /** The element that elements of markup blocks are appended to, in plain control flow. */
12
+ contentTarget = null;
13
+ /** Whether the markup being written is inside `<svg>`: its elements are SVG then. */
14
+ inSvg = false;
15
+ /** `attrs` of `...attrs` in the component being written: `{...attrs}` applies them. */
16
+ attrsParam = null;
17
+ /** `document.createElement("div")`, or `createElementNS` for an SVG element. */
18
+ createElement(tag, svg) {
19
+ const quoted = JSON.stringify(tag);
20
+ return svg
21
+ ? `document.createElementNS(${JSON.stringify(SVG_NAMESPACE)}, ${quoted})`
22
+ : `document.createElement(${quoted})`;
23
+ }
24
+ /** `onClick={count++}` becomes `() => { count++; }`; a function is used as it is. */
25
+ handler(value) {
26
+ return this.withRendering(0, () => this.handlerFunction(value));
27
+ }
28
+ elementName(tag) {
29
+ const base = tag === null ? 'fragment' : /^[a-z][a-z0-9]*$/.test(tag) ? tag : 'element';
30
+ return `$$${base}${this.nextId()}`;
31
+ }
32
+ attribute(element, tag, attribute, live, svg = false, scope = null) {
33
+ if (attribute.kind === 'JsxSpreadAttribute') {
34
+ const { argument } = attribute;
35
+ // The attributes passed on: the use made a function that sets them on the element.
36
+ if (argument.kind === 'Identifier' && argument.name === this.attrsParam) {
37
+ this.line(`${this.name(argument.name)}?.(${element});`);
38
+ return;
39
+ }
40
+ this.setLive(live, argument, (text) => `Object.assign(${element}, ${text});`);
41
+ return;
42
+ }
43
+ const name = attribute.name.name;
44
+ const { value } = attribute;
45
+ if (name === 'class' && scope) {
46
+ this.scopedClass(element, value, live, svg, scope);
47
+ return;
48
+ }
49
+ const webProperty = tag === null || svg ? null : this.webComponentProperty(tag, name);
50
+ if (webProperty) {
51
+ this.webComponentAttribute(element, webProperty, value, live);
52
+ return;
53
+ }
54
+ if (/^on[A-Z]/.test(name)) {
55
+ this.inHandler++;
56
+ try {
57
+ this.line(`${element}.addEventListener(${JSON.stringify(eventName(name))}, ${this.handler(value)});`);
58
+ }
59
+ finally {
60
+ this.inHandler--;
61
+ }
62
+ return;
63
+ }
64
+ if (value?.kind === 'EventHandler')
65
+ return;
66
+ if (name === 'style' && value !== null) {
67
+ const isString = value.kind === 'StringLiteral' || this.typeOf(value)?.kind === 'string';
68
+ this.setLive(live, value, (text) => isString
69
+ ? `${element}.style.cssText = ${text};`
70
+ : `Object.assign(${element}.style, ${text});`);
71
+ return;
72
+ }
73
+ // SVG attributes are set with setAttribute: their properties are objects.
74
+ const property = tag === null || svg ? null : domProperty(tag, name);
75
+ if (value === null) {
76
+ // `<input disabled>`: true for boolean properties, an empty attribute otherwise.
77
+ this.line(property?.type.kind === 'bool'
78
+ ? `${element}.${property.name} = true;`
79
+ : `${element}.setAttribute(${JSON.stringify(name)}, "");`);
80
+ return;
81
+ }
82
+ if (property) {
83
+ this.setLive(live, value, (text) => `${element}.${property.name} = ${text};`);
84
+ return;
85
+ }
86
+ const quoted = JSON.stringify(name);
87
+ if (this.typeOf(value)?.kind !== 'nullable') {
88
+ this.setLive(live, value, (text) => `${element}.setAttribute(${quoted}, ${text});`);
89
+ }
90
+ else if (live && live.dependencies(value).size > 0) {
91
+ this.helpers.add('attribute');
92
+ this.setLive(live, value, (text) => `$$attribute(${element}, ${quoted}, ${text});`);
93
+ }
94
+ else {
95
+ // A null value leaves the attribute out.
96
+ const text = this.once(value);
97
+ this.line(`if (${text} != null) ${element}.setAttribute(${quoted}, ${text});`);
98
+ }
99
+ }
100
+ /** The class of an element with the class of its style: `"card rook-card-1x9k2az"`. */
101
+ scopedClass(element, value, live, svg, scope) {
102
+ if (value === null || value.kind === 'StringLiteral') {
103
+ this.scopeClass(element, `${value?.value ?? ''} ${scope}`.trim(), svg);
104
+ }
105
+ else if (value.kind !== 'EventHandler') {
106
+ const nullable = this.typeOf(value)?.kind !== 'string';
107
+ this.setLive(live, value, (text) => {
108
+ const scoped = `\`\${${nullable ? `${text} ?? ""` : text}} ${scope}\``;
109
+ return svg
110
+ ? `${element}.setAttribute("class", ${scoped});`
111
+ : `${element}.className = ${scoped};`;
112
+ });
113
+ }
114
+ }
115
+ /** Sets the class of an element: `className`, or the attribute of an SVG element. */
116
+ scopeClass(element, classes, svg) {
117
+ const text = JSON.stringify(classes);
118
+ this.line(svg ? `${element}.setAttribute("class", ${text});` : `${element}.className = ${text};`);
119
+ }
120
+ /** `<app-card item-count={1} />`: the property of a web component of this module, if it has one. */
121
+ webComponentProperty(tag, name) {
122
+ const component = this.webComponents.get(tag);
123
+ if (!component)
124
+ return null;
125
+ const param = component.params.find((param) => param.name.name !== 'children' &&
126
+ (param.name.name === name || attributeName(param.name.name) === name));
127
+ return param ?? null;
128
+ }
129
+ /** A property of a web component is set as a JS property, so it may be an object or a function. */
130
+ webComponentAttribute(element, param, value, live) {
131
+ const target = `${element}.${param.name.name}`;
132
+ if (value === null) {
133
+ this.line(`${target} = true;`);
134
+ }
135
+ else if (value.kind === 'EventHandler' ||
136
+ value.kind === 'ArrowFunction' ||
137
+ value.kind === 'FuncExpression') {
138
+ this.inHandler++;
139
+ try {
140
+ this.line(`${target} = ${this.handler(value)};`);
141
+ }
142
+ finally {
143
+ this.inHandler--;
144
+ }
145
+ }
146
+ else {
147
+ this.setLive(live, value, (text) => `${target} = ${text};`);
148
+ }
149
+ }
150
+ /**
151
+ * Writes the statement that sets a value. In live markup, when the value depends on state, the
152
+ * same statement also goes into the updates of that state.
153
+ */
154
+ setLive(live, value, statement) {
155
+ const sources = live && value.kind !== 'StringLiteral' ? live.dependencies(value) : null;
156
+ if (!live || !sources || sources.size === 0) {
157
+ this.line(statement(this.expression(value, ARROW)));
158
+ return;
159
+ }
160
+ const text = statement(this.liveExpression(value));
161
+ this.line(text);
162
+ live.depend(sources, text);
163
+ }
164
+ /**
165
+ * `bind:value={name}`: the element shows the variable, and what the user enters is written
166
+ * back to it. In live markup the element is also updated when the variable changes elsewhere.
167
+ */
168
+ binding(element, tag, attribute, live) {
169
+ const value = attribute.value;
170
+ const isNumber = attribute.name.name === 'bind:value' && this.typeOf(value)?.kind === 'number';
171
+ const property = attribute.name.name === 'bind:checked' ? 'checked' : isNumber ? 'valueAsNumber' : 'value';
172
+ const target = this.liveExpression(value);
173
+ const current = `${element}.${property}`;
174
+ this.line(`${current} = ${target};`);
175
+ const event = property === 'checked' || tag === 'select' ? 'change' : 'input';
176
+ const written = [...(this.reactive?.rootSources(value) ?? [])].filter((source) => source.kind === 'state');
177
+ const dependsOn = live?.dependencies(value) ?? new Set();
178
+ this.inHandler++;
179
+ try {
180
+ const body = this.block(() => {
181
+ this.line(`${target} = ${current};`);
182
+ for (const source of written) {
183
+ // The element already shows what was entered: its own update is skipped.
184
+ const own = live && dependsOn.has(source) ? live.ownIndex(source) : null;
185
+ this.line(`${this.write(source, own)};`);
186
+ }
187
+ });
188
+ this.line(`${element}.addEventListener(${JSON.stringify(event)}, () => ${body});`);
189
+ }
190
+ finally {
191
+ this.inHandler--;
192
+ }
193
+ // NaN from an unfinished number must not clear the input while the user types.
194
+ const differs = isNumber ? `!Object.is(${current}, ${target})` : `${current} !== ${target}`;
195
+ if (live && dependsOn.size > 0) {
196
+ live.depend(dependsOn, `if (${differs}) ${current} = ${target};`);
197
+ }
198
+ }
199
+ handlerFunction(value) {
200
+ if (value === null)
201
+ return '() => {}';
202
+ if (value.kind !== 'EventHandler')
203
+ return this.expression(value, ARROW);
204
+ const name = {
205
+ kind: 'Identifier',
206
+ name: 'event',
207
+ start: value.start,
208
+ end: value.start,
209
+ };
210
+ const params = mentions(value, 'event')
211
+ ? [
212
+ {
213
+ kind: 'Parameter',
214
+ name,
215
+ type: null,
216
+ defaultValue: null,
217
+ start: value.start,
218
+ end: value.start,
219
+ },
220
+ ]
221
+ : [];
222
+ return this.withFunction('none', params, () => {
223
+ const body = this.block(() => this.statements(value.body));
224
+ return `(${params.length > 0 ? 'event' : ''}) => ${body}`;
225
+ });
226
+ }
227
+ }
@@ -0,0 +1,22 @@
1
+ import type * as ast from '../ast.ts';
2
+ import { ExpressionEmitter } from './expressions.ts';
3
+ export declare abstract class CallEmitter extends ExpressionEmitter {
4
+ protected call(node: ast.CallExpression): string;
5
+ /** `yield v` for `yield(v)` in an iterator, `yield [k, v]` for `yield(k, v)`; else `null`. */
6
+ private yielded;
7
+ protected expressionStatement(node: ast.Expression): string;
8
+ /** `if !yield(v) { return }`, the way an iterator of Go stops: in a generator, `yield v`. */
9
+ protected yieldStatement(node: ast.IfStatement): string | null;
10
+ /** `string(data)`, unless `string` is the name of a value here. */
11
+ private isStringConversion;
12
+ protected newExpression(node: ast.NewExpression): string;
13
+ /** `new (f())()` and `new (a?.B)()` need parentheses around the class expression. */
14
+ private newCallee;
15
+ protected arrayLiteral(node: ast.ArrayLiteral): string;
16
+ /**
17
+ * `make([]float64, n)` → `new Float64Array(n)`; with `shared`, on a SharedArrayBuffer of its
18
+ * size in bytes, `new Float64Array($$shared(8 * n))`. Without types the name is the one written.
19
+ */
20
+ protected make(node: ast.MakeExpression): string;
21
+ protected conversion(node: ast.ConversionExpression): string;
22
+ }