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,149 @@
1
+ import {
2
+ Expression,
3
+ LiteralPattern,
4
+ Pattern,
5
+ PrimitiveValue,
6
+ SymbolPrimitive,
7
+ Variable,
8
+ VariablePattern,
9
+ EnvStack,
10
+ isPattern,
11
+ NumberPrimitive,
12
+ StringPrimitive,
13
+ BooleanPrimitive,
14
+ ListPrimitive,
15
+ ListPattern,
16
+ ConsExpression,
17
+ ConsPattern,
18
+ FunctorPattern,
19
+ } from "yukigo-ast";
20
+ import { Substitution, instantiate } from "./LogicResolver.js";
21
+ import { ExpressionEvaluator } from "../../utils.js";
22
+ import { InterpreterError } from "../../errors.js";
23
+ import {
24
+ idContinuation,
25
+ trampoline,
26
+ Continuation,
27
+ Thunk,
28
+ } from "../../trampoline.js";
29
+ import { RuntimeContext } from "../RuntimeContext.js";
30
+
31
+ export class LogicTranslator {
32
+ constructor(
33
+ private evaluator: ExpressionEvaluator,
34
+ private ctx: RuntimeContext,
35
+ ) {}
36
+
37
+ public patternToPrimitive(pat: Pattern): PrimitiveValue | undefined {
38
+ if (pat instanceof LiteralPattern) {
39
+ const primitive = pat.name;
40
+ return primitive.value;
41
+ }
42
+ if (pat instanceof ListPattern) {
43
+ return pat.elements.map((el) => this.patternToPrimitive(el));
44
+ }
45
+ if (pat instanceof ConsPattern) {
46
+ const head = this.patternToPrimitive(pat.left);
47
+ const tail = this.patternToPrimitive(pat.right);
48
+ if (Array.isArray(tail)) {
49
+ return [head, ...tail];
50
+ }
51
+ return [head, tail];
52
+ }
53
+ // Return the pattern itself for non-primitive logic terms (VariablePattern, FunctorPattern, etc.)
54
+ return pat as any;
55
+ }
56
+
57
+ public expressionToPattern<R = Pattern>(
58
+ expr: Expression,
59
+ k: Continuation<Pattern, R>,
60
+ ): Thunk<R> {
61
+ if (isPattern(expr)) return k(expr);
62
+
63
+ if (expr instanceof ListPrimitive) {
64
+ const results: Pattern[] = [];
65
+ const next = (index: number): Thunk<R> => {
66
+ if (index >= expr.value.length) return k(new ListPattern(results));
67
+ return this.expressionToPattern(expr.value[index], (p) => {
68
+ results.push(p);
69
+ return () => next(index + 1);
70
+ });
71
+ };
72
+ return next(0);
73
+ }
74
+
75
+ if (expr instanceof ConsExpression) {
76
+ return this.expressionToPattern(expr.head, (headPat) => {
77
+ return () =>
78
+ this.expressionToPattern(expr.tail, (tailPat) => {
79
+ return k(new ConsPattern(headPat, tailPat));
80
+ });
81
+ });
82
+ }
83
+
84
+ if (expr instanceof Variable || expr instanceof SymbolPrimitive) {
85
+ const name =
86
+ expr instanceof Variable ? expr.identifier.value : expr.value;
87
+ if (this.ctx.isDefined(name)) {
88
+ return this.evaluator.evaluate(expr, (val) => {
89
+ return k(this.primitiveToPattern(val));
90
+ });
91
+ }
92
+ return k(
93
+ new VariablePattern(expr instanceof Variable ? expr.identifier : expr),
94
+ );
95
+ }
96
+ return this.evaluator.evaluate(expr, (val) => {
97
+ return k(this.primitiveToPattern(val));
98
+ });
99
+ }
100
+
101
+ public primitiveToPattern(val: PrimitiveValue): Pattern {
102
+ if (isPattern(val as any)) {
103
+ return val as any;
104
+ }
105
+ if (typeof val === "number") {
106
+ return new LiteralPattern(new NumberPrimitive(val));
107
+ }
108
+ if (typeof val === "string") {
109
+ return new LiteralPattern(new StringPrimitive(val));
110
+ }
111
+ if (typeof val === "boolean") {
112
+ return new LiteralPattern(new BooleanPrimitive(val));
113
+ }
114
+ if (Array.isArray(val)) {
115
+ return new ListPattern(val.map((v) => this.primitiveToPattern(v)));
116
+ }
117
+ if (
118
+ val &&
119
+ typeof val === "object" &&
120
+ "type" in val &&
121
+ val.type === "Object"
122
+ ) {
123
+ // Convert RuntimeObject to FunctorPattern for logic matching
124
+ const args: Pattern[] = [];
125
+ for (const [_, fieldVal] of (val as any).fields) {
126
+ args.push(this.primitiveToPattern(fieldVal));
127
+ }
128
+ return new FunctorPattern(
129
+ new SymbolPrimitive((val as any).className || (val as any).identifier),
130
+ args,
131
+ );
132
+ }
133
+
134
+ throw new InterpreterError(
135
+ "primitiveToPattern",
136
+ `Cannot convert value ${val} to Logic Pattern`,
137
+ );
138
+ }
139
+
140
+ public instantiateExpressionAsPattern<R = Pattern>(
141
+ expr: Expression,
142
+ substs: Substitution,
143
+ k: Continuation<Pattern, R>,
144
+ ): Thunk<R> {
145
+ return this.expressionToPattern(expr, (patternBase) => {
146
+ return k(instantiate(patternBase, substs));
147
+ });
148
+ }
149
+ }
@@ -0,0 +1,227 @@
1
+ import {
2
+ EquationRuntime,
3
+ PrimitiveValue,
4
+ UnguardedBody,
5
+ Sequence,
6
+ Return,
7
+ Function,
8
+ RuntimeFunction,
9
+ isRuntimeFunction,
10
+ } from "yukigo-ast";
11
+ import { Bindings } from "../../index.js";
12
+ import { PatternMatcher } from "../PatternMatcher.js";
13
+ import { ExpressionEvaluator } from "../../utils.js";
14
+ import { InterpreterError } from "../../errors.js";
15
+ import { EnvBuilderVisitor } from "../EnvBuilder.js";
16
+ import { Continuation, CPSThunk, Thunk, valueToCPS } from "../../trampoline.js";
17
+ import { RuntimeContext } from "../RuntimeContext.js";
18
+ import { InterpreterVisitor } from "../Visitor.js";
19
+
20
+ class NonExhaustivePatterns extends InterpreterError {
21
+ constructor(funcName: string) {
22
+ super("PatternMatch", `Non-exhaustive patterns in '${funcName}'`);
23
+ }
24
+ }
25
+
26
+ type EvaluatorFactory = (ctx: RuntimeContext) => ExpressionEvaluator;
27
+
28
+ export class FunctionRuntime {
29
+ constructor(private context: RuntimeContext) {}
30
+
31
+ public apply(
32
+ func: RuntimeFunction,
33
+ args: PrimitiveValue[],
34
+ k: Continuation<PrimitiveValue>,
35
+ ): Thunk<PrimitiveValue> {
36
+ const funcName = func.identifier;
37
+ const equations = func.equations;
38
+ const oldEnv = this.context.env;
39
+
40
+ if (this.context.config.debug)
41
+ console.log(
42
+ `[FunctionRuntime] Applying function: ${funcName} with args:`,
43
+ args,
44
+ );
45
+
46
+ const tryNextEquation = (eqIndex: number): Thunk<PrimitiveValue> => {
47
+ if (eqIndex >= equations.length) {
48
+ this.context.setEnv(oldEnv);
49
+ throw new NonExhaustivePatterns(funcName);
50
+ }
51
+
52
+ const eq = equations[eqIndex];
53
+ if (eq.patterns.length !== args.length)
54
+ return () => tryNextEquation(eqIndex + 1);
55
+
56
+ const bindings: Bindings = [];
57
+
58
+ return this.patternsMatch(eq, args, bindings, (isMatch) => {
59
+ if (!isMatch) return () => tryNextEquation(eqIndex + 1);
60
+
61
+ if (this.context.config.debug)
62
+ console.log(
63
+ `[FunctionRuntime] Match successful for ${funcName} equation ${eqIndex}`,
64
+ );
65
+
66
+ const localEnv = new Map<string, PrimitiveValue>(bindings);
67
+ if (func.closure) this.context.setEnv(func.closure);
68
+ this.context.pushEnv(localEnv);
69
+
70
+ const wrappedK = (res: PrimitiveValue) => {
71
+ this.context.setEnv(oldEnv);
72
+ return k(res);
73
+ };
74
+
75
+ const evaluatorFactory: EvaluatorFactory = (ctx) =>
76
+ new InterpreterVisitor(ctx);
77
+
78
+ const body = eq.body;
79
+
80
+ // UnguardedBody
81
+ if (body instanceof UnguardedBody)
82
+ return this.evaluateSequence(
83
+ body.sequence,
84
+ this.context,
85
+ evaluatorFactory,
86
+ wrappedK,
87
+ );
88
+
89
+ // GuardedBody
90
+ return () => {
91
+ if (Array.isArray(body) && body.length > 0) {
92
+ const prototypeBody = body[0].body;
93
+ if (prototypeBody instanceof Sequence)
94
+ this.preloadDefinitions(prototypeBody, evaluatorFactory);
95
+ }
96
+
97
+ const tryNextGuard = (guardIndex: number): Thunk<PrimitiveValue> => {
98
+ if (guardIndex >= body.length) {
99
+ this.context.setEnv(oldEnv);
100
+ return () => tryNextEquation(eqIndex + 1);
101
+ }
102
+
103
+ const evaluator = evaluatorFactory(this.context);
104
+ const guard = body[guardIndex];
105
+ return evaluator.evaluate(guard.condition, (cond) => {
106
+ if (cond !== true) return () => tryNextGuard(guardIndex + 1);
107
+
108
+ if (!(guard.body instanceof Sequence))
109
+ return evaluator.evaluate(guard.body, wrappedK);
110
+
111
+ return this.evaluateSequence(
112
+ guard.body,
113
+ this.context,
114
+ evaluatorFactory,
115
+ wrappedK,
116
+ );
117
+ });
118
+ };
119
+
120
+ return tryNextGuard(0);
121
+ };
122
+ });
123
+ };
124
+
125
+ return tryNextEquation(0);
126
+ }
127
+
128
+ public applyArguments(
129
+ func: RuntimeFunction,
130
+ args: (PrimitiveValue | (() => PrimitiveValue))[],
131
+ ): CPSThunk<PrimitiveValue> {
132
+ if (args.length < func.arity) {
133
+ return valueToCPS({
134
+ ...func,
135
+ pendingArgs: args,
136
+ });
137
+ }
138
+
139
+ const argsToConsume = args.slice(0, func.arity);
140
+ const remainingArgs = args.slice(func.arity);
141
+
142
+ const evaluatedArgs = argsToConsume.map((arg) =>
143
+ typeof arg === "function" ? arg() : arg,
144
+ );
145
+
146
+ return (cont) => () =>
147
+ this.apply(func, evaluatedArgs, (result) => {
148
+ if (remainingArgs.length > 0) {
149
+ if (isRuntimeFunction(result)) {
150
+ const nextArgs = result.pendingArgs
151
+ ? [...result.pendingArgs, ...remainingArgs]
152
+ : remainingArgs;
153
+
154
+ return this.applyArguments(result, nextArgs)(cont);
155
+ } else {
156
+ throw new InterpreterError(
157
+ "Application",
158
+ `Too many arguments provided. Result was '${result}' (not a function), but had ${remainingArgs.length} args left.`,
159
+ );
160
+ }
161
+ }
162
+ return cont(result);
163
+ });
164
+ }
165
+
166
+ private preloadDefinitions(
167
+ seq: Sequence,
168
+ evaluatorFactory: EvaluatorFactory,
169
+ ): void {
170
+ const ctx = new RuntimeContext();
171
+ new EnvBuilderVisitor(ctx).build(seq.statements);
172
+ const evaluator = evaluatorFactory(ctx);
173
+
174
+ for (const stmt of seq.statements) {
175
+ if (stmt instanceof Function || stmt instanceof Return) continue;
176
+ evaluator.evaluate(stmt, (val) => val);
177
+ }
178
+ }
179
+
180
+ private patternsMatch(
181
+ eq: EquationRuntime,
182
+ args: PrimitiveValue[],
183
+ bindings: Bindings,
184
+ k: Continuation<boolean>,
185
+ ): Thunk<boolean> {
186
+ const matchNext = (index: number): Thunk<boolean> => {
187
+ if (index >= args.length) return k(true);
188
+ const matcher = new PatternMatcher(args[index], bindings, this.context);
189
+ return eq.patterns[index].accept(matcher)((isMatch) => {
190
+ if (!isMatch) return k(false);
191
+ return () => matchNext(index + 1);
192
+ });
193
+ };
194
+ return matchNext(0);
195
+ }
196
+
197
+ private evaluateSequence(
198
+ seq: Sequence,
199
+ ctx: RuntimeContext,
200
+ evaluatorFactory: EvaluatorFactory,
201
+ k: Continuation<PrimitiveValue>,
202
+ ): Thunk<PrimitiveValue> {
203
+ new EnvBuilderVisitor(ctx).build(seq.statements);
204
+ const evaluator = evaluatorFactory(ctx);
205
+
206
+ const evaluateNext = (
207
+ index: number,
208
+ lastResult: PrimitiveValue,
209
+ ): Thunk<PrimitiveValue> => {
210
+ if (index >= seq.statements.length) return k(lastResult);
211
+
212
+ const stmt = seq.statements[index];
213
+ if (stmt instanceof Function)
214
+ return () => evaluateNext(index + 1, lastResult);
215
+
216
+ if (stmt instanceof Return) {
217
+ return evaluator.evaluate(stmt.body, k);
218
+ } else {
219
+ return evaluator.evaluate(stmt, (result) => {
220
+ return () => evaluateNext(index + 1, result);
221
+ });
222
+ }
223
+ };
224
+
225
+ return evaluateNext(0, undefined);
226
+ }
227
+ }
@@ -0,0 +1,334 @@
1
+ import {
2
+ PrimitiveValue,
3
+ RangeExpression,
4
+ ConsExpression,
5
+ isLazyList,
6
+ ListBinaryOperation,
7
+ LazyList,
8
+ } from "yukigo-ast";
9
+ import { ExpressionEvaluator } from "../../utils.js";
10
+ import {
11
+ createMemoizedStream,
12
+ InternalConsState,
13
+ isMemoizedList,
14
+ } from "../PatternMatcher.js";
15
+ import {
16
+ Continuation,
17
+ idContinuation,
18
+ Thunk,
19
+ trampoline,
20
+ } from "../../trampoline.js";
21
+ import { RuntimeContext } from "../RuntimeContext.js";
22
+
23
+ export class LazyRuntime {
24
+ constructor(private context: RuntimeContext) {}
25
+
26
+ public realizeList<R = PrimitiveValue[]>(
27
+ val: PrimitiveValue,
28
+ k: Continuation<PrimitiveValue[], R>,
29
+ ): Thunk<R> {
30
+ if (Array.isArray(val)) return k(val);
31
+ if (typeof val === "string") return k(val.split(""));
32
+ if (isLazyList(val)) {
33
+ const result: PrimitiveValue[] = [];
34
+ const iter = val.generator();
35
+
36
+ const next = (): Thunk<R> => {
37
+ const step = iter.next();
38
+ if (step.done) return k(result);
39
+ if (step.value === undefined)
40
+ throw new Error("LazyList yielded undefined");
41
+ result.push(step.value);
42
+ return () => next();
43
+ };
44
+ return next();
45
+ }
46
+ throw new Error(`Expected List or LazyList, got ${typeof val}`);
47
+ }
48
+
49
+ public evaluateRange(
50
+ node: RangeExpression,
51
+ evaluator: ExpressionEvaluator,
52
+ k: Continuation<PrimitiveValue>,
53
+ ): Thunk<PrimitiveValue> {
54
+ return evaluator.evaluate(node.start, (startVal) => {
55
+ if (typeof startVal !== "number")
56
+ throw new Error("Range start must be a number");
57
+
58
+ const hasEnd = node.end != null;
59
+
60
+ const finishWithStep = (step: number): Thunk<PrimitiveValue> => {
61
+ if (!hasEnd) {
62
+ return k(
63
+ createMemoizedStream(function* () {
64
+ let current = startVal;
65
+ while (true) {
66
+ yield current;
67
+ current += step;
68
+ }
69
+ }),
70
+ );
71
+ }
72
+
73
+ return evaluator.evaluate(node.end!, (endVal) => {
74
+ if (typeof endVal !== "number")
75
+ throw new Error("Range end must be a number");
76
+
77
+ const cond =
78
+ step > 0 ? (c: number) => c <= endVal : (c: number) => c >= endVal;
79
+
80
+ if (this.context.config.lazyLoading) {
81
+ return k(
82
+ createMemoizedStream(function* () {
83
+ let current = startVal;
84
+ while (cond(current)) {
85
+ yield current;
86
+ current += step;
87
+ }
88
+ }),
89
+ );
90
+ }
91
+
92
+ const result: number[] = [];
93
+ let current = startVal;
94
+ while (cond(current)) {
95
+ result.push(current);
96
+ current += step;
97
+ }
98
+ return k(result);
99
+ });
100
+ };
101
+
102
+ if (node.step) {
103
+ return evaluator.evaluate(node.step, (secondVal) => {
104
+ if (typeof secondVal !== "number")
105
+ throw new Error("Range step must be a number");
106
+ const step = secondVal - startVal;
107
+ if (step === 0) throw new Error("Range step cannot be zero");
108
+ return finishWithStep(step);
109
+ });
110
+ }
111
+
112
+ return finishWithStep(1);
113
+ });
114
+ }
115
+
116
+ public evaluateCons(
117
+ node: ConsExpression,
118
+ evaluator: ExpressionEvaluator,
119
+ k: Continuation<PrimitiveValue>,
120
+ ): Thunk<PrimitiveValue> {
121
+ const ctx = this.context;
122
+ const capturedEnv = ctx.clone();
123
+ return evaluator.evaluate(node.head, (head) => {
124
+ if (ctx.config.lazyLoading) {
125
+ const consState: InternalConsState = {
126
+ head,
127
+ tailExpr: node.tail,
128
+ evaluator,
129
+ capturedEnv,
130
+ realizedTail: undefined,
131
+ };
132
+
133
+ const consList: LazyList = {
134
+ type: "LazyList",
135
+ generator: function* () {
136
+ let current: any = consState;
137
+
138
+ while (current !== undefined && current !== null) {
139
+ if (current.tailExpr !== undefined) {
140
+ yield current.head;
141
+
142
+ if (current.realizedTail === undefined) {
143
+ const prevEnv = ctx.env;
144
+ ctx.setEnv(current.capturedEnv);
145
+ try {
146
+ current.realizedTail = trampoline(
147
+ current.evaluator.evaluate(current.tailExpr, idContinuation),
148
+ );
149
+ } finally {
150
+ ctx.setEnv(prevEnv);
151
+ }
152
+ }
153
+ current = current.realizedTail;
154
+ } else if (isLazyList(current)) {
155
+ const memoized = current;
156
+ if (isMemoizedList(memoized) && memoized._consState) {
157
+ current = memoized._consState;
158
+ } else {
159
+ const iter = current.generator();
160
+ let step = iter.next();
161
+ while (!step.done) {
162
+ yield step.value;
163
+ step = iter.next();
164
+ }
165
+ break;
166
+ }
167
+ } else if (
168
+ Array.isArray(current) ||
169
+ typeof current === "string"
170
+ ) {
171
+ for (const x of current) yield x;
172
+ break;
173
+ } else {
174
+ throw new Error(
175
+ `Invalid tail type for Cons: ${typeof current}`,
176
+ );
177
+ }
178
+ }
179
+ },
180
+ };
181
+
182
+ const memoized = createMemoizedStream(() => consList.generator());
183
+
184
+ memoized._consState = consState;
185
+
186
+ return k(memoized);
187
+ }
188
+
189
+ // Eager behavior
190
+ return evaluator.evaluate(node.tail, (tail) => {
191
+ if (typeof tail === "string") return k((head as string) + tail);
192
+ if (isLazyList(tail) || !Array.isArray(tail))
193
+ throw new Error("Expected Array in eager Cons");
194
+ return k([head, ...tail]);
195
+ });
196
+ });
197
+ }
198
+
199
+ public evaluateConcat(
200
+ left: PrimitiveValue,
201
+ right: PrimitiveValue,
202
+ k: Continuation<PrimitiveValue>,
203
+ ): Thunk<PrimitiveValue> {
204
+ if (this.context.config.lazyLoading) {
205
+ return k(
206
+ createMemoizedStream(function* () {
207
+ if (Array.isArray(left)) yield* left;
208
+ else if (typeof left === "string") yield* (left as string).split("");
209
+ else if (isLazyList(left)) yield* left.generator();
210
+ else throw new Error("Invalid left operand for lazy Concat");
211
+
212
+ if (Array.isArray(right)) yield* right;
213
+ else if (typeof right === "string")
214
+ yield* (right as string).split("");
215
+ else if (isLazyList(right)) yield* right.generator();
216
+ else throw new Error("Invalid right operand for lazy Concat");
217
+ }),
218
+ );
219
+ }
220
+
221
+ if (typeof left === "string" && typeof right === "string")
222
+ return k(left + right);
223
+
224
+ return this.realizeList(left, (lArr) => {
225
+ return () =>
226
+ this.realizeList(right, (rArr) => {
227
+ return k(lArr.concat(rArr));
228
+ });
229
+ });
230
+ }
231
+
232
+ public evaluateConcatLazy(
233
+ node: ListBinaryOperation,
234
+ evaluator: ExpressionEvaluator,
235
+ k: Continuation<PrimitiveValue>,
236
+ ): Thunk<PrimitiveValue> {
237
+ const ctx = this.context;
238
+
239
+ return evaluator.evaluate(node.left, (left) => {
240
+ const capturedEnv = ctx.clone();
241
+ return k(
242
+ createMemoizedStream(function* () {
243
+ if (Array.isArray(left)) yield* left;
244
+ else if (typeof left === "string") yield* left.split("");
245
+ else if (isLazyList(left)) yield* left.generator();
246
+ else throw new Error("Invalid left operand for lazy Concat");
247
+
248
+ // right evaluates lazily on demand
249
+ const prevEnv = ctx.env;
250
+ ctx.setEnv(capturedEnv);
251
+ let right: PrimitiveValue;
252
+ try {
253
+ right = trampoline(evaluator.evaluate(node.right, idContinuation));
254
+ } finally {
255
+ ctx.setEnv(prevEnv);
256
+ }
257
+
258
+ if (Array.isArray(right)) yield* right;
259
+ else if (typeof right === "string") yield* right.split("");
260
+ else if (isLazyList(right)) yield* right.generator();
261
+ else throw new Error("Invalid right operand for lazy Concat");
262
+ }),
263
+ );
264
+ });
265
+ }
266
+ public deepEqual<R = boolean>(
267
+ a: PrimitiveValue,
268
+ b: PrimitiveValue,
269
+ k: Continuation<boolean, R>,
270
+ ): Thunk<R> {
271
+ if (a === b) return k(true);
272
+
273
+ const aIsListLike = this.isListLike(a);
274
+ const bIsListLike = this.isListLike(b);
275
+ const eitherIsCollection = this.isCollection(a) || this.isCollection(b);
276
+
277
+ if (eitherIsCollection && aIsListLike && bIsListLike) {
278
+ return this.realizeList(a, (valA) => () =>
279
+ this.realizeList(b, (valB) => {
280
+ if (valA.length !== valB.length) return k(false);
281
+ return this.deepEqualCollection(valA, valB, 0, k);
282
+ })
283
+ );
284
+ }
285
+
286
+ if (eitherIsCollection) return k(false); // colección vs número → false
287
+
288
+ if (this.isPlainObject(a) && this.isPlainObject(b))
289
+ return this.deepEqualObject(a, b, k);
290
+
291
+ return k(a == b); // primitivos: number, boolean, string==number
292
+ }
293
+ private isPlainObject(val: unknown): val is Record<string, any> {
294
+ return val !== null && typeof val === "object"
295
+ && !isLazyList(val) && !Array.isArray(val);
296
+ }
297
+ private isCollection(val: unknown): val is PrimitiveValue[] | LazyList {
298
+ return Array.isArray(val) || isLazyList(val);
299
+ }
300
+
301
+ private isListLike(val: unknown): val is string | PrimitiveValue[] | LazyList {
302
+ return Array.isArray(val) || isLazyList(val) || typeof val === "string";
303
+ }
304
+ private deepEqualCollection<R>(
305
+ a: PrimitiveValue[],
306
+ b: PrimitiveValue[],
307
+ index: number,
308
+ k: Continuation<boolean, R>,
309
+ ): Thunk<R> {
310
+ if (index >= a.length) return k(true);
311
+ return this.deepEqual(a[index], b[index], (eq) => {
312
+ if (!eq) return k(false);
313
+ return () => this.deepEqualCollection(a, b, index + 1, k);
314
+ });
315
+ }
316
+
317
+ private deepEqualObject<R>(
318
+ a: Record<string, any>,
319
+ b: Record<string, any>,
320
+ k: Continuation<boolean, R>,
321
+ ): Thunk<R> {
322
+ const keys = Object.keys(a);
323
+ if (keys.length !== Object.keys(b).length) return k(false);
324
+ const checkNext = (index: number): Thunk<R> => {
325
+ if (index >= keys.length) return k(true);
326
+ const key = keys[index];
327
+ return this.deepEqual(a[key], b[key], (eq) => {
328
+ if (!eq) return k(false);
329
+ return () => checkNext(index + 1);
330
+ });
331
+ };
332
+ return checkNext(0);
333
+ }
334
+ }