diff options
author | Elizabeth Hunt <elizabeth.hunt@simponic.xyz> | 2024-02-13 20:00:02 -0700 |
---|---|---|
committer | Elizabeth Hunt <elizabeth.hunt@simponic.xyz> | 2024-02-13 20:00:02 -0700 |
commit | 0c476e92e1807928ffb30864126076ef4c6a0821 (patch) | |
tree | a4992161ce4b6203edffb5d78533e9c73e61e6f1 /src | |
parent | 512c245466fad78106a046c1ea6233acdcc3e4de (diff) | |
download | compiling-the-lambda-calculus-main.tar.gz compiling-the-lambda-calculus-main.zip |
Diffstat (limited to 'src')
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; +}; |