@justscale/typescript 0.1.1
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 +21 -0
- package/README.md +128 -0
- package/dist/api.d.ts +144 -0
- package/dist/api.d.ts.map +1 -0
- package/dist/api.js +380 -0
- package/dist/api.js.map +1 -0
- package/dist/compiler/analyzer.d.ts +247 -0
- package/dist/compiler/analyzer.d.ts.map +1 -0
- package/dist/compiler/analyzer.js +3201 -0
- package/dist/compiler/analyzer.js.map +1 -0
- package/dist/compiler/cli.d.ts +12 -0
- package/dist/compiler/cli.d.ts.map +1 -0
- package/dist/compiler/cli.js +209 -0
- package/dist/compiler/cli.js.map +1 -0
- package/dist/compiler/compile.d.ts +26 -0
- package/dist/compiler/compile.d.ts.map +1 -0
- package/dist/compiler/compile.js +121 -0
- package/dist/compiler/compile.js.map +1 -0
- package/dist/compiler/errors.d.ts +336 -0
- package/dist/compiler/errors.d.ts.map +1 -0
- package/dist/compiler/errors.js +466 -0
- package/dist/compiler/errors.js.map +1 -0
- package/dist/compiler/exports-prepass.d.ts +31 -0
- package/dist/compiler/exports-prepass.d.ts.map +1 -0
- package/dist/compiler/exports-prepass.js +249 -0
- package/dist/compiler/exports-prepass.js.map +1 -0
- package/dist/compiler/hmr-change-detector.d.ts +47 -0
- package/dist/compiler/hmr-change-detector.d.ts.map +1 -0
- package/dist/compiler/hmr-change-detector.js +395 -0
- package/dist/compiler/hmr-change-detector.js.map +1 -0
- package/dist/compiler/hmr-transformer.d.ts +54 -0
- package/dist/compiler/hmr-transformer.d.ts.map +1 -0
- package/dist/compiler/hmr-transformer.js +535 -0
- package/dist/compiler/hmr-transformer.js.map +1 -0
- package/dist/compiler/index.d.ts +19 -0
- package/dist/compiler/index.d.ts.map +1 -0
- package/dist/compiler/index.js +16 -0
- package/dist/compiler/index.js.map +1 -0
- package/dist/compiler/primitive-detector.d.ts +70 -0
- package/dist/compiler/primitive-detector.d.ts.map +1 -0
- package/dist/compiler/primitive-detector.js +338 -0
- package/dist/compiler/primitive-detector.js.map +1 -0
- package/dist/compiler/ptsc.d.ts +40 -0
- package/dist/compiler/ptsc.d.ts.map +1 -0
- package/dist/compiler/ptsc.js +462 -0
- package/dist/compiler/ptsc.js.map +1 -0
- package/dist/compiler/rewriter.d.ts +96 -0
- package/dist/compiler/rewriter.d.ts.map +1 -0
- package/dist/compiler/rewriter.js +418 -0
- package/dist/compiler/rewriter.js.map +1 -0
- package/dist/compiler/step-hash.d.ts +43 -0
- package/dist/compiler/step-hash.d.ts.map +1 -0
- package/dist/compiler/step-hash.js +83 -0
- package/dist/compiler/step-hash.js.map +1 -0
- package/dist/compiler/switch-codegen.d.ts +84 -0
- package/dist/compiler/switch-codegen.d.ts.map +1 -0
- package/dist/compiler/switch-codegen.js +1540 -0
- package/dist/compiler/switch-codegen.js.map +1 -0
- package/dist/compiler/transformer.d.ts +29 -0
- package/dist/compiler/transformer.d.ts.map +1 -0
- package/dist/compiler/transformer.js +216 -0
- package/dist/compiler/transformer.js.map +1 -0
- package/dist/config/index.d.ts +122 -0
- package/dist/config/index.d.ts.map +1 -0
- package/dist/config/index.js +215 -0
- package/dist/config/index.js.map +1 -0
- package/dist/di-errors/formatter.d.ts +126 -0
- package/dist/di-errors/formatter.d.ts.map +1 -0
- package/dist/di-errors/formatter.js +384 -0
- package/dist/di-errors/formatter.js.map +1 -0
- package/dist/di-errors/index.d.ts +5 -0
- package/dist/di-errors/index.d.ts.map +1 -0
- package/dist/di-errors/index.js +13 -0
- package/dist/di-errors/index.js.map +1 -0
- package/dist/editor.d.ts +11 -0
- package/dist/editor.d.ts.map +1 -0
- package/dist/editor.js +2 -0
- package/dist/editor.js.map +1 -0
- package/dist/index.d.ts +35 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +40 -0
- package/dist/index.js.map +1 -0
- package/dist/language-service/index.d.ts +52 -0
- package/dist/language-service/index.d.ts.map +1 -0
- package/dist/language-service/index.js +366 -0
- package/dist/language-service/index.js.map +1 -0
- package/dist/language-service/process-quick-fixes.d.ts +20 -0
- package/dist/language-service/process-quick-fixes.d.ts.map +1 -0
- package/dist/language-service/process-quick-fixes.js +114 -0
- package/dist/language-service/process-quick-fixes.js.map +1 -0
- package/dist/language-service/quick-fix-discovery.d.ts +39 -0
- package/dist/language-service/quick-fix-discovery.d.ts.map +1 -0
- package/dist/language-service/quick-fix-discovery.js +124 -0
- package/dist/language-service/quick-fix-discovery.js.map +1 -0
- package/dist/loader/incremental.d.ts +50 -0
- package/dist/loader/incremental.d.ts.map +1 -0
- package/dist/loader/incremental.js +151 -0
- package/dist/loader/incremental.js.map +1 -0
- package/dist/loader/index.d.ts +25 -0
- package/dist/loader/index.d.ts.map +1 -0
- package/dist/loader/index.js +24 -0
- package/dist/loader/index.js.map +1 -0
- package/dist/loader/loader.d.ts +52 -0
- package/dist/loader/loader.d.ts.map +1 -0
- package/dist/loader/loader.js +248 -0
- package/dist/loader/loader.js.map +1 -0
- package/dist/loader/register.d.ts +14 -0
- package/dist/loader/register.d.ts.map +1 -0
- package/dist/loader/register.js +20 -0
- package/dist/loader/register.js.map +1 -0
- package/dist/plugins/index.d.ts +13 -0
- package/dist/plugins/index.d.ts.map +1 -0
- package/dist/plugins/index.js +13 -0
- package/dist/plugins/index.js.map +1 -0
- package/dist/plugins/index.public.d.ts +13 -0
- package/dist/plugins/index.public.d.ts.map +1 -0
- package/dist/plugins/index.public.js +13 -0
- package/dist/plugins/index.public.js.map +1 -0
- package/dist/plugins/types.d.ts +83 -0
- package/dist/plugins/types.d.ts.map +1 -0
- package/dist/plugins/types.js +24 -0
- package/dist/plugins/types.js.map +1 -0
- package/dist/server/index.d.ts +33 -0
- package/dist/server/index.d.ts.map +1 -0
- package/dist/server/index.js +42 -0
- package/dist/server/index.js.map +1 -0
- package/dist/server/tsserver.d.ts +28 -0
- package/dist/server/tsserver.d.ts.map +1 -0
- package/dist/server/tsserver.js +126 -0
- package/dist/server/tsserver.js.map +1 -0
- package/lib/lib.d.ts +20 -0
- package/lib/lib.decorators.d.ts +382 -0
- package/lib/lib.decorators.legacy.d.ts +20 -0
- package/lib/lib.dom.asynciterable.d.ts +18 -0
- package/lib/lib.dom.d.ts +45125 -0
- package/lib/lib.dom.iterable.d.ts +18 -0
- package/lib/lib.es2015.collection.d.ts +150 -0
- package/lib/lib.es2015.core.d.ts +595 -0
- package/lib/lib.es2015.d.ts +26 -0
- package/lib/lib.es2015.generator.d.ts +75 -0
- package/lib/lib.es2015.iterable.d.ts +603 -0
- package/lib/lib.es2015.promise.d.ts +79 -0
- package/lib/lib.es2015.proxy.d.ts +126 -0
- package/lib/lib.es2015.reflect.d.ts +142 -0
- package/lib/lib.es2015.symbol.d.ts +44 -0
- package/lib/lib.es2015.symbol.wellknown.d.ts +324 -0
- package/lib/lib.es2016.array.include.d.ts +114 -0
- package/lib/lib.es2016.d.ts +19 -0
- package/lib/lib.es2016.full.d.ts +21 -0
- package/lib/lib.es2016.intl.d.ts +29 -0
- package/lib/lib.es2017.arraybuffer.d.ts +19 -0
- package/lib/lib.es2017.d.ts +24 -0
- package/lib/lib.es2017.date.d.ts +29 -0
- package/lib/lib.es2017.full.d.ts +21 -0
- package/lib/lib.es2017.intl.d.ts +42 -0
- package/lib/lib.es2017.object.d.ts +47 -0
- package/lib/lib.es2017.sharedmemory.d.ts +133 -0
- package/lib/lib.es2017.string.d.ts +43 -0
- package/lib/lib.es2017.typedarrays.d.ts +51 -0
- package/lib/lib.es2018.asyncgenerator.d.ts +75 -0
- package/lib/lib.es2018.asynciterable.d.ts +51 -0
- package/lib/lib.es2018.d.ts +22 -0
- package/lib/lib.es2018.full.d.ts +22 -0
- package/lib/lib.es2018.intl.d.ts +81 -0
- package/lib/lib.es2018.promise.d.ts +28 -0
- package/lib/lib.es2018.regexp.d.ts +35 -0
- package/lib/lib.es2019.array.d.ts +77 -0
- package/lib/lib.es2019.d.ts +22 -0
- package/lib/lib.es2019.full.d.ts +22 -0
- package/lib/lib.es2019.intl.d.ts +21 -0
- package/lib/lib.es2019.object.d.ts +31 -0
- package/lib/lib.es2019.string.d.ts +35 -0
- package/lib/lib.es2019.symbol.d.ts +22 -0
- package/lib/lib.es2020.bigint.d.ts +763 -0
- package/lib/lib.es2020.d.ts +25 -0
- package/lib/lib.es2020.date.d.ts +40 -0
- package/lib/lib.es2020.full.d.ts +22 -0
- package/lib/lib.es2020.intl.d.ts +472 -0
- package/lib/lib.es2020.number.d.ts +26 -0
- package/lib/lib.es2020.promise.d.ts +45 -0
- package/lib/lib.es2020.sharedmemory.d.ts +97 -0
- package/lib/lib.es2020.string.d.ts +42 -0
- package/lib/lib.es2020.symbol.wellknown.d.ts +39 -0
- package/lib/lib.es2021.d.ts +21 -0
- package/lib/lib.es2021.full.d.ts +22 -0
- package/lib/lib.es2021.intl.d.ts +164 -0
- package/lib/lib.es2021.promise.d.ts +46 -0
- package/lib/lib.es2021.string.d.ts +31 -0
- package/lib/lib.es2021.weakref.d.ts +76 -0
- package/lib/lib.es2022.array.d.ts +119 -0
- package/lib/lib.es2022.d.ts +23 -0
- package/lib/lib.es2022.error.d.ts +73 -0
- package/lib/lib.es2022.full.d.ts +22 -0
- package/lib/lib.es2022.intl.d.ts +143 -0
- package/lib/lib.es2022.object.d.ts +24 -0
- package/lib/lib.es2022.regexp.d.ts +37 -0
- package/lib/lib.es2022.string.d.ts +23 -0
- package/lib/lib.es2023.array.d.ts +922 -0
- package/lib/lib.es2023.collection.d.ts +19 -0
- package/lib/lib.es2023.d.ts +20 -0
- package/lib/lib.es2023.full.d.ts +22 -0
- package/lib/lib.es2023.intl.d.ts +62 -0
- package/lib/lib.es2024.arraybuffer.d.ts +63 -0
- package/lib/lib.es2024.collection.d.ts +27 -0
- package/lib/lib.es2024.d.ts +24 -0
- package/lib/lib.es2024.full.d.ts +22 -0
- package/lib/lib.es2024.object.d.ts +27 -0
- package/lib/lib.es2024.promise.d.ts +33 -0
- package/lib/lib.es2024.regexp.d.ts +23 -0
- package/lib/lib.es2024.sharedmemory.d.ts +66 -0
- package/lib/lib.es2024.string.d.ts +27 -0
- package/lib/lib.es2025.collection.d.ts +94 -0
- package/lib/lib.es2025.d.ts +23 -0
- package/lib/lib.es2025.float16.d.ts +443 -0
- package/lib/lib.es2025.full.d.ts +22 -0
- package/lib/lib.es2025.intl.d.ts +200 -0
- package/lib/lib.es2025.iterator.d.ts +146 -0
- package/lib/lib.es2025.promise.d.ts +32 -0
- package/lib/lib.es2025.regexp.d.ts +30 -0
- package/lib/lib.es5.d.ts +4599 -0
- package/lib/lib.es6.d.ts +21 -0
- package/lib/lib.esnext.array.d.ts +33 -0
- package/lib/lib.esnext.collection.d.ts +47 -0
- package/lib/lib.esnext.d.ts +27 -0
- package/lib/lib.esnext.date.d.ts +21 -0
- package/lib/lib.esnext.decorators.d.ts +26 -0
- package/lib/lib.esnext.disposable.d.ts +191 -0
- package/lib/lib.esnext.error.d.ts +22 -0
- package/lib/lib.esnext.full.d.ts +22 -0
- package/lib/lib.esnext.intl.d.ts +107 -0
- package/lib/lib.esnext.sharedmemory.d.ts +23 -0
- package/lib/lib.esnext.temporal.d.ts +485 -0
- package/lib/lib.esnext.typedarrays.d.ts +90 -0
- package/lib/lib.scripthost.d.ts +320 -0
- package/lib/lib.webworker.asynciterable.d.ts +18 -0
- package/lib/lib.webworker.d.ts +15606 -0
- package/lib/lib.webworker.importscripts.d.ts +21 -0
- package/lib/lib.webworker.iterable.d.ts +18 -0
- package/lib/logger.js +144 -0
- package/lib/package.json +7 -0
- package/lib/tsserver.js +57 -0
- package/lib/tsserverlibrary.js +171 -0
- package/lib/typesMap.json +497 -0
- package/lib/typescript.js +373 -0
- package/package.json +115 -0
|
@@ -0,0 +1,1540 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @justscale/core/process - Switch-Based Code Generator
|
|
3
|
+
*
|
|
4
|
+
* Generates VM-style switch-based execution code from process analysis.
|
|
5
|
+
*
|
|
6
|
+
* The generated code follows this pattern:
|
|
7
|
+
* ```
|
|
8
|
+
* async execute(ctx) {
|
|
9
|
+
* const { state, services } = ctx
|
|
10
|
+
* let step = state.step | 0
|
|
11
|
+
* const __r = [0, undefined] // [DONE/SUSPEND, payload]
|
|
12
|
+
*
|
|
13
|
+
* main_loop: while (true) {
|
|
14
|
+
* switch (step) {
|
|
15
|
+
* case 0: { ... }
|
|
16
|
+
* case 1: { ... }
|
|
17
|
+
* }
|
|
18
|
+
* }
|
|
19
|
+
* return __r
|
|
20
|
+
* }
|
|
21
|
+
* ```
|
|
22
|
+
*/
|
|
23
|
+
import ts from 'typescript';
|
|
24
|
+
import { rewriteStatement, rewriteExpression, extractParamVars, extractParamAliases, extractServiceVars, extractDeclaredVars, cloneExpression, } from './rewriter.js';
|
|
25
|
+
import { computeStepHash } from './step-hash.js';
|
|
26
|
+
/**
|
|
27
|
+
* Get the using variables that need rehydration for a step executing the
|
|
28
|
+
* opcode range `[start, end)`.
|
|
29
|
+
*
|
|
30
|
+
* On resume, any `using` var whose initializer runs in this step (via a REHYDRATE
|
|
31
|
+
* opcode) needs to be re-run. Three passes:
|
|
32
|
+
*
|
|
33
|
+
* 1. Vars whose REHYDRATE opcode lives in `[start, end)` - they're local to this step.
|
|
34
|
+
* 2. Vars referenced in BLOCK opcodes within `[start, end)` whose `usedInBlocks` overlaps.
|
|
35
|
+
* 3. Transitive deps: if var A's initializer references using-var B, B must
|
|
36
|
+
* also be in the prelude. We resolve transitivity here so the caller can
|
|
37
|
+
* topologically sort them in the emitter.
|
|
38
|
+
*
|
|
39
|
+
* Scanning is bounded to `[start, end)` to avoid leaking vars from sibling race
|
|
40
|
+
* branches or later steps - replaying branch A must never run branch B's
|
|
41
|
+
* `await rooms.lock(...)` just because B's opcodes live after A's in the flat
|
|
42
|
+
* opcode list.
|
|
43
|
+
*/
|
|
44
|
+
function getRehydrateDepsForStep(analysis, start, end) {
|
|
45
|
+
const { opcodes, rehydrationBlocks } = analysis;
|
|
46
|
+
const usingVarNames = Object.keys(rehydrationBlocks);
|
|
47
|
+
if (usingVarNames.length === 0)
|
|
48
|
+
return undefined;
|
|
49
|
+
const usingVarSet = new Set(usingVarNames);
|
|
50
|
+
const needed = new Set();
|
|
51
|
+
// REHYDRATE opcodes within [start, end): vars initialized here must be re-run on resume.
|
|
52
|
+
for (let i = start; i < end; i++) {
|
|
53
|
+
const op = opcodes[i];
|
|
54
|
+
if (op.op === 'REHYDRATE') {
|
|
55
|
+
needed.add(op.var);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
// BLOCK opcodes within [start, end): vars referenced (but declared earlier) also need rehydration.
|
|
59
|
+
const usedBlockIds = new Set();
|
|
60
|
+
for (let i = start; i < end; i++) {
|
|
61
|
+
const op = opcodes[i];
|
|
62
|
+
if (op.op === 'BLOCK' && op.blockId !== undefined) {
|
|
63
|
+
usedBlockIds.add(op.blockId);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
for (const varName of usingVarNames) {
|
|
67
|
+
const varInfo = analysis.variables.get(varName);
|
|
68
|
+
if (varInfo && varInfo.usedInBlocks.some(blockId => usedBlockIds.has(blockId))) {
|
|
69
|
+
needed.add(varName);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
// Transitive deps: if A's initializer references using-var B, B must also be rehydrated.
|
|
73
|
+
// Per-opcode REHYDRATE expressions take priority over the global rehydrationBlocks map.
|
|
74
|
+
const opcodeExprByVar = new Map();
|
|
75
|
+
for (let i = start; i < end; i++) {
|
|
76
|
+
const op = opcodes[i];
|
|
77
|
+
if (op.op === 'REHYDRATE') {
|
|
78
|
+
opcodeExprByVar.set(op.var, op.expression);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
let changed = true;
|
|
82
|
+
while (changed) {
|
|
83
|
+
changed = false;
|
|
84
|
+
for (const varName of Array.from(needed)) {
|
|
85
|
+
// Get the initializer expression for this var
|
|
86
|
+
const expr = opcodeExprByVar.get(varName) ?? rehydrationBlocks[varName]?.expression;
|
|
87
|
+
if (!expr)
|
|
88
|
+
continue;
|
|
89
|
+
// Walk the expression to find referenced using-vars
|
|
90
|
+
const visit = (node) => {
|
|
91
|
+
if (ts.isIdentifier(node) && usingVarSet.has(node.text) && !needed.has(node.text)) {
|
|
92
|
+
needed.add(node.text);
|
|
93
|
+
changed = true;
|
|
94
|
+
}
|
|
95
|
+
ts.forEachChild(node, visit);
|
|
96
|
+
};
|
|
97
|
+
visit(expr);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
return needed.size > 0 ? Array.from(needed) : undefined;
|
|
101
|
+
}
|
|
102
|
+
/**
|
|
103
|
+
* Build steps from opcodes.
|
|
104
|
+
* Steps are created for:
|
|
105
|
+
* 1. Entry point (step 0)
|
|
106
|
+
* 2. After each suspension point (WAIT, RACE_SUSPEND)
|
|
107
|
+
* 3. Jump targets (after JUMP_IF, LABEL targets)
|
|
108
|
+
* 4. Race branch handlers
|
|
109
|
+
*/
|
|
110
|
+
export function buildSteps(analysis) {
|
|
111
|
+
const { opcodes, opcodeSourceNodes, raceBranchSourceNodes } = analysis;
|
|
112
|
+
const steps = [];
|
|
113
|
+
const stepMap = {};
|
|
114
|
+
const sourceMap = {};
|
|
115
|
+
// First pass: identify step boundaries
|
|
116
|
+
const stepBoundaries = new Set();
|
|
117
|
+
stepBoundaries.add(0); // Entry point
|
|
118
|
+
for (let i = 0; i < opcodes.length; i++) {
|
|
119
|
+
const op = opcodes[i];
|
|
120
|
+
// After WAIT: next opcode is a resume point
|
|
121
|
+
if (op.op === 'WAIT' && i + 1 < opcodes.length) {
|
|
122
|
+
stepBoundaries.add(i + 1);
|
|
123
|
+
}
|
|
124
|
+
// After RACE_SUSPEND: each branch jumpTarget is a resume point
|
|
125
|
+
if (op.op === 'RACE_SUSPEND') {
|
|
126
|
+
// Find the preceding RACE_START
|
|
127
|
+
for (let j = i - 1; j >= 0; j--) {
|
|
128
|
+
const prevOp = opcodes[j];
|
|
129
|
+
if (prevOp.op === 'RACE_START') {
|
|
130
|
+
for (const branch of prevOp.branches) {
|
|
131
|
+
stepBoundaries.add(branch.jumpTarget);
|
|
132
|
+
}
|
|
133
|
+
break;
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
// JUMP targets
|
|
138
|
+
if (op.op === 'JUMP') {
|
|
139
|
+
stepBoundaries.add(op.target);
|
|
140
|
+
}
|
|
141
|
+
// JUMP_IF targets (both true and false paths)
|
|
142
|
+
if (op.op === 'JUMP_IF') {
|
|
143
|
+
stepBoundaries.add(op.target);
|
|
144
|
+
if (i + 1 < opcodes.length) {
|
|
145
|
+
stepBoundaries.add(i + 1); // Fall-through path
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
// LABEL is a jump target
|
|
149
|
+
if (op.op === 'LABEL') {
|
|
150
|
+
stepBoundaries.add(i);
|
|
151
|
+
}
|
|
152
|
+
// ITER_NEXT doneTarget is a jump target
|
|
153
|
+
if (op.op === 'ITER_NEXT') {
|
|
154
|
+
stepBoundaries.add(op.doneTarget);
|
|
155
|
+
}
|
|
156
|
+
// After PARALLEL_WAIT: next opcode (PARALLEL_COLLECT) is a resume point
|
|
157
|
+
if (op.op === 'PARALLEL_WAIT' && i + 1 < opcodes.length) {
|
|
158
|
+
stepBoundaries.add(i + 1);
|
|
159
|
+
}
|
|
160
|
+
// After SCOPE_WAIT or SCOPE_HANDLER: next opcode is a resume point
|
|
161
|
+
if ((op.op === 'SCOPE_WAIT' || op.op === 'SCOPE_HANDLER') && i + 1 < opcodes.length) {
|
|
162
|
+
stepBoundaries.add(i + 1);
|
|
163
|
+
}
|
|
164
|
+
// SUBPROCESS_SPAWN: next opcode is a resume point (subprocess may suspend)
|
|
165
|
+
if (op.op === 'SUBPROCESS_SPAWN' && i + 1 < opcodes.length) {
|
|
166
|
+
stepBoundaries.add(i + 1);
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
// Sort boundaries
|
|
170
|
+
const sortedBoundaries = Array.from(stepBoundaries).sort((a, b) => a - b);
|
|
171
|
+
// Second pass: create steps
|
|
172
|
+
for (let i = 0; i < sortedBoundaries.length; i++) {
|
|
173
|
+
const start = sortedBoundaries[i];
|
|
174
|
+
const end = i + 1 < sortedBoundaries.length ? sortedBoundaries[i + 1] : opcodes.length;
|
|
175
|
+
// Skip empty ranges
|
|
176
|
+
if (start >= opcodes.length)
|
|
177
|
+
continue;
|
|
178
|
+
// Determine step type and branch info
|
|
179
|
+
let type = 'block';
|
|
180
|
+
let branchInfo;
|
|
181
|
+
if (start === 0) {
|
|
182
|
+
type = 'entry';
|
|
183
|
+
}
|
|
184
|
+
else {
|
|
185
|
+
// Check if this is a resume after WAIT or PARALLEL_WAIT
|
|
186
|
+
const prevOp = start > 0 ? opcodes[start - 1] : null;
|
|
187
|
+
if (prevOp?.op === 'WAIT' || prevOp?.op === 'PARALLEL_WAIT') {
|
|
188
|
+
type = 'resume';
|
|
189
|
+
}
|
|
190
|
+
// Check if this is a race branch target
|
|
191
|
+
for (let j = 0; j < start; j++) {
|
|
192
|
+
const op = opcodes[j];
|
|
193
|
+
if (op.op === 'RACE_START') {
|
|
194
|
+
for (const branch of op.branches) {
|
|
195
|
+
if (branch.jumpTarget === start) {
|
|
196
|
+
type = 'branch';
|
|
197
|
+
branchInfo = { raceOpcodeIndex: j, branchId: branch.id };
|
|
198
|
+
break;
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
// Get rehydrate deps for resume points (both WAIT resume and race branches).
|
|
205
|
+
// Only consider blocks inside THIS step's opcode range - pulling deps from
|
|
206
|
+
// later steps or sibling race branches leaks cross-branch `using` vars.
|
|
207
|
+
let rehydrateDeps;
|
|
208
|
+
if (start > 0 && (type === 'resume' || type === 'branch')) {
|
|
209
|
+
rehydrateDeps = getRehydrateDepsForStep(analysis, start, end);
|
|
210
|
+
}
|
|
211
|
+
// Compute source range
|
|
212
|
+
let sourceRange;
|
|
213
|
+
// For branch steps, use the race branch source node (the case clause)
|
|
214
|
+
if (type === 'branch' && branchInfo) {
|
|
215
|
+
const branchNodes = raceBranchSourceNodes.get(branchInfo.raceOpcodeIndex);
|
|
216
|
+
const branchNode = branchNodes?.get(branchInfo.branchId);
|
|
217
|
+
if (branchNode) {
|
|
218
|
+
const sf = branchNode.getSourceFile();
|
|
219
|
+
if (sf) {
|
|
220
|
+
const startLine = sf.getLineAndCharacterOfPosition(branchNode.getStart()).line + 1;
|
|
221
|
+
const endLine = sf.getLineAndCharacterOfPosition(branchNode.getEnd()).line + 1;
|
|
222
|
+
sourceRange = [startLine, endLine];
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
// For other steps or as fallback, use opcodes in the range
|
|
227
|
+
if (!sourceRange) {
|
|
228
|
+
for (let j = start; j < end; j++) {
|
|
229
|
+
const sourceNode = opcodeSourceNodes[j];
|
|
230
|
+
if (sourceNode) {
|
|
231
|
+
const sf = sourceNode.getSourceFile();
|
|
232
|
+
if (sf) {
|
|
233
|
+
const startLine = sf.getLineAndCharacterOfPosition(sourceNode.getStart()).line + 1;
|
|
234
|
+
const endLine = sf.getLineAndCharacterOfPosition(sourceNode.getEnd()).line + 1;
|
|
235
|
+
if (!sourceRange) {
|
|
236
|
+
sourceRange = [startLine, endLine];
|
|
237
|
+
}
|
|
238
|
+
else {
|
|
239
|
+
sourceRange[0] = Math.min(sourceRange[0], startLine);
|
|
240
|
+
sourceRange[1] = Math.max(sourceRange[1], endLine);
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
// Compute hash
|
|
247
|
+
const hashInput = {
|
|
248
|
+
type,
|
|
249
|
+
opcodeRange: { start, end },
|
|
250
|
+
index: steps.length,
|
|
251
|
+
};
|
|
252
|
+
const hash = computeStepHash(hashInput, analysis, start);
|
|
253
|
+
const step = {
|
|
254
|
+
index: steps.length,
|
|
255
|
+
hash,
|
|
256
|
+
type,
|
|
257
|
+
sourceRange,
|
|
258
|
+
opcodeRange: { start, end },
|
|
259
|
+
rehydrateDeps,
|
|
260
|
+
branchInfo,
|
|
261
|
+
};
|
|
262
|
+
// Determine next step
|
|
263
|
+
if (end < opcodes.length && !stepBoundaries.has(end)) {
|
|
264
|
+
// Find the step that contains the next opcode
|
|
265
|
+
const nextBoundary = sortedBoundaries.find(b => b > end);
|
|
266
|
+
if (nextBoundary !== undefined) {
|
|
267
|
+
// Will be patched after all steps are created
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
stepMap[hash] = step.index;
|
|
271
|
+
if (sourceRange) {
|
|
272
|
+
sourceMap[step.index] = sourceRange;
|
|
273
|
+
}
|
|
274
|
+
steps.push(step);
|
|
275
|
+
}
|
|
276
|
+
// Third pass: patch nextStep references
|
|
277
|
+
// First, compute each race branch's opcode range so we can determine
|
|
278
|
+
// which steps belong to which branch body.
|
|
279
|
+
// Map: raceOpcodeIndex -> array of { branchId, startOpcode, endOpcode }
|
|
280
|
+
const raceBranchRanges = new Map();
|
|
281
|
+
for (let i = 0; i < opcodes.length; i++) {
|
|
282
|
+
const op = opcodes[i];
|
|
283
|
+
if (op.op === 'RACE_START') {
|
|
284
|
+
const ranges = [];
|
|
285
|
+
for (let b = 0; b < op.branches.length; b++) {
|
|
286
|
+
const branch = op.branches[b];
|
|
287
|
+
const start = branch.jumpTarget;
|
|
288
|
+
// End is the next branch's start, or the end of all opcodes
|
|
289
|
+
const end = b + 1 < op.branches.length
|
|
290
|
+
? op.branches[b + 1].jumpTarget
|
|
291
|
+
: opcodes.length;
|
|
292
|
+
ranges.push({ branchId: branch.id, start, end });
|
|
293
|
+
}
|
|
294
|
+
raceBranchRanges.set(i, ranges);
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
// Find the continuation step for each race (first step after all branch bodies)
|
|
298
|
+
const raceContinuationSteps = new Map();
|
|
299
|
+
for (const [raceIdx, ranges] of raceBranchRanges) {
|
|
300
|
+
const lastRange = ranges[ranges.length - 1];
|
|
301
|
+
// Find the first step whose opcodes start at or after the last branch's end
|
|
302
|
+
const continuationStep = steps.find(s => s.opcodeRange.start >= lastRange.end);
|
|
303
|
+
if (continuationStep) {
|
|
304
|
+
raceContinuationSteps.set(raceIdx, continuationStep.index);
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
for (let i = 0; i < steps.length; i++) {
|
|
308
|
+
const step = steps[i];
|
|
309
|
+
const endOpcodeIdx = step.opcodeRange.end - 1;
|
|
310
|
+
if (endOpcodeIdx >= 0 && endOpcodeIdx < opcodes.length) {
|
|
311
|
+
const lastOp = opcodes[endOpcodeIdx];
|
|
312
|
+
// If last opcode is JUMP, set nextStep to the target step
|
|
313
|
+
if (lastOp.op === 'JUMP') {
|
|
314
|
+
const targetStep = steps.find(s => s.opcodeRange.start === lastOp.target);
|
|
315
|
+
if (targetStep) {
|
|
316
|
+
step.nextStep = targetStep.index;
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
// If last opcode is WAIT, RACE_SUSPEND, or PARALLEL_WAIT, no nextStep (handled by resume handler)
|
|
320
|
+
else if (lastOp.op === 'WAIT' || lastOp.op === 'RACE_SUSPEND' || lastOp.op === 'PARALLEL_WAIT' || lastOp.op === 'SCOPE_WAIT' || lastOp.op === 'SCOPE_HANDLER') {
|
|
321
|
+
// No nextStep - will be set by resume handler
|
|
322
|
+
}
|
|
323
|
+
// If last opcode is RETURN, no nextStep
|
|
324
|
+
else if (lastOp.op === 'RETURN') {
|
|
325
|
+
// No nextStep
|
|
326
|
+
}
|
|
327
|
+
// Race branch entry: nextStep depends on whether the branch has
|
|
328
|
+
// intermediate steps (e.g. for-of loops creating additional steps).
|
|
329
|
+
// If the next step is still within this branch's body, flow to it.
|
|
330
|
+
// Otherwise, jump to the continuation step after all race branches.
|
|
331
|
+
else if (step.type === 'branch' && step.branchInfo) {
|
|
332
|
+
if (i + 1 < steps.length) {
|
|
333
|
+
const nextStepCandidate = steps[i + 1];
|
|
334
|
+
// Check if next step is another branch entry of the same race
|
|
335
|
+
const isNextBranchEntry = nextStepCandidate.type === 'branch' &&
|
|
336
|
+
nextStepCandidate.branchInfo?.raceOpcodeIndex === step.branchInfo.raceOpcodeIndex;
|
|
337
|
+
if (isNextBranchEntry) {
|
|
338
|
+
// This branch is a single step; jump to continuation after all branches
|
|
339
|
+
let continuation = raceContinuationSteps.get(step.branchInfo.raceOpcodeIndex);
|
|
340
|
+
if (continuation === undefined) {
|
|
341
|
+
// No continuation found by opcode range (happens when last branch extends
|
|
342
|
+
// to end of opcodes, e.g. race inside while(true)). Find the first non-branch
|
|
343
|
+
// step after all branches of this race.
|
|
344
|
+
for (let j = i + 1; j < steps.length; j++) {
|
|
345
|
+
const candidate = steps[j];
|
|
346
|
+
if (candidate.type !== 'branch' || candidate.branchInfo?.raceOpcodeIndex !== step.branchInfo.raceOpcodeIndex) {
|
|
347
|
+
continuation = candidate.index;
|
|
348
|
+
raceContinuationSteps.set(step.branchInfo.raceOpcodeIndex, continuation);
|
|
349
|
+
break;
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
if (continuation !== undefined) {
|
|
354
|
+
step.nextStep = continuation;
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
else {
|
|
358
|
+
// Branch has more steps; flow to the next one
|
|
359
|
+
step.nextStep = nextStepCandidate.index;
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
else {
|
|
363
|
+
// Last step overall - use continuation if available
|
|
364
|
+
const continuation = raceContinuationSteps.get(step.branchInfo.raceOpcodeIndex);
|
|
365
|
+
if (continuation !== undefined) {
|
|
366
|
+
step.nextStep = continuation;
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
// Otherwise, nextStep is the following step - but check if we're at
|
|
371
|
+
// the end of a race branch body, in which case we need to jump to
|
|
372
|
+
// the continuation step (past all branches) instead of falling into
|
|
373
|
+
// the next branch's entry.
|
|
374
|
+
else if (i + 1 < steps.length) {
|
|
375
|
+
const nextCandidate = steps[i + 1];
|
|
376
|
+
// Check if next step is a branch entry (of any race) while current
|
|
377
|
+
// step is within that race's branch body
|
|
378
|
+
if (nextCandidate.type === 'branch' && nextCandidate.branchInfo) {
|
|
379
|
+
const ranges = raceBranchRanges.get(nextCandidate.branchInfo.raceOpcodeIndex);
|
|
380
|
+
const isInSameRace = ranges?.some(r => step.opcodeRange.start >= r.start && step.opcodeRange.start < r.end);
|
|
381
|
+
if (isInSameRace) {
|
|
382
|
+
// Current step is the last step of a branch body - jump to continuation
|
|
383
|
+
const continuation = raceContinuationSteps.get(nextCandidate.branchInfo.raceOpcodeIndex);
|
|
384
|
+
if (continuation !== undefined) {
|
|
385
|
+
step.nextStep = continuation;
|
|
386
|
+
}
|
|
387
|
+
// else: no continuation (race is at end of handler) - no nextStep
|
|
388
|
+
}
|
|
389
|
+
else {
|
|
390
|
+
step.nextStep = nextCandidate.index;
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
else {
|
|
394
|
+
step.nextStep = nextCandidate.index;
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
return { steps, stepMap, sourceMap };
|
|
400
|
+
}
|
|
401
|
+
/**
|
|
402
|
+
* Generate the compiled switch-based process.
|
|
403
|
+
*/
|
|
404
|
+
export function generateSwitchProcess(factory, input) {
|
|
405
|
+
const { id, path, version, injectNode, handler, analysis, originalNode } = input;
|
|
406
|
+
// Build rewriter context
|
|
407
|
+
// Subprocess varNames are compile-time constructs - the compiler emits
|
|
408
|
+
// SUBPROCESS_SPAWN opcodes for calls to them, so they must never be
|
|
409
|
+
// rewritten to state.vars.* or treated as persisted locals.
|
|
410
|
+
const subprocessVarNames = new Set(analysis.subprocesses.map(s => s.varName));
|
|
411
|
+
const rewriterCtx = {
|
|
412
|
+
paramVars: extractParamVars(handler),
|
|
413
|
+
paramAliases: extractParamAliases(handler),
|
|
414
|
+
serviceVars: extractServiceVars(handler),
|
|
415
|
+
localVars: new Set(Array.from(analysis.variables.entries())
|
|
416
|
+
.filter(([name, info]) => !info.isUsing && !subprocessVarNames.has(name))
|
|
417
|
+
.map(([name]) => name)),
|
|
418
|
+
usingVars: new Set(Array.from(analysis.variables.entries())
|
|
419
|
+
.filter(([_, info]) => info.isUsing)
|
|
420
|
+
.map(([name]) => name)),
|
|
421
|
+
raceVars: analysis.raceVars,
|
|
422
|
+
};
|
|
423
|
+
// Build steps from opcodes
|
|
424
|
+
const { steps, stepMap, sourceMap } = buildSteps(analysis);
|
|
425
|
+
// Generate the execute function
|
|
426
|
+
const executeFunction = generateExecuteFunction(factory, steps, analysis, rewriterCtx, handler);
|
|
427
|
+
// Build property assignments for the __createProcess call
|
|
428
|
+
const processProperties = [
|
|
429
|
+
factory.createPropertyAssignment('id', factory.createStringLiteral(id)),
|
|
430
|
+
factory.createPropertyAssignment('path', factory.createStringLiteral(path)),
|
|
431
|
+
factory.createPropertyAssignment('version', factory.createStringLiteral(version)),
|
|
432
|
+
factory.createPropertyAssignment('inject', injectNode ?? factory.createObjectLiteralExpression([])),
|
|
433
|
+
factory.createPropertyAssignment('stepMap', generateStepMapObject(factory, stepMap)),
|
|
434
|
+
factory.createPropertyAssignment('sourceMap', generateSourceMapObject(factory, sourceMap)),
|
|
435
|
+
factory.createPropertyAssignment('signals', generateSignalsObject(factory, analysis.signals)),
|
|
436
|
+
factory.createPropertyAssignment('execute', executeFunction),
|
|
437
|
+
];
|
|
438
|
+
// Emit types config when provided (for runtime ref wrapping of params)
|
|
439
|
+
if (input.typesNode) {
|
|
440
|
+
processProperties.push(factory.createPropertyAssignment('types', input.typesNode));
|
|
441
|
+
}
|
|
442
|
+
// Emit exports metadata when handler uses `using exports = { ... }`
|
|
443
|
+
if (analysis.exports) {
|
|
444
|
+
processProperties.push(factory.createPropertyAssignment('exports', generateExportsMetadata(factory, analysis.exports)));
|
|
445
|
+
}
|
|
446
|
+
// Emit subprocess definitions
|
|
447
|
+
if (analysis.subprocesses.length > 0) {
|
|
448
|
+
processProperties.push(factory.createPropertyAssignment('subprocesses', factory.createArrayLiteralExpression(analysis.subprocesses.map(sub => generateSubProcessDefinition(factory, sub, rewriterCtx.serviceVars)), true)));
|
|
449
|
+
}
|
|
450
|
+
// Build the __createProcess({ ... }) call
|
|
451
|
+
const callExpr = factory.createCallExpression(factory.createIdentifier('__createProcess'), undefined, [factory.createObjectLiteralExpression(processProperties, true)]);
|
|
452
|
+
// If exports exist, wrap with a type assertion so TExports flows through to the declaration
|
|
453
|
+
if (analysis.exports && input.typeChecker) {
|
|
454
|
+
const exportsTypeNode = buildExportsTypeNode(factory, analysis.exports, input.typeChecker);
|
|
455
|
+
if (exportsTypeNode) {
|
|
456
|
+
// Emit: __createProcess({...}) as __WithExports<typeof __createProcess({...}), TExports>
|
|
457
|
+
// We use a helper type to avoid repeating all generic args.
|
|
458
|
+
// __withExports is: <T, E>(def: T) => T & { data: E }
|
|
459
|
+
// Add a phantom property to carry TExports through to __createProcess.
|
|
460
|
+
// __createProcess infers TExports from compiled.__exportsType.
|
|
461
|
+
processProperties.push(factory.createPropertyAssignment('__exportsType', factory.createAsExpression(factory.createVoidExpression(factory.createNumericLiteral(0)), exportsTypeNode)));
|
|
462
|
+
}
|
|
463
|
+
}
|
|
464
|
+
if (originalNode) {
|
|
465
|
+
return ts.setTextRange(callExpr, originalNode);
|
|
466
|
+
}
|
|
467
|
+
return callExpr;
|
|
468
|
+
}
|
|
469
|
+
/**
|
|
470
|
+
* Generate the stepMap object: { 'entry_abc': 0, ... }
|
|
471
|
+
*/
|
|
472
|
+
function generateStepMapObject(factory, stepMap) {
|
|
473
|
+
return factory.createObjectLiteralExpression(Object.entries(stepMap).map(([hash, index]) => factory.createPropertyAssignment(factory.createStringLiteral(hash), factory.createNumericLiteral(index))), true);
|
|
474
|
+
}
|
|
475
|
+
/**
|
|
476
|
+
* Generate the sourceMap object: { 0: [5, 10], ... }
|
|
477
|
+
*/
|
|
478
|
+
function generateSourceMapObject(factory, sourceMap) {
|
|
479
|
+
return factory.createObjectLiteralExpression(Object.entries(sourceMap).map(([index, range]) => factory.createPropertyAssignment(factory.createNumericLiteral(parseInt(index)), factory.createArrayLiteralExpression([
|
|
480
|
+
factory.createNumericLiteral(range[0]),
|
|
481
|
+
factory.createNumericLiteral(range[1]),
|
|
482
|
+
]))), true);
|
|
483
|
+
}
|
|
484
|
+
/**
|
|
485
|
+
* Generate the signals object.
|
|
486
|
+
*/
|
|
487
|
+
function generateSignalsObject(factory, signals) {
|
|
488
|
+
return factory.createObjectLiteralExpression(Object.entries(signals).map(([name, info]) => factory.createPropertyAssignment(factory.createStringLiteral(name), factory.createObjectLiteralExpression([
|
|
489
|
+
factory.createPropertyAssignment('identity', factory.createArrayLiteralExpression(info.identity.map(i => factory.createStringLiteral(i)))),
|
|
490
|
+
factory.createPropertyAssignment('payloadType', factory.createStringLiteral(info.payloadType)),
|
|
491
|
+
]))), true);
|
|
492
|
+
}
|
|
493
|
+
/**
|
|
494
|
+
* Generate a compiled subprocess definition object.
|
|
495
|
+
* Each subprocess gets its own execute function, step map, and signal definitions.
|
|
496
|
+
*/
|
|
497
|
+
function generateSubProcessDefinition(factory, sub, parentServiceVars = new Set()) {
|
|
498
|
+
const subAnalysis = sub.analysis;
|
|
499
|
+
// Build subprocess rewriter context.
|
|
500
|
+
// The subprocess handler closes over the parent's injected services, so
|
|
501
|
+
// identifiers like `signals` must be rewritten to `services.signals` just
|
|
502
|
+
// as they are in the parent execute function. Inherit parent serviceVars.
|
|
503
|
+
const subRewriterCtx = {
|
|
504
|
+
paramVars: new Set(sub.handlerParams),
|
|
505
|
+
paramAliases: new Map(),
|
|
506
|
+
serviceVars: parentServiceVars,
|
|
507
|
+
localVars: new Set(Array.from(subAnalysis.variables.entries())
|
|
508
|
+
.filter(([_, info]) => !info.isUsing)
|
|
509
|
+
.map(([name]) => name)),
|
|
510
|
+
usingVars: new Set(Array.from(subAnalysis.variables.entries())
|
|
511
|
+
.filter(([_, info]) => info.isUsing)
|
|
512
|
+
.map(([name]) => name)),
|
|
513
|
+
raceVars: subAnalysis.raceVars,
|
|
514
|
+
};
|
|
515
|
+
// Build steps from subprocess opcodes
|
|
516
|
+
const { steps, stepMap } = buildSteps(subAnalysis);
|
|
517
|
+
// Generate the subprocess execute function
|
|
518
|
+
const executeFunction = generateExecuteFunction(factory, steps, subAnalysis, subRewriterCtx, sub.handlerNode);
|
|
519
|
+
const properties = [
|
|
520
|
+
factory.createPropertyAssignment('name', factory.createStringLiteral(sub.name)),
|
|
521
|
+
factory.createPropertyAssignment('path', factory.createStringLiteral(sub.path)),
|
|
522
|
+
factory.createPropertyAssignment('params', factory.createArrayLiteralExpression(sub.handlerParams.map(p => factory.createStringLiteral(p)))),
|
|
523
|
+
factory.createPropertyAssignment('stepMap', generateStepMapObject(factory, stepMap)),
|
|
524
|
+
factory.createPropertyAssignment('signals', generateSignalsObject(factory, subAnalysis.signals)),
|
|
525
|
+
factory.createPropertyAssignment('execute', executeFunction),
|
|
526
|
+
];
|
|
527
|
+
if (subAnalysis.exports) {
|
|
528
|
+
properties.push(factory.createPropertyAssignment('exports', generateExportsMetadata(factory, subAnalysis.exports)));
|
|
529
|
+
}
|
|
530
|
+
return factory.createObjectLiteralExpression(properties, true);
|
|
531
|
+
}
|
|
532
|
+
/**
|
|
533
|
+
* Build a TypeNode for the exports type using the type checker.
|
|
534
|
+
* Extracts the type from the `using exports = { ... }` declaration and converts
|
|
535
|
+
* it to a TypeNode that can be emitted as a type argument on __createProcess.
|
|
536
|
+
*/
|
|
537
|
+
function buildExportsTypeNode(factory, exports, typeChecker) {
|
|
538
|
+
const declNode = exports.declarationNode;
|
|
539
|
+
if (!ts.isVariableDeclaration(declNode))
|
|
540
|
+
return undefined;
|
|
541
|
+
const type = typeChecker.getTypeAtLocation(declNode);
|
|
542
|
+
const typeNode = typeChecker.typeToTypeNode(type, declNode, ts.NodeBuilderFlags.NoTruncation | ts.NodeBuilderFlags.WriteArrayAsGenericType);
|
|
543
|
+
return typeNode;
|
|
544
|
+
}
|
|
545
|
+
/**
|
|
546
|
+
* Generate exports metadata: { fields: ['count', 'name'], methods: { getCount() { ... } } }
|
|
547
|
+
* Methods are emitted as-is from the source AST so they can be reattached on resume.
|
|
548
|
+
*/
|
|
549
|
+
function generateExportsMetadata(factory, exports) {
|
|
550
|
+
const fieldsArray = factory.createArrayLiteralExpression(exports.fields.map(f => factory.createStringLiteral(f.name)));
|
|
551
|
+
const methodProperties = [];
|
|
552
|
+
for (const method of exports.methods) {
|
|
553
|
+
if (ts.isMethodDeclaration(method.node)) {
|
|
554
|
+
// method declaration: getCount() { ... } -> getCount: function() { ... }
|
|
555
|
+
const funcExpr = factory.createFunctionExpression(method.node.modifiers?.filter(m => m.kind === ts.SyntaxKind.AsyncKeyword), undefined, undefined, method.node.typeParameters, method.node.parameters, method.node.type, method.node.body ?? factory.createBlock([]));
|
|
556
|
+
methodProperties.push(factory.createPropertyAssignment(factory.createStringLiteral(method.name), funcExpr));
|
|
557
|
+
}
|
|
558
|
+
else if (ts.isPropertyAssignment(method.node)) {
|
|
559
|
+
// arrow/function expression: getFirst: () => 'x' or compute: function() { ... }
|
|
560
|
+
methodProperties.push(factory.createPropertyAssignment(factory.createStringLiteral(method.name), method.node.initializer));
|
|
561
|
+
}
|
|
562
|
+
}
|
|
563
|
+
return factory.createObjectLiteralExpression([
|
|
564
|
+
factory.createPropertyAssignment('fields', fieldsArray),
|
|
565
|
+
factory.createPropertyAssignment('methods', factory.createObjectLiteralExpression(methodProperties, true)),
|
|
566
|
+
], true);
|
|
567
|
+
}
|
|
568
|
+
/**
|
|
569
|
+
* Generate the execute function.
|
|
570
|
+
*/
|
|
571
|
+
function generateExecuteFunction(factory, steps, analysis, rewriterCtx, handler) {
|
|
572
|
+
// Get the handler's closing brace for source mapping the function exit
|
|
573
|
+
const handlerBody = handler.body;
|
|
574
|
+
const closingBracePos = handlerBody && ts.isBlock(handlerBody) ? handlerBody.end : handler.end;
|
|
575
|
+
const statements = [];
|
|
576
|
+
// Source range for the original handler, used to map generated boilerplate
|
|
577
|
+
const handlerRange = { pos: handler.pos, end: handler.end };
|
|
578
|
+
// const { state, services } = ctx
|
|
579
|
+
const ctxDestructure = factory.createVariableStatement(undefined, factory.createVariableDeclarationList([
|
|
580
|
+
factory.createVariableDeclaration(factory.createObjectBindingPattern([
|
|
581
|
+
factory.createBindingElement(undefined, undefined, 'state'),
|
|
582
|
+
factory.createBindingElement(undefined, undefined, 'services'),
|
|
583
|
+
]), undefined, undefined, factory.createIdentifier('ctx')),
|
|
584
|
+
], ts.NodeFlags.Const));
|
|
585
|
+
ts.setSourceMapRange(ctxDestructure, handlerRange);
|
|
586
|
+
statements.push(ctxDestructure);
|
|
587
|
+
// Declare local variables for using vars
|
|
588
|
+
const usingVars = Array.from(rewriterCtx.usingVars);
|
|
589
|
+
if (usingVars.length > 0) {
|
|
590
|
+
statements.push(factory.createVariableStatement(undefined, factory.createVariableDeclarationList(usingVars.map(v => factory.createVariableDeclaration(v, undefined, undefined, undefined)), ts.NodeFlags.Let)));
|
|
591
|
+
}
|
|
592
|
+
// let step = state.step | 0
|
|
593
|
+
const stepDecl = factory.createVariableStatement(undefined, factory.createVariableDeclarationList([
|
|
594
|
+
factory.createVariableDeclaration('step', undefined, undefined, factory.createBinaryExpression(factory.createPropertyAccessExpression(factory.createIdentifier('state'), 'step'), ts.SyntaxKind.BarToken, factory.createNumericLiteral(0))),
|
|
595
|
+
], ts.NodeFlags.Let));
|
|
596
|
+
ts.setSourceMapRange(stepDecl, handlerRange);
|
|
597
|
+
statements.push(stepDecl);
|
|
598
|
+
// const __r: [number, unknown] = [0, undefined]
|
|
599
|
+
const rDecl = factory.createVariableStatement(undefined, factory.createVariableDeclarationList([
|
|
600
|
+
factory.createVariableDeclaration('__r', undefined, factory.createTupleTypeNode([
|
|
601
|
+
factory.createKeywordTypeNode(ts.SyntaxKind.NumberKeyword),
|
|
602
|
+
factory.createKeywordTypeNode(ts.SyntaxKind.UnknownKeyword),
|
|
603
|
+
]), factory.createArrayLiteralExpression([
|
|
604
|
+
factory.createNumericLiteral(0),
|
|
605
|
+
factory.createIdentifier('undefined'),
|
|
606
|
+
])),
|
|
607
|
+
], ts.NodeFlags.Const));
|
|
608
|
+
ts.setSourceMapRange(rDecl, handlerRange);
|
|
609
|
+
statements.push(rDecl);
|
|
610
|
+
// let __blockResult: unknown
|
|
611
|
+
const blockResultDecl = factory.createVariableStatement(undefined, factory.createVariableDeclarationList([
|
|
612
|
+
factory.createVariableDeclaration('__blockResult', undefined, factory.createKeywordTypeNode(ts.SyntaxKind.UnknownKeyword), undefined),
|
|
613
|
+
], ts.NodeFlags.Let));
|
|
614
|
+
ts.setSourceMapRange(blockResultDecl, handlerRange);
|
|
615
|
+
statements.push(blockResultDecl);
|
|
616
|
+
// let __raceResult: unknown
|
|
617
|
+
const raceResultDecl = factory.createVariableStatement(undefined, factory.createVariableDeclarationList([
|
|
618
|
+
factory.createVariableDeclaration('__raceResult', undefined, factory.createKeywordTypeNode(ts.SyntaxKind.UnknownKeyword), undefined),
|
|
619
|
+
], ts.NodeFlags.Let));
|
|
620
|
+
ts.setSourceMapRange(raceResultDecl, handlerRange);
|
|
621
|
+
statements.push(raceResultDecl);
|
|
622
|
+
// const __dispose: (Disposable | undefined)[] = [undefined, ...]
|
|
623
|
+
if (usingVars.length > 0) {
|
|
624
|
+
statements.push(factory.createVariableStatement(undefined, factory.createVariableDeclarationList([
|
|
625
|
+
factory.createVariableDeclaration('__dispose', undefined, undefined, factory.createArrayLiteralExpression(usingVars.map(() => factory.createIdentifier('undefined')))),
|
|
626
|
+
], ts.NodeFlags.Const)));
|
|
627
|
+
// let __dispose_i = 0
|
|
628
|
+
statements.push(factory.createVariableStatement(undefined, factory.createVariableDeclarationList([
|
|
629
|
+
factory.createVariableDeclaration('__dispose_i', undefined, undefined, factory.createNumericLiteral(0)),
|
|
630
|
+
], ts.NodeFlags.Let)));
|
|
631
|
+
}
|
|
632
|
+
// Generate the main_loop: while (true) { switch (step) { ... } }
|
|
633
|
+
const switchCases = steps.map(step => generateSwitchCase(factory, step, steps, analysis, rewriterCtx));
|
|
634
|
+
// Add default case
|
|
635
|
+
switchCases.push(factory.createDefaultClause([
|
|
636
|
+
factory.createThrowStatement(factory.createNewExpression(factory.createIdentifier('Error'), undefined, [
|
|
637
|
+
factory.createTemplateExpression(factory.createTemplateHead('Invalid step: '), [
|
|
638
|
+
factory.createTemplateSpan(factory.createIdentifier('step'), factory.createTemplateTail('')),
|
|
639
|
+
]),
|
|
640
|
+
])),
|
|
641
|
+
]));
|
|
642
|
+
const switchStatement = factory.createSwitchStatement(factory.createIdentifier('step'), factory.createCaseBlock(switchCases));
|
|
643
|
+
const whileStatement = factory.createWhileStatement(factory.createTrue(), factory.createBlock([switchStatement], true));
|
|
644
|
+
// Add label: main_loop
|
|
645
|
+
const labeledWhile = factory.createLabeledStatement('main_loop', whileStatement);
|
|
646
|
+
statements.push(labeledWhile);
|
|
647
|
+
// Cleanup: dispose using vars
|
|
648
|
+
if (usingVars.length > 0) {
|
|
649
|
+
// while (__dispose_i > 0) { __dispose[--__dispose_i]?.[Symbol.dispose]?.() }
|
|
650
|
+
statements.push(factory.createWhileStatement(factory.createBinaryExpression(factory.createIdentifier('__dispose_i'), ts.SyntaxKind.GreaterThanToken, factory.createNumericLiteral(0)), factory.createBlock([
|
|
651
|
+
factory.createExpressionStatement(factory.createCallChain(factory.createElementAccessChain(factory.createElementAccessChain(factory.createIdentifier('__dispose'), factory.createToken(ts.SyntaxKind.QuestionDotToken), factory.createPrefixUnaryExpression(ts.SyntaxKind.MinusMinusToken, factory.createIdentifier('__dispose_i'))), factory.createToken(ts.SyntaxKind.QuestionDotToken), factory.createPropertyAccessExpression(factory.createIdentifier('Symbol'), 'dispose')), factory.createToken(ts.SyntaxKind.QuestionDotToken), undefined, [])),
|
|
652
|
+
])));
|
|
653
|
+
}
|
|
654
|
+
// Suspend handling: persist locals to state
|
|
655
|
+
// if (__r[0] === 1) { state.step = step; ... }
|
|
656
|
+
// Map to closing brace - this is function exit logic
|
|
657
|
+
const stateStepAssign = factory.createExpressionStatement(factory.createBinaryExpression(factory.createPropertyAccessExpression(factory.createIdentifier('state'), 'step'), ts.SyntaxKind.EqualsToken, factory.createIdentifier('step')));
|
|
658
|
+
const suspendIfStmt = factory.createIfStatement(factory.createBinaryExpression(factory.createElementAccessExpression(factory.createIdentifier('__r'), factory.createNumericLiteral(0)), ts.SyntaxKind.ExclamationEqualsEqualsToken, factory.createNumericLiteral(0) // not DONE - save step on SUSPEND or SUBPROCESS
|
|
659
|
+
), factory.createBlock([
|
|
660
|
+
stateStepAssign,
|
|
661
|
+
// Locals are already persisted: block rewriting transforms `const x = ...`
|
|
662
|
+
// to `state.vars.x = ...`, so all locals survive suspension points.
|
|
663
|
+
]));
|
|
664
|
+
const closingRange = { pos: closingBracePos - 1, end: closingBracePos };
|
|
665
|
+
ts.setSourceMapRange(stateStepAssign, closingRange);
|
|
666
|
+
ts.setTextRange(suspendIfStmt, closingRange);
|
|
667
|
+
ts.setSourceMapRange(suspendIfStmt, closingRange);
|
|
668
|
+
statements.push(suspendIfStmt);
|
|
669
|
+
// return __r - map to closing brace for clean function exit in debugger
|
|
670
|
+
const returnStmt = factory.createReturnStatement(factory.createIdentifier('__r'));
|
|
671
|
+
ts.setTextRange(returnStmt, closingRange);
|
|
672
|
+
ts.setSourceMapRange(returnStmt, closingRange);
|
|
673
|
+
statements.push(returnStmt);
|
|
674
|
+
// Create the async arrow function
|
|
675
|
+
const arrowFn = factory.createArrowFunction([factory.createModifier(ts.SyntaxKind.AsyncKeyword)], undefined, [factory.createParameterDeclaration(undefined, undefined, 'ctx')], undefined, factory.createToken(ts.SyntaxKind.EqualsGreaterThanToken), factory.createBlock(statements, true));
|
|
676
|
+
// Map generated nodes back to original handler for source maps
|
|
677
|
+
ts.setSourceMapRange(arrowFn, handlerRange);
|
|
678
|
+
if (handlerBody && ts.isBlock(handlerBody)) {
|
|
679
|
+
const bodyRange = { pos: handlerBody.pos, end: handlerBody.end };
|
|
680
|
+
ts.setSourceMapRange(switchStatement, bodyRange);
|
|
681
|
+
ts.setSourceMapRange(whileStatement, bodyRange);
|
|
682
|
+
ts.setSourceMapRange(labeledWhile, bodyRange);
|
|
683
|
+
}
|
|
684
|
+
return arrowFn;
|
|
685
|
+
}
|
|
686
|
+
/**
|
|
687
|
+
* Emit a single rehydrate-prelude entry: varName = expr; __dispose[__dispose_i++] = varName.
|
|
688
|
+
* Uses the expression from the REHYDRATE opcode so that sibling branches with the same
|
|
689
|
+
* variable name each use their own initializer.
|
|
690
|
+
*/
|
|
691
|
+
function emitRehydrateEntry(factory, varName, expression, rewriterCtx, out) {
|
|
692
|
+
const rewrittenExpr = rewriteExpression(factory, expression, rewriterCtx);
|
|
693
|
+
out.push(factory.createExpressionStatement(factory.createBinaryExpression(factory.createIdentifier(varName), ts.SyntaxKind.EqualsToken, rewrittenExpr)));
|
|
694
|
+
out.push(factory.createExpressionStatement(factory.createBinaryExpression(factory.createElementAccessExpression(factory.createIdentifier('__dispose'), factory.createPostfixUnaryExpression(factory.createIdentifier('__dispose_i'), ts.SyntaxKind.PlusPlusToken)), ts.SyntaxKind.EqualsToken, factory.createIdentifier(varName))));
|
|
695
|
+
}
|
|
696
|
+
/**
|
|
697
|
+
* Topologically sort rehydrate deps so that a var whose initializer references
|
|
698
|
+
* another using-var is emitted after that dep.
|
|
699
|
+
*
|
|
700
|
+
* The expression carried on each REHYDRATE opcode is the authoritative source;
|
|
701
|
+
* deps extracted from it tell us what other using-vars must be available first.
|
|
702
|
+
*/
|
|
703
|
+
function topoSortRehydrateDeps(varNames, rehydrateOpcodeExprs, usingVars) {
|
|
704
|
+
// Build adjacency: varName -> set of using-vars it depends on
|
|
705
|
+
const deps = new Map();
|
|
706
|
+
for (const v of varNames) {
|
|
707
|
+
deps.set(v, new Set());
|
|
708
|
+
const expr = rehydrateOpcodeExprs.get(v);
|
|
709
|
+
if (expr) {
|
|
710
|
+
const visit = (node) => {
|
|
711
|
+
if (ts.isIdentifier(node) && usingVars.has(node.text) && node.text !== v) {
|
|
712
|
+
deps.get(v).add(node.text);
|
|
713
|
+
}
|
|
714
|
+
ts.forEachChild(node, visit);
|
|
715
|
+
};
|
|
716
|
+
visit(expr);
|
|
717
|
+
}
|
|
718
|
+
}
|
|
719
|
+
// Kahn's algorithm
|
|
720
|
+
const inDegree = new Map();
|
|
721
|
+
for (const v of varNames)
|
|
722
|
+
inDegree.set(v, 0);
|
|
723
|
+
for (const [, ds] of deps) {
|
|
724
|
+
for (const d of ds) {
|
|
725
|
+
if (inDegree.has(d)) {
|
|
726
|
+
// d is a dep of something; increment in-degree of the dependent
|
|
727
|
+
}
|
|
728
|
+
}
|
|
729
|
+
}
|
|
730
|
+
// Actually: inDegree[v] = number of vars in varNames that v depends on
|
|
731
|
+
for (const v of varNames) {
|
|
732
|
+
let count = 0;
|
|
733
|
+
for (const d of deps.get(v)) {
|
|
734
|
+
if (inDegree.has(d))
|
|
735
|
+
count++;
|
|
736
|
+
}
|
|
737
|
+
inDegree.set(v, count);
|
|
738
|
+
}
|
|
739
|
+
const queue = varNames.filter(v => inDegree.get(v) === 0);
|
|
740
|
+
const result = [];
|
|
741
|
+
while (queue.length > 0) {
|
|
742
|
+
const v = queue.shift();
|
|
743
|
+
result.push(v);
|
|
744
|
+
// Vars that depend on v can now have their in-degree reduced
|
|
745
|
+
for (const other of varNames) {
|
|
746
|
+
if (deps.get(other)?.has(v)) {
|
|
747
|
+
const newDeg = inDegree.get(other) - 1;
|
|
748
|
+
inDegree.set(other, newDeg);
|
|
749
|
+
if (newDeg === 0)
|
|
750
|
+
queue.push(other);
|
|
751
|
+
}
|
|
752
|
+
}
|
|
753
|
+
}
|
|
754
|
+
// If cycle (shouldn't happen in valid code), append remaining
|
|
755
|
+
for (const v of varNames) {
|
|
756
|
+
if (!result.includes(v))
|
|
757
|
+
result.push(v);
|
|
758
|
+
}
|
|
759
|
+
return result;
|
|
760
|
+
}
|
|
761
|
+
/**
|
|
762
|
+
* Generate a switch case for a step.
|
|
763
|
+
*/
|
|
764
|
+
function generateSwitchCase(factory, step, steps, analysis, rewriterCtx) {
|
|
765
|
+
const { opcodes, rehydrationBlocks } = analysis;
|
|
766
|
+
const caseStatements = [];
|
|
767
|
+
// Add comment with step hash
|
|
768
|
+
// This is done via synthetic comments on the first statement
|
|
769
|
+
// For branch steps, the first opcode is always STORE __raceResult fromRace.
|
|
770
|
+
// That STORE must run before the rehydrate prelude so that initializers that
|
|
771
|
+
// reference the race result (e.g. `using lk = await svc.lock(r.id)`) can
|
|
772
|
+
// resolve `r` correctly.
|
|
773
|
+
//
|
|
774
|
+
// Collect the REHYDRATE opcodes in this step's range so we can:
|
|
775
|
+
// - use each opcode's own expression (not the global rehydrationBlocks map)
|
|
776
|
+
// for the correct per-branch initializer
|
|
777
|
+
// - skip those REHYDRATE opcodes when iterating later (no double-acquire)
|
|
778
|
+
const rehydrateOpcodeExprs = new Map();
|
|
779
|
+
const rehydrateOpcodeIndices = new Set();
|
|
780
|
+
for (let i = step.opcodeRange.start; i < step.opcodeRange.end; i++) {
|
|
781
|
+
const op = opcodes[i];
|
|
782
|
+
if (op.op === 'REHYDRATE') {
|
|
783
|
+
rehydrateOpcodeExprs.set(op.var, op.expression);
|
|
784
|
+
rehydrateOpcodeIndices.add(i);
|
|
785
|
+
}
|
|
786
|
+
}
|
|
787
|
+
// For branch steps: emit any leading STORE opcodes FIRST, before the rehydrate prelude.
|
|
788
|
+
// Leading STORE opcodes are at the very beginning of the branch range (the fromRace STORE).
|
|
789
|
+
const leadingStoreIndices = new Set();
|
|
790
|
+
if (step.type === 'branch') {
|
|
791
|
+
for (let i = step.opcodeRange.start; i < step.opcodeRange.end; i++) {
|
|
792
|
+
const op = opcodes[i];
|
|
793
|
+
if (op.op === 'STORE' && op.fromRace) {
|
|
794
|
+
const stmts = generateOpcodeStatements(factory, op, step, steps, analysis, rewriterCtx, i);
|
|
795
|
+
const sourceNode = analysis.opcodeSourceNodes[i];
|
|
796
|
+
const stepSourceRng = getStepSourceRange(step, analysis);
|
|
797
|
+
const opcodeRange = sourceNode ? { pos: sourceNode.pos, end: sourceNode.end } : stepSourceRng;
|
|
798
|
+
if (opcodeRange)
|
|
799
|
+
stmts.forEach(s => ts.setSourceMapRange(s, opcodeRange));
|
|
800
|
+
caseStatements.push(...stmts);
|
|
801
|
+
leadingStoreIndices.add(i);
|
|
802
|
+
}
|
|
803
|
+
else {
|
|
804
|
+
// Stop at first non-STORE opcode
|
|
805
|
+
break;
|
|
806
|
+
}
|
|
807
|
+
}
|
|
808
|
+
}
|
|
809
|
+
// Handle rehydration prelude for resume steps (and branch steps after STORE).
|
|
810
|
+
// Resolve the expression from the REHYDRATE opcode within this step's range when
|
|
811
|
+
// available (per-branch expression from REHYDRATE opcode), otherwise fall back to rehydrationBlocks.
|
|
812
|
+
if (step.rehydrateDeps && step.rehydrateDeps.length > 0) {
|
|
813
|
+
// Build a map of expressions to use for each dep
|
|
814
|
+
const exprMap = new Map();
|
|
815
|
+
for (const varName of step.rehydrateDeps) {
|
|
816
|
+
// Prefer the expression carried on the REHYDRATE opcode in this step
|
|
817
|
+
const opcodeExpr = rehydrateOpcodeExprs.get(varName);
|
|
818
|
+
if (opcodeExpr) {
|
|
819
|
+
exprMap.set(varName, opcodeExpr);
|
|
820
|
+
}
|
|
821
|
+
else {
|
|
822
|
+
// Var was declared in a prior step; look up in global map
|
|
823
|
+
const rehydBlock = rehydrationBlocks[varName];
|
|
824
|
+
if (rehydBlock)
|
|
825
|
+
exprMap.set(varName, rehydBlock.expression);
|
|
826
|
+
}
|
|
827
|
+
}
|
|
828
|
+
// Topologically sort so deps are emitted before dependents
|
|
829
|
+
const depsToEmit = step.rehydrateDeps.filter(v => exprMap.has(v));
|
|
830
|
+
const sorted = topoSortRehydrateDeps(depsToEmit, exprMap, rewriterCtx.usingVars);
|
|
831
|
+
for (const varName of sorted) {
|
|
832
|
+
const expr = exprMap.get(varName);
|
|
833
|
+
emitRehydrateEntry(factory, varName, expr, rewriterCtx, caseStatements);
|
|
834
|
+
}
|
|
835
|
+
}
|
|
836
|
+
// Compute step-level source range for fallback when individual opcodes lack source nodes
|
|
837
|
+
const stepSourceRange = getStepSourceRange(step, analysis);
|
|
838
|
+
// For race-branch steps, thread the continuation step through as the
|
|
839
|
+
// target for unlabeled `break` and `continue` statements that the user
|
|
840
|
+
// wrote inside their `switch(true) { case signal(...): ... }` branch bodies.
|
|
841
|
+
// Both statements inside an if-body of a race branch need to advance `step`
|
|
842
|
+
// to the continuation before restarting main_loop. See transformStatement.
|
|
843
|
+
const stepCtx = step.type === 'branch' && step.nextStep !== undefined
|
|
844
|
+
? { ...rewriterCtx, breakTarget: step.nextStep, continueTarget: step.nextStep }
|
|
845
|
+
: rewriterCtx;
|
|
846
|
+
// Process opcodes in this step's range, skipping:
|
|
847
|
+
// - leading STORE opcodes already emitted above (branch steps, branch-result-first ordering)
|
|
848
|
+
// - REHYDRATE opcodes for vars in the rehydrate prelude (no double-acquire)
|
|
849
|
+
for (let i = step.opcodeRange.start; i < step.opcodeRange.end; i++) {
|
|
850
|
+
if (leadingStoreIndices.has(i))
|
|
851
|
+
continue;
|
|
852
|
+
// Skip REHYDRATE opcodes for vars already emitted by the rehydrate prelude (no double-acquire)
|
|
853
|
+
if (rehydrateOpcodeIndices.has(i)) {
|
|
854
|
+
const op = opcodes[i];
|
|
855
|
+
if (op.op === 'REHYDRATE' && step.rehydrateDeps?.includes(op.var))
|
|
856
|
+
continue;
|
|
857
|
+
}
|
|
858
|
+
const opcode = opcodes[i];
|
|
859
|
+
const stmts = generateOpcodeStatements(factory, opcode, step, steps, analysis, stepCtx, i);
|
|
860
|
+
// Map generated statements to the opcode's original source node for source maps
|
|
861
|
+
const sourceNode = analysis.opcodeSourceNodes[i];
|
|
862
|
+
const opcodeRange = sourceNode
|
|
863
|
+
? { pos: sourceNode.pos, end: sourceNode.end }
|
|
864
|
+
: stepSourceRange; // fallback to step-level range
|
|
865
|
+
if (opcodeRange) {
|
|
866
|
+
for (const stmt of stmts) {
|
|
867
|
+
ts.setSourceMapRange(stmt, opcodeRange);
|
|
868
|
+
}
|
|
869
|
+
}
|
|
870
|
+
caseStatements.push(...stmts);
|
|
871
|
+
}
|
|
872
|
+
// Add explicit fallthrough to nextStep if the step doesn't end with a control flow statement.
|
|
873
|
+
// This handles race branches that don't emit JUMP (their break is a no-op).
|
|
874
|
+
if (step.nextStep !== undefined) {
|
|
875
|
+
// Check if last statement is a control flow (break, continue, return equiv)
|
|
876
|
+
const lastStmt = caseStatements[caseStatements.length - 1];
|
|
877
|
+
const hasControlFlow = lastStmt && (ts.isBreakStatement(lastStmt) ||
|
|
878
|
+
ts.isContinueStatement(lastStmt));
|
|
879
|
+
if (!hasControlFlow) {
|
|
880
|
+
// step = nextStep; continue main_loop
|
|
881
|
+
caseStatements.push(factory.createExpressionStatement(factory.createBinaryExpression(factory.createIdentifier('step'), ts.SyntaxKind.EqualsToken, factory.createNumericLiteral(step.nextStep))));
|
|
882
|
+
caseStatements.push(factory.createContinueStatement(factory.createIdentifier('main_loop')));
|
|
883
|
+
}
|
|
884
|
+
}
|
|
885
|
+
// Create the case clause
|
|
886
|
+
const caseClause = factory.createCaseClause(factory.createNumericLiteral(step.index), [factory.createBlock(caseStatements, true)]);
|
|
887
|
+
// Map case clause to original source range for source maps
|
|
888
|
+
const sourceRange = getStepSourceRange(step, analysis);
|
|
889
|
+
if (sourceRange) {
|
|
890
|
+
ts.setSourceMapRange(caseClause, sourceRange);
|
|
891
|
+
}
|
|
892
|
+
return caseClause;
|
|
893
|
+
}
|
|
894
|
+
/**
|
|
895
|
+
* Get the original source byte range (pos/end) for a step,
|
|
896
|
+
* derived from the opcode source nodes in the analysis.
|
|
897
|
+
*/
|
|
898
|
+
function getStepSourceRange(step, analysis) {
|
|
899
|
+
const { opcodeSourceNodes, raceBranchSourceNodes } = analysis;
|
|
900
|
+
// For branch steps, prefer the race branch source node
|
|
901
|
+
if (step.type === 'branch' && step.branchInfo) {
|
|
902
|
+
const branchNodes = raceBranchSourceNodes.get(step.branchInfo.raceOpcodeIndex);
|
|
903
|
+
const branchNode = branchNodes?.get(step.branchInfo.branchId);
|
|
904
|
+
if (branchNode) {
|
|
905
|
+
return { pos: branchNode.pos, end: branchNode.end };
|
|
906
|
+
}
|
|
907
|
+
}
|
|
908
|
+
// For other steps, compute range from opcode source nodes
|
|
909
|
+
let pos;
|
|
910
|
+
let end;
|
|
911
|
+
for (let j = step.opcodeRange.start; j < step.opcodeRange.end; j++) {
|
|
912
|
+
const sourceNode = opcodeSourceNodes[j];
|
|
913
|
+
if (sourceNode) {
|
|
914
|
+
if (pos === undefined || sourceNode.pos < pos)
|
|
915
|
+
pos = sourceNode.pos;
|
|
916
|
+
if (end === undefined || sourceNode.end > end)
|
|
917
|
+
end = sourceNode.end;
|
|
918
|
+
}
|
|
919
|
+
}
|
|
920
|
+
if (pos !== undefined && end !== undefined) {
|
|
921
|
+
return { pos, end };
|
|
922
|
+
}
|
|
923
|
+
return undefined;
|
|
924
|
+
}
|
|
925
|
+
/**
|
|
926
|
+
* Transform a statement for codegen.
|
|
927
|
+
* Handles if statements specially - transforming their returns to __r[1] = expr; break main_loop;
|
|
928
|
+
* Returns transformed statements (may be multiple for a single input).
|
|
929
|
+
*/
|
|
930
|
+
function transformStatement(factory, stmt, rewriterCtx) {
|
|
931
|
+
// Unlabeled `break` inside an if-body of a race branch: rewrite to jump
|
|
932
|
+
// to the race's continuation step. Without this the compiled case ends
|
|
933
|
+
// with a naked `break;` that exits `switch(step)` but leaves `step`
|
|
934
|
+
// unchanged, re-entering the same branch body on the next iteration of
|
|
935
|
+
// `main_loop` - infinite loop.
|
|
936
|
+
//
|
|
937
|
+
// We only rewrite here, not inside user-written for/while/switch bodies
|
|
938
|
+
// (those fall through to the `rewriteStatement` path which preserves
|
|
939
|
+
// their `break` verbatim so it continues to target the user's own
|
|
940
|
+
// construct).
|
|
941
|
+
if (ts.isBreakStatement(stmt) &&
|
|
942
|
+
!stmt.label &&
|
|
943
|
+
rewriterCtx.breakTarget !== undefined) {
|
|
944
|
+
return [
|
|
945
|
+
ts.setTextRange(factory.createExpressionStatement(factory.createBinaryExpression(factory.createIdentifier('step'), ts.SyntaxKind.EqualsToken, factory.createNumericLiteral(rewriterCtx.breakTarget))), stmt),
|
|
946
|
+
factory.createContinueStatement(factory.createIdentifier('main_loop')),
|
|
947
|
+
];
|
|
948
|
+
}
|
|
949
|
+
// Unlabeled `continue` inside an if-body of a race branch: same class of
|
|
950
|
+
// bug as the `break` case above. A naked `continue` restarts `main_loop`
|
|
951
|
+
// without advancing `step`, causing the same branch body to run again -
|
|
952
|
+
// infinite loop. Rewrite to `step = continueTarget; continue main_loop;`.
|
|
953
|
+
if (ts.isContinueStatement(stmt) &&
|
|
954
|
+
!stmt.label &&
|
|
955
|
+
rewriterCtx.continueTarget !== undefined) {
|
|
956
|
+
return [
|
|
957
|
+
ts.setTextRange(factory.createExpressionStatement(factory.createBinaryExpression(factory.createIdentifier('step'), ts.SyntaxKind.EqualsToken, factory.createNumericLiteral(rewriterCtx.continueTarget))), stmt),
|
|
958
|
+
factory.createContinueStatement(factory.createIdentifier('main_loop')),
|
|
959
|
+
];
|
|
960
|
+
}
|
|
961
|
+
// Return statement -> __r[1] = expr; break main_loop;
|
|
962
|
+
if (ts.isReturnStatement(stmt)) {
|
|
963
|
+
const returnExpr = stmt.expression
|
|
964
|
+
? rewriteExpression(factory, stmt.expression, rewriterCtx)
|
|
965
|
+
: factory.createIdentifier('undefined');
|
|
966
|
+
return [
|
|
967
|
+
// __r[0] = 0 (DONE)
|
|
968
|
+
factory.createExpressionStatement(factory.createBinaryExpression(factory.createElementAccessExpression(factory.createIdentifier('__r'), factory.createNumericLiteral(0)), ts.SyntaxKind.EqualsToken, factory.createNumericLiteral(0))),
|
|
969
|
+
// __r[1] = returnExpr
|
|
970
|
+
ts.setTextRange(factory.createExpressionStatement(factory.createBinaryExpression(factory.createElementAccessExpression(factory.createIdentifier('__r'), factory.createNumericLiteral(1)), ts.SyntaxKind.EqualsToken, returnExpr)), stmt),
|
|
971
|
+
// break main_loop
|
|
972
|
+
factory.createBreakStatement(factory.createIdentifier('main_loop')),
|
|
973
|
+
];
|
|
974
|
+
}
|
|
975
|
+
// If statement -> transform with rewritten condition and transformed body
|
|
976
|
+
if (ts.isIfStatement(stmt)) {
|
|
977
|
+
const condition = rewriteExpression(factory, stmt.expression, rewriterCtx);
|
|
978
|
+
// Transform then branch
|
|
979
|
+
const thenStatements = [];
|
|
980
|
+
if (ts.isBlock(stmt.thenStatement)) {
|
|
981
|
+
for (const s of stmt.thenStatement.statements) {
|
|
982
|
+
thenStatements.push(...transformStatement(factory, s, rewriterCtx));
|
|
983
|
+
}
|
|
984
|
+
}
|
|
985
|
+
else {
|
|
986
|
+
thenStatements.push(...transformStatement(factory, stmt.thenStatement, rewriterCtx));
|
|
987
|
+
}
|
|
988
|
+
// Transform else branch if exists
|
|
989
|
+
let elseStatement;
|
|
990
|
+
if (stmt.elseStatement) {
|
|
991
|
+
const elseStatements = [];
|
|
992
|
+
if (ts.isBlock(stmt.elseStatement)) {
|
|
993
|
+
for (const s of stmt.elseStatement.statements) {
|
|
994
|
+
elseStatements.push(...transformStatement(factory, s, rewriterCtx));
|
|
995
|
+
}
|
|
996
|
+
}
|
|
997
|
+
else if (ts.isIfStatement(stmt.elseStatement)) {
|
|
998
|
+
// else if - recursively transform
|
|
999
|
+
elseStatements.push(...transformStatement(factory, stmt.elseStatement, rewriterCtx));
|
|
1000
|
+
}
|
|
1001
|
+
else {
|
|
1002
|
+
elseStatements.push(...transformStatement(factory, stmt.elseStatement, rewriterCtx));
|
|
1003
|
+
}
|
|
1004
|
+
elseStatement = factory.createBlock(elseStatements, true);
|
|
1005
|
+
}
|
|
1006
|
+
return [
|
|
1007
|
+
ts.setTextRange(factory.createIfStatement(condition, factory.createBlock(thenStatements, true), elseStatement), stmt),
|
|
1008
|
+
];
|
|
1009
|
+
}
|
|
1010
|
+
// Other statements - just rewrite
|
|
1011
|
+
return [rewriteStatement(factory, stmt, rewriterCtx)];
|
|
1012
|
+
}
|
|
1013
|
+
/**
|
|
1014
|
+
* Find the step index that contains a given opcode index.
|
|
1015
|
+
*/
|
|
1016
|
+
function findStepIndexForOpcode(steps, opcodeIndex) {
|
|
1017
|
+
for (const step of steps) {
|
|
1018
|
+
if (opcodeIndex >= step.opcodeRange.start && opcodeIndex < step.opcodeRange.end) {
|
|
1019
|
+
return step.index;
|
|
1020
|
+
}
|
|
1021
|
+
}
|
|
1022
|
+
// If the opcode index is a boundary point, find the step that starts at that index
|
|
1023
|
+
const step = steps.find(s => s.opcodeRange.start === opcodeIndex);
|
|
1024
|
+
return step?.index ?? 0;
|
|
1025
|
+
}
|
|
1026
|
+
/**
|
|
1027
|
+
* Wrap an expression with `expr[Symbol.asyncIterator]()` to obtain an iterator from an iterable.
|
|
1028
|
+
*/
|
|
1029
|
+
function wrapWithAsyncIterator(factory, expr) {
|
|
1030
|
+
return factory.createCallExpression(factory.createElementAccessExpression(expr, factory.createPropertyAccessExpression(factory.createIdentifier('Symbol'), 'asyncIterator')), undefined, []);
|
|
1031
|
+
}
|
|
1032
|
+
/**
|
|
1033
|
+
* Generate statements for a single opcode within a step.
|
|
1034
|
+
*/
|
|
1035
|
+
function generateOpcodeStatements(factory, opcode, step, steps, analysis, rewriterCtx, opcodeIndex) {
|
|
1036
|
+
const statements = [];
|
|
1037
|
+
const { blocks, rehydrationBlocks } = analysis;
|
|
1038
|
+
switch (opcode.op) {
|
|
1039
|
+
case 'BLOCK': {
|
|
1040
|
+
const block = blocks[opcode.blockId];
|
|
1041
|
+
if (block && block.statements.length > 0) {
|
|
1042
|
+
// Extract variables declared in this block, excluding localVars which persist
|
|
1043
|
+
// blockLocalVars are for variables that DON'T need state.vars rewriting
|
|
1044
|
+
const allDeclaredVars = extractDeclaredVars(block.statements);
|
|
1045
|
+
const blockLocalVars = new Set(Array.from(allDeclaredVars).filter(v => !rewriterCtx.localVars.has(v)));
|
|
1046
|
+
const blockCtx = { ...rewriterCtx, blockLocalVars };
|
|
1047
|
+
// Rewrite and add each statement
|
|
1048
|
+
for (const stmt of block.statements) {
|
|
1049
|
+
// If statements - transform with special return handling
|
|
1050
|
+
if (ts.isIfStatement(stmt)) {
|
|
1051
|
+
statements.push(...transformStatement(factory, stmt, blockCtx));
|
|
1052
|
+
}
|
|
1053
|
+
// Return statements at block level - use __blockResult for later RETURN opcode
|
|
1054
|
+
else if (ts.isReturnStatement(stmt)) {
|
|
1055
|
+
const returnExpr = stmt.expression
|
|
1056
|
+
? rewriteExpression(factory, stmt.expression, blockCtx)
|
|
1057
|
+
: factory.createIdentifier('undefined');
|
|
1058
|
+
statements.push(ts.setTextRange(factory.createExpressionStatement(factory.createBinaryExpression(factory.createIdentifier('__blockResult'), ts.SyntaxKind.EqualsToken, returnExpr)), stmt));
|
|
1059
|
+
}
|
|
1060
|
+
// Variable statements - transform localVar declarations to state.vars assignments
|
|
1061
|
+
else if (ts.isVariableStatement(stmt)) {
|
|
1062
|
+
for (const decl of stmt.declarationList.declarations) {
|
|
1063
|
+
if (ts.isIdentifier(decl.name)) {
|
|
1064
|
+
const varName = decl.name.text;
|
|
1065
|
+
// LocalVar (but not raceVar) -> state.vars.xxx = initializer
|
|
1066
|
+
// These need to persist across suspension points
|
|
1067
|
+
// Race vars stay local (the result is in __raceResult after resumption)
|
|
1068
|
+
if (rewriterCtx.localVars.has(varName) && !rewriterCtx.raceVars.has(varName)) {
|
|
1069
|
+
const initializer = decl.initializer
|
|
1070
|
+
? rewriteExpression(factory, decl.initializer, blockCtx)
|
|
1071
|
+
: factory.createIdentifier('undefined');
|
|
1072
|
+
statements.push(ts.setTextRange(factory.createExpressionStatement(factory.createBinaryExpression(factory.createPropertyAccessExpression(factory.createPropertyAccessExpression(factory.createIdentifier('state'), 'vars'), varName), ts.SyntaxKind.EqualsToken, initializer)), stmt));
|
|
1073
|
+
continue;
|
|
1074
|
+
}
|
|
1075
|
+
}
|
|
1076
|
+
// Other declarations - keep as variable statement with rewritten initializer
|
|
1077
|
+
// This handles usingVars, destructuring, etc.
|
|
1078
|
+
const newInitializer = decl.initializer
|
|
1079
|
+
? rewriteExpression(factory, decl.initializer, blockCtx)
|
|
1080
|
+
: undefined;
|
|
1081
|
+
statements.push(factory.createVariableStatement(stmt.modifiers, factory.createVariableDeclarationList([factory.createVariableDeclaration(decl.name, decl.exclamationToken, decl.type, newInitializer)], stmt.declarationList.flags)));
|
|
1082
|
+
}
|
|
1083
|
+
}
|
|
1084
|
+
// Other statements - just rewrite
|
|
1085
|
+
else {
|
|
1086
|
+
statements.push(rewriteStatement(factory, stmt, blockCtx));
|
|
1087
|
+
}
|
|
1088
|
+
}
|
|
1089
|
+
}
|
|
1090
|
+
break;
|
|
1091
|
+
}
|
|
1092
|
+
case 'STORE': {
|
|
1093
|
+
// For race results: __raceResult = state.vars.__raceResult (load from state)
|
|
1094
|
+
// For block/signal results: state.vars.varName = __blockResult / ctx.signalPayload
|
|
1095
|
+
if (opcode.fromRace) {
|
|
1096
|
+
// Load race result from state.vars (set by executor) into local variable
|
|
1097
|
+
statements.push(factory.createExpressionStatement(factory.createBinaryExpression(factory.createIdentifier('__raceResult'), ts.SyntaxKind.EqualsToken, factory.createPropertyAccessExpression(factory.createPropertyAccessExpression(factory.createIdentifier('state'), 'vars'), '__raceResult'))));
|
|
1098
|
+
break;
|
|
1099
|
+
}
|
|
1100
|
+
// state.vars.varName = __blockResult / ctx.signalPayload
|
|
1101
|
+
let valueExpr;
|
|
1102
|
+
if (opcode.fromBlock) {
|
|
1103
|
+
valueExpr = factory.createIdentifier('__blockResult');
|
|
1104
|
+
}
|
|
1105
|
+
else if (opcode.fromSignal) {
|
|
1106
|
+
valueExpr = factory.createPropertyAccessExpression(factory.createIdentifier('ctx'), 'signalPayload');
|
|
1107
|
+
}
|
|
1108
|
+
else {
|
|
1109
|
+
valueExpr = factory.createIdentifier('undefined');
|
|
1110
|
+
}
|
|
1111
|
+
statements.push(factory.createExpressionStatement(factory.createBinaryExpression(factory.createPropertyAccessExpression(factory.createPropertyAccessExpression(factory.createIdentifier('state'), 'vars'), opcode.var), ts.SyntaxKind.EqualsToken, valueExpr)));
|
|
1112
|
+
break;
|
|
1113
|
+
}
|
|
1114
|
+
case 'REHYDRATE': {
|
|
1115
|
+
// Use the expression carried on the opcode (each branch site has its own
|
|
1116
|
+
// expression, so two sibling branches declaring `using v = X()` and `using v = Y()`
|
|
1117
|
+
// will each emit their own correct initializer rather than sharing the last-write
|
|
1118
|
+
// from the global rehydrationBlocks map).
|
|
1119
|
+
const expr = opcode.expression ?? rehydrationBlocks[opcode.var]?.expression;
|
|
1120
|
+
if (expr) {
|
|
1121
|
+
const rewrittenExpr = rewriteExpression(factory, expr, rewriterCtx);
|
|
1122
|
+
// varName = await ... (expression may already be an await, don't double-wrap)
|
|
1123
|
+
const awaitedExpr = ts.isAwaitExpression(rewrittenExpr)
|
|
1124
|
+
? rewrittenExpr
|
|
1125
|
+
: factory.createAwaitExpression(rewrittenExpr);
|
|
1126
|
+
statements.push(factory.createExpressionStatement(factory.createBinaryExpression(factory.createIdentifier(opcode.var), ts.SyntaxKind.EqualsToken, awaitedExpr)));
|
|
1127
|
+
// __dispose[__dispose_i++] = varName
|
|
1128
|
+
statements.push(factory.createExpressionStatement(factory.createBinaryExpression(factory.createElementAccessExpression(factory.createIdentifier('__dispose'), factory.createPostfixUnaryExpression(factory.createIdentifier('__dispose_i'), ts.SyntaxKind.PlusPlusToken)), ts.SyntaxKind.EqualsToken, factory.createIdentifier(opcode.var))));
|
|
1129
|
+
}
|
|
1130
|
+
break;
|
|
1131
|
+
}
|
|
1132
|
+
case 'WAIT': {
|
|
1133
|
+
// Find the resume step for this wait
|
|
1134
|
+
const resumeStepIndex = step.nextStep ?? step.index + 1;
|
|
1135
|
+
// step = resumeStep
|
|
1136
|
+
statements.push(factory.createExpressionStatement(factory.createBinaryExpression(factory.createIdentifier('step'), ts.SyntaxKind.EqualsToken, factory.createNumericLiteral(resumeStepIndex))));
|
|
1137
|
+
// __r[0] = 1 (SUSPEND)
|
|
1138
|
+
statements.push(factory.createExpressionStatement(factory.createBinaryExpression(factory.createElementAccessExpression(factory.createIdentifier('__r'), factory.createNumericLiteral(0)), ts.SyntaxKind.EqualsToken, factory.createNumericLiteral(1))));
|
|
1139
|
+
// __r[1] = { signal: '...' } or { timer: { ... } }
|
|
1140
|
+
let suspendConfig;
|
|
1141
|
+
if (opcode.signal === '__timer__') {
|
|
1142
|
+
// Timer suspend - use unit-based format: { timer: { seconds: 30 } }
|
|
1143
|
+
const timerProperties = opcode.timer
|
|
1144
|
+
? [
|
|
1145
|
+
factory.createPropertyAssignment(opcode.timer.unit, rewriteExpression(factory, cloneExpression(factory, opcode.timer.valueExpr), rewriterCtx)),
|
|
1146
|
+
]
|
|
1147
|
+
: [];
|
|
1148
|
+
suspendConfig = factory.createObjectLiteralExpression([
|
|
1149
|
+
factory.createPropertyAssignment('timer', factory.createObjectLiteralExpression(timerProperties)),
|
|
1150
|
+
]);
|
|
1151
|
+
}
|
|
1152
|
+
else if (opcode.signalExpr) {
|
|
1153
|
+
// Use the signal expression and access .signalName at runtime
|
|
1154
|
+
suspendConfig = factory.createObjectLiteralExpression([
|
|
1155
|
+
factory.createPropertyAssignment('signal', factory.createPropertyAccessExpression(rewriteExpression(factory, opcode.signalExpr, rewriterCtx), 'signalName')),
|
|
1156
|
+
]);
|
|
1157
|
+
}
|
|
1158
|
+
else {
|
|
1159
|
+
// Fallback to static signal name (legacy)
|
|
1160
|
+
suspendConfig = factory.createObjectLiteralExpression([
|
|
1161
|
+
factory.createPropertyAssignment('signal', factory.createStringLiteral(opcode.signal)),
|
|
1162
|
+
]);
|
|
1163
|
+
}
|
|
1164
|
+
statements.push(factory.createExpressionStatement(factory.createBinaryExpression(factory.createElementAccessExpression(factory.createIdentifier('__r'), factory.createNumericLiteral(1)), ts.SyntaxKind.EqualsToken, suspendConfig)));
|
|
1165
|
+
// break main_loop
|
|
1166
|
+
statements.push(factory.createBreakStatement(factory.createIdentifier('main_loop')));
|
|
1167
|
+
break;
|
|
1168
|
+
}
|
|
1169
|
+
case 'RACE_START': {
|
|
1170
|
+
// Generate race suspend config
|
|
1171
|
+
// Convert branch.jumpTarget (opcode index) to step index for each branch
|
|
1172
|
+
const raceBranches = opcode.branches.map(branch => {
|
|
1173
|
+
const branchStepIndex = findStepIndexForOpcode(steps, branch.jumpTarget);
|
|
1174
|
+
// For stream branches (stream:ModelName:*:fieldName), use the static signal name
|
|
1175
|
+
// For signal branches, access .signalName on the signal service call
|
|
1176
|
+
const isStreamBranch = branch.signal?.startsWith('stream:');
|
|
1177
|
+
const signalAssignment = branch.signalExpr
|
|
1178
|
+
? isStreamBranch
|
|
1179
|
+
? [
|
|
1180
|
+
// Stream: use the static signal name from analysis (with wildcard)
|
|
1181
|
+
factory.createPropertyAssignment('signal', factory.createStringLiteral(branch.signal)),
|
|
1182
|
+
]
|
|
1183
|
+
: [
|
|
1184
|
+
// Signal: access .signalName on the rewritten signal expression
|
|
1185
|
+
factory.createPropertyAssignment('signal', factory.createPropertyAccessExpression(rewriteExpression(factory, branch.signalExpr, rewriterCtx), 'signalName')),
|
|
1186
|
+
]
|
|
1187
|
+
: [];
|
|
1188
|
+
return factory.createObjectLiteralExpression([
|
|
1189
|
+
factory.createPropertyAssignment('id', factory.createStringLiteral(branch.id)),
|
|
1190
|
+
...signalAssignment,
|
|
1191
|
+
...(branch.timer
|
|
1192
|
+
? [
|
|
1193
|
+
factory.createPropertyAssignment('timer', factory.createObjectLiteralExpression([
|
|
1194
|
+
// Use unit-based format for persistence: { seconds: X } or { minutes: X }
|
|
1195
|
+
// This is human-readable in the database and can be recalculated on resume
|
|
1196
|
+
factory.createPropertyAssignment(branch.timer.unit, // 'seconds', 'minutes', 'hours', 'days'
|
|
1197
|
+
// Clone and rewrite the value expression (may contain state.vars references)
|
|
1198
|
+
rewriteExpression(factory, cloneExpression(factory, branch.timer.valueExpr), rewriterCtx)),
|
|
1199
|
+
])),
|
|
1200
|
+
]
|
|
1201
|
+
: []),
|
|
1202
|
+
factory.createPropertyAssignment('resumeStep', factory.createNumericLiteral(branchStepIndex)),
|
|
1203
|
+
]);
|
|
1204
|
+
});
|
|
1205
|
+
// Store for use by RACE_SUSPEND
|
|
1206
|
+
statements.push(factory.createExpressionStatement(factory.createBinaryExpression(factory.createPropertyAccessExpression(factory.createPropertyAccessExpression(factory.createIdentifier('state'), 'vars'), '__raceBranches'), ts.SyntaxKind.EqualsToken, factory.createArrayLiteralExpression(raceBranches, true))));
|
|
1207
|
+
break;
|
|
1208
|
+
}
|
|
1209
|
+
case 'RACE_SUSPEND': {
|
|
1210
|
+
// __r[0] = 1 (SUSPEND)
|
|
1211
|
+
statements.push(factory.createExpressionStatement(factory.createBinaryExpression(factory.createElementAccessExpression(factory.createIdentifier('__r'), factory.createNumericLiteral(0)), ts.SyntaxKind.EqualsToken, factory.createNumericLiteral(1))));
|
|
1212
|
+
// __r[1] = { race: state.vars.__raceBranches }
|
|
1213
|
+
statements.push(factory.createExpressionStatement(factory.createBinaryExpression(factory.createElementAccessExpression(factory.createIdentifier('__r'), factory.createNumericLiteral(1)), ts.SyntaxKind.EqualsToken, factory.createObjectLiteralExpression([
|
|
1214
|
+
factory.createPropertyAssignment('race', factory.createPropertyAccessExpression(factory.createPropertyAccessExpression(factory.createIdentifier('state'), 'vars'), '__raceBranches')),
|
|
1215
|
+
]))));
|
|
1216
|
+
// break main_loop
|
|
1217
|
+
statements.push(factory.createBreakStatement(factory.createIdentifier('main_loop')));
|
|
1218
|
+
break;
|
|
1219
|
+
}
|
|
1220
|
+
case 'JUMP': {
|
|
1221
|
+
// Find the step that contains the target opcode and convert to step index
|
|
1222
|
+
const targetStepIndex = findStepIndexForOpcode(steps, opcode.target);
|
|
1223
|
+
// step = targetStepIndex
|
|
1224
|
+
// continue main_loop
|
|
1225
|
+
statements.push(factory.createExpressionStatement(factory.createBinaryExpression(factory.createIdentifier('step'), ts.SyntaxKind.EqualsToken, factory.createNumericLiteral(targetStepIndex))));
|
|
1226
|
+
statements.push(factory.createContinueStatement(factory.createIdentifier('main_loop')));
|
|
1227
|
+
break;
|
|
1228
|
+
}
|
|
1229
|
+
case 'JUMP_IF': {
|
|
1230
|
+
// if (condition) { step = target; continue main_loop }
|
|
1231
|
+
// For now, we assume __condition is in state.vars
|
|
1232
|
+
const condition = opcode.condition === '__condition_false'
|
|
1233
|
+
? factory.createPrefixUnaryExpression(ts.SyntaxKind.ExclamationToken, factory.createPropertyAccessExpression(factory.createPropertyAccessExpression(factory.createIdentifier('state'), 'vars'), '__condition'))
|
|
1234
|
+
: factory.createPropertyAccessExpression(factory.createPropertyAccessExpression(factory.createIdentifier('state'), 'vars'), opcode.condition);
|
|
1235
|
+
// Convert opcode target index to step index
|
|
1236
|
+
const targetStepIndex = findStepIndexForOpcode(steps, opcode.target);
|
|
1237
|
+
statements.push(factory.createIfStatement(condition, factory.createBlock([
|
|
1238
|
+
factory.createExpressionStatement(factory.createBinaryExpression(factory.createIdentifier('step'), ts.SyntaxKind.EqualsToken, factory.createNumericLiteral(targetStepIndex))),
|
|
1239
|
+
factory.createContinueStatement(factory.createIdentifier('main_loop')),
|
|
1240
|
+
])));
|
|
1241
|
+
break;
|
|
1242
|
+
}
|
|
1243
|
+
case 'LABEL': {
|
|
1244
|
+
// Labels don't generate code - they're just markers for jump targets
|
|
1245
|
+
break;
|
|
1246
|
+
}
|
|
1247
|
+
case 'LABEL_ENTER': {
|
|
1248
|
+
// Initialize label tracking arrays if needed
|
|
1249
|
+
// state.labelStack = state.labelStack ?? []
|
|
1250
|
+
statements.push(factory.createExpressionStatement(factory.createBinaryExpression(factory.createPropertyAccessExpression(factory.createIdentifier('state'), 'labelStack'), ts.SyntaxKind.EqualsToken, factory.createBinaryExpression(factory.createPropertyAccessExpression(factory.createIdentifier('state'), 'labelStack'), ts.SyntaxKind.QuestionQuestionToken, factory.createArrayLiteralExpression([])))));
|
|
1251
|
+
// state.labelHistory = state.labelHistory ?? []
|
|
1252
|
+
statements.push(factory.createExpressionStatement(factory.createBinaryExpression(factory.createPropertyAccessExpression(factory.createIdentifier('state'), 'labelHistory'), ts.SyntaxKind.EqualsToken, factory.createBinaryExpression(factory.createPropertyAccessExpression(factory.createIdentifier('state'), 'labelHistory'), ts.SyntaxKind.QuestionQuestionToken, factory.createArrayLiteralExpression([])))));
|
|
1253
|
+
// state.labelStack.push('labelName')
|
|
1254
|
+
statements.push(factory.createExpressionStatement(factory.createCallExpression(factory.createPropertyAccessExpression(factory.createPropertyAccessExpression(factory.createIdentifier('state'), 'labelStack'), 'push'), undefined, [factory.createStringLiteral(opcode.label)])));
|
|
1255
|
+
// state.label = 'labelName'
|
|
1256
|
+
statements.push(factory.createExpressionStatement(factory.createBinaryExpression(factory.createPropertyAccessExpression(factory.createIdentifier('state'), 'label'), ts.SyntaxKind.EqualsToken, factory.createStringLiteral(opcode.label))));
|
|
1257
|
+
// state.labelHistory.push({ label: 'labelName', enteredAt: state.step })
|
|
1258
|
+
statements.push(factory.createExpressionStatement(factory.createCallExpression(factory.createPropertyAccessExpression(factory.createPropertyAccessExpression(factory.createIdentifier('state'), 'labelHistory'), 'push'), undefined, [
|
|
1259
|
+
factory.createObjectLiteralExpression([
|
|
1260
|
+
factory.createPropertyAssignment('label', factory.createStringLiteral(opcode.label)),
|
|
1261
|
+
factory.createPropertyAssignment('enteredAt', factory.createPropertyAccessExpression(factory.createIdentifier('state'), 'step')),
|
|
1262
|
+
]),
|
|
1263
|
+
])));
|
|
1264
|
+
// Keep history bounded: if (state.labelHistory.length > 50) state.labelHistory.shift()
|
|
1265
|
+
statements.push(factory.createIfStatement(factory.createBinaryExpression(factory.createPropertyAccessExpression(factory.createPropertyAccessExpression(factory.createIdentifier('state'), 'labelHistory'), 'length'), ts.SyntaxKind.GreaterThanToken, factory.createNumericLiteral(50)), factory.createExpressionStatement(factory.createCallExpression(factory.createPropertyAccessExpression(factory.createPropertyAccessExpression(factory.createIdentifier('state'), 'labelHistory'), 'shift'), undefined, []))));
|
|
1266
|
+
break;
|
|
1267
|
+
}
|
|
1268
|
+
case 'LABEL_EXIT': {
|
|
1269
|
+
// state.labelStack?.pop()
|
|
1270
|
+
statements.push(factory.createExpressionStatement(factory.createCallChain(factory.createPropertyAccessChain(factory.createPropertyAccessExpression(factory.createIdentifier('state'), 'labelStack'), factory.createToken(ts.SyntaxKind.QuestionDotToken), 'pop'), undefined, undefined, [])));
|
|
1271
|
+
// state.label = state.labelStack?.[state.labelStack.length - 1]
|
|
1272
|
+
statements.push(factory.createExpressionStatement(factory.createBinaryExpression(factory.createPropertyAccessExpression(factory.createIdentifier('state'), 'label'), ts.SyntaxKind.EqualsToken, factory.createElementAccessChain(factory.createPropertyAccessExpression(factory.createIdentifier('state'), 'labelStack'), factory.createToken(ts.SyntaxKind.QuestionDotToken), factory.createBinaryExpression(factory.createPropertyAccessExpression(factory.createPropertyAccessExpression(factory.createIdentifier('state'), 'labelStack'), 'length'), ts.SyntaxKind.MinusToken, factory.createNumericLiteral(1))))));
|
|
1273
|
+
// Find the last entry for this label in history and set exitedAt
|
|
1274
|
+
// const __lastEntry = state.labelHistory?.findLast(e => e.label === 'labelName' && e.exitedAt === undefined)
|
|
1275
|
+
// if (__lastEntry) __lastEntry.exitedAt = state.step
|
|
1276
|
+
const lastEntryVarName = `__lastEntry_${opcode.label.replace(/\W/g, '_')}`;
|
|
1277
|
+
statements.push(factory.createVariableStatement(undefined, factory.createVariableDeclarationList([
|
|
1278
|
+
factory.createVariableDeclaration(lastEntryVarName, undefined, undefined, factory.createCallChain(factory.createPropertyAccessChain(factory.createPropertyAccessExpression(factory.createIdentifier('state'), 'labelHistory'), factory.createToken(ts.SyntaxKind.QuestionDotToken), 'findLast'), undefined, undefined, [
|
|
1279
|
+
factory.createArrowFunction(undefined, undefined, [factory.createParameterDeclaration(undefined, undefined, 'e')], undefined, factory.createToken(ts.SyntaxKind.EqualsGreaterThanToken), factory.createBinaryExpression(factory.createBinaryExpression(factory.createPropertyAccessExpression(factory.createIdentifier('e'), 'label'), ts.SyntaxKind.EqualsEqualsEqualsToken, factory.createStringLiteral(opcode.label)), ts.SyntaxKind.AmpersandAmpersandToken, factory.createBinaryExpression(factory.createPropertyAccessExpression(factory.createIdentifier('e'), 'exitedAt'), ts.SyntaxKind.EqualsEqualsEqualsToken, factory.createIdentifier('undefined')))),
|
|
1280
|
+
])),
|
|
1281
|
+
], ts.NodeFlags.Const)));
|
|
1282
|
+
statements.push(factory.createIfStatement(factory.createIdentifier(lastEntryVarName), factory.createExpressionStatement(factory.createBinaryExpression(factory.createPropertyAccessExpression(factory.createIdentifier(lastEntryVarName), 'exitedAt'), ts.SyntaxKind.EqualsToken, factory.createPropertyAccessExpression(factory.createIdentifier('state'), 'step')))));
|
|
1283
|
+
break;
|
|
1284
|
+
}
|
|
1285
|
+
case 'RETURN': {
|
|
1286
|
+
// __r[0] = 0 (DONE)
|
|
1287
|
+
statements.push(factory.createExpressionStatement(factory.createBinaryExpression(factory.createElementAccessExpression(factory.createIdentifier('__r'), factory.createNumericLiteral(0)), ts.SyntaxKind.EqualsToken, factory.createNumericLiteral(0))));
|
|
1288
|
+
// __r[1] = result (from __blockResult or literal)
|
|
1289
|
+
// The return value comes from the preceding BLOCK opcode
|
|
1290
|
+
statements.push(factory.createExpressionStatement(factory.createBinaryExpression(factory.createElementAccessExpression(factory.createIdentifier('__r'), factory.createNumericLiteral(1)), ts.SyntaxKind.EqualsToken, factory.createIdentifier('__blockResult'))));
|
|
1291
|
+
// break main_loop
|
|
1292
|
+
statements.push(factory.createBreakStatement(factory.createIdentifier('main_loop')));
|
|
1293
|
+
break;
|
|
1294
|
+
}
|
|
1295
|
+
case 'YIELD_EMIT': {
|
|
1296
|
+
// Emit a value without suspending: ctx.emit(rewrittenValueExpr)
|
|
1297
|
+
const emitValue = rewriteExpression(factory, opcode.valueExpr, rewriterCtx);
|
|
1298
|
+
statements.push(factory.createExpressionStatement(factory.createCallExpression(factory.createPropertyAccessExpression(factory.createIdentifier('ctx'), 'emit'), undefined, [emitValue])));
|
|
1299
|
+
break;
|
|
1300
|
+
}
|
|
1301
|
+
case 'SCOPE_ENTER':
|
|
1302
|
+
case 'SCOPE_EXIT': {
|
|
1303
|
+
// Scope opcodes are handled implicitly by the using var cleanup
|
|
1304
|
+
break;
|
|
1305
|
+
}
|
|
1306
|
+
case 'SCOPE_START': {
|
|
1307
|
+
// Evaluate entities and store them in state vars for the executor
|
|
1308
|
+
// Generated: state.vars.__scope_N_entities = Array.from(rewrittenIterableExpr)
|
|
1309
|
+
const entitiesExpr = rewriteExpression(factory, opcode.iterableExpr, rewriterCtx);
|
|
1310
|
+
statements.push(factory.createExpressionStatement(factory.createBinaryExpression(factory.createElementAccessExpression(factory.createPropertyAccessExpression(factory.createIdentifier('state'), 'vars'), factory.createStringLiteral(`__scope_${opcode.scopeId}_entities`)), ts.SyntaxKind.EqualsToken, factory.createCallExpression(factory.createPropertyAccessExpression(factory.createIdentifier('Array'), 'from'), undefined, [entitiesExpr]))));
|
|
1311
|
+
// Store idExtractor if provided
|
|
1312
|
+
// Generated: state.vars.__scope_N_idFn = rewrittenIdExtractor
|
|
1313
|
+
if (opcode.idExtractor) {
|
|
1314
|
+
const idExpr = rewriteExpression(factory, opcode.idExtractor, rewriterCtx);
|
|
1315
|
+
statements.push(factory.createExpressionStatement(factory.createBinaryExpression(factory.createElementAccessExpression(factory.createPropertyAccessExpression(factory.createIdentifier('state'), 'vars'), factory.createStringLiteral(`__scope_${opcode.scopeId}_idFn`)), ts.SyntaxKind.EqualsToken, idExpr)));
|
|
1316
|
+
}
|
|
1317
|
+
// Store param alias if provided
|
|
1318
|
+
if (opcode.paramAlias) {
|
|
1319
|
+
statements.push(factory.createExpressionStatement(factory.createBinaryExpression(factory.createElementAccessExpression(factory.createPropertyAccessExpression(factory.createIdentifier('state'), 'vars'), factory.createStringLiteral(`__scope_${opcode.scopeId}_alias`)), ts.SyntaxKind.EqualsToken, factory.createStringLiteral(opcode.paramAlias))));
|
|
1320
|
+
}
|
|
1321
|
+
break;
|
|
1322
|
+
}
|
|
1323
|
+
case 'SCOPE_WAIT': {
|
|
1324
|
+
// Signal-first form: suspend with scope config
|
|
1325
|
+
const signalExpr = rewriteExpression(factory, opcode.signalExpr, rewriterCtx);
|
|
1326
|
+
const resumeStepIndex = step.nextStep ?? step.index + 1;
|
|
1327
|
+
// step = resumeStep
|
|
1328
|
+
statements.push(factory.createExpressionStatement(factory.createBinaryExpression(factory.createIdentifier('step'), ts.SyntaxKind.EqualsToken, factory.createNumericLiteral(resumeStepIndex))));
|
|
1329
|
+
// __r[0] = 1 (SUSPEND)
|
|
1330
|
+
statements.push(factory.createExpressionStatement(factory.createBinaryExpression(factory.createElementAccessExpression(factory.createIdentifier('__r'), factory.createNumericLiteral(0)), ts.SyntaxKind.EqualsToken, factory.createNumericLiteral(1))));
|
|
1331
|
+
// __r[1] = { scope: { scopeId, type: 'signal', signal, resumeStep } }
|
|
1332
|
+
statements.push(factory.createExpressionStatement(factory.createBinaryExpression(factory.createElementAccessExpression(factory.createIdentifier('__r'), factory.createNumericLiteral(1)), ts.SyntaxKind.EqualsToken, factory.createObjectLiteralExpression([
|
|
1333
|
+
factory.createPropertyAssignment('scope', factory.createObjectLiteralExpression([
|
|
1334
|
+
factory.createPropertyAssignment('scopeId', factory.createNumericLiteral(opcode.scopeId)),
|
|
1335
|
+
factory.createPropertyAssignment('type', factory.createStringLiteral('signal')),
|
|
1336
|
+
factory.createPropertyAssignment('signal', factory.createPropertyAccessExpression(signalExpr, 'signalName')),
|
|
1337
|
+
factory.createPropertyAssignment('resumeStep', factory.createNumericLiteral(resumeStepIndex)),
|
|
1338
|
+
])),
|
|
1339
|
+
]))));
|
|
1340
|
+
// break main_loop
|
|
1341
|
+
statements.push(factory.createBreakStatement(factory.createIdentifier('main_loop')));
|
|
1342
|
+
break;
|
|
1343
|
+
}
|
|
1344
|
+
case 'SCOPE_HANDLER': {
|
|
1345
|
+
// Handler form: suspend with scope config
|
|
1346
|
+
const resumeStepIndex = step.nextStep ?? step.index + 1;
|
|
1347
|
+
// step = resumeStep
|
|
1348
|
+
statements.push(factory.createExpressionStatement(factory.createBinaryExpression(factory.createIdentifier('step'), ts.SyntaxKind.EqualsToken, factory.createNumericLiteral(resumeStepIndex))));
|
|
1349
|
+
// __r[0] = 1 (SUSPEND)
|
|
1350
|
+
statements.push(factory.createExpressionStatement(factory.createBinaryExpression(factory.createElementAccessExpression(factory.createIdentifier('__r'), factory.createNumericLiteral(0)), ts.SyntaxKind.EqualsToken, factory.createNumericLiteral(1))));
|
|
1351
|
+
// __r[1] = { scope: { scopeId, type: 'handler', resumeStep } }
|
|
1352
|
+
statements.push(factory.createExpressionStatement(factory.createBinaryExpression(factory.createElementAccessExpression(factory.createIdentifier('__r'), factory.createNumericLiteral(1)), ts.SyntaxKind.EqualsToken, factory.createObjectLiteralExpression([
|
|
1353
|
+
factory.createPropertyAssignment('scope', factory.createObjectLiteralExpression([
|
|
1354
|
+
factory.createPropertyAssignment('scopeId', factory.createNumericLiteral(opcode.scopeId)),
|
|
1355
|
+
factory.createPropertyAssignment('type', factory.createStringLiteral('handler')),
|
|
1356
|
+
factory.createPropertyAssignment('resumeStep', factory.createNumericLiteral(resumeStepIndex)),
|
|
1357
|
+
])),
|
|
1358
|
+
]))));
|
|
1359
|
+
// break main_loop
|
|
1360
|
+
statements.push(factory.createBreakStatement(factory.createIdentifier('main_loop')));
|
|
1361
|
+
break;
|
|
1362
|
+
}
|
|
1363
|
+
case 'SCOPE_NEXT': {
|
|
1364
|
+
// Not used in current codegen - per-entity iteration handled by executor
|
|
1365
|
+
break;
|
|
1366
|
+
}
|
|
1367
|
+
case 'SCOPE_END': {
|
|
1368
|
+
// Collect scope results from state vars
|
|
1369
|
+
// Generated: state.vars.resultVar = state.vars.__scope_N_results
|
|
1370
|
+
statements.push(factory.createExpressionStatement(factory.createBinaryExpression(factory.createElementAccessExpression(factory.createPropertyAccessExpression(factory.createIdentifier('state'), 'vars'), factory.createStringLiteral(opcode.resultVar)), ts.SyntaxKind.EqualsToken, factory.createElementAccessExpression(factory.createPropertyAccessExpression(factory.createIdentifier('state'), 'vars'), factory.createStringLiteral(`__scope_${opcode.scopeId}_results`)))));
|
|
1371
|
+
break;
|
|
1372
|
+
}
|
|
1373
|
+
case 'ITER_START': {
|
|
1374
|
+
// Initialize iterator from iterable expression
|
|
1375
|
+
// const __iter_N = state.vars.__cursor_N
|
|
1376
|
+
// ? iterable[FromCursor](state.vars.__cursor_N)
|
|
1377
|
+
// : createDurableArrayIterator(iterable)
|
|
1378
|
+
const iterVarName = `__iter_${opcode.loopId}`;
|
|
1379
|
+
// First, store the iterable expression in a temp var
|
|
1380
|
+
const iterableVarName = `__iterable_${opcode.loopId}`;
|
|
1381
|
+
const rewrittenIterable = rewriteExpression(factory, opcode.iterableExpr, rewriterCtx);
|
|
1382
|
+
statements.push(factory.createVariableStatement(undefined, factory.createVariableDeclarationList([factory.createVariableDeclaration(iterableVarName, undefined, undefined, rewrittenIterable)], ts.NodeFlags.Const)));
|
|
1383
|
+
// Check if cursor exists in state.vars, if so use FromCursor, otherwise create new
|
|
1384
|
+
// const __iter_N = state.vars.__cursor_N !== undefined
|
|
1385
|
+
// ? (__iterable_N[FromCursor] ? __iterable_N[FromCursor](state.vars.__cursor_N) : createDurableArrayIterator(__iterable_N, state.vars.__cursor_N))
|
|
1386
|
+
// : (__iterable_N[FromCursor] ? __iterable_N : createDurableArrayIterator(__iterable_N))
|
|
1387
|
+
const cursorAccess = factory.createPropertyAccessExpression(factory.createPropertyAccessExpression(factory.createIdentifier('state'), 'vars'), opcode.cursorVar);
|
|
1388
|
+
// Symbol.for('justscale:FromCursor') is emitted into user code (cross-realm) - keep Symbol.for.
|
|
1389
|
+
const fromCursorSymbolExpr = factory.createCallExpression(factory.createPropertyAccessExpression(factory.createIdentifier('Symbol'), 'for'), undefined, [factory.createStringLiteral('justscale:FromCursor')]);
|
|
1390
|
+
const hasFromCursor = factory.createBinaryExpression(fromCursorSymbolExpr, ts.SyntaxKind.InKeyword, factory.createIdentifier(iterableVarName));
|
|
1391
|
+
// If cursor exists
|
|
1392
|
+
const withCursor = factory.createConditionalExpression(hasFromCursor, factory.createToken(ts.SyntaxKind.QuestionToken),
|
|
1393
|
+
// Iterable has FromCursor - use it, then get iterator
|
|
1394
|
+
wrapWithAsyncIterator(factory, factory.createCallExpression(factory.createElementAccessExpression(factory.createIdentifier(iterableVarName), factory.createCallExpression(factory.createPropertyAccessExpression(factory.createIdentifier('Symbol'), 'for'), undefined, [factory.createStringLiteral('justscale:FromCursor')])), undefined, [cursorAccess])), factory.createToken(ts.SyntaxKind.ColonToken),
|
|
1395
|
+
// Array - wrap with DurableArrayIterator
|
|
1396
|
+
factory.createNewExpression(factory.createIdentifier('DurableArrayIterator'), undefined, [factory.createIdentifier(iterableVarName), cursorAccess]));
|
|
1397
|
+
// If no cursor exists
|
|
1398
|
+
const withoutCursor = factory.createConditionalExpression(hasFromCursor, factory.createToken(ts.SyntaxKind.QuestionToken),
|
|
1399
|
+
// Iterable has FromCursor - get iterator from iterable
|
|
1400
|
+
wrapWithAsyncIterator(factory, factory.createIdentifier(iterableVarName)), factory.createToken(ts.SyntaxKind.ColonToken),
|
|
1401
|
+
// Array - wrap with DurableArrayIterator
|
|
1402
|
+
factory.createNewExpression(factory.createIdentifier('DurableArrayIterator'), undefined, [factory.createIdentifier(iterableVarName)]));
|
|
1403
|
+
// Complete conditional
|
|
1404
|
+
const iteratorInit = factory.createConditionalExpression(factory.createBinaryExpression(cursorAccess, ts.SyntaxKind.ExclamationEqualsEqualsToken, factory.createIdentifier('undefined')), factory.createToken(ts.SyntaxKind.QuestionToken), withCursor, factory.createToken(ts.SyntaxKind.ColonToken), withoutCursor);
|
|
1405
|
+
statements.push(factory.createVariableStatement(undefined, factory.createVariableDeclarationList([factory.createVariableDeclaration(iterVarName, undefined, undefined, iteratorInit)], ts.NodeFlags.Const)));
|
|
1406
|
+
break;
|
|
1407
|
+
}
|
|
1408
|
+
case 'ITER_NEXT': {
|
|
1409
|
+
// Fetch next item from iterator
|
|
1410
|
+
// const { value, done } = await __iter_N.next()
|
|
1411
|
+
// if (done) { step = doneTarget; continue main_loop }
|
|
1412
|
+
// state.vars.itemVar = value
|
|
1413
|
+
const iterVarName = `__iter_${opcode.loopId}`;
|
|
1414
|
+
const resultVarName = `__iterResult_${opcode.loopId}`;
|
|
1415
|
+
// const __iterResult_N = await __iter_N.next()
|
|
1416
|
+
statements.push(factory.createVariableStatement(undefined, factory.createVariableDeclarationList([
|
|
1417
|
+
factory.createVariableDeclaration(resultVarName, undefined, undefined, factory.createAwaitExpression(factory.createCallExpression(factory.createPropertyAccessExpression(factory.createIdentifier(iterVarName), 'next'), undefined, []))),
|
|
1418
|
+
], ts.NodeFlags.Const)));
|
|
1419
|
+
// if (__iterResult_N.done) { step = doneTarget; continue main_loop }
|
|
1420
|
+
const doneTarget = findStepIndexForOpcode(steps, opcode.doneTarget);
|
|
1421
|
+
statements.push(factory.createIfStatement(factory.createPropertyAccessExpression(factory.createIdentifier(resultVarName), 'done'), factory.createBlock([
|
|
1422
|
+
factory.createExpressionStatement(factory.createBinaryExpression(factory.createIdentifier('step'), ts.SyntaxKind.EqualsToken, factory.createNumericLiteral(doneTarget))),
|
|
1423
|
+
factory.createContinueStatement(factory.createIdentifier('main_loop')),
|
|
1424
|
+
])));
|
|
1425
|
+
// state.vars.itemVar = __iterResult_N.value
|
|
1426
|
+
// Find the ITER_START opcode to get the itemVar name
|
|
1427
|
+
let itemVarName = '__item';
|
|
1428
|
+
for (let j = opcodeIndex - 1; j >= 0; j--) {
|
|
1429
|
+
const prevOp = analysis.opcodes[j];
|
|
1430
|
+
if (prevOp.op === 'ITER_START' && prevOp.loopId === opcode.loopId) {
|
|
1431
|
+
itemVarName = prevOp.itemVar;
|
|
1432
|
+
break;
|
|
1433
|
+
}
|
|
1434
|
+
}
|
|
1435
|
+
statements.push(factory.createExpressionStatement(factory.createBinaryExpression(factory.createPropertyAccessExpression(factory.createPropertyAccessExpression(factory.createIdentifier('state'), 'vars'), itemVarName), ts.SyntaxKind.EqualsToken, factory.createPropertyAccessExpression(factory.createIdentifier(resultVarName), 'value'))));
|
|
1436
|
+
break;
|
|
1437
|
+
}
|
|
1438
|
+
case 'ITER_SAVE': {
|
|
1439
|
+
// Save cursor position before suspension
|
|
1440
|
+
// state.vars.__cursor_N = __iter_N[DurableCursor]?.() ?? state.vars.__cursor_N
|
|
1441
|
+
const iterVarName = `__iter_${opcode.loopId}`;
|
|
1442
|
+
// Try to get cursor from iterator if it supports DurableCursor
|
|
1443
|
+
const getCursor = factory.createCallChain(factory.createElementAccessChain(factory.createIdentifier(iterVarName), factory.createToken(ts.SyntaxKind.QuestionDotToken), factory.createCallExpression(factory.createPropertyAccessExpression(factory.createIdentifier('Symbol'), 'for'), undefined, [factory.createStringLiteral('justscale:DurableCursor')])), undefined, undefined, []);
|
|
1444
|
+
statements.push(factory.createExpressionStatement(factory.createBinaryExpression(factory.createPropertyAccessExpression(factory.createPropertyAccessExpression(factory.createIdentifier('state'), 'vars'), opcode.cursorVar), ts.SyntaxKind.EqualsToken, factory.createBinaryExpression(getCursor, ts.SyntaxKind.QuestionQuestionToken, factory.createPropertyAccessExpression(factory.createPropertyAccessExpression(factory.createIdentifier('state'), 'vars'), opcode.cursorVar)))));
|
|
1445
|
+
break;
|
|
1446
|
+
}
|
|
1447
|
+
case 'PARALLEL_START': {
|
|
1448
|
+
// Initialize parallel context in state.vars (persisted across suspension)
|
|
1449
|
+
// state.vars.__parallel_N = { pending: N, results: [], errors: [], isSettled: bool }
|
|
1450
|
+
const parallelVarName = `__parallel_${opcode.parallelId}`;
|
|
1451
|
+
const branchCount = opcode.branches.length;
|
|
1452
|
+
// Create parallel context initialization
|
|
1453
|
+
statements.push(factory.createExpressionStatement(factory.createBinaryExpression(factory.createPropertyAccessExpression(factory.createPropertyAccessExpression(factory.createIdentifier('state'), 'vars'), parallelVarName), ts.SyntaxKind.EqualsToken, factory.createObjectLiteralExpression([
|
|
1454
|
+
factory.createPropertyAssignment('parallelId', factory.createNumericLiteral(opcode.parallelId)),
|
|
1455
|
+
factory.createPropertyAssignment('pending', factory.createNumericLiteral(branchCount)),
|
|
1456
|
+
factory.createPropertyAssignment('results', factory.createArrayLiteralExpression([])),
|
|
1457
|
+
factory.createPropertyAssignment('errors', factory.createArrayLiteralExpression([])),
|
|
1458
|
+
factory.createPropertyAssignment('isSettled', opcode.isSettled ? factory.createTrue() : factory.createFalse()),
|
|
1459
|
+
factory.createPropertyAssignment('branches', factory.createArrayLiteralExpression(opcode.branches.map((branch) => factory.createObjectLiteralExpression([
|
|
1460
|
+
factory.createPropertyAssignment('id', typeof branch.id === 'number'
|
|
1461
|
+
? factory.createNumericLiteral(branch.id)
|
|
1462
|
+
: factory.createStringLiteral(branch.id)),
|
|
1463
|
+
factory.createPropertyAssignment('type', factory.createStringLiteral(branch.type)),
|
|
1464
|
+
factory.createPropertyAssignment('expr', rewriteExpression(factory, cloneExpression(factory, branch.expr), rewriterCtx)),
|
|
1465
|
+
])))),
|
|
1466
|
+
], true))));
|
|
1467
|
+
break;
|
|
1468
|
+
}
|
|
1469
|
+
case 'PARALLEL_WAIT': {
|
|
1470
|
+
// Subscribe to all branches and suspend
|
|
1471
|
+
// This is a suspension point - we'll return SUSPEND with parallel info
|
|
1472
|
+
const parallelVarName = `__parallel_${opcode.parallelId}`;
|
|
1473
|
+
// Set state.step to the resume step (PARALLEL_COLLECT is next)
|
|
1474
|
+
statements.push(factory.createExpressionStatement(factory.createBinaryExpression(factory.createPropertyAccessExpression(factory.createIdentifier('state'), 'step'), ts.SyntaxKind.EqualsToken, factory.createBinaryExpression(factory.createIdentifier('step'), ts.SyntaxKind.PlusToken, factory.createNumericLiteral(1)))));
|
|
1475
|
+
// __r[0] = SUSPEND
|
|
1476
|
+
statements.push(factory.createExpressionStatement(factory.createBinaryExpression(factory.createElementAccessExpression(factory.createIdentifier('__r'), 0), ts.SyntaxKind.EqualsToken, factory.createIdentifier('SUSPEND'))));
|
|
1477
|
+
// __r[1] = { parallel: state.vars.__parallel_N }
|
|
1478
|
+
statements.push(factory.createExpressionStatement(factory.createBinaryExpression(factory.createElementAccessExpression(factory.createIdentifier('__r'), 1), ts.SyntaxKind.EqualsToken, factory.createObjectLiteralExpression([
|
|
1479
|
+
factory.createPropertyAssignment('parallel', factory.createPropertyAccessExpression(factory.createPropertyAccessExpression(factory.createIdentifier('state'), 'vars'), parallelVarName)),
|
|
1480
|
+
]))));
|
|
1481
|
+
// break main_loop (use same pattern as WAIT/RACE_SUSPEND)
|
|
1482
|
+
statements.push(factory.createBreakStatement(factory.createIdentifier('main_loop')));
|
|
1483
|
+
break;
|
|
1484
|
+
}
|
|
1485
|
+
case 'PARALLEL_COLLECT': {
|
|
1486
|
+
// Collect results from parallel execution
|
|
1487
|
+
// The results are stored in state.vars.__parallel_N.results by the runtime
|
|
1488
|
+
const parallelVarName = `__parallel_${opcode.parallelId}`;
|
|
1489
|
+
// Helper: generates state.vars.__parallel_N
|
|
1490
|
+
const stateVarsParallel = () => factory.createPropertyAccessExpression(factory.createPropertyAccessExpression(factory.createIdentifier('state'), 'vars'), parallelVarName);
|
|
1491
|
+
if (opcode.isObject) {
|
|
1492
|
+
// Object form: convert array results to object using branch IDs as keys
|
|
1493
|
+
// state.vars.result = Object.fromEntries(state.vars.__parallel_N.branches.map((b, i) => [b.id, state.vars.__parallel_N.results[i]]))
|
|
1494
|
+
statements.push(factory.createExpressionStatement(factory.createBinaryExpression(factory.createPropertyAccessExpression(factory.createPropertyAccessExpression(factory.createIdentifier('state'), 'vars'), opcode.resultVar), ts.SyntaxKind.EqualsToken, factory.createCallExpression(factory.createPropertyAccessExpression(factory.createIdentifier('Object'), 'fromEntries'), undefined, [
|
|
1495
|
+
factory.createCallExpression(factory.createPropertyAccessExpression(factory.createPropertyAccessExpression(stateVarsParallel(), 'branches'), 'map'), undefined, [
|
|
1496
|
+
factory.createArrowFunction(undefined, undefined, [
|
|
1497
|
+
factory.createParameterDeclaration(undefined, undefined, 'b'),
|
|
1498
|
+
factory.createParameterDeclaration(undefined, undefined, 'i'),
|
|
1499
|
+
], undefined, factory.createToken(ts.SyntaxKind.EqualsGreaterThanToken), factory.createArrayLiteralExpression([
|
|
1500
|
+
factory.createPropertyAccessExpression(factory.createIdentifier('b'), 'id'),
|
|
1501
|
+
factory.createElementAccessExpression(factory.createPropertyAccessExpression(stateVarsParallel(), 'results'), factory.createIdentifier('i')),
|
|
1502
|
+
])),
|
|
1503
|
+
]),
|
|
1504
|
+
]))));
|
|
1505
|
+
}
|
|
1506
|
+
else {
|
|
1507
|
+
// Array form: directly assign results array
|
|
1508
|
+
// state.vars.result = state.vars.__parallel_N.results
|
|
1509
|
+
statements.push(factory.createExpressionStatement(factory.createBinaryExpression(factory.createPropertyAccessExpression(factory.createPropertyAccessExpression(factory.createIdentifier('state'), 'vars'), opcode.resultVar), ts.SyntaxKind.EqualsToken, factory.createPropertyAccessExpression(stateVarsParallel(), 'results'))));
|
|
1510
|
+
}
|
|
1511
|
+
break;
|
|
1512
|
+
}
|
|
1513
|
+
case 'SUBPROCESS_SPAWN': {
|
|
1514
|
+
// Set step to resume point
|
|
1515
|
+
const resumeStepIndex = step.nextStep ?? step.index + 1;
|
|
1516
|
+
statements.push(factory.createExpressionStatement(factory.createBinaryExpression(factory.createIdentifier('step'), ts.SyntaxKind.EqualsToken, factory.createNumericLiteral(resumeStepIndex))));
|
|
1517
|
+
// __r[0] = 2 (SUBPROCESS)
|
|
1518
|
+
statements.push(factory.createExpressionStatement(factory.createBinaryExpression(factory.createElementAccessExpression(factory.createIdentifier('__r'), factory.createNumericLiteral(0)), ts.SyntaxKind.EqualsToken, factory.createNumericLiteral(2))));
|
|
1519
|
+
// __r[1] = { name: '...', args: [...], storeVar: '...', awaited: true }
|
|
1520
|
+
const spawnProperties = [
|
|
1521
|
+
factory.createPropertyAssignment('name', factory.createStringLiteral(opcode.name)),
|
|
1522
|
+
factory.createPropertyAssignment('args', factory.createArrayLiteralExpression(opcode.argExprs.map((arg) => rewriteExpression(factory, cloneExpression(factory, arg), rewriterCtx)))),
|
|
1523
|
+
factory.createPropertyAssignment('awaited', opcode.awaited ? factory.createTrue() : factory.createFalse()),
|
|
1524
|
+
];
|
|
1525
|
+
if (opcode.storeVar) {
|
|
1526
|
+
spawnProperties.push(factory.createPropertyAssignment('storeVar', factory.createStringLiteral(opcode.storeVar)));
|
|
1527
|
+
}
|
|
1528
|
+
statements.push(factory.createExpressionStatement(factory.createBinaryExpression(factory.createElementAccessExpression(factory.createIdentifier('__r'), factory.createNumericLiteral(1)), ts.SyntaxKind.EqualsToken, factory.createObjectLiteralExpression(spawnProperties))));
|
|
1529
|
+
// break main_loop
|
|
1530
|
+
statements.push(factory.createBreakStatement(factory.createIdentifier('main_loop')));
|
|
1531
|
+
break;
|
|
1532
|
+
}
|
|
1533
|
+
case 'SUBPROCESS_DECL': {
|
|
1534
|
+
// Declaration opcodes don't generate runtime code - they're metadata only
|
|
1535
|
+
break;
|
|
1536
|
+
}
|
|
1537
|
+
}
|
|
1538
|
+
return statements;
|
|
1539
|
+
}
|
|
1540
|
+
//# sourceMappingURL=switch-codegen.js.map
|