Skip to content

Commit 953a57c

Browse files
fix: alpha renaming with multiple clashes
1 parent a677cba commit 953a57c

22 files changed

+1546
-617
lines changed

src/tracer/__tests__/__snapshots__/tracer_full.ts.snap

Lines changed: 1219 additions & 442 deletions
Large diffs are not rendered by default.

src/tracer/__tests__/tracer_debug.ts

Lines changed: 11 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -93,26 +93,18 @@ test('function calling', () => {
9393
*/
9494
test('general', () => {
9595
const code = `
96-
function repeat_pattern(n, p, r) {
97-
function twice_p(r) {
98-
return p(p(r));
99-
}
100-
return n === 0
101-
? r
102-
: n % 2 !== 0
103-
? repeat_pattern(n - 1, p, p(r))
104-
: repeat_pattern(n / 2, twice_p, r);
105-
}
106-
107-
function plus_one(x) {
108-
return x + 1;
109-
}
110-
111-
repeat_pattern(5, plus_one, 0);
96+
const f = x => {
97+
const x_1 = 1;
98+
return x_1 => x_2 + g();
99+
};
100+
const g = () => x + x_1;
101+
const x_2 = 0;
102+
const x_1 = 2;
103+
const x = 1;
104+
f(1)(1);
112105
`
113106
const program = parse(code, { ecmaVersion: 10 })!
114-
const steps = getSteps(convert(program), {stepLimit: 200})
107+
const steps = getSteps(convert(program), { stepLimit: 200 })
115108
const output = steps.map(stringifyWithExplanation)
116-
console.log(output.join('\n'))
109+
console.log(output)
117110
})
118-

src/tracer/__tests__/tracer_full.ts

Lines changed: 34 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -331,6 +331,23 @@ describe('Alpha renaming', () => {
331331
expect(steps[steps.length - 1]).toEqual('3;\n[noMarker] Evaluation complete\n')
332332
})
333333

334+
test(`multiple clash for lambda function with block expression`, async () => {
335+
const code = `
336+
const f = x => {
337+
const x_1 = 1;
338+
return x_1 => x_2 + g();
339+
};
340+
const g = () => x + x_1;
341+
const x_2 = 0;
342+
const x_1 = 2;
343+
const x = 1;
344+
f(1)(1);
345+
`
346+
const steps = await codify(acornParser(code))
347+
expect(steps.join('\n')).toMatchSnapshot()
348+
expect(steps[steps.length - 1]).toEqual('3;\n[noMarker] Evaluation complete\n')
349+
})
350+
334351
test(`multiple clash 2 for function expression`, async () => {
335352
const code = `
336353
function f(x) {
@@ -1308,4 +1325,20 @@ describe(`Expressions: conditionals`, () => {
13081325
expect(steps.join('\n')).toMatchSnapshot()
13091326
expect(steps[steps.length - 1]).toEqual('5;\n[noMarker] Evaluation complete\n')
13101327
})
1311-
})
1328+
})
1329+
1330+
1331+
// Other tests
1332+
test("Church numerals", async () => {
1333+
const code = `
1334+
const one = f => x => f(x);
1335+
const inc = a => f => x => f(a(f)(x));
1336+
const decode = a => a(x => x + 1)(0);
1337+
decode(inc(inc(one))) === 3;
1338+
1339+
`
1340+
const steps = await codify(acornParser(code))
1341+
expect(steps.join('\n')).toMatchSnapshot()
1342+
expect(steps.join('\n')).toMatchSnapshot()
1343+
expect(steps[steps.length - 1]).toEqual('true;\n[noMarker] Evaluation complete\n')
1344+
});

src/tracer/interface.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,5 +8,6 @@ export interface StepperBaseNode {
88
oneStep(): StepperBaseNode
99
substitute(id: StepperPattern, value: StepperExpression): StepperBaseNode
1010
freeNames(): string[]
11+
allNames(): string[]
1112
rename(before: string, after: string): StepperBaseNode
1213
}

src/tracer/nodes/Expression/ArrayExpression.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,14 @@ export class StepperArrayExpression implements ArrayExpression, StepperBaseNode
8181
return Array.from(new Set(names))
8282
}
8383

84+
allNames(): string[] {
85+
const names = this.elements
86+
.filter(element => element !== null)
87+
.map(element => element!.allNames())
88+
.flat()
89+
return Array.from(new Set(names))
90+
}
91+
8492
rename(before: string, after: string): StepperExpression {
8593
return new StepperArrayExpression(
8694
this.elements.map(element => (element ? element.rename(before, after) : null))

src/tracer/nodes/Expression/ArrowFunctionExpression.ts

Lines changed: 23 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -89,7 +89,7 @@ export class StepperArrowFunctionExpression implements ArrowFunctionExpression,
8989

9090
scanAllDeclarationNames(): string[] {
9191
const paramNames = this.params.map(param => param.name);
92-
92+
9393
let bodyDeclarations: string[] = [];
9494
// @ts-ignore
9595
if (this.body.type === 'BlockStatement') {
@@ -103,24 +103,32 @@ export class StepperArrowFunctionExpression implements ArrowFunctionExpression,
103103
return [...paramNames, ...bodyDeclarations];
104104
}
105105

106-
substitute(id: StepperPattern, value: StepperExpression): StepperExpression {
106+
// TODO: Fix name handling for lambda
107+
substitute(id: StepperPattern, value: StepperExpression, upperBoundName?: string[]): StepperExpression {
107108
const valueFreeNames = value.freeNames()
108109
const scopeNames = this.scanAllDeclarationNames()
109110
const repeatedNames = valueFreeNames.filter(name => scopeNames.includes(name))
110111

111112
var currentArrowFunction: StepperArrowFunctionExpression = this;
112-
for (var index in repeatedNames) {
113-
const name = repeatedNames[index]
114-
currentArrowFunction = currentArrowFunction.rename(name, getFreshName(name)) as StepperArrowFunctionExpression
113+
let protectedNamesSet = new Set([this.allNames(), upperBoundName ?? []].flat());
114+
repeatedNames.forEach(name => protectedNamesSet.delete(name));
115+
const protectedNames = Array.from(protectedNamesSet);
116+
const newNames = getFreshName(repeatedNames, protectedNames);
117+
for (var index in newNames) {
118+
currentArrowFunction = currentArrowFunction
119+
.rename(repeatedNames[index], newNames[index]) as StepperArrowFunctionExpression
115120
}
116121

122+
117123
if (currentArrowFunction.scanAllDeclarationNames().includes(id.name)) {
118124
return currentArrowFunction;
119125
}
120126

121127
return new StepperArrowFunctionExpression(
122128
currentArrowFunction.params,
123-
currentArrowFunction.body.substitute(id, value),
129+
currentArrowFunction.body.substitute(id,
130+
value,
131+
currentArrowFunction.params.flatMap(p => p.allNames())),
124132
currentArrowFunction.name
125133
)
126134
}
@@ -132,6 +140,13 @@ export class StepperArrowFunctionExpression implements ArrowFunctionExpression,
132140
return this.body.freeNames().filter(name => !paramNames.includes(name))
133141
}
134142

143+
allNames(): string[] {
144+
const paramNames = this.params
145+
.filter(param => param.type === 'Identifier')
146+
.map(param => param.name)
147+
return Array.from(new Set([paramNames, this.body.allNames()].flat()))
148+
}
149+
135150
rename(before: string, after: string): StepperExpression {
136151
return new StepperArrowFunctionExpression(
137152
this.params.map(param => param.rename(before, after)),
@@ -143,3 +158,5 @@ export class StepperArrowFunctionExpression implements ArrowFunctionExpression,
143158
)
144159
}
145160
}
161+
162+

src/tracer/nodes/Expression/BinaryExpression.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -134,6 +134,10 @@ export class StepperBinaryExpression implements BinaryExpression, StepperBaseNod
134134
return Array.from(new Set([this.left.freeNames(), this.right.freeNames()].flat()));
135135
}
136136

137+
allNames(): string[] {
138+
return Array.from(new Set([this.left.allNames(), this.right.allNames()].flat()));
139+
}
140+
137141
rename(before: string, after: string): StepperExpression {
138142
return new StepperBinaryExpression(this.operator,
139143
this.left.rename(before, after), this.right.rename(before, after));

src/tracer/nodes/Expression/BlockExpression.ts

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -149,8 +149,13 @@ export class StepperBlockExpression implements StepperBaseNode {
149149
const scopeNames = this.scanAllDeclarationNames();
150150
const repeatedNames = valueFreeNames.filter(name => scopeNames.includes(name));
151151
let currentBlockExpression: StepperBlockExpression = this;
152-
for (const name of repeatedNames) {
153-
currentBlockExpression = currentBlockExpression.rename(name, getFreshName(name)) as StepperBlockExpression;
152+
let protectedNamesSet = new Set(this.allNames());
153+
repeatedNames.forEach(name => protectedNamesSet.delete(name));
154+
const protectedNames = Array.from(protectedNamesSet);
155+
const newNames = getFreshName(repeatedNames, protectedNames);
156+
for (var index in newNames) {
157+
currentBlockExpression = currentBlockExpression
158+
.rename(repeatedNames[index], newNames[index]) as StepperBlockExpression
154159
}
155160

156161
if (currentBlockExpression.scanAllDeclarationNames().includes(id.name)) {
@@ -174,6 +179,10 @@ export class StepperBlockExpression implements StepperBaseNode {
174179
return Array.from(names);
175180
}
176181

182+
allNames(): string[] {
183+
return Array.from(new Set(this.body.flatMap((ast) => ast.allNames())));
184+
}
185+
177186
rename(before: string, after: string): StepperBlockExpression {
178187
return new StepperBlockExpression(
179188
this.body.map(statement => statement.rename(before, after) as StepperStatement)

src/tracer/nodes/Expression/ConditionalExpression.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,14 @@ export class StepperConditionalExpression implements ConditionalExpression, Step
9696
]))
9797
}
9898

99+
allNames(): string[] {
100+
return Array.from(new Set([
101+
...this.test.allNames(),
102+
...this.consequent.allNames(),
103+
...this.alternate.allNames()
104+
]))
105+
}
106+
99107
rename(before: string, after: string): StepperExpression {
100108
return new StepperConditionalExpression(
101109
this.test.rename(before, after),

src/tracer/nodes/Expression/FunctionApplication.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -154,6 +154,13 @@ export class StepperFunctionApplication implements SimpleCallExpression, Stepper
154154
]))
155155
}
156156

157+
allNames(): string[] {
158+
return Array.from(new Set([
159+
...this.callee.allNames(),
160+
...this.arguments.flatMap(arg => arg.allNames())
161+
]))
162+
}
163+
157164
rename(before: string, after: string): StepperExpression {
158165
return new StepperFunctionApplication(
159166
this.callee.rename(before, after),

0 commit comments

Comments
 (0)