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.
Files changed (104) hide show
  1. package/CHANGELOG.md +54 -0
  2. package/dist/analyzer/index.d.ts +8 -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/dist/src/analyzer/GraphBuilder.d.ts +30 -0
  17. package/dist/src/analyzer/GraphBuilder.js +100 -0
  18. package/dist/src/analyzer/index.d.ts +59 -0
  19. package/dist/src/analyzer/index.js +152 -0
  20. package/dist/src/analyzer/inspections/functional/functional.d.ts +44 -0
  21. package/dist/src/analyzer/inspections/functional/functional.js +149 -0
  22. package/dist/src/analyzer/inspections/functional/smells.d.ts +16 -0
  23. package/dist/src/analyzer/inspections/functional/smells.js +98 -0
  24. package/dist/src/analyzer/inspections/generic/generic.d.ts +178 -0
  25. package/dist/src/analyzer/inspections/generic/generic.js +604 -0
  26. package/dist/src/analyzer/inspections/generic/smells.d.ts +61 -0
  27. package/dist/src/analyzer/inspections/generic/smells.js +349 -0
  28. package/dist/src/analyzer/inspections/imperative/imperative.d.ts +35 -0
  29. package/dist/src/analyzer/inspections/imperative/imperative.js +109 -0
  30. package/dist/src/analyzer/inspections/imperative/smells.d.ts +16 -0
  31. package/dist/src/analyzer/inspections/imperative/smells.js +58 -0
  32. package/dist/src/analyzer/inspections/logic/logic.d.ts +32 -0
  33. package/dist/src/analyzer/inspections/logic/logic.js +96 -0
  34. package/dist/src/analyzer/inspections/logic/smells.d.ts +15 -0
  35. package/dist/src/analyzer/inspections/logic/smells.js +60 -0
  36. package/dist/src/analyzer/inspections/object/object.d.ts +90 -0
  37. package/dist/src/analyzer/inspections/object/object.js +321 -0
  38. package/dist/src/analyzer/inspections/object/smells.d.ts +30 -0
  39. package/dist/src/analyzer/inspections/object/smells.js +135 -0
  40. package/dist/src/analyzer/utils.d.ts +30 -0
  41. package/dist/src/analyzer/utils.js +78 -0
  42. package/dist/src/index.d.ts +4 -0
  43. package/dist/src/index.js +4 -0
  44. package/dist/src/interpreter/components/EnvBuilder.d.ts +21 -0
  45. package/dist/src/interpreter/components/EnvBuilder.js +155 -0
  46. package/dist/src/interpreter/components/Operations.d.ts +14 -0
  47. package/dist/src/interpreter/components/Operations.js +84 -0
  48. package/dist/src/interpreter/components/PatternMatcher.d.ts +73 -0
  49. package/dist/src/interpreter/components/PatternMatcher.js +358 -0
  50. package/dist/src/interpreter/components/RuntimeContext.d.ts +35 -0
  51. package/dist/src/interpreter/components/RuntimeContext.js +93 -0
  52. package/dist/src/interpreter/components/TestRunner.d.ts +19 -0
  53. package/dist/src/interpreter/components/TestRunner.js +110 -0
  54. package/dist/src/interpreter/components/Visitor.d.ts +71 -0
  55. package/dist/src/interpreter/components/Visitor.js +638 -0
  56. package/dist/src/interpreter/components/logic/LogicEngine.d.ts +29 -0
  57. package/dist/src/interpreter/components/logic/LogicEngine.js +259 -0
  58. package/dist/src/interpreter/components/logic/LogicResolver.d.ts +53 -0
  59. package/dist/src/interpreter/components/logic/LogicResolver.js +484 -0
  60. package/dist/src/interpreter/components/logic/LogicTranslator.d.ts +14 -0
  61. package/dist/src/interpreter/components/logic/LogicTranslator.js +99 -0
  62. package/dist/src/interpreter/components/runtimes/FunctionRuntime.d.ts +12 -0
  63. package/dist/src/interpreter/components/runtimes/FunctionRuntime.js +149 -0
  64. package/dist/src/interpreter/components/runtimes/LazyRuntime.d.ts +19 -0
  65. package/dist/src/interpreter/components/runtimes/LazyRuntime.js +269 -0
  66. package/dist/src/interpreter/components/runtimes/ObjectRuntime.d.ts +35 -0
  67. package/dist/{interpreter/components → src/interpreter/components/runtimes}/ObjectRuntime.js +23 -20
  68. package/dist/src/interpreter/errors.d.ts +19 -0
  69. package/dist/src/interpreter/errors.js +36 -0
  70. package/dist/src/interpreter/index.d.ts +24 -0
  71. package/dist/src/interpreter/index.js +41 -0
  72. package/dist/src/interpreter/trampoline.d.ts +17 -0
  73. package/dist/src/interpreter/trampoline.js +38 -0
  74. package/dist/src/interpreter/utils.d.ts +11 -0
  75. package/dist/src/interpreter/utils.js +65 -0
  76. package/dist/src/tester/index.d.ts +25 -0
  77. package/dist/src/tester/index.js +113 -0
  78. package/dist/src/utils/helpers.d.ts +13 -0
  79. package/dist/src/utils/helpers.js +52 -0
  80. package/dist/utils/helpers.d.ts +5 -1
  81. package/dist/utils/helpers.js +79 -6
  82. package/package.json +5 -3
  83. package/src/analyzer/index.ts +12 -2
  84. package/src/interpreter/components/PatternMatcher.ts +2 -0
  85. package/src/interpreter/components/RuntimeContext.ts +10 -4
  86. package/src/interpreter/components/TestRunner.ts +4 -40
  87. package/src/interpreter/components/Visitor.ts +34 -47
  88. package/src/interpreter/components/runtimes/FunctionRuntime.ts +42 -3
  89. package/src/interpreter/components/runtimes/LazyRuntime.ts +82 -49
  90. package/src/utils/helpers.ts +111 -10
  91. package/tests/analyzer/generic.spec.ts +14 -0
  92. package/tests/analyzer/helpers.spec.ts +14 -8
  93. package/tests/interpreter/interpreter.spec.ts +10 -0
  94. package/tests/tester/Tester.spec.ts +0 -1
  95. package/dist/interpreter/components/FunctionRuntime.d.ts +0 -8
  96. package/dist/interpreter/components/FunctionRuntime.js +0 -81
  97. package/dist/interpreter/components/LazyRuntime.d.ts +0 -7
  98. package/dist/interpreter/components/LazyRuntime.js +0 -94
  99. package/dist/interpreter/components/LogicEngine.d.ts +0 -24
  100. package/dist/interpreter/components/LogicEngine.js +0 -173
  101. package/dist/interpreter/components/LogicResolver.d.ts +0 -10
  102. package/dist/interpreter/components/LogicResolver.js +0 -97
  103. package/dist/interpreter/components/ObjectRuntime.d.ts +0 -33
  104. 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
- return this.evaluate(node.parameter, (arg) => {
699
- const argThunk = () => arg;
700
- const allPendingArgs = func.pendingArgs
701
- ? [...func.pendingArgs, argThunk]
702
- : [argThunk];
703
-
704
- return this.applyArguments(func, allPendingArgs)(k);
705
- });
706
- });
707
- }
708
-
709
- private applyArguments(
710
- func: RuntimeFunction,
711
- args: (PrimitiveValue | (() => PrimitiveValue))[],
712
- ): CPSThunk<PrimitiveValue> {
713
- if (args.length < func.arity) {
714
- return valueToCPS({
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
- return cont(result);
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 (this.context.config.lazyLoading) {
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
- return k(
242
- createMemoizedStream(function* () {
243
- const prevEnv = ctx.env;
244
- ctx.setEnv(capturedEnv);
245
- try {
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
- } finally {
258
- ctx.setEnv(prevEnv);
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 compareValues = (valA: any, valB: any): Thunk<R> => {
271
- if (typeof valA === "string" && typeof valB === "string")
272
- return k(valA === valB);
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 (isLazyList(a) || isLazyList(b)) {
291
- return this.realizeList(a, (valA) => {
292
- return () =>
293
- this.realizeList(b, (valB) => {
294
- return () => compareValues(valA, valB);
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 compareValues(a, b);
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
  }
@@ -1,11 +1,26 @@
1
1
  import { parseDocument } from "yaml";
2
- import { InspectionRule } from "../analyzer/index.js";
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: any): InspectionRule {
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
- const inspection: string[] = mulangInspection.inspection.split(":");
27
- const expected: boolean =
28
- inspection[0] !== "Not" && inspection[0] !== "Except";
29
- const args: string[] = inspection.slice(expected ? 1 : 2);
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: expected ? inspection[0] : inspection[1],
49
+ inspection,
33
50
  expected,
34
- args,
35
- binding: mulangInspection.binding,
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: "HasBinding",
37
- binding: "squareList",
38
- args: [],
37
+ inspection: "Declares",
38
+ binding: "*",
39
+ args: ["squareList"],
39
40
  expected: true,
41
+ targetSuffix: "named",
40
42
  },
41
43
  {
42
- inspection: "HasLambdaExpression",
44
+ inspection: "UsesLambdaExpression",
43
45
  binding: "squareList",
44
46
  args: [],
45
47
  expected: true,
46
48
  },
47
49
  {
48
- inspection: "HasArithmetic",
50
+ inspection: "UsesArithmetic",
49
51
  binding: "square",
50
52
  args: [],
51
53
  expected: true,
52
54
  },
53
55
  {
54
- inspection: "HasBinding",
55
- binding: "doble",
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(
@@ -12,7 +12,6 @@ import {
12
12
  SymbolPrimitive,
13
13
  ArithmeticBinaryOperation,
14
14
  Lambda,
15
- Call,
16
15
  Application,
17
16
  VariablePattern,
18
17
  } from "yukigo-ast";
@@ -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
- }