ts2workflows 0.11.0 → 0.12.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 (48) hide show
  1. package/README.md +17 -7
  2. package/dist/ast/expressions.d.ts +37 -41
  3. package/dist/ast/expressions.d.ts.map +1 -1
  4. package/dist/ast/expressions.js +124 -255
  5. package/dist/ast/statements.d.ts +132 -0
  6. package/dist/ast/statements.d.ts.map +1 -0
  7. package/dist/ast/statements.js +197 -0
  8. package/dist/ast/steps.d.ts +82 -194
  9. package/dist/ast/steps.d.ts.map +1 -1
  10. package/dist/ast/steps.js +862 -523
  11. package/dist/ast/workflows.d.ts +14 -7
  12. package/dist/ast/workflows.d.ts.map +1 -1
  13. package/dist/ast/workflows.js +19 -10
  14. package/dist/cli.js +45 -32
  15. package/dist/errors.d.ts +4 -0
  16. package/dist/errors.d.ts.map +1 -1
  17. package/dist/errors.js +7 -0
  18. package/dist/transpiler/index.d.ts +14 -1
  19. package/dist/transpiler/index.d.ts.map +1 -1
  20. package/dist/transpiler/index.js +150 -31
  21. package/dist/transpiler/linker.d.ts +3 -0
  22. package/dist/transpiler/linker.d.ts.map +1 -0
  23. package/dist/transpiler/linker.js +41 -0
  24. package/dist/transpiler/parseexpressions.d.ts +11 -0
  25. package/dist/transpiler/parseexpressions.d.ts.map +1 -0
  26. package/dist/transpiler/{expressions.js → parseexpressions.js} +90 -103
  27. package/dist/transpiler/parsestatement.d.ts +7 -0
  28. package/dist/transpiler/parsestatement.d.ts.map +1 -0
  29. package/dist/transpiler/parsestatement.js +862 -0
  30. package/dist/transpiler/stepnames.d.ts +3 -0
  31. package/dist/transpiler/stepnames.d.ts.map +1 -0
  32. package/dist/transpiler/stepnames.js +17 -0
  33. package/dist/transpiler/transformations.d.ts +3 -19
  34. package/dist/transpiler/transformations.d.ts.map +1 -1
  35. package/dist/transpiler/transformations.js +267 -322
  36. package/language_reference.md +8 -2
  37. package/package.json +8 -6
  38. package/dist/ast/stepnames.d.ts +0 -9
  39. package/dist/ast/stepnames.d.ts.map +0 -1
  40. package/dist/ast/stepnames.js +0 -280
  41. package/dist/transpiler/expressions.d.ts +0 -11
  42. package/dist/transpiler/expressions.d.ts.map +0 -1
  43. package/dist/transpiler/statements.d.ts +0 -10
  44. package/dist/transpiler/statements.d.ts.map +0 -1
  45. package/dist/transpiler/statements.js +0 -1098
  46. package/dist/utils.d.ts +0 -2
  47. package/dist/utils.d.ts.map +0 -1
  48. package/dist/utils.js +0 -3
@@ -85,7 +85,7 @@ sys.get_env('GOOGLE_CLOUD_PROJECT_ID')
85
85
  | !=, !== | not equal to (both mean strict equality) |
86
86
  | <, >, <=, >= | inequality comparisons |
87
87
  | &&, \|\|, ! | logical and, or, not |
88
- | in | check if a property is present in an object |
88
+ | in | check if a key is present in a map or array |
89
89
  | ?? | nullish coalescing |
90
90
  | ?. | optional chaining |
91
91
  | ? : | conditional operator |
@@ -129,7 +129,7 @@ ${default(x, "default value")}
129
129
 
130
130
  ### Optional chaining
131
131
 
132
- ts2workflows converte the Typescript optional chaining expression
132
+ ts2workflows converts the Typescript optional chaining expression
133
133
 
134
134
  ```javascript
135
135
  data.user?.name
@@ -141,6 +141,12 @@ to a [map.get() expression](https://cloud.google.com/workflows/docs/reference/st
141
141
  ${map.get(data, ["user", "name"])}
142
142
  ```
143
143
 
144
+ ### in operator
145
+
146
+ The `in` operator checks if a key is present in a map or if a value is present in a list.
147
+
148
+ ⚠️ The `in` operator applied to a list behaves differently on Workflows than on Typescript. The Workflows `in` operator checks if a value is present in the list, whereas on Typescript `in` checks for the presence of a _property_ on the array object. In particular, the following evaluates to `true` on Workflows (but `false` on Typescript): `"x" in ["x", "y"]`.
149
+
144
150
  ### typeof operator
145
151
 
146
152
  Returns the type of the operand as a string. The return values are the same ones that Javascript typeof operation returns. The following table shows the possible values for different operand types.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ts2workflows",
3
- "version": "0.11.0",
3
+ "version": "0.12.0",
4
4
  "description": "Transpile Typescript code to GCP Workflows programs",
5
5
  "homepage": "https://github.com/aajanki/ts2workflows",
6
6
  "repository": {
@@ -20,7 +20,7 @@
20
20
  "lint": "eslint src test scripts",
21
21
  "format": "prettier . --write",
22
22
  "test": "mocha",
23
- "test-coverage": "nyc mocha",
23
+ "test-coverage": "nyc --reporter=text --reporter=html mocha",
24
24
  "prepare": "husky && npm run build"
25
25
  },
26
26
  "lint-staged": {
@@ -60,13 +60,14 @@
60
60
  ],
61
61
  "devDependencies": {
62
62
  "@eslint/js": "^9.10.0",
63
+ "@istanbuljs/nyc-config-typescript": "^1.0.2",
63
64
  "@types/chai": "^5.0.1",
64
65
  "@types/mocha": "^10.0.6",
65
66
  "@types/node": "^20",
66
- "@types/ramda": "^0.30.2",
67
+ "@types/ramda": "^0.31.0",
67
68
  "@typescript-eslint/eslint-plugin": "^8.0.0",
68
69
  "@typescript-eslint/parser": "^8.0.0",
69
- "chai": "^5.1.1",
70
+ "chai": "^6.0.1",
70
71
  "eslint": "^9.10.0",
71
72
  "husky": "^9.1.6",
72
73
  "lint-staged": "^16.1.2",
@@ -74,14 +75,15 @@
74
75
  "nyc": "^17.1.0",
75
76
  "prettier": "^3.2.5",
76
77
  "rimraf": "^6.0.1",
77
- "tsx": "~4.19.4",
78
+ "source-map-support": "^0.5.21",
79
+ "tsx": "^4.20.5",
78
80
  "typescript-eslint": "^8.0.0"
79
81
  },
80
82
  "dependencies": {
81
83
  "@typescript-eslint/typescript-estree": "^8.0.0",
82
84
  "commander": "^14.0.0",
83
85
  "ramda": "^0.31.3",
84
- "typescript": "^5.4.0",
86
+ "typescript": "^5.5.4",
85
87
  "yaml": "^2.4.2"
86
88
  }
87
89
  }
@@ -1,9 +0,0 @@
1
- import { WorkflowAST } from './steps.js';
2
- import { WorkflowApp } from './workflows.js';
3
- export declare class StepNameGenerator {
4
- private counters;
5
- constructor();
6
- generate(prefix: string): string;
7
- }
8
- export declare function generateStepNames(ast: WorkflowAST): WorkflowApp;
9
- //# sourceMappingURL=stepnames.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"stepnames.d.ts","sourceRoot":"","sources":["../../src/ast/stepnames.ts"],"names":[],"mappings":"AAAA,OAAO,EAUL,WAAW,EAGZ,MAAM,YAAY,CAAA;AACnB,OAAO,EAAe,WAAW,EAAE,MAAM,gBAAgB,CAAA;AAQzD,qBAAa,iBAAiB;IAC5B,OAAO,CAAC,QAAQ,CAAqB;;IAMrC,QAAQ,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM;CAMjC;AAED,wBAAgB,iBAAiB,CAAC,GAAG,EAAE,WAAW,GAAG,WAAW,CAS/D"}
@@ -1,280 +0,0 @@
1
- import { ForStepASTNamed, NextStepAST, ParallelStepASTNamed, StepsStepASTNamed, SwitchStepASTNamed, TryStepASTNamed, nestedSteps, } from './steps.js';
2
- import { Subworkflow, WorkflowApp } from './workflows.js';
3
- export class StepNameGenerator {
4
- counters;
5
- constructor() {
6
- this.counters = new Map();
7
- }
8
- generate(prefix) {
9
- const i = this.counters.get(prefix) ?? 1;
10
- this.counters.set(prefix, i + 1);
11
- return `${prefix}${i}`;
12
- }
13
- }
14
- export function generateStepNames(ast) {
15
- const subworkflows = ast.subworkflows
16
- .map((subworkflow) => {
17
- const stepNameGenerator = new StepNameGenerator();
18
- return subworkflow.withStepNames((x) => stepNameGenerator.generate(x));
19
- })
20
- .map(fixJumpLabels);
21
- return new WorkflowApp(subworkflows);
22
- }
23
- function fixJumpLabels(subworkflow) {
24
- const jumpTargetLabels = collectActualJumpTargets(subworkflow);
25
- const stepsWithoutJumpTargetNodes = removeJumpTargetSteps(subworkflow.steps);
26
- const relabeledSteps = relabelNextLabels(stepsWithoutJumpTargetNodes, jumpTargetLabels);
27
- return new Subworkflow(subworkflow.name, relabeledSteps, subworkflow.params);
28
- }
29
- /**
30
- * Find a mapping from jump target labels to step names.
31
- *
32
- * Iterate over all steps in the workflow. For JumpTargetAST nodes, find the
33
- * next node that is not a JumpTargetAST, save its name as the real jump taget
34
- * name.
35
- */
36
- function collectActualJumpTargets(subworkflow) {
37
- const replacements = new Map();
38
- // The processing is done iteratively with an explicit stack because
39
- // nextNonJumpTargetNode() needs the stack. Note the order of steps on the
40
- // stack: the first step is the last element of the stack.
41
- const stack = [];
42
- stack.push(...stepsToJumpStackElements(subworkflow.steps, 0));
43
- while (stack.length > 0) {
44
- // Do not yet pop in case nextNonJumpTargetNode needs to search the stack
45
- const { namedStep, nestingLevel } = stack[stack.length - 1];
46
- if (namedStep.step.tag === 'jumptarget') {
47
- const currentLabel = namedStep.step.label;
48
- const target = nextNonJumpTargetNode(stack);
49
- const targetName = target ? target.name : 'end';
50
- replacements.set(currentLabel, targetName);
51
- }
52
- // Now nextNonJumpTargetNode has been executed and it's safe to pop the
53
- // current element from the stack.
54
- stack.pop();
55
- const children = nestedSteps(namedStep.step).map((x) => stepsToJumpStackElements(x, nestingLevel + 1));
56
- children.reverse();
57
- children.forEach((children) => {
58
- stack.push(...children);
59
- });
60
- }
61
- return replacements;
62
- }
63
- function stepsToJumpStackElements(steps, nestingLevel) {
64
- const block = steps.map((step, i) => ({
65
- namedStep: step,
66
- nestingLevel,
67
- isLastInBlock: i === steps.length - 1,
68
- }));
69
- block.reverse();
70
- return block;
71
- }
72
- function nextNonJumpTargetNode(stack) {
73
- if (stack.length <= 0) {
74
- return undefined;
75
- }
76
- let nestingLevel = stack[stack.length - 1].nestingLevel;
77
- while (nestingLevel >= 0) {
78
- // Consider only the steps in the current code block (= the same nesting
79
- // level, taking steps until isLastInBlock)
80
- let endOfBlockIndex = stack.findLastIndex((x) => x.nestingLevel === nestingLevel && x.isLastInBlock);
81
- if (endOfBlockIndex < 0) {
82
- // should not be reached
83
- endOfBlockIndex = stack.findLastIndex((x) => x.nestingLevel <= nestingLevel);
84
- if (endOfBlockIndex < 0) {
85
- endOfBlockIndex = 0;
86
- }
87
- }
88
- const firstNonJumpTarget = stack
89
- .slice(endOfBlockIndex)
90
- .findLast((x) => x.nestingLevel === nestingLevel &&
91
- x.namedStep.step.tag !== 'jumptarget');
92
- if (firstNonJumpTarget) {
93
- return firstNonJumpTarget.namedStep;
94
- }
95
- nestingLevel--;
96
- }
97
- return undefined;
98
- }
99
- function removeJumpTargetSteps(steps) {
100
- return steps
101
- .filter((x) => x.step.tag !== 'jumptarget')
102
- .map(({ name, step }) => ({
103
- name,
104
- step: removeJumpTargetRecurse(step),
105
- }));
106
- }
107
- function removeJumpTargetRecurse(step) {
108
- switch (step.tag) {
109
- case 'assign':
110
- case 'call':
111
- case 'next':
112
- case 'raise':
113
- case 'return':
114
- case 'jumptarget':
115
- return step;
116
- case 'for':
117
- return removeJumpTargetsFor(step);
118
- case 'parallel':
119
- return removeJumpTargetsParallel(step);
120
- case 'steps':
121
- return new StepsStepASTNamed(removeJumpTargetSteps(step.steps));
122
- case 'switch':
123
- return removeJumpTargetsSwitch(step);
124
- case 'try':
125
- return new TryStepASTNamed(removeJumpTargetSteps(step.trySteps), step.exceptSteps !== undefined
126
- ? removeJumpTargetSteps(step.exceptSteps)
127
- : undefined, step.retryPolicy, step.errorMap);
128
- }
129
- }
130
- function removeJumpTargetsFor(step) {
131
- return new ForStepASTNamed(removeJumpTargetSteps(step.steps), step.loopVariableName, step.listExpression, step.indexVariableName, step.rangeStart, step.rangeEnd);
132
- }
133
- function removeJumpTargetsParallel(step) {
134
- let transformedSteps;
135
- if (step.branches) {
136
- transformedSteps = Object.fromEntries(step.branches.map((x) => {
137
- return [
138
- x.name,
139
- new StepsStepASTNamed(removeJumpTargetSteps(nestedSteps(x.step).flat())),
140
- ];
141
- }));
142
- }
143
- else if (step.forStep) {
144
- transformedSteps = removeJumpTargetsFor(step.forStep);
145
- }
146
- else {
147
- // should not be reached
148
- transformedSteps = {};
149
- }
150
- return new ParallelStepASTNamed(transformedSteps, step.shared, step.concurrenceLimit, step.exceptionPolicy);
151
- }
152
- function removeJumpTargetsSwitch(step) {
153
- const transformedConditions = step.branches.map((cond) => {
154
- return {
155
- condition: cond.condition,
156
- steps: removeJumpTargetSteps(cond.steps),
157
- next: cond.next,
158
- };
159
- });
160
- return new SwitchStepASTNamed(transformedConditions, step.next);
161
- }
162
- function relabelNextLabels(steps, replacements) {
163
- return steps.map((step) => ({
164
- name: step.name,
165
- step: renameJumpTargets(step.step, replacements),
166
- }));
167
- }
168
- /**
169
- * Renames a copy of a step with jump targets renamed according to replaceLabels map.
170
- */
171
- function renameJumpTargets(step, replaceLabels) {
172
- switch (step.tag) {
173
- case 'call':
174
- case 'raise':
175
- case 'return':
176
- case 'jumptarget':
177
- return step;
178
- case 'assign':
179
- return renameJumpTargetsAssign(step, replaceLabels);
180
- case 'next':
181
- return renameJumpTargetsNext(step, replaceLabels);
182
- case 'for':
183
- return renameJumpTargetsFor(step, replaceLabels);
184
- case 'parallel':
185
- return renameJumpTargetsParallel(step, replaceLabels);
186
- case 'steps':
187
- return renameJumpTargetsSteps(step, replaceLabels);
188
- case 'switch':
189
- return renameJumpTargetsSwitch(step, replaceLabels);
190
- case 'try':
191
- return renameJumpTargetsTry(step, replaceLabels);
192
- }
193
- }
194
- function renameJumpTargetsAssign(step, replaceLabels) {
195
- if (step.next) {
196
- const newLabel = replaceLabels.get(step.next);
197
- if (newLabel) {
198
- return step.withNext(newLabel);
199
- }
200
- }
201
- return step;
202
- }
203
- function renameJumpTargetsFor(step, replaceLabels) {
204
- const transformedSteps = step.steps.map(({ name, step: nested }) => ({
205
- name,
206
- step: renameJumpTargets(nested, replaceLabels),
207
- }));
208
- return new ForStepASTNamed(transformedSteps, step.loopVariableName, step.listExpression, step.indexVariableName, step.rangeStart, step.rangeEnd);
209
- }
210
- function renameJumpTargetsNext(step, replaceLabels) {
211
- const newLabel = replaceLabels.get(step.target);
212
- if (newLabel) {
213
- return new NextStepAST(newLabel);
214
- }
215
- else {
216
- return step;
217
- }
218
- }
219
- function renameJumpTargetsParallel(step, replaceLabels) {
220
- let transformedSteps;
221
- if (step.branches) {
222
- transformedSteps = Object.fromEntries(step.branches.map(({ name, step: nested }) => {
223
- const renamedNested = nestedSteps(nested)
224
- .flat()
225
- .map((x) => ({
226
- name: x.name,
227
- step: renameJumpTargets(x.step, replaceLabels),
228
- }));
229
- return [name, new StepsStepASTNamed(renamedNested)];
230
- }));
231
- }
232
- else if (step.forStep) {
233
- transformedSteps = renameJumpTargetsFor(step.forStep, replaceLabels);
234
- }
235
- else {
236
- // should not be reached
237
- transformedSteps = {};
238
- }
239
- return new ParallelStepASTNamed(transformedSteps, step.shared, step.concurrenceLimit, step.exceptionPolicy);
240
- }
241
- function renameJumpTargetsSteps(step, replaceLabels) {
242
- const transformedSteps = step.steps.map(({ name, step: nested }) => ({
243
- name,
244
- step: renameJumpTargets(nested, replaceLabels),
245
- }));
246
- return new StepsStepASTNamed(transformedSteps);
247
- }
248
- function renameJumpTargetsSwitch(step, replaceLabels) {
249
- let updatedNext = undefined;
250
- if (step.next) {
251
- updatedNext = replaceLabels.get(step.next) ?? step.next;
252
- }
253
- const updatedConditions = step.branches.map((cond) => {
254
- let updatedCondNext = undefined;
255
- if (cond.next) {
256
- updatedCondNext = replaceLabels.get(cond.next) ?? cond.next;
257
- }
258
- const updatedCondSteps = cond.steps.map((nested) => ({
259
- name: nested.name,
260
- step: renameJumpTargets(nested.step, replaceLabels),
261
- }));
262
- return {
263
- condition: cond.condition,
264
- steps: updatedCondSteps,
265
- next: updatedCondNext,
266
- };
267
- });
268
- return new SwitchStepASTNamed(updatedConditions, updatedNext);
269
- }
270
- function renameJumpTargetsTry(step, replaceLabels) {
271
- const transformedTrySteps = step.trySteps.map(({ name, step: nested }) => ({
272
- name,
273
- step: renameJumpTargets(nested, replaceLabels),
274
- }));
275
- const transformedExceptSteps = step.exceptSteps?.map(({ name, step: nested }) => ({
276
- name,
277
- step: renameJumpTargets(nested, replaceLabels),
278
- }));
279
- return new TryStepASTNamed(transformedTrySteps, transformedExceptSteps, step.retryPolicy, step.errorMap);
280
- }
@@ -1,11 +0,0 @@
1
- import { TSESTree } from '@typescript-eslint/typescript-estree';
2
- import { Expression, MemberExpression, Primitive, VariableReferenceExpression } from '../ast/expressions.js';
3
- export declare function convertExpression(instance: TSESTree.Expression): Expression;
4
- export declare function convertObjectExpression(node: TSESTree.ObjectExpression): Record<string, Primitive | Expression>;
5
- export declare function convertObjectAsExpressionValues(node: TSESTree.ObjectExpression): Record<string, Expression>;
6
- export declare function convertMemberExpression(node: TSESTree.MemberExpression): Expression;
7
- export declare function isIntrinsic(calleeName: string): boolean;
8
- export declare function isIntrinsicStatment(calleeName: string): boolean;
9
- export declare function throwIfSpread<T extends TSESTree.Expression | TSESTree.Property | TSESTree.SpreadElement | null>(nodes: T[]): Exclude<T, TSESTree.SpreadElement>[];
10
- export declare function convertVariableNameExpression(instance: TSESTree.Expression): VariableReferenceExpression | MemberExpression;
11
- //# sourceMappingURL=expressions.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"expressions.d.ts","sourceRoot":"","sources":["../../src/transpiler/expressions.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,QAAQ,EAAkB,MAAM,sCAAsC,CAAA;AAC/E,OAAO,EAGL,UAAU,EAEV,gBAAgB,EAChB,SAAS,EAGT,2BAA2B,EAI5B,MAAM,uBAAuB,CAAA;AAG9B,wBAAgB,iBAAiB,CAAC,QAAQ,EAAE,QAAQ,CAAC,UAAU,GAAG,UAAU,CAiE3E;AAcD,wBAAgB,uBAAuB,CACrC,IAAI,EAAE,QAAQ,CAAC,gBAAgB,GAC9B,MAAM,CAAC,MAAM,EAAE,SAAS,GAAG,UAAU,CAAC,CAgCxC;AAED,wBAAgB,+BAA+B,CAC7C,IAAI,EAAE,QAAQ,CAAC,gBAAgB,GAC9B,MAAM,CAAC,MAAM,EAAE,UAAU,CAAC,CAG5B;AAwJD,wBAAgB,uBAAuB,CACrC,IAAI,EAAE,QAAQ,CAAC,gBAAgB,GAC9B,UAAU,CAcZ;AA0JD,wBAAgB,WAAW,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAGvD;AAED,wBAAgB,mBAAmB,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAG/D;AAoDD,wBAAgB,aAAa,CAC3B,CAAC,SACG,QAAQ,CAAC,UAAU,GACnB,QAAQ,CAAC,QAAQ,GACjB,QAAQ,CAAC,aAAa,GACtB,IAAI,EACR,KAAK,EAAE,CAAC,EAAE,GAAG,OAAO,CAAC,CAAC,EAAE,QAAQ,CAAC,aAAa,CAAC,EAAE,CAgBlD;AAED,wBAAgB,6BAA6B,CAC3C,QAAQ,EAAE,QAAQ,CAAC,UAAU,GAC5B,2BAA2B,GAAG,gBAAgB,CAchD"}
@@ -1,10 +0,0 @@
1
- import { TSESTree } from '@typescript-eslint/typescript-estree';
2
- import { StepName, WorkflowStepAST } from '../ast/steps.js';
3
- export interface ParsingContext {
4
- readonly breakTarget?: StepName;
5
- readonly continueTarget?: StepName;
6
- readonly parallelNestingLevel?: number;
7
- finalizerTargets?: StepName[];
8
- }
9
- export declare function parseStatement(node: TSESTree.Statement, ctx: ParsingContext): WorkflowStepAST[];
10
- //# sourceMappingURL=statements.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"statements.d.ts","sourceRoot":"","sources":["../../src/transpiler/statements.ts"],"names":[],"mappings":"AACA,OAAO,EAAkB,QAAQ,EAAE,MAAM,sCAAsC,CAAA;AAC/E,OAAO,EAWL,QAAQ,EAOR,eAAe,EAChB,MAAM,iBAAiB,CAAA;AAkCxB,MAAM,WAAW,cAAc;IAE7B,QAAQ,CAAC,WAAW,CAAC,EAAE,QAAQ,CAAA;IAE/B,QAAQ,CAAC,cAAc,CAAC,EAAE,QAAQ,CAAA;IAGlC,QAAQ,CAAC,oBAAoB,CAAC,EAAE,MAAM,CAAA;IAKtC,gBAAgB,CAAC,EAAE,QAAQ,EAAE,CAAA;CAC9B;AAED,wBAAgB,cAAc,CAC5B,IAAI,EAAE,QAAQ,CAAC,SAAS,EACxB,GAAG,EAAE,cAAc,GAClB,eAAe,EAAE,CA6EnB"}