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,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
+ }
@@ -1,59 +1,52 @@
1
- import { PrimitiveValue, Expression, AST } from "yukigo-ast";
2
- import { InterpreterVisitor } from "./components/Visitor.js";
3
- import { EnvBuilderVisitor } from "./components/EnvBuilder.js";
4
- import { InterpreterError } from "./errors.js";
5
-
6
- export type Bindings = [string, PrimitiveValue][];
7
-
8
- export type LogicSearchMode = "first" | "all" | "stream";
9
- export type InterpreterConfig = {
10
- lazyLoading?: boolean;
11
- debug?: boolean;
12
- outputMode?: LogicSearchMode;
13
- };
14
-
15
- export type Environment = Map<string, PrimitiveValue>;
16
- export type EnvStack = Environment[];
17
-
18
- const DefaultConfiguration: Required<InterpreterConfig> = {
19
- lazyLoading: false,
20
- debug: false,
21
- outputMode: "first",
22
- };
23
-
24
- /**
25
- * The Interpreter class is responsible for evaluating the Abstract Syntax Tree (AST)
26
- * generated by the parsers.
27
- *
28
- * It manages the global execution environment and delegates the actual evaluation
29
- * of Expression nodes to a dedicated visitor.
30
- */
31
- export class Interpreter {
32
- private globalEnv: EnvStack;
33
- private readonly config: InterpreterConfig;
34
-
35
- /**
36
- * @param ast The Abstract Syntax Tree (AST) of the program to be interpreted.
37
- */
38
- constructor(ast: AST, config?: InterpreterConfig) {
39
- this.globalEnv = new EnvBuilderVisitor().build(ast);
40
- this.config = config ?? DefaultConfiguration;
41
- }
42
-
43
- /**
44
- * Evaluates a single Expression node within the context of the global environment.
45
- *
46
- * @param expr The root Expression node to be evaluated.
47
- * @returns The resulting primitive value (number, string, boolean, etc.) after evaluation.
48
- */
49
- public evaluate(expr: Expression): PrimitiveValue {
50
- try {
51
- const visitor = new InterpreterVisitor(this.globalEnv, this.config);
52
- const evaluatedExpr = expr.accept(visitor);
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
+ }
@@ -1,79 +1,84 @@
1
- import { Expression, LazyList, PrimitiveValue } from "yukigo-ast";
2
- import { Environment, EnvStack } from "./index.js";
3
- import { UnboundVariable } from "./errors.js";
4
-
5
- export interface ExpressionEvaluator {
6
- evaluate(node: Expression): PrimitiveValue;
7
- }
8
-
9
- export function createStream(
10
- generator: () => Generator<PrimitiveValue, void, unknown>
11
- ): LazyList {
12
- return {
13
- type: "LazyList",
14
- generator,
15
- };
16
- }
17
-
18
- export function isArrayOfNumbers(arr: PrimitiveValue[]): arr is number[] {
19
- for (const item of arr) if (typeof item !== "number") return false;
20
- return true;
21
- }
22
-
23
- export function generateRange(
24
- start: number,
25
- end: number,
26
- step: number
27
- ): number[] {
28
- if (step === 0) throw new Error("Step cannot be zero in range expression");
29
-
30
- const result: number[] = [];
31
- let current = start;
32
-
33
- if (step > 0) {
34
- while (current <= end) {
35
- result.push(current);
36
- current += step;
37
- }
38
- } else {
39
- while (current >= end) {
40
- result.push(current);
41
- current += step;
42
- }
43
- }
44
-
45
- return result;
46
- }
47
-
48
- export function createEnv(bindings: [string, PrimitiveValue][]): Environment {
49
- const env = new Map();
50
- for (const [name, value] of bindings) env.set(name, value);
51
- return env;
52
- }
53
-
54
- export function createGlobalEnv(): EnvStack {
55
- return [new Map<string, PrimitiveValue>()];
56
- }
57
-
58
- export function pushEnv(env: EnvStack, frame?: Environment): EnvStack {
59
- return [frame ?? new Map(), ...env];
60
- }
61
-
62
- export function popEnv(env: EnvStack): EnvStack {
63
- return env.slice(1);
64
- }
65
-
66
- export function lookup(env: EnvStack, name: string): PrimitiveValue {
67
- for (const frame of env) {
68
- if (frame.has(name)) return frame.get(name);
69
- }
70
- throw new UnboundVariable(name);
71
- }
72
-
73
- export function define(
74
- env: EnvStack,
75
- name: string,
76
- value: PrimitiveValue
77
- ): void {
78
- env[0].set(name, value);
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
+ }