@rsconcept/domain 1.0.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/LICENSE +21 -0
- package/README.md +55 -0
- package/dist/cctext/index.d.ts +1 -0
- package/dist/cctext/index.js +42 -0
- package/dist/cctext/index.js.map +1 -0
- package/dist/cctext/language-api.d.ts +43 -0
- package/dist/cctext/language-api.js +252 -0
- package/dist/cctext/language-api.js.map +1 -0
- package/dist/cctext/language.d.ts +58 -0
- package/dist/cctext/language.js +44 -0
- package/dist/cctext/language.js.map +1 -0
- package/dist/graph/graph.d.ts +62 -0
- package/dist/graph/graph.js +385 -0
- package/dist/graph/graph.js.map +1 -0
- package/dist/graph/index.d.ts +1 -0
- package/dist/graph/index.js +384 -0
- package/dist/graph/index.js.map +1 -0
- package/dist/index.d.ts +28 -0
- package/dist/index.js +5851 -0
- package/dist/index.js.map +1 -0
- package/dist/library/folder-tree.d.ts +32 -0
- package/dist/library/folder-tree.js +136 -0
- package/dist/library/folder-tree.js.map +1 -0
- package/dist/library/index.d.ts +17 -0
- package/dist/library/index.js +2800 -0
- package/dist/library/index.js.map +1 -0
- package/dist/library/library-api.d.ts +6 -0
- package/dist/library/library-api.js +13 -0
- package/dist/library/library-api.js.map +1 -0
- package/dist/library/library.d.ts +56 -0
- package/dist/library/library.js +23 -0
- package/dist/library/library.js.map +1 -0
- package/dist/library/oss-api.d.ts +47 -0
- package/dist/library/oss-api.js +1105 -0
- package/dist/library/oss-api.js.map +1 -0
- package/dist/library/oss-layout-api.d.ts +36 -0
- package/dist/library/oss-layout-api.js +330 -0
- package/dist/library/oss-layout-api.js.map +1 -0
- package/dist/library/oss-layout.d.ts +25 -0
- package/dist/library/oss-layout.js +1 -0
- package/dist/library/oss-layout.js.map +1 -0
- package/dist/library/oss.d.ts +136 -0
- package/dist/library/oss.js +30 -0
- package/dist/library/oss.js.map +1 -0
- package/dist/library/rsengine.d.ts +116 -0
- package/dist/library/rsengine.js +2604 -0
- package/dist/library/rsengine.js.map +1 -0
- package/dist/library/rsform-api.d.ts +74 -0
- package/dist/library/rsform-api.js +879 -0
- package/dist/library/rsform-api.js.map +1 -0
- package/dist/library/rsform.d.ts +206 -0
- package/dist/library/rsform.js +32 -0
- package/dist/library/rsform.js.map +1 -0
- package/dist/library/rsmodel-api.d.ts +43 -0
- package/dist/library/rsmodel-api.js +836 -0
- package/dist/library/rsmodel-api.js.map +1 -0
- package/dist/library/rsmodel.d.ts +52 -0
- package/dist/library/rsmodel.js +25 -0
- package/dist/library/rsmodel.js.map +1 -0
- package/dist/library/structure-planner.d.ts +33 -0
- package/dist/library/structure-planner.js +481 -0
- package/dist/library/structure-planner.js.map +1 -0
- package/dist/parsing/ast.d.ts +49 -0
- package/dist/parsing/ast.js +93 -0
- package/dist/parsing/ast.js.map +1 -0
- package/dist/parsing/index.d.ts +3 -0
- package/dist/parsing/index.js +141 -0
- package/dist/parsing/index.js.map +1 -0
- package/dist/parsing/lezer-tree.d.ts +13 -0
- package/dist/parsing/lezer-tree.js +50 -0
- package/dist/parsing/lezer-tree.js.map +1 -0
- package/dist/rslang/api.d.ts +53 -0
- package/dist/rslang/api.js +846 -0
- package/dist/rslang/api.js.map +1 -0
- package/dist/rslang/ast-annotations.d.ts +18 -0
- package/dist/rslang/ast-annotations.js +56 -0
- package/dist/rslang/ast-annotations.js.map +1 -0
- package/dist/rslang/error.d.ts +85 -0
- package/dist/rslang/error.js +159 -0
- package/dist/rslang/error.js.map +1 -0
- package/dist/rslang/eval/calculator.d.ts +43 -0
- package/dist/rslang/eval/calculator.js +1639 -0
- package/dist/rslang/eval/calculator.js.map +1 -0
- package/dist/rslang/eval/evaluation-cache.d.ts +36 -0
- package/dist/rslang/eval/evaluation-cache.js +310 -0
- package/dist/rslang/eval/evaluation-cache.js.map +1 -0
- package/dist/rslang/eval/evaluator.d.ts +70 -0
- package/dist/rslang/eval/evaluator.js +1514 -0
- package/dist/rslang/eval/evaluator.js.map +1 -0
- package/dist/rslang/eval/value-api.d.ts +48 -0
- package/dist/rslang/eval/value-api.js +490 -0
- package/dist/rslang/eval/value-api.js.map +1 -0
- package/dist/rslang/eval/value.d.ts +36 -0
- package/dist/rslang/eval/value.js +118 -0
- package/dist/rslang/eval/value.js.map +1 -0
- package/dist/rslang/index.d.ts +17 -0
- package/dist/rslang/index.js +4314 -0
- package/dist/rslang/index.js.map +1 -0
- package/dist/rslang/labels.d.ts +16 -0
- package/dist/rslang/labels.js +315 -0
- package/dist/rslang/labels.js.map +1 -0
- package/dist/rslang/parser/expression-generator.d.ts +10 -0
- package/dist/rslang/parser/expression-generator.js +451 -0
- package/dist/rslang/parser/expression-generator.js.map +1 -0
- package/dist/rslang/parser/normalize.d.ts +11 -0
- package/dist/rslang/parser/normalize.js +507 -0
- package/dist/rslang/parser/normalize.js.map +1 -0
- package/dist/rslang/parser/parser.d.ts +5 -0
- package/dist/rslang/parser/parser.js +24 -0
- package/dist/rslang/parser/parser.js.map +1 -0
- package/dist/rslang/parser/parser.terms.d.ts +42 -0
- package/dist/rslang/parser/parser.terms.js +84 -0
- package/dist/rslang/parser/parser.terms.js.map +1 -0
- package/dist/rslang/parser/syntax-errors.d.ts +11 -0
- package/dist/rslang/parser/syntax-errors.js +403 -0
- package/dist/rslang/parser/syntax-errors.js.map +1 -0
- package/dist/rslang/parser/token.d.ts +79 -0
- package/dist/rslang/parser/token.js +95 -0
- package/dist/rslang/parser/token.js.map +1 -0
- package/dist/rslang/semantic/analyzer.d.ts +39 -0
- package/dist/rslang/semantic/analyzer.js +2604 -0
- package/dist/rslang/semantic/analyzer.js.map +1 -0
- package/dist/rslang/semantic/arguments-extractor.d.ts +42 -0
- package/dist/rslang/semantic/arguments-extractor.js +366 -0
- package/dist/rslang/semantic/arguments-extractor.js.map +1 -0
- package/dist/rslang/semantic/type-auditor.d.ts +73 -0
- package/dist/rslang/semantic/type-auditor.js +1570 -0
- package/dist/rslang/semantic/type-auditor.js.map +1 -0
- package/dist/rslang/semantic/typification-api.d.ts +27 -0
- package/dist/rslang/semantic/typification-api.js +320 -0
- package/dist/rslang/semantic/typification-api.js.map +1 -0
- package/dist/rslang/semantic/typification-parser.d.ts +12 -0
- package/dist/rslang/semantic/typification-parser.js +226 -0
- package/dist/rslang/semantic/typification-parser.js.map +1 -0
- package/dist/rslang/semantic/typification.d.ts +119 -0
- package/dist/rslang/semantic/typification.js +74 -0
- package/dist/rslang/semantic/typification.js.map +1 -0
- package/dist/rslang/semantic/value-auditor.d.ts +43 -0
- package/dist/rslang/semantic/value-auditor.js +523 -0
- package/dist/rslang/semantic/value-auditor.js.map +1 -0
- package/dist/rslang/semantic/value-class.d.ts +10 -0
- package/dist/rslang/semantic/value-class.js +9 -0
- package/dist/rslang/semantic/value-class.js.map +1 -0
- package/dist/rslang/typification-graph.d.ts +33 -0
- package/dist/rslang/typification-graph.js +311 -0
- package/dist/rslang/typification-graph.js.map +1 -0
- package/dist/shared/branded.d.ts +7 -0
- package/dist/shared/branded.js +1 -0
- package/dist/shared/branded.js.map +1 -0
- package/dist/shared/hash.d.ts +6 -0
- package/dist/shared/hash.js +18 -0
- package/dist/shared/hash.js.map +1 -0
- package/dist/shared/index.d.ts +2 -0
- package/dist/shared/index.js +18 -0
- package/dist/shared/index.js.map +1 -0
- package/package.json +184 -0
- package/src/cctext/index.ts +9 -0
- package/src/cctext/language-api.test.ts +149 -0
- package/src/cctext/language-api.ts +285 -0
- package/src/cctext/language.ts +80 -0
- package/src/graph/graph.test.ts +392 -0
- package/src/graph/graph.ts +433 -0
- package/src/graph/index.ts +1 -0
- package/src/index.ts +96 -0
- package/src/library/folder-tree.test.ts +47 -0
- package/src/library/folder-tree.ts +156 -0
- package/src/library/index.ts +46 -0
- package/src/library/library-api.test.ts +32 -0
- package/src/library/library-api.ts +11 -0
- package/src/library/library.ts +61 -0
- package/src/library/oss-api.ts +449 -0
- package/src/library/oss-layout-api.ts +377 -0
- package/src/library/oss-layout.ts +27 -0
- package/src/library/oss.ts +150 -0
- package/src/library/rsengine.ts +593 -0
- package/src/library/rsform-api.ts +533 -0
- package/src/library/rsform.ts +228 -0
- package/src/library/rsmodel-api.ts +340 -0
- package/src/library/rsmodel.ts +50 -0
- package/src/library/structure-planner.ts +143 -0
- package/src/parsing/ast.ts +136 -0
- package/src/parsing/index.ts +15 -0
- package/src/parsing/lezer-tree.ts +69 -0
- package/src/rslang/api.test.ts +116 -0
- package/src/rslang/api.ts +183 -0
- package/src/rslang/ast-annotations.ts +70 -0
- package/src/rslang/error.ts +129 -0
- package/src/rslang/eval/calculator.test.ts +124 -0
- package/src/rslang/eval/calculator.ts +121 -0
- package/src/rslang/eval/evaluation-cache.ts +257 -0
- package/src/rslang/eval/evaluator.test.ts +352 -0
- package/src/rslang/eval/evaluator.ts +935 -0
- package/src/rslang/eval/value-api.test.ts +105 -0
- package/src/rslang/eval/value-api.ts +444 -0
- package/src/rslang/eval/value.ts +102 -0
- package/src/rslang/index.ts +23 -0
- package/src/rslang/labels.ts +191 -0
- package/src/rslang/parser/expression-generator.test.ts +100 -0
- package/src/rslang/parser/expression-generator.ts +466 -0
- package/src/rslang/parser/normalize.test.ts +99 -0
- package/src/rslang/parser/normalize.ts +462 -0
- package/src/rslang/parser/parser.terms.ts +42 -0
- package/src/rslang/parser/parser.test.ts +153 -0
- package/src/rslang/parser/parser.ts +20 -0
- package/src/rslang/parser/rslang.grammar +251 -0
- package/src/rslang/parser/syntax-errors.ts +209 -0
- package/src/rslang/parser/token.ts +106 -0
- package/src/rslang/semantic/analyzer.test.ts +59 -0
- package/src/rslang/semantic/analyzer.ts +179 -0
- package/src/rslang/semantic/arguments-extractor.ts +327 -0
- package/src/rslang/semantic/type-auditor.test.ts +326 -0
- package/src/rslang/semantic/type-auditor.ts +1049 -0
- package/src/rslang/semantic/typification-api.test.ts +46 -0
- package/src/rslang/semantic/typification-api.ts +321 -0
- package/src/rslang/semantic/typification-parser.test.ts +50 -0
- package/src/rslang/semantic/typification-parser.ts +220 -0
- package/src/rslang/semantic/typification.ts +180 -0
- package/src/rslang/semantic/value-auditor.test.ts +206 -0
- package/src/rslang/semantic/value-auditor.ts +332 -0
- package/src/rslang/semantic/value-class.ts +11 -0
- package/src/rslang/typification-graph.ts +155 -0
- package/src/shared/branded.ts +6 -0
- package/src/shared/hash.ts +17 -0
- package/src/shared/index.ts +2 -0
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest';
|
|
2
|
+
|
|
3
|
+
import type { TypePath } from './typification';
|
|
4
|
+
import { AnyTypificationT, basic, bool, IntegerT, tuple } from './typification';
|
|
5
|
+
import { applyPath } from './typification-api';
|
|
6
|
+
|
|
7
|
+
describe('applyPath', () => {
|
|
8
|
+
function typePath(path: number[]): TypePath {
|
|
9
|
+
return path as TypePath;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
it('returns the same typification for empty path', () => {
|
|
13
|
+
const type = basic('X1');
|
|
14
|
+
expect(applyPath(type, typePath([]))).toBe(type);
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
it('returns null for path on non-nested types', () => {
|
|
18
|
+
const types = [AnyTypificationT, basic('X1'), IntegerT];
|
|
19
|
+
for (const typ of types) {
|
|
20
|
+
expect(applyPath(typ, typePath([0]))).toBeNull();
|
|
21
|
+
}
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
it('follows collection base as expected', () => {
|
|
25
|
+
const base = basic('X1');
|
|
26
|
+
const collection = bool(base);
|
|
27
|
+
expect(applyPath(collection, typePath([0]))).toBe(base);
|
|
28
|
+
expect(applyPath(collection, typePath([0, 0]))).toBeNull();
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
it('navigates within tuple by path index', () => {
|
|
32
|
+
const f1 = basic('X1');
|
|
33
|
+
const f2 = basic('X2');
|
|
34
|
+
const tup = tuple([f1, f2]);
|
|
35
|
+
expect(applyPath(tup, typePath([0]))).toBeNull();
|
|
36
|
+
expect(applyPath(tup, typePath([1]))).toBe(f1);
|
|
37
|
+
expect(applyPath(tup, typePath([2]))).toBe(f2);
|
|
38
|
+
expect(applyPath(tup, typePath([3]))).toBeNull();
|
|
39
|
+
expect(applyPath(tup, typePath([1, 0]))).toBeNull();
|
|
40
|
+
|
|
41
|
+
const nested = tuple([tup, f2]);
|
|
42
|
+
expect(applyPath(nested, typePath([1, 2]))).toBe(f2);
|
|
43
|
+
expect(applyPath(nested, typePath([1, 3]))).toBeNull();
|
|
44
|
+
expect(applyPath(nested, typePath([2, 1]))).toBeNull();
|
|
45
|
+
});
|
|
46
|
+
});
|
|
@@ -0,0 +1,321 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Module: Typification API for RSLang.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import {
|
|
6
|
+
bool,
|
|
7
|
+
component,
|
|
8
|
+
type EchelonBase,
|
|
9
|
+
type EchelonCollection,
|
|
10
|
+
type EchelonTuple,
|
|
11
|
+
type ExpressionType,
|
|
12
|
+
IntegerT,
|
|
13
|
+
isRadical,
|
|
14
|
+
isTypification,
|
|
15
|
+
tuple,
|
|
16
|
+
TypeClass,
|
|
17
|
+
TypeID,
|
|
18
|
+
type TypePath,
|
|
19
|
+
type Typification
|
|
20
|
+
} from './typification';
|
|
21
|
+
|
|
22
|
+
/** Record map from typeID to typeClass. */
|
|
23
|
+
const TypeIDToClass: Record<TypeID, TypeClass> = {
|
|
24
|
+
[TypeID.anyTypification]: TypeClass.typification,
|
|
25
|
+
[TypeID.integer]: TypeClass.typification,
|
|
26
|
+
[TypeID.basic]: TypeClass.typification,
|
|
27
|
+
[TypeID.tuple]: TypeClass.typification,
|
|
28
|
+
[TypeID.collection]: TypeClass.typification,
|
|
29
|
+
[TypeID.logic]: TypeClass.logic,
|
|
30
|
+
[TypeID.function]: TypeClass.function,
|
|
31
|
+
[TypeID.predicate]: TypeClass.predicate
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
/** Returns the TypeClass associated with the given TypeID. */
|
|
35
|
+
export function getTypeClass(typeID: TypeID): TypeClass {
|
|
36
|
+
return TypeIDToClass[typeID];
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/** Returns true if the given typification is generic. */
|
|
40
|
+
export function hasGenerics(type: Typification): boolean {
|
|
41
|
+
switch (type.typeID) {
|
|
42
|
+
case TypeID.anyTypification:
|
|
43
|
+
return true;
|
|
44
|
+
case TypeID.integer:
|
|
45
|
+
return false;
|
|
46
|
+
case TypeID.basic:
|
|
47
|
+
return isRadical(type.baseID);
|
|
48
|
+
case TypeID.collection:
|
|
49
|
+
return hasGenerics(type.base);
|
|
50
|
+
case TypeID.tuple:
|
|
51
|
+
for (const factor of type.factors) {
|
|
52
|
+
if (hasGenerics(factor)) {
|
|
53
|
+
return true;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
return false;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/** Checks if two typifications are equal. */
|
|
61
|
+
export function checkEquality(type1: Typification, type2: Typification): boolean {
|
|
62
|
+
if (type1 === type2) {
|
|
63
|
+
return true;
|
|
64
|
+
}
|
|
65
|
+
if (type1.typeID !== type2.typeID) {
|
|
66
|
+
return false;
|
|
67
|
+
}
|
|
68
|
+
switch (type1.typeID) {
|
|
69
|
+
case TypeID.integer:
|
|
70
|
+
case TypeID.anyTypification:
|
|
71
|
+
return true;
|
|
72
|
+
case TypeID.basic:
|
|
73
|
+
return type1.baseID === (type2 as EchelonBase).baseID;
|
|
74
|
+
case TypeID.collection:
|
|
75
|
+
return checkEquality(type1.base, (type2 as EchelonCollection).base);
|
|
76
|
+
case TypeID.tuple: {
|
|
77
|
+
if (type1.factors.length !== (type2 as EchelonTuple).factors.length) {
|
|
78
|
+
return false;
|
|
79
|
+
}
|
|
80
|
+
for (let index = 0; index < type1.factors.length; ++index) {
|
|
81
|
+
if (!checkEquality(type1.factors[index], (type2 as EchelonTuple).factors[index])) {
|
|
82
|
+
return false;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
return true;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/** Merge two types to get result of binary operation. */
|
|
91
|
+
export function mergeTypifications(type1: Typification, type2: Typification): Typification | null {
|
|
92
|
+
if (type1 === type2) {
|
|
93
|
+
return type1;
|
|
94
|
+
}
|
|
95
|
+
if (type1.typeID === TypeID.anyTypification) {
|
|
96
|
+
return isTypification(type2) ? type2 : null;
|
|
97
|
+
}
|
|
98
|
+
if (type2.typeID === TypeID.anyTypification) {
|
|
99
|
+
return isTypification(type1) ? type1 : null;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
switch (type1.typeID) {
|
|
103
|
+
case TypeID.integer:
|
|
104
|
+
case TypeID.basic:
|
|
105
|
+
return commonType(type1, type2);
|
|
106
|
+
case TypeID.collection:
|
|
107
|
+
if (type2.typeID !== TypeID.collection) {
|
|
108
|
+
return null;
|
|
109
|
+
}
|
|
110
|
+
const base = mergeTypifications(type1.base, type2.base);
|
|
111
|
+
return base ? bool(base) : null;
|
|
112
|
+
case TypeID.tuple:
|
|
113
|
+
if (type2.typeID !== TypeID.tuple) {
|
|
114
|
+
return null;
|
|
115
|
+
}
|
|
116
|
+
if (type1.factors.length !== type2.factors.length) {
|
|
117
|
+
return null;
|
|
118
|
+
}
|
|
119
|
+
const components: Typification[] = [];
|
|
120
|
+
for (let index = 0; index < type1.factors.length; ++index) {
|
|
121
|
+
const component = mergeTypifications(type1.factors[index], type2.factors[index]);
|
|
122
|
+
if (component === null) {
|
|
123
|
+
return null;
|
|
124
|
+
}
|
|
125
|
+
components.push(component);
|
|
126
|
+
}
|
|
127
|
+
return tuple(components);
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/** Check compatibility of typifications. */
|
|
132
|
+
export function checkCompatibility(type1: ExpressionType, type2: ExpressionType): boolean {
|
|
133
|
+
if (type1 === type2) {
|
|
134
|
+
return true;
|
|
135
|
+
}
|
|
136
|
+
if (type1.typeID === TypeID.anyTypification) {
|
|
137
|
+
return isTypification(type2);
|
|
138
|
+
}
|
|
139
|
+
if (type2.typeID === TypeID.anyTypification) {
|
|
140
|
+
return isTypification(type1);
|
|
141
|
+
}
|
|
142
|
+
if (isTypification(type1) !== isTypification(type2)) {
|
|
143
|
+
return false;
|
|
144
|
+
}
|
|
145
|
+
if (
|
|
146
|
+
type1.typeID === TypeID.integer ||
|
|
147
|
+
type1.typeID === TypeID.basic ||
|
|
148
|
+
type2.typeID === TypeID.integer ||
|
|
149
|
+
type2.typeID === TypeID.basic
|
|
150
|
+
) {
|
|
151
|
+
return commonType(type1 as Typification, type2 as Typification) !== null;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
if (type1.typeID !== type2.typeID) {
|
|
155
|
+
return false;
|
|
156
|
+
}
|
|
157
|
+
switch (type1.typeID) {
|
|
158
|
+
case TypeID.collection:
|
|
159
|
+
return checkCompatibility(type1.base, (type2 as EchelonCollection).base);
|
|
160
|
+
case TypeID.tuple:
|
|
161
|
+
if (type1.factors.length !== (type2 as EchelonTuple).factors.length) {
|
|
162
|
+
return false;
|
|
163
|
+
}
|
|
164
|
+
for (let index = 0; index < type1.factors.length; ++index) {
|
|
165
|
+
if (!checkCompatibility(type1.factors[index], (type2 as EchelonTuple).factors[index])) {
|
|
166
|
+
return false;
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
return true;
|
|
170
|
+
case TypeID.logic:
|
|
171
|
+
case TypeID.predicate:
|
|
172
|
+
case TypeID.function:
|
|
173
|
+
return false;
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
/** Compare typification with substitutions. */
|
|
178
|
+
export function compareTemplated(
|
|
179
|
+
substitutes: Map<string, Typification>,
|
|
180
|
+
arg: Typification,
|
|
181
|
+
value: Typification
|
|
182
|
+
): boolean {
|
|
183
|
+
if (arg === value) {
|
|
184
|
+
return true;
|
|
185
|
+
}
|
|
186
|
+
if (arg.typeID === TypeID.basic && isRadical(arg.baseID)) {
|
|
187
|
+
if (!substitutes.has(arg.baseID)) {
|
|
188
|
+
substitutes.set(arg.baseID, value);
|
|
189
|
+
return true;
|
|
190
|
+
} else {
|
|
191
|
+
const mergeType = mergeTypifications(substitutes.get(arg.baseID)!, value);
|
|
192
|
+
if (mergeType === null) {
|
|
193
|
+
return false;
|
|
194
|
+
}
|
|
195
|
+
substitutes.set(arg.baseID, mergeType);
|
|
196
|
+
return true;
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
if (value.typeID === TypeID.anyTypification) {
|
|
200
|
+
return true;
|
|
201
|
+
}
|
|
202
|
+
if (
|
|
203
|
+
arg.typeID === TypeID.integer ||
|
|
204
|
+
arg.typeID === TypeID.basic ||
|
|
205
|
+
value.typeID === TypeID.integer ||
|
|
206
|
+
value.typeID === TypeID.basic
|
|
207
|
+
) {
|
|
208
|
+
return commonType(arg, value) !== null;
|
|
209
|
+
}
|
|
210
|
+
if (arg.typeID !== value.typeID) {
|
|
211
|
+
return false;
|
|
212
|
+
}
|
|
213
|
+
switch (arg.typeID) {
|
|
214
|
+
case TypeID.collection:
|
|
215
|
+
return compareTemplated(substitutes, arg.base, (value as EchelonCollection).base);
|
|
216
|
+
case TypeID.tuple: {
|
|
217
|
+
if (arg.factors.length !== (value as EchelonTuple).factors.length) {
|
|
218
|
+
return false;
|
|
219
|
+
}
|
|
220
|
+
for (let index = 0; index < arg.factors.length; ++index) {
|
|
221
|
+
if (!compareTemplated(substitutes, arg.factors[index], (value as EchelonTuple).factors[index])) {
|
|
222
|
+
return false;
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
return true;
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
/** Apply substitutions to typification. */
|
|
231
|
+
export function substituteBase(target: Typification, substitutes: Map<string, Typification>): void {
|
|
232
|
+
switch (target.typeID) {
|
|
233
|
+
case TypeID.basic: {
|
|
234
|
+
if (substitutes.has(target.baseID)) {
|
|
235
|
+
Object.assign(target, substitutes.get(target.baseID)!);
|
|
236
|
+
}
|
|
237
|
+
return;
|
|
238
|
+
}
|
|
239
|
+
case TypeID.collection: {
|
|
240
|
+
substituteBase(target.base, substitutes);
|
|
241
|
+
return;
|
|
242
|
+
}
|
|
243
|
+
case TypeID.tuple: {
|
|
244
|
+
for (const factor of target.factors) {
|
|
245
|
+
substituteBase(factor, substitutes);
|
|
246
|
+
}
|
|
247
|
+
return;
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
/** Extract bases from typification. */
|
|
253
|
+
export function extractBases(target: ExpressionType): Set<string> {
|
|
254
|
+
return new Set(extractBasesImpl(target));
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
/** Apply type path to typification. */
|
|
258
|
+
export function applyPath(target: Typification, path: TypePath, index = 0): Typification | null {
|
|
259
|
+
let current: Typification | null = target;
|
|
260
|
+
let i = index;
|
|
261
|
+
while (i < path.length && current) {
|
|
262
|
+
switch (current.typeID) {
|
|
263
|
+
case TypeID.anyTypification:
|
|
264
|
+
case TypeID.integer:
|
|
265
|
+
case TypeID.basic:
|
|
266
|
+
return null;
|
|
267
|
+
case TypeID.collection:
|
|
268
|
+
current = current.base;
|
|
269
|
+
i++;
|
|
270
|
+
break;
|
|
271
|
+
case TypeID.tuple:
|
|
272
|
+
current = component(current, path[i]);
|
|
273
|
+
if (current === null) {
|
|
274
|
+
return null;
|
|
275
|
+
}
|
|
276
|
+
i++;
|
|
277
|
+
break;
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
return current;
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
// ===== Internals =====
|
|
284
|
+
function commonType(type1: Typification, type2: Typification): Typification | null {
|
|
285
|
+
if (type1 === type2) {
|
|
286
|
+
return type1;
|
|
287
|
+
}
|
|
288
|
+
const int1 = 'isIntegerCompatible' in type1 && type1.isIntegerCompatible;
|
|
289
|
+
const int2 = 'isIntegerCompatible' in type2 && type2.isIntegerCompatible;
|
|
290
|
+
if (!int1 || !int2) {
|
|
291
|
+
if (type1.typeID === TypeID.basic && type2.typeID === TypeID.basic) {
|
|
292
|
+
return type1.baseID === type2.baseID ? type1 : null;
|
|
293
|
+
}
|
|
294
|
+
return null;
|
|
295
|
+
}
|
|
296
|
+
if (type1.typeID === TypeID.integer) {
|
|
297
|
+
return type2;
|
|
298
|
+
} else if (type2.typeID === TypeID.integer) {
|
|
299
|
+
return type1;
|
|
300
|
+
} else {
|
|
301
|
+
return IntegerT;
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
function extractBasesImpl(target: ExpressionType): string[] {
|
|
306
|
+
switch (target.typeID) {
|
|
307
|
+
case TypeID.basic:
|
|
308
|
+
return [target.baseID];
|
|
309
|
+
case TypeID.collection:
|
|
310
|
+
return extractBasesImpl(target.base);
|
|
311
|
+
case TypeID.tuple:
|
|
312
|
+
return target.factors.reduce((result, factor) => result.concat(extractBasesImpl(factor)), [] as string[]);
|
|
313
|
+
case TypeID.function:
|
|
314
|
+
const result = extractBasesImpl(target.result);
|
|
315
|
+
return target.args.reduce((result, arg) => result.concat(extractBasesImpl(arg.type)), result);
|
|
316
|
+
case TypeID.predicate:
|
|
317
|
+
return target.args.reduce((result, arg) => result.concat(extractBasesImpl(arg.type)), [] as string[]);
|
|
318
|
+
default:
|
|
319
|
+
return [];
|
|
320
|
+
}
|
|
321
|
+
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest';
|
|
2
|
+
|
|
3
|
+
import { labelType } from '../labels';
|
|
4
|
+
|
|
5
|
+
import { TypeID } from './typification';
|
|
6
|
+
import { applyAsciiTypeSubstitutions, parseTypeText } from './typification-parser';
|
|
7
|
+
|
|
8
|
+
describe('parseTypeText', () => {
|
|
9
|
+
it('parses simple typifications', () => {
|
|
10
|
+
expect(labelType(parseTypeText('X1').type)).toBe('X1');
|
|
11
|
+
expect(labelType(parseTypeText('ℬ(X1×X2)').type)).toBe('ℬ(X1×X2)');
|
|
12
|
+
expect(labelType(parseTypeText('Z').type)).toBe('Z');
|
|
13
|
+
expect(labelType(parseTypeText('ℬℬ(X1)').type)).toBe('ℬℬ(X1)');
|
|
14
|
+
expect(labelType(parseTypeText('ℬ(ℬ(X1))').type)).toBe('ℬℬ(X1)');
|
|
15
|
+
expect(labelType(parseTypeText('X1×ℬ(X2)').type)).toBe('X1×ℬ(X2)');
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
it('parses function typification with unicode and ascii arrows', () => {
|
|
19
|
+
const unicode = parseTypeText('[X1, ℬ(X2)] → X3');
|
|
20
|
+
const ascii = parseTypeText('[X1, ℬ(X2)] -> X3');
|
|
21
|
+
|
|
22
|
+
expect(unicode.error).toBeNull();
|
|
23
|
+
expect(unicode.type?.typeID).toBe(TypeID.function);
|
|
24
|
+
expect(labelType(unicode.type)).toBe('[X1, ℬ(X2)] → X3');
|
|
25
|
+
|
|
26
|
+
expect(ascii.error).toBeNull();
|
|
27
|
+
expect(ascii.type?.typeID).toBe(TypeID.function);
|
|
28
|
+
expect(labelType(ascii.type)).toBe('[X1, ℬ(X2)] → X3');
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
it('parses predicate typification when result is logic', () => {
|
|
32
|
+
const parsed = parseTypeText('[X1] → Logic');
|
|
33
|
+
expect(parsed.error).toBeNull();
|
|
34
|
+
expect(parsed.type?.typeID).toBe(TypeID.predicate);
|
|
35
|
+
expect(labelType(parsed.type)).toBe('[X1] → Logic');
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
it('rejects invalid typification strings', () => {
|
|
39
|
+
expect(parseTypeText('[X1, ] → X2').type).toBeNull();
|
|
40
|
+
expect(parseTypeText('ℬ(X1').type).toBeNull();
|
|
41
|
+
expect(parseTypeText('ℬX1').type).toBeNull();
|
|
42
|
+
expect(parseTypeText('ℬX1×X2').type).toBeNull();
|
|
43
|
+
expect(parseTypeText('').error).toBeNull();
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
it('applies live input substitutions', () => {
|
|
47
|
+
expect(applyAsciiTypeSubstitutions('X1*X2 -> X3')).toBe('X1×X2 → X3');
|
|
48
|
+
expect(applyAsciiTypeSubstitutions('B(X1)')).toBe('ℬ(X1)');
|
|
49
|
+
});
|
|
50
|
+
});
|
|
@@ -0,0 +1,220 @@
|
|
|
1
|
+
import {
|
|
2
|
+
bool,
|
|
3
|
+
type ExpressionType,
|
|
4
|
+
IntegerT,
|
|
5
|
+
isTypification,
|
|
6
|
+
LogicT,
|
|
7
|
+
tuple,
|
|
8
|
+
TypeID,
|
|
9
|
+
type Typification
|
|
10
|
+
} from './typification';
|
|
11
|
+
|
|
12
|
+
export interface TypificationParseResult {
|
|
13
|
+
type: ExpressionType | null;
|
|
14
|
+
error: string | null;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export function parseTypeText(input: string): TypificationParseResult {
|
|
18
|
+
const normalized = input.trim().replaceAll('->', '→');
|
|
19
|
+
if (normalized.length === 0) {
|
|
20
|
+
return { type: null, error: null };
|
|
21
|
+
}
|
|
22
|
+
const parser = new TypificationParser(normalized);
|
|
23
|
+
return parser.parse();
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/** ASCII substitutions while editing typification text */
|
|
27
|
+
export function applyAsciiTypeSubstitutions(raw: string): string {
|
|
28
|
+
return raw.replaceAll('->', '→').replaceAll('*', '×').replaceAll('B', 'ℬ');
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
class TypificationParser {
|
|
32
|
+
private index = 0;
|
|
33
|
+
private input: string;
|
|
34
|
+
|
|
35
|
+
constructor(input: string) {
|
|
36
|
+
this.index = 0;
|
|
37
|
+
this.input = input;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
parse(): TypificationParseResult {
|
|
41
|
+
try {
|
|
42
|
+
const result = this.parseExpressionType();
|
|
43
|
+
this.skipSpaces();
|
|
44
|
+
if (!this.eof()) {
|
|
45
|
+
throw new Error('Unexpected trailing symbols');
|
|
46
|
+
}
|
|
47
|
+
return { type: result, error: null };
|
|
48
|
+
} catch (error) {
|
|
49
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
50
|
+
return { type: null, error: message };
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
private parseExpressionType(): ExpressionType {
|
|
55
|
+
this.skipSpaces();
|
|
56
|
+
if (this.peek() === '[') {
|
|
57
|
+
return this.parseCallableType();
|
|
58
|
+
} else {
|
|
59
|
+
return this.parseTypification();
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
private parseCallableType(): ExpressionType {
|
|
64
|
+
this.expect('[');
|
|
65
|
+
const args: Typification[] = [];
|
|
66
|
+
this.skipSpaces();
|
|
67
|
+
if (this.peek() !== ']') {
|
|
68
|
+
while (true) {
|
|
69
|
+
args.push(this.parseTypification());
|
|
70
|
+
this.skipSpaces();
|
|
71
|
+
if (this.peek() === ',') {
|
|
72
|
+
this.next();
|
|
73
|
+
this.skipSpaces();
|
|
74
|
+
if (this.peek() === ']') {
|
|
75
|
+
throw new Error('Empty argument in callable typification');
|
|
76
|
+
}
|
|
77
|
+
continue;
|
|
78
|
+
}
|
|
79
|
+
break;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
this.expect(']');
|
|
83
|
+
this.skipSpaces();
|
|
84
|
+
this.expect('→');
|
|
85
|
+
const result = this.parseCallableResult();
|
|
86
|
+
if (result.typeID === TypeID.logic) {
|
|
87
|
+
return {
|
|
88
|
+
typeID: TypeID.predicate,
|
|
89
|
+
result: LogicT,
|
|
90
|
+
args: args.map((arg, index) => ({ alias: `a${index + 1}`, type: arg }))
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
if (!isTypification(result)) {
|
|
94
|
+
throw new Error('Invalid callable result type');
|
|
95
|
+
}
|
|
96
|
+
return {
|
|
97
|
+
typeID: TypeID.function,
|
|
98
|
+
result: result as Typification,
|
|
99
|
+
args: args.map((arg, index) => ({ alias: `a${index + 1}`, type: arg }))
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
private parseCallableResult(): ExpressionType {
|
|
104
|
+
this.skipSpaces();
|
|
105
|
+
const lookahead = this.readToken();
|
|
106
|
+
this.index -= lookahead.length;
|
|
107
|
+
if (lookahead === 'Logic') {
|
|
108
|
+
this.index += lookahead.length;
|
|
109
|
+
return LogicT;
|
|
110
|
+
}
|
|
111
|
+
return this.parseTypification();
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
private parseTypification(): Typification {
|
|
115
|
+
this.skipSpaces();
|
|
116
|
+
if (this.match('ℬ')) {
|
|
117
|
+
this.skipSpaces();
|
|
118
|
+
if (this.peek() === '(') {
|
|
119
|
+
this.next();
|
|
120
|
+
const base = this.parseTypification();
|
|
121
|
+
this.expect(')');
|
|
122
|
+
return bool(base);
|
|
123
|
+
}
|
|
124
|
+
this.skipSpaces();
|
|
125
|
+
if (this.input.startsWith('ℬ', this.index)) {
|
|
126
|
+
const base = this.parseTypification();
|
|
127
|
+
return bool(base);
|
|
128
|
+
}
|
|
129
|
+
throw new Error('Expected "(" after "ℬ"');
|
|
130
|
+
}
|
|
131
|
+
const first = this.parseTypificationAtom();
|
|
132
|
+
const factors: Typification[] = [first];
|
|
133
|
+
while (true) {
|
|
134
|
+
this.skipSpaces();
|
|
135
|
+
if (!this.match('×')) {
|
|
136
|
+
break;
|
|
137
|
+
}
|
|
138
|
+
factors.push(this.parseTypificationAtom());
|
|
139
|
+
}
|
|
140
|
+
if (factors.length === 1) {
|
|
141
|
+
return first;
|
|
142
|
+
}
|
|
143
|
+
return tuple(factors);
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
private parseTypificationAtom(): Typification {
|
|
147
|
+
this.skipSpaces();
|
|
148
|
+
if (this.input.startsWith('ℬ', this.index)) {
|
|
149
|
+
return this.parseTypification();
|
|
150
|
+
}
|
|
151
|
+
if (this.peek() === '(') {
|
|
152
|
+
this.next();
|
|
153
|
+
const wrapped = this.parseTypification();
|
|
154
|
+
this.expect(')');
|
|
155
|
+
return wrapped;
|
|
156
|
+
}
|
|
157
|
+
const token = this.readToken();
|
|
158
|
+
if (token === 'Z') {
|
|
159
|
+
return IntegerT;
|
|
160
|
+
}
|
|
161
|
+
if (token === 'R0') {
|
|
162
|
+
return { typeID: TypeID.anyTypification };
|
|
163
|
+
}
|
|
164
|
+
if (!/^[A-Z]\d+$/.test(token)) {
|
|
165
|
+
throw new Error(`Unknown typification token "${token}"`);
|
|
166
|
+
}
|
|
167
|
+
return { typeID: TypeID.basic, baseID: token };
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
private readToken(): string {
|
|
171
|
+
this.skipSpaces();
|
|
172
|
+
const start = this.index;
|
|
173
|
+
while (!this.eof()) {
|
|
174
|
+
const char = this.peek();
|
|
175
|
+
if (!char || /[\s,()[\]→×]/.test(char)) {
|
|
176
|
+
break;
|
|
177
|
+
}
|
|
178
|
+
this.next();
|
|
179
|
+
}
|
|
180
|
+
if (start === this.index) {
|
|
181
|
+
throw new Error('Expected typification token');
|
|
182
|
+
}
|
|
183
|
+
return this.input.slice(start, this.index);
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
private skipSpaces(): void {
|
|
187
|
+
while (!this.eof() && /\s/.test(this.peek()!)) {
|
|
188
|
+
this.index += 1;
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
private expect(symbol: string): void {
|
|
193
|
+
this.skipSpaces();
|
|
194
|
+
if (!this.match(symbol)) {
|
|
195
|
+
throw new Error(`Expected "${symbol}"`);
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
private match(symbol: string): boolean {
|
|
200
|
+
if (this.input.startsWith(symbol, this.index)) {
|
|
201
|
+
this.index += symbol.length;
|
|
202
|
+
return true;
|
|
203
|
+
}
|
|
204
|
+
return false;
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
private peek(): string | undefined {
|
|
208
|
+
return this.input[this.index];
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
private next(): string | undefined {
|
|
212
|
+
const value = this.input[this.index];
|
|
213
|
+
this.index += 1;
|
|
214
|
+
return value;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
private eof(): boolean {
|
|
218
|
+
return this.index >= this.input.length;
|
|
219
|
+
}
|
|
220
|
+
}
|