summaryrefslogtreecommitdiff
path: root/src/components
diff options
context:
space:
mode:
Diffstat (limited to 'src/components')
-rw-r--r--src/components/function_box.tsx151
-rw-r--r--src/components/lambda_reducer.tsx188
2 files changed, 276 insertions, 63 deletions
diff --git a/src/components/function_box.tsx b/src/components/function_box.tsx
index e1ed497..9f72979 100644
--- a/src/components/function_box.tsx
+++ b/src/components/function_box.tsx
@@ -72,9 +72,13 @@ export class FunctionBox extends Node {
this.padding = props?.padding ?? 100;
this.workingText = props?.workingText ?? "👷‍♀️⚙️";
+
this.idlingText = props?.idlingText ?? "😴";
this.isChild = props?.isChild ?? false;
+ if (this.isChild) {
+ this.idlingText += " | 🧬";
+ }
this.outputFontSize = props?.outputFontSize ?? 30;
this.inputFontSize = props?.inputFontSize ?? 30;
@@ -82,80 +86,97 @@ export class FunctionBox extends Node {
this.add(
<Rect
opacity={this.opacity}
- direction={"row"}
- alignItems={"center"}
+ direction="column"
+ alignItems="center"
layout
>
- <Rect direction={"column"} alignItems={"end"} gap={10}>
- {range(this.arity).map((i) => (
- <Rect direction={"row"} alignItems={"center"} gap={10}>
- <Rect
- direction={"row"}
+ <Rect direction="row" alignItems="center">
+ <Rect
+ direction="column"
+ justifyContent="center"
+ alignItems="end"
+ gap={10}
+ >
+ {range(this.arity).map((i) => (
+ <Rect direction="row" alignItems="center" gap={10}>
+ <Rect
+ direction="row"
+ fontSize={0}
+ ref={makeRef(this.inputs, i)}
+ justifyContent="end"
+ opacity={1}
+ ></Rect>
+
+ <Line
+ points={[]}
+ stroke={theme.green.hex}
+ ref={makeRef(this.inputSegments, i)}
+ lineWidth={5}
+ arrowSize={10}
+ endArrow
+ ></Line>
+ </Rect>
+ ))}
+ </Rect>
+
+ <Rect
+ ref={this.rect}
+ radius={4}
+ stroke={theme.overlay0.hex}
+ fill={theme.crust.hex}
+ lineWidth={4}
+ padding={60}
+ direction="row"
+ height="100%"
+ alignItems="center"
+ >
+ <Txt
+ fontFamily={theme.font}
+ fill={theme.text.hex}
+ ref={this.boxMoji}
+ >
+ {this.idlingText}
+ </Txt>
+ <Rect
+ gap={10}
+ alignItems="center"
+ direction="column"
+ ref={this.node}
+ opacity={0}
+ >
+ <CodeBlock
+ fontFamily={theme.font}
+ language="typescript"
+ ref={this.block}
fontSize={0}
- ref={makeRef(this.inputs, i)}
- justifyContent={"end"}
- opacity={1}
- ></Rect>
+ code={this.source}
+ ></CodeBlock>
+ </Rect>
+ </Rect>
+ <Rect
+ direction="column"
+ height={"100%"}
+ alignItems={"end"}
+ justifyContent="center"
+ >
+ <Rect direction="row" alignItems="center" gap={10}>
<Line
points={[]}
- stroke={theme.green.hex}
- ref={makeRef(this.inputSegments, i)}
+ stroke={theme.red.hex}
lineWidth={5}
arrowSize={10}
+ ref={this.outputSegment}
endArrow
></Line>
+ <Rect
+ direction={"row"}
+ ref={this.output}
+ justifyContent={"end"}
+ opacity={1}
+ fontSize={0}
+ ></Rect>
</Rect>
- ))}
- </Rect>
-
- <Rect
- ref={this.rect}
- radius={4}
- stroke={theme.overlay0.hex}
- fill={theme.crust.hex}
- lineWidth={4}
- padding={60}
- direction="row"
- height="100%"
- alignItems="center"
- >
- <Txt fontFamily={theme.font} fill={theme.text.hex} ref={this.boxMoji}>
- {this.idlingText}
- </Txt>
- <Node ref={this.node} opacity={0}>
- <CodeBlock
- fontFamily={theme.font}
- language="typescript"
- ref={this.block}
- fontSize={0}
- code={this.source}
- ></CodeBlock>
- </Node>
- </Rect>
-
- <Rect
- direction="column"
- height={"100%"}
- alignItems={"end"}
- justifyContent="center"
- >
- <Rect direction="row" alignItems="center" gap={10}>
- <Line
- points={[]}
- stroke={theme.red.hex}
- lineWidth={5}
- arrowSize={10}
- ref={this.outputSegment}
- endArrow
- ></Line>
- <Rect
- direction={"row"}
- ref={this.output}
- justifyContent={"end"}
- opacity={1}
- fontSize={0}
- ></Rect>
</Rect>
</Rect>
</Rect>,
@@ -252,11 +273,15 @@ export class FunctionBox extends Node {
const output = this.function(...this.currentArgs.map((input) => input.val));
switch (typeof output) {
case "function":
+ console.dir(output);
+
yield this.output().add(
<FunctionBox
opacity={0}
isChild={true}
ref={this.child}
+ outputFontSize={this.outputFontSize}
+ inputFontSize={this.inputFontSize}
fn={output}
></FunctionBox>,
);
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;
+ }
+}