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,194 +1,327 @@
1
- import { expect } from "chai";
2
- import {
3
- SymbolPrimitive,
4
- NumberPrimitive,
5
- LiteralPattern,
6
- VariablePattern,
7
- Fact,
8
- Findall,
9
- Goal,
10
- Pattern,
11
- Rule,
12
- RuntimeFact,
13
- RuntimeRule,
14
- LogicResult,
15
- } from "yukigo-ast";
16
- import { ExpressionEvaluator } from "../../src/interpreter/utils.js";
17
- import { LogicEngine } from "../../src/interpreter/components/LogicEngine.js";
18
- import { EnvStack, InterpreterConfig } from "../../src/interpreter/index.js";
19
- import { unify } from "../../src/interpreter/components/LogicResolver.js";
20
- import { PatternResolver } from "../../src/interpreter/components/PatternMatcher.js";
21
-
22
- const s = (val: string) => new SymbolPrimitive(val);
23
- const n = (val: number) => new NumberPrimitive(val);
24
- const lit = (val: string | number) =>
25
- new LiteralPattern(typeof val === "string" ? s(val) : n(val));
26
- const varPat = (name: string) => new VariablePattern(s(name));
27
-
28
- const makeFact = (id: string, args: Pattern[]) => new Fact(s(id), args);
29
- const makeRule = (id: string, args: Pattern[], body: Goal[]) =>
30
- new Rule(s(id), args, body);
31
- const makeGoal = (id: string, args: Pattern[]) => new Goal(s(id), args);
32
-
33
- class MockEvaluator implements ExpressionEvaluator {
34
- evaluate(node: any): any {
35
- if (node instanceof SymbolPrimitive) return node.value;
36
- if (node instanceof NumberPrimitive) return node.value;
37
- if (node instanceof LiteralPattern) return this.evaluate(node.name);
38
- return null;
39
- }
40
- }
41
-
42
- describe("Logic Engine & Unification", () => {
43
- let engine: LogicEngine;
44
- let env: EnvStack;
45
- let evaluator: MockEvaluator;
46
-
47
- beforeEach(() => {
48
- const globalEnv = new Map<string, any>();
49
- env = [globalEnv];
50
- evaluator = new MockEvaluator();
51
-
52
- const config: InterpreterConfig = {
53
- debug: false,
54
- outputMode: "all",
55
- };
56
-
57
- engine = new LogicEngine(env, config, evaluator);
58
- });
59
-
60
- describe("Unification Algorithm", () => {
61
- it("should unify two identical literals", () => {
62
- const p1 = lit("cat");
63
- const p2 = lit("cat");
64
- const result = unify(p1, p2);
65
- expect(result).to.not.be.null;
66
- });
67
-
68
- it("should not unify different literals", () => {
69
- const p1 = lit("cat");
70
- const p2 = lit("dog");
71
- const result = unify(p1, p2);
72
- expect(result).to.be.null;
73
- });
74
-
75
- it("should unify a variable with a literal", () => {
76
- const v1 = varPat("X");
77
- const p2 = lit("cat");
78
- const result = unify(v1, p2);
79
-
80
- expect(result).to.not.be.null;
81
- const resolved = result!.get("X");
82
- expect(resolved).to.be.instanceOf(LiteralPattern);
83
- const resolver = new PatternResolver();
84
- expect(resolved?.accept(resolver)).to.equal("cat");
85
- });
86
-
87
- it("should unify two variables (aliasing)", () => {
88
- const v1 = varPat("X");
89
- const v2 = varPat("Y");
90
- const result = unify(v1, v2);
91
- expect(result).to.not.be.null;
92
- expect(result!.has("X")).to.be.true;
93
- });
94
- });
95
-
96
- describe("LogicEngine Execution", () => {
97
- beforeEach(() => {
98
- const globalEnv = env[0];
99
- const factsParent: RuntimeFact = {
100
- kind: "Fact",
101
- identifier: "parent",
102
- equations: [
103
- makeFact("parent", [lit("zeus"), lit("ares")]),
104
- makeFact("parent", [lit("zeus"), lit("athena")]),
105
- makeFact("parent", [lit("hera"), lit("ares")]),
106
- ],
107
- };
108
- globalEnv.set("parent", factsParent);
109
-
110
- const rulesSibling: RuntimeRule = {
111
- kind: "Rule",
112
- identifier: "sibling",
113
- equations: [
114
- makeRule(
115
- "sibling",
116
- [varPat("X"), varPat("Y")],
117
- [
118
- makeGoal("parent", [varPat("Z"), varPat("X")]),
119
- makeGoal("parent", [varPat("Z"), varPat("Y")]),
120
- ]
121
- ),
122
- ],
123
- };
124
- globalEnv.set("sibling", rulesSibling);
125
- });
126
-
127
- it("should solve a simple ground goal (Fact exists)", () => {
128
- const query = makeGoal("parent", [lit("zeus"), lit("ares")]);
129
- const results = engine.solveGoal(query) as LogicResult[];
130
- expect(results).to.not.be.false;
131
- results.forEach((res) => expect(res.success).to.be.true);
132
- });
133
-
134
- it("should fail a ground goal that does not exist", () => {
135
- const query = makeGoal("parent", [lit("zeus"), lit("thor")]);
136
- const results = engine.solveGoal(query) as LogicResult[];
137
- results.forEach((res) => expect(res.success).to.be.false);
138
- });
139
-
140
- it("should solve a goal with a variable", () => {
141
- const query = makeGoal("parent", [lit("zeus"), varPat("Child")]);
142
- const results = engine.solveGoal(query) as LogicResult[];
143
- results.forEach((res) => expect(res.success).to.be.true);
144
- results.forEach((res) => expect(res.solutions.has("Child")).to.be.true);
145
-
146
- const names = results.map((r) => r.solutions.get("Child"));
147
- expect(names).to.include("ares");
148
- expect(names).to.include("athena");
149
- });
150
-
151
- it("should solve a rule using backtracking", () => {
152
- const query = makeGoal("sibling", [lit("ares"), lit("athena")]);
153
- const results = engine.solveGoal(query) as LogicResult[];
154
- results.forEach((res) => expect(res.success).to.be.true);
155
- });
156
- it("should solve a rule using backtracking with variable", () => {
157
- const query = makeGoal("sibling", [lit("ares"), varPat("Child")]);
158
- const results = engine.solveGoal(query) as LogicResult[];
159
- results.forEach((res) => expect(res.success).to.be.true);
160
- const names = results.map((r) => r.solutions.get("Child"));
161
- expect(names).to.include("ares");
162
- expect(names).to.include("athena");
163
- });
164
- describe("Output Modes", () => {
165
- it('should return all results when outputMode is "all"', () => {
166
- engine = new LogicEngine(env, { outputMode: "all" }, evaluator);
167
- const query = makeGoal("parent", [lit("zeus"), varPat("X")]);
168
-
169
- const results = engine.solveGoal(query) as LogicResult[];
170
- expect(results).to.be.an("array");
171
- expect(results).to.have.lengthOf(2);
172
-
173
- const names = results.map((r) => r.solutions.get("X"));
174
- expect(names).to.include("ares");
175
- expect(names).to.include("athena");
176
- });
177
- });
178
-
179
- describe("Findall", () => {
180
- it("should collect all solutions into a list", () => {
181
- const findallNode = new Findall(
182
- varPat("X"), // Template
183
- makeGoal("parent", [lit("zeus"), varPat("X")]), // Goal
184
- varPat("List") // Bag variable
185
- );
186
- const result = engine.solveFindall(findallNode) as LogicResult[];
187
- expect(Array.isArray(result)).to.be.true;
188
- expect(result).to.have.lengthOf(2);
189
- expect(result).to.include("ares");
190
- expect(result).to.include("athena");
191
- });
192
- });
193
- });
194
- });
1
+ import { expect } from "chai";
2
+ import {
3
+ SymbolPrimitive,
4
+ NumberPrimitive,
5
+ LiteralPattern,
6
+ VariablePattern,
7
+ Fact,
8
+ Findall,
9
+ Goal,
10
+ Pattern,
11
+ Rule,
12
+ LogicResult,
13
+ EnvStack,
14
+ Equation,
15
+ Sequence,
16
+ UnguardedBody,
17
+ Statement,
18
+ Variable,
19
+ ConsPattern,
20
+ ListPattern,
21
+ NilPrimitive,
22
+ LazyList,
23
+ LogicConstraint,
24
+ Expression,
25
+ SuccessLogicResult,
26
+ RuntimePredicate,
27
+ } from "yukigo-ast";
28
+ import {
29
+ createGlobalEnv,
30
+ ExpressionEvaluator,
31
+ } from "../../src/interpreter/utils.js";
32
+ import { LogicEngine } from "../../src/interpreter/components/logic/LogicEngine.js";
33
+ import { unify } from "../../src/interpreter/components/logic/LogicResolver.js";
34
+ import { PatternResolver } from "../../src/interpreter/components/PatternMatcher.js";
35
+ import { InterpreterVisitor } from "../../src/interpreter/components/Visitor.js";
36
+ import {
37
+ idContinuation,
38
+ trampoline,
39
+ } from "../../src/interpreter/trampoline.js";
40
+ import {
41
+ InterpreterConfig,
42
+ RuntimeContext,
43
+ } from "../../src/interpreter/components/RuntimeContext.js";
44
+
45
+ const s = (val: string) => new SymbolPrimitive(val);
46
+ const n = (val: number) => new NumberPrimitive(val);
47
+ const lit = (val: string | number) =>
48
+ new LiteralPattern(typeof val === "string" ? s(val) : n(val));
49
+ const varPat = (name: string) => new VariablePattern(s(name));
50
+
51
+ const makeEq = (args: Pattern[], stmts: Statement[]) =>
52
+ new Equation(args, new UnguardedBody(new Sequence(stmts)));
53
+ const makeFact = (id: string, args: Pattern[]) => new Fact(s(id), args);
54
+ const makeRule = (id: string, body: Equation[]) => new Rule(s(id), body);
55
+ const makeGoal = (id: string, args: Pattern[]) => new Goal(s(id), args);
56
+ const makeConstraint = (expr: Expression) => new LogicConstraint(expr);
57
+
58
+ const factsParent: RuntimePredicate = {
59
+ kind: "Fact",
60
+ identifier: "parent",
61
+ equations: [
62
+ makeFact("parent", [lit("zeus"), lit("ares")]),
63
+ makeFact("parent", [lit("zeus"), lit("athena")]),
64
+ makeFact("parent", [lit("hera"), lit("ares")]),
65
+ ],
66
+ };
67
+ const rulesSibling: RuntimePredicate = {
68
+ kind: "Rule",
69
+ identifier: "sibling",
70
+ equations: [
71
+ makeRule("sibling", [
72
+ makeEq(
73
+ [varPat("X"), varPat("Y")],
74
+ [
75
+ makeConstraint(makeGoal("parent", [varPat("Z"), varPat("X")])),
76
+ makeConstraint(makeGoal("parent", [varPat("Z"), varPat("Y")])),
77
+ ],
78
+ ),
79
+ ]),
80
+ ],
81
+ };
82
+
83
+ const env = createGlobalEnv();
84
+ const context = new RuntimeContext({
85
+ debug: false,
86
+ outputMode: "all",
87
+ });
88
+ context.setEnv(env);
89
+ context.define("sibling", rulesSibling);
90
+ context.define("parent", factsParent);
91
+
92
+ describe("Logic Engine & Unification", () => {
93
+ let engine: LogicEngine;
94
+ let evaluator: ExpressionEvaluator;
95
+
96
+ beforeEach(() => {
97
+ evaluator = new InterpreterVisitor(context);
98
+ engine = new LogicEngine(evaluator, context);
99
+ });
100
+
101
+ describe("Unification Algorithm", () => {
102
+ it("should unify two identical literals", () => {
103
+ const p1 = lit("cat");
104
+ const p2 = lit("cat");
105
+ const result = unify(p1, p2);
106
+ expect(result).to.not.be.null;
107
+ });
108
+
109
+ it("should not unify different literals", () => {
110
+ const p1 = lit("cat");
111
+ const p2 = lit("dog");
112
+ const result = unify(p1, p2);
113
+ expect(result).to.be.null;
114
+ });
115
+
116
+ it("should unify a variable with a literal", () => {
117
+ const v1 = varPat("X");
118
+ const p2 = lit("cat");
119
+ const result = unify(v1, p2);
120
+
121
+ expect(result).to.not.be.null;
122
+ const resolved = result!.get("X");
123
+ expect(resolved).to.be.instanceOf(LiteralPattern);
124
+ const resolver = new PatternResolver();
125
+ expect(resolved?.accept(resolver)).to.equal("cat");
126
+ });
127
+
128
+ it("should unify two variables (aliasing)", () => {
129
+ const v1 = varPat("X");
130
+ const v2 = varPat("Y");
131
+ const result = unify(v1, v2);
132
+ expect(result).to.not.be.null;
133
+ expect(result!.has("X")).to.be.true;
134
+ });
135
+
136
+ describe("ConsPattern Unification", () => {
137
+ it("should unify two identical ConsPatterns", () => {
138
+ const p1 = new ConsPattern(lit(1), new ListPattern([]));
139
+ const p2 = new ConsPattern(lit(1), new ListPattern([]));
140
+ const result = unify(p1, p2);
141
+ expect(result).to.not.be.null;
142
+ });
143
+
144
+ it("should unify ConsPattern with equivalent ListPattern", () => {
145
+ const cons = new ConsPattern(lit(1), new ListPattern([lit(2)]));
146
+ const list = new ListPattern([lit(1), lit(2)]);
147
+ const result = unify(cons, list);
148
+ expect(result).to.not.be.null;
149
+ });
150
+
151
+ it("should unify ListPattern with equivalent ConsPattern", () => {
152
+ const list = new ListPattern([lit(1), lit(2)]);
153
+ const cons = new ConsPattern(lit(1), new ListPattern([lit(2)]));
154
+ const result = unify(list, cons);
155
+ expect(result).to.not.be.null;
156
+ });
157
+
158
+ it("should fail if heads do not match", () => {
159
+ const cons = new ConsPattern(lit(1), new ListPattern([lit(2)]));
160
+ const list = new ListPattern([lit(3), lit(2)]);
161
+ const result = unify(cons, list);
162
+ expect(result).to.be.null;
163
+ });
164
+
165
+ it("should fail if ListPattern is empty and ConsPattern expects head", () => {
166
+ const list = new ListPattern([]);
167
+ const cons = new ConsPattern(varPat("H"), varPat("T"));
168
+ const result = unify(list, cons);
169
+ expect(result).to.be.null;
170
+ });
171
+
172
+ it("should bind variables in ConsPattern", () => {
173
+ const cons = new ConsPattern(varPat("H"), varPat("T"));
174
+ const list = new ListPattern([lit(1), lit(2)]);
175
+ const result = unify(cons, list);
176
+ expect(result).to.not.be.null;
177
+
178
+ const h = result!.get("H");
179
+ expect(h).to.be.instanceOf(LiteralPattern);
180
+
181
+ const t = result!.get("T");
182
+ expect(t).to.be.instanceOf(ListPattern);
183
+ expect((t as ListPattern).elements).to.have.lengthOf(1);
184
+ });
185
+
186
+ it("should unify infinite LazyList with Variable lazily", () => {
187
+ // Create infinite generator
188
+ const gen = function*() {
189
+ let i = 1;
190
+ while(true) yield i++;
191
+ };
192
+ const lazyList: LazyList = {
193
+ type: "LazyList",
194
+ generator: gen
195
+ };
196
+
197
+ // Mock evaluator to return this lazy list for variable "Infinite"
198
+ evaluator.evaluate = (node: any) => {
199
+ if (node instanceof Variable && node.identifier.value === "Infinite") {
200
+ return lazyList;
201
+ }
202
+ if (node instanceof SymbolPrimitive) return node.value;
203
+ return null;
204
+ };
205
+
206
+ // Unify X = Infinite
207
+ const infiniteVar = new Variable(s("Infinite"), new NilPrimitive(null));
208
+ const xVar = new Variable(s("X"), new NilPrimitive(null));
209
+
210
+ const result = trampoline(engine.unifyExpr(xVar, infiniteVar, idContinuation)) as boolean;
211
+ expect(result).to.be.true;
212
+ });
213
+ });
214
+ });
215
+
216
+ describe("LogicEngine Execution", () => {
217
+ it("should solve a simple ground goal (Fact exists)", () => {
218
+ const query = makeGoal("parent", [lit("zeus"), lit("ares")]);
219
+ const results = trampoline(
220
+ engine.solveGoal(query, idContinuation),
221
+ ) as LogicResult[];
222
+ expect(results).to.not.be.false;
223
+ results.forEach((res) => expect(res.success).to.be.true);
224
+ });
225
+
226
+ it("should fail a ground goal that does not exist", () => {
227
+ const query = makeGoal("parent", [lit("zeus"), lit("thor")]);
228
+ const results = trampoline(
229
+ engine.solveGoal(query, idContinuation),
230
+ ) as LogicResult[];
231
+ results.forEach((res) => expect(res.success).to.be.false);
232
+ });
233
+
234
+ it("should solve a goal with a variable", () => {
235
+ const query = makeGoal("parent", [lit("zeus"), varPat("Child")]);
236
+ const results = trampoline(
237
+ engine.solveGoal(query, idContinuation),
238
+ ) as LogicResult[];
239
+
240
+ results.forEach((res) => {
241
+ if (!res.success) expect.fail("Result should be successful");
242
+ expect(res.solutions.has("Child")).to.be.true;
243
+ });
244
+
245
+ const names = results.map((r) => {
246
+ if (!r.success) throw new Error("Unexpected failure");
247
+ return r.solutions.get("Child");
248
+ });
249
+
250
+ expect(names).to.include("ares");
251
+ expect(names).to.include("athena");
252
+ });
253
+
254
+ it("should solve a rule using backtracking", () => {
255
+ const query = makeGoal("sibling", [lit("ares"), lit("athena")]);
256
+ const results = trampoline(
257
+ engine.solveGoal(query, idContinuation),
258
+ ) as LogicResult[];
259
+ expect(results.length).to.be.eq(1);
260
+ const solution = results[0] as SuccessLogicResult;
261
+ expect(solution.success).to.be.true;
262
+ expect(solution.solutions.get("X")).to.eq("ares");
263
+ expect(solution.solutions.get("Y")).to.eq("athena");
264
+ expect(solution.solutions.get("Z")).to.eq("zeus");
265
+ });
266
+ });
267
+ it("should solve a rule using backtracking with variable", () => {
268
+ const query = makeGoal("sibling", [lit("ares"), varPat("Child")]);
269
+ const results = trampoline(
270
+ engine.solveGoal(query, idContinuation),
271
+ ) as LogicResult[];
272
+ results.forEach((res) => {
273
+ if (!res.success) expect.fail("Result should be successful");
274
+ expect(res.solutions.has("Child")).to.be.true;
275
+ });
276
+ const names = results.map((r) => {
277
+ if (!r.success) throw new Error("Unexpected failure");
278
+ return r.solutions.get("Child");
279
+ });
280
+ expect(names).to.include("ares");
281
+ expect(names).to.include("athena");
282
+ });
283
+ describe("Output Modes", () => {
284
+ it('should return all results when outputMode is "all"', () => {
285
+ const query = makeGoal("parent", [lit("zeus"), varPat("X")]);
286
+
287
+ const results = trampoline(
288
+ engine.solveGoal(query, idContinuation),
289
+ ) as LogicResult[];
290
+ expect(results).to.be.an("array");
291
+ expect(results).to.have.lengthOf(2);
292
+ const names = results.map((r) => {
293
+ if (!r.success) throw new Error("Unexpected failure");
294
+ return r.solutions.get("X");
295
+ });
296
+ expect(names).to.include("ares");
297
+ expect(names).to.include("athena");
298
+ });
299
+ });
300
+
301
+ describe("Findall", () => {
302
+ it("should collect all solutions into a list", () => {
303
+ const findallNode = new Findall(
304
+ varPat("X"), // Template
305
+ makeGoal("parent", [lit("zeus"), varPat("X")]), // Goal
306
+ varPat("List"), // Bag variable
307
+ );
308
+ const result = trampoline(
309
+ engine.solveFindall(findallNode, idContinuation),
310
+ ) as any;
311
+ expect(Array.isArray(result)).to.be.true;
312
+ expect(result).to.have.lengthOf(2);
313
+ expect(result).to.include("ares");
314
+ expect(result).to.include("athena");
315
+ });
316
+
317
+ describe("Expression Unification", () => {
318
+ it("should successfully unify a variable with an array expression", () => {
319
+ env.head.set("myList", "dummy");
320
+ const X = new Variable(s("X"), new NilPrimitive(null));
321
+ const myList = new Variable(s("myList"), new NilPrimitive(null));
322
+ const result = trampoline(engine.unifyExpr(X, myList, idContinuation));
323
+ expect(result).to.be.true;
324
+ });
325
+ });
326
+ });
327
+ });
@@ -0,0 +1,80 @@
1
+ import { expect } from "chai";
2
+ import {
3
+ SymbolPrimitive,
4
+ NumberPrimitive,
5
+ LiteralPattern,
6
+ VariablePattern,
7
+ ListPattern,
8
+ ConsPattern,
9
+ FunctorPattern,
10
+ Pattern,
11
+ } from "yukigo-ast";
12
+ import {
13
+ instantiate,
14
+ Substitution,
15
+ } from "../../src/interpreter/components/logic/LogicResolver.js";
16
+
17
+ const s = (val: string) => new SymbolPrimitive(val);
18
+ const n = (val: number) => new NumberPrimitive(val);
19
+ const lit = (val: string | number) =>
20
+ new LiteralPattern(typeof val === "string" ? s(val) : n(val));
21
+ const varPat = (name: string) => new VariablePattern(s(name));
22
+
23
+ describe("Logic Substitution (instantiate)", () => {
24
+ it("should recursively substitute in ListPattern", () => {
25
+ const pattern = new ListPattern([varPat("X"), lit(1), varPat("Y")]);
26
+ const substs: Substitution = new Map([
27
+ ["X", lit("cat")],
28
+ ["Y", lit("dog")],
29
+ ]);
30
+
31
+ const result = instantiate(pattern, substs) as ListPattern;
32
+ expect(result).to.be.instanceOf(ListPattern);
33
+ expect(result.elements[0]).to.deep.equal(lit("cat"));
34
+ expect(result.elements[1]).to.deep.equal(lit(1));
35
+ expect(result.elements[2]).to.deep.equal(lit("dog"));
36
+ });
37
+
38
+ it("should recursively substitute in ConsPattern", () => {
39
+ const pattern = new ConsPattern(varPat("X"), varPat("Y"));
40
+ const substs: Substitution = new Map<string, Pattern>([
41
+ ["X", lit(1)],
42
+ ["Y", new ListPattern([lit(2), lit(3)])],
43
+ ]);
44
+
45
+ const result = instantiate(pattern, substs) as ConsPattern;
46
+ expect(result).to.be.instanceOf(ConsPattern);
47
+ expect(result.left).to.deep.equal(lit(1));
48
+ expect(result.right).to.be.instanceOf(ListPattern);
49
+ expect((result.right as ListPattern).elements).to.have.lengthOf(2);
50
+ });
51
+
52
+ it("should recursively substitute in FunctorPattern", () => {
53
+ const pattern = new FunctorPattern(s("person"), [
54
+ varPat("Name"),
55
+ varPat("Age"),
56
+ ]);
57
+ const substs: Substitution = new Map([
58
+ ["Name", lit("Alice")],
59
+ ["Age", lit(30)],
60
+ ]);
61
+
62
+ const result = instantiate(pattern, substs) as FunctorPattern;
63
+ expect(result).to.be.instanceOf(FunctorPattern);
64
+ expect(result.identifier.value).to.equal("person");
65
+ expect(result.args[0]).to.deep.equal(lit("Alice"));
66
+ expect(result.args[1]).to.deep.equal(lit(30));
67
+ });
68
+
69
+ it("should handle nested substitutions", () => {
70
+ const pattern = varPat("X");
71
+ const substs: Substitution = new Map<string, Pattern>([
72
+ ["X", new ListPattern([varPat("Y")])],
73
+ ["Y", lit("hello")],
74
+ ]);
75
+
76
+ const result = instantiate(pattern, substs) as ListPattern;
77
+ expect(result).to.be.instanceOf(ListPattern);
78
+ expect(result.elements[0]).to.deep.equal(lit("hello"));
79
+ });
80
+ });