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
@@ -1,493 +1,1065 @@
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
- }
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
+ Assignment,
25
+ TupleExpression,
26
+ FieldExpression,
27
+ DataExpression,
28
+ ConsExpression,
29
+ LetInExpression,
30
+ Call,
31
+ Otherwise,
32
+ CompositionExpression,
33
+ VariablePattern,
34
+ Expression,
35
+ Application,
36
+ Lambda,
37
+ EquationRuntime,
38
+ UnguardedBody,
39
+ Sequence,
40
+ Return,
41
+ Exist,
42
+ Not,
43
+ Findall,
44
+ Forall,
45
+ Goal,
46
+ Send,
47
+ New,
48
+ Implement,
49
+ Self,
50
+ ListComprehension,
51
+ RangeExpression,
52
+ RuntimeFunction,
53
+ Generator as YuGenerator,
54
+ BinaryOperation,
55
+ UnaryOperation,
56
+ ASTNode,
57
+ Raise,
58
+ Query,
59
+ TypeCast,
60
+ isRuntimeObject,
61
+ isRuntimeClass,
62
+ isRuntimePredicate,
63
+ Super,
64
+ EnvStack,
65
+ Environment,
66
+ If,
67
+ isRuntimeFunction,
68
+ Assert,
69
+ Test,
70
+ TestGroup,
71
+ LogicConstraint,
72
+ isLazyList,
73
+ } from "yukigo-ast";
74
+ import {
75
+ ArithmeticBinaryTable,
76
+ ArithmeticUnaryTable,
77
+ BitwiseBinaryTable,
78
+ BitwiseUnaryTable,
79
+ ComparisonOperationTable,
80
+ ListBinaryTable,
81
+ ListUnaryTable,
82
+ LogicalBinaryTable,
83
+ LogicalUnaryTable,
84
+ StringOperationTable,
85
+ } from "./Operations.js";
86
+ import { ExpressionEvaluator } from "../utils.js";
87
+ import { LogicEngine } from "./logic/LogicEngine.js";
88
+ import { ErrorFrame, InterpreterError, UnexpectedValue } from "../errors.js";
89
+ import { EnvBuilderVisitor } from "./EnvBuilder.js";
90
+ import { FailedAssert, TestRunner } from "./TestRunner.js";
91
+ import {
92
+ Continuation,
93
+ CPSThunk,
94
+ idContinuation,
95
+ Thunk,
96
+ trampoline,
97
+ valueToCPS,
98
+ } from "../trampoline.js";
99
+ import { RuntimeContext } from "./RuntimeContext.js";
100
+
101
+ export class InterpreterVisitor
102
+ implements Visitor<CPSThunk<PrimitiveValue>>, ExpressionEvaluator
103
+ {
104
+ constructor(
105
+ private context: RuntimeContext,
106
+ private frames: ErrorFrame[] = [],
107
+ ) {}
108
+
109
+ evaluate<R = PrimitiveValue>(
110
+ node: ASTNode,
111
+ cont: Continuation<PrimitiveValue, R>,
112
+ ): Thunk<R> {
113
+ return () => {
114
+ try {
115
+ const cpsThunk = node.accept(this);
116
+ return cpsThunk(cont);
117
+ } catch (err) {
118
+ if (err instanceof InterpreterError || err instanceof FailedAssert) {
119
+ if (err instanceof InterpreterError) {
120
+ err.pushFrame({ nodeType: node.constructor.name, loc: node.loc });
121
+ }
122
+ throw err;
123
+ }
124
+ throw new InterpreterError(
125
+ node.constructor.name,
126
+ (err as Error).message,
127
+ [...this.frames, { nodeType: node.constructor.name, loc: node.loc }],
128
+ );
129
+ }
130
+ };
131
+ }
132
+
133
+ visitSequence(node: Sequence): CPSThunk<PrimitiveValue> {
134
+ return (k) => {
135
+ if (node.statements.length === 0) return k(undefined);
136
+
137
+ if (this.context.config.debug)
138
+ console.log(
139
+ `[Interpreter] Entering sequence with ${node.statements.length} statements`,
140
+ );
141
+
142
+ const evaluateNext = (
143
+ index: number,
144
+ lastResult: PrimitiveValue,
145
+ ): Thunk<PrimitiveValue> => {
146
+ if (index >= node.statements.length) return k(lastResult);
147
+ const stmt = node.statements[index];
148
+
149
+ return this.evaluate(stmt, (result) => {
150
+ if (stmt instanceof Return) return k(result);
151
+ return () => evaluateNext(index + 1, result);
152
+ });
153
+ };
154
+
155
+ return evaluateNext(0, undefined);
156
+ };
157
+ }
158
+
159
+ visitAssert(node: Assert): CPSThunk<PrimitiveValue> {
160
+ if (this.context.config.debug) {
161
+ console.log(`[Interpreter] Visiting Assert`);
162
+ }
163
+ return (k) =>
164
+ new TestRunner(this, this.context.lazyRuntime).visitAssert(node)((val) =>
165
+ k(val),
166
+ );
167
+ }
168
+
169
+ visitTest(node: Test): CPSThunk<PrimitiveValue> {
170
+ if (this.context.config.debug) {
171
+ console.log(`[Interpreter] Visiting Test`);
172
+ }
173
+ return (k) =>
174
+ new TestRunner(this, this.context.lazyRuntime).visitTest(node)((val) =>
175
+ k(val),
176
+ );
177
+ }
178
+
179
+ visitTestGroup(node: TestGroup): CPSThunk<PrimitiveValue> {
180
+ if (this.context.config.debug) {
181
+ console.log(`[Interpreter] Visiting TestGroup`);
182
+ }
183
+ return (k) =>
184
+ new TestRunner(this, this.context.lazyRuntime).visitTestGroup(node)(
185
+ (val) => k(val),
186
+ );
187
+ }
188
+
189
+ visitNumberPrimitive(node: NumberPrimitive): CPSThunk<PrimitiveValue> {
190
+ return valueToCPS(node.value);
191
+ }
192
+
193
+ visitBooleanPrimitive(node: BooleanPrimitive): CPSThunk<PrimitiveValue> {
194
+ return valueToCPS(node.value);
195
+ }
196
+
197
+ visitStringPrimitive(node: StringPrimitive): CPSThunk<PrimitiveValue> {
198
+ return valueToCPS(node.value);
199
+ }
200
+
201
+ visitListPrimitive(node: ListPrimitive): CPSThunk<PrimitiveValue> {
202
+ return (k) => {
203
+ if (node.value.length === 0) return k([]);
204
+
205
+ const results: PrimitiveValue[] = [];
206
+ const evaluateNext = (index: number): Thunk<PrimitiveValue> => {
207
+ if (index >= node.value.length) return k(results);
208
+
209
+ return this.evaluate(node.value[index], (val) => {
210
+ results.push(val);
211
+ return () => evaluateNext(index + 1);
212
+ });
213
+ };
214
+
215
+ return evaluateNext(0);
216
+ };
217
+ }
218
+
219
+ visitNilPrimitive(node: NilPrimitive): CPSThunk<PrimitiveValue> {
220
+ return valueToCPS(node.value);
221
+ }
222
+
223
+ visitCharPrimitive(node: CharPrimitive): CPSThunk<PrimitiveValue> {
224
+ return valueToCPS(node.value);
225
+ }
226
+
227
+ visitSymbolPrimitive(node: SymbolPrimitive): CPSThunk<PrimitiveValue> {
228
+ if (this.context.config.debug) {
229
+ console.log(
230
+ `[Interpreter] Looking for \`${node.value}\` in the environment`,
231
+ );
232
+ }
233
+ try {
234
+ const val = this.context.lookup(node.value);
235
+ if (isRuntimeFunction(val) && val.arity === 0) {
236
+ if (this.context.config.debug) {
237
+ console.log(
238
+ `[Interpreter] Resolved symbol \`${node.value}\` as arity-0 function, applying...`,
239
+ );
240
+ }
241
+ return (k) => () => this.context.funcRuntime.apply(val, [], k);
242
+ }
243
+ if (this.context.config.debug) {
244
+ console.log(
245
+ `[Interpreter] Found \`${node.value}\`: ${typeof val === "object" && "type" in val ? val.type : val}`,
246
+ );
247
+ }
248
+ return valueToCPS(val);
249
+ } catch (error) {
250
+ throw new InterpreterError(
251
+ "Symbol Lookup",
252
+ (error as Error).message,
253
+ this.frames,
254
+ );
255
+ }
256
+ }
257
+
258
+ visitVariable(node: Variable): CPSThunk<PrimitiveValue> {
259
+ const name = node.identifier.value;
260
+ if (this.context.config.debug) {
261
+ console.log(`[Interpreter] Defining variable: ${name}`);
262
+ }
263
+ return (k) =>
264
+ this.evaluate(node.expression, (value) => {
265
+ this.context.define(name, value);
266
+ return k(true);
267
+ });
268
+ }
269
+
270
+ visitAssignment(node: Assignment): CPSThunk<PrimitiveValue> {
271
+ if (!this.context.config.mutability) {
272
+ throw new InterpreterError(
273
+ "Assignment",
274
+ `Cannot reassign variable '${node.identifier.value}': mutability is disabled`,
275
+ this.frames,
276
+ );
277
+ }
278
+
279
+ const name = node.identifier.value;
280
+ if (this.context.config.debug) {
281
+ console.log(`[Interpreter] Assigning variable: ${name}`);
282
+ }
283
+ return (k) =>
284
+ this.evaluate(node.expression, (value) => {
285
+ const onReplace = (scope: Environment) => {
286
+ if (scope.has("self")) {
287
+ const self = scope.get("self");
288
+ if (isRuntimeObject(self) && self.fields.has(name))
289
+ self.fields.set(name, value);
290
+ }
291
+ };
292
+
293
+ if (!this.context.replace(name, value, onReplace))
294
+ throw new InterpreterError(
295
+ "Assignment",
296
+ `Cannot assign to undefined variable: ${name}`,
297
+ this.frames,
298
+ );
299
+
300
+ return k(value);
301
+ });
302
+ }
303
+
304
+ visitArithmeticUnaryOperation(
305
+ node: ArithmeticUnaryOperation,
306
+ ): CPSThunk<PrimitiveValue> {
307
+ return this.processUnary(
308
+ node,
309
+ ArithmeticUnaryTable,
310
+ (a: number) => !Number.isNaN(a),
311
+ "ArithmeticUnaryOperation",
312
+ );
313
+ }
314
+
315
+ visitArithmeticBinaryOperation(
316
+ node: ArithmeticBinaryOperation,
317
+ ): CPSThunk<PrimitiveValue> {
318
+ return this.processBinary(
319
+ node,
320
+ ArithmeticBinaryTable,
321
+ (a, b) => typeof a === "number" && typeof b === "number",
322
+ "ArithmeticBinaryOperation",
323
+ );
324
+ }
325
+
326
+ visitListUnaryOperation(node: ListUnaryOperation): CPSThunk<PrimitiveValue> {
327
+ return (k) =>
328
+ this.evaluate(node.operand, (operand) => {
329
+ if (
330
+ typeof operand !== "string" &&
331
+ !Array.isArray(operand) &&
332
+ !isLazyList(operand)
333
+ )
334
+ throw new UnexpectedValue(
335
+ "ListUnaryOperation",
336
+ "Array, String or LazyList",
337
+ typeof operand,
338
+ );
339
+
340
+ return this.context.lazyRuntime.realizeList(operand, (arr) => {
341
+ const fn = ListUnaryTable[node.operator];
342
+ if (!fn)
343
+ throw new InterpreterError(
344
+ "ListUnaryOperation",
345
+ `Unknown operator: ${node.operator}`,
346
+ );
347
+ return k(fn(arr));
348
+ });
349
+ });
350
+ }
351
+
352
+ visitListBinaryOperation(
353
+ node: ListBinaryOperation,
354
+ ): CPSThunk<PrimitiveValue> {
355
+ if (node.operator === "Concat") {
356
+ if (this.context.config.lazyLoading) {
357
+ return (k) => this.context.lazyRuntime.evaluateConcatLazy(node, this, k);
358
+ }
359
+ return (k) =>
360
+ this.evaluate(node.left, (left) => {
361
+ return () =>
362
+ this.evaluate(node.right, (right) => {
363
+ return this.context.lazyRuntime.evaluateConcat(left, right, k);
364
+ });
365
+ });
366
+ }
367
+
368
+ return this.processBinary(
369
+ node,
370
+ ListBinaryTable,
371
+ (a, b) =>
372
+ (Array.isArray(a) || typeof a === "string" || isLazyList(a)) &&
373
+ (Array.isArray(b) || typeof b === "string" || isLazyList(b)),
374
+ "ListBinaryOperation",
375
+ );
376
+ }
377
+
378
+ visitComparisonOperation(
379
+ node: ComparisonOperation,
380
+ ): CPSThunk<PrimitiveValue> {
381
+ if (node.operator === "Equal" || node.operator === "NotEqual") {
382
+ return (k) =>
383
+ this.evaluate(node.left, (left) => () =>
384
+ this.evaluate(node.right, (right) =>
385
+ this.context.lazyRuntime.deepEqual(left, right, (eq) =>
386
+ k(node.operator === "Equal" ? eq : !eq)
387
+ )
388
+ )
389
+ );
390
+ }
391
+
392
+ return this.processBinary(
393
+ node,
394
+ ComparisonOperationTable,
395
+ () => true,
396
+ "ComparisonOperation",
397
+ );
398
+ }
399
+
400
+ visitLogicalBinaryOperation(
401
+ node: LogicalBinaryOperation,
402
+ ): CPSThunk<PrimitiveValue> {
403
+ return (k) =>
404
+ this.evaluate(node.left, (left) => {
405
+ if (typeof left !== "boolean")
406
+ throw new InterpreterError(
407
+ "LogicalBinaryOperation",
408
+ `Expected left side to be boolean and got: ${left}`,
409
+ );
410
+
411
+ const fn = LogicalBinaryTable[node.operator];
412
+ if (!fn)
413
+ throw new InterpreterError(
414
+ "LogicalBinaryOperation",
415
+ `Unknown operator '${node.operator}'`,
416
+ );
417
+
418
+ if (this.context.config.lazyLoading) {
419
+ if (node.operator === "And" && left === false) return k(false);
420
+ if (node.operator === "Or" && left === true) return k(true);
421
+ }
422
+
423
+ return this.evaluate(node.right, (right) => {
424
+ if (typeof right !== "boolean")
425
+ throw new InterpreterError(
426
+ "LogicalBinaryOperation",
427
+ `Expected right side to be boolean and got: ${right}`,
428
+ );
429
+ return k(fn(left, () => right));
430
+ });
431
+ });
432
+ }
433
+
434
+ visitLogicalUnaryOperation(
435
+ node: LogicalUnaryOperation,
436
+ ): CPSThunk<PrimitiveValue> {
437
+ return this.processUnary(
438
+ node,
439
+ LogicalUnaryTable,
440
+ (a) => typeof a === "boolean",
441
+ "LogicalUnaryOperation",
442
+ );
443
+ }
444
+
445
+ visitBitwiseBinaryOperation(
446
+ node: BitwiseBinaryOperation,
447
+ ): CPSThunk<PrimitiveValue> {
448
+ return this.processBinary(
449
+ node,
450
+ BitwiseBinaryTable,
451
+ (a, b) => !Number.isNaN(a) && !Number.isNaN(b),
452
+ "BitwiseBinaryOperation",
453
+ );
454
+ }
455
+
456
+ visitBitwiseUnaryOperation(
457
+ node: BitwiseUnaryOperation,
458
+ ): CPSThunk<PrimitiveValue> {
459
+ return this.processUnary(
460
+ node,
461
+ BitwiseUnaryTable,
462
+ (a) => !Number.isNaN(a),
463
+ "BitwiseUnaryOperation",
464
+ );
465
+ }
466
+
467
+ visitStringOperation(node: StringOperation): CPSThunk<PrimitiveValue> {
468
+ return this.processBinary(
469
+ node,
470
+ StringOperationTable,
471
+ (a, b) => typeof a === "string" || typeof b === "string",
472
+ "StringOperation",
473
+ );
474
+ }
475
+
476
+ visitUnifyOperation(node: UnifyOperation): CPSThunk<PrimitiveValue> {
477
+ return (k) => this.getLogicEngine().unifyExpr(node.left, node.right, k);
478
+ }
479
+
480
+ visitAssignOperation(node: AssignOperation): CPSThunk<PrimitiveValue> {
481
+ if (!this.context.config.mutability) {
482
+ throw new InterpreterError(
483
+ "AssignOperation",
484
+ `Cannot perform assignment operation: mutability is disabled`,
485
+ this.frames,
486
+ );
487
+ }
488
+
489
+ if (!(node.left instanceof SymbolPrimitive))
490
+ throw new InterpreterError(
491
+ "AssignOperation",
492
+ "Left side must be a SymbolPrimitive",
493
+ );
494
+ const name = node.left.value;
495
+
496
+ return (k) =>
497
+ this.evaluate(node.right, (value) => {
498
+ const onReplace = (scope: Environment) => {
499
+ if (scope.has("self")) {
500
+ const self = scope.get("self");
501
+ if (isRuntimeObject(self) && self.fields.has(name)) {
502
+ self.fields.set(name, value);
503
+ }
504
+ }
505
+ };
506
+
507
+ if (!this.context.replace(name, value, onReplace)) {
508
+ this.context.define(name, value);
509
+ }
510
+
511
+ return k(true);
512
+ });
513
+ }
514
+
515
+ visitTupleExpr(node: TupleExpression): CPSThunk<PrimitiveValue> {
516
+ return (k) => {
517
+ const results: PrimitiveValue[] = [];
518
+ const evaluateNext = (index: number): Thunk<PrimitiveValue> => {
519
+ if (index >= node.elements.length) return k(results);
520
+ return this.evaluate(node.elements[index], (val) => {
521
+ results.push(val);
522
+ return () => evaluateNext(index + 1);
523
+ });
524
+ };
525
+ return evaluateNext(0);
526
+ };
527
+ }
528
+
529
+ visitFieldExpression(node: FieldExpression): CPSThunk<PrimitiveValue> {
530
+ return (k) =>
531
+ this.evaluate(node.name, (obj) => {
532
+ return k(this.context.objRuntime.getField(obj, node.name.value));
533
+ });
534
+ }
535
+
536
+ visitDataExpr(node: DataExpression): CPSThunk<PrimitiveValue> {
537
+ return (k) => {
538
+ const fieldValues = new Map<string, PrimitiveValue>();
539
+
540
+ const evaluateFields = (index: number): Thunk<PrimitiveValue> => {
541
+ if (index >= node.contents.length) {
542
+ return k(
543
+ this.context.objRuntime.instantiate(
544
+ node.name.value,
545
+ node.name.value,
546
+ fieldValues,
547
+ new Map(),
548
+ ),
549
+ );
550
+ }
551
+ const field = node.contents[index];
552
+ return this.evaluate(field.expression, (value) => {
553
+ fieldValues.set(field.name.value, value);
554
+ return () => evaluateFields(index + 1);
555
+ });
556
+ };
557
+
558
+ return evaluateFields(0);
559
+ };
560
+ }
561
+
562
+ visitConsExpr(node: ConsExpression): CPSThunk<PrimitiveValue> {
563
+ return (k) => this.context.lazyRuntime.evaluateCons(node, this, k);
564
+ }
565
+
566
+ visitLetInExpr(node: LetInExpression): CPSThunk<PrimitiveValue> {
567
+ return (k) => {
568
+ const oldEnv = this.context.env;
569
+ this.context.pushEnv();
570
+ const envBuilder = new EnvBuilderVisitor(this.context);
571
+ node.declarations.accept(envBuilder);
572
+ return this.evaluate(node.expression, (result) => {
573
+ this.context.env = oldEnv;
574
+ return k(result);
575
+ });
576
+ };
577
+ }
578
+
579
+ visitIf(node: If): CPSThunk<PrimitiveValue> {
580
+ return (k) =>
581
+ this.evaluate(node.condition, (condition) => {
582
+ if (typeof condition !== "boolean")
583
+ throw new InterpreterError(
584
+ "If",
585
+ `Expected boolean in condition and got ${typeof condition}`,
586
+ this.frames,
587
+ );
588
+ if (this.context.config.debug) {
589
+ console.log(`[Interpreter] If condition: ${condition}`);
590
+ }
591
+ return condition
592
+ ? node.then.accept(this)(k)
593
+ : node.elseExpr.accept(this)(k);
594
+ });
595
+ }
596
+
597
+ visitCall(node: Call): CPSThunk<PrimitiveValue> {
598
+ return (k) =>
599
+ this.evaluate(node.callee, (callee) => {
600
+ const args: PrimitiveValue[] = [];
601
+ const evaluateArgs = (index: number): Thunk<PrimitiveValue> => {
602
+ if (index < node.args.length)
603
+ return this.evaluate(node.args[index], (val) => {
604
+ args.push(val);
605
+ return () => evaluateArgs(index + 1);
606
+ });
607
+
608
+ if (!isRuntimeFunction(callee))
609
+ throw new InterpreterError("Call", "Target is not a function");
610
+
611
+ if (this.context.config.debug)
612
+ console.log(
613
+ `[Interpreter] Calling function: ${callee.identifier} with ${args.length} args`,
614
+ );
615
+
616
+ return this.context.funcRuntime.apply(callee, args, k);
617
+ };
618
+ return evaluateArgs(0);
619
+ });
620
+ }
621
+
622
+ visitOtherwise(node: Otherwise): CPSThunk<PrimitiveValue> {
623
+ return valueToCPS(true);
624
+ }
625
+
626
+ visitCompositionExpression(
627
+ node: CompositionExpression,
628
+ ): CPSThunk<PrimitiveValue> {
629
+ return (k) =>
630
+ this.evaluate(node.left, (f) => {
631
+ return this.evaluate(node.right, (g) => {
632
+ if (!isRuntimeFunction(f) || !isRuntimeFunction(g)) {
633
+ throw new InterpreterError(
634
+ "Composition",
635
+ "Both operands of (.) must be functions",
636
+ );
637
+ }
638
+
639
+ const F_REF = "__internal_f";
640
+ const G_REF = "__internal_g";
641
+ const PARAM_NAME = "__x";
642
+
643
+ const compositionBody = new Application(
644
+ new SymbolPrimitive(F_REF),
645
+ new Application(
646
+ new SymbolPrimitive(G_REF),
647
+ new SymbolPrimitive(PARAM_NAME),
648
+ ),
649
+ );
650
+
651
+ const patterns = [
652
+ new VariablePattern(new SymbolPrimitive(PARAM_NAME)),
653
+ ];
654
+ const equation: EquationRuntime = {
655
+ patterns,
656
+ body: new UnguardedBody(
657
+ new Sequence([new Return(compositionBody)]),
658
+ ),
659
+ };
660
+
661
+ const privateScope = new Map<string, PrimitiveValue>();
662
+ privateScope.set(F_REF, f);
663
+ privateScope.set(G_REF, g);
664
+
665
+ const capturedEnv: EnvStack = {
666
+ head: privateScope,
667
+ tail: this.context.env,
668
+ };
669
+ return k({
670
+ type: "Function",
671
+ arity: 1,
672
+ identifier: `<(${f.identifier} . ${g.identifier})>`,
673
+ equations: [equation],
674
+ pendingArgs: [],
675
+ closure: capturedEnv,
676
+ });
677
+ });
678
+ });
679
+ }
680
+
681
+ visitLambda(node: Lambda): CPSThunk<PrimitiveValue> {
682
+ const patterns = node.parameters;
683
+ const equation: EquationRuntime = {
684
+ patterns,
685
+ body: new UnguardedBody(new Sequence([new Return(node.body)])),
686
+ };
687
+ return valueToCPS({
688
+ type: "Function",
689
+ arity: patterns.length,
690
+ equations: [equation],
691
+ pendingArgs: [],
692
+ identifier: "<lambda>",
693
+ closure: this.context.env,
694
+ });
695
+ }
696
+
697
+ visitApplication(node: Application): CPSThunk<PrimitiveValue> {
698
+ const { funcRuntime } = this.context
699
+ if (this.context.config.debug) {
700
+ console.log(`[Interpreter] Visiting Application`);
701
+ }
702
+ return (k) =>
703
+ this.evaluate(node.functionExpr, (func) => {
704
+ if (!isRuntimeFunction(func))
705
+ throw new InterpreterError(
706
+ "Application",
707
+ "Cannot apply non-function",
708
+ );
709
+
710
+ const applyFuncToNode = (func: RuntimeFunction): CPSThunk<PrimitiveValue> => (k) =>
711
+ this.evaluate(node.parameter, (arg) => {
712
+ const argThunk = () => arg;
713
+ const allPendingArgs = func.pendingArgs
714
+ ? [...func.pendingArgs, argThunk]
715
+ : [argThunk];
716
+ return funcRuntime.applyArguments(func, allPendingArgs)(k);
717
+ });
718
+ if (func.arity === 0) {
719
+ return funcRuntime.applyArguments(func, [])(resultOfFunc => {
720
+ if (!isRuntimeFunction(resultOfFunc))
721
+ throw new InterpreterError(
722
+ "Application",
723
+ `Cannot apply non-function result of arity-0 function`,
724
+ );
725
+ return applyFuncToNode(resultOfFunc)(k);
726
+ });
727
+ }
728
+
729
+ return applyFuncToNode(func)(k);
730
+ });
731
+ }
732
+
733
+ visitQuery(node: Query): CPSThunk<PrimitiveValue> {
734
+ if (this.context.config.debug) {
735
+ console.log(
736
+ `[Interpreter] Visiting Query.`,
737
+ );
738
+ }
739
+ return (k) =>
740
+ this.getLogicEngine().solveQuery(node, (res) => {
741
+ this.bindLogicResults(res);
742
+ return k(res);
743
+ });
744
+ }
745
+
746
+ visitExist(node: Exist): CPSThunk<PrimitiveValue> {
747
+ return (k) =>
748
+ this.getLogicEngine().solveExist(node, (res) => {
749
+ this.bindLogicResults(res);
750
+ return k(res);
751
+ });
752
+ }
753
+
754
+ visitNot(node: Not): CPSThunk<PrimitiveValue> {
755
+ return (k) => this.getLogicEngine().solveNot(node, k);
756
+ }
757
+
758
+ visitFindall(node: Findall): CPSThunk<PrimitiveValue> {
759
+ return (k) => this.getLogicEngine().solveFindall(node, k);
760
+ }
761
+
762
+ visitForall(node: Forall): CPSThunk<PrimitiveValue> {
763
+ return (k) => this.getLogicEngine().solveForall(node, k);
764
+ }
765
+
766
+ visitGoal(node: Goal): CPSThunk<PrimitiveValue> {
767
+ return (k) =>
768
+ this.getLogicEngine().solveGoal(node, (res) => {
769
+ this.bindLogicResults(res);
770
+ return k(res);
771
+ });
772
+ }
773
+
774
+ private bindLogicResults(res: PrimitiveValue) {
775
+ if (!res) return;
776
+ if (Array.isArray(res)) {
777
+ if (res.length > 0) this.bindLogicResults(res[0]);
778
+ return;
779
+ }
780
+ if (
781
+ typeof res === "object" &&
782
+ res !== null &&
783
+ "success" in res &&
784
+ res.success &&
785
+ "solutions" in res
786
+ ) {
787
+ for (const [name, val] of res.solutions) {
788
+ this.context.define(name, val);
789
+ }
790
+ }
791
+ }
792
+
793
+ visitLogicConstraint(node: LogicConstraint): CPSThunk<PrimitiveValue> {
794
+ return (k) =>
795
+ this.evaluate(node.expression, (val) => {
796
+ if (Array.isArray(val)) return k(val.length > 0);
797
+ return k(!!val);
798
+ });
799
+ }
800
+
801
+ visitSuper(node: Super): CPSThunk<PrimitiveValue> {
802
+ let methodName: string;
803
+ try {
804
+ methodName = this.context.lookup("__METHOD_NAME__") as string;
805
+ } catch (e) {
806
+ throw new InterpreterError(
807
+ "Super",
808
+ "'super' keyword used outside of a method context",
809
+ this.frames,
810
+ );
811
+ }
812
+
813
+ return (k) => {
814
+ const args: PrimitiveValue[] = [];
815
+ const evaluateNextArg = (index: number): Thunk<PrimitiveValue> => {
816
+ if (index >= node.args.length) {
817
+ if (this.context.config.debug) {
818
+ console.log(`[Interpreter] Dispatching super call: ${methodName}`);
819
+ }
820
+ return this.context.objRuntime.dispatchSuper(
821
+ this.context.env,
822
+ methodName,
823
+ args,
824
+ k,
825
+ );
826
+ }
827
+ return this.evaluate(node.args[index], (val) => {
828
+ args.push(val);
829
+ return () => evaluateNextArg(index + 1);
830
+ });
831
+ };
832
+ return evaluateNextArg(0);
833
+ };
834
+ }
835
+
836
+ visitSend(node: Send): CPSThunk<PrimitiveValue> {
837
+ return (k) => {
838
+ if (node.receiver instanceof Super) {
839
+ const methodName = node.selector.value;
840
+ const args: PrimitiveValue[] = [];
841
+ const evaluateNextArg = (index: number): Thunk<PrimitiveValue> => {
842
+ if (index >= node.args.length) {
843
+ if (this.context.config.debug) {
844
+ console.log(
845
+ `[Interpreter] Dispatching super call: ${methodName}`,
846
+ );
847
+ }
848
+ return this.context.objRuntime.dispatchSuper(
849
+ this.context.env,
850
+ methodName,
851
+ args,
852
+ k,
853
+ );
854
+ }
855
+ return this.evaluate(node.args[index], (val) => {
856
+ args.push(val);
857
+ return () => evaluateNextArg(index + 1);
858
+ });
859
+ };
860
+ return evaluateNextArg(0);
861
+ }
862
+
863
+ return this.evaluate(node.receiver, (receiver) => {
864
+ const methodName = node.selector.value;
865
+ const args: PrimitiveValue[] = [];
866
+ const evaluateNextArg = (index: number): Thunk<PrimitiveValue> => {
867
+ if (index >= node.args.length) {
868
+ if (this.context.config.debug) {
869
+ console.log(
870
+ `[Interpreter] Sending method call: ${methodName} to object`,
871
+ );
872
+ }
873
+ return this.context.objRuntime.dispatch(
874
+ receiver,
875
+ methodName,
876
+ args,
877
+ this.context.env,
878
+ k,
879
+ );
880
+ }
881
+ return this.evaluate(node.args[index], (val) => {
882
+ args.push(val);
883
+ return () => evaluateNextArg(index + 1);
884
+ });
885
+ };
886
+ return evaluateNextArg(0);
887
+ });
888
+ };
889
+ }
890
+
891
+ visitNew(node: New): CPSThunk<PrimitiveValue> {
892
+ const className = node.identifier.value;
893
+ const classDef = this.context.lookup(className);
894
+ if (!isRuntimeClass(classDef))
895
+ throw new InterpreterError(
896
+ "New",
897
+ `${className} is not a class.`,
898
+ this.frames,
899
+ );
900
+
901
+ if (this.context.config.debug) {
902
+ console.log(`[Interpreter] Instantiating class: ${className}`);
903
+ }
904
+
905
+ return valueToCPS(
906
+ this.context.objRuntime.instantiate(
907
+ className,
908
+ node.identifier.value,
909
+ classDef.fields,
910
+ classDef.methods,
911
+ ),
912
+ );
913
+ }
914
+
915
+ visitSelf(node: Self): CPSThunk<PrimitiveValue> {
916
+ try {
917
+ return valueToCPS(this.context.lookup("self"));
918
+ } catch {
919
+ throw new InterpreterError(
920
+ "Self",
921
+ "'self' is not defined in this context",
922
+ );
923
+ }
924
+ }
925
+
926
+ visitListComprehension(node: ListComprehension): CPSThunk<PrimitiveValue> {
927
+ return (k) => {
928
+ const results: PrimitiveValue[] = [];
929
+
930
+ const process = (index: number): Thunk<PrimitiveValue> => {
931
+ if (index >= node.generators.length) {
932
+ return this.evaluate(node.projection, (proj) => {
933
+ results.push(proj);
934
+ return () => k(results);
935
+ });
936
+ }
937
+
938
+ const current = node.generators[index];
939
+
940
+ if (current instanceof YuGenerator) {
941
+ return this.evaluate(current.expression, (exprResult) => {
942
+ return this.context.lazyRuntime.realizeList(
943
+ exprResult,
944
+ (sourceList) => {
945
+ const iterateSource = (
946
+ sourceIndex: number,
947
+ ): Thunk<PrimitiveValue> => {
948
+ if (sourceIndex >= sourceList.length) return () => k(results);
949
+
950
+ const item = sourceList[sourceIndex];
951
+ const varName = current.variable.value;
952
+ this.context.define(varName, item);
953
+
954
+ return () => process(index + 1);
955
+ };
956
+ return iterateSource(0);
957
+ },
958
+ );
959
+ });
960
+ } else {
961
+ return this.evaluate(current as Expression, (condition) => {
962
+ return condition === true
963
+ ? () => process(index + 1)
964
+ : () => k(results);
965
+ });
966
+ }
967
+ };
968
+
969
+ this.context.pushEnv();
970
+ return process(0);
971
+ };
972
+ }
973
+ visitTypeCast(node: TypeCast): CPSThunk<PrimitiveValue> {
974
+ return node.expression.accept(this);
975
+ }
976
+ visitGenerator(node: YuGenerator): CPSThunk<PrimitiveValue> {
977
+ return (k) => this.evaluate(node.expression, k);
978
+ }
979
+
980
+ visitRaise(node: Raise): CPSThunk<PrimitiveValue> {
981
+ return (k) =>
982
+ this.evaluate(node.body, (msg) => {
983
+ if (typeof msg !== "string")
984
+ throw new UnexpectedValue("Raise", "string", typeof msg);
985
+ throw new InterpreterError("Raise", msg);
986
+ });
987
+ }
988
+
989
+ visitRangeExpression(node: RangeExpression): CPSThunk<PrimitiveValue> {
990
+ return (k) => this.context.lazyRuntime.evaluateRange(node, this, k);
991
+ }
992
+
993
+ visit(node: Expression): CPSThunk<PrimitiveValue> {
994
+ return node.accept(this);
995
+ }
996
+
997
+ public realizeList<R = PrimitiveValue[]>(
998
+ val: PrimitiveValue,
999
+ k: Continuation<PrimitiveValue[], R>,
1000
+ ): Thunk<R> {
1001
+ return this.context.lazyRuntime.realizeList(val, k);
1002
+ }
1003
+
1004
+ private processBinary(
1005
+ node: BinaryOperation,
1006
+ table: any,
1007
+ typeGuard: (a: any, b: any) => boolean,
1008
+ contextName: string,
1009
+ ): CPSThunk<PrimitiveValue> {
1010
+ return (k) =>
1011
+ this.evaluate(node.left, (left) => {
1012
+ return this.evaluate(node.right, (right) => {
1013
+ if (!typeGuard(left, right))
1014
+ throw new InterpreterError(
1015
+ contextName,
1016
+ `Type mismatch: ${left}, ${right}`,
1017
+ this.frames,
1018
+ );
1019
+
1020
+ const fn = table[node.operator];
1021
+ if (!fn)
1022
+ throw new InterpreterError(
1023
+ contextName,
1024
+ `Unknown op: ${node.operator}`,
1025
+ );
1026
+ return k(fn(left, right));
1027
+ });
1028
+ });
1029
+ }
1030
+
1031
+ private processUnary(
1032
+ node: UnaryOperation,
1033
+ table: any,
1034
+ typeGuard: (a: any) => boolean,
1035
+ contextName: string,
1036
+ ): CPSThunk<PrimitiveValue> {
1037
+ return (k) =>
1038
+ this.evaluate(node.operand, (operand) => {
1039
+ if (!typeGuard(operand))
1040
+ throw new InterpreterError(
1041
+ contextName,
1042
+ `Type mismatch: ${operand}`,
1043
+ this.frames,
1044
+ );
1045
+
1046
+ const fn = table[node.operator];
1047
+ if (!fn)
1048
+ throw new InterpreterError(
1049
+ contextName,
1050
+ `Unknown op: ${node.operator}`,
1051
+ );
1052
+ return k(fn(operand));
1053
+ });
1054
+ }
1055
+
1056
+ private getLogicEngine(): LogicEngine {
1057
+ return new LogicEngine(this, this.context);
1058
+ }
1059
+
1060
+ static evaluateLiteral(node: ASTNode): PrimitiveValue {
1061
+ const ctx = new RuntimeContext();
1062
+ const visitor = new InterpreterVisitor(ctx);
1063
+ return trampoline(visitor.evaluate(node, idContinuation));
1064
+ }
1065
+ }