@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.
Files changed (96) hide show
  1. package/dist/{analyzer-B35QsI3L.d.ts → analyzer-BcPMzR38.d.ts} +2 -2
  2. package/dist/{api-DgecYHjR.d.ts → api-BRrnB6yd.d.ts} +4 -2
  3. package/dist/{ast-annotations-DclU1Ser.d.ts → ast-annotations-B0XO6380.d.ts} +2 -2
  4. package/dist/{calculator-B-1p712X.d.ts → calculator-SVf1trzN.d.ts} +2 -2
  5. package/dist/cctext/index.js +1 -2
  6. package/dist/cctext/language-api.d.ts +11 -12
  7. package/dist/cctext/language.d.ts +13 -14
  8. package/dist/{diagnostics-D4_B7rt2.d.ts → diagnostics-DC6QECex.d.ts} +3 -3
  9. package/dist/{diagnostics-D1Cp21nP.js → diagnostics-D_4TpAH4.js} +4 -5
  10. package/dist/{diagnostics-D1Cp21nP.js.map → diagnostics-D_4TpAH4.js.map} +1 -1
  11. package/dist/{error-S1JghCNN.d.ts → error-DBbO_NIg.d.ts} +2 -1
  12. package/dist/graph/index.js +1 -2
  13. package/dist/{index-DzvllU3P.d.ts → index-CEOeehqz.d.ts} +5 -5
  14. package/dist/{index-D-w-bgei.d.ts → index-CEUOBGLn.d.ts} +6 -5
  15. package/dist/index.d.ts +8 -7
  16. package/dist/index.js +4 -3
  17. package/dist/library/diagnostics.d.ts +1 -1
  18. package/dist/library/diagnostics.js +1 -2
  19. package/dist/library/folder-tree.d.ts +2 -3
  20. package/dist/library/index.d.ts +5 -5
  21. package/dist/library/index.js +1 -1
  22. package/dist/library/library-api.d.ts +2 -3
  23. package/dist/library/oss-api.d.ts +5 -6
  24. package/dist/library/oss-api.js +2 -4
  25. package/dist/library/oss-api.js.map +1 -1
  26. package/dist/library/oss-layout-api.d.ts +4 -5
  27. package/dist/library/oss.d.ts +16 -16
  28. package/dist/library/oss.js +1 -1
  29. package/dist/library/rsengine.d.ts +6 -7
  30. package/dist/library/rsengine.js +23 -24
  31. package/dist/library/rsengine.js.map +1 -1
  32. package/dist/library/rsform-api/index.d.ts +1 -1
  33. package/dist/library/rsform-api/index.js +1 -2
  34. package/dist/library/rsform.d.ts +1 -1
  35. package/dist/library/rsmodel-api.d.ts +17 -18
  36. package/dist/library/rsmodel-api.js +6 -4
  37. package/dist/library/rsmodel-api.js.map +1 -1
  38. package/dist/library/rsmodel.d.ts +1 -1
  39. package/dist/library/structure-planner.d.ts +4 -5
  40. package/dist/library/structure-planner.js +1 -1
  41. package/dist/parsing/lezer-tree.js.map +1 -1
  42. package/dist/rslang/api.d.ts +2 -2
  43. package/dist/rslang/api.js +12 -7
  44. package/dist/rslang/api.js.map +1 -1
  45. package/dist/rslang/ast-annotations.d.ts +1 -1
  46. package/dist/rslang/error.d.ts +1 -1
  47. package/dist/rslang/error.js +1 -0
  48. package/dist/rslang/error.js.map +1 -1
  49. package/dist/rslang/eval/calculator.d.ts +1 -1
  50. package/dist/rslang/eval/calculator.js +1 -1
  51. package/dist/rslang/eval/evaluation-cache.d.ts +3 -4
  52. package/dist/rslang/eval/evaluation-cache.js +1 -3
  53. package/dist/rslang/eval/evaluation-cache.js.map +1 -1
  54. package/dist/rslang/eval/evaluator.d.ts +3 -4
  55. package/dist/rslang/eval/evaluator.js +5 -3
  56. package/dist/rslang/eval/evaluator.js.map +1 -1
  57. package/dist/rslang/eval/value-api.js +1 -2
  58. package/dist/rslang/eval/value.js +1 -2
  59. package/dist/rslang/index.d.ts +8 -7
  60. package/dist/rslang/index.js +3 -2
  61. package/dist/rslang/labels.d.ts +5 -6
  62. package/dist/rslang/parser/normalize.d.ts +1 -2
  63. package/dist/rslang/parser/normalize.js.map +1 -1
  64. package/dist/rslang/parser/parser.terms.d.ts +1 -2
  65. package/dist/rslang/parser/parser.terms.js.map +1 -1
  66. package/dist/rslang/parser/syntax-errors/index.d.ts +3 -4
  67. package/dist/rslang/parser/syntax-errors/index.js +1 -2
  68. package/dist/rslang/semantic/analyzer.d.ts +1 -1
  69. package/dist/rslang/semantic/analyzer.js +1 -1
  70. package/dist/rslang/semantic/arguments-extractor.js +6 -4
  71. package/dist/rslang/semantic/arguments-extractor.js.map +1 -1
  72. package/dist/rslang/semantic/type-auditor.d.ts +2 -3
  73. package/dist/rslang/semantic/type-auditor.js +20 -16
  74. package/dist/rslang/semantic/type-auditor.js.map +1 -1
  75. package/dist/rslang/semantic/typification-api.d.ts +10 -11
  76. package/dist/rslang/semantic/typification-api.js +10 -9
  77. package/dist/rslang/semantic/typification-api.js.map +1 -1
  78. package/dist/rslang/semantic/typification-parser.js +4 -1
  79. package/dist/rslang/semantic/typification-parser.js.map +1 -1
  80. package/dist/rslang/semantic/value-auditor.d.ts +2 -3
  81. package/dist/shared/index.js +1 -2
  82. package/dist/{syntax-errors-BN_bqTaX.js → syntax-errors-WGLQNaNe.js} +19 -4
  83. package/dist/{syntax-errors-BN_bqTaX.js.map → syntax-errors-WGLQNaNe.js.map} +1 -1
  84. package/dist/{value-COprb2Je.js → value-BDISzQqI.js} +3 -2
  85. package/dist/{value-COprb2Je.js.map → value-BDISzQqI.js.map} +1 -1
  86. package/package.json +24 -25
  87. package/src/index.ts +2 -1
  88. package/src/library/rsengine.test.ts +164 -0
  89. package/src/library/rsengine.ts +25 -24
  90. package/src/rslang/api.ts +3 -0
  91. package/src/rslang/error.ts +1 -0
  92. package/src/rslang/index.ts +2 -1
  93. package/src/rslang/parser/parser.test.ts +38 -0
  94. package/src/rslang/parser/syntax-errors/README.md +2 -2
  95. package/src/rslang/parser/syntax-errors/expression-errors.ts +18 -0
  96. 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
+ });
@@ -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 Promise.all([
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 this.data!.items) {
395
- const cst = this.schema!.cstByID.get(item.id)!;
396
- if (item.type === TYPE_BASIC) {
397
- if (cst.cst_type !== CstType.BASE && cst.cst_type !== CstType.CONSTANT) {
398
- throw new Error(`Invalid data for ${cst.alias}`);
399
- }
400
- const data = item.value as BasicBinding;
401
- this.basics.set(cst.id, data);
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 this.data!.items) {
406
- const cst = this.schema!.cstByID.get(item.id)!;
407
- if (item.type !== TYPE_BASIC) {
408
- this.calculator.setValue(cst.alias, item.value as Value);
409
- if (!cst.effectiveType || !this.calculator.validate(item.value as Value, cst.effectiveType)) {
410
- this.invalidData.add(item.id);
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
 
@@ -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
@@ -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. **Forbidden characters** — scan the source string; if any illegal symbol is found, report only those errors and stop.
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` | Forbidden chars, brackets, dedup |
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 { deduplicateErrors, extractBracketErrors, extractForbiddenCharacterErrors } from './expression-errors';
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
- const forbiddenErrors = extractForbiddenCharacterErrors(expression);
42
- if (forbiddenErrors.length > 0) {
43
- for (const error of forbiddenErrors) {
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);