@rsconcept/domain 1.7.0 → 1.7.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/dist/{analyzer-CqUJ3FO9.d.ts → analyzer-QxWs73gP.d.ts} +2 -2
- package/dist/{ast-annotations-DvB1VcUv.d.ts → ast-annotations-CtF7C9uK.d.ts} +2 -2
- package/dist/{calculator-DKxDpTdT.d.ts → calculator-REeX2ZS1.d.ts} +2 -2
- package/dist/{error-CLnXbkly.d.ts → error-toW0TUHF.d.ts} +6 -1
- package/dist/index.d.ts +4 -4
- package/dist/library/rsengine.d.ts +1 -1
- package/dist/library/rsform-api.d.ts +1 -1
- package/dist/library/rsform.d.ts +1 -1
- package/dist/rslang/ast-annotations.d.ts +1 -1
- package/dist/rslang/error.d.ts +1 -1
- package/dist/rslang/error.js +5 -0
- package/dist/rslang/error.js.map +1 -1
- package/dist/rslang/eval/calculator.d.ts +1 -1
- package/dist/rslang/eval/evaluator.d.ts +1 -1
- package/dist/rslang/index.d.ts +4 -4
- package/dist/rslang/labels.d.ts +1 -1
- package/dist/rslang/labels.js +1 -1
- package/dist/rslang/labels.js.map +1 -1
- package/dist/rslang/parser/normalize.js +5 -1
- package/dist/rslang/parser/normalize.js.map +1 -1
- package/dist/rslang/parser/syntax-errors/index.d.ts +22 -0
- package/dist/rslang/parser/syntax-errors/index.js +2 -0
- package/dist/rslang/semantic/analyzer.d.ts +1 -1
- package/dist/rslang/semantic/analyzer.js +1 -1
- package/dist/rslang/semantic/type-auditor.d.ts +1 -1
- package/dist/rslang/semantic/value-auditor.d.ts +1 -1
- package/dist/{rslang/parser/syntax-errors.js → syntax-errors-pCI6RicF.js} +399 -233
- package/dist/syntax-errors-pCI6RicF.js.map +1 -0
- package/package.json +1 -1
- package/src/rslang/error.ts +5 -0
- package/src/rslang/labels.ts +1 -1
- package/src/rslang/parser/README.md +73 -0
- package/src/rslang/parser/normalize.ts +5 -1
- package/src/rslang/parser/parser.test.ts +53 -0
- package/src/rslang/parser/syntax-errors/README.md +61 -0
- package/src/rslang/parser/syntax-errors/ast-utils.ts +112 -0
- package/src/rslang/parser/syntax-errors/classify.ts +70 -0
- package/src/rslang/parser/syntax-errors/context.ts +25 -0
- package/src/rslang/parser/syntax-errors/error-builders.ts +28 -0
- package/src/rslang/parser/syntax-errors/expression-errors.ts +237 -0
- package/src/rslang/parser/syntax-errors/function-calls.ts +76 -0
- package/src/rslang/parser/syntax-errors/generators.ts +307 -0
- package/src/rslang/parser/syntax-errors/incomplete-formal.ts +76 -0
- package/src/rslang/parser/syntax-errors/incomplete-operands.ts +184 -0
- package/src/rslang/parser/syntax-errors/index.ts +102 -0
- package/src/rslang/parser/syntax-errors/trailing-slot.ts +45 -0
- package/dist/rslang/parser/syntax-errors.d.ts +0 -14
- package/dist/rslang/parser/syntax-errors.js.map +0 -1
- package/src/rslang/parser/syntax-errors.ts +0 -929
|
@@ -1,929 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Module: Syntactic errors reporting.
|
|
3
|
-
*/
|
|
4
|
-
|
|
5
|
-
import { type AstNode, visitAstDFS } from '../../parsing';
|
|
6
|
-
import { annotateError } from '../ast-annotations';
|
|
7
|
-
import { type ErrorReporter, RSErrorCode, type RSErrorDescription } from '../error';
|
|
8
|
-
import { type TypeClass, TypeClass as TypeClassEnum } from '../semantic/typification';
|
|
9
|
-
|
|
10
|
-
import {
|
|
11
|
-
Arguments,
|
|
12
|
-
Declarative,
|
|
13
|
-
Filter,
|
|
14
|
-
Function,
|
|
15
|
-
Function_decl,
|
|
16
|
-
Imp_blocks,
|
|
17
|
-
Imperative,
|
|
18
|
-
Logic_quantor,
|
|
19
|
-
Predicate,
|
|
20
|
-
Recursion,
|
|
21
|
-
Setexpr,
|
|
22
|
-
Variable,
|
|
23
|
-
Variable_pack
|
|
24
|
-
} from './parser.terms';
|
|
25
|
-
import { TokenID } from './token';
|
|
26
|
-
|
|
27
|
-
/** Options for syntax error extraction. */
|
|
28
|
-
export interface SyntaxErrorOptions {
|
|
29
|
-
expected?: TypeClass;
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
/** Extracts syntax errors from an AST. */
|
|
33
|
-
export function extractSyntaxErrors(
|
|
34
|
-
ast: AstNode,
|
|
35
|
-
expression: string,
|
|
36
|
-
reporter: ErrorReporter,
|
|
37
|
-
annotateErrors: boolean = false,
|
|
38
|
-
options?: SyntaxErrorOptions
|
|
39
|
-
) {
|
|
40
|
-
const forbiddenErrors = extractForbiddenCharacterErrors(expression);
|
|
41
|
-
if (forbiddenErrors.length > 0) {
|
|
42
|
-
for (const error of forbiddenErrors) {
|
|
43
|
-
reporter(error);
|
|
44
|
-
if (annotateErrors) {
|
|
45
|
-
annotateError(ast, error.code, error.params);
|
|
46
|
-
}
|
|
47
|
-
}
|
|
48
|
-
return;
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
const collected: RSErrorDescription[] = [];
|
|
52
|
-
const annotationTargets = new Map<RSErrorDescription, AstNode>();
|
|
53
|
-
const collect = (error: RSErrorDescription, target?: AstNode) => {
|
|
54
|
-
collected.push(error);
|
|
55
|
-
if (target !== undefined) {
|
|
56
|
-
annotationTargets.set(error, target);
|
|
57
|
-
}
|
|
58
|
-
};
|
|
59
|
-
|
|
60
|
-
const bracketError = extractBracketErrors(expression);
|
|
61
|
-
const hasBracketErrors = bracketError !== null;
|
|
62
|
-
visitAstDFS(ast, node => extractInternal(node, expression, collect, hasBracketErrors, options));
|
|
63
|
-
|
|
64
|
-
const hasIncompleteGenerator = collected.some(error => isIncompleteGeneratorBodyCode(error.code));
|
|
65
|
-
if (bracketError !== null && !hasIncompleteGenerator) {
|
|
66
|
-
collect(bracketError, ast);
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
for (const error of deduplicateErrors(collected)) {
|
|
70
|
-
reporter(error);
|
|
71
|
-
if (annotateErrors) {
|
|
72
|
-
const target = annotationTargets.get(error);
|
|
73
|
-
if (target !== undefined) {
|
|
74
|
-
annotateError(target, error.code, error.params);
|
|
75
|
-
}
|
|
76
|
-
}
|
|
77
|
-
}
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
// ====== Internals =========
|
|
81
|
-
function extractInternal(
|
|
82
|
-
node: AstNode,
|
|
83
|
-
expression: string,
|
|
84
|
-
collect: (error: RSErrorDescription, target: AstNode) => void,
|
|
85
|
-
ignoreUnknownErrors: boolean,
|
|
86
|
-
options?: SyntaxErrorOptions
|
|
87
|
-
) {
|
|
88
|
-
if (node.typeID !== TokenID.ERROR) {
|
|
89
|
-
return;
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
function emit(target: AstNode, code: RSErrorCode, params?: readonly string[]) {
|
|
93
|
-
collect({ code: code, from: target.from, to: target.to, params }, target);
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
const classified = classifyParseError(node, expression, options);
|
|
97
|
-
if (classified !== null) {
|
|
98
|
-
collect(classified, node);
|
|
99
|
-
return;
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
const parent = node.parent;
|
|
103
|
-
if (parent === null) {
|
|
104
|
-
if (!ignoreUnknownErrors) {
|
|
105
|
-
return emit(node, RSErrorCode.unknownSyntax);
|
|
106
|
-
}
|
|
107
|
-
return;
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
if (parent.typeID === Variable) {
|
|
111
|
-
return emit(parent, RSErrorCode.expectedLocal);
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
if (!ignoreUnknownErrors) {
|
|
115
|
-
emit(node.from === node.to ? parent : node, RSErrorCode.unknownSyntax);
|
|
116
|
-
}
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
function classifyParseError(
|
|
120
|
-
node: AstNode,
|
|
121
|
-
expression: string,
|
|
122
|
-
options?: SyntaxErrorOptions
|
|
123
|
-
): RSErrorDescription | null {
|
|
124
|
-
const incompleteFunctionDecl = detectIncompleteFunctionDecl(node, expression, options?.expected);
|
|
125
|
-
if (incompleteFunctionDecl !== null) {
|
|
126
|
-
return incompleteFunctionDecl;
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
const incompleteQuantifier = detectIncompleteQuantifier(node, expression);
|
|
130
|
-
if (incompleteQuantifier !== null) {
|
|
131
|
-
return incompleteQuantifier;
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
const incompleteGenerator = detectIncompleteGenerator(node, expression);
|
|
135
|
-
if (incompleteGenerator !== null) {
|
|
136
|
-
return incompleteGenerator;
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
const filterParen = detectFilterParenMismatch(node, expression);
|
|
140
|
-
if (filterParen !== null) {
|
|
141
|
-
return filterParen;
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
return detectGlobalFuncWithoutArgs(node, expression);
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
function detectIncompleteFunctionDecl(
|
|
148
|
-
node: AstNode,
|
|
149
|
-
expression: string,
|
|
150
|
-
expected?: TypeClass
|
|
151
|
-
): RSErrorDescription | null {
|
|
152
|
-
const funcDecl = findAncestor(node, isFunctionDeclNode);
|
|
153
|
-
if (funcDecl === null) {
|
|
154
|
-
return null;
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
const bodyChild = funcDecl.children[funcDecl.children.length - 1];
|
|
158
|
-
if (bodyChild?.typeID !== TokenID.ERROR || node !== bodyChild) {
|
|
159
|
-
return null;
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
const argsNode = funcDecl.children.find(child => child.typeID === Arguments || child.typeID === TokenID.NT_ARGUMENTS);
|
|
163
|
-
if (argsNode === undefined || subtreeHasError(argsNode)) {
|
|
164
|
-
return null;
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
if (expression.slice(funcDecl.to).trim().length > 0) {
|
|
168
|
-
return null;
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
return {
|
|
172
|
-
code: incompleteFormalExpressionCode(expected),
|
|
173
|
-
from: bodyChild.from,
|
|
174
|
-
to: bodyChild.to
|
|
175
|
-
};
|
|
176
|
-
}
|
|
177
|
-
|
|
178
|
-
function incompleteFormalExpressionCode(expected?: TypeClass): RSErrorCode {
|
|
179
|
-
switch (expected) {
|
|
180
|
-
case TypeClassEnum.function:
|
|
181
|
-
return RSErrorCode.expectedExpressionBody;
|
|
182
|
-
case TypeClassEnum.predicate:
|
|
183
|
-
case TypeClassEnum.logic:
|
|
184
|
-
return RSErrorCode.expectedLogicBody;
|
|
185
|
-
default:
|
|
186
|
-
return RSErrorCode.expectedFunctionBody;
|
|
187
|
-
}
|
|
188
|
-
}
|
|
189
|
-
|
|
190
|
-
function detectIncompleteQuantifier(node: AstNode, expression: string): RSErrorDescription | null {
|
|
191
|
-
const quantNode = findAncestor(node, isQuantifierNode);
|
|
192
|
-
if (quantNode === null) {
|
|
193
|
-
return null;
|
|
194
|
-
}
|
|
195
|
-
|
|
196
|
-
const bodyChild = quantNode.children[quantNode.children.length - 1];
|
|
197
|
-
if (bodyChild?.typeID !== TokenID.ERROR || node !== bodyChild) {
|
|
198
|
-
return null;
|
|
199
|
-
}
|
|
200
|
-
|
|
201
|
-
const headerChildren = quantNode.children.slice(0, -1);
|
|
202
|
-
if (headerChildren.length < 3) {
|
|
203
|
-
return null;
|
|
204
|
-
}
|
|
205
|
-
|
|
206
|
-
const varPack = headerChildren.find(child => child.typeID === Variable_pack || child.typeID === TokenID.NT_ENUM_DECL);
|
|
207
|
-
if (varPack === undefined || subtreeHasError(varPack)) {
|
|
208
|
-
return null;
|
|
209
|
-
}
|
|
210
|
-
|
|
211
|
-
const domainChild = headerChildren[headerChildren.length - 1];
|
|
212
|
-
if (domainChild === varPack || subtreeHasError(domainChild)) {
|
|
213
|
-
return null;
|
|
214
|
-
}
|
|
215
|
-
|
|
216
|
-
if (expression.slice(quantNode.to).trim().length > 0) {
|
|
217
|
-
return null;
|
|
218
|
-
}
|
|
219
|
-
|
|
220
|
-
return {
|
|
221
|
-
code: RSErrorCode.expectedQuantifierBody,
|
|
222
|
-
from: bodyChild.from,
|
|
223
|
-
to: bodyChild.to
|
|
224
|
-
};
|
|
225
|
-
}
|
|
226
|
-
|
|
227
|
-
function detectIncompleteGenerator(node: AstNode, expression: string): RSErrorDescription | null {
|
|
228
|
-
return (
|
|
229
|
-
detectIncompleteDeclarative(node, expression) ??
|
|
230
|
-
detectIncompleteImperative(node, expression) ??
|
|
231
|
-
detectIncompleteRecursion(node, expression)
|
|
232
|
-
);
|
|
233
|
-
}
|
|
234
|
-
|
|
235
|
-
function detectIncompleteDeclarative(node: AstNode, expression: string): RSErrorDescription | null {
|
|
236
|
-
const generatorNode = findAncestor(
|
|
237
|
-
node,
|
|
238
|
-
target => target.typeID === Declarative || target.typeID === TokenID.NT_DECLARATIVE_EXPR
|
|
239
|
-
);
|
|
240
|
-
return detectIncompleteGeneratorBody(node, expression, generatorNode, RSErrorCode.expectedDeclarativeBody, slot =>
|
|
241
|
-
isIncompleteDeclarativeBody(slot)
|
|
242
|
-
);
|
|
243
|
-
}
|
|
244
|
-
|
|
245
|
-
function detectIncompleteImperative(node: AstNode, expression: string): RSErrorDescription | null {
|
|
246
|
-
const generatorNode = findAncestor(
|
|
247
|
-
node,
|
|
248
|
-
target => target.typeID === Imperative || target.typeID === TokenID.NT_IMPERATIVE_EXPR
|
|
249
|
-
);
|
|
250
|
-
return detectIncompleteGeneratorBody(node, expression, generatorNode, RSErrorCode.expectedImperativeBody, slot =>
|
|
251
|
-
isIncompleteImperativeBody(slot, expression)
|
|
252
|
-
);
|
|
253
|
-
}
|
|
254
|
-
|
|
255
|
-
function detectIncompleteRecursion(node: AstNode, expression: string): RSErrorDescription | null {
|
|
256
|
-
const generatorNode = findAncestor(node, target => target.typeID === Recursion);
|
|
257
|
-
return detectIncompleteGeneratorBody(node, expression, generatorNode, RSErrorCode.expectedRecursiveBody, slot =>
|
|
258
|
-
isIncompleteRecursionBody(slot)
|
|
259
|
-
);
|
|
260
|
-
}
|
|
261
|
-
|
|
262
|
-
interface GeneratorBodySlot {
|
|
263
|
-
nodes: AstNode[];
|
|
264
|
-
headerEndIndex: number;
|
|
265
|
-
}
|
|
266
|
-
|
|
267
|
-
function detectIncompleteGeneratorBody(
|
|
268
|
-
node: AstNode,
|
|
269
|
-
expression: string,
|
|
270
|
-
generatorNode: AstNode | null,
|
|
271
|
-
code: RSErrorCode,
|
|
272
|
-
isIncomplete: (slot: GeneratorBodySlot) => boolean
|
|
273
|
-
): RSErrorDescription | null {
|
|
274
|
-
if (generatorNode === null) {
|
|
275
|
-
return null;
|
|
276
|
-
}
|
|
277
|
-
|
|
278
|
-
if (expression.slice(generatorNode.to).trim().length > 0) {
|
|
279
|
-
return null;
|
|
280
|
-
}
|
|
281
|
-
|
|
282
|
-
const slot = getGeneratorBodySlot(generatorNode, expression);
|
|
283
|
-
if (slot === null || !isIncomplete(slot)) {
|
|
284
|
-
return null;
|
|
285
|
-
}
|
|
286
|
-
|
|
287
|
-
if (!generatorHeaderIsValid(generatorNode, expression, slot.headerEndIndex)) {
|
|
288
|
-
return null;
|
|
289
|
-
}
|
|
290
|
-
|
|
291
|
-
if (!isErrorInBodySlot(node, slot, generatorNode)) {
|
|
292
|
-
return null;
|
|
293
|
-
}
|
|
294
|
-
|
|
295
|
-
return incompleteGeneratorError(code, node);
|
|
296
|
-
}
|
|
297
|
-
|
|
298
|
-
function getGeneratorBodySlot(generatorNode: AstNode, expression: string): GeneratorBodySlot | null {
|
|
299
|
-
if (generatorNode.typeID === Declarative || generatorNode.typeID === TokenID.NT_DECLARATIVE_EXPR) {
|
|
300
|
-
return getDeclarativeBodySlot(generatorNode, expression);
|
|
301
|
-
}
|
|
302
|
-
if (generatorNode.typeID === Imperative || generatorNode.typeID === TokenID.NT_IMPERATIVE_EXPR) {
|
|
303
|
-
return getImperativeBodySlot(generatorNode, expression);
|
|
304
|
-
}
|
|
305
|
-
if (generatorNode.typeID === Recursion) {
|
|
306
|
-
return getRecursionBodySlot(generatorNode, expression);
|
|
307
|
-
}
|
|
308
|
-
return null;
|
|
309
|
-
}
|
|
310
|
-
|
|
311
|
-
function getDeclarativeBodySlot(declNode: AstNode, expression: string): GeneratorBodySlot | null {
|
|
312
|
-
const pipeIndex = declNode.children.findIndex(child => isPipeToken(child, expression));
|
|
313
|
-
if (pipeIndex >= 0) {
|
|
314
|
-
return {
|
|
315
|
-
nodes: contentChildren(declNode, expression, pipeIndex + 1),
|
|
316
|
-
headerEndIndex: pipeIndex + 1
|
|
317
|
-
};
|
|
318
|
-
}
|
|
319
|
-
|
|
320
|
-
const domainNode = findDeclarativeDomain(declNode, expression);
|
|
321
|
-
if (domainNode === null) {
|
|
322
|
-
return null;
|
|
323
|
-
}
|
|
324
|
-
|
|
325
|
-
const domainIndex = declNode.children.indexOf(domainNode);
|
|
326
|
-
return {
|
|
327
|
-
nodes: contentChildren(declNode, expression, domainIndex + 1),
|
|
328
|
-
headerEndIndex: domainIndex + 1
|
|
329
|
-
};
|
|
330
|
-
}
|
|
331
|
-
|
|
332
|
-
function getImperativeBodySlot(impNode: AstNode, expression: string): GeneratorBodySlot | null {
|
|
333
|
-
const pipeIndex = impNode.children.findIndex(child => isPipeToken(child, expression));
|
|
334
|
-
if (pipeIndex >= 0) {
|
|
335
|
-
return {
|
|
336
|
-
nodes: contentChildren(impNode, expression, pipeIndex + 1),
|
|
337
|
-
headerEndIndex: pipeIndex + 1
|
|
338
|
-
};
|
|
339
|
-
}
|
|
340
|
-
|
|
341
|
-
const tupleNode = findImperativeTuple(impNode);
|
|
342
|
-
if (tupleNode === null) {
|
|
343
|
-
return null;
|
|
344
|
-
}
|
|
345
|
-
|
|
346
|
-
const tupleIndex = impNode.children.indexOf(tupleNode);
|
|
347
|
-
return {
|
|
348
|
-
nodes: contentChildren(impNode, expression, tupleIndex + 1),
|
|
349
|
-
headerEndIndex: tupleIndex + 1
|
|
350
|
-
};
|
|
351
|
-
}
|
|
352
|
-
|
|
353
|
-
function getRecursionBodySlot(recNode: AstNode, expression: string): GeneratorBodySlot | null {
|
|
354
|
-
const initNode = findRecursionInit(recNode, expression);
|
|
355
|
-
if (initNode === null) {
|
|
356
|
-
return null;
|
|
357
|
-
}
|
|
358
|
-
|
|
359
|
-
const initIndex = recNode.children.indexOf(initNode);
|
|
360
|
-
const afterInit = contentChildren(recNode, expression, initIndex + 1);
|
|
361
|
-
const lastPipeIndex = findLastPipeIndex(afterInit, expression);
|
|
362
|
-
if (lastPipeIndex < 0) {
|
|
363
|
-
return {
|
|
364
|
-
nodes: afterInit,
|
|
365
|
-
headerEndIndex: initIndex + 1
|
|
366
|
-
};
|
|
367
|
-
}
|
|
368
|
-
|
|
369
|
-
return {
|
|
370
|
-
nodes: afterInit.slice(lastPipeIndex + 1),
|
|
371
|
-
headerEndIndex: recNode.children.indexOf(afterInit[lastPipeIndex]) + 1
|
|
372
|
-
};
|
|
373
|
-
}
|
|
374
|
-
|
|
375
|
-
function contentChildren(generatorNode: AstNode, expression: string, fromIndex: number): AstNode[] {
|
|
376
|
-
return generatorNode.children.slice(fromIndex).filter(child => !isClosingBrace(child, expression));
|
|
377
|
-
}
|
|
378
|
-
|
|
379
|
-
function findDeclarativeDomain(declNode: AstNode, expression: string): AstNode | null {
|
|
380
|
-
const inIndex = declNode.children.findIndex(child => expression.slice(child.from, child.to) === '∈');
|
|
381
|
-
if (inIndex < 0) {
|
|
382
|
-
return null;
|
|
383
|
-
}
|
|
384
|
-
|
|
385
|
-
for (let index = inIndex + 1; index < declNode.children.length; index++) {
|
|
386
|
-
const child = declNode.children[index];
|
|
387
|
-
if (isPipeToken(child, expression) || isClosingBrace(child, expression)) {
|
|
388
|
-
break;
|
|
389
|
-
}
|
|
390
|
-
if (child.typeID === Setexpr) {
|
|
391
|
-
return child;
|
|
392
|
-
}
|
|
393
|
-
}
|
|
394
|
-
|
|
395
|
-
return null;
|
|
396
|
-
}
|
|
397
|
-
|
|
398
|
-
function findImperativeTuple(impNode: AstNode): AstNode | null {
|
|
399
|
-
for (const child of impNode.children) {
|
|
400
|
-
if (child.typeID === Setexpr) {
|
|
401
|
-
return child;
|
|
402
|
-
}
|
|
403
|
-
}
|
|
404
|
-
return null;
|
|
405
|
-
}
|
|
406
|
-
|
|
407
|
-
function findLastPipeIndex(nodes: AstNode[], expression: string): number {
|
|
408
|
-
for (let index = nodes.length - 1; index >= 0; index--) {
|
|
409
|
-
if (isPipeToken(nodes[index], expression)) {
|
|
410
|
-
return index;
|
|
411
|
-
}
|
|
412
|
-
}
|
|
413
|
-
return -1;
|
|
414
|
-
}
|
|
415
|
-
|
|
416
|
-
function generatorHeaderIsValid(generatorNode: AstNode, expression: string, headerEndIndex: number): boolean {
|
|
417
|
-
for (let index = 0; index < headerEndIndex; index++) {
|
|
418
|
-
const child = generatorNode.children[index];
|
|
419
|
-
if (child.typeID === TokenID.ERROR) {
|
|
420
|
-
return false;
|
|
421
|
-
}
|
|
422
|
-
if (isStructuralGeneratorToken(child, expression)) {
|
|
423
|
-
continue;
|
|
424
|
-
}
|
|
425
|
-
if (subtreeHasError(child)) {
|
|
426
|
-
return false;
|
|
427
|
-
}
|
|
428
|
-
}
|
|
429
|
-
return true;
|
|
430
|
-
}
|
|
431
|
-
|
|
432
|
-
function isIncompleteDeclarativeBody(slot: GeneratorBodySlot): boolean {
|
|
433
|
-
return isEmptyOrErrorOnlyBody(slot.nodes);
|
|
434
|
-
}
|
|
435
|
-
|
|
436
|
-
function isIncompleteImperativeBody(slot: GeneratorBodySlot, expression: string): boolean {
|
|
437
|
-
if (slot.nodes.length === 0) {
|
|
438
|
-
return true;
|
|
439
|
-
}
|
|
440
|
-
if (slot.nodes.length === 1 && slot.nodes[0].typeID === Imp_blocks) {
|
|
441
|
-
return isImpBlocksIncomplete(slot.nodes[0], expression);
|
|
442
|
-
}
|
|
443
|
-
return isEmptyOrErrorOnlyBody(slot.nodes);
|
|
444
|
-
}
|
|
445
|
-
|
|
446
|
-
function isIncompleteRecursionBody(slot: GeneratorBodySlot): boolean {
|
|
447
|
-
return isEmptyOrErrorOnlyBody(slot.nodes);
|
|
448
|
-
}
|
|
449
|
-
|
|
450
|
-
function isEmptyOrErrorOnlyBody(nodes: AstNode[]): boolean {
|
|
451
|
-
if (nodes.length === 0) {
|
|
452
|
-
return true;
|
|
453
|
-
}
|
|
454
|
-
if (nodes.length === 1) {
|
|
455
|
-
return isErrorPlaceholder(nodes[0]);
|
|
456
|
-
}
|
|
457
|
-
return false;
|
|
458
|
-
}
|
|
459
|
-
|
|
460
|
-
function isImpBlocksIncomplete(blocksNode: AstNode, expression: string): boolean {
|
|
461
|
-
if (blocksNode.children.length === 0) {
|
|
462
|
-
return true;
|
|
463
|
-
}
|
|
464
|
-
|
|
465
|
-
const lastChild = blocksNode.children[blocksNode.children.length - 1];
|
|
466
|
-
if (lastChild.typeID === TokenID.ERROR) {
|
|
467
|
-
return true;
|
|
468
|
-
}
|
|
469
|
-
if (isSemicolonToken(lastChild, expression)) {
|
|
470
|
-
return true;
|
|
471
|
-
}
|
|
472
|
-
if (lastChild.typeID === Setexpr && isErrorPlaceholder(lastChild)) {
|
|
473
|
-
return true;
|
|
474
|
-
}
|
|
475
|
-
|
|
476
|
-
return false;
|
|
477
|
-
}
|
|
478
|
-
|
|
479
|
-
function isErrorInBodySlot(errorNode: AstNode, slot: GeneratorBodySlot, generatorNode: AstNode): boolean {
|
|
480
|
-
if (slot.nodes.length === 0) {
|
|
481
|
-
const bodyStart = generatorNode.children[slot.headerEndIndex]?.from ?? generatorNode.from;
|
|
482
|
-
return errorNode.from >= bodyStart;
|
|
483
|
-
}
|
|
484
|
-
|
|
485
|
-
for (const bodyNode of slot.nodes) {
|
|
486
|
-
if (errorNode === bodyNode || isDescendantOf(bodyNode, errorNode)) {
|
|
487
|
-
if (isErrorPlaceholder(bodyNode)) {
|
|
488
|
-
return true;
|
|
489
|
-
}
|
|
490
|
-
if (bodyNode.typeID === Imp_blocks && isImpBlocksTailError(bodyNode, errorNode)) {
|
|
491
|
-
return true;
|
|
492
|
-
}
|
|
493
|
-
}
|
|
494
|
-
}
|
|
495
|
-
|
|
496
|
-
return false;
|
|
497
|
-
}
|
|
498
|
-
|
|
499
|
-
function isImpBlocksTailError(blocksNode: AstNode, errorNode: AstNode): boolean {
|
|
500
|
-
const lastChild = blocksNode.children[blocksNode.children.length - 1];
|
|
501
|
-
return lastChild.typeID === TokenID.ERROR && (errorNode === lastChild || isDescendantOf(lastChild, errorNode));
|
|
502
|
-
}
|
|
503
|
-
|
|
504
|
-
function isErrorPlaceholder(node: AstNode): boolean {
|
|
505
|
-
if (node.typeID === TokenID.ERROR) {
|
|
506
|
-
return true;
|
|
507
|
-
}
|
|
508
|
-
if (node.children.length === 0) {
|
|
509
|
-
return false;
|
|
510
|
-
}
|
|
511
|
-
return node.children.every(child => isErrorPlaceholder(child));
|
|
512
|
-
}
|
|
513
|
-
|
|
514
|
-
function isDescendantOf(ancestor: AstNode, node: AstNode): boolean {
|
|
515
|
-
let current: AstNode | null = node.parent;
|
|
516
|
-
while (current !== null) {
|
|
517
|
-
if (current === ancestor) {
|
|
518
|
-
return true;
|
|
519
|
-
}
|
|
520
|
-
current = current.parent;
|
|
521
|
-
}
|
|
522
|
-
return false;
|
|
523
|
-
}
|
|
524
|
-
|
|
525
|
-
function findRecursionInit(recursionNode: AstNode, expression: string): AstNode | null {
|
|
526
|
-
const assignIndex = recursionNode.children.findIndex(child => expression.slice(child.from, child.to) === ':=');
|
|
527
|
-
if (assignIndex < 0) {
|
|
528
|
-
return null;
|
|
529
|
-
}
|
|
530
|
-
|
|
531
|
-
for (let index = assignIndex + 1; index < recursionNode.children.length; index++) {
|
|
532
|
-
const child = recursionNode.children[index];
|
|
533
|
-
if (child.typeID === TokenID.ERROR) {
|
|
534
|
-
break;
|
|
535
|
-
}
|
|
536
|
-
if (isPipeToken(child, expression) || isClosingBrace(child, expression)) {
|
|
537
|
-
continue;
|
|
538
|
-
}
|
|
539
|
-
return child;
|
|
540
|
-
}
|
|
541
|
-
|
|
542
|
-
return null;
|
|
543
|
-
}
|
|
544
|
-
|
|
545
|
-
function incompleteGeneratorError(code: RSErrorCode, errorNode: AstNode): RSErrorDescription {
|
|
546
|
-
return {
|
|
547
|
-
code,
|
|
548
|
-
from: errorNode.from,
|
|
549
|
-
to: errorNode.to
|
|
550
|
-
};
|
|
551
|
-
}
|
|
552
|
-
|
|
553
|
-
function isIncompleteGeneratorBodyCode(code: RSErrorCode): boolean {
|
|
554
|
-
return (
|
|
555
|
-
code === RSErrorCode.expectedDeclarativeBody ||
|
|
556
|
-
code === RSErrorCode.expectedImperativeBody ||
|
|
557
|
-
code === RSErrorCode.expectedRecursiveBody
|
|
558
|
-
);
|
|
559
|
-
}
|
|
560
|
-
|
|
561
|
-
function isPipeToken(node: AstNode, expression: string): boolean {
|
|
562
|
-
return expression.slice(node.from, node.to) === '|';
|
|
563
|
-
}
|
|
564
|
-
|
|
565
|
-
function isSemicolonToken(node: AstNode, expression: string): boolean {
|
|
566
|
-
return expression.slice(node.from, node.to) === ';';
|
|
567
|
-
}
|
|
568
|
-
|
|
569
|
-
function isClosingBrace(node: AstNode, expression: string): boolean {
|
|
570
|
-
return node.typeID !== TokenID.ERROR && expression.slice(node.from, node.to) === '}';
|
|
571
|
-
}
|
|
572
|
-
|
|
573
|
-
function isStructuralGeneratorToken(node: AstNode, expression: string): boolean {
|
|
574
|
-
const text = expression.slice(node.from, node.to);
|
|
575
|
-
return text === '{' || text === '}' || text === '|' || text === '∈' || text === ':=' || text === ';';
|
|
576
|
-
}
|
|
577
|
-
|
|
578
|
-
function subtreeHasError(node: AstNode): boolean {
|
|
579
|
-
if (node.typeID === TokenID.ERROR) {
|
|
580
|
-
return true;
|
|
581
|
-
}
|
|
582
|
-
return node.children.some(subtreeHasError);
|
|
583
|
-
}
|
|
584
|
-
|
|
585
|
-
function detectFilterParenMismatch(node: AstNode, expression: string): RSErrorDescription | null {
|
|
586
|
-
const filterNode = findAncestor(node, isFilterNode) ?? findRelatedNode(node, isFilterNode);
|
|
587
|
-
if (filterNode === null) {
|
|
588
|
-
return null;
|
|
589
|
-
}
|
|
590
|
-
|
|
591
|
-
const nextChar = expression[filterNode.to];
|
|
592
|
-
if (nextChar === '(') {
|
|
593
|
-
return {
|
|
594
|
-
code: RSErrorCode.invalidFilterSyntax,
|
|
595
|
-
from: filterNode.from,
|
|
596
|
-
to: filterNode.to + 1
|
|
597
|
-
};
|
|
598
|
-
}
|
|
599
|
-
return null;
|
|
600
|
-
}
|
|
601
|
-
|
|
602
|
-
function detectGlobalFuncWithoutArgs(node: AstNode, expression: string): RSErrorDescription | null {
|
|
603
|
-
const funcNode =
|
|
604
|
-
findAncestor(node, n => isFunctionNode(n) || isPredicateNode(n)) ??
|
|
605
|
-
findRelatedNode(node, n => isFunctionNode(n) || isPredicateNode(n));
|
|
606
|
-
if (funcNode === null) {
|
|
607
|
-
return null;
|
|
608
|
-
}
|
|
609
|
-
return classifyFuncWithoutArgs(funcNode, expression);
|
|
610
|
-
}
|
|
611
|
-
|
|
612
|
-
function findRelatedNode(node: AstNode, predicate: (target: AstNode) => boolean): AstNode | null {
|
|
613
|
-
let current: AstNode | null = node.parent;
|
|
614
|
-
while (current !== null) {
|
|
615
|
-
for (const child of current.children) {
|
|
616
|
-
if (predicate(child)) {
|
|
617
|
-
return child;
|
|
618
|
-
}
|
|
619
|
-
}
|
|
620
|
-
current = current.parent;
|
|
621
|
-
}
|
|
622
|
-
return null;
|
|
623
|
-
}
|
|
624
|
-
|
|
625
|
-
function classifyFuncWithoutArgs(funcNode: AstNode, expression: string): RSErrorDescription | null {
|
|
626
|
-
const name = expression.slice(funcNode.from, funcNode.to);
|
|
627
|
-
const rest = expression.slice(funcNode.to);
|
|
628
|
-
|
|
629
|
-
const withoutArgs: RSErrorDescription = {
|
|
630
|
-
code: RSErrorCode.globalFuncWithoutArgs,
|
|
631
|
-
from: funcNode.from,
|
|
632
|
-
to: funcNode.to,
|
|
633
|
-
params: [name]
|
|
634
|
-
};
|
|
635
|
-
|
|
636
|
-
// Function with nothing (or only whitespace) after it is genuinely called without arguments.
|
|
637
|
-
if (/^\s*$/.test(rest)) {
|
|
638
|
-
return withoutArgs;
|
|
639
|
-
}
|
|
640
|
-
|
|
641
|
-
// Anything other than a bracketed argument form (e.g. parentheses or braces) is malformed call
|
|
642
|
-
// syntax: defer to the bracket/syntax error handling instead of reporting a missing-arguments error.
|
|
643
|
-
const openBracket = /^\s*\[/.exec(rest);
|
|
644
|
-
if (openBracket === null) {
|
|
645
|
-
return null;
|
|
646
|
-
}
|
|
647
|
-
|
|
648
|
-
const closeIdx = findMatchingCloseBracket(rest, openBracket[0].length - 1);
|
|
649
|
-
if (closeIdx >= 0) {
|
|
650
|
-
const inner = rest.slice(openBracket[0].length, closeIdx).trim();
|
|
651
|
-
if (inner.length === 0) {
|
|
652
|
-
return withoutArgs;
|
|
653
|
-
}
|
|
654
|
-
}
|
|
655
|
-
|
|
656
|
-
return null;
|
|
657
|
-
}
|
|
658
|
-
|
|
659
|
-
function findAncestor(node: AstNode, predicate: (node: AstNode) => boolean): AstNode | null {
|
|
660
|
-
let current: AstNode | null = node;
|
|
661
|
-
while (current !== null) {
|
|
662
|
-
if (predicate(current)) {
|
|
663
|
-
return current;
|
|
664
|
-
}
|
|
665
|
-
current = current.parent;
|
|
666
|
-
}
|
|
667
|
-
return null;
|
|
668
|
-
}
|
|
669
|
-
|
|
670
|
-
function isFunctionDeclNode(node: AstNode): boolean {
|
|
671
|
-
return node.typeID === Function_decl || node.typeID === TokenID.NT_FUNC_DEFINITION;
|
|
672
|
-
}
|
|
673
|
-
|
|
674
|
-
function isQuantifierNode(node: AstNode): boolean {
|
|
675
|
-
return node.typeID === Logic_quantor;
|
|
676
|
-
}
|
|
677
|
-
|
|
678
|
-
function isFunctionNode(node: AstNode): boolean {
|
|
679
|
-
return node.typeID === Function || node.typeID === TokenID.ID_FUNCTION;
|
|
680
|
-
}
|
|
681
|
-
|
|
682
|
-
function isPredicateNode(node: AstNode): boolean {
|
|
683
|
-
return node.typeID === Predicate || node.typeID === TokenID.ID_PREDICATE;
|
|
684
|
-
}
|
|
685
|
-
|
|
686
|
-
function isFilterNode(node: AstNode): boolean {
|
|
687
|
-
return node.typeID === Filter || node.typeID === TokenID.FILTER;
|
|
688
|
-
}
|
|
689
|
-
|
|
690
|
-
function findMatchingCloseBracket(expression: string, openPos: number): number {
|
|
691
|
-
const open = expression[openPos];
|
|
692
|
-
if (open !== '[') {
|
|
693
|
-
return -1;
|
|
694
|
-
}
|
|
695
|
-
|
|
696
|
-
let depth = 0;
|
|
697
|
-
for (let index = openPos; index < expression.length; index++) {
|
|
698
|
-
const symbol = expression[index];
|
|
699
|
-
if (symbol === '[') {
|
|
700
|
-
depth++;
|
|
701
|
-
} else if (symbol === ']') {
|
|
702
|
-
depth--;
|
|
703
|
-
if (depth === 0) {
|
|
704
|
-
return index;
|
|
705
|
-
}
|
|
706
|
-
}
|
|
707
|
-
}
|
|
708
|
-
return -1;
|
|
709
|
-
}
|
|
710
|
-
|
|
711
|
-
function deduplicateErrors(errors: RSErrorDescription[]): RSErrorDescription[] {
|
|
712
|
-
const result: RSErrorDescription[] = [];
|
|
713
|
-
|
|
714
|
-
for (const error of errors) {
|
|
715
|
-
const duplicate = result.some(
|
|
716
|
-
existing => existing.code === error.code && existing.from === error.from && existing.to === error.to
|
|
717
|
-
);
|
|
718
|
-
if (duplicate) {
|
|
719
|
-
continue;
|
|
720
|
-
}
|
|
721
|
-
|
|
722
|
-
if (error.code !== RSErrorCode.unknownSyntax) {
|
|
723
|
-
for (let index = result.length - 1; index >= 0; index--) {
|
|
724
|
-
if (result[index].code === RSErrorCode.unknownSyntax && rangesOverlap(error, result[index])) {
|
|
725
|
-
result.splice(index, 1);
|
|
726
|
-
}
|
|
727
|
-
}
|
|
728
|
-
}
|
|
729
|
-
|
|
730
|
-
result.push(error);
|
|
731
|
-
}
|
|
732
|
-
|
|
733
|
-
const specific = result.filter(error => error.code !== RSErrorCode.unknownSyntax);
|
|
734
|
-
return result.filter(
|
|
735
|
-
error => error.code !== RSErrorCode.unknownSyntax || !specific.some(item => rangesOverlap(error, item))
|
|
736
|
-
);
|
|
737
|
-
}
|
|
738
|
-
|
|
739
|
-
function rangesOverlap(a: RSErrorDescription, b: RSErrorDescription): boolean {
|
|
740
|
-
return a.from < b.to && b.from < a.to;
|
|
741
|
-
}
|
|
742
|
-
|
|
743
|
-
type OpenBracket = '(' | '[' | '{';
|
|
744
|
-
type CloseBracket = ')' | ']' | '}';
|
|
745
|
-
|
|
746
|
-
interface BracketFrame {
|
|
747
|
-
bracket: OpenBracket;
|
|
748
|
-
index: number;
|
|
749
|
-
}
|
|
750
|
-
|
|
751
|
-
const ALLOWED_EXPRESSION_CHARS = buildAllowedExpressionChars();
|
|
752
|
-
|
|
753
|
-
function buildAllowedExpressionChars(): Set<string> {
|
|
754
|
-
const chars = new Set<string>();
|
|
755
|
-
for (const chunk of [
|
|
756
|
-
'0123456789',
|
|
757
|
-
'abcdefghijklmnopqrstuvwxyz',
|
|
758
|
-
'ABCDEFGHIJKLMNOPQRSTUVWXYZ',
|
|
759
|
-
'_',
|
|
760
|
-
'∅¬∀∃⇔⇒∨&ℬ+-*∪\\∆∩×∈∉⊆⊄⊂<>≥≤≠=,:;|[]{}()'
|
|
761
|
-
]) {
|
|
762
|
-
for (const symbol of chunk) {
|
|
763
|
-
chars.add(symbol);
|
|
764
|
-
}
|
|
765
|
-
}
|
|
766
|
-
for (let codePoint = 0x03b1; codePoint <= 0x03c9; codePoint++) {
|
|
767
|
-
chars.add(String.fromCodePoint(codePoint));
|
|
768
|
-
}
|
|
769
|
-
return chars;
|
|
770
|
-
}
|
|
771
|
-
|
|
772
|
-
function isAllowedExpressionChar(symbol: string): boolean {
|
|
773
|
-
return /\s/u.test(symbol) || ALLOWED_EXPRESSION_CHARS.has(symbol);
|
|
774
|
-
}
|
|
775
|
-
|
|
776
|
-
function extractForbiddenCharacterErrors(expression: string): RSErrorDescription[] {
|
|
777
|
-
const errors: RSErrorDescription[] = [];
|
|
778
|
-
let pos = 0;
|
|
779
|
-
|
|
780
|
-
while (pos < expression.length) {
|
|
781
|
-
const symbol = expression[pos];
|
|
782
|
-
if (isAllowedExpressionChar(symbol)) {
|
|
783
|
-
pos++;
|
|
784
|
-
continue;
|
|
785
|
-
}
|
|
786
|
-
|
|
787
|
-
const start = pos;
|
|
788
|
-
while (pos < expression.length && !isAllowedExpressionChar(expression[pos])) {
|
|
789
|
-
pos++;
|
|
790
|
-
}
|
|
791
|
-
|
|
792
|
-
errors.push({
|
|
793
|
-
code: RSErrorCode.forbiddenCharacter,
|
|
794
|
-
from: start,
|
|
795
|
-
to: pos,
|
|
796
|
-
params: [expression.slice(start, pos)]
|
|
797
|
-
});
|
|
798
|
-
}
|
|
799
|
-
|
|
800
|
-
return errors;
|
|
801
|
-
}
|
|
802
|
-
|
|
803
|
-
function extractBracketErrors(expression: string): RSErrorDescription | null {
|
|
804
|
-
const stack: BracketFrame[] = [];
|
|
805
|
-
|
|
806
|
-
for (let pos = 0; pos < expression.length; pos++) {
|
|
807
|
-
const symbol = expression[pos];
|
|
808
|
-
if (isOpenBracket(symbol)) {
|
|
809
|
-
if (isDoubleParenthesis(expression, pos)) {
|
|
810
|
-
return {
|
|
811
|
-
code: RSErrorCode.doubleParenthesis,
|
|
812
|
-
from: pos,
|
|
813
|
-
to: pos + 2
|
|
814
|
-
};
|
|
815
|
-
}
|
|
816
|
-
stack.push({ bracket: symbol, index: pos });
|
|
817
|
-
continue;
|
|
818
|
-
}
|
|
819
|
-
if (!isCloseBracket(symbol)) {
|
|
820
|
-
continue;
|
|
821
|
-
}
|
|
822
|
-
|
|
823
|
-
const expectedOpen = closeToOpen(symbol);
|
|
824
|
-
const top = stack[stack.length - 1];
|
|
825
|
-
if (top === undefined) {
|
|
826
|
-
return {
|
|
827
|
-
code: RSErrorCode.missingOpenBracket,
|
|
828
|
-
from: pos,
|
|
829
|
-
to: pos + 1,
|
|
830
|
-
params: [symbol]
|
|
831
|
-
};
|
|
832
|
-
}
|
|
833
|
-
|
|
834
|
-
if (top.bracket !== expectedOpen) {
|
|
835
|
-
return {
|
|
836
|
-
code: RSErrorCode.bracketMismatch,
|
|
837
|
-
from: pos,
|
|
838
|
-
to: pos + 1,
|
|
839
|
-
params: [openToClose(top.bracket), symbol]
|
|
840
|
-
};
|
|
841
|
-
}
|
|
842
|
-
|
|
843
|
-
stack.pop();
|
|
844
|
-
}
|
|
845
|
-
|
|
846
|
-
if (stack.length > 0) {
|
|
847
|
-
const unclosed = stack[0];
|
|
848
|
-
return {
|
|
849
|
-
code: RSErrorCode.missingCloseBracket,
|
|
850
|
-
from: unclosed.index,
|
|
851
|
-
to: unclosed.index + 1,
|
|
852
|
-
params: [unclosed.bracket]
|
|
853
|
-
};
|
|
854
|
-
}
|
|
855
|
-
|
|
856
|
-
return null;
|
|
857
|
-
}
|
|
858
|
-
|
|
859
|
-
function isOpenBracket(symbol: string): symbol is OpenBracket {
|
|
860
|
-
return symbol === '(' || symbol === '[' || symbol === '{';
|
|
861
|
-
}
|
|
862
|
-
|
|
863
|
-
function isCloseBracket(symbol: string): symbol is CloseBracket {
|
|
864
|
-
return symbol === ')' || symbol === ']' || symbol === '}';
|
|
865
|
-
}
|
|
866
|
-
|
|
867
|
-
function openToClose(symbol: OpenBracket): CloseBracket {
|
|
868
|
-
switch (symbol) {
|
|
869
|
-
case '(':
|
|
870
|
-
return ')';
|
|
871
|
-
case '[':
|
|
872
|
-
return ']';
|
|
873
|
-
case '{':
|
|
874
|
-
return '}';
|
|
875
|
-
}
|
|
876
|
-
}
|
|
877
|
-
|
|
878
|
-
function closeToOpen(symbol: CloseBracket): OpenBracket {
|
|
879
|
-
switch (symbol) {
|
|
880
|
-
case ')':
|
|
881
|
-
return '(';
|
|
882
|
-
case ']':
|
|
883
|
-
return '[';
|
|
884
|
-
case '}':
|
|
885
|
-
return '{';
|
|
886
|
-
}
|
|
887
|
-
}
|
|
888
|
-
|
|
889
|
-
function isDoubleParenthesis(expression: string, pos: number): boolean {
|
|
890
|
-
const isOpenDoubleParenthesis = expression[pos] === '(' && expression[pos + 1] === '(';
|
|
891
|
-
if (!isOpenDoubleParenthesis) {
|
|
892
|
-
return false;
|
|
893
|
-
}
|
|
894
|
-
|
|
895
|
-
const outerClose = findMatchingCloseParen(expression, pos);
|
|
896
|
-
const innerClose = findMatchingCloseParen(expression, pos + 1);
|
|
897
|
-
if (outerClose < 0 || innerClose < 0) {
|
|
898
|
-
return false;
|
|
899
|
-
}
|
|
900
|
-
|
|
901
|
-
return outerClose === innerClose + 1;
|
|
902
|
-
}
|
|
903
|
-
|
|
904
|
-
function findMatchingCloseParen(expression: string, openPos: number): number {
|
|
905
|
-
if (expression[openPos] !== '(') {
|
|
906
|
-
return -1;
|
|
907
|
-
}
|
|
908
|
-
|
|
909
|
-
const stack: OpenBracket[] = [];
|
|
910
|
-
for (let index = openPos; index < expression.length; index++) {
|
|
911
|
-
const symbol = expression[index];
|
|
912
|
-
if (isOpenBracket(symbol)) {
|
|
913
|
-
stack.push(symbol);
|
|
914
|
-
continue;
|
|
915
|
-
}
|
|
916
|
-
if (!isCloseBracket(symbol)) {
|
|
917
|
-
continue;
|
|
918
|
-
}
|
|
919
|
-
const top = stack.pop();
|
|
920
|
-
if (top === undefined || top !== closeToOpen(symbol)) {
|
|
921
|
-
return -1;
|
|
922
|
-
}
|
|
923
|
-
if (stack.length === 0) {
|
|
924
|
-
return index;
|
|
925
|
-
}
|
|
926
|
-
}
|
|
927
|
-
|
|
928
|
-
return -1;
|
|
929
|
-
}
|