@virune/compiler 1.1.0-rc.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 (300) hide show
  1. package/LICENSE +201 -0
  2. package/NOTICE +2 -0
  3. package/dist/src/analysis/control-flow.d.ts +9 -0
  4. package/dist/src/analysis/control-flow.d.ts.map +1 -0
  5. package/dist/src/analysis/control-flow.js +47 -0
  6. package/dist/src/analysis/control-flow.js.map +1 -0
  7. package/dist/src/ast/nodes.d.ts +495 -0
  8. package/dist/src/ast/nodes.d.ts.map +1 -0
  9. package/dist/src/ast/nodes.js +2 -0
  10. package/dist/src/ast/nodes.js.map +1 -0
  11. package/dist/src/binder/symbols.d.ts +36 -0
  12. package/dist/src/binder/symbols.d.ts.map +1 -0
  13. package/dist/src/binder/symbols.js +30 -0
  14. package/dist/src/binder/symbols.js.map +1 -0
  15. package/dist/src/checker/builtin-members.d.ts +4 -0
  16. package/dist/src/checker/builtin-members.d.ts.map +1 -0
  17. package/dist/src/checker/builtin-members.js +175 -0
  18. package/dist/src/checker/builtin-members.js.map +1 -0
  19. package/dist/src/checker/checker.d.ts +148 -0
  20. package/dist/src/checker/checker.d.ts.map +1 -0
  21. package/dist/src/checker/checker.js +2985 -0
  22. package/dist/src/checker/checker.js.map +1 -0
  23. package/dist/src/checker/effect-registry.d.ts +14 -0
  24. package/dist/src/checker/effect-registry.d.ts.map +1 -0
  25. package/dist/src/checker/effect-registry.js +11 -0
  26. package/dist/src/checker/effect-registry.js.map +1 -0
  27. package/dist/src/checker/type-operations.d.ts +27 -0
  28. package/dist/src/checker/type-operations.d.ts.map +1 -0
  29. package/dist/src/checker/type-operations.js +268 -0
  30. package/dist/src/checker/type-operations.js.map +1 -0
  31. package/dist/src/codegen/emitter.d.ts +83 -0
  32. package/dist/src/codegen/emitter.d.ts.map +1 -0
  33. package/dist/src/codegen/emitter.js +1029 -0
  34. package/dist/src/codegen/emitter.js.map +1 -0
  35. package/dist/src/codegen/helpers.d.ts +7 -0
  36. package/dist/src/codegen/helpers.d.ts.map +1 -0
  37. package/dist/src/codegen/helpers.js +48 -0
  38. package/dist/src/codegen/helpers.js.map +1 -0
  39. package/dist/src/codegen/runtime-imports.d.ts +6 -0
  40. package/dist/src/codegen/runtime-imports.d.ts.map +1 -0
  41. package/dist/src/codegen/runtime-imports.js +45 -0
  42. package/dist/src/codegen/runtime-imports.js.map +1 -0
  43. package/dist/src/codegen/writer.d.ts +14 -0
  44. package/dist/src/codegen/writer.d.ts.map +1 -0
  45. package/dist/src/codegen/writer.js +56 -0
  46. package/dist/src/codegen/writer.js.map +1 -0
  47. package/dist/src/compiler.d.ts +24 -0
  48. package/dist/src/compiler.d.ts.map +1 -0
  49. package/dist/src/compiler.js +97 -0
  50. package/dist/src/compiler.js.map +1 -0
  51. package/dist/src/diagnostics/codes.d.ts +18 -0
  52. package/dist/src/diagnostics/codes.d.ts.map +1 -0
  53. package/dist/src/diagnostics/codes.js +43 -0
  54. package/dist/src/diagnostics/codes.js.map +1 -0
  55. package/dist/src/diagnostics/diagnostic.d.ts +41 -0
  56. package/dist/src/diagnostics/diagnostic.d.ts.map +1 -0
  57. package/dist/src/diagnostics/diagnostic.js +50 -0
  58. package/dist/src/diagnostics/diagnostic.js.map +1 -0
  59. package/dist/src/diagnostics/render.d.ts +44 -0
  60. package/dist/src/diagnostics/render.d.ts.map +1 -0
  61. package/dist/src/diagnostics/render.js +44 -0
  62. package/dist/src/diagnostics/render.js.map +1 -0
  63. package/dist/src/experimental-api.d.ts +19 -0
  64. package/dist/src/experimental-api.d.ts.map +1 -0
  65. package/dist/src/experimental-api.js +17 -0
  66. package/dist/src/experimental-api.js.map +1 -0
  67. package/dist/src/hir/lower.d.ts +6 -0
  68. package/dist/src/hir/lower.d.ts.map +1 -0
  69. package/dist/src/hir/lower.js +124 -0
  70. package/dist/src/hir/lower.js.map +1 -0
  71. package/dist/src/hir/nodes.d.ts +12 -0
  72. package/dist/src/hir/nodes.d.ts.map +1 -0
  73. package/dist/src/hir/nodes.js +2 -0
  74. package/dist/src/hir/nodes.js.map +1 -0
  75. package/dist/src/index.d.ts +2 -0
  76. package/dist/src/index.d.ts.map +1 -0
  77. package/dist/src/index.js +2 -0
  78. package/dist/src/index.js.map +1 -0
  79. package/dist/src/interop/checked-api.d.ts +21 -0
  80. package/dist/src/interop/checked-api.d.ts.map +1 -0
  81. package/dist/src/interop/checked-api.js +46 -0
  82. package/dist/src/interop/checked-api.js.map +1 -0
  83. package/dist/src/interop/decision.d.ts +24 -0
  84. package/dist/src/interop/decision.d.ts.map +1 -0
  85. package/dist/src/interop/decision.js +96 -0
  86. package/dist/src/interop/decision.js.map +1 -0
  87. package/dist/src/interop/frontend-component-emission.d.ts +17 -0
  88. package/dist/src/interop/frontend-component-emission.d.ts.map +1 -0
  89. package/dist/src/interop/frontend-component-emission.js +271 -0
  90. package/dist/src/interop/frontend-component-emission.js.map +1 -0
  91. package/dist/src/interop/jsx-view-validation.d.ts +20 -0
  92. package/dist/src/interop/jsx-view-validation.d.ts.map +1 -0
  93. package/dist/src/interop/jsx-view-validation.js +847 -0
  94. package/dist/src/interop/jsx-view-validation.js.map +1 -0
  95. package/dist/src/interop/operation-api.d.ts +32 -0
  96. package/dist/src/interop/operation-api.d.ts.map +1 -0
  97. package/dist/src/interop/operation-api.js +61 -0
  98. package/dist/src/interop/operation-api.js.map +1 -0
  99. package/dist/src/interop/operation.d.ts +136 -0
  100. package/dist/src/interop/operation.d.ts.map +1 -0
  101. package/dist/src/interop/operation.js +575 -0
  102. package/dist/src/interop/operation.js.map +1 -0
  103. package/dist/src/interop/repetition-host-locator.d.ts +25 -0
  104. package/dist/src/interop/repetition-host-locator.d.ts.map +1 -0
  105. package/dist/src/interop/repetition-host-locator.js +61 -0
  106. package/dist/src/interop/repetition-host-locator.js.map +1 -0
  107. package/dist/src/interop/repetition-host-usage-validation.d.ts +11 -0
  108. package/dist/src/interop/repetition-host-usage-validation.d.ts.map +1 -0
  109. package/dist/src/interop/repetition-host-usage-validation.js +89 -0
  110. package/dist/src/interop/repetition-host-usage-validation.js.map +1 -0
  111. package/dist/src/interop/types.d.ts +365 -0
  112. package/dist/src/interop/types.d.ts.map +1 -0
  113. package/dist/src/interop/types.js +2 -0
  114. package/dist/src/interop/types.js.map +1 -0
  115. package/dist/src/mir/lower.d.ts +4 -0
  116. package/dist/src/mir/lower.d.ts.map +1 -0
  117. package/dist/src/mir/lower.js +131 -0
  118. package/dist/src/mir/lower.js.map +1 -0
  119. package/dist/src/mir/nodes.d.ts +36 -0
  120. package/dist/src/mir/nodes.d.ts.map +1 -0
  121. package/dist/src/mir/nodes.js +2 -0
  122. package/dist/src/mir/nodes.js.map +1 -0
  123. package/dist/src/project/incremental.d.ts +9 -0
  124. package/dist/src/project/incremental.d.ts.map +1 -0
  125. package/dist/src/project/incremental.js +15 -0
  126. package/dist/src/project/incremental.js.map +1 -0
  127. package/dist/src/project/project.d.ts +98 -0
  128. package/dist/src/project/project.d.ts.map +1 -0
  129. package/dist/src/project/project.js +884 -0
  130. package/dist/src/project/project.js.map +1 -0
  131. package/dist/src/public-api.d.ts +45 -0
  132. package/dist/src/public-api.d.ts.map +1 -0
  133. package/dist/src/public-api.js +37 -0
  134. package/dist/src/public-api.js.map +1 -0
  135. package/dist/src/reference/evaluator.d.ts +13 -0
  136. package/dist/src/reference/evaluator.d.ts.map +1 -0
  137. package/dist/src/reference/evaluator.js +304 -0
  138. package/dist/src/reference/evaluator.js.map +1 -0
  139. package/dist/src/selfhost/bootstrap-artifact-normalizer.d.ts +65 -0
  140. package/dist/src/selfhost/bootstrap-artifact-normalizer.d.ts.map +1 -0
  141. package/dist/src/selfhost/bootstrap-artifact-normalizer.js +226 -0
  142. package/dist/src/selfhost/bootstrap-artifact-normalizer.js.map +1 -0
  143. package/dist/src/selfhost/bootstrap-artifact-snapshot.d.ts +23 -0
  144. package/dist/src/selfhost/bootstrap-artifact-snapshot.d.ts.map +1 -0
  145. package/dist/src/selfhost/bootstrap-artifact-snapshot.js +122 -0
  146. package/dist/src/selfhost/bootstrap-artifact-snapshot.js.map +1 -0
  147. package/dist/src/selfhost/bootstrap-compiler-selection.d.ts +36 -0
  148. package/dist/src/selfhost/bootstrap-compiler-selection.d.ts.map +1 -0
  149. package/dist/src/selfhost/bootstrap-compiler-selection.js +52 -0
  150. package/dist/src/selfhost/bootstrap-compiler-selection.js.map +1 -0
  151. package/dist/src/selfhost/bootstrap-execution-probe.d.ts +42 -0
  152. package/dist/src/selfhost/bootstrap-execution-probe.d.ts.map +1 -0
  153. package/dist/src/selfhost/bootstrap-execution-probe.js +115 -0
  154. package/dist/src/selfhost/bootstrap-execution-probe.js.map +1 -0
  155. package/dist/src/selfhost/bootstrap-rollback-decision.d.ts +40 -0
  156. package/dist/src/selfhost/bootstrap-rollback-decision.d.ts.map +1 -0
  157. package/dist/src/selfhost/bootstrap-rollback-decision.js +144 -0
  158. package/dist/src/selfhost/bootstrap-rollback-decision.js.map +1 -0
  159. package/dist/src/selfhost/bootstrap-shadow-history.d.ts +49 -0
  160. package/dist/src/selfhost/bootstrap-shadow-history.d.ts.map +1 -0
  161. package/dist/src/selfhost/bootstrap-shadow-history.js +348 -0
  162. package/dist/src/selfhost/bootstrap-shadow-history.js.map +1 -0
  163. package/dist/src/selfhost/bootstrap-shadow-report.d.ts +43 -0
  164. package/dist/src/selfhost/bootstrap-shadow-report.d.ts.map +1 -0
  165. package/dist/src/selfhost/bootstrap-shadow-report.js +94 -0
  166. package/dist/src/selfhost/bootstrap-shadow-report.js.map +1 -0
  167. package/dist/src/selfhost/bootstrap-stage-executor.d.ts +53 -0
  168. package/dist/src/selfhost/bootstrap-stage-executor.d.ts.map +1 -0
  169. package/dist/src/selfhost/bootstrap-stage-executor.js +159 -0
  170. package/dist/src/selfhost/bootstrap-stage-executor.js.map +1 -0
  171. package/dist/src/selfhost/bootstrap-stage-loader.d.ts +14 -0
  172. package/dist/src/selfhost/bootstrap-stage-loader.d.ts.map +1 -0
  173. package/dist/src/selfhost/bootstrap-stage-loader.js +112 -0
  174. package/dist/src/selfhost/bootstrap-stage-loader.js.map +1 -0
  175. package/dist/src/selfhost/bootstrap-stage-pipeline.d.ts +8 -0
  176. package/dist/src/selfhost/bootstrap-stage-pipeline.d.ts.map +1 -0
  177. package/dist/src/selfhost/bootstrap-stage-pipeline.js +85 -0
  178. package/dist/src/selfhost/bootstrap-stage-pipeline.js.map +1 -0
  179. package/dist/src/selfhost/bootstrap-stage-runner.d.ts +47 -0
  180. package/dist/src/selfhost/bootstrap-stage-runner.d.ts.map +1 -0
  181. package/dist/src/selfhost/bootstrap-stage-runner.js +106 -0
  182. package/dist/src/selfhost/bootstrap-stage-runner.js.map +1 -0
  183. package/dist/src/selfhost/clean-bootstrap-evidence.d.ts +83 -0
  184. package/dist/src/selfhost/clean-bootstrap-evidence.d.ts.map +1 -0
  185. package/dist/src/selfhost/clean-bootstrap-evidence.js +233 -0
  186. package/dist/src/selfhost/clean-bootstrap-evidence.js.map +1 -0
  187. package/dist/src/selfhost/compiler-facade.d.ts +33 -0
  188. package/dist/src/selfhost/compiler-facade.d.ts.map +1 -0
  189. package/dist/src/selfhost/compiler-facade.js +65 -0
  190. package/dist/src/selfhost/compiler-facade.js.map +1 -0
  191. package/dist/src/selfhost/contract.d.ts +151 -0
  192. package/dist/src/selfhost/contract.d.ts.map +1 -0
  193. package/dist/src/selfhost/contract.js +335 -0
  194. package/dist/src/selfhost/contract.js.map +1 -0
  195. package/dist/src/selfhost/differential-artifacts.d.ts +8 -0
  196. package/dist/src/selfhost/differential-artifacts.d.ts.map +1 -0
  197. package/dist/src/selfhost/differential-artifacts.js +50 -0
  198. package/dist/src/selfhost/differential-artifacts.js.map +1 -0
  199. package/dist/src/selfhost/differential-harness.d.ts +91 -0
  200. package/dist/src/selfhost/differential-harness.d.ts.map +1 -0
  201. package/dist/src/selfhost/differential-harness.js +246 -0
  202. package/dist/src/selfhost/differential-harness.js.map +1 -0
  203. package/dist/src/selfhost/full-language-inventory-runner.d.ts +49 -0
  204. package/dist/src/selfhost/full-language-inventory-runner.d.ts.map +1 -0
  205. package/dist/src/selfhost/full-language-inventory-runner.js +225 -0
  206. package/dist/src/selfhost/full-language-inventory-runner.js.map +1 -0
  207. package/dist/src/selfhost/full-language-inventory.d.ts +42 -0
  208. package/dist/src/selfhost/full-language-inventory.d.ts.map +1 -0
  209. package/dist/src/selfhost/full-language-inventory.js +163 -0
  210. package/dist/src/selfhost/full-language-inventory.js.map +1 -0
  211. package/dist/src/selfhost/interop-resolution-witness.d.ts +35 -0
  212. package/dist/src/selfhost/interop-resolution-witness.d.ts.map +1 -0
  213. package/dist/src/selfhost/interop-resolution-witness.js +186 -0
  214. package/dist/src/selfhost/interop-resolution-witness.js.map +1 -0
  215. package/dist/src/selfhost/kernel-model-host-adapter.d.ts +28 -0
  216. package/dist/src/selfhost/kernel-model-host-adapter.d.ts.map +1 -0
  217. package/dist/src/selfhost/kernel-model-host-adapter.js +36 -0
  218. package/dist/src/selfhost/kernel-model-host-adapter.js.map +1 -0
  219. package/dist/src/selfhost/legacy-adapter.d.ts +8 -0
  220. package/dist/src/selfhost/legacy-adapter.d.ts.map +1 -0
  221. package/dist/src/selfhost/legacy-adapter.js +186 -0
  222. package/dist/src/selfhost/legacy-adapter.js.map +1 -0
  223. package/dist/src/selfhost/module-graph.d.ts +57 -0
  224. package/dist/src/selfhost/module-graph.d.ts.map +1 -0
  225. package/dist/src/selfhost/module-graph.js +267 -0
  226. package/dist/src/selfhost/module-graph.js.map +1 -0
  227. package/dist/src/selfhost/mvp-adapter.d.ts +19 -0
  228. package/dist/src/selfhost/mvp-adapter.d.ts.map +1 -0
  229. package/dist/src/selfhost/mvp-adapter.js +186 -0
  230. package/dist/src/selfhost/mvp-adapter.js.map +1 -0
  231. package/dist/src/selfhost/node-executor.d.ts +6 -0
  232. package/dist/src/selfhost/node-executor.d.ts.map +1 -0
  233. package/dist/src/selfhost/node-executor.js +138 -0
  234. package/dist/src/selfhost/node-executor.js.map +1 -0
  235. package/dist/src/selfhost/project-compiler-adapter.d.ts +77 -0
  236. package/dist/src/selfhost/project-compiler-adapter.d.ts.map +1 -0
  237. package/dist/src/selfhost/project-compiler-adapter.js +358 -0
  238. package/dist/src/selfhost/project-compiler-adapter.js.map +1 -0
  239. package/dist/src/selfhost/project-differential-adapter.d.ts +15 -0
  240. package/dist/src/selfhost/project-differential-adapter.d.ts.map +1 -0
  241. package/dist/src/selfhost/project-differential-adapter.js +65 -0
  242. package/dist/src/selfhost/project-differential-adapter.js.map +1 -0
  243. package/dist/src/selfhost/promotion-evidence.d.ts +62 -0
  244. package/dist/src/selfhost/promotion-evidence.d.ts.map +1 -0
  245. package/dist/src/selfhost/promotion-evidence.js +332 -0
  246. package/dist/src/selfhost/promotion-evidence.js.map +1 -0
  247. package/dist/src/selfhost/promotion-policy-replay-v2.d.ts +54 -0
  248. package/dist/src/selfhost/promotion-policy-replay-v2.d.ts.map +1 -0
  249. package/dist/src/selfhost/promotion-policy-replay-v2.js +343 -0
  250. package/dist/src/selfhost/promotion-policy-replay-v2.js.map +1 -0
  251. package/dist/src/selfhost/promotion-shadow-history-v2.d.ts +55 -0
  252. package/dist/src/selfhost/promotion-shadow-history-v2.d.ts.map +1 -0
  253. package/dist/src/selfhost/promotion-shadow-history-v2.js +227 -0
  254. package/dist/src/selfhost/promotion-shadow-history-v2.js.map +1 -0
  255. package/dist/src/selfhost/promotion-subject.d.ts +34 -0
  256. package/dist/src/selfhost/promotion-subject.d.ts.map +1 -0
  257. package/dist/src/selfhost/promotion-subject.js +141 -0
  258. package/dist/src/selfhost/promotion-subject.js.map +1 -0
  259. package/dist/src/selfhost/source-manifest.d.ts +31 -0
  260. package/dist/src/selfhost/source-manifest.d.ts.map +1 -0
  261. package/dist/src/selfhost/source-manifest.js +194 -0
  262. package/dist/src/selfhost/source-manifest.js.map +1 -0
  263. package/dist/src/selfhost/stage-compiler-facade.d.ts +25 -0
  264. package/dist/src/selfhost/stage-compiler-facade.d.ts.map +1 -0
  265. package/dist/src/selfhost/stage-compiler-facade.js +100 -0
  266. package/dist/src/selfhost/stage-compiler-facade.js.map +1 -0
  267. package/dist/src/semantic-evidence/snapshot.d.ts +114 -0
  268. package/dist/src/semantic-evidence/snapshot.d.ts.map +1 -0
  269. package/dist/src/semantic-evidence/snapshot.js +336 -0
  270. package/dist/src/semantic-evidence/snapshot.js.map +1 -0
  271. package/dist/src/source.d.ts +25 -0
  272. package/dist/src/source.d.ts.map +1 -0
  273. package/dist/src/source.js +10 -0
  274. package/dist/src/source.js.map +1 -0
  275. package/dist/src/syntax/cst-to-ast.d.ts +108 -0
  276. package/dist/src/syntax/cst-to-ast.d.ts.map +1 -0
  277. package/dist/src/syntax/cst-to-ast.js +383 -0
  278. package/dist/src/syntax/cst-to-ast.js.map +1 -0
  279. package/dist/src/syntax/documentation.d.ts +11 -0
  280. package/dist/src/syntax/documentation.d.ts.map +1 -0
  281. package/dist/src/syntax/documentation.js +261 -0
  282. package/dist/src/syntax/documentation.js.map +1 -0
  283. package/dist/src/syntax/parser.d.ts +114 -0
  284. package/dist/src/syntax/parser.d.ts.map +1 -0
  285. package/dist/src/syntax/parser.js +970 -0
  286. package/dist/src/syntax/parser.js.map +1 -0
  287. package/dist/src/syntax/syntax-metadata.d.ts +8 -0
  288. package/dist/src/syntax/syntax-metadata.d.ts.map +1 -0
  289. package/dist/src/syntax/syntax-metadata.js +14 -0
  290. package/dist/src/syntax/syntax-metadata.js.map +1 -0
  291. package/dist/src/syntax/tokens.d.ts +96 -0
  292. package/dist/src/syntax/tokens.d.ts.map +1 -0
  293. package/dist/src/syntax/tokens.js +260 -0
  294. package/dist/src/syntax/tokens.js.map +1 -0
  295. package/dist/src/types/types.d.ts +97 -0
  296. package/dist/src/types/types.d.ts.map +1 -0
  297. package/dist/src/types/types.js +105 -0
  298. package/dist/src/types/types.js.map +1 -0
  299. package/package.json +47 -0
  300. package/schema/diagnostics-v1.schema.json +75 -0
@@ -0,0 +1,1029 @@
1
+ import { escapeTemplate, javascriptStringLiteral, panicEmitter, safeName } from './helpers.js';
2
+ import { runtimeImportLines } from './runtime-imports.js';
3
+ import { SourceWriter } from './writer.js';
4
+ const nativeChildrenProperty = '$viruneChildren';
5
+ export class JavaScriptEmitter {
6
+ #writer;
7
+ #semantic;
8
+ #source;
9
+ #outputFile;
10
+ #symbolNames = new Map();
11
+ #componentParameterReads = new Map();
12
+ #repetitionReads = new Map();
13
+ #repetitionHost;
14
+ #sourceMap;
15
+ #contextName = '$ctx';
16
+ #temporary = 0;
17
+ #currentAsync = false;
18
+ #deferStacks = [];
19
+ constructor(hir, source, outputFile, options = {}) {
20
+ this.#semantic = hir.semantic;
21
+ this.#source = source;
22
+ this.#outputFile = outputFile;
23
+ this.#repetitionHost = options.repetitionHost;
24
+ this.#sourceMap = options.sourceMap ?? true;
25
+ this.#writer = new SourceWriter(source, outputFile, options.sourcePath ?? source.path, options.sourcesContent ?? true);
26
+ for (const [id, symbol] of hir.semantic.symbols)
27
+ this.#symbolNames.set(id, safeName(symbol.name));
28
+ }
29
+ emit(module) {
30
+ this.#moduleDeclarations = module.declarations;
31
+ for (const declaration of module.declarations) {
32
+ if (declaration.kind === 'FunctionDeclaration' && declaration.symbolId !== undefined && declaration.attributes.some(item => item.name === 'jsExport'))
33
+ this.#symbolNames.set(declaration.symbolId, `$impl_${safeName(declaration.name)}`);
34
+ }
35
+ this.emitHeader(module);
36
+ for (const declaration of module.imports)
37
+ this.emitImport(declaration);
38
+ this.emitExternImports(module.declarations);
39
+ if (this.#repetitionHost !== undefined)
40
+ this.#writer.line(`import * as $viruneRepetitionHostModule from ${JSON.stringify(this.#repetitionHost.moduleSpecifier)};`);
41
+ if (module.imports.length > 0 || module.declarations.some(item => item.kind === 'ExternDeclaration') || this.#repetitionHost !== undefined)
42
+ this.#writer.line();
43
+ for (const declaration of module.declarations) {
44
+ this.emitDeclaration(declaration);
45
+ this.#writer.line();
46
+ }
47
+ const result = this.#writer.result();
48
+ return { code: this.#sourceMap ? `${result.code}//# sourceMappingURL=${this.outputMapName()}\n` : result.code, map: result.map };
49
+ }
50
+ emitHeader(module) {
51
+ const hasCallableProjection = (this.#semantic.interop.callableProjections?.length ?? 0) > 0 || (this.#semantic.interop.objectCallableProjections?.length ?? 0) > 0 || this.#semantic.frontendCallableProjections.length > 0;
52
+ for (const line of runtimeImportLines(module, hasCallableProjection))
53
+ this.#writer.line(line);
54
+ if (hasCallableProjection) {
55
+ this.#writer.line("const $viruneCallableShimCacheKey = '$virune.callable-shim.cache/v1';");
56
+ this.#writer.line('const $viruneCallableShimObject = ({}).constructor;');
57
+ this.#writer.line('function $viruneProjectCallable($fn, $descriptor, $factory) {');
58
+ this.#writer.indent(() => {
59
+ this.#writer.line('let $cache = $viruneCallableShimObject.getOwnPropertyDescriptor($fn, $viruneCallableShimCacheKey)?.value;');
60
+ this.#writer.line('if ($cache === undefined) {');
61
+ this.#writer.indent(() => {
62
+ this.#writer.line('$cache = [];');
63
+ this.#writer.line('$viruneCallableShimObject.defineProperty($fn, $viruneCallableShimCacheKey, { value: $cache, enumerable: false, configurable: false, writable: false });');
64
+ });
65
+ this.#writer.line('}');
66
+ this.#writer.line('for (const $entry of $cache) if ($entry[0] === $descriptor) return $entry[1];');
67
+ this.#writer.line('const $shim = $factory($fn);');
68
+ this.#writer.line('$cache.push([$descriptor, $shim]);');
69
+ this.#writer.line('return $shim;');
70
+ });
71
+ this.#writer.line('}');
72
+ this.#writer.line();
73
+ }
74
+ }
75
+ outputMapName() { return `${this.#outputFile.split(/[\\/]/u).at(-1) ?? 'module.js'}.map`; }
76
+ emitImport(declaration) {
77
+ if (declaration.typeOnly)
78
+ return;
79
+ const source = declaration.sourceKind === 'virune' && declaration.source.endsWith('.virune')
80
+ ? declaration.source.slice(0, -7) + '.js'
81
+ : declaration.sourceKind === 'javascript' && declaration.source.endsWith('.interop.ts')
82
+ ? declaration.source.slice(0, -3) + '.mjs'
83
+ : declaration.source;
84
+ this.#writer.mark(declaration.span);
85
+ if (declaration.defaultImport !== undefined) {
86
+ this.#writer.line(`import ${safeName(declaration.defaultImport)} from ${JSON.stringify(source)};`);
87
+ if (declaration.public)
88
+ this.#writer.line(`export { ${safeName(declaration.defaultImport)} };`);
89
+ return;
90
+ }
91
+ if (declaration.namespaceImport !== undefined) {
92
+ this.#writer.line(`import * as ${safeName(declaration.namespaceImport)} from ${JSON.stringify(source)};`);
93
+ if (declaration.public)
94
+ this.#writer.line(`export { ${safeName(declaration.namespaceImport)} };`);
95
+ return;
96
+ }
97
+ if (declaration.items.length === 0) {
98
+ this.#writer.line(`import ${JSON.stringify(source)};`);
99
+ return;
100
+ }
101
+ const items = declaration.items.map(item => item.imported === item.local ? item.imported : `${item.imported} as ${safeName(item.local)}`).join(', ');
102
+ this.#writer.line(`import { ${items} } from ${JSON.stringify(source)};`);
103
+ if (declaration.public)
104
+ this.#writer.line(`export { ${declaration.items.map(item => safeName(item.local)).join(', ')} };`);
105
+ }
106
+ emitExternImports(declarations) {
107
+ let index = 0;
108
+ for (const declaration of declarations) {
109
+ if (declaration.kind !== 'ExternDeclaration')
110
+ continue;
111
+ this.#writer.line(`import * as $ffi${index} from ${JSON.stringify(declaration.module)};`);
112
+ index++;
113
+ }
114
+ }
115
+ emitDeclaration(declaration) {
116
+ this.#writer.mark(declaration.span, 'name' in declaration ? declaration.name : undefined);
117
+ switch (declaration.kind) {
118
+ case 'FunctionDeclaration':
119
+ this.emitFunction(declaration);
120
+ break;
121
+ case 'ComponentDeclaration':
122
+ this.emitComponent(declaration);
123
+ break;
124
+ case 'RecordDeclaration':
125
+ this.emitRecordDeclaration(declaration);
126
+ break;
127
+ case 'EnumDeclaration':
128
+ this.emitEnumDeclaration(declaration);
129
+ break;
130
+ case 'NewtypeDeclaration':
131
+ this.emitNewtypeDeclaration(declaration);
132
+ break;
133
+ case 'TypeAliasDeclaration':
134
+ this.#writer.line(`// type ${declaration.name} is erased during JavaScript emission.`);
135
+ break;
136
+ case 'ExternDeclaration':
137
+ this.emitExternDeclaration(declaration);
138
+ break;
139
+ case 'TestDeclaration':
140
+ this.emitTest(declaration);
141
+ break;
142
+ case 'TopLevelLetDeclaration':
143
+ this.#writer.line(`${this.moduleExported(declaration) ? 'export ' : this.exportPrefix(declaration.attributes)}const ${this.nameOf(declaration.symbolId, declaration.name)} = ${this.expression(declaration.value)};`);
144
+ break;
145
+ }
146
+ }
147
+ emitRecordDeclaration(declaration) {
148
+ this.#writer.line(`// record ${declaration.name}`);
149
+ if (this.moduleExported(declaration))
150
+ this.#writer.line(`export const ${safeName(declaration.name)} = Object.freeze({ name: ${JSON.stringify(declaration.name)}, typeId: ${JSON.stringify(this.declarationTypeId(declaration.symbolId, declaration.definitionId ?? declaration.name))} });`);
151
+ }
152
+ emitEnumDeclaration(declaration) {
153
+ this.#writer.line(`// enum ${declaration.name}`);
154
+ for (const variant of declaration.variants) {
155
+ const prefix = this.moduleExported(declaration) ? 'export ' : '';
156
+ const name = this.nameOf(variant.symbolId, variant.name);
157
+ if (variant.values.length === 0)
158
+ this.#writer.line(`${prefix}const ${name} = Object.freeze(makeVariant(${JSON.stringify(variant.name)}, [], ${JSON.stringify(this.declarationTypeId(declaration.symbolId, declaration.definitionId ?? declaration.name))}));`);
159
+ else {
160
+ const args = variant.values.map((_, index) => `$value${index}`).join(', ');
161
+ this.#writer.line(`${prefix}function ${name}(${args}) { return makeVariant(${JSON.stringify(variant.name)}, [${args}], ${JSON.stringify(this.declarationTypeId(declaration.symbolId, declaration.definitionId ?? declaration.name))}); }`);
162
+ }
163
+ }
164
+ this.#writer.line(`${this.moduleExported(declaration) ? 'export ' : ''}const ${safeName(declaration.name)} = Object.freeze({ ${declaration.variants.map(variant => safeName(variant.name)).join(', ')} });`);
165
+ }
166
+ emitNewtypeDeclaration(declaration) {
167
+ const prefix = this.moduleExported(declaration) ? 'export ' : '';
168
+ this.#writer.line(`${prefix}const ${safeName(declaration.name)} = Object.freeze({ create: value => value });`);
169
+ }
170
+ emitFunction(declaration) {
171
+ const jsExport = declaration.attributes.some(item => item.name === 'jsExport');
172
+ const prefix = this.moduleExported(declaration) && !jsExport ? 'export ' : '';
173
+ const asyncKeyword = declaration.async ? 'async ' : '';
174
+ const name = this.nameOf(declaration.symbolId, declaration.name);
175
+ const parameters = declaration.parameters.map(parameter => this.nameOf(parameter.symbolId, parameter.name));
176
+ parameters.push('$ctx = rootTaskContext()');
177
+ this.#writer.line(`${prefix}${asyncKeyword}function ${name}(${parameters.join(', ')}) {`);
178
+ const previousAsync = this.#currentAsync;
179
+ const previousContextName = this.#contextName;
180
+ this.#currentAsync = declaration.async;
181
+ this.#contextName = '$ctx';
182
+ this.#writer.indent(() => {
183
+ this.#writer.line('try {');
184
+ this.#writer.indent(() => {
185
+ if (declaration.expressionBody)
186
+ this.#writer.line(`return ${this.expression(declaration.body)};`);
187
+ else
188
+ this.emitBlock(declaration.body);
189
+ });
190
+ this.#writer.line('} catch ($error) {');
191
+ this.#writer.indent(() => {
192
+ this.#writer.line('if (isPropagation($error)) return $error.value;');
193
+ this.#writer.line('throw $error;');
194
+ });
195
+ this.#writer.line('}');
196
+ });
197
+ this.#writer.line('}');
198
+ this.#currentAsync = previousAsync;
199
+ this.#contextName = previousContextName;
200
+ if (jsExport)
201
+ this.emitJsExportWrapper(declaration, name);
202
+ }
203
+ emitComponent(declaration) {
204
+ const prefix = this.moduleExported(declaration) ? 'export ' : '';
205
+ const name = this.nameOf(declaration.symbolId, declaration.name);
206
+ this.#componentParameterReads.clear();
207
+ for (const parameter of declaration.parameters) {
208
+ if (parameter.symbolId === undefined)
209
+ return panicEmitter(`Component parameter ${parameter.name} reached emission without a symbol`);
210
+ this.#componentParameterReads.set(parameter.symbolId, `$viruneValidateSafeFfiValue($props[${JSON.stringify(parameter.name)}], ${this.safeFfiBoundary(this.typeDescriptor(parameter.type))}, ${JSON.stringify(`$.${parameter.name}`)})`);
211
+ }
212
+ const previousAsync = this.#currentAsync;
213
+ const previousContextName = this.#contextName;
214
+ this.#currentAsync = false;
215
+ this.#contextName = '$ctx';
216
+ this.#writer.line(`${prefix}function ${name}($props) {`);
217
+ this.#writer.indent(() => {
218
+ this.#writer.line('const $ctx = rootTaskContext();');
219
+ this.#writer.line('try {');
220
+ this.#writer.indent(() => this.emitBlock(declaration.body));
221
+ this.#writer.line('} catch ($error) {');
222
+ this.#writer.indent(() => {
223
+ this.#writer.line('if (isPropagation($error)) return $error.value;');
224
+ this.#writer.line('throw $error;');
225
+ });
226
+ this.#writer.line('}');
227
+ });
228
+ this.#writer.line('}');
229
+ this.#currentAsync = previousAsync;
230
+ this.#contextName = previousContextName;
231
+ this.#componentParameterReads.clear();
232
+ }
233
+ emitJsExportWrapper(declaration, implementationName) {
234
+ const rawParameters = declaration.parameters.map(parameter => `$js_${safeName(parameter.name)}`);
235
+ this.#writer.line(`export ${declaration.async ? 'async ' : ''}function ${safeName(declaration.name)}(${rawParameters.join(', ')}) {`);
236
+ this.#writer.indent(() => {
237
+ const validated = declaration.parameters.map((parameter, index) => {
238
+ const local = `$arg_${safeName(parameter.name)}`;
239
+ this.#writer.line(`const ${local} = $viruneValidateSafeFfiValue(${rawParameters[index]}, ${this.safeFfiBoundary(this.typeDescriptor(parameter.type))}, ${JSON.stringify(`$.${parameter.name}`)});`);
240
+ return local;
241
+ });
242
+ const call = `${implementationName}(${[...validated, 'rootTaskContext()'].join(', ')})`;
243
+ this.#writer.line(`return $viruneEncodeSafeFfiValue(${declaration.async ? `await ${call}` : call}, ${this.safeFfiBoundary(this.typeDescriptor(declaration.returnType))});`);
244
+ });
245
+ this.#writer.line('}');
246
+ }
247
+ emitExternDeclaration(declaration) {
248
+ const externIndex = this.externIndex(declaration);
249
+ for (const fn of declaration.functions) {
250
+ const name = this.nameOf(fn.symbolId, fn.name);
251
+ const args = fn.parameters.map(parameter => this.nameOf(parameter.symbolId, parameter.name));
252
+ if (fn.async)
253
+ args.push('$ctx = rootTaskContext()');
254
+ const encoded = fn.parameters.map(parameter => declaration.unsafe
255
+ ? `encodeFfiValue(${this.nameOf(parameter.symbolId, parameter.name)}, ${this.typeDescriptor(parameter.type)})`
256
+ : `$viruneEncodeSafeFfiValue(${this.nameOf(parameter.symbolId, parameter.name)}, ${this.safeFfiBoundary(this.typeDescriptor(parameter.type))})`);
257
+ const optionalCount = [...fn.parameters].reverse().findIndex(parameter => !parameter.optional);
258
+ const trailingOptional = optionalCount < 0 ? fn.parameters.length : optionalCount;
259
+ const encodedArguments = encoded.join(', ');
260
+ const requiredCount = fn.parameters.length - trailingOptional;
261
+ const foreignMember = `$ffi${externIndex}[${javascriptStringLiteral(fn.jsName)}]`;
262
+ const directCall = `${foreignMember}(${encodedArguments})`;
263
+ const spreadCall = `${foreignMember}(...$args)`;
264
+ const argumentSetup = `const $args = [${encodedArguments}]; while ($args.length > ${requiredCount} && $args[$args.length - 1] === undefined) $args.pop();`;
265
+ if (declaration.unsafe) {
266
+ const body = trailingOptional === 0 ? `{ return ${directCall}; }` : `{ ${argumentSetup} return ${spreadCall}; }`;
267
+ this.#writer.line(`${fn.async ? 'async ' : ''}function ${name}(${args.join(', ')}) ${body}`);
268
+ }
269
+ else if (fn.async) {
270
+ const success = fn.returnType.name === 'Result' ? fn.returnType.arguments[0] : undefined;
271
+ const operation = trailingOptional === 0 ? `() => ${directCall}` : `() => { ${argumentSetup} return ${spreadCall}; }`;
272
+ this.#writer.line(`async function ${name}(${args.join(', ')}) { return safeCallAsync(${operation}, $value => $viruneValidateSafeFfiValue($value, ${this.safeFfiBoundary(this.typeDescriptor(success))}, '$')); }`);
273
+ }
274
+ else {
275
+ const success = fn.returnType.name === 'Result' ? fn.returnType.arguments[0] : undefined;
276
+ const descriptor = this.safeFfiBoundary(this.typeDescriptor(success));
277
+ const directValidated = `$viruneValidateSafeFfiValue(${directCall}, ${descriptor}, '$')`;
278
+ const spreadValidated = `$viruneValidateSafeFfiValue(${spreadCall}, ${descriptor}, '$')`;
279
+ const operation = trailingOptional === 0 ? `() => ${directValidated}` : `() => { ${argumentSetup} return ${spreadValidated}; }`;
280
+ this.#writer.line(`function ${name}(${args.join(', ')}) { return safeCall(${operation}); }`);
281
+ }
282
+ }
283
+ }
284
+ externIndex(target) {
285
+ let index = 0;
286
+ for (const declaration of this.currentModuleDeclarations()) {
287
+ if (declaration === target)
288
+ return index;
289
+ if (declaration.kind === 'ExternDeclaration')
290
+ index++;
291
+ }
292
+ return index;
293
+ }
294
+ #moduleDeclarations = [];
295
+ currentModuleDeclarations() { return this.#moduleDeclarations; }
296
+ emitTest(declaration) {
297
+ this.#writer.line(`test(${JSON.stringify(declaration.name)}, ${declaration.async ? 'async ' : ''}() => {`);
298
+ const previousAsync = this.#currentAsync;
299
+ this.#currentAsync = declaration.async;
300
+ this.#writer.indent(() => {
301
+ this.#writer.line('const $ctx = rootTaskContext();');
302
+ this.emitBlock(declaration.body);
303
+ });
304
+ this.#currentAsync = previousAsync;
305
+ this.#writer.line('});');
306
+ }
307
+ emitBlock(block) {
308
+ const hasDefer = block.statements.some(statement => statement.kind === 'DeferStatement');
309
+ if (!hasDefer) {
310
+ for (const statement of block.statements)
311
+ this.emitStatement(statement);
312
+ return;
313
+ }
314
+ const stack = `$defers${this.#temporary++}`;
315
+ const primary = `$primary${this.#temporary++}`;
316
+ this.#writer.line(`const ${stack} = [];`);
317
+ this.#writer.line(`let ${primary};`);
318
+ this.#writer.line('try {');
319
+ this.#deferStacks.push(stack);
320
+ this.#writer.indent(() => { for (const statement of block.statements)
321
+ this.emitStatement(statement); });
322
+ this.#deferStacks.pop();
323
+ this.#writer.line('} catch ($error) {');
324
+ this.#writer.indent(() => { this.#writer.line(`${primary} = $error;`); this.#writer.line('throw $error;'); });
325
+ this.#writer.line('} finally {');
326
+ this.#writer.indent(() => this.#writer.line(`${this.#currentAsync ? 'await runDefersAsync' : 'runDefers'}(${stack}, ${primary});`));
327
+ this.#writer.line('}');
328
+ }
329
+ emitStatement(statement) {
330
+ this.#writer.mark(statement.span);
331
+ switch (statement.kind) {
332
+ case 'LetStatement':
333
+ this.#writer.line(`${statement.mutable ? 'let' : 'const'} ${this.nameOf(statement.symbolId, statement.name)} = ${this.expression(statement.value)};`);
334
+ break;
335
+ case 'ReturnStatement':
336
+ this.#writer.line(statement.value === undefined ? 'return undefined;' : `return ${this.expression(statement.value)};`);
337
+ break;
338
+ case 'IfStatement':
339
+ this.emitIf(statement);
340
+ break;
341
+ case 'ForStatement': {
342
+ this.#writer.line(`for (const ${this.nameOf(statement.symbolId, statement.name)} of ${this.expression(statement.iterable)}) {`);
343
+ this.#writer.indent(() => this.emitBlock(statement.body));
344
+ this.#writer.line('}');
345
+ break;
346
+ }
347
+ case 'WhileStatement':
348
+ this.#writer.line(`while (${this.expression(statement.condition)}) {`);
349
+ this.#writer.indent(() => this.emitBlock(statement.body));
350
+ this.#writer.line('}');
351
+ break;
352
+ case 'AssignmentStatement':
353
+ if (statement.invalidTarget !== undefined)
354
+ throw new Error('Invalid assignment target reached JavaScript emission');
355
+ this.#writer.line(`${this.nameOf(statement.targetSymbolId, statement.name)} = ${this.expression(statement.value)};`);
356
+ break;
357
+ case 'MemberAssignmentStatement':
358
+ if (statement.foreignWrite !== true)
359
+ throw new Error('Unproven writable facet reached JavaScript emission');
360
+ this.#writer.line(`${this.expression(statement.target)}[${JSON.stringify(statement.field)}] = ${this.expression(statement.value)};`);
361
+ break;
362
+ case 'IndexAssignmentStatement':
363
+ if (statement.foreignWrite !== true)
364
+ throw new Error('Unproven writable facet reached JavaScript emission');
365
+ this.#writer.line(`${this.expression(statement.target)}[${this.expression(statement.index)}] = ${this.expression(statement.value)};`);
366
+ break;
367
+ case 'DeferStatement': {
368
+ const stack = this.#deferStacks.at(-1);
369
+ if (stack === undefined)
370
+ throw new Error('defer emitted outside deferred block');
371
+ this.#writer.line(`${stack}.push(${this.#currentAsync ? 'async ' : ''}() => ${this.expression(statement.expression)});`);
372
+ break;
373
+ }
374
+ case 'BreakStatement':
375
+ this.#writer.line('break;');
376
+ break;
377
+ case 'ContinueStatement':
378
+ this.#writer.line('continue;');
379
+ break;
380
+ case 'DiscardStatement':
381
+ this.#writer.line(`void ${this.expression(statement.expression)};`);
382
+ break;
383
+ case 'ExpressionStatement':
384
+ this.#writer.line(`${this.expression(statement.expression)};`);
385
+ break;
386
+ }
387
+ }
388
+ emitIf(statement) {
389
+ this.#writer.line(`if (${this.expression(statement.condition)}) {`);
390
+ this.#writer.indent(() => this.emitBlock(statement.thenBlock));
391
+ if (statement.elseBranch === undefined)
392
+ this.#writer.line('}');
393
+ else if (statement.elseBranch.kind === 'BlockStatement') {
394
+ this.#writer.line('} else {');
395
+ this.#writer.indent(() => this.emitBlock(statement.elseBranch));
396
+ this.#writer.line('}');
397
+ }
398
+ else {
399
+ this.#writer.line('} else {');
400
+ this.#writer.indent(() => this.emitIf(statement.elseBranch));
401
+ this.#writer.line('}');
402
+ }
403
+ }
404
+ expression(expression, contextName = this.#contextName) {
405
+ const raw = this.expressionRaw(expression, contextName);
406
+ switch (expression.foreignBridge) {
407
+ case 'string': return `checkForeignString(${raw})`;
408
+ case 'bool': return `checkForeignBool(${raw})`;
409
+ case 'float': return `checkForeignFloat(${raw})`;
410
+ case 'bigint': return `checkForeignBigInt(${raw})`;
411
+ case 'unit': return `(${raw}, undefined)`;
412
+ case 'unknown': return `$viruneValidateSafeFfiValue(${raw}, ${this.safeFfiBoundary(`{ kind: 'unknown' }`)})`;
413
+ default: return raw;
414
+ }
415
+ }
416
+ expressionRaw(expression, contextName = this.#contextName) {
417
+ switch (expression.kind) {
418
+ case 'LiteralExpression': return this.literal(expression);
419
+ case 'IdentifierExpression': return this.identifier(expression);
420
+ case 'WildcardExpression': return 'undefined';
421
+ case 'CallExpression': return this.call(expression, contextName);
422
+ case 'FieldExpression': return this.field(expression, contextName);
423
+ case 'IndexExpression':
424
+ if (expression.foreignIndex !== true)
425
+ return panicEmitter('IndexExpression reached emission without a checked index facet');
426
+ return `${this.expression(expression.target, contextName)}[${this.expression(expression.index, contextName)}]`;
427
+ case 'BinaryExpression': return this.binary(expression, contextName);
428
+ case 'UnaryExpression': return expression.operator === '-' && expression.inferredTypeId === this.#semantic.arena.int ? `intNegate(${this.expression(expression.operand, contextName)})` : `(${expression.operator}${this.expression(expression.operand, contextName)})`;
429
+ case 'PipelineExpression': return panicEmitter('PipelineExpression should be lowered before emission');
430
+ case 'TryExpression': return `propagate(${this.expression(expression.operand, contextName)})`;
431
+ case 'AwaitExpression': return `(await ${this.expression(expression.operand, contextName)})`;
432
+ case 'RecordExpression': {
433
+ const type = expression.inferredTypeId === undefined ? undefined : this.#semantic.arena.get(expression.inferredTypeId);
434
+ const typeId = type?.kind === 'named' ? type.definitionId : expression.name;
435
+ return `makeRecord({ ${expression.entries.map(entry => `${safeName(entry.name)}: ${this.expression(entry.value, contextName)}`).join(', ')} }, ${javascriptStringLiteral(typeId)})`;
436
+ }
437
+ case 'RecordUpdateExpression': return `updateRecord(${this.expression(expression.base, contextName)}, { ${expression.entries.map(entry => `${safeName(entry.name)}: ${this.expression(entry.value, contextName)}`).join(', ')} })`;
438
+ case 'ContextualAggregateExpression': return this.contextualAggregate(expression, contextName);
439
+ case 'ListExpression': return `[${expression.items.map(item => this.expression(item, contextName)).join(', ')}]`;
440
+ case 'TupleExpression': return `[${expression.items.map(item => this.expression(item, contextName)).join(', ')}]`;
441
+ case 'ConditionalExpression': return `(${this.expression(expression.condition, contextName)} ? ${this.expression(expression.thenExpression, contextName)} : ${this.expression(expression.elseExpression, contextName)})`;
442
+ case 'MatchExpression': return this.match(expression, contextName);
443
+ case 'LambdaExpression': return this.lambdaExpression(expression, contextName);
444
+ case 'ParallelExpression': return this.parallelExpression(expression, contextName);
445
+ case 'ViewExpression': return this.viewBlockExpression(expression.body, contextName);
446
+ }
447
+ }
448
+ viewBlockExpression(block, contextName) {
449
+ if (block.children.length === 1)
450
+ return this.viewChildExpression(block.children[0], contextName);
451
+ return `<>${block.children.map(child => this.viewChild(child, contextName)).join('')}</>`;
452
+ }
453
+ viewBlockContents(block, contextName) {
454
+ return block.children.map(child => this.viewChild(child, contextName)).join('');
455
+ }
456
+ viewChild(child, contextName) {
457
+ switch (child.kind) {
458
+ case 'ViewTextChild': return `{${javascriptStringLiteral(child.value)}}`;
459
+ case 'ViewExpressionChild': return `{${this.expression(child.expression, contextName)}}`;
460
+ case 'ViewChildrenSlot': return `{${this.nativeChildrenSlotExpression()}}`;
461
+ case 'ViewElement': return this.viewElement(child, contextName);
462
+ case 'ViewConditional': return `{${this.viewConditionalExpression(child, contextName)}}`;
463
+ case 'ViewRepetition': return `{${this.viewRepetitionExpression(child)}}`;
464
+ }
465
+ }
466
+ viewChildExpression(child, contextName) {
467
+ switch (child.kind) {
468
+ case 'ViewTextChild': return javascriptStringLiteral(child.value);
469
+ case 'ViewExpressionChild': return this.expression(child.expression, contextName);
470
+ case 'ViewChildrenSlot': return this.nativeChildrenSlotExpression();
471
+ case 'ViewElement': return this.viewElement(child, contextName);
472
+ case 'ViewConditional': return `(${this.viewConditionalExpression(child, contextName)})`;
473
+ case 'ViewRepetition': return this.viewRepetitionExpression(child);
474
+ }
475
+ }
476
+ nativeChildrenSlotExpression() {
477
+ return `(() => { const $slot = $props[${javascriptStringLiteral(nativeChildrenProperty)}]; return $slot === undefined ? <></> : $slot(); })()`;
478
+ }
479
+ viewElement(element, contextName) {
480
+ const tag = element.tag.join('.');
481
+ const root = element.tag[0];
482
+ const native = element.tag.length === 1 && root !== undefined && this.#semantic.globalScope.lookup(root)?.kind === 'component';
483
+ const properties = element.properties.map((property, propertyIndex) => `${property.name}={${native ? this.nativeComponentPropertyValue(property.value, contextName) : this.frontendViewPropertyValue(element.id, propertyIndex, property, contextName)}}`);
484
+ if (native && element.children !== undefined)
485
+ properties.push(`${nativeChildrenProperty}={() => ${this.viewBlockExpression(element.children, contextName)}}`);
486
+ const attributes = properties.length === 0 ? '' : ` ${properties.join(' ')}`;
487
+ if (element.children === undefined || native)
488
+ return `<${tag}${attributes} />`;
489
+ return `<${tag}${attributes}>${this.viewBlockContents(element.children, contextName)}</${tag}>`;
490
+ }
491
+ frontendViewPropertyValue(elementId, propertyIndex, property, contextName) {
492
+ const projection = this.#semantic.frontendCallableProjections.find(item => item.viewElementNodeId === elementId && item.propertyIndex === propertyIndex && item.property === property.name);
493
+ if (projection?.viewCallback !== undefined) {
494
+ if (property.value.kind !== 'LambdaExpression' || !property.value.expressionBody || property.value.body.kind !== 'ViewExpression')
495
+ return panicEmitter('View callback projection reached emission without a direct View lambda');
496
+ const raw = this.lambdaExpression(property.value, contextName, true);
497
+ return this.frontendViewCallbackProjection(raw, projection.viewCallback);
498
+ }
499
+ const descriptor = projection?.descriptor;
500
+ const projectedSyncExternal = property.value.kind === 'LambdaExpression' && descriptor?.async === false;
501
+ const raw = projectedSyncExternal ? this.lambdaExpression(property.value, contextName, true) : this.expression(property.value, contextName);
502
+ return descriptor === undefined ? raw : this.callableProjection(raw, descriptor);
503
+ }
504
+ nativeComponentPropertyValue(expression, contextName) {
505
+ const value = this.expression(expression, contextName);
506
+ const typeId = expression.inferredTypeId;
507
+ if (typeId === undefined)
508
+ return value;
509
+ const type = this.#semantic.arena.get(typeId);
510
+ if (type.kind !== 'named' || type.declarationKind !== 'record')
511
+ return value;
512
+ return `encodeFfiValue(${value}, ${this.safeFfiBoundary(this.typeDescriptorFromTypeId(typeId))})`;
513
+ }
514
+ viewConditionalExpression(conditional, contextName) {
515
+ const thenBranch = this.viewBlockExpression(conditional.thenBlock, contextName);
516
+ const elseBranch = conditional.elseBranch === undefined
517
+ ? '<></>'
518
+ : conditional.elseBranch.kind === 'ViewBlock'
519
+ ? this.viewBlockExpression(conditional.elseBranch, contextName)
520
+ : `(${this.viewConditionalExpression(conditional.elseBranch, contextName)})`;
521
+ return `${this.expression(conditional.condition, contextName)} ? ${thenBranch} : ${elseBranch}`;
522
+ }
523
+ checkedViewRepetitionEvidence(repetition) {
524
+ const evidence = repetition.checkedEvidence;
525
+ if (evidence === undefined || (repetition.indexName === undefined) !== (evidence.indexSymbolId === undefined))
526
+ return panicEmitter('View repetition reached emission without matching checked repetition evidence');
527
+ return evidence;
528
+ }
529
+ viewRepetitionExpression(repetition) {
530
+ this.checkedViewRepetitionEvidence(repetition);
531
+ if (this.#repetitionHost === undefined)
532
+ return panicEmitter('View repetition reached emission without a project Repetition Host binding');
533
+ return this.hostViewRepetitionExpression(repetition);
534
+ }
535
+ hostViewRepetitionExpression(repetition) {
536
+ const evidence = this.checkedViewRepetitionEvidence(repetition);
537
+ const identity = repetition.identity;
538
+ if (this.#repetitionHost === undefined)
539
+ return panicEmitter('Host View repetition reached emission without Host binding');
540
+ const identityType = identity.inferredTypeId === undefined ? undefined : this.#semantic.arena.get(identity.inferredTypeId);
541
+ if (identityType?.kind !== 'primitive' || (identityType.name !== 'String' && identityType.name !== 'Int'))
542
+ return panicEmitter('Host View repetition identity reached emission without checked String or Int evidence');
543
+ const sourceName = `$viewSource${this.#temporary++}`;
544
+ const lengthName = `$viewLength${this.#temporary++}`;
545
+ const sourceIndexName = `$viewIndex${this.#temporary++}`;
546
+ const itemName = this.nameOf(evidence.itemSymbolId, repetition.itemName);
547
+ const indexName = repetition.indexName === undefined ? undefined : this.nameOf(evidence.indexSymbolId, repetition.indexName);
548
+ const identitySeenName = `$viewIdentitySeen${this.#temporary++}`;
549
+ const identityName = `$viewIdentity${this.#temporary++}`;
550
+ const entriesName = `$viewEntries${this.#temporary++}`;
551
+ const snapshotContextName = `$viewSnapshotCtx${this.#temporary++}`;
552
+ const readValueName = `$viewReadValue${this.#temporary++}`;
553
+ const readIndexName = `$viewReadIndex${this.#temporary++}`;
554
+ const groupIdentityName = `$viewGroupIdentity${this.#temporary++}`;
555
+ const bodyContextName = `$viewBodyCtx${this.#temporary++}`;
556
+ const tag = identityType.name === 'String' ? 's:' : 'i:';
557
+ const lines = [
558
+ `$viruneRepetitionHostModule[${javascriptStringLiteral(this.#repetitionHost.exportName)}](`,
559
+ '\t() => {',
560
+ `\t\tconst ${snapshotContextName} = rootTaskContext();`,
561
+ `\t\tconst ${sourceName} = ${this.expression(repetition.source, snapshotContextName)};`,
562
+ `\t\tconst ${lengthName} = ${sourceName}.length;`,
563
+ `\t\tconst ${identitySeenName} = new Set();`,
564
+ `\t\tconst ${entriesName} = [];`,
565
+ `\t\tfor (let ${sourceIndexName} = 0; ${sourceIndexName} < ${lengthName}; ${sourceIndexName}++) {`,
566
+ ];
567
+ if (evidence.sourceKind === 'external-array')
568
+ lines.push(`\t\t\tif (!Object.prototype.hasOwnProperty.call(${sourceName}, ${sourceIndexName})) continue;`);
569
+ lines.push(`\t\t\tconst ${itemName} = ${sourceName}[${sourceIndexName}];`);
570
+ if (indexName !== undefined)
571
+ lines.push(`\t\t\tconst ${indexName} = ${sourceIndexName};`);
572
+ lines.push(`\t\t\tconst ${identityName} = ${JSON.stringify(tag)} + (${this.expression(identity, snapshotContextName)});`);
573
+ lines.push(`\t\t\tif (${identitySeenName}.has(${identityName})) throw new Error('Duplicate View repetition identity');`);
574
+ lines.push(`\t\t\t${identitySeenName}.add(${identityName});`);
575
+ lines.push(`\t\t\t${entriesName}.push({ id: ${identityName}, index: ${sourceIndexName}, value: ${itemName} });`);
576
+ lines.push('\t\t}', `\t\treturn ${entriesName};`, '\t},');
577
+ const previousItem = this.#repetitionReads.get(evidence.itemSymbolId);
578
+ const hadItem = this.#repetitionReads.has(evidence.itemSymbolId);
579
+ this.#repetitionReads.set(evidence.itemSymbolId, `${readValueName}()`);
580
+ let previousIndex;
581
+ let hadIndex = false;
582
+ if (evidence.indexSymbolId !== undefined) {
583
+ previousIndex = this.#repetitionReads.get(evidence.indexSymbolId);
584
+ hadIndex = this.#repetitionReads.has(evidence.indexSymbolId);
585
+ this.#repetitionReads.set(evidence.indexSymbolId, `${readIndexName}()`);
586
+ }
587
+ const body = this.viewBlockExpression(repetition.body, bodyContextName);
588
+ if (hadItem)
589
+ this.#repetitionReads.set(evidence.itemSymbolId, previousItem);
590
+ else
591
+ this.#repetitionReads.delete(evidence.itemSymbolId);
592
+ if (evidence.indexSymbolId !== undefined) {
593
+ if (hadIndex)
594
+ this.#repetitionReads.set(evidence.indexSymbolId, previousIndex);
595
+ else
596
+ this.#repetitionReads.delete(evidence.indexSymbolId);
597
+ }
598
+ lines.push(`\t(${readValueName}, ${readIndexName}, ${groupIdentityName}) => {`, `\t\tconst ${bodyContextName} = rootTaskContext();`, `\t\treturn ${body};`, '\t},', ')');
599
+ return lines.join('\n');
600
+ }
601
+ contextualAggregate(expression, contextName) {
602
+ if (expression.foreignObject !== true)
603
+ return panicEmitter('ContextualAggregateExpression reached emission without a checked aggregate facet');
604
+ const entries = expression.entries.map((entry, index) => {
605
+ const projection = this.#semantic.interop.objectCallableProjections?.find(item => item.objectNodeId === expression.id && item.entryIndex === index && item.property === entry.name);
606
+ const projectedSyncExternal = entry.value.kind === 'LambdaExpression' && projection?.descriptor.version === 'virune-callable-shim/v2' && projection.descriptor.async === false;
607
+ const raw = projectedSyncExternal ? this.lambdaExpression(entry.value, contextName, true) : this.expression(entry.value, contextName);
608
+ const value = projection === undefined ? raw : this.callableProjection(raw, projection.descriptor);
609
+ return `[${JSON.stringify(entry.name)}]: ${value}`;
610
+ });
611
+ return `({ ${entries.join(', ')} })`;
612
+ }
613
+ lambdaExpression(expression, outerContextName, forceProjectedSyncExternal = false) {
614
+ const parameters = expression.parameters.map(parameter => this.nameOf(parameter.symbolId, parameter.name));
615
+ const projectedSyncExternal = !expression.async && (forceProjectedSyncExternal || this.#semantic.interop.callableProjections?.some(item => item.nodeId === expression.id && item.descriptor.async === false) === true);
616
+ const ownsContext = expression.async || projectedSyncExternal;
617
+ const contextName = ownsContext ? `$lambdaCtx${this.#temporary++}` : outerContextName;
618
+ if (ownsContext)
619
+ parameters.push(`${contextName} = rootTaskContext()`);
620
+ const lines = [`${expression.async ? 'async ' : ''}(${parameters.join(', ')}) => {`, '\ttry {'];
621
+ if (expression.expressionBody)
622
+ lines.push(`\t\treturn ${this.expression(expression.body, contextName)};`);
623
+ else
624
+ lines.push(...this.inlineBlock(expression.body, contextName, expression.async, 2));
625
+ lines.push('\t} catch ($error) {', '\t\tif (isPropagation($error)) return $error.value;', '\t\tthrow $error;', '\t}', '}');
626
+ return `(${lines.join('\n')})`;
627
+ }
628
+ inlineBlock(block, contextName, async, indent) {
629
+ const prefix = '\t'.repeat(indent);
630
+ const hasDefer = block.statements.some(statement => statement.kind === 'DeferStatement');
631
+ if (!hasDefer)
632
+ return block.statements.flatMap(statement => this.inlineStatement(statement, contextName, async, indent));
633
+ const stack = `$lambdaDefers${this.#temporary++}`;
634
+ const primary = `$lambdaPrimary${this.#temporary++}`;
635
+ const lines = [`${prefix}const ${stack} = [];`, `${prefix}let ${primary};`, `${prefix}try {`];
636
+ for (const statement of block.statements)
637
+ lines.push(...this.inlineStatement(statement, contextName, async, indent + 1, stack));
638
+ lines.push(`${prefix}} catch ($error) {`, `${prefix}\t${primary} = $error;`, `${prefix}\tthrow $error;`, `${prefix}} finally {`, `${prefix}\t${async ? 'await runDefersAsync' : 'runDefers'}(${stack}, ${primary});`, `${prefix}}`);
639
+ return lines;
640
+ }
641
+ inlineStatement(statement, contextName, async, indent, deferStack) {
642
+ const prefix = '\t'.repeat(indent);
643
+ switch (statement.kind) {
644
+ case 'LetStatement': return [`${prefix}${statement.mutable ? 'let' : 'const'} ${this.nameOf(statement.symbolId, statement.name)} = ${this.expression(statement.value, contextName)};`];
645
+ case 'ReturnStatement': return [`${prefix}${statement.value === undefined ? 'return undefined;' : `return ${this.expression(statement.value, contextName)};`}`];
646
+ case 'AssignmentStatement':
647
+ if (statement.invalidTarget !== undefined)
648
+ throw new Error('Invalid assignment target reached JavaScript emission');
649
+ return [`${prefix}${this.nameOf(statement.targetSymbolId, statement.name)} = ${this.expression(statement.value, contextName)};`];
650
+ case 'MemberAssignmentStatement':
651
+ if (statement.foreignWrite !== true)
652
+ throw new Error('Unproven writable facet reached JavaScript emission');
653
+ return [`${prefix}${this.expression(statement.target, contextName)}[${JSON.stringify(statement.field)}] = ${this.expression(statement.value, contextName)};`];
654
+ case 'IndexAssignmentStatement':
655
+ if (statement.foreignWrite !== true)
656
+ throw new Error('Unproven writable facet reached JavaScript emission');
657
+ return [`${prefix}${this.expression(statement.target, contextName)}[${this.expression(statement.index, contextName)}] = ${this.expression(statement.value, contextName)};`];
658
+ case 'BreakStatement': return [`${prefix}break;`];
659
+ case 'ContinueStatement': return [`${prefix}continue;`];
660
+ case 'DiscardStatement': return [`${prefix}void ${this.expression(statement.expression, contextName)};`];
661
+ case 'ExpressionStatement': return [`${prefix}${this.expression(statement.expression, contextName)};`];
662
+ case 'DeferStatement': {
663
+ if (deferStack === undefined)
664
+ throw new Error('defer emitted without an inline defer stack');
665
+ return [`${prefix}${deferStack}.push(${async ? 'async ' : ''}() => ${this.expression(statement.expression, contextName)});`];
666
+ }
667
+ case 'WhileStatement': return [`${prefix}while (${this.expression(statement.condition, contextName)}) {`, ...this.inlineBlock(statement.body, contextName, async, indent + 1), `${prefix}}`];
668
+ case 'ForStatement': return [`${prefix}for (const ${this.nameOf(statement.symbolId, statement.name)} of ${this.expression(statement.iterable, contextName)}) {`, ...this.inlineBlock(statement.body, contextName, async, indent + 1), `${prefix}}`];
669
+ case 'IfStatement': return this.inlineIf(statement, contextName, async, indent);
670
+ }
671
+ }
672
+ inlineIf(statement, contextName, async, indent) {
673
+ const prefix = '\t'.repeat(indent);
674
+ const lines = [`${prefix}if (${this.expression(statement.condition, contextName)}) {`, ...this.inlineBlock(statement.thenBlock, contextName, async, indent + 1)];
675
+ if (statement.elseBranch === undefined)
676
+ return [...lines, `${prefix}}`];
677
+ if (statement.elseBranch.kind === 'BlockStatement')
678
+ return [...lines, `${prefix}} else {`, ...this.inlineBlock(statement.elseBranch, contextName, async, indent + 1), `${prefix}}`];
679
+ return [...lines, `${prefix}} else {`, ...this.inlineIf(statement.elseBranch, contextName, async, indent + 1), `${prefix}}`];
680
+ }
681
+ literal(expression) {
682
+ if (expression.literalKind === 'String')
683
+ return this.interpolatedString(expression.value);
684
+ if (expression.literalKind === 'BigInt')
685
+ return `${String(expression.value)}n`;
686
+ return String(expression.value);
687
+ }
688
+ interpolatedString(value) {
689
+ const placeholder = /(?<!\{)\{([A-Za-z_][A-Za-z0-9_]*(?:\.[A-Za-z_][A-Za-z0-9_]*)*)\}(?!\})/gu;
690
+ if (!placeholder.test(value))
691
+ return javascriptStringLiteral(value.replaceAll('{{', '{').replaceAll('}}', '}'));
692
+ placeholder.lastIndex = 0;
693
+ let result = '`';
694
+ let cursor = 0;
695
+ for (const match of value.matchAll(placeholder)) {
696
+ result += escapeTemplate(value.slice(cursor, match.index).replaceAll('{{', '{').replaceAll('}}', '}'));
697
+ result += `\${${match[1]}}`;
698
+ cursor = (match.index ?? 0) + match[0].length;
699
+ }
700
+ result += `${escapeTemplate(value.slice(cursor).replaceAll('{{', '{').replaceAll('}}', '}'))}\``;
701
+ return result;
702
+ }
703
+ identifier(expression) {
704
+ if (expression.symbolId !== undefined) {
705
+ const repetitionRead = this.#repetitionReads.get(expression.symbolId);
706
+ if (repetitionRead !== undefined)
707
+ return repetitionRead;
708
+ const componentRead = this.#componentParameterReads.get(expression.symbolId);
709
+ if (componentRead !== undefined)
710
+ return componentRead;
711
+ }
712
+ if (expression.name === 'Unit')
713
+ return 'undefined';
714
+ if (expression.name === 'expect')
715
+ return '$viruneExpect';
716
+ if (['Some', 'None', 'Ok', 'Err', 'panic'].includes(expression.name))
717
+ return expression.name;
718
+ return this.nameOf(expression.symbolId, expression.name);
719
+ }
720
+ call(expression, contextName) {
721
+ const foreignInvocation = expression.foreignCall === true || expression.foreignConstruct === true;
722
+ const args = expression.arguments.map((argument, index) => {
723
+ const raw = this.expression(argument, contextName);
724
+ const type = argument.inferredTypeId === undefined ? undefined : this.#semantic.arena.get(argument.inferredTypeId);
725
+ const guarded = foreignInvocation && type?.kind === 'primitive' && type.name === 'Unknown'
726
+ ? `$viruneEncodeSafeFfiValue(${raw}, ${this.safeFfiBoundary(`{ kind: 'unknown' }`)})`
727
+ : raw;
728
+ const projection = this.#semantic.interop.callableProjections?.find(item => item.callNodeId === expression.id && item.argumentIndex === index);
729
+ return projection === undefined ? guarded : this.callableProjection(guarded, projection.descriptor);
730
+ });
731
+ if (expression.callee.kind === 'FieldExpression' && expression.callee.target.kind === 'IdentifierExpression' && expression.callee.target.name === 'Json') {
732
+ if (expression.callee.field === 'parse')
733
+ return `parseJson(${args[0] ?? '""'})`;
734
+ if (expression.callee.field === 'decode') {
735
+ const result = expression.inferredTypeId === undefined ? undefined : this.#semantic.arena.get(expression.inferredTypeId);
736
+ const target = result?.kind === 'result' ? result.value : this.#semantic.arena.unknown;
737
+ return `decodeJsonValue(${args[0] ?? 'undefined'}, ${this.typeDescriptorFromTypeId(target)})`;
738
+ }
739
+ if (expression.callee.field === 'encode') {
740
+ const target = expression.arguments[0]?.inferredTypeId ?? this.#semantic.arena.unknown;
741
+ return `encodeJsonValue(${args[0] ?? 'undefined'}, ${this.typeDescriptorFromTypeId(target)})`;
742
+ }
743
+ }
744
+ const callee = this.expression(expression.callee, contextName);
745
+ if (expression.foreignConstruct === true)
746
+ return `new (${callee})(${args.join(', ')})`;
747
+ if (expression.foreignCall !== true && this.acceptsTaskContext(expression.callee))
748
+ args.push(contextName);
749
+ if (callee === '$viruneExpect')
750
+ return `(${args[0] ?? 'false'} ? undefined : panic('Expectation failed'))`;
751
+ return `${callee}(${args.join(', ')})`;
752
+ }
753
+ frontendViewCallbackProjection(callable, evidence) {
754
+ const rawParameters = Array.from({ length: evidence.parameterCount }, (_, index) => `$raw${index}`);
755
+ const invocation = `$fn(${[...rawParameters, 'rootTaskContext()'].join(', ')})`;
756
+ const wrapper = `(${rawParameters.join(', ')}) => { try { return ${invocation}; } catch ($error) { throw $viruneExternalizeInteropError($error); } }`;
757
+ const descriptorKey = JSON.stringify({ version: 'virune-frontend-view-callback/v1', parameterCount: evidence.parameterCount, effects: evidence.effects });
758
+ return `$viruneProjectCallable(${callable}, ${javascriptStringLiteral(descriptorKey)}, $fn => (${wrapper}))`;
759
+ }
760
+ callableProjection(callable, descriptor) {
761
+ const rawParameters = descriptor.parameters.map((_, index) => `$raw${index}`);
762
+ let body;
763
+ if (descriptor.version === 'virune-callable-shim/v1') {
764
+ const validated = descriptor.parameters.map((parameter, index) => `validateFfiValue(${rawParameters[index]}, ${this.callableFfiDescriptor(parameter)}, ${javascriptStringLiteral(`$[${index}]`)})`);
765
+ const invocation = `$fn(${[...validated, 'rootTaskContext()'].join(', ')})`;
766
+ const result = descriptor.async ? `await ${invocation}` : invocation;
767
+ body = `return encodeFfiValue(${result}, ${this.callableFfiDescriptor(descriptor.result)});`;
768
+ }
769
+ else if (descriptor.version === 'virune-callable-shim/v3') {
770
+ const validated = descriptor.parameters.map((parameter, index) => `validateFfiValue(${rawParameters[index]}, ${this.callableFfiDescriptor(parameter)}, ${javascriptStringLiteral(`$[${index}]`)})`);
771
+ const invocation = `$fn(${[...validated, 'rootTaskContext()'].join(', ')})`;
772
+ body = `const $result = ${invocation}; return ${this.callableProjection('$result', descriptor.result)};`;
773
+ }
774
+ else {
775
+ const invocation = `$fn(${[...rawParameters, 'rootTaskContext()'].join(', ')})`;
776
+ const result = descriptor.async ? `await ${invocation}` : invocation;
777
+ body = descriptor.result === 'Never'
778
+ ? `${result}; throw new Error("Virune Never callback returned unexpectedly");`
779
+ : `return ${result};`;
780
+ }
781
+ const wrapper = `${descriptor.async ? 'async ' : ''}(${rawParameters.join(', ')}) => { try { ${body} } catch ($error) { throw $viruneExternalizeInteropError($error); } }`;
782
+ const descriptorKey = JSON.stringify(descriptor);
783
+ return `$viruneProjectCallable(${callable}, ${javascriptStringLiteral(descriptorKey)}, $fn => (${wrapper}))`;
784
+ }
785
+ callableFfiDescriptor(primitive) {
786
+ switch (primitive) {
787
+ case 'Bool': return `{ kind: 'bool' }`;
788
+ case 'Int': return `{ kind: 'int' }`;
789
+ case 'Float': return `{ kind: 'float' }`;
790
+ case 'BigInt': return `{ kind: 'bigint' }`;
791
+ case 'String': return `{ kind: 'string' }`;
792
+ case 'Unit': return `{ kind: 'undefined' }`;
793
+ }
794
+ }
795
+ field(expression, contextName) {
796
+ if (expression.target.kind === 'IdentifierExpression') {
797
+ const key = `${expression.target.name}.${expression.field}`;
798
+ const mapped = {
799
+ 'Console.print': 'console.log', 'Int.toFloat': 'Number', 'Float.toInt': 'floatToInt',
800
+ 'Duration.milliseconds': 'durationMilliseconds', 'Duration.seconds': 'durationSeconds', 'Duration.minutes': 'durationMinutes', 'Duration.hours': 'durationHours', 'Duration.toMilliseconds': 'durationToMilliseconds',
801
+ 'List.length': 'listLength', 'List.isEmpty': 'listIsEmpty', 'List.isNotEmpty': 'listIsNotEmpty', 'List.first': 'listFirst', 'List.last': 'listLast', 'List.get': 'listGet',
802
+ 'List.append': 'listAppend', 'List.prepend': 'listPrepend', 'List.concat': 'listConcat', 'List.take': 'listTake', 'List.drop': 'listDrop', 'List.reverse': 'listReverse',
803
+ 'List.map': 'listMap', 'List.flatMap': 'listFlatMap', 'List.filter': 'listFilter', 'List.find': 'listFind', 'List.any': 'listAny', 'List.all': 'listAll', 'List.fold': 'listFold', 'List.zip': 'listZip', 'List.enumerate': 'listEnumerate', 'List.unique': 'listUnique', 'List.uniqueBy': 'listUniqueBy',
804
+ 'Map.empty': 'mapEmpty', 'Map.get': 'mapGet', 'Map.set': 'mapSet', 'Map.has': 'mapHas', 'Map.remove': 'mapRemove', 'Map.size': 'mapSize', 'Map.keys': 'mapKeys', 'Map.values': 'mapValues', 'Map.entries': 'mapEntries', 'Map.merge': 'mapMerge', 'Map.mapValues': 'mapMapValues',
805
+ 'Set.empty': 'setEmpty', 'Set.from': 'setFrom', 'Set.add': 'setAdd', 'Set.has': 'setHas', 'Set.remove': 'setRemove', 'Set.size': 'setSize', 'Set.toList': 'setToList', 'Set.union': 'setUnion', 'Set.intersection': 'setIntersection', 'Set.difference': 'setDifference',
806
+ 'Queue.empty': 'queueEmpty', 'Queue.enqueue': 'queueEnqueue', 'Queue.dequeue': 'queueDequeue', 'Stack.empty': 'stackEmpty', 'Stack.push': 'stackPush', 'Stack.pop': 'stackPop',
807
+ 'String.codePoints': 'stringCodePoints', 'String.graphemes': 'stringGraphemes', 'String.graphemeLength': 'stringGraphemeLength', 'String.normalizeNfc': 'stringNormalizeNfc', 'String.normalizeNfd': 'stringNormalizeNfd', 'String.normalizeNfkc': 'stringNormalizeNfkc', 'String.normalizeNfkd': 'stringNormalizeNfkd', 'String.length': 'stringLength', 'String.trim': 'stringTrim', 'String.trimStart': 'stringTrimStart', 'String.trimEnd': 'stringTrimEnd', 'String.contains': 'stringContains', 'String.startsWith': 'stringStartsWith', 'String.endsWith': 'stringEndsWith', 'String.toLowerCase': 'stringToLowerCase', 'String.toUpperCase': 'stringToUpperCase', 'String.split': 'stringSplit', 'String.slice': 'stringSlice', 'String.join': 'stringJoin', 'String.replace': 'stringReplace', 'String.at': 'stringAt', 'String.isEmpty': 'stringIsEmpty', 'String.isNotEmpty': 'stringIsNotEmpty',
808
+ 'ByteOrder.BigEndian': JSON.stringify('BigEndian'), 'ByteOrder.LittleEndian': JSON.stringify('LittleEndian'), 'HttpBody.Empty': "makeVariant('Empty', [], 'std:HttpBody')", 'HttpBody.Text': "(value => makeVariant('Text', [value], 'std:HttpBody'))", 'HttpBody.Bytes': "(value => makeVariant('Bytes', [value], 'std:HttpBody'))", 'Bytes.empty': 'bytesEmpty', 'Bytes.length': 'bytesLength', 'Bytes.fromUtf8': 'bytesFromUtf8', 'Bytes.toUtf8': 'bytesToUtf8', 'Bytes.fromHex': 'bytesFromHex', 'Bytes.toHex': 'bytesToHex', 'Bytes.fromBase64': 'bytesFromBase64', 'Bytes.toBase64': 'bytesToBase64', 'Bytes.concat': 'bytesConcat', 'Bytes.slice': 'bytesSlice', 'Bytes.get': 'bytesGet', 'Bytes.set': 'bytesSet', 'Bytes.readInt32': 'bytesReadInt32', 'Bytes.writeInt32': 'bytesWriteInt32', 'MutableBytes.create': 'mutableBytesCreate', 'MutableBytes.fromBytes': 'mutableBytesFromBytes', 'MutableBytes.toBytes': 'mutableBytesToBytes', 'MutableBytes.length': 'mutableBytesLength', 'MutableBytes.get': 'mutableBytesGet', 'MutableBytes.set': 'mutableBytesSet', 'MutableBytes.fill': 'mutableBytesFill', 'Byte.fromInt': 'byteCreate', 'Int8.fromInt': 'int8Create', 'Int8.toInt': 'int8ToInt', 'UInt8.fromInt': 'uint8Create', 'UInt8.toInt': 'uint8ToInt', 'Int16.fromInt': 'int16Create', 'Int16.toInt': 'int16ToInt', 'UInt16.fromInt': 'uint16Create', 'UInt16.toInt': 'uint16ToInt', 'Int32.fromInt': 'int32Create', 'Int32.toInt': 'int32ToInt', 'UInt32.fromInt': 'uint32Create', 'UInt32.toInt': 'uint32ToInt', 'Int64.fromBigInt': 'int64Create', 'Int64.toBigInt': 'int64ToBigInt', 'UInt64.fromBigInt': 'uint64Create', 'UInt64.toBigInt': 'uint64ToBigInt',
809
+ 'Debug.format': 'debugValue',
810
+ 'Option.map': 'optionMap', 'Option.andThen': 'optionAndThen', 'Option.filter': 'optionFilter', 'Option.unwrapOr': 'optionUnwrapOr', 'Option.toResult': 'optionToResult', 'Option.collect': 'optionCollect',
811
+ 'Result.map': 'resultMap', 'Result.mapError': 'resultMapError', 'Result.andThen': 'resultAndThen', 'Result.orElse': 'resultOrElse', 'Result.unwrapOr': 'resultUnwrapOr', 'Result.toOption': 'resultToOption', 'Result.collect': 'resultCollect', 'Result.collectErrors': 'resultCollectErrors',
812
+ 'Validation.valid': 'valid', 'Validation.invalid': 'invalid', 'Validation.map': 'validationMap', 'Validation.andThen': 'validationAndThen', 'Validation.collect': 'validationCollect',
813
+ 'Task.sleep': 'taskSleepBuiltin', 'Task.timeout': 'taskTimeoutBuiltin', 'Task.race': 'taskRaceBuiltin', 'Task.firstOk': 'taskFirstOkBuiltin', 'Task.retry': 'taskRetryBuiltin', 'Task.mapParallel': 'taskMapParallelBuiltin', 'Task.supervise': 'taskSuperviseBuiltin',
814
+ 'Stream.fromList': 'streamFromList', 'Stream.map': 'streamMap', 'Stream.filter': 'streamFilter', 'Stream.collect': 'streamCollect', 'Stream.take': 'streamTake',
815
+ 'File.readText': '$fileReadText', 'File.writeText': '$fileWriteText', 'File.readBytes': '$fileReadBytes', 'File.writeBytes': '$fileWriteBytes', 'File.open': '$fileOpen', 'File.read': '$fileRead', 'File.readHandleBytes': '$fileReadHandleBytes', 'File.write': '$fileWrite', 'File.writeHandleBytes': '$fileWriteHandleBytes', 'File.close': '$fileClose',
816
+ 'Path.join': '$pathJoin', 'Path.resolve': '$pathResolve', 'Path.dirname': '$pathDirname', 'Path.basename': '$pathBasename', 'Path.extname': '$pathExtname', 'Path.normalize': '$pathNormalize', 'Path.relative': '$pathRelative', 'Path.isAbsolute': '$pathIsAbsolute',
817
+ 'Process.args': '$processArgs', 'Process.cwd': '$processCwd', 'Process.exitCode': '$processExitCode', 'Process.environment': '$processEnvironment', 'Process.platform': '$processPlatform', 'Process.architecture': '$processArchitecture',
818
+ 'Http.get': '$httpGet', 'Http.request': '$httpRequest', 'Fetch.get': '$fetchGet', 'Fetch.request': '$fetchRequest', 'Timer.sleep': '$timerSleep', 'Timer.now': '$timerNow',
819
+ 'Storage.get': '$storageGet', 'Storage.set': '$storageSet', 'Storage.remove': '$storageRemove', 'Storage.clear': '$storageClear',
820
+ 'Dom.getText': '$domGetText', 'Dom.setText': '$domSetText', 'Dom.setAttribute': '$domSetAttribute', 'Dom.addClass': '$domAddClass',
821
+ 'Crypto.randomUuid': '$cryptoRandomUuid', 'Url.encodeComponent': '$urlEncodeComponent', 'Url.decodeComponent': '$urlDecodeComponent', 'Url.isValid': '$urlIsValid', 'Json.parse': 'parseJson',
822
+ };
823
+ if (mapped[key] !== undefined)
824
+ return mapped[key];
825
+ const symbol = expression.target.symbolId === undefined ? undefined : this.#semantic.symbols.get(expression.target.symbolId);
826
+ const type = symbol === undefined ? undefined : this.#semantic.arena.get(symbol.typeId);
827
+ if (symbol?.kind === 'type' && type?.kind === 'named' && type.declarationKind === 'newtype' && expression.field === 'create')
828
+ return '(value => value)';
829
+ }
830
+ return `${this.expression(expression.target, contextName)}.${safeName(expression.field)}`;
831
+ }
832
+ binary(expression, contextName) {
833
+ const left = this.expression(expression.left, contextName);
834
+ const right = this.expression(expression.right, contextName);
835
+ if (['==', '!='].includes(expression.operator))
836
+ return expression.operator === '==' ? `viruneEquals(${left}, ${right})` : `!viruneEquals(${left}, ${right})`;
837
+ if (expression.inferredTypeId === this.#semantic.arena.int) {
838
+ const helpers = { '+': 'intAdd', '-': 'intSubtract', '*': 'intMultiply', '/': 'intDivide', '%': 'intRemainder' };
839
+ if (helpers[expression.operator] !== undefined)
840
+ return `${helpers[expression.operator]}(${left}, ${right})`;
841
+ }
842
+ return `(${left} ${expression.operator} ${right})`;
843
+ }
844
+ match(expression, contextName) {
845
+ const temp = `$match${this.#temporary++}`;
846
+ const lines = [`(() => {`, `\tconst ${temp} = ${this.expression(expression.target, contextName)};`];
847
+ for (const arm of expression.arms) {
848
+ const pattern = this.pattern(arm.pattern, temp);
849
+ lines.push(`\tif (${pattern.condition}) {`);
850
+ for (const binding of pattern.bindings)
851
+ lines.push(`\t\t${binding}`);
852
+ if (arm.guard !== undefined)
853
+ lines.push(`\t\tif (${this.expression(arm.guard, contextName)}) return ${this.expression(arm.expression, contextName)};`);
854
+ else
855
+ lines.push(`\t\treturn ${this.expression(arm.expression, contextName)};`);
856
+ lines.push('\t}');
857
+ }
858
+ lines.push(`\treturn panic('Non-exhaustive match reached at runtime');`, '})()');
859
+ return lines.join('\n');
860
+ }
861
+ pattern(pattern, target) {
862
+ switch (pattern.kind) {
863
+ case 'WildcardPattern': return { condition: 'true', bindings: [] };
864
+ case 'BindingPattern': return { condition: 'true', bindings: [`const ${this.nameOf(pattern.symbolId, pattern.name)} = ${target};`] };
865
+ case 'LiteralPattern': return { condition: `viruneEquals(${target}, ${JSON.stringify(pattern.value)})`, bindings: [] };
866
+ case 'VariantPattern': {
867
+ const conditions = [`${target}.$tag === ${JSON.stringify(pattern.name)}`];
868
+ const bindings = [];
869
+ pattern.values.forEach((value, index) => { const child = this.pattern(value, `${target}.$values[${index}]`); conditions.push(child.condition); bindings.push(...child.bindings); });
870
+ return { condition: conditions.join(' && '), bindings };
871
+ }
872
+ case 'RecordPattern': {
873
+ const conditions = [];
874
+ const bindings = [];
875
+ for (const field of pattern.fields) {
876
+ const child = this.pattern(field.pattern, `${target}.${safeName(field.name)}`);
877
+ conditions.push(child.condition);
878
+ bindings.push(...child.bindings);
879
+ }
880
+ return { condition: conditions.length === 0 ? 'true' : conditions.join(' && '), bindings };
881
+ }
882
+ case 'RangePattern': return { condition: `Number.isSafeInteger(${target}) && ${target} >= ${pattern.start} && ${target} <= ${pattern.end}`, bindings: [] };
883
+ case 'ListPattern': {
884
+ const conditions = [`Array.isArray(${target})`, `${target}.length ${pattern.rest === undefined ? '===' : '>='} ${pattern.items.length}`];
885
+ const bindings = [];
886
+ pattern.items.forEach((item, index) => { const child = this.pattern(item, `${target}[${index}]`); conditions.push(child.condition); bindings.push(...child.bindings); });
887
+ if (pattern.rest?.kind === 'BindingPattern')
888
+ bindings.push(`const ${this.nameOf(pattern.rest.symbolId, pattern.rest.name)} = ${target}.slice(${pattern.items.length});`);
889
+ return { condition: conditions.join(' && '), bindings };
890
+ }
891
+ case 'TuplePattern': {
892
+ const conditions = [`Array.isArray(${target})`, `${target}.length === ${pattern.items.length}`];
893
+ const bindings = [];
894
+ pattern.items.forEach((item, index) => { const child = this.pattern(item, `${target}[${index}]`); conditions.push(child.condition); bindings.push(...child.bindings); });
895
+ return { condition: conditions.join(' && '), bindings };
896
+ }
897
+ case 'OrPattern': {
898
+ const alternatives = pattern.alternatives.map(item => this.pattern(item, target));
899
+ return { condition: alternatives.map(item => `(${item.condition})`).join(' || '), bindings: alternatives.flatMap(item => item.bindings) };
900
+ }
901
+ }
902
+ }
903
+ parallelExpression(expression, contextName) {
904
+ const helper = expression.tryMode ? 'parallelTry' : 'parallel';
905
+ const entries = expression.entries.map(entry => `${safeName(entry.name)}: async ($child) => ${this.expression(entry.value, '$child')}`).join(', ');
906
+ return `${helper}(${contextName}, { ${entries} })`;
907
+ }
908
+ acceptsTaskContext(callee) {
909
+ if (callee.inferredTypeId !== undefined) {
910
+ const inferred = this.#semantic.arena.get(callee.inferredTypeId);
911
+ if (inferred.kind === 'function' && inferred.async)
912
+ return true;
913
+ }
914
+ if (callee.kind === 'LambdaExpression')
915
+ return callee.async;
916
+ if (callee.kind !== 'IdentifierExpression' || callee.symbolId === undefined)
917
+ return false;
918
+ const symbol = this.#semantic.symbols.get(callee.symbolId);
919
+ if (symbol === undefined || symbol.typeOnly)
920
+ return false;
921
+ return symbol.kind === 'function' || symbol.kind === 'import';
922
+ }
923
+ typeDescriptorFromTypeId(typeId, seen = new Set()) {
924
+ if (seen.has(typeId))
925
+ return `{ kind: 'unknown' }`;
926
+ seen.add(typeId);
927
+ const type = this.#semantic.arena.get(typeId);
928
+ switch (type.kind) {
929
+ case 'primitive':
930
+ switch (type.name) {
931
+ case 'String': return `{ kind: 'string' }`;
932
+ case 'Bool': return `{ kind: 'bool' }`;
933
+ case 'Int': return `{ kind: 'int' }`;
934
+ case 'Float': return `{ kind: 'float' }`;
935
+ case 'BigInt': return `{ kind: 'bigint' }`;
936
+ case 'Unit': return `{ kind: 'unit' }`;
937
+ default: return `{ kind: 'unknown' }`;
938
+ }
939
+ case 'list': return `{ kind: 'list', item: ${this.typeDescriptorFromTypeId(type.element, new Set(seen))} }`;
940
+ case 'tuple': return `{ kind: 'tuple', items: [${type.items.map(item => this.typeDescriptorFromTypeId(item, new Set(seen))).join(', ')}] }`;
941
+ case 'map': return `{ kind: 'map', key: ${this.typeDescriptorFromTypeId(type.key, new Set(seen))}, value: ${this.typeDescriptorFromTypeId(type.value, new Set(seen))} }`;
942
+ case 'set': return `{ kind: 'set', item: ${this.typeDescriptorFromTypeId(type.element, new Set(seen))} }`;
943
+ case 'option': return `{ kind: 'option', value: ${this.typeDescriptorFromTypeId(type.value, new Set(seen))} }`;
944
+ case 'result': return `{ kind: 'result', value: ${this.typeDescriptorFromTypeId(type.value, new Set(seen))}, error: ${this.typeDescriptorFromTypeId(type.error, new Set(seen))} }`;
945
+ case 'named': {
946
+ if (type.definitionId === 'std:Bytes')
947
+ return `{ kind: 'bytes' }`;
948
+ if ((type.declarationKind === 'newtype' || type.declarationKind === 'alias') && type.underlying !== undefined)
949
+ return this.typeDescriptorFromTypeId(type.underlying, new Set(seen));
950
+ if (type.declarationKind === 'record' && type.fields !== undefined) {
951
+ const declaration = this.#moduleDeclarations.find(item => item.kind === 'RecordDeclaration' && item.name === type.name);
952
+ const strict = declaration?.attributes.some(attribute => attribute.name === 'json' && attribute.arguments.some(argument => argument.kind === 'IdentifierExpression' && argument.name === 'strict')) === true;
953
+ return `{ kind: 'record', name: ${javascriptStringLiteral(type.name)}, typeId: ${javascriptStringLiteral(type.definitionId)}, fields: { ${[...type.fields].map(([name, field]) => `[${javascriptStringLiteral(name)}]: ${this.recordFieldDescriptor(name, this.typeDescriptorFromTypeId(field, new Set(seen)), declaration)}`).join(', ')} }${strict ? ', strict: true' : ''} }`;
954
+ }
955
+ if (type.declarationKind === 'enum' && type.variants !== undefined)
956
+ return `{ kind: 'enum', name: ${javascriptStringLiteral(type.name)}, typeId: ${javascriptStringLiteral(type.definitionId)}, variants: { ${[...type.variants].map(([name, values]) => `[${javascriptStringLiteral(name)}]: [${values.map(value => this.typeDescriptorFromTypeId(value, new Set(seen))).join(', ')}]`).join(', ')} } }`;
957
+ return `{ kind: 'unknown' }`;
958
+ }
959
+ default: return `{ kind: 'unknown' }`;
960
+ }
961
+ }
962
+ recordFieldDescriptor(name, typeDescriptor, declaration) {
963
+ const field = declaration?.fields.find(item => item.name === name);
964
+ const jsonName = field?.attributes.find(attribute => attribute.name === 'jsonName')?.arguments[0];
965
+ const jsonDefault = field?.attributes.find(attribute => attribute.name === 'jsonDefault')?.arguments[0];
966
+ const jsOptional = field?.attributes.some(attribute => attribute.name === 'jsOptional') === true;
967
+ if (jsonName === undefined && jsonDefault === undefined && !jsOptional)
968
+ return typeDescriptor;
969
+ const properties = [`type: ${typeDescriptor}`];
970
+ if (jsonName?.kind === 'LiteralExpression' && jsonName.literalKind === 'String')
971
+ properties.push(`jsonName: ${javascriptStringLiteral(jsonName.value)}`);
972
+ if (jsonDefault !== undefined)
973
+ properties.push(`hasDefault: true`, `defaultValue: ${this.expression(jsonDefault)}`);
974
+ if (jsOptional)
975
+ properties.push(`missingAsNone: true`, `omitWhenNone: true`);
976
+ return `{ ${properties.join(', ')} }`;
977
+ }
978
+ typeDescriptor(type) {
979
+ if (type === undefined)
980
+ return `{ kind: 'unknown' }`;
981
+ if (type.resolvedTypeId !== undefined)
982
+ return this.typeDescriptorFromTypeId(type.resolvedTypeId);
983
+ const base = (() => {
984
+ switch (type.name) {
985
+ case 'String': return `{ kind: 'string' }`;
986
+ case 'Bool': return `{ kind: 'bool' }`;
987
+ case 'Int': return `{ kind: 'int' }`;
988
+ case 'Float': return `{ kind: 'float' }`;
989
+ case 'BigInt': return `{ kind: 'bigint' }`;
990
+ case 'Unit': return `{ kind: 'unit' }`;
991
+ case 'Unknown': return `{ kind: 'unknown' }`;
992
+ case 'Bytes': return `{ kind: 'bytes' }`;
993
+ case 'List': return `{ kind: 'list', item: ${this.typeDescriptor(type.arguments[0])} }`;
994
+ case 'Map': return `{ kind: 'map', key: ${this.typeDescriptor(type.arguments[0])}, value: ${this.typeDescriptor(type.arguments[1])} }`;
995
+ case 'Set': return `{ kind: 'set', item: ${this.typeDescriptor(type.arguments[0])} }`;
996
+ case 'Option': return `{ kind: 'option', value: ${this.typeDescriptor(type.arguments[0])} }`;
997
+ case 'Result': return `{ kind: 'result', value: ${this.typeDescriptor(type.arguments[0])}, error: ${this.typeDescriptor(type.arguments[1])} }`;
998
+ }
999
+ const declaration = this.#moduleDeclarations.find(item => 'name' in item && item.name === type.name);
1000
+ if (declaration?.kind === 'RecordDeclaration')
1001
+ return `{ kind: 'record', name: ${javascriptStringLiteral(type.name)}, typeId: ${javascriptStringLiteral(this.declarationTypeId(declaration.symbolId, declaration.definitionId ?? `${this.#source.id}#${declaration.name}`))}, fields: { ${declaration.fields.map(field => `[${javascriptStringLiteral(field.name)}]: ${this.recordFieldDescriptor(field.name, this.typeDescriptor(field.type), declaration)}`).join(', ')} } }`;
1002
+ if (declaration?.kind === 'EnumDeclaration')
1003
+ return `{ kind: 'enum', name: ${javascriptStringLiteral(type.name)}, typeId: ${javascriptStringLiteral(this.declarationTypeId(declaration.symbolId, declaration.definitionId ?? `${this.#source.id}#${declaration.name}`))}, variants: { ${declaration.variants.map(variant => `[${javascriptStringLiteral(variant.name)}]: [${variant.values.map(value => this.typeDescriptor(value)).join(', ')}]`).join(', ')} } }`;
1004
+ if (declaration?.kind === 'NewtypeDeclaration')
1005
+ return this.typeDescriptor(declaration.underlying);
1006
+ if (declaration?.kind === 'TypeAliasDeclaration')
1007
+ return this.typeDescriptor(declaration.target);
1008
+ return `{ kind: 'unknown' }`;
1009
+ })();
1010
+ return type.optional ? `{ kind: 'option', value: ${base} }` : base;
1011
+ }
1012
+ safeFfiBoundary(typeDescriptor) {
1013
+ return `{ version: 'virune-safe-ffi/v1', type: ${typeDescriptor} }`;
1014
+ }
1015
+ declarationTypeId(symbolId, fallback) {
1016
+ if (symbolId === undefined)
1017
+ return fallback;
1018
+ const symbol = this.#semantic.symbols.get(symbolId);
1019
+ const type = symbol === undefined ? undefined : this.#semantic.arena.get(symbol.typeId);
1020
+ return type?.kind === 'named' ? type.definitionId : fallback;
1021
+ }
1022
+ nameOf(symbolId, fallback) { return symbolId === undefined ? safeName(fallback) : this.#symbolNames.get(symbolId) ?? safeName(fallback); }
1023
+ moduleExported(declaration) { return declaration.public || declaration.internal === true; }
1024
+ exportPrefix(attributes) { return attributes.some(item => item.name === 'jsExport') ? 'export ' : ''; }
1025
+ }
1026
+ export function emitJavaScript(hir, source, outputFile, options = {}) {
1027
+ return new JavaScriptEmitter(hir, source, outputFile, options).emit(hir.module);
1028
+ }
1029
+ //# sourceMappingURL=emitter.js.map