@rsconcept/domain 3.1.0 → 3.2.0
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-B35QsI3L.d.ts → analyzer-BcPMzR38.d.ts} +2 -2
- package/dist/{api-DgecYHjR.d.ts → api-BRrnB6yd.d.ts} +4 -2
- package/dist/{ast-annotations-DclU1Ser.d.ts → ast-annotations-B0XO6380.d.ts} +2 -2
- package/dist/{calculator-B-1p712X.d.ts → calculator-SVf1trzN.d.ts} +2 -2
- package/dist/cctext/index.js +1 -2
- package/dist/cctext/language-api.d.ts +11 -12
- package/dist/cctext/language.d.ts +13 -14
- package/dist/{diagnostics-D4_B7rt2.d.ts → diagnostics-DC6QECex.d.ts} +3 -3
- package/dist/{diagnostics-D1Cp21nP.js → diagnostics-D_4TpAH4.js} +4 -5
- package/dist/{diagnostics-D1Cp21nP.js.map → diagnostics-D_4TpAH4.js.map} +1 -1
- package/dist/{error-S1JghCNN.d.ts → error-DBbO_NIg.d.ts} +2 -1
- package/dist/graph/index.js +1 -2
- package/dist/{index-DzvllU3P.d.ts → index-CEOeehqz.d.ts} +5 -5
- package/dist/{index-D-w-bgei.d.ts → index-CEUOBGLn.d.ts} +6 -5
- package/dist/index.d.ts +8 -7
- package/dist/index.js +4 -3
- package/dist/library/diagnostics.d.ts +1 -1
- package/dist/library/diagnostics.js +1 -2
- package/dist/library/folder-tree.d.ts +2 -3
- package/dist/library/index.d.ts +5 -5
- package/dist/library/index.js +1 -1
- package/dist/library/library-api.d.ts +2 -3
- package/dist/library/oss-api.d.ts +5 -6
- package/dist/library/oss-api.js +2 -4
- package/dist/library/oss-api.js.map +1 -1
- package/dist/library/oss-layout-api.d.ts +4 -5
- package/dist/library/oss.d.ts +16 -16
- package/dist/library/oss.js +1 -1
- package/dist/library/rsengine.d.ts +6 -7
- package/dist/library/rsengine.js +23 -24
- package/dist/library/rsengine.js.map +1 -1
- package/dist/library/rsform-api/index.d.ts +1 -1
- package/dist/library/rsform-api/index.js +1 -2
- package/dist/library/rsform.d.ts +1 -1
- package/dist/library/rsmodel-api.d.ts +17 -18
- package/dist/library/rsmodel-api.js +6 -4
- package/dist/library/rsmodel-api.js.map +1 -1
- package/dist/library/rsmodel.d.ts +1 -1
- package/dist/library/structure-planner.d.ts +4 -5
- package/dist/library/structure-planner.js +1 -1
- package/dist/parsing/lezer-tree.js.map +1 -1
- package/dist/rslang/api.d.ts +2 -2
- package/dist/rslang/api.js +12 -7
- package/dist/rslang/api.js.map +1 -1
- package/dist/rslang/ast-annotations.d.ts +1 -1
- package/dist/rslang/error.d.ts +1 -1
- package/dist/rslang/error.js +1 -0
- package/dist/rslang/error.js.map +1 -1
- package/dist/rslang/eval/calculator.d.ts +1 -1
- package/dist/rslang/eval/calculator.js +1 -1
- package/dist/rslang/eval/evaluation-cache.d.ts +3 -4
- package/dist/rslang/eval/evaluation-cache.js +1 -3
- package/dist/rslang/eval/evaluation-cache.js.map +1 -1
- package/dist/rslang/eval/evaluator.d.ts +3 -4
- package/dist/rslang/eval/evaluator.js +5 -3
- package/dist/rslang/eval/evaluator.js.map +1 -1
- package/dist/rslang/eval/value-api.js +1 -2
- package/dist/rslang/eval/value.js +1 -2
- package/dist/rslang/index.d.ts +8 -7
- package/dist/rslang/index.js +3 -2
- package/dist/rslang/labels.d.ts +5 -6
- package/dist/rslang/parser/normalize.d.ts +1 -2
- package/dist/rslang/parser/normalize.js.map +1 -1
- package/dist/rslang/parser/parser.terms.d.ts +1 -2
- package/dist/rslang/parser/parser.terms.js.map +1 -1
- package/dist/rslang/parser/syntax-errors/index.d.ts +3 -4
- package/dist/rslang/parser/syntax-errors/index.js +1 -2
- package/dist/rslang/semantic/analyzer.d.ts +1 -1
- package/dist/rslang/semantic/analyzer.js +1 -1
- package/dist/rslang/semantic/arguments-extractor.js +6 -4
- package/dist/rslang/semantic/arguments-extractor.js.map +1 -1
- package/dist/rslang/semantic/type-auditor.d.ts +2 -3
- package/dist/rslang/semantic/type-auditor.js +20 -16
- package/dist/rslang/semantic/type-auditor.js.map +1 -1
- package/dist/rslang/semantic/typification-api.d.ts +10 -11
- package/dist/rslang/semantic/typification-api.js +10 -9
- package/dist/rslang/semantic/typification-api.js.map +1 -1
- package/dist/rslang/semantic/typification-parser.js +4 -1
- package/dist/rslang/semantic/typification-parser.js.map +1 -1
- package/dist/rslang/semantic/value-auditor.d.ts +2 -3
- package/dist/shared/index.js +1 -2
- package/dist/{syntax-errors-BN_bqTaX.js → syntax-errors-WGLQNaNe.js} +19 -4
- package/dist/{syntax-errors-BN_bqTaX.js.map → syntax-errors-WGLQNaNe.js.map} +1 -1
- package/dist/{value-COprb2Je.js → value-BDISzQqI.js} +3 -2
- package/dist/{value-COprb2Je.js.map → value-BDISzQqI.js.map} +1 -1
- package/package.json +24 -25
- package/src/index.ts +2 -1
- package/src/library/rsengine.test.ts +164 -0
- package/src/library/rsengine.ts +25 -24
- package/src/rslang/api.ts +3 -0
- package/src/rslang/error.ts +1 -0
- package/src/rslang/index.ts +2 -1
- package/src/rslang/parser/parser.test.ts +38 -0
- package/src/rslang/parser/syntax-errors/README.md +2 -2
- package/src/rslang/parser/syntax-errors/expression-errors.ts +18 -0
- package/src/rslang/parser/syntax-errors/index.ts +11 -4
package/src/index.ts
CHANGED
|
@@ -80,8 +80,9 @@ export {
|
|
|
80
80
|
visitAstDFS
|
|
81
81
|
} from './parsing/ast';
|
|
82
82
|
export { type CMSyntaxNode, printTree } from './parsing/lezer-tree';
|
|
83
|
+
export { DELETED_ALIAS } from './rslang/api';
|
|
83
84
|
export { readErrorAnnotation, readTypeAnnotation } from './rslang/ast-annotations';
|
|
84
|
-
export { type EvalStackFrame, formatEvalCallStack,RSErrorCode, type RSErrorDescription } from './rslang/error';
|
|
85
|
+
export { type EvalStackFrame, formatEvalCallStack, RSErrorCode, type RSErrorDescription } from './rslang/error';
|
|
85
86
|
export { type CalculatorEvaluateOptions, type CalculatorResult, RSCalculator } from './rslang/eval/calculator';
|
|
86
87
|
export { makeValuePath, TUPLE_ID, type Value, type ValuePath } from './rslang/eval/value';
|
|
87
88
|
export { printValue } from './rslang/eval/value-api';
|
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest';
|
|
2
|
+
|
|
3
|
+
import { Graph } from '../graph';
|
|
4
|
+
import { RSLangAnalyzer } from '../rslang/semantic/analyzer';
|
|
5
|
+
import { basic } from '../rslang/semantic/typification';
|
|
6
|
+
|
|
7
|
+
import { RSEngine } from './rsengine';
|
|
8
|
+
import { type Constituenta, CstClass, CstType, type RSForm } from './rsform';
|
|
9
|
+
import { EvalStatus, type RSModel, TYPE_BASIC } from './rsmodel';
|
|
10
|
+
|
|
11
|
+
function mockConstituenta(
|
|
12
|
+
partial: Pick<Constituenta, 'id' | 'alias' | 'cst_type'> & Partial<Constituenta>
|
|
13
|
+
): Constituenta {
|
|
14
|
+
return {
|
|
15
|
+
schema: 1,
|
|
16
|
+
crucial: false,
|
|
17
|
+
convention: '',
|
|
18
|
+
definition_formal: '',
|
|
19
|
+
definition_raw: '',
|
|
20
|
+
definition_resolved: '',
|
|
21
|
+
term_raw: '',
|
|
22
|
+
term_resolved: '',
|
|
23
|
+
term_forms: [],
|
|
24
|
+
typification_manual: '',
|
|
25
|
+
value_is_property: false,
|
|
26
|
+
diagnostics: [],
|
|
27
|
+
analysis: { success: true } as Constituenta['analysis'],
|
|
28
|
+
effectiveType: null,
|
|
29
|
+
is_type_mismatch: false,
|
|
30
|
+
status: 'verified',
|
|
31
|
+
is_template: false,
|
|
32
|
+
is_simple_expression: true,
|
|
33
|
+
parent_schema_index: 0,
|
|
34
|
+
parent_schema: null,
|
|
35
|
+
is_inherited: false,
|
|
36
|
+
has_inherited_children: false,
|
|
37
|
+
attributes: [],
|
|
38
|
+
spawn: [],
|
|
39
|
+
spawn_alias: [],
|
|
40
|
+
cst_class: CstClass.BASIC,
|
|
41
|
+
...partial
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function mockSchema(items: Constituenta[]): RSForm {
|
|
46
|
+
const analyzer = new RSLangAnalyzer();
|
|
47
|
+
for (const item of items) {
|
|
48
|
+
if (item.cst_type === CstType.BASE) {
|
|
49
|
+
analyzer.addBase(item.alias);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
return {
|
|
53
|
+
id: 1,
|
|
54
|
+
items,
|
|
55
|
+
analyzer,
|
|
56
|
+
graph: new Graph(items.map(item => [item.id])),
|
|
57
|
+
cstByAlias: new Map(items.map(item => [item.alias, item])),
|
|
58
|
+
cstByID: new Map(items.map(item => [item.id, item]))
|
|
59
|
+
} as RSForm;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function mockModel(items: RSModel['items']): RSModel {
|
|
63
|
+
return { id: 10, schema: 1, items } as RSModel;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function createEngine(): RSEngine {
|
|
67
|
+
return new RSEngine(10, {
|
|
68
|
+
setCstValue: () => Promise.resolve(),
|
|
69
|
+
clearValues: () => Promise.resolve()
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
describe('RSEngine.setBasicValue', () => {
|
|
74
|
+
it('awaits setCstValue before clearValues when dependents must reset', async () => {
|
|
75
|
+
const x1 = mockConstituenta({ id: 1, alias: 'X1', cst_type: CstType.BASE });
|
|
76
|
+
const s1 = mockConstituenta({
|
|
77
|
+
id: 2,
|
|
78
|
+
alias: 'S1',
|
|
79
|
+
cst_type: CstType.STRUCTURED,
|
|
80
|
+
cst_class: CstClass.BASIC,
|
|
81
|
+
// Non-number value cannot be reconciled with a basic domain shrink, so it must be cleared.
|
|
82
|
+
effectiveType: basic('X1')
|
|
83
|
+
});
|
|
84
|
+
const order: string[] = [];
|
|
85
|
+
let releaseSet!: () => void;
|
|
86
|
+
const setGate = new Promise<void>(resolve => {
|
|
87
|
+
releaseSet = resolve;
|
|
88
|
+
});
|
|
89
|
+
const engine = new RSEngine(10, {
|
|
90
|
+
setCstValue: async () => {
|
|
91
|
+
order.push('set-start');
|
|
92
|
+
await setGate;
|
|
93
|
+
order.push('set-end');
|
|
94
|
+
},
|
|
95
|
+
clearValues: () => {
|
|
96
|
+
order.push('clear');
|
|
97
|
+
return Promise.resolve();
|
|
98
|
+
}
|
|
99
|
+
});
|
|
100
|
+
const schema = mockSchema([x1, s1]);
|
|
101
|
+
schema.graph = new Graph([[1], [2], [1, 2]]);
|
|
102
|
+
engine.loadData(
|
|
103
|
+
schema,
|
|
104
|
+
mockModel([
|
|
105
|
+
{ id: 1, type: TYPE_BASIC, value: { 1: 'a', 2: 'b' } },
|
|
106
|
+
{ id: 2, type: 'X1', value: [1, 2] }
|
|
107
|
+
])
|
|
108
|
+
);
|
|
109
|
+
|
|
110
|
+
const pending = engine.setBasicValue(1, { 9: 'only-new' });
|
|
111
|
+
expect(order).toEqual(['set-start']);
|
|
112
|
+
releaseSet();
|
|
113
|
+
await pending;
|
|
114
|
+
expect(order).toEqual(['set-start', 'set-end', 'clear']);
|
|
115
|
+
});
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
describe('RSEngine.loadData', () => {
|
|
119
|
+
it('applies basic bindings for constituents that exist in the schema', () => {
|
|
120
|
+
const x1 = mockConstituenta({ id: 1, alias: 'X1', cst_type: CstType.BASE });
|
|
121
|
+
const engine = createEngine();
|
|
122
|
+
|
|
123
|
+
engine.loadData(mockSchema([x1]), mockModel([{ id: 1, type: TYPE_BASIC, value: { 1: 'a', 2: 'b' } }]));
|
|
124
|
+
|
|
125
|
+
expect(engine.basics.get(1)).toEqual({ 1: 'a', 2: 'b' });
|
|
126
|
+
expect(engine.getCstValue(1)).toEqual([1, 2]);
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
it('skips value items whose constituent is missing from the schema', () => {
|
|
130
|
+
const x1 = mockConstituenta({ id: 1, alias: 'X1', cst_type: CstType.BASE });
|
|
131
|
+
const engine = createEngine();
|
|
132
|
+
|
|
133
|
+
expect(() =>
|
|
134
|
+
engine.loadData(
|
|
135
|
+
mockSchema([x1]),
|
|
136
|
+
mockModel([
|
|
137
|
+
{ id: 1, type: TYPE_BASIC, value: { 1: 'a' } },
|
|
138
|
+
{ id: 35772, type: 'ℬ(X1)', value: [1] }
|
|
139
|
+
])
|
|
140
|
+
)
|
|
141
|
+
).not.toThrow();
|
|
142
|
+
|
|
143
|
+
expect(engine.basics.get(1)).toEqual({ 1: 'a' });
|
|
144
|
+
expect(engine.getCstValue(1)).toEqual([1]);
|
|
145
|
+
expect(engine.getCstValue(35772)).toBeNull();
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
it('skips basic bindings attached to a non-base constituent', () => {
|
|
149
|
+
const s1 = mockConstituenta({
|
|
150
|
+
id: 2,
|
|
151
|
+
alias: 'S1',
|
|
152
|
+
cst_type: CstType.STRUCTURED,
|
|
153
|
+
cst_class: CstClass.BASIC
|
|
154
|
+
});
|
|
155
|
+
const engine = createEngine();
|
|
156
|
+
|
|
157
|
+
expect(() =>
|
|
158
|
+
engine.loadData(mockSchema([s1]), mockModel([{ id: 2, type: TYPE_BASIC, value: { 1: 'a' } }]))
|
|
159
|
+
).not.toThrow();
|
|
160
|
+
|
|
161
|
+
expect(engine.basics.size).toBe(0);
|
|
162
|
+
expect(engine.getCstStatus(2)).toBe(EvalStatus.INVALID_DATA);
|
|
163
|
+
});
|
|
164
|
+
});
|
package/src/library/rsengine.ts
CHANGED
|
@@ -62,8 +62,8 @@ export class RSEngine {
|
|
|
62
62
|
this.onChangeDefinitions(changedCst);
|
|
63
63
|
}
|
|
64
64
|
if (this.data !== model) {
|
|
65
|
+
this.prepareValues(model);
|
|
65
66
|
this.data = model;
|
|
66
|
-
this.prepareValues();
|
|
67
67
|
}
|
|
68
68
|
this.notifyAll();
|
|
69
69
|
}
|
|
@@ -225,13 +225,11 @@ export class RSEngine {
|
|
|
225
225
|
}
|
|
226
226
|
}
|
|
227
227
|
|
|
228
|
+
// Sequential writes: both endpoints share the model concurrency token.
|
|
229
|
+
// A parallel pair would send the same expected time_update and the second would 409.
|
|
230
|
+
await this.services.setCstValue({ itemID: this.modelID, data: updateList });
|
|
228
231
|
if (resetList.length > 0) {
|
|
229
|
-
await
|
|
230
|
-
this.services.setCstValue({ itemID: this.modelID, data: updateList }),
|
|
231
|
-
this.services.clearValues({ itemID: this.modelID, data: { items: resetList } })
|
|
232
|
-
]);
|
|
233
|
-
} else {
|
|
234
|
-
await this.services.setCstValue({ itemID: this.modelID, data: updateList });
|
|
232
|
+
await this.services.clearValues({ itemID: this.modelID, data: { items: resetList } });
|
|
235
233
|
}
|
|
236
234
|
const changed = [...resetList, ...updateList.map(item => item.target)];
|
|
237
235
|
|
|
@@ -386,29 +384,32 @@ export class RSEngine {
|
|
|
386
384
|
}
|
|
387
385
|
}
|
|
388
386
|
|
|
389
|
-
private prepareValues(): void {
|
|
387
|
+
private prepareValues(model: RSModel): void {
|
|
390
388
|
this.basics.clear();
|
|
391
389
|
this.invalidData.clear();
|
|
392
390
|
this.calculatedSet.clear();
|
|
393
391
|
|
|
394
|
-
for (const item of
|
|
395
|
-
const cst = this.schema
|
|
396
|
-
if (item.type
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
this.calculator.setValue(cst.alias, Object.keys(data).map(Number));
|
|
392
|
+
for (const item of model.items) {
|
|
393
|
+
const cst = this.schema?.cstByID.get(item.id);
|
|
394
|
+
if (!cst || item.type !== TYPE_BASIC) {
|
|
395
|
+
continue;
|
|
396
|
+
}
|
|
397
|
+
if (!isBaseSet(cst.cst_type)) {
|
|
398
|
+
this.invalidData.add(item.id);
|
|
399
|
+
continue;
|
|
403
400
|
}
|
|
401
|
+
const data = item.value as BasicBinding;
|
|
402
|
+
this.basics.set(cst.id, data);
|
|
403
|
+
this.calculator.setValue(cst.alias, Object.keys(data).map(Number));
|
|
404
404
|
}
|
|
405
|
-
for (const item of
|
|
406
|
-
const cst = this.schema
|
|
407
|
-
if (item.type
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
405
|
+
for (const item of model.items) {
|
|
406
|
+
const cst = this.schema?.cstByID.get(item.id);
|
|
407
|
+
if (!cst || item.type === TYPE_BASIC) {
|
|
408
|
+
continue;
|
|
409
|
+
}
|
|
410
|
+
this.calculator.setValue(cst.alias, item.value as Value);
|
|
411
|
+
if (!cst.effectiveType || !this.calculator.validate(item.value as Value, cst.effectiveType)) {
|
|
412
|
+
this.invalidData.add(item.id);
|
|
412
413
|
}
|
|
413
414
|
}
|
|
414
415
|
this.setupEmptySets();
|
package/src/rslang/api.ts
CHANGED
|
@@ -8,6 +8,9 @@ import { parser as rslangParser } from './parser/parser';
|
|
|
8
8
|
import { ArgumentsExtractor, type ArgumentsType } from './semantic/arguments-extractor';
|
|
9
9
|
export { generateExpressionFromAst } from './parser/expression-generator';
|
|
10
10
|
|
|
11
|
+
/** Placeholder alias written into expressions in place of references to deleted constituents. */
|
|
12
|
+
export const DELETED_ALIAS = 'DEL';
|
|
13
|
+
|
|
11
14
|
/** Represents alias mapping. */
|
|
12
15
|
export type AliasMapping = Record<string, string>;
|
|
13
16
|
|
package/src/rslang/error.ts
CHANGED
|
@@ -105,6 +105,7 @@ export const RSErrorCode = {
|
|
|
105
105
|
invalidCard: 0x8808, // 34824
|
|
106
106
|
invalidDebool: 0x8809, // 34825
|
|
107
107
|
globalFuncWithoutArgs: 0x880b, // 34827
|
|
108
|
+
globalDeleted: 0x880c, // 34828
|
|
108
109
|
invalidReduce: 0x8810, // 34832
|
|
109
110
|
invalidEnumeration: 0x8813, // 34835
|
|
110
111
|
projectionSetArgumentNotSet: 0x8830, // 34864
|
package/src/rslang/index.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
|
+
export { DELETED_ALIAS } from './api';
|
|
1
2
|
export { readErrorAnnotation, readTypeAnnotation } from './ast-annotations';
|
|
2
|
-
export { type EvalStackFrame, formatEvalCallStack,RSErrorCode, type RSErrorDescription } from './error';
|
|
3
|
+
export { type EvalStackFrame, formatEvalCallStack, RSErrorCode, type RSErrorDescription } from './error';
|
|
3
4
|
export { type CalculatorEvaluateOptions, type CalculatorResult, RSCalculator } from './eval/calculator';
|
|
4
5
|
export { makeValuePath, type Value, type ValuePath } from './eval/value';
|
|
5
6
|
export { printValue } from './eval/value-api';
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { describe, expect, it } from 'vitest';
|
|
2
2
|
|
|
3
3
|
import { buildTree, printTree } from '../../parsing';
|
|
4
|
+
import { DELETED_ALIAS } from '../api';
|
|
4
5
|
import { RSErrorCode, type RSErrorDescription } from '../error';
|
|
5
6
|
import { TypeClass } from '../semantic/typification';
|
|
6
7
|
|
|
@@ -68,6 +69,10 @@ const testError = [
|
|
|
68
69
|
['!', '[Expression[⚠]]']
|
|
69
70
|
];
|
|
70
71
|
|
|
72
|
+
function deletedReferenceAt(from: number): RSErrorDescription {
|
|
73
|
+
return { code: RSErrorCode.globalDeleted, from, to: from + DELETED_ALIAS.length, params: [DELETED_ALIAS] };
|
|
74
|
+
}
|
|
75
|
+
|
|
71
76
|
const testErrorData = [
|
|
72
77
|
['(', { code: RSErrorCode.missingCloseBracket, from: 0, to: 1, params: ['('] }],
|
|
73
78
|
['{X1', { code: RSErrorCode.missingCloseBracket, from: 0, to: 1, params: ['{'] }],
|
|
@@ -86,6 +91,10 @@ const testErrorData = [
|
|
|
86
91
|
['?', { code: RSErrorCode.forbiddenCharacter, from: 0, to: 1, params: ['?'] }],
|
|
87
92
|
['16=2^4', { code: RSErrorCode.forbiddenCharacter, from: 4, to: 5, params: ['^'] }],
|
|
88
93
|
['16=2/4', { code: RSErrorCode.forbiddenCharacter, from: 4, to: 5, params: ['/'] }],
|
|
94
|
+
[DELETED_ALIAS, deletedReferenceAt(0)],
|
|
95
|
+
[`X1∪${DELETED_ALIAS}`, deletedReferenceAt(3)],
|
|
96
|
+
[`F1[${DELETED_ALIAS}]`, deletedReferenceAt(3)],
|
|
97
|
+
[`∀ξ∈${DELETED_ALIAS} ξ∈X1`, deletedReferenceAt(3)],
|
|
89
98
|
['Fi1(S1)', { code: RSErrorCode.invalidFilterSyntax, from: 0, to: 4 }],
|
|
90
99
|
['[α∈ℬ(X1×X1)]', { code: RSErrorCode.expectedFunctionBody, from: 12, to: 12 }]
|
|
91
100
|
];
|
|
@@ -283,6 +292,35 @@ describe('Testing RSParser error data', () => {
|
|
|
283
292
|
});
|
|
284
293
|
});
|
|
285
294
|
|
|
295
|
+
it('Reports every reference to a deleted constituent instead of syntax errors', () => {
|
|
296
|
+
const input = `${DELETED_ALIAS}∪X1 = ${DELETED_ALIAS}^2`;
|
|
297
|
+
const tree = parser.parse(input);
|
|
298
|
+
const ast = buildTree(tree.cursor());
|
|
299
|
+
const errors: RSErrorDescription[] = [];
|
|
300
|
+
extractSyntaxErrors(ast, input, error => errors.push(error));
|
|
301
|
+
|
|
302
|
+
expect(errors).toEqual([
|
|
303
|
+
deletedReferenceAt(0),
|
|
304
|
+
deletedReferenceAt(input.lastIndexOf(DELETED_ALIAS)),
|
|
305
|
+
{
|
|
306
|
+
code: RSErrorCode.forbiddenCharacter,
|
|
307
|
+
from: input.indexOf('^'),
|
|
308
|
+
to: input.indexOf('^') + 1,
|
|
309
|
+
params: ['^']
|
|
310
|
+
}
|
|
311
|
+
]);
|
|
312
|
+
});
|
|
313
|
+
|
|
314
|
+
it('Does not treat identifiers containing the deleted alias as deleted references', () => {
|
|
315
|
+
const input = `${DELETED_ALIAS}X1`;
|
|
316
|
+
const tree = parser.parse(input);
|
|
317
|
+
const ast = buildTree(tree.cursor());
|
|
318
|
+
const errors: RSErrorDescription[] = [];
|
|
319
|
+
extractSyntaxErrors(ast, input, error => errors.push(error));
|
|
320
|
+
|
|
321
|
+
expect(errors.some(error => error.code === RSErrorCode.globalDeleted)).toBe(false);
|
|
322
|
+
});
|
|
323
|
+
|
|
286
324
|
it('Reports a single forbidden-character error for exponentiation', () => {
|
|
287
325
|
const input = '[σ∈S2]I{γ | ξ:∈ℬ(σ); γ:=(ξ×{1})∪((σ\\ξ)×{0}); 16=2^4}';
|
|
288
326
|
const tree = parser.parse(input);
|
|
@@ -8,7 +8,7 @@ Classifiers must handle both grammar terms (`parser.terms`) and `TokenID` values
|
|
|
8
8
|
|
|
9
9
|
## Pipeline (`index.ts`)
|
|
10
10
|
|
|
11
|
-
1. **
|
|
11
|
+
1. **Text-level checks** — scan the source string for references to deleted constituents (`DELETED_ALIAS`, semantic code `globalDeleted`) and forbidden characters; if any are found, report only those errors and stop.
|
|
12
12
|
2. **AST walk** — for each `ERROR` node, run classifiers (`classify.ts`) or fall back to `unknownSyntax` / `expectedLocal`.
|
|
13
13
|
3. **Bracket pass** — one expression-level bracket diagnostic (`missingCloseBracket`, `bracketMismatch`, …), unless an incomplete D/I/R body was already reported (generator may legally end with `}` while brackets look wrong).
|
|
14
14
|
4. **Deduplication** — drop overlaps; prefer specific codes over `unknownSyntax`; keep a single `unknownSyntax` when several ERROR nodes fire.
|
|
@@ -54,7 +54,7 @@ Fallback: `expectedLocal` under `Variable`; otherwise `unknownSyntax` (`0x8400`)
|
|
|
54
54
|
| `trailing-slot.ts` | Reusable incomplete-trailing-body detector |
|
|
55
55
|
| `ast-utils.ts` | Tree navigation, node predicates, bracket matching |
|
|
56
56
|
| `error-builders.ts` | `errorAt`, text-token helpers |
|
|
57
|
-
| `expression-errors.ts` |
|
|
57
|
+
| `expression-errors.ts` | Deleted references, forbidden chars, brackets, dedup |
|
|
58
58
|
| `incomplete-formal.ts` | `[…]` declarations, quantifiers |
|
|
59
59
|
| `incomplete-operands.ts` | Operators, `ℬ()`, bracket argument lists |
|
|
60
60
|
| `generators.ts` | D{…}, I{…}, R{…} incomplete bodies |
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
* Expression-level diagnostics (no AST required) and error deduplication.
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
|
+
import { DELETED_ALIAS } from '../../api';
|
|
5
6
|
import { RSErrorCode, type RSErrorDescription } from '../../error';
|
|
6
7
|
|
|
7
8
|
type OpenBracket = '(' | '[' | '{';
|
|
@@ -64,6 +65,23 @@ export function extractForbiddenCharacterErrors(expression: string): RSErrorDesc
|
|
|
64
65
|
return errors;
|
|
65
66
|
}
|
|
66
67
|
|
|
68
|
+
/** Matches {@link DELETED_ALIAS} as a standalone identifier (not a part of a longer name). */
|
|
69
|
+
const DELETED_ALIAS_REGEXP = new RegExp(`(?<![\\p{L}\\p{N}_])${DELETED_ALIAS}(?![\\p{L}\\p{N}_])`, 'gu');
|
|
70
|
+
|
|
71
|
+
/** Reports every reference to a deleted constituent ({@link DELETED_ALIAS}) in the expression. */
|
|
72
|
+
export function extractDeletedReferenceErrors(expression: string): RSErrorDescription[] {
|
|
73
|
+
const errors: RSErrorDescription[] = [];
|
|
74
|
+
for (const match of expression.matchAll(DELETED_ALIAS_REGEXP)) {
|
|
75
|
+
errors.push({
|
|
76
|
+
code: RSErrorCode.globalDeleted,
|
|
77
|
+
from: match.index,
|
|
78
|
+
to: match.index + match[0].length,
|
|
79
|
+
params: [match[0]]
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
return errors;
|
|
83
|
+
}
|
|
84
|
+
|
|
67
85
|
export function extractBracketErrors(expression: string): RSErrorDescription | null {
|
|
68
86
|
const stack: BracketFrame[] = [];
|
|
69
87
|
|
|
@@ -14,7 +14,12 @@ import { type TypeClass } from '../../semantic/typification';
|
|
|
14
14
|
import { TokenID } from '../token';
|
|
15
15
|
|
|
16
16
|
import { classifyFallbackError, classifyParseError } from './classify';
|
|
17
|
-
import {
|
|
17
|
+
import {
|
|
18
|
+
deduplicateErrors,
|
|
19
|
+
extractBracketErrors,
|
|
20
|
+
extractDeletedReferenceErrors,
|
|
21
|
+
extractForbiddenCharacterErrors
|
|
22
|
+
} from './expression-errors';
|
|
18
23
|
import { isIncompleteGeneratorBodyCode } from './generators';
|
|
19
24
|
|
|
20
25
|
/** When set, refines incomplete formal headers (`[α∈…]`, quantifiers). */
|
|
@@ -38,9 +43,11 @@ export function extractSyntaxErrors(
|
|
|
38
43
|
annotateErrors: boolean = false,
|
|
39
44
|
options?: SyntaxErrorOptions
|
|
40
45
|
) {
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
46
|
+
// Deleted references and forbidden characters make the AST meaningless: report only them.
|
|
47
|
+
const textErrors = [...extractDeletedReferenceErrors(expression), ...extractForbiddenCharacterErrors(expression)];
|
|
48
|
+
if (textErrors.length > 0) {
|
|
49
|
+
textErrors.sort((left, right) => left.from - right.from);
|
|
50
|
+
for (const error of textErrors) {
|
|
44
51
|
reporter(error);
|
|
45
52
|
if (annotateErrors) {
|
|
46
53
|
annotateError(ast, error.code, error.params);
|