@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.
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-pCI6RicF.js} +399 -233
  28. package/dist/syntax-errors-pCI6RicF.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 +237 -0
  41. package/src/rslang/parser/syntax-errors/function-calls.ts +76 -0
  42. package/src/rslang/parser/syntax-errors/generators.ts +307 -0
  43. package/src/rslang/parser/syntax-errors/incomplete-formal.ts +76 -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,159 @@
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);
117
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);
118
142
  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));
143
+ return detectIncompleteGeneratorBody(node, expression, findAncestor(node, (target) => target.typeID === 60 || target.typeID === TokenID.NT_DECLARATIVE_EXPR), RSErrorCode.expectedDeclarativeBody, (slot) => isIncompleteDeclarativeBody(slot.nodes));
120
144
  }
121
145
  function detectIncompleteImperative(node, expression) {
122
146
  return detectIncompleteGeneratorBody(node, expression, findAncestor(node, (target) => target.typeID === 64 || target.typeID === TokenID.NT_IMPERATIVE_EXPR), RSErrorCode.expectedImperativeBody, (slot) => isIncompleteImperativeBody(slot, expression));
123
147
  }
124
148
  function detectIncompleteRecursion(node, expression) {
125
- return detectIncompleteGeneratorBody(node, expression, findAncestor(node, (target) => target.typeID === 68), RSErrorCode.expectedRecursiveBody, (slot) => isIncompleteRecursionBody(slot));
149
+ return detectIncompleteGeneratorBody(node, expression, findAncestor(node, (target) => target.typeID === 68), RSErrorCode.expectedRecursiveBody, (slot) => isIncompleteRecursionBody(slot.nodes));
126
150
  }
127
151
  function detectIncompleteGeneratorBody(node, expression, generatorNode, code, isIncomplete) {
128
- if (generatorNode === null) return null;
129
- if (expression.slice(generatorNode.to).trim().length > 0) return null;
152
+ if (generatorNode === null || expression.slice(generatorNode.to).trim().length > 0) return null;
130
153
  const slot = getGeneratorBodySlot(generatorNode, expression);
131
- if (slot === null || !isIncomplete(slot)) return null;
132
- if (!generatorHeaderIsValid(generatorNode, expression, slot.headerEndIndex)) return null;
154
+ if (slot === null || !isIncomplete(slot) || !generatorHeaderIsValid(generatorNode, expression, slot.headerEndIndex)) return null;
133
155
  if (!isErrorInBodySlot(node, slot, generatorNode)) return null;
134
- return incompleteGeneratorError(code, node);
156
+ return errorAt(node, code);
135
157
  }
136
158
  function getGeneratorBodySlot(generatorNode, expression) {
137
159
  if (generatorNode.typeID === 60 || generatorNode.typeID === TokenID.NT_DECLARATIVE_EXPR) return getDeclarativeBodySlot(generatorNode, expression);
@@ -140,7 +162,7 @@ function getGeneratorBodySlot(generatorNode, expression) {
140
162
  return null;
141
163
  }
142
164
  function getDeclarativeBodySlot(declNode, expression) {
143
- const pipeIndex = declNode.children.findIndex((child) => isPipeToken(child, expression));
165
+ const pipeIndex = declNode.children.findIndex((child) => isTextToken(child, expression, "|"));
144
166
  if (pipeIndex >= 0) return {
145
167
  nodes: contentChildren(declNode, expression, pipeIndex + 1),
146
168
  headerEndIndex: pipeIndex + 1
@@ -154,7 +176,7 @@ function getDeclarativeBodySlot(declNode, expression) {
154
176
  };
155
177
  }
156
178
  function getImperativeBodySlot(impNode, expression) {
157
- const pipeIndex = impNode.children.findIndex((child) => isPipeToken(child, expression));
179
+ const pipeIndex = impNode.children.findIndex((child) => isTextToken(child, expression, "|"));
158
180
  if (pipeIndex >= 0) return {
159
181
  nodes: contentChildren(impNode, expression, pipeIndex + 1),
160
182
  headerEndIndex: pipeIndex + 1
@@ -186,11 +208,11 @@ function contentChildren(generatorNode, expression, fromIndex) {
186
208
  return generatorNode.children.slice(fromIndex).filter((child) => !isClosingBrace(child, expression));
187
209
  }
188
210
  function findDeclarativeDomain(declNode, expression) {
189
- const inIndex = declNode.children.findIndex((child) => expression.slice(child.from, child.to) === "∈");
211
+ const inIndex = declNode.children.findIndex((child) => isTextToken(child, expression, "∈"));
190
212
  if (inIndex < 0) return null;
191
213
  for (let index = inIndex + 1; index < declNode.children.length; index++) {
192
214
  const child = declNode.children[index];
193
- if (isPipeToken(child, expression) || isClosingBrace(child, expression)) break;
215
+ if (isTextToken(child, expression, "|") || isClosingBrace(child, expression)) break;
194
216
  if (child.typeID === 38) return child;
195
217
  }
196
218
  return null;
@@ -200,7 +222,7 @@ function findImperativeTuple(impNode) {
200
222
  return null;
201
223
  }
202
224
  function findLastPipeIndex(nodes, expression) {
203
- for (let index = nodes.length - 1; index >= 0; index--) if (isPipeToken(nodes[index], expression)) return index;
225
+ for (let index = nodes.length - 1; index >= 0; index--) if (isTextToken(nodes[index], expression, "|")) return index;
204
226
  return -1;
205
227
  }
206
228
  function generatorHeaderIsValid(generatorNode, expression, headerEndIndex) {
@@ -212,16 +234,16 @@ function generatorHeaderIsValid(generatorNode, expression, headerEndIndex) {
212
234
  }
213
235
  return true;
214
236
  }
215
- function isIncompleteDeclarativeBody(slot) {
216
- return isEmptyOrErrorOnlyBody(slot.nodes);
237
+ function isIncompleteDeclarativeBody(nodes) {
238
+ return isEmptyOrErrorOnlyBody(nodes);
217
239
  }
218
240
  function isIncompleteImperativeBody(slot, expression) {
219
241
  if (slot.nodes.length === 0) return true;
220
242
  if (slot.nodes.length === 1 && slot.nodes[0].typeID === 65) return isImpBlocksIncomplete(slot.nodes[0], expression);
221
243
  return isEmptyOrErrorOnlyBody(slot.nodes);
222
244
  }
223
- function isIncompleteRecursionBody(slot) {
224
- return isEmptyOrErrorOnlyBody(slot.nodes);
245
+ function isIncompleteRecursionBody(nodes) {
246
+ return isEmptyOrErrorOnlyBody(nodes);
225
247
  }
226
248
  function isEmptyOrErrorOnlyBody(nodes) {
227
249
  if (nodes.length === 0) return true;
@@ -232,7 +254,7 @@ function isImpBlocksIncomplete(blocksNode, expression) {
232
254
  if (blocksNode.children.length === 0) return true;
233
255
  const lastChild = blocksNode.children[blocksNode.children.length - 1];
234
256
  if (lastChild.typeID === TokenID.ERROR) return true;
235
- if (isSemicolonToken(lastChild, expression)) return true;
257
+ if (isTextToken(lastChild, expression, ";")) return true;
236
258
  if (lastChild.typeID === 38 && isErrorPlaceholder(lastChild)) return true;
237
259
  return false;
238
260
  }
@@ -251,149 +273,211 @@ function isImpBlocksTailError(blocksNode, errorNode) {
251
273
  const lastChild = blocksNode.children[blocksNode.children.length - 1];
252
274
  return lastChild.typeID === TokenID.ERROR && (errorNode === lastChild || isDescendantOf(lastChild, errorNode));
253
275
  }
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
276
  function findRecursionInit(recursionNode, expression) {
268
- const assignIndex = recursionNode.children.findIndex((child) => expression.slice(child.from, child.to) === ":=");
277
+ const assignIndex = recursionNode.children.findIndex((child) => isTextToken(child, expression, ":="));
269
278
  if (assignIndex < 0) return null;
270
279
  for (let index = assignIndex + 1; index < recursionNode.children.length; index++) {
271
280
  const child = recursionNode.children[index];
272
281
  if (child.typeID === TokenID.ERROR) break;
273
- if (isPipeToken(child, expression) || isClosingBrace(child, expression)) continue;
282
+ if (isTextToken(child, expression, "|") || isClosingBrace(child, expression)) continue;
274
283
  return child;
275
284
  }
276
285
  return null;
277
286
  }
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
287
  function isClosingBrace(node, expression) {
295
- return node.typeID !== TokenID.ERROR && expression.slice(node.from, node.to) === "}";
288
+ return node.typeID !== TokenID.ERROR && isTextToken(node, expression, "}");
296
289
  }
297
290
  function isStructuralGeneratorToken(node, expression) {
298
291
  const text = expression.slice(node.from, node.to);
299
292
  return text === "{" || text === "}" || text === "|" || text === "∈" || text === ":=" || text === ";";
300
293
  }
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;
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);
314
305
  }
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);
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
+ });
319
317
  }
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;
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;
325
324
  }
326
- return null;
327
325
  }
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;
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);
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,28 @@ 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 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
+ }
477
583
  function isOpenBracket(symbol) {
478
584
  return symbol === "(" || symbol === "[" || symbol === "{";
479
585
  }
@@ -518,6 +624,66 @@ function findMatchingCloseParen(expression, openPos) {
518
624
  return -1;
519
625
  }
520
626
  //#endregion
521
- export { extractSyntaxErrors };
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 };
522
688
 
523
- //# sourceMappingURL=syntax-errors.js.map
689
+ //# sourceMappingURL=syntax-errors-pCI6RicF.js.map