ts2workflows 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +22 -0
- package/README.md +82 -0
- package/dist/ast/expressions.d.ts +57 -0
- package/dist/ast/expressions.d.ts.map +1 -0
- package/dist/ast/expressions.js +300 -0
- package/dist/ast/stepnames.d.ts +9 -0
- package/dist/ast/stepnames.d.ts.map +1 -0
- package/dist/ast/stepnames.js +268 -0
- package/dist/ast/steps.d.ts +176 -0
- package/dist/ast/steps.d.ts.map +1 -0
- package/dist/ast/steps.js +534 -0
- package/dist/ast/validation.d.ts +20 -0
- package/dist/ast/validation.d.ts.map +1 -0
- package/dist/ast/validation.js +214 -0
- package/dist/ast/workflows.d.ts +28 -0
- package/dist/ast/workflows.d.ts.map +1 -0
- package/dist/ast/workflows.js +74 -0
- package/dist/cli.d.ts +3 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +114 -0
- package/dist/errors.d.ts +18 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +12 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +2 -0
- package/dist/transpiler/asserts.d.ts +7 -0
- package/dist/transpiler/asserts.d.ts.map +1 -0
- package/dist/transpiler/asserts.js +11 -0
- package/dist/transpiler/expressions.d.ts +6 -0
- package/dist/transpiler/expressions.d.ts.map +1 -0
- package/dist/transpiler/expressions.js +223 -0
- package/dist/transpiler/generated/functionMetadata.d.ts +2 -0
- package/dist/transpiler/generated/functionMetadata.d.ts.map +1 -0
- package/dist/transpiler/generated/functionMetadata.js +324 -0
- package/dist/transpiler/index.d.ts +2 -0
- package/dist/transpiler/index.d.ts.map +1 -0
- package/dist/transpiler/index.js +74 -0
- package/dist/transpiler/statements.d.ts +7 -0
- package/dist/transpiler/statements.d.ts.map +1 -0
- package/dist/transpiler/statements.js +533 -0
- package/dist/transpiler/transformations.d.ts +28 -0
- package/dist/transpiler/transformations.d.ts.map +1 -0
- package/dist/transpiler/transformations.js +461 -0
- package/dist/utils.d.ts +2 -0
- package/dist/utils.d.ts.map +1 -0
- package/dist/utils.js +3 -0
- package/language_reference.md +771 -0
- package/package.json +62 -0
- package/types/workflowslib.d.ts +714 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
The MIT License (MIT)
|
|
2
|
+
|
|
3
|
+
Copyright © 2024 Antti Ajanki, <antti.ajanki@iki.fi>
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
|
6
|
+
a copy of this software and associated documentation files (the
|
|
7
|
+
“Software”), to deal in the Software without restriction, including
|
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
|
11
|
+
the following conditions:
|
|
12
|
+
|
|
13
|
+
The above copyright notice and this permission notice shall be
|
|
14
|
+
included in all copies or substantial portions of the Software.
|
|
15
|
+
|
|
16
|
+
THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND,
|
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
|
19
|
+
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
|
20
|
+
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
|
21
|
+
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
|
22
|
+
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
# Typescript to GCP Workflows transpiler
|
|
2
|
+
|
|
3
|
+
ts2workflows converts Typescript code to a [GCP Workflows](https://cloud.google.com/workflows/docs/apis) program.
|
|
4
|
+
|
|
5
|
+
Only a subset of Typescript features are supported. The [language reference](language_reference.md) describes the supported Typescript features.
|
|
6
|
+
|
|
7
|
+
See the [samples](samples) directory for code examples.
|
|
8
|
+
|
|
9
|
+
Project status: It's possible to write Workflows programs, but the transpiler has not been extensively tested. Expect some rough edges!
|
|
10
|
+
|
|
11
|
+
## Command for transpiling a Typescript file to Workflows syntax
|
|
12
|
+
|
|
13
|
+
Converting Typescript code in a file samples/sample1.ts into GCP Workflows YAML syntax on stdout:
|
|
14
|
+
|
|
15
|
+
```sh
|
|
16
|
+
npx ts2workflows samples/sample1.ts
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
When developing ts2workflows, you can run the transpiler directly from the source directory:
|
|
20
|
+
|
|
21
|
+
```sh
|
|
22
|
+
npx tsx src/cli.ts samples/sample1.ts
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
## Type checking
|
|
26
|
+
|
|
27
|
+
One benefit of writing the workflow programs in Typescript is that the sources can be type checked.
|
|
28
|
+
|
|
29
|
+
This example command shows how to type check source files in the [samples](samples) directory using the standard Typescript compiler `tsc`. The command prints typing errors or finishes silently, if there are no errors.
|
|
30
|
+
|
|
31
|
+
```sh
|
|
32
|
+
npx tsc --project samples/tsconfig.workflows.json
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
The file [samples/tsconfig.workflows.json](samples/tsconfig.workflows.json) contains a sample configuration for type checking workflow sources.
|
|
36
|
+
|
|
37
|
+
Type annotations for [Workflows standard library functions and expression helpers](https://cloud.google.com/workflows/docs/reference/stdlib/overview) and for some [connectors](https://cloud.google.com/workflows/docs/reference/googleapis) are provided in [types/workflowslib.d.ts](types/workflowslib.d.ts). They are also included in the published npm module. To import type annotations in a project that has ts2workflows module as a dependency, use the following import command:
|
|
38
|
+
|
|
39
|
+
```javascript
|
|
40
|
+
import { http, retry_policy } from 'ts2workflows/types/workflowslib'
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
Type checking step is completely separate from the transpiling. The ts2workflows command ignores type annotations.
|
|
44
|
+
|
|
45
|
+
## Build
|
|
46
|
+
|
|
47
|
+
```
|
|
48
|
+
npm install
|
|
49
|
+
npm run build
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
## Run unit tests
|
|
53
|
+
|
|
54
|
+
```
|
|
55
|
+
npm run test
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
## Architecture
|
|
59
|
+
|
|
60
|
+
A transpilation using ts2workflows consists of five phases:
|
|
61
|
+
|
|
62
|
+
- parsing Typescript source code
|
|
63
|
+
- converting to intermediate representation (IR)
|
|
64
|
+
- transforming and optimizing the IR
|
|
65
|
+
- assigning names for the Workflows steps
|
|
66
|
+
- outputting in the program in Workflows YAML format
|
|
67
|
+
|
|
68
|
+
The main function implementing these phases is `transpile()` in [src/transpiler/index.ts](src/transpiler/index.ts).
|
|
69
|
+
|
|
70
|
+
The transpiler parses a Typescript source file using [@typescript-eslint/typescript-estree](https://www.npmjs.com/package/@typescript-eslint/typescript-estree) module by the [typescript-eslint](https://typescript-eslint.io/) project. The result of the parsing is an [ESTree](https://github.com/estree/estree/blob/master/README.md)-compatible abstract syntax tree (AST) of the source code. [typescript-eslint playgroud](https://typescript-eslint.io/play/) is an useful tool for exploring the Typescript parsing.
|
|
71
|
+
|
|
72
|
+
Next, ts2workflows converts the AST to a custom intermediate representation (IR). The IR format brings the representation of the program closer the GCP Workflows language than Typescript code. The types for IR are defined in the [src/ast](src/ast) directory. Particularly, see [src/ast/steps.ts](src/ast/steps.ts) for Workflow step types and [src/ast/expressions.ts](src/ast/expressions.ts) for expression types.
|
|
73
|
+
|
|
74
|
+
The transpiler applies a few transformations to the IR to make it a valid and optimized Workflows program. These transformations, for example, merge consecutive assignments into a single assign step and ensure that blocking function are invoked by call steps (as required by Workflows). The transformations are implemented in [src/transpiler/transformations.ts](src/transpiler/transformations.ts).
|
|
75
|
+
|
|
76
|
+
To spare the programmer from labeling each and every Workflows step manually, ts2workflows automatically assigns names to the steps. The automatically choosen step names consist of the step type and a sequential number. The implementation is in [src/ast/stepnames.ts](src/ast/stepnames.ts).
|
|
77
|
+
|
|
78
|
+
Finally, the transpiler converts the result into a YAML document.
|
|
79
|
+
|
|
80
|
+
## License
|
|
81
|
+
|
|
82
|
+
[The MIT License](LICENSE)
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
export type VariableName = string;
|
|
2
|
+
export type BinaryOperator = '+' | '-' | '*' | '/' | '//' | '%' | '==' | '!=' | '>' | '>=' | '<' | '<=' | 'in' | 'and' | 'or';
|
|
3
|
+
export type UnaryOperator = '-' | '+' | 'not';
|
|
4
|
+
export type Primitive = null | string | number | boolean | (Primitive | Expression)[] | {
|
|
5
|
+
[key: string]: Primitive | Expression;
|
|
6
|
+
};
|
|
7
|
+
export type LiteralValueOrLiteralExpression = null | string | number | boolean | LiteralValueOrLiteralExpression[] | {
|
|
8
|
+
[key: string]: LiteralValueOrLiteralExpression;
|
|
9
|
+
};
|
|
10
|
+
export type Expression = PrimitiveExpression | BinaryExpression | VariableReferenceExpression | FunctionInvocationExpression | MemberExpression | UnaryExpression;
|
|
11
|
+
export declare class PrimitiveExpression {
|
|
12
|
+
readonly expressionType = "primitive";
|
|
13
|
+
readonly value: Primitive;
|
|
14
|
+
constructor(value: Primitive);
|
|
15
|
+
toString(): string;
|
|
16
|
+
}
|
|
17
|
+
export declare class BinaryExpression {
|
|
18
|
+
readonly expressionType = "binary";
|
|
19
|
+
readonly binaryOperator: BinaryOperator;
|
|
20
|
+
readonly left: Expression;
|
|
21
|
+
readonly right: Expression;
|
|
22
|
+
constructor(left: Expression, binaryOperator: BinaryOperator, right: Expression);
|
|
23
|
+
toString(): string;
|
|
24
|
+
}
|
|
25
|
+
export declare class VariableReferenceExpression {
|
|
26
|
+
readonly expressionType = "variableReference";
|
|
27
|
+
readonly variableName: VariableName;
|
|
28
|
+
constructor(variableName: VariableName);
|
|
29
|
+
toString(): string;
|
|
30
|
+
}
|
|
31
|
+
export declare class FunctionInvocationExpression {
|
|
32
|
+
readonly expressionType = "functionInvocation";
|
|
33
|
+
readonly functionName: string;
|
|
34
|
+
readonly arguments: Expression[];
|
|
35
|
+
constructor(functionName: string, argumentExpressions: Expression[]);
|
|
36
|
+
toString(): string;
|
|
37
|
+
}
|
|
38
|
+
export declare class MemberExpression {
|
|
39
|
+
readonly expressionType = "member";
|
|
40
|
+
readonly object: Expression;
|
|
41
|
+
readonly property: Expression;
|
|
42
|
+
readonly computed: boolean;
|
|
43
|
+
constructor(object: Expression, property: Expression, computed: boolean);
|
|
44
|
+
toString(): string;
|
|
45
|
+
}
|
|
46
|
+
export declare class UnaryExpression {
|
|
47
|
+
readonly expressionType = "unary";
|
|
48
|
+
readonly operator: UnaryOperator;
|
|
49
|
+
readonly value: Expression;
|
|
50
|
+
constructor(operator: UnaryOperator, value: Expression);
|
|
51
|
+
toString(): string;
|
|
52
|
+
}
|
|
53
|
+
export declare function isExpression(val: Primitive | Expression): val is Expression;
|
|
54
|
+
export declare function expressionToLiteralValueOrLiteralExpression(ex: Expression): LiteralValueOrLiteralExpression;
|
|
55
|
+
export declare function isLiteral(ex: Expression): boolean;
|
|
56
|
+
export declare function isFullyQualifiedName(ex: Expression): boolean;
|
|
57
|
+
//# sourceMappingURL=expressions.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"expressions.d.ts","sourceRoot":"","sources":["../../src/ast/expressions.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,300 @@
|
|
|
1
|
+
import { isRecord } from '../utils.js';
|
|
2
|
+
// Operator precendence for unary and binary operators in the Workflows language.
|
|
3
|
+
// Note that these differ somewhat from Javascript.
|
|
4
|
+
// https://cloud.google.com/workflows/docs/reference/syntax/datatypes
|
|
5
|
+
const operatorPrecedenceValue = new Map([
|
|
6
|
+
['not', 7],
|
|
7
|
+
// ['-', 6], // unary minus, commented out to avoid confusion with binary minus
|
|
8
|
+
['*', 5],
|
|
9
|
+
['/', 5],
|
|
10
|
+
['//', 5],
|
|
11
|
+
['%', 5],
|
|
12
|
+
['+', 4],
|
|
13
|
+
['-', 4],
|
|
14
|
+
['<=', 3],
|
|
15
|
+
['>=', 3],
|
|
16
|
+
['<', 3],
|
|
17
|
+
['>', 3],
|
|
18
|
+
['==', 3],
|
|
19
|
+
['===', 3],
|
|
20
|
+
['!=', 3],
|
|
21
|
+
['!==', 3],
|
|
22
|
+
['in', 3],
|
|
23
|
+
['and', 2],
|
|
24
|
+
['or', 1],
|
|
25
|
+
]);
|
|
26
|
+
// A primitive (string, number, list, etc) value
|
|
27
|
+
// TODO: Is this needed? Use unboxed Primitive instead?
|
|
28
|
+
export class PrimitiveExpression {
|
|
29
|
+
expressionType = 'primitive';
|
|
30
|
+
value;
|
|
31
|
+
constructor(value) {
|
|
32
|
+
this.value = value;
|
|
33
|
+
}
|
|
34
|
+
// Return the string representation of this expression.
|
|
35
|
+
// Not enclosed in ${}.
|
|
36
|
+
toString() {
|
|
37
|
+
const val = this.value;
|
|
38
|
+
if (Array.isArray(val)) {
|
|
39
|
+
const elements = val.map((v) => {
|
|
40
|
+
return isExpression(v) ? v.toString() : primitiveToString(v);
|
|
41
|
+
});
|
|
42
|
+
return `[${elements.join(', ')}]`;
|
|
43
|
+
}
|
|
44
|
+
else if (isRecord(val)) {
|
|
45
|
+
const elements = Object.entries(val).map(([k, v]) => {
|
|
46
|
+
return `"${k}": ${isExpression(v) ? v.toString() : primitiveToString(v)}`;
|
|
47
|
+
});
|
|
48
|
+
return `{${elements.join(', ')}}`;
|
|
49
|
+
}
|
|
50
|
+
else {
|
|
51
|
+
return `${JSON.stringify(val)}`;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
// expr OPERATOR expr
|
|
56
|
+
export class BinaryExpression {
|
|
57
|
+
expressionType = 'binary';
|
|
58
|
+
binaryOperator;
|
|
59
|
+
left;
|
|
60
|
+
right;
|
|
61
|
+
constructor(left, binaryOperator, right) {
|
|
62
|
+
this.binaryOperator = binaryOperator;
|
|
63
|
+
this.left = left;
|
|
64
|
+
this.right = right;
|
|
65
|
+
}
|
|
66
|
+
toString() {
|
|
67
|
+
let leftString = this.left.toString();
|
|
68
|
+
let rightString = this.right.toString();
|
|
69
|
+
if (this.left.expressionType === 'binary') {
|
|
70
|
+
const leftOpValue = operatorPrecedenceValue.get(this.left.binaryOperator) ?? 0;
|
|
71
|
+
const thisOpValue = operatorPrecedenceValue.get(this.binaryOperator) ?? 0;
|
|
72
|
+
if (leftOpValue < thisOpValue) {
|
|
73
|
+
leftString = `(${leftString})`;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
if (this.right.expressionType === 'binary') {
|
|
77
|
+
const rightOpValue = operatorPrecedenceValue.get(this.right.binaryOperator) ?? 0;
|
|
78
|
+
const thisOpValue = operatorPrecedenceValue.get(this.binaryOperator) ?? 0;
|
|
79
|
+
if (rightOpValue < thisOpValue) {
|
|
80
|
+
rightString = `(${rightString})`;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
return `${leftString} ${this.binaryOperator} ${rightString}`;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
// Variable name: a plain identifier (y, year) or a list
|
|
87
|
+
// element accessor (names[3])
|
|
88
|
+
export class VariableReferenceExpression {
|
|
89
|
+
expressionType = 'variableReference';
|
|
90
|
+
variableName;
|
|
91
|
+
constructor(variableName) {
|
|
92
|
+
this.variableName = variableName;
|
|
93
|
+
}
|
|
94
|
+
toString() {
|
|
95
|
+
return this.variableName;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
// Function invocation with anonymous parameters: sys.get_env("GOOGLE_CLOUD_PROJECT_ID")
|
|
99
|
+
export class FunctionInvocationExpression {
|
|
100
|
+
expressionType = 'functionInvocation';
|
|
101
|
+
functionName;
|
|
102
|
+
arguments;
|
|
103
|
+
constructor(functionName, argumentExpressions) {
|
|
104
|
+
this.functionName = functionName;
|
|
105
|
+
this.arguments = argumentExpressions;
|
|
106
|
+
}
|
|
107
|
+
toString() {
|
|
108
|
+
const argumentStrings = this.arguments.map((x) => x.toString());
|
|
109
|
+
return `${this.functionName}(${argumentStrings.join(', ')})`;
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
// object.property or object[property]
|
|
113
|
+
export class MemberExpression {
|
|
114
|
+
expressionType = 'member';
|
|
115
|
+
object;
|
|
116
|
+
property;
|
|
117
|
+
computed;
|
|
118
|
+
constructor(object, property, computed) {
|
|
119
|
+
this.object = object;
|
|
120
|
+
this.property = property;
|
|
121
|
+
this.computed = computed;
|
|
122
|
+
}
|
|
123
|
+
toString() {
|
|
124
|
+
if (this.computed) {
|
|
125
|
+
return `${this.object.toString()}[${this.property.toString()}]`;
|
|
126
|
+
}
|
|
127
|
+
else {
|
|
128
|
+
return `${this.object.toString()}.${this.property.toString()}`;
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
export class UnaryExpression {
|
|
133
|
+
expressionType = 'unary';
|
|
134
|
+
operator;
|
|
135
|
+
value;
|
|
136
|
+
constructor(operator, value) {
|
|
137
|
+
this.operator = operator;
|
|
138
|
+
this.value = value;
|
|
139
|
+
}
|
|
140
|
+
toString() {
|
|
141
|
+
const separator = this.operator === 'not' ? ' ' : '';
|
|
142
|
+
let valueString = this.value.toString();
|
|
143
|
+
if (this.value.expressionType === 'binary') {
|
|
144
|
+
valueString = `(${valueString})`;
|
|
145
|
+
}
|
|
146
|
+
return `${this.operator}${separator}${valueString}`;
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
// Convert a Primitive to a string representation.
|
|
150
|
+
// Does not add ${}.
|
|
151
|
+
function primitiveToString(val) {
|
|
152
|
+
if (Array.isArray(val)) {
|
|
153
|
+
const elements = val.map((x) => {
|
|
154
|
+
return isExpression(x) ? x.toString() : primitiveToString(x);
|
|
155
|
+
});
|
|
156
|
+
return `[${elements.join(', ')}]`;
|
|
157
|
+
}
|
|
158
|
+
else if (val !== null && typeof val === 'object') {
|
|
159
|
+
const items = Object.entries(val).map(([k, v]) => {
|
|
160
|
+
return [k, isExpression(v) ? v.toString() : primitiveToString(v)];
|
|
161
|
+
});
|
|
162
|
+
const elements = items.map(([k, v]) => `"${k}":${v}`);
|
|
163
|
+
return `{${elements.join(',')}}`;
|
|
164
|
+
}
|
|
165
|
+
else {
|
|
166
|
+
return JSON.stringify(val);
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
export function isExpression(val) {
|
|
170
|
+
return val instanceof Object && 'expressionType' in val && !isRecord(val);
|
|
171
|
+
}
|
|
172
|
+
// Returns a literal for simple terms and a literal expression enclosed in ${} for complex terms.
|
|
173
|
+
export function expressionToLiteralValueOrLiteralExpression(ex) {
|
|
174
|
+
switch (ex.expressionType) {
|
|
175
|
+
case 'primitive':
|
|
176
|
+
return primitiveExpressionToLiteralValueOrLiteralExpression(ex);
|
|
177
|
+
case 'binary':
|
|
178
|
+
case 'variableReference':
|
|
179
|
+
case 'functionInvocation':
|
|
180
|
+
case 'member':
|
|
181
|
+
return `\${${ex.toString()}}`;
|
|
182
|
+
case 'unary':
|
|
183
|
+
if (ex.value.expressionType === 'primitive' &&
|
|
184
|
+
typeof ex.value.value === 'number') {
|
|
185
|
+
if (ex.operator === '+') {
|
|
186
|
+
return ex.value.value;
|
|
187
|
+
}
|
|
188
|
+
else if (ex.operator === '-') {
|
|
189
|
+
return -ex.value.value;
|
|
190
|
+
}
|
|
191
|
+
else {
|
|
192
|
+
return `\${${ex.toString()}}`;
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
else {
|
|
196
|
+
return `\${${ex.toString()}}`;
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
function primitiveExpressionToLiteralValueOrLiteralExpression(ex) {
|
|
201
|
+
if (typeof ex.value === 'number' ||
|
|
202
|
+
typeof ex.value === 'string' ||
|
|
203
|
+
typeof ex.value === 'boolean' ||
|
|
204
|
+
ex.value === null) {
|
|
205
|
+
return ex.value;
|
|
206
|
+
}
|
|
207
|
+
else if (Array.isArray(ex.value)) {
|
|
208
|
+
return ex.value.map((x) => {
|
|
209
|
+
if (isExpression(x)) {
|
|
210
|
+
return expressionToLiteralValueOrLiteralExpression(x);
|
|
211
|
+
}
|
|
212
|
+
else if (Array.isArray(x) || isRecord(x)) {
|
|
213
|
+
return primitiveExpressionToLiteralValueOrLiteralExpression(new PrimitiveExpression(x));
|
|
214
|
+
}
|
|
215
|
+
else if (x === null ||
|
|
216
|
+
typeof x === 'string' ||
|
|
217
|
+
typeof x === 'number' ||
|
|
218
|
+
typeof x === 'boolean') {
|
|
219
|
+
return x;
|
|
220
|
+
}
|
|
221
|
+
else {
|
|
222
|
+
return `\${${primitiveToString(x)}}`;
|
|
223
|
+
}
|
|
224
|
+
});
|
|
225
|
+
}
|
|
226
|
+
else if (isRecord(ex.value)) {
|
|
227
|
+
return Object.fromEntries(Object.entries(ex.value).map(([k, v]) => {
|
|
228
|
+
if (isExpression(v)) {
|
|
229
|
+
return [k, expressionToLiteralValueOrLiteralExpression(v)];
|
|
230
|
+
}
|
|
231
|
+
else if (Array.isArray(v) || isRecord(v)) {
|
|
232
|
+
return [
|
|
233
|
+
k,
|
|
234
|
+
primitiveExpressionToLiteralValueOrLiteralExpression(new PrimitiveExpression(v)),
|
|
235
|
+
];
|
|
236
|
+
}
|
|
237
|
+
else if (v === null ||
|
|
238
|
+
typeof v === 'string' ||
|
|
239
|
+
typeof v === 'number' ||
|
|
240
|
+
typeof v === 'boolean') {
|
|
241
|
+
return [k, v];
|
|
242
|
+
}
|
|
243
|
+
else {
|
|
244
|
+
return [k, `\${${primitiveToString(v)}}`];
|
|
245
|
+
}
|
|
246
|
+
}));
|
|
247
|
+
}
|
|
248
|
+
else {
|
|
249
|
+
return `\${${ex.toString()}}`;
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
// Returns true if expression is a literal value.
|
|
253
|
+
// Examples of literals: number, string, array of numbers or strings, etc.
|
|
254
|
+
// Examples of non-literals: array that contains complex expressions.
|
|
255
|
+
export function isLiteral(ex) {
|
|
256
|
+
switch (ex.expressionType) {
|
|
257
|
+
case 'primitive':
|
|
258
|
+
return primitiveIsLiteral(ex.value);
|
|
259
|
+
case 'unary':
|
|
260
|
+
return isLiteral(ex.value);
|
|
261
|
+
case 'binary':
|
|
262
|
+
case 'variableReference':
|
|
263
|
+
case 'functionInvocation':
|
|
264
|
+
case 'member':
|
|
265
|
+
return false;
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
function primitiveIsLiteral(value) {
|
|
269
|
+
if (Array.isArray(value)) {
|
|
270
|
+
return value.every((x) => {
|
|
271
|
+
return isExpression(x) ? isLiteral(x) : primitiveIsLiteral(x);
|
|
272
|
+
});
|
|
273
|
+
}
|
|
274
|
+
else if (isRecord(value)) {
|
|
275
|
+
return Object.values(value).every((x) => {
|
|
276
|
+
return isExpression(x) ? isLiteral(x) : primitiveIsLiteral(x);
|
|
277
|
+
});
|
|
278
|
+
}
|
|
279
|
+
else {
|
|
280
|
+
return (typeof value === 'string' ||
|
|
281
|
+
typeof value === 'number' ||
|
|
282
|
+
typeof value === 'boolean' ||
|
|
283
|
+
value === null);
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
export function isFullyQualifiedName(ex) {
|
|
287
|
+
switch (ex.expressionType) {
|
|
288
|
+
case 'primitive':
|
|
289
|
+
case 'binary':
|
|
290
|
+
case 'functionInvocation':
|
|
291
|
+
return false;
|
|
292
|
+
case 'variableReference':
|
|
293
|
+
return true;
|
|
294
|
+
case 'member':
|
|
295
|
+
return (isFullyQualifiedName(ex.object) &&
|
|
296
|
+
(ex.computed || isFullyQualifiedName(ex.property)));
|
|
297
|
+
case 'unary':
|
|
298
|
+
return isFullyQualifiedName(ex.value);
|
|
299
|
+
}
|
|
300
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
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
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"stepnames.d.ts","sourceRoot":"","sources":["../../src/ast/stepnames.ts"],"names":[],"mappings":"AAAA,OAAO,EASL,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"}
|