@qrvey/formula-lang 0.0.3 → 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +46 -29
- package/__tests__/src/parser.test.ts +17 -0
- package/__tests__/src/transpiler.test.ts +147 -0
- package/dist/constants/index.d.ts +32 -0
- package/dist/constants/index.js +65 -0
- package/dist/constants/interfaces.d.ts +60 -0
- package/dist/constants/interfaces.js +1 -0
- package/dist/errors/definitions.d.ts +11 -0
- package/dist/errors/definitions.js +18 -0
- package/dist/errors/elasticsearch.d.ts +24 -0
- package/dist/errors/elasticsearch.js +26 -0
- package/dist/errors/index.d.ts +5 -0
- package/dist/errors/index.js +38 -0
- package/dist/errors/snowflake.d.ts +15 -0
- package/dist/errors/snowflake.js +16 -0
- package/dist/functions/index.d.ts +2 -0
- package/dist/functions/index.js +4 -0
- package/dist/functions/mid.d.ts +2 -0
- package/dist/functions/mid.js +47 -0
- package/dist/grammar/generated/qformula.lang.js +15 -15
- package/dist/grammar/qformula.grammar.d.ts +1 -1
- package/dist/grammar/qformula.grammar.js +1 -1
- package/dist/index.d.ts +8 -2
- package/dist/index.js +17 -2
- package/dist/parser/json-parser.d.ts +3 -3
- package/dist/parser/json-parser.js +47 -38
- package/dist/transpiler/index.d.ts +15 -0
- package/dist/transpiler/index.js +77 -0
- package/dist/utils/index.d.ts +4 -0
- package/dist/utils/index.js +3 -0
- package/package.json +31 -6
package/README.md
CHANGED
|
@@ -1,51 +1,68 @@
|
|
|
1
1
|
# Qrvey Formula Language
|
|
2
|
+
[](http://commitizen.github.io/cz-cli/)
|
|
3
|
+
|
|
2
4
|
|
|
3
5
|
## Usage
|
|
4
6
|
|
|
5
|
-
|
|
7
|
+
### Transpile function
|
|
8
|
+
```js
|
|
9
|
+
import { ENGINES, Transpile } from "@qrvey/formula-lang";
|
|
6
10
|
|
|
11
|
+
const program = "MID(MID(\"This is a test\", 1, 5), 1, 2)"
|
|
12
|
+
const transpiled = Transpile(program, ENGINES.ELASTICSEARCH)
|
|
13
|
+
console.log(transpiled)
|
|
14
|
+
```
|
|
15
|
+
the output will be
|
|
16
|
+
```
|
|
17
|
+
'This is a test'.substring(1, 5).substring(1, 2)
|
|
7
18
|
```
|
|
8
|
-
import { QFormulaLang, calculateAST } from "@qrvey/formula-lang"
|
|
9
|
-
import { iRootAST, ENGINES, Transpiler } from '../../qrvey-lang/transpiler/lib/constants/interfaces';
|
|
10
|
-
|
|
11
|
-
// input expression
|
|
12
|
-
const expression = 'MID("this is an ex", 1)';
|
|
13
|
-
|
|
14
|
-
// get the parser from QFormulaLang
|
|
15
|
-
const parser = QFormulaLang.parser;
|
|
16
|
-
|
|
17
|
-
// get the tree from input expression
|
|
18
|
-
const tree = parser.parse(expression);
|
|
19
19
|
|
|
20
|
-
|
|
21
|
-
const ast = calculateAST(expression, tree.topNode);
|
|
20
|
+
### TranspileAST function
|
|
22
21
|
|
|
23
|
-
|
|
24
|
-
|
|
22
|
+
```js
|
|
23
|
+
import { ENGINES, TranspileAST } from "@qrvey/formula-lang";
|
|
25
24
|
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
25
|
+
const ast = {
|
|
26
|
+
"type": "Program",
|
|
27
|
+
"exp": "1 + 2",
|
|
28
|
+
"lang": "QrveyLang",
|
|
29
|
+
"version": "0.0.0",
|
|
30
|
+
"body": {
|
|
31
|
+
"operator": "+",
|
|
32
|
+
"type": "BinaryExpression",
|
|
33
|
+
"left": {
|
|
34
|
+
"type": "Literal",
|
|
35
|
+
"dataType": "number",
|
|
36
|
+
"value": 1
|
|
37
|
+
},
|
|
38
|
+
"right": {
|
|
39
|
+
"type": "Literal",
|
|
40
|
+
"dataType": "number",
|
|
41
|
+
"value": 2
|
|
42
|
+
}
|
|
33
43
|
}
|
|
34
|
-
return result
|
|
35
44
|
}
|
|
45
|
+
const transpiled = TranspileAST(ast, ENGINES.ELASTICSEARCH)
|
|
46
|
+
console.log(transpiled)
|
|
36
47
|
```
|
|
37
|
-
|
|
38
|
-
|
|
48
|
+
the output will be
|
|
49
|
+
```
|
|
50
|
+
(1 + 2)
|
|
39
51
|
```
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
### codemirror editor
|
|
55
|
+
```js
|
|
40
56
|
import { EditorState } from "@codemirror/state";
|
|
41
|
-
import {
|
|
57
|
+
import { basicSetup } from "codemirror";
|
|
58
|
+
import { FormulaHighlight, calculateAST } from "@qrvey/formula-lang"
|
|
42
59
|
|
|
43
60
|
// set the editor state with the QFormula plugin. This adds the support for
|
|
44
61
|
this._state = EditorState.create({
|
|
45
|
-
doc:
|
|
62
|
+
doc: 'Test formula',
|
|
46
63
|
extensions: [
|
|
47
64
|
basicSetup,
|
|
48
|
-
|
|
65
|
+
FormulaHighlight(),
|
|
49
66
|
...
|
|
50
67
|
]
|
|
51
68
|
})
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { AST_TYPES } from '../../src/constants';
|
|
2
|
+
import { calculateAST, FormulaLang } from '../../src';
|
|
3
|
+
import { BinaryExpressionAST } from '../../src/constants/interfaces';
|
|
4
|
+
|
|
5
|
+
describe('Parser tests suite', () => {
|
|
6
|
+
test('Should parse a single node', () => {
|
|
7
|
+
const program = '1 + 2';
|
|
8
|
+
const parser = FormulaLang.parser;
|
|
9
|
+
const { topNode } = parser.parse(program);
|
|
10
|
+
const ast = calculateAST(program, topNode);
|
|
11
|
+
expect(ast?.body).not.toBeUndefined();
|
|
12
|
+
|
|
13
|
+
const body = ast?.body as BinaryExpressionAST;
|
|
14
|
+
expect(body.operator).toBe('+');
|
|
15
|
+
expect(body.type).toBe(AST_TYPES.binaryExpression);
|
|
16
|
+
});
|
|
17
|
+
});
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
import { ENGINES } from '../../src/constants';
|
|
2
|
+
import { ProgramAST } from '../../src/constants/interfaces';
|
|
3
|
+
import { Transpiler } from '../../src/transpiler';
|
|
4
|
+
import { Transpile } from '../../src/index';
|
|
5
|
+
|
|
6
|
+
describe('Transpiler tests suit', () => {
|
|
7
|
+
test('Should resolve basic arithmetic operation', () => {
|
|
8
|
+
const AST: ProgramAST = {
|
|
9
|
+
type: 'Program',
|
|
10
|
+
exp: '1 + 1',
|
|
11
|
+
lang: 'QrveyLang',
|
|
12
|
+
version: '0.0.0',
|
|
13
|
+
body: {
|
|
14
|
+
operator: '+',
|
|
15
|
+
type: 'BinaryExpression',
|
|
16
|
+
left: {
|
|
17
|
+
type: 'Literal',
|
|
18
|
+
dataType: 'number',
|
|
19
|
+
value: 1,
|
|
20
|
+
},
|
|
21
|
+
right: {
|
|
22
|
+
type: 'Literal',
|
|
23
|
+
dataType: 'number',
|
|
24
|
+
value: 2,
|
|
25
|
+
},
|
|
26
|
+
},
|
|
27
|
+
} as ProgramAST;
|
|
28
|
+
|
|
29
|
+
const transpiler = new Transpiler(AST, ENGINES.ELASTICSEARCH);
|
|
30
|
+
const result = transpiler.get();
|
|
31
|
+
expect(result.expression).toBe('(1 + 2)');
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
test('Should resolve basic logic operation', () => {
|
|
35
|
+
const AST: ProgramAST = {
|
|
36
|
+
type: 'Program',
|
|
37
|
+
exp: '1 <= 1',
|
|
38
|
+
lang: 'QrveyLang',
|
|
39
|
+
version: '0.0.0',
|
|
40
|
+
body: {
|
|
41
|
+
operator: '<=',
|
|
42
|
+
type: 'BinaryExpression',
|
|
43
|
+
left: {
|
|
44
|
+
type: 'Literal',
|
|
45
|
+
dataType: 'number',
|
|
46
|
+
value: 1,
|
|
47
|
+
},
|
|
48
|
+
right: {
|
|
49
|
+
type: 'Literal',
|
|
50
|
+
dataType: 'number',
|
|
51
|
+
value: 2,
|
|
52
|
+
},
|
|
53
|
+
},
|
|
54
|
+
} as ProgramAST;
|
|
55
|
+
|
|
56
|
+
const transpiler = new Transpiler(AST, ENGINES.ELASTICSEARCH);
|
|
57
|
+
const result = transpiler.get();
|
|
58
|
+
expect(result.expression).toBe('(1 <= 2)');
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
test('Should resolve MID function', () => {
|
|
62
|
+
const AST: ProgramAST = {
|
|
63
|
+
type: 'Program',
|
|
64
|
+
exp: 'MID("string", 2, 3)',
|
|
65
|
+
lang: 'QrveyLang',
|
|
66
|
+
version: '0.0.0',
|
|
67
|
+
body: {
|
|
68
|
+
type: 'FunctionCall',
|
|
69
|
+
name: 'MID',
|
|
70
|
+
arguments: [
|
|
71
|
+
{
|
|
72
|
+
type: 'Literal',
|
|
73
|
+
dataType: 'string',
|
|
74
|
+
value: 'string',
|
|
75
|
+
},
|
|
76
|
+
{
|
|
77
|
+
type: 'Literal',
|
|
78
|
+
dataType: 'number',
|
|
79
|
+
value: 2,
|
|
80
|
+
},
|
|
81
|
+
{
|
|
82
|
+
type: 'Literal',
|
|
83
|
+
dataType: 'number',
|
|
84
|
+
value: 3,
|
|
85
|
+
},
|
|
86
|
+
],
|
|
87
|
+
},
|
|
88
|
+
} as ProgramAST;
|
|
89
|
+
|
|
90
|
+
const transpiler = new Transpiler(AST, ENGINES.ELASTICSEARCH);
|
|
91
|
+
const result = transpiler.get();
|
|
92
|
+
expect(result.expression).toBe(`'string'.substring(2, 3)`);
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
test('Should throw a exception because is a invalid expression (function does not exists)', () => {
|
|
96
|
+
const AST: ProgramAST = {
|
|
97
|
+
type: 'Program',
|
|
98
|
+
exp: 'MID1("string", 2, 3)',
|
|
99
|
+
lang: 'QrveyLang',
|
|
100
|
+
version: '0.0.0',
|
|
101
|
+
body: {
|
|
102
|
+
type: 'FunctionCall',
|
|
103
|
+
name: 'MID1',
|
|
104
|
+
arguments: [
|
|
105
|
+
{
|
|
106
|
+
type: 'Literal',
|
|
107
|
+
dataType: 'string',
|
|
108
|
+
value: 'string',
|
|
109
|
+
},
|
|
110
|
+
{
|
|
111
|
+
type: 'Literal',
|
|
112
|
+
dataType: 'number',
|
|
113
|
+
value: 2,
|
|
114
|
+
},
|
|
115
|
+
{
|
|
116
|
+
type: 'Literal',
|
|
117
|
+
dataType: 'number',
|
|
118
|
+
value: 3,
|
|
119
|
+
},
|
|
120
|
+
],
|
|
121
|
+
},
|
|
122
|
+
} as ProgramAST;
|
|
123
|
+
|
|
124
|
+
const transpiler = new Transpiler(AST, ENGINES.ELASTICSEARCH);
|
|
125
|
+
const result = transpiler.get();
|
|
126
|
+
expect(result.valid).toBe(false);
|
|
127
|
+
expect(result.error).not.toBeUndefined();
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
test('should transpile a function to elasticsearch', () => {
|
|
131
|
+
const program = 'MID(MID("This is a test", 1, 5), 1, 2)';
|
|
132
|
+
const result = Transpile(program, ENGINES.ELASTICSEARCH);
|
|
133
|
+
expect(result?.valid).toBe(true);
|
|
134
|
+
expect(result?.expression).toBe(
|
|
135
|
+
"'This is a test'.substring(1, 5).substring(1, 2)",
|
|
136
|
+
);
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
test('should transpile a function to snowflake', () => {
|
|
140
|
+
const program = 'MID(MID("This is a test", 1, 5), 1, 2)';
|
|
141
|
+
const result = Transpile(program, ENGINES.SNOWFLAKE);
|
|
142
|
+
expect(result?.valid).toBe(true);
|
|
143
|
+
expect(result?.expression).toBe(
|
|
144
|
+
"SUBSTRING(SUBSTRING('This is a test', 1, 5), 1, 2)",
|
|
145
|
+
);
|
|
146
|
+
});
|
|
147
|
+
});
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
export declare enum ENGINES {
|
|
2
|
+
ELASTICSEARCH = "elasticsearch",
|
|
3
|
+
SNOWFLAKE = "snowflake"
|
|
4
|
+
}
|
|
5
|
+
export declare enum AST_TYPES {
|
|
6
|
+
program = "Program",
|
|
7
|
+
binaryExpression = "BinaryExpression",
|
|
8
|
+
unaryExpression = "UnaryExpression",
|
|
9
|
+
functionCall = "FunctionCall",
|
|
10
|
+
variable = "Variable",
|
|
11
|
+
literal = "Literal"
|
|
12
|
+
}
|
|
13
|
+
export declare enum ERROR_LIST {
|
|
14
|
+
arguments = "arguments",
|
|
15
|
+
nulls = "nulls",
|
|
16
|
+
floatingPoint = "floatingPoint",
|
|
17
|
+
missingString = "missingString",
|
|
18
|
+
missingNumber = "missingNumber",
|
|
19
|
+
greaterThanZero = "greaterThanZero",
|
|
20
|
+
missingArg = "missingArg",
|
|
21
|
+
unknownFunction = "unknownFunction",
|
|
22
|
+
unknown = "unknown"
|
|
23
|
+
}
|
|
24
|
+
type iErrorsType = {
|
|
25
|
+
[key in ERROR_LIST]: IErrorDictionary;
|
|
26
|
+
};
|
|
27
|
+
interface IErrorDictionary {
|
|
28
|
+
message: string;
|
|
29
|
+
code: string;
|
|
30
|
+
}
|
|
31
|
+
export declare const ERROR_DICTIONARY: iErrorsType;
|
|
32
|
+
export {};
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
export var ENGINES;
|
|
2
|
+
(function (ENGINES) {
|
|
3
|
+
ENGINES["ELASTICSEARCH"] = "elasticsearch";
|
|
4
|
+
ENGINES["SNOWFLAKE"] = "snowflake";
|
|
5
|
+
// REDSHIFT = 'redshift',
|
|
6
|
+
})(ENGINES || (ENGINES = {}));
|
|
7
|
+
export var AST_TYPES;
|
|
8
|
+
(function (AST_TYPES) {
|
|
9
|
+
AST_TYPES["program"] = "Program";
|
|
10
|
+
AST_TYPES["binaryExpression"] = "BinaryExpression";
|
|
11
|
+
AST_TYPES["unaryExpression"] = "UnaryExpression";
|
|
12
|
+
AST_TYPES["functionCall"] = "FunctionCall";
|
|
13
|
+
AST_TYPES["variable"] = "Variable";
|
|
14
|
+
AST_TYPES["literal"] = "Literal";
|
|
15
|
+
})(AST_TYPES || (AST_TYPES = {}));
|
|
16
|
+
export var ERROR_LIST;
|
|
17
|
+
(function (ERROR_LIST) {
|
|
18
|
+
ERROR_LIST["arguments"] = "arguments";
|
|
19
|
+
ERROR_LIST["nulls"] = "nulls";
|
|
20
|
+
ERROR_LIST["floatingPoint"] = "floatingPoint";
|
|
21
|
+
ERROR_LIST["missingString"] = "missingString";
|
|
22
|
+
ERROR_LIST["missingNumber"] = "missingNumber";
|
|
23
|
+
ERROR_LIST["greaterThanZero"] = "greaterThanZero";
|
|
24
|
+
ERROR_LIST["missingArg"] = "missingArg";
|
|
25
|
+
ERROR_LIST["unknownFunction"] = "unknownFunction";
|
|
26
|
+
ERROR_LIST["unknown"] = "unknown";
|
|
27
|
+
})(ERROR_LIST || (ERROR_LIST = {}));
|
|
28
|
+
export const ERROR_DICTIONARY = {
|
|
29
|
+
[ERROR_LIST.arguments]: {
|
|
30
|
+
message: 'Ilegal number of arguments for function',
|
|
31
|
+
code: 'NUMBER_ARGS',
|
|
32
|
+
},
|
|
33
|
+
[ERROR_LIST.nulls]: {
|
|
34
|
+
message: 'Can not apply function with null values',
|
|
35
|
+
code: 'NULL_FOUNDS',
|
|
36
|
+
},
|
|
37
|
+
[ERROR_LIST.floatingPoint]: {
|
|
38
|
+
message: 'Invalid floating point operation to integer',
|
|
39
|
+
code: 'FLOATING_POINT',
|
|
40
|
+
},
|
|
41
|
+
[ERROR_LIST.missingString]: {
|
|
42
|
+
message: 'Element has to be a String',
|
|
43
|
+
code: 'MISSING_STRING',
|
|
44
|
+
},
|
|
45
|
+
[ERROR_LIST.missingNumber]: {
|
|
46
|
+
message: 'Element has to be a Number',
|
|
47
|
+
code: 'MISSING_NUMBER',
|
|
48
|
+
},
|
|
49
|
+
[ERROR_LIST.greaterThanZero]: {
|
|
50
|
+
message: 'Element has to be greater than 0',
|
|
51
|
+
code: 'GREATER_THAN_ZERO',
|
|
52
|
+
},
|
|
53
|
+
[ERROR_LIST.missingArg]: {
|
|
54
|
+
message: 'Missing arguments',
|
|
55
|
+
code: 'MISSING_ARG',
|
|
56
|
+
},
|
|
57
|
+
[ERROR_LIST.unknownFunction]: {
|
|
58
|
+
message: 'Unknown function',
|
|
59
|
+
code: 'UNKNOWN_FUNCTION',
|
|
60
|
+
},
|
|
61
|
+
[ERROR_LIST.unknown]: {
|
|
62
|
+
message: 'Unknown error',
|
|
63
|
+
code: 'UNKNOWN',
|
|
64
|
+
},
|
|
65
|
+
};
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { ENGINES, AST_TYPES } from '.';
|
|
2
|
+
interface FunctionElementDefinition {
|
|
3
|
+
identifier: string;
|
|
4
|
+
name: string;
|
|
5
|
+
}
|
|
6
|
+
export interface FunctionDefinition extends FunctionElementDefinition {
|
|
7
|
+
transpiler: TranspilerEnginesFunction;
|
|
8
|
+
parameters?: Array<FunctionParameters>;
|
|
9
|
+
}
|
|
10
|
+
type TranspilerEnginesFunction = {
|
|
11
|
+
[key in ENGINES]: Function;
|
|
12
|
+
};
|
|
13
|
+
interface FunctionParameters extends FunctionElementDefinition {
|
|
14
|
+
type: string;
|
|
15
|
+
optional: boolean;
|
|
16
|
+
validator: Array<ValidatorFunctionParameter>;
|
|
17
|
+
}
|
|
18
|
+
interface ValidatorFunctionParameter {
|
|
19
|
+
(args: any): ValidatorResponse;
|
|
20
|
+
}
|
|
21
|
+
export interface ValidatorResponse {
|
|
22
|
+
valid: boolean;
|
|
23
|
+
code?: string;
|
|
24
|
+
message?: string;
|
|
25
|
+
}
|
|
26
|
+
export interface TranspilationResponse {
|
|
27
|
+
valid: boolean;
|
|
28
|
+
expression?: string;
|
|
29
|
+
error?: any;
|
|
30
|
+
}
|
|
31
|
+
export interface FunctionList {
|
|
32
|
+
[identifier: string]: FunctionDefinition;
|
|
33
|
+
}
|
|
34
|
+
export interface CommonAST {
|
|
35
|
+
type: AST_TYPES;
|
|
36
|
+
}
|
|
37
|
+
export interface ProgramAST extends CommonAST {
|
|
38
|
+
exp: string;
|
|
39
|
+
lang: string;
|
|
40
|
+
version: string;
|
|
41
|
+
body?: CommonAST;
|
|
42
|
+
}
|
|
43
|
+
export interface UnaryExpressionAST extends CommonAST {
|
|
44
|
+
operator: string;
|
|
45
|
+
right: CommonAST;
|
|
46
|
+
}
|
|
47
|
+
export interface BinaryExpressionAST extends UnaryExpressionAST {
|
|
48
|
+
left: CommonAST;
|
|
49
|
+
}
|
|
50
|
+
export interface FunctionCallAST extends CommonAST {
|
|
51
|
+
name: string;
|
|
52
|
+
arguments: Array<CommonAST>;
|
|
53
|
+
}
|
|
54
|
+
export interface VariableAST extends CommonAST {
|
|
55
|
+
value: string | boolean | number | CommonAST[] | null;
|
|
56
|
+
}
|
|
57
|
+
export interface LiteralAST extends VariableAST {
|
|
58
|
+
dataType: string;
|
|
59
|
+
}
|
|
60
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export declare class BaseError extends Error {
|
|
2
|
+
code: string;
|
|
3
|
+
constructor(code: string, message: string);
|
|
4
|
+
toString(): {
|
|
5
|
+
code: string;
|
|
6
|
+
message: string;
|
|
7
|
+
};
|
|
8
|
+
}
|
|
9
|
+
export declare class GenericError extends BaseError {
|
|
10
|
+
constructor(message?: string);
|
|
11
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { ERROR_DICTIONARY, ERROR_LIST } from '../constants';
|
|
2
|
+
export class BaseError extends Error {
|
|
3
|
+
constructor(code, message) {
|
|
4
|
+
super(message);
|
|
5
|
+
this.code = code;
|
|
6
|
+
}
|
|
7
|
+
toString() {
|
|
8
|
+
return {
|
|
9
|
+
code: this.code,
|
|
10
|
+
message: this.message,
|
|
11
|
+
};
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
export class GenericError extends BaseError {
|
|
15
|
+
constructor(message = ERROR_DICTIONARY[ERROR_LIST.unknown].message) {
|
|
16
|
+
super(ERROR_DICTIONARY[ERROR_LIST.unknown].code, message);
|
|
17
|
+
}
|
|
18
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { ERROR_LIST } from '../constants';
|
|
2
|
+
export declare const errorRegex: ({
|
|
3
|
+
unknownFunction: {
|
|
4
|
+
regex: RegExp;
|
|
5
|
+
value: ERROR_LIST;
|
|
6
|
+
};
|
|
7
|
+
missingArguments: {
|
|
8
|
+
regex: RegExp;
|
|
9
|
+
value: ERROR_LIST;
|
|
10
|
+
};
|
|
11
|
+
} | {
|
|
12
|
+
arguments: {
|
|
13
|
+
regex: RegExp;
|
|
14
|
+
value: ERROR_LIST;
|
|
15
|
+
};
|
|
16
|
+
state: {
|
|
17
|
+
regex: RegExp;
|
|
18
|
+
value: ERROR_LIST;
|
|
19
|
+
};
|
|
20
|
+
null_pointer: {
|
|
21
|
+
regex: RegExp;
|
|
22
|
+
value: ERROR_LIST;
|
|
23
|
+
};
|
|
24
|
+
})[];
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { ERROR_LIST } from '../constants';
|
|
2
|
+
const reasonsRegex = {
|
|
3
|
+
unknownFunction: {
|
|
4
|
+
regex: /Unknown call \[.*\] with/,
|
|
5
|
+
value: ERROR_LIST.unknownFunction,
|
|
6
|
+
},
|
|
7
|
+
missingArguments: {
|
|
8
|
+
regex: /static method \[.*\] not found/,
|
|
9
|
+
value: ERROR_LIST.arguments,
|
|
10
|
+
},
|
|
11
|
+
};
|
|
12
|
+
const typesRegex = {
|
|
13
|
+
arguments: {
|
|
14
|
+
regex: /illegal_argument_exception/,
|
|
15
|
+
value: ERROR_LIST.arguments,
|
|
16
|
+
},
|
|
17
|
+
state: {
|
|
18
|
+
regex: /illegal_state_exception/,
|
|
19
|
+
value: ERROR_LIST.nulls,
|
|
20
|
+
},
|
|
21
|
+
null_pointer: {
|
|
22
|
+
regex: /null_pointer_exception/,
|
|
23
|
+
value: ERROR_LIST.nulls,
|
|
24
|
+
},
|
|
25
|
+
};
|
|
26
|
+
export const errorRegex = [reasonsRegex, typesRegex];
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import { ValidatorResponse } from '../constants/interfaces';
|
|
2
|
+
export declare function engineErrors(messages: Array<string>, regexs: Array<object>): ValidatorResponse;
|
|
3
|
+
export declare function isStringParam(param: any): ValidatorResponse;
|
|
4
|
+
export declare function isNumberParam(param: any): ValidatorResponse;
|
|
5
|
+
export declare function isPositiveNumberParam(param: any): ValidatorResponse;
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { ERROR_LIST, ERROR_DICTIONARY } from '../constants';
|
|
2
|
+
export function engineErrors(messages, regexs) {
|
|
3
|
+
const loopParams = (obj, str) => {
|
|
4
|
+
for (const key in obj) {
|
|
5
|
+
const regex = new RegExp(obj[key].regex, 'g');
|
|
6
|
+
if (regex.test(str)) {
|
|
7
|
+
return obj[key].value;
|
|
8
|
+
}
|
|
9
|
+
}
|
|
10
|
+
};
|
|
11
|
+
let value;
|
|
12
|
+
messages.forEach((message) => {
|
|
13
|
+
regexs.forEach((regex) => {
|
|
14
|
+
value = loopParams(regex, message);
|
|
15
|
+
});
|
|
16
|
+
});
|
|
17
|
+
if (!value)
|
|
18
|
+
value = ERROR_LIST.unknown;
|
|
19
|
+
return Object.assign({ valid: false }, ERROR_DICTIONARY[value]);
|
|
20
|
+
}
|
|
21
|
+
export function isStringParam(param) {
|
|
22
|
+
const valid = typeof param === 'string';
|
|
23
|
+
if (!valid)
|
|
24
|
+
return Object.assign({ valid }, ERROR_DICTIONARY[ERROR_LIST.missingString]);
|
|
25
|
+
return { valid };
|
|
26
|
+
}
|
|
27
|
+
export function isNumberParam(param) {
|
|
28
|
+
const valid = typeof param === 'number';
|
|
29
|
+
if (!valid)
|
|
30
|
+
return Object.assign({ valid }, ERROR_DICTIONARY[ERROR_LIST.missingNumber]);
|
|
31
|
+
return { valid };
|
|
32
|
+
}
|
|
33
|
+
export function isPositiveNumberParam(param) {
|
|
34
|
+
const valid = param >= 0;
|
|
35
|
+
if (!valid)
|
|
36
|
+
return Object.assign({ valid }, ERROR_DICTIONARY[ERROR_LIST.greaterThanZero]);
|
|
37
|
+
return { valid };
|
|
38
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { ERROR_LIST } from '../constants';
|
|
2
|
+
export declare const errorRegex: {
|
|
3
|
+
missingArguments: {
|
|
4
|
+
regex: RegExp;
|
|
5
|
+
value: ERROR_LIST;
|
|
6
|
+
};
|
|
7
|
+
floatingPoint: {
|
|
8
|
+
regex: RegExp;
|
|
9
|
+
value: ERROR_LIST;
|
|
10
|
+
};
|
|
11
|
+
unknownFunction: {
|
|
12
|
+
regex: RegExp;
|
|
13
|
+
value: ERROR_LIST;
|
|
14
|
+
};
|
|
15
|
+
}[];
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { ERROR_LIST } from '../constants';
|
|
2
|
+
const reasonsRegex = {
|
|
3
|
+
missingArguments: {
|
|
4
|
+
regex: /not enough arguments for function/,
|
|
5
|
+
value: ERROR_LIST.arguments,
|
|
6
|
+
},
|
|
7
|
+
floatingPoint: {
|
|
8
|
+
regex: /Invalid floating point operation/,
|
|
9
|
+
value: ERROR_LIST.floatingPoint,
|
|
10
|
+
},
|
|
11
|
+
unknownFunction: {
|
|
12
|
+
regex: /Unknown function/,
|
|
13
|
+
value: ERROR_LIST.unknownFunction,
|
|
14
|
+
},
|
|
15
|
+
};
|
|
16
|
+
export const errorRegex = [reasonsRegex];
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import * as errorList from '../errors';
|
|
2
|
+
export const MID = {
|
|
3
|
+
identifier: 'MID',
|
|
4
|
+
name: 'MID',
|
|
5
|
+
parameters: [
|
|
6
|
+
{
|
|
7
|
+
identifier: 'CURRENT',
|
|
8
|
+
name: 'current',
|
|
9
|
+
type: 'String',
|
|
10
|
+
optional: false,
|
|
11
|
+
validator: [errorList.isStringParam],
|
|
12
|
+
},
|
|
13
|
+
{
|
|
14
|
+
identifier: 'STARTING_AT',
|
|
15
|
+
name: 'starting_at',
|
|
16
|
+
type: 'Number',
|
|
17
|
+
optional: false,
|
|
18
|
+
validator: [errorList.isNumberParam, customIsPositive],
|
|
19
|
+
},
|
|
20
|
+
{
|
|
21
|
+
identifier: 'EXTRACT_LENGTH',
|
|
22
|
+
name: 'extract_length',
|
|
23
|
+
type: 'Number',
|
|
24
|
+
optional: true,
|
|
25
|
+
validator: [customIsPositive],
|
|
26
|
+
},
|
|
27
|
+
],
|
|
28
|
+
transpiler: {
|
|
29
|
+
elasticsearch,
|
|
30
|
+
snowflake,
|
|
31
|
+
},
|
|
32
|
+
};
|
|
33
|
+
//TODO esto no sirve para nada, es solo un ejemplo de implementacion.
|
|
34
|
+
function customIsPositive(param) {
|
|
35
|
+
const isPositive = errorList.isPositiveNumberParam(param);
|
|
36
|
+
if (!isPositive.valid)
|
|
37
|
+
return isPositive;
|
|
38
|
+
return { valid: true };
|
|
39
|
+
}
|
|
40
|
+
function elasticsearch(current, start, length) {
|
|
41
|
+
const optional = length ? `, ${length}` : '';
|
|
42
|
+
return `${current}.substring(${start}${optional})`;
|
|
43
|
+
}
|
|
44
|
+
function snowflake(current, start, length) {
|
|
45
|
+
const optional = length ? `, ${length}` : '';
|
|
46
|
+
return `SUBSTRING(${current}, ${start}${optional})`;
|
|
47
|
+
}
|
|
@@ -2,21 +2,21 @@ import { LRParser } from '@lezer/lr';
|
|
|
2
2
|
import { LRLanguage, LanguageSupport } from '@codemirror/language';
|
|
3
3
|
import { styleTags, tags } from '@lezer/highlight';
|
|
4
4
|
// This file was generated by lezer-generator. You probably shouldn't edit it.
|
|
5
|
-
const spec_identifier = { __proto__: null, true: 14, false: 14 };
|
|
5
|
+
const spec_identifier = { __proto__: null, true: 14, false: 14, null: 16 };
|
|
6
6
|
const parser = LRParser.deserialize({
|
|
7
7
|
version: 14,
|
|
8
|
-
states: "%
|
|
9
|
-
stateData: "
|
|
10
|
-
goto: "#
|
|
11
|
-
nodeNames: "⚠ LineComment Program Expression Value Number String Boolean Array Function FunctionIdentifier FunctionArguments Variable UnaryExpression ArithOp BinaryExpression ArithOp ArithOp ParenthesizedExpression",
|
|
12
|
-
maxTerm:
|
|
8
|
+
states: "%vO]QPOOO!]QPO'#CeOOQO'#C`'#C`OOQO'#Cg'#CgO!dQPO'#CfO!iQQO'#CiO]QPO'#CjQ!nQSOOO]QPO'#CpOOQO'#C_'#C_O!|QPO'#CzO#UQPO,59PO#ZQPO,59QO#bQPO,59TOOQO,59U,59UO]QPO,59WO]QPO,59WO]QPO,59WO#gQSO,59[O#xQPO'#CqO$SQPO,59fOOQO1G.k1G.kO$[QSO'#DOOOQO'#Ch'#ChO$pQPO1G.lOOQO1G.o1G.oOOQO1G.r1G.rO%WQSO1G.rO%bQSO1G.rOOQO1G.v1G.vOOQO,59],59]OOQO-E6o-E6oO%lQPO'#CrO%vQPO,59jOOQO7+$W7+$WO&OQSO,59^OOQO-E6p-E6p",
|
|
9
|
+
stateData: "&k~OiOSjOSPOS~OTQOUQOVQOWQO_UOkROmPOqWOtTO~OTQOUQOVQOWQOmPO~OlnP~PzOq[O~Ou]O~O_aOa_Ob`Oc`O~OocOlnX~OleO~OprP~P]OsiO~O_aOa_Ob`Oc`OpmO~OleXoeX~PzOocOlna~O_aOa_Ob`Oc`OopOprX~OprO~Oa_O_`ig`ip`io`i~Ob`ic`i~P$uOb`Oc`O~P$uOofXpfX~P]OopOpra~O_aOa_Ob`Oc`Oofapfa~OPbijki~",
|
|
10
|
+
goto: "#usPPPt!^PPPP!n!z#U#`!z!zP!zPPP!z#c#iPPPPPPP#oPPP#rQVOQ^UQbWQf[Qj_Qk`QlaRsp`XOUW[_`apQYPRnceQOPUW[_`acpaXOUW[_`apaSOUW[_`apRh[QdYRodQqfRtqRZPRg[",
|
|
11
|
+
nodeNames: "⚠ LineComment Program Expression Value Number String Boolean Null Array Function FunctionIdentifier FunctionArguments Variable UnaryExpression ArithOp BinaryExpression LogicOp ArithOp ArithOp ParenthesizedExpression",
|
|
12
|
+
maxTerm: 37,
|
|
13
13
|
skippedNodes: [0, 1],
|
|
14
14
|
repeatNodeCount: 2,
|
|
15
|
-
tokenData: "
|
|
16
|
-
tokenizers: [0, 1],
|
|
15
|
+
tokenData: "*U~RpXY#VYZ#zZ[#V]^#zpq#Vrs$Pxy%myz%rz{%w{|%||}'O}!O%|!O!P'T!P!Q'f!Q![(V!^!_(s!_!`)Q!`!a)V!c!})a!}#O)z#P#Q*P#R#S&g#T#o)a$f$g#V#BY#BZ#V$IS$I_#V$I|$I}#z$I}$JO#z$JT$JU#V$KV$KW#V&FU&FV#V?HT?HU#V~#[Yi~XY#VZ[#Vpq#V$f$g#V#BY#BZ#V$IS$I_#V$JT$JU#V$KV$KW#V&FU&FV#V?HT?HU#V~$POj~~$SVOr$Prs$is#O$P#O#P$n#P;'S$P;'S;=`%g<%lO$P~$nOU~~$qRO;'S$P;'S;=`$z;=`O$P~$}WOr$Prs$is#O$P#O#P$n#P;'S$P;'S;=`%g;=`<%l$P<%lO$P~%jP;=`<%l$P~%rOq~~%wOp~~%|Oc~V&TU_TuQ{|&g}!O&g!Q![&g!c!}&g#R#S&g#T#o&gQ&lUuQ{|&g}!O&g!Q![&g!c!}&g#R#S&g#T#o&g~'TOo~P'WP!Q!['ZP'`QTP!Q!['Z#R#S'Z~'kPb~!P!Q'n~'sSP~OY'nZ;'S'n;'S;=`(P<%lO'n~(SP;=`<%l'nR(^VTPuQ{|&g}!O&g!O!P'Z!Q![(V!c!}&g#R#S(V#T#o&gT(zQmPaS!_!`)Q!`!a)QS)VOaST)^PlPaS!_!`)QR)hUuQkP{|&g}!O&g!Q![)a!c!})a#R#S)a#T#o)a~*POt~~*UOs~",
|
|
16
|
+
tokenizers: [0, 1, 2],
|
|
17
17
|
topRules: { "Program": [0, 2] },
|
|
18
|
-
specialized: [{ term:
|
|
19
|
-
tokenPrec:
|
|
18
|
+
specialized: [{ term: 27, get: value => spec_identifier[value] || -1 }],
|
|
19
|
+
tokenPrec: 250
|
|
20
20
|
});
|
|
21
21
|
const QFormulaLang = LRLanguage.define({
|
|
22
22
|
parser: parser.configure({
|
|
@@ -28,13 +28,13 @@ const QFormulaLang = LRLanguage.define({
|
|
|
28
28
|
Variable: tags.keyword,
|
|
29
29
|
LineComment: tags.lineComment,
|
|
30
30
|
FunctionIdentifier: tags.lineComment,
|
|
31
|
-
|
|
32
|
-
})
|
|
33
|
-
]
|
|
31
|
+
'( )': tags.paren,
|
|
32
|
+
}),
|
|
33
|
+
],
|
|
34
34
|
}),
|
|
35
35
|
languageData: {
|
|
36
|
-
commentTokens: { line:
|
|
37
|
-
}
|
|
36
|
+
commentTokens: { line: '//' },
|
|
37
|
+
},
|
|
38
38
|
});
|
|
39
39
|
function QFormula() {
|
|
40
40
|
return new LanguageSupport(QFormulaLang);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export * from
|
|
1
|
+
export * from './generated/qformula.lang.js';
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export * from
|
|
1
|
+
export * from './generated/qformula.lang.js';
|
package/dist/index.d.ts
CHANGED
|
@@ -1,2 +1,8 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
import { ENGINES } from './constants/index';
|
|
2
|
+
import { TranspilationResponse } from './constants/interfaces';
|
|
3
|
+
export { QFormula as FormulaHighlight, QFormulaLang as FormulaLang, } from './grammar/qformula.grammar';
|
|
4
|
+
export { calculateAST } from './parser/json-parser';
|
|
5
|
+
export { ENGINES } from './constants/index';
|
|
6
|
+
export { TranspileAST } from './transpiler/index';
|
|
7
|
+
export { ProgramAST } from './constants/interfaces';
|
|
8
|
+
export declare function Transpile(program: string, engine: ENGINES): TranspilationResponse | undefined;
|
package/dist/index.js
CHANGED
|
@@ -1,2 +1,17 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
import { QFormulaLang } from './grammar/qformula.grammar';
|
|
2
|
+
import { calculateAST } from './parser/json-parser';
|
|
3
|
+
import { TranspileAST } from './transpiler/index';
|
|
4
|
+
export { QFormula as FormulaHighlight, QFormulaLang as FormulaLang, } from './grammar/qformula.grammar';
|
|
5
|
+
export { calculateAST } from './parser/json-parser';
|
|
6
|
+
export { ENGINES } from './constants/index';
|
|
7
|
+
export { TranspileAST } from './transpiler/index';
|
|
8
|
+
export function Transpile(program, engine) {
|
|
9
|
+
const parser = QFormulaLang.parser;
|
|
10
|
+
const { topNode } = parser.parse(program);
|
|
11
|
+
if (!topNode)
|
|
12
|
+
return;
|
|
13
|
+
const ast = calculateAST(program, topNode);
|
|
14
|
+
if (!ast)
|
|
15
|
+
return;
|
|
16
|
+
return TranspileAST(ast, engine);
|
|
17
|
+
}
|
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
export declare function calculateAST(program: string, startNode: SyntaxNode):
|
|
1
|
+
import { SyntaxNode } from '@lezer/common';
|
|
2
|
+
import { ProgramAST } from '../constants/interfaces';
|
|
3
|
+
export declare function calculateAST(program: string, startNode: SyntaxNode): ProgramAST | undefined;
|
|
@@ -1,20 +1,19 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
3
|
-
const
|
|
1
|
+
import { AST_TYPES } from '../constants';
|
|
2
|
+
import { getNodeValue } from '../utils';
|
|
3
|
+
const PARSER_VERSION = '0.0.0';
|
|
4
|
+
const LANG_NAME = 'QrveyLang';
|
|
4
5
|
export function calculateAST(program, startNode) {
|
|
5
6
|
if (!startNode)
|
|
6
|
-
return
|
|
7
|
+
return;
|
|
7
8
|
return {
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
9
|
+
type: AST_TYPES.program,
|
|
10
|
+
exp: program,
|
|
11
|
+
lang: LANG_NAME,
|
|
12
|
+
version: PARSER_VERSION,
|
|
13
|
+
body: transformNode(program, startNode),
|
|
13
14
|
};
|
|
14
15
|
}
|
|
15
16
|
function transformNode(program, node) {
|
|
16
|
-
if (!node)
|
|
17
|
-
return { type: 'undefined' };
|
|
18
17
|
switch (node.name) {
|
|
19
18
|
case 'BinaryExpression':
|
|
20
19
|
return transformBinaryExpression(program, node);
|
|
@@ -24,48 +23,56 @@ function transformNode(program, node) {
|
|
|
24
23
|
return transformFunctionExpression(program, node);
|
|
25
24
|
case 'Number':
|
|
26
25
|
return {
|
|
27
|
-
type:
|
|
28
|
-
dataType:
|
|
29
|
-
value: parseFloat(getNodeValue(program, node))
|
|
26
|
+
type: 'Literal',
|
|
27
|
+
dataType: 'number',
|
|
28
|
+
value: parseFloat(getNodeValue(program, node)),
|
|
30
29
|
};
|
|
31
30
|
case 'String':
|
|
32
31
|
return {
|
|
33
|
-
type:
|
|
34
|
-
dataType:
|
|
35
|
-
value: getNodeValue(program, node).slice(1, -1)
|
|
32
|
+
type: 'Literal',
|
|
33
|
+
dataType: 'string',
|
|
34
|
+
value: getNodeValue(program, node).slice(1, -1),
|
|
36
35
|
};
|
|
37
36
|
case 'Boolean':
|
|
38
37
|
return {
|
|
39
|
-
type:
|
|
40
|
-
dataType:
|
|
41
|
-
value: getNodeValue(program, node) === 'true'
|
|
38
|
+
type: 'Literal',
|
|
39
|
+
dataType: 'boolean',
|
|
40
|
+
value: getNodeValue(program, node) === 'true',
|
|
41
|
+
};
|
|
42
|
+
case 'Null':
|
|
43
|
+
return {
|
|
44
|
+
type: 'Literal',
|
|
45
|
+
dataType: 'boolean',
|
|
46
|
+
value: null,
|
|
42
47
|
};
|
|
43
48
|
case 'Array':
|
|
44
49
|
return {
|
|
45
|
-
type:
|
|
46
|
-
dataType:
|
|
47
|
-
value: node.getChildren('Value')
|
|
48
|
-
.map(node => {
|
|
50
|
+
type: 'Literal',
|
|
51
|
+
dataType: 'array',
|
|
52
|
+
value: node.getChildren('Value').map((node) => {
|
|
49
53
|
return transformNode(program, node);
|
|
50
|
-
})
|
|
54
|
+
}),
|
|
51
55
|
};
|
|
52
56
|
case 'Variable':
|
|
53
57
|
return {
|
|
54
|
-
type:
|
|
55
|
-
value: getNodeValue(program, node).slice(1, -1)
|
|
58
|
+
type: 'Variable',
|
|
59
|
+
value: getNodeValue(program, node).slice(1, -1),
|
|
56
60
|
};
|
|
57
61
|
default:
|
|
58
|
-
return
|
|
62
|
+
return node.firstChild
|
|
63
|
+
? transformNode(program, node.firstChild)
|
|
64
|
+
: undefined;
|
|
59
65
|
}
|
|
60
66
|
}
|
|
61
67
|
function transformBinaryExpression(program, node) {
|
|
62
68
|
const [left, right] = node.getChildren('Expression');
|
|
63
|
-
const
|
|
69
|
+
const logicOperator = node.getChild('LogicOp');
|
|
70
|
+
const arithOperator = node.getChild('ArithOp');
|
|
64
71
|
return {
|
|
65
|
-
operator: getNodeValue(program,
|
|
66
|
-
type:
|
|
72
|
+
operator: getNodeValue(program, logicOperator !== null && logicOperator !== void 0 ? logicOperator : arithOperator),
|
|
73
|
+
type: AST_TYPES.binaryExpression,
|
|
67
74
|
left: transformNode(program, left),
|
|
68
|
-
right: transformNode(program, right)
|
|
75
|
+
right: transformNode(program, right),
|
|
69
76
|
};
|
|
70
77
|
}
|
|
71
78
|
function transformUnaryExpression(program, node) {
|
|
@@ -73,18 +80,20 @@ function transformUnaryExpression(program, node) {
|
|
|
73
80
|
const operator = node.getChild('ArithOp');
|
|
74
81
|
return {
|
|
75
82
|
operator: getNodeValue(program, operator),
|
|
76
|
-
type:
|
|
77
|
-
right: transformNode(program, right)
|
|
83
|
+
type: AST_TYPES.unaryExpression,
|
|
84
|
+
right: transformNode(program, right),
|
|
78
85
|
};
|
|
79
86
|
}
|
|
80
87
|
function transformFunctionExpression(program, node) {
|
|
81
88
|
const args = node.getChild('FunctionArguments');
|
|
82
89
|
const nodeValue = node.getChild('FunctionIdentifier');
|
|
83
90
|
return {
|
|
84
|
-
type:
|
|
91
|
+
type: AST_TYPES.functionCall,
|
|
85
92
|
name: getNodeValue(program, nodeValue),
|
|
86
|
-
arguments:
|
|
87
|
-
|
|
88
|
-
|
|
93
|
+
arguments: args
|
|
94
|
+
? args
|
|
95
|
+
.getChildren('Expression')
|
|
96
|
+
.map((node) => transformNode(program, node))
|
|
97
|
+
: [],
|
|
89
98
|
};
|
|
90
99
|
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { ENGINES } from '../constants';
|
|
2
|
+
import { CommonAST, ProgramAST, BinaryExpressionAST, UnaryExpressionAST, FunctionCallAST, LiteralAST, FunctionDefinition, TranspilationResponse } from '../constants/interfaces';
|
|
3
|
+
export declare class Transpiler {
|
|
4
|
+
readonly engine: ENGINES;
|
|
5
|
+
private ast;
|
|
6
|
+
constructor(ast: ProgramAST, engine: ENGINES);
|
|
7
|
+
get(): TranspilationResponse;
|
|
8
|
+
processNode(expression: CommonAST): string;
|
|
9
|
+
literal({ value, dataType }: LiteralAST): string | number | boolean | CommonAST[] | null;
|
|
10
|
+
unaryExpression({ operator, right }: UnaryExpressionAST): string;
|
|
11
|
+
binaryExpression({ left, operator, right }: BinaryExpressionAST): string;
|
|
12
|
+
functionCall(node: FunctionCallAST): string;
|
|
13
|
+
executer(func: FunctionDefinition, args: Array<any>, node: FunctionCallAST): string;
|
|
14
|
+
}
|
|
15
|
+
export declare function TranspileAST(ast: ProgramAST, engine: ENGINES): TranspilationResponse;
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import { AST_TYPES, ERROR_DICTIONARY, ERROR_LIST } from '../constants';
|
|
2
|
+
import { GenericError } from '../errors/definitions';
|
|
3
|
+
import { functionList } from '../functions';
|
|
4
|
+
export class Transpiler {
|
|
5
|
+
constructor(ast, engine) {
|
|
6
|
+
this.ast = ast;
|
|
7
|
+
this.engine = engine;
|
|
8
|
+
}
|
|
9
|
+
get() {
|
|
10
|
+
try {
|
|
11
|
+
const { body } = this.ast;
|
|
12
|
+
return {
|
|
13
|
+
valid: body !== undefined,
|
|
14
|
+
expression: body ? this.processNode(body) : undefined,
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
catch (error) {
|
|
18
|
+
return {
|
|
19
|
+
valid: false,
|
|
20
|
+
error: new GenericError(error.message),
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
processNode(expression) {
|
|
25
|
+
const { type } = expression;
|
|
26
|
+
const nodes = {
|
|
27
|
+
[AST_TYPES.variable]: this.literal,
|
|
28
|
+
[AST_TYPES.literal]: this.literal,
|
|
29
|
+
[AST_TYPES.unaryExpression]: this.unaryExpression,
|
|
30
|
+
[AST_TYPES.binaryExpression]: this.binaryExpression,
|
|
31
|
+
[AST_TYPES.functionCall]: this.functionCall,
|
|
32
|
+
};
|
|
33
|
+
return nodes[type].call(this, expression);
|
|
34
|
+
}
|
|
35
|
+
literal({ value, dataType }) {
|
|
36
|
+
if (dataType === 'string')
|
|
37
|
+
value = `'${value}'`;
|
|
38
|
+
return value;
|
|
39
|
+
}
|
|
40
|
+
unaryExpression({ operator, right }) {
|
|
41
|
+
const rightResult = this.processNode(right);
|
|
42
|
+
return `(${operator} ${rightResult})`;
|
|
43
|
+
}
|
|
44
|
+
binaryExpression({ left, operator, right }) {
|
|
45
|
+
const leftResult = this.processNode(left);
|
|
46
|
+
const rightResult = this.processNode(right);
|
|
47
|
+
return `(${leftResult} ${operator} ${rightResult})`;
|
|
48
|
+
}
|
|
49
|
+
functionCall(node) {
|
|
50
|
+
const { name, arguments: args } = node;
|
|
51
|
+
const func = functionList[name];
|
|
52
|
+
if (!func)
|
|
53
|
+
throw Object.assign({ node }, ERROR_DICTIONARY[ERROR_LIST.unknownFunction]);
|
|
54
|
+
const parameters = args.map((param) => this.processNode(param));
|
|
55
|
+
return this.executer(func, parameters, node);
|
|
56
|
+
}
|
|
57
|
+
executer(func, args, node) {
|
|
58
|
+
const params = (func === null || func === void 0 ? void 0 : func.parameters) ? [...func.parameters] : [];
|
|
59
|
+
for (const [index, element] of params.entries()) {
|
|
60
|
+
const value = args[index];
|
|
61
|
+
if (!value && element.optional)
|
|
62
|
+
continue;
|
|
63
|
+
if (!value && !element.optional)
|
|
64
|
+
throw Object.assign({ node }, ERROR_DICTIONARY[ERROR_LIST.missingArg]);
|
|
65
|
+
element.validator.forEach((validatorFunction) => {
|
|
66
|
+
const validator = validatorFunction(value);
|
|
67
|
+
if (!validator.valid)
|
|
68
|
+
throw Object.assign({ node }, validator);
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
return func.transpiler[this.engine](...args);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
export function TranspileAST(ast, engine) {
|
|
75
|
+
const transpiler = new Transpiler(ast, engine);
|
|
76
|
+
return transpiler.get();
|
|
77
|
+
}
|
package/package.json
CHANGED
|
@@ -1,16 +1,21 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@qrvey/formula-lang",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.1.1",
|
|
4
4
|
"description": "QFormula support for qrvey projects",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
7
7
|
"type": "module",
|
|
8
8
|
"scripts": {
|
|
9
|
-
"test": "
|
|
9
|
+
"test": "jest __tests__",
|
|
10
10
|
"build": "tsc",
|
|
11
11
|
"build:watch": "tsc -w",
|
|
12
12
|
"prepublishOnly": "npm run build",
|
|
13
|
-
"
|
|
13
|
+
"cm": "cz",
|
|
14
|
+
"prepare-lang": "rollup -c --bundleConfigAsCjs",
|
|
15
|
+
"lint": "eslint . --ext .ts",
|
|
16
|
+
"lint:fix": "eslint . --fix --ext .ts",
|
|
17
|
+
"prettier:fix": "prettier --write ./src",
|
|
18
|
+
"prepare:husky": "husky install"
|
|
14
19
|
},
|
|
15
20
|
"repository": {
|
|
16
21
|
"type": "git",
|
|
@@ -25,15 +30,35 @@
|
|
|
25
30
|
"author": "Qrvey Inc",
|
|
26
31
|
"homepage": "https://bitbucket.org/qrvey/qrvey_formula_lang#readme",
|
|
27
32
|
"dependencies": {
|
|
28
|
-
"@codemirror/language": "
|
|
29
|
-
"@lezer/common": "
|
|
30
|
-
"@lezer/highlight": "
|
|
33
|
+
"@codemirror/language": "6.6.0",
|
|
34
|
+
"@lezer/common": "1.0.2",
|
|
35
|
+
"@lezer/highlight": "1.1.4"
|
|
31
36
|
},
|
|
32
37
|
"devDependencies": {
|
|
38
|
+
"@commitlint/cli": "^17.5.1",
|
|
39
|
+
"@commitlint/config-conventional": "^17.4.4",
|
|
33
40
|
"@lezer/generator": "^1.2.2",
|
|
41
|
+
"@types/jest": "^29.5.0",
|
|
42
|
+
"@typescript-eslint/eslint-plugin": "^5.58.0",
|
|
43
|
+
"@typescript-eslint/parser": "^5.58.0",
|
|
44
|
+
"commitizen": "^4.3.0",
|
|
45
|
+
"cz-conventional-changelog": "^3.3.0",
|
|
46
|
+
"eslint": "^8.38.0",
|
|
47
|
+
"eslint-config-prettier": "^8.8.0",
|
|
48
|
+
"eslint-plugin-prettier": "^4.2.1",
|
|
49
|
+
"husky": "^8.0.3",
|
|
50
|
+
"jest": "^29.5.0",
|
|
51
|
+
"lint-staged": "^13.2.1",
|
|
52
|
+
"prettier": "^2.8.7",
|
|
34
53
|
"rollup": "^3.20.2",
|
|
35
54
|
"rollup-plugin-dts": "^5.3.0",
|
|
36
55
|
"rollup-plugin-ts": "^3.2.0",
|
|
56
|
+
"ts-jest": "^29.1.0",
|
|
37
57
|
"typescript": "^5.0.4"
|
|
58
|
+
},
|
|
59
|
+
"config": {
|
|
60
|
+
"commitizen": {
|
|
61
|
+
"path": "./node_modules/cz-conventional-changelog"
|
|
62
|
+
}
|
|
38
63
|
}
|
|
39
64
|
}
|