summaryrefslogtreecommitdiff
path: root/src/components/lambda_reducer.tsx
diff options
context:
space:
mode:
Diffstat (limited to 'src/components/lambda_reducer.tsx')
-rw-r--r--src/components/lambda_reducer.tsx188
1 files changed, 188 insertions, 0 deletions
diff --git a/src/components/lambda_reducer.tsx b/src/components/lambda_reducer.tsx
new file mode 100644
index 0000000..bef8fde
--- /dev/null
+++ b/src/components/lambda_reducer.tsx
@@ -0,0 +1,188 @@
+import { Rect, Node, Txt, Line, NodeProps } from "@motion-canvas/2d";
+import { createRef, all, range, makeRef, DEFAULT } from "@motion-canvas/core";
+
+import {
+ CodeBlock,
+ insert,
+ edit,
+ lines,
+} from "@motion-canvas/2d/lib/components/CodeBlock";
+import { Abstraction, Application, LambdaTerm, parse } from "../parser/parser";
+import {
+ EditOperation,
+ calculateLevenshteinOperations,
+} from "../utils/levenshtein";
+
+export interface LambdaReducerProps extends NodeProps {
+ lambdaTerm?: string;
+ definitions?: Record<string, string>;
+}
+
+export class LambdaReducer extends Node {
+ private readonly codeBlock = createRef<CodeBlock>();
+ private reductions: string[];
+ private ast: LambdaTerm;
+
+ public constructor(props?: LambdaReducerProps) {
+ super({
+ ...props,
+ });
+
+ const functionDefinitions = props.definitions ?? {};
+ let lambdaTerm = props.lambdaTerm ?? "((λ x . x) (λ y . y))";
+ while (
+ Object.keys(functionDefinitions).some((x) => lambdaTerm.includes(x))
+ ) {
+ lambdaTerm = Object.entries(functionDefinitions).reduce(
+ (acc, [name, definition]) => {
+ return acc.replace(new RegExp(name, "g"), definition);
+ },
+ lambdaTerm
+ );
+ }
+ console.log(lambdaTerm);
+
+ this.reductions = [props.lambdaTerm, lambdaTerm];
+ this.ast = parse(lambdaTerm);
+ this.add(
+ <CodeBlock
+ fontSize={25}
+ ref={this.codeBlock}
+ language="racket"
+ code={this.getReductions()}
+ ></CodeBlock>
+ );
+ }
+
+ private getReductions() {
+ return this.reductions.filter((x) => x).join("\n=> ");
+ }
+
+ private isApplication(ast: any): boolean {
+ return ast.left && ast.right;
+ }
+
+ private isAbstraction(ast: any): boolean {
+ return ast.param && ast.body;
+ }
+
+ private isVariable(ast: any): boolean {
+ return !!ast.name;
+ }
+
+ private substitute(
+ ast: LambdaTerm,
+ param: string,
+ replacement: LambdaTerm
+ ): LambdaTerm {
+ const node = ast as any;
+
+ if (this.isVariable(node)) {
+ return node.name === param ? replacement : node;
+ } else if (this.isApplication(node)) {
+ const left = this.substitute(node.left, param, replacement);
+ const right = this.substitute(node.right, param, replacement);
+
+ return { left, right } as Application;
+ }
+
+ return {
+ param: node.param,
+ body: this.substitute(node.body, param, replacement) as Abstraction,
+ };
+ }
+
+ public getCode() {
+ return this.emitCode(this.ast);
+ }
+
+ private betaReduceStep(ast: LambdaTerm): LambdaTerm {
+ const node = ast as any;
+
+ if (this.isApplication(node)) {
+ const left = node.left as any;
+ const right = node.right as any;
+
+ if (this.isAbstraction(left)) {
+ return this.substitute(left.body, left.param.name, right);
+ }
+
+ return {
+ left: this.betaReduceStep(left),
+ right: this.betaReduceStep(right),
+ } as Application;
+ }
+
+ if (this.isAbstraction(node)) {
+ return {
+ param: node.param,
+ body: this.betaReduceStep(node.body),
+ } as Abstraction;
+ }
+
+ return node;
+ }
+
+ private emitCode(ast: LambdaTerm): string {
+ const node = ast as any;
+
+ if (this.isVariable(node)) {
+ return node.name;
+ } else if (this.isApplication(node)) {
+ return `(${this.emitCode(node.left)} ${this.emitCode(node.right)})`;
+ } else if (this.isAbstraction(node)) {
+ return `(λ ${node.param.name}.${this.emitCode(node.body)})`;
+ }
+
+ throw new Error("Invalid lambda term");
+ }
+
+ public isDone() {
+ return (
+ this.emitCode(this.betaReduceStep(this.ast)) === this.emitCode(this.ast)
+ );
+ }
+
+ public *step(duration: number) {
+ yield* this.codeBlock().selection([], 0);
+
+ const old = this.getReductions();
+ const next = this.betaReduceStep(this.ast);
+ const nextCode = this.emitCode(next);
+
+ const operations = calculateLevenshteinOperations(
+ this.reductions.at(-1),
+ nextCode
+ );
+
+ yield* this.codeBlock().edit(duration)`${old}${insert(
+ "\n=> " + this.reductions.at(-1)
+ )}`;
+
+ let code = `this.codeBlock().edit(duration)\`${old}\\n=> `;
+
+ // this is SO FUCKING CURSED
+ window.editInLambda = edit;
+ window.insertInLambda = insert;
+ for (const { operation, diff } of operations) {
+ const next = `'${diff.new ?? ""}'`;
+ const old = `'${diff.old ?? ""}'`;
+
+ if (operation === EditOperation.Edit) {
+ code += `\${editInLambda(${old}, ${next})}`;
+ continue;
+ }
+ if (operation === EditOperation.Insert) {
+ code += `\${insertInLambda(${next})}`;
+ continue;
+ }
+ code += diff.new ?? "";
+ }
+ code += "`;";
+ yield* eval(code);
+
+ yield* this.codeBlock().selection(lines(this.reductions.length), 0.3);
+ this.reductions.push(nextCode);
+ this.ast = next;
+ }
+}