@rsconcept/domain 1.6.0 → 1.7.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.
@@ -7,7 +7,21 @@ import { annotateError } from '../ast-annotations';
7
7
  import { type ErrorReporter, RSErrorCode, type RSErrorDescription } from '../error';
8
8
  import { type TypeClass, TypeClass as TypeClassEnum } from '../semantic/typification';
9
9
 
10
- import { Arguments, Filter, Function, Function_decl, Predicate, Variable } from './parser.terms';
10
+ import {
11
+ Arguments,
12
+ Declarative,
13
+ Filter,
14
+ Function,
15
+ Function_decl,
16
+ Imp_blocks,
17
+ Imperative,
18
+ Logic_quantor,
19
+ Predicate,
20
+ Recursion,
21
+ Setexpr,
22
+ Variable,
23
+ Variable_pack
24
+ } from './parser.terms';
11
25
  import { TokenID } from './token';
12
26
 
13
27
  /** Options for syntax error extraction. */
@@ -23,6 +37,17 @@ export function extractSyntaxErrors(
23
37
  annotateErrors: boolean = false,
24
38
  options?: SyntaxErrorOptions
25
39
  ) {
40
+ const forbiddenErrors = extractForbiddenCharacterErrors(expression);
41
+ if (forbiddenErrors.length > 0) {
42
+ for (const error of forbiddenErrors) {
43
+ reporter(error);
44
+ if (annotateErrors) {
45
+ annotateError(ast, error.code, error.params);
46
+ }
47
+ }
48
+ return;
49
+ }
50
+
26
51
  const collected: RSErrorDescription[] = [];
27
52
  const annotationTargets = new Map<RSErrorDescription, AstNode>();
28
53
  const collect = (error: RSErrorDescription, target?: AstNode) => {
@@ -33,12 +58,14 @@ export function extractSyntaxErrors(
33
58
  };
34
59
 
35
60
  const bracketError = extractBracketErrors(expression);
36
- if (bracketError !== null) {
37
- collect(bracketError, ast);
38
- }
39
61
  const hasBracketErrors = bracketError !== null;
40
62
  visitAstDFS(ast, node => extractInternal(node, expression, collect, hasBracketErrors, options));
41
63
 
64
+ const hasIncompleteGenerator = collected.some(error => isIncompleteGeneratorBodyCode(error.code));
65
+ if (bracketError !== null && !hasIncompleteGenerator) {
66
+ collect(bracketError, ast);
67
+ }
68
+
42
69
  for (const error of deduplicateErrors(collected)) {
43
70
  reporter(error);
44
71
  if (annotateErrors) {
@@ -99,6 +126,16 @@ function classifyParseError(
99
126
  return incompleteFunctionDecl;
100
127
  }
101
128
 
129
+ const incompleteQuantifier = detectIncompleteQuantifier(node, expression);
130
+ if (incompleteQuantifier !== null) {
131
+ return incompleteQuantifier;
132
+ }
133
+
134
+ const incompleteGenerator = detectIncompleteGenerator(node, expression);
135
+ if (incompleteGenerator !== null) {
136
+ return incompleteGenerator;
137
+ }
138
+
102
139
  const filterParen = detectFilterParenMismatch(node, expression);
103
140
  if (filterParen !== null) {
104
141
  return filterParen;
@@ -150,6 +187,394 @@ function incompleteFormalExpressionCode(expected?: TypeClass): RSErrorCode {
150
187
  }
151
188
  }
152
189
 
190
+ function detectIncompleteQuantifier(node: AstNode, expression: string): RSErrorDescription | null {
191
+ const quantNode = findAncestor(node, isQuantifierNode);
192
+ if (quantNode === null) {
193
+ return null;
194
+ }
195
+
196
+ const bodyChild = quantNode.children[quantNode.children.length - 1];
197
+ if (bodyChild?.typeID !== TokenID.ERROR || node !== bodyChild) {
198
+ return null;
199
+ }
200
+
201
+ const headerChildren = quantNode.children.slice(0, -1);
202
+ if (headerChildren.length < 3) {
203
+ return null;
204
+ }
205
+
206
+ const varPack = headerChildren.find(child => child.typeID === Variable_pack || child.typeID === TokenID.NT_ENUM_DECL);
207
+ if (varPack === undefined || subtreeHasError(varPack)) {
208
+ return null;
209
+ }
210
+
211
+ const domainChild = headerChildren[headerChildren.length - 1];
212
+ if (domainChild === varPack || subtreeHasError(domainChild)) {
213
+ return null;
214
+ }
215
+
216
+ if (expression.slice(quantNode.to).trim().length > 0) {
217
+ return null;
218
+ }
219
+
220
+ return {
221
+ code: RSErrorCode.expectedQuantifierBody,
222
+ from: bodyChild.from,
223
+ to: bodyChild.to
224
+ };
225
+ }
226
+
227
+ function detectIncompleteGenerator(node: AstNode, expression: string): RSErrorDescription | null {
228
+ return (
229
+ detectIncompleteDeclarative(node, expression) ??
230
+ detectIncompleteImperative(node, expression) ??
231
+ detectIncompleteRecursion(node, expression)
232
+ );
233
+ }
234
+
235
+ function detectIncompleteDeclarative(node: AstNode, expression: string): RSErrorDescription | null {
236
+ const generatorNode = findAncestor(
237
+ node,
238
+ target => target.typeID === Declarative || target.typeID === TokenID.NT_DECLARATIVE_EXPR
239
+ );
240
+ return detectIncompleteGeneratorBody(node, expression, generatorNode, RSErrorCode.expectedDeclarativeBody, slot =>
241
+ isIncompleteDeclarativeBody(slot)
242
+ );
243
+ }
244
+
245
+ function detectIncompleteImperative(node: AstNode, expression: string): RSErrorDescription | null {
246
+ const generatorNode = findAncestor(
247
+ node,
248
+ target => target.typeID === Imperative || target.typeID === TokenID.NT_IMPERATIVE_EXPR
249
+ );
250
+ return detectIncompleteGeneratorBody(node, expression, generatorNode, RSErrorCode.expectedImperativeBody, slot =>
251
+ isIncompleteImperativeBody(slot, expression)
252
+ );
253
+ }
254
+
255
+ function detectIncompleteRecursion(node: AstNode, expression: string): RSErrorDescription | null {
256
+ const generatorNode = findAncestor(node, target => target.typeID === Recursion);
257
+ return detectIncompleteGeneratorBody(node, expression, generatorNode, RSErrorCode.expectedRecursiveBody, slot =>
258
+ isIncompleteRecursionBody(slot)
259
+ );
260
+ }
261
+
262
+ interface GeneratorBodySlot {
263
+ nodes: AstNode[];
264
+ headerEndIndex: number;
265
+ }
266
+
267
+ function detectIncompleteGeneratorBody(
268
+ node: AstNode,
269
+ expression: string,
270
+ generatorNode: AstNode | null,
271
+ code: RSErrorCode,
272
+ isIncomplete: (slot: GeneratorBodySlot) => boolean
273
+ ): RSErrorDescription | null {
274
+ if (generatorNode === null) {
275
+ return null;
276
+ }
277
+
278
+ if (expression.slice(generatorNode.to).trim().length > 0) {
279
+ return null;
280
+ }
281
+
282
+ const slot = getGeneratorBodySlot(generatorNode, expression);
283
+ if (slot === null || !isIncomplete(slot)) {
284
+ return null;
285
+ }
286
+
287
+ if (!generatorHeaderIsValid(generatorNode, expression, slot.headerEndIndex)) {
288
+ return null;
289
+ }
290
+
291
+ if (!isErrorInBodySlot(node, slot, generatorNode)) {
292
+ return null;
293
+ }
294
+
295
+ return incompleteGeneratorError(code, node);
296
+ }
297
+
298
+ function getGeneratorBodySlot(generatorNode: AstNode, expression: string): GeneratorBodySlot | null {
299
+ if (generatorNode.typeID === Declarative || generatorNode.typeID === TokenID.NT_DECLARATIVE_EXPR) {
300
+ return getDeclarativeBodySlot(generatorNode, expression);
301
+ }
302
+ if (generatorNode.typeID === Imperative || generatorNode.typeID === TokenID.NT_IMPERATIVE_EXPR) {
303
+ return getImperativeBodySlot(generatorNode, expression);
304
+ }
305
+ if (generatorNode.typeID === Recursion) {
306
+ return getRecursionBodySlot(generatorNode, expression);
307
+ }
308
+ return null;
309
+ }
310
+
311
+ function getDeclarativeBodySlot(declNode: AstNode, expression: string): GeneratorBodySlot | null {
312
+ const pipeIndex = declNode.children.findIndex(child => isPipeToken(child, expression));
313
+ if (pipeIndex >= 0) {
314
+ return {
315
+ nodes: contentChildren(declNode, expression, pipeIndex + 1),
316
+ headerEndIndex: pipeIndex + 1
317
+ };
318
+ }
319
+
320
+ const domainNode = findDeclarativeDomain(declNode, expression);
321
+ if (domainNode === null) {
322
+ return null;
323
+ }
324
+
325
+ const domainIndex = declNode.children.indexOf(domainNode);
326
+ return {
327
+ nodes: contentChildren(declNode, expression, domainIndex + 1),
328
+ headerEndIndex: domainIndex + 1
329
+ };
330
+ }
331
+
332
+ function getImperativeBodySlot(impNode: AstNode, expression: string): GeneratorBodySlot | null {
333
+ const pipeIndex = impNode.children.findIndex(child => isPipeToken(child, expression));
334
+ if (pipeIndex >= 0) {
335
+ return {
336
+ nodes: contentChildren(impNode, expression, pipeIndex + 1),
337
+ headerEndIndex: pipeIndex + 1
338
+ };
339
+ }
340
+
341
+ const tupleNode = findImperativeTuple(impNode);
342
+ if (tupleNode === null) {
343
+ return null;
344
+ }
345
+
346
+ const tupleIndex = impNode.children.indexOf(tupleNode);
347
+ return {
348
+ nodes: contentChildren(impNode, expression, tupleIndex + 1),
349
+ headerEndIndex: tupleIndex + 1
350
+ };
351
+ }
352
+
353
+ function getRecursionBodySlot(recNode: AstNode, expression: string): GeneratorBodySlot | null {
354
+ const initNode = findRecursionInit(recNode, expression);
355
+ if (initNode === null) {
356
+ return null;
357
+ }
358
+
359
+ const initIndex = recNode.children.indexOf(initNode);
360
+ const afterInit = contentChildren(recNode, expression, initIndex + 1);
361
+ const lastPipeIndex = findLastPipeIndex(afterInit, expression);
362
+ if (lastPipeIndex < 0) {
363
+ return {
364
+ nodes: afterInit,
365
+ headerEndIndex: initIndex + 1
366
+ };
367
+ }
368
+
369
+ return {
370
+ nodes: afterInit.slice(lastPipeIndex + 1),
371
+ headerEndIndex: recNode.children.indexOf(afterInit[lastPipeIndex]) + 1
372
+ };
373
+ }
374
+
375
+ function contentChildren(generatorNode: AstNode, expression: string, fromIndex: number): AstNode[] {
376
+ return generatorNode.children.slice(fromIndex).filter(child => !isClosingBrace(child, expression));
377
+ }
378
+
379
+ function findDeclarativeDomain(declNode: AstNode, expression: string): AstNode | null {
380
+ const inIndex = declNode.children.findIndex(child => expression.slice(child.from, child.to) === '∈');
381
+ if (inIndex < 0) {
382
+ return null;
383
+ }
384
+
385
+ for (let index = inIndex + 1; index < declNode.children.length; index++) {
386
+ const child = declNode.children[index];
387
+ if (isPipeToken(child, expression) || isClosingBrace(child, expression)) {
388
+ break;
389
+ }
390
+ if (child.typeID === Setexpr) {
391
+ return child;
392
+ }
393
+ }
394
+
395
+ return null;
396
+ }
397
+
398
+ function findImperativeTuple(impNode: AstNode): AstNode | null {
399
+ for (const child of impNode.children) {
400
+ if (child.typeID === Setexpr) {
401
+ return child;
402
+ }
403
+ }
404
+ return null;
405
+ }
406
+
407
+ function findLastPipeIndex(nodes: AstNode[], expression: string): number {
408
+ for (let index = nodes.length - 1; index >= 0; index--) {
409
+ if (isPipeToken(nodes[index], expression)) {
410
+ return index;
411
+ }
412
+ }
413
+ return -1;
414
+ }
415
+
416
+ function generatorHeaderIsValid(generatorNode: AstNode, expression: string, headerEndIndex: number): boolean {
417
+ for (let index = 0; index < headerEndIndex; index++) {
418
+ const child = generatorNode.children[index];
419
+ if (child.typeID === TokenID.ERROR) {
420
+ return false;
421
+ }
422
+ if (isStructuralGeneratorToken(child, expression)) {
423
+ continue;
424
+ }
425
+ if (subtreeHasError(child)) {
426
+ return false;
427
+ }
428
+ }
429
+ return true;
430
+ }
431
+
432
+ function isIncompleteDeclarativeBody(slot: GeneratorBodySlot): boolean {
433
+ return isEmptyOrErrorOnlyBody(slot.nodes);
434
+ }
435
+
436
+ function isIncompleteImperativeBody(slot: GeneratorBodySlot, expression: string): boolean {
437
+ if (slot.nodes.length === 0) {
438
+ return true;
439
+ }
440
+ if (slot.nodes.length === 1 && slot.nodes[0].typeID === Imp_blocks) {
441
+ return isImpBlocksIncomplete(slot.nodes[0], expression);
442
+ }
443
+ return isEmptyOrErrorOnlyBody(slot.nodes);
444
+ }
445
+
446
+ function isIncompleteRecursionBody(slot: GeneratorBodySlot): boolean {
447
+ return isEmptyOrErrorOnlyBody(slot.nodes);
448
+ }
449
+
450
+ function isEmptyOrErrorOnlyBody(nodes: AstNode[]): boolean {
451
+ if (nodes.length === 0) {
452
+ return true;
453
+ }
454
+ if (nodes.length === 1) {
455
+ return isErrorPlaceholder(nodes[0]);
456
+ }
457
+ return false;
458
+ }
459
+
460
+ function isImpBlocksIncomplete(blocksNode: AstNode, expression: string): boolean {
461
+ if (blocksNode.children.length === 0) {
462
+ return true;
463
+ }
464
+
465
+ const lastChild = blocksNode.children[blocksNode.children.length - 1];
466
+ if (lastChild.typeID === TokenID.ERROR) {
467
+ return true;
468
+ }
469
+ if (isSemicolonToken(lastChild, expression)) {
470
+ return true;
471
+ }
472
+ if (lastChild.typeID === Setexpr && isErrorPlaceholder(lastChild)) {
473
+ return true;
474
+ }
475
+
476
+ return false;
477
+ }
478
+
479
+ function isErrorInBodySlot(errorNode: AstNode, slot: GeneratorBodySlot, generatorNode: AstNode): boolean {
480
+ if (slot.nodes.length === 0) {
481
+ const bodyStart = generatorNode.children[slot.headerEndIndex]?.from ?? generatorNode.from;
482
+ return errorNode.from >= bodyStart;
483
+ }
484
+
485
+ for (const bodyNode of slot.nodes) {
486
+ if (errorNode === bodyNode || isDescendantOf(bodyNode, errorNode)) {
487
+ if (isErrorPlaceholder(bodyNode)) {
488
+ return true;
489
+ }
490
+ if (bodyNode.typeID === Imp_blocks && isImpBlocksTailError(bodyNode, errorNode)) {
491
+ return true;
492
+ }
493
+ }
494
+ }
495
+
496
+ return false;
497
+ }
498
+
499
+ function isImpBlocksTailError(blocksNode: AstNode, errorNode: AstNode): boolean {
500
+ const lastChild = blocksNode.children[blocksNode.children.length - 1];
501
+ return lastChild.typeID === TokenID.ERROR && (errorNode === lastChild || isDescendantOf(lastChild, errorNode));
502
+ }
503
+
504
+ function isErrorPlaceholder(node: AstNode): boolean {
505
+ if (node.typeID === TokenID.ERROR) {
506
+ return true;
507
+ }
508
+ if (node.children.length === 0) {
509
+ return false;
510
+ }
511
+ return node.children.every(child => isErrorPlaceholder(child));
512
+ }
513
+
514
+ function isDescendantOf(ancestor: AstNode, node: AstNode): boolean {
515
+ let current: AstNode | null = node.parent;
516
+ while (current !== null) {
517
+ if (current === ancestor) {
518
+ return true;
519
+ }
520
+ current = current.parent;
521
+ }
522
+ return false;
523
+ }
524
+
525
+ function findRecursionInit(recursionNode: AstNode, expression: string): AstNode | null {
526
+ const assignIndex = recursionNode.children.findIndex(child => expression.slice(child.from, child.to) === ':=');
527
+ if (assignIndex < 0) {
528
+ return null;
529
+ }
530
+
531
+ for (let index = assignIndex + 1; index < recursionNode.children.length; index++) {
532
+ const child = recursionNode.children[index];
533
+ if (child.typeID === TokenID.ERROR) {
534
+ break;
535
+ }
536
+ if (isPipeToken(child, expression) || isClosingBrace(child, expression)) {
537
+ continue;
538
+ }
539
+ return child;
540
+ }
541
+
542
+ return null;
543
+ }
544
+
545
+ function incompleteGeneratorError(code: RSErrorCode, errorNode: AstNode): RSErrorDescription {
546
+ return {
547
+ code,
548
+ from: errorNode.from,
549
+ to: errorNode.to
550
+ };
551
+ }
552
+
553
+ function isIncompleteGeneratorBodyCode(code: RSErrorCode): boolean {
554
+ return (
555
+ code === RSErrorCode.expectedDeclarativeBody ||
556
+ code === RSErrorCode.expectedImperativeBody ||
557
+ code === RSErrorCode.expectedRecursiveBody
558
+ );
559
+ }
560
+
561
+ function isPipeToken(node: AstNode, expression: string): boolean {
562
+ return expression.slice(node.from, node.to) === '|';
563
+ }
564
+
565
+ function isSemicolonToken(node: AstNode, expression: string): boolean {
566
+ return expression.slice(node.from, node.to) === ';';
567
+ }
568
+
569
+ function isClosingBrace(node: AstNode, expression: string): boolean {
570
+ return node.typeID !== TokenID.ERROR && expression.slice(node.from, node.to) === '}';
571
+ }
572
+
573
+ function isStructuralGeneratorToken(node: AstNode, expression: string): boolean {
574
+ const text = expression.slice(node.from, node.to);
575
+ return text === '{' || text === '}' || text === '|' || text === '∈' || text === ':=' || text === ';';
576
+ }
577
+
153
578
  function subtreeHasError(node: AstNode): boolean {
154
579
  if (node.typeID === TokenID.ERROR) {
155
580
  return true;
@@ -246,6 +671,10 @@ function isFunctionDeclNode(node: AstNode): boolean {
246
671
  return node.typeID === Function_decl || node.typeID === TokenID.NT_FUNC_DEFINITION;
247
672
  }
248
673
 
674
+ function isQuantifierNode(node: AstNode): boolean {
675
+ return node.typeID === Logic_quantor;
676
+ }
677
+
249
678
  function isFunctionNode(node: AstNode): boolean {
250
679
  return node.typeID === Function || node.typeID === TokenID.ID_FUNCTION;
251
680
  }
@@ -319,6 +748,58 @@ interface BracketFrame {
319
748
  index: number;
320
749
  }
321
750
 
751
+ const ALLOWED_EXPRESSION_CHARS = buildAllowedExpressionChars();
752
+
753
+ function buildAllowedExpressionChars(): Set<string> {
754
+ const chars = new Set<string>();
755
+ for (const chunk of [
756
+ '0123456789',
757
+ 'abcdefghijklmnopqrstuvwxyz',
758
+ 'ABCDEFGHIJKLMNOPQRSTUVWXYZ',
759
+ '_',
760
+ '∅¬∀∃⇔⇒∨&ℬ+-*∪\\∆∩×∈∉⊆⊄⊂<>≥≤≠=,:;|[]{}()'
761
+ ]) {
762
+ for (const symbol of chunk) {
763
+ chars.add(symbol);
764
+ }
765
+ }
766
+ for (let codePoint = 0x03b1; codePoint <= 0x03c9; codePoint++) {
767
+ chars.add(String.fromCodePoint(codePoint));
768
+ }
769
+ return chars;
770
+ }
771
+
772
+ function isAllowedExpressionChar(symbol: string): boolean {
773
+ return /\s/u.test(symbol) || ALLOWED_EXPRESSION_CHARS.has(symbol);
774
+ }
775
+
776
+ function extractForbiddenCharacterErrors(expression: string): RSErrorDescription[] {
777
+ const errors: RSErrorDescription[] = [];
778
+ let pos = 0;
779
+
780
+ while (pos < expression.length) {
781
+ const symbol = expression[pos];
782
+ if (isAllowedExpressionChar(symbol)) {
783
+ pos++;
784
+ continue;
785
+ }
786
+
787
+ const start = pos;
788
+ while (pos < expression.length && !isAllowedExpressionChar(expression[pos])) {
789
+ pos++;
790
+ }
791
+
792
+ errors.push({
793
+ code: RSErrorCode.forbiddenCharacter,
794
+ from: start,
795
+ to: pos,
796
+ params: [expression.slice(start, pos)]
797
+ });
798
+ }
799
+
800
+ return errors;
801
+ }
802
+
322
803
  function extractBracketErrors(expression: string): RSErrorDescription | null {
323
804
  const stack: BracketFrame[] = [];
324
805