-
Notifications
You must be signed in to change notification settings - Fork 2k
/
parser.ts
146 lines (141 loc) Β· 4.29 KB
/
parser.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
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
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
import {
Comparator,
Comparators,
Comparison,
FilterDirective,
Operation,
Operator,
Operators,
} from "./ir.js";
import {
CallExpressionType,
ExpressionParser,
ParsedType,
} from "../../output_parsers/expression.js";
/**
* A type representing the possible types that can be traversed in an
* expression.
*/
export type TraverseType =
| boolean
| Operation
| Comparison
| string
| number
| { [key: string]: TraverseType }
| TraverseType[];
/**
* A class for transforming and parsing query expressions.
*/
export class QueryTransformer {
constructor(
public allowedComparators: Comparator[] = [],
public allowedOperators: Operator[] = []
) {}
/**
* Matches a function name to a comparator or operator. Throws an error if
* the function name is unknown or not allowed.
* @param funcName The function name to match.
* @returns The matched function name.
*/
private matchFunctionName(funcName: string) {
if (funcName in Comparators) {
if (this.allowedComparators.length > 0) {
if (this.allowedComparators.includes(funcName as Comparator)) {
return funcName;
} else {
throw new Error("Received comparator not allowed");
}
} else {
return funcName;
}
}
if (funcName in Operators) {
if (this.allowedOperators.length > 0) {
if (this.allowedOperators.includes(funcName as Operator)) {
return funcName;
} else {
throw new Error("Received operator not allowed");
}
} else {
return funcName;
}
}
throw new Error("Unknown function name");
}
/**
* Transforms a parsed expression into an operation or comparison. Throws
* an error if the parsed expression is not supported.
* @param parsed The parsed expression to transform.
* @returns The transformed operation or comparison.
*/
private transform(parsed: CallExpressionType): Operation | Comparison {
const traverse = (node: ParsedType): TraverseType => {
switch (node.type) {
case "call_expression": {
if (typeof node.funcCall !== "string") {
throw new Error(
"Property access expression and element access expression not supported"
);
}
const funcName = this.matchFunctionName(node.funcCall);
if (funcName in Operators) {
return new Operation(
funcName as Operator,
node.args?.map((arg) => traverse(arg)) as FilterDirective[]
);
}
if (funcName in Comparators) {
if (node.args && node.args.length === 2) {
return new Comparison(
funcName as Comparator,
traverse(node.args[0]) as string,
traverse(node.args[1]) as string | number
);
}
throw new Error("Comparator must have exactly 2 arguments");
}
throw new Error("Function name neither operator nor comparator");
}
case "string_literal": {
return node.value;
}
case "numeric_literal": {
return node.value;
}
case "array_literal": {
return node.values.map((value) => traverse(value));
}
case "object_literal": {
return node.values.reduce((acc, value) => {
acc[value.identifier] = traverse(value.value);
return acc;
}, {} as { [key: string]: TraverseType });
}
case "boolean_literal": {
return node.value;
}
default: {
throw new Error("Unknown node type");
}
}
};
return traverse(parsed) as Operation | Comparison;
}
/**
* Parses an expression and returns the transformed operation or
* comparison. Throws an error if the expression cannot be parsed.
* @param expression The expression to parse.
* @returns A Promise that resolves to the transformed operation or comparison.
*/
async parse(expression: string): Promise<Operation | Comparison> {
const expressionParser = new ExpressionParser();
const parsed = (await expressionParser.parse(
expression
)) as CallExpressionType;
if (!parsed) {
throw new Error("Could not parse expression");
}
return this.transform(parsed);
}
}