@rsconcept/domain 1.6.1 → 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-camCAEkQ.d.ts → analyzer-QxWs73gP.d.ts} +2 -2
- package/dist/{ast-annotations-DiXmFraa.d.ts → ast-annotations-CtF7C9uK.d.ts} +2 -2
- package/dist/{calculator-BuVooqpb.d.ts → calculator-REeX2ZS1.d.ts} +2 -2
- package/dist/{error-CZXO5Y6a.d.ts → error-toW0TUHF.d.ts} +10 -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 +9 -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/syntax-errors-pCI6RicF.js +689 -0
- package/dist/syntax-errors-pCI6RicF.js.map +1 -0
- package/package.json +1 -1
- package/src/rslang/error.ts +9 -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 +92 -6
- 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 +0 -311
- package/dist/rslang/parser/syntax-errors.js.map +0 -1
- package/src/rslang/parser/syntax-errors.ts +0 -511
|
@@ -0,0 +1,689 @@
|
|
|
1
|
+
import { annotateError } from "./rslang/ast-annotations.js";
|
|
2
|
+
import { RSErrorCode } from "./rslang/error.js";
|
|
3
|
+
import { visitAstDFS } from "./parsing/ast.js";
|
|
4
|
+
import "./parsing/index.js";
|
|
5
|
+
import { TokenID } from "./rslang/parser/token.js";
|
|
6
|
+
import { TypeClass } from "./rslang/semantic/typification.js";
|
|
7
|
+
import "./rslang/parser/parser.terms.js";
|
|
8
|
+
//#region src/rslang/parser/syntax-errors/context.ts
|
|
9
|
+
/** Skips classifiers that conflict with bracket-level diagnostics. */
|
|
10
|
+
function whenNoBracketErrors(classifier) {
|
|
11
|
+
return (node, expression, context) => context.hasBracketErrors ? null : classifier(node, expression, context);
|
|
12
|
+
}
|
|
13
|
+
//#endregion
|
|
14
|
+
//#region src/rslang/parser/syntax-errors/ast-utils.ts
|
|
15
|
+
function findAncestor(node, predicate) {
|
|
16
|
+
let current = node;
|
|
17
|
+
while (current !== null) {
|
|
18
|
+
if (predicate(current)) return current;
|
|
19
|
+
current = current.parent;
|
|
20
|
+
}
|
|
21
|
+
return null;
|
|
22
|
+
}
|
|
23
|
+
function findRelatedNode(node, predicate) {
|
|
24
|
+
let current = node.parent;
|
|
25
|
+
while (current !== null) {
|
|
26
|
+
for (const child of current.children) if (predicate(child)) return child;
|
|
27
|
+
current = current.parent;
|
|
28
|
+
}
|
|
29
|
+
return null;
|
|
30
|
+
}
|
|
31
|
+
function isDescendantOf(ancestor, node) {
|
|
32
|
+
let current = node.parent;
|
|
33
|
+
while (current !== null) {
|
|
34
|
+
if (current === ancestor) return true;
|
|
35
|
+
current = current.parent;
|
|
36
|
+
}
|
|
37
|
+
return false;
|
|
38
|
+
}
|
|
39
|
+
function subtreeHasError(node) {
|
|
40
|
+
if (node.typeID === TokenID.ERROR) return true;
|
|
41
|
+
return node.children.some(subtreeHasError);
|
|
42
|
+
}
|
|
43
|
+
function isFunctionDeclNode(node) {
|
|
44
|
+
return node.typeID === 76 || node.typeID === TokenID.NT_FUNC_DEFINITION;
|
|
45
|
+
}
|
|
46
|
+
function isQuantifierNode(node) {
|
|
47
|
+
return node.typeID === 29;
|
|
48
|
+
}
|
|
49
|
+
function isFunctionNode(node) {
|
|
50
|
+
return node.typeID === 69 || node.typeID === TokenID.ID_FUNCTION;
|
|
51
|
+
}
|
|
52
|
+
function isPredicateNode(node) {
|
|
53
|
+
return node.typeID === 26 || node.typeID === TokenID.ID_PREDICATE;
|
|
54
|
+
}
|
|
55
|
+
function isFilterNode(node) {
|
|
56
|
+
return node.typeID === 59 || node.typeID === TokenID.FILTER;
|
|
57
|
+
}
|
|
58
|
+
function findMatchingCloseBracket(expression, openPos) {
|
|
59
|
+
if (expression[openPos] !== "[") return -1;
|
|
60
|
+
let depth = 0;
|
|
61
|
+
for (let index = openPos; index < expression.length; index++) {
|
|
62
|
+
const symbol = expression[index];
|
|
63
|
+
if (symbol === "[") depth++;
|
|
64
|
+
else if (symbol === "]") {
|
|
65
|
+
depth--;
|
|
66
|
+
if (depth === 0) return index;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
return -1;
|
|
70
|
+
}
|
|
71
|
+
function getErrorOperandNode(node) {
|
|
72
|
+
if (node.typeID === TokenID.ERROR) return node;
|
|
73
|
+
if (node.children.length === 1) return getErrorOperandNode(node.children[0]);
|
|
74
|
+
return null;
|
|
75
|
+
}
|
|
76
|
+
function isErrorPlaceholder(node) {
|
|
77
|
+
if (node.typeID === TokenID.ERROR) return true;
|
|
78
|
+
if (node.children.length === 0) return false;
|
|
79
|
+
return node.children.every((child) => isErrorPlaceholder(child));
|
|
80
|
+
}
|
|
81
|
+
//#endregion
|
|
82
|
+
//#region src/rslang/parser/syntax-errors/function-calls.ts
|
|
83
|
+
const classifyFilterParenMismatch = (node, expression) => {
|
|
84
|
+
const filterNode = findAncestor(node, isFilterNode) ?? findRelatedNode(node, isFilterNode);
|
|
85
|
+
if (filterNode === null || expression[filterNode.to] !== "(") return null;
|
|
86
|
+
return {
|
|
87
|
+
code: RSErrorCode.invalidFilterSyntax,
|
|
88
|
+
from: filterNode.from,
|
|
89
|
+
to: filterNode.to + 1
|
|
90
|
+
};
|
|
91
|
+
};
|
|
92
|
+
const classifyGlobalFuncCall = (node, expression) => {
|
|
93
|
+
const funcNode = findAncestor(node, (n) => isFunctionNode(n) || isPredicateNode(n)) ?? findRelatedNode(node, (n) => isFunctionNode(n) || isPredicateNode(n));
|
|
94
|
+
if (funcNode === null) return null;
|
|
95
|
+
return classifyFuncWithoutArgs(funcNode, expression);
|
|
96
|
+
};
|
|
97
|
+
function classifyFuncWithoutArgs(funcNode, expression) {
|
|
98
|
+
const name = expression.slice(funcNode.from, funcNode.to);
|
|
99
|
+
const rest = expression.slice(funcNode.to);
|
|
100
|
+
const withoutArgs = {
|
|
101
|
+
code: RSErrorCode.globalFuncWithoutArgs,
|
|
102
|
+
from: funcNode.from,
|
|
103
|
+
to: funcNode.to,
|
|
104
|
+
params: [name]
|
|
105
|
+
};
|
|
106
|
+
if (/^\s*$/.test(rest)) return withoutArgs;
|
|
107
|
+
if (/^\s*\(/.test(rest)) return {
|
|
108
|
+
code: RSErrorCode.globalFuncParenCall,
|
|
109
|
+
from: funcNode.from,
|
|
110
|
+
to: funcNode.to,
|
|
111
|
+
params: [name]
|
|
112
|
+
};
|
|
113
|
+
const openBracket = /^\s*\[/.exec(rest);
|
|
114
|
+
if (openBracket === null) return null;
|
|
115
|
+
const closeIdx = findMatchingCloseBracket(rest, openBracket[0].length - 1);
|
|
116
|
+
if (closeIdx >= 0 && rest.slice(openBracket[0].length, closeIdx).trim().length === 0) return withoutArgs;
|
|
117
|
+
return null;
|
|
118
|
+
}
|
|
119
|
+
//#endregion
|
|
120
|
+
//#region src/rslang/parser/syntax-errors/error-builders.ts
|
|
121
|
+
function errorAt(node, code, params) {
|
|
122
|
+
return {
|
|
123
|
+
code,
|
|
124
|
+
from: node.from,
|
|
125
|
+
to: node.to,
|
|
126
|
+
params
|
|
127
|
+
};
|
|
128
|
+
}
|
|
129
|
+
function hasOnlyClosingSuffix(expression, from) {
|
|
130
|
+
const suffix = expression.slice(from).trim();
|
|
131
|
+
return suffix.length === 0 || /^[\]\)\}]*$/.test(suffix);
|
|
132
|
+
}
|
|
133
|
+
function sliceText(node, expression) {
|
|
134
|
+
return expression.slice(node.from, node.to);
|
|
135
|
+
}
|
|
136
|
+
function isTextToken(node, expression, text) {
|
|
137
|
+
return sliceText(node, expression) === text;
|
|
138
|
+
}
|
|
139
|
+
//#endregion
|
|
140
|
+
//#region src/rslang/parser/syntax-errors/generators.ts
|
|
141
|
+
const classifyIncompleteGenerator = (node, expression) => detectIncompleteDeclarative(node, expression) ?? detectIncompleteImperative(node, expression) ?? detectIncompleteRecursion(node, expression);
|
|
142
|
+
function detectIncompleteDeclarative(node, expression) {
|
|
143
|
+
return detectIncompleteGeneratorBody(node, expression, findAncestor(node, (target) => target.typeID === 60 || target.typeID === TokenID.NT_DECLARATIVE_EXPR), RSErrorCode.expectedDeclarativeBody, (slot) => isIncompleteDeclarativeBody(slot.nodes));
|
|
144
|
+
}
|
|
145
|
+
function detectIncompleteImperative(node, expression) {
|
|
146
|
+
return detectIncompleteGeneratorBody(node, expression, findAncestor(node, (target) => target.typeID === 64 || target.typeID === TokenID.NT_IMPERATIVE_EXPR), RSErrorCode.expectedImperativeBody, (slot) => isIncompleteImperativeBody(slot, expression));
|
|
147
|
+
}
|
|
148
|
+
function detectIncompleteRecursion(node, expression) {
|
|
149
|
+
return detectIncompleteGeneratorBody(node, expression, findAncestor(node, (target) => target.typeID === 68), RSErrorCode.expectedRecursiveBody, (slot) => isIncompleteRecursionBody(slot.nodes));
|
|
150
|
+
}
|
|
151
|
+
function detectIncompleteGeneratorBody(node, expression, generatorNode, code, isIncomplete) {
|
|
152
|
+
if (generatorNode === null || expression.slice(generatorNode.to).trim().length > 0) return null;
|
|
153
|
+
const slot = getGeneratorBodySlot(generatorNode, expression);
|
|
154
|
+
if (slot === null || !isIncomplete(slot) || !generatorHeaderIsValid(generatorNode, expression, slot.headerEndIndex)) return null;
|
|
155
|
+
if (!isErrorInBodySlot(node, slot, generatorNode)) return null;
|
|
156
|
+
return errorAt(node, code);
|
|
157
|
+
}
|
|
158
|
+
function getGeneratorBodySlot(generatorNode, expression) {
|
|
159
|
+
if (generatorNode.typeID === 60 || generatorNode.typeID === TokenID.NT_DECLARATIVE_EXPR) return getDeclarativeBodySlot(generatorNode, expression);
|
|
160
|
+
if (generatorNode.typeID === 64 || generatorNode.typeID === TokenID.NT_IMPERATIVE_EXPR) return getImperativeBodySlot(generatorNode, expression);
|
|
161
|
+
if (generatorNode.typeID === 68) return getRecursionBodySlot(generatorNode, expression);
|
|
162
|
+
return null;
|
|
163
|
+
}
|
|
164
|
+
function getDeclarativeBodySlot(declNode, expression) {
|
|
165
|
+
const pipeIndex = declNode.children.findIndex((child) => isTextToken(child, expression, "|"));
|
|
166
|
+
if (pipeIndex >= 0) return {
|
|
167
|
+
nodes: contentChildren(declNode, expression, pipeIndex + 1),
|
|
168
|
+
headerEndIndex: pipeIndex + 1
|
|
169
|
+
};
|
|
170
|
+
const domainNode = findDeclarativeDomain(declNode, expression);
|
|
171
|
+
if (domainNode === null) return null;
|
|
172
|
+
const domainIndex = declNode.children.indexOf(domainNode);
|
|
173
|
+
return {
|
|
174
|
+
nodes: contentChildren(declNode, expression, domainIndex + 1),
|
|
175
|
+
headerEndIndex: domainIndex + 1
|
|
176
|
+
};
|
|
177
|
+
}
|
|
178
|
+
function getImperativeBodySlot(impNode, expression) {
|
|
179
|
+
const pipeIndex = impNode.children.findIndex((child) => isTextToken(child, expression, "|"));
|
|
180
|
+
if (pipeIndex >= 0) return {
|
|
181
|
+
nodes: contentChildren(impNode, expression, pipeIndex + 1),
|
|
182
|
+
headerEndIndex: pipeIndex + 1
|
|
183
|
+
};
|
|
184
|
+
const tupleNode = findImperativeTuple(impNode);
|
|
185
|
+
if (tupleNode === null) return null;
|
|
186
|
+
const tupleIndex = impNode.children.indexOf(tupleNode);
|
|
187
|
+
return {
|
|
188
|
+
nodes: contentChildren(impNode, expression, tupleIndex + 1),
|
|
189
|
+
headerEndIndex: tupleIndex + 1
|
|
190
|
+
};
|
|
191
|
+
}
|
|
192
|
+
function getRecursionBodySlot(recNode, expression) {
|
|
193
|
+
const initNode = findRecursionInit(recNode, expression);
|
|
194
|
+
if (initNode === null) return null;
|
|
195
|
+
const initIndex = recNode.children.indexOf(initNode);
|
|
196
|
+
const afterInit = contentChildren(recNode, expression, initIndex + 1);
|
|
197
|
+
const lastPipeIndex = findLastPipeIndex(afterInit, expression);
|
|
198
|
+
if (lastPipeIndex < 0) return {
|
|
199
|
+
nodes: afterInit,
|
|
200
|
+
headerEndIndex: initIndex + 1
|
|
201
|
+
};
|
|
202
|
+
return {
|
|
203
|
+
nodes: afterInit.slice(lastPipeIndex + 1),
|
|
204
|
+
headerEndIndex: recNode.children.indexOf(afterInit[lastPipeIndex]) + 1
|
|
205
|
+
};
|
|
206
|
+
}
|
|
207
|
+
function contentChildren(generatorNode, expression, fromIndex) {
|
|
208
|
+
return generatorNode.children.slice(fromIndex).filter((child) => !isClosingBrace(child, expression));
|
|
209
|
+
}
|
|
210
|
+
function findDeclarativeDomain(declNode, expression) {
|
|
211
|
+
const inIndex = declNode.children.findIndex((child) => isTextToken(child, expression, "∈"));
|
|
212
|
+
if (inIndex < 0) return null;
|
|
213
|
+
for (let index = inIndex + 1; index < declNode.children.length; index++) {
|
|
214
|
+
const child = declNode.children[index];
|
|
215
|
+
if (isTextToken(child, expression, "|") || isClosingBrace(child, expression)) break;
|
|
216
|
+
if (child.typeID === 38) return child;
|
|
217
|
+
}
|
|
218
|
+
return null;
|
|
219
|
+
}
|
|
220
|
+
function findImperativeTuple(impNode) {
|
|
221
|
+
for (const child of impNode.children) if (child.typeID === 38) return child;
|
|
222
|
+
return null;
|
|
223
|
+
}
|
|
224
|
+
function findLastPipeIndex(nodes, expression) {
|
|
225
|
+
for (let index = nodes.length - 1; index >= 0; index--) if (isTextToken(nodes[index], expression, "|")) return index;
|
|
226
|
+
return -1;
|
|
227
|
+
}
|
|
228
|
+
function generatorHeaderIsValid(generatorNode, expression, headerEndIndex) {
|
|
229
|
+
for (let index = 0; index < headerEndIndex; index++) {
|
|
230
|
+
const child = generatorNode.children[index];
|
|
231
|
+
if (child.typeID === TokenID.ERROR) return false;
|
|
232
|
+
if (isStructuralGeneratorToken(child, expression)) continue;
|
|
233
|
+
if (subtreeHasError(child)) return false;
|
|
234
|
+
}
|
|
235
|
+
return true;
|
|
236
|
+
}
|
|
237
|
+
function isIncompleteDeclarativeBody(nodes) {
|
|
238
|
+
return isEmptyOrErrorOnlyBody(nodes);
|
|
239
|
+
}
|
|
240
|
+
function isIncompleteImperativeBody(slot, expression) {
|
|
241
|
+
if (slot.nodes.length === 0) return true;
|
|
242
|
+
if (slot.nodes.length === 1 && slot.nodes[0].typeID === 65) return isImpBlocksIncomplete(slot.nodes[0], expression);
|
|
243
|
+
return isEmptyOrErrorOnlyBody(slot.nodes);
|
|
244
|
+
}
|
|
245
|
+
function isIncompleteRecursionBody(nodes) {
|
|
246
|
+
return isEmptyOrErrorOnlyBody(nodes);
|
|
247
|
+
}
|
|
248
|
+
function isEmptyOrErrorOnlyBody(nodes) {
|
|
249
|
+
if (nodes.length === 0) return true;
|
|
250
|
+
if (nodes.length === 1) return isErrorPlaceholder(nodes[0]);
|
|
251
|
+
return false;
|
|
252
|
+
}
|
|
253
|
+
function isImpBlocksIncomplete(blocksNode, expression) {
|
|
254
|
+
if (blocksNode.children.length === 0) return true;
|
|
255
|
+
const lastChild = blocksNode.children[blocksNode.children.length - 1];
|
|
256
|
+
if (lastChild.typeID === TokenID.ERROR) return true;
|
|
257
|
+
if (isTextToken(lastChild, expression, ";")) return true;
|
|
258
|
+
if (lastChild.typeID === 38 && isErrorPlaceholder(lastChild)) return true;
|
|
259
|
+
return false;
|
|
260
|
+
}
|
|
261
|
+
function isErrorInBodySlot(errorNode, slot, generatorNode) {
|
|
262
|
+
if (slot.nodes.length === 0) {
|
|
263
|
+
const bodyStart = generatorNode.children[slot.headerEndIndex]?.from ?? generatorNode.from;
|
|
264
|
+
return errorNode.from >= bodyStart;
|
|
265
|
+
}
|
|
266
|
+
for (const bodyNode of slot.nodes) if (errorNode === bodyNode || isDescendantOf(bodyNode, errorNode)) {
|
|
267
|
+
if (isErrorPlaceholder(bodyNode)) return true;
|
|
268
|
+
if (bodyNode.typeID === 65 && isImpBlocksTailError(bodyNode, errorNode)) return true;
|
|
269
|
+
}
|
|
270
|
+
return false;
|
|
271
|
+
}
|
|
272
|
+
function isImpBlocksTailError(blocksNode, errorNode) {
|
|
273
|
+
const lastChild = blocksNode.children[blocksNode.children.length - 1];
|
|
274
|
+
return lastChild.typeID === TokenID.ERROR && (errorNode === lastChild || isDescendantOf(lastChild, errorNode));
|
|
275
|
+
}
|
|
276
|
+
function findRecursionInit(recursionNode, expression) {
|
|
277
|
+
const assignIndex = recursionNode.children.findIndex((child) => isTextToken(child, expression, ":="));
|
|
278
|
+
if (assignIndex < 0) return null;
|
|
279
|
+
for (let index = assignIndex + 1; index < recursionNode.children.length; index++) {
|
|
280
|
+
const child = recursionNode.children[index];
|
|
281
|
+
if (child.typeID === TokenID.ERROR) break;
|
|
282
|
+
if (isTextToken(child, expression, "|") || isClosingBrace(child, expression)) continue;
|
|
283
|
+
return child;
|
|
284
|
+
}
|
|
285
|
+
return null;
|
|
286
|
+
}
|
|
287
|
+
function isClosingBrace(node, expression) {
|
|
288
|
+
return node.typeID !== TokenID.ERROR && isTextToken(node, expression, "}");
|
|
289
|
+
}
|
|
290
|
+
function isStructuralGeneratorToken(node, expression) {
|
|
291
|
+
const text = expression.slice(node.from, node.to);
|
|
292
|
+
return text === "{" || text === "}" || text === "|" || text === "∈" || text === ":=" || text === ";";
|
|
293
|
+
}
|
|
294
|
+
//#endregion
|
|
295
|
+
//#region src/rslang/parser/syntax-errors/trailing-slot.ts
|
|
296
|
+
function detectTrailingErrorSlot(node, expression, container, options) {
|
|
297
|
+
if (container === null) return null;
|
|
298
|
+
const bodyChild = container.children[container.children.length - 1];
|
|
299
|
+
if (bodyChild?.typeID !== TokenID.ERROR || node !== bodyChild) return null;
|
|
300
|
+
if (expression.slice(container.to).trim().length > 0) return null;
|
|
301
|
+
if (options.headerValid !== void 0 && !options.headerValid(container)) return null;
|
|
302
|
+
const code = typeof options.code === "function" ? options.code(container, bodyChild) : options.code;
|
|
303
|
+
if (code === null) return null;
|
|
304
|
+
return errorAt(bodyChild, code);
|
|
305
|
+
}
|
|
306
|
+
//#endregion
|
|
307
|
+
//#region src/rslang/parser/syntax-errors/incomplete-formal.ts
|
|
308
|
+
const classifyIncompleteFormal = (node, expression, context) => detectIncompleteFunctionDecl(node, expression, context.expected) ?? detectIncompleteQuantifier(node, expression);
|
|
309
|
+
function detectIncompleteFunctionDecl(node, expression, expected) {
|
|
310
|
+
return detectTrailingErrorSlot(node, expression, findAncestor(node, isFunctionDeclNode), {
|
|
311
|
+
headerValid: (container) => {
|
|
312
|
+
const argsNode = container.children.find((child) => child.typeID === 77 || child.typeID === TokenID.NT_ARGUMENTS);
|
|
313
|
+
return argsNode !== void 0 && !subtreeHasError(argsNode);
|
|
314
|
+
},
|
|
315
|
+
code: () => incompleteFormalExpressionCode(expected)
|
|
316
|
+
});
|
|
317
|
+
}
|
|
318
|
+
function incompleteFormalExpressionCode(expected) {
|
|
319
|
+
switch (expected) {
|
|
320
|
+
case TypeClass.function: return RSErrorCode.expectedExpressionBody;
|
|
321
|
+
case TypeClass.predicate:
|
|
322
|
+
case TypeClass.logic: return RSErrorCode.expectedLogicBody;
|
|
323
|
+
default: return RSErrorCode.expectedFunctionBody;
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
function detectIncompleteQuantifier(node, expression) {
|
|
327
|
+
return detectTrailingErrorSlot(node, expression, findAncestor(node, isQuantifierNode), {
|
|
328
|
+
headerValid: (container) => {
|
|
329
|
+
const varPack = container.children.find((child) => child.typeID === 31 || child.typeID === TokenID.NT_ENUM_DECL);
|
|
330
|
+
return varPack !== void 0 && !subtreeHasError(varPack);
|
|
331
|
+
},
|
|
332
|
+
code: (container, bodyChild) => {
|
|
333
|
+
const inIndex = container.children.findIndex((child) => expression.slice(child.from, child.to) === "∈");
|
|
334
|
+
if (inIndex < 0) return RSErrorCode.expectedQuantifierDomain;
|
|
335
|
+
const domainChild = container.children[inIndex + 1];
|
|
336
|
+
if (domainChild === void 0 || domainChild === bodyChild || subtreeHasError(domainChild)) return RSErrorCode.expectedQuantifierDomain;
|
|
337
|
+
return RSErrorCode.expectedQuantifierBody;
|
|
338
|
+
}
|
|
339
|
+
});
|
|
340
|
+
}
|
|
341
|
+
function isIncompleteGeneratorBodyCode(code) {
|
|
342
|
+
return code === RSErrorCode.expectedDeclarativeBody || code === RSErrorCode.expectedImperativeBody || code === RSErrorCode.expectedRecursiveBody;
|
|
343
|
+
}
|
|
344
|
+
//#endregion
|
|
345
|
+
//#region src/rslang/parser/syntax-errors/incomplete-operands.ts
|
|
346
|
+
const classifyIncompleteOperands = (node, expression) => detectIncompleteUnaryOperand(node, expression) ?? detectIncompleteBinaryOperand(node, expression) ?? detectIncompleteBooleanOperand(node, expression);
|
|
347
|
+
const classifyIncompleteArgument = (node, expression) => detectIncompleteArgument(node, expression);
|
|
348
|
+
function detectIncompleteUnaryOperand(node, expression) {
|
|
349
|
+
const unaryNode = findAncestor(node, (target) => target.typeID === 24);
|
|
350
|
+
if (unaryNode === null) return null;
|
|
351
|
+
const operandChild = unaryNode.children[unaryNode.children.length - 1];
|
|
352
|
+
if (operandChild?.typeID !== TokenID.ERROR || node !== operandChild) return null;
|
|
353
|
+
if (expression.slice(unaryNode.to).trim().length > 0) return null;
|
|
354
|
+
const operatorChild = unaryNode.children[0];
|
|
355
|
+
if (operatorChild === void 0 || operatorChild === operandChild) return null;
|
|
356
|
+
if (!isTextToken(operatorChild, expression, "¬")) return null;
|
|
357
|
+
return errorAt(operandChild, RSErrorCode.expectedUnaryOperand);
|
|
358
|
+
}
|
|
359
|
+
function detectIncompleteBinaryOperand(node, expression) {
|
|
360
|
+
const binaryNode = findAncestor(node, (target) => target.typeID === 33 || target.typeID === 3 || target.typeID === 44);
|
|
361
|
+
if (binaryNode === null) return null;
|
|
362
|
+
const operandChild = binaryNode.children[binaryNode.children.length - 1];
|
|
363
|
+
if (operandChild?.typeID !== TokenID.ERROR || node !== operandChild) return null;
|
|
364
|
+
if (expression.slice(binaryNode.to).trim().length > 0) return null;
|
|
365
|
+
if (binaryNode.children.length < 2) return null;
|
|
366
|
+
const leftChild = binaryNode.children[0];
|
|
367
|
+
if (leftChild.typeID === TokenID.ERROR || subtreeHasError(leftChild)) return null;
|
|
368
|
+
return errorAt(operandChild, RSErrorCode.expectedRightOperand);
|
|
369
|
+
}
|
|
370
|
+
function detectIncompleteBooleanOperand(node, expression) {
|
|
371
|
+
const booleanNode = findAncestor(node, (target) => target.typeID === 56);
|
|
372
|
+
if (booleanNode === null) return null;
|
|
373
|
+
const operandChild = findTrailingErrorOperand(booleanNode, expression);
|
|
374
|
+
if (operandChild === null || node !== operandChild) return null;
|
|
375
|
+
if (!hasOnlyClosingSuffix(expression, booleanNode.to)) return null;
|
|
376
|
+
return errorAt(operandChild, RSErrorCode.expectedRightOperand);
|
|
377
|
+
}
|
|
378
|
+
function detectIncompleteArgument(node, expression) {
|
|
379
|
+
const enumNode = findAncestor(node, (target) => target.typeID === 9);
|
|
380
|
+
if (enumNode === null || !isLegitimateArgumentEnum(enumNode, expression)) return null;
|
|
381
|
+
const errorChild = findEnumArgumentError(enumNode, expression);
|
|
382
|
+
if (errorChild === null || node !== errorChild || !hasOnlyClosingSuffix(expression, enumNode.to)) return null;
|
|
383
|
+
return errorAt(errorChild, RSErrorCode.expectedArgument);
|
|
384
|
+
}
|
|
385
|
+
function findTrailingErrorOperand(container, expression) {
|
|
386
|
+
for (let index = container.children.length - 1; index >= 0; index--) {
|
|
387
|
+
const child = container.children[index];
|
|
388
|
+
if (isOperandStructuralToken(child, expression)) continue;
|
|
389
|
+
return getErrorOperandNode(child);
|
|
390
|
+
}
|
|
391
|
+
return null;
|
|
392
|
+
}
|
|
393
|
+
function findEnumArgumentError(enumNode, expression) {
|
|
394
|
+
for (const child of enumNode.children) {
|
|
395
|
+
if (isTextToken(child, expression, ",")) continue;
|
|
396
|
+
const errorNode = getErrorOperandNode(child);
|
|
397
|
+
if (errorNode !== null) return errorNode;
|
|
398
|
+
}
|
|
399
|
+
return null;
|
|
400
|
+
}
|
|
401
|
+
function isOperandStructuralToken(node, expression) {
|
|
402
|
+
const text = sliceText(node, expression);
|
|
403
|
+
return text === "(" || text === ")" || text === "ℬ";
|
|
404
|
+
}
|
|
405
|
+
function isLegitimateArgumentEnum(enumNode, expression) {
|
|
406
|
+
return findCallHostForBracketEnum(enumNode, expression) !== null;
|
|
407
|
+
}
|
|
408
|
+
function findCallHostForBracketEnum(enumNode, expression) {
|
|
409
|
+
let current = enumNode.parent;
|
|
410
|
+
while (current !== null) {
|
|
411
|
+
for (const child of current.children) {
|
|
412
|
+
if (!isFunctionNode(child) && !isPredicateNode(child) && !isFilterNode(child)) continue;
|
|
413
|
+
if (isEnumInCallBrackets(enumNode, child, expression)) return child;
|
|
414
|
+
}
|
|
415
|
+
current = current.parent;
|
|
416
|
+
}
|
|
417
|
+
return null;
|
|
418
|
+
}
|
|
419
|
+
function isEnumInCallBrackets(enumNode, nameNode, expression) {
|
|
420
|
+
const rest = expression.slice(nameNode.to);
|
|
421
|
+
const match = /^\s*\[/.exec(rest);
|
|
422
|
+
if (match === null) return false;
|
|
423
|
+
const openPos = nameNode.to + match.index + match[0].indexOf("[");
|
|
424
|
+
const closePos = findMatchingCloseBracket(expression, openPos);
|
|
425
|
+
if (closePos < 0) return false;
|
|
426
|
+
return enumNode.from > openPos && enumNode.from < closePos;
|
|
427
|
+
}
|
|
428
|
+
//#endregion
|
|
429
|
+
//#region src/rslang/parser/syntax-errors/classify.ts
|
|
430
|
+
const PARSE_ERROR_CLASSIFIERS = [
|
|
431
|
+
classifyIncompleteFormal,
|
|
432
|
+
classifyIncompleteGenerator,
|
|
433
|
+
whenNoBracketErrors(classifyIncompleteOperands),
|
|
434
|
+
whenNoBracketErrors(classifyIncompleteArgument),
|
|
435
|
+
classifyFilterParenMismatch,
|
|
436
|
+
classifyGlobalFuncCall
|
|
437
|
+
];
|
|
438
|
+
/** Runs classifiers in priority order for one `ERROR` AST node. */
|
|
439
|
+
function classifyParseError(node, expression, context) {
|
|
440
|
+
for (const classifier of PARSE_ERROR_CLASSIFIERS) {
|
|
441
|
+
const error = classifier(node, expression, context);
|
|
442
|
+
if (error !== null) return error;
|
|
443
|
+
}
|
|
444
|
+
return null;
|
|
445
|
+
}
|
|
446
|
+
/** Default rules when no classifier matched: `expectedLocal` or `unknownSyntax`. */
|
|
447
|
+
function classifyFallbackError(node, ignoreUnknownErrors) {
|
|
448
|
+
const parent = node.parent;
|
|
449
|
+
if (parent === null) return ignoreUnknownErrors ? null : {
|
|
450
|
+
error: {
|
|
451
|
+
code: RSErrorCode.unknownSyntax,
|
|
452
|
+
from: node.from,
|
|
453
|
+
to: node.to
|
|
454
|
+
},
|
|
455
|
+
target: node
|
|
456
|
+
};
|
|
457
|
+
if (parent.typeID === 4) return {
|
|
458
|
+
error: {
|
|
459
|
+
code: RSErrorCode.expectedLocal,
|
|
460
|
+
from: parent.from,
|
|
461
|
+
to: parent.to
|
|
462
|
+
},
|
|
463
|
+
target: parent
|
|
464
|
+
};
|
|
465
|
+
if (ignoreUnknownErrors) return null;
|
|
466
|
+
const target = node.from === node.to ? parent : node;
|
|
467
|
+
return {
|
|
468
|
+
error: {
|
|
469
|
+
code: RSErrorCode.unknownSyntax,
|
|
470
|
+
from: target.from,
|
|
471
|
+
to: target.to
|
|
472
|
+
},
|
|
473
|
+
target
|
|
474
|
+
};
|
|
475
|
+
}
|
|
476
|
+
//#endregion
|
|
477
|
+
//#region src/rslang/parser/syntax-errors/expression-errors.ts
|
|
478
|
+
/**
|
|
479
|
+
* Expression-level diagnostics (no AST required) and error deduplication.
|
|
480
|
+
*/
|
|
481
|
+
const ALLOWED_EXPRESSION_CHARS = buildAllowedExpressionChars();
|
|
482
|
+
function buildAllowedExpressionChars() {
|
|
483
|
+
const chars = /* @__PURE__ */ new Set();
|
|
484
|
+
for (const chunk of [
|
|
485
|
+
"0123456789",
|
|
486
|
+
"abcdefghijklmnopqrstuvwxyz",
|
|
487
|
+
"ABCDEFGHIJKLMNOPQRSTUVWXYZ",
|
|
488
|
+
"_",
|
|
489
|
+
"∅¬∀∃⇔⇒∨&ℬ+-*∪\\∆∩×∈∉⊆⊄⊂<>≥≤≠=,:;|[]{}()"
|
|
490
|
+
]) for (const symbol of chunk) chars.add(symbol);
|
|
491
|
+
for (let codePoint = 945; codePoint <= 969; codePoint++) chars.add(String.fromCodePoint(codePoint));
|
|
492
|
+
return chars;
|
|
493
|
+
}
|
|
494
|
+
function isAllowedExpressionChar(symbol) {
|
|
495
|
+
return /\s/u.test(symbol) || ALLOWED_EXPRESSION_CHARS.has(symbol);
|
|
496
|
+
}
|
|
497
|
+
function extractForbiddenCharacterErrors(expression) {
|
|
498
|
+
const errors = [];
|
|
499
|
+
let pos = 0;
|
|
500
|
+
while (pos < expression.length) {
|
|
501
|
+
const symbol = expression[pos];
|
|
502
|
+
if (isAllowedExpressionChar(symbol)) {
|
|
503
|
+
pos++;
|
|
504
|
+
continue;
|
|
505
|
+
}
|
|
506
|
+
const start = pos;
|
|
507
|
+
while (pos < expression.length && !isAllowedExpressionChar(expression[pos])) pos++;
|
|
508
|
+
errors.push({
|
|
509
|
+
code: RSErrorCode.forbiddenCharacter,
|
|
510
|
+
from: start,
|
|
511
|
+
to: pos,
|
|
512
|
+
params: [expression.slice(start, pos)]
|
|
513
|
+
});
|
|
514
|
+
}
|
|
515
|
+
return errors;
|
|
516
|
+
}
|
|
517
|
+
function extractBracketErrors(expression) {
|
|
518
|
+
const stack = [];
|
|
519
|
+
for (let pos = 0; pos < expression.length; pos++) {
|
|
520
|
+
const symbol = expression[pos];
|
|
521
|
+
if (isOpenBracket(symbol)) {
|
|
522
|
+
if (isDoubleParenthesis(expression, pos)) return {
|
|
523
|
+
code: RSErrorCode.doubleParenthesis,
|
|
524
|
+
from: pos,
|
|
525
|
+
to: pos + 2
|
|
526
|
+
};
|
|
527
|
+
stack.push({
|
|
528
|
+
bracket: symbol,
|
|
529
|
+
index: pos
|
|
530
|
+
});
|
|
531
|
+
continue;
|
|
532
|
+
}
|
|
533
|
+
if (!isCloseBracket(symbol)) continue;
|
|
534
|
+
const expectedOpen = closeToOpen(symbol);
|
|
535
|
+
const top = stack[stack.length - 1];
|
|
536
|
+
if (top === void 0) return {
|
|
537
|
+
code: RSErrorCode.missingOpenBracket,
|
|
538
|
+
from: pos,
|
|
539
|
+
to: pos + 1,
|
|
540
|
+
params: [symbol]
|
|
541
|
+
};
|
|
542
|
+
if (top.bracket !== expectedOpen) return {
|
|
543
|
+
code: RSErrorCode.bracketMismatch,
|
|
544
|
+
from: pos,
|
|
545
|
+
to: pos + 1,
|
|
546
|
+
params: [openToClose(top.bracket), symbol]
|
|
547
|
+
};
|
|
548
|
+
stack.pop();
|
|
549
|
+
}
|
|
550
|
+
if (stack.length > 0) {
|
|
551
|
+
const unclosed = stack[0];
|
|
552
|
+
return {
|
|
553
|
+
code: RSErrorCode.missingCloseBracket,
|
|
554
|
+
from: unclosed.index,
|
|
555
|
+
to: unclosed.index + 1,
|
|
556
|
+
params: [unclosed.bracket]
|
|
557
|
+
};
|
|
558
|
+
}
|
|
559
|
+
return null;
|
|
560
|
+
}
|
|
561
|
+
function deduplicateErrors(errors) {
|
|
562
|
+
const result = [];
|
|
563
|
+
for (const error of errors) {
|
|
564
|
+
if (result.some((existing) => existing.code === error.code && existing.from === error.from && existing.to === error.to)) continue;
|
|
565
|
+
if (error.code !== RSErrorCode.unknownSyntax) {
|
|
566
|
+
for (let index = result.length - 1; index >= 0; index--) if (result[index].code === RSErrorCode.unknownSyntax && rangesOverlap(error, result[index])) result.splice(index, 1);
|
|
567
|
+
}
|
|
568
|
+
result.push(error);
|
|
569
|
+
}
|
|
570
|
+
const specific = result.filter((error) => error.code !== RSErrorCode.unknownSyntax);
|
|
571
|
+
return collapseContiguousUnknownSyntax(result.filter((error) => error.code !== RSErrorCode.unknownSyntax || !specific.some((item) => rangesOverlap(error, item))));
|
|
572
|
+
}
|
|
573
|
+
function collapseContiguousUnknownSyntax(errors) {
|
|
574
|
+
const unknowns = errors.filter((error) => error.code === RSErrorCode.unknownSyntax);
|
|
575
|
+
if (unknowns.length <= 1) return errors;
|
|
576
|
+
const others = errors.filter((error) => error.code !== RSErrorCode.unknownSyntax);
|
|
577
|
+
const first = [...unknowns].sort((left, right) => left.from - right.from)[0];
|
|
578
|
+
return [...others, first];
|
|
579
|
+
}
|
|
580
|
+
function rangesOverlap(a, b) {
|
|
581
|
+
return a.from < b.to && b.from < a.to;
|
|
582
|
+
}
|
|
583
|
+
function isOpenBracket(symbol) {
|
|
584
|
+
return symbol === "(" || symbol === "[" || symbol === "{";
|
|
585
|
+
}
|
|
586
|
+
function isCloseBracket(symbol) {
|
|
587
|
+
return symbol === ")" || symbol === "]" || symbol === "}";
|
|
588
|
+
}
|
|
589
|
+
function openToClose(symbol) {
|
|
590
|
+
switch (symbol) {
|
|
591
|
+
case "(": return ")";
|
|
592
|
+
case "[": return "]";
|
|
593
|
+
case "{": return "}";
|
|
594
|
+
}
|
|
595
|
+
}
|
|
596
|
+
function closeToOpen(symbol) {
|
|
597
|
+
switch (symbol) {
|
|
598
|
+
case ")": return "(";
|
|
599
|
+
case "]": return "[";
|
|
600
|
+
case "}": return "{";
|
|
601
|
+
}
|
|
602
|
+
}
|
|
603
|
+
function isDoubleParenthesis(expression, pos) {
|
|
604
|
+
if (!(expression[pos] === "(" && expression[pos + 1] === "(")) return false;
|
|
605
|
+
const outerClose = findMatchingCloseParen(expression, pos);
|
|
606
|
+
const innerClose = findMatchingCloseParen(expression, pos + 1);
|
|
607
|
+
if (outerClose < 0 || innerClose < 0) return false;
|
|
608
|
+
return outerClose === innerClose + 1;
|
|
609
|
+
}
|
|
610
|
+
function findMatchingCloseParen(expression, openPos) {
|
|
611
|
+
if (expression[openPos] !== "(") return -1;
|
|
612
|
+
const stack = [];
|
|
613
|
+
for (let index = openPos; index < expression.length; index++) {
|
|
614
|
+
const symbol = expression[index];
|
|
615
|
+
if (isOpenBracket(symbol)) {
|
|
616
|
+
stack.push(symbol);
|
|
617
|
+
continue;
|
|
618
|
+
}
|
|
619
|
+
if (!isCloseBracket(symbol)) continue;
|
|
620
|
+
const top = stack.pop();
|
|
621
|
+
if (top === void 0 || top !== closeToOpen(symbol)) return -1;
|
|
622
|
+
if (stack.length === 0) return index;
|
|
623
|
+
}
|
|
624
|
+
return -1;
|
|
625
|
+
}
|
|
626
|
+
//#endregion
|
|
627
|
+
//#region src/rslang/parser/syntax-errors/index.ts
|
|
628
|
+
/**
|
|
629
|
+
* Syntax error extraction — public entry point.
|
|
630
|
+
*
|
|
631
|
+
* Expects the **hybrid** AST produced by `normalizeAST` (see `parser/README.md`).
|
|
632
|
+
* See {@link ./README.md} for pipeline, classifier order, and how to add codes.
|
|
633
|
+
*
|
|
634
|
+
* @module rslang/parser/syntax-errors
|
|
635
|
+
*/
|
|
636
|
+
/**
|
|
637
|
+
* Walks a parsed expression AST and reports syntax errors.
|
|
638
|
+
*
|
|
639
|
+
* @param ast - Root AST from `buildTree(parser.parse(expression))`
|
|
640
|
+
* @param expression - Original source text (positions must match the AST)
|
|
641
|
+
* @param reporter - Called once per deduplicated error
|
|
642
|
+
* @param annotateErrors - When true, also calls `annotateError` on the triggering node
|
|
643
|
+
* @param options - Optional typification hint for incomplete formal expressions
|
|
644
|
+
*/
|
|
645
|
+
function extractSyntaxErrors(ast, expression, reporter, annotateErrors = false, options) {
|
|
646
|
+
const forbiddenErrors = extractForbiddenCharacterErrors(expression);
|
|
647
|
+
if (forbiddenErrors.length > 0) {
|
|
648
|
+
for (const error of forbiddenErrors) {
|
|
649
|
+
reporter(error);
|
|
650
|
+
if (annotateErrors) annotateError(ast, error.code, error.params);
|
|
651
|
+
}
|
|
652
|
+
return;
|
|
653
|
+
}
|
|
654
|
+
const collected = [];
|
|
655
|
+
const annotationTargets = /* @__PURE__ */ new Map();
|
|
656
|
+
const collect = (error, target) => {
|
|
657
|
+
collected.push(error);
|
|
658
|
+
if (target !== void 0) annotationTargets.set(error, target);
|
|
659
|
+
};
|
|
660
|
+
const bracketError = extractBracketErrors(expression);
|
|
661
|
+
const hasBracketErrors = bracketError !== null;
|
|
662
|
+
visitAstDFS(ast, (node) => extractInternal(node, expression, collect, hasBracketErrors, options));
|
|
663
|
+
const hasIncompleteGenerator = collected.some((error) => isIncompleteGeneratorBodyCode(error.code));
|
|
664
|
+
if (bracketError !== null && !hasIncompleteGenerator) collect(bracketError, ast);
|
|
665
|
+
for (const error of deduplicateErrors(collected)) {
|
|
666
|
+
reporter(error);
|
|
667
|
+
if (annotateErrors) {
|
|
668
|
+
const target = annotationTargets.get(error);
|
|
669
|
+
if (target !== void 0) annotateError(target, error.code, error.params);
|
|
670
|
+
}
|
|
671
|
+
}
|
|
672
|
+
}
|
|
673
|
+
function extractInternal(node, expression, collect, hasBracketErrors, options) {
|
|
674
|
+
if (node.typeID !== TokenID.ERROR) return;
|
|
675
|
+
const classified = classifyParseError(node, expression, {
|
|
676
|
+
hasBracketErrors,
|
|
677
|
+
expected: options?.expected
|
|
678
|
+
});
|
|
679
|
+
if (classified !== null) {
|
|
680
|
+
collect(classified, node);
|
|
681
|
+
return;
|
|
682
|
+
}
|
|
683
|
+
const fallback = classifyFallbackError(node, hasBracketErrors);
|
|
684
|
+
if (fallback !== null) collect(fallback.error, fallback.target);
|
|
685
|
+
}
|
|
686
|
+
//#endregion
|
|
687
|
+
export { extractSyntaxErrors as t };
|
|
688
|
+
|
|
689
|
+
//# sourceMappingURL=syntax-errors-pCI6RicF.js.map
|