@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,593 @@
|
|
|
1
|
+
import { type AstNode } from '../parsing';
|
|
2
|
+
import { type CalculatorEvaluateOptions, type CalculatorResult, RSCalculator, TypeID, type Value } from '../rslang';
|
|
3
|
+
import { compare } from '../rslang/eval/value';
|
|
4
|
+
import { normalizeType } from '../rslang/labels';
|
|
5
|
+
|
|
6
|
+
import { CstType, type RSForm } from './rsform';
|
|
7
|
+
import { getAnalysisFor, isBaseSet, isBasicConcept, isFunctional } from './rsform-api';
|
|
8
|
+
import { type BasicBinding, type BasicsContext, type EvalStatus, type RSModel, TYPE_BASIC } from './rsmodel';
|
|
9
|
+
import { inferEvalStatus, isInferrable, tryFixValue } from './rsmodel-api';
|
|
10
|
+
|
|
11
|
+
const INVALID_TYPE_MARKER = 'INVALID';
|
|
12
|
+
|
|
13
|
+
/** Services for {@link RSEngine}. */
|
|
14
|
+
export interface RSEngineServices {
|
|
15
|
+
setCstValue: (args: {
|
|
16
|
+
itemID: number;
|
|
17
|
+
data: { target: number; type: string; data: Value | BasicBinding }[];
|
|
18
|
+
}) => Promise<unknown>;
|
|
19
|
+
clearValues: (args: { itemID: number; data: { items: number[] } }) => Promise<unknown>;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/** Notifications for {@link RSEngine}. */
|
|
23
|
+
export interface RSEngineNotifications {
|
|
24
|
+
onInvalidSetValue: () => void;
|
|
25
|
+
onCalculationSuccess: (timeSpent: string) => void;
|
|
26
|
+
onEvaluationError: (message: string) => void;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/** Calculation engine for {@link RSModel}. */
|
|
30
|
+
export class RSEngine {
|
|
31
|
+
public modelID: number;
|
|
32
|
+
public schema: RSForm | null = null;
|
|
33
|
+
public data: RSModel | null = null;
|
|
34
|
+
public calculator = new RSCalculator();
|
|
35
|
+
public basics: BasicsContext = new Map<number, BasicBinding>();
|
|
36
|
+
|
|
37
|
+
private services: RSEngineServices;
|
|
38
|
+
private notifications: RSEngineNotifications | null;
|
|
39
|
+
private invalidData = new Set<number>();
|
|
40
|
+
private calculatedSet = new Set<number>();
|
|
41
|
+
private valueSubscribers = new Map<number, Set<() => void>>();
|
|
42
|
+
private statusSubscribers = new Map<number, Set<() => void>>();
|
|
43
|
+
private changeSubscribers = new Set<() => void>();
|
|
44
|
+
private changeGeneration = 0;
|
|
45
|
+
private pendingChange = false;
|
|
46
|
+
private coalescedEmitTimeout: ReturnType<typeof setTimeout> | null = null;
|
|
47
|
+
|
|
48
|
+
constructor(modelID: number, services: RSEngineServices, notifications: RSEngineNotifications | null = null) {
|
|
49
|
+
this.services = services;
|
|
50
|
+
this.notifications = notifications;
|
|
51
|
+
this.modelID = modelID;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/** Updates data for {@link RSEngine}. */
|
|
55
|
+
public loadData(schema: RSForm, model: RSModel): void {
|
|
56
|
+
const oldSchema = this.schema;
|
|
57
|
+
const newSchema = oldSchema !== schema;
|
|
58
|
+
this.schema = schema;
|
|
59
|
+
if (newSchema) {
|
|
60
|
+
const changedCst = this.collectChanged(oldSchema, schema);
|
|
61
|
+
this.prepareAst();
|
|
62
|
+
this.setupEmptySets();
|
|
63
|
+
this.onChangeDefinitions(changedCst);
|
|
64
|
+
}
|
|
65
|
+
if (this.data !== model) {
|
|
66
|
+
this.data = model;
|
|
67
|
+
this.prepareValues();
|
|
68
|
+
}
|
|
69
|
+
this.notifyAll();
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/** Updates services for {@link RSEngine}. */
|
|
73
|
+
public updateServices(services: RSEngineServices): void {
|
|
74
|
+
this.services = services;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/** Updates notifications for {@link RSEngine}. */
|
|
78
|
+
public updateNotifications(notifications: RSEngineNotifications | null): void {
|
|
79
|
+
this.notifications = notifications;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/** Gets value of {@link Constituenta}. */
|
|
83
|
+
public getCstValue(cstID: number): Value | null {
|
|
84
|
+
const cst = this.schema?.cstByID.get(cstID);
|
|
85
|
+
if (!cst) {
|
|
86
|
+
return null;
|
|
87
|
+
}
|
|
88
|
+
return this.calculator.getValue(cst.alias);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/** Gets status of {@link Constituenta}. */
|
|
92
|
+
public getCstStatus(cstID: number): EvalStatus {
|
|
93
|
+
const cst = this.schema?.cstByID.get(cstID);
|
|
94
|
+
if (!cst) {
|
|
95
|
+
return inferEvalStatus(null, CstType.NOMINAL, false);
|
|
96
|
+
}
|
|
97
|
+
const value = this.calculator.getValue(cst.alias);
|
|
98
|
+
return inferEvalStatus(value, cst.cst_type, this.calculatedSet.has(cstID), this.invalidData.has(cstID));
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/** Subscribe to value change of {@link Constituenta}. */
|
|
102
|
+
public subscribeValue(cstID: number, callbackFn: () => void): () => void {
|
|
103
|
+
let subs = this.valueSubscribers.get(cstID);
|
|
104
|
+
if (!subs) {
|
|
105
|
+
subs = new Set();
|
|
106
|
+
this.valueSubscribers.set(cstID, subs);
|
|
107
|
+
}
|
|
108
|
+
subs.add(callbackFn);
|
|
109
|
+
return () => {
|
|
110
|
+
const current = this.valueSubscribers.get(cstID);
|
|
111
|
+
if (!current) {
|
|
112
|
+
return;
|
|
113
|
+
}
|
|
114
|
+
current.delete(callbackFn);
|
|
115
|
+
if (current.size === 0) {
|
|
116
|
+
this.valueSubscribers.delete(cstID);
|
|
117
|
+
}
|
|
118
|
+
};
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/** Subscribe to status change of {@link Constituenta}. */
|
|
122
|
+
public subscribeStatus(cstID: number, callbackFn: () => void): () => void {
|
|
123
|
+
let subs = this.statusSubscribers.get(cstID);
|
|
124
|
+
if (!subs) {
|
|
125
|
+
subs = new Set();
|
|
126
|
+
this.statusSubscribers.set(cstID, subs);
|
|
127
|
+
}
|
|
128
|
+
subs.add(callbackFn);
|
|
129
|
+
return () => {
|
|
130
|
+
const current = this.statusSubscribers.get(cstID);
|
|
131
|
+
if (!current) {
|
|
132
|
+
return;
|
|
133
|
+
}
|
|
134
|
+
current.delete(callbackFn);
|
|
135
|
+
if (current.size === 0) {
|
|
136
|
+
this.statusSubscribers.delete(cstID);
|
|
137
|
+
}
|
|
138
|
+
};
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* Subscribe to any engine change that can affect evaluation (values, status, or loaded data).
|
|
143
|
+
*/
|
|
144
|
+
public subscribeChanges(callbackFn: () => void): () => void {
|
|
145
|
+
this.changeSubscribers.add(callbackFn);
|
|
146
|
+
return () => {
|
|
147
|
+
this.changeSubscribers.delete(callbackFn);
|
|
148
|
+
};
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
/** Monotonic counter bumped whenever the engine emits a change to {@link RSEngine.subscribeChanges} listeners. */
|
|
152
|
+
public getChangeGeneration(): number {
|
|
153
|
+
return this.changeGeneration;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
/**
|
|
157
|
+
* Runs pending {@link RSEngine.subscribeChanges} notifications immediately and clears the coalescing queue.
|
|
158
|
+
* Use after a synchronous batch of engine updates when listeners must observe a bumped {@link getChangeGeneration}
|
|
159
|
+
* in the same turn.
|
|
160
|
+
*/
|
|
161
|
+
public flushPendingChanges(): void {
|
|
162
|
+
if (this.coalescedEmitTimeout !== null) {
|
|
163
|
+
clearTimeout(this.coalescedEmitTimeout);
|
|
164
|
+
this.coalescedEmitTimeout = null;
|
|
165
|
+
}
|
|
166
|
+
if (!this.pendingChange) {
|
|
167
|
+
return;
|
|
168
|
+
}
|
|
169
|
+
this.pendingChange = false;
|
|
170
|
+
this.emitChange();
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
/** Sets value for {@link Constituenta} from {@link Value}. */
|
|
174
|
+
public async setStructureValue(cstID: number, data: Value): Promise<void> {
|
|
175
|
+
const cst = this.schema?.cstByID.get(cstID);
|
|
176
|
+
if (!this.schema || !cst || isInferrable(cst.cst_type)) {
|
|
177
|
+
this.notifications?.onInvalidSetValue();
|
|
178
|
+
return;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
const typeStr = cst.effectiveType ? normalizeType(cst.effectiveType) : INVALID_TYPE_MARKER;
|
|
182
|
+
const payload = [{ target: cstID, type: typeStr, data }];
|
|
183
|
+
await this.services.setCstValue({ itemID: this.modelID, data: payload });
|
|
184
|
+
|
|
185
|
+
if (!cst.effectiveType || !this.calculator.validate(data, cst.effectiveType)) {
|
|
186
|
+
this.invalidData.add(cstID);
|
|
187
|
+
} else {
|
|
188
|
+
this.invalidData.delete(cstID);
|
|
189
|
+
}
|
|
190
|
+
this.calculator.setValue(cst.alias, data);
|
|
191
|
+
this.notifyCst(cstID);
|
|
192
|
+
this.cascadeReset([cstID]);
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
/** Sets value for {@link Constituenta} from {@link BasicBinding}. */
|
|
196
|
+
public async setBasicValue(cstID: number, data: BasicBinding): Promise<void> {
|
|
197
|
+
const cst = this.schema?.cstByID.get(cstID);
|
|
198
|
+
if (!this.schema || !cst || !isBaseSet(cst.cst_type)) {
|
|
199
|
+
this.notifications?.onInvalidSetValue();
|
|
200
|
+
return;
|
|
201
|
+
}
|
|
202
|
+
const oldValue = this.calculator.getValue(cst.alias);
|
|
203
|
+
const newValue = Object.keys(data).map(Number);
|
|
204
|
+
|
|
205
|
+
const updateList: Parameters<RSEngineServices['setCstValue']>[0]['data'] = [
|
|
206
|
+
{ target: cstID, type: TYPE_BASIC, data }
|
|
207
|
+
];
|
|
208
|
+
const resetList: number[] = [];
|
|
209
|
+
|
|
210
|
+
if (oldValue !== null && compare(newValue, oldValue) !== 0) {
|
|
211
|
+
const dependencies = this.schema.graph.expandAllOutputs([cstID]);
|
|
212
|
+
for (const childID of dependencies) {
|
|
213
|
+
const child = this.schema.cstByID.get(childID)!;
|
|
214
|
+
if (child.cst_type === CstType.STRUCTURED && !!child.effectiveType) {
|
|
215
|
+
const value = this.calculator.getValue(child.alias);
|
|
216
|
+
if (value !== null) {
|
|
217
|
+
const fix = tryFixValue(value, child.effectiveType, cst.alias, newValue);
|
|
218
|
+
if (fix === null) {
|
|
219
|
+
resetList.push(childID);
|
|
220
|
+
} else if (fix === true) {
|
|
221
|
+
const typeStr = normalizeType(child.effectiveType);
|
|
222
|
+
updateList.push({ target: childID, type: typeStr, data: [...(value as Value[])] });
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
if (resetList.length > 0) {
|
|
230
|
+
await Promise.all([
|
|
231
|
+
this.services.setCstValue({ itemID: this.modelID, data: updateList }),
|
|
232
|
+
this.services.clearValues({ itemID: this.modelID, data: { items: resetList } })
|
|
233
|
+
]);
|
|
234
|
+
} else {
|
|
235
|
+
await this.services.setCstValue({ itemID: this.modelID, data: updateList });
|
|
236
|
+
}
|
|
237
|
+
const changed = [...resetList, ...updateList.map(item => item.target)];
|
|
238
|
+
|
|
239
|
+
this.basics.set(cstID, data);
|
|
240
|
+
this.calculator.setValue(cst.alias, Object.keys(data).map(Number));
|
|
241
|
+
|
|
242
|
+
for (const item of resetList) {
|
|
243
|
+
this.calculator.resetValue(this.schema.cstByID.get(item)!.alias);
|
|
244
|
+
this.notifyCst(item);
|
|
245
|
+
}
|
|
246
|
+
for (const updateData of updateList) {
|
|
247
|
+
if (updateData.target !== cstID) {
|
|
248
|
+
this.calculator.setValue(
|
|
249
|
+
this.schema.cstByID.get(updateData.target)!.alias,
|
|
250
|
+
updateData.data as unknown as Value
|
|
251
|
+
);
|
|
252
|
+
this.notifyCst(updateData.target);
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
for (const item of changed) {
|
|
256
|
+
this.notifyCst(item);
|
|
257
|
+
}
|
|
258
|
+
this.cascadeReset(changed);
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
/** Resets value for {@link Constituenta}. */
|
|
262
|
+
public async resetValue(cstID: number): Promise<void> {
|
|
263
|
+
const cst = this.schema?.cstByID.get(cstID);
|
|
264
|
+
if (!cst) {
|
|
265
|
+
return;
|
|
266
|
+
}
|
|
267
|
+
await this.services.clearValues({ itemID: this.modelID, data: { items: [cstID] } });
|
|
268
|
+
this.calculator.resetValue(cst.alias);
|
|
269
|
+
this.basics.delete(cstID);
|
|
270
|
+
this.calculatedSet.delete(cstID);
|
|
271
|
+
this.invalidData.delete(cstID);
|
|
272
|
+
this.notifyCst(cstID);
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
/** Evaluates expression for {@link RSEngine}. */
|
|
276
|
+
public evaluateExpression(expression: string, cstType: CstType): CalculatorResult {
|
|
277
|
+
return getEvaluationFor(expression, cstType, this.schema!, this.calculator, message =>
|
|
278
|
+
this.notifications?.onEvaluationError(message)
|
|
279
|
+
);
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
/** Evaluates AST for {@link RSEngine}. */
|
|
283
|
+
public evaluateAst(ast: AstNode, options?: CalculatorEvaluateOptions): CalculatorResult {
|
|
284
|
+
try {
|
|
285
|
+
const evaluation = this.calculator.evaluateFull(ast, options);
|
|
286
|
+
return evaluation;
|
|
287
|
+
} catch (error) {
|
|
288
|
+
this.notifications?.onEvaluationError((error as Error).message);
|
|
289
|
+
console.error(error);
|
|
290
|
+
return {
|
|
291
|
+
value: null,
|
|
292
|
+
iterations: 0,
|
|
293
|
+
cacheHits: 0,
|
|
294
|
+
errors: []
|
|
295
|
+
};
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
/** Calculates value for {@link Constituenta}. */
|
|
300
|
+
public calculateCst(cstID: number): CalculatorResult {
|
|
301
|
+
const cst = this.schema?.cstByID.get(cstID);
|
|
302
|
+
if (!cst || !this.schema) {
|
|
303
|
+
return { value: null, iterations: 0, errors: [], cacheHits: 0 };
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
if (!this.calculatedSet.has(cstID)) {
|
|
307
|
+
const predecessors = this.schema.graph.expandAllInputs([cstID]);
|
|
308
|
+
this.prepareEvaluation(predecessors);
|
|
309
|
+
}
|
|
310
|
+
const result = getEvaluationFor(cst.definition_formal, cst.cst_type, this.schema, this.calculator, message =>
|
|
311
|
+
this.notifications?.onEvaluationError(message)
|
|
312
|
+
);
|
|
313
|
+
|
|
314
|
+
if (result.value === null) {
|
|
315
|
+
this.calculator.resetValue(cst.alias);
|
|
316
|
+
} else {
|
|
317
|
+
this.calculator.setValue(cst.alias, result.value);
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
this.calculatedSet.add(cstID);
|
|
321
|
+
this.notifyCst(cstID);
|
|
322
|
+
|
|
323
|
+
return result;
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
/** Recalculate model for all inferrable expressions. */
|
|
327
|
+
recalculateAll(): void {
|
|
328
|
+
const start = performance.now();
|
|
329
|
+
this.calculatedSet.clear();
|
|
330
|
+
this.recalculateInternal();
|
|
331
|
+
const end = performance.now();
|
|
332
|
+
const timeSpent = ((end - start) / 1000).toFixed(2);
|
|
333
|
+
this.notifications?.onCalculationSuccess(timeSpent);
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
/** Notify subscribers about value and status change of {@link Constituenta}. */
|
|
337
|
+
private notifyCst(cstID: number) {
|
|
338
|
+
this.notifyStatus(cstID);
|
|
339
|
+
this.notifyValue(cstID);
|
|
340
|
+
this.scheduleCoalescedEmitChange();
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
/** Notify all subscribers about value change. */
|
|
344
|
+
private notifyValue(cstID: number) {
|
|
345
|
+
const subs = this.valueSubscribers.get(cstID);
|
|
346
|
+
if (subs) {
|
|
347
|
+
for (const cb of subs) cb();
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
/** Notify all subscribers about status change. */
|
|
352
|
+
private notifyStatus(cstID: number) {
|
|
353
|
+
const subs = this.statusSubscribers.get(cstID);
|
|
354
|
+
if (subs) {
|
|
355
|
+
for (const cb of subs) cb();
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
/** Notify all subscribers about value and status change. */
|
|
360
|
+
private notifyAll(): void {
|
|
361
|
+
for (const subs of this.valueSubscribers.values()) {
|
|
362
|
+
for (const cb of subs) cb();
|
|
363
|
+
}
|
|
364
|
+
for (const subs of this.statusSubscribers.values()) {
|
|
365
|
+
for (const cb of subs) cb();
|
|
366
|
+
}
|
|
367
|
+
this.scheduleCoalescedEmitChange();
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
private scheduleCoalescedEmitChange(): void {
|
|
371
|
+
this.pendingChange = true;
|
|
372
|
+
if (this.coalescedEmitTimeout !== null) {
|
|
373
|
+
return;
|
|
374
|
+
}
|
|
375
|
+
this.coalescedEmitTimeout = setTimeout(
|
|
376
|
+
function runCoalescedEmitChange(this: RSEngine) {
|
|
377
|
+
this.coalescedEmitTimeout = null;
|
|
378
|
+
if (!this.pendingChange) {
|
|
379
|
+
return;
|
|
380
|
+
}
|
|
381
|
+
this.pendingChange = false;
|
|
382
|
+
this.emitChange();
|
|
383
|
+
}.bind(this),
|
|
384
|
+
0
|
|
385
|
+
);
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
private emitChange(): void {
|
|
389
|
+
this.changeGeneration += 1;
|
|
390
|
+
for (const cb of this.changeSubscribers) {
|
|
391
|
+
cb();
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
private prepareAst(): void {
|
|
396
|
+
this.calculator.clearAllAst();
|
|
397
|
+
const functions = this.schema!.items.filter(cst => isFunctional(cst.cst_type) && cst.analysis?.success);
|
|
398
|
+
for (const cst of functions) {
|
|
399
|
+
const fullAnalysis = getAnalysisFor(cst.definition_formal, cst.cst_type, this.schema!);
|
|
400
|
+
if (fullAnalysis.ast) {
|
|
401
|
+
this.calculator.setAST(cst.alias, fullAnalysis.ast);
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
private prepareValues(): void {
|
|
407
|
+
this.basics.clear();
|
|
408
|
+
this.invalidData.clear();
|
|
409
|
+
this.calculatedSet.clear();
|
|
410
|
+
|
|
411
|
+
for (const item of this.data!.items) {
|
|
412
|
+
const cst = this.schema!.cstByID.get(item.id)!;
|
|
413
|
+
if (item.type === TYPE_BASIC) {
|
|
414
|
+
if (cst.cst_type !== CstType.BASE && cst.cst_type !== CstType.CONSTANT) {
|
|
415
|
+
throw new Error(`Invalid data for ${cst.alias}`);
|
|
416
|
+
}
|
|
417
|
+
const data = item.value as BasicBinding;
|
|
418
|
+
this.basics.set(cst.id, data);
|
|
419
|
+
this.calculator.setValue(cst.alias, Object.keys(data).map(Number));
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
for (const item of this.data!.items) {
|
|
423
|
+
const cst = this.schema!.cstByID.get(item.id)!;
|
|
424
|
+
if (item.type !== TYPE_BASIC) {
|
|
425
|
+
this.calculator.setValue(cst.alias, item.value as Value);
|
|
426
|
+
if (!cst.effectiveType || !this.calculator.validate(item.value as Value, cst.effectiveType)) {
|
|
427
|
+
this.invalidData.add(item.id);
|
|
428
|
+
}
|
|
429
|
+
}
|
|
430
|
+
}
|
|
431
|
+
this.setupEmptySets();
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
private setupEmptySets(): void {
|
|
435
|
+
for (const cst of this.schema!.items) {
|
|
436
|
+
if (isBasicConcept(cst.cst_type) && this.schema!.analyzer.getType(cst.alias)?.typeID === TypeID.collection) {
|
|
437
|
+
if (this.calculator.getValue(cst.alias) === null) {
|
|
438
|
+
this.calculator.setValue(cst.alias, []);
|
|
439
|
+
}
|
|
440
|
+
}
|
|
441
|
+
}
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
private collectChanged(previousSchema: RSForm | null, nextSchema: RSForm): number[] {
|
|
445
|
+
if (!previousSchema) {
|
|
446
|
+
return [];
|
|
447
|
+
}
|
|
448
|
+
const changedIDs: number[] = [];
|
|
449
|
+
for (const cst of nextSchema.items) {
|
|
450
|
+
const prev = previousSchema.cstByID.get(cst.id);
|
|
451
|
+
if (prev && (prev.definition_formal !== cst.definition_formal || prev.alias !== cst.alias)) {
|
|
452
|
+
changedIDs.push(cst.id);
|
|
453
|
+
}
|
|
454
|
+
}
|
|
455
|
+
return changedIDs;
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
private onChangeDefinitions(cstIDs: number[]): void {
|
|
459
|
+
if (!this.schema || cstIDs.length === 0) {
|
|
460
|
+
return;
|
|
461
|
+
}
|
|
462
|
+
for (const cstID of cstIDs) {
|
|
463
|
+
const cst = this.schema.cstByID.get(cstID);
|
|
464
|
+
if (!cst || !isInferrable(cst.cst_type)) {
|
|
465
|
+
continue;
|
|
466
|
+
}
|
|
467
|
+
this.calculatedSet.delete(cstID);
|
|
468
|
+
this.calculator.resetValue(cst.alias);
|
|
469
|
+
this.notifyCst(cstID);
|
|
470
|
+
}
|
|
471
|
+
this.cascadeReset(cstIDs);
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
private cascadeReset(cstIDs: number[]): void {
|
|
475
|
+
if (!this.schema) {
|
|
476
|
+
return;
|
|
477
|
+
}
|
|
478
|
+
const dependencies = this.schema.graph.expandAllOutputs(cstIDs);
|
|
479
|
+
for (const cstID of dependencies) {
|
|
480
|
+
const cst = this.schema.cstByID.get(cstID);
|
|
481
|
+
if (!cst || !isInferrable(cst.cst_type)) {
|
|
482
|
+
continue;
|
|
483
|
+
}
|
|
484
|
+
this.calculatedSet.delete(cstID);
|
|
485
|
+
this.calculator.resetValue(this.schema.cstByID.get(cstID)!.alias);
|
|
486
|
+
this.notifyCst(cstID);
|
|
487
|
+
}
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
private prepareEvaluation(dependencies: number[]): void {
|
|
491
|
+
for (const cstID of this.schema!.graph.topologicalOrder()) {
|
|
492
|
+
if (dependencies.includes(cstID)) {
|
|
493
|
+
const cst = this.schema!.cstByID.get(cstID)!;
|
|
494
|
+
if (isInferrable(cst.cst_type)) {
|
|
495
|
+
const value = fastEvaluation(cst.definition_formal, cst.cst_type, this.schema!, this.calculator, message =>
|
|
496
|
+
this.notifications?.onEvaluationError(message)
|
|
497
|
+
);
|
|
498
|
+
if (value !== null) {
|
|
499
|
+
this.calculator.setValue(cst.alias, value);
|
|
500
|
+
} else {
|
|
501
|
+
this.calculator.resetValue(cst.alias);
|
|
502
|
+
}
|
|
503
|
+
this.notifyCst(cstID);
|
|
504
|
+
}
|
|
505
|
+
this.calculatedSet.add(cstID);
|
|
506
|
+
}
|
|
507
|
+
}
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
private recalculateInternal(): void {
|
|
511
|
+
const processedIDs = [];
|
|
512
|
+
for (const cst of this.schema!.cstByID.values()) {
|
|
513
|
+
if (isInferrable(cst.cst_type)) {
|
|
514
|
+
this.calculator.resetValue(cst.alias);
|
|
515
|
+
}
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
for (const cstID of this.schema!.graph.topologicalOrder()) {
|
|
519
|
+
processedIDs.push(cstID);
|
|
520
|
+
const cst = this.schema!.cstByID.get(cstID)!;
|
|
521
|
+
if (isInferrable(cst.cst_type)) {
|
|
522
|
+
const value = fastEvaluation(cst.definition_formal, cst.cst_type, this.schema!, this.calculator);
|
|
523
|
+
this.calculatedSet.add(cstID);
|
|
524
|
+
if (value !== null) {
|
|
525
|
+
this.calculator.setValue(cst.alias, value);
|
|
526
|
+
}
|
|
527
|
+
}
|
|
528
|
+
}
|
|
529
|
+
this.notifyAll();
|
|
530
|
+
}
|
|
531
|
+
}
|
|
532
|
+
|
|
533
|
+
// ==== Internal functions ==== /
|
|
534
|
+
|
|
535
|
+
/** Evaluates expression for {@link Constituenta}, including error handling. */
|
|
536
|
+
function getEvaluationFor(
|
|
537
|
+
expression: string,
|
|
538
|
+
cstType: CstType,
|
|
539
|
+
schema: RSForm,
|
|
540
|
+
calculator: RSCalculator,
|
|
541
|
+
onEvaluationError?: (message: string) => void
|
|
542
|
+
): CalculatorResult {
|
|
543
|
+
const parse = getAnalysisFor(expression, cstType, schema);
|
|
544
|
+
if (!parse.success || !parse.ast) {
|
|
545
|
+
return {
|
|
546
|
+
value: null,
|
|
547
|
+
iterations: 0,
|
|
548
|
+
cacheHits: 0,
|
|
549
|
+
errors: parse.errors
|
|
550
|
+
};
|
|
551
|
+
} else {
|
|
552
|
+
try {
|
|
553
|
+
const result = calculator.evaluateFull(parse.ast);
|
|
554
|
+
return {
|
|
555
|
+
value: result.value,
|
|
556
|
+
iterations: result.iterations,
|
|
557
|
+
cacheHits: result.cacheHits,
|
|
558
|
+
errors: [...parse.errors, ...result.errors]
|
|
559
|
+
};
|
|
560
|
+
} catch (error) {
|
|
561
|
+
onEvaluationError?.((error as Error).message);
|
|
562
|
+
console.error(expression, error);
|
|
563
|
+
return {
|
|
564
|
+
value: null,
|
|
565
|
+
iterations: 0,
|
|
566
|
+
cacheHits: 0,
|
|
567
|
+
errors: []
|
|
568
|
+
};
|
|
569
|
+
}
|
|
570
|
+
}
|
|
571
|
+
}
|
|
572
|
+
|
|
573
|
+
/** Evaluates expression for {@link RSModel}. */
|
|
574
|
+
function fastEvaluation(
|
|
575
|
+
expression: string,
|
|
576
|
+
cstType: CstType,
|
|
577
|
+
schema: RSForm,
|
|
578
|
+
calculator: RSCalculator,
|
|
579
|
+
onEvaluationError?: (message: string) => void
|
|
580
|
+
): Value | null {
|
|
581
|
+
const parse = getAnalysisFor(expression, cstType, schema);
|
|
582
|
+
if (!parse.success || !parse.ast) {
|
|
583
|
+
return null;
|
|
584
|
+
} else {
|
|
585
|
+
try {
|
|
586
|
+
return calculator.evaluateFast(parse.ast);
|
|
587
|
+
} catch (error) {
|
|
588
|
+
onEvaluationError?.((error as Error).message);
|
|
589
|
+
console.error(expression, error);
|
|
590
|
+
return null;
|
|
591
|
+
}
|
|
592
|
+
}
|
|
593
|
+
}
|