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.
- package/CHANGELOG.md +26 -0
- package/dist/analyzer/index.d.ts +1 -1
- package/dist/analyzer/index.js +2 -1
- package/dist/interpreter/components/PatternMatcher.d.ts +2 -1
- package/dist/interpreter/components/RuntimeContext.d.ts +2 -1
- package/dist/interpreter/components/RuntimeContext.js +10 -3
- package/dist/interpreter/components/TestRunner.js +1 -42
- package/dist/interpreter/components/Visitor.d.ts +2 -2
- package/dist/interpreter/components/Visitor.js +16 -28
- package/dist/interpreter/components/runtimes/FunctionRuntime.d.ts +2 -1
- package/dist/interpreter/components/runtimes/FunctionRuntime.js +29 -2
- package/dist/interpreter/components/runtimes/LazyRuntime.d.ts +5 -0
- package/dist/interpreter/components/runtimes/LazyRuntime.js +70 -39
- package/dist/interpreter/entities.d.ts +105 -0
- package/dist/interpreter/entities.js +96 -0
- package/package.json +2 -2
- package/src/analyzer/index.ts +3 -2
- package/src/interpreter/components/PatternMatcher.ts +2 -0
- package/src/interpreter/components/RuntimeContext.ts +10 -4
- package/src/interpreter/components/TestRunner.ts +4 -40
- package/src/interpreter/components/Visitor.ts +34 -47
- package/src/interpreter/components/runtimes/FunctionRuntime.ts +42 -3
- package/src/interpreter/components/runtimes/LazyRuntime.ts +82 -49
- package/tests/analyzer/generic.spec.ts +14 -0
- package/tests/interpreter/interpreter.spec.ts +10 -0
- package/dist/interpreter/components/FunctionRuntime.d.ts +0 -8
- package/dist/interpreter/components/FunctionRuntime.js +0 -81
- package/dist/interpreter/components/LazyRuntime.d.ts +0 -7
- package/dist/interpreter/components/LazyRuntime.js +0 -94
- package/dist/interpreter/components/LogicEngine.d.ts +0 -24
- package/dist/interpreter/components/LogicEngine.js +0 -173
- package/dist/interpreter/components/LogicResolver.d.ts +0 -10
- package/dist/interpreter/components/LogicResolver.js +0 -97
- package/dist/interpreter/components/ObjectRuntime.d.ts +0 -33
- package/dist/interpreter/components/ObjectRuntime.js +0 -123
- package/tsconfig.tsbuildinfo +0 -1
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
export function isLogicResult(value) {
|
|
2
|
+
return (value &&
|
|
3
|
+
typeof value === "object" &&
|
|
4
|
+
"success" in value &&
|
|
5
|
+
typeof value.success === "boolean" &&
|
|
6
|
+
"solutions" in value &&
|
|
7
|
+
value.solutions instanceof Map);
|
|
8
|
+
}
|
|
9
|
+
export class RuntimePredicate {
|
|
10
|
+
kind;
|
|
11
|
+
identifier;
|
|
12
|
+
equations;
|
|
13
|
+
constructor(kind, identifier, equations) {
|
|
14
|
+
this.kind = kind;
|
|
15
|
+
this.identifier = identifier;
|
|
16
|
+
this.equations = equations;
|
|
17
|
+
}
|
|
18
|
+
pushEquation(eq) {
|
|
19
|
+
this.equations.push(eq);
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
// TODO: Replace with correct polymorphism
|
|
23
|
+
export const isRuntimePredicate = (prim) => {
|
|
24
|
+
return prim instanceof RuntimePredicate;
|
|
25
|
+
};
|
|
26
|
+
export class RuntimeEquation {
|
|
27
|
+
patterns;
|
|
28
|
+
body;
|
|
29
|
+
constructor(patterns, body) {
|
|
30
|
+
this.patterns = patterns;
|
|
31
|
+
this.body = body;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
export class RuntimeFunction {
|
|
35
|
+
arity;
|
|
36
|
+
identifier;
|
|
37
|
+
equations;
|
|
38
|
+
pendingArgs;
|
|
39
|
+
closure;
|
|
40
|
+
constructor(arity, identifier, equations, pendingArgs, closure) {
|
|
41
|
+
this.arity = arity;
|
|
42
|
+
this.identifier = identifier;
|
|
43
|
+
this.equations = equations;
|
|
44
|
+
this.pendingArgs = pendingArgs;
|
|
45
|
+
this.closure = closure;
|
|
46
|
+
}
|
|
47
|
+
setPendingArgs(args) {
|
|
48
|
+
this.pendingArgs = args;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
// TODO: Replace with correct polymorphism
|
|
52
|
+
export function isRuntimeFunction(val) {
|
|
53
|
+
return val instanceof RuntimeFunction;
|
|
54
|
+
}
|
|
55
|
+
export class RuntimeClass {
|
|
56
|
+
identifier;
|
|
57
|
+
fields;
|
|
58
|
+
methods;
|
|
59
|
+
mixins;
|
|
60
|
+
superclass;
|
|
61
|
+
constructor(identifier, fields, methods, mixins, superclass) {
|
|
62
|
+
this.identifier = identifier;
|
|
63
|
+
this.fields = fields;
|
|
64
|
+
this.methods = methods;
|
|
65
|
+
this.mixins = mixins;
|
|
66
|
+
this.superclass = superclass;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
// TODO: prev as before
|
|
70
|
+
export function isRuntimeClass(val) {
|
|
71
|
+
return val instanceof RuntimeClass;
|
|
72
|
+
}
|
|
73
|
+
export class RuntimeObject {
|
|
74
|
+
identifier;
|
|
75
|
+
className;
|
|
76
|
+
fields;
|
|
77
|
+
methods;
|
|
78
|
+
constructor(identifier, className, fields, methods) {
|
|
79
|
+
this.identifier = identifier;
|
|
80
|
+
this.className = className;
|
|
81
|
+
this.fields = fields;
|
|
82
|
+
this.methods = methods;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
export function isRuntimeObject(val) {
|
|
86
|
+
return val instanceof RuntimeObject;
|
|
87
|
+
}
|
|
88
|
+
export class LazyList {
|
|
89
|
+
generator;
|
|
90
|
+
constructor(generator) {
|
|
91
|
+
this.generator = generator;
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
export function isLazyList(prim) {
|
|
95
|
+
return prim instanceof LazyList;
|
|
96
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "yukigo",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
"dependencies": {
|
|
16
16
|
"typescript": "^5.9.2",
|
|
17
17
|
"yaml": "^2.8.0",
|
|
18
|
-
"yukigo-ast": "0.
|
|
18
|
+
"yukigo-ast": "^0.2.0"
|
|
19
19
|
},
|
|
20
20
|
"devDependencies": {
|
|
21
21
|
"@types/chai": "^5.2.2",
|
package/src/analyzer/index.ts
CHANGED
|
@@ -22,7 +22,7 @@ export type AnalysisResult = {
|
|
|
22
22
|
export type InspectionRule = {
|
|
23
23
|
inspection: string;
|
|
24
24
|
binding?: string;
|
|
25
|
-
args
|
|
25
|
+
args?: string[];
|
|
26
26
|
expected: boolean;
|
|
27
27
|
};
|
|
28
28
|
|
|
@@ -154,7 +154,8 @@ export class Analyzer {
|
|
|
154
154
|
// Execution Loop
|
|
155
155
|
const isGlobalVisitor = !rule.binding || rule.binding === "*";
|
|
156
156
|
const normalizedBinding = isGlobalVisitor ? undefined : rule.binding;
|
|
157
|
-
const
|
|
157
|
+
const args = rule.args ?? []
|
|
158
|
+
const visitor = new InspectionClass(...args, normalizedBinding);
|
|
158
159
|
|
|
159
160
|
for (const { node, binding } of targets) {
|
|
160
161
|
try {
|
|
@@ -21,6 +21,7 @@ import {
|
|
|
21
21
|
TypePattern,
|
|
22
22
|
SimpleType,
|
|
23
23
|
ListType,
|
|
24
|
+
EnvStack,
|
|
24
25
|
} from "yukigo-ast";
|
|
25
26
|
import { Bindings } from "../index.js";
|
|
26
27
|
import { InterpreterVisitor } from "./Visitor.js";
|
|
@@ -61,6 +62,7 @@ export interface InternalConsState {
|
|
|
61
62
|
readonly head: PrimitiveValue;
|
|
62
63
|
readonly tailExpr: Expression;
|
|
63
64
|
readonly evaluator: ExpressionEvaluator;
|
|
65
|
+
readonly capturedEnv: EnvStack;
|
|
64
66
|
realizedTail?: PrimitiveValue;
|
|
65
67
|
}
|
|
66
68
|
|
|
@@ -4,7 +4,6 @@ import { LazyRuntime } from "./runtimes/LazyRuntime.js";
|
|
|
4
4
|
import { ObjectRuntime } from "./runtimes/ObjectRuntime.js";
|
|
5
5
|
import { createGlobalEnv } from "../utils.js";
|
|
6
6
|
import { UnboundVariable } from "../errors.js";
|
|
7
|
-
import { inspect } from "util";
|
|
8
7
|
|
|
9
8
|
export const DefaultConfiguration: Required<InterpreterConfig> = {
|
|
10
9
|
lazyLoading: false,
|
|
@@ -87,12 +86,12 @@ export class RuntimeContext {
|
|
|
87
86
|
|
|
88
87
|
return false;
|
|
89
88
|
}
|
|
90
|
-
public popEnv(
|
|
91
|
-
if (!env.tail)
|
|
89
|
+
public popEnv() {
|
|
90
|
+
if (!this.env.tail)
|
|
92
91
|
throw new Error(
|
|
93
92
|
"Runtime Error: Cannot pop the global environment scope.",
|
|
94
93
|
);
|
|
95
|
-
this.env = env.tail;
|
|
94
|
+
this.env = this.env.tail;
|
|
96
95
|
}
|
|
97
96
|
public lookup(name: string): PrimitiveValue {
|
|
98
97
|
let current: EnvStack | null = this.env;
|
|
@@ -110,4 +109,11 @@ export class RuntimeContext {
|
|
|
110
109
|
public define(name: string, value: PrimitiveValue): void {
|
|
111
110
|
this.env.head.set(name, value);
|
|
112
111
|
}
|
|
112
|
+
public clone(env?: EnvStack): EnvStack {
|
|
113
|
+
const target = env ?? this.env;
|
|
114
|
+
return {
|
|
115
|
+
head: new Map(target.head),
|
|
116
|
+
tail: this.env.tail,
|
|
117
|
+
};
|
|
118
|
+
}
|
|
113
119
|
}
|
|
@@ -2,6 +2,7 @@ import {
|
|
|
2
2
|
Assert,
|
|
3
3
|
Equality,
|
|
4
4
|
Failure,
|
|
5
|
+
isLazyList,
|
|
5
6
|
PrimitiveValue,
|
|
6
7
|
Test,
|
|
7
8
|
TestGroup,
|
|
@@ -84,18 +85,17 @@ class AssertionVisitor extends TraverseVisitor {
|
|
|
84
85
|
this.interpreter.evaluate(node.value, (value) => {
|
|
85
86
|
return () =>
|
|
86
87
|
this.interpreter.evaluate(node.expected, (expected) => {
|
|
87
|
-
|
|
88
|
+
this.lazyRuntime.deepEqual(value, expected, (passed) => {
|
|
88
89
|
if (this.negated === passed) {
|
|
89
90
|
throw new FailedAssert(
|
|
90
|
-
value,
|
|
91
|
-
expected,
|
|
91
|
+
value, expected,
|
|
92
92
|
this.negated
|
|
93
93
|
? `Expected ${JSON.stringify(value)} NOT to be equal to ${JSON.stringify(expected)}`
|
|
94
94
|
: `Expected ${JSON.stringify(expected)}, but got ${JSON.stringify(value)}`,
|
|
95
95
|
);
|
|
96
96
|
}
|
|
97
97
|
return k(undefined);
|
|
98
|
-
})
|
|
98
|
+
})
|
|
99
99
|
});
|
|
100
100
|
});
|
|
101
101
|
}
|
|
@@ -116,42 +116,6 @@ class AssertionVisitor extends TraverseVisitor {
|
|
|
116
116
|
return k(undefined);
|
|
117
117
|
});
|
|
118
118
|
}
|
|
119
|
-
private isEqual(a: any, b: any, k: Continuation<boolean>): Thunk<boolean> {
|
|
120
|
-
if (a === b) return k(true);
|
|
121
|
-
if (a && b && typeof a === "object" && typeof b === "object") {
|
|
122
|
-
return this.lazyRuntime.realizeList(a, (valA) => {
|
|
123
|
-
return () =>
|
|
124
|
-
this.lazyRuntime.realizeList(b, (valB) => {
|
|
125
|
-
if (Array.isArray(valA) && Array.isArray(valB)) {
|
|
126
|
-
if (valA.length !== valB.length) return k(false);
|
|
127
|
-
const checkNext = (index: number): Thunk<boolean> => {
|
|
128
|
-
if (index >= valA.length) return k(true);
|
|
129
|
-
return this.isEqual(valA[index], valB[index], (res) => {
|
|
130
|
-
if (!res) return k(false);
|
|
131
|
-
return () => checkNext(index + 1);
|
|
132
|
-
});
|
|
133
|
-
};
|
|
134
|
-
return checkNext(0);
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
if (Array.isArray(valA) !== Array.isArray(valB)) return k(false);
|
|
138
|
-
|
|
139
|
-
const keys = Object.keys(valA);
|
|
140
|
-
if (keys.length !== Object.keys(valB).length) return k(false);
|
|
141
|
-
const checkNextKey = (index: number): Thunk<boolean> => {
|
|
142
|
-
if (index >= keys.length) return k(true);
|
|
143
|
-
const key = keys[index];
|
|
144
|
-
return this.isEqual(valA[key], valB[key], (res) => {
|
|
145
|
-
if (!res) return k(false);
|
|
146
|
-
return () => checkNextKey(index + 1);
|
|
147
|
-
});
|
|
148
|
-
};
|
|
149
|
-
return checkNextKey(0);
|
|
150
|
-
});
|
|
151
|
-
});
|
|
152
|
-
}
|
|
153
|
-
return k(false);
|
|
154
|
-
}
|
|
155
119
|
}
|
|
156
120
|
|
|
157
121
|
export class TestRunner extends TraverseVisitor {
|
|
@@ -378,6 +378,17 @@ export class InterpreterVisitor
|
|
|
378
378
|
visitComparisonOperation(
|
|
379
379
|
node: ComparisonOperation,
|
|
380
380
|
): CPSThunk<PrimitiveValue> {
|
|
381
|
+
if (node.operator === "Equal" || node.operator === "NotEqual") {
|
|
382
|
+
return (k) =>
|
|
383
|
+
this.evaluate(node.left, (left) => () =>
|
|
384
|
+
this.evaluate(node.right, (right) =>
|
|
385
|
+
this.context.lazyRuntime.deepEqual(left, right, (eq) =>
|
|
386
|
+
k(node.operator === "Equal" ? eq : !eq)
|
|
387
|
+
)
|
|
388
|
+
)
|
|
389
|
+
);
|
|
390
|
+
}
|
|
391
|
+
|
|
381
392
|
return this.processBinary(
|
|
382
393
|
node,
|
|
383
394
|
ComparisonOperationTable,
|
|
@@ -684,6 +695,7 @@ export class InterpreterVisitor
|
|
|
684
695
|
}
|
|
685
696
|
|
|
686
697
|
visitApplication(node: Application): CPSThunk<PrimitiveValue> {
|
|
698
|
+
const { funcRuntime } = this.context
|
|
687
699
|
if (this.context.config.debug) {
|
|
688
700
|
console.log(`[Interpreter] Visiting Application`);
|
|
689
701
|
}
|
|
@@ -695,53 +707,26 @@ export class InterpreterVisitor
|
|
|
695
707
|
"Cannot apply non-function",
|
|
696
708
|
);
|
|
697
709
|
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
...func,
|
|
716
|
-
pendingArgs: args,
|
|
717
|
-
});
|
|
718
|
-
}
|
|
719
|
-
|
|
720
|
-
const argsToConsume = args.slice(0, func.arity);
|
|
721
|
-
const remainingArgs = args.slice(func.arity);
|
|
722
|
-
|
|
723
|
-
const evaluatedArgs = argsToConsume.map((arg) =>
|
|
724
|
-
typeof arg === "function" ? arg() : arg,
|
|
725
|
-
);
|
|
726
|
-
|
|
727
|
-
if (func.closure) this.context.pushEnv(func.closure.head);
|
|
728
|
-
return (cont) => () =>
|
|
729
|
-
this.context.funcRuntime.apply(func, evaluatedArgs, (result) => {
|
|
730
|
-
if (remainingArgs.length > 0) {
|
|
731
|
-
if (isRuntimeFunction(result)) {
|
|
732
|
-
const nextArgs = result.pendingArgs
|
|
733
|
-
? [...result.pendingArgs, ...remainingArgs]
|
|
734
|
-
: remainingArgs;
|
|
735
|
-
|
|
736
|
-
return this.applyArguments(result, nextArgs)(cont);
|
|
737
|
-
} else {
|
|
738
|
-
throw new InterpreterError(
|
|
739
|
-
"Application",
|
|
740
|
-
`Too many arguments provided. Result was '${result}' (not a function), but had ${remainingArgs.length} args left.`,
|
|
741
|
-
);
|
|
742
|
-
}
|
|
710
|
+
const applyFuncToNode = (func: RuntimeFunction): CPSThunk<PrimitiveValue> => (k) =>
|
|
711
|
+
this.evaluate(node.parameter, (arg) => {
|
|
712
|
+
const argThunk = () => arg;
|
|
713
|
+
const allPendingArgs = func.pendingArgs
|
|
714
|
+
? [...func.pendingArgs, argThunk]
|
|
715
|
+
: [argThunk];
|
|
716
|
+
return funcRuntime.applyArguments(func, allPendingArgs)(k);
|
|
717
|
+
});
|
|
718
|
+
if (func.arity === 0) {
|
|
719
|
+
return funcRuntime.applyArguments(func, [])(resultOfFunc => {
|
|
720
|
+
if (!isRuntimeFunction(resultOfFunc))
|
|
721
|
+
throw new InterpreterError(
|
|
722
|
+
"Application",
|
|
723
|
+
`Cannot apply non-function result of arity-0 function`,
|
|
724
|
+
);
|
|
725
|
+
return applyFuncToNode(resultOfFunc)(k);
|
|
726
|
+
});
|
|
743
727
|
}
|
|
744
|
-
|
|
728
|
+
|
|
729
|
+
return applyFuncToNode(func)(k);
|
|
745
730
|
});
|
|
746
731
|
}
|
|
747
732
|
|
|
@@ -985,7 +970,9 @@ export class InterpreterVisitor
|
|
|
985
970
|
return process(0);
|
|
986
971
|
};
|
|
987
972
|
}
|
|
988
|
-
|
|
973
|
+
visitTypeCast(node: TypeCast): CPSThunk<PrimitiveValue> {
|
|
974
|
+
return node.expression.accept(this);
|
|
975
|
+
}
|
|
989
976
|
visitGenerator(node: YuGenerator): CPSThunk<PrimitiveValue> {
|
|
990
977
|
return (k) => this.evaluate(node.expression, k);
|
|
991
978
|
}
|
|
@@ -4,16 +4,16 @@ import {
|
|
|
4
4
|
UnguardedBody,
|
|
5
5
|
Sequence,
|
|
6
6
|
Return,
|
|
7
|
-
EnvStack,
|
|
8
7
|
Function,
|
|
9
8
|
RuntimeFunction,
|
|
9
|
+
isRuntimeFunction,
|
|
10
10
|
} from "yukigo-ast";
|
|
11
11
|
import { Bindings } from "../../index.js";
|
|
12
12
|
import { PatternMatcher } from "../PatternMatcher.js";
|
|
13
13
|
import { ExpressionEvaluator } from "../../utils.js";
|
|
14
14
|
import { InterpreterError } from "../../errors.js";
|
|
15
15
|
import { EnvBuilderVisitor } from "../EnvBuilder.js";
|
|
16
|
-
import { Continuation, Thunk } from "../../trampoline.js";
|
|
16
|
+
import { Continuation, CPSThunk, Thunk, valueToCPS } from "../../trampoline.js";
|
|
17
17
|
import { RuntimeContext } from "../RuntimeContext.js";
|
|
18
18
|
import { InterpreterVisitor } from "../Visitor.js";
|
|
19
19
|
|
|
@@ -35,6 +35,7 @@ export class FunctionRuntime {
|
|
|
35
35
|
): Thunk<PrimitiveValue> {
|
|
36
36
|
const funcName = func.identifier;
|
|
37
37
|
const equations = func.equations;
|
|
38
|
+
const oldEnv = this.context.env;
|
|
38
39
|
|
|
39
40
|
if (this.context.config.debug)
|
|
40
41
|
console.log(
|
|
@@ -44,6 +45,7 @@ export class FunctionRuntime {
|
|
|
44
45
|
|
|
45
46
|
const tryNextEquation = (eqIndex: number): Thunk<PrimitiveValue> => {
|
|
46
47
|
if (eqIndex >= equations.length) {
|
|
48
|
+
this.context.setEnv(oldEnv);
|
|
47
49
|
throw new NonExhaustivePatterns(funcName);
|
|
48
50
|
}
|
|
49
51
|
|
|
@@ -62,7 +64,6 @@ export class FunctionRuntime {
|
|
|
62
64
|
);
|
|
63
65
|
|
|
64
66
|
const localEnv = new Map<string, PrimitiveValue>(bindings);
|
|
65
|
-
const oldEnv = this.context.env;
|
|
66
67
|
if (func.closure) this.context.setEnv(func.closure);
|
|
67
68
|
this.context.pushEnv(localEnv);
|
|
68
69
|
|
|
@@ -124,6 +125,44 @@ export class FunctionRuntime {
|
|
|
124
125
|
return tryNextEquation(0);
|
|
125
126
|
}
|
|
126
127
|
|
|
128
|
+
public applyArguments(
|
|
129
|
+
func: RuntimeFunction,
|
|
130
|
+
args: (PrimitiveValue | (() => PrimitiveValue))[],
|
|
131
|
+
): CPSThunk<PrimitiveValue> {
|
|
132
|
+
if (args.length < func.arity) {
|
|
133
|
+
return valueToCPS({
|
|
134
|
+
...func,
|
|
135
|
+
pendingArgs: args,
|
|
136
|
+
});
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
const argsToConsume = args.slice(0, func.arity);
|
|
140
|
+
const remainingArgs = args.slice(func.arity);
|
|
141
|
+
|
|
142
|
+
const evaluatedArgs = argsToConsume.map((arg) =>
|
|
143
|
+
typeof arg === "function" ? arg() : arg,
|
|
144
|
+
);
|
|
145
|
+
|
|
146
|
+
return (cont) => () =>
|
|
147
|
+
this.apply(func, evaluatedArgs, (result) => {
|
|
148
|
+
if (remainingArgs.length > 0) {
|
|
149
|
+
if (isRuntimeFunction(result)) {
|
|
150
|
+
const nextArgs = result.pendingArgs
|
|
151
|
+
? [...result.pendingArgs, ...remainingArgs]
|
|
152
|
+
: remainingArgs;
|
|
153
|
+
|
|
154
|
+
return this.applyArguments(result, nextArgs)(cont);
|
|
155
|
+
} else {
|
|
156
|
+
throw new InterpreterError(
|
|
157
|
+
"Application",
|
|
158
|
+
`Too many arguments provided. Result was '${result}' (not a function), but had ${remainingArgs.length} args left.`,
|
|
159
|
+
);
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
return cont(result);
|
|
163
|
+
});
|
|
164
|
+
}
|
|
165
|
+
|
|
127
166
|
private preloadDefinitions(
|
|
128
167
|
seq: Sequence,
|
|
129
168
|
evaluatorFactory: EvaluatorFactory,
|
|
@@ -118,14 +118,15 @@ export class LazyRuntime {
|
|
|
118
118
|
evaluator: ExpressionEvaluator,
|
|
119
119
|
k: Continuation<PrimitiveValue>,
|
|
120
120
|
): Thunk<PrimitiveValue> {
|
|
121
|
-
const capturedEnv = this.context.env;
|
|
122
121
|
const ctx = this.context;
|
|
122
|
+
const capturedEnv = ctx.clone();
|
|
123
123
|
return evaluator.evaluate(node.head, (head) => {
|
|
124
|
-
if (
|
|
124
|
+
if (ctx.config.lazyLoading) {
|
|
125
125
|
const consState: InternalConsState = {
|
|
126
126
|
head,
|
|
127
127
|
tailExpr: node.tail,
|
|
128
128
|
evaluator,
|
|
129
|
+
capturedEnv,
|
|
129
130
|
realizedTail: undefined,
|
|
130
131
|
};
|
|
131
132
|
|
|
@@ -140,13 +141,10 @@ export class LazyRuntime {
|
|
|
140
141
|
|
|
141
142
|
if (current.realizedTail === undefined) {
|
|
142
143
|
const prevEnv = ctx.env;
|
|
143
|
-
ctx.setEnv(capturedEnv);
|
|
144
|
+
ctx.setEnv(current.capturedEnv);
|
|
144
145
|
try {
|
|
145
146
|
current.realizedTail = trampoline(
|
|
146
|
-
current.evaluator.evaluate(
|
|
147
|
-
current.tailExpr,
|
|
148
|
-
idContinuation,
|
|
149
|
-
),
|
|
147
|
+
current.evaluator.evaluate(current.tailExpr, idContinuation),
|
|
150
148
|
);
|
|
151
149
|
} finally {
|
|
152
150
|
ctx.setEnv(prevEnv);
|
|
@@ -236,29 +234,34 @@ export class LazyRuntime {
|
|
|
236
234
|
evaluator: ExpressionEvaluator,
|
|
237
235
|
k: Continuation<PrimitiveValue>,
|
|
238
236
|
): Thunk<PrimitiveValue> {
|
|
239
|
-
const capturedEnv = this.context.env;
|
|
240
237
|
const ctx = this.context;
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
const left = trampoline(evaluator.evaluate(node.left, idContinuation));
|
|
238
|
+
|
|
239
|
+
return evaluator.evaluate(node.left, (left) => {
|
|
240
|
+
const capturedEnv = ctx.clone();
|
|
241
|
+
return k(
|
|
242
|
+
createMemoizedStream(function* () {
|
|
247
243
|
if (Array.isArray(left)) yield* left;
|
|
248
244
|
else if (typeof left === "string") yield* left.split("");
|
|
249
245
|
else if (isLazyList(left)) yield* left.generator();
|
|
246
|
+
else throw new Error("Invalid left operand for lazy Concat");
|
|
247
|
+
|
|
248
|
+
// right evaluates lazily on demand
|
|
249
|
+
const prevEnv = ctx.env;
|
|
250
|
+
ctx.setEnv(capturedEnv);
|
|
251
|
+
let right: PrimitiveValue;
|
|
252
|
+
try {
|
|
253
|
+
right = trampoline(evaluator.evaluate(node.right, idContinuation));
|
|
254
|
+
} finally {
|
|
255
|
+
ctx.setEnv(prevEnv);
|
|
256
|
+
}
|
|
250
257
|
|
|
251
|
-
const right = trampoline(
|
|
252
|
-
evaluator.evaluate(node.right, idContinuation),
|
|
253
|
-
);
|
|
254
258
|
if (Array.isArray(right)) yield* right;
|
|
255
259
|
else if (typeof right === "string") yield* right.split("");
|
|
256
260
|
else if (isLazyList(right)) yield* right.generator();
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
);
|
|
261
|
+
else throw new Error("Invalid right operand for lazy Concat");
|
|
262
|
+
}),
|
|
263
|
+
);
|
|
264
|
+
});
|
|
262
265
|
}
|
|
263
266
|
public deepEqual<R = boolean>(
|
|
264
267
|
a: PrimitiveValue,
|
|
@@ -267,35 +270,65 @@ export class LazyRuntime {
|
|
|
267
270
|
): Thunk<R> {
|
|
268
271
|
if (a === b) return k(true);
|
|
269
272
|
|
|
270
|
-
const
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
if (Array.isArray(valA) && Array.isArray(valB)) {
|
|
275
|
-
if (valA.length !== valB.length) return k(false);
|
|
276
|
-
const compareNext = (index: number): Thunk<R> => {
|
|
277
|
-
if (index >= valA.length) return k(true);
|
|
278
|
-
return () =>
|
|
279
|
-
this.deepEqual(valA[index], valB[index], (isEqual) => {
|
|
280
|
-
if (!isEqual) return k(false);
|
|
281
|
-
return () => compareNext(index + 1);
|
|
282
|
-
});
|
|
283
|
-
};
|
|
284
|
-
return compareNext(0);
|
|
285
|
-
}
|
|
286
|
-
|
|
287
|
-
return k(valA == valB);
|
|
288
|
-
};
|
|
273
|
+
const aIsListLike = this.isListLike(a);
|
|
274
|
+
const bIsListLike = this.isListLike(b);
|
|
275
|
+
const eitherIsCollection = this.isCollection(a) || this.isCollection(b);
|
|
289
276
|
|
|
290
|
-
if (
|
|
291
|
-
return this.realizeList(a, (valA) =>
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
277
|
+
if (eitherIsCollection && aIsListLike && bIsListLike) {
|
|
278
|
+
return this.realizeList(a, (valA) => () =>
|
|
279
|
+
this.realizeList(b, (valB) => {
|
|
280
|
+
if (valA.length !== valB.length) return k(false);
|
|
281
|
+
return this.deepEqualCollection(valA, valB, 0, k);
|
|
282
|
+
})
|
|
283
|
+
);
|
|
297
284
|
}
|
|
298
285
|
|
|
299
|
-
return
|
|
286
|
+
if (eitherIsCollection) return k(false); // colección vs número → false
|
|
287
|
+
|
|
288
|
+
if (this.isPlainObject(a) && this.isPlainObject(b))
|
|
289
|
+
return this.deepEqualObject(a, b, k);
|
|
290
|
+
|
|
291
|
+
return k(a == b); // primitivos: number, boolean, string==number
|
|
292
|
+
}
|
|
293
|
+
private isPlainObject(val: unknown): val is Record<string, any> {
|
|
294
|
+
return val !== null && typeof val === "object"
|
|
295
|
+
&& !isLazyList(val) && !Array.isArray(val);
|
|
296
|
+
}
|
|
297
|
+
private isCollection(val: unknown): val is PrimitiveValue[] | LazyList {
|
|
298
|
+
return Array.isArray(val) || isLazyList(val);
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
private isListLike(val: unknown): val is string | PrimitiveValue[] | LazyList {
|
|
302
|
+
return Array.isArray(val) || isLazyList(val) || typeof val === "string";
|
|
303
|
+
}
|
|
304
|
+
private deepEqualCollection<R>(
|
|
305
|
+
a: PrimitiveValue[],
|
|
306
|
+
b: PrimitiveValue[],
|
|
307
|
+
index: number,
|
|
308
|
+
k: Continuation<boolean, R>,
|
|
309
|
+
): Thunk<R> {
|
|
310
|
+
if (index >= a.length) return k(true);
|
|
311
|
+
return this.deepEqual(a[index], b[index], (eq) => {
|
|
312
|
+
if (!eq) return k(false);
|
|
313
|
+
return () => this.deepEqualCollection(a, b, index + 1, k);
|
|
314
|
+
});
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
private deepEqualObject<R>(
|
|
318
|
+
a: Record<string, any>,
|
|
319
|
+
b: Record<string, any>,
|
|
320
|
+
k: Continuation<boolean, R>,
|
|
321
|
+
): Thunk<R> {
|
|
322
|
+
const keys = Object.keys(a);
|
|
323
|
+
if (keys.length !== Object.keys(b).length) return k(false);
|
|
324
|
+
const checkNext = (index: number): Thunk<R> => {
|
|
325
|
+
if (index >= keys.length) return k(true);
|
|
326
|
+
const key = keys[index];
|
|
327
|
+
return this.deepEqual(a[key], b[key], (eq) => {
|
|
328
|
+
if (!eq) return k(false);
|
|
329
|
+
return () => checkNext(index + 1);
|
|
330
|
+
});
|
|
331
|
+
};
|
|
332
|
+
return checkNext(0);
|
|
300
333
|
}
|
|
301
334
|
}
|
|
@@ -5,6 +5,7 @@ import {
|
|
|
5
5
|
ParameterizedType,
|
|
6
6
|
SymbolPrimitive,
|
|
7
7
|
SimpleType,
|
|
8
|
+
Function,
|
|
8
9
|
} from "yukigo-ast";
|
|
9
10
|
|
|
10
11
|
describe("Generic Inspections", () => {
|
|
@@ -23,6 +24,19 @@ describe("Generic Inspections", () => {
|
|
|
23
24
|
return new TypeSignature(identifier, body);
|
|
24
25
|
};
|
|
25
26
|
|
|
27
|
+
it("should allow expectations without args", () => {
|
|
28
|
+
const ast = [new Function(new SymbolPrimitive("func"), [])];
|
|
29
|
+
const rules: InspectionRule[] = [
|
|
30
|
+
{
|
|
31
|
+
binding: "func",
|
|
32
|
+
inspection: "HasBinding",
|
|
33
|
+
expected: true
|
|
34
|
+
},
|
|
35
|
+
];
|
|
36
|
+
const analyzer = new Analyzer();
|
|
37
|
+
const results = analyzer.analyze(ast, rules);
|
|
38
|
+
expect(results[0].passed).to.be.true;
|
|
39
|
+
});
|
|
26
40
|
describe("TypesParameterAs", () => {
|
|
27
41
|
it("should find a match when parameter type matches at correct index", () => {
|
|
28
42
|
const ast = [createTypeSignature("f", ["Int", "String"], "Bool")];
|
|
@@ -22,9 +22,11 @@ import {
|
|
|
22
22
|
RangeExpression,
|
|
23
23
|
Return,
|
|
24
24
|
Sequence,
|
|
25
|
+
SimpleType,
|
|
25
26
|
StringOperation,
|
|
26
27
|
StringPrimitive,
|
|
27
28
|
SymbolPrimitive,
|
|
29
|
+
TypeCast,
|
|
28
30
|
UnguardedBody,
|
|
29
31
|
Variable,
|
|
30
32
|
VariablePattern,
|
|
@@ -892,6 +894,14 @@ describe("Interpreter Spec", () => {
|
|
|
892
894
|
assert.equal(res, 16);
|
|
893
895
|
});
|
|
894
896
|
});
|
|
897
|
+
describe("Evaluates TypeCast", () => {
|
|
898
|
+
it("Type casting of number 2 to string should be '2'", () => {
|
|
899
|
+
const res = interpreter.evaluate(
|
|
900
|
+
new TypeCast(new NumberPrimitive(2), new SimpleType("String", [])),
|
|
901
|
+
);
|
|
902
|
+
assert.equal(res, "2");
|
|
903
|
+
});
|
|
904
|
+
});
|
|
895
905
|
describe("Evaluates Range Expression", () => {
|
|
896
906
|
it("Evaluates [1..5] to full range", () => {
|
|
897
907
|
const range = new RangeExpression(
|