yukigo 0.1.1 → 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 (36) hide show
  1. package/CHANGELOG.md +26 -0
  2. package/dist/analyzer/index.d.ts +1 -1
  3. package/dist/analyzer/index.js +2 -1
  4. package/dist/interpreter/components/PatternMatcher.d.ts +2 -1
  5. package/dist/interpreter/components/RuntimeContext.d.ts +2 -1
  6. package/dist/interpreter/components/RuntimeContext.js +10 -3
  7. package/dist/interpreter/components/TestRunner.js +1 -42
  8. package/dist/interpreter/components/Visitor.d.ts +2 -2
  9. package/dist/interpreter/components/Visitor.js +16 -28
  10. package/dist/interpreter/components/runtimes/FunctionRuntime.d.ts +2 -1
  11. package/dist/interpreter/components/runtimes/FunctionRuntime.js +29 -2
  12. package/dist/interpreter/components/runtimes/LazyRuntime.d.ts +5 -0
  13. package/dist/interpreter/components/runtimes/LazyRuntime.js +70 -39
  14. package/dist/interpreter/entities.d.ts +105 -0
  15. package/dist/interpreter/entities.js +96 -0
  16. package/package.json +2 -2
  17. package/src/analyzer/index.ts +3 -2
  18. package/src/interpreter/components/PatternMatcher.ts +2 -0
  19. package/src/interpreter/components/RuntimeContext.ts +10 -4
  20. package/src/interpreter/components/TestRunner.ts +4 -40
  21. package/src/interpreter/components/Visitor.ts +34 -47
  22. package/src/interpreter/components/runtimes/FunctionRuntime.ts +42 -3
  23. package/src/interpreter/components/runtimes/LazyRuntime.ts +82 -49
  24. package/tests/analyzer/generic.spec.ts +14 -0
  25. package/tests/interpreter/interpreter.spec.ts +10 -0
  26. package/dist/interpreter/components/FunctionRuntime.d.ts +0 -8
  27. package/dist/interpreter/components/FunctionRuntime.js +0 -81
  28. package/dist/interpreter/components/LazyRuntime.d.ts +0 -7
  29. package/dist/interpreter/components/LazyRuntime.js +0 -94
  30. package/dist/interpreter/components/LogicEngine.d.ts +0 -24
  31. package/dist/interpreter/components/LogicEngine.js +0 -173
  32. package/dist/interpreter/components/LogicResolver.d.ts +0 -10
  33. package/dist/interpreter/components/LogicResolver.js +0 -97
  34. package/dist/interpreter/components/ObjectRuntime.d.ts +0 -33
  35. package/dist/interpreter/components/ObjectRuntime.js +0 -123
  36. package/tsconfig.tsbuildinfo +0 -1
package/CHANGELOG.md CHANGED
@@ -1,3 +1,29 @@
1
+ ## 0.2.0 (2026-04-11)
2
+
3
+ ### 🚀 Features
4
+
5
+ - **yukigo:** agrego `clone` a `RuntimeContext` para clonar el entorno ([ee8e0af](https://github.com/miyukiproject/yukigo/commit/ee8e0af))
6
+
7
+ ### 🩹 Fixes
8
+
9
+ - **yukigo:** permitir a las inspections tener args undefined ([4f36ebe](https://github.com/miyukiproject/yukigo/commit/4f36ebe))
10
+ - **yukigo:** Evaluar `Equal` y `NotEqual` fuera de `processBinary` ([31eca15](https://github.com/miyukiproject/yukigo/commit/31eca15))
11
+ - **yukigo:** delegue logica de deepEqual a LazyRuntime ([8496113](https://github.com/miyukiproject/yukigo/commit/8496113))
12
+ - **yukigo:** evaluateConcatLazy permite eager y lazy evaluation ([3faf646](https://github.com/miyukiproject/yukigo/commit/3faf646))
13
+ - **yukigo:** corrijo `isEqual` para que compare strings con listas ([d6225c9](https://github.com/miyukiproject/yukigo/commit/d6225c9))
14
+ - **yukigo:** corrijo manejo de entorno en `applyArguments` y `visitApplication` ([5c7ab1f](https://github.com/miyukiproject/yukigo/commit/5c7ab1f))
15
+ - **yukigo:** capturar correctamente el entorno en `evaluateCons` ([a1bc7a4](https://github.com/miyukiproject/yukigo/commit/a1bc7a4))
16
+ - **yukigo:** `popEnv` no tiene que esperar el `env` por parametro ([575bacf](https://github.com/miyukiproject/yukigo/commit/575bacf))
17
+ - **yukigo:** agrego metodo visitTypeCast faltante ([61c9984](https://github.com/miyukiproject/yukigo/commit/61c9984))
18
+
19
+ ### 🧱 Updated Dependencies
20
+
21
+ - Updated yukigo-ast to 0.2.0
22
+
23
+ ### ❤️ Thank You
24
+
25
+ - noiseArch
26
+
1
27
  ## 0.1.0 (2025-12-09)
2
28
 
3
29
  ### 🧱 Updated Dependencies
@@ -9,7 +9,7 @@ export type AnalysisResult = {
9
9
  export type InspectionRule = {
10
10
  inspection: string;
11
11
  binding?: string;
12
- args: string[];
12
+ args?: string[];
13
13
  expected: boolean;
14
14
  };
15
15
  export declare const DefaultInspectionSet: InspectionMap;
@@ -121,7 +121,8 @@ export class Analyzer {
121
121
  // Execution Loop
122
122
  const isGlobalVisitor = !rule.binding || rule.binding === "*";
123
123
  const normalizedBinding = isGlobalVisitor ? undefined : rule.binding;
124
- const visitor = new InspectionClass(...rule.args, normalizedBinding);
124
+ const args = rule.args ?? [];
125
+ const visitor = new InspectionClass(...args, normalizedBinding);
125
126
  for (const { node, binding } of targets) {
126
127
  try {
127
128
  if (!isGlobalVisitor && visitor.setBinding) {
@@ -1,4 +1,4 @@
1
- import { ApplicationPattern, AsPattern, ASTNode, ConsPattern, ConstructorPattern, Expression, FunctorPattern, LazyList, ListPattern, LiteralPattern, PrimitiveValue, TuplePattern, UnionPattern, VariablePattern, Visitor, WildcardPattern, TypePattern } from "yukigo-ast";
1
+ import { ApplicationPattern, AsPattern, ASTNode, ConsPattern, ConstructorPattern, Expression, FunctorPattern, LazyList, ListPattern, LiteralPattern, PrimitiveValue, TuplePattern, UnionPattern, VariablePattern, Visitor, WildcardPattern, TypePattern, EnvStack } from "yukigo-ast";
2
2
  import { Bindings } from "../index.js";
3
3
  import { CPSThunk } from "../trampoline.js";
4
4
  import { RuntimeContext } from "./RuntimeContext.js";
@@ -17,6 +17,7 @@ export interface InternalConsState {
17
17
  readonly head: PrimitiveValue;
18
18
  readonly tailExpr: Expression;
19
19
  readonly evaluator: ExpressionEvaluator;
20
+ readonly capturedEnv: EnvStack;
20
21
  realizedTail?: PrimitiveValue;
21
22
  }
22
23
  export interface MemoizedLazyList extends LazyList {
@@ -27,8 +27,9 @@ export declare class RuntimeContext {
27
27
  isDefined(name: string): boolean;
28
28
  pushEnv(frame?: Environment): void;
29
29
  replace(name: string, value: PrimitiveValue, onReplace?: (env: Environment) => void): boolean;
30
- popEnv(env: EnvStack): void;
30
+ popEnv(): void;
31
31
  lookup(name: string): PrimitiveValue;
32
32
  remove(name: string): void;
33
33
  define(name: string, value: PrimitiveValue): void;
34
+ clone(env?: EnvStack): EnvStack;
34
35
  }
@@ -63,10 +63,10 @@ export class RuntimeContext {
63
63
  }
64
64
  return false;
65
65
  }
66
- popEnv(env) {
67
- if (!env.tail)
66
+ popEnv() {
67
+ if (!this.env.tail)
68
68
  throw new Error("Runtime Error: Cannot pop the global environment scope.");
69
- this.env = env.tail;
69
+ this.env = this.env.tail;
70
70
  }
71
71
  lookup(name) {
72
72
  let current = this.env;
@@ -83,4 +83,11 @@ export class RuntimeContext {
83
83
  define(name, value) {
84
84
  this.env.head.set(name, value);
85
85
  }
86
+ clone(env) {
87
+ const target = env ?? this.env;
88
+ return {
89
+ head: new Map(target.head),
90
+ tail: this.env.tail,
91
+ };
92
+ }
86
93
  }
@@ -53,7 +53,7 @@ class AssertionVisitor extends TraverseVisitor {
53
53
  visitEquality(node) {
54
54
  return (k) => this.interpreter.evaluate(node.value, (value) => {
55
55
  return () => this.interpreter.evaluate(node.expected, (expected) => {
56
- return this.isEqual(value, expected, (passed) => {
56
+ this.lazyRuntime.deepEqual(value, expected, (passed) => {
57
57
  if (this.negated === passed) {
58
58
  throw new FailedAssert(value, expected, this.negated
59
59
  ? `Expected ${JSON.stringify(value)} NOT to be equal to ${JSON.stringify(expected)}`
@@ -75,47 +75,6 @@ class AssertionVisitor extends TraverseVisitor {
75
75
  return k(undefined);
76
76
  });
77
77
  }
78
- isEqual(a, b, k) {
79
- if (a === b)
80
- return k(true);
81
- if (a && b && typeof a === "object" && typeof b === "object") {
82
- return this.lazyRuntime.realizeList(a, (valA) => {
83
- return () => this.lazyRuntime.realizeList(b, (valB) => {
84
- if (Array.isArray(valA) && Array.isArray(valB)) {
85
- if (valA.length !== valB.length)
86
- return k(false);
87
- const checkNext = (index) => {
88
- if (index >= valA.length)
89
- return k(true);
90
- return this.isEqual(valA[index], valB[index], (res) => {
91
- if (!res)
92
- return k(false);
93
- return () => checkNext(index + 1);
94
- });
95
- };
96
- return checkNext(0);
97
- }
98
- if (Array.isArray(valA) !== Array.isArray(valB))
99
- return k(false);
100
- const keys = Object.keys(valA);
101
- if (keys.length !== Object.keys(valB).length)
102
- return k(false);
103
- const checkNextKey = (index) => {
104
- if (index >= keys.length)
105
- return k(true);
106
- const key = keys[index];
107
- return this.isEqual(valA[key], valB[key], (res) => {
108
- if (!res)
109
- return k(false);
110
- return () => checkNextKey(index + 1);
111
- });
112
- };
113
- return checkNextKey(0);
114
- });
115
- });
116
- }
117
- return k(false);
118
- }
119
78
  }
120
79
  export class TestRunner extends TraverseVisitor {
121
80
  interpreter;
@@ -1,4 +1,4 @@
1
- import { Visitor, PrimitiveValue, NumberPrimitive, BooleanPrimitive, StringPrimitive, ListPrimitive, NilPrimitive, SymbolPrimitive, Variable, CharPrimitive, ArithmeticUnaryOperation, ArithmeticBinaryOperation, ListUnaryOperation, ListBinaryOperation, ComparisonOperation, LogicalBinaryOperation, LogicalUnaryOperation, BitwiseBinaryOperation, BitwiseUnaryOperation, StringOperation, UnifyOperation, AssignOperation, Assignment, TupleExpression, FieldExpression, DataExpression, ConsExpression, LetInExpression, Call, Otherwise, CompositionExpression, Expression, Application, Lambda, Sequence, Exist, Not, Findall, Forall, Goal, Send, New, Self, ListComprehension, RangeExpression, Generator as YuGenerator, ASTNode, Raise, Query, Super, If, Assert, Test, TestGroup, LogicConstraint } from "yukigo-ast";
1
+ import { Visitor, PrimitiveValue, NumberPrimitive, BooleanPrimitive, StringPrimitive, ListPrimitive, NilPrimitive, SymbolPrimitive, Variable, CharPrimitive, ArithmeticUnaryOperation, ArithmeticBinaryOperation, ListUnaryOperation, ListBinaryOperation, ComparisonOperation, LogicalBinaryOperation, LogicalUnaryOperation, BitwiseBinaryOperation, BitwiseUnaryOperation, StringOperation, UnifyOperation, AssignOperation, Assignment, TupleExpression, FieldExpression, DataExpression, ConsExpression, LetInExpression, Call, Otherwise, CompositionExpression, Expression, Application, Lambda, Sequence, Exist, Not, Findall, Forall, Goal, Send, New, Self, ListComprehension, RangeExpression, Generator as YuGenerator, ASTNode, Raise, Query, TypeCast, Super, If, Assert, Test, TestGroup, LogicConstraint } from "yukigo-ast";
2
2
  import { ExpressionEvaluator } from "../utils.js";
3
3
  import { ErrorFrame } from "../errors.js";
4
4
  import { Continuation, CPSThunk, Thunk } from "../trampoline.js";
@@ -44,7 +44,6 @@ export declare class InterpreterVisitor implements Visitor<CPSThunk<PrimitiveVal
44
44
  visitCompositionExpression(node: CompositionExpression): CPSThunk<PrimitiveValue>;
45
45
  visitLambda(node: Lambda): CPSThunk<PrimitiveValue>;
46
46
  visitApplication(node: Application): CPSThunk<PrimitiveValue>;
47
- private applyArguments;
48
47
  visitQuery(node: Query): CPSThunk<PrimitiveValue>;
49
48
  visitExist(node: Exist): CPSThunk<PrimitiveValue>;
50
49
  visitNot(node: Not): CPSThunk<PrimitiveValue>;
@@ -58,6 +57,7 @@ export declare class InterpreterVisitor implements Visitor<CPSThunk<PrimitiveVal
58
57
  visitNew(node: New): CPSThunk<PrimitiveValue>;
59
58
  visitSelf(node: Self): CPSThunk<PrimitiveValue>;
60
59
  visitListComprehension(node: ListComprehension): CPSThunk<PrimitiveValue>;
60
+ visitTypeCast(node: TypeCast): CPSThunk<PrimitiveValue>;
61
61
  visitGenerator(node: YuGenerator): CPSThunk<PrimitiveValue>;
62
62
  visitRaise(node: Raise): CPSThunk<PrimitiveValue>;
63
63
  visitRangeExpression(node: RangeExpression): CPSThunk<PrimitiveValue>;
@@ -185,6 +185,9 @@ export class InterpreterVisitor {
185
185
  (Array.isArray(b) || typeof b === "string" || isLazyList(b)), "ListBinaryOperation");
186
186
  }
187
187
  visitComparisonOperation(node) {
188
+ if (node.operator === "Equal" || node.operator === "NotEqual") {
189
+ return (k) => this.evaluate(node.left, (left) => () => this.evaluate(node.right, (right) => this.context.lazyRuntime.deepEqual(left, right, (eq) => k(node.operator === "Equal" ? eq : !eq))));
190
+ }
188
191
  return this.processBinary(node, ComparisonOperationTable, () => true, "ComparisonOperation");
189
192
  }
190
193
  visitLogicalBinaryOperation(node) {
@@ -378,46 +381,28 @@ export class InterpreterVisitor {
378
381
  });
379
382
  }
380
383
  visitApplication(node) {
384
+ const { funcRuntime } = this.context;
381
385
  if (this.context.config.debug) {
382
386
  console.log(`[Interpreter] Visiting Application`);
383
387
  }
384
388
  return (k) => this.evaluate(node.functionExpr, (func) => {
385
389
  if (!isRuntimeFunction(func))
386
390
  throw new InterpreterError("Application", "Cannot apply non-function");
387
- return this.evaluate(node.parameter, (arg) => {
391
+ const applyFuncToNode = (func) => (k) => this.evaluate(node.parameter, (arg) => {
388
392
  const argThunk = () => arg;
389
393
  const allPendingArgs = func.pendingArgs
390
394
  ? [...func.pendingArgs, argThunk]
391
395
  : [argThunk];
392
- return this.applyArguments(func, allPendingArgs)(k);
396
+ return funcRuntime.applyArguments(func, allPendingArgs)(k);
393
397
  });
394
- });
395
- }
396
- applyArguments(func, args) {
397
- if (args.length < func.arity) {
398
- return valueToCPS({
399
- ...func,
400
- pendingArgs: args,
401
- });
402
- }
403
- const argsToConsume = args.slice(0, func.arity);
404
- const remainingArgs = args.slice(func.arity);
405
- const evaluatedArgs = argsToConsume.map((arg) => typeof arg === "function" ? arg() : arg);
406
- if (func.closure)
407
- this.context.pushEnv(func.closure.head);
408
- return (cont) => () => this.context.funcRuntime.apply(func, evaluatedArgs, (result) => {
409
- if (remainingArgs.length > 0) {
410
- if (isRuntimeFunction(result)) {
411
- const nextArgs = result.pendingArgs
412
- ? [...result.pendingArgs, ...remainingArgs]
413
- : remainingArgs;
414
- return this.applyArguments(result, nextArgs)(cont);
415
- }
416
- else {
417
- throw new InterpreterError("Application", `Too many arguments provided. Result was '${result}' (not a function), but had ${remainingArgs.length} args left.`);
418
- }
398
+ if (func.arity === 0) {
399
+ return funcRuntime.applyArguments(func, [])(resultOfFunc => {
400
+ if (!isRuntimeFunction(resultOfFunc))
401
+ throw new InterpreterError("Application", `Cannot apply non-function result of arity-0 function`);
402
+ return applyFuncToNode(resultOfFunc)(k);
403
+ });
419
404
  }
420
- return cont(result);
405
+ return applyFuncToNode(func)(k);
421
406
  });
422
407
  }
423
408
  visitQuery(node) {
@@ -594,6 +579,9 @@ export class InterpreterVisitor {
594
579
  return process(0);
595
580
  };
596
581
  }
582
+ visitTypeCast(node) {
583
+ return node.expression.accept(this);
584
+ }
597
585
  visitGenerator(node) {
598
586
  return (k) => this.evaluate(node.expression, k);
599
587
  }
@@ -1,10 +1,11 @@
1
1
  import { PrimitiveValue, RuntimeFunction } from "yukigo-ast";
2
- import { Continuation, Thunk } from "../../trampoline.js";
2
+ import { Continuation, CPSThunk, Thunk } from "../../trampoline.js";
3
3
  import { RuntimeContext } from "../RuntimeContext.js";
4
4
  export declare class FunctionRuntime {
5
5
  private context;
6
6
  constructor(context: RuntimeContext);
7
7
  apply(func: RuntimeFunction, args: PrimitiveValue[], k: Continuation<PrimitiveValue>): Thunk<PrimitiveValue>;
8
+ applyArguments(func: RuntimeFunction, args: (PrimitiveValue | (() => PrimitiveValue))[]): CPSThunk<PrimitiveValue>;
8
9
  private preloadDefinitions;
9
10
  private patternsMatch;
10
11
  private evaluateSequence;
@@ -1,7 +1,8 @@
1
- import { UnguardedBody, Sequence, Return, Function, } from "yukigo-ast";
1
+ import { UnguardedBody, Sequence, Return, Function, isRuntimeFunction, } from "yukigo-ast";
2
2
  import { PatternMatcher } from "../PatternMatcher.js";
3
3
  import { InterpreterError } from "../../errors.js";
4
4
  import { EnvBuilderVisitor } from "../EnvBuilder.js";
5
+ import { valueToCPS } from "../../trampoline.js";
5
6
  import { RuntimeContext } from "../RuntimeContext.js";
6
7
  import { InterpreterVisitor } from "../Visitor.js";
7
8
  class NonExhaustivePatterns extends InterpreterError {
@@ -17,10 +18,12 @@ export class FunctionRuntime {
17
18
  apply(func, args, k) {
18
19
  const funcName = func.identifier;
19
20
  const equations = func.equations;
21
+ const oldEnv = this.context.env;
20
22
  if (this.context.config.debug)
21
23
  console.log(`[FunctionRuntime] Applying function: ${funcName} with args:`, args);
22
24
  const tryNextEquation = (eqIndex) => {
23
25
  if (eqIndex >= equations.length) {
26
+ this.context.setEnv(oldEnv);
24
27
  throw new NonExhaustivePatterns(funcName);
25
28
  }
26
29
  const eq = equations[eqIndex];
@@ -33,7 +36,6 @@ export class FunctionRuntime {
33
36
  if (this.context.config.debug)
34
37
  console.log(`[FunctionRuntime] Match successful for ${funcName} equation ${eqIndex}`);
35
38
  const localEnv = new Map(bindings);
36
- const oldEnv = this.context.env;
37
39
  if (func.closure)
38
40
  this.context.setEnv(func.closure);
39
41
  this.context.pushEnv(localEnv);
@@ -74,6 +76,31 @@ export class FunctionRuntime {
74
76
  };
75
77
  return tryNextEquation(0);
76
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
+ }
77
104
  preloadDefinitions(seq, evaluatorFactory) {
78
105
  const ctx = new RuntimeContext();
79
106
  new EnvBuilderVisitor(ctx).build(seq.statements);
@@ -11,4 +11,9 @@ export declare class LazyRuntime {
11
11
  evaluateConcat(left: PrimitiveValue, right: PrimitiveValue, k: Continuation<PrimitiveValue>): Thunk<PrimitiveValue>;
12
12
  evaluateConcatLazy(node: ListBinaryOperation, evaluator: ExpressionEvaluator, k: Continuation<PrimitiveValue>): Thunk<PrimitiveValue>;
13
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;
14
19
  }
@@ -78,14 +78,15 @@ export class LazyRuntime {
78
78
  });
79
79
  }
80
80
  evaluateCons(node, evaluator, k) {
81
- const capturedEnv = this.context.env;
82
81
  const ctx = this.context;
82
+ const capturedEnv = ctx.clone();
83
83
  return evaluator.evaluate(node.head, (head) => {
84
- if (this.context.config.lazyLoading) {
84
+ if (ctx.config.lazyLoading) {
85
85
  const consState = {
86
86
  head,
87
87
  tailExpr: node.tail,
88
88
  evaluator,
89
+ capturedEnv,
89
90
  realizedTail: undefined,
90
91
  };
91
92
  const consList = {
@@ -97,7 +98,7 @@ export class LazyRuntime {
97
98
  yield current.head;
98
99
  if (current.realizedTail === undefined) {
99
100
  const prevEnv = ctx.env;
100
- ctx.setEnv(capturedEnv);
101
+ ctx.setEnv(current.capturedEnv);
101
102
  try {
102
103
  current.realizedTail = trampoline(current.evaluator.evaluate(current.tailExpr, idContinuation));
103
104
  }
@@ -178,61 +179,91 @@ export class LazyRuntime {
178
179
  });
179
180
  }
180
181
  evaluateConcatLazy(node, evaluator, k) {
181
- const capturedEnv = this.context.env;
182
182
  const ctx = this.context;
183
- return k(createMemoizedStream(function* () {
184
- const prevEnv = ctx.env;
185
- ctx.setEnv(capturedEnv);
186
- try {
187
- const left = trampoline(evaluator.evaluate(node.left, idContinuation));
183
+ return evaluator.evaluate(node.left, (left) => {
184
+ const capturedEnv = ctx.clone();
185
+ return k(createMemoizedStream(function* () {
188
186
  if (Array.isArray(left))
189
187
  yield* left;
190
188
  else if (typeof left === "string")
191
189
  yield* left.split("");
192
190
  else if (isLazyList(left))
193
191
  yield* left.generator();
194
- const right = trampoline(evaluator.evaluate(node.right, idContinuation));
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
+ }
195
204
  if (Array.isArray(right))
196
205
  yield* right;
197
206
  else if (typeof right === "string")
198
207
  yield* right.split("");
199
208
  else if (isLazyList(right))
200
209
  yield* right.generator();
201
- }
202
- finally {
203
- ctx.setEnv(prevEnv);
204
- }
205
- }));
210
+ else
211
+ throw new Error("Invalid right operand for lazy Concat");
212
+ }));
213
+ });
206
214
  }
207
215
  deepEqual(a, b, k) {
208
216
  if (a === b)
209
217
  return k(true);
210
- const compareValues = (valA, valB) => {
211
- if (typeof valA === "string" && typeof valB === "string")
212
- return k(valA === valB);
213
- if (Array.isArray(valA) && Array.isArray(valB)) {
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) => {
214
223
  if (valA.length !== valB.length)
215
224
  return k(false);
216
- const compareNext = (index) => {
217
- if (index >= valA.length)
218
- return k(true);
219
- return () => this.deepEqual(valA[index], valB[index], (isEqual) => {
220
- if (!isEqual)
221
- return k(false);
222
- return () => compareNext(index + 1);
223
- });
224
- };
225
- return compareNext(0);
226
- }
227
- return k(valA == valB);
228
- };
229
- if (isLazyList(a) || isLazyList(b)) {
230
- return this.realizeList(a, (valA) => {
231
- return () => this.realizeList(b, (valB) => {
232
- return () => compareValues(valA, valB);
233
- });
234
- });
225
+ return this.deepEqualCollection(valA, valB, 0, k);
226
+ }));
235
227
  }
236
- return compareValues(a, b);
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);
237
268
  }
238
269
  }
@@ -0,0 +1,105 @@
1
+ import { Fact, Rule, Pattern, GuardedBody, UnguardedBody } from "yukigo-ast";
2
+ export type SuccessLogicResult = {
3
+ success: true;
4
+ solutions: Map<string, PrimitiveValue>;
5
+ };
6
+ export type FailedLogicResult = {
7
+ success: false;
8
+ };
9
+ export type LogicResult = SuccessLogicResult | FailedLogicResult;
10
+ export type PrimitiveValue = number | boolean | string | RuntimeFunction | RuntimePredicate | LogicResult | LazyList | null | void | PrimitiveValue[] | RuntimeObject | RuntimeClass | undefined;
11
+ export declare function isLogicResult(value: PrimitiveValue): value is LogicResult;
12
+ export type PrimitiveThunk = () => PrimitiveValue;
13
+ export type Environment = Map<string, PrimitiveValue>;
14
+ export type EnvStack = {
15
+ head: Environment;
16
+ tail: EnvStack | null;
17
+ };
18
+ type RuntimePredicateKind = "Fact" | "Rule" | "Predicate";
19
+ type PredicateEquation = Fact | Rule;
20
+ export interface IRuntimePredicate {
21
+ kind: RuntimePredicateKind;
22
+ identifier: string;
23
+ equations: PredicateEquation[];
24
+ }
25
+ export declare class RuntimePredicate implements IRuntimePredicate {
26
+ kind: RuntimePredicateKind;
27
+ identifier: string;
28
+ equations: PredicateEquation[];
29
+ constructor(kind: RuntimePredicateKind, identifier: string, equations: PredicateEquation[]);
30
+ pushEquation<T extends Fact | Rule>(eq: T): void;
31
+ }
32
+ export declare const isRuntimePredicate: (prim: PrimitiveValue) => prim is RuntimePredicate;
33
+ type Body = GuardedBody[] | UnguardedBody;
34
+ export interface IRuntimeEquation {
35
+ patterns: Pattern[];
36
+ body: Body;
37
+ }
38
+ export declare class RuntimeEquation implements IRuntimeEquation {
39
+ patterns: Pattern[];
40
+ body: Body;
41
+ constructor(patterns: Pattern[], body: Body);
42
+ }
43
+ type PendingArg = PrimitiveValue | PrimitiveThunk;
44
+ /**
45
+ * Runtime Function used in the Interpreter
46
+ */
47
+ export interface IRuntimeFunction {
48
+ arity: number;
49
+ identifier: string;
50
+ equations: RuntimeEquation[];
51
+ pendingArgs: PendingArg[];
52
+ closure?: EnvStack;
53
+ }
54
+ export declare class RuntimeFunction implements IRuntimeFunction {
55
+ arity: number;
56
+ identifier: string;
57
+ equations: RuntimeEquation[];
58
+ pendingArgs: PendingArg[];
59
+ closure?: EnvStack;
60
+ constructor(arity: number, identifier: string, equations: RuntimeEquation[], pendingArgs: PendingArg[], closure?: EnvStack);
61
+ setPendingArgs(args: PendingArg[]): void;
62
+ }
63
+ export declare function isRuntimeFunction(val: PrimitiveValue): val is RuntimeFunction;
64
+ type Fields = Map<string, PrimitiveValue>;
65
+ type Methods = Map<string, RuntimeFunction>;
66
+ export interface IRuntimeClass {
67
+ identifier: string;
68
+ fields: Fields;
69
+ methods: Methods;
70
+ mixins: string[];
71
+ superclass?: string;
72
+ }
73
+ export declare class RuntimeClass implements IRuntimeClass {
74
+ identifier: string;
75
+ fields: Fields;
76
+ methods: Methods;
77
+ mixins: string[];
78
+ superclass?: string;
79
+ constructor(identifier: string, fields: Fields, methods: Methods, mixins: string[], superclass?: string);
80
+ }
81
+ export declare function isRuntimeClass(val: PrimitiveValue): val is RuntimeClass;
82
+ export interface IRuntimeObject {
83
+ identifier: string;
84
+ className: string;
85
+ fields: Map<string, PrimitiveValue>;
86
+ methods: Map<string, RuntimeFunction>;
87
+ }
88
+ export declare class RuntimeObject implements IRuntimeObject {
89
+ identifier: string;
90
+ className: string;
91
+ fields: Fields;
92
+ methods: Methods;
93
+ constructor(identifier: string, className: string, fields: Fields, methods: Methods);
94
+ }
95
+ export declare function isRuntimeObject(val: PrimitiveValue): val is RuntimeObject;
96
+ export type LazyGenerator = () => Generator<PrimitiveValue, void, unknown>;
97
+ export interface ILazyList {
98
+ readonly generator: LazyGenerator;
99
+ }
100
+ export declare class LazyList implements ILazyList {
101
+ readonly generator: LazyGenerator;
102
+ constructor(generator: LazyGenerator);
103
+ }
104
+ export declare function isLazyList(prim: unknown): prim is LazyList;
105
+ export {};