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.
Files changed (144) hide show
  1. package/.mocharc.json +3 -3
  2. package/CHANGELOG.md +26 -0
  3. package/README.md +193 -199
  4. package/dist/analyzer/GraphBuilder.d.ts +29 -0
  5. package/dist/analyzer/GraphBuilder.js +99 -0
  6. package/dist/analyzer/index.d.ts +11 -23
  7. package/dist/analyzer/index.js +100 -58
  8. package/dist/analyzer/inspections/functional/functional.d.ts +44 -0
  9. package/dist/analyzer/inspections/functional/functional.js +149 -0
  10. package/dist/analyzer/inspections/functional/smells.d.ts +16 -0
  11. package/dist/analyzer/inspections/functional/smells.js +98 -0
  12. package/dist/analyzer/inspections/{generic.d.ts → generic/generic.d.ts} +70 -43
  13. package/dist/analyzer/inspections/generic/generic.js +604 -0
  14. package/dist/analyzer/inspections/generic/smells.d.ts +61 -0
  15. package/dist/analyzer/inspections/generic/smells.js +349 -0
  16. package/dist/analyzer/inspections/imperative/imperative.d.ts +35 -0
  17. package/dist/analyzer/inspections/imperative/imperative.js +109 -0
  18. package/dist/analyzer/inspections/imperative/smells.d.ts +16 -0
  19. package/dist/analyzer/inspections/imperative/smells.js +58 -0
  20. package/dist/analyzer/inspections/logic/logic.d.ts +32 -0
  21. package/dist/analyzer/inspections/logic/logic.js +96 -0
  22. package/dist/analyzer/inspections/logic/smells.d.ts +15 -0
  23. package/dist/analyzer/inspections/logic/smells.js +60 -0
  24. package/dist/analyzer/inspections/object/object.d.ts +88 -0
  25. package/dist/analyzer/inspections/object/object.js +319 -0
  26. package/dist/analyzer/inspections/object/smells.d.ts +30 -0
  27. package/dist/analyzer/inspections/object/smells.js +135 -0
  28. package/dist/analyzer/utils.d.ts +26 -4
  29. package/dist/analyzer/utils.js +71 -13
  30. package/dist/index.d.ts +1 -0
  31. package/dist/index.js +1 -0
  32. package/dist/interpreter/components/EnvBuilder.d.ts +9 -5
  33. package/dist/interpreter/components/EnvBuilder.js +100 -30
  34. package/dist/interpreter/components/Operations.d.ts +4 -4
  35. package/dist/interpreter/components/Operations.js +17 -2
  36. package/dist/interpreter/components/PatternMatcher.d.ts +47 -17
  37. package/dist/interpreter/components/PatternMatcher.js +264 -119
  38. package/dist/interpreter/components/RuntimeContext.d.ts +35 -0
  39. package/dist/interpreter/components/RuntimeContext.js +93 -0
  40. package/dist/interpreter/components/TestRunner.d.ts +18 -0
  41. package/dist/interpreter/components/TestRunner.js +103 -0
  42. package/dist/interpreter/components/Visitor.d.ts +63 -57
  43. package/dist/interpreter/components/Visitor.js +508 -173
  44. package/dist/interpreter/components/logic/LogicEngine.d.ts +29 -0
  45. package/dist/interpreter/components/logic/LogicEngine.js +259 -0
  46. package/dist/interpreter/components/logic/LogicResolver.d.ts +53 -0
  47. package/dist/interpreter/components/logic/LogicResolver.js +471 -0
  48. package/dist/interpreter/components/logic/LogicTranslator.d.ts +14 -0
  49. package/dist/interpreter/components/logic/LogicTranslator.js +99 -0
  50. package/dist/interpreter/components/runtimes/FunctionRuntime.d.ts +12 -0
  51. package/dist/interpreter/components/runtimes/FunctionRuntime.js +147 -0
  52. package/dist/interpreter/components/runtimes/LazyRuntime.d.ts +19 -0
  53. package/dist/interpreter/components/runtimes/LazyRuntime.js +269 -0
  54. package/dist/interpreter/components/runtimes/ObjectRuntime.d.ts +35 -0
  55. package/dist/interpreter/components/runtimes/ObjectRuntime.js +126 -0
  56. package/dist/interpreter/entities.d.ts +105 -0
  57. package/dist/interpreter/entities.js +96 -0
  58. package/dist/interpreter/errors.d.ts +1 -1
  59. package/dist/interpreter/index.d.ts +4 -12
  60. package/dist/interpreter/index.js +10 -13
  61. package/dist/interpreter/trampoline.d.ts +17 -0
  62. package/dist/interpreter/trampoline.js +38 -0
  63. package/dist/interpreter/utils.d.ts +4 -7
  64. package/dist/interpreter/utils.js +25 -17
  65. package/dist/tester/index.d.ts +25 -0
  66. package/dist/tester/index.js +108 -0
  67. package/dist/utils/helpers.d.ts +0 -4
  68. package/dist/utils/helpers.js +20 -24
  69. package/package.json +2 -2
  70. package/src/analyzer/GraphBuilder.ts +142 -0
  71. package/src/analyzer/index.ts +185 -132
  72. package/src/analyzer/inspections/functional/functional.ts +121 -0
  73. package/src/analyzer/inspections/functional/smells.ts +102 -0
  74. package/src/analyzer/inspections/{generic.ts → generic/generic.ts} +581 -499
  75. package/src/analyzer/inspections/generic/smells.ts +365 -0
  76. package/src/analyzer/inspections/imperative/imperative.ts +101 -0
  77. package/src/analyzer/inspections/imperative/smells.ts +54 -0
  78. package/src/analyzer/inspections/logic/logic.ts +90 -0
  79. package/src/analyzer/inspections/logic/smells.ts +54 -0
  80. package/src/analyzer/inspections/{object.ts → object/object.ts} +264 -282
  81. package/src/analyzer/inspections/object/smells.ts +144 -0
  82. package/src/analyzer/utils.ts +109 -26
  83. package/src/index.ts +3 -2
  84. package/src/interpreter/components/EnvBuilder.ts +202 -97
  85. package/src/interpreter/components/Operations.ts +99 -81
  86. package/src/interpreter/components/PatternMatcher.ts +475 -254
  87. package/src/interpreter/components/RuntimeContext.ts +119 -0
  88. package/src/interpreter/components/TestRunner.ts +151 -0
  89. package/src/interpreter/components/Visitor.ts +1065 -493
  90. package/src/interpreter/components/logic/LogicEngine.ts +519 -0
  91. package/src/interpreter/components/logic/LogicResolver.ts +858 -0
  92. package/src/interpreter/components/logic/LogicTranslator.ts +149 -0
  93. package/src/interpreter/components/runtimes/FunctionRuntime.ts +227 -0
  94. package/src/interpreter/components/runtimes/LazyRuntime.ts +334 -0
  95. package/src/interpreter/components/runtimes/ObjectRuntime.ts +224 -0
  96. package/src/interpreter/errors.ts +47 -47
  97. package/src/interpreter/index.ts +52 -59
  98. package/src/interpreter/trampoline.ts +71 -0
  99. package/src/interpreter/utils.ts +84 -79
  100. package/src/tester/index.ts +128 -0
  101. package/src/utils/helpers.ts +67 -73
  102. package/tests/analyzer/functional.spec.ts +207 -221
  103. package/tests/analyzer/generic.spec.ts +178 -100
  104. package/tests/analyzer/helpers.spec.ts +83 -83
  105. package/tests/analyzer/logic.spec.ts +237 -292
  106. package/tests/analyzer/oop.spec.ts +323 -338
  107. package/tests/analyzer/transitive.spec.ts +166 -0
  108. package/tests/interpreter/EnvBuilder.spec.ts +183 -178
  109. package/tests/interpreter/FunctionRuntime.spec.ts +223 -234
  110. package/tests/interpreter/LazyRuntime.spec.ts +225 -190
  111. package/tests/interpreter/LogicEngine.spec.ts +327 -194
  112. package/tests/interpreter/LogicSubstitution.spec.ts +80 -0
  113. package/tests/interpreter/ObjectRuntime.spec.ts +606 -0
  114. package/tests/interpreter/Operations.spec.ts +220 -220
  115. package/tests/interpreter/PatternSystem.spec.ts +213 -189
  116. package/tests/interpreter/Tests.spec.ts +122 -0
  117. package/tests/interpreter/interpreter.spec.ts +991 -937
  118. package/tests/tester/Tester.spec.ts +153 -0
  119. package/tsconfig.build.json +15 -7
  120. package/tsconfig.json +25 -17
  121. package/dist/analyzer/inspections/functional.d.ts +0 -46
  122. package/dist/analyzer/inspections/functional.js +0 -123
  123. package/dist/analyzer/inspections/generic.js +0 -427
  124. package/dist/analyzer/inspections/imperative.d.ts +0 -37
  125. package/dist/analyzer/inspections/imperative.js +0 -105
  126. package/dist/analyzer/inspections/logic.d.ts +0 -49
  127. package/dist/analyzer/inspections/logic.js +0 -140
  128. package/dist/analyzer/inspections/object.d.ts +0 -83
  129. package/dist/analyzer/inspections/object.js +0 -235
  130. package/dist/interpreter/components/FunctionRuntime.d.ts +0 -8
  131. package/dist/interpreter/components/FunctionRuntime.js +0 -52
  132. package/dist/interpreter/components/LazyRuntime.d.ts +0 -7
  133. package/dist/interpreter/components/LazyRuntime.js +0 -75
  134. package/dist/interpreter/components/LogicEngine.d.ts +0 -21
  135. package/dist/interpreter/components/LogicEngine.js +0 -152
  136. package/dist/interpreter/components/LogicResolver.d.ts +0 -11
  137. package/dist/interpreter/components/LogicResolver.js +0 -87
  138. package/src/analyzer/inspections/functional.ts +0 -159
  139. package/src/analyzer/inspections/imperative.ts +0 -129
  140. package/src/analyzer/inspections/logic.ts +0 -166
  141. package/src/interpreter/components/FunctionRuntime.ts +0 -79
  142. package/src/interpreter/components/LazyRuntime.ts +0 -97
  143. package/src/interpreter/components/LogicEngine.ts +0 -227
  144. 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
+ };
@@ -1,26 +1,109 @@
1
- import { ASTNode, StopTraversalException, TraverseVisitor } from "yukigo-ast";
2
-
3
- export type InspectionHandler = (
4
- node: ASTNode,
5
- args: string[],
6
- binding?: string
7
- ) => boolean;
8
-
9
- export type InspectionMap = Record<string, InspectionHandler>;
10
-
11
- export function executeVisitor(
12
- node: ASTNode,
13
- visitor: TraverseVisitor
14
- ): boolean {
15
- let passes = false;
16
- try {
17
- node.accept(visitor);
18
- } catch (e) {
19
- if (e instanceof StopTraversalException) {
20
- passes = true;
21
- } else {
22
- throw e;
23
- }
24
- }
25
- return passes;
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,3 +1,4 @@
1
- export * from "./interpreter/index.js";
2
- export * from "./analyzer/index.js";
1
+ export * from "./interpreter/index.js";
2
+ export * from "./analyzer/index.js";
3
+ export * from "./tester/index.js";
3
4
  export * from "./utils/helpers.js";
@@ -1,97 +1,202 @@
1
- import {
2
- AST,
3
- ASTNode,
4
- EquationRuntime,
5
- Fact,
6
- Function,
7
- isRuntimePredicate,
8
- Rule,
9
- RuntimeFunction,
10
- TraverseVisitor,
11
- } from "yukigo-ast";
12
- import { EnvStack } from "../index.js";
13
- import { define } from "../utils.js";
14
-
15
- /**
16
- * Builds the initial environment by collecting all top-level function declarations.
17
- * Each function captures a closure of the environment at its definition time,
18
- * allowing recursion by including itself in the closure.
19
- */
20
- export class EnvBuilderVisitor extends TraverseVisitor {
21
- private env: EnvStack;
22
-
23
- constructor(baseEnv?: EnvStack) {
24
- super();
25
- this.env = baseEnv ?? [new Map()];
26
- }
27
-
28
- public build(ast: AST): EnvStack {
29
- for (const node of ast) node.accept(this);
30
- return this.env;
31
- }
32
- visitFunction(node: Function): void {
33
- const name = node.identifier.value;
34
-
35
- if (node.equations.length === 0)
36
- throw new Error(`Function ${name} has no equations`);
37
-
38
- const arity = node.equations[0].patterns.length;
39
- for (const eq of node.equations) {
40
- if (eq.patterns.length !== arity)
41
- throw new Error(`All equations of ${name} must have the same arity`);
42
- }
43
-
44
- let placeholder: RuntimeFunction;
45
- define(this.env, name, placeholder);
46
-
47
- const equations: EquationRuntime[] = node.equations.map((eq) => ({
48
- patterns: eq.patterns,
49
- body: eq.body,
50
- }));
51
-
52
- const runtimeFunc: RuntimeFunction = {
53
- identifier: name,
54
- arity,
55
- equations,
56
- };
57
- define(this.env, name, runtimeFunc);
58
- }
59
- visitFact(node: Fact): void {
60
- const identifier = node.identifier.value;
61
- const runtimeValue = this.env[0].get(identifier);
62
-
63
- if (isRuntimePredicate(runtimeValue) && runtimeValue.kind === "Fact") {
64
- this.env[0].set(identifier, {
65
- ...runtimeValue,
66
- equations: [...runtimeValue.equations, node],
67
- });
68
- } else {
69
- this.env[0].set(identifier, {
70
- kind: "Fact",
71
- identifier,
72
- equations: [node],
73
- });
74
- }
75
- }
76
-
77
- visitRule(node: Rule): void {
78
- const identifier = node.identifier.value;
79
- const runtimeValue = this.env[0].get(identifier);
80
-
81
- if (isRuntimePredicate(runtimeValue) && runtimeValue.kind === "Rule") {
82
- this.env[0].set(identifier, {
83
- ...runtimeValue,
84
- equations: [...runtimeValue.equations, node],
85
- });
86
- } else {
87
- this.env[0].set(identifier, {
88
- kind: "Rule",
89
- identifier,
90
- equations: [node],
91
- });
92
- }
93
- }
94
- visit(node: ASTNode): void {
95
- return node.accept(this);
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
+ }