kivikakk.ee

tranquilidad

Esta semana nos mudamos de casa. Todo se ha vuelto silencioso y bonito. Estamos rodeadas de pájaros y árboles y el hermoso gran cielo. Tenemos espacio y seguridad. Fue mucho trabajo y aún queda mucho por hacer, pero por fin, por la primera vez en no sé cuánto tiempo, yo siento paz.

you should hand-write a parser

I found myself adding attribute support to Comrak on the weekend. Attributes in Markdown (as they’re often supported; there’s nothing for them in CommonMark) look like this:

Henlo [world](https://agave.syrup){.delicious}

This would, in most dialects that support it, attach a “class” attribute called delicious, which, in most dialects that then render these to HTML, would produce something like:

Henlo <a href="https://agave.syrup" class="delicious">world</a>

You can also attach IDs with #bweh and arbitrary key-pairs with wan=nyan, not to mention double-quote and escaping support, leading to {#monstrosities .somewhat like=this}. But y’know, it’s fine, and it’s even helpful for attaching things like height and width to images without having to resort to HTML. GitLab happens to use them for this purpose, though it implements it with a Very Special Post-Processing Step.

So how do we parse these fun strings? Ignoring the actual situating-them-in-the-document bit — assume we get to write a function that gets called whenever a { is found in a place where it might be an attribute (like right after a link or image, say), and it’s given what follows, and we need to (a) decide if it’s actually an attribute, and if so, (b) return the structured data from it, and how long that was (so the parent parser knows how much to skip).

I don’t know what your first impulse is, but it should probably be to just hand-write a state machine driven parser. Here’s mine! Consider reading the parse function closely enough that it makes sense to you, if your impulse is to reach for a regular expression, lexer/parser generator, or otherwise.

Some notes:

  • Locally defined state enums are fun!
  • Other than the result (which is random-write, as attributes may arrive in any order), all state is stored in the state enum member; this is why Value embeds Kind and Quote, and why Kind embeds Pair. I have to shuffle these around in some states, but it’s cleaner than having more function locals which are effectively “globals” that only apply sometimes, and which you can forget to reinit.
  • We use str::char_indices because the input is Unicode, we really do prefer to enumerate characters and not bytes, but also we return the (byte) index to the parent parser so it knows how many to skip (since a character count would necessitate O(n) through the string, thank’s UTF-8). Note we fearlessly calculate things like i + 1 when we know the character under i is single-byte (i.e. '}').
  • None is returned if the input didn’t have a valid attribute set as a prefix, i.e. any parse error whatsoever; we take advantage of this to call ci.next()?, since it’ll bubble None up if we hit the end of the string without the parse finishing.
  • The double loop is a side-effect of the particular way I’ve chosen to deal with a certain property of many state machines, including this one: most state/character combinations result in a break (which means we repeat the outer loop and go to the next character), but some, like seeing a whitespace or } while reading a value, instead change the state back to some parent one (because we know the value has ended), and then let the new state handle the specifics of the character.
    • There’s more than one way to handle this kind of thing, where you want to transition to a state and let it handle that same character too, this was just least-effort. Some setups mean you’ll be able to only ‘consume’ the input token in branches that you want to (i.e. everywhere I break), others let you “unput” it back onto the input so it gets read again next loop, you can be creative. :)

    • Concretely, to make our one cleaner, you could DIY unput with something like:

      // ...
      let mut unput: Option<(usize, char)> = None;
      loop {
      let (i, c) = unput.take().or_else(|| ci.next())?;
      // ...

      Then you can drop the inner loop, replace each point that doesn’t break with unput = Some((i, c));, and remove all the breaks! PRs welcome :D

That’s basically all I have to mention. Hand-write a parser! You can do really silly things like the function that follows and even do a partial parse backwards, if your input syntax is sympathetic to it :)

pintado

Ever-declining sleep quality lately has blessed me with a preponderance of dreams turning lucid; most recently, I was lying on a grassy, featureless field, and decided I wanted to see a magpie. I painted one on the ground in front of me, with big brush strokes of my mind, and it started to sing; I could reach out and just barely touch it, feeling it light on the back of my fingers like aether. As I continued to paint, my intention settled that it should be younger, and its song turned into a baby’s meeps.

degeneration

Some months back, I stopped being able to sleep (or comfortably lie at all) on my left side. Like most of the slowly encroaching issues of my body since getting COVID, something circulation-related happened, this time in my shoulder, and lying on it causes my heart to beat hard and it’s impossible to sleep, or feel like it’s a good idea remaining in that position at all. I keep trying it, just in case, and it doesn’t get better. This is the same shoulder that gave me a whole lot of pain last year, so, I dunno, guess it’s related.

This was disquieting but practically speaking not a huge deal; I’m a side sleeper but don’t prefer sleeping on my left side anyway, and I still had lying on my back to switch between.

A week ago, though, the same problem started on my right side. Like I don’t have enough trouble sleeping.

self-affirmations in the time of ai (c/o ikea)

Photo from the plant section of an IKEA. There’s a sign above a part of the section which says: “We are real.”, with smaller text: “Please be gentle with us.”