ts2workflows 0.11.0 → 0.13.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (50) hide show
  1. package/CHANGELOG.md +117 -0
  2. package/README.md +17 -7
  3. package/dist/ast/expressions.d.ts +37 -41
  4. package/dist/ast/expressions.d.ts.map +1 -1
  5. package/dist/ast/expressions.js +124 -255
  6. package/dist/ast/statements.d.ts +132 -0
  7. package/dist/ast/statements.d.ts.map +1 -0
  8. package/dist/ast/statements.js +197 -0
  9. package/dist/ast/steps.d.ts +82 -194
  10. package/dist/ast/steps.d.ts.map +1 -1
  11. package/dist/ast/steps.js +862 -523
  12. package/dist/ast/workflows.d.ts +14 -7
  13. package/dist/ast/workflows.d.ts.map +1 -1
  14. package/dist/ast/workflows.js +19 -10
  15. package/dist/cli.js +45 -32
  16. package/dist/errors.d.ts +4 -0
  17. package/dist/errors.d.ts.map +1 -1
  18. package/dist/errors.js +7 -0
  19. package/dist/transpiler/index.d.ts +14 -1
  20. package/dist/transpiler/index.d.ts.map +1 -1
  21. package/dist/transpiler/index.js +150 -31
  22. package/dist/transpiler/linker.d.ts +3 -0
  23. package/dist/transpiler/linker.d.ts.map +1 -0
  24. package/dist/transpiler/linker.js +45 -0
  25. package/dist/transpiler/parseexpressions.d.ts +11 -0
  26. package/dist/transpiler/parseexpressions.d.ts.map +1 -0
  27. package/dist/transpiler/{expressions.js → parseexpressions.js} +90 -103
  28. package/dist/transpiler/parsestatement.d.ts +7 -0
  29. package/dist/transpiler/parsestatement.d.ts.map +1 -0
  30. package/dist/transpiler/parsestatement.js +862 -0
  31. package/dist/transpiler/stepnames.d.ts +3 -0
  32. package/dist/transpiler/stepnames.d.ts.map +1 -0
  33. package/dist/transpiler/stepnames.js +17 -0
  34. package/dist/transpiler/transformations.d.ts +3 -19
  35. package/dist/transpiler/transformations.d.ts.map +1 -1
  36. package/dist/transpiler/transformations.js +267 -322
  37. package/language_reference.md +8 -2
  38. package/package.json +13 -7
  39. package/types/workflowslib.d.ts +26 -13
  40. package/dist/ast/stepnames.d.ts +0 -9
  41. package/dist/ast/stepnames.d.ts.map +0 -1
  42. package/dist/ast/stepnames.js +0 -280
  43. package/dist/transpiler/expressions.d.ts +0 -11
  44. package/dist/transpiler/expressions.d.ts.map +0 -1
  45. package/dist/transpiler/statements.d.ts +0 -10
  46. package/dist/transpiler/statements.d.ts.map +0 -1
  47. package/dist/transpiler/statements.js +0 -1098
  48. package/dist/utils.d.ts +0 -2
  49. package/dist/utils.d.ts.map +0 -1
  50. package/dist/utils.js +0 -3
package/dist/ast/steps.js CHANGED
@@ -1,540 +1,48 @@
1
1
  import * as R from 'ramda';
2
- import { isRecord } from '../utils.js';
3
- import { expressionToLiteralValueOrLiteralExpression, } from './expressions.js';
2
+ import { InternalTranspilingError } from '../errors.js';
3
+ import { binaryEx, expressionToLiteralValueOrLiteralExpression, expressionToString, nullEx, stringEx, variableReferenceEx, } from './expressions.js';
4
4
  import { Subworkflow } from './workflows.js';
5
- export class SubworkflowAST {
6
- name;
7
- steps;
8
- params;
9
- constructor(name, steps, params) {
10
- this.name = name;
11
- this.steps = steps;
12
- this.params = params;
13
- }
14
- withStepNames(generate) {
15
- const steps = this.steps.map((step) => namedSteps(step, generate));
16
- return new Subworkflow(this.name, steps, this.params);
17
- }
18
- }
19
- // https://cloud.google.com/workflows/docs/reference/syntax/variables#assign-step
20
- export class AssignStepAST {
21
- tag = 'assign';
22
- assignments;
23
- label;
24
- next;
25
- constructor(assignments, next, label) {
26
- this.assignments = assignments;
27
- this.next = next;
28
- this.label = label;
29
- }
30
- withNext(newNext) {
31
- if (newNext === this.next) {
32
- return this;
33
- }
34
- else {
35
- return new AssignStepAST(this.assignments, newNext, this.label);
36
- }
37
- }
38
- withLabel(newLabel) {
39
- return new AssignStepAST(this.assignments, this.next, newLabel);
40
- }
41
- applyNestedSteps() {
42
- return this;
43
- }
44
- }
45
- // https://cloud.google.com/workflows/docs/reference/syntax/calls
46
- export class CallStepAST {
47
- tag = 'call';
48
- call;
49
- args;
50
- result;
51
- label;
52
- constructor(call, args, result, label) {
53
- this.call = call;
54
- this.args = args;
55
- this.result = result;
56
- this.label = label;
57
- }
58
- labelPrefix() {
59
- return 'call_' + this.call.replaceAll(/[^a-zA-Z0-9]/g, '_') + '_';
60
- }
61
- withLabel(newLabel) {
62
- return new CallStepAST(this.call, this.args, this.result, newLabel);
63
- }
64
- applyNestedSteps() {
65
- return this;
66
- }
67
- }
68
- // https://cloud.google.com/workflows/docs/reference/syntax/iteration
69
- export class ForStepAST {
70
- tag = 'for';
71
- steps;
72
- loopVariableName;
73
- indexVariableName;
74
- listExpression;
75
- label;
76
- constructor(steps, loopVariableName, listExpression, indexVariable, label) {
77
- this.steps = steps;
78
- this.loopVariableName = loopVariableName;
79
- this.listExpression = listExpression;
80
- this.indexVariableName = indexVariable;
81
- this.label = label;
82
- }
83
- withLabel(newLabel) {
84
- return new ForStepAST(this.steps, this.loopVariableName, this.listExpression, this.indexVariableName, newLabel);
85
- }
86
- applyNestedSteps(fn) {
87
- return new ForStepAST(fn(this.steps), this.loopVariableName, this.listExpression, this.indexVariableName, this.label);
88
- }
89
- }
90
- export class ForRangeStepAST {
91
- tag = 'forrange';
92
- steps;
93
- loopVariableName;
94
- rangeStart;
95
- rangeEnd;
96
- label;
97
- constructor(steps, loopVariableName, rangeStart, rangeEnd, label) {
98
- this.steps = steps;
99
- this.loopVariableName = loopVariableName;
100
- this.rangeStart = rangeStart;
101
- this.rangeEnd = rangeEnd;
102
- this.label = label;
103
- }
104
- withLabel(newLabel) {
105
- return new ForRangeStepAST(this.steps, this.loopVariableName, this.rangeStart, this.rangeEnd, newLabel);
106
- }
107
- applyNestedSteps(fn) {
108
- return new ForRangeStepAST(fn(this.steps), this.loopVariableName, this.rangeStart, this.rangeEnd, this.label);
109
- }
110
- }
111
- export class ForStepASTNamed {
112
- tag = 'for';
113
- steps;
114
- loopVariableName;
115
- indexVariableName;
116
- listExpression;
117
- rangeStart;
118
- rangeEnd;
119
- constructor(steps, loopVariableName, listExpression, indexVariable, rangeStart, rangeEnd) {
120
- this.steps = steps;
121
- this.loopVariableName = loopVariableName;
122
- this.listExpression = listExpression;
123
- this.indexVariableName = indexVariable;
124
- this.rangeStart = rangeStart;
125
- this.rangeEnd = rangeEnd;
126
- }
127
- }
128
- export class NextStepAST {
129
- tag = 'next';
130
- target;
131
- label;
132
- constructor(target, label) {
133
- this.target = target;
134
- this.label = label;
135
- }
136
- withLabel(newLabel) {
137
- return new NextStepAST(this.target, newLabel);
138
- }
139
- applyNestedSteps() {
140
- return this;
141
- }
142
- }
143
- // https://cloud.google.com/workflows/docs/reference/syntax/parallel-steps
144
- export class ParallelStepAST {
145
- tag = 'parallel';
146
- steps;
147
- shared;
148
- concurrencyLimit;
149
- exceptionPolicy;
150
- label;
151
- constructor(steps, shared, concurrencyLimit, exceptionPolicy, label) {
152
- this.steps = steps;
153
- this.shared = shared;
154
- this.concurrencyLimit = concurrencyLimit;
155
- this.exceptionPolicy = exceptionPolicy;
156
- this.label = label;
157
- }
158
- withLabel(newLabel) {
159
- return new ParallelStepAST(this.steps, this.shared, this.concurrencyLimit, this.exceptionPolicy, newLabel);
160
- }
161
- applyNestedSteps(fn) {
162
- let transformedSteps;
163
- if (this.steps instanceof ForStepAST) {
164
- transformedSteps = this.steps.applyNestedSteps(fn);
165
- }
166
- else {
167
- transformedSteps = R.map((s) => new StepsStepAST(fn(s.steps), s.label), this.steps);
168
- }
169
- return new ParallelStepAST(transformedSteps, this.shared, this.concurrencyLimit, this.exceptionPolicy, this.label);
170
- }
171
- }
172
- export class ParallelStepASTNamed {
173
- tag = 'parallel';
174
- branches; // Either steps for each branch
175
- forStep; // ... or a parallel for
176
- shared;
177
- concurrenceLimit;
178
- exceptionPolicy;
179
- constructor(steps, shared, concurrencyLimit, exceptionPolicy) {
180
- this.shared = shared;
181
- this.concurrenceLimit = concurrencyLimit;
182
- this.exceptionPolicy = exceptionPolicy;
183
- if (!isRecord(steps)) {
184
- this.forStep = steps;
185
- }
186
- else {
187
- this.branches = Object.entries(steps).map((x) => {
188
- return { name: x[0], step: x[1] };
189
- });
190
- }
191
- }
192
- }
193
- // https://cloud.google.com/workflows/docs/reference/syntax/raising-errors
194
- export class RaiseStepAST {
195
- tag = 'raise';
196
- value;
197
- label;
198
- constructor(value, label) {
199
- this.value = value;
200
- this.label = label;
201
- }
202
- withLabel(newLabel) {
203
- return new RaiseStepAST(this.value, newLabel);
204
- }
205
- applyNestedSteps() {
206
- return this;
207
- }
208
- }
209
- // https://cloud.google.com/workflows/docs/reference/syntax/completing
210
- export class ReturnStepAST {
211
- tag = 'return';
212
- value;
213
- label;
214
- constructor(value, label) {
215
- this.value = value;
216
- this.label = label;
217
- }
218
- withLabel(newLabel) {
219
- return new ReturnStepAST(this.value, newLabel);
220
- }
221
- applyNestedSteps() {
222
- return this;
223
- }
224
- }
225
- // https://cloud.google.com/workflows/docs/reference/syntax/steps#embedded-steps
226
- export class StepsStepAST {
227
- tag = 'steps';
228
- steps;
229
- label;
230
- constructor(steps, label) {
231
- this.steps = steps;
232
- this.label = label;
233
- }
234
- withLabel(newLabel) {
235
- return new StepsStepAST(this.steps, newLabel);
236
- }
237
- applyNestedSteps(fn) {
238
- return new StepsStepAST(fn(this.steps), this.label);
239
- }
240
- }
241
- export class StepsStepASTNamed {
242
- tag = 'steps';
243
- steps;
244
- constructor(steps) {
245
- this.steps = steps;
246
- }
247
- }
248
- // https://cloud.google.com/workflows/docs/reference/syntax/conditions
249
- export class SwitchStepAST {
250
- tag = 'switch';
251
- branches;
252
- label;
253
- constructor(branches, label) {
254
- this.branches = branches;
255
- this.label = label;
256
- }
257
- withLabel(newLabel) {
258
- return new SwitchStepAST(this.branches, newLabel);
259
- }
260
- applyNestedSteps(fn) {
261
- return new SwitchStepAST(this.branches.map((branch) => ({
262
- condition: branch.condition,
263
- steps: fn(branch.steps),
264
- next: branch.next,
265
- })), this.label);
266
- }
267
- }
268
- export class SwitchStepASTNamed {
269
- tag = 'switch';
270
- branches;
271
- next;
272
- constructor(branches, next) {
273
- this.branches = branches;
274
- this.next = next;
275
- }
276
- }
277
- // https://cloud.google.com/workflows/docs/reference/syntax/catching-errors
278
- export class TryStepAST {
279
- tag = 'try';
280
- // Steps in the try block
281
- trySteps;
282
- // Steps in the except block
283
- exceptSteps;
284
- retryPolicy;
285
- errorMap;
286
- label;
287
- constructor(trySteps, exceptSteps, retryPolicy, errorMap, label) {
288
- this.trySteps = trySteps;
289
- this.exceptSteps = exceptSteps;
290
- this.retryPolicy = retryPolicy;
291
- this.errorMap = errorMap;
292
- this.label = label;
293
- }
294
- withLabel(newLabel) {
295
- return new TryStepAST(this.trySteps, this.exceptSteps, this.retryPolicy, this.errorMap, newLabel);
296
- }
297
- applyNestedSteps(fn) {
298
- return new TryStepAST(fn(this.trySteps), this.exceptSteps ? fn(this.exceptSteps) : undefined, this.retryPolicy, this.errorMap, this.label);
299
- }
300
- }
301
- export class TryStepASTNamed {
302
- tag = 'try';
303
- // Steps in the try block
304
- trySteps;
305
- // Steps in the except block
306
- exceptSteps;
307
- retryPolicy;
308
- errorMap;
309
- constructor(trySteps, exceptSteps, retryPolicy, errorMap) {
310
- this.trySteps = trySteps;
311
- this.retryPolicy = retryPolicy;
312
- this.errorMap = errorMap;
313
- this.exceptSteps = exceptSteps;
314
- }
315
- }
316
- // Internal step that represents a potential jump target.
317
- // This can be ued as a placeholder when the actual target step is not yet known.
318
- // JumpTargetAST is removed before transpiling to workflows YAML.
319
- export class JumpTargetAST {
320
- tag = 'jumptarget';
321
- label;
322
- constructor() {
323
- this.label = `jumptarget_${Math.floor(Math.random() * 2 ** 32).toString(16)}`;
324
- }
325
- applyNestedSteps() {
326
- return this;
327
- }
328
- }
329
- /**
330
- * Assign a name for this step and its child steps.
331
- *
332
- * The name is generated by calling generateName with a prefix identifying the step type.
333
- */
334
- export function namedSteps(step, generateName) {
335
- switch (step.tag) {
336
- case 'assign':
337
- case 'next':
338
- case 'raise':
339
- case 'return':
340
- return {
341
- name: step.label ?? generateName(step.tag),
342
- step,
343
- };
344
- case 'call':
345
- return {
346
- name: step.label ?? generateName(step.labelPrefix()),
347
- step,
348
- };
349
- case 'for':
350
- return namedStepsFor(step, generateName);
351
- case 'forrange':
352
- return namedStepsForRange(step, generateName);
353
- case 'parallel':
354
- return namedStepsParallel(step, generateName);
355
- case 'steps':
356
- return namedStepsSteps(step, generateName);
357
- case 'switch':
358
- return namedStepsSwitch(step, generateName);
359
- case 'try':
360
- return namedStepsTry(step, generateName);
361
- case 'jumptarget':
362
- return {
363
- name: step.label,
364
- step: step,
365
- };
366
- }
367
- }
368
- function namedStepsFor(step, generateName) {
369
- return {
370
- name: step.label ?? generateName('for'),
371
- step: new ForStepASTNamed(step.steps.map((nestedStep) => namedSteps(nestedStep, generateName)), step.loopVariableName, step.listExpression, step.indexVariableName),
372
- };
373
- }
374
- function namedStepsForRange(step, generateName) {
375
- return {
376
- name: step.label ?? generateName('for'),
377
- step: new ForStepASTNamed(step.steps.map((nestedStep) => namedSteps(nestedStep, generateName)), step.loopVariableName, undefined, undefined, step.rangeStart, step.rangeEnd),
378
- };
379
- }
380
- function namedStepsParallel(step, generateName) {
381
- let steps;
382
- const mainLabel = step.label ?? generateName('parallel');
383
- if (!isRecord(step.steps)) {
384
- const forStep = namedSteps(step.steps, generateName).step;
385
- if (forStep.tag !== 'for') {
386
- throw new Error(`Encountered a step of type ${forStep.tag} when a for step was expected`);
387
- }
388
- steps = forStep;
389
- }
390
- else {
391
- steps = R.map((step) => {
392
- const named = step.steps.map((x) => namedSteps(x, generateName));
393
- return new StepsStepASTNamed(named);
394
- }, step.steps);
395
- }
396
- return {
397
- name: mainLabel,
398
- step: new ParallelStepASTNamed(steps, step.shared, step.concurrencyLimit, step.exceptionPolicy),
399
- };
400
- }
401
- function namedStepsSteps(step, generateName) {
402
- return {
403
- name: step.label ?? generateName('steps'),
404
- step: new StepsStepASTNamed(step.steps.map((nested) => namedSteps(nested, generateName))),
405
- };
406
- }
407
- function namedStepsSwitch(step, generateName) {
408
- const mainLabel = step.label ?? generateName('switch');
409
- const namedBranches = step.branches.map((branch) => ({
410
- condition: branch.condition,
411
- steps: branch.steps.map((nested) => namedSteps(nested, generateName)),
412
- next: branch.next,
413
- }));
414
- return {
415
- name: mainLabel,
416
- step: new SwitchStepASTNamed(namedBranches),
417
- };
418
- }
419
- function namedStepsTry(step, generateName) {
420
- const mainLabel = step.label ?? generateName('try');
421
- const namedTrySteps = step.trySteps.map((nested) => namedSteps(nested, generateName));
422
- const namedExceptSteps = step.exceptSteps?.map((nested) => namedSteps(nested, generateName));
423
- return {
424
- name: mainLabel,
425
- step: new TryStepASTNamed(namedTrySteps, namedExceptSteps, step.retryPolicy, step.errorMap),
426
- };
427
- }
428
- /**
429
- * Returns the nested steps in contained in this step.
430
- *
431
- * Used in iterating the AST tree.
432
- */
433
- export function nestedSteps(step) {
434
- switch (step.tag) {
435
- case 'assign':
436
- case 'call':
437
- case 'next':
438
- case 'raise':
439
- case 'return':
440
- case 'jumptarget':
441
- return [];
442
- case 'for':
443
- case 'steps':
444
- return [step.steps];
445
- case 'parallel':
446
- return nestedStepsParallel(step);
447
- case 'switch':
448
- return step.branches.map((x) => x.steps);
449
- case 'try':
450
- return nestedStepsTry(step);
451
- }
452
- }
453
- function nestedStepsParallel(step) {
454
- const nested = [];
455
- if (step.branches && step.branches.length > 0) {
456
- // return each branch as a separate child
457
- nested.push(...step.branches.map((x) => [x]));
458
- }
459
- if (step.forStep?.steps && step.forStep.steps.length > 0) {
460
- nested.push(step.forStep.steps);
461
- }
462
- return nested;
463
- }
464
- function nestedStepsTry(step) {
465
- const nested = [];
466
- if (step.trySteps.length > 0) {
467
- nested.push(step.trySteps);
468
- }
469
- if (step.exceptSteps) {
470
- nested.push(step.exceptSteps);
471
- }
472
- return nested;
473
- }
474
5
  /**
475
6
  * Returns an GCP Workflows object representation of a step.
476
7
  */
477
8
  export function renderStep(step) {
478
9
  switch (step.tag) {
479
10
  case 'assign':
480
- return {
481
- assign: step.assignments.map(({ name, value }) => {
482
- return {
483
- [name.toString()]: expressionToLiteralValueOrLiteralExpression(value),
484
- };
485
- }),
486
- ...(step.next && { next: step.next }),
487
- };
11
+ return { [step.label]: renderAssignStep(step) };
488
12
  case 'call':
489
- return renderCallStep(step);
13
+ return { [step.label]: renderCallStep(step) };
490
14
  case 'for':
491
- return {
492
- for: renderForBody(step),
493
- };
494
- case 'parallel':
495
- return {
496
- parallel: {
497
- ...(step.shared && { shared: step.shared }),
498
- ...(step.concurrenceLimit !== undefined && {
499
- concurrency_limit: step.concurrenceLimit,
500
- }),
501
- ...(step.exceptionPolicy !== undefined && {
502
- exception_policy: step.exceptionPolicy,
503
- }),
504
- ...(step.branches && { branches: renderSteps(step.branches) }),
505
- ...(step.forStep && { for: renderForBody(step.forStep) }),
506
- },
507
- };
15
+ return { [step.label]: renderForStep(step) };
508
16
  case 'next':
509
- return {
510
- next: step.target,
511
- };
17
+ return { [step.label]: { next: step.next } };
18
+ case 'parallel':
19
+ return { [step.label]: renderParallelStep(step) };
20
+ case 'parallel-for':
21
+ return { [step.label]: renderParallelIterationStep(step) };
512
22
  case 'raise':
513
- return {
514
- raise: expressionToLiteralValueOrLiteralExpression(step.value),
515
- };
23
+ return { [step.label]: renderRaiseStep(step) };
516
24
  case 'return':
517
- return renderReturnStep(step);
518
- case 'steps':
519
- return {
520
- steps: renderSteps(step.steps),
521
- };
25
+ return { [step.label]: renderReturnStep(step) };
522
26
  case 'switch':
523
- return {
524
- switch: step.branches.map(renderSwitchCondition),
525
- ...(step.next && { next: step.next }),
526
- };
27
+ return { [step.label]: renderSwitchStep(step) };
527
28
  case 'try':
528
- return renderTryStep(step);
529
- case 'jumptarget':
29
+ return { [step.label]: renderTryStep(step) };
30
+ case 'jump-target':
31
+ // Should not be reached
530
32
  return {};
531
33
  }
532
34
  }
533
- function renderSwitchCondition(cond) {
35
+ function renderSteps(steps) {
36
+ return steps.map(renderStep);
37
+ }
38
+ function renderAssignStep(step) {
534
39
  return {
535
- condition: expressionToLiteralValueOrLiteralExpression(cond.condition),
536
- ...(cond.steps.length > 0 && { steps: renderSteps(cond.steps) }),
537
- ...(cond.next && { next: cond.next }),
40
+ assign: step.assignments.map(({ name, value }) => {
41
+ return {
42
+ [expressionToString(name)]: expressionToLiteralValueOrLiteralExpression(value),
43
+ };
44
+ }),
45
+ ...(step.next !== undefined && { next: step.next }),
538
46
  };
539
47
  }
540
48
  function renderCallStep(step) {
@@ -548,6 +56,11 @@ function renderCallStep(step) {
548
56
  ...(step.result !== undefined && { result: step.result }),
549
57
  };
550
58
  }
59
+ function renderForStep(step) {
60
+ return {
61
+ for: renderForBody(step),
62
+ };
63
+ }
551
64
  function renderForBody(step) {
552
65
  let range;
553
66
  let inValue;
@@ -573,13 +86,56 @@ function renderForBody(step) {
573
86
  };
574
87
  }
575
88
  function renderForRangeLimit(value) {
576
- if (value === undefined || typeof value === 'number') {
577
- return value ?? null;
89
+ if (value === undefined) {
90
+ return null;
91
+ }
92
+ else if (typeof value === 'number') {
93
+ return value;
578
94
  }
579
95
  else {
580
96
  return expressionToLiteralValueOrLiteralExpression(value);
581
97
  }
582
98
  }
99
+ function renderParallelStep(step) {
100
+ const renderedBranches = {
101
+ branches: step.branches.map(({ name, steps }) => ({
102
+ [name]: {
103
+ steps: renderSteps(steps),
104
+ },
105
+ })),
106
+ };
107
+ return {
108
+ parallel: {
109
+ ...renderedBranches,
110
+ ...(step.shared && { shared: step.shared }),
111
+ ...(step.concurrencyLimit !== undefined && {
112
+ concurrency_limit: step.concurrencyLimit,
113
+ }),
114
+ ...(step.exceptionPolicy !== undefined && {
115
+ exception_policy: step.exceptionPolicy,
116
+ }),
117
+ },
118
+ };
119
+ }
120
+ function renderParallelIterationStep(step) {
121
+ return {
122
+ parallel: {
123
+ for: renderForBody(step.forStep),
124
+ ...(step.shared && { shared: step.shared }),
125
+ ...(step.concurrencyLimit !== undefined && {
126
+ concurrency_limit: step.concurrencyLimit,
127
+ }),
128
+ ...(step.exceptionPolicy !== undefined && {
129
+ exception_policy: step.exceptionPolicy,
130
+ }),
131
+ },
132
+ };
133
+ }
134
+ function renderRaiseStep(step) {
135
+ return {
136
+ raise: expressionToLiteralValueOrLiteralExpression(step.value),
137
+ };
138
+ }
583
139
  function renderReturnStep(step) {
584
140
  if (step.value) {
585
141
  return {
@@ -592,6 +148,20 @@ function renderReturnStep(step) {
592
148
  };
593
149
  }
594
150
  }
151
+ function renderSwitchStep(step) {
152
+ return {
153
+ switch: step.branches.map(renderSwitchBranch),
154
+ ...(step.next !== undefined && { next: step.next }),
155
+ };
156
+ }
157
+ function renderSwitchBranch(branch) {
158
+ const includeSteps = (branch.steps ?? []).length > 0 || branch.next === undefined;
159
+ return {
160
+ condition: expressionToLiteralValueOrLiteralExpression(branch.condition),
161
+ ...(includeSteps && { steps: renderSteps(branch.steps ?? []) }),
162
+ ...(branch.next !== undefined && { next: branch.next }),
163
+ };
164
+ }
595
165
  function renderTryStep(step) {
596
166
  let retry;
597
167
  if (typeof step.retryPolicy === 'undefined') {
@@ -642,8 +212,777 @@ function renderTryStep(step) {
642
212
  ...(except && { except }),
643
213
  };
644
214
  }
645
- function renderSteps(steps) {
646
- return steps.map((x) => {
647
- return { [x.name]: renderStep(x.step) };
215
+ /**
216
+ * Convert a IR subworkflow to WorkflowSteps.
217
+ *
218
+ * This function also assigns step labels.
219
+ */
220
+ export function toStepSubworkflow(ast, generateLabel) {
221
+ const steps = fixJumpLabels(statementListToSteps(generateLabel, {}, ast.statements));
222
+ return new Subworkflow(ast.name, steps, ast.params);
223
+ }
224
+ const statementListToSteps = R.curry(function (generateLabel, ctx, statements) {
225
+ return mergeNextStep(statements.flatMap((s) => statementToSteps(generateLabel, ctx, s)));
226
+ });
227
+ function statementToSteps(generateLabel, ctx, statement) {
228
+ switch (statement.tag) {
229
+ case 'assign':
230
+ return [
231
+ {
232
+ tag: statement.tag,
233
+ label: generateLabel(statement.tag),
234
+ assignments: statement.assignments,
235
+ },
236
+ ];
237
+ case 'break':
238
+ return [breakStep(generateLabel, ctx, statement)];
239
+ case 'continue':
240
+ return [continueStep(generateLabel, ctx, statement)];
241
+ case 'do-while':
242
+ return doWhileSteps(generateLabel, ctx, statement);
243
+ case 'for':
244
+ return [forStep(generateLabel, ctx, statement)];
245
+ case 'for-range':
246
+ return [forRangeStep(generateLabel, ctx, statement)];
247
+ case 'function-invocation':
248
+ return [callStep(generateLabel, statement)];
249
+ case 'if':
250
+ return [ifStep(generateLabel, ctx, statement)];
251
+ case 'label':
252
+ return labelledSteps(generateLabel, ctx, statement);
253
+ case 'parallel-for':
254
+ return [parallelIterationStep(generateLabel, ctx, statement)];
255
+ case 'parallel':
256
+ return [parallelStep(generateLabel, ctx, statement)];
257
+ case 'raise':
258
+ return [
259
+ {
260
+ tag: statement.tag,
261
+ label: generateLabel(statement.tag),
262
+ value: statement.value,
263
+ },
264
+ ];
265
+ case 'return':
266
+ return [returnStep(generateLabel, ctx, statement)];
267
+ case 'switch':
268
+ return switchSteps(generateLabel, ctx, statement);
269
+ case 'try':
270
+ return trySteps(generateLabel, ctx, statement);
271
+ case 'while':
272
+ return whileSteps(generateLabel, ctx, statement);
273
+ }
274
+ }
275
+ function callStep(generateLabel, statement) {
276
+ const labelPrefix = 'call_' + statement.callee.replaceAll(/[^a-zA-Z0-9]/g, '_') + '_';
277
+ return {
278
+ tag: 'call',
279
+ label: generateLabel(labelPrefix),
280
+ call: statement.callee,
281
+ args: statement.args,
282
+ result: statement.result,
283
+ };
284
+ }
285
+ function breakStep(generateLabel, ctx, statement) {
286
+ if (ctx.finalizerTargets) {
287
+ // TODO: would need to detect if this breaks out of the try or catch block,
288
+ // execute the finally block first and then do the break.
289
+ throw new InternalTranspilingError('break is not supported inside a try-finally block');
290
+ }
291
+ return {
292
+ tag: 'next',
293
+ label: generateLabel('next'),
294
+ next: statement.label ?? ctx.breakTarget ?? 'break',
295
+ };
296
+ }
297
+ function continueStep(generateLabel, ctx, statement) {
298
+ if (ctx.finalizerTargets) {
299
+ // TODO: would need to detect if continue breaks out of the try or catch block,
300
+ // execute the finally block first and then do the continue.
301
+ throw new InternalTranspilingError('continue is not supported inside a try-finally block');
302
+ }
303
+ return {
304
+ tag: 'next',
305
+ label: generateLabel('next'),
306
+ next: statement.label ?? ctx.continueTarget ?? 'continue',
307
+ };
308
+ }
309
+ function doWhileSteps(generateLabel, ctx, statement) {
310
+ const startOfLoopLabel = generatePlaceholderLabel();
311
+ const endOfLoopLabel = generatePlaceholderLabel();
312
+ const ctx2 = Object.assign({}, ctx, {
313
+ continueTarget: startOfLoopLabel,
314
+ breakTarget: endOfLoopLabel,
315
+ });
316
+ const toSteps = statementListToSteps(generateLabel, ctx2);
317
+ return [
318
+ {
319
+ tag: 'jump-target',
320
+ label: startOfLoopLabel,
321
+ },
322
+ ...toSteps(statement.body),
323
+ {
324
+ tag: 'switch',
325
+ label: generateLabel('switch'),
326
+ branches: [
327
+ {
328
+ condition: statement.condition,
329
+ steps: [],
330
+ next: startOfLoopLabel,
331
+ },
332
+ ],
333
+ },
334
+ {
335
+ tag: 'jump-target',
336
+ label: endOfLoopLabel,
337
+ },
338
+ ];
339
+ }
340
+ function whileSteps(generateLabel, ctx, statement) {
341
+ const startOfLoopLabel = generateLabel('switch');
342
+ const endOfLoopLabel = generatePlaceholderLabel();
343
+ const ctx2 = Object.assign({}, ctx, {
344
+ continueTarget: startOfLoopLabel,
345
+ breakTarget: endOfLoopLabel,
346
+ });
347
+ const toSteps = statementListToSteps(generateLabel, ctx2);
348
+ const steps = appendNextStep(generateLabel, toSteps(statement.body), startOfLoopLabel);
349
+ const body = {
350
+ condition: statement.condition,
351
+ steps,
352
+ };
353
+ return [
354
+ {
355
+ tag: 'switch',
356
+ label: startOfLoopLabel,
357
+ branches: [body],
358
+ },
359
+ {
360
+ tag: 'jump-target',
361
+ label: endOfLoopLabel,
362
+ },
363
+ ];
364
+ }
365
+ function forStep(generateLabel, ctx, statement) {
366
+ const label = generateLabel('for');
367
+ const bodyCtx = Object.assign({}, ctx, {
368
+ continueTarget: undefined,
369
+ breakTarget: undefined,
370
+ });
371
+ const toSteps = statementListToSteps(generateLabel, bodyCtx);
372
+ return {
373
+ tag: 'for',
374
+ label,
375
+ steps: toSteps(statement.body),
376
+ loopVariableName: statement.loopVariableName,
377
+ listExpression: statement.listExpression,
378
+ };
379
+ }
380
+ function forRangeStep(generateLabel, ctx, statement) {
381
+ const label = generateLabel('for');
382
+ const bodyCtx = Object.assign({}, ctx, {
383
+ continueTarget: undefined,
384
+ breakTarget: undefined,
385
+ });
386
+ const toSteps = statementListToSteps(generateLabel, bodyCtx);
387
+ return {
388
+ tag: 'for',
389
+ label,
390
+ steps: toSteps(statement.body),
391
+ loopVariableName: statement.loopVariableName,
392
+ rangeStart: statement.rangeStart,
393
+ rangeEnd: statement.rangeEnd,
394
+ };
395
+ }
396
+ function ifStep(generateLabel, ctx, statement) {
397
+ const label = generateLabel('switch');
398
+ const toSteps = statementListToSteps(generateLabel, ctx);
399
+ const branches = statement.branches.map((branch) => {
400
+ if ('body' in branch) {
401
+ // Use the shorter "next" form on break and continue statements
402
+ if (branch.body.length === 1 && branch.body[0].tag === 'break') {
403
+ return {
404
+ condition: branch.condition,
405
+ next: breakStep(generateLabel, ctx, branch.body[0]).next,
406
+ };
407
+ }
408
+ else if (branch.body.length === 1 &&
409
+ branch.body[0].tag === 'continue') {
410
+ return {
411
+ condition: branch.condition,
412
+ next: continueStep(generateLabel, ctx, branch.body[0]).next,
413
+ };
414
+ }
415
+ else {
416
+ return {
417
+ condition: branch.condition,
418
+ steps: toSteps(branch.body),
419
+ };
420
+ }
421
+ }
422
+ else {
423
+ return {
424
+ condition: branch.condition,
425
+ next: branch.next,
426
+ };
427
+ }
428
+ });
429
+ return {
430
+ tag: 'switch',
431
+ label,
432
+ branches,
433
+ };
434
+ }
435
+ function labelledSteps(generateLabel, ctx, statement) {
436
+ const toSteps = statementListToSteps(generateLabel, ctx);
437
+ const steps = toSteps(statement.statements);
438
+ if (steps.length >= 1) {
439
+ // FIXME: do not first generate a label and then immediately overwrite it here
440
+ steps[0].label = statement.label;
441
+ }
442
+ return steps;
443
+ }
444
+ function parallelStep(generateLabel, ctx, statement) {
445
+ const label = generateLabel('parallel');
446
+ const toSteps = statementListToSteps(generateLabel, ctx);
447
+ const branches = statement.branches.map((branch) => ({
448
+ name: branch.name,
449
+ steps: toSteps(branch.body),
450
+ }));
451
+ return {
452
+ tag: 'parallel',
453
+ label,
454
+ branches,
455
+ shared: statement.shared,
456
+ concurrencyLimit: statement.concurrencyLimit,
457
+ exceptionPolicy: statement.exceptionPolicy,
458
+ };
459
+ }
460
+ function parallelIterationStep(generateLabel, ctx, statement) {
461
+ const label = generateLabel('parallel');
462
+ const innerStep = statement.forStep.tag === 'for'
463
+ ? forStep(generateLabel, ctx, statement.forStep)
464
+ : forRangeStep(generateLabel, ctx, statement.forStep);
465
+ return {
466
+ tag: 'parallel-for',
467
+ label,
468
+ forStep: innerStep,
469
+ shared: statement.shared,
470
+ concurrencyLimit: statement.concurrencyLimit,
471
+ exceptionPolicy: statement.exceptionPolicy,
472
+ };
473
+ }
474
+ function returnStep(generateLabel, ctx, statement) {
475
+ if (ctx.finalizerTargets && ctx.finalizerTargets.length > 0) {
476
+ // If we are in try statement with a finally block, return statements are
477
+ // replaced by a jump to finally back with a captured return value.
478
+ return delayedReturnAndJumpToFinalizer(generateLabel, ctx, statement.value);
479
+ }
480
+ else {
481
+ return {
482
+ tag: 'return',
483
+ label: generateLabel('return'),
484
+ value: statement.value,
485
+ };
486
+ }
487
+ }
488
+ function delayedReturnAndJumpToFinalizer(generateLabel, ctx, value) {
489
+ const finalizerTarget = ctx.finalizerTargets && ctx.finalizerTargets.length > 0
490
+ ? ctx.finalizerTargets[ctx.finalizerTargets.length - 1]
491
+ : undefined;
492
+ const [conditionVariable, valueVariable] = finalizerVariables(ctx);
493
+ return {
494
+ tag: 'assign',
495
+ label: generateLabel('assign'),
496
+ assignments: [
497
+ {
498
+ name: variableReferenceEx(conditionVariable),
499
+ value: stringEx('return'),
500
+ },
501
+ {
502
+ name: variableReferenceEx(valueVariable),
503
+ value: value ?? nullEx,
504
+ },
505
+ ],
506
+ next: finalizerTarget,
507
+ };
508
+ }
509
+ function switchSteps(generateLabel, ctx, statement) {
510
+ const label = generateLabel('switch');
511
+ const endOfSwitchLabel = generatePlaceholderLabel();
512
+ const switchCtx = Object.assign({}, ctx, { breakTarget: endOfSwitchLabel });
513
+ const toSteps = statementListToSteps(generateLabel, switchCtx);
514
+ const steps = [];
515
+ const branches = [];
516
+ // prepare steps for each branch so that we will have jump targets ready even for
517
+ // fall-through branches that need to jump to the next branch
518
+ const branchSteps = statement.branches.map((branch) => toSteps(branch.body));
519
+ steps.push(...branchSteps.flat());
520
+ statement.branches.forEach((branch, i) => {
521
+ // search forward until the next non-fall-through branch (= a branch that has
522
+ // non-empty steps)
523
+ const jumpTarget = branchSteps.slice(i).find((s) => s.length > 0);
524
+ const next = jumpTarget?.[0]?.label ?? endOfSwitchLabel;
525
+ branches.push({
526
+ condition: branch.condition,
527
+ next,
528
+ });
529
+ });
530
+ steps.unshift({
531
+ tag: 'switch',
532
+ label,
533
+ branches,
534
+ });
535
+ steps.push({
536
+ tag: 'jump-target',
537
+ label: endOfSwitchLabel,
538
+ });
539
+ return steps;
540
+ }
541
+ function trySteps(generateLabel, ctx, statement) {
542
+ if (!statement.finalizerBody) {
543
+ // Basic try-catch without a finally block
544
+ return [tryCatchRetrySteps(generateLabel, ctx, statement)];
545
+ }
546
+ else {
547
+ // Try-finally is translated to two nested try blocks. The innermost try is
548
+ // the actual try body with control flow statements (return, in the future
549
+ // also break/continue) replaced by jumps to the finally block.
550
+ //
551
+ // The outer try's catch block saves the exception and continues to the
552
+ // finally block.
553
+ //
554
+ // The nested try blocks are followed by the finally block and a switch for
555
+ // checking if we need to perform a delayed return/raise.
556
+ const startOfFinalizer = {
557
+ tag: 'jump-target',
558
+ label: generatePlaceholderLabel(),
559
+ };
560
+ const targets = ctx.finalizerTargets ?? [];
561
+ targets.push(startOfFinalizer.label);
562
+ ctx = Object.assign({}, ctx, { finalizerTargets: targets });
563
+ const [conditionVariable, valueVariable] = finalizerVariables(ctx);
564
+ const initStatement = finalizerInitializer(generateLabel, conditionVariable, valueVariable);
565
+ const outerLabel = generateLabel('try');
566
+ const innerTry = tryCatchRetrySteps(generateLabel, ctx, statement);
567
+ const outerTry = {
568
+ tag: 'try',
569
+ label: outerLabel,
570
+ trySteps: [innerTry],
571
+ exceptSteps: finalizerDelayedException(generateLabel, '__fin_exc', conditionVariable, valueVariable),
572
+ errorMap: '__fin_exc',
573
+ };
574
+ // Reset ctx before parsing the finally block because we don't want to
575
+ // transform returns in finally block in to delayed returns
576
+ if (ctx.finalizerTargets && ctx.finalizerTargets.length <= 1) {
577
+ delete ctx.finalizerTargets;
578
+ }
579
+ else {
580
+ ctx.finalizerTargets?.pop();
581
+ }
582
+ const toSteps = statementListToSteps(generateLabel, ctx);
583
+ const finallyBlock = toSteps(statement.finalizerBody);
584
+ return [
585
+ initStatement,
586
+ outerTry,
587
+ startOfFinalizer,
588
+ ...finallyBlock,
589
+ finalizerFooter(generateLabel, conditionVariable, valueVariable),
590
+ ];
591
+ }
592
+ }
593
+ function tryCatchRetrySteps(generateLabel, ctx, statement) {
594
+ const toSteps = statementListToSteps(generateLabel, ctx);
595
+ return {
596
+ tag: 'try',
597
+ label: generateLabel('try'),
598
+ trySteps: toSteps(statement.tryBody),
599
+ exceptSteps: statement.exceptBody
600
+ ? toSteps(statement.exceptBody)
601
+ : undefined,
602
+ retryPolicy: statement.retryPolicy,
603
+ errorMap: statement.errorMap,
604
+ };
605
+ }
606
+ function finalizerVariables(ctx) {
607
+ const targets = ctx.finalizerTargets ?? [];
608
+ const nestingLevel = targets.length > 0 ? `${targets.length}` : '';
609
+ const conditionVariable = `__t2w_finally_condition${nestingLevel}`;
610
+ const valueVariable = `__t2w_finally_value${nestingLevel}`;
611
+ return [conditionVariable, valueVariable];
612
+ }
613
+ /**
614
+ * The shared header for try-finally for initializing the temp variables
615
+ */
616
+ function finalizerInitializer(generateLabel, conditionVariable, valueVariable) {
617
+ return {
618
+ tag: 'assign',
619
+ label: generateLabel('assign'),
620
+ assignments: [
621
+ {
622
+ name: variableReferenceEx(conditionVariable),
623
+ value: nullEx,
624
+ },
625
+ {
626
+ name: variableReferenceEx(valueVariable),
627
+ value: nullEx,
628
+ },
629
+ ],
630
+ };
631
+ }
632
+ /**
633
+ * The shared footer of a finally block that re-throws the exception or
634
+ * returns the value returned by the try body.
635
+ *
636
+ * The footer code in TypeScript:
637
+ *
638
+ * if (__t2w_finally_condition == "return") {
639
+ * return __t2w_finally_value
640
+ * } elseif (__t2w_finally_condition == "raise") {
641
+ * throw __t2w_finally_value
642
+ * }
643
+ */
644
+ function finalizerFooter(generateLabel, conditionVariable, valueVariable) {
645
+ const variable = variableReferenceEx(conditionVariable);
646
+ const val = variableReferenceEx(valueVariable);
647
+ const returnString = stringEx('return');
648
+ const raiseString = stringEx('raise');
649
+ return {
650
+ tag: 'switch',
651
+ label: generateLabel('switch'),
652
+ branches: [
653
+ {
654
+ condition: binaryEx(variable, '==', returnString),
655
+ steps: [{ tag: 'return', label: generateLabel('return'), value: val }],
656
+ },
657
+ {
658
+ condition: binaryEx(variable, '==', raiseString),
659
+ steps: [{ tag: 'raise', label: generateLabel('raise'), value: val }],
660
+ },
661
+ ],
662
+ };
663
+ }
664
+ function finalizerDelayedException(generateLabel, exceptionVariableName, conditionVariableName, valueVariableName) {
665
+ return [
666
+ {
667
+ tag: 'assign',
668
+ label: generateLabel('assign'),
669
+ assignments: [
670
+ {
671
+ name: variableReferenceEx(conditionVariableName),
672
+ value: stringEx('raise'),
673
+ },
674
+ {
675
+ name: variableReferenceEx(valueVariableName),
676
+ value: variableReferenceEx(exceptionVariableName),
677
+ },
678
+ ],
679
+ },
680
+ ];
681
+ }
682
+ function generatePlaceholderLabel() {
683
+ return `jumptarget_${Math.floor(Math.random() * 2 ** 32).toString(16)}`;
684
+ }
685
+ /**
686
+ * Merge a next step to the previous step.
687
+ *
688
+ * For example, transforms this:
689
+ *
690
+ * - assign1:
691
+ * assign:
692
+ * - a: 1
693
+ * - next1:
694
+ * next: target1
695
+ *
696
+ * into this:
697
+ *
698
+ * - assign1:
699
+ * assign:
700
+ * - a: 1
701
+ * next: target1
702
+ */
703
+ function mergeNextStep(steps) {
704
+ return steps.reduce((acc, step) => {
705
+ const prev = acc.at(-1);
706
+ if (step.tag === 'next' && prev?.tag === 'assign') {
707
+ // TODO: This will delete the "next" step that already had a label. The
708
+ // output will have a hole in label number. How to avoid that?
709
+ acc.pop();
710
+ acc.push({ ...prev, next: step.next });
711
+ }
712
+ else {
713
+ acc.push(step);
714
+ }
715
+ return acc;
716
+ }, []);
717
+ }
718
+ function appendNextStep(generateLabel, steps, nextLabel) {
719
+ const last = steps.at(-1);
720
+ if (last?.tag === 'assign') {
721
+ steps.pop();
722
+ steps.push({ ...last, next: nextLabel });
723
+ }
724
+ else {
725
+ steps.push({
726
+ tag: 'next',
727
+ label: generateLabel('next'),
728
+ next: nextLabel,
729
+ });
730
+ }
731
+ return steps;
732
+ }
733
+ function fixJumpLabels(steps) {
734
+ const jumpTargetLabels = collectActualJumpTargets(steps);
735
+ const stepsWithoutJumpTargetNodes = removeJumpTargetSteps(steps);
736
+ const relabeledSteps = relabelNextLabels(stepsWithoutJumpTargetNodes, jumpTargetLabels);
737
+ return relabeledSteps;
738
+ }
739
+ /**
740
+ * Find a mapping from jump target labels to step names.
741
+ *
742
+ * Iterate over all steps in the workflow. For JumpTargetAST nodes, find the
743
+ * next node that is not a JumpTargetAST, save its name as the real jump taget
744
+ * name.
745
+ */
746
+ function collectActualJumpTargets(steps) {
747
+ const replacements = new Map();
748
+ // The processing is done iteratively with an explicit stack because
749
+ // nextNonJumpTargetNode() needs the stack. Note the order of steps on the
750
+ // stack: the first step is the last element of the stack.
751
+ const stack = [];
752
+ stack.push(...stepsToJumpStackElements(steps, 0));
753
+ while (stack.length > 0) {
754
+ // Do not yet pop in case nextNonJumpTargetNode needs to search the stack
755
+ const { step, nestingLevel } = stack[stack.length - 1];
756
+ if (step.tag === 'jump-target') {
757
+ const currentLabel = step.label;
758
+ const target = nextNonJumpTargetNode(stack);
759
+ const targetName = target ? target.label : 'end';
760
+ replacements.set(currentLabel, targetName);
761
+ }
762
+ // Now nextNonJumpTargetNode has been executed and it's safe to pop the
763
+ // current element from the stack.
764
+ stack.pop();
765
+ const children = nestedSteps(step).map((x) => stepsToJumpStackElements(x, nestingLevel + 1));
766
+ children.reverse();
767
+ children.forEach((nestedChildren) => {
768
+ stack.push(...nestedChildren);
769
+ });
770
+ }
771
+ return replacements;
772
+ }
773
+ function stepsToJumpStackElements(steps, nestingLevel) {
774
+ const block = steps.map((step, i) => ({
775
+ step,
776
+ nestingLevel,
777
+ isLastInBlock: i === steps.length - 1,
778
+ }));
779
+ block.reverse();
780
+ return block;
781
+ }
782
+ function nextNonJumpTargetNode(stack) {
783
+ let nestingLevel = stack[stack.length - 1]?.nestingLevel ?? -1;
784
+ while (nestingLevel >= 0) {
785
+ // Consider only the steps in the current code block (= the same nesting
786
+ // level, taking steps until isLastInBlock)
787
+ const endOfBlockIndex = stack.findLastIndex((x) => x.nestingLevel === nestingLevel && x.isLastInBlock);
788
+ if (endOfBlockIndex < 0) {
789
+ // should not be reached
790
+ throw new InternalTranspilingError('Failed to find end of block in nextNonJumpTargetNode');
791
+ }
792
+ const firstNonJumpTarget = stack
793
+ .slice(endOfBlockIndex)
794
+ .findLast((x) => x.nestingLevel === nestingLevel && x.step.tag !== 'jump-target');
795
+ if (firstNonJumpTarget) {
796
+ return firstNonJumpTarget.step;
797
+ }
798
+ nestingLevel--;
799
+ }
800
+ return undefined;
801
+ }
802
+ function nestedSteps(step) {
803
+ switch (step.tag) {
804
+ case 'assign':
805
+ case 'call':
806
+ case 'next':
807
+ case 'raise':
808
+ case 'return':
809
+ case 'jump-target':
810
+ return [];
811
+ case 'for':
812
+ return [step.steps];
813
+ case 'parallel':
814
+ return step.branches.map((x) => x.steps);
815
+ case 'parallel-for':
816
+ return [step.forStep.steps];
817
+ case 'switch':
818
+ return step.branches.map((x) => x.steps ?? []);
819
+ case 'try':
820
+ return nestedStepsTry(step);
821
+ }
822
+ }
823
+ function nestedStepsTry(step) {
824
+ const nested = [];
825
+ if (step.trySteps.length > 0) {
826
+ nested.push(step.trySteps);
827
+ }
828
+ if (step.exceptSteps) {
829
+ nested.push(step.exceptSteps);
830
+ }
831
+ return nested;
832
+ }
833
+ function removeJumpTargetSteps(steps) {
834
+ return steps
835
+ .filter((x) => x.tag !== 'jump-target')
836
+ .map(removeJumpTargetRecurse);
837
+ }
838
+ function removeJumpTargetRecurse(step) {
839
+ switch (step.tag) {
840
+ case 'assign':
841
+ case 'call':
842
+ case 'next':
843
+ case 'raise':
844
+ case 'return':
845
+ case 'jump-target':
846
+ return step;
847
+ case 'for':
848
+ return removeJumpTargetsFor(step);
849
+ case 'parallel':
850
+ return removeJumpTargetsParallel(step);
851
+ case 'parallel-for':
852
+ return removeJumpTargetsParallelIteration(step);
853
+ case 'switch':
854
+ return removeJumpTargetsSwitch(step);
855
+ case 'try':
856
+ return removeJumpTargetTry(step);
857
+ }
858
+ }
859
+ function removeJumpTargetsFor(step) {
860
+ return {
861
+ ...step,
862
+ steps: removeJumpTargetSteps(step.steps),
863
+ };
864
+ }
865
+ function removeJumpTargetsParallel(step) {
866
+ const branches = step.branches.map(({ name, steps: nestedSteps }) => ({
867
+ name,
868
+ steps: removeJumpTargetSteps(nestedSteps),
869
+ }));
870
+ return {
871
+ ...step,
872
+ branches,
873
+ };
874
+ }
875
+ function removeJumpTargetsParallelIteration(step) {
876
+ return {
877
+ ...step,
878
+ forStep: removeJumpTargetsFor(step.forStep),
879
+ };
880
+ }
881
+ function removeJumpTargetsSwitch(step) {
882
+ const transformedBranches = step.branches.map((branch) => {
883
+ return {
884
+ condition: branch.condition,
885
+ steps: branch.steps !== undefined
886
+ ? removeJumpTargetSteps(branch.steps)
887
+ : undefined,
888
+ next: branch.next,
889
+ };
648
890
  });
891
+ return {
892
+ ...step,
893
+ branches: transformedBranches,
894
+ };
895
+ }
896
+ function removeJumpTargetTry(step) {
897
+ return {
898
+ ...step,
899
+ trySteps: removeJumpTargetSteps(step.trySteps),
900
+ exceptSteps: step.exceptSteps !== undefined
901
+ ? removeJumpTargetSteps(step.exceptSteps)
902
+ : undefined,
903
+ };
904
+ }
905
+ function relabelNextLabels(steps, replacements) {
906
+ return steps.map((step) => renameJumpTargets(step, replacements));
907
+ }
908
+ function renameJumpTargets(step, replaceLabels) {
909
+ switch (step.tag) {
910
+ case 'call':
911
+ case 'raise':
912
+ case 'return':
913
+ case 'jump-target':
914
+ return step;
915
+ case 'assign':
916
+ return renameJumpTargetsAssign(step, replaceLabels);
917
+ case 'for':
918
+ return renameJumpTargetsFor(step, replaceLabels);
919
+ case 'next':
920
+ return renameJumpTargetsNext(step, replaceLabels);
921
+ case 'parallel':
922
+ return renameJumpTargetsParallel(step, replaceLabels);
923
+ case 'parallel-for':
924
+ return renameJumpTargetsParallelIteration(step, replaceLabels);
925
+ case 'switch':
926
+ return renameJumpTargetsSwitch(step, replaceLabels);
927
+ case 'try':
928
+ return renameJumpTargetsTry(step, replaceLabels);
929
+ }
930
+ }
931
+ function renameJumpTargetsAssign(step, replaceLabels) {
932
+ if (step.next) {
933
+ const newLabel = replaceLabels.get(step.next);
934
+ if (newLabel) {
935
+ return { ...step, next: newLabel };
936
+ }
937
+ }
938
+ return step;
939
+ }
940
+ function renameJumpTargetsFor(step, replaceLabels) {
941
+ const transformedSteps = step.steps.map((s) => renameJumpTargets(s, replaceLabels));
942
+ return { ...step, steps: transformedSteps };
943
+ }
944
+ function renameJumpTargetsNext(step, replaceLabels) {
945
+ const newLabel = replaceLabels.get(step.next) ?? step.next;
946
+ return { ...step, next: newLabel };
947
+ }
948
+ function renameJumpTargetsParallel(step, replaceLabels) {
949
+ const branches = step.branches.map(({ name, steps }) => ({
950
+ name,
951
+ steps: steps.map((s) => renameJumpTargets(s, replaceLabels)),
952
+ }));
953
+ return { ...step, branches };
954
+ }
955
+ function renameJumpTargetsParallelIteration(step, replaceLabels) {
956
+ return { ...step, forStep: renameJumpTargetsFor(step.forStep, replaceLabels) };
957
+ }
958
+ function renameJumpTargetsSwitch(step, replaceLabels) {
959
+ const updatedNext = step.next
960
+ ? (replaceLabels.get(step.next) ?? step.next)
961
+ : undefined;
962
+ const updatedBranches = step.branches.map((cond) => {
963
+ const updatedCondNext = cond.next
964
+ ? (replaceLabels.get(cond.next) ?? cond.next)
965
+ : undefined;
966
+ const updatedCondSteps = cond.steps?.map((nested) => renameJumpTargets(nested, replaceLabels));
967
+ return {
968
+ condition: cond.condition,
969
+ steps: updatedCondSteps,
970
+ next: updatedCondNext,
971
+ };
972
+ });
973
+ return {
974
+ tag: 'switch',
975
+ label: step.label,
976
+ branches: updatedBranches,
977
+ next: updatedNext,
978
+ };
979
+ }
980
+ function renameJumpTargetsTry(step, replaceLabels) {
981
+ const transformedTrySteps = step.trySteps.map((nested) => renameJumpTargets(nested, replaceLabels));
982
+ const transformedExceptSteps = step.exceptSteps?.map((nested) => renameJumpTargets(nested, replaceLabels));
983
+ return {
984
+ ...step,
985
+ trySteps: transformedTrySteps,
986
+ exceptSteps: transformedExceptSteps,
987
+ };
649
988
  }