yukigo 0.1.0 → 0.2.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/.mocharc.json +3 -3
- package/CHANGELOG.md +26 -0
- package/README.md +193 -199
- package/dist/analyzer/GraphBuilder.d.ts +29 -0
- package/dist/analyzer/GraphBuilder.js +99 -0
- package/dist/analyzer/index.d.ts +11 -23
- package/dist/analyzer/index.js +100 -58
- package/dist/analyzer/inspections/functional/functional.d.ts +44 -0
- package/dist/analyzer/inspections/functional/functional.js +149 -0
- package/dist/analyzer/inspections/functional/smells.d.ts +16 -0
- package/dist/analyzer/inspections/functional/smells.js +98 -0
- package/dist/analyzer/inspections/{generic.d.ts → generic/generic.d.ts} +70 -43
- package/dist/analyzer/inspections/generic/generic.js +604 -0
- package/dist/analyzer/inspections/generic/smells.d.ts +61 -0
- package/dist/analyzer/inspections/generic/smells.js +349 -0
- package/dist/analyzer/inspections/imperative/imperative.d.ts +35 -0
- package/dist/analyzer/inspections/imperative/imperative.js +109 -0
- package/dist/analyzer/inspections/imperative/smells.d.ts +16 -0
- package/dist/analyzer/inspections/imperative/smells.js +58 -0
- package/dist/analyzer/inspections/logic/logic.d.ts +32 -0
- package/dist/analyzer/inspections/logic/logic.js +96 -0
- package/dist/analyzer/inspections/logic/smells.d.ts +15 -0
- package/dist/analyzer/inspections/logic/smells.js +60 -0
- package/dist/analyzer/inspections/object/object.d.ts +88 -0
- package/dist/analyzer/inspections/object/object.js +319 -0
- package/dist/analyzer/inspections/object/smells.d.ts +30 -0
- package/dist/analyzer/inspections/object/smells.js +135 -0
- package/dist/analyzer/utils.d.ts +26 -4
- package/dist/analyzer/utils.js +71 -13
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/interpreter/components/EnvBuilder.d.ts +9 -5
- package/dist/interpreter/components/EnvBuilder.js +100 -30
- package/dist/interpreter/components/Operations.d.ts +4 -4
- package/dist/interpreter/components/Operations.js +17 -2
- package/dist/interpreter/components/PatternMatcher.d.ts +47 -17
- package/dist/interpreter/components/PatternMatcher.js +264 -119
- package/dist/interpreter/components/RuntimeContext.d.ts +35 -0
- package/dist/interpreter/components/RuntimeContext.js +93 -0
- package/dist/interpreter/components/TestRunner.d.ts +18 -0
- package/dist/interpreter/components/TestRunner.js +103 -0
- package/dist/interpreter/components/Visitor.d.ts +63 -57
- package/dist/interpreter/components/Visitor.js +508 -173
- package/dist/interpreter/components/logic/LogicEngine.d.ts +29 -0
- package/dist/interpreter/components/logic/LogicEngine.js +259 -0
- package/dist/interpreter/components/logic/LogicResolver.d.ts +53 -0
- package/dist/interpreter/components/logic/LogicResolver.js +471 -0
- package/dist/interpreter/components/logic/LogicTranslator.d.ts +14 -0
- package/dist/interpreter/components/logic/LogicTranslator.js +99 -0
- package/dist/interpreter/components/runtimes/FunctionRuntime.d.ts +12 -0
- package/dist/interpreter/components/runtimes/FunctionRuntime.js +147 -0
- package/dist/interpreter/components/runtimes/LazyRuntime.d.ts +19 -0
- package/dist/interpreter/components/runtimes/LazyRuntime.js +269 -0
- package/dist/interpreter/components/runtimes/ObjectRuntime.d.ts +35 -0
- package/dist/interpreter/components/runtimes/ObjectRuntime.js +126 -0
- package/dist/interpreter/entities.d.ts +105 -0
- package/dist/interpreter/entities.js +96 -0
- package/dist/interpreter/errors.d.ts +1 -1
- package/dist/interpreter/index.d.ts +4 -12
- package/dist/interpreter/index.js +10 -13
- package/dist/interpreter/trampoline.d.ts +17 -0
- package/dist/interpreter/trampoline.js +38 -0
- package/dist/interpreter/utils.d.ts +4 -7
- package/dist/interpreter/utils.js +25 -17
- package/dist/tester/index.d.ts +25 -0
- package/dist/tester/index.js +108 -0
- package/dist/utils/helpers.d.ts +0 -4
- package/dist/utils/helpers.js +20 -24
- package/package.json +2 -2
- package/src/analyzer/GraphBuilder.ts +142 -0
- package/src/analyzer/index.ts +185 -132
- package/src/analyzer/inspections/functional/functional.ts +121 -0
- package/src/analyzer/inspections/functional/smells.ts +102 -0
- package/src/analyzer/inspections/{generic.ts → generic/generic.ts} +581 -499
- package/src/analyzer/inspections/generic/smells.ts +365 -0
- package/src/analyzer/inspections/imperative/imperative.ts +101 -0
- package/src/analyzer/inspections/imperative/smells.ts +54 -0
- package/src/analyzer/inspections/logic/logic.ts +90 -0
- package/src/analyzer/inspections/logic/smells.ts +54 -0
- package/src/analyzer/inspections/{object.ts → object/object.ts} +264 -282
- package/src/analyzer/inspections/object/smells.ts +144 -0
- package/src/analyzer/utils.ts +109 -26
- package/src/index.ts +3 -2
- package/src/interpreter/components/EnvBuilder.ts +202 -97
- package/src/interpreter/components/Operations.ts +99 -81
- package/src/interpreter/components/PatternMatcher.ts +475 -254
- package/src/interpreter/components/RuntimeContext.ts +119 -0
- package/src/interpreter/components/TestRunner.ts +151 -0
- package/src/interpreter/components/Visitor.ts +1065 -493
- package/src/interpreter/components/logic/LogicEngine.ts +519 -0
- package/src/interpreter/components/logic/LogicResolver.ts +858 -0
- package/src/interpreter/components/logic/LogicTranslator.ts +149 -0
- package/src/interpreter/components/runtimes/FunctionRuntime.ts +227 -0
- package/src/interpreter/components/runtimes/LazyRuntime.ts +334 -0
- package/src/interpreter/components/runtimes/ObjectRuntime.ts +224 -0
- package/src/interpreter/errors.ts +47 -47
- package/src/interpreter/index.ts +52 -59
- package/src/interpreter/trampoline.ts +71 -0
- package/src/interpreter/utils.ts +84 -79
- package/src/tester/index.ts +128 -0
- package/src/utils/helpers.ts +67 -73
- package/tests/analyzer/functional.spec.ts +207 -221
- package/tests/analyzer/generic.spec.ts +178 -100
- package/tests/analyzer/helpers.spec.ts +83 -83
- package/tests/analyzer/logic.spec.ts +237 -292
- package/tests/analyzer/oop.spec.ts +323 -338
- package/tests/analyzer/transitive.spec.ts +166 -0
- package/tests/interpreter/EnvBuilder.spec.ts +183 -178
- package/tests/interpreter/FunctionRuntime.spec.ts +223 -234
- package/tests/interpreter/LazyRuntime.spec.ts +225 -190
- package/tests/interpreter/LogicEngine.spec.ts +327 -194
- package/tests/interpreter/LogicSubstitution.spec.ts +80 -0
- package/tests/interpreter/ObjectRuntime.spec.ts +606 -0
- package/tests/interpreter/Operations.spec.ts +220 -220
- package/tests/interpreter/PatternSystem.spec.ts +213 -189
- package/tests/interpreter/Tests.spec.ts +122 -0
- package/tests/interpreter/interpreter.spec.ts +991 -937
- package/tests/tester/Tester.spec.ts +153 -0
- package/tsconfig.build.json +15 -7
- package/tsconfig.json +25 -17
- package/dist/analyzer/inspections/functional.d.ts +0 -46
- package/dist/analyzer/inspections/functional.js +0 -123
- package/dist/analyzer/inspections/generic.js +0 -427
- package/dist/analyzer/inspections/imperative.d.ts +0 -37
- package/dist/analyzer/inspections/imperative.js +0 -105
- package/dist/analyzer/inspections/logic.d.ts +0 -49
- package/dist/analyzer/inspections/logic.js +0 -140
- package/dist/analyzer/inspections/object.d.ts +0 -83
- package/dist/analyzer/inspections/object.js +0 -235
- package/dist/interpreter/components/FunctionRuntime.d.ts +0 -8
- package/dist/interpreter/components/FunctionRuntime.js +0 -52
- package/dist/interpreter/components/LazyRuntime.d.ts +0 -7
- package/dist/interpreter/components/LazyRuntime.js +0 -75
- package/dist/interpreter/components/LogicEngine.d.ts +0 -21
- package/dist/interpreter/components/LogicEngine.js +0 -152
- package/dist/interpreter/components/LogicResolver.d.ts +0 -11
- package/dist/interpreter/components/LogicResolver.js +0 -87
- package/src/analyzer/inspections/functional.ts +0 -159
- package/src/analyzer/inspections/imperative.ts +0 -129
- package/src/analyzer/inspections/logic.ts +0 -166
- package/src/interpreter/components/FunctionRuntime.ts +0 -79
- package/src/interpreter/components/LazyRuntime.ts +0 -97
- package/src/interpreter/components/LogicEngine.ts +0 -227
- package/src/interpreter/components/LogicResolver.ts +0 -130
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
import {
|
|
2
|
+
ASTNode,
|
|
3
|
+
Class,
|
|
4
|
+
Interface,
|
|
5
|
+
LogicalBinaryOperation,
|
|
6
|
+
Method,
|
|
7
|
+
NilPrimitive,
|
|
8
|
+
Object,
|
|
9
|
+
Return,
|
|
10
|
+
StopTraversalException,
|
|
11
|
+
StringPrimitive,
|
|
12
|
+
TraverseVisitor,
|
|
13
|
+
} from "yukigo-ast";
|
|
14
|
+
import { AutoScoped, ScopedVisitor, VisitorConstructor } from "../../utils.js";
|
|
15
|
+
import { Uses } from "../generic/generic.js";
|
|
16
|
+
|
|
17
|
+
class MethodCollector extends TraverseVisitor {
|
|
18
|
+
private collectedMethods: Method[] = [];
|
|
19
|
+
collect(node: Class | Object | Interface): Method[] {
|
|
20
|
+
node.expression.accept(this);
|
|
21
|
+
return this.collectedMethods;
|
|
22
|
+
}
|
|
23
|
+
visitMethod(node: Method) {
|
|
24
|
+
this.collectedMethods.push(node);
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
@AutoScoped
|
|
29
|
+
export class DoesNilTest extends ScopedVisitor {
|
|
30
|
+
visitLogicalBinaryOperation(node: LogicalBinaryOperation): void {
|
|
31
|
+
if (
|
|
32
|
+
node.left instanceof NilPrimitive ||
|
|
33
|
+
node.right instanceof NilPrimitive
|
|
34
|
+
) {
|
|
35
|
+
throw new StopTraversalException();
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
@AutoScoped
|
|
41
|
+
export class DoesTypeTest extends ScopedVisitor {
|
|
42
|
+
visitLogicalBinaryOperation(node: LogicalBinaryOperation): void {
|
|
43
|
+
if (
|
|
44
|
+
node.left instanceof StringPrimitive ||
|
|
45
|
+
node.right instanceof StringPrimitive
|
|
46
|
+
) {
|
|
47
|
+
throw new StopTraversalException();
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
@AutoScoped
|
|
53
|
+
export class ReturnsNil extends ScopedVisitor {
|
|
54
|
+
visitReturn(node: Return): void {
|
|
55
|
+
if (!Boolean(node.body) || node.body instanceof NilPrimitive)
|
|
56
|
+
throw new StopTraversalException();
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
@AutoScoped
|
|
61
|
+
export class HasTooManyMethods extends ScopedVisitor {
|
|
62
|
+
private counter: number;
|
|
63
|
+
private readonly maxMethods: number;
|
|
64
|
+
|
|
65
|
+
constructor(maxMethods: number = 10, scopeName?: string) {
|
|
66
|
+
super(scopeName);
|
|
67
|
+
this.maxMethods = maxMethods;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
visitInterface(node: Interface): void {
|
|
71
|
+
node.expression.accept(this);
|
|
72
|
+
this.checkMethodCount();
|
|
73
|
+
this.counter = 0;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
visitClass(node: Class): void {
|
|
77
|
+
node.expression.accept(this);
|
|
78
|
+
this.checkMethodCount();
|
|
79
|
+
this.counter = 0;
|
|
80
|
+
}
|
|
81
|
+
visitObject(node: Object): void {
|
|
82
|
+
node.expression.accept(this);
|
|
83
|
+
this.checkMethodCount();
|
|
84
|
+
this.counter = 0;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
visitMethod(node: Method): void {
|
|
88
|
+
this.counter += 1;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
private checkMethodCount(): void {
|
|
92
|
+
if (this.counter > this.maxMethods) throw new StopTraversalException();
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
@AutoScoped
|
|
97
|
+
export class OverridesEqualOrHashButNotBoth extends ScopedVisitor {
|
|
98
|
+
visitClass(node: Class): void {
|
|
99
|
+
const collector = new MethodCollector();
|
|
100
|
+
const methods = collector.collect(node);
|
|
101
|
+
const names = methods.map((m) => m.identifier.value);
|
|
102
|
+
const hasEquals =
|
|
103
|
+
names.includes("equals") ||
|
|
104
|
+
names.includes("==") ||
|
|
105
|
+
names.includes("eql?");
|
|
106
|
+
|
|
107
|
+
const hasHash = names.includes("hashCode") || names.includes("hash");
|
|
108
|
+
|
|
109
|
+
if ((hasEquals && !hasHash) || (!hasEquals && hasHash))
|
|
110
|
+
throw new StopTraversalException();
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
@AutoScoped
|
|
115
|
+
export class UsesNamedSelfReference extends ScopedVisitor {
|
|
116
|
+
visitObject(node: Object): void {
|
|
117
|
+
this.checkSelfReference(node.identifier.value, node.expression);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
visitClass(node: Class): void {
|
|
121
|
+
this.checkSelfReference(node.identifier.value, node.expression);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
private checkSelfReference(selfName: string, body: ASTNode): void {
|
|
125
|
+
const checker = new Uses(selfName, this.binding);
|
|
126
|
+
|
|
127
|
+
try {
|
|
128
|
+
body.accept(checker);
|
|
129
|
+
} catch (e) {
|
|
130
|
+
if (e instanceof StopTraversalException)
|
|
131
|
+
throw new StopTraversalException();
|
|
132
|
+
throw e;
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
export const objectSmells: Record<string, VisitorConstructor> = {
|
|
138
|
+
DoesNilTest: DoesNilTest,
|
|
139
|
+
DoesTypeTest: DoesTypeTest,
|
|
140
|
+
HasTooManyMethods: HasTooManyMethods,
|
|
141
|
+
OverridesEqualOrHashButNotBoth: OverridesEqualOrHashButNotBoth,
|
|
142
|
+
ReturnsNil: ReturnsNil,
|
|
143
|
+
UsesNamedSelfReference: UsesNamedSelfReference,
|
|
144
|
+
};
|
package/src/analyzer/utils.ts
CHANGED
|
@@ -1,26 +1,109 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
1
|
+
import {
|
|
2
|
+
Class,
|
|
3
|
+
Fact,
|
|
4
|
+
Function,
|
|
5
|
+
Method,
|
|
6
|
+
Object as YuObject,
|
|
7
|
+
Procedure,
|
|
8
|
+
Rule,
|
|
9
|
+
TraverseVisitor,
|
|
10
|
+
Variable,
|
|
11
|
+
ASTNode,
|
|
12
|
+
} from "yukigo-ast";
|
|
13
|
+
|
|
14
|
+
export interface VisitorConstructor {
|
|
15
|
+
new (...args: any[]): TraverseVisitor & { setBinding?(binding: string): void };
|
|
16
|
+
IS_INTRANSITIVE?: boolean;
|
|
17
|
+
}
|
|
18
|
+
export type InspectionMap = Record<string, VisitorConstructor>;
|
|
19
|
+
|
|
20
|
+
type TopNode =
|
|
21
|
+
| Variable
|
|
22
|
+
| Function
|
|
23
|
+
| Class
|
|
24
|
+
| YuObject
|
|
25
|
+
| Method
|
|
26
|
+
| Rule
|
|
27
|
+
| Fact
|
|
28
|
+
| Procedure;
|
|
29
|
+
export class ScopedVisitor extends TraverseVisitor {
|
|
30
|
+
protected binding?: string;
|
|
31
|
+
public inScope: boolean;
|
|
32
|
+
|
|
33
|
+
constructor(binding?: string) {
|
|
34
|
+
super();
|
|
35
|
+
this.setBinding(binding)
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
setBinding(binding?: string) {
|
|
39
|
+
this.binding = binding;
|
|
40
|
+
this.inScope = !binding;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
visitVariable(node: Variable): void {
|
|
45
|
+
this.manageScope(node, () => super.visitVariable(node));
|
|
46
|
+
}
|
|
47
|
+
visitFunction(node: Function): void {
|
|
48
|
+
this.manageScope(node, () => super.visitFunction(node));
|
|
49
|
+
}
|
|
50
|
+
visitClass(node: Class): void {
|
|
51
|
+
this.manageScope(node, () => super.visitClass(node));
|
|
52
|
+
}
|
|
53
|
+
visitObject(node: YuObject): void {
|
|
54
|
+
this.manageScope(node, () => super.visitObject(node));
|
|
55
|
+
}
|
|
56
|
+
visitMethod(node: Method): void {
|
|
57
|
+
this.manageScope(node, () => super.visitMethod(node));
|
|
58
|
+
}
|
|
59
|
+
visitRule(node: Rule): void {
|
|
60
|
+
this.manageScope(node, () => super.visitRule(node));
|
|
61
|
+
}
|
|
62
|
+
visitFact(node: Fact): void {
|
|
63
|
+
this.manageScope(node, () => super.visitFact(node));
|
|
64
|
+
}
|
|
65
|
+
visitProcedure(node: Procedure): void {
|
|
66
|
+
this.manageScope(node, () => super.visitProcedure(node));
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
private manageScope(node: TopNode, traverse: () => void) {
|
|
70
|
+
const isTarget = node.identifier.value === this.binding;
|
|
71
|
+
if (isTarget) this.inScope = true;
|
|
72
|
+
traverse();
|
|
73
|
+
if (isTarget) this.inScope = false;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
export function AutoScoped<T extends { new (...args: any[]): any }>(
|
|
78
|
+
constructor: T
|
|
79
|
+
) {
|
|
80
|
+
const proto = constructor.prototype;
|
|
81
|
+
const methodNames = Object.getOwnPropertyNames(proto);
|
|
82
|
+
|
|
83
|
+
for (const name of methodNames) {
|
|
84
|
+
if (isValidVisitMethod(name)) {
|
|
85
|
+
const originalMethod = proto[name];
|
|
86
|
+
|
|
87
|
+
// replace the method on the prototype
|
|
88
|
+
proto[name] = function (this: any, node: ASTNode) {
|
|
89
|
+
if (!this.inScope) return;
|
|
90
|
+
// run the inspection logic if in scope
|
|
91
|
+
return originalMethod.call(this, node);
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
function isScopeBoundary(name: string): boolean {
|
|
97
|
+
return [
|
|
98
|
+
"visitFunction",
|
|
99
|
+
"visitMethod",
|
|
100
|
+
"visitProcedure",
|
|
101
|
+
"visitRule",
|
|
102
|
+
"visitFact",
|
|
103
|
+
"visitVariable",
|
|
104
|
+
"visitClass",
|
|
105
|
+
"visitObject",
|
|
106
|
+
].includes(name);
|
|
107
|
+
}
|
|
108
|
+
const isValidVisitMethod = (name: string) =>
|
|
109
|
+
name.startsWith("visit") && name !== "constructor" && !isScopeBoundary(name);
|
package/src/index.ts
CHANGED
|
@@ -1,97 +1,202 @@
|
|
|
1
|
-
import {
|
|
2
|
-
AST,
|
|
3
|
-
ASTNode,
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
1
|
+
import {
|
|
2
|
+
AST,
|
|
3
|
+
ASTNode,
|
|
4
|
+
Attribute,
|
|
5
|
+
Class,
|
|
6
|
+
EquationRuntime,
|
|
7
|
+
Fact,
|
|
8
|
+
Function,
|
|
9
|
+
isRuntimePredicate,
|
|
10
|
+
Method,
|
|
11
|
+
PrimitiveValue,
|
|
12
|
+
Rule,
|
|
13
|
+
RuntimeClass,
|
|
14
|
+
RuntimeFunction,
|
|
15
|
+
EnvStack,
|
|
16
|
+
TraverseVisitor,
|
|
17
|
+
Object,
|
|
18
|
+
RuntimeObject,
|
|
19
|
+
Variable,
|
|
20
|
+
Return,
|
|
21
|
+
Sequence,
|
|
22
|
+
SymbolPrimitive,
|
|
23
|
+
VariablePattern,
|
|
24
|
+
} from "yukigo-ast";
|
|
25
|
+
import { InterpreterVisitor } from "./Visitor.js";
|
|
26
|
+
import { idContinuation, trampoline } from "../trampoline.js";
|
|
27
|
+
import { RuntimeContext } from "./RuntimeContext.js";
|
|
28
|
+
import { InterpreterError } from "../errors.js";
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Builds the initial environment by collecting all top-level function declarations.
|
|
32
|
+
* Each function captures a closure of the environment at its definition time,
|
|
33
|
+
* allowing recursion by including itself in the closure.
|
|
34
|
+
*/
|
|
35
|
+
export class EnvBuilderVisitor extends TraverseVisitor {
|
|
36
|
+
constructor(private ctx: RuntimeContext) {
|
|
37
|
+
super();
|
|
38
|
+
}
|
|
39
|
+
public build(ast: AST) {
|
|
40
|
+
for (const node of ast) node.accept(this);
|
|
41
|
+
}
|
|
42
|
+
visitSequence(node: Sequence): void {
|
|
43
|
+
for (const stmt of node.statements) stmt.accept(this);
|
|
44
|
+
}
|
|
45
|
+
visitFunction(node: Function): void {
|
|
46
|
+
const name = node.identifier.value;
|
|
47
|
+
|
|
48
|
+
if (this.ctx.config.debug)
|
|
49
|
+
console.log(`[EnvBuilder] Defining function: ${name}`);
|
|
50
|
+
|
|
51
|
+
if (node.equations.length === 0)
|
|
52
|
+
throw new Error(`Function ${name} has no equations`);
|
|
53
|
+
|
|
54
|
+
const arity = node.equations[0].patterns.length;
|
|
55
|
+
|
|
56
|
+
if (node.equations.some((eq) => eq.patterns.length !== arity))
|
|
57
|
+
throw new Error(`All equations of ${name} must have the same arity`);
|
|
58
|
+
|
|
59
|
+
let placeholder: RuntimeFunction;
|
|
60
|
+
this.ctx.define(name, placeholder);
|
|
61
|
+
|
|
62
|
+
const equations: EquationRuntime[] = node.equations.map((eq) => ({
|
|
63
|
+
patterns: eq.patterns,
|
|
64
|
+
body: eq.body,
|
|
65
|
+
}));
|
|
66
|
+
|
|
67
|
+
const runtimeFunc: RuntimeFunction = {
|
|
68
|
+
type: "Function",
|
|
69
|
+
identifier: name,
|
|
70
|
+
arity,
|
|
71
|
+
equations,
|
|
72
|
+
closure: this.ctx.env,
|
|
73
|
+
};
|
|
74
|
+
this.ctx.define(name, runtimeFunc);
|
|
75
|
+
}
|
|
76
|
+
visitClass(node: Class): void {
|
|
77
|
+
const identifier = node.identifier.value;
|
|
78
|
+
|
|
79
|
+
if (this.ctx.config.debug)
|
|
80
|
+
console.log(`[EnvBuilder] Defining class: ${identifier}`);
|
|
81
|
+
|
|
82
|
+
const superclass = node.extendsSymbol?.value;
|
|
83
|
+
|
|
84
|
+
const mixins = node.includes.map((symbol) => symbol.value);
|
|
85
|
+
|
|
86
|
+
const collector = new OOPCollector();
|
|
87
|
+
node.expression.accept(collector);
|
|
88
|
+
|
|
89
|
+
const fields = collector.collectedFields;
|
|
90
|
+
const methods = collector.collectedMethods;
|
|
91
|
+
|
|
92
|
+
const runtimeClass: RuntimeClass = {
|
|
93
|
+
type: "Class",
|
|
94
|
+
identifier,
|
|
95
|
+
fields,
|
|
96
|
+
methods,
|
|
97
|
+
superclass,
|
|
98
|
+
mixins,
|
|
99
|
+
};
|
|
100
|
+
|
|
101
|
+
this.ctx.define(identifier, runtimeClass);
|
|
102
|
+
}
|
|
103
|
+
visitObject(node: Object): void {
|
|
104
|
+
const identifier = node.identifier.value;
|
|
105
|
+
|
|
106
|
+
if (this.ctx.config.debug)
|
|
107
|
+
console.log(`[EnvBuilder] Defining object: ${identifier}`);
|
|
108
|
+
|
|
109
|
+
const collector = new OOPCollector();
|
|
110
|
+
node.expression.accept(collector);
|
|
111
|
+
|
|
112
|
+
const fields = collector.collectedFields;
|
|
113
|
+
const methods = collector.collectedMethods;
|
|
114
|
+
|
|
115
|
+
const runtimeObject: RuntimeObject = {
|
|
116
|
+
type: "Object",
|
|
117
|
+
identifier,
|
|
118
|
+
className: "",
|
|
119
|
+
fields,
|
|
120
|
+
methods,
|
|
121
|
+
};
|
|
122
|
+
|
|
123
|
+
this.ctx.define(identifier, runtimeObject);
|
|
124
|
+
}
|
|
125
|
+
visitFact(node: Fact): void {
|
|
126
|
+
const identifier = node.identifier.value;
|
|
127
|
+
|
|
128
|
+
if (this.ctx.config.debug)
|
|
129
|
+
console.log(`[EnvBuilder] Defining fact: ${identifier}`);
|
|
130
|
+
try {
|
|
131
|
+
const runtimeValue = this.ctx.lookup(identifier);
|
|
132
|
+
if (!isRuntimePredicate(runtimeValue))
|
|
133
|
+
throw new InterpreterError(
|
|
134
|
+
"EnvBuilder",
|
|
135
|
+
`"${identifier}" is not a predicate. Maybe there is something else defined as "${identifier}"?`,
|
|
136
|
+
);
|
|
137
|
+
runtimeValue.equations.push(node);
|
|
138
|
+
} catch (error) {
|
|
139
|
+
this.ctx.define(identifier, {
|
|
140
|
+
kind: "Predicate",
|
|
141
|
+
identifier,
|
|
142
|
+
equations: [node],
|
|
143
|
+
});
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
visitRule(node: Rule): void {
|
|
148
|
+
const identifier = node.identifier.value;
|
|
149
|
+
|
|
150
|
+
if (this.ctx.config.debug)
|
|
151
|
+
console.log(`[EnvBuilder] Defining rule: ${identifier}`);
|
|
152
|
+
try {
|
|
153
|
+
const runtimeValue = this.ctx.lookup(identifier);
|
|
154
|
+
if (!isRuntimePredicate(runtimeValue))
|
|
155
|
+
throw new InterpreterError(
|
|
156
|
+
"EnvBuilder",
|
|
157
|
+
`"${identifier}" is not a predicate. Maybe there is something else defined as "${identifier}"?`,
|
|
158
|
+
);
|
|
159
|
+
runtimeValue.equations.push(node);
|
|
160
|
+
} catch (error) {
|
|
161
|
+
this.ctx.define(identifier, {
|
|
162
|
+
kind: "Predicate",
|
|
163
|
+
identifier,
|
|
164
|
+
equations: [node],
|
|
165
|
+
});
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
visitVariable(node: Variable): void {
|
|
169
|
+
const identifier = node.identifier.value;
|
|
170
|
+
|
|
171
|
+
if (this.ctx.config.debug)
|
|
172
|
+
console.log(`[EnvBuilder] Defining variable: ${identifier}`);
|
|
173
|
+
|
|
174
|
+
const interpreter = new InterpreterVisitor(this.ctx);
|
|
175
|
+
const cps = node.expression.accept(interpreter);
|
|
176
|
+
this.ctx.define(identifier, trampoline(cps(idContinuation)));
|
|
177
|
+
}
|
|
178
|
+
visit(node: ASTNode): void {
|
|
179
|
+
return node.accept(this);
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
class OOPCollector extends TraverseVisitor {
|
|
184
|
+
public collectedMethods: Map<string, RuntimeFunction> = new Map();
|
|
185
|
+
public collectedFields: Map<string, PrimitiveValue> = new Map();
|
|
186
|
+
visitMethod(node: Method) {
|
|
187
|
+
const runtimeMethod: RuntimeFunction = {
|
|
188
|
+
type: "Function",
|
|
189
|
+
identifier: node.identifier.value,
|
|
190
|
+
arity: node.equations[0].patterns.length,
|
|
191
|
+
equations: node.equations,
|
|
192
|
+
};
|
|
193
|
+
this.collectedMethods.set(node.identifier.value, runtimeMethod);
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
visitAttribute(node: Attribute) {
|
|
197
|
+
this.collectedFields.set(
|
|
198
|
+
node.identifier.value,
|
|
199
|
+
InterpreterVisitor.evaluateLiteral(node.expression),
|
|
200
|
+
);
|
|
201
|
+
}
|
|
202
|
+
}
|