Compare commits

..

10 commits

19 changed files with 2605 additions and 74 deletions

View file

@ -1,3 +1,5 @@
%% title = "about the treehouse"
% id = "01H89RFHCQCD3E1XS5XAPW86J5"
- liquidex's treehouse can be thought of as many things.

View file

@ -14,7 +14,7 @@ feel free to go back at any time by clicking here.
- welcome! make yourself at home :ralsei_wave:
% id = "01H8VWEHX501SNYQTE61WX7YJC"
- [_"owo, what's this?"_][page:kuroneko]{.secret}
- :folder: [_"owo, what's this?"_][page:kuroneko]{.secret}
% id = "about"
content.link = "about"
@ -26,7 +26,7 @@ feel free to go back at any time by clicking here.
% id = "01H8V556P1GRAA3717VH3QJFMV"
classes.branch_children = "index:hobby-corners"
- hobby corners
- :folder: hobby corners
% id = "programming"
content.link = "programming"
@ -85,7 +85,11 @@ feel free to go back at any time by clicking here.
```
% id = "01HFYZKREWE2AM61ZRW3R501H6"
- various thoughts
- :folder: /var
% id = "short-thoughts"
content.link = "short-thoughts"
+ ## [sh](https://www.youtube.com/watch?v=rFL2VOdb944){.secret}ort thoughts
% id = "philosophy"
content.link = "philosophy"

View file

@ -5,6 +5,10 @@
id = "01H969NN1BJYFYJJR0F458ACPN"
+ ### :folder: stuff I listen to
% content.link = "music/brainz"
id = "01J73BSW850Z2SDVM832FB60QT"
+ ### :page: the ListenBrainz dataset
% content.link = "music/fuck-drm"
id = "01HPECQ3ZE1YKC1FS2X23H77R2"
+ ### :folder: fuck DRM

86
content/music/brainz.tree Normal file
View file

@ -0,0 +1,86 @@
%% title = "the ListenBrainz data set"
% id = "01J73BSW7VS69RQ84XWRAEYEHV"
- I've been using [ListenBrainz](https://listenbrainz.org) as my primary way of keeping track of my listens for a couple years now---I
don't recall exactly when I created my account, as I've imported my listens from Spotify, but my music players have been pushing data to ListenBrainz for a while now either way.
% id = "01J73BSW7VBGDHEYH2KERY66VY"
- I made an account because I was dissatisfied with being tied to a single service for all my music.
especially since I've been listening to more and more music outside of streaming services, which means my listening data wouldn't show up in Spotify.
% id = "01J73BSW7VT509JW9R1EWV5V2P"
- I chose ListenBrainz over a proprietary service, because I like the idea of building a huge data set of what people are listening to.
and adding my own two drops of water to that data set feels really cool.
not only can my data be used for greater scientific purposes, but I can also use _my own data_ for _my own scientific purposes_.
% id = "01J73BSW7VVXN98C2X6BTDGWV4"
- obviously there's the question of _data ownership_; and technically speaking, I don't _own_ my data on ListenBrainz.
but honestly I don't mind that so much, since it's an open source service, that lets me export my data whenever I want to.
% id = "01J73BSW7VEA11HX4ANRVA8JNT"
- you can find my account [here][def:social/listenbrainz].
% id = "01J73BSW7VYBWPM3DZ08HGTRC6"
- below are some things I've done with my data, as well as ideas about what I could do with it in the future.
when I have the time, patience, and motivation, of course.
% id = "01J73BSW7VTGN0ZXSXN206EACN"
- :TODO: the importing of my listens from Spotify
% id = "01J73BSW7V5EP9Y8GHG3MVTV01"
- I've never published the code, but I wrote a little script that took my exported Spotify data (thanks, GDPR!) and imported it into ListenBrainz.
not without faults though. it'd be nice to write up more about it.
% id = "01J73BSW7VJ5C3SRGZR35H9CXA"
- :TODO: nerdsniped by Spotify Wrapped
% id = "01J73BSW7V8TA5SQKARPDR1AP4"
- [it's this](https://github.com/liquidev/nerdsniped-by-spotify-wrapped); it'd be nice to write up some more about it.
% id = "01J73BSW7VY08T1S2W7RG2BY26"
- :TODO: a story told by a thousand tracks
% id = "01J73BSW7VX1WCVZKVRME1WW3P"
- I'd like to use my data set to take my top 1000 most listened tracks, and sort them in chronological order---tracks
I listened to earliest come first---to generate a sort of _life's soundtrack._
% id = "01J73BSW7VWJNCHC3X3WBJVMA5"
+ I already have such a _curated_ playlist like this on Spotify, but I'm curious how such a playlist would look, were it generated from objective listen data.
% id = "01J73BSW7VXYKVRBGY1RCVDWA8"
- sans some inaccuracies of course, since Bogdan Raczynski's [boku mo wakaran](https://bogdanraczynski.bandcamp.com/album/boku-mo-wakaran) got imported all as one track.
this is because the individual tracks are untitled, and ListenBrainz failed to differentiate them, despite my Spotify data saying "Untitled 1," "Untitled 2," and so on, with track indices.
so I'd have to skip it, which is a real shame, since it's a pretty cool album.
% id = "01J73BSW7V9FNPCWXHBVDEDPBC"
- :TODO: diminishing listens, or longer attention span?
% id = "01J73BSW7VHTJ81WBRF2RT40PD"
- in 2018, I listened to 25652 tracks.
in 2019, I listened to 34904 tracks.
in 2020, I listened to 29248 tracks.
in 2021 it was 24451, in 2022 it was 22437, and in 2023 it was 22002.
% id = "01J73BSW7VTJ6549Y74JGPVTWF"
- we're already past half of 2024, and I'd only listened to 10536 tracks so far as of typing this.
if I keep up the same pace throughout the other half of the year, and if I'm doing my math correctly, that'd mean at the end of the year I will have listened to around 15000 tracks.
that's quite a bit less than 2023, isn't it?
% id = "01J73BSW7V8QEM1P7ME5MR6VXX"
- I wonder why the numbers are getting lower and lower each year.
is it because I listen to less music overall, or is it because I listen to longer music?
I strongly presume it's the former, as a lot of things have changed for me in 2024---I
moved out to live with my girlfriend, and that means spending more time on housework and other mundane things.
% id = "01J73BSW7VKG46GQAK3H2RPPAQ"
- until recently I didn't have a pair of good wireless headphones to listen to music while cooking, cleaning, or doing the laundry, so that reduced my listen count quite a bit.
% id = "01J73BSW7VR7H7HW7JZCFB18W4"
- funny how in July there was a large spike of 1747 listens, where other months seem to be oscillating around 1000 listens on average.
I don't know what that was about.
% id = "01J73BSW7VCC5N7MQ705J4QM7Q"
- I do wonder where the 2000 listens per month from last year went though.
% id = "01J73BSW7VZ2T6DNQRK54TDSND"
- either way, I'd like to derive a data set that's counted by _minutes listened_ to iron over this, similar to what I did with Spotify Wrapped in 2022.

View file

@ -7,9 +7,13 @@
% id = "01HPD4XQQ5GPQ20C6BPA8G670F"
- ### :folder: blog
% content.link = "programming/blog/tairu"
id = "01HPD4XQQ5WM0APCAX014HM43V"
+ :page: [featured]{.badge .blue} tairu - an interactive exploration of 2D autotiling techniques
% content.link = "programming/blog/haku"
id = "01J4J4PAXRWZDP9PAZNGCQ9S3D"
+ [featured]{.badge .blue} :page: haku - writing a little programming language for fun
% content.link = "programming/blog/buildsome"
id = "01J7BYKQHZKYQ969T3PH3V8HF1"
+ :page: not quite buildless
% content.link = "programming/blog/lvalues"
id = "01HY5R1ZW0M0Y5KQ1E8F0Q73ZT"
@ -23,6 +27,10 @@
id = "01HTWN4XB2YMF3615BE8V6Y76A"
+ :page: OR-types
% content.link = "programming/blog/tairu"
id = "01HPD4XQQ5WM0APCAX014HM43V"
+ :page: tairu - an interactive exploration of 2D autotiling techniques
% id = "programming/projects"
content.link = "programming/projects"
+ ### :folder: projects

View file

@ -0,0 +1,698 @@
%% title = "not quite buildless"
% id = "01J7BYKQGYPF50050K67N2MP1G"
- ...buildsome?
% id = "01J7BYKQGYBH7DGS459RAHMTN5"
- I've seen a few articles about going _buildless_ with programming Web applications.
% id = "01J7BYKQGYCB5WWQ8MZE1FPZE4"
- there's of course the classic [Vanilla JS](http://vanilla-js.com/)
% id = "01J7BYKQGYT8YW993V21WP9CVY"
- a couple weeks ago I saw someone's idea for [_Vanilla Prime_](https://github.com/morris/vanilla-prime), which is an opinion on how to make Vanilla JS somewhat optimal and more pleasant
% id = "01J7BYKQGY9WAXZYSQAQWGGXSE"
- and yesterday I saw [_Going Buildless_](https://mxb.dev/blog/buildless/), a blog post which evaluates how far you can go without _any_ builds.
% id = "01J7BYKQGYTM78Y1170ERMPE5F"
- as a big fan of simplicity, I really like that there's a corner of the Web which values simplicity over 100% convenience.
% id = "01J7BYKQGYFZ5RTRYXKMR0V815"
- a lot of focus in modern day Web development seems to be on delivering apps ASAP but implemented inefficiently, and then trying to iron over that with complex tooling such as minifiers.
% id = "01J7BYKQGYK1YKB0XTWAN4F9FA"
- I think there's a genuine use case for that---if so many people are using it, that means there's a demand for it.
but I honestly don't like the complexity.
I deal with [enough of it](https://www.unrealengine.com/) at work, and I wouldn't wanna have to understand a complex build toolchain for my little homegrown website.
% id = "01J7BYKQGYW5DS0PP13QA2WXDV"
- I mean, if the browser does it for you... then it's probably smart to [conserve that energy](https://anilist.co/anime/12189/Hyouka/), and do something more interesting!
% id = "01J7BYKQGY7BA7M2WM92H4Q7W6"
- be it your energy, or _the literal power that flows through your wall to fuel your computer running that complicated website build._
% id = "01J7BYKQGYK2RC8YEFS3GEGBAT"
- so I built my own static website generator!
% id = "01J7BYKQGYVW9NGN7HMWNNHF9Q"
+ but I promise this blog post is not _just_ about that---I've written about it [before][branch:01H89RFHCQAXJ0ST31TP1A104V], and I don't really see value in me writing _yet another_ blog post in the vein of "here's an overview of my statically generated blog's tech stack okay bye,"
% id = "01J7BYKQGYW5CJPBVH5N00352Q"
+ which isn't to say _you_ shouldn't write a blog post about your tech stack!
I think there's a lot of value in writing about how you made your blog.
it's another thing to _write_ about, and if you want to learn writing... you should write a lot!
% id = "01J7C1ASXR72HYP2P3R5YGP230"
- write write write a write a write.
% id = "01J7BYKQGYC6M88FFBKM7NSK7W"
- so here are some stories about my handiwork: a website built [with my very own two paws](https://www.youtube.com/watch?v=KVqwvU49JLg){.secret}.
enjoy!
% id = "01J7BYKQGYPSWQ9KB4V6361PD1"
- ### [I Ate My Themplate Engine](https://www.youtube.com/watch?v=lZktMGvW-rk){.secret}, and why not quite buildless
% id = "01J7BYKQGY7QEAN677Q9AT4T0V"
- I'll put _probably_ the most interesting bit right at the start.
the treehouse is _not quite_ buildless.
% id = "01J7BYKQGYRV6TKEACZ0DXNDG4"
- one might even call it... buildsome?
I mean, it's not quite buildful, and definitely not buildless...?
% id = "01J7BYKQGYE3AVESR1MBSA11JT"
- initially the treehouse started off as a completely statically generated website---to reduce the work needed to be done by the Web server, I decided to make it do as little as it possibly had to.
so static generation it ended up being.
% id = "01J7BYKQGY7TPY5D31GDVY86ZJ"
- static generation is super cool, because all you have to end up writing is a single function that deletes the existing output directory, creates a new one, and fills it in with a bunch of files.
% id = "01J7BYKQGYCE9YBTGAR88CPZ1Y"
- in case of the treehouse, the sources for the statically generated files are [`.tree` files][branch:01H8V55APDWN8TV31K4SXBTTWB], a bunch of [Handlebars][] templates, and a bunch of assorted static files, including JavaScript.
[Handlebars]: https://handlebarsjs.com/
% id = "01J7BYKQGYB2BHKE1CZGDXEA52"
- but honestly... I don't really like that choice of templating engine, or rather templating engine implementation!
the Rust implementation in particular has been kind of a pain in the butt, because it tries _very_ hard to be multithreading friendly.
that of course means some annoying `Send + Sync` bounds...
% id = "01J7BYKQGYA7FFV3ZT0RTC8R6Y"
- Handlebars allows you to write _helpers_, which are custom functions for processing text.
I use them a bit in the treehouse---for example, I have `include_static`, which pastes in a file from my `static` directory into the template.
% id = "01J7BYKQGY303Y8NA3VMWW5D5D"
- however, if a helper has to reference some outside data, or wants to cache its results because it's expensive to run...
you're pretty much out of luck, because the `Handlebars` registry does not encode any lifetime bounds on the helpers.
% id = "01J7BYKQGYT5JR68BZQ1FQX9HY"
- in a single-threaded setting this would mean you'd need to wrap your shared state in an `Rc`...
but in a single-threaded setting, only one thread can ever access the Handlebars instance, so you may as well let the helper mutate itself, or reference outside data! (as in `&`, not `Rc`)!
% id = "01J7BYKQGY4VRPQ0YB2SPQ3468"
- my personal opinion is that it would be neater if there was an initial setup step, which _freezes_ some parts of the registry---these parts become immutable after you set them up, and then you can send them out to threads---for example, behind an `Arc`.
afterwards, you construct the _render-time_ part, which requires mutable access---and therefore every thread that needs to render templates gets its own instance of that.
% id = "01J7BYKQGYKFM99J8HSF9GYP3H"
- using this architecture, you can forgo any locking at all, which is really cool for ergonomics, and great for performance!
% id = "01J7BYKQGYZPFFQYFTKE229141"
- I've looked at some other popular templating engines for Rust, and none of them really seem to solve this problem very neatly...
% id = "01J7BYKQGYBZRG3BSX9RP22YE2"
- so much so that I ended up writing my own little templating engine for the `.tree` format, which is stupid, but yeah.
I need to access my generator's shared state somehow (why? I don't remember :oh:), and I'm not gonna `Clone` _all that_, or put that beind a `Mutex`.
% id = "01J7BYKQGYZBM45J0HE07FCW9R"
- I'd love to write a more proper templating engine that implements that dream architecture of mine, but it's not a priority.
Handlebars works well enough where it does, and I don't really feel like spending my precious engineering hours on something that doesn't have much of a benefit.
% id = "01J7BYKQGYNNWVA8S560HANZVP"
- other than being really heckin' fun of course.
which is why I may do it someday anyways.
% id = "01J7BYKQGYGZ0S2AMHSKRBZKB1"
- at some point I wanted to add [OpenGraph](https://ogp.me/) metadata, so that branches you link to via permalinks [such as this one][branch:01J7BYKQGYGZ0S2AMHSKRBZKB1] would get nice embeds in Discord and other chat apps, including the branch's text---which prompted me to drop the [`try_files`](https://nginx.org/en/docs/http/ngx_http_core_module.html#try_files) and [`proxy_pass`](https://nginx.org/en/docs/http/ngx_http_proxy_module.html#proxy_pass) to an axum server instead.
but it was fun and simple while it lasted!
% id = "01J7BYKQGYJF77C68FPHTSCR0W"
- ### not incremental by design
% id = "01J7BYKQGYB6E2HFB38ZW92X0W"
- did you know: you don't need incremental builds, if your non-incremental builds are really fast!
% id = "01J7BYKQGY1MCKWKCJKVDHMJ13"
- on my Linux box at home, the treehouse manages to build itself in around 250ms.
on my work PC running Windows, it takes about 800ms, which is still very acceptable.
% id = "01J7BYKQGYDJ839P5MG0HNVG93"
- ...and that's a debug build!!
% id = "01J7BYKQGYVV2WRHT1J2HEJG0H"
- the bottleneck on Windows is probably due to IOps being much slower there.
I believe it's deleting and copying around files that takes so long, because there's a barely noticable but present delay on that task in the log output.
% id = "01J7BYKQGYBADRFGGY65G83BB7"
- this is the idea I built the treehouse generator in mind with---computers are pretty heckin' fast.
for the 56 unique `.tree`-derived pages the treehouse generates, most of the time is not actually spent parsing and generating text files, but rather...
reading metadata for image files, so that I can generate proper `width="" height=""` attributes on `<img>` elements.
% id = "01J7BYKQGY5QGYYR4NJYWW1CQ9"
- I don't have any profiling infrastructure set up just yet, but I suspect it's something with the [`image` crate](https://lib.rs/crates/image) doing more work than it needs to in order to obtain size metadata.
% id = "01J7BYKQGYSZ6FKG9E5GAFB13H"
- shame on me, but I don't have image size probing implemented for SVG just yet.
which I use for some emoji, such as :smile: :sparkles:
% id = "01J7BYKQGY0PR13G71PK163976"
- if I _wanted_ to implement incremental builds, I feel like the dependency tracking would get pretty hellish pretty quickly.
% id = "01J7BYKQGY75XN1HBVZ110D16P"
- say a `.tree` file uses the `include_static` template directive to include some file into itself.
now in addition to compiling the `.tree` file when it changes, I'd also need to recompile the `.tree` file when that `include`d`_static` file changes too---and that sort of dependency tracking is ripe for bugs as the codebase grows more complex!
% id = "01J7BYKQGYSYJD97AQ3ZJB7B8J"
- not with a well-built abstraction of course, but do I really want to invest my time into that abstraction, if my debug builds take a quarter of a second?
% id = "01J7BYKQGY75NF07X03VSK3XX1"
- I'll probably keep using this non-incremental build system, at least until my build times get unbearably long---I'd say 2 or 3 seconds would be testing my patience already.
I really don't like using slow software.
% id = "01J7BYKQGYMEQXWFVXETNZGJRB"
- ### Vanilla JS
% id = "01J7BYKQGYESGP0ZKSYVNTEKCF"
- TypeScript is cool, but it's yet another build step.
and with `tsc` it's a pretty slow one at that!
% id = "01J7BYKQGYWNPK11XFN30DFZ74"
- _and_ it's a dependency---one you have to install outside of `rustc` and `cargo`, which isn't great...
% id = "01J7BYKQGYAGD2V31HP66SHZ91"
- _and_ if you want to use something else like [`swc`](https://swc.rs/), you're out of luck unless you use npm. yuck.
% id = "01J7BYKQGY7EBD6MF2T3A07GCF"
- I'm sure there'll be a lot of people thinking "it's not _that_ slow," but consider me a fanatic who _despises_ long build times.
adding an additional 100ms of Node.js warmup to that would be like comitting a cardinal sin!
% id = "01J7BYKQGY8MCJA4DZ3904Q6ZT"
+ so I decided to go with Vanilla JS for the treehouse.
% id = "01J7BYKQGYZNWKA398QHHNC0M6"
- in fact, not just for the treehouse.
[the app I'm building currently][def:rkgk/repo] is also completely Vanilla JS, for the exact same reasons.
% id = "01J7BYKQGYZ0WC3YS5NMKCT3V7"
- it actually hasn't been that bad!
aside from type errors being a little harder to debug, and IDE support not being as great---I honestly do miss the latter...---you can program in Vanilla JS just fine.
% id = "01J7BYKQGY8X9SWKYFJ62GSBBG"
+ so I don't really get hystericising over not having TypeScript.
I've had worse experiences writing code.
the best part about plain vanilla JavaScript is that the iteration times are really quick, and you have stack traces, and a pretty darn good graphical debugger to help you out in trying times.
% id = "01J7BYKQGYY3B66TJDHYTQ1V2R"
- not a _replay_ debugger mind you... but it's still a pretty good debugger!
way snappier than most tools for debugging C++ on Windows. (I gotta try out RAD Debugger one day >--<)
% id = "01J7BYKQGYZB7DM185YM80633H"
- my experience writing rakugaki only confirms that TypeScript isn't really needed---it's a pretty sizable Web app, and aside from me wishing JavaScript had more strict error handling---which TypeScript _does not_ solve by the way, it's _really not that bad_.
% id = "01J7BYKQGYRT9125BTEJPEWBWB"
- [I've written about my feelings towards JavaScript before though][page:programming/languages/javascript], so I won't repeat myself too much here.
% id = "01J7BYKQGYSD6SEA5CEF64YB28"
- ### not minified
% id = "01J7BYKQGYZTR6JE316HXB6F4J"
- did you know: your browser has a handy DevTools panel. ain't that neat?
% id = "01J7BYKQGY3HHC5FFCTJ4XHJHB"
- too bad it's useless if you minify your code!
% id = "01J7BYKQGYD8VFKHGR8FP05MPS"
- remember: a lot of people can learn from reading someone else's source code, including you!
so try not minifying your source code.
% id = "01J7BYKQGYA94T9AR4FDSP3Y6Y"
- let compression do most of the heavy lifting, and liberate yourself with an improved debugging experience, as well as letting people see how the cool parts of your website are implemented.
% id = "01J7BYKQGYG1993HRJMDDBJ0W8"
- even if they're really janky!
% id = "01J7EMBKN42WH0BKXKDQ020F8M"
- you can also provide source maps to transfer optimal minified sources by default, and let your browser's DevTools display your original sources to the user upon their request.
% id = "01J7EMJYX91P3RE3PEBM2M3MR1"
- the important part is that having _some_ form of readable sources _right on your page_ is really nice!
% id = "01J7BYKQGYTBEQFJ1AK0SRP0ZG"
- ### release mode? what's that?
% id = "01J7BYKQGY1EFG6HSNJEJXKCWB"
- tying into the previous point, the treehouse builds mostly the same code, both in debug mode and release mode.
% id = "01J7BYKQGYAVY2JGSYR90M0FQZ"
- the reason is that I don't want to waste time testing my website separately on release mode, so having it behave differently in subtle ways means more bugs!
% id = "01J7BYKQGY6KJ6ZZX4EREEAB79"
- there are only two parts of the treehouse that behaves differently between debug and release mode.
% id = "01J7BYKQGYB4G0J7TZQCF4X7W6"
- generation speed, and...
% id = "01J7BYKQGYFM6668GH8W7RH1A1"
- ### live reloading
% id = "01J7BYKQGYS3KAXCAXW066M32K"
- in debug mode, I have the treehouse reload itself on change automatically.
% id = "01J7BYKQGYMVS2PYT5YNAEG8FR"
- I can't count the number of precious developer seconds this has saved me---not having to refocus my window to the browser is really nice!
% id = "01J7BYKQGY6AX2C8CR0BSDFR2E"
- I actually learned the Web ecosystem has such cool mechanisms for app development back when I was trying out React.js for a little project of mine.
I didn't end up building anything in the end because the project idea _just didn't end up vibing with me_, but it provided me with lots of useful knowledge.
especially related to how frickin' cool React is in terms of developer experience.
% id = "01J7BYKQGY16FEAPZ11DD6JYTM"
- here's the script I use for [rkgk][def:rkgk/repo], hosted under `static/live-reload.js`:
```javascript
// NOTE: The server never fulfills this request, it stalls forever.
// Once the connection is closed, we try to connect with the server until we establish a successful
// connection. Then we reload the page.
await fetch("/auto-reload/stall").catch(async () => {
while (true) {
try {
let response = await fetch("/auto-reload/back-up");
if (response.status == 200) {
window.location.reload();
break;
}
} catch (e) {
await new Promise((resolve) => setTimeout(resolve, 100));
}
}
});
```
% id = "01J7BYKQGYF8Y1E013XTNT5Q32"
- I then import it into any HTML page which I want to reload on change.
```html
<script type="module">
import "rkgk/live-reload.js";
</script>
```
% id = "01J7BYKQGYMWFP0YQK92MB1133"
- on the Rust side, these `/stall` and `/back-up` endpoints are implemented like so:
```rust
use std::time::Duration;
use axum::{routing::get, Router};
use tokio::time::sleep;
pub fn router<S>() -> Router<S> {
let router = Router::new().route("/back-up", get(back_up));
#[cfg(debug_assertions)]
let router = router.route("/stall", get(stall));
router.with_state(())
}
#[cfg(debug_assertions)]
async fn stall() -> String {
loop {
// Sleep for a day, I guess. Just to uphold the connection forever without really using any
// significant resources.
sleep(Duration::from_secs(60 * 60 * 24)).await;
}
}
async fn back_up() -> String {
"".into()
}
```
% id = "01J7BYKQGYDY8E84BEM5V7MMGB"
- the `/stall` route is only enabled in debug builds, because in release mode, rkgk uses a smarter exponential backoff system with some random noise added to the reload timeout, to prevent the server from DDoSing itself if it dies.
% id = "01J7C16RSZMAJ5TKZ1H2MMTCXK"
- or right after deploying, which also causes all existing WebSocket connections to close.
% id = "01J7BYKQGYKYMZA4HZEXV117G8"
- in the treehouse, I use [`tower-livereload`](https://lib.rs/crates/tower-livereload), but I wouldn't recommend it.
% id = "01J7BYKQGYC579CYCJH0WJN8MT"
- as you can see above, there's not a lot of client-side JavaScript, and it's not hard to write the HTTP routes either!
% id = "01J7BYKQGYVS4CZGVC4ZHGJ7KF"
- also, `tower-livereload` also has a couple disadvantages compared to the solution I've shown here.
% id = "01J7BYKQGY3P95RM10ZZGRP2Y3"
- it's slow, because it only polls the server's `/back-up` endpoint every second, which increases reload latency.
I've bumped that up to polling every 100ms in my script, because I don't like slow.
% id = "01J7BYKQGY8GVZQS6EP9D8WMNA"
- it [cats](https://en.wikipedia.org/wiki/Concatenation) the JavaScript payload directly to your server's `Content-Type: text/html` responses, which produces invalid HTML.
as far as I can tell, all major browsers seem to parse it correctly, but it's a pretty ugly hack nevertheless.
% id = "01J7BYKQGY8AX7298QPHR208BZ"
- it's 500 lines of [dependency](https://www.youtube.com/watch?v=PAAkCSZUG1c&t=568s) for something you can do with 12 lines of JavaScript, 3 lines of HTML, and 28 lines of Rust!
% id = "01J7BYKQGY1VBDW7TDVV47XK2T"
- to trigger the reloads, I use [`cargo-watch`](https://lib.rs/crates/cargo-watch), mostly because it's really convenient.
```
cargo watch -x run
```
and you're done!
your site will now reload when you [`:w`](https://vim.rtorr.com/).
% id = "01J7BYKQGYX9B0RYN4KWSMEJV6"
- ### Web Component shenanigans
% id = "01J7BYKQGYNDQSZ7NW4MJBH6WQ"
- as the treehouse uses Vanilla JS, I needed some solution for building reusable components that wasn't React.
luckily for me, I already knew about [Web Components](https://developer.mozilla.org/en-US/docs/Web/API/Web_components)---in particular, [custom elements](https://developer.mozilla.org/en-US/docs/Web/API/Web_components/Using_custom_elements).
% id = "01J7BYKQGYK91F1Z45YJGGNX8Q"
- if you're using them for your own website, I'd recommend skipping shadow DOM, because it's not really useful in case you have control over all styles.
it's good to know what it does in case you ever need it, but it shouldn't be your go-to tool for building components.
% id = "01J7BYKQGYKBNJYZB1ASBJRGD3"
- _custom elements_ are just that---custom HTML elements that you can include in your page.
according to [the MDN docs](https://developer.mozilla.org/en-US/docs/Web/API/Web_components/Using_custom_elements), these have two flavours:
% id = "01J7BYKQGYWEXYDGRFT0P8RMH6"
- _autonomous custom elements_, which always extend `HTMLElement` in JavaScript.
these can be used to implement standalone components composed out of smaller pieces.
these are used like any HTML element---`<your-element></your-element>`.
% id = "01J7BYKQGYM9HD00VY2BVEZPZ0"
- _customized built-in elements_, which extend _any_ built-in element.
these are applied using the `is=""` attribute on a base built-in element, like `<li is="your-element"></li>`.
% id = "01J7BYKQGY90D6C0KF9CFSMF1A"
- unfortunately customized built-in elements are practically useless, because Safari doesn't implement them, [and doesn't even plan to do so](https://github.com/WICG/webcomponents/issues/509#issuecomment-222860736)...
but that doesn't diminish the usefulness of autonomous custom elements either way!
% id = "01J7BYKQGYND1AMZ6PG3M67BN2"
- to implement a custom element, I usually use this pattern:
```javascript
class YourElement extends HTMLElement {
// You can use the constructor of a custom element to require some
// parameters from other JS code.
// Note that adding *required* parameters here makes your element
// practically unusable from HTML, because there's no way to pass them in!
constructor() {
super();
}
connectedCallback() {
// Read attributes and add children here.
}
}
// Choose a different prefix; `owo` is just an example to get you going.
// You *must* choose a prefix, because custom elements require at least one dash `-` in their names.
customElements.define("owo-your-element", YourElement);
```
and off we go!
% id = "01J7BYKQGY6YE329DB9PMY8KNW"
- I've found a couple useful idioms for working with custom elements.
% id = "01J7BYKQGYDG6XYVK8ZFAM4KBJ"
- styling: custom elements start off with `display: inline;`, which is probably not what you want.
therefore, you'll usually want to replace that with `display: block;`.
```css
owo-custom-element {
display: block;
}
```
% id = "01J7BYKQGY65233M8NCPBM0N63"
- DOM construction: a useful idiom is passing `createElement` to `appendChild`.
this allows you to append a new element to the component (or really anything, it's a useful idiom overall).
I usually follow it up with adding a CSS class for easy styling, naming the CSS class after the object field name on the JavaScript side.
```javascript
this.textArea = this.appendChild(document.createElement("textarea"));
this.textArea.classList.add("text-area");
```
it's kind of verbose, but if you don't like it, you're free to wrap that up in a helper function---I personally don't mind, since it's simple code that I usually follow this up with more initialization logic.
% id = "01J7BYKQGYT5RPTQVZ0S9G18AM"
- patching everything together: I usually use plain DOM `Event`s for event handlers that don't need to return any data back to the component.
I prefix their names with `.` to not confuse them with built-in DOM events such as `mousedown`.
it's pretty convenient to construct events with `Object.assign`, too.
```javascript
this.dispatchEvent(
Object.assign(
new Event(".codeChanged"),
{ newCode },
),
);
```
you can wrap the event construction in a function too if you mind the verbosity much, but again---I personally don't.
% id = "01J7BYKQGYYPXJVESTMGAA0TB8"
- ### cache bust a little, cache bust some more
% id = "01J7BYKQGYBRSHQT8HJ3KNY9NA"
- _cache busting_ is a super cool technique for ensuring the browser does not download assets that haven't changed.
essentially, for each asset you serve to the user, you compute a hash that's then included in all URLs referencing the asset from your website.
% id = "01J7BYKQGYR6A6N3P1EG4383RN"
- for example, as of writing this, treehouse's CSS stylesheets are linked into the main page more or less like so:
```html
<link rel="stylesheet" href="/static/css/main.css?cache=b3-f12e225c">
<link rel="stylesheet" href="/static/css/tree.css?cache=b3-62885cff">
```
% id = "01J7BYKQGY3070025QXPDCF4N6"
- my server sees the `cache` query parameter, and adds in a little HTTP header, that basically tells the client "don't redownload this, that'd be stupid. this asset isn't going to change like, ever."
```http
Cache-Control: public, max-age=31536000, immutable
```
% id = "01J7BYKQGYYBY9RDFJG8BP61QP"
- and that's it! the actual value of the `?cache` parameter is never interpreted by anyone, anyhow.
it's only there so that whenever something _does_ change, we change the URL, and the browser thinks that "hey, that's a different asset! gotta download it."
that way, the browser only ever downloads files that changed since your last visit.
% id = "01J7BYKQGYZ6MS7CKVDFQDVMQ8"
- initially I implemented cache busting for most static assets, because that's pretty easy to do: add a helper to your templating engine that can derive these `?cache`-augmented URLs by computing a hash of the linked file.
% id = "01J7BYKQGY4VC1JRT671YJ1509"
- in my case I use [BLAKE3](https://en.wikipedia.org/wiki/BLAKE3)---as indicated by the `b3-` prefix---but the choice of hash function shouldn't matter that much; I just chose a fast crypto hash for lower likelihood of collisions.
which would of course cause assets _not_ to get redownloaded, if that ever happened.
% id = "01J7BYKQGYE3AKGWS8RTZKAJPE"
- which is kind of bad, but eh.
happens rarely enough we don't need to care about it.
% id = "01J7C16RSZKDBC3427V4BAV7VB"
- (and yes, I know that I'm increasing the likelihood of collisions by truncating the hash.
as I said: it doesn't matter, I don't care.)
% id = "01J7BYKQGY5ASNC96MCHF1TRB8"
- the far bigger challenge was making this work for JavaScript files.
% id = "01J7BYKQGYHDT3AZ78WA5WYGJC"
- HTML we generate, so that's easy---add a template helper, and replace all occurrences of `/static` URLs with that helper.
% id = "01J7BYKQGYRWTWQZX0B86QMSH6"
- CSS doesn't refer to too many assets---there's fonts and a couple images in [that one blog post's stylesheet][page:programming/blog/tairu], which will probably never change, so we can hardcode those.
% id = "01J7BYKQGYTDQEEH58DDGCVG8M"
- but JavaScript. man.
where do I even begin.
% id = "01J7BYKQGYSPVQK1AM2XKV39QC"
- treehouse is built on ES modules.
as I mentioned before, I don't bundle or minify anything, because HTTP/2 makes using plain ES modules quite efficient, as long as you import them all from your main HTML file.
the problem is that if you're referring to modules like this...
```javascript
import "treehouse/vendor/codejar.js";
```
how the heck are you going to add that `?cache` parameter in there?
% id = "01J7BYKQGYMCBHPH645GMQCKWJ"
- as you can see in the previous example, the treehouse had already used [import maps](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/script/type/importmap) by that point.
for those of you who don't know, these are handy little bits of JSON that tell the browser's JavaScript runtime where to source your modules from.
% id = "01J7C16RSZS6GK1JPHFEV2MZXH"
- before implementing cache busting, I'd use a simple import map hardcoded right into my Handlebars template:
```html
<script type="importmap">{
"imports": {
"treehouse/": "{{ config.site }}/static/js/"
}
}</script>
```
% id = "01J7BYKQGYJDRF3FXHE23ADK21"
- so the challenge was to turn _that_ puny import map into something that lists all the individual modules, with a `?cache` parameter!
and initially I thought, "well this sure is gonna be simple, just walk through all my `.js` files, compute those hashful URLs,"
% id = "01J7C16RSZ7NHE5N3KV57NGWT0"
- okay cool we're getting somewhere,
% id = "01J7C16RSZQPMABF0SBDH1RHA7"
- "and then we could even cache that import map with a `?cache` parameter too---"
% id = "01J7BYKQGYBB81J2XGXPG4WQS5"
- and then [reality struck](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/script/type/importmap#syntax):
> The `src`, [...and other] attributes must not be specified.
Fuck.
% id = "01J7BYKQGYSB23S0G2G2M09YNK"
- so, "sure," I say.
"I'll have to include the whole import map verbatim in each `.html` file.
no big deal, we don't cache `.html`s anyways..."
% id = "01J7BYKQGYPXKQFGYHQRG17TFF"
+ it's kind of sad, because it'd allow me to cache linked branches (such as [this one][branch:about])---I'd love it if I could get rid of the `Loading...` text entirely if you've ever loaded a branch, but while that _is_ feasible, it's probably going to benefit snappiness less than I'd like, due to import maps influencing the hash of each `.html` file.
and therefore each time I add a `.js` file, all cached HTML files would get busted...
oh well.
% id = "01J7C16RSZ1KRZ63HXGN3G3HF3"
- on the other hand, I do understand why browser vendors wouldn't want to implement it---it's a performance pitfall.
it adds in an additional dependency towards evaluating `<script>`s, which would block parsing on any inline JavaScript that's `type="module"`.
% id = "01J7BYKQGYWKRYZ7A5V8PGBZNM"
- and with an import map implemented, I go look at my glorious generated sources, and see... that my import map keys change every build :ralsei_dead:
% id = "01J7BYKQGYRKG8MDAKD8T2DZHF"
- this is a really stupid thing, but Rust (and other languages) randomise the ordering of hash maps to prevent [hash DoS attacks](https://en.wikipedia.org/wiki/Collision_attack#Hash_flooding), which means you can't use them to generate deterministic data. such as a file that shouldn't change across rebuilds!
% id = "01J7BYKQGY83JVHZNAK14CWTK9"
+ so I swapped the [`std::collections::HashMap`](https://doc.rust-lang.org/std/collections/struct.HashMap.html) with a [`indexmap::IndexMap`](https://docs.rs/indexmap/latest/indexmap/map/struct.IndexMap.html), sorted it after generation, and everything's working smoothly :sunglasses:
% id = "01J7C16RSZCK3RMZNEWK6EQQVW"
- in theory, I could've used a `Vec<(String, String)>`, but `serde` won't serialize that as a map by default (for good reasons. it's not a map after all, it's a sequence!) and I was too lazy to implement that serialization logic myself.
% id = "01J7BYKQGYJ7QJ7T6NFFFE11ZY"
- ...and all that's possible without ever parsing any JavaScript!
% id = "01J7BYKQGY42DB1C4YQ1Q0WRR8"
- ### Djot down some notes
% id = "01J7BYKQGYH4C6SYF1WCE3EPGK"
- I'd initially chosen Markdown as my website's markup language, simply because I was already familiar with it, and because I've seen the Rust ecosystem had a [nice parser for it that seemed pretty customizable](https://lib.rs/crates/pulldown-cmark).
% id = "01J7BYKQGYMMCAKQX9S8JCTJW6"
- as time went on though, I discovered another light markup language: [_Djot_](https://djot.net/), made by the same person who made Markdown, with _lots_ of lessons learned from his previous attempt.
% id = "01J7BYKQGYQQP0YSWA1TEHRB9S"
- I initially didn't wanna go through with it, because "_sigh_ am I really gonna have to rewrite my entire content to use Djot?"
% id = "01J7BYKQGYSKJNTB7BGKTED4S0"
+ but then I did it anyways, because life's too short to have to deal with poorly designed markup languages :hat_smug:
% id = "01J7C16RSZV1A8J3TA1JZJV5J4"
- and also because I'm are have stupid, but let's not talk about that. :shhh:
% id = "01J7BYKQGYHNH61BN1FGQRRQHV"
- the one thing that sold me on Djot was how easy it is to create custom HTML elements.
for instance, it has syntax for a div:
```djot
::: class-goes-here
I'm in a div!
:::
```
or a span:
```djot
[I'm in a span!]{.whatever}
```
which is really cool if you're doing [a lot of bespoke markup in your blog posts](https://ciechanow.ski/).
% id = "01J7BYKQGY9DDCDWW6D59EW3BG"
- ultimately, the switch mostly came down to converting `*abc*` into `_abc_`, `**abc**` into `*abc*`, and `~~abc~~` into `{~abc~}`, as well as fencing any inline HTML off [with some `=html`](https://github.com/jgm/djot/blob/56ca538cedc94bc636763bce954847407fe40eb7/doc/quickstart-for-markdown-users.md#raw-html), as well as fixing up some links---because Djot forces you to use two pairs of `[]`, like `[here's a link that's defined elsewhere][]`, instead of just one like Markdown.
% id = "01J7BYKQGY4BE6Z8EHNPDHD1XS"
+ you get the gist---pretty mechanical actions, that probably _could_ have been automated away, but I decided it wasn't worth it for what little content I had here.
% id = "01J7C16RSZQVXV5RCKJ3A3Q8GG"
- says that with 50000 words on his website already, and counting...
% id = "01J7BYKQGY08SDWD0J7K6MMPMV"
- and in the end, it's interesting the switch made parsing slightly faster, and the HTML generation code slightly cleaner!
% id = "01J7BYKQGY02WPNH9RGS29TE27"
+ I believe I can attribute the parsing speedups to Djot's more computer-friendly syntax, but I haven't measured.
just my guess.
maybe it's my HTML generator doing less useless error handling work, too.
% id = "01J7C16RSZAECM1G9CD57WX1M6"
- I am pushing into a string, that literally cannot fail! (other than with an OOM, but that's a panic)
% id = "01J7BYKQGYJMRC7DY7SNMAQ538"
- the HTML generation code got cleaner, because the crate I'm using---[`jotdown`](https://lib.rs/crates/jotdown)---does not use a callback for filling in broken links.
a pattern that's best known under the moniker "yeah, don't do that" in the Rust world.
% id = "01J7BYKQGYEYE7VAYYQBJF18ZX"
+ I found the easiest way of going about writing your HTML generator is copying the built-in one in your light markup parsing library of choice, and adjusting it to your needs.
so yeah.
mine's mostly stolen code.
% id = "01J7BYKQGYGZTN14SHQA3B3ZH6"
- aside, but there's one frustrating thing about the Rust ecosystem: why does everything have to be a trait?
I don't think I've declared a trait _once_ in my recent projects---both in the treehouse and rkgk, yet I constantly see examples of unnecessary traits [such as this one](https://docs.rs/jotdown/latest/jotdown/trait.Render.html) in the wild.
% id = "01J7BYKQGYY60YHG7VAFX6WRR4"
- and _of course_ `Render::push` has to take _anything_ that implements `Write`, because handling I/O errors is _oh_ so nice and efficient to do, especially when you have to do it every 5 characters you emit!
% id = "01J7BYKQGYJEY2VMEF6VP2WAHZ"
- why can't it take in a `&mut String` instead?
% id = "01J7BYKQGY75E13YC9D7J786HZ"
- or... why does this trait have to exist in first place? _how often_ will you want to swap out renderer implementations, really?
% id = "01J7BYKQGYDFTJPK4K2JANTMCR"
- and when you swap out renderer implementations, isn't the API gonna be quite different, because both renderers require different data to derive your HTML from?
shouldn't that warrant you calling a different function with a different set of arguments?
% id = "01J7BYKQGYA5TG1HS92T4P8NN7"
- _please_ don't take this as me bashing `jotdown` or its author; I think it's an otherwise well-designed and generally great library for parsing Djot, and a single odd API decision shouldn't detract you from just how awesome it is.
% id = "01J7BYKQGYQVS2SWW06SB970BT"
- ### a `rustc` stability benchmark
% id = "01J7BYKQGYSNDM9RYYB4GP88EZ"
- this is a weird one, but: sometimes `rustc` will choke up on evaluating obligations for the implementation of `Unpin` for [`SemaBranch`](https://github.com/liquidev/treehouse/blob/4bbbd65bd0a30852f06646276ae6f187cc7304fb/crates/treehouse/src/tree.rs#L143), and just... die.
% id = "01J7BYKQGYV3H2WX6VVSR9EFEF"
- what's funny is that it dies dropping a `rustc-ice-YYYY-MM-DDTHH_MM_SS-NNNNN.txt` file into your current working directory, and combined with `cargo-watch` this has the super funny effect of generating tens of those files in the treehouse repo.
% id = "01J7C16RT0H84KPGJMBK1QT84E"
- I have forgotten to remove these before checking in at least once before.
I don't think I ever ended up pushing that commit though, so I can't show you... [but you can see the ICE I have stored in my local Git history here](/static/text/rustc-ice-2024-07-20T21_00_23-69819.txt).
% id = "01J7C16RT0R80J7KN516B06Q05"
- an annoying side effect is that to fix this, I have to `^C` out of `cargo-watch`, run `cargo clean`, run `cargo-watch` again, and wait until the whole project compiles.
% id = "01J7C16RT0XX4PZYRXAAV2M7P9"
- at least it's a debug build...
% id = "01J7BYKQGYAHS3T5GDRR4KC8QB"
- I unfortunately don't have a consistent repro on this, though `rustc` _has_ told me this is a known issue.
it's weird it only happens with the treehouse.
like the[re's a ghost inha][page:kuroneko]{.secret}biting it...
% id = "01J7BYKQGY9XC7C20SA2BWDAE8"
- and that's all the stories I have for now!
feel free to come back here any time.
I may or may not update this post with more of them in the future.

File diff suppressed because it is too large Load diff

View file

@ -1,7 +1,7 @@
%% title = "JavaScript is not as bad as people make it out to be"
scripts = [
"components/literate-programming.js",
"vendor/codejar.js",
"treehouse/components/literate-programming.js",
"treehouse/vendor/codejar.js",
]
% id = "01J291S06DS12DCFTNKJ27BNSQ"

View file

@ -0,0 +1,19 @@
%% title = "short thoughts"
% id = "01J4J6400Q4MWF2QPGX3P9Q2WQ"
- if there's one thing I learned from Go...
```rust
let mut list = WalkList::new(args);
let condition = list.expect_arg(c, src, "missing `if` condition");
let if_true = list.expect_arg(c, src, "missing `if` true branch");
let if_false = list.expect_arg(c, src, "missing `if` false branch");
list.expect_nil(c, src, "extra arguments after `if` false branch");
if !list.ok {
return Ok(());
}
```
...it's that `?` is not the only option.

View file

@ -13,6 +13,18 @@
% id = "issue-list"
- ## issue list
% id = "01J093FGZF8M0Q02YX3VFSZ06X"
+ :TODO: :l_feat: RSS/Atom feed
% id = "01J094P8H3VZKESVHKVJRVXTG1"
- there's no real way to subscribe to updates from the treehouse yet. (talking about [page:treehouse/new] here)
% id = "01J094P8H3Q59F5CAE8CK6DWYY"
- arguably this might be a bit hard, since it seems like feed readers are meant to let you read the whole post inline - which uh, treehouse, uhhhh
% id = "01J094P8H37P5F1MP14758ZNQH"
- its structure makes this kind of hard
% id = "01J3A23S4RVHZR3BE7EFQ8ZPPB"
- :TODO: :l_perf: caching the import map
@ -109,18 +121,6 @@
- maybe using IndexDB to save branch content and fetch it quickly would help too.
but I don't like that as much as just using what the browser offers.
% id = "01J093FGZF8M0Q02YX3VFSZ06X"
+ :TODO: :l_feat: RSS/Atom feed
% id = "01J094P8H3VZKESVHKVJRVXTG1"
- there's no real way to subscribe to updates from the treehouse yet. (talking about [page:treehouse/new] here)
% id = "01J094P8H3Q59F5CAE8CK6DWYY"
- arguably this might be a bit hard, since it seems like feed readers are meant to let you read the whole post inline - which uh, treehouse, uhhhh
% id = "01J094P8H37P5F1MP14758ZNQH"
- its structure makes this kind of hard
% id = "01J093FGZF3M6HR6E0X3P44D0M"
+ :TODO: :l_a11y: respect `@media (prefers-reduced-motion: reduce)`

View file

@ -3,12 +3,36 @@
styles = ["new.css"]
feed = "news"
% id = "01HQ6G30PTVT5H0Z04VVRHEZQF"
- [featured]{.badge .blue} ever wondered how Terraria renders its worlds? or how editors like Tiled manage to make painting tiles so easy?
% id = "01J4J5N6WZQ03VTB3TZ51J7QZK"
- [featured]{.badge .blue} I was bored over a weekend, so I decided to write the tiniest programming language I could imagine.
it came out looking pretty Lispy, and I'm glad about that!
I learned a ton about Lisps in the process of researching it.
even though it didn't end up having macros...
### tairu - an interactive exploration of 2D autotiling techniques
### haku - writing a little programming language for fun
[read][page:programming/blog/tairu]
[read][page:programming/blog/haku]
% id = "01J7C1KBZ58BR21AVFA1PMWV68"
- I like lean websites. did you know that?
I also really like lean websites that are simple in construction and deployment. did you know the treehouse is a website like that?
so I've decided to write up a few anecdotes and other cool stories about the treehouse's inner workings.
also, it's (way past) its one year anniversary! hooray!
### not quite buildless
[read][page:programming/blog/buildsome]
% id = "01J73BSWA15KHTQ21T0S14NZW0"
- I decided to write up some ideas on what sort of cool data analysis I could do on my [ListenBrainz data set][def:social/listenbrainz].
I haven't done any of it yet, but I thought it'd be cool to share my ideas anyways!
### the ListenBrainz data set
[read][page:music/brainz]
% id = "01J293BFEBT15W0Z3XF1HEFGZT"
- sometimes people call me crazy for saying that bashing JavaScript is senseless and that it's not as bad of a language as people make it out to be.
@ -83,3 +107,10 @@ seriously though. I don't like them.
### liquidex's treehouse: design
[read: _on digital textures_][page:design/digital-textures] [go to branch][page:design]
% id = "01HQ6G30PTVT5H0Z04VVRHEZQF"
- ever wondered how Terraria renders its worlds? or how editors like Tiled manage to make painting tiles so easy?
### tairu - an interactive exploration of 2D autotiling techniques
[read][page:programming/blog/tairu]

View file

@ -174,6 +174,12 @@ async fn sandbox(State(state): State<Arc<Server>>) -> Response {
.extensions_mut()
.insert(live_reload::DisableLiveReload);
}
// Debounce requests a bit. There's a tendency to have very many sandboxes on a page, and
// loading this page as many times as there are sandboxes doesn't seem like the best way to do
// things.
response
.headers_mut()
.insert(CACHE_CONTROL, HeaderValue::from_static("max-age=10"));
response
}

View file

@ -10,9 +10,7 @@ lexer.init = (input) => {
export const eof = "end of file";
lexer.current = (state) => {
return state.position < state.input.length
? state.input.charAt(state.position)
: eof;
return state.position < state.input.length ? state.input.charAt(state.position) : eof;
};
lexer.advance = (state) => ++state.position;
@ -31,10 +29,7 @@ lexer.skipWhitespaceAndComments = (state) => {
continue;
}
if (c == ";") {
while (
lexer.current(state) != "\n" &&
lexer.current(state) != eof
) {
while (lexer.current(state) != "\n" && lexer.current(state) != eof) {
lexer.advance(state);
}
lexer.advance(state); // skip over newline, too
@ -45,13 +40,29 @@ lexer.skipWhitespaceAndComments = (state) => {
}
};
lexer.string = (state) => {
lexer.advance(state); // skip over initial quotation mark
while (lexer.current(state) != '"') {
if (lexer.current(state) == eof) {
return "error";
}
lexer.advance(state);
}
lexer.advance(state); // skip over closing quotation mark
return "string";
};
export const isDigit = (c) => c >= "0" && c <= "9";
export const isIdentifier = (c) =>
/^[a-zA-Z0-9+~!@$%^&*=<>+?/.,:\\|-]$/.test(c);
export const isIdentifier = (c) => /^[a-zA-Z0-9+~!@$%^&*=<>+?/.,:\\|-]$/.test(c);
lexer.nextToken = (state) => {
let c = lexer.current(state);
if (c == '"') {
return lexer.string(state);
}
if (isDigit(c)) {
lexer.advanceWhile(state, isDigit);
return "integer";
@ -151,7 +162,19 @@ parser.parseList = (state, leftParen) => {
};
};
parser.parseRoot = parser.parseExpr;
parser.parseToplevel = (state) => {
let children = [];
while (parser.current(state).kind != eof) {
children.push(parser.parseExpr(state));
}
return {
kind: "toplevel",
children,
// Don't bother with start..end for now.
};
};
parser.parseRoot = (state) => parser.parseToplevel(state);
export function parse(input) {
let state = parser.init(input);
@ -184,3 +207,15 @@ export function exprToString(expr, input) {
return `<error ${expr.start}..${expr.end} '${inputSubstring}': ${expr.error}>`;
}
}
export function insertSources(node, input) {
if (node.start != null) {
node.source = input.substring(node.start, node.end);
}
if (node.children != null) {
for (let child of node.children) {
insertSources(child, input);
}
}
}

View file

@ -1,33 +1,65 @@
export const treewalk = {};
export const builtins = {};
treewalk.init = (input) => {
return { input };
treewalk.init = (env, input) => {
return {
input,
scopes: [new Map(Object.entries(builtins)), env],
env,
};
};
treewalk.lookupVariable = (state, name) => {
for (let i = state.scopes.length; i-- > 0; ) {
let scope = state.scopes[i];
if (scope.has(name)) {
return scope.get(name);
}
}
console.log(new Error().stack);
throw new Error(`variable ${name} is undefined`);
};
treewalk.eval = (state, node) => {
switch (node.kind) {
case "integer":
let sourceString = state.input.substring(node.start, node.end);
return parseInt(sourceString);
return parseInt(node.source);
case "string":
// NOTE: We chop the quotes off of the string literal here.
return node.source.substring(1, node.source.length - 1);
case "identifier":
return treewalk.lookupVariable(state, node.source);
case "list":
let functionToCall = node.children[0];
let builtin = builtins[state.input.substring(functionToCall.start, functionToCall.end)];
return builtin(state, node);
let functionToCall = treewalk.eval(state, node.children[0]);
return functionToCall(state, node);
case "toplevel":
let result = undefined;
for (let i = 0; i < node.children.length; ++i) {
result = treewalk.eval(state, node.children[i]);
if (result !== undefined && i != node.children.length - 1)
throw new Error(`expression ${i + 1} had a result despite not being the last`);
}
return result;
default:
throw new Error(`unhandled node kind: ${node.kind}`);
}
};
export function run(input, node) {
let state = treewalk.init(input);
export function run(env, input, node) {
let state = treewalk.init(env, input);
return treewalk.eval(state, node);
}
function arithmeticBuiltin(op) {
return (state, node) => {
if (node.children.length < 3)
throw new Error("arithmetic operations require at least two arguments");
let result = treewalk.eval(state, node.children[1]);
for (let i = 2; i < node.children.length; ++i) {
result = op(result, treewalk.eval(state, node.children[i]));
@ -36,7 +68,137 @@ function arithmeticBuiltin(op) {
};
}
function comparisonBuiltin(op) {
return (state, node) => {
if (node.children.length != 3)
throw new Error("comparison operators require exactly two arguments");
let a = treewalk.eval(state, node.children[1]);
let b = treewalk.eval(state, node.children[2]);
return op(a, b) ? 1 : 0;
};
}
builtins["+"] = arithmeticBuiltin((a, b) => a + b);
builtins["-"] = arithmeticBuiltin((a, b) => a - b);
builtins["*"] = arithmeticBuiltin((a, b) => a * b);
builtins["/"] = arithmeticBuiltin((a, b) => a / b);
builtins["="] = comparisonBuiltin((a, b) => a === b);
builtins["<"] = comparisonBuiltin((a, b) => a < b);
export function makeFunction(state, paramNames, bodyExpr) {
let capturedScopes = [];
// Start from 1 to skip builtins, which are always present anyways.
for (let i = 1; i < state.scopes.length; ++i) {
// We don't really mutate the scopes after pushing them onto the stack, so keeping
// references to them is okay.
capturedScopes.push(state.scopes[i]);
}
return (state, node) => {
if (node.children.length != paramNames.length + 1)
throw new Error(
`incorrect number of arguments: expected ${paramNames.length}, but got ${node.children.length - 1}`,
);
let scope = new Map();
for (let i = 0; i < paramNames.length; ++i) {
scope.set(paramNames[i], treewalk.eval(state, node.children[i + 1]));
}
state.scopes.push(...capturedScopes);
state.scopes.push(scope);
let result = treewalk.eval(state, bodyExpr);
state.scopes.pop();
return result;
};
}
builtins.fn = (state, node) => {
if (node.children.length != 3)
throw new Error("an `fn` must have an argument list and a result expression");
let params = node.children[1];
if (node.children[1].kind != "list")
throw new Error("expected parameter list as second argument to `fn`");
let paramNames = [];
for (let param of params.children) {
if (param.kind != "identifier") {
throw new Error("`fn` parameters must be identifiers");
}
paramNames.push(param.source);
}
let expr = node.children[2];
return makeFunction(state, paramNames, expr);
};
builtins["if"] = (state, node) => {
if (node.children.length != 4)
throw new Error("an `if` must have a condition, true expression, and false expression");
let condition = treewalk.eval(state, node.children[1]);
if (condition !== 0) {
return treewalk.eval(state, node.children[2]);
} else {
return treewalk.eval(state, node.children[3]);
}
};
builtins.def = (state, node) => {
if (node.children.length != 3)
throw new Error(
"a `def` expects the name of the variable to assign, and the value to assign to the variable",
);
if (node.children[1].kind != "identifier")
throw new Error("variable name must be an identifier");
let name = node.children[1];
let value = treewalk.eval(state, node.children[2]);
state.env.set(name.source, value);
};
export function wrapJavaScriptFunctionVarargs(func) {
return (state, node) => {
let args = Array(node.children.length - 1);
for (let i = 1; i < node.children.length; ++i) {
args[i - 1] = treewalk.eval(state, node.children[i]);
}
return func(...args);
};
}
export function wrapJavaScriptFunction(func) {
let inner = wrapJavaScriptFunctionVarargs(func);
return (state, node) => {
if (node.children.length != func.length + 1)
throw new Error(
`\`${func.name}\` expects ${func.length} arguments, but ${node.children.length - 1} were given`,
);
return inner(state, node);
};
}
builtins.cat = wrapJavaScriptFunctionVarargs((...strings) => {
return strings.join("");
});
builtins.sub = wrapJavaScriptFunctionVarargs((str, start, end) => {
if (typeof str != "string") throw new Error("`sub` expects a string as the first argument");
if (typeof start != "number") throw new Error("`sub` expects a number as the second argument");
end ??= start + 1;
return str.substring(start, end);
});
builtins.len = wrapJavaScriptFunction((string) => string.length);
builtins.chr = wrapJavaScriptFunction((n) => String.fromCodePoint(n));
builtins.ord = wrapJavaScriptFunction((s) => s.codePointAt(0));
builtins["to-string"] = wrapJavaScriptFunction((n) => n.toString());
builtins["to-number"] = wrapJavaScriptFunction((s) => parseInt(s));

View file

@ -2,6 +2,13 @@ let outputIndex = 0;
export const jsConsole = console;
const loggingEnabled = false;
function log(...message) {
if (loggingEnabled) {
jsConsole.log("[eval]", ...message);
}
}
// Overwrite globalThis.console with domConsole to redirect output to the DOM console.
// To always output to the JavaScript console regardless, use jsConsole.
export const domConsole = {
@ -17,23 +24,21 @@ export const domConsole = {
},
};
let kernel = {
init() {
return {};
},
async evalModule(_state, source, language, _params) {
if (language == "javascript") {
let blobUrl = URL.createObjectURL(new Blob([source], { type: "text/javascript" }));
let module = await import(blobUrl);
for (let exportedKey in module) {
globalThis[exportedKey] = module[exportedKey];
}
return true;
} else {
return false;
export async function defaultEvalModule(_state, source, language, _params) {
if (language == "javascript") {
let blobUrl = URL.createObjectURL(new Blob([source], { type: "text/javascript" }));
let module = await import(blobUrl);
for (let exportedKey in module) {
globalThis[exportedKey] = module[exportedKey];
}
},
return _state;
} else {
return null;
}
}
let kernel = {
evalModule: defaultEvalModule,
};
export function getKernel() {
@ -52,11 +57,11 @@ export async function evaluate(commands, { error, newOutput }) {
signalEvaluationComplete = resolve;
});
let kernelState = kernel.init();
outputIndex = 0;
try {
let kernelState = {};
for (let command of commands) {
log(`frame ${treehouseSandboxInternals.outputIndex} module`, command);
if (command.kind == "module") {
await kernel.evalModule(
kernelState,
@ -71,15 +76,19 @@ export async function evaluate(commands, { error, newOutput }) {
++outputIndex;
}
}
log(`frame ${treehouseSandboxInternals.outputIndex} evalComplete`);
postMessage({
kind: "evalComplete",
});
} catch (err) {
log(`frame ${treehouseSandboxInternals.outputIndex} error`, err);
postMessage({
kind: "output",
output: {
kind: "error",
message: [err.toString()],
message: [
err.stack.length > 0 ? err.toString() + "\n\n" + err.stack : err.toString(),
],
},
outputIndex,
});

View file

@ -53,7 +53,13 @@ function tokenize(text, syntax) {
text.substring(start, end),
);
}
lastMatchEnd = end;
}
pushToken(
tokens,
pattern.is.default,
text.substring(lastMatchEnd, match.indices[0][1]),
);
} else {
pushToken(tokens, pattern.is, match[0]);
}

View file

@ -1,6 +1,7 @@
{
"patterns": [
{ "regex": ";.*", "is": "comment" },
{ "regex": "\"[^\"]*\"", "is": "string" },
{ "regex": "[0-9]+", "is": "literal" },
{
"regex": "\\((fn)\\s*\\(.*?\\)",

View file

@ -0,0 +1,70 @@
thread 'rustc' panicked at /rustc/ed7e35f3494045fa1194be29085fa73e2d6dab40/compiler/rustc_query_system/src/query/plumbing.rs:726:9:
Found unstable fingerprints for evaluate_obligation(e338efe858759e13-82eebbd52b683deb): Ok(EvaluatedToOkModuloRegions)
stack backtrace:
0: 0x75acdc388665 - std::backtrace_rs::backtrace::libunwind::trace::hca492ccc0a8ac1c7
at /rustc/ed7e35f3494045fa1194be29085fa73e2d6dab40/library/std/src/../../backtrace/src/backtrace/libunwind.rs:116:5
1: 0x75acdc388665 - std::backtrace_rs::backtrace::trace_unsynchronized::hcfe2514ba01c8e76
at /rustc/ed7e35f3494045fa1194be29085fa73e2d6dab40/library/std/src/../../backtrace/src/backtrace/mod.rs:66:5
2: 0x75acdc388665 - std::backtrace::Backtrace::create::he544b6c05cd93b06
at /rustc/ed7e35f3494045fa1194be29085fa73e2d6dab40/library/std/src/backtrace.rs:331:13
3: 0x75acdc3885b5 - std::backtrace::Backtrace::force_capture::h1343d66c0dc4355f
at /rustc/ed7e35f3494045fa1194be29085fa73e2d6dab40/library/std/src/backtrace.rs:312:9
4: 0x75acd8813731 - std[4da4969639bb92c1]::panicking::update_hook::<alloc[1bc3c981dff8b25f]::boxed::Box<rustc_driver_impl[a6166d3ac53fd75e]::install_ice_hook::{closure#0}>>::{closure#0}
5: 0x75acdc3a368f - <alloc::boxed::Box<F,A> as core::ops::function::Fn<Args>>::call::h6f2232ec5d59aa96
at /rustc/ed7e35f3494045fa1194be29085fa73e2d6dab40/library/alloc/src/boxed.rs:2078:9
6: 0x75acdc3a368f - std::panicking::rust_panic_with_hook::h33a68c34dc0a2db3
at /rustc/ed7e35f3494045fa1194be29085fa73e2d6dab40/library/std/src/panicking.rs:804:13
7: 0x75acdc3a32b7 - std::panicking::begin_panic_handler::{{closure}}::h3bb784f281e80598
at /rustc/ed7e35f3494045fa1194be29085fa73e2d6dab40/library/std/src/panicking.rs:670:13
8: 0x75acdc3a0b09 - std::sys::backtrace::__rust_end_short_backtrace::h35cbe923cc3ff143
at /rustc/ed7e35f3494045fa1194be29085fa73e2d6dab40/library/std/src/sys/backtrace.rs:171:18
9: 0x75acdc3a2f44 - rust_begin_unwind
at /rustc/ed7e35f3494045fa1194be29085fa73e2d6dab40/library/std/src/panicking.rs:661:5
10: 0x75acdc3ec303 - core::panicking::panic_fmt::h7b0423324cb11cf8
at /rustc/ed7e35f3494045fa1194be29085fa73e2d6dab40/library/core/src/panicking.rs:74:14
11: 0x75acd917d5e3 - rustc_query_system[3ac4999a83714bb4]::query::plumbing::incremental_verify_ich_failed::<rustc_middle[e6bbbe3c9c9cddcc]::ty::context::TyCtxt>
12: 0x75acda31bfe5 - rustc_query_system[3ac4999a83714bb4]::query::plumbing::try_execute_query::<rustc_query_impl[8e795424f1957fa1]::DynamicConfig<rustc_query_system[3ac4999a83714bb4]::query::caches::DefaultCache<rustc_type_ir[448e3dbafdcfce29]::canonical::Canonical<rustc_middle[e6bbbe3c9c9cddcc]::ty::context::TyCtxt, rustc_middle[e6bbbe3c9c9cddcc]::ty::ParamEnvAnd<rustc_middle[e6bbbe3c9c9cddcc]::ty::predicate::Predicate>>, rustc_middle[e6bbbe3c9c9cddcc]::query::erase::Erased<[u8; 2usize]>>, false, false, false>, rustc_query_impl[8e795424f1957fa1]::plumbing::QueryCtxt, true>
13: 0x75acda31a3e2 - rustc_query_impl[8e795424f1957fa1]::query_impl::evaluate_obligation::get_query_incr::__rust_end_short_backtrace
14: 0x75acd6ef4ea6 - <rustc_infer[b80a5de4f24bd35e]::infer::InferCtxt as rustc_trait_selection[b34b4ff8df5539e2]::traits::query::evaluate_obligation::InferCtxtExt>::evaluate_obligation_no_overflow
15: 0x75acda59fc40 - rustc_ty_utils[11fbd16e0b4df61]::common_traits::is_item_raw
16: 0x75acda59f67d - rustc_query_impl[8e795424f1957fa1]::plumbing::__rust_begin_short_backtrace::<rustc_query_impl[8e795424f1957fa1]::query_impl::is_unpin_raw::dynamic_query::{closure#2}::{closure#0}, rustc_middle[e6bbbe3c9c9cddcc]::query::erase::Erased<[u8; 1usize]>>
17: 0x75acda5ad1c0 - rustc_query_system[3ac4999a83714bb4]::query::plumbing::try_execute_query::<rustc_query_impl[8e795424f1957fa1]::DynamicConfig<rustc_query_system[3ac4999a83714bb4]::query::caches::DefaultCache<rustc_middle[e6bbbe3c9c9cddcc]::ty::ParamEnvAnd<rustc_middle[e6bbbe3c9c9cddcc]::ty::Ty>, rustc_middle[e6bbbe3c9c9cddcc]::query::erase::Erased<[u8; 1usize]>>, false, false, false>, rustc_query_impl[8e795424f1957fa1]::plumbing::QueryCtxt, true>
18: 0x75acda4bfd9f - rustc_query_impl[8e795424f1957fa1]::query_impl::is_unpin_raw::get_query_incr::__rust_end_short_backtrace
19: 0x75acda4bf75e - rustc_middle[e6bbbe3c9c9cddcc]::query::plumbing::query_get_at::<rustc_query_system[3ac4999a83714bb4]::query::caches::DefaultCache<rustc_middle[e6bbbe3c9c9cddcc]::ty::ParamEnvAnd<rustc_middle[e6bbbe3c9c9cddcc]::ty::Ty>, rustc_middle[e6bbbe3c9c9cddcc]::query::erase::Erased<[u8; 1usize]>>>
20: 0x75acda4c02b3 - <core[cea19c0b7b712f1b]::iter::adapters::enumerate::Enumerate<_> as core[cea19c0b7b712f1b]::iter::traits::iterator::Iterator>::try_fold::enumerate::<rustc_middle[e6bbbe3c9c9cddcc]::ty::Ty, (), core[cea19c0b7b712f1b]::ops::control_flow::ControlFlow<core[cea19c0b7b712f1b]::ops::control_flow::ControlFlow<rustc_target[96ac51005291ebe8]::abi::call::ArgAbi<rustc_middle[e6bbbe3c9c9cddcc]::ty::Ty>>>, core[cea19c0b7b712f1b]::iter::adapters::map::map_try_fold<(usize, rustc_middle[e6bbbe3c9c9cddcc]::ty::Ty), core[cea19c0b7b712f1b]::result::Result<rustc_target[96ac51005291ebe8]::abi::call::ArgAbi<rustc_middle[e6bbbe3c9c9cddcc]::ty::Ty>, &rustc_middle[e6bbbe3c9c9cddcc]::ty::layout::FnAbiError>, (), core[cea19c0b7b712f1b]::ops::control_flow::ControlFlow<core[cea19c0b7b712f1b]::ops::control_flow::ControlFlow<rustc_target[96ac51005291ebe8]::abi::call::ArgAbi<rustc_middle[e6bbbe3c9c9cddcc]::ty::Ty>>>, rustc_ty_utils[11fbd16e0b4df61]::abi::fn_abi_new_uncached::{closure#1}, <core[cea19c0b7b712f1b]::iter::adapters::GenericShunt<core[cea19c0b7b712f1b]::iter::adapters::map::Map<core[cea19c0b7b712f1b]::iter::adapters::enumerate::Enumerate<core[cea19c0b7b712f1b]::iter::adapters::chain::Chain<core[cea19c0b7b712f1b]::iter::adapters::chain::Chain<core[cea19c0b7b712f1b]::iter::adapters::copied::Copied<core[cea19c0b7b712f1b]::slice::iter::Iter<rustc_middle[e6bbbe3c9c9cddcc]::ty::Ty>>, core[cea19c0b7b712f1b]::iter::adapters::copied::Copied<core[cea19c0b7b712f1b]::slice::iter::Iter<rustc_middle[e6bbbe3c9c9cddcc]::ty::Ty>>>, core[cea19c0b7b712f1b]::option::IntoIter<rustc_middle[e6bbbe3c9c9cddcc]::ty::Ty>>>, rustc_ty_utils[11fbd16e0b4df61]::abi::fn_abi_new_uncached::{closure#1}>, core[cea19c0b7b712f1b]::result::Result<core[cea19c0b7b712f1b]::convert::Infallible, &rustc_middle[e6bbbe3c9c9cddcc]::ty::layout::FnAbiError>> as core[cea19c0b7b712f1b]::iter::traits::iterator::Iterator>::try_fold<(), core[cea19c0b7b712f1b]::iter::traits::iterator::Iterator::try_for_each::call<rustc_target[96ac51005291ebe8]::abi::call::ArgAbi<rustc_middle[e6bbbe3c9c9cddcc]::ty::Ty>, core[cea19c0b7b712f1b]::ops::control_flow::ControlFlow<rustc_target[96ac51005291ebe8]::abi::call::ArgAbi<rustc_middle[e6bbbe3c9c9cddcc]::ty::Ty>>, core[cea19c0b7b712f1b]::ops::control_flow::ControlFlow<rustc_target[96ac51005291ebe8]::abi::call::ArgAbi<rustc_middle[e6bbbe3c9c9cddcc]::ty::Ty>>::Break>::{closure#0}, core[cea19c0b7b712f1b]::ops::control_flow::ControlFlow<rustc_target[96ac51005291ebe8]::abi::call::ArgAbi<rustc_middle[e6bbbe3c9c9cddcc]::ty::Ty>>>::{closure#0}>::{closure#0}>::{closure#0}
21: 0x75acda5c4943 - rustc_ty_utils[11fbd16e0b4df61]::abi::fn_abi_new_uncached
22: 0x75acda5bd7cc - rustc_ty_utils[11fbd16e0b4df61]::abi::fn_abi_of_instance
23: 0x75acda5bc0c9 - rustc_query_impl[8e795424f1957fa1]::plumbing::__rust_begin_short_backtrace::<rustc_query_impl[8e795424f1957fa1]::query_impl::fn_abi_of_instance::dynamic_query::{closure#2}::{closure#0}, rustc_middle[e6bbbe3c9c9cddcc]::query::erase::Erased<[u8; 16usize]>>
24: 0x75acda5b662f - <rustc_query_impl[8e795424f1957fa1]::query_impl::fn_abi_of_instance::dynamic_query::{closure#2} as core[cea19c0b7b712f1b]::ops::function::FnOnce<(rustc_middle[e6bbbe3c9c9cddcc]::ty::context::TyCtxt, rustc_middle[e6bbbe3c9c9cddcc]::ty::ParamEnvAnd<(rustc_middle[e6bbbe3c9c9cddcc]::ty::instance::Instance, &rustc_middle[e6bbbe3c9c9cddcc]::ty::list::RawList<(), rustc_middle[e6bbbe3c9c9cddcc]::ty::Ty>)>)>>::call_once
25: 0x75acda142daf - rustc_query_system[3ac4999a83714bb4]::query::plumbing::try_execute_query::<rustc_query_impl[8e795424f1957fa1]::DynamicConfig<rustc_query_system[3ac4999a83714bb4]::query::caches::DefaultCache<rustc_middle[e6bbbe3c9c9cddcc]::ty::ParamEnvAnd<(rustc_middle[e6bbbe3c9c9cddcc]::ty::instance::Instance, &rustc_middle[e6bbbe3c9c9cddcc]::ty::list::RawList<(), rustc_middle[e6bbbe3c9c9cddcc]::ty::Ty>)>, rustc_middle[e6bbbe3c9c9cddcc]::query::erase::Erased<[u8; 16usize]>>, false, false, false>, rustc_query_impl[8e795424f1957fa1]::plumbing::QueryCtxt, true>
26: 0x75acda1417a0 - rustc_query_impl[8e795424f1957fa1]::query_impl::fn_abi_of_instance::get_query_incr::__rust_end_short_backtrace
27: 0x75acda596b07 - rustc_middle[e6bbbe3c9c9cddcc]::query::plumbing::query_get_at::<rustc_query_system[3ac4999a83714bb4]::query::caches::DefaultCache<rustc_middle[e6bbbe3c9c9cddcc]::ty::ParamEnvAnd<(rustc_middle[e6bbbe3c9c9cddcc]::ty::instance::Instance, &rustc_middle[e6bbbe3c9c9cddcc]::ty::list::RawList<(), rustc_middle[e6bbbe3c9c9cddcc]::ty::Ty>)>, rustc_middle[e6bbbe3c9c9cddcc]::query::erase::Erased<[u8; 16usize]>>>
28: 0x75acda59776a - rustc_codegen_llvm[4c19696f2cfb3ba0]::callee::get_fn
29: 0x75acd6cbc825 - <rustc_codegen_ssa[45b564ea31421ae0]::mir::FunctionCx<rustc_codegen_llvm[4c19696f2cfb3ba0]::builder::Builder>>::codegen_terminator
30: 0x75acdac38ab3 - rustc_codegen_ssa[45b564ea31421ae0]::mir::codegen_mir::<rustc_codegen_llvm[4c19696f2cfb3ba0]::builder::Builder>
31: 0x75acdac2169f - rustc_codegen_llvm[4c19696f2cfb3ba0]::base::compile_codegen_unit::module_codegen
32: 0x75acdac1ed45 - <rustc_codegen_llvm[4c19696f2cfb3ba0]::LlvmCodegenBackend as rustc_codegen_ssa[45b564ea31421ae0]::traits::backend::ExtraBackendMethods>::compile_codegen_unit
33: 0x75acdadd6827 - <rustc_codegen_llvm[4c19696f2cfb3ba0]::LlvmCodegenBackend as rustc_codegen_ssa[45b564ea31421ae0]::traits::backend::CodegenBackend>::codegen_crate
34: 0x75acdad87398 - <rustc_interface[2e5cfc044300370]::queries::Linker>::codegen_and_build_linker
35: 0x75acdabf9776 - rustc_interface[2e5cfc044300370]::interface::run_compiler::<core[cea19c0b7b712f1b]::result::Result<(), rustc_span[d25ac71fb91ff2d1]::ErrorGuaranteed>, rustc_driver_impl[a6166d3ac53fd75e]::run_compiler::{closure#0}>::{closure#1}
36: 0x75acdabc08c9 - std[4da4969639bb92c1]::sys::backtrace::__rust_begin_short_backtrace::<rustc_interface[2e5cfc044300370]::util::run_in_thread_with_globals<rustc_interface[2e5cfc044300370]::util::run_in_thread_pool_with_globals<rustc_interface[2e5cfc044300370]::interface::run_compiler<core[cea19c0b7b712f1b]::result::Result<(), rustc_span[d25ac71fb91ff2d1]::ErrorGuaranteed>, rustc_driver_impl[a6166d3ac53fd75e]::run_compiler::{closure#0}>::{closure#1}, core[cea19c0b7b712f1b]::result::Result<(), rustc_span[d25ac71fb91ff2d1]::ErrorGuaranteed>>::{closure#0}, core[cea19c0b7b712f1b]::result::Result<(), rustc_span[d25ac71fb91ff2d1]::ErrorGuaranteed>>::{closure#0}::{closure#0}, core[cea19c0b7b712f1b]::result::Result<(), rustc_span[d25ac71fb91ff2d1]::ErrorGuaranteed>>
37: 0x75acdabc067a - <<std[4da4969639bb92c1]::thread::Builder>::spawn_unchecked_<rustc_interface[2e5cfc044300370]::util::run_in_thread_with_globals<rustc_interface[2e5cfc044300370]::util::run_in_thread_pool_with_globals<rustc_interface[2e5cfc044300370]::interface::run_compiler<core[cea19c0b7b712f1b]::result::Result<(), rustc_span[d25ac71fb91ff2d1]::ErrorGuaranteed>, rustc_driver_impl[a6166d3ac53fd75e]::run_compiler::{closure#0}>::{closure#1}, core[cea19c0b7b712f1b]::result::Result<(), rustc_span[d25ac71fb91ff2d1]::ErrorGuaranteed>>::{closure#0}, core[cea19c0b7b712f1b]::result::Result<(), rustc_span[d25ac71fb91ff2d1]::ErrorGuaranteed>>::{closure#0}::{closure#0}, core[cea19c0b7b712f1b]::result::Result<(), rustc_span[d25ac71fb91ff2d1]::ErrorGuaranteed>>::{closure#2} as core[cea19c0b7b712f1b]::ops::function::FnOnce<()>>::call_once::{shim:vtable#0}
38: 0x75acdc3ad4eb - <alloc::boxed::Box<F,A> as core::ops::function::FnOnce<Args>>::call_once::h6951d17da67feb24
at /rustc/ed7e35f3494045fa1194be29085fa73e2d6dab40/library/alloc/src/boxed.rs:2064:9
39: 0x75acdc3ad4eb - <alloc::boxed::Box<F,A> as core::ops::function::FnOnce<Args>>::call_once::he417e7ee10089c9d
at /rustc/ed7e35f3494045fa1194be29085fa73e2d6dab40/library/alloc/src/boxed.rs:2064:9
40: 0x75acdc3ad4eb - std::sys::pal::unix::thread::Thread::new::thread_start::he7841f7a2b8c4bc9
at /rustc/ed7e35f3494045fa1194be29085fa73e2d6dab40/library/std/src/sys/pal/unix/thread.rs:108:17
41: 0x75acd52a6ded - <unknown>
42: 0x75acd532a0dc - <unknown>
43: 0x0 - <unknown>
rustc version: 1.81.0-nightly (ed7e35f34 2024-07-06)
platform: x86_64-unknown-linux-gnu
query stack during panic:
#0 [evaluate_obligation] evaluating trait selection obligation `tree::SemaBranch: core::marker::Unpin`
#1 [is_unpin_raw] computing whether `tree::SemaBranch` is `Unpin`
#2 [fn_abi_of_instance] computing call ABI of `core::ptr::drop_in_place::<tree::SemaBranch> - shim(Some(tree::SemaBranch))`
end of query stack

View file

@ -29,6 +29,7 @@ description = "a place on the Internet I like to call home"
"rokugo/repo" = "https://github.com/rokugo-lang/rokugo"
"mica/repo" = "https://github.com/mica-lang/mica"
"planet_overgamma/repo" = "https://github.com/liquidev/planet-overgamma"
"rkgk/repo" = "https://github.com/liquidev/rkgk"
# Blog posts I like to reference
"article/function_coloring" = "https://journal.stuffwithstuff.com/2015/02/01/what-color-is-your-function/"