yukigo 0.1.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 (78) hide show
  1. package/.mocharc.json +4 -0
  2. package/CHANGELOG.md +6 -0
  3. package/README.md +199 -0
  4. package/dist/analyzer/index.d.ts +71 -0
  5. package/dist/analyzer/index.js +110 -0
  6. package/dist/analyzer/inspections/functional.d.ts +46 -0
  7. package/dist/analyzer/inspections/functional.js +123 -0
  8. package/dist/analyzer/inspections/generic.d.ts +151 -0
  9. package/dist/analyzer/inspections/generic.js +427 -0
  10. package/dist/analyzer/inspections/imperative.d.ts +37 -0
  11. package/dist/analyzer/inspections/imperative.js +105 -0
  12. package/dist/analyzer/inspections/logic.d.ts +49 -0
  13. package/dist/analyzer/inspections/logic.js +140 -0
  14. package/dist/analyzer/inspections/object.d.ts +83 -0
  15. package/dist/analyzer/inspections/object.js +235 -0
  16. package/dist/analyzer/utils.d.ts +4 -0
  17. package/dist/analyzer/utils.js +16 -0
  18. package/dist/index.d.ts +3 -0
  19. package/dist/index.js +3 -0
  20. package/dist/interpreter/components/EnvBuilder.d.ts +16 -0
  21. package/dist/interpreter/components/EnvBuilder.js +78 -0
  22. package/dist/interpreter/components/FunctionRuntime.d.ts +8 -0
  23. package/dist/interpreter/components/FunctionRuntime.js +52 -0
  24. package/dist/interpreter/components/LazyRuntime.d.ts +7 -0
  25. package/dist/interpreter/components/LazyRuntime.js +75 -0
  26. package/dist/interpreter/components/LogicEngine.d.ts +21 -0
  27. package/dist/interpreter/components/LogicEngine.js +152 -0
  28. package/dist/interpreter/components/LogicResolver.d.ts +11 -0
  29. package/dist/interpreter/components/LogicResolver.js +87 -0
  30. package/dist/interpreter/components/Operations.d.ts +14 -0
  31. package/dist/interpreter/components/Operations.js +69 -0
  32. package/dist/interpreter/components/PatternMatcher.d.ts +41 -0
  33. package/dist/interpreter/components/PatternMatcher.js +206 -0
  34. package/dist/interpreter/components/Visitor.d.ts +64 -0
  35. package/dist/interpreter/components/Visitor.js +299 -0
  36. package/dist/interpreter/errors.d.ts +19 -0
  37. package/dist/interpreter/errors.js +36 -0
  38. package/dist/interpreter/index.d.ts +32 -0
  39. package/dist/interpreter/index.js +44 -0
  40. package/dist/interpreter/utils.d.ts +14 -0
  41. package/dist/interpreter/utils.js +57 -0
  42. package/dist/utils/helpers.d.ts +14 -0
  43. package/dist/utils/helpers.js +51 -0
  44. package/package.json +30 -0
  45. package/src/analyzer/index.ts +132 -0
  46. package/src/analyzer/inspections/functional.ts +159 -0
  47. package/src/analyzer/inspections/generic.ts +499 -0
  48. package/src/analyzer/inspections/imperative.ts +129 -0
  49. package/src/analyzer/inspections/logic.ts +166 -0
  50. package/src/analyzer/inspections/object.ts +282 -0
  51. package/src/analyzer/utils.ts +26 -0
  52. package/src/index.ts +3 -0
  53. package/src/interpreter/components/EnvBuilder.ts +97 -0
  54. package/src/interpreter/components/FunctionRuntime.ts +79 -0
  55. package/src/interpreter/components/LazyRuntime.ts +97 -0
  56. package/src/interpreter/components/LogicEngine.ts +227 -0
  57. package/src/interpreter/components/LogicResolver.ts +130 -0
  58. package/src/interpreter/components/Operations.ts +81 -0
  59. package/src/interpreter/components/PatternMatcher.ts +254 -0
  60. package/src/interpreter/components/Visitor.ts +493 -0
  61. package/src/interpreter/errors.ts +47 -0
  62. package/src/interpreter/index.ts +59 -0
  63. package/src/interpreter/utils.ts +79 -0
  64. package/src/utils/helpers.ts +73 -0
  65. package/tests/analyzer/functional.spec.ts +221 -0
  66. package/tests/analyzer/generic.spec.ts +100 -0
  67. package/tests/analyzer/helpers.spec.ts +83 -0
  68. package/tests/analyzer/logic.spec.ts +292 -0
  69. package/tests/analyzer/oop.spec.ts +338 -0
  70. package/tests/interpreter/EnvBuilder.spec.ts +178 -0
  71. package/tests/interpreter/FunctionRuntime.spec.ts +234 -0
  72. package/tests/interpreter/LazyRuntime.spec.ts +190 -0
  73. package/tests/interpreter/LogicEngine.spec.ts +194 -0
  74. package/tests/interpreter/Operations.spec.ts +220 -0
  75. package/tests/interpreter/PatternSystem.spec.ts +189 -0
  76. package/tests/interpreter/interpreter.spec.ts +937 -0
  77. package/tsconfig.build.json +7 -0
  78. package/tsconfig.json +17 -0
@@ -0,0 +1,493 @@
1
+ import {
2
+ Visitor,
3
+ PrimitiveValue,
4
+ NumberPrimitive,
5
+ BooleanPrimitive,
6
+ StringPrimitive,
7
+ ListPrimitive,
8
+ NilPrimitive,
9
+ SymbolPrimitive,
10
+ Variable,
11
+ CharPrimitive,
12
+ ArithmeticUnaryOperation,
13
+ ArithmeticBinaryOperation,
14
+ ListUnaryOperation,
15
+ ListBinaryOperation,
16
+ ComparisonOperation,
17
+ LogicalBinaryOperation,
18
+ LogicalUnaryOperation,
19
+ BitwiseBinaryOperation,
20
+ BitwiseUnaryOperation,
21
+ StringOperation,
22
+ UnifyOperation,
23
+ AssignOperation,
24
+ TupleExpression,
25
+ FieldExpression,
26
+ DataExpression,
27
+ ConsExpression,
28
+ LetInExpression,
29
+ Call,
30
+ Otherwise,
31
+ CompositionExpression,
32
+ VariablePattern,
33
+ Expression,
34
+ Application,
35
+ Lambda,
36
+ EquationRuntime,
37
+ UnguardedBody,
38
+ Sequence,
39
+ Return,
40
+ Exist,
41
+ Not,
42
+ Findall,
43
+ Forall,
44
+ Goal,
45
+ Send,
46
+ New,
47
+ Implement,
48
+ Include,
49
+ Self,
50
+ ListComprehension,
51
+ RangeExpression,
52
+ RuntimeFunction,
53
+ Generator as YuGenerator,
54
+ BinaryOperation,
55
+ UnaryOperation,
56
+ ASTNode,
57
+ Raise,
58
+ Query,
59
+ } from "yukigo-ast";
60
+ import { EnvStack, InterpreterConfig } from "../index.js";
61
+ import {
62
+ ArithmeticBinaryTable,
63
+ ArithmeticUnaryTable,
64
+ BitwiseBinaryTable,
65
+ BitwiseUnaryTable,
66
+ ComparisonOperationTable,
67
+ ListBinaryTable,
68
+ ListUnaryTable,
69
+ LogicalBinaryTable,
70
+ LogicalUnaryTable,
71
+ StringOperationTable,
72
+ } from "./Operations.js";
73
+ import { define, ExpressionEvaluator, lookup } from "../utils.js";
74
+ import { LogicEngine } from "./LogicEngine.js";
75
+ import { ErrorFrame, InterpreterError, UnexpectedValue } from "../errors.js";
76
+ import { LazyRuntime } from "./LazyRuntime.js";
77
+ import { FunctionRuntime } from "./FunctionRuntime.js";
78
+
79
+ export class InterpreterVisitor
80
+ implements Visitor<PrimitiveValue>, ExpressionEvaluator
81
+ {
82
+ private frames: ErrorFrame[];
83
+ private env: EnvStack;
84
+ private readonly config: InterpreterConfig;
85
+
86
+ constructor(
87
+ env: EnvStack,
88
+ config: InterpreterConfig,
89
+ frames: ErrorFrame[] = []
90
+ ) {
91
+ this.frames = frames;
92
+ this.env = env;
93
+ this.config = config;
94
+ }
95
+
96
+ evaluate(node: Expression): PrimitiveValue {
97
+ return node.accept(this);
98
+ }
99
+
100
+ private getLogicEngine(): LogicEngine {
101
+ return new LogicEngine(this.env, this.config, this);
102
+ }
103
+
104
+ visitNumberPrimitive(node: NumberPrimitive): PrimitiveValue {
105
+ return node.value;
106
+ }
107
+ visitBooleanPrimitive(node: BooleanPrimitive): PrimitiveValue {
108
+ return node.value;
109
+ }
110
+ visitStringPrimitive(node: StringPrimitive): PrimitiveValue {
111
+ return node.value;
112
+ }
113
+ visitListPrimitive(node: ListPrimitive): PrimitiveValue {
114
+ return node.elements.map((elem) => elem.accept(this));
115
+ }
116
+ visitNilPrimitive(node: NilPrimitive): PrimitiveValue {
117
+ return node.value;
118
+ }
119
+ visitCharPrimitive(node: CharPrimitive): PrimitiveValue {
120
+ return node.value;
121
+ }
122
+ visitSymbolPrimitive(node: SymbolPrimitive): PrimitiveValue {
123
+ try {
124
+ return lookup(this.env, node.value);
125
+ } catch (error) {
126
+ throw new InterpreterError("Symbol Lookup", error.message, this.frames);
127
+ }
128
+ }
129
+ visitVariable(node: Variable): PrimitiveValue {
130
+ const name = node.identifier.value;
131
+ const value = node.expression.accept(this);
132
+ this.env.at(-1).set(name, value);
133
+ return true;
134
+ }
135
+ visitArithmeticUnaryOperation(
136
+ node: ArithmeticUnaryOperation
137
+ ): PrimitiveValue {
138
+ return this.processUnary(
139
+ node,
140
+ ArithmeticUnaryTable,
141
+ (a: number) => !Number.isNaN(a),
142
+ "ArithmeticUnaryOperation"
143
+ );
144
+ }
145
+ visitArithmeticBinaryOperation(
146
+ node: ArithmeticBinaryOperation
147
+ ): PrimitiveValue {
148
+ return this.processBinary(
149
+ node,
150
+ ArithmeticBinaryTable,
151
+ (a, b) => typeof a === "number" && typeof b === "number",
152
+ "ArithmeticBinaryOperation"
153
+ );
154
+ }
155
+ visitListUnaryOperation(node: ListUnaryOperation): PrimitiveValue {
156
+ const operand = node.operand.accept(this);
157
+ if (!Array.isArray(operand))
158
+ throw new UnexpectedValue("ListUnaryOperation", "Array", typeof operand);
159
+
160
+ const arr = this.realizeList(operand);
161
+ const fn = ListUnaryTable[node.operator];
162
+ if (!fn)
163
+ throw new InterpreterError(
164
+ "ListUnaryOperation",
165
+ `Unknown operator: ${node.operator}`
166
+ );
167
+ return fn(arr);
168
+ }
169
+ visitListBinaryOperation(node: ListBinaryOperation): PrimitiveValue {
170
+ return this.processBinary(
171
+ node,
172
+ ListBinaryTable,
173
+ (a, b) => Array.isArray(a) && Array.isArray(b),
174
+ "ListBinaryOperation"
175
+ );
176
+ }
177
+ visitComparisonOperation(node: ComparisonOperation): PrimitiveValue {
178
+ return this.processBinary(
179
+ node,
180
+ ComparisonOperationTable,
181
+ () => true,
182
+ "ComparisonOperation"
183
+ );
184
+ }
185
+ visitLogicalBinaryOperation(node: LogicalBinaryOperation): PrimitiveValue {
186
+ const left = node.left.accept(this);
187
+ if (typeof left !== "boolean")
188
+ throw new InterpreterError(
189
+ "LogicalBinaryOperation",
190
+ `Expected left side to be boolean and got: ${left}`
191
+ );
192
+
193
+ const fn = LogicalBinaryTable[node.operator];
194
+ if (!fn)
195
+ throw new InterpreterError(
196
+ "LogicalBinaryOperation",
197
+ `Unknown operator '${node.operator}'`
198
+ );
199
+
200
+ const rightThunk = () => {
201
+ const right = node.right.accept(this);
202
+ if (typeof right !== "boolean")
203
+ throw new InterpreterError(
204
+ "LogicalBinaryOperation",
205
+ `Expected right side to be boolean and got: ${right}`
206
+ );
207
+ return right;
208
+ };
209
+
210
+ // short circuit if lazy loading is enabled
211
+ if (this.config.lazyLoading) {
212
+ if (node.operator === "And" && left === false) return false;
213
+ if (node.operator === "Or" && left === true) return true;
214
+ }
215
+ return fn(left, rightThunk);
216
+ }
217
+ visitLogicalUnaryOperation(node: LogicalUnaryOperation): PrimitiveValue {
218
+ return this.processUnary(
219
+ node,
220
+ LogicalUnaryTable,
221
+ (a) => typeof a === "boolean",
222
+ "LogicalUnaryOperation"
223
+ );
224
+ }
225
+ visitBitwiseBinaryOperation(node: BitwiseBinaryOperation): PrimitiveValue {
226
+ return this.processBinary(
227
+ node,
228
+ BitwiseBinaryTable,
229
+ (a, b) => !Number.isNaN(a) && !Number.isNaN(b),
230
+ "BitwiseBinaryOperation"
231
+ );
232
+ }
233
+ visitBitwiseUnaryOperation(node: BitwiseUnaryOperation): PrimitiveValue {
234
+ return this.processUnary(
235
+ node,
236
+ BitwiseUnaryTable,
237
+ (a) => !Number.isNaN(a),
238
+ "BitwiseUnaryOperation"
239
+ );
240
+ }
241
+ visitStringOperation(node: StringOperation): PrimitiveValue {
242
+ return this.processBinary(
243
+ node,
244
+ StringOperationTable,
245
+ (a, b) => typeof a === "string" || typeof b === "string",
246
+ "StringOperation"
247
+ );
248
+ }
249
+ visitUnifyOperation(node: UnifyOperation): PrimitiveValue {
250
+ throw new Error("Method not implemented.");
251
+ }
252
+ visitAssignOperation(node: AssignOperation): PrimitiveValue {
253
+ throw new Error("Method not implemented.");
254
+ }
255
+ visitTupleExpr(node: TupleExpression): PrimitiveValue {
256
+ throw new Error("Method not implemented.");
257
+ }
258
+ visitFieldExpr(node: FieldExpression): PrimitiveValue {
259
+ throw new Error("Method not implemented.");
260
+ }
261
+ visitDataExpr(node: DataExpression): PrimitiveValue {
262
+ throw new Error("Method not implemented.");
263
+ }
264
+ visitConsExpr(node: ConsExpression): PrimitiveValue {
265
+ try {
266
+ return LazyRuntime.evaluateCons(node, this, this.config.lazyLoading);
267
+ } catch (e) {
268
+ throw new InterpreterError("Cons", e.message, this.frames);
269
+ }
270
+ }
271
+ visitLetInExpr(node: LetInExpression): PrimitiveValue {
272
+ throw new Error("Method not implemented.");
273
+ }
274
+ visitCall(node: Call): PrimitiveValue {
275
+ throw new Error("Method not implemented.");
276
+ }
277
+ visitOtherwise(node: Otherwise): PrimitiveValue {
278
+ return true;
279
+ }
280
+ visitCompositionExpression(node: CompositionExpression): PrimitiveValue {
281
+ const f = node.left.accept(this);
282
+ const g = node.right.accept(this);
283
+
284
+ if (!this.isRuntimeFunction(f) || !this.isRuntimeFunction(g))
285
+ throw new InterpreterError(
286
+ "CompositionExpression",
287
+ "Both operands of (.) must be functions"
288
+ );
289
+
290
+ const fName = `__comp_f_${Date.now()}_${Math.random()
291
+ .toString(36)
292
+ .substring(2, 5)}`;
293
+ const gName = `__comp_g_${Date.now()}_${Math.random()
294
+ .toString(36)
295
+ .substring(2, 5)}`;
296
+ define(this.env, fName, f);
297
+ define(this.env, gName, g);
298
+ const arity = g.arity;
299
+
300
+ const placeholders = Array.from(
301
+ { length: arity },
302
+ (_, i) => new VariablePattern(new SymbolPrimitive(`_p${i}`))
303
+ );
304
+
305
+ const gCall = placeholders.reduce<Expression>(
306
+ (acc, p) => new Application(acc, new SymbolPrimitive(p.name.value)),
307
+ new SymbolPrimitive(gName)
308
+ );
309
+
310
+ const composedBody = new Application(new SymbolPrimitive(fName), gCall);
311
+ const lambda = new Lambda(placeholders, composedBody);
312
+ return lambda.accept(this);
313
+ }
314
+ visitLambda(node: Lambda): PrimitiveValue {
315
+ const patterns = node.parameters;
316
+ const equation: EquationRuntime = {
317
+ patterns,
318
+ body: new UnguardedBody(new Sequence([new Return(node.body)])),
319
+ };
320
+ return {
321
+ arity: patterns.length,
322
+ equations: [equation],
323
+ pendingArgs: [],
324
+ identifier: "<lambda>",
325
+ closure: Array.from(this.env),
326
+ };
327
+ }
328
+
329
+ visitApplication(node: Application): PrimitiveValue {
330
+ const func = node.functionExpr.accept(this);
331
+
332
+ if (!this.isRuntimeFunction(func))
333
+ throw new InterpreterError("Application", "Cannot apply non-function");
334
+
335
+ const argThunk = () => node.parameter.accept(this);
336
+
337
+ const pending = func.pendingArgs
338
+ ? [...func.pendingArgs, argThunk]
339
+ : [argThunk];
340
+
341
+ // partially applied
342
+ if (pending.length < func.arity)
343
+ return {
344
+ ...func,
345
+ pendingArgs: pending,
346
+ };
347
+
348
+ // fully applied
349
+ if (pending.length === func.arity) {
350
+ const evaluatedArgs = pending.map((arg) =>
351
+ typeof arg === "function" ? arg() : arg
352
+ );
353
+ const executionEnv = func.closure ?? this.env;
354
+ return FunctionRuntime.apply(
355
+ func.identifier ?? "<anonymous>",
356
+ func.equations,
357
+ evaluatedArgs,
358
+ executionEnv,
359
+ (newEnv) => new InterpreterVisitor(newEnv, this.config, this.frames)
360
+ );
361
+ }
362
+
363
+ throw new InterpreterError("Application", "Too many arguments provided");
364
+ }
365
+ visitQuery(node: Query): PrimitiveValue {
366
+ return this.getLogicEngine().solveQuery(node);
367
+ }
368
+ visitExist(node: Exist): PrimitiveValue {
369
+ return this.getLogicEngine().solveExist(node);
370
+ }
371
+ visitNot(node: Not): PrimitiveValue {
372
+ throw new Error("Method not implemented.");
373
+ }
374
+ visitFindall(node: Findall): PrimitiveValue {
375
+ return this.getLogicEngine().solveFindall(node);
376
+ }
377
+ visitForall(node: Forall): PrimitiveValue {
378
+ throw new Error("Method not implemented.");
379
+ }
380
+ visitGoal(node: Goal): PrimitiveValue {
381
+ return this.getLogicEngine().solveGoal(node);
382
+ }
383
+ visitSend(node: Send): PrimitiveValue {
384
+ throw new Error("Method not implemented.");
385
+ }
386
+ visitNew(node: New): PrimitiveValue {
387
+ throw new Error("Method not implemented.");
388
+ }
389
+ visitImplement(node: Implement): PrimitiveValue {
390
+ throw new Error("Method not implemented.");
391
+ }
392
+ visitInclude(node: Include): PrimitiveValue {
393
+ throw new Error("Method not implemented.");
394
+ }
395
+ visitSelf(node: Self): PrimitiveValue {
396
+ throw new Error("Method not implemented.");
397
+ }
398
+ visitListComprehension(node: ListComprehension): PrimitiveValue {
399
+ throw new Error("Method not implemented.");
400
+ }
401
+ visitGenerator(node: YuGenerator): PrimitiveValue {
402
+ throw new Error("Method not implemented.");
403
+ }
404
+ visitRaise(node: Raise): PrimitiveValue {
405
+ const msg = node.body.accept(this);
406
+ if (typeof msg !== "string")
407
+ throw new UnexpectedValue("Raise", "string", typeof msg);
408
+ throw new InterpreterError("Raise", msg);
409
+ }
410
+ visitRangeExpression(node: RangeExpression): PrimitiveValue {
411
+ try {
412
+ return LazyRuntime.evaluateRange(node, this, this.config);
413
+ } catch (e) {
414
+ throw new InterpreterError("Range", e.message, this.frames);
415
+ }
416
+ }
417
+ visit(node: Expression): PrimitiveValue {
418
+ return this.safelyVisit(node, () => node.accept(this));
419
+ }
420
+ private safelyVisit<T>(node: Expression, fn: () => T): T {
421
+ try {
422
+ return fn();
423
+ } catch (err) {
424
+ if (err instanceof InterpreterError) {
425
+ err.pushFrame({ nodeType: node.constructor.name, loc: node.loc });
426
+ throw err;
427
+ }
428
+ const wrapped = new InterpreterError(
429
+ node.constructor.name,
430
+ (err as Error).message,
431
+ [...this.frames, { nodeType: node.constructor.name, loc: node.loc }]
432
+ );
433
+ throw wrapped;
434
+ }
435
+ }
436
+ private isRuntimeFunction(val: any): val is RuntimeFunction {
437
+ return (
438
+ typeof val === "object" &&
439
+ val !== null &&
440
+ Array.isArray(val.equations) &&
441
+ typeof val.arity === "number"
442
+ );
443
+ }
444
+ public realizeList(val: PrimitiveValue): PrimitiveValue[] {
445
+ return LazyRuntime.realizeList(val);
446
+ }
447
+ private processBinary(
448
+ node: BinaryOperation,
449
+ table: any,
450
+ typeGuard: (a: any, b: any) => boolean,
451
+ contextName: string
452
+ ): PrimitiveValue {
453
+ const left = node.left.accept(this);
454
+ const right = node.right.accept(this);
455
+
456
+ if (!typeGuard(left, right))
457
+ throw new InterpreterError(
458
+ contextName,
459
+ `Type mismatch: ${left}, ${right}`,
460
+ this.frames
461
+ );
462
+
463
+ const fn = table[node.operator];
464
+ if (!fn)
465
+ throw new InterpreterError(contextName, `Unknown op: ${node.operator}`);
466
+ return fn(left, right);
467
+ }
468
+
469
+ private processUnary(
470
+ node: UnaryOperation,
471
+ table: any,
472
+ typeGuard: (a: any) => boolean,
473
+ contextName: string
474
+ ): PrimitiveValue {
475
+ const operand = node.operand.accept(this);
476
+ if (!typeGuard(operand))
477
+ throw new InterpreterError(
478
+ contextName,
479
+ `Type mismatch: ${operand}`,
480
+ this.frames
481
+ );
482
+
483
+ const fn = table[node.operator];
484
+ if (!fn)
485
+ throw new InterpreterError(contextName, `Unknown op: ${node.operator}`);
486
+ return fn(operand);
487
+ }
488
+ static evaluateLiteral(node: ASTNode): PrimitiveValue {
489
+ return node.accept(
490
+ new InterpreterVisitor([new Map()], { lazyLoading: false })
491
+ );
492
+ }
493
+ }
@@ -0,0 +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
+ }
@@ -0,0 +1,59 @@
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
+ }
@@ -0,0 +1,79 @@
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
+ }