summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/components/function_box.tsx151
-rw-r--r--src/components/lambda_reducer.tsx188
-rw-r--r--src/parser/grammar.pegjs27
-rw-r--r--src/parser/parser.ts1282
-rw-r--r--src/project.meta4
-rw-r--r--src/scenes/boolean_algebra_lambda.meta5
-rw-r--r--src/scenes/boolean_algebra_lambda.tsx49
-rw-r--r--src/scenes/boolean_encoding.meta5
-rw-r--r--src/scenes/boolean_encoding.tsx42
-rw-r--r--src/scenes/church_encoding.meta5
-rw-r--r--src/scenes/church_encoding.tsx41
-rw-r--r--src/scenes/currying.meta5
-rw-r--r--src/scenes/currying.tsx114
-rw-r--r--src/scenes/currying_detail.meta5
-rw-r--r--src/scenes/currying_detail.tsx155
-rw-r--r--src/scenes/dna.meta5
-rw-r--r--src/scenes/dna.tsx22
-rw-r--r--src/scenes/function_dna.meta5
-rw-r--r--src/scenes/function_dna.tsx91
-rw-r--r--src/scenes/further.meta5
-rw-r--r--src/scenes/further.tsx43
-rw-r--r--src/scenes/generalized.tsx6
-rw-r--r--src/scenes/impostor.meta5
-rw-r--r--src/scenes/impostor.tsx134
-rw-r--r--src/scenes/index.ts58
-rw-r--r--src/scenes/lambda_recursion.meta5
-rw-r--r--src/scenes/lambda_recursion.tsx45
-rw-r--r--src/scenes/lambda_reduction_example.meta5
-rw-r--r--src/scenes/lambda_reduction_example.tsx47
-rw-r--r--src/scenes/me.tsx8
-rw-r--r--src/scenes/parttwo.meta5
-rw-r--r--src/scenes/parttwo.tsx16
-rw-r--r--src/scenes/pros_cons.meta5
-rw-r--r--src/scenes/pros_cons.tsx61
-rw-r--r--src/scenes/questions.meta5
-rw-r--r--src/scenes/questions.tsx16
-rw-r--r--src/scenes/recursion.meta5
-rw-r--r--src/scenes/recursion.tsx36
-rw-r--r--src/scenes/reductions.meta5
-rw-r--r--src/scenes/reductions.tsx40
-rw-r--r--src/scenes/substitution.meta5
-rw-r--r--src/scenes/substitution.tsx45
-rw-r--r--src/scenes/the_lambda_calculus.meta5
-rw-r--r--src/scenes/the_lambda_calculus.tsx40
-rw-r--r--src/utils/lambdas.ts35
-rw-r--r--src/utils/levenshtein.ts103
46 files changed, 2906 insertions, 83 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;
+ }
+}
diff --git a/src/parser/grammar.pegjs b/src/parser/grammar.pegjs
new file mode 100644
index 0000000..48a7564
--- /dev/null
+++ b/src/parser/grammar.pegjs
@@ -0,0 +1,27 @@
+LambdaTerm
+ = Abstraction
+ / Application
+ / Variable
+
+Application
+ = LPAREN _? left:LambdaTerm _? right:LambdaTerm _? RPAREN {
+ return { left, right };
+ }
+
+Abstraction
+ = LPAREN _? LAMBDA _? param:Variable _? DOT _? body:LambdaTerm _? RPAREN {
+ return { param, body };
+ }
+
+Variable
+ = name:([a-zA-Z][A-Z0-9a-z]*) { return { name: name[0] + name[1].join('') }; }
+
+LAMBDA = "λ"
+
+DOT = "."
+
+LPAREN = "("
+
+RPAREN = ")"
+
+_ = (" " / "\n" / "\t" / "\t\n")+ \ No newline at end of file
diff --git a/src/parser/parser.ts b/src/parser/parser.ts
new file mode 100644
index 0000000..3dcbd6c
--- /dev/null
+++ b/src/parser/parser.ts
@@ -0,0 +1,1282 @@
+/* eslint-disable */
+
+
+
+const peggyParser: {parse: any, SyntaxError: any, DefaultTracer?: any} = // Generated by Peggy 3.0.2.
+//
+// https://peggyjs.org/
+// @ts-ignore
+(function() {
+// @ts-ignore
+ "use strict";
+
+// @ts-ignore
+function peg$subclass(child, parent) {
+// @ts-ignore
+ function C() { this.constructor = child; }
+// @ts-ignore
+ C.prototype = parent.prototype;
+// @ts-ignore
+ child.prototype = new C();
+}
+
+// @ts-ignore
+function peg$SyntaxError(message, expected, found, location) {
+// @ts-ignore
+ var self = Error.call(this, message);
+ // istanbul ignore next Check is a necessary evil to support older environments
+// @ts-ignore
+ if (Object.setPrototypeOf) {
+// @ts-ignore
+ Object.setPrototypeOf(self, peg$SyntaxError.prototype);
+ }
+// @ts-ignore
+ self.expected = expected;
+// @ts-ignore
+ self.found = found;
+// @ts-ignore
+ self.location = location;
+// @ts-ignore
+ self.name = "SyntaxError";
+// @ts-ignore
+ return self;
+}
+
+// @ts-ignore
+peg$subclass(peg$SyntaxError, Error);
+
+// @ts-ignore
+function peg$padEnd(str, targetLength, padString) {
+// @ts-ignore
+ padString = padString || " ";
+// @ts-ignore
+ if (str.length > targetLength) { return str; }
+// @ts-ignore
+ targetLength -= str.length;
+// @ts-ignore
+ padString += padString.repeat(targetLength);
+// @ts-ignore
+ return str + padString.slice(0, targetLength);
+}
+
+// @ts-ignore
+peg$SyntaxError.prototype.format = function(sources) {
+// @ts-ignore
+ var str = "Error: " + this.message;
+// @ts-ignore
+ if (this.location) {
+// @ts-ignore
+ var src = null;
+// @ts-ignore
+ var k;
+// @ts-ignore
+ for (k = 0; k < sources.length; k++) {
+// @ts-ignore
+ if (sources[k].source === this.location.source) {
+// @ts-ignore
+ src = sources[k].text.split(/\r\n|\n|\r/g);
+// @ts-ignore
+ break;
+ }
+ }
+// @ts-ignore
+ var s = this.location.start;
+// @ts-ignore
+ var offset_s = (this.location.source && (typeof this.location.source.offset === "function"))
+// @ts-ignore
+ ? this.location.source.offset(s)
+// @ts-ignore
+ : s;
+// @ts-ignore
+ var loc = this.location.source + ":" + offset_s.line + ":" + offset_s.column;
+// @ts-ignore
+ if (src) {
+// @ts-ignore
+ var e = this.location.end;
+// @ts-ignore
+ var filler = peg$padEnd("", offset_s.line.toString().length, ' ');
+// @ts-ignore
+ var line = src[s.line - 1];
+// @ts-ignore
+ var last = s.line === e.line ? e.column : line.length + 1;
+// @ts-ignore
+ var hatLen = (last - s.column) || 1;
+// @ts-ignore
+ str += "\n --> " + loc + "\n"
+// @ts-ignore
+ + filler + " |\n"
+// @ts-ignore
+ + offset_s.line + " | " + line + "\n"
+// @ts-ignore
+ + filler + " | " + peg$padEnd("", s.column - 1, ' ')
+// @ts-ignore
+ + peg$padEnd("", hatLen, "^");
+// @ts-ignore
+ } else {
+// @ts-ignore
+ str += "\n at " + loc;
+ }
+ }
+// @ts-ignore
+ return str;
+};
+
+// @ts-ignore
+peg$SyntaxError.buildMessage = function(expected, found) {
+// @ts-ignore
+ var DESCRIBE_EXPECTATION_FNS = {
+// @ts-ignore
+ literal: function(expectation) {
+// @ts-ignore
+ return "\"" + literalEscape(expectation.text) + "\"";
+ },
+
+// @ts-ignore
+ class: function(expectation) {
+// @ts-ignore
+ var escapedParts = expectation.parts.map(function(part) {
+// @ts-ignore
+ return Array.isArray(part)
+// @ts-ignore
+ ? classEscape(part[0]) + "-" + classEscape(part[1])
+// @ts-ignore
+ : classEscape(part);
+ });
+
+// @ts-ignore
+ return "[" + (expectation.inverted ? "^" : "") + escapedParts.join("") + "]";
+ },
+
+// @ts-ignore
+ any: function() {
+// @ts-ignore
+ return "any character";
+ },
+
+// @ts-ignore
+ end: function() {
+// @ts-ignore
+ return "end of input";
+ },
+
+// @ts-ignore
+ other: function(expectation) {
+// @ts-ignore
+ return expectation.description;
+ }
+ };
+
+// @ts-ignore
+ function hex(ch) {
+// @ts-ignore
+ return ch.charCodeAt(0).toString(16).toUpperCase();
+ }
+
+// @ts-ignore
+ function literalEscape(s) {
+// @ts-ignore
+ return s
+// @ts-ignore
+ .replace(/\\/g, "\\\\")
+// @ts-ignore
+ .replace(/"/g, "\\\"")
+// @ts-ignore
+ .replace(/\0/g, "\\0")
+// @ts-ignore
+ .replace(/\t/g, "\\t")
+// @ts-ignore
+ .replace(/\n/g, "\\n")
+// @ts-ignore
+ .replace(/\r/g, "\\r")
+// @ts-ignore
+ .replace(/[\x00-\x0F]/g, function(ch) { return "\\x0" + hex(ch); })
+// @ts-ignore
+ .replace(/[\x10-\x1F\x7F-\x9F]/g, function(ch) { return "\\x" + hex(ch); });
+ }
+
+// @ts-ignore
+ function classEscape(s) {
+// @ts-ignore
+ return s
+// @ts-ignore
+ .replace(/\\/g, "\\\\")
+// @ts-ignore
+ .replace(/\]/g, "\\]")
+// @ts-ignore
+ .replace(/\^/g, "\\^")
+// @ts-ignore
+ .replace(/-/g, "\\-")
+// @ts-ignore
+ .replace(/\0/g, "\\0")
+// @ts-ignore
+ .replace(/\t/g, "\\t")
+// @ts-ignore
+ .replace(/\n/g, "\\n")
+// @ts-ignore
+ .replace(/\r/g, "\\r")
+// @ts-ignore
+ .replace(/[\x00-\x0F]/g, function(ch) { return "\\x0" + hex(ch); })
+// @ts-ignore
+ .replace(/[\x10-\x1F\x7F-\x9F]/g, function(ch) { return "\\x" + hex(ch); });
+ }
+
+// @ts-ignore
+ function describeExpectation(expectation) {
+// @ts-ignore
+ return DESCRIBE_EXPECTATION_FNS[expectation.type](expectation);
+ }
+
+// @ts-ignore
+ function describeExpected(expected) {
+// @ts-ignore
+ var descriptions = expected.map(describeExpectation);
+// @ts-ignore
+ var i, j;
+
+// @ts-ignore
+ descriptions.sort();
+
+// @ts-ignore
+ if (descriptions.length > 0) {
+// @ts-ignore
+ for (i = 1, j = 1; i < descriptions.length; i++) {
+// @ts-ignore
+ if (descriptions[i - 1] !== descriptions[i]) {
+// @ts-ignore
+ descriptions[j] = descriptions[i];
+// @ts-ignore
+ j++;
+ }
+ }
+// @ts-ignore
+ descriptions.length = j;
+ }
+
+// @ts-ignore
+ switch (descriptions.length) {
+// @ts-ignore
+ case 1:
+// @ts-ignore
+ return descriptions[0];
+
+// @ts-ignore
+ case 2:
+// @ts-ignore
+ return descriptions[0] + " or " + descriptions[1];
+
+// @ts-ignore
+ default:
+// @ts-ignore
+ return descriptions.slice(0, -1).join(", ")
+// @ts-ignore
+ + ", or "
+// @ts-ignore
+ + descriptions[descriptions.length - 1];
+ }
+ }
+
+// @ts-ignore
+ function describeFound(found) {
+// @ts-ignore
+ return found ? "\"" + literalEscape(found) + "\"" : "end of input";
+ }
+
+// @ts-ignore
+ return "Expected " + describeExpected(expected) + " but " + describeFound(found) + " found.";
+};
+
+// @ts-ignore
+function peg$parse(input, options) {
+// @ts-ignore
+ options = options !== undefined ? options : {};
+
+// @ts-ignore
+ var peg$FAILED = {};
+// @ts-ignore
+ var peg$source = options.grammarSource;
+
+// @ts-ignore
+ var peg$startRuleFunctions = { LambdaTerm: peg$parseLambdaTerm };
+// @ts-ignore
+ var peg$startRuleFunction = peg$parseLambdaTerm;
+
+// @ts-ignore
+ var peg$c0 = "\u03BB";
+ var peg$c1 = ".";
+ var peg$c2 = "(";
+ var peg$c3 = ")";
+ var peg$c4 = " ";
+ var peg$c5 = "\n";
+ var peg$c6 = "\t";
+ var peg$c7 = "\t\n";
+
+ var peg$r0 = /^[a-zA-Z]/;
+ var peg$r1 = /^[A-Z0-9a-z]/;
+
+ var peg$e0 = peg$classExpectation([["a", "z"], ["A", "Z"]], false, false);
+ var peg$e1 = peg$classExpectation([["A", "Z"], ["0", "9"], ["a", "z"]], false, false);
+ var peg$e2 = peg$literalExpectation("\u03BB", false);
+ var peg$e3 = peg$literalExpectation(".", false);
+ var peg$e4 = peg$literalExpectation("(", false);
+ var peg$e5 = peg$literalExpectation(")", false);
+ var peg$e6 = peg$literalExpectation(" ", false);
+ var peg$e7 = peg$literalExpectation("\n", false);
+ var peg$e8 = peg$literalExpectation("\t", false);
+ var peg$e9 = peg$literalExpectation("\t\n", false);
+// @ts-ignore
+
+ var peg$f0 = function(left, right) {
+// @ts-ignore
+ return { left, right };
+ };// @ts-ignore
+
+ var peg$f1 = function(param, body) {
+// @ts-ignore
+ return { param, body };
+ };// @ts-ignore
+
+ var peg$f2 = function(name) {// @ts-ignore
+ return { name: name[0] + name[1].join('') }; };
+// @ts-ignore
+ var peg$currPos = 0;
+// @ts-ignore
+ var peg$savedPos = 0;
+// @ts-ignore
+ var peg$posDetailsCache = [{ line: 1, column: 1 }];
+// @ts-ignore
+ var peg$maxFailPos = 0;
+// @ts-ignore
+ var peg$maxFailExpected = [];
+// @ts-ignore
+ var peg$silentFails = 0;
+
+// @ts-ignore
+ var peg$result;
+
+// @ts-ignore
+ if ("startRule" in options) {
+// @ts-ignore
+ if (!(options.startRule in peg$startRuleFunctions)) {
+// @ts-ignore
+ throw new Error("Can't start parsing from rule \"" + options.startRule + "\".");
+ }
+
+// @ts-ignore
+ peg$startRuleFunction = peg$startRuleFunctions[options.startRule];
+ }
+
+// @ts-ignore
+ function text() {
+// @ts-ignore
+ return input.substring(peg$savedPos, peg$currPos);
+ }
+
+// @ts-ignore
+ function offset() {
+// @ts-ignore
+ return peg$savedPos;
+ }
+
+// @ts-ignore
+ function range() {
+// @ts-ignore
+ return {
+// @ts-ignore
+ source: peg$source,
+// @ts-ignore
+ start: peg$savedPos,
+// @ts-ignore
+ end: peg$currPos
+ };
+ }
+
+// @ts-ignore
+ function location() {
+// @ts-ignore
+ return peg$computeLocation(peg$savedPos, peg$currPos);
+ }
+
+// @ts-ignore
+ function expected(description, location) {
+// @ts-ignore
+ location = location !== undefined
+// @ts-ignore
+ ? location
+// @ts-ignore
+ : peg$computeLocation(peg$savedPos, peg$currPos);
+
+// @ts-ignore
+ throw peg$buildStructuredError(
+// @ts-ignore
+ [peg$otherExpectation(description)],
+// @ts-ignore
+ input.substring(peg$savedPos, peg$currPos),
+// @ts-ignore
+ location
+ );
+ }
+
+// @ts-ignore
+ function error(message, location) {
+// @ts-ignore
+ location = location !== undefined
+// @ts-ignore
+ ? location
+// @ts-ignore
+ : peg$computeLocation(peg$savedPos, peg$currPos);
+
+// @ts-ignore
+ throw peg$buildSimpleError(message, location);
+ }
+
+// @ts-ignore
+ function peg$literalExpectation(text, ignoreCase) {
+// @ts-ignore
+ return { type: "literal", text: text, ignoreCase: ignoreCase };
+ }
+
+// @ts-ignore
+ function peg$classExpectation(parts, inverted, ignoreCase) {
+// @ts-ignore
+ return { type: "class", parts: parts, inverted: inverted, ignoreCase: ignoreCase };
+ }
+
+// @ts-ignore
+ function peg$anyExpectation() {
+// @ts-ignore
+ return { type: "any" };
+ }
+
+// @ts-ignore
+ function peg$endExpectation() {
+// @ts-ignore
+ return { type: "end" };
+ }
+
+// @ts-ignore
+ function peg$otherExpectation(description) {
+// @ts-ignore
+ return { type: "other", description: description };
+ }
+
+// @ts-ignore
+ function peg$computePosDetails(pos) {
+// @ts-ignore
+ var details = peg$posDetailsCache[pos];
+// @ts-ignore
+ var p;
+
+// @ts-ignore
+ if (details) {
+// @ts-ignore
+ return details;
+// @ts-ignore
+ } else {
+// @ts-ignore
+ p = pos - 1;
+// @ts-ignore
+ while (!peg$posDetailsCache[p]) {
+// @ts-ignore
+ p--;
+ }
+
+// @ts-ignore
+ details = peg$posDetailsCache[p];
+// @ts-ignore
+ details = {
+// @ts-ignore
+ line: details.line,
+// @ts-ignore
+ column: details.column
+ };
+
+// @ts-ignore
+ while (p < pos) {
+// @ts-ignore
+ if (input.charCodeAt(p) === 10) {
+// @ts-ignore
+ details.line++;
+// @ts-ignore
+ details.column = 1;
+// @ts-ignore
+ } else {
+// @ts-ignore
+ details.column++;
+ }
+
+// @ts-ignore
+ p++;
+ }
+
+// @ts-ignore
+ peg$posDetailsCache[pos] = details;
+
+// @ts-ignore
+ return details;
+ }
+ }
+
+// @ts-ignore
+ function peg$computeLocation(startPos, endPos, offset) {
+// @ts-ignore
+ var startPosDetails = peg$computePosDetails(startPos);
+// @ts-ignore
+ var endPosDetails = peg$computePosDetails(endPos);
+
+// @ts-ignore
+ var res = {
+// @ts-ignore
+ source: peg$source,
+// @ts-ignore
+ start: {
+// @ts-ignore
+ offset: startPos,
+// @ts-ignore
+ line: startPosDetails.line,
+// @ts-ignore
+ column: startPosDetails.column
+ },
+// @ts-ignore
+ end: {
+// @ts-ignore
+ offset: endPos,
+// @ts-ignore
+ line: endPosDetails.line,
+// @ts-ignore
+ column: endPosDetails.column
+ }
+ };
+// @ts-ignore
+ if (offset && peg$source && (typeof peg$source.offset === "function")) {
+// @ts-ignore
+ res.start = peg$source.offset(res.start);
+// @ts-ignore
+ res.end = peg$source.offset(res.end);
+ }
+// @ts-ignore
+ return res;
+ }
+
+// @ts-ignore
+ function peg$fail(expected) {
+// @ts-ignore
+ if (peg$currPos < peg$maxFailPos) { return; }
+
+// @ts-ignore
+ if (peg$currPos > peg$maxFailPos) {
+// @ts-ignore
+ peg$maxFailPos = peg$currPos;
+// @ts-ignore
+ peg$maxFailExpected = [];
+ }
+
+// @ts-ignore
+ peg$maxFailExpected.push(expected);
+ }
+
+// @ts-ignore
+ function peg$buildSimpleError(message, location) {
+// @ts-ignore
+ return new peg$SyntaxError(message, null, null, location);
+ }
+
+// @ts-ignore
+ function peg$buildStructuredError(expected, found, location) {
+// @ts-ignore
+ return new peg$SyntaxError(
+// @ts-ignore
+ peg$SyntaxError.buildMessage(expected, found),
+// @ts-ignore
+ expected,
+// @ts-ignore
+ found,
+// @ts-ignore
+ location
+ );
+ }
+
+// @ts-ignore
+ function // @ts-ignore
+peg$parseLambdaTerm() {
+// @ts-ignore
+ var s0;
+
+// @ts-ignore
+ s0 = peg$parseAbstraction();
+// @ts-ignore
+ if (s0 === peg$FAILED) {
+// @ts-ignore
+ s0 = peg$parseApplication();
+// @ts-ignore
+ if (s0 === peg$FAILED) {
+// @ts-ignore
+ s0 = peg$parseVariable();
+ }
+ }
+
+// @ts-ignore
+ return s0;
+ }
+
+// @ts-ignore
+ function // @ts-ignore
+peg$parseApplication() {
+// @ts-ignore
+ var s0, s1, s2, s3, s4, s5, s6, s7;
+
+// @ts-ignore
+ s0 = peg$currPos;
+// @ts-ignore
+ s1 = peg$parseLPAREN();
+// @ts-ignore
+ if (s1 !== peg$FAILED) {
+// @ts-ignore
+ s2 = peg$parse_();
+// @ts-ignore
+ if (s2 === peg$FAILED) {
+// @ts-ignore
+ s2 = null;
+ }
+// @ts-ignore
+ s3 = peg$parseLambdaTerm();
+// @ts-ignore
+ if (s3 !== peg$FAILED) {
+// @ts-ignore
+ s4 = peg$parse_();
+// @ts-ignore
+ if (s4 === peg$FAILED) {
+// @ts-ignore
+ s4 = null;
+ }
+// @ts-ignore
+ s5 = peg$parseLambdaTerm();
+// @ts-ignore
+ if (s5 !== peg$FAILED) {
+// @ts-ignore
+ s6 = peg$parse_();
+// @ts-ignore
+ if (s6 === peg$FAILED) {
+// @ts-ignore
+ s6 = null;
+ }
+// @ts-ignore
+ s7 = peg$parseRPAREN();
+// @ts-ignore
+ if (s7 !== peg$FAILED) {
+// @ts-ignore
+ peg$savedPos = s0;
+// @ts-ignore
+ s0 = peg$f0(s3, s5);
+// @ts-ignore
+ } else {
+// @ts-ignore
+ peg$currPos = s0;
+// @ts-ignore
+ s0 = peg$FAILED;
+ }
+// @ts-ignore
+ } else {
+// @ts-ignore
+ peg$currPos = s0;
+// @ts-ignore
+ s0 = peg$FAILED;
+ }
+// @ts-ignore
+ } else {
+// @ts-ignore
+ peg$currPos = s0;
+// @ts-ignore
+ s0 = peg$FAILED;
+ }
+// @ts-ignore
+ } else {
+// @ts-ignore
+ peg$currPos = s0;
+// @ts-ignore
+ s0 = peg$FAILED;
+ }
+
+// @ts-ignore
+ return s0;
+ }
+
+// @ts-ignore
+ function // @ts-ignore
+peg$parseAbstraction() {
+// @ts-ignore
+ var s0, s1, s2, s3, s4, s5, s6, s7, s8, s9, s10, s11;
+
+// @ts-ignore
+ s0 = peg$currPos;
+// @ts-ignore
+ s1 = peg$parseLPAREN();
+// @ts-ignore
+ if (s1 !== peg$FAILED) {
+// @ts-ignore
+ s2 = peg$parse_();
+// @ts-ignore
+ if (s2 === peg$FAILED) {
+// @ts-ignore
+ s2 = null;
+ }
+// @ts-ignore
+ s3 = peg$parseLAMBDA();
+// @ts-ignore
+ if (s3 !== peg$FAILED) {
+// @ts-ignore
+ s4 = peg$parse_();
+// @ts-ignore
+ if (s4 === peg$FAILED) {
+// @ts-ignore
+ s4 = null;
+ }
+// @ts-ignore
+ s5 = peg$parseVariable();
+// @ts-ignore
+ if (s5 !== peg$FAILED) {
+// @ts-ignore
+ s6 = peg$parse_();
+// @ts-ignore
+ if (s6 === peg$FAILED) {
+// @ts-ignore
+ s6 = null;
+ }
+// @ts-ignore
+ s7 = peg$parseDOT();
+// @ts-ignore
+ if (s7 !== peg$FAILED) {
+// @ts-ignore
+ s8 = peg$parse_();
+// @ts-ignore
+ if (s8 === peg$FAILED) {
+// @ts-ignore
+ s8 = null;
+ }
+// @ts-ignore
+ s9 = peg$parseLambdaTerm();
+// @ts-ignore
+ if (s9 !== peg$FAILED) {
+// @ts-ignore
+ s10 = peg$parse_();
+// @ts-ignore
+ if (s10 === peg$FAILED) {
+// @ts-ignore
+ s10 = null;
+ }
+// @ts-ignore
+ s11 = peg$parseRPAREN();
+// @ts-ignore
+ if (s11 !== peg$FAILED) {
+// @ts-ignore
+ peg$savedPos = s0;
+// @ts-ignore
+ s0 = peg$f1(s5, s9);
+// @ts-ignore
+ } else {
+// @ts-ignore
+ peg$currPos = s0;
+// @ts-ignore
+ s0 = peg$FAILED;
+ }
+// @ts-ignore
+ } else {
+// @ts-ignore
+ peg$currPos = s0;
+// @ts-ignore
+ s0 = peg$FAILED;
+ }
+// @ts-ignore
+ } else {
+// @ts-ignore
+ peg$currPos = s0;
+// @ts-ignore
+ s0 = peg$FAILED;
+ }
+// @ts-ignore
+ } else {
+// @ts-ignore
+ peg$currPos = s0;
+// @ts-ignore
+ s0 = peg$FAILED;
+ }
+// @ts-ignore
+ } else {
+// @ts-ignore
+ peg$currPos = s0;
+// @ts-ignore
+ s0 = peg$FAILED;
+ }
+// @ts-ignore
+ } else {
+// @ts-ignore
+ peg$currPos = s0;
+// @ts-ignore
+ s0 = peg$FAILED;
+ }
+
+// @ts-ignore
+ return s0;
+ }
+
+// @ts-ignore
+ function // @ts-ignore
+peg$parseVariable() {
+// @ts-ignore
+ var s0, s1, s2, s3, s4;
+
+// @ts-ignore
+ s0 = peg$currPos;
+// @ts-ignore
+ s1 = peg$currPos;
+// @ts-ignore
+ if (peg$r0.test(input.charAt(peg$currPos))) {
+// @ts-ignore
+ s2 = input.charAt(peg$currPos);
+// @ts-ignore
+ peg$currPos++;
+// @ts-ignore
+ } else {
+// @ts-ignore
+ s2 = peg$FAILED;
+// @ts-ignore
+ if (peg$silentFails === 0) { peg$fail(peg$e0); }
+ }
+// @ts-ignore
+ if (s2 !== peg$FAILED) {
+// @ts-ignore
+ s3 = [];
+// @ts-ignore
+ if (peg$r1.test(input.charAt(peg$currPos))) {
+// @ts-ignore
+ s4 = input.charAt(peg$currPos);
+// @ts-ignore
+ peg$currPos++;
+// @ts-ignore
+ } else {
+// @ts-ignore
+ s4 = peg$FAILED;
+// @ts-ignore
+ if (peg$silentFails === 0) { peg$fail(peg$e1); }
+ }
+// @ts-ignore
+ while (s4 !== peg$FAILED) {
+// @ts-ignore
+ s3.push(s4);
+// @ts-ignore
+ if (peg$r1.test(input.charAt(peg$currPos))) {
+// @ts-ignore
+ s4 = input.charAt(peg$currPos);
+// @ts-ignore
+ peg$currPos++;
+// @ts-ignore
+ } else {
+// @ts-ignore
+ s4 = peg$FAILED;
+// @ts-ignore
+ if (peg$silentFails === 0) { peg$fail(peg$e1); }
+ }
+ }
+// @ts-ignore
+ s2 = [s2, s3];
+// @ts-ignore
+ s1 = s2;
+// @ts-ignore
+ } else {
+// @ts-ignore
+ peg$currPos = s1;
+// @ts-ignore
+ s1 = peg$FAILED;
+ }
+// @ts-ignore
+ if (s1 !== peg$FAILED) {
+// @ts-ignore
+ peg$savedPos = s0;
+// @ts-ignore
+ s1 = peg$f2(s1);
+ }
+// @ts-ignore
+ s0 = s1;
+
+// @ts-ignore
+ return s0;
+ }
+
+// @ts-ignore
+ function // @ts-ignore
+peg$parseLAMBDA() {
+// @ts-ignore
+ var s0;
+
+// @ts-ignore
+ if (input.charCodeAt(peg$currPos) === 955) {
+// @ts-ignore
+ s0 = peg$c0;
+// @ts-ignore
+ peg$currPos++;
+// @ts-ignore
+ } else {
+// @ts-ignore
+ s0 = peg$FAILED;
+// @ts-ignore
+ if (peg$silentFails === 0) { peg$fail(peg$e2); }
+ }
+
+// @ts-ignore
+ return s0;
+ }
+
+// @ts-ignore
+ function // @ts-ignore
+peg$parseDOT() {
+// @ts-ignore
+ var s0;
+
+// @ts-ignore
+ if (input.charCodeAt(peg$currPos) === 46) {
+// @ts-ignore
+ s0 = peg$c1;
+// @ts-ignore
+ peg$currPos++;
+// @ts-ignore
+ } else {
+// @ts-ignore
+ s0 = peg$FAILED;
+// @ts-ignore
+ if (peg$silentFails === 0) { peg$fail(peg$e3); }
+ }
+
+// @ts-ignore
+ return s0;
+ }
+
+// @ts-ignore
+ function // @ts-ignore
+peg$parseLPAREN() {
+// @ts-ignore
+ var s0;
+
+// @ts-ignore
+ if (input.charCodeAt(peg$currPos) === 40) {
+// @ts-ignore
+ s0 = peg$c2;
+// @ts-ignore
+ peg$currPos++;
+// @ts-ignore
+ } else {
+// @ts-ignore
+ s0 = peg$FAILED;
+// @ts-ignore
+ if (peg$silentFails === 0) { peg$fail(peg$e4); }
+ }
+
+// @ts-ignore
+ return s0;
+ }
+
+// @ts-ignore
+ function // @ts-ignore
+peg$parseRPAREN() {
+// @ts-ignore
+ var s0;
+
+// @ts-ignore
+ if (input.charCodeAt(peg$currPos) === 41) {
+// @ts-ignore
+ s0 = peg$c3;
+// @ts-ignore
+ peg$currPos++;
+// @ts-ignore
+ } else {
+// @ts-ignore
+ s0 = peg$FAILED;
+// @ts-ignore
+ if (peg$silentFails === 0) { peg$fail(peg$e5); }
+ }
+
+// @ts-ignore
+ return s0;
+ }
+
+// @ts-ignore
+ function // @ts-ignore
+peg$parse_() {
+// @ts-ignore
+ var s0, s1;
+
+// @ts-ignore
+ s0 = [];
+// @ts-ignore
+ if (input.charCodeAt(peg$currPos) === 32) {
+// @ts-ignore
+ s1 = peg$c4;
+// @ts-ignore
+ peg$currPos++;
+// @ts-ignore
+ } else {
+// @ts-ignore
+ s1 = peg$FAILED;
+// @ts-ignore
+ if (peg$silentFails === 0) { peg$fail(peg$e6); }
+ }
+// @ts-ignore
+ if (s1 === peg$FAILED) {
+// @ts-ignore
+ if (input.charCodeAt(peg$currPos) === 10) {
+// @ts-ignore
+ s1 = peg$c5;
+// @ts-ignore
+ peg$currPos++;
+// @ts-ignore
+ } else {
+// @ts-ignore
+ s1 = peg$FAILED;
+// @ts-ignore
+ if (peg$silentFails === 0) { peg$fail(peg$e7); }
+ }
+// @ts-ignore
+ if (s1 === peg$FAILED) {
+// @ts-ignore
+ if (input.charCodeAt(peg$currPos) === 9) {
+// @ts-ignore
+ s1 = peg$c6;
+// @ts-ignore
+ peg$currPos++;
+// @ts-ignore
+ } else {
+// @ts-ignore
+ s1 = peg$FAILED;
+// @ts-ignore
+ if (peg$silentFails === 0) { peg$fail(peg$e8); }
+ }
+// @ts-ignore
+ if (s1 === peg$FAILED) {
+// @ts-ignore
+ if (input.substr(peg$currPos, 2) === peg$c7) {
+// @ts-ignore
+ s1 = peg$c7;
+// @ts-ignore
+ peg$currPos += 2;
+// @ts-ignore
+ } else {
+// @ts-ignore
+ s1 = peg$FAILED;
+// @ts-ignore
+ if (peg$silentFails === 0) { peg$fail(peg$e9); }
+ }
+ }
+ }
+ }
+// @ts-ignore
+ if (s1 !== peg$FAILED) {
+// @ts-ignore
+ while (s1 !== peg$FAILED) {
+// @ts-ignore
+ s0.push(s1);
+// @ts-ignore
+ if (input.charCodeAt(peg$currPos) === 32) {
+// @ts-ignore
+ s1 = peg$c4;
+// @ts-ignore
+ peg$currPos++;
+// @ts-ignore
+ } else {
+// @ts-ignore
+ s1 = peg$FAILED;
+// @ts-ignore
+ if (peg$silentFails === 0) { peg$fail(peg$e6); }
+ }
+// @ts-ignore
+ if (s1 === peg$FAILED) {
+// @ts-ignore
+ if (input.charCodeAt(peg$currPos) === 10) {
+// @ts-ignore
+ s1 = peg$c5;
+// @ts-ignore
+ peg$currPos++;
+// @ts-ignore
+ } else {
+// @ts-ignore
+ s1 = peg$FAILED;
+// @ts-ignore
+ if (peg$silentFails === 0) { peg$fail(peg$e7); }
+ }
+// @ts-ignore
+ if (s1 === peg$FAILED) {
+// @ts-ignore
+ if (input.charCodeAt(peg$currPos) === 9) {
+// @ts-ignore
+ s1 = peg$c6;
+// @ts-ignore
+ peg$currPos++;
+// @ts-ignore
+ } else {
+// @ts-ignore
+ s1 = peg$FAILED;
+// @ts-ignore
+ if (peg$silentFails === 0) { peg$fail(peg$e8); }
+ }
+// @ts-ignore
+ if (s1 === peg$FAILED) {
+// @ts-ignore
+ if (input.substr(peg$currPos, 2) === peg$c7) {
+// @ts-ignore
+ s1 = peg$c7;
+// @ts-ignore
+ peg$currPos += 2;
+// @ts-ignore
+ } else {
+// @ts-ignore
+ s1 = peg$FAILED;
+// @ts-ignore
+ if (peg$silentFails === 0) { peg$fail(peg$e9); }
+ }
+ }
+ }
+ }
+ }
+// @ts-ignore
+ } else {
+// @ts-ignore
+ s0 = peg$FAILED;
+ }
+
+// @ts-ignore
+ return s0;
+ }
+
+// @ts-ignore
+ peg$result = peg$startRuleFunction();
+
+// @ts-ignore
+ if (peg$result !== peg$FAILED && peg$currPos === input.length) {
+// @ts-ignore
+ return peg$result;
+// @ts-ignore
+ } else {
+// @ts-ignore
+ if (peg$result !== peg$FAILED && peg$currPos < input.length) {
+// @ts-ignore
+ peg$fail(peg$endExpectation());
+ }
+
+// @ts-ignore
+ throw peg$buildStructuredError(
+// @ts-ignore
+ peg$maxFailExpected,
+// @ts-ignore
+ peg$maxFailPos < input.length ? input.charAt(peg$maxFailPos) : null,
+// @ts-ignore
+ peg$maxFailPos < input.length
+// @ts-ignore
+ ? peg$computeLocation(peg$maxFailPos, peg$maxFailPos + 1)
+// @ts-ignore
+ : peg$computeLocation(peg$maxFailPos, peg$maxFailPos)
+ );
+ }
+}
+
+// @ts-ignore
+ return {
+ SyntaxError: peg$SyntaxError,
+ parse: peg$parse
+ };
+})()
+
+export interface FilePosition {
+ offset: number;
+ line: number;
+ column: number;
+}
+
+export interface FileRange {
+ start: FilePosition;
+ end: FilePosition;
+ source: string;
+}
+
+export interface LiteralExpectation {
+ type: "literal";
+ text: string;
+ ignoreCase: boolean;
+}
+
+export interface ClassParts extends Array<string | ClassParts> {}
+
+export interface ClassExpectation {
+ type: "class";
+ parts: ClassParts;
+ inverted: boolean;
+ ignoreCase: boolean;
+}
+
+export interface AnyExpectation {
+ type: "any";
+}
+
+export interface EndExpectation {
+ type: "end";
+}
+
+export interface OtherExpectation {
+ type: "other";
+ description: string;
+}
+
+export type Expectation = LiteralExpectation | ClassExpectation | AnyExpectation | EndExpectation | OtherExpectation;
+
+declare class _PeggySyntaxError extends Error {
+ public static buildMessage(expected: Expectation[], found: string | null): string;
+ public message: string;
+ public expected: Expectation[];
+ public found: string | null;
+ public location: FileRange;
+ public name: string;
+ constructor(message: string, expected: Expectation[], found: string | null, location: FileRange);
+ format(sources: {
+ source?: any;
+ text: string;
+ }[]): string;
+}
+
+export interface TraceEvent {
+ type: string;
+ rule: string;
+ result?: any;
+ location: FileRange;
+ }
+
+declare class _DefaultTracer {
+ private indentLevel: number;
+ public trace(event: TraceEvent): void;
+}
+
+peggyParser.SyntaxError.prototype.name = "PeggySyntaxError";
+
+export interface ParseOptions {
+ filename?: string;
+ startRule?: "LambdaTerm";
+ tracer?: any;
+ [key: string]: any;
+}
+export type ParseFunction = <Options extends ParseOptions>(
+ input: string,
+ options?: Options
+ ) => Options extends { startRule: infer StartRule } ?
+ StartRule extends "LambdaTerm" ? LambdaTerm : LambdaTerm
+ : LambdaTerm;
+export const parse: ParseFunction = peggyParser.parse;
+
+export const PeggySyntaxError = peggyParser.SyntaxError as typeof _PeggySyntaxError;
+
+export type PeggySyntaxError = _PeggySyntaxError;
+
+// These types were autogenerated by ts-pegjs
+export type LambdaTerm = Abstraction | Application | Variable;
+export type Application = { left: LambdaTerm; right: LambdaTerm };
+export type Abstraction = { param: Variable; body: LambdaTerm };
+export type Variable = { name: string };
+export type LAMBDA = "\u03bb";
+export type DOT = ".";
+export type LPAREN = "(";
+export type RPAREN = ")";
+export type _ = (" " | "\n" | "\t" | "\t\n")[];
diff --git a/src/project.meta b/src/project.meta
index 7c59eb3..4ebbd36 100644
--- a/src/project.meta
+++ b/src/project.meta
@@ -3,8 +3,8 @@
"shared": {
"background": "rgb(30,30,46)",
"range": [
- 0,
- 33.18333233333333
+ 3.5666656666666667,
+ null
],
"size": {
"x": 1920,
diff --git a/src/scenes/boolean_algebra_lambda.meta b/src/scenes/boolean_algebra_lambda.meta
new file mode 100644
index 0000000..385a68c
--- /dev/null
+++ b/src/scenes/boolean_algebra_lambda.meta
@@ -0,0 +1,5 @@
+{
+ "version": 0,
+ "timeEvents": [],
+ "seed": 2667185663
+} \ No newline at end of file
diff --git a/src/scenes/boolean_algebra_lambda.tsx b/src/scenes/boolean_algebra_lambda.tsx
new file mode 100644
index 0000000..2639461
--- /dev/null
+++ b/src/scenes/boolean_algebra_lambda.tsx
@@ -0,0 +1,49 @@
+import { Layout, makeScene2D } from "@motion-canvas/2d";
+import {
+ Direction,
+ beginSlide,
+ createRef,
+ slideTransition,
+} from "@motion-canvas/core";
+import { LambdaReducer } from "../components/lambda_reducer";
+import { baseDefinitions } from "../utils/lambdas";
+
+export default makeScene2D(function* (view) {
+ const lambdaReducer = createRef<LambdaReducer>();
+ const layout = createRef<Layout>();
+
+ view.add(
+ <Layout
+ layout
+ ref={layout}
+ direction="column"
+ alignItems="center"
+ gap={50}
+ ></Layout>
+ );
+ yield* slideTransition(Direction.Right);
+ yield* beginSlide("Boolean Reductions");
+
+ for (const term of [
+ "((false one) zero)",
+ "((true one) zero)",
+ "(((if true) one) zero)",
+ ]) {
+ yield* layout().opacity(0, 0.5);
+ layout().add(
+ <LambdaReducer
+ ref={lambdaReducer}
+ lambdaTerm={term}
+ definitions={baseDefinitions}
+ ></LambdaReducer>
+ );
+ yield* layout().opacity(1, 0.5);
+
+ yield* beginSlide("Next Reduction " + term);
+ for (let i = 0; !lambdaReducer().isDone(); i++) {
+ yield* lambdaReducer().step(0.5);
+ yield* beginSlide(term + " Next Step " + i);
+ }
+ layout().removeChildren();
+ }
+});
diff --git a/src/scenes/boolean_encoding.meta b/src/scenes/boolean_encoding.meta
new file mode 100644
index 0000000..8f4d744
--- /dev/null
+++ b/src/scenes/boolean_encoding.meta
@@ -0,0 +1,5 @@
+{
+ "version": 0,
+ "timeEvents": [],
+ "seed": 2394701117
+} \ No newline at end of file
diff --git a/src/scenes/boolean_encoding.tsx b/src/scenes/boolean_encoding.tsx
new file mode 100644
index 0000000..60a0ea9
--- /dev/null
+++ b/src/scenes/boolean_encoding.tsx
@@ -0,0 +1,42 @@
+import { Layout, Txt, makeScene2D } from "@motion-canvas/2d";
+import {
+ Direction,
+ beginSlide,
+ createRef,
+ slideTransition,
+} from "@motion-canvas/core";
+import { theme } from "../theme";
+
+const boolE = [
+ "true = λ t . λ f . t",
+ "false = λ t . λ f . f",
+ "if = λ b . λ x . λ y . b x y",
+ "not = λ b . b false true",
+ "and = λ b . λ c . b c false",
+];
+
+export default makeScene2D(function* (view) {
+ const numerals = createRef<Txt>();
+
+ view.add(
+ <Layout layout direction="column" alignItems="center">
+ <Txt fontSize={40} fontFamily={theme.font} fill={theme.text.hex}>
+ Boolean Encoding
+ </Txt>
+ <Txt
+ ref={numerals}
+ fontSize={30}
+ fontFamily={theme.font}
+ fill={theme.text.hex}
+ ></Txt>
+ </Layout>
+ );
+
+ yield* slideTransition(Direction.Right);
+ yield* beginSlide("The Lambda Calculus - Boolean Encoding");
+
+ for (const bool of boolE) {
+ yield* numerals().text(numerals().text() + "\n\n" + bool, 1);
+ yield* beginSlide("boolean - " + bool);
+ }
+});
diff --git a/src/scenes/church_encoding.meta b/src/scenes/church_encoding.meta
new file mode 100644
index 0000000..638e825
--- /dev/null
+++ b/src/scenes/church_encoding.meta
@@ -0,0 +1,5 @@
+{
+ "version": 0,
+ "timeEvents": [],
+ "seed": 533121975
+} \ No newline at end of file
diff --git a/src/scenes/church_encoding.tsx b/src/scenes/church_encoding.tsx
new file mode 100644
index 0000000..70bafe4
--- /dev/null
+++ b/src/scenes/church_encoding.tsx
@@ -0,0 +1,41 @@
+import { Layout, Txt, makeScene2D } from "@motion-canvas/2d";
+import {
+ Direction,
+ beginSlide,
+ createRef,
+ slideTransition,
+} from "@motion-canvas/core";
+import { theme } from "../theme";
+
+const churchNumerals = [
+ "0 = λ f . λ x . x (no application of f)",
+ "1 = λ f . λ x . f x (one application of f)",
+ "2 = λ f . λ x . f (f x) (two applications of f)",
+ "succ = λ n . λ f . λ x . f ((n f) x)",
+];
+
+export default makeScene2D(function* (view) {
+ const numerals = createRef<Txt>();
+
+ view.add(
+ <Layout layout direction="column" alignItems="center">
+ <Txt fontSize={40} fontFamily={theme.font} fill={theme.text.hex}>
+ Church Encoding
+ </Txt>
+ <Txt
+ ref={numerals}
+ fontSize={30}
+ fontFamily={theme.font}
+ fill={theme.text.hex}
+ ></Txt>
+ </Layout>
+ );
+
+ yield* slideTransition(Direction.Right);
+ yield* beginSlide("The Lambda Calculus - Church Encoding");
+
+ for (const numeral of churchNumerals) {
+ yield* numerals().text(numerals().text() + "\n\n" + numeral, 1);
+ yield* beginSlide("substitution - " + numeral);
+ }
+});
diff --git a/src/scenes/currying.meta b/src/scenes/currying.meta
new file mode 100644
index 0000000..1dd71f1
--- /dev/null
+++ b/src/scenes/currying.meta
@@ -0,0 +1,5 @@
+{
+ "version": 0,
+ "timeEvents": [],
+ "seed": 2262306570
+} \ No newline at end of file
diff --git a/src/scenes/currying.tsx b/src/scenes/currying.tsx
new file mode 100644
index 0000000..01ce24a
--- /dev/null
+++ b/src/scenes/currying.tsx
@@ -0,0 +1,114 @@
+import { Video, Layout, makeScene2D } from "@motion-canvas/2d";
+import {
+ Direction,
+ all,
+ beginSlide,
+ createRef,
+ slideTransition,
+} from "@motion-canvas/core";
+import { PEOPLE, Person, PersonI } from "../components/person";
+import { birthdayCardFn, valentineCardFn } from "./generalized";
+import { CardI } from "./birthday_letters";
+import { FunctionBox } from "../components/function_box";
+import curry from "../../public/img/curry.mp4";
+
+export const cardGeneratorsFor = (
+ person: PersonI,
+): ((type: string) => () => CardI) => {
+ const birthdayCardGenerator = () => birthdayCardFn(person);
+ const valentineCardGenerator = () => valentineCardFn(person);
+
+ const messageType = (type: string) => {
+ switch (type) {
+ case "valentine":
+ return valentineCardGenerator;
+ case "birthday":
+ return birthdayCardGenerator;
+ }
+ throw new Error(type + " not implemented");
+ };
+
+ return messageType;
+};
+
+export const cardGeneratorsForSource = `const cardGeneratorsFor = (person: PersonI): ((type: string) => () => CardI) => {
+ const birthdayCardGenerator = () => birthdayCardFn(person);
+ const valentineCardGenerator = () => valentineCardFn(person);
+
+ const generatorForType = (type: string) => {
+ switch (type) {
+ case "valentine":
+ return valentineCardGenerator;
+ case "birthday":
+ return birthdayCardGenerator;
+ }
+
+ throw new Error(type + " not implemented");
+ };
+
+ return generatorForType;
+};`;
+
+export default makeScene2D(function* (view) {
+ const box = createRef<FunctionBox>();
+ const vid = createRef<Video>();
+
+ view.add(
+ <Layout>
+ <FunctionBox
+ ref={box}
+ fn={cardGeneratorsFor}
+ source={cardGeneratorsForSource}
+ outputFontSize={20}
+ />
+ <Video ref={vid} src={curry} width={0} />
+ </Layout>,
+ );
+
+ yield* all(
+ box().reset(0.1),
+ box().showCode(0.5),
+ slideTransition(Direction.Left, 0.5),
+ );
+
+ yield* beginSlide("show code");
+
+ const [alan] = PEOPLE;
+ yield* box().setInputs([{ val: alan, node: <Person person={alan} /> }], 0.5);
+
+ yield* beginSlide("show inputs");
+
+ yield* box().hideCode(0.5);
+ yield* box().propogateInput(0.5);
+ yield* box().propogateOutput(0.5);
+
+ yield* beginSlide("create child function");
+
+ const child = box().getChild();
+ yield* child().showCode(0.5);
+
+ yield* beginSlide("show child function");
+
+ yield* child().hideCode(0.5);
+ yield* child().setInputs([{ val: "valentine" }], 0.5);
+ yield* child().propogateInput(0.5);
+ yield* child().propogateOutput(0.5);
+
+ const curriedChild = child().getChild();
+ yield* curriedChild().showCode(0.5);
+
+ yield* beginSlide("propogate child function");
+
+ yield* curriedChild().hideCode(0.5);
+ yield* curriedChild().setInputs([{ val: "" }], 0.5);
+ yield* curriedChild().propogateInput(0.5);
+ yield* curriedChild().propogateOutput(0.5);
+
+ yield* beginSlide("propogate curry");
+
+ yield vid().play();
+ yield vid().loop(true);
+ yield* vid().width(400, 2);
+
+ yield* beginSlide("do i smell curry");
+});
diff --git a/src/scenes/currying_detail.meta b/src/scenes/currying_detail.meta
new file mode 100644
index 0000000..71a034a
--- /dev/null
+++ b/src/scenes/currying_detail.meta
@@ -0,0 +1,5 @@
+{
+ "version": 0,
+ "timeEvents": [],
+ "seed": 2012730104
+} \ No newline at end of file
diff --git a/src/scenes/currying_detail.tsx b/src/scenes/currying_detail.tsx
new file mode 100644
index 0000000..fd7c3d3
--- /dev/null
+++ b/src/scenes/currying_detail.tsx
@@ -0,0 +1,155 @@
+import { Layout, makeScene2D } from "@motion-canvas/2d";
+import {
+ Direction,
+ beginSlide,
+ createRef,
+ slideTransition,
+} from "@motion-canvas/core";
+import {
+ CodeBlock,
+ edit,
+ insert,
+ range,
+} from "@motion-canvas/2d/lib/components/CodeBlock";
+import { cardGeneratorForSource } from "./function_dna";
+import { cardGeneratorsForSource } from "./currying";
+
+export default makeScene2D(function* (view) {
+ const curried = createRef<CodeBlock>();
+
+ view.add(
+ <Layout direction="row" gap={100} layout>
+ <CodeBlock
+ fontSize={25}
+ language="typescript"
+ ref={curried}
+ code={cardGeneratorForSource}
+ />
+ </Layout>,
+ );
+
+ yield* slideTransition(Direction.Left);
+
+ yield* beginSlide("show function");
+
+ yield* curried().edit(
+ 1,
+ )`const cardGeneratorFor = (person: PersonI, type: string): (() => CardI) => {
+ const birthdayCardGenerator = () => birthdayCardFn(person); // closure
+ const valentineCardGenerator = () => valentineCardFn(person); // closure${insert(
+ "\n",
+ )}
+ ${insert(`const generatorForType = (type: string) => {`)}
+ ${insert(" ")}switch (type) {
+ ${insert(" ")} case "valentine":
+ ${insert(" ")} return valentineCardGenerator;
+ ${insert(" ")} case "birthday":
+ ${insert(" ")} return birthdayCardGenerator;
+ ${insert(" ")}}
+
+ ${insert(" ")}throw new Error(type + " not implemented");
+ ${insert("};\n")}}`;
+
+ yield* beginSlide("first currying step");
+
+ yield* curried().edit(
+ 1,
+ )`const cardGeneratorFor = (person: PersonI, type: string): (() => CardI) => {
+ const birthdayCardGenerator = () => birthdayCardFn(person); // closure
+ const valentineCardGenerator = () => valentineCardFn(person); // closure
+
+ const generatorForType = (type: string) => {
+ switch (type) {
+ case "valentine":
+ return valentineCardGenerator;
+ case "birthday":
+ return birthdayCardGenerator;
+ }
+
+ throw new Error(type + " not implemented");
+ };${insert("\n\n return generatorForType;")}
+}`;
+ yield* beginSlide("second currying step");
+
+ yield* curried().edit(1)`const ${edit(
+ "cardGeneratorFor",
+ "cardGeneratorsFor",
+ )} = ${edit(
+ "(person: PersonI, type: string)",
+ "(person: PersonI)",
+ )}: (() => CardI) => {
+ const birthdayCardGenerator = () => birthdayCardFn(person); // closure
+ const valentineCardGenerator = () => valentineCardFn(person); // closure
+
+ const generatorForType = (type: string) => {
+ switch (type) {
+ case "valentine":
+ return valentineCardGenerator;
+ case "birthday":
+ return birthdayCardGenerator;
+ }
+
+ throw new Error(type + " not implemented");
+ };
+
+ return generatorForType;
+}`;
+ yield* beginSlide("third currying step");
+
+ yield* curried().edit(1)`const cardGeneratorsFor = (person: PersonI): ${edit(
+ "(() => CardI)",
+ "((type: string) => () => CardI)",
+ )} => {
+ const birthdayCardGenerator = () => birthdayCardFn(person); // closure
+ const valentineCardGenerator = () => valentineCardFn(person); // closure
+
+ const generatorForType = (type: string) => {
+ switch (type) {
+ case "valentine":
+ return valentineCardGenerator;
+ case "birthday":
+ return birthdayCardGenerator;
+ }
+
+ throw new Error(type + " not implemented");
+ };
+
+ return generatorForType;
+}`;
+ yield* beginSlide("fourth currying step");
+
+ yield* curried().edit(1)`${edit(
+ cardGeneratorsForSource,
+ `const alanCardGenerator = (type: string): (() => CardI) => {
+ const alan: PersonI = {
+ name: "Alan Turing",
+ birthday: new Date("06/23/1912"),
+ color: theme.green.hex,
+ };
+
+ return cardGeneratorFor(alan, type);
+};`,
+ )}`;
+ yield* beginSlide("partial application");
+
+ yield* curried().selection(
+ [...range(7, 9, 7, 38), ...range(0, 26, 0, 40)],
+ 1,
+ );
+
+ yield* beginSlide("highlight");
+
+ yield* curried().edit(
+ 1,
+ )`const alanCardGenerator = (type: string): (() => CardI) => {
+ const alan: PersonI = {
+ name: "Alan Turing",
+ birthday: new Date("06/23/1912"),
+ color: theme.green.hex,
+ };
+
+ return cardGeneratorFor(alan, type);
+};${insert(`
+(alanCardGenerator("valentine")()).message === (cardGeneratorsFor(alan)("valentine")()).message;`)}`;
+ yield* beginSlide("show application of pa");
+});
diff --git a/src/scenes/dna.meta b/src/scenes/dna.meta
new file mode 100644
index 0000000..c1ba9fb
--- /dev/null
+++ b/src/scenes/dna.meta
@@ -0,0 +1,5 @@
+{
+ "version": 0,
+ "timeEvents": [],
+ "seed": 659686876
+} \ No newline at end of file
diff --git a/src/scenes/dna.tsx b/src/scenes/dna.tsx
new file mode 100644
index 0000000..9cb77bd
--- /dev/null
+++ b/src/scenes/dna.tsx
@@ -0,0 +1,22 @@
+import { Video, Layout, makeScene2D } from "@motion-canvas/2d";
+import {
+ Direction,
+ beginSlide,
+ createRef,
+ slideTransition,
+} from "@motion-canvas/core";
+import mitosis from "../../public/img/cell_division.mp4";
+
+export default makeScene2D(function* (view) {
+ const layout = createRef<Layout>();
+ const vid = createRef<Video>();
+
+ view.add(
+ <Video playbackRate={4} width={900} ref={vid} src={mitosis} x={0} />,
+ );
+
+ yield* slideTransition(Direction.Left);
+ yield vid().play();
+ yield vid().loop(true);
+ yield* beginSlide("show mitosis");
+});
diff --git a/src/scenes/function_dna.meta b/src/scenes/function_dna.meta
new file mode 100644
index 0000000..2648f3d
--- /dev/null
+++ b/src/scenes/function_dna.meta
@@ -0,0 +1,5 @@
+{
+ "version": 0,
+ "timeEvents": [],
+ "seed": 4141071538
+} \ No newline at end of file
diff --git a/src/scenes/function_dna.tsx b/src/scenes/function_dna.tsx
new file mode 100644
index 0000000..64098d3
--- /dev/null
+++ b/src/scenes/function_dna.tsx
@@ -0,0 +1,91 @@
+import { makeScene2D } from "@motion-canvas/2d";
+import {
+ Direction,
+ all,
+ beginSlide,
+ createRef,
+ slideTransition,
+} from "@motion-canvas/core";
+import { PEOPLE, Person, PersonI } from "../components/person";
+import { birthdayCardFn, valentineCardFn } from "./generalized";
+import { CardI } from "./birthday_letters";
+import { FunctionBox } from "../components/function_box";
+
+export const cardGeneratorFor = (
+ person: PersonI,
+ type: string,
+): (() => CardI) => {
+ const birthdayCardGenerator = () => birthdayCardFn(person);
+ const valentineCardGenerator = () => valentineCardFn(person);
+
+ switch (type) {
+ case "valentine":
+ return valentineCardGenerator;
+ case "birthday":
+ return birthdayCardGenerator;
+ }
+
+ throw new Error(type + " not implemented");
+};
+
+export const cardGeneratorForSource = `const cardGeneratorFor = (person: PersonI, type: string): (() => CardI) => {
+ const birthdayCardGenerator = () => birthdayCardFn(person); // closure
+ const valentineCardGenerator = () => valentineCardFn(person); // closure
+
+ switch (type) {
+ case "valentine":
+ return valentineCardGenerator;
+ case "birthday":
+ return birthdayCardGenerator;
+ }
+
+ throw new Error(type + " not implemented");
+}`;
+
+export default makeScene2D(function* (view) {
+ const box = createRef<FunctionBox>();
+
+ view.add(
+ <FunctionBox
+ arity={2}
+ ref={box}
+ fn={cardGeneratorFor}
+ source={cardGeneratorForSource}
+ outputFontSize={20}
+ />,
+ );
+
+ yield* all(
+ box().reset(0.1),
+ box().showCode(0.5),
+ slideTransition(Direction.Left, 0.5),
+ );
+
+ yield* beginSlide("show code");
+
+ const [alan] = PEOPLE;
+ yield* box().setInputs(
+ [{ val: alan, node: <Person person={alan} /> }, { val: "valentine" }],
+ 0.5,
+ );
+
+ yield* beginSlide("show inputs");
+
+ yield* box().hideCode(0.5);
+ yield* box().propogateInput(0.5);
+ yield* box().propogateOutput(0.5);
+
+ yield* beginSlide("create child function");
+
+ const child = box().getChild();
+ yield* child().showCode(0.5);
+
+ yield* beginSlide("show child function");
+
+ yield* child().hideCode(0.5);
+ yield* child().setInputs([{ val: "" }], 0.5);
+ yield* child().propogateInput(0.5);
+ yield* child().propogateOutput(0.5);
+
+ yield* beginSlide("propogate child function");
+});
diff --git a/src/scenes/further.meta b/src/scenes/further.meta
new file mode 100644
index 0000000..bc7d625
--- /dev/null
+++ b/src/scenes/further.meta
@@ -0,0 +1,5 @@
+{
+ "version": 0,
+ "timeEvents": [],
+ "seed": 60068077
+} \ No newline at end of file
diff --git a/src/scenes/further.tsx b/src/scenes/further.tsx
new file mode 100644
index 0000000..8758080
--- /dev/null
+++ b/src/scenes/further.tsx
@@ -0,0 +1,43 @@
+import { Layout, Txt, makeScene2D } from "@motion-canvas/2d";
+import {
+ Direction,
+ beginSlide,
+ createRef,
+ slideTransition,
+} from "@motion-canvas/core";
+import { theme } from "../theme";
+
+const goingFurtherLInes = [
+ "pattern matching",
+ "the typed lambda calculus",
+ "church turing thesis",
+ "lazy vs eager loading, why we use left innermost reduction",
+ "compiling to machine code & continuation passing style as IR",
+ "monads",
+];
+
+export default makeScene2D(function* (view) {
+ const lines = createRef<Txt>();
+
+ view.add(
+ <Layout layout direction="column" alignItems="center">
+ <Txt fontSize={40} fontFamily={theme.font} fill={theme.text.hex}>
+ Going Further
+ </Txt>
+ <Txt
+ ref={lines}
+ fontSize={30}
+ fontFamily={theme.font}
+ fill={theme.text.hex}
+ ></Txt>
+ </Layout>
+ );
+
+ yield* slideTransition(Direction.Right);
+ yield* beginSlide("Going Further");
+
+ for (const line of goingFurtherLInes) {
+ yield* lines().text(lines().text() + "\n\n" + line, 1);
+ yield* beginSlide("line - " + line);
+ }
+});
diff --git a/src/scenes/generalized.tsx b/src/scenes/generalized.tsx
index 67cbb31..a3cc841 100644
--- a/src/scenes/generalized.tsx
+++ b/src/scenes/generalized.tsx
@@ -1,4 +1,4 @@
-import { Layout, Txt, makeScene2D } from "@motion-canvas/2d";
+import { Layout, makeScene2D } from "@motion-canvas/2d";
import {
Direction,
all,
@@ -11,7 +11,7 @@ import { FunctionBox } from "../components/function_box";
import { PEOPLE, Person, PersonI } from "../components/person";
import { CardI, daysUntilNextDate } from "./birthday_letters";
-const valentineCardFn = (person: PersonI): CardI => {
+export const valentineCardFn = (person: PersonI): CardI => {
const valentinesDay = new Date("02/14/2024");
const message =
`Dear, ${person.name}\n.` +
@@ -21,7 +21,7 @@ const valentineCardFn = (person: PersonI): CardI => {
return { message, deliverInDays };
};
-const birthdayCardFn = (person: PersonI): CardI => {
+export const birthdayCardFn = (person: PersonI): CardI => {
const age = new Date().getFullYear() - person.birthday.getFullYear();
const ending =
({ 1: "st", 2: "nd", 3: "rd" } as Record<number, string>)[
diff --git a/src/scenes/impostor.meta b/src/scenes/impostor.meta
new file mode 100644
index 0000000..ab81c2f
--- /dev/null
+++ b/src/scenes/impostor.meta
@@ -0,0 +1,5 @@
+{
+ "version": 0,
+ "timeEvents": [],
+ "seed": 675349706
+} \ No newline at end of file
diff --git a/src/scenes/impostor.tsx b/src/scenes/impostor.tsx
new file mode 100644
index 0000000..830178a
--- /dev/null
+++ b/src/scenes/impostor.tsx
@@ -0,0 +1,134 @@
+import { Img, Layout, makeScene2D } from "@motion-canvas/2d";
+import {
+ Direction,
+ beginSlide,
+ createRef,
+ slideTransition,
+ all,
+} from "@motion-canvas/core";
+import {
+ CodeBlock,
+ edit,
+ insert,
+ lines,
+} from "@motion-canvas/2d/lib/components/CodeBlock";
+import amogusSus from "../../public/amogus.png";
+
+const cardBuilder = `const buildCards = (
+ people: PersonI[],
+ cardGenerator: (person: PersonI) => CardI,
+): CardI[] => {
+ const cards: CardI[] = [];
+
+ for (const person of people) {
+ const card = cardGenerator(person);
+ cards.push(card);
+ }
+
+ return cards;
+};`;
+
+export default makeScene2D(function* (view) {
+ const layout = createRef<Layout>();
+
+ const buildCards = createRef<CodeBlock>();
+ const amogus = createRef<Img>();
+
+ view.add(
+ <Layout ref={layout} layout direction="row" gap={0}>
+ <CodeBlock
+ fontSize={25}
+ language="typescript"
+ ref={buildCards}
+ code={cardBuilder}
+ />
+ <Img ref={amogus} src={amogusSus} opacity={0} width={0} height={0} />
+ </Layout>,
+ );
+
+ yield* slideTransition(Direction.Right);
+ yield* beginSlide("show buildCards");
+
+ yield* buildCards().selection([...lines(6), ...lines(8)], 0.75);
+ yield* all(
+ amogus().width(300, 0.5),
+ amogus().height(400, 0.5),
+
+ amogus().opacity(1, 0.5),
+ layout().gap(100, 0.5),
+ );
+
+ yield* beginSlide("show side effects");
+
+ yield* all(
+ buildCards().edit(1.2)`const buildCards = (
+ people: PersonI[],
+ cardGenerator: (person: PersonI) => CardI,
+): CardI[] => {${insert(`
+ if (people.length === 0) {
+ return [];
+ }
+`)}
+ const cards: CardI[] = [];
+
+ for (const person of people) {
+ const card = cardGenerator(person);
+ cards.push(card);
+ }
+
+ return cards;
+};`,
+ amogus().width(0, 0.3),
+ amogus().height(0, 0.3),
+
+ layout().gap(0, 0.3),
+ );
+ yield* beginSlide("base case");
+
+ yield* buildCards().edit(1.2)`const buildCards = (
+ people: PersonI[],
+ cardGenerator: (person: PersonI) => CardI,
+): CardI[] => {
+ if (people.length === 0) {
+ return [];
+ }
+
+ ${edit(
+ "const cards: CardI[] = [];",
+ `const person = people[0];
+ const card = cardGenerator(person);`,
+ )}
+
+ for (const person of people) {
+ const card = cardGenerator(person);
+ cards.push(card);
+ }
+
+ return cards;
+};`;
+ yield* beginSlide("first card");
+
+ yield* buildCards().edit(1.2)`const buildCards = (
+ people: PersonI[],
+ cardGenerator: (person: PersonI) => CardI,
+): CardI[] => {
+ if (people.length === 0) {
+ return [];
+ }
+
+ const person = people[0];
+ const card = cardGenerator(person);
+
+ ${edit(
+ `for (const person of people) {
+ const card = cardGenerator(person);
+ cards.push(card);
+ }`,
+ `const restPeople = people.slice(1);
+ const cards = [card].concat(buildCards(restPeople, cardGenerator));`,
+ )}
+
+ return cards;
+};`;
+ yield* beginSlide("recursion");
+});
diff --git a/src/scenes/index.ts b/src/scenes/index.ts
index 6f09c88..1a5c5f8 100644
--- a/src/scenes/index.ts
+++ b/src/scenes/index.ts
@@ -10,18 +10,54 @@ import valentines_letters from "./valentines_letters?scene";
import birthday_letters from "./birthday_letters?scene";
import sad_people from "./sad_people?scene";
import generalized from "./generalized?scene";
+import dna from "./dna?scene";
+import function_dna from "./function_dna?scene";
+import currying from "./currying?scene";
+import currying_detail from "./currying_detail?scene";
+import impostor from "./impostor?scene";
+import pros_cons from "./pros_cons?scene";
+import parttwo from "./parttwo?scene";
+import the_lambda_calculus from "./the_lambda_calculus?scene";
+import reductions from "./reductions?scene";
+import substitution from "./substitution?scene";
+import lambda_reduction_example from "./lambda_reduction_example?scene";
+import church_encoding from "./church_encoding?scene";
+import boolean_algebra_lambda from "./boolean_algebra_lambda?scene";
+import boolean_encoding from "./boolean_encoding?scene";
+import recursion from "./recursion?scene";
+import lambda_recursion from "./lambda_recursion?scene";
+import further from "./further?scene";
+import questions from "./questions?scene";
export const scenes = [
- //title,
- //me,
- //partone,
- //flirtingwithfunctions,
- //doctor,
- //first_box,
- //hungry_partner,
- //pure_functions,
- //valentines_letters,
- //sad_people,
- //birthday_letters,
+ title,
+ me,
+ partone,
+ flirtingwithfunctions,
+ doctor,
+ first_box,
+ hungry_partner,
+ pure_functions,
+ valentines_letters,
+ sad_people,
+ birthday_letters,
generalized,
+ dna,
+ function_dna,
+ currying,
+ currying_detail,
+ impostor,
+ pros_cons,
+ parttwo,
+ the_lambda_calculus,
+ reductions,
+ substitution,
+ church_encoding,
+ lambda_reduction_example,
+ boolean_encoding,
+ boolean_algebra_lambda,
+ recursion,
+ lambda_recursion,
+ further,
+ questions,
];
diff --git a/src/scenes/lambda_recursion.meta b/src/scenes/lambda_recursion.meta
new file mode 100644
index 0000000..35466b8
--- /dev/null
+++ b/src/scenes/lambda_recursion.meta
@@ -0,0 +1,5 @@
+{
+ "version": 0,
+ "timeEvents": [],
+ "seed": 1988478153
+} \ No newline at end of file
diff --git a/src/scenes/lambda_recursion.tsx b/src/scenes/lambda_recursion.tsx
new file mode 100644
index 0000000..4a2b4a1
--- /dev/null
+++ b/src/scenes/lambda_recursion.tsx
@@ -0,0 +1,45 @@
+import { Layout, makeScene2D } from "@motion-canvas/2d";
+import {
+ Direction,
+ beginSlide,
+ createRef,
+ slideTransition,
+} from "@motion-canvas/core";
+import { LambdaReducer } from "../components/lambda_reducer";
+import { baseDefinitions } from "../utils/lambdas";
+
+export default makeScene2D(function* (view) {
+ const lambdaReducer = createRef<LambdaReducer>();
+ const layout = createRef<Layout>();
+
+ view.add(
+ <Layout
+ layout
+ ref={layout}
+ direction="column"
+ alignItems="center"
+ gap={50}
+ ></Layout>,
+ );
+ yield* slideTransition(Direction.Right);
+ yield* beginSlide("Boolean Reductions");
+
+ for (const term of ["Y", "(Y (λ y . y))"]) {
+ yield* layout().opacity(0, 0.5);
+ layout().add(
+ <LambdaReducer
+ ref={lambdaReducer}
+ lambdaTerm={term}
+ definitions={baseDefinitions}
+ ></LambdaReducer>,
+ );
+ yield* layout().opacity(1, 0.5);
+
+ yield* beginSlide("Next Reduction " + term);
+ for (let i = 0; i < 6; i++) {
+ yield* lambdaReducer().step(0.5);
+ yield* beginSlide(term + " Next Step " + i);
+ }
+ layout().removeChildren();
+ }
+});
diff --git a/src/scenes/lambda_reduction_example.meta b/src/scenes/lambda_reduction_example.meta
new file mode 100644
index 0000000..eeefd28
--- /dev/null
+++ b/src/scenes/lambda_reduction_example.meta
@@ -0,0 +1,5 @@
+{
+ "version": 0,
+ "timeEvents": [],
+ "seed": 2126031881
+} \ No newline at end of file
diff --git a/src/scenes/lambda_reduction_example.tsx b/src/scenes/lambda_reduction_example.tsx
new file mode 100644
index 0000000..8f2436a
--- /dev/null
+++ b/src/scenes/lambda_reduction_example.tsx
@@ -0,0 +1,47 @@
+import { Layout, makeScene2D } from "@motion-canvas/2d";
+import {
+ Direction,
+ beginSlide,
+ createRef,
+ slideTransition,
+} from "@motion-canvas/core";
+import { LambdaReducer } from "../components/lambda_reducer";
+import { baseDefinitions } from "../utils/lambdas";
+
+export default makeScene2D(function* (view) {
+ const lambdaReducer = createRef<LambdaReducer>();
+ const layout = createRef<Layout>();
+
+ view.add(
+ <Layout layout ref={layout} direction="column" alignItems="center" gap={50}>
+ <LambdaReducer
+ ref={lambdaReducer}
+ lambdaTerm={"(succ zero)"}
+ definitions={baseDefinitions}
+ ></LambdaReducer>
+ </Layout>
+ );
+
+ yield* slideTransition(Direction.Right);
+ yield* beginSlide("Example Reductions");
+
+ for (let i = 0; !lambdaReducer().isDone(); i++) {
+ yield* lambdaReducer().step(0.5);
+ yield* beginSlide("1 Next Step " + i);
+ }
+
+ const one = lambdaReducer().getCode();
+ const reduceToTwo = createRef<LambdaReducer>();
+ layout().add(
+ <LambdaReducer
+ ref={reduceToTwo}
+ lambdaTerm={`(succ ${one})`}
+ definitions={baseDefinitions}
+ ></LambdaReducer>
+ );
+
+ for (let i = 0; !reduceToTwo().isDone(); i++) {
+ yield* reduceToTwo().step(0.5);
+ yield* beginSlide("2 Next Step " + i);
+ }
+});
diff --git a/src/scenes/me.tsx b/src/scenes/me.tsx
index 1d819e3..143d131 100644
--- a/src/scenes/me.tsx
+++ b/src/scenes/me.tsx
@@ -1,7 +1,7 @@
import { Node, Img, Txt, Layout, makeScene2D } from "@motion-canvas/2d";
import { beginSlide, createRef, all } from "@motion-canvas/core";
-import me from "../../public/img/me.jpg";
+import pengy from "../../public/img/pengy.png";
import { theme } from "../theme";
export default makeScene2D(function* (view) {
@@ -41,11 +41,11 @@ export default makeScene2D(function* (view) {
</Txt>
</Node>
</Layout>
- <Img y={-10} ref={img} src={me} width={10} alpha={0} radius={20} />{" "}
+ <Img y={-10} ref={img} src={pengy} width={10} alpha={0} radius={20} />{" "}
<Txt opacity={0} ref={src} fontFamily={theme.font} fill={theme.green.hex}>
git.simponic.xyz/simponic/compiling-the-lambda-calculus
</Txt>
- </>,
+ </>
);
yield img().fill(img().getColorAtPoint(0));
@@ -59,7 +59,7 @@ export default makeScene2D(function* (view) {
node().opacity(1, 1),
layout().position.x(diff, 1),
src().opacity(1, 1),
- src().position.y(290, 1),
+ src().position.y(290, 1)
);
yield* beginSlide("About Me");
diff --git a/src/scenes/parttwo.meta b/src/scenes/parttwo.meta
new file mode 100644
index 0000000..c18d436
--- /dev/null
+++ b/src/scenes/parttwo.meta
@@ -0,0 +1,5 @@
+{
+ "version": 0,
+ "timeEvents": [],
+ "seed": 2908089933
+} \ No newline at end of file
diff --git a/src/scenes/parttwo.tsx b/src/scenes/parttwo.tsx
new file mode 100644
index 0000000..eb62e09
--- /dev/null
+++ b/src/scenes/parttwo.tsx
@@ -0,0 +1,16 @@
+import { Layout, Txt, makeScene2D } from "@motion-canvas/2d";
+import { Direction, beginSlide, slideTransition } from "@motion-canvas/core";
+import { theme } from "../theme";
+
+export default makeScene2D(function* (view) {
+ yield view.add(
+ <Layout layout direction="column" alignItems="center">
+ <Txt fontFamily={theme.font} fontSize={100} fill={theme.text.hex}>
+ Part Two
+ </Txt>
+ </Layout>
+ );
+
+ yield* slideTransition(Direction.Right);
+ yield* beginSlide("Part Two");
+});
diff --git a/src/scenes/pros_cons.meta b/src/scenes/pros_cons.meta
new file mode 100644
index 0000000..128b02e
--- /dev/null
+++ b/src/scenes/pros_cons.meta
@@ -0,0 +1,5 @@
+{
+ "version": 0,
+ "timeEvents": [],
+ "seed": 3275339858
+} \ No newline at end of file
diff --git a/src/scenes/pros_cons.tsx b/src/scenes/pros_cons.tsx
new file mode 100644
index 0000000..508c7bc
--- /dev/null
+++ b/src/scenes/pros_cons.tsx
@@ -0,0 +1,61 @@
+import { Img, Layout, Txt, makeScene2D } from "@motion-canvas/2d";
+import {
+ Direction,
+ beginSlide,
+ createRef,
+ slideTransition,
+} from "@motion-canvas/core";
+import { theme } from "../theme";
+
+const PROS = [
+ "readbility, reasoning",
+ "concurrency",
+ "no side effects",
+ "computers and math!",
+];
+
+const CONS = ["more computation", "higher learning curve"];
+
+export default makeScene2D(function* (view) {
+ const pros = createRef<Txt>();
+ const cons = createRef<Txt>();
+
+ view.add(
+ <Layout direction="row" justifyContent="center" gap={300} layout>
+ <Layout direction="column" justifyContent="end">
+ <Txt fontSize={30} fontFamily={theme.font} fill={theme.green.hex}>
+ PROS :)
+ </Txt>
+ <Txt
+ ref={pros}
+ fontSize={30}
+ fontFamily={theme.font}
+ fill={theme.text.hex}
+ ></Txt>
+ </Layout>
+ <Layout direction="column" justifyContent="start">
+ <Txt fontSize={30} fontFamily={theme.font} fill={theme.red.hex}>
+ CONS :(
+ </Txt>
+ <Txt
+ ref={cons}
+ fontSize={30}
+ fontFamily={theme.font}
+ fill={theme.text.hex}
+ ></Txt>
+ </Layout>
+ </Layout>,
+ );
+ yield* slideTransition(Direction.Right);
+ yield* beginSlide("layout pros and cons");
+
+ for (const pro of PROS) {
+ yield* pros().text(pros().text() + "\n\n+ " + pro, 0.5);
+ yield* beginSlide("pro - " + pro);
+ }
+
+ for (const con of CONS) {
+ yield* cons().text(cons().text() + "\n\n- " + con, 0.5);
+ yield* beginSlide("con - " + con);
+ }
+});
diff --git a/src/scenes/questions.meta b/src/scenes/questions.meta
new file mode 100644
index 0000000..c6cc2f9
--- /dev/null
+++ b/src/scenes/questions.meta
@@ -0,0 +1,5 @@
+{
+ "version": 0,
+ "timeEvents": [],
+ "seed": 3904825059
+} \ No newline at end of file
diff --git a/src/scenes/questions.tsx b/src/scenes/questions.tsx
new file mode 100644
index 0000000..16adf54
--- /dev/null
+++ b/src/scenes/questions.tsx
@@ -0,0 +1,16 @@
+import { Layout, Txt, makeScene2D } from "@motion-canvas/2d";
+import { Direction, beginSlide, slideTransition } from "@motion-canvas/core";
+import { theme } from "../theme";
+
+export default makeScene2D(function* (view) {
+ view.add(
+ <Layout layout direction="column" alignItems="center">
+ <Txt fontSize={40} fontFamily={theme.font} fill={theme.text.hex}>
+ Questions?
+ </Txt>
+ </Layout>
+ );
+
+ yield* slideTransition(Direction.Right);
+ yield* beginSlide("Questions");
+});
diff --git a/src/scenes/recursion.meta b/src/scenes/recursion.meta
new file mode 100644
index 0000000..b574f39
--- /dev/null
+++ b/src/scenes/recursion.meta
@@ -0,0 +1,5 @@
+{
+ "version": 0,
+ "timeEvents": [],
+ "seed": 693500437
+} \ No newline at end of file
diff --git a/src/scenes/recursion.tsx b/src/scenes/recursion.tsx
new file mode 100644
index 0000000..167dd00
--- /dev/null
+++ b/src/scenes/recursion.tsx
@@ -0,0 +1,36 @@
+import { Layout, Txt, makeScene2D } from "@motion-canvas/2d";
+import {
+ Direction,
+ beginSlide,
+ createRef,
+ slideTransition,
+} from "@motion-canvas/core";
+import { theme } from "../theme";
+
+const recursion = ["Y = λ f . (λ x . f (x x)) (λ x . f (x x))"];
+
+export default makeScene2D(function* (view) {
+ const lines = createRef<Txt>();
+
+ view.add(
+ <Layout layout direction="column" alignItems="center">
+ <Txt fontSize={40} fontFamily={theme.font} fill={theme.text.hex}>
+ Recursion and Combinators
+ </Txt>
+ <Txt
+ ref={lines}
+ fontSize={30}
+ fontFamily={theme.font}
+ fill={theme.text.hex}
+ ></Txt>
+ </Layout>
+ );
+
+ yield* slideTransition(Direction.Right);
+ yield* beginSlide("The Lambda Calculus - Church Encoding");
+
+ for (const line of recursion) {
+ yield* lines().text(lines().text() + "\n\n" + line, 1);
+ yield* beginSlide("recursion - " + line);
+ }
+});
diff --git a/src/scenes/reductions.meta b/src/scenes/reductions.meta
new file mode 100644
index 0000000..6c4e6c7
--- /dev/null
+++ b/src/scenes/reductions.meta
@@ -0,0 +1,5 @@
+{
+ "version": 0,
+ "timeEvents": [],
+ "seed": 2906227318
+} \ No newline at end of file
diff --git a/src/scenes/reductions.tsx b/src/scenes/reductions.tsx
new file mode 100644
index 0000000..5ee8961
--- /dev/null
+++ b/src/scenes/reductions.tsx
@@ -0,0 +1,40 @@
+import { Layout, Txt, makeScene2D } from "@motion-canvas/2d";
+import {
+ Direction,
+ beginSlide,
+ createRef,
+ slideTransition,
+} from "@motion-canvas/core";
+import { theme } from "../theme";
+
+const reductions = [
+ "1. α - conversion: Renaming of bound variables to avoid name clashes (out of\nscope - assume user knows not to bind variable names already chosen elsewhere).",
+ "2. β - reduction: Application of a function to an argument.",
+ "3. η - reduction: Conversion of a function to a point-free form (out of scope).",
+];
+
+export default makeScene2D(function* (view) {
+ const rules = createRef<Txt>();
+
+ view.add(
+ <Layout layout direction="column" alignItems="center">
+ <Txt fontSize={40} fontFamily={theme.font} fill={theme.text.hex}>
+ The Lambda Calculus - Reductions
+ </Txt>
+ <Txt
+ ref={rules}
+ fontSize={30}
+ fontFamily={theme.font}
+ fill={theme.text.hex}
+ ></Txt>
+ </Layout>
+ );
+
+ yield* slideTransition(Direction.Right);
+ yield* beginSlide("The Lambda Calculus - Reductions");
+
+ for (const rule of reductions) {
+ yield* rules().text(rules().text() + "\n\n" + rule, 1);
+ yield* beginSlide("reduction - " + rule);
+ }
+});
diff --git a/src/scenes/substitution.meta b/src/scenes/substitution.meta
new file mode 100644
index 0000000..56299b4
--- /dev/null
+++ b/src/scenes/substitution.meta
@@ -0,0 +1,5 @@
+{
+ "version": 0,
+ "timeEvents": [],
+ "seed": 3113568447
+} \ No newline at end of file
diff --git a/src/scenes/substitution.tsx b/src/scenes/substitution.tsx
new file mode 100644
index 0000000..1e7e48c
--- /dev/null
+++ b/src/scenes/substitution.tsx
@@ -0,0 +1,45 @@
+import { Layout, Txt, makeScene2D } from "@motion-canvas/2d";
+import {
+ Direction,
+ beginSlide,
+ createRef,
+ slideTransition,
+} from "@motion-canvas/core";
+import { theme } from "../theme";
+
+const substitution = [
+ "β-reduce [ (λ x . x) y ] => y\n\n",
+ "Formal definition:",
+ "1. x[x := N] = N",
+ "2. y[x := N] = y if y != x",
+ "3. (M N)[x := P] = (M[x := P]) (N[x := P])",
+ "4. (λ x . M)[x := N] = λ x . M",
+ "5. (λ y . M)[x := N] = λ y . (M[x := N]) if y != x and y is not _free_ in N",
+ " +=> A variable is _free_ in a lambda term if it is not bound by a λ abstraction.",
+];
+
+export default makeScene2D(function* (view) {
+ const rules = createRef<Txt>();
+
+ view.add(
+ <Layout layout direction="column" alignItems="center">
+ <Txt fontSize={40} fontFamily={theme.font} fill={theme.text.hex}>
+ The Lambda Calculus - Substitution Rules
+ </Txt>
+ <Txt
+ ref={rules}
+ fontSize={30}
+ fontFamily={theme.font}
+ fill={theme.text.hex}
+ ></Txt>
+ </Layout>
+ );
+
+ yield* slideTransition(Direction.Right);
+ yield* beginSlide("The Lambda Calculus - Substitutions");
+
+ for (const rule of substitution) {
+ yield* rules().text(rules().text() + "\n\n" + rule, 1);
+ yield* beginSlide("substitution - " + rule);
+ }
+});
diff --git a/src/scenes/the_lambda_calculus.meta b/src/scenes/the_lambda_calculus.meta
new file mode 100644
index 0000000..56b46d0
--- /dev/null
+++ b/src/scenes/the_lambda_calculus.meta
@@ -0,0 +1,5 @@
+{
+ "version": 0,
+ "timeEvents": [],
+ "seed": 561758562
+} \ No newline at end of file
diff --git a/src/scenes/the_lambda_calculus.tsx b/src/scenes/the_lambda_calculus.tsx
new file mode 100644
index 0000000..7a3e692
--- /dev/null
+++ b/src/scenes/the_lambda_calculus.tsx
@@ -0,0 +1,40 @@
+import { Layout, Txt, makeScene2D } from "@motion-canvas/2d";
+import {
+ Direction,
+ beginSlide,
+ createRef,
+ slideTransition,
+} from "@motion-canvas/core";
+import { theme } from "../theme";
+
+const lambdaRules = [
+ '1. "x": A variable is a character or string representing a parameter, and is a valid lambda term.',
+ '2. "(λ x . t)" is an "abstraction" - a function definition, taking as input the \nbound variable x and returning the lambda term t.',
+ '3. (M N) is an "application", applying a lambda term M with a lambda term N.',
+];
+
+export default makeScene2D(function* (view) {
+ const rules = createRef<Txt>();
+
+ view.add(
+ <Layout layout direction="column" alignItems="center">
+ <Txt fontSize={40} fontFamily={theme.font} fill={theme.text.hex}>
+ The Lambda Calculus
+ </Txt>
+ <Txt
+ ref={rules}
+ fontSize={30}
+ fontFamily={theme.font}
+ fill={theme.text.hex}
+ ></Txt>
+ </Layout>
+ );
+
+ yield* slideTransition(Direction.Right);
+ yield* beginSlide("The Lambda Calculus");
+
+ for (const rule of lambdaRules) {
+ yield* rules().text(rules().text() + "\n\n" + rule, 1);
+ yield* beginSlide("rule - " + rule);
+ }
+});
diff --git a/src/utils/lambdas.ts b/src/utils/lambdas.ts
new file mode 100644
index 0000000..ef6b399
--- /dev/null
+++ b/src/utils/lambdas.ts
@@ -0,0 +1,35 @@
+const succ = "(λ n.(λ f.(λ x.(f ((n f) x)))))";
+const zero = "(λ g.(λ y.y))";
+const one = "(λ g.(λ y.(g y)))";
+const two = "(λ g.(λ y.(g (g y))))";
+const three = "(λ g.(λ y.(g (g (g y)))))";
+const four = "(λ g.(λ y.(g (g (g (g y))))))";
+const five = "(λ g.(λ y.(g (g (g (g (g y)))))))";
+const mult = "(λ m.(λ n.(λ f.(m (n f)))))";
+const trueL = "(λ a.(λ b.a))";
+const falseL = "(λ a.(λ b.b))";
+const ifL = "(λ b.(λ n.(λ m.((b n) m))))";
+const iszero = "(λ num.((num (λ x.false)) true))";
+const not = "(λ b.((b false) true))";
+const and = "(λ b.(λ c.((b c) false)))";
+const or = "(λ b.(λ c.((b true) c)))";
+const Y = "(λ h.((λ z.(h (z z))) (λ z.(h (z z)))))";
+
+export const baseDefinitions = {
+ iszero,
+ succ,
+ zero,
+ one,
+ two,
+ three,
+ four,
+ five,
+ mult,
+ true: trueL,
+ false: falseL,
+ if: ifL,
+ not,
+ and,
+ or,
+ Y,
+};
diff --git a/src/utils/levenshtein.ts b/src/utils/levenshtein.ts
new file mode 100644
index 0000000..87bfd9d
--- /dev/null
+++ b/src/utils/levenshtein.ts
@@ -0,0 +1,103 @@
+export enum EditOperation {
+ Insert = "insert",
+ Delete = "delete",
+ Edit = "edit",
+ None = "none",
+}
+
+interface Diff {
+ old: string;
+ new: string;
+}
+
+interface Edit {
+ operation: EditOperation;
+ diff: Diff;
+}
+
+export const calculateLevenshteinOperations = (
+ str1: string,
+ str2: string
+): Edit[] => {
+ const len1 = str1.length;
+ const len2 = str2.length;
+ const dp: number[][] = Array.from({ length: len1 + 1 }, () =>
+ Array(len2 + 1).fill(0)
+ );
+
+ // Initialize DP table
+ for (let i = 0; i <= len1; i++) dp[i][0] = i;
+ for (let j = 0; j <= len2; j++) dp[0][j] = j;
+
+ // Fill DP table
+ for (let i = 1; i <= len1; i++) {
+ for (let j = 1; j <= len2; j++) {
+ const cost = str1[i - 1] === str2[j - 1] ? 0 : 1;
+ dp[i][j] = Math.min(
+ dp[i - 1][j] + 1, // Deletion
+ dp[i][j - 1] + 1, // Insertion
+ dp[i - 1][j - 1] + cost // Substitution (Edit)
+ );
+ }
+ }
+
+ // Backtrack to find operations
+ let operations: Edit[] = [];
+ let i = len1,
+ j = len2;
+ while (i > 0 || j > 0) {
+ if (
+ i > 0 &&
+ j > 0 &&
+ dp[i][j] === dp[i - 1][j - 1] &&
+ str1[i - 1] === str2[j - 1]
+ ) {
+ // No operation needed
+ operations.unshift({
+ operation: EditOperation.None,
+ diff: { old: str1[i - 1], new: str2[j - 1] },
+ });
+ i--;
+ j--;
+ } else if (i > 0 && dp[i][j] === dp[i - 1][j] + 1) {
+ // Delete operation
+ operations.unshift({
+ operation: EditOperation.Delete,
+ diff: { old: str1[i - 1], new: "" },
+ });
+ i--;
+ } else if (j > 0 && dp[i][j] === dp[i][j - 1] + 1) {
+ // Insert operation
+ operations.unshift({
+ operation: EditOperation.Insert,
+ diff: { old: "", new: str2[j - 1] },
+ });
+ j--;
+ } else if (i > 0 && j > 0) {
+ // Edit operation
+ operations.unshift({
+ operation: EditOperation.Edit,
+ diff: { old: str1[i - 1], new: str2[j - 1] },
+ });
+ i--;
+ j--;
+ }
+ }
+
+ // Simplify consecutive "none" operations into single operations
+ const simplifiedOperations = operations.reduce<Edit[]>((acc, op) => {
+ if (
+ acc.length > 0 &&
+ op.operation === EditOperation.None &&
+ acc[acc.length - 1].operation === EditOperation.None
+ ) {
+ acc[acc.length - 1].diff.old += op.diff.old;
+ acc[acc.length - 1].diff.new += op.diff.new;
+ } else {
+ acc.push(op);
+ }
+ return acc;
+ }, []);
+
+ return simplifiedOperations;
+};