yukigo 0.1.0 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (144) hide show
  1. package/.mocharc.json +3 -3
  2. package/CHANGELOG.md +26 -0
  3. package/README.md +193 -199
  4. package/dist/analyzer/GraphBuilder.d.ts +29 -0
  5. package/dist/analyzer/GraphBuilder.js +99 -0
  6. package/dist/analyzer/index.d.ts +11 -23
  7. package/dist/analyzer/index.js +100 -58
  8. package/dist/analyzer/inspections/functional/functional.d.ts +44 -0
  9. package/dist/analyzer/inspections/functional/functional.js +149 -0
  10. package/dist/analyzer/inspections/functional/smells.d.ts +16 -0
  11. package/dist/analyzer/inspections/functional/smells.js +98 -0
  12. package/dist/analyzer/inspections/{generic.d.ts → generic/generic.d.ts} +70 -43
  13. package/dist/analyzer/inspections/generic/generic.js +604 -0
  14. package/dist/analyzer/inspections/generic/smells.d.ts +61 -0
  15. package/dist/analyzer/inspections/generic/smells.js +349 -0
  16. package/dist/analyzer/inspections/imperative/imperative.d.ts +35 -0
  17. package/dist/analyzer/inspections/imperative/imperative.js +109 -0
  18. package/dist/analyzer/inspections/imperative/smells.d.ts +16 -0
  19. package/dist/analyzer/inspections/imperative/smells.js +58 -0
  20. package/dist/analyzer/inspections/logic/logic.d.ts +32 -0
  21. package/dist/analyzer/inspections/logic/logic.js +96 -0
  22. package/dist/analyzer/inspections/logic/smells.d.ts +15 -0
  23. package/dist/analyzer/inspections/logic/smells.js +60 -0
  24. package/dist/analyzer/inspections/object/object.d.ts +88 -0
  25. package/dist/analyzer/inspections/object/object.js +319 -0
  26. package/dist/analyzer/inspections/object/smells.d.ts +30 -0
  27. package/dist/analyzer/inspections/object/smells.js +135 -0
  28. package/dist/analyzer/utils.d.ts +26 -4
  29. package/dist/analyzer/utils.js +71 -13
  30. package/dist/index.d.ts +1 -0
  31. package/dist/index.js +1 -0
  32. package/dist/interpreter/components/EnvBuilder.d.ts +9 -5
  33. package/dist/interpreter/components/EnvBuilder.js +100 -30
  34. package/dist/interpreter/components/Operations.d.ts +4 -4
  35. package/dist/interpreter/components/Operations.js +17 -2
  36. package/dist/interpreter/components/PatternMatcher.d.ts +47 -17
  37. package/dist/interpreter/components/PatternMatcher.js +264 -119
  38. package/dist/interpreter/components/RuntimeContext.d.ts +35 -0
  39. package/dist/interpreter/components/RuntimeContext.js +93 -0
  40. package/dist/interpreter/components/TestRunner.d.ts +18 -0
  41. package/dist/interpreter/components/TestRunner.js +103 -0
  42. package/dist/interpreter/components/Visitor.d.ts +63 -57
  43. package/dist/interpreter/components/Visitor.js +508 -173
  44. package/dist/interpreter/components/logic/LogicEngine.d.ts +29 -0
  45. package/dist/interpreter/components/logic/LogicEngine.js +259 -0
  46. package/dist/interpreter/components/logic/LogicResolver.d.ts +53 -0
  47. package/dist/interpreter/components/logic/LogicResolver.js +471 -0
  48. package/dist/interpreter/components/logic/LogicTranslator.d.ts +14 -0
  49. package/dist/interpreter/components/logic/LogicTranslator.js +99 -0
  50. package/dist/interpreter/components/runtimes/FunctionRuntime.d.ts +12 -0
  51. package/dist/interpreter/components/runtimes/FunctionRuntime.js +147 -0
  52. package/dist/interpreter/components/runtimes/LazyRuntime.d.ts +19 -0
  53. package/dist/interpreter/components/runtimes/LazyRuntime.js +269 -0
  54. package/dist/interpreter/components/runtimes/ObjectRuntime.d.ts +35 -0
  55. package/dist/interpreter/components/runtimes/ObjectRuntime.js +126 -0
  56. package/dist/interpreter/entities.d.ts +105 -0
  57. package/dist/interpreter/entities.js +96 -0
  58. package/dist/interpreter/errors.d.ts +1 -1
  59. package/dist/interpreter/index.d.ts +4 -12
  60. package/dist/interpreter/index.js +10 -13
  61. package/dist/interpreter/trampoline.d.ts +17 -0
  62. package/dist/interpreter/trampoline.js +38 -0
  63. package/dist/interpreter/utils.d.ts +4 -7
  64. package/dist/interpreter/utils.js +25 -17
  65. package/dist/tester/index.d.ts +25 -0
  66. package/dist/tester/index.js +108 -0
  67. package/dist/utils/helpers.d.ts +0 -4
  68. package/dist/utils/helpers.js +20 -24
  69. package/package.json +2 -2
  70. package/src/analyzer/GraphBuilder.ts +142 -0
  71. package/src/analyzer/index.ts +185 -132
  72. package/src/analyzer/inspections/functional/functional.ts +121 -0
  73. package/src/analyzer/inspections/functional/smells.ts +102 -0
  74. package/src/analyzer/inspections/{generic.ts → generic/generic.ts} +581 -499
  75. package/src/analyzer/inspections/generic/smells.ts +365 -0
  76. package/src/analyzer/inspections/imperative/imperative.ts +101 -0
  77. package/src/analyzer/inspections/imperative/smells.ts +54 -0
  78. package/src/analyzer/inspections/logic/logic.ts +90 -0
  79. package/src/analyzer/inspections/logic/smells.ts +54 -0
  80. package/src/analyzer/inspections/{object.ts → object/object.ts} +264 -282
  81. package/src/analyzer/inspections/object/smells.ts +144 -0
  82. package/src/analyzer/utils.ts +109 -26
  83. package/src/index.ts +3 -2
  84. package/src/interpreter/components/EnvBuilder.ts +202 -97
  85. package/src/interpreter/components/Operations.ts +99 -81
  86. package/src/interpreter/components/PatternMatcher.ts +475 -254
  87. package/src/interpreter/components/RuntimeContext.ts +119 -0
  88. package/src/interpreter/components/TestRunner.ts +151 -0
  89. package/src/interpreter/components/Visitor.ts +1065 -493
  90. package/src/interpreter/components/logic/LogicEngine.ts +519 -0
  91. package/src/interpreter/components/logic/LogicResolver.ts +858 -0
  92. package/src/interpreter/components/logic/LogicTranslator.ts +149 -0
  93. package/src/interpreter/components/runtimes/FunctionRuntime.ts +227 -0
  94. package/src/interpreter/components/runtimes/LazyRuntime.ts +334 -0
  95. package/src/interpreter/components/runtimes/ObjectRuntime.ts +224 -0
  96. package/src/interpreter/errors.ts +47 -47
  97. package/src/interpreter/index.ts +52 -59
  98. package/src/interpreter/trampoline.ts +71 -0
  99. package/src/interpreter/utils.ts +84 -79
  100. package/src/tester/index.ts +128 -0
  101. package/src/utils/helpers.ts +67 -73
  102. package/tests/analyzer/functional.spec.ts +207 -221
  103. package/tests/analyzer/generic.spec.ts +178 -100
  104. package/tests/analyzer/helpers.spec.ts +83 -83
  105. package/tests/analyzer/logic.spec.ts +237 -292
  106. package/tests/analyzer/oop.spec.ts +323 -338
  107. package/tests/analyzer/transitive.spec.ts +166 -0
  108. package/tests/interpreter/EnvBuilder.spec.ts +183 -178
  109. package/tests/interpreter/FunctionRuntime.spec.ts +223 -234
  110. package/tests/interpreter/LazyRuntime.spec.ts +225 -190
  111. package/tests/interpreter/LogicEngine.spec.ts +327 -194
  112. package/tests/interpreter/LogicSubstitution.spec.ts +80 -0
  113. package/tests/interpreter/ObjectRuntime.spec.ts +606 -0
  114. package/tests/interpreter/Operations.spec.ts +220 -220
  115. package/tests/interpreter/PatternSystem.spec.ts +213 -189
  116. package/tests/interpreter/Tests.spec.ts +122 -0
  117. package/tests/interpreter/interpreter.spec.ts +991 -937
  118. package/tests/tester/Tester.spec.ts +153 -0
  119. package/tsconfig.build.json +15 -7
  120. package/tsconfig.json +25 -17
  121. package/dist/analyzer/inspections/functional.d.ts +0 -46
  122. package/dist/analyzer/inspections/functional.js +0 -123
  123. package/dist/analyzer/inspections/generic.js +0 -427
  124. package/dist/analyzer/inspections/imperative.d.ts +0 -37
  125. package/dist/analyzer/inspections/imperative.js +0 -105
  126. package/dist/analyzer/inspections/logic.d.ts +0 -49
  127. package/dist/analyzer/inspections/logic.js +0 -140
  128. package/dist/analyzer/inspections/object.d.ts +0 -83
  129. package/dist/analyzer/inspections/object.js +0 -235
  130. package/dist/interpreter/components/FunctionRuntime.d.ts +0 -8
  131. package/dist/interpreter/components/FunctionRuntime.js +0 -52
  132. package/dist/interpreter/components/LazyRuntime.d.ts +0 -7
  133. package/dist/interpreter/components/LazyRuntime.js +0 -75
  134. package/dist/interpreter/components/LogicEngine.d.ts +0 -21
  135. package/dist/interpreter/components/LogicEngine.js +0 -152
  136. package/dist/interpreter/components/LogicResolver.d.ts +0 -11
  137. package/dist/interpreter/components/LogicResolver.js +0 -87
  138. package/src/analyzer/inspections/functional.ts +0 -159
  139. package/src/analyzer/inspections/imperative.ts +0 -129
  140. package/src/analyzer/inspections/logic.ts +0 -166
  141. package/src/interpreter/components/FunctionRuntime.ts +0 -79
  142. package/src/interpreter/components/LazyRuntime.ts +0 -97
  143. package/src/interpreter/components/LogicEngine.ts +0 -227
  144. package/src/interpreter/components/LogicResolver.ts +0 -130
@@ -1,254 +1,475 @@
1
- import {
2
- ApplicationPattern,
3
- AsPattern,
4
- ASTNode,
5
- ConsPattern,
6
- ConstructorPattern,
7
- FunctorPattern,
8
- isLazyList,
9
- LazyList,
10
- ListPattern,
11
- ListPrimitive,
12
- LiteralPattern,
13
- Pattern,
14
- PrimitiveValue,
15
- SymbolPrimitive,
16
- TuplePattern,
17
- UnionPattern,
18
- VariablePattern,
19
- Visitor,
20
- WildcardPattern,
21
- } from "yukigo-ast";
22
- import { Bindings } from "../index.js";
23
- import { InterpreterVisitor } from "./Visitor.js";
24
- import { createStream } from "../utils.js";
25
-
26
- export class PatternResolver implements Visitor<string> {
27
- visitVariablePattern(node: VariablePattern): string {
28
- return node.name.value;
29
- }
30
-
31
- visitWildcardPattern(node: WildcardPattern): string {
32
- return "_";
33
- }
34
-
35
- visitLiteralPattern(node: LiteralPattern): string {
36
- const { name } = node;
37
- if (name instanceof ListPrimitive)
38
- return String(name.elements.map((elem) => elem.accept(this)));
39
- return String(name.value);
40
- }
41
-
42
- visitTuplePattern(node: TuplePattern): string {
43
- return String(node.elements.map((elem) => elem.accept(this)));
44
- }
45
-
46
- visitListPattern(node: ListPattern): string {
47
- const { elements } = node;
48
- return elements.length === 0
49
- ? "[]"
50
- : String(elements.map((elem) => elem.accept(this)));
51
- }
52
-
53
- visitConsPattern(node: ConsPattern): string {
54
- const head = node.head.accept(this);
55
- const tail = node.tail.accept(this);
56
- return `(${head}:${tail})`;
57
- }
58
-
59
- visitConstructorPattern(node: ConstructorPattern): string {
60
- const constr = node.constr;
61
- const args = node.patterns.map((pat) => pat.accept(this)).join(" ");
62
- return `${constr} ${args}`;
63
- }
64
-
65
- visitFunctorPattern(node: FunctorPattern): string {
66
- // Same as ConstructorPattern (alias)
67
- return this.visitConstructorPattern(
68
- new ConstructorPattern(node.identifier.value, node.args)
69
- );
70
- }
71
-
72
- visitApplicationPattern(node: ApplicationPattern): string {
73
- // Same as FunctorPattern
74
- return this.visitConstructorPattern(
75
- new ConstructorPattern(node.symbol.value, node.args)
76
- );
77
- }
78
-
79
- visitAsPattern(node: AsPattern): string {
80
- const pattern = node.pattern.accept(this);
81
- const alias = node.alias.accept(this);
82
- return `${alias}@${pattern}`;
83
- }
84
-
85
- visit(node: ASTNode): string {
86
- return node.accept(this);
87
- }
88
- }
89
- /**
90
- * Recursively matches a value against a pattern node.
91
- * Updates `bindings` when variables are bound successfully.
92
- * Returns true if the pattern matches, false otherwise.
93
- */
94
- export class PatternMatcher implements Visitor<boolean> {
95
- private value: PrimitiveValue;
96
- private bindings: Bindings;
97
-
98
- constructor(value: PrimitiveValue, bindings: Bindings) {
99
- this.value = value;
100
- this.bindings = bindings;
101
- }
102
-
103
- visitVariablePattern(node: VariablePattern): boolean {
104
- this.bindings.push([node.name.value, this.value]);
105
- return true;
106
- }
107
-
108
- visitWildcardPattern(node: WildcardPattern): boolean {
109
- return true;
110
- }
111
-
112
- visitLiteralPattern(node: LiteralPattern): boolean {
113
- const literalValue = InterpreterVisitor.evaluateLiteral(node.name);
114
- return this.deepEqual(this.value, literalValue);
115
- }
116
-
117
- visitTuplePattern(node: TuplePattern): boolean {
118
- if (!Array.isArray(this.value)) return false;
119
- if (this.value.length !== node.elements.length) return false;
120
-
121
- for (let i = 0; i < node.elements.length; i++) {
122
- const matcher = new PatternMatcher(this.value[i], this.bindings);
123
- if (!node.elements[i].accept(matcher)) return false;
124
- }
125
- return true;
126
- }
127
-
128
- visitListPattern(node: ListPattern): boolean {
129
- // empty list case
130
- if (node.elements.length === 0) {
131
- if (Array.isArray(this.value)) return this.value.length === 0;
132
-
133
- if (isLazyList(this.value)) {
134
- const iter = this.value.generator();
135
- return iter.next().done;
136
- }
137
- return false;
138
- }
139
-
140
- // finite list case
141
- if (Array.isArray(this.value))
142
- return this.matchList(node.elements, this.value);
143
-
144
- // lazy list case
145
- if (isLazyList(this.value)) {
146
- const realized = this.realize(this.value);
147
- return this.matchList(node.elements, realized);
148
- }
149
-
150
- return false;
151
- }
152
- private matchList(elements: Pattern[], value: PrimitiveValue[]) {
153
- if (value.length !== elements.length) return false;
154
- for (let i = 0; i < elements.length; i++) {
155
- const matcher = new PatternMatcher(value[i], this.bindings);
156
- const isMatch = elements[i].accept(matcher);
157
- if (!isMatch) return false;
158
- }
159
- return true;
160
- }
161
-
162
- visitConsPattern(node: ConsPattern): boolean {
163
- const [head, tail] = this.resolveCons(this.value);
164
- if(!head || !tail) return false
165
- const headMatcher = new PatternMatcher(head, this.bindings);
166
- const headMatches = node.head.accept(headMatcher);
167
- if (!headMatches) return false;
168
-
169
- const tailMatcher = new PatternMatcher(tail, this.bindings);
170
- return node.tail.accept(tailMatcher);
171
- }
172
- private resolveCons(list: PrimitiveValue): [PrimitiveValue, PrimitiveValue] {
173
- if (Array.isArray(list))
174
- return list.length === 0 ? [null, null] : [list[0], list.slice(1)];
175
- if (isLazyList(list)) {
176
- const headIter = list.generator();
177
- const next = headIter.next();
178
- if (next.done) return [null, null];
179
- const parentGeneratorFactory = list.generator;
180
- const tailGenerator = function* (): Generator<PrimitiveValue> {
181
- const freshIter = parentGeneratorFactory();
182
- freshIter.next();
183
- let next: IteratorResult<PrimitiveValue, void>;
184
- while (!(next = freshIter.next()).done) yield next.value;
185
- };
186
-
187
- return [next.value, createStream(tailGenerator)];
188
- }
189
- return [null, null]
190
- }
191
-
192
- visitConstructorPattern(node: ConstructorPattern): boolean {
193
- if (!Array.isArray(this.value) || this.value.length === 0) return false;
194
- if (this.value[0] !== node.constr) return false;
195
-
196
- const args = this.value.slice(1);
197
- return this.matchList(node.patterns, args);
198
- }
199
-
200
- visitFunctorPattern(node: FunctorPattern): boolean {
201
- return this.visitConstructorPattern(
202
- new ConstructorPattern(node.identifier.value, node.args)
203
- );
204
- }
205
-
206
- visitApplicationPattern(node: ApplicationPattern): boolean {
207
- return this.visitConstructorPattern(
208
- new ConstructorPattern(node.symbol.value, node.args)
209
- );
210
- }
211
-
212
- visitAsPattern(node: AsPattern): boolean {
213
- const innerMatcher = new PatternMatcher(this.value, this.bindings);
214
- const innerMatches = node.pattern.accept(innerMatcher);
215
- if (!innerMatches) return false;
216
-
217
- const aliasMatcher = new PatternMatcher(this.value, this.bindings);
218
- return node.alias.accept(aliasMatcher);
219
- }
220
-
221
- visitUnionPattern(node: UnionPattern): boolean {
222
- for (const pattern of node.patterns) {
223
- const trialBindings: Bindings = [];
224
- const matcher = new PatternMatcher(this.value, trialBindings);
225
- if (pattern.accept(matcher)) {
226
- this.bindings.push(...trialBindings);
227
- return true;
228
- }
229
- }
230
- return false;
231
- }
232
-
233
- visit(node: ASTNode): boolean {
234
- return node.accept(this);
235
- }
236
-
237
- private deepEqual(a: PrimitiveValue, b: PrimitiveValue): boolean {
238
- if (a === b) return true;
239
- if (typeof a !== typeof b) return false;
240
-
241
- if (Array.isArray(a) && Array.isArray(b)) {
242
- if (a.length !== b.length) return false;
243
- for (let i = 0; i < a.length; i++) {
244
- if (!this.deepEqual(a[i], b[i])) return false;
245
- }
246
- return true;
247
- }
248
-
249
- return false;
250
- }
251
- private realize(value: PrimitiveValue): PrimitiveValue[] {
252
- return new InterpreterVisitor([new Map()], {}).realizeList(value);
253
- }
254
- }
1
+ import {
2
+ ApplicationPattern,
3
+ AsPattern,
4
+ ASTNode,
5
+ ConsPattern,
6
+ ConstructorPattern,
7
+ Expression,
8
+ FunctorPattern,
9
+ isLazyList,
10
+ LazyList,
11
+ ListPattern,
12
+ ListPrimitive,
13
+ LiteralPattern,
14
+ Pattern,
15
+ PrimitiveValue,
16
+ TuplePattern,
17
+ UnionPattern,
18
+ VariablePattern,
19
+ Visitor,
20
+ WildcardPattern,
21
+ TypePattern,
22
+ SimpleType,
23
+ ListType,
24
+ EnvStack,
25
+ } from "yukigo-ast";
26
+ import { Bindings } from "../index.js";
27
+ import { InterpreterVisitor } from "./Visitor.js";
28
+ import { CPSThunk, Thunk, Continuation } from "../trampoline.js";
29
+ import { RuntimeContext } from "./RuntimeContext.js";
30
+ import { ExpressionEvaluator, getYukigoType } from "../utils.js";
31
+
32
+ class SharedSequence {
33
+ private cache: PrimitiveValue[] = [];
34
+ private source: Generator<PrimitiveValue, void, unknown>;
35
+ private isDone: boolean = false;
36
+
37
+ constructor(
38
+ generatorFactory: () => Generator<PrimitiveValue, void, unknown>,
39
+ ) {
40
+ this.source = generatorFactory();
41
+ }
42
+
43
+ get(index: number): { value: PrimitiveValue | null; done: boolean } {
44
+ if (index < this.cache.length)
45
+ return { value: this.cache[index], done: false };
46
+
47
+ if (this.isDone) return { value: null, done: true };
48
+
49
+ while (this.cache.length <= index) {
50
+ const next = this.source.next();
51
+ if (next.done) {
52
+ this.isDone = true;
53
+ return { value: null, done: true };
54
+ }
55
+ this.cache.push(next.value);
56
+ }
57
+
58
+ return { value: this.cache[index], done: false };
59
+ }
60
+ }
61
+ export interface InternalConsState {
62
+ readonly head: PrimitiveValue;
63
+ readonly tailExpr: Expression;
64
+ readonly evaluator: ExpressionEvaluator;
65
+ readonly capturedEnv: EnvStack;
66
+ realizedTail?: PrimitiveValue;
67
+ }
68
+
69
+ export interface MemoizedLazyList extends LazyList {
70
+ _sequence: SharedSequence;
71
+ _offset: number;
72
+ _consState?: InternalConsState;
73
+ toJSON: () => any;
74
+ }
75
+
76
+ export function isMemoizedList(list: unknown): list is MemoizedLazyList {
77
+ return (
78
+ list &&
79
+ typeof list === "object" &&
80
+ "type" in list &&
81
+ "_offset" in list &&
82
+ "_sequence" in list
83
+ );
84
+ }
85
+
86
+ export function createMemoizedStream(
87
+ genFactory: () => Generator<PrimitiveValue, void, unknown>,
88
+ sequence?: SharedSequence,
89
+ offset: number = 0,
90
+ ): MemoizedLazyList {
91
+ const seq = sequence ?? new SharedSequence(genFactory);
92
+
93
+ return {
94
+ type: "LazyList",
95
+ _sequence: seq,
96
+ _offset: offset,
97
+
98
+ generator: function* () {
99
+ let currentIdx = offset;
100
+ while (true) {
101
+ const res = seq.get(currentIdx);
102
+ if (res.done) return;
103
+ yield res.value!;
104
+ currentIdx++;
105
+ }
106
+ },
107
+ toJSON() {
108
+ const iterator = this.generator();
109
+ const buffer: PrimitiveValue[] = [];
110
+ let next = iterator.next();
111
+
112
+ while (!next.done) {
113
+ buffer.push(next.value);
114
+ next = iterator.next();
115
+ }
116
+
117
+ return buffer;
118
+ },
119
+ };
120
+ }
121
+
122
+ export class PatternResolver implements Visitor<string> {
123
+ visitVariablePattern(node: VariablePattern): string {
124
+ return node.name.value;
125
+ }
126
+
127
+ visitWildcardPattern(node: WildcardPattern): string {
128
+ return "_";
129
+ }
130
+
131
+ visitLiteralPattern(node: LiteralPattern): string {
132
+ const { name } = node;
133
+ if (name instanceof ListPrimitive)
134
+ return String(name.value.map((elem) => elem.accept(this)));
135
+ return String(name.value);
136
+ }
137
+
138
+ visitTuplePattern(node: TuplePattern): string {
139
+ return String(node.elements.map((elem) => elem.accept(this)));
140
+ }
141
+
142
+ visitListPattern(node: ListPattern): string {
143
+ const { elements } = node;
144
+ return elements.length === 0
145
+ ? "[]"
146
+ : String(elements.map((elem) => elem.accept(this)));
147
+ }
148
+
149
+ visitConsPattern(node: ConsPattern): string {
150
+ const head = node.left.accept(this);
151
+ const tail = node.right.accept(this);
152
+ return `(${head}:${tail})`;
153
+ }
154
+
155
+ visitConstructorPattern(node: ConstructorPattern): string {
156
+ const constr = node.identifier.value;
157
+ const args = node.args.map((pat) => pat.accept(this)).join(" ");
158
+ return `${constr} ${args}`;
159
+ }
160
+
161
+ visitFunctorPattern(node: FunctorPattern): string {
162
+ // Same as ConstructorPattern (alias)
163
+ return this.visitConstructorPattern(
164
+ new ConstructorPattern(node.identifier, node.args),
165
+ );
166
+ }
167
+
168
+ visitApplicationPattern(node: ApplicationPattern): string {
169
+ // Same as FunctorPattern
170
+ return this.visitConstructorPattern(
171
+ new ConstructorPattern(node.identifier, node.args),
172
+ );
173
+ }
174
+
175
+ visitAsPattern(node: AsPattern): string {
176
+ const alias = node.left.accept(this);
177
+ const pattern = node.right.accept(this);
178
+ return `${alias}@${pattern}`;
179
+ }
180
+
181
+ visitTypePattern(node: TypePattern): string {
182
+ const typeStr = node.targetType.toString();
183
+ return node.innerPattern
184
+ ? `(${typeStr} ${node.innerPattern.accept(this)})`
185
+ : typeStr;
186
+ }
187
+
188
+ visit(node: ASTNode): string {
189
+ return node.accept(this);
190
+ }
191
+ }
192
+ /**
193
+ * Recursively matches a value against a pattern node.
194
+ * Updates `bindings` when variables are bound successfully.
195
+ * Returns true if the pattern matches, false otherwise.
196
+ */
197
+ export class PatternMatcher implements Visitor<CPSThunk<boolean>> {
198
+ constructor(
199
+ private value: PrimitiveValue,
200
+ private bindings: Bindings,
201
+ private ctx: RuntimeContext,
202
+ ) {}
203
+
204
+ visitVariablePattern(node: VariablePattern): CPSThunk<boolean> {
205
+ return (k) => {
206
+ this.bindings.push([node.name.value, this.value]);
207
+ return k(true);
208
+ };
209
+ }
210
+
211
+ visitWildcardPattern(node: WildcardPattern): CPSThunk<boolean> {
212
+ return (k) => k(true);
213
+ }
214
+
215
+ visitLiteralPattern(node: LiteralPattern): CPSThunk<boolean> {
216
+ return (k) => {
217
+ const literalValue = InterpreterVisitor.evaluateLiteral(node.name);
218
+ return this.ctx.lazyRuntime.deepEqual(this.value, literalValue, k);
219
+ };
220
+ }
221
+
222
+ visitTuplePattern(node: TuplePattern): CPSThunk<boolean> {
223
+ return (k) => {
224
+ const processValue = (val: PrimitiveValue): Thunk<boolean> => {
225
+ if (!Array.isArray(val)) return k(false);
226
+ if (val.length !== node.elements.length) return k(false);
227
+
228
+ const matchNext = (index: number): Thunk<boolean> => {
229
+ if (index >= node.elements.length) return k(true);
230
+ const matcher = new PatternMatcher(
231
+ val[index],
232
+ this.bindings,
233
+ this.ctx,
234
+ );
235
+ return node.elements[index].accept(matcher)((isMatch) => {
236
+ if (!isMatch) return k(false);
237
+ return () => matchNext(index + 1);
238
+ });
239
+ };
240
+ return matchNext(0);
241
+ };
242
+
243
+ if (isLazyList(this.value)) {
244
+ return this.ctx.lazyRuntime.realizeList(this.value, (val) => {
245
+ return () => processValue(val);
246
+ });
247
+ }
248
+ return processValue(this.value);
249
+ };
250
+ }
251
+
252
+ visitListPattern(node: ListPattern): CPSThunk<boolean> {
253
+ return (k) => {
254
+ const value = this.value;
255
+ const neededLength = node.elements.length;
256
+
257
+ const finishMatching = (valArr: PrimitiveValue[]): Thunk<boolean> => {
258
+ if (valArr.length !== neededLength) return k(false);
259
+ return this.matchList(node.elements, valArr, k);
260
+ };
261
+
262
+ // empty list case
263
+ if (neededLength === 0) {
264
+ if (Array.isArray(value) || typeof value === "string")
265
+ return k(value.length === 0);
266
+
267
+ if (isLazyList(value)) {
268
+ const iter = value.generator();
269
+ return k(iter.next().done);
270
+ }
271
+ return k(false);
272
+ }
273
+
274
+ if (Array.isArray(value)) return finishMatching(value);
275
+ if (typeof value === "string") return finishMatching(value.split(""));
276
+
277
+ if (isLazyList(value)) {
278
+ return this.ctx.lazyRuntime.realizeList(value, (valArr) => {
279
+ return () => finishMatching(valArr);
280
+ });
281
+ }
282
+
283
+ return k(false);
284
+ };
285
+ }
286
+
287
+ private matchList(
288
+ elements: Pattern[],
289
+ value: PrimitiveValue[],
290
+ k: Continuation<boolean>,
291
+ ): Thunk<boolean> {
292
+ if (value.length !== elements.length) return k(false);
293
+ const matchNext = (index: number): Thunk<boolean> => {
294
+ if (index >= elements.length) return k(true);
295
+ const matcher = new PatternMatcher(
296
+ value[index],
297
+ this.bindings,
298
+ this.ctx,
299
+ );
300
+ return elements[index].accept(matcher)((isMatch) => {
301
+ if (!isMatch) return k(false);
302
+ return () => matchNext(index + 1);
303
+ });
304
+ };
305
+ return matchNext(0);
306
+ }
307
+
308
+ visitConsPattern(node: ConsPattern): CPSThunk<boolean> {
309
+ return (k) => {
310
+ const [head, tail] = this.resolveCons(this.value);
311
+ if (head === null || tail === null) return k(false);
312
+
313
+ const headMatcher = new PatternMatcher(
314
+ head,
315
+ this.bindings,
316
+ this.ctx,
317
+ );
318
+ return node.left.accept(headMatcher)((headMatches) => {
319
+ if (!headMatches) return k(false);
320
+ const tailMatcher = new PatternMatcher(
321
+ tail,
322
+ this.bindings,
323
+ this.ctx,
324
+ );
325
+ return node.right.accept(tailMatcher)(k);
326
+ });
327
+ };
328
+ }
329
+
330
+ visitTypePattern(node: TypePattern): CPSThunk<boolean> {
331
+ return (k) => {
332
+ const actualType = getYukigoType(this.value);
333
+
334
+ let matches = false;
335
+ const targetType = node.targetType;
336
+
337
+ if (targetType instanceof SimpleType) {
338
+ matches = targetType.value === actualType;
339
+ } else if (targetType instanceof ListType) {
340
+ matches = actualType === "YuList";
341
+ }
342
+
343
+ if (!matches) return k(false);
344
+
345
+ if (node.innerPattern) {
346
+ const innerMatcher = new PatternMatcher(
347
+ this.value,
348
+ this.bindings,
349
+ this.ctx,
350
+ );
351
+ return node.innerPattern.accept(innerMatcher)(k);
352
+ }
353
+
354
+ return k(true);
355
+ };
356
+ }
357
+
358
+ private resolveCons(list: PrimitiveValue): [PrimitiveValue, PrimitiveValue] {
359
+ if (Array.isArray(list)) {
360
+ if (list.length === 0) return [null, null];
361
+ const isLazy = this.ctx.config.lazyLoading;
362
+ if (!isLazy) return [list[0], list.slice(1)];
363
+ const tail: LazyList = {
364
+ type: "LazyList",
365
+ generator: function* () {
366
+ for (let i = 1; i < list.length; i++) yield list[i];
367
+ },
368
+ };
369
+ return [list[0], tail];
370
+ }
371
+
372
+ if (typeof list === "string") {
373
+ if (list.length === 0) return [null, null];
374
+
375
+ const isLazy = this.ctx.config.lazyLoading;
376
+ if (!isLazy) return [list[0], list.slice(1)];
377
+ const tail: LazyList = {
378
+ type: "LazyList",
379
+ generator: function* () {
380
+ for (let i = 1; i < list.length; i++) yield list[i];
381
+ },
382
+ };
383
+ return [list[0], tail];
384
+ }
385
+
386
+ // lazy list case
387
+ if (isLazyList(list)) {
388
+ let memoList: MemoizedLazyList;
389
+
390
+ // optimize, convert to memoized
391
+ if (isMemoizedList(list)) {
392
+ memoList = list;
393
+ } else {
394
+ memoList = createMemoizedStream(list.generator);
395
+ }
396
+ const currentRes = memoList._sequence.get(memoList._offset);
397
+
398
+ if (currentRes.done) return [null, null];
399
+ const tail = createMemoizedStream(
400
+ list.generator,
401
+ memoList._sequence,
402
+ memoList._offset + 1,
403
+ );
404
+
405
+ return [currentRes.value!, tail];
406
+ }
407
+
408
+ return [null, null];
409
+ }
410
+
411
+ visitConstructorPattern(node: ConstructorPattern): CPSThunk<boolean> {
412
+ return (k) => {
413
+ if (!Array.isArray(this.value) || this.value.length === 0)
414
+ return k(false);
415
+ if (this.value[0] !== node.identifier.value) return k(false);
416
+
417
+ const args = this.value.slice(1);
418
+ return this.matchList(node.args, args, k);
419
+ };
420
+ }
421
+
422
+ visitFunctorPattern(node: FunctorPattern): CPSThunk<boolean> {
423
+ return this.visitConstructorPattern(
424
+ new ConstructorPattern(node.identifier, node.args),
425
+ );
426
+ }
427
+
428
+ visitApplicationPattern(node: ApplicationPattern): CPSThunk<boolean> {
429
+ return this.visitConstructorPattern(
430
+ new ConstructorPattern(node.identifier, node.args),
431
+ );
432
+ }
433
+
434
+ visitAsPattern(node: AsPattern): CPSThunk<boolean> {
435
+ return (k) => {
436
+ const innerMatcher = new PatternMatcher(
437
+ this.value,
438
+ this.bindings,
439
+ this.ctx,
440
+ );
441
+ return node.right.accept(innerMatcher)((innerMatches) => {
442
+ if (!innerMatches) return k(false);
443
+ const aliasMatcher = new PatternMatcher(
444
+ this.value,
445
+ this.bindings,
446
+ this.ctx,
447
+ );
448
+ return node.left.accept(aliasMatcher)(k);
449
+ });
450
+ };
451
+ }
452
+
453
+ visitUnionPattern(node: UnionPattern): CPSThunk<boolean> {
454
+ return (k) => {
455
+ const tryNext = (index: number): Thunk<boolean> => {
456
+ if (index >= node.elements.length) return k(false);
457
+ const pattern = node.elements[index];
458
+ const trialBindings: Bindings = [];
459
+ const matcher = new PatternMatcher(this.value, trialBindings, this.ctx);
460
+ return pattern.accept(matcher)((isMatch) => {
461
+ if (isMatch) {
462
+ this.bindings.push(...trialBindings);
463
+ return k(true);
464
+ }
465
+ return () => tryNext(index + 1);
466
+ });
467
+ };
468
+ return tryNext(0);
469
+ };
470
+ }
471
+
472
+ visit(node: ASTNode): CPSThunk<boolean> {
473
+ return node.accept(this);
474
+ }
475
+ }