diff options
Diffstat (limited to 'src/components')
-rw-r--r-- | src/components/function_box.tsx | 151 | ||||
-rw-r--r-- | src/components/lambda_reducer.tsx | 188 |
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; + } +} |