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,147 @@
1
+ import { UnguardedBody, Sequence, Return, Function, isRuntimeFunction, } from "yukigo-ast";
2
+ import { PatternMatcher } from "../PatternMatcher.js";
3
+ import { InterpreterError } from "../../errors.js";
4
+ import { EnvBuilderVisitor } from "../EnvBuilder.js";
5
+ import { valueToCPS } from "../../trampoline.js";
6
+ import { RuntimeContext } from "../RuntimeContext.js";
7
+ import { InterpreterVisitor } from "../Visitor.js";
8
+ class NonExhaustivePatterns extends InterpreterError {
9
+ constructor(funcName) {
10
+ super("PatternMatch", `Non-exhaustive patterns in '${funcName}'`);
11
+ }
12
+ }
13
+ export class FunctionRuntime {
14
+ context;
15
+ constructor(context) {
16
+ this.context = context;
17
+ }
18
+ apply(func, args, k) {
19
+ const funcName = func.identifier;
20
+ const equations = func.equations;
21
+ const oldEnv = this.context.env;
22
+ if (this.context.config.debug)
23
+ console.log(`[FunctionRuntime] Applying function: ${funcName} with args:`, args);
24
+ const tryNextEquation = (eqIndex) => {
25
+ if (eqIndex >= equations.length) {
26
+ this.context.setEnv(oldEnv);
27
+ throw new NonExhaustivePatterns(funcName);
28
+ }
29
+ const eq = equations[eqIndex];
30
+ if (eq.patterns.length !== args.length)
31
+ return () => tryNextEquation(eqIndex + 1);
32
+ const bindings = [];
33
+ return this.patternsMatch(eq, args, bindings, (isMatch) => {
34
+ if (!isMatch)
35
+ return () => tryNextEquation(eqIndex + 1);
36
+ if (this.context.config.debug)
37
+ console.log(`[FunctionRuntime] Match successful for ${funcName} equation ${eqIndex}`);
38
+ const localEnv = new Map(bindings);
39
+ if (func.closure)
40
+ this.context.setEnv(func.closure);
41
+ this.context.pushEnv(localEnv);
42
+ const wrappedK = (res) => {
43
+ this.context.setEnv(oldEnv);
44
+ return k(res);
45
+ };
46
+ const evaluatorFactory = (ctx) => new InterpreterVisitor(ctx);
47
+ const body = eq.body;
48
+ // UnguardedBody
49
+ if (body instanceof UnguardedBody)
50
+ return this.evaluateSequence(body.sequence, this.context, evaluatorFactory, wrappedK);
51
+ // GuardedBody
52
+ return () => {
53
+ if (Array.isArray(body) && body.length > 0) {
54
+ const prototypeBody = body[0].body;
55
+ if (prototypeBody instanceof Sequence)
56
+ this.preloadDefinitions(prototypeBody, evaluatorFactory);
57
+ }
58
+ const tryNextGuard = (guardIndex) => {
59
+ if (guardIndex >= body.length) {
60
+ this.context.setEnv(oldEnv);
61
+ return () => tryNextEquation(eqIndex + 1);
62
+ }
63
+ const evaluator = evaluatorFactory(this.context);
64
+ const guard = body[guardIndex];
65
+ return evaluator.evaluate(guard.condition, (cond) => {
66
+ if (cond !== true)
67
+ return () => tryNextGuard(guardIndex + 1);
68
+ if (!(guard.body instanceof Sequence))
69
+ return evaluator.evaluate(guard.body, wrappedK);
70
+ return this.evaluateSequence(guard.body, this.context, evaluatorFactory, wrappedK);
71
+ });
72
+ };
73
+ return tryNextGuard(0);
74
+ };
75
+ });
76
+ };
77
+ return tryNextEquation(0);
78
+ }
79
+ applyArguments(func, args) {
80
+ if (args.length < func.arity) {
81
+ return valueToCPS({
82
+ ...func,
83
+ pendingArgs: args,
84
+ });
85
+ }
86
+ const argsToConsume = args.slice(0, func.arity);
87
+ const remainingArgs = args.slice(func.arity);
88
+ const evaluatedArgs = argsToConsume.map((arg) => typeof arg === "function" ? arg() : arg);
89
+ return (cont) => () => this.apply(func, evaluatedArgs, (result) => {
90
+ if (remainingArgs.length > 0) {
91
+ if (isRuntimeFunction(result)) {
92
+ const nextArgs = result.pendingArgs
93
+ ? [...result.pendingArgs, ...remainingArgs]
94
+ : remainingArgs;
95
+ return this.applyArguments(result, nextArgs)(cont);
96
+ }
97
+ else {
98
+ throw new InterpreterError("Application", `Too many arguments provided. Result was '${result}' (not a function), but had ${remainingArgs.length} args left.`);
99
+ }
100
+ }
101
+ return cont(result);
102
+ });
103
+ }
104
+ preloadDefinitions(seq, evaluatorFactory) {
105
+ const ctx = new RuntimeContext();
106
+ new EnvBuilderVisitor(ctx).build(seq.statements);
107
+ const evaluator = evaluatorFactory(ctx);
108
+ for (const stmt of seq.statements) {
109
+ if (stmt instanceof Function || stmt instanceof Return)
110
+ continue;
111
+ evaluator.evaluate(stmt, (val) => val);
112
+ }
113
+ }
114
+ patternsMatch(eq, args, bindings, k) {
115
+ const matchNext = (index) => {
116
+ if (index >= args.length)
117
+ return k(true);
118
+ const matcher = new PatternMatcher(args[index], bindings, this.context);
119
+ return eq.patterns[index].accept(matcher)((isMatch) => {
120
+ if (!isMatch)
121
+ return k(false);
122
+ return () => matchNext(index + 1);
123
+ });
124
+ };
125
+ return matchNext(0);
126
+ }
127
+ evaluateSequence(seq, ctx, evaluatorFactory, k) {
128
+ new EnvBuilderVisitor(ctx).build(seq.statements);
129
+ const evaluator = evaluatorFactory(ctx);
130
+ const evaluateNext = (index, lastResult) => {
131
+ if (index >= seq.statements.length)
132
+ return k(lastResult);
133
+ const stmt = seq.statements[index];
134
+ if (stmt instanceof Function)
135
+ return () => evaluateNext(index + 1, lastResult);
136
+ if (stmt instanceof Return) {
137
+ return evaluator.evaluate(stmt.body, k);
138
+ }
139
+ else {
140
+ return evaluator.evaluate(stmt, (result) => {
141
+ return () => evaluateNext(index + 1, result);
142
+ });
143
+ }
144
+ };
145
+ return evaluateNext(0, undefined);
146
+ }
147
+ }
@@ -0,0 +1,19 @@
1
+ import { PrimitiveValue, RangeExpression, ConsExpression, ListBinaryOperation } from "yukigo-ast";
2
+ import { ExpressionEvaluator } from "../../utils.js";
3
+ import { Continuation, Thunk } from "../../trampoline.js";
4
+ import { RuntimeContext } from "../RuntimeContext.js";
5
+ export declare class LazyRuntime {
6
+ private context;
7
+ constructor(context: RuntimeContext);
8
+ realizeList<R = PrimitiveValue[]>(val: PrimitiveValue, k: Continuation<PrimitiveValue[], R>): Thunk<R>;
9
+ evaluateRange(node: RangeExpression, evaluator: ExpressionEvaluator, k: Continuation<PrimitiveValue>): Thunk<PrimitiveValue>;
10
+ evaluateCons(node: ConsExpression, evaluator: ExpressionEvaluator, k: Continuation<PrimitiveValue>): Thunk<PrimitiveValue>;
11
+ evaluateConcat(left: PrimitiveValue, right: PrimitiveValue, k: Continuation<PrimitiveValue>): Thunk<PrimitiveValue>;
12
+ evaluateConcatLazy(node: ListBinaryOperation, evaluator: ExpressionEvaluator, k: Continuation<PrimitiveValue>): Thunk<PrimitiveValue>;
13
+ deepEqual<R = boolean>(a: PrimitiveValue, b: PrimitiveValue, k: Continuation<boolean, R>): Thunk<R>;
14
+ private isPlainObject;
15
+ private isCollection;
16
+ private isListLike;
17
+ private deepEqualCollection;
18
+ private deepEqualObject;
19
+ }
@@ -0,0 +1,269 @@
1
+ import { isLazyList, } from "yukigo-ast";
2
+ import { createMemoizedStream, isMemoizedList, } from "../PatternMatcher.js";
3
+ import { idContinuation, trampoline, } from "../../trampoline.js";
4
+ export class LazyRuntime {
5
+ context;
6
+ constructor(context) {
7
+ this.context = context;
8
+ }
9
+ realizeList(val, k) {
10
+ if (Array.isArray(val))
11
+ return k(val);
12
+ if (typeof val === "string")
13
+ return k(val.split(""));
14
+ if (isLazyList(val)) {
15
+ const result = [];
16
+ const iter = val.generator();
17
+ const next = () => {
18
+ const step = iter.next();
19
+ if (step.done)
20
+ return k(result);
21
+ if (step.value === undefined)
22
+ throw new Error("LazyList yielded undefined");
23
+ result.push(step.value);
24
+ return () => next();
25
+ };
26
+ return next();
27
+ }
28
+ throw new Error(`Expected List or LazyList, got ${typeof val}`);
29
+ }
30
+ evaluateRange(node, evaluator, k) {
31
+ return evaluator.evaluate(node.start, (startVal) => {
32
+ if (typeof startVal !== "number")
33
+ throw new Error("Range start must be a number");
34
+ const hasEnd = node.end != null;
35
+ const finishWithStep = (step) => {
36
+ if (!hasEnd) {
37
+ return k(createMemoizedStream(function* () {
38
+ let current = startVal;
39
+ while (true) {
40
+ yield current;
41
+ current += step;
42
+ }
43
+ }));
44
+ }
45
+ return evaluator.evaluate(node.end, (endVal) => {
46
+ if (typeof endVal !== "number")
47
+ throw new Error("Range end must be a number");
48
+ const cond = step > 0 ? (c) => c <= endVal : (c) => c >= endVal;
49
+ if (this.context.config.lazyLoading) {
50
+ return k(createMemoizedStream(function* () {
51
+ let current = startVal;
52
+ while (cond(current)) {
53
+ yield current;
54
+ current += step;
55
+ }
56
+ }));
57
+ }
58
+ const result = [];
59
+ let current = startVal;
60
+ while (cond(current)) {
61
+ result.push(current);
62
+ current += step;
63
+ }
64
+ return k(result);
65
+ });
66
+ };
67
+ if (node.step) {
68
+ return evaluator.evaluate(node.step, (secondVal) => {
69
+ if (typeof secondVal !== "number")
70
+ throw new Error("Range step must be a number");
71
+ const step = secondVal - startVal;
72
+ if (step === 0)
73
+ throw new Error("Range step cannot be zero");
74
+ return finishWithStep(step);
75
+ });
76
+ }
77
+ return finishWithStep(1);
78
+ });
79
+ }
80
+ evaluateCons(node, evaluator, k) {
81
+ const ctx = this.context;
82
+ const capturedEnv = ctx.clone();
83
+ return evaluator.evaluate(node.head, (head) => {
84
+ if (ctx.config.lazyLoading) {
85
+ const consState = {
86
+ head,
87
+ tailExpr: node.tail,
88
+ evaluator,
89
+ capturedEnv,
90
+ realizedTail: undefined,
91
+ };
92
+ const consList = {
93
+ type: "LazyList",
94
+ generator: function* () {
95
+ let current = consState;
96
+ while (current !== undefined && current !== null) {
97
+ if (current.tailExpr !== undefined) {
98
+ yield current.head;
99
+ if (current.realizedTail === undefined) {
100
+ const prevEnv = ctx.env;
101
+ ctx.setEnv(current.capturedEnv);
102
+ try {
103
+ current.realizedTail = trampoline(current.evaluator.evaluate(current.tailExpr, idContinuation));
104
+ }
105
+ finally {
106
+ ctx.setEnv(prevEnv);
107
+ }
108
+ }
109
+ current = current.realizedTail;
110
+ }
111
+ else if (isLazyList(current)) {
112
+ const memoized = current;
113
+ if (isMemoizedList(memoized) && memoized._consState) {
114
+ current = memoized._consState;
115
+ }
116
+ else {
117
+ const iter = current.generator();
118
+ let step = iter.next();
119
+ while (!step.done) {
120
+ yield step.value;
121
+ step = iter.next();
122
+ }
123
+ break;
124
+ }
125
+ }
126
+ else if (Array.isArray(current) ||
127
+ typeof current === "string") {
128
+ for (const x of current)
129
+ yield x;
130
+ break;
131
+ }
132
+ else {
133
+ throw new Error(`Invalid tail type for Cons: ${typeof current}`);
134
+ }
135
+ }
136
+ },
137
+ };
138
+ const memoized = createMemoizedStream(() => consList.generator());
139
+ memoized._consState = consState;
140
+ return k(memoized);
141
+ }
142
+ // Eager behavior
143
+ return evaluator.evaluate(node.tail, (tail) => {
144
+ if (typeof tail === "string")
145
+ return k(head + tail);
146
+ if (isLazyList(tail) || !Array.isArray(tail))
147
+ throw new Error("Expected Array in eager Cons");
148
+ return k([head, ...tail]);
149
+ });
150
+ });
151
+ }
152
+ evaluateConcat(left, right, k) {
153
+ if (this.context.config.lazyLoading) {
154
+ return k(createMemoizedStream(function* () {
155
+ if (Array.isArray(left))
156
+ yield* left;
157
+ else if (typeof left === "string")
158
+ yield* left.split("");
159
+ else if (isLazyList(left))
160
+ yield* left.generator();
161
+ else
162
+ throw new Error("Invalid left operand for lazy Concat");
163
+ if (Array.isArray(right))
164
+ yield* right;
165
+ else if (typeof right === "string")
166
+ yield* right.split("");
167
+ else if (isLazyList(right))
168
+ yield* right.generator();
169
+ else
170
+ throw new Error("Invalid right operand for lazy Concat");
171
+ }));
172
+ }
173
+ if (typeof left === "string" && typeof right === "string")
174
+ return k(left + right);
175
+ return this.realizeList(left, (lArr) => {
176
+ return () => this.realizeList(right, (rArr) => {
177
+ return k(lArr.concat(rArr));
178
+ });
179
+ });
180
+ }
181
+ evaluateConcatLazy(node, evaluator, k) {
182
+ const ctx = this.context;
183
+ return evaluator.evaluate(node.left, (left) => {
184
+ const capturedEnv = ctx.clone();
185
+ return k(createMemoizedStream(function* () {
186
+ if (Array.isArray(left))
187
+ yield* left;
188
+ else if (typeof left === "string")
189
+ yield* left.split("");
190
+ else if (isLazyList(left))
191
+ yield* left.generator();
192
+ else
193
+ throw new Error("Invalid left operand for lazy Concat");
194
+ // right evaluates lazily on demand
195
+ const prevEnv = ctx.env;
196
+ ctx.setEnv(capturedEnv);
197
+ let right;
198
+ try {
199
+ right = trampoline(evaluator.evaluate(node.right, idContinuation));
200
+ }
201
+ finally {
202
+ ctx.setEnv(prevEnv);
203
+ }
204
+ if (Array.isArray(right))
205
+ yield* right;
206
+ else if (typeof right === "string")
207
+ yield* right.split("");
208
+ else if (isLazyList(right))
209
+ yield* right.generator();
210
+ else
211
+ throw new Error("Invalid right operand for lazy Concat");
212
+ }));
213
+ });
214
+ }
215
+ deepEqual(a, b, k) {
216
+ if (a === b)
217
+ return k(true);
218
+ const aIsListLike = this.isListLike(a);
219
+ const bIsListLike = this.isListLike(b);
220
+ const eitherIsCollection = this.isCollection(a) || this.isCollection(b);
221
+ if (eitherIsCollection && aIsListLike && bIsListLike) {
222
+ return this.realizeList(a, (valA) => () => this.realizeList(b, (valB) => {
223
+ if (valA.length !== valB.length)
224
+ return k(false);
225
+ return this.deepEqualCollection(valA, valB, 0, k);
226
+ }));
227
+ }
228
+ if (eitherIsCollection)
229
+ return k(false); // colección vs número → false
230
+ if (this.isPlainObject(a) && this.isPlainObject(b))
231
+ return this.deepEqualObject(a, b, k);
232
+ return k(a == b); // primitivos: number, boolean, string==number
233
+ }
234
+ isPlainObject(val) {
235
+ return val !== null && typeof val === "object"
236
+ && !isLazyList(val) && !Array.isArray(val);
237
+ }
238
+ isCollection(val) {
239
+ return Array.isArray(val) || isLazyList(val);
240
+ }
241
+ isListLike(val) {
242
+ return Array.isArray(val) || isLazyList(val) || typeof val === "string";
243
+ }
244
+ deepEqualCollection(a, b, index, k) {
245
+ if (index >= a.length)
246
+ return k(true);
247
+ return this.deepEqual(a[index], b[index], (eq) => {
248
+ if (!eq)
249
+ return k(false);
250
+ return () => this.deepEqualCollection(a, b, index + 1, k);
251
+ });
252
+ }
253
+ deepEqualObject(a, b, k) {
254
+ const keys = Object.keys(a);
255
+ if (keys.length !== Object.keys(b).length)
256
+ return k(false);
257
+ const checkNext = (index) => {
258
+ if (index >= keys.length)
259
+ return k(true);
260
+ const key = keys[index];
261
+ return this.deepEqual(a[key], b[key], (eq) => {
262
+ if (!eq)
263
+ return k(false);
264
+ return () => checkNext(index + 1);
265
+ });
266
+ };
267
+ return checkNext(0);
268
+ }
269
+ }
@@ -0,0 +1,35 @@
1
+ import { PrimitiveValue, RuntimeFunction, RuntimeObject, EnvStack } from "yukigo-ast";
2
+ import { Continuation, Thunk } from "../../trampoline.js";
3
+ import { RuntimeContext } from "../RuntimeContext.js";
4
+ export declare class ObjectRuntime {
5
+ private context;
6
+ constructor(context: RuntimeContext);
7
+ /**
8
+ * Creates a new instance of an Object.
9
+ * Typically called by visitNew()
10
+ */
11
+ instantiate(className: string, identifier: string, fieldDefinitions: Map<string, PrimitiveValue>, methodDefinitions: Map<string, RuntimeFunction>): RuntimeObject;
12
+ /**
13
+ * Handles Method Calls (Message Passing).
14
+ * Reuses FunctionRuntime to execute the method body.
15
+ */
16
+ dispatch(receiver: PrimitiveValue, methodName: string, args: PrimitiveValue[], env: EnvStack, k: Continuation<PrimitiveValue>): Thunk<PrimitiveValue>;
17
+ /**
18
+ * Handles calls to super() or super.method()
19
+ */
20
+ dispatchSuper(currentEnv: EnvStack, methodName: string, args: PrimitiveValue[], k: Continuation<PrimitiveValue>): Thunk<PrimitiveValue>;
21
+ private createDispatchScope;
22
+ private getResolutionChain;
23
+ private expandClassHierarchy;
24
+ private findMethodInChain;
25
+ /**
26
+ * Field Access (Get)
27
+ * e.g. self.myField
28
+ */
29
+ getField(receiver: PrimitiveValue, fieldName: string): PrimitiveValue;
30
+ /**
31
+ * Field Mutation (Set)
32
+ * e.g. self.myField = 10
33
+ */
34
+ setField(receiver: PrimitiveValue, fieldName: string, value: PrimitiveValue): PrimitiveValue;
35
+ }
@@ -0,0 +1,126 @@
1
+ import { isRuntimeObject, isRuntimeClass, } from "yukigo-ast";
2
+ import { InterpreterError } from "../../errors.js";
3
+ export class ObjectRuntime {
4
+ context;
5
+ constructor(context) {
6
+ this.context = context;
7
+ }
8
+ /**
9
+ * Creates a new instance of an Object.
10
+ * Typically called by visitNew()
11
+ */
12
+ instantiate(className, identifier, fieldDefinitions, methodDefinitions) {
13
+ return {
14
+ type: "Object",
15
+ className,
16
+ identifier,
17
+ fields: new Map(fieldDefinitions),
18
+ methods: methodDefinitions,
19
+ };
20
+ }
21
+ /**
22
+ * Handles Method Calls (Message Passing).
23
+ * Reuses FunctionRuntime to execute the method body.
24
+ */
25
+ dispatch(receiver, methodName, args, env, k) {
26
+ if (!isRuntimeObject(receiver))
27
+ throw new Error(`${receiver} is not an object`);
28
+ const chain = this.getResolutionChain(receiver, env);
29
+ const match = this.findMethodInChain(chain, methodName);
30
+ if (!match)
31
+ throw new InterpreterError("MethodDispatch", `${receiver.className} does not understand '${methodName}'.`);
32
+ const objectScope = this.createDispatchScope(receiver, match, methodName);
33
+ this.context.pushEnv(objectScope);
34
+ return this.context.funcRuntime.apply(match.method, args, k);
35
+ }
36
+ /**
37
+ * Handles calls to super() or super.method()
38
+ */
39
+ dispatchSuper(currentEnv, methodName, args, k) {
40
+ const self = this.context.lookup("self");
41
+ const currentHolder = this.context.lookup("__CONTEXT_CLASS__");
42
+ const currentMethodName = this.context.lookup("__METHOD_NAME__");
43
+ const targetMethodName = methodName || currentMethodName;
44
+ if (!self || !currentHolder)
45
+ throw new InterpreterError("SuperError", "'super' used outside of a method context");
46
+ const chain = this.getResolutionChain(self, currentEnv);
47
+ const currentIndex = chain.findIndex((c) => c === currentHolder);
48
+ if (currentIndex === -1)
49
+ throw new Error("Fatal: Execution context not found in hierarchy chain");
50
+ const remainingChain = chain.slice(currentIndex + 1);
51
+ const match = this.findMethodInChain(remainingChain, methodName);
52
+ if (!match)
53
+ throw new InterpreterError("Super", `Super method '${methodName}' not found`);
54
+ const objectScope = this.createDispatchScope(self, match, targetMethodName);
55
+ this.context.pushEnv(objectScope);
56
+ return this.context.funcRuntime.apply(match.method, args, k);
57
+ }
58
+ createDispatchScope(self, match, targetName) {
59
+ const objectScope = new Map();
60
+ objectScope.set("self", self);
61
+ objectScope.set("__CONTEXT_CLASS__", match.holder);
62
+ objectScope.set("__METHOD_NAME__", targetName);
63
+ for (const [key, val] of self.fields)
64
+ objectScope.set(key, val);
65
+ return objectScope;
66
+ }
67
+ getResolutionChain(receiver, env) {
68
+ const chain = [];
69
+ chain.push(receiver);
70
+ if (receiver.className) {
71
+ this.expandClassHierarchy(receiver.className, env, chain);
72
+ }
73
+ return chain;
74
+ }
75
+ expandClassHierarchy(className, env, chain) {
76
+ const classDef = this.context.lookup(className);
77
+ if (!isRuntimeClass(classDef))
78
+ throw new InterpreterError("expandClassHierarchy", "classDef was expected to be a RuntimeClass");
79
+ chain.push(classDef);
80
+ const classDefCopy = [...classDef.mixins];
81
+ if (classDef.mixins)
82
+ classDefCopy.reverse().forEach((mixinName) => {
83
+ this.expandClassHierarchy(mixinName, env, chain);
84
+ });
85
+ if (classDef.superclass)
86
+ this.expandClassHierarchy(classDef.superclass, env, chain);
87
+ }
88
+ findMethodInChain(chain, methodName) {
89
+ for (const link of chain) {
90
+ if (link.methods.has(methodName))
91
+ return {
92
+ method: link.methods.get(methodName),
93
+ holder: link,
94
+ };
95
+ }
96
+ return undefined;
97
+ }
98
+ /**
99
+ * Field Access (Get)
100
+ * e.g. self.myField
101
+ */
102
+ getField(receiver, fieldName) {
103
+ if (!isRuntimeObject(receiver))
104
+ throw new InterpreterError("FieldAccess", "Target is not an object");
105
+ if (!receiver.fields.has(fieldName)) {
106
+ if (receiver.methods.has(fieldName))
107
+ return receiver.methods.get(fieldName);
108
+ throw new InterpreterError("FieldAccess", `Field '${fieldName}' not found in ${receiver.className}`);
109
+ }
110
+ return receiver.fields.get(fieldName);
111
+ }
112
+ /**
113
+ * Field Mutation (Set)
114
+ * e.g. self.myField = 10
115
+ */
116
+ setField(receiver, fieldName, value) {
117
+ if (!this.context.config.mutability)
118
+ throw new InterpreterError("FieldAssignment", `Cannot mutate field '${fieldName}': mutability is disabled`);
119
+ if (!isRuntimeObject(receiver))
120
+ throw new InterpreterError("FieldAssignment", "Target is not an object");
121
+ if (!receiver.fields.has(fieldName))
122
+ throw new InterpreterError("FieldAssignment", `Cannot set unknown field '${fieldName}'`);
123
+ receiver.fields.set(fieldName, value);
124
+ return value;
125
+ }
126
+ }