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.
- package/README.md +17 -7
- package/dist/ast/expressions.d.ts +37 -41
- package/dist/ast/expressions.d.ts.map +1 -1
- package/dist/ast/expressions.js +124 -255
- package/dist/ast/statements.d.ts +132 -0
- package/dist/ast/statements.d.ts.map +1 -0
- package/dist/ast/statements.js +197 -0
- package/dist/ast/steps.d.ts +82 -194
- package/dist/ast/steps.d.ts.map +1 -1
- package/dist/ast/steps.js +862 -523
- package/dist/ast/workflows.d.ts +14 -7
- package/dist/ast/workflows.d.ts.map +1 -1
- package/dist/ast/workflows.js +19 -10
- package/dist/cli.js +45 -32
- package/dist/errors.d.ts +4 -0
- package/dist/errors.d.ts.map +1 -1
- package/dist/errors.js +7 -0
- package/dist/transpiler/index.d.ts +14 -1
- package/dist/transpiler/index.d.ts.map +1 -1
- package/dist/transpiler/index.js +150 -31
- package/dist/transpiler/linker.d.ts +3 -0
- package/dist/transpiler/linker.d.ts.map +1 -0
- package/dist/transpiler/linker.js +41 -0
- package/dist/transpiler/parseexpressions.d.ts +11 -0
- package/dist/transpiler/parseexpressions.d.ts.map +1 -0
- package/dist/transpiler/{expressions.js → parseexpressions.js} +90 -103
- package/dist/transpiler/parsestatement.d.ts +7 -0
- package/dist/transpiler/parsestatement.d.ts.map +1 -0
- package/dist/transpiler/parsestatement.js +862 -0
- package/dist/transpiler/stepnames.d.ts +3 -0
- package/dist/transpiler/stepnames.d.ts.map +1 -0
- package/dist/transpiler/stepnames.js +17 -0
- package/dist/transpiler/transformations.d.ts +3 -19
- package/dist/transpiler/transformations.d.ts.map +1 -1
- package/dist/transpiler/transformations.js +267 -322
- package/language_reference.md +8 -2
- package/package.json +8 -6
- package/dist/ast/stepnames.d.ts +0 -9
- package/dist/ast/stepnames.d.ts.map +0 -1
- package/dist/ast/stepnames.js +0 -280
- package/dist/transpiler/expressions.d.ts +0 -11
- package/dist/transpiler/expressions.d.ts.map +0 -1
- package/dist/transpiler/statements.d.ts +0 -10
- package/dist/transpiler/statements.d.ts.map +0 -1
- package/dist/transpiler/statements.js +0 -1098
- package/dist/utils.d.ts +0 -2
- package/dist/utils.d.ts.map +0 -1
- package/dist/utils.js +0 -3
package/README.md
CHANGED
|
@@ -9,11 +9,9 @@ Only a subset of Typescript features are supported. The [language reference](lan
|
|
|
9
9
|
|
|
10
10
|
See the [samples](samples) directory for code examples.
|
|
11
11
|
|
|
12
|
-
Project status: It's possible to write Workflows programs, but the transpiler has not been extensively tested. Expect some rough edges!
|
|
13
|
-
|
|
14
12
|
## Installation
|
|
15
13
|
|
|
16
|
-
```
|
|
14
|
+
```sh
|
|
17
15
|
npm install ts2workflows
|
|
18
16
|
```
|
|
19
17
|
|
|
@@ -31,12 +29,24 @@ Compile multiple files and write result to an output directory `workflowsfiles`.
|
|
|
31
29
|
npx ts2workflows --project samples/tsconfig.json --outdir workflowsfiles samples/*.ts
|
|
32
30
|
```
|
|
33
31
|
|
|
32
|
+
The `--link` argument generates YAML output that includes all necessary subworkflows (i.e. imported Typescript functions) in a single file. It will generate one output file for each input file.
|
|
33
|
+
|
|
34
|
+
```sh
|
|
35
|
+
npx ts2workflows --link --project samples/tsconfig.json --outdir workflowsfiles samples/sample*.ts
|
|
36
|
+
```
|
|
37
|
+
|
|
34
38
|
When developing ts2workflows, you can run the transpiler directly from the source directory:
|
|
35
39
|
|
|
36
40
|
```sh
|
|
37
41
|
npx tsx src/cli.ts samples/sample1.ts
|
|
38
42
|
```
|
|
39
43
|
|
|
44
|
+
### Command arguments
|
|
45
|
+
|
|
46
|
+
- `--project`: Path to TSConfig for the Typescript sources files
|
|
47
|
+
- `--link`: Emit a self-contained YAML. That is, the output includes code from the main input file and all subworkflows imported from the main file. Without this, emits only subworkflows in the input file. Requires --project.
|
|
48
|
+
- `--[no-]generated-file-comment`: Start the output with a comment mentioning that the file has been generated by ts2workflows.
|
|
49
|
+
|
|
40
50
|
## Type checking workflow sources
|
|
41
51
|
|
|
42
52
|
One benefit of writing the workflow programs in Typescript is that the sources can be type checked.
|
|
@@ -61,20 +71,20 @@ Type checking step is completely separate from the transpiling. The ts2workflows
|
|
|
61
71
|
|
|
62
72
|
### Build
|
|
63
73
|
|
|
64
|
-
```
|
|
74
|
+
```sh
|
|
65
75
|
npm install
|
|
66
76
|
npm run build
|
|
67
77
|
```
|
|
68
78
|
|
|
69
79
|
### Run unit tests
|
|
70
80
|
|
|
71
|
-
```
|
|
81
|
+
```sh
|
|
72
82
|
npm run test
|
|
73
83
|
```
|
|
74
84
|
|
|
75
85
|
Run tests and print the test coverage:
|
|
76
86
|
|
|
77
|
-
```
|
|
87
|
+
```sh
|
|
78
88
|
npm run test-coverage
|
|
79
89
|
```
|
|
80
90
|
|
|
@@ -92,7 +102,7 @@ The main function implementing these phases is `transpile()` in [src/transpiler/
|
|
|
92
102
|
|
|
93
103
|
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.
|
|
94
104
|
|
|
95
|
-
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/
|
|
105
|
+
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/statements.ts](src/ast/statements.ts) for statement types and [src/ast/expressions.ts](src/ast/expressions.ts) for expression types.
|
|
96
106
|
|
|
97
107
|
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).
|
|
98
108
|
|
|
@@ -1,66 +1,62 @@
|
|
|
1
1
|
export type VariableName = string;
|
|
2
2
|
export type BinaryOperator = '+' | '-' | '*' | '/' | '//' | '%' | '==' | '!=' | '>' | '>=' | '<' | '<=' | 'in' | 'and' | 'or';
|
|
3
3
|
export type UnaryOperator = '-' | '+' | 'not';
|
|
4
|
-
export type Primitive = null | string | number | boolean | (Primitive | Expression)[] | {
|
|
5
|
-
[key: string]: Primitive | Expression;
|
|
6
|
-
};
|
|
7
4
|
export type LiteralValueOrLiteralExpression = null | string | number | boolean | LiteralValueOrLiteralExpression[] | {
|
|
8
5
|
[key: string]: LiteralValueOrLiteralExpression;
|
|
9
6
|
};
|
|
10
|
-
export type Expression =
|
|
11
|
-
|
|
12
|
-
readonly
|
|
13
|
-
readonly value:
|
|
14
|
-
constructor(value: Primitive);
|
|
15
|
-
toString(): string;
|
|
7
|
+
export type Expression = NullExpression | StringExpression | NumberExpression | BooleanExpression | ListExpression | MapExpression | BinaryExpression | VariableReferenceExpression | FunctionInvocationExpression | MemberExpression | UnaryExpression;
|
|
8
|
+
interface ExpressionLiteral<V, Tag> {
|
|
9
|
+
readonly tag: Tag;
|
|
10
|
+
readonly value: V;
|
|
16
11
|
}
|
|
17
|
-
export
|
|
18
|
-
export
|
|
19
|
-
export
|
|
20
|
-
export
|
|
21
|
-
|
|
12
|
+
export type NullExpression = ExpressionLiteral<null, 'null'>;
|
|
13
|
+
export type StringExpression = ExpressionLiteral<string, 'string'>;
|
|
14
|
+
export type NumberExpression = ExpressionLiteral<number, 'number'>;
|
|
15
|
+
export type BooleanExpression = ExpressionLiteral<boolean, 'boolean'>;
|
|
16
|
+
export type ListExpression = ExpressionLiteral<Expression[], 'list'>;
|
|
17
|
+
export type MapExpression = ExpressionLiteral<Record<string, Expression>, 'map'>;
|
|
18
|
+
export declare const nullEx: NullExpression;
|
|
19
|
+
export declare function stringEx(value: string): StringExpression;
|
|
20
|
+
export declare function numberEx(value: number): NumberExpression;
|
|
21
|
+
export declare function booleanEx(value: boolean): BooleanExpression;
|
|
22
|
+
export declare const trueEx: BooleanExpression;
|
|
23
|
+
export declare const falseEx: BooleanExpression;
|
|
24
|
+
export declare function listEx(value: Expression[]): ListExpression;
|
|
25
|
+
export declare function mapEx(value: Record<string, Expression>): MapExpression;
|
|
26
|
+
export interface BinaryExpression {
|
|
27
|
+
readonly tag: 'binary';
|
|
22
28
|
readonly binaryOperator: BinaryOperator;
|
|
23
29
|
readonly left: Expression;
|
|
24
30
|
readonly right: Expression;
|
|
25
|
-
constructor(left: Expression, binaryOperator: BinaryOperator, right: Expression);
|
|
26
|
-
toString(): string;
|
|
27
31
|
}
|
|
28
|
-
export declare
|
|
29
|
-
|
|
32
|
+
export declare function binaryEx(left: Expression, binaryOperator: BinaryOperator, right: Expression): BinaryExpression;
|
|
33
|
+
export interface VariableReferenceExpression {
|
|
34
|
+
readonly tag: 'variableReference';
|
|
30
35
|
readonly variableName: VariableName;
|
|
31
|
-
constructor(variableName: VariableName);
|
|
32
|
-
toString(): string;
|
|
33
36
|
}
|
|
34
|
-
export declare
|
|
35
|
-
|
|
37
|
+
export declare function variableReferenceEx(variableName: VariableName): VariableReferenceExpression;
|
|
38
|
+
export interface FunctionInvocationExpression {
|
|
39
|
+
readonly tag: 'functionInvocation';
|
|
36
40
|
readonly functionName: string;
|
|
37
41
|
readonly arguments: Expression[];
|
|
38
|
-
constructor(functionName: string, argumentExpressions: Expression[]);
|
|
39
|
-
toString(): string;
|
|
40
42
|
}
|
|
41
|
-
export declare
|
|
42
|
-
|
|
43
|
+
export declare function functionInvocationEx(functionName: string, argumentExpressions: Expression[]): FunctionInvocationExpression;
|
|
44
|
+
export interface MemberExpression {
|
|
45
|
+
readonly tag: 'member';
|
|
43
46
|
readonly object: Expression;
|
|
44
47
|
readonly property: Expression;
|
|
45
48
|
readonly computed: boolean;
|
|
46
|
-
constructor(object: Expression, property: Expression, computed: boolean);
|
|
47
|
-
toString(): string;
|
|
48
49
|
}
|
|
49
|
-
export declare
|
|
50
|
-
|
|
50
|
+
export declare function memberEx(object: Expression, property: Expression, computed: boolean): MemberExpression;
|
|
51
|
+
export interface UnaryExpression {
|
|
52
|
+
readonly tag: 'unary';
|
|
51
53
|
readonly operator: UnaryOperator;
|
|
52
54
|
readonly value: Expression;
|
|
53
|
-
constructor(operator: UnaryOperator, value: Expression);
|
|
54
|
-
toString(): string;
|
|
55
55
|
}
|
|
56
|
-
export declare function
|
|
57
|
-
export declare function
|
|
58
|
-
export declare function safeAsExpression(x: Primitive | Expression | undefined): Expression | undefined;
|
|
56
|
+
export declare function unaryEx(operator: UnaryOperator, value: Expression): UnaryExpression;
|
|
57
|
+
export declare function expressionToString(ex: Expression): string;
|
|
59
58
|
export declare function expressionToLiteralValueOrLiteralExpression(ex: Expression): LiteralValueOrLiteralExpression;
|
|
60
|
-
export declare function
|
|
61
|
-
export declare function
|
|
62
|
-
|
|
63
|
-
* Returns true if ex is pure expression (can't have side-effects)
|
|
64
|
-
*/
|
|
65
|
-
export declare function isPure(ex: Expression): boolean;
|
|
59
|
+
export declare function isQualifiedName(ex: Expression): boolean;
|
|
60
|
+
export declare function isPrimitive(ex: Expression): ex is StringExpression | NumberExpression | BooleanExpression | NullExpression;
|
|
61
|
+
export {};
|
|
66
62
|
//# sourceMappingURL=expressions.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"expressions.d.ts","sourceRoot":"","sources":["../../src/ast/expressions.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"expressions.d.ts","sourceRoot":"","sources":["../../src/ast/expressions.ts"],"names":[],"mappings":"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"}
|
package/dist/ast/expressions.js
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import * as R from 'ramda';
|
|
2
|
-
import { isRecord } from '../utils.js';
|
|
3
2
|
// Operator precendence for unary and binary operators in the Workflows language.
|
|
4
3
|
// Note that these differ somewhat from Javascript.
|
|
5
4
|
// https://cloud.google.com/workflows/docs/reference/syntax/datatypes
|
|
@@ -24,171 +23,130 @@ const operatorPrecedenceValue = new Map([
|
|
|
24
23
|
['and', 2],
|
|
25
24
|
['or', 1],
|
|
26
25
|
]);
|
|
27
|
-
|
|
28
|
-
export
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
export
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
26
|
+
export const nullEx = { tag: 'null', value: null };
|
|
27
|
+
export function stringEx(value) {
|
|
28
|
+
return { tag: 'string', value };
|
|
29
|
+
}
|
|
30
|
+
export function numberEx(value) {
|
|
31
|
+
return { tag: 'number', value };
|
|
32
|
+
}
|
|
33
|
+
export function booleanEx(value) {
|
|
34
|
+
return { tag: 'boolean', value };
|
|
35
|
+
}
|
|
36
|
+
export const trueEx = booleanEx(true);
|
|
37
|
+
export const falseEx = booleanEx(false);
|
|
38
|
+
export function listEx(value) {
|
|
39
|
+
return { tag: 'list', value };
|
|
40
|
+
}
|
|
41
|
+
export function mapEx(value) {
|
|
42
|
+
return { tag: 'map', value };
|
|
43
|
+
}
|
|
44
|
+
export function binaryEx(left, binaryOperator, right) {
|
|
45
|
+
return {
|
|
46
|
+
tag: 'binary',
|
|
47
|
+
binaryOperator,
|
|
48
|
+
left,
|
|
49
|
+
right,
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
export function variableReferenceEx(variableName) {
|
|
53
|
+
return { tag: 'variableReference', variableName };
|
|
54
|
+
}
|
|
55
|
+
export function functionInvocationEx(functionName, argumentExpressions) {
|
|
56
|
+
return {
|
|
57
|
+
tag: 'functionInvocation',
|
|
58
|
+
functionName,
|
|
59
|
+
arguments: argumentExpressions,
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
export function memberEx(object, property, computed) {
|
|
63
|
+
return { tag: 'member', object, property, computed };
|
|
64
|
+
}
|
|
65
|
+
export function unaryEx(operator, value) {
|
|
66
|
+
return { tag: 'unary', operator, value };
|
|
67
|
+
}
|
|
68
|
+
// Returns a string representation of ex, not enclosed in ${}
|
|
69
|
+
export function expressionToString(ex) {
|
|
70
|
+
switch (ex.tag) {
|
|
71
|
+
case 'string':
|
|
72
|
+
return JSON.stringify(ex.value);
|
|
73
|
+
case 'number':
|
|
74
|
+
return ex.value.toString();
|
|
75
|
+
case 'boolean':
|
|
76
|
+
return ex.value ? 'true' : 'false';
|
|
77
|
+
case 'null':
|
|
78
|
+
return 'null';
|
|
79
|
+
case 'list':
|
|
80
|
+
return '[' + ex.value.map(expressionToString).join(', ') + ']';
|
|
81
|
+
case 'map':
|
|
82
|
+
return ('{' +
|
|
83
|
+
Object.entries(ex.value)
|
|
84
|
+
.map(([key, val]) => JSON.stringify(key) + ': ' + expressionToString(val))
|
|
85
|
+
.join(', ') +
|
|
86
|
+
'}');
|
|
87
|
+
case 'binary':
|
|
88
|
+
return binaryExpressionToString(ex);
|
|
89
|
+
case 'variableReference':
|
|
90
|
+
return ex.variableName;
|
|
91
|
+
case 'functionInvocation':
|
|
92
|
+
return `${ex.functionName}(${ex.arguments.map(expressionToString).join(', ')})`;
|
|
93
|
+
case 'member':
|
|
94
|
+
if (ex.computed) {
|
|
95
|
+
return `${expressionToString(ex.object)}[${expressionToString(ex.property)}]`;
|
|
77
96
|
}
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
const rightOpValue = operatorPrecedenceValue.get(this.right.binaryOperator) ?? 0;
|
|
81
|
-
const thisOpValue = operatorPrecedenceValue.get(this.binaryOperator) ?? 0;
|
|
82
|
-
if (rightOpValue < thisOpValue) {
|
|
83
|
-
rightString = `(${rightString})`;
|
|
97
|
+
else {
|
|
98
|
+
return `${expressionToString(ex.object)}.${expressionToString(ex.property)}`;
|
|
84
99
|
}
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
}
|
|
88
|
-
}
|
|
89
|
-
// Variable name: a plain identifier (y, year) or a list
|
|
90
|
-
// element accessor (names[3])
|
|
91
|
-
export class VariableReferenceExpression {
|
|
92
|
-
expressionType = 'variableReference';
|
|
93
|
-
variableName;
|
|
94
|
-
constructor(variableName) {
|
|
95
|
-
this.variableName = variableName;
|
|
96
|
-
}
|
|
97
|
-
toString() {
|
|
98
|
-
return this.variableName;
|
|
99
|
-
}
|
|
100
|
-
}
|
|
101
|
-
// Function invocation with anonymous parameters: sys.get_env("GOOGLE_CLOUD_PROJECT_ID")
|
|
102
|
-
export class FunctionInvocationExpression {
|
|
103
|
-
expressionType = 'functionInvocation';
|
|
104
|
-
functionName;
|
|
105
|
-
arguments;
|
|
106
|
-
constructor(functionName, argumentExpressions) {
|
|
107
|
-
this.functionName = functionName;
|
|
108
|
-
this.arguments = argumentExpressions;
|
|
109
|
-
}
|
|
110
|
-
toString() {
|
|
111
|
-
const argumentStrings = this.arguments.map((x) => x.toString());
|
|
112
|
-
return `${this.functionName}(${argumentStrings.join(', ')})`;
|
|
100
|
+
case 'unary':
|
|
101
|
+
return unaryExpressionToString(ex);
|
|
113
102
|
}
|
|
114
103
|
}
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
this.property = property;
|
|
124
|
-
this.computed = computed;
|
|
125
|
-
}
|
|
126
|
-
toString() {
|
|
127
|
-
if (this.computed) {
|
|
128
|
-
return `${this.object.toString()}[${this.property.toString()}]`;
|
|
129
|
-
}
|
|
130
|
-
else {
|
|
131
|
-
return `${this.object.toString()}.${this.property.toString()}`;
|
|
104
|
+
function binaryExpressionToString(ex) {
|
|
105
|
+
let leftString = expressionToString(ex.left);
|
|
106
|
+
let rightString = expressionToString(ex.right);
|
|
107
|
+
if (ex.left.tag === 'binary') {
|
|
108
|
+
const leftOpValue = operatorPrecedenceValue.get(ex.left.binaryOperator) ?? 0;
|
|
109
|
+
const thisOpValue = operatorPrecedenceValue.get(ex.binaryOperator) ?? 0;
|
|
110
|
+
if (leftOpValue < thisOpValue) {
|
|
111
|
+
leftString = `(${leftString})`;
|
|
132
112
|
}
|
|
133
113
|
}
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
constructor(operator, value) {
|
|
140
|
-
this.operator = operator;
|
|
141
|
-
this.value = value;
|
|
142
|
-
}
|
|
143
|
-
toString() {
|
|
144
|
-
const separator = this.operator === 'not' ? ' ' : '';
|
|
145
|
-
let valueString = this.value.toString();
|
|
146
|
-
if (this.value.expressionType === 'binary') {
|
|
147
|
-
valueString = `(${valueString})`;
|
|
114
|
+
if (ex.right.tag === 'binary') {
|
|
115
|
+
const rightOpValue = operatorPrecedenceValue.get(ex.right.binaryOperator) ?? 0;
|
|
116
|
+
const thisOpValue = operatorPrecedenceValue.get(ex.binaryOperator) ?? 0;
|
|
117
|
+
if (rightOpValue < thisOpValue) {
|
|
118
|
+
rightString = `(${rightString})`;
|
|
148
119
|
}
|
|
149
|
-
return `${this.operator}${separator}${valueString}`;
|
|
150
120
|
}
|
|
121
|
+
return `${leftString} ${ex.binaryOperator} ${rightString}`;
|
|
151
122
|
}
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
function primitiveToString(val) {
|
|
158
|
-
const valuesToString = R.map(expressionOrPrimitiveToString);
|
|
159
|
-
if (Array.isArray(val)) {
|
|
160
|
-
return `[${valuesToString(val).join(', ')}]`;
|
|
123
|
+
function unaryExpressionToString(ex) {
|
|
124
|
+
const separator = ex.operator === 'not' ? ' ' : '';
|
|
125
|
+
let valueString = expressionToString(ex.value);
|
|
126
|
+
if (ex.value.tag === 'binary') {
|
|
127
|
+
valueString = `(${valueString})`;
|
|
161
128
|
}
|
|
162
|
-
|
|
163
|
-
const elements = Object.values(valuesToString(val)).map(([k, v]) => `"${k}":${v}`);
|
|
164
|
-
return `{${elements.join(',')}}`;
|
|
165
|
-
}
|
|
166
|
-
else {
|
|
167
|
-
return JSON.stringify(val);
|
|
168
|
-
}
|
|
169
|
-
}
|
|
170
|
-
export function isExpression(val) {
|
|
171
|
-
return val instanceof Object && 'expressionType' in val && !isRecord(val);
|
|
172
|
-
}
|
|
173
|
-
export function asExpression(x) {
|
|
174
|
-
return isExpression(x) ? x : new PrimitiveExpression(x);
|
|
175
|
-
}
|
|
176
|
-
export function safeAsExpression(x) {
|
|
177
|
-
return x === undefined ? undefined : asExpression(x);
|
|
129
|
+
return `${ex.operator}${separator}${valueString}`;
|
|
178
130
|
}
|
|
179
131
|
// Returns a literal for simple terms and a literal expression enclosed in ${} for complex terms.
|
|
180
132
|
export function expressionToLiteralValueOrLiteralExpression(ex) {
|
|
181
|
-
switch (ex.
|
|
182
|
-
case '
|
|
183
|
-
|
|
133
|
+
switch (ex.tag) {
|
|
134
|
+
case 'string':
|
|
135
|
+
case 'number':
|
|
136
|
+
case 'boolean':
|
|
137
|
+
case 'null':
|
|
138
|
+
return ex.value;
|
|
139
|
+
case 'list':
|
|
140
|
+
return R.map(expressionToLiteralValueOrLiteralExpression, ex.value);
|
|
141
|
+
case 'map':
|
|
142
|
+
return R.map(expressionToLiteralValueOrLiteralExpression, ex.value);
|
|
184
143
|
case 'binary':
|
|
185
144
|
case 'variableReference':
|
|
186
145
|
case 'functionInvocation':
|
|
187
146
|
case 'member':
|
|
188
|
-
return `\${${ex
|
|
147
|
+
return `\${${expressionToString(ex)}}`;
|
|
189
148
|
case 'unary':
|
|
190
|
-
if (ex.value.
|
|
191
|
-
typeof ex.value.value === 'number') {
|
|
149
|
+
if (ex.value.tag === 'number') {
|
|
192
150
|
if (ex.operator === '+') {
|
|
193
151
|
return ex.value.value;
|
|
194
152
|
}
|
|
@@ -196,98 +154,22 @@ export function expressionToLiteralValueOrLiteralExpression(ex) {
|
|
|
196
154
|
return -ex.value.value;
|
|
197
155
|
}
|
|
198
156
|
else {
|
|
199
|
-
return `\${${ex
|
|
157
|
+
return `\${${expressionToString(ex)}}`;
|
|
200
158
|
}
|
|
201
159
|
}
|
|
202
160
|
else {
|
|
203
|
-
return `\${${ex
|
|
204
|
-
}
|
|
205
|
-
}
|
|
206
|
-
}
|
|
207
|
-
function primitiveExpressionToLiteralValueOrLiteralExpression(ex) {
|
|
208
|
-
if (typeof ex.value === 'number' ||
|
|
209
|
-
typeof ex.value === 'string' ||
|
|
210
|
-
typeof ex.value === 'boolean' ||
|
|
211
|
-
ex.value === null) {
|
|
212
|
-
return ex.value;
|
|
213
|
-
}
|
|
214
|
-
else if (Array.isArray(ex.value)) {
|
|
215
|
-
return ex.value.map((x) => {
|
|
216
|
-
if (isExpression(x)) {
|
|
217
|
-
return expressionToLiteralValueOrLiteralExpression(x);
|
|
218
|
-
}
|
|
219
|
-
else if (Array.isArray(x) || isRecord(x)) {
|
|
220
|
-
return primitiveExpressionToLiteralValueOrLiteralExpression(new PrimitiveExpression(x));
|
|
221
|
-
}
|
|
222
|
-
else if (x === null ||
|
|
223
|
-
typeof x === 'string' ||
|
|
224
|
-
typeof x === 'number' ||
|
|
225
|
-
typeof x === 'boolean') {
|
|
226
|
-
return x;
|
|
227
|
-
}
|
|
228
|
-
else {
|
|
229
|
-
return `\${${primitiveToString(x)}}`;
|
|
161
|
+
return `\${${expressionToString(ex)}}`;
|
|
230
162
|
}
|
|
231
|
-
});
|
|
232
|
-
}
|
|
233
|
-
else if (isRecord(ex.value)) {
|
|
234
|
-
const mapRecord = R.map((v) => {
|
|
235
|
-
if (isExpression(v)) {
|
|
236
|
-
return expressionToLiteralValueOrLiteralExpression(v);
|
|
237
|
-
}
|
|
238
|
-
else if (Array.isArray(v) || isRecord(v)) {
|
|
239
|
-
return primitiveExpressionToLiteralValueOrLiteralExpression(new PrimitiveExpression(v));
|
|
240
|
-
}
|
|
241
|
-
else if (v === null ||
|
|
242
|
-
typeof v === 'string' ||
|
|
243
|
-
typeof v === 'number' ||
|
|
244
|
-
typeof v === 'boolean') {
|
|
245
|
-
return v;
|
|
246
|
-
}
|
|
247
|
-
else {
|
|
248
|
-
return `\${${primitiveToString(v)}}`;
|
|
249
|
-
}
|
|
250
|
-
});
|
|
251
|
-
return mapRecord(ex.value);
|
|
252
|
-
}
|
|
253
|
-
else {
|
|
254
|
-
return `\${${ex.toString()}}`;
|
|
255
163
|
}
|
|
256
164
|
}
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
case '
|
|
263
|
-
|
|
264
|
-
case '
|
|
265
|
-
return isLiteral(ex.value);
|
|
266
|
-
case 'binary':
|
|
267
|
-
case 'variableReference':
|
|
268
|
-
case 'functionInvocation':
|
|
269
|
-
case 'member':
|
|
270
|
-
return false;
|
|
271
|
-
}
|
|
272
|
-
}
|
|
273
|
-
const expressionOrPrimitiveIsLiteral = R.ifElse(isExpression, isLiteral, primitiveIsLiteral);
|
|
274
|
-
function primitiveIsLiteral(value) {
|
|
275
|
-
if (Array.isArray(value)) {
|
|
276
|
-
return value.every(expressionOrPrimitiveIsLiteral);
|
|
277
|
-
}
|
|
278
|
-
else if (isRecord(value)) {
|
|
279
|
-
return Object.values(value).every(expressionOrPrimitiveIsLiteral);
|
|
280
|
-
}
|
|
281
|
-
else {
|
|
282
|
-
return (typeof value === 'string' ||
|
|
283
|
-
typeof value === 'number' ||
|
|
284
|
-
typeof value === 'boolean' ||
|
|
285
|
-
value === null);
|
|
286
|
-
}
|
|
287
|
-
}
|
|
288
|
-
export function isFullyQualifiedName(ex) {
|
|
289
|
-
switch (ex.expressionType) {
|
|
290
|
-
case 'primitive':
|
|
165
|
+
export function isQualifiedName(ex) {
|
|
166
|
+
switch (ex.tag) {
|
|
167
|
+
case 'string':
|
|
168
|
+
case 'number':
|
|
169
|
+
case 'boolean':
|
|
170
|
+
case 'null':
|
|
171
|
+
case 'list':
|
|
172
|
+
case 'map':
|
|
291
173
|
case 'binary':
|
|
292
174
|
case 'functionInvocation':
|
|
293
175
|
case 'unary':
|
|
@@ -295,27 +177,14 @@ export function isFullyQualifiedName(ex) {
|
|
|
295
177
|
case 'variableReference':
|
|
296
178
|
return true;
|
|
297
179
|
case 'member':
|
|
298
|
-
return (
|
|
299
|
-
(
|
|
300
|
-
(ex.computed && ex.property
|
|
180
|
+
return (isQualifiedName(ex.object) &&
|
|
181
|
+
(isQualifiedName(ex.property) ||
|
|
182
|
+
(ex.computed && isPrimitive(ex.property))));
|
|
301
183
|
}
|
|
302
184
|
}
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
case 'primitive':
|
|
309
|
-
return true;
|
|
310
|
-
case 'binary':
|
|
311
|
-
return isPure(ex.left) && isPure(ex.right);
|
|
312
|
-
case 'functionInvocation':
|
|
313
|
-
return false;
|
|
314
|
-
case 'variableReference':
|
|
315
|
-
return true;
|
|
316
|
-
case 'member':
|
|
317
|
-
return isPure(ex.object) && isPure(ex.property);
|
|
318
|
-
case 'unary':
|
|
319
|
-
return isPure(ex.value);
|
|
320
|
-
}
|
|
185
|
+
export function isPrimitive(ex) {
|
|
186
|
+
return (ex.tag === 'string' ||
|
|
187
|
+
ex.tag === 'number' ||
|
|
188
|
+
ex.tag === 'boolean' ||
|
|
189
|
+
ex.tag === 'null');
|
|
321
190
|
}
|