@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.
- package/dist/{analyzer-CqUJ3FO9.d.ts → analyzer-QxWs73gP.d.ts} +2 -2
- package/dist/{ast-annotations-DvB1VcUv.d.ts → ast-annotations-CtF7C9uK.d.ts} +2 -2
- package/dist/{calculator-DKxDpTdT.d.ts → calculator-REeX2ZS1.d.ts} +2 -2
- package/dist/{error-CLnXbkly.d.ts → error-toW0TUHF.d.ts} +6 -1
- package/dist/index.d.ts +4 -4
- package/dist/library/rsengine.d.ts +1 -1
- package/dist/library/rsform-api.d.ts +1 -1
- package/dist/library/rsform.d.ts +1 -1
- package/dist/rslang/ast-annotations.d.ts +1 -1
- package/dist/rslang/error.d.ts +1 -1
- package/dist/rslang/error.js +5 -0
- package/dist/rslang/error.js.map +1 -1
- package/dist/rslang/eval/calculator.d.ts +1 -1
- package/dist/rslang/eval/evaluator.d.ts +1 -1
- package/dist/rslang/index.d.ts +4 -4
- package/dist/rslang/labels.d.ts +1 -1
- package/dist/rslang/labels.js +1 -1
- package/dist/rslang/labels.js.map +1 -1
- package/dist/rslang/parser/normalize.js +5 -1
- package/dist/rslang/parser/normalize.js.map +1 -1
- package/dist/rslang/parser/syntax-errors/index.d.ts +22 -0
- package/dist/rslang/parser/syntax-errors/index.js +2 -0
- package/dist/rslang/semantic/analyzer.d.ts +1 -1
- package/dist/rslang/semantic/analyzer.js +1 -1
- package/dist/rslang/semantic/type-auditor.d.ts +1 -1
- package/dist/rslang/semantic/value-auditor.d.ts +1 -1
- package/dist/{rslang/parser/syntax-errors.js → syntax-errors-B6c2vsan.js} +421 -233
- package/dist/syntax-errors-B6c2vsan.js.map +1 -0
- package/package.json +1 -1
- package/src/rslang/error.ts +5 -0
- package/src/rslang/labels.ts +1 -1
- package/src/rslang/parser/README.md +73 -0
- package/src/rslang/parser/normalize.ts +5 -1
- package/src/rslang/parser/parser.test.ts +53 -0
- package/src/rslang/parser/syntax-errors/README.md +61 -0
- package/src/rslang/parser/syntax-errors/ast-utils.ts +112 -0
- package/src/rslang/parser/syntax-errors/classify.ts +70 -0
- package/src/rslang/parser/syntax-errors/context.ts +25 -0
- package/src/rslang/parser/syntax-errors/error-builders.ts +28 -0
- package/src/rslang/parser/syntax-errors/expression-errors.ts +255 -0
- package/src/rslang/parser/syntax-errors/function-calls.ts +76 -0
- package/src/rslang/parser/syntax-errors/generators.ts +315 -0
- package/src/rslang/parser/syntax-errors/incomplete-formal.ts +68 -0
- package/src/rslang/parser/syntax-errors/incomplete-operands.ts +184 -0
- package/src/rslang/parser/syntax-errors/index.ts +102 -0
- package/src/rslang/parser/syntax-errors/trailing-slot.ts +45 -0
- package/dist/rslang/parser/syntax-errors.d.ts +0 -14
- package/dist/rslang/parser/syntax-errors.js.map +0 -1
- package/src/rslang/parser/syntax-errors.ts +0 -929
|
@@ -0,0 +1,255 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Expression-level diagnostics (no AST required) and error deduplication.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { RSErrorCode, type RSErrorDescription } from '../../error';
|
|
6
|
+
|
|
7
|
+
type OpenBracket = '(' | '[' | '{';
|
|
8
|
+
type CloseBracket = ')' | ']' | '}';
|
|
9
|
+
|
|
10
|
+
interface BracketFrame {
|
|
11
|
+
bracket: OpenBracket;
|
|
12
|
+
index: number;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
const ALLOWED_EXPRESSION_CHARS = buildAllowedExpressionChars();
|
|
16
|
+
|
|
17
|
+
function buildAllowedExpressionChars(): Set<string> {
|
|
18
|
+
const chars = new Set<string>();
|
|
19
|
+
for (const chunk of [
|
|
20
|
+
'0123456789',
|
|
21
|
+
'abcdefghijklmnopqrstuvwxyz',
|
|
22
|
+
'ABCDEFGHIJKLMNOPQRSTUVWXYZ',
|
|
23
|
+
'_',
|
|
24
|
+
'∅¬∀∃⇔⇒∨&ℬ+-*∪\\∆∩×∈∉⊆⊄⊂<>≥≤≠=,:;|[]{}()'
|
|
25
|
+
]) {
|
|
26
|
+
for (const symbol of chunk) {
|
|
27
|
+
chars.add(symbol);
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
for (let codePoint = 0x03b1; codePoint <= 0x03c9; codePoint++) {
|
|
31
|
+
chars.add(String.fromCodePoint(codePoint));
|
|
32
|
+
}
|
|
33
|
+
return chars;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function isAllowedExpressionChar(symbol: string): boolean {
|
|
37
|
+
return /\s/u.test(symbol) || ALLOWED_EXPRESSION_CHARS.has(symbol);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export function extractForbiddenCharacterErrors(expression: string): RSErrorDescription[] {
|
|
41
|
+
const errors: RSErrorDescription[] = [];
|
|
42
|
+
let pos = 0;
|
|
43
|
+
|
|
44
|
+
while (pos < expression.length) {
|
|
45
|
+
const symbol = expression[pos];
|
|
46
|
+
if (isAllowedExpressionChar(symbol)) {
|
|
47
|
+
pos++;
|
|
48
|
+
continue;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
const start = pos;
|
|
52
|
+
while (pos < expression.length && !isAllowedExpressionChar(expression[pos])) {
|
|
53
|
+
pos++;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
errors.push({
|
|
57
|
+
code: RSErrorCode.forbiddenCharacter,
|
|
58
|
+
from: start,
|
|
59
|
+
to: pos,
|
|
60
|
+
params: [expression.slice(start, pos)]
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
return errors;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export function extractBracketErrors(expression: string): RSErrorDescription | null {
|
|
68
|
+
const stack: BracketFrame[] = [];
|
|
69
|
+
|
|
70
|
+
for (let pos = 0; pos < expression.length; pos++) {
|
|
71
|
+
const symbol = expression[pos];
|
|
72
|
+
if (isOpenBracket(symbol)) {
|
|
73
|
+
if (isDoubleParenthesis(expression, pos)) {
|
|
74
|
+
return {
|
|
75
|
+
code: RSErrorCode.doubleParenthesis,
|
|
76
|
+
from: pos,
|
|
77
|
+
to: pos + 2
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
stack.push({ bracket: symbol, index: pos });
|
|
81
|
+
continue;
|
|
82
|
+
}
|
|
83
|
+
if (!isCloseBracket(symbol)) {
|
|
84
|
+
continue;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
const expectedOpen = closeToOpen(symbol);
|
|
88
|
+
const top = stack[stack.length - 1];
|
|
89
|
+
if (top === undefined) {
|
|
90
|
+
return {
|
|
91
|
+
code: RSErrorCode.missingOpenBracket,
|
|
92
|
+
from: pos,
|
|
93
|
+
to: pos + 1,
|
|
94
|
+
params: [symbol]
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
if (top.bracket !== expectedOpen) {
|
|
99
|
+
return {
|
|
100
|
+
code: RSErrorCode.bracketMismatch,
|
|
101
|
+
from: pos,
|
|
102
|
+
to: pos + 1,
|
|
103
|
+
params: [openToClose(top.bracket), symbol]
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
stack.pop();
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
if (stack.length > 0) {
|
|
111
|
+
const unclosed = stack[0];
|
|
112
|
+
return {
|
|
113
|
+
code: RSErrorCode.missingCloseBracket,
|
|
114
|
+
from: unclosed.index,
|
|
115
|
+
to: unclosed.index + 1,
|
|
116
|
+
params: [unclosed.bracket]
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
return null;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
export function deduplicateErrors(errors: RSErrorDescription[]): RSErrorDescription[] {
|
|
124
|
+
const result: RSErrorDescription[] = [];
|
|
125
|
+
|
|
126
|
+
for (const error of errors) {
|
|
127
|
+
const duplicate = result.some(
|
|
128
|
+
existing => existing.code === error.code && existing.from === error.from && existing.to === error.to
|
|
129
|
+
);
|
|
130
|
+
if (duplicate) {
|
|
131
|
+
continue;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
if (error.code !== RSErrorCode.unknownSyntax) {
|
|
135
|
+
for (let index = result.length - 1; index >= 0; index--) {
|
|
136
|
+
if (result[index].code === RSErrorCode.unknownSyntax && rangesOverlap(error, result[index])) {
|
|
137
|
+
result.splice(index, 1);
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
result.push(error);
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
const specific = result.filter(error => error.code !== RSErrorCode.unknownSyntax);
|
|
146
|
+
const filtered = result.filter(
|
|
147
|
+
error => error.code !== RSErrorCode.unknownSyntax || !specific.some(item => rangesOverlap(error, item))
|
|
148
|
+
);
|
|
149
|
+
return collapseContiguousUnknownSyntax(filtered);
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
function collapseContiguousUnknownSyntax(errors: RSErrorDescription[]): RSErrorDescription[] {
|
|
153
|
+
const unknowns = errors.filter(error => error.code === RSErrorCode.unknownSyntax);
|
|
154
|
+
if (unknowns.length <= 1) {
|
|
155
|
+
return errors;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
const others = errors.filter(error => error.code !== RSErrorCode.unknownSyntax);
|
|
159
|
+
const sorted = [...unknowns].sort((left, right) => left.from - right.from || left.to - right.to);
|
|
160
|
+
const merged: RSErrorDescription[] = [];
|
|
161
|
+
|
|
162
|
+
let clusterFrom = sorted[0].from;
|
|
163
|
+
let clusterTo = sorted[0].to;
|
|
164
|
+
|
|
165
|
+
for (let index = 1; index < sorted.length; index++) {
|
|
166
|
+
const error = sorted[index];
|
|
167
|
+
if (error.from <= clusterTo) {
|
|
168
|
+
clusterTo = Math.max(clusterTo, error.to);
|
|
169
|
+
continue;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
merged.push({ code: RSErrorCode.unknownSyntax, from: clusterFrom, to: clusterTo });
|
|
173
|
+
clusterFrom = error.from;
|
|
174
|
+
clusterTo = error.to;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
merged.push({ code: RSErrorCode.unknownSyntax, from: clusterFrom, to: clusterTo });
|
|
178
|
+
return [...others, ...merged];
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
function rangesOverlap(a: RSErrorDescription, b: RSErrorDescription): boolean {
|
|
182
|
+
return a.from < b.to && b.from < a.to;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
function isOpenBracket(symbol: string): symbol is OpenBracket {
|
|
186
|
+
return symbol === '(' || symbol === '[' || symbol === '{';
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
function isCloseBracket(symbol: string): symbol is CloseBracket {
|
|
190
|
+
return symbol === ')' || symbol === ']' || symbol === '}';
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
function openToClose(symbol: OpenBracket): CloseBracket {
|
|
194
|
+
switch (symbol) {
|
|
195
|
+
case '(':
|
|
196
|
+
return ')';
|
|
197
|
+
case '[':
|
|
198
|
+
return ']';
|
|
199
|
+
case '{':
|
|
200
|
+
return '}';
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
function closeToOpen(symbol: CloseBracket): OpenBracket {
|
|
205
|
+
switch (symbol) {
|
|
206
|
+
case ')':
|
|
207
|
+
return '(';
|
|
208
|
+
case ']':
|
|
209
|
+
return '[';
|
|
210
|
+
case '}':
|
|
211
|
+
return '{';
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
function isDoubleParenthesis(expression: string, pos: number): boolean {
|
|
216
|
+
const isOpenDoubleParenthesis = expression[pos] === '(' && expression[pos + 1] === '(';
|
|
217
|
+
if (!isOpenDoubleParenthesis) {
|
|
218
|
+
return false;
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
const outerClose = findMatchingCloseParen(expression, pos);
|
|
222
|
+
const innerClose = findMatchingCloseParen(expression, pos + 1);
|
|
223
|
+
if (outerClose < 0 || innerClose < 0) {
|
|
224
|
+
return false;
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
return outerClose === innerClose + 1;
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
function findMatchingCloseParen(expression: string, openPos: number): number {
|
|
231
|
+
if (expression[openPos] !== '(') {
|
|
232
|
+
return -1;
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
const stack: OpenBracket[] = [];
|
|
236
|
+
for (let index = openPos; index < expression.length; index++) {
|
|
237
|
+
const symbol = expression[index];
|
|
238
|
+
if (isOpenBracket(symbol)) {
|
|
239
|
+
stack.push(symbol);
|
|
240
|
+
continue;
|
|
241
|
+
}
|
|
242
|
+
if (!isCloseBracket(symbol)) {
|
|
243
|
+
continue;
|
|
244
|
+
}
|
|
245
|
+
const top = stack.pop();
|
|
246
|
+
if (top === undefined || top !== closeToOpen(symbol)) {
|
|
247
|
+
return -1;
|
|
248
|
+
}
|
|
249
|
+
if (stack.length === 0) {
|
|
250
|
+
return index;
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
return -1;
|
|
255
|
+
}
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Malformed calls: `Fi…(…)`, `F1` / `P1` without or with wrong argument brackets.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import type { AstNode } from '../../../parsing';
|
|
6
|
+
import { RSErrorCode, type RSErrorDescription } from '../../error';
|
|
7
|
+
|
|
8
|
+
import {
|
|
9
|
+
findAncestor,
|
|
10
|
+
findMatchingCloseBracket,
|
|
11
|
+
findRelatedNode,
|
|
12
|
+
isFilterNode,
|
|
13
|
+
isFunctionNode,
|
|
14
|
+
isPredicateNode
|
|
15
|
+
} from './ast-utils';
|
|
16
|
+
import type { ErrorClassifier } from './context';
|
|
17
|
+
|
|
18
|
+
export const classifyFilterParenMismatch: ErrorClassifier = (node, expression) => {
|
|
19
|
+
const filterNode = findAncestor(node, isFilterNode) ?? findRelatedNode(node, isFilterNode);
|
|
20
|
+
if (filterNode === null || expression[filterNode.to] !== '(') {
|
|
21
|
+
return null;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
return {
|
|
25
|
+
code: RSErrorCode.invalidFilterSyntax,
|
|
26
|
+
from: filterNode.from,
|
|
27
|
+
to: filterNode.to + 1
|
|
28
|
+
};
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
export const classifyGlobalFuncCall: ErrorClassifier = (node, expression) => {
|
|
32
|
+
const funcNode =
|
|
33
|
+
findAncestor(node, n => isFunctionNode(n) || isPredicateNode(n)) ??
|
|
34
|
+
findRelatedNode(node, n => isFunctionNode(n) || isPredicateNode(n));
|
|
35
|
+
if (funcNode === null) {
|
|
36
|
+
return null;
|
|
37
|
+
}
|
|
38
|
+
return classifyFuncWithoutArgs(funcNode, expression);
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
function classifyFuncWithoutArgs(funcNode: AstNode, expression: string): RSErrorDescription | null {
|
|
42
|
+
const name = expression.slice(funcNode.from, funcNode.to);
|
|
43
|
+
const rest = expression.slice(funcNode.to);
|
|
44
|
+
|
|
45
|
+
const withoutArgs: RSErrorDescription = {
|
|
46
|
+
code: RSErrorCode.globalFuncWithoutArgs,
|
|
47
|
+
from: funcNode.from,
|
|
48
|
+
to: funcNode.to,
|
|
49
|
+
params: [name]
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
if (/^\s*$/.test(rest)) {
|
|
53
|
+
return withoutArgs;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
if (/^\s*\(/.test(rest)) {
|
|
57
|
+
return {
|
|
58
|
+
code: RSErrorCode.globalFuncParenCall,
|
|
59
|
+
from: funcNode.from,
|
|
60
|
+
to: funcNode.to,
|
|
61
|
+
params: [name]
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
const openBracket = /^\s*\[/.exec(rest);
|
|
66
|
+
if (openBracket === null) {
|
|
67
|
+
return null;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
const closeIdx = findMatchingCloseBracket(rest, openBracket[0].length - 1);
|
|
71
|
+
if (closeIdx >= 0 && rest.slice(openBracket[0].length, closeIdx).trim().length === 0) {
|
|
72
|
+
return withoutArgs;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
return null;
|
|
76
|
+
}
|
|
@@ -0,0 +1,315 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Incomplete generator bodies: declarative `D{…}`, imperative `I{…}`, recursive `R{…}`.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import type { AstNode } from '../../../parsing';
|
|
6
|
+
import { RSErrorCode, type RSErrorDescription } from '../../error';
|
|
7
|
+
import { Declarative, Imp_blocks, Imperative, Recursion, Setexpr } from '../parser.terms';
|
|
8
|
+
import { TokenID } from '../token';
|
|
9
|
+
|
|
10
|
+
import { findAncestor, isDescendantOf, isErrorPlaceholder, subtreeHasError } from './ast-utils';
|
|
11
|
+
import type { ErrorClassifier } from './context';
|
|
12
|
+
import { errorAt, isTextToken } from './error-builders';
|
|
13
|
+
|
|
14
|
+
export const classifyIncompleteGenerator: ErrorClassifier = (node, expression) =>
|
|
15
|
+
detectIncompleteDeclarative(node, expression) ??
|
|
16
|
+
detectIncompleteImperative(node, expression) ??
|
|
17
|
+
detectIncompleteRecursion(node, expression);
|
|
18
|
+
|
|
19
|
+
export function isIncompleteGeneratorBodyCode(code: RSErrorCode): boolean {
|
|
20
|
+
return (
|
|
21
|
+
code === RSErrorCode.expectedDeclarativeBody ||
|
|
22
|
+
code === RSErrorCode.expectedImperativeBody ||
|
|
23
|
+
code === RSErrorCode.expectedRecursiveBody
|
|
24
|
+
);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
interface GeneratorBodySlot {
|
|
28
|
+
nodes: AstNode[];
|
|
29
|
+
headerEndIndex: number;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function detectIncompleteDeclarative(node: AstNode, expression: string): RSErrorDescription | null {
|
|
33
|
+
const generatorNode = findAncestor(
|
|
34
|
+
node,
|
|
35
|
+
target => target.typeID === Declarative || target.typeID === TokenID.NT_DECLARATIVE_EXPR
|
|
36
|
+
);
|
|
37
|
+
return detectIncompleteGeneratorBody(node, expression, generatorNode, RSErrorCode.expectedDeclarativeBody, slot =>
|
|
38
|
+
isIncompleteDeclarativeBody(slot.nodes)
|
|
39
|
+
);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function detectIncompleteImperative(node: AstNode, expression: string): RSErrorDescription | null {
|
|
43
|
+
const generatorNode = findAncestor(
|
|
44
|
+
node,
|
|
45
|
+
target => target.typeID === Imperative || target.typeID === TokenID.NT_IMPERATIVE_EXPR
|
|
46
|
+
);
|
|
47
|
+
return detectIncompleteGeneratorBody(node, expression, generatorNode, RSErrorCode.expectedImperativeBody, slot =>
|
|
48
|
+
isIncompleteImperativeBody(slot, expression)
|
|
49
|
+
);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function detectIncompleteRecursion(node: AstNode, expression: string): RSErrorDescription | null {
|
|
53
|
+
const generatorNode = findAncestor(node, target => target.typeID === Recursion);
|
|
54
|
+
return detectIncompleteGeneratorBody(node, expression, generatorNode, RSErrorCode.expectedRecursiveBody, slot =>
|
|
55
|
+
isIncompleteRecursionBody(slot.nodes)
|
|
56
|
+
);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function detectIncompleteGeneratorBody(
|
|
60
|
+
node: AstNode,
|
|
61
|
+
expression: string,
|
|
62
|
+
generatorNode: AstNode | null,
|
|
63
|
+
code: RSErrorCode,
|
|
64
|
+
isIncomplete: (slot: GeneratorBodySlot) => boolean
|
|
65
|
+
): RSErrorDescription | null {
|
|
66
|
+
if (generatorNode === null || expression.slice(generatorNode.to).trim().length > 0) {
|
|
67
|
+
return null;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
const slot = getGeneratorBodySlot(generatorNode, expression);
|
|
71
|
+
if (slot === null || !isIncomplete(slot) || !generatorHeaderIsValid(generatorNode, expression, slot.headerEndIndex)) {
|
|
72
|
+
return null;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
if (!isErrorInBodySlot(node, slot, generatorNode)) {
|
|
76
|
+
return null;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
return errorAt(node, code);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
function getGeneratorBodySlot(generatorNode: AstNode, expression: string): GeneratorBodySlot | null {
|
|
83
|
+
if (generatorNode.typeID === Declarative || generatorNode.typeID === TokenID.NT_DECLARATIVE_EXPR) {
|
|
84
|
+
return getDeclarativeBodySlot(generatorNode, expression);
|
|
85
|
+
}
|
|
86
|
+
if (generatorNode.typeID === Imperative || generatorNode.typeID === TokenID.NT_IMPERATIVE_EXPR) {
|
|
87
|
+
return getImperativeBodySlot(generatorNode, expression);
|
|
88
|
+
}
|
|
89
|
+
if (generatorNode.typeID === Recursion) {
|
|
90
|
+
return getRecursionBodySlot(generatorNode, expression);
|
|
91
|
+
}
|
|
92
|
+
return null;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
function getDeclarativeBodySlot(declNode: AstNode, expression: string): GeneratorBodySlot | null {
|
|
96
|
+
const pipeIndex = declNode.children.findIndex(child => isTextToken(child, expression, '|'));
|
|
97
|
+
if (pipeIndex >= 0) {
|
|
98
|
+
return {
|
|
99
|
+
nodes: contentChildren(declNode, expression, pipeIndex + 1),
|
|
100
|
+
headerEndIndex: pipeIndex + 1
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
const domainNode = findDeclarativeDomain(declNode, expression);
|
|
105
|
+
if (domainNode === null) {
|
|
106
|
+
return null;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
const domainIndex = declNode.children.indexOf(domainNode);
|
|
110
|
+
return {
|
|
111
|
+
nodes: contentChildren(declNode, expression, domainIndex + 1),
|
|
112
|
+
headerEndIndex: domainIndex + 1
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
function getImperativeBodySlot(impNode: AstNode, expression: string): GeneratorBodySlot | null {
|
|
117
|
+
const pipeIndex = impNode.children.findIndex(child => isTextToken(child, expression, '|'));
|
|
118
|
+
if (pipeIndex >= 0) {
|
|
119
|
+
return {
|
|
120
|
+
nodes: contentChildren(impNode, expression, pipeIndex + 1),
|
|
121
|
+
headerEndIndex: pipeIndex + 1
|
|
122
|
+
};
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
const tupleNode = findImperativeTuple(impNode);
|
|
126
|
+
if (tupleNode === null) {
|
|
127
|
+
return null;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
const tupleIndex = impNode.children.indexOf(tupleNode);
|
|
131
|
+
return {
|
|
132
|
+
nodes: contentChildren(impNode, expression, tupleIndex + 1),
|
|
133
|
+
headerEndIndex: tupleIndex + 1
|
|
134
|
+
};
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
function getRecursionBodySlot(recNode: AstNode, expression: string): GeneratorBodySlot | null {
|
|
138
|
+
const initNode = findRecursionInit(recNode, expression);
|
|
139
|
+
if (initNode === null) {
|
|
140
|
+
return null;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
const initIndex = recNode.children.indexOf(initNode);
|
|
144
|
+
const afterInit = contentChildren(recNode, expression, initIndex + 1);
|
|
145
|
+
const lastPipeIndex = findLastPipeIndex(afterInit, expression);
|
|
146
|
+
if (lastPipeIndex < 0) {
|
|
147
|
+
return {
|
|
148
|
+
nodes: afterInit,
|
|
149
|
+
headerEndIndex: initIndex + 1
|
|
150
|
+
};
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
return {
|
|
154
|
+
nodes: afterInit.slice(lastPipeIndex + 1),
|
|
155
|
+
headerEndIndex: recNode.children.indexOf(afterInit[lastPipeIndex]) + 1
|
|
156
|
+
};
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
function contentChildren(generatorNode: AstNode, expression: string, fromIndex: number): AstNode[] {
|
|
160
|
+
return generatorNode.children.slice(fromIndex).filter(child => !isClosingBrace(child, expression));
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
function findDeclarativeDomain(declNode: AstNode, expression: string): AstNode | null {
|
|
164
|
+
const inIndex = declNode.children.findIndex(child => isTextToken(child, expression, '∈'));
|
|
165
|
+
if (inIndex < 0) {
|
|
166
|
+
return null;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
for (let index = inIndex + 1; index < declNode.children.length; index++) {
|
|
170
|
+
const child = declNode.children[index];
|
|
171
|
+
if (isTextToken(child, expression, '|') || isClosingBrace(child, expression)) {
|
|
172
|
+
break;
|
|
173
|
+
}
|
|
174
|
+
if (child.typeID === Setexpr) {
|
|
175
|
+
return child;
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
return null;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
function findImperativeTuple(impNode: AstNode): AstNode | null {
|
|
183
|
+
for (const child of impNode.children) {
|
|
184
|
+
if (child.typeID === Setexpr) {
|
|
185
|
+
return child;
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
return null;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
function findLastPipeIndex(nodes: AstNode[], expression: string): number {
|
|
192
|
+
for (let index = nodes.length - 1; index >= 0; index--) {
|
|
193
|
+
if (isTextToken(nodes[index], expression, '|')) {
|
|
194
|
+
return index;
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
return -1;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
function generatorHeaderIsValid(generatorNode: AstNode, expression: string, headerEndIndex: number): boolean {
|
|
201
|
+
for (let index = 0; index < headerEndIndex; index++) {
|
|
202
|
+
const child = generatorNode.children[index];
|
|
203
|
+
if (child.typeID === TokenID.ERROR) {
|
|
204
|
+
return false;
|
|
205
|
+
}
|
|
206
|
+
if (isStructuralGeneratorToken(child, expression)) {
|
|
207
|
+
continue;
|
|
208
|
+
}
|
|
209
|
+
if (subtreeHasError(child)) {
|
|
210
|
+
return false;
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
return true;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
function isIncompleteDeclarativeBody(nodes: AstNode[]): boolean {
|
|
217
|
+
return isEmptyOrErrorOnlyBody(nodes);
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
function isIncompleteImperativeBody(slot: GeneratorBodySlot, expression: string): boolean {
|
|
221
|
+
if (slot.nodes.length === 0) {
|
|
222
|
+
return true;
|
|
223
|
+
}
|
|
224
|
+
if (slot.nodes.length === 1 && slot.nodes[0].typeID === Imp_blocks) {
|
|
225
|
+
return isImpBlocksIncomplete(slot.nodes[0], expression);
|
|
226
|
+
}
|
|
227
|
+
return isEmptyOrErrorOnlyBody(slot.nodes);
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
function isIncompleteRecursionBody(nodes: AstNode[]): boolean {
|
|
231
|
+
return isEmptyOrErrorOnlyBody(nodes);
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
function isEmptyOrErrorOnlyBody(nodes: AstNode[]): boolean {
|
|
235
|
+
if (nodes.length === 0) {
|
|
236
|
+
return true;
|
|
237
|
+
}
|
|
238
|
+
if (nodes.length === 1) {
|
|
239
|
+
return isErrorPlaceholder(nodes[0]);
|
|
240
|
+
}
|
|
241
|
+
return false;
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
function isImpBlocksIncomplete(blocksNode: AstNode, expression: string): boolean {
|
|
245
|
+
if (blocksNode.children.length === 0) {
|
|
246
|
+
return true;
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
const lastChild = blocksNode.children[blocksNode.children.length - 1];
|
|
250
|
+
if (lastChild.typeID === TokenID.ERROR) {
|
|
251
|
+
return true;
|
|
252
|
+
}
|
|
253
|
+
if (isTextToken(lastChild, expression, ';')) {
|
|
254
|
+
return true;
|
|
255
|
+
}
|
|
256
|
+
if (lastChild.typeID === Setexpr && isErrorPlaceholder(lastChild)) {
|
|
257
|
+
return true;
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
return false;
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
function isErrorInBodySlot(errorNode: AstNode, slot: GeneratorBodySlot, generatorNode: AstNode): boolean {
|
|
264
|
+
if (slot.nodes.length === 0) {
|
|
265
|
+
const bodyStart = generatorNode.children[slot.headerEndIndex]?.from ?? generatorNode.from;
|
|
266
|
+
return errorNode.from >= bodyStart;
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
for (const bodyNode of slot.nodes) {
|
|
270
|
+
if (errorNode === bodyNode || isDescendantOf(bodyNode, errorNode)) {
|
|
271
|
+
if (isErrorPlaceholder(bodyNode)) {
|
|
272
|
+
return true;
|
|
273
|
+
}
|
|
274
|
+
if (bodyNode.typeID === Imp_blocks && isImpBlocksTailError(bodyNode, errorNode)) {
|
|
275
|
+
return true;
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
return false;
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
function isImpBlocksTailError(blocksNode: AstNode, errorNode: AstNode): boolean {
|
|
284
|
+
const lastChild = blocksNode.children[blocksNode.children.length - 1];
|
|
285
|
+
return lastChild.typeID === TokenID.ERROR && (errorNode === lastChild || isDescendantOf(lastChild, errorNode));
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
function findRecursionInit(recursionNode: AstNode, expression: string): AstNode | null {
|
|
289
|
+
const assignIndex = recursionNode.children.findIndex(child => isTextToken(child, expression, ':='));
|
|
290
|
+
if (assignIndex < 0) {
|
|
291
|
+
return null;
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
for (let index = assignIndex + 1; index < recursionNode.children.length; index++) {
|
|
295
|
+
const child = recursionNode.children[index];
|
|
296
|
+
if (child.typeID === TokenID.ERROR) {
|
|
297
|
+
break;
|
|
298
|
+
}
|
|
299
|
+
if (isTextToken(child, expression, '|') || isClosingBrace(child, expression)) {
|
|
300
|
+
continue;
|
|
301
|
+
}
|
|
302
|
+
return child;
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
return null;
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
function isClosingBrace(node: AstNode, expression: string): boolean {
|
|
309
|
+
return node.typeID !== TokenID.ERROR && isTextToken(node, expression, '}');
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
function isStructuralGeneratorToken(node: AstNode, expression: string): boolean {
|
|
313
|
+
const text = expression.slice(node.from, node.to);
|
|
314
|
+
return text === '{' || text === '}' || text === '|' || text === '∈' || text === ':=' || text === ';';
|
|
315
|
+
}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Incomplete formal headers: `[α∈…]` declarations and quantifiers (`∀α∈…`).
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import type { AstNode } from '../../../parsing';
|
|
6
|
+
import { RSErrorCode, type RSErrorDescription } from '../../error';
|
|
7
|
+
import { TypeClass as TypeClassEnum } from '../../semantic/typification';
|
|
8
|
+
import { Arguments, Variable_pack } from '../parser.terms';
|
|
9
|
+
import { TokenID } from '../token';
|
|
10
|
+
|
|
11
|
+
import { findAncestor, isFunctionDeclNode, isQuantifierNode, subtreeHasError } from './ast-utils';
|
|
12
|
+
import type { ClassifyContext, ErrorClassifier } from './context';
|
|
13
|
+
import { detectTrailingErrorSlot } from './trailing-slot';
|
|
14
|
+
|
|
15
|
+
export const classifyIncompleteFormal: ErrorClassifier = (node, expression, context) =>
|
|
16
|
+
detectIncompleteFunctionDecl(node, expression, context.expected) ?? detectIncompleteQuantifier(node, expression);
|
|
17
|
+
|
|
18
|
+
function detectIncompleteFunctionDecl(
|
|
19
|
+
node: AstNode,
|
|
20
|
+
expression: string,
|
|
21
|
+
expected?: ClassifyContext['expected']
|
|
22
|
+
): RSErrorDescription | null {
|
|
23
|
+
return detectTrailingErrorSlot(node, expression, findAncestor(node, isFunctionDeclNode), {
|
|
24
|
+
headerValid: container => {
|
|
25
|
+
const argsNode = container.children.find(
|
|
26
|
+
child => child.typeID === Arguments || child.typeID === TokenID.NT_ARGUMENTS
|
|
27
|
+
);
|
|
28
|
+
return argsNode !== undefined && !subtreeHasError(argsNode);
|
|
29
|
+
},
|
|
30
|
+
code: () => incompleteFormalExpressionCode(expected)
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function incompleteFormalExpressionCode(expected?: ClassifyContext['expected']): RSErrorCode {
|
|
35
|
+
switch (expected) {
|
|
36
|
+
case TypeClassEnum.function:
|
|
37
|
+
return RSErrorCode.expectedExpressionBody;
|
|
38
|
+
case TypeClassEnum.predicate:
|
|
39
|
+
case TypeClassEnum.logic:
|
|
40
|
+
return RSErrorCode.expectedLogicBody;
|
|
41
|
+
default:
|
|
42
|
+
return RSErrorCode.expectedFunctionBody;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function detectIncompleteQuantifier(node: AstNode, expression: string): RSErrorDescription | null {
|
|
47
|
+
return detectTrailingErrorSlot(node, expression, findAncestor(node, isQuantifierNode), {
|
|
48
|
+
headerValid: container => {
|
|
49
|
+
const varPack = container.children.find(
|
|
50
|
+
child => child.typeID === Variable_pack || child.typeID === TokenID.NT_ENUM_DECL
|
|
51
|
+
);
|
|
52
|
+
return varPack !== undefined && !subtreeHasError(varPack);
|
|
53
|
+
},
|
|
54
|
+
code: (container, bodyChild) => {
|
|
55
|
+
const inIndex = container.children.findIndex(child => expression.slice(child.from, child.to) === '∈');
|
|
56
|
+
if (inIndex < 0) {
|
|
57
|
+
return RSErrorCode.expectedQuantifierDomain;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
const domainChild = container.children[inIndex + 1];
|
|
61
|
+
if (domainChild === undefined || domainChild === bodyChild || subtreeHasError(domainChild)) {
|
|
62
|
+
return RSErrorCode.expectedQuantifierDomain;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
return RSErrorCode.expectedQuantifierBody;
|
|
66
|
+
}
|
|
67
|
+
});
|
|
68
|
+
}
|