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,224 @@
|
|
|
1
|
+
import {
|
|
2
|
+
PrimitiveValue,
|
|
3
|
+
RuntimeFunction,
|
|
4
|
+
RuntimeObject,
|
|
5
|
+
isRuntimeObject,
|
|
6
|
+
isRuntimeClass,
|
|
7
|
+
RuntimeClass,
|
|
8
|
+
EnvStack,
|
|
9
|
+
} from "yukigo-ast";
|
|
10
|
+
import { InterpreterError } from "../../errors.js";
|
|
11
|
+
import { Continuation, Thunk } from "../../trampoline.js";
|
|
12
|
+
import { RuntimeContext } from "../RuntimeContext.js";
|
|
13
|
+
|
|
14
|
+
type OOPEntity = RuntimeClass | RuntimeObject;
|
|
15
|
+
|
|
16
|
+
type OOPMatch = {
|
|
17
|
+
method: RuntimeFunction;
|
|
18
|
+
holder: RuntimeObject | RuntimeClass;
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
export class ObjectRuntime {
|
|
22
|
+
constructor(private context: RuntimeContext) {}
|
|
23
|
+
/**
|
|
24
|
+
* Creates a new instance of an Object.
|
|
25
|
+
* Typically called by visitNew()
|
|
26
|
+
*/
|
|
27
|
+
public instantiate(
|
|
28
|
+
className: string,
|
|
29
|
+
identifier: string,
|
|
30
|
+
fieldDefinitions: Map<string, PrimitiveValue>,
|
|
31
|
+
methodDefinitions: Map<string, RuntimeFunction>,
|
|
32
|
+
): RuntimeObject {
|
|
33
|
+
return {
|
|
34
|
+
type: "Object",
|
|
35
|
+
className,
|
|
36
|
+
identifier,
|
|
37
|
+
fields: new Map(fieldDefinitions),
|
|
38
|
+
methods: methodDefinitions,
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Handles Method Calls (Message Passing).
|
|
44
|
+
* Reuses FunctionRuntime to execute the method body.
|
|
45
|
+
*/
|
|
46
|
+
public dispatch(
|
|
47
|
+
receiver: PrimitiveValue,
|
|
48
|
+
methodName: string,
|
|
49
|
+
args: PrimitiveValue[],
|
|
50
|
+
env: EnvStack,
|
|
51
|
+
k: Continuation<PrimitiveValue>,
|
|
52
|
+
): Thunk<PrimitiveValue> {
|
|
53
|
+
if (!isRuntimeObject(receiver))
|
|
54
|
+
throw new Error(`${receiver} is not an object`);
|
|
55
|
+
|
|
56
|
+
const chain = this.getResolutionChain(receiver, env);
|
|
57
|
+
const match = this.findMethodInChain(chain, methodName);
|
|
58
|
+
|
|
59
|
+
if (!match)
|
|
60
|
+
throw new InterpreterError(
|
|
61
|
+
"MethodDispatch",
|
|
62
|
+
`${receiver.className} does not understand '${methodName}'.`,
|
|
63
|
+
);
|
|
64
|
+
|
|
65
|
+
const objectScope = this.createDispatchScope(receiver, match, methodName);
|
|
66
|
+
this.context.pushEnv(objectScope);
|
|
67
|
+
return this.context.funcRuntime.apply(match.method, args, k);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Handles calls to super() or super.method()
|
|
72
|
+
*/
|
|
73
|
+
public dispatchSuper(
|
|
74
|
+
currentEnv: EnvStack,
|
|
75
|
+
methodName: string,
|
|
76
|
+
args: PrimitiveValue[],
|
|
77
|
+
k: Continuation<PrimitiveValue>,
|
|
78
|
+
): Thunk<PrimitiveValue> {
|
|
79
|
+
const self = this.context.lookup("self") as RuntimeObject;
|
|
80
|
+
const currentHolder = this.context.lookup("__CONTEXT_CLASS__") as OOPEntity;
|
|
81
|
+
const currentMethodName = this.context.lookup("__METHOD_NAME__");
|
|
82
|
+
const targetMethodName = methodName || currentMethodName;
|
|
83
|
+
|
|
84
|
+
if (!self || !currentHolder)
|
|
85
|
+
throw new InterpreterError(
|
|
86
|
+
"SuperError",
|
|
87
|
+
"'super' used outside of a method context",
|
|
88
|
+
);
|
|
89
|
+
|
|
90
|
+
const chain = this.getResolutionChain(self, currentEnv);
|
|
91
|
+
|
|
92
|
+
const currentIndex = chain.findIndex((c) => c === currentHolder);
|
|
93
|
+
|
|
94
|
+
if (currentIndex === -1)
|
|
95
|
+
throw new Error("Fatal: Execution context not found in hierarchy chain");
|
|
96
|
+
|
|
97
|
+
const remainingChain = chain.slice(currentIndex + 1);
|
|
98
|
+
const match = this.findMethodInChain(remainingChain, methodName);
|
|
99
|
+
|
|
100
|
+
if (!match)
|
|
101
|
+
throw new InterpreterError(
|
|
102
|
+
"Super",
|
|
103
|
+
`Super method '${methodName}' not found`,
|
|
104
|
+
);
|
|
105
|
+
|
|
106
|
+
const objectScope = this.createDispatchScope(self, match, targetMethodName);
|
|
107
|
+
|
|
108
|
+
this.context.pushEnv(objectScope);
|
|
109
|
+
return this.context.funcRuntime.apply(match.method, args, k);
|
|
110
|
+
}
|
|
111
|
+
private createDispatchScope(
|
|
112
|
+
self: RuntimeObject,
|
|
113
|
+
match: OOPMatch,
|
|
114
|
+
targetName: PrimitiveValue,
|
|
115
|
+
) {
|
|
116
|
+
const objectScope = new Map<string, PrimitiveValue>();
|
|
117
|
+
objectScope.set("self", self);
|
|
118
|
+
objectScope.set("__CONTEXT_CLASS__", match.holder);
|
|
119
|
+
objectScope.set("__METHOD_NAME__", targetName);
|
|
120
|
+
|
|
121
|
+
for (const [key, val] of self.fields) objectScope.set(key, val);
|
|
122
|
+
return objectScope;
|
|
123
|
+
}
|
|
124
|
+
private getResolutionChain(
|
|
125
|
+
receiver: RuntimeObject,
|
|
126
|
+
env: EnvStack,
|
|
127
|
+
): Array<RuntimeObject | RuntimeClass> {
|
|
128
|
+
const chain: Array<RuntimeObject | RuntimeClass> = [];
|
|
129
|
+
|
|
130
|
+
chain.push(receiver);
|
|
131
|
+
|
|
132
|
+
if (receiver.className) {
|
|
133
|
+
this.expandClassHierarchy(receiver.className, env, chain);
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
return chain;
|
|
137
|
+
}
|
|
138
|
+
private expandClassHierarchy(
|
|
139
|
+
className: string,
|
|
140
|
+
env: EnvStack,
|
|
141
|
+
chain: Array<RuntimeObject | RuntimeClass>,
|
|
142
|
+
) {
|
|
143
|
+
const classDef = this.context.lookup(className);
|
|
144
|
+
if (!isRuntimeClass(classDef))
|
|
145
|
+
throw new InterpreterError(
|
|
146
|
+
"expandClassHierarchy",
|
|
147
|
+
"classDef was expected to be a RuntimeClass",
|
|
148
|
+
);
|
|
149
|
+
|
|
150
|
+
chain.push(classDef);
|
|
151
|
+
|
|
152
|
+
const classDefCopy = [...classDef.mixins];
|
|
153
|
+
|
|
154
|
+
if (classDef.mixins)
|
|
155
|
+
classDefCopy.reverse().forEach((mixinName) => {
|
|
156
|
+
this.expandClassHierarchy(mixinName, env, chain);
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
if (classDef.superclass)
|
|
160
|
+
this.expandClassHierarchy(classDef.superclass, env, chain);
|
|
161
|
+
}
|
|
162
|
+
private findMethodInChain(
|
|
163
|
+
chain: Array<RuntimeObject | RuntimeClass>,
|
|
164
|
+
methodName: string,
|
|
165
|
+
): OOPMatch | undefined {
|
|
166
|
+
for (const link of chain) {
|
|
167
|
+
if (link.methods.has(methodName))
|
|
168
|
+
return {
|
|
169
|
+
method: link.methods.get(methodName)!,
|
|
170
|
+
holder: link,
|
|
171
|
+
};
|
|
172
|
+
}
|
|
173
|
+
return undefined;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
/**
|
|
177
|
+
* Field Access (Get)
|
|
178
|
+
* e.g. self.myField
|
|
179
|
+
*/
|
|
180
|
+
public getField(receiver: PrimitiveValue, fieldName: string): PrimitiveValue {
|
|
181
|
+
if (!isRuntimeObject(receiver))
|
|
182
|
+
throw new InterpreterError("FieldAccess", "Target is not an object");
|
|
183
|
+
|
|
184
|
+
if (!receiver.fields.has(fieldName)) {
|
|
185
|
+
if (receiver.methods.has(fieldName))
|
|
186
|
+
return receiver.methods.get(fieldName);
|
|
187
|
+
|
|
188
|
+
throw new InterpreterError(
|
|
189
|
+
"FieldAccess",
|
|
190
|
+
`Field '${fieldName}' not found in ${receiver.className}`,
|
|
191
|
+
);
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
return receiver.fields.get(fieldName);
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
/**
|
|
198
|
+
* Field Mutation (Set)
|
|
199
|
+
* e.g. self.myField = 10
|
|
200
|
+
*/
|
|
201
|
+
public setField(
|
|
202
|
+
receiver: PrimitiveValue,
|
|
203
|
+
fieldName: string,
|
|
204
|
+
value: PrimitiveValue,
|
|
205
|
+
): PrimitiveValue {
|
|
206
|
+
if (!this.context.config.mutability)
|
|
207
|
+
throw new InterpreterError(
|
|
208
|
+
"FieldAssignment",
|
|
209
|
+
`Cannot mutate field '${fieldName}': mutability is disabled`,
|
|
210
|
+
);
|
|
211
|
+
|
|
212
|
+
if (!isRuntimeObject(receiver))
|
|
213
|
+
throw new InterpreterError("FieldAssignment", "Target is not an object");
|
|
214
|
+
|
|
215
|
+
if (!receiver.fields.has(fieldName))
|
|
216
|
+
throw new InterpreterError(
|
|
217
|
+
"FieldAssignment",
|
|
218
|
+
`Cannot set unknown field '${fieldName}'`,
|
|
219
|
+
);
|
|
220
|
+
|
|
221
|
+
receiver.fields.set(fieldName, value);
|
|
222
|
+
return value;
|
|
223
|
+
}
|
|
224
|
+
}
|
|
@@ -1,47 +1,47 @@
|
|
|
1
|
-
import { SourceLocation } from "yukigo-ast";
|
|
2
|
-
|
|
3
|
-
export interface ErrorFrame {
|
|
4
|
-
nodeType: string;
|
|
5
|
-
loc?: SourceLocation;
|
|
6
|
-
}
|
|
7
|
-
|
|
8
|
-
export class InterpreterError extends Error {
|
|
9
|
-
context: string;
|
|
10
|
-
frames: ErrorFrame[];
|
|
11
|
-
|
|
12
|
-
constructor(context: string, message: string, frames: ErrorFrame[] = []) {
|
|
13
|
-
super(`[${context}] ${message}`);
|
|
14
|
-
this.context = context;
|
|
15
|
-
this.frames = frames;
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
pushFrame(frame: ErrorFrame) {
|
|
19
|
-
this.frames.push(frame);
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
formatStack(): string {
|
|
23
|
-
if (!this.frames.length) return "";
|
|
24
|
-
const formatted = this.frames
|
|
25
|
-
.map((f) => {
|
|
26
|
-
const loc = f.loc ? ` (line ${f.loc.line}, col ${f.loc.column})` : "";
|
|
27
|
-
return ` • ${f.nodeType}${loc}`;
|
|
28
|
-
})
|
|
29
|
-
.join("\n");
|
|
30
|
-
return `\nTrace:\n${formatted}`;
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
override toString(): string {
|
|
34
|
-
return `${this.message}${this.formatStack()}`;
|
|
35
|
-
}
|
|
36
|
-
}
|
|
37
|
-
export class UnexpectedValue extends InterpreterError {
|
|
38
|
-
constructor(ctx: string, expected: string, got: string) {
|
|
39
|
-
super(ctx, `Expected ${expected} but got ${got}`);
|
|
40
|
-
}
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
export class UnboundVariable extends Error {
|
|
44
|
-
constructor(name: string) {
|
|
45
|
-
super(`Unbound variable: ${name}`);
|
|
46
|
-
}
|
|
47
|
-
}
|
|
1
|
+
import { SourceLocation } from "yukigo-ast";
|
|
2
|
+
|
|
3
|
+
export interface ErrorFrame {
|
|
4
|
+
nodeType: string;
|
|
5
|
+
loc?: SourceLocation;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export class InterpreterError extends Error {
|
|
9
|
+
context: string;
|
|
10
|
+
frames: ErrorFrame[];
|
|
11
|
+
|
|
12
|
+
constructor(context: string, message: string, frames: ErrorFrame[] = []) {
|
|
13
|
+
super(`[${context}] ${message}`);
|
|
14
|
+
this.context = context;
|
|
15
|
+
this.frames = frames;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
pushFrame(frame: ErrorFrame) {
|
|
19
|
+
this.frames.push(frame);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
formatStack(): string {
|
|
23
|
+
if (!this.frames.length) return "";
|
|
24
|
+
const formatted = this.frames
|
|
25
|
+
.map((f) => {
|
|
26
|
+
const loc = f.loc ? ` (line ${f.loc.line}, col ${f.loc.column})` : "";
|
|
27
|
+
return ` • ${f.nodeType}${loc}`;
|
|
28
|
+
})
|
|
29
|
+
.join("\n");
|
|
30
|
+
return `\nTrace:\n${formatted}`;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
override toString(): string {
|
|
34
|
+
return `${this.message}${this.formatStack()}`;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
export class UnexpectedValue extends InterpreterError {
|
|
38
|
+
constructor(ctx: string, expected: string, got: string) {
|
|
39
|
+
super(ctx, `Expected ${expected} but got ${got}`);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export class UnboundVariable extends Error {
|
|
44
|
+
constructor(name: string) {
|
|
45
|
+
super(`Unbound variable: ${name}`);
|
|
46
|
+
}
|
|
47
|
+
}
|
package/src/interpreter/index.ts
CHANGED
|
@@ -1,59 +1,52 @@
|
|
|
1
|
-
import { PrimitiveValue,
|
|
2
|
-
import { InterpreterVisitor } from "./components/Visitor.js";
|
|
3
|
-
import { EnvBuilderVisitor } from "./components/EnvBuilder.js";
|
|
4
|
-
import { InterpreterError } from "./errors.js";
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
};
|
|
14
|
-
|
|
15
|
-
export type
|
|
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
|
-
return evaluatedExpr;
|
|
54
|
-
} catch (error) {
|
|
55
|
-
if (error instanceof InterpreterError) console.log(error.formatStack());
|
|
56
|
-
throw error;
|
|
57
|
-
}
|
|
58
|
-
}
|
|
59
|
-
}
|
|
1
|
+
import { PrimitiveValue, AST, EnvStack, ASTNode } from "yukigo-ast";
|
|
2
|
+
import { InterpreterVisitor } from "./components/Visitor.js";
|
|
3
|
+
import { EnvBuilderVisitor } from "./components/EnvBuilder.js";
|
|
4
|
+
import { InterpreterError } from "./errors.js";
|
|
5
|
+
import { createGlobalEnv } from "./utils.js";
|
|
6
|
+
import { idContinuation, trampoline } from "./trampoline.js";
|
|
7
|
+
import {
|
|
8
|
+
InterpreterConfig,
|
|
9
|
+
RuntimeContext,
|
|
10
|
+
} from "./components/RuntimeContext.js";
|
|
11
|
+
import { LazyRuntime } from "./components/runtimes/LazyRuntime.js";
|
|
12
|
+
import { FunctionRuntime } from "./components/runtimes/FunctionRuntime.js";
|
|
13
|
+
import { ObjectRuntime } from "./components/runtimes/ObjectRuntime.js";
|
|
14
|
+
|
|
15
|
+
export type Bindings = [string, PrimitiveValue][];
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* The Interpreter class is responsible for evaluating the Abstract Syntax Tree (AST)
|
|
19
|
+
* generated by the parsers.
|
|
20
|
+
*
|
|
21
|
+
* It manages the global execution environment and delegates the actual evaluation
|
|
22
|
+
* of Expression nodes to a dedicated visitor.
|
|
23
|
+
*/
|
|
24
|
+
export class Interpreter {
|
|
25
|
+
private context: RuntimeContext;
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* @param ast The Abstract Syntax Tree (AST) of the program to be interpreted.
|
|
29
|
+
*/
|
|
30
|
+
constructor(ast: AST, config: InterpreterConfig = {}) {
|
|
31
|
+
this.context = new RuntimeContext(config);
|
|
32
|
+
const builder = new EnvBuilderVisitor(this.context);
|
|
33
|
+
builder.build(ast);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Evaluates a single Expression node within the context of the global environment.
|
|
38
|
+
*
|
|
39
|
+
* @param expr The root Expression node to be evaluated.
|
|
40
|
+
* @returns The resulting primitive value (number, string, boolean, etc.) after evaluation.
|
|
41
|
+
*/
|
|
42
|
+
public evaluate(expr: ASTNode): PrimitiveValue {
|
|
43
|
+
try {
|
|
44
|
+
const visitor = new InterpreterVisitor(this.context);
|
|
45
|
+
const evaluatedCPS = expr.accept(visitor);
|
|
46
|
+
return trampoline(evaluatedCPS(idContinuation));
|
|
47
|
+
} catch (error) {
|
|
48
|
+
if (error instanceof InterpreterError) console.log(error.formatStack());
|
|
49
|
+
throw error;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import { PrimitiveValue } from "yukigo-ast";
|
|
2
|
+
|
|
3
|
+
// A Thunk now represents a deferred computation that, when called,
|
|
4
|
+
// will produce either a value or another thunk.
|
|
5
|
+
// Importantly, for our visitor, the Thunk will wrap the *decision* of what to do next.
|
|
6
|
+
// A node's evaluation might return a function that *takes* the continuation for that node.
|
|
7
|
+
export type CallableThunk<T> = () => Thunk<T>;
|
|
8
|
+
export type Thunk<T> = T | CallableThunk<T>;
|
|
9
|
+
|
|
10
|
+
const isCallableThunk = <T>(thunk: Thunk<T>): thunk is CallableThunk<T> =>
|
|
11
|
+
typeof thunk === "function";
|
|
12
|
+
|
|
13
|
+
// A Continuation is a function that takes a value and returns a Thunk,
|
|
14
|
+
// representing the "rest of the computation" after the value is produced.
|
|
15
|
+
export type Continuation<T, R = any> = (value: T) => Thunk<R>;
|
|
16
|
+
|
|
17
|
+
// The identity continuation: simply returns the value as a resolved Thunk.
|
|
18
|
+
// This is used for the very last step of a computation or when a value is final.
|
|
19
|
+
export const idContinuation: Continuation<PrimitiveValue> = (value) => value;
|
|
20
|
+
|
|
21
|
+
// The trampoline function executes a chain of Thunks until a final non-function value is reached.
|
|
22
|
+
export function trampoline<T>(thunk: Thunk<T>): T {
|
|
23
|
+
let result = thunk;
|
|
24
|
+
while (isCallableThunk(result)) result = result();
|
|
25
|
+
return result;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* A specialized Thunk type for our CPS visitor.
|
|
30
|
+
* This is a function that, when called, takes the continuation for the current node's evaluation
|
|
31
|
+
* and returns the actual Thunk chain for that node.
|
|
32
|
+
* This effectively injects the continuation *after* the `visit` method has returned.
|
|
33
|
+
*/
|
|
34
|
+
export type CPSThunk<T, R = any> = (k: Continuation<T, R>) => Thunk<R>;
|
|
35
|
+
|
|
36
|
+
// Helper to create a CPSThunk for visitor methods.
|
|
37
|
+
// Visitor methods will call this, providing a function that knows how to use 'k'.
|
|
38
|
+
export function makeCPSThunk<T>(
|
|
39
|
+
fn: (k: Continuation<T>) => Thunk<T>,
|
|
40
|
+
): CPSThunk<T> {
|
|
41
|
+
return fn;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// Helper to compose asynchronous/CPS operations.
|
|
45
|
+
// `cpsA` is a CPSThunk that eventually produces a value A.
|
|
46
|
+
// `f` is a function that takes A and returns a CPSThunk that eventually produces B.
|
|
47
|
+
export function bindCPS<A, B, R>(
|
|
48
|
+
cpsA: CPSThunk<A, R>,
|
|
49
|
+
f: (valueA: A) => CPSThunk<B, R>,
|
|
50
|
+
): CPSThunk<B, R> {
|
|
51
|
+
return (k: Continuation<B, R>): Thunk<R> => {
|
|
52
|
+
return () =>
|
|
53
|
+
cpsA((valueA: A): Thunk<R> => {
|
|
54
|
+
// f(valueA) returns a CPSThunk<B, R>
|
|
55
|
+
// calling that with 'k' returns Thunk<R>
|
|
56
|
+
return f(valueA)(k);
|
|
57
|
+
});
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// Helper to lift a direct value into a CPSThunk
|
|
62
|
+
export function valueToCPS<T, R>(value: T): CPSThunk<T, R> {
|
|
63
|
+
return (k: Continuation<T, R>) => () => k(value);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// Helper to lift a regular Thunk into a CPSThunk (if it resolves a simple Thunk)
|
|
67
|
+
export function thunkToCPS<T>(thunk: Thunk<T>): CPSThunk<T> {
|
|
68
|
+
return (k: Continuation<T>) => {
|
|
69
|
+
return () => k(trampoline(thunk)); // Trampoline the simple thunk and pass its result to k
|
|
70
|
+
};
|
|
71
|
+
}
|
package/src/interpreter/utils.ts
CHANGED
|
@@ -1,79 +1,84 @@
|
|
|
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
|
-
|
|
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
|
-
return env
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
export function
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
export function
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
):
|
|
78
|
-
|
|
79
|
-
|
|
1
|
+
import {
|
|
2
|
+
Expression,
|
|
3
|
+
LazyList,
|
|
4
|
+
PrimitiveValue,
|
|
5
|
+
Environment,
|
|
6
|
+
EnvStack,
|
|
7
|
+
ASTNode,
|
|
8
|
+
isRuntimeFunction,
|
|
9
|
+
isRuntimeObject,
|
|
10
|
+
isLazyList,
|
|
11
|
+
isRuntimeClass,
|
|
12
|
+
isRuntimePredicate,
|
|
13
|
+
} from "yukigo-ast";
|
|
14
|
+
import { UnboundVariable } from "./errors.js";
|
|
15
|
+
import { Continuation, Thunk } from "./trampoline.js";
|
|
16
|
+
|
|
17
|
+
export interface ExpressionEvaluator {
|
|
18
|
+
evaluate<R = PrimitiveValue>(node: ASTNode, cont: Continuation<PrimitiveValue, R>): Thunk<R>;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export function createStream(
|
|
22
|
+
generator: () => Generator<PrimitiveValue, void, unknown>,
|
|
23
|
+
): LazyList {
|
|
24
|
+
return {
|
|
25
|
+
type: "LazyList",
|
|
26
|
+
generator,
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export function isArrayOfNumbers(arr: PrimitiveValue[]): arr is number[] {
|
|
31
|
+
for (const item of arr) if (typeof item !== "number") return false;
|
|
32
|
+
return true;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export function generateRange(
|
|
36
|
+
start: number,
|
|
37
|
+
end: number,
|
|
38
|
+
step: number,
|
|
39
|
+
): number[] {
|
|
40
|
+
if (step === 0) throw new Error("Step cannot be zero in range expression");
|
|
41
|
+
|
|
42
|
+
const result: number[] = [];
|
|
43
|
+
let current = start;
|
|
44
|
+
|
|
45
|
+
if (step > 0) {
|
|
46
|
+
while (current <= end) {
|
|
47
|
+
result.push(current);
|
|
48
|
+
current += step;
|
|
49
|
+
}
|
|
50
|
+
} else {
|
|
51
|
+
while (current >= end) {
|
|
52
|
+
result.push(current);
|
|
53
|
+
current += step;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
return result;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export function createEnv(bindings: [string, PrimitiveValue][]): Environment {
|
|
61
|
+
const env = new Map();
|
|
62
|
+
for (const [name, value] of bindings) env.set(name, value);
|
|
63
|
+
return env;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export function createGlobalEnv(): EnvStack {
|
|
67
|
+
return {
|
|
68
|
+
head: new Map<string, PrimitiveValue>(),
|
|
69
|
+
tail: null,
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
export function getYukigoType(val: PrimitiveValue): string {
|
|
74
|
+
if (val === null || val === undefined) return "YuNil";
|
|
75
|
+
if (typeof val === "number") return "YuNumber";
|
|
76
|
+
if (typeof val === "boolean") return "YuBoolean";
|
|
77
|
+
if (typeof val === "string") return val.length === 1 ? "YuChar" : "YuString";
|
|
78
|
+
if (Array.isArray(val) || isLazyList(val)) return "YuList";
|
|
79
|
+
if (isRuntimeFunction(val)) return "YuFunction";
|
|
80
|
+
if (isRuntimeObject(val)) return "YuObject";
|
|
81
|
+
if (isRuntimeClass(val)) return "YuClass";
|
|
82
|
+
if (isRuntimePredicate(val)) return "YuPredicate";
|
|
83
|
+
return "YuUnknown";
|
|
84
|
+
}
|