Skip to content

Commit a4485eb

Browse files
committed
Add stepper functionality with lambda and user-defined function support
1 parent 7ce0ea1 commit a4485eb

File tree

14 files changed

+1122
-0
lines changed

14 files changed

+1122
-0
lines changed

src/index.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,9 @@ export { initialise } from "./conductor/runner/util/initialise";
2929
export * from "./utils/encoder-visitor";
3030
export { unparse } from "./utils/reverse_parser";
3131

32+
// Export tracer functionality
33+
export { stepExpression } from "./tracer/stepper";
34+
3235
const JS_KEYWORDS: string[] = [
3336
"break",
3437
"case",

src/tracer/generator.ts

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
import { StepperBaseNode } from './interface';
2+
import { StepperLiteral } from './nodes/Expression/Literal';
3+
import { StepperBinaryExpression } from './nodes/Expression/BinaryExpression';
4+
import { StepperIdentifier } from './nodes/Expression/Identifier';
5+
import { StepperFunctionApplication } from './nodes/Expression/FunctionApplication';
6+
import { StepperLambdaExpression } from './nodes/Expression/LambdaExpression';
7+
import { StepperProgram } from './nodes/Program';
8+
9+
const undefinedNode = new StepperLiteral('undefined');
10+
11+
// Helper function to convert nodes without circular dependency
12+
function convertNode(node: any): StepperBaseNode {
13+
const nodeType = node.constructor.name;
14+
15+
switch (nodeType) {
16+
case 'NumericLiteral':
17+
case 'BooleanLiteral':
18+
case 'StringLiteral':
19+
return StepperLiteral.create(node);
20+
case 'Identifier':
21+
return StepperIdentifier.create(node);
22+
case 'Application':
23+
return new StepperFunctionApplication(
24+
convertNode(node.operator),
25+
node.operands.map(convertNode)
26+
);
27+
case 'Lambda':
28+
return new StepperLambdaExpression(
29+
node.params || [],
30+
convertNode(node.body)
31+
);
32+
case 'Sequence':
33+
return new StepperProgram(
34+
node.expressions.map(convertNode)
35+
);
36+
default:
37+
return undefinedNode;
38+
}
39+
}
40+
41+
const nodeConverters: { [Key: string]: (node: any) => StepperBaseNode } = {
42+
NumericLiteral: (node: any) => StepperLiteral.create(node),
43+
BooleanLiteral: (node: any) => StepperLiteral.create(node),
44+
StringLiteral: (node: any) => StepperLiteral.create(node),
45+
Identifier: (node: any) => StepperIdentifier.create(node),
46+
Application: (node: any) => convertNode(node),
47+
Lambda: (node: any) => convertNode(node),
48+
Sequence: (node: any) => convertNode(node)
49+
};
50+
51+
export function convert(node: any): StepperBaseNode {
52+
const converter = nodeConverters[node.type as keyof typeof nodeConverters];
53+
return converter ? converter(node) : undefinedNode;
54+
}
55+
56+
export function explain(node: StepperBaseNode): string {
57+
// Generate explanation based on node type
58+
switch (node.type) {
59+
case 'Literal':
60+
return `Evaluated to literal value: ${node.toString()}`;
61+
case 'BinaryExpression':
62+
return `Evaluated binary expression: ${node.toString()}`;
63+
case 'Identifier':
64+
return `Variable reference: ${node.toString()}`;
65+
case 'FunctionApplication':
66+
return `Function application: ${node.toString()}`;
67+
case 'LambdaExpression':
68+
return `Lambda expression: ${node.toString()}`;
69+
default:
70+
return `Processed ${node.type}`;
71+
}
72+
}

src/tracer/index.ts

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
import { StepperBaseNode } from './interface';
2+
3+
export let redex: { preRedex: StepperBaseNode[]; postRedex: StepperBaseNode[] } = {
4+
preRedex: [],
5+
postRedex: []
6+
};
7+
8+
export interface Marker {
9+
redex?: StepperBaseNode | null;
10+
redexType?: 'beforeMarker' | 'afterMarker';
11+
explanation?: string;
12+
}
13+
14+
export interface IStepperPropContents {
15+
ast: StepperBaseNode;
16+
markers: Marker[];
17+
}
18+
19+
// Export all modules
20+
export * from './generator';
21+
export * from './steppers';
22+
export * from './stepper';
23+
export * from './nodes';

src/tracer/interface.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
import { StepperExpression, StepperPattern } from './nodes';
2+
3+
export interface StepperBaseNode {
4+
type: string;
5+
isContractible(): boolean;
6+
isOneStepPossible(): boolean;
7+
contract(): StepperBaseNode;
8+
oneStep(): StepperBaseNode;
9+
substitute(id: StepperPattern, value: StepperExpression): StepperBaseNode;
10+
freeNames(): string[];
11+
allNames(): string[];
12+
rename(before: string, after: string): StepperBaseNode;
13+
toString(): string;
14+
}
Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
import { StepperBaseNode } from '../../interface';
2+
import { StepperExpression, StepperPattern } from '../index';
3+
4+
export class StepperBinaryExpression implements StepperBaseNode {
5+
type = 'BinaryExpression';
6+
operator: string;
7+
left: StepperBaseNode;
8+
right: StepperBaseNode;
9+
10+
constructor(operator: string, left: StepperBaseNode, right: StepperBaseNode) {
11+
this.operator = operator;
12+
this.left = left;
13+
this.right = right;
14+
}
15+
16+
static create(node: any): StepperBinaryExpression {
17+
// This would need to be implemented based on how Scheme binary expressions are parsed
18+
// For now, creating a basic implementation
19+
return new StepperBinaryExpression(
20+
node.operator || '+',
21+
node.left || { type: 'Literal', value: 0 },
22+
node.right || { type: 'Literal', value: 0 }
23+
);
24+
}
25+
26+
isContractible(): boolean {
27+
return this.left.isContractible() && this.right.isContractible();
28+
}
29+
30+
isOneStepPossible(): boolean {
31+
return !this.left.isContractible() || !this.right.isContractible();
32+
}
33+
34+
contract(): StepperBaseNode {
35+
if (!this.isContractible()) {
36+
throw new Error('Cannot contract non-contractible expression');
37+
}
38+
39+
// Evaluate the binary expression
40+
const leftValue = this.left.contract();
41+
const rightValue = this.right.contract();
42+
43+
if (leftValue.type === 'Literal' && rightValue.type === 'Literal') {
44+
const left = (leftValue as any).value;
45+
const right = (rightValue as any).value;
46+
let result: any;
47+
48+
switch (this.operator) {
49+
case '+':
50+
result = left + right;
51+
break;
52+
case '-':
53+
result = left - right;
54+
break;
55+
case '*':
56+
result = left * right;
57+
break;
58+
case '/':
59+
result = left / right;
60+
break;
61+
default:
62+
throw new Error(`Unknown operator: ${this.operator}`);
63+
}
64+
65+
return { type: 'Literal', value: result, raw: String(result), toString: () => String(result) } as any;
66+
}
67+
68+
return this;
69+
}
70+
71+
oneStep(): StepperBaseNode {
72+
if (!this.left.isContractible()) {
73+
return new StepperBinaryExpression(this.operator, this.left.oneStep(), this.right);
74+
}
75+
if (!this.right.isContractible()) {
76+
return new StepperBinaryExpression(this.operator, this.left, this.right.oneStep());
77+
}
78+
return this.contract();
79+
}
80+
81+
substitute(id: StepperPattern, value: StepperExpression): StepperBaseNode {
82+
return new StepperBinaryExpression(
83+
this.operator,
84+
this.left.substitute(id, value),
85+
this.right.substitute(id, value)
86+
);
87+
}
88+
89+
freeNames(): string[] {
90+
return [...this.left.freeNames(), ...this.right.freeNames()];
91+
}
92+
93+
allNames(): string[] {
94+
return [...this.left.allNames(), ...this.right.allNames()];
95+
}
96+
97+
rename(before: string, after: string): StepperBaseNode {
98+
return new StepperBinaryExpression(
99+
this.operator,
100+
this.left.rename(before, after),
101+
this.right.rename(before, after)
102+
);
103+
}
104+
105+
toString(): string {
106+
return `(${this.operator} ${this.left.toString()} ${this.right.toString()})`;
107+
}
108+
}
Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
import { StepperBaseNode } from '../../interface';
2+
import { StepperExpression, StepperPattern } from '../index';
3+
4+
export class StepperFunctionApplication implements StepperBaseNode {
5+
type = 'FunctionApplication';
6+
operator: StepperBaseNode;
7+
operands: StepperBaseNode[];
8+
9+
constructor(operator: StepperBaseNode, operands: StepperBaseNode[]) {
10+
this.operator = operator;
11+
this.operands = operands;
12+
}
13+
14+
static create(node: any): StepperFunctionApplication {
15+
// This will be handled by the convertNode function in generator.ts
16+
return new StepperFunctionApplication(
17+
node.operator,
18+
node.operands
19+
);
20+
}
21+
22+
isContractible(): boolean {
23+
// Check if operator is a lambda and all operands are literals
24+
if (this.operator.type === 'LambdaExpression') {
25+
return this.operands.every(op => op.isContractible());
26+
}
27+
return false;
28+
}
29+
30+
isOneStepPossible(): boolean {
31+
// Can step if any operand is not contractible, or if operator is lambda and all operands are contractible
32+
return this.operands.some(op => !op.isContractible()) || this.isContractible();
33+
}
34+
35+
contract(): StepperBaseNode {
36+
if (!this.isContractible()) {
37+
throw new Error('Cannot contract non-contractible expression');
38+
}
39+
40+
// Perform beta-reduction for lambda applications
41+
if (this.operator.type === 'LambdaExpression') {
42+
const lambda = this.operator as any;
43+
let body = lambda.body;
44+
45+
// Substitute parameters with arguments
46+
for (let i = 0; i < lambda.params.length && i < this.operands.length; i++) {
47+
const param = lambda.params[i];
48+
const arg = this.operands[i];
49+
body = body.substitute(param, arg);
50+
}
51+
52+
return body;
53+
}
54+
55+
return this;
56+
}
57+
58+
oneStep(): StepperBaseNode {
59+
// First, step any non-contractible operands
60+
const steppedOperands = this.operands.map(op =>
61+
op.isContractible() ? op : op.oneStep()
62+
);
63+
64+
// If all operands are now contractible and operator is lambda, contract
65+
if (this.operator.type === 'LambdaExpression' &&
66+
steppedOperands.every(op => op.isContractible())) {
67+
return this.contract();
68+
}
69+
70+
return new StepperFunctionApplication(this.operator, steppedOperands);
71+
}
72+
73+
substitute(id: StepperPattern, value: StepperExpression): StepperBaseNode {
74+
return new StepperFunctionApplication(
75+
this.operator.substitute(id, value),
76+
this.operands.map(op => op.substitute(id, value))
77+
);
78+
}
79+
80+
freeNames(): string[] {
81+
return [
82+
...this.operator.freeNames(),
83+
...this.operands.flatMap(op => op.freeNames())
84+
];
85+
}
86+
87+
allNames(): string[] {
88+
return [
89+
...this.operator.allNames(),
90+
...this.operands.flatMap(op => op.allNames())
91+
];
92+
}
93+
94+
rename(before: string, after: string): StepperBaseNode {
95+
return new StepperFunctionApplication(
96+
this.operator.rename(before, after),
97+
this.operands.map(op => op.rename(before, after))
98+
);
99+
}
100+
101+
toString(): string {
102+
return `(${this.operator.toString()} ${this.operands.map(op => op.toString()).join(' ')})`;
103+
}
104+
}
105+
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
import { StepperBaseNode } from '../../interface';
2+
import { StepperExpression, StepperPattern } from '../index';
3+
4+
export class StepperIdentifier implements StepperBaseNode {
5+
type = 'Identifier';
6+
name: string;
7+
8+
constructor(name: string) {
9+
this.name = name;
10+
}
11+
12+
static create(node: any): StepperIdentifier {
13+
return new StepperIdentifier(node.name);
14+
}
15+
16+
isContractible(): boolean {
17+
return true;
18+
}
19+
20+
isOneStepPossible(): boolean {
21+
return false;
22+
}
23+
24+
contract(): StepperBaseNode {
25+
return this;
26+
}
27+
28+
oneStep(): StepperBaseNode {
29+
return this;
30+
}
31+
32+
substitute(id: StepperPattern, value: StepperExpression): StepperBaseNode {
33+
if (this.name === id.name) {
34+
return value;
35+
}
36+
return this;
37+
}
38+
39+
freeNames(): string[] {
40+
return [this.name];
41+
}
42+
43+
allNames(): string[] {
44+
return [this.name];
45+
}
46+
47+
rename(before: string, after: string): StepperBaseNode {
48+
if (this.name === before) {
49+
return new StepperIdentifier(after);
50+
}
51+
return this;
52+
}
53+
54+
toString(): string {
55+
return this.name;
56+
}
57+
}

0 commit comments

Comments
 (0)