@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.
- package/LICENSE +201 -0
- package/NOTICE +2 -0
- package/dist/src/analysis/control-flow.d.ts +9 -0
- package/dist/src/analysis/control-flow.d.ts.map +1 -0
- package/dist/src/analysis/control-flow.js +47 -0
- package/dist/src/analysis/control-flow.js.map +1 -0
- package/dist/src/ast/nodes.d.ts +495 -0
- package/dist/src/ast/nodes.d.ts.map +1 -0
- package/dist/src/ast/nodes.js +2 -0
- package/dist/src/ast/nodes.js.map +1 -0
- package/dist/src/binder/symbols.d.ts +36 -0
- package/dist/src/binder/symbols.d.ts.map +1 -0
- package/dist/src/binder/symbols.js +30 -0
- package/dist/src/binder/symbols.js.map +1 -0
- package/dist/src/checker/builtin-members.d.ts +4 -0
- package/dist/src/checker/builtin-members.d.ts.map +1 -0
- package/dist/src/checker/builtin-members.js +175 -0
- package/dist/src/checker/builtin-members.js.map +1 -0
- package/dist/src/checker/checker.d.ts +148 -0
- package/dist/src/checker/checker.d.ts.map +1 -0
- package/dist/src/checker/checker.js +2985 -0
- package/dist/src/checker/checker.js.map +1 -0
- package/dist/src/checker/effect-registry.d.ts +14 -0
- package/dist/src/checker/effect-registry.d.ts.map +1 -0
- package/dist/src/checker/effect-registry.js +11 -0
- package/dist/src/checker/effect-registry.js.map +1 -0
- package/dist/src/checker/type-operations.d.ts +27 -0
- package/dist/src/checker/type-operations.d.ts.map +1 -0
- package/dist/src/checker/type-operations.js +268 -0
- package/dist/src/checker/type-operations.js.map +1 -0
- package/dist/src/codegen/emitter.d.ts +83 -0
- package/dist/src/codegen/emitter.d.ts.map +1 -0
- package/dist/src/codegen/emitter.js +1029 -0
- package/dist/src/codegen/emitter.js.map +1 -0
- package/dist/src/codegen/helpers.d.ts +7 -0
- package/dist/src/codegen/helpers.d.ts.map +1 -0
- package/dist/src/codegen/helpers.js +48 -0
- package/dist/src/codegen/helpers.js.map +1 -0
- package/dist/src/codegen/runtime-imports.d.ts +6 -0
- package/dist/src/codegen/runtime-imports.d.ts.map +1 -0
- package/dist/src/codegen/runtime-imports.js +45 -0
- package/dist/src/codegen/runtime-imports.js.map +1 -0
- package/dist/src/codegen/writer.d.ts +14 -0
- package/dist/src/codegen/writer.d.ts.map +1 -0
- package/dist/src/codegen/writer.js +56 -0
- package/dist/src/codegen/writer.js.map +1 -0
- package/dist/src/compiler.d.ts +24 -0
- package/dist/src/compiler.d.ts.map +1 -0
- package/dist/src/compiler.js +97 -0
- package/dist/src/compiler.js.map +1 -0
- package/dist/src/diagnostics/codes.d.ts +18 -0
- package/dist/src/diagnostics/codes.d.ts.map +1 -0
- package/dist/src/diagnostics/codes.js +43 -0
- package/dist/src/diagnostics/codes.js.map +1 -0
- package/dist/src/diagnostics/diagnostic.d.ts +41 -0
- package/dist/src/diagnostics/diagnostic.d.ts.map +1 -0
- package/dist/src/diagnostics/diagnostic.js +50 -0
- package/dist/src/diagnostics/diagnostic.js.map +1 -0
- package/dist/src/diagnostics/render.d.ts +44 -0
- package/dist/src/diagnostics/render.d.ts.map +1 -0
- package/dist/src/diagnostics/render.js +44 -0
- package/dist/src/diagnostics/render.js.map +1 -0
- package/dist/src/experimental-api.d.ts +19 -0
- package/dist/src/experimental-api.d.ts.map +1 -0
- package/dist/src/experimental-api.js +17 -0
- package/dist/src/experimental-api.js.map +1 -0
- package/dist/src/hir/lower.d.ts +6 -0
- package/dist/src/hir/lower.d.ts.map +1 -0
- package/dist/src/hir/lower.js +124 -0
- package/dist/src/hir/lower.js.map +1 -0
- package/dist/src/hir/nodes.d.ts +12 -0
- package/dist/src/hir/nodes.d.ts.map +1 -0
- package/dist/src/hir/nodes.js +2 -0
- package/dist/src/hir/nodes.js.map +1 -0
- package/dist/src/index.d.ts +2 -0
- package/dist/src/index.d.ts.map +1 -0
- package/dist/src/index.js +2 -0
- package/dist/src/index.js.map +1 -0
- package/dist/src/interop/checked-api.d.ts +21 -0
- package/dist/src/interop/checked-api.d.ts.map +1 -0
- package/dist/src/interop/checked-api.js +46 -0
- package/dist/src/interop/checked-api.js.map +1 -0
- package/dist/src/interop/decision.d.ts +24 -0
- package/dist/src/interop/decision.d.ts.map +1 -0
- package/dist/src/interop/decision.js +96 -0
- package/dist/src/interop/decision.js.map +1 -0
- package/dist/src/interop/frontend-component-emission.d.ts +17 -0
- package/dist/src/interop/frontend-component-emission.d.ts.map +1 -0
- package/dist/src/interop/frontend-component-emission.js +271 -0
- package/dist/src/interop/frontend-component-emission.js.map +1 -0
- package/dist/src/interop/jsx-view-validation.d.ts +20 -0
- package/dist/src/interop/jsx-view-validation.d.ts.map +1 -0
- package/dist/src/interop/jsx-view-validation.js +847 -0
- package/dist/src/interop/jsx-view-validation.js.map +1 -0
- package/dist/src/interop/operation-api.d.ts +32 -0
- package/dist/src/interop/operation-api.d.ts.map +1 -0
- package/dist/src/interop/operation-api.js +61 -0
- package/dist/src/interop/operation-api.js.map +1 -0
- package/dist/src/interop/operation.d.ts +136 -0
- package/dist/src/interop/operation.d.ts.map +1 -0
- package/dist/src/interop/operation.js +575 -0
- package/dist/src/interop/operation.js.map +1 -0
- package/dist/src/interop/repetition-host-locator.d.ts +25 -0
- package/dist/src/interop/repetition-host-locator.d.ts.map +1 -0
- package/dist/src/interop/repetition-host-locator.js +61 -0
- package/dist/src/interop/repetition-host-locator.js.map +1 -0
- package/dist/src/interop/repetition-host-usage-validation.d.ts +11 -0
- package/dist/src/interop/repetition-host-usage-validation.d.ts.map +1 -0
- package/dist/src/interop/repetition-host-usage-validation.js +89 -0
- package/dist/src/interop/repetition-host-usage-validation.js.map +1 -0
- package/dist/src/interop/types.d.ts +365 -0
- package/dist/src/interop/types.d.ts.map +1 -0
- package/dist/src/interop/types.js +2 -0
- package/dist/src/interop/types.js.map +1 -0
- package/dist/src/mir/lower.d.ts +4 -0
- package/dist/src/mir/lower.d.ts.map +1 -0
- package/dist/src/mir/lower.js +131 -0
- package/dist/src/mir/lower.js.map +1 -0
- package/dist/src/mir/nodes.d.ts +36 -0
- package/dist/src/mir/nodes.d.ts.map +1 -0
- package/dist/src/mir/nodes.js +2 -0
- package/dist/src/mir/nodes.js.map +1 -0
- package/dist/src/project/incremental.d.ts +9 -0
- package/dist/src/project/incremental.d.ts.map +1 -0
- package/dist/src/project/incremental.js +15 -0
- package/dist/src/project/incremental.js.map +1 -0
- package/dist/src/project/project.d.ts +98 -0
- package/dist/src/project/project.d.ts.map +1 -0
- package/dist/src/project/project.js +884 -0
- package/dist/src/project/project.js.map +1 -0
- package/dist/src/public-api.d.ts +45 -0
- package/dist/src/public-api.d.ts.map +1 -0
- package/dist/src/public-api.js +37 -0
- package/dist/src/public-api.js.map +1 -0
- package/dist/src/reference/evaluator.d.ts +13 -0
- package/dist/src/reference/evaluator.d.ts.map +1 -0
- package/dist/src/reference/evaluator.js +304 -0
- package/dist/src/reference/evaluator.js.map +1 -0
- package/dist/src/selfhost/bootstrap-artifact-normalizer.d.ts +65 -0
- package/dist/src/selfhost/bootstrap-artifact-normalizer.d.ts.map +1 -0
- package/dist/src/selfhost/bootstrap-artifact-normalizer.js +226 -0
- package/dist/src/selfhost/bootstrap-artifact-normalizer.js.map +1 -0
- package/dist/src/selfhost/bootstrap-artifact-snapshot.d.ts +23 -0
- package/dist/src/selfhost/bootstrap-artifact-snapshot.d.ts.map +1 -0
- package/dist/src/selfhost/bootstrap-artifact-snapshot.js +122 -0
- package/dist/src/selfhost/bootstrap-artifact-snapshot.js.map +1 -0
- package/dist/src/selfhost/bootstrap-compiler-selection.d.ts +36 -0
- package/dist/src/selfhost/bootstrap-compiler-selection.d.ts.map +1 -0
- package/dist/src/selfhost/bootstrap-compiler-selection.js +52 -0
- package/dist/src/selfhost/bootstrap-compiler-selection.js.map +1 -0
- package/dist/src/selfhost/bootstrap-execution-probe.d.ts +42 -0
- package/dist/src/selfhost/bootstrap-execution-probe.d.ts.map +1 -0
- package/dist/src/selfhost/bootstrap-execution-probe.js +115 -0
- package/dist/src/selfhost/bootstrap-execution-probe.js.map +1 -0
- package/dist/src/selfhost/bootstrap-rollback-decision.d.ts +40 -0
- package/dist/src/selfhost/bootstrap-rollback-decision.d.ts.map +1 -0
- package/dist/src/selfhost/bootstrap-rollback-decision.js +144 -0
- package/dist/src/selfhost/bootstrap-rollback-decision.js.map +1 -0
- package/dist/src/selfhost/bootstrap-shadow-history.d.ts +49 -0
- package/dist/src/selfhost/bootstrap-shadow-history.d.ts.map +1 -0
- package/dist/src/selfhost/bootstrap-shadow-history.js +348 -0
- package/dist/src/selfhost/bootstrap-shadow-history.js.map +1 -0
- package/dist/src/selfhost/bootstrap-shadow-report.d.ts +43 -0
- package/dist/src/selfhost/bootstrap-shadow-report.d.ts.map +1 -0
- package/dist/src/selfhost/bootstrap-shadow-report.js +94 -0
- package/dist/src/selfhost/bootstrap-shadow-report.js.map +1 -0
- package/dist/src/selfhost/bootstrap-stage-executor.d.ts +53 -0
- package/dist/src/selfhost/bootstrap-stage-executor.d.ts.map +1 -0
- package/dist/src/selfhost/bootstrap-stage-executor.js +159 -0
- package/dist/src/selfhost/bootstrap-stage-executor.js.map +1 -0
- package/dist/src/selfhost/bootstrap-stage-loader.d.ts +14 -0
- package/dist/src/selfhost/bootstrap-stage-loader.d.ts.map +1 -0
- package/dist/src/selfhost/bootstrap-stage-loader.js +112 -0
- package/dist/src/selfhost/bootstrap-stage-loader.js.map +1 -0
- package/dist/src/selfhost/bootstrap-stage-pipeline.d.ts +8 -0
- package/dist/src/selfhost/bootstrap-stage-pipeline.d.ts.map +1 -0
- package/dist/src/selfhost/bootstrap-stage-pipeline.js +85 -0
- package/dist/src/selfhost/bootstrap-stage-pipeline.js.map +1 -0
- package/dist/src/selfhost/bootstrap-stage-runner.d.ts +47 -0
- package/dist/src/selfhost/bootstrap-stage-runner.d.ts.map +1 -0
- package/dist/src/selfhost/bootstrap-stage-runner.js +106 -0
- package/dist/src/selfhost/bootstrap-stage-runner.js.map +1 -0
- package/dist/src/selfhost/clean-bootstrap-evidence.d.ts +83 -0
- package/dist/src/selfhost/clean-bootstrap-evidence.d.ts.map +1 -0
- package/dist/src/selfhost/clean-bootstrap-evidence.js +233 -0
- package/dist/src/selfhost/clean-bootstrap-evidence.js.map +1 -0
- package/dist/src/selfhost/compiler-facade.d.ts +33 -0
- package/dist/src/selfhost/compiler-facade.d.ts.map +1 -0
- package/dist/src/selfhost/compiler-facade.js +65 -0
- package/dist/src/selfhost/compiler-facade.js.map +1 -0
- package/dist/src/selfhost/contract.d.ts +151 -0
- package/dist/src/selfhost/contract.d.ts.map +1 -0
- package/dist/src/selfhost/contract.js +335 -0
- package/dist/src/selfhost/contract.js.map +1 -0
- package/dist/src/selfhost/differential-artifacts.d.ts +8 -0
- package/dist/src/selfhost/differential-artifacts.d.ts.map +1 -0
- package/dist/src/selfhost/differential-artifacts.js +50 -0
- package/dist/src/selfhost/differential-artifacts.js.map +1 -0
- package/dist/src/selfhost/differential-harness.d.ts +91 -0
- package/dist/src/selfhost/differential-harness.d.ts.map +1 -0
- package/dist/src/selfhost/differential-harness.js +246 -0
- package/dist/src/selfhost/differential-harness.js.map +1 -0
- package/dist/src/selfhost/full-language-inventory-runner.d.ts +49 -0
- package/dist/src/selfhost/full-language-inventory-runner.d.ts.map +1 -0
- package/dist/src/selfhost/full-language-inventory-runner.js +225 -0
- package/dist/src/selfhost/full-language-inventory-runner.js.map +1 -0
- package/dist/src/selfhost/full-language-inventory.d.ts +42 -0
- package/dist/src/selfhost/full-language-inventory.d.ts.map +1 -0
- package/dist/src/selfhost/full-language-inventory.js +163 -0
- package/dist/src/selfhost/full-language-inventory.js.map +1 -0
- package/dist/src/selfhost/interop-resolution-witness.d.ts +35 -0
- package/dist/src/selfhost/interop-resolution-witness.d.ts.map +1 -0
- package/dist/src/selfhost/interop-resolution-witness.js +186 -0
- package/dist/src/selfhost/interop-resolution-witness.js.map +1 -0
- package/dist/src/selfhost/kernel-model-host-adapter.d.ts +28 -0
- package/dist/src/selfhost/kernel-model-host-adapter.d.ts.map +1 -0
- package/dist/src/selfhost/kernel-model-host-adapter.js +36 -0
- package/dist/src/selfhost/kernel-model-host-adapter.js.map +1 -0
- package/dist/src/selfhost/legacy-adapter.d.ts +8 -0
- package/dist/src/selfhost/legacy-adapter.d.ts.map +1 -0
- package/dist/src/selfhost/legacy-adapter.js +186 -0
- package/dist/src/selfhost/legacy-adapter.js.map +1 -0
- package/dist/src/selfhost/module-graph.d.ts +57 -0
- package/dist/src/selfhost/module-graph.d.ts.map +1 -0
- package/dist/src/selfhost/module-graph.js +267 -0
- package/dist/src/selfhost/module-graph.js.map +1 -0
- package/dist/src/selfhost/mvp-adapter.d.ts +19 -0
- package/dist/src/selfhost/mvp-adapter.d.ts.map +1 -0
- package/dist/src/selfhost/mvp-adapter.js +186 -0
- package/dist/src/selfhost/mvp-adapter.js.map +1 -0
- package/dist/src/selfhost/node-executor.d.ts +6 -0
- package/dist/src/selfhost/node-executor.d.ts.map +1 -0
- package/dist/src/selfhost/node-executor.js +138 -0
- package/dist/src/selfhost/node-executor.js.map +1 -0
- package/dist/src/selfhost/project-compiler-adapter.d.ts +77 -0
- package/dist/src/selfhost/project-compiler-adapter.d.ts.map +1 -0
- package/dist/src/selfhost/project-compiler-adapter.js +358 -0
- package/dist/src/selfhost/project-compiler-adapter.js.map +1 -0
- package/dist/src/selfhost/project-differential-adapter.d.ts +15 -0
- package/dist/src/selfhost/project-differential-adapter.d.ts.map +1 -0
- package/dist/src/selfhost/project-differential-adapter.js +65 -0
- package/dist/src/selfhost/project-differential-adapter.js.map +1 -0
- package/dist/src/selfhost/promotion-evidence.d.ts +62 -0
- package/dist/src/selfhost/promotion-evidence.d.ts.map +1 -0
- package/dist/src/selfhost/promotion-evidence.js +332 -0
- package/dist/src/selfhost/promotion-evidence.js.map +1 -0
- package/dist/src/selfhost/promotion-policy-replay-v2.d.ts +54 -0
- package/dist/src/selfhost/promotion-policy-replay-v2.d.ts.map +1 -0
- package/dist/src/selfhost/promotion-policy-replay-v2.js +343 -0
- package/dist/src/selfhost/promotion-policy-replay-v2.js.map +1 -0
- package/dist/src/selfhost/promotion-shadow-history-v2.d.ts +55 -0
- package/dist/src/selfhost/promotion-shadow-history-v2.d.ts.map +1 -0
- package/dist/src/selfhost/promotion-shadow-history-v2.js +227 -0
- package/dist/src/selfhost/promotion-shadow-history-v2.js.map +1 -0
- package/dist/src/selfhost/promotion-subject.d.ts +34 -0
- package/dist/src/selfhost/promotion-subject.d.ts.map +1 -0
- package/dist/src/selfhost/promotion-subject.js +141 -0
- package/dist/src/selfhost/promotion-subject.js.map +1 -0
- package/dist/src/selfhost/source-manifest.d.ts +31 -0
- package/dist/src/selfhost/source-manifest.d.ts.map +1 -0
- package/dist/src/selfhost/source-manifest.js +194 -0
- package/dist/src/selfhost/source-manifest.js.map +1 -0
- package/dist/src/selfhost/stage-compiler-facade.d.ts +25 -0
- package/dist/src/selfhost/stage-compiler-facade.d.ts.map +1 -0
- package/dist/src/selfhost/stage-compiler-facade.js +100 -0
- package/dist/src/selfhost/stage-compiler-facade.js.map +1 -0
- package/dist/src/semantic-evidence/snapshot.d.ts +114 -0
- package/dist/src/semantic-evidence/snapshot.d.ts.map +1 -0
- package/dist/src/semantic-evidence/snapshot.js +336 -0
- package/dist/src/semantic-evidence/snapshot.js.map +1 -0
- package/dist/src/source.d.ts +25 -0
- package/dist/src/source.d.ts.map +1 -0
- package/dist/src/source.js +10 -0
- package/dist/src/source.js.map +1 -0
- package/dist/src/syntax/cst-to-ast.d.ts +108 -0
- package/dist/src/syntax/cst-to-ast.d.ts.map +1 -0
- package/dist/src/syntax/cst-to-ast.js +383 -0
- package/dist/src/syntax/cst-to-ast.js.map +1 -0
- package/dist/src/syntax/documentation.d.ts +11 -0
- package/dist/src/syntax/documentation.d.ts.map +1 -0
- package/dist/src/syntax/documentation.js +261 -0
- package/dist/src/syntax/documentation.js.map +1 -0
- package/dist/src/syntax/parser.d.ts +114 -0
- package/dist/src/syntax/parser.d.ts.map +1 -0
- package/dist/src/syntax/parser.js +970 -0
- package/dist/src/syntax/parser.js.map +1 -0
- package/dist/src/syntax/syntax-metadata.d.ts +8 -0
- package/dist/src/syntax/syntax-metadata.d.ts.map +1 -0
- package/dist/src/syntax/syntax-metadata.js +14 -0
- package/dist/src/syntax/syntax-metadata.js.map +1 -0
- package/dist/src/syntax/tokens.d.ts +96 -0
- package/dist/src/syntax/tokens.d.ts.map +1 -0
- package/dist/src/syntax/tokens.js +260 -0
- package/dist/src/syntax/tokens.js.map +1 -0
- package/dist/src/types/types.d.ts +97 -0
- package/dist/src/types/types.d.ts.map +1 -0
- package/dist/src/types/types.js +105 -0
- package/dist/src/types/types.js.map +1 -0
- package/package.json +47 -0
- 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
|