@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.
Files changed (224) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +55 -0
  3. package/dist/cctext/index.d.ts +1 -0
  4. package/dist/cctext/index.js +42 -0
  5. package/dist/cctext/index.js.map +1 -0
  6. package/dist/cctext/language-api.d.ts +43 -0
  7. package/dist/cctext/language-api.js +252 -0
  8. package/dist/cctext/language-api.js.map +1 -0
  9. package/dist/cctext/language.d.ts +58 -0
  10. package/dist/cctext/language.js +44 -0
  11. package/dist/cctext/language.js.map +1 -0
  12. package/dist/graph/graph.d.ts +62 -0
  13. package/dist/graph/graph.js +385 -0
  14. package/dist/graph/graph.js.map +1 -0
  15. package/dist/graph/index.d.ts +1 -0
  16. package/dist/graph/index.js +384 -0
  17. package/dist/graph/index.js.map +1 -0
  18. package/dist/index.d.ts +28 -0
  19. package/dist/index.js +5851 -0
  20. package/dist/index.js.map +1 -0
  21. package/dist/library/folder-tree.d.ts +32 -0
  22. package/dist/library/folder-tree.js +136 -0
  23. package/dist/library/folder-tree.js.map +1 -0
  24. package/dist/library/index.d.ts +17 -0
  25. package/dist/library/index.js +2800 -0
  26. package/dist/library/index.js.map +1 -0
  27. package/dist/library/library-api.d.ts +6 -0
  28. package/dist/library/library-api.js +13 -0
  29. package/dist/library/library-api.js.map +1 -0
  30. package/dist/library/library.d.ts +56 -0
  31. package/dist/library/library.js +23 -0
  32. package/dist/library/library.js.map +1 -0
  33. package/dist/library/oss-api.d.ts +47 -0
  34. package/dist/library/oss-api.js +1105 -0
  35. package/dist/library/oss-api.js.map +1 -0
  36. package/dist/library/oss-layout-api.d.ts +36 -0
  37. package/dist/library/oss-layout-api.js +330 -0
  38. package/dist/library/oss-layout-api.js.map +1 -0
  39. package/dist/library/oss-layout.d.ts +25 -0
  40. package/dist/library/oss-layout.js +1 -0
  41. package/dist/library/oss-layout.js.map +1 -0
  42. package/dist/library/oss.d.ts +136 -0
  43. package/dist/library/oss.js +30 -0
  44. package/dist/library/oss.js.map +1 -0
  45. package/dist/library/rsengine.d.ts +116 -0
  46. package/dist/library/rsengine.js +2604 -0
  47. package/dist/library/rsengine.js.map +1 -0
  48. package/dist/library/rsform-api.d.ts +74 -0
  49. package/dist/library/rsform-api.js +879 -0
  50. package/dist/library/rsform-api.js.map +1 -0
  51. package/dist/library/rsform.d.ts +206 -0
  52. package/dist/library/rsform.js +32 -0
  53. package/dist/library/rsform.js.map +1 -0
  54. package/dist/library/rsmodel-api.d.ts +43 -0
  55. package/dist/library/rsmodel-api.js +836 -0
  56. package/dist/library/rsmodel-api.js.map +1 -0
  57. package/dist/library/rsmodel.d.ts +52 -0
  58. package/dist/library/rsmodel.js +25 -0
  59. package/dist/library/rsmodel.js.map +1 -0
  60. package/dist/library/structure-planner.d.ts +33 -0
  61. package/dist/library/structure-planner.js +481 -0
  62. package/dist/library/structure-planner.js.map +1 -0
  63. package/dist/parsing/ast.d.ts +49 -0
  64. package/dist/parsing/ast.js +93 -0
  65. package/dist/parsing/ast.js.map +1 -0
  66. package/dist/parsing/index.d.ts +3 -0
  67. package/dist/parsing/index.js +141 -0
  68. package/dist/parsing/index.js.map +1 -0
  69. package/dist/parsing/lezer-tree.d.ts +13 -0
  70. package/dist/parsing/lezer-tree.js +50 -0
  71. package/dist/parsing/lezer-tree.js.map +1 -0
  72. package/dist/rslang/api.d.ts +53 -0
  73. package/dist/rslang/api.js +846 -0
  74. package/dist/rslang/api.js.map +1 -0
  75. package/dist/rslang/ast-annotations.d.ts +18 -0
  76. package/dist/rslang/ast-annotations.js +56 -0
  77. package/dist/rslang/ast-annotations.js.map +1 -0
  78. package/dist/rslang/error.d.ts +85 -0
  79. package/dist/rslang/error.js +159 -0
  80. package/dist/rslang/error.js.map +1 -0
  81. package/dist/rslang/eval/calculator.d.ts +43 -0
  82. package/dist/rslang/eval/calculator.js +1639 -0
  83. package/dist/rslang/eval/calculator.js.map +1 -0
  84. package/dist/rslang/eval/evaluation-cache.d.ts +36 -0
  85. package/dist/rslang/eval/evaluation-cache.js +310 -0
  86. package/dist/rslang/eval/evaluation-cache.js.map +1 -0
  87. package/dist/rslang/eval/evaluator.d.ts +70 -0
  88. package/dist/rslang/eval/evaluator.js +1514 -0
  89. package/dist/rslang/eval/evaluator.js.map +1 -0
  90. package/dist/rslang/eval/value-api.d.ts +48 -0
  91. package/dist/rslang/eval/value-api.js +490 -0
  92. package/dist/rslang/eval/value-api.js.map +1 -0
  93. package/dist/rslang/eval/value.d.ts +36 -0
  94. package/dist/rslang/eval/value.js +118 -0
  95. package/dist/rslang/eval/value.js.map +1 -0
  96. package/dist/rslang/index.d.ts +17 -0
  97. package/dist/rslang/index.js +4314 -0
  98. package/dist/rslang/index.js.map +1 -0
  99. package/dist/rslang/labels.d.ts +16 -0
  100. package/dist/rslang/labels.js +315 -0
  101. package/dist/rslang/labels.js.map +1 -0
  102. package/dist/rslang/parser/expression-generator.d.ts +10 -0
  103. package/dist/rslang/parser/expression-generator.js +451 -0
  104. package/dist/rslang/parser/expression-generator.js.map +1 -0
  105. package/dist/rslang/parser/normalize.d.ts +11 -0
  106. package/dist/rslang/parser/normalize.js +507 -0
  107. package/dist/rslang/parser/normalize.js.map +1 -0
  108. package/dist/rslang/parser/parser.d.ts +5 -0
  109. package/dist/rslang/parser/parser.js +24 -0
  110. package/dist/rslang/parser/parser.js.map +1 -0
  111. package/dist/rslang/parser/parser.terms.d.ts +42 -0
  112. package/dist/rslang/parser/parser.terms.js +84 -0
  113. package/dist/rslang/parser/parser.terms.js.map +1 -0
  114. package/dist/rslang/parser/syntax-errors.d.ts +11 -0
  115. package/dist/rslang/parser/syntax-errors.js +403 -0
  116. package/dist/rslang/parser/syntax-errors.js.map +1 -0
  117. package/dist/rslang/parser/token.d.ts +79 -0
  118. package/dist/rslang/parser/token.js +95 -0
  119. package/dist/rslang/parser/token.js.map +1 -0
  120. package/dist/rslang/semantic/analyzer.d.ts +39 -0
  121. package/dist/rslang/semantic/analyzer.js +2604 -0
  122. package/dist/rslang/semantic/analyzer.js.map +1 -0
  123. package/dist/rslang/semantic/arguments-extractor.d.ts +42 -0
  124. package/dist/rslang/semantic/arguments-extractor.js +366 -0
  125. package/dist/rslang/semantic/arguments-extractor.js.map +1 -0
  126. package/dist/rslang/semantic/type-auditor.d.ts +73 -0
  127. package/dist/rslang/semantic/type-auditor.js +1570 -0
  128. package/dist/rslang/semantic/type-auditor.js.map +1 -0
  129. package/dist/rslang/semantic/typification-api.d.ts +27 -0
  130. package/dist/rslang/semantic/typification-api.js +320 -0
  131. package/dist/rslang/semantic/typification-api.js.map +1 -0
  132. package/dist/rslang/semantic/typification-parser.d.ts +12 -0
  133. package/dist/rslang/semantic/typification-parser.js +226 -0
  134. package/dist/rslang/semantic/typification-parser.js.map +1 -0
  135. package/dist/rslang/semantic/typification.d.ts +119 -0
  136. package/dist/rslang/semantic/typification.js +74 -0
  137. package/dist/rslang/semantic/typification.js.map +1 -0
  138. package/dist/rslang/semantic/value-auditor.d.ts +43 -0
  139. package/dist/rslang/semantic/value-auditor.js +523 -0
  140. package/dist/rslang/semantic/value-auditor.js.map +1 -0
  141. package/dist/rslang/semantic/value-class.d.ts +10 -0
  142. package/dist/rslang/semantic/value-class.js +9 -0
  143. package/dist/rslang/semantic/value-class.js.map +1 -0
  144. package/dist/rslang/typification-graph.d.ts +33 -0
  145. package/dist/rslang/typification-graph.js +311 -0
  146. package/dist/rslang/typification-graph.js.map +1 -0
  147. package/dist/shared/branded.d.ts +7 -0
  148. package/dist/shared/branded.js +1 -0
  149. package/dist/shared/branded.js.map +1 -0
  150. package/dist/shared/hash.d.ts +6 -0
  151. package/dist/shared/hash.js +18 -0
  152. package/dist/shared/hash.js.map +1 -0
  153. package/dist/shared/index.d.ts +2 -0
  154. package/dist/shared/index.js +18 -0
  155. package/dist/shared/index.js.map +1 -0
  156. package/package.json +184 -0
  157. package/src/cctext/index.ts +9 -0
  158. package/src/cctext/language-api.test.ts +149 -0
  159. package/src/cctext/language-api.ts +285 -0
  160. package/src/cctext/language.ts +80 -0
  161. package/src/graph/graph.test.ts +392 -0
  162. package/src/graph/graph.ts +433 -0
  163. package/src/graph/index.ts +1 -0
  164. package/src/index.ts +96 -0
  165. package/src/library/folder-tree.test.ts +47 -0
  166. package/src/library/folder-tree.ts +156 -0
  167. package/src/library/index.ts +46 -0
  168. package/src/library/library-api.test.ts +32 -0
  169. package/src/library/library-api.ts +11 -0
  170. package/src/library/library.ts +61 -0
  171. package/src/library/oss-api.ts +449 -0
  172. package/src/library/oss-layout-api.ts +377 -0
  173. package/src/library/oss-layout.ts +27 -0
  174. package/src/library/oss.ts +150 -0
  175. package/src/library/rsengine.ts +593 -0
  176. package/src/library/rsform-api.ts +533 -0
  177. package/src/library/rsform.ts +228 -0
  178. package/src/library/rsmodel-api.ts +340 -0
  179. package/src/library/rsmodel.ts +50 -0
  180. package/src/library/structure-planner.ts +143 -0
  181. package/src/parsing/ast.ts +136 -0
  182. package/src/parsing/index.ts +15 -0
  183. package/src/parsing/lezer-tree.ts +69 -0
  184. package/src/rslang/api.test.ts +116 -0
  185. package/src/rslang/api.ts +183 -0
  186. package/src/rslang/ast-annotations.ts +70 -0
  187. package/src/rslang/error.ts +129 -0
  188. package/src/rslang/eval/calculator.test.ts +124 -0
  189. package/src/rslang/eval/calculator.ts +121 -0
  190. package/src/rslang/eval/evaluation-cache.ts +257 -0
  191. package/src/rslang/eval/evaluator.test.ts +352 -0
  192. package/src/rslang/eval/evaluator.ts +935 -0
  193. package/src/rslang/eval/value-api.test.ts +105 -0
  194. package/src/rslang/eval/value-api.ts +444 -0
  195. package/src/rslang/eval/value.ts +102 -0
  196. package/src/rslang/index.ts +23 -0
  197. package/src/rslang/labels.ts +191 -0
  198. package/src/rslang/parser/expression-generator.test.ts +100 -0
  199. package/src/rslang/parser/expression-generator.ts +466 -0
  200. package/src/rslang/parser/normalize.test.ts +99 -0
  201. package/src/rslang/parser/normalize.ts +462 -0
  202. package/src/rslang/parser/parser.terms.ts +42 -0
  203. package/src/rslang/parser/parser.test.ts +153 -0
  204. package/src/rslang/parser/parser.ts +20 -0
  205. package/src/rslang/parser/rslang.grammar +251 -0
  206. package/src/rslang/parser/syntax-errors.ts +209 -0
  207. package/src/rslang/parser/token.ts +106 -0
  208. package/src/rslang/semantic/analyzer.test.ts +59 -0
  209. package/src/rslang/semantic/analyzer.ts +179 -0
  210. package/src/rslang/semantic/arguments-extractor.ts +327 -0
  211. package/src/rslang/semantic/type-auditor.test.ts +326 -0
  212. package/src/rslang/semantic/type-auditor.ts +1049 -0
  213. package/src/rslang/semantic/typification-api.test.ts +46 -0
  214. package/src/rslang/semantic/typification-api.ts +321 -0
  215. package/src/rslang/semantic/typification-parser.test.ts +50 -0
  216. package/src/rslang/semantic/typification-parser.ts +220 -0
  217. package/src/rslang/semantic/typification.ts +180 -0
  218. package/src/rslang/semantic/value-auditor.test.ts +206 -0
  219. package/src/rslang/semantic/value-auditor.ts +332 -0
  220. package/src/rslang/semantic/value-class.ts +11 -0
  221. package/src/rslang/typification-graph.ts +155 -0
  222. package/src/shared/branded.ts +6 -0
  223. package/src/shared/hash.ts +17 -0
  224. package/src/shared/index.ts +2 -0
@@ -0,0 +1,70 @@
1
+ /** Module: AST annotations. */
2
+
3
+ import { type AstNode } from '../parsing';
4
+
5
+ import { type ExpressionType } from './semantic/typification';
6
+ import { type RSErrorCode, type RSErrorInfo } from './error';
7
+
8
+ const AST_ERRORS_KEY = 'rsErrors' as const;
9
+ const AST_TYPE_KEY = 'rsType' as const;
10
+
11
+ /** Appends {@link ExpressionType} onto the node's `annotation.rsType`. */
12
+ export function annotateType(node: AstNode, type: ExpressionType): void {
13
+ node.annotation = {
14
+ ...(typeof node.annotation === 'object' && node.annotation !== null ? node.annotation : {}),
15
+ [AST_TYPE_KEY]: type
16
+ };
17
+ }
18
+
19
+ /** Reads {@link ExpressionType} from node's `annotation`. */
20
+ export function readTypeAnnotation(node: AstNode): ExpressionType | null {
21
+ if (node.annotation && AST_TYPE_KEY in node.annotation) {
22
+ return node.annotation[AST_TYPE_KEY] as ExpressionType;
23
+ }
24
+ return null;
25
+ }
26
+
27
+ /** Appends {@link RSErrorInfo} onto the node's `annotation.rsErrors` if not already set. */
28
+ export function annotateError(node: AstNode, code: RSErrorCode, params?: readonly string[]): void {
29
+ if (
30
+ typeof node.annotation === 'object' &&
31
+ node.annotation !== null &&
32
+ AST_ERRORS_KEY in node.annotation &&
33
+ isAstNodeErrorRef(node.annotation[AST_ERRORS_KEY])
34
+ ) {
35
+ return;
36
+ }
37
+ const entry: RSErrorInfo = params !== undefined && params.length > 0 ? { code, params: [...params] } : { code };
38
+ node.annotation = {
39
+ ...(typeof node.annotation === 'object' && node.annotation !== null ? node.annotation : {}),
40
+ [AST_ERRORS_KEY]: entry
41
+ };
42
+ }
43
+
44
+ /** Reads validated {@link RSErrorInfo} entry from node's `annotation`. */
45
+ export function readErrorAnnotation(node: AstNode): RSErrorInfo | null {
46
+ const raw = node.annotation?.[AST_ERRORS_KEY];
47
+ if (isAstNodeErrorRef(raw)) {
48
+ return raw;
49
+ }
50
+ return null;
51
+ }
52
+
53
+ // ====== Internal ======
54
+ function isAstNodeErrorRef(x: unknown): x is RSErrorInfo {
55
+ if (typeof x !== 'object' || x === null || !('code' in x)) {
56
+ return false;
57
+ }
58
+ const code = x.code;
59
+ if (typeof code !== 'number') {
60
+ return false;
61
+ }
62
+ if (!('params' in x)) {
63
+ return true;
64
+ }
65
+ const p = (x as { params: unknown }).params;
66
+ if (p === undefined) {
67
+ return true;
68
+ }
69
+ return Array.isArray(p) && p.every(item => typeof item === 'string');
70
+ }
@@ -0,0 +1,129 @@
1
+ /** Module: Error types and functions. */
2
+
3
+ /** Represents error class. */
4
+ export const RSErrorClass = {
5
+ LEXER: 0,
6
+ PARSER: 1,
7
+ SEMANTIC: 2,
8
+ EVALUATION: 3,
9
+ UNKNOWN: 4
10
+ } as const;
11
+ export type RSErrorClass = (typeof RSErrorClass)[keyof typeof RSErrorClass];
12
+
13
+ /** Represents RSLang expression error information. */
14
+ export interface RSErrorInfo {
15
+ code: RSErrorCode;
16
+ params?: readonly string[];
17
+ }
18
+
19
+ /** Represents RSLang expression error description. */
20
+ export interface RSErrorDescription extends RSErrorInfo {
21
+ from: number;
22
+ to: number;
23
+ }
24
+
25
+ /** Error reporter function type. */
26
+ export type ErrorReporter = (error: RSErrorDescription) => void;
27
+
28
+ /** Represents RSLang expression error types. */
29
+ export const RSErrorCode = {
30
+ unknownSyntax: 0x8400, // 33792
31
+ missingParenthesis: 0x8406, // 33798
32
+ missingCurlyBrace: 0x8407, // 33799
33
+ missingSquareBracket: 0x8408, // 33800
34
+ bracketMismatch: 0x8409, // 33801
35
+ doubleParenthesis: 0x840a, // 33802
36
+ missingOpenBracket: 0x840b, // 33803
37
+ expectedLocal: 0x8415, // 33813
38
+ expectedType: 0x8416, // 33814
39
+
40
+ localDoubleDeclare: 0x2801, // 10241
41
+ localNotUsed: 0x2802, // 10242
42
+
43
+ localUndeclared: 0x8801, // 34817
44
+ localShadowing: 0x8802, // 34818
45
+
46
+ typesNotEqual: 0x8803, // 34819
47
+ globalNotTyped: 0x8804, // 34820
48
+ invalidDecart: 0x8805, // 34821
49
+ invalidBoolean: 0x8806, // 34822
50
+ invalidTypeOperation: 0x8807, // 34823
51
+ invalidCard: 0x8808, // 34824
52
+ invalidDebool: 0x8809, // 34825
53
+ globalFuncWithoutArgs: 0x880b, // 34827
54
+ invalidReduce: 0x8810, // 34832
55
+ invalidProjectionTuple: 0x8811, // 34833
56
+ invalidProjectionSet: 0x8812, // 34834
57
+ invalidEnumeration: 0x8813, // 34835
58
+ invalidCortegeDeclare: 0x8814, // 34836
59
+ localOutOfScope: 0x8815, // 34837
60
+ invalidElementPredicate: 0x8816, // 34838
61
+ invalidEmptySetUsage: 0x8817, // 34839
62
+ invalidArgsArity: 0x8818, // 34840
63
+ invalidArgumentType: 0x8819, // 34841
64
+ globalStructure: 0x881c, // 34844
65
+ radicalUsage: 0x8821, // 34849
66
+ invalidFilterArgumentType: 0x8822, // 34850
67
+ invalidFilterArity: 0x8823, // 34851
68
+ arithmeticNotSupported: 0x8824, // 34852
69
+ typesNotCompatible: 0x8825, // 34853
70
+ orderingNotSupported: 0x8826, // 34854
71
+ expectedLogic: 0x8827, // 34855
72
+ expectedSetexpr: 0x8828, // 34856
73
+ invalidArgumentCortegeDeclare: 0x8829, // 34857
74
+
75
+ globalNoValue: 0x8840, // 34880
76
+ invalidPropertyUsage: 0x8841, // 34881
77
+
78
+ // Value evaluation (runtime)
79
+ calcUnknownError: 0x8100, // 35328
80
+ setOverflow: 0x8101, // 35329
81
+ booleanBaseLimit: 0x8102, // 35330
82
+ calcGlobalMissing: 0x8103, // 35331
83
+ iterationsLimit: 0x8104, // 35332
84
+ calcInvalidDebool: 0x8105, // 35333
85
+ iterateInfinity: 0x8106, // 35334
86
+ calculationNotSupported: 0x8107, // 35335
87
+
88
+ cstEmptyDerived: 0x8861, // 34913
89
+ definitionNotAllowed: 0x8862 // 34914
90
+ } as const;
91
+ export type RSErrorCode = (typeof RSErrorCode)[keyof typeof RSErrorCode];
92
+
93
+ const ERROR_EVALUATION_MASK = 0x0100;
94
+ const ERROR_LEXER_MASK = 0x0200;
95
+ const ERROR_PARSER_MASK = 0x0400;
96
+ const ERROR_SEMANTIC_MASK = 0x0800;
97
+
98
+ /** Infers error class from error type (code). */
99
+ function inferErrorClass(error: RSErrorCode): RSErrorClass {
100
+ if ((error & ERROR_EVALUATION_MASK) !== 0) {
101
+ return RSErrorClass.EVALUATION;
102
+ } else if ((error & ERROR_LEXER_MASK) !== 0) {
103
+ return RSErrorClass.LEXER;
104
+ } else if ((error & ERROR_PARSER_MASK) !== 0) {
105
+ return RSErrorClass.PARSER;
106
+ } else if ((error & ERROR_SEMANTIC_MASK) !== 0) {
107
+ return RSErrorClass.SEMANTIC;
108
+ } else {
109
+ return RSErrorClass.UNKNOWN;
110
+ }
111
+ }
112
+
113
+ /** Generate ErrorID label. */
114
+ export function getRSErrorPrefix(code: RSErrorCode): string {
115
+ const id = code.toString(16).toUpperCase();
116
+ // prettier-ignore
117
+ switch (inferErrorClass(code)) {
118
+ case RSErrorClass.LEXER: return 'L' + id;
119
+ case RSErrorClass.PARSER: return 'P' + id;
120
+ case RSErrorClass.SEMANTIC: return 'S' + id;
121
+ case RSErrorClass.EVALUATION: return 'E' + id;
122
+ case RSErrorClass.UNKNOWN: return 'U' + id;
123
+ }
124
+ }
125
+
126
+ /** Checks if error is critical. */
127
+ export function isCritical(code: RSErrorCode): boolean {
128
+ return code !== RSErrorCode.localDoubleDeclare && code !== RSErrorCode.localNotUsed;
129
+ }
@@ -0,0 +1,124 @@
1
+ import { beforeEach, describe, expect, it } from 'vitest';
2
+
3
+ import { type AstNode, buildTree } from '../../parsing';
4
+ import { RSErrorCode } from '../error';
5
+ import { normalizeAST } from '../parser/normalize';
6
+ import { parser as rslangParser } from '../parser/parser';
7
+
8
+ import { type CalculatorResult, RSCalculator } from './calculator';
9
+ import { printValue } from './value-api';
10
+
11
+ function buildAST(expression: string): AstNode {
12
+ const tree = rslangParser.parse(expression);
13
+ const ast = buildTree(tree.cursor());
14
+ normalizeAST(ast, expression);
15
+ return ast;
16
+ }
17
+
18
+ describe('RSCalculator', () => {
19
+ let calculator: RSCalculator;
20
+
21
+ beforeEach(() => {
22
+ calculator = new RSCalculator();
23
+ calculator.setValue('X1', [1, 2, 3]);
24
+ calculator.setValue('C1', [10, 20, 30]);
25
+ calculator.setAST('S1', buildAST('ℬ(X1)'));
26
+ calculator.setAST('D1', buildAST('X1'));
27
+ });
28
+
29
+ function expectCalcValue(expr: string, expected: string) {
30
+ const ast = buildAST(expr);
31
+ expect(ast.hasError).toBe(false);
32
+ const value = calculator.evaluateFast(ast);
33
+ expect(printValue(value)).toBe(expected);
34
+ }
35
+
36
+ function expectCalcFull(expr: string, options?: { expectValue?: string; expectErrorCode?: RSErrorCode }) {
37
+ const ast = buildAST(expr);
38
+ expect(ast.hasError).toBe(false);
39
+ const result: CalculatorResult = calculator.evaluateFull(ast);
40
+ if (options?.expectValue !== undefined) {
41
+ expect(printValue(result.value)).toBe(options.expectValue);
42
+ expect(result.errors.length).toBe(0);
43
+ }
44
+ if (options?.expectErrorCode !== undefined) {
45
+ expect(result.value).toBe(null);
46
+ expect(result.errors.length).toBeGreaterThan(0);
47
+ expect(result.errors[0].code).toBe(options.expectErrorCode);
48
+ }
49
+ }
50
+
51
+ it('returns null value on syntax error', () => {
52
+ // Introduce an invalid expression: should set hasError and return null value
53
+ const ast = buildAST('a ==='); // Invalid
54
+ // Manually setting hasError for testing, because buildAST may not do this
55
+ ast.hasError = true;
56
+ expect(calculator.evaluateFast(ast)).toBe(null);
57
+ expect(calculator.evaluateFull(ast).value).toBe(null);
58
+ });
59
+
60
+ it('evaluates a simple constant', () => {
61
+ expectCalcValue('1', '1');
62
+ });
63
+
64
+ it('evaluates a variable from context', () => {
65
+ expectCalcValue('X1', '{1, 2, 3}');
66
+ });
67
+
68
+ it('evaluates a basic set operation', () => {
69
+ expectCalcValue('{2,3,4}∩{2,3,5}', '{2, 3}');
70
+ });
71
+
72
+ it('returns error for unknown variable', () => {
73
+ expectCalcFull('X11', { expectErrorCode: RSErrorCode.calcGlobalMissing });
74
+ });
75
+
76
+ it('returns error for infinite quantifier', () => {
77
+ expectCalcFull('∀a∈Z a=a', { expectErrorCode: RSErrorCode.iterateInfinity });
78
+ });
79
+
80
+ it('setValue, getValue, and resetValue behave as expected', () => {
81
+ calculator.setValue('X42', 123);
82
+ expect(calculator.getValue('X42')).toBe(123);
83
+ calculator.resetValue('X42');
84
+ expect(calculator.getValue('X42')).toBe(null);
85
+ });
86
+
87
+ it('setAST registers the AST for future re-evaluation', () => {
88
+ const ast = buildAST('1+2');
89
+ calculator.setAST('myAdd', ast);
90
+ expect(() => calculator.setAST('myAdd', ast)).not.toThrow();
91
+ });
92
+
93
+ it('subscribes to and notifies listeners on setValue/resetValue', () => {
94
+ let called = false;
95
+ const unsubscribe = calculator.subscribe('X1', () => (called = true));
96
+ calculator.setValue('X1', [10, 20]);
97
+ expect(called).toBe(true);
98
+ called = false;
99
+ calculator.resetValue('X1');
100
+ expect(called).toBe(true);
101
+ unsubscribe();
102
+ called = false;
103
+ calculator.setValue('X1', [3, 4]);
104
+ expect(called).toBe(false);
105
+ });
106
+
107
+ it('listeners removed when no more subscribers', () => {
108
+ let calls = 0;
109
+ const unsubscribe = calculator.subscribe('X1', () => {
110
+ calls++;
111
+ });
112
+ calculator.setValue('X1', [8, 9]);
113
+ expect(calls).toBe(1);
114
+ unsubscribe();
115
+ calculator.setValue('X1', [2, 3]);
116
+ expect(calls).toBe(1); // not incremented
117
+ });
118
+
119
+ // Additional case for real RS logic
120
+ it('evaluates logical expressions', () => {
121
+ expectCalcValue('1=1', '1');
122
+ expectCalcValue('1=0', '0');
123
+ });
124
+ });
@@ -0,0 +1,121 @@
1
+ /**
2
+ * Module: API for calculations.
3
+ */
4
+
5
+ import { type AstNode } from '../../parsing';
6
+ import { annotateError } from '../ast-annotations';
7
+ import { RSErrorCode, type RSErrorDescription } from '../error';
8
+ import { type ExpressionType } from '../semantic/typification';
9
+
10
+ import { type ASTContext, Evaluator } from './evaluator';
11
+ import { type Value, type ValueContext } from './value';
12
+ import { validateValue } from './value-api';
13
+
14
+ /** Result of calculator evaluation. */
15
+ export interface CalculatorResult {
16
+ value: Value | null;
17
+ iterations: number;
18
+ cacheHits: number;
19
+ errors: RSErrorDescription[];
20
+ }
21
+
22
+ /** Options for {@link RSCalculator.evaluateFull}. */
23
+ export interface CalculatorEvaluateOptions {
24
+ annotateErrors?: boolean;
25
+ disableCache?: boolean;
26
+ }
27
+
28
+ type Listener = () => void;
29
+
30
+ /** AST calculator - evaluates RS expressions via visitor pattern and provides updates via listeners. */
31
+ export class RSCalculator {
32
+ private context: ValueContext = new Map();
33
+ private treeContext: ASTContext = new Map();
34
+ private evaluator: Evaluator = new Evaluator(this.context, this.treeContext);
35
+
36
+ private listeners = new Map<string, Set<Listener>>();
37
+
38
+ public subscribe = (alias: string, listener: Listener) => {
39
+ let notifyList = this.listeners.get(alias);
40
+
41
+ if (!notifyList) {
42
+ notifyList = new Set();
43
+ this.listeners.set(alias, notifyList);
44
+ }
45
+
46
+ notifyList.add(listener);
47
+ return () => {
48
+ notifyList.delete(listener);
49
+ if (notifyList.size === 0) {
50
+ this.listeners.delete(alias);
51
+ }
52
+ };
53
+ };
54
+
55
+ public setValue(alias: string, value: Value): void {
56
+ this.context.set(alias, value);
57
+ this.notify(alias);
58
+ }
59
+
60
+ public resetValue(alias: string): void {
61
+ this.context.delete(alias);
62
+ this.notify(alias);
63
+ }
64
+
65
+ public clearAllAst(): void {
66
+ this.treeContext.clear();
67
+ }
68
+
69
+ public getValue(alias: string): Value | null {
70
+ return this.context.get(alias) ?? null;
71
+ }
72
+
73
+ public setAST(alias: string, ast: AstNode): void {
74
+ this.treeContext.set(alias, ast);
75
+ }
76
+
77
+ public validate(value: Value, type: ExpressionType): boolean {
78
+ return validateValue(value, type, this.context);
79
+ }
80
+
81
+ public evaluateFast(ast: AstNode): Value | null {
82
+ if (ast.hasError) {
83
+ return null;
84
+ }
85
+ return this.evaluator.run(ast);
86
+ }
87
+
88
+ public evaluateFull(ast: AstNode, options?: CalculatorEvaluateOptions): CalculatorResult {
89
+ const errors: RSErrorDescription[] = [];
90
+ const reporter = (error: RSErrorDescription) => {
91
+ errors.push(error);
92
+ };
93
+
94
+ if (ast.hasError) {
95
+ return { value: null, iterations: 0, cacheHits: 0, errors };
96
+ }
97
+
98
+ const value = this.evaluator.run(ast, reporter, options?.annotateErrors ?? false, options?.disableCache ?? false);
99
+ if (value === null && errors.length === 0) {
100
+ errors.push({ code: RSErrorCode.calcUnknownError, from: 0, to: 0 });
101
+ if (options?.annotateErrors) {
102
+ annotateError(ast, RSErrorCode.calcUnknownError);
103
+ }
104
+ }
105
+ return {
106
+ value,
107
+ iterations: this.evaluator.iterationCounter,
108
+ cacheHits: this.evaluator.cacheHits,
109
+ errors
110
+ };
111
+ }
112
+
113
+ private notify(alias: string) {
114
+ const set = this.listeners.get(alias);
115
+ if (!set) return;
116
+
117
+ for (const l of set) {
118
+ l();
119
+ }
120
+ }
121
+ }
@@ -0,0 +1,257 @@
1
+ /**
2
+ * Module: Dependency metadata and per-run memoization for RSLang evaluation.
3
+ */
4
+
5
+ import { type AstNode, getNodeIndices, getNodeText } from '../../parsing';
6
+ import { TokenID } from '../parser/token';
7
+
8
+ import { type Value } from './value';
9
+
10
+ /** Static metadata for one AST node. */
11
+ export interface EvalNodeInfo {
12
+ /** Local aliases this expression reads (for dependency stamping). */
13
+ reads: ReadonlySet<string>;
14
+ /** Stable key shared by structurally equivalent expressions. */
15
+ structuralKey: string;
16
+ /** Whether the node may be memoized for the current evaluation run. */
17
+ cacheable: boolean;
18
+ }
19
+
20
+ /** Per-evaluator cache of immutable AST dependency metadata (no runtime values). */
21
+ export class EvaluationMetadata {
22
+ private readonly byNode = new WeakMap<AstNode, EvalNodeInfo>();
23
+
24
+ get(node: AstNode): EvalNodeInfo {
25
+ let info = this.byNode.get(node);
26
+ if (!info) {
27
+ info = analyzeNode(node, new Set());
28
+ this.byNode.set(node, info);
29
+ }
30
+ return info;
31
+ }
32
+ }
33
+
34
+ /** Per-run memo of computed values (cleared on each {@link Evaluator.run}). */
35
+ export class EvaluationCache {
36
+ private entries = new Map<string, CacheEntry>();
37
+
38
+ /** Cache hits in the current evaluation run (for tests/diagnostics). */
39
+ hits = 0;
40
+
41
+ /** Returns cached value, or `undefined` on miss or stamp mismatch. */
42
+ lookup(structuralKey: string, stamp: string): Value | undefined {
43
+ const entry = this.entries.get(structuralKey);
44
+ if (entry?.stamp !== stamp) {
45
+ return undefined;
46
+ }
47
+ this.hits++;
48
+ return entry.value;
49
+ }
50
+
51
+ /** Stores one value per structural key (replaces previous stamp). */
52
+ store(structuralKey: string, stamp: string, value: Value): void {
53
+ this.entries.set(structuralKey, { stamp, value });
54
+ }
55
+
56
+ clear(): void {
57
+ this.entries.clear();
58
+ this.hits = 0;
59
+ }
60
+ }
61
+
62
+ interface CacheEntry {
63
+ stamp: string;
64
+ value: Value;
65
+ }
66
+
67
+ function analyzeNode(node: AstNode, bound: Set<string>): EvalNodeInfo {
68
+ const reads = collectReads(node, bound);
69
+ const structuralKey = buildStructuralKey(node);
70
+ const cacheable = isCacheableNode(node);
71
+ return { reads, structuralKey, cacheable };
72
+ }
73
+
74
+ function isCacheableNode(node: AstNode): boolean {
75
+ switch (node.typeID) {
76
+ case TokenID.ASSIGN:
77
+ case TokenID.ITERATE:
78
+ case TokenID.NT_IMPERATIVE_EXPR:
79
+ case TokenID.NT_DECLARATIVE_EXPR:
80
+ case TokenID.NT_RECURSIVE_FULL:
81
+ case TokenID.NT_RECURSIVE_SHORT:
82
+ case TokenID.QUANTOR_UNIVERSAL:
83
+ case TokenID.QUANTOR_EXISTS:
84
+ case TokenID.LOGIC_AND:
85
+ case TokenID.LOGIC_OR:
86
+ case TokenID.LOGIC_IMPLICATION:
87
+ case TokenID.NT_FUNC_DEFINITION:
88
+ case TokenID.LIT_INTEGER:
89
+ case TokenID.LIT_EMPTYSET:
90
+ case TokenID.LIT_WHOLE_NUMBERS:
91
+ case TokenID.ID_LOCAL:
92
+ case TokenID.ID_RADICAL:
93
+ case TokenID.ID_GLOBAL:
94
+ return false;
95
+ default:
96
+ return !node.hasError;
97
+ }
98
+ }
99
+
100
+ function buildStructuralKey(node: AstNode): string {
101
+ switch (node.typeID) {
102
+ case TokenID.ID_GLOBAL:
103
+ case TokenID.ID_LOCAL:
104
+ case TokenID.ID_RADICAL:
105
+ case TokenID.LIT_INTEGER:
106
+ case TokenID.LIT_EMPTYSET:
107
+ case TokenID.LIT_WHOLE_NUMBERS:
108
+ return `${node.typeID}:${nodeTextKey(node)}`;
109
+
110
+ case TokenID.NT_FUNC_CALL:
111
+ return `${node.typeID}:${nodeTextKey(node.children[0])}(${node.children
112
+ .slice(1)
113
+ .map(buildStructuralKey)
114
+ .join(',')})`;
115
+
116
+ case TokenID.BIGPR:
117
+ case TokenID.SMALLPR:
118
+ case TokenID.FILTER:
119
+ return `${node.typeID}:${indicesKey(node)}(${node.children.map(buildStructuralKey).join(',')})`;
120
+
121
+ default:
122
+ return `${node.typeID}(${node.children.map(buildStructuralKey).join(',')})`;
123
+ }
124
+ }
125
+
126
+ function nodeTextKey(node: AstNode): string {
127
+ if (node.data.dataType === 'number') {
128
+ return String(node.data.value);
129
+ }
130
+ return getNodeText(node);
131
+ }
132
+
133
+ function indicesKey(node: AstNode): string {
134
+ return getNodeIndices(node).join('.');
135
+ }
136
+
137
+ function collectReads(node: AstNode, bound: Set<string>): Set<string> {
138
+ const reads = new Set<string>();
139
+ collectReadsImpl(node, bound, reads);
140
+ return reads;
141
+ }
142
+
143
+ function collectReadsImpl(node: AstNode, bound: Set<string>, reads: Set<string>): void {
144
+ switch (node.typeID) {
145
+ case TokenID.ID_LOCAL:
146
+ case TokenID.ID_RADICAL: {
147
+ reads.add(getNodeText(node));
148
+ return;
149
+ }
150
+
151
+ case TokenID.QUANTOR_UNIVERSAL:
152
+ case TokenID.QUANTOR_EXISTS: {
153
+ collectReadsImpl(node.children[1], bound, reads);
154
+ const innerBound = extendBound(bound, node.children[0]);
155
+ collectReadsImpl(node.children[2], innerBound, reads);
156
+ return;
157
+ }
158
+
159
+ case TokenID.NT_DECLARATIVE_EXPR: {
160
+ collectReadsImpl(node.children[1], bound, reads);
161
+ const innerBound = extendBound(bound, node.children[0]);
162
+ collectReadsImpl(node.children[2], innerBound, reads);
163
+ return;
164
+ }
165
+
166
+ case TokenID.ITERATE: {
167
+ collectReadsImpl(node.children[1], bound, reads);
168
+ return;
169
+ }
170
+
171
+ case TokenID.ASSIGN: {
172
+ collectReadsImpl(node.children[1], bound, reads);
173
+ return;
174
+ }
175
+
176
+ case TokenID.NT_IMPERATIVE_EXPR: {
177
+ const innerBound = new Set(bound);
178
+ collectReadsImpl(node.children[0], innerBound, reads);
179
+ for (let i = 1; i < node.children.length; i++) {
180
+ const child = node.children[i];
181
+ if (child.typeID === TokenID.ITERATE) {
182
+ extendBoundInPlace(innerBound, child.children[0]);
183
+ collectReadsImpl(child.children[1], innerBound, reads);
184
+ } else if (child.typeID === TokenID.ASSIGN) {
185
+ collectReadsImpl(child.children[1], innerBound, reads);
186
+ extendBoundInPlace(innerBound, child.children[0]);
187
+ } else {
188
+ collectReadsImpl(child, innerBound, reads);
189
+ }
190
+ }
191
+ return;
192
+ }
193
+
194
+ case TokenID.NT_RECURSIVE_FULL:
195
+ case TokenID.NT_RECURSIVE_SHORT: {
196
+ collectReadsImpl(node.children[1], bound, reads);
197
+ const innerBound = extendBound(bound, node.children[0]);
198
+ if (node.typeID === TokenID.NT_RECURSIVE_FULL) {
199
+ collectReadsImpl(node.children[2], innerBound, reads);
200
+ collectReadsImpl(node.children[3], innerBound, reads);
201
+ } else {
202
+ collectReadsImpl(node.children[2], innerBound, reads);
203
+ }
204
+ return;
205
+ }
206
+
207
+ case TokenID.NT_FUNC_DEFINITION: {
208
+ const innerBound = extendBound(bound, node.children[0]);
209
+ collectReadsImpl(node.children[1], innerBound, reads);
210
+ return;
211
+ }
212
+
213
+ case TokenID.NT_FUNC_CALL: {
214
+ for (let i = 1; i < node.children.length; i++) {
215
+ collectReadsImpl(node.children[i], bound, reads);
216
+ }
217
+ return;
218
+ }
219
+
220
+ case TokenID.LOGIC_AND:
221
+ case TokenID.LOGIC_OR:
222
+ case TokenID.LOGIC_IMPLICATION: {
223
+ collectReadsImpl(node.children[0], bound, reads);
224
+ collectReadsImpl(node.children[1], bound, reads);
225
+ return;
226
+ }
227
+
228
+ default:
229
+ for (const child of node.children) {
230
+ collectReadsImpl(child, bound, reads);
231
+ }
232
+ }
233
+ }
234
+
235
+ function extendBound(bound: Set<string>, declNode: AstNode): Set<string> {
236
+ const next = new Set(bound);
237
+ extendBoundInPlace(next, declNode);
238
+ return next;
239
+ }
240
+
241
+ function extendBoundInPlace(bound: Set<string>, declNode: AstNode): void {
242
+ switch (declNode.typeID) {
243
+ case TokenID.ID_LOCAL:
244
+ case TokenID.ID_RADICAL:
245
+ bound.add(getNodeText(declNode));
246
+ break;
247
+ case TokenID.NT_TUPLE_DECL:
248
+ case TokenID.NT_ENUM_DECL:
249
+ for (const child of declNode.children) {
250
+ extendBoundInPlace(bound, child);
251
+ }
252
+ break;
253
+ case TokenID.NT_ARG_DECL:
254
+ extendBoundInPlace(bound, declNode.children[0]);
255
+ break;
256
+ }
257
+ }