@rsconcept/domain 1.7.0 → 1.7.2

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.
Files changed (49) hide show
  1. package/dist/{analyzer-CqUJ3FO9.d.ts → analyzer-QxWs73gP.d.ts} +2 -2
  2. package/dist/{ast-annotations-DvB1VcUv.d.ts → ast-annotations-CtF7C9uK.d.ts} +2 -2
  3. package/dist/{calculator-DKxDpTdT.d.ts → calculator-REeX2ZS1.d.ts} +2 -2
  4. package/dist/{error-CLnXbkly.d.ts → error-toW0TUHF.d.ts} +6 -1
  5. package/dist/index.d.ts +4 -4
  6. package/dist/library/rsengine.d.ts +1 -1
  7. package/dist/library/rsform-api.d.ts +1 -1
  8. package/dist/library/rsform.d.ts +1 -1
  9. package/dist/rslang/ast-annotations.d.ts +1 -1
  10. package/dist/rslang/error.d.ts +1 -1
  11. package/dist/rslang/error.js +5 -0
  12. package/dist/rslang/error.js.map +1 -1
  13. package/dist/rslang/eval/calculator.d.ts +1 -1
  14. package/dist/rslang/eval/evaluator.d.ts +1 -1
  15. package/dist/rslang/index.d.ts +4 -4
  16. package/dist/rslang/labels.d.ts +1 -1
  17. package/dist/rslang/labels.js +1 -1
  18. package/dist/rslang/labels.js.map +1 -1
  19. package/dist/rslang/parser/normalize.js +5 -1
  20. package/dist/rslang/parser/normalize.js.map +1 -1
  21. package/dist/rslang/parser/syntax-errors/index.d.ts +22 -0
  22. package/dist/rslang/parser/syntax-errors/index.js +2 -0
  23. package/dist/rslang/semantic/analyzer.d.ts +1 -1
  24. package/dist/rslang/semantic/analyzer.js +1 -1
  25. package/dist/rslang/semantic/type-auditor.d.ts +1 -1
  26. package/dist/rslang/semantic/value-auditor.d.ts +1 -1
  27. package/dist/{rslang/parser/syntax-errors.js → syntax-errors-B6c2vsan.js} +421 -233
  28. package/dist/syntax-errors-B6c2vsan.js.map +1 -0
  29. package/package.json +1 -1
  30. package/src/rslang/error.ts +5 -0
  31. package/src/rslang/labels.ts +1 -1
  32. package/src/rslang/parser/README.md +73 -0
  33. package/src/rslang/parser/normalize.ts +5 -1
  34. package/src/rslang/parser/parser.test.ts +53 -0
  35. package/src/rslang/parser/syntax-errors/README.md +61 -0
  36. package/src/rslang/parser/syntax-errors/ast-utils.ts +112 -0
  37. package/src/rslang/parser/syntax-errors/classify.ts +70 -0
  38. package/src/rslang/parser/syntax-errors/context.ts +25 -0
  39. package/src/rslang/parser/syntax-errors/error-builders.ts +28 -0
  40. package/src/rslang/parser/syntax-errors/expression-errors.ts +255 -0
  41. package/src/rslang/parser/syntax-errors/function-calls.ts +76 -0
  42. package/src/rslang/parser/syntax-errors/generators.ts +315 -0
  43. package/src/rslang/parser/syntax-errors/incomplete-formal.ts +68 -0
  44. package/src/rslang/parser/syntax-errors/incomplete-operands.ts +184 -0
  45. package/src/rslang/parser/syntax-errors/index.ts +102 -0
  46. package/src/rslang/parser/syntax-errors/trailing-slot.ts +45 -0
  47. package/dist/rslang/parser/syntax-errors.d.ts +0 -14
  48. package/dist/rslang/parser/syntax-errors.js.map +0 -1
  49. package/src/rslang/parser/syntax-errors.ts +0 -929
@@ -1,137 +1,162 @@
1
- import { annotateError } from "../ast-annotations.js";
2
- import { RSErrorCode } from "../error.js";
3
- import { visitAstDFS } from "../../parsing/ast.js";
4
- import "../../parsing/index.js";
5
- import { TokenID } from "./token.js";
6
- import { TypeClass } from "../semantic/typification.js";
7
- import "./parser.terms.js";
8
- //#region src/rslang/parser/syntax-errors.ts
9
- /**
10
- * Module: Syntactic errors reporting.
11
- */
12
- /** Extracts syntax errors from an AST. */
13
- function extractSyntaxErrors(ast, expression, reporter, annotateErrors = false, options) {
14
- const forbiddenErrors = extractForbiddenCharacterErrors(expression);
15
- if (forbiddenErrors.length > 0) {
16
- for (const error of forbiddenErrors) {
17
- reporter(error);
18
- if (annotateErrors) annotateError(ast, error.code, error.params);
19
- }
20
- return;
21
- }
22
- const collected = [];
23
- const annotationTargets = /* @__PURE__ */ new Map();
24
- const collect = (error, target) => {
25
- collected.push(error);
26
- if (target !== void 0) annotationTargets.set(error, target);
27
- };
28
- const bracketError = extractBracketErrors(expression);
29
- const hasBracketErrors = bracketError !== null;
30
- visitAstDFS(ast, (node) => extractInternal(node, expression, collect, hasBracketErrors, options));
31
- const hasIncompleteGenerator = collected.some((error) => isIncompleteGeneratorBodyCode(error.code));
32
- if (bracketError !== null && !hasIncompleteGenerator) collect(bracketError, ast);
33
- for (const error of deduplicateErrors(collected)) {
34
- reporter(error);
35
- if (annotateErrors) {
36
- const target = annotationTargets.get(error);
37
- if (target !== void 0) annotateError(target, error.code, error.params);
38
- }
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;
39
20
  }
21
+ return null;
40
22
  }
41
- function extractInternal(node, expression, collect, ignoreUnknownErrors, options) {
42
- if (node.typeID !== TokenID.ERROR) return;
43
- function emit(target, code, params) {
44
- collect({
45
- code,
46
- from: target.from,
47
- to: target.to,
48
- params
49
- }, target);
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;
50
28
  }
51
- const classified = classifyParseError(node, expression, options);
52
- if (classified !== null) {
53
- collect(classified, node);
54
- return;
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;
55
36
  }
56
- const parent = node.parent;
57
- if (parent === null) {
58
- if (!ignoreUnknownErrors) return emit(node, RSErrorCode.unknownSyntax);
59
- return;
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
+ }
60
68
  }
61
- if (parent.typeID === 4) return emit(parent, RSErrorCode.expectedLocal);
62
- if (!ignoreUnknownErrors) emit(node.from === node.to ? parent : node, RSErrorCode.unknownSyntax);
63
- }
64
- function classifyParseError(node, expression, options) {
65
- const incompleteFunctionDecl = detectIncompleteFunctionDecl(node, expression, options?.expected);
66
- if (incompleteFunctionDecl !== null) return incompleteFunctionDecl;
67
- const incompleteQuantifier = detectIncompleteQuantifier(node, expression);
68
- if (incompleteQuantifier !== null) return incompleteQuantifier;
69
- const incompleteGenerator = detectIncompleteGenerator(node, expression);
70
- if (incompleteGenerator !== null) return incompleteGenerator;
71
- const filterParen = detectFilterParenMismatch(node, expression);
72
- if (filterParen !== null) return filterParen;
73
- return detectGlobalFuncWithoutArgs(node, expression);
69
+ return -1;
74
70
  }
75
- function detectIncompleteFunctionDecl(node, expression, expected) {
76
- const funcDecl = findAncestor(node, isFunctionDeclNode);
77
- if (funcDecl === null) return null;
78
- const bodyChild = funcDecl.children[funcDecl.children.length - 1];
79
- if (bodyChild?.typeID !== TokenID.ERROR || node !== bodyChild) return null;
80
- const argsNode = funcDecl.children.find((child) => child.typeID === 77 || child.typeID === TokenID.NT_ARGUMENTS);
81
- if (argsNode === void 0 || subtreeHasError(argsNode)) return null;
82
- if (expression.slice(funcDecl.to).trim().length > 0) return null;
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;
83
86
  return {
84
- code: incompleteFormalExpressionCode(expected),
85
- from: bodyChild.from,
86
- to: bodyChild.to
87
+ code: RSErrorCode.invalidFilterSyntax,
88
+ from: filterNode.from,
89
+ to: filterNode.to + 1
87
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;
88
118
  }
89
- function incompleteFormalExpressionCode(expected) {
90
- switch (expected) {
91
- case TypeClass.function: return RSErrorCode.expectedExpressionBody;
92
- case TypeClass.predicate:
93
- case TypeClass.logic: return RSErrorCode.expectedLogicBody;
94
- default: return RSErrorCode.expectedFunctionBody;
95
- }
96
- }
97
- function detectIncompleteQuantifier(node, expression) {
98
- const quantNode = findAncestor(node, isQuantifierNode);
99
- if (quantNode === null) return null;
100
- const bodyChild = quantNode.children[quantNode.children.length - 1];
101
- if (bodyChild?.typeID !== TokenID.ERROR || node !== bodyChild) return null;
102
- const headerChildren = quantNode.children.slice(0, -1);
103
- if (headerChildren.length < 3) return null;
104
- const varPack = headerChildren.find((child) => child.typeID === 31 || child.typeID === TokenID.NT_ENUM_DECL);
105
- if (varPack === void 0 || subtreeHasError(varPack)) return null;
106
- const domainChild = headerChildren[headerChildren.length - 1];
107
- if (domainChild === varPack || subtreeHasError(domainChild)) return null;
108
- if (expression.slice(quantNode.to).trim().length > 0) return null;
119
+ //#endregion
120
+ //#region src/rslang/parser/syntax-errors/error-builders.ts
121
+ function errorAt(node, code, params) {
109
122
  return {
110
- code: RSErrorCode.expectedQuantifierBody,
111
- from: bodyChild.from,
112
- to: bodyChild.to
123
+ code,
124
+ from: node.from,
125
+ to: node.to,
126
+ params
113
127
  };
114
128
  }
115
- function detectIncompleteGenerator(node, expression) {
116
- return detectIncompleteDeclarative(node, expression) ?? detectIncompleteImperative(node, expression) ?? detectIncompleteRecursion(node, expression);
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 isIncompleteGeneratorBodyCode(code) {
143
+ return code === RSErrorCode.expectedDeclarativeBody || code === RSErrorCode.expectedImperativeBody || code === RSErrorCode.expectedRecursiveBody;
117
144
  }
118
145
  function detectIncompleteDeclarative(node, expression) {
119
- return detectIncompleteGeneratorBody(node, expression, findAncestor(node, (target) => target.typeID === 60 || target.typeID === TokenID.NT_DECLARATIVE_EXPR), RSErrorCode.expectedDeclarativeBody, (slot) => isIncompleteDeclarativeBody(slot));
146
+ return detectIncompleteGeneratorBody(node, expression, findAncestor(node, (target) => target.typeID === 60 || target.typeID === TokenID.NT_DECLARATIVE_EXPR), RSErrorCode.expectedDeclarativeBody, (slot) => isIncompleteDeclarativeBody(slot.nodes));
120
147
  }
121
148
  function detectIncompleteImperative(node, expression) {
122
149
  return detectIncompleteGeneratorBody(node, expression, findAncestor(node, (target) => target.typeID === 64 || target.typeID === TokenID.NT_IMPERATIVE_EXPR), RSErrorCode.expectedImperativeBody, (slot) => isIncompleteImperativeBody(slot, expression));
123
150
  }
124
151
  function detectIncompleteRecursion(node, expression) {
125
- return detectIncompleteGeneratorBody(node, expression, findAncestor(node, (target) => target.typeID === 68), RSErrorCode.expectedRecursiveBody, (slot) => isIncompleteRecursionBody(slot));
152
+ return detectIncompleteGeneratorBody(node, expression, findAncestor(node, (target) => target.typeID === 68), RSErrorCode.expectedRecursiveBody, (slot) => isIncompleteRecursionBody(slot.nodes));
126
153
  }
127
154
  function detectIncompleteGeneratorBody(node, expression, generatorNode, code, isIncomplete) {
128
- if (generatorNode === null) return null;
129
- if (expression.slice(generatorNode.to).trim().length > 0) return null;
155
+ if (generatorNode === null || expression.slice(generatorNode.to).trim().length > 0) return null;
130
156
  const slot = getGeneratorBodySlot(generatorNode, expression);
131
- if (slot === null || !isIncomplete(slot)) return null;
132
- if (!generatorHeaderIsValid(generatorNode, expression, slot.headerEndIndex)) return null;
157
+ if (slot === null || !isIncomplete(slot) || !generatorHeaderIsValid(generatorNode, expression, slot.headerEndIndex)) return null;
133
158
  if (!isErrorInBodySlot(node, slot, generatorNode)) return null;
134
- return incompleteGeneratorError(code, node);
159
+ return errorAt(node, code);
135
160
  }
136
161
  function getGeneratorBodySlot(generatorNode, expression) {
137
162
  if (generatorNode.typeID === 60 || generatorNode.typeID === TokenID.NT_DECLARATIVE_EXPR) return getDeclarativeBodySlot(generatorNode, expression);
@@ -140,7 +165,7 @@ function getGeneratorBodySlot(generatorNode, expression) {
140
165
  return null;
141
166
  }
142
167
  function getDeclarativeBodySlot(declNode, expression) {
143
- const pipeIndex = declNode.children.findIndex((child) => isPipeToken(child, expression));
168
+ const pipeIndex = declNode.children.findIndex((child) => isTextToken(child, expression, "|"));
144
169
  if (pipeIndex >= 0) return {
145
170
  nodes: contentChildren(declNode, expression, pipeIndex + 1),
146
171
  headerEndIndex: pipeIndex + 1
@@ -154,7 +179,7 @@ function getDeclarativeBodySlot(declNode, expression) {
154
179
  };
155
180
  }
156
181
  function getImperativeBodySlot(impNode, expression) {
157
- const pipeIndex = impNode.children.findIndex((child) => isPipeToken(child, expression));
182
+ const pipeIndex = impNode.children.findIndex((child) => isTextToken(child, expression, "|"));
158
183
  if (pipeIndex >= 0) return {
159
184
  nodes: contentChildren(impNode, expression, pipeIndex + 1),
160
185
  headerEndIndex: pipeIndex + 1
@@ -186,11 +211,11 @@ function contentChildren(generatorNode, expression, fromIndex) {
186
211
  return generatorNode.children.slice(fromIndex).filter((child) => !isClosingBrace(child, expression));
187
212
  }
188
213
  function findDeclarativeDomain(declNode, expression) {
189
- const inIndex = declNode.children.findIndex((child) => expression.slice(child.from, child.to) === "∈");
214
+ const inIndex = declNode.children.findIndex((child) => isTextToken(child, expression, "∈"));
190
215
  if (inIndex < 0) return null;
191
216
  for (let index = inIndex + 1; index < declNode.children.length; index++) {
192
217
  const child = declNode.children[index];
193
- if (isPipeToken(child, expression) || isClosingBrace(child, expression)) break;
218
+ if (isTextToken(child, expression, "|") || isClosingBrace(child, expression)) break;
194
219
  if (child.typeID === 38) return child;
195
220
  }
196
221
  return null;
@@ -200,7 +225,7 @@ function findImperativeTuple(impNode) {
200
225
  return null;
201
226
  }
202
227
  function findLastPipeIndex(nodes, expression) {
203
- for (let index = nodes.length - 1; index >= 0; index--) if (isPipeToken(nodes[index], expression)) return index;
228
+ for (let index = nodes.length - 1; index >= 0; index--) if (isTextToken(nodes[index], expression, "|")) return index;
204
229
  return -1;
205
230
  }
206
231
  function generatorHeaderIsValid(generatorNode, expression, headerEndIndex) {
@@ -212,16 +237,16 @@ function generatorHeaderIsValid(generatorNode, expression, headerEndIndex) {
212
237
  }
213
238
  return true;
214
239
  }
215
- function isIncompleteDeclarativeBody(slot) {
216
- return isEmptyOrErrorOnlyBody(slot.nodes);
240
+ function isIncompleteDeclarativeBody(nodes) {
241
+ return isEmptyOrErrorOnlyBody(nodes);
217
242
  }
218
243
  function isIncompleteImperativeBody(slot, expression) {
219
244
  if (slot.nodes.length === 0) return true;
220
245
  if (slot.nodes.length === 1 && slot.nodes[0].typeID === 65) return isImpBlocksIncomplete(slot.nodes[0], expression);
221
246
  return isEmptyOrErrorOnlyBody(slot.nodes);
222
247
  }
223
- function isIncompleteRecursionBody(slot) {
224
- return isEmptyOrErrorOnlyBody(slot.nodes);
248
+ function isIncompleteRecursionBody(nodes) {
249
+ return isEmptyOrErrorOnlyBody(nodes);
225
250
  }
226
251
  function isEmptyOrErrorOnlyBody(nodes) {
227
252
  if (nodes.length === 0) return true;
@@ -232,7 +257,7 @@ function isImpBlocksIncomplete(blocksNode, expression) {
232
257
  if (blocksNode.children.length === 0) return true;
233
258
  const lastChild = blocksNode.children[blocksNode.children.length - 1];
234
259
  if (lastChild.typeID === TokenID.ERROR) return true;
235
- if (isSemicolonToken(lastChild, expression)) return true;
260
+ if (isTextToken(lastChild, expression, ";")) return true;
236
261
  if (lastChild.typeID === 38 && isErrorPlaceholder(lastChild)) return true;
237
262
  return false;
238
263
  }
@@ -251,149 +276,208 @@ function isImpBlocksTailError(blocksNode, errorNode) {
251
276
  const lastChild = blocksNode.children[blocksNode.children.length - 1];
252
277
  return lastChild.typeID === TokenID.ERROR && (errorNode === lastChild || isDescendantOf(lastChild, errorNode));
253
278
  }
254
- function isErrorPlaceholder(node) {
255
- if (node.typeID === TokenID.ERROR) return true;
256
- if (node.children.length === 0) return false;
257
- return node.children.every((child) => isErrorPlaceholder(child));
258
- }
259
- function isDescendantOf(ancestor, node) {
260
- let current = node.parent;
261
- while (current !== null) {
262
- if (current === ancestor) return true;
263
- current = current.parent;
264
- }
265
- return false;
266
- }
267
279
  function findRecursionInit(recursionNode, expression) {
268
- const assignIndex = recursionNode.children.findIndex((child) => expression.slice(child.from, child.to) === ":=");
280
+ const assignIndex = recursionNode.children.findIndex((child) => isTextToken(child, expression, ":="));
269
281
  if (assignIndex < 0) return null;
270
282
  for (let index = assignIndex + 1; index < recursionNode.children.length; index++) {
271
283
  const child = recursionNode.children[index];
272
284
  if (child.typeID === TokenID.ERROR) break;
273
- if (isPipeToken(child, expression) || isClosingBrace(child, expression)) continue;
285
+ if (isTextToken(child, expression, "|") || isClosingBrace(child, expression)) continue;
274
286
  return child;
275
287
  }
276
288
  return null;
277
289
  }
278
- function incompleteGeneratorError(code, errorNode) {
279
- return {
280
- code,
281
- from: errorNode.from,
282
- to: errorNode.to
283
- };
284
- }
285
- function isIncompleteGeneratorBodyCode(code) {
286
- return code === RSErrorCode.expectedDeclarativeBody || code === RSErrorCode.expectedImperativeBody || code === RSErrorCode.expectedRecursiveBody;
287
- }
288
- function isPipeToken(node, expression) {
289
- return expression.slice(node.from, node.to) === "|";
290
- }
291
- function isSemicolonToken(node, expression) {
292
- return expression.slice(node.from, node.to) === ";";
293
- }
294
290
  function isClosingBrace(node, expression) {
295
- return node.typeID !== TokenID.ERROR && expression.slice(node.from, node.to) === "}";
291
+ return node.typeID !== TokenID.ERROR && isTextToken(node, expression, "}");
296
292
  }
297
293
  function isStructuralGeneratorToken(node, expression) {
298
294
  const text = expression.slice(node.from, node.to);
299
295
  return text === "{" || text === "}" || text === "|" || text === "∈" || text === ":=" || text === ";";
300
296
  }
301
- function subtreeHasError(node) {
302
- if (node.typeID === TokenID.ERROR) return true;
303
- return node.children.some(subtreeHasError);
304
- }
305
- function detectFilterParenMismatch(node, expression) {
306
- const filterNode = findAncestor(node, isFilterNode) ?? findRelatedNode(node, isFilterNode);
307
- if (filterNode === null) return null;
308
- if (expression[filterNode.to] === "(") return {
309
- code: RSErrorCode.invalidFilterSyntax,
310
- from: filterNode.from,
311
- to: filterNode.to + 1
312
- };
313
- return null;
297
+ //#endregion
298
+ //#region src/rslang/parser/syntax-errors/trailing-slot.ts
299
+ function detectTrailingErrorSlot(node, expression, container, options) {
300
+ if (container === null) return null;
301
+ const bodyChild = container.children[container.children.length - 1];
302
+ if (bodyChild?.typeID !== TokenID.ERROR || node !== bodyChild) return null;
303
+ if (expression.slice(container.to).trim().length > 0) return null;
304
+ if (options.headerValid !== void 0 && !options.headerValid(container)) return null;
305
+ const code = typeof options.code === "function" ? options.code(container, bodyChild) : options.code;
306
+ if (code === null) return null;
307
+ return errorAt(bodyChild, code);
314
308
  }
315
- function detectGlobalFuncWithoutArgs(node, expression) {
316
- const funcNode = findAncestor(node, (n) => isFunctionNode(n) || isPredicateNode(n)) ?? findRelatedNode(node, (n) => isFunctionNode(n) || isPredicateNode(n));
317
- if (funcNode === null) return null;
318
- return classifyFuncWithoutArgs(funcNode, expression);
309
+ //#endregion
310
+ //#region src/rslang/parser/syntax-errors/incomplete-formal.ts
311
+ const classifyIncompleteFormal = (node, expression, context) => detectIncompleteFunctionDecl(node, expression, context.expected) ?? detectIncompleteQuantifier(node, expression);
312
+ function detectIncompleteFunctionDecl(node, expression, expected) {
313
+ return detectTrailingErrorSlot(node, expression, findAncestor(node, isFunctionDeclNode), {
314
+ headerValid: (container) => {
315
+ const argsNode = container.children.find((child) => child.typeID === 77 || child.typeID === TokenID.NT_ARGUMENTS);
316
+ return argsNode !== void 0 && !subtreeHasError(argsNode);
317
+ },
318
+ code: () => incompleteFormalExpressionCode(expected)
319
+ });
319
320
  }
320
- function findRelatedNode(node, predicate) {
321
- let current = node.parent;
322
- while (current !== null) {
323
- for (const child of current.children) if (predicate(child)) return child;
324
- current = current.parent;
321
+ function incompleteFormalExpressionCode(expected) {
322
+ switch (expected) {
323
+ case TypeClass.function: return RSErrorCode.expectedExpressionBody;
324
+ case TypeClass.predicate:
325
+ case TypeClass.logic: return RSErrorCode.expectedLogicBody;
326
+ default: return RSErrorCode.expectedFunctionBody;
325
327
  }
326
- return null;
327
328
  }
328
- function classifyFuncWithoutArgs(funcNode, expression) {
329
- const name = expression.slice(funcNode.from, funcNode.to);
330
- const rest = expression.slice(funcNode.to);
331
- const withoutArgs = {
332
- code: RSErrorCode.globalFuncWithoutArgs,
333
- from: funcNode.from,
334
- to: funcNode.to,
335
- params: [name]
336
- };
337
- if (/^\s*$/.test(rest)) return withoutArgs;
338
- const openBracket = /^\s*\[/.exec(rest);
339
- if (openBracket === null) return null;
340
- const closeIdx = findMatchingCloseBracket(rest, openBracket[0].length - 1);
341
- if (closeIdx >= 0) {
342
- if (rest.slice(openBracket[0].length, closeIdx).trim().length === 0) return withoutArgs;
329
+ function detectIncompleteQuantifier(node, expression) {
330
+ return detectTrailingErrorSlot(node, expression, findAncestor(node, isQuantifierNode), {
331
+ headerValid: (container) => {
332
+ const varPack = container.children.find((child) => child.typeID === 31 || child.typeID === TokenID.NT_ENUM_DECL);
333
+ return varPack !== void 0 && !subtreeHasError(varPack);
334
+ },
335
+ code: (container, bodyChild) => {
336
+ const inIndex = container.children.findIndex((child) => expression.slice(child.from, child.to) === "∈");
337
+ if (inIndex < 0) return RSErrorCode.expectedQuantifierDomain;
338
+ const domainChild = container.children[inIndex + 1];
339
+ if (domainChild === void 0 || domainChild === bodyChild || subtreeHasError(domainChild)) return RSErrorCode.expectedQuantifierDomain;
340
+ return RSErrorCode.expectedQuantifierBody;
341
+ }
342
+ });
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);
343
390
  }
344
391
  return null;
345
392
  }
346
- function findAncestor(node, predicate) {
347
- let current = node;
348
- while (current !== null) {
349
- if (predicate(current)) return current;
350
- current = current.parent;
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;
351
398
  }
352
399
  return null;
353
400
  }
354
- function isFunctionDeclNode(node) {
355
- return node.typeID === 76 || node.typeID === TokenID.NT_FUNC_DEFINITION;
356
- }
357
- function isQuantifierNode(node) {
358
- return node.typeID === 29;
359
- }
360
- function isFunctionNode(node) {
361
- return node.typeID === 69 || node.typeID === TokenID.ID_FUNCTION;
362
- }
363
- function isPredicateNode(node) {
364
- return node.typeID === 26 || node.typeID === TokenID.ID_PREDICATE;
401
+ function isOperandStructuralToken(node, expression) {
402
+ const text = sliceText(node, expression);
403
+ return text === "(" || text === ")" || text === "ℬ";
365
404
  }
366
- function isFilterNode(node) {
367
- return node.typeID === 59 || node.typeID === TokenID.FILTER;
405
+ function isLegitimateArgumentEnum(enumNode, expression) {
406
+ return findCallHostForBracketEnum(enumNode, expression) !== null;
368
407
  }
369
- function findMatchingCloseBracket(expression, openPos) {
370
- if (expression[openPos] !== "[") return -1;
371
- let depth = 0;
372
- for (let index = openPos; index < expression.length; index++) {
373
- const symbol = expression[index];
374
- if (symbol === "[") depth++;
375
- else if (symbol === "]") {
376
- depth--;
377
- if (depth === 0) return index;
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;
378
414
  }
415
+ current = current.parent;
379
416
  }
380
- return -1;
417
+ return null;
381
418
  }
382
- function deduplicateErrors(errors) {
383
- const result = [];
384
- for (const error of errors) {
385
- if (result.some((existing) => existing.code === error.code && existing.from === error.from && existing.to === error.to)) continue;
386
- if (error.code !== RSErrorCode.unknownSyntax) {
387
- for (let index = result.length - 1; index >= 0; index--) if (result[index].code === RSErrorCode.unknownSyntax && rangesOverlap(error, result[index])) result.splice(index, 1);
388
- }
389
- result.push(error);
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;
390
443
  }
391
- const specific = result.filter((error) => error.code !== RSErrorCode.unknownSyntax);
392
- return result.filter((error) => error.code !== RSErrorCode.unknownSyntax || !specific.some((item) => rangesOverlap(error, item)));
444
+ return null;
393
445
  }
394
- function rangesOverlap(a, b) {
395
- return a.from < b.to && b.from < a.to;
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
+ };
396
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
+ */
397
481
  const ALLOWED_EXPRESSION_CHARS = buildAllowedExpressionChars();
398
482
  function buildAllowedExpressionChars() {
399
483
  const chars = /* @__PURE__ */ new Set();
@@ -474,6 +558,50 @@ function extractBracketErrors(expression) {
474
558
  }
475
559
  return null;
476
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 sorted = [...unknowns].sort((left, right) => left.from - right.from || left.to - right.to);
578
+ const merged = [];
579
+ let clusterFrom = sorted[0].from;
580
+ let clusterTo = sorted[0].to;
581
+ for (let index = 1; index < sorted.length; index++) {
582
+ const error = sorted[index];
583
+ if (error.from <= clusterTo) {
584
+ clusterTo = Math.max(clusterTo, error.to);
585
+ continue;
586
+ }
587
+ merged.push({
588
+ code: RSErrorCode.unknownSyntax,
589
+ from: clusterFrom,
590
+ to: clusterTo
591
+ });
592
+ clusterFrom = error.from;
593
+ clusterTo = error.to;
594
+ }
595
+ merged.push({
596
+ code: RSErrorCode.unknownSyntax,
597
+ from: clusterFrom,
598
+ to: clusterTo
599
+ });
600
+ return [...others, ...merged];
601
+ }
602
+ function rangesOverlap(a, b) {
603
+ return a.from < b.to && b.from < a.to;
604
+ }
477
605
  function isOpenBracket(symbol) {
478
606
  return symbol === "(" || symbol === "[" || symbol === "{";
479
607
  }
@@ -518,6 +646,66 @@ function findMatchingCloseParen(expression, openPos) {
518
646
  return -1;
519
647
  }
520
648
  //#endregion
521
- export { extractSyntaxErrors };
649
+ //#region src/rslang/parser/syntax-errors/index.ts
650
+ /**
651
+ * Syntax error extraction — public entry point.
652
+ *
653
+ * Expects the **hybrid** AST produced by `normalizeAST` (see `parser/README.md`).
654
+ * See {@link ./README.md} for pipeline, classifier order, and how to add codes.
655
+ *
656
+ * @module rslang/parser/syntax-errors
657
+ */
658
+ /**
659
+ * Walks a parsed expression AST and reports syntax errors.
660
+ *
661
+ * @param ast - Root AST from `buildTree(parser.parse(expression))`
662
+ * @param expression - Original source text (positions must match the AST)
663
+ * @param reporter - Called once per deduplicated error
664
+ * @param annotateErrors - When true, also calls `annotateError` on the triggering node
665
+ * @param options - Optional typification hint for incomplete formal expressions
666
+ */
667
+ function extractSyntaxErrors(ast, expression, reporter, annotateErrors = false, options) {
668
+ const forbiddenErrors = extractForbiddenCharacterErrors(expression);
669
+ if (forbiddenErrors.length > 0) {
670
+ for (const error of forbiddenErrors) {
671
+ reporter(error);
672
+ if (annotateErrors) annotateError(ast, error.code, error.params);
673
+ }
674
+ return;
675
+ }
676
+ const collected = [];
677
+ const annotationTargets = /* @__PURE__ */ new Map();
678
+ const collect = (error, target) => {
679
+ collected.push(error);
680
+ if (target !== void 0) annotationTargets.set(error, target);
681
+ };
682
+ const bracketError = extractBracketErrors(expression);
683
+ const hasBracketErrors = bracketError !== null;
684
+ visitAstDFS(ast, (node) => extractInternal(node, expression, collect, hasBracketErrors, options));
685
+ const hasIncompleteGenerator = collected.some((error) => isIncompleteGeneratorBodyCode(error.code));
686
+ if (bracketError !== null && !hasIncompleteGenerator) collect(bracketError, ast);
687
+ for (const error of deduplicateErrors(collected)) {
688
+ reporter(error);
689
+ if (annotateErrors) {
690
+ const target = annotationTargets.get(error);
691
+ if (target !== void 0) annotateError(target, error.code, error.params);
692
+ }
693
+ }
694
+ }
695
+ function extractInternal(node, expression, collect, hasBracketErrors, options) {
696
+ if (node.typeID !== TokenID.ERROR) return;
697
+ const classified = classifyParseError(node, expression, {
698
+ hasBracketErrors,
699
+ expected: options?.expected
700
+ });
701
+ if (classified !== null) {
702
+ collect(classified, node);
703
+ return;
704
+ }
705
+ const fallback = classifyFallbackError(node, hasBracketErrors);
706
+ if (fallback !== null) collect(fallback.error, fallback.target);
707
+ }
708
+ //#endregion
709
+ export { extractSyntaxErrors as t };
522
710
 
523
- //# sourceMappingURL=syntax-errors.js.map
711
+ //# sourceMappingURL=syntax-errors-B6c2vsan.js.map