From 94b912b649150b9863a62096ba4740b2cc8ad21a Mon Sep 17 00:00:00 2001 From: Elizabeth Alexander Hunt Date: Sun, 5 Jul 2026 12:42:44 -0700 Subject: Factor --- .gitignore | 39 - Dockerfile | 29 +- eleventy.config.js | 40 - html/assets/bellevue.jpeg | Bin 0 -> 377844 bytes html/assets/bread.jpeg | Bin 0 -> 206615 bytes html/assets/critter.jpeg | Bin 0 -> 181598 bytes html/assets/euler.png | Bin 0 -> 297917 bytes html/assets/fourier.png | Bin 0 -> 461309 bytes html/assets/godel.png | Bin 0 -> 466399 bytes html/assets/gus.jpeg | Bin 0 -> 148667 bytes html/assets/julia.png | Bin 0 -> 263809 bytes html/assets/lambda.png | Bin 0 -> 179682 bytes html/assets/penguin.gif | Bin 0 -> 53409 bytes html/assets/petting_basil.mp4 | Bin 0 -> 1816422 bytes html/assets/scooters_on_ceiling.jpeg | Bin 0 -> 493630 bytes html/assets/tabloid.png | Bin 0 -> 254486 bytes html/assets/turing.png | Bin 0 -> 133163 bytes html/index.html | 216 ++++ html/toys/euler-golf/css/styles.css | 81 ++ html/toys/euler-golf/index.html | 80 ++ html/toys/euler-golf/js/controls.js | 33 + html/toys/euler-golf/js/cx.js | 308 +++++ html/toys/euler-golf/js/game.js | 275 ++++ html/toys/euler-golf/js/json-ds.js | 19 + html/toys/euler-golf/js/modal-vanilla.min.js | 1 + html/toys/euler-golf/js/sol.js | 44 + html/toys/fourier/index.html | 46 + html/toys/fourier/js/script.js | 294 +++++ html/toys/godel/index.html | 51 + html/toys/godel/js/compiler.js | 226 ++++ html/toys/godel/js/godel-worker.js | 42 + html/toys/godel/js/main.js | 5 + html/toys/godel/js/parser.js | 1059 ++++++++++++++++ html/toys/godel/js/ui.js | 224 ++++ html/toys/index.html | 0 html/toys/julia/index.html | 51 + html/toys/julia/julia.js | 143 +++ html/toys/tabloid/index.html | 53 + html/toys/tabloid/js/main.js | 5 + html/toys/tabloid/js/playground.js | 120 ++ html/toys/tabloid/js/samples.js | 308 +++++ html/toys/tabloid/js/tabloid.js | 707 +++++++++++ html/toys/turing/css/styles.css | 66 + html/toys/turing/index.html | 42 + html/toys/turing/js/machine.js | 75 ++ html/toys/turing/js/main.js | 4 + html/toys/turing/js/parser.js | 141 +++ html/toys/turing/js/samples.js | 88 ++ html/toys/turing/js/tape.js | 103 ++ html/toys/turing/js/ui.js | 386 ++++++ package-lock.json | 1730 -------------------------- package.json | 17 - src/_layouts/base.njk | 103 -- src/about.njk | 35 - src/assets/bellevue.jpeg | Bin 377844 -> 0 bytes src/assets/bread.jpeg | Bin 206615 -> 0 bytes src/assets/critter.jpeg | Bin 181598 -> 0 bytes src/assets/euler.png | Bin 297917 -> 0 bytes src/assets/fourier.png | Bin 461309 -> 0 bytes src/assets/godel.png | Bin 466399 -> 0 bytes src/assets/gus.jpeg | Bin 274894 -> 0 bytes src/assets/julia.png | Bin 263809 -> 0 bytes src/assets/lambda.png | Bin 179682 -> 0 bytes src/assets/penguin.gif | Bin 53409 -> 0 bytes src/assets/petting_basil.mp4 | Bin 1816422 -> 0 bytes src/assets/scooters_on_ceiling.jpeg | Bin 493630 -> 0 bytes src/assets/tabloid.png | Bin 254486 -> 0 bytes src/assets/turing.png | Bin 133163 -> 0 bytes src/blinkies/add.njk | 176 --- src/blinkies/submitted.njk | 9 - src/feed.njk | 32 - src/index.njk | 33 - src/resume.njk | 6 - src/toys.md | 94 -- src/toys/euler-golf/css/styles.css | 81 -- src/toys/euler-golf/index.html | 80 -- src/toys/euler-golf/js/controls.js | 33 - src/toys/euler-golf/js/cx.js | 308 ----- src/toys/euler-golf/js/game.js | 275 ---- src/toys/euler-golf/js/json-ds.js | 19 - src/toys/euler-golf/js/modal-vanilla.min.js | 1 - src/toys/euler-golf/js/sol.js | 44 - src/toys/fourier/index.html | 46 - src/toys/fourier/js/script.js | 294 ----- src/toys/godel/index.html | 51 - src/toys/godel/js/compiler.js | 226 ---- src/toys/godel/js/godel-worker.js | 42 - src/toys/godel/js/main.js | 5 - src/toys/godel/js/parser.js | 1059 ---------------- src/toys/godel/js/ui.js | 224 ---- src/toys/julia/index.html | 51 - src/toys/julia/julia.js | 136 -- src/toys/tabloid/index.html | 53 - src/toys/tabloid/js/main.js | 5 - src/toys/tabloid/js/playground.js | 120 -- src/toys/tabloid/js/samples.js | 308 ----- src/toys/tabloid/js/tabloid.js | 707 ----------- src/toys/turing/css/styles.css | 66 - src/toys/turing/index.html | 42 - src/toys/turing/js/machine.js | 75 -- src/toys/turing/js/main.js | 4 - src/toys/turing/js/parser.js | 141 --- src/toys/turing/js/samples.js | 88 -- src/toys/turing/js/tape.js | 103 -- src/toys/turing/js/ui.js | 386 ------ 105 files changed, 5311 insertions(+), 7401 deletions(-) delete mode 100644 .gitignore delete mode 100644 eleventy.config.js create mode 100644 html/assets/bellevue.jpeg create mode 100644 html/assets/bread.jpeg create mode 100644 html/assets/critter.jpeg create mode 100644 html/assets/euler.png create mode 100644 html/assets/fourier.png create mode 100644 html/assets/godel.png create mode 100644 html/assets/gus.jpeg create mode 100644 html/assets/julia.png create mode 100644 html/assets/lambda.png create mode 100644 html/assets/penguin.gif create mode 100644 html/assets/petting_basil.mp4 create mode 100644 html/assets/scooters_on_ceiling.jpeg create mode 100644 html/assets/tabloid.png create mode 100644 html/assets/turing.png create mode 100644 html/index.html create mode 100644 html/toys/euler-golf/css/styles.css create mode 100644 html/toys/euler-golf/index.html create mode 100644 html/toys/euler-golf/js/controls.js create mode 100644 html/toys/euler-golf/js/cx.js create mode 100644 html/toys/euler-golf/js/game.js create mode 100644 html/toys/euler-golf/js/json-ds.js create mode 100644 html/toys/euler-golf/js/modal-vanilla.min.js create mode 100644 html/toys/euler-golf/js/sol.js create mode 100644 html/toys/fourier/index.html create mode 100644 html/toys/fourier/js/script.js create mode 100644 html/toys/godel/index.html create mode 100644 html/toys/godel/js/compiler.js create mode 100644 html/toys/godel/js/godel-worker.js create mode 100644 html/toys/godel/js/main.js create mode 100644 html/toys/godel/js/parser.js create mode 100644 html/toys/godel/js/ui.js create mode 100644 html/toys/index.html create mode 100644 html/toys/julia/index.html create mode 100644 html/toys/julia/julia.js create mode 100644 html/toys/tabloid/index.html create mode 100644 html/toys/tabloid/js/main.js create mode 100644 html/toys/tabloid/js/playground.js create mode 100644 html/toys/tabloid/js/samples.js create mode 100644 html/toys/tabloid/js/tabloid.js create mode 100644 html/toys/turing/css/styles.css create mode 100644 html/toys/turing/index.html create mode 100644 html/toys/turing/js/machine.js create mode 100644 html/toys/turing/js/main.js create mode 100644 html/toys/turing/js/parser.js create mode 100644 html/toys/turing/js/samples.js create mode 100644 html/toys/turing/js/tape.js create mode 100644 html/toys/turing/js/ui.js delete mode 100644 package-lock.json delete mode 100644 package.json delete mode 100644 src/_layouts/base.njk delete mode 100644 src/about.njk delete mode 100644 src/assets/bellevue.jpeg delete mode 100644 src/assets/bread.jpeg delete mode 100644 src/assets/critter.jpeg delete mode 100644 src/assets/euler.png delete mode 100644 src/assets/fourier.png delete mode 100644 src/assets/godel.png delete mode 100644 src/assets/gus.jpeg delete mode 100644 src/assets/julia.png delete mode 100644 src/assets/lambda.png delete mode 100644 src/assets/penguin.gif delete mode 100644 src/assets/petting_basil.mp4 delete mode 100644 src/assets/scooters_on_ceiling.jpeg delete mode 100644 src/assets/tabloid.png delete mode 100644 src/assets/turing.png delete mode 100644 src/blinkies/add.njk delete mode 100644 src/blinkies/submitted.njk delete mode 100644 src/feed.njk delete mode 100644 src/index.njk delete mode 100644 src/resume.njk delete mode 100644 src/toys.md delete mode 100644 src/toys/euler-golf/css/styles.css delete mode 100644 src/toys/euler-golf/index.html delete mode 100644 src/toys/euler-golf/js/controls.js delete mode 100644 src/toys/euler-golf/js/cx.js delete mode 100644 src/toys/euler-golf/js/game.js delete mode 100644 src/toys/euler-golf/js/json-ds.js delete mode 100644 src/toys/euler-golf/js/modal-vanilla.min.js delete mode 100644 src/toys/euler-golf/js/sol.js delete mode 100644 src/toys/fourier/index.html delete mode 100644 src/toys/fourier/js/script.js delete mode 100644 src/toys/godel/index.html delete mode 100644 src/toys/godel/js/compiler.js delete mode 100644 src/toys/godel/js/godel-worker.js delete mode 100644 src/toys/godel/js/main.js delete mode 100644 src/toys/godel/js/parser.js delete mode 100644 src/toys/godel/js/ui.js delete mode 100644 src/toys/julia/index.html delete mode 100644 src/toys/julia/julia.js delete mode 100644 src/toys/tabloid/index.html delete mode 100644 src/toys/tabloid/js/main.js delete mode 100644 src/toys/tabloid/js/playground.js delete mode 100644 src/toys/tabloid/js/samples.js delete mode 100644 src/toys/tabloid/js/tabloid.js delete mode 100644 src/toys/turing/css/styles.css delete mode 100644 src/toys/turing/index.html delete mode 100644 src/toys/turing/js/machine.js delete mode 100644 src/toys/turing/js/main.js delete mode 100644 src/toys/turing/js/parser.js delete mode 100644 src/toys/turing/js/samples.js delete mode 100644 src/toys/turing/js/tape.js delete mode 100644 src/toys/turing/js/ui.js diff --git a/.gitignore b/.gitignore deleted file mode 100644 index b91c6ad..0000000 --- a/.gitignore +++ /dev/null @@ -1,39 +0,0 @@ -node_modules/ -_site/ -.DS_Store -._* -.Spotlight-V100 -.Trashes -ehthumbs.db -Thumbs.db - -# IDE -.vscode -.idea -*.swp -*.swo -*~ -.vim - -# Environment -.env -.env.local - -# Node and Build -node_modules/ -dist/ -npm-debug.log -yarn-error.log -*.log - -# Docker -.docker - -# Claude -.claude - -# Temporary files -*.tmp -*.log - - diff --git a/Dockerfile b/Dockerfile index 91f0185..b6d642c 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,17 +1,18 @@ -FROM node:22-alpine AS builder - -WORKDIR /app - -COPY package*.json ./ -RUN npm install - -COPY . . - -RUN npm run build - -FROM nginx:alpine as lizdotcoffee - -COPY --from=builder /app/_site /usr/share/nginx/html +#FROM node:22-alpine AS builder +# +#WORKDIR /app +# +#COPY package*.json ./ +#RUN npm install +# +#COPY . . +# +#RUN npm run build + +FROM nginx:alpine AS lizdotcoffee + +# COPY --from=builder /app/_site /usr/share/nginx/html +COPY html /usr/share/nginx/html COPY nginx.conf /etc/nginx/conf.d/default.conf EXPOSE 80 diff --git a/eleventy.config.js b/eleventy.config.js deleted file mode 100644 index a12047a..0000000 --- a/eleventy.config.js +++ /dev/null @@ -1,40 +0,0 @@ -import pluginRss from "@11ty/eleventy-plugin-rss"; -import markdownIt from "markdown-it"; -import { outdent } from "outdent"; - -export default function(eleventyConfig) { - eleventyConfig.addPlugin(pluginRss); - - eleventyConfig.addPassthroughCopy("src/assets"); - eleventyConfig.addPassthroughCopy("src/toys"); - - // Configure markdown-it - const md = markdownIt({ - html: true, - breaks: false, - linkify: true - }); - - eleventyConfig.setLibrary("md", md); - - // Add markdown shortcode - eleventyConfig.addPairedShortcode("markdown", (content) => { - return md.render(outdent`${content}`); - }); - - // Add posts collection - eleventyConfig.addCollection("posts", (collectionApi) => { - return collectionApi.getFilteredByGlob("src/posts/**/*.md"); - }); - - return { - dir: { - input: "src", - output: "_site", - includes: "_includes", - layouts: "_layouts" - }, - markdownTemplateEngine: "njk", - htmlTemplateEngine: "njk" - }; -} diff --git a/html/assets/bellevue.jpeg b/html/assets/bellevue.jpeg new file mode 100644 index 0000000..0c68197 Binary files /dev/null and b/html/assets/bellevue.jpeg differ diff --git a/html/assets/bread.jpeg b/html/assets/bread.jpeg new file mode 100644 index 0000000..e9f4021 Binary files /dev/null and b/html/assets/bread.jpeg differ diff --git a/html/assets/critter.jpeg b/html/assets/critter.jpeg new file mode 100644 index 0000000..168dde4 Binary files /dev/null and b/html/assets/critter.jpeg differ diff --git a/html/assets/euler.png b/html/assets/euler.png new file mode 100644 index 0000000..9ae56f3 Binary files /dev/null and b/html/assets/euler.png differ diff --git a/html/assets/fourier.png b/html/assets/fourier.png new file mode 100644 index 0000000..0e7f221 Binary files /dev/null and b/html/assets/fourier.png differ diff --git a/html/assets/godel.png b/html/assets/godel.png new file mode 100644 index 0000000..22c3f45 Binary files /dev/null and b/html/assets/godel.png differ diff --git a/html/assets/gus.jpeg b/html/assets/gus.jpeg new file mode 100644 index 0000000..c75d559 Binary files /dev/null and b/html/assets/gus.jpeg differ diff --git a/html/assets/julia.png b/html/assets/julia.png new file mode 100644 index 0000000..1d259b5 Binary files /dev/null and b/html/assets/julia.png differ diff --git a/html/assets/lambda.png b/html/assets/lambda.png new file mode 100644 index 0000000..4f07e8c Binary files /dev/null and b/html/assets/lambda.png differ diff --git a/html/assets/penguin.gif b/html/assets/penguin.gif new file mode 100644 index 0000000..48bd0e2 Binary files /dev/null and b/html/assets/penguin.gif differ diff --git a/html/assets/petting_basil.mp4 b/html/assets/petting_basil.mp4 new file mode 100644 index 0000000..0ed34ba Binary files /dev/null and b/html/assets/petting_basil.mp4 differ diff --git a/html/assets/scooters_on_ceiling.jpeg b/html/assets/scooters_on_ceiling.jpeg new file mode 100644 index 0000000..2b1e856 Binary files /dev/null and b/html/assets/scooters_on_ceiling.jpeg differ diff --git a/html/assets/tabloid.png b/html/assets/tabloid.png new file mode 100644 index 0000000..5fd489a Binary files /dev/null and b/html/assets/tabloid.png differ diff --git a/html/assets/turing.png b/html/assets/turing.png new file mode 100644 index 0000000..51ae5a5 Binary files /dev/null and b/html/assets/turing.png differ diff --git a/html/index.html b/html/index.html new file mode 100644 index 0000000..6199372 --- /dev/null +++ b/html/index.html @@ -0,0 +1,216 @@ + + + + + + +liz.coffee + + + + +
+
+
+
+
+
+
+
+ +
arXiv:2607.0404v3  [cs.DOG]  5 Jul 2026
+ +
+
Elizabeth / Alexander Hunt (she/he)
+
Department of Penguins
+
me@liz.coffee
+
+
July 4, 2026
+ + + +
+
Abstract
+

We introduce the author and establish some of her fundamental properties. If it's a proof you're looking for, well, the sourdough did that.

+

Fig. 1 - The bread, proven. Also, baked.
+
+ +

1. Introduction

+

+ The author is a programmer at a Big Ol' Tech™ company in Seattle. She was previously at USU studying CS and Math, thankfully escaping from BYU-I at 16. +

+

She enjoys recreational programming and studying outside of work; some of her interests are in functional programming, compilers/PLs, game programming, overengineering her homelab, and low level fun. She hacks on some LISP, drives Emacs, and is generally fond of free software (from 2021 to 2023 she was the president of the Free Software and Linux Club).

+

+ When not computing, she runs, bikes, watches anime, rides trains, bakes bread, daydreams about going back to school. On rare occassions when nostalgic, she is heard practicing violin. She is overly fascinated by penguins and visits them when possible. +

+

+ The author is also the advisor to PhD (PHuckin-cuteass Dog) candidate Gus, whose current work involves proving something about squirrels. +

+


Fig. 2 - PhD Candidate.
+

+ + +
+ + +
+
+ + + + + diff --git a/html/toys/euler-golf/css/styles.css b/html/toys/euler-golf/css/styles.css new file mode 100644 index 0000000..219012d --- /dev/null +++ b/html/toys/euler-golf/css/styles.css @@ -0,0 +1,81 @@ +body { + margin: 0; + padding: 0; + overflow: scroll; + font-family: Lucida Console, Lucida Sans Typewriter, monaco, + Bitstream Vera Sans Mono, monospace; + width: 100vw; + height: 100vh; + background: rgb(238, 174, 202); + background: radial-gradient( + circle, + rgba(238, 174, 202, 1) 0%, + rgba(148, 187, 233, 1) 100% + ); +} + +.canvas { + padding: 0; + margin: auto; + display: block; + border: 1px solid black; + + width: 100vw; + height: 100vw; +} + +button { + border-radius: 5px; + padding: 5px; + cursor: pointer; + margin-left: 5px; +} + +.controls { + cursor: pointer; + padding: 12px; + position: fixed; + bottom: 0; + right: 0; + background-color: rgba(255, 255, 255, 0.8); + border: 1px solid white; + border-radius: 8px; + margin-right: 6px; + margin-bottom: 6px; +} + +.buttons { + display: flex; + justify-content: space-around; + align-items: center; +} + +.modal { + display: flex; + position: absolute; + left: 50%; + top: 50%; + transform: translate(-50%, -50%); + + width: 80vw; + max-width: 500px; + min-height: 200px; + + padding: 12px; + + background-color: rgba(255, 255, 255, 0.8); + border: 1px solid black; + border-radius: 15px; +} + +.modal-body { + display: flex; + justify-content: center; +} + +.slider { + display: flex; + justify-content: center; + align-items: center; + margin-top: 3px; +} diff --git a/html/toys/euler-golf/index.html b/html/toys/euler-golf/index.html new file mode 100644 index 0000000..8164823 --- /dev/null +++ b/html/toys/euler-golf/index.html @@ -0,0 +1,80 @@ + + + + Euler Golf 2 + + + + + + +
+ + ↑↑ +
+ + + + + + + + + + + + + diff --git a/html/toys/euler-golf/js/controls.js b/html/toys/euler-golf/js/controls.js new file mode 100644 index 0000000..7f606cc --- /dev/null +++ b/html/toys/euler-golf/js/controls.js @@ -0,0 +1,33 @@ +document + .getElementById("controls-container") + .addEventListener("mouseover", () => { + document.getElementById("controls").style.display = "block"; + document.getElementById("expand-show").style.display = "none"; + }); + +document + .getElementById("controls-container") + .addEventListener("mouseout", () => { + document.getElementById("controls").style.display = "none"; + document.getElementById("expand-show").style.display = "inline"; + }); + +document.getElementById("reset").addEventListener("click", () => { + state = reset_state(state); + + state.target = rand_target(state.rows, state.cols); +}); + +document.getElementById("solve").addEventListener("click", () => { + if (!cx.eq(state.path.at(-2), new cx(0, 0))) state = reset_state(state); + + state.solution = sol(state.target); +}); + +document + .getElementById("directions") + .addEventListener("click", () => directions_modal.show()); + +document.getElementById("gap").addEventListener("input", function () { + state.changes.gap = Number(this.value); +}); diff --git a/html/toys/euler-golf/js/cx.js b/html/toys/euler-golf/js/cx.js new file mode 100644 index 0000000..371415d --- /dev/null +++ b/html/toys/euler-golf/js/cx.js @@ -0,0 +1,308 @@ +// http://www.russellcottrell.com/fractalsEtc/cx.js + +class cx { + static degrees(d) { + cx._RD = d ? Math.PI / 180 : 1; + } + // Math.PI/180 for degrees, 1 for radians + // applies to i/o (constructor, get/set arg, and toString etc.) + + constructor(x, y, polar) { + if (!polar) { + this.re = x; + this.im = y; + } else { + y *= cx._RD; // may be radians or degrees + this.re = x * Math.cos(y); + this.im = x * Math.sin(y); + } + } + + get abs() { + return Math.sqrt(this.re * this.re + this.im * this.im); + } + + set abs(r) { + var theta = this._arg; + this.re = r * Math.cos(theta); + this.im = r * Math.sin(theta); + } + + get arg() { + // returns radians or degrees, non-negative + return ( + ((Math.atan2(this.im, this.re) + 2 * Math.PI) % (2 * Math.PI)) / cx._RD + ); + } + + set arg(theta) { + // may be radians or degrees + var r = this.abs; + this.re = r * Math.cos(theta * cx._RD); + this.im = r * Math.sin(theta * cx._RD); + } + + get _arg() { + // internal; returns radians + return Math.atan2(this.im, this.re); + } + + static get i() { + return new cx(0, 1); + } + + static set i(x) { + throw new Error("i is read-only"); + } + + toString(polar) { + if (!polar) + return ( + this.re.toString() + + (this.im >= 0 ? " + " : " - ") + + Math.abs(this.im).toString() + + "i" + ); + else return this.abs.toString() + " cis " + this.arg.toString(); + } + + toPrecision(n, polar) { + if (!polar) + return ( + this.re.toPrecision(n) + + (this.im >= 0 ? " + " : " - ") + + Math.abs(this.im).toPrecision(n) + + "i" + ); + else return this.abs.toPrecision(n) + " cis " + this.arg.toPrecision(n); + } + + toPrecis(n, polar) { + // trims trailing zeros + if (!polar) + return ( + parseFloat(this.re.toPrecision(n)).toString() + + (this.im >= 0 ? " + " : " - ") + + parseFloat(Math.abs(this.im).toPrecision(n)).toString() + + "i" + ); + else + return ( + parseFloat(this.abs.toPrecision(n)).toString() + + " cis " + + parseFloat(this.arg.toPrecision(n)).toString() + ); + } + + toFixed(n, polar) { + if (!polar) + return ( + this.re.toFixed(n) + + (this.im >= 0 ? " + " : " - ") + + Math.abs(this.im).toFixed(n) + + "i" + ); + else return this.abs.toFixed(n) + " cis " + this.arg.toFixed(n); + } + + toExponential(n, polar) { + if (!polar) + return ( + this.re.toExponential(n) + + (this.im >= 0 ? " + " : " - ") + + Math.abs(this.im).toExponential(n) + + "i" + ); + else return this.abs.toExponential(n) + " cis " + this.arg.toExponential(n); + } + + static getReals(c, d) { + // when c or d may be simple or complex + var x, y, u, v; + if (c instanceof cx) { + x = c.re; + y = c.im; + } else { + x = c; + y = 0; + } + if (d instanceof cx) { + u = d.re; + v = d.im; + } else { + u = d; + v = 0; + } + return [x, y, u, v]; + } + + static conj(c) { + return new cx(c.re, -c.im); + } + + static neg(c) { + return new cx(-c.re, -c.im); + } + + static add(c, d) { + var a = cx.getReals(c, d); + var x = a[0]; + var y = a[1]; + var u = a[2]; + var v = a[3]; + return new cx(x + u, y + v); + } + + static sub(c, d) { + var a = cx.getReals(c, d); + var x = a[0]; + var y = a[1]; + var u = a[2]; + var v = a[3]; + return new cx(x - u, y - v); + } + + static mult(c, d) { + var a = cx.getReals(c, d); + var x = a[0]; + var y = a[1]; + var u = a[2]; + var v = a[3]; + return new cx(x * u - y * v, x * v + y * u); + } + + static div(c, d) { + var a = cx.getReals(c, d); + var x = a[0]; + var y = a[1]; + var u = a[2]; + var v = a[3]; + return new cx( + (x * u + y * v) / (u * u + v * v), + (y * u - x * v) / (u * u + v * v) + ); + } + + static pow(c, int) { + if (Number.isInteger(int) && int >= 0) { + var r = Math.pow(c.abs, int); + var theta = int * c._arg; + return new cx(r * Math.cos(theta), r * Math.sin(theta)); + } else return NaN; + } + + static root(c, int, k) { + if (!k) k = 0; + if ( + Number.isInteger(int) && + int >= 2 && + Number.isInteger(k) && + k >= 0 && + k < int + ) { + var r = Math.pow(c.abs, 1 / int); + var theta = (c._arg + 2 * k * Math.PI) / int; + return new cx(r * Math.cos(theta), r * Math.sin(theta)); + } else return NaN; + } + + static log(c) { + return new cx(Math.log(c.abs), c._arg); + } + + static exp(c) { + var r = Math.exp(c.re); + var theta = c.im; + return new cx(r * Math.cos(theta), r * Math.sin(theta)); + } + + static sin(c) { + var a = c.re; + var b = c.im; + return new cx(Math.sin(a) * Math.cosh(b), Math.cos(a) * Math.sinh(b)); + } + + static cos(c) { + var a = c.re; + var b = c.im; + return new cx(Math.cos(a) * Math.cosh(b), -Math.sin(a) * Math.sinh(b)); + } + + static tan(c) { + return cx.div(cx.sin(c), cx.cos(c)); + } + + static asin(c, k) { + if (!k) k = 0; + var ic = cx.mult(cx.i, c); + var c2 = cx.pow(c, 2); + return cx.mult( + cx.neg(cx.i), + cx.log(cx.add(ic, cx.root(cx.sub(1, c2), 2, k))) + ); + } + + static acos(c, k) { + if (!k) k = 0; + var c2 = cx.pow(c, 2); + return cx.mult( + cx.neg(cx.i), + cx.log(cx.add(c, cx.mult(cx.i, cx.root(cx.sub(1, c2), 2, k)))) + ); + } + + static atan(c) { + return cx.mult( + cx.div(cx.i, 2), + cx.log(cx.div(cx.add(cx.i, c), cx.sub(cx.i, c))) + ); + } + + static sinh(c) { + var a = c.re; + var b = c.im; + return new cx(Math.sinh(a) * Math.cos(b), Math.cosh(a) * Math.sin(b)); + } + + static cosh(c) { + var a = c.re; + var b = c.im; + return new cx(Math.cosh(a) * Math.cos(b), Math.sinh(a) * Math.sin(b)); + } + + static tanh(c) { + return cx.div(cx.sinh(c), cx.cosh(c)); + } + + static asinh(c, k) { + if (!k) k = 0; + var c2 = cx.pow(c, 2); + return cx.log(cx.add(c, cx.root(cx.add(c2, 1), 2, k))); + } + + static acosh(c, k) { + if (!k) k = 0; + var c2 = cx.pow(c, 2); + return cx.log(cx.add(c, cx.root(cx.sub(c2, 1), 2, k))); + } + + static atanh(c) { + return cx.mult(cx.div(1, 2), cx.log(cx.div(cx.add(1, c), cx.sub(1, c)))); + } + + static copy(c) { + return new cx(c.re, c.im); + } + + static eq(c, d, epsilon) { + if (!epsilon) { + if (c.re == d.re && c.im == d.im) return true; + } else { + if (Math.abs(c.re - d.re) < epsilon && Math.abs(c.im - d.im) < epsilon) + return true; + } + return false; + } +} + +cx.degrees(true); // need to call this diff --git a/html/toys/euler-golf/js/game.js b/html/toys/euler-golf/js/game.js new file mode 100644 index 0000000..5e123a0 --- /dev/null +++ b/html/toys/euler-golf/js/game.js @@ -0,0 +1,275 @@ +const DEFAULTS = { + max_rows: 80, + max_cols: 80, + min_gap: 30, + angle_multiplier: 10e-4, +}; + +const CANVAS = document.getElementById("canvas"); + +let state = { + grid_padding: 30, + gap: DEFAULTS.min_gap, + canvas: CANVAS, + ctx: CANVAS.getContext("2d"), + last_render: 0, + keys: {}, + changes: {}, +}; + +// Rendering +CanvasRenderingContext2D.prototype.circle = function (x, y, r, color) { + this.beginPath(); + this.arc(x, y, r, 0, Math.PI * 2); + this.fillStyle = color; + this.fill(); + this.closePath(); +}; + +CanvasRenderingContext2D.prototype.line = function ( + { x_pos: x1, y_pos: y1 }, + { x_pos: x2, y_pos: y2 }, + width, + color, + cap = "round" +) { + this.lineWidth = width; + this.strokeStyle = color; + this.lineCap = cap; + + this.beginPath(); + this.moveTo(x1, y1); + this.lineTo(x2, y2); + this.stroke(); + this.closePath(); +}; + +CanvasRenderingContext2D.prototype.draw_cartesian_path = function ( + grid_spec, + cartesian_path, + width = 2, + color = "#fff" +) { + const path = cartesian_path.map((coord) => grid_to_canvas(coord, grid_spec)); + path.slice(1).forEach((coord, i) => { + this.line(path[i], coord, width, color); + }); +}; + +CanvasRenderingContext2D.prototype.do_grid = function ( + rows, + cols, + draw_at_grid_pos = (ctx, x, y) => ctx.circle(x, y, 10, "#44ff44") +) { + for (let y = 0; y < rows; y++) { + for (let x = 0; x < cols; x++) { + draw_at_grid_pos(this, x, y); + } + } +}; + +CanvasRenderingContext2D.prototype.cartesian_grid = function ( + rows, + cols, + grid_spec, + circle_spec_at_coords = (_x, _y) => ({ radius: 5, color: "#000" }) +) { + this.do_grid(rows, cols, (ctx, x, y) => { + const { x_pos, y_pos } = grid_to_canvas({ x, y }, grid_spec); + const { radius, color } = circle_spec_at_coords(x, y); + + ctx.circle(x_pos, y_pos, radius, color); + }); +}; + +// Utilities +const move = (prev, curr, c) => cx.add(prev, cx.mult(c, cx.sub(curr, prev))); + +const rand_between = (min, max) => + Math.floor(Math.random() * (max - min + 1)) + min; + +const rand_target = (rows, cols) => { + const r = Math.floor((rows - 1) / 2); + const c = Math.floor((cols - 1) / 2); + const res = new cx(rand_between(-c, c), rand_between(-r, r)); + if (!sol(res)) return rand_target(rows, cols); + + return res; +}; + +const calculate_grid_spec = ({ rows, cols, width, height, grid_padding }) => { + const dx = (width - 2 * grid_padding) / cols; + const dy = (height - 2 * grid_padding) / rows; + + return { + dx, + dy, + start_x: grid_padding + dx / 2, + start_y: grid_padding + dy / 2, + }; +}; + +const grid_to_canvas = ({ x, y }, { dx, dy, start_x, start_y }) => ({ + x_pos: x * dx + start_x, + y_pos: y * dy + start_y, +}); + +const complex_to_grid = (c, rows, cols) => { + const { re, im } = c; + return { + x: re + Math.floor(cols / 2), + y: Math.floor(rows / 2) - im, + }; +}; + +// Game loop + +const maybe_add_state_angle_move = ({ angle } = state) => { + if (angle.im <= -1 || angle.im >= 1) { + angle.im = angle.im <= -1 ? -1 : 1; + state.path.push(move(state.path.at(-2), state.path.at(-1), angle)); + state.angle = new cx(0, 0); + } + return state; +}; + +const handle_input = (state, dt) => { + if (state.keys.ArrowLeft) { + state.angle.im += DEFAULTS.angle_multiplier * dt; + } else if (state.keys.ArrowRight) { + state.angle.im -= DEFAULTS.angle_multiplier * dt; + } + state = maybe_add_state_angle_move(state); +}; + +const render = ({ width, height, ctx, rows, cols, target, gap } = state) => { + ctx.clearRect(0, 0, width, height); + ctx.fillStyle = "rgba(0, 0, 0, 0)"; + ctx.fillRect(0, 0, width, height); + + const grid_spec = calculate_grid_spec(state); + + const curr = state.path.at(-1); + const prev = state.path.at(-2); + + const v_diff = cx.sub(curr, prev); + const theta = (state.angle.im * Math.PI) / 2; + + const angle_re = Math.cos(theta) * v_diff.re - Math.sin(theta) * v_diff.im; + const angle_im = Math.sin(theta) * v_diff.re + Math.cos(theta) * v_diff.im; + + ctx.draw_cartesian_path(grid_spec, [ + ...state.path.map((c) => complex_to_grid(c, rows, cols)), + complex_to_grid(cx.add(new cx(angle_re, angle_im), prev), rows, cols), + ]); + + if (!(state.angle.im == state.angle.re && state.angle.re == 0)) { + // Draw path to next player's target + const [a, b] = [ + curr, + move(prev, curr, new cx(0, state.angle.im < 0 ? -1 : 1)), + ].map((c) => grid_to_canvas(complex_to_grid(c, rows, cols), grid_spec)); + + ctx.line(a, b, 6, "rgba(127, 127, 127, 0.3)"); + } + + const grid_target = complex_to_grid(target, rows, cols); + ctx.cartesian_grid(rows, cols, grid_spec, (x, y) => { + if (x == Math.floor(cols / 2) && y == Math.floor(rows / 2)) { + return { + radius: 7, + color: "#2f9c94", + }; + } else if (x == grid_target.x && y == grid_target.y) { + return { + radius: 8, + color: "#fff", + }; + } else { + return { + radius: 3, + color: `rgb(${255 * (x / cols)}, 100, 100)`, // todo: animate with last_render + }; + } + }); + + // Render gap value in slider + document.getElementById("gap").value = gap; +}; + +const loop = (now) => { + const dt = now - state.last_render; + state.changes.last_render = now; + + if (Object.keys(state.changes).length > 0) { + state = { ...state, ...state.changes }; + + if (state.changes.width || state.changes.height || state.changes.gap) { + state.rows = Math.floor(state.height / state.gap); + state.cols = Math.floor(state.width / state.gap); + } + + state.changes = {}; + } + + if (!state.target) state.target = rand_target(state.rows, state.cols); + + if (!state.solution) { + handle_input(state, dt); + } else { + if (!state?.solution.length) { + delete state.solution; + } else { + state.angle.im += + (state.solution[0] === "-" ? 1 : -1) * DEFAULTS.angle_multiplier * dt; + + state = maybe_add_state_angle_move(state); + + if (cx.eq(state.angle, new cx(0, 0))) state.solution.shift(); + } + } + render(state); + requestAnimationFrame(loop); +}; + +const reset_state = ({ rows, cols } = state) => ({ + ...state, + solution: null, + path: [new cx(0, 0), new cx(1, 0)], + angle: new cx(0, 0), +}); + +// DOM +const directions_modal = new Modal({ + el: document.getElementById("directions-modal"), +}); + +const on_resize = () => { + CANVAS.width = document.body.clientWidth; + CANVAS.height = document.body.clientHeight; + state.changes.width = CANVAS.width; + state.changes.height = CANVAS.height; +}; + +const on_keyup = (e) => { + delete state.keys[e.key]; +}; + +const on_keydown = (e) => { + state.keys[e.key] = true; +}; + +window.addEventListener("resize", on_resize); +window.addEventListener("keydown", on_keydown); +window.addEventListener("keyup", on_keyup); + +// main +on_resize(); +state = reset_state(state); + +if (!sessionStorage.getItem("seen-instructions")) { + directions_modal.show(); + sessionStorage.setItem("seen-instructions", true); +} + +requestAnimationFrame(loop); diff --git a/html/toys/euler-golf/js/json-ds.js b/html/toys/euler-golf/js/json-ds.js new file mode 100644 index 0000000..dc7e88e --- /dev/null +++ b/html/toys/euler-golf/js/json-ds.js @@ -0,0 +1,19 @@ +class JSONSet { + items = new Set(); + + constructor(initial) { + if (Array.isArray(initial)) { + initial.map((x) => this.apply_set_function("add", x)); + } else { + this.apply_set_function("add", initial); + } + + ["add", "has", "remove"].forEach( + (f_name) => (this[f_name] = (x) => this.apply_set_function(f_name, x)) + ); + } + + apply_set_function(f_name, x) { + return this.items[f_name](JSON.stringify(x)); + } +} diff --git a/html/toys/euler-golf/js/modal-vanilla.min.js b/html/toys/euler-golf/js/modal-vanilla.min.js new file mode 100644 index 0000000..0d314c7 --- /dev/null +++ b/html/toys/euler-golf/js/modal-vanilla.min.js @@ -0,0 +1 @@ +var Modal=function(e){function t(i){if(n[i])return n[i].exports;var o=n[i]={i:i,l:!1,exports:{}};return e[i].call(o.exports,o,o.exports,t),o.l=!0,o.exports}var n={};return t.m=e,t.c=n,t.d=function(e,n,i){t.o(e,n)||Object.defineProperty(e,n,{configurable:!1,enumerable:!0,get:i})},t.n=function(e){var n=e&&e.__esModule?function(){return e.default}:function(){return e};return t.d(n,"a",n),n},t.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},t.p="",t(t.s=0)}([function(e,t,n){e.exports=n(1).default},function(e,t,n){"use strict";function i(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}function o(e,t){if(!e)throw new ReferenceError("this hasn't been initialised - super() hasn't been called");return!t||"object"!=typeof t&&"function"!=typeof t?e:t}function s(e,t){if("function"!=typeof t&&null!==t)throw new TypeError("Super expression must either be null or a function, not "+typeof t);e.prototype=Object.create(t&&t.prototype,{constructor:{value:e,enumerable:!1,writable:!0,configurable:!0}}),t&&(Object.setPrototypeOf?Object.setPrototypeOf(e,t):e.__proto__=t)}function r(e){for(var t in e)Array.isArray(e[t])?e[t].forEach(function(e){r(e)}):null!==e[t]&&"object"===p(e[t])&&Object.freeze(e[t]);return Object.freeze(e)}function a(){return(65536*(1+Math.random())|0).toString(16)+(65536*(1+Math.random())|0).toString(16)}function l(e,t,n){var i=e.data||{};if(void 0===n){if(e.data&&e.data[t])return e.data[t];var o=e.getAttribute("data-"+t);return void 0!==o?o:null}return i[t]=n,e.data=i,e}function d(e,t){return e.nodeName?e:(e=e.replace(/(\t|\n$)/g,""),_||(_=document.createElement("div")),_.innerHTML="",_.innerHTML=e,!0===t?_.childNodes:_.childNodes[0])}function c(){var e=void 0,t=void 0,n=void 0,i=document.createElement("div");return v(i.style,{visibility:"hidden",width:"100px"}),document.body.appendChild(i),n=i.offsetWidth,i.style.overflow="scroll",e=document.createElement("div"),e.style.width="100%",i.appendChild(e),t=n-e.offsetWidth,document.body.removeChild(i),t}function h(e){for(var t=[e];e.parentNode;)e=e.parentNode,t.push(e);return t}Object.defineProperty(t,"__esModule",{value:!0});var u=function(){function e(e,t){for(var n=0;n',dialog:'',content:'',header:'',headerClose:'',body:'',footer:'',backdrop:''},k=function(e){function t(){var e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{};i(this,t);var n=o(this,(t.__proto__||Object.getPrototypeOf(t)).call(this));if(n.id=a(),n.el=null,n._html={},n._events={},n._visible=!1,n._pointerInContent=!1,n._options=v({},t.options,e),n._templates=v({},t.templates,e.templates||{}),n._html.appendTo=document.querySelector(n._options.appendTo),n._scrollbarWidth=c(),null===n._options.buttons&&(n._options.buttons=t.buttons.dialog),n._options.el){var s=n._options.el;if("string"==typeof n._options.el&&!(s=document.querySelector(n._options.el)))throw new Error("Selector: DOM Element "+n._options.el+" not found.");l(s,"modal",n),n.el=s}else n._options.construct=!0;return n._options.construct?n._render():n._mapDom(),n}return s(t,e),u(t,null,[{key:"alert",value:function(e){var n=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{};return new t(v({},y,{title:e,content:!1,construct:!0,headerClose:!1,buttons:t.buttons.alert},n))}},{key:"confirm",value:function(e){var n=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{};return new t(v({},y,{title:e,content:!1,construct:!0,headerClose:!1,buttons:t.buttons.confirm},n))}},{key:"templates",set:function(e){this._baseTemplates=e},get:function(){return v({},g,t._baseTemplates||{})}},{key:"buttons",set:function(e){this._baseButtons=e},get:function(){return v({},b,t._baseButtons||{})}},{key:"options",set:function(e){this._baseOptions=e},get:function(){return v({},y,t._baseOptions||{})}},{key:"version",get:function(){return"0.12.0"}}]),u(t,[{key:"_render",value:function(){var e=this._html,t=this._options,n=this._templates,i=!!t.animate&&t.animateClass;return e.container=d(n.container),e.dialog=d(n.dialog),e.content=d(n.content),e.header=d(n.header),e.headerClose=d(n.headerClose),e.body=d(n.body),e.footer=d(n.footer),i&&e.container.classList.add(i),this._setHeader(),this._setContent(),this._setFooter(),this.el=e.container,e.dialog.appendChild(e.content),e.container.appendChild(e.dialog),this}},{key:"_mapDom",value:function(){var e=this._html,t=this._options;return this.el.classList.contains(t.animateClass)&&(t.animate=!0),e.container=this.el,e.dialog=this.el.querySelector(".modal-dialog"),e.content=this.el.querySelector(".modal-content"),e.header=this.el.querySelector(".modal-header"),e.headerClose=this.el.querySelector(".modal-header .close"),e.body=this.el.querySelector(".modal-body"),e.footer=this.el.querySelector(".modal-footer"),this._setHeader(),this._setContent(),this._setFooter(),this}},{key:"_setHeader",value:function(){var e=this._html,t=this._options;t.header&&e.header&&(t.title.nodeName?e.header.innerHTML=t.title.outerHTML:"string"==typeof t.title&&(e.header.innerHTML='"),null===this.el&&e.headerClose&&t.headerClose&&e.header.appendChild(e.headerClose),t.construct&&e.content.appendChild(e.header))}},{key:"_setContent",value:function(){var e=this._html,t=this._options;t.content&&e.body&&("string"==typeof t.content?e.body.innerHTML=t.content:e.body.innerHTML=t.content.outerHTML,t.construct&&e.content.appendChild(e.body))}},{key:"_setFooter",value:function(){var e=this._html,t=this._options;t.footer&&e.footer&&(t.footer.nodeName?e.footer.ineerHTML=t.footer.outerHTML:"string"==typeof t.footer?e.footer.innerHTML=t.footer:e.footer.children.length||t.buttons.forEach(function(t){var n=document.createElement("button");l(n,"button",t),n.innerHTML=t.text,n.setAttribute("type","button");for(var i in t.attr)n.setAttribute(i,t.attr[i]);e.footer.appendChild(n)}),t.construct&&e.content.appendChild(e.footer))}},{key:"_setEvents",value:function(){var e=(this._options,this._html);this._events.keydownHandler=this._handleKeydownEvent.bind(this),document.body.addEventListener("keydown",this._events.keydownHandler),this._events.mousedownHandler=this._handleMousedownEvent.bind(this),e.container.addEventListener("mousedown",this._events.mousedownHandler),this._events.clickHandler=this._handleClickEvent.bind(this),e.container.addEventListener("click",this._events.clickHandler),this._events.resizeHandler=this._handleResizeEvent.bind(this),window.addEventListener("resize",this._events.resizeHandler)}},{key:"_handleMousedownEvent",value:function(e){var t=this;this._pointerInContent=!1,h(e.target).every(function(e){return!e.classList||!e.classList.contains("modal-content")||(t._pointerInContent=!0,!1)})}},{key:"_handleClickEvent",value:function(e){var t=this;h(e.target).every(function(n){return!("HTML"===n.tagName||!0!==t._options.backdrop&&n.classList.contains("modal")||n.classList.contains("modal-content")||("modal"===n.getAttribute("data-dismiss")?(t.emit("dismiss",t,e,l(e.target,"button")),t.hide(),1):!t._pointerInContent&&n.classList.contains("modal")&&(t.emit("dismiss",t,e,null),t.hide(),1)))}),this._pointerInContent=!1}},{key:"_handleKeydownEvent",value:function(e){27===e.which&&this._options.keyboard&&(this.emit("dismiss",this,e,null),this.hide())}},{key:"_handleResizeEvent",value:function(e){this._resize()}},{key:"show",value:function(){var e=this,t=this._options,n=this._html;return this.emit("show",this),this._checkScrollbar(),this._setScrollbar(),document.body.classList.add("modal-open"),t.construct&&n.appendTo.appendChild(n.container),n.container.style.display="block",n.container.scrollTop=0,!1!==t.backdrop?(this.once("showBackdrop",function(){e._setEvents(),t.animate&&n.container.offsetWidth,n.container.classList.add(t.animateInClass),setTimeout(function(){e._visible=!0,e.emit("shown",e)},t.transition)}),this._backdrop()):(this._setEvents(),t.animate&&n.container.offsetWidth,n.container.classList.add(t.animateInClass),setTimeout(function(){e._visible=!0,e.emit("shown",e)},t.transition)),this._resize(),this}},{key:"toggle",value:function(){this._visible?this.hide():this.show()}},{key:"_resize",value:function(){var e=this._html.container.scrollHeight>document.documentElement.clientHeight;this._html.container.style.paddingLeft=!this.bodyIsOverflowing&&e?this._scrollbarWidth+"px":"",this._html.container.style.paddingRight=this.bodyIsOverflowing&&!e?this._scrollbarWidth+"px":""}},{key:"_backdrop",value:function(){var e=this,t=this._html,n=this._templates,i=this._options,o=!!i.animate&&i.animateClass;t.backdrop=d(n.backdrop),o&&t.backdrop.classList.add(o),t.appendTo.appendChild(t.backdrop),o&&t.backdrop.offsetWidth,t.backdrop.classList.add(i.animateInClass),setTimeout(function(){e.emit("showBackdrop",e)},this._options.backdropTransition)}},{key:"hide",value:function(){var e=this,t=this._html,n=this._options,i=t.container.classList;if(this.emit("hide",this),i.remove(n.animateInClass),n.backdrop){t.backdrop.classList.remove(n.animateInClass)}return this._removeEvents(),setTimeout(function(){document.body.classList.remove("modal-open"),document.body.style.paddingRight=e.originalBodyPad},n.backdropTransition),setTimeout(function(){n.backdrop&&t.backdrop.parentNode.removeChild(t.backdrop),t.container.style.display="none",n.construct&&t.container.parentNode.removeChild(t.container),e._visible=!1,e.emit("hidden",e)},n.transition),this}},{key:"_removeEvents",value:function(){this._events.keydownHandler&&document.body.removeEventListener("keydown",this._events.keydownHandler),this._html.container.removeEventListener("mousedown",this._events.mousedownHandler),this._html.container.removeEventListener("click",this._events.clickHandler),window.removeEventListener("resize",this._events.resizeHandler)}},{key:"_checkScrollbar",value:function(){this.bodyIsOverflowing=document.body.clientWidth0&&this._events[e].length>o&&(this._events[e].warned=!0,console.error("(node) warning: possible EventEmitter memory leak detected. %d listeners added. Use emitter.setMaxListeners() to increase limit.",this._events[e].length),"function"==typeof console.trace&&console.trace()),this},n.prototype.on=n.prototype.addListener,n.prototype.once=function(e,t){function n(){this.removeListener(e,n),o||(o=!0,t.apply(this,arguments))}if(!i(t))throw TypeError("listener must be a function");var o=!1;return n.listener=t,this.on(e,n),this},n.prototype.removeListener=function(e,t){var n,o,r,a;if(!i(t))throw TypeError("listener must be a function");if(!this._events||!this._events[e])return this;if(n=this._events[e],r=n.length,o=-1,n===t||i(n.listener)&&n.listener===t)delete this._events[e],this._events.removeListener&&this.emit("removeListener",e,t);else if(s(n)){for(a=r;a-- >0;)if(n[a]===t||n[a].listener&&n[a].listener===t){o=a;break}if(o<0)return this;1===n.length?(n.length=0,delete this._events[e]):n.splice(o,1),this._events.removeListener&&this.emit("removeListener",e,t)}return this},n.prototype.removeAllListeners=function(e){var t,n;if(!this._events)return this;if(!this._events.removeListener)return 0===arguments.length?this._events={}:this._events[e]&&delete this._events[e],this;if(0===arguments.length){for(t in this._events)"removeListener"!==t&&this.removeAllListeners(t);return this.removeAllListeners("removeListener"),this._events={},this}if(n=this._events[e],i(n))this.removeListener(e,n);else if(n)for(;n.length;)this.removeListener(e,n[n.length-1]);return delete this._events[e],this},n.prototype.listeners=function(e){return this._events&&this._events[e]?i(this._events[e])?[this._events[e]]:this._events[e].slice():[]},n.prototype.listenerCount=function(e){if(this._events){var t=this._events[e];if(i(t))return 1;if(t)return t.length}return 0},n.listenerCount=function(e,t){return e.listenerCount(t)}}]); diff --git a/html/toys/euler-golf/js/sol.js b/html/toys/euler-golf/js/sol.js new file mode 100644 index 0000000..b4e527f --- /dev/null +++ b/html/toys/euler-golf/js/sol.js @@ -0,0 +1,44 @@ +const DEPTH = 15; + +const DIRECTION = { + 0: new cx(0, 1), + 1: new cx(0, -1), +}; + +const construct_moves = (curr, prev) => + Object.keys(DIRECTION).map((x) => move(curr, prev, DIRECTION[x])); + +const backtrack = (local_index, depth) => + local_index + .toString(2) + .padStart(depth, "0") + .split("") + .map((direction) => (Number(direction) ? "+" : "-")); + +const sol = (target, start_from = new cx(0, 0), start_to = new cx(1, 0)) => { + const next_moves = construct_moves(start_from, start_to); + const solved_in_first_move = next_moves.findIndex((move) => + cx.eq(move, target) + ); + if (solved_in_first_move != -1) return backtrack(solved_in_first_move, 1); + + let moves = [start_to, ...next_moves]; + let curr_depth = 2; + while (curr_depth < DEPTH) { + for (let i = 0; i < Math.pow(2, curr_depth); i++) { + const direction = DIRECTION[Number(i.toString(2).at(-1))]; + // Current element is at i >> 1 + the offset for the previous group (which is + // the sum of the geometric series 2**n until curr_depth - 1) + const current_i = (i >> 1) + (1 - Math.pow(2, curr_depth - 1)) / (1 - 2); + const previous_i = (i >> 2) + (1 - Math.pow(2, curr_depth - 2)) / (1 - 2); + + const new_move = move(moves[previous_i], moves[current_i], direction); + + moves.push(new_move); + if (cx.eq(new_move, target)) return backtrack(i, curr_depth); + } + curr_depth++; + } + + return null; +}; diff --git a/html/toys/fourier/index.html b/html/toys/fourier/index.html new file mode 100644 index 0000000..ccb8f48 --- /dev/null +++ b/html/toys/fourier/index.html @@ -0,0 +1,46 @@ + + + + Simponic's FT Visualizer + + + +
+
+
+ +
+
+ + + + + diff --git a/html/toys/fourier/js/script.js b/html/toys/fourier/js/script.js new file mode 100644 index 0000000..8d5af31 --- /dev/null +++ b/html/toys/fourier/js/script.js @@ -0,0 +1,294 @@ +const RENDER_TYPE = { + LATEX: 1, + FUNC: 2, +}; +const THRESHOLD = 1e-12; +const FONT = "Courier New"; +const FONT_HEIGHT_PX = 24; +const DX = 4; + +const canvas = document.getElementById("canvas"); +const ctx = canvas.getContext("2d"); +let state = {}; +const initializeState = () => { + const xLabels = [ + "Jan", + "Feb", + "Mar", + "Apr", + "May", + "Jun", + "Jul", + "Aug", + "Sept", + "Oct", + "Nov", + "Dec", + "Jan", + ]; + const yLabels = ["Great", "Good", "Meh", "Bad", "Horrible"]; + return { + width: canvas.parentElement.clientWidth, + height: canvas.parentElement.clientHeight, + xLabels, + yLabels, + yLabelPadding: 12, + heights: Array(xLabels.length * DX - 1).fill(0), + }; +}; + +const dft = (heights, render = RENDER_TYPE.LATEX, threshold = THRESHOLD) => { + const n = heights.length; + return Array(n) + .fill() + .map((x, w) => { + const rate = -2 * Math.PI * w; + const s = heights.reduce( + (a, x, i) => ({ + re: a.re + x * Math.cos((rate * i) / n), + im: a.im + x * Math.sin((rate * i) / n), + }), + { re: 0, im: 0 } + ); + + Object.entries(s).forEach( + ([key, value]) => + (s[key] = ((Math.abs(value) < threshold ? 0 : 1) * value) / n) + ); + + const amp = Math.sqrt(s.re * s.re + s.im * s.im); + const phase = Math.atan2(s.im, s.re); + + switch (render) { + case RENDER_TYPE.LATEX: + return `${amp}\\cos\\left(${w}\\frac{${ + 2 * DX + }\\pi}{${n}}x+${phase}\\right)`; + case RENDER_TYPE.FUNC: + return (t) => amp * Math.cos(w * t + phase); + } + }); +}; + +const resizeCanvas = ({ width, height }) => { + canvas.width = width; + canvas.style.width = width; + canvas.height = height; + canvas.style.height = height; +}; + +const loop = () => { + const stateChanges = Object.keys(state.diff); + if (stateChanges.length > 0) { + state = { ...state, ...state.diff }; + if ( + state.diff.width || + state.diff.height || + state.diff.xLabels || + state.diff.yLabels + ) { + resizeCanvas(state.diff); + ctx.font = `${FONT_HEIGHT_PX}px ${FONT}`; + state.maxYLabelWidth = state.yLabels.reduce( + (a, label) => Math.max(ctx.measureText(label).width, a), + -Infinity + ); + + state.gridBoxWidth = + state.width - state.maxYLabelWidth - state.yLabelPadding; + state.gridBoxHeight = state.height - 2.5 * FONT_HEIGHT_PX; // 2.5 to include bottom part of tall letters ("g", "y", etc.) + + state.topLeftGridPos = { + x: state.maxYLabelWidth + state.yLabelPadding, + y: FONT_HEIGHT_PX, + }; + + state.bottomRightGridPos = { + x: state.topLeftGridPos.x + state.gridBoxWidth, + y: state.topLeftGridPos.y + state.gridBoxHeight, + }; + } + if (state.diff.heights) drawDesmos(); + draw(state); + state.diff = {}; + } + + requestAnimationFrame(loop); +}; + +const drawLine = (pos1, pos2) => { + ctx.beginPath(); + ctx.moveTo(pos1.x, pos1.y); + ctx.lineTo(pos2.x, pos2.y); + ctx.stroke(); +}; + +const drawDividers = ( + xDividers, + yDividers, + topLeftGridPos, + bottomRightGridPos, + yLabelPadding +) => { + ctx.font = `${FONT_HEIGHT_PX}px ${FONT}`; + xDividers.forEach(({ label, position }) => { + ctx.fillText(label, position.x - ctx.measureText(label).width, position.y); + drawLine( + { ...position, y: topLeftGridPos.y }, + { ...position, y: bottomRightGridPos.y } + ); + }); + yDividers.forEach(({ label, position }) => { + ctx.fillText( + label, + topLeftGridPos.x - yLabelPadding - ctx.measureText(label).width, + position.y + ); + drawLine( + { ...position, x: topLeftGridPos.x }, + { ...position, x: bottomRightGridPos.x } + ); + }); +}; + +const draw = ({ + heights, + gridBoxWidth, + gridBoxHeight, + topLeftGridPos, + bottomRightGridPos, + maxYLabelWidth, + xLabels, + yLabels, + width, + height, +}) => { + ctx.clearRect(0, 0, width, height); + + const xDividers = xLabels.map((label, i) => ({ + label, + position: { + x: topLeftGridPos.x + (gridBoxWidth / (xLabels.length - 1)) * i, + y: bottomRightGridPos.y + FONT_HEIGHT_PX, + }, + })); + + const yDividers = yLabels.map((label, i) => ({ + label, + position: { + x: 0, + y: topLeftGridPos.y + (gridBoxHeight / (yLabels.length - 1)) * i, + }, + })); + + drawDividers(xDividers, yDividers, topLeftGridPos, bottomRightGridPos, 12); + + const dx = gridBoxWidth / (DX * (xLabels.length - 1)); + const prevStrokeStyle = ctx.strokeStyle; + ctx.strokeStyle = "red"; + for (let i = 0; i < heights.length; ++i) { + const x = dx * i + topLeftGridPos.x; + drawLine( + { x, y: (gridBoxHeight / 2) * (1 - heights[i]) + topLeftGridPos.y }, + { + x: x + dx, + y: (gridBoxHeight / 2) * (1 - heights[i + 1]) + topLeftGridPos.y, + } + ); + } + ctx.strokeStyle = prevStrokeStyle; +}; + +const calculator = Desmos.GraphingCalculator( + document.getElementById("calculator"), + { + expressionsCollapsed: true, + autosize: true, + } +); +calculator.setMathBounds({ + left: -0.8, + right: 12, + bottom: -3, + top: 3, +}); + +const drawDesmos = () => { + const equations = dft(state.heights); + + calculator.setExpression({ + id: `graph-total`, + latex: equations.map((_x, i) => `y_{${i}}`).join(" + "), + }); + equations.forEach((x, i) => + calculator.setExpression({ + id: `graph${i}`, + latex: `y_{${i}}=${x}`, + hidden: true, + }) + ); +}; + +let isDown = false; +canvas.addEventListener( + "mousedown", + (e) => { + e.preventDefault(); + isDown = true; + }, + true +); + +canvas.addEventListener( + "mouseup", + (e) => { + e.preventDefault(); + isDown = false; + }, + true +); + +canvas.addEventListener( + "mousemove", + (e) => { + e.preventDefault(); + if (isDown) { + const rect = canvas.getBoundingClientRect(); + const [x, y] = [e.clientX - rect.left, e.clientY - rect.top]; + + const { + topLeftGridPos, + bottomRightGridPos, + gridBoxWidth, + gridBoxHeight, + heights, + xLabels, + } = state; + const delta = gridBoxWidth / (DX * (xLabels.length - 1)); + + const bin = Math.min( + Math.round(Math.max(x - topLeftGridPos.x, 0) / delta), + heights.length - 1 + ); + heights[bin] = Math.min( + Math.max(1 - (2 * (y - topLeftGridPos.y)) / gridBoxHeight, -1), + 1 + ); + state.diff.heights = heights; + } + }, + true +); + +window.addEventListener("resize", () => { + state.diff = { + ...state.diff, + width: canvas.parentElement.clientWidth, + height: canvas.parentElement.clientHeight, + }; +}); + +(() => { + state.diff = initializeState(); + window.requestAnimationFrame(loop); +})(); diff --git a/html/toys/godel/index.html b/html/toys/godel/index.html new file mode 100644 index 0000000..50a680a --- /dev/null +++ b/html/toys/godel/index.html @@ -0,0 +1,51 @@ + + + + + + + Gödel Numbering + + +
+
+

Gödel Numbering

+
+
+

L Source

+
+
+ + +
+ +
+
+ +
+
+

"Compiled" JS

+
+
+ +
+ +
+
+ +
+

Gödel Sequence

+
Compile to view the Gödel sequence.
+
+ +
+

+      
+
+
+ + + + + + diff --git a/html/toys/godel/js/compiler.js b/html/toys/godel/js/compiler.js new file mode 100644 index 0000000..d15c0f4 --- /dev/null +++ b/html/toys/godel/js/compiler.js @@ -0,0 +1,226 @@ +const INDENT_SIZE = 2; + +class CodeBuilder { + constructor(indentSize = INDENT_SIZE) { + this.indentSize = indentSize; + this.indentLevel = 0; + this.parts = []; + } + + addLine(line = "") { + const indent = " ".repeat(this.indentLevel * this.indentSize); + this.parts.push(line ? `${indent}${line}` : ""); + } + + open(line) { + if (line) { + this.addLine(line); + } + this.indentLevel += 1; + } + + close(line) { + this.indentLevel = Math.max(0, this.indentLevel - 1); + if (line) { + this.addLine(line); + } + } + + toString() { + return this.parts.join("\n"); + } +} + +const compileGoto = (gotoNode, builder) => { + builder.addLine(`this.followGoto("${gotoNode.label.symbol}");`); + builder.addLine("return;"); +}; + +const compileConditional = (conditionalNode, builder) => { + const variable = conditionalNode.variable.symbol; + builder.addLine(`if (this.get("${variable}") !== 0) {`); + builder.open(); + compileGoto(conditionalNode.goto, builder); + builder.close("}"); +}; + +const compileAssignment = (assignmentNode, builder) => { + const variable = assignmentNode.variable.symbol; + const expr = assignmentNode.expr || {}; + + if (expr.opr === "+") { + builder.addLine(`this.addOne("${variable}");`); + } else if (expr.opr === "-") { + builder.addLine(`this.subtractOne("${variable}");`); + } else { + builder.addLine("// noop"); + } +}; + +const compileInstruction = (instruction, builder) => { + if (instruction.goto) { + compileGoto(instruction.goto, builder); + return; + } + + if (instruction.conditional) { + compileConditional(instruction.conditional, builder); + } else if (instruction.assignment) { + compileAssignment(instruction.assignment, builder); + } + + builder.addLine("this.instructionPointer++;"); +}; + +const emitMethod = (builder, signature, bodyFn) => { + builder.addLine(`${signature} {`); + builder.open(); + bodyFn(); + builder.close("}"); + builder.addLine(""); +}; + +const emitConstructor = (builder, ast, godelSequence, methodCatalog) => { + emitMethod(builder, "constructor()", () => { + builder.addLine("this.variables = new Map();"); + builder.addLine("this.labelInstructions = new Map();"); + builder.addLine("this.instructions = new Map();"); + builder.addLine("this.instructions.set(0, () => this.main());"); + builder.addLine("this.instructionPointer = 0;"); + builder.addLine('this.variables.set("Y", 0);'); + builder.addLine(`this.finalInstruction = ${ast.instructions.length + 1};`); + builder.addLine('this.labelInstructions.set("E1", this.finalInstruction);'); + builder.addLine(""); + builder.addLine("// instruction bindings"); + + ast.instructions.forEach((entry, index) => { + const instructionNode = entry.instruction; + const instructionIdx = index + 1; + godelSequence.push(entry.godel); + + if (instructionNode.label) { + const labelName = instructionNode.label.symbol; + builder.addLine( + `this.instructions.set(${instructionIdx}, () => this.${labelName}());` + ); + builder.addLine( + `this.labelInstructions.set("${labelName}", ${instructionIdx});` + ); + methodCatalog.push({ + name: labelName, + index: instructionIdx, + node: instructionNode.instruction + }); + } else { + const methodName = `instruction${instructionIdx}`; + builder.addLine( + `this.instructions.set(${instructionIdx}, () => this.${methodName}());` + ); + methodCatalog.push({ + name: methodName, + index: instructionIdx, + node: instructionNode + }); + } + }); + }); +}; + +const emitRuntimeHelpers = (builder, instructionCount) => { + emitMethod(builder, "get(variable)", () => { + builder.addLine("if (!this.variables.has(variable)) {"); + builder.open(); + builder.addLine("this.variables.set(variable, 0);"); + builder.close("}"); + builder.addLine("return this.variables.get(variable);"); + }); + + emitMethod(builder, "addOne(variable)", () => { + builder.addLine("const val = this.get(variable);"); + builder.addLine("this.variables.set(variable, val + 1);"); + }); + + emitMethod(builder, "subtractOne(variable)", () => { + builder.addLine("const val = this.get(variable);"); + builder.addLine("this.variables.set(variable, val - 1);"); + }); + + emitMethod(builder, "followGoto(label)", () => { + builder.addLine("this.instructionPointer = this.labelInstructions.get(label);"); + }); + + emitMethod(builder, "step()", () => { + builder.addLine("if (!this.isCompleted()) {"); + builder.open(); + builder.addLine("const procedure = this.instructions.get(this.instructionPointer);"); + builder.addLine("procedure();"); + builder.close("}"); + builder.addLine("return this.instructionPointer;"); + }); + + emitMethod(builder, "isCompleted()", () => { + builder.addLine("return this.instructionPointer === this.finalInstruction;"); + }); + + emitMethod(builder, "getResult()", () => { + builder.addLine('return this.variables.get("Y");'); + }); + + emitMethod(builder, "run(maxIter = 500_000)", () => { + builder.addLine("let iter = 0;"); + builder.addLine("while (!this.isCompleted() && ++iter < maxIter) {"); + builder.open(); + builder.addLine("this.step();"); + builder.close("}"); + builder.addLine("if (iter < maxIter) {"); + builder.open(); + builder.addLine("return this.getResult();"); + builder.close("}"); + builder.addLine( + 'throw new Error("Program exceeded iteration limit. Try optimizing your instructions or increasing the cap.");' + ); + }); + + emitMethod(builder, "main()", () => { + if (instructionCount > 0) { + builder.addLine("this.instructionPointer = 1;"); + } else { + builder.addLine("this.instructionPointer = this.finalInstruction;"); + } + }); +}; + +const emitInstructionMethods = (builder, catalog) => { + catalog.forEach((entry) => { + emitMethod(builder, `${entry.name}()`, () => { + builder.addLine(`this.instructionPointer = ${entry.index};`); + compileInstruction(entry.node, builder); + }); + }); +}; + +export const compileProgram = (ast) => { + const builder = new CodeBuilder(); + const godelSequence = []; + const methodCatalog = []; + + builder.addLine("class Program {"); + builder.open(); + emitConstructor(builder, ast, godelSequence, methodCatalog); + emitRuntimeHelpers(builder, ast.instructions.length); + emitInstructionMethods(builder, methodCatalog); + builder.close("}"); + builder.addLine(""); + builder.addLine("// bootstrap"); + builder.addLine("const program = new Program();"); + builder.addLine('// program.variables.set("X1", 2);'); + builder.addLine('// program.variables.set("X2", 3);'); + builder.addLine("program.run();"); + builder.addLine("console.log(program.variables);"); + builder.addLine("program.getResult();"); + + return { + js: builder.toString(), + godelSequence + }; +}; diff --git a/html/toys/godel/js/godel-worker.js b/html/toys/godel/js/godel-worker.js new file mode 100644 index 0000000..1ba9da4 --- /dev/null +++ b/html/toys/godel/js/godel-worker.js @@ -0,0 +1,42 @@ +const isPrime = (n) => { + if (n < 2) { + return false; + } + if (n === 2) { + return true; + } + if (n % 2 === 0) { + return false; + } + const limit = Math.floor(Math.sqrt(n)); + for (let i = 3; i <= limit; i += 2) { + if (n % i === 0) { + return false; + } + } + return true; +}; + +const primes = [2]; +const primeAt = (index) => { + while (primes.length < index) { + let candidate = primes[primes.length - 1] + 1; + while (!isPrime(candidate)) { + candidate += 1; + } + primes.push(candidate); + } + return primes[index - 1]; +}; + +const computeGodelNumber = (sequence) => { + return sequence.reduce((acc, exponent, idx) => { + const prime = BigInt(primeAt(idx + 1)); + return acc * prime ** BigInt(exponent); + }, BigInt(1)) - BigInt(1); +}; + +self.addEventListener("message", (event) => { + const result = computeGodelNumber(event.data); + self.postMessage(result.toString()); +}); diff --git a/html/toys/godel/js/main.js b/html/toys/godel/js/main.js new file mode 100644 index 0000000..b752206 --- /dev/null +++ b/html/toys/godel/js/main.js @@ -0,0 +1,5 @@ +import { GodelPlayground } from "./ui.js"; + +document.addEventListener("DOMContentLoaded", () => { + new GodelPlayground(); +}); diff --git a/html/toys/godel/js/parser.js b/html/toys/godel/js/parser.js new file mode 100644 index 0000000..a9436b6 --- /dev/null +++ b/html/toys/godel/js/parser.js @@ -0,0 +1,1059 @@ +const parser = /* + * Generated by PEG.js 0.10.0. + * + * http://pegjs.org/ + */ +(function() { + "use strict"; + + function peg$subclass(child, parent) { + function ctor() { this.constructor = child; } + ctor.prototype = parent.prototype; + child.prototype = new ctor(); + } + + function peg$SyntaxError(message, expected, found, location) { + this.message = message; + this.expected = expected; + this.found = found; + this.location = location; + this.name = "SyntaxError"; + + if (typeof Error.captureStackTrace === "function") { + Error.captureStackTrace(this, peg$SyntaxError); + } + } + + peg$subclass(peg$SyntaxError, Error); + + peg$SyntaxError.buildMessage = function(expected, found) { + var DESCRIBE_EXPECTATION_FNS = { + literal: function(expectation) { + return "\"" + literalEscape(expectation.text) + "\""; + }, + + "class": function(expectation) { + var escapedParts = "", + i; + + for (i = 0; i < expectation.parts.length; i++) { + escapedParts += expectation.parts[i] instanceof Array + ? classEscape(expectation.parts[i][0]) + "-" + classEscape(expectation.parts[i][1]) + : classEscape(expectation.parts[i]); + } + + return "[" + (expectation.inverted ? "^" : "") + escapedParts + "]"; + }, + + any: function(expectation) { + return "any character"; + }, + + end: function(expectation) { + return "end of input"; + }, + + other: function(expectation) { + return expectation.description; + } + }; + + function hex(ch) { + return ch.charCodeAt(0).toString(16).toUpperCase(); + } + + function literalEscape(s) { + return s + .replace(/\\/g, '\\\\') + .replace(/"/g, '\\"') + .replace(/\0/g, '\\0') + .replace(/\t/g, '\\t') + .replace(/\n/g, '\\n') + .replace(/\r/g, '\\r') + .replace(/[\x00-\x0F]/g, function(ch) { return '\\x0' + hex(ch); }) + .replace(/[\x10-\x1F\x7F-\x9F]/g, function(ch) { return '\\x' + hex(ch); }); + } + + function classEscape(s) { + return s + .replace(/\\/g, '\\\\') + .replace(/\]/g, '\\]') + .replace(/\^/g, '\\^') + .replace(/-/g, '\\-') + .replace(/\0/g, '\\0') + .replace(/\t/g, '\\t') + .replace(/\n/g, '\\n') + .replace(/\r/g, '\\r') + .replace(/[\x00-\x0F]/g, function(ch) { return '\\x0' + hex(ch); }) + .replace(/[\x10-\x1F\x7F-\x9F]/g, function(ch) { return '\\x' + hex(ch); }); + } + + function describeExpectation(expectation) { + return DESCRIBE_EXPECTATION_FNS[expectation.type](expectation); + } + + function describeExpected(expected) { + var descriptions = new Array(expected.length), + i, j; + + for (i = 0; i < expected.length; i++) { + descriptions[i] = describeExpectation(expected[i]); + } + + descriptions.sort(); + + if (descriptions.length > 0) { + for (i = 1, j = 1; i < descriptions.length; i++) { + if (descriptions[i - 1] !== descriptions[i]) { + descriptions[j] = descriptions[i]; + j++; + } + } + descriptions.length = j; + } + + switch (descriptions.length) { + case 1: + return descriptions[0]; + + case 2: + return descriptions[0] + " or " + descriptions[1]; + + default: + return descriptions.slice(0, -1).join(", ") + + ", or " + + descriptions[descriptions.length - 1]; + } + } + + function describeFound(found) { + return found ? "\"" + literalEscape(found) + "\"" : "end of input"; + } + + return "Expected " + describeExpected(expected) + " but " + describeFound(found) + " found."; + }; + + function peg$parse(input, options) { + options = options !== void 0 ? options : {}; + + var peg$FAILED = {}, + + peg$startRuleFunctions = { Program: peg$parseProgram }, + peg$startRuleFunction = peg$parseProgram, + + peg$c0 = /^[\n]/, + peg$c1 = peg$classExpectation(["\n"], false, false), + peg$c2 = function(lines) { + return { instructions: lines.filter((line) => typeof line !== "string" || line.trim() != "") }; + }, + peg$c3 = function(instruction) { + let x = 0; + let y = 0; + if (instruction.label) { + x = instruction.label.godel; + y = instruction.instruction.godel; + } else { + y = instruction.godel; + } + return { instruction, godel: ((2 ** x) * ((2 * y) + 1) - 1) }; + }, + peg$c4 = function(label, instruction) { + return { label, instruction }; + }, + peg$c5 = "[", + peg$c6 = peg$literalExpectation("[", false), + peg$c7 = "]", + peg$c8 = peg$literalExpectation("]", false), + peg$c9 = function(label) { + return label; + }, + peg$c10 = function(conditional) { return { conditional, godel: conditional.godel }; }, + peg$c11 = function(assignment) { return { assignment, godel: assignment.godel }; }, + peg$c12 = function(goto) { return { goto, godel: goto.godel }; }, + peg$c13 = function(label) { + return { label, godel: label.godel + 2 }; + }, + peg$c14 = "IF", + peg$c15 = peg$literalExpectation("IF", false), + peg$c16 = "!=", + peg$c17 = peg$literalExpectation("!=", false), + peg$c18 = "0", + peg$c19 = peg$literalExpectation("0", false), + peg$c20 = function(variable, goto) { + const y = variable.godel - 1; + const x = goto.godel; + return { variable, goto, godel: ((2 ** x) * ((2 * y) + 1) - 1) }; + }, + peg$c21 = "<-", + peg$c22 = peg$literalExpectation("<-", false), + peg$c23 = function(variable, expr) { + if (expr.left.symbol != variable.symbol) { + error("left hand variable must match right hand"); + } + const x = expr.instructionNumber; + const y = variable.godel - 1; + return { variable, expr, godel: ((2 ** x) * ((2 * y) + 1) - 1) }; + }, + peg$c24 = "1", + peg$c25 = peg$literalExpectation("1", false), + peg$c26 = function(left, opr) { + const instructionNumber = { "+" : 1, "-" : 2 }[opr]; + return { left, opr, instructionNumber }; + }, + peg$c27 = function(left) { + return { left, instructionNumber: 0 }; + }, + peg$c28 = "Y", + peg$c29 = peg$literalExpectation("Y", false), + peg$c30 = function(symbol) { return { symbol, godel: 1 }; }, + peg$c31 = "X", + peg$c32 = peg$literalExpectation("X", false), + peg$c33 = "Z", + peg$c34 = peg$literalExpectation("Z", false), + peg$c35 = function(symbol, ind) { + const index = parseInt(ind); + const order = ["X", "Z"]; + const godel = index * order.length + order.indexOf(symbol); + return { symbol: symbol + ind, godel }; + }, + peg$c36 = "GOTO", + peg$c37 = peg$literalExpectation("GOTO", false), + peg$c38 = "+", + peg$c39 = peg$literalExpectation("+", false), + peg$c40 = "-", + peg$c41 = peg$literalExpectation("-", false), + peg$c42 = /^[A-E]/, + peg$c43 = peg$classExpectation([["A", "E"]], false, false), + peg$c44 = function(symbol, ind) { + const index = parseInt(ind); + const godel = (symbol.charCodeAt(0) - "A".charCodeAt(0) + 1) + 5*(index-1); + return { symbol: symbol + ind, godel }; + }, + peg$c45 = peg$otherExpectation("integer"), + peg$c46 = /^[0-9]/, + peg$c47 = peg$classExpectation([["0", "9"]], false, false), + peg$c48 = function() { return parseInt(text(), 10); }, + peg$c49 = peg$otherExpectation("whitespace"), + peg$c50 = /^[ \t]/, + peg$c51 = peg$classExpectation([" ", "\t"], false, false), + peg$c52 = function() { }, + + peg$currPos = 0, + peg$savedPos = 0, + peg$posDetailsCache = [{ line: 1, column: 1 }], + peg$maxFailPos = 0, + peg$maxFailExpected = [], + peg$silentFails = 0, + + peg$result; + + if ("startRule" in options) { + if (!(options.startRule in peg$startRuleFunctions)) { + throw new Error("Can't start parsing from rule \"" + options.startRule + "\"."); + } + + peg$startRuleFunction = peg$startRuleFunctions[options.startRule]; + } + + function text() { + return input.substring(peg$savedPos, peg$currPos); + } + + function location() { + return peg$computeLocation(peg$savedPos, peg$currPos); + } + + function expected(description, location) { + location = location !== void 0 ? location : peg$computeLocation(peg$savedPos, peg$currPos) + + throw peg$buildStructuredError( + [peg$otherExpectation(description)], + input.substring(peg$savedPos, peg$currPos), + location + ); + } + + function error(message, location) { + location = location !== void 0 ? location : peg$computeLocation(peg$savedPos, peg$currPos) + + throw peg$buildSimpleError(message, location); + } + + function peg$literalExpectation(text, ignoreCase) { + return { type: "literal", text: text, ignoreCase: ignoreCase }; + } + + function peg$classExpectation(parts, inverted, ignoreCase) { + return { type: "class", parts: parts, inverted: inverted, ignoreCase: ignoreCase }; + } + + function peg$anyExpectation() { + return { type: "any" }; + } + + function peg$endExpectation() { + return { type: "end" }; + } + + function peg$otherExpectation(description) { + return { type: "other", description: description }; + } + + function peg$computePosDetails(pos) { + var details = peg$posDetailsCache[pos], p; + + if (details) { + return details; + } else { + p = pos - 1; + while (!peg$posDetailsCache[p]) { + p--; + } + + details = peg$posDetailsCache[p]; + details = { + line: details.line, + column: details.column + }; + + while (p < pos) { + if (input.charCodeAt(p) === 10) { + details.line++; + details.column = 1; + } else { + details.column++; + } + + p++; + } + + peg$posDetailsCache[pos] = details; + return details; + } + } + + function peg$computeLocation(startPos, endPos) { + var startPosDetails = peg$computePosDetails(startPos), + endPosDetails = peg$computePosDetails(endPos); + + return { + start: { + offset: startPos, + line: startPosDetails.line, + column: startPosDetails.column + }, + end: { + offset: endPos, + line: endPosDetails.line, + column: endPosDetails.column + } + }; + } + + function peg$fail(expected) { + if (peg$currPos < peg$maxFailPos) { return; } + + if (peg$currPos > peg$maxFailPos) { + peg$maxFailPos = peg$currPos; + peg$maxFailExpected = []; + } + + peg$maxFailExpected.push(expected); + } + + function peg$buildSimpleError(message, location) { + return new peg$SyntaxError(message, null, null, location); + } + + function peg$buildStructuredError(expected, found, location) { + return new peg$SyntaxError( + peg$SyntaxError.buildMessage(expected, found), + expected, + found, + location + ); + } + + function peg$parseProgram() { + var s0, s1, s2; + + s0 = peg$currPos; + s1 = []; + s2 = peg$parseProgramInstruction(); + if (s2 === peg$FAILED) { + s2 = peg$parse_(); + if (s2 === peg$FAILED) { + if (peg$c0.test(input.charAt(peg$currPos))) { + s2 = input.charAt(peg$currPos); + peg$currPos++; + } else { + s2 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$c1); } + } + } + } + while (s2 !== peg$FAILED) { + s1.push(s2); + s2 = peg$parseProgramInstruction(); + if (s2 === peg$FAILED) { + s2 = peg$parse_(); + if (s2 === peg$FAILED) { + if (peg$c0.test(input.charAt(peg$currPos))) { + s2 = input.charAt(peg$currPos); + peg$currPos++; + } else { + s2 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$c1); } + } + } + } + } + if (s1 !== peg$FAILED) { + peg$savedPos = s0; + s1 = peg$c2(s1); + } + s0 = s1; + + return s0; + } + + function peg$parseProgramInstruction() { + var s0, s1, s2, s3, s4; + + s0 = peg$currPos; + s1 = peg$parse_(); + if (s1 === peg$FAILED) { + s1 = null; + } + if (s1 !== peg$FAILED) { + s2 = peg$parseLabeledInstruction(); + if (s2 === peg$FAILED) { + s2 = peg$parseInstruction(); + } + if (s2 !== peg$FAILED) { + s3 = peg$parse_(); + if (s3 === peg$FAILED) { + s3 = null; + } + if (s3 !== peg$FAILED) { + if (peg$c0.test(input.charAt(peg$currPos))) { + s4 = input.charAt(peg$currPos); + peg$currPos++; + } else { + s4 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$c1); } + } + if (s4 === peg$FAILED) { + s4 = null; + } + if (s4 !== peg$FAILED) { + peg$savedPos = s0; + s1 = peg$c3(s2); + s0 = s1; + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + + return s0; + } + + function peg$parseLabeledInstruction() { + var s0, s1, s2, s3; + + s0 = peg$currPos; + s1 = peg$parseLabel(); + if (s1 !== peg$FAILED) { + s2 = peg$parse_(); + if (s2 !== peg$FAILED) { + s3 = peg$parseInstruction(); + if (s3 !== peg$FAILED) { + peg$savedPos = s0; + s1 = peg$c4(s1, s3); + s0 = s1; + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + + return s0; + } + + function peg$parseLabel() { + var s0, s1, s2, s3, s4, s5; + + s0 = peg$currPos; + if (input.charCodeAt(peg$currPos) === 91) { + s1 = peg$c5; + peg$currPos++; + } else { + s1 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$c6); } + } + if (s1 !== peg$FAILED) { + s2 = peg$parse_(); + if (s2 === peg$FAILED) { + s2 = null; + } + if (s2 !== peg$FAILED) { + s3 = peg$parseLABEL_V(); + if (s3 !== peg$FAILED) { + s4 = peg$parse_(); + if (s4 === peg$FAILED) { + s4 = null; + } + if (s4 !== peg$FAILED) { + if (input.charCodeAt(peg$currPos) === 93) { + s5 = peg$c7; + peg$currPos++; + } else { + s5 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$c8); } + } + if (s5 !== peg$FAILED) { + peg$savedPos = s0; + s1 = peg$c9(s3); + s0 = s1; + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + + return s0; + } + + function peg$parseInstruction() { + var s0, s1; + + s0 = peg$currPos; + s1 = peg$parseConditional(); + if (s1 !== peg$FAILED) { + peg$savedPos = s0; + s1 = peg$c10(s1); + } + s0 = s1; + if (s0 === peg$FAILED) { + s0 = peg$currPos; + s1 = peg$parseAssignment(); + if (s1 !== peg$FAILED) { + peg$savedPos = s0; + s1 = peg$c11(s1); + } + s0 = s1; + if (s0 === peg$FAILED) { + s0 = peg$currPos; + s1 = peg$parseGoto(); + if (s1 !== peg$FAILED) { + peg$savedPos = s0; + s1 = peg$c12(s1); + } + s0 = s1; + } + } + + return s0; + } + + function peg$parseGoto() { + var s0, s1, s2, s3; + + s0 = peg$currPos; + s1 = peg$parseGOTO(); + if (s1 !== peg$FAILED) { + s2 = peg$parse_(); + if (s2 !== peg$FAILED) { + s3 = peg$parseLABEL_V(); + if (s3 !== peg$FAILED) { + peg$savedPos = s0; + s1 = peg$c13(s3); + s0 = s1; + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + + return s0; + } + + function peg$parseConditional() { + var s0, s1, s2, s3, s4, s5, s6, s7, s8, s9; + + s0 = peg$currPos; + if (input.substr(peg$currPos, 2) === peg$c14) { + s1 = peg$c14; + peg$currPos += 2; + } else { + s1 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$c15); } + } + if (s1 !== peg$FAILED) { + s2 = peg$parse_(); + if (s2 !== peg$FAILED) { + s3 = peg$parseVAR(); + if (s3 !== peg$FAILED) { + s4 = peg$parse_(); + if (s4 === peg$FAILED) { + s4 = null; + } + if (s4 !== peg$FAILED) { + if (input.substr(peg$currPos, 2) === peg$c16) { + s5 = peg$c16; + peg$currPos += 2; + } else { + s5 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$c17); } + } + if (s5 !== peg$FAILED) { + s6 = peg$parse_(); + if (s6 === peg$FAILED) { + s6 = null; + } + if (s6 !== peg$FAILED) { + if (input.charCodeAt(peg$currPos) === 48) { + s7 = peg$c18; + peg$currPos++; + } else { + s7 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$c19); } + } + if (s7 !== peg$FAILED) { + s8 = peg$parse_(); + if (s8 !== peg$FAILED) { + s9 = peg$parseGoto(); + if (s9 !== peg$FAILED) { + peg$savedPos = s0; + s1 = peg$c20(s3, s9); + s0 = s1; + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + + return s0; + } + + function peg$parseAssignment() { + var s0, s1, s2, s3, s4, s5; + + s0 = peg$currPos; + s1 = peg$parseVAR(); + if (s1 !== peg$FAILED) { + s2 = peg$parse_(); + if (s2 !== peg$FAILED) { + if (input.substr(peg$currPos, 2) === peg$c21) { + s3 = peg$c21; + peg$currPos += 2; + } else { + s3 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$c22); } + } + if (s3 !== peg$FAILED) { + s4 = peg$parse_(); + if (s4 !== peg$FAILED) { + s5 = peg$parseExpression(); + if (s5 !== peg$FAILED) { + peg$savedPos = s0; + s1 = peg$c23(s1, s5); + s0 = s1; + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + + return s0; + } + + function peg$parseExpression() { + var s0, s1, s2, s3, s4, s5; + + s0 = peg$currPos; + s1 = peg$parseVAR(); + if (s1 !== peg$FAILED) { + s2 = peg$parse_(); + if (s2 !== peg$FAILED) { + s3 = peg$parseOPERATION(); + if (s3 !== peg$FAILED) { + s4 = peg$parse_(); + if (s4 !== peg$FAILED) { + if (input.charCodeAt(peg$currPos) === 49) { + s5 = peg$c24; + peg$currPos++; + } else { + s5 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$c25); } + } + if (s5 !== peg$FAILED) { + peg$savedPos = s0; + s1 = peg$c26(s1, s3); + s0 = s1; + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + if (s0 === peg$FAILED) { + s0 = peg$currPos; + s1 = peg$parseVAR(); + if (s1 !== peg$FAILED) { + peg$savedPos = s0; + s1 = peg$c27(s1); + } + s0 = s1; + } + + return s0; + } + + function peg$parseVAR() { + var s0, s1, s2, s3; + + s0 = peg$currPos; + if (input.charCodeAt(peg$currPos) === 89) { + s1 = peg$c28; + peg$currPos++; + } else { + s1 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$c29); } + } + if (s1 !== peg$FAILED) { + peg$savedPos = s0; + s1 = peg$c30(s1); + } + s0 = s1; + if (s0 === peg$FAILED) { + s0 = peg$currPos; + if (input.charCodeAt(peg$currPos) === 88) { + s1 = peg$c31; + peg$currPos++; + } else { + s1 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$c32); } + } + if (s1 === peg$FAILED) { + if (input.charCodeAt(peg$currPos) === 90) { + s1 = peg$c33; + peg$currPos++; + } else { + s1 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$c34); } + } + } + if (s1 !== peg$FAILED) { + s2 = []; + s3 = peg$parseInteger(); + if (s3 !== peg$FAILED) { + while (s3 !== peg$FAILED) { + s2.push(s3); + s3 = peg$parseInteger(); + } + } else { + s2 = peg$FAILED; + } + if (s2 !== peg$FAILED) { + peg$savedPos = s0; + s1 = peg$c35(s1, s2); + s0 = s1; + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + } + + return s0; + } + + function peg$parseGOTO() { + var s0; + + if (input.substr(peg$currPos, 4) === peg$c36) { + s0 = peg$c36; + peg$currPos += 4; + } else { + s0 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$c37); } + } + + return s0; + } + + function peg$parseOPERATION() { + var s0; + + if (input.charCodeAt(peg$currPos) === 43) { + s0 = peg$c38; + peg$currPos++; + } else { + s0 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$c39); } + } + if (s0 === peg$FAILED) { + if (input.charCodeAt(peg$currPos) === 45) { + s0 = peg$c40; + peg$currPos++; + } else { + s0 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$c41); } + } + } + + return s0; + } + + function peg$parseLABEL_V() { + var s0, s1, s2, s3; + + s0 = peg$currPos; + if (peg$c42.test(input.charAt(peg$currPos))) { + s1 = input.charAt(peg$currPos); + peg$currPos++; + } else { + s1 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$c43); } + } + if (s1 !== peg$FAILED) { + s2 = []; + s3 = peg$parseInteger(); + if (s3 !== peg$FAILED) { + while (s3 !== peg$FAILED) { + s2.push(s3); + s3 = peg$parseInteger(); + } + } else { + s2 = peg$FAILED; + } + if (s2 !== peg$FAILED) { + peg$savedPos = s0; + s1 = peg$c44(s1, s2); + s0 = s1; + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + + return s0; + } + + function peg$parseInteger() { + var s0, s1, s2; + + peg$silentFails++; + s0 = peg$currPos; + s1 = []; + if (peg$c46.test(input.charAt(peg$currPos))) { + s2 = input.charAt(peg$currPos); + peg$currPos++; + } else { + s2 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$c47); } + } + if (s2 !== peg$FAILED) { + while (s2 !== peg$FAILED) { + s1.push(s2); + if (peg$c46.test(input.charAt(peg$currPos))) { + s2 = input.charAt(peg$currPos); + peg$currPos++; + } else { + s2 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$c47); } + } + } + } else { + s1 = peg$FAILED; + } + if (s1 !== peg$FAILED) { + peg$savedPos = s0; + s1 = peg$c48(); + } + s0 = s1; + peg$silentFails--; + if (s0 === peg$FAILED) { + s1 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$c45); } + } + + return s0; + } + + function peg$parse_() { + var s0, s1, s2; + + peg$silentFails++; + s0 = peg$currPos; + s1 = []; + if (peg$c50.test(input.charAt(peg$currPos))) { + s2 = input.charAt(peg$currPos); + peg$currPos++; + } else { + s2 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$c51); } + } + if (s2 !== peg$FAILED) { + while (s2 !== peg$FAILED) { + s1.push(s2); + if (peg$c50.test(input.charAt(peg$currPos))) { + s2 = input.charAt(peg$currPos); + peg$currPos++; + } else { + s2 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$c51); } + } + } + } else { + s1 = peg$FAILED; + } + if (s1 !== peg$FAILED) { + peg$savedPos = s0; + s1 = peg$c52(); + } + s0 = s1; + peg$silentFails--; + if (s0 === peg$FAILED) { + s1 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$c49); } + } + + return s0; + } + + peg$result = peg$startRuleFunction(); + + if (peg$result !== peg$FAILED && peg$currPos === input.length) { + return peg$result; + } else { + if (peg$result !== peg$FAILED && peg$currPos < input.length) { + peg$fail(peg$endExpectation()); + } + + throw peg$buildStructuredError( + peg$maxFailExpected, + peg$maxFailPos < input.length ? input.charAt(peg$maxFailPos) : null, + peg$maxFailPos < input.length + ? peg$computeLocation(peg$maxFailPos, peg$maxFailPos + 1) + : peg$computeLocation(peg$maxFailPos, peg$maxFailPos) + ); + } + } + + return { + SyntaxError: peg$SyntaxError, + parse: peg$parse + }; +})(); + +export default parser; +export const SyntaxError = parser.SyntaxError; diff --git a/html/toys/godel/js/ui.js b/html/toys/godel/js/ui.js new file mode 100644 index 0000000..97e5209 --- /dev/null +++ b/html/toys/godel/js/ui.js @@ -0,0 +1,224 @@ +import parser from "./parser.js"; +import { compileProgram } from "./compiler.js"; + +const SAMPLE_PROGRAM = `// THIS PROGRAM COMPUTES X1 + X2 + +// Y <- X1 +[ A1 ] IF X1 != 0 GOTO A2 + GOTO B1 +[ A2 ] X1 <- X1 - 1 + Y <- Y + 1 + GOTO A1 + +// Z1 <- X2 +[ B1 ] IF X2 != 0 GOTO B2 + GOTO C1 +[ B2 ] X2 <- X2 - 1 + Z1 <- Z1 + 1 + GOTO B1 + +// Y <- Y + Z1 +[ C1 ] IF Z1 != 0 GOTO C2 + GOTO E1 +[ C2 ] Z1 <- Z1 - 1 + Y <- Y + 1 + GOTO C1`; + +const STATUS_VARIANTS = { + success: "text-success", + error: "text-error", + info: "muted" +}; + +export class GodelPlayground { + constructor() { + this.elements = { + compileBtn: document.getElementById("compile-btn"), + copyBtn: document.getElementById("copy-btn"), + evalBtn: document.getElementById("eval-btn"), + computeNumberBtn: document.getElementById("compute-godel-btn"), + compileStatus: document.getElementById("compile-status"), + evalStatus: document.getElementById("eval-status"), + godelSequence: document.getElementById("godel-sequence"), + godelNumber: document.getElementById("godel-number") + }; + + this.sourceEditor = null; + this.compiledEditor = null; + this.worker = null; + this.latestSequence = []; + + this.init(); + } + + init() { + this.setupEditors(); + this.bindEvents(); + this.hydrateFromParams(); + if (!this.getSource().trim()) { + this.setSource(SAMPLE_PROGRAM); + } + this.compileSource(); + } + + setupEditors() { + this.sourceEditor = adelieEditor.init("#source-editor", { + language: "javascript" + }); + this.compiledEditor = adelieEditor.init("#compiled-editor", { + language: "javascript" + }); + this.setSource(SAMPLE_PROGRAM); + } + + bindEvents() { + this.elements.compileBtn.addEventListener("click", () => this.compileSource()); + this.elements.evalBtn.addEventListener("click", () => this.evaluateCompiled()); + this.elements.copyBtn.addEventListener("click", () => this.copyShareLink()); + this.elements.computeNumberBtn.addEventListener("click", () => this.computeGodelNumber()); + + document.addEventListener("keydown", (event) => { + if (event.ctrlKey && event.key === "Enter") { + event.preventDefault(); + this.compileSource(); + } + }); + } + + hydrateFromParams() { + const params = new URLSearchParams(window.location.search); + const encoded = params.get("instructions"); + if (encoded) { + try { + const decoded = atob(encoded); + this.setSource(decoded); + } catch (error) { + console.warn("Failed to decode instructions from URL", error); + } + } + } + + getSource() { + return this.sourceEditor.state.doc.toString(); + } + + setSource(content) { + const docLength = this.sourceEditor.state.doc.toString().length; + this.sourceEditor.dispatch({ + changes: { from: 0, to: docLength, insert: content } + }); + } + + getCompiled() { + return this.compiledEditor.state.doc.toString(); + } + + setCompiled(content) { + const docLength = this.compiledEditor.state.doc.toString().length; + this.compiledEditor.dispatch({ + changes: { from: 0, to: docLength, insert: content } + }); + } + + prepareSource(source) { + return source.replace(/\/\/.*$/gm, "").trim(); + } + + compileSource() { + const raw = this.getSource(); + const prepared = this.prepareSource(raw); + + if (!prepared) { + this.setStatus("compile", "Provide some source to compile", "error"); + return; + } + + try { + const ast = parser.parse(prepared); + const { js, godelSequence } = compileProgram(ast); + this.latestSequence = godelSequence; + this.setCompiled(js); + this.renderSequence(godelSequence); + this.setStatus("compile", "Successful compilation", "success"); + this.elements.computeNumberBtn.disabled = godelSequence.length === 0; + } catch (error) { + this.latestSequence = []; + this.renderSequence([]); + this.elements.computeNumberBtn.disabled = true; + this.setStatus("compile", error.message || "Error compiling", "error"); + } + } + + evaluateCompiled() { + const js = this.getCompiled(); + if (!js.trim()) { + this.setStatus("eval", "Compile a program first", "error"); + return; + } + + try { + const result = (0, eval)(js); + this.setStatus( + "eval", + `Result: ${typeof result === "undefined" ? "(no return)" : result}`, + "success" + ); + } catch (error) { + this.setStatus("eval", error.message || "Failed to evaluate program", "error"); + } + } + + renderSequence(sequence) { + if (!sequence.length) { + this.elements.godelSequence.textContent = "Compile to view the Gödel sequence."; + this.elements.godelNumber.textContent = ""; + return; + } + this.elements.godelSequence.textContent = `[${sequence.join(", ")}]`; + this.elements.godelNumber.textContent = ""; + } + + copyShareLink() { + const data = btoa(this.getSource()); + const url = `${window.location.href.split("?")[0]}?instructions=${data}`; + + navigator.clipboard.writeText(url) + .then(() => alert("Shareable link copied to clipboard")) + .catch(() => alert("Failed to copy link")); + } + + computeGodelNumber() { + if (!this.latestSequence.length) { + this.setStatus("compile", "Compile a program to produce its Gödel sequence", "error"); + return; + } + + this.elements.godelNumber.textContent = "Working..."; + this.elements.computeNumberBtn.disabled = true; + + const worker = this.getWorker(); + worker.onmessage = (event) => { + this.elements.godelNumber.textContent = event.data; + this.elements.computeNumberBtn.disabled = false; + }; + worker.onerror = () => { + this.elements.godelNumber.textContent = "Failed to compute Gödel number"; + this.elements.computeNumberBtn.disabled = false; + }; + worker.postMessage(this.latestSequence); + } + + getWorker() { + if (!this.worker) { + const workerUrl = new URL("./godel-worker.js", import.meta.url); + this.worker = new Worker(workerUrl, { type: "module" }); + } + return this.worker; + } + + setStatus(kind, message, variant = "info") { + const element = kind === "compile" ? this.elements.compileStatus : this.elements.evalStatus; + element.textContent = message; + element.className = `status-text ${STATUS_VARIANTS[variant] || ""}`; + } +} diff --git a/html/toys/index.html b/html/toys/index.html new file mode 100644 index 0000000..e69de29 diff --git a/html/toys/julia/index.html b/html/toys/julia/index.html new file mode 100644 index 0000000..9d9de51 --- /dev/null +++ b/html/toys/julia/index.html @@ -0,0 +1,51 @@ + + + + + + + +
+ +
+
+ + +
+ imaginary: + +
+ real: + +
+ powered by gpu.js +
+ + + + + diff --git a/html/toys/julia/julia.js b/html/toys/julia/julia.js new file mode 100644 index 0000000..0744e6f --- /dev/null +++ b/html/toys/julia/julia.js @@ -0,0 +1,143 @@ +const MIN_ZOOM = 0.0001; +const MAX_ZOOM = 4; +const C_THRESHOLD = Math.sqrt(2)/2; +const SLIDER_DIV = 2000*C_THRESHOLD; + +function initGPU() { + try { + return new window.GPU.GPU(); + } catch (e) { + return new GPU(); + } +} +const gpu = initGPU(); +const buildRender = (width, height) => gpu.createKernel(function (maxIterations, cr, ci, centerX, centerY, zoom, colorMultipliers) { + let zx = (this.output.x / this.output.y) * (centerX + (4 * this.thread.x / this.output.x - 2) * (zoom / 4)); + let zy = centerY + (4 * this.thread.y / this.output.y - 2) * (zoom / 4); + let iterations = 0; + for (let i = 0; i < maxIterations; i++) { + const xtemp = zx * zx - zy * zy + cr; + zy = 2 * zx * zy + ci; + zx = xtemp; + if (zx * zx + zy * zy > 4) { + iterations = i; + break; + } + } + if (iterations == 0 || iterations == maxIterations) { + this.color(0, 0, 0); + } else { + this.color(colorMultipliers[0] * iterations, colorMultipliers[1] * iterations, colorMultipliers[2] * iterations); + } +}, { output: [width, height], graphical: true }); + +const canvasHolder = document.getElementById('canvasHolder'); +let render; // The GPU kernel built from buildRender +let state = { + colorMultipliers: [0.01 * Math.random() + 0.015, 0.03 * Math.random() + 0.007, 0.02 * Math.random() + 0.010], + changes: { + centerX: 0, + centerY: 0, + zoom: 3, + cr: parseFloat(document.getElementById('realSlider').value) / SLIDER_DIV, + ci: parseFloat(document.getElementById('imaginarySlider').value) / SLIDER_DIV, + maxIterations: 1000, + width: document.body.clientWidth, + height: document.body.clientHeight, + }, +}; + +const doRender = (renderF, state) => { + // gpu.js doesn't support JS objects as kernel parameters - https://github.com/gpujs/gpu.js/issues/245 + renderF(state.maxIterations, state.cr, state.ci, state.centerX, state.centerY, state.zoom, state.colorMultipliers); +}; + +const loop = () => { + const stateChanges = Object.keys(state.changes); + if (stateChanges.length > 0) { + state = {...state, ...state.changes}; + if (state.changes.width || state.changes.height) { + render = buildRender(state.width, state.height); + canvasHolder.appendChild(render.canvas); + } + if (typeof state.changes.ci !== 'undefined') { + document.getElementById('imag-val').value = state.ci.toFixed(4); + document.getElementById('imaginarySlider').value = state.changes.ci * SLIDER_DIV; + } + if (typeof state.changes.cr !== 'undefined') { + document.getElementById('real-val').value = state.cr.toFixed(4); + document.getElementById('realSlider').value = state.changes.cr * SLIDER_DIV; + } + state.changes = {}; + doRender(render, state); + } + + requestAnimationFrame(loop); +}; + +loop(); + +// UI Code + +const startAnim = (sliderId, complexComponentName='ci') => { + const restart = (interval) => { + clearInterval(interval); + document.getElementById(sliderId).innerHTML = 'Animate'; + document.getElementById(sliderId).onclick = ()=>startAnim(sliderId, complexComponentName); + return; + }; + const start = setInterval(() => { + if (state[complexComponentName] >= C_THRESHOLD) { + restart(start); + } + + state.changes[complexComponentName] = state[complexComponentName] + 0.001; + }, 1000/60); + document.getElementById(sliderId).innerHTML = 'Stop'; + document.getElementById(sliderId).onclick = ()=>restart(start); +}; + +document.getElementById('imaginarySlider').oninput = function() { + state.changes.ci = parseFloat(this.value) / SLIDER_DIV; +}; +document.getElementById('realSlider').oninput = function() { + state.changes.cr = parseFloat(this.value) / SLIDER_DIV; +}; + +document.getElementById('imag-val').addEventListener('change', function (e) { + state.changes.ci = parseFloat(this.value); +}); +document.getElementById('real-val').addEventListener('change', function (e) { + state.changes.cr = parseFloat(this.value); +}); + +canvasHolder.addEventListener('wheel', (e) => { + e.preventDefault(); + state.changes.zoom = Math.min(Math.max(state.zoom + e.deltaY * 0.001 * state.zoom, MIN_ZOOM), MAX_ZOOM); +}); + +let isDown = false; +canvasHolder.addEventListener('mousedown', (e) => { + e.preventDefault(); + isDown = true; +}, true); + +canvasHolder.addEventListener('mouseup', (e) => { + e.preventDefault(); + isDown = false; +}, true); + +canvasHolder.addEventListener('mousemove', (e) => { + e.preventDefault(); + if (isDown) { + let deltaX = -e.movementX * state.zoom / document.body.clientWidth; + let deltaY = e.movementY * state.zoom / document.body.clientHeight; + state.changes.centerX = state.centerX + deltaX; + state.changes.centerY = state.centerY + deltaY; + } +}, true); + +window.addEventListener('resize', () => { + state.changes.width = document.body.clientWidth; + state.changes.height = document.body.clientHeight; +}); diff --git a/html/toys/tabloid/index.html b/html/toys/tabloid/index.html new file mode 100644 index 0000000..61ec937 --- /dev/null +++ b/html/toys/tabloid/index.html @@ -0,0 +1,53 @@ + + + + + + + + Tabloid + + +
+
+

Tabloid

+

Forgotten how to write Tabloid since your schooldays? Here's a refresher.

+ +
+ + +
+ +
+
+
+ +
+ + +
+ +
+

stdout

+

+      
+ +
+

stderr

+

+      
+
+
+ + + + + + diff --git a/html/toys/tabloid/js/main.js b/html/toys/tabloid/js/main.js new file mode 100644 index 0000000..1f0b5dd --- /dev/null +++ b/html/toys/tabloid/js/main.js @@ -0,0 +1,5 @@ +import { TabloidPlayground } from "./playground.js"; + +document.addEventListener("DOMContentLoaded", () => { + new TabloidPlayground(); +}); diff --git a/html/toys/tabloid/js/playground.js b/html/toys/tabloid/js/playground.js new file mode 100644 index 0000000..d6ff71a --- /dev/null +++ b/html/toys/tabloid/js/playground.js @@ -0,0 +1,120 @@ +import { tokenize, Parser, Environment } from "./tabloid.js"; +import { SAMPLE_PROGRAMS } from "./samples.js"; + +class TabloidRunner { + run(code) { + const stdout = []; + const stderr = []; + + if (!code) { + stderr.push("No code to execute."); + return { stdout, stderr }; + } + + try { + const tokens = tokenize(code); + const nodes = new Parser(tokens).parse(); + const env = new Environment({ + print: (msg) => stdout.push(String(msg)), + input: (promptText) => window.prompt(promptText) ?? "" + }); + env.run(nodes); + } catch (error) { + stderr.push(error.message || error.toString()); + } + + return { stdout, stderr }; + } +} + +export class TabloidPlayground { + constructor() { + this.runner = new TabloidRunner(); + this.programLookup = new Map(SAMPLE_PROGRAMS.map((program) => [program.id, program])); + this.activeProgramId = this.defaultProgramId = SAMPLE_PROGRAMS[0].id; + + this.stdoutElement = document.getElementById("stdout-content"); + this.errorElement = document.getElementById("error-content"); + this.programSelect = document.getElementById("program-select"); + this.runButton = document.getElementById("run-button"); + this.clearButton = document.getElementById("clear-button"); + + this.editor = adelieEditor.init("#code-editor", { + language: "tabloid" + }); + + this.init(); + } + + init() { + this.populateProgramSelect(); + this.registerEvents(); + this.loadProgram(this.activeProgramId); + } + + populateProgramSelect() { + if (!this.programSelect) { + return; + } + + this.programSelect.innerHTML = ""; + SAMPLE_PROGRAMS.forEach((program) => { + const option = document.createElement("option"); + option.value = program.id; + option.textContent = program.label; + this.programSelect.appendChild(option); + }); + + this.programSelect.value = this.activeProgramId; + } + + registerEvents() { + this.runButton.addEventListener("click", () => this.runCode()); + this.clearButton.addEventListener("click", () => this.resetOutput()); + + this.programSelect.addEventListener("change", () => { + this.loadProgram(this.programSelect.value); + }); + + document.addEventListener("keydown", (event) => { + if (event.ctrlKey && event.key === "Enter") { + event.preventDefault(); + this.runCode(); + } + }); + } + + setEditorContent(content) { + const length = this.editor.state.doc.toString().length; + this.editor.dispatch({ + changes: { from: 0, to: length, insert: content } + }); + } + + loadProgram(programId = this.defaultProgramId) { + const selectedProgram = this.programLookup.get(programId); + if (!selectedProgram) { + return; + } + + this.activeProgramId = selectedProgram.id; + this.setEditorContent(selectedProgram.code); + this.resetOutput(); + + if (this.programSelect) { + this.programSelect.value = this.activeProgramId; + } + } + + resetOutput() { + this.stdoutElement.textContent = ""; + this.errorElement.textContent = ""; + } + + runCode() { + const code = this.editor.state.doc.toString(); + const result = this.runner.run(code); + this.stdoutElement.textContent = result.stdout.join("\n"); + this.errorElement.textContent = result.stderr.join("\n"); + } +} diff --git a/html/toys/tabloid/js/samples.js b/html/toys/tabloid/js/samples.js new file mode 100644 index 0000000..dc7bd58 --- /dev/null +++ b/html/toys/tabloid/js/samples.js @@ -0,0 +1,308 @@ +const CONS_SNIPPET = [ + "DISCOVER HOW TO cons WITH a, b", + "RUMOR HAS IT", + " DISCOVER HOW TO retrieve WITH is_first", + " RUMOR HAS IT", + " WHAT IF is_first IS ACTUALLY TOTALLY RIGHT", + " SHOCKING DEVELOPMENT a", + " LIES!", + " SHOCKING DEVELOPMENT b", + " END OF STORY", + " SHOCKING DEVELOPMENT retrieve", + "END OF STORY", +].join('\n'); + +const BINARY_INORDER_TRAVERSAL_PROGRAM = [ + CONS_SNIPPET, + '', + "DISCOVER HOW TO in_order_traverse WITH node, is_dual_ptr", + "RUMOR HAS IT", + " EXPERTS CLAIM left TO BE node OF TOTALLY RIGHT", + " EXPERTS CLAIM right TO BE node OF COMPLETELY WRONG", + '', + " WHAT IF is_dual_ptr IS ACTUALLY COMPLETELY WRONG", + " RUMOR HAS IT", + " YOU WON'T WANT TO MISS left", + " WHAT IF right IS ACTUALLY COMPLETELY WRONG", + " 1", + " LIES!", + " in_order_traverse OF right, TOTALLY RIGHT", + " END OF STORY", + " LIES!", + " RUMOR HAS IT", + " WHAT IF left IS ACTUALLY COMPLETELY WRONG", + " 1", + " LIES!", + " in_order_traverse OF left, COMPLETELY WRONG", + '', + " WHAT IF right IS ACTUALLY COMPLETELY WRONG", + " 1", + " LIES!", + " in_order_traverse OF right, COMPLETELY WRONG", + " END OF STORY", + "END OF STORY", + '', + "EXPERTS CLAIM l TO BE cons OF 1, COMPLETELY WRONG", + "EXPERTS CLAIM r TO BE cons OF 3, COMPLETELY WRONG", + "EXPERTS CLAIM root TO BE cons OF l, r", + "EXPERTS CLAIM head TO BE cons OF 2, root", + '', + "in_order_traverse OF head, COMPLETELY WRONG", + '', + "PLEASE LIKE AND SUBSCRIBE", +].join('\n'); + +const MERGE_SORT_PROGRAM = [ + CONS_SNIPPET, + '', + "DISCOVER HOW TO print WITH x", + "RUMOR HAS IT", + " YOU WON'T WANT TO MISS x", + "END OF STORY", + '', + "DISCOVER HOW TO map WITH fn, list", + "RUMOR HAS IT", + " WHAT IF list IS ACTUALLY COMPLETELY WRONG", + " SHOCKING DEVELOPMENT COMPLETELY WRONG", + " LIES!", + " RUMOR HAS IT", + " EXPERTS CLAIM car TO BE list OF TOTALLY RIGHT", + " EXPERTS CLAIM cdr TO BE list OF COMPLETELY WRONG", + " EXPERTS CLAIM new_car TO BE fn OF car", + " EXPERTS CLAIM rest_mapped TO BE map OF fn, cdr", + '', + " SHOCKING DEVELOPMENT cons OF new_car, rest_mapped", + " END OF STORY", + "END OF STORY", + '', + "DISCOVER HOW TO reduce WITH fn, list, accumulator", + "RUMOR HAS IT", + " WHAT IF list IS ACTUALLY COMPLETELY WRONG", + " SHOCKING DEVELOPMENT accumulator", + " LIES!", + " RUMOR HAS IT", + " EXPERTS CLAIM car TO BE list OF TOTALLY RIGHT", + " EXPERTS CLAIM cdr TO BE list OF COMPLETELY WRONG", + " EXPERTS CLAIM added_accumulator TO BE fn OF car, accumulator", + '', + " SHOCKING DEVELOPMENT reduce OF fn, cdr, added_accumulator", + " END OF STORY", + "END OF STORY", + '', + "DISCOVER HOW TO str_join_reducer WITH element, accumulator", + "RUMOR HAS IT", + " EXPERTS CLAIM added_comma TO BE element PLUS ', '", + " SHOCKING DEVELOPMENT added_comma PLUS accumulator", + "END OF STORY", + '', + "DISCOVER HOW TO join WITH list", + "RUMOR HAS IT", + " SHOCKING DEVELOPMENT reduce OF str_join_reducer, list, ''", + "END OF STORY", + '', + "DISCOVER HOW TO append WITH n, m", + "RUMOR HAS IT", + " WHAT IF n IS ACTUALLY COMPLETELY WRONG", + " SHOCKING DEVELOPMENT m", + " LIES!", + " RUMOR HAS IT", + " EXPERTS CLAIM car_n TO BE n OF TOTALLY RIGHT", + " EXPERTS CLAIM cdr_n TO BE n OF COMPLETELY WRONG", + " EXPERTS CLAIM appended TO BE append OF cdr_n, m", + '', + " SHOCKING DEVELOPMENT cons OF car_n, appended", + " END OF STORY", + "END OF STORY", + '', + "DISCOVER HOW TO reverse WITH l", + "RUMOR HAS IT", + " WHAT IF l IS ACTUALLY COMPLETELY WRONG", + " SHOCKING DEVELOPMENT COMPLETELY WRONG", + " LIES!", + " 1", + '', + " EXPERTS CLAIM car TO BE l OF TOTALLY RIGHT", + " EXPERTS CLAIM cdr TO BE l OF COMPLETELY WRONG", + " EXPERTS CLAIM reversed_cdr TO BE reverse OF cdr", + " EXPERTS CLAIM car_cons TO BE cons OF car, COMPLETELY WRONG", + '', + " SHOCKING DEVELOPMENT append OF reversed_cdr, car_cons", + "END OF STORY", + '', + "DISCOVER HOW TO merge WITH x, y", + "RUMOR HAS IT", + " WHAT IF x IS ACTUALLY COMPLETELY WRONG", + " SHOCKING DEVELOPMENT y", + " LIES!", + " 1", + '', + " WHAT IF y IS ACTUALLY COMPLETELY WRONG", + " RUMOR HAS IT", + " WHAT IF x IS ACTUALLY COMPLETELY WRONG", + " SHOCKING DEVELOPMENT COMPLETELY WRONG", + " LIES!", + " SHOCKING DEVELOPMENT x", + " END OF STORY", + " LIES!", + " 1", + '', + " EXPERTS CLAIM car_x TO BE x OF TOTALLY RIGHT", + " EXPERTS CLAIM car_y TO BE y OF TOTALLY RIGHT", + " EXPERTS CLAIM cdr_x TO BE x OF COMPLETELY WRONG", + " EXPERTS CLAIM cdr_y TO BE y OF COMPLETELY WRONG", + '', + " EXPERTS CLAIM x_gt_y TO BE car_x BEATS car_y", + '', + " WHAT IF x_gt_y IS ACTUALLY TOTALLY RIGHT", + " RUMOR HAS IT", + " EXPERTS CLAIM rest_x_merge_y TO BE merge OF cdr_x, y", + " SHOCKING DEVELOPMENT cons OF car_x, rest_x_merge_y", + " END OF STORY", + " LIES!", + " RUMOR HAS IT", + " EXPERTS CLAIM x_merge_rest_y TO BE merge OF x, cdr_y", + " SHOCKING DEVELOPMENT cons OF car_y, x_merge_rest_y", + " END OF STORY", + "END OF STORY", + '', + "DISCOVER HOW TO split_middle_helper WITH slow, fast, mid_to_head", + "RUMOR HAS IT", + " WHAT IF fast IS ACTUALLY COMPLETELY WRONG", + " RUMOR HAS IT", + " EXPERTS CLAIM head_to_mid TO BE reverse OF mid_to_head", + " SHOCKING DEVELOPMENT cons OF head_to_mid, slow", + " END OF STORY", + " LIES!", + " 1", + '', + " EXPERTS CLAIM fast_cdr TO BE fast OF COMPLETELY WRONG", + " EXPERTS CLAIM slow_car TO BE slow OF TOTALLY RIGHT", + " EXPERTS CLAIM slow_cdr TO BE slow OF COMPLETELY WRONG", + '', + " WHAT IF fast_cdr IS ACTUALLY COMPLETELY WRONG", + " RUMOR HAS IT", + " EXPERTS CLAIM mid_to_head_plus_slow TO BE cons OF slow_car, mid_to_head", + " EXPERTS CLAIM head_to_mid_plus_slow TO BE reverse OF mid_to_head_plus_slow", + '', + " SHOCKING DEVELOPMENT cons OF head_to_mid_plus_slow, slow_cdr", + " END OF STORY", + " LIES!", + " 1", + '', + " EXPERTS CLAIM fast_cddr TO BE fast_cdr OF COMPLETELY WRONG", + " EXPERTS CLAIM slow_car_mid_to_head TO BE cons OF slow_car, mid_to_head", + '', + " SHOCKING DEVELOPMENT split_middle_helper OF slow_cdr, fast_cddr, slow_car_mid_to_head", + "END OF STORY", + '', + "DISCOVER HOW TO split_middle WITH start", + "RUMOR HAS IT", + " EXPERTS CLAIM cdr TO BE start OF COMPLETELY WRONG", + '', + " SHOCKING DEVELOPMENT split_middle_helper OF start, cdr, COMPLETELY WRONG", + "END OF STORY", + '', + "DISCOVER HOW TO sort WITH root", + "RUMOR HAS IT", + " WHAT IF root IS ACTUALLY COMPLETELY WRONG", + " SHOCKING DEVELOPMENT root", + " LIES!", + " 1", + '', + " EXPERTS CLAIM root_cdr TO BE root OF COMPLETELY WRONG", + " WHAT IF root_cdr IS ACTUALLY COMPLETELY WRONG", + " SHOCKING DEVELOPMENT root", + " LIES!", + " 1", + '', + " EXPERTS CLAIM left_right_cons_cell TO BE split_middle OF root", + " EXPERTS CLAIM left TO BE left_right_cons_cell OF TOTALLY RIGHT", + " EXPERTS CLAIM right TO BE left_right_cons_cell OF COMPLETELY WRONG", + " EXPERTS CLAIM left_s TO BE sort OF left", + " EXPERTS CLAIM right_s TO BE sort OF right", + '', + " SHOCKING DEVELOPMENT merge OF left_s, right_s", + "END OF STORY", + '', + "EXPERTS CLAIM a_3 TO BE cons OF 3, COMPLETELY WRONG", + "EXPERTS CLAIM a_2 TO BE cons OF 1, a_3", + "EXPERTS CLAIM a_1 TO BE cons OF -2, a_2", + "EXPERTS CLAIM a_0 TO BE cons OF 5, a_1", + "EXPERTS CLAIM b_3 TO BE cons OF 2, a_0", + "EXPERTS CLAIM b_2 TO BE cons OF 7, b_3", + "EXPERTS CLAIM b_1 TO BE cons OF 3, b_2", + "EXPERTS CLAIM b_0 TO BE cons OF -1, b_1", + '', + "EXPERTS CLAIM b_sorted TO BE sort OF b_0", + '', + "YOU WON'T WANT TO MISS join OF b_sorted", + '', + "PLEASE LIKE AND SUBSCRIBE", +].join('\n'); + +export const SAMPLE_PROGRAMS = [ + { + id: 'fibonacci', + label: 'Fibonacci', + code: [ + "DISCOVER HOW TO fibonacci WITH a, b, n", + "RUMOR HAS IT", + " WHAT IF n SMALLER THAN 1", + " SHOCKING DEVELOPMENT b", + " LIES! RUMOR HAS IT", + " YOU WON'T WANT TO MISS b", + " SHOCKING DEVELOPMENT", + " fibonacci OF b, a PLUS b, n MINUS 1", + " END OF STORY", + "END OF STORY", + "", + "EXPERTS CLAIM limit TO BE 10", + "", + "fibonacci OF 0, 1, limit", + "", + "PLEASE LIKE AND SUBSCRIBE", + ].join('\n'), + }, + { + id: 'countdown', + label: 'Countdown', + code: [ + "EXPERTS CLAIM start TO BE 4", + "YOU WON'T WANT TO MISS 't minus 5...'", + "", + "DISCOVER HOW TO countdown WITH current", + "RUMOR HAS IT", + " WHAT IF current SMALLER THAN 1 RUMOR HAS IT", + " SHOCKING DEVELOPMENT 'Blastoff!'", + " END OF STORY", + " LIES! RUMOR HAS IT", + " YOU WON'T WANT TO MISS current", + " SHOCKING DEVELOPMENT countdown OF current MINUS 1", + " END OF STORY", + "END OF STORY", + "", + "YOU WON'T WANT TO MISS countdown OF start", + "", + "PLEASE LIKE AND SUBSCRIBE", + ].join('\n'), + }, + { + id: 'hello', + label: 'Hello!', + code: [ + "YOU WON'T WANT TO MISS ('Hello, ' PLUS (LATEST NEWS ON 'What is your name?')) PLUS '!'", + "", + "PLEASE LIKE AND SUBSCRIBE", + ].join('\n'), + }, + { + id: 'binary-inorder-traversal', + label: 'Binary tree in-order traversal', + code: BINARY_INORDER_TRAVERSAL_PROGRAM, + }, + { + id: 'merge-sort', + label: 'Merge sort linked list', + code: MERGE_SORT_PROGRAM, + }, +]; diff --git a/html/toys/tabloid/js/tabloid.js b/html/toys/tabloid/js/tabloid.js new file mode 100644 index 0000000..468ee9a --- /dev/null +++ b/html/toys/tabloid/js/tabloid.js @@ -0,0 +1,707 @@ +/* Tabloid: the clickbait headline programming language */ + +/* tokenizer */ + +/** + * Reads in char or word chunks + */ +class Reader { + constructor(str, base = '') { + this.base = base; + this.i = 0; + this.str = str; + } + peek() { + return this.str[this.i]; + } + next() { + return this.str[this.i++]; + } + hasNext() { + return this.str[this.i] !== undefined; + } + backstep() { + this.i--; + } + readUntil(pred) { + let result = this.base.slice(); + while (this.hasNext() && !pred(this.peek())) { + result += this.next(); + } + return result; + } + dropWhitespace() { + this.readUntil(c => !!c.trim()); + } + expect(tok) { + const next = this.next(); + if (next !== tok) { + throw new Error(`Parsing error: expected ${tok}, got ${next}`); + } + } +} + +/** + * Split into words for easier tokenization + * with keywords. + */ +class Wordifier { + constructor(str) { + this.reader = new Reader(str.trim()); + this.tokens = []; + } + wordify() { + if (this.tokens.length) return this.tokens; + + while (this.reader.hasNext()) { + const next = this.reader.next(); + switch (next) { + case '(': { + this.tokens.push('('); + break; + } + case ')': { + this.tokens.push(')'); + break; + } + case ',': { + this.tokens.push(','); + break; + } + case '"': + case "'": { + this.wordifyString(next); + break; + } + default: { + // read until WS + this.reader.backstep(); + this.tokens.push(this.reader.readUntil(c => { + return !c.trim() || ['(', ')', ','].includes(c) + })); + } + } + this.reader.dropWhitespace(); + } + return this.tokens; + } + wordifyString(endChar) { + let acc = ''; + acc += this.reader.readUntil(c => c == endChar); + while (acc.endsWith('\\') || !this.reader.hasNext()) { + acc = acc.substr(0, acc.length - 1); + this.reader.next(); // endChar + acc += endChar + this.reader.readUntil(c => c == endChar); + } + this.reader.next(); // throw away closing char + this.tokens.push('"' + acc); + } +} + +const T = { + LParen: Symbol('LParen'), + RParen: Symbol('RParen'), + Comma: Symbol('Comma'), + DiscoverHowTo: Symbol('DiscoverHowTo'), + With: Symbol('With'), + Of: Symbol('Of'), + RumorHasIt: Symbol('RumorHasIt'), + WhatIf: Symbol('WhatIf'), + LiesBang: Symbol('LiesBang'), + EndOfStory: Symbol('EndOfStory'), + ExpertsClaim: Symbol('ExpertsClaim'), + ToBe: Symbol('ToBe'), + YouWontWantToMiss: Symbol('YouWontWantToMiss'), + LatestNewsOn: Symbol('LatestNewsOn'), + TotallyRight: Symbol('TotallyRight'), + CompletelyWrong: Symbol('CompletelyWrong'), + IsActually: Symbol('IsActually'), + And: Symbol('And'), + Or: Symbol('Or'), + Plus: Symbol('Plus'), + Minus: Symbol('Minus'), + Times: Symbol('Times'), + DividedBy: Symbol('DividedBy'), + Modulo: Symbol('Modulo'), + Beats: Symbol('Beats'), // > + SmallerThan: Symbol('SmallerThan'), // < + ShockingDevelopment: Symbol('ShockingDevelopment'), + PleaseLikeAndSubscribe: Symbol('PleaseLikeAndSubscribe'), +} + +const BINARY_OPS = [ + T.IsActually, + T.And, + T.Or, + T.Plus, + T.Minus, + T.Times, + T.DividedBy, + T.Modulo, + T.Beats, + T.SmallerThan, +]; + +export function tokenize(prog) { + const reader = new Reader(new Wordifier(prog).wordify(), []); + const tokens = []; + + while (reader.hasNext()) { + const next = reader.next(); + switch (next) { + case 'DISCOVER': { + reader.expect('HOW'); + reader.expect('TO'); + tokens.push(T.DiscoverHowTo); + break; + } + case 'WITH': { + tokens.push(T.With); + break; + } + case 'OF': { + tokens.push(T.Of); + break; + } + case 'RUMOR': { + reader.expect('HAS'); + reader.expect('IT'); + tokens.push(T.RumorHasIt); + break; + } + case 'WHAT': { + reader.expect('IF'); + tokens.push(T.WhatIf); + break; + } + case 'LIES!': { + tokens.push(T.LiesBang); + break; + } + case 'END': { + reader.expect('OF'); + reader.expect('STORY'); + tokens.push(T.EndOfStory); + break; + } + case 'EXPERTS': { + reader.expect('CLAIM'); + tokens.push(T.ExpertsClaim); + break; + } + case 'TO': { + reader.expect('BE'); + tokens.push(T.ToBe); + break; + } + case 'YOU': { + reader.expect('WON\'T'); + reader.expect('WANT'); + reader.expect('TO'); + reader.expect('MISS'); + tokens.push(T.YouWontWantToMiss); + break; + } + case 'LATEST': { + reader.expect('NEWS'); + reader.expect('ON'); + tokens.push(T.LatestNewsOn); + break; + } + case 'IS': { + reader.expect('ACTUALLY'); + tokens.push(T.IsActually); + break; + } + case 'AND': { + tokens.push(T.And); + break; + } + case 'OR': { + tokens.push(T.Or); + break; + } + case 'PLUS': { + tokens.push(T.Plus); + break; + } + case 'MINUS': { + tokens.push(T.Minus); + break; + } + case 'TIMES': { + tokens.push(T.Times); + break; + } + case 'DIVIDED': { + reader.expect('BY'); + tokens.push(T.DividedBy); + break; + } + case 'MODULO': { + tokens.push(T.Modulo); + break; + } + case 'BEATS': { + tokens.push(T.Beats); + break; + } + case 'SMALLER': { + reader.expect('THAN'); + tokens.push(T.SmallerThan); + break; + } + case 'SHOCKING': { + reader.expect('DEVELOPMENT'); + tokens.push(T.ShockingDevelopment); + break; + } + case 'PLEASE': { + reader.expect('LIKE'); + reader.expect('AND'); + reader.expect('SUBSCRIBE'); + tokens.push(T.PleaseLikeAndSubscribe); + break; + } + case 'TOTALLY': { + reader.expect('RIGHT'); + tokens.push(T.TotallyRight); + break; + } + case 'COMPLETELY': { + reader.expect('WRONG'); + tokens.push(T.CompletelyWrong); + break; + } + case '(': { + tokens.push(T.LParen); + break; + } + case ')': { + tokens.push(T.RParen); + break; + } + case ',': { + tokens.push(T.Comma); + break; + } + default: { + if (!isNaN(parseFloat(next))) { + // number literal + tokens.push(parseFloat(next)); + } else { + // string or varname + tokens.push(next); + } + } + } + } + return tokens; +} + +/* parser */ + +const N = { + NumberLiteral: Symbol('NumberLiteral'), + StringLiteral: Symbol('StringLiteral'), + BoolLiteral: Symbol('BoolLiteral'), + FnDecl: Symbol('FnDecl'), + FnCall: Symbol('FnCall'), + Ident: Symbol('Ident'), + Assignment: Symbol('Assignment'), + BinaryOp: Symbol('BinaryOp'), + IfExpr: Symbol('IfExpr'), + ExprGroup: Symbol('ExprGroup'), + ReturnExpr: Symbol('ReturnExpr'), + ProgEndExpr: Symbol('ProgEndExpr'), + PrintExpr: Symbol('PrintExpr'), + InputExpr: Symbol('InputExpr'), +} + +export class Parser { + constructor(tokens) { + this.tokens = new Reader(tokens, []); + } + /** + * Atom + * Ident + * NumberLiteral + * StringLiteral + * BoolLiteral + * FnCall + * FnDecl + * ExprGroup + * + * Expression: + * (begins with atom) + * BinaryOp + * Atom + * (begins with keyword) + * IfExpr + * Assignment + * ReturnExpr + * ProgEndExpr + * PrintExpr + * InputExpr + * + */ + parse() { + const nodes = []; + while (this.tokens.hasNext()) { + nodes.push(this.expr()); + } + + if (nodes[nodes.length - 1].type !== N.ProgEndExpr) { + throw new Error('Parsing error: A Tabloid program MUST end with PLEASE LIKE AND SUBSCRIBE'); + } + + return nodes; + } + expectIdentString() { + const ident = this.tokens.next(); + if (typeof ident === 'string' && !ident.startsWith('"')) { + return ident; + } + throw new Error(`Parsing error: expected identifier, got ${ident.toString()}`); + } + atom() { + const next = this.tokens.next(); + if (typeof next === 'number') { + return { + type: N.NumberLiteral, + val: next, + } + } else if (typeof next === 'string') { + if (next.startsWith('"')) { + return { + type: N.StringLiteral, + val: next.substr(1), + } + } + const ident = { + type: N.Ident, + val: next, + } + if (this.tokens.peek() === T.Of) { + return this.fnCall(ident); + } + return ident; + } else if (next === T.TotallyRight) { + return { + type: N.BoolLiteral, + val: true, + } + } else if (next === T.CompletelyWrong) { + return { + type: N.BoolLiteral, + val: false, + } + } else if (next === T.DiscoverHowTo) { + // fn literal + const fnName = this.tokens.next(); + if (this.tokens.peek(T.With)) { + this.tokens.next(); // with + // with args + const args = [this.expectIdentString()]; + while (this.tokens.peek() === T.Comma) { + this.tokens.next(); // comma + args.push(this.expectIdentString()); + } + return { + type: N.FnDecl, + name: fnName, + args: args, + body: this.expr(), + } + } else { + return { + type: N.FnDecl, + name: fnName, + args: [], + body: this.expr(), + } + } + } else if (next === T.RumorHasIt) { + // block + const exprs = []; + while (this.tokens.hasNext() && this.tokens.peek() !== T.EndOfStory) { + exprs.push(this.expr()); + } + this.tokens.expect(T.EndOfStory); + return { + type: N.ExprGroup, + exprs: exprs, + }; + } else if (next === T.LParen) { + // block, but guarded by parens, for binary exprs + const exprs = []; + while (this.tokens.hasNext() && this.tokens.peek() !== T.RParen) { + exprs.push(this.expr()); + } + this.tokens.expect(T.RParen); + return { + type: N.ExprGroup, + exprs: exprs, + }; + } + + throw new Error(`Parsing error: expected ident, literal, or block, got ${ + next.toString() + } before ${this.tokens.peek().toString()}`); + } + expr() { + const next = this.tokens.next(); + if (next === T.WhatIf) { + // if expr + const cond = this.expr(); + const ifBody = this.expr(); + + let elseBody = null; + if (this.tokens.peek() == T.LiesBang) { + this.tokens.next(); // LiesBang + elseBody = this.expr(); + } + return { + type: N.IfExpr, + cond: cond, + ifBody: ifBody, + elseBody: elseBody, + } + } else if (next === T.ExpertsClaim) { + // assignment + const name = this.expectIdentString(); + this.tokens.expect(T.ToBe); + const val = this.expr(); + return { + type: N.Assignment, + name, + val, + } + } else if (next === T.ShockingDevelopment) { + // return + return { + type: N.ReturnExpr, + val: this.expr(), + } + } else if (next === T.PleaseLikeAndSubscribe) { + // prog end + return { + type: N.ProgEndExpr, + } + } else if (next === T.YouWontWantToMiss) { + // print expr + return { + type: N.PrintExpr, + val: this.expr(), + } + } else if (next === T.LatestNewsOn) { + // input expr + return { + type: N.InputExpr, + val: this.expr(), + } + } + + this.tokens.backstep(); + const atom = this.atom(); + if (BINARY_OPS.includes(this.tokens.peek())) { + // infix binary ops + const left = atom; + const op = this.tokens.next(); + const right = this.atom(); + return { + type: N.BinaryOp, + op, + left, + right, + } + } + + return atom; + } + fnCall(fnNode) { + this.tokens.expect(T.Of); + const args = [this.expr()]; + while (this.tokens.peek() === T.Comma) { + this.tokens.next(); // comma + args.push(this.expr()); + } + return { + type: N.FnCall, + fn: fnNode, + args: args, + } + } +} + +/* executor (tree walk) */ + +/** + * Abused (slightly) to easily return values upstack + */ +class ReturnError { + constructor(value) { + this.value = value; + } + unwrap() { + return this.value; + } +} + +export class Environment { + constructor(runtime) { + /** + * Runtime contains the following functions: + * - print(s) + * - input(s) + */ + this.runtime = runtime; + this.scopes = [{}]; // begin with global scope + } + run(nodes) { + let rv; + for (const node of nodes) { + rv = this.eval(node); + } + return rv; + } + eval(node) { + const scope = this.scopes[this.scopes.length - 1]; + + switch (node.type) { + case N.NumberLiteral: + case N.StringLiteral: + case N.BoolLiteral: + return node.val; + case N.FnDecl: { + const fnValue = { + node: node, + closure: { ...scope } + }; + scope[node.name] = fnValue; + return fnValue; + } + case N.FnCall: { + const fn = this.eval(node.fn); + const args = node.args.map(arg => this.eval(arg)); + + const calleeScope = {}; + fn.node.args.forEach((argName, i) => { + calleeScope[argName] = args[i]; + }); + + this.scopes.push(fn.closure); + this.scopes.push(calleeScope); + let rv; + try { + this.eval(fn.node.body); + } catch (maybeReturnErr) { + if (maybeReturnErr instanceof ReturnError) { + rv = maybeReturnErr.unwrap(); + } else { + throw maybeReturnErr; + } + } + this.scopes.pop(); + this.scopes.pop(); + + return rv; + } + case N.Ident: { + let i = this.scopes.length - 1; + while (i >= 0) { + if (node.val in this.scopes[i]) { + return this.scopes[i][node.val]; + } + i --; + } + throw new Error(`Runtime error: Undefined variable "${node.val}"`); + } + case N.Assignment: { + scope[node.name] = this.eval(node.val); + return scope[node.name]; + } + case N.BinaryOp: { + const left = this.eval(node.left); + const right = this.eval(node.right); + switch (node.op) { + case T.IsActually: + return left === right; + case T.And: + return left && right; + case T.Or: + return left || right; + case T.Plus: + return left + right; + case T.Minus: + return left - right; + case T.Times: + return left * right; + case T.DividedBy: + return left / right; + case T.Modulo: + return left % right; + case T.Beats: + return left > right; + case T.SmallerThan: + return left < right; + default: + throw new Error(`Runtime error: Unknown binary op ${node.op.toString()}`); + } + } + case N.IfExpr: { + if (this.eval(node.cond)) { + return this.eval(node.ifBody); + } + if (node.elseBody != null) { + return this.eval(node.elseBody); + } + } + case N.ExprGroup: { + if (!node.exprs.length) { + throw new Error('Runtime error: Empty expression group with no expressions'); + } + + let rv; + for (const expr of node.exprs) { + rv = this.eval(expr); + } + return rv; + } + case N.ReturnExpr: { + const rv = this.eval(node.val); + throw new ReturnError(rv); + } + case N.ProgEndExpr: { + // do nothing + break; + } + case N.PrintExpr: { + let val = this.eval(node.val); + // shim for boolean to-string's + if (val === true) { + val = 'TOTALLY RIGHT'; + } else if (val === false) { + val = 'COMPLETELY WRONG'; + } + this.runtime.print(val); + return val; + } + case N.InputExpr: { + let val = this.eval(node.val); + // shim for boolean to-string's + if (val === true) { + val = 'TOTALLY RIGHT'; + } else if (val === false) { + val = 'COMPLETELY WRONG'; + } + return this.runtime.input(val); + } + default: + console.log(JSON.stringify(node, null, 2)); + throw new Error(`Runtime error: Unknown AST Node of type ${ + node.type.toString() + }:\n${JSON.stringify(node, null, 2)}`); + } + } +} diff --git a/html/toys/turing/css/styles.css b/html/toys/turing/css/styles.css new file mode 100644 index 0000000..2407cb4 --- /dev/null +++ b/html/toys/turing/css/styles.css @@ -0,0 +1,66 @@ +.tape { + display: flex; + gap: 0.25rem; + padding: var(--space-md); + overflow-x: auto; + background: var(--surface-alt); + border: var(--border-width) solid var(--border); + box-shadow: inset 1px 1px 0 var(--border-light), + inset -1px -1px 0 var(--border-dark); + min-height: 5rem; +} + +.cell { + position: relative; + width: 3.25rem; + padding: var(--space-xs); + background: var(--surface); + border: 2px solid var(--border-dark); + box-shadow: inset 1px 1px 0 var(--border-light), + inset -1px -1px 0 var(--border-dark); + flex-shrink: 0; +} + +.cell input { + width: 100%; + text-align: center; + border: none; + background: transparent; + font-family: var(--font-mono); + color: var(--fg); + font-size: 0.875rem; + padding: 0.25rem; +} + +.cell input:focus { + outline: 1px dotted var(--fg); + outline-offset: 2px; +} + +.cell.active { + border-color: var(--primary); + background: color-mix(in srgb, var(--primary) 15%, var(--surface)); +} + +.cell.active::after { + content: '▲'; + position: absolute; + bottom: -1.5rem; + left: 50%; + transform: translateX(-50%); + color: var(--primary); + font-size: 1rem; + line-height: 1; +} + +#state-text { + font-family: var(--font-mono); + font-weight: 700; + margin-top: var(--space-md); +} + +.controls { + display: flex; + gap: var(--space-sm); + flex-wrap: wrap; +} diff --git a/html/toys/turing/index.html b/html/toys/turing/index.html new file mode 100644 index 0000000..838c5bf --- /dev/null +++ b/html/toys/turing/index.html @@ -0,0 +1,42 @@ + + + + + + + + Turing Machine + + +
+
+

Turing Machine

+ +

State: _, Step: 0

+ +
+ +
+ + + + +
+ +
+
+ + +
+ +
+
+
+
+ + + + + + diff --git a/html/toys/turing/js/machine.js b/html/toys/turing/js/machine.js new file mode 100644 index 0000000..6af4be6 --- /dev/null +++ b/html/toys/turing/js/machine.js @@ -0,0 +1,75 @@ +export class TuringMachine { + constructor({ + tape, + rules, + startState, + acceptStates = [], + rejectStates = [] + }) { + this.tape = tape; + this.rules = rules; + this.state = startState; + this.acceptStates = new Set(acceptStates); + this.rejectStates = new Set(rejectStates); + this.iteration = 0; + } + + step() { + if (this.isHalted()) { + return false; + } + + const currentSymbol = this.tape.readHead(); + const ruleKey = this.getRuleKey(this.state, currentSymbol); + if (!this.rules.has(ruleKey)) { + return false; + } + + const { nextState, writeSymbol, direction } = this.rules.get(ruleKey); + this.tape.writeHead(writeSymbol); + + if (direction === "R") { + this.tape.moveRight(); + } else if (direction === "L") { + this.tape.moveLeft(); + } + + this.state = nextState; + this.iteration += 1; + return !this.isHalted(); + } + + canStep() { + if (this.isHalted()) { + return false; + } + + const currentSymbol = this.tape.readHead(); + const ruleKey = this.getRuleKey(this.state, currentSymbol); + return this.rules.has(ruleKey); + } + + getRuleKey(state, symbol) { + return `${state}:${symbol}`; + } + + isAccepting() { + return this.acceptStates.has(this.state); + } + + isRejecting() { + return this.rejectStates.has(this.state); + } + + isHalted() { + return this.isAccepting() || this.isRejecting(); + } + + getStateStatus() { + return `State: ${this.state}, Step: ${this.iteration}`; + } + + getState() { + return this.state; + } +} diff --git a/html/toys/turing/js/main.js b/html/toys/turing/js/main.js new file mode 100644 index 0000000..c9f37fc --- /dev/null +++ b/html/toys/turing/js/main.js @@ -0,0 +1,4 @@ +import { TuringMachineUI } from "./ui.js"; +document.addEventListener("DOMContentLoaded", (event) => { + new TuringMachineUI(); +}); diff --git a/html/toys/turing/js/parser.js b/html/toys/turing/js/parser.js new file mode 100644 index 0000000..dd65613 --- /dev/null +++ b/html/toys/turing/js/parser.js @@ -0,0 +1,141 @@ +export function parseInstructionSet(code) { + const lines = code.split("\n"); + const instructions = []; + const config = { + startState: null, + acceptStates: new Set(), + rejectStates: new Set() + }; + + lines.forEach((line, lineIndex) => { + const withoutComments = line.replace(/\/\/.*$/, "").trim(); + if (!withoutComments) { + return; + } + + if (withoutComments.startsWith("#")) { + applyDirective(withoutComments.slice(1).trim(), config, lineIndex + 1); + return; + } + + const parts = withoutComments.split(/\s+/).filter(Boolean); + if (parts.length !== 5) { + throw new Error(`Invalid instruction on line ${lineIndex + 1}: expected 5 parts, received ${parts.length}`); + } + + const [fromState, readSymbol, writeSymbol, direction, toState] = parts; + if (!config.startState) { + config.startState = fromState; + } + + instructions.push({ fromState, readSymbol, writeSymbol, direction, toState, line: lineIndex + 1 }); + }); + + if (!instructions.length) { + throw new Error("No instructions provided"); + } + + const { acceptStates, rejectStates } = deriveHaltingStates(instructions, config); + const rules = buildRuleMap(instructions); + + return { + rules, + startState: config.startState ?? instructions[0].fromState, + acceptStates, + rejectStates + }; +} + +function applyDirective(directiveLine, config, lineNumber) { + if (!directiveLine) { + return; + } + + const [keyword, ...values] = directiveLine.split(/\s+/).filter(Boolean); + if (!keyword) { + return; + } + + switch (keyword.toLowerCase()) { + case "start": { + if (values.length !== 1) { + throw new Error(`#start on line ${lineNumber} must provide exactly one state`); + } + config.startState = values[0]; + break; + } + case "accept": + case "accepts": + case "accepting": { + if (!values.length) { + throw new Error(`#${keyword} on line ${lineNumber} must include at least one state`); + } + values.forEach((value) => config.acceptStates.add(value)); + break; + } + case "reject": + case "rejects": + case "rejecting": { + if (!values.length) { + throw new Error(`#${keyword} on line ${lineNumber} must include at least one state`); + } + values.forEach((value) => config.rejectStates.add(value)); + break; + } + default: + throw new Error(`Unknown directive '#${keyword}' on line ${lineNumber}`); + } +} + +function deriveHaltingStates(instructions, config) { + const fromStates = new Set(); + const toStates = new Set(); + const allStates = new Set(); + + instructions.forEach(({ fromState, toState }) => { + fromStates.add(fromState); + toStates.add(toState); + allStates.add(fromState); + allStates.add(toState); + }); + + // Remove any overlap so rejects always win + config.rejectStates.forEach((state) => config.acceptStates.delete(state)); + + if (!config.acceptStates.size) { + for (const state of allStates) { + if (!fromStates.has(state) && toStates.has(state) && !config.rejectStates.has(state)) { + config.acceptStates.add(state); + } + } + } + + return { + acceptStates: Array.from(config.acceptStates), + rejectStates: Array.from(config.rejectStates) + }; +} + +function buildRuleMap(instructions) { + const rules = new Map(); + + instructions.forEach(({ fromState, readSymbol, writeSymbol, direction, toState, line }) => { + const dir = direction.toUpperCase(); + if (!["L", "R", "S"].includes(dir)) { + throw new Error(`Invalid direction '${direction}' on line ${line}. Use L, R, or S.`); + } + + const key = `${fromState}:${readSymbol}`; + if (rules.has(key)) { + throw new Error(`Duplicate rule for state '${fromState}' reading '${readSymbol}' (line ${line})`); + } + + rules.set(key, { + nextState: toState, + writeSymbol, + direction: dir + }); + }); + + return rules; +} diff --git a/html/toys/turing/js/samples.js b/html/toys/turing/js/samples.js new file mode 100644 index 0000000..0998ee4 --- /dev/null +++ b/html/toys/turing/js/samples.js @@ -0,0 +1,88 @@ +// Example programs with initial tape states +export const EXAMPLE_PROGRAMS = [ + { + name: "Replace two B's", + code: `#start q0 +#accept acc +#reject rej + +q0 B 1 R q1 +q1 1 1 R q1 +q1 B 1 R acc`, + initialTape: "" + }, + { + name: "Binary equality checker", + code: `// https://stackoverflow.com/questions/59045832 + +#start q0 +#accept acc +#reject rej + +q0 0 X R q1 +q0 1 X R q2 +q0 = = R q7 +q1 0 0 R q1 +q1 1 1 R q1 +q1 = = R q3 +q2 0 0 R q2 +q2 1 1 R q2 +q2 = = R q4 +q3 X X R q3 +q3 0 X L q5 +q3 1 1 L rej +q3 B B L rej +q4 X X R q4 +q4 0 0 L rej +q4 B B L rej +q4 1 X L q5 +q5 X X L q5 +q5 = = L q6 +q6 0 0 L q6 +q6 1 1 L q6 +q6 X X R q0 +q7 X X R q7 +q7 B B L q8 +q7 0 0 L rej +q7 1 1 L rej +q8 X X L q8 +q8 0 0 L q8 +q8 1 1 L q8 +q8 = = L acc`, + initialTape: "1011=1011" + }, + { + name: "Binary addition", + code: `// https://stackoverflow.com/questions/59045832 + +#start q0 +#accept acc +#reject rej + +q0 B B R q0 +q0 0 0 R q0 +q0 1 1 R q0 +q0 + + R q1 +q1 0 0 R q1 +q1 1 1 R q1 +q1 B B L q2 +q2 0 1 L q2 +q2 1 0 L q3 +q2 + + R q5 +q3 0 0 L q3 +q3 1 1 L q3 +q3 + + L q4 +q4 0 1 R q0 +q4 1 0 L q4 +q4 B 1 R q0 +q5 1 B R q5 +q5 B B R q6 +q6 B B L q6 +q6 + B L q7 +q7 0 0 L q7 +q7 1 1 L q7 +q7 B B R acc +`, + initialTape: "101+110" + } +]; diff --git a/html/toys/turing/js/tape.js b/html/toys/turing/js/tape.js new file mode 100644 index 0000000..fd05366 --- /dev/null +++ b/html/toys/turing/js/tape.js @@ -0,0 +1,103 @@ +const ESCAPE_REGEX = /[.*+?^${}()|[\]\\]/g; + +function escapeForRegex(value) { + return value.replace(ESCAPE_REGEX, "\\$&"); +} + +export class Tape { + constructor({ + initialContent = "", + blankSymbol = "B", + minLength = 50, + padding = 40 + } = {}) { + this.blankSymbol = blankSymbol; + this.minLength = minLength; + this.padding = padding; + this.reset(initialContent); + } + + reset(initialContent = "") { + const targetLength = Math.max(this.minLength, initialContent.length + this.padding); + this.cells = Array(targetLength).fill(this.blankSymbol); + const startOffset = Math.floor((targetLength - initialContent.length) / 2); + for (let i = 0; i < initialContent.length; i++) { + this.cells[startOffset + i] = initialContent[i]; + } + this.headIndex = startOffset; + } + + get length() { + return this.cells.length; + } + + getHeadIndex() { + return this.headIndex; + } + + readHead() { + return this.getCell(this.headIndex); + } + + writeHead(symbol) { + this.cells[this.headIndex] = symbol || this.blankSymbol; + } + + readAt(index) { + return this.getCell(index); + } + + writeAt(index, symbol) { + if (index < 0) { + throw new Error("Cannot write to a negative tape index"); + } + this.ensureRightCapacity(index); + this.cells[index] = symbol || this.blankSymbol; + } + + setHead(index) { + if (index < 0) { + throw new Error("Head index cannot be negative"); + } + this.ensureRightCapacity(index); + this.headIndex = index; + } + + moveLeft() { + if (this.headIndex === 0) { + this.cells.unshift(this.blankSymbol); + } else { + this.headIndex -= 1; + return; + } + } + + moveRight() { + this.headIndex += 1; + if (this.headIndex >= this.cells.length) { + this.cells.push(this.blankSymbol); + } + } + + getCell(index) { + if (index < 0 || index >= this.cells.length) { + return this.blankSymbol; + } + return this.cells[index]; + } + + ensureRightCapacity(index) { + while (index >= this.cells.length) { + this.cells.push(this.blankSymbol); + } + } + + getContents({ trimTrailing = true } = {}) { + let snapshot = this.cells.join(""); + if (trimTrailing) { + const regex = new RegExp(`${escapeForRegex(this.blankSymbol)}+$`, "g"); + snapshot = snapshot.replace(regex, ""); + } + return snapshot; + } +} diff --git a/html/toys/turing/js/ui.js b/html/toys/turing/js/ui.js new file mode 100644 index 0000000..ae01a4b --- /dev/null +++ b/html/toys/turing/js/ui.js @@ -0,0 +1,386 @@ +import { Tape } from "./tape.js"; +import { TuringMachine } from "./machine.js"; +import { parseInstructionSet } from "./parser.js"; +import { EXAMPLE_PROGRAMS } from "./samples.js"; + +const SCROLL_THRESHOLD = 3; + +export class TuringMachineUI { + constructor() { + this.machine = null; + this.editor = null; + this.intervalId = null; + this.isRunning = false; + this.initialTapeSize = 50; + this.blankSymbol = "B"; + this.currentProgramIndex = 0; + this.loadedFromURL = false; + this.urlTapeState = ""; + this.lastScrollPosition = 0; + this.renderedTapeLength = 0; + this.simulationInterval = 200; + + this.elements = { + tape: document.getElementById("tape"), + stateText: document.getElementById("state-text"), + runBtn: document.getElementById("run-btn"), + stepBtn: document.getElementById("step-btn"), + resetBtn: document.getElementById("reset-btn"), + copyBtn: document.getElementById("copy-btn"), + programSelect: document.getElementById("program-select") + }; + + this.init(); + } + + init() { + this.setupEditor(); + this.populateProgramSelect(); + this.setupEventListeners(); + this.loadFromURL(); + } + + setupEditor() { + this.editor = adelieEditor.init("#code-editor", { + language: "javascript" + }); + + this.editor.dom.addEventListener("input", () => { + this.machine = null; + }); + } + + populateProgramSelect() { + if (!this.elements.programSelect) { + return; + } + + this.elements.programSelect.innerHTML = ""; + EXAMPLE_PROGRAMS.forEach((program, index) => { + const option = document.createElement("option"); + option.value = index.toString(); + option.textContent = program.name; + this.elements.programSelect.appendChild(option); + }); + } + + setupEventListeners() { + this.elements.runBtn.addEventListener("click", () => this.toggleRun()); + this.elements.stepBtn.addEventListener("click", () => this.step()); + this.elements.resetBtn.addEventListener("click", () => this.reset()); + this.elements.copyBtn.addEventListener("click", () => this.copyState()); + + this.elements.programSelect.addEventListener("change", (event) => { + this.loadProgram(parseInt(event.target.value, 10)); + }); + + document.addEventListener("keydown", (event) => { + if (event.ctrlKey && event.key === "Enter") { + event.preventDefault(); + this.reset(); + setTimeout(() => this.run(), 0); + } + }); + + this.elements.tape.addEventListener("input", (event) => this.handleTapeInput(event)); + this.elements.tape.addEventListener("focusin", () => { + if (this.isRunning) { + this.pause(); + } + }); + } + + handleTapeInput(event) { + if (!this.machine) { + return; + } + const target = event.target; + if (!(target instanceof HTMLInputElement)) { + return; + } + + const parentCell = target.closest(".cell"); + if (!parentCell) { + return; + } + + const index = Number(parentCell.dataset.index); + if (Number.isNaN(index)) { + return; + } + + const sanitized = (target.value || this.blankSymbol).slice(0, 1); + target.value = sanitized; + this.machine.tape.writeAt(index, sanitized || this.blankSymbol); + } + + loadFromURL() { + const urlParams = new URLSearchParams(window.location.search); + const startState = urlParams.get("start") ?? ""; + const instructions = urlParams.get("instructions"); + + if (!instructions) { + this.loadProgram(0); + return; + } + + try { + const code = atob(instructions); + this.setEditorContent(code); + this.loadedFromURL = true; + this.urlTapeState = startState; + this.compile(this.urlTapeState); + } catch (error) { + console.error("Failed to load from URL", error); + this.loadedFromURL = false; + this.loadProgram(0); + } + } + + setEditorContent(content) { + const length = this.editor.state.doc.toString().length; + this.editor.dispatch({ + changes: { from: 0, to: length, insert: content } + }); + } + + getEditorContent() { + return this.editor.state.doc.toString(); + } + + loadProgram(index = 0) { + const program = EXAMPLE_PROGRAMS[index]; + if (!program) { + return; + } + + this.loadedFromURL = false; + this.currentProgramIndex = index; + this.elements.programSelect.value = index.toString(); + this.setEditorContent(program.code); + + try { + this.compile(program.initialTape); + } catch (error) { + this.elements.stateText.innerHTML = `Error: ${error.message}`; + } + } + + compile(initialTape = "") { + this.pause(); + + const code = this.getEditorContent(); + const instructionSet = parseInstructionSet(code); + const tapeSeed = initialTape || EXAMPLE_PROGRAMS[this.currentProgramIndex]?.initialTape || ""; + + const tape = new Tape({ + initialContent: tapeSeed, + blankSymbol: this.blankSymbol, + minLength: Math.max(this.initialTapeSize, tapeSeed.length + 40) + }); + + this.machine = new TuringMachine({ + tape, + rules: instructionSet.rules, + startState: instructionSet.startState, + acceptStates: instructionSet.acceptStates, + rejectStates: instructionSet.rejectStates + }); + + this.renderedTapeLength = 0; + this.lastScrollPosition = tape.getHeadIndex(); + this.updateStateDisplay(); + this.updateTape(true); + } + + reset() { + this.pause(); + + if (this.loadedFromURL) { + try { + this.compile(this.urlTapeState); + return; + } catch (error) { + this.elements.stateText.innerHTML = `Error: ${error.message}`; + } + } + + this.loadProgram(this.currentProgramIndex); + } + + step() { + if (!this.machine) { + try { + this.compile(); + } catch (error) { + this.elements.stateText.innerHTML = `Error: ${error.message}`; + return; + } + } + + const canContinue = this.machine.step(); + this.updateStateDisplay(); + this.updateTape(); + + if (!canContinue) { + this.pause(); + } + } + + toggleRun() { + if (this.isRunning) { + this.pause(); + } else { + this.run(); + } + } + + run() { + if (this.isRunning) { + return; + } + + if (!this.machine) { + try { + this.compile(); + } catch (error) { + this.elements.stateText.innerHTML = `Error: ${error.message}`; + return; + } + } + + this.isRunning = true; + this.elements.runBtn.textContent = "⏸ Pause"; + this.elements.runBtn.classList.remove("primary"); + + this.intervalId = setInterval(() => { + const canContinue = this.machine.step(); + this.updateStateDisplay(); + this.updateTape(); + if (!canContinue) { + this.pause(); + } + }, this.simulationInterval); + } + + pause() { + if (!this.isRunning) { + return; + } + + this.isRunning = false; + clearInterval(this.intervalId); + this.intervalId = null; + + this.elements.runBtn.textContent = "▶ Run (Ctrl + Enter)"; + this.elements.runBtn.classList.add("primary"); + } + + updateStateDisplay() { + if (!this.machine) { + this.elements.stateText.textContent = "State: _, Step: 0"; + return; + } + + const status = this.machine.getStateStatus(); + + if (!this.machine.canStep()) { + if (this.machine.isAccepting()) { + this.elements.stateText.innerHTML = `Accept(${status})`; + } else if (this.machine.isRejecting()) { + this.elements.stateText.innerHTML = `Reject(${status})`; + } else { + this.elements.stateText.innerHTML = `Halt(${status})`; + } + return; + } + + this.elements.stateText.textContent = status; + } + + updateTape(forceRender = false) { + if (!this.machine) { + return; + } + + const tape = this.machine.tape; + if (forceRender || this.renderedTapeLength !== tape.length) { + this.renderTape(); + } + + const cells = this.elements.tape.querySelectorAll(".cell"); + const headIndex = tape.getHeadIndex(); + + cells.forEach((cell, index) => { + cell.dataset.index = index.toString(); + const input = cell.querySelector("input"); + const value = tape.getCell(index); + if (input.value !== value) { + input.value = value; + } + + if (index === headIndex) { + cell.classList.add("active"); + this.maybeScrollIntoView(cell, headIndex, forceRender); + } else { + cell.classList.remove("active"); + } + }); + } + + renderTape() { + const fragment = document.createDocumentFragment(); + const tape = this.machine.tape; + + for (let i = 0; i < tape.length; i++) { + fragment.appendChild(this.createCell(i, tape.getCell(i))); + } + + this.elements.tape.innerHTML = ""; + this.elements.tape.appendChild(fragment); + this.renderedTapeLength = tape.length; + } + + createCell(index, value) { + const cell = document.createElement("div"); + cell.classList.add("cell"); + cell.dataset.index = index.toString(); + + const input = document.createElement("input"); + input.type = "text"; + input.maxLength = 1; + input.value = value; + + cell.appendChild(input); + return cell; + } + + maybeScrollIntoView(cell, headIndex, forceImmediate = false) { + if (forceImmediate) { + cell.scrollIntoView({ behavior: "auto", block: "nearest", inline: "center" }); + this.lastScrollPosition = headIndex; + return; + } + + if (Math.abs(headIndex - this.lastScrollPosition) < SCROLL_THRESHOLD) { + return; + } + + cell.scrollIntoView({ behavior: "smooth", block: "nearest", inline: "center" }); + this.lastScrollPosition = headIndex; + } + + copyState() { + if (!this.machine) { + return; + } + + const tapeState = this.machine.tape.getContents(); + const instructions = btoa(this.getEditorContent()); + const url = `${window.location.href.split("?")[0]}?start=${tapeState}&instructions=${instructions}`; + + navigator.clipboard.writeText(url) + .then(() => alert("State copied to clipboard!")) + .catch(() => alert("Failed to copy to clipboard")); + } +} diff --git a/package-lock.json b/package-lock.json deleted file mode 100644 index cecfa92..0000000 --- a/package-lock.json +++ /dev/null @@ -1,1730 +0,0 @@ -{ - "name": "lizdotcoffee", - "version": "1.0.0", - "lockfileVersion": 3, - "requires": true, - "packages": { - "": { - "name": "lizdotcoffee", - "version": "1.0.0", - "dependencies": { - "markdown-it": "^14.1.0", - "outdent": "^0.8.0" - }, - "devDependencies": { - "@11ty/eleventy": "^3.1.2", - "@11ty/eleventy-plugin-rss": "^2.0.4" - } - }, - "node_modules/@11ty/dependency-tree": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/@11ty/dependency-tree/-/dependency-tree-4.0.1.tgz", - "integrity": "sha512-6EPI9ZkGU4BX2KNZpWlf4WdV3vrmIWQpn//nAXicTzdPubI3jZlmFdqEv0Yj5M7oavRUGNzw9GbV9cBxhulZWw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@11ty/eleventy-utils": "^2.0.1" - } - }, - "node_modules/@11ty/dependency-tree-esm": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/@11ty/dependency-tree-esm/-/dependency-tree-esm-2.0.3.tgz", - "integrity": "sha512-zlcfZq5WC9ksnLeLBROshNeE9P1jvOAPvUY9787cND/7SKf+QM7RGjNyF5taoCQww12B6Dx0Y9cwXsNbRipzlA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@11ty/eleventy-utils": "^2.0.7", - "acorn": "^8.15.0", - "dependency-graph": "^1.0.0", - "normalize-path": "^3.0.0" - } - }, - "node_modules/@11ty/eleventy": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/@11ty/eleventy/-/eleventy-3.1.2.tgz", - "integrity": "sha512-IcsDlbXnBf8cHzbM1YBv3JcTyLB35EK88QexmVyFdVJVgUU6bh9g687rpxryJirHzo06PuwnYaEEdVZQfIgRGg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@11ty/dependency-tree": "^4.0.0", - "@11ty/dependency-tree-esm": "^2.0.0", - "@11ty/eleventy-dev-server": "^2.0.8", - "@11ty/eleventy-plugin-bundle": "^3.0.6", - "@11ty/eleventy-utils": "^2.0.7", - "@11ty/lodash-custom": "^4.17.21", - "@11ty/posthtml-urls": "^1.0.1", - "@11ty/recursive-copy": "^4.0.2", - "@sindresorhus/slugify": "^2.2.1", - "bcp-47-normalize": "^2.3.0", - "chokidar": "^3.6.0", - "debug": "^4.4.1", - "dependency-graph": "^1.0.0", - "entities": "^6.0.1", - "filesize": "^10.1.6", - "gray-matter": "^4.0.3", - "iso-639-1": "^3.1.5", - "js-yaml": "^4.1.0", - "kleur": "^4.1.5", - "liquidjs": "^10.21.1", - "luxon": "^3.6.1", - "markdown-it": "^14.1.0", - "minimist": "^1.2.8", - "moo": "^0.5.2", - "node-retrieve-globals": "^6.0.1", - "nunjucks": "^3.2.4", - "picomatch": "^4.0.2", - "please-upgrade-node": "^3.2.0", - "posthtml": "^0.16.6", - "posthtml-match-helper": "^2.0.3", - "semver": "^7.7.2", - "slugify": "^1.6.6", - "tinyglobby": "^0.2.14" - }, - "bin": { - "eleventy": "cmd.cjs" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/11ty" - } - }, - "node_modules/@11ty/eleventy-dev-server": { - "version": "2.0.8", - "resolved": "https://registry.npmjs.org/@11ty/eleventy-dev-server/-/eleventy-dev-server-2.0.8.tgz", - "integrity": "sha512-15oC5M1DQlCaOMUq4limKRYmWiGecDaGwryr7fTE/oM9Ix8siqMvWi+I8VjsfrGr+iViDvWcH/TVI6D12d93mA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@11ty/eleventy-utils": "^2.0.1", - "chokidar": "^3.6.0", - "debug": "^4.4.0", - "finalhandler": "^1.3.1", - "mime": "^3.0.0", - "minimist": "^1.2.8", - "morphdom": "^2.7.4", - "please-upgrade-node": "^3.2.0", - "send": "^1.1.0", - "ssri": "^11.0.0", - "urlpattern-polyfill": "^10.0.0", - "ws": "^8.18.1" - }, - "bin": { - "eleventy-dev-server": "cmd.js" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/11ty" - } - }, - "node_modules/@11ty/eleventy-plugin-bundle": { - "version": "3.0.7", - "resolved": "https://registry.npmjs.org/@11ty/eleventy-plugin-bundle/-/eleventy-plugin-bundle-3.0.7.tgz", - "integrity": "sha512-QK1tRFBhQdZASnYU8GMzpTdsMMFLVAkuU0gVVILqNyp09xJJZb81kAS3AFrNrwBCsgLxTdWHJ8N64+OTTsoKkA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@11ty/eleventy-utils": "^2.0.2", - "debug": "^4.4.0", - "posthtml-match-helper": "^2.0.3" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/11ty" - } - }, - "node_modules/@11ty/eleventy-plugin-rss": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/@11ty/eleventy-plugin-rss/-/eleventy-plugin-rss-2.0.4.tgz", - "integrity": "sha512-LF60sGVlxGTryQe3hTifuzrwF8R7XbrNsM2xfcDcNMSliLN4kmB+7zvoLRySRx0AQDjqhPTAeeeT0ra6/9zHUQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@11ty/eleventy-utils": "^2.0.0", - "@11ty/posthtml-urls": "^1.0.1", - "debug": "^4.4.0", - "posthtml": "^0.16.6" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/11ty" - } - }, - "node_modules/@11ty/eleventy-utils": { - "version": "2.0.7", - "resolved": "https://registry.npmjs.org/@11ty/eleventy-utils/-/eleventy-utils-2.0.7.tgz", - "integrity": "sha512-6QE+duqSQ0GY9rENXYb4iPR4AYGdrFpqnmi59tFp9VrleOl0QSh8VlBr2yd6dlhkdtj7904poZW5PvGr9cMiJQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/11ty" - } - }, - "node_modules/@11ty/lodash-custom": { - "version": "4.17.21", - "resolved": "https://registry.npmjs.org/@11ty/lodash-custom/-/lodash-custom-4.17.21.tgz", - "integrity": "sha512-Mqt6im1xpb1Ykn3nbcCovWXK3ggywRJa+IXIdoz4wIIK+cvozADH63lexcuPpGS/gJ6/m2JxyyXDyupkMr5DHw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=14" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/11ty" - } - }, - "node_modules/@11ty/posthtml-urls": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/@11ty/posthtml-urls/-/posthtml-urls-1.0.2.tgz", - "integrity": "sha512-0vaV3Wt0surZ+oS1VdKKe0axeeupuM+l7W/Z866WFQwF+dGg2Tc/nmhk/5l74/Y55P8KyImnLN9CdygNw2huHg==", - "dev": true, - "license": "MIT", - "dependencies": { - "evaluate-value": "^2.0.0", - "http-equiv-refresh": "^2.0.1", - "list-to-array": "^1.1.0", - "parse-srcset": "^1.0.2" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/@11ty/recursive-copy": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/@11ty/recursive-copy/-/recursive-copy-4.0.3.tgz", - "integrity": "sha512-SX48BTLEGX8T/OsKWORsHAAeiDsbFl79Oa/0Wg/mv/d27b7trCVZs7fMHvpSgDvZz/fZqx5rDk8+nx5oyT7xBw==", - "dev": true, - "license": "ISC", - "dependencies": { - "errno": "^1.0.0", - "junk": "^3.1.0", - "maximatch": "^0.1.0", - "slash": "^3.0.0" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/@sindresorhus/slugify": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/@sindresorhus/slugify/-/slugify-2.2.1.tgz", - "integrity": "sha512-MkngSCRZ8JdSOCHRaYd+D01XhvU3Hjy6MGl06zhOk614hp9EOAp5gIkBeQg7wtmxpitU6eAL4kdiRMcJa2dlrw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@sindresorhus/transliterate": "^1.0.0", - "escape-string-regexp": "^5.0.0" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/@sindresorhus/transliterate": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/@sindresorhus/transliterate/-/transliterate-1.6.0.tgz", - "integrity": "sha512-doH1gimEu3A46VX6aVxpHTeHrytJAG6HgdxntYnCFiIFHEM/ZGpG8KiZGBChchjQmG0XFIBL552kBTjVcMZXwQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "escape-string-regexp": "^5.0.0" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/a-sync-waterfall": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/a-sync-waterfall/-/a-sync-waterfall-1.0.1.tgz", - "integrity": "sha512-RYTOHHdWipFUliRFMCS4X2Yn2X8M87V/OpSqWzKKOGhzqyUxzyVmhHDH9sAvG+ZuQf/TAOFsLCpMw09I1ufUnA==", - "dev": true, - "license": "MIT" - }, - "node_modules/acorn": { - "version": "8.15.0", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", - "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", - "dev": true, - "license": "MIT", - "bin": { - "acorn": "bin/acorn" - }, - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/acorn-walk": { - "version": "8.3.4", - "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.4.tgz", - "integrity": "sha512-ueEepnujpqee2o5aIYnvHU6C0A42MNdsIDeqy5BydrkuC5R1ZuUFnm27EeFJGoEHJQgn3uleRvmTXaJgfXbt4g==", - "dev": true, - "license": "MIT", - "dependencies": { - "acorn": "^8.11.0" - }, - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/anymatch": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", - "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", - "dev": true, - "license": "ISC", - "dependencies": { - "normalize-path": "^3.0.0", - "picomatch": "^2.0.4" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/anymatch/node_modules/picomatch": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", - "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8.6" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" - } - }, - "node_modules/argparse": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", - "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", - "license": "Python-2.0" - }, - "node_modules/array-differ": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/array-differ/-/array-differ-1.0.0.tgz", - "integrity": "sha512-LeZY+DZDRnvP7eMuQ6LHfCzUGxAAIViUBliK24P3hWXL6y4SortgR6Nim6xrkfSLlmH0+k+9NYNwVC2s53ZrYQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/array-union": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/array-union/-/array-union-1.0.2.tgz", - "integrity": "sha512-Dxr6QJj/RdU/hCaBjOfxW+q6lyuVE6JFWIrAUpuOOhoJJoQ99cUn3igRaHVB5P9WrgFVN0FfArM3x0cueOU8ng==", - "dev": true, - "license": "MIT", - "dependencies": { - "array-uniq": "^1.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/array-uniq": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/array-uniq/-/array-uniq-1.0.3.tgz", - "integrity": "sha512-MNha4BWQ6JbwhFhj03YK552f7cb3AzoE8SzeljgChvL1dl3IcvggXVz1DilzySZkCja+CXuZbdW7yATchWn8/Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/arrify": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/arrify/-/arrify-1.0.1.tgz", - "integrity": "sha512-3CYzex9M9FGQjCGMGyi6/31c8GJbgb0qGyrx5HWxPd0aCwh4cB2YjMb2Xf9UuoogrMrlO9cTqnB5rI5GHZTcUA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/asap": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/asap/-/asap-2.0.6.tgz", - "integrity": "sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA==", - "dev": true, - "license": "MIT" - }, - "node_modules/balanced-match": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", - "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", - "dev": true, - "license": "MIT" - }, - "node_modules/bcp-47": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/bcp-47/-/bcp-47-2.1.0.tgz", - "integrity": "sha512-9IIS3UPrvIa1Ej+lVDdDwO7zLehjqsaByECw0bu2RRGP73jALm6FYbzI5gWbgHLvNdkvfXB5YrSbocZdOS0c0w==", - "dev": true, - "license": "MIT", - "dependencies": { - "is-alphabetical": "^2.0.0", - "is-alphanumerical": "^2.0.0", - "is-decimal": "^2.0.0" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, - "node_modules/bcp-47-match": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/bcp-47-match/-/bcp-47-match-2.0.3.tgz", - "integrity": "sha512-JtTezzbAibu8G0R9op9zb3vcWZd9JF6M0xOYGPn0fNCd7wOpRB1mU2mH9T8gaBGbAAyIIVgB2G7xG0GP98zMAQ==", - "dev": true, - "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, - "node_modules/bcp-47-normalize": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/bcp-47-normalize/-/bcp-47-normalize-2.3.0.tgz", - "integrity": "sha512-8I/wfzqQvttUFz7HVJgIZ7+dj3vUaIyIxYXaTRP1YWoSDfzt6TUmxaKZeuXR62qBmYr+nvuWINFRl6pZ5DlN4Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "bcp-47": "^2.0.0", - "bcp-47-match": "^2.0.0" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, - "node_modules/binary-extensions": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", - "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/brace-expansion": { - "version": "1.1.12", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", - "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", - "dev": true, - "license": "MIT", - "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "node_modules/braces": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", - "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", - "dev": true, - "license": "MIT", - "dependencies": { - "fill-range": "^7.1.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/chokidar": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", - "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", - "dev": true, - "license": "MIT", - "dependencies": { - "anymatch": "~3.1.2", - "braces": "~3.0.2", - "glob-parent": "~5.1.2", - "is-binary-path": "~2.1.0", - "is-glob": "~4.0.1", - "normalize-path": "~3.0.0", - "readdirp": "~3.6.0" - }, - "engines": { - "node": ">= 8.10.0" - }, - "funding": { - "url": "https://paulmillr.com/funding/" - }, - "optionalDependencies": { - "fsevents": "~2.3.2" - } - }, - "node_modules/commander": { - "version": "10.0.1", - "resolved": "https://registry.npmjs.org/commander/-/commander-10.0.1.tgz", - "integrity": "sha512-y4Mg2tXshplEbSGzx7amzPwKKOCGuoSRP/CjEdwwk0FOGlUbq6lKuoyDZTNZkmxHdJtp54hdfY/JUrdL7Xfdug==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=14" - } - }, - "node_modules/concat-map": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", - "dev": true, - "license": "MIT" - }, - "node_modules/debug": { - "version": "4.4.3", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", - "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", - "dev": true, - "license": "MIT", - "dependencies": { - "ms": "^2.1.3" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/depd": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", - "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/dependency-graph": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/dependency-graph/-/dependency-graph-1.0.0.tgz", - "integrity": "sha512-cW3gggJ28HZ/LExwxP2B++aiKxhJXMSIt9K48FOXQkm+vuG5gyatXnLsONRJdzO/7VfjDIiaOOa/bs4l464Lwg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=4" - } - }, - "node_modules/dom-serializer": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-1.4.1.tgz", - "integrity": "sha512-VHwB3KfrcOOkelEG2ZOfxqLZdfkil8PtJi4P8N2MMXucZq2yLp75ClViUlOVwyoHEDjYU433Aq+5zWP61+RGag==", - "dev": true, - "license": "MIT", - "dependencies": { - "domelementtype": "^2.0.1", - "domhandler": "^4.2.0", - "entities": "^2.0.0" - }, - "funding": { - "url": "https://github.com/cheeriojs/dom-serializer?sponsor=1" - } - }, - "node_modules/dom-serializer/node_modules/entities": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/entities/-/entities-2.2.0.tgz", - "integrity": "sha512-p92if5Nz619I0w+akJrLZH0MX0Pb5DX39XOwQTtXSdQQOaYH03S1uIQp4mhOZtAXrxq4ViO67YTiLBo2638o9A==", - "dev": true, - "license": "BSD-2-Clause", - "funding": { - "url": "https://github.com/fb55/entities?sponsor=1" - } - }, - "node_modules/domelementtype": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.3.0.tgz", - "integrity": "sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/fb55" - } - ], - "license": "BSD-2-Clause" - }, - "node_modules/domhandler": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-4.3.1.tgz", - "integrity": "sha512-GrwoxYN+uWlzO8uhUXRl0P+kHE4GtVPfYzVLcUxPL7KNdHKj66vvlhiweIHqYYXWlw+T8iLMp42Lm67ghw4WMQ==", - "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "domelementtype": "^2.2.0" - }, - "engines": { - "node": ">= 4" - }, - "funding": { - "url": "https://github.com/fb55/domhandler?sponsor=1" - } - }, - "node_modules/domutils": { - "version": "2.8.0", - "resolved": "https://registry.npmjs.org/domutils/-/domutils-2.8.0.tgz", - "integrity": "sha512-w96Cjofp72M5IIhpjgobBimYEfoPjx1Vx0BSX9P30WBdZW2WIKU0T1Bd0kz2eNZ9ikjKgHbEyKx8BB6H1L3h3A==", - "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "dom-serializer": "^1.0.1", - "domelementtype": "^2.2.0", - "domhandler": "^4.2.0" - }, - "funding": { - "url": "https://github.com/fb55/domutils?sponsor=1" - } - }, - "node_modules/ee-first": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", - "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==", - "dev": true, - "license": "MIT" - }, - "node_modules/encodeurl": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", - "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/entities": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/entities/-/entities-6.0.1.tgz", - "integrity": "sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g==", - "dev": true, - "license": "BSD-2-Clause", - "engines": { - "node": ">=0.12" - }, - "funding": { - "url": "https://github.com/fb55/entities?sponsor=1" - } - }, - "node_modules/errno": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/errno/-/errno-1.0.0.tgz", - "integrity": "sha512-3zV5mFS1E8/1bPxt/B0xxzI1snsg3uSCIh6Zo1qKg6iMw93hzPANk9oBFzSFBFrwuVoQuE3rLoouAUfwOAj1wQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "prr": "~1.0.1" - }, - "bin": { - "errno": "cli.js" - } - }, - "node_modules/escape-html": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", - "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==", - "dev": true, - "license": "MIT" - }, - "node_modules/escape-string-regexp": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-5.0.0.tgz", - "integrity": "sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/esm-import-transformer": { - "version": "3.0.5", - "resolved": "https://registry.npmjs.org/esm-import-transformer/-/esm-import-transformer-3.0.5.tgz", - "integrity": "sha512-1GKLvfuMnnpI75l8c6sHoz0L3Z872xL5akGuBudgqTDPv4Vy6f2Ec7jEMKTxlqWl/3kSvNbHELeimJtnqgYniw==", - "dev": true, - "license": "MIT", - "dependencies": { - "acorn": "^8.15.0" - } - }, - "node_modules/esprima": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", - "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", - "dev": true, - "license": "BSD-2-Clause", - "bin": { - "esparse": "bin/esparse.js", - "esvalidate": "bin/esvalidate.js" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/etag": { - "version": "1.8.1", - "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", - "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/evaluate-value": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/evaluate-value/-/evaluate-value-2.0.0.tgz", - "integrity": "sha512-VonfiuDJc0z4sOO7W0Pd130VLsXN6vmBWZlrog1mCb/o7o/Nl5Lr25+Kj/nkCCAhG+zqeeGjxhkK9oHpkgTHhQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 8" - } - }, - "node_modules/extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha512-zCnTtlxNoAiDc3gqY2aYAWFx7XWWiasuF2K8Me5WbN8otHKTUKBwjPtNpRs/rbUZm7KxWAaNj7P1a/p52GbVug==", - "dev": true, - "license": "MIT", - "dependencies": { - "is-extendable": "^0.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/fdir": { - "version": "6.5.0", - "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", - "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12.0.0" - }, - "peerDependencies": { - "picomatch": "^3 || ^4" - }, - "peerDependenciesMeta": { - "picomatch": { - "optional": true - } - } - }, - "node_modules/filesize": { - "version": "10.1.6", - "resolved": "https://registry.npmjs.org/filesize/-/filesize-10.1.6.tgz", - "integrity": "sha512-sJslQKU2uM33qH5nqewAwVB2QgR6w1aMNsYUp3aN5rMRyXEwJGmZvaWzeJFNTOXWlHQyBFCWrdj3fV/fsTOX8w==", - "dev": true, - "license": "BSD-3-Clause", - "engines": { - "node": ">= 10.4.0" - } - }, - "node_modules/fill-range": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", - "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", - "dev": true, - "license": "MIT", - "dependencies": { - "to-regex-range": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/finalhandler": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.3.2.tgz", - "integrity": "sha512-aA4RyPcd3badbdABGDuTXCMTtOneUCAYH/gxoYRTZlIJdF0YPWuGqiAsIrhNnnqdXGswYk6dGujem4w80UJFhg==", - "dev": true, - "license": "MIT", - "dependencies": { - "debug": "2.6.9", - "encodeurl": "~2.0.0", - "escape-html": "~1.0.3", - "on-finished": "~2.4.1", - "parseurl": "~1.3.3", - "statuses": "~2.0.2", - "unpipe": "~1.0.0" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/finalhandler/node_modules/debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dev": true, - "license": "MIT", - "dependencies": { - "ms": "2.0.0" - } - }, - "node_modules/finalhandler/node_modules/ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", - "dev": true, - "license": "MIT" - }, - "node_modules/fresh": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/fresh/-/fresh-2.0.0.tgz", - "integrity": "sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/fsevents": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", - "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", - "dev": true, - "hasInstallScript": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": "^8.16.0 || ^10.6.0 || >=11.0.0" - } - }, - "node_modules/glob-parent": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", - "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", - "dev": true, - "license": "ISC", - "dependencies": { - "is-glob": "^4.0.1" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/gray-matter": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/gray-matter/-/gray-matter-4.0.3.tgz", - "integrity": "sha512-5v6yZd4JK3eMI3FqqCouswVqwugaA9r4dNZB1wwcmrD02QkV5H0y7XBQW8QwQqEaZY1pM9aqORSORhJRdNK44Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "js-yaml": "^3.13.1", - "kind-of": "^6.0.2", - "section-matter": "^1.0.0", - "strip-bom-string": "^1.0.0" - }, - "engines": { - "node": ">=6.0" - } - }, - "node_modules/gray-matter/node_modules/argparse": { - "version": "1.0.10", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", - "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", - "dev": true, - "license": "MIT", - "dependencies": { - "sprintf-js": "~1.0.2" - } - }, - "node_modules/gray-matter/node_modules/js-yaml": { - "version": "3.14.2", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.2.tgz", - "integrity": "sha512-PMSmkqxr106Xa156c2M265Z+FTrPl+oxd/rgOQy2tijQeK5TxQ43psO1ZCwhVOSdnn+RzkzlRz/eY4BgJBYVpg==", - "dev": true, - "license": "MIT", - "dependencies": { - "argparse": "^1.0.7", - "esprima": "^4.0.0" - }, - "bin": { - "js-yaml": "bin/js-yaml.js" - } - }, - "node_modules/htmlparser2": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-7.2.0.tgz", - "integrity": "sha512-H7MImA4MS6cw7nbyURtLPO1Tms7C5H602LRETv95z1MxO/7CP7rDVROehUYeYBUYEON94NXXDEPmZuq+hX4sog==", - "dev": true, - "funding": [ - "https://github.com/fb55/htmlparser2?sponsor=1", - { - "type": "github", - "url": "https://github.com/sponsors/fb55" - } - ], - "license": "MIT", - "dependencies": { - "domelementtype": "^2.0.1", - "domhandler": "^4.2.2", - "domutils": "^2.8.0", - "entities": "^3.0.1" - } - }, - "node_modules/htmlparser2/node_modules/entities": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/entities/-/entities-3.0.1.tgz", - "integrity": "sha512-WiyBqoomrwMdFG1e0kqvASYfnlb0lp8M5o5Fw2OFq1hNZxxcNk8Ik0Xm7LxzBhuidnZB/UtBqVCgUz3kBOP51Q==", - "dev": true, - "license": "BSD-2-Clause", - "engines": { - "node": ">=0.12" - }, - "funding": { - "url": "https://github.com/fb55/entities?sponsor=1" - } - }, - "node_modules/http-equiv-refresh": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/http-equiv-refresh/-/http-equiv-refresh-2.0.1.tgz", - "integrity": "sha512-XJpDL/MLkV3dKwLzHwr2dY05dYNfBNlyPu4STQ8WvKCFdc6vC5tPXuq28of663+gHVg03C+16pHHs/+FmmDjcw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 6" - } - }, - "node_modules/http-errors": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.1.tgz", - "integrity": "sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "depd": "~2.0.0", - "inherits": "~2.0.4", - "setprototypeof": "~1.2.0", - "statuses": "~2.0.2", - "toidentifier": "~1.0.1" - }, - "engines": { - "node": ">= 0.8" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" - } - }, - "node_modules/inherits": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", - "dev": true, - "license": "ISC" - }, - "node_modules/is-alphabetical": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/is-alphabetical/-/is-alphabetical-2.0.1.tgz", - "integrity": "sha512-FWyyY60MeTNyeSRpkM2Iry0G9hpr7/9kD40mD/cGQEuilcZYS4okz8SN2Q6rLCJ8gbCt6fN+rC+6tMGS99LaxQ==", - "dev": true, - "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, - "node_modules/is-alphanumerical": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/is-alphanumerical/-/is-alphanumerical-2.0.1.tgz", - "integrity": "sha512-hmbYhX/9MUMF5uh7tOXyK/n0ZvWpad5caBA17GsC6vyuCqaWliRG5K1qS9inmUhEMaOBIW7/whAnSwveW/LtZw==", - "dev": true, - "license": "MIT", - "dependencies": { - "is-alphabetical": "^2.0.0", - "is-decimal": "^2.0.0" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, - "node_modules/is-binary-path": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", - "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", - "dev": true, - "license": "MIT", - "dependencies": { - "binary-extensions": "^2.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/is-decimal": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/is-decimal/-/is-decimal-2.0.1.tgz", - "integrity": "sha512-AAB9hiomQs5DXWcRB1rqsxGUstbRroFOPPVAomNk/3XHR5JyEZChOyTWe2oayKnsSsr/kcGqF+z6yuH6HHpN0A==", - "dev": true, - "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, - "node_modules/is-extendable": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", - "integrity": "sha512-5BMULNob1vgFX6EjQw5izWDxrecWK9AM72rugNr0TFldMOi0fj6Jk+zeKIt0xGj4cEfQIJth4w3OKWOJ4f+AFw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-extglob": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", - "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-glob": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", - "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", - "dev": true, - "license": "MIT", - "dependencies": { - "is-extglob": "^2.1.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-json": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/is-json/-/is-json-2.0.1.tgz", - "integrity": "sha512-6BEnpVn1rcf3ngfmViLM6vjUjGErbdrL4rwlv+u1NO1XO8kqT4YGL8+19Q+Z/bas8tY90BTWMk2+fW1g6hQjbA==", - "dev": true, - "license": "ISC" - }, - "node_modules/is-number": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", - "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.12.0" - } - }, - "node_modules/iso-639-1": { - "version": "3.1.5", - "resolved": "https://registry.npmjs.org/iso-639-1/-/iso-639-1-3.1.5.tgz", - "integrity": "sha512-gXkz5+KN7HrG0Q5UGqSMO2qB9AsbEeyLP54kF1YrMsIxmu+g4BdB7rflReZTSTZGpfj8wywu6pfPBCylPIzGQA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.0" - } - }, - "node_modules/js-yaml": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz", - "integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==", - "dev": true, - "license": "MIT", - "dependencies": { - "argparse": "^2.0.1" - }, - "bin": { - "js-yaml": "bin/js-yaml.js" - } - }, - "node_modules/junk": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/junk/-/junk-3.1.0.tgz", - "integrity": "sha512-pBxcB3LFc8QVgdggvZWyeys+hnrNWg4OcZIU/1X59k5jQdLBlCsYGRQaz234SqoRLTCgMH00fY0xRJH+F9METQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/kind-of": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", - "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/kleur": { - "version": "4.1.5", - "resolved": "https://registry.npmjs.org/kleur/-/kleur-4.1.5.tgz", - "integrity": "sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/linkify-it": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/linkify-it/-/linkify-it-5.0.0.tgz", - "integrity": "sha512-5aHCbzQRADcdP+ATqnDuhhJ/MRIqDkZX5pyjFHRRysS8vZ5AbqGEoFIb6pYHPZ+L/OC2Lc+xT8uHVVR5CAK/wQ==", - "license": "MIT", - "dependencies": { - "uc.micro": "^2.0.0" - } - }, - "node_modules/liquidjs": { - "version": "10.24.0", - "resolved": "https://registry.npmjs.org/liquidjs/-/liquidjs-10.24.0.tgz", - "integrity": "sha512-TAUNAdgwaAXjjcUFuYVJm9kOVH7zc0mTKxsG9t9Lu4qdWjB2BEblyVIYpjWcmJLMGgiYqnGNJjpNMHx0gp/46A==", - "dev": true, - "license": "MIT", - "dependencies": { - "commander": "^10.0.0" - }, - "bin": { - "liquid": "bin/liquid.js", - "liquidjs": "bin/liquid.js" - }, - "engines": { - "node": ">=16" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/liquidjs" - } - }, - "node_modules/list-to-array": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/list-to-array/-/list-to-array-1.1.0.tgz", - "integrity": "sha512-+dAZZ2mM+/m+vY9ezfoueVvrgnHIGi5FvgSymbIgJOFwiznWyA59mav95L+Mc6xPtL3s9gm5eNTlNtxJLbNM1g==", - "dev": true, - "license": "MIT" - }, - "node_modules/luxon": { - "version": "3.7.2", - "resolved": "https://registry.npmjs.org/luxon/-/luxon-3.7.2.tgz", - "integrity": "sha512-vtEhXh/gNjI9Yg1u4jX/0YVPMvxzHuGgCm6tC5kZyb08yjGWGnqAjGJvcXbqQR2P3MyMEFnRbpcdFS6PBcLqew==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - } - }, - "node_modules/markdown-it": { - "version": "14.1.0", - "resolved": "https://registry.npmjs.org/markdown-it/-/markdown-it-14.1.0.tgz", - "integrity": "sha512-a54IwgWPaeBCAAsv13YgmALOF1elABB08FxO9i+r4VFk5Vl4pKokRPeX8u5TCgSsPi6ec1otfLjdOpVcgbpshg==", - "license": "MIT", - "dependencies": { - "argparse": "^2.0.1", - "entities": "^4.4.0", - "linkify-it": "^5.0.0", - "mdurl": "^2.0.0", - "punycode.js": "^2.3.1", - "uc.micro": "^2.1.0" - }, - "bin": { - "markdown-it": "bin/markdown-it.mjs" - } - }, - "node_modules/markdown-it/node_modules/entities": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", - "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", - "license": "BSD-2-Clause", - "engines": { - "node": ">=0.12" - }, - "funding": { - "url": "https://github.com/fb55/entities?sponsor=1" - } - }, - "node_modules/maximatch": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/maximatch/-/maximatch-0.1.0.tgz", - "integrity": "sha512-9ORVtDUFk4u/NFfo0vG/ND/z7UQCVZBL539YW0+U1I7H1BkZwizcPx5foFv7LCPcBnm2U6RjFnQOsIvN4/Vm2A==", - "dev": true, - "license": "MIT", - "dependencies": { - "array-differ": "^1.0.0", - "array-union": "^1.0.1", - "arrify": "^1.0.0", - "minimatch": "^3.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/mdurl": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/mdurl/-/mdurl-2.0.0.tgz", - "integrity": "sha512-Lf+9+2r+Tdp5wXDXC4PcIBjTDtq4UKjCPMQhKIuzpJNW0b96kVqSwW0bT7FhRSfmAiFYgP+SCRvdrDozfh0U5w==", - "license": "MIT" - }, - "node_modules/mime": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/mime/-/mime-3.0.0.tgz", - "integrity": "sha512-jSCU7/VB1loIWBZe14aEYHU/+1UMEHoaO7qxCOVJOw9GgH72VAWppxNcjU+x9a2k3GSIBXNKxXQFqRvvZ7vr3A==", - "dev": true, - "license": "MIT", - "bin": { - "mime": "cli.js" - }, - "engines": { - "node": ">=10.0.0" - } - }, - "node_modules/mime-db": { - "version": "1.54.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz", - "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/mime-types": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.2.tgz", - "integrity": "sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A==", - "dev": true, - "license": "MIT", - "dependencies": { - "mime-db": "^1.54.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" - } - }, - "node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", - "dev": true, - "license": "ISC", - "dependencies": { - "brace-expansion": "^1.1.7" - }, - "engines": { - "node": "*" - } - }, - "node_modules/minimist": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", - "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", - "dev": true, - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/minipass": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", - "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==", - "dev": true, - "license": "ISC", - "engines": { - "node": ">=16 || 14 >=14.17" - } - }, - "node_modules/moo": { - "version": "0.5.2", - "resolved": "https://registry.npmjs.org/moo/-/moo-0.5.2.tgz", - "integrity": "sha512-iSAJLHYKnX41mKcJKjqvnAN9sf0LMDTXDEvFv+ffuRR9a1MIuXLjMNL6EsnDHSkKLTWNqQQ5uo61P4EbU4NU+Q==", - "dev": true, - "license": "BSD-3-Clause" - }, - "node_modules/morphdom": { - "version": "2.7.7", - "resolved": "https://registry.npmjs.org/morphdom/-/morphdom-2.7.7.tgz", - "integrity": "sha512-04GmsiBcalrSCNmzfo+UjU8tt3PhZJKzcOy+r1FlGA7/zri8wre3I1WkYN9PT3sIeIKfW9bpyElA+VzOg2E24g==", - "dev": true, - "license": "MIT" - }, - "node_modules/ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "dev": true, - "license": "MIT" - }, - "node_modules/node-retrieve-globals": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/node-retrieve-globals/-/node-retrieve-globals-6.0.1.tgz", - "integrity": "sha512-j0DeFuZ/Wg3VlklfbxUgZF/mdHMTEiEipBb3q0SpMMbHaV3AVfoUQF8UGxh1s/yjqO0TgRZd4Pi/x2yRqoQ4Eg==", - "dev": true, - "license": "MIT", - "dependencies": { - "acorn": "^8.14.1", - "acorn-walk": "^8.3.4", - "esm-import-transformer": "^3.0.3" - } - }, - "node_modules/normalize-path": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", - "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/nunjucks": { - "version": "3.2.4", - "resolved": "https://registry.npmjs.org/nunjucks/-/nunjucks-3.2.4.tgz", - "integrity": "sha512-26XRV6BhkgK0VOxfbU5cQI+ICFUtMLixv1noZn1tGU38kQH5A5nmmbk/O45xdyBhD1esk47nKrY0mvQpZIhRjQ==", - "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "a-sync-waterfall": "^1.0.0", - "asap": "^2.0.3", - "commander": "^5.1.0" - }, - "bin": { - "nunjucks-precompile": "bin/precompile" - }, - "engines": { - "node": ">= 6.9.0" - }, - "peerDependencies": { - "chokidar": "^3.3.0" - }, - "peerDependenciesMeta": { - "chokidar": { - "optional": true - } - } - }, - "node_modules/nunjucks/node_modules/commander": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/commander/-/commander-5.1.0.tgz", - "integrity": "sha512-P0CysNDQ7rtVw4QIQtm+MRxV66vKFSvlsQvGYXZWR3qFU0jlMKHZZZgw8e+8DSah4UDKMqnknRDQz+xuQXQ/Zg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 6" - } - }, - "node_modules/on-finished": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", - "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", - "dev": true, - "license": "MIT", - "dependencies": { - "ee-first": "1.1.1" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/outdent": { - "version": "0.8.0", - "resolved": "https://registry.npmjs.org/outdent/-/outdent-0.8.0.tgz", - "integrity": "sha512-KiOAIsdpUTcAXuykya5fnVVT+/5uS0Q1mrkRHcF89tpieSmY33O/tmc54CqwA+bfhbtEfZUNLHaPUiB9X3jt1A==", - "license": "MIT" - }, - "node_modules/parse-srcset": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/parse-srcset/-/parse-srcset-1.0.2.tgz", - "integrity": "sha512-/2qh0lav6CmI15FzA3i/2Bzk2zCgQhGMkvhOhKNcBVQ1ldgpbfiNTVslmooUmWJcADi1f1kIeynbDRVzNlfR6Q==", - "dev": true, - "license": "MIT" - }, - "node_modules/parseurl": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", - "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/picomatch": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", - "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", - "dev": true, - "license": "MIT", - "peer": true, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" - } - }, - "node_modules/please-upgrade-node": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/please-upgrade-node/-/please-upgrade-node-3.2.0.tgz", - "integrity": "sha512-gQR3WpIgNIKwBMVLkpMUeR3e1/E1y42bqDQZfql+kDeXd8COYfM8PQA4X6y7a8u9Ua9FHmsrrmirW2vHs45hWg==", - "dev": true, - "license": "MIT", - "dependencies": { - "semver-compare": "^1.0.0" - } - }, - "node_modules/posthtml": { - "version": "0.16.7", - "resolved": "https://registry.npmjs.org/posthtml/-/posthtml-0.16.7.tgz", - "integrity": "sha512-7Hc+IvlQ7hlaIfQFZnxlRl0jnpWq2qwibORBhQYIb0QbNtuicc5ZxvKkVT71HJ4Py1wSZ/3VR1r8LfkCtoCzhw==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "posthtml-parser": "^0.11.0", - "posthtml-render": "^3.0.0" - }, - "engines": { - "node": ">=12.0.0" - } - }, - "node_modules/posthtml-match-helper": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/posthtml-match-helper/-/posthtml-match-helper-2.0.3.tgz", - "integrity": "sha512-p9oJgTdMF2dyd7WE54QI1LvpBIkNkbSiiECKezNnDVYhGhD1AaOnAkw0Uh0y5TW+OHO8iBdSqnd8Wkpb6iUqmw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "posthtml": "^0.16.6" - } - }, - "node_modules/posthtml-parser": { - "version": "0.11.0", - "resolved": "https://registry.npmjs.org/posthtml-parser/-/posthtml-parser-0.11.0.tgz", - "integrity": "sha512-QecJtfLekJbWVo/dMAA+OSwY79wpRmbqS5TeXvXSX+f0c6pW4/SE6inzZ2qkU7oAMCPqIDkZDvd/bQsSFUnKyw==", - "dev": true, - "license": "MIT", - "dependencies": { - "htmlparser2": "^7.1.1" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/posthtml-render": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/posthtml-render/-/posthtml-render-3.0.0.tgz", - "integrity": "sha512-z+16RoxK3fUPgwaIgH9NGnK1HKY9XIDpydky5eQGgAFVXTCSezalv9U2jQuNV+Z9qV1fDWNzldcw4eK0SSbqKA==", - "dev": true, - "license": "MIT", - "dependencies": { - "is-json": "^2.0.1" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/prr": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/prr/-/prr-1.0.1.tgz", - "integrity": "sha512-yPw4Sng1gWghHQWj0B3ZggWUm4qVbPwPFcRG8KyxiU7J2OHFSoEHKS+EZ3fv5l1t9CyCiop6l/ZYeWbrgoQejw==", - "dev": true, - "license": "MIT" - }, - "node_modules/punycode.js": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/punycode.js/-/punycode.js-2.3.1.tgz", - "integrity": "sha512-uxFIHU0YlHYhDQtV4R9J6a52SLx28BCjT+4ieh7IGbgwVJWO+km431c4yRlREUAsAmt/uMjQUyQHNEPf0M39CA==", - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/range-parser": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", - "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/readdirp": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", - "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", - "dev": true, - "license": "MIT", - "dependencies": { - "picomatch": "^2.2.1" - }, - "engines": { - "node": ">=8.10.0" - } - }, - "node_modules/readdirp/node_modules/picomatch": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", - "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8.6" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" - } - }, - "node_modules/section-matter": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/section-matter/-/section-matter-1.0.0.tgz", - "integrity": "sha512-vfD3pmTzGpufjScBh50YHKzEu2lxBWhVEHsNGoEXmCmn2hKGfeNLYMzCJpe8cD7gqX7TJluOVpBkAequ6dgMmA==", - "dev": true, - "license": "MIT", - "dependencies": { - "extend-shallow": "^2.0.1", - "kind-of": "^6.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/semver": { - "version": "7.7.3", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz", - "integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==", - "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/semver-compare": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/semver-compare/-/semver-compare-1.0.0.tgz", - "integrity": "sha512-YM3/ITh2MJ5MtzaM429anh+x2jiLVjqILF4m4oyQB18W7Ggea7BfqdH/wGMK7dDiMghv/6WG7znWMwUDzJiXow==", - "dev": true, - "license": "MIT" - }, - "node_modules/send": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/send/-/send-1.2.0.tgz", - "integrity": "sha512-uaW0WwXKpL9blXE2o0bRhoL2EGXIrZxQ2ZQ4mgcfoBxdFmQold+qWsD2jLrfZ0trjKL6vOw0j//eAwcALFjKSw==", - "dev": true, - "license": "MIT", - "dependencies": { - "debug": "^4.3.5", - "encodeurl": "^2.0.0", - "escape-html": "^1.0.3", - "etag": "^1.8.1", - "fresh": "^2.0.0", - "http-errors": "^2.0.0", - "mime-types": "^3.0.1", - "ms": "^2.1.3", - "on-finished": "^2.4.1", - "range-parser": "^1.2.1", - "statuses": "^2.0.1" - }, - "engines": { - "node": ">= 18" - } - }, - "node_modules/setprototypeof": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", - "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", - "dev": true, - "license": "ISC" - }, - "node_modules/slash": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", - "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/slugify": { - "version": "1.6.6", - "resolved": "https://registry.npmjs.org/slugify/-/slugify-1.6.6.tgz", - "integrity": "sha512-h+z7HKHYXj6wJU+AnS/+IH8Uh9fdcX1Lrhg1/VMdf9PwoBQXFcXiAdsy2tSK0P6gKwJLXp02r90ahUCqHk9rrw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8.0.0" - } - }, - "node_modules/sprintf-js": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", - "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==", - "dev": true, - "license": "BSD-3-Clause" - }, - "node_modules/ssri": { - "version": "11.0.0", - "resolved": "https://registry.npmjs.org/ssri/-/ssri-11.0.0.tgz", - "integrity": "sha512-aZpUoMN/Jj2MqA4vMCeiKGnc/8SuSyHbGSBdgFbZxP8OJGF/lFkIuElzPxsN0q8TQQ+prw3P4EDfB3TBHHgfXw==", - "dev": true, - "license": "ISC", - "dependencies": { - "minipass": "^7.0.3" - }, - "engines": { - "node": "^16.14.0 || >=18.0.0" - } - }, - "node_modules/statuses": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz", - "integrity": "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/strip-bom-string": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/strip-bom-string/-/strip-bom-string-1.0.0.tgz", - "integrity": "sha512-uCC2VHvQRYu+lMh4My/sFNmF2klFymLX1wHJeXnbEJERpV/ZsVuonzerjfrGpIGF7LBVa1O7i9kjiWvJiFck8g==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/tinyglobby": { - "version": "0.2.15", - "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz", - "integrity": "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "fdir": "^6.5.0", - "picomatch": "^4.0.3" - }, - "engines": { - "node": ">=12.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/SuperchupuDev" - } - }, - "node_modules/to-regex-range": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", - "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "is-number": "^7.0.0" - }, - "engines": { - "node": ">=8.0" - } - }, - "node_modules/toidentifier": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", - "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.6" - } - }, - "node_modules/uc.micro": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/uc.micro/-/uc.micro-2.1.0.tgz", - "integrity": "sha512-ARDJmphmdvUk6Glw7y9DQ2bFkKBHwQHLi2lsaH6PPmz/Ka9sFOBsBluozhDltWmnv9u/cF6Rt87znRTPV+yp/A==", - "license": "MIT" - }, - "node_modules/unpipe": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", - "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/urlpattern-polyfill": { - "version": "10.1.0", - "resolved": "https://registry.npmjs.org/urlpattern-polyfill/-/urlpattern-polyfill-10.1.0.tgz", - "integrity": "sha512-IGjKp/o0NL3Bso1PymYURCJxMPNAf/ILOpendP9f5B6e1rTJgdgiOvgfoT8VxCAdY+Wisb9uhGaJJf3yZ2V9nw==", - "dev": true, - "license": "MIT" - }, - "node_modules/ws": { - "version": "8.18.3", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.3.tgz", - "integrity": "sha512-PEIGCY5tSlUt50cqyMXfCzX+oOPqN0vuGqWzbcJ2xvnkzkq46oOpz7dQaTDBdfICb4N14+GARUDw2XV2N4tvzg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10.0.0" - }, - "peerDependencies": { - "bufferutil": "^4.0.1", - "utf-8-validate": ">=5.0.2" - }, - "peerDependenciesMeta": { - "bufferutil": { - "optional": true - }, - "utf-8-validate": { - "optional": true - } - } - } - } -} diff --git a/package.json b/package.json deleted file mode 100644 index 340f77b..0000000 --- a/package.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "name": "lizdotcoffee", - "version": "1.0.0", - "type": "module", - "scripts": { - "dev": "eleventy --serve", - "build": "eleventy" - }, - "devDependencies": { - "@11ty/eleventy": "^3.1.2", - "@11ty/eleventy-plugin-rss": "^2.0.4" - }, - "dependencies": { - "markdown-it": "^14.1.0", - "outdent": "^0.8.0" - } -} diff --git a/src/_layouts/base.njk b/src/_layouts/base.njk deleted file mode 100644 index 21e8bae..0000000 --- a/src/_layouts/base.njk +++ /dev/null @@ -1,103 +0,0 @@ - - - - - - - {% if title %}{{ title }} - {% endif %}liz.coffee - - - - - - - {% if description %} - - - {% endif %} - {% if ogImage %} - - {% else %} - - {% endif %} - - - - - - - {% if description %} - - {% endif %} - {% if ogImage %} - - {% else %} - - {% endif %} - - - - {% if pageStyles %} - - {% endif %} - - -
- - -
-
-
- {{ content | safe }} -
-
- - - - {% if pageScripts %} - - {% endif %} - - diff --git a/src/about.njk b/src/about.njk deleted file mode 100644 index bfe1d7f..0000000 --- a/src/about.njk +++ /dev/null @@ -1,35 +0,0 @@ ---- -layout: base.njk -title: About ---- - -{% markdown %} - -## About Me - -
- - - -

bellevue, washington across the water and a small critter.

-
- -I'm a 22 year old programmer with a few years of industry experience under my belt. Currently at AWS. -I graduated from Utah State University in 2023 at 20 with a degree in CS and a minor in math. -I live in Seattle with my partners and my dog. - - -
- - - -

sourdough bread, and some lime scooters where they ought to be parked.

-
- -I love running. -I also love baking sourdough bread. -I obsess and read as much as I can about, objectively, the most fascinating creatures there are - penguins. - -I love taking long train rides, anime, Rubik's cubes, thocky and soft mechanical keyboards, collecting stickers and stuffed animals, loud thundery storms, etc. - -{% endmarkdown %} diff --git a/src/assets/bellevue.jpeg b/src/assets/bellevue.jpeg deleted file mode 100644 index 0c68197..0000000 Binary files a/src/assets/bellevue.jpeg and /dev/null differ diff --git a/src/assets/bread.jpeg b/src/assets/bread.jpeg deleted file mode 100644 index e9f4021..0000000 Binary files a/src/assets/bread.jpeg and /dev/null differ diff --git a/src/assets/critter.jpeg b/src/assets/critter.jpeg deleted file mode 100644 index 168dde4..0000000 Binary files a/src/assets/critter.jpeg and /dev/null differ diff --git a/src/assets/euler.png b/src/assets/euler.png deleted file mode 100644 index 9ae56f3..0000000 Binary files a/src/assets/euler.png and /dev/null differ diff --git a/src/assets/fourier.png b/src/assets/fourier.png deleted file mode 100644 index 0e7f221..0000000 Binary files a/src/assets/fourier.png and /dev/null differ diff --git a/src/assets/godel.png b/src/assets/godel.png deleted file mode 100644 index 22c3f45..0000000 Binary files a/src/assets/godel.png and /dev/null differ diff --git a/src/assets/gus.jpeg b/src/assets/gus.jpeg deleted file mode 100644 index 1cdae76..0000000 Binary files a/src/assets/gus.jpeg and /dev/null differ diff --git a/src/assets/julia.png b/src/assets/julia.png deleted file mode 100644 index 1d259b5..0000000 Binary files a/src/assets/julia.png and /dev/null differ diff --git a/src/assets/lambda.png b/src/assets/lambda.png deleted file mode 100644 index 4f07e8c..0000000 Binary files a/src/assets/lambda.png and /dev/null differ diff --git a/src/assets/penguin.gif b/src/assets/penguin.gif deleted file mode 100644 index 48bd0e2..0000000 Binary files a/src/assets/penguin.gif and /dev/null differ diff --git a/src/assets/petting_basil.mp4 b/src/assets/petting_basil.mp4 deleted file mode 100644 index 0ed34ba..0000000 Binary files a/src/assets/petting_basil.mp4 and /dev/null differ diff --git a/src/assets/scooters_on_ceiling.jpeg b/src/assets/scooters_on_ceiling.jpeg deleted file mode 100644 index 2b1e856..0000000 Binary files a/src/assets/scooters_on_ceiling.jpeg and /dev/null differ diff --git a/src/assets/tabloid.png b/src/assets/tabloid.png deleted file mode 100644 index 5fd489a..0000000 Binary files a/src/assets/tabloid.png and /dev/null differ diff --git a/src/assets/turing.png b/src/assets/turing.png deleted file mode 100644 index 51ae5a5..0000000 Binary files a/src/assets/turing.png and /dev/null differ diff --git a/src/blinkies/add.njk b/src/blinkies/add.njk deleted file mode 100644 index 95d29df..0000000 --- a/src/blinkies/add.njk +++ /dev/null @@ -1,176 +0,0 @@ ---- -layout: base.njk -title: Add a Blinkie! ---- - -

Add your very own blinkie!

-

- Please allow us 1 to 10 business days to process your request. Your engagement - is very important to our shareholders. -

-

- - Want to create your own blinkie? Check out Pixel Wave Studio! - Need inspiration? Browse Adrian's Blinkie Collection. - -

- -
-
- - -
-
- -
- -

- - In case something goes wrong or you want to update the blinkie in the future and need to email me. - -

- -
-
- -
- -

- - Please explain why you think this blinkie is a good addition to this site. - "I am cool as fuck yo" is a valid reason. - -

- -
-
- -
- -

- - Optional link to direct visitors to when they click the blinkie. - -

- -
-
- -
- -

Blinkie gif in 150px:20px or equivalent aspect ratio.

- - - -
-
- -
- -
- - - - - -
- - - - diff --git a/src/blinkies/submitted.njk b/src/blinkies/submitted.njk deleted file mode 100644 index f06afa5..0000000 --- a/src/blinkies/submitted.njk +++ /dev/null @@ -1,9 +0,0 @@ ---- -layout: base.njk -title: Blinkie Sent! ---- - -

Thank you!

-

Thank you for submitting a blinkie!

- -

Our shareholders will extract your labor value and perhaps add it to the larger blinkie economy. You may go Home

diff --git a/src/feed.njk b/src/feed.njk deleted file mode 100644 index a8ae238..0000000 --- a/src/feed.njk +++ /dev/null @@ -1,32 +0,0 @@ ---- -permalink: /feed.xml -eleventyExcludeFromCollections: true -metadata: - title: liz.coffee - subtitle: elizabeth's ramblings - url: https://liz.coffee/ - feedUrl: https://liz.coffee/feed.xml - author: - name: emprespresso ---- - - - {{ metadata.title }} - {{ metadata.subtitle }} - - - {{ collections.posts | getNewestCollectionItemDate | dateToRfc3339 }} - {{ metadata.url }} - - {{ metadata.author.name }} - - {%- for post in collections.posts | reverse -%} - - {{ post.data.title }} - - {{ post.date | dateToRfc3339 }} - {{ metadata.url }}{{ post.url }} - {{ post.templateContent | htmlToAbsoluteUrls(metadata.url) }} - - {%- endfor -%} - diff --git a/src/index.njk b/src/index.njk deleted file mode 100644 index aaae563..0000000 --- a/src/index.njk +++ /dev/null @@ -1,33 +0,0 @@ ---- -layout: base.njk -title: Home ---- - -

Liz Dot Coffee

- -

- Hello! My name is Elizabeth/Alex (she/he). This is me on the best day of my life, meeting and petting penguins in San Diego. This other guy? That's Gus. -

-
- - -
- -
-

Together we run, bake bread, and chase squirrels. Sometimes I do computers while he snoozes.

- -

You can find me on the internet at these places:

- - -
- "Right," Holden said. "No coffee. This is a terrible, terrible planet." -
diff --git a/src/resume.njk b/src/resume.njk deleted file mode 100644 index 6d3bdd1..0000000 --- a/src/resume.njk +++ /dev/null @@ -1,6 +0,0 @@ ---- -layout: base.njk -title: Resume ---- - -I'll add this if I need to. diff --git a/src/toys.md b/src/toys.md deleted file mode 100644 index 0351468..0000000 --- a/src/toys.md +++ /dev/null @@ -1,94 +0,0 @@ ---- -layout: base.njk -title: Toys -description: A collection of little interactive toys and experiments ---- - -## Toys! - -These are the web-based toys I've ported over (so far) from my old website. New and improved! - ---- - -#### The Abstraction Engine - -
- -
- -A game inspired by [Baba Is You](https://store.steampowered.com/app/736260/Baba_Is_You/). Your character is a Lambda and you aim to eat the [Curry](https://en.wikipedia.org/wiki/Currying) by writing real lambda-calculus terms and applying them to other lambda-calculus terms to solve puzzles. The core is a [custom ECS engine and lambda calculus interpreter in typescript](https://code.liz.coffee/the-abstraction-engine-ts.git/). So far I've only gotten two levels as a "tech demo" showcasing basic functionality. But the groundwork was put in place to get a [fixed-point-combinator](https://en.wikipedia.org/wiki/Fixed-point_combinator) level written. - -Due to the game's inner complexity and the fact that I just keep getting discouraged, I've been procrastinating making a full rewrite and release of this on real platforms as my first "published" game. Hopefully I get there some day soon. Maybe I just need to set myself a deadline. - -[Play now](https://the-abstraction-engine.pages.dev)! - ---- - -#### Tabloid Closures - -
- -
- -For fun (and procrastination) I added closures to the [Tabloid](https://tabloid.vercel.app/) esoteric programming language and wrote lambda-calculus-inspired primitives of pair/cons, then extended them to -valid merge sort and binary tree traversal programs. - -[It is horribly inefficient](/toys/tabloid). - ---- - -#### Gödel Number Playground - -
- -
- -A PEG grammar (I'd hardly call this a compiler) which computes the [Gödel number](https://en.wikipedia.org/wiki/G%C3%B6del_numbering) from a valid L program. - -[View your favorite L program's Gödel number here](/toys/godel). - ---- - -#### Turing Machine - -
- -
- -[I swear the tape is infinite](/toys/turing). - ---- - -#### Euler Golf 2 - -
- -
- -Euler Golf 2 is a reimplemetation and solver of [Val Hovey's Euler Golf](https://valhovey.github.io/EulerGolf). - -[Play now!](/toys/euler-golf) - ---- - -#### Julia Explorer - -
- -
- -A quick experiment with [GPU.js](https://gpu.rocks/#/). - -[Explore julia set fractals in real time](/toys/julia). - ---- - -#### A Fourier New Year - -
- -
- -A quick app I hacked together for some friends on Discord at the time to explore a discrete fourier transform of the function of their mood wrt time for the year. - -[Drag your mouse on a canvas here](/toys/fourier)! diff --git a/src/toys/euler-golf/css/styles.css b/src/toys/euler-golf/css/styles.css deleted file mode 100644 index 219012d..0000000 --- a/src/toys/euler-golf/css/styles.css +++ /dev/null @@ -1,81 +0,0 @@ -body { - margin: 0; - padding: 0; - overflow: scroll; - font-family: Lucida Console, Lucida Sans Typewriter, monaco, - Bitstream Vera Sans Mono, monospace; - width: 100vw; - height: 100vh; - background: rgb(238, 174, 202); - background: radial-gradient( - circle, - rgba(238, 174, 202, 1) 0%, - rgba(148, 187, 233, 1) 100% - ); -} - -.canvas { - padding: 0; - margin: auto; - display: block; - border: 1px solid black; - - width: 100vw; - height: 100vw; -} - -button { - border-radius: 5px; - padding: 5px; - cursor: pointer; - margin-left: 5px; -} - -.controls { - cursor: pointer; - padding: 12px; - position: fixed; - bottom: 0; - right: 0; - background-color: rgba(255, 255, 255, 0.8); - border: 1px solid white; - border-radius: 8px; - margin-right: 6px; - margin-bottom: 6px; -} - -.buttons { - display: flex; - justify-content: space-around; - align-items: center; -} - -.modal { - display: flex; - position: absolute; - left: 50%; - top: 50%; - transform: translate(-50%, -50%); - - width: 80vw; - max-width: 500px; - min-height: 200px; - - padding: 12px; - - background-color: rgba(255, 255, 255, 0.8); - border: 1px solid black; - border-radius: 15px; -} - -.modal-body { - display: flex; - justify-content: center; -} - -.slider { - display: flex; - justify-content: center; - align-items: center; - margin-top: 3px; -} diff --git a/src/toys/euler-golf/index.html b/src/toys/euler-golf/index.html deleted file mode 100644 index 8164823..0000000 --- a/src/toys/euler-golf/index.html +++ /dev/null @@ -1,80 +0,0 @@ - - - - Euler Golf 2 - - - - - - -
- - ↑↑ -
- - - - - - - - - - - - - diff --git a/src/toys/euler-golf/js/controls.js b/src/toys/euler-golf/js/controls.js deleted file mode 100644 index 7f606cc..0000000 --- a/src/toys/euler-golf/js/controls.js +++ /dev/null @@ -1,33 +0,0 @@ -document - .getElementById("controls-container") - .addEventListener("mouseover", () => { - document.getElementById("controls").style.display = "block"; - document.getElementById("expand-show").style.display = "none"; - }); - -document - .getElementById("controls-container") - .addEventListener("mouseout", () => { - document.getElementById("controls").style.display = "none"; - document.getElementById("expand-show").style.display = "inline"; - }); - -document.getElementById("reset").addEventListener("click", () => { - state = reset_state(state); - - state.target = rand_target(state.rows, state.cols); -}); - -document.getElementById("solve").addEventListener("click", () => { - if (!cx.eq(state.path.at(-2), new cx(0, 0))) state = reset_state(state); - - state.solution = sol(state.target); -}); - -document - .getElementById("directions") - .addEventListener("click", () => directions_modal.show()); - -document.getElementById("gap").addEventListener("input", function () { - state.changes.gap = Number(this.value); -}); diff --git a/src/toys/euler-golf/js/cx.js b/src/toys/euler-golf/js/cx.js deleted file mode 100644 index 371415d..0000000 --- a/src/toys/euler-golf/js/cx.js +++ /dev/null @@ -1,308 +0,0 @@ -// http://www.russellcottrell.com/fractalsEtc/cx.js - -class cx { - static degrees(d) { - cx._RD = d ? Math.PI / 180 : 1; - } - // Math.PI/180 for degrees, 1 for radians - // applies to i/o (constructor, get/set arg, and toString etc.) - - constructor(x, y, polar) { - if (!polar) { - this.re = x; - this.im = y; - } else { - y *= cx._RD; // may be radians or degrees - this.re = x * Math.cos(y); - this.im = x * Math.sin(y); - } - } - - get abs() { - return Math.sqrt(this.re * this.re + this.im * this.im); - } - - set abs(r) { - var theta = this._arg; - this.re = r * Math.cos(theta); - this.im = r * Math.sin(theta); - } - - get arg() { - // returns radians or degrees, non-negative - return ( - ((Math.atan2(this.im, this.re) + 2 * Math.PI) % (2 * Math.PI)) / cx._RD - ); - } - - set arg(theta) { - // may be radians or degrees - var r = this.abs; - this.re = r * Math.cos(theta * cx._RD); - this.im = r * Math.sin(theta * cx._RD); - } - - get _arg() { - // internal; returns radians - return Math.atan2(this.im, this.re); - } - - static get i() { - return new cx(0, 1); - } - - static set i(x) { - throw new Error("i is read-only"); - } - - toString(polar) { - if (!polar) - return ( - this.re.toString() + - (this.im >= 0 ? " + " : " - ") + - Math.abs(this.im).toString() + - "i" - ); - else return this.abs.toString() + " cis " + this.arg.toString(); - } - - toPrecision(n, polar) { - if (!polar) - return ( - this.re.toPrecision(n) + - (this.im >= 0 ? " + " : " - ") + - Math.abs(this.im).toPrecision(n) + - "i" - ); - else return this.abs.toPrecision(n) + " cis " + this.arg.toPrecision(n); - } - - toPrecis(n, polar) { - // trims trailing zeros - if (!polar) - return ( - parseFloat(this.re.toPrecision(n)).toString() + - (this.im >= 0 ? " + " : " - ") + - parseFloat(Math.abs(this.im).toPrecision(n)).toString() + - "i" - ); - else - return ( - parseFloat(this.abs.toPrecision(n)).toString() + - " cis " + - parseFloat(this.arg.toPrecision(n)).toString() - ); - } - - toFixed(n, polar) { - if (!polar) - return ( - this.re.toFixed(n) + - (this.im >= 0 ? " + " : " - ") + - Math.abs(this.im).toFixed(n) + - "i" - ); - else return this.abs.toFixed(n) + " cis " + this.arg.toFixed(n); - } - - toExponential(n, polar) { - if (!polar) - return ( - this.re.toExponential(n) + - (this.im >= 0 ? " + " : " - ") + - Math.abs(this.im).toExponential(n) + - "i" - ); - else return this.abs.toExponential(n) + " cis " + this.arg.toExponential(n); - } - - static getReals(c, d) { - // when c or d may be simple or complex - var x, y, u, v; - if (c instanceof cx) { - x = c.re; - y = c.im; - } else { - x = c; - y = 0; - } - if (d instanceof cx) { - u = d.re; - v = d.im; - } else { - u = d; - v = 0; - } - return [x, y, u, v]; - } - - static conj(c) { - return new cx(c.re, -c.im); - } - - static neg(c) { - return new cx(-c.re, -c.im); - } - - static add(c, d) { - var a = cx.getReals(c, d); - var x = a[0]; - var y = a[1]; - var u = a[2]; - var v = a[3]; - return new cx(x + u, y + v); - } - - static sub(c, d) { - var a = cx.getReals(c, d); - var x = a[0]; - var y = a[1]; - var u = a[2]; - var v = a[3]; - return new cx(x - u, y - v); - } - - static mult(c, d) { - var a = cx.getReals(c, d); - var x = a[0]; - var y = a[1]; - var u = a[2]; - var v = a[3]; - return new cx(x * u - y * v, x * v + y * u); - } - - static div(c, d) { - var a = cx.getReals(c, d); - var x = a[0]; - var y = a[1]; - var u = a[2]; - var v = a[3]; - return new cx( - (x * u + y * v) / (u * u + v * v), - (y * u - x * v) / (u * u + v * v) - ); - } - - static pow(c, int) { - if (Number.isInteger(int) && int >= 0) { - var r = Math.pow(c.abs, int); - var theta = int * c._arg; - return new cx(r * Math.cos(theta), r * Math.sin(theta)); - } else return NaN; - } - - static root(c, int, k) { - if (!k) k = 0; - if ( - Number.isInteger(int) && - int >= 2 && - Number.isInteger(k) && - k >= 0 && - k < int - ) { - var r = Math.pow(c.abs, 1 / int); - var theta = (c._arg + 2 * k * Math.PI) / int; - return new cx(r * Math.cos(theta), r * Math.sin(theta)); - } else return NaN; - } - - static log(c) { - return new cx(Math.log(c.abs), c._arg); - } - - static exp(c) { - var r = Math.exp(c.re); - var theta = c.im; - return new cx(r * Math.cos(theta), r * Math.sin(theta)); - } - - static sin(c) { - var a = c.re; - var b = c.im; - return new cx(Math.sin(a) * Math.cosh(b), Math.cos(a) * Math.sinh(b)); - } - - static cos(c) { - var a = c.re; - var b = c.im; - return new cx(Math.cos(a) * Math.cosh(b), -Math.sin(a) * Math.sinh(b)); - } - - static tan(c) { - return cx.div(cx.sin(c), cx.cos(c)); - } - - static asin(c, k) { - if (!k) k = 0; - var ic = cx.mult(cx.i, c); - var c2 = cx.pow(c, 2); - return cx.mult( - cx.neg(cx.i), - cx.log(cx.add(ic, cx.root(cx.sub(1, c2), 2, k))) - ); - } - - static acos(c, k) { - if (!k) k = 0; - var c2 = cx.pow(c, 2); - return cx.mult( - cx.neg(cx.i), - cx.log(cx.add(c, cx.mult(cx.i, cx.root(cx.sub(1, c2), 2, k)))) - ); - } - - static atan(c) { - return cx.mult( - cx.div(cx.i, 2), - cx.log(cx.div(cx.add(cx.i, c), cx.sub(cx.i, c))) - ); - } - - static sinh(c) { - var a = c.re; - var b = c.im; - return new cx(Math.sinh(a) * Math.cos(b), Math.cosh(a) * Math.sin(b)); - } - - static cosh(c) { - var a = c.re; - var b = c.im; - return new cx(Math.cosh(a) * Math.cos(b), Math.sinh(a) * Math.sin(b)); - } - - static tanh(c) { - return cx.div(cx.sinh(c), cx.cosh(c)); - } - - static asinh(c, k) { - if (!k) k = 0; - var c2 = cx.pow(c, 2); - return cx.log(cx.add(c, cx.root(cx.add(c2, 1), 2, k))); - } - - static acosh(c, k) { - if (!k) k = 0; - var c2 = cx.pow(c, 2); - return cx.log(cx.add(c, cx.root(cx.sub(c2, 1), 2, k))); - } - - static atanh(c) { - return cx.mult(cx.div(1, 2), cx.log(cx.div(cx.add(1, c), cx.sub(1, c)))); - } - - static copy(c) { - return new cx(c.re, c.im); - } - - static eq(c, d, epsilon) { - if (!epsilon) { - if (c.re == d.re && c.im == d.im) return true; - } else { - if (Math.abs(c.re - d.re) < epsilon && Math.abs(c.im - d.im) < epsilon) - return true; - } - return false; - } -} - -cx.degrees(true); // need to call this diff --git a/src/toys/euler-golf/js/game.js b/src/toys/euler-golf/js/game.js deleted file mode 100644 index 5e123a0..0000000 --- a/src/toys/euler-golf/js/game.js +++ /dev/null @@ -1,275 +0,0 @@ -const DEFAULTS = { - max_rows: 80, - max_cols: 80, - min_gap: 30, - angle_multiplier: 10e-4, -}; - -const CANVAS = document.getElementById("canvas"); - -let state = { - grid_padding: 30, - gap: DEFAULTS.min_gap, - canvas: CANVAS, - ctx: CANVAS.getContext("2d"), - last_render: 0, - keys: {}, - changes: {}, -}; - -// Rendering -CanvasRenderingContext2D.prototype.circle = function (x, y, r, color) { - this.beginPath(); - this.arc(x, y, r, 0, Math.PI * 2); - this.fillStyle = color; - this.fill(); - this.closePath(); -}; - -CanvasRenderingContext2D.prototype.line = function ( - { x_pos: x1, y_pos: y1 }, - { x_pos: x2, y_pos: y2 }, - width, - color, - cap = "round" -) { - this.lineWidth = width; - this.strokeStyle = color; - this.lineCap = cap; - - this.beginPath(); - this.moveTo(x1, y1); - this.lineTo(x2, y2); - this.stroke(); - this.closePath(); -}; - -CanvasRenderingContext2D.prototype.draw_cartesian_path = function ( - grid_spec, - cartesian_path, - width = 2, - color = "#fff" -) { - const path = cartesian_path.map((coord) => grid_to_canvas(coord, grid_spec)); - path.slice(1).forEach((coord, i) => { - this.line(path[i], coord, width, color); - }); -}; - -CanvasRenderingContext2D.prototype.do_grid = function ( - rows, - cols, - draw_at_grid_pos = (ctx, x, y) => ctx.circle(x, y, 10, "#44ff44") -) { - for (let y = 0; y < rows; y++) { - for (let x = 0; x < cols; x++) { - draw_at_grid_pos(this, x, y); - } - } -}; - -CanvasRenderingContext2D.prototype.cartesian_grid = function ( - rows, - cols, - grid_spec, - circle_spec_at_coords = (_x, _y) => ({ radius: 5, color: "#000" }) -) { - this.do_grid(rows, cols, (ctx, x, y) => { - const { x_pos, y_pos } = grid_to_canvas({ x, y }, grid_spec); - const { radius, color } = circle_spec_at_coords(x, y); - - ctx.circle(x_pos, y_pos, radius, color); - }); -}; - -// Utilities -const move = (prev, curr, c) => cx.add(prev, cx.mult(c, cx.sub(curr, prev))); - -const rand_between = (min, max) => - Math.floor(Math.random() * (max - min + 1)) + min; - -const rand_target = (rows, cols) => { - const r = Math.floor((rows - 1) / 2); - const c = Math.floor((cols - 1) / 2); - const res = new cx(rand_between(-c, c), rand_between(-r, r)); - if (!sol(res)) return rand_target(rows, cols); - - return res; -}; - -const calculate_grid_spec = ({ rows, cols, width, height, grid_padding }) => { - const dx = (width - 2 * grid_padding) / cols; - const dy = (height - 2 * grid_padding) / rows; - - return { - dx, - dy, - start_x: grid_padding + dx / 2, - start_y: grid_padding + dy / 2, - }; -}; - -const grid_to_canvas = ({ x, y }, { dx, dy, start_x, start_y }) => ({ - x_pos: x * dx + start_x, - y_pos: y * dy + start_y, -}); - -const complex_to_grid = (c, rows, cols) => { - const { re, im } = c; - return { - x: re + Math.floor(cols / 2), - y: Math.floor(rows / 2) - im, - }; -}; - -// Game loop - -const maybe_add_state_angle_move = ({ angle } = state) => { - if (angle.im <= -1 || angle.im >= 1) { - angle.im = angle.im <= -1 ? -1 : 1; - state.path.push(move(state.path.at(-2), state.path.at(-1), angle)); - state.angle = new cx(0, 0); - } - return state; -}; - -const handle_input = (state, dt) => { - if (state.keys.ArrowLeft) { - state.angle.im += DEFAULTS.angle_multiplier * dt; - } else if (state.keys.ArrowRight) { - state.angle.im -= DEFAULTS.angle_multiplier * dt; - } - state = maybe_add_state_angle_move(state); -}; - -const render = ({ width, height, ctx, rows, cols, target, gap } = state) => { - ctx.clearRect(0, 0, width, height); - ctx.fillStyle = "rgba(0, 0, 0, 0)"; - ctx.fillRect(0, 0, width, height); - - const grid_spec = calculate_grid_spec(state); - - const curr = state.path.at(-1); - const prev = state.path.at(-2); - - const v_diff = cx.sub(curr, prev); - const theta = (state.angle.im * Math.PI) / 2; - - const angle_re = Math.cos(theta) * v_diff.re - Math.sin(theta) * v_diff.im; - const angle_im = Math.sin(theta) * v_diff.re + Math.cos(theta) * v_diff.im; - - ctx.draw_cartesian_path(grid_spec, [ - ...state.path.map((c) => complex_to_grid(c, rows, cols)), - complex_to_grid(cx.add(new cx(angle_re, angle_im), prev), rows, cols), - ]); - - if (!(state.angle.im == state.angle.re && state.angle.re == 0)) { - // Draw path to next player's target - const [a, b] = [ - curr, - move(prev, curr, new cx(0, state.angle.im < 0 ? -1 : 1)), - ].map((c) => grid_to_canvas(complex_to_grid(c, rows, cols), grid_spec)); - - ctx.line(a, b, 6, "rgba(127, 127, 127, 0.3)"); - } - - const grid_target = complex_to_grid(target, rows, cols); - ctx.cartesian_grid(rows, cols, grid_spec, (x, y) => { - if (x == Math.floor(cols / 2) && y == Math.floor(rows / 2)) { - return { - radius: 7, - color: "#2f9c94", - }; - } else if (x == grid_target.x && y == grid_target.y) { - return { - radius: 8, - color: "#fff", - }; - } else { - return { - radius: 3, - color: `rgb(${255 * (x / cols)}, 100, 100)`, // todo: animate with last_render - }; - } - }); - - // Render gap value in slider - document.getElementById("gap").value = gap; -}; - -const loop = (now) => { - const dt = now - state.last_render; - state.changes.last_render = now; - - if (Object.keys(state.changes).length > 0) { - state = { ...state, ...state.changes }; - - if (state.changes.width || state.changes.height || state.changes.gap) { - state.rows = Math.floor(state.height / state.gap); - state.cols = Math.floor(state.width / state.gap); - } - - state.changes = {}; - } - - if (!state.target) state.target = rand_target(state.rows, state.cols); - - if (!state.solution) { - handle_input(state, dt); - } else { - if (!state?.solution.length) { - delete state.solution; - } else { - state.angle.im += - (state.solution[0] === "-" ? 1 : -1) * DEFAULTS.angle_multiplier * dt; - - state = maybe_add_state_angle_move(state); - - if (cx.eq(state.angle, new cx(0, 0))) state.solution.shift(); - } - } - render(state); - requestAnimationFrame(loop); -}; - -const reset_state = ({ rows, cols } = state) => ({ - ...state, - solution: null, - path: [new cx(0, 0), new cx(1, 0)], - angle: new cx(0, 0), -}); - -// DOM -const directions_modal = new Modal({ - el: document.getElementById("directions-modal"), -}); - -const on_resize = () => { - CANVAS.width = document.body.clientWidth; - CANVAS.height = document.body.clientHeight; - state.changes.width = CANVAS.width; - state.changes.height = CANVAS.height; -}; - -const on_keyup = (e) => { - delete state.keys[e.key]; -}; - -const on_keydown = (e) => { - state.keys[e.key] = true; -}; - -window.addEventListener("resize", on_resize); -window.addEventListener("keydown", on_keydown); -window.addEventListener("keyup", on_keyup); - -// main -on_resize(); -state = reset_state(state); - -if (!sessionStorage.getItem("seen-instructions")) { - directions_modal.show(); - sessionStorage.setItem("seen-instructions", true); -} - -requestAnimationFrame(loop); diff --git a/src/toys/euler-golf/js/json-ds.js b/src/toys/euler-golf/js/json-ds.js deleted file mode 100644 index dc7e88e..0000000 --- a/src/toys/euler-golf/js/json-ds.js +++ /dev/null @@ -1,19 +0,0 @@ -class JSONSet { - items = new Set(); - - constructor(initial) { - if (Array.isArray(initial)) { - initial.map((x) => this.apply_set_function("add", x)); - } else { - this.apply_set_function("add", initial); - } - - ["add", "has", "remove"].forEach( - (f_name) => (this[f_name] = (x) => this.apply_set_function(f_name, x)) - ); - } - - apply_set_function(f_name, x) { - return this.items[f_name](JSON.stringify(x)); - } -} diff --git a/src/toys/euler-golf/js/modal-vanilla.min.js b/src/toys/euler-golf/js/modal-vanilla.min.js deleted file mode 100644 index 0d314c7..0000000 --- a/src/toys/euler-golf/js/modal-vanilla.min.js +++ /dev/null @@ -1 +0,0 @@ -var Modal=function(e){function t(i){if(n[i])return n[i].exports;var o=n[i]={i:i,l:!1,exports:{}};return e[i].call(o.exports,o,o.exports,t),o.l=!0,o.exports}var n={};return t.m=e,t.c=n,t.d=function(e,n,i){t.o(e,n)||Object.defineProperty(e,n,{configurable:!1,enumerable:!0,get:i})},t.n=function(e){var n=e&&e.__esModule?function(){return e.default}:function(){return e};return t.d(n,"a",n),n},t.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},t.p="",t(t.s=0)}([function(e,t,n){e.exports=n(1).default},function(e,t,n){"use strict";function i(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}function o(e,t){if(!e)throw new ReferenceError("this hasn't been initialised - super() hasn't been called");return!t||"object"!=typeof t&&"function"!=typeof t?e:t}function s(e,t){if("function"!=typeof t&&null!==t)throw new TypeError("Super expression must either be null or a function, not "+typeof t);e.prototype=Object.create(t&&t.prototype,{constructor:{value:e,enumerable:!1,writable:!0,configurable:!0}}),t&&(Object.setPrototypeOf?Object.setPrototypeOf(e,t):e.__proto__=t)}function r(e){for(var t in e)Array.isArray(e[t])?e[t].forEach(function(e){r(e)}):null!==e[t]&&"object"===p(e[t])&&Object.freeze(e[t]);return Object.freeze(e)}function a(){return(65536*(1+Math.random())|0).toString(16)+(65536*(1+Math.random())|0).toString(16)}function l(e,t,n){var i=e.data||{};if(void 0===n){if(e.data&&e.data[t])return e.data[t];var o=e.getAttribute("data-"+t);return void 0!==o?o:null}return i[t]=n,e.data=i,e}function d(e,t){return e.nodeName?e:(e=e.replace(/(\t|\n$)/g,""),_||(_=document.createElement("div")),_.innerHTML="",_.innerHTML=e,!0===t?_.childNodes:_.childNodes[0])}function c(){var e=void 0,t=void 0,n=void 0,i=document.createElement("div");return v(i.style,{visibility:"hidden",width:"100px"}),document.body.appendChild(i),n=i.offsetWidth,i.style.overflow="scroll",e=document.createElement("div"),e.style.width="100%",i.appendChild(e),t=n-e.offsetWidth,document.body.removeChild(i),t}function h(e){for(var t=[e];e.parentNode;)e=e.parentNode,t.push(e);return t}Object.defineProperty(t,"__esModule",{value:!0});var u=function(){function e(e,t){for(var n=0;n',dialog:'',content:'',header:'',headerClose:'',body:'',footer:'',backdrop:''},k=function(e){function t(){var e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{};i(this,t);var n=o(this,(t.__proto__||Object.getPrototypeOf(t)).call(this));if(n.id=a(),n.el=null,n._html={},n._events={},n._visible=!1,n._pointerInContent=!1,n._options=v({},t.options,e),n._templates=v({},t.templates,e.templates||{}),n._html.appendTo=document.querySelector(n._options.appendTo),n._scrollbarWidth=c(),null===n._options.buttons&&(n._options.buttons=t.buttons.dialog),n._options.el){var s=n._options.el;if("string"==typeof n._options.el&&!(s=document.querySelector(n._options.el)))throw new Error("Selector: DOM Element "+n._options.el+" not found.");l(s,"modal",n),n.el=s}else n._options.construct=!0;return n._options.construct?n._render():n._mapDom(),n}return s(t,e),u(t,null,[{key:"alert",value:function(e){var n=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{};return new t(v({},y,{title:e,content:!1,construct:!0,headerClose:!1,buttons:t.buttons.alert},n))}},{key:"confirm",value:function(e){var n=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{};return new t(v({},y,{title:e,content:!1,construct:!0,headerClose:!1,buttons:t.buttons.confirm},n))}},{key:"templates",set:function(e){this._baseTemplates=e},get:function(){return v({},g,t._baseTemplates||{})}},{key:"buttons",set:function(e){this._baseButtons=e},get:function(){return v({},b,t._baseButtons||{})}},{key:"options",set:function(e){this._baseOptions=e},get:function(){return v({},y,t._baseOptions||{})}},{key:"version",get:function(){return"0.12.0"}}]),u(t,[{key:"_render",value:function(){var e=this._html,t=this._options,n=this._templates,i=!!t.animate&&t.animateClass;return e.container=d(n.container),e.dialog=d(n.dialog),e.content=d(n.content),e.header=d(n.header),e.headerClose=d(n.headerClose),e.body=d(n.body),e.footer=d(n.footer),i&&e.container.classList.add(i),this._setHeader(),this._setContent(),this._setFooter(),this.el=e.container,e.dialog.appendChild(e.content),e.container.appendChild(e.dialog),this}},{key:"_mapDom",value:function(){var e=this._html,t=this._options;return this.el.classList.contains(t.animateClass)&&(t.animate=!0),e.container=this.el,e.dialog=this.el.querySelector(".modal-dialog"),e.content=this.el.querySelector(".modal-content"),e.header=this.el.querySelector(".modal-header"),e.headerClose=this.el.querySelector(".modal-header .close"),e.body=this.el.querySelector(".modal-body"),e.footer=this.el.querySelector(".modal-footer"),this._setHeader(),this._setContent(),this._setFooter(),this}},{key:"_setHeader",value:function(){var e=this._html,t=this._options;t.header&&e.header&&(t.title.nodeName?e.header.innerHTML=t.title.outerHTML:"string"==typeof t.title&&(e.header.innerHTML='"),null===this.el&&e.headerClose&&t.headerClose&&e.header.appendChild(e.headerClose),t.construct&&e.content.appendChild(e.header))}},{key:"_setContent",value:function(){var e=this._html,t=this._options;t.content&&e.body&&("string"==typeof t.content?e.body.innerHTML=t.content:e.body.innerHTML=t.content.outerHTML,t.construct&&e.content.appendChild(e.body))}},{key:"_setFooter",value:function(){var e=this._html,t=this._options;t.footer&&e.footer&&(t.footer.nodeName?e.footer.ineerHTML=t.footer.outerHTML:"string"==typeof t.footer?e.footer.innerHTML=t.footer:e.footer.children.length||t.buttons.forEach(function(t){var n=document.createElement("button");l(n,"button",t),n.innerHTML=t.text,n.setAttribute("type","button");for(var i in t.attr)n.setAttribute(i,t.attr[i]);e.footer.appendChild(n)}),t.construct&&e.content.appendChild(e.footer))}},{key:"_setEvents",value:function(){var e=(this._options,this._html);this._events.keydownHandler=this._handleKeydownEvent.bind(this),document.body.addEventListener("keydown",this._events.keydownHandler),this._events.mousedownHandler=this._handleMousedownEvent.bind(this),e.container.addEventListener("mousedown",this._events.mousedownHandler),this._events.clickHandler=this._handleClickEvent.bind(this),e.container.addEventListener("click",this._events.clickHandler),this._events.resizeHandler=this._handleResizeEvent.bind(this),window.addEventListener("resize",this._events.resizeHandler)}},{key:"_handleMousedownEvent",value:function(e){var t=this;this._pointerInContent=!1,h(e.target).every(function(e){return!e.classList||!e.classList.contains("modal-content")||(t._pointerInContent=!0,!1)})}},{key:"_handleClickEvent",value:function(e){var t=this;h(e.target).every(function(n){return!("HTML"===n.tagName||!0!==t._options.backdrop&&n.classList.contains("modal")||n.classList.contains("modal-content")||("modal"===n.getAttribute("data-dismiss")?(t.emit("dismiss",t,e,l(e.target,"button")),t.hide(),1):!t._pointerInContent&&n.classList.contains("modal")&&(t.emit("dismiss",t,e,null),t.hide(),1)))}),this._pointerInContent=!1}},{key:"_handleKeydownEvent",value:function(e){27===e.which&&this._options.keyboard&&(this.emit("dismiss",this,e,null),this.hide())}},{key:"_handleResizeEvent",value:function(e){this._resize()}},{key:"show",value:function(){var e=this,t=this._options,n=this._html;return this.emit("show",this),this._checkScrollbar(),this._setScrollbar(),document.body.classList.add("modal-open"),t.construct&&n.appendTo.appendChild(n.container),n.container.style.display="block",n.container.scrollTop=0,!1!==t.backdrop?(this.once("showBackdrop",function(){e._setEvents(),t.animate&&n.container.offsetWidth,n.container.classList.add(t.animateInClass),setTimeout(function(){e._visible=!0,e.emit("shown",e)},t.transition)}),this._backdrop()):(this._setEvents(),t.animate&&n.container.offsetWidth,n.container.classList.add(t.animateInClass),setTimeout(function(){e._visible=!0,e.emit("shown",e)},t.transition)),this._resize(),this}},{key:"toggle",value:function(){this._visible?this.hide():this.show()}},{key:"_resize",value:function(){var e=this._html.container.scrollHeight>document.documentElement.clientHeight;this._html.container.style.paddingLeft=!this.bodyIsOverflowing&&e?this._scrollbarWidth+"px":"",this._html.container.style.paddingRight=this.bodyIsOverflowing&&!e?this._scrollbarWidth+"px":""}},{key:"_backdrop",value:function(){var e=this,t=this._html,n=this._templates,i=this._options,o=!!i.animate&&i.animateClass;t.backdrop=d(n.backdrop),o&&t.backdrop.classList.add(o),t.appendTo.appendChild(t.backdrop),o&&t.backdrop.offsetWidth,t.backdrop.classList.add(i.animateInClass),setTimeout(function(){e.emit("showBackdrop",e)},this._options.backdropTransition)}},{key:"hide",value:function(){var e=this,t=this._html,n=this._options,i=t.container.classList;if(this.emit("hide",this),i.remove(n.animateInClass),n.backdrop){t.backdrop.classList.remove(n.animateInClass)}return this._removeEvents(),setTimeout(function(){document.body.classList.remove("modal-open"),document.body.style.paddingRight=e.originalBodyPad},n.backdropTransition),setTimeout(function(){n.backdrop&&t.backdrop.parentNode.removeChild(t.backdrop),t.container.style.display="none",n.construct&&t.container.parentNode.removeChild(t.container),e._visible=!1,e.emit("hidden",e)},n.transition),this}},{key:"_removeEvents",value:function(){this._events.keydownHandler&&document.body.removeEventListener("keydown",this._events.keydownHandler),this._html.container.removeEventListener("mousedown",this._events.mousedownHandler),this._html.container.removeEventListener("click",this._events.clickHandler),window.removeEventListener("resize",this._events.resizeHandler)}},{key:"_checkScrollbar",value:function(){this.bodyIsOverflowing=document.body.clientWidth0&&this._events[e].length>o&&(this._events[e].warned=!0,console.error("(node) warning: possible EventEmitter memory leak detected. %d listeners added. Use emitter.setMaxListeners() to increase limit.",this._events[e].length),"function"==typeof console.trace&&console.trace()),this},n.prototype.on=n.prototype.addListener,n.prototype.once=function(e,t){function n(){this.removeListener(e,n),o||(o=!0,t.apply(this,arguments))}if(!i(t))throw TypeError("listener must be a function");var o=!1;return n.listener=t,this.on(e,n),this},n.prototype.removeListener=function(e,t){var n,o,r,a;if(!i(t))throw TypeError("listener must be a function");if(!this._events||!this._events[e])return this;if(n=this._events[e],r=n.length,o=-1,n===t||i(n.listener)&&n.listener===t)delete this._events[e],this._events.removeListener&&this.emit("removeListener",e,t);else if(s(n)){for(a=r;a-- >0;)if(n[a]===t||n[a].listener&&n[a].listener===t){o=a;break}if(o<0)return this;1===n.length?(n.length=0,delete this._events[e]):n.splice(o,1),this._events.removeListener&&this.emit("removeListener",e,t)}return this},n.prototype.removeAllListeners=function(e){var t,n;if(!this._events)return this;if(!this._events.removeListener)return 0===arguments.length?this._events={}:this._events[e]&&delete this._events[e],this;if(0===arguments.length){for(t in this._events)"removeListener"!==t&&this.removeAllListeners(t);return this.removeAllListeners("removeListener"),this._events={},this}if(n=this._events[e],i(n))this.removeListener(e,n);else if(n)for(;n.length;)this.removeListener(e,n[n.length-1]);return delete this._events[e],this},n.prototype.listeners=function(e){return this._events&&this._events[e]?i(this._events[e])?[this._events[e]]:this._events[e].slice():[]},n.prototype.listenerCount=function(e){if(this._events){var t=this._events[e];if(i(t))return 1;if(t)return t.length}return 0},n.listenerCount=function(e,t){return e.listenerCount(t)}}]); diff --git a/src/toys/euler-golf/js/sol.js b/src/toys/euler-golf/js/sol.js deleted file mode 100644 index b4e527f..0000000 --- a/src/toys/euler-golf/js/sol.js +++ /dev/null @@ -1,44 +0,0 @@ -const DEPTH = 15; - -const DIRECTION = { - 0: new cx(0, 1), - 1: new cx(0, -1), -}; - -const construct_moves = (curr, prev) => - Object.keys(DIRECTION).map((x) => move(curr, prev, DIRECTION[x])); - -const backtrack = (local_index, depth) => - local_index - .toString(2) - .padStart(depth, "0") - .split("") - .map((direction) => (Number(direction) ? "+" : "-")); - -const sol = (target, start_from = new cx(0, 0), start_to = new cx(1, 0)) => { - const next_moves = construct_moves(start_from, start_to); - const solved_in_first_move = next_moves.findIndex((move) => - cx.eq(move, target) - ); - if (solved_in_first_move != -1) return backtrack(solved_in_first_move, 1); - - let moves = [start_to, ...next_moves]; - let curr_depth = 2; - while (curr_depth < DEPTH) { - for (let i = 0; i < Math.pow(2, curr_depth); i++) { - const direction = DIRECTION[Number(i.toString(2).at(-1))]; - // Current element is at i >> 1 + the offset for the previous group (which is - // the sum of the geometric series 2**n until curr_depth - 1) - const current_i = (i >> 1) + (1 - Math.pow(2, curr_depth - 1)) / (1 - 2); - const previous_i = (i >> 2) + (1 - Math.pow(2, curr_depth - 2)) / (1 - 2); - - const new_move = move(moves[previous_i], moves[current_i], direction); - - moves.push(new_move); - if (cx.eq(new_move, target)) return backtrack(i, curr_depth); - } - curr_depth++; - } - - return null; -}; diff --git a/src/toys/fourier/index.html b/src/toys/fourier/index.html deleted file mode 100644 index ccb8f48..0000000 --- a/src/toys/fourier/index.html +++ /dev/null @@ -1,46 +0,0 @@ - - - - Simponic's FT Visualizer - - - -
-
-
- -
-
- - - - - diff --git a/src/toys/fourier/js/script.js b/src/toys/fourier/js/script.js deleted file mode 100644 index 8d5af31..0000000 --- a/src/toys/fourier/js/script.js +++ /dev/null @@ -1,294 +0,0 @@ -const RENDER_TYPE = { - LATEX: 1, - FUNC: 2, -}; -const THRESHOLD = 1e-12; -const FONT = "Courier New"; -const FONT_HEIGHT_PX = 24; -const DX = 4; - -const canvas = document.getElementById("canvas"); -const ctx = canvas.getContext("2d"); -let state = {}; -const initializeState = () => { - const xLabels = [ - "Jan", - "Feb", - "Mar", - "Apr", - "May", - "Jun", - "Jul", - "Aug", - "Sept", - "Oct", - "Nov", - "Dec", - "Jan", - ]; - const yLabels = ["Great", "Good", "Meh", "Bad", "Horrible"]; - return { - width: canvas.parentElement.clientWidth, - height: canvas.parentElement.clientHeight, - xLabels, - yLabels, - yLabelPadding: 12, - heights: Array(xLabels.length * DX - 1).fill(0), - }; -}; - -const dft = (heights, render = RENDER_TYPE.LATEX, threshold = THRESHOLD) => { - const n = heights.length; - return Array(n) - .fill() - .map((x, w) => { - const rate = -2 * Math.PI * w; - const s = heights.reduce( - (a, x, i) => ({ - re: a.re + x * Math.cos((rate * i) / n), - im: a.im + x * Math.sin((rate * i) / n), - }), - { re: 0, im: 0 } - ); - - Object.entries(s).forEach( - ([key, value]) => - (s[key] = ((Math.abs(value) < threshold ? 0 : 1) * value) / n) - ); - - const amp = Math.sqrt(s.re * s.re + s.im * s.im); - const phase = Math.atan2(s.im, s.re); - - switch (render) { - case RENDER_TYPE.LATEX: - return `${amp}\\cos\\left(${w}\\frac{${ - 2 * DX - }\\pi}{${n}}x+${phase}\\right)`; - case RENDER_TYPE.FUNC: - return (t) => amp * Math.cos(w * t + phase); - } - }); -}; - -const resizeCanvas = ({ width, height }) => { - canvas.width = width; - canvas.style.width = width; - canvas.height = height; - canvas.style.height = height; -}; - -const loop = () => { - const stateChanges = Object.keys(state.diff); - if (stateChanges.length > 0) { - state = { ...state, ...state.diff }; - if ( - state.diff.width || - state.diff.height || - state.diff.xLabels || - state.diff.yLabels - ) { - resizeCanvas(state.diff); - ctx.font = `${FONT_HEIGHT_PX}px ${FONT}`; - state.maxYLabelWidth = state.yLabels.reduce( - (a, label) => Math.max(ctx.measureText(label).width, a), - -Infinity - ); - - state.gridBoxWidth = - state.width - state.maxYLabelWidth - state.yLabelPadding; - state.gridBoxHeight = state.height - 2.5 * FONT_HEIGHT_PX; // 2.5 to include bottom part of tall letters ("g", "y", etc.) - - state.topLeftGridPos = { - x: state.maxYLabelWidth + state.yLabelPadding, - y: FONT_HEIGHT_PX, - }; - - state.bottomRightGridPos = { - x: state.topLeftGridPos.x + state.gridBoxWidth, - y: state.topLeftGridPos.y + state.gridBoxHeight, - }; - } - if (state.diff.heights) drawDesmos(); - draw(state); - state.diff = {}; - } - - requestAnimationFrame(loop); -}; - -const drawLine = (pos1, pos2) => { - ctx.beginPath(); - ctx.moveTo(pos1.x, pos1.y); - ctx.lineTo(pos2.x, pos2.y); - ctx.stroke(); -}; - -const drawDividers = ( - xDividers, - yDividers, - topLeftGridPos, - bottomRightGridPos, - yLabelPadding -) => { - ctx.font = `${FONT_HEIGHT_PX}px ${FONT}`; - xDividers.forEach(({ label, position }) => { - ctx.fillText(label, position.x - ctx.measureText(label).width, position.y); - drawLine( - { ...position, y: topLeftGridPos.y }, - { ...position, y: bottomRightGridPos.y } - ); - }); - yDividers.forEach(({ label, position }) => { - ctx.fillText( - label, - topLeftGridPos.x - yLabelPadding - ctx.measureText(label).width, - position.y - ); - drawLine( - { ...position, x: topLeftGridPos.x }, - { ...position, x: bottomRightGridPos.x } - ); - }); -}; - -const draw = ({ - heights, - gridBoxWidth, - gridBoxHeight, - topLeftGridPos, - bottomRightGridPos, - maxYLabelWidth, - xLabels, - yLabels, - width, - height, -}) => { - ctx.clearRect(0, 0, width, height); - - const xDividers = xLabels.map((label, i) => ({ - label, - position: { - x: topLeftGridPos.x + (gridBoxWidth / (xLabels.length - 1)) * i, - y: bottomRightGridPos.y + FONT_HEIGHT_PX, - }, - })); - - const yDividers = yLabels.map((label, i) => ({ - label, - position: { - x: 0, - y: topLeftGridPos.y + (gridBoxHeight / (yLabels.length - 1)) * i, - }, - })); - - drawDividers(xDividers, yDividers, topLeftGridPos, bottomRightGridPos, 12); - - const dx = gridBoxWidth / (DX * (xLabels.length - 1)); - const prevStrokeStyle = ctx.strokeStyle; - ctx.strokeStyle = "red"; - for (let i = 0; i < heights.length; ++i) { - const x = dx * i + topLeftGridPos.x; - drawLine( - { x, y: (gridBoxHeight / 2) * (1 - heights[i]) + topLeftGridPos.y }, - { - x: x + dx, - y: (gridBoxHeight / 2) * (1 - heights[i + 1]) + topLeftGridPos.y, - } - ); - } - ctx.strokeStyle = prevStrokeStyle; -}; - -const calculator = Desmos.GraphingCalculator( - document.getElementById("calculator"), - { - expressionsCollapsed: true, - autosize: true, - } -); -calculator.setMathBounds({ - left: -0.8, - right: 12, - bottom: -3, - top: 3, -}); - -const drawDesmos = () => { - const equations = dft(state.heights); - - calculator.setExpression({ - id: `graph-total`, - latex: equations.map((_x, i) => `y_{${i}}`).join(" + "), - }); - equations.forEach((x, i) => - calculator.setExpression({ - id: `graph${i}`, - latex: `y_{${i}}=${x}`, - hidden: true, - }) - ); -}; - -let isDown = false; -canvas.addEventListener( - "mousedown", - (e) => { - e.preventDefault(); - isDown = true; - }, - true -); - -canvas.addEventListener( - "mouseup", - (e) => { - e.preventDefault(); - isDown = false; - }, - true -); - -canvas.addEventListener( - "mousemove", - (e) => { - e.preventDefault(); - if (isDown) { - const rect = canvas.getBoundingClientRect(); - const [x, y] = [e.clientX - rect.left, e.clientY - rect.top]; - - const { - topLeftGridPos, - bottomRightGridPos, - gridBoxWidth, - gridBoxHeight, - heights, - xLabels, - } = state; - const delta = gridBoxWidth / (DX * (xLabels.length - 1)); - - const bin = Math.min( - Math.round(Math.max(x - topLeftGridPos.x, 0) / delta), - heights.length - 1 - ); - heights[bin] = Math.min( - Math.max(1 - (2 * (y - topLeftGridPos.y)) / gridBoxHeight, -1), - 1 - ); - state.diff.heights = heights; - } - }, - true -); - -window.addEventListener("resize", () => { - state.diff = { - ...state.diff, - width: canvas.parentElement.clientWidth, - height: canvas.parentElement.clientHeight, - }; -}); - -(() => { - state.diff = initializeState(); - window.requestAnimationFrame(loop); -})(); diff --git a/src/toys/godel/index.html b/src/toys/godel/index.html deleted file mode 100644 index 50a680a..0000000 --- a/src/toys/godel/index.html +++ /dev/null @@ -1,51 +0,0 @@ - - - - - - - Gödel Numbering - - -
-
-

Gödel Numbering

-
-
-

L Source

-
-
- - -
- -
-
- -
-
-

"Compiled" JS

-
-
- -
- -
-
- -
-

Gödel Sequence

-
Compile to view the Gödel sequence.
-
- -
-

-      
-
-
- - - - - - diff --git a/src/toys/godel/js/compiler.js b/src/toys/godel/js/compiler.js deleted file mode 100644 index d15c0f4..0000000 --- a/src/toys/godel/js/compiler.js +++ /dev/null @@ -1,226 +0,0 @@ -const INDENT_SIZE = 2; - -class CodeBuilder { - constructor(indentSize = INDENT_SIZE) { - this.indentSize = indentSize; - this.indentLevel = 0; - this.parts = []; - } - - addLine(line = "") { - const indent = " ".repeat(this.indentLevel * this.indentSize); - this.parts.push(line ? `${indent}${line}` : ""); - } - - open(line) { - if (line) { - this.addLine(line); - } - this.indentLevel += 1; - } - - close(line) { - this.indentLevel = Math.max(0, this.indentLevel - 1); - if (line) { - this.addLine(line); - } - } - - toString() { - return this.parts.join("\n"); - } -} - -const compileGoto = (gotoNode, builder) => { - builder.addLine(`this.followGoto("${gotoNode.label.symbol}");`); - builder.addLine("return;"); -}; - -const compileConditional = (conditionalNode, builder) => { - const variable = conditionalNode.variable.symbol; - builder.addLine(`if (this.get("${variable}") !== 0) {`); - builder.open(); - compileGoto(conditionalNode.goto, builder); - builder.close("}"); -}; - -const compileAssignment = (assignmentNode, builder) => { - const variable = assignmentNode.variable.symbol; - const expr = assignmentNode.expr || {}; - - if (expr.opr === "+") { - builder.addLine(`this.addOne("${variable}");`); - } else if (expr.opr === "-") { - builder.addLine(`this.subtractOne("${variable}");`); - } else { - builder.addLine("// noop"); - } -}; - -const compileInstruction = (instruction, builder) => { - if (instruction.goto) { - compileGoto(instruction.goto, builder); - return; - } - - if (instruction.conditional) { - compileConditional(instruction.conditional, builder); - } else if (instruction.assignment) { - compileAssignment(instruction.assignment, builder); - } - - builder.addLine("this.instructionPointer++;"); -}; - -const emitMethod = (builder, signature, bodyFn) => { - builder.addLine(`${signature} {`); - builder.open(); - bodyFn(); - builder.close("}"); - builder.addLine(""); -}; - -const emitConstructor = (builder, ast, godelSequence, methodCatalog) => { - emitMethod(builder, "constructor()", () => { - builder.addLine("this.variables = new Map();"); - builder.addLine("this.labelInstructions = new Map();"); - builder.addLine("this.instructions = new Map();"); - builder.addLine("this.instructions.set(0, () => this.main());"); - builder.addLine("this.instructionPointer = 0;"); - builder.addLine('this.variables.set("Y", 0);'); - builder.addLine(`this.finalInstruction = ${ast.instructions.length + 1};`); - builder.addLine('this.labelInstructions.set("E1", this.finalInstruction);'); - builder.addLine(""); - builder.addLine("// instruction bindings"); - - ast.instructions.forEach((entry, index) => { - const instructionNode = entry.instruction; - const instructionIdx = index + 1; - godelSequence.push(entry.godel); - - if (instructionNode.label) { - const labelName = instructionNode.label.symbol; - builder.addLine( - `this.instructions.set(${instructionIdx}, () => this.${labelName}());` - ); - builder.addLine( - `this.labelInstructions.set("${labelName}", ${instructionIdx});` - ); - methodCatalog.push({ - name: labelName, - index: instructionIdx, - node: instructionNode.instruction - }); - } else { - const methodName = `instruction${instructionIdx}`; - builder.addLine( - `this.instructions.set(${instructionIdx}, () => this.${methodName}());` - ); - methodCatalog.push({ - name: methodName, - index: instructionIdx, - node: instructionNode - }); - } - }); - }); -}; - -const emitRuntimeHelpers = (builder, instructionCount) => { - emitMethod(builder, "get(variable)", () => { - builder.addLine("if (!this.variables.has(variable)) {"); - builder.open(); - builder.addLine("this.variables.set(variable, 0);"); - builder.close("}"); - builder.addLine("return this.variables.get(variable);"); - }); - - emitMethod(builder, "addOne(variable)", () => { - builder.addLine("const val = this.get(variable);"); - builder.addLine("this.variables.set(variable, val + 1);"); - }); - - emitMethod(builder, "subtractOne(variable)", () => { - builder.addLine("const val = this.get(variable);"); - builder.addLine("this.variables.set(variable, val - 1);"); - }); - - emitMethod(builder, "followGoto(label)", () => { - builder.addLine("this.instructionPointer = this.labelInstructions.get(label);"); - }); - - emitMethod(builder, "step()", () => { - builder.addLine("if (!this.isCompleted()) {"); - builder.open(); - builder.addLine("const procedure = this.instructions.get(this.instructionPointer);"); - builder.addLine("procedure();"); - builder.close("}"); - builder.addLine("return this.instructionPointer;"); - }); - - emitMethod(builder, "isCompleted()", () => { - builder.addLine("return this.instructionPointer === this.finalInstruction;"); - }); - - emitMethod(builder, "getResult()", () => { - builder.addLine('return this.variables.get("Y");'); - }); - - emitMethod(builder, "run(maxIter = 500_000)", () => { - builder.addLine("let iter = 0;"); - builder.addLine("while (!this.isCompleted() && ++iter < maxIter) {"); - builder.open(); - builder.addLine("this.step();"); - builder.close("}"); - builder.addLine("if (iter < maxIter) {"); - builder.open(); - builder.addLine("return this.getResult();"); - builder.close("}"); - builder.addLine( - 'throw new Error("Program exceeded iteration limit. Try optimizing your instructions or increasing the cap.");' - ); - }); - - emitMethod(builder, "main()", () => { - if (instructionCount > 0) { - builder.addLine("this.instructionPointer = 1;"); - } else { - builder.addLine("this.instructionPointer = this.finalInstruction;"); - } - }); -}; - -const emitInstructionMethods = (builder, catalog) => { - catalog.forEach((entry) => { - emitMethod(builder, `${entry.name}()`, () => { - builder.addLine(`this.instructionPointer = ${entry.index};`); - compileInstruction(entry.node, builder); - }); - }); -}; - -export const compileProgram = (ast) => { - const builder = new CodeBuilder(); - const godelSequence = []; - const methodCatalog = []; - - builder.addLine("class Program {"); - builder.open(); - emitConstructor(builder, ast, godelSequence, methodCatalog); - emitRuntimeHelpers(builder, ast.instructions.length); - emitInstructionMethods(builder, methodCatalog); - builder.close("}"); - builder.addLine(""); - builder.addLine("// bootstrap"); - builder.addLine("const program = new Program();"); - builder.addLine('// program.variables.set("X1", 2);'); - builder.addLine('// program.variables.set("X2", 3);'); - builder.addLine("program.run();"); - builder.addLine("console.log(program.variables);"); - builder.addLine("program.getResult();"); - - return { - js: builder.toString(), - godelSequence - }; -}; diff --git a/src/toys/godel/js/godel-worker.js b/src/toys/godel/js/godel-worker.js deleted file mode 100644 index 1ba9da4..0000000 --- a/src/toys/godel/js/godel-worker.js +++ /dev/null @@ -1,42 +0,0 @@ -const isPrime = (n) => { - if (n < 2) { - return false; - } - if (n === 2) { - return true; - } - if (n % 2 === 0) { - return false; - } - const limit = Math.floor(Math.sqrt(n)); - for (let i = 3; i <= limit; i += 2) { - if (n % i === 0) { - return false; - } - } - return true; -}; - -const primes = [2]; -const primeAt = (index) => { - while (primes.length < index) { - let candidate = primes[primes.length - 1] + 1; - while (!isPrime(candidate)) { - candidate += 1; - } - primes.push(candidate); - } - return primes[index - 1]; -}; - -const computeGodelNumber = (sequence) => { - return sequence.reduce((acc, exponent, idx) => { - const prime = BigInt(primeAt(idx + 1)); - return acc * prime ** BigInt(exponent); - }, BigInt(1)) - BigInt(1); -}; - -self.addEventListener("message", (event) => { - const result = computeGodelNumber(event.data); - self.postMessage(result.toString()); -}); diff --git a/src/toys/godel/js/main.js b/src/toys/godel/js/main.js deleted file mode 100644 index b752206..0000000 --- a/src/toys/godel/js/main.js +++ /dev/null @@ -1,5 +0,0 @@ -import { GodelPlayground } from "./ui.js"; - -document.addEventListener("DOMContentLoaded", () => { - new GodelPlayground(); -}); diff --git a/src/toys/godel/js/parser.js b/src/toys/godel/js/parser.js deleted file mode 100644 index a9436b6..0000000 --- a/src/toys/godel/js/parser.js +++ /dev/null @@ -1,1059 +0,0 @@ -const parser = /* - * Generated by PEG.js 0.10.0. - * - * http://pegjs.org/ - */ -(function() { - "use strict"; - - function peg$subclass(child, parent) { - function ctor() { this.constructor = child; } - ctor.prototype = parent.prototype; - child.prototype = new ctor(); - } - - function peg$SyntaxError(message, expected, found, location) { - this.message = message; - this.expected = expected; - this.found = found; - this.location = location; - this.name = "SyntaxError"; - - if (typeof Error.captureStackTrace === "function") { - Error.captureStackTrace(this, peg$SyntaxError); - } - } - - peg$subclass(peg$SyntaxError, Error); - - peg$SyntaxError.buildMessage = function(expected, found) { - var DESCRIBE_EXPECTATION_FNS = { - literal: function(expectation) { - return "\"" + literalEscape(expectation.text) + "\""; - }, - - "class": function(expectation) { - var escapedParts = "", - i; - - for (i = 0; i < expectation.parts.length; i++) { - escapedParts += expectation.parts[i] instanceof Array - ? classEscape(expectation.parts[i][0]) + "-" + classEscape(expectation.parts[i][1]) - : classEscape(expectation.parts[i]); - } - - return "[" + (expectation.inverted ? "^" : "") + escapedParts + "]"; - }, - - any: function(expectation) { - return "any character"; - }, - - end: function(expectation) { - return "end of input"; - }, - - other: function(expectation) { - return expectation.description; - } - }; - - function hex(ch) { - return ch.charCodeAt(0).toString(16).toUpperCase(); - } - - function literalEscape(s) { - return s - .replace(/\\/g, '\\\\') - .replace(/"/g, '\\"') - .replace(/\0/g, '\\0') - .replace(/\t/g, '\\t') - .replace(/\n/g, '\\n') - .replace(/\r/g, '\\r') - .replace(/[\x00-\x0F]/g, function(ch) { return '\\x0' + hex(ch); }) - .replace(/[\x10-\x1F\x7F-\x9F]/g, function(ch) { return '\\x' + hex(ch); }); - } - - function classEscape(s) { - return s - .replace(/\\/g, '\\\\') - .replace(/\]/g, '\\]') - .replace(/\^/g, '\\^') - .replace(/-/g, '\\-') - .replace(/\0/g, '\\0') - .replace(/\t/g, '\\t') - .replace(/\n/g, '\\n') - .replace(/\r/g, '\\r') - .replace(/[\x00-\x0F]/g, function(ch) { return '\\x0' + hex(ch); }) - .replace(/[\x10-\x1F\x7F-\x9F]/g, function(ch) { return '\\x' + hex(ch); }); - } - - function describeExpectation(expectation) { - return DESCRIBE_EXPECTATION_FNS[expectation.type](expectation); - } - - function describeExpected(expected) { - var descriptions = new Array(expected.length), - i, j; - - for (i = 0; i < expected.length; i++) { - descriptions[i] = describeExpectation(expected[i]); - } - - descriptions.sort(); - - if (descriptions.length > 0) { - for (i = 1, j = 1; i < descriptions.length; i++) { - if (descriptions[i - 1] !== descriptions[i]) { - descriptions[j] = descriptions[i]; - j++; - } - } - descriptions.length = j; - } - - switch (descriptions.length) { - case 1: - return descriptions[0]; - - case 2: - return descriptions[0] + " or " + descriptions[1]; - - default: - return descriptions.slice(0, -1).join(", ") - + ", or " - + descriptions[descriptions.length - 1]; - } - } - - function describeFound(found) { - return found ? "\"" + literalEscape(found) + "\"" : "end of input"; - } - - return "Expected " + describeExpected(expected) + " but " + describeFound(found) + " found."; - }; - - function peg$parse(input, options) { - options = options !== void 0 ? options : {}; - - var peg$FAILED = {}, - - peg$startRuleFunctions = { Program: peg$parseProgram }, - peg$startRuleFunction = peg$parseProgram, - - peg$c0 = /^[\n]/, - peg$c1 = peg$classExpectation(["\n"], false, false), - peg$c2 = function(lines) { - return { instructions: lines.filter((line) => typeof line !== "string" || line.trim() != "") }; - }, - peg$c3 = function(instruction) { - let x = 0; - let y = 0; - if (instruction.label) { - x = instruction.label.godel; - y = instruction.instruction.godel; - } else { - y = instruction.godel; - } - return { instruction, godel: ((2 ** x) * ((2 * y) + 1) - 1) }; - }, - peg$c4 = function(label, instruction) { - return { label, instruction }; - }, - peg$c5 = "[", - peg$c6 = peg$literalExpectation("[", false), - peg$c7 = "]", - peg$c8 = peg$literalExpectation("]", false), - peg$c9 = function(label) { - return label; - }, - peg$c10 = function(conditional) { return { conditional, godel: conditional.godel }; }, - peg$c11 = function(assignment) { return { assignment, godel: assignment.godel }; }, - peg$c12 = function(goto) { return { goto, godel: goto.godel }; }, - peg$c13 = function(label) { - return { label, godel: label.godel + 2 }; - }, - peg$c14 = "IF", - peg$c15 = peg$literalExpectation("IF", false), - peg$c16 = "!=", - peg$c17 = peg$literalExpectation("!=", false), - peg$c18 = "0", - peg$c19 = peg$literalExpectation("0", false), - peg$c20 = function(variable, goto) { - const y = variable.godel - 1; - const x = goto.godel; - return { variable, goto, godel: ((2 ** x) * ((2 * y) + 1) - 1) }; - }, - peg$c21 = "<-", - peg$c22 = peg$literalExpectation("<-", false), - peg$c23 = function(variable, expr) { - if (expr.left.symbol != variable.symbol) { - error("left hand variable must match right hand"); - } - const x = expr.instructionNumber; - const y = variable.godel - 1; - return { variable, expr, godel: ((2 ** x) * ((2 * y) + 1) - 1) }; - }, - peg$c24 = "1", - peg$c25 = peg$literalExpectation("1", false), - peg$c26 = function(left, opr) { - const instructionNumber = { "+" : 1, "-" : 2 }[opr]; - return { left, opr, instructionNumber }; - }, - peg$c27 = function(left) { - return { left, instructionNumber: 0 }; - }, - peg$c28 = "Y", - peg$c29 = peg$literalExpectation("Y", false), - peg$c30 = function(symbol) { return { symbol, godel: 1 }; }, - peg$c31 = "X", - peg$c32 = peg$literalExpectation("X", false), - peg$c33 = "Z", - peg$c34 = peg$literalExpectation("Z", false), - peg$c35 = function(symbol, ind) { - const index = parseInt(ind); - const order = ["X", "Z"]; - const godel = index * order.length + order.indexOf(symbol); - return { symbol: symbol + ind, godel }; - }, - peg$c36 = "GOTO", - peg$c37 = peg$literalExpectation("GOTO", false), - peg$c38 = "+", - peg$c39 = peg$literalExpectation("+", false), - peg$c40 = "-", - peg$c41 = peg$literalExpectation("-", false), - peg$c42 = /^[A-E]/, - peg$c43 = peg$classExpectation([["A", "E"]], false, false), - peg$c44 = function(symbol, ind) { - const index = parseInt(ind); - const godel = (symbol.charCodeAt(0) - "A".charCodeAt(0) + 1) + 5*(index-1); - return { symbol: symbol + ind, godel }; - }, - peg$c45 = peg$otherExpectation("integer"), - peg$c46 = /^[0-9]/, - peg$c47 = peg$classExpectation([["0", "9"]], false, false), - peg$c48 = function() { return parseInt(text(), 10); }, - peg$c49 = peg$otherExpectation("whitespace"), - peg$c50 = /^[ \t]/, - peg$c51 = peg$classExpectation([" ", "\t"], false, false), - peg$c52 = function() { }, - - peg$currPos = 0, - peg$savedPos = 0, - peg$posDetailsCache = [{ line: 1, column: 1 }], - peg$maxFailPos = 0, - peg$maxFailExpected = [], - peg$silentFails = 0, - - peg$result; - - if ("startRule" in options) { - if (!(options.startRule in peg$startRuleFunctions)) { - throw new Error("Can't start parsing from rule \"" + options.startRule + "\"."); - } - - peg$startRuleFunction = peg$startRuleFunctions[options.startRule]; - } - - function text() { - return input.substring(peg$savedPos, peg$currPos); - } - - function location() { - return peg$computeLocation(peg$savedPos, peg$currPos); - } - - function expected(description, location) { - location = location !== void 0 ? location : peg$computeLocation(peg$savedPos, peg$currPos) - - throw peg$buildStructuredError( - [peg$otherExpectation(description)], - input.substring(peg$savedPos, peg$currPos), - location - ); - } - - function error(message, location) { - location = location !== void 0 ? location : peg$computeLocation(peg$savedPos, peg$currPos) - - throw peg$buildSimpleError(message, location); - } - - function peg$literalExpectation(text, ignoreCase) { - return { type: "literal", text: text, ignoreCase: ignoreCase }; - } - - function peg$classExpectation(parts, inverted, ignoreCase) { - return { type: "class", parts: parts, inverted: inverted, ignoreCase: ignoreCase }; - } - - function peg$anyExpectation() { - return { type: "any" }; - } - - function peg$endExpectation() { - return { type: "end" }; - } - - function peg$otherExpectation(description) { - return { type: "other", description: description }; - } - - function peg$computePosDetails(pos) { - var details = peg$posDetailsCache[pos], p; - - if (details) { - return details; - } else { - p = pos - 1; - while (!peg$posDetailsCache[p]) { - p--; - } - - details = peg$posDetailsCache[p]; - details = { - line: details.line, - column: details.column - }; - - while (p < pos) { - if (input.charCodeAt(p) === 10) { - details.line++; - details.column = 1; - } else { - details.column++; - } - - p++; - } - - peg$posDetailsCache[pos] = details; - return details; - } - } - - function peg$computeLocation(startPos, endPos) { - var startPosDetails = peg$computePosDetails(startPos), - endPosDetails = peg$computePosDetails(endPos); - - return { - start: { - offset: startPos, - line: startPosDetails.line, - column: startPosDetails.column - }, - end: { - offset: endPos, - line: endPosDetails.line, - column: endPosDetails.column - } - }; - } - - function peg$fail(expected) { - if (peg$currPos < peg$maxFailPos) { return; } - - if (peg$currPos > peg$maxFailPos) { - peg$maxFailPos = peg$currPos; - peg$maxFailExpected = []; - } - - peg$maxFailExpected.push(expected); - } - - function peg$buildSimpleError(message, location) { - return new peg$SyntaxError(message, null, null, location); - } - - function peg$buildStructuredError(expected, found, location) { - return new peg$SyntaxError( - peg$SyntaxError.buildMessage(expected, found), - expected, - found, - location - ); - } - - function peg$parseProgram() { - var s0, s1, s2; - - s0 = peg$currPos; - s1 = []; - s2 = peg$parseProgramInstruction(); - if (s2 === peg$FAILED) { - s2 = peg$parse_(); - if (s2 === peg$FAILED) { - if (peg$c0.test(input.charAt(peg$currPos))) { - s2 = input.charAt(peg$currPos); - peg$currPos++; - } else { - s2 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$c1); } - } - } - } - while (s2 !== peg$FAILED) { - s1.push(s2); - s2 = peg$parseProgramInstruction(); - if (s2 === peg$FAILED) { - s2 = peg$parse_(); - if (s2 === peg$FAILED) { - if (peg$c0.test(input.charAt(peg$currPos))) { - s2 = input.charAt(peg$currPos); - peg$currPos++; - } else { - s2 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$c1); } - } - } - } - } - if (s1 !== peg$FAILED) { - peg$savedPos = s0; - s1 = peg$c2(s1); - } - s0 = s1; - - return s0; - } - - function peg$parseProgramInstruction() { - var s0, s1, s2, s3, s4; - - s0 = peg$currPos; - s1 = peg$parse_(); - if (s1 === peg$FAILED) { - s1 = null; - } - if (s1 !== peg$FAILED) { - s2 = peg$parseLabeledInstruction(); - if (s2 === peg$FAILED) { - s2 = peg$parseInstruction(); - } - if (s2 !== peg$FAILED) { - s3 = peg$parse_(); - if (s3 === peg$FAILED) { - s3 = null; - } - if (s3 !== peg$FAILED) { - if (peg$c0.test(input.charAt(peg$currPos))) { - s4 = input.charAt(peg$currPos); - peg$currPos++; - } else { - s4 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$c1); } - } - if (s4 === peg$FAILED) { - s4 = null; - } - if (s4 !== peg$FAILED) { - peg$savedPos = s0; - s1 = peg$c3(s2); - s0 = s1; - } else { - peg$currPos = s0; - s0 = peg$FAILED; - } - } else { - peg$currPos = s0; - s0 = peg$FAILED; - } - } else { - peg$currPos = s0; - s0 = peg$FAILED; - } - } else { - peg$currPos = s0; - s0 = peg$FAILED; - } - - return s0; - } - - function peg$parseLabeledInstruction() { - var s0, s1, s2, s3; - - s0 = peg$currPos; - s1 = peg$parseLabel(); - if (s1 !== peg$FAILED) { - s2 = peg$parse_(); - if (s2 !== peg$FAILED) { - s3 = peg$parseInstruction(); - if (s3 !== peg$FAILED) { - peg$savedPos = s0; - s1 = peg$c4(s1, s3); - s0 = s1; - } else { - peg$currPos = s0; - s0 = peg$FAILED; - } - } else { - peg$currPos = s0; - s0 = peg$FAILED; - } - } else { - peg$currPos = s0; - s0 = peg$FAILED; - } - - return s0; - } - - function peg$parseLabel() { - var s0, s1, s2, s3, s4, s5; - - s0 = peg$currPos; - if (input.charCodeAt(peg$currPos) === 91) { - s1 = peg$c5; - peg$currPos++; - } else { - s1 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$c6); } - } - if (s1 !== peg$FAILED) { - s2 = peg$parse_(); - if (s2 === peg$FAILED) { - s2 = null; - } - if (s2 !== peg$FAILED) { - s3 = peg$parseLABEL_V(); - if (s3 !== peg$FAILED) { - s4 = peg$parse_(); - if (s4 === peg$FAILED) { - s4 = null; - } - if (s4 !== peg$FAILED) { - if (input.charCodeAt(peg$currPos) === 93) { - s5 = peg$c7; - peg$currPos++; - } else { - s5 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$c8); } - } - if (s5 !== peg$FAILED) { - peg$savedPos = s0; - s1 = peg$c9(s3); - s0 = s1; - } else { - peg$currPos = s0; - s0 = peg$FAILED; - } - } else { - peg$currPos = s0; - s0 = peg$FAILED; - } - } else { - peg$currPos = s0; - s0 = peg$FAILED; - } - } else { - peg$currPos = s0; - s0 = peg$FAILED; - } - } else { - peg$currPos = s0; - s0 = peg$FAILED; - } - - return s0; - } - - function peg$parseInstruction() { - var s0, s1; - - s0 = peg$currPos; - s1 = peg$parseConditional(); - if (s1 !== peg$FAILED) { - peg$savedPos = s0; - s1 = peg$c10(s1); - } - s0 = s1; - if (s0 === peg$FAILED) { - s0 = peg$currPos; - s1 = peg$parseAssignment(); - if (s1 !== peg$FAILED) { - peg$savedPos = s0; - s1 = peg$c11(s1); - } - s0 = s1; - if (s0 === peg$FAILED) { - s0 = peg$currPos; - s1 = peg$parseGoto(); - if (s1 !== peg$FAILED) { - peg$savedPos = s0; - s1 = peg$c12(s1); - } - s0 = s1; - } - } - - return s0; - } - - function peg$parseGoto() { - var s0, s1, s2, s3; - - s0 = peg$currPos; - s1 = peg$parseGOTO(); - if (s1 !== peg$FAILED) { - s2 = peg$parse_(); - if (s2 !== peg$FAILED) { - s3 = peg$parseLABEL_V(); - if (s3 !== peg$FAILED) { - peg$savedPos = s0; - s1 = peg$c13(s3); - s0 = s1; - } else { - peg$currPos = s0; - s0 = peg$FAILED; - } - } else { - peg$currPos = s0; - s0 = peg$FAILED; - } - } else { - peg$currPos = s0; - s0 = peg$FAILED; - } - - return s0; - } - - function peg$parseConditional() { - var s0, s1, s2, s3, s4, s5, s6, s7, s8, s9; - - s0 = peg$currPos; - if (input.substr(peg$currPos, 2) === peg$c14) { - s1 = peg$c14; - peg$currPos += 2; - } else { - s1 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$c15); } - } - if (s1 !== peg$FAILED) { - s2 = peg$parse_(); - if (s2 !== peg$FAILED) { - s3 = peg$parseVAR(); - if (s3 !== peg$FAILED) { - s4 = peg$parse_(); - if (s4 === peg$FAILED) { - s4 = null; - } - if (s4 !== peg$FAILED) { - if (input.substr(peg$currPos, 2) === peg$c16) { - s5 = peg$c16; - peg$currPos += 2; - } else { - s5 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$c17); } - } - if (s5 !== peg$FAILED) { - s6 = peg$parse_(); - if (s6 === peg$FAILED) { - s6 = null; - } - if (s6 !== peg$FAILED) { - if (input.charCodeAt(peg$currPos) === 48) { - s7 = peg$c18; - peg$currPos++; - } else { - s7 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$c19); } - } - if (s7 !== peg$FAILED) { - s8 = peg$parse_(); - if (s8 !== peg$FAILED) { - s9 = peg$parseGoto(); - if (s9 !== peg$FAILED) { - peg$savedPos = s0; - s1 = peg$c20(s3, s9); - s0 = s1; - } else { - peg$currPos = s0; - s0 = peg$FAILED; - } - } else { - peg$currPos = s0; - s0 = peg$FAILED; - } - } else { - peg$currPos = s0; - s0 = peg$FAILED; - } - } else { - peg$currPos = s0; - s0 = peg$FAILED; - } - } else { - peg$currPos = s0; - s0 = peg$FAILED; - } - } else { - peg$currPos = s0; - s0 = peg$FAILED; - } - } else { - peg$currPos = s0; - s0 = peg$FAILED; - } - } else { - peg$currPos = s0; - s0 = peg$FAILED; - } - } else { - peg$currPos = s0; - s0 = peg$FAILED; - } - - return s0; - } - - function peg$parseAssignment() { - var s0, s1, s2, s3, s4, s5; - - s0 = peg$currPos; - s1 = peg$parseVAR(); - if (s1 !== peg$FAILED) { - s2 = peg$parse_(); - if (s2 !== peg$FAILED) { - if (input.substr(peg$currPos, 2) === peg$c21) { - s3 = peg$c21; - peg$currPos += 2; - } else { - s3 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$c22); } - } - if (s3 !== peg$FAILED) { - s4 = peg$parse_(); - if (s4 !== peg$FAILED) { - s5 = peg$parseExpression(); - if (s5 !== peg$FAILED) { - peg$savedPos = s0; - s1 = peg$c23(s1, s5); - s0 = s1; - } else { - peg$currPos = s0; - s0 = peg$FAILED; - } - } else { - peg$currPos = s0; - s0 = peg$FAILED; - } - } else { - peg$currPos = s0; - s0 = peg$FAILED; - } - } else { - peg$currPos = s0; - s0 = peg$FAILED; - } - } else { - peg$currPos = s0; - s0 = peg$FAILED; - } - - return s0; - } - - function peg$parseExpression() { - var s0, s1, s2, s3, s4, s5; - - s0 = peg$currPos; - s1 = peg$parseVAR(); - if (s1 !== peg$FAILED) { - s2 = peg$parse_(); - if (s2 !== peg$FAILED) { - s3 = peg$parseOPERATION(); - if (s3 !== peg$FAILED) { - s4 = peg$parse_(); - if (s4 !== peg$FAILED) { - if (input.charCodeAt(peg$currPos) === 49) { - s5 = peg$c24; - peg$currPos++; - } else { - s5 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$c25); } - } - if (s5 !== peg$FAILED) { - peg$savedPos = s0; - s1 = peg$c26(s1, s3); - s0 = s1; - } else { - peg$currPos = s0; - s0 = peg$FAILED; - } - } else { - peg$currPos = s0; - s0 = peg$FAILED; - } - } else { - peg$currPos = s0; - s0 = peg$FAILED; - } - } else { - peg$currPos = s0; - s0 = peg$FAILED; - } - } else { - peg$currPos = s0; - s0 = peg$FAILED; - } - if (s0 === peg$FAILED) { - s0 = peg$currPos; - s1 = peg$parseVAR(); - if (s1 !== peg$FAILED) { - peg$savedPos = s0; - s1 = peg$c27(s1); - } - s0 = s1; - } - - return s0; - } - - function peg$parseVAR() { - var s0, s1, s2, s3; - - s0 = peg$currPos; - if (input.charCodeAt(peg$currPos) === 89) { - s1 = peg$c28; - peg$currPos++; - } else { - s1 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$c29); } - } - if (s1 !== peg$FAILED) { - peg$savedPos = s0; - s1 = peg$c30(s1); - } - s0 = s1; - if (s0 === peg$FAILED) { - s0 = peg$currPos; - if (input.charCodeAt(peg$currPos) === 88) { - s1 = peg$c31; - peg$currPos++; - } else { - s1 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$c32); } - } - if (s1 === peg$FAILED) { - if (input.charCodeAt(peg$currPos) === 90) { - s1 = peg$c33; - peg$currPos++; - } else { - s1 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$c34); } - } - } - if (s1 !== peg$FAILED) { - s2 = []; - s3 = peg$parseInteger(); - if (s3 !== peg$FAILED) { - while (s3 !== peg$FAILED) { - s2.push(s3); - s3 = peg$parseInteger(); - } - } else { - s2 = peg$FAILED; - } - if (s2 !== peg$FAILED) { - peg$savedPos = s0; - s1 = peg$c35(s1, s2); - s0 = s1; - } else { - peg$currPos = s0; - s0 = peg$FAILED; - } - } else { - peg$currPos = s0; - s0 = peg$FAILED; - } - } - - return s0; - } - - function peg$parseGOTO() { - var s0; - - if (input.substr(peg$currPos, 4) === peg$c36) { - s0 = peg$c36; - peg$currPos += 4; - } else { - s0 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$c37); } - } - - return s0; - } - - function peg$parseOPERATION() { - var s0; - - if (input.charCodeAt(peg$currPos) === 43) { - s0 = peg$c38; - peg$currPos++; - } else { - s0 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$c39); } - } - if (s0 === peg$FAILED) { - if (input.charCodeAt(peg$currPos) === 45) { - s0 = peg$c40; - peg$currPos++; - } else { - s0 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$c41); } - } - } - - return s0; - } - - function peg$parseLABEL_V() { - var s0, s1, s2, s3; - - s0 = peg$currPos; - if (peg$c42.test(input.charAt(peg$currPos))) { - s1 = input.charAt(peg$currPos); - peg$currPos++; - } else { - s1 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$c43); } - } - if (s1 !== peg$FAILED) { - s2 = []; - s3 = peg$parseInteger(); - if (s3 !== peg$FAILED) { - while (s3 !== peg$FAILED) { - s2.push(s3); - s3 = peg$parseInteger(); - } - } else { - s2 = peg$FAILED; - } - if (s2 !== peg$FAILED) { - peg$savedPos = s0; - s1 = peg$c44(s1, s2); - s0 = s1; - } else { - peg$currPos = s0; - s0 = peg$FAILED; - } - } else { - peg$currPos = s0; - s0 = peg$FAILED; - } - - return s0; - } - - function peg$parseInteger() { - var s0, s1, s2; - - peg$silentFails++; - s0 = peg$currPos; - s1 = []; - if (peg$c46.test(input.charAt(peg$currPos))) { - s2 = input.charAt(peg$currPos); - peg$currPos++; - } else { - s2 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$c47); } - } - if (s2 !== peg$FAILED) { - while (s2 !== peg$FAILED) { - s1.push(s2); - if (peg$c46.test(input.charAt(peg$currPos))) { - s2 = input.charAt(peg$currPos); - peg$currPos++; - } else { - s2 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$c47); } - } - } - } else { - s1 = peg$FAILED; - } - if (s1 !== peg$FAILED) { - peg$savedPos = s0; - s1 = peg$c48(); - } - s0 = s1; - peg$silentFails--; - if (s0 === peg$FAILED) { - s1 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$c45); } - } - - return s0; - } - - function peg$parse_() { - var s0, s1, s2; - - peg$silentFails++; - s0 = peg$currPos; - s1 = []; - if (peg$c50.test(input.charAt(peg$currPos))) { - s2 = input.charAt(peg$currPos); - peg$currPos++; - } else { - s2 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$c51); } - } - if (s2 !== peg$FAILED) { - while (s2 !== peg$FAILED) { - s1.push(s2); - if (peg$c50.test(input.charAt(peg$currPos))) { - s2 = input.charAt(peg$currPos); - peg$currPos++; - } else { - s2 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$c51); } - } - } - } else { - s1 = peg$FAILED; - } - if (s1 !== peg$FAILED) { - peg$savedPos = s0; - s1 = peg$c52(); - } - s0 = s1; - peg$silentFails--; - if (s0 === peg$FAILED) { - s1 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$c49); } - } - - return s0; - } - - peg$result = peg$startRuleFunction(); - - if (peg$result !== peg$FAILED && peg$currPos === input.length) { - return peg$result; - } else { - if (peg$result !== peg$FAILED && peg$currPos < input.length) { - peg$fail(peg$endExpectation()); - } - - throw peg$buildStructuredError( - peg$maxFailExpected, - peg$maxFailPos < input.length ? input.charAt(peg$maxFailPos) : null, - peg$maxFailPos < input.length - ? peg$computeLocation(peg$maxFailPos, peg$maxFailPos + 1) - : peg$computeLocation(peg$maxFailPos, peg$maxFailPos) - ); - } - } - - return { - SyntaxError: peg$SyntaxError, - parse: peg$parse - }; -})(); - -export default parser; -export const SyntaxError = parser.SyntaxError; diff --git a/src/toys/godel/js/ui.js b/src/toys/godel/js/ui.js deleted file mode 100644 index 97e5209..0000000 --- a/src/toys/godel/js/ui.js +++ /dev/null @@ -1,224 +0,0 @@ -import parser from "./parser.js"; -import { compileProgram } from "./compiler.js"; - -const SAMPLE_PROGRAM = `// THIS PROGRAM COMPUTES X1 + X2 - -// Y <- X1 -[ A1 ] IF X1 != 0 GOTO A2 - GOTO B1 -[ A2 ] X1 <- X1 - 1 - Y <- Y + 1 - GOTO A1 - -// Z1 <- X2 -[ B1 ] IF X2 != 0 GOTO B2 - GOTO C1 -[ B2 ] X2 <- X2 - 1 - Z1 <- Z1 + 1 - GOTO B1 - -// Y <- Y + Z1 -[ C1 ] IF Z1 != 0 GOTO C2 - GOTO E1 -[ C2 ] Z1 <- Z1 - 1 - Y <- Y + 1 - GOTO C1`; - -const STATUS_VARIANTS = { - success: "text-success", - error: "text-error", - info: "muted" -}; - -export class GodelPlayground { - constructor() { - this.elements = { - compileBtn: document.getElementById("compile-btn"), - copyBtn: document.getElementById("copy-btn"), - evalBtn: document.getElementById("eval-btn"), - computeNumberBtn: document.getElementById("compute-godel-btn"), - compileStatus: document.getElementById("compile-status"), - evalStatus: document.getElementById("eval-status"), - godelSequence: document.getElementById("godel-sequence"), - godelNumber: document.getElementById("godel-number") - }; - - this.sourceEditor = null; - this.compiledEditor = null; - this.worker = null; - this.latestSequence = []; - - this.init(); - } - - init() { - this.setupEditors(); - this.bindEvents(); - this.hydrateFromParams(); - if (!this.getSource().trim()) { - this.setSource(SAMPLE_PROGRAM); - } - this.compileSource(); - } - - setupEditors() { - this.sourceEditor = adelieEditor.init("#source-editor", { - language: "javascript" - }); - this.compiledEditor = adelieEditor.init("#compiled-editor", { - language: "javascript" - }); - this.setSource(SAMPLE_PROGRAM); - } - - bindEvents() { - this.elements.compileBtn.addEventListener("click", () => this.compileSource()); - this.elements.evalBtn.addEventListener("click", () => this.evaluateCompiled()); - this.elements.copyBtn.addEventListener("click", () => this.copyShareLink()); - this.elements.computeNumberBtn.addEventListener("click", () => this.computeGodelNumber()); - - document.addEventListener("keydown", (event) => { - if (event.ctrlKey && event.key === "Enter") { - event.preventDefault(); - this.compileSource(); - } - }); - } - - hydrateFromParams() { - const params = new URLSearchParams(window.location.search); - const encoded = params.get("instructions"); - if (encoded) { - try { - const decoded = atob(encoded); - this.setSource(decoded); - } catch (error) { - console.warn("Failed to decode instructions from URL", error); - } - } - } - - getSource() { - return this.sourceEditor.state.doc.toString(); - } - - setSource(content) { - const docLength = this.sourceEditor.state.doc.toString().length; - this.sourceEditor.dispatch({ - changes: { from: 0, to: docLength, insert: content } - }); - } - - getCompiled() { - return this.compiledEditor.state.doc.toString(); - } - - setCompiled(content) { - const docLength = this.compiledEditor.state.doc.toString().length; - this.compiledEditor.dispatch({ - changes: { from: 0, to: docLength, insert: content } - }); - } - - prepareSource(source) { - return source.replace(/\/\/.*$/gm, "").trim(); - } - - compileSource() { - const raw = this.getSource(); - const prepared = this.prepareSource(raw); - - if (!prepared) { - this.setStatus("compile", "Provide some source to compile", "error"); - return; - } - - try { - const ast = parser.parse(prepared); - const { js, godelSequence } = compileProgram(ast); - this.latestSequence = godelSequence; - this.setCompiled(js); - this.renderSequence(godelSequence); - this.setStatus("compile", "Successful compilation", "success"); - this.elements.computeNumberBtn.disabled = godelSequence.length === 0; - } catch (error) { - this.latestSequence = []; - this.renderSequence([]); - this.elements.computeNumberBtn.disabled = true; - this.setStatus("compile", error.message || "Error compiling", "error"); - } - } - - evaluateCompiled() { - const js = this.getCompiled(); - if (!js.trim()) { - this.setStatus("eval", "Compile a program first", "error"); - return; - } - - try { - const result = (0, eval)(js); - this.setStatus( - "eval", - `Result: ${typeof result === "undefined" ? "(no return)" : result}`, - "success" - ); - } catch (error) { - this.setStatus("eval", error.message || "Failed to evaluate program", "error"); - } - } - - renderSequence(sequence) { - if (!sequence.length) { - this.elements.godelSequence.textContent = "Compile to view the Gödel sequence."; - this.elements.godelNumber.textContent = ""; - return; - } - this.elements.godelSequence.textContent = `[${sequence.join(", ")}]`; - this.elements.godelNumber.textContent = ""; - } - - copyShareLink() { - const data = btoa(this.getSource()); - const url = `${window.location.href.split("?")[0]}?instructions=${data}`; - - navigator.clipboard.writeText(url) - .then(() => alert("Shareable link copied to clipboard")) - .catch(() => alert("Failed to copy link")); - } - - computeGodelNumber() { - if (!this.latestSequence.length) { - this.setStatus("compile", "Compile a program to produce its Gödel sequence", "error"); - return; - } - - this.elements.godelNumber.textContent = "Working..."; - this.elements.computeNumberBtn.disabled = true; - - const worker = this.getWorker(); - worker.onmessage = (event) => { - this.elements.godelNumber.textContent = event.data; - this.elements.computeNumberBtn.disabled = false; - }; - worker.onerror = () => { - this.elements.godelNumber.textContent = "Failed to compute Gödel number"; - this.elements.computeNumberBtn.disabled = false; - }; - worker.postMessage(this.latestSequence); - } - - getWorker() { - if (!this.worker) { - const workerUrl = new URL("./godel-worker.js", import.meta.url); - this.worker = new Worker(workerUrl, { type: "module" }); - } - return this.worker; - } - - setStatus(kind, message, variant = "info") { - const element = kind === "compile" ? this.elements.compileStatus : this.elements.evalStatus; - element.textContent = message; - element.className = `status-text ${STATUS_VARIANTS[variant] || ""}`; - } -} diff --git a/src/toys/julia/index.html b/src/toys/julia/index.html deleted file mode 100644 index 9d9de51..0000000 --- a/src/toys/julia/index.html +++ /dev/null @@ -1,51 +0,0 @@ - - - - - - - -
- -
-
- - -
- imaginary: - -
- real: - -
- powered by gpu.js -
- - - - - diff --git a/src/toys/julia/julia.js b/src/toys/julia/julia.js deleted file mode 100644 index 38bcd4d..0000000 --- a/src/toys/julia/julia.js +++ /dev/null @@ -1,136 +0,0 @@ -const MIN_ZOOM = 0.0001; -const MAX_ZOOM = 4; -const C_THRESHOLD = Math.sqrt(2)/2; -const SLIDER_DIV = 2000*C_THRESHOLD; - -const gpu = new GPU.GPU(); -const buildRender = (width, height) => gpu.createKernel(function (maxIterations, cr, ci, centerX, centerY, zoom, colorMultipliers) { - let zx = (this.output.x / this.output.y) * (centerX + (4 * this.thread.x / this.output.x - 2) * (zoom / 4)); - let zy = centerY + (4 * this.thread.y / this.output.y - 2) * (zoom / 4); - let iterations = 0; - for (let i = 0; i < maxIterations; i++) { - const xtemp = zx * zx - zy * zy + cr; - zy = 2 * zx * zy + ci; - zx = xtemp; - if (zx * zx + zy * zy > 4) { - iterations = i; - break; - } - } - if (iterations == 0 || iterations == maxIterations) { - this.color(0, 0, 0); - } else { - this.color(colorMultipliers[0] * iterations, colorMultipliers[1] * iterations, colorMultipliers[2] * iterations); - } -}, { output: [width, height], graphical: true }); - -const canvasHolder = document.getElementById('canvasHolder'); -let render; // The GPU kernel built from buildRender -let state = { - colorMultipliers: [0.01 * Math.random() + 0.015, 0.03 * Math.random() + 0.007, 0.02 * Math.random() + 0.010], - changes: { - centerX: 0, - centerY: 0, - zoom: 3, - cr: parseFloat(document.getElementById('realSlider').value) / SLIDER_DIV, - ci: parseFloat(document.getElementById('imaginarySlider').value) / SLIDER_DIV, - maxIterations: 1000, - width: document.body.clientWidth, - height: document.body.clientHeight, - }, -}; - -const doRender = (renderF, state) => { - // gpu.js doesn't support JS objects as kernel parameters - https://github.com/gpujs/gpu.js/issues/245 - renderF(state.maxIterations, state.cr, state.ci, state.centerX, state.centerY, state.zoom, state.colorMultipliers); -}; - -const loop = () => { - const stateChanges = Object.keys(state.changes); - if (stateChanges.length > 0) { - state = {...state, ...state.changes}; - if (state.changes.width || state.changes.height) { - render = buildRender(state.width, state.height); - canvasHolder.appendChild(render.canvas); - } - if (typeof state.changes.ci !== 'undefined') { - document.getElementById('imag-val').value = state.ci.toFixed(4); - document.getElementById('imaginarySlider').value = state.changes.ci * SLIDER_DIV; - } - if (typeof state.changes.cr !== 'undefined') { - document.getElementById('real-val').value = state.cr.toFixed(4); - document.getElementById('realSlider').value = state.changes.cr * SLIDER_DIV; - } - state.changes = {}; - doRender(render, state); - } - - requestAnimationFrame(loop); -}; - -loop(); - -// UI Code - -const startAnim = (sliderId, complexComponentName='ci') => { - const restart = (interval) => { - clearInterval(interval); - document.getElementById(sliderId).innerHTML = 'Animate'; - document.getElementById(sliderId).onclick = ()=>startAnim(sliderId, complexComponentName); - return; - }; - const start = setInterval(() => { - if (state[complexComponentName] >= C_THRESHOLD) { - restart(start); - } - - state.changes[complexComponentName] = state[complexComponentName] + 0.001; - }, 1000/60); - document.getElementById(sliderId).innerHTML = 'Stop'; - document.getElementById(sliderId).onclick = ()=>restart(start); -}; - -document.getElementById('imaginarySlider').oninput = function() { - state.changes.ci = parseFloat(this.value) / SLIDER_DIV; -}; -document.getElementById('realSlider').oninput = function() { - state.changes.cr = parseFloat(this.value) / SLIDER_DIV; -}; - -document.getElementById('imag-val').addEventListener('change', function (e) { - state.changes.ci = parseFloat(this.value); -}); -document.getElementById('real-val').addEventListener('change', function (e) { - state.changes.cr = parseFloat(this.value); -}); - -canvasHolder.addEventListener('wheel', (e) => { - e.preventDefault(); - state.changes.zoom = Math.min(Math.max(state.zoom + e.deltaY * 0.001 * state.zoom, MIN_ZOOM), MAX_ZOOM); -}); - -let isDown = false; -canvasHolder.addEventListener('mousedown', (e) => { - e.preventDefault(); - isDown = true; -}, true); - -canvasHolder.addEventListener('mouseup', (e) => { - e.preventDefault(); - isDown = false; -}, true); - -canvasHolder.addEventListener('mousemove', (e) => { - e.preventDefault(); - if (isDown) { - let deltaX = -e.movementX * state.zoom / document.body.clientWidth; - let deltaY = e.movementY * state.zoom / document.body.clientHeight; - state.changes.centerX = state.centerX + deltaX; - state.changes.centerY = state.centerY + deltaY; - } -}, true); - -window.addEventListener('resize', () => { - state.changes.width = document.body.clientWidth; - state.changes.height = document.body.clientHeight; -}); diff --git a/src/toys/tabloid/index.html b/src/toys/tabloid/index.html deleted file mode 100644 index 61ec937..0000000 --- a/src/toys/tabloid/index.html +++ /dev/null @@ -1,53 +0,0 @@ - - - - - - - - Tabloid - - -
-
-

Tabloid

-

Forgotten how to write Tabloid since your schooldays? Here's a refresher.

- -
- - -
- -
-
-
- -
- - -
- -
-

stdout

-

-      
- -
-

stderr

-

-      
-
-
- - - - - - diff --git a/src/toys/tabloid/js/main.js b/src/toys/tabloid/js/main.js deleted file mode 100644 index 1f0b5dd..0000000 --- a/src/toys/tabloid/js/main.js +++ /dev/null @@ -1,5 +0,0 @@ -import { TabloidPlayground } from "./playground.js"; - -document.addEventListener("DOMContentLoaded", () => { - new TabloidPlayground(); -}); diff --git a/src/toys/tabloid/js/playground.js b/src/toys/tabloid/js/playground.js deleted file mode 100644 index d6ff71a..0000000 --- a/src/toys/tabloid/js/playground.js +++ /dev/null @@ -1,120 +0,0 @@ -import { tokenize, Parser, Environment } from "./tabloid.js"; -import { SAMPLE_PROGRAMS } from "./samples.js"; - -class TabloidRunner { - run(code) { - const stdout = []; - const stderr = []; - - if (!code) { - stderr.push("No code to execute."); - return { stdout, stderr }; - } - - try { - const tokens = tokenize(code); - const nodes = new Parser(tokens).parse(); - const env = new Environment({ - print: (msg) => stdout.push(String(msg)), - input: (promptText) => window.prompt(promptText) ?? "" - }); - env.run(nodes); - } catch (error) { - stderr.push(error.message || error.toString()); - } - - return { stdout, stderr }; - } -} - -export class TabloidPlayground { - constructor() { - this.runner = new TabloidRunner(); - this.programLookup = new Map(SAMPLE_PROGRAMS.map((program) => [program.id, program])); - this.activeProgramId = this.defaultProgramId = SAMPLE_PROGRAMS[0].id; - - this.stdoutElement = document.getElementById("stdout-content"); - this.errorElement = document.getElementById("error-content"); - this.programSelect = document.getElementById("program-select"); - this.runButton = document.getElementById("run-button"); - this.clearButton = document.getElementById("clear-button"); - - this.editor = adelieEditor.init("#code-editor", { - language: "tabloid" - }); - - this.init(); - } - - init() { - this.populateProgramSelect(); - this.registerEvents(); - this.loadProgram(this.activeProgramId); - } - - populateProgramSelect() { - if (!this.programSelect) { - return; - } - - this.programSelect.innerHTML = ""; - SAMPLE_PROGRAMS.forEach((program) => { - const option = document.createElement("option"); - option.value = program.id; - option.textContent = program.label; - this.programSelect.appendChild(option); - }); - - this.programSelect.value = this.activeProgramId; - } - - registerEvents() { - this.runButton.addEventListener("click", () => this.runCode()); - this.clearButton.addEventListener("click", () => this.resetOutput()); - - this.programSelect.addEventListener("change", () => { - this.loadProgram(this.programSelect.value); - }); - - document.addEventListener("keydown", (event) => { - if (event.ctrlKey && event.key === "Enter") { - event.preventDefault(); - this.runCode(); - } - }); - } - - setEditorContent(content) { - const length = this.editor.state.doc.toString().length; - this.editor.dispatch({ - changes: { from: 0, to: length, insert: content } - }); - } - - loadProgram(programId = this.defaultProgramId) { - const selectedProgram = this.programLookup.get(programId); - if (!selectedProgram) { - return; - } - - this.activeProgramId = selectedProgram.id; - this.setEditorContent(selectedProgram.code); - this.resetOutput(); - - if (this.programSelect) { - this.programSelect.value = this.activeProgramId; - } - } - - resetOutput() { - this.stdoutElement.textContent = ""; - this.errorElement.textContent = ""; - } - - runCode() { - const code = this.editor.state.doc.toString(); - const result = this.runner.run(code); - this.stdoutElement.textContent = result.stdout.join("\n"); - this.errorElement.textContent = result.stderr.join("\n"); - } -} diff --git a/src/toys/tabloid/js/samples.js b/src/toys/tabloid/js/samples.js deleted file mode 100644 index dc7bd58..0000000 --- a/src/toys/tabloid/js/samples.js +++ /dev/null @@ -1,308 +0,0 @@ -const CONS_SNIPPET = [ - "DISCOVER HOW TO cons WITH a, b", - "RUMOR HAS IT", - " DISCOVER HOW TO retrieve WITH is_first", - " RUMOR HAS IT", - " WHAT IF is_first IS ACTUALLY TOTALLY RIGHT", - " SHOCKING DEVELOPMENT a", - " LIES!", - " SHOCKING DEVELOPMENT b", - " END OF STORY", - " SHOCKING DEVELOPMENT retrieve", - "END OF STORY", -].join('\n'); - -const BINARY_INORDER_TRAVERSAL_PROGRAM = [ - CONS_SNIPPET, - '', - "DISCOVER HOW TO in_order_traverse WITH node, is_dual_ptr", - "RUMOR HAS IT", - " EXPERTS CLAIM left TO BE node OF TOTALLY RIGHT", - " EXPERTS CLAIM right TO BE node OF COMPLETELY WRONG", - '', - " WHAT IF is_dual_ptr IS ACTUALLY COMPLETELY WRONG", - " RUMOR HAS IT", - " YOU WON'T WANT TO MISS left", - " WHAT IF right IS ACTUALLY COMPLETELY WRONG", - " 1", - " LIES!", - " in_order_traverse OF right, TOTALLY RIGHT", - " END OF STORY", - " LIES!", - " RUMOR HAS IT", - " WHAT IF left IS ACTUALLY COMPLETELY WRONG", - " 1", - " LIES!", - " in_order_traverse OF left, COMPLETELY WRONG", - '', - " WHAT IF right IS ACTUALLY COMPLETELY WRONG", - " 1", - " LIES!", - " in_order_traverse OF right, COMPLETELY WRONG", - " END OF STORY", - "END OF STORY", - '', - "EXPERTS CLAIM l TO BE cons OF 1, COMPLETELY WRONG", - "EXPERTS CLAIM r TO BE cons OF 3, COMPLETELY WRONG", - "EXPERTS CLAIM root TO BE cons OF l, r", - "EXPERTS CLAIM head TO BE cons OF 2, root", - '', - "in_order_traverse OF head, COMPLETELY WRONG", - '', - "PLEASE LIKE AND SUBSCRIBE", -].join('\n'); - -const MERGE_SORT_PROGRAM = [ - CONS_SNIPPET, - '', - "DISCOVER HOW TO print WITH x", - "RUMOR HAS IT", - " YOU WON'T WANT TO MISS x", - "END OF STORY", - '', - "DISCOVER HOW TO map WITH fn, list", - "RUMOR HAS IT", - " WHAT IF list IS ACTUALLY COMPLETELY WRONG", - " SHOCKING DEVELOPMENT COMPLETELY WRONG", - " LIES!", - " RUMOR HAS IT", - " EXPERTS CLAIM car TO BE list OF TOTALLY RIGHT", - " EXPERTS CLAIM cdr TO BE list OF COMPLETELY WRONG", - " EXPERTS CLAIM new_car TO BE fn OF car", - " EXPERTS CLAIM rest_mapped TO BE map OF fn, cdr", - '', - " SHOCKING DEVELOPMENT cons OF new_car, rest_mapped", - " END OF STORY", - "END OF STORY", - '', - "DISCOVER HOW TO reduce WITH fn, list, accumulator", - "RUMOR HAS IT", - " WHAT IF list IS ACTUALLY COMPLETELY WRONG", - " SHOCKING DEVELOPMENT accumulator", - " LIES!", - " RUMOR HAS IT", - " EXPERTS CLAIM car TO BE list OF TOTALLY RIGHT", - " EXPERTS CLAIM cdr TO BE list OF COMPLETELY WRONG", - " EXPERTS CLAIM added_accumulator TO BE fn OF car, accumulator", - '', - " SHOCKING DEVELOPMENT reduce OF fn, cdr, added_accumulator", - " END OF STORY", - "END OF STORY", - '', - "DISCOVER HOW TO str_join_reducer WITH element, accumulator", - "RUMOR HAS IT", - " EXPERTS CLAIM added_comma TO BE element PLUS ', '", - " SHOCKING DEVELOPMENT added_comma PLUS accumulator", - "END OF STORY", - '', - "DISCOVER HOW TO join WITH list", - "RUMOR HAS IT", - " SHOCKING DEVELOPMENT reduce OF str_join_reducer, list, ''", - "END OF STORY", - '', - "DISCOVER HOW TO append WITH n, m", - "RUMOR HAS IT", - " WHAT IF n IS ACTUALLY COMPLETELY WRONG", - " SHOCKING DEVELOPMENT m", - " LIES!", - " RUMOR HAS IT", - " EXPERTS CLAIM car_n TO BE n OF TOTALLY RIGHT", - " EXPERTS CLAIM cdr_n TO BE n OF COMPLETELY WRONG", - " EXPERTS CLAIM appended TO BE append OF cdr_n, m", - '', - " SHOCKING DEVELOPMENT cons OF car_n, appended", - " END OF STORY", - "END OF STORY", - '', - "DISCOVER HOW TO reverse WITH l", - "RUMOR HAS IT", - " WHAT IF l IS ACTUALLY COMPLETELY WRONG", - " SHOCKING DEVELOPMENT COMPLETELY WRONG", - " LIES!", - " 1", - '', - " EXPERTS CLAIM car TO BE l OF TOTALLY RIGHT", - " EXPERTS CLAIM cdr TO BE l OF COMPLETELY WRONG", - " EXPERTS CLAIM reversed_cdr TO BE reverse OF cdr", - " EXPERTS CLAIM car_cons TO BE cons OF car, COMPLETELY WRONG", - '', - " SHOCKING DEVELOPMENT append OF reversed_cdr, car_cons", - "END OF STORY", - '', - "DISCOVER HOW TO merge WITH x, y", - "RUMOR HAS IT", - " WHAT IF x IS ACTUALLY COMPLETELY WRONG", - " SHOCKING DEVELOPMENT y", - " LIES!", - " 1", - '', - " WHAT IF y IS ACTUALLY COMPLETELY WRONG", - " RUMOR HAS IT", - " WHAT IF x IS ACTUALLY COMPLETELY WRONG", - " SHOCKING DEVELOPMENT COMPLETELY WRONG", - " LIES!", - " SHOCKING DEVELOPMENT x", - " END OF STORY", - " LIES!", - " 1", - '', - " EXPERTS CLAIM car_x TO BE x OF TOTALLY RIGHT", - " EXPERTS CLAIM car_y TO BE y OF TOTALLY RIGHT", - " EXPERTS CLAIM cdr_x TO BE x OF COMPLETELY WRONG", - " EXPERTS CLAIM cdr_y TO BE y OF COMPLETELY WRONG", - '', - " EXPERTS CLAIM x_gt_y TO BE car_x BEATS car_y", - '', - " WHAT IF x_gt_y IS ACTUALLY TOTALLY RIGHT", - " RUMOR HAS IT", - " EXPERTS CLAIM rest_x_merge_y TO BE merge OF cdr_x, y", - " SHOCKING DEVELOPMENT cons OF car_x, rest_x_merge_y", - " END OF STORY", - " LIES!", - " RUMOR HAS IT", - " EXPERTS CLAIM x_merge_rest_y TO BE merge OF x, cdr_y", - " SHOCKING DEVELOPMENT cons OF car_y, x_merge_rest_y", - " END OF STORY", - "END OF STORY", - '', - "DISCOVER HOW TO split_middle_helper WITH slow, fast, mid_to_head", - "RUMOR HAS IT", - " WHAT IF fast IS ACTUALLY COMPLETELY WRONG", - " RUMOR HAS IT", - " EXPERTS CLAIM head_to_mid TO BE reverse OF mid_to_head", - " SHOCKING DEVELOPMENT cons OF head_to_mid, slow", - " END OF STORY", - " LIES!", - " 1", - '', - " EXPERTS CLAIM fast_cdr TO BE fast OF COMPLETELY WRONG", - " EXPERTS CLAIM slow_car TO BE slow OF TOTALLY RIGHT", - " EXPERTS CLAIM slow_cdr TO BE slow OF COMPLETELY WRONG", - '', - " WHAT IF fast_cdr IS ACTUALLY COMPLETELY WRONG", - " RUMOR HAS IT", - " EXPERTS CLAIM mid_to_head_plus_slow TO BE cons OF slow_car, mid_to_head", - " EXPERTS CLAIM head_to_mid_plus_slow TO BE reverse OF mid_to_head_plus_slow", - '', - " SHOCKING DEVELOPMENT cons OF head_to_mid_plus_slow, slow_cdr", - " END OF STORY", - " LIES!", - " 1", - '', - " EXPERTS CLAIM fast_cddr TO BE fast_cdr OF COMPLETELY WRONG", - " EXPERTS CLAIM slow_car_mid_to_head TO BE cons OF slow_car, mid_to_head", - '', - " SHOCKING DEVELOPMENT split_middle_helper OF slow_cdr, fast_cddr, slow_car_mid_to_head", - "END OF STORY", - '', - "DISCOVER HOW TO split_middle WITH start", - "RUMOR HAS IT", - " EXPERTS CLAIM cdr TO BE start OF COMPLETELY WRONG", - '', - " SHOCKING DEVELOPMENT split_middle_helper OF start, cdr, COMPLETELY WRONG", - "END OF STORY", - '', - "DISCOVER HOW TO sort WITH root", - "RUMOR HAS IT", - " WHAT IF root IS ACTUALLY COMPLETELY WRONG", - " SHOCKING DEVELOPMENT root", - " LIES!", - " 1", - '', - " EXPERTS CLAIM root_cdr TO BE root OF COMPLETELY WRONG", - " WHAT IF root_cdr IS ACTUALLY COMPLETELY WRONG", - " SHOCKING DEVELOPMENT root", - " LIES!", - " 1", - '', - " EXPERTS CLAIM left_right_cons_cell TO BE split_middle OF root", - " EXPERTS CLAIM left TO BE left_right_cons_cell OF TOTALLY RIGHT", - " EXPERTS CLAIM right TO BE left_right_cons_cell OF COMPLETELY WRONG", - " EXPERTS CLAIM left_s TO BE sort OF left", - " EXPERTS CLAIM right_s TO BE sort OF right", - '', - " SHOCKING DEVELOPMENT merge OF left_s, right_s", - "END OF STORY", - '', - "EXPERTS CLAIM a_3 TO BE cons OF 3, COMPLETELY WRONG", - "EXPERTS CLAIM a_2 TO BE cons OF 1, a_3", - "EXPERTS CLAIM a_1 TO BE cons OF -2, a_2", - "EXPERTS CLAIM a_0 TO BE cons OF 5, a_1", - "EXPERTS CLAIM b_3 TO BE cons OF 2, a_0", - "EXPERTS CLAIM b_2 TO BE cons OF 7, b_3", - "EXPERTS CLAIM b_1 TO BE cons OF 3, b_2", - "EXPERTS CLAIM b_0 TO BE cons OF -1, b_1", - '', - "EXPERTS CLAIM b_sorted TO BE sort OF b_0", - '', - "YOU WON'T WANT TO MISS join OF b_sorted", - '', - "PLEASE LIKE AND SUBSCRIBE", -].join('\n'); - -export const SAMPLE_PROGRAMS = [ - { - id: 'fibonacci', - label: 'Fibonacci', - code: [ - "DISCOVER HOW TO fibonacci WITH a, b, n", - "RUMOR HAS IT", - " WHAT IF n SMALLER THAN 1", - " SHOCKING DEVELOPMENT b", - " LIES! RUMOR HAS IT", - " YOU WON'T WANT TO MISS b", - " SHOCKING DEVELOPMENT", - " fibonacci OF b, a PLUS b, n MINUS 1", - " END OF STORY", - "END OF STORY", - "", - "EXPERTS CLAIM limit TO BE 10", - "", - "fibonacci OF 0, 1, limit", - "", - "PLEASE LIKE AND SUBSCRIBE", - ].join('\n'), - }, - { - id: 'countdown', - label: 'Countdown', - code: [ - "EXPERTS CLAIM start TO BE 4", - "YOU WON'T WANT TO MISS 't minus 5...'", - "", - "DISCOVER HOW TO countdown WITH current", - "RUMOR HAS IT", - " WHAT IF current SMALLER THAN 1 RUMOR HAS IT", - " SHOCKING DEVELOPMENT 'Blastoff!'", - " END OF STORY", - " LIES! RUMOR HAS IT", - " YOU WON'T WANT TO MISS current", - " SHOCKING DEVELOPMENT countdown OF current MINUS 1", - " END OF STORY", - "END OF STORY", - "", - "YOU WON'T WANT TO MISS countdown OF start", - "", - "PLEASE LIKE AND SUBSCRIBE", - ].join('\n'), - }, - { - id: 'hello', - label: 'Hello!', - code: [ - "YOU WON'T WANT TO MISS ('Hello, ' PLUS (LATEST NEWS ON 'What is your name?')) PLUS '!'", - "", - "PLEASE LIKE AND SUBSCRIBE", - ].join('\n'), - }, - { - id: 'binary-inorder-traversal', - label: 'Binary tree in-order traversal', - code: BINARY_INORDER_TRAVERSAL_PROGRAM, - }, - { - id: 'merge-sort', - label: 'Merge sort linked list', - code: MERGE_SORT_PROGRAM, - }, -]; diff --git a/src/toys/tabloid/js/tabloid.js b/src/toys/tabloid/js/tabloid.js deleted file mode 100644 index 468ee9a..0000000 --- a/src/toys/tabloid/js/tabloid.js +++ /dev/null @@ -1,707 +0,0 @@ -/* Tabloid: the clickbait headline programming language */ - -/* tokenizer */ - -/** - * Reads in char or word chunks - */ -class Reader { - constructor(str, base = '') { - this.base = base; - this.i = 0; - this.str = str; - } - peek() { - return this.str[this.i]; - } - next() { - return this.str[this.i++]; - } - hasNext() { - return this.str[this.i] !== undefined; - } - backstep() { - this.i--; - } - readUntil(pred) { - let result = this.base.slice(); - while (this.hasNext() && !pred(this.peek())) { - result += this.next(); - } - return result; - } - dropWhitespace() { - this.readUntil(c => !!c.trim()); - } - expect(tok) { - const next = this.next(); - if (next !== tok) { - throw new Error(`Parsing error: expected ${tok}, got ${next}`); - } - } -} - -/** - * Split into words for easier tokenization - * with keywords. - */ -class Wordifier { - constructor(str) { - this.reader = new Reader(str.trim()); - this.tokens = []; - } - wordify() { - if (this.tokens.length) return this.tokens; - - while (this.reader.hasNext()) { - const next = this.reader.next(); - switch (next) { - case '(': { - this.tokens.push('('); - break; - } - case ')': { - this.tokens.push(')'); - break; - } - case ',': { - this.tokens.push(','); - break; - } - case '"': - case "'": { - this.wordifyString(next); - break; - } - default: { - // read until WS - this.reader.backstep(); - this.tokens.push(this.reader.readUntil(c => { - return !c.trim() || ['(', ')', ','].includes(c) - })); - } - } - this.reader.dropWhitespace(); - } - return this.tokens; - } - wordifyString(endChar) { - let acc = ''; - acc += this.reader.readUntil(c => c == endChar); - while (acc.endsWith('\\') || !this.reader.hasNext()) { - acc = acc.substr(0, acc.length - 1); - this.reader.next(); // endChar - acc += endChar + this.reader.readUntil(c => c == endChar); - } - this.reader.next(); // throw away closing char - this.tokens.push('"' + acc); - } -} - -const T = { - LParen: Symbol('LParen'), - RParen: Symbol('RParen'), - Comma: Symbol('Comma'), - DiscoverHowTo: Symbol('DiscoverHowTo'), - With: Symbol('With'), - Of: Symbol('Of'), - RumorHasIt: Symbol('RumorHasIt'), - WhatIf: Symbol('WhatIf'), - LiesBang: Symbol('LiesBang'), - EndOfStory: Symbol('EndOfStory'), - ExpertsClaim: Symbol('ExpertsClaim'), - ToBe: Symbol('ToBe'), - YouWontWantToMiss: Symbol('YouWontWantToMiss'), - LatestNewsOn: Symbol('LatestNewsOn'), - TotallyRight: Symbol('TotallyRight'), - CompletelyWrong: Symbol('CompletelyWrong'), - IsActually: Symbol('IsActually'), - And: Symbol('And'), - Or: Symbol('Or'), - Plus: Symbol('Plus'), - Minus: Symbol('Minus'), - Times: Symbol('Times'), - DividedBy: Symbol('DividedBy'), - Modulo: Symbol('Modulo'), - Beats: Symbol('Beats'), // > - SmallerThan: Symbol('SmallerThan'), // < - ShockingDevelopment: Symbol('ShockingDevelopment'), - PleaseLikeAndSubscribe: Symbol('PleaseLikeAndSubscribe'), -} - -const BINARY_OPS = [ - T.IsActually, - T.And, - T.Or, - T.Plus, - T.Minus, - T.Times, - T.DividedBy, - T.Modulo, - T.Beats, - T.SmallerThan, -]; - -export function tokenize(prog) { - const reader = new Reader(new Wordifier(prog).wordify(), []); - const tokens = []; - - while (reader.hasNext()) { - const next = reader.next(); - switch (next) { - case 'DISCOVER': { - reader.expect('HOW'); - reader.expect('TO'); - tokens.push(T.DiscoverHowTo); - break; - } - case 'WITH': { - tokens.push(T.With); - break; - } - case 'OF': { - tokens.push(T.Of); - break; - } - case 'RUMOR': { - reader.expect('HAS'); - reader.expect('IT'); - tokens.push(T.RumorHasIt); - break; - } - case 'WHAT': { - reader.expect('IF'); - tokens.push(T.WhatIf); - break; - } - case 'LIES!': { - tokens.push(T.LiesBang); - break; - } - case 'END': { - reader.expect('OF'); - reader.expect('STORY'); - tokens.push(T.EndOfStory); - break; - } - case 'EXPERTS': { - reader.expect('CLAIM'); - tokens.push(T.ExpertsClaim); - break; - } - case 'TO': { - reader.expect('BE'); - tokens.push(T.ToBe); - break; - } - case 'YOU': { - reader.expect('WON\'T'); - reader.expect('WANT'); - reader.expect('TO'); - reader.expect('MISS'); - tokens.push(T.YouWontWantToMiss); - break; - } - case 'LATEST': { - reader.expect('NEWS'); - reader.expect('ON'); - tokens.push(T.LatestNewsOn); - break; - } - case 'IS': { - reader.expect('ACTUALLY'); - tokens.push(T.IsActually); - break; - } - case 'AND': { - tokens.push(T.And); - break; - } - case 'OR': { - tokens.push(T.Or); - break; - } - case 'PLUS': { - tokens.push(T.Plus); - break; - } - case 'MINUS': { - tokens.push(T.Minus); - break; - } - case 'TIMES': { - tokens.push(T.Times); - break; - } - case 'DIVIDED': { - reader.expect('BY'); - tokens.push(T.DividedBy); - break; - } - case 'MODULO': { - tokens.push(T.Modulo); - break; - } - case 'BEATS': { - tokens.push(T.Beats); - break; - } - case 'SMALLER': { - reader.expect('THAN'); - tokens.push(T.SmallerThan); - break; - } - case 'SHOCKING': { - reader.expect('DEVELOPMENT'); - tokens.push(T.ShockingDevelopment); - break; - } - case 'PLEASE': { - reader.expect('LIKE'); - reader.expect('AND'); - reader.expect('SUBSCRIBE'); - tokens.push(T.PleaseLikeAndSubscribe); - break; - } - case 'TOTALLY': { - reader.expect('RIGHT'); - tokens.push(T.TotallyRight); - break; - } - case 'COMPLETELY': { - reader.expect('WRONG'); - tokens.push(T.CompletelyWrong); - break; - } - case '(': { - tokens.push(T.LParen); - break; - } - case ')': { - tokens.push(T.RParen); - break; - } - case ',': { - tokens.push(T.Comma); - break; - } - default: { - if (!isNaN(parseFloat(next))) { - // number literal - tokens.push(parseFloat(next)); - } else { - // string or varname - tokens.push(next); - } - } - } - } - return tokens; -} - -/* parser */ - -const N = { - NumberLiteral: Symbol('NumberLiteral'), - StringLiteral: Symbol('StringLiteral'), - BoolLiteral: Symbol('BoolLiteral'), - FnDecl: Symbol('FnDecl'), - FnCall: Symbol('FnCall'), - Ident: Symbol('Ident'), - Assignment: Symbol('Assignment'), - BinaryOp: Symbol('BinaryOp'), - IfExpr: Symbol('IfExpr'), - ExprGroup: Symbol('ExprGroup'), - ReturnExpr: Symbol('ReturnExpr'), - ProgEndExpr: Symbol('ProgEndExpr'), - PrintExpr: Symbol('PrintExpr'), - InputExpr: Symbol('InputExpr'), -} - -export class Parser { - constructor(tokens) { - this.tokens = new Reader(tokens, []); - } - /** - * Atom - * Ident - * NumberLiteral - * StringLiteral - * BoolLiteral - * FnCall - * FnDecl - * ExprGroup - * - * Expression: - * (begins with atom) - * BinaryOp - * Atom - * (begins with keyword) - * IfExpr - * Assignment - * ReturnExpr - * ProgEndExpr - * PrintExpr - * InputExpr - * - */ - parse() { - const nodes = []; - while (this.tokens.hasNext()) { - nodes.push(this.expr()); - } - - if (nodes[nodes.length - 1].type !== N.ProgEndExpr) { - throw new Error('Parsing error: A Tabloid program MUST end with PLEASE LIKE AND SUBSCRIBE'); - } - - return nodes; - } - expectIdentString() { - const ident = this.tokens.next(); - if (typeof ident === 'string' && !ident.startsWith('"')) { - return ident; - } - throw new Error(`Parsing error: expected identifier, got ${ident.toString()}`); - } - atom() { - const next = this.tokens.next(); - if (typeof next === 'number') { - return { - type: N.NumberLiteral, - val: next, - } - } else if (typeof next === 'string') { - if (next.startsWith('"')) { - return { - type: N.StringLiteral, - val: next.substr(1), - } - } - const ident = { - type: N.Ident, - val: next, - } - if (this.tokens.peek() === T.Of) { - return this.fnCall(ident); - } - return ident; - } else if (next === T.TotallyRight) { - return { - type: N.BoolLiteral, - val: true, - } - } else if (next === T.CompletelyWrong) { - return { - type: N.BoolLiteral, - val: false, - } - } else if (next === T.DiscoverHowTo) { - // fn literal - const fnName = this.tokens.next(); - if (this.tokens.peek(T.With)) { - this.tokens.next(); // with - // with args - const args = [this.expectIdentString()]; - while (this.tokens.peek() === T.Comma) { - this.tokens.next(); // comma - args.push(this.expectIdentString()); - } - return { - type: N.FnDecl, - name: fnName, - args: args, - body: this.expr(), - } - } else { - return { - type: N.FnDecl, - name: fnName, - args: [], - body: this.expr(), - } - } - } else if (next === T.RumorHasIt) { - // block - const exprs = []; - while (this.tokens.hasNext() && this.tokens.peek() !== T.EndOfStory) { - exprs.push(this.expr()); - } - this.tokens.expect(T.EndOfStory); - return { - type: N.ExprGroup, - exprs: exprs, - }; - } else if (next === T.LParen) { - // block, but guarded by parens, for binary exprs - const exprs = []; - while (this.tokens.hasNext() && this.tokens.peek() !== T.RParen) { - exprs.push(this.expr()); - } - this.tokens.expect(T.RParen); - return { - type: N.ExprGroup, - exprs: exprs, - }; - } - - throw new Error(`Parsing error: expected ident, literal, or block, got ${ - next.toString() - } before ${this.tokens.peek().toString()}`); - } - expr() { - const next = this.tokens.next(); - if (next === T.WhatIf) { - // if expr - const cond = this.expr(); - const ifBody = this.expr(); - - let elseBody = null; - if (this.tokens.peek() == T.LiesBang) { - this.tokens.next(); // LiesBang - elseBody = this.expr(); - } - return { - type: N.IfExpr, - cond: cond, - ifBody: ifBody, - elseBody: elseBody, - } - } else if (next === T.ExpertsClaim) { - // assignment - const name = this.expectIdentString(); - this.tokens.expect(T.ToBe); - const val = this.expr(); - return { - type: N.Assignment, - name, - val, - } - } else if (next === T.ShockingDevelopment) { - // return - return { - type: N.ReturnExpr, - val: this.expr(), - } - } else if (next === T.PleaseLikeAndSubscribe) { - // prog end - return { - type: N.ProgEndExpr, - } - } else if (next === T.YouWontWantToMiss) { - // print expr - return { - type: N.PrintExpr, - val: this.expr(), - } - } else if (next === T.LatestNewsOn) { - // input expr - return { - type: N.InputExpr, - val: this.expr(), - } - } - - this.tokens.backstep(); - const atom = this.atom(); - if (BINARY_OPS.includes(this.tokens.peek())) { - // infix binary ops - const left = atom; - const op = this.tokens.next(); - const right = this.atom(); - return { - type: N.BinaryOp, - op, - left, - right, - } - } - - return atom; - } - fnCall(fnNode) { - this.tokens.expect(T.Of); - const args = [this.expr()]; - while (this.tokens.peek() === T.Comma) { - this.tokens.next(); // comma - args.push(this.expr()); - } - return { - type: N.FnCall, - fn: fnNode, - args: args, - } - } -} - -/* executor (tree walk) */ - -/** - * Abused (slightly) to easily return values upstack - */ -class ReturnError { - constructor(value) { - this.value = value; - } - unwrap() { - return this.value; - } -} - -export class Environment { - constructor(runtime) { - /** - * Runtime contains the following functions: - * - print(s) - * - input(s) - */ - this.runtime = runtime; - this.scopes = [{}]; // begin with global scope - } - run(nodes) { - let rv; - for (const node of nodes) { - rv = this.eval(node); - } - return rv; - } - eval(node) { - const scope = this.scopes[this.scopes.length - 1]; - - switch (node.type) { - case N.NumberLiteral: - case N.StringLiteral: - case N.BoolLiteral: - return node.val; - case N.FnDecl: { - const fnValue = { - node: node, - closure: { ...scope } - }; - scope[node.name] = fnValue; - return fnValue; - } - case N.FnCall: { - const fn = this.eval(node.fn); - const args = node.args.map(arg => this.eval(arg)); - - const calleeScope = {}; - fn.node.args.forEach((argName, i) => { - calleeScope[argName] = args[i]; - }); - - this.scopes.push(fn.closure); - this.scopes.push(calleeScope); - let rv; - try { - this.eval(fn.node.body); - } catch (maybeReturnErr) { - if (maybeReturnErr instanceof ReturnError) { - rv = maybeReturnErr.unwrap(); - } else { - throw maybeReturnErr; - } - } - this.scopes.pop(); - this.scopes.pop(); - - return rv; - } - case N.Ident: { - let i = this.scopes.length - 1; - while (i >= 0) { - if (node.val in this.scopes[i]) { - return this.scopes[i][node.val]; - } - i --; - } - throw new Error(`Runtime error: Undefined variable "${node.val}"`); - } - case N.Assignment: { - scope[node.name] = this.eval(node.val); - return scope[node.name]; - } - case N.BinaryOp: { - const left = this.eval(node.left); - const right = this.eval(node.right); - switch (node.op) { - case T.IsActually: - return left === right; - case T.And: - return left && right; - case T.Or: - return left || right; - case T.Plus: - return left + right; - case T.Minus: - return left - right; - case T.Times: - return left * right; - case T.DividedBy: - return left / right; - case T.Modulo: - return left % right; - case T.Beats: - return left > right; - case T.SmallerThan: - return left < right; - default: - throw new Error(`Runtime error: Unknown binary op ${node.op.toString()}`); - } - } - case N.IfExpr: { - if (this.eval(node.cond)) { - return this.eval(node.ifBody); - } - if (node.elseBody != null) { - return this.eval(node.elseBody); - } - } - case N.ExprGroup: { - if (!node.exprs.length) { - throw new Error('Runtime error: Empty expression group with no expressions'); - } - - let rv; - for (const expr of node.exprs) { - rv = this.eval(expr); - } - return rv; - } - case N.ReturnExpr: { - const rv = this.eval(node.val); - throw new ReturnError(rv); - } - case N.ProgEndExpr: { - // do nothing - break; - } - case N.PrintExpr: { - let val = this.eval(node.val); - // shim for boolean to-string's - if (val === true) { - val = 'TOTALLY RIGHT'; - } else if (val === false) { - val = 'COMPLETELY WRONG'; - } - this.runtime.print(val); - return val; - } - case N.InputExpr: { - let val = this.eval(node.val); - // shim for boolean to-string's - if (val === true) { - val = 'TOTALLY RIGHT'; - } else if (val === false) { - val = 'COMPLETELY WRONG'; - } - return this.runtime.input(val); - } - default: - console.log(JSON.stringify(node, null, 2)); - throw new Error(`Runtime error: Unknown AST Node of type ${ - node.type.toString() - }:\n${JSON.stringify(node, null, 2)}`); - } - } -} diff --git a/src/toys/turing/css/styles.css b/src/toys/turing/css/styles.css deleted file mode 100644 index 2407cb4..0000000 --- a/src/toys/turing/css/styles.css +++ /dev/null @@ -1,66 +0,0 @@ -.tape { - display: flex; - gap: 0.25rem; - padding: var(--space-md); - overflow-x: auto; - background: var(--surface-alt); - border: var(--border-width) solid var(--border); - box-shadow: inset 1px 1px 0 var(--border-light), - inset -1px -1px 0 var(--border-dark); - min-height: 5rem; -} - -.cell { - position: relative; - width: 3.25rem; - padding: var(--space-xs); - background: var(--surface); - border: 2px solid var(--border-dark); - box-shadow: inset 1px 1px 0 var(--border-light), - inset -1px -1px 0 var(--border-dark); - flex-shrink: 0; -} - -.cell input { - width: 100%; - text-align: center; - border: none; - background: transparent; - font-family: var(--font-mono); - color: var(--fg); - font-size: 0.875rem; - padding: 0.25rem; -} - -.cell input:focus { - outline: 1px dotted var(--fg); - outline-offset: 2px; -} - -.cell.active { - border-color: var(--primary); - background: color-mix(in srgb, var(--primary) 15%, var(--surface)); -} - -.cell.active::after { - content: '▲'; - position: absolute; - bottom: -1.5rem; - left: 50%; - transform: translateX(-50%); - color: var(--primary); - font-size: 1rem; - line-height: 1; -} - -#state-text { - font-family: var(--font-mono); - font-weight: 700; - margin-top: var(--space-md); -} - -.controls { - display: flex; - gap: var(--space-sm); - flex-wrap: wrap; -} diff --git a/src/toys/turing/index.html b/src/toys/turing/index.html deleted file mode 100644 index 838c5bf..0000000 --- a/src/toys/turing/index.html +++ /dev/null @@ -1,42 +0,0 @@ - - - - - - - - Turing Machine - - -
-
-

Turing Machine

- -

State: _, Step: 0

- -
- -
- - - - -
- -
-
- - -
- -
-
-
-
- - - - - - diff --git a/src/toys/turing/js/machine.js b/src/toys/turing/js/machine.js deleted file mode 100644 index 6af4be6..0000000 --- a/src/toys/turing/js/machine.js +++ /dev/null @@ -1,75 +0,0 @@ -export class TuringMachine { - constructor({ - tape, - rules, - startState, - acceptStates = [], - rejectStates = [] - }) { - this.tape = tape; - this.rules = rules; - this.state = startState; - this.acceptStates = new Set(acceptStates); - this.rejectStates = new Set(rejectStates); - this.iteration = 0; - } - - step() { - if (this.isHalted()) { - return false; - } - - const currentSymbol = this.tape.readHead(); - const ruleKey = this.getRuleKey(this.state, currentSymbol); - if (!this.rules.has(ruleKey)) { - return false; - } - - const { nextState, writeSymbol, direction } = this.rules.get(ruleKey); - this.tape.writeHead(writeSymbol); - - if (direction === "R") { - this.tape.moveRight(); - } else if (direction === "L") { - this.tape.moveLeft(); - } - - this.state = nextState; - this.iteration += 1; - return !this.isHalted(); - } - - canStep() { - if (this.isHalted()) { - return false; - } - - const currentSymbol = this.tape.readHead(); - const ruleKey = this.getRuleKey(this.state, currentSymbol); - return this.rules.has(ruleKey); - } - - getRuleKey(state, symbol) { - return `${state}:${symbol}`; - } - - isAccepting() { - return this.acceptStates.has(this.state); - } - - isRejecting() { - return this.rejectStates.has(this.state); - } - - isHalted() { - return this.isAccepting() || this.isRejecting(); - } - - getStateStatus() { - return `State: ${this.state}, Step: ${this.iteration}`; - } - - getState() { - return this.state; - } -} diff --git a/src/toys/turing/js/main.js b/src/toys/turing/js/main.js deleted file mode 100644 index c9f37fc..0000000 --- a/src/toys/turing/js/main.js +++ /dev/null @@ -1,4 +0,0 @@ -import { TuringMachineUI } from "./ui.js"; -document.addEventListener("DOMContentLoaded", (event) => { - new TuringMachineUI(); -}); diff --git a/src/toys/turing/js/parser.js b/src/toys/turing/js/parser.js deleted file mode 100644 index dd65613..0000000 --- a/src/toys/turing/js/parser.js +++ /dev/null @@ -1,141 +0,0 @@ -export function parseInstructionSet(code) { - const lines = code.split("\n"); - const instructions = []; - const config = { - startState: null, - acceptStates: new Set(), - rejectStates: new Set() - }; - - lines.forEach((line, lineIndex) => { - const withoutComments = line.replace(/\/\/.*$/, "").trim(); - if (!withoutComments) { - return; - } - - if (withoutComments.startsWith("#")) { - applyDirective(withoutComments.slice(1).trim(), config, lineIndex + 1); - return; - } - - const parts = withoutComments.split(/\s+/).filter(Boolean); - if (parts.length !== 5) { - throw new Error(`Invalid instruction on line ${lineIndex + 1}: expected 5 parts, received ${parts.length}`); - } - - const [fromState, readSymbol, writeSymbol, direction, toState] = parts; - if (!config.startState) { - config.startState = fromState; - } - - instructions.push({ fromState, readSymbol, writeSymbol, direction, toState, line: lineIndex + 1 }); - }); - - if (!instructions.length) { - throw new Error("No instructions provided"); - } - - const { acceptStates, rejectStates } = deriveHaltingStates(instructions, config); - const rules = buildRuleMap(instructions); - - return { - rules, - startState: config.startState ?? instructions[0].fromState, - acceptStates, - rejectStates - }; -} - -function applyDirective(directiveLine, config, lineNumber) { - if (!directiveLine) { - return; - } - - const [keyword, ...values] = directiveLine.split(/\s+/).filter(Boolean); - if (!keyword) { - return; - } - - switch (keyword.toLowerCase()) { - case "start": { - if (values.length !== 1) { - throw new Error(`#start on line ${lineNumber} must provide exactly one state`); - } - config.startState = values[0]; - break; - } - case "accept": - case "accepts": - case "accepting": { - if (!values.length) { - throw new Error(`#${keyword} on line ${lineNumber} must include at least one state`); - } - values.forEach((value) => config.acceptStates.add(value)); - break; - } - case "reject": - case "rejects": - case "rejecting": { - if (!values.length) { - throw new Error(`#${keyword} on line ${lineNumber} must include at least one state`); - } - values.forEach((value) => config.rejectStates.add(value)); - break; - } - default: - throw new Error(`Unknown directive '#${keyword}' on line ${lineNumber}`); - } -} - -function deriveHaltingStates(instructions, config) { - const fromStates = new Set(); - const toStates = new Set(); - const allStates = new Set(); - - instructions.forEach(({ fromState, toState }) => { - fromStates.add(fromState); - toStates.add(toState); - allStates.add(fromState); - allStates.add(toState); - }); - - // Remove any overlap so rejects always win - config.rejectStates.forEach((state) => config.acceptStates.delete(state)); - - if (!config.acceptStates.size) { - for (const state of allStates) { - if (!fromStates.has(state) && toStates.has(state) && !config.rejectStates.has(state)) { - config.acceptStates.add(state); - } - } - } - - return { - acceptStates: Array.from(config.acceptStates), - rejectStates: Array.from(config.rejectStates) - }; -} - -function buildRuleMap(instructions) { - const rules = new Map(); - - instructions.forEach(({ fromState, readSymbol, writeSymbol, direction, toState, line }) => { - const dir = direction.toUpperCase(); - if (!["L", "R", "S"].includes(dir)) { - throw new Error(`Invalid direction '${direction}' on line ${line}. Use L, R, or S.`); - } - - const key = `${fromState}:${readSymbol}`; - if (rules.has(key)) { - throw new Error(`Duplicate rule for state '${fromState}' reading '${readSymbol}' (line ${line})`); - } - - rules.set(key, { - nextState: toState, - writeSymbol, - direction: dir - }); - }); - - return rules; -} diff --git a/src/toys/turing/js/samples.js b/src/toys/turing/js/samples.js deleted file mode 100644 index 0998ee4..0000000 --- a/src/toys/turing/js/samples.js +++ /dev/null @@ -1,88 +0,0 @@ -// Example programs with initial tape states -export const EXAMPLE_PROGRAMS = [ - { - name: "Replace two B's", - code: `#start q0 -#accept acc -#reject rej - -q0 B 1 R q1 -q1 1 1 R q1 -q1 B 1 R acc`, - initialTape: "" - }, - { - name: "Binary equality checker", - code: `// https://stackoverflow.com/questions/59045832 - -#start q0 -#accept acc -#reject rej - -q0 0 X R q1 -q0 1 X R q2 -q0 = = R q7 -q1 0 0 R q1 -q1 1 1 R q1 -q1 = = R q3 -q2 0 0 R q2 -q2 1 1 R q2 -q2 = = R q4 -q3 X X R q3 -q3 0 X L q5 -q3 1 1 L rej -q3 B B L rej -q4 X X R q4 -q4 0 0 L rej -q4 B B L rej -q4 1 X L q5 -q5 X X L q5 -q5 = = L q6 -q6 0 0 L q6 -q6 1 1 L q6 -q6 X X R q0 -q7 X X R q7 -q7 B B L q8 -q7 0 0 L rej -q7 1 1 L rej -q8 X X L q8 -q8 0 0 L q8 -q8 1 1 L q8 -q8 = = L acc`, - initialTape: "1011=1011" - }, - { - name: "Binary addition", - code: `// https://stackoverflow.com/questions/59045832 - -#start q0 -#accept acc -#reject rej - -q0 B B R q0 -q0 0 0 R q0 -q0 1 1 R q0 -q0 + + R q1 -q1 0 0 R q1 -q1 1 1 R q1 -q1 B B L q2 -q2 0 1 L q2 -q2 1 0 L q3 -q2 + + R q5 -q3 0 0 L q3 -q3 1 1 L q3 -q3 + + L q4 -q4 0 1 R q0 -q4 1 0 L q4 -q4 B 1 R q0 -q5 1 B R q5 -q5 B B R q6 -q6 B B L q6 -q6 + B L q7 -q7 0 0 L q7 -q7 1 1 L q7 -q7 B B R acc -`, - initialTape: "101+110" - } -]; diff --git a/src/toys/turing/js/tape.js b/src/toys/turing/js/tape.js deleted file mode 100644 index fd05366..0000000 --- a/src/toys/turing/js/tape.js +++ /dev/null @@ -1,103 +0,0 @@ -const ESCAPE_REGEX = /[.*+?^${}()|[\]\\]/g; - -function escapeForRegex(value) { - return value.replace(ESCAPE_REGEX, "\\$&"); -} - -export class Tape { - constructor({ - initialContent = "", - blankSymbol = "B", - minLength = 50, - padding = 40 - } = {}) { - this.blankSymbol = blankSymbol; - this.minLength = minLength; - this.padding = padding; - this.reset(initialContent); - } - - reset(initialContent = "") { - const targetLength = Math.max(this.minLength, initialContent.length + this.padding); - this.cells = Array(targetLength).fill(this.blankSymbol); - const startOffset = Math.floor((targetLength - initialContent.length) / 2); - for (let i = 0; i < initialContent.length; i++) { - this.cells[startOffset + i] = initialContent[i]; - } - this.headIndex = startOffset; - } - - get length() { - return this.cells.length; - } - - getHeadIndex() { - return this.headIndex; - } - - readHead() { - return this.getCell(this.headIndex); - } - - writeHead(symbol) { - this.cells[this.headIndex] = symbol || this.blankSymbol; - } - - readAt(index) { - return this.getCell(index); - } - - writeAt(index, symbol) { - if (index < 0) { - throw new Error("Cannot write to a negative tape index"); - } - this.ensureRightCapacity(index); - this.cells[index] = symbol || this.blankSymbol; - } - - setHead(index) { - if (index < 0) { - throw new Error("Head index cannot be negative"); - } - this.ensureRightCapacity(index); - this.headIndex = index; - } - - moveLeft() { - if (this.headIndex === 0) { - this.cells.unshift(this.blankSymbol); - } else { - this.headIndex -= 1; - return; - } - } - - moveRight() { - this.headIndex += 1; - if (this.headIndex >= this.cells.length) { - this.cells.push(this.blankSymbol); - } - } - - getCell(index) { - if (index < 0 || index >= this.cells.length) { - return this.blankSymbol; - } - return this.cells[index]; - } - - ensureRightCapacity(index) { - while (index >= this.cells.length) { - this.cells.push(this.blankSymbol); - } - } - - getContents({ trimTrailing = true } = {}) { - let snapshot = this.cells.join(""); - if (trimTrailing) { - const regex = new RegExp(`${escapeForRegex(this.blankSymbol)}+$`, "g"); - snapshot = snapshot.replace(regex, ""); - } - return snapshot; - } -} diff --git a/src/toys/turing/js/ui.js b/src/toys/turing/js/ui.js deleted file mode 100644 index ae01a4b..0000000 --- a/src/toys/turing/js/ui.js +++ /dev/null @@ -1,386 +0,0 @@ -import { Tape } from "./tape.js"; -import { TuringMachine } from "./machine.js"; -import { parseInstructionSet } from "./parser.js"; -import { EXAMPLE_PROGRAMS } from "./samples.js"; - -const SCROLL_THRESHOLD = 3; - -export class TuringMachineUI { - constructor() { - this.machine = null; - this.editor = null; - this.intervalId = null; - this.isRunning = false; - this.initialTapeSize = 50; - this.blankSymbol = "B"; - this.currentProgramIndex = 0; - this.loadedFromURL = false; - this.urlTapeState = ""; - this.lastScrollPosition = 0; - this.renderedTapeLength = 0; - this.simulationInterval = 200; - - this.elements = { - tape: document.getElementById("tape"), - stateText: document.getElementById("state-text"), - runBtn: document.getElementById("run-btn"), - stepBtn: document.getElementById("step-btn"), - resetBtn: document.getElementById("reset-btn"), - copyBtn: document.getElementById("copy-btn"), - programSelect: document.getElementById("program-select") - }; - - this.init(); - } - - init() { - this.setupEditor(); - this.populateProgramSelect(); - this.setupEventListeners(); - this.loadFromURL(); - } - - setupEditor() { - this.editor = adelieEditor.init("#code-editor", { - language: "javascript" - }); - - this.editor.dom.addEventListener("input", () => { - this.machine = null; - }); - } - - populateProgramSelect() { - if (!this.elements.programSelect) { - return; - } - - this.elements.programSelect.innerHTML = ""; - EXAMPLE_PROGRAMS.forEach((program, index) => { - const option = document.createElement("option"); - option.value = index.toString(); - option.textContent = program.name; - this.elements.programSelect.appendChild(option); - }); - } - - setupEventListeners() { - this.elements.runBtn.addEventListener("click", () => this.toggleRun()); - this.elements.stepBtn.addEventListener("click", () => this.step()); - this.elements.resetBtn.addEventListener("click", () => this.reset()); - this.elements.copyBtn.addEventListener("click", () => this.copyState()); - - this.elements.programSelect.addEventListener("change", (event) => { - this.loadProgram(parseInt(event.target.value, 10)); - }); - - document.addEventListener("keydown", (event) => { - if (event.ctrlKey && event.key === "Enter") { - event.preventDefault(); - this.reset(); - setTimeout(() => this.run(), 0); - } - }); - - this.elements.tape.addEventListener("input", (event) => this.handleTapeInput(event)); - this.elements.tape.addEventListener("focusin", () => { - if (this.isRunning) { - this.pause(); - } - }); - } - - handleTapeInput(event) { - if (!this.machine) { - return; - } - const target = event.target; - if (!(target instanceof HTMLInputElement)) { - return; - } - - const parentCell = target.closest(".cell"); - if (!parentCell) { - return; - } - - const index = Number(parentCell.dataset.index); - if (Number.isNaN(index)) { - return; - } - - const sanitized = (target.value || this.blankSymbol).slice(0, 1); - target.value = sanitized; - this.machine.tape.writeAt(index, sanitized || this.blankSymbol); - } - - loadFromURL() { - const urlParams = new URLSearchParams(window.location.search); - const startState = urlParams.get("start") ?? ""; - const instructions = urlParams.get("instructions"); - - if (!instructions) { - this.loadProgram(0); - return; - } - - try { - const code = atob(instructions); - this.setEditorContent(code); - this.loadedFromURL = true; - this.urlTapeState = startState; - this.compile(this.urlTapeState); - } catch (error) { - console.error("Failed to load from URL", error); - this.loadedFromURL = false; - this.loadProgram(0); - } - } - - setEditorContent(content) { - const length = this.editor.state.doc.toString().length; - this.editor.dispatch({ - changes: { from: 0, to: length, insert: content } - }); - } - - getEditorContent() { - return this.editor.state.doc.toString(); - } - - loadProgram(index = 0) { - const program = EXAMPLE_PROGRAMS[index]; - if (!program) { - return; - } - - this.loadedFromURL = false; - this.currentProgramIndex = index; - this.elements.programSelect.value = index.toString(); - this.setEditorContent(program.code); - - try { - this.compile(program.initialTape); - } catch (error) { - this.elements.stateText.innerHTML = `Error: ${error.message}`; - } - } - - compile(initialTape = "") { - this.pause(); - - const code = this.getEditorContent(); - const instructionSet = parseInstructionSet(code); - const tapeSeed = initialTape || EXAMPLE_PROGRAMS[this.currentProgramIndex]?.initialTape || ""; - - const tape = new Tape({ - initialContent: tapeSeed, - blankSymbol: this.blankSymbol, - minLength: Math.max(this.initialTapeSize, tapeSeed.length + 40) - }); - - this.machine = new TuringMachine({ - tape, - rules: instructionSet.rules, - startState: instructionSet.startState, - acceptStates: instructionSet.acceptStates, - rejectStates: instructionSet.rejectStates - }); - - this.renderedTapeLength = 0; - this.lastScrollPosition = tape.getHeadIndex(); - this.updateStateDisplay(); - this.updateTape(true); - } - - reset() { - this.pause(); - - if (this.loadedFromURL) { - try { - this.compile(this.urlTapeState); - return; - } catch (error) { - this.elements.stateText.innerHTML = `Error: ${error.message}`; - } - } - - this.loadProgram(this.currentProgramIndex); - } - - step() { - if (!this.machine) { - try { - this.compile(); - } catch (error) { - this.elements.stateText.innerHTML = `Error: ${error.message}`; - return; - } - } - - const canContinue = this.machine.step(); - this.updateStateDisplay(); - this.updateTape(); - - if (!canContinue) { - this.pause(); - } - } - - toggleRun() { - if (this.isRunning) { - this.pause(); - } else { - this.run(); - } - } - - run() { - if (this.isRunning) { - return; - } - - if (!this.machine) { - try { - this.compile(); - } catch (error) { - this.elements.stateText.innerHTML = `Error: ${error.message}`; - return; - } - } - - this.isRunning = true; - this.elements.runBtn.textContent = "⏸ Pause"; - this.elements.runBtn.classList.remove("primary"); - - this.intervalId = setInterval(() => { - const canContinue = this.machine.step(); - this.updateStateDisplay(); - this.updateTape(); - if (!canContinue) { - this.pause(); - } - }, this.simulationInterval); - } - - pause() { - if (!this.isRunning) { - return; - } - - this.isRunning = false; - clearInterval(this.intervalId); - this.intervalId = null; - - this.elements.runBtn.textContent = "▶ Run (Ctrl + Enter)"; - this.elements.runBtn.classList.add("primary"); - } - - updateStateDisplay() { - if (!this.machine) { - this.elements.stateText.textContent = "State: _, Step: 0"; - return; - } - - const status = this.machine.getStateStatus(); - - if (!this.machine.canStep()) { - if (this.machine.isAccepting()) { - this.elements.stateText.innerHTML = `Accept(${status})`; - } else if (this.machine.isRejecting()) { - this.elements.stateText.innerHTML = `Reject(${status})`; - } else { - this.elements.stateText.innerHTML = `Halt(${status})`; - } - return; - } - - this.elements.stateText.textContent = status; - } - - updateTape(forceRender = false) { - if (!this.machine) { - return; - } - - const tape = this.machine.tape; - if (forceRender || this.renderedTapeLength !== tape.length) { - this.renderTape(); - } - - const cells = this.elements.tape.querySelectorAll(".cell"); - const headIndex = tape.getHeadIndex(); - - cells.forEach((cell, index) => { - cell.dataset.index = index.toString(); - const input = cell.querySelector("input"); - const value = tape.getCell(index); - if (input.value !== value) { - input.value = value; - } - - if (index === headIndex) { - cell.classList.add("active"); - this.maybeScrollIntoView(cell, headIndex, forceRender); - } else { - cell.classList.remove("active"); - } - }); - } - - renderTape() { - const fragment = document.createDocumentFragment(); - const tape = this.machine.tape; - - for (let i = 0; i < tape.length; i++) { - fragment.appendChild(this.createCell(i, tape.getCell(i))); - } - - this.elements.tape.innerHTML = ""; - this.elements.tape.appendChild(fragment); - this.renderedTapeLength = tape.length; - } - - createCell(index, value) { - const cell = document.createElement("div"); - cell.classList.add("cell"); - cell.dataset.index = index.toString(); - - const input = document.createElement("input"); - input.type = "text"; - input.maxLength = 1; - input.value = value; - - cell.appendChild(input); - return cell; - } - - maybeScrollIntoView(cell, headIndex, forceImmediate = false) { - if (forceImmediate) { - cell.scrollIntoView({ behavior: "auto", block: "nearest", inline: "center" }); - this.lastScrollPosition = headIndex; - return; - } - - if (Math.abs(headIndex - this.lastScrollPosition) < SCROLL_THRESHOLD) { - return; - } - - cell.scrollIntoView({ behavior: "smooth", block: "nearest", inline: "center" }); - this.lastScrollPosition = headIndex; - } - - copyState() { - if (!this.machine) { - return; - } - - const tapeState = this.machine.tape.getContents(); - const instructions = btoa(this.getEditorContent()); - const url = `${window.location.href.split("?")[0]}?start=${tapeState}&instructions=${instructions}`; - - navigator.clipboard.writeText(url) - .then(() => alert("State copied to clipboard!")) - .catch(() => alert("Failed to copy to clipboard")); - } -} -- cgit v1.3