flowquery 1.0.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/.github/workflows/npm-publish.yml +30 -0
- package/.github/workflows/release.yml +84 -0
- package/CODE_OF_CONDUCT.md +10 -0
- package/FlowQueryLogoIcon.png +0 -0
- package/LICENSE +21 -0
- package/README.md +113 -0
- package/SECURITY.md +14 -0
- package/SUPPORT.md +13 -0
- package/docs/flowquery.min.js +1 -0
- package/docs/index.html +105 -0
- package/flowquery-vscode/.vscode-test.mjs +5 -0
- package/flowquery-vscode/.vscodeignore +13 -0
- package/flowquery-vscode/LICENSE +21 -0
- package/flowquery-vscode/README.md +11 -0
- package/flowquery-vscode/demo/FlowQueryVSCodeDemo.gif +0 -0
- package/flowquery-vscode/eslint.config.mjs +25 -0
- package/flowquery-vscode/extension.js +508 -0
- package/flowquery-vscode/flowQueryEngine/flowquery.min.js +1 -0
- package/flowquery-vscode/flowquery-worker.js +66 -0
- package/flowquery-vscode/images/FlowQueryLogoIcon.png +0 -0
- package/flowquery-vscode/jsconfig.json +13 -0
- package/flowquery-vscode/libs/page.css +53 -0
- package/flowquery-vscode/libs/table.css +13 -0
- package/flowquery-vscode/libs/tabs.css +66 -0
- package/flowquery-vscode/package-lock.json +2917 -0
- package/flowquery-vscode/package.json +51 -0
- package/flowquery-vscode/test/extension.test.js +196 -0
- package/flowquery-vscode/test/worker.test.js +25 -0
- package/flowquery-vscode/vsc-extension-quickstart.md +42 -0
- package/jest.config.js +11 -0
- package/package.json +28 -0
- package/queries/analyze_catfacts.cql +75 -0
- package/queries/azure_openai_completions.cql +13 -0
- package/queries/azure_openai_models.cql +9 -0
- package/queries/mock_pipeline.cql +84 -0
- package/queries/openai_completions.cql +15 -0
- package/queries/openai_models.cql +13 -0
- package/queries/test.cql +6 -0
- package/queries/tool_inference.cql +24 -0
- package/queries/wisdom.cql +6 -0
- package/queries/wisdom_letter_histogram.cql +8 -0
- package/src/compute/runner.ts +65 -0
- package/src/index.browser.ts +11 -0
- package/src/index.ts +12 -0
- package/src/io/command_line.ts +74 -0
- package/src/parsing/alias.ts +23 -0
- package/src/parsing/alias_option.ts +5 -0
- package/src/parsing/ast_node.ts +153 -0
- package/src/parsing/base_parser.ts +92 -0
- package/src/parsing/components/csv.ts +9 -0
- package/src/parsing/components/from.ts +12 -0
- package/src/parsing/components/headers.ts +12 -0
- package/src/parsing/components/json.ts +9 -0
- package/src/parsing/components/null.ts +9 -0
- package/src/parsing/components/post.ts +9 -0
- package/src/parsing/components/text.ts +9 -0
- package/src/parsing/context.ts +48 -0
- package/src/parsing/data_structures/associative_array.ts +43 -0
- package/src/parsing/data_structures/json_array.ts +31 -0
- package/src/parsing/data_structures/key_value_pair.ts +37 -0
- package/src/parsing/data_structures/lookup.ts +40 -0
- package/src/parsing/data_structures/range_lookup.ts +36 -0
- package/src/parsing/expressions/expression.ts +142 -0
- package/src/parsing/expressions/f_string.ts +26 -0
- package/src/parsing/expressions/identifier.ts +22 -0
- package/src/parsing/expressions/number.ts +40 -0
- package/src/parsing/expressions/operator.ts +179 -0
- package/src/parsing/expressions/reference.ts +42 -0
- package/src/parsing/expressions/string.ts +34 -0
- package/src/parsing/functions/aggregate_function.ts +58 -0
- package/src/parsing/functions/avg.ts +37 -0
- package/src/parsing/functions/collect.ts +44 -0
- package/src/parsing/functions/function.ts +60 -0
- package/src/parsing/functions/function_factory.ts +66 -0
- package/src/parsing/functions/join.ts +26 -0
- package/src/parsing/functions/predicate_function.ts +44 -0
- package/src/parsing/functions/predicate_function_factory.ts +15 -0
- package/src/parsing/functions/predicate_sum.ts +29 -0
- package/src/parsing/functions/rand.ts +13 -0
- package/src/parsing/functions/range.ts +18 -0
- package/src/parsing/functions/reducer_element.ts +10 -0
- package/src/parsing/functions/replace.ts +19 -0
- package/src/parsing/functions/round.ts +17 -0
- package/src/parsing/functions/size.ts +17 -0
- package/src/parsing/functions/split.ts +26 -0
- package/src/parsing/functions/stringify.ts +26 -0
- package/src/parsing/functions/sum.ts +31 -0
- package/src/parsing/functions/to_json.ts +17 -0
- package/src/parsing/functions/value_holder.ts +13 -0
- package/src/parsing/logic/case.ts +26 -0
- package/src/parsing/logic/else.ts +12 -0
- package/src/parsing/logic/end.ts +9 -0
- package/src/parsing/logic/then.ts +12 -0
- package/src/parsing/logic/when.ts +12 -0
- package/src/parsing/operations/aggregated_return.ts +18 -0
- package/src/parsing/operations/aggregated_with.ts +18 -0
- package/src/parsing/operations/group_by.ts +124 -0
- package/src/parsing/operations/limit.ts +22 -0
- package/src/parsing/operations/load.ts +92 -0
- package/src/parsing/operations/operation.ts +65 -0
- package/src/parsing/operations/projection.ts +18 -0
- package/src/parsing/operations/return.ts +43 -0
- package/src/parsing/operations/unwind.ts +32 -0
- package/src/parsing/operations/where.ts +38 -0
- package/src/parsing/operations/with.ts +20 -0
- package/src/parsing/parser.ts +762 -0
- package/src/parsing/token_to_node.ts +91 -0
- package/src/tokenization/keyword.ts +43 -0
- package/src/tokenization/operator.ts +25 -0
- package/src/tokenization/string_walker.ts +194 -0
- package/src/tokenization/symbol.ts +15 -0
- package/src/tokenization/token.ts +633 -0
- package/src/tokenization/token_mapper.ts +53 -0
- package/src/tokenization/token_type.ts +15 -0
- package/src/tokenization/tokenizer.ts +229 -0
- package/src/tokenization/trie.ts +117 -0
- package/src/utils/object_utils.ts +17 -0
- package/src/utils/string_utils.ts +114 -0
- package/tests/compute/runner.test.ts +498 -0
- package/tests/parsing/context.test.ts +27 -0
- package/tests/parsing/expression.test.ts +40 -0
- package/tests/parsing/parser.test.ts +434 -0
- package/tests/tokenization/token_mapper.test.ts +47 -0
- package/tests/tokenization/tokenizer.test.ts +67 -0
- package/tests/tokenization/trie.test.ts +20 -0
- package/tsconfig.json +15 -0
- package/typedoc.json +16 -0
- package/webpack.config.js +26 -0
|
@@ -0,0 +1,229 @@
|
|
|
1
|
+
import Keyword from './keyword';
|
|
2
|
+
import Token from './token';
|
|
3
|
+
import StringWalker from './string_walker';
|
|
4
|
+
import StringUtils from '../utils/string_utils';
|
|
5
|
+
import Symbol from './symbol';
|
|
6
|
+
import Operator from './operator';
|
|
7
|
+
import TokenMapper from './token_mapper';
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Tokenizes FlowQuery input strings into a sequence of tokens.
|
|
11
|
+
*
|
|
12
|
+
* The tokenizer performs lexical analysis, breaking down the input text into
|
|
13
|
+
* meaningful tokens such as keywords, identifiers, operators, strings, numbers,
|
|
14
|
+
* and symbols. It handles comments, whitespace, and f-strings.
|
|
15
|
+
*
|
|
16
|
+
* @example
|
|
17
|
+
* ```typescript
|
|
18
|
+
* const tokenizer = new Tokenizer("WITH x = 1 RETURN x");
|
|
19
|
+
* const tokens = tokenizer.tokenize();
|
|
20
|
+
* ```
|
|
21
|
+
*/
|
|
22
|
+
class Tokenizer {
|
|
23
|
+
private walker: StringWalker;
|
|
24
|
+
private keywords: TokenMapper = new TokenMapper(Keyword);
|
|
25
|
+
private symbols: TokenMapper = new TokenMapper(Symbol);
|
|
26
|
+
private operators: TokenMapper = new TokenMapper(Operator);
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Creates a new Tokenizer instance for the given input.
|
|
30
|
+
*
|
|
31
|
+
* @param input - The FlowQuery input string to tokenize
|
|
32
|
+
*/
|
|
33
|
+
constructor(input: string) {
|
|
34
|
+
this.walker = new StringWalker(input);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Tokenizes the input string into an array of tokens.
|
|
39
|
+
*
|
|
40
|
+
* @returns An array of Token objects representing the tokenized input
|
|
41
|
+
* @throws {Error} If an unrecognized token is encountered
|
|
42
|
+
*/
|
|
43
|
+
public tokenize(): Token[] {
|
|
44
|
+
const tokens: Token[] = [];
|
|
45
|
+
let last: Token | null = null;
|
|
46
|
+
while (!this.walker.isAtEnd) {
|
|
47
|
+
tokens.push(...this.f_string());
|
|
48
|
+
last = this.getLastNonWhitespaceOrNonCommentToken(tokens) || last;
|
|
49
|
+
const token = this.getNextToken(last);
|
|
50
|
+
if (token === null) {
|
|
51
|
+
throw new Error(`Unrecognized token at position ${this.walker.position}`);
|
|
52
|
+
}
|
|
53
|
+
token.position = this.walker.position;
|
|
54
|
+
tokens.push(token);
|
|
55
|
+
}
|
|
56
|
+
return tokens;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
private getLastNonWhitespaceOrNonCommentToken(tokens: Token[]): Token | null {
|
|
60
|
+
if (tokens.length === 0) {
|
|
61
|
+
return null;
|
|
62
|
+
}
|
|
63
|
+
if(!tokens[tokens.length - 1].isWhitespaceOrComment()) {
|
|
64
|
+
return tokens[tokens.length - 1];
|
|
65
|
+
}
|
|
66
|
+
return null;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
private getNextToken(last: Token | null = null): Token | null {
|
|
70
|
+
if (this.walker.isAtEnd) {
|
|
71
|
+
return Token.EOF;
|
|
72
|
+
}
|
|
73
|
+
return (
|
|
74
|
+
this.comment() ||
|
|
75
|
+
this.whitespace() ||
|
|
76
|
+
this.lookup(this.keywords) ||
|
|
77
|
+
this.lookup(this.operators, last, this.skipMinus) ||
|
|
78
|
+
this.identifier() ||
|
|
79
|
+
this.string() ||
|
|
80
|
+
this.number() ||
|
|
81
|
+
this.lookup(this.symbols)
|
|
82
|
+
);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
public comment(): Token | null {
|
|
86
|
+
const startPosition = this.walker.position;
|
|
87
|
+
if (this.walker.checkForSingleComment() || this.walker.checkForMultiLineComment()) {
|
|
88
|
+
const uncommented = StringUtils.uncomment(this.walker.getString(startPosition));
|
|
89
|
+
return Token.COMMENT(uncommented);
|
|
90
|
+
}
|
|
91
|
+
return null;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
private identifier(): Token | null {
|
|
95
|
+
const startPosition = this.walker.position;
|
|
96
|
+
if (this.walker.checkForUnderScore() || this.walker.checkForLetter()) {
|
|
97
|
+
while (!this.walker.isAtEnd && (this.walker.checkForLetter() || this.walker.checkForDigit() || this.walker.checkForUnderScore())) {
|
|
98
|
+
;
|
|
99
|
+
}
|
|
100
|
+
return Token.IDENTIFIER(this.walker.getString(startPosition));
|
|
101
|
+
}
|
|
102
|
+
return null;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
private string(): Token | null {
|
|
106
|
+
const startPosition = this.walker.position;
|
|
107
|
+
const quoteChar = this.walker.checkForQuote();
|
|
108
|
+
if (quoteChar === null) {
|
|
109
|
+
return null;
|
|
110
|
+
}
|
|
111
|
+
while (!this.walker.isAtEnd) {
|
|
112
|
+
if (this.walker.escaped(quoteChar)) {
|
|
113
|
+
this.walker.moveNext();
|
|
114
|
+
this.walker.moveNext();
|
|
115
|
+
continue;
|
|
116
|
+
}
|
|
117
|
+
if (this.walker.checkForString(quoteChar)) {
|
|
118
|
+
const value = this.walker.getString(startPosition);
|
|
119
|
+
if (quoteChar === Symbol.BACKTICK) {
|
|
120
|
+
return Token.BACKTICK_STRING(value, quoteChar);
|
|
121
|
+
}
|
|
122
|
+
return Token.STRING(value, quoteChar);
|
|
123
|
+
}
|
|
124
|
+
this.walker.moveNext();
|
|
125
|
+
}
|
|
126
|
+
throw new Error(`Unterminated string at position ${startPosition}`);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
private *f_string(): Iterable<Token> {
|
|
130
|
+
if(!this.walker.checkForFStringStart()) {
|
|
131
|
+
return;
|
|
132
|
+
}
|
|
133
|
+
this.walker.moveNext(); // skip the f
|
|
134
|
+
let position = this.walker.position;
|
|
135
|
+
const quoteChar = this.walker.checkForQuote();
|
|
136
|
+
if (quoteChar === null) {
|
|
137
|
+
return;
|
|
138
|
+
}
|
|
139
|
+
while (!this.walker.isAtEnd) {
|
|
140
|
+
if (this.walker.escaped(quoteChar) || this.walker.escapedBrace()) {
|
|
141
|
+
this.walker.moveNext();
|
|
142
|
+
this.walker.moveNext();
|
|
143
|
+
continue;
|
|
144
|
+
}
|
|
145
|
+
if(this.walker.openingBrace()) {
|
|
146
|
+
yield Token.F_STRING(this.walker.getString(position), quoteChar);
|
|
147
|
+
position = this.walker.position;
|
|
148
|
+
yield Token.OPENING_BRACE;
|
|
149
|
+
this.walker.moveNext(); // skip the opening brace
|
|
150
|
+
position = this.walker.position;
|
|
151
|
+
while(!this.walker.isAtEnd && !this.walker.closingBrace()) {
|
|
152
|
+
const token = this.getNextToken();
|
|
153
|
+
if(token !== null) {
|
|
154
|
+
yield token;
|
|
155
|
+
} else {
|
|
156
|
+
break;
|
|
157
|
+
}
|
|
158
|
+
if(this.walker.closingBrace()) {
|
|
159
|
+
yield Token.CLOSING_BRACE;
|
|
160
|
+
this.walker.moveNext(); // skip the closing brace
|
|
161
|
+
position = this.walker.position;
|
|
162
|
+
break;
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
if (this.walker.checkForString(quoteChar)) {
|
|
167
|
+
yield Token.F_STRING(this.walker.getString(position), quoteChar);
|
|
168
|
+
return;
|
|
169
|
+
};
|
|
170
|
+
this.walker.moveNext();
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
private whitespace(): Token | null {
|
|
175
|
+
let foundWhitespace = false;
|
|
176
|
+
while (!this.walker.isAtEnd && this.walker.checkForWhitespace()) {
|
|
177
|
+
this.walker.moveNext();
|
|
178
|
+
foundWhitespace = true;
|
|
179
|
+
}
|
|
180
|
+
return foundWhitespace ? Token.WHITESPACE : null;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
private number(): Token | null {
|
|
184
|
+
const startPosition = this.walker.position;
|
|
185
|
+
if (this.walker.checkForString('-') || this.walker.checkForDigit()) {
|
|
186
|
+
while (!this.walker.isAtEnd && this.walker.checkForDigit()) {
|
|
187
|
+
;
|
|
188
|
+
}
|
|
189
|
+
if (this.walker.checkForString(Symbol.DOT)) {
|
|
190
|
+
while (!this.walker.isAtEnd && this.walker.checkForDigit()) {
|
|
191
|
+
;
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
const _number = this.walker.getString(startPosition);
|
|
195
|
+
return Token.NUMBER(_number);
|
|
196
|
+
}
|
|
197
|
+
return null;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
private lookup(mapper: TokenMapper, last: Token | null = null, skip?: (last: Token | null, current: Token) => boolean): Token | null {
|
|
201
|
+
const token = mapper.map(this.walker.getRemainingString());
|
|
202
|
+
if (token !== undefined && token.value !== null) {
|
|
203
|
+
if(token.can_be_identifier && this.walker.word_continuation(token.value)) {
|
|
204
|
+
return null;
|
|
205
|
+
}
|
|
206
|
+
if (skip && last && skip(last, token)) {
|
|
207
|
+
return null;
|
|
208
|
+
}
|
|
209
|
+
this.walker.moveBy(token.value.length);
|
|
210
|
+
if(mapper.last_found !== null) {
|
|
211
|
+
token.case_sensitive_value = mapper.last_found;
|
|
212
|
+
}
|
|
213
|
+
return token;
|
|
214
|
+
}
|
|
215
|
+
return null;
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
private skipMinus(last: Token | null, current: Token): boolean {
|
|
219
|
+
if (last === null) {
|
|
220
|
+
return false;
|
|
221
|
+
}
|
|
222
|
+
if((last.isKeyword() || last.isComma() || last.isColon()) && current.isNegation()) {
|
|
223
|
+
return true;
|
|
224
|
+
}
|
|
225
|
+
return false;
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
export default Tokenizer;
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
import Token from "./token";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Represents a node in a Trie data structure.
|
|
5
|
+
*
|
|
6
|
+
* Each node can have children nodes (one per character) and may contain a token
|
|
7
|
+
* if the path to this node represents a complete word.
|
|
8
|
+
*/
|
|
9
|
+
class Node {
|
|
10
|
+
private _children: Map<string, Node> = new Map();
|
|
11
|
+
private _token: Token | undefined = undefined;
|
|
12
|
+
|
|
13
|
+
public map(char: string): Node {
|
|
14
|
+
return this._children.get(char) || this._children.set(char, new Node()).get(char)!;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
public retrieve(char: string): Node | undefined {
|
|
18
|
+
return this._children.get(char);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
public set token(token: Token) {
|
|
22
|
+
this._token = token;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
public get token(): Token | undefined {
|
|
26
|
+
return this._token;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
public is_end_of_word(): boolean {
|
|
30
|
+
return this._token !== undefined;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
public no_children(): boolean {
|
|
34
|
+
return this._children.size === 0;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Trie (prefix tree) data structure for efficient keyword and operator lookup.
|
|
40
|
+
*
|
|
41
|
+
* Used during tokenization to quickly match input strings against known keywords
|
|
42
|
+
* and operators. Supports case-insensitive matching and tracks the longest match found.
|
|
43
|
+
*
|
|
44
|
+
* @example
|
|
45
|
+
* ```typescript
|
|
46
|
+
* const trie = new Trie();
|
|
47
|
+
* trie.insert(Token.WITH);
|
|
48
|
+
* const found = trie.find("WITH");
|
|
49
|
+
* ```
|
|
50
|
+
*/
|
|
51
|
+
class Trie {
|
|
52
|
+
private _root: Node = new Node();
|
|
53
|
+
private _max_length: number = 0;
|
|
54
|
+
private _last_found: string | null = null;
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Inserts a token into the trie.
|
|
58
|
+
*
|
|
59
|
+
* @param token - The token to insert
|
|
60
|
+
* @throws {Error} If the token value is null or empty
|
|
61
|
+
*/
|
|
62
|
+
public insert(token: Token): void {
|
|
63
|
+
if(token.value === null || token.value.length === 0) {
|
|
64
|
+
throw new Error("Token value cannot be null or empty");
|
|
65
|
+
}
|
|
66
|
+
let currentNode = this._root;
|
|
67
|
+
for (const char of token.value) {
|
|
68
|
+
currentNode = currentNode.map(char.toLowerCase());
|
|
69
|
+
}
|
|
70
|
+
if (token.value.length > this._max_length) {
|
|
71
|
+
this._max_length = token.value.length;
|
|
72
|
+
}
|
|
73
|
+
currentNode.token = token;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Finds a token by searching for the longest matching prefix in the trie.
|
|
78
|
+
*
|
|
79
|
+
* @param value - The string value to search for
|
|
80
|
+
* @returns The token if found, undefined otherwise
|
|
81
|
+
*/
|
|
82
|
+
public find(value: string): Token | undefined {
|
|
83
|
+
if(value.length === 0) {
|
|
84
|
+
return undefined;
|
|
85
|
+
}
|
|
86
|
+
let index = 0;
|
|
87
|
+
let current: Node | undefined = undefined;
|
|
88
|
+
let found: Token | undefined = undefined;
|
|
89
|
+
this._last_found = null;
|
|
90
|
+
while((current = (current || this._root).retrieve(value[index].toLowerCase())) !== undefined) {
|
|
91
|
+
if(current.is_end_of_word()) {
|
|
92
|
+
found = current.token;
|
|
93
|
+
this._last_found = value.substring(0, index + 1);
|
|
94
|
+
}
|
|
95
|
+
index++;
|
|
96
|
+
if(index === value.length || index > this._max_length) {
|
|
97
|
+
break;
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
if(current?.is_end_of_word()) {
|
|
101
|
+
found = current.token;
|
|
102
|
+
this._last_found = value.substring(0, index);
|
|
103
|
+
}
|
|
104
|
+
return found;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* Gets the last matched string from the most recent find operation.
|
|
109
|
+
*
|
|
110
|
+
* @returns The last found string, or null if no match was found
|
|
111
|
+
*/
|
|
112
|
+
public get last_found(): string | null {
|
|
113
|
+
return this._last_found;
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
export default Trie;
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Utility class for object-related operations.
|
|
3
|
+
*/
|
|
4
|
+
class ObjectUtils {
|
|
5
|
+
/**
|
|
6
|
+
* Checks if an object is an instance of any of the provided classes.
|
|
7
|
+
*
|
|
8
|
+
* @param obj - The object to check
|
|
9
|
+
* @param classes - Array of class constructors to test against
|
|
10
|
+
* @returns True if the object is an instance of any class, false otherwise
|
|
11
|
+
*/
|
|
12
|
+
static isInstanceOfAny(obj: any, classes: any[]): boolean {
|
|
13
|
+
return classes.some(cls => obj instanceof cls);
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export default ObjectUtils;
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Utility class for string manipulation and validation.
|
|
3
|
+
*
|
|
4
|
+
* Provides methods for handling quoted strings, comments, escape sequences,
|
|
5
|
+
* and identifier validation.
|
|
6
|
+
*/
|
|
7
|
+
class StringUtils {
|
|
8
|
+
static readonly quotes: string[] = ['"', "'", '`'];
|
|
9
|
+
static readonly letters = 'abcdefghijklmnopqrstuvwxyz';
|
|
10
|
+
static readonly digits = '0123456789';
|
|
11
|
+
static readonly whitespace = ' \t\n\r';
|
|
12
|
+
static readonly word_valid_chars = StringUtils.letters + StringUtils.digits + '_';
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Removes surrounding quotes from a string.
|
|
16
|
+
*
|
|
17
|
+
* @param str - The string to unquote
|
|
18
|
+
* @returns The unquoted string
|
|
19
|
+
*/
|
|
20
|
+
static unquote(str: string): string {
|
|
21
|
+
if(str.length === 0) {
|
|
22
|
+
return str;
|
|
23
|
+
}
|
|
24
|
+
if(str.length === 1 && StringUtils.quotes.includes(str)) {
|
|
25
|
+
return '';
|
|
26
|
+
}
|
|
27
|
+
const first: string = str[0];
|
|
28
|
+
const last: string = str[str.length - 1];
|
|
29
|
+
if (StringUtils.quotes.includes(first) && first === last) {
|
|
30
|
+
return str.substring(1, str.length - 1);
|
|
31
|
+
}
|
|
32
|
+
if (StringUtils.quotes.includes(last) && first !== last) {
|
|
33
|
+
return str.substring(0, str.length - 1);
|
|
34
|
+
}
|
|
35
|
+
if (StringUtils.quotes.includes(first) && first !== last) {
|
|
36
|
+
return str.substring(1);
|
|
37
|
+
}
|
|
38
|
+
return str;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Removes comment markers from a string.
|
|
43
|
+
*
|
|
44
|
+
* @param str - The comment string
|
|
45
|
+
* @returns The string without comment markers
|
|
46
|
+
*/
|
|
47
|
+
static uncomment(str: string): string {
|
|
48
|
+
if (str.length < 2) {
|
|
49
|
+
return str;
|
|
50
|
+
}
|
|
51
|
+
if (str[0] === '/' && str[1] === '/') {
|
|
52
|
+
return str.substring(2);
|
|
53
|
+
}
|
|
54
|
+
if (str[0] === '/' && str[1] === '*' && str[str.length - 2] === '*' && str[str.length - 1] === '/') {
|
|
55
|
+
return str.substring(2, str.length - 2);
|
|
56
|
+
}
|
|
57
|
+
return str;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Removes escape sequences before quotes in a string.
|
|
62
|
+
*
|
|
63
|
+
* @param str - The string to process
|
|
64
|
+
* @param quoteChar - The quote character that was escaped
|
|
65
|
+
* @returns The string with escape sequences removed
|
|
66
|
+
*/
|
|
67
|
+
static removeEscapedQuotes(str: string, quoteChar: string): string {
|
|
68
|
+
let unescaped = '';
|
|
69
|
+
for (let i = 0; i < str.length; i++) {
|
|
70
|
+
if (str[i] === '\\' && str[i + 1] === quoteChar) {
|
|
71
|
+
i++;
|
|
72
|
+
}
|
|
73
|
+
unescaped += str[i];
|
|
74
|
+
}
|
|
75
|
+
return unescaped;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Removes escaped braces ({{ and }}) from f-strings.
|
|
80
|
+
*
|
|
81
|
+
* @param str - The string to process
|
|
82
|
+
* @returns The string with escaped braces resolved
|
|
83
|
+
*/
|
|
84
|
+
static removeEscapedBraces(str: string): string {
|
|
85
|
+
let unescaped = '';
|
|
86
|
+
for (let i = 0; i < str.length; i++) {
|
|
87
|
+
if((str[i] === '{' && str[i + 1] === '{') || (str[i] === '}' && str[i + 1] === '}')) {
|
|
88
|
+
i++;
|
|
89
|
+
}
|
|
90
|
+
unescaped += str[i];
|
|
91
|
+
}
|
|
92
|
+
return unescaped;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Checks if a string is a valid identifier.
|
|
97
|
+
*
|
|
98
|
+
* @param str - The string to validate
|
|
99
|
+
* @returns True if the string can be used as an identifier, false otherwise
|
|
100
|
+
*/
|
|
101
|
+
static can_be_identifier(str: string): boolean {
|
|
102
|
+
const lower = str.toLowerCase();
|
|
103
|
+
if(lower.length === 0) {
|
|
104
|
+
return false;
|
|
105
|
+
}
|
|
106
|
+
if(!StringUtils.letters.includes(lower[0]) && lower[0] !== '_') {
|
|
107
|
+
return false;
|
|
108
|
+
}
|
|
109
|
+
return lower.split('').every((char) => StringUtils.word_valid_chars.includes(char));
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
export default StringUtils;
|