From 94b912b649150b9863a62096ba4740b2cc8ad21a Mon Sep 17 00:00:00 2001 From: Elizabeth Alexander Hunt Date: Sun, 5 Jul 2026 12:42:44 -0700 Subject: Factor --- 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 ---------- 53 files changed, 5561 deletions(-) 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 (limited to 'src') 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