yukigo 0.1.1 → 0.2.2
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 +54 -0
- package/dist/analyzer/index.d.ts +8 -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/dist/src/analyzer/GraphBuilder.d.ts +30 -0
- package/dist/src/analyzer/GraphBuilder.js +100 -0
- package/dist/src/analyzer/index.d.ts +59 -0
- package/dist/src/analyzer/index.js +152 -0
- package/dist/src/analyzer/inspections/functional/functional.d.ts +44 -0
- package/dist/src/analyzer/inspections/functional/functional.js +149 -0
- package/dist/src/analyzer/inspections/functional/smells.d.ts +16 -0
- package/dist/src/analyzer/inspections/functional/smells.js +98 -0
- package/dist/src/analyzer/inspections/generic/generic.d.ts +178 -0
- package/dist/src/analyzer/inspections/generic/generic.js +604 -0
- package/dist/src/analyzer/inspections/generic/smells.d.ts +61 -0
- package/dist/src/analyzer/inspections/generic/smells.js +349 -0
- package/dist/src/analyzer/inspections/imperative/imperative.d.ts +35 -0
- package/dist/src/analyzer/inspections/imperative/imperative.js +109 -0
- package/dist/src/analyzer/inspections/imperative/smells.d.ts +16 -0
- package/dist/src/analyzer/inspections/imperative/smells.js +58 -0
- package/dist/src/analyzer/inspections/logic/logic.d.ts +32 -0
- package/dist/src/analyzer/inspections/logic/logic.js +96 -0
- package/dist/src/analyzer/inspections/logic/smells.d.ts +15 -0
- package/dist/src/analyzer/inspections/logic/smells.js +60 -0
- package/dist/src/analyzer/inspections/object/object.d.ts +90 -0
- package/dist/src/analyzer/inspections/object/object.js +321 -0
- package/dist/src/analyzer/inspections/object/smells.d.ts +30 -0
- package/dist/src/analyzer/inspections/object/smells.js +135 -0
- package/dist/src/analyzer/utils.d.ts +30 -0
- package/dist/src/analyzer/utils.js +78 -0
- package/dist/src/index.d.ts +4 -0
- package/dist/src/index.js +4 -0
- package/dist/src/interpreter/components/EnvBuilder.d.ts +21 -0
- package/dist/src/interpreter/components/EnvBuilder.js +155 -0
- package/dist/src/interpreter/components/Operations.d.ts +14 -0
- package/dist/src/interpreter/components/Operations.js +84 -0
- package/dist/src/interpreter/components/PatternMatcher.d.ts +73 -0
- package/dist/src/interpreter/components/PatternMatcher.js +358 -0
- package/dist/src/interpreter/components/RuntimeContext.d.ts +35 -0
- package/dist/src/interpreter/components/RuntimeContext.js +93 -0
- package/dist/src/interpreter/components/TestRunner.d.ts +19 -0
- package/dist/src/interpreter/components/TestRunner.js +110 -0
- package/dist/src/interpreter/components/Visitor.d.ts +71 -0
- package/dist/src/interpreter/components/Visitor.js +638 -0
- package/dist/src/interpreter/components/logic/LogicEngine.d.ts +29 -0
- package/dist/src/interpreter/components/logic/LogicEngine.js +259 -0
- package/dist/src/interpreter/components/logic/LogicResolver.d.ts +53 -0
- package/dist/src/interpreter/components/logic/LogicResolver.js +484 -0
- package/dist/src/interpreter/components/logic/LogicTranslator.d.ts +14 -0
- package/dist/src/interpreter/components/logic/LogicTranslator.js +99 -0
- package/dist/src/interpreter/components/runtimes/FunctionRuntime.d.ts +12 -0
- package/dist/src/interpreter/components/runtimes/FunctionRuntime.js +149 -0
- package/dist/src/interpreter/components/runtimes/LazyRuntime.d.ts +19 -0
- package/dist/src/interpreter/components/runtimes/LazyRuntime.js +269 -0
- package/dist/src/interpreter/components/runtimes/ObjectRuntime.d.ts +35 -0
- package/dist/{interpreter/components → src/interpreter/components/runtimes}/ObjectRuntime.js +23 -20
- package/dist/src/interpreter/errors.d.ts +19 -0
- package/dist/src/interpreter/errors.js +36 -0
- package/dist/src/interpreter/index.d.ts +24 -0
- package/dist/src/interpreter/index.js +41 -0
- package/dist/src/interpreter/trampoline.d.ts +17 -0
- package/dist/src/interpreter/trampoline.js +38 -0
- package/dist/src/interpreter/utils.d.ts +11 -0
- package/dist/src/interpreter/utils.js +65 -0
- package/dist/src/tester/index.d.ts +25 -0
- package/dist/src/tester/index.js +113 -0
- package/dist/src/utils/helpers.d.ts +13 -0
- package/dist/src/utils/helpers.js +52 -0
- package/dist/utils/helpers.d.ts +5 -1
- package/dist/utils/helpers.js +79 -6
- package/package.json +5 -3
- package/src/analyzer/index.ts +12 -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/src/utils/helpers.ts +111 -10
- package/tests/analyzer/generic.spec.ts +14 -0
- package/tests/analyzer/helpers.spec.ts +14 -8
- package/tests/interpreter/interpreter.spec.ts +10 -0
- package/tests/tester/Tester.spec.ts +0 -1
- 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/tsconfig.tsbuildinfo +0 -1
|
@@ -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
|
}
|
package/src/utils/helpers.ts
CHANGED
|
@@ -1,11 +1,26 @@
|
|
|
1
1
|
import { parseDocument } from "yaml";
|
|
2
|
-
import {
|
|
2
|
+
import {
|
|
3
|
+
InspectionRule,
|
|
4
|
+
TargetSuffix,
|
|
5
|
+
} from "../analyzer/index.js";
|
|
6
|
+
|
|
7
|
+
const declareMap: Record<string, string> = {
|
|
8
|
+
HasBinding: "Declares",
|
|
9
|
+
HasTypeDeclaration: "DeclaresTypeAlias",
|
|
10
|
+
HasTypeSignature: "DeclaresTypeSignature",
|
|
11
|
+
HasVariable: "DeclaresVariable",
|
|
12
|
+
HasDirectRecursion: "DeclaresRecursively",
|
|
13
|
+
};
|
|
3
14
|
|
|
4
15
|
type MulangInspection = {
|
|
5
16
|
inspection: string;
|
|
6
17
|
binding: string;
|
|
18
|
+
args?: string[];
|
|
7
19
|
};
|
|
8
20
|
|
|
21
|
+
const NEGATION_PREFIXES = new Set(["Not", "Except"]);
|
|
22
|
+
const SUFFIX_ARGS = new Set<TargetSuffix>(["like", "except"]);
|
|
23
|
+
|
|
9
24
|
const isValidFormat = (inspection: any): inspection is MulangInspection =>
|
|
10
25
|
typeof inspection === "object" &&
|
|
11
26
|
"inspection" in inspection &&
|
|
@@ -17,22 +32,27 @@ const isValidFormat = (inspection: any): inspection is MulangInspection =>
|
|
|
17
32
|
* @returns An array of InspectionRule objects.
|
|
18
33
|
*/
|
|
19
34
|
export class MulangAdapter {
|
|
20
|
-
public translateMulangInspection(mulangInspection:
|
|
35
|
+
public translateMulangInspection(mulangInspection: unknown): InspectionRule {
|
|
21
36
|
if (!isValidFormat(mulangInspection))
|
|
22
37
|
throw new Error(
|
|
23
|
-
`Skipping malformed Mulang inspection entry: ${mulangInspection}
|
|
38
|
+
`Skipping malformed Mulang inspection entry: ${mulangInspection}`,
|
|
24
39
|
);
|
|
25
40
|
|
|
26
|
-
|
|
27
|
-
const expected
|
|
28
|
-
|
|
29
|
-
|
|
41
|
+
// transforms Mulang v0 to v2
|
|
42
|
+
const { inspection, expected, binding, args } =
|
|
43
|
+
this.transformToV2(mulangInspection);
|
|
44
|
+
|
|
45
|
+
const { resolvedArgs, targetSuffix, matcher } =
|
|
46
|
+
this.retrieveArguments(args);
|
|
30
47
|
|
|
31
48
|
return {
|
|
32
|
-
inspection
|
|
49
|
+
inspection,
|
|
33
50
|
expected,
|
|
34
|
-
|
|
35
|
-
|
|
51
|
+
binding,
|
|
52
|
+
args: resolvedArgs,
|
|
53
|
+
// these should not exist in the InspectionRule if they are undefined
|
|
54
|
+
...(targetSuffix !== undefined && { targetSuffix }),
|
|
55
|
+
...(matcher !== undefined && { matcher }),
|
|
36
56
|
};
|
|
37
57
|
}
|
|
38
58
|
|
|
@@ -64,4 +84,85 @@ export class MulangAdapter {
|
|
|
64
84
|
|
|
65
85
|
return inspectionRules;
|
|
66
86
|
}
|
|
87
|
+
|
|
88
|
+
private transformToV2(mulangInspection: MulangInspection) {
|
|
89
|
+
const parts = mulangInspection.inspection.split(":");
|
|
90
|
+
const {
|
|
91
|
+
expected,
|
|
92
|
+
inspection,
|
|
93
|
+
args: parsedArgs,
|
|
94
|
+
} = this.parseNegation(parts);
|
|
95
|
+
const args = mulangInspection.args ?? parsedArgs;
|
|
96
|
+
const v2 = this.applyV0ToV2(inspection, args, mulangInspection.binding);
|
|
97
|
+
return { expected, ...v2 };
|
|
98
|
+
}
|
|
99
|
+
private parseNegation(parts: string[]) {
|
|
100
|
+
const negated = NEGATION_PREFIXES.has(parts[0]);
|
|
101
|
+
return {
|
|
102
|
+
expected: !negated,
|
|
103
|
+
inspection: parts[negated ? 1 : 0],
|
|
104
|
+
args: parts.slice(negated ? 2 : 1),
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
private applyV0ToV2(inspection: string, args: string[], binding: string) {
|
|
108
|
+
if (inspection in declareMap)
|
|
109
|
+
return {
|
|
110
|
+
inspection: declareMap[inspection],
|
|
111
|
+
...promoteBindingToTarget(args, binding),
|
|
112
|
+
};
|
|
113
|
+
|
|
114
|
+
if (inspection === "HasArity")
|
|
115
|
+
return {
|
|
116
|
+
inspection: `DeclaresComputationWithArity${args[0]}`,
|
|
117
|
+
...promoteBindingToTarget([], binding),
|
|
118
|
+
};
|
|
119
|
+
|
|
120
|
+
if (inspection.startsWith("Has"))
|
|
121
|
+
return {
|
|
122
|
+
inspection: inspection.replace(/^Has(Usage)?/, (_, usage) =>
|
|
123
|
+
usage ? "Uses" : "Uses",
|
|
124
|
+
),
|
|
125
|
+
args,
|
|
126
|
+
binding,
|
|
127
|
+
};
|
|
128
|
+
|
|
129
|
+
return { inspection, args, binding };
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
private retrieveArguments(args: string[]) {
|
|
133
|
+
const targetSuffix = args.find((a): a is TargetSuffix =>
|
|
134
|
+
SUFFIX_ARGS.has(a as TargetSuffix),
|
|
135
|
+
);
|
|
136
|
+
|
|
137
|
+
const matcherIndex = args.findIndex((a) => a.startsWith("With"));
|
|
138
|
+
const matcher =
|
|
139
|
+
matcherIndex !== -1
|
|
140
|
+
? {
|
|
141
|
+
type: "with_" + args[matcherIndex].slice(4).toLowerCase(),
|
|
142
|
+
value: args[matcherIndex + 1],
|
|
143
|
+
}
|
|
144
|
+
: undefined;
|
|
145
|
+
|
|
146
|
+
const consumed = new Set([
|
|
147
|
+
...(targetSuffix ? [targetSuffix] : []),
|
|
148
|
+
...(matcherIndex !== -1
|
|
149
|
+
? [args[matcherIndex], args[matcherIndex + 1]]
|
|
150
|
+
: []),
|
|
151
|
+
]);
|
|
152
|
+
|
|
153
|
+
const resolvedArgs = args.filter((a) => !consumed.has(a));
|
|
154
|
+
|
|
155
|
+
return {
|
|
156
|
+
targetSuffix:
|
|
157
|
+
targetSuffix ??
|
|
158
|
+
(resolvedArgs.length > 0 ? ("named" as TargetSuffix) : undefined),
|
|
159
|
+
matcher,
|
|
160
|
+
resolvedArgs,
|
|
161
|
+
};
|
|
162
|
+
}
|
|
67
163
|
}
|
|
164
|
+
|
|
165
|
+
const promoteBindingToTarget = (args: string[], binding: string) => ({
|
|
166
|
+
args: binding && binding !== "*" ? [binding, ...args] : args,
|
|
167
|
+
binding: "*",
|
|
168
|
+
});
|
|
@@ -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")];
|
|
@@ -31,48 +31,54 @@ expectations:
|
|
|
31
31
|
|
|
32
32
|
const yukigoExpectations =
|
|
33
33
|
mulangAdapter.translateMulangExpectations(mulangExpectations);
|
|
34
|
+
|
|
34
35
|
assert.deepEqual(yukigoExpectations, [
|
|
35
36
|
{
|
|
36
|
-
inspection: "
|
|
37
|
-
binding: "
|
|
38
|
-
args: [],
|
|
37
|
+
inspection: "Declares",
|
|
38
|
+
binding: "*",
|
|
39
|
+
args: ["squareList"],
|
|
39
40
|
expected: true,
|
|
41
|
+
targetSuffix: "named",
|
|
40
42
|
},
|
|
41
43
|
{
|
|
42
|
-
inspection: "
|
|
44
|
+
inspection: "UsesLambdaExpression",
|
|
43
45
|
binding: "squareList",
|
|
44
46
|
args: [],
|
|
45
47
|
expected: true,
|
|
46
48
|
},
|
|
47
49
|
{
|
|
48
|
-
inspection: "
|
|
50
|
+
inspection: "UsesArithmetic",
|
|
49
51
|
binding: "square",
|
|
50
52
|
args: [],
|
|
51
53
|
expected: true,
|
|
52
54
|
},
|
|
53
55
|
{
|
|
54
|
-
inspection: "
|
|
55
|
-
binding: "
|
|
56
|
-
args: [],
|
|
56
|
+
inspection: "Declares",
|
|
57
|
+
binding: "*",
|
|
58
|
+
args: ["doble"],
|
|
57
59
|
expected: false,
|
|
60
|
+
targetSuffix: "named",
|
|
58
61
|
},
|
|
59
62
|
{
|
|
60
63
|
inspection: "Uses",
|
|
61
64
|
binding: "square",
|
|
62
65
|
args: ["x"],
|
|
63
66
|
expected: true,
|
|
67
|
+
targetSuffix: "named",
|
|
64
68
|
},
|
|
65
69
|
{
|
|
66
70
|
inspection: "Uses",
|
|
67
71
|
binding: "squareList",
|
|
68
72
|
args: ["map"],
|
|
69
73
|
expected: true,
|
|
74
|
+
targetSuffix: "named",
|
|
70
75
|
},
|
|
71
76
|
{
|
|
72
77
|
inspection: "Uses",
|
|
73
78
|
binding: "squareList",
|
|
74
79
|
args: ["map"],
|
|
75
80
|
expected: false,
|
|
81
|
+
targetSuffix: "named",
|
|
76
82
|
},
|
|
77
83
|
]);
|
|
78
84
|
});
|
|
@@ -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(
|
|
@@ -1,8 +0,0 @@
|
|
|
1
|
-
import { EquationRuntime, PrimitiveValue, EnvStack } from "yukigo-ast";
|
|
2
|
-
import { ExpressionEvaluator } from "../utils.js";
|
|
3
|
-
export declare class FunctionRuntime {
|
|
4
|
-
static apply(funcName: string, equations: EquationRuntime[], args: PrimitiveValue[], currentEnv: EnvStack, evaluatorFactory: (env: EnvStack) => ExpressionEvaluator): PrimitiveValue;
|
|
5
|
-
private static preloadDefinitions;
|
|
6
|
-
private static patternsMatch;
|
|
7
|
-
private static evaluateSequence;
|
|
8
|
-
}
|