@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,466 @@
|
|
|
1
|
+
import { type AstNode, getNodeIndices, getNodeText } from '../../parsing';
|
|
2
|
+
|
|
3
|
+
import { TokenID } from './token';
|
|
4
|
+
|
|
5
|
+
interface ExpressionGeneratorOptions {
|
|
6
|
+
normalize?: boolean;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
/** Generates Math-syntax RS expression from AST, with optional identifier normalization. */
|
|
10
|
+
export function generateExpressionFromAst(ast: AstNode, options: ExpressionGeneratorOptions = {}): string {
|
|
11
|
+
return new ExpressionGenerator(options).run(ast);
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
interface VisitContext {
|
|
15
|
+
localDeclaration: boolean;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
type ScopeFrame = Map<string, string>;
|
|
19
|
+
|
|
20
|
+
/** Generates Math-syntax RS expression from AST, with optional identifier normalization. */
|
|
21
|
+
class ExpressionGenerator {
|
|
22
|
+
private readonly options: ExpressionGeneratorOptions;
|
|
23
|
+
private readonly freeLocals = new Map<string, string>();
|
|
24
|
+
private readonly radicals = new Map<string, string>();
|
|
25
|
+
private readonly scopes: ScopeFrame[] = [];
|
|
26
|
+
private localCounter = 0;
|
|
27
|
+
private radicalCounter = 0;
|
|
28
|
+
|
|
29
|
+
constructor(options: ExpressionGeneratorOptions) {
|
|
30
|
+
this.options = options;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/** Run generator. Requires that the AST has no syntax errors. */
|
|
34
|
+
run(ast: AstNode): string {
|
|
35
|
+
if (ast.hasError) {
|
|
36
|
+
throw new Error('Cannot generate expression: AST has error');
|
|
37
|
+
}
|
|
38
|
+
return this.visit(ast, { localDeclaration: false });
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
private visit(node: AstNode, context: VisitContext): string {
|
|
42
|
+
const shouldScope = this.shouldStartScope(node.typeID);
|
|
43
|
+
if (shouldScope) {
|
|
44
|
+
this.startScope();
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const result = this.renderNode(node, context);
|
|
48
|
+
|
|
49
|
+
if (shouldScope) {
|
|
50
|
+
this.endScope();
|
|
51
|
+
}
|
|
52
|
+
return result;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
private renderNode(node: AstNode, context: VisitContext): string {
|
|
56
|
+
switch (node.typeID) {
|
|
57
|
+
case TokenID.ID_LOCAL: {
|
|
58
|
+
const alias = getNodeText(node);
|
|
59
|
+
return this.options.normalize
|
|
60
|
+
? context.localDeclaration
|
|
61
|
+
? this.declareLocal(alias)
|
|
62
|
+
: this.resolveLocal(alias)
|
|
63
|
+
: alias;
|
|
64
|
+
}
|
|
65
|
+
case TokenID.ID_GLOBAL:
|
|
66
|
+
case TokenID.ID_FUNCTION:
|
|
67
|
+
case TokenID.ID_PREDICATE:
|
|
68
|
+
return getNodeText(node);
|
|
69
|
+
case TokenID.ID_RADICAL: {
|
|
70
|
+
const alias = getNodeText(node);
|
|
71
|
+
return this.options.normalize ? this.resolveRadical(alias) : alias;
|
|
72
|
+
}
|
|
73
|
+
case TokenID.LIT_INTEGER:
|
|
74
|
+
return String(node.data.value);
|
|
75
|
+
case TokenID.LIT_WHOLE_NUMBERS:
|
|
76
|
+
return 'Z';
|
|
77
|
+
case TokenID.LIT_EMPTYSET:
|
|
78
|
+
return '∅';
|
|
79
|
+
|
|
80
|
+
case TokenID.BIGPR:
|
|
81
|
+
return `Pr${this.formatIndices(node)}(${this.visitChild(node, 0, context)})`;
|
|
82
|
+
case TokenID.SMALLPR:
|
|
83
|
+
return `pr${this.formatIndices(node)}(${this.visitChild(node, 0, context)})`;
|
|
84
|
+
case TokenID.FILTER: {
|
|
85
|
+
const args = node.children
|
|
86
|
+
.slice(0, -1)
|
|
87
|
+
.map(child => this.visit(child, context))
|
|
88
|
+
.join(', ');
|
|
89
|
+
const body = this.visitChild(node, node.children.length - 1, context);
|
|
90
|
+
return `Fi${this.formatIndices(node)}[${args}](${body})`;
|
|
91
|
+
}
|
|
92
|
+
case TokenID.CARD:
|
|
93
|
+
return `card(${this.visitChild(node, 0, context)})`;
|
|
94
|
+
case TokenID.BOOL:
|
|
95
|
+
return `bool(${this.visitChild(node, 0, context)})`;
|
|
96
|
+
case TokenID.DEBOOL:
|
|
97
|
+
return `debool(${this.visitChild(node, 0, context)})`;
|
|
98
|
+
case TokenID.REDUCE:
|
|
99
|
+
return `red(${this.visitChild(node, 0, context)})`;
|
|
100
|
+
case TokenID.BOOLEAN:
|
|
101
|
+
return this.renderBoolean(node, context);
|
|
102
|
+
|
|
103
|
+
case TokenID.NT_FUNC_CALL: {
|
|
104
|
+
const callee = this.visitChild(node, 0, context);
|
|
105
|
+
const args = node.children
|
|
106
|
+
.slice(1)
|
|
107
|
+
.map(child => this.visit(child, context))
|
|
108
|
+
.join(', ');
|
|
109
|
+
return `${callee}[${args}]`;
|
|
110
|
+
}
|
|
111
|
+
case TokenID.NT_TUPLE:
|
|
112
|
+
return `(${node.children.map(child => this.visit(child, context)).join(', ')})`;
|
|
113
|
+
case TokenID.NT_ENUMERATION:
|
|
114
|
+
return `{${node.children.map(child => this.visit(child, context)).join(', ')}}`;
|
|
115
|
+
case TokenID.NT_ARGUMENTS:
|
|
116
|
+
case TokenID.NT_ENUM_DECL:
|
|
117
|
+
return node.children
|
|
118
|
+
.map((child, index) => this.visit(child, this.childContext(node.typeID, index, context)))
|
|
119
|
+
.join(', ');
|
|
120
|
+
case TokenID.NT_TUPLE_DECL:
|
|
121
|
+
return `(${node.children.map((child, index) => this.visit(child, this.childContext(node.typeID, index, context))).join(', ')})`;
|
|
122
|
+
case TokenID.NT_ARG_DECL:
|
|
123
|
+
return `${this.visitChild(node, 0, this.childContext(node.typeID, 0, context))}∈${this.visitChild(node, 1, context)}`;
|
|
124
|
+
case TokenID.QUANTOR_UNIVERSAL:
|
|
125
|
+
return this.renderQuantifier('∀', node, context);
|
|
126
|
+
case TokenID.QUANTOR_EXISTS:
|
|
127
|
+
return this.renderQuantifier('∃', node, context);
|
|
128
|
+
case TokenID.NT_DECLARATIVE_EXPR:
|
|
129
|
+
return `D{${this.visitChild(node, 0, this.childContext(node.typeID, 0, context))}∈${this.visitChild(node, 1, context)} | ${this.visitChild(node, 2, context)}}`;
|
|
130
|
+
case TokenID.NT_FUNC_DEFINITION:
|
|
131
|
+
return `[${this.visitChild(node, 0, this.childContext(node.typeID, 0, context))}] ${this.visitChild(node, 1, context)}`;
|
|
132
|
+
case TokenID.ITERATE:
|
|
133
|
+
return `${this.visitChild(node, 0, this.childContext(node.typeID, 0, context))}:∈${this.visitChild(node, 1, context)}`;
|
|
134
|
+
case TokenID.ASSIGN:
|
|
135
|
+
return `${this.visitChild(node, 0, this.childContext(node.typeID, 0, context))}:=${this.visitChild(node, 1, context)}`;
|
|
136
|
+
case TokenID.NT_IMPERATIVE_EXPR: {
|
|
137
|
+
const statements = node.children
|
|
138
|
+
.slice(1)
|
|
139
|
+
.map((child, index) => this.visit(child, this.childContext(node.typeID, index + 1, context)));
|
|
140
|
+
const target = this.visitChild(node, 0, context);
|
|
141
|
+
return `I{${target} | ${statements.join('; ')}}`;
|
|
142
|
+
}
|
|
143
|
+
case TokenID.NT_RECURSIVE_SHORT:
|
|
144
|
+
return `R{${this.visitChild(node, 0, this.childContext(node.typeID, 0, context))}:=${this.visitChild(node, 1, context)} | ${this.visitChild(node, 2, context)}}`;
|
|
145
|
+
case TokenID.NT_RECURSIVE_FULL:
|
|
146
|
+
return `R{${this.visitChild(node, 0, this.childContext(node.typeID, 0, context))}:=${this.visitChild(node, 1, context)} | ${this.visitChild(node, 2, context)} | ${this.visitChild(node, 3, context)}}`;
|
|
147
|
+
|
|
148
|
+
case TokenID.PLUS:
|
|
149
|
+
return this.renderInfix('+', node, context);
|
|
150
|
+
case TokenID.MINUS:
|
|
151
|
+
return this.renderInfix('-', node, context);
|
|
152
|
+
case TokenID.MULTIPLY:
|
|
153
|
+
return this.renderInfix('*', node, context);
|
|
154
|
+
case TokenID.GREATER:
|
|
155
|
+
return this.renderInfix('>', node, context);
|
|
156
|
+
case TokenID.LESSER:
|
|
157
|
+
return this.renderInfix('<', node, context);
|
|
158
|
+
case TokenID.GREATER_OR_EQ:
|
|
159
|
+
return this.renderInfix('≥', node, context);
|
|
160
|
+
case TokenID.LESSER_OR_EQ:
|
|
161
|
+
return this.renderInfix('≤', node, context);
|
|
162
|
+
case TokenID.EQUAL:
|
|
163
|
+
return this.renderInfix('=', node, context);
|
|
164
|
+
case TokenID.NOTEQUAL:
|
|
165
|
+
return this.renderInfix('≠', node, context);
|
|
166
|
+
case TokenID.LOGIC_AND:
|
|
167
|
+
return this.renderInfix('&', node, context);
|
|
168
|
+
case TokenID.LOGIC_OR:
|
|
169
|
+
return this.renderInfix('∨', node, context);
|
|
170
|
+
case TokenID.LOGIC_IMPLICATION:
|
|
171
|
+
return this.renderInfix('⇒', node, context);
|
|
172
|
+
case TokenID.LOGIC_EQUIVALENT:
|
|
173
|
+
return this.renderInfix('⇔', node, context);
|
|
174
|
+
case TokenID.SET_IN:
|
|
175
|
+
return this.renderInfix('∈', node, context);
|
|
176
|
+
case TokenID.SET_NOT_IN:
|
|
177
|
+
return this.renderInfix('∉', node, context);
|
|
178
|
+
case TokenID.SUBSET:
|
|
179
|
+
return this.renderInfix('⊂', node, context);
|
|
180
|
+
case TokenID.SUBSET_OR_EQ:
|
|
181
|
+
return this.renderInfix('⊆', node, context);
|
|
182
|
+
case TokenID.NOT_SUBSET:
|
|
183
|
+
return this.renderInfix('⊄', node, context);
|
|
184
|
+
case TokenID.DECART:
|
|
185
|
+
return this.renderInfix('×', node, context);
|
|
186
|
+
case TokenID.SET_UNION:
|
|
187
|
+
return this.renderInfix('∪', node, context);
|
|
188
|
+
case TokenID.SET_INTERSECTION:
|
|
189
|
+
return this.renderInfix('∩', node, context);
|
|
190
|
+
case TokenID.SET_MINUS:
|
|
191
|
+
return this.renderInfix('\\', node, context);
|
|
192
|
+
case TokenID.SET_SYMMETRIC_MINUS:
|
|
193
|
+
return this.renderInfix('∆', node, context);
|
|
194
|
+
case TokenID.LOGIC_NOT:
|
|
195
|
+
return `¬${this.wrapIfNeeded(this.visitChild(node, 0, context), node.children[0])}`;
|
|
196
|
+
default:
|
|
197
|
+
return node.children.map(child => this.visit(child, context)).join('');
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
private visitChild(node: AstNode, index: number, context: VisitContext): string {
|
|
202
|
+
const child = node.children[index];
|
|
203
|
+
if (!child) {
|
|
204
|
+
return '';
|
|
205
|
+
}
|
|
206
|
+
return this.visit(child, this.childContext(node.typeID, index, context));
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
private renderQuantifier(symbol: string, node: AstNode, context: VisitContext): string {
|
|
210
|
+
const declaration = this.visitChild(node, 0, this.childContext(node.typeID, 0, context));
|
|
211
|
+
const domain = this.visitChild(node, 1, context);
|
|
212
|
+
const bodyNode = node.children[2];
|
|
213
|
+
const bodyText = this.visitChild(node, 2, context);
|
|
214
|
+
const body = bodyNode && this.shouldWrapQuantifierBody(bodyNode) ? `(${bodyText})` : bodyText;
|
|
215
|
+
return `${symbol}${declaration}∈${domain} ${body}`;
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
private shouldWrapQuantifierBody(node: AstNode): boolean {
|
|
219
|
+
const quantifierPrecedence = this.getPrecedence(TokenID.QUANTOR_UNIVERSAL);
|
|
220
|
+
const bodyPrecedence = this.getPrecedence(node.typeID);
|
|
221
|
+
if (quantifierPrecedence === null || bodyPrecedence === null) {
|
|
222
|
+
return false;
|
|
223
|
+
}
|
|
224
|
+
return bodyPrecedence < quantifierPrecedence;
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
private renderInfix(symbol: string, node: AstNode, context: VisitContext): string {
|
|
228
|
+
if (this.isLogicBinary(node.typeID)) {
|
|
229
|
+
const parts = node.children.map((child, index) => {
|
|
230
|
+
const text = this.visit(child, context);
|
|
231
|
+
return this.needParen(node, child, index) ? `(${text})` : text;
|
|
232
|
+
});
|
|
233
|
+
return parts.join(` ${symbol} `);
|
|
234
|
+
}
|
|
235
|
+
const parts = node.children.map(child => this.wrapIfNeeded(this.visit(child, context), child));
|
|
236
|
+
return parts.join(symbol);
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
private needParen(parent: AstNode, child: AstNode, childIndex: number): boolean {
|
|
240
|
+
if (this.isAtomic(child.typeID)) {
|
|
241
|
+
return false;
|
|
242
|
+
}
|
|
243
|
+
const parentPrecedence = this.getPrecedence(parent.typeID);
|
|
244
|
+
const childPrecedence = this.getPrecedence(child.typeID);
|
|
245
|
+
if (parentPrecedence === null || childPrecedence === null) {
|
|
246
|
+
return true;
|
|
247
|
+
}
|
|
248
|
+
if (childPrecedence > parentPrecedence) {
|
|
249
|
+
return false;
|
|
250
|
+
}
|
|
251
|
+
if (childPrecedence < parentPrecedence) {
|
|
252
|
+
return true;
|
|
253
|
+
}
|
|
254
|
+
if (this.isAssociative(parent.typeID) && child.typeID === parent.typeID) {
|
|
255
|
+
return false;
|
|
256
|
+
}
|
|
257
|
+
if (this.isLeftAssociative(parent.typeID)) {
|
|
258
|
+
return childIndex > 0;
|
|
259
|
+
}
|
|
260
|
+
return true;
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
private getPrecedence(typeID: number): number | null {
|
|
264
|
+
switch (typeID) {
|
|
265
|
+
case TokenID.LOGIC_EQUIVALENT:
|
|
266
|
+
return 1;
|
|
267
|
+
case TokenID.LOGIC_IMPLICATION:
|
|
268
|
+
return 2;
|
|
269
|
+
case TokenID.LOGIC_OR:
|
|
270
|
+
return 3;
|
|
271
|
+
case TokenID.LOGIC_AND:
|
|
272
|
+
return 4;
|
|
273
|
+
case TokenID.QUANTOR_UNIVERSAL:
|
|
274
|
+
case TokenID.QUANTOR_EXISTS:
|
|
275
|
+
return 5;
|
|
276
|
+
case TokenID.EQUAL:
|
|
277
|
+
case TokenID.NOTEQUAL:
|
|
278
|
+
case TokenID.GREATER:
|
|
279
|
+
case TokenID.LESSER:
|
|
280
|
+
case TokenID.GREATER_OR_EQ:
|
|
281
|
+
case TokenID.LESSER_OR_EQ:
|
|
282
|
+
case TokenID.SET_IN:
|
|
283
|
+
case TokenID.SET_NOT_IN:
|
|
284
|
+
case TokenID.SUBSET:
|
|
285
|
+
case TokenID.SUBSET_OR_EQ:
|
|
286
|
+
case TokenID.NOT_SUBSET:
|
|
287
|
+
return 6;
|
|
288
|
+
case TokenID.PLUS:
|
|
289
|
+
case TokenID.MINUS:
|
|
290
|
+
case TokenID.SET_UNION:
|
|
291
|
+
case TokenID.SET_SYMMETRIC_MINUS:
|
|
292
|
+
return 7;
|
|
293
|
+
case TokenID.MULTIPLY:
|
|
294
|
+
case TokenID.DECART:
|
|
295
|
+
case TokenID.SET_INTERSECTION:
|
|
296
|
+
case TokenID.SET_MINUS:
|
|
297
|
+
return 8;
|
|
298
|
+
default:
|
|
299
|
+
return null;
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
private isAssociative(typeID: number): boolean {
|
|
304
|
+
return (
|
|
305
|
+
typeID === TokenID.PLUS ||
|
|
306
|
+
typeID === TokenID.MULTIPLY ||
|
|
307
|
+
typeID === TokenID.LOGIC_AND ||
|
|
308
|
+
typeID === TokenID.LOGIC_OR ||
|
|
309
|
+
typeID === TokenID.LOGIC_EQUIVALENT ||
|
|
310
|
+
typeID === TokenID.SET_UNION ||
|
|
311
|
+
typeID === TokenID.SET_INTERSECTION ||
|
|
312
|
+
typeID === TokenID.DECART
|
|
313
|
+
);
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
private isLeftAssociative(typeID: number): boolean {
|
|
317
|
+
return (
|
|
318
|
+
typeID === TokenID.LOGIC_IMPLICATION ||
|
|
319
|
+
typeID === TokenID.MINUS ||
|
|
320
|
+
typeID === TokenID.SET_MINUS ||
|
|
321
|
+
typeID === TokenID.SET_SYMMETRIC_MINUS
|
|
322
|
+
);
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
private renderBoolean(node: AstNode, context: VisitContext): string {
|
|
326
|
+
let current: AstNode = node;
|
|
327
|
+
let depth = 0;
|
|
328
|
+
while (current.typeID === TokenID.BOOLEAN && current.children.length === 1) {
|
|
329
|
+
depth += 1;
|
|
330
|
+
current = current.children[0];
|
|
331
|
+
}
|
|
332
|
+
return `${'ℬ'.repeat(depth)}(${this.visit(current, context)})`;
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
private wrapIfNeeded(text: string, node: AstNode): string {
|
|
336
|
+
if (this.isAtomic(node.typeID)) {
|
|
337
|
+
return text;
|
|
338
|
+
}
|
|
339
|
+
return `(${text})`;
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
private isAtomic(typeID: number): boolean {
|
|
343
|
+
return (
|
|
344
|
+
typeID === TokenID.ID_LOCAL ||
|
|
345
|
+
typeID === TokenID.ID_GLOBAL ||
|
|
346
|
+
typeID === TokenID.ID_FUNCTION ||
|
|
347
|
+
typeID === TokenID.ID_PREDICATE ||
|
|
348
|
+
typeID === TokenID.ID_RADICAL ||
|
|
349
|
+
typeID === TokenID.LIT_INTEGER ||
|
|
350
|
+
typeID === TokenID.LIT_WHOLE_NUMBERS ||
|
|
351
|
+
typeID === TokenID.LIT_EMPTYSET ||
|
|
352
|
+
typeID === TokenID.NT_FUNC_CALL ||
|
|
353
|
+
typeID === TokenID.NT_TUPLE ||
|
|
354
|
+
typeID === TokenID.NT_ENUMERATION ||
|
|
355
|
+
typeID === TokenID.BIGPR ||
|
|
356
|
+
typeID === TokenID.SMALLPR ||
|
|
357
|
+
typeID === TokenID.FILTER ||
|
|
358
|
+
typeID === TokenID.REDUCE ||
|
|
359
|
+
typeID === TokenID.BOOL ||
|
|
360
|
+
typeID === TokenID.DEBOOL ||
|
|
361
|
+
typeID === TokenID.CARD ||
|
|
362
|
+
typeID === TokenID.BOOLEAN
|
|
363
|
+
);
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
private isLogicBinary(typeID: number): boolean {
|
|
367
|
+
return (
|
|
368
|
+
typeID === TokenID.LOGIC_AND ||
|
|
369
|
+
typeID === TokenID.LOGIC_OR ||
|
|
370
|
+
typeID === TokenID.LOGIC_IMPLICATION ||
|
|
371
|
+
typeID === TokenID.LOGIC_EQUIVALENT
|
|
372
|
+
);
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
private formatIndices(node: AstNode): string {
|
|
376
|
+
return getNodeIndices(node).join(',');
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
private shouldStartScope(typeID: number): boolean {
|
|
380
|
+
return (
|
|
381
|
+
typeID === TokenID.QUANTOR_UNIVERSAL ||
|
|
382
|
+
typeID === TokenID.QUANTOR_EXISTS ||
|
|
383
|
+
typeID === TokenID.NT_DECLARATIVE_EXPR ||
|
|
384
|
+
typeID === TokenID.NT_FUNC_DEFINITION ||
|
|
385
|
+
typeID === TokenID.NT_IMPERATIVE_EXPR ||
|
|
386
|
+
typeID === TokenID.NT_RECURSIVE_SHORT ||
|
|
387
|
+
typeID === TokenID.NT_RECURSIVE_FULL
|
|
388
|
+
);
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
private childContext(typeID: number, index: number, parent: VisitContext): VisitContext {
|
|
392
|
+
if (!this.options.normalize) {
|
|
393
|
+
return parent;
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
const isDeclaration =
|
|
397
|
+
((typeID === TokenID.QUANTOR_UNIVERSAL ||
|
|
398
|
+
typeID === TokenID.QUANTOR_EXISTS ||
|
|
399
|
+
typeID === TokenID.NT_DECLARATIVE_EXPR ||
|
|
400
|
+
typeID === TokenID.ITERATE ||
|
|
401
|
+
typeID === TokenID.ASSIGN ||
|
|
402
|
+
typeID === TokenID.NT_RECURSIVE_SHORT ||
|
|
403
|
+
typeID === TokenID.NT_RECURSIVE_FULL) &&
|
|
404
|
+
index === 0) ||
|
|
405
|
+
(typeID === TokenID.NT_FUNC_DEFINITION && index === 0) ||
|
|
406
|
+
(typeID === TokenID.NT_ARG_DECL && index === 0) ||
|
|
407
|
+
typeID === TokenID.NT_TUPLE_DECL ||
|
|
408
|
+
typeID === TokenID.NT_ENUM_DECL;
|
|
409
|
+
|
|
410
|
+
return { localDeclaration: isDeclaration };
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
private declareLocal(alias: string): string {
|
|
414
|
+
const scope = this.currentScope();
|
|
415
|
+
const local = this.nextLocal();
|
|
416
|
+
scope.set(alias, local);
|
|
417
|
+
return local;
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
private resolveLocal(alias: string): string {
|
|
421
|
+
for (let index = this.scopes.length - 1; index >= 0; index -= 1) {
|
|
422
|
+
const local = this.scopes[index].get(alias);
|
|
423
|
+
if (local) {
|
|
424
|
+
return local;
|
|
425
|
+
}
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
let local = this.freeLocals.get(alias);
|
|
429
|
+
if (!local) {
|
|
430
|
+
local = this.nextLocal();
|
|
431
|
+
this.freeLocals.set(alias, local);
|
|
432
|
+
}
|
|
433
|
+
return local;
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
private resolveRadical(alias: string): string {
|
|
437
|
+
let radical = this.radicals.get(alias);
|
|
438
|
+
if (!radical) {
|
|
439
|
+
this.radicalCounter += 1;
|
|
440
|
+
radical = `R${this.radicalCounter}`;
|
|
441
|
+
this.radicals.set(alias, radical);
|
|
442
|
+
}
|
|
443
|
+
return radical;
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
private startScope(): void {
|
|
447
|
+
this.scopes.push(new Map());
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
private endScope(): void {
|
|
451
|
+
this.scopes.pop();
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
private currentScope(): ScopeFrame {
|
|
455
|
+
const scope = this.scopes.at(-1);
|
|
456
|
+
if (!scope) {
|
|
457
|
+
throw new Error('Attempted to declare local outside of scope');
|
|
458
|
+
}
|
|
459
|
+
return scope;
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
private nextLocal(): string {
|
|
463
|
+
this.localCounter += 1;
|
|
464
|
+
return `a${this.localCounter}`;
|
|
465
|
+
}
|
|
466
|
+
}
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest';
|
|
2
|
+
|
|
3
|
+
import { buildTree, printAst } from '../../parsing';
|
|
4
|
+
import { labelRSLangNode } from '../labels';
|
|
5
|
+
|
|
6
|
+
import { normalizeAST } from './normalize';
|
|
7
|
+
import { parser } from './parser';
|
|
8
|
+
|
|
9
|
+
const testData = [
|
|
10
|
+
// Predicates
|
|
11
|
+
['1 = 2', '[=[1][2]]'],
|
|
12
|
+
['1 < X1', '[<[1][X1]]'],
|
|
13
|
+
['1 > 2', '[>[1][2]]'],
|
|
14
|
+
['1 < 2', '[<[1][2]]'],
|
|
15
|
+
['X1 ≠ a', '[≠[X1][a]]'],
|
|
16
|
+
['P1[a,b]', '[CALL[P1][a][b]]'],
|
|
17
|
+
// Logic operators
|
|
18
|
+
['¬1=2', '[¬[=[1][2]]]'],
|
|
19
|
+
['1=1 & 2=2 & 3=3', '[&[&[=[1][1]][=[2][2]]][=[3][3]]]'],
|
|
20
|
+
['1=1 ∨ 2=2 ∨ 3=3', '[∨[∨[=[1][1]][=[2][2]]][=[3][3]]]'],
|
|
21
|
+
['1=1 ⇒ 2=2 ⇒ 3=3', '[⇒[⇒[=[1][1]][=[2][2]]][=[3][3]]]'],
|
|
22
|
+
['1=1 ⇔ 2=2 ⇔ 3=3', '[⇔[⇔[=[1][1]][=[2][2]]][=[3][3]]]'],
|
|
23
|
+
['(1=1 & 2=2) & 3=3', '[&[&[=[1][1]][=[2][2]]][=[3][3]]]'],
|
|
24
|
+
['1=1 & (2=2 & 3=3)', '[&[=[1][1]][&[=[2][2]][=[3][3]]]]'],
|
|
25
|
+
['1=1 ∨ 2=2 & 3=3', '[∨[=[1][1]][&[=[2][2]][=[3][3]]]]'],
|
|
26
|
+
['1=1 & 2=2 ∨ 3=3 ⇒ 4=4 ⇔ 5=5', '[⇔[⇒[∨[&[=[1][1]][=[2][2]]][=[3][3]]][=[4][4]]][=[5][5]]]'],
|
|
27
|
+
// Quantifiers
|
|
28
|
+
['∀a∈X1 1=1', '[∀[a][X1][=[1][1]]]'],
|
|
29
|
+
['∃a∈X1 1=1', '[∃[a][X1][=[1][1]]]'],
|
|
30
|
+
['∀a∈X1 1=1 & 2=2', '[&[∀[a][X1][=[1][1]]][=[2][2]]]'],
|
|
31
|
+
['∀a∈X1 a=a & a=a', '[&[∀[a][X1][=[a][a]]][=[a][a]]]'],
|
|
32
|
+
['∀a,b∈X1 1=2', '[∀[ENUM_DECLARE[a][b]][X1][=[1][2]]]'],
|
|
33
|
+
['∀(a,b),(c,d)∈S1 1=2', '[∀[ENUM_DECLARE[TUPLE_DECLARE[a][b]][TUPLE_DECLARE[c][d]]][S1][=[1][2]]]'],
|
|
34
|
+
// Setexpr operators
|
|
35
|
+
['1+2+3', '[+[+[1][2]][3]]'],
|
|
36
|
+
['(1+2)+3', '[+[+[1][2]][3]]'],
|
|
37
|
+
['1+(2+3)', '[+[1][+[2][3]]]'],
|
|
38
|
+
['1+2-3', '[-[+[1][2]][3]]'],
|
|
39
|
+
['1-2-3', '[-[-[1][2]][3]]'],
|
|
40
|
+
['1+(2-3)', '[+[1][-[2][3]]]'],
|
|
41
|
+
['1*(2+3)', '[*[1][+[2][3]]]'],
|
|
42
|
+
['1*2*3', '[*[*[1][2]][3]]'],
|
|
43
|
+
['a\\b\\c', '[\\[\\[a][b]][c]]'],
|
|
44
|
+
['(a\\b)\\c', '[\\[\\[a][b]][c]]'],
|
|
45
|
+
['a\\(b\\c)', '[\\[a][\\[b][c]]]'],
|
|
46
|
+
['((a ∪ b) ∩ (c \\ d)) ∆ (e × f)', '[∆[∩[∪[a][b]][\\[c][d]]][×[e][f]]]'],
|
|
47
|
+
// Function calls / text functions
|
|
48
|
+
['card(X1)', '[card[X1]]'],
|
|
49
|
+
['card(1)', '[card[1]]'],
|
|
50
|
+
['card(a)', '[card[a]]'],
|
|
51
|
+
['card(∅)', '[card[∅]]'],
|
|
52
|
+
['card(Z)', '[card[Z]]'],
|
|
53
|
+
['Pr2(a)', '[Pr2[a]]'],
|
|
54
|
+
['bool(a)', '[bool[a]]'],
|
|
55
|
+
['debool(a)', '[debool[a]]'],
|
|
56
|
+
['red(a)', '[red[a]]'],
|
|
57
|
+
['Fi1,2[b](a)', '[Fi1,2[b][a]]'],
|
|
58
|
+
['F1[a]', '[CALL[F1][a]]'],
|
|
59
|
+
['F1[a,b]', '[CALL[F1][a][b]]'],
|
|
60
|
+
['F1[{(a,b)}]', '[CALL[F1][ENUM[TUPLE[a][b]]]]'],
|
|
61
|
+
// Tuple & Set constructors
|
|
62
|
+
['(a,b,c)', '[TUPLE[a][b][c]]'],
|
|
63
|
+
['{a,b,c}', '[ENUM[a][b][c]]'],
|
|
64
|
+
['{a}', '[ENUM[a]]'],
|
|
65
|
+
['{(a,b)}', '[ENUM[TUPLE[a][b]]]'],
|
|
66
|
+
['{(a,b),(b,c)}', '[ENUM[TUPLE[a][b]][TUPLE[b][c]]]'],
|
|
67
|
+
['{{a, b}, {c, d}}', '[ENUM[ENUM[a][b]][ENUM[c][d]]]'],
|
|
68
|
+
['ℬ(a)', '[ℬ[a]]'],
|
|
69
|
+
['ℬℬ(a)', '[ℬ[ℬ[a]]]'],
|
|
70
|
+
['a×b×c', '[×[a][b][c]]'],
|
|
71
|
+
['a×(b×c)', '[×[a][×[b][c]]]'],
|
|
72
|
+
// Term constructors
|
|
73
|
+
['D{a∈X1 | 1=2}', '[DECLARATIVE[a][X1][=[1][2]]]'],
|
|
74
|
+
['{a∈X1 | 1=2}', '[DECLARATIVE[a][X1][=[1][2]]]'],
|
|
75
|
+
['D{(a,b)∈X1 | 1=2}', '[DECLARATIVE[TUPLE_DECLARE[a][b]][X1][=[1][2]]]'],
|
|
76
|
+
['R{a:=S1 | card(a)<10 | a \\ a}', '[RECURSIVE[a][S1][<[card[a]][10]][\\[a][a]]]'],
|
|
77
|
+
['R{a:=S1 | a \\ a}', '[RECURSIVE[a][S1][\\[a][a]]]'],
|
|
78
|
+
['R{(a,b):=S1 | (a \\ a, b)}', '[RECURSIVE[TUPLE_DECLARE[a][b]][S1][TUPLE[\\[a][a]][b]]]'],
|
|
79
|
+
['I{(a, b) | a:∈X1; b:=a}', '[IMPERATIVE[TUPLE[a][b]][:∈[a][X1]][:=[b][a]]]'],
|
|
80
|
+
['I{(a, b) | (a,b):∈Z×Z}', '[IMPERATIVE[TUPLE[a][b]][:∈[TUPLE_DECLARE[a][b]][×[Z][Z]]]]'],
|
|
81
|
+
['I{(a, b) | a:∈X1; b:=a; (a,b) ∈ S1}', '[IMPERATIVE[TUPLE[a][b]][:∈[a][X1]][:=[b][a]][∈[TUPLE[a][b]][S1]]]'],
|
|
82
|
+
// Functions
|
|
83
|
+
[
|
|
84
|
+
'[σ∈ℬ((R1×R1)×R1)] ∀((α1,α2),γ)∈σ ((α2,α1),γ)∈σ',
|
|
85
|
+
'[FUNCTION_DEFINE[ARGS[ARG[σ][ℬ[×[×[R1][R1]][R1]]]]][∀[TUPLE_DECLARE[TUPLE_DECLARE[α1][α2]][γ]][σ][∈[TUPLE[TUPLE[α2][α1]][γ]][σ]]]]'
|
|
86
|
+
]
|
|
87
|
+
];
|
|
88
|
+
|
|
89
|
+
// .filter(([input]) => input === 'I{(a, b) | (a,b):∈Z×Z}')
|
|
90
|
+
describe('Testing AST normalization', () => {
|
|
91
|
+
testData.forEach(([input, expectedTree]) => {
|
|
92
|
+
it(`Parse "${input}"`, () => {
|
|
93
|
+
const tree = parser.configure({ strict: false }).parse(input);
|
|
94
|
+
const ast = buildTree(tree.cursor());
|
|
95
|
+
normalizeAST(ast, input);
|
|
96
|
+
expect(printAst(ast, labelRSLangNode)).toBe(expectedTree);
|
|
97
|
+
});
|
|
98
|
+
});
|
|
99
|
+
});
|