yukigo 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (78) hide show
  1. package/.mocharc.json +4 -0
  2. package/CHANGELOG.md +6 -0
  3. package/README.md +199 -0
  4. package/dist/analyzer/index.d.ts +71 -0
  5. package/dist/analyzer/index.js +110 -0
  6. package/dist/analyzer/inspections/functional.d.ts +46 -0
  7. package/dist/analyzer/inspections/functional.js +123 -0
  8. package/dist/analyzer/inspections/generic.d.ts +151 -0
  9. package/dist/analyzer/inspections/generic.js +427 -0
  10. package/dist/analyzer/inspections/imperative.d.ts +37 -0
  11. package/dist/analyzer/inspections/imperative.js +105 -0
  12. package/dist/analyzer/inspections/logic.d.ts +49 -0
  13. package/dist/analyzer/inspections/logic.js +140 -0
  14. package/dist/analyzer/inspections/object.d.ts +83 -0
  15. package/dist/analyzer/inspections/object.js +235 -0
  16. package/dist/analyzer/utils.d.ts +4 -0
  17. package/dist/analyzer/utils.js +16 -0
  18. package/dist/index.d.ts +3 -0
  19. package/dist/index.js +3 -0
  20. package/dist/interpreter/components/EnvBuilder.d.ts +16 -0
  21. package/dist/interpreter/components/EnvBuilder.js +78 -0
  22. package/dist/interpreter/components/FunctionRuntime.d.ts +8 -0
  23. package/dist/interpreter/components/FunctionRuntime.js +52 -0
  24. package/dist/interpreter/components/LazyRuntime.d.ts +7 -0
  25. package/dist/interpreter/components/LazyRuntime.js +75 -0
  26. package/dist/interpreter/components/LogicEngine.d.ts +21 -0
  27. package/dist/interpreter/components/LogicEngine.js +152 -0
  28. package/dist/interpreter/components/LogicResolver.d.ts +11 -0
  29. package/dist/interpreter/components/LogicResolver.js +87 -0
  30. package/dist/interpreter/components/Operations.d.ts +14 -0
  31. package/dist/interpreter/components/Operations.js +69 -0
  32. package/dist/interpreter/components/PatternMatcher.d.ts +41 -0
  33. package/dist/interpreter/components/PatternMatcher.js +206 -0
  34. package/dist/interpreter/components/Visitor.d.ts +64 -0
  35. package/dist/interpreter/components/Visitor.js +299 -0
  36. package/dist/interpreter/errors.d.ts +19 -0
  37. package/dist/interpreter/errors.js +36 -0
  38. package/dist/interpreter/index.d.ts +32 -0
  39. package/dist/interpreter/index.js +44 -0
  40. package/dist/interpreter/utils.d.ts +14 -0
  41. package/dist/interpreter/utils.js +57 -0
  42. package/dist/utils/helpers.d.ts +14 -0
  43. package/dist/utils/helpers.js +51 -0
  44. package/package.json +30 -0
  45. package/src/analyzer/index.ts +132 -0
  46. package/src/analyzer/inspections/functional.ts +159 -0
  47. package/src/analyzer/inspections/generic.ts +499 -0
  48. package/src/analyzer/inspections/imperative.ts +129 -0
  49. package/src/analyzer/inspections/logic.ts +166 -0
  50. package/src/analyzer/inspections/object.ts +282 -0
  51. package/src/analyzer/utils.ts +26 -0
  52. package/src/index.ts +3 -0
  53. package/src/interpreter/components/EnvBuilder.ts +97 -0
  54. package/src/interpreter/components/FunctionRuntime.ts +79 -0
  55. package/src/interpreter/components/LazyRuntime.ts +97 -0
  56. package/src/interpreter/components/LogicEngine.ts +227 -0
  57. package/src/interpreter/components/LogicResolver.ts +130 -0
  58. package/src/interpreter/components/Operations.ts +81 -0
  59. package/src/interpreter/components/PatternMatcher.ts +254 -0
  60. package/src/interpreter/components/Visitor.ts +493 -0
  61. package/src/interpreter/errors.ts +47 -0
  62. package/src/interpreter/index.ts +59 -0
  63. package/src/interpreter/utils.ts +79 -0
  64. package/src/utils/helpers.ts +73 -0
  65. package/tests/analyzer/functional.spec.ts +221 -0
  66. package/tests/analyzer/generic.spec.ts +100 -0
  67. package/tests/analyzer/helpers.spec.ts +83 -0
  68. package/tests/analyzer/logic.spec.ts +292 -0
  69. package/tests/analyzer/oop.spec.ts +338 -0
  70. package/tests/interpreter/EnvBuilder.spec.ts +178 -0
  71. package/tests/interpreter/FunctionRuntime.spec.ts +234 -0
  72. package/tests/interpreter/LazyRuntime.spec.ts +190 -0
  73. package/tests/interpreter/LogicEngine.spec.ts +194 -0
  74. package/tests/interpreter/Operations.spec.ts +220 -0
  75. package/tests/interpreter/PatternSystem.spec.ts +189 -0
  76. package/tests/interpreter/interpreter.spec.ts +937 -0
  77. package/tsconfig.build.json +7 -0
  78. package/tsconfig.json +17 -0
@@ -0,0 +1,178 @@
1
+ import { expect } from 'chai';
2
+ import {
3
+ Function as AstFunction,
4
+ Fact,
5
+ Rule,
6
+ SymbolPrimitive,
7
+ Equation,
8
+ } from 'yukigo-ast';
9
+ import { EnvBuilderVisitor } from '../../src/interpreter/components/EnvBuilder.js';
10
+
11
+ const id = (val: string) => ({ value: val } as SymbolPrimitive);
12
+
13
+ const makeEq = (arity: number): Equation => ({
14
+ patterns: new Array(arity).fill({ type: 'MockPattern' }), // Relleno dummy
15
+ body: { type: 'MockBody' } as any
16
+ } as any);
17
+
18
+ const makeFunc = (name: string, arity: number, eqCount: number = 1): AstFunction => ({
19
+ type: 'Function',
20
+ identifier: id(name),
21
+ equations: new Array(eqCount).fill(null).map(() => makeEq(arity)),
22
+ accept: (v: any) => v.visitFunction(makeFunc(name, arity, eqCount))
23
+ } as any);
24
+
25
+ const makeFact = (name: string): Fact => ({
26
+ type: 'Fact',
27
+ identifier: id(name),
28
+ patterns: [],
29
+ accept: (v: any) => v.visitFact(makeFact(name))
30
+ } as any);
31
+
32
+ const makeRule = (name: string): Rule => ({
33
+ type: 'Rule',
34
+ identifier: id(name),
35
+ patterns: [],
36
+ expressions: [],
37
+ accept: (v: any) => v.visitRule(makeRule(name))
38
+ } as any);
39
+
40
+ describe('EnvBuilderVisitor', () => {
41
+ let visitor: EnvBuilderVisitor;
42
+ let env: Map<string, any>;
43
+
44
+ beforeEach(() => {
45
+ const initialStack = [new Map<string, any>()];
46
+ visitor = new EnvBuilderVisitor(initialStack);
47
+ env = initialStack[0];
48
+ });
49
+
50
+ describe('Function Declarations', () => {
51
+ it('should register a valid function', () => {
52
+ const funcNode = makeFunc('myFunc', 1);
53
+
54
+ visitor.visitFunction(funcNode);
55
+
56
+ expect(env.has('myFunc')).to.be.true;
57
+ const entry = env.get('myFunc');
58
+
59
+ expect(entry).to.have.property('identifier', 'myFunc');
60
+ expect(entry).to.have.property('arity', 1);
61
+ expect(entry.equations).to.have.lengthOf(1);
62
+ });
63
+
64
+ it('should register a function with multiple equations', () => {
65
+ const funcNode = makeFunc('fib', 1, 2);
66
+
67
+ visitor.visitFunction(funcNode);
68
+
69
+ const entry = env.get('fib');
70
+ expect(entry.equations).to.have.lengthOf(2);
71
+ });
72
+
73
+ it('should throw error if function has no equations', () => {
74
+ const funcNode = makeFunc('empty', 0, 0);
75
+
76
+ expect(() => visitor.visitFunction(funcNode))
77
+ .to.throw(/has no equations/);
78
+ });
79
+
80
+ it('should throw error if equations have different arity', () => {
81
+ const funcNode = makeFunc('badFunc', 1);
82
+ funcNode.equations.push(makeEq(2));
83
+
84
+ expect(() => visitor.visitFunction(funcNode))
85
+ .to.throw(/must have the same arity/);
86
+ });
87
+ });
88
+
89
+ describe('Logic Programming (Facts)', () => {
90
+ it('should register a new Fact', () => {
91
+ const factNode = makeFact('parent');
92
+
93
+ visitor.visitFact(factNode);
94
+
95
+ expect(env.has('parent')).to.be.true;
96
+ const entry = env.get('parent');
97
+ expect(entry).to.have.property('kind', 'Fact');
98
+ expect(entry.equations).to.have.lengthOf(1);
99
+ expect(entry.equations[0]).to.equal(factNode);
100
+ });
101
+
102
+ it('should append to existing Fact if identifier exists', () => {
103
+ const fact1 = makeFact('parent');
104
+ const fact2 = makeFact('parent');
105
+
106
+ visitor.visitFact(fact1);
107
+ visitor.visitFact(fact2);
108
+
109
+ const entry = env.get('parent');
110
+ expect(entry.kind).to.equal('Fact');
111
+ expect(entry.equations).to.have.lengthOf(2);
112
+ expect(entry.equations[0]).to.equal(fact1);
113
+ expect(entry.equations[1]).to.equal(fact2);
114
+ });
115
+
116
+ it('should overwrite existing entry if it is not a Fact', () => {
117
+ env.set('test', { kind: 'SomethingElse', equations: [] });
118
+
119
+ const fact = makeFact('test');
120
+ visitor.visitFact(fact);
121
+
122
+ const entry = env.get('test');
123
+ expect(entry.kind).to.equal('Fact');
124
+ expect(entry.equations).to.have.lengthOf(1);
125
+ });
126
+ });
127
+
128
+ describe('Logic Programming (Rules)', () => {
129
+ it('should register a new Rule', () => {
130
+ const ruleNode = makeRule('grandparent');
131
+
132
+ visitor.visitRule(ruleNode);
133
+
134
+ expect(env.has('grandparent')).to.be.true;
135
+ const entry = env.get('grandparent');
136
+ expect(entry).to.have.property('kind', 'Rule');
137
+ expect(entry.equations).to.have.lengthOf(1);
138
+ });
139
+
140
+ it('should append to existing Rule group', () => {
141
+ const rule1 = makeRule('ancestor');
142
+ const rule2 = makeRule('ancestor');
143
+
144
+ visitor.visitRule(rule1);
145
+ visitor.visitRule(rule2);
146
+
147
+ const entry = env.get('ancestor');
148
+ expect(entry.kind).to.equal('Rule');
149
+ expect(entry.equations).to.have.lengthOf(2);
150
+ });
151
+ });
152
+
153
+ describe('Build Method (Integration)', () => {
154
+ it('should traverse the AST and build the complete environment', () => {
155
+ const nodes = [
156
+ makeFunc('add', 2),
157
+ makeFact('is_human'),
158
+ makeRule('is_mortal')
159
+ ];
160
+
161
+ const mockAST = {
162
+ [Symbol.iterator]: function* () { yield* nodes; }
163
+ } as any;
164
+
165
+ // mock of the accept method
166
+ nodes[0].accept = (v: any) => v.visitFunction(nodes[0]);
167
+ nodes[1].accept = (v: any) => v.visitFact(nodes[1]);
168
+ nodes[2].accept = (v: any) => v.visitRule(nodes[2]);
169
+
170
+ const resultingStack = visitor.build(mockAST);
171
+ const resultingEnv = resultingStack[0];
172
+
173
+ expect(resultingEnv.has('add')).to.be.true;
174
+ expect(resultingEnv.has('is_human')).to.be.true;
175
+ expect(resultingEnv.has('is_mortal')).to.be.true;
176
+ });
177
+ });
178
+ });
@@ -0,0 +1,234 @@
1
+ import { expect } from "chai";
2
+ import {
3
+ EquationRuntime,
4
+ UnguardedBody,
5
+ Sequence,
6
+ Return,
7
+ SymbolPrimitive,
8
+ NumberPrimitive,
9
+ LiteralPattern,
10
+ VariablePattern,
11
+ GuardedBody,
12
+ } from "yukigo-ast";
13
+ import { FunctionRuntime } from "../../src/interpreter/components/FunctionRuntime.js";
14
+
15
+ const s = (val: string) => new SymbolPrimitive(val);
16
+ const n = (val: number) => new NumberPrimitive(val);
17
+ const litPat = (val: number | string) =>
18
+ new LiteralPattern(typeof val === "number" ? n(val) : s(val));
19
+ const varPat = (name: string) => new VariablePattern(s(name));
20
+ const valExpr = (val: any) => ({ type: "Value", value: val } as any);
21
+ const varExpr = (name: string) => ({ type: "Variable", value: name } as any);
22
+ const seq = (stmts: any[]) => new Sequence(stmts);
23
+ const unguarded = (stmts: any[]) => new UnguardedBody(seq(stmts));
24
+ const guarded = (guards: { cond: any; body: any }[]): GuardedBody[] => {
25
+ return guards.map((g) => new GuardedBody(g.cond, valExpr(g.body)));
26
+ };
27
+
28
+ class MockEvaluator {
29
+ constructor(public env: any[]) {}
30
+
31
+ evaluate(node: any): any {
32
+ if (node.type === "Value") return node.value;
33
+ if (node.type === "Variable") {
34
+ const val = this.env[0].get(node.value);
35
+ return val !== undefined ? val : `Error: ${node.value} not found`;
36
+ }
37
+ return node;
38
+ }
39
+ }
40
+
41
+ describe("FunctionRuntime", () => {
42
+ let globalEnv: any[];
43
+ let evaluatorFactory: any;
44
+
45
+ beforeEach(() => {
46
+ globalEnv = [new Map()];
47
+ evaluatorFactory = (env: any[]) => new MockEvaluator(env);
48
+ });
49
+
50
+ describe("Pattern Matching & Dispatch", () => {
51
+ it("should match arguments to literal patterns", () => {
52
+ const eq1: EquationRuntime = {
53
+ patterns: [litPat(10)],
54
+ body: unguarded([valExpr("ten")]),
55
+ };
56
+ const eq2: EquationRuntime = {
57
+ patterns: [litPat(20)],
58
+ body: unguarded([valExpr("twenty")]),
59
+ };
60
+
61
+ const result = FunctionRuntime.apply(
62
+ "f",
63
+ [eq1, eq2],
64
+ [20],
65
+ globalEnv,
66
+ evaluatorFactory
67
+ );
68
+
69
+ expect(result).to.equal("twenty");
70
+ });
71
+
72
+ it("should throw error if no pattern matches (Non-exhaustive)", () => {
73
+ const eq1: EquationRuntime = {
74
+ patterns: [litPat(10)],
75
+ body: unguarded([valExpr("ten")]),
76
+ };
77
+
78
+ expect(() => {
79
+ FunctionRuntime.apply("f", [eq1], [99], globalEnv, evaluatorFactory);
80
+ }).to.throw(/Non-exhaustive patterns/);
81
+ });
82
+
83
+ it("should skip equations with wrong arity (argument count)", () => {
84
+ const eq1: EquationRuntime = {
85
+ patterns: [varPat("X")],
86
+ body: unguarded([valExpr("one arg")]),
87
+ };
88
+
89
+ expect(() => {
90
+ FunctionRuntime.apply("f", [eq1], [1, 2], globalEnv, evaluatorFactory);
91
+ }).to.throw(/Non-exhaustive patterns/);
92
+ });
93
+ });
94
+
95
+ describe("Scope & Bindings", () => {
96
+ it("should bind variables to a new local scope", () => {
97
+ const eq1: EquationRuntime = {
98
+ patterns: [varPat("X")],
99
+ body: unguarded([varExpr("X")]),
100
+ };
101
+
102
+ const result = FunctionRuntime.apply(
103
+ "identity",
104
+ [eq1],
105
+ [500],
106
+ globalEnv,
107
+ evaluatorFactory
108
+ );
109
+
110
+ expect(result).to.equal(500);
111
+ });
112
+
113
+ it("should prioritize local scope over global scope", () => {
114
+ globalEnv[0].set("X", 1);
115
+
116
+ const eq1: EquationRuntime = {
117
+ patterns: [varPat("X")],
118
+ body: unguarded([varExpr("X")]),
119
+ };
120
+
121
+ const result = FunctionRuntime.apply(
122
+ "shadow",
123
+ [eq1],
124
+ [999],
125
+ globalEnv,
126
+ evaluatorFactory
127
+ );
128
+ expect(result).to.equal(999);
129
+ });
130
+ });
131
+
132
+ describe("Guarded Bodies", () => {
133
+ const guardedEvaluatorFactory = (env: any[]) => ({
134
+ evaluate: (node: any) => {
135
+ if (node.type === "Condition") return node.value;
136
+ if (node.type === "Value") return node.value;
137
+ return null;
138
+ },
139
+ });
140
+
141
+ it("should execute the body of the first true guard", () => {
142
+ const guards = [
143
+ { cond: { type: "Condition", value: false }, body: 1 },
144
+ { cond: { type: "Condition", value: true }, body: 2 },
145
+ ];
146
+
147
+ const eq: EquationRuntime = {
148
+ patterns: [varPat("_")],
149
+ body: guarded(guards),
150
+ };
151
+
152
+ const result = FunctionRuntime.apply(
153
+ "guards",
154
+ [eq],
155
+ [0],
156
+ globalEnv,
157
+ guardedEvaluatorFactory as any
158
+ );
159
+ expect(result).to.equal(2);
160
+ });
161
+
162
+ it("should fall through to next equation if no guard matches", () => {
163
+ const eq1: EquationRuntime = {
164
+ patterns: [varPat("_")],
165
+ body: guarded([{ cond: { type: "Condition", value: false }, body: 1 }]),
166
+ };
167
+
168
+ const eq2: EquationRuntime = {
169
+ patterns: [varPat("_")],
170
+ body: unguarded([valExpr(2)]),
171
+ };
172
+
173
+ const result = FunctionRuntime.apply(
174
+ "fallback",
175
+ [eq1, eq2],
176
+ [0],
177
+ globalEnv,
178
+ guardedEvaluatorFactory as any
179
+ );
180
+ expect(result).to.equal(2);
181
+ });
182
+ });
183
+
184
+ describe("Imperative Sequences & Returns", () => {
185
+ it("should return the value of the last statement implicitly", () => {
186
+ const eq: EquationRuntime = {
187
+ patterns: [],
188
+ body: unguarded([valExpr(10), valExpr(20), valExpr(30)]),
189
+ };
190
+
191
+ const result = FunctionRuntime.apply(
192
+ "seq",
193
+ [eq],
194
+ [],
195
+ globalEnv,
196
+ evaluatorFactory
197
+ );
198
+ expect(result).to.equal(30);
199
+ });
200
+
201
+ it("should return early with Return statement", () => {
202
+ const retStmt = new Return(valExpr(99));
203
+
204
+ const eq: EquationRuntime = {
205
+ patterns: [],
206
+ body: unguarded([valExpr(10), retStmt, valExpr(30)]),
207
+ };
208
+
209
+ const result = FunctionRuntime.apply(
210
+ "earlyRet",
211
+ [eq],
212
+ [],
213
+ globalEnv,
214
+ evaluatorFactory
215
+ );
216
+ expect(result).to.equal(99);
217
+ });
218
+
219
+ it("should return undefined for empty sequence", () => {
220
+ const eq: EquationRuntime = {
221
+ patterns: [],
222
+ body: unguarded([]),
223
+ };
224
+ const result = FunctionRuntime.apply(
225
+ "empty",
226
+ [eq],
227
+ [],
228
+ globalEnv,
229
+ evaluatorFactory
230
+ );
231
+ expect(result).to.be.undefined;
232
+ });
233
+ });
234
+ });
@@ -0,0 +1,190 @@
1
+ import { expect } from "chai";
2
+ import { LazyRuntime } from "../../src/interpreter/components/LazyRuntime.js";
3
+ import {
4
+ RangeExpression,
5
+ ConsExpression,
6
+ LazyList,
7
+ NumberPrimitive,
8
+ } from "yukigo-ast";
9
+ import { createStream } from "../../src/interpreter/utils.js";
10
+
11
+ class MockEvaluator {
12
+ evaluate(node: any): any {
13
+ if (node && typeof node === "object" && "value" in node) {
14
+ return node.value;
15
+ }
16
+ return node;
17
+ }
18
+ }
19
+
20
+ const num = (value: number) => new NumberPrimitive(value);
21
+
22
+ const range = (start: number, end?: number, step?: number): RangeExpression =>
23
+ new RangeExpression(
24
+ num(start),
25
+ end ? num(end) : undefined,
26
+ step ? num(step) : undefined
27
+ );
28
+
29
+ // Helper para crear nodos Cons
30
+ const cons = (headVal: any, tailVal: any): ConsExpression =>
31
+ new ConsExpression(headVal, tailVal);
32
+
33
+ describe("LazyRuntime", () => {
34
+ let evaluator: any;
35
+
36
+ beforeEach(() => {
37
+ evaluator = new MockEvaluator();
38
+ });
39
+
40
+ describe("realizeList", () => {
41
+ it("should return the array as-is if input is already an array", () => {
42
+ const input = [1, 2, 3];
43
+ const result = LazyRuntime.realizeList(input);
44
+ expect(result).to.equal(input);
45
+ expect(result).to.deep.equal([1, 2, 3]);
46
+ });
47
+
48
+ it("should consume a LazyList into an array", () => {
49
+ const lazyList: LazyList = createStream(function* () {
50
+ yield 10;
51
+ yield 20;
52
+ });
53
+
54
+ const result = LazyRuntime.realizeList(lazyList);
55
+ expect(result).to.deep.equal([10, 20]);
56
+ });
57
+
58
+ it("should throw if value is not a list or lazy list", () => {
59
+ expect(() => LazyRuntime.realizeList(123 as any)).to.throw(
60
+ /Expected List or LazyList/
61
+ );
62
+ });
63
+ });
64
+
65
+ describe("evaluateRange", () => {
66
+ describe("Finite Ranges", () => {
67
+ it("should create a simple range [1..5]", () => {
68
+ const node = range(1, 5);
69
+ const result = LazyRuntime.evaluateRange(node, evaluator, {
70
+ lazyLoading: false,
71
+ });
72
+ expect(result).to.deep.equal([1, 2, 3, 4, 5]);
73
+ });
74
+
75
+ it("should handle custom steps [0, 2 .. 10]", () => {
76
+ const node = range(0, 10, 2);
77
+ const result = LazyRuntime.evaluateRange(node, evaluator, {
78
+ lazyLoading: false,
79
+ });
80
+ expect(result).to.deep.equal([0, 2, 4, 6, 8, 10]);
81
+ });
82
+
83
+ it("should handle negative steps [5, 4 .. 1]", () => {
84
+ const node = range(5, 1, 4);
85
+ const result = LazyRuntime.evaluateRange(node, evaluator, {
86
+ lazyLoading: false,
87
+ });
88
+ expect(result).to.deep.equal([5, 4, 3, 2, 1]);
89
+ });
90
+
91
+ it("should throw if step is zero", () => {
92
+ const node = range(5, 10, 5);
93
+ expect(() => LazyRuntime.evaluateRange(node, evaluator, {})).to.throw(
94
+ /Range step cannot be zero/
95
+ );
96
+ });
97
+ });
98
+
99
+ describe("Infinite Ranges", () => {
100
+ it("should return a LazyList object", () => {
101
+ const node = range(1);
102
+ const result = LazyRuntime.evaluateRange(node, evaluator, {
103
+ lazyLoading: true,
104
+ });
105
+
106
+ expect(result).to.have.property("type", "LazyList");
107
+ expect(result).to.have.property("generator");
108
+ });
109
+
110
+ it("should generate values on demand [1..]", () => {
111
+ const node = range(1);
112
+ const result = LazyRuntime.evaluateRange(node, evaluator, {
113
+ lazyLoading: true,
114
+ }) as LazyList;
115
+ const gen = result.generator();
116
+ expect(gen.next().value).to.equal(1);
117
+ expect(gen.next().value).to.equal(2);
118
+ expect(gen.next().value).to.equal(3);
119
+ });
120
+
121
+ it("should generate values with step on demand [0, 5 ..]", () => {
122
+ const node = range(0, undefined, 5);
123
+ const result = LazyRuntime.evaluateRange(node, evaluator, {
124
+ lazyLoading: true,
125
+ }) as LazyList;
126
+
127
+ const gen = result.generator();
128
+ expect(gen.next().value).to.equal(0);
129
+ expect(gen.next().value).to.equal(5);
130
+ expect(gen.next().value).to.equal(10);
131
+ });
132
+ });
133
+ });
134
+
135
+ describe("evaluateCons", () => {
136
+ describe("Eager Mode (lazy: false)", () => {
137
+ it("should construct an array if tail is an array", () => {
138
+ const node = cons(1, [2, 3]);
139
+ const result = LazyRuntime.evaluateCons(node, evaluator, false);
140
+ expect(result).to.deep.equal([1, 2, 3]);
141
+ });
142
+
143
+ it("should throw if tail is not an array", () => {
144
+ const lazyListMock = { type: "LazyList" };
145
+ const node = cons(1, lazyListMock);
146
+
147
+ expect(() => LazyRuntime.evaluateCons(node, evaluator, false)).to.throw(
148
+ /Expected Array in eager Cons/
149
+ );
150
+ });
151
+ });
152
+
153
+ describe("Lazy Mode (lazy: true)", () => {
154
+ it("should return an array if tail is an array (hybrid)", () => {
155
+ const node = cons(1, [2, 3]);
156
+ const result = LazyRuntime.evaluateCons(node, evaluator, true);
157
+ expect(result).to.deep.equal([1, 2, 3]);
158
+ });
159
+
160
+ it("should return a new LazyList if tail is a LazyList", () => {
161
+ const tailLazy: LazyList = createStream(function* () {
162
+ yield 2;
163
+ yield 3;
164
+ });
165
+
166
+ const node = cons(1, tailLazy);
167
+ const result = LazyRuntime.evaluateCons(
168
+ node,
169
+ evaluator,
170
+ true
171
+ ) as LazyList;
172
+
173
+ expect(result.type).to.equal("LazyList");
174
+
175
+ const gen = result.generator();
176
+ expect(gen.next().value).to.equal(1);
177
+ expect(gen.next().value).to.equal(2);
178
+ expect(gen.next().value).to.equal(3);
179
+ expect(gen.next().done).to.be.true;
180
+ });
181
+
182
+ it("should throw if tail is invalid", () => {
183
+ const node = cons(1, "invalid string");
184
+ expect(() => LazyRuntime.evaluateCons(node, evaluator, true)).to.throw(
185
+ /Invalid tail type/
186
+ );
187
+ });
188
+ });
189
+ });
190
+ });